综合平台编程器项目的远程存储
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

3532 строки
124 KiB

  1. #include "logic_editor_service.h"
  2. #include "project_service.h"
  3. #include <algorithm>
  4. #include <cctype>
  5. #include <iterator>
  6. #include <map>
  7. #include <type_traits>
  8. #include <unordered_map>
  9. #include <unordered_set>
  10. #include <utility>
  11. namespace {
  12. bool isBlank(const std::string &value)
  13. {
  14. return value.empty()
  15. || std::all_of(
  16. value.cbegin(), value.cend(),
  17. [](unsigned char character)
  18. {
  19. return std::isspace(character) != 0;
  20. });
  21. }
  22. bool containsLineBreak(const std::string &value)
  23. {
  24. return value.find('\r') != std::string::npos
  25. || value.find('\n') != std::string::npos;
  26. }
  27. std::string makeUniqueLogicId(const Project &project)
  28. {
  29. for (std::size_t suffix = 1U;; ++suffix)
  30. {
  31. const std::string candidate = "logic-" + std::to_string(suffix);
  32. if (std::none_of(
  33. project.controlLogics.cbegin(), project.controlLogics.cend(),
  34. [&candidate](const ControlLogic &logic)
  35. {
  36. return logic.id == candidate;
  37. }))
  38. {
  39. return candidate;
  40. }
  41. }
  42. }
  43. std::size_t totalRungCount(const Project &project)
  44. {
  45. std::size_t count = 0U;
  46. for (const ControlLogic &logic : project.controlLogics)
  47. {
  48. count += logic.rungs.size();
  49. }
  50. return count;
  51. }
  52. ControlLogic *editableLogic(Project *project, const std::string &logic_id)
  53. {
  54. if (project == nullptr)
  55. {
  56. return nullptr;
  57. }
  58. const auto found = std::find_if(
  59. project->controlLogics.begin(), project->controlLogics.end(),
  60. [&logic_id](const ControlLogic &logic) { return logic.id == logic_id; });
  61. return found == project->controlLogics.end() ? nullptr : &*found;
  62. }
  63. LadderRung *editableRung(ControlLogic *logic, const std::string &rung_id)
  64. {
  65. if (logic == nullptr)
  66. {
  67. return nullptr;
  68. }
  69. const auto found = std::find_if(
  70. logic->rungs.begin(), logic->rungs.end(),
  71. [&rung_id](const LadderRung &rung) { return rung.id == rung_id; });
  72. return found == logic->rungs.end() ? nullptr : &*found;
  73. }
  74. std::size_t rungIndex(const ControlLogic &logic, const std::string &rung_id)
  75. {
  76. const auto found = std::find_if(
  77. logic.rungs.cbegin(), logic.rungs.cend(),
  78. [&rung_id](const LadderRung &rung) { return rung.id == rung_id; });
  79. return found == logic.rungs.cend()
  80. ? logic.rungs.size()
  81. : static_cast<std::size_t>(std::distance(logic.rungs.cbegin(), found));
  82. }
  83. bool connectionMatches(
  84. const VerticalConnection &connection,
  85. const std::string &upper_rung_id,
  86. const std::string &lower_rung_id,
  87. int column_boundary)
  88. {
  89. return connection.upperRungId == upper_rung_id
  90. && connection.lowerRungId == lower_rung_id
  91. && connection.columnBoundary == column_boundary;
  92. }
  93. struct SyntaxConnectivity
  94. {
  95. std::size_t rowCount = 0U;
  96. std::vector<unsigned char> forwardBase;
  97. std::vector<unsigned char> forward;
  98. std::vector<unsigned char> backwardBase;
  99. std::vector<unsigned char> backward;
  100. std::vector<const VerticalConnection *> verticalByBoundaryAndUpperRow;
  101. std::size_t stateIndex(std::size_t row, int boundary) const
  102. {
  103. return row * static_cast<std::size_t>(
  104. ProjectLimits::kMaximumConditionColumns + 1)
  105. + static_cast<std::size_t>(boundary);
  106. }
  107. bool stateAt(
  108. const std::vector<unsigned char> &states,
  109. std::size_t row,
  110. int boundary) const
  111. {
  112. return states[stateIndex(row, boundary)] != 0U;
  113. }
  114. const VerticalConnection *verticalAt(
  115. int boundary, std::size_t upper_row) const
  116. {
  117. if (rowCount < 2U || upper_row + 1U >= rowCount)
  118. {
  119. return nullptr;
  120. }
  121. return verticalByBoundaryAndUpperRow[
  122. static_cast<std::size_t>(boundary) * (rowCount - 1U) + upper_row];
  123. }
  124. };
  125. SyntaxConnectivity analyzeConnectivity(const ControlLogic &logic)
  126. {
  127. SyntaxConnectivity analysis;
  128. analysis.rowCount = logic.rungs.size();
  129. const std::size_t state_count = analysis.rowCount
  130. * static_cast<std::size_t>(
  131. ProjectLimits::kMaximumConditionColumns + 1);
  132. analysis.forwardBase.assign(state_count, 0U);
  133. analysis.forward.assign(state_count, 0U);
  134. analysis.backwardBase.assign(state_count, 0U);
  135. analysis.backward.assign(state_count, 0U);
  136. if (analysis.rowCount == 0U)
  137. {
  138. return analysis;
  139. }
  140. analysis.verticalByBoundaryAndUpperRow.assign(
  141. static_cast<std::size_t>(
  142. ProjectLimits::kMaximumConditionColumns + 1)
  143. * (analysis.rowCount - 1U),
  144. nullptr);
  145. std::unordered_map<std::string, std::size_t> row_indices;
  146. row_indices.reserve(analysis.rowCount);
  147. for (std::size_t row = 0U; row < analysis.rowCount; ++row)
  148. {
  149. row_indices.emplace(logic.rungs[row].id, row);
  150. }
  151. for (const VerticalConnection &connection : logic.verticalConnections)
  152. {
  153. const std::size_t upper = row_indices.at(connection.upperRungId);
  154. analysis.verticalByBoundaryAndUpperRow[
  155. static_cast<std::size_t>(connection.columnBoundary)
  156. * (analysis.rowCount - 1U)
  157. + upper] = &connection;
  158. }
  159. const auto mergeBoundaryComponents = [&analysis](
  160. int boundary,
  161. const std::vector<unsigned char> &base,
  162. std::vector<unsigned char> *merged)
  163. {
  164. std::size_t first = 0U;
  165. while (first < analysis.rowCount)
  166. {
  167. std::size_t last = first;
  168. while (last + 1U < analysis.rowCount
  169. && analysis.verticalAt(boundary, last) != nullptr)
  170. {
  171. ++last;
  172. }
  173. bool active = false;
  174. for (std::size_t row = first; row <= last; ++row)
  175. {
  176. active = active || analysis.stateAt(base, row, boundary);
  177. }
  178. for (std::size_t row = first; row <= last; ++row)
  179. {
  180. (*merged)[analysis.stateIndex(row, boundary)] = active ? 1U : 0U;
  181. }
  182. first = last + 1U;
  183. }
  184. };
  185. for (int boundary = 0;
  186. boundary <= ProjectLimits::kMaximumConditionColumns;
  187. ++boundary)
  188. {
  189. for (std::size_t row = 0U; row < analysis.rowCount; ++row)
  190. {
  191. const bool active = boundary == 0
  192. || (analysis.stateAt(analysis.forward, row, boundary - 1)
  193. && logic.rungs[row]
  194. .cells[static_cast<std::size_t>(boundary - 1)]
  195. .kind != LadderCellKind::Gap);
  196. analysis.forwardBase[analysis.stateIndex(row, boundary)] =
  197. active ? 1U : 0U;
  198. }
  199. mergeBoundaryComponents(
  200. boundary, analysis.forwardBase, &analysis.forward);
  201. }
  202. for (int boundary = ProjectLimits::kMaximumConditionColumns;
  203. boundary >= 0;
  204. --boundary)
  205. {
  206. for (std::size_t row = 0U; row < analysis.rowCount; ++row)
  207. {
  208. const bool active = boundary
  209. == ProjectLimits::kMaximumConditionColumns
  210. ? logic.rungs[row].output.has_value()
  211. : logic.rungs[row]
  212. .cells[static_cast<std::size_t>(boundary)]
  213. .kind != LadderCellKind::Gap
  214. && analysis.stateAt(
  215. analysis.backward, row, boundary + 1);
  216. analysis.backwardBase[analysis.stateIndex(row, boundary)] =
  217. active ? 1U : 0U;
  218. }
  219. mergeBoundaryComponents(
  220. boundary, analysis.backwardBase, &analysis.backward);
  221. }
  222. return analysis;
  223. }
  224. std::vector<int> networkNumbersByRow(const ControlLogic &logic)
  225. {
  226. std::vector<int> networks(logic.rungs.size(), 0);
  227. int network = 0;
  228. for (std::size_t row = 0U; row < logic.rungs.size(); ++row)
  229. {
  230. if (row > 0U)
  231. {
  232. const std::string &upper = logic.rungs[row - 1U].id;
  233. const std::string &lower = logic.rungs[row].id;
  234. const bool connected = std::any_of(
  235. logic.verticalConnections.cbegin(),
  236. logic.verticalConnections.cend(),
  237. [&upper, &lower](const VerticalConnection &connection)
  238. {
  239. return connection.upperRungId == upper
  240. && connection.lowerRungId == lower;
  241. });
  242. if (!connected)
  243. {
  244. ++network;
  245. }
  246. }
  247. networks[row] = network;
  248. }
  249. return networks;
  250. }
  251. std::unordered_set<std::string> activeVerticalConnections(
  252. const SyntaxConnectivity &analysis)
  253. {
  254. std::unordered_set<std::string> active_connections;
  255. for (int boundary = 0;
  256. boundary <= ProjectLimits::kMaximumConditionColumns;
  257. ++boundary)
  258. {
  259. std::size_t first = 0U;
  260. while (first < analysis.rowCount)
  261. {
  262. std::size_t last = first;
  263. while (last + 1U < analysis.rowCount
  264. && analysis.verticalAt(boundary, last) != nullptr)
  265. {
  266. ++last;
  267. }
  268. int total_forward = 0;
  269. int total_backward = 0;
  270. for (std::size_t row = first; row <= last; ++row)
  271. {
  272. total_forward += analysis.stateAt(
  273. analysis.forwardBase, row, boundary) ? 1 : 0;
  274. total_backward += analysis.stateAt(
  275. analysis.backwardBase, row, boundary) ? 1 : 0;
  276. }
  277. int upper_forward = 0;
  278. int upper_backward = 0;
  279. for (std::size_t upper = first; upper < last; ++upper)
  280. {
  281. upper_forward += analysis.stateAt(
  282. analysis.forwardBase, upper, boundary) ? 1 : 0;
  283. upper_backward += analysis.stateAt(
  284. analysis.backwardBase, upper, boundary) ? 1 : 0;
  285. const int lower_forward = total_forward - upper_forward;
  286. const int lower_backward = total_backward - upper_backward;
  287. if ((upper_forward > 0 && lower_backward > 0)
  288. || (upper_backward > 0 && lower_forward > 0))
  289. {
  290. active_connections.insert(
  291. analysis.verticalAt(boundary, upper)->id);
  292. }
  293. }
  294. first = last + 1U;
  295. }
  296. }
  297. return active_connections;
  298. }
  299. struct LogicCleanupStats
  300. {
  301. std::size_t wireCells = 0U;
  302. std::size_t verticalConnections = 0U;
  303. };
  304. LogicCleanupStats normalizeLogicWires(ControlLogic *logic)
  305. {
  306. LogicCleanupStats stats;
  307. if (logic == nullptr || logic->rungs.empty())
  308. {
  309. return stats;
  310. }
  311. const SyntaxConnectivity analysis = analyzeConnectivity(*logic);
  312. const std::vector<int> networks = networkNumbersByRow(*logic);
  313. const int network_count = networks.empty() ? 0 : networks.back() + 1;
  314. std::vector<unsigned char> invalid_networks(
  315. static_cast<std::size_t>(network_count), 0U);
  316. for (std::size_t row = 0U; row < logic->rungs.size(); ++row)
  317. {
  318. if (logic->rungs[row].output.has_value()
  319. && !analysis.stateAt(
  320. analysis.forward,
  321. row,
  322. ProjectLimits::kMaximumConditionColumns))
  323. {
  324. invalid_networks[static_cast<std::size_t>(networks[row])] = 1U;
  325. }
  326. }
  327. const std::unordered_set<std::string> active_verticals =
  328. activeVerticalConnections(analysis);
  329. std::unordered_map<std::string, std::size_t> row_indices;
  330. row_indices.reserve(logic->rungs.size());
  331. for (std::size_t row = 0U; row < logic->rungs.size(); ++row)
  332. {
  333. row_indices.emplace(logic->rungs[row].id, row);
  334. }
  335. for (std::size_t row = 0U; row < logic->rungs.size(); ++row)
  336. {
  337. if (invalid_networks[static_cast<std::size_t>(networks[row])] != 0U)
  338. {
  339. continue;
  340. }
  341. for (int column = 0;
  342. column < ProjectLimits::kMaximumConditionColumns;
  343. ++column)
  344. {
  345. LadderCell &cell = logic->rungs[row]
  346. .cells[static_cast<std::size_t>(column)];
  347. if (cell.kind == LadderCellKind::Wire
  348. && !(analysis.stateAt(analysis.forward, row, column)
  349. && analysis.stateAt(
  350. analysis.backward, row, column + 1)))
  351. {
  352. cell.kind = LadderCellKind::Gap;
  353. ++stats.wireCells;
  354. }
  355. }
  356. }
  357. const auto new_end = std::remove_if(
  358. logic->verticalConnections.begin(),
  359. logic->verticalConnections.end(),
  360. [&row_indices, &networks, &invalid_networks, &active_verticals, &stats](
  361. const VerticalConnection &connection)
  362. {
  363. const std::size_t upper_row = row_indices.at(connection.upperRungId);
  364. if (invalid_networks[
  365. static_cast<std::size_t>(networks[upper_row])] != 0U
  366. || active_verticals.find(connection.id)
  367. != active_verticals.end())
  368. {
  369. return false;
  370. }
  371. ++stats.verticalConnections;
  372. return true;
  373. });
  374. logic->verticalConnections.erase(new_end, logic->verticalConnections.end());
  375. return stats;
  376. }
  377. std::optional<std::pair<LogicSyntaxLocation, std::string>> firstSyntaxIssue(
  378. const ControlLogic &logic)
  379. {
  380. const std::vector<int> networks = networkNumbersByRow(logic);
  381. for (std::size_t row = 0U; row < logic.rungs.size(); ++row)
  382. {
  383. const LadderRung &rung = logic.rungs[row];
  384. for (std::size_t column = 0U; column < rung.cells.size(); ++column)
  385. {
  386. if (rung.cells[column].node.has_value()
  387. && !rung.cells[column].node->isConfigured())
  388. {
  389. LogicSyntaxLocation location{
  390. logic.id,
  391. rung.id,
  392. networks[row] + 1,
  393. static_cast<int>(row + 1U),
  394. static_cast<int>(column + 1U)};
  395. return std::make_pair(
  396. location,
  397. "控制逻辑 " + logic.name + " 的网络 "
  398. + std::to_string(location.network) + ",第 "
  399. + std::to_string(location.row) + " 行第 "
  400. + std::to_string(location.column)
  401. + " 列:程序语法分析发生错误,条件指令尚未配置");
  402. }
  403. }
  404. if (rung.output.has_value() && !rung.output->isConfigured())
  405. {
  406. LogicSyntaxLocation location{
  407. logic.id,
  408. rung.id,
  409. networks[row] + 1,
  410. static_cast<int>(row + 1U),
  411. ProjectLimits::kMaximumLadderColumns};
  412. return std::make_pair(
  413. location,
  414. "控制逻辑 " + logic.name + " 的网络 "
  415. + std::to_string(location.network) + ",第 "
  416. + std::to_string(location.row) + " 行第 "
  417. + std::to_string(location.column)
  418. + " 列:程序语法分析发生错误,输出指令尚未配置");
  419. }
  420. }
  421. const SyntaxConnectivity analysis = analyzeConnectivity(logic);
  422. for (std::size_t row = 0U; row < logic.rungs.size(); ++row)
  423. {
  424. const LadderRung &rung = logic.rungs[row];
  425. if (!rung.output.has_value()
  426. || analysis.stateAt(
  427. analysis.forward,
  428. row,
  429. ProjectLimits::kMaximumConditionColumns))
  430. {
  431. continue;
  432. }
  433. int last_reachable_boundary = 0;
  434. for (int boundary = 0;
  435. boundary <= ProjectLimits::kMaximumConditionColumns;
  436. ++boundary)
  437. {
  438. if (analysis.stateAt(analysis.forward, row, boundary))
  439. {
  440. last_reachable_boundary = boundary;
  441. }
  442. }
  443. const int disconnected_column = std::min(
  444. last_reachable_boundary + 1,
  445. ProjectLimits::kMaximumConditionColumns);
  446. LogicSyntaxLocation location{
  447. logic.id,
  448. rung.id,
  449. networks[row] + 1,
  450. static_cast<int>(row + 1U),
  451. ProjectLimits::kMaximumLadderColumns};
  452. return std::make_pair(
  453. location,
  454. "控制逻辑 " + logic.name + " 的网络 "
  455. + std::to_string(location.network) + ",第 "
  456. + std::to_string(location.row) + " 行第 "
  457. + std::to_string(location.column)
  458. + " 列:程序语法分析发生错误,输出路径从第 "
  459. + std::to_string(disconnected_column)
  460. + " 列起断开,未连接到左母线");
  461. }
  462. return std::nullopt;
  463. }
  464. } // namespace
  465. LogicEditorService::LogicEditorService(ProjectService &project_service)
  466. : project_service_(project_service)
  467. {
  468. }
  469. LogicEditorService::HistoryState LogicEditorService::captureState() const
  470. {
  471. return {project_service_.project().controlLogics};
  472. }
  473. void LogicEditorService::recordHistory(HistoryState before)
  474. {
  475. history_.record(
  476. std::move(before), captureState(), &LogicEditorService::statesEqual);
  477. }
  478. void LogicEditorService::rollbackEdit(
  479. HistoryState before, bool modified_before)
  480. {
  481. project_service_.editProject().controlLogics = std::move(before.logics);
  482. project_service_.restoreModifiedState(modified_before);
  483. }
  484. bool LogicEditorService::statesEqual(
  485. const HistoryState &left, const HistoryState &right)
  486. {
  487. if (left.logics.size() != right.logics.size())
  488. {
  489. return false;
  490. }
  491. for (std::size_t index = 0U; index < left.logics.size(); ++index)
  492. {
  493. if (!logicsEqual(left.logics[index], right.logics[index]))
  494. {
  495. return false;
  496. }
  497. }
  498. return true;
  499. }
  500. bool LogicEditorService::logicsEqual(
  501. const ControlLogic &left, const ControlLogic &right)
  502. {
  503. if (left.id != right.id || left.name != right.name
  504. || left.enabled != right.enabled
  505. || left.rungs.size() != right.rungs.size()
  506. || left.verticalConnections.size() != right.verticalConnections.size())
  507. {
  508. return false;
  509. }
  510. for (std::size_t index = 0U; index < left.rungs.size(); ++index)
  511. {
  512. if (!rungsEqual(left.rungs[index], right.rungs[index]))
  513. {
  514. return false;
  515. }
  516. }
  517. for (std::size_t index = 0U;
  518. index < left.verticalConnections.size();
  519. ++index)
  520. {
  521. const VerticalConnection &left_connection =
  522. left.verticalConnections[index];
  523. const VerticalConnection &right_connection =
  524. right.verticalConnections[index];
  525. if (left_connection.id != right_connection.id
  526. || left_connection.upperRungId != right_connection.upperRungId
  527. || left_connection.lowerRungId != right_connection.lowerRungId
  528. || left_connection.columnBoundary
  529. != right_connection.columnBoundary)
  530. {
  531. return false;
  532. }
  533. }
  534. return true;
  535. }
  536. bool LogicEditorService::rungsEqual(
  537. const LadderRung &left, const LadderRung &right)
  538. {
  539. if (left.id != right.id || left.name != right.name
  540. || left.comment != right.comment
  541. || left.cells.size() != right.cells.size()
  542. || left.output.has_value() != right.output.has_value())
  543. {
  544. return false;
  545. }
  546. for (std::size_t index = 0U; index < left.cells.size(); ++index)
  547. {
  548. if (!cellsEqual(left.cells[index], right.cells[index]))
  549. {
  550. return false;
  551. }
  552. }
  553. return !left.output.has_value()
  554. || nodesEqual(*left.output, *right.output);
  555. }
  556. bool LogicEditorService::cellsEqual(
  557. const LadderCell &left, const LadderCell &right)
  558. {
  559. return left.id == right.id && left.kind == right.kind
  560. && left.node.has_value() == right.node.has_value()
  561. && (!left.node.has_value() || nodesEqual(*left.node, *right.node));
  562. }
  563. bool LogicEditorService::nodesEqual(
  564. const LogicNode &left, const LogicNode &right)
  565. {
  566. return left.id == right.id && left.configured == right.configured
  567. && configsEqual(left.config, right.config);
  568. }
  569. bool LogicEditorService::configsEqual(
  570. const LogicNodeConfig &left, const LogicNodeConfig &right)
  571. {
  572. return std::visit(
  573. [](const auto &left_config, const auto &right_config)
  574. {
  575. using Left = std::decay_t<decltype(left_config)>;
  576. using Right = std::decay_t<decltype(right_config)>;
  577. if constexpr (!std::is_same_v<Left, Right>)
  578. {
  579. return false;
  580. }
  581. else if constexpr (std::is_same_v<Left, ContactNodeConfig>)
  582. {
  583. return left_config.address == right_config.address
  584. && left_config.mode == right_config.mode;
  585. }
  586. else if constexpr (std::is_same_v<Left, EdgeContactNodeConfig>)
  587. {
  588. return left_config.address == right_config.address
  589. && left_config.mode == right_config.mode;
  590. }
  591. else if constexpr (std::is_same_v<Left, CoilNodeConfig>)
  592. {
  593. return left_config.address == right_config.address
  594. && left_config.mode == right_config.mode;
  595. }
  596. else if constexpr (std::is_same_v<Left, CompareNodeConfig>)
  597. {
  598. return left_config.address == right_config.address
  599. && left_config.comparison == right_config.comparison
  600. && left_config.value == right_config.value;
  601. }
  602. else if constexpr (std::is_same_v<Left, MoveNodeConfig>)
  603. {
  604. return left_config.source.kind == right_config.source.kind
  605. && left_config.source.address == right_config.source.address
  606. && left_config.source.constant == right_config.source.constant
  607. && left_config.destination == right_config.destination;
  608. }
  609. else
  610. {
  611. return left_config.operation == right_config.operation
  612. && left_config.left.kind == right_config.left.kind
  613. && left_config.left.address == right_config.left.address
  614. && left_config.left.constant == right_config.left.constant
  615. && left_config.right.kind == right_config.right.kind
  616. && left_config.right.address == right_config.right.address
  617. && left_config.right.constant == right_config.right.constant
  618. && left_config.destination == right_config.destination;
  619. }
  620. },
  621. left,
  622. right);
  623. }
  624. LogicEditorResult LogicEditorService::historyFailure(
  625. const std::string &message)
  626. {
  627. return {false, LogicEditorError::InvalidOperation, message, {}};
  628. }
  629. const ControlLogic *LogicEditorService::findLogic(
  630. const std::string &logic_id) const
  631. {
  632. const auto &logics = project_service_.project().controlLogics;
  633. const auto found = std::find_if(
  634. logics.cbegin(), logics.cend(),
  635. [&logic_id](const ControlLogic &logic) { return logic.id == logic_id; });
  636. return found == logics.cend() ? nullptr : &*found;
  637. }
  638. const LadderRung *LogicEditorService::findRung(
  639. const std::string &logic_id, const std::string &rung_id) const
  640. {
  641. const ControlLogic *logic = findLogic(logic_id);
  642. if (logic == nullptr)
  643. {
  644. return nullptr;
  645. }
  646. const auto found = std::find_if(
  647. logic->rungs.cbegin(), logic->rungs.cend(),
  648. [&rung_id](const LadderRung &rung) { return rung.id == rung_id; });
  649. return found == logic->rungs.cend() ? nullptr : &*found;
  650. }
  651. const LadderCell *LogicEditorService::findCell(
  652. const std::string &logic_id,
  653. const std::string &rung_id,
  654. int column) const
  655. {
  656. const LadderRung *rung = findRung(logic_id, rung_id);
  657. return rung == nullptr || column < 0
  658. || column >= static_cast<int>(rung->cells.size())
  659. ? nullptr
  660. : &rung->cells[static_cast<std::size_t>(column)];
  661. }
  662. const LadderCell *LogicEditorService::findCell(
  663. const std::string &logic_id,
  664. const std::string &rung_id,
  665. const std::string &cell_id) const
  666. {
  667. const LadderRung *rung = findRung(logic_id, rung_id);
  668. return rung == nullptr ? nullptr : findLadderCell(*rung, cell_id);
  669. }
  670. const VerticalConnection *LogicEditorService::findConnection(
  671. const std::string &logic_id,
  672. const std::string &connection_id) const
  673. {
  674. const ControlLogic *logic = findLogic(logic_id);
  675. return logic == nullptr
  676. ? nullptr : findVerticalConnection(*logic, connection_id);
  677. }
  678. const LogicNode *LogicEditorService::findNode(
  679. const std::string &logic_id, const std::string &node_id) const
  680. {
  681. const ControlLogic *logic = findLogic(logic_id);
  682. if (logic == nullptr)
  683. {
  684. return nullptr;
  685. }
  686. for (const LadderRung &rung : logic->rungs)
  687. {
  688. for (const LadderCell &cell : rung.cells)
  689. {
  690. if (cell.node.has_value() && cell.node->id == node_id)
  691. {
  692. return &*cell.node;
  693. }
  694. }
  695. if (rung.output.has_value() && rung.output->id == node_id)
  696. {
  697. return &*rung.output;
  698. }
  699. }
  700. return nullptr;
  701. }
  702. std::string LogicEditorService::firstLogicId() const
  703. {
  704. const auto &logics = project_service_.project().controlLogics;
  705. return logics.empty() ? std::string{} : logics.front().id;
  706. }
  707. std::string LogicEditorService::firstRungId(
  708. const std::string &logic_id) const
  709. {
  710. const ControlLogic *logic = findLogic(logic_id);
  711. return logic == nullptr || logic->rungs.empty()
  712. ? std::string{} : logic->rungs.front().id;
  713. }
  714. std::string LogicEditorService::rungIdForNode(
  715. const std::string &logic_id, const std::string &node_id) const
  716. {
  717. const ControlLogic *logic = findLogic(logic_id);
  718. if (logic == nullptr)
  719. {
  720. return {};
  721. }
  722. for (const LadderRung &rung : logic->rungs)
  723. {
  724. if (rung.output.has_value() && rung.output->id == node_id)
  725. {
  726. return rung.id;
  727. }
  728. for (const LadderCell &cell : rung.cells)
  729. {
  730. if (cell.node.has_value() && cell.node->id == node_id)
  731. {
  732. return rung.id;
  733. }
  734. }
  735. }
  736. return {};
  737. }
  738. std::string LogicEditorService::registerCommentFor(
  739. const RegisterAddress &address) const
  740. {
  741. const RegisterComment *comment =
  742. project_service_.project().findRegisterComment(address);
  743. return comment == nullptr ? std::string{} : comment->text;
  744. }
  745. LogicEditorResult LogicEditorService::ensureDefaultLogic()
  746. {
  747. if (!project_service_.project().controlLogics.empty())
  748. {
  749. return {true, LogicEditorError::None, {}, firstLogicId()};
  750. }
  751. ControlLogic logic;
  752. logic.id = "logic-1";
  753. logic.name = "控制逻辑 1";
  754. Project &project = project_service_.editProject();
  755. project.controlLogics.push_back(std::move(logic));
  756. return {true, LogicEditorError::None, {}, project.controlLogics.back().id};
  757. }
  758. LogicEditorResult LogicEditorService::addLogic(const std::string &name)
  759. {
  760. if (isBlank(name) || name.size() > ProjectLimits::kMaximumTextBytes)
  761. {
  762. return failure(
  763. LogicEditorError::InvalidOperation,
  764. "控制逻辑名称不能为空且不能超过 256 个 UTF-8 字节");
  765. }
  766. const Project &current = project_service_.project();
  767. if (current.controlLogics.size()
  768. >= project_service_.projectLimits().maximumControlLogics)
  769. {
  770. return failure(
  771. LogicEditorError::InvalidOperation,
  772. "控制逻辑数量已经达到当前配置上限");
  773. }
  774. if (std::any_of(
  775. current.controlLogics.cbegin(), current.controlLogics.cend(),
  776. [&name](const ControlLogic &logic) { return logic.name == name; }))
  777. {
  778. return failure(LogicEditorError::DuplicateName, "控制逻辑名称必须唯一");
  779. }
  780. ControlLogic logic;
  781. logic.id = makeUniqueLogicId(current);
  782. logic.name = name;
  783. HistoryState before = captureState();
  784. Project &project = project_service_.editProject();
  785. project.controlLogics.push_back(std::move(logic));
  786. recordHistory(std::move(before));
  787. return {true, LogicEditorError::None, {}, project.controlLogics.back().id};
  788. }
  789. LogicEditorResult LogicEditorService::renameLogic(
  790. const std::string &logic_id, const std::string &name)
  791. {
  792. if (isBlank(name) || name.size() > ProjectLimits::kMaximumTextBytes)
  793. {
  794. return failure(
  795. LogicEditorError::InvalidOperation,
  796. "控制逻辑名称不能为空且不能超过 256 个 UTF-8 字节");
  797. }
  798. const ControlLogic *logic = findLogic(logic_id);
  799. if (logic == nullptr)
  800. {
  801. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  802. }
  803. const Project &current = project_service_.project();
  804. if (std::any_of(
  805. current.controlLogics.cbegin(), current.controlLogics.cend(),
  806. [&logic_id, &name](const ControlLogic &candidate)
  807. {
  808. return candidate.id != logic_id && candidate.name == name;
  809. }))
  810. {
  811. return failure(LogicEditorError::DuplicateName, "控制逻辑名称必须唯一");
  812. }
  813. if (logic->name == name)
  814. {
  815. return {true, LogicEditorError::None, {}, logic_id};
  816. }
  817. HistoryState before = captureState();
  818. Project &project = project_service_.editProject();
  819. editableLogic(&project, logic_id)->name = name;
  820. recordHistory(std::move(before));
  821. return {true, LogicEditorError::None, {}, logic_id};
  822. }
  823. LogicEditorResult LogicEditorService::removeLogic(
  824. const std::string &logic_id)
  825. {
  826. const Project &current = project_service_.project();
  827. if (findLogic(logic_id) == nullptr)
  828. {
  829. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  830. }
  831. if (current.controlLogics.size() <= 1U)
  832. {
  833. return failure(
  834. LogicEditorError::LastLogicRequired,
  835. "工程至少需要保留一个控制逻辑");
  836. }
  837. HistoryState before = captureState();
  838. Project &project = project_service_.editProject();
  839. project.controlLogics.erase(
  840. std::remove_if(
  841. project.controlLogics.begin(), project.controlLogics.end(),
  842. [&logic_id](const ControlLogic &logic)
  843. {
  844. return logic.id == logic_id;
  845. }),
  846. project.controlLogics.end());
  847. recordHistory(std::move(before));
  848. return {true, LogicEditorError::None, {}, logic_id};
  849. }
  850. LogicEditorResult LogicEditorService::moveLogic(
  851. const std::string &logic_id, int offset)
  852. {
  853. if (offset != -1 && offset != 1)
  854. {
  855. return failure(
  856. LogicEditorError::InvalidOperation,
  857. "控制逻辑每次只能上移或下移一位");
  858. }
  859. const Project &current = project_service_.project();
  860. const auto found = std::find_if(
  861. current.controlLogics.cbegin(), current.controlLogics.cend(),
  862. [&logic_id](const ControlLogic &logic) { return logic.id == logic_id; });
  863. if (found == current.controlLogics.cend())
  864. {
  865. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  866. }
  867. const std::ptrdiff_t index =
  868. std::distance(current.controlLogics.cbegin(), found);
  869. const std::ptrdiff_t target = index + offset;
  870. if (target < 0
  871. || target >= static_cast<std::ptrdiff_t>(current.controlLogics.size()))
  872. {
  873. return failure(
  874. LogicEditorError::InvalidOperation,
  875. "控制逻辑已经位于目标边界");
  876. }
  877. HistoryState before = captureState();
  878. Project &project = project_service_.editProject();
  879. std::iter_swap(
  880. project.controlLogics.begin() + index,
  881. project.controlLogics.begin() + target);
  882. recordHistory(std::move(before));
  883. return {true, LogicEditorError::None, {}, logic_id};
  884. }
  885. LogicEditorResult LogicEditorService::setLogicEnabled(
  886. const std::string &logic_id, bool enabled)
  887. {
  888. const ControlLogic *logic = findLogic(logic_id);
  889. if (logic == nullptr)
  890. {
  891. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  892. }
  893. if (logic->enabled == enabled)
  894. {
  895. return {true, LogicEditorError::None, {}, logic_id};
  896. }
  897. HistoryState before = captureState();
  898. Project &project = project_service_.editProject();
  899. editableLogic(&project, logic_id)->enabled = enabled;
  900. recordHistory(std::move(before));
  901. return {true, LogicEditorError::None, {}, logic_id};
  902. }
  903. LogicSyntaxCheckResult LogicEditorService::checkSyntax(
  904. const std::string &logic_id)
  905. {
  906. if (findLogic(logic_id) == nullptr)
  907. {
  908. LogicSyntaxCheckResult result;
  909. result.message = "未找到要检查的控制逻辑";
  910. return result;
  911. }
  912. return checkSyntaxForLogics({logic_id});
  913. }
  914. LogicSyntaxCheckResult LogicEditorService::checkDoubleCoils(
  915. const std::string &logic_id) const
  916. {
  917. LogicSyntaxCheckResult result;
  918. const ControlLogic *logic = findLogic(logic_id);
  919. if (logic == nullptr)
  920. {
  921. result.message = "未找到要检查的控制逻辑";
  922. return result;
  923. }
  924. result.completed = true;
  925. result.valid = true;
  926. result.checkedLogicCount = 1U;
  927. const std::vector<int> networks = networkNumbersByRow(*logic);
  928. std::map<int, std::size_t> first_rows_by_address;
  929. for (std::size_t row = 0U; row < logic->rungs.size(); ++row)
  930. {
  931. const LadderRung &rung = logic->rungs[row];
  932. if (!rung.output.has_value())
  933. {
  934. continue;
  935. }
  936. const auto *coil = std::get_if<CoilNodeConfig>(&rung.output->config);
  937. if (coil == nullptr)
  938. {
  939. continue;
  940. }
  941. const auto inserted = first_rows_by_address.emplace(
  942. coil->address.index(), row);
  943. if (inserted.second)
  944. {
  945. continue;
  946. }
  947. const std::size_t first_row = inserted.first->second;
  948. LogicSyntaxLocation location{
  949. logic->id,
  950. rung.id,
  951. networks[row] + 1,
  952. static_cast<int>(row + 1U),
  953. ProjectLimits::kMaximumLadderColumns};
  954. result.valid = false;
  955. result.location = location;
  956. result.message = "控制逻辑 " + logic->name + " 的网络 "
  957. + std::to_string(location.network) + ",第 "
  958. + std::to_string(location.row) + " 行第 "
  959. + std::to_string(location.column) + " 列:发现双线圈输出 "
  960. + coil->address.toString() + ",首次输出位于第 "
  961. + std::to_string(first_row + 1U) + " 行";
  962. return result;
  963. }
  964. result.message = "双线圈检查通过";
  965. return result;
  966. }
  967. LogicSyntaxCheckResult LogicEditorService::checkEnabledSyntax()
  968. {
  969. std::vector<std::string> logic_ids;
  970. for (const ControlLogic &logic : project_service_.project().controlLogics)
  971. {
  972. if (logic.enabled)
  973. {
  974. logic_ids.push_back(logic.id);
  975. }
  976. }
  977. return checkSyntaxForLogics(logic_ids);
  978. }
  979. LogicSyntaxCheckResult LogicEditorService::checkSyntaxForLogics(
  980. const std::vector<std::string> &logic_ids)
  981. {
  982. LogicSyntaxCheckResult result;
  983. result.checkedLogicCount = logic_ids.size();
  984. HistoryState before = captureState();
  985. HistoryState candidate = before;
  986. for (const std::string &logic_id : logic_ids)
  987. {
  988. ControlLogic *logic = nullptr;
  989. const auto found = std::find_if(
  990. candidate.logics.begin(),
  991. candidate.logics.end(),
  992. [&logic_id](const ControlLogic &item) { return item.id == logic_id; });
  993. if (found != candidate.logics.end())
  994. {
  995. logic = &*found;
  996. }
  997. if (logic == nullptr)
  998. {
  999. result.message = "语法检查期间未找到控制逻辑";
  1000. return result;
  1001. }
  1002. std::string structure_error;
  1003. if (!logic->validateStructure(
  1004. project_service_.projectLimits(), &structure_error))
  1005. {
  1006. result.completed = true;
  1007. result.message = "程序语法分析发生错误:" + structure_error;
  1008. return result;
  1009. }
  1010. }
  1011. for (const std::string &logic_id : logic_ids)
  1012. {
  1013. const auto found = std::find_if(
  1014. candidate.logics.begin(),
  1015. candidate.logics.end(),
  1016. [&logic_id](const ControlLogic &item) { return item.id == logic_id; });
  1017. const LogicCleanupStats stats = normalizeLogicWires(&*found);
  1018. result.removedWireCells += stats.wireCells;
  1019. result.removedVerticalConnections += stats.verticalConnections;
  1020. }
  1021. for (const std::string &logic_id : logic_ids)
  1022. {
  1023. const auto found = std::find_if(
  1024. candidate.logics.cbegin(),
  1025. candidate.logics.cend(),
  1026. [&logic_id](const ControlLogic &item) { return item.id == logic_id; });
  1027. const auto issue = firstSyntaxIssue(*found);
  1028. if (issue.has_value())
  1029. {
  1030. result.location = issue->first;
  1031. result.message = issue->second;
  1032. break;
  1033. }
  1034. }
  1035. result.completed = true;
  1036. result.valid = !result.location.has_value();
  1037. result.changed = !statesEqual(before, candidate);
  1038. if (result.changed)
  1039. {
  1040. project_service_.editProject().controlLogics = std::move(candidate.logics);
  1041. recordHistory(std::move(before));
  1042. }
  1043. if (result.valid)
  1044. {
  1045. result.message = "语法检查通过";
  1046. }
  1047. return result;
  1048. }
  1049. LogicEditorResult LogicEditorService::addRung(const std::string &logic_id)
  1050. {
  1051. return insertRung(logic_id, {}, true);
  1052. }
  1053. LogicEditorResult LogicEditorService::insertRung(
  1054. const std::string &logic_id,
  1055. const std::string &reference_rung_id,
  1056. bool after)
  1057. {
  1058. const ControlLogic *logic = findLogic(logic_id);
  1059. if (logic == nullptr)
  1060. {
  1061. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  1062. }
  1063. if (logic->rungs.size()
  1064. >= project_service_.projectLimits().maximumRungsPerLogic
  1065. || totalRungCount(project_service_.project())
  1066. >= ProjectLimits::kMaximumRungsPerProject)
  1067. {
  1068. return failure(
  1069. LogicEditorError::InvalidOperation,
  1070. "梯形图行数已经达到当前上限");
  1071. }
  1072. std::size_t position = logic->rungs.size();
  1073. if (!reference_rung_id.empty())
  1074. {
  1075. const std::size_t reference = rungIndex(*logic, reference_rung_id);
  1076. if (reference == logic->rungs.size())
  1077. {
  1078. return failure(LogicEditorError::RungNotFound, "未找到梯形图行");
  1079. }
  1080. position = reference + (after ? 1U : 0U);
  1081. }
  1082. HistoryState before = captureState();
  1083. const bool modified_before = project_service_.isModified();
  1084. Project &project = project_service_.editProject();
  1085. ControlLogic *editable = editableLogic(&project, logic_id);
  1086. const std::string new_id = insertEmptyRungAt(editable, position);
  1087. std::string error;
  1088. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  1089. {
  1090. rollbackEdit(std::move(before), modified_before);
  1091. return failure(LogicEditorError::InvalidOperation, error);
  1092. }
  1093. recordHistory(std::move(before));
  1094. return {true, LogicEditorError::None, {}, new_id};
  1095. }
  1096. LogicEditorResult LogicEditorService::removeRung(
  1097. const std::string &logic_id, const std::string &rung_id)
  1098. {
  1099. return removeRungs(logic_id, {rung_id});
  1100. }
  1101. LogicEditorResult LogicEditorService::removeRungs(
  1102. const std::string &logic_id,
  1103. const std::vector<std::string> &rung_ids)
  1104. {
  1105. const ControlLogic *logic = findLogic(logic_id);
  1106. if (logic == nullptr)
  1107. {
  1108. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  1109. }
  1110. if (rung_ids.empty())
  1111. {
  1112. return failure(LogicEditorError::InvalidOperation, "请先选择要删除的行");
  1113. }
  1114. std::unordered_set<std::string> unique_ids;
  1115. std::vector<std::size_t> indices;
  1116. for (const std::string &rung_id : rung_ids)
  1117. {
  1118. const std::size_t index = rungIndex(*logic, rung_id);
  1119. if (!unique_ids.insert(rung_id).second)
  1120. {
  1121. return failure(
  1122. LogicEditorError::InvalidOperation,
  1123. "删除列表中存在重复行");
  1124. }
  1125. if (index == logic->rungs.size())
  1126. {
  1127. return failure(LogicEditorError::RungNotFound, "未找到梯形图行");
  1128. }
  1129. indices.push_back(index);
  1130. }
  1131. std::sort(indices.begin(), indices.end(), std::greater<std::size_t>());
  1132. HistoryState before = captureState();
  1133. const bool modified_before = project_service_.isModified();
  1134. Project &project = project_service_.editProject();
  1135. ControlLogic *editable = editableLogic(&project, logic_id);
  1136. for (std::size_t index : indices)
  1137. {
  1138. removeRungAt(editable, index);
  1139. }
  1140. std::string error;
  1141. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  1142. {
  1143. rollbackEdit(std::move(before), modified_before);
  1144. return failure(LogicEditorError::InvalidOperation, error);
  1145. }
  1146. recordHistory(std::move(before));
  1147. return {true, LogicEditorError::None, {}, rung_ids.front()};
  1148. }
  1149. LogicEditorResult LogicEditorService::updateRungComment(
  1150. const std::string &logic_id,
  1151. const std::string &rung_id,
  1152. const std::string &comment)
  1153. {
  1154. const LadderRung *rung = findRung(logic_id, rung_id);
  1155. if (rung == nullptr)
  1156. {
  1157. return failure(LogicEditorError::RungNotFound, "未找到梯形图行");
  1158. }
  1159. if (containsLineBreak(comment)
  1160. || comment.size() > ProjectLimits::kMaximumRungCommentBytes)
  1161. {
  1162. return failure(
  1163. LogicEditorError::InvalidOperation,
  1164. "行注释必须是最多 128 个 UTF-8 字节的单行文本");
  1165. }
  1166. if (rung->comment == comment)
  1167. {
  1168. return {true, LogicEditorError::None, {}, rung_id};
  1169. }
  1170. HistoryState before = captureState();
  1171. Project &project = project_service_.editProject();
  1172. editableRung(editableLogic(&project, logic_id), rung_id)->comment = comment;
  1173. recordHistory(std::move(before));
  1174. return {true, LogicEditorError::None, {}, rung_id};
  1175. }
  1176. LogicEditorResult LogicEditorService::setConditionAtColumn(
  1177. const std::string &logic_id,
  1178. const std::string &rung_id,
  1179. int column,
  1180. const LogicNodeConfig &config,
  1181. bool configured)
  1182. {
  1183. const LadderCell *cell = findCell(logic_id, rung_id, column);
  1184. if (cell == nullptr)
  1185. {
  1186. return failure(LogicEditorError::CellNotFound, "未找到目标条件网格");
  1187. }
  1188. if (!isConditionConfig(config))
  1189. {
  1190. return failure(LogicEditorError::InvalidNode, "条件区只能放置条件指令");
  1191. }
  1192. LogicNode candidate{"candidate", config, configured};
  1193. std::string error;
  1194. if (!candidate.validate(&error))
  1195. {
  1196. return failure(LogicEditorError::InvalidNode, error);
  1197. }
  1198. HistoryState before = captureState();
  1199. const bool modified_before = project_service_.isModified();
  1200. Project &project = project_service_.editProject();
  1201. ControlLogic *logic = editableLogic(&project, logic_id);
  1202. LadderRung *rung = editableRung(logic, rung_id);
  1203. LadderCell &editable_cell = rung->cells[static_cast<std::size_t>(column)];
  1204. const std::string node_id = makeUniqueId(*logic, nodePrefix(config));
  1205. editable_cell.kind = LadderCellKind::Node;
  1206. editable_cell.node = LogicNode{node_id, config, configured};
  1207. if (!logic->validateStructure(project_service_.projectLimits(), &error))
  1208. {
  1209. rollbackEdit(std::move(before), modified_before);
  1210. return failure(LogicEditorError::InvalidOperation, error);
  1211. }
  1212. recordHistory(std::move(before));
  1213. return {true, LogicEditorError::None, {}, node_id};
  1214. }
  1215. LogicEditResult LogicEditorService::applyConditionAndAdvance(
  1216. const std::string &logic_id,
  1217. const LogicEditCursor &cursor,
  1218. const LogicNodeConfig &config,
  1219. bool configured)
  1220. {
  1221. return applyCellAndAdvance(
  1222. logic_id, cursor, &config, configured);
  1223. }
  1224. LogicEditorResult LogicEditorService::insertConditionAtColumn(
  1225. const std::string &logic_id,
  1226. const std::string &rung_id,
  1227. int column,
  1228. const LogicNodeConfig &config,
  1229. bool configured)
  1230. {
  1231. const LadderRung *existing = findRung(logic_id, rung_id);
  1232. if (existing == nullptr || column < 0
  1233. || column >= ProjectLimits::kMaximumConditionColumns)
  1234. {
  1235. return failure(LogicEditorError::CellNotFound, "未找到目标条件网格");
  1236. }
  1237. if (!isConditionConfig(config))
  1238. {
  1239. return failure(LogicEditorError::InvalidNode, "条件区只能放置条件指令");
  1240. }
  1241. if (existing->cells.back().kind != LadderCellKind::Gap)
  1242. {
  1243. return failure(
  1244. LogicEditorError::InvalidOperation,
  1245. "第 10 列已有内容,无法继续向右插入");
  1246. }
  1247. HistoryState before = captureState();
  1248. const bool modified_before = project_service_.isModified();
  1249. Project &project = project_service_.editProject();
  1250. ControlLogic *logic = editableLogic(&project, logic_id);
  1251. LadderRung *rung = editableRung(logic, rung_id);
  1252. for (int index = ProjectLimits::kMaximumConditionColumns - 1;
  1253. index > column;
  1254. --index)
  1255. {
  1256. rung->cells[static_cast<std::size_t>(index)] =
  1257. std::move(rung->cells[static_cast<std::size_t>(index - 1)]);
  1258. }
  1259. // 插入列会让右侧网格右移,边界 10 是固定输出侧边界,不能继续右移
  1260. for (VerticalConnection &connection : logic->verticalConnections)
  1261. {
  1262. if ((connection.upperRungId == rung_id
  1263. || connection.lowerRungId == rung_id)
  1264. && connection.columnBoundary >= column
  1265. && connection.columnBoundary < ProjectLimits::kMaximumConditionColumns)
  1266. {
  1267. ++connection.columnBoundary;
  1268. }
  1269. }
  1270. LadderCell inserted;
  1271. inserted.id = makeUniqueId(*logic, "cell");
  1272. inserted.kind = LadderCellKind::Node;
  1273. const std::string node_id = makeUniqueId(*logic, nodePrefix(config));
  1274. inserted.node = LogicNode{node_id, config, configured};
  1275. rung->cells[static_cast<std::size_t>(column)] = std::move(inserted);
  1276. std::string error;
  1277. if (!logic->validateStructure(project_service_.projectLimits(), &error))
  1278. {
  1279. rollbackEdit(std::move(before), modified_before);
  1280. return failure(LogicEditorError::InvalidOperation, error);
  1281. }
  1282. recordHistory(std::move(before));
  1283. return {true, LogicEditorError::None, {}, node_id};
  1284. }
  1285. LogicEditorResult LogicEditorService::appendCondition(
  1286. const std::string &logic_id,
  1287. const std::string &rung_id,
  1288. const LogicNodeConfig &config,
  1289. bool configured)
  1290. {
  1291. const ControlLogic *logic = findLogic(logic_id);
  1292. if (logic == nullptr)
  1293. {
  1294. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  1295. }
  1296. if (!isConditionConfig(config))
  1297. {
  1298. return failure(LogicEditorError::InvalidNode, "条件区只能放置条件指令");
  1299. }
  1300. std::string target_rung_id = rung_id;
  1301. if (target_rung_id.empty())
  1302. {
  1303. if (logic->rungs.size()
  1304. >= project_service_.projectLimits().maximumRungsPerLogic
  1305. || totalRungCount(project_service_.project())
  1306. >= ProjectLimits::kMaximumRungsPerProject)
  1307. {
  1308. return failure(
  1309. LogicEditorError::InvalidOperation,
  1310. "梯形图行数已经达到当前上限");
  1311. }
  1312. // 新建首行和放置首个节点必须共用一条历史记录
  1313. HistoryState before = captureState();
  1314. const bool modified_before = project_service_.isModified();
  1315. Project &project = project_service_.editProject();
  1316. ControlLogic *editable = editableLogic(&project, logic_id);
  1317. target_rung_id = insertEmptyRungAt(editable, editable->rungs.size());
  1318. LadderRung *created = editableRung(editable, target_rung_id);
  1319. const auto empty = std::find_if(
  1320. created->cells.begin(), created->cells.end(),
  1321. [](const LadderCell &cell)
  1322. {
  1323. return cell.kind == LadderCellKind::Gap;
  1324. });
  1325. if (empty == created->cells.end())
  1326. {
  1327. rollbackEdit(std::move(before), modified_before);
  1328. return failure(
  1329. LogicEditorError::InvalidOperation,
  1330. "条件区 10 列已经占满");
  1331. }
  1332. const std::string node_id = makeUniqueId(*editable, nodePrefix(config));
  1333. LadderCell &cell = created->cells[
  1334. static_cast<std::size_t>(std::distance(created->cells.begin(), empty))];
  1335. cell.kind = LadderCellKind::Node;
  1336. cell.node = LogicNode{node_id, config, configured};
  1337. std::string error;
  1338. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  1339. {
  1340. rollbackEdit(std::move(before), modified_before);
  1341. return failure(LogicEditorError::InvalidOperation, error);
  1342. }
  1343. recordHistory(std::move(before));
  1344. return {true, LogicEditorError::None, {}, node_id};
  1345. }
  1346. const LadderRung *rung = findRung(logic_id, target_rung_id);
  1347. if (rung == nullptr)
  1348. {
  1349. return failure(LogicEditorError::RungNotFound, "未找到梯形图行");
  1350. }
  1351. const auto empty = std::find_if(
  1352. rung->cells.cbegin(), rung->cells.cend(),
  1353. [](const LadderCell &cell) { return cell.kind == LadderCellKind::Gap; });
  1354. if (empty == rung->cells.cend())
  1355. {
  1356. return failure(LogicEditorError::InvalidOperation, "条件区 10 列已经占满");
  1357. }
  1358. return setConditionAtColumn(
  1359. logic_id,
  1360. target_rung_id,
  1361. static_cast<int>(std::distance(rung->cells.cbegin(), empty)),
  1362. config,
  1363. configured);
  1364. }
  1365. LogicEditorResult LogicEditorService::appendWire(
  1366. const std::string &logic_id,
  1367. const std::string &rung_id,
  1368. int column_span)
  1369. {
  1370. const ControlLogic *logic = findLogic(logic_id);
  1371. if (logic == nullptr)
  1372. {
  1373. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  1374. }
  1375. if (column_span <= 0
  1376. || column_span > ProjectLimits::kMaximumConditionColumns)
  1377. {
  1378. return failure(LogicEditorError::InvalidOperation, "横线参数无效");
  1379. }
  1380. if (rung_id.empty())
  1381. {
  1382. if (logic->rungs.size()
  1383. >= project_service_.projectLimits().maximumRungsPerLogic
  1384. || totalRungCount(project_service_.project())
  1385. >= ProjectLimits::kMaximumRungsPerProject)
  1386. {
  1387. return failure(
  1388. LogicEditorError::InvalidOperation,
  1389. "梯形图行数已经达到当前上限");
  1390. }
  1391. HistoryState before = captureState();
  1392. const bool modified_before = project_service_.isModified();
  1393. Project &project = project_service_.editProject();
  1394. ControlLogic *editable = editableLogic(&project, logic_id);
  1395. const std::string new_rung_id = insertEmptyRungAt(
  1396. editable, editable->rungs.size());
  1397. LadderRung *rung = editableRung(editable, new_rung_id);
  1398. const int first = ProjectLimits::kMaximumConditionColumns - column_span;
  1399. for (int column = first;
  1400. column < ProjectLimits::kMaximumConditionColumns;
  1401. ++column)
  1402. {
  1403. rung->cells[static_cast<std::size_t>(column)].kind =
  1404. LadderCellKind::Wire;
  1405. }
  1406. std::string error;
  1407. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  1408. {
  1409. rollbackEdit(std::move(before), modified_before);
  1410. return failure(LogicEditorError::InvalidOperation, error);
  1411. }
  1412. recordHistory(std::move(before));
  1413. return {true, LogicEditorError::None, {}, new_rung_id};
  1414. }
  1415. const LadderRung *rung = findRung(logic_id, rung_id);
  1416. if (rung == nullptr)
  1417. {
  1418. return failure(LogicEditorError::RungNotFound, "未找到梯形图行");
  1419. }
  1420. const int first = ProjectLimits::kMaximumConditionColumns - column_span;
  1421. return setHorizontalWireRange(logic_id, rung_id, first,
  1422. ProjectLimits::kMaximumConditionColumns - 1,
  1423. true);
  1424. }
  1425. LogicEditResult LogicEditorService::applyWireAndAdvance(
  1426. const std::string &logic_id,
  1427. const LogicEditCursor &cursor)
  1428. {
  1429. return applyCellAndAdvance(logic_id, cursor, nullptr, false);
  1430. }
  1431. LogicEditorResult LogicEditorService::setHorizontalWireRange(
  1432. const std::string &logic_id,
  1433. const std::string &rung_id,
  1434. int first_column,
  1435. int last_column,
  1436. bool connected)
  1437. {
  1438. if (first_column > last_column)
  1439. {
  1440. std::swap(first_column, last_column);
  1441. }
  1442. if (findRung(logic_id, rung_id) == nullptr || first_column < 0
  1443. || last_column >= ProjectLimits::kMaximumConditionColumns)
  1444. {
  1445. return failure(LogicEditorError::CellNotFound, "横线范围超出条件网格");
  1446. }
  1447. std::vector<std::pair<std::string, int>> cells;
  1448. for (int column = first_column; column <= last_column; ++column)
  1449. {
  1450. cells.emplace_back(rung_id, column);
  1451. }
  1452. return setWireCells(logic_id, cells, connected);
  1453. }
  1454. LogicEditorResult LogicEditorService::setWireCells(
  1455. const std::string &logic_id,
  1456. const std::vector<std::pair<std::string, int>> &cells,
  1457. bool connected)
  1458. {
  1459. const ControlLogic *logic = findLogic(logic_id);
  1460. if (logic == nullptr)
  1461. {
  1462. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  1463. }
  1464. if (cells.empty())
  1465. {
  1466. return failure(LogicEditorError::InvalidOperation, "没有需要修改的横线网格");
  1467. }
  1468. std::unordered_set<std::string> positions;
  1469. for (const auto &position : cells)
  1470. {
  1471. const std::string key = position.first + "\n" + std::to_string(position.second);
  1472. if (!positions.insert(key).second)
  1473. {
  1474. return failure(LogicEditorError::InvalidOperation, "横线范围包含重复网格");
  1475. }
  1476. if (findCell(logic_id, position.first, position.second) == nullptr)
  1477. {
  1478. return failure(LogicEditorError::CellNotFound, "横线范围包含无效网格");
  1479. }
  1480. }
  1481. HistoryState before = captureState();
  1482. const bool modified_before = project_service_.isModified();
  1483. Project &project = project_service_.editProject();
  1484. ControlLogic *editable = editableLogic(&project, logic_id);
  1485. for (const auto &position : cells)
  1486. {
  1487. LadderCell &cell = editableRung(editable, position.first)
  1488. ->cells[static_cast<std::size_t>(position.second)];
  1489. if (cell.kind == LadderCellKind::Node)
  1490. {
  1491. continue;
  1492. }
  1493. cell.kind = connected ? LadderCellKind::Wire : LadderCellKind::Gap;
  1494. cell.node.reset();
  1495. }
  1496. std::string error;
  1497. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  1498. {
  1499. rollbackEdit(std::move(before), modified_before);
  1500. return failure(LogicEditorError::InvalidOperation, error);
  1501. }
  1502. recordHistory(std::move(before));
  1503. return {true, LogicEditorError::None, {}, cells.front().first};
  1504. }
  1505. LogicEditorResult LogicEditorService::clearCells(
  1506. const std::string &logic_id,
  1507. const std::vector<std::pair<std::string, int>> &cells)
  1508. {
  1509. if (findLogic(logic_id) == nullptr)
  1510. {
  1511. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  1512. }
  1513. if (cells.empty())
  1514. {
  1515. return failure(LogicEditorError::InvalidOperation, "没有需要清空的网格");
  1516. }
  1517. for (const auto &position : cells)
  1518. {
  1519. if (findCell(logic_id, position.first, position.second) == nullptr)
  1520. {
  1521. return failure(LogicEditorError::CellNotFound, "清空范围包含无效网格");
  1522. }
  1523. }
  1524. HistoryState before = captureState();
  1525. Project &project = project_service_.editProject();
  1526. ControlLogic *logic = editableLogic(&project, logic_id);
  1527. for (const auto &position : cells)
  1528. {
  1529. LadderCell &cell = editableRung(logic, position.first)
  1530. ->cells[static_cast<std::size_t>(position.second)];
  1531. cell.kind = LadderCellKind::Gap;
  1532. cell.node.reset();
  1533. }
  1534. recordHistory(std::move(before));
  1535. return {true, LogicEditorError::None, {}, cells.front().first};
  1536. }
  1537. LogicEditorResult LogicEditorService::setVerticalConnection(
  1538. const std::string &logic_id,
  1539. const std::string &upper_rung_id,
  1540. const std::string &lower_rung_id,
  1541. int column_boundary,
  1542. bool connected)
  1543. {
  1544. const ControlLogic *logic = findLogic(logic_id);
  1545. if (logic == nullptr)
  1546. {
  1547. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  1548. }
  1549. const std::size_t upper = rungIndex(*logic, upper_rung_id);
  1550. const std::size_t lower = rungIndex(*logic, lower_rung_id);
  1551. if (upper == logic->rungs.size() || lower != upper + 1U)
  1552. {
  1553. return failure(
  1554. LogicEditorError::InvalidOperation,
  1555. "竖线只能连接相邻的上下两行");
  1556. }
  1557. return setVerticalConnectionRange(
  1558. logic_id,
  1559. upper_rung_id,
  1560. lower_rung_id,
  1561. column_boundary,
  1562. connected);
  1563. }
  1564. LogicVerticalEditResult LogicEditorService::applyVerticalConnectionAndAdvance(
  1565. const std::string &logic_id,
  1566. const std::string &upper_rung_id,
  1567. int column_boundary)
  1568. {
  1569. const ControlLogic *logic = findLogic(logic_id);
  1570. if (logic == nullptr)
  1571. {
  1572. return {
  1573. failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"),
  1574. {},
  1575. -1,
  1576. false};
  1577. }
  1578. if (column_boundary < 0
  1579. || column_boundary > ProjectLimits::kMaximumConditionColumns)
  1580. {
  1581. return {
  1582. failure(
  1583. LogicEditorError::InvalidOperation,
  1584. "请先选择一个列边界或网格,再插入竖线"),
  1585. {},
  1586. -1,
  1587. false};
  1588. }
  1589. const std::size_t upper = rungIndex(*logic, upper_rung_id);
  1590. if (upper == logic->rungs.size())
  1591. {
  1592. return {
  1593. failure(LogicEditorError::RungNotFound, "未找到梯形图行"),
  1594. {},
  1595. -1,
  1596. false};
  1597. }
  1598. if (upper + 1U >= logic->rungs.size())
  1599. {
  1600. return {
  1601. failure(
  1602. LogicEditorError::InvalidOperation,
  1603. "当前已经是末行,无法继续建立竖线"),
  1604. {},
  1605. -1,
  1606. false};
  1607. }
  1608. const std::string lower_rung_id = logic->rungs[upper + 1U].id;
  1609. const bool already_connected = std::any_of(
  1610. logic->verticalConnections.cbegin(),
  1611. logic->verticalConnections.cend(),
  1612. [&upper_rung_id, &lower_rung_id, column_boundary](
  1613. const VerticalConnection &connection)
  1614. {
  1615. return connectionMatches(
  1616. connection,
  1617. upper_rung_id,
  1618. lower_rung_id,
  1619. column_boundary);
  1620. });
  1621. LogicEditorResult edit = setVerticalConnection(
  1622. logic_id,
  1623. upper_rung_id,
  1624. lower_rung_id,
  1625. column_boundary,
  1626. true);
  1627. if (!edit.succeeded)
  1628. {
  1629. return {std::move(edit), {}, -1, false};
  1630. }
  1631. edit.message = already_connected
  1632. ? "竖线已经存在,已移至下一行"
  1633. : "竖线连接已建立,已移至下一行";
  1634. return {
  1635. std::move(edit),
  1636. lower_rung_id,
  1637. column_boundary,
  1638. !already_connected};
  1639. }
  1640. LogicEditorResult LogicEditorService::setVerticalConnectionRange(
  1641. const std::string &logic_id,
  1642. const std::string &first_rung_id,
  1643. const std::string &last_rung_id,
  1644. int column_boundary,
  1645. bool connected)
  1646. {
  1647. const ControlLogic *logic = findLogic(logic_id);
  1648. if (logic == nullptr)
  1649. {
  1650. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  1651. }
  1652. std::size_t first = rungIndex(*logic, first_rung_id);
  1653. std::size_t last = rungIndex(*logic, last_rung_id);
  1654. if (first == logic->rungs.size() || last == logic->rungs.size()
  1655. || first == last || column_boundary < 0
  1656. || column_boundary > ProjectLimits::kMaximumConditionColumns)
  1657. {
  1658. return failure(
  1659. LogicEditorError::InvalidOperation,
  1660. "竖线范围必须跨越至少两行且位于 0~10 列边界");
  1661. }
  1662. if (first > last)
  1663. {
  1664. std::swap(first, last);
  1665. }
  1666. bool changed = false;
  1667. for (std::size_t row = first; row < last; ++row)
  1668. {
  1669. const std::string &upper_id = logic->rungs[row].id;
  1670. const std::string &lower_id = logic->rungs[row + 1U].id;
  1671. const bool exists = std::any_of(
  1672. logic->verticalConnections.cbegin(),
  1673. logic->verticalConnections.cend(),
  1674. [&upper_id, &lower_id, column_boundary](
  1675. const VerticalConnection &connection)
  1676. {
  1677. return connectionMatches(
  1678. connection, upper_id, lower_id, column_boundary);
  1679. });
  1680. if (exists != connected)
  1681. {
  1682. changed = true;
  1683. break;
  1684. }
  1685. }
  1686. if (!changed)
  1687. {
  1688. return {
  1689. true,
  1690. LogicEditorError::None,
  1691. connected ? "竖线连接已经存在" : "目标位置没有竖线",
  1692. first_rung_id};
  1693. }
  1694. HistoryState before = captureState();
  1695. const bool modified_before = project_service_.isModified();
  1696. Project &project = project_service_.editProject();
  1697. ControlLogic *editable = editableLogic(&project, logic_id);
  1698. for (std::size_t row = first; row < last; ++row)
  1699. {
  1700. const std::string upper_id = editable->rungs[row].id;
  1701. const std::string lower_id = editable->rungs[row + 1U].id;
  1702. const auto found = std::find_if(
  1703. editable->verticalConnections.begin(),
  1704. editable->verticalConnections.end(),
  1705. [&upper_id, &lower_id, column_boundary](
  1706. const VerticalConnection &connection)
  1707. {
  1708. return connectionMatches(
  1709. connection, upper_id, lower_id, column_boundary);
  1710. });
  1711. if (connected && found == editable->verticalConnections.end())
  1712. {
  1713. editable->verticalConnections.push_back({
  1714. makeUniqueId(*editable, "vertical"),
  1715. upper_id,
  1716. lower_id,
  1717. column_boundary});
  1718. }
  1719. else if (!connected && found != editable->verticalConnections.end())
  1720. {
  1721. editable->verticalConnections.erase(found);
  1722. }
  1723. }
  1724. std::string error;
  1725. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  1726. {
  1727. rollbackEdit(std::move(before), modified_before);
  1728. return failure(LogicEditorError::InvalidOperation, error);
  1729. }
  1730. recordHistory(std::move(before));
  1731. return {true, LogicEditorError::None, {}, first_rung_id};
  1732. }
  1733. LogicEditorResult LogicEditorService::removeVerticalConnections(
  1734. const std::string &logic_id,
  1735. const std::vector<std::string> &connection_ids)
  1736. {
  1737. const ControlLogic *logic = findLogic(logic_id);
  1738. if (logic == nullptr)
  1739. {
  1740. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  1741. }
  1742. if (connection_ids.empty())
  1743. {
  1744. return failure(LogicEditorError::InvalidOperation, "请先选择要删除的竖线");
  1745. }
  1746. std::unordered_set<std::string> ids;
  1747. for (const std::string &id : connection_ids)
  1748. {
  1749. if (!ids.insert(id).second
  1750. || findVerticalConnection(*logic, id) == nullptr)
  1751. {
  1752. return failure(
  1753. LogicEditorError::ConnectionNotFound,
  1754. "竖线删除列表包含重复或不存在的对象");
  1755. }
  1756. }
  1757. HistoryState before = captureState();
  1758. Project &project = project_service_.editProject();
  1759. ControlLogic *editable = editableLogic(&project, logic_id);
  1760. editable->verticalConnections.erase(
  1761. std::remove_if(
  1762. editable->verticalConnections.begin(),
  1763. editable->verticalConnections.end(),
  1764. [&ids](const VerticalConnection &connection)
  1765. {
  1766. return ids.count(connection.id) != 0U;
  1767. }),
  1768. editable->verticalConnections.end());
  1769. recordHistory(std::move(before));
  1770. return {true, LogicEditorError::None, {}, connection_ids.front()};
  1771. }
  1772. LogicEditorResult LogicEditorService::deleteSelection(
  1773. const std::string &logic_id,
  1774. const LogicSelectionDeleteRequest &selection)
  1775. {
  1776. const ControlLogic *logic = findLogic(logic_id);
  1777. if (logic == nullptr)
  1778. {
  1779. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  1780. }
  1781. if (selection.cells.empty() && selection.outputRungIds.empty()
  1782. && selection.verticalConnectionIds.empty())
  1783. {
  1784. return failure(LogicEditorError::InvalidOperation, "没有可删除的选中对象");
  1785. }
  1786. std::unordered_set<std::string> cell_positions;
  1787. for (const auto &position : selection.cells)
  1788. {
  1789. const std::string key = position.first + "\n"
  1790. + std::to_string(position.second);
  1791. const LadderCell *cell = findCell(
  1792. logic_id, position.first, position.second);
  1793. if (!cell_positions.insert(key).second || cell == nullptr)
  1794. {
  1795. return failure(
  1796. LogicEditorError::CellNotFound,
  1797. "删除列表包含重复或不存在的网格");
  1798. }
  1799. if (cell->kind == LadderCellKind::Gap)
  1800. {
  1801. return failure(
  1802. LogicEditorError::InvalidOperation,
  1803. "删除列表包含空白网格");
  1804. }
  1805. }
  1806. std::unordered_set<std::string> output_rung_ids;
  1807. for (const std::string &rung_id : selection.outputRungIds)
  1808. {
  1809. const LadderRung *rung = findRung(logic_id, rung_id);
  1810. if (!output_rung_ids.insert(rung_id).second || rung == nullptr
  1811. || !rung->output.has_value())
  1812. {
  1813. return failure(
  1814. LogicEditorError::NodeNotFound,
  1815. "删除列表包含重复或不存在的输出指令");
  1816. }
  1817. }
  1818. std::unordered_set<std::string> connection_ids;
  1819. for (const std::string &connection_id
  1820. : selection.verticalConnectionIds)
  1821. {
  1822. if (!connection_ids.insert(connection_id).second
  1823. || findVerticalConnection(*logic, connection_id) == nullptr)
  1824. {
  1825. return failure(
  1826. LogicEditorError::ConnectionNotFound,
  1827. "删除列表包含重复或不存在的竖线");
  1828. }
  1829. }
  1830. HistoryState before = captureState();
  1831. const bool modified_before = project_service_.isModified();
  1832. Project &project = project_service_.editProject();
  1833. ControlLogic *editable = editableLogic(&project, logic_id);
  1834. for (const auto &position : selection.cells)
  1835. {
  1836. LadderCell &cell = editableRung(editable, position.first)
  1837. ->cells[static_cast<std::size_t>(position.second)];
  1838. cell.kind = LadderCellKind::Gap;
  1839. cell.node.reset();
  1840. }
  1841. for (const std::string &rung_id : selection.outputRungIds)
  1842. {
  1843. editableRung(editable, rung_id)->output.reset();
  1844. }
  1845. editable->verticalConnections.erase(
  1846. std::remove_if(
  1847. editable->verticalConnections.begin(),
  1848. editable->verticalConnections.end(),
  1849. [&connection_ids](const VerticalConnection &connection)
  1850. {
  1851. return connection_ids.count(connection.id) != 0U;
  1852. }),
  1853. editable->verticalConnections.end());
  1854. std::string error;
  1855. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  1856. {
  1857. rollbackEdit(std::move(before), modified_before);
  1858. return failure(LogicEditorError::InvalidOperation, error);
  1859. }
  1860. recordHistory(std::move(before));
  1861. return {true, LogicEditorError::None, {}, logic_id};
  1862. }
  1863. LogicEditorResult LogicEditorService::addParallelBranch(
  1864. const std::string &logic_id,
  1865. const std::string &rung_id,
  1866. const std::vector<std::string> &selected_node_ids,
  1867. const LogicNodeConfig &config,
  1868. bool configured)
  1869. {
  1870. const ControlLogic *logic = findLogic(logic_id);
  1871. const LadderRung *rung = findRung(logic_id, rung_id);
  1872. if (logic == nullptr || rung == nullptr)
  1873. {
  1874. return failure(LogicEditorError::RungNotFound, "未找到梯形图行");
  1875. }
  1876. if (!isConditionConfig(config)
  1877. || !areConditionNodesContiguous(logic_id, rung_id, selected_node_ids))
  1878. {
  1879. return failure(
  1880. LogicEditorError::InvalidOperation,
  1881. "请先选择同一行中连续的条件节点");
  1882. }
  1883. if (logic->rungs.size()
  1884. >= project_service_.projectLimits().maximumRungsPerLogic
  1885. || totalRungCount(project_service_.project())
  1886. >= ProjectLimits::kMaximumRungsPerProject)
  1887. {
  1888. return failure(LogicEditorError::InvalidOperation, "梯形图行数已经达到上限");
  1889. }
  1890. int first_column = ProjectLimits::kMaximumConditionColumns;
  1891. int last_column = -1;
  1892. for (int column = 0; column < ProjectLimits::kMaximumConditionColumns; ++column)
  1893. {
  1894. const LadderCell &cell = rung->cells[static_cast<std::size_t>(column)];
  1895. if (cell.node.has_value()
  1896. && std::find(
  1897. selected_node_ids.cbegin(), selected_node_ids.cend(),
  1898. cell.node->id) != selected_node_ids.cend())
  1899. {
  1900. first_column = std::min(first_column, column);
  1901. last_column = std::max(last_column, column);
  1902. }
  1903. }
  1904. HistoryState before = captureState();
  1905. const bool modified_before = project_service_.isModified();
  1906. Project &project = project_service_.editProject();
  1907. ControlLogic *editable = editableLogic(&project, logic_id);
  1908. const std::size_t source_index = rungIndex(*editable, rung_id);
  1909. const std::string new_rung_id = insertEmptyRungAt(editable, source_index + 1U);
  1910. LadderRung *branch = editableRung(editable, new_rung_id);
  1911. const std::string node_id = makeUniqueId(*editable, nodePrefix(config));
  1912. branch->cells[static_cast<std::size_t>(first_column)].kind =
  1913. LadderCellKind::Node;
  1914. branch->cells[static_cast<std::size_t>(first_column)].node =
  1915. LogicNode{node_id, config, configured};
  1916. for (int column = first_column + 1; column <= last_column; ++column)
  1917. {
  1918. branch->cells[static_cast<std::size_t>(column)].kind =
  1919. LadderCellKind::Wire;
  1920. }
  1921. const auto ensure_branch_edge = [editable, &rung_id, &new_rung_id](
  1922. int boundary)
  1923. {
  1924. const bool exists = std::any_of(
  1925. editable->verticalConnections.cbegin(),
  1926. editable->verticalConnections.cend(),
  1927. [&rung_id, &new_rung_id, boundary](
  1928. const VerticalConnection &connection)
  1929. {
  1930. return connectionMatches(
  1931. connection, rung_id, new_rung_id, boundary);
  1932. });
  1933. if (!exists)
  1934. {
  1935. editable->verticalConnections.push_back({
  1936. makeUniqueId(*editable, "vertical"),
  1937. rung_id,
  1938. new_rung_id,
  1939. boundary});
  1940. }
  1941. };
  1942. ensure_branch_edge(first_column);
  1943. ensure_branch_edge(last_column + 1);
  1944. std::string error;
  1945. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  1946. {
  1947. rollbackEdit(std::move(before), modified_before);
  1948. return failure(LogicEditorError::InvalidOperation, error);
  1949. }
  1950. recordHistory(std::move(before));
  1951. return {true, LogicEditorError::None, {}, node_id};
  1952. }
  1953. LogicEditorResult LogicEditorService::addParallelToWholeCondition(
  1954. const std::string &logic_id,
  1955. const std::string &rung_id,
  1956. const LogicNodeConfig &config,
  1957. bool configured)
  1958. {
  1959. const LadderRung *rung = findRung(logic_id, rung_id);
  1960. if (rung == nullptr)
  1961. {
  1962. return failure(LogicEditorError::RungNotFound, "未找到梯形图行");
  1963. }
  1964. std::vector<std::string> node_ids;
  1965. for (const LadderCell &cell : rung->cells)
  1966. {
  1967. if (cell.node.has_value())
  1968. {
  1969. node_ids.push_back(cell.node->id);
  1970. }
  1971. }
  1972. if (node_ids.empty())
  1973. {
  1974. return failure(
  1975. LogicEditorError::InvalidOperation,
  1976. "当前行还没有可并联的条件节点");
  1977. }
  1978. return addParallelBranch(
  1979. logic_id, rung_id, node_ids, config, configured);
  1980. }
  1981. LogicEditorResult LogicEditorService::setOutput(
  1982. const std::string &logic_id,
  1983. const std::string &rung_id,
  1984. const LogicNodeConfig &config,
  1985. bool configured)
  1986. {
  1987. const ControlLogic *existing_logic = findLogic(logic_id);
  1988. if (existing_logic == nullptr)
  1989. {
  1990. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  1991. }
  1992. if (!isOutputConfig(config))
  1993. {
  1994. return failure(LogicEditorError::InvalidNode, "输出槽只能放置输出指令");
  1995. }
  1996. if (rung_id.empty())
  1997. {
  1998. if (existing_logic->rungs.size()
  1999. >= project_service_.projectLimits().maximumRungsPerLogic
  2000. || totalRungCount(project_service_.project())
  2001. >= ProjectLimits::kMaximumRungsPerProject)
  2002. {
  2003. return failure(
  2004. LogicEditorError::InvalidOperation,
  2005. "梯形图行数已经达到当前上限");
  2006. }
  2007. HistoryState before = captureState();
  2008. const bool modified_before = project_service_.isModified();
  2009. Project &project = project_service_.editProject();
  2010. ControlLogic *logic = editableLogic(&project, logic_id);
  2011. const std::string new_rung_id = insertEmptyRungAt(
  2012. logic, logic->rungs.size());
  2013. LadderRung *rung = editableRung(logic, new_rung_id);
  2014. const std::string node_id = makeUniqueId(*logic, nodePrefix(config));
  2015. rung->output = LogicNode{node_id, config, configured};
  2016. std::string error;
  2017. if (!logic->validateStructure(project_service_.projectLimits(), &error))
  2018. {
  2019. rollbackEdit(std::move(before), modified_before);
  2020. return failure(LogicEditorError::InvalidNode, error);
  2021. }
  2022. recordHistory(std::move(before));
  2023. return {true, LogicEditorError::None, {}, node_id};
  2024. }
  2025. if (findRung(logic_id, rung_id) == nullptr)
  2026. {
  2027. return failure(LogicEditorError::RungNotFound, "未找到梯形图行");
  2028. }
  2029. HistoryState before = captureState();
  2030. const bool modified_before = project_service_.isModified();
  2031. Project &project = project_service_.editProject();
  2032. ControlLogic *logic = editableLogic(&project, logic_id);
  2033. LadderRung *rung = editableRung(logic, rung_id);
  2034. const std::string node_id = makeUniqueId(*logic, nodePrefix(config));
  2035. rung->output = LogicNode{node_id, config, configured};
  2036. std::string error;
  2037. if (!logic->validateStructure(project_service_.projectLimits(), &error))
  2038. {
  2039. rollbackEdit(std::move(before), modified_before);
  2040. return failure(LogicEditorError::InvalidNode, error);
  2041. }
  2042. recordHistory(std::move(before));
  2043. return {true, LogicEditorError::None, {}, node_id};
  2044. }
  2045. LogicEditResult LogicEditorService::applyOutputAndAdvance(
  2046. const std::string &logic_id,
  2047. const LogicEditCursor &cursor,
  2048. const LogicNodeConfig &config,
  2049. bool configured)
  2050. {
  2051. const ControlLogic *existing_logic = findLogic(logic_id);
  2052. if (existing_logic == nullptr)
  2053. {
  2054. return {failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"), {}};
  2055. }
  2056. if (!isOutputConfig(config))
  2057. {
  2058. return {failure(LogicEditorError::InvalidNode,
  2059. "输出槽只能放置输出指令"), {}};
  2060. }
  2061. LogicNode candidate{"candidate", config, configured};
  2062. std::string error;
  2063. if (!candidate.validate(&error))
  2064. {
  2065. return {failure(LogicEditorError::InvalidNode, error), {}};
  2066. }
  2067. std::string target_rung_id = cursor.rungId;
  2068. std::size_t target_index = existing_logic->rungs.size();
  2069. bool create_output_rung = target_rung_id.empty();
  2070. if (create_output_rung)
  2071. {
  2072. if (!existing_logic->rungs.empty())
  2073. {
  2074. return {failure(LogicEditorError::RungNotFound,
  2075. "请先选择输出所在行"), {}};
  2076. }
  2077. }
  2078. else
  2079. {
  2080. target_index = rungIndex(*existing_logic, target_rung_id);
  2081. if (target_index == existing_logic->rungs.size())
  2082. {
  2083. return {failure(LogicEditorError::RungNotFound,
  2084. "未找到梯形图行"), {}};
  2085. }
  2086. }
  2087. std::size_t group_end = target_index;
  2088. if (!create_output_rung)
  2089. {
  2090. while (group_end + 1U < existing_logic->rungs.size())
  2091. {
  2092. const std::string &upper_id = existing_logic->rungs[group_end].id;
  2093. const std::string &lower_id = existing_logic->rungs[group_end + 1U].id;
  2094. const bool connected = std::any_of(
  2095. existing_logic->verticalConnections.cbegin(),
  2096. existing_logic->verticalConnections.cend(),
  2097. [&upper_id, &lower_id](const VerticalConnection &connection)
  2098. {
  2099. return connection.upperRungId == upper_id
  2100. && connection.lowerRungId == lower_id;
  2101. });
  2102. if (!connected)
  2103. {
  2104. break;
  2105. }
  2106. ++group_end;
  2107. }
  2108. }
  2109. const bool append_next_rung = create_output_rung
  2110. || group_end + 1U == existing_logic->rungs.size();
  2111. const std::size_t rows_to_add = append_next_rung
  2112. ? (create_output_rung ? 2U : 1U) : 0U;
  2113. if (existing_logic->rungs.size() + rows_to_add
  2114. > project_service_.projectLimits().maximumRungsPerLogic
  2115. || totalRungCount(project_service_.project()) + rows_to_add
  2116. > ProjectLimits::kMaximumRungsPerProject)
  2117. {
  2118. return {failure(
  2119. LogicEditorError::InvalidOperation,
  2120. "输出后无法创建下一空行,梯形图行数已经达到当前上限"),
  2121. {}};
  2122. }
  2123. HistoryState before = captureState();
  2124. const bool modified_before = project_service_.isModified();
  2125. Project &project = project_service_.editProject();
  2126. ControlLogic *logic = editableLogic(&project, logic_id);
  2127. if (create_output_rung)
  2128. {
  2129. target_rung_id = insertEmptyRungAt(logic, logic->rungs.size());
  2130. target_index = logic->rungs.size() - 1U;
  2131. group_end = target_index;
  2132. }
  2133. LadderRung *rung = editableRung(logic, target_rung_id);
  2134. int rightmost_content = -1;
  2135. for (int column = 0;
  2136. column < ProjectLimits::kMaximumConditionColumns;
  2137. ++column)
  2138. {
  2139. if (rung->cells[static_cast<std::size_t>(column)].kind
  2140. != LadderCellKind::Gap)
  2141. {
  2142. rightmost_content = column;
  2143. }
  2144. }
  2145. // 空网络直接输出时补满横线;已有内容时只补尾部,不跨越中间断点
  2146. for (int column = rightmost_content + 1;
  2147. column < ProjectLimits::kMaximumConditionColumns;
  2148. ++column)
  2149. {
  2150. LadderCell &cell = rung->cells[static_cast<std::size_t>(column)];
  2151. cell.kind = LadderCellKind::Wire;
  2152. cell.node.reset();
  2153. }
  2154. const std::string node_id = makeUniqueId(*logic, nodePrefix(config));
  2155. rung->output = LogicNode{node_id, config, configured};
  2156. std::string next_rung_id;
  2157. if (append_next_rung)
  2158. {
  2159. next_rung_id = insertEmptyRungAt(logic, logic->rungs.size());
  2160. }
  2161. else
  2162. {
  2163. next_rung_id = logic->rungs[group_end + 1U].id;
  2164. }
  2165. if (!logic->validateStructure(project_service_.projectLimits(), &error))
  2166. {
  2167. rollbackEdit(std::move(before), modified_before);
  2168. return {failure(LogicEditorError::InvalidNode, error), {}};
  2169. }
  2170. recordHistory(std::move(before));
  2171. return {
  2172. {true, LogicEditorError::None, {}, node_id},
  2173. {next_rung_id, 0, false}};
  2174. }
  2175. LogicEditorResult LogicEditorService::updateNodeConfig(
  2176. const std::string &logic_id,
  2177. const std::string &node_id,
  2178. const LogicNodeConfig &config)
  2179. {
  2180. const LogicNode *node = findNode(logic_id, node_id);
  2181. if (node == nullptr)
  2182. {
  2183. return failure(LogicEditorError::NodeNotFound, "未找到逻辑节点");
  2184. }
  2185. if (node->isCondition() != isConditionConfig(config)
  2186. || node->isOutput() != isOutputConfig(config))
  2187. {
  2188. return failure(
  2189. LogicEditorError::UnsupportedNodeChange,
  2190. "条件节点和输出节点不能互相改型");
  2191. }
  2192. LogicNode candidate{node_id, config, true};
  2193. std::string error;
  2194. if (!candidate.validate(&error))
  2195. {
  2196. return failure(LogicEditorError::InvalidNode, error);
  2197. }
  2198. HistoryState before = captureState();
  2199. Project &project = project_service_.editProject();
  2200. ControlLogic *logic = editableLogic(&project, logic_id);
  2201. for (LadderRung &rung : logic->rungs)
  2202. {
  2203. for (LadderCell &cell : rung.cells)
  2204. {
  2205. if (cell.node.has_value() && cell.node->id == node_id)
  2206. {
  2207. cell.node->config = config;
  2208. cell.node->configured = true;
  2209. recordHistory(std::move(before));
  2210. return {true, LogicEditorError::None, {}, node_id};
  2211. }
  2212. }
  2213. if (rung.output.has_value() && rung.output->id == node_id)
  2214. {
  2215. rung.output->config = config;
  2216. rung.output->configured = true;
  2217. recordHistory(std::move(before));
  2218. return {true, LogicEditorError::None, {}, node_id};
  2219. }
  2220. }
  2221. return failure(LogicEditorError::NodeNotFound, "未找到逻辑节点");
  2222. }
  2223. LogicClipboardCopyResult LogicEditorService::copySelection(
  2224. const std::string &logic_id,
  2225. const LogicSelectionCopyRequest &selection) const
  2226. {
  2227. const ControlLogic *logic = findLogic(logic_id);
  2228. if (logic == nullptr)
  2229. {
  2230. return {failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"), {}};
  2231. }
  2232. const bool has_grid_objects = !selection.cells.empty()
  2233. || !selection.outputRungIds.empty()
  2234. || !selection.verticalConnectionIds.empty();
  2235. if (!selection.wholeRungIds.empty())
  2236. {
  2237. if (has_grid_objects)
  2238. {
  2239. return {failure(
  2240. LogicEditorError::InvalidOperation,
  2241. "整行选择不能和网格对象混合复制"), {}};
  2242. }
  2243. std::unordered_set<std::string> unique_ids;
  2244. std::vector<std::size_t> indices;
  2245. indices.reserve(selection.wholeRungIds.size());
  2246. for (const std::string &rung_id : selection.wholeRungIds)
  2247. {
  2248. const std::size_t index = rungIndex(*logic, rung_id);
  2249. if (!unique_ids.insert(rung_id).second
  2250. || index == logic->rungs.size())
  2251. {
  2252. return {failure(
  2253. LogicEditorError::InvalidOperation,
  2254. "整行复制选择中存在重复或失效的行"), {}};
  2255. }
  2256. indices.push_back(index);
  2257. }
  2258. std::sort(indices.begin(), indices.end());
  2259. for (std::size_t index = 1U; index < indices.size(); ++index)
  2260. {
  2261. if (indices[index] != indices[index - 1U] + 1U)
  2262. {
  2263. return {failure(
  2264. LogicEditorError::InvalidOperation,
  2265. "整行复制只支持连续的视觉行"), {}};
  2266. }
  2267. }
  2268. LogicClipboardFragment fragment;
  2269. fragment.mode = LogicClipboardMode::WholeRows;
  2270. fragment.rowSpan = static_cast<int>(indices.size());
  2271. fragment.columnSpan = ProjectLimits::kMaximumLadderColumns;
  2272. fragment.rows.reserve(indices.size());
  2273. for (std::size_t index : indices)
  2274. {
  2275. const LadderRung &rung = logic->rungs[index];
  2276. fragment.rows.push_back({rung.comment, rung.cells, rung.output});
  2277. }
  2278. const std::size_t first = indices.front();
  2279. const std::size_t last = indices.back();
  2280. for (const VerticalConnection &connection : logic->verticalConnections)
  2281. {
  2282. const std::size_t upper = rungIndex(*logic, connection.upperRungId);
  2283. const std::size_t lower = rungIndex(*logic, connection.lowerRungId);
  2284. if (upper >= first && lower <= last && lower == upper + 1U)
  2285. {
  2286. fragment.verticalConnections.push_back({
  2287. static_cast<int>(upper - first),
  2288. connection.columnBoundary});
  2289. }
  2290. }
  2291. return {{
  2292. true,
  2293. LogicEditorError::None,
  2294. {},
  2295. logic->rungs[first].id}, std::move(fragment)};
  2296. }
  2297. if (!has_grid_objects)
  2298. {
  2299. return {failure(
  2300. LogicEditorError::InvalidOperation,
  2301. "请先选择横线、指令、输出或竖线"), {}};
  2302. }
  2303. LogicClipboardFragment fragment;
  2304. fragment.mode = LogicClipboardMode::GridObjects;
  2305. std::size_t minimum_row = logic->rungs.size();
  2306. std::size_t maximum_row = 0U;
  2307. int minimum_column = ProjectLimits::kMaximumLadderColumns;
  2308. int maximum_column = 0;
  2309. const auto include_position = [
  2310. &minimum_row,
  2311. &maximum_row,
  2312. &minimum_column,
  2313. &maximum_column](std::size_t row, int column)
  2314. {
  2315. minimum_row = std::min(minimum_row, row);
  2316. maximum_row = std::max(maximum_row, row);
  2317. minimum_column = std::min(minimum_column, column);
  2318. maximum_column = std::max(maximum_column, column);
  2319. };
  2320. std::unordered_set<std::string> unique_cells;
  2321. for (const auto &position : selection.cells)
  2322. {
  2323. const std::size_t row = rungIndex(*logic, position.first);
  2324. const LadderCell *cell = findCell(
  2325. logic_id, position.first, position.second);
  2326. const std::string key = position.first + ":"
  2327. + std::to_string(position.second);
  2328. if (row == logic->rungs.size() || cell == nullptr
  2329. || !unique_cells.insert(key).second
  2330. || cell->kind == LadderCellKind::Gap)
  2331. {
  2332. return {failure(
  2333. LogicEditorError::InvalidOperation,
  2334. "复制选择中存在重复、空白或失效的网格"), {}};
  2335. }
  2336. fragment.cells.push_back({
  2337. static_cast<int>(row),
  2338. position.second,
  2339. cell->kind,
  2340. cell->node});
  2341. include_position(row, position.second);
  2342. }
  2343. std::unordered_set<std::string> unique_outputs;
  2344. for (const std::string &rung_id : selection.outputRungIds)
  2345. {
  2346. const std::size_t row = rungIndex(*logic, rung_id);
  2347. const LadderRung *rung = findRung(logic_id, rung_id);
  2348. if (row == logic->rungs.size() || rung == nullptr
  2349. || !rung->output.has_value()
  2350. || !unique_outputs.insert(rung_id).second)
  2351. {
  2352. return {failure(
  2353. LogicEditorError::InvalidOperation,
  2354. "复制选择中存在重复、空白或失效的输出槽"), {}};
  2355. }
  2356. fragment.outputs.push_back({
  2357. static_cast<int>(row),
  2358. ProjectLimits::kMaximumConditionColumns,
  2359. *rung->output});
  2360. include_position(row, ProjectLimits::kMaximumConditionColumns);
  2361. }
  2362. std::unordered_set<std::string> unique_connections;
  2363. for (const std::string &connection_id : selection.verticalConnectionIds)
  2364. {
  2365. const VerticalConnection *connection = findConnection(
  2366. logic_id, connection_id);
  2367. if (connection == nullptr
  2368. || !unique_connections.insert(connection_id).second)
  2369. {
  2370. return {failure(
  2371. LogicEditorError::InvalidOperation,
  2372. "复制选择中存在重复或失效的竖线"), {}};
  2373. }
  2374. const std::size_t upper = rungIndex(*logic, connection->upperRungId);
  2375. const std::size_t lower = rungIndex(*logic, connection->lowerRungId);
  2376. if (upper == logic->rungs.size() || lower != upper + 1U)
  2377. {
  2378. return {failure(
  2379. LogicEditorError::InvalidOperation,
  2380. "复制选择中存在悬空竖线"), {}};
  2381. }
  2382. fragment.verticalConnections.push_back({
  2383. static_cast<int>(upper), connection->columnBoundary});
  2384. include_position(upper, connection->columnBoundary);
  2385. include_position(lower, connection->columnBoundary);
  2386. }
  2387. for (LogicClipboardCell &cell : fragment.cells)
  2388. {
  2389. cell.relativeRow -= static_cast<int>(minimum_row);
  2390. cell.relativeColumn -= minimum_column;
  2391. }
  2392. for (LogicClipboardOutput &output : fragment.outputs)
  2393. {
  2394. output.relativeRow -= static_cast<int>(minimum_row);
  2395. output.relativeColumn -= minimum_column;
  2396. }
  2397. for (LogicClipboardVerticalConnection &connection
  2398. : fragment.verticalConnections)
  2399. {
  2400. connection.upperRelativeRow -= static_cast<int>(minimum_row);
  2401. connection.relativeColumnBoundary -= minimum_column;
  2402. }
  2403. fragment.rowSpan = static_cast<int>(maximum_row - minimum_row + 1U);
  2404. fragment.columnSpan = maximum_column - minimum_column + 1;
  2405. std::sort(
  2406. fragment.cells.begin(), fragment.cells.end(),
  2407. [](const LogicClipboardCell &left, const LogicClipboardCell &right)
  2408. {
  2409. return left.relativeRow != right.relativeRow
  2410. ? left.relativeRow < right.relativeRow
  2411. : left.relativeColumn < right.relativeColumn;
  2412. });
  2413. std::sort(
  2414. fragment.outputs.begin(), fragment.outputs.end(),
  2415. [](const LogicClipboardOutput &left, const LogicClipboardOutput &right)
  2416. {
  2417. return left.relativeRow < right.relativeRow;
  2418. });
  2419. std::sort(
  2420. fragment.verticalConnections.begin(),
  2421. fragment.verticalConnections.end(),
  2422. [](const LogicClipboardVerticalConnection &left,
  2423. const LogicClipboardVerticalConnection &right)
  2424. {
  2425. return left.upperRelativeRow != right.upperRelativeRow
  2426. ? left.upperRelativeRow < right.upperRelativeRow
  2427. : left.relativeColumnBoundary
  2428. < right.relativeColumnBoundary;
  2429. });
  2430. return {{true, LogicEditorError::None, {}, {}}, std::move(fragment)};
  2431. }
  2432. LogicClipboardPasteResult LogicEditorService::pasteClipboard(
  2433. const std::string &logic_id,
  2434. const LogicClipboardFragment &fragment,
  2435. const LogicPasteTarget &target)
  2436. {
  2437. const ControlLogic *logic = findLogic(logic_id);
  2438. if (logic == nullptr)
  2439. {
  2440. return {failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"), {}, {}};
  2441. }
  2442. if (fragment.mode == LogicClipboardMode::WholeRows)
  2443. {
  2444. if (fragment.rows.empty() || !fragment.cells.empty()
  2445. || !fragment.outputs.empty())
  2446. {
  2447. return {failure(
  2448. LogicEditorError::InvalidOperation,
  2449. "整行剪贴板内容无效"), {}, {}};
  2450. }
  2451. if (logic->rungs.size() + fragment.rows.size()
  2452. > project_service_.projectLimits().maximumRungsPerLogic
  2453. || totalRungCount(project_service_.project()) + fragment.rows.size()
  2454. > ProjectLimits::kMaximumRungsPerProject)
  2455. {
  2456. return {failure(
  2457. LogicEditorError::InvalidOperation,
  2458. "粘贴整行后将超过梯形图行数上限"), {}, {}};
  2459. }
  2460. std::size_t position = 0U;
  2461. if (!logic->rungs.empty())
  2462. {
  2463. const std::size_t reference = rungIndex(*logic, target.rungId);
  2464. if (reference == logic->rungs.size())
  2465. {
  2466. return {failure(
  2467. LogicEditorError::RungNotFound,
  2468. "请先选择整行粘贴位置"), {}, {}};
  2469. }
  2470. position = reference + 1U;
  2471. }
  2472. for (const LogicClipboardRow &row : fragment.rows)
  2473. {
  2474. if (row.cells.size()
  2475. != static_cast<std::size_t>(
  2476. ProjectLimits::kMaximumConditionColumns)
  2477. || containsLineBreak(row.comment)
  2478. || row.comment.size() > ProjectLimits::kMaximumRungCommentBytes)
  2479. {
  2480. return {failure(
  2481. LogicEditorError::InvalidOperation,
  2482. "复制的整行结构或注释无效"), {}, {}};
  2483. }
  2484. for (const LadderCell &cell : row.cells)
  2485. {
  2486. if (!cell.validate()
  2487. || (cell.node.has_value() && !cell.node->isCondition()))
  2488. {
  2489. return {failure(
  2490. LogicEditorError::InvalidNode,
  2491. "复制的整行包含无效条件"), {}, {}};
  2492. }
  2493. }
  2494. if (row.output.has_value()
  2495. && (!row.output->validate() || !row.output->isOutput()))
  2496. {
  2497. return {failure(
  2498. LogicEditorError::InvalidNode,
  2499. "复制的整行包含无效输出"), {}, {}};
  2500. }
  2501. }
  2502. std::unordered_set<std::string> unique_connections;
  2503. for (const LogicClipboardVerticalConnection &connection
  2504. : fragment.verticalConnections)
  2505. {
  2506. const std::string key = std::to_string(connection.upperRelativeRow)
  2507. + ":" + std::to_string(connection.relativeColumnBoundary);
  2508. if (connection.upperRelativeRow < 0
  2509. || connection.upperRelativeRow + 1
  2510. >= static_cast<int>(fragment.rows.size())
  2511. || connection.relativeColumnBoundary < 0
  2512. || connection.relativeColumnBoundary
  2513. > ProjectLimits::kMaximumConditionColumns
  2514. || !unique_connections.insert(key).second)
  2515. {
  2516. return {failure(
  2517. LogicEditorError::InvalidOperation,
  2518. "复制的整行包含无效竖线"), {}, {}};
  2519. }
  2520. }
  2521. HistoryState before = captureState();
  2522. const bool modified_before = project_service_.isModified();
  2523. Project &project = project_service_.editProject();
  2524. ControlLogic *editable = editableLogic(&project, logic_id);
  2525. std::vector<std::string> inserted_ids;
  2526. inserted_ids.reserve(fragment.rows.size());
  2527. for (std::size_t offset = 0U; offset < fragment.rows.size(); ++offset)
  2528. {
  2529. const std::string inserted_id = insertEmptyRungAt(
  2530. editable, position + offset);
  2531. LadderRung *destination = editableRung(editable, inserted_id);
  2532. const LogicClipboardRow &source = fragment.rows[offset];
  2533. destination->comment = source.comment;
  2534. for (std::size_t column = 0U; column < source.cells.size(); ++column)
  2535. {
  2536. destination->cells[column].kind = source.cells[column].kind;
  2537. destination->cells[column].node.reset();
  2538. if (source.cells[column].node.has_value())
  2539. {
  2540. const LogicNode &source_node = *source.cells[column].node;
  2541. destination->cells[column].node = LogicNode{
  2542. makeUniqueId(*editable, nodePrefix(source_node.config)),
  2543. source_node.config,
  2544. source_node.configured};
  2545. }
  2546. }
  2547. if (source.output.has_value())
  2548. {
  2549. destination->output = LogicNode{
  2550. makeUniqueId(*editable, nodePrefix(source.output->config)),
  2551. source.output->config,
  2552. source.output->configured};
  2553. }
  2554. inserted_ids.push_back(inserted_id);
  2555. }
  2556. for (const LogicClipboardVerticalConnection &source
  2557. : fragment.verticalConnections)
  2558. {
  2559. const std::string &upper = inserted_ids[
  2560. static_cast<std::size_t>(source.upperRelativeRow)];
  2561. const std::string &lower = inserted_ids[
  2562. static_cast<std::size_t>(source.upperRelativeRow + 1)];
  2563. const auto existing = std::find_if(
  2564. editable->verticalConnections.cbegin(),
  2565. editable->verticalConnections.cend(),
  2566. [&upper, &lower, &source](const VerticalConnection &connection)
  2567. {
  2568. return connectionMatches(
  2569. connection,
  2570. upper,
  2571. lower,
  2572. source.relativeColumnBoundary);
  2573. });
  2574. if (existing == editable->verticalConnections.cend())
  2575. {
  2576. editable->verticalConnections.push_back({
  2577. makeUniqueId(*editable, "vertical"),
  2578. upper,
  2579. lower,
  2580. source.relativeColumnBoundary});
  2581. }
  2582. }
  2583. std::string error;
  2584. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  2585. {
  2586. rollbackEdit(std::move(before), modified_before);
  2587. return {failure(LogicEditorError::InvalidOperation, error), {}, {}};
  2588. }
  2589. refreshRungNames(editable);
  2590. recordHistory(std::move(before));
  2591. return {{
  2592. true,
  2593. LogicEditorError::None,
  2594. {},
  2595. inserted_ids.front()}, {}, std::move(inserted_ids)};
  2596. }
  2597. if (!fragment.rows.empty()
  2598. || (fragment.cells.empty() && fragment.outputs.empty()
  2599. && fragment.verticalConnections.empty())
  2600. || fragment.rowSpan <= 0 || fragment.columnSpan <= 0)
  2601. {
  2602. return {failure(
  2603. LogicEditorError::InvalidOperation,
  2604. "网格剪贴板内容无效"), {}, {}};
  2605. }
  2606. const std::size_t target_row = rungIndex(*logic, target.rungId);
  2607. if (target_row == logic->rungs.size())
  2608. {
  2609. return {failure(
  2610. LogicEditorError::RungNotFound,
  2611. "请先选择粘贴目标"), {}, {}};
  2612. }
  2613. const bool only_vertical = fragment.cells.empty()
  2614. && fragment.outputs.empty();
  2615. const bool only_output = fragment.cells.empty()
  2616. && fragment.verticalConnections.empty();
  2617. if ((only_vertical && (!target.boundary || target.output))
  2618. || (only_output && (!target.output || target.boundary)))
  2619. {
  2620. return {failure(
  2621. LogicEditorError::InvalidOperation,
  2622. "剪贴板对象类型与当前粘贴位置不匹配"), {}, {}};
  2623. }
  2624. if (target.column < 0
  2625. || target.column > ProjectLimits::kMaximumConditionColumns
  2626. || target_row + static_cast<std::size_t>(fragment.rowSpan)
  2627. > logic->rungs.size())
  2628. {
  2629. return {failure(
  2630. LogicEditorError::InvalidOperation,
  2631. "粘贴片段超出当前梯形图行列范围"), {}, {}};
  2632. }
  2633. std::unordered_set<std::string> destination_cells;
  2634. for (const LogicClipboardCell &source : fragment.cells)
  2635. {
  2636. const int column = target.column + source.relativeColumn;
  2637. const std::size_t row = target_row
  2638. + static_cast<std::size_t>(source.relativeRow);
  2639. const std::string key = std::to_string(row) + ":"
  2640. + std::to_string(column);
  2641. if (source.relativeRow < 0
  2642. || source.relativeRow >= fragment.rowSpan
  2643. || source.relativeColumn < 0
  2644. || column < 0
  2645. || column >= ProjectLimits::kMaximumConditionColumns
  2646. || !destination_cells.insert(key).second
  2647. || (source.kind != LadderCellKind::Wire
  2648. && source.kind != LadderCellKind::Node)
  2649. || (source.kind == LadderCellKind::Node
  2650. && (!source.node.has_value()
  2651. || !source.node->validate()
  2652. || !source.node->isCondition()))
  2653. || (source.kind == LadderCellKind::Wire
  2654. && source.node.has_value()))
  2655. {
  2656. return {failure(
  2657. LogicEditorError::InvalidOperation,
  2658. "复制片段包含无效或越界的条件网格"), {}, {}};
  2659. }
  2660. const LadderCell &destination = logic->rungs[row].cells[
  2661. static_cast<std::size_t>(column)];
  2662. if (destination.kind == LadderCellKind::Node)
  2663. {
  2664. return {failure(
  2665. LogicEditorError::InvalidOperation,
  2666. "粘贴目标已有触点或比较指令,未执行任何修改"), {}, {}};
  2667. }
  2668. }
  2669. const bool explicit_output_replace = only_output
  2670. && fragment.outputs.size() == 1U && fragment.rowSpan == 1;
  2671. std::unordered_set<std::size_t> destination_outputs;
  2672. for (const LogicClipboardOutput &source : fragment.outputs)
  2673. {
  2674. const int column = target.column + source.relativeColumn;
  2675. const std::size_t row = target_row
  2676. + static_cast<std::size_t>(source.relativeRow);
  2677. if (source.relativeRow < 0
  2678. || source.relativeRow >= fragment.rowSpan
  2679. || source.relativeColumn < 0
  2680. || column != ProjectLimits::kMaximumConditionColumns
  2681. || !destination_outputs.insert(row).second
  2682. || !source.node.validate() || !source.node.isOutput())
  2683. {
  2684. return {failure(
  2685. LogicEditorError::InvalidOperation,
  2686. "复制片段包含无效或错位的输出"), {}, {}};
  2687. }
  2688. if (logic->rungs[row].output.has_value() && !explicit_output_replace)
  2689. {
  2690. return {failure(
  2691. LogicEditorError::InvalidOperation,
  2692. "混合片段的目标输出槽已有指令,未执行任何修改"), {}, {}};
  2693. }
  2694. }
  2695. std::unordered_set<std::string> destination_connections;
  2696. for (const LogicClipboardVerticalConnection &source
  2697. : fragment.verticalConnections)
  2698. {
  2699. const int boundary = target.column + source.relativeColumnBoundary;
  2700. const std::size_t upper = target_row
  2701. + static_cast<std::size_t>(source.upperRelativeRow);
  2702. const std::string key = std::to_string(upper) + ":"
  2703. + std::to_string(boundary);
  2704. if (source.upperRelativeRow < 0
  2705. || source.upperRelativeRow + 1 >= fragment.rowSpan
  2706. || source.relativeColumnBoundary < 0
  2707. || upper + 1U >= logic->rungs.size()
  2708. || boundary < 0
  2709. || boundary > ProjectLimits::kMaximumConditionColumns
  2710. || !destination_connections.insert(key).second)
  2711. {
  2712. return {failure(
  2713. LogicEditorError::InvalidOperation,
  2714. "复制片段包含无效、重复或悬空的竖线"), {}, {}};
  2715. }
  2716. }
  2717. HistoryState before = captureState();
  2718. const bool modified_before = project_service_.isModified();
  2719. Project &project = project_service_.editProject();
  2720. ControlLogic *editable = editableLogic(&project, logic_id);
  2721. LogicClipboardPasteResult result;
  2722. for (const LogicClipboardCell &source : fragment.cells)
  2723. {
  2724. const std::size_t row = target_row
  2725. + static_cast<std::size_t>(source.relativeRow);
  2726. const int column = target.column + source.relativeColumn;
  2727. LadderCell &destination = editable->rungs[row].cells[
  2728. static_cast<std::size_t>(column)];
  2729. destination.kind = source.kind;
  2730. destination.node.reset();
  2731. if (source.node.has_value())
  2732. {
  2733. const std::string node_id = makeUniqueId(
  2734. *editable, nodePrefix(source.node->config));
  2735. destination.node = LogicNode{
  2736. node_id, source.node->config, source.node->configured};
  2737. if (result.edit.id.empty())
  2738. {
  2739. result.edit.id = node_id;
  2740. }
  2741. }
  2742. result.selection.cells.emplace_back(editable->rungs[row].id, column);
  2743. if (result.edit.id.empty())
  2744. {
  2745. result.edit.id = destination.id;
  2746. }
  2747. }
  2748. for (const LogicClipboardOutput &source : fragment.outputs)
  2749. {
  2750. const std::size_t row = target_row
  2751. + static_cast<std::size_t>(source.relativeRow);
  2752. const std::string node_id = makeUniqueId(
  2753. *editable, nodePrefix(source.node.config));
  2754. editable->rungs[row].output = LogicNode{
  2755. node_id, source.node.config, source.node.configured};
  2756. result.selection.outputRungIds.push_back(editable->rungs[row].id);
  2757. if (result.edit.id.empty())
  2758. {
  2759. result.edit.id = node_id;
  2760. }
  2761. }
  2762. for (const LogicClipboardVerticalConnection &source
  2763. : fragment.verticalConnections)
  2764. {
  2765. const std::size_t upper = target_row
  2766. + static_cast<std::size_t>(source.upperRelativeRow);
  2767. const int boundary = target.column + source.relativeColumnBoundary;
  2768. const std::string &upper_id = editable->rungs[upper].id;
  2769. const std::string &lower_id = editable->rungs[upper + 1U].id;
  2770. auto existing = std::find_if(
  2771. editable->verticalConnections.begin(),
  2772. editable->verticalConnections.end(),
  2773. [&upper_id, &lower_id, boundary](const VerticalConnection &connection)
  2774. {
  2775. return connectionMatches(
  2776. connection, upper_id, lower_id, boundary);
  2777. });
  2778. if (existing == editable->verticalConnections.end())
  2779. {
  2780. editable->verticalConnections.push_back({
  2781. makeUniqueId(*editable, "vertical"),
  2782. upper_id,
  2783. lower_id,
  2784. boundary});
  2785. existing = std::prev(editable->verticalConnections.end());
  2786. }
  2787. result.selection.verticalConnectionIds.push_back(existing->id);
  2788. if (result.edit.id.empty())
  2789. {
  2790. result.edit.id = existing->id;
  2791. }
  2792. }
  2793. std::string error;
  2794. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  2795. {
  2796. rollbackEdit(std::move(before), modified_before);
  2797. return {failure(LogicEditorError::InvalidOperation, error), {}, {}};
  2798. }
  2799. recordHistory(std::move(before));
  2800. result.edit.succeeded = true;
  2801. result.edit.error = LogicEditorError::None;
  2802. return result;
  2803. }
  2804. LogicEditorResult LogicEditorService::pasteConditionNodes(
  2805. const std::string &logic_id,
  2806. const std::string &rung_id,
  2807. const std::vector<LogicNode> &nodes,
  2808. int start_column)
  2809. {
  2810. const ControlLogic *logic = findLogic(logic_id);
  2811. const LadderRung *rung = findRung(logic_id, rung_id);
  2812. if (logic == nullptr || rung == nullptr)
  2813. {
  2814. return failure(LogicEditorError::RungNotFound, "未找到粘贴目标行");
  2815. }
  2816. if (nodes.empty()
  2817. || nodes.size()
  2818. > static_cast<std::size_t>(ProjectLimits::kMaximumConditionColumns))
  2819. {
  2820. return failure(LogicEditorError::InvalidOperation, "复制的条件数量无效");
  2821. }
  2822. for (const LogicNode &node : nodes)
  2823. {
  2824. if (!node.validate() || !node.isCondition())
  2825. {
  2826. return failure(LogicEditorError::InvalidNode, "只能粘贴有效的条件节点");
  2827. }
  2828. }
  2829. if (start_column < 0)
  2830. {
  2831. for (int candidate = 0;
  2832. candidate + static_cast<int>(nodes.size())
  2833. <= ProjectLimits::kMaximumConditionColumns;
  2834. ++candidate)
  2835. {
  2836. bool available = true;
  2837. for (std::size_t offset = 0U; offset < nodes.size(); ++offset)
  2838. {
  2839. available = available
  2840. && rung->cells[static_cast<std::size_t>(candidate) + offset].kind
  2841. == LadderCellKind::Gap;
  2842. }
  2843. if (available)
  2844. {
  2845. start_column = candidate;
  2846. break;
  2847. }
  2848. }
  2849. }
  2850. if (start_column < 0
  2851. || start_column + static_cast<int>(nodes.size())
  2852. > ProjectLimits::kMaximumConditionColumns)
  2853. {
  2854. return failure(LogicEditorError::InvalidOperation, "目标行没有足够连续空格");
  2855. }
  2856. HistoryState before = captureState();
  2857. const bool modified_before = project_service_.isModified();
  2858. Project &project = project_service_.editProject();
  2859. ControlLogic *editable = editableLogic(&project, logic_id);
  2860. LadderRung *target = editableRung(editable, rung_id);
  2861. std::string first_id;
  2862. for (std::size_t offset = 0U; offset < nodes.size(); ++offset)
  2863. {
  2864. LadderCell &cell = target->cells[
  2865. static_cast<std::size_t>(start_column) + offset];
  2866. const std::string id = makeUniqueId(
  2867. *editable, nodePrefix(nodes[offset].config));
  2868. cell.kind = LadderCellKind::Node;
  2869. cell.node = LogicNode{id, nodes[offset].config, nodes[offset].configured};
  2870. if (first_id.empty())
  2871. {
  2872. first_id = id;
  2873. }
  2874. }
  2875. std::string error;
  2876. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  2877. {
  2878. rollbackEdit(std::move(before), modified_before);
  2879. return failure(LogicEditorError::InvalidOperation, error);
  2880. }
  2881. recordHistory(std::move(before));
  2882. return {true, LogicEditorError::None, {}, first_id};
  2883. }
  2884. bool LogicEditorService::areConditionNodesContiguous(
  2885. const std::string &logic_id,
  2886. const std::string &rung_id,
  2887. const std::vector<std::string> &node_ids) const
  2888. {
  2889. const LadderRung *rung = findRung(logic_id, rung_id);
  2890. if (rung == nullptr || node_ids.empty())
  2891. {
  2892. return false;
  2893. }
  2894. std::unordered_set<std::string> requested(
  2895. node_ids.cbegin(), node_ids.cend());
  2896. if (requested.size() != node_ids.size())
  2897. {
  2898. return false;
  2899. }
  2900. std::vector<int> columns;
  2901. for (int column = 0; column < ProjectLimits::kMaximumConditionColumns; ++column)
  2902. {
  2903. const LadderCell &cell = rung->cells[static_cast<std::size_t>(column)];
  2904. if (cell.node.has_value() && requested.count(cell.node->id) != 0U)
  2905. {
  2906. columns.push_back(column);
  2907. }
  2908. }
  2909. return columns.size() == node_ids.size()
  2910. && columns.back() - columns.front() + 1
  2911. == static_cast<int>(columns.size());
  2912. }
  2913. LogicEditorResult LogicEditorService::pasteRung(
  2914. const std::string &logic_id,
  2915. const LadderRung &source)
  2916. {
  2917. const ControlLogic *logic = findLogic(logic_id);
  2918. std::string error;
  2919. if (logic == nullptr)
  2920. {
  2921. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  2922. }
  2923. if (!source.validateStructure(&error))
  2924. {
  2925. return failure(LogicEditorError::InvalidOperation, error);
  2926. }
  2927. if (logic->rungs.size()
  2928. >= project_service_.projectLimits().maximumRungsPerLogic
  2929. || totalRungCount(project_service_.project())
  2930. >= ProjectLimits::kMaximumRungsPerProject)
  2931. {
  2932. return failure(LogicEditorError::InvalidOperation, "梯形图行数已经达到上限");
  2933. }
  2934. HistoryState before = captureState();
  2935. const bool modified_before = project_service_.isModified();
  2936. Project &project = project_service_.editProject();
  2937. ControlLogic *editable = editableLogic(&project, logic_id);
  2938. LadderRung pasted = makeEmptyRung(*editable, editable->rungs.size());
  2939. pasted.comment = source.comment;
  2940. for (std::size_t column = 0U; column < source.cells.size(); ++column)
  2941. {
  2942. pasted.cells[column].kind = source.cells[column].kind;
  2943. if (source.cells[column].node.has_value())
  2944. {
  2945. const LogicNode &source_node = *source.cells[column].node;
  2946. pasted.cells[column].node = LogicNode{
  2947. makeUniqueId(*editable, nodePrefix(source_node.config)),
  2948. source_node.config,
  2949. source_node.configured};
  2950. }
  2951. }
  2952. if (source.output.has_value())
  2953. {
  2954. pasted.output = LogicNode{
  2955. makeUniqueId(*editable, nodePrefix(source.output->config)),
  2956. source.output->config,
  2957. source.output->configured};
  2958. }
  2959. const std::string pasted_id = pasted.id;
  2960. editable->rungs.push_back(std::move(pasted));
  2961. refreshRungNames(editable);
  2962. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  2963. {
  2964. rollbackEdit(std::move(before), modified_before);
  2965. return failure(LogicEditorError::InvalidOperation, error);
  2966. }
  2967. recordHistory(std::move(before));
  2968. return {true, LogicEditorError::None, {}, pasted_id};
  2969. }
  2970. LogicEditorResult LogicEditorService::removeNode(
  2971. const std::string &logic_id, const std::string &node_id)
  2972. {
  2973. return removeNodes(logic_id, {node_id});
  2974. }
  2975. LogicEditorResult LogicEditorService::removeNodes(
  2976. const std::string &logic_id,
  2977. const std::vector<std::string> &node_ids)
  2978. {
  2979. if (findLogic(logic_id) == nullptr)
  2980. {
  2981. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  2982. }
  2983. if (node_ids.empty())
  2984. {
  2985. return failure(LogicEditorError::InvalidOperation, "请先选择要删除的节点");
  2986. }
  2987. std::unordered_set<std::string> ids;
  2988. for (const std::string &id : node_ids)
  2989. {
  2990. if (!ids.insert(id).second || findNode(logic_id, id) == nullptr)
  2991. {
  2992. return failure(
  2993. LogicEditorError::NodeNotFound,
  2994. "节点删除列表包含重复或不存在的对象");
  2995. }
  2996. }
  2997. HistoryState before = captureState();
  2998. Project &project = project_service_.editProject();
  2999. ControlLogic *logic = editableLogic(&project, logic_id);
  3000. for (LadderRung &rung : logic->rungs)
  3001. {
  3002. for (LadderCell &cell : rung.cells)
  3003. {
  3004. if (cell.node.has_value() && ids.count(cell.node->id) != 0U)
  3005. {
  3006. cell.kind = LadderCellKind::Gap;
  3007. cell.node.reset();
  3008. }
  3009. }
  3010. if (rung.output.has_value() && ids.count(rung.output->id) != 0U)
  3011. {
  3012. rung.output.reset();
  3013. }
  3014. }
  3015. recordHistory(std::move(before));
  3016. return {true, LogicEditorError::None, {}, node_ids.front()};
  3017. }
  3018. bool LogicEditorService::canUndo() const
  3019. {
  3020. return history_.canUndo();
  3021. }
  3022. bool LogicEditorService::canRedo() const
  3023. {
  3024. return history_.canRedo();
  3025. }
  3026. LogicEditorResult LogicEditorService::undo()
  3027. {
  3028. const std::optional<HistoryState> target = history_.undo(captureState());
  3029. if (!target.has_value())
  3030. {
  3031. return historyFailure("没有可撤销的梯形图编辑操作");
  3032. }
  3033. project_service_.editProject().controlLogics = target->logics;
  3034. return {true, LogicEditorError::None, {}, {}};
  3035. }
  3036. LogicEditorResult LogicEditorService::redo()
  3037. {
  3038. const std::optional<HistoryState> target = history_.redo(captureState());
  3039. if (!target.has_value())
  3040. {
  3041. return historyFailure("没有可重做的梯形图编辑操作");
  3042. }
  3043. project_service_.editProject().controlLogics = target->logics;
  3044. return {true, LogicEditorError::None, {}, {}};
  3045. }
  3046. void LogicEditorService::clearHistory()
  3047. {
  3048. history_.clear();
  3049. }
  3050. LogicEditResult LogicEditorService::applyCellAndAdvance(
  3051. const std::string &logic_id,
  3052. const LogicEditCursor &cursor,
  3053. const LogicNodeConfig *config,
  3054. bool configured)
  3055. {
  3056. const ControlLogic *existing_logic = findLogic(logic_id);
  3057. if (existing_logic == nullptr)
  3058. {
  3059. return {failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"), {}};
  3060. }
  3061. if (cursor.output || cursor.column < 0
  3062. || cursor.column >= ProjectLimits::kMaximumConditionColumns)
  3063. {
  3064. return {failure(LogicEditorError::CellNotFound,
  3065. "条件指令只能放在第 1~10 列"), {}};
  3066. }
  3067. if (config != nullptr)
  3068. {
  3069. if (!isConditionConfig(*config))
  3070. {
  3071. return {failure(LogicEditorError::InvalidNode,
  3072. "条件区只能放置条件指令"), {}};
  3073. }
  3074. LogicNode candidate{"candidate", *config, configured};
  3075. std::string candidate_error;
  3076. if (!candidate.validate(&candidate_error))
  3077. {
  3078. return {failure(LogicEditorError::InvalidNode, candidate_error), {}};
  3079. }
  3080. }
  3081. std::string target_rung_id = cursor.rungId;
  3082. int target_column = cursor.column;
  3083. const bool create_first_rung = target_rung_id.empty();
  3084. if (create_first_rung)
  3085. {
  3086. if (!existing_logic->rungs.empty())
  3087. {
  3088. return {failure(LogicEditorError::RungNotFound,
  3089. "请先选择要编辑的梯形图行"), {}};
  3090. }
  3091. if (existing_logic->rungs.size()
  3092. >= project_service_.projectLimits().maximumRungsPerLogic
  3093. || totalRungCount(project_service_.project())
  3094. >= ProjectLimits::kMaximumRungsPerProject)
  3095. {
  3096. return {failure(LogicEditorError::InvalidOperation,
  3097. "梯形图行数已经达到当前上限"), {}};
  3098. }
  3099. target_column = 0;
  3100. }
  3101. else
  3102. {
  3103. const LadderCell *existing_cell = findCell(
  3104. logic_id, target_rung_id, target_column);
  3105. if (existing_cell == nullptr)
  3106. {
  3107. return {failure(LogicEditorError::CellNotFound,
  3108. "未找到目标条件网格"), {}};
  3109. }
  3110. if (config == nullptr && existing_cell->kind == LadderCellKind::Node)
  3111. {
  3112. return {failure(LogicEditorError::InvalidOperation,
  3113. "横线不能覆盖已有条件指令"), {}};
  3114. }
  3115. }
  3116. HistoryState before = captureState();
  3117. const bool modified_before = project_service_.isModified();
  3118. Project &project = project_service_.editProject();
  3119. ControlLogic *logic = editableLogic(&project, logic_id);
  3120. if (create_first_rung)
  3121. {
  3122. target_rung_id = insertEmptyRungAt(logic, 0U);
  3123. }
  3124. LadderCell &cell = editableRung(logic, target_rung_id)
  3125. ->cells[static_cast<std::size_t>(target_column)];
  3126. std::string edited_id = cell.id;
  3127. if (config == nullptr)
  3128. {
  3129. cell.kind = LadderCellKind::Wire;
  3130. cell.node.reset();
  3131. }
  3132. else
  3133. {
  3134. edited_id = makeUniqueId(*logic, nodePrefix(*config));
  3135. cell.kind = LadderCellKind::Node;
  3136. cell.node = LogicNode{edited_id, *config, configured};
  3137. }
  3138. std::string error;
  3139. if (!logic->validateStructure(project_service_.projectLimits(), &error))
  3140. {
  3141. rollbackEdit(std::move(before), modified_before);
  3142. return {failure(LogicEditorError::InvalidOperation, error), {}};
  3143. }
  3144. recordHistory(std::move(before));
  3145. LogicEditCursor next{target_rung_id, target_column + 1, false};
  3146. if (next.column >= ProjectLimits::kMaximumConditionColumns)
  3147. {
  3148. next.column = ProjectLimits::kMaximumConditionColumns;
  3149. next.output = true;
  3150. }
  3151. return {
  3152. {true, LogicEditorError::None, {}, edited_id},
  3153. std::move(next)};
  3154. }
  3155. bool LogicEditorService::isConditionConfig(const LogicNodeConfig &config)
  3156. {
  3157. return std::holds_alternative<ContactNodeConfig>(config)
  3158. || std::holds_alternative<EdgeContactNodeConfig>(config)
  3159. || std::holds_alternative<CompareNodeConfig>(config);
  3160. }
  3161. bool LogicEditorService::isOutputConfig(const LogicNodeConfig &config)
  3162. {
  3163. return std::holds_alternative<CoilNodeConfig>(config)
  3164. || std::holds_alternative<MoveNodeConfig>(config)
  3165. || std::holds_alternative<ArithmeticNodeConfig>(config);
  3166. }
  3167. std::string LogicEditorService::nodePrefix(const LogicNodeConfig &config)
  3168. {
  3169. return std::visit(
  3170. [](const auto &value) -> std::string
  3171. {
  3172. using Config = std::decay_t<decltype(value)>;
  3173. if constexpr (std::is_same_v<Config, ContactNodeConfig>)
  3174. {
  3175. return "contact";
  3176. }
  3177. else if constexpr (std::is_same_v<Config, EdgeContactNodeConfig>)
  3178. {
  3179. return "edge";
  3180. }
  3181. else if constexpr (std::is_same_v<Config, CoilNodeConfig>)
  3182. {
  3183. return "coil";
  3184. }
  3185. else if constexpr (std::is_same_v<Config, MoveNodeConfig>)
  3186. {
  3187. return "move";
  3188. }
  3189. else if constexpr (std::is_same_v<Config, ArithmeticNodeConfig>)
  3190. {
  3191. return "arithmetic";
  3192. }
  3193. else
  3194. {
  3195. return "compare";
  3196. }
  3197. },
  3198. config);
  3199. }
  3200. std::string LogicEditorService::makeUniqueId(
  3201. const ControlLogic &logic, const std::string &prefix)
  3202. {
  3203. const auto exists = [&logic](const std::string &candidate)
  3204. {
  3205. if (logic.id == candidate)
  3206. {
  3207. return true;
  3208. }
  3209. for (const LadderRung &rung : logic.rungs)
  3210. {
  3211. if (rung.id == candidate
  3212. || (rung.output.has_value() && rung.output->id == candidate))
  3213. {
  3214. return true;
  3215. }
  3216. for (const LadderCell &cell : rung.cells)
  3217. {
  3218. if (cell.id == candidate
  3219. || (cell.node.has_value() && cell.node->id == candidate))
  3220. {
  3221. return true;
  3222. }
  3223. }
  3224. }
  3225. return std::any_of(
  3226. logic.verticalConnections.cbegin(),
  3227. logic.verticalConnections.cend(),
  3228. [&candidate](const VerticalConnection &connection)
  3229. {
  3230. return connection.id == candidate;
  3231. });
  3232. };
  3233. for (std::size_t index = 1U;; ++index)
  3234. {
  3235. const std::string candidate = prefix + '-' + std::to_string(index);
  3236. if (!exists(candidate))
  3237. {
  3238. return candidate;
  3239. }
  3240. }
  3241. }
  3242. LadderRung LogicEditorService::makeEmptyRung(
  3243. const ControlLogic &logic, std::size_t visual_index)
  3244. {
  3245. LadderRung rung;
  3246. rung.id = makeUniqueId(logic, "rung");
  3247. rung.name = "行 " + std::to_string(visual_index + 1U);
  3248. rung.cells.reserve(
  3249. static_cast<std::size_t>(ProjectLimits::kMaximumConditionColumns));
  3250. for (int column = 0;
  3251. column < ProjectLimits::kMaximumConditionColumns;
  3252. ++column)
  3253. {
  3254. rung.cells.push_back({
  3255. rung.id + "-cell-" + std::to_string(column + 1),
  3256. LadderCellKind::Gap,
  3257. std::nullopt});
  3258. }
  3259. return rung;
  3260. }
  3261. std::string LogicEditorService::insertEmptyRungAt(
  3262. ControlLogic *logic, std::size_t position)
  3263. {
  3264. if (logic == nullptr || position > logic->rungs.size())
  3265. {
  3266. return {};
  3267. }
  3268. const std::string upper_id = position > 0U
  3269. ? logic->rungs[position - 1U].id : std::string{};
  3270. const std::string lower_id = position < logic->rungs.size()
  3271. ? logic->rungs[position].id : std::string{};
  3272. std::vector<VerticalConnection> bridges;
  3273. if (!upper_id.empty() && !lower_id.empty())
  3274. {
  3275. for (const VerticalConnection &connection : logic->verticalConnections)
  3276. {
  3277. if (connection.upperRungId == upper_id
  3278. && connection.lowerRungId == lower_id)
  3279. {
  3280. bridges.push_back(connection);
  3281. }
  3282. }
  3283. logic->verticalConnections.erase(
  3284. std::remove_if(
  3285. logic->verticalConnections.begin(),
  3286. logic->verticalConnections.end(),
  3287. [&upper_id, &lower_id](const VerticalConnection &connection)
  3288. {
  3289. return connection.upperRungId == upper_id
  3290. && connection.lowerRungId == lower_id;
  3291. }),
  3292. logic->verticalConnections.end());
  3293. }
  3294. LadderRung rung = makeEmptyRung(*logic, position);
  3295. const std::string new_id = rung.id;
  3296. logic->rungs.insert(
  3297. logic->rungs.begin() + static_cast<std::ptrdiff_t>(position),
  3298. std::move(rung));
  3299. for (VerticalConnection &bridge : bridges)
  3300. {
  3301. bridge.lowerRungId = new_id;
  3302. logic->verticalConnections.push_back(bridge);
  3303. }
  3304. for (const VerticalConnection &bridge : bridges)
  3305. {
  3306. logic->verticalConnections.push_back({
  3307. makeUniqueId(*logic, "vertical"),
  3308. new_id,
  3309. lower_id,
  3310. bridge.columnBoundary});
  3311. }
  3312. refreshRungNames(logic);
  3313. return new_id;
  3314. }
  3315. void LogicEditorService::removeRungAt(
  3316. ControlLogic *logic, std::size_t position)
  3317. {
  3318. if (logic == nullptr || position >= logic->rungs.size())
  3319. {
  3320. return;
  3321. }
  3322. const std::string removed_id = logic->rungs[position].id;
  3323. const std::string upper_id = position > 0U
  3324. ? logic->rungs[position - 1U].id : std::string{};
  3325. const std::string lower_id = position + 1U < logic->rungs.size()
  3326. ? logic->rungs[position + 1U].id : std::string{};
  3327. std::map<int, std::string> upper_connections;
  3328. std::map<int, std::string> lower_connections;
  3329. for (const VerticalConnection &connection : logic->verticalConnections)
  3330. {
  3331. if (connection.upperRungId == upper_id
  3332. && connection.lowerRungId == removed_id)
  3333. {
  3334. upper_connections[connection.columnBoundary] = connection.id;
  3335. }
  3336. if (connection.upperRungId == removed_id
  3337. && connection.lowerRungId == lower_id)
  3338. {
  3339. lower_connections[connection.columnBoundary] = connection.id;
  3340. }
  3341. }
  3342. logic->verticalConnections.erase(
  3343. std::remove_if(
  3344. logic->verticalConnections.begin(),
  3345. logic->verticalConnections.end(),
  3346. [&removed_id](const VerticalConnection &connection)
  3347. {
  3348. return connection.upperRungId == removed_id
  3349. || connection.lowerRungId == removed_id;
  3350. }),
  3351. logic->verticalConnections.end());
  3352. logic->rungs.erase(
  3353. logic->rungs.begin() + static_cast<std::ptrdiff_t>(position));
  3354. if (!upper_id.empty() && !lower_id.empty())
  3355. {
  3356. for (const auto &upper : upper_connections)
  3357. {
  3358. if (lower_connections.count(upper.first) != 0U)
  3359. {
  3360. logic->verticalConnections.push_back({
  3361. upper.second,
  3362. upper_id,
  3363. lower_id,
  3364. upper.first});
  3365. }
  3366. }
  3367. }
  3368. refreshRungNames(logic);
  3369. }
  3370. void LogicEditorService::refreshRungNames(ControlLogic *logic)
  3371. {
  3372. if (logic == nullptr)
  3373. {
  3374. return;
  3375. }
  3376. for (std::size_t index = 0U; index < logic->rungs.size(); ++index)
  3377. {
  3378. logic->rungs[index].name = "行 " + std::to_string(index + 1U);
  3379. }
  3380. }
  3381. LogicEditorResult LogicEditorService::failure(
  3382. LogicEditorError error, const std::string &message)
  3383. {
  3384. return {false, error, message, {}};
  3385. }