@tscircuit/schematic-trace-solver 0.0.95 → 0.0.97

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 (21) hide show
  1. package/dist/index.d.ts +7 -6
  2. package/dist/index.js +271 -221
  3. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +55 -14
  4. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions.ts +31 -0
  5. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +11 -7
  6. package/lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem.ts +47 -40
  7. package/lib/types/InputProblem.ts +1 -0
  8. package/package.json +1 -1
  9. package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +1 -1
  10. package/tests/bug-reports/bug-report-20260706T213649Z/bug-report-20260706T213649Z.test.ts +3 -1
  11. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +1 -1
  12. package/tests/bug-reports/bug-report-20260707T092615Z/bug-report-20260707T092615Z.test.ts +3 -1
  13. package/tests/repros/__snapshots__/repro-rp2040-gamepad-trace-alignment.snap.svg +178 -0
  14. package/tests/repros/__snapshots__/repro-rp2040-zero-crystal-fallback-netlabels.snap.svg +54 -0
  15. package/tests/repros/assets/repro-rp2040-gamepad-trace-alignment.input.json +237 -0
  16. package/tests/repros/assets/repro-rp2040-zero-crystal-fallback-netlabels.input.json +121 -0
  17. package/tests/repros/repro-rp2040-gamepad-trace-alignment.test.ts +14 -0
  18. package/tests/repros/repro-rp2040-zero-crystal-fallback-netlabels.test.ts +16 -0
  19. package/tests/solvers/SchematicTraceSingleLineSolver2/segment-intersects-rect-interior.test.ts +15 -0
  20. package/tests/solvers/SchematicTraceSingleLineSolver2/segment-overlaps-rect-boundary-boundary.test.ts +18 -0
  21. package/tests/solvers/SchematicTraceSingleLineSolver2/segment-overlaps-rect-boundary-interior.test.ts +18 -0
@@ -1,24 +1,29 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import { calculateElbow } from "calculate-elbow"
1
3
  import type { GraphicsObject } from "graphics-debug"
2
- import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
3
4
  import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
4
5
  import type { MspConnectionPair } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
5
- import type { InputChip, InputProblem, PinId } from "lib/types/InputProblem"
6
- import type { Point } from "@tscircuit/math-utils"
7
- import { calculateElbow } from "calculate-elbow"
6
+ import { getDimsForOrientation } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
7
+ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
8
+ import type { InputChip, InputProblem } from "lib/types/InputProblem"
9
+ import type { FacingDirection } from "lib/utils/dir"
10
+ import type { RectPadding } from "lib/utils/textBoxBounds"
8
11
  import { getPinDirection } from "../SchematicTraceSingleLineSolver/getPinDirection"
9
- import { getObstacleRects, type ObstacleRect } from "./rect"
10
- import { findFirstCollision, isHorizontal, isVertical } from "./collisions"
12
+ import { calculateDirectShortPath } from "./calculateDirectShortPath"
11
13
  import {
14
+ findFirstCollision,
15
+ isHorizontal,
16
+ isVertical,
17
+ segmentOverlapsRectBoundary,
18
+ } from "./collisions"
19
+ import {
20
+ type Axis,
12
21
  aabbFromPoints,
13
22
  candidateMidsFromSet,
14
23
  midBetweenPointAndRect,
15
- type Axis,
16
24
  } from "./mid"
17
25
  import { pathKey, shiftSegmentOrth } from "./pathOps"
18
- import type { FacingDirection } from "lib/utils/dir"
19
- import { getDimsForOrientation } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
20
- import type { RectPadding } from "lib/utils/textBoxBounds"
21
- import { calculateDirectShortPath } from "./calculateDirectShortPath"
26
+ import { getObstacleRects, type ObstacleRect } from "./rect"
22
27
 
23
28
  type PathKey = string
24
29
 
@@ -268,9 +273,42 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
268
273
  const collision = findFirstCollision(path, this.obstacles, {
269
274
  excludeRectsForSegment: (segIndex) => {
270
275
  const lastSegIndex = path.length - 2
276
+ const excludedRects = new Set<ObstacleRect>()
277
+
271
278
  if (segIndex === 0 || segIndex === lastSegIndex) {
272
- return this.textObstacles
279
+ for (const textObstacle of this.textObstacles) {
280
+ excludedRects.add(textObstacle)
281
+ }
273
282
  }
283
+
284
+ const segmentStart = path[segIndex]!
285
+ const segmentEnd = path[segIndex + 1]!
286
+ const endpointEpsilon = 1e-9
287
+ const segmentTouchesPin = (pin: MspConnectionPair["pins"][number]) =>
288
+ [segmentStart, segmentEnd].some(
289
+ (point) =>
290
+ Math.abs(point.x - pin.x) <= endpointEpsilon &&
291
+ Math.abs(point.y - pin.y) <= endpointEpsilon,
292
+ )
293
+
294
+ for (const pin of this.pins) {
295
+ if (!segmentTouchesPin(pin)) continue
296
+ const endpointChipObstacle = this.obstacles.find(
297
+ (obstacle) =>
298
+ obstacle.kind === "chip" && obstacle.chipId === pin.chipId,
299
+ )
300
+ if (
301
+ endpointChipObstacle &&
302
+ segmentOverlapsRectBoundary(
303
+ segmentStart,
304
+ segmentEnd,
305
+ endpointChipObstacle,
306
+ )
307
+ ) {
308
+ excludedRects.add(endpointChipObstacle)
309
+ }
310
+ }
311
+
274
312
  const adjacentEndpointSegIndex =
275
313
  segIndex === 1
276
314
  ? 2
@@ -286,9 +324,12 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
286
324
  path[adjacentEndpointSegIndex + 1]!,
287
325
  )
288
326
  ) {
289
- return this.endpointTextObstacles
327
+ for (const textObstacle of this.endpointTextObstacles) {
328
+ excludedRects.add(textObstacle)
329
+ }
290
330
  }
291
- return new Set<ObstacleRect>()
331
+
332
+ return excludedRects
292
333
  },
293
334
  })
294
335
 
@@ -35,6 +35,37 @@ export const segmentIntersectsRect = <TRect extends RectBounds>(
35
35
  }
36
36
  }
37
37
 
38
+ export const segmentOverlapsRectBoundary = <TRect extends RectBounds>(
39
+ a: Point,
40
+ b: Point,
41
+ r: TRect,
42
+ eps = EPS,
43
+ ): boolean => {
44
+ if (isVertical(a, b, eps)) {
45
+ const onVerticalBoundary =
46
+ Math.abs(a.x - r.minX) <= eps || Math.abs(a.x - r.maxX) <= eps
47
+ if (!onVerticalBoundary) return false
48
+
49
+ const overlap =
50
+ Math.min(Math.max(a.y, b.y), r.maxY) -
51
+ Math.max(Math.min(a.y, b.y), r.minY)
52
+ return overlap > eps
53
+ }
54
+
55
+ if (isHorizontal(a, b, eps)) {
56
+ const onHorizontalBoundary =
57
+ Math.abs(a.y - r.minY) <= eps || Math.abs(a.y - r.maxY) <= eps
58
+ if (!onHorizontalBoundary) return false
59
+
60
+ const overlap =
61
+ Math.min(Math.max(a.x, b.x), r.maxX) -
62
+ Math.max(Math.min(a.x, b.x), r.minX)
63
+ return overlap > eps
64
+ }
65
+
66
+ return false
67
+ }
68
+
38
69
  export const findFirstCollision = <TRect extends RectBounds>(
39
70
  pts: Point[],
40
71
  rects: TRect[],
@@ -61,9 +61,8 @@ function definePipelineStep<
61
61
  }
62
62
  }
63
63
 
64
- export interface SchematicTracePipelineSolverParams {
65
- inputProblem: InputProblem
66
- allowLongDistanceTraces?: boolean
64
+ interface Options {
65
+ hideRatsNet?: boolean
67
66
  }
68
67
 
69
68
  export class SchematicTracePipelineSolver extends BaseSolver {
@@ -89,6 +88,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
89
88
  firstIterationOfPhase: Record<string, number>
90
89
 
91
90
  inputProblem: InputProblem
91
+ hideRatsNet: boolean
92
92
 
93
93
  pipelineDef = [
94
94
  definePipelineStep(
@@ -318,8 +318,9 @@ export class SchematicTracePipelineSolver extends BaseSolver {
318
318
  ),
319
319
  ]
320
320
 
321
- constructor(inputProblem: InputProblem) {
321
+ constructor(inputProblem: InputProblem, opts?: Options) {
322
322
  super()
323
+ this.hideRatsNet = opts?.hideRatsNet ?? false
323
324
  this.inputProblem = this.cloneAndCorrectInputProblem(inputProblem)
324
325
  this.MAX_ITERATIONS = 1e6
325
326
  this.startTimeOfPhase = {}
@@ -330,8 +331,8 @@ export class SchematicTracePipelineSolver extends BaseSolver {
330
331
 
331
332
  override getConstructorParams(): ConstructorParameters<
332
333
  typeof SchematicTracePipelineSolver
333
- >[0] {
334
- return this.inputProblem
334
+ > {
335
+ return [this.inputProblem, { hideRatsNet: this.hideRatsNet }]
335
336
  }
336
337
 
337
338
  currentPipelineStepIndex = 0
@@ -340,6 +341,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
340
341
  const cloned: InputProblem = structuredClone({
341
342
  ...original,
342
343
  _chipObstacleSpatialIndex: undefined,
344
+ _hideRatsNet: this.hideRatsNet,
343
345
  })
344
346
 
345
347
  // First, expand chips so existing pin coordinates sit on or within their edges without shrinking.
@@ -399,7 +401,9 @@ export class SchematicTracePipelineSolver extends BaseSolver {
399
401
  return this.activeSubSolver.visualize()
400
402
 
401
403
  const visualizations = [
402
- visualizeInputProblem(this.inputProblem),
404
+ visualizeInputProblem(this.inputProblem, {
405
+ hideRatsNet: this.hideRatsNet,
406
+ }),
403
407
  ...(this.pipelineDef
404
408
  .map((p) => (this as any)[p.solverName]?.visualize())
405
409
  .filter(Boolean)
@@ -9,9 +9,14 @@ export const visualizeInputProblem = (
9
9
  opts: {
10
10
  chipAlpha?: number
11
11
  connectionAlpha?: number
12
+ hideRatsNet?: boolean
12
13
  } = {},
13
14
  ): GraphicsObject => {
14
- const { connectionAlpha = 0.8, chipAlpha = 0.8 } = opts
15
+ const {
16
+ connectionAlpha = 0.8,
17
+ chipAlpha = 0.8,
18
+ hideRatsNet = inputProblem._hideRatsNet ?? false,
19
+ } = opts
15
20
  const graphics: Pick<
16
21
  Required<GraphicsObject>,
17
22
  "lines" | "points" | "rects"
@@ -58,48 +63,50 @@ export const visualizeInputProblem = (
58
63
  } as any)
59
64
  }
60
65
 
61
- for (const directConn of inputProblem.directConnections) {
62
- const [pinId1, pinId2] = directConn.pinIds
63
- const pin1 = pinIdMap.get(pinId1)!
64
- const pin2 = pinIdMap.get(pinId2)!
65
- if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
66
- continue
66
+ if (!hideRatsNet) {
67
+ for (const directConn of inputProblem.directConnections) {
68
+ const [pinId1, pinId2] = directConn.pinIds
69
+ const pin1 = pinIdMap.get(pinId1)!
70
+ const pin2 = pinIdMap.get(pinId2)!
71
+ if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
72
+ continue
73
+ }
74
+ graphics.lines.push({
75
+ points: [
76
+ {
77
+ x: pin1.x,
78
+ y: pin1.y,
79
+ },
80
+ {
81
+ x: pin2.x,
82
+ y: pin2.y,
83
+ },
84
+ ],
85
+ strokeColor: getColorFromString(
86
+ directConn.netId ?? `${pinId1}-${pinId2}`,
87
+ connectionAlpha,
88
+ ),
89
+ })
67
90
  }
68
- graphics.lines.push({
69
- points: [
70
- {
71
- x: pin1.x,
72
- y: pin1.y,
73
- },
74
- {
75
- x: pin2.x,
76
- y: pin2.y,
77
- },
78
- ],
79
- strokeColor: getColorFromString(
80
- directConn.netId ?? `${pinId1}-${pinId2}`,
81
- connectionAlpha,
82
- ),
83
- })
84
- }
85
91
 
86
- for (const netConn of inputProblem.netConnections) {
87
- const pins = netConn.pinIds.map((pinId) => pinIdMap.get(pinId)!)
88
- for (let i = 0; i < pins.length - 1; i++) {
89
- for (let j = i + 1; j < pins.length; j++) {
90
- const pin1 = pins[i]!
91
- const pin2 = pins[j]!
92
- if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
93
- continue
92
+ for (const netConn of inputProblem.netConnections) {
93
+ const pins = netConn.pinIds.map((pinId) => pinIdMap.get(pinId)!)
94
+ for (let i = 0; i < pins.length - 1; i++) {
95
+ for (let j = i + 1; j < pins.length; j++) {
96
+ const pin1 = pins[i]!
97
+ const pin2 = pins[j]!
98
+ if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
99
+ continue
100
+ }
101
+ graphics.lines.push({
102
+ points: [
103
+ { x: pin1.x, y: pin1.y },
104
+ { x: pin2.x, y: pin2.y },
105
+ ],
106
+ strokeColor: getColorFromString(netConn.netId, connectionAlpha),
107
+ strokeDash: "4 2",
108
+ })
94
109
  }
95
- graphics.lines.push({
96
- points: [
97
- { x: pin1.x, y: pin1.y },
98
- { x: pin2.x, y: pin2.y },
99
- ],
100
- strokeColor: getColorFromString(netConn.netId, connectionAlpha),
101
- strokeDash: "4 2",
102
- })
103
110
  }
104
111
  }
105
112
  }
@@ -53,4 +53,5 @@ export interface InputProblem {
53
53
  maxMspPairDistance?: number
54
54
 
55
55
  _chipObstacleSpatialIndex?: ChipObstacleSpatialIndex
56
+ _hideRatsNet?: boolean
56
57
  }
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.95",
4
+ "version": "0.0.97",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",