#include "ObjLoader.h" #include #include #include #include #include #include #include #include #include /** * @brief 构造函数 * 初始化3D场景相关成员变量,启动动画定时器 * @param parent 父对象 */ ObjLoader::ObjLoader(QObject *parent) : QObject(parent), m_3dView(nullptr), m_viewContainer(nullptr), m_rootEntity(nullptr), m_mainCamera(nullptr), m_cameraCtrl(nullptr), m_wholeModelEntity(nullptr), m_wholeModelMesh(nullptr), m_wholeModelTrans(nullptr), m_wholeModelCenter(QVector3D(0, 0, 0)), m_wholeModelRadius(0.0f), m_currentMaterial(""), m_lastError("") { m_animTimer = new QTimer(this); m_animTimer->setInterval(30); // 约33fps的动画刷新频率 m_animTimer->start(); } /** * @brief 析构函数 * 释放所有动态分配的3D实体、变换组件和资源,避免内存泄漏 */ ObjLoader::~ObjLoader() { // 1. 先销毁所有子模型(避免父模型先销毁导致子模型悬空) QVector childDeviceNames; for (const auto& device : m_deviceMap) { if (!device.parentDeviceName.isEmpty()) { childDeviceNames.append(device.name); } } for (const auto& childName : childDeviceNames) { auto it = m_deviceMap.find(childName); if (it != m_deviceMap.end()) { delete it->modelTransform; delete it->transform; delete it->modelEntity; delete it->entity; m_deviceMap.erase(it); } } // 2. 再销毁父模型 for (auto &device : m_deviceMap) { delete device.modelTransform; delete device.transform; delete device.modelEntity; delete device.entity; } m_deviceMap.clear(); // 3. 清理场景资源 delete m_wholeModelEntity; delete m_rootEntity; delete m_3dView; } /** * @brief ObjLoader::setEnDebug 是否启用qDebug打印信息 * @param En True=启用,False=禁用; */ void ObjLoader::setEnDebug(bool En){ EnDebug = En; } /** * @brief 获取相机设备 * @return 当前创建的相机设备指针 */ Qt3DRender::QCamera* ObjLoader::getCamera(){ return m_cameraEntity; } /** * @brief 获取器件指针 * 从器件映射表中查找指定名称的器件 * @param deviceName 器件名称 * @return 找到返回Device指针,否则返回nullptr并设置错误信息 */ Device* ObjLoader::getDevice(const QString &deviceName) { if (m_deviceMap.contains(deviceName)) { return &m_deviceMap[deviceName]; } m_lastError = "未找到器件: " + deviceName; return nullptr; } /** * @brief 设置相机控制速度 * @param lookSpeed 旋转速度 * @param linearSpeed 移动速度 */ void ObjLoader::setCameraSpeed(float lookSpeed, float linearSpeed) { if (m_cameraCtrl) { m_cameraCtrl->setLookSpeed(lookSpeed); m_cameraCtrl->setLinearSpeed(linearSpeed); } } /** * @brief 重置相机到默认位置 * 相机位置设置为模型中心沿Z轴偏移3倍半径的位置 */ void ObjLoader::resetCamera() { if (m_mainCamera && m_wholeModelRadius > 0) { QVector3D cameraPos = m_wholeModelCenter + QVector3D(0, 0, m_wholeModelRadius * 3.0f); m_mainCamera->setPosition(cameraPos); m_mainCamera->setViewCenter(QVector3D(0, 0, 0)); } } /** * @brief 字符串分割工具函数 * 将输入字符串按指定分隔符分割,忽略空元素 * @param s 待分割的字符串 * @param sep 分隔符(默认空格) * @return 分割后的字符串列表 */ QVector ObjLoader::splitStr(const QString &s, const QString &sep) { return s.split(sep, Qt::SkipEmptyParts).toVector(); } /** * @brief 初始化3D场景 * 创建3D窗口、根实体、相机、光源和控制器,设置透明背景 * @param containerWidget 用于显示3D场景的窗口容器 */ void ObjLoader::init3DScene(QWidget *containerWidget) { if (!containerWidget) { m_lastError = "无效的3D容器"; return; } if (EnDebug) qDebug() << "3D组件初始化" << "containerWidget" << containerWidget; m_viewContainer = containerWidget; // if (EnDebug) qDebug() << "3D组件初始化" << "基础容器透明设置"; // // 1. 基础容器透明设置(画框透明) // m_viewContainer->setAttribute(Qt::WA_TranslucentBackground); // m_viewContainer->setStyleSheet("background: transparent; border: none;"); // if (EnDebug) qDebug() << "3D组件初始化" << "配置OpenGL格式(必须启用Alpha通道以支持透明)"; // // 2. 配置OpenGL格式(必须启用Alpha通道以支持透明) // QSurfaceFormat format = QSurfaceFormat::defaultFormat(); // format.setAlphaBufferSize(8); // 启用Alpha通道(透明的核心) // format.setRenderableType(QSurfaceFormat::OpenGL); // format.setVersion(3, 3); // format.setProfile(QSurfaceFormat::CompatibilityProfile); // QSurfaceFormat::setDefaultFormat(format); if (EnDebug) qDebug() << "3D组件初始化" << "创建3D窗口并设置透明背景"; // 3. 创建3D窗口并设置透明背景 m_3dView = new Qt3DExtras::Qt3DWindow(); // m_3dView->setFormat(format); if (EnDebug) qDebug() << "3D组件初始化" << "核心:3D场景背景透明(使用Qt3DExtras的QForwardRenderer)"; // 核心:3D场景背景透明(使用Qt3DExtras的QForwardRenderer) Qt3DExtras::QForwardRenderer *frameGraph = m_3dView->defaultFrameGraph(); if (frameGraph) { frameGraph->setClearColor(QColor(7, 19, 35)); // 全透明背景 //frameGraph->setClearColor(QColor(7, 19, 255)); // 全透明背景 } if (EnDebug) qDebug() << "3D组件初始化" << "创建窗口容器(确保透明)"; // 4. 创建窗口容器(确保透明) QWidget *container3D = QWidget::createWindowContainer(m_3dView, m_viewContainer); // container3D->setAttribute(Qt::WA_TranslucentBackground); // container3D->setStyleSheet("background: transparent !important;"); // 强制透明 // container3D->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); if (EnDebug) qDebug() << "3D组件初始化" << "布局设置"; // 5. 布局设置 QVBoxLayout *layout = new QVBoxLayout(m_viewContainer); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(container3D); m_viewContainer->setLayout(layout); if (EnDebug) qDebug() << "3D组件初始化" << "3D场景核心组件(确保模型显示)"; // 6. 3D场景核心组件(确保模型显示) m_rootEntity = new Qt3DCore::QEntity(); m_3dView->setRootEntity(m_rootEntity); if (EnDebug) qDebug() << "3D组件初始化" << "相机设置(确保能看到物体)"; // 7. 相机设置(确保能看到物体) m_cameraEntity = m_3dView->camera(); m_cameraEntity->setPosition(QVector3D(0, 0, 10)); // 相机初始位置(Z轴10单位处) m_cameraEntity->setViewCenter(QVector3D(0, 0, 0)); // 看向原点 // 添加相机信息变化监听(关键修改) if (EnDebug) { connect(m_cameraEntity, &Qt3DRender::QCamera::positionChanged, this, &ObjLoader::onCameraChanged); connect(m_cameraEntity, &Qt3DRender::QCamera::viewCenterChanged, this, &ObjLoader::onCameraChanged); connect(m_cameraEntity->lens(), &Qt3DRender::QCameraLens::fieldOfViewChanged, this, &ObjLoader::onCameraChanged); } if (EnDebug) qDebug() << "3D组件初始化" << "相机控制器(可拖动旋转查看)"; // 8. 相机控制器(可拖动旋转查看) Qt3DExtras::QOrbitCameraController *controller = new Qt3DExtras::QOrbitCameraController(m_rootEntity); controller->setCamera(m_cameraEntity); if (EnDebug) qDebug() << "3D组件初始化" << "光源(确保模型可见)"; // 9. 光源(确保模型可见) Qt3DCore::QEntity *lightEntity = new Qt3DCore::QEntity(m_cameraEntity); light = new Qt3DRender::QDirectionalLight(lightEntity); light->setColor(Qt::white); light->setIntensity(1.5f); // 光源强度 lightEntity->addComponent(light); Qt3DCore::QTransform *lightTransform = new Qt3DCore::QTransform(lightEntity); lightTransform->setTranslation(m_cameraEntity->position()); // 光源位置与相机一致 lightEntity->addComponent(lightTransform); } /** * @brief 初始化3D场景 * 创建3D窗口、根实体、相机、光源和控制器,支持透明背景、场景占满容器、尺寸同步 * @param containerWidget 用于显示3D场景的窗口容器(如QFrame) * @param enableTransparent 是否启用场景背景透明(true=透明,false=纯色背景) * @return 初始化成功返回true,失败返回false(可通过lastError()获取错误信息) */ bool ObjLoader::init3DScene(QWidget *containerWidget, bool enableTransparent) { // 1. 基础校验与初始化 if (!containerWidget) { m_lastError = "3D场景初始化失败:容器Widget为空"; if (EnDebug) qCritical() << m_lastError; return false; } m_viewContainer = containerWidget; const QString containerName = m_viewContainer->objectName(); if (EnDebug) qDebug() << "[3D初始化] 目标容器:" << containerName << ",初始尺寸:" << m_viewContainer->size(); // 2. 强制解除父容器的尺寸约束(核心!解决Frame默认尺寸限制) m_viewContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 允许拉伸 m_viewContainer->setMinimumSize(0, 0); // 取消最小尺寸限制(避免Frame默认固定大小) m_viewContainer->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); // 取消最大尺寸限制 m_viewContainer->setContentsMargins(0, 0, 0, 0); // 容器自身无内边距 // 强制父容器立即刷新尺寸(避免UI缓存导致的延迟) m_viewContainer->updateGeometry(); m_viewContainer->repaint(); // 3. 透明模式配置(按需启用) if (enableTransparent) { m_viewContainer->setAttribute(Qt::WA_TranslucentBackground, true); m_viewContainer->setStyleSheet("QFrame{background: transparent; border: none; padding: 0px; margin: 0px;}"); } else { m_viewContainer->setStyleSheet("QFrame{background: #071323; border: none; padding: 0px; margin: 0px;}"); } // 4. OpenGL格式配置(确保渲染兼容性) QSurfaceFormat glFormat = QSurfaceFormat::defaultFormat(); glFormat.setRenderableType(QSurfaceFormat::OpenGL); glFormat.setVersion(3, 3); glFormat.setProfile(QSurfaceFormat::CompatibilityProfile); if (enableTransparent) { glFormat.setAlphaBufferSize(8); glFormat.setDepthBufferSize(24); } QSurfaceFormat::setDefaultFormat(glFormat); // 5. 创建3D窗口与渲染容器(强制占满) m_3dView = new Qt3DExtras::Qt3DWindow(); m_3dView->setFormat(glFormat); // 配置3D场景背景 Qt3DExtras::QForwardRenderer *frameGraph = m_3dView->defaultFrameGraph(); if (!frameGraph) { m_lastError = "3D场景初始化失败:获取帧图失败"; delete m_3dView; return false; } frameGraph->setClearColor(enableTransparent ? QColor(0,0,0,0) : QColor(7,19,35)); // 6. 3D容器:强制拉伸+无间隙(关键!) QWidget *container3D = QWidget::createWindowContainer(m_3dView, m_viewContainer); container3D->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 最高拉伸优先级 container3D->setContentsMargins(0, 0, 0, 0); // 容器无内边距 container3D->setStyleSheet("background: transparent; padding: 0px; margin: 0px;"); // 强制3D容器初始尺寸与父容器完全一致(避免初始空白) container3D->resize(m_viewContainer->size()); if (EnDebug) qDebug() << "[3D初始化] 3D容器初始尺寸:" << container3D->size(); // 7. 布局:锁定3D容器为唯一子组件(无任何间隙) // 先清除父容器原有布局(避免Qt Designer残留布局冲突) QLayout *oldLayout = m_viewContainer->layout(); if (oldLayout) { oldLayout->deleteLater(); // 删除旧布局 if (EnDebug) qDebug() << "[3D初始化] 已清除容器原有布局"; } // 创建新布局并锁定3D容器 QVBoxLayout *sceneLayout = new QVBoxLayout(m_viewContainer); sceneLayout->setContentsMargins(0, 0, 0, 0); // 布局无内边距 sceneLayout->setSpacing(0); // 布局无间距 sceneLayout->setSizeConstraint(QLayout::SetFixedSize); // 布局尺寸跟随父容器 sceneLayout->addWidget(container3D); // 强制布局立即生效 sceneLayout->activate(); m_viewContainer->setLayout(sceneLayout); // 8. 3D场景核心组件(根实体+相机+光源+控制器) m_rootEntity = new Qt3DCore::QEntity(); m_3dView->setRootEntity(m_rootEntity); // 7. 相机设置(确保能看到物体) m_cameraEntity = m_3dView->camera(); m_cameraEntity->setPosition(QVector3D(0, 0, 10)); // 相机初始位置(Z轴10单位处) m_cameraEntity->setViewCenter(QVector3D(0, 0, 0)); // 看向原点 // 添加相机信息变化监听(关键修改) if (EnDebug) { connect(m_cameraEntity, &Qt3DRender::QCamera::positionChanged, this, &ObjLoader::onCameraChanged); connect(m_cameraEntity, &Qt3DRender::QCamera::viewCenterChanged, this, &ObjLoader::onCameraChanged); connect(m_cameraEntity->lens(), &Qt3DRender::QCameraLens::fieldOfViewChanged, this, &ObjLoader::onCameraChanged); } if (EnDebug) qDebug() << "3D组件初始化" << "相机控制器(可拖动旋转查看)"; // 8. 相机控制器(可拖动旋转查看) Qt3DExtras::QOrbitCameraController *controller = new Qt3DExtras::QOrbitCameraController(m_rootEntity); controller->setCamera(m_cameraEntity); // 光源:确保模型可见 Qt3DCore::QEntity *lightEntity = new Qt3DCore::QEntity(m_rootEntity); light = new Qt3DRender::QDirectionalLight(lightEntity); light->setColor(Qt::white); light->setIntensity(2.0f); light->setWorldDirection(QVector3D(-1, -2, -3).normalized()); lightEntity->addComponent(light); Qt3DCore::QTransform *lightTransform = new Qt3DCore::QTransform(lightEntity); lightTransform->setTranslation(QVector3D(10, 20, 30)); lightEntity->addComponent(lightTransform); QTimer::singleShot(10, this, [this, containerPtr = m_viewContainer]() { if (!containerPtr || !containerPtr->layout()) return; QWidget *container3D = containerPtr->layout()->itemAt(0)->widget(); if (!container3D) return; // 1. 实时获取父容器的初始有效尺寸(排除0x0) QSize parentSize = containerPtr->contentsRect().size(); QSize validInitialSize = (parentSize.width() > 0 && parentSize.height() > 0) ? parentSize : QSize(420, 230); // 兜底尺寸(匹配日志初始值) // 2. 实时获取父容器当前尺寸(避免被外部缩小) QSize currentParentSize = containerPtr->contentsRect().size(); QSize validCurrentSize = (currentParentSize.width() > 0 && currentParentSize.height() > 0) ? currentParentSize : validInitialSize; // 3. 最终尺寸:取“有效初始尺寸”和“当前有效尺寸”的最大值(禁止缩小) QSize finalSize = validCurrentSize.expandedTo(validInitialSize); container3D->resize(finalSize); // 4. 同步相机宽高比(避免模型拉伸) if (m_cameraEntity && finalSize.width() > 0 && finalSize.height() > 0) { m_cameraEntity->setAspectRatio(float(finalSize.width()) / float(finalSize.height())); } // 调试日志:明确各尺寸来源 if (EnDebug) { qDebug() << "[3D尺寸同步] 有效初始尺寸:" << validInitialSize << ",当前父容器尺寸:" << currentParentSize << ",最终锁定尺寸:" << finalSize; } }); return true; } // 预处理多个设备 void ObjLoader::preprocessDevices(const QStringList& deviceNames, const QString& basePath) { foreach (const QString& deviceName, deviceNames) { preprocessSingleDevice(deviceName, basePath); } } // 预处理单个设备 void ObjLoader::preprocessSingleDevice(const QString& deviceName, const QString& basePath) { QString filePath = basePath + deviceName + ".obj"; QFileInfo testFile(filePath); if (!testFile.exists()) { qCritical() << "预处理失败,文件不存在: " << filePath; return; } // 计算模型中心和半径(耗时操作) QVector3D modelCenter = parseObjCenter(filePath); float modelRadius = calculateModelRadiusFromFile(filePath); // 发送结果信号 emit preprocessDone(deviceName, modelCenter, modelRadius); } /** * @brief 加载器件(独立模型) * 创建双层实体结构(外层控制位置,内层控制旋转/缩放),解析并应用MTL材质 * @param deviceName 器件名称 * @param filePath OBJ文件路径 * @param position 初始位置 * @return 加载成功返回true */ bool ObjLoader::loadDevice(const QString &deviceName, const QString &filePath, const QVector3D &position) { if (m_deviceMap.contains(deviceName)) { m_lastError = "器件名称已存在: " + deviceName; return false; } if (!m_rootEntity) { m_lastError = "3D场景未初始化,请先调用init3DScene"; return false; } QFileInfo testFile(filePath); if(!testFile.exists()){ m_lastError = "文件不存在: " + filePath; if (EnDebug) qDebug() << filePath<<"设备 文件是否存在:" << testFile.exists() << ",绝对路径:" << testFile.absoluteFilePath(); return false; } // 1. 计算模型几何中心(用于预中心化,使旋转围绕模型自身中心) QVector3D modelCenter = parseObjCenter(filePath); float modelRadius = calculateModelRadiusFromFile(filePath); if (EnDebug) qDebug() << "模型几何中心: " << modelCenter << " 模型半径: " << modelRadius; // 2. 创建双层实体结构 Qt3DCore::QEntity *deviceEntity = new Qt3DCore::QEntity(m_rootEntity); // 外层实体 Qt3DCore::QTransform *deviceTransform = new Qt3DCore::QTransform(); deviceTransform->setTranslation(position); // 外层控制世界位置 deviceEntity->addComponent(deviceTransform); // 内层实体:控制旋转和缩放 Qt3DCore::QEntity *modelEntity = new Qt3DCore::QEntity(deviceEntity); Qt3DCore::QTransform *modelTransform = new Qt3DCore::QTransform(); modelTransform->setTranslation(-modelCenter); // 预中心化(将模型中心移至局部原点) modelEntity->addComponent(modelTransform); // 3. 加载模型网格 Qt3DRender::QMesh *deviceMesh = new Qt3DRender::QMesh(); deviceMesh->setSource(QUrl::fromLocalFile(filePath)); modelEntity->addComponent(deviceMesh); // 4. 加载并应用MTL材质 Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial(); material->setDiffuse(QColor(0, 255, 255,80)); // 默认白色 modelEntity->addComponent(material); // 5. 存储器件信息 Device device; device.name = deviceName; device.entity = deviceEntity; device.modelEntity = modelEntity; device.transform = deviceTransform; device.modelTransform = modelTransform; device.worldPosition = position; device.originalPosition = position; device.currentRotation = QVector3D(0, 0, 0); device.modelCenter = modelCenter; device.scale = 1.0f; device.isInitialized = true; m_deviceMap[deviceName] = device; return true; } /** * @brief 加载器件(独立模型) * 创建双层实体结构(外层控制位置,内层控制旋转/缩放),解析并应用MTL材质 * 加载完成后自动调整相机位置,确保模型可见 * @param deviceName 器件名称 * @param filePath OBJ文件路径 * @param position 初始位置 * @return 加载成功返回true */ bool ObjLoader::loadModel(const QString &deviceName, const QString &filePath, const QVector3D &position) { if (m_deviceMap.contains(deviceName)) { m_lastError = "器件名称已存在: " + deviceName; return false; } if (!m_rootEntity) { m_lastError = "3D场景未初始化,请先调用init3DScene"; return false; } QFileInfo testFile(filePath); if(!testFile.exists()){ m_lastError = "文件不存在: " + filePath; if (EnDebug) qDebug() << filePath<<"设备 文件是否存在:" << testFile.exists() << ",绝对路径:" << testFile.absoluteFilePath(); return false; } // 1. 计算模型几何中心(用于预中心化,使旋转围绕模型自身中心) QVector3D modelCenter = parseObjCenter(filePath); float modelRadius = calculateModelRadiusFromFile(filePath); if (EnDebug) qDebug() << "模型几何中心: " << modelCenter << " 模型半径: " << modelRadius; // 2. 创建双层实体结构 Qt3DCore::QEntity *deviceEntity = new Qt3DCore::QEntity(m_rootEntity); // 外层实体 Qt3DCore::QTransform *deviceTransform = new Qt3DCore::QTransform(); deviceTransform->setTranslation(position); // 外层控制世界位置 deviceEntity->addComponent(deviceTransform); // 内层实体:控制旋转和缩放 Qt3DCore::QEntity *modelEntity = new Qt3DCore::QEntity(deviceEntity); Qt3DCore::QTransform *modelTransform = new Qt3DCore::QTransform(); modelTransform->setTranslation(-modelCenter); // 预中心化(将模型中心移至局部原点) modelEntity->addComponent(modelTransform); // 3. 加载模型网格 Qt3DRender::QMesh *deviceMesh = new Qt3DRender::QMesh(); deviceMesh->setSource(QUrl::fromLocalFile(filePath)); modelEntity->addComponent(deviceMesh); // 4. 加载并应用MTL材质(关键:用UTF-8编码读取OBJ文件) Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial(); QFile objFile(filePath); if (objFile.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&objFile); // 关键修改:明确设置为UTF-8编码,匹配OBJ文件的实际编码 stream.setCodec("UTF-8"); QString mtlPath; QString useMtl; while (!stream.atEnd()) { QString line = stream.readLine().trimmed(); if (line.startsWith("mtllib")) { // 分割行内容(用正则分割,避免多空格/制表符问题) QStringList parts = line.split(QRegExp("\\s+"), Qt::SkipEmptyParts); if (parts.size() >= 2) { QString mtlFileName = parts[1]; // 此时会正确读取为“泵站电机.mtl”(无乱码) // 拼接MTL文件的绝对路径 QFileInfo objFileInfo(filePath); mtlPath = objFileInfo.absolutePath() + "/" + mtlFileName; // 调试输出:确认文件名和路径正确 if (EnDebug) { qDebug() << "解析到MTL文件名(UTF-8): " << mtlFileName; qDebug() << "MTL文件绝对路径: " << mtlPath; qDebug() << "MTL文件是否存在: " << QFile::exists(mtlPath); } // 解析MTL文件(此时路径正确,文件存在) if (QFile::exists(mtlPath)) { if (parseMtlFile(mtlPath)) { if (EnDebug) qDebug() << "MTL文件解析成功"; } else { if (EnDebug) qDebug() << "MTL文件解析失败,但继续加载模型"; } } else { m_lastError = "MTL文件不存在: " + mtlPath; if (EnDebug) qDebug() << m_lastError; } } } else if (line.startsWith("usemtl")) { QStringList parts = line.split(QRegExp("\\s+"), Qt::SkipEmptyParts); if (parts.size() >= 2) { useMtl = parts[1]; if (EnDebug) qDebug() << "解析到材质名称: " << useMtl; // 应正确读取为“color_00ffff” } } } objFile.close(); // 应用材质(此时m_materials中应有color_00ffff) if (m_materials.contains(useMtl)) { Material mtl = m_materials[useMtl]; material->setAmbient(mtl.ambient); material->setDiffuse(mtl.diffuse); material->setSpecular(mtl.specular); material->setShininess(mtl.shininess); // 设置透明度 QColor diffuse = mtl.diffuse; diffuse.setAlphaF(mtl.opacity); material->setDiffuse(diffuse); QColor ambient = mtl.ambient; ambient.setAlphaF(mtl.opacity); material->setAmbient(ambient); if (EnDebug) qDebug() << "应用材质: " << useMtl << " 到设备: " << deviceName; } else { if (EnDebug) qDebug() << "未找到材质: " << useMtl << ",使用默认材质"; // 保持默认材质为不透明红色,便于观察 material->setDiffuse(QColor(255, 0, 0, 255)); material->setAmbient(QColor(255, 0, 0, 255)); } } modelEntity->addComponent(material); // 5. 存储器件信息 Device device; device.name = deviceName; device.entity = deviceEntity; device.modelEntity = modelEntity; device.transform = deviceTransform; device.modelTransform = modelTransform; device.worldPosition = position; device.originalPosition = position; device.currentRotation = QVector3D(0, 0, 0); device.modelCenter = modelCenter; device.scale = 1.0f; device.isInitialized = true; m_deviceMap[deviceName] = device; return true; } /** * @brief 加载器件(独立模型) * 创建双层实体结构(外层控制位置,内层控制旋转/缩放),解析并应用MTL材质 * 加载完成后自动调整相机位置,确保模型可见 * @param deviceName 器件名称 * @param filePath OBJ文件路径 * @param position 初始位置 * @return 加载成功返回true */ bool ObjLoader::loadModelAsync(const QString &deviceName, const QString &filePath, const QVector3D &position, QVector3D &modelCenter, float modelRadius) { if (m_deviceMap.contains(deviceName)) { m_lastError = "器件名称已存在: " + deviceName; return false; } if (!m_rootEntity) { m_lastError = "3D场景未初始化,请先调用init3DScene"; return false; } QFileInfo testFile(filePath); if(!testFile.exists()){ m_lastError = "文件不存在: " + filePath; if (EnDebug) qDebug() << filePath<<"设备 文件是否存在:" << testFile.exists() << ",绝对路径:" << testFile.absoluteFilePath(); return false; } // 1. 计算模型几何中心(用于预中心化,使旋转围绕模型自身中心) modelCenter = parseObjCenter(filePath); modelRadius = calculateModelRadiusFromFile(filePath); if (EnDebug) qDebug() << "模型几何中心: " << modelCenter << " 模型半径: " << modelRadius; // 2. 创建双层实体结构 Qt3DCore::QEntity *deviceEntity = new Qt3DCore::QEntity(m_rootEntity); // 外层实体 Qt3DCore::QTransform *deviceTransform = new Qt3DCore::QTransform(); deviceTransform->setTranslation(position); // 外层控制世界位置 deviceEntity->addComponent(deviceTransform); // 内层实体:控制旋转和缩放 Qt3DCore::QEntity *modelEntity = new Qt3DCore::QEntity(deviceEntity); Qt3DCore::QTransform *modelTransform = new Qt3DCore::QTransform(); modelTransform->setTranslation(-modelCenter); // 预中心化(将模型中心移至局部原点) modelEntity->addComponent(modelTransform); // 3. 加载模型网格 Qt3DRender::QMesh *deviceMesh = new Qt3DRender::QMesh(); deviceMesh->setSource(QUrl::fromLocalFile(filePath)); modelEntity->addComponent(deviceMesh); // 4. 加载并应用MTL材质(关键:用UTF-8编码读取OBJ文件) Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial(); QFile objFile(filePath); if (objFile.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&objFile); // 关键修改:明确设置为UTF-8编码,匹配OBJ文件的实际编码 stream.setCodec("UTF-8"); QString mtlPath; QString useMtl; while (!stream.atEnd()) { QString line = stream.readLine().trimmed(); if (line.startsWith("mtllib")) { // 分割行内容(用正则分割,避免多空格/制表符问题) QStringList parts = line.split(QRegExp("\\s+"), Qt::SkipEmptyParts); if (parts.size() >= 2) { QString mtlFileName = parts[1]; // 此时会正确读取为“泵站电机.mtl”(无乱码) // 拼接MTL文件的绝对路径 QFileInfo objFileInfo(filePath); mtlPath = objFileInfo.absolutePath() + "/" + mtlFileName; // 调试输出:确认文件名和路径正确 if (EnDebug) { qDebug() << "解析到MTL文件名(UTF-8): " << mtlFileName; qDebug() << "MTL文件绝对路径: " << mtlPath; qDebug() << "MTL文件是否存在: " << QFile::exists(mtlPath); } // 解析MTL文件(此时路径正确,文件存在) if (QFile::exists(mtlPath)) { if (parseMtlFile(mtlPath)) { if (EnDebug) qDebug() << "MTL文件解析成功"; } else { if (EnDebug) qDebug() << "MTL文件解析失败,但继续加载模型"; } } else { m_lastError = "MTL文件不存在: " + mtlPath; if (EnDebug) qDebug() << m_lastError; } } } else if (line.startsWith("usemtl")) { QStringList parts = line.split(QRegExp("\\s+"), Qt::SkipEmptyParts); if (parts.size() >= 2) { useMtl = parts[1]; if (EnDebug) qDebug() << "解析到材质名称: " << useMtl; // 应正确读取为“color_00ffff” } } } objFile.close(); // 应用材质(此时m_materials中应有color_00ffff) if (m_materials.contains(useMtl)) { Material mtl = m_materials[useMtl]; material->setAmbient(mtl.ambient); material->setDiffuse(mtl.diffuse); material->setSpecular(mtl.specular); material->setShininess(mtl.shininess); // 设置透明度 QColor diffuse = mtl.diffuse; diffuse.setAlphaF(mtl.opacity); material->setDiffuse(diffuse); QColor ambient = mtl.ambient; ambient.setAlphaF(mtl.opacity); material->setAmbient(ambient); if (EnDebug) qDebug() << "应用材质: " << useMtl << " 到设备: " << deviceName; } else { if (EnDebug) qDebug() << "未找到材质: " << useMtl << ",使用默认材质"; // 保持默认材质为不透明红色,便于观察 material->setDiffuse(QColor(255, 0, 0, 255)); material->setAmbient(QColor(255, 0, 0, 255)); } } modelEntity->addComponent(material); // 5. 存储器件信息 Device device; device.name = deviceName; device.entity = deviceEntity; device.modelEntity = modelEntity; device.transform = deviceTransform; device.modelTransform = modelTransform; device.worldPosition = position; device.originalPosition = position; device.currentRotation = QVector3D(0, 0, 0); device.modelCenter = modelCenter; device.scale = 1.0f; device.isInitialized = true; m_deviceMap[deviceName] = device; return true; } /** * @brief 从OBJ文件计算模型的包围球半径 * 解析OBJ文件中的顶点数据,计算模型几何中心到最远顶点的距离,作为模型的包围球半径 * 该半径用于确定相机与模型的合适距离,确保模型能完整显示在视野中 * @param filePath OBJ模型文件的路径 * @return 模型包围球半径(单位与模型顶点坐标一致),若文件解析失败则返回1.0f默认值 */ float ObjLoader::calculateModelRadiusFromFile(const QString &filePath) { QVector vertices; QFile objFile(filePath); if (!objFile.open(QIODevice::ReadOnly | QIODevice::Text)) { m_lastError = "无法打开OBJ文件计算半径: " + filePath; return 1.0f; // 默认半径 } QTextStream fileStream(&objFile); while (!fileStream.atEnd()) { QString line = fileStream.readLine().trimmed(); if (line.isEmpty() || line.startsWith('#')) continue; QVector lineParts = splitStr(line); if (lineParts.isEmpty()) continue; // 只解析顶点位置("v x y z"格式) if (lineParts[0] == "v" && lineParts.size() >= 4) { vertices.append(QVector3D( lineParts[1].toFloat(), lineParts[2].toFloat(), lineParts[3].toFloat() )); } } objFile.close(); // 如果没有顶点数据,返回默认半径 if (vertices.isEmpty()) return 1.0f; // 计算模型中心 QVector3D center = calculateBoundingBoxCenter(vertices); // 找到距离中心最远的顶点,计算半径 float maxDistance = 0.0f; for (const auto& v : vertices) { float distance = (v - center).length(); if (distance > maxDistance) { maxDistance = distance; } } return maxDistance; } /** * @brief 加载子器件(挂载到父器件下) * 确保子器件与父器件的初始相对位置正确,并继承父器件的变换 * @param childDeviceName 子器件名称 * @param filePath OBJ文件路径 * @param parentDeviceName 父器件名称 * @param offsetDir 相对父器件的偏移方向 * @param diffuse 材质 * @return 加载成功返回true */ bool ObjLoader::loadChildDevice( const QString &childDeviceName, const QString &filePath, const QString &parentDeviceName, const QVector3D &offsetDir, const QColor &diffuse ) { // 基础校验 if (m_deviceMap.contains(childDeviceName)) { m_lastError = "子设备名称已存在: " + childDeviceName; return false; } if (!m_rootEntity) { m_lastError = "3D场景未初始化"; return false; } // 获取父设备(摇臂) Device* parentDevice = getDevice(parentDeviceName); if (!parentDevice || !parentDevice->isInitialized) { m_lastError = "父设备不存在或未初始化: " + parentDeviceName; return false; } QFileInfo testFile(filePath); if(!testFile.exists()){ m_lastError = "文件不存在: " + parentDeviceName; if (EnDebug) qDebug() << childDeviceName<<"设备 文件是否存在:" << testFile.exists() << ",绝对路径:" << testFile.absoluteFilePath(); return false; } // 计算子设备(滚筒)的几何中心 QVector3D childModelCenter = parseObjCenter(filePath); // 精确计算相对偏移:确保父设备与子设备的几何中心相对位置正确 QVector3D modelCenter = parseObjCenter(filePath); //世界模型中心 QVector3D parentCenter = parentDevice->modelCenter; // 父设备几何中心 QVector3D installOffset = parentCenter + offsetDir - childModelCenter;// 模型几何中心 float modelRadius = calculateModelRadiusFromFile(filePath);//顶点 if (EnDebug) qDebug() << "模型几何中心: " << modelCenter << "父设备几何中心" <entity); Qt3DCore::QTransform* childTransform = new Qt3DCore::QTransform(); childTransform->setTranslation(installOffset); // 设置精确计算的偏移 childEntity->addComponent(childTransform); // 创建子设备模型实体(内层,处理旋转和缩放) Qt3DCore::QEntity* childModelEntity = new Qt3DCore::QEntity(childEntity); Qt3DCore::QTransform* childModelTrans = new Qt3DCore::QTransform(); childModelTrans->setTranslation(-childModelCenter); // 子模型预中心化 childModelEntity->addComponent(childModelTrans); // 加载网格和材质 Qt3DRender::QMesh* childMesh = new Qt3DRender::QMesh(); childMesh->setSource(QUrl::fromLocalFile(filePath)); childModelEntity->addComponent(childMesh); // 4. 加载并应用MTL材质 Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial(); material->setDiffuse(diffuse); // 默认白色 // // 加载MTL材质 // Qt3DExtras::QPhongMaterial* childMat = new Qt3DExtras::QPhongMaterial(); // QString mtlPath; // MTL文件路径(从OBJ中读取) // QString useMtlName; // 子设备使用的材质名称(从OBJ中读取) // QString objBaseDir = QFileInfo(filePath).path() + "/"; // OBJ文件所在目录 // // 解析OBJ文件,提取MTL路径和当前使用的材质名称 // QFile objFile(filePath); // if (objFile.open(QIODevice::ReadOnly | QIODevice::Text)) { // QTextStream objStream(&objFile); // while (!objStream.atEnd()) { // QString line = objStream.readLine().trimmed(); // if (line.isEmpty() || line.startsWith('#')) continue; // 跳过注释和空行 // QVector lineParts = splitStr(line); // if (lineParts.isEmpty()) continue; // QString cmd = lineParts[0]; // // 读取MTL文件路径(OBJ中的"mtllib"指令) // if (cmd == "mtllib" && lineParts.size() >= 2) { // mtlPath = objBaseDir + lineParts[1]; // 拼接MTL的绝对路径 // if (QFile::exists(mtlPath)) { // parseMtlFile(mtlPath); // 解析MTL文件,存入m_materials // if(EnDebug) qDebug() << "[子设备材质] 已解析MTL文件:" << mtlPath; // } else { // qWarning() << "[子设备材质] MTL文件不存在:" << mtlPath; // } // } // // 读取当前使用的材质名称(OBJ中的"usemtl"指令) // else if (cmd == "usemtl" && lineParts.size() >= 2) { // useMtlName = lineParts[1]; // 记录子设备要使用的材质名称 // if(EnDebug) qDebug() << "[子设备材质] 子设备" << childDeviceName << "使用材质:" << useMtlName; // } // } // objFile.close(); // } else { // qWarning() << "[子设备材质] 无法打开OBJ文件:" << filePath; // } // // 应用MTL材质(优先使用解析到的材质,无则用默认颜色) // if (!useMtlName.isEmpty() && m_materials.contains(useMtlName)) { // Material mtl = m_materials[useMtlName]; // childMat->setAmbient(mtl.ambient); // childMat->setDiffuse(mtl.diffuse); // childMat->setSpecular(mtl.specular); // childMat->setShininess(mtl.shininess); // // 设置透明度(通过漫反射颜色的alpha通道) // QColor diffuseWithAlpha = mtl.diffuse; // diffuseWithAlpha.setAlphaF(mtl.opacity); // childMat->setDiffuse(diffuseWithAlpha); // if(EnDebug) qDebug() << "[子设备材质] 成功应用MTL材质:" << useMtlName // << " | 漫反射颜色:" << mtl.diffuse // << " | 透明度:" << mtl.opacity; // } else { // // fallback:使用默认颜色 // childMat->setDiffuse(QColor(100, 200, 255)); // if(EnDebug) qDebug() << "[子设备材质] 未找到指定材质,使用默认颜色:(100,200,255)"; // } childModelEntity->addComponent(material); // 将材质添加到子设备模型实体 // 初始化子设备信息 Device childDevice; childDevice.name = childDeviceName; childDevice.entity = childEntity; childDevice.modelEntity = childModelEntity; childDevice.transform = childTransform; childDevice.modelTransform = childModelTrans; childDevice.worldPosition = parentDevice->worldPosition + installOffset; childDevice.currentRotation = QVector3D(0, 0, 0); childDevice.modelCenter = childModelCenter; childDevice.scale = 1.0f; childDevice.isInitialized = true; childDevice.parentDeviceName = parentDeviceName; childDevice.offsetToParent = installOffset; // 记录初始相对偏移 // 保存初始状态(用于后续精确复位) childDevice.initialWorldPosition = childDevice.worldPosition; childDevice.initialRotation = childDevice.currentRotation; childDevice.initialTransformMatrix = childTransform->matrix(); childDevice.initialModelTransformMatrix = childModelTrans->matrix(); m_deviceMap[childDeviceName] = childDevice; qInfo() << "子设备" << childDeviceName << "已挂载到" << parentDeviceName << ",初始相对偏移:" << installOffset; return true; } /** * @brief 旋转器件(绕自身中心) * 将角度转换为弧度后应用到内层变换组件 * @param deviceName 器件名称 * @param xAngle X轴旋转角度(度) * @param yAngle Y轴旋转角度(度) * @param zAngle Z轴旋转角度(度) */ void ObjLoader::rotateDevice(const QString &deviceName, float xAngle, float yAngle, float zAngle) { Device *device = getDevice(deviceName); if (!device || !device->isInitialized) return; // 打印传入的角度值(调试用) if(EnDebug) qDebug() << "接收旋转参数:" << xAngle << "," << yAngle << "," << zAngle; device->currentRotation = QVector3D(xAngle, yAngle, zAngle); // 角度→弧度(Qt3D的旋转函数接受弧度) float xRad = xAngle * M_PI / 180.0f; float yRad = yAngle * M_PI / 180.0f; float zRad = zAngle * M_PI / 180.0f; // 应用旋转到内层变换(同时保持缩放) device->modelTransform->setRotationX(xRad); device->modelTransform->setRotationY(yRad); device->modelTransform->setRotationZ(zRad); device->modelTransform->setScale(device->scale); } /** * @brief 移动器件(相对偏移) * 更新器件的世界位置并应用到外层变换 * @param deviceName 器件名称 * @param offset 偏移量(世界坐标) */ void ObjLoader::moveDevice(const QString &deviceName, const QVector3D &offset) { Device *device = getDevice(deviceName); if (!device || !device->isInitialized) return; // 更新世界位置 device->worldPosition += offset; device->transform->setTranslation(device->worldPosition); } /** * @brief 设置器件位置(绝对位置) * 直接设置器件的世界位置并应用到外层变换 * @param deviceName 器件名称 * @param targetPos 目标位置(世界坐标) */ void ObjLoader::setDevicePos(const QString &deviceName, const QVector3D &targetPos) { Device *device = getDevice(deviceName); if (!device || !device->isInitialized) return; // 更新世界位置 device->worldPosition = targetPos; device->transform->setTranslation(device->worldPosition); } /** * @brief 缩放器件 * 更新缩放因子并重新应用变换(确保旋转状态保持) * @param deviceName 器件名称 * @param scaleFactor 缩放因子(>0) */ void ObjLoader::scaleDevice(const QString &deviceName, float scaleFactor) { if (scaleFactor <= 0) return; Device *device = getDevice(deviceName); if (!device || !device->isInitialized) return; // 保存新的缩放因子 device->scale = scaleFactor; // 重新应用变换(保持当前旋转角度) rotateDevice(deviceName, device->currentRotation.x(), device->currentRotation.y(), device->currentRotation.z()); } /** * @brief 重置器件到初始状态 * 恢复初始位置、旋转和缩放 * @param deviceName 器件名称 */ void ObjLoader::resetDevice(const QString &deviceName) { Device *device = getDevice(deviceName); if (!device || !device->isInitialized) return; // 重置所有属性 device->worldPosition = device->originalPosition; device->currentRotation = QVector3D(0, 0, 0); device->scale = 1.0f; // 应用重置后的变换 device->transform->setTranslation(device->worldPosition); rotateDevice(deviceName, 0, 0, 0); } /** * @brief 从数据映射更新多个器件的属性 * 支持同时更新缩放、旋转和位置 * @param deviceData 器件数据映射 */ void ObjLoader::updateDevicesFromData(const QMap> &deviceData) { for (auto it = deviceData.begin(); it != deviceData.end(); ++it) { const QString &deviceName = it.key(); const QMap &data = it.value(); // 处理缩放数据 if (data.contains("scale")) { scaleDevice(deviceName, data["scale"]); } // 处理旋转数据 if (data.contains("xRot") && data.contains("yRot") && data.contains("zRot")) { rotateDevice(deviceName, data["xRot"], data["yRot"], data["zRot"]); } // 处理位置数据 if (data.contains("xPos") && data.contains("yPos") && data.contains("zPos")) { setDevicePos(deviceName, QVector3D( data["xPos"], data["yPos"], data["zPos"] )); } } } /** * @brief 设置父器件位置(影响子器件) * 无父级的器件直接设置位置,有子级的器件通过调整偏移更新 * @param deviceName 器件名称 * @param position 目标位置(世界坐标) */ void ObjLoader::setParentDevicePosition(const QString &deviceName, const QVector3D &position) { Device *device = getDevice(deviceName); if (!device || !device->isInitialized) { m_lastError = "模型不存在或未初始化: " + deviceName; return; } // 只有无父级的模型才直接设置位置 if (device->parentDeviceName.isEmpty()) { device->worldPosition = position; device->transform->setTranslation(position); // 直接修改世界坐标 } else { // 如果是子模型,使用相对偏移方法 adjustChildDeviceOffset(deviceName, position); } } /** * @brief 添加坐标轴 gizmo * 创建X(红)、Y(绿)、Z(蓝)三条坐标轴,端点带小球标记 * @param parent 父实体(默认根实体) * @param length 坐标轴长度 */ void ObjLoader::addAxisGizmo(Qt3DCore::QEntity *parent, float length) { if (!parent) parent = m_rootEntity; // 如果未指定父实体,使用根实体 if (!parent) return; // 确保父实体有效 // 创建单个轴的辅助函数(使用内置材质) auto createAxis = [&](const QVector3D& end, const QColor& color) { // 1. 创建线段几何 Qt3DRender::QGeometry *geometry = new Qt3DRender::QGeometry(); // 顶点数据(从原点到终点) QByteArray vertexData; vertexData.resize(2 * 3 * sizeof(float)); // 2个点,每个点3个坐标 float *vertices = reinterpret_cast(vertexData.data()); // 起点(原点) vertices[0] = 0.0f; vertices[1] = 0.0f; vertices[2] = 0.0f; // 终点 vertices[3] = end.x(); vertices[4] = end.y(); vertices[5] = end.z(); // 创建顶点缓冲 Qt3DRender::QBuffer *vertexBuffer = new Qt3DRender::QBuffer(Qt3DRender::QBuffer::VertexBuffer); vertexBuffer->setData(vertexData); // 创建位置属性 Qt3DRender::QAttribute *positionAttribute = new Qt3DRender::QAttribute(); positionAttribute->setName(Qt3DRender::QAttribute::defaultPositionAttributeName()); positionAttribute->setVertexBaseType(Qt3DRender::QAttribute::Float); positionAttribute->setVertexSize(3); positionAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute); positionAttribute->setBuffer(vertexBuffer); positionAttribute->setByteOffset(0); positionAttribute->setByteStride(3 * sizeof(float)); positionAttribute->setCount(2); geometry->addAttribute(positionAttribute); // 2. 创建线段渲染器 Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer(); renderer->setGeometry(geometry); renderer->setPrimitiveType(Qt3DRender::QGeometryRenderer::Lines); // 3. 使用内置材质(避免自定义着色器) Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial(); material->setDiffuse(color); material->setAmbient(color); material->setSpecular(color); material->setShininess(1.0f); // 4. 创建线段实体 Qt3DCore::QEntity *axisEntity = new Qt3DCore::QEntity(parent); axisEntity->addComponent(renderer); axisEntity->addComponent(material); // 5. 在轴的端点添加一个小球标记 Qt3DCore::QEntity *marker = new Qt3DCore::QEntity(axisEntity); Qt3DExtras::QSphereMesh *markerMesh = new Qt3DExtras::QSphereMesh(); markerMesh->setRadius(length * 0.05f); // 小球半径为轴长的5% marker->addComponent(markerMesh); Qt3DCore::QTransform *markerTransform = new Qt3DCore::QTransform(); markerTransform->setTranslation(end); marker->addComponent(markerTransform); Qt3DExtras::QPhongMaterial *markerMat = new Qt3DExtras::QPhongMaterial(); markerMat->setDiffuse(color); marker->addComponent(markerMat); return axisEntity; }; // 创建三条坐标轴 createAxis(QVector3D(length, 0, 0), Qt::red); // X轴(红色) createAxis(QVector3D(0, length, 0), Qt::green); // Y轴(绿色) createAxis(QVector3D(0, 0, length), Qt::blue); // Z轴(蓝色) } /** * @brief 解析OBJ文件计算模型中心 * 读取OBJ文件中的顶点数据,计算包围盒中心 * @param filePath OBJ文件路径 * @return 模型中心坐标(局部坐标系) */ QVector3D ObjLoader::parseObjCenter(const QString &filePath) { QVector vertices; QFile objFile(filePath); if (!objFile.open(QIODevice::ReadOnly | QIODevice::Text)) { m_lastError = "无法打开OBJ文件计算中心: " + filePath; return QVector3D(0, 0, 0); } QTextStream fileStream(&objFile); while (!fileStream.atEnd()) { QString line = fileStream.readLine().trimmed(); if (line.isEmpty() || line.startsWith('#')) continue; QVector lineParts = splitStr(line); if (lineParts.isEmpty()) continue; // 只解析顶点位置("v x y z"格式) if (lineParts[0] == "v" && lineParts.size() >= 4) { vertices.append(QVector3D( lineParts[1].toFloat(), lineParts[2].toFloat(), lineParts[3].toFloat() )); } } objFile.close(); return calculateBoundingBoxCenter(vertices); } /** * @brief 打印器件的世界坐标信息 * 输出器件的外层位置、几何中心及实际世界中心 * @param deviceName 器件名称 */ void ObjLoader::printDeviceWorldPos(const QString &deviceName) { Device *device = getDevice(deviceName); if (!device || !device->isInitialized) { qCritical() << "[定位错误] 设备'" << deviceName << "'未找到或未初始化"; return; } // 计算滚筒几何中心的世界坐标(外层位置 + 局部中心) QVector3D deviceCenterWorld = device->worldPosition + device->modelCenter; if(EnDebug) qDebug() << "=== 设备'" << deviceName << "'位置信息 ==="; if(EnDebug) qDebug() << "外层实体世界坐标(位置基准):" << device->worldPosition; if(EnDebug) qDebug() << "几何中心(局部坐标):" << device->modelCenter; if(EnDebug) qDebug() << "几何中心世界坐标(实际本体位置):" << deviceCenterWorld; if(EnDebug) qDebug() << "当前旋转角度:" << device->currentRotation; if(EnDebug) qDebug() << "缩放比例:" << device->scale; } /** * @brief 更新整体模型(重建实体) * 清除旧的整体模型实体,创建新的实体、变换和材质 */ void ObjLoader::updateWholeModel() { delete m_wholeModelEntity; m_wholeModelEntity = nullptr; m_wholeModelMesh = nullptr; m_wholeModelTrans = nullptr; m_wholeModelEntity = new Qt3DCore::QEntity(m_rootEntity); // 添加模型变换组件(预中心化) m_wholeModelTrans = new Qt3DCore::QTransform(); m_wholeModelTrans->setTranslation(-m_wholeModelCenter); m_wholeModelEntity->addComponent(m_wholeModelTrans); // 添加网格组件 m_wholeModelMesh = new Qt3DRender::QMesh(); m_wholeModelEntity->addComponent(m_wholeModelMesh); // 添加材质 Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial(); material->setDiffuse(QColor(0xbeb32b)); // 金色 material->setSpecular(QColor(255, 255, 255)); material->setShininess(60.0f); m_wholeModelEntity->addComponent(material); } /** * @brief 摇臂围绕自身局部原点旋转,带动子设备 * 首次调用记录初始状态,0°时复位,其他角度计算旋转矩阵并应用 * @param deviceName 摇臂名称 * @param targetZAngle 目标Z轴角度(度) */ void ObjLoader::rotateArmToAbsoluteZAngle(const QString &deviceName, float targetZAngle) { Device *armDevice = getDevice(deviceName); if (!armDevice || !armDevice->isInitialized) { m_lastError = deviceName + " ->设备不存在或未初始化: " + deviceName; qCritical() << "[错误]" << m_lastError; return; } /* 1. 首次调用时记录初始状态(只需一次) */ if (armDevice->initialRotation == QVector3D()) { armDevice->initialRotation = QVector3D(0, 0, 0); armDevice->initialWorldPosition = armDevice->worldPosition; armDevice->initialTransformMatrix = armDevice->transform->matrix(); armDevice->initialModelTransformMatrix = armDevice->modelTransform->matrix(); } /* 2. 0°复位 */ if (qFuzzyCompare(targetZAngle, 0.0f)) { armDevice->worldPosition = armDevice->initialWorldPosition; armDevice->currentRotation = armDevice->initialRotation; armDevice->transform->setMatrix(armDevice->initialTransformMatrix); armDevice->modelTransform->setMatrix(armDevice->initialModelTransformMatrix); for (Device *child : getAllChildren(armDevice)) { child->worldPosition = child->initialWorldPosition; child->transform->setMatrix(child->initialTransformMatrix); child->modelTransform->setMatrix(child->initialModelTransformMatrix); } return; } /* 3. 计算旋转矩阵:绕摇臂自身局部原点(0,0,0) */ QMatrix4x4 rotMat; rotMat.setToIdentity(); rotMat.rotate(targetZAngle, 0, 0, 1); // 仅 Z 轴 /* 4. 应用旋转到外层 transform(平移+旋转) */ armDevice->currentRotation.setZ(targetZAngle); armDevice->transform->setRotationZ(targetZAngle); // 外层只转 Z // 保持原平移不变 armDevice->transform->setTranslation(armDevice->worldPosition); /* 5. 同步子设备:继承父旋转,更新世界坐标记录 */ for (Device *child : getAllChildren(armDevice)) { // 子设备挂在 armDevice->entity 下,自动继承旋转 QMatrix4x4 worldMat = child->transform->matrix(); // 局部→世界矩阵 child->worldPosition = worldMat.column(3).toVector3D(); } } /** * @brief 摇臂围绕自身局部原点绕X轴旋转,带动子设备 * 首次调用记录初始状态,0°时复位,其他角度计算旋转矩阵并应用 * @param deviceName 摇臂名称 * @param targetXAngle 目标X轴角度(度) */ void ObjLoader::rotateArmToAbsoluteXAngle(const QString &deviceName, float targetXAngle) { Device *armDevice = getDevice(deviceName); if (!armDevice || !armDevice->isInitialized) { m_lastError = "摇臂设备不存在或未初始化: " + deviceName; qCritical() << "[错误]" << m_lastError; return; } /* 1. 首次调用时记录初始状态(只需一次) */ if (armDevice->initialRotation == QVector3D()) { armDevice->initialRotation = QVector3D(0, 0, 0); armDevice->initialWorldPosition = armDevice->worldPosition; armDevice->initialTransformMatrix = armDevice->transform->matrix(); armDevice->initialModelTransformMatrix = armDevice->modelTransform->matrix(); } /* 2. 0°复位 */ if (qFuzzyCompare(targetXAngle, 0.0f)) { armDevice->worldPosition = armDevice->initialWorldPosition; armDevice->currentRotation = armDevice->initialRotation; armDevice->transform->setMatrix(armDevice->initialTransformMatrix); armDevice->modelTransform->setMatrix(armDevice->initialModelTransformMatrix); for (Device *child : getAllChildren(armDevice)) { child->worldPosition = child->initialWorldPosition; child->transform->setMatrix(child->initialTransformMatrix); child->modelTransform->setMatrix(child->initialModelTransformMatrix); } return; } /* 3. 计算旋转矩阵:绕摇臂自身局部原点(0,0,0)的X轴 */ QMatrix4x4 rotMat; rotMat.setToIdentity(); rotMat.rotate(targetXAngle, 1, 0, 0); // 仅 X 轴 /* 4. 应用旋转到外层 transform(平移+旋转) */ armDevice->currentRotation.setX(targetXAngle); armDevice->transform->setRotationX(targetXAngle); // 外层只转 X // 保持原平移不变 armDevice->transform->setTranslation(armDevice->worldPosition); /* 5. 同步子设备:继承父旋转,更新世界坐标记录 */ for (Device *child : getAllChildren(armDevice)) { // 子设备挂在 armDevice->entity 下,自动继承旋转 QMatrix4x4 worldMat = child->transform->matrix(); // 局部→世界矩阵 child->worldPosition = worldMat.column(3).toVector3D(); } } /** * @brief 摇臂围绕自身局部原点绕Y轴旋转,带动子设备 * 首次调用记录初始状态,0°时复位,其他角度计算旋转矩阵并应用 * @param deviceName 摇臂名称 * @param targetYAngle 目标Y轴角度(度) */ void ObjLoader::rotateArmToAbsoluteYAngle(const QString &deviceName, float targetYAngle) { Device *armDevice = getDevice(deviceName); if (!armDevice || !armDevice->isInitialized) { m_lastError = "摇臂设备不存在或未初始化: " + deviceName; qCritical() << "[错误]" << m_lastError; return; } /* 1. 首次调用时记录初始状态(只需一次) */ if (armDevice->initialRotation == QVector3D()) { armDevice->initialRotation = QVector3D(0, 0, 0); armDevice->initialWorldPosition = armDevice->worldPosition; armDevice->initialTransformMatrix = armDevice->transform->matrix(); armDevice->initialModelTransformMatrix = armDevice->modelTransform->matrix(); } /* 2. 0°复位 */ if (qFuzzyCompare(targetYAngle, 0.0f)) { armDevice->worldPosition = armDevice->initialWorldPosition; armDevice->currentRotation = armDevice->initialRotation; armDevice->transform->setMatrix(armDevice->initialTransformMatrix); armDevice->modelTransform->setMatrix(armDevice->initialModelTransformMatrix); for (Device *child : getAllChildren(armDevice)) { child->worldPosition = child->initialWorldPosition; child->transform->setMatrix(child->initialTransformMatrix); child->modelTransform->setMatrix(child->initialModelTransformMatrix); } return; } /* 3. 计算旋转矩阵:绕摇臂自身局部原点(0,0,0)的Y轴 */ QMatrix4x4 rotMat; rotMat.setToIdentity(); rotMat.rotate(targetYAngle, 0, 1, 0); // 仅 Y 轴 /* 4. 应用旋转到外层 transform(平移+旋转) */ armDevice->currentRotation.setY(targetYAngle); armDevice->transform->setRotationY(targetYAngle); // 外层只转 Y // 保持原平移不变 armDevice->transform->setTranslation(armDevice->worldPosition); /* 5. 同步子设备:继承父旋转,更新世界坐标记录 */ for (Device *child : getAllChildren(armDevice)) { // 子设备挂在 armDevice->entity 下,自动继承旋转 QMatrix4x4 worldMat = child->transform->matrix(); // 局部→世界矩阵 child->worldPosition = worldMat.column(3).toVector3D(); } } /** * @brief 调整子器件相对父器件的偏移量 * 用于更新子器件相对于其父器件的位置偏移,同时同步更新世界坐标 * @param childDeviceName 子器件名称 * @param newOffset 新的相对偏移量(父器件局部坐标系下) */ void ObjLoader::adjustChildDeviceOffset(const QString &childDeviceName, const QVector3D &newOffset) { // 获取子器件指针并验证有效性 Device *childDevice = getDevice(childDeviceName); if (!childDevice || !childDevice->isInitialized || childDevice->parentDeviceName.isEmpty()) { m_lastError = "子模型不存在或无父级: " + childDeviceName; return; } // 更新相对偏移量并应用到变换组件 childDevice->offsetToParent = newOffset; childDevice->transform->setTranslation(newOffset); // 根据父器件位置更新子器件的世界坐标记录 Device *parentDevice = getDevice(childDevice->parentDeviceName); if (parentDevice) { childDevice->worldPosition = parentDevice->worldPosition + newOffset; } } /** * @brief 解析MTL材质文件 * 从MTL文件中提取材质属性(环境光、漫反射、高光等)并存储到材质映射表 * @param filePath MTL文件路径 * @return 解析成功返回true,失败返回false */ bool ObjLoader::parseMtlFile(const QString &filePath) { // 清空现有材质(可选,根据需求决定是否保留之前的材质) // m_materials.clear(); // 检查文件是否存在 QFileInfo fileInfo(filePath); if (!fileInfo.exists()) { if (EnDebug) qDebug() << "MTL文件不存在: " << filePath; m_lastError = "MTL文件不存在: " + filePath; return false; } // 尝试打开文件 QFile mtlFile(filePath); if (!mtlFile.open(QIODevice::ReadOnly | QIODevice::Text)) { QString errorMsg = "无法打开MTL文件: " + filePath + ",错误: " + mtlFile.errorString(); if (EnDebug) qDebug() << errorMsg; m_lastError = errorMsg; return false; } QTextStream stream(&mtlFile); Material currentMaterial; QString baseDir = fileInfo.path() + "/"; int materialCount = 0; try { while (!stream.atEnd()) { QString line = stream.readLine().trimmed(); if (line.isEmpty() || line.startsWith("#")) { continue; } QStringList parts = line.split(QRegExp("\\s+"), Qt::SkipEmptyParts); if (parts.isEmpty()) continue; const QString &keyword = parts[0]; if (keyword == "newmtl") { // 保存上一个材质 if (!currentMaterial.name.isEmpty()) { m_materials[currentMaterial.name] = currentMaterial; materialCount++; if (EnDebug) qDebug() << "解析材质: " << currentMaterial.name; } // 初始化新材质 currentMaterial = Material(); if (parts.size() >= 2) { currentMaterial.name = parts[1]; } else { if (EnDebug) qDebug() << "警告: newmtl指令缺少材质名称,行内容: " << line; } } else if (keyword == "Ka") { if (parts.size() >= 4) { currentMaterial.ambient = QColor::fromRgbF( parts[1].toFloat(), parts[2].toFloat(), parts[3].toFloat() ); } else { if (EnDebug) qDebug() << "警告: Ka指令参数不足,行内容: " << line; } } else if (keyword == "Kd") { if (parts.size() >= 4) { currentMaterial.diffuse = QColor::fromRgbF( parts[1].toFloat(), parts[2].toFloat(), parts[3].toFloat() ); } else { if (EnDebug) qDebug() << "警告: Kd指令参数不足,行内容: " << line; } } else if (keyword == "Ks") { if (parts.size() >= 4) { currentMaterial.specular = QColor::fromRgbF( parts[1].toFloat(), parts[2].toFloat(), parts[3].toFloat() ); } else { if (EnDebug) qDebug() << "警告: Ks指令参数不足,行内容: " << line; } } else if (keyword == "Ns") { if (parts.size() >= 2) { currentMaterial.shininess = parts[1].toFloat(); } else { if (EnDebug) qDebug() << "警告: Ns指令参数不足,行内容: " << line; } } else if (keyword == "d") { if (parts.size() >= 2) { currentMaterial.opacity = parts[1].toFloat(); } else { if (EnDebug) qDebug() << "警告: d指令参数不足,行内容: " << line; } } else if (keyword == "Tr") { if (parts.size() >= 2) { currentMaterial.opacity = 1.0 - parts[1].toFloat(); } else { if (EnDebug) qDebug() << "警告: Tr指令参数不足,行内容: " << line; } } else if (keyword == "map_Kd") { if (parts.size() >= 2) { QString texturePath = parts[1]; if (!QFileInfo(texturePath).isAbsolute()) { texturePath = baseDir + texturePath; } currentMaterial.diffuseMap = texturePath; if (!QFile::exists(texturePath) && EnDebug) { qDebug() << "警告: 漫反射纹理文件不存在 - " << texturePath; } } else { if (EnDebug) qDebug() << "警告: map_Kd指令参数不足,行内容: " << line; } } // 可以添加其他MTL指令的解析 } // 保存最后一个材质 // 保存最后一个材质(在parseMtlFile函数末尾) if (!currentMaterial.name.isEmpty()) { // 补充FreeCAD默认参数(已修改Ka的判断) if (!currentMaterial.ambient.isValid()) { currentMaterial.ambient = currentMaterial.diffuse; } if (!currentMaterial.specular.isValid()) { // 同理,检测Ks是否无效 currentMaterial.specular = QColor::fromRgbF(1.0, 1.0, 1.0); } if (currentMaterial.shininess <= 0.0f) { currentMaterial.shininess = 30.0f; } if (currentMaterial.opacity < 0.0f || currentMaterial.opacity > 1.0f) { currentMaterial.opacity = 1.0f; } m_materials[currentMaterial.name] = currentMaterial; materialCount++; // 统计最后一个材质 if (EnDebug) { qDebug() << "解析材质完成: " << currentMaterial.name; qDebug() << "Ka(环境光): " << currentMaterial.ambient; // 此时会显示有效颜色 qDebug() << "Kd(漫反射): " << currentMaterial.diffuse; qDebug() << "Opacity(透明度): " << currentMaterial.opacity; } } mtlFile.close(); // 3. 关键调整:在保存最后一个材质后,再判断材质计数 if (materialCount == 0) { m_lastError = "MTL文件中未找到任何材质: " + filePath; if (EnDebug) qDebug() << m_lastError; return false; } if (EnDebug) qDebug() << "MTL文件解析完成,共加载" << materialCount << "个材质"; return true; } catch (...) { mtlFile.close(); m_lastError = "解析MTL文件时发生未知错误: " + filePath; if (EnDebug) qDebug() << m_lastError; return false; } } /** * @brief 递归获取指定父器件的所有子器件(包括孙子辈) * 用于获取器件的完整层级结构,支持多层级嵌套 * @param parentDevice 父器件指针 * @return 所有子器件(含嵌套子器件)的指针列表 */ QList ObjLoader::getAllChildren(Device *parentDevice) { QList children; if (!parentDevice) return children; // 遍历所有器件查找直接子器件 for (auto it = m_deviceMap.begin(); it != m_deviceMap.end(); ++it) { Device &device = it.value(); if (device.parentDeviceName == parentDevice->name && device.isInitialized) { children.append(&device); // 递归获取当前子器件的子器件(孙子辈) QList grandChildren = getAllChildren(&device); children << grandChildren; } } return children; } /** * @brief 计算顶点集合的包围盒中心 * 通过找出X、Y、Z三个轴上的最小值和最大值,计算包围盒的几何中心 * @param vertices 顶点坐标列表 * @return 包围盒中心坐标(局部坐标系) */ QVector3D ObjLoader::calculateBoundingBoxCenter(const QVector& vertices) { if (vertices.isEmpty()) return QVector3D(0, 0, 0); // 初始化最小/最大值为第一个顶点的坐标 float minX = vertices[0].x(), maxX = vertices[0].x(); float minY = vertices[0].y(), maxY = vertices[0].y(); float minZ = vertices[0].z(), maxZ = vertices[0].z(); // 遍历所有顶点,更新最小/最大值 for (const auto& v : vertices) { minX = qMin(minX, v.x()); maxX = qMax(maxX, v.x()); minY = qMin(minY, v.y()); maxY = qMax(maxY, v.y()); minZ = qMin(minZ, v.z()); maxZ = qMax(maxZ, v.z()); } // 计算并返回包围盒中心点坐标 return QVector3D( (minX + maxX) / 2.0f, (minY + maxY) / 2.0f, (minZ + maxZ) / 2.0f ); } /** * @brief 将摇臂的局部坐标系原点移动到指定世界坐标 * 调整摇臂的外层和内层变换,使其实体原点与目标世界坐标对齐,同时保持子器件相对位置 * @param deviceName 摇臂器件名称 * @param targetWorldPos 目标世界坐标(新的局部原点位置) */ void ObjLoader::moveArmLocalOriginTo(const QString &deviceName, const QVector3D &targetWorldPos) { // 1. 获取摇臂设备并验证 Device *armDevice = getDevice(deviceName); if (!armDevice || !armDevice->isInitialized) { m_lastError = "摇臂设备不存在或未初始化: " + deviceName; qCritical() << "[错误]" << m_lastError; return; } // 2. 记录当前世界位置(用于计算偏移) QVector3D currentWorldPos = armDevice->worldPosition; // 3. 计算原点偏移量:目标位置与当前位置的差值 QVector3D originOffset = targetWorldPos - currentWorldPos; // 4. 调整外层变换:将实体原点移动到目标世界坐标 armDevice->transform->setTranslation(targetWorldPos); armDevice->worldPosition = targetWorldPos; // 5. 补偿内层变换:保持模型几何中心与局部原点的相对位置 // 原模型中心在局部坐标中为(modelCenter),通过偏移抵消外层变换的影响 QVector3D newModelTranslation = armDevice->modelTransform->translation() - originOffset; armDevice->modelTransform->setTranslation(newModelTranslation); // 6. 更新初始状态:确保后续旋转/复位操作基于新原点 armDevice->initialWorldPosition = targetWorldPos; armDevice->initialTransformMatrix = armDevice->transform->matrix(); armDevice->initialModelTransformMatrix = armDevice->modelTransform->matrix(); // 7. 同步更新所有子设备(如滚筒)的位置,保持相对偏移 QList children = getAllChildren(armDevice); for (Device* child : children) { // 计算子设备相对摇臂原原点的偏移量 QVector3D childRelativeOffset = child->worldPosition - currentWorldPos; // 基于新原点计算子设备新位置 child->worldPosition = targetWorldPos + childRelativeOffset; child->transform->setTranslation(child->worldPosition); // 更新子设备的初始状态 child->initialWorldPosition = child->worldPosition; child->initialTransformMatrix = child->transform->matrix(); } // 标记局部旋转中心为新原点 armDevice->localPivot = QVector3D(0,0,0); if(EnDebug) qDebug() << "[坐标系移动完成] 摇臂" << deviceName << "局部原点已移动到世界坐标:" << targetWorldPos << "| 偏移补偿量:" << originOffset; } // 相机信息变化处理函数 void ObjLoader::onCameraChanged() { if (!m_cameraEntity) return; // 获取当前相机信息 QVector3D pos = m_cameraEntity->position(); QVector3D center = m_cameraEntity->viewCenter(); float fov = m_cameraEntity->lens()->fieldOfView(); if(EnDebug) qDebug() << QString("相机位置: (X: %1, Y: %2, Z: %3)") .arg(pos.x(), 0, 'f', 4) .arg(pos.y(), 0, 'f', 4) .arg(pos.z(), 0, 'f', 4); if(EnDebug) qDebug() << QString("看向位置: (X: %1, Y: %2, Z: %3)") .arg(center.x(), 0, 'f', 4) .arg(center.y(), 0, 'f', 4) .arg(center.z(), 0, 'f', 4); if(EnDebug) qDebug() << QString("视野角度: %1°").arg(fov, 0, 'f', 1); if(EnDebug) qDebug() << "----------------------------------------"; }