@tscircuit/schematic-trace-solver 0.0.1

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 (51) hide show
  1. package/.claude/settings.local.json +6 -0
  2. package/.github/workflows/bun-formatcheck.yml +26 -0
  3. package/.github/workflows/bun-pver-release.yml +28 -0
  4. package/.github/workflows/bun-test.yml +28 -0
  5. package/.github/workflows/bun-typecheck.yml +26 -0
  6. package/LICENSE +21 -0
  7. package/README.md +75 -0
  8. package/biome.json +93 -0
  9. package/bunfig.toml +5 -0
  10. package/cosmos.config.json +6 -0
  11. package/cosmos.decorator.tsx +21 -0
  12. package/dist/index.d.ts +420 -0
  13. package/dist/index.js +7863 -0
  14. package/index.html +17 -0
  15. package/lib/data-structures/ChipObstacleSpatialIndex.ts +70 -0
  16. package/lib/index.ts +2 -0
  17. package/lib/solvers/BaseSolver/BaseSolver.ts +83 -0
  18. package/lib/solvers/GuidelinesSolver/GuidelinesSolver.ts +138 -0
  19. package/lib/solvers/GuidelinesSolver/getGeneratorForAllChipPairs.ts +39 -0
  20. package/lib/solvers/GuidelinesSolver/getHorizontalGuidelineY.ts +18 -0
  21. package/lib/solvers/GuidelinesSolver/getInputChipBounds.ts +20 -0
  22. package/lib/solvers/GuidelinesSolver/getVerticalGuidelineX.ts +18 -0
  23. package/lib/solvers/GuidelinesSolver/visualizeGuidelines.ts +45 -0
  24. package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +156 -0
  25. package/lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem.ts +20 -0
  26. package/lib/solvers/MspConnectionPairSolver/getMspConnectionPairsFromPins.ts +96 -0
  27. package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +192 -0
  28. package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver.ts +479 -0
  29. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver.ts +127 -0
  30. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/SchematicTraceSingleLineSolver.ts +191 -0
  31. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/generateElbowVariants.ts +132 -0
  32. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection.ts +42 -0
  33. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +244 -0
  34. package/lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem.ts +89 -0
  35. package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/TraceOverlapIssueSolver.ts +180 -0
  36. package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts +264 -0
  37. package/lib/types/InputProblem.ts +40 -0
  38. package/lib/utils/dir.ts +16 -0
  39. package/lib/utils/getAllPossibleOrderingsGenerator.ts +47 -0
  40. package/lib/utils/getColorFromString.ts +7 -0
  41. package/package.json +29 -0
  42. package/site/components/GenericSolverDebugger.tsx +0 -0
  43. package/site/components/PipelineDebugger.tsx +29 -0
  44. package/site/components/PipelineStageTable.tsx +149 -0
  45. package/site/components/SolverBreadcrumbInputDownloader.tsx +62 -0
  46. package/site/components/SolverToolbar.tsx +128 -0
  47. package/site/examples/example01-basic.page.tsx +104 -0
  48. package/tests/functions/generateElbowVariants.test.ts +98 -0
  49. package/tests/functions/getOrthogonalMinimumSpanningTree.test.ts +23 -0
  50. package/tsconfig.json +37 -0
  51. package/vite.config.ts +12 -0
