| @@ -17,13 +17,13 @@ bool validateConfig(const ContactNodeConfig &config, std::string *error) | |||
| { | |||
| if (!config.address.isValid() || config.address.area() != RegisterArea::M) | |||
| { | |||
| setError(error, "contact node requires a valid M address"); | |||
| setError(error, "触点节点必须使用有效的 M 区地址"); | |||
| return false; | |||
| } | |||
| if (config.mode != ContactMode::NormallyOpen | |||
| && config.mode != ContactMode::NormallyClosed) | |||
| { | |||
| setError(error, "contact node has an unsupported mode"); | |||
| setError(error, "触点节点使用了不支持的模式"); | |||
| return false; | |||
| } | |||
| return true; | |||
| @@ -33,14 +33,14 @@ bool validateConfig(const CoilNodeConfig &config, std::string *error) | |||
| { | |||
| if (!config.address.isValid() || config.address.area() != RegisterArea::M) | |||
| { | |||
| setError(error, "coil node requires a valid M address"); | |||
| setError(error, "线圈节点必须使用有效的 M 区地址"); | |||
| return false; | |||
| } | |||
| if (config.mode != CoilMode::Normal | |||
| && config.mode != CoilMode::Set | |||
| && config.mode != CoilMode::Reset) | |||
| { | |||
| setError(error, "coil node has an unsupported mode"); | |||
| setError(error, "线圈节点使用了不支持的模式"); | |||
| return false; | |||
| } | |||
| return true; | |||
| @@ -50,7 +50,7 @@ bool validateConfig(const CompareNodeConfig &config, std::string *error) | |||
| { | |||
| if (!config.address.isValid() || config.address.area() != RegisterArea::D) | |||
| { | |||
| setError(error, "comparison node requires a valid D address"); | |||
| setError(error, "比较节点必须使用有效的 D 区地址"); | |||
| return false; | |||
| } | |||
| if (config.comparison != ComparisonOperator::Equal | |||
| @@ -60,7 +60,7 @@ bool validateConfig(const CompareNodeConfig &config, std::string *error) | |||
| && config.comparison != ComparisonOperator::GreaterThan | |||
| && config.comparison != ComparisonOperator::GreaterThanOrEqual) | |||
| { | |||
| setError(error, "comparison node has an unsupported operator"); | |||
| setError(error, "比较节点使用了不支持的运算符"); | |||
| return false; | |||
| } | |||
| return true; | |||
| @@ -93,7 +93,7 @@ bool validateUniqueExpressionIds( | |||
| std::sort(ids.begin(), ids.end()); | |||
| if (std::adjacent_find(ids.cbegin(), ids.cend()) != ids.cend()) | |||
| { | |||
| setError(error, "condition expression ids must be unique within a rung"); | |||
| setError(error, "同一网络内的条件表达式 ID 必须唯一"); | |||
| return false; | |||
| } | |||
| return true; | |||
| @@ -138,7 +138,7 @@ bool LogicNode::validate(std::string *error) const | |||
| { | |||
| if (id.empty()) | |||
| { | |||
| setError(error, "logic node id must not be empty"); | |||
| setError(error, "逻辑节点 ID 不能为空"); | |||
| return false; | |||
| } | |||
| return std::visit( | |||
| @@ -174,28 +174,28 @@ bool ConditionExpression::validate(std::string *error) const | |||
| { | |||
| if (id.empty()) | |||
| { | |||
| setError(error, "condition expression id must not be empty"); | |||
| setError(error, "条件表达式 ID 不能为空"); | |||
| return false; | |||
| } | |||
| if (kind == ConditionExpressionKind::Node) | |||
| { | |||
| if (!node.has_value() || !children.empty() || !node->isCondition()) | |||
| { | |||
| setError(error, "condition leaf requires one condition node and no children"); | |||
| setError(error, "条件叶节点必须包含一个条件节点且不能包含子表达式"); | |||
| return false; | |||
| } | |||
| return node->validate(error); | |||
| } | |||
| if (node.has_value() || children.size() < 2U) | |||
| { | |||
| setError(error, "series and parallel expressions require at least two children"); | |||
| setError(error, "串联和并联表达式至少需要两个子表达式"); | |||
| return false; | |||
| } | |||
| for (const ConditionExpression &child : children) | |||
| { | |||
| if (child.kind == kind) | |||
| { | |||
| setError(error, "nested expressions of the same kind must be normalized"); | |||
| setError(error, "同类型嵌套表达式必须先完成归一化"); | |||
| return false; | |||
| } | |||
| if (!child.validate(error)) | |||
| @@ -216,7 +216,7 @@ bool ConditionExpression::validateForRunning(std::string *error) const | |||
| { | |||
| if (!node->isConfigured()) | |||
| { | |||
| setError(error, "ladder condition " + node->id + " is not configured"); | |||
| setError(error, "梯形图条件节点 " + node->id + " 尚未配置"); | |||
| return false; | |||
| } | |||
| return true; | |||
| @@ -343,7 +343,7 @@ bool LadderRung::validateForRunning(std::string *error) const | |||
| } | |||
| if (!condition.has_value() || !output.has_value()) | |||
| { | |||
| setError(error, "incomplete ladder network requires conditions and an output coil"); | |||
| setError(error, "未完成的梯形图网络必须同时包含条件和输出线圈"); | |||
| return false; | |||
| } | |||
| if (!condition->validateForRunning(error)) | |||
| @@ -352,7 +352,7 @@ bool LadderRung::validateForRunning(std::string *error) const | |||
| } | |||
| if (!output->isConfigured()) | |||
| { | |||
| setError(error, "output coil " + output->id + " is not configured"); | |||
| setError(error, "输出线圈 " + output->id + " 尚未配置"); | |||
| return false; | |||
| } | |||
| return true; | |||
| @@ -362,7 +362,7 @@ bool LadderRung::validateStructure(std::string *error) const | |||
| { | |||
| if (id.empty() || name.empty()) | |||
| { | |||
| setError(error, "ladder rung id and name must not be empty"); | |||
| setError(error, "梯形图网络 ID 和名称不能为空"); | |||
| return false; | |||
| } | |||
| std::vector<std::string> node_ids; | |||
| @@ -383,7 +383,7 @@ bool LadderRung::validateStructure(std::string *error) const | |||
| { | |||
| if (!output->validate(error) || !output->isOutput()) | |||
| { | |||
| setError(error, "ladder rung output must be a coil node"); | |||
| setError(error, "梯形图网络输出必须是线圈节点"); | |||
| return false; | |||
| } | |||
| node_ids.push_back(output->id); | |||
| @@ -391,7 +391,7 @@ bool LadderRung::validateStructure(std::string *error) const | |||
| std::sort(node_ids.begin(), node_ids.end()); | |||
| if (std::adjacent_find(node_ids.cbegin(), node_ids.cend()) != node_ids.cend()) | |||
| { | |||
| setError(error, "logic node ids must be unique within a rung"); | |||
| setError(error, "同一网络内的逻辑节点 ID 必须唯一"); | |||
| return false; | |||
| } | |||
| return true; | |||
| @@ -417,12 +417,12 @@ bool ControlLogic::validateStructure(std::string *error) const | |||
| { | |||
| if (id.empty() || name.empty()) | |||
| { | |||
| setError(error, "control logic id and name must not be empty"); | |||
| setError(error, "控制逻辑 ID 和名称不能为空"); | |||
| return false; | |||
| } | |||
| if (hasDuplicateId(rungs)) | |||
| { | |||
| setError(error, "ladder rung ids must be unique within a logic"); | |||
| setError(error, "同一控制逻辑内的网络 ID 必须唯一"); | |||
| return false; | |||
| } | |||
| std::vector<std::string> node_ids; | |||
| @@ -452,13 +452,13 @@ bool ControlLogic::validateStructure(std::string *error) const | |||
| std::sort(expression_ids.begin(), expression_ids.end()); | |||
| if (std::adjacent_find(node_ids.cbegin(), node_ids.cend()) != node_ids.cend()) | |||
| { | |||
| setError(error, "logic node ids must be unique within a logic"); | |||
| setError(error, "同一控制逻辑内的逻辑节点 ID 必须唯一"); | |||
| return false; | |||
| } | |||
| if (std::adjacent_find(expression_ids.cbegin(), expression_ids.cend()) | |||
| != expression_ids.cend()) | |||
| { | |||
| setError(error, "condition expression ids must be unique within a logic"); | |||
| setError(error, "同一控制逻辑内的条件表达式 ID 必须唯一"); | |||
| return false; | |||
| } | |||
| return true; | |||
| @@ -31,19 +31,19 @@ bool HmiControl::validate(std::string *error) const | |||
| { | |||
| if (id.empty()) | |||
| { | |||
| setError(error, "HMI control id must not be empty"); | |||
| setError(error, "HMI 控件 ID 不能为空"); | |||
| return false; | |||
| } | |||
| if (bounds.width <= 0 || bounds.height <= 0) | |||
| { | |||
| setError(error, "HMI control bounds must have positive size"); | |||
| setError(error, "HMI 控件的宽度和高度必须大于 0"); | |||
| return false; | |||
| } | |||
| for (const auto &property : properties) | |||
| { | |||
| if (property.first.empty()) | |||
| { | |||
| setError(error, "HMI control property names must not be empty"); | |||
| setError(error, "HMI 控件属性名称不能为空"); | |||
| return false; | |||
| } | |||
| } | |||
| @@ -51,7 +51,7 @@ bool HmiControl::validate(std::string *error) const | |||
| { | |||
| if (binding.has_value() && binding->area() != RegisterArea::M) | |||
| { | |||
| setError(error, "button and indicator controls require an M address"); | |||
| setError(error, "按钮和指示灯必须绑定 M 区地址"); | |||
| return false; | |||
| } | |||
| } | |||
| @@ -59,13 +59,13 @@ bool HmiControl::validate(std::string *error) const | |||
| { | |||
| if (binding.has_value() && binding->area() != RegisterArea::D) | |||
| { | |||
| setError(error, "numeric controls require a D address"); | |||
| setError(error, "数值控件必须绑定 D 区地址"); | |||
| return false; | |||
| } | |||
| } | |||
| if (binding.has_value() && !binding->isValid()) | |||
| { | |||
| setError(error, "HMI control binding has an invalid address"); | |||
| setError(error, "HMI 控件绑定了无效地址"); | |||
| return false; | |||
| } | |||
| return true; | |||
| @@ -92,12 +92,12 @@ bool HmiPage::validate(std::string *error) const | |||
| { | |||
| if (id.empty() || name.empty()) | |||
| { | |||
| setError(error, "HMI page id and name must not be empty"); | |||
| setError(error, "HMI 页面 ID 和名称不能为空"); | |||
| return false; | |||
| } | |||
| if (width <= 0 || height <= 0) | |||
| { | |||
| setError(error, "HMI page size must be positive"); | |||
| setError(error, "HMI 页面尺寸必须大于 0"); | |||
| return false; | |||
| } | |||
| @@ -113,13 +113,13 @@ bool HmiPage::validate(std::string *error) const | |||
| || control.bounds.x > width - control.bounds.width | |||
| || control.bounds.y > height - control.bounds.height) | |||
| { | |||
| setError(error, "HMI control bounds must stay within the page"); | |||
| setError(error, "HMI 控件不能超出页面范围"); | |||
| return false; | |||
| } | |||
| // 检查控件 id 是否唯一 | |||
| if (std::find(ids.cbegin(), ids.cend(), control.id) != ids.cend()) | |||
| { | |||
| setError(error, "HMI control ids must be unique within a page"); | |||
| setError(error, "同一 HMI 页面内的控件 ID 必须唯一"); | |||
| return false; | |||
| } | |||
| ids.push_back(control.id); | |||
| @@ -137,7 +137,7 @@ bool HmiPage::validateForRunning(std::string *error) const | |||
| { | |||
| if (!control.isConfigured()) | |||
| { | |||
| setError(error, "HMI control " + control.id + " is not bound to a register"); | |||
| setError(error, "HMI 控件 " + control.id + " 尚未绑定寄存器"); | |||
| return false; | |||
| } | |||
| } | |||
| @@ -39,17 +39,17 @@ bool Project::validate(std::string *error) const | |||
| { | |||
| if (metadata.id.empty() || metadata.name.empty() || metadata.formatVersion.empty()) | |||
| { | |||
| setError(error, "project id, name and format version must not be empty"); | |||
| setError(error, "工程 ID、名称和格式版本不能为空"); | |||
| return false; | |||
| } | |||
| if (containsDuplicateId(hmiPages)) | |||
| { | |||
| setError(error, "HMI page ids must be unique within a project"); | |||
| setError(error, "工程内的 HMI 页面 ID 必须唯一"); | |||
| return false; | |||
| } | |||
| if (containsDuplicateId(controlLogics)) | |||
| { | |||
| setError(error, "control logic ids must be unique within a project"); | |||
| setError(error, "工程内的控制逻辑 ID 必须唯一"); | |||
| return false; | |||
| } | |||
| for (const HmiPage &page : hmiPages) | |||
| @@ -67,7 +67,7 @@ bool readValue( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::MissingField, | |||
| "missing required field " + fieldPath(context, field)); | |||
| "缺少必填字段:" + fieldPath(context, field)); | |||
| } | |||
| *value = object.value(key); | |||
| return true; | |||
| @@ -90,7 +90,7 @@ bool readString( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| fieldPath(context, field) + " must be a string"); | |||
| fieldPath(context, field) + " 必须是字符串"); | |||
| } | |||
| *value = toUtf8(json_value.toString()); | |||
| return true; | |||
| @@ -113,7 +113,7 @@ bool readBool( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| fieldPath(context, field) + " must be a boolean"); | |||
| fieldPath(context, field) + " 必须是布尔值"); | |||
| } | |||
| *value = json_value.toBool(); | |||
| return true; | |||
| @@ -138,7 +138,7 @@ bool readInt( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| fieldPath(context, field) + " must be an integer"); | |||
| fieldPath(context, field) + " 必须是整数"); | |||
| } | |||
| // Qt JSON 统一用 double 表示数值,需要额外确认它能无损转换为 int | |||
| @@ -148,7 +148,7 @@ bool readInt( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| fieldPath(context, field) + " is outside the supported integer range"); | |||
| fieldPath(context, field) + " 超出支持的整数范围"); | |||
| } | |||
| *value = static_cast<int>(number); | |||
| return true; | |||
| @@ -171,7 +171,7 @@ bool readObject( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| fieldPath(context, field) + " must be an object"); | |||
| fieldPath(context, field) + " 必须是对象"); | |||
| } | |||
| *value = json_value.toObject(); | |||
| return true; | |||
| @@ -194,7 +194,7 @@ bool readArray( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| fieldPath(context, field) + " must be an array"); | |||
| fieldPath(context, field) + " 必须是数组"); | |||
| } | |||
| *value = json_value.toArray(); | |||
| return true; | |||
| @@ -246,7 +246,7 @@ bool parseAddress( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| context + ".area must be M or D"); | |||
| context + ".area 必须是 M 或 D"); | |||
| } | |||
| *address = RegisterAddress{area, index}; | |||
| @@ -313,7 +313,7 @@ bool parseHmiControlType( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| "unsupported HMI control type " + value); | |||
| "不支持的 HMI 控件类型:" + value); | |||
| } | |||
| return true; | |||
| } | |||
| @@ -393,7 +393,7 @@ bool parseProperties( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| "HMI control property values must be strings"); | |||
| "HMI 控件属性值必须是字符串"); | |||
| } | |||
| properties->emplace(toUtf8(current.key()), toUtf8(current.value().toString())); | |||
| } | |||
| @@ -458,7 +458,7 @@ bool parseHmiControl( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| context + ".binding must be an object or null"); | |||
| context + ".binding 必须是对象或 null"); | |||
| } | |||
| RegisterAddress address{RegisterArea::M, 0}; | |||
| @@ -515,7 +515,7 @@ bool parseHmiPage( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| context + ".controls items must be objects"); | |||
| context + ".controls 的元素必须是对象"); | |||
| } | |||
| HmiControl control; | |||
| if (!parseHmiControl( | |||
| @@ -555,7 +555,7 @@ bool parseContactMode( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| "unsupported contact mode " + value); | |||
| "不支持的触点模式:" + value); | |||
| } | |||
| return true; | |||
| } | |||
| @@ -603,7 +603,7 @@ bool parseCoilMode(const std::string &value, CoilMode *mode, ParseState *state) | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| "unsupported coil mode " + value); | |||
| "不支持的线圈模式:" + value); | |||
| } | |||
| return true; | |||
| } | |||
| @@ -678,7 +678,7 @@ bool parseComparison( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| "unsupported comparison operator " + value); | |||
| "不支持的比较运算符:" + value); | |||
| } | |||
| return true; | |||
| } | |||
| @@ -804,7 +804,7 @@ bool parseNodeConfig( | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| "unsupported logic node type " + type); | |||
| "不支持的逻辑节点类型:" + type); | |||
| } | |||
| // 解析逻辑节点标识及其多态配置 | |||
| @@ -892,7 +892,7 @@ bool parseConditionExpression( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| context + ".kind must be node, series or parallel"); | |||
| context + ".kind 必须是 node、series 或 parallel"); | |||
| } | |||
| QJsonArray children; | |||
| if (!readArray(object, "children", context, &children, state)) | |||
| @@ -908,7 +908,7 @@ bool parseConditionExpression( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| context + ".children items must be objects"); | |||
| context + ".children 的元素必须是对象"); | |||
| } | |||
| ConditionExpression child; | |||
| if (!parseConditionExpression( | |||
| @@ -967,7 +967,7 @@ bool parseLadderRung( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| context + ".condition must be an object or null"); | |||
| context + ".condition 必须是对象或 null"); | |||
| } | |||
| else | |||
| { | |||
| @@ -988,7 +988,7 @@ bool parseLadderRung( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| context + ".output must be an object or null"); | |||
| context + ".output 必须是对象或 null"); | |||
| } | |||
| LogicNode node; | |||
| if (!parseLogicNode(output.toObject(), context + ".output", &node, state)) | |||
| @@ -1035,7 +1035,7 @@ bool parseControlLogic( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| context + ".rungs items must be objects"); | |||
| context + ".rungs 的元素必须是对象"); | |||
| } | |||
| LadderRung rung; | |||
| if (!parseLadderRung( | |||
| @@ -1095,7 +1095,7 @@ bool parseProject( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::UnsupportedVersion, | |||
| "unsupported project format version " + project->metadata.formatVersion); | |||
| "不支持的工程格式版本:" + project->metadata.formatVersion); | |||
| } | |||
| if (!readString(object, "id", "project", &project->metadata.id, state) | |||
| @@ -1114,7 +1114,7 @@ bool parseProject( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| "project.hmiPages items must be objects"); | |||
| "project.hmiPages 的元素必须是对象"); | |||
| } | |||
| HmiPage page; | |||
| if (!parseHmiPage( | |||
| @@ -1135,7 +1135,7 @@ bool parseProject( | |||
| { | |||
| return state->fail( | |||
| ProjectStorageError::InvalidField, | |||
| "project.controlLogics items must be objects"); | |||
| "project.controlLogics 的元素必须是对象"); | |||
| } | |||
| ControlLogic logic; | |||
| if (!parseControlLogic( | |||
| @@ -1174,7 +1174,7 @@ ProjectSaveResult JsonProjectStorage::save( | |||
| { | |||
| return {false, | |||
| ProjectStorageError::UnsupportedVersion, | |||
| "unsupported project format version " + project.metadata.formatVersion}; | |||
| "不支持的工程格式版本:" + project.metadata.formatVersion}; | |||
| } | |||
| // QSaveFile 先写临时文件,仅在 commit 成功后替换目标文件 | |||
| @@ -1226,7 +1226,7 @@ ProjectLoadResult JsonProjectStorage::load(const std::string &file_path) | |||
| if (parse_error.error != QJsonParseError::NoError || !document.isObject()) | |||
| { | |||
| const QString message = parse_error.error == QJsonParseError::NoError | |||
| ? QStringLiteral("project JSON root must be an object") | |||
| ? QStringLiteral("工程 JSON 根节点必须是对象") | |||
| : parse_error.errorString(); | |||
| return {false, | |||
| {}, | |||
| @@ -147,7 +147,7 @@ HmiEditorResult HmiEditorService::addControl( | |||
| const HmiPage *page = findPage(page_id); | |||
| if (page == nullptr) | |||
| { | |||
| return failure(HmiEditorError::PageNotFound, "HMI page was not found"); | |||
| return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面"); | |||
| } | |||
| // 新控件初始不绑定寄存器,避免自动分配地址造成误写风险 | |||
| @@ -172,11 +172,11 @@ HmiEditorResult HmiEditorService::removeControl( | |||
| { | |||
| if (findPage(page_id) == nullptr) | |||
| { | |||
| return failure(HmiEditorError::PageNotFound, "HMI page was not found"); | |||
| return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面"); | |||
| } | |||
| if (findControl(page_id, control_id) == nullptr) | |||
| { | |||
| return failure(HmiEditorError::ControlNotFound, "HMI control was not found"); | |||
| return failure(HmiEditorError::ControlNotFound, "未找到 HMI 控件"); | |||
| } | |||
| Project &project = project_service_.editProject(); | |||
| @@ -209,11 +209,11 @@ HmiEditorResult HmiEditorService::moveControl( | |||
| const HmiControl *control = findControl(page_id, control_id); | |||
| if (page == nullptr) | |||
| { | |||
| return failure(HmiEditorError::PageNotFound, "HMI page was not found"); | |||
| return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面"); | |||
| } | |||
| if (control == nullptr) | |||
| { | |||
| return failure(HmiEditorError::ControlNotFound, "HMI control was not found"); | |||
| return failure(HmiEditorError::ControlNotFound, "未找到 HMI 控件"); | |||
| } | |||
| // 先校验候选位置,失败时不修改工程模型 | |||
| @@ -247,15 +247,15 @@ HmiEditorResult HmiEditorService::updateControl( | |||
| const HmiControl *existing = findControl(page_id, control_id); | |||
| if (page == nullptr) | |||
| { | |||
| return failure(HmiEditorError::PageNotFound, "HMI page was not found"); | |||
| return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面"); | |||
| } | |||
| if (existing == nullptr) | |||
| { | |||
| return failure(HmiEditorError::ControlNotFound, "HMI control was not found"); | |||
| return failure(HmiEditorError::ControlNotFound, "未找到 HMI 控件"); | |||
| } | |||
| if (control.type != existing->type) | |||
| { | |||
| return failure(HmiEditorError::InvalidControl, "HMI control type cannot be changed"); | |||
| return failure(HmiEditorError::InvalidControl, "不能修改已创建 HMI 控件的类型"); | |||
| } | |||
| std::string error; | |||
| @@ -265,7 +265,7 @@ HmiEditorResult HmiEditorService::updateControl( | |||
| } | |||
| if (hasDuplicateControlId(*page, control_id, control.id)) | |||
| { | |||
| return failure(HmiEditorError::DuplicateId, "HMI control id must be unique within a page"); | |||
| return failure(HmiEditorError::DuplicateId, "同一 HMI 页面内的控件 ID 必须唯一"); | |||
| } | |||
| Project &project = project_service_.editProject(); | |||
| @@ -284,7 +284,7 @@ bool HmiEditorService::validateEditableControl( | |||
| { | |||
| if (control.id.empty()) | |||
| { | |||
| setError(error, "HMI control id must not be empty"); | |||
| setError(error, "HMI 控件 ID 不能为空"); | |||
| return false; | |||
| } | |||
| if (control.bounds.width <= 0 || control.bounds.height <= 0 | |||
| @@ -293,20 +293,20 @@ bool HmiEditorService::validateEditableControl( | |||
| || control.bounds.x > page.width - control.bounds.width | |||
| || control.bounds.y > page.height - control.bounds.height) | |||
| { | |||
| setError(error, "HMI control bounds must stay within the page"); | |||
| setError(error, "HMI 控件不能超出页面范围"); | |||
| return false; | |||
| } | |||
| for (const auto &property : control.properties) | |||
| { | |||
| if (property.first.empty()) | |||
| { | |||
| setError(error, "HMI control property names must not be empty"); | |||
| setError(error, "HMI 控件属性名称不能为空"); | |||
| return false; | |||
| } | |||
| } | |||
| if (control.binding.has_value() && !control.binding->isValid()) | |||
| { | |||
| setError(error, "HMI control binding has an invalid address"); | |||
| setError(error, "HMI 控件绑定了无效地址"); | |||
| return false; | |||
| } | |||
| if (control.binding.has_value() | |||
| @@ -314,7 +314,7 @@ bool HmiEditorService::validateEditableControl( | |||
| || control.type == HmiControlType::Indicator) | |||
| && control.binding->area() != RegisterArea::M) | |||
| { | |||
| setError(error, "button and indicator controls require an M address"); | |||
| setError(error, "按钮和指示灯必须绑定 M 区地址"); | |||
| return false; | |||
| } | |||
| if (control.binding.has_value() | |||
| @@ -322,7 +322,7 @@ bool HmiEditorService::validateEditableControl( | |||
| || control.type == HmiControlType::NumericInput) | |||
| && control.binding->area() != RegisterArea::D) | |||
| { | |||
| setError(error, "numeric controls require a D address"); | |||
| setError(error, "数值控件必须绑定 D 区地址"); | |||
| return false; | |||
| } | |||
| return true; | |||
| @@ -346,7 +346,7 @@ LogicEditorResult LogicEditorService::addRung(const std::string &logic_id) | |||
| const ControlLogic *logic = findLogic(logic_id); | |||
| if (logic == nullptr) | |||
| { | |||
| return failure(LogicEditorError::LogicNotFound, "control logic was not found"); | |||
| return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); | |||
| } | |||
| LadderRung rung; | |||
| rung.id = makeUniqueRungId(*logic); | |||
| @@ -365,15 +365,15 @@ LogicEditorResult LogicEditorService::removeRung( | |||
| const ControlLogic *logic = findLogic(logic_id); | |||
| if (logic == nullptr) | |||
| { | |||
| return failure(LogicEditorError::LogicNotFound, "control logic was not found"); | |||
| return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); | |||
| } | |||
| if (findRung(logic_id, rung_id) == nullptr) | |||
| { | |||
| return failure(LogicEditorError::RungNotFound, "ladder rung was not found"); | |||
| return failure(LogicEditorError::RungNotFound, "未找到梯形图网络"); | |||
| } | |||
| if (logic->rungs.size() == 1U) | |||
| { | |||
| return failure(LogicEditorError::InvalidOperation, "control logic requires one rung"); | |||
| return failure(LogicEditorError::InvalidOperation, "控制逻辑至少需要保留一个网络"); | |||
| } | |||
| Project &project = project_service_.editProject(); | |||
| auto target = std::find_if( | |||
| @@ -397,11 +397,11 @@ LogicEditorResult LogicEditorService::appendCondition( | |||
| { | |||
| return failure( | |||
| logic == nullptr ? LogicEditorError::LogicNotFound : LogicEditorError::RungNotFound, | |||
| logic == nullptr ? "control logic was not found" : "ladder rung was not found"); | |||
| logic == nullptr ? "未找到控制逻辑" : "未找到梯形图网络"); | |||
| } | |||
| if (!isConditionConfig(config)) | |||
| { | |||
| return failure(LogicEditorError::InvalidNode, "ladder condition cannot be a coil"); | |||
| return failure(LogicEditorError::InvalidNode, "梯形图条件不能使用线圈节点"); | |||
| } | |||
| const std::string node_id = makeUniqueNodeId(*logic, nodePrefix(config)); | |||
| ConditionExpression leaf = ConditionExpression::fromNode(makeNode(node_id, config)); | |||
| @@ -435,7 +435,7 @@ LogicEditorResult LogicEditorService::insertConditionAfter( | |||
| const ControlLogic *logic = findLogic(logic_id); | |||
| if (logic == nullptr || findRung(logic_id, rung_id) == nullptr) | |||
| { | |||
| return failure(LogicEditorError::RungNotFound, "ladder network was not found"); | |||
| return failure(LogicEditorError::RungNotFound, "未找到梯形图网络"); | |||
| } | |||
| const LadderRung *existing_rung = findRung(logic_id, rung_id); | |||
| if (!isConditionConfig(config) | |||
| @@ -443,7 +443,7 @@ LogicEditorResult LogicEditorService::insertConditionAfter( | |||
| || !existing_rung->condition.has_value() | |||
| || findConditionNode(*existing_rung->condition, target_node_id) == nullptr) | |||
| { | |||
| return failure(LogicEditorError::NodeNotFound, "series insertion target was not found"); | |||
| return failure(LogicEditorError::NodeNotFound, "未找到串联插入目标"); | |||
| } | |||
| const std::string node_id = makeUniqueNodeId(*logic, nodePrefix(config)); | |||
| ConditionExpression leaf = ConditionExpression::fromNode(makeNode(node_id, config)); | |||
| @@ -482,30 +482,30 @@ LogicEditorResult LogicEditorService::addParallelBranch( | |||
| const ControlLogic *logic = findLogic(logic_id); | |||
| if (logic == nullptr || findRung(logic_id, rung_id) == nullptr) | |||
| { | |||
| return failure(LogicEditorError::RungNotFound, "ladder network was not found"); | |||
| return failure(LogicEditorError::RungNotFound, "未找到梯形图网络"); | |||
| } | |||
| if (!isConditionConfig(config) || selected_node_ids.empty()) | |||
| { | |||
| return failure(LogicEditorError::InvalidOperation, "parallel branch requires a selection"); | |||
| return failure(LogicEditorError::InvalidOperation, "建立并联支路前必须选择节点"); | |||
| } | |||
| const LadderRung *existing_rung = findRung(logic_id, rung_id); | |||
| if (!existing_rung->condition.has_value()) | |||
| { | |||
| return failure(LogicEditorError::InvalidOperation, "ladder network has no condition"); | |||
| return failure(LogicEditorError::InvalidOperation, "梯形图网络尚无条件节点"); | |||
| } | |||
| NodeIdSet selected_ids(selected_node_ids.cbegin(), selected_node_ids.cend()); | |||
| if (selected_ids.size() != selected_node_ids.size()) | |||
| { | |||
| return failure( | |||
| LogicEditorError::InvalidOperation, | |||
| "parallel selection contains duplicates"); | |||
| "并联选择中存在重复节点"); | |||
| } | |||
| const NodeIdSet available_ids = conditionNodeIds(*existing_rung->condition); | |||
| if (!isSubset(selected_ids, available_ids)) | |||
| { | |||
| return failure( | |||
| LogicEditorError::NodeNotFound, | |||
| "parallel selection contains an unknown node"); | |||
| "并联选择中包含未知节点"); | |||
| } | |||
| const std::string node_id = makeUniqueNodeId(*logic, nodePrefix(config)); | |||
| ConditionExpression leaf = ConditionExpression::fromNode(makeNode(node_id, config)); | |||
| @@ -526,7 +526,7 @@ LogicEditorResult LogicEditorService::addParallelBranch( | |||
| { | |||
| return failure( | |||
| LogicEditorError::InvalidOperation, | |||
| "parallel selection must be one continuous logic range"); | |||
| "并联选择必须是一个连续的逻辑范围"); | |||
| } | |||
| return {true, LogicEditorError::None, {}, node_id}; | |||
| } | |||
| @@ -540,7 +540,7 @@ LogicEditorResult LogicEditorService::setOutput( | |||
| const LadderRung *existing_rung = findRung(logic_id, rung_id); | |||
| if (logic == nullptr || existing_rung == nullptr || !isOutputConfig(config)) | |||
| { | |||
| return failure(LogicEditorError::InvalidNode, "ladder output requires a valid coil"); | |||
| return failure(LogicEditorError::InvalidNode, "梯形图输出必须使用有效线圈"); | |||
| } | |||
| const std::string node_id = existing_rung->output.has_value() | |||
| ? existing_rung->output->id : makeUniqueNodeId(*logic, nodePrefix(config)); | |||
| @@ -557,13 +557,13 @@ LogicEditorResult LogicEditorService::updateNodeConfig( | |||
| const LogicNode *node = findNode(logic_id, node_id); | |||
| if (node == nullptr) | |||
| { | |||
| return failure(LogicEditorError::NodeNotFound, "logic node was not found"); | |||
| return failure(LogicEditorError::NodeNotFound, "未找到逻辑节点"); | |||
| } | |||
| if (node->config.index() != config.index()) | |||
| { | |||
| return failure( | |||
| LogicEditorError::UnsupportedNodeChange, | |||
| "node category cannot be changed after creation"); | |||
| "节点创建后不能修改节点类别"); | |||
| } | |||
| LogicNode candidate{node_id, config, true}; | |||
| std::string error; | |||
| @@ -595,7 +595,7 @@ LogicEditorResult LogicEditorService::updateNodeConfig( | |||
| } | |||
| } | |||
| } | |||
| return failure(LogicEditorError::NodeNotFound, "logic node was not found"); | |||
| return failure(LogicEditorError::NodeNotFound, "未找到逻辑节点"); | |||
| } | |||
| LogicEditorResult LogicEditorService::removeNode( | |||
| @@ -604,7 +604,7 @@ LogicEditorResult LogicEditorService::removeNode( | |||
| const std::string rung_id = rungIdForNode(logic_id, node_id); | |||
| if (rung_id.empty()) | |||
| { | |||
| return failure(LogicEditorError::NodeNotFound, "logic node was not found"); | |||
| return failure(LogicEditorError::NodeNotFound, "未找到逻辑节点"); | |||
| } | |||
| const LadderRung *rung = findRung(logic_id, rung_id); | |||
| if (rung->output.has_value() && rung->output->id == node_id) | |||
| @@ -623,7 +623,7 @@ LogicEditorResult LogicEditorService::removeExpression( | |||
| { | |||
| if (findExpression(logic_id, rung_id, expression_id) == nullptr) | |||
| { | |||
| return failure(LogicEditorError::ExpressionNotFound, "condition branch was not found"); | |||
| return failure(LogicEditorError::ExpressionNotFound, "未找到条件支路"); | |||
| } | |||
| Project &project = project_service_.editProject(); | |||
| LadderRung *rung = findEditableRung(project, logic_id, rung_id); | |||
| @@ -61,7 +61,7 @@ LogicScanResult OfflineSimulationService::executeOnce() | |||
| { | |||
| return {false, | |||
| LogicScanError::InvalidLogic, | |||
| "offline simulation is not running", | |||
| "离线仿真尚未运行", | |||
| {}, | |||
| {}, | |||
| {}}; | |||
| @@ -26,7 +26,7 @@ std::string generateProjectId() | |||
| ProjectService::ProjectService(ProjectStorage &storage) | |||
| : storage_(storage), | |||
| project_(makeNewProject("Untitled")) | |||
| project_(makeNewProject("未命名工程")) | |||
| { | |||
| } | |||
| @@ -64,7 +64,7 @@ ProjectOperationResult ProjectService::createNewProject(const std::string &name) | |||
| return {false, | |||
| ProjectServiceError::InvalidProjectName, | |||
| ProjectStorageError::None, | |||
| "project name must not be empty"}; | |||
| "工程名称不能为空"}; | |||
| } | |||
| // 新工程创建成功后清除原文件关联,并标记为待保存 | |||
| @@ -81,7 +81,7 @@ ProjectOperationResult ProjectService::save() | |||
| return {false, | |||
| ProjectServiceError::FilePathRequired, | |||
| ProjectStorageError::None, | |||
| "project file path is required"}; | |||
| "必须指定工程文件路径"}; | |||
| } | |||
| // 复用另存为流程,确保两种保存方式拥有相同的校验和错误处理 | |||
| return saveAs(current_file_path_); | |||
| @@ -94,7 +94,7 @@ ProjectOperationResult ProjectService::saveAs(const std::string &file_path) | |||
| return {false, | |||
| ProjectServiceError::FilePathRequired, | |||
| ProjectStorageError::None, | |||
| "project file path is required"}; | |||
| "必须指定工程文件路径"}; | |||
| } | |||
| std::string validation_error; | |||
| @@ -126,7 +126,7 @@ ProjectOperationResult ProjectService::load(const std::string &file_path) | |||
| return {false, | |||
| ProjectServiceError::FilePathRequired, | |||
| ProjectStorageError::None, | |||
| "project file path is required"}; | |||
| "必须指定工程文件路径"}; | |||
| } | |||
| ProjectLoadResult result = storage_.load(file_path); | |||
| @@ -176,7 +176,7 @@ PlcCommunicationResult RuntimeModeService::connectPlc( | |||
| { | |||
| if (plc_gateway_ == nullptr) | |||
| { | |||
| return {false, "PLC communication is not configured"}; | |||
| return {false, "PLC 通信服务尚未配置"}; | |||
| } | |||
| std::vector<RegisterAddress> addresses; | |||
| const Project &project = project_service_.project(); | |||
| @@ -96,7 +96,7 @@ LogicScanResult SoftwareLogicExecutor::validate( | |||
| { | |||
| return failure( | |||
| LogicScanError::InvalidLogic, | |||
| "ladder output is not a coil", | |||
| "梯形图输出不是线圈节点", | |||
| logic.id, | |||
| rung.id, | |||
| rung.output->id); | |||
| @@ -106,7 +106,7 @@ LogicScanResult SoftwareLogicExecutor::validate( | |||
| { | |||
| return failure( | |||
| LogicScanError::ConflictingOutput, | |||
| "mixed coil modes target " + output->address.toString(), | |||
| "同一地址使用了不同线圈模式:" + output->address.toString(), | |||
| logic.id, | |||
| rung.id, | |||
| rung.output->id); | |||
| @@ -180,7 +180,7 @@ LogicScanResult SoftwareLogicExecutor::evaluateExpression( | |||
| { | |||
| if (value == nullptr) | |||
| { | |||
| return failure(LogicScanError::InvalidLogic, "expression result target is missing"); | |||
| return failure(LogicScanError::InvalidLogic, "缺少表达式结果接收对象"); | |||
| } | |||
| if (expression.kind == ConditionExpressionKind::Node) | |||
| { | |||
| @@ -222,7 +222,7 @@ LogicScanResult SoftwareLogicExecutor::evaluateCondition( | |||
| { | |||
| return failure( | |||
| LogicScanError::InvalidLogic, | |||
| "condition result target is missing", | |||
| "缺少条件结果接收对象", | |||
| {}, | |||
| {}, | |||
| node.id); | |||
| @@ -239,7 +239,7 @@ LogicScanResult SoftwareLogicExecutor::evaluateCondition( | |||
| { | |||
| return failure( | |||
| LogicScanError::RegisterReadFailed, | |||
| "failed to read " + config.address.toString(), | |||
| "读取寄存器失败:" + config.address.toString(), | |||
| {}, | |||
| {}, | |||
| node.id); | |||
| @@ -255,7 +255,7 @@ LogicScanResult SoftwareLogicExecutor::evaluateCondition( | |||
| { | |||
| return failure( | |||
| LogicScanError::RegisterReadFailed, | |||
| "failed to read " + config.address.toString(), | |||
| "读取寄存器失败:" + config.address.toString(), | |||
| {}, | |||
| {}, | |||
| node.id); | |||
| @@ -267,7 +267,7 @@ LogicScanResult SoftwareLogicExecutor::evaluateCondition( | |||
| { | |||
| return failure( | |||
| LogicScanError::InvalidLogic, | |||
| "condition node contains an output coil", | |||
| "条件节点不能包含输出线圈", | |||
| {}, | |||
| {}, | |||
| node.id); | |||
| @@ -286,7 +286,7 @@ LogicScanResult SoftwareLogicExecutor::writeOutput( | |||
| { | |||
| return failure( | |||
| LogicScanError::InvalidLogic, | |||
| "ladder output is not a coil", | |||
| "梯形图输出不是线圈节点", | |||
| {}, | |||
| {}, | |||
| node.id); | |||
| @@ -316,7 +316,7 @@ LogicScanResult SoftwareLogicExecutor::writeOutput( | |||
| { | |||
| return failure( | |||
| LogicScanError::InvalidLogic, | |||
| "unsupported coil mode", | |||
| "不支持的线圈模式", | |||
| {}, | |||
| {}, | |||
| node.id); | |||
| @@ -333,7 +333,7 @@ LogicScanResult SoftwareLogicExecutor::writeOutput( | |||
| { | |||
| return failure( | |||
| LogicScanError::RegisterWriteFailed, | |||
| "failed to write " + config->address.toString(), | |||
| "写入寄存器失败:" + config->address.toString(), | |||
| {}, | |||
| {}, | |||
| node.id); | |||