|
- #include "logic_editor_service.h"
-
- #include "project_service.h"
-
- #include <algorithm>
- #include <cctype>
- #include <iterator>
- #include <map>
- #include <type_traits>
- #include <unordered_map>
- #include <unordered_set>
- #include <utility>
-
- 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::size_t>(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<unsigned char> forwardBase;
- std::vector<unsigned char> forward;
- std::vector<unsigned char> backwardBase;
- std::vector<unsigned char> backward;
- std::vector<const VerticalConnection *> verticalByBoundaryAndUpperRow;
-
- std::size_t stateIndex(std::size_t row, int boundary) const
- {
- return row * static_cast<std::size_t>(
- ProjectLimits::kMaximumConditionColumns + 1)
- + static_cast<std::size_t>(boundary);
- }
-
- bool stateAt(
- const std::vector<unsigned char> &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<std::size_t>(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<std::size_t>(
- 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<std::size_t>(
- ProjectLimits::kMaximumConditionColumns + 1)
- * (analysis.rowCount - 1U),
- nullptr);
- std::unordered_map<std::string, std::size_t> 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<std::size_t>(connection.columnBoundary)
- * (analysis.rowCount - 1U)
- + upper] = &connection;
- }
-
- const auto mergeBoundaryComponents = [&analysis](
- int boundary,
- const std::vector<unsigned char> &base,
- std::vector<unsigned char> *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<std::size_t>(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<std::size_t>(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<int> networkNumbersByRow(const ControlLogic &logic)
- {
- std::vector<int> 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<std::string> activeVerticalConnections(
- const SyntaxConnectivity &analysis)
- {
- std::unordered_set<std::string> 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<int> networks = networkNumbersByRow(*logic);
- const int network_count = networks.empty() ? 0 : networks.back() + 1;
- std::vector<unsigned char> invalid_networks(
- static_cast<std::size_t>(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<std::size_t>(networks[row])] = 1U;
- }
- }
-
- const std::unordered_set<std::string> active_verticals =
- activeVerticalConnections(analysis);
- std::unordered_map<std::string, std::size_t> 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<std::size_t>(networks[row])] != 0U)
- {
- continue;
- }
- for (int column = 0;
- column < ProjectLimits::kMaximumConditionColumns;
- ++column)
- {
- LadderCell &cell = logic->rungs[row]
- .cells[static_cast<std::size_t>(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<std::size_t>(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<std::pair<LogicSyntaxLocation, std::string>> firstSyntaxIssue(
- const ControlLogic &logic)
- {
- const std::vector<int> 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<int>(row + 1U),
- static_cast<int>(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<int>(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<int>(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<decltype(left_config)>;
- using Right = std::decay_t<decltype(right_config)>;
- if constexpr (!std::is_same_v<Left, Right>)
- {
- return false;
- }
- else if constexpr (std::is_same_v<Left, ContactNodeConfig>)
- {
- return left_config.address == right_config.address
- && left_config.mode == right_config.mode;
- }
- else if constexpr (std::is_same_v<Left, EdgeContactNodeConfig>)
- {
- return left_config.address == right_config.address
- && left_config.mode == right_config.mode;
- }
- else if constexpr (std::is_same_v<Left, CoilNodeConfig>)
- {
- return left_config.address == right_config.address
- && left_config.mode == right_config.mode;
- }
- else if constexpr (std::is_same_v<Left, CompareNodeConfig>)
- {
- 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<Left, MoveNodeConfig>)
- {
- 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<int>(rung->cells.size())
- ? nullptr
- : &rung->cells[static_cast<std::size_t>(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<std::ptrdiff_t>(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<int> networks = networkNumbersByRow(*logic);
- std::map<int, std::size_t> 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<CoilNodeConfig>(&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<int>(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<std::string> 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<std::string> &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<std::string> &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<std::string> unique_ids;
- std::vector<std::size_t> 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<std::size_t>());
-
- 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<std::size_t>(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<std::size_t>(index)] =
- std::move(rung->cells[static_cast<std::size_t>(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<std::size_t>(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::size_t>(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<int>(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<std::size_t>(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<std::pair<std::string, int>> 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<std::pair<std::string, int>> &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<std::string> 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<std::size_t>(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<std::pair<std::string, int>> &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<std::size_t>(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<std::string> &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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::size_t>(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<std::string> &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<std::size_t>(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<std::size_t>(first_column)].kind =
- LadderCellKind::Node;
- branch->cells[static_cast<std::size_t>(first_column)].node =
- LogicNode{node_id, config, configured};
- for (int column = first_column + 1; column <= last_column; ++column)
- {
- branch->cells[static_cast<std::size_t>(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<std::string> 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<std::size_t>(column)].kind
- != LadderCellKind::Gap)
- {
- rightmost_content = column;
- }
- }
- // 空网络直接输出时补满横线;已有内容时只补尾部,不跨越中间断点
- for (int column = rightmost_content + 1;
- column < ProjectLimits::kMaximumConditionColumns;
- ++column)
- {
- LadderCell &cell = rung->cells[static_cast<std::size_t>(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<std::string> unique_ids;
- std::vector<std::size_t> 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<int>(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<int>(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<std::string> 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<int>(row),
- position.second,
- cell->kind,
- cell->node});
- include_position(row, position.second);
- }
-
- std::unordered_set<std::string> 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<int>(row),
- ProjectLimits::kMaximumConditionColumns,
- *rung->output});
- include_position(row, ProjectLimits::kMaximumConditionColumns);
- }
-
- std::unordered_set<std::string> 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<int>(upper), connection->columnBoundary});
- include_position(upper, connection->columnBoundary);
- include_position(lower, connection->columnBoundary);
- }
-
- for (LogicClipboardCell &cell : fragment.cells)
- {
- cell.relativeRow -= static_cast<int>(minimum_row);
- cell.relativeColumn -= minimum_column;
- }
- for (LogicClipboardOutput &output : fragment.outputs)
- {
- output.relativeRow -= static_cast<int>(minimum_row);
- output.relativeColumn -= minimum_column;
- }
- for (LogicClipboardVerticalConnection &connection
- : fragment.verticalConnections)
- {
- connection.upperRelativeRow -= static_cast<int>(minimum_row);
- connection.relativeColumnBoundary -= minimum_column;
- }
- fragment.rowSpan = static_cast<int>(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<std::size_t>(
- 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<std::string> 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<int>(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<std::string> 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<std::size_t>(source.upperRelativeRow)];
- const std::string &lower = inserted_ids[
- static_cast<std::size_t>(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<std::size_t>(fragment.rowSpan)
- > logic->rungs.size())
- {
- return {failure(
- LogicEditorError::InvalidOperation,
- "粘贴片段超出当前梯形图行列范围"), {}, {}};
- }
-
- std::unordered_set<std::string> destination_cells;
- for (const LogicClipboardCell &source : fragment.cells)
- {
- const int column = target.column + source.relativeColumn;
- const std::size_t row = target_row
- + static_cast<std::size_t>(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<std::size_t>(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<std::size_t> destination_outputs;
- for (const LogicClipboardOutput &source : fragment.outputs)
- {
- const int column = target.column + source.relativeColumn;
- const std::size_t row = target_row
- + static_cast<std::size_t>(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<std::string> destination_connections;
- for (const LogicClipboardVerticalConnection &source
- : fragment.verticalConnections)
- {
- const int boundary = target.column + source.relativeColumnBoundary;
- const std::size_t upper = target_row
- + static_cast<std::size_t>(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<std::size_t>(source.relativeRow);
- const int column = target.column + source.relativeColumn;
- LadderCell &destination = editable->rungs[row].cells[
- static_cast<std::size_t>(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<std::size_t>(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<std::size_t>(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<LogicNode> &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<std::size_t>(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<int>(nodes.size())
- <= ProjectLimits::kMaximumConditionColumns;
- ++candidate)
- {
- bool available = true;
- for (std::size_t offset = 0U; offset < nodes.size(); ++offset)
- {
- available = available
- && rung->cells[static_cast<std::size_t>(candidate) + offset].kind
- == LadderCellKind::Gap;
- }
- if (available)
- {
- start_column = candidate;
- break;
- }
- }
- }
- if (start_column < 0
- || start_column + static_cast<int>(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<std::size_t>(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<std::string> &node_ids) const
- {
- const LadderRung *rung = findRung(logic_id, rung_id);
- if (rung == nullptr || node_ids.empty())
- {
- return false;
- }
- std::unordered_set<std::string> requested(
- node_ids.cbegin(), node_ids.cend());
- if (requested.size() != node_ids.size())
- {
- return false;
- }
- std::vector<int> columns;
- for (int column = 0; column < ProjectLimits::kMaximumConditionColumns; ++column)
- {
- const LadderCell &cell = rung->cells[static_cast<std::size_t>(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<int>(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<std::string> &node_ids)
- {
- if (findLogic(logic_id) == nullptr)
- {
- return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
- }
- if (node_ids.empty())
- {
- return failure(LogicEditorError::InvalidOperation, "请先选择要删除的节点");
- }
- std::unordered_set<std::string> 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<HistoryState> 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<HistoryState> 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<std::size_t>(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<ContactNodeConfig>(config)
- || std::holds_alternative<EdgeContactNodeConfig>(config)
- || std::holds_alternative<CompareNodeConfig>(config);
- }
-
- bool LogicEditorService::isOutputConfig(const LogicNodeConfig &config)
- {
- return std::holds_alternative<CoilNodeConfig>(config)
- || std::holds_alternative<MoveNodeConfig>(config)
- || std::holds_alternative<ArithmeticNodeConfig>(config);
- }
-
- std::string LogicEditorService::nodePrefix(const LogicNodeConfig &config)
- {
- return std::visit(
- [](const auto &value) -> std::string
- {
- using Config = std::decay_t<decltype(value)>;
- if constexpr (std::is_same_v<Config, ContactNodeConfig>)
- {
- return "contact";
- }
- else if constexpr (std::is_same_v<Config, EdgeContactNodeConfig>)
- {
- return "edge";
- }
- else if constexpr (std::is_same_v<Config, CoilNodeConfig>)
- {
- return "coil";
- }
- else if constexpr (std::is_same_v<Config, MoveNodeConfig>)
- {
- return "move";
- }
- else if constexpr (std::is_same_v<Config, ArithmeticNodeConfig>)
- {
- 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<std::size_t>(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<VerticalConnection> 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<std::ptrdiff_t>(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<int, std::string> upper_connections;
- std::map<int, std::string> 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<std::ptrdiff_t>(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, {}};
- }
|