#include "VrConfig.h" #include #include #include #include "VrLog.h" #include "ConfigXmlUtils.h" using namespace tinyxml2; CVrConfig::CVrConfig() : m_pNotify(nullptr) { } CVrConfig::~CVrConfig() { } int CVrConfig::LoadConfig(const std::string& filePath, ConfigResult& configResult) { // 读取 HoleDetection 专用配置文件。 XMLDocument doc; XMLError err = doc.LoadFile(filePath.c_str()); if (err != XML_SUCCESS) { LOG_ERR("open config file failed: %s\n", filePath.c_str()); return LOAD_CONFIG_FILE_NOT_FOUND; } // 根节点固定为 HoleDetectionConfig,便于和其它应用配置区分。 XMLElement* root = doc.RootElement(); if (!root || std::string(root->Name()) != "HoleDetectionConfig") { std::cerr << "config file format error: root element is not HoleDetectionConfig" << std::endl; return LOAD_CONFIG_INVALID_FORMAT; } // 通用配置仍复用公共工具读取,例如相机列表、调试参数、串口等。 ConfigXmlUtils::LoadCameraList(root, configResult.cameraList); XMLElement* devicesElement = root->FirstChildElement("Devices"); if (devicesElement) { XMLElement* deviceElement = devicesElement->FirstChildElement("Device"); while (deviceElement) { DeviceInfo device; if (deviceElement->Attribute("name")) device.name = deviceElement->Attribute("name"); if (deviceElement->Attribute("ip")) device.ip = deviceElement->Attribute("ip"); configResult.deviceList.push_back(device); deviceElement = deviceElement->NextSiblingElement("Device"); } } // 算法参数分为 RANSAC / Detection / Filter 三组,分别与算法头文件一一对应。 XMLElement* algoParamsElement = root->FirstChildElement("AlgorithmParams"); if (algoParamsElement) { XMLElement* ransacParamElement = algoParamsElement->FirstChildElement("RansacParam"); if (ransacParamElement) { // 读取 RANSAC 平面分割参数。 if (ransacParamElement->Attribute("distanceThreshold")) configResult.algorithmParams.ransacParam.distanceThreshold = ransacParamElement->DoubleAttribute("distanceThreshold"); if (ransacParamElement->Attribute("maxIterations")) configResult.algorithmParams.ransacParam.maxIterations = ransacParamElement->IntAttribute("maxIterations"); if (ransacParamElement->Attribute("minPlanePoints")) configResult.algorithmParams.ransacParam.minPlanePoints = ransacParamElement->IntAttribute("minPlanePoints"); if (ransacParamElement->Attribute("maxPlanes")) configResult.algorithmParams.ransacParam.maxPlanes = ransacParamElement->IntAttribute("maxPlanes"); if (ransacParamElement->Attribute("growthZThreshold")) configResult.algorithmParams.ransacParam.growthZThreshold = ransacParamElement->DoubleAttribute("growthZThreshold"); if (ransacParamElement->Attribute("minPlaneRatio")) configResult.algorithmParams.ransacParam.minPlaneRatio = ransacParamElement->DoubleAttribute("minPlaneRatio"); if (ransacParamElement->Attribute("maxNormalAngleDeg")) configResult.algorithmParams.ransacParam.maxNormalAngleDeg = ransacParamElement->DoubleAttribute("maxNormalAngleDeg"); } XMLElement* detectionParamElement = algoParamsElement->FirstChildElement("DetectionParam"); if (detectionParamElement) { // 孔检测参数,需要完整覆盖 SHoleDetectionParams。 if (detectionParamElement->Attribute("angleThresholdPos")) configResult.algorithmParams.detectionParam.angleThresholdPos = detectionParamElement->DoubleAttribute("angleThresholdPos"); if (detectionParamElement->Attribute("angleThresholdNeg")) configResult.algorithmParams.detectionParam.angleThresholdNeg = detectionParamElement->DoubleAttribute("angleThresholdNeg"); if (detectionParamElement->Attribute("angleSearchDistance")) configResult.algorithmParams.detectionParam.angleSearchDistance = detectionParamElement->DoubleAttribute("angleSearchDistance"); if (detectionParamElement->Attribute("minPitDepth")) configResult.algorithmParams.detectionParam.minPitDepth = detectionParamElement->DoubleAttribute("minPitDepth"); if (detectionParamElement->Attribute("minRadius")) configResult.algorithmParams.detectionParam.minRadius = detectionParamElement->DoubleAttribute("minRadius"); if (detectionParamElement->Attribute("maxRadius")) configResult.algorithmParams.detectionParam.maxRadius = detectionParamElement->DoubleAttribute("maxRadius"); if (detectionParamElement->Attribute("expansionSize1")) configResult.algorithmParams.detectionParam.expansionSize1 = detectionParamElement->IntAttribute("expansionSize1"); if (detectionParamElement->Attribute("expansionSize2")) configResult.algorithmParams.detectionParam.expansionSize2 = detectionParamElement->IntAttribute("expansionSize2"); if (detectionParamElement->Attribute("minVTransitionPoints")) configResult.algorithmParams.detectionParam.minVTransitionPoints = detectionParamElement->IntAttribute("minVTransitionPoints"); if (detectionParamElement->Attribute("jumpThresholdResidual")) configResult.algorithmParams.detectionParam.jumpThresholdResidual = detectionParamElement->DoubleAttribute("jumpThresholdResidual"); if (detectionParamElement->Attribute("gapJumpThresholdResidual")) configResult.algorithmParams.detectionParam.gapJumpThresholdResidual = detectionParamElement->DoubleAttribute("gapJumpThresholdResidual"); if (detectionParamElement->Attribute("maxGapPointsInLine")) configResult.algorithmParams.detectionParam.maxGapPointsInLine = detectionParamElement->IntAttribute("maxGapPointsInLine"); if (detectionParamElement->Attribute("minFeatureSpan")) configResult.algorithmParams.detectionParam.minFeatureSpan = detectionParamElement->DoubleAttribute("minFeatureSpan"); if (detectionParamElement->Attribute("residualSmoothWindow")) configResult.algorithmParams.detectionParam.residualSmoothWindow = detectionParamElement->IntAttribute("residualSmoothWindow"); if (detectionParamElement->Attribute("slopeAngleThreshold")) configResult.algorithmParams.detectionParam.slopeAngleThreshold = detectionParamElement->DoubleAttribute("slopeAngleThreshold"); if (detectionParamElement->Attribute("edgeBoundaryFilterDist")) configResult.algorithmParams.detectionParam.edgeBoundaryFilterDist = detectionParamElement->DoubleAttribute("edgeBoundaryFilterDist"); } XMLElement* filterParamElement = algoParamsElement->FirstChildElement("FilterParam"); if (filterParamElement) { // 孔过滤参数,需要完整覆盖 SHoleFilterParams。 if (filterParamElement->Attribute("maxEccentricity")) configResult.algorithmParams.filterParam.maxEccentricity = filterParamElement->DoubleAttribute("maxEccentricity"); if (filterParamElement->Attribute("minAngularCoverage")) configResult.algorithmParams.filterParam.minAngularCoverage = filterParamElement->DoubleAttribute("minAngularCoverage"); if (filterParamElement->Attribute("maxRadiusFitRatio")) configResult.algorithmParams.filterParam.maxRadiusFitRatio = filterParamElement->DoubleAttribute("maxRadiusFitRatio"); if (filterParamElement->Attribute("minQualityScore")) configResult.algorithmParams.filterParam.minQualityScore = filterParamElement->DoubleAttribute("minQualityScore"); if (filterParamElement->Attribute("maxPlaneResidual")) configResult.algorithmParams.filterParam.maxPlaneResidual = filterParamElement->DoubleAttribute("maxPlaneResidual"); if (filterParamElement->Attribute("maxAngularGap")) configResult.algorithmParams.filterParam.maxAngularGap = filterParamElement->DoubleAttribute("maxAngularGap"); if (filterParamElement->Attribute("minInlierRatio")) configResult.algorithmParams.filterParam.minInlierRatio = filterParamElement->DoubleAttribute("minInlierRatio"); if (filterParamElement->Attribute("minHoleDepth")) configResult.algorithmParams.filterParam.minHoleDepth = filterParamElement->DoubleAttribute("minHoleDepth"); } // 排序模式 sortMode 属于应用层选项,但仍跟随算法参数一起持久化。 XMLElement* sortModeElement = algoParamsElement->FirstChildElement("SortMode"); if (sortModeElement && sortModeElement->Attribute("value")) configResult.algorithmParams.sortMode = sortModeElement->IntAttribute("value"); // 平面标定参数继续复用通用读写逻辑。 ConfigXmlUtils::LoadPlaneCalibParams(algoParamsElement, configResult.algorithmParams.planeCalibParam); } ConfigXmlUtils::LoadDebugParam(root, configResult.debugParam); ConfigXmlUtils::LoadSerialConfig(root, configResult.serialConfig); ConfigXmlUtils::LoadHandEyeCalibMatrixs(root, configResult.handEyeCalibMatrixList); // TCP 服务配置由当前应用消费,用于姿态输出与网络通讯。 XMLElement* tcpServerConfigElement = root->FirstChildElement("TcpServerConfig"); if (tcpServerConfigElement) { if (tcpServerConfigElement->Attribute("tcpServerPort")) configResult.plcRobotServerConfig.tcpServerPort = tcpServerConfigElement->IntAttribute("tcpServerPort"); else configResult.plcRobotServerConfig.tcpServerPort = 7800; if (tcpServerConfigElement->Attribute("poseOutputOrder")) configResult.plcRobotServerConfig.poseOutputOrder = tcpServerConfigElement->IntAttribute("poseOutputOrder"); else configResult.plcRobotServerConfig.poseOutputOrder = POSE_ORDER_RPY; if (tcpServerConfigElement->Attribute("dirVectorInvert")) configResult.plcRobotServerConfig.dirVectorInvert = tcpServerConfigElement->IntAttribute("dirVectorInvert"); else configResult.plcRobotServerConfig.dirVectorInvert = DIR_INVERT_YZ; } return LOAD_CONFIG_SUCCESS; } bool CVrConfig::SaveConfig(const std::string& filePath, ConfigResult& configResult) { // 保存时按当前应用的 XML 结构完整重建配置文档。 XMLDocument doc; XMLDeclaration* declaration = doc.NewDeclaration("xml version=\"1.0\" encoding=\"UTF-8\""); doc.InsertFirstChild(declaration); XMLElement* root = doc.NewElement("HoleDetectionConfig"); doc.InsertEndChild(root); ConfigXmlUtils::SaveCameraList(doc, root, configResult.cameraList); XMLElement* devicesElement = doc.NewElement("Devices"); root->InsertEndChild(devicesElement); for (const auto& device : configResult.deviceList) { XMLElement* deviceElement = doc.NewElement("Device"); deviceElement->SetAttribute("name", device.name.c_str()); deviceElement->SetAttribute("ip", device.ip.c_str()); devicesElement->InsertEndChild(deviceElement); } XMLElement* algoParamsElement = doc.NewElement("AlgorithmParams"); root->InsertEndChild(algoParamsElement); // 先写 RANSAC,再写 Detection / Filter,顺序与 LoadConfig 保持一致。 XMLElement* ransacParamElement = doc.NewElement("RansacParam"); ransacParamElement->SetAttribute("distanceThreshold", configResult.algorithmParams.ransacParam.distanceThreshold); ransacParamElement->SetAttribute("maxIterations", configResult.algorithmParams.ransacParam.maxIterations); ransacParamElement->SetAttribute("minPlanePoints", configResult.algorithmParams.ransacParam.minPlanePoints); ransacParamElement->SetAttribute("maxPlanes", configResult.algorithmParams.ransacParam.maxPlanes); ransacParamElement->SetAttribute("growthZThreshold", configResult.algorithmParams.ransacParam.growthZThreshold); ransacParamElement->SetAttribute("minPlaneRatio", configResult.algorithmParams.ransacParam.minPlaneRatio); ransacParamElement->SetAttribute("maxNormalAngleDeg", configResult.algorithmParams.ransacParam.maxNormalAngleDeg); algoParamsElement->InsertEndChild(ransacParamElement); // 检测参数 DetectionParam 需要覆盖算法库当前全部检测字段。 XMLElement* detectionParamElement = doc.NewElement("DetectionParam"); detectionParamElement->SetAttribute("angleThresholdPos", configResult.algorithmParams.detectionParam.angleThresholdPos); detectionParamElement->SetAttribute("angleThresholdNeg", configResult.algorithmParams.detectionParam.angleThresholdNeg); detectionParamElement->SetAttribute("angleSearchDistance", configResult.algorithmParams.detectionParam.angleSearchDistance); detectionParamElement->SetAttribute("minPitDepth", configResult.algorithmParams.detectionParam.minPitDepth); detectionParamElement->SetAttribute("minRadius", configResult.algorithmParams.detectionParam.minRadius); detectionParamElement->SetAttribute("maxRadius", configResult.algorithmParams.detectionParam.maxRadius); detectionParamElement->SetAttribute("expansionSize1", configResult.algorithmParams.detectionParam.expansionSize1); detectionParamElement->SetAttribute("expansionSize2", configResult.algorithmParams.detectionParam.expansionSize2); detectionParamElement->SetAttribute("minVTransitionPoints", configResult.algorithmParams.detectionParam.minVTransitionPoints); detectionParamElement->SetAttribute("jumpThresholdResidual", configResult.algorithmParams.detectionParam.jumpThresholdResidual); detectionParamElement->SetAttribute("gapJumpThresholdResidual", configResult.algorithmParams.detectionParam.gapJumpThresholdResidual); detectionParamElement->SetAttribute("maxGapPointsInLine", configResult.algorithmParams.detectionParam.maxGapPointsInLine); detectionParamElement->SetAttribute("minFeatureSpan", configResult.algorithmParams.detectionParam.minFeatureSpan); detectionParamElement->SetAttribute("residualSmoothWindow", configResult.algorithmParams.detectionParam.residualSmoothWindow); detectionParamElement->SetAttribute("slopeAngleThreshold", configResult.algorithmParams.detectionParam.slopeAngleThreshold); detectionParamElement->SetAttribute("edgeBoundaryFilterDist", configResult.algorithmParams.detectionParam.edgeBoundaryFilterDist); algoParamsElement->InsertEndChild(detectionParamElement); // 过滤参数 FilterParam 需要覆盖算法库当前全部过滤字段。 XMLElement* filterParamElement = doc.NewElement("FilterParam"); filterParamElement->SetAttribute("maxEccentricity", configResult.algorithmParams.filterParam.maxEccentricity); filterParamElement->SetAttribute("minAngularCoverage", configResult.algorithmParams.filterParam.minAngularCoverage); filterParamElement->SetAttribute("maxRadiusFitRatio", configResult.algorithmParams.filterParam.maxRadiusFitRatio); filterParamElement->SetAttribute("minQualityScore", configResult.algorithmParams.filterParam.minQualityScore); filterParamElement->SetAttribute("maxPlaneResidual", configResult.algorithmParams.filterParam.maxPlaneResidual); filterParamElement->SetAttribute("maxAngularGap", configResult.algorithmParams.filterParam.maxAngularGap); filterParamElement->SetAttribute("minInlierRatio", configResult.algorithmParams.filterParam.minInlierRatio); filterParamElement->SetAttribute("minHoleDepth", configResult.algorithmParams.filterParam.minHoleDepth); algoParamsElement->InsertEndChild(filterParamElement); // 排序模式 sortMode 仍由应用层维护,因此单独保存。 XMLElement* sortModeElement = doc.NewElement("SortMode"); sortModeElement->SetAttribute("value", configResult.algorithmParams.sortMode); algoParamsElement->InsertEndChild(sortModeElement); ConfigXmlUtils::SavePlaneCalibParams(doc, algoParamsElement, configResult.algorithmParams.planeCalibParam); ConfigXmlUtils::SaveDebugParam(doc, root, configResult.debugParam); ConfigXmlUtils::SaveSerialConfig(doc, root, configResult.serialConfig); ConfigXmlUtils::SaveHandEyeCalibMatrixs(doc, root, configResult.handEyeCalibMatrixList); // 网络输出配置单独放在根节点下,和算法参数解耦。 XMLElement* tcpServerConfigElement = doc.NewElement("TcpServerConfig"); tcpServerConfigElement->SetAttribute("tcpServerPort", configResult.plcRobotServerConfig.tcpServerPort); tcpServerConfigElement->SetAttribute("poseOutputOrder", configResult.plcRobotServerConfig.poseOutputOrder); tcpServerConfigElement->SetAttribute("dirVectorInvert", configResult.plcRobotServerConfig.dirVectorInvert); root->InsertEndChild(tcpServerConfigElement); XMLError err = doc.SaveFile(filePath.c_str()); if (err != XML_SUCCESS) { std::cerr << "无法保存配置文件: " << filePath << std::endl; return false; } // 保存成功后通知上层刷新运行时配置。 if (m_pNotify) { m_pNotify->OnConfigChanged(configResult); } return true; } void CVrConfig::SetConfigChangeNotify(IVrConfigChangeNotify* notify) { m_pNotify = notify; } bool IVrConfig::CreateInstance(IVrConfig** ppVrConfig) { *ppVrConfig = new CVrConfig(); return *ppVrConfig != nullptr; }