@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
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import { getLocalTraceLabelShifts } from "./getLocalTraceLabelShifts"
|
|
2
|
+
import {
|
|
3
|
+
getOutputLabelCollisions,
|
|
4
|
+
sameOutputLabelCollision,
|
|
5
|
+
type OutputLabelCollision,
|
|
6
|
+
} from "./getOutputLabelCollisions"
|
|
7
|
+
import { getInlineLabelObstacles } from "./getInlineLabelObstacles"
|
|
8
|
+
import { NetLabelNetLabelCollisionSolver } from "../NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver"
|
|
1
9
|
import type { Bounds, Point } from "@tscircuit/math-utils"
|
|
2
10
|
import type { GraphicsObject, Rect, Text } from "graphics-debug"
|
|
3
11
|
import type { ConnectivityMap } from "connectivity-map"
|
|
@@ -17,6 +25,7 @@ import type {
|
|
|
17
25
|
} from "lib/types/InputProblem"
|
|
18
26
|
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
19
27
|
import { boundsOverlap, getTextBoxBounds } from "lib/utils/textBoxBounds"
|
|
28
|
+
import { isLabelRepresentedInline } from "./isLabelRepresentedInline"
|
|
20
29
|
import { alignPortOnlyInlineNetLabelStubs } from "./alignPortOnlyInlineNetLabelStubs"
|
|
21
30
|
import {
|
|
22
31
|
type AxisAlignedSegment,
|
|
@@ -30,7 +39,8 @@ import { restoreReroutesAroundSupersededLabels } from "./restoreReroutesAroundSu
|
|
|
30
39
|
export const DEFAULT_INLINE_NET_LABEL_HEIGHT = 0.18
|
|
31
40
|
|
|
32
41
|
/**
|
|
33
|
-
*
|
|
42
|
+
* Preferred gap between a trace and its inline text. A tighter parallel-wire
|
|
43
|
+
* slot may use a smaller, evenly divided gap if the full text still fits.
|
|
34
44
|
*/
|
|
35
45
|
export const INLINE_NET_LABEL_TRACE_MARGIN = 0.05
|
|
36
46
|
|
|
@@ -94,6 +104,9 @@ export interface InlineNetLabelPlacement {
|
|
|
94
104
|
}
|
|
95
105
|
|
|
96
106
|
interface InlineNetLabelSolverInput {
|
|
107
|
+
/** Exact connector IDs supplied by their producer; IDs are never parsed. */
|
|
108
|
+
netLabelConnectorTraceIds?: ReadonlySet<string>
|
|
109
|
+
resolveTraceCollisions?: boolean
|
|
97
110
|
inputProblem: InputProblem
|
|
98
111
|
traces: SolvedTracePath[]
|
|
99
112
|
netLabelPlacements: NetLabelPlacement[]
|
|
@@ -101,12 +114,15 @@ interface InlineNetLabelSolverInput {
|
|
|
101
114
|
}
|
|
102
115
|
|
|
103
116
|
export interface InlineNetLabelOutput {
|
|
117
|
+
netLabelConnectorTraceIds?: ReadonlySet<string>
|
|
104
118
|
inputProblem: InputProblem
|
|
105
119
|
traces: SolvedTracePath[]
|
|
106
120
|
netLabelPlacements: NetLabelPlacement[]
|
|
107
121
|
inlineNetLabelPlacements: InlineNetLabelPlacement[]
|
|
108
122
|
}
|
|
109
123
|
|
|
124
|
+
type InlineOutput = Omit<InlineNetLabelOutput, "inputProblem">
|
|
125
|
+
|
|
110
126
|
type InlineEligibleConnection = InputDirectConnection | InputNetConnection
|
|
111
127
|
|
|
112
128
|
type QueuedInlineConnection =
|
|
@@ -125,13 +141,8 @@ interface NetConnectionComponent {
|
|
|
125
141
|
traces: SolvedTracePath[]
|
|
126
142
|
}
|
|
127
143
|
|
|
128
|
-
const AVAILABLE_NET_ORIENTATION_TRACE_PREFIX = "available-net-orientation-"
|
|
129
|
-
|
|
130
|
-
const isGeneratedAnchoredLabelConnector = (trace: SolvedTracePath) =>
|
|
131
|
-
trace.mspPairId.startsWith(AVAILABLE_NET_ORIENTATION_TRACE_PREFIX)
|
|
132
|
-
|
|
133
144
|
const getPinPairKey = (pinIds: readonly string[]) =>
|
|
134
|
-
[...pinIds].sort()
|
|
145
|
+
JSON.stringify([...pinIds].sort())
|
|
135
146
|
|
|
136
147
|
const getTraceLength = (trace: SolvedTracePath) => {
|
|
137
148
|
let length = 0
|
|
@@ -161,12 +172,33 @@ const getInlinePlacementBounds = (
|
|
|
161
172
|
}
|
|
162
173
|
}
|
|
163
174
|
|
|
175
|
+
const getInlinePlacementKey = (placement: InlineNetLabelPlacement) =>
|
|
176
|
+
JSON.stringify([
|
|
177
|
+
placement.globalConnNetId,
|
|
178
|
+
[...placement.pinIds].sort(),
|
|
179
|
+
placement.mspPairId ?? null,
|
|
180
|
+
])
|
|
181
|
+
|
|
182
|
+
const terminalStubIntersectsTrace = (
|
|
183
|
+
path: [Point, Point],
|
|
184
|
+
trace: SolvedTracePath,
|
|
185
|
+
) => {
|
|
186
|
+
const clearance = INLINE_NET_LABEL_STUB_TRACE_CLEARANCE - GEOMETRY_EPSILON
|
|
187
|
+
const bounds = {
|
|
188
|
+
minX: Math.min(path[0].x, path[1].x) - clearance,
|
|
189
|
+
maxX: Math.max(path[0].x, path[1].x) + clearance,
|
|
190
|
+
minY: Math.min(path[0].y, path[1].y) - clearance,
|
|
191
|
+
maxY: Math.max(path[0].y, path[1].y) + clearance,
|
|
192
|
+
}
|
|
193
|
+
return doesPathIntersectBounds(trace.tracePath, bounds)
|
|
194
|
+
}
|
|
195
|
+
|
|
164
196
|
/**
|
|
165
197
|
* Places "inline net labels" - net names drawn alongside the trace they belong
|
|
166
198
|
* to - for connections that opted in via `allowInlineNetLabel`.
|
|
167
199
|
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
200
|
+
* Anchored labels are removed only for the connected components represented
|
|
201
|
+
* inline. A blocked component keeps its label without undoing clear ones.
|
|
170
202
|
*/
|
|
171
203
|
export class InlineNetLabelSolver extends BaseSolver {
|
|
172
204
|
inputProblem: InputProblem
|
|
@@ -179,18 +211,29 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
179
211
|
/** Eligible connections still waiting to be processed. */
|
|
180
212
|
queuedConnections: QueuedInlineConnection[]
|
|
181
213
|
|
|
214
|
+
private readonly netLabelConnectorTraceIds: ReadonlySet<string>
|
|
182
215
|
private tracesByPinPairKey: Map<string, SolvedTracePath[]>
|
|
183
216
|
private globalConnMap: ConnectivityMap
|
|
184
|
-
private
|
|
217
|
+
private inlineConversions: NetConnectionInlineConversion[] = []
|
|
185
218
|
private hasAlignedPortOnlyStubs = false
|
|
186
|
-
private postProcessedOutput?:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
219
|
+
private postProcessedOutput?: InlineOutput
|
|
220
|
+
declare activeSubSolver: InlineNetLabelSolver | null
|
|
221
|
+
private refinementComplete = false
|
|
222
|
+
private refinement?: {
|
|
223
|
+
proposals: Generator<InlineOutput>
|
|
224
|
+
queue: Array<{
|
|
225
|
+
output: InlineOutput
|
|
226
|
+
distance: number
|
|
227
|
+
}>
|
|
228
|
+
collecting: boolean
|
|
229
|
+
before: OutputLabelCollision[]
|
|
190
230
|
}
|
|
231
|
+
private currentProposal?: InlineOutput
|
|
191
232
|
|
|
192
|
-
constructor(input: InlineNetLabelSolverInput) {
|
|
233
|
+
constructor(private readonly input: InlineNetLabelSolverInput) {
|
|
193
234
|
super()
|
|
235
|
+
this.netLabelConnectorTraceIds =
|
|
236
|
+
input.netLabelConnectorTraceIds ?? new Set()
|
|
194
237
|
this.inputProblem = input.inputProblem
|
|
195
238
|
this.traces = input.traces
|
|
196
239
|
this.inputNetLabelPlacements = input.netLabelPlacements
|
|
@@ -202,7 +245,17 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
202
245
|
this.queuedConnections = [
|
|
203
246
|
...this.inputProblem.directConnections
|
|
204
247
|
.filter(
|
|
205
|
-
(connection) =>
|
|
248
|
+
(connection) =>
|
|
249
|
+
connection.allowInlineNetLabel &&
|
|
250
|
+
connection.netId &&
|
|
251
|
+
// An opted-in named net owns all of its connected components.
|
|
252
|
+
// Do not plan the same pair again through its direct connection.
|
|
253
|
+
!this.inputProblem.netConnections.some(
|
|
254
|
+
(net) =>
|
|
255
|
+
net.allowInlineNetLabel &&
|
|
256
|
+
net.netId &&
|
|
257
|
+
connection.pinIds.every((pin) => net.pinIds.includes(pin)),
|
|
258
|
+
),
|
|
206
259
|
)
|
|
207
260
|
.map((connection) => ({
|
|
208
261
|
kind: "direct_connection" as const,
|
|
@@ -223,7 +276,7 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
223
276
|
|
|
224
277
|
this.tracesByPinPairKey = new Map()
|
|
225
278
|
for (const trace of this.traces) {
|
|
226
|
-
if (
|
|
279
|
+
if (this.netLabelConnectorTraceIds.has(trace.mspPairId)) continue
|
|
227
280
|
const key = getPinPairKey(trace.pins.map((p) => p.pinId))
|
|
228
281
|
const existing = this.tracesByPinPairKey.get(key)
|
|
229
282
|
if (existing) {
|
|
@@ -241,6 +294,8 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
241
294
|
traces: this.traces,
|
|
242
295
|
netLabelPlacements: this.inputNetLabelPlacements,
|
|
243
296
|
completedReroutes: this.completedReroutes,
|
|
297
|
+
resolveTraceCollisions: this.input.resolveTraceCollisions,
|
|
298
|
+
netLabelConnectorTraceIds: this.netLabelConnectorTraceIds,
|
|
244
299
|
},
|
|
245
300
|
]
|
|
246
301
|
}
|
|
@@ -276,17 +331,7 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
276
331
|
netConnections,
|
|
277
332
|
)) {
|
|
278
333
|
this.inlineNetLabelPlacements.push(...conversion.placements)
|
|
279
|
-
|
|
280
|
-
this.supersededTraceIdsByGlobalConnNetId.get(
|
|
281
|
-
conversion.globalConnNetId,
|
|
282
|
-
) ?? new Set<string>()
|
|
283
|
-
for (const traceId of conversion.supersededTraceIds) {
|
|
284
|
-
supersededTraceIds.add(traceId)
|
|
285
|
-
}
|
|
286
|
-
this.supersededTraceIdsByGlobalConnNetId.set(
|
|
287
|
-
conversion.globalConnNetId,
|
|
288
|
-
supersededTraceIds,
|
|
289
|
-
)
|
|
334
|
+
this.inlineConversions.push(conversion)
|
|
290
335
|
}
|
|
291
336
|
return
|
|
292
337
|
}
|
|
@@ -307,15 +352,15 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
307
352
|
})
|
|
308
353
|
if (placement) this.inlineNetLabelPlacements.push(placement)
|
|
309
354
|
} else {
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
this.inlineNetLabelPlacements.push(
|
|
355
|
+
// Unrouted endpoints are separate connected components. A blocked
|
|
356
|
+
// endpoint keeps its anchored tag without changing the label style at
|
|
357
|
+
// clear endpoints of the same named net.
|
|
358
|
+
for (const pinId of connection.pinIds) {
|
|
359
|
+
const placement = this.computeTerminalInlinePlacement(
|
|
360
|
+
connection,
|
|
361
|
+
pinId,
|
|
362
|
+
)
|
|
363
|
+
if (placement) this.inlineNetLabelPlacements.push(placement)
|
|
319
364
|
}
|
|
320
365
|
}
|
|
321
366
|
return
|
|
@@ -337,6 +382,10 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
337
382
|
return
|
|
338
383
|
}
|
|
339
384
|
|
|
385
|
+
if (this.input.resolveTraceCollisions && !this.refinementComplete) {
|
|
386
|
+
this.stepTraceRefinement()
|
|
387
|
+
return
|
|
388
|
+
}
|
|
340
389
|
this.solved = true
|
|
341
390
|
this.stats.inlineNetLabelCount = this.inlineNetLabelPlacements.length
|
|
342
391
|
}
|
|
@@ -344,7 +393,7 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
344
393
|
/**
|
|
345
394
|
* Plans all opted-in multi-pin nets together. Short routed components can
|
|
346
395
|
* mutually block the outward endpoint stubs that replace them, so tentative
|
|
347
|
-
* replacements are ignored as obstacles only while their
|
|
396
|
+
* replacements are ignored as obstacles only while their connected component still
|
|
348
397
|
* converts successfully. Failed conversions retain both their traces and
|
|
349
398
|
* their conventional labels, and the remaining conversions are retried.
|
|
350
399
|
*/
|
|
@@ -392,13 +441,11 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
392
441
|
}))
|
|
393
442
|
const nextIgnoredTraceIds = new Set(
|
|
394
443
|
conversions.flatMap(({ conversion }) =>
|
|
395
|
-
conversion
|
|
444
|
+
conversion.flatMap((component) => [...component.supersededTraceIds]),
|
|
396
445
|
),
|
|
397
446
|
)
|
|
398
447
|
if (setsEqual(ignoredTraceIds, nextIgnoredTraceIds)) {
|
|
399
|
-
return conversions.flatMap(({ conversion }) =>
|
|
400
|
-
conversion ? [conversion] : [],
|
|
401
|
-
)
|
|
448
|
+
return conversions.flatMap(({ conversion }) => conversion)
|
|
402
449
|
}
|
|
403
450
|
ignoredTraceIds = nextIgnoredTraceIds
|
|
404
451
|
}
|
|
@@ -436,7 +483,7 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
436
483
|
// anchored label. They are removed when that label becomes inline, so
|
|
437
484
|
// treating them as routed circuitry would leave text on a deleted wire.
|
|
438
485
|
traces: this.traces.filter(
|
|
439
|
-
(trace) => !
|
|
486
|
+
(trace) => !this.netLabelConnectorTraceIds.has(trace.mspPairId),
|
|
440
487
|
),
|
|
441
488
|
})
|
|
442
489
|
.map((component) => ({
|
|
@@ -473,19 +520,19 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
473
520
|
* Converts every conventional label representation for an opted-in named
|
|
474
521
|
* net. Routed connected components become labels on their representative
|
|
475
522
|
* trace; disconnected pins become outward terminal stubs. The conversion is
|
|
476
|
-
* atomic
|
|
523
|
+
* atomic within each routed component, so replacing its traces cannot strand
|
|
524
|
+
* a pin. Disconnected components can keep their own label style.
|
|
477
525
|
*/
|
|
478
526
|
private computeNetConnectionInlinePlacements(
|
|
479
527
|
connection: InputNetConnection,
|
|
480
528
|
ignoredTraceIds: Set<string>,
|
|
481
529
|
forcedTerminalTraceIds: Set<string>,
|
|
482
|
-
): NetConnectionInlineConversion
|
|
530
|
+
): NetConnectionInlineConversion[] {
|
|
483
531
|
const components = this.getNetConnectionComponents(connection)
|
|
484
532
|
const globalConnNetId = this.getGlobalConnNetId(connection)
|
|
485
|
-
if (!globalConnNetId) return
|
|
533
|
+
if (!globalConnNetId) return []
|
|
486
534
|
|
|
487
|
-
const
|
|
488
|
-
const supersededTraceIds = new Set<string>()
|
|
535
|
+
const conversions: NetConnectionInlineConversion[] = []
|
|
489
536
|
for (const component of components) {
|
|
490
537
|
const shouldUseTerminalStubs = component.traces.some((trace) =>
|
|
491
538
|
forcedTerminalTraceIds.has(trace.mspPairId),
|
|
@@ -504,21 +551,25 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
504
551
|
}
|
|
505
552
|
}
|
|
506
553
|
if (inlinePlacement) {
|
|
507
|
-
|
|
554
|
+
conversions.push({
|
|
555
|
+
globalConnNetId,
|
|
556
|
+
placements: [inlinePlacement],
|
|
557
|
+
supersededTraceIds: new Set(),
|
|
558
|
+
})
|
|
508
559
|
continue
|
|
509
560
|
}
|
|
510
561
|
if (component.traces.length > 0 && !shouldUseTerminalStubs) {
|
|
511
562
|
// This route originally had a valid inline placement, but a trace from
|
|
512
|
-
// a conversion that had to be rolled back now blocks it.
|
|
513
|
-
// this
|
|
563
|
+
// a conversion that had to be rolled back now blocks it. Retain
|
|
564
|
+
// this component as well instead of introducing a new replacement and
|
|
514
565
|
// allowing the batch plan to oscillate.
|
|
515
|
-
|
|
566
|
+
continue
|
|
516
567
|
}
|
|
517
568
|
if (component.labeledPinIds.length !== component.pinIds.length) {
|
|
518
569
|
// Removing this component's traces would strand an intermediate pin
|
|
519
570
|
// that was connected through the global net but was not an endpoint of
|
|
520
571
|
// this named connection. Retain the original routed representation.
|
|
521
|
-
|
|
572
|
+
continue
|
|
522
573
|
}
|
|
523
574
|
|
|
524
575
|
// A routed component may be too short to carry its text. In that case,
|
|
@@ -534,33 +585,69 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
534
585
|
),
|
|
535
586
|
)
|
|
536
587
|
if (terminalPlacements.some((placement) => placement === null)) {
|
|
537
|
-
|
|
588
|
+
continue
|
|
538
589
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
590
|
+
conversions.push({
|
|
591
|
+
globalConnNetId,
|
|
592
|
+
placements: terminalPlacements as InlineNetLabelPlacement[],
|
|
593
|
+
supersededTraceIds: new Set(
|
|
594
|
+
component.traces.map((trace) => trace.mspPairId),
|
|
543
595
|
),
|
|
544
|
-
)
|
|
545
|
-
for (const trace of component.traces) {
|
|
546
|
-
supersededTraceIds.add(trace.mspPairId)
|
|
547
|
-
}
|
|
596
|
+
})
|
|
548
597
|
}
|
|
549
598
|
|
|
550
|
-
return
|
|
599
|
+
return conversions
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/** Prefer the normal gap, then center text in a tighter parallel-wire slot. */
|
|
603
|
+
private getInlineOffsets(
|
|
604
|
+
anchor: Point,
|
|
605
|
+
axis: "x" | "y",
|
|
606
|
+
side: InlineNetLabelPlacement["side"],
|
|
607
|
+
width: number,
|
|
608
|
+
height: number,
|
|
609
|
+
traces: SolvedTracePath[],
|
|
610
|
+
ignoredTraceIds: Set<string>,
|
|
611
|
+
): number[] {
|
|
612
|
+
const normal = height / 2 + INLINE_NET_LABEL_TRACE_MARGIN
|
|
613
|
+
const across = axis === "x" ? "y" : "x"
|
|
614
|
+
const sign = side === "x+" || side === "y+" ? 1 : -1
|
|
615
|
+
let gap = Infinity
|
|
616
|
+
for (const trace of traces) {
|
|
617
|
+
if (ignoredTraceIds.has(trace.mspPairId)) continue
|
|
618
|
+
for (const segment of getAxisAlignedSegments(trace.tracePath)) {
|
|
619
|
+
if (segment.axis !== axis) continue
|
|
620
|
+
if (
|
|
621
|
+
Math.max(segment.start[axis], segment.end[axis]) <=
|
|
622
|
+
anchor[axis] - width / 2 ||
|
|
623
|
+
Math.min(segment.start[axis], segment.end[axis]) >=
|
|
624
|
+
anchor[axis] + width / 2
|
|
625
|
+
)
|
|
626
|
+
continue
|
|
627
|
+
const distance = sign * (segment.start[across] - anchor[across])
|
|
628
|
+
if (distance > GEOMETRY_EPSILON) gap = Math.min(gap, distance)
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return gap > height + GEOMETRY_EPSILON &&
|
|
632
|
+
gap / 2 < normal - GEOMETRY_EPSILON
|
|
633
|
+
? [normal, gap / 2]
|
|
634
|
+
: [normal]
|
|
551
635
|
}
|
|
552
636
|
|
|
553
637
|
/**
|
|
554
|
-
* Converts one conventional endpoint placement into an inline label on
|
|
555
|
-
*
|
|
556
|
-
* an anchored label may finish in another direction after an elbow, which is
|
|
557
|
-
* not the direction a terminal stub should leave the pin.
|
|
638
|
+
* Converts one conventional endpoint placement into an inline label on an
|
|
639
|
+
* outward stub that follows the pin's true facing direction.
|
|
558
640
|
*/
|
|
559
641
|
private computeTerminalInlinePlacement(
|
|
560
642
|
connection: InlineEligibleConnection,
|
|
561
643
|
pinId: PinId,
|
|
562
644
|
knownGlobalConnNetId?: string,
|
|
563
645
|
ignoredTraceIds = new Set<string>(),
|
|
646
|
+
obstacles?: {
|
|
647
|
+
traces: SolvedTracePath[]
|
|
648
|
+
anchored: NetLabelPlacement[]
|
|
649
|
+
inline: InlineNetLabelPlacement[]
|
|
650
|
+
},
|
|
564
651
|
): InlineNetLabelPlacement | null {
|
|
565
652
|
if (!connection.netId) return null
|
|
566
653
|
|
|
@@ -611,7 +698,7 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
611
698
|
start,
|
|
612
699
|
intendedEnd,
|
|
613
700
|
minimumLength: width + 2 * INLINE_NET_LABEL_TRACE_MARGIN,
|
|
614
|
-
traces: this.traces,
|
|
701
|
+
traces: obstacles?.traces ?? this.traces,
|
|
615
702
|
ownGlobalConnNetId: globalConnNetId,
|
|
616
703
|
ignoredTraceIds,
|
|
617
704
|
})
|
|
@@ -623,61 +710,70 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
623
710
|
x: (start.x + end.x) / 2,
|
|
624
711
|
y: (start.y + end.y) / 2,
|
|
625
712
|
}
|
|
626
|
-
const offset = height / 2 + INLINE_NET_LABEL_TRACE_MARGIN
|
|
627
713
|
const sides: InlineNetLabelPlacement["side"][] =
|
|
628
714
|
axis === "x" ? ["y+", "y-"] : ["x-", "x+"]
|
|
629
715
|
|
|
630
716
|
for (const side of sides) {
|
|
631
|
-
const
|
|
632
|
-
side === "y+"
|
|
633
|
-
? { x: anchorPoint.x, y: anchorPoint.y + offset }
|
|
634
|
-
: side === "y-"
|
|
635
|
-
? { x: anchorPoint.x, y: anchorPoint.y - offset }
|
|
636
|
-
: side === "x-"
|
|
637
|
-
? { x: anchorPoint.x - offset, y: anchorPoint.y }
|
|
638
|
-
: { x: anchorPoint.x + offset, y: anchorPoint.y }
|
|
639
|
-
|
|
640
|
-
const halfAlong = width / 2
|
|
641
|
-
const halfAcross = height / 2
|
|
642
|
-
const bounds: Bounds =
|
|
643
|
-
axis === "x"
|
|
644
|
-
? {
|
|
645
|
-
minX: center.x - halfAlong,
|
|
646
|
-
maxX: center.x + halfAlong,
|
|
647
|
-
minY: center.y - halfAcross,
|
|
648
|
-
maxY: center.y + halfAcross,
|
|
649
|
-
}
|
|
650
|
-
: {
|
|
651
|
-
minX: center.x - halfAcross,
|
|
652
|
-
maxX: center.x + halfAcross,
|
|
653
|
-
minY: center.y - halfAlong,
|
|
654
|
-
maxY: center.y + halfAlong,
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
if (
|
|
658
|
-
this.isObstructed(bounds, {
|
|
659
|
-
ownGlobalConnNetId: globalConnNetId,
|
|
660
|
-
ignoredTraceIds,
|
|
661
|
-
})
|
|
662
|
-
) {
|
|
663
|
-
continue
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
return {
|
|
667
|
-
globalConnNetId,
|
|
668
|
-
netId: connection.netId,
|
|
669
|
-
netLabelText,
|
|
670
|
-
pinIds: [pinId],
|
|
671
|
-
stubTracePath: [start, end],
|
|
672
|
-
axis,
|
|
717
|
+
for (const offset of this.getInlineOffsets(
|
|
673
718
|
anchorPoint,
|
|
674
|
-
|
|
719
|
+
axis,
|
|
720
|
+
side,
|
|
675
721
|
width,
|
|
676
722
|
height,
|
|
677
|
-
|
|
723
|
+
obstacles?.traces ?? this.traces,
|
|
724
|
+
ignoredTraceIds,
|
|
725
|
+
)) {
|
|
726
|
+
const center: Point =
|
|
727
|
+
side === "y+"
|
|
728
|
+
? { x: anchorPoint.x, y: anchorPoint.y + offset }
|
|
729
|
+
: side === "y-"
|
|
730
|
+
? { x: anchorPoint.x, y: anchorPoint.y - offset }
|
|
731
|
+
: side === "x-"
|
|
732
|
+
? { x: anchorPoint.x - offset, y: anchorPoint.y }
|
|
733
|
+
: { x: anchorPoint.x + offset, y: anchorPoint.y }
|
|
734
|
+
|
|
735
|
+
const halfAlong = width / 2
|
|
736
|
+
const halfAcross = height / 2
|
|
737
|
+
const bounds: Bounds =
|
|
738
|
+
axis === "x"
|
|
739
|
+
? {
|
|
740
|
+
minX: center.x - halfAlong,
|
|
741
|
+
maxX: center.x + halfAlong,
|
|
742
|
+
minY: center.y - halfAcross,
|
|
743
|
+
maxY: center.y + halfAcross,
|
|
744
|
+
}
|
|
745
|
+
: {
|
|
746
|
+
minX: center.x - halfAcross,
|
|
747
|
+
maxX: center.x + halfAcross,
|
|
748
|
+
minY: center.y - halfAlong,
|
|
749
|
+
maxY: center.y + halfAlong,
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
if (
|
|
753
|
+
this.isObstructed(bounds, {
|
|
754
|
+
ownGlobalConnNetId: globalConnNetId,
|
|
755
|
+
ignoredTraceIds,
|
|
756
|
+
obstacles,
|
|
757
|
+
})
|
|
758
|
+
) {
|
|
759
|
+
continue
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
return {
|
|
763
|
+
globalConnNetId,
|
|
764
|
+
netId: connection.netId,
|
|
765
|
+
netLabelText,
|
|
766
|
+
pinIds: [pinId],
|
|
767
|
+
stubTracePath: [start, end],
|
|
768
|
+
axis,
|
|
769
|
+
anchorPoint,
|
|
770
|
+
center,
|
|
771
|
+
width,
|
|
772
|
+
height,
|
|
773
|
+
side,
|
|
774
|
+
}
|
|
678
775
|
}
|
|
679
776
|
}
|
|
680
|
-
|
|
681
777
|
return null
|
|
682
778
|
}
|
|
683
779
|
|
|
@@ -722,53 +818,63 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
722
818
|
|
|
723
819
|
for (const side of sides) {
|
|
724
820
|
for (const anchorPoint of getAnchorCandidates(segment, width)) {
|
|
725
|
-
const
|
|
726
|
-
side === "y+"
|
|
727
|
-
? { x: anchorPoint.x, y: anchorPoint.y + offset }
|
|
728
|
-
: side === "y-"
|
|
729
|
-
? { x: anchorPoint.x, y: anchorPoint.y - offset }
|
|
730
|
-
: side === "x-"
|
|
731
|
-
? { x: anchorPoint.x - offset, y: anchorPoint.y }
|
|
732
|
-
: { x: anchorPoint.x + offset, y: anchorPoint.y }
|
|
733
|
-
|
|
734
|
-
const halfAlong = width / 2
|
|
735
|
-
const halfAcross = height / 2
|
|
736
|
-
const bounds: Bounds =
|
|
737
|
-
segment.axis === "x"
|
|
738
|
-
? {
|
|
739
|
-
minX: center.x - halfAlong,
|
|
740
|
-
maxX: center.x + halfAlong,
|
|
741
|
-
minY: center.y - halfAcross,
|
|
742
|
-
maxY: center.y + halfAcross,
|
|
743
|
-
}
|
|
744
|
-
: {
|
|
745
|
-
minX: center.x - halfAcross,
|
|
746
|
-
maxX: center.x + halfAcross,
|
|
747
|
-
minY: center.y - halfAlong,
|
|
748
|
-
maxY: center.y + halfAlong,
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
if (
|
|
752
|
-
this.isObstructed(bounds, {
|
|
753
|
-
ownTrace: trace,
|
|
754
|
-
ownGlobalConnNetId: trace.globalConnNetId,
|
|
755
|
-
ignoredTraceIds,
|
|
756
|
-
})
|
|
757
|
-
)
|
|
758
|
-
continue
|
|
759
|
-
|
|
760
|
-
return {
|
|
761
|
-
globalConnNetId: trace.globalConnNetId,
|
|
762
|
-
netId: connection.netId,
|
|
763
|
-
netLabelText,
|
|
764
|
-
mspPairId: trace.mspPairId,
|
|
765
|
-
pinIds: [...pinIds],
|
|
766
|
-
axis: segment.axis,
|
|
821
|
+
for (const offset of this.getInlineOffsets(
|
|
767
822
|
anchorPoint,
|
|
768
|
-
|
|
823
|
+
segment.axis,
|
|
824
|
+
side,
|
|
769
825
|
width,
|
|
770
826
|
height,
|
|
771
|
-
|
|
827
|
+
this.traces,
|
|
828
|
+
ignoredTraceIds,
|
|
829
|
+
)) {
|
|
830
|
+
const center =
|
|
831
|
+
side === "y+"
|
|
832
|
+
? { x: anchorPoint.x, y: anchorPoint.y + offset }
|
|
833
|
+
: side === "y-"
|
|
834
|
+
? { x: anchorPoint.x, y: anchorPoint.y - offset }
|
|
835
|
+
: side === "x-"
|
|
836
|
+
? { x: anchorPoint.x - offset, y: anchorPoint.y }
|
|
837
|
+
: { x: anchorPoint.x + offset, y: anchorPoint.y }
|
|
838
|
+
|
|
839
|
+
const halfAlong = width / 2
|
|
840
|
+
const halfAcross = height / 2
|
|
841
|
+
const bounds: Bounds =
|
|
842
|
+
segment.axis === "x"
|
|
843
|
+
? {
|
|
844
|
+
minX: center.x - halfAlong,
|
|
845
|
+
maxX: center.x + halfAlong,
|
|
846
|
+
minY: center.y - halfAcross,
|
|
847
|
+
maxY: center.y + halfAcross,
|
|
848
|
+
}
|
|
849
|
+
: {
|
|
850
|
+
minX: center.x - halfAcross,
|
|
851
|
+
maxX: center.x + halfAcross,
|
|
852
|
+
minY: center.y - halfAlong,
|
|
853
|
+
maxY: center.y + halfAlong,
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
if (
|
|
857
|
+
this.isObstructed(bounds, {
|
|
858
|
+
ownTrace: trace,
|
|
859
|
+
ownGlobalConnNetId: trace.globalConnNetId,
|
|
860
|
+
ignoredTraceIds,
|
|
861
|
+
})
|
|
862
|
+
)
|
|
863
|
+
continue
|
|
864
|
+
|
|
865
|
+
return {
|
|
866
|
+
globalConnNetId: trace.globalConnNetId,
|
|
867
|
+
netId: connection.netId,
|
|
868
|
+
netLabelText,
|
|
869
|
+
mspPairId: trace.mspPairId,
|
|
870
|
+
pinIds: [...pinIds],
|
|
871
|
+
axis: segment.axis,
|
|
872
|
+
anchorPoint,
|
|
873
|
+
center,
|
|
874
|
+
width,
|
|
875
|
+
height,
|
|
876
|
+
side,
|
|
877
|
+
}
|
|
772
878
|
}
|
|
773
879
|
}
|
|
774
880
|
}
|
|
@@ -933,10 +1039,16 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
933
1039
|
ownTrace,
|
|
934
1040
|
ownGlobalConnNetId,
|
|
935
1041
|
ignoredTraceIds = new Set<string>(),
|
|
1042
|
+
obstacles,
|
|
936
1043
|
}: {
|
|
937
1044
|
ownTrace?: SolvedTracePath
|
|
938
1045
|
ownGlobalConnNetId: string
|
|
939
1046
|
ignoredTraceIds?: Set<string>
|
|
1047
|
+
obstacles?: {
|
|
1048
|
+
traces: SolvedTracePath[]
|
|
1049
|
+
anchored: NetLabelPlacement[]
|
|
1050
|
+
inline: InlineNetLabelPlacement[]
|
|
1051
|
+
}
|
|
940
1052
|
},
|
|
941
1053
|
): boolean {
|
|
942
1054
|
for (const chip of this.inputProblem.chips) {
|
|
@@ -953,109 +1065,154 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
953
1065
|
if (boundsOverlap(bounds, getTextBoxBounds(textBox))) return true
|
|
954
1066
|
}
|
|
955
1067
|
|
|
956
|
-
for (const trace of this.traces) {
|
|
1068
|
+
for (const trace of obstacles?.traces ?? this.traces) {
|
|
957
1069
|
if (ignoredTraceIds.has(trace.mspPairId)) continue
|
|
958
1070
|
if (ownTrace && trace.mspPairId === ownTrace.mspPairId) continue
|
|
959
1071
|
if (trace.globalConnNetId === ownGlobalConnNetId) continue
|
|
960
1072
|
if (doesPathIntersectBounds(trace.tracePath, bounds)) return true
|
|
961
1073
|
}
|
|
962
1074
|
|
|
1075
|
+
if (
|
|
1076
|
+
obstacles?.anchored.some((label) =>
|
|
1077
|
+
boundsOverlap(bounds, getAnchoredNetLabelRenderedBounds(label)),
|
|
1078
|
+
)
|
|
1079
|
+
)
|
|
1080
|
+
return true
|
|
1081
|
+
if (
|
|
1082
|
+
obstacles?.inline.some(
|
|
1083
|
+
(label) =>
|
|
1084
|
+
boundsOverlap(bounds, getInlinePlacementBounds(label)) ||
|
|
1085
|
+
(label.stubTracePath &&
|
|
1086
|
+
label.globalConnNetId !== ownGlobalConnNetId &&
|
|
1087
|
+
doesPathIntersectBounds(label.stubTracePath, bounds)),
|
|
1088
|
+
)
|
|
1089
|
+
)
|
|
1090
|
+
return true
|
|
963
1091
|
return false
|
|
964
1092
|
}
|
|
965
1093
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
return keys
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
private getOutputTraces(
|
|
981
|
-
supersededNetLabelKeys: Set<string>,
|
|
982
|
-
supersededTraceIds: Set<string>,
|
|
983
|
-
) {
|
|
1094
|
+
private getOutputTraces(placements: InlineNetLabelPlacement[]) {
|
|
1095
|
+
const activeKeys = new Set(placements.map(getInlinePlacementKey))
|
|
1096
|
+
const supersededTraceIds = new Set(
|
|
1097
|
+
this.inlineConversions
|
|
1098
|
+
.filter((conversion) =>
|
|
1099
|
+
conversion.placements.every((placement) =>
|
|
1100
|
+
activeKeys.has(getInlinePlacementKey(placement)),
|
|
1101
|
+
),
|
|
1102
|
+
)
|
|
1103
|
+
.flatMap((conversion) => [...conversion.supersededTraceIds]),
|
|
1104
|
+
)
|
|
984
1105
|
return this.traces.filter(
|
|
985
1106
|
(trace) =>
|
|
986
1107
|
!supersededTraceIds.has(trace.mspPairId) &&
|
|
987
1108
|
!(
|
|
988
|
-
|
|
989
|
-
|
|
1109
|
+
this.netLabelConnectorTraceIds.has(trace.mspPairId) &&
|
|
1110
|
+
isLabelRepresentedInline(trace, placements)
|
|
990
1111
|
),
|
|
991
1112
|
)
|
|
992
1113
|
}
|
|
993
1114
|
|
|
994
|
-
private
|
|
1115
|
+
private getBlockedInlinePlacements({
|
|
995
1116
|
inlineNetLabelPlacements,
|
|
996
1117
|
anchoredNetLabelPlacements,
|
|
1118
|
+
traces,
|
|
997
1119
|
}: {
|
|
998
1120
|
inlineNetLabelPlacements: InlineNetLabelPlacement[]
|
|
999
1121
|
anchoredNetLabelPlacements: NetLabelPlacement[]
|
|
1122
|
+
traces: SolvedTracePath[]
|
|
1000
1123
|
}): Set<string> {
|
|
1001
|
-
const
|
|
1002
|
-
for (const
|
|
1003
|
-
const
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1124
|
+
const blockedKeys = new Set<string>()
|
|
1125
|
+
for (const inline of inlineNetLabelPlacements) {
|
|
1126
|
+
const bounds = getInlinePlacementBounds(inline)
|
|
1127
|
+
const blockedByLabel = anchoredNetLabelPlacements.some((anchored) => {
|
|
1128
|
+
const anchoredBounds = getAnchoredNetLabelRenderedBounds(anchored)
|
|
1129
|
+
return (
|
|
1130
|
+
boundsOverlap(bounds, anchoredBounds) ||
|
|
1131
|
+
(inline.stubTracePath &&
|
|
1132
|
+
doesPathIntersectBounds(inline.stubTracePath, anchoredBounds))
|
|
1133
|
+
)
|
|
1134
|
+
})
|
|
1135
|
+
// Rolling back a routed component restores its original wires. Recheck
|
|
1136
|
+
// other inline conversions against those wires before accepting them.
|
|
1137
|
+
const blockedByTrace = traces.some(
|
|
1138
|
+
(trace) =>
|
|
1139
|
+
trace.globalConnNetId !== inline.globalConnNetId &&
|
|
1140
|
+
(doesPathIntersectBounds(trace.tracePath, bounds) ||
|
|
1141
|
+
(inline.stubTracePath &&
|
|
1142
|
+
terminalStubIntersectsTrace(inline.stubTracePath, trace))),
|
|
1143
|
+
)
|
|
1144
|
+
const blockedByInlineWire = inlineNetLabelPlacements.some(
|
|
1145
|
+
(other) =>
|
|
1146
|
+
other !== inline &&
|
|
1147
|
+
other.globalConnNetId !== inline.globalConnNetId &&
|
|
1148
|
+
other.stubTracePath &&
|
|
1149
|
+
doesPathIntersectBounds(other.stubTracePath, bounds),
|
|
1150
|
+
)
|
|
1151
|
+
const blockedByInlineText = inlineNetLabelPlacements.some(
|
|
1152
|
+
(other) =>
|
|
1153
|
+
other !== inline &&
|
|
1154
|
+
boundsOverlap(bounds, getInlinePlacementBounds(other)),
|
|
1155
|
+
)
|
|
1156
|
+
if (
|
|
1157
|
+
blockedByLabel ||
|
|
1158
|
+
blockedByTrace ||
|
|
1159
|
+
blockedByInlineWire ||
|
|
1160
|
+
blockedByInlineText
|
|
1161
|
+
)
|
|
1162
|
+
blockedKeys.add(getInlinePlacementKey(inline))
|
|
1163
|
+
}
|
|
1164
|
+
for (const conversion of this.inlineConversions) {
|
|
1165
|
+
if (
|
|
1166
|
+
conversion.placements.some((placement) =>
|
|
1167
|
+
blockedKeys.has(getInlinePlacementKey(placement)),
|
|
1168
|
+
)
|
|
1169
|
+
) {
|
|
1170
|
+
for (const placement of conversion.placements)
|
|
1171
|
+
blockedKeys.add(getInlinePlacementKey(placement))
|
|
1023
1172
|
}
|
|
1024
1173
|
}
|
|
1025
|
-
return
|
|
1174
|
+
return blockedKeys
|
|
1026
1175
|
}
|
|
1027
1176
|
|
|
1028
|
-
private buildPostProcessedOutput() {
|
|
1177
|
+
private buildPostProcessedOutput(): InlineOutput {
|
|
1029
1178
|
let activeInlinePlacements = this.inlineNetLabelPlacements
|
|
1030
1179
|
while (true) {
|
|
1031
|
-
const supersededNetLabelKeys = this.getSupersededNetLabelKeys(
|
|
1032
|
-
activeInlinePlacements,
|
|
1033
|
-
)
|
|
1034
|
-
const supersededTraceIds = new Set(
|
|
1035
|
-
[...supersededNetLabelKeys].flatMap((globalConnNetId) => [
|
|
1036
|
-
...(this.supersededTraceIdsByGlobalConnNetId.get(globalConnNetId) ??
|
|
1037
|
-
[]),
|
|
1038
|
-
]),
|
|
1039
|
-
)
|
|
1040
1180
|
const retainedNetLabelPlacements = this.inputNetLabelPlacements.filter(
|
|
1041
|
-
(placement) =>
|
|
1181
|
+
(placement) =>
|
|
1182
|
+
!isLabelRepresentedInline(placement, activeInlinePlacements),
|
|
1042
1183
|
)
|
|
1043
|
-
const outputTraces = this.getOutputTraces(
|
|
1044
|
-
|
|
1045
|
-
|
|
1184
|
+
const outputTraces = this.getOutputTraces(activeInlinePlacements)
|
|
1185
|
+
// Replacing tags frees space on neighboring routed nets. Reconsider
|
|
1186
|
+
// their anchors with the inline text and terminal wires held fixed.
|
|
1187
|
+
const { fixedLabels, terminalTraces } = getInlineLabelObstacles(
|
|
1188
|
+
this.inputProblem,
|
|
1189
|
+
activeInlinePlacements,
|
|
1046
1190
|
)
|
|
1191
|
+
const labelCollisionSolver = new NetLabelNetLabelCollisionSolver({
|
|
1192
|
+
inputProblem: this.inputProblem,
|
|
1193
|
+
traces: [...outputTraces, ...terminalTraces],
|
|
1194
|
+
netLabelPlacements: retainedNetLabelPlacements,
|
|
1195
|
+
fixedNetLabelPlacements: fixedLabels,
|
|
1196
|
+
netLabelConnectorTraceIds: this.netLabelConnectorTraceIds,
|
|
1197
|
+
useRenderedLabelBounds: activeInlinePlacements.length > 0,
|
|
1198
|
+
})
|
|
1199
|
+
if (activeInlinePlacements.length > 0) labelCollisionSolver.solve()
|
|
1047
1200
|
const pushed = pushAnchoredNetLabelsAwayFromInlineLabels({
|
|
1048
1201
|
inputProblem: this.inputProblem,
|
|
1049
1202
|
traces: outputTraces,
|
|
1050
|
-
netLabelPlacements:
|
|
1203
|
+
netLabelPlacements:
|
|
1204
|
+
labelCollisionSolver.solved && !labelCollisionSolver.failed
|
|
1205
|
+
? labelCollisionSolver.getOutput().netLabelPlacements
|
|
1206
|
+
: retainedNetLabelPlacements,
|
|
1051
1207
|
inlineNetLabelPlacements: activeInlinePlacements,
|
|
1208
|
+
netLabelConnectorTraceIds: this.netLabelConnectorTraceIds,
|
|
1052
1209
|
})
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
if (
|
|
1210
|
+
let blockedPlacementKeys = this.getBlockedInlinePlacements({
|
|
1211
|
+
inlineNetLabelPlacements: activeInlinePlacements,
|
|
1212
|
+
anchoredNetLabelPlacements: pushed.netLabelPlacements,
|
|
1213
|
+
traces: pushed.traces,
|
|
1214
|
+
})
|
|
1215
|
+
if (blockedPlacementKeys.size > 0) {
|
|
1059
1216
|
const shiftedInlineTerminalLabels =
|
|
1060
1217
|
pushInlineTerminalLabelsAwayFromAnchoredLabels({
|
|
1061
1218
|
inputProblem: this.inputProblem,
|
|
@@ -1068,8 +1225,60 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
1068
1225
|
shiftedInlineTerminalLabels.inlineNetLabelPlacements
|
|
1069
1226
|
continue
|
|
1070
1227
|
}
|
|
1228
|
+
// Obstacles changed after anchor placement. Reuse the existing side
|
|
1229
|
+
// search, starting with obstructed terminals. Keep every terminal wire
|
|
1230
|
+
// fixed as an obstacle while selecting text positions.
|
|
1231
|
+
const { terminalTraces } = getInlineLabelObstacles(
|
|
1232
|
+
this.inputProblem,
|
|
1233
|
+
activeInlinePlacements,
|
|
1234
|
+
)
|
|
1235
|
+
const replanned = activeInlinePlacements.filter(
|
|
1236
|
+
(label) => !label.stubTracePath,
|
|
1237
|
+
)
|
|
1238
|
+
const terminals = activeInlinePlacements
|
|
1239
|
+
.filter((label) => label.stubTracePath)
|
|
1240
|
+
.sort(
|
|
1241
|
+
(a, b) =>
|
|
1242
|
+
Number(blockedPlacementKeys.has(getInlinePlacementKey(b))) -
|
|
1243
|
+
Number(blockedPlacementKeys.has(getInlinePlacementKey(a))),
|
|
1244
|
+
)
|
|
1245
|
+
for (const placement of terminals) {
|
|
1246
|
+
const connection = [
|
|
1247
|
+
...this.inputProblem.netConnections,
|
|
1248
|
+
...this.inputProblem.directConnections,
|
|
1249
|
+
].find(
|
|
1250
|
+
(connection) =>
|
|
1251
|
+
connection.netId === placement.netId &&
|
|
1252
|
+
connection.allowInlineNetLabel,
|
|
1253
|
+
)
|
|
1254
|
+
const candidate = connection
|
|
1255
|
+
? this.computeTerminalInlinePlacement(
|
|
1256
|
+
connection,
|
|
1257
|
+
placement.pinIds[0]!,
|
|
1258
|
+
placement.globalConnNetId,
|
|
1259
|
+
new Set(),
|
|
1260
|
+
{
|
|
1261
|
+
traces: [...pushed.traces, ...terminalTraces],
|
|
1262
|
+
anchored: pushed.netLabelPlacements,
|
|
1263
|
+
inline: replanned,
|
|
1264
|
+
},
|
|
1265
|
+
)
|
|
1266
|
+
: null
|
|
1267
|
+
replanned.push(candidate ?? placement)
|
|
1268
|
+
}
|
|
1269
|
+
const byKey = new Map(
|
|
1270
|
+
replanned.map((label) => [getInlinePlacementKey(label), label]),
|
|
1271
|
+
)
|
|
1272
|
+
activeInlinePlacements = activeInlinePlacements.map(
|
|
1273
|
+
(label) => byKey.get(getInlinePlacementKey(label)) ?? label,
|
|
1274
|
+
)
|
|
1275
|
+
blockedPlacementKeys = this.getBlockedInlinePlacements({
|
|
1276
|
+
inlineNetLabelPlacements: activeInlinePlacements,
|
|
1277
|
+
anchoredNetLabelPlacements: pushed.netLabelPlacements,
|
|
1278
|
+
traces: pushed.traces,
|
|
1279
|
+
})
|
|
1071
1280
|
}
|
|
1072
|
-
if (
|
|
1281
|
+
if (blockedPlacementKeys.size === 0) {
|
|
1073
1282
|
this.inlineNetLabelPlacements = activeInlinePlacements
|
|
1074
1283
|
this.stats.pushedAnchoredNetLabelCount = pushed.movedLabelCount
|
|
1075
1284
|
const restored = restoreReroutesAroundSupersededLabels({
|
|
@@ -1081,19 +1290,186 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
1081
1290
|
})
|
|
1082
1291
|
this.stats.restoredSupersededLabelRerouteCount =
|
|
1083
1292
|
restored.restoredTraceCount
|
|
1084
|
-
|
|
1293
|
+
const output = {
|
|
1085
1294
|
traces: restored.traces,
|
|
1295
|
+
netLabelConnectorTraceIds: pushed.netLabelConnectorTraceIds,
|
|
1086
1296
|
netLabelPlacements: pushed.netLabelPlacements,
|
|
1087
1297
|
inlineNetLabelPlacements: activeInlinePlacements,
|
|
1088
1298
|
}
|
|
1299
|
+
this.inlineNetLabelPlacements = output.inlineNetLabelPlacements
|
|
1300
|
+
return output
|
|
1089
1301
|
}
|
|
1090
1302
|
activeInlinePlacements = activeInlinePlacements.filter(
|
|
1091
|
-
(placement) =>
|
|
1303
|
+
(placement) =>
|
|
1304
|
+
!blockedPlacementKeys.has(getInlinePlacementKey(placement)),
|
|
1305
|
+
)
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
/** Enumerate nearby moves in the existing order; collect one proposal per step. */
|
|
1310
|
+
private *getTraceRefinementProposals(current: InlineOutput) {
|
|
1311
|
+
yield* getLocalTraceLabelShifts(this.inputProblem, current)
|
|
1312
|
+
// An anchored endpoint can hide the larger footprint of its requested
|
|
1313
|
+
// inline label. Consider that footprint without changing caller opt-ins.
|
|
1314
|
+
for (const label of current.netLabelPlacements) {
|
|
1315
|
+
if (label.pinIds.length !== 1) continue
|
|
1316
|
+
const connection = [
|
|
1317
|
+
...this.inputProblem.netConnections,
|
|
1318
|
+
...this.inputProblem.directConnections,
|
|
1319
|
+
].find(
|
|
1320
|
+
(connection) =>
|
|
1321
|
+
connection.allowInlineNetLabel &&
|
|
1322
|
+
connection.netId === label.netId &&
|
|
1323
|
+
connection.pinIds.includes(label.pinIds[0]!),
|
|
1092
1324
|
)
|
|
1325
|
+
if (!connection) continue
|
|
1326
|
+
const placement = this.computeTerminalInlinePlacement(
|
|
1327
|
+
connection,
|
|
1328
|
+
label.pinIds[0]!,
|
|
1329
|
+
label.globalConnNetId,
|
|
1330
|
+
new Set(),
|
|
1331
|
+
{ traces: [], anchored: [], inline: [] },
|
|
1332
|
+
)
|
|
1333
|
+
if (placement)
|
|
1334
|
+
yield* getLocalTraceLabelShifts(this.inputProblem, current, [placement])
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
private createRefinementCandidate(
|
|
1339
|
+
proposal: InlineOutput,
|
|
1340
|
+
): InlineNetLabelSolver {
|
|
1341
|
+
const traceMap = new Map(
|
|
1342
|
+
proposal.traces.map((trace) => [trace.mspPairId, trace]),
|
|
1343
|
+
)
|
|
1344
|
+
const labelKey = (label: NetLabelPlacement) =>
|
|
1345
|
+
JSON.stringify([label.globalConnNetId, [...label.pinIds].sort()])
|
|
1346
|
+
const labelMap = new Map(
|
|
1347
|
+
proposal.netLabelPlacements.map((label) => [labelKey(label), label]),
|
|
1348
|
+
)
|
|
1349
|
+
const originalTraceIds = new Set(
|
|
1350
|
+
this.traces.map((trace) => trace.mspPairId),
|
|
1351
|
+
)
|
|
1352
|
+
return new InlineNetLabelSolver({
|
|
1353
|
+
inputProblem: this.inputProblem,
|
|
1354
|
+
traces: [
|
|
1355
|
+
...this.traces.map((trace) => traceMap.get(trace.mspPairId) ?? trace),
|
|
1356
|
+
...proposal.traces.filter(
|
|
1357
|
+
(trace) => !originalTraceIds.has(trace.mspPairId),
|
|
1358
|
+
),
|
|
1359
|
+
],
|
|
1360
|
+
netLabelPlacements: this.inputNetLabelPlacements.map(
|
|
1361
|
+
(label) => labelMap.get(labelKey(label)) ?? label,
|
|
1362
|
+
),
|
|
1363
|
+
completedReroutes: this.completedReroutes,
|
|
1364
|
+
netLabelConnectorTraceIds: proposal.netLabelConnectorTraceIds,
|
|
1365
|
+
})
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
private acceptRefinementCandidate(
|
|
1369
|
+
proposal: InlineOutput,
|
|
1370
|
+
result: InlineOutput,
|
|
1371
|
+
): boolean {
|
|
1372
|
+
const current = this.postProcessedOutput!
|
|
1373
|
+
const before = this.refinement!.before
|
|
1374
|
+
const after = getOutputLabelCollisions(result)
|
|
1375
|
+
if (
|
|
1376
|
+
after.some(
|
|
1377
|
+
(collision) =>
|
|
1378
|
+
!before.some((previous) =>
|
|
1379
|
+
sameOutputLabelCollision(collision, previous),
|
|
1380
|
+
),
|
|
1381
|
+
)
|
|
1382
|
+
)
|
|
1383
|
+
return false
|
|
1384
|
+
const movedTraceIds = new Set(
|
|
1385
|
+
proposal.traces
|
|
1386
|
+
.filter((trace, index) => trace !== current.traces[index])
|
|
1387
|
+
.map((trace) => trace.mspPairId),
|
|
1388
|
+
)
|
|
1389
|
+
// Every moved route must clear its collisions, using its exact identity.
|
|
1390
|
+
if (
|
|
1391
|
+
after.some(
|
|
1392
|
+
(collision) =>
|
|
1393
|
+
collision.kind === "trace-label" &&
|
|
1394
|
+
collision.trace.kind === "routed" &&
|
|
1395
|
+
movedTraceIds.has(collision.trace.id),
|
|
1396
|
+
)
|
|
1397
|
+
)
|
|
1398
|
+
return false
|
|
1399
|
+
if (
|
|
1400
|
+
after.length >= before.length &&
|
|
1401
|
+
result.inlineNetLabelPlacements.length <=
|
|
1402
|
+
current.inlineNetLabelPlacements.length
|
|
1403
|
+
)
|
|
1404
|
+
return false
|
|
1405
|
+
this.postProcessedOutput = result
|
|
1406
|
+
this.inlineNetLabelPlacements = result.inlineNetLabelPlacements
|
|
1407
|
+
return true
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
private stepTraceRefinement() {
|
|
1411
|
+
if (this.activeSubSolver) {
|
|
1412
|
+
this.activeSubSolver.step()
|
|
1413
|
+
if (!this.activeSubSolver.solved && !this.activeSubSolver.failed) return
|
|
1414
|
+
// A failed or exhausted solver cannot replace the last accepted output.
|
|
1415
|
+
if (
|
|
1416
|
+
this.activeSubSolver.solved &&
|
|
1417
|
+
!this.activeSubSolver.failed &&
|
|
1418
|
+
this.acceptRefinementCandidate(
|
|
1419
|
+
this.currentProposal!,
|
|
1420
|
+
this.activeSubSolver.getOutput(),
|
|
1421
|
+
)
|
|
1422
|
+
) {
|
|
1423
|
+
this.refinement = undefined
|
|
1424
|
+
}
|
|
1425
|
+
this.activeSubSolver = null
|
|
1426
|
+
this.currentProposal = undefined
|
|
1427
|
+
return
|
|
1428
|
+
}
|
|
1429
|
+
const current = this.postProcessedOutput!
|
|
1430
|
+
if (!this.refinement) {
|
|
1431
|
+
this.refinement = {
|
|
1432
|
+
proposals: this.getTraceRefinementProposals(current),
|
|
1433
|
+
queue: [],
|
|
1434
|
+
collecting: true,
|
|
1435
|
+
before: getOutputLabelCollisions(current),
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
const refinement = this.refinement
|
|
1439
|
+
if (refinement.collecting) {
|
|
1440
|
+
const next = refinement.proposals.next()
|
|
1441
|
+
if (!next.done) {
|
|
1442
|
+
const distance = next.value.traces.reduce(
|
|
1443
|
+
(sum, trace, index) =>
|
|
1444
|
+
sum +
|
|
1445
|
+
trace.tracePath.reduce((distance, point, pointIndex) => {
|
|
1446
|
+
const previous = current.traces[index]!.tracePath[pointIndex]!
|
|
1447
|
+
return (
|
|
1448
|
+
distance +
|
|
1449
|
+
Math.abs(point.x - previous.x) +
|
|
1450
|
+
Math.abs(point.y - previous.y)
|
|
1451
|
+
)
|
|
1452
|
+
}, 0),
|
|
1453
|
+
0,
|
|
1454
|
+
)
|
|
1455
|
+
refinement.queue.push({ output: next.value, distance })
|
|
1456
|
+
} else {
|
|
1457
|
+
// Keep the previous smallest-move-first ordering, including stable ties.
|
|
1458
|
+
refinement.queue.sort((a, b) => a.distance - b.distance)
|
|
1459
|
+
refinement.collecting = false
|
|
1460
|
+
}
|
|
1461
|
+
return
|
|
1462
|
+
}
|
|
1463
|
+
const next = refinement.queue.shift()
|
|
1464
|
+
if (!next) {
|
|
1465
|
+
this.refinementComplete = true
|
|
1466
|
+
return
|
|
1093
1467
|
}
|
|
1468
|
+
this.currentProposal = next.output
|
|
1469
|
+
this.activeSubSolver = this.createRefinementCandidate(next.output)
|
|
1094
1470
|
}
|
|
1095
1471
|
|
|
1096
|
-
getOutput() {
|
|
1472
|
+
getOutput(): InlineOutput {
|
|
1097
1473
|
if (this.postProcessedOutput) return this.postProcessedOutput
|
|
1098
1474
|
return this.buildPostProcessedOutput()
|
|
1099
1475
|
}
|
|
@@ -1335,7 +1711,10 @@ const getCollisionLimitedTerminalStubEnd = ({
|
|
|
1335
1711
|
if (
|
|
1336
1712
|
isCollinear &&
|
|
1337
1713
|
maxAlong >= -GEOMETRY_EPSILON &&
|
|
1338
|
-
minAlong <=
|
|
1714
|
+
minAlong <=
|
|
1715
|
+
intendedLength +
|
|
1716
|
+
INLINE_NET_LABEL_STUB_TRACE_CLEARANCE +
|
|
1717
|
+
GEOMETRY_EPSILON
|
|
1339
1718
|
) {
|
|
1340
1719
|
collisionDistance = Math.max(0, minAlong)
|
|
1341
1720
|
} else {
|
|
@@ -1346,7 +1725,10 @@ const getCollisionLimitedTerminalStubEnd = ({
|
|
|
1346
1725
|
crossesStubAxis &&
|
|
1347
1726
|
Math.abs(startAlong - endAlong) <= GEOMETRY_EPSILON &&
|
|
1348
1727
|
perpendicularAlong >= -GEOMETRY_EPSILON &&
|
|
1349
|
-
perpendicularAlong <=
|
|
1728
|
+
perpendicularAlong <=
|
|
1729
|
+
intendedLength +
|
|
1730
|
+
INLINE_NET_LABEL_STUB_TRACE_CLEARANCE +
|
|
1731
|
+
GEOMETRY_EPSILON
|
|
1350
1732
|
) {
|
|
1351
1733
|
collisionDistance = Math.max(0, perpendicularAlong)
|
|
1352
1734
|
}
|