@tscircuit/schematic-trace-solver 0.0.186 → 0.0.188
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 +40 -23
- package/dist/index.js +1041 -319
- package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +611 -230
- package/lib/solvers/InlineNetLabelSolver/alignPortOnlyInlineNetLabelStubs.ts +2 -6
- 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 +83 -14
- package/lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts +4 -3
- package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +14 -11
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +3 -0
- package/lib/types/InputProblem.ts +2 -2
- package/package.json +1 -1
- package/site/bug-reports/bug-report-20260907T083336Z.page.tsx +4 -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-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-20260907T083336Z/__snapshots__/bug-report-20260907T083336Z.snap.svg +520 -0
- package/tests/bug-reports/bug-report-20260907T083336Z/bug-report-20260907T083336Z.json +2031 -0
- package/tests/bug-reports/bug-report-20260907T083336Z/bug-report-20260907T083336Z.test.ts +27 -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__/board-1273-trace-overlap-cycle.snap.svg +2 -2
- 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-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/repro-battery-management-bq24073.test.ts +13 -1
- package/tests/repros/repro-hub-usb-sheet.test.ts +25 -0
- package/tests/repros/repro-usb-power-vbus-label-detour.test.ts +59 -13
- 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/SameNetJunctionAlignmentSolver/parallel-load-rails.test.ts +24 -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,3 +1,4 @@
|
|
|
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"
|
|
@@ -91,6 +92,10 @@ function sampleAnchorsAlongSegment(
|
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
export interface NetLabelNetLabelCollisionSolverParams {
|
|
95
|
+
/** Use the final horizontal tag envelope after inline conversion. */
|
|
96
|
+
useRenderedLabelBounds?: boolean
|
|
97
|
+
/** Already placed labels that are obstacles, but must not be moved. */
|
|
98
|
+
fixedNetLabelPlacements?: NetLabelPlacement[]
|
|
94
99
|
inputProblem: InputProblem
|
|
95
100
|
traces: SolvedTracePath[]
|
|
96
101
|
netLabelPlacements: NetLabelPlacement[]
|
|
@@ -107,7 +112,7 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
107
112
|
currentLabelToMove: NetLabelPlacement | null = null
|
|
108
113
|
candidateResults: Candidate[] = []
|
|
109
114
|
|
|
110
|
-
private chipIndex: ChipObstacleSpatialIndex
|
|
115
|
+
private chipIndex: ChipObstacleSpatialIndex | undefined
|
|
111
116
|
private traceMap: Record<MspConnectionPairId, SolvedTracePath>
|
|
112
117
|
private skippedCollisionKeys = new Set<string>()
|
|
113
118
|
|
|
@@ -115,7 +120,7 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
115
120
|
private candidateQueue: Candidate[] = []
|
|
116
121
|
private candidateIndex = 0
|
|
117
122
|
|
|
118
|
-
constructor(params: NetLabelNetLabelCollisionSolverParams) {
|
|
123
|
+
constructor(private params: NetLabelNetLabelCollisionSolverParams) {
|
|
119
124
|
super()
|
|
120
125
|
this.inputProblem = params.inputProblem
|
|
121
126
|
this.traces = params.traces
|
|
@@ -123,7 +128,9 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
123
128
|
this.outputNetLabelPlacements = [...params.netLabelPlacements]
|
|
124
129
|
this.chipIndex =
|
|
125
130
|
params.inputProblem._chipObstacleSpatialIndex ??
|
|
126
|
-
|
|
131
|
+
(params.inputProblem.chips.length > 0
|
|
132
|
+
? new ChipObstacleSpatialIndex(params.inputProblem.chips)
|
|
133
|
+
: undefined)
|
|
127
134
|
this.traceMap = Object.fromEntries(
|
|
128
135
|
params.traces.map((t) => [t.mspPairId, t]),
|
|
129
136
|
) as Record<MspConnectionPairId, SolvedTracePath>
|
|
@@ -136,6 +143,8 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
136
143
|
inputProblem: this.inputProblem,
|
|
137
144
|
traces: this.traces,
|
|
138
145
|
netLabelPlacements: this.netLabelPlacements,
|
|
146
|
+
fixedNetLabelPlacements: this.params.fixedNetLabelPlacements,
|
|
147
|
+
useRenderedLabelBounds: this.params.useRenderedLabelBounds,
|
|
139
148
|
}
|
|
140
149
|
}
|
|
141
150
|
|
|
@@ -144,7 +153,12 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
144
153
|
}
|
|
145
154
|
|
|
146
155
|
private labelBounds(label: NetLabelPlacement) {
|
|
147
|
-
|
|
156
|
+
if (
|
|
157
|
+
!this.params.useRenderedLabelBounds ||
|
|
158
|
+
this.params.fixedNetLabelPlacements?.includes(label)
|
|
159
|
+
)
|
|
160
|
+
return getRectBounds(label.center, label.width, label.height)
|
|
161
|
+
return getAnchoredNetLabelRenderedBounds(label)
|
|
148
162
|
}
|
|
149
163
|
|
|
150
164
|
private collisionKey(a: NetLabelPlacement, b: NetLabelPlacement) {
|
|
@@ -154,12 +168,19 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
154
168
|
private findNextCollidingPair():
|
|
155
169
|
| [NetLabelPlacement, NetLabelPlacement]
|
|
156
170
|
| null {
|
|
157
|
-
const labels =
|
|
158
|
-
|
|
171
|
+
const labels = [
|
|
172
|
+
...this.outputNetLabelPlacements,
|
|
173
|
+
...(this.params.fixedNetLabelPlacements ?? []),
|
|
174
|
+
]
|
|
175
|
+
for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
|
|
159
176
|
for (let j = i + 1; j < labels.length; j++) {
|
|
160
177
|
const a = labels[i]!
|
|
161
178
|
const b = labels[j]!
|
|
162
|
-
if (
|
|
179
|
+
if (
|
|
180
|
+
a.globalConnNetId === b.globalConnNetId &&
|
|
181
|
+
!this.params.fixedNetLabelPlacements?.includes(b)
|
|
182
|
+
)
|
|
183
|
+
continue
|
|
163
184
|
if (this.skippedCollisionKeys.has(this.collisionKey(a, b))) continue
|
|
164
185
|
if (boundsOverlap(this.labelBounds(a), this.labelBounds(b)))
|
|
165
186
|
return [a, b]
|
|
@@ -208,7 +229,16 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
208
229
|
center,
|
|
209
230
|
width,
|
|
210
231
|
height,
|
|
211
|
-
bounds:
|
|
232
|
+
bounds: this.params.useRenderedLabelBounds
|
|
233
|
+
? getAnchoredNetLabelRenderedBounds({
|
|
234
|
+
...label,
|
|
235
|
+
orientation,
|
|
236
|
+
anchorPoint: anchor,
|
|
237
|
+
center,
|
|
238
|
+
width,
|
|
239
|
+
height,
|
|
240
|
+
})
|
|
241
|
+
: getRectBounds(center, width, height),
|
|
212
242
|
hostPairId,
|
|
213
243
|
hostSegIndex,
|
|
214
244
|
status: null,
|
|
@@ -271,7 +301,7 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
271
301
|
): CandidateStatus {
|
|
272
302
|
const { bounds, hostPairId, hostSegIndex } = candidate
|
|
273
303
|
|
|
274
|
-
if (this.chipIndex
|
|
304
|
+
if ((this.chipIndex?.getChipsInBounds(bounds).length ?? 0) > 0)
|
|
275
305
|
return "chip-collision"
|
|
276
306
|
if (rectIntersectsAnyTextBox(bounds, this.inputProblem))
|
|
277
307
|
return "text-collision"
|
|
@@ -284,7 +314,11 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
284
314
|
}
|
|
285
315
|
|
|
286
316
|
for (const obstacle of obstacleLabels) {
|
|
287
|
-
if (
|
|
317
|
+
if (
|
|
318
|
+
obstacle.globalConnNetId === movingLabelNetId &&
|
|
319
|
+
!this.params.fixedNetLabelPlacements?.includes(obstacle)
|
|
320
|
+
)
|
|
321
|
+
continue
|
|
288
322
|
if (boundsOverlap(bounds, this.labelBounds(obstacle)))
|
|
289
323
|
return "label-collision"
|
|
290
324
|
}
|
|
@@ -299,6 +333,38 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
299
333
|
this.candidateResults = []
|
|
300
334
|
}
|
|
301
335
|
|
|
336
|
+
/** Reuse the normal anchor search for a local trace-clearance proposal. */
|
|
337
|
+
getNearbyValidPlacements(
|
|
338
|
+
label: NetLabelPlacement,
|
|
339
|
+
maxDistance: number,
|
|
340
|
+
): NetLabelPlacement[] {
|
|
341
|
+
const distance = (candidate: Candidate) =>
|
|
342
|
+
Math.hypot(
|
|
343
|
+
candidate.anchor.x - label.anchorPoint.x,
|
|
344
|
+
candidate.anchor.y - label.anchorPoint.y,
|
|
345
|
+
)
|
|
346
|
+
const obstacles = [
|
|
347
|
+
...this.outputNetLabelPlacements.filter((other) => other !== label),
|
|
348
|
+
...(this.params.fixedNetLabelPlacements ?? []),
|
|
349
|
+
]
|
|
350
|
+
return this.buildCandidatesForLabel(label)
|
|
351
|
+
.filter(
|
|
352
|
+
(candidate) =>
|
|
353
|
+
distance(candidate) <= maxDistance &&
|
|
354
|
+
this.checkCandidate(candidate, label.globalConnNetId, obstacles) ===
|
|
355
|
+
"ok",
|
|
356
|
+
)
|
|
357
|
+
.sort((a, b) => distance(a) - distance(b))
|
|
358
|
+
.map((candidate) => ({
|
|
359
|
+
...label,
|
|
360
|
+
orientation: candidate.orientation,
|
|
361
|
+
anchorPoint: candidate.anchor,
|
|
362
|
+
center: candidate.center,
|
|
363
|
+
width: candidate.width,
|
|
364
|
+
height: candidate.height,
|
|
365
|
+
}))
|
|
366
|
+
}
|
|
367
|
+
|
|
302
368
|
private clearActiveSearch() {
|
|
303
369
|
this.currentCollision = null
|
|
304
370
|
this.currentLabelToMove = null
|
|
@@ -316,7 +382,9 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
316
382
|
return
|
|
317
383
|
}
|
|
318
384
|
this.currentCollision = pair
|
|
319
|
-
this.labelsToTry = [pair[1], pair[0]]
|
|
385
|
+
this.labelsToTry = [pair[1], pair[0]].filter(
|
|
386
|
+
(label) => !(this.params.fixedNetLabelPlacements ?? []).includes(label),
|
|
387
|
+
)
|
|
320
388
|
this.beginSearchForLabel(this.labelsToTry.shift()!)
|
|
321
389
|
return
|
|
322
390
|
}
|
|
@@ -342,9 +410,10 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
342
410
|
fixedLabel = labelB
|
|
343
411
|
}
|
|
344
412
|
const obstacleLabels = [
|
|
345
|
-
...
|
|
346
|
-
|
|
347
|
-
|
|
413
|
+
...[
|
|
414
|
+
...this.outputNetLabelPlacements,
|
|
415
|
+
...(this.params.fixedNetLabelPlacements ?? []),
|
|
416
|
+
].filter((l) => l !== labelA && l !== labelB),
|
|
348
417
|
fixedLabel,
|
|
349
418
|
]
|
|
350
419
|
|
|
@@ -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",
|
|
@@ -648,20 +648,18 @@ const pathsIntersect = (firstPath: Point[], secondPath: Point[]) => {
|
|
|
648
648
|
}
|
|
649
649
|
|
|
650
650
|
/**
|
|
651
|
-
* Keep
|
|
651
|
+
* Keep same-net branches joined when their host trace moves. Only a
|
|
652
652
|
* connector endpoint is projected, and only along its existing first/last
|
|
653
653
|
* segment, so the label and the rest of its route stay fixed.
|
|
654
654
|
*/
|
|
655
|
-
const
|
|
655
|
+
const preserveAttachedTraceJunctions = ({
|
|
656
656
|
originalTrace,
|
|
657
657
|
candidateTrace,
|
|
658
658
|
traces,
|
|
659
|
-
netLabelConnectorTraceIds,
|
|
660
659
|
}: {
|
|
661
660
|
originalTrace: SolvedTracePath
|
|
662
661
|
candidateTrace: SolvedTracePath
|
|
663
662
|
traces: SolvedTracePath[]
|
|
664
|
-
netLabelConnectorTraceIds: ReadonlySet<MspConnectionPairId>
|
|
665
663
|
}): SolvedTracePath[] | null => {
|
|
666
664
|
let outputTraces = traces.map((trace) =>
|
|
667
665
|
trace.mspPairId === originalTrace.mspPairId ? candidateTrace : trace,
|
|
@@ -670,7 +668,6 @@ const preserveAttachedLabelConnectorJunctions = ({
|
|
|
670
668
|
for (const connector of traces) {
|
|
671
669
|
if (
|
|
672
670
|
connector.mspPairId === originalTrace.mspPairId ||
|
|
673
|
-
!netLabelConnectorTraceIds.has(connector.mspPairId) ||
|
|
674
671
|
connector.globalConnNetId !== originalTrace.globalConnNetId ||
|
|
675
672
|
!pathsIntersect(originalTrace.tracePath, connector.tracePath) ||
|
|
676
673
|
pathsIntersect(candidateTrace.tracePath, connector.tracePath)
|
|
@@ -686,6 +683,16 @@ const preserveAttachedLabelConnectorJunctions = ({
|
|
|
686
683
|
)
|
|
687
684
|
if (endpointIndex === undefined) return null
|
|
688
685
|
const originalJunction = connector.tracePath[endpointIndex]!
|
|
686
|
+
// Component pins are fixed; reject a rail move that would detach one.
|
|
687
|
+
if (
|
|
688
|
+
connector.pins.some(
|
|
689
|
+
(pin) =>
|
|
690
|
+
nearlyEqual(pin.x, originalJunction.x) &&
|
|
691
|
+
nearlyEqual(pin.y, originalJunction.y),
|
|
692
|
+
)
|
|
693
|
+
) {
|
|
694
|
+
return null
|
|
695
|
+
}
|
|
689
696
|
const movedJunction = getMovedAnchorPointForReroute(
|
|
690
697
|
originalJunction,
|
|
691
698
|
originalTrace.tracePath,
|
|
@@ -1250,11 +1257,10 @@ export const alignSameNetJunctions = ({
|
|
|
1250
1257
|
netLabelPlacements: outputNetLabelPlacements,
|
|
1251
1258
|
attachedLabelIndexes,
|
|
1252
1259
|
})
|
|
1253
|
-
const candidateTraces =
|
|
1260
|
+
const candidateTraces = preserveAttachedTraceJunctions({
|
|
1254
1261
|
originalTrace: branchTrace,
|
|
1255
1262
|
candidateTrace,
|
|
1256
1263
|
traces: outputTraces,
|
|
1257
|
-
netLabelConnectorTraceIds,
|
|
1258
1264
|
})
|
|
1259
1265
|
if (!candidateTraces) continue
|
|
1260
1266
|
if (
|
|
@@ -1272,10 +1278,7 @@ export const alignSameNetJunctions = ({
|
|
|
1272
1278
|
}
|
|
1273
1279
|
|
|
1274
1280
|
const movedConnectorsAreClear = candidateTraces.every((trace) => {
|
|
1275
|
-
if (
|
|
1276
|
-
trace.mspPairId === branchTrace.mspPairId ||
|
|
1277
|
-
!netLabelConnectorTraceIds.has(trace.mspPairId)
|
|
1278
|
-
) {
|
|
1281
|
+
if (trace.mspPairId === branchTrace.mspPairId) {
|
|
1279
1282
|
return true
|
|
1280
1283
|
}
|
|
1281
1284
|
const originalConnector = outputTraces.find(
|
|
@@ -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
|
},
|
|
@@ -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