@tscircuit/schematic-trace-solver 0.0.46 → 0.0.47

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.
@@ -1,12 +1,14 @@
1
1
  import type { Point } from "graphics-debug"
2
2
  import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
3
- import { getRectBounds } from "../NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
4
3
  import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
5
- import {
6
- segmentIntersectsRect,
7
- isVertical,
8
- } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
4
+ import { segmentIntersectsRect } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
9
5
 
6
+ /**
7
+ * Generates candidate four-point detour paths for a trace colliding with a label obstacle.
8
+ * It uses a two-strategy approach: a simpler one for individual labels and a more robust one
9
+ * for merged labels, finding precise entry and exit points. Detours are U-shaped, respecting
10
+ * the trace's direction to prevent loops.
11
+ */
10
12
  export const generateFourPointDetourCandidates = ({
11
13
  initialTrace,
12
14
  label,
@@ -16,89 +18,137 @@ export const generateFourPointDetourCandidates = ({
16
18
  }: {
17
19
  initialTrace: SolvedTracePath
18
20
  label: NetLabelPlacement
19
- labelBounds: any
21
+ labelBounds: { minX: number; maxX: number; minY: number; maxY: number }
20
22
  paddingBuffer: number
21
23
  detourCount: number
22
24
  }): Point[][] => {
23
- let collidingSegIndex = -1
24
- for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
25
- if (
26
- segmentIntersectsRect(
27
- initialTrace.tracePath[i],
28
- initialTrace.tracePath[i + 1],
29
- labelBounds,
30
- )
31
- ) {
32
- collidingSegIndex = i
33
- break
34
- }
25
+ const isMergedLabel = label.globalConnNetId.startsWith("merged-group-")
26
+ const effectivePadding = paddingBuffer + detourCount * paddingBuffer
27
+ const paddedLabelBounds = {
28
+ minX: labelBounds.minX - effectivePadding,
29
+ maxX: labelBounds.maxX + effectivePadding,
30
+ minY: labelBounds.minY - effectivePadding,
31
+ maxY: labelBounds.maxY + effectivePadding,
35
32
  }
36
33
 
37
- if (collidingSegIndex === -1) return []
34
+ let entryPoint: Point, exitPoint: Point
35
+ let entryIndex: number, exitIndex: number
38
36
 
39
- const pA = initialTrace.tracePath[collidingSegIndex]
40
- const pB = initialTrace.tracePath[collidingSegIndex + 1]
37
+ const isPointInRect = (p: Point) =>
38
+ p.x >= paddedLabelBounds.minX &&
39
+ p.x <= paddedLabelBounds.maxX &&
40
+ p.y >= paddedLabelBounds.minY &&
41
+ p.y <= paddedLabelBounds.maxY
41
42
 
42
- if (!pA || !pB) return []
43
+ if (isMergedLabel) {
44
+ // STRATEGY 2: Merged Label - find first point before and after the entire crossing
45
+ let firstInsideIndex = -1
46
+ for (let i = 0; i < initialTrace.tracePath.length; i++) {
47
+ if (isPointInRect(initialTrace.tracePath[i])) {
48
+ firstInsideIndex = i
49
+ break
50
+ }
51
+ }
43
52
 
44
- const candidateDetours: Point[][] = []
45
- const paddedLabelBounds = getRectBounds(
46
- label.center,
47
- label.width,
48
- label.height,
49
- )
53
+ if (firstInsideIndex === -1) return [] // No points inside, shouldn't be called
50
54
 
51
- const effectivePadding = paddingBuffer + detourCount * paddingBuffer
55
+ entryIndex = Math.max(0, firstInsideIndex - 1)
56
+ entryPoint = initialTrace.tracePath[entryIndex]
52
57
 
53
- if (isVertical(pA, pB)) {
54
- const xCandidates = [
55
- paddedLabelBounds.maxX + effectivePadding,
56
- paddedLabelBounds.minX - effectivePadding,
57
- ]
58
- for (const newX of xCandidates) {
58
+ let firstOutsideIndex = -1
59
+ for (let i = firstInsideIndex; i < initialTrace.tracePath.length; i++) {
60
+ if (!isPointInRect(initialTrace.tracePath[i])) {
61
+ firstOutsideIndex = i
62
+ break
63
+ }
64
+ }
65
+
66
+ if (firstOutsideIndex === -1) {
67
+ // Trace ends inside the box, use the last point as exit
68
+ exitIndex = initialTrace.tracePath.length - 1
69
+ } else {
70
+ exitIndex = firstOutsideIndex
71
+ }
72
+ exitPoint = initialTrace.tracePath[exitIndex]
73
+ } else {
74
+ // STRATEGY 1: Single Label - find the first intersecting segment
75
+ let collidingSegIndex = -1
76
+ for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
77
+ if (
78
+ segmentIntersectsRect(
79
+ initialTrace.tracePath[i],
80
+ initialTrace.tracePath[i + 1],
81
+ { ...paddedLabelBounds, chipId: "temp-obstacle" },
82
+ )
83
+ ) {
84
+ collidingSegIndex = i
85
+ break
86
+ }
87
+ }
88
+
89
+ if (collidingSegIndex === -1) return []
90
+
91
+ entryIndex = collidingSegIndex
92
+ exitIndex = collidingSegIndex + 1
93
+ entryPoint = initialTrace.tracePath[entryIndex]
94
+ exitPoint = initialTrace.tracePath[exitIndex]
95
+ }
96
+
97
+ if (!entryPoint || !exitPoint || entryIndex >= exitIndex) return []
98
+
99
+ const candidateDetours: Point[][] = []
100
+ const dx = exitPoint.x - entryPoint.x
101
+ const dy = exitPoint.y - entryPoint.y
102
+
103
+ if (Math.abs(dx) > Math.abs(dy)) {
104
+ // More horizontal
105
+ const yCandidates = [paddedLabelBounds.maxY, paddedLabelBounds.minY]
106
+ for (const newY of yCandidates) {
59
107
  candidateDetours.push(
60
- pB.y > pA.y
108
+ dx > 0 // Left-to-right
61
109
  ? [
62
- { x: pA.x, y: paddedLabelBounds.minY - effectivePadding },
63
- { x: newX, y: paddedLabelBounds.minY - effectivePadding },
64
- { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
65
- { x: pB.x, y: paddedLabelBounds.maxY + effectivePadding },
110
+ { x: paddedLabelBounds.minX, y: entryPoint.y },
111
+ { x: paddedLabelBounds.minX, y: newY },
112
+ { x: paddedLabelBounds.maxX, y: newY },
113
+ { x: paddedLabelBounds.maxX, y: exitPoint.y },
66
114
  ]
67
115
  : [
68
- { x: pA.x, y: paddedLabelBounds.maxY + effectivePadding },
69
- { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
70
- { x: newX, y: paddedLabelBounds.minY - effectivePadding },
71
- { x: pB.x, y: paddedLabelBounds.minY - effectivePadding },
116
+ // Right-to-left
117
+ { x: paddedLabelBounds.maxX, y: entryPoint.y },
118
+ { x: paddedLabelBounds.maxX, y: newY },
119
+ { x: paddedLabelBounds.minX, y: newY },
120
+ { x: paddedLabelBounds.minX, y: exitPoint.y },
72
121
  ],
73
122
  )
74
123
  }
75
124
  } else {
76
- const yCandidates = [
77
- paddedLabelBounds.maxY + effectivePadding,
78
- paddedLabelBounds.minY - effectivePadding,
79
- ]
80
- for (const newY of yCandidates) {
125
+ // More vertical
126
+ const xCandidates = [paddedLabelBounds.maxX, paddedLabelBounds.minX]
127
+ for (const newX of xCandidates) {
81
128
  candidateDetours.push(
82
- pB.x > pA.x
129
+ dy > 0 // Top-to-bottom
83
130
  ? [
84
- { x: paddedLabelBounds.minX - effectivePadding, y: pA.y },
85
- { x: paddedLabelBounds.minX - effectivePadding, y: newY },
86
- { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
87
- { x: paddedLabelBounds.maxX + effectivePadding, y: pB.y },
131
+ { x: entryPoint.x, y: paddedLabelBounds.minY },
132
+ { x: newX, y: paddedLabelBounds.minY },
133
+ { x: newX, y: paddedLabelBounds.maxY },
134
+ { x: exitPoint.x, y: paddedLabelBounds.maxY },
88
135
  ]
89
136
  : [
90
- { x: paddedLabelBounds.maxX + effectivePadding, y: pA.y },
91
- { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
92
- { x: paddedLabelBounds.minX - effectivePadding, y: newY },
93
- { x: paddedLabelBounds.minX - effectivePadding, y: pB.y },
137
+ // Bottom-to-top
138
+ { x: entryPoint.x, y: paddedLabelBounds.maxY },
139
+ { x: newX, y: paddedLabelBounds.maxY },
140
+ { x: newX, y: paddedLabelBounds.minY },
141
+ { x: exitPoint.x, y: paddedLabelBounds.minY },
94
142
  ],
95
143
  )
96
144
  }
97
145
  }
98
146
 
99
147
  return candidateDetours.map((detourPoints) => [
100
- ...initialTrace.tracePath.slice(0, collidingSegIndex + 1),
148
+ ...initialTrace.tracePath.slice(0, entryIndex),
149
+ entryPoint,
101
150
  ...detourPoints,
102
- ...initialTrace.tracePath.slice(collidingSegIndex + 1),
151
+ exitPoint,
152
+ ...initialTrace.tracePath.slice(exitIndex + 1),
103
153
  ])
104
154
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-trace-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.46",
4
+ "version": "0.0.47",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/assets/example27.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />
@@ -0,0 +1,168 @@
1
+ {
2
+ "chips": [
3
+ {
4
+ "chipId": "schematic_component_0",
5
+ "center": {
6
+ "x": 0,
7
+ "y": 0
8
+ },
9
+ "width": 0.4,
10
+ "height": 2.5999999999999996,
11
+ "pins": [
12
+ {
13
+ "pinId": "P.1",
14
+ "x": -0.6000000000000001,
15
+ "y": 1.0999999999999999
16
+ },
17
+ {
18
+ "pinId": "P.2",
19
+ "x": -0.6000000000000001,
20
+ "y": 0.8999999999999999
21
+ },
22
+ {
23
+ "pinId": "P.3",
24
+ "x": -0.6000000000000001,
25
+ "y": 0.6999999999999998
26
+ },
27
+ {
28
+ "pinId": "P.4",
29
+ "x": -0.6000000000000001,
30
+ "y": 0.4999999999999998
31
+ },
32
+ {
33
+ "pinId": "P.5",
34
+ "x": -0.6000000000000001,
35
+ "y": 0.2999999999999998
36
+ },
37
+ {
38
+ "pinId": "P.6",
39
+ "x": -0.6000000000000001,
40
+ "y": 0.09999999999999987
41
+ },
42
+ {
43
+ "pinId": "P.7",
44
+ "x": -0.6000000000000001,
45
+ "y": -0.10000000000000009
46
+ },
47
+ {
48
+ "pinId": "P.8",
49
+ "x": -0.6000000000000001,
50
+ "y": -0.30000000000000004
51
+ },
52
+ {
53
+ "pinId": "P.9",
54
+ "x": -0.6000000000000001,
55
+ "y": -0.5
56
+ },
57
+ {
58
+ "pinId": "P.10",
59
+ "x": -0.6000000000000001,
60
+ "y": -0.7
61
+ },
62
+ {
63
+ "pinId": "P.11",
64
+ "x": -0.6000000000000001,
65
+ "y": -0.8999999999999999
66
+ },
67
+ {
68
+ "pinId": "P.12",
69
+ "x": -0.6000000000000001,
70
+ "y": -1.0999999999999999
71
+ },
72
+ {
73
+ "pinId": "P.13",
74
+ "x": 0.6000000000000001,
75
+ "y": -1.0999999999999999
76
+ },
77
+ {
78
+ "pinId": "P.14",
79
+ "x": 0.6000000000000001,
80
+ "y": -0.8999999999999999
81
+ },
82
+ {
83
+ "pinId": "P.15",
84
+ "x": 0.6000000000000001,
85
+ "y": -0.6999999999999998
86
+ },
87
+ {
88
+ "pinId": "P.16",
89
+ "x": 0.6000000000000001,
90
+ "y": -0.4999999999999998
91
+ },
92
+ {
93
+ "pinId": "P.17",
94
+ "x": 0.6000000000000001,
95
+ "y": -0.2999999999999998
96
+ },
97
+ {
98
+ "pinId": "P.18",
99
+ "x": 0.6000000000000001,
100
+ "y": -0.09999999999999987
101
+ },
102
+ {
103
+ "pinId": "P.19",
104
+ "x": 0.6000000000000001,
105
+ "y": 0.10000000000000009
106
+ },
107
+ {
108
+ "pinId": "P.20",
109
+ "x": 0.6000000000000001,
110
+ "y": 0.30000000000000004
111
+ },
112
+ {
113
+ "pinId": "P.21",
114
+ "x": 0.6000000000000001,
115
+ "y": 0.5
116
+ },
117
+ {
118
+ "pinId": "P.22",
119
+ "x": 0.6000000000000001,
120
+ "y": 0.7
121
+ },
122
+ {
123
+ "pinId": "P.23",
124
+ "x": 0.6000000000000001,
125
+ "y": 0.8999999999999999
126
+ },
127
+ {
128
+ "pinId": "P.24",
129
+ "x": 0.6000000000000001,
130
+ "y": 1.0999999999999999
131
+ }
132
+ ]
133
+ }
134
+ ],
135
+ "directConnections": [
136
+ {
137
+ "pinIds": ["P.1", "P.18"],
138
+ "netId": "P.pin1 to P.pin18"
139
+ },
140
+ {
141
+ "pinIds": ["P.2", "P.17"],
142
+ "netId": "P.pin2 to P.pin17"
143
+ },
144
+ {
145
+ "pinIds": ["P.3", "P.16"],
146
+ "netId": "P.pin3 to P.pin16"
147
+ },
148
+ {
149
+ "pinIds": ["P.4", "P.15"],
150
+ "netId": "P.pin4 to P.pin15"
151
+ },
152
+ {
153
+ "pinIds": ["P.5", "P.14"],
154
+ "netId": "P.pin5 to P.pin14"
155
+ },
156
+ {
157
+ "pinIds": ["P.6", "P.13"],
158
+ "netId": "P.pin6 to P.pin13"
159
+ },
160
+ {
161
+ "pinIds": ["P.7", "P.19"],
162
+ "netId": "P.pin7 to P.pin19"
163
+ }
164
+ ],
165
+ "netConnections": [],
166
+ "availableNetLabelOrientations": {},
167
+ "maxMspPairDistance": 2.4
168
+ }