@@ -0,0 +1,479 @@
1
+ import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
2
+ import type {
3
+ NetLabelPlacement,
4
+ OverlappingSameNetTraceGroup,
5
+ } from "../NetLabelPlacementSolver"
6
+ import type { InputChip, InputProblem } from "lib/types/InputProblem"
7
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
8
+ import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
9
+ import type { FacingDirection } from "lib/utils/dir"
10
+ import type { GraphicsObject } from "graphics-debug"
11
+ import { ChipObstacleSpatialIndex } from "lib/data-structures/ChipObstacleSpatialIndex"
12
+ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
13
+ import { getColorFromString } from "lib/utils/getColorFromString"
14
+
15
+ export const NET_LABEL_HORIZONTAL_WIDTH = 0.4
16
+ export const NET_LABEL_HORIZONTAL_HEIGHT = 0.2
17
+ // NOTE: net labels, when in the y+/y- orientation, are rotated and therefore
18
+ // the width/height are swapped
19
+
20
+ /**
21
+ * Find a location in the overlappingSameNetTraceGroup where a net label should
22
+ * be placed. We do this by looking for the largest chip, and starting our
23
+ * search from the segment directly connected to the largest chip. We then
24
+ * travel along the segment, moving to any connected segment. Each step, we
25
+ * check a specific segment
26
+ *
27
+ * When checking a segment, we check the following locations with each
28
+ * orientation:
29
+ * - The start of the segment
30
+ * - The start of the segment, plus the width of the net label
31
+ * - The end of the segment
32
+ * - The end of the segment, minus the width of the net label
33
+ *
34
+ * When checking a location, we check for the following:
35
+ * 1. Would placing a net label at this location cause a collision with a chip?
36
+ * 2. Would placing a net label at this location cause a collision with ANY
37
+ * trace? (Note: you must offset the anchor point slightly from the trace to
38
+ * avoid counting the point where the net label contacts the trace)
39
+ *
40
+ * The first location that satisfies the above conditions, in our traversal
41
+ * order from the largest chip, is the location we return in netLabelPlacement
42
+ */
43
+ export class SingleNetLabelPlacementSolver extends BaseSolver {
44
+ inputProblem: InputProblem
45
+ inputTraceMap: Record<MspConnectionPairId, SolvedTracePath>
46
+ overlappingSameNetTraceGroup: OverlappingSameNetTraceGroup
47
+ availableOrientations: Array<FacingDirection>
48
+
49
+ chipObstacleSpatialIndex: ChipObstacleSpatialIndex
50
+
51
+ netLabelPlacement: NetLabelPlacement | null = null
52
+ testedCandidates: Array<{
53
+ center: { x: number; y: number }
54
+ width: number
55
+ height: number
56
+ bounds: { minX: number; minY: number; maxX: number; maxY: number }
57
+ anchor: { x: number; y: number }
58
+ orientation: FacingDirection
59
+ status: "ok" | "chip-collision" | "trace-collision" | "parallel-to-segment"
60
+ hostSegIndex: number
61
+ }> = []
62
+
63
+ constructor(params: {
64
+ inputProblem: InputProblem
65
+ inputTraceMap: Record<MspConnectionPairId, SolvedTracePath>
66
+ overlappingSameNetTraceGroup: OverlappingSameNetTraceGroup
67
+ availableOrientations: FacingDirection[]
68
+ }) {
69
+ super()
70
+ this.inputProblem = params.inputProblem
71
+ this.inputTraceMap = params.inputTraceMap
72
+ this.overlappingSameNetTraceGroup = params.overlappingSameNetTraceGroup
73
+ this.availableOrientations = params.availableOrientations
74
+
75
+ this.chipObstacleSpatialIndex =
76
+ params.inputProblem._chipObstacleSpatialIndex ??
77
+ new ChipObstacleSpatialIndex(params.inputProblem.chips)
78
+ }
79
+
80
+ private getDimsForOrientation(orientation: FacingDirection) {
81
+ if (orientation === "y+" || orientation === "y-") {
82
+ return {
83
+ width: NET_LABEL_HORIZONTAL_HEIGHT,
84
+ height: NET_LABEL_HORIZONTAL_WIDTH,
85
+ }
86
+ }
87
+ return {
88
+ width: NET_LABEL_HORIZONTAL_WIDTH,
89
+ height: NET_LABEL_HORIZONTAL_HEIGHT,
90
+ }
91
+ }
92
+
93
+ private getCenterFromAnchor(
94
+ anchor: { x: number; y: number },
95
+ orientation: FacingDirection,
96
+ width: number,
97
+ height: number,
98
+ ) {
99
+ switch (orientation) {
100
+ case "x+":
101
+ return { x: anchor.x + width / 2, y: anchor.y }
102
+ case "x-":
103
+ return { x: anchor.x - width / 2, y: anchor.y }
104
+ case "y+":
105
+ return { x: anchor.x, y: anchor.y + height / 2 }
106
+ case "y-":
107
+ return { x: anchor.x, y: anchor.y - height / 2 }
108
+ }
109
+ }
110
+
111
+ private getRectBounds(
112
+ center: { x: number; y: number },
113
+ w: number,
114
+ h: number,
115
+ ) {
116
+ return {
117
+ minX: center.x - w / 2,
118
+ minY: center.y - h / 2,
119
+ maxX: center.x + w / 2,
120
+ maxY: center.y + h / 2,
121
+ }
122
+ }
123
+
124
+ private segmentIntersectsRect(
125
+ p1: { x: number; y: number },
126
+ p2: { x: number; y: number },
127
+ rect: { minX: number; minY: number; maxX: number; maxY: number },
128
+ EPS = 1e-9,
129
+ ): boolean {
130
+ const isVert = Math.abs(p1.x - p2.x) < EPS
131
+ const isHorz = Math.abs(p1.y - p2.y) < EPS
132
+ if (!isVert && !isHorz) return false
133
+
134
+ if (isVert) {
135
+ const x = p1.x
136
+ if (x < rect.minX - EPS || x > rect.maxX + EPS) return false
137
+ const segMinY = Math.min(p1.y, p2.y)
138
+ const segMaxY = Math.max(p1.y, p2.y)
139
+ const overlap =
140
+ Math.min(segMaxY, rect.maxY) - Math.max(segMinY, rect.minY)
141
+ return overlap > EPS
142
+ } else {
143
+ const y = p1.y
144
+ if (y < rect.minY - EPS || y > rect.maxY + EPS) return false
145
+ const segMinX = Math.min(p1.x, p2.x)
146
+ const segMaxX = Math.max(p1.x, p2.x)
147
+ const overlap =
148
+ Math.min(segMaxX, rect.maxX) - Math.max(segMinX, rect.minX)
149
+ return overlap > EPS
150
+ }
151
+ }
152
+
153
+ private rectIntersectsAnyTrace(
154
+ bounds: { minX: number; minY: number; maxX: number; maxY: number },
155
+ hostPathId: MspConnectionPairId,
156
+ hostSegIndex: number,
157
+ ): boolean {
158
+ for (const [pairId, solved] of Object.entries(this.inputTraceMap)) {
159
+ const pts = solved.tracePath
160
+ for (let i = 0; i < pts.length - 1; i++) {
161
+ if (pairId === hostPathId && i === hostSegIndex) continue
162
+ if (this.segmentIntersectsRect(pts[i]!, pts[i + 1]!, bounds))
163
+ return true
164
+ }
165
+ }
166
+ return false
167
+ }
168
+
169
+ override _step() {
170
+ if (this.netLabelPlacement) {
171
+ this.solved = true
172
+ return
173
+ }
174
+
175
+ // Prefer starting from the trace connected to the "largest" chip (most pins)
176
+ const groupId = this.overlappingSameNetTraceGroup.globalConnNetId
177
+ const chipsById: Record<string, InputChip> = Object.fromEntries(
178
+ this.inputProblem.chips.map((c) => [c.chipId, c]),
179
+ )
180
+ const groupTraces = Object.values(this.inputTraceMap).filter(
181
+ (t) => t.globalConnNetId === groupId,
182
+ )
183
+ const chipIdsInGroup = new Set<string>()
184
+ for (const t of groupTraces) {
185
+ chipIdsInGroup.add(t.pins[0].chipId)
186
+ chipIdsInGroup.add(t.pins[1].chipId)
187
+ }
188
+ let largestChipId: string | null = null
189
+ let largestPinCount = -1
190
+ for (const id of chipIdsInGroup) {
191
+ const chip = chipsById[id]
192
+ const count = chip?.pins?.length ?? 0
193
+ if (count > largestPinCount) {
194
+ largestPinCount = count
195
+ largestChipId = id
196
+ }
197
+ }
198
+ const lengthOf = (path: SolvedTracePath) => {
199
+ let sum = 0
200
+ const pts = path.tracePath
201
+ for (let i = 0; i < pts.length - 1; i++) {
202
+ sum +=
203
+ Math.abs(pts[i + 1]!.x - pts[i]!.x) +
204
+ Math.abs(pts[i + 1]!.y - pts[i]!.y)
205
+ }
206
+ return sum
207
+ }
208
+ const hostCandidates =
209
+ largestChipId == null
210
+ ? []
211
+ : groupTraces.filter(
212
+ (t) =>
213
+ t.pins[0].chipId === largestChipId ||
214
+ t.pins[1].chipId === largestChipId,
215
+ )
216
+ let host =
217
+ hostCandidates.length > 0
218
+ ? hostCandidates.reduce((a, b) => (lengthOf(a) >= lengthOf(b) ? a : b))
219
+ : this.overlappingSameNetTraceGroup.overlappingTraces
220
+
221
+ // Ensure we traverse the host path starting at the segment attached to the largest chip's pin
222
+ let pts = host.tracePath.slice()
223
+ if (largestChipId) {
224
+ const largePin =
225
+ host.pins[0].chipId === largestChipId ? host.pins[0] : host.pins[1]
226
+ const d0 =
227
+ Math.abs(pts[0].x - largePin.x) + Math.abs(pts[0].y - largePin.y)
228
+ const dL =
229
+ Math.abs(pts[pts.length - 1].x - largePin.x) +
230
+ Math.abs(pts[pts.length - 1].y - largePin.y)
231
+ if (d0 > dL) {
232
+ pts = pts.slice().reverse()
233
+ }
234
+ }
235
+
236
+ const orientations =
237
+ this.availableOrientations.length > 0
238
+ ? this.availableOrientations
239
+ : (["x+", "x-", "y+", "y-"] as FacingDirection[])
240
+
241
+ const EPS = 1e-6
242
+ const anchorsForSegment = (
243
+ a: { x: number; y: number },
244
+ b: { x: number; y: number },
245
+ ) => {
246
+ // Start, midpoint, end
247
+ return [
248
+ { x: a.x, y: a.y },
249
+ { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 },
250
+ { x: b.x, y: b.y },
251
+ ]
252
+ }
253
+
254
+ for (let si = 0; si < pts.length - 1; si++) {
255
+ const a = pts[si]!
256
+ const b = pts[si + 1]!
257
+ const isH = Math.abs(a.y - b.y) < EPS
258
+ const isV = Math.abs(a.x - b.x) < EPS
259
+ if (!isH && !isV) continue
260
+
261
+ // Only consider orientations perpendicular to the segment to avoid
262
+ // self-overlap with the host segment.
263
+ const segmentAllowed: FacingDirection[] = isH
264
+ ? (["y+", "y-"] as FacingDirection[])
265
+ : (["x+", "x-"] as FacingDirection[])
266
+ const candidateOrients = orientations.filter((o) =>
267
+ segmentAllowed.includes(o),
268
+ )
269
+ if (candidateOrients.length === 0) continue
270
+
271
+ const anchors = anchorsForSegment(a, b)
272
+ for (const anchor of anchors) {
273
+ for (const orientation of candidateOrients) {
274
+ const { width, height } = this.getDimsForOrientation(orientation)
275
+ const center = this.getCenterFromAnchor(
276
+ anchor,
277
+ orientation,
278
+ width,
279
+ height,
280
+ )
281
+
282
+ // Small outward offset to avoid counting the touching trace as a collision
283
+ const outward =
284
+ orientation === "x+"
285
+ ? { x: 1, y: 0 }
286
+ : orientation === "x-"
287
+ ? { x: -1, y: 0 }
288
+ : orientation === "y+"
289
+ ? { x: 0, y: 1 }
290
+ : { x: 0, y: -1 }
291
+ const offset = 1e-4
292
+ const testCenter = {
293
+ x: center.x + outward.x * offset,
294
+ y: center.y + outward.y * offset,
295
+ }
296
+ const bounds = this.getRectBounds(testCenter, width, height)
297
+
298
+ // Chip collision check
299
+ const chips = this.chipObstacleSpatialIndex.getChipsInBounds(bounds)
300
+ if (chips.length > 0) {
301
+ this.testedCandidates.push({
302
+ center: testCenter,
303
+ width,
304
+ height,
305
+ bounds,
306
+ anchor,
307
+ orientation,
308
+ status: "chip-collision",
309
+ hostSegIndex: si,
310
+ })
311
+ continue
312
+ }
313
+
314
+ // Trace collision check (ignore the host segment)
315
+ if (this.rectIntersectsAnyTrace(bounds, host.mspPairId, si)) {
316
+ this.testedCandidates.push({
317
+ center: testCenter,
318
+ width,
319
+ height,
320
+ bounds,
321
+ anchor,
322
+ orientation,
323
+ status: "trace-collision",
324
+ hostSegIndex: si,
325
+ })
326
+ continue
327
+ }
328
+
329
+ // Found a valid placement
330
+ this.testedCandidates.push({
331
+ center: testCenter,
332
+ width,
333
+ height,
334
+ bounds,
335
+ anchor,
336
+ orientation,
337
+ status: "ok",
338
+ hostSegIndex: si,
339
+ })
340
+
341
+ this.netLabelPlacement = {
342
+ globalConnNetId: this.overlappingSameNetTraceGroup.globalConnNetId,
343
+ dcConnNetId: host.dcConnNetId,
344
+ orientation,
345
+ anchorPoint: anchor,
346
+ width,
347
+ height,
348
+ center,
349
+ }
350
+ this.solved = true
351
+ return
352
+ }
353
+ }
354
+ }
355
+
356
+ this.failed = true
357
+ this.error = "Could not place net label without collisions"
358
+ }
359
+
360
+ override visualize(): GraphicsObject {
361
+ const graphics = visualizeInputProblem(this.inputProblem)
362
+
363
+ // Visualize the entire trace group for this net id
364
+ const groupId = this.overlappingSameNetTraceGroup.globalConnNetId
365
+ // Choose host as in _step: the trace that touches the largest chip in the group
366
+ const chipsById: Record<string, InputChip> = Object.fromEntries(
367
+ this.inputProblem.chips.map((c) => [c.chipId, c]),
368
+ )
369
+ const groupTraces = Object.values(this.inputTraceMap).filter(
370
+ (t) => t.globalConnNetId === groupId,
371
+ )
372
+ const chipIdsInGroup = new Set<string>()
373
+ for (const t of groupTraces) {
374
+ chipIdsInGroup.add(t.pins[0].chipId)
375
+ chipIdsInGroup.add(t.pins[1].chipId)
376
+ }
377
+ let largestChipId: string | null = null
378
+ let largestPinCount = -1
379
+ for (const id of chipIdsInGroup) {
380
+ const chip = chipsById[id]
381
+ const count = chip?.pins?.length ?? 0
382
+ if (count > largestPinCount) {
383
+ largestPinCount = count
384
+ largestChipId = id
385
+ }
386
+ }
387
+ const lengthOf = (path: SolvedTracePath) => {
388
+ let sum = 0
389
+ const pts = path.tracePath
390
+ for (let i = 0; i < pts.length - 1; i++) {
391
+ sum +=
392
+ Math.abs(pts[i + 1]!.x - pts[i]!.x) +
393
+ Math.abs(pts[i + 1]!.y - pts[i]!.y)
394
+ }
395
+ return sum
396
+ }
397
+ const hostCandidates =
398
+ largestChipId == null
399
+ ? []
400
+ : groupTraces.filter(
401
+ (t) =>
402
+ t.pins[0].chipId === largestChipId ||
403
+ t.pins[1].chipId === largestChipId,
404
+ )
405
+ const host =
406
+ hostCandidates.length > 0
407
+ ? hostCandidates.reduce((a, b) => (lengthOf(a) >= lengthOf(b) ? a : b))
408
+ : this.overlappingSameNetTraceGroup.overlappingTraces
409
+ const groupStroke = getColorFromString(groupId, 0.9)
410
+ const groupFill = getColorFromString(groupId, 0.5)
411
+
412
+ for (const trace of Object.values(this.inputTraceMap)) {
413
+ if (trace.globalConnNetId !== groupId) continue
414
+ const isHost = trace.mspPairId === host.mspPairId
415
+ graphics.lines!.push({
416
+ points: trace.tracePath,
417
+ strokeColor: isHost ? groupStroke : groupFill,
418
+ strokeWidth: isHost ? 0.006 : 0.003,
419
+ strokeDash: isHost ? undefined : "4 2",
420
+ } as any)
421
+ }
422
+
423
+ // Visualize all tested candidate rectangles with reason coloring
424
+ for (const c of this.testedCandidates) {
425
+ const fill =
426
+ c.status === "ok"
427
+ ? "rgba(0, 180, 0, 0.25)"
428
+ : c.status === "chip-collision"
429
+ ? "rgba(220, 0, 0, 0.25)"
430
+ : c.status === "trace-collision"
431
+ ? "rgba(220, 140, 0, 0.25)"
432
+ : "rgba(120, 120, 120, 0.15)"
433
+ const stroke =
434
+ c.status === "ok"
435
+ ? "green"
436
+ : c.status === "chip-collision"
437
+ ? "red"
438
+ : c.status === "trace-collision"
439
+ ? "orange"
440
+ : "gray"
441
+
442
+ graphics.rects!.push({
443
+ center: {
444
+ x: (c.bounds.minX + c.bounds.maxX) / 2,
445
+ y: (c.bounds.minY + c.bounds.maxY) / 2,
446
+ },
447
+ width: c.width,
448
+ height: c.height,
449
+ fill,
450
+ strokeColor: stroke,
451
+ } as any)
452
+
453
+ graphics.points!.push({
454
+ x: c.anchor.x,
455
+ y: c.anchor.y,
456
+ color: stroke,
457
+ } as any)
458
+ }
459
+
460
+ // Visualize the final accepted label (if any)
461
+ if (this.netLabelPlacement) {
462
+ const p = this.netLabelPlacement
463
+ graphics.rects!.push({
464
+ center: p.center,
465
+ width: p.width,
466
+ height: p.height,
467
+ fill: "rgba(0, 128, 255, 0.35)",
468
+ strokeColor: "blue",
469
+ } as any)
470
+ graphics.points!.push({
471
+ x: p.anchorPoint.x,
472
+ y: p.anchorPoint.y,
473
+ color: "blue",
474
+ } as any)
475
+ }
476
+
477
+ return graphics
478
+ }
479
+ }
@@ -0,0 +1,127 @@
1
+ import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
2
+ import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
3
+ import { getBounds, type GraphicsObject } from "graphics-debug"
4
+ import type { InputChip, InputProblem } from "lib/types/InputProblem"
5
+ import type {
6
+ MspConnectionPair,
7
+ MspConnectionPairId,
8
+ } from "../MspConnectionPairSolver/MspConnectionPairSolver"
9
+ import type { ConnectivityMap } from "connectivity-map"
10
+ import { SchematicTraceSingleLineSolver } from "./SchematicTraceSingleLineSolver/SchematicTraceSingleLineSolver"
11
+ import type { Guideline } from "../GuidelinesSolver/GuidelinesSolver"
12
+ import { visualizeGuidelines } from "../GuidelinesSolver/visualizeGuidelines"
13
+ import type { Point } from "@tscircuit/math-utils"
14
+
15
+ export interface SolvedTracePath extends MspConnectionPair {
16
+ tracePath: Point[]
17
+ }
18
+
19
+ export class SchematicTraceLinesSolver extends BaseSolver {
20
+ inputProblem: InputProblem
21
+ guidelines: Guideline[]
22
+ mspConnectionPairs: MspConnectionPair[]
23
+
24
+ dcConnMap: ConnectivityMap
25
+ globalConnMap: ConnectivityMap
26
+
27
+ queuedConnectionPairs: MspConnectionPair[]
28
+ chipMap: Record<string, InputChip>
29
+
30
+ currentConnectionPair: MspConnectionPair | null = null
31
+
32
+ solvedTracePaths: Array<SolvedTracePath> = []
33
+
34
+ declare activeSubSolver: SchematicTraceSingleLineSolver | null
35
+
36
+ constructor(params: {
37
+ mspConnectionPairs: MspConnectionPair[]
38
+ chipMap: Record<string, InputChip>
39
+ dcConnMap: ConnectivityMap
40
+ globalConnMap: ConnectivityMap
41
+ inputProblem: InputProblem
42
+ guidelines: Guideline[]
43
+ }) {
44
+ super()
45
+ this.inputProblem = params.inputProblem
46
+ this.mspConnectionPairs = params.mspConnectionPairs
47
+ this.dcConnMap = params.dcConnMap
48
+ this.globalConnMap = params.globalConnMap
49
+ this.guidelines = params.guidelines
50
+ this.chipMap = params.chipMap
51
+
52
+ this.queuedConnectionPairs = [...this.mspConnectionPairs]
53
+ }
54
+
55
+ override getConstructorParams(): ConstructorParameters<
56
+ typeof SchematicTraceLinesSolver
57
+ >[0] {
58
+ return {
59
+ inputProblem: this.inputProblem,
60
+ chipMap: this.chipMap,
61
+ mspConnectionPairs: this.mspConnectionPairs,
62
+ dcConnMap: this.dcConnMap,
63
+ globalConnMap: this.globalConnMap,
64
+ guidelines: this.guidelines,
65
+ }
66
+ }
67
+
68
+ override _step() {
69
+ if (this.activeSubSolver?.solved) {
70
+ this.solvedTracePaths.push({
71
+ ...this.currentConnectionPair!,
72
+ tracePath: this.activeSubSolver!.solvedTracePath!,
73
+ })
74
+ this.activeSubSolver = null
75
+ this.currentConnectionPair = null
76
+ }
77
+ if (this.activeSubSolver?.failed) {
78
+ this.failed = true
79
+ this.error = this.activeSubSolver.error
80
+ return
81
+ }
82
+
83
+ if (this.activeSubSolver) {
84
+ this.activeSubSolver.step()
85
+ return
86
+ }
87
+
88
+ const connectionPair = this.queuedConnectionPairs.shift()
89
+ this.currentConnectionPair = connectionPair!
90
+
91
+ if (!connectionPair) {
92
+ this.solved = true
93
+ return
94
+ }
95
+
96
+ const { pins } = connectionPair
97
+
98
+ this.activeSubSolver = new SchematicTraceSingleLineSolver({
99
+ inputProblem: this.inputProblem,
100
+ pins,
101
+ chipMap: this.chipMap,
102
+ guidelines: this.guidelines,
103
+ })
104
+ }
105
+
106
+ override visualize(): GraphicsObject {
107
+ if (this.activeSubSolver) {
108
+ return this.activeSubSolver.visualize()
109
+ }
110
+ const graphics = visualizeInputProblem(this.inputProblem, {
111
+ chipAlpha: 0.1,
112
+ connectionAlpha: 0.1,
113
+ })
114
+
115
+ visualizeGuidelines({ guidelines: this.guidelines, graphics })
116
+
117
+ for (const { mspPairId, tracePath } of this.solvedTracePaths) {
118
+ graphics.lines!.push({
119
+ points: tracePath,
120
+ strokeWidth: 0.005,
121
+ strokeColor: "green",
122
+ })
123
+ }
124
+
125
+ return graphics
126
+ }
127
+ }