#include "logic_editor_service.h" #include "project_service.h" #include #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; }); } bool containsLineBreak(const std::string &value) { return value.find('\r') != std::string::npos || value.find('\n') != std::string::npos; } std::string makeUniqueLogicId(const Project &project) { for (std::size_t suffix = 1U;; ++suffix) { const std::string candidate = "logic-" + std::to_string(suffix); if (std::none_of( project.controlLogics.cbegin(), project.controlLogics.cend(), [&candidate](const ControlLogic &logic) { return logic.id == candidate; })) { return candidate; } } } std::size_t totalRungCount(const Project &project) { std::size_t count = 0U; for (const ControlLogic &logic : project.controlLogics) { count += logic.rungs.size(); } return count; } ControlLogic *editableLogic(Project *project, const std::string &logic_id) { if (project == nullptr) { return nullptr; } const auto found = std::find_if( project->controlLogics.begin(), project->controlLogics.end(), [&logic_id](const ControlLogic &logic) { return logic.id == logic_id; }); return found == project->controlLogics.end() ? nullptr : &*found; } LadderRung *editableRung(ControlLogic *logic, const std::string &rung_id) { if (logic == nullptr) { return nullptr; } const auto found = std::find_if( logic->rungs.begin(), logic->rungs.end(), [&rung_id](const LadderRung &rung) { return rung.id == rung_id; }); return found == logic->rungs.end() ? nullptr : &*found; } std::size_t rungIndex(const ControlLogic &logic, const std::string &rung_id) { const auto found = std::find_if( logic.rungs.cbegin(), logic.rungs.cend(), [&rung_id](const LadderRung &rung) { return rung.id == rung_id; }); return found == logic.rungs.cend() ? logic.rungs.size() : static_cast(std::distance(logic.rungs.cbegin(), found)); } bool connectionMatches( const VerticalConnection &connection, const std::string &upper_rung_id, const std::string &lower_rung_id, int column_boundary) { return connection.upperRungId == upper_rung_id && connection.lowerRungId == lower_rung_id && connection.columnBoundary == column_boundary; } struct SyntaxConnectivity { std::size_t rowCount = 0U; std::vector forwardBase; std::vector forward; std::vector backwardBase; std::vector backward; std::vector verticalByBoundaryAndUpperRow; std::size_t stateIndex(std::size_t row, int boundary) const { return row * static_cast( ProjectLimits::kMaximumConditionColumns + 1) + static_cast(boundary); } bool stateAt( const std::vector &states, std::size_t row, int boundary) const { return states[stateIndex(row, boundary)] != 0U; } const VerticalConnection *verticalAt( int boundary, std::size_t upper_row) const { if (rowCount < 2U || upper_row + 1U >= rowCount) { return nullptr; } return verticalByBoundaryAndUpperRow[ static_cast(boundary) * (rowCount - 1U) + upper_row]; } }; SyntaxConnectivity analyzeConnectivity(const ControlLogic &logic) { SyntaxConnectivity analysis; analysis.rowCount = logic.rungs.size(); const std::size_t state_count = analysis.rowCount * static_cast( ProjectLimits::kMaximumConditionColumns + 1); analysis.forwardBase.assign(state_count, 0U); analysis.forward.assign(state_count, 0U); analysis.backwardBase.assign(state_count, 0U); analysis.backward.assign(state_count, 0U); if (analysis.rowCount == 0U) { return analysis; } analysis.verticalByBoundaryAndUpperRow.assign( static_cast( ProjectLimits::kMaximumConditionColumns + 1) * (analysis.rowCount - 1U), nullptr); std::unordered_map row_indices; row_indices.reserve(analysis.rowCount); for (std::size_t row = 0U; row < analysis.rowCount; ++row) { row_indices.emplace(logic.rungs[row].id, row); } for (const VerticalConnection &connection : logic.verticalConnections) { const std::size_t upper = row_indices.at(connection.upperRungId); analysis.verticalByBoundaryAndUpperRow[ static_cast(connection.columnBoundary) * (analysis.rowCount - 1U) + upper] = &connection; } const auto mergeBoundaryComponents = [&analysis]( int boundary, const std::vector &base, std::vector *merged) { std::size_t first = 0U; while (first < analysis.rowCount) { std::size_t last = first; while (last + 1U < analysis.rowCount && analysis.verticalAt(boundary, last) != nullptr) { ++last; } bool active = false; for (std::size_t row = first; row <= last; ++row) { active = active || analysis.stateAt(base, row, boundary); } for (std::size_t row = first; row <= last; ++row) { (*merged)[analysis.stateIndex(row, boundary)] = active ? 1U : 0U; } first = last + 1U; } }; for (int boundary = 0; boundary <= ProjectLimits::kMaximumConditionColumns; ++boundary) { for (std::size_t row = 0U; row < analysis.rowCount; ++row) { const bool active = boundary == 0 || (analysis.stateAt(analysis.forward, row, boundary - 1) && logic.rungs[row] .cells[static_cast(boundary - 1)] .kind != LadderCellKind::Gap); analysis.forwardBase[analysis.stateIndex(row, boundary)] = active ? 1U : 0U; } mergeBoundaryComponents( boundary, analysis.forwardBase, &analysis.forward); } for (int boundary = ProjectLimits::kMaximumConditionColumns; boundary >= 0; --boundary) { for (std::size_t row = 0U; row < analysis.rowCount; ++row) { const bool active = boundary == ProjectLimits::kMaximumConditionColumns ? logic.rungs[row].output.has_value() : logic.rungs[row] .cells[static_cast(boundary)] .kind != LadderCellKind::Gap && analysis.stateAt( analysis.backward, row, boundary + 1); analysis.backwardBase[analysis.stateIndex(row, boundary)] = active ? 1U : 0U; } mergeBoundaryComponents( boundary, analysis.backwardBase, &analysis.backward); } return analysis; } std::vector networkNumbersByRow(const ControlLogic &logic) { std::vector networks(logic.rungs.size(), 0); int network = 0; for (std::size_t row = 0U; row < logic.rungs.size(); ++row) { if (row > 0U) { const std::string &upper = logic.rungs[row - 1U].id; const std::string &lower = logic.rungs[row].id; const bool connected = std::any_of( logic.verticalConnections.cbegin(), logic.verticalConnections.cend(), [&upper, &lower](const VerticalConnection &connection) { return connection.upperRungId == upper && connection.lowerRungId == lower; }); if (!connected) { ++network; } } networks[row] = network; } return networks; } std::unordered_set activeVerticalConnections( const SyntaxConnectivity &analysis) { std::unordered_set active_connections; for (int boundary = 0; boundary <= ProjectLimits::kMaximumConditionColumns; ++boundary) { std::size_t first = 0U; while (first < analysis.rowCount) { std::size_t last = first; while (last + 1U < analysis.rowCount && analysis.verticalAt(boundary, last) != nullptr) { ++last; } int total_forward = 0; int total_backward = 0; for (std::size_t row = first; row <= last; ++row) { total_forward += analysis.stateAt( analysis.forwardBase, row, boundary) ? 1 : 0; total_backward += analysis.stateAt( analysis.backwardBase, row, boundary) ? 1 : 0; } int upper_forward = 0; int upper_backward = 0; for (std::size_t upper = first; upper < last; ++upper) { upper_forward += analysis.stateAt( analysis.forwardBase, upper, boundary) ? 1 : 0; upper_backward += analysis.stateAt( analysis.backwardBase, upper, boundary) ? 1 : 0; const int lower_forward = total_forward - upper_forward; const int lower_backward = total_backward - upper_backward; if ((upper_forward > 0 && lower_backward > 0) || (upper_backward > 0 && lower_forward > 0)) { active_connections.insert( analysis.verticalAt(boundary, upper)->id); } } first = last + 1U; } } return active_connections; } struct LogicCleanupStats { std::size_t wireCells = 0U; std::size_t verticalConnections = 0U; }; LogicCleanupStats normalizeLogicWires(ControlLogic *logic) { LogicCleanupStats stats; if (logic == nullptr || logic->rungs.empty()) { return stats; } const SyntaxConnectivity analysis = analyzeConnectivity(*logic); const std::vector networks = networkNumbersByRow(*logic); const int network_count = networks.empty() ? 0 : networks.back() + 1; std::vector invalid_networks( static_cast(network_count), 0U); for (std::size_t row = 0U; row < logic->rungs.size(); ++row) { if (logic->rungs[row].output.has_value() && !analysis.stateAt( analysis.forward, row, ProjectLimits::kMaximumConditionColumns)) { invalid_networks[static_cast(networks[row])] = 1U; } } const std::unordered_set active_verticals = activeVerticalConnections(analysis); std::unordered_map row_indices; row_indices.reserve(logic->rungs.size()); for (std::size_t row = 0U; row < logic->rungs.size(); ++row) { row_indices.emplace(logic->rungs[row].id, row); } for (std::size_t row = 0U; row < logic->rungs.size(); ++row) { if (invalid_networks[static_cast(networks[row])] != 0U) { continue; } for (int column = 0; column < ProjectLimits::kMaximumConditionColumns; ++column) { LadderCell &cell = logic->rungs[row] .cells[static_cast(column)]; if (cell.kind == LadderCellKind::Wire && !(analysis.stateAt(analysis.forward, row, column) && analysis.stateAt( analysis.backward, row, column + 1))) { cell.kind = LadderCellKind::Gap; ++stats.wireCells; } } } const auto new_end = std::remove_if( logic->verticalConnections.begin(), logic->verticalConnections.end(), [&row_indices, &networks, &invalid_networks, &active_verticals, &stats]( const VerticalConnection &connection) { const std::size_t upper_row = row_indices.at(connection.upperRungId); if (invalid_networks[ static_cast(networks[upper_row])] != 0U || active_verticals.find(connection.id) != active_verticals.end()) { return false; } ++stats.verticalConnections; return true; }); logic->verticalConnections.erase(new_end, logic->verticalConnections.end()); return stats; } std::optional> firstSyntaxIssue( const ControlLogic &logic) { const std::vector networks = networkNumbersByRow(logic); for (std::size_t row = 0U; row < logic.rungs.size(); ++row) { const LadderRung &rung = logic.rungs[row]; for (std::size_t column = 0U; column < rung.cells.size(); ++column) { if (rung.cells[column].node.has_value() && !rung.cells[column].node->isConfigured()) { LogicSyntaxLocation location{ logic.id, rung.id, networks[row] + 1, static_cast(row + 1U), static_cast(column + 1U)}; return std::make_pair( location, "控制逻辑 " + logic.name + " 的网络 " + std::to_string(location.network) + ",第 " + std::to_string(location.row) + " 行第 " + std::to_string(location.column) + " 列:程序语法分析发生错误,条件指令尚未配置"); } } if (rung.output.has_value() && !rung.output->isConfigured()) { LogicSyntaxLocation location{ logic.id, rung.id, networks[row] + 1, static_cast(row + 1U), ProjectLimits::kMaximumLadderColumns}; return std::make_pair( location, "控制逻辑 " + logic.name + " 的网络 " + std::to_string(location.network) + ",第 " + std::to_string(location.row) + " 行第 " + std::to_string(location.column) + " 列:程序语法分析发生错误,输出指令尚未配置"); } } const SyntaxConnectivity analysis = analyzeConnectivity(logic); for (std::size_t row = 0U; row < logic.rungs.size(); ++row) { const LadderRung &rung = logic.rungs[row]; if (!rung.output.has_value() || analysis.stateAt( analysis.forward, row, ProjectLimits::kMaximumConditionColumns)) { continue; } int last_reachable_boundary = 0; for (int boundary = 0; boundary <= ProjectLimits::kMaximumConditionColumns; ++boundary) { if (analysis.stateAt(analysis.forward, row, boundary)) { last_reachable_boundary = boundary; } } const int disconnected_column = std::min( last_reachable_boundary + 1, ProjectLimits::kMaximumConditionColumns); LogicSyntaxLocation location{ logic.id, rung.id, networks[row] + 1, static_cast(row + 1U), ProjectLimits::kMaximumLadderColumns}; return std::make_pair( location, "控制逻辑 " + logic.name + " 的网络 " + std::to_string(location.network) + ",第 " + std::to_string(location.row) + " 行第 " + std::to_string(location.column) + " 列:程序语法分析发生错误,输出路径从第 " + std::to_string(disconnected_column) + " 列起断开,未连接到左母线"); } return std::nullopt; } } // 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) { history_.record( std::move(before), captureState(), &LogicEditorService::statesEqual); } void LogicEditorService::rollbackEdit( HistoryState before, bool modified_before) { project_service_.editProject().controlLogics = std::move(before.logics); project_service_.restoreModifiedState(modified_before); } bool LogicEditorService::statesEqual( const HistoryState &left, const HistoryState &right) { if (left.logics.size() != right.logics.size()) { return false; } for (std::size_t index = 0U; 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() || left.verticalConnections.size() != right.verticalConnections.size()) { return false; } for (std::size_t index = 0U; index < left.rungs.size(); ++index) { if (!rungsEqual(left.rungs[index], right.rungs[index])) { return false; } } for (std::size_t index = 0U; index < left.verticalConnections.size(); ++index) { const VerticalConnection &left_connection = left.verticalConnections[index]; const VerticalConnection &right_connection = right.verticalConnections[index]; if (left_connection.id != right_connection.id || left_connection.upperRungId != right_connection.upperRungId || left_connection.lowerRungId != right_connection.lowerRungId || left_connection.columnBoundary != right_connection.columnBoundary) { 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.cells.size() != right.cells.size() || left.output.has_value() != right.output.has_value()) { return false; } for (std::size_t index = 0U; index < left.cells.size(); ++index) { if (!cellsEqual(left.cells[index], right.cells[index])) { return false; } } return !left.output.has_value() || nodesEqual(*left.output, *right.output); } bool LogicEditorService::cellsEqual( const LadderCell &left, const LadderCell &right) { return left.id == right.id && left.kind == right.kind && left.node.has_value() == right.node.has_value() && (!left.node.has_value() || nodesEqual(*left.node, *right.node)); } bool LogicEditorService::nodesEqual( const LogicNode &left, const LogicNode &right) { return left.id == right.id && 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.comparison == right_config.comparison && left_config.value == right_config.value; } 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 found = std::find_if( logics.cbegin(), logics.cend(), [&logic_id](const ControlLogic &logic) { return logic.id == logic_id; }); return found == logics.cend() ? nullptr : &*found; } 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 found = std::find_if( logic->rungs.cbegin(), logic->rungs.cend(), [&rung_id](const LadderRung &rung) { return rung.id == rung_id; }); return found == logic->rungs.cend() ? nullptr : &*found; } const LadderCell *LogicEditorService::findCell( const std::string &logic_id, const std::string &rung_id, int column) const { const LadderRung *rung = findRung(logic_id, rung_id); return rung == nullptr || column < 0 || column >= static_cast(rung->cells.size()) ? nullptr : &rung->cells[static_cast(column)]; } const LadderCell *LogicEditorService::findCell( const std::string &logic_id, const std::string &rung_id, const std::string &cell_id) const { const LadderRung *rung = findRung(logic_id, rung_id); return rung == nullptr ? nullptr : findLadderCell(*rung, cell_id); } const VerticalConnection *LogicEditorService::findConnection( const std::string &logic_id, const std::string &connection_id) const { const ControlLogic *logic = findLogic(logic_id); return logic == nullptr ? nullptr : findVerticalConnection(*logic, connection_id); } 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) { for (const LadderCell &cell : rung.cells) { if (cell.node.has_value() && cell.node->id == node_id) { return &*cell.node; } } if (rung.output.has_value() && rung.output->id == node_id) { return &*rung.output; } } return nullptr; } 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) { return rung.id; } for (const LadderCell &cell : rung.cells) { if (cell.node.has_value() && cell.node->id == node_id) { 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"; 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) || name.size() > ProjectLimits::kMaximumTextBytes) { return failure( LogicEditorError::InvalidOperation, "控制逻辑名称不能为空且不能超过 256 个 UTF-8 字节"); } const Project ¤t = project_service_.project(); if (current.controlLogics.size() >= project_service_.projectLimits().maximumControlLogics) { return failure( LogicEditorError::InvalidOperation, "控制逻辑数量已经达到当前配置上限"); } if (std::any_of( current.controlLogics.cbegin(), current.controlLogics.cend(), [&name](const ControlLogic &logic) { return logic.name == name; })) { return failure(LogicEditorError::DuplicateName, "控制逻辑名称必须唯一"); } ControlLogic logic; logic.id = makeUniqueLogicId(current); logic.name = name; 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) || name.size() > ProjectLimits::kMaximumTextBytes) { return failure( LogicEditorError::InvalidOperation, "控制逻辑名称不能为空且不能超过 256 个 UTF-8 字节"); } const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } const Project ¤t = project_service_.project(); if (std::any_of( current.controlLogics.cbegin(), current.controlLogics.cend(), [&logic_id, &name](const ControlLogic &candidate) { return candidate.id != logic_id && candidate.name == name; })) { return failure(LogicEditorError::DuplicateName, "控制逻辑名称必须唯一"); } if (logic->name == name) { return {true, LogicEditorError::None, {}, logic_id}; } HistoryState before = captureState(); Project &project = project_service_.editProject(); editableLogic(&project, logic_id)->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(); if (findLogic(logic_id) == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (current.controlLogics.size() <= 1U) { 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 &logic) { return logic.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 found = std::find_if( current.controlLogics.cbegin(), current.controlLogics.cend(), [&logic_id](const ControlLogic &logic) { return logic.id == logic_id; }); if (found == current.controlLogics.cend()) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } const std::ptrdiff_t index = std::distance(current.controlLogics.cbegin(), found); const std::ptrdiff_t target = index + offset; if (target < 0 || target >= 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); recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, logic_id}; } LogicEditorResult LogicEditorService::setLogicEnabled( const std::string &logic_id, bool enabled) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (logic->enabled == enabled) { return {true, LogicEditorError::None, {}, logic_id}; } HistoryState before = captureState(); Project &project = project_service_.editProject(); editableLogic(&project, logic_id)->enabled = enabled; recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, logic_id}; } LogicSyntaxCheckResult LogicEditorService::checkSyntax( const std::string &logic_id) { if (findLogic(logic_id) == nullptr) { LogicSyntaxCheckResult result; result.message = "未找到要检查的控制逻辑"; return result; } return checkSyntaxForLogics({logic_id}); } LogicSyntaxCheckResult LogicEditorService::checkDoubleCoils( const std::string &logic_id) const { LogicSyntaxCheckResult result; const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { result.message = "未找到要检查的控制逻辑"; return result; } result.completed = true; result.valid = true; result.checkedLogicCount = 1U; const std::vector networks = networkNumbersByRow(*logic); std::map first_rows_by_address; for (std::size_t row = 0U; row < logic->rungs.size(); ++row) { const LadderRung &rung = logic->rungs[row]; if (!rung.output.has_value()) { continue; } const auto *coil = std::get_if(&rung.output->config); if (coil == nullptr) { continue; } const auto inserted = first_rows_by_address.emplace( coil->address.index(), row); if (inserted.second) { continue; } const std::size_t first_row = inserted.first->second; LogicSyntaxLocation location{ logic->id, rung.id, networks[row] + 1, static_cast(row + 1U), ProjectLimits::kMaximumLadderColumns}; result.valid = false; result.location = location; result.message = "控制逻辑 " + logic->name + " 的网络 " + std::to_string(location.network) + ",第 " + std::to_string(location.row) + " 行第 " + std::to_string(location.column) + " 列:发现双线圈输出 " + coil->address.toString() + ",首次输出位于第 " + std::to_string(first_row + 1U) + " 行"; return result; } result.message = "双线圈检查通过"; return result; } LogicSyntaxCheckResult LogicEditorService::checkEnabledSyntax() { std::vector logic_ids; for (const ControlLogic &logic : project_service_.project().controlLogics) { if (logic.enabled) { logic_ids.push_back(logic.id); } } return checkSyntaxForLogics(logic_ids); } LogicSyntaxCheckResult LogicEditorService::checkSyntaxForLogics( const std::vector &logic_ids) { LogicSyntaxCheckResult result; result.checkedLogicCount = logic_ids.size(); HistoryState before = captureState(); HistoryState candidate = before; for (const std::string &logic_id : logic_ids) { ControlLogic *logic = nullptr; const auto found = std::find_if( candidate.logics.begin(), candidate.logics.end(), [&logic_id](const ControlLogic &item) { return item.id == logic_id; }); if (found != candidate.logics.end()) { logic = &*found; } if (logic == nullptr) { result.message = "语法检查期间未找到控制逻辑"; return result; } std::string structure_error; if (!logic->validateStructure( project_service_.projectLimits(), &structure_error)) { result.completed = true; result.message = "程序语法分析发生错误:" + structure_error; return result; } } for (const std::string &logic_id : logic_ids) { const auto found = std::find_if( candidate.logics.begin(), candidate.logics.end(), [&logic_id](const ControlLogic &item) { return item.id == logic_id; }); const LogicCleanupStats stats = normalizeLogicWires(&*found); result.removedWireCells += stats.wireCells; result.removedVerticalConnections += stats.verticalConnections; } for (const std::string &logic_id : logic_ids) { const auto found = std::find_if( candidate.logics.cbegin(), candidate.logics.cend(), [&logic_id](const ControlLogic &item) { return item.id == logic_id; }); const auto issue = firstSyntaxIssue(*found); if (issue.has_value()) { result.location = issue->first; result.message = issue->second; break; } } result.completed = true; result.valid = !result.location.has_value(); result.changed = !statesEqual(before, candidate); if (result.changed) { project_service_.editProject().controlLogics = std::move(candidate.logics); recordHistory(std::move(before)); } if (result.valid) { result.message = "语法检查通过"; } return result; } LogicEditorResult LogicEditorService::addRung(const std::string &logic_id) { return insertRung(logic_id, {}, true); } LogicEditorResult LogicEditorService::insertRung( const std::string &logic_id, const std::string &reference_rung_id, bool after) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (logic->rungs.size() >= project_service_.projectLimits().maximumRungsPerLogic || totalRungCount(project_service_.project()) >= ProjectLimits::kMaximumRungsPerProject) { return failure( LogicEditorError::InvalidOperation, "梯形图行数已经达到当前上限"); } std::size_t position = logic->rungs.size(); if (!reference_rung_id.empty()) { const std::size_t reference = rungIndex(*logic, reference_rung_id); if (reference == logic->rungs.size()) { return failure(LogicEditorError::RungNotFound, "未找到梯形图行"); } position = reference + (after ? 1U : 0U); } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *editable = editableLogic(&project, logic_id); const std::string new_id = insertEmptyRungAt(editable, position); std::string error; if (!editable->validateStructure(project_service_.projectLimits(), &error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, new_id}; } LogicEditorResult LogicEditorService::removeRung( const std::string &logic_id, const std::string &rung_id) { return removeRungs(logic_id, {rung_id}); } LogicEditorResult LogicEditorService::removeRungs( const std::string &logic_id, const std::vector &rung_ids) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (rung_ids.empty()) { return failure(LogicEditorError::InvalidOperation, "请先选择要删除的行"); } std::unordered_set unique_ids; std::vector indices; for (const std::string &rung_id : rung_ids) { const std::size_t index = rungIndex(*logic, rung_id); if (!unique_ids.insert(rung_id).second) { return failure( LogicEditorError::InvalidOperation, "删除列表中存在重复行"); } if (index == logic->rungs.size()) { return failure(LogicEditorError::RungNotFound, "未找到梯形图行"); } indices.push_back(index); } std::sort(indices.begin(), indices.end(), std::greater()); HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *editable = editableLogic(&project, logic_id); for (std::size_t index : indices) { removeRungAt(editable, index); } std::string error; if (!editable->validateStructure(project_service_.projectLimits(), &error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, rung_ids.front()}; } 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 (containsLineBreak(comment) || comment.size() > ProjectLimits::kMaximumRungCommentBytes) { return failure( LogicEditorError::InvalidOperation, "行注释必须是最多 128 个 UTF-8 字节的单行文本"); } if (rung->comment == comment) { return {true, LogicEditorError::None, {}, rung_id}; } HistoryState before = captureState(); Project &project = project_service_.editProject(); editableRung(editableLogic(&project, logic_id), rung_id)->comment = comment; recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, rung_id}; } LogicEditorResult LogicEditorService::setConditionAtColumn( const std::string &logic_id, const std::string &rung_id, int column, const LogicNodeConfig &config, bool configured) { const LadderCell *cell = findCell(logic_id, rung_id, column); if (cell == nullptr) { return failure(LogicEditorError::CellNotFound, "未找到目标条件网格"); } if (!isConditionConfig(config)) { return failure(LogicEditorError::InvalidNode, "条件区只能放置条件指令"); } LogicNode candidate{"candidate", config, configured}; std::string error; if (!candidate.validate(&error)) { return failure(LogicEditorError::InvalidNode, error); } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *logic = editableLogic(&project, logic_id); LadderRung *rung = editableRung(logic, rung_id); LadderCell &editable_cell = rung->cells[static_cast(column)]; const std::string node_id = makeUniqueId(*logic, nodePrefix(config)); editable_cell.kind = LadderCellKind::Node; editable_cell.node = LogicNode{node_id, config, configured}; if (!logic->validateStructure(project_service_.projectLimits(), &error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } LogicEditResult LogicEditorService::applyConditionAndAdvance( const std::string &logic_id, const LogicEditCursor &cursor, const LogicNodeConfig &config, bool configured) { return applyCellAndAdvance( logic_id, cursor, &config, configured); } LogicEditorResult LogicEditorService::insertConditionAtColumn( const std::string &logic_id, const std::string &rung_id, int column, const LogicNodeConfig &config, bool configured) { const LadderRung *existing = findRung(logic_id, rung_id); if (existing == nullptr || column < 0 || column >= ProjectLimits::kMaximumConditionColumns) { return failure(LogicEditorError::CellNotFound, "未找到目标条件网格"); } if (!isConditionConfig(config)) { return failure(LogicEditorError::InvalidNode, "条件区只能放置条件指令"); } if (existing->cells.back().kind != LadderCellKind::Gap) { return failure( LogicEditorError::InvalidOperation, "第 10 列已有内容,无法继续向右插入"); } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *logic = editableLogic(&project, logic_id); LadderRung *rung = editableRung(logic, rung_id); for (int index = ProjectLimits::kMaximumConditionColumns - 1; index > column; --index) { rung->cells[static_cast(index)] = std::move(rung->cells[static_cast(index - 1)]); } // 插入列会让右侧网格右移,边界 10 是固定输出侧边界,不能继续右移 for (VerticalConnection &connection : logic->verticalConnections) { if ((connection.upperRungId == rung_id || connection.lowerRungId == rung_id) && connection.columnBoundary >= column && connection.columnBoundary < ProjectLimits::kMaximumConditionColumns) { ++connection.columnBoundary; } } LadderCell inserted; inserted.id = makeUniqueId(*logic, "cell"); inserted.kind = LadderCellKind::Node; const std::string node_id = makeUniqueId(*logic, nodePrefix(config)); inserted.node = LogicNode{node_id, config, configured}; rung->cells[static_cast(column)] = std::move(inserted); std::string error; if (!logic->validateStructure(project_service_.projectLimits(), &error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } LogicEditorResult LogicEditorService::appendCondition( const std::string &logic_id, const std::string &rung_id, const LogicNodeConfig &config, bool configured) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (!isConditionConfig(config)) { return failure(LogicEditorError::InvalidNode, "条件区只能放置条件指令"); } std::string target_rung_id = rung_id; if (target_rung_id.empty()) { if (logic->rungs.size() >= project_service_.projectLimits().maximumRungsPerLogic || totalRungCount(project_service_.project()) >= ProjectLimits::kMaximumRungsPerProject) { return failure( LogicEditorError::InvalidOperation, "梯形图行数已经达到当前上限"); } // 新建首行和放置首个节点必须共用一条历史记录 HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *editable = editableLogic(&project, logic_id); target_rung_id = insertEmptyRungAt(editable, editable->rungs.size()); LadderRung *created = editableRung(editable, target_rung_id); const auto empty = std::find_if( created->cells.begin(), created->cells.end(), [](const LadderCell &cell) { return cell.kind == LadderCellKind::Gap; }); if (empty == created->cells.end()) { rollbackEdit(std::move(before), modified_before); return failure( LogicEditorError::InvalidOperation, "条件区 10 列已经占满"); } const std::string node_id = makeUniqueId(*editable, nodePrefix(config)); LadderCell &cell = created->cells[ static_cast(std::distance(created->cells.begin(), empty))]; cell.kind = LadderCellKind::Node; cell.node = LogicNode{node_id, config, configured}; std::string error; if (!editable->validateStructure(project_service_.projectLimits(), &error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } const LadderRung *rung = findRung(logic_id, target_rung_id); if (rung == nullptr) { return failure(LogicEditorError::RungNotFound, "未找到梯形图行"); } const auto empty = std::find_if( rung->cells.cbegin(), rung->cells.cend(), [](const LadderCell &cell) { return cell.kind == LadderCellKind::Gap; }); if (empty == rung->cells.cend()) { return failure(LogicEditorError::InvalidOperation, "条件区 10 列已经占满"); } return setConditionAtColumn( logic_id, target_rung_id, static_cast(std::distance(rung->cells.cbegin(), empty)), config, configured); } 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) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (column_span <= 0 || column_span > ProjectLimits::kMaximumConditionColumns) { return failure(LogicEditorError::InvalidOperation, "横线参数无效"); } if (rung_id.empty()) { if (logic->rungs.size() >= project_service_.projectLimits().maximumRungsPerLogic || totalRungCount(project_service_.project()) >= ProjectLimits::kMaximumRungsPerProject) { return failure( LogicEditorError::InvalidOperation, "梯形图行数已经达到当前上限"); } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *editable = editableLogic(&project, logic_id); const std::string new_rung_id = insertEmptyRungAt( editable, editable->rungs.size()); LadderRung *rung = editableRung(editable, new_rung_id); const int first = ProjectLimits::kMaximumConditionColumns - column_span; for (int column = first; column < ProjectLimits::kMaximumConditionColumns; ++column) { rung->cells[static_cast(column)].kind = LadderCellKind::Wire; } std::string error; if (!editable->validateStructure(project_service_.projectLimits(), &error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, new_rung_id}; } const LadderRung *rung = findRung(logic_id, rung_id); if (rung == nullptr) { return failure(LogicEditorError::RungNotFound, "未找到梯形图行"); } const int first = ProjectLimits::kMaximumConditionColumns - column_span; return setHorizontalWireRange(logic_id, rung_id, first, ProjectLimits::kMaximumConditionColumns - 1, true); } LogicEditResult LogicEditorService::applyWireAndAdvance( const std::string &logic_id, const LogicEditCursor &cursor) { return applyCellAndAdvance(logic_id, cursor, nullptr, false); } LogicEditorResult LogicEditorService::setHorizontalWireRange( const std::string &logic_id, const std::string &rung_id, int first_column, int last_column, bool connected) { if (first_column > last_column) { std::swap(first_column, last_column); } if (findRung(logic_id, rung_id) == nullptr || first_column < 0 || last_column >= ProjectLimits::kMaximumConditionColumns) { return failure(LogicEditorError::CellNotFound, "横线范围超出条件网格"); } std::vector> cells; for (int column = first_column; column <= last_column; ++column) { cells.emplace_back(rung_id, column); } return setWireCells(logic_id, cells, connected); } LogicEditorResult LogicEditorService::setWireCells( const std::string &logic_id, const std::vector> &cells, bool connected) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (cells.empty()) { return failure(LogicEditorError::InvalidOperation, "没有需要修改的横线网格"); } std::unordered_set positions; for (const auto &position : cells) { const std::string key = position.first + "\n" + std::to_string(position.second); if (!positions.insert(key).second) { return failure(LogicEditorError::InvalidOperation, "横线范围包含重复网格"); } if (findCell(logic_id, position.first, position.second) == nullptr) { return failure(LogicEditorError::CellNotFound, "横线范围包含无效网格"); } } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *editable = editableLogic(&project, logic_id); for (const auto &position : cells) { LadderCell &cell = editableRung(editable, position.first) ->cells[static_cast(position.second)]; if (cell.kind == LadderCellKind::Node) { continue; } cell.kind = connected ? LadderCellKind::Wire : LadderCellKind::Gap; cell.node.reset(); } std::string error; if (!editable->validateStructure(project_service_.projectLimits(), &error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, cells.front().first}; } LogicEditorResult LogicEditorService::clearCells( const std::string &logic_id, const std::vector> &cells) { if (findLogic(logic_id) == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (cells.empty()) { return failure(LogicEditorError::InvalidOperation, "没有需要清空的网格"); } for (const auto &position : cells) { if (findCell(logic_id, position.first, position.second) == nullptr) { return failure(LogicEditorError::CellNotFound, "清空范围包含无效网格"); } } HistoryState before = captureState(); Project &project = project_service_.editProject(); ControlLogic *logic = editableLogic(&project, logic_id); for (const auto &position : cells) { LadderCell &cell = editableRung(logic, position.first) ->cells[static_cast(position.second)]; cell.kind = LadderCellKind::Gap; cell.node.reset(); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, cells.front().first}; } LogicEditorResult LogicEditorService::setVerticalConnection( const std::string &logic_id, const std::string &upper_rung_id, const std::string &lower_rung_id, int column_boundary, bool connected) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } const std::size_t upper = rungIndex(*logic, upper_rung_id); const std::size_t lower = rungIndex(*logic, lower_rung_id); if (upper == logic->rungs.size() || lower != upper + 1U) { return failure( LogicEditorError::InvalidOperation, "竖线只能连接相邻的上下两行"); } return setVerticalConnectionRange( logic_id, upper_rung_id, lower_rung_id, column_boundary, connected); } LogicVerticalEditResult LogicEditorService::applyVerticalConnectionAndAdvance( const std::string &logic_id, const std::string &upper_rung_id, int column_boundary) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return { failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"), {}, -1, false}; } if (column_boundary < 0 || column_boundary > ProjectLimits::kMaximumConditionColumns) { return { failure( LogicEditorError::InvalidOperation, "请先选择一个列边界或网格,再插入竖线"), {}, -1, false}; } const std::size_t upper = rungIndex(*logic, upper_rung_id); if (upper == logic->rungs.size()) { return { failure(LogicEditorError::RungNotFound, "未找到梯形图行"), {}, -1, false}; } if (upper + 1U >= logic->rungs.size()) { return { failure( LogicEditorError::InvalidOperation, "当前已经是末行,无法继续建立竖线"), {}, -1, false}; } const std::string lower_rung_id = logic->rungs[upper + 1U].id; const bool already_connected = std::any_of( logic->verticalConnections.cbegin(), logic->verticalConnections.cend(), [&upper_rung_id, &lower_rung_id, column_boundary]( const VerticalConnection &connection) { return connectionMatches( connection, upper_rung_id, lower_rung_id, column_boundary); }); LogicEditorResult edit = setVerticalConnection( logic_id, upper_rung_id, lower_rung_id, column_boundary, true); if (!edit.succeeded) { return {std::move(edit), {}, -1, false}; } edit.message = already_connected ? "竖线已经存在,已移至下一行" : "竖线连接已建立,已移至下一行"; return { std::move(edit), lower_rung_id, column_boundary, !already_connected}; } LogicEditorResult LogicEditorService::setVerticalConnectionRange( const std::string &logic_id, const std::string &first_rung_id, const std::string &last_rung_id, int column_boundary, bool connected) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } std::size_t first = rungIndex(*logic, first_rung_id); std::size_t last = rungIndex(*logic, last_rung_id); if (first == logic->rungs.size() || last == logic->rungs.size() || first == last || column_boundary < 0 || column_boundary > ProjectLimits::kMaximumConditionColumns) { return failure( LogicEditorError::InvalidOperation, "竖线范围必须跨越至少两行且位于 0~10 列边界"); } if (first > last) { std::swap(first, last); } bool changed = false; for (std::size_t row = first; row < last; ++row) { const std::string &upper_id = logic->rungs[row].id; const std::string &lower_id = logic->rungs[row + 1U].id; const bool exists = std::any_of( logic->verticalConnections.cbegin(), logic->verticalConnections.cend(), [&upper_id, &lower_id, column_boundary]( const VerticalConnection &connection) { return connectionMatches( connection, upper_id, lower_id, column_boundary); }); if (exists != connected) { changed = true; break; } } if (!changed) { return { true, LogicEditorError::None, connected ? "竖线连接已经存在" : "目标位置没有竖线", first_rung_id}; } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *editable = editableLogic(&project, logic_id); for (std::size_t row = first; row < last; ++row) { const std::string upper_id = editable->rungs[row].id; const std::string lower_id = editable->rungs[row + 1U].id; const auto found = std::find_if( editable->verticalConnections.begin(), editable->verticalConnections.end(), [&upper_id, &lower_id, column_boundary]( const VerticalConnection &connection) { return connectionMatches( connection, upper_id, lower_id, column_boundary); }); if (connected && found == editable->verticalConnections.end()) { editable->verticalConnections.push_back({ makeUniqueId(*editable, "vertical"), upper_id, lower_id, column_boundary}); } else if (!connected && found != editable->verticalConnections.end()) { editable->verticalConnections.erase(found); } } std::string error; if (!editable->validateStructure(project_service_.projectLimits(), &error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, first_rung_id}; } LogicEditorResult LogicEditorService::removeVerticalConnections( const std::string &logic_id, const std::vector &connection_ids) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (connection_ids.empty()) { return failure(LogicEditorError::InvalidOperation, "请先选择要删除的竖线"); } std::unordered_set ids; for (const std::string &id : connection_ids) { if (!ids.insert(id).second || findVerticalConnection(*logic, id) == nullptr) { return failure( LogicEditorError::ConnectionNotFound, "竖线删除列表包含重复或不存在的对象"); } } HistoryState before = captureState(); Project &project = project_service_.editProject(); ControlLogic *editable = editableLogic(&project, logic_id); editable->verticalConnections.erase( std::remove_if( editable->verticalConnections.begin(), editable->verticalConnections.end(), [&ids](const VerticalConnection &connection) { return ids.count(connection.id) != 0U; }), editable->verticalConnections.end()); recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, connection_ids.front()}; } LogicEditorResult LogicEditorService::deleteSelection( const std::string &logic_id, const LogicSelectionDeleteRequest &selection) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (selection.cells.empty() && selection.outputRungIds.empty() && selection.verticalConnectionIds.empty()) { return failure(LogicEditorError::InvalidOperation, "没有可删除的选中对象"); } std::unordered_set cell_positions; for (const auto &position : selection.cells) { const std::string key = position.first + "\n" + std::to_string(position.second); const LadderCell *cell = findCell( logic_id, position.first, position.second); if (!cell_positions.insert(key).second || cell == nullptr) { return failure( LogicEditorError::CellNotFound, "删除列表包含重复或不存在的网格"); } if (cell->kind == LadderCellKind::Gap) { return failure( LogicEditorError::InvalidOperation, "删除列表包含空白网格"); } } std::unordered_set output_rung_ids; for (const std::string &rung_id : selection.outputRungIds) { const LadderRung *rung = findRung(logic_id, rung_id); if (!output_rung_ids.insert(rung_id).second || rung == nullptr || !rung->output.has_value()) { return failure( LogicEditorError::NodeNotFound, "删除列表包含重复或不存在的输出指令"); } } std::unordered_set connection_ids; for (const std::string &connection_id : selection.verticalConnectionIds) { if (!connection_ids.insert(connection_id).second || findVerticalConnection(*logic, connection_id) == nullptr) { return failure( LogicEditorError::ConnectionNotFound, "删除列表包含重复或不存在的竖线"); } } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *editable = editableLogic(&project, logic_id); for (const auto &position : selection.cells) { LadderCell &cell = editableRung(editable, position.first) ->cells[static_cast(position.second)]; cell.kind = LadderCellKind::Gap; cell.node.reset(); } for (const std::string &rung_id : selection.outputRungIds) { editableRung(editable, rung_id)->output.reset(); } editable->verticalConnections.erase( std::remove_if( editable->verticalConnections.begin(), editable->verticalConnections.end(), [&connection_ids](const VerticalConnection &connection) { return connection_ids.count(connection.id) != 0U; }), editable->verticalConnections.end()); std::string error; if (!editable->validateStructure(project_service_.projectLimits(), &error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, logic_id}; } LogicEditorResult LogicEditorService::addParallelBranch( const std::string &logic_id, const std::string &rung_id, const std::vector &selected_node_ids, const LogicNodeConfig &config, bool configured) { const ControlLogic *logic = findLogic(logic_id); const LadderRung *rung = findRung(logic_id, rung_id); if (logic == nullptr || rung == nullptr) { return failure(LogicEditorError::RungNotFound, "未找到梯形图行"); } if (!isConditionConfig(config) || !areConditionNodesContiguous(logic_id, rung_id, selected_node_ids)) { return failure( LogicEditorError::InvalidOperation, "请先选择同一行中连续的条件节点"); } if (logic->rungs.size() >= project_service_.projectLimits().maximumRungsPerLogic || totalRungCount(project_service_.project()) >= ProjectLimits::kMaximumRungsPerProject) { return failure(LogicEditorError::InvalidOperation, "梯形图行数已经达到上限"); } int first_column = ProjectLimits::kMaximumConditionColumns; int last_column = -1; for (int column = 0; column < ProjectLimits::kMaximumConditionColumns; ++column) { const LadderCell &cell = rung->cells[static_cast(column)]; if (cell.node.has_value() && std::find( selected_node_ids.cbegin(), selected_node_ids.cend(), cell.node->id) != selected_node_ids.cend()) { first_column = std::min(first_column, column); last_column = std::max(last_column, column); } } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *editable = editableLogic(&project, logic_id); const std::size_t source_index = rungIndex(*editable, rung_id); const std::string new_rung_id = insertEmptyRungAt(editable, source_index + 1U); LadderRung *branch = editableRung(editable, new_rung_id); const std::string node_id = makeUniqueId(*editable, nodePrefix(config)); branch->cells[static_cast(first_column)].kind = LadderCellKind::Node; branch->cells[static_cast(first_column)].node = LogicNode{node_id, config, configured}; for (int column = first_column + 1; column <= last_column; ++column) { branch->cells[static_cast(column)].kind = LadderCellKind::Wire; } const auto ensure_branch_edge = [editable, &rung_id, &new_rung_id]( int boundary) { const bool exists = std::any_of( editable->verticalConnections.cbegin(), editable->verticalConnections.cend(), [&rung_id, &new_rung_id, boundary]( const VerticalConnection &connection) { return connectionMatches( connection, rung_id, new_rung_id, boundary); }); if (!exists) { editable->verticalConnections.push_back({ makeUniqueId(*editable, "vertical"), rung_id, new_rung_id, boundary}); } }; ensure_branch_edge(first_column); ensure_branch_edge(last_column + 1); std::string error; if (!editable->validateStructure(project_service_.projectLimits(), &error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } LogicEditorResult LogicEditorService::addParallelToWholeCondition( const std::string &logic_id, const std::string &rung_id, const LogicNodeConfig &config, bool configured) { const LadderRung *rung = findRung(logic_id, rung_id); if (rung == nullptr) { return failure(LogicEditorError::RungNotFound, "未找到梯形图行"); } std::vector node_ids; for (const LadderCell &cell : rung->cells) { if (cell.node.has_value()) { node_ids.push_back(cell.node->id); } } if (node_ids.empty()) { return failure( LogicEditorError::InvalidOperation, "当前行还没有可并联的条件节点"); } return addParallelBranch( logic_id, rung_id, node_ids, config, configured); } LogicEditorResult LogicEditorService::setOutput( const std::string &logic_id, const std::string &rung_id, const LogicNodeConfig &config, bool configured) { const ControlLogic *existing_logic = findLogic(logic_id); if (existing_logic == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (!isOutputConfig(config)) { return failure(LogicEditorError::InvalidNode, "输出槽只能放置输出指令"); } if (rung_id.empty()) { if (existing_logic->rungs.size() >= project_service_.projectLimits().maximumRungsPerLogic || totalRungCount(project_service_.project()) >= ProjectLimits::kMaximumRungsPerProject) { return failure( LogicEditorError::InvalidOperation, "梯形图行数已经达到当前上限"); } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *logic = editableLogic(&project, logic_id); const std::string new_rung_id = insertEmptyRungAt( logic, logic->rungs.size()); LadderRung *rung = editableRung(logic, new_rung_id); const std::string node_id = makeUniqueId(*logic, nodePrefix(config)); rung->output = LogicNode{node_id, config, configured}; std::string error; if (!logic->validateStructure(project_service_.projectLimits(), &error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidNode, error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } if (findRung(logic_id, rung_id) == nullptr) { return failure(LogicEditorError::RungNotFound, "未找到梯形图行"); } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *logic = editableLogic(&project, logic_id); LadderRung *rung = editableRung(logic, rung_id); const std::string node_id = makeUniqueId(*logic, nodePrefix(config)); rung->output = LogicNode{node_id, config, configured}; std::string error; if (!logic->validateStructure(project_service_.projectLimits(), &error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidNode, error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } LogicEditResult LogicEditorService::applyOutputAndAdvance( const std::string &logic_id, const LogicEditCursor &cursor, const LogicNodeConfig &config, bool configured) { const ControlLogic *existing_logic = findLogic(logic_id); if (existing_logic == nullptr) { return {failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"), {}}; } if (!isOutputConfig(config)) { return {failure(LogicEditorError::InvalidNode, "输出槽只能放置输出指令"), {}}; } LogicNode candidate{"candidate", config, configured}; std::string error; if (!candidate.validate(&error)) { return {failure(LogicEditorError::InvalidNode, error), {}}; } std::string target_rung_id = cursor.rungId; std::size_t target_index = existing_logic->rungs.size(); bool create_output_rung = target_rung_id.empty(); if (create_output_rung) { if (!existing_logic->rungs.empty()) { return {failure(LogicEditorError::RungNotFound, "请先选择输出所在行"), {}}; } } else { target_index = rungIndex(*existing_logic, target_rung_id); if (target_index == existing_logic->rungs.size()) { return {failure(LogicEditorError::RungNotFound, "未找到梯形图行"), {}}; } } std::size_t group_end = target_index; if (!create_output_rung) { while (group_end + 1U < existing_logic->rungs.size()) { const std::string &upper_id = existing_logic->rungs[group_end].id; const std::string &lower_id = existing_logic->rungs[group_end + 1U].id; const bool connected = std::any_of( existing_logic->verticalConnections.cbegin(), existing_logic->verticalConnections.cend(), [&upper_id, &lower_id](const VerticalConnection &connection) { return connection.upperRungId == upper_id && connection.lowerRungId == lower_id; }); if (!connected) { break; } ++group_end; } } const bool append_next_rung = create_output_rung || group_end + 1U == existing_logic->rungs.size(); const std::size_t rows_to_add = append_next_rung ? (create_output_rung ? 2U : 1U) : 0U; if (existing_logic->rungs.size() + rows_to_add > project_service_.projectLimits().maximumRungsPerLogic || totalRungCount(project_service_.project()) + rows_to_add > ProjectLimits::kMaximumRungsPerProject) { return {failure( LogicEditorError::InvalidOperation, "输出后无法创建下一空行,梯形图行数已经达到当前上限"), {}}; } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *logic = editableLogic(&project, logic_id); if (create_output_rung) { target_rung_id = insertEmptyRungAt(logic, logic->rungs.size()); target_index = logic->rungs.size() - 1U; group_end = target_index; } LadderRung *rung = editableRung(logic, target_rung_id); int rightmost_content = -1; for (int column = 0; column < ProjectLimits::kMaximumConditionColumns; ++column) { if (rung->cells[static_cast(column)].kind != LadderCellKind::Gap) { rightmost_content = column; } } // 空网络直接输出时补满横线;已有内容时只补尾部,不跨越中间断点 for (int column = rightmost_content + 1; column < ProjectLimits::kMaximumConditionColumns; ++column) { LadderCell &cell = rung->cells[static_cast(column)]; cell.kind = LadderCellKind::Wire; cell.node.reset(); } const std::string node_id = makeUniqueId(*logic, nodePrefix(config)); rung->output = LogicNode{node_id, config, configured}; std::string next_rung_id; if (append_next_rung) { next_rung_id = insertEmptyRungAt(logic, logic->rungs.size()); } else { next_rung_id = logic->rungs[group_end + 1U].id; } if (!logic->validateStructure(project_service_.projectLimits(), &error)) { rollbackEdit(std::move(before), modified_before); return {failure(LogicEditorError::InvalidNode, error), {}}; } recordHistory(std::move(before)); return { {true, LogicEditorError::None, {}, node_id}, {next_rung_id, 0, false}}; } 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->isCondition() != isConditionConfig(config) || node->isOutput() != isOutputConfig(config)) { return failure( LogicEditorError::UnsupportedNodeChange, "条件节点和输出节点不能互相改型"); } LogicNode candidate{node_id, config, true}; std::string error; if (!candidate.validate(&error)) { return failure(LogicEditorError::InvalidNode, error); } HistoryState before = captureState(); Project &project = project_service_.editProject(); ControlLogic *logic = editableLogic(&project, logic_id); for (LadderRung &rung : logic->rungs) { for (LadderCell &cell : rung.cells) { if (cell.node.has_value() && cell.node->id == node_id) { cell.node->config = config; cell.node->configured = true; recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } } if (rung.output.has_value() && rung.output->id == node_id) { rung.output->config = config; rung.output->configured = true; recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } } return failure(LogicEditorError::NodeNotFound, "未找到逻辑节点"); } LogicClipboardCopyResult LogicEditorService::copySelection( const std::string &logic_id, const LogicSelectionCopyRequest &selection) const { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return {failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"), {}}; } const bool has_grid_objects = !selection.cells.empty() || !selection.outputRungIds.empty() || !selection.verticalConnectionIds.empty(); if (!selection.wholeRungIds.empty()) { if (has_grid_objects) { return {failure( LogicEditorError::InvalidOperation, "整行选择不能和网格对象混合复制"), {}}; } std::unordered_set unique_ids; std::vector indices; indices.reserve(selection.wholeRungIds.size()); for (const std::string &rung_id : selection.wholeRungIds) { const std::size_t index = rungIndex(*logic, rung_id); if (!unique_ids.insert(rung_id).second || index == logic->rungs.size()) { return {failure( LogicEditorError::InvalidOperation, "整行复制选择中存在重复或失效的行"), {}}; } indices.push_back(index); } std::sort(indices.begin(), indices.end()); for (std::size_t index = 1U; index < indices.size(); ++index) { if (indices[index] != indices[index - 1U] + 1U) { return {failure( LogicEditorError::InvalidOperation, "整行复制只支持连续的视觉行"), {}}; } } LogicClipboardFragment fragment; fragment.mode = LogicClipboardMode::WholeRows; fragment.rowSpan = static_cast(indices.size()); fragment.columnSpan = ProjectLimits::kMaximumLadderColumns; fragment.rows.reserve(indices.size()); for (std::size_t index : indices) { const LadderRung &rung = logic->rungs[index]; fragment.rows.push_back({rung.comment, rung.cells, rung.output}); } const std::size_t first = indices.front(); const std::size_t last = indices.back(); for (const VerticalConnection &connection : logic->verticalConnections) { const std::size_t upper = rungIndex(*logic, connection.upperRungId); const std::size_t lower = rungIndex(*logic, connection.lowerRungId); if (upper >= first && lower <= last && lower == upper + 1U) { fragment.verticalConnections.push_back({ static_cast(upper - first), connection.columnBoundary}); } } return {{ true, LogicEditorError::None, {}, logic->rungs[first].id}, std::move(fragment)}; } if (!has_grid_objects) { return {failure( LogicEditorError::InvalidOperation, "请先选择横线、指令、输出或竖线"), {}}; } LogicClipboardFragment fragment; fragment.mode = LogicClipboardMode::GridObjects; std::size_t minimum_row = logic->rungs.size(); std::size_t maximum_row = 0U; int minimum_column = ProjectLimits::kMaximumLadderColumns; int maximum_column = 0; const auto include_position = [ &minimum_row, &maximum_row, &minimum_column, &maximum_column](std::size_t row, int column) { minimum_row = std::min(minimum_row, row); maximum_row = std::max(maximum_row, row); minimum_column = std::min(minimum_column, column); maximum_column = std::max(maximum_column, column); }; std::unordered_set unique_cells; for (const auto &position : selection.cells) { const std::size_t row = rungIndex(*logic, position.first); const LadderCell *cell = findCell( logic_id, position.first, position.second); const std::string key = position.first + ":" + std::to_string(position.second); if (row == logic->rungs.size() || cell == nullptr || !unique_cells.insert(key).second || cell->kind == LadderCellKind::Gap) { return {failure( LogicEditorError::InvalidOperation, "复制选择中存在重复、空白或失效的网格"), {}}; } fragment.cells.push_back({ static_cast(row), position.second, cell->kind, cell->node}); include_position(row, position.second); } std::unordered_set unique_outputs; for (const std::string &rung_id : selection.outputRungIds) { const std::size_t row = rungIndex(*logic, rung_id); const LadderRung *rung = findRung(logic_id, rung_id); if (row == logic->rungs.size() || rung == nullptr || !rung->output.has_value() || !unique_outputs.insert(rung_id).second) { return {failure( LogicEditorError::InvalidOperation, "复制选择中存在重复、空白或失效的输出槽"), {}}; } fragment.outputs.push_back({ static_cast(row), ProjectLimits::kMaximumConditionColumns, *rung->output}); include_position(row, ProjectLimits::kMaximumConditionColumns); } std::unordered_set unique_connections; for (const std::string &connection_id : selection.verticalConnectionIds) { const VerticalConnection *connection = findConnection( logic_id, connection_id); if (connection == nullptr || !unique_connections.insert(connection_id).second) { return {failure( LogicEditorError::InvalidOperation, "复制选择中存在重复或失效的竖线"), {}}; } const std::size_t upper = rungIndex(*logic, connection->upperRungId); const std::size_t lower = rungIndex(*logic, connection->lowerRungId); if (upper == logic->rungs.size() || lower != upper + 1U) { return {failure( LogicEditorError::InvalidOperation, "复制选择中存在悬空竖线"), {}}; } fragment.verticalConnections.push_back({ static_cast(upper), connection->columnBoundary}); include_position(upper, connection->columnBoundary); include_position(lower, connection->columnBoundary); } for (LogicClipboardCell &cell : fragment.cells) { cell.relativeRow -= static_cast(minimum_row); cell.relativeColumn -= minimum_column; } for (LogicClipboardOutput &output : fragment.outputs) { output.relativeRow -= static_cast(minimum_row); output.relativeColumn -= minimum_column; } for (LogicClipboardVerticalConnection &connection : fragment.verticalConnections) { connection.upperRelativeRow -= static_cast(minimum_row); connection.relativeColumnBoundary -= minimum_column; } fragment.rowSpan = static_cast(maximum_row - minimum_row + 1U); fragment.columnSpan = maximum_column - minimum_column + 1; std::sort( fragment.cells.begin(), fragment.cells.end(), [](const LogicClipboardCell &left, const LogicClipboardCell &right) { return left.relativeRow != right.relativeRow ? left.relativeRow < right.relativeRow : left.relativeColumn < right.relativeColumn; }); std::sort( fragment.outputs.begin(), fragment.outputs.end(), [](const LogicClipboardOutput &left, const LogicClipboardOutput &right) { return left.relativeRow < right.relativeRow; }); std::sort( fragment.verticalConnections.begin(), fragment.verticalConnections.end(), [](const LogicClipboardVerticalConnection &left, const LogicClipboardVerticalConnection &right) { return left.upperRelativeRow != right.upperRelativeRow ? left.upperRelativeRow < right.upperRelativeRow : left.relativeColumnBoundary < right.relativeColumnBoundary; }); return {{true, LogicEditorError::None, {}, {}}, std::move(fragment)}; } LogicClipboardPasteResult LogicEditorService::pasteClipboard( const std::string &logic_id, const LogicClipboardFragment &fragment, const LogicPasteTarget &target) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return {failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"), {}, {}}; } if (fragment.mode == LogicClipboardMode::WholeRows) { if (fragment.rows.empty() || !fragment.cells.empty() || !fragment.outputs.empty()) { return {failure( LogicEditorError::InvalidOperation, "整行剪贴板内容无效"), {}, {}}; } if (logic->rungs.size() + fragment.rows.size() > project_service_.projectLimits().maximumRungsPerLogic || totalRungCount(project_service_.project()) + fragment.rows.size() > ProjectLimits::kMaximumRungsPerProject) { return {failure( LogicEditorError::InvalidOperation, "粘贴整行后将超过梯形图行数上限"), {}, {}}; } std::size_t position = 0U; if (!logic->rungs.empty()) { const std::size_t reference = rungIndex(*logic, target.rungId); if (reference == logic->rungs.size()) { return {failure( LogicEditorError::RungNotFound, "请先选择整行粘贴位置"), {}, {}}; } position = reference + 1U; } for (const LogicClipboardRow &row : fragment.rows) { if (row.cells.size() != static_cast( ProjectLimits::kMaximumConditionColumns) || containsLineBreak(row.comment) || row.comment.size() > ProjectLimits::kMaximumRungCommentBytes) { return {failure( LogicEditorError::InvalidOperation, "复制的整行结构或注释无效"), {}, {}}; } for (const LadderCell &cell : row.cells) { if (!cell.validate() || (cell.node.has_value() && !cell.node->isCondition())) { return {failure( LogicEditorError::InvalidNode, "复制的整行包含无效条件"), {}, {}}; } } if (row.output.has_value() && (!row.output->validate() || !row.output->isOutput())) { return {failure( LogicEditorError::InvalidNode, "复制的整行包含无效输出"), {}, {}}; } } std::unordered_set unique_connections; for (const LogicClipboardVerticalConnection &connection : fragment.verticalConnections) { const std::string key = std::to_string(connection.upperRelativeRow) + ":" + std::to_string(connection.relativeColumnBoundary); if (connection.upperRelativeRow < 0 || connection.upperRelativeRow + 1 >= static_cast(fragment.rows.size()) || connection.relativeColumnBoundary < 0 || connection.relativeColumnBoundary > ProjectLimits::kMaximumConditionColumns || !unique_connections.insert(key).second) { return {failure( LogicEditorError::InvalidOperation, "复制的整行包含无效竖线"), {}, {}}; } } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *editable = editableLogic(&project, logic_id); std::vector inserted_ids; inserted_ids.reserve(fragment.rows.size()); for (std::size_t offset = 0U; offset < fragment.rows.size(); ++offset) { const std::string inserted_id = insertEmptyRungAt( editable, position + offset); LadderRung *destination = editableRung(editable, inserted_id); const LogicClipboardRow &source = fragment.rows[offset]; destination->comment = source.comment; for (std::size_t column = 0U; column < source.cells.size(); ++column) { destination->cells[column].kind = source.cells[column].kind; destination->cells[column].node.reset(); if (source.cells[column].node.has_value()) { const LogicNode &source_node = *source.cells[column].node; destination->cells[column].node = LogicNode{ makeUniqueId(*editable, nodePrefix(source_node.config)), source_node.config, source_node.configured}; } } if (source.output.has_value()) { destination->output = LogicNode{ makeUniqueId(*editable, nodePrefix(source.output->config)), source.output->config, source.output->configured}; } inserted_ids.push_back(inserted_id); } for (const LogicClipboardVerticalConnection &source : fragment.verticalConnections) { const std::string &upper = inserted_ids[ static_cast(source.upperRelativeRow)]; const std::string &lower = inserted_ids[ static_cast(source.upperRelativeRow + 1)]; const auto existing = std::find_if( editable->verticalConnections.cbegin(), editable->verticalConnections.cend(), [&upper, &lower, &source](const VerticalConnection &connection) { return connectionMatches( connection, upper, lower, source.relativeColumnBoundary); }); if (existing == editable->verticalConnections.cend()) { editable->verticalConnections.push_back({ makeUniqueId(*editable, "vertical"), upper, lower, source.relativeColumnBoundary}); } } std::string error; if (!editable->validateStructure(project_service_.projectLimits(), &error)) { rollbackEdit(std::move(before), modified_before); return {failure(LogicEditorError::InvalidOperation, error), {}, {}}; } refreshRungNames(editable); recordHistory(std::move(before)); return {{ true, LogicEditorError::None, {}, inserted_ids.front()}, {}, std::move(inserted_ids)}; } if (!fragment.rows.empty() || (fragment.cells.empty() && fragment.outputs.empty() && fragment.verticalConnections.empty()) || fragment.rowSpan <= 0 || fragment.columnSpan <= 0) { return {failure( LogicEditorError::InvalidOperation, "网格剪贴板内容无效"), {}, {}}; } const std::size_t target_row = rungIndex(*logic, target.rungId); if (target_row == logic->rungs.size()) { return {failure( LogicEditorError::RungNotFound, "请先选择粘贴目标"), {}, {}}; } const bool only_vertical = fragment.cells.empty() && fragment.outputs.empty(); const bool only_output = fragment.cells.empty() && fragment.verticalConnections.empty(); if ((only_vertical && (!target.boundary || target.output)) || (only_output && (!target.output || target.boundary))) { return {failure( LogicEditorError::InvalidOperation, "剪贴板对象类型与当前粘贴位置不匹配"), {}, {}}; } if (target.column < 0 || target.column > ProjectLimits::kMaximumConditionColumns || target_row + static_cast(fragment.rowSpan) > logic->rungs.size()) { return {failure( LogicEditorError::InvalidOperation, "粘贴片段超出当前梯形图行列范围"), {}, {}}; } std::unordered_set destination_cells; for (const LogicClipboardCell &source : fragment.cells) { const int column = target.column + source.relativeColumn; const std::size_t row = target_row + static_cast(source.relativeRow); const std::string key = std::to_string(row) + ":" + std::to_string(column); if (source.relativeRow < 0 || source.relativeRow >= fragment.rowSpan || source.relativeColumn < 0 || column < 0 || column >= ProjectLimits::kMaximumConditionColumns || !destination_cells.insert(key).second || (source.kind != LadderCellKind::Wire && source.kind != LadderCellKind::Node) || (source.kind == LadderCellKind::Node && (!source.node.has_value() || !source.node->validate() || !source.node->isCondition())) || (source.kind == LadderCellKind::Wire && source.node.has_value())) { return {failure( LogicEditorError::InvalidOperation, "复制片段包含无效或越界的条件网格"), {}, {}}; } const LadderCell &destination = logic->rungs[row].cells[ static_cast(column)]; if (destination.kind == LadderCellKind::Node) { return {failure( LogicEditorError::InvalidOperation, "粘贴目标已有触点或比较指令,未执行任何修改"), {}, {}}; } } const bool explicit_output_replace = only_output && fragment.outputs.size() == 1U && fragment.rowSpan == 1; std::unordered_set destination_outputs; for (const LogicClipboardOutput &source : fragment.outputs) { const int column = target.column + source.relativeColumn; const std::size_t row = target_row + static_cast(source.relativeRow); if (source.relativeRow < 0 || source.relativeRow >= fragment.rowSpan || source.relativeColumn < 0 || column != ProjectLimits::kMaximumConditionColumns || !destination_outputs.insert(row).second || !source.node.validate() || !source.node.isOutput()) { return {failure( LogicEditorError::InvalidOperation, "复制片段包含无效或错位的输出"), {}, {}}; } if (logic->rungs[row].output.has_value() && !explicit_output_replace) { return {failure( LogicEditorError::InvalidOperation, "混合片段的目标输出槽已有指令,未执行任何修改"), {}, {}}; } } std::unordered_set destination_connections; for (const LogicClipboardVerticalConnection &source : fragment.verticalConnections) { const int boundary = target.column + source.relativeColumnBoundary; const std::size_t upper = target_row + static_cast(source.upperRelativeRow); const std::string key = std::to_string(upper) + ":" + std::to_string(boundary); if (source.upperRelativeRow < 0 || source.upperRelativeRow + 1 >= fragment.rowSpan || source.relativeColumnBoundary < 0 || upper + 1U >= logic->rungs.size() || boundary < 0 || boundary > ProjectLimits::kMaximumConditionColumns || !destination_connections.insert(key).second) { return {failure( LogicEditorError::InvalidOperation, "复制片段包含无效、重复或悬空的竖线"), {}, {}}; } } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *editable = editableLogic(&project, logic_id); LogicClipboardPasteResult result; for (const LogicClipboardCell &source : fragment.cells) { const std::size_t row = target_row + static_cast(source.relativeRow); const int column = target.column + source.relativeColumn; LadderCell &destination = editable->rungs[row].cells[ static_cast(column)]; destination.kind = source.kind; destination.node.reset(); if (source.node.has_value()) { const std::string node_id = makeUniqueId( *editable, nodePrefix(source.node->config)); destination.node = LogicNode{ node_id, source.node->config, source.node->configured}; if (result.edit.id.empty()) { result.edit.id = node_id; } } result.selection.cells.emplace_back(editable->rungs[row].id, column); if (result.edit.id.empty()) { result.edit.id = destination.id; } } for (const LogicClipboardOutput &source : fragment.outputs) { const std::size_t row = target_row + static_cast(source.relativeRow); const std::string node_id = makeUniqueId( *editable, nodePrefix(source.node.config)); editable->rungs[row].output = LogicNode{ node_id, source.node.config, source.node.configured}; result.selection.outputRungIds.push_back(editable->rungs[row].id); if (result.edit.id.empty()) { result.edit.id = node_id; } } for (const LogicClipboardVerticalConnection &source : fragment.verticalConnections) { const std::size_t upper = target_row + static_cast(source.upperRelativeRow); const int boundary = target.column + source.relativeColumnBoundary; const std::string &upper_id = editable->rungs[upper].id; const std::string &lower_id = editable->rungs[upper + 1U].id; auto existing = std::find_if( editable->verticalConnections.begin(), editable->verticalConnections.end(), [&upper_id, &lower_id, boundary](const VerticalConnection &connection) { return connectionMatches( connection, upper_id, lower_id, boundary); }); if (existing == editable->verticalConnections.end()) { editable->verticalConnections.push_back({ makeUniqueId(*editable, "vertical"), upper_id, lower_id, boundary}); existing = std::prev(editable->verticalConnections.end()); } result.selection.verticalConnectionIds.push_back(existing->id); if (result.edit.id.empty()) { result.edit.id = existing->id; } } std::string error; if (!editable->validateStructure(project_service_.projectLimits(), &error)) { rollbackEdit(std::move(before), modified_before); return {failure(LogicEditorError::InvalidOperation, error), {}, {}}; } recordHistory(std::move(before)); result.edit.succeeded = true; result.edit.error = LogicEditorError::None; return result; } LogicEditorResult LogicEditorService::pasteConditionNodes( const std::string &logic_id, const std::string &rung_id, const std::vector &nodes, int start_column) { const ControlLogic *logic = findLogic(logic_id); const LadderRung *rung = findRung(logic_id, rung_id); if (logic == nullptr || rung == nullptr) { return failure(LogicEditorError::RungNotFound, "未找到粘贴目标行"); } if (nodes.empty() || nodes.size() > static_cast(ProjectLimits::kMaximumConditionColumns)) { return failure(LogicEditorError::InvalidOperation, "复制的条件数量无效"); } for (const LogicNode &node : nodes) { if (!node.validate() || !node.isCondition()) { return failure(LogicEditorError::InvalidNode, "只能粘贴有效的条件节点"); } } if (start_column < 0) { for (int candidate = 0; candidate + static_cast(nodes.size()) <= ProjectLimits::kMaximumConditionColumns; ++candidate) { bool available = true; for (std::size_t offset = 0U; offset < nodes.size(); ++offset) { available = available && rung->cells[static_cast(candidate) + offset].kind == LadderCellKind::Gap; } if (available) { start_column = candidate; break; } } } if (start_column < 0 || start_column + static_cast(nodes.size()) > ProjectLimits::kMaximumConditionColumns) { return failure(LogicEditorError::InvalidOperation, "目标行没有足够连续空格"); } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *editable = editableLogic(&project, logic_id); LadderRung *target = editableRung(editable, rung_id); std::string first_id; for (std::size_t offset = 0U; offset < nodes.size(); ++offset) { LadderCell &cell = target->cells[ static_cast(start_column) + offset]; const std::string id = makeUniqueId( *editable, nodePrefix(nodes[offset].config)); cell.kind = LadderCellKind::Node; cell.node = LogicNode{id, nodes[offset].config, nodes[offset].configured}; if (first_id.empty()) { first_id = id; } } std::string error; if (!editable->validateStructure(project_service_.projectLimits(), &error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, first_id}; } bool LogicEditorService::areConditionNodesContiguous( const std::string &logic_id, const std::string &rung_id, const std::vector &node_ids) const { const LadderRung *rung = findRung(logic_id, rung_id); if (rung == nullptr || node_ids.empty()) { return false; } std::unordered_set requested( node_ids.cbegin(), node_ids.cend()); if (requested.size() != node_ids.size()) { return false; } std::vector columns; for (int column = 0; column < ProjectLimits::kMaximumConditionColumns; ++column) { const LadderCell &cell = rung->cells[static_cast(column)]; if (cell.node.has_value() && requested.count(cell.node->id) != 0U) { columns.push_back(column); } } return columns.size() == node_ids.size() && columns.back() - columns.front() + 1 == static_cast(columns.size()); } LogicEditorResult LogicEditorService::pasteRung( const std::string &logic_id, const LadderRung &source) { const ControlLogic *logic = findLogic(logic_id); std::string error; if (logic == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (!source.validateStructure(&error)) { return failure(LogicEditorError::InvalidOperation, error); } if (logic->rungs.size() >= project_service_.projectLimits().maximumRungsPerLogic || totalRungCount(project_service_.project()) >= ProjectLimits::kMaximumRungsPerProject) { return failure(LogicEditorError::InvalidOperation, "梯形图行数已经达到上限"); } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *editable = editableLogic(&project, logic_id); LadderRung pasted = makeEmptyRung(*editable, editable->rungs.size()); pasted.comment = source.comment; for (std::size_t column = 0U; column < source.cells.size(); ++column) { pasted.cells[column].kind = source.cells[column].kind; if (source.cells[column].node.has_value()) { const LogicNode &source_node = *source.cells[column].node; pasted.cells[column].node = LogicNode{ makeUniqueId(*editable, nodePrefix(source_node.config)), source_node.config, source_node.configured}; } } if (source.output.has_value()) { pasted.output = LogicNode{ makeUniqueId(*editable, nodePrefix(source.output->config)), source.output->config, source.output->configured}; } const std::string pasted_id = pasted.id; editable->rungs.push_back(std::move(pasted)); refreshRungNames(editable); if (!editable->validateStructure(project_service_.projectLimits(), &error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, pasted_id}; } 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 ids; for (const std::string &id : node_ids) { if (!ids.insert(id).second || findNode(logic_id, id) == nullptr) { return failure( LogicEditorError::NodeNotFound, "节点删除列表包含重复或不存在的对象"); } } HistoryState before = captureState(); Project &project = project_service_.editProject(); ControlLogic *logic = editableLogic(&project, logic_id); for (LadderRung &rung : logic->rungs) { for (LadderCell &cell : rung.cells) { if (cell.node.has_value() && ids.count(cell.node->id) != 0U) { cell.kind = LadderCellKind::Gap; cell.node.reset(); } } if (rung.output.has_value() && ids.count(rung.output->id) != 0U) { rung.output.reset(); } } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_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(); } LogicEditResult LogicEditorService::applyCellAndAdvance( const std::string &logic_id, const LogicEditCursor &cursor, const LogicNodeConfig *config, bool configured) { const ControlLogic *existing_logic = findLogic(logic_id); if (existing_logic == nullptr) { return {failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"), {}}; } if (cursor.output || cursor.column < 0 || cursor.column >= ProjectLimits::kMaximumConditionColumns) { return {failure(LogicEditorError::CellNotFound, "条件指令只能放在第 1~10 列"), {}}; } if (config != nullptr) { if (!isConditionConfig(*config)) { return {failure(LogicEditorError::InvalidNode, "条件区只能放置条件指令"), {}}; } LogicNode candidate{"candidate", *config, configured}; std::string candidate_error; if (!candidate.validate(&candidate_error)) { return {failure(LogicEditorError::InvalidNode, candidate_error), {}}; } } std::string target_rung_id = cursor.rungId; int target_column = cursor.column; const bool create_first_rung = target_rung_id.empty(); if (create_first_rung) { if (!existing_logic->rungs.empty()) { return {failure(LogicEditorError::RungNotFound, "请先选择要编辑的梯形图行"), {}}; } if (existing_logic->rungs.size() >= project_service_.projectLimits().maximumRungsPerLogic || totalRungCount(project_service_.project()) >= ProjectLimits::kMaximumRungsPerProject) { return {failure(LogicEditorError::InvalidOperation, "梯形图行数已经达到当前上限"), {}}; } target_column = 0; } else { const LadderCell *existing_cell = findCell( logic_id, target_rung_id, target_column); if (existing_cell == nullptr) { return {failure(LogicEditorError::CellNotFound, "未找到目标条件网格"), {}}; } if (config == nullptr && existing_cell->kind == LadderCellKind::Node) { return {failure(LogicEditorError::InvalidOperation, "横线不能覆盖已有条件指令"), {}}; } } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *logic = editableLogic(&project, logic_id); if (create_first_rung) { target_rung_id = insertEmptyRungAt(logic, 0U); } LadderCell &cell = editableRung(logic, target_rung_id) ->cells[static_cast(target_column)]; std::string edited_id = cell.id; if (config == nullptr) { cell.kind = LadderCellKind::Wire; cell.node.reset(); } else { edited_id = makeUniqueId(*logic, nodePrefix(*config)); cell.kind = LadderCellKind::Node; cell.node = LogicNode{edited_id, *config, configured}; } std::string error; if (!logic->validateStructure(project_service_.projectLimits(), &error)) { rollbackEdit(std::move(before), modified_before); return {failure(LogicEditorError::InvalidOperation, error), {}}; } recordHistory(std::move(before)); LogicEditCursor next{target_rung_id, target_column + 1, false}; if (next.column >= ProjectLimits::kMaximumConditionColumns) { next.column = ProjectLimits::kMaximumConditionColumns; next.output = true; } return { {true, LogicEditorError::None, {}, edited_id}, std::move(next)}; } bool LogicEditorService::isConditionConfig(const LogicNodeConfig &config) { return 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::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 "coil"; } 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::makeUniqueId( const ControlLogic &logic, const std::string &prefix) { const auto exists = [&logic](const std::string &candidate) { if (logic.id == candidate) { return true; } for (const LadderRung &rung : logic.rungs) { if (rung.id == candidate || (rung.output.has_value() && rung.output->id == candidate)) { return true; } for (const LadderCell &cell : rung.cells) { if (cell.id == candidate || (cell.node.has_value() && cell.node->id == candidate)) { return true; } } } return std::any_of( logic.verticalConnections.cbegin(), logic.verticalConnections.cend(), [&candidate](const VerticalConnection &connection) { return connection.id == candidate; }); }; for (std::size_t index = 1U;; ++index) { const std::string candidate = prefix + '-' + std::to_string(index); if (!exists(candidate)) { return candidate; } } } LadderRung LogicEditorService::makeEmptyRung( const ControlLogic &logic, std::size_t visual_index) { LadderRung rung; rung.id = makeUniqueId(logic, "rung"); rung.name = "行 " + std::to_string(visual_index + 1U); rung.cells.reserve( static_cast(ProjectLimits::kMaximumConditionColumns)); for (int column = 0; column < ProjectLimits::kMaximumConditionColumns; ++column) { rung.cells.push_back({ rung.id + "-cell-" + std::to_string(column + 1), LadderCellKind::Gap, std::nullopt}); } return rung; } std::string LogicEditorService::insertEmptyRungAt( ControlLogic *logic, std::size_t position) { if (logic == nullptr || position > logic->rungs.size()) { return {}; } const std::string upper_id = position > 0U ? logic->rungs[position - 1U].id : std::string{}; const std::string lower_id = position < logic->rungs.size() ? logic->rungs[position].id : std::string{}; std::vector bridges; if (!upper_id.empty() && !lower_id.empty()) { for (const VerticalConnection &connection : logic->verticalConnections) { if (connection.upperRungId == upper_id && connection.lowerRungId == lower_id) { bridges.push_back(connection); } } logic->verticalConnections.erase( std::remove_if( logic->verticalConnections.begin(), logic->verticalConnections.end(), [&upper_id, &lower_id](const VerticalConnection &connection) { return connection.upperRungId == upper_id && connection.lowerRungId == lower_id; }), logic->verticalConnections.end()); } LadderRung rung = makeEmptyRung(*logic, position); const std::string new_id = rung.id; logic->rungs.insert( logic->rungs.begin() + static_cast(position), std::move(rung)); for (VerticalConnection &bridge : bridges) { bridge.lowerRungId = new_id; logic->verticalConnections.push_back(bridge); } for (const VerticalConnection &bridge : bridges) { logic->verticalConnections.push_back({ makeUniqueId(*logic, "vertical"), new_id, lower_id, bridge.columnBoundary}); } refreshRungNames(logic); return new_id; } void LogicEditorService::removeRungAt( ControlLogic *logic, std::size_t position) { if (logic == nullptr || position >= logic->rungs.size()) { return; } const std::string removed_id = logic->rungs[position].id; const std::string upper_id = position > 0U ? logic->rungs[position - 1U].id : std::string{}; const std::string lower_id = position + 1U < logic->rungs.size() ? logic->rungs[position + 1U].id : std::string{}; std::map upper_connections; std::map lower_connections; for (const VerticalConnection &connection : logic->verticalConnections) { if (connection.upperRungId == upper_id && connection.lowerRungId == removed_id) { upper_connections[connection.columnBoundary] = connection.id; } if (connection.upperRungId == removed_id && connection.lowerRungId == lower_id) { lower_connections[connection.columnBoundary] = connection.id; } } logic->verticalConnections.erase( std::remove_if( logic->verticalConnections.begin(), logic->verticalConnections.end(), [&removed_id](const VerticalConnection &connection) { return connection.upperRungId == removed_id || connection.lowerRungId == removed_id; }), logic->verticalConnections.end()); logic->rungs.erase( logic->rungs.begin() + static_cast(position)); if (!upper_id.empty() && !lower_id.empty()) { for (const auto &upper : upper_connections) { if (lower_connections.count(upper.first) != 0U) { logic->verticalConnections.push_back({ upper.second, upper_id, lower_id, upper.first}); } } } refreshRungNames(logic); } void LogicEditorService::refreshRungNames(ControlLogic *logic) { if (logic == nullptr) { return; } for (std::size_t index = 0U; index < logic->rungs.size(); ++index) { logic->rungs[index].name = "行 " + std::to_string(index + 1U); } } LogicEditorResult LogicEditorService::failure( LogicEditorError error, const std::string &message) { return {false, error, message, {}}; }