@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.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +275 -228
- package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +1 -0
- package/lib/solvers/InlineNetLabelSolver/getAnchoredNetLabelRenderedBounds.ts +13 -0
- package/lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver.ts +11 -2
- package/lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts +4 -9
- package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +17 -9
- package/lib/solvers/SameNetJunctionAlignmentSolver/collapseSameNetCycles.ts +75 -38
- package/lib/solvers/TraceCleanupSolver/sub-solver/generateLShapeRerouteCandidates.ts +5 -1
- package/package.json +1 -1
- package/site/SchematicTracePipelineSolver/repro-robot-controller-imu-tof.page.tsx +7 -0
- package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +18 -18
- package/tests/repros/__snapshots__/repro-repeated-power-rail-junctions.snap.svg +255 -0
- package/tests/repros/__snapshots__/repro-robot-controller-imu-tof.snap.svg +199 -0
- package/tests/repros/__snapshots__/repro-robot-controller-imu-tof.source.svg +33 -0
- package/tests/repros/__snapshots__/rp2040-robot-controller-disconnected-gnd.snap.svg +103 -0
- package/tests/repros/assets/repro-repeated-power-rail-junctions.input.json +927 -0
- package/tests/repros/assets/repro-robot-controller-imu-tof.input.json +540 -0
- package/tests/repros/assets/rp2040-robot-controller.input.json +349 -0
- package/tests/repros/repro-repeated-power-rail-junctions.test.ts +22 -0
- package/tests/repros/repro-robot-controller-imu-tof.test.ts +83 -0
- package/tests/repros/rp2040-robot-controller-disconnected-gnd.test.ts +59 -0
- package/tests/solvers/InlineNetLabelSolver/get-anchored-net-label-rendered-bounds.test.ts +24 -0
- package/tests/solvers/SameNetJunctionAlignmentSolver/collapse-same-net-cycles.test.ts +30 -0
- 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
|
-
|
|
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:
|
|
43
|
-
netLabelPlacements:
|
|
37
|
+
traces: alignment.traces,
|
|
38
|
+
netLabelPlacements: alignment.netLabelPlacements,
|
|
44
39
|
})
|
|
45
40
|
this.stats.alignedJunctionCount = alignment.alignedJunctionCount
|
|
46
|
-
this.stats.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
|
-
|
|
186
|
-
label
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
245
|
+
...remainingPath,
|
|
242
246
|
]),
|
|
243
247
|
)
|
|
244
248
|
}
|
package/package.json
CHANGED
|
@@ -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
|
+
)
|