@tscircuit/schematic-trace-solver 0.0.174 → 0.0.176

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 (63) hide show
  1. package/dist/index.d.ts +5 -0
  2. package/dist/index.js +759 -91
  3. package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +5 -1
  4. package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +16 -1
  5. package/lib/solvers/InlineNetLabelSolver/restoreReroutesAroundSupersededLabels.ts +174 -0
  6. package/lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts +2 -30
  7. package/lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts +2 -0
  8. package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +748 -15
  9. package/lib/solvers/SameNetJunctionAlignmentSolver/findNearestSharedPinExitRail.ts +140 -0
  10. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +12 -5
  11. package/lib/utils/doesPathCoincideWithTraces.ts +17 -4
  12. package/lib/utils/pathIntersectsRenderedLabel.ts +33 -0
  13. package/package.json +1 -1
  14. package/site/bug-reports/bug-report-20260901T055358Z.page.tsx +4 -0
  15. package/site/bug-reports/bug-report-20260901T064117Z.page.tsx +4 -0
  16. package/site/bug-reports/bug-report-20260901T134241Z.page.tsx +4 -0
  17. package/tests/bug-reports/bug-report-20260707T134549Z/__snapshots__/bug-report-20260707T134549Z.snap.svg +2 -2
  18. package/tests/bug-reports/bug-report-20260707T134549Z/bug-report-20260707T134549Z.test.ts +9 -0
  19. package/tests/bug-reports/bug-report-20260707T230831Z/__snapshots__/bug-report-20260707T230831Z.snap.svg +2 -2
  20. package/tests/bug-reports/bug-report-20260717T042845Z/__snapshots__/bug-report-20260717T042845Z.snap.svg +2 -2
  21. package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +2 -2
  22. package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +2 -2
  23. package/tests/bug-reports/bug-report-20260730T061837Z/bug-report-20260730T061837Z.test.ts +8 -4
  24. package/tests/bug-reports/bug-report-20260804T171919Z/__snapshots__/bug-report-20260804T171919Z.snap.svg +2 -2
  25. package/tests/bug-reports/bug-report-20260805T061316Z/__snapshots__/bug-report-20260805T061316Z.snap.svg +2 -2
  26. package/tests/bug-reports/bug-report-20260806T061548Z/__snapshots__/bug-report-20260806T061548Z.snap.svg +2 -2
  27. package/tests/bug-reports/bug-report-20260825T045913Z/__snapshots__/bug-report-20260825T045913Z.snap.svg +2 -2
  28. package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +6 -6
  29. package/tests/bug-reports/bug-report-20260901T055358Z/__snapshots__/bug-report-20260901T055358Z.snap.svg +676 -0
  30. package/tests/bug-reports/bug-report-20260901T055358Z/bug-report-20260901T055358Z.json +2467 -0
  31. package/tests/bug-reports/bug-report-20260901T055358Z/bug-report-20260901T055358Z.test.ts +59 -0
  32. package/tests/bug-reports/bug-report-20260901T064117Z/__snapshots__/bug-report-20260901T064117Z.snap.svg +134 -0
  33. package/tests/bug-reports/bug-report-20260901T064117Z/bug-report-20260901T064117Z.json +215 -0
  34. package/tests/bug-reports/bug-report-20260901T064117Z/bug-report-20260901T064117Z.test.ts +12 -0
  35. package/tests/bug-reports/bug-report-20260901T134241Z/__snapshots__/bug-report-20260901T134241Z.snap.svg +115 -0
  36. package/tests/bug-reports/bug-report-20260901T134241Z/bug-report-20260901T134241Z.json +359 -0
  37. package/tests/bug-reports/bug-report-20260901T134241Z/bug-report-20260901T134241Z.test.ts +60 -0
  38. package/tests/examples/__snapshots__/example51.snap.svg +2 -2
  39. package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +10 -10
  40. package/tests/repros/__snapshots__/board1096-usb-label-overlap-iteration.snap.svg +2 -2
  41. package/tests/repros/__snapshots__/repro-bq25895-cross-net-trace-overlap.snap.svg +2 -2
  42. package/tests/repros/__snapshots__/repro-core-ground-inline-label-fallback.snap.svg +10 -10
  43. package/tests/repros/__snapshots__/repro-example35-minimize-trace-crossing.snap.svg +2 -2
  44. package/tests/repros/__snapshots__/repro-isolated-rs485-isow7841.snap.svg +18 -18
  45. package/tests/repros/__snapshots__/repro-pga300-redundant-ground-traces.snap.svg +18 -18
  46. package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +10 -10
  47. package/tests/repros/__snapshots__/repro-powerbank-3v-system-power.snap.svg +2 -2
  48. package/tests/repros/__snapshots__/repro-ti-power-output-section.snap.svg +2 -2
  49. package/tests/repros/__snapshots__/repro-tida-01389-hbridge.snap.svg +2 -2
  50. package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +2 -2
  51. package/tests/repros/__snapshots__/repro-trellis-core-gnd-net-label-overlap.snap.svg +134 -0
  52. package/tests/repros/__snapshots__/repro-wireless-mouse-charger-section.snap.svg +164 -0
  53. package/tests/repros/__snapshots__/repro161-power-section-autolayout.snap.svg +2 -2
  54. package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +2 -2
  55. package/tests/repros/assets/repro-trellis-core-gnd-net-label-overlap.input.json +178 -0
  56. package/tests/repros/assets/repro-wireless-mouse-charger-section.input.json +591 -0
  57. package/tests/repros/repro-isolated-rs485-isow7841.test.ts +4 -1
  58. package/tests/repros/repro-powerbank-3v-system-power.test.ts +24 -0
  59. package/tests/repros/repro-tida-01389-hbridge.test.ts +24 -0
  60. package/tests/repros/repro-trellis-core-gnd-net-label-overlap.test.ts +73 -0
  61. package/tests/repros/repro-wireless-mouse-charger-section.test.ts +16 -0
  62. package/tests/solvers/InlineNetLabelSolver/restore-reroutes-around-superseded-labels.test.ts +186 -0
  63. package/tests/solvers/SameNetJunctionAlignmentSolver/parallel-load-rails.test.ts +1 -0
