symbiote-ui 0.3.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/LICENSE +21 -0
  3. package/README.md +76 -0
  4. package/canvas/AutoLayout.js +731 -0
  5. package/canvas/Breadcrumb/Breadcrumb.css.js +75 -0
  6. package/canvas/Breadcrumb/Breadcrumb.js +96 -0
  7. package/canvas/Breadcrumb/Breadcrumb.tpl.js +7 -0
  8. package/canvas/CanvasConnectionRenderer.js +971 -0
  9. package/canvas/CanvasGraph/CanvasGraph.css.js +29 -0
  10. package/canvas/CanvasGraph/CanvasGraph.js +1697 -0
  11. package/canvas/CanvasGraph/CanvasGraphDrawState.js +280 -0
  12. package/canvas/CanvasGraph/CanvasGraphGeometry.js +194 -0
  13. package/canvas/CanvasViewport.js +550 -0
  14. package/canvas/ConnectionRenderer.js +1283 -0
  15. package/canvas/FlowSimulator.js +326 -0
  16. package/canvas/ForceLayout.js +226 -0
  17. package/canvas/ForceWorker.js +1303 -0
  18. package/canvas/FrameManager.js +223 -0
  19. package/canvas/GraphExplorerShell/GraphExplorerShell.css.js +136 -0
  20. package/canvas/GraphExplorerShell/GraphExplorerShell.js +129 -0
  21. package/canvas/GraphExplorerShell/GraphExplorerShell.tpl.js +12 -0
  22. package/canvas/GraphTabs/GraphTabs.css.js +101 -0
  23. package/canvas/GraphTabs/GraphTabs.js +189 -0
  24. package/canvas/GraphTabs/GraphTabs.tpl.js +12 -0
  25. package/canvas/LODManager.js +88 -0
  26. package/canvas/Minimap/Minimap.css.js +73 -0
  27. package/canvas/Minimap/Minimap.js +210 -0
  28. package/canvas/Minimap/Minimap.tpl.js +7 -0
  29. package/canvas/NodeCanvas/NodeCanvas.css.js +398 -0
  30. package/canvas/NodeCanvas/NodeCanvas.js +1499 -0
  31. package/canvas/NodeCanvas/NodeCanvas.tpl.js +22 -0
  32. package/canvas/NodeSearch/NodeSearch.css.js +97 -0
  33. package/canvas/NodeSearch/NodeSearch.js +140 -0
  34. package/canvas/NodeSearch/NodeSearch.tpl.js +25 -0
  35. package/canvas/NodeViewManager.js +748 -0
  36. package/canvas/PcbRouteDiagnostics.js +463 -0
  37. package/canvas/PcbRouter.js +1127 -0
  38. package/canvas/PinExpansion.js +134 -0
  39. package/canvas/PseudoConnection.js +84 -0
  40. package/canvas/SelectionSync.js +163 -0
  41. package/canvas/SubgraphManager.js +203 -0
  42. package/canvas/SubgraphRouter.js +452 -0
  43. package/canvas/ViewportActions.js +473 -0
  44. package/canvas/graph-explorer.js +339 -0
  45. package/canvas/graph-layout.js +148 -0
  46. package/canvas/graph-model.js +68 -0
  47. package/canvas/html-in-canvas.js +202 -0
  48. package/canvas/project-graph-builder.js +440 -0
  49. package/canvas/project-graph-model.js +183 -0
  50. package/chat/ChatComposer/ChatComposer.css.js +652 -0
  51. package/chat/ChatComposer/ChatComposer.js +304 -0
  52. package/chat/ChatList/ChatList.css.js +102 -0
  53. package/chat/ChatList/ChatList.js +99 -0
  54. package/chat/ChatList/ChatList.tpl.js +20 -0
  55. package/chat/ChatListItem/ChatListItem.css.js +117 -0
  56. package/chat/ChatListItem/ChatListItem.js +32 -0
  57. package/chat/ChatListItem/ChatListItem.tpl.js +17 -0
  58. package/chat/ChatMessageItem/ChatMessageItem.css.js +628 -0
  59. package/chat/ChatMessageItem/ChatMessageItem.js +156 -0
  60. package/chat/ChatSidebar/ChatSidebar.css.js +150 -0
  61. package/chat/ChatSidebar/ChatSidebar.js +230 -0
  62. package/chat/ChatSidebar/ChatSidebar.tpl.js +18 -0
  63. package/chat/ChatSidebar/constants.js +11 -0
  64. package/chat/ChatSidebarItem/ChatSidebarItem.css.js +445 -0
  65. package/chat/ChatSidebarItem/ChatSidebarItem.js +304 -0
  66. package/chat/ChatTranscript/ChatTranscript.css.js +90 -0
  67. package/chat/ChatTranscript/ChatTranscript.js +244 -0
  68. package/chat/chat-context.js +123 -0
  69. package/chat/message-model.js +156 -0
  70. package/cli.js +20 -0
  71. package/control/Button/Button.css.js +93 -0
  72. package/control/Button/Button.js +78 -0
  73. package/control/Button/Button.tpl.js +3 -0
  74. package/control/Field/Field.css.js +91 -0
  75. package/control/Field/Field.js +17 -0
  76. package/control/Field/Field.tpl.js +3 -0
  77. package/core/Connection.js +47 -0
  78. package/core/Editor.js +449 -0
  79. package/core/Frame.js +33 -0
  80. package/core/GraphMermaid.js +348 -0
  81. package/core/GraphText.js +228 -0
  82. package/core/Node.js +145 -0
  83. package/core/Portal.js +106 -0
  84. package/core/Socket.js +187 -0
  85. package/core/SubgraphNode.js +121 -0
  86. package/core/base-path.js +55 -0
  87. package/core/dom-utils.js +14 -0
  88. package/core/index.js +18 -0
  89. package/core/local-cache.js +26 -0
  90. package/core/state-sync.js +227 -0
  91. package/custom-elements.json +6380 -0
  92. package/discover.js +240 -0
  93. package/display/Badge/Badge.css.js +44 -0
  94. package/display/Badge/Badge.js +17 -0
  95. package/display/Badge/Badge.tpl.js +3 -0
  96. package/display/Banner/Banner.css.js +61 -0
  97. package/display/Banner/Banner.js +17 -0
  98. package/display/Banner/Banner.tpl.js +3 -0
  99. package/display/CodeBlock/CodeBlock.css.js +194 -0
  100. package/display/CodeBlock/CodeBlock.js +220 -0
  101. package/display/CodeBlock/CodeBlock.tpl.js +11 -0
  102. package/display/DataTable/DataTable.css.js +101 -0
  103. package/display/DataTable/DataTable.js +136 -0
  104. package/display/DataTable/DataTable.tpl.js +13 -0
  105. package/display/EmptyState/EmptyState.css.js +33 -0
  106. package/display/EmptyState/EmptyState.js +17 -0
  107. package/display/EmptyState/EmptyState.tpl.js +3 -0
  108. package/display/EventFeed/EventFeed.css.js +145 -0
  109. package/display/EventFeed/EventFeed.js +64 -0
  110. package/display/EventFeed/EventFeed.tpl.js +14 -0
  111. package/display/EventFeed/EventFeedItem.js +116 -0
  112. package/display/EventFeed/EventFeedItem.tpl.js +22 -0
  113. package/display/LoadingOverlay/LoadingOverlay.css.js +91 -0
  114. package/display/LoadingOverlay/LoadingOverlay.js +48 -0
  115. package/display/LoadingOverlay/LoadingOverlay.tpl.js +12 -0
  116. package/display/Metric/Metric.css.js +60 -0
  117. package/display/Metric/Metric.js +17 -0
  118. package/display/Metric/Metric.tpl.js +6 -0
  119. package/display/OutputGraphPreview/OutputGraphPreview.css.js +122 -0
  120. package/display/OutputGraphPreview/OutputGraphPreview.js +89 -0
  121. package/display/OutputGraphPreview/OutputGraphPreview.tpl.js +13 -0
  122. package/display/OutputListPreview/OutputListPreview.css.js +109 -0
  123. package/display/OutputListPreview/OutputListPreview.js +77 -0
  124. package/display/OutputListPreview/OutputListPreview.tpl.js +13 -0
  125. package/display/SourceEditor/SourceEditor.css.js +39 -0
  126. package/display/SourceEditor/SourceEditor.js +129 -0
  127. package/display/SourceEditor/SourceEditor.tpl.js +10 -0
  128. package/display/SourceViewer/SourceViewer.css.js +80 -0
  129. package/display/SourceViewer/SourceViewer.js +418 -0
  130. package/display/SourceViewer/SourceViewer.tpl.js +17 -0
  131. package/display/StatusRibbon/StatusRibbon.css.js +73 -0
  132. package/display/StatusRibbon/StatusRibbon.js +87 -0
  133. package/display/StatusRibbon/StatusRibbon.tpl.js +7 -0
  134. package/display/event-feed-adapter.js +72 -0
  135. package/display/format-utils.js +29 -0
  136. package/display/highlight.js +659 -0
  137. package/display/icons.js +37 -0
  138. package/display/markdown-formatter.js +60 -0
  139. package/display/network-approval-page.js +487 -0
  140. package/display/output-preview.js +261 -0
  141. package/effects/CellBg/CellBg.css.js +33 -0
  142. package/effects/CellBg/CellBg.js +410 -0
  143. package/effects/CellBg/CellBg.tpl.js +5 -0
  144. package/graph/canvas-adapter.js +223 -0
  145. package/graph/graph-algorithms.js +31 -0
  146. package/graph/index.js +46 -0
  147. package/graph/model.js +176 -0
  148. package/graph/project-graph-build.js +66 -0
  149. package/graph/project-graph-metadata.js +253 -0
  150. package/graph/project-package.js +128 -0
  151. package/graph/project-runtime.js +116 -0
  152. package/graph/project-transaction.js +284 -0
  153. package/graph/skeleton-utils.js +84 -0
  154. package/graph/theme-contract.js +36 -0
  155. package/graph/transaction-parser.js +56 -0
  156. package/icons/MaterialSymbols.js +69 -0
  157. package/icons/material-symbols-outlined-400.ttf +0 -0
  158. package/icons/material-symbols.css +24 -0
  159. package/index.js +95 -0
  160. package/inspector/InspectorPanel/InspectorPanel.css.js +375 -0
  161. package/inspector/InspectorPanel/InspectorPanel.js +368 -0
  162. package/inspector/InspectorPanel/InspectorPanel.tpl.js +96 -0
  163. package/inspector/TemplatePreview/TemplatePreview.css.js +104 -0
  164. package/inspector/TemplatePreview/TemplatePreview.js +145 -0
  165. package/inspector/TemplatePreview/TemplatePreview.tpl.js +33 -0
  166. package/interactions/ConnectFlow.js +304 -0
  167. package/interactions/Drag.js +104 -0
  168. package/interactions/Selector.js +133 -0
  169. package/interactions/SnapGrid.js +66 -0
  170. package/interactions/Zoom.js +139 -0
  171. package/layout/ActionZone/ActionZone.css.js +88 -0
  172. package/layout/ActionZone/ActionZone.js +261 -0
  173. package/layout/ActionZone/ActionZone.tpl.js +11 -0
  174. package/layout/CrossLayoutPortalBridge/CrossLayoutPortalBridge.js +255 -0
  175. package/layout/Layout/Layout.css.js +91 -0
  176. package/layout/Layout/Layout.js +637 -0
  177. package/layout/Layout/Layout.tpl.js +27 -0
  178. package/layout/LayoutNode/LayoutNode.css.js +302 -0
  179. package/layout/LayoutNode/LayoutNode.js +509 -0
  180. package/layout/LayoutNode/LayoutNode.tpl.js +39 -0
  181. package/layout/LayoutPreview/LayoutPreview.css.js +46 -0
  182. package/layout/LayoutPreview/LayoutPreview.js +102 -0
  183. package/layout/LayoutPreview/LayoutPreview.tpl.js +6 -0
  184. package/layout/LayoutRouter/LayoutRouter.js +274 -0
  185. package/layout/LayoutRouter/SectionRegistry.js +135 -0
  186. package/layout/LayoutRouter/routerSync.js +250 -0
  187. package/layout/LayoutSidebar/LayoutSidebar.css.js +411 -0
  188. package/layout/LayoutSidebar/LayoutSidebar.js +368 -0
  189. package/layout/LayoutSidebar/LayoutSidebar.tpl.js +26 -0
  190. package/layout/LayoutSidebar/SidebarSection.css.js +20 -0
  191. package/layout/LayoutSidebar/SidebarSection.js +184 -0
  192. package/layout/LayoutSidebar/SidebarSection.tpl.js +22 -0
  193. package/layout/LayoutTree.js +373 -0
  194. package/layout/PanelMenu/PanelMenu.css.js +43 -0
  195. package/layout/PanelMenu/PanelMenu.js +95 -0
  196. package/layout/PanelMenu/PanelMenu.tpl.js +17 -0
  197. package/layout/ProjectTabs/ProjectTabs.css.js +188 -0
  198. package/layout/ProjectTabs/ProjectTabs.js +77 -0
  199. package/layout/ProjectTabs/ProjectTabs.tpl.js +15 -0
  200. package/layout/index.js +40 -0
  201. package/list/ListDetailShell/ListDetailShell.css.js +128 -0
  202. package/list/ListDetailShell/ListDetailShell.js +72 -0
  203. package/list/ListDetailShell/ListDetailShell.tpl.js +36 -0
  204. package/list/ListItem/ListItem.css.js +111 -0
  205. package/list/ListItem/ListItem.js +66 -0
  206. package/list/ListItem/ListItem.tpl.js +18 -0
  207. package/locale/index.js +503 -0
  208. package/manifest/component-registry.js +2446 -0
  209. package/manifest/graph-schema.js +285 -0
  210. package/manifest/index.js +6 -0
  211. package/manifest/project-schema-catalog.js +246 -0
  212. package/manifest/rule-catalog.js +201 -0
  213. package/manifest/theme-catalog.js +2149 -0
  214. package/manifest/ui-schema-catalog.js +334 -0
  215. package/menu/ContextMenu/ContextMenu.css.js +61 -0
  216. package/menu/ContextMenu/ContextMenu.js +82 -0
  217. package/menu/ContextMenu/ContextMenu.tpl.js +19 -0
  218. package/navigation/QuickOpen/QuickOpen.css.js +92 -0
  219. package/navigation/QuickOpen/QuickOpen.js +185 -0
  220. package/navigation/QuickOpen/QuickOpen.tpl.js +15 -0
  221. package/navigation/quick-open-utils.js +101 -0
  222. package/node/CtrlItem/CtrlItem.css.js +41 -0
  223. package/node/CtrlItem/CtrlItem.js +24 -0
  224. package/node/CtrlItem/CtrlItem.tpl.js +17 -0
  225. package/node/GraphFrame/GraphFrame.css.js +66 -0
  226. package/node/GraphFrame/GraphFrame.js +32 -0
  227. package/node/GraphFrame/GraphFrame.tpl.js +13 -0
  228. package/node/GraphNode/GraphNode.css.js +815 -0
  229. package/node/GraphNode/GraphNode.js +173 -0
  230. package/node/GraphNode/GraphNode.tpl.js +33 -0
  231. package/node/NodeCallout/NodeCallout.css.js +91 -0
  232. package/node/NodeCallout/NodeCallout.js +281 -0
  233. package/node/NodeCallout/NodeCallout.tpl.js +8 -0
  234. package/node/NodeSocket/NodeSocket.css.js +68 -0
  235. package/node/NodeSocket/NodeSocket.js +26 -0
  236. package/node/NodeSocket/NodeSocket.tpl.js +7 -0
  237. package/node/PortItem/PortItem.css.js +93 -0
  238. package/node/PortItem/PortItem.js +87 -0
  239. package/node/PortItem/PortItem.tpl.js +10 -0
  240. package/package.json +165 -0
  241. package/palette/PaletteBrowser/PaletteBrowser.css.js +143 -0
  242. package/palette/PaletteBrowser/PaletteBrowser.js +152 -0
  243. package/palette/PaletteBrowser/PaletteBrowser.tpl.js +23 -0
  244. package/plugins/History.js +408 -0
  245. package/plugins/Readonly.js +60 -0
  246. package/rules/symbiote-3x.json +170 -0
  247. package/schemas/component-descriptor-v1.json +91 -0
  248. package/schemas/component-descriptor-v2.json +145 -0
  249. package/schemas/graph-model-v1.json +179 -0
  250. package/schemas/graph-v1.json +91 -0
  251. package/schemas/project-package-v1.json +102 -0
  252. package/schemas/project-transaction-v1.json +114 -0
  253. package/schemas/runtime-ui-v1.json +80 -0
  254. package/schemas/theme-rule-block-v1.json +73 -0
  255. package/shapes/CircleShape.js +79 -0
  256. package/shapes/CommentShape.js +35 -0
  257. package/shapes/DiamondShape.js +130 -0
  258. package/shapes/NodeShape.js +79 -0
  259. package/shapes/PillShape.js +91 -0
  260. package/shapes/RectShape.js +84 -0
  261. package/shapes/SVGShape.js +525 -0
  262. package/shapes/index.js +63 -0
  263. package/surface/Card/Card.css.js +57 -0
  264. package/surface/Card/Card.js +17 -0
  265. package/surface/Card/Card.tpl.js +3 -0
  266. package/themes/Palette.js +30 -0
  267. package/themes/Skin.js +113 -0
  268. package/themes/Theme.js +82 -0
  269. package/themes/carbon.js +135 -0
  270. package/themes/dark.js +140 -0
  271. package/themes/default-dark.js +714 -0
  272. package/themes/default-provider.css +635 -0
  273. package/themes/default-provider.js +718 -0
  274. package/themes/ebook.js +136 -0
  275. package/themes/grey.js +137 -0
  276. package/themes/light.js +139 -0
  277. package/themes/neon.js +138 -0
  278. package/themes/pcb.js +273 -0
  279. package/themes/synthwave.js +138 -0
  280. package/tokens/base.json +29 -0
  281. package/tokens/themes/carbon.json +11 -0
  282. package/tokens/themes/dark.json +12 -0
  283. package/tokens/themes/default-dark.json +1543 -0
  284. package/tokens/themes/default-provider.json +1543 -0
  285. package/tokens/themes/ebook.json +11 -0
  286. package/tokens/themes/grey.json +11 -0
  287. package/tokens/themes/light.json +12 -0
  288. package/tokens/themes/neon.json +11 -0
  289. package/tokens/themes/pcb.json +11 -0
  290. package/tokens/themes/synthwave.json +11 -0
  291. package/toolbar/QuickToolbar/QuickToolbar.css.js +152 -0
  292. package/toolbar/QuickToolbar/QuickToolbar.js +529 -0
  293. package/toolbar/QuickToolbar/QuickToolbar.tpl.js +34 -0
  294. package/tree/TreePanel/TreePanel.css.js +112 -0
  295. package/tree/TreePanel/TreePanel.js +147 -0
  296. package/tree/TreePanel/TreePanel.tpl.js +18 -0
  297. package/tree/TreeView/TreeView.css.js +122 -0
  298. package/tree/TreeView/TreeView.js +365 -0
  299. package/tree/TreeView/TreeView.tpl.js +10 -0
  300. package/ui/dialogs.js +221 -0
  301. package/ui/host-adapters.js +114 -0
  302. package/ui/index.js +660 -0
  303. package/ui/locale.js +50 -0
  304. package/ui/overlay-stack.js +89 -0
  305. package/ui/shared-styles.js +26 -0
  306. package/webmcp.js +37 -0
  307. package/xr/deep-graph.js +646 -0
  308. package/xr/emulation.js +198 -0
  309. package/xr/gesture.js +228 -0
  310. package/xr/html-canvas-renderer.js +472 -0
  311. package/xr/index.js +15 -0
  312. package/xr/layout-projection.js +1046 -0
  313. package/xr/panel-frame.js +128 -0
  314. package/xr/panel-host.js +267 -0
  315. package/xr/pointer.js +258 -0
  316. package/xr/scene-controller.js +242 -0
  317. package/xr/spatial-scene.js +212 -0
  318. package/xr/theme-bridge.js +105 -0
  319. package/xr/three-webxr-adapter.js +3439 -0
  320. package/xr/webgl-layer-renderer.js +419 -0
  321. package/xr/webxr.js +679 -0
  322. package/xr/workbench.js +516 -0
