@tscircuit/schematic-trace-solver 0.0.141 → 0.0.143
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 +28 -1
- package/dist/index.js +348 -34
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +92 -16
- package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +216 -17
- package/lib/solvers/InlineNetLabelSolver/alignPortOnlyInlineNetLabelStubs.ts +233 -0
- package/lib/types/InputProblem.ts +13 -0
- package/package.json +1 -1
- package/tests/assets/inline-net-label-port-stub.json +67 -0
- package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +3 -3
- package/tests/repros/__snapshots__/repro-tida010076-j4-ground-bus.snap.svg +52 -0
- package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +624 -0
- package/tests/repros/assets/repro-tida010076-page02.input.json +2964 -0
- package/tests/repros/repro-tida010076-j4-ground-bus.test.ts +78 -0
- package/tests/repros/repro-tida010076-page02.test.ts +46 -0
- package/tests/repros/repro-usb-power-vbus-label-detour.test.ts +23 -1
- package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label-port-stub.snap.svg +64 -0
- package/tests/solvers/InlineNetLabelSolver/align-port-only-inline-net-label-stubs-conflict.test.ts +75 -0
- package/tests/solvers/InlineNetLabelSolver/inline-net-label-port-stub.test.ts +82 -0
|
@@ -635,13 +635,14 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
635
635
|
labelIndex: number,
|
|
636
636
|
) {
|
|
637
637
|
const direction = dir(orientation)
|
|
638
|
-
const candidatePoints = this.getTraceAnchorCandidatePoints(
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
638
|
+
const candidatePoints = this.getTraceAnchorCandidatePoints(
|
|
639
|
+
label,
|
|
640
|
+
orientation,
|
|
641
|
+
).sort((a, b) => {
|
|
642
|
+
const aAlongDirection = a.x * direction.x + a.y * direction.y
|
|
643
|
+
const bAlongDirection = b.x * direction.x + b.y * direction.y
|
|
644
|
+
return bAlongDirection - aAlongDirection
|
|
645
|
+
})
|
|
645
646
|
|
|
646
647
|
for (const anchorPoint of candidatePoints) {
|
|
647
648
|
const candidate = this.createCandidate(label, anchorPoint, orientation)
|
|
@@ -668,13 +669,14 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
668
669
|
labelIndex: number,
|
|
669
670
|
) {
|
|
670
671
|
const direction = dir(orientation)
|
|
671
|
-
const candidatePoints = this.getTraceAnchorCandidatePoints(
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
672
|
+
const candidatePoints = this.getTraceAnchorCandidatePoints(
|
|
673
|
+
label,
|
|
674
|
+
orientation,
|
|
675
|
+
).sort((a, b) => {
|
|
676
|
+
const aAlongDirection = a.x * direction.x + a.y * direction.y
|
|
677
|
+
const bAlongDirection = b.x * direction.x + b.y * direction.y
|
|
678
|
+
return bAlongDirection - aAlongDirection
|
|
679
|
+
})
|
|
678
680
|
|
|
679
681
|
for (const connectorSource of candidatePoints) {
|
|
680
682
|
const preservedColumnAnchor = this.getSearchStartAnchor(
|
|
@@ -742,11 +744,52 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
742
744
|
return null
|
|
743
745
|
}
|
|
744
746
|
|
|
745
|
-
private getTraceAnchorCandidatePoints(
|
|
747
|
+
private getTraceAnchorCandidatePoints(
|
|
748
|
+
label: NetLabelPlacement,
|
|
749
|
+
orientation: FacingDirection,
|
|
750
|
+
) {
|
|
746
751
|
const seen = new Set<string>()
|
|
747
752
|
const points: Point[] = []
|
|
753
|
+
const connectedTraceIds = new Set(label.mspConnectionPairIds ?? [])
|
|
754
|
+
|
|
755
|
+
if (isYOrientation(orientation)) {
|
|
756
|
+
const connectedPinIds = new Set<string>()
|
|
757
|
+
for (const traceId of connectedTraceIds) {
|
|
758
|
+
for (const pinId of this.traceMap[traceId]?.pinIds ?? []) {
|
|
759
|
+
connectedPinIds.add(pinId)
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Multi-pin rails are routed as a chain of pairwise traces. Follow the
|
|
764
|
+
// pairs only when they share both a pin and an aligned vertical rail,
|
|
765
|
+
// which identifies one continuous rail. A shared pin alone can join
|
|
766
|
+
// unrelated branches whose rail offsets are far apart; walking those
|
|
767
|
+
// branches would extend the label connector across the schematic.
|
|
768
|
+
let foundConnectedTrace = true
|
|
769
|
+
while (foundConnectedTrace) {
|
|
770
|
+
foundConnectedTrace = false
|
|
771
|
+
for (const trace of Object.values(this.traceMap)) {
|
|
772
|
+
if (connectedTraceIds.has(trace.mspPairId)) continue
|
|
773
|
+
if (trace.mspPairId.startsWith("available-net-orientation-")) continue
|
|
774
|
+
if (trace.globalConnNetId !== label.globalConnNetId) continue
|
|
775
|
+
if (!trace.pinIds.some((pinId) => connectedPinIds.has(pinId)))
|
|
776
|
+
continue
|
|
777
|
+
const connectedTraces = [...connectedTraceIds].flatMap((traceId) => {
|
|
778
|
+
const connectedTrace = this.traceMap[traceId]
|
|
779
|
+
return connectedTrace ? [connectedTrace] : []
|
|
780
|
+
})
|
|
781
|
+
if (!this.sharesVerticalRailWithAny(trace, connectedTraces)) {
|
|
782
|
+
continue
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
connectedTraceIds.add(trace.mspPairId)
|
|
786
|
+
for (const pinId of trace.pinIds) connectedPinIds.add(pinId)
|
|
787
|
+
foundConnectedTrace = true
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
748
791
|
|
|
749
|
-
for (const traceId of
|
|
792
|
+
for (const traceId of connectedTraceIds) {
|
|
750
793
|
const trace = this.traceMap[traceId]
|
|
751
794
|
if (!trace) continue
|
|
752
795
|
for (const point of trace.tracePath) {
|
|
@@ -760,6 +803,39 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
760
803
|
return points
|
|
761
804
|
}
|
|
762
805
|
|
|
806
|
+
private sharesVerticalRailWithAny(
|
|
807
|
+
trace: SolvedTracePath,
|
|
808
|
+
otherTraces: SolvedTracePath[],
|
|
809
|
+
) {
|
|
810
|
+
const verticalRailXs = new Set<number>()
|
|
811
|
+
for (const otherTrace of otherTraces) {
|
|
812
|
+
for (let i = 0; i < otherTrace.tracePath.length - 1; i++) {
|
|
813
|
+
const start = otherTrace.tracePath[i]!
|
|
814
|
+
const end = otherTrace.tracePath[i + 1]!
|
|
815
|
+
if (
|
|
816
|
+
Math.abs(start.x - end.x) <= EPS &&
|
|
817
|
+
Math.abs(start.y - end.y) > EPS
|
|
818
|
+
) {
|
|
819
|
+
verticalRailXs.add(start.x)
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
for (let i = 0; i < trace.tracePath.length - 1; i++) {
|
|
825
|
+
const start = trace.tracePath[i]!
|
|
826
|
+
const end = trace.tracePath[i + 1]!
|
|
827
|
+
if (
|
|
828
|
+
Math.abs(start.x - end.x) <= EPS &&
|
|
829
|
+
Math.abs(start.y - end.y) > EPS &&
|
|
830
|
+
[...verticalRailXs].some((x) => Math.abs(x - start.x) <= EPS)
|
|
831
|
+
) {
|
|
832
|
+
return true
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
return false
|
|
837
|
+
}
|
|
838
|
+
|
|
763
839
|
private getAvailableOrientations(label: NetLabelPlacement) {
|
|
764
840
|
const effectiveNetId = label.netId ?? label.globalConnNetId
|
|
765
841
|
return this.inputProblem.availableNetLabelOrientations[effectiveNetId] ?? []
|
|
@@ -3,9 +3,11 @@ import type { GraphicsObject, Rect } from "graphics-debug"
|
|
|
3
3
|
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
4
4
|
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
5
5
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
6
|
+
import { getPinDirection } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection"
|
|
6
7
|
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
7
8
|
import type {
|
|
8
9
|
InputDirectConnection,
|
|
10
|
+
InputNetConnection,
|
|
9
11
|
InputProblem,
|
|
10
12
|
PinId,
|
|
11
13
|
} from "lib/types/InputProblem"
|
|
@@ -15,6 +17,7 @@ import {
|
|
|
15
17
|
type AxisAlignedSegment,
|
|
16
18
|
getAxisAlignedSegments,
|
|
17
19
|
} from "./getAxisAlignedSegments"
|
|
20
|
+
import { alignPortOnlyInlineNetLabelStubs } from "./alignPortOnlyInlineNetLabelStubs"
|
|
18
21
|
|
|
19
22
|
export const DEFAULT_INLINE_NET_LABEL_HEIGHT = 0.18
|
|
20
23
|
|
|
@@ -45,9 +48,15 @@ export const INLINE_NET_LABEL_MAX_SPAN_JOG = 0.4
|
|
|
45
48
|
export interface InlineNetLabelPlacement {
|
|
46
49
|
globalConnNetId: string
|
|
47
50
|
netId?: string
|
|
48
|
-
mspPairId
|
|
51
|
+
mspPairId?: string
|
|
49
52
|
pinIds: PinId[]
|
|
50
53
|
|
|
54
|
+
/**
|
|
55
|
+
* A generated single-ended trace stub. Present only for an eligible
|
|
56
|
+
* single-pin net connection; routed point-to-point traces omit it.
|
|
57
|
+
*/
|
|
58
|
+
stubTracePath?: [Point, Point]
|
|
59
|
+
|
|
51
60
|
/** Axis the text runs along: "x" reads left-to-right, "y" reads bottom-to-top */
|
|
52
61
|
axis: "x" | "y"
|
|
53
62
|
|
|
@@ -96,7 +105,11 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
96
105
|
/** Direct connections that opted in, still waiting to be processed */
|
|
97
106
|
queuedDirectConnections: InputDirectConnection[]
|
|
98
107
|
|
|
108
|
+
/** Single-pin net connections that opted in, still waiting to be processed */
|
|
109
|
+
queuedPortOnlyNetConnections: InputNetConnection[]
|
|
110
|
+
|
|
99
111
|
private tracesByPinPairKey: Map<string, SolvedTracePath[]>
|
|
112
|
+
private hasAlignedPortOnlyStubs = false
|
|
100
113
|
|
|
101
114
|
constructor(input: InlineNetLabelSolverInput) {
|
|
102
115
|
super()
|
|
@@ -107,6 +120,9 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
107
120
|
this.queuedDirectConnections = this.inputProblem.directConnections.filter(
|
|
108
121
|
(dc) => dc.allowInlineNetLabel && dc.netId,
|
|
109
122
|
)
|
|
123
|
+
this.queuedPortOnlyNetConnections = this.inputProblem.netConnections.filter(
|
|
124
|
+
(nc) => nc.allowInlineNetLabel && nc.pinIds.length === 1 && nc.netId,
|
|
125
|
+
)
|
|
110
126
|
|
|
111
127
|
this.tracesByPinPairKey = new Map()
|
|
112
128
|
for (const trace of this.traces) {
|
|
@@ -132,15 +148,140 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
132
148
|
|
|
133
149
|
override _step() {
|
|
134
150
|
const directConnection = this.queuedDirectConnections.shift()
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
151
|
+
if (directConnection) {
|
|
152
|
+
const placement = this.computeInlinePlacement(directConnection)
|
|
153
|
+
if (placement) {
|
|
154
|
+
this.inlineNetLabelPlacements.push(placement)
|
|
155
|
+
}
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const portOnlyNetConnection = this.queuedPortOnlyNetConnections.shift()
|
|
160
|
+
if (portOnlyNetConnection) {
|
|
161
|
+
const placement = this.computePortOnlyInlinePlacement(
|
|
162
|
+
portOnlyNetConnection,
|
|
163
|
+
)
|
|
164
|
+
if (placement) {
|
|
165
|
+
this.inlineNetLabelPlacements.push(placement)
|
|
166
|
+
}
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!this.hasAlignedPortOnlyStubs) {
|
|
171
|
+
this.inlineNetLabelPlacements = alignPortOnlyInlineNetLabelStubs({
|
|
172
|
+
placements: this.inlineNetLabelPlacements,
|
|
173
|
+
inputProblem: this.inputProblem,
|
|
174
|
+
traces: this.traces,
|
|
175
|
+
netLabelPlacements: this.inputNetLabelPlacements,
|
|
176
|
+
})
|
|
177
|
+
this.hasAlignedPortOnlyStubs = true
|
|
138
178
|
return
|
|
139
179
|
}
|
|
140
180
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
181
|
+
this.solved = true
|
|
182
|
+
this.stats.inlineNetLabelCount = this.inlineNetLabelPlacements.length
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Converts a conventional port-only anchored placement into an inline label
|
|
187
|
+
* on a generated outward stub. The stub follows the pin's true facing
|
|
188
|
+
* direction; an anchored label may finish in another direction after an
|
|
189
|
+
* elbow, which is not the direction a terminal stub should leave the pin.
|
|
190
|
+
*/
|
|
191
|
+
private computePortOnlyInlinePlacement(
|
|
192
|
+
netConnection: InputNetConnection,
|
|
193
|
+
): InlineNetLabelPlacement | null {
|
|
194
|
+
const [pinId] = netConnection.pinIds
|
|
195
|
+
if (!pinId) return null
|
|
196
|
+
|
|
197
|
+
const anchoredPlacement = this.inputNetLabelPlacements.find(
|
|
198
|
+
(placement) =>
|
|
199
|
+
placement.netId === netConnection.netId &&
|
|
200
|
+
placement.pinIds.length === 1 &&
|
|
201
|
+
placement.pinIds[0] === pinId,
|
|
202
|
+
)
|
|
203
|
+
if (!anchoredPlacement) return null
|
|
204
|
+
|
|
205
|
+
const inputChip = this.inputProblem.chips.find((chip) =>
|
|
206
|
+
chip.pins.some((pin) => pin.pinId === pinId),
|
|
207
|
+
)
|
|
208
|
+
const inputPin = inputChip?.pins.find((pin) => pin.pinId === pinId)
|
|
209
|
+
|
|
210
|
+
const height =
|
|
211
|
+
netConnection.inlineNetLabelHeight ?? DEFAULT_INLINE_NET_LABEL_HEIGHT
|
|
212
|
+
const width =
|
|
213
|
+
netConnection.inlineNetLabelWidth ??
|
|
214
|
+
netConnection.netLabelWidth ??
|
|
215
|
+
estimateInlineNetLabelWidth(netConnection.netId, height)
|
|
216
|
+
|
|
217
|
+
// Leave a small wire tail at both ends of the text so it unmistakably
|
|
218
|
+
// reads as a label on a trace rather than free-standing text.
|
|
219
|
+
const stubLength = Math.max(width + 0.2, 0.6)
|
|
220
|
+
const start = inputPin
|
|
221
|
+
? { x: inputPin.x, y: inputPin.y }
|
|
222
|
+
: anchoredPlacement.anchorPoint
|
|
223
|
+
const direction =
|
|
224
|
+
inputPin && inputChip
|
|
225
|
+
? (inputPin._facingDirection ?? getPinDirection(inputPin, inputChip))
|
|
226
|
+
: anchoredPlacement.orientation
|
|
227
|
+
const end: Point =
|
|
228
|
+
direction === "x+"
|
|
229
|
+
? { x: start.x + stubLength, y: start.y }
|
|
230
|
+
: direction === "x-"
|
|
231
|
+
? { x: start.x - stubLength, y: start.y }
|
|
232
|
+
: direction === "y+"
|
|
233
|
+
? { x: start.x, y: start.y + stubLength }
|
|
234
|
+
: { x: start.x, y: start.y - stubLength }
|
|
235
|
+
|
|
236
|
+
const axis: InlineNetLabelPlacement["axis"] =
|
|
237
|
+
direction === "x+" || direction === "x-" ? "x" : "y"
|
|
238
|
+
const side: InlineNetLabelPlacement["side"] = axis === "x" ? "y+" : "x-"
|
|
239
|
+
const anchorPoint = {
|
|
240
|
+
x: (start.x + end.x) / 2,
|
|
241
|
+
y: (start.y + end.y) / 2,
|
|
242
|
+
}
|
|
243
|
+
const offset = height / 2 + INLINE_NET_LABEL_TRACE_MARGIN
|
|
244
|
+
const center: Point =
|
|
245
|
+
side === "y+"
|
|
246
|
+
? { x: anchorPoint.x, y: anchorPoint.y + offset }
|
|
247
|
+
: { x: anchorPoint.x - offset, y: anchorPoint.y }
|
|
248
|
+
|
|
249
|
+
const halfAlong = width / 2
|
|
250
|
+
const halfAcross = height / 2
|
|
251
|
+
const bounds: Bounds =
|
|
252
|
+
axis === "x"
|
|
253
|
+
? {
|
|
254
|
+
minX: center.x - halfAlong,
|
|
255
|
+
maxX: center.x + halfAlong,
|
|
256
|
+
minY: center.y - halfAcross,
|
|
257
|
+
maxY: center.y + halfAcross,
|
|
258
|
+
}
|
|
259
|
+
: {
|
|
260
|
+
minX: center.x - halfAcross,
|
|
261
|
+
maxX: center.x + halfAcross,
|
|
262
|
+
minY: center.y - halfAlong,
|
|
263
|
+
maxY: center.y + halfAlong,
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (
|
|
267
|
+
this.isObstructed(bounds, {
|
|
268
|
+
ownGlobalConnNetId: anchoredPlacement.globalConnNetId,
|
|
269
|
+
})
|
|
270
|
+
) {
|
|
271
|
+
return null
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
globalConnNetId: anchoredPlacement.globalConnNetId,
|
|
276
|
+
netId: netConnection.netId,
|
|
277
|
+
pinIds: [pinId],
|
|
278
|
+
stubTracePath: [start, end],
|
|
279
|
+
axis,
|
|
280
|
+
anchorPoint,
|
|
281
|
+
center,
|
|
282
|
+
width,
|
|
283
|
+
height,
|
|
284
|
+
side,
|
|
144
285
|
}
|
|
145
286
|
}
|
|
146
287
|
|
|
@@ -209,7 +350,13 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
209
350
|
maxY: center.y + halfAlong,
|
|
210
351
|
}
|
|
211
352
|
|
|
212
|
-
if (
|
|
353
|
+
if (
|
|
354
|
+
this.isObstructed(bounds, {
|
|
355
|
+
ownTrace: trace,
|
|
356
|
+
ownGlobalConnNetId: trace.globalConnNetId,
|
|
357
|
+
})
|
|
358
|
+
)
|
|
359
|
+
continue
|
|
213
360
|
|
|
214
361
|
return {
|
|
215
362
|
globalConnNetId: trace.globalConnNetId,
|
|
@@ -334,7 +481,13 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
334
481
|
maxY: center.y + halfAlong,
|
|
335
482
|
}
|
|
336
483
|
|
|
337
|
-
if (
|
|
484
|
+
if (
|
|
485
|
+
this.isObstructed(bounds, {
|
|
486
|
+
ownTrace: trace,
|
|
487
|
+
ownGlobalConnNetId: trace.globalConnNetId,
|
|
488
|
+
})
|
|
489
|
+
)
|
|
490
|
+
continue
|
|
338
491
|
|
|
339
492
|
const anchorPoint: Point =
|
|
340
493
|
axis === "x"
|
|
@@ -363,7 +516,16 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
363
516
|
* An inline label may not sit on top of a chip, a component's text, or a
|
|
364
517
|
* trace belonging to another net.
|
|
365
518
|
*/
|
|
366
|
-
private isObstructed(
|
|
519
|
+
private isObstructed(
|
|
520
|
+
bounds: Bounds,
|
|
521
|
+
{
|
|
522
|
+
ownTrace,
|
|
523
|
+
ownGlobalConnNetId,
|
|
524
|
+
}: {
|
|
525
|
+
ownTrace?: SolvedTracePath
|
|
526
|
+
ownGlobalConnNetId: string
|
|
527
|
+
},
|
|
528
|
+
): boolean {
|
|
367
529
|
for (const chip of this.inputProblem.chips) {
|
|
368
530
|
const chipBounds: Bounds = {
|
|
369
531
|
minX: chip.center.x - chip.width / 2,
|
|
@@ -379,8 +541,8 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
379
541
|
}
|
|
380
542
|
|
|
381
543
|
for (const trace of this.traces) {
|
|
382
|
-
if (trace.mspPairId === ownTrace.mspPairId) continue
|
|
383
|
-
if (trace.globalConnNetId ===
|
|
544
|
+
if (ownTrace && trace.mspPairId === ownTrace.mspPairId) continue
|
|
545
|
+
if (trace.globalConnNetId === ownGlobalConnNetId) continue
|
|
384
546
|
if (doesPathIntersectBounds(trace.tracePath, bounds)) return true
|
|
385
547
|
}
|
|
386
548
|
|
|
@@ -399,10 +561,23 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
399
561
|
return keys
|
|
400
562
|
}
|
|
401
563
|
|
|
564
|
+
private getOutputTraces(supersededNetLabelKeys: Set<string>) {
|
|
565
|
+
return this.traces.filter(
|
|
566
|
+
(trace) =>
|
|
567
|
+
!(
|
|
568
|
+
supersededNetLabelKeys.has(trace.globalConnNetId) &&
|
|
569
|
+
trace.mspPairId.startsWith("available-net-orientation-")
|
|
570
|
+
),
|
|
571
|
+
)
|
|
572
|
+
}
|
|
573
|
+
|
|
402
574
|
getOutput() {
|
|
403
575
|
const superseded = this.getSupersededNetLabelKeys()
|
|
404
576
|
return {
|
|
405
|
-
|
|
577
|
+
// AvailableNetOrientationSolver may have routed an elbow from a port to
|
|
578
|
+
// the anchored label that this inline placement supersedes. Keep the
|
|
579
|
+
// actual net trace, but discard that now-orphaned label connector.
|
|
580
|
+
traces: this.getOutputTraces(superseded),
|
|
406
581
|
netLabelPlacements: this.inputNetLabelPlacements.filter(
|
|
407
582
|
(placement) => !superseded.has(placement.globalConnNetId),
|
|
408
583
|
),
|
|
@@ -420,7 +595,8 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
420
595
|
graphics.points ??= []
|
|
421
596
|
graphics.texts ??= []
|
|
422
597
|
|
|
423
|
-
|
|
598
|
+
const output = this.getOutput()
|
|
599
|
+
for (const trace of output.traces) {
|
|
424
600
|
graphics.lines.push({
|
|
425
601
|
points: trace.tracePath,
|
|
426
602
|
strokeColor: "purple",
|
|
@@ -446,6 +622,12 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
446
622
|
}
|
|
447
623
|
|
|
448
624
|
for (const inlineLabel of this.inlineNetLabelPlacements) {
|
|
625
|
+
if (inlineLabel.stubTracePath) {
|
|
626
|
+
graphics.lines.push({
|
|
627
|
+
points: inlineLabel.stubTracePath,
|
|
628
|
+
strokeColor: "purple",
|
|
629
|
+
})
|
|
630
|
+
}
|
|
449
631
|
const isHorizontal = inlineLabel.axis === "x"
|
|
450
632
|
graphics.rects.push({
|
|
451
633
|
center: inlineLabel.center,
|
|
@@ -460,12 +642,29 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
460
642
|
].join("\n"),
|
|
461
643
|
} as Rect & { strokeColor: string })
|
|
462
644
|
graphics.texts.push({
|
|
463
|
-
x:
|
|
464
|
-
|
|
645
|
+
x:
|
|
646
|
+
inlineLabel.stubTracePath && inlineLabel.axis === "x"
|
|
647
|
+
? inlineLabel.center.x +
|
|
648
|
+
(inlineLabel.stubTracePath[1].x > inlineLabel.stubTracePath[0].x
|
|
649
|
+
? -inlineLabel.width / 2
|
|
650
|
+
: inlineLabel.width / 2)
|
|
651
|
+
: inlineLabel.center.x,
|
|
652
|
+
y:
|
|
653
|
+
inlineLabel.stubTracePath && inlineLabel.axis === "y"
|
|
654
|
+
? inlineLabel.center.y +
|
|
655
|
+
(inlineLabel.stubTracePath[1].y > inlineLabel.stubTracePath[0].y
|
|
656
|
+
? -inlineLabel.width / 2
|
|
657
|
+
: inlineLabel.width / 2)
|
|
658
|
+
: inlineLabel.center.y,
|
|
465
659
|
text: inlineLabel.netId ?? "",
|
|
466
660
|
color: "green",
|
|
467
661
|
fontSize: inlineLabel.height,
|
|
468
|
-
anchorSide:
|
|
662
|
+
anchorSide: inlineLabel.stubTracePath
|
|
663
|
+
? inlineLabel.stubTracePath[1][inlineLabel.axis] >
|
|
664
|
+
inlineLabel.stubTracePath[0][inlineLabel.axis]
|
|
665
|
+
? "center_left"
|
|
666
|
+
: "center_right"
|
|
667
|
+
: "center",
|
|
469
668
|
// Vertical labels read bottom-to-top, alongside the wire they name.
|
|
470
669
|
rotation: inlineLabel.axis === "y" ? 90 : undefined,
|
|
471
670
|
})
|