@tscircuit/schematic-trace-solver 0.0.188 → 0.0.190

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 (26) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +275 -228
  3. package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +1 -0
  4. package/lib/solvers/InlineNetLabelSolver/getAnchoredNetLabelRenderedBounds.ts +13 -0
  5. package/lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver.ts +11 -2
  6. package/lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts +4 -9
  7. package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +17 -9
  8. package/lib/solvers/SameNetJunctionAlignmentSolver/collapseSameNetCycles.ts +75 -38
  9. package/lib/solvers/TraceCleanupSolver/sub-solver/generateLShapeRerouteCandidates.ts +5 -1
  10. package/package.json +1 -1
  11. package/site/SchematicTracePipelineSolver/repro-robot-controller-imu-tof.page.tsx +7 -0
  12. package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +2 -2
  13. package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +18 -18
  14. package/tests/repros/__snapshots__/repro-repeated-power-rail-junctions.snap.svg +255 -0
  15. package/tests/repros/__snapshots__/repro-robot-controller-imu-tof.snap.svg +199 -0
  16. package/tests/repros/__snapshots__/repro-robot-controller-imu-tof.source.svg +33 -0
  17. package/tests/repros/__snapshots__/rp2040-robot-controller-disconnected-gnd.snap.svg +103 -0
  18. package/tests/repros/assets/repro-repeated-power-rail-junctions.input.json +927 -0
  19. package/tests/repros/assets/repro-robot-controller-imu-tof.input.json +540 -0
  20. package/tests/repros/assets/rp2040-robot-controller.input.json +349 -0
  21. package/tests/repros/repro-repeated-power-rail-junctions.test.ts +22 -0
  22. package/tests/repros/repro-robot-controller-imu-tof.test.ts +83 -0
  23. package/tests/repros/rp2040-robot-controller-disconnected-gnd.test.ts +59 -0
  24. package/tests/solvers/InlineNetLabelSolver/get-anchored-net-label-rendered-bounds.test.ts +24 -0
  25. package/tests/solvers/SameNetJunctionAlignmentSolver/collapse-same-net-cycles.test.ts +30 -0
  26. package/tests/solvers/TraceCleanupSolver/perpendicular-detours-preserve-endpoints.test.ts +60 -0
@@ -7,7 +7,6 @@ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/
7
7
  import type { InputProblem } from "lib/types/InputProblem"
8
8
  import { getColorFromString } from "lib/utils/getColorFromString"
9
9
  import { alignSameNetJunctions } from "./alignSameNetJunctions"
10
- import { collapseSameNetCycles } from "./collapseSameNetCycles"
11
10
  import { placeGroundRailLabelsAtOuterEnd } from "./placeGroundRailLabelsAtOuterEnd"
12
11
 