@@ -0,0 +1,463 @@
1
+ const DEFAULT_RULE_COUNTS = {
2
+ nodeIntersection: 0,
3
+ reversal: 0,
4
+ longDiagonal: 0,
5
+ selfIntersection: 0,
6
+ inefficient: 0,
7
+ sharedChannel: 0,
8
+ endpointOverlap: 0,
9
+ };
10
+
11
+ export const PCB_ROUTE_HARD_RULES = Object.freeze([
12
+ 'nodeIntersection',
13
+ 'reversal',
14
+ 'longDiagonal',
15
+ 'selfIntersection',
16
+ ]);
17
+
18
+ export const PCB_ROUTE_SOFT_RULES = Object.freeze([
19
+ 'inefficient',
20
+ 'sharedChannel',
21
+ 'endpointOverlap',
22
+ ]);
23
+
24
+ function pickRuleCounts(byRule, rules) {
25
+ const counts = {};
26
+ for (const rule of rules) {
27
+ counts[rule] = byRule?.[rule] || 0;
28
+ }
29
+ return counts;
30
+ }
31
+
32
+ function sumRuleCounts(byRule, rules) {
33
+ return rules.reduce((total, rule) => total + (byRule?.[rule] || 0), 0);
34
+ }
35
+
36
+ export function summarizePcbRouteQuality(summary) {
37
+ const byRule = { ...DEFAULT_RULE_COUNTS, ...(summary?.byRule || {}) };
38
+ const hardFailures = sumRuleCounts(byRule, PCB_ROUTE_HARD_RULES);
39
+ const softWarnings = sumRuleCounts(byRule, PCB_ROUTE_SOFT_RULES);
40
+ const classified = hardFailures + softWarnings;
41
+ const total = Number(summary?.total) || Object.values(byRule).reduce((sum, count) => sum + count, 0);
42
+ return {
43
+ pass: hardFailures === 0,
44
+ hardFailures,
45
+ softWarnings,
46
+ unclassified: Math.max(0, total - classified),
47
+ total,
48
+ hardRules: pickRuleCounts(byRule, PCB_ROUTE_HARD_RULES),
49
+ softRules: pickRuleCounts(byRule, PCB_ROUTE_SOFT_RULES),
50
+ };
51
+ }
52
+
53
+ function rectX(rect) {
54
+ return Number(rect?.x) || 0;
55
+ }
56
+
57
+ function rectY(rect) {
58
+ return Number(rect?.y) || 0;
59
+ }
60
+
61
+ function rectW(rect) {
62
+ return Number(rect?.w ?? rect?.width) || 0;
63
+ }
64
+
65
+ function rectH(rect) {
66
+ return Number(rect?.h ?? rect?.height) || 0;
67
+ }
68
+
69
+ function normalizeRect(rect) {
70
+ return {
71
+ ...rect,
72
+ id: rect?.id,
73
+ x: rectX(rect),
74
+ y: rectY(rect),
75
+ w: rectW(rect),
76
+ h: rectH(rect),
77
+ };
78
+ }
79
+
80
+ function addViolation(violations, rule, details = {}) {
81
+ violations.push({ rule, ...details });
82
+ }
83
+
84
+ function summarize(violations) {
85
+ const byRule = { ...DEFAULT_RULE_COUNTS };
86
+ for (const violation of violations) {
87
+ byRule[violation.rule] = (byRule[violation.rule] || 0) + 1;
88
+ }
89
+ return {
90
+ total: violations.length,
91
+ byRule,
92
+ };
93
+ }
94
+
95
+ export function parsePcbPathPoints(path) {
96
+ const commands = String(path || '').match(/[MLHV][^MLHV]*/gi) || [];
97
+ let x = 0;
98
+ let y = 0;
99
+ const points = [];
100
+
101
+ for (const command of commands) {
102
+ const type = command[0].toUpperCase();
103
+ const values = command.slice(1).trim().split(/[ ,]+/).filter(Boolean).map(Number);
104
+ if (type === 'M' || type === 'L') {
105
+ [x, y] = values;
106
+ } else if (type === 'H') {
107
+ [x] = values;
108
+ } else if (type === 'V') {
109
+ [y] = values;
110
+ }
111
+ if (Number.isFinite(x) && Number.isFinite(y)) {
112
+ points.push({ x, y });
113
+ }
114
+ }
115
+
116
+ return points;
117
+ }
118
+
119
+ export function routeLength(points) {
120
+ let length = 0;
121
+ for (let index = 0; index < points.length - 1; index += 1) {
122
+ length += Math.abs(points[index + 1].x - points[index].x) + Math.abs(points[index + 1].y - points[index].y);
123
+ }
124
+ return length;
125
+ }
126
+
127
+ export function segmentDirections(points) {
128
+ const directions = [];
129
+ for (let index = 0; index < points.length - 1; index += 1) {
130
+ const a = points[index];
131
+ const b = points[index + 1];
132
+ const dx = b.x - a.x;
133
+ const dy = b.y - a.y;
134
+ if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) continue;
135
+ directions.push(Math.abs(dx) >= Math.abs(dy)
136
+ ? { axis: 'x', sign: Math.sign(dx), length: Math.abs(dx) }
137
+ : { axis: 'y', sign: Math.sign(dy), length: Math.abs(dy) });
138
+ }
139
+ return directions;
140
+ }
141
+
142
+ export function countBends(points) {
143
+ const directions = segmentDirections(points);
144
+ let bends = 0;
145
+ for (let index = 1; index < directions.length; index += 1) {
146
+ if (directions[index].axis !== directions[index - 1].axis) bends += 1;
147
+ }
148
+ return bends;
149
+ }
150
+
151
+ export function countReversals(points, grid = 10) {
152
+ const directions = segmentDirections(points);
153
+ let reversals = 0;
154
+ for (let index = 1; index < directions.length; index += 1) {
155
+ const prev = directions[index - 1];
156
+ const curr = directions[index];
157
+ if (prev.axis === curr.axis && prev.sign === -curr.sign) {
158
+ reversals += 1;
159
+ }
160
+ const before = directions[index - 2];
161
+ if (
162
+ before &&
163
+ before.axis === curr.axis &&
164
+ before.sign === -curr.sign &&
165
+ prev.length <= grid * 2
166
+ ) {
167
+ reversals += 1;
168
+ }
169
+ }
170
+ return reversals;
171
+ }
172
+
173
+ function pointInRect(point, rect, pad = 0) {
174
+ return (
175
+ point.x >= rect.x - pad &&
176
+ point.x <= rect.x + rect.w + pad &&
177
+ point.y >= rect.y - pad &&
178
+ point.y <= rect.y + rect.h + pad
179
+ );
180
+ }
181
+
182
+ function rectsOverlap(a, b, pad = 0) {
183
+ return !(
184
+ a.x + a.w + pad < b.x - pad ||
185
+ b.x + b.w + pad < a.x - pad ||
186
+ a.y + a.h + pad < b.y - pad ||
187
+ b.y + b.h + pad < a.y - pad
188
+ );
189
+ }
190
+
191
+ function orientation(a, b, c) {
192
+ const value = (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y);
193
+ if (Math.abs(value) < 0.5) return 0;
194
+ return value > 0 ? 1 : 2;
195
+ }
196
+
197
+ function onSegment(a, b, c) {
198
+ return (
199
+ b.x <= Math.max(a.x, c.x) + 0.5 &&
200
+ b.x >= Math.min(a.x, c.x) - 0.5 &&
201
+ b.y <= Math.max(a.y, c.y) + 0.5 &&
202
+ b.y >= Math.min(a.y, c.y) - 0.5
203
+ );
204
+ }
205
+
206
+ function segmentsIntersect(a1, a2, b1, b2) {
207
+ const o1 = orientation(a1, a2, b1);
208
+ const o2 = orientation(a1, a2, b2);
209
+ const o3 = orientation(b1, b2, a1);
210
+ const o4 = orientation(b1, b2, a2);
211
+
212
+ if (o1 !== o2 && o3 !== o4) return true;
213
+ if (o1 === 0 && onSegment(a1, b1, a2)) return true;
214
+ if (o2 === 0 && onSegment(a1, b2, a2)) return true;
215
+ if (o3 === 0 && onSegment(b1, a1, b2)) return true;
216
+ if (o4 === 0 && onSegment(b1, a2, b2)) return true;
217
+ return false;
218
+ }
219
+
220
+ function segmentIntersectsRect(a, b, rect, pad = 0) {
221
+ const expanded = {
222
+ x: rect.x - pad,
223
+ y: rect.y - pad,
224
+ w: rect.w + pad * 2,
225
+ h: rect.h + pad * 2,
226
+ };
227
+ if (pointInRect(a, expanded) || pointInRect(b, expanded)) return true;
228
+
229
+ const left = expanded.x;
230
+ const right = expanded.x + expanded.w;
231
+ const top = expanded.y;
232
+ const bottom = expanded.y + expanded.h;
233
+ const edges = [
234
+ [{ x: left, y: top }, { x: right, y: top }],
235
+ [{ x: right, y: top }, { x: right, y: bottom }],
236
+ [{ x: right, y: bottom }, { x: left, y: bottom }],
237
+ [{ x: left, y: bottom }, { x: left, y: top }],
238
+ ];
239
+
240
+ return edges.some(([edgeStart, edgeEnd]) => segmentsIntersect(a, b, edgeStart, edgeEnd));
241
+ }
242
+
243
+ function segmentCrossesRectInterior(a, b, rect, inset = 3) {
244
+ const inner = {
245
+ x: rect.x + inset,
246
+ y: rect.y + inset,
247
+ w: rect.w - inset * 2,
248
+ h: rect.h - inset * 2,
249
+ };
250
+ if (inner.w <= 0 || inner.h <= 0) return false;
251
+ return segmentIntersectsRect(a, b, inner, 0);
252
+ }
253
+
254
+ function pointOnRectBoundary(point, rect, tolerance = 0.25) {
255
+ const withinX = point.x >= rect.x - tolerance && point.x <= rect.x + rect.w + tolerance;
256
+ const withinY = point.y >= rect.y - tolerance && point.y <= rect.y + rect.h + tolerance;
257
+ if (!withinX || !withinY) return false;
258
+ return (
259
+ Math.abs(point.x - rect.x) <= tolerance ||
260
+ Math.abs(point.x - (rect.x + rect.w)) <= tolerance ||
261
+ Math.abs(point.y - rect.y) <= tolerance ||
262
+ Math.abs(point.y - (rect.y + rect.h)) <= tolerance
263
+ );
264
+ }
265
+
266
+ function countLongDiagonals(points, maxLength) {
267
+ let count = 0;
268
+ for (let index = 0; index < points.length - 1; index += 1) {
269
+ const dx = Math.abs(points[index + 1].x - points[index].x);
270
+ const dy = Math.abs(points[index + 1].y - points[index].y);
271
+ if (dx > 0.5 && dy > 0.5 && Math.hypot(dx, dy) > maxLength) {
272
+ count += 1;
273
+ }
274
+ }
275
+ return count;
276
+ }
277
+
278
+ function countRouteLongDiagonals(routePoints, renderedPoints, maxLength, straightLineAllowance) {
279
+ if (routePoints.length <= 2 && routeLength(routePoints) <= straightLineAllowance) {
280
+ return 0;
281
+ }
282
+ return countLongDiagonals(renderedPoints, maxLength);
283
+ }
284
+
285
+ function samePoint(a, b) {
286
+ return Math.abs(a.x - b.x) < 0.5 && Math.abs(a.y - b.y) < 0.5;
287
+ }
288
+
289
+ function countSelfIntersections(points) {
290
+ let count = 0;
291
+ for (let aIndex = 0; aIndex < points.length - 1; aIndex += 1) {
292
+ for (let bIndex = aIndex + 2; bIndex < points.length - 1; bIndex += 1) {
293
+ const a1 = points[aIndex];
294
+ const a2 = points[aIndex + 1];
295
+ const b1 = points[bIndex];
296
+ const b2 = points[bIndex + 1];
297
+ if (samePoint(a2, b1)) continue;
298
+ if (aIndex === 0 && bIndex === points.length - 2 && samePoint(a1, b2)) continue;
299
+ if (segmentsIntersect(a1, a2, b1, b2)) count += 1;
300
+ }
301
+ }
302
+ return count;
303
+ }
304
+
305
+ function sharedOrthogonalLength(a1, a2, b1, b2) {
306
+ if (Math.abs(a1.x - a2.x) < 0.5 && Math.abs(b1.x - b2.x) < 0.5 && Math.abs(a1.x - b1.x) < 0.5) {
307
+ const aMin = Math.min(a1.y, a2.y);
308
+ const aMax = Math.max(a1.y, a2.y);
309
+ const bMin = Math.min(b1.y, b2.y);
310
+ const bMax = Math.max(b1.y, b2.y);
311
+ return Math.max(0, Math.min(aMax, bMax) - Math.max(aMin, bMin));
312
+ }
313
+
314
+ if (Math.abs(a1.y - a2.y) < 0.5 && Math.abs(b1.y - b2.y) < 0.5 && Math.abs(a1.y - b1.y) < 0.5) {
315
+ const aMin = Math.min(a1.x, a2.x);
316
+ const aMax = Math.max(a1.x, a2.x);
317
+ const bMin = Math.min(b1.x, b2.x);
318
+ const bMax = Math.max(b1.x, b2.x);
319
+ return Math.max(0, Math.min(aMax, bMax) - Math.max(aMin, bMin));
320
+ }
321
+
322
+ return 0;
323
+ }
324
+
325
+ export function maxSharedMiddleSegmentLength(aPoints, bPoints) {
326
+ let max = 0;
327
+ for (let aIndex = 1; aIndex < aPoints.length - 2; aIndex += 1) {
328
+ for (let bIndex = 1; bIndex < bPoints.length - 2; bIndex += 1) {
329
+ max = Math.max(max, sharedOrthogonalLength(
330
+ aPoints[aIndex],
331
+ aPoints[aIndex + 1],
332
+ bPoints[bIndex],
333
+ bPoints[bIndex + 1]
334
+ ));
335
+ }
336
+ }
337
+ return max;
338
+ }
339
+
340
+ export function analyzePcbRoute({
341
+ id = '',
342
+ path = '',
343
+ points = [],
344
+ fromRect = null,
345
+ toRect = null,
346
+ rects = [],
347
+ grid = 10,
348
+ pad = 1,
349
+ maxDiagonal = 12,
350
+ straightLineAllowance = 80,
351
+ maxLengthRatio = 2.4,
352
+ } = {}) {
353
+ const routePoints = points.length ? points : parsePcbPathPoints(path);
354
+ const renderedPoints = path ? parsePcbPathPoints(path) : routePoints;
355
+ const normalizedRects = rects.map(normalizeRect);
356
+ const sourceId = fromRect?.id;
357
+ const targetId = toRect?.id;
358
+ const normalizedSource = normalizedRects.find((rect) => rect.id === sourceId) || (fromRect ? normalizeRect(fromRect) : null);
359
+ const normalizedTarget = normalizedRects.find((rect) => rect.id === targetId) || (toRect ? normalizeRect(toRect) : null);
360
+ const endpointsOverlap = normalizedSource && normalizedTarget && rectsOverlap(normalizedSource, normalizedTarget, pad);
361
+ const violations = [];
362
+
363
+ if (endpointsOverlap) {
364
+ addViolation(violations, 'endpointOverlap', { routeId: id, rectIds: [sourceId, targetId].filter(Boolean) });
365
+ }
366
+
367
+ for (let index = 0; index < routePoints.length - 1; index += 1) {
368
+ for (const rect of normalizedRects) {
369
+ if (endpointsOverlap && (rect.id === sourceId || rect.id === targetId)) continue;
370
+ const crossesEndpointInterior = segmentCrossesRectInterior(routePoints[index], routePoints[index + 1], rect);
371
+ const nearSourceEndpoint = rect.id === sourceId && index <= 1;
372
+ const nearTargetEndpoint = rect.id === targetId && index >= routePoints.length - 3;
373
+ const endpointEdgeSegment =
374
+ (nearSourceEndpoint || nearTargetEndpoint) &&
375
+ !crossesEndpointInterior &&
376
+ (pointOnRectBoundary(routePoints[index], rect) || pointOnRectBoundary(routePoints[index + 1], rect));
377
+ if (endpointEdgeSegment) continue;
378
+ const sourceStub = rect.id === sourceId && index === 0 && !crossesEndpointInterior;
379
+ const targetStub = rect.id === targetId && index === routePoints.length - 2 && !crossesEndpointInterior;
380
+ if (sourceStub || targetStub) continue;
381
+ if (segmentIntersectsRect(routePoints[index], routePoints[index + 1], rect, pad)) {
382
+ addViolation(violations, 'nodeIntersection', { routeId: id, rectId: rect.id, segmentIndex: index });
383
+ }
384
+ }
385
+ }
386
+
387
+ const reversals = countReversals(routePoints, grid);
388
+ for (let index = 0; index < reversals; index += 1) {
389
+ addViolation(violations, 'reversal', { routeId: id });
390
+ }
391
+
392
+ const longDiagonals = countRouteLongDiagonals(routePoints, renderedPoints, maxDiagonal, straightLineAllowance);
393
+ for (let index = 0; index < longDiagonals; index += 1) {
394
+ addViolation(violations, 'longDiagonal', { routeId: id });
395
+ }
396
+
397
+ const selfIntersections = countSelfIntersections(routePoints);
398
+ for (let index = 0; index < selfIntersections; index += 1) {
399
+ addViolation(violations, 'selfIntersection', { routeId: id });
400
+ }
401
+
402
+ if (routePoints.length >= 2 && Number.isFinite(maxLengthRatio)) {
403
+ const start = routePoints[0];
404
+ const end = routePoints.at(-1);
405
+ const shortest = Math.abs(end.x - start.x) + Math.abs(end.y - start.y);
406
+ const length = routeLength(routePoints);
407
+ const inefficientLimit = Math.max(shortest * maxLengthRatio, shortest + straightLineAllowance * 2);
408
+ if (shortest > 0 && length > inefficientLimit) {
409
+ addViolation(violations, 'inefficient', {
410
+ routeId: id,
411
+ ratio: length / shortest,
412
+ length,
413
+ shortest,
414
+ });
415
+ }
416
+ }
417
+
418
+ return {
419
+ id,
420
+ metrics: {
421
+ length: routeLength(routePoints),
422
+ bends: countBends(routePoints),
423
+ reversals,
424
+ longDiagonals,
425
+ selfIntersections,
426
+ },
427
+ violations,
428
+ summary: summarize(violations),
429
+ };
430
+ }
431
+
432
+ export function analyzePcbRouteSet(routes = [], options = {}) {
433
+ const routeResults = routes.map((route) => analyzePcbRoute({ ...options, ...route }));
434
+ const sharedViolations = [];
435
+ const maxSharedMiddleSegment = options.maxSharedMiddleSegment ?? 1;
436
+
437
+ for (let aIndex = 0; aIndex < routes.length - 1; aIndex += 1) {
438
+ for (let bIndex = aIndex + 1; bIndex < routes.length; bIndex += 1) {
439
+ const aPoints = routes[aIndex].points?.length ? routes[aIndex].points : parsePcbPathPoints(routes[aIndex].path);
440
+ const bPoints = routes[bIndex].points?.length ? routes[bIndex].points : parsePcbPathPoints(routes[bIndex].path);
441
+ const length = maxSharedMiddleSegmentLength(aPoints, bPoints);
442
+ if (length > maxSharedMiddleSegment) {
443
+ sharedViolations.push({
444
+ rule: 'sharedChannel',
445
+ routeIds: [routes[aIndex].id, routes[bIndex].id],
446
+ length,
447
+ });
448
+ }
449
+ }
450
+ }
451
+
452
+ const violations = [
453
+ ...routeResults.flatMap((result) => result.violations),
454
+ ...sharedViolations,
455
+ ];
456
+
457
+ return {
458
+ routes: routeResults,
459
+ sharedViolations,
460
+ violations,
461
+ summary: summarize(violations),
462
+ };
463
+ }