@@ -0,0 +1,140 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
3
+ import {
4
+ isHorizontal,
5
+ isVertical,
6
+ } from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
7
+ import type { InputPin } from "lib/types/InputProblem"
8
+ import { dir } from "lib/utils/dir"
9
+
10
+ export type Axis = "x" | "y"
11
+
12
+ export interface AxisAlignedSegment {
13
+ start: Point
14
+ end: Point
15
+ axis: Axis
16
+ coordinate: number
17
+ }
18
+
19
+ export interface SharedPinExitRail {
20
+ segment: AxisAlignedSegment
21
+ sharedPinExit: Point
22
+ }
23
+
24
+ export const getPinFacingAxis = (pin: InputPin): Axis | null => {
25
+ if (!pin._facingDirection) return null
26
+ return dir(pin._facingDirection).x === 0 ? "y" : "x"
27
+ }
28
+
29
+ export const isCoordinateOnPinFacingSide = ({
30
+ coordinate,
31
+ axis,
32
+ pin,
33
+ }: {
34
+ coordinate: number
35
+ axis: Axis
36
+ pin: InputPin
37
+ }) => {
38
+ if (!pin._facingDirection) return false
39
+ const facingVector = dir(pin._facingDirection)
40
+ return (coordinate - pin[axis]) * facingVector[axis] > 0
41
+ }
42
+
43
+ const getSegmentAxis = (start: Point, end: Point): Axis | null => {
44
+ if (isHorizontal(start, end)) return "x"
45
+ if (isVertical(start, end)) return "y"
46
+ return null
47
+ }
48
+
49
+ const getAxisAlignedSegments = (path: Point[]): AxisAlignedSegment[] => {
50
+ const segments: AxisAlignedSegment[] = []
51
+ for (let index = 0; index < path.length - 1; index++) {
52
+ const start = path[index]!
53
+ const end = path[index + 1]!
54
+ const axis = getSegmentAxis(start, end)
55
+ if (!axis) continue
56
+ const coordinateAxis = axis === "x" ? "y" : "x"
57
+ segments.push({ start, end, axis, coordinate: start[coordinateAxis] })
58
+ }
59
+ return segments
60
+ }
61
+
62
+ const getPinExitPoint = ({
63
+ pin,
64
+ departureAxis,
65
+ railCoordinate,
66
+ }: {
67
+ pin: InputPin
68
+ departureAxis: Axis
69
+ railCoordinate: number
70
+ }): Point =>
71
+ departureAxis === "x"
72
+ ? { x: railCoordinate, y: pin.y }
73
+ : { x: pin.x, y: railCoordinate }
74
+
75
+ /**
76
+ * Finds the nearest existing donor rail that joins two same-side pin exits
77
+ * and is already reached by the branch leaving the shared pin.
78
+ */
79
+ export const findNearestSharedPinExitRail = ({
80
+ donorPath,
81
+ branchDepartureSegment,
82
+ sharedPin,
83
+ adjacentPin,
84
+ }: {
85
+ donorPath: Point[]
86
+ branchDepartureSegment: [Point, Point]
87
+ sharedPin: InputPin
88
+ adjacentPin: InputPin
89
+ }): SharedPinExitRail | null => {
90
+ const departureAxis = getPinFacingAxis(sharedPin)
91
+ if (!departureAxis) return null
92
+ const railAxis = departureAxis === "x" ? "y" : "x"
93
+
94
+ const branchDeparture = getAxisAlignedSegments(branchDepartureSegment)[0]
95
+ if (!branchDeparture || branchDeparture.axis !== departureAxis) return null
96
+
97
+ let nearestRail: SharedPinExitRail | null = null
98
+ let nearestDistance = Number.POSITIVE_INFINITY
99
+
100
+ for (const segment of getAxisAlignedSegments(donorPath)) {
101
+ if (segment.axis !== railAxis) continue
102
+ if (
103
+ !isCoordinateOnPinFacingSide({
104
+ coordinate: segment.coordinate,
105
+ axis: departureAxis,
106
+ pin: sharedPin,
107
+ })
108
+ ) {
109
+ continue
110
+ }
111
+
112
+ const sharedPinExit = getPinExitPoint({
113
+ pin: sharedPin,
114
+ departureAxis,
115
+ railCoordinate: segment.coordinate,
116
+ })
117
+ const adjacentPinExit = getPinExitPoint({
118
+ pin: adjacentPin,
119
+ departureAxis,
120
+ railCoordinate: segment.coordinate,
121
+ })
122
+ if (
123
+ !tracePathContainsPoint([segment.start, segment.end], sharedPinExit) ||
124
+ !tracePathContainsPoint([segment.start, segment.end], adjacentPinExit) ||
125
+ !tracePathContainsPoint(
126
+ [branchDeparture.start, branchDeparture.end],
127
+ sharedPinExit,
128
+ )
129
+ ) {
130
+ continue
131
+ }
132
+
133
+ const distance = Math.abs(segment.coordinate - sharedPin[departureAxis])
134
+ if (distance >= nearestDistance) continue
135
+ nearestRail = { segment, sharedPinExit }
136
+ nearestDistance = distance
137
+ }
138
+
139
+ return nearestRail
140
+ }
@@ -385,11 +385,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
385
385
  inputTracePaths: instance.availableNetOrientationSolver!.traces,
