#include "logic_editor_service.h" #include "project_service.h" #include #include #include #include #include #include #include namespace { bool isBlank(const std::string &value) { return value.empty() || std::all_of( value.cbegin(), value.cend(), [](unsigned char character) { return std::isspace(character) != 0; }); } std::string makeUniqueLogicId(const Project &project) { int suffix = 1; while (true) { const std::string candidate = "logic-" + std::to_string(suffix); const bool found = std::any_of( project.controlLogics.cbegin(), project.controlLogics.cend(), [&candidate](const ControlLogic &logic) { return logic.id == candidate; }); if (!found) { return candidate; } ++suffix; } } LogicNode makeNode(const std::string &id, const LogicNodeConfig &config) { return {id, config, false}; } ConditionExpression makeContainer( const std::string &id, ConditionExpressionKind kind, ConditionExpression first, ConditionExpression second) { ConditionExpression expression; expression.id = id; expression.kind = kind; expression.children.push_back(std::move(first)); expression.children.push_back(std::move(second)); return expression; } ConditionExpression *findParentExpression( ConditionExpression &expression, const std::string &child_id) { for (ConditionExpression &child : expression.children) { if (child.id == child_id) { return &expression; } if (ConditionExpression *parent = findParentExpression(child, child_id)) { return parent; } } return nullptr; } using NodeIdSet = std::unordered_set; NodeIdSet conditionLeafIds(const ConditionExpression &expression) { NodeIdSet ids; if (expression.kind == ConditionExpressionKind::Node || expression.kind == ConditionExpressionKind::Wire) { ids.insert(expression.id); return ids; } for (const ConditionExpression &child : expression.children) { NodeIdSet child_ids = conditionLeafIds(child); ids.insert(child_ids.cbegin(), child_ids.cend()); } return ids; } int expressionColumns(const ConditionExpression &expression) { if (expression.kind == ConditionExpressionKind::Node) { return 1; } if (expression.kind == ConditionExpressionKind::Wire) { return expression.wire->columnSpan; } int columns = expression.kind == ConditionExpressionKind::Series ? 0 : 1; for (const ConditionExpression &child : expression.children) { const int child_columns = expressionColumns(child); columns = expression.kind == ConditionExpressionKind::Series ? columns + child_columns : std::max(columns, child_columns); } return columns; } bool isSubset(const NodeIdSet &subset, const NodeIdSet &values) { return std::all_of( subset.cbegin(), subset.cend(), [&values](const std::string &value) { return values.count(value) != 0U; }); } bool sameValues(const NodeIdSet &left, const NodeIdSet &right) { return left.size() == right.size() && isSubset(left, right); } void addParallelSibling( ConditionExpression *target, ConditionExpression branch, const std::string &container_id) { if (target->kind == ConditionExpressionKind::Parallel) { target->children.push_back(std::move(branch)); return; } ConditionExpression original = std::move(*target); *target = makeContainer( container_id, ConditionExpressionKind::Parallel, std::move(original), std::move(branch)); } bool addParallelForSelection( ConditionExpression *expression, const NodeIdSet &selected_ids, ConditionExpression *branch, const std::string ¶llel_id, const std::string &series_id) { const NodeIdSet expression_ids = conditionLeafIds(*expression); if (sameValues(expression_ids, selected_ids)) { addParallelSibling(expression, std::move(*branch), parallel_id); return true; } std::vector child_ids; child_ids.reserve(expression->children.size()); std::vector matching_children; for (std::size_t index = 0; index < expression->children.size(); ++index) { child_ids.push_back(conditionLeafIds(expression->children[index])); const bool intersects = std::any_of( child_ids.back().cbegin(), child_ids.back().cend(), [&selected_ids](const std::string &id) { return selected_ids.count(id) != 0U; }); if (intersects) { matching_children.push_back(index); } } if (matching_children.empty()) { return false; } if (matching_children.size() == 1U) { const std::size_t child_index = matching_children.front(); if (expression->kind == ConditionExpressionKind::Parallel && sameValues(child_ids[child_index], selected_ids)) { expression->children.push_back(std::move(*branch)); return true; } return addParallelForSelection( &expression->children[child_index], selected_ids, branch, parallel_id, series_id); } if (expression->kind != ConditionExpressionKind::Series) { return false; } const std::size_t first = matching_children.front(); const std::size_t last = matching_children.back(); if (last - first + 1U != matching_children.size()) { return false; } NodeIdSet range_ids; for (std::size_t index = first; index <= last; ++index) { range_ids.insert(child_ids[index].cbegin(), child_ids[index].cend()); } if (!sameValues(range_ids, selected_ids)) { return false; } ConditionExpression range; range.id = series_id; range.kind = ConditionExpressionKind::Series; auto range_begin = expression->children.begin() + static_cast(first); auto range_end = expression->children.begin() + static_cast(last + 1U); range.children.insert( range.children.end(), std::make_move_iterator(range_begin), std::make_move_iterator(range_end)); range_begin = expression->children.erase(range_begin, range_end); ConditionExpression parallel = makeContainer( parallel_id, ConditionExpressionKind::Parallel, std::move(range), std::move(*branch)); expression->children.insert(range_begin, std::move(parallel)); return true; } bool removeExpressionRecursive( ConditionExpression *expression, const std::string &expression_id) { if (expression == nullptr || expression->kind == ConditionExpressionKind::Node || expression->kind == ConditionExpressionKind::Wire) { return false; } const auto removable = std::find_if( expression->children.begin(), expression->children.end(), [&expression_id](const ConditionExpression &child) { return child.id == expression_id; }); if (removable != expression->children.end()) { expression->children.erase(removable); return true; } for (ConditionExpression &child : expression->children) { if (removeExpressionRecursive(&child, expression_id)) { return true; } } return false; } std::optional selectedExpressionColumns( const ConditionExpression &expression, const NodeIdSet &selected_ids) { const NodeIdSet expression_ids = conditionLeafIds(expression); if (sameValues(expression_ids, selected_ids)) { return expressionColumns(expression); } if (expression.kind == ConditionExpressionKind::Node || expression.kind == ConditionExpressionKind::Wire) { return std::nullopt; } std::vector child_ids; std::vector matching_children; child_ids.reserve(expression.children.size()); for (std::size_t index = 0; index < expression.children.size(); ++index) { child_ids.push_back(conditionLeafIds(expression.children[index])); const bool intersects = std::any_of( child_ids.back().cbegin(), child_ids.back().cend(), [&selected_ids](const std::string &id) { return selected_ids.count(id) != 0U; }); if (intersects) { matching_children.push_back(index); } } if (matching_children.size() == 1U) { return selectedExpressionColumns( expression.children[matching_children.front()], selected_ids); } if (matching_children.empty() || expression.kind != ConditionExpressionKind::Series) { return std::nullopt; } const std::size_t first = matching_children.front(); const std::size_t last = matching_children.back(); if (last - first + 1U != matching_children.size()) { return std::nullopt; } NodeIdSet range_ids; int columns = 0; for (std::size_t index = first; index <= last; ++index) { range_ids.insert(child_ids[index].cbegin(), child_ids[index].cend()); columns += expressionColumns(expression.children[index]); } return sameValues(range_ids, selected_ids) ? std::optional{columns} : std::nullopt; } LadderRung *findEditableRung( Project &project, const std::string &logic_id, const std::string &rung_id) { const auto logic = std::find_if( project.controlLogics.begin(), project.controlLogics.end(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); if (logic == project.controlLogics.end()) { return nullptr; } const auto rung = std::find_if( logic->rungs.begin(), logic->rungs.end(), [&rung_id](const LadderRung &candidate) { return candidate.id == rung_id; }); return rung == logic->rungs.end() ? nullptr : &*rung; } } // namespace LogicEditorService::LogicEditorService(ProjectService &project_service) : project_service_(project_service) { } LogicEditorService::HistoryState LogicEditorService::captureState() const { return {project_service_.project().controlLogics}; } void LogicEditorService::recordHistory(HistoryState before) { const HistoryState after = captureState(); history_.record(std::move(before), after, &LogicEditorService::statesEqual); } bool LogicEditorService::statesEqual( const HistoryState &left, const HistoryState &right) { if (left.logics.size() != right.logics.size()) { return false; } for (std::size_t index = 0; index < left.logics.size(); ++index) { if (!logicsEqual(left.logics[index], right.logics[index])) { return false; } } return true; } bool LogicEditorService::logicsEqual( const ControlLogic &left, const ControlLogic &right) { if (left.id != right.id || left.name != right.name || left.enabled != right.enabled || left.rungs.size() != right.rungs.size()) { return false; } for (std::size_t index = 0; index < left.rungs.size(); ++index) { if (!rungsEqual(left.rungs[index], right.rungs[index])) { return false; } } return true; } bool LogicEditorService::rungsEqual( const LadderRung &left, const LadderRung &right) { if (left.id != right.id || left.name != right.name || left.comment != right.comment || left.condition.has_value() != right.condition.has_value() || left.output.has_value() != right.output.has_value()) { return false; } return (!left.condition.has_value() || expressionsEqual(*left.condition, *right.condition)) && (!left.output.has_value() || nodesEqual(*left.output, *right.output)); } bool LogicEditorService::expressionsEqual( const ConditionExpression &left, const ConditionExpression &right) { if (left.id != right.id || left.kind != right.kind || left.node.has_value() != right.node.has_value() || left.wire.has_value() != right.wire.has_value() || left.children.size() != right.children.size()) { return false; } if (left.node.has_value() && !nodesEqual(*left.node, *right.node)) { return false; } if (left.wire.has_value() && left.wire->columnSpan != right.wire->columnSpan) { return false; } for (std::size_t index = 0; index < left.children.size(); ++index) { if (!expressionsEqual(left.children[index], right.children[index])) { return false; } } return true; } bool LogicEditorService::nodesEqual( const LogicNode &left, const LogicNode &right) { return left.id == right.id && left.config.index() == right.config.index() && left.configured == right.configured && configsEqual(left.config, right.config); } bool LogicEditorService::configsEqual( const LogicNodeConfig &left, const LogicNodeConfig &right) { return std::visit( [](const auto &left_config, const auto &right_config) { using Left = std::decay_t; using Right = std::decay_t; if constexpr (!std::is_same_v) { return false; } else if constexpr (std::is_same_v) { return left_config.address == right_config.address && left_config.mode == right_config.mode; } else if constexpr (std::is_same_v) { return left_config.address == right_config.address && left_config.mode == right_config.mode; } else if constexpr (std::is_same_v) { return left_config.address == right_config.address && left_config.mode == right_config.mode; } else if constexpr (std::is_same_v) { return left_config.address == right_config.address && left_config.mode == right_config.mode; } else if constexpr (std::is_same_v) { return left_config.address == right_config.address && left_config.mode == right_config.mode; } else if constexpr (std::is_same_v) { return left_config.address == right_config.address && left_config.comparison == right_config.comparison && left_config.value == right_config.value; } else if constexpr (std::is_same_v) { return left_config.address == right_config.address && left_config.presetMs == right_config.presetMs; } else if constexpr (std::is_same_v) { return left_config.address == right_config.address && left_config.mode == right_config.mode && left_config.currentValueAddress == right_config.currentValueAddress && left_config.preset.kind == right_config.preset.kind && left_config.preset.address == right_config.preset.address && left_config.preset.constant == right_config.preset.constant && left_config.resetAddress == right_config.resetAddress; } else if constexpr (std::is_same_v) { return left_config.source.kind == right_config.source.kind && left_config.source.address == right_config.source.address && left_config.source.constant == right_config.source.constant && left_config.destination == right_config.destination; } else { return left_config.operation == right_config.operation && left_config.left.kind == right_config.left.kind && left_config.left.address == right_config.left.address && left_config.left.constant == right_config.left.constant && left_config.right.kind == right_config.right.kind && left_config.right.address == right_config.right.address && left_config.right.constant == right_config.right.constant && left_config.destination == right_config.destination; } }, left, right); } LogicEditorResult LogicEditorService::historyFailure(const std::string &message) { return {false, LogicEditorError::InvalidOperation, message, {}}; } const ControlLogic *LogicEditorService::findLogic(const std::string &logic_id) const { const auto &logics = project_service_.project().controlLogics; const auto logic = std::find_if( logics.cbegin(), logics.cend(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); return logic == logics.cend() ? nullptr : &*logic; } const LadderRung *LogicEditorService::findRung( const std::string &logic_id, const std::string &rung_id) const { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return nullptr; } const auto rung = std::find_if( logic->rungs.cbegin(), logic->rungs.cend(), [&rung_id](const LadderRung &candidate) { return candidate.id == rung_id; }); return rung == logic->rungs.cend() ? nullptr : &*rung; } const LogicNode *LogicEditorService::findNode( const std::string &logic_id, const std::string &node_id) const { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return nullptr; } for (const LadderRung &rung : logic->rungs) { if (rung.output.has_value() && rung.output->id == node_id) { return &*rung.output; } if (rung.condition.has_value()) { if (const LogicNode *node = findConditionNode(*rung.condition, node_id)) { return node; } } } return nullptr; } const ConditionExpression *LogicEditorService::findExpression( const std::string &logic_id, const std::string &rung_id, const std::string &expression_id) const { const LadderRung *rung = findRung(logic_id, rung_id); return rung == nullptr || !rung->condition.has_value() ? nullptr : findConditionExpression(*rung->condition, expression_id); } std::string LogicEditorService::firstLogicId() const { const auto &logics = project_service_.project().controlLogics; return logics.empty() ? std::string{} : logics.front().id; } std::string LogicEditorService::firstRungId(const std::string &logic_id) const { const ControlLogic *logic = findLogic(logic_id); return logic == nullptr || logic->rungs.empty() ? std::string{} : logic->rungs.front().id; } std::string LogicEditorService::rungIdForNode( const std::string &logic_id, const std::string &node_id) const { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return {}; } for (const LadderRung &rung : logic->rungs) { if ((rung.output.has_value() && rung.output->id == node_id) || (rung.condition.has_value() && findConditionNode(*rung.condition, node_id) != nullptr)) { return rung.id; } } return {}; } std::string LogicEditorService::registerCommentFor( const RegisterAddress &address) const { const RegisterComment *comment = project_service_.project().findRegisterComment(address); return comment == nullptr ? std::string{} : comment->text; } LogicEditorResult LogicEditorService::ensureDefaultLogic() { if (!project_service_.project().controlLogics.empty()) { return {true, LogicEditorError::None, {}, firstLogicId()}; } ControlLogic logic; logic.id = "logic-1"; logic.name = "控制逻辑 1"; logic.rungs.push_back({"rung-1", "网络 1", {}, std::nullopt, std::nullopt}); Project &project = project_service_.editProject(); project.controlLogics.push_back(std::move(logic)); return {true, LogicEditorError::None, {}, project.controlLogics.back().id}; } LogicEditorResult LogicEditorService::addLogic(const std::string &name) { if (isBlank(name)) { return failure(LogicEditorError::InvalidOperation, "控制逻辑名称不能为空"); } const Project ¤t = project_service_.project(); const bool duplicate = std::any_of( current.controlLogics.cbegin(), current.controlLogics.cend(), [&name](const ControlLogic &logic) { return logic.name == name; }); if (duplicate) { return failure(LogicEditorError::DuplicateName, "控制逻辑名称必须唯一"); } ControlLogic logic; logic.id = makeUniqueLogicId(current); logic.name = name; logic.rungs.push_back({"rung-1", "网络 1", {}, std::nullopt, std::nullopt}); HistoryState before = captureState(); Project &project = project_service_.editProject(); project.controlLogics.push_back(std::move(logic)); recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, project.controlLogics.back().id}; } LogicEditorResult LogicEditorService::renameLogic( const std::string &logic_id, const std::string &name) { if (isBlank(name)) { return failure(LogicEditorError::InvalidOperation, "控制逻辑名称不能为空"); } const Project ¤t = project_service_.project(); const auto logic = std::find_if( current.controlLogics.cbegin(), current.controlLogics.cend(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); if (logic == current.controlLogics.cend()) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } const bool duplicate = std::any_of( current.controlLogics.cbegin(), current.controlLogics.cend(), [&logic_id, &name](const ControlLogic &candidate) { return candidate.id != logic_id && candidate.name == name; }); if (duplicate) { return failure(LogicEditorError::DuplicateName, "控制逻辑名称必须唯一"); } if (logic->name == name) { return {true, LogicEditorError::None, {}, logic_id}; } HistoryState before = captureState(); Project &project = project_service_.editProject(); auto editable = std::find_if( project.controlLogics.begin(), project.controlLogics.end(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); editable->name = name; recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, logic_id}; } LogicEditorResult LogicEditorService::removeLogic(const std::string &logic_id) { const Project ¤t = project_service_.project(); const auto logic = std::find_if( current.controlLogics.cbegin(), current.controlLogics.cend(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); if (logic == current.controlLogics.cend()) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (current.controlLogics.size() <= 1) { return failure(LogicEditorError::LastLogicRequired, "工程至少需要保留一个控制逻辑"); } HistoryState before = captureState(); Project &project = project_service_.editProject(); project.controlLogics.erase( std::remove_if( project.controlLogics.begin(), project.controlLogics.end(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }), project.controlLogics.end()); recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, logic_id}; } LogicEditorResult LogicEditorService::moveLogic(const std::string &logic_id, int offset) { if (offset != -1 && offset != 1) { return failure(LogicEditorError::InvalidOperation, "控制逻辑每次只能上移或下移一位"); } const Project ¤t = project_service_.project(); const auto logic = std::find_if( current.controlLogics.cbegin(), current.controlLogics.cend(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); if (logic == current.controlLogics.cend()) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } const auto index = std::distance(current.controlLogics.cbegin(), logic); const std::ptrdiff_t target_index = index + offset; if (target_index < 0 || target_index >= static_cast(current.controlLogics.size())) { return failure(LogicEditorError::InvalidOperation, "控制逻辑已经位于目标边界"); } HistoryState before = captureState(); Project &project = project_service_.editProject(); std::iter_swap( project.controlLogics.begin() + index, project.controlLogics.begin() + target_index); recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, logic_id}; } LogicEditorResult LogicEditorService::setLogicEnabled( const std::string &logic_id, bool enabled) { if (findLogic(logic_id) == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } const ControlLogic *existing = findLogic(logic_id); if (existing->enabled == enabled) { return {true, LogicEditorError::None, {}, logic_id}; } HistoryState before = captureState(); Project &project = project_service_.editProject(); auto logic = std::find_if( project.controlLogics.begin(), project.controlLogics.end(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); logic->enabled = enabled; recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, logic_id}; } LogicEditorResult LogicEditorService::addRung(const std::string &logic_id) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } LadderRung rung; rung.id = makeUniqueRungId(*logic); rung.name = "网络 " + std::to_string(logic->rungs.size() + 1U); HistoryState before = captureState(); Project &project = project_service_.editProject(); auto target = std::find_if( project.controlLogics.begin(), project.controlLogics.end(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); target->rungs.push_back(std::move(rung)); recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, target->rungs.back().id}; } LogicEditorResult LogicEditorService::removeRung( const std::string &logic_id, const std::string &rung_id) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (findRung(logic_id, rung_id) == nullptr) { return failure(LogicEditorError::RungNotFound, "未找到梯形图网络"); } if (logic->rungs.size() == 1U) { return failure(LogicEditorError::InvalidOperation, "控制逻辑至少需要保留一个网络"); } HistoryState before = captureState(); Project &project = project_service_.editProject(); auto target = std::find_if( project.controlLogics.begin(), project.controlLogics.end(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); target->rungs.erase( std::remove_if( target->rungs.begin(), target->rungs.end(), [&rung_id](const LadderRung &rung) { return rung.id == rung_id; }), target->rungs.end()); recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, rung_id}; } LogicEditorResult LogicEditorService::updateRungComment( const std::string &logic_id, const std::string &rung_id, const std::string &comment) { const LadderRung *rung = findRung(logic_id, rung_id); if (rung == nullptr) { return failure(LogicEditorError::RungNotFound, "未找到梯形图网络"); } if (rung->comment == comment) { return {true, LogicEditorError::None, {}, rung_id}; } HistoryState before = captureState(); LadderRung &editable = *findEditableRung( project_service_.editProject(), logic_id, rung_id); editable.comment = comment; recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, rung_id}; } LogicEditorResult LogicEditorService::appendCondition( const std::string &logic_id, const std::string &rung_id, const LogicNodeConfig &config) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr || findRung(logic_id, rung_id) == nullptr) { return failure( logic == nullptr ? LogicEditorError::LogicNotFound : LogicEditorError::RungNotFound, logic == nullptr ? "未找到控制逻辑" : "未找到梯形图网络"); } if (!isConditionConfig(config)) { return failure(LogicEditorError::InvalidNode, "梯形图条件不能使用线圈节点"); } const std::string node_id = makeUniqueNodeId(*logic, nodePrefix(config)); ConditionExpression leaf = ConditionExpression::fromNode(makeNode(node_id, config)); HistoryState before = captureState(); Project &project = project_service_.editProject(); LadderRung *rung = findEditableRung(project, logic_id, rung_id); if (!rung->condition.has_value()) { rung->condition = std::move(leaf); } else if (rung->condition->kind == ConditionExpressionKind::Series) { rung->condition->children.push_back(std::move(leaf)); } else { rung->condition = makeContainer( makeUniqueExpressionId(*logic), ConditionExpressionKind::Series, std::move(*rung->condition), std::move(leaf)); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } LogicEditorResult LogicEditorService::appendWire( const std::string &logic_id, const std::string &rung_id, int column_span) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr || findRung(logic_id, rung_id) == nullptr) { return failure( logic == nullptr ? LogicEditorError::LogicNotFound : LogicEditorError::RungNotFound, logic == nullptr ? "未找到控制逻辑" : "未找到梯形图网络"); } WireSegment wire{column_span}; std::string error; if (!wire.validate(&error)) { return failure(LogicEditorError::InvalidOperation, error); } const std::string wire_id = makeUniqueWireId(*logic); ConditionExpression leaf = ConditionExpression::fromWire(wire_id, column_span); HistoryState before = captureState(); LadderRung *rung = findEditableRung( project_service_.editProject(), logic_id, rung_id); if (!rung->condition.has_value()) { rung->condition = std::move(leaf); } else if (rung->condition->kind == ConditionExpressionKind::Series) { rung->condition->children.push_back(std::move(leaf)); } else { rung->condition = makeContainer( makeUniqueExpressionId(*logic), ConditionExpressionKind::Series, std::move(*rung->condition), std::move(leaf)); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, wire_id}; } LogicEditorResult LogicEditorService::insertConditionAfter( const std::string &logic_id, const std::string &rung_id, const std::string &target_node_id, const LogicNodeConfig &config) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr || findRung(logic_id, rung_id) == nullptr) { return failure(LogicEditorError::RungNotFound, "未找到梯形图网络"); } const LadderRung *existing_rung = findRung(logic_id, rung_id); if (!isConditionConfig(config) || existing_rung == nullptr || !existing_rung->condition.has_value() || findConditionNode(*existing_rung->condition, target_node_id) == nullptr) { return failure(LogicEditorError::NodeNotFound, "未找到串联插入目标"); } const std::string node_id = makeUniqueNodeId(*logic, nodePrefix(config)); ConditionExpression leaf = ConditionExpression::fromNode(makeNode(node_id, config)); HistoryState before = captureState(); Project &project = project_service_.editProject(); LadderRung *rung = findEditableRung(project, logic_id, rung_id); ConditionExpression *target = findConditionExpression(*rung->condition, target_node_id); ConditionExpression *parent = findParentExpression(*rung->condition, target_node_id); if (parent != nullptr && parent->kind == ConditionExpressionKind::Series) { const auto target_iterator = std::find_if( parent->children.begin(), parent->children.end(), [&target_node_id](const ConditionExpression &child) { return child.id == target_node_id; }); parent->children.insert(target_iterator + 1, std::move(leaf)); } else { ConditionExpression original = std::move(*target); *target = makeContainer( makeUniqueExpressionId(*logic), ConditionExpressionKind::Series, std::move(original), std::move(leaf)); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } LogicEditorResult LogicEditorService::insertWireAfter( const std::string &logic_id, const std::string &rung_id, const std::string &target_expression_id, int column_span) { const ControlLogic *logic = findLogic(logic_id); const LadderRung *existing_rung = findRung(logic_id, rung_id); const ConditionExpression *target = findExpression( logic_id, rung_id, target_expression_id); WireSegment wire{column_span}; std::string error; if (logic == nullptr || existing_rung == nullptr || target == nullptr) { return failure(LogicEditorError::ExpressionNotFound, "未找到横线插入位置"); } if ((target->kind != ConditionExpressionKind::Node && target->kind != ConditionExpressionKind::Wire) || !wire.validate(&error)) { return failure( LogicEditorError::InvalidOperation, error.empty() ? "只能在触点或横线后插入横线" : error); } const std::string wire_id = makeUniqueWireId(*logic); ConditionExpression leaf = ConditionExpression::fromWire(wire_id, column_span); HistoryState before = captureState(); LadderRung *rung = findEditableRung( project_service_.editProject(), logic_id, rung_id); ConditionExpression *editable_target = findConditionExpression( *rung->condition, target_expression_id); ConditionExpression *parent = findParentExpression( *rung->condition, target_expression_id); if (parent != nullptr && parent->kind == ConditionExpressionKind::Series) { const auto target_iterator = std::find_if( parent->children.begin(), parent->children.end(), [&target_expression_id](const ConditionExpression &child) { return child.id == target_expression_id; }); parent->children.insert(target_iterator + 1, std::move(leaf)); } else { ConditionExpression original = std::move(*editable_target); *editable_target = makeContainer( makeUniqueExpressionId(*logic), ConditionExpressionKind::Series, std::move(original), std::move(leaf)); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, wire_id}; } LogicEditorResult LogicEditorService::replaceWireWithCondition( const std::string &logic_id, const std::string &rung_id, const std::string &wire_expression_id, const LogicNodeConfig &config) { const ControlLogic *logic = findLogic(logic_id); const ConditionExpression *wire = findExpression( logic_id, rung_id, wire_expression_id); if (logic == nullptr || wire == nullptr || wire->kind != ConditionExpressionKind::Wire) { return failure(LogicEditorError::ExpressionNotFound, "未找到要替换的横线"); } if (!isConditionConfig(config)) { return failure(LogicEditorError::InvalidNode, "横线只能替换为条件节点"); } const std::string node_id = makeUniqueNodeId(*logic, nodePrefix(config)); HistoryState before = captureState(); LadderRung *rung = findEditableRung( project_service_.editProject(), logic_id, rung_id); ConditionExpression *editable = findConditionExpression( *rung->condition, wire_expression_id); *editable = ConditionExpression::fromNode(makeNode(node_id, config)); recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } LogicEditorResult LogicEditorService::addParallelBranch( const std::string &logic_id, const std::string &rung_id, const std::vector &selected_node_ids, const LogicNodeConfig &config) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr || findRung(logic_id, rung_id) == nullptr) { return failure(LogicEditorError::RungNotFound, "未找到梯形图网络"); } if (!isConditionConfig(config) || selected_node_ids.empty()) { return failure(LogicEditorError::InvalidOperation, "建立并联支路前必须选择节点"); } const LadderRung *existing_rung = findRung(logic_id, rung_id); if (!existing_rung->condition.has_value()) { 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, "并联选择中存在重复节点"); } const NodeIdSet available_ids = conditionLeafIds(*existing_rung->condition); if (!isSubset(selected_ids, available_ids)) { return failure( LogicEditorError::NodeNotFound, "并联选择中包含未知节点"); } const std::string node_id = makeUniqueNodeId(*logic, nodePrefix(config)); ConditionExpression leaf = ConditionExpression::fromNode(makeNode(node_id, config)); const std::string parallel_id = makeUniqueExpressionId(*logic); std::string series_id = parallel_id + "-range"; while (findExpression(logic_id, rung_id, series_id) != nullptr) { series_id += "-range"; } HistoryState before = captureState(); Project &project = project_service_.editProject(); LadderRung *rung = findEditableRung(project, logic_id, rung_id); if (!addParallelForSelection( &*rung->condition, selected_ids, &leaf, parallel_id, series_id)) { return failure( LogicEditorError::InvalidOperation, "并联选择必须是一个连续的逻辑范围"); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } LogicEditorResult LogicEditorService::addParallelWireBranch( const std::string &logic_id, const std::string &rung_id, const std::vector &selected_expression_ids) { const ControlLogic *logic = findLogic(logic_id); const LadderRung *existing_rung = findRung(logic_id, rung_id); if (logic == nullptr || existing_rung == nullptr || !existing_rung->condition.has_value()) { return failure(LogicEditorError::RungNotFound, "未找到梯形图条件网络"); } if (selected_expression_ids.empty()) { return failure( LogicEditorError::InvalidOperation, "建立竖线连接前必须选择连续的触点或横线"); } NodeIdSet selected_ids( selected_expression_ids.cbegin(), selected_expression_ids.cend()); if (selected_ids.size() != selected_expression_ids.size()) { return failure(LogicEditorError::InvalidOperation, "并联选择中存在重复对象"); } const NodeIdSet available_ids = conditionLeafIds(*existing_rung->condition); if (!isSubset(selected_ids, available_ids)) { return failure(LogicEditorError::ExpressionNotFound, "并联选择中包含未知对象"); } const std::optional column_span = selectedExpressionColumns( *existing_rung->condition, selected_ids); if (!column_span.has_value() || *column_span > WireSegment::kMaximumColumnSpan) { return failure( LogicEditorError::InvalidOperation, "竖线连接只能围绕同一支路中的连续逻辑范围"); } const std::string wire_id = makeUniqueWireId(*logic); ConditionExpression wire = ConditionExpression::fromWire( wire_id, *column_span); const std::string parallel_id = makeUniqueExpressionId(*logic); std::string series_id = parallel_id + "-range"; while (findExpression(logic_id, rung_id, series_id) != nullptr) { series_id += "-range"; } HistoryState before = captureState(); LadderRung *rung = findEditableRung( project_service_.editProject(), logic_id, rung_id); if (!addParallelForSelection( &*rung->condition, selected_ids, &wire, parallel_id, series_id)) { return failure( LogicEditorError::InvalidOperation, "竖线连接只能围绕同一支路中的连续逻辑范围"); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, wire_id}; } LogicEditorResult LogicEditorService::setOutput( const std::string &logic_id, const std::string &rung_id, const LogicNodeConfig &config, bool configured) { const ControlLogic *logic = findLogic(logic_id); const LadderRung *existing_rung = findRung(logic_id, rung_id); if (logic == nullptr || existing_rung == nullptr || !isOutputConfig(config)) { return failure( LogicEditorError::InvalidNode, "梯形图输出必须使用有效的输出指令"); } const std::string node_id = existing_rung->output.has_value() ? existing_rung->output->id : makeUniqueNodeId(*logic, nodePrefix(config)); LogicNode node = makeNode(node_id, config); node.configured = configured; if (existing_rung->output.has_value() && nodesEqual(*existing_rung->output, node)) { return {true, LogicEditorError::None, {}, node_id}; } HistoryState before = captureState(); Project &project = project_service_.editProject(); findEditableRung(project, logic_id, rung_id)->output = std::move(node); recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } LogicEditorResult LogicEditorService::updateNodeConfig( const std::string &logic_id, const std::string &node_id, const LogicNodeConfig &config) { const LogicNode *node = findNode(logic_id, node_id); if (node == nullptr) { return failure(LogicEditorError::NodeNotFound, "未找到逻辑节点"); } if (node->config.index() != config.index()) { return failure( LogicEditorError::UnsupportedNodeChange, "节点创建后不能修改节点类别"); } LogicNode candidate{node_id, config, true}; std::string error; if (!candidate.validate(&error)) { return failure(LogicEditorError::InvalidNode, error); } if (nodesEqual(*node, candidate)) { return {true, LogicEditorError::None, {}, node_id}; } HistoryState before = captureState(); Project &project = project_service_.editProject(); for (ControlLogic &logic : project.controlLogics) { if (logic.id != logic_id) { continue; } for (LadderRung &rung : logic.rungs) { if (rung.output.has_value() && rung.output->id == node_id) { *rung.output = candidate; recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } if (rung.condition.has_value()) { if (LogicNode *editable = findConditionNode(*rung.condition, node_id)) { *editable = candidate; recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } } } } return failure(LogicEditorError::NodeNotFound, "未找到逻辑节点"); } LogicEditorResult LogicEditorService::removeNode( const std::string &logic_id, const std::string &node_id) { return removeNodes(logic_id, {node_id}); } LogicEditorResult LogicEditorService::removeNodes( const std::string &logic_id, const std::vector &node_ids) { if (findLogic(logic_id) == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (node_ids.empty()) { return failure(LogicEditorError::InvalidOperation, "请先选择要删除的逻辑节点"); } std::unordered_set selected_ids; for (const std::string &node_id : node_ids) { if (!selected_ids.insert(node_id).second) { return failure(LogicEditorError::InvalidOperation, "删除列表中存在重复节点"); } if (findNode(logic_id, node_id) == nullptr) { return failure(LogicEditorError::NodeNotFound, "未找到逻辑节点"); } } HistoryState before = captureState(); Project &project = project_service_.editProject(); ControlLogic *editable_logic = nullptr; for (ControlLogic &candidate : project.controlLogics) { if (candidate.id == logic_id) { editable_logic = &candidate; break; } } for (LadderRung &rung : editable_logic->rungs) { if (rung.output.has_value() && selected_ids.find(rung.output->id) != selected_ids.end()) { rung.output.reset(); } if (rung.condition.has_value()) { for (const std::string &node_id : node_ids) { if (rung.condition->kind == ConditionExpressionKind::Node && rung.condition->id == node_id) { rung.condition.reset(); break; } removeExpressionRecursive(&*rung.condition, node_id); } if (rung.condition.has_value()) { normalizeConditionExpression(&rung.condition); } } } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_ids.front()}; } LogicEditorResult LogicEditorService::removeExpression( const std::string &logic_id, const std::string &rung_id, const std::string &expression_id) { if (findExpression(logic_id, rung_id, expression_id) == nullptr) { return failure(LogicEditorError::ExpressionNotFound, "未找到条件支路"); } HistoryState before = captureState(); Project &project = project_service_.editProject(); LadderRung *rung = findEditableRung(project, logic_id, rung_id); if (rung->condition->id == expression_id) { rung->condition.reset(); } else { removeExpressionRecursive(&*rung->condition, expression_id); normalizeConditionExpression(&rung->condition); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, expression_id}; } LogicEditorResult LogicEditorService::removeExpressions( const std::string &logic_id, const std::string &rung_id, const std::vector &expression_ids) { const LadderRung *existing_rung = findRung(logic_id, rung_id); if (existing_rung == nullptr || !existing_rung->condition.has_value()) { return failure(LogicEditorError::RungNotFound, "未找到梯形图条件网络"); } if (expression_ids.empty()) { return failure(LogicEditorError::InvalidOperation, "请先选择要删除的条件或横线"); } NodeIdSet unique_ids; for (const std::string &expression_id : expression_ids) { const ConditionExpression *expression = findExpression( logic_id, rung_id, expression_id); if (!unique_ids.insert(expression_id).second) { return failure(LogicEditorError::InvalidOperation, "删除列表中存在重复对象"); } if (expression == nullptr) { return failure( LogicEditorError::ExpressionNotFound, "删除列表中包含未知条件表达式"); } } HistoryState before = captureState(); LadderRung *rung = findEditableRung( project_service_.editProject(), logic_id, rung_id); for (const std::string &expression_id : expression_ids) { if (!rung->condition.has_value()) { break; } if (rung->condition->id == expression_id) { rung->condition.reset(); break; } removeExpressionRecursive(&*rung->condition, expression_id); } normalizeConditionExpression(&rung->condition); recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, expression_ids.front()}; } bool LogicEditorService::canUndo() const { return history_.canUndo(); } bool LogicEditorService::canRedo() const { return history_.canRedo(); } LogicEditorResult LogicEditorService::undo() { const std::optional target = history_.undo(captureState()); if (!target.has_value()) { return historyFailure("没有可撤销的梯形图编辑操作"); } project_service_.editProject().controlLogics = target->logics; return {true, LogicEditorError::None, {}, {}}; } LogicEditorResult LogicEditorService::redo() { const std::optional target = history_.redo(captureState()); if (!target.has_value()) { return historyFailure("没有可重做的梯形图编辑操作"); } project_service_.editProject().controlLogics = target->logics; return {true, LogicEditorError::None, {}, {}}; } void LogicEditorService::clearHistory() { history_.clear(); } bool LogicEditorService::isConditionConfig(const LogicNodeConfig &config) { return std::holds_alternative(config) || std::holds_alternative(config) || std::holds_alternative(config) || std::holds_alternative(config) || std::holds_alternative(config); } bool LogicEditorService::isOutputConfig(const LogicNodeConfig &config) { return std::holds_alternative(config) || std::holds_alternative(config) || std::holds_alternative(config) || std::holds_alternative(config) || std::holds_alternative(config); } std::string LogicEditorService::nodePrefix(const LogicNodeConfig &config) { return std::visit( [](const auto &value) -> std::string { using Config = std::decay_t; if constexpr (std::is_same_v) { return "contact"; } else if constexpr (std::is_same_v) { return "edge"; } else if constexpr (std::is_same_v) { return "timer-contact"; } else if constexpr (std::is_same_v) { return "counter-contact"; } else if constexpr (std::is_same_v) { return "coil"; } else if constexpr (std::is_same_v) { return "ton"; } else if constexpr (std::is_same_v) { return "counter"; } else if constexpr (std::is_same_v) { return "move"; } else if constexpr (std::is_same_v) { return "arithmetic"; } else { return "compare"; } }, config); } std::string LogicEditorService::makeUniqueNodeId( const ControlLogic &logic, const std::string &prefix) { for (std::size_t index = 1;; ++index) { const std::string candidate = prefix + '-' + std::to_string(index); bool found = false; for (const LadderRung &rung : logic.rungs) { found = found || (rung.output.has_value() && rung.output->id == candidate) || (rung.condition.has_value() && findConditionNode(*rung.condition, candidate) != nullptr); } if (!found) { return candidate; } } } std::string LogicEditorService::makeUniqueWireId(const ControlLogic &logic) { for (std::size_t index = 1;; ++index) { const std::string candidate = "wire-" + std::to_string(index); const bool found = std::any_of( logic.rungs.cbegin(), logic.rungs.cend(), [&candidate](const LadderRung &rung) { return rung.condition.has_value() && findConditionExpression(*rung.condition, candidate) != nullptr; }); if (!found) { return candidate; } } } std::string LogicEditorService::makeUniqueExpressionId(const ControlLogic &logic) { for (std::size_t index = 1;; ++index) { const std::string candidate = "expression-" + std::to_string(index); bool found = false; for (const LadderRung &rung : logic.rungs) { found = found || (rung.condition.has_value() && findConditionExpression(*rung.condition, candidate) != nullptr); } if (!found) { return candidate; } } } std::string LogicEditorService::makeUniqueRungId(const ControlLogic &logic) { for (std::size_t index = 1;; ++index) { const std::string candidate = "rung-" + std::to_string(index); if (std::none_of( logic.rungs.cbegin(), logic.rungs.cend(), [&candidate](const LadderRung &rung) { return rung.id == candidate; })) { return candidate; } } } LogicEditorResult LogicEditorService::failure( LogicEditorError error, const std::string &message) { return {false, error, message, {}}; }