13
12
  interface SameNetJunctionAlignmentSolverInput {
@@ -32,18 +31,14 @@ export class SameNetJunctionAlignmentSolver extends BaseSolver {
32
31
 
33
32
  override _step() {
34
33
  const alignment = alignSameNetJunctions(this.input)
35
- const cycleCollapse = collapseSameNetCycles({
36
- traces: alignment.traces,
37
- netLabelPlacements: alignment.netLabelPlacements,
38
- })
39
- this.outputTraces = cycleCollapse.traces
34
+ this.outputTraces = alignment.traces
40
35
  this.outputNetLabelPlacements = placeGroundRailLabelsAtOuterEnd({
41
36
  inputProblem: this.input.inputProblem,
42
- traces: cycleCollapse.traces,
43
- netLabelPlacements: cycleCollapse.netLabelPlacements,
37
+ traces: alignment.traces,
38
+ netLabelPlacements: alignment.netLabelPlacements,
44
39
  })
45
40
  this.stats.alignedJunctionCount = alignment.alignedJunctionCount
46
- this.stats.collapsedCycleCount = cycleCollapse.collapsedCycleCount
41
+ this.stats.collapsedCycleCount = alignment.collapsedCycleCount
47
42
  this.solved = true
48
43
  }
49
44
 
@@ -8,6 +8,7 @@ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/Sche
8
8
  import { isPathCollidingWithObstacles } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
9
9
  import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
10
10
  import { getMovedAnchorPointForReroute } from "lib/solvers/Example28Solver/getMovedAnchorPointForReroute"
11
+ import { isLabelAttachedToTrace } from "lib/solvers/Example28Solver/isLabelAttachedToTrace"
11
12
  import { moveAttachedLabelsToReroutedTrace } from "lib/solvers/Example28Solver/labelMovement"
12
13
  import {
13
14
  rectsOverlap,
@@ -33,6 +34,7 @@ import {
33
34
  isCoordinateOnPinFacingSide,
34
35
  } from "./findNearestSharedPinExitRail"
35
36
  import { collapseRedundantSharedEndpointStubs } from "./collapseRedundantSharedEndpointStubs"
37
+ import { collapseSameNetCycles } from "./collapseSameNetCycles"
36
38
  import { getSharedPin } from "./getSharedPin"
37
39
 
38
40
  interface AlignSameNetJunctionsInput {
@@ -182,14 +184,13 @@ const getAttachedLabelIndexes = (
182
184
  netLabelPlacements: NetLabelPlacement[],
183
185
  ) =>
184
186
  netLabelPlacements.flatMap((label, index) => {
185
- const labelOwnsTrace =
186
- label.mspConnectionPairIds.includes(trace.mspPairId) ||
187
- (label.mspConnectionPairIds.length === 0 &&
188
- label.globalConnNetId === trace.globalConnNetId)
189
- return labelOwnsTrace &&
187
+ if (
188
+ isLabelAttachedToTrace(label, trace) &&
190
189
  tracePathContainsPoint(trace.tracePath, label.anchorPoint)
191
- ? [index]
192
- : []
190
+ ) {
191
+ return [index]
192
+ }
193
+ return []
193
194
  })
194
195
 
195
196
  const moveAttachedLabels = ({
@@ -1314,17 +1315,24 @@ export const alignSameNetJunctions = ({
1314
1315
  }
1315
1316
  }
1316
1317
 
1317
- const collapsedSharedEndpointStubs = collapseRedundantSharedEndpointStubs({
1318
+ // Shared-stub collapse can remove the common pin from one trace path, so
1319
+ // collapse cycles while both paths still expose their shared endpoint.
1320
+ const cycleCollapse = collapseSameNetCycles({
1318
1321
  traces: outputTraces,
1319
1322
  netLabelPlacements: outputNetLabelPlacements,
1323
+ })
1324
+ outputTraces = collapseRedundantSharedEndpointStubs({
1325
+ traces: cycleCollapse.traces,
1326
+ netLabelPlacements: cycleCollapse.netLabelPlacements,
1320
1327
  netLabelConnectorTraceIds,
1321
1328
  multiPinNetPinIds: getMultiPinNetPinIds(inputProblem),
1322
1329
  })
1323
- outputTraces = collapsedSharedEndpointStubs
1330
+ outputNetLabelPlacements = cycleCollapse.netLabelPlacements
1324
1331
 
1325
1332
  return {
1326
1333
  traces: outputTraces,
1327
1334
  netLabelPlacements: outputNetLabelPlacements,
1328
1335
  alignedJunctionCount,
1336
+ collapsedCycleCount: cycleCollapse.collapsedCycleCount,
1329
1337
  }
1330
1338
  }
@@ -11,6 +11,7 @@ import {
11
11
  pointsEqual,
12
12
  } from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
13
13
  import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
14
+ import { findPerpendicularPathCrossings } from "lib/solvers/TraceCleanupSolver/sub-solver/findIntersectionsWithObstacles"
14
15
  import { getSharedPin } from "./getSharedPin"
15
16
  import {
16
17
  pathEntersAnyNetLabel,
@@ -100,6 +101,25 @@ const getPerpendicularPathCrossings = (
100
101
  return crossings
101
102
  }
102
103
 
104
+ const buildCollapsedSelfCyclePath = (
105
+ path: Point[],
106
+ firstSegmentIndex: number,
107
+ secondSegmentIndex: number,
108
+ ) => {
109
+ const intersectionPoint = getSegmentIntersection(
110
+ path[firstSegmentIndex]!,
111
+ path[firstSegmentIndex + 1]!,
112
+ path[secondSegmentIndex]!,
113
+ path[secondSegmentIndex + 1]!,
114
+ )
115
+ if (!intersectionPoint) return path
116
+ return simplifyPath([
117
+ ...path.slice(0, firstSegmentIndex + 1),
118
+ intersectionPoint,
119
+ ...path.slice(secondSegmentIndex + 1),
120
+ ])
121
+ }
122
+
103
123
  const buildCollapsedCyclePath = ({
104
124
  targetTrace,
105
125
  targetPath,
@@ -209,6 +229,60 @@ const getBestCycleCollapseCandidate = ({
209
229
  const baselineTargetVisibleLength = getVisibleTraceLength([targetTrace])
210
230
  let bestCandidate: CycleCollapseCandidate | null = null
211
231
 
232
+ const considerTracePath = (tracePath: Point[]) => {
233
+ const candidateTrace = { ...targetTrace, tracePath }
234
+ const candidateTargetVisibleLength = getVisibleTraceLength([candidateTrace])
235
+ if (
236
+ candidateTargetVisibleLength >
237
+ baselineTargetVisibleLength + TRACE_LENGTH_EPSILON
238
+ ) {
239
+ return
240
+ }
241
+ const candidateNetLabelPlacements = getNetLabelPlacementsForCycleCollapse({
242
+ targetTrace,
243
+ tracePath,
244
+ netLabelPlacements,
245
+ })
246
+ if (!candidateNetLabelPlacements) return
247
+
248
+ const candidateNetTraces = sameNetTraces.map((trace) => {
249
+ if (trace.mspPairId === targetTrace.mspPairId) return candidateTrace
250
+ return trace
251
+ })
252
+ const netVisibleLength = getVisibleTraceLength(candidateNetTraces)
253
+ if (netVisibleLength >= baselineNetVisibleLength - TRACE_LENGTH_EPSILON) {
254
+ return
255
+ }
256
+ const candidate: CycleCollapseCandidate = {
257
+ tracePath,
258
+ netLabelPlacements: candidateNetLabelPlacements,
259
+ netVisibleLength,
260
+ netVisibleSegmentCount: getVisibleTraceSegmentCount(candidateNetTraces),
261
+ }
262
+ if (candidateIsBetter(candidate, bestCandidate)) {
263
+ bestCandidate = candidate
264
+ }
265
+ }
266
+
267
+ // Comparing a path with itself reports each crossing in both directions.
268
+ const selfCrossings = findPerpendicularPathCrossings(
269
+ targetTrace.tracePath,
270
+ targetTrace.tracePath,
271
+ { includeTerminalSegments: true },
272
+ ).filter(
273
+ ({ pathSegmentIndex, otherPathSegmentIndex }) =>
274
+ pathSegmentIndex < otherPathSegmentIndex,
275
+ )
276
+ for (const crossing of selfCrossings) {
277
+ considerTracePath(
278
+ buildCollapsedSelfCyclePath(
279
+ targetTrace.tracePath,
280
+ crossing.pathSegmentIndex,
281
+ crossing.otherPathSegmentIndex,
282
+ ),
283
+ )
284
+ }
285
+
212
286
  for (const donorTrace of sameNetTraces) {
213
287
  if (donorTrace.mspPairId === targetTrace.mspPairId) continue
214
288
  const sharedPin = getSharedPin({
@@ -228,44 +302,7 @@ const getBestCycleCollapseCandidate = ({
228
302
  donorPath,
229
303
  crossing,
230
304
  })
231
- const candidateTrace = { ...targetTrace, tracePath }
232
- const candidateTargetVisibleLength = getVisibleTraceLength([
233
- candidateTrace,
234
- ])
235
- if (
236
- candidateTargetVisibleLength >
237
- baselineTargetVisibleLength + TRACE_LENGTH_EPSILON
238
- ) {
239
- continue
240
- }
241
- const candidateNetLabelPlacements = getNetLabelPlacementsForCycleCollapse(
242
- {
243
- targetTrace,
244
- tracePath,
245
- netLabelPlacements,
246
- },
247
- )
248
- if (!candidateNetLabelPlacements) continue
249
-
250
- const candidateNetTraces = sameNetTraces.map((trace) => {
251
- if (trace.mspPairId === targetTrace.mspPairId) return candidateTrace
252
- return trace
253
- })
254
- const netVisibleLength = getVisibleTraceLength(candidateNetTraces)
255
- if (netVisibleLength >= baselineNetVisibleLength - TRACE_LENGTH_EPSILON) {
256
- continue
257
- }
258
- const netVisibleSegmentCount =
259
- getVisibleTraceSegmentCount(candidateNetTraces)
260
- const candidate = {
261
- tracePath,
262
- netLabelPlacements: candidateNetLabelPlacements,
263
- netVisibleLength,
264
- netVisibleSegmentCount,
265
- }
266
- if (candidateIsBetter(candidate, bestCandidate)) {
267
- bestCandidate = candidate
268
- }
305
+ considerTracePath(tracePath)
269
306
  }
270
307
  }
271
308
 
@@ -232,13 +232,17 @@ export const generatePerpendicularTraceDetours = ({
232
232
  ]),
233
233
  ].filter((coordinate): coordinate is number => coordinate !== undefined)
234
234
 
235
+ const remainingPath = path.slice(index + 2)
236
+ // A terminal segment has no following leg to reconnect the detour.
237
+ if (remainingPath.length === 0) remainingPath.push(end)
238
+
235
239
  return [...new Set(detourCoordinates)].map((detour) =>
236
240
  simplifyPath([
237
241
  ...path.slice(0, index + 1),
238
242
  { ...start, [movingAxis]: gate },
239
243
  { ...start, [movingAxis]: gate, [detourAxis]: detour },
240
244
  { ...end, [detourAxis]: detour },
241
- ...path.slice(index + 2),
245
+ ...remainingPath,
242
246
  ]),
243
247
  )
244
248
  }
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.188",
8
+ "version": "0.0.190",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "start": "cosmos",
@@ -0,0 +1,7 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import type { InputProblem } from "lib/types/InputProblem"
3
+ import inputProblem from "../../tests/repros/assets/repro-robot-controller-imu-tof.input.json"
4
+
5
+ export default () => (
6
+ <PipelineDebugger inputProblem={inputProblem as InputProblem} />
7
+ )