@tscircuit/schematic-trace-solver 0.0.187 → 0.0.189
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 +42 -23
- package/dist/index.js +1053 -316
- package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +612 -230
- package/lib/solvers/InlineNetLabelSolver/alignPortOnlyInlineNetLabelStubs.ts +2 -6
- package/lib/solvers/InlineNetLabelSolver/getAnchoredNetLabelRenderedBounds.ts +13 -0
- package/lib/solvers/InlineNetLabelSolver/getInlineLabelObstacles.ts +64 -0
- package/lib/solvers/InlineNetLabelSolver/getLocalTraceLabelShifts.ts +407 -0
- package/lib/solvers/InlineNetLabelSolver/getOutputLabelCollisions.ts +145 -0
- package/lib/solvers/InlineNetLabelSolver/isLabelRepresentedInline.ts +15 -0
- package/lib/solvers/InlineNetLabelSolver/pushAnchoredNetLabelsAwayFromInlineLabels.ts +61 -31
- package/lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver.ts +94 -16
- package/lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts +4 -3
- package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +7 -7
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +3 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/generateLShapeRerouteCandidates.ts +5 -1
- package/lib/types/InputProblem.ts +2 -2
- package/package.json +1 -1
- package/site/SchematicTracePipelineSolver/repro-robot-controller-imu-tof.page.tsx +7 -0
- package/site/bug-reports/bug-report-20260907T110144Z.page.tsx +4 -0
- package/site/bug-reports/bug-report-20260907T145640Z.page.tsx +4 -0
- package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260811T075142Z/__snapshots__/bug-report-20260811T075142Z.snap.svg +18 -18
- package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260819T091818Z/bug-report-20260819T091818Z.test.ts +21 -0
- package/tests/bug-reports/bug-report-20260825T103621Z/__snapshots__/bug-report-20260825T103621Z.snap.svg +5 -38
- package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +3 -9
- package/tests/bug-reports/bug-report-20260901T055358Z/__snapshots__/bug-report-20260901T055358Z.snap.svg +18 -30
- package/tests/bug-reports/bug-report-20260901T055358Z/bug-report-20260901T055358Z.test.ts +33 -0
- package/tests/bug-reports/bug-report-20260907T110144Z/__snapshots__/bug-report-20260907T110144Z.snap.svg +282 -0
- package/tests/bug-reports/bug-report-20260907T110144Z/bug-report-20260907T110144Z.json +1073 -0
- package/tests/bug-reports/bug-report-20260907T110144Z/bug-report-20260907T110144Z.test.ts +12 -0
- package/tests/bug-reports/bug-report-20260907T145640Z/__snapshots__/bug-report-20260907T145640Z.snap.svg +1107 -0
- package/tests/bug-reports/bug-report-20260907T145640Z/bug-report-20260907T145640Z.json +3563 -0
- package/tests/bug-reports/bug-report-20260907T145640Z/bug-report-20260907T145640Z.test.ts +117 -0
- package/tests/repros/__snapshots__/repro-battery-management-bq24073.snap.svg +4 -4
- package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-core-tb67s579ftg-inline-label-routing.snap.svg +20 -20
- package/tests/repros/__snapshots__/repro-esp12f-section.snap.svg +37 -55
- package/tests/repros/__snapshots__/repro-hub-usb-sheet.snap.svg +44 -68
- package/tests/repros/__snapshots__/repro-mspm0l1306-capacitor-symmetry.snap.svg +3 -9
- 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__/repro-usb-power-vbus-label-detour.snap.svg +50 -74
- package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +2 -2
- package/tests/repros/__snapshots__/rp2040-robot-controller-disconnected-gnd.snap.svg +103 -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-battery-management-bq24073.test.ts +13 -1
- package/tests/repros/repro-hub-usb-sheet.test.ts +25 -0
- package/tests/repros/repro-robot-controller-imu-tof.test.ts +83 -0
- package/tests/repros/repro-usb-power-vbus-label-detour.test.ts +59 -13
- 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/InlineNetLabelSolver/local-trace-label-shifts.test.ts +296 -0
- package/tests/solvers/InlineNetLabelSolver/multi-pin-connected-components.test.ts +7 -4
- package/tests/solvers/InlineNetLabelSolver/push-anchored-net-labels-away.test.ts +2 -1
- package/tests/solvers/InlineNetLabelSolver/shared-direct-and-named-connection.test.ts +62 -0
- package/tests/solvers/InlineNetLabelSolver/two-pin-terminal-stubs-atomic.test.ts +9 -3
- package/tests/solvers/TraceCleanupSolver/perpendicular-detours-preserve-endpoints.test.ts +60 -0
|
@@ -76,33 +76,52 @@ const getRequiredOutwardDistance = (
|
|
|
76
76
|
label: NetLabelPlacement,
|
|
77
77
|
inlineBounds: Bounds[],
|
|
78
78
|
) => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
let distance = 0
|
|
80
|
+
// Only clear obstacles within the current proposal's clearance. A label elsewhere
|
|
81
|
+
// on the same row must not force this label across the whole schematic.
|
|
82
|
+
// Moving past one obstacle can encounter another, so continue until clear.
|
|
83
|
+
for (let iteration = 0; iteration < inlineBounds.length; iteration++) {
|
|
84
|
+
const labelBounds = getAnchoredNetLabelRenderedBounds(
|
|
85
|
+
moveLabel(label, label.orientation, distance),
|
|
86
|
+
)
|
|
87
|
+
const horizontal = label.orientation === "x-" || label.orientation === "x+"
|
|
88
|
+
const overlapping = inlineBounds.filter((bounds) =>
|
|
89
|
+
boundsOverlap(labelBounds, {
|
|
90
|
+
minX: bounds.minX - (horizontal ? LABEL_CLEARANCE : 0),
|
|
91
|
+
maxX: bounds.maxX + (horizontal ? LABEL_CLEARANCE : 0),
|
|
92
|
+
minY: bounds.minY - (horizontal ? 0 : LABEL_CLEARANCE),
|
|
93
|
+
maxY: bounds.maxY + (horizontal ? 0 : LABEL_CLEARANCE),
|
|
94
|
+
}),
|
|
85
95
|
)
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
96
|
+
if (overlapping.length === 0) break
|
|
97
|
+
switch (label.orientation) {
|
|
98
|
+
case "x-":
|
|
99
|
+
distance +=
|
|
100
|
+
labelBounds.maxX -
|
|
101
|
+
Math.min(...overlapping.map((b) => b.minX)) +
|
|
102
|
+
LABEL_CLEARANCE
|
|
103
|
+
break
|
|
104
|
+
case "x+":
|
|
105
|
+
distance +=
|
|
106
|
+
Math.max(...overlapping.map((b) => b.maxX)) -
|
|
107
|
+
labelBounds.minX +
|
|
108
|
+
LABEL_CLEARANCE
|
|
109
|
+
break
|
|
110
|
+
case "y-":
|
|
111
|
+
distance +=
|
|
112
|
+
labelBounds.maxY -
|
|
113
|
+
Math.min(...overlapping.map((b) => b.minY)) +
|
|
114
|
+
LABEL_CLEARANCE
|
|
115
|
+
break
|
|
116
|
+
case "y+":
|
|
117
|
+
distance +=
|
|
118
|
+
Math.max(...overlapping.map((b) => b.maxY)) -
|
|
119
|
+
labelBounds.minY +
|
|
120
|
+
LABEL_CLEARANCE
|
|
121
|
+
break
|
|
90
122
|
}
|
|
91
|
-
const targetMinX = Math.max(...nearby.map((bounds) => bounds.maxX))
|
|
92
|
-
return Math.max(0, targetMinX - labelBounds.minX + LABEL_CLEARANCE)
|
|
93
123
|
}
|
|
94
|
-
|
|
95
|
-
const nearby = inlineBounds.filter(
|
|
96
|
-
(bounds) =>
|
|
97
|
-
labelBounds.minX < bounds.maxX && labelBounds.maxX > bounds.minX,
|
|
98
|
-
)
|
|
99
|
-
if (nearby.length === 0) return 0
|
|
100
|
-
if (label.orientation === "y-") {
|
|
101
|
-
const targetMaxY = Math.min(...nearby.map((bounds) => bounds.minY))
|
|
102
|
-
return Math.max(0, labelBounds.maxY - targetMaxY + LABEL_CLEARANCE)
|
|
103
|
-
}
|
|
104
|
-
const targetMinY = Math.max(...nearby.map((bounds) => bounds.maxY))
|
|
105
|
-
return Math.max(0, targetMinY - labelBounds.minY + LABEL_CLEARANCE)
|
|
124
|
+
return distance
|
|
106
125
|
}
|
|
107
126
|
|
|
108
127
|
const moveLabel = (
|
|
@@ -159,17 +178,14 @@ const sharesOwnerChip = (
|
|
|
159
178
|
) =>
|
|
160
179
|
label.pinIds.some((pinId) => ownerChipIds.has(chipIdByPinId.get(pinId) ?? ""))
|
|
161
180
|
|
|
162
|
-
const isGeneratedLabelConnector = (trace: SolvedTracePath) =>
|
|
163
|
-
trace.mspPairId.startsWith("available-net-orientation-") ||
|
|
164
|
-
trace.mspPairId.startsWith("inline-net-label-clearance-")
|
|
165
|
-
|
|
166
181
|
const findConnectorTraceIndex = (
|
|
167
182
|
label: NetLabelPlacement,
|
|
168
183
|
traces: SolvedTracePath[],
|
|
184
|
+
connectorTraceIds: ReadonlySet<string>,
|
|
169
185
|
) =>
|
|
170
186
|
traces.findIndex((trace) => {
|
|
171
187
|
if (trace.globalConnNetId !== label.globalConnNetId) return false
|
|
172
|
-
if (!
|
|
188
|
+
if (!connectorTraceIds.has(trace.mspPairId)) return false
|
|
173
189
|
const first = trace.tracePath[0]
|
|
174
190
|
const last = trace.tracePath.at(-1)
|
|
175
191
|
return Boolean(
|
|
@@ -298,7 +314,9 @@ export const pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
298
314
|
traces,
|
|
299
315
|
netLabelPlacements,
|
|
300
316
|
inlineNetLabelPlacements,
|
|
317
|
+
netLabelConnectorTraceIds = new Set<string>(),
|
|
301
318
|
}: {
|
|
319
|
+
netLabelConnectorTraceIds?: ReadonlySet<string>
|
|
302
320
|
inputProblem: InputProblem
|
|
303
321
|
traces: SolvedTracePath[]
|
|
304
322
|
netLabelPlacements: NetLabelPlacement[]
|
|
@@ -307,7 +325,9 @@ export const pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
307
325
|
traces: SolvedTracePath[]
|
|
308
326
|
netLabelPlacements: NetLabelPlacement[]
|
|
309
327
|
movedLabelCount: number
|
|
328
|
+
netLabelConnectorTraceIds: ReadonlySet<string>
|
|
310
329
|
} => {
|
|
330
|
+
const connectorTraceIds = new Set(netLabelConnectorTraceIds)
|
|
311
331
|
const outputTraces = traces.map((trace) => ({
|
|
312
332
|
...trace,
|
|
313
333
|
tracePath: trace.tracePath.map((point) => ({ ...point })),
|
|
@@ -364,7 +384,11 @@ export const pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
364
384
|
if (!boundsOverlap(movingBounds, obstacleBounds)) continue
|
|
365
385
|
if (
|
|
366
386
|
!sharesOwnerChip(obstacle, ownerChipIds, chipIdByPinId) ||
|
|
367
|
-
(findConnectorTraceIndex(
|
|
387
|
+
(findConnectorTraceIndex(
|
|
388
|
+
obstacle,
|
|
389
|
+
outputTraces,
|
|
390
|
+
connectorTraceIds,
|
|
391
|
+
) === -1 &&
|
|
368
392
|
!canAddConnectorAtAnchor(obstacle, outputTraces, pinMap))
|
|
369
393
|
) {
|
|
370
394
|
failed = true
|
|
@@ -460,7 +484,11 @@ export const pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
460
484
|
}> = []
|
|
461
485
|
for (const [labelIndex, movedLabel] of proposals) {
|
|
462
486
|
const label = outputLabels[labelIndex]!
|
|
463
|
-
const connectorIndex = findConnectorTraceIndex(
|
|
487
|
+
const connectorIndex = findConnectorTraceIndex(
|
|
488
|
+
label,
|
|
489
|
+
outputTraces,
|
|
490
|
+
connectorTraceIds,
|
|
491
|
+
)
|
|
464
492
|
if (
|
|
465
493
|
connectorIndex === -1 &&
|
|
466
494
|
!canAddConnectorAtAnchor(label, outputTraces, pinMap)
|
|
@@ -528,6 +556,7 @@ export const pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
528
556
|
movedLabelIndices.add(labelIndex)
|
|
529
557
|
}
|
|
530
558
|
for (const update of connectorUpdates) {
|
|
559
|
+
connectorTraceIds.add(update.trace.mspPairId)
|
|
531
560
|
if (update.connectorIndex === -1) outputTraces.push(update.trace)
|
|
532
561
|
else outputTraces[update.connectorIndex] = update.trace
|
|
533
562
|
}
|
|
@@ -537,5 +566,6 @@ export const pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
537
566
|
traces: outputTraces,
|
|
538
567
|
netLabelPlacements: outputLabels,
|
|
539
568
|
movedLabelCount: movedLabelIndices.size,
|
|
569
|
+
netLabelConnectorTraceIds: connectorTraceIds,
|
|
540
570
|
}
|
|
541
571
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { getAnchoredNetLabelRenderedBounds } from "../InlineNetLabelSolver/getAnchoredNetLabelRenderedBounds"
|
|
1
2
|
import type { GraphicsObject } from "graphics-debug"
|
|
2
3
|
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
3
4
|
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
5
|
+
import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
|
|
4
6
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
5
7
|
import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
6
8
|
import type { InputProblem } from "lib/types/InputProblem"
|
|
@@ -91,6 +93,12 @@ function sampleAnchorsAlongSegment(
|
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
export interface NetLabelNetLabelCollisionSolverParams {
|
|
96
|
+
/** Use the final horizontal tag envelope after inline conversion. */
|
|
97
|
+
useRenderedLabelBounds?: boolean
|
|
98
|
+
/** Already placed labels that are obstacles, but must not be moved. */
|
|
99
|
+
fixedNetLabelPlacements?: NetLabelPlacement[]
|
|
100
|
+
/** Existing connector identities from AvailableNetOrientationSolver. */
|
|
101
|
+
netLabelConnectorTraceIds?: ReadonlySet<MspConnectionPairId>
|
|
94
102
|
inputProblem: InputProblem
|
|
95
103
|
traces: SolvedTracePath[]
|
|
96
104
|
netLabelPlacements: NetLabelPlacement[]
|
|
@@ -107,7 +115,7 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
107
115
|
currentLabelToMove: NetLabelPlacement | null = null
|
|
108
116
|
candidateResults: Candidate[] = []
|
|
109
117
|
|
|
110
|
-
private chipIndex: ChipObstacleSpatialIndex
|
|
118
|
+
private chipIndex: ChipObstacleSpatialIndex | undefined
|
|
111
119
|
private traceMap: Record<MspConnectionPairId, SolvedTracePath>
|
|
112
120
|
private skippedCollisionKeys = new Set<string>()
|
|
113
121
|
|
|
@@ -115,7 +123,7 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
115
123
|
private candidateQueue: Candidate[] = []
|
|
116
124
|
private candidateIndex = 0
|
|
117
125
|
|
|
118
|
-
constructor(params: NetLabelNetLabelCollisionSolverParams) {
|
|
126
|
+
constructor(private params: NetLabelNetLabelCollisionSolverParams) {
|
|
119
127
|
super()
|
|
120
128
|
this.inputProblem = params.inputProblem
|
|
121
129
|
this.traces = params.traces
|
|
@@ -123,7 +131,9 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
123
131
|
this.outputNetLabelPlacements = [...params.netLabelPlacements]
|
|
124
132
|
this.chipIndex =
|
|
125
133
|
params.inputProblem._chipObstacleSpatialIndex ??
|
|
126
|
-
|
|
134
|
+
(params.inputProblem.chips.length > 0
|
|
135
|
+
? new ChipObstacleSpatialIndex(params.inputProblem.chips)
|
|
136
|
+
: undefined)
|
|
127
137
|
this.traceMap = Object.fromEntries(
|
|
128
138
|
params.traces.map((t) => [t.mspPairId, t]),
|
|
129
139
|
) as Record<MspConnectionPairId, SolvedTracePath>
|
|
@@ -136,6 +146,8 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
136
146
|
inputProblem: this.inputProblem,
|
|
137
147
|
traces: this.traces,
|
|
138
148
|
netLabelPlacements: this.netLabelPlacements,
|
|
149
|
+
fixedNetLabelPlacements: this.params.fixedNetLabelPlacements,
|
|
150
|
+
useRenderedLabelBounds: this.params.useRenderedLabelBounds,
|
|
139
151
|
}
|
|
140
152
|
}
|
|
141
153
|
|
|
@@ -144,7 +156,12 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
144
156
|
}
|
|
145
157
|
|
|
146
158
|
private labelBounds(label: NetLabelPlacement) {
|
|
147
|
-
|
|
159
|
+
if (
|
|
160
|
+
!this.params.useRenderedLabelBounds ||
|
|
161
|
+
this.params.fixedNetLabelPlacements?.includes(label)
|
|
162
|
+
)
|
|
163
|
+
return getRectBounds(label.center, label.width, label.height)
|
|
164
|
+
return getAnchoredNetLabelRenderedBounds(label)
|
|
148
165
|
}
|
|
149
166
|
|
|
150
167
|
private collisionKey(a: NetLabelPlacement, b: NetLabelPlacement) {
|
|
@@ -154,12 +171,19 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
154
171
|
private findNextCollidingPair():
|
|
155
172
|
| [NetLabelPlacement, NetLabelPlacement]
|
|
156
173
|
| null {
|
|
157
|
-
const labels =
|
|
158
|
-
|
|
174
|
+
const labels = [
|
|
175
|
+
...this.outputNetLabelPlacements,
|
|
176
|
+
...(this.params.fixedNetLabelPlacements ?? []),
|
|
177
|
+
]
|
|
178
|
+
for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
|
|
159
179
|
for (let j = i + 1; j < labels.length; j++) {
|
|
160
180
|
const a = labels[i]!
|
|
161
181
|
const b = labels[j]!
|
|
162
|
-
if (
|
|
182
|
+
if (
|
|
183
|
+
a.globalConnNetId === b.globalConnNetId &&
|
|
184
|
+
!this.params.fixedNetLabelPlacements?.includes(b)
|
|
185
|
+
)
|
|
186
|
+
continue
|
|
163
187
|
if (this.skippedCollisionKeys.has(this.collisionKey(a, b))) continue
|
|
164
188
|
if (boundsOverlap(this.labelBounds(a), this.labelBounds(b)))
|
|
165
189
|
return [a, b]
|
|
@@ -208,16 +232,31 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
208
232
|
center,
|
|
209
233
|
width,
|
|
210
234
|
height,
|
|
211
|
-
bounds:
|
|
235
|
+
bounds: this.params.useRenderedLabelBounds
|
|
236
|
+
? getAnchoredNetLabelRenderedBounds({
|
|
237
|
+
...label,
|
|
238
|
+
orientation,
|
|
239
|
+
anchorPoint: anchor,
|
|
240
|
+
center,
|
|
241
|
+
width,
|
|
242
|
+
height,
|
|
243
|
+
})
|
|
244
|
+
: getRectBounds(center, width, height),
|
|
212
245
|
hostPairId,
|
|
213
246
|
hostSegIndex,
|
|
214
247
|
status: null,
|
|
215
248
|
}
|
|
216
249
|
}
|
|
217
250
|
|
|
218
|
-
|
|
251
|
+
// A label on an extended connector must keep that attachment point.
|
|
252
|
+
const isAnchoredOnConnector = this.traces.some(
|
|
253
|
+
(trace) =>
|
|
254
|
+
this.params.netLabelConnectorTraceIds?.has(trace.mspPairId) &&
|
|
255
|
+
trace.globalConnNetId === label.globalConnNetId &&
|
|
256
|
+
tracePathContainsPoint(trace.tracePath, label.anchorPoint),
|
|
257
|
+
)
|
|
219
258
|
|
|
220
|
-
if (
|
|
259
|
+
if (label.mspConnectionPairIds.length === 0 || isAnchoredOnConnector) {
|
|
221
260
|
const allOrientations: FacingDirection[] = ["x+", "x-", "y+", "y-"]
|
|
222
261
|
const orderedOrientations = [
|
|
223
262
|
label.orientation,
|
|
@@ -271,7 +310,7 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
271
310
|
): CandidateStatus {
|
|
272
311
|
const { bounds, hostPairId, hostSegIndex } = candidate
|
|
273
312
|
|
|
274
|
-
if (this.chipIndex
|
|
313
|
+
if ((this.chipIndex?.getChipsInBounds(bounds).length ?? 0) > 0)
|
|
275
314
|
return "chip-collision"
|
|
276
315
|
if (rectIntersectsAnyTextBox(bounds, this.inputProblem))
|
|
277
316
|
return "text-collision"
|
|
@@ -284,7 +323,11 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
284
323
|
}
|
|
285
324
|
|
|
286
325
|
for (const obstacle of obstacleLabels) {
|
|
287
|
-
if (
|
|
326
|
+
if (
|
|
327
|
+
obstacle.globalConnNetId === movingLabelNetId &&
|
|
328
|
+
!this.params.fixedNetLabelPlacements?.includes(obstacle)
|
|
329
|
+
)
|
|
330
|
+
continue
|
|
288
331
|
if (boundsOverlap(bounds, this.labelBounds(obstacle)))
|
|
289
332
|
return "label-collision"
|
|
290
333
|
}
|
|
@@ -299,6 +342,38 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
299
342
|
this.candidateResults = []
|
|
300
343
|
}
|
|
301
344
|
|
|
345
|
+
/** Reuse the normal anchor search for a local trace-clearance proposal. */
|
|
346
|
+
getNearbyValidPlacements(
|
|
347
|
+
label: NetLabelPlacement,
|
|
348
|
+
maxDistance: number,
|
|
349
|
+
): NetLabelPlacement[] {
|
|
350
|
+
const distance = (candidate: Candidate) =>
|
|
351
|
+
Math.hypot(
|
|
352
|
+
candidate.anchor.x - label.anchorPoint.x,
|
|
353
|
+
candidate.anchor.y - label.anchorPoint.y,
|
|
354
|
+
)
|
|
355
|
+
const obstacles = [
|
|
356
|
+
...this.outputNetLabelPlacements.filter((other) => other !== label),
|
|
357
|
+
...(this.params.fixedNetLabelPlacements ?? []),
|
|
358
|
+
]
|
|
359
|
+
return this.buildCandidatesForLabel(label)
|
|
360
|
+
.filter(
|
|
361
|
+
(candidate) =>
|
|
362
|
+
distance(candidate) <= maxDistance &&
|
|
363
|
+
this.checkCandidate(candidate, label.globalConnNetId, obstacles) ===
|
|
364
|
+
"ok",
|
|
365
|
+
)
|
|
366
|
+
.sort((a, b) => distance(a) - distance(b))
|
|
367
|
+
.map((candidate) => ({
|
|
368
|
+
...label,
|
|
369
|
+
orientation: candidate.orientation,
|
|
370
|
+
anchorPoint: candidate.anchor,
|
|
371
|
+
center: candidate.center,
|
|
372
|
+
width: candidate.width,
|
|
373
|
+
height: candidate.height,
|
|
374
|
+
}))
|
|
375
|
+
}
|
|
376
|
+
|
|
302
377
|
private clearActiveSearch() {
|
|
303
378
|
this.currentCollision = null
|
|
304
379
|
this.currentLabelToMove = null
|
|
@@ -316,7 +391,9 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
316
391
|
return
|
|
317
392
|
}
|
|
318
393
|
this.currentCollision = pair
|
|
319
|
-
this.labelsToTry = [pair[1], pair[0]]
|
|
394
|
+
this.labelsToTry = [pair[1], pair[0]].filter(
|
|
395
|
+
(label) => !(this.params.fixedNetLabelPlacements ?? []).includes(label),
|
|
396
|
+
)
|
|
320
397
|
this.beginSearchForLabel(this.labelsToTry.shift()!)
|
|
321
398
|
return
|
|
322
399
|
}
|
|
@@ -342,9 +419,10 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
342
419
|
fixedLabel = labelB
|
|
343
420
|
}
|
|
344
421
|
const obstacleLabels = [
|
|
345
|
-
...
|
|
346
|
-
|
|
347
|
-
|
|
422
|
+
...[
|
|
423
|
+
...this.outputNetLabelPlacements,
|
|
424
|
+
...(this.params.fixedNetLabelPlacements ?? []),
|
|
425
|
+
].filter((l) => l !== labelA && l !== labelB),
|
|
348
426
|
fixedLabel,
|
|
349
427
|
]
|
|
350
428
|
|
|
@@ -44,7 +44,6 @@ interface CandidatePair {
|
|
|
44
44
|
netConnectionPinIds?: PinId[]
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
const AVAILABLE_NET_ORIENTATION_PREFIX = "available-net-orientation-"
|
|
48
47
|
const RECOVERED_TRACE_PREFIX = "net-label-to-trace-"
|
|
49
48
|
const MAX_NAMED_NET_RECOVERY_PERPENDICULAR_OFFSET = 0.05
|
|
50
49
|
const MAX_ROUTED_COMPONENT_RECOVERY_PERPENDICULAR_OFFSET = 0.25
|
|
@@ -82,6 +81,7 @@ export class NetLabelToTraceSolver extends BaseSolver {
|
|
|
82
81
|
outputTraces: SolvedTracePath[]
|
|
83
82
|
outputNetLabelPlacements: NetLabelPlacement[]
|
|
84
83
|
|
|
84
|
+
private readonly recoveredTraceIds = new Set<string>()
|
|
85
85
|
private chipMap: Record<ChipId, InputChip>
|
|
86
86
|
private pinMap: Map<PinId, TraceRecoveryPin>
|
|
87
87
|
private queuedCandidates: CandidatePair[]
|
|
@@ -424,7 +424,7 @@ export class NetLabelToTraceSolver extends BaseSolver {
|
|
|
424
424
|
trace: SolvedTracePath,
|
|
425
425
|
candidate: CandidatePair,
|
|
426
426
|
) {
|
|
427
|
-
if (!trace.mspPairId
|
|
427
|
+
if (!this.input.netLabelConnectorTraceIds?.has(trace.mspPairId)) {
|
|
428
428
|
return false
|
|
429
429
|
}
|
|
430
430
|
if (trace.pinIds.length !== 1) return false
|
|
@@ -491,6 +491,7 @@ export class NetLabelToTraceSolver extends BaseSolver {
|
|
|
491
491
|
pinIds: [firstPin.pinId, secondPin.pinId],
|
|
492
492
|
}
|
|
493
493
|
|
|
494
|
+
this.recoveredTraceIds.add(mspPairId)
|
|
494
495
|
this.outputTraces = [...retainedTraces, recoveredTrace]
|
|
495
496
|
if (candidate.recoveryMode !== "routed_components") {
|
|
496
497
|
this.outputNetLabelPlacements = this.outputNetLabelPlacements.filter(
|
|
@@ -566,7 +567,7 @@ export class NetLabelToTraceSolver extends BaseSolver {
|
|
|
566
567
|
...this.getOutput(),
|
|
567
568
|
})
|
|
568
569
|
for (const trace of this.outputTraces) {
|
|
569
|
-
if (!trace.mspPairId
|
|
570
|
+
if (!this.recoveredTraceIds.has(trace.mspPairId)) continue
|
|
570
571
|
graphics.lines!.push({
|
|
571
572
|
points: trace.tracePath,
|
|
572
573
|
strokeColor: "green",
|
|
@@ -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,
|
|
@@ -182,14 +183,13 @@ const getAttachedLabelIndexes = (
|
|
|
182
183
|
netLabelPlacements: NetLabelPlacement[],
|
|
183
184
|
) =>
|
|
184
185
|
netLabelPlacements.flatMap((label, index) => {
|
|
185
|
-
|
|
186
|
-
label
|
|
187
|
-
(label.mspConnectionPairIds.length === 0 &&
|
|
188
|
-
label.globalConnNetId === trace.globalConnNetId)
|
|
189
|
-
return labelOwnsTrace &&
|
|
186
|
+
if (
|
|
187
|
+
isLabelAttachedToTrace(label, trace) &&
|
|
190
188
|
tracePathContainsPoint(trace.tracePath, label.anchorPoint)
|
|
191
|
-
|
|
192
|
-
|
|
189
|
+
) {
|
|
190
|
+
return [index]
|
|
191
|
+
}
|
|
192
|
+
return []
|
|
193
193
|
})
|
|
194
194
|
|
|
195
195
|
const moveAttachedLabels = ({
|
|
@@ -598,6 +598,9 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
598
598
|
traces: junctionOutput.traces,
|
|
599
599
|
netLabelPlacements: junctionOutput.netLabelPlacements,
|
|
600
600
|
completedReroutes,
|
|
601
|
+
resolveTraceCollisions: true,
|
|
602
|
+
netLabelConnectorTraceIds:
|
|
603
|
+
instance.availableNetOrientationSolver!.netLabelConnectorTraceIds,
|
|
601
604
|
},
|
|
602
605
|
]
|
|
603
606
|
},
|
|
@@ -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
|
}
|
|
@@ -120,8 +120,8 @@ export interface InputNetConnection {
|
|
|
120
120
|
* When true, a named net may use inline labels. Each routed connected
|
|
121
121
|
* component gets a label along its representative trace, while each
|
|
122
122
|
* disconnected endpoint gets an outward inline-label stub. Conversion is
|
|
123
|
-
* atomic
|
|
124
|
-
*
|
|
123
|
+
* atomic within each routed component. An obstructed component retains its
|
|
124
|
+
* anchored label without preventing inline labels on disconnected components.
|
|
125
125
|
*/
|
|
126
126
|
allowInlineNetLabel?: boolean
|
|
127
127
|
|
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
|
+
)
|