386
386
  globalConnMap: instance.mspConnectionPairSolver!.globalConnMap,
387
387
  traceIdsToShift: new Set(
388
- instance
389
- .availableNetOrientationSolver!.traces.filter((trace) =>
390
- trace.mspPairId.startsWith("available-net-orientation-"),
391
- )
392
- .map((trace) => trace.mspPairId),
388
+ instance.availableNetOrientationSolver!.netLabelConnectorTraceIds,
393
389
  ),
394
390
  },
395
391
  ],
@@ -571,6 +567,8 @@ export class SchematicTracePipelineSolver extends BaseSolver {
571
567
  inputProblem: instance.inputProblem,
572
568
  traces: instance.netLabelNetLabelCollisionSolver!.traces,
573
569
  netLabelPlacements: collisionOutput.netLabelPlacements,
570
+ netLabelConnectorTraceIds:
571
+ instance.availableNetOrientationSolver!.netLabelConnectorTraceIds,
574
572
  },
575
573
  ]
576
574
  },
@@ -581,11 +579,20 @@ export class SchematicTracePipelineSolver extends BaseSolver {
581
579
  (instance) => {
582
580
  const junctionOutput =
583
581
  instance.sameNetJunctionAlignmentSolver!.getOutput()
582
+ const completedReroutes = [
583
+ ...instance.traceLabelOverlapAvoidanceSolver!.getOutput()
584
+ .completedReroutes,
585
+ ...instance.preAlignmentNetLabelTraceCollisionSolver!.getOutput()
586
+ .completedReroutes,
587
+ ...instance.netLabelTraceCollisionSolver!.getOutput()
588
+ .completedReroutes,
589
+ ]
584
590
  return [
585
591
  {
586
592
  inputProblem: instance.inputProblem,
587
593
  traces: junctionOutput.traces,
588
594
  netLabelPlacements: junctionOutput.netLabelPlacements,
595
+ completedReroutes,
589
596
  },
590
597
  ]
591
598
  },
@@ -22,6 +22,19 @@ export const SCHEMATIC_TRACE_MIN_VISUAL_CENTERLINE_CLEARANCE =
22
22
  export const doesPathCoincideWithTraces = (
23
23
  path: Point[],
24
24
  traces: SolvedTracePath[],
25
+ ): boolean =>
26
+ doesPathCoincideWithPaths(
27
+ path,
28
+ traces.map((trace) => trace.tracePath),
29
+ )
30
+
31
+ /**
32
+ * Path-only form for rendered wire geometry that is not represented by a
33
+ * SolvedTracePath, such as inline-label terminal stubs.
34
+ */
35
+ export const doesPathCoincideWithPaths = (
36
+ path: Point[],
37
+ otherPaths: Point[][],
25
38
  ): boolean => {
26
39
  const rangesOverlap1D = (a1: number, a2: number, b1: number, b2: number) =>
27
40
  Math.min(Math.max(a1, a2), Math.max(b1, b2)) -
@@ -39,10 +52,10 @@ export const doesPathCoincideWithTraces = (
39
52
  const crossAxis = isVertical ? "x" : "y"
40
53
  const alongAxis = isVertical ? "y" : "x"
41
54
 
42
- for (const trace of traces) {
43
- for (let j = 0; j < trace.tracePath.length - 1; j++) {
44
- const traceSegStart = trace.tracePath[j]!
45
- const traceSegEnd = trace.tracePath[j + 1]!
55
+ for (const otherPath of otherPaths) {
56
+ for (let j = 0; j < otherPath.length - 1; j++) {
57
+ const traceSegStart = otherPath[j]!
58
+ const traceSegEnd = otherPath[j + 1]!
46
59
 
47
60
  const isParallel =
48
61
  Math.abs(traceSegStart[crossAxis] - traceSegEnd[crossAxis]) <
@@ -0,0 +1,33 @@
1
+ import {
2
+ doesSegmentIntersectRect,
3
+ getBoundFromCenteredRect,
4
+ type Point,
5
+ } from "@tscircuit/math-utils"
6
+ import type { InlineNetLabelPlacement } from "lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver"
7
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
8
+
9
+ /** Returns whether a path crosses the rendered text bounds of a net label. */
10
+ export const pathIntersectsRenderedLabel = (
11
+ path: Point[],
12
+ label: NetLabelPlacement | InlineNetLabelPlacement,
13
+ ) => {
14
+ let width = label.width
15
+ let height = label.height
16
+ if ("axis" in label && label.axis === "y") {
17
+ width = label.height
18
+ height = label.width
19
+ }
20
+ const bounds = getBoundFromCenteredRect({
21
+ center: label.center,
22
+ width,
23
+ height,
24
+ })
25
+ for (let pathIndex = 0; pathIndex < path.length - 1; pathIndex++) {
26
+ if (
27
+ doesSegmentIntersectRect(path[pathIndex]!, path[pathIndex + 1]!, bounds)
28
+ ) {
29
+ return true
30
+ }
31
+ }
32
+ return false
33
+ }
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "https://github.com/tscircuit/schematic-trace-solver.git"
6
6
  },
7
7
  "main": "dist/index.js",
8
- "version": "0.0.174",
8
+ "version": "0.0.176",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "start": "cosmos",
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/bug-reports/bug-report-20260901T055358Z/bug-report-20260901T055358Z.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/bug-reports/bug-report-20260901T064117Z/bug-report-20260901T064117Z.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/bug-reports/bug-report-20260901T134241Z/bug-report-20260901T134241Z.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />