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

3425 строки
120 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. LogicEditorResult LogicEditorService::setVerticalConnectionRange(
  1565. const std::string &logic_id,
  1566. const std::string &first_rung_id,
  1567. const std::string &last_rung_id,
  1568. int column_boundary,
  1569. bool connected)
  1570. {
  1571. const ControlLogic *logic = findLogic(logic_id);
  1572. if (logic == nullptr)
  1573. {
  1574. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  1575. }
  1576. std::size_t first = rungIndex(*logic, first_rung_id);
  1577. std::size_t last = rungIndex(*logic, last_rung_id);
  1578. if (first == logic->rungs.size() || last == logic->rungs.size()
  1579. || first == last || column_boundary < 0
  1580. || column_boundary > ProjectLimits::kMaximumConditionColumns)
  1581. {
  1582. return failure(
  1583. LogicEditorError::InvalidOperation,
  1584. "竖线范围必须跨越至少两行且位于 0~10 列边界");
  1585. }
  1586. if (first > last)
  1587. {
  1588. std::swap(first, last);
  1589. }
  1590. HistoryState before = captureState();
  1591. const bool modified_before = project_service_.isModified();
  1592. Project &project = project_service_.editProject();
  1593. ControlLogic *editable = editableLogic(&project, logic_id);
  1594. for (std::size_t row = first; row < last; ++row)
  1595. {
  1596. const std::string upper_id = editable->rungs[row].id;
  1597. const std::string lower_id = editable->rungs[row + 1U].id;
  1598. const auto found = std::find_if(
  1599. editable->verticalConnections.begin(),
  1600. editable->verticalConnections.end(),
  1601. [&upper_id, &lower_id, column_boundary](
  1602. const VerticalConnection &connection)
  1603. {
  1604. return connectionMatches(
  1605. connection, upper_id, lower_id, column_boundary);
  1606. });
  1607. if (connected && found == editable->verticalConnections.end())
  1608. {
  1609. editable->verticalConnections.push_back({
  1610. makeUniqueId(*editable, "vertical"),
  1611. upper_id,
  1612. lower_id,
  1613. column_boundary});
  1614. }
  1615. else if (!connected && found != editable->verticalConnections.end())
  1616. {
  1617. editable->verticalConnections.erase(found);
  1618. }
  1619. }
  1620. std::string error;
  1621. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  1622. {
  1623. rollbackEdit(std::move(before), modified_before);
  1624. return failure(LogicEditorError::InvalidOperation, error);
  1625. }
  1626. recordHistory(std::move(before));
  1627. return {true, LogicEditorError::None, {}, first_rung_id};
  1628. }
  1629. LogicEditorResult LogicEditorService::removeVerticalConnections(
  1630. const std::string &logic_id,
  1631. const std::vector<std::string> &connection_ids)
  1632. {
  1633. const ControlLogic *logic = findLogic(logic_id);
  1634. if (logic == nullptr)
  1635. {
  1636. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  1637. }
  1638. if (connection_ids.empty())
  1639. {
  1640. return failure(LogicEditorError::InvalidOperation, "请先选择要删除的竖线");
  1641. }
  1642. std::unordered_set<std::string> ids;
  1643. for (const std::string &id : connection_ids)
  1644. {
  1645. if (!ids.insert(id).second
  1646. || findVerticalConnection(*logic, id) == nullptr)
  1647. {
  1648. return failure(
  1649. LogicEditorError::ConnectionNotFound,
  1650. "竖线删除列表包含重复或不存在的对象");
  1651. }
  1652. }
  1653. HistoryState before = captureState();
  1654. Project &project = project_service_.editProject();
  1655. ControlLogic *editable = editableLogic(&project, logic_id);
  1656. editable->verticalConnections.erase(
  1657. std::remove_if(
  1658. editable->verticalConnections.begin(),
  1659. editable->verticalConnections.end(),
  1660. [&ids](const VerticalConnection &connection)
  1661. {
  1662. return ids.count(connection.id) != 0U;
  1663. }),
  1664. editable->verticalConnections.end());
  1665. recordHistory(std::move(before));
  1666. return {true, LogicEditorError::None, {}, connection_ids.front()};
  1667. }
  1668. LogicEditorResult LogicEditorService::deleteSelection(
  1669. const std::string &logic_id,
  1670. const LogicSelectionDeleteRequest &selection)
  1671. {
  1672. const ControlLogic *logic = findLogic(logic_id);
  1673. if (logic == nullptr)
  1674. {
  1675. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  1676. }
  1677. if (selection.cells.empty() && selection.outputRungIds.empty()
  1678. && selection.verticalConnectionIds.empty())
  1679. {
  1680. return failure(LogicEditorError::InvalidOperation, "没有可删除的选中对象");
  1681. }
  1682. std::unordered_set<std::string> cell_positions;
  1683. for (const auto &position : selection.cells)
  1684. {
  1685. const std::string key = position.first + "\n"
  1686. + std::to_string(position.second);
  1687. const LadderCell *cell = findCell(
  1688. logic_id, position.first, position.second);
  1689. if (!cell_positions.insert(key).second || cell == nullptr)
  1690. {
  1691. return failure(
  1692. LogicEditorError::CellNotFound,
  1693. "删除列表包含重复或不存在的网格");
  1694. }
  1695. if (cell->kind == LadderCellKind::Gap)
  1696. {
  1697. return failure(
  1698. LogicEditorError::InvalidOperation,
  1699. "删除列表包含空白网格");
  1700. }
  1701. }
  1702. std::unordered_set<std::string> output_rung_ids;
  1703. for (const std::string &rung_id : selection.outputRungIds)
  1704. {
  1705. const LadderRung *rung = findRung(logic_id, rung_id);
  1706. if (!output_rung_ids.insert(rung_id).second || rung == nullptr
  1707. || !rung->output.has_value())
  1708. {
  1709. return failure(
  1710. LogicEditorError::NodeNotFound,
  1711. "删除列表包含重复或不存在的输出指令");
  1712. }
  1713. }
  1714. std::unordered_set<std::string> connection_ids;
  1715. for (const std::string &connection_id
  1716. : selection.verticalConnectionIds)
  1717. {
  1718. if (!connection_ids.insert(connection_id).second
  1719. || findVerticalConnection(*logic, connection_id) == nullptr)
  1720. {
  1721. return failure(
  1722. LogicEditorError::ConnectionNotFound,
  1723. "删除列表包含重复或不存在的竖线");
  1724. }
  1725. }
  1726. HistoryState before = captureState();
  1727. const bool modified_before = project_service_.isModified();
  1728. Project &project = project_service_.editProject();
  1729. ControlLogic *editable = editableLogic(&project, logic_id);
  1730. for (const auto &position : selection.cells)
  1731. {
  1732. LadderCell &cell = editableRung(editable, position.first)
  1733. ->cells[static_cast<std::size_t>(position.second)];
  1734. cell.kind = LadderCellKind::Gap;
  1735. cell.node.reset();
  1736. }
  1737. for (const std::string &rung_id : selection.outputRungIds)
  1738. {
  1739. editableRung(editable, rung_id)->output.reset();
  1740. }
  1741. editable->verticalConnections.erase(
  1742. std::remove_if(
  1743. editable->verticalConnections.begin(),
  1744. editable->verticalConnections.end(),
  1745. [&connection_ids](const VerticalConnection &connection)
  1746. {
  1747. return connection_ids.count(connection.id) != 0U;
  1748. }),
  1749. editable->verticalConnections.end());
  1750. std::string error;
  1751. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  1752. {
  1753. rollbackEdit(std::move(before), modified_before);
  1754. return failure(LogicEditorError::InvalidOperation, error);
  1755. }
  1756. recordHistory(std::move(before));
  1757. return {true, LogicEditorError::None, {}, logic_id};
  1758. }
  1759. LogicEditorResult LogicEditorService::addParallelBranch(
  1760. const std::string &logic_id,
  1761. const std::string &rung_id,
  1762. const std::vector<std::string> &selected_node_ids,
  1763. const LogicNodeConfig &config,
  1764. bool configured)
  1765. {
  1766. const ControlLogic *logic = findLogic(logic_id);
  1767. const LadderRung *rung = findRung(logic_id, rung_id);
  1768. if (logic == nullptr || rung == nullptr)
  1769. {
  1770. return failure(LogicEditorError::RungNotFound, "未找到梯形图行");
  1771. }
  1772. if (!isConditionConfig(config)
  1773. || !areConditionNodesContiguous(logic_id, rung_id, selected_node_ids))
  1774. {
  1775. return failure(
  1776. LogicEditorError::InvalidOperation,
  1777. "请先选择同一行中连续的条件节点");
  1778. }
  1779. if (logic->rungs.size()
  1780. >= project_service_.projectLimits().maximumRungsPerLogic
  1781. || totalRungCount(project_service_.project())
  1782. >= ProjectLimits::kMaximumRungsPerProject)
  1783. {
  1784. return failure(LogicEditorError::InvalidOperation, "梯形图行数已经达到上限");
  1785. }
  1786. int first_column = ProjectLimits::kMaximumConditionColumns;
  1787. int last_column = -1;
  1788. for (int column = 0; column < ProjectLimits::kMaximumConditionColumns; ++column)
  1789. {
  1790. const LadderCell &cell = rung->cells[static_cast<std::size_t>(column)];
  1791. if (cell.node.has_value()
  1792. && std::find(
  1793. selected_node_ids.cbegin(), selected_node_ids.cend(),
  1794. cell.node->id) != selected_node_ids.cend())
  1795. {
  1796. first_column = std::min(first_column, column);
  1797. last_column = std::max(last_column, column);
  1798. }
  1799. }
  1800. HistoryState before = captureState();
  1801. const bool modified_before = project_service_.isModified();
  1802. Project &project = project_service_.editProject();
  1803. ControlLogic *editable = editableLogic(&project, logic_id);
  1804. const std::size_t source_index = rungIndex(*editable, rung_id);
  1805. const std::string new_rung_id = insertEmptyRungAt(editable, source_index + 1U);
  1806. LadderRung *branch = editableRung(editable, new_rung_id);
  1807. const std::string node_id = makeUniqueId(*editable, nodePrefix(config));
  1808. branch->cells[static_cast<std::size_t>(first_column)].kind =
  1809. LadderCellKind::Node;
  1810. branch->cells[static_cast<std::size_t>(first_column)].node =
  1811. LogicNode{node_id, config, configured};
  1812. for (int column = first_column + 1; column <= last_column; ++column)
  1813. {
  1814. branch->cells[static_cast<std::size_t>(column)].kind =
  1815. LadderCellKind::Wire;
  1816. }
  1817. const auto ensure_branch_edge = [editable, &rung_id, &new_rung_id](
  1818. int boundary)
  1819. {
  1820. const bool exists = std::any_of(
  1821. editable->verticalConnections.cbegin(),
  1822. editable->verticalConnections.cend(),
  1823. [&rung_id, &new_rung_id, boundary](
  1824. const VerticalConnection &connection)
  1825. {
  1826. return connectionMatches(
  1827. connection, rung_id, new_rung_id, boundary);
  1828. });
  1829. if (!exists)
  1830. {
  1831. editable->verticalConnections.push_back({
  1832. makeUniqueId(*editable, "vertical"),
  1833. rung_id,
  1834. new_rung_id,
  1835. boundary});
  1836. }
  1837. };
  1838. ensure_branch_edge(first_column);
  1839. ensure_branch_edge(last_column + 1);
  1840. std::string error;
  1841. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  1842. {
  1843. rollbackEdit(std::move(before), modified_before);
  1844. return failure(LogicEditorError::InvalidOperation, error);
  1845. }
  1846. recordHistory(std::move(before));
  1847. return {true, LogicEditorError::None, {}, node_id};
  1848. }
  1849. LogicEditorResult LogicEditorService::addParallelToWholeCondition(
  1850. const std::string &logic_id,
  1851. const std::string &rung_id,
  1852. const LogicNodeConfig &config,
  1853. bool configured)
  1854. {
  1855. const LadderRung *rung = findRung(logic_id, rung_id);
  1856. if (rung == nullptr)
  1857. {
  1858. return failure(LogicEditorError::RungNotFound, "未找到梯形图行");
  1859. }
  1860. std::vector<std::string> node_ids;
  1861. for (const LadderCell &cell : rung->cells)
  1862. {
  1863. if (cell.node.has_value())
  1864. {
  1865. node_ids.push_back(cell.node->id);
  1866. }
  1867. }
  1868. if (node_ids.empty())
  1869. {
  1870. return failure(
  1871. LogicEditorError::InvalidOperation,
  1872. "当前行还没有可并联的条件节点");
  1873. }
  1874. return addParallelBranch(
  1875. logic_id, rung_id, node_ids, config, configured);
  1876. }
  1877. LogicEditorResult LogicEditorService::setOutput(
  1878. const std::string &logic_id,
  1879. const std::string &rung_id,
  1880. const LogicNodeConfig &config,
  1881. bool configured)
  1882. {
  1883. const ControlLogic *existing_logic = findLogic(logic_id);
  1884. if (existing_logic == nullptr)
  1885. {
  1886. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  1887. }
  1888. if (!isOutputConfig(config))
  1889. {
  1890. return failure(LogicEditorError::InvalidNode, "输出槽只能放置输出指令");
  1891. }
  1892. if (rung_id.empty())
  1893. {
  1894. if (existing_logic->rungs.size()
  1895. >= project_service_.projectLimits().maximumRungsPerLogic
  1896. || totalRungCount(project_service_.project())
  1897. >= ProjectLimits::kMaximumRungsPerProject)
  1898. {
  1899. return failure(
  1900. LogicEditorError::InvalidOperation,
  1901. "梯形图行数已经达到当前上限");
  1902. }
  1903. HistoryState before = captureState();
  1904. const bool modified_before = project_service_.isModified();
  1905. Project &project = project_service_.editProject();
  1906. ControlLogic *logic = editableLogic(&project, logic_id);
  1907. const std::string new_rung_id = insertEmptyRungAt(
  1908. logic, logic->rungs.size());
  1909. LadderRung *rung = editableRung(logic, new_rung_id);
  1910. const std::string node_id = makeUniqueId(*logic, nodePrefix(config));
  1911. rung->output = LogicNode{node_id, config, configured};
  1912. std::string error;
  1913. if (!logic->validateStructure(project_service_.projectLimits(), &error))
  1914. {
  1915. rollbackEdit(std::move(before), modified_before);
  1916. return failure(LogicEditorError::InvalidNode, error);
  1917. }
  1918. recordHistory(std::move(before));
  1919. return {true, LogicEditorError::None, {}, node_id};
  1920. }
  1921. if (findRung(logic_id, rung_id) == nullptr)
  1922. {
  1923. return failure(LogicEditorError::RungNotFound, "未找到梯形图行");
  1924. }
  1925. HistoryState before = captureState();
  1926. const bool modified_before = project_service_.isModified();
  1927. Project &project = project_service_.editProject();
  1928. ControlLogic *logic = editableLogic(&project, logic_id);
  1929. LadderRung *rung = editableRung(logic, rung_id);
  1930. const std::string node_id = makeUniqueId(*logic, nodePrefix(config));
  1931. rung->output = LogicNode{node_id, config, configured};
  1932. std::string error;
  1933. if (!logic->validateStructure(project_service_.projectLimits(), &error))
  1934. {
  1935. rollbackEdit(std::move(before), modified_before);
  1936. return failure(LogicEditorError::InvalidNode, error);
  1937. }
  1938. recordHistory(std::move(before));
  1939. return {true, LogicEditorError::None, {}, node_id};
  1940. }
  1941. LogicEditResult LogicEditorService::applyOutputAndAdvance(
  1942. const std::string &logic_id,
  1943. const LogicEditCursor &cursor,
  1944. const LogicNodeConfig &config,
  1945. bool configured)
  1946. {
  1947. const ControlLogic *existing_logic = findLogic(logic_id);
  1948. if (existing_logic == nullptr)
  1949. {
  1950. return {failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"), {}};
  1951. }
  1952. if (!isOutputConfig(config))
  1953. {
  1954. return {failure(LogicEditorError::InvalidNode,
  1955. "输出槽只能放置输出指令"), {}};
  1956. }
  1957. LogicNode candidate{"candidate", config, configured};
  1958. std::string error;
  1959. if (!candidate.validate(&error))
  1960. {
  1961. return {failure(LogicEditorError::InvalidNode, error), {}};
  1962. }
  1963. std::string target_rung_id = cursor.rungId;
  1964. std::size_t target_index = existing_logic->rungs.size();
  1965. bool create_output_rung = target_rung_id.empty();
  1966. if (create_output_rung)
  1967. {
  1968. if (!existing_logic->rungs.empty())
  1969. {
  1970. return {failure(LogicEditorError::RungNotFound,
  1971. "请先选择输出所在行"), {}};
  1972. }
  1973. }
  1974. else
  1975. {
  1976. target_index = rungIndex(*existing_logic, target_rung_id);
  1977. if (target_index == existing_logic->rungs.size())
  1978. {
  1979. return {failure(LogicEditorError::RungNotFound,
  1980. "未找到梯形图行"), {}};
  1981. }
  1982. }
  1983. std::size_t group_end = target_index;
  1984. if (!create_output_rung)
  1985. {
  1986. while (group_end + 1U < existing_logic->rungs.size())
  1987. {
  1988. const std::string &upper_id = existing_logic->rungs[group_end].id;
  1989. const std::string &lower_id = existing_logic->rungs[group_end + 1U].id;
  1990. const bool connected = std::any_of(
  1991. existing_logic->verticalConnections.cbegin(),
  1992. existing_logic->verticalConnections.cend(),
  1993. [&upper_id, &lower_id](const VerticalConnection &connection)
  1994. {
  1995. return connection.upperRungId == upper_id
  1996. && connection.lowerRungId == lower_id;
  1997. });
  1998. if (!connected)
  1999. {
  2000. break;
  2001. }
  2002. ++group_end;
  2003. }
  2004. }
  2005. const bool append_next_rung = create_output_rung
  2006. || group_end + 1U == existing_logic->rungs.size();
  2007. const std::size_t rows_to_add = append_next_rung
  2008. ? (create_output_rung ? 2U : 1U) : 0U;
  2009. if (existing_logic->rungs.size() + rows_to_add
  2010. > project_service_.projectLimits().maximumRungsPerLogic
  2011. || totalRungCount(project_service_.project()) + rows_to_add
  2012. > ProjectLimits::kMaximumRungsPerProject)
  2013. {
  2014. return {failure(
  2015. LogicEditorError::InvalidOperation,
  2016. "输出后无法创建下一空行,梯形图行数已经达到当前上限"),
  2017. {}};
  2018. }
  2019. HistoryState before = captureState();
  2020. const bool modified_before = project_service_.isModified();
  2021. Project &project = project_service_.editProject();
  2022. ControlLogic *logic = editableLogic(&project, logic_id);
  2023. if (create_output_rung)
  2024. {
  2025. target_rung_id = insertEmptyRungAt(logic, logic->rungs.size());
  2026. target_index = logic->rungs.size() - 1U;
  2027. group_end = target_index;
  2028. }
  2029. LadderRung *rung = editableRung(logic, target_rung_id);
  2030. int rightmost_content = -1;
  2031. for (int column = 0;
  2032. column < ProjectLimits::kMaximumConditionColumns;
  2033. ++column)
  2034. {
  2035. if (rung->cells[static_cast<std::size_t>(column)].kind
  2036. != LadderCellKind::Gap)
  2037. {
  2038. rightmost_content = column;
  2039. }
  2040. }
  2041. // 空网络直接输出时补满横线;已有内容时只补尾部,不跨越中间断点
  2042. for (int column = rightmost_content + 1;
  2043. column < ProjectLimits::kMaximumConditionColumns;
  2044. ++column)
  2045. {
  2046. LadderCell &cell = rung->cells[static_cast<std::size_t>(column)];
  2047. cell.kind = LadderCellKind::Wire;
  2048. cell.node.reset();
  2049. }
  2050. const std::string node_id = makeUniqueId(*logic, nodePrefix(config));
  2051. rung->output = LogicNode{node_id, config, configured};
  2052. std::string next_rung_id;
  2053. if (append_next_rung)
  2054. {
  2055. next_rung_id = insertEmptyRungAt(logic, logic->rungs.size());
  2056. }
  2057. else
  2058. {
  2059. next_rung_id = logic->rungs[group_end + 1U].id;
  2060. }
  2061. if (!logic->validateStructure(project_service_.projectLimits(), &error))
  2062. {
  2063. rollbackEdit(std::move(before), modified_before);
  2064. return {failure(LogicEditorError::InvalidNode, error), {}};
  2065. }
  2066. recordHistory(std::move(before));
  2067. return {
  2068. {true, LogicEditorError::None, {}, node_id},
  2069. {next_rung_id, 0, false}};
  2070. }
  2071. LogicEditorResult LogicEditorService::updateNodeConfig(
  2072. const std::string &logic_id,
  2073. const std::string &node_id,
  2074. const LogicNodeConfig &config)
  2075. {
  2076. const LogicNode *node = findNode(logic_id, node_id);
  2077. if (node == nullptr)
  2078. {
  2079. return failure(LogicEditorError::NodeNotFound, "未找到逻辑节点");
  2080. }
  2081. if (node->isCondition() != isConditionConfig(config)
  2082. || node->isOutput() != isOutputConfig(config))
  2083. {
  2084. return failure(
  2085. LogicEditorError::UnsupportedNodeChange,
  2086. "条件节点和输出节点不能互相改型");
  2087. }
  2088. LogicNode candidate{node_id, config, true};
  2089. std::string error;
  2090. if (!candidate.validate(&error))
  2091. {
  2092. return failure(LogicEditorError::InvalidNode, error);
  2093. }
  2094. HistoryState before = captureState();
  2095. Project &project = project_service_.editProject();
  2096. ControlLogic *logic = editableLogic(&project, logic_id);
  2097. for (LadderRung &rung : logic->rungs)
  2098. {
  2099. for (LadderCell &cell : rung.cells)
  2100. {
  2101. if (cell.node.has_value() && cell.node->id == node_id)
  2102. {
  2103. cell.node->config = config;
  2104. cell.node->configured = true;
  2105. recordHistory(std::move(before));
  2106. return {true, LogicEditorError::None, {}, node_id};
  2107. }
  2108. }
  2109. if (rung.output.has_value() && rung.output->id == node_id)
  2110. {
  2111. rung.output->config = config;
  2112. rung.output->configured = true;
  2113. recordHistory(std::move(before));
  2114. return {true, LogicEditorError::None, {}, node_id};
  2115. }
  2116. }
  2117. return failure(LogicEditorError::NodeNotFound, "未找到逻辑节点");
  2118. }
  2119. LogicClipboardCopyResult LogicEditorService::copySelection(
  2120. const std::string &logic_id,
  2121. const LogicSelectionCopyRequest &selection) const
  2122. {
  2123. const ControlLogic *logic = findLogic(logic_id);
  2124. if (logic == nullptr)
  2125. {
  2126. return {failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"), {}};
  2127. }
  2128. const bool has_grid_objects = !selection.cells.empty()
  2129. || !selection.outputRungIds.empty()
  2130. || !selection.verticalConnectionIds.empty();
  2131. if (!selection.wholeRungIds.empty())
  2132. {
  2133. if (has_grid_objects)
  2134. {
  2135. return {failure(
  2136. LogicEditorError::InvalidOperation,
  2137. "整行选择不能和网格对象混合复制"), {}};
  2138. }
  2139. std::unordered_set<std::string> unique_ids;
  2140. std::vector<std::size_t> indices;
  2141. indices.reserve(selection.wholeRungIds.size());
  2142. for (const std::string &rung_id : selection.wholeRungIds)
  2143. {
  2144. const std::size_t index = rungIndex(*logic, rung_id);
  2145. if (!unique_ids.insert(rung_id).second
  2146. || index == logic->rungs.size())
  2147. {
  2148. return {failure(
  2149. LogicEditorError::InvalidOperation,
  2150. "整行复制选择中存在重复或失效的行"), {}};
  2151. }
  2152. indices.push_back(index);
  2153. }
  2154. std::sort(indices.begin(), indices.end());
  2155. for (std::size_t index = 1U; index < indices.size(); ++index)
  2156. {
  2157. if (indices[index] != indices[index - 1U] + 1U)
  2158. {
  2159. return {failure(
  2160. LogicEditorError::InvalidOperation,
  2161. "整行复制只支持连续的视觉行"), {}};
  2162. }
  2163. }
  2164. LogicClipboardFragment fragment;
  2165. fragment.mode = LogicClipboardMode::WholeRows;
  2166. fragment.rowSpan = static_cast<int>(indices.size());
  2167. fragment.columnSpan = ProjectLimits::kMaximumLadderColumns;
  2168. fragment.rows.reserve(indices.size());
  2169. for (std::size_t index : indices)
  2170. {
  2171. const LadderRung &rung = logic->rungs[index];
  2172. fragment.rows.push_back({rung.comment, rung.cells, rung.output});
  2173. }
  2174. const std::size_t first = indices.front();
  2175. const std::size_t last = indices.back();
  2176. for (const VerticalConnection &connection : logic->verticalConnections)
  2177. {
  2178. const std::size_t upper = rungIndex(*logic, connection.upperRungId);
  2179. const std::size_t lower = rungIndex(*logic, connection.lowerRungId);
  2180. if (upper >= first && lower <= last && lower == upper + 1U)
  2181. {
  2182. fragment.verticalConnections.push_back({
  2183. static_cast<int>(upper - first),
  2184. connection.columnBoundary});
  2185. }
  2186. }
  2187. return {{
  2188. true,
  2189. LogicEditorError::None,
  2190. {},
  2191. logic->rungs[first].id}, std::move(fragment)};
  2192. }
  2193. if (!has_grid_objects)
  2194. {
  2195. return {failure(
  2196. LogicEditorError::InvalidOperation,
  2197. "请先选择横线、指令、输出或竖线"), {}};
  2198. }
  2199. LogicClipboardFragment fragment;
  2200. fragment.mode = LogicClipboardMode::GridObjects;
  2201. std::size_t minimum_row = logic->rungs.size();
  2202. std::size_t maximum_row = 0U;
  2203. int minimum_column = ProjectLimits::kMaximumLadderColumns;
  2204. int maximum_column = 0;
  2205. const auto include_position = [
  2206. &minimum_row,
  2207. &maximum_row,
  2208. &minimum_column,
  2209. &maximum_column](std::size_t row, int column)
  2210. {
  2211. minimum_row = std::min(minimum_row, row);
  2212. maximum_row = std::max(maximum_row, row);
  2213. minimum_column = std::min(minimum_column, column);
  2214. maximum_column = std::max(maximum_column, column);
  2215. };
  2216. std::unordered_set<std::string> unique_cells;
  2217. for (const auto &position : selection.cells)
  2218. {
  2219. const std::size_t row = rungIndex(*logic, position.first);
  2220. const LadderCell *cell = findCell(
  2221. logic_id, position.first, position.second);
  2222. const std::string key = position.first + ":"
  2223. + std::to_string(position.second);
  2224. if (row == logic->rungs.size() || cell == nullptr
  2225. || !unique_cells.insert(key).second
  2226. || cell->kind == LadderCellKind::Gap)
  2227. {
  2228. return {failure(
  2229. LogicEditorError::InvalidOperation,
  2230. "复制选择中存在重复、空白或失效的网格"), {}};
  2231. }
  2232. fragment.cells.push_back({
  2233. static_cast<int>(row),
  2234. position.second,
  2235. cell->kind,
  2236. cell->node});
  2237. include_position(row, position.second);
  2238. }
  2239. std::unordered_set<std::string> unique_outputs;
  2240. for (const std::string &rung_id : selection.outputRungIds)
  2241. {
  2242. const std::size_t row = rungIndex(*logic, rung_id);
  2243. const LadderRung *rung = findRung(logic_id, rung_id);
  2244. if (row == logic->rungs.size() || rung == nullptr
  2245. || !rung->output.has_value()
  2246. || !unique_outputs.insert(rung_id).second)
  2247. {
  2248. return {failure(
  2249. LogicEditorError::InvalidOperation,
  2250. "复制选择中存在重复、空白或失效的输出槽"), {}};
  2251. }
  2252. fragment.outputs.push_back({
  2253. static_cast<int>(row),
  2254. ProjectLimits::kMaximumConditionColumns,
  2255. *rung->output});
  2256. include_position(row, ProjectLimits::kMaximumConditionColumns);
  2257. }
  2258. std::unordered_set<std::string> unique_connections;
  2259. for (const std::string &connection_id : selection.verticalConnectionIds)
  2260. {
  2261. const VerticalConnection *connection = findConnection(
  2262. logic_id, connection_id);
  2263. if (connection == nullptr
  2264. || !unique_connections.insert(connection_id).second)
  2265. {
  2266. return {failure(
  2267. LogicEditorError::InvalidOperation,
  2268. "复制选择中存在重复或失效的竖线"), {}};
  2269. }
  2270. const std::size_t upper = rungIndex(*logic, connection->upperRungId);
  2271. const std::size_t lower = rungIndex(*logic, connection->lowerRungId);
  2272. if (upper == logic->rungs.size() || lower != upper + 1U)
  2273. {
  2274. return {failure(
  2275. LogicEditorError::InvalidOperation,
  2276. "复制选择中存在悬空竖线"), {}};
  2277. }
  2278. fragment.verticalConnections.push_back({
  2279. static_cast<int>(upper), connection->columnBoundary});
  2280. include_position(upper, connection->columnBoundary);
  2281. include_position(lower, connection->columnBoundary);
  2282. }
  2283. for (LogicClipboardCell &cell : fragment.cells)
  2284. {
  2285. cell.relativeRow -= static_cast<int>(minimum_row);
  2286. cell.relativeColumn -= minimum_column;
  2287. }
  2288. for (LogicClipboardOutput &output : fragment.outputs)
  2289. {
  2290. output.relativeRow -= static_cast<int>(minimum_row);
  2291. output.relativeColumn -= minimum_column;
  2292. }
  2293. for (LogicClipboardVerticalConnection &connection
  2294. : fragment.verticalConnections)
  2295. {
  2296. connection.upperRelativeRow -= static_cast<int>(minimum_row);
  2297. connection.relativeColumnBoundary -= minimum_column;
  2298. }
  2299. fragment.rowSpan = static_cast<int>(maximum_row - minimum_row + 1U);
  2300. fragment.columnSpan = maximum_column - minimum_column + 1;
  2301. std::sort(
  2302. fragment.cells.begin(), fragment.cells.end(),
  2303. [](const LogicClipboardCell &left, const LogicClipboardCell &right)
  2304. {
  2305. return left.relativeRow != right.relativeRow
  2306. ? left.relativeRow < right.relativeRow
  2307. : left.relativeColumn < right.relativeColumn;
  2308. });
  2309. std::sort(
  2310. fragment.outputs.begin(), fragment.outputs.end(),
  2311. [](const LogicClipboardOutput &left, const LogicClipboardOutput &right)
  2312. {
  2313. return left.relativeRow < right.relativeRow;
  2314. });
  2315. std::sort(
  2316. fragment.verticalConnections.begin(),
  2317. fragment.verticalConnections.end(),
  2318. [](const LogicClipboardVerticalConnection &left,
  2319. const LogicClipboardVerticalConnection &right)
  2320. {
  2321. return left.upperRelativeRow != right.upperRelativeRow
  2322. ? left.upperRelativeRow < right.upperRelativeRow
  2323. : left.relativeColumnBoundary
  2324. < right.relativeColumnBoundary;
  2325. });
  2326. return {{true, LogicEditorError::None, {}, {}}, std::move(fragment)};
  2327. }
  2328. LogicClipboardPasteResult LogicEditorService::pasteClipboard(
  2329. const std::string &logic_id,
  2330. const LogicClipboardFragment &fragment,
  2331. const LogicPasteTarget &target)
  2332. {
  2333. const ControlLogic *logic = findLogic(logic_id);
  2334. if (logic == nullptr)
  2335. {
  2336. return {failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"), {}, {}};
  2337. }
  2338. if (fragment.mode == LogicClipboardMode::WholeRows)
  2339. {
  2340. if (fragment.rows.empty() || !fragment.cells.empty()
  2341. || !fragment.outputs.empty())
  2342. {
  2343. return {failure(
  2344. LogicEditorError::InvalidOperation,
  2345. "整行剪贴板内容无效"), {}, {}};
  2346. }
  2347. if (logic->rungs.size() + fragment.rows.size()
  2348. > project_service_.projectLimits().maximumRungsPerLogic
  2349. || totalRungCount(project_service_.project()) + fragment.rows.size()
  2350. > ProjectLimits::kMaximumRungsPerProject)
  2351. {
  2352. return {failure(
  2353. LogicEditorError::InvalidOperation,
  2354. "粘贴整行后将超过梯形图行数上限"), {}, {}};
  2355. }
  2356. std::size_t position = 0U;
  2357. if (!logic->rungs.empty())
  2358. {
  2359. const std::size_t reference = rungIndex(*logic, target.rungId);
  2360. if (reference == logic->rungs.size())
  2361. {
  2362. return {failure(
  2363. LogicEditorError::RungNotFound,
  2364. "请先选择整行粘贴位置"), {}, {}};
  2365. }
  2366. position = reference + 1U;
  2367. }
  2368. for (const LogicClipboardRow &row : fragment.rows)
  2369. {
  2370. if (row.cells.size()
  2371. != static_cast<std::size_t>(
  2372. ProjectLimits::kMaximumConditionColumns)
  2373. || containsLineBreak(row.comment)
  2374. || row.comment.size() > ProjectLimits::kMaximumRungCommentBytes)
  2375. {
  2376. return {failure(
  2377. LogicEditorError::InvalidOperation,
  2378. "复制的整行结构或注释无效"), {}, {}};
  2379. }
  2380. for (const LadderCell &cell : row.cells)
  2381. {
  2382. if (!cell.validate()
  2383. || (cell.node.has_value() && !cell.node->isCondition()))
  2384. {
  2385. return {failure(
  2386. LogicEditorError::InvalidNode,
  2387. "复制的整行包含无效条件"), {}, {}};
  2388. }
  2389. }
  2390. if (row.output.has_value()
  2391. && (!row.output->validate() || !row.output->isOutput()))
  2392. {
  2393. return {failure(
  2394. LogicEditorError::InvalidNode,
  2395. "复制的整行包含无效输出"), {}, {}};
  2396. }
  2397. }
  2398. std::unordered_set<std::string> unique_connections;
  2399. for (const LogicClipboardVerticalConnection &connection
  2400. : fragment.verticalConnections)
  2401. {
  2402. const std::string key = std::to_string(connection.upperRelativeRow)
  2403. + ":" + std::to_string(connection.relativeColumnBoundary);
  2404. if (connection.upperRelativeRow < 0
  2405. || connection.upperRelativeRow + 1
  2406. >= static_cast<int>(fragment.rows.size())
  2407. || connection.relativeColumnBoundary < 0
  2408. || connection.relativeColumnBoundary
  2409. > ProjectLimits::kMaximumConditionColumns
  2410. || !unique_connections.insert(key).second)
  2411. {
  2412. return {failure(
  2413. LogicEditorError::InvalidOperation,
  2414. "复制的整行包含无效竖线"), {}, {}};
  2415. }
  2416. }
  2417. HistoryState before = captureState();
  2418. const bool modified_before = project_service_.isModified();
  2419. Project &project = project_service_.editProject();
  2420. ControlLogic *editable = editableLogic(&project, logic_id);
  2421. std::vector<std::string> inserted_ids;
  2422. inserted_ids.reserve(fragment.rows.size());
  2423. for (std::size_t offset = 0U; offset < fragment.rows.size(); ++offset)
  2424. {
  2425. const std::string inserted_id = insertEmptyRungAt(
  2426. editable, position + offset);
  2427. LadderRung *destination = editableRung(editable, inserted_id);
  2428. const LogicClipboardRow &source = fragment.rows[offset];
  2429. destination->comment = source.comment;
  2430. for (std::size_t column = 0U; column < source.cells.size(); ++column)
  2431. {
  2432. destination->cells[column].kind = source.cells[column].kind;
  2433. destination->cells[column].node.reset();
  2434. if (source.cells[column].node.has_value())
  2435. {
  2436. const LogicNode &source_node = *source.cells[column].node;
  2437. destination->cells[column].node = LogicNode{
  2438. makeUniqueId(*editable, nodePrefix(source_node.config)),
  2439. source_node.config,
  2440. source_node.configured};
  2441. }
  2442. }
  2443. if (source.output.has_value())
  2444. {
  2445. destination->output = LogicNode{
  2446. makeUniqueId(*editable, nodePrefix(source.output->config)),
  2447. source.output->config,
  2448. source.output->configured};
  2449. }
  2450. inserted_ids.push_back(inserted_id);
  2451. }
  2452. for (const LogicClipboardVerticalConnection &source
  2453. : fragment.verticalConnections)
  2454. {
  2455. const std::string &upper = inserted_ids[
  2456. static_cast<std::size_t>(source.upperRelativeRow)];
  2457. const std::string &lower = inserted_ids[
  2458. static_cast<std::size_t>(source.upperRelativeRow + 1)];
  2459. const auto existing = std::find_if(
  2460. editable->verticalConnections.cbegin(),
  2461. editable->verticalConnections.cend(),
  2462. [&upper, &lower, &source](const VerticalConnection &connection)
  2463. {
  2464. return connectionMatches(
  2465. connection,
  2466. upper,
  2467. lower,
  2468. source.relativeColumnBoundary);
  2469. });
  2470. if (existing == editable->verticalConnections.cend())
  2471. {
  2472. editable->verticalConnections.push_back({
  2473. makeUniqueId(*editable, "vertical"),
  2474. upper,
  2475. lower,
  2476. source.relativeColumnBoundary});
  2477. }
  2478. }
  2479. std::string error;
  2480. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  2481. {
  2482. rollbackEdit(std::move(before), modified_before);
  2483. return {failure(LogicEditorError::InvalidOperation, error), {}, {}};
  2484. }
  2485. refreshRungNames(editable);
  2486. recordHistory(std::move(before));
  2487. return {{
  2488. true,
  2489. LogicEditorError::None,
  2490. {},
  2491. inserted_ids.front()}, {}, std::move(inserted_ids)};
  2492. }
  2493. if (!fragment.rows.empty()
  2494. || (fragment.cells.empty() && fragment.outputs.empty()
  2495. && fragment.verticalConnections.empty())
  2496. || fragment.rowSpan <= 0 || fragment.columnSpan <= 0)
  2497. {
  2498. return {failure(
  2499. LogicEditorError::InvalidOperation,
  2500. "网格剪贴板内容无效"), {}, {}};
  2501. }
  2502. const std::size_t target_row = rungIndex(*logic, target.rungId);
  2503. if (target_row == logic->rungs.size())
  2504. {
  2505. return {failure(
  2506. LogicEditorError::RungNotFound,
  2507. "请先选择粘贴目标"), {}, {}};
  2508. }
  2509. const bool only_vertical = fragment.cells.empty()
  2510. && fragment.outputs.empty();
  2511. const bool only_output = fragment.cells.empty()
  2512. && fragment.verticalConnections.empty();
  2513. if ((only_vertical && (!target.boundary || target.output))
  2514. || (only_output && (!target.output || target.boundary)))
  2515. {
  2516. return {failure(
  2517. LogicEditorError::InvalidOperation,
  2518. "剪贴板对象类型与当前粘贴位置不匹配"), {}, {}};
  2519. }
  2520. if (target.column < 0
  2521. || target.column > ProjectLimits::kMaximumConditionColumns
  2522. || target_row + static_cast<std::size_t>(fragment.rowSpan)
  2523. > logic->rungs.size())
  2524. {
  2525. return {failure(
  2526. LogicEditorError::InvalidOperation,
  2527. "粘贴片段超出当前梯形图行列范围"), {}, {}};
  2528. }
  2529. std::unordered_set<std::string> destination_cells;
  2530. for (const LogicClipboardCell &source : fragment.cells)
  2531. {
  2532. const int column = target.column + source.relativeColumn;
  2533. const std::size_t row = target_row
  2534. + static_cast<std::size_t>(source.relativeRow);
  2535. const std::string key = std::to_string(row) + ":"
  2536. + std::to_string(column);
  2537. if (source.relativeRow < 0
  2538. || source.relativeRow >= fragment.rowSpan
  2539. || source.relativeColumn < 0
  2540. || column < 0
  2541. || column >= ProjectLimits::kMaximumConditionColumns
  2542. || !destination_cells.insert(key).second
  2543. || (source.kind != LadderCellKind::Wire
  2544. && source.kind != LadderCellKind::Node)
  2545. || (source.kind == LadderCellKind::Node
  2546. && (!source.node.has_value()
  2547. || !source.node->validate()
  2548. || !source.node->isCondition()))
  2549. || (source.kind == LadderCellKind::Wire
  2550. && source.node.has_value()))
  2551. {
  2552. return {failure(
  2553. LogicEditorError::InvalidOperation,
  2554. "复制片段包含无效或越界的条件网格"), {}, {}};
  2555. }
  2556. const LadderCell &destination = logic->rungs[row].cells[
  2557. static_cast<std::size_t>(column)];
  2558. if (destination.kind == LadderCellKind::Node)
  2559. {
  2560. return {failure(
  2561. LogicEditorError::InvalidOperation,
  2562. "粘贴目标已有触点或比较指令,未执行任何修改"), {}, {}};
  2563. }
  2564. }
  2565. const bool explicit_output_replace = only_output
  2566. && fragment.outputs.size() == 1U && fragment.rowSpan == 1;
  2567. std::unordered_set<std::size_t> destination_outputs;
  2568. for (const LogicClipboardOutput &source : fragment.outputs)
  2569. {
  2570. const int column = target.column + source.relativeColumn;
  2571. const std::size_t row = target_row
  2572. + static_cast<std::size_t>(source.relativeRow);
  2573. if (source.relativeRow < 0
  2574. || source.relativeRow >= fragment.rowSpan
  2575. || source.relativeColumn < 0
  2576. || column != ProjectLimits::kMaximumConditionColumns
  2577. || !destination_outputs.insert(row).second
  2578. || !source.node.validate() || !source.node.isOutput())
  2579. {
  2580. return {failure(
  2581. LogicEditorError::InvalidOperation,
  2582. "复制片段包含无效或错位的输出"), {}, {}};
  2583. }
  2584. if (logic->rungs[row].output.has_value() && !explicit_output_replace)
  2585. {
  2586. return {failure(
  2587. LogicEditorError::InvalidOperation,
  2588. "混合片段的目标输出槽已有指令,未执行任何修改"), {}, {}};
  2589. }
  2590. }
  2591. std::unordered_set<std::string> destination_connections;
  2592. for (const LogicClipboardVerticalConnection &source
  2593. : fragment.verticalConnections)
  2594. {
  2595. const int boundary = target.column + source.relativeColumnBoundary;
  2596. const std::size_t upper = target_row
  2597. + static_cast<std::size_t>(source.upperRelativeRow);
  2598. const std::string key = std::to_string(upper) + ":"
  2599. + std::to_string(boundary);
  2600. if (source.upperRelativeRow < 0
  2601. || source.upperRelativeRow + 1 >= fragment.rowSpan
  2602. || source.relativeColumnBoundary < 0
  2603. || upper + 1U >= logic->rungs.size()
  2604. || boundary < 0
  2605. || boundary > ProjectLimits::kMaximumConditionColumns
  2606. || !destination_connections.insert(key).second)
  2607. {
  2608. return {failure(
  2609. LogicEditorError::InvalidOperation,
  2610. "复制片段包含无效、重复或悬空的竖线"), {}, {}};
  2611. }
  2612. }
  2613. HistoryState before = captureState();
  2614. const bool modified_before = project_service_.isModified();
  2615. Project &project = project_service_.editProject();
  2616. ControlLogic *editable = editableLogic(&project, logic_id);
  2617. LogicClipboardPasteResult result;
  2618. for (const LogicClipboardCell &source : fragment.cells)
  2619. {
  2620. const std::size_t row = target_row
  2621. + static_cast<std::size_t>(source.relativeRow);
  2622. const int column = target.column + source.relativeColumn;
  2623. LadderCell &destination = editable->rungs[row].cells[
  2624. static_cast<std::size_t>(column)];
  2625. destination.kind = source.kind;
  2626. destination.node.reset();
  2627. if (source.node.has_value())
  2628. {
  2629. const std::string node_id = makeUniqueId(
  2630. *editable, nodePrefix(source.node->config));
  2631. destination.node = LogicNode{
  2632. node_id, source.node->config, source.node->configured};
  2633. if (result.edit.id.empty())
  2634. {
  2635. result.edit.id = node_id;
  2636. }
  2637. }
  2638. result.selection.cells.emplace_back(editable->rungs[row].id, column);
  2639. if (result.edit.id.empty())
  2640. {
  2641. result.edit.id = destination.id;
  2642. }
  2643. }
  2644. for (const LogicClipboardOutput &source : fragment.outputs)
  2645. {
  2646. const std::size_t row = target_row
  2647. + static_cast<std::size_t>(source.relativeRow);
  2648. const std::string node_id = makeUniqueId(
  2649. *editable, nodePrefix(source.node.config));
  2650. editable->rungs[row].output = LogicNode{
  2651. node_id, source.node.config, source.node.configured};
  2652. result.selection.outputRungIds.push_back(editable->rungs[row].id);
  2653. if (result.edit.id.empty())
  2654. {
  2655. result.edit.id = node_id;
  2656. }
  2657. }
  2658. for (const LogicClipboardVerticalConnection &source
  2659. : fragment.verticalConnections)
  2660. {
  2661. const std::size_t upper = target_row
  2662. + static_cast<std::size_t>(source.upperRelativeRow);
  2663. const int boundary = target.column + source.relativeColumnBoundary;
  2664. const std::string &upper_id = editable->rungs[upper].id;
  2665. const std::string &lower_id = editable->rungs[upper + 1U].id;
  2666. auto existing = std::find_if(
  2667. editable->verticalConnections.begin(),
  2668. editable->verticalConnections.end(),
  2669. [&upper_id, &lower_id, boundary](const VerticalConnection &connection)
  2670. {
  2671. return connectionMatches(
  2672. connection, upper_id, lower_id, boundary);
  2673. });
  2674. if (existing == editable->verticalConnections.end())
  2675. {
  2676. editable->verticalConnections.push_back({
  2677. makeUniqueId(*editable, "vertical"),
  2678. upper_id,
  2679. lower_id,
  2680. boundary});
  2681. existing = std::prev(editable->verticalConnections.end());
  2682. }
  2683. result.selection.verticalConnectionIds.push_back(existing->id);
  2684. if (result.edit.id.empty())
  2685. {
  2686. result.edit.id = existing->id;
  2687. }
  2688. }
  2689. std::string error;
  2690. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  2691. {
  2692. rollbackEdit(std::move(before), modified_before);
  2693. return {failure(LogicEditorError::InvalidOperation, error), {}, {}};
  2694. }
  2695. recordHistory(std::move(before));
  2696. result.edit.succeeded = true;
  2697. result.edit.error = LogicEditorError::None;
  2698. return result;
  2699. }
  2700. LogicEditorResult LogicEditorService::pasteConditionNodes(
  2701. const std::string &logic_id,
  2702. const std::string &rung_id,
  2703. const std::vector<LogicNode> &nodes,
  2704. int start_column)
  2705. {
  2706. const ControlLogic *logic = findLogic(logic_id);
  2707. const LadderRung *rung = findRung(logic_id, rung_id);
  2708. if (logic == nullptr || rung == nullptr)
  2709. {
  2710. return failure(LogicEditorError::RungNotFound, "未找到粘贴目标行");
  2711. }
  2712. if (nodes.empty()
  2713. || nodes.size()
  2714. > static_cast<std::size_t>(ProjectLimits::kMaximumConditionColumns))
  2715. {
  2716. return failure(LogicEditorError::InvalidOperation, "复制的条件数量无效");
  2717. }
  2718. for (const LogicNode &node : nodes)
  2719. {
  2720. if (!node.validate() || !node.isCondition())
  2721. {
  2722. return failure(LogicEditorError::InvalidNode, "只能粘贴有效的条件节点");
  2723. }
  2724. }
  2725. if (start_column < 0)
  2726. {
  2727. for (int candidate = 0;
  2728. candidate + static_cast<int>(nodes.size())
  2729. <= ProjectLimits::kMaximumConditionColumns;
  2730. ++candidate)
  2731. {
  2732. bool available = true;
  2733. for (std::size_t offset = 0U; offset < nodes.size(); ++offset)
  2734. {
  2735. available = available
  2736. && rung->cells[static_cast<std::size_t>(candidate) + offset].kind
  2737. == LadderCellKind::Gap;
  2738. }
  2739. if (available)
  2740. {
  2741. start_column = candidate;
  2742. break;
  2743. }
  2744. }
  2745. }
  2746. if (start_column < 0
  2747. || start_column + static_cast<int>(nodes.size())
  2748. > ProjectLimits::kMaximumConditionColumns)
  2749. {
  2750. return failure(LogicEditorError::InvalidOperation, "目标行没有足够连续空格");
  2751. }
  2752. HistoryState before = captureState();
  2753. const bool modified_before = project_service_.isModified();
  2754. Project &project = project_service_.editProject();
  2755. ControlLogic *editable = editableLogic(&project, logic_id);
  2756. LadderRung *target = editableRung(editable, rung_id);
  2757. std::string first_id;
  2758. for (std::size_t offset = 0U; offset < nodes.size(); ++offset)
  2759. {
  2760. LadderCell &cell = target->cells[
  2761. static_cast<std::size_t>(start_column) + offset];
  2762. const std::string id = makeUniqueId(
  2763. *editable, nodePrefix(nodes[offset].config));
  2764. cell.kind = LadderCellKind::Node;
  2765. cell.node = LogicNode{id, nodes[offset].config, nodes[offset].configured};
  2766. if (first_id.empty())
  2767. {
  2768. first_id = id;
  2769. }
  2770. }
  2771. std::string error;
  2772. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  2773. {
  2774. rollbackEdit(std::move(before), modified_before);
  2775. return failure(LogicEditorError::InvalidOperation, error);
  2776. }
  2777. recordHistory(std::move(before));
  2778. return {true, LogicEditorError::None, {}, first_id};
  2779. }
  2780. bool LogicEditorService::areConditionNodesContiguous(
  2781. const std::string &logic_id,
  2782. const std::string &rung_id,
  2783. const std::vector<std::string> &node_ids) const
  2784. {
  2785. const LadderRung *rung = findRung(logic_id, rung_id);
  2786. if (rung == nullptr || node_ids.empty())
  2787. {
  2788. return false;
  2789. }
  2790. std::unordered_set<std::string> requested(
  2791. node_ids.cbegin(), node_ids.cend());
  2792. if (requested.size() != node_ids.size())
  2793. {
  2794. return false;
  2795. }
  2796. std::vector<int> columns;
  2797. for (int column = 0; column < ProjectLimits::kMaximumConditionColumns; ++column)
  2798. {
  2799. const LadderCell &cell = rung->cells[static_cast<std::size_t>(column)];
  2800. if (cell.node.has_value() && requested.count(cell.node->id) != 0U)
  2801. {
  2802. columns.push_back(column);
  2803. }
  2804. }
  2805. return columns.size() == node_ids.size()
  2806. && columns.back() - columns.front() + 1
  2807. == static_cast<int>(columns.size());
  2808. }
  2809. LogicEditorResult LogicEditorService::pasteRung(
  2810. const std::string &logic_id,
  2811. const LadderRung &source)
  2812. {
  2813. const ControlLogic *logic = findLogic(logic_id);
  2814. std::string error;
  2815. if (logic == nullptr)
  2816. {
  2817. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  2818. }
  2819. if (!source.validateStructure(&error))
  2820. {
  2821. return failure(LogicEditorError::InvalidOperation, error);
  2822. }
  2823. if (logic->rungs.size()
  2824. >= project_service_.projectLimits().maximumRungsPerLogic
  2825. || totalRungCount(project_service_.project())
  2826. >= ProjectLimits::kMaximumRungsPerProject)
  2827. {
  2828. return failure(LogicEditorError::InvalidOperation, "梯形图行数已经达到上限");
  2829. }
  2830. HistoryState before = captureState();
  2831. const bool modified_before = project_service_.isModified();
  2832. Project &project = project_service_.editProject();
  2833. ControlLogic *editable = editableLogic(&project, logic_id);
  2834. LadderRung pasted = makeEmptyRung(*editable, editable->rungs.size());
  2835. pasted.comment = source.comment;
  2836. for (std::size_t column = 0U; column < source.cells.size(); ++column)
  2837. {
  2838. pasted.cells[column].kind = source.cells[column].kind;
  2839. if (source.cells[column].node.has_value())
  2840. {
  2841. const LogicNode &source_node = *source.cells[column].node;
  2842. pasted.cells[column].node = LogicNode{
  2843. makeUniqueId(*editable, nodePrefix(source_node.config)),
  2844. source_node.config,
  2845. source_node.configured};
  2846. }
  2847. }
  2848. if (source.output.has_value())
  2849. {
  2850. pasted.output = LogicNode{
  2851. makeUniqueId(*editable, nodePrefix(source.output->config)),
  2852. source.output->config,
  2853. source.output->configured};
  2854. }
  2855. const std::string pasted_id = pasted.id;
  2856. editable->rungs.push_back(std::move(pasted));
  2857. refreshRungNames(editable);
  2858. if (!editable->validateStructure(project_service_.projectLimits(), &error))
  2859. {
  2860. rollbackEdit(std::move(before), modified_before);
  2861. return failure(LogicEditorError::InvalidOperation, error);
  2862. }
  2863. recordHistory(std::move(before));
  2864. return {true, LogicEditorError::None, {}, pasted_id};
  2865. }
  2866. LogicEditorResult LogicEditorService::removeNode(
  2867. const std::string &logic_id, const std::string &node_id)
  2868. {
  2869. return removeNodes(logic_id, {node_id});
  2870. }
  2871. LogicEditorResult LogicEditorService::removeNodes(
  2872. const std::string &logic_id,
  2873. const std::vector<std::string> &node_ids)
  2874. {
  2875. if (findLogic(logic_id) == nullptr)
  2876. {
  2877. return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑");
  2878. }
  2879. if (node_ids.empty())
  2880. {
  2881. return failure(LogicEditorError::InvalidOperation, "请先选择要删除的节点");
  2882. }
  2883. std::unordered_set<std::string> ids;
  2884. for (const std::string &id : node_ids)
  2885. {
  2886. if (!ids.insert(id).second || findNode(logic_id, id) == nullptr)
  2887. {
  2888. return failure(
  2889. LogicEditorError::NodeNotFound,
  2890. "节点删除列表包含重复或不存在的对象");
  2891. }
  2892. }
  2893. HistoryState before = captureState();
  2894. Project &project = project_service_.editProject();
  2895. ControlLogic *logic = editableLogic(&project, logic_id);
  2896. for (LadderRung &rung : logic->rungs)
  2897. {
  2898. for (LadderCell &cell : rung.cells)
  2899. {
  2900. if (cell.node.has_value() && ids.count(cell.node->id) != 0U)
  2901. {
  2902. cell.kind = LadderCellKind::Gap;
  2903. cell.node.reset();
  2904. }
  2905. }
  2906. if (rung.output.has_value() && ids.count(rung.output->id) != 0U)
  2907. {
  2908. rung.output.reset();
  2909. }
  2910. }
  2911. recordHistory(std::move(before));
  2912. return {true, LogicEditorError::None, {}, node_ids.front()};
  2913. }
  2914. bool LogicEditorService::canUndo() const
  2915. {
  2916. return history_.canUndo();
  2917. }
  2918. bool LogicEditorService::canRedo() const
  2919. {
  2920. return history_.canRedo();
  2921. }
  2922. LogicEditorResult LogicEditorService::undo()
  2923. {
  2924. const std::optional<HistoryState> target = history_.undo(captureState());
  2925. if (!target.has_value())
  2926. {
  2927. return historyFailure("没有可撤销的梯形图编辑操作");
  2928. }
  2929. project_service_.editProject().controlLogics = target->logics;
  2930. return {true, LogicEditorError::None, {}, {}};
  2931. }
  2932. LogicEditorResult LogicEditorService::redo()
  2933. {
  2934. const std::optional<HistoryState> target = history_.redo(captureState());
  2935. if (!target.has_value())
  2936. {
  2937. return historyFailure("没有可重做的梯形图编辑操作");
  2938. }
  2939. project_service_.editProject().controlLogics = target->logics;
  2940. return {true, LogicEditorError::None, {}, {}};
  2941. }
  2942. void LogicEditorService::clearHistory()
  2943. {
  2944. history_.clear();
  2945. }
  2946. LogicEditResult LogicEditorService::applyCellAndAdvance(
  2947. const std::string &logic_id,
  2948. const LogicEditCursor &cursor,
  2949. const LogicNodeConfig *config,
  2950. bool configured)
  2951. {
  2952. const ControlLogic *existing_logic = findLogic(logic_id);
  2953. if (existing_logic == nullptr)
  2954. {
  2955. return {failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"), {}};
  2956. }
  2957. if (cursor.output || cursor.column < 0
  2958. || cursor.column >= ProjectLimits::kMaximumConditionColumns)
  2959. {
  2960. return {failure(LogicEditorError::CellNotFound,
  2961. "条件指令只能放在第 1~10 列"), {}};
  2962. }
  2963. if (config != nullptr)
  2964. {
  2965. if (!isConditionConfig(*config))
  2966. {
  2967. return {failure(LogicEditorError::InvalidNode,
  2968. "条件区只能放置条件指令"), {}};
  2969. }
  2970. LogicNode candidate{"candidate", *config, configured};
  2971. std::string candidate_error;
  2972. if (!candidate.validate(&candidate_error))
  2973. {
  2974. return {failure(LogicEditorError::InvalidNode, candidate_error), {}};
  2975. }
  2976. }
  2977. std::string target_rung_id = cursor.rungId;
  2978. int target_column = cursor.column;
  2979. const bool create_first_rung = target_rung_id.empty();
  2980. if (create_first_rung)
  2981. {
  2982. if (!existing_logic->rungs.empty())
  2983. {
  2984. return {failure(LogicEditorError::RungNotFound,
  2985. "请先选择要编辑的梯形图行"), {}};
  2986. }
  2987. if (existing_logic->rungs.size()
  2988. >= project_service_.projectLimits().maximumRungsPerLogic
  2989. || totalRungCount(project_service_.project())
  2990. >= ProjectLimits::kMaximumRungsPerProject)
  2991. {
  2992. return {failure(LogicEditorError::InvalidOperation,
  2993. "梯形图行数已经达到当前上限"), {}};
  2994. }
  2995. target_column = 0;
  2996. }
  2997. else
  2998. {
  2999. const LadderCell *existing_cell = findCell(
  3000. logic_id, target_rung_id, target_column);
  3001. if (existing_cell == nullptr)
  3002. {
  3003. return {failure(LogicEditorError::CellNotFound,
  3004. "未找到目标条件网格"), {}};
  3005. }
  3006. if (config == nullptr && existing_cell->kind == LadderCellKind::Node)
  3007. {
  3008. return {failure(LogicEditorError::InvalidOperation,
  3009. "横线不能覆盖已有条件指令"), {}};
  3010. }
  3011. }
  3012. HistoryState before = captureState();
  3013. const bool modified_before = project_service_.isModified();
  3014. Project &project = project_service_.editProject();
  3015. ControlLogic *logic = editableLogic(&project, logic_id);
  3016. if (create_first_rung)
  3017. {
  3018. target_rung_id = insertEmptyRungAt(logic, 0U);
  3019. }
  3020. LadderCell &cell = editableRung(logic, target_rung_id)
  3021. ->cells[static_cast<std::size_t>(target_column)];
  3022. std::string edited_id = cell.id;
  3023. if (config == nullptr)
  3024. {
  3025. cell.kind = LadderCellKind::Wire;
  3026. cell.node.reset();
  3027. }
  3028. else
  3029. {
  3030. edited_id = makeUniqueId(*logic, nodePrefix(*config));
  3031. cell.kind = LadderCellKind::Node;
  3032. cell.node = LogicNode{edited_id, *config, configured};
  3033. }
  3034. std::string error;
  3035. if (!logic->validateStructure(project_service_.projectLimits(), &error))
  3036. {
  3037. rollbackEdit(std::move(before), modified_before);
  3038. return {failure(LogicEditorError::InvalidOperation, error), {}};
  3039. }
  3040. recordHistory(std::move(before));
  3041. LogicEditCursor next{target_rung_id, target_column + 1, false};
  3042. if (next.column >= ProjectLimits::kMaximumConditionColumns)
  3043. {
  3044. next.column = ProjectLimits::kMaximumConditionColumns;
  3045. next.output = true;
  3046. }
  3047. return {
  3048. {true, LogicEditorError::None, {}, edited_id},
  3049. std::move(next)};
  3050. }
  3051. bool LogicEditorService::isConditionConfig(const LogicNodeConfig &config)
  3052. {
  3053. return std::holds_alternative<ContactNodeConfig>(config)
  3054. || std::holds_alternative<EdgeContactNodeConfig>(config)
  3055. || std::holds_alternative<CompareNodeConfig>(config);
  3056. }
  3057. bool LogicEditorService::isOutputConfig(const LogicNodeConfig &config)
  3058. {
  3059. return std::holds_alternative<CoilNodeConfig>(config)
  3060. || std::holds_alternative<MoveNodeConfig>(config)
  3061. || std::holds_alternative<ArithmeticNodeConfig>(config);
  3062. }
  3063. std::string LogicEditorService::nodePrefix(const LogicNodeConfig &config)
  3064. {
  3065. return std::visit(
  3066. [](const auto &value) -> std::string
  3067. {
  3068. using Config = std::decay_t<decltype(value)>;
  3069. if constexpr (std::is_same_v<Config, ContactNodeConfig>)
  3070. {
  3071. return "contact";
  3072. }
  3073. else if constexpr (std::is_same_v<Config, EdgeContactNodeConfig>)
  3074. {
  3075. return "edge";
  3076. }
  3077. else if constexpr (std::is_same_v<Config, CoilNodeConfig>)
  3078. {
  3079. return "coil";
  3080. }
  3081. else if constexpr (std::is_same_v<Config, MoveNodeConfig>)
  3082. {
  3083. return "move";
  3084. }
  3085. else if constexpr (std::is_same_v<Config, ArithmeticNodeConfig>)
  3086. {
  3087. return "arithmetic";
  3088. }
  3089. else
  3090. {
  3091. return "compare";
  3092. }
  3093. },
  3094. config);
  3095. }
  3096. std::string LogicEditorService::makeUniqueId(
  3097. const ControlLogic &logic, const std::string &prefix)
  3098. {
  3099. const auto exists = [&logic](const std::string &candidate)
  3100. {
  3101. if (logic.id == candidate)
  3102. {
  3103. return true;
  3104. }
  3105. for (const LadderRung &rung : logic.rungs)
  3106. {
  3107. if (rung.id == candidate
  3108. || (rung.output.has_value() && rung.output->id == candidate))
  3109. {
  3110. return true;
  3111. }
  3112. for (const LadderCell &cell : rung.cells)
  3113. {
  3114. if (cell.id == candidate
  3115. || (cell.node.has_value() && cell.node->id == candidate))
  3116. {
  3117. return true;
  3118. }
  3119. }
  3120. }
  3121. return std::any_of(
  3122. logic.verticalConnections.cbegin(),
  3123. logic.verticalConnections.cend(),
  3124. [&candidate](const VerticalConnection &connection)
  3125. {
  3126. return connection.id == candidate;
  3127. });
  3128. };
  3129. for (std::size_t index = 1U;; ++index)
  3130. {
  3131. const std::string candidate = prefix + '-' + std::to_string(index);
  3132. if (!exists(candidate))
  3133. {
  3134. return candidate;
  3135. }
  3136. }
  3137. }
  3138. LadderRung LogicEditorService::makeEmptyRung(
  3139. const ControlLogic &logic, std::size_t visual_index)
  3140. {
  3141. LadderRung rung;
  3142. rung.id = makeUniqueId(logic, "rung");
  3143. rung.name = "行 " + std::to_string(visual_index + 1U);
  3144. rung.cells.reserve(
  3145. static_cast<std::size_t>(ProjectLimits::kMaximumConditionColumns));
  3146. for (int column = 0;
  3147. column < ProjectLimits::kMaximumConditionColumns;
  3148. ++column)
  3149. {
  3150. rung.cells.push_back({
  3151. rung.id + "-cell-" + std::to_string(column + 1),
  3152. LadderCellKind::Gap,
  3153. std::nullopt});
  3154. }
  3155. return rung;
  3156. }
  3157. std::string LogicEditorService::insertEmptyRungAt(
  3158. ControlLogic *logic, std::size_t position)
  3159. {
  3160. if (logic == nullptr || position > logic->rungs.size())
  3161. {
  3162. return {};
  3163. }
  3164. const std::string upper_id = position > 0U
  3165. ? logic->rungs[position - 1U].id : std::string{};
  3166. const std::string lower_id = position < logic->rungs.size()
  3167. ? logic->rungs[position].id : std::string{};
  3168. std::vector<VerticalConnection> bridges;
  3169. if (!upper_id.empty() && !lower_id.empty())
  3170. {
  3171. for (const VerticalConnection &connection : logic->verticalConnections)
  3172. {
  3173. if (connection.upperRungId == upper_id
  3174. && connection.lowerRungId == lower_id)
  3175. {
  3176. bridges.push_back(connection);
  3177. }
  3178. }
  3179. logic->verticalConnections.erase(
  3180. std::remove_if(
  3181. logic->verticalConnections.begin(),
  3182. logic->verticalConnections.end(),
  3183. [&upper_id, &lower_id](const VerticalConnection &connection)
  3184. {
  3185. return connection.upperRungId == upper_id
  3186. && connection.lowerRungId == lower_id;
  3187. }),
  3188. logic->verticalConnections.end());
  3189. }
  3190. LadderRung rung = makeEmptyRung(*logic, position);
  3191. const std::string new_id = rung.id;
  3192. logic->rungs.insert(
  3193. logic->rungs.begin() + static_cast<std::ptrdiff_t>(position),
  3194. std::move(rung));
  3195. for (VerticalConnection &bridge : bridges)
  3196. {
  3197. bridge.lowerRungId = new_id;
  3198. logic->verticalConnections.push_back(bridge);
  3199. }
  3200. for (const VerticalConnection &bridge : bridges)
  3201. {
  3202. logic->verticalConnections.push_back({
  3203. makeUniqueId(*logic, "vertical"),
  3204. new_id,
  3205. lower_id,
  3206. bridge.columnBoundary});
  3207. }
  3208. refreshRungNames(logic);
  3209. return new_id;
  3210. }
  3211. void LogicEditorService::removeRungAt(
  3212. ControlLogic *logic, std::size_t position)
  3213. {
  3214. if (logic == nullptr || position >= logic->rungs.size())
  3215. {
  3216. return;
  3217. }
  3218. const std::string removed_id = logic->rungs[position].id;
  3219. const std::string upper_id = position > 0U
  3220. ? logic->rungs[position - 1U].id : std::string{};
  3221. const std::string lower_id = position + 1U < logic->rungs.size()
  3222. ? logic->rungs[position + 1U].id : std::string{};
  3223. std::map<int, std::string> upper_connections;
  3224. std::map<int, std::string> lower_connections;
  3225. for (const VerticalConnection &connection : logic->verticalConnections)
  3226. {
  3227. if (connection.upperRungId == upper_id
  3228. && connection.lowerRungId == removed_id)
  3229. {
  3230. upper_connections[connection.columnBoundary] = connection.id;
  3231. }
  3232. if (connection.upperRungId == removed_id
  3233. && connection.lowerRungId == lower_id)
  3234. {
  3235. lower_connections[connection.columnBoundary] = connection.id;
  3236. }
  3237. }
  3238. logic->verticalConnections.erase(
  3239. std::remove_if(
  3240. logic->verticalConnections.begin(),
  3241. logic->verticalConnections.end(),
  3242. [&removed_id](const VerticalConnection &connection)
  3243. {
  3244. return connection.upperRungId == removed_id
  3245. || connection.lowerRungId == removed_id;
  3246. }),
  3247. logic->verticalConnections.end());
  3248. logic->rungs.erase(
  3249. logic->rungs.begin() + static_cast<std::ptrdiff_t>(position));
  3250. if (!upper_id.empty() && !lower_id.empty())
  3251. {
  3252. for (const auto &upper : upper_connections)
  3253. {
  3254. if (lower_connections.count(upper.first) != 0U)
  3255. {
  3256. logic->verticalConnections.push_back({
  3257. upper.second,
  3258. upper_id,
  3259. lower_id,
  3260. upper.first});
  3261. }
  3262. }
  3263. }
  3264. refreshRungNames(logic);
  3265. }
  3266. void LogicEditorService::refreshRungNames(ControlLogic *logic)
  3267. {
  3268. if (logic == nullptr)
  3269. {
  3270. return;
  3271. }
  3272. for (std::size_t index = 0U; index < logic->rungs.size(); ++index)
  3273. {
  3274. logic->rungs[index].name = "行 " + std::to_string(index + 1U);
  3275. }
  3276. }
  3277. LogicEditorResult LogicEditorService::failure(
  3278. LogicEditorError error, const std::string &message)
  3279. {
  3280. return {false, error, message, {}};
  3281. }