@tscircuit/schematic-trace-solver 0.0.150 → 0.0.152

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.
@@ -1,5 +1,5 @@
1
1
  import type { Bounds, Point } from "@tscircuit/math-utils"
2
- import type { GraphicsObject, Rect } from "graphics-debug"
2
+ import type { GraphicsObject, Rect, Text } 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"
@@ -92,6 +92,13 @@ interface InlineNetLabelSolverInput {
92
92
  netLabelPlacements: NetLabelPlacement[]
93
93
  }
94
94
 
95
+ export interface InlineNetLabelOutput {
96
+ inputProblem: InputProblem
97
+ traces: SolvedTracePath[]
98
+ netLabelPlacements: NetLabelPlacement[]
99
+ inlineNetLabelPlacements: InlineNetLabelPlacement[]
100
+ }
101
+
95
102
  type InlineEligibleConnection = InputDirectConnection | InputNetConnection
96
103
 
97
104
  const getPinPairKey = (pinIds: readonly string[]) =>
@@ -646,95 +653,117 @@ export class InlineNetLabelSolver extends BaseSolver {
646
653
  // Mirrors the previous pipeline stage's visualization so that a problem
647
654
  // with no inline labels renders identically, then layers the inline labels
648
655
  // on top.
649
- const graphics = visualizeInputProblem(this.inputProblem)
650
- graphics.lines ??= []
651
- graphics.rects ??= []
652
- graphics.points ??= []
653
- graphics.texts ??= []
654
-
655
- const output = this.getOutput()
656
- for (const trace of output.traces) {
656
+ return visualizeInlineNetLabelOutput({
657
+ inputProblem: this.inputProblem,
658
+ ...this.getOutput(),
659
+ })
660
+ }
661
+ }
662
+
663
+ export const visualizeInlineNetLabelOutput = ({
664
+ inputProblem,
665
+ traces,
666
+ netLabelPlacements,
667
+ inlineNetLabelPlacements,
668
+ }: InlineNetLabelOutput): GraphicsObject => {
669
+ const graphics = visualizeInputProblem(inputProblem)
670
+ graphics.lines ??= []
671
+ graphics.rects ??= []
672
+ graphics.points ??= []
673
+ graphics.texts ??= []
674
+
675
+ for (const trace of traces) {
676
+ graphics.lines.push({
677
+ points: trace.tracePath,
678
+ strokeColor: "purple",
679
+ })
680
+ }
681
+
682
+ for (const label of netLabelPlacements) {
683
+ graphics.rects.push({
684
+ center: label.center,
685
+ width: label.width,
686
+ height: label.height,
687
+ fill: getColorFromString(label.globalConnNetId, 0.35),
688
+ strokeColor: getColorFromString(label.globalConnNetId, 0.9),
689
+ label: `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`,
690
+ } as Rect & { strokeColor: string })
691
+ graphics.points.push({
692
+ x: label.anchorPoint.x,
693
+ y: label.anchorPoint.y,
694
+ color: getColorFromString(label.globalConnNetId, 0.9),
695
+ label: `anchorPoint\norientation: ${label.orientation}`,
696
+ })
697
+ }
698
+
699
+ for (const inlineLabel of inlineNetLabelPlacements) {
700
+ if (inlineLabel.stubTracePath) {
657
701
  graphics.lines.push({
658
- points: trace.tracePath,
702
+ points: inlineLabel.stubTracePath,
659
703
  strokeColor: "purple",
660
704
  })
661
705
  }
662
-
663
- const { netLabelPlacements } = this.getOutput()
664
- for (const label of netLabelPlacements) {
665
- graphics.rects.push({
666
- center: label.center,
667
- width: label.width,
668
- height: label.height,
669
- fill: getColorFromString(label.globalConnNetId, 0.35),
670
- strokeColor: getColorFromString(label.globalConnNetId, 0.9),
671
- label: `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`,
672
- } as Rect & { strokeColor: string })
673
- graphics.points.push({
674
- x: label.anchorPoint.x,
675
- y: label.anchorPoint.y,
676
- color: getColorFromString(label.globalConnNetId, 0.9),
677
- label: `anchorPoint\norientation: ${label.orientation}`,
678
- })
706
+ const isHorizontal = inlineLabel.axis === "x"
707
+ let renderedWidth = inlineLabel.width
708
+ let renderedHeight = inlineLabel.height
709
+ if (!isHorizontal) {
710
+ renderedWidth = inlineLabel.height
711
+ renderedHeight = inlineLabel.width
679
712
  }
680
-
681
- for (const inlineLabel of this.inlineNetLabelPlacements) {
682
- if (inlineLabel.stubTracePath) {
683
- graphics.lines.push({
684
- points: inlineLabel.stubTracePath,
685
- strokeColor: "purple",
686
- })
713
+ graphics.rects.push({
714
+ center: inlineLabel.center,
715
+ width: renderedWidth,
716
+ height: renderedHeight,
717
+ fill: getColorFromString(inlineLabel.globalConnNetId, 0.35),
718
+ strokeColor: "green",
719
+ label: [
720
+ `INLINE netId: ${inlineLabel.netId}`,
721
+ `axis: ${inlineLabel.axis}`,
722
+ `side: ${inlineLabel.side}`,
723
+ ].join("\n"),
724
+ } as Rect & { strokeColor: string })
725
+
726
+ let textX = inlineLabel.center.x
727
+ let textY = inlineLabel.center.y
728
+ let anchorSide: Text["anchorSide"] = "center"
729
+ let rotation: Text["rotation"]
730
+ if (inlineLabel.stubTracePath) {
731
+ const stubStart = inlineLabel.stubTracePath[0]
732
+ const stubEnd = inlineLabel.stubTracePath[1]
733
+ let labelOffset = inlineLabel.width / 2
734
+ if (stubEnd[inlineLabel.axis] > stubStart[inlineLabel.axis]) {
735
+ labelOffset = -inlineLabel.width / 2
736
+ anchorSide = "center_left"
737
+ } else {
738
+ anchorSide = "center_right"
739
+ }
740
+ if (inlineLabel.axis === "x") {
741
+ textX += labelOffset
742
+ } else {
743
+ textY += labelOffset
687
744
  }
688
- const isHorizontal = inlineLabel.axis === "x"
689
- graphics.rects.push({
690
- center: inlineLabel.center,
691
- width: isHorizontal ? inlineLabel.width : inlineLabel.height,
692
- height: isHorizontal ? inlineLabel.height : inlineLabel.width,
693
- fill: getColorFromString(inlineLabel.globalConnNetId, 0.35),
694
- strokeColor: "green",
695
- label: [
696
- `INLINE netId: ${inlineLabel.netId}`,
697
- `axis: ${inlineLabel.axis}`,
698
- `side: ${inlineLabel.side}`,
699
- ].join("\n"),
700
- } as Rect & { strokeColor: string })
701
- graphics.texts.push({
702
- x:
703
- inlineLabel.stubTracePath && inlineLabel.axis === "x"
704
- ? inlineLabel.center.x +
705
- (inlineLabel.stubTracePath[1].x > inlineLabel.stubTracePath[0].x
706
- ? -inlineLabel.width / 2
707
- : inlineLabel.width / 2)
708
- : inlineLabel.center.x,
709
- y:
710
- inlineLabel.stubTracePath && inlineLabel.axis === "y"
711
- ? inlineLabel.center.y +
712
- (inlineLabel.stubTracePath[1].y > inlineLabel.stubTracePath[0].y
713
- ? -inlineLabel.width / 2
714
- : inlineLabel.width / 2)
715
- : inlineLabel.center.y,
716
- text: inlineLabel.netLabelText ?? inlineLabel.netId ?? "",
717
- color: "green",
718
- fontSize: inlineLabel.height,
719
- anchorSide: inlineLabel.stubTracePath
720
- ? inlineLabel.stubTracePath[1][inlineLabel.axis] >
721
- inlineLabel.stubTracePath[0][inlineLabel.axis]
722
- ? "center_left"
723
- : "center_right"
724
- : "center",
725
- // Vertical labels read bottom-to-top, alongside the wire they name.
726
- rotation: inlineLabel.axis === "y" ? 90 : undefined,
727
- })
728
- graphics.points.push({
729
- x: inlineLabel.anchorPoint.x,
730
- y: inlineLabel.anchorPoint.y,
731
- color: "green",
732
- label: `inline anchor\n${inlineLabel.netLabelText ?? inlineLabel.netId}`,
733
- })
734
745
  }
735
-
736
- return graphics
746
+ if (inlineLabel.axis === "y") rotation = 90
747
+
748
+ graphics.texts.push({
749
+ x: textX,
750
+ y: textY,
751
+ text: inlineLabel.netLabelText ?? inlineLabel.netId ?? "",
752
+ color: "green",
753
+ fontSize: inlineLabel.height,
754
+ anchorSide,
755
+ // Vertical labels read bottom-to-top, alongside the wire they name.
756
+ rotation,
757
+ })
758
+ graphics.points.push({
759
+ x: inlineLabel.anchorPoint.x,
760
+ y: inlineLabel.anchorPoint.y,
761
+ color: "green",
762
+ label: `inline anchor\n${inlineLabel.netLabelText ?? inlineLabel.netId}`,
763
+ })
737
764
  }
765
+
766
+ return graphics
738
767
  }
739
768
 
740
769
  /**
@@ -0,0 +1,338 @@
1
+ import {
2
+ doesSegmentIntersectRect,
3
+ getBoundFromCenteredRect,
4
+ type Point,
5
+ } from "@tscircuit/math-utils"
6
+ import type { GraphicsObject } from "graphics-debug"
7
+ import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
8
+ import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem"
9
+ import { doesPairCrossRestrictedCenterLines } from "lib/solvers/MspConnectionPairSolver/doesPairCrossRestrictedCenterLines"
10
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
11
+ import {
12
+ getTraceRecoveryConnectivityMaps,
13
+ type TraceRecoveryPin,
14
+ } from "lib/solvers/NetLabelTraceRecovery/getTraceRecoveryConnectivityMaps"
15
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
16
+ import { SchematicTraceSingleLineSolver2 } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2"
17
+ import type {
18
+ ChipId,
19
+ InputChip,
20
+ InputProblem,
21
+ PinId,
22
+ } from "lib/types/InputProblem"
23
+ import { arePinsInDifferentSchematicSections } from "lib/utils/arePinsInDifferentSchematicSections"
24
+ import { doesTraceOverlapWithExistingTraces } from "lib/utils/does-trace-overlap-with-existing-traces"
25
+ import {
26
+ type InlineNetLabelPlacement,
27
+ type InlineNetLabelOutput,
28
+ visualizeInlineNetLabelOutput,
29
+ } from "../InlineNetLabelSolver/InlineNetLabelSolver"
30
+
31
+ type GlobalConnNetId = NetLabelPlacement["globalConnNetId"]
32
+
33
+ interface CandidatePair {
34
+ firstLabel: NetLabelPlacement
35
+ secondLabel: NetLabelPlacement
36
+ pins: [TraceRecoveryPin, TraceRecoveryPin]
37
+ perpendicularOffset: number
38
+ routeDistance: number
39
+ key: string
40
+ }
41
+
42
+ const AVAILABLE_NET_ORIENTATION_PREFIX = "available-net-orientation-"
43
+ const RECOVERED_TRACE_PREFIX = "net-label-to-trace-"
44
+
45
+ const getCanonicalPairKey = (firstPinId: PinId, secondPinId: PinId) =>
46
+ [firstPinId, secondPinId].sort().join("--")
47
+
48
+ export const pathIntersectsRenderedLabel = (
49
+ path: Point[],
50
+ label: NetLabelPlacement | InlineNetLabelPlacement,
51
+ ) => {
52
+ let width = label.width
53
+ let height = label.height
54
+ if ("axis" in label && label.axis === "y") {
55
+ width = label.height
56
+ height = label.width
57
+ }
58
+ const bounds = getBoundFromCenteredRect({
59
+ center: label.center,
60
+ width,
61
+ height,
62
+ })
63
+ for (let pathIndex = 0; pathIndex < path.length - 1; pathIndex++) {
64
+ if (
65
+ doesSegmentIntersectRect(path[pathIndex]!, path[pathIndex + 1]!, bounds)
66
+ ) {
67
+ return true
68
+ }
69
+ }
70
+ return false
71
+ }
72
+
73
+ const getPerpendicularOffset = (
74
+ firstPin: TraceRecoveryPin,
75
+ secondPin: TraceRecoveryPin,
76
+ ) => {
77
+ const xDistance = Math.abs(firstPin.x - secondPin.x)
78
+ const yDistance = Math.abs(firstPin.y - secondPin.y)
79
+ if (xDistance >= yDistance) return yDistance
80
+ return xDistance
81
+ }
82
+
83
+ export class NetLabelToTraceSolver extends BaseSolver {
84
+ inputProblem: InputProblem
85
+
86
+ outputTraces: SolvedTracePath[]
87
+ outputNetLabelPlacements: NetLabelPlacement[]
88
+
89
+ private chipMap: Record<ChipId, InputChip>
90
+ private pinMap: Map<PinId, TraceRecoveryPin>
91
+ private queuedCandidates: CandidatePair[]
92
+ private currentCandidate: CandidatePair | null = null
93
+ declare activeSubSolver: SchematicTraceSingleLineSolver2 | null
94
+
95
+ constructor(private input: InlineNetLabelOutput) {
96
+ super()
97
+ this.inputProblem = input.inputProblem
98
+ this.outputTraces = [...input.traces]
99
+ this.outputNetLabelPlacements = [...input.netLabelPlacements]
100
+
101
+ const { chipMap, pinMap } = getTraceRecoveryConnectivityMaps(
102
+ this.inputProblem,
103
+ )
104
+ this.chipMap = chipMap
105
+ this.pinMap = pinMap
106
+
107
+ this.queuedCandidates = this.buildCandidatePairs()
108
+ this.stats.candidateCount = this.queuedCandidates.length
109
+ this.stats.recoveredTraceCount = 0
110
+ }
111
+
112
+ override getConstructorParams(): [InlineNetLabelOutput] {
113
+ return [this.input]
114
+ }
115
+
116
+ private isEligiblePortOnlyDirectConnectionLabel(
117
+ label: NetLabelPlacement,
118
+ groundGlobalConnNetId?: GlobalConnNetId,
119
+ ) {
120
+ if (
121
+ label.pinIds.length !== 1 ||
122
+ label.mspConnectionPairIds.length !== 0 ||
123
+ !label.netId ||
124
+ label.netId === "GND" ||
125
+ label.globalConnNetId === groundGlobalConnNetId
126
+ ) {
127
+ return false
128
+ }
129
+
130
+ const pinId = label.pinIds[0]!
131
+ return this.inputProblem.directConnections.some(
132
+ (connection) =>
133
+ connection.netId === label.netId && connection.pinIds.includes(pinId),
134
+ )
135
+ }
136
+
137
+ private buildCandidatePairs() {
138
+ const { netConnMap } = getConnectivityMapsFromInputProblem(
139
+ this.inputProblem,
140
+ )
141
+ const groundGlobalConnNetId =
142
+ netConnMap.getNetConnectedToId("GND") ?? undefined
143
+ const labelsByGlobalNet = new Map<GlobalConnNetId, NetLabelPlacement[]>()
144
+
145
+ for (const label of this.input.netLabelPlacements) {
146
+ if (
147
+ !this.isEligiblePortOnlyDirectConnectionLabel(
148
+ label,
149
+ groundGlobalConnNetId,
150
+ )
151
+ ) {
152
+ continue
153
+ }
154
+ const labels = labelsByGlobalNet.get(label.globalConnNetId) ?? []
155
+ labels.push(label)
156
+ labelsByGlobalNet.set(label.globalConnNetId, labels)
157
+ }
158
+
159
+ const candidates: CandidatePair[] = []
160
+ for (const labels of labelsByGlobalNet.values()) {
161
+ for (let firstIndex = 0; firstIndex < labels.length; firstIndex++) {
162
+ for (
163
+ let secondIndex = firstIndex + 1;
164
+ secondIndex < labels.length;
165
+ secondIndex++
166
+ ) {
167
+ const firstLabel = labels[firstIndex]!
168
+ const secondLabel = labels[secondIndex]!
169
+ const firstPin = this.pinMap.get(firstLabel.pinIds[0]!)
170
+ const secondPin = this.pinMap.get(secondLabel.pinIds[0]!)
171
+ if (!firstPin || !secondPin) continue
172
+ if (
173
+ arePinsInDifferentSchematicSections(
174
+ this.inputProblem,
175
+ firstPin,
176
+ secondPin,
177
+ )
178
+ ) {
179
+ continue
180
+ }
181
+ if (
182
+ doesPairCrossRestrictedCenterLines({
183
+ inputProblem: this.inputProblem,
184
+ chipMap: this.chipMap,
185
+ pinIdMap: this.pinMap,
186
+ p1: firstPin,
187
+ p2: secondPin,
188
+ })
189
+ ) {
190
+ continue
191
+ }
192
+
193
+ candidates.push({
194
+ firstLabel,
195
+ secondLabel,
196
+ pins: [firstPin, secondPin],
197
+ perpendicularOffset: getPerpendicularOffset(firstPin, secondPin),
198
+ routeDistance:
199
+ Math.abs(firstPin.x - secondPin.x) +
200
+ Math.abs(firstPin.y - secondPin.y),
201
+ key: getCanonicalPairKey(firstPin.pinId, secondPin.pinId),
202
+ })
203
+ }
204
+ }
205
+ }
206
+
207
+ candidates.sort(
208
+ (first, second) =>
209
+ first.perpendicularOffset - second.perpendicularOffset ||
210
+ first.routeDistance - second.routeDistance ||
211
+ first.key.localeCompare(second.key),
212
+ )
213
+ return candidates
214
+ }
215
+
216
+ private isSupersededConnectorTrace(
217
+ trace: SolvedTracePath,
218
+ candidate: CandidatePair,
219
+ ) {
220
+ if (!trace.mspPairId.startsWith(AVAILABLE_NET_ORIENTATION_PREFIX)) {
221
+ return false
222
+ }
223
+ if (trace.pinIds.length !== 1) return false
224
+ return candidate.pins.some((pin) => pin.pinId === trace.pinIds[0])
225
+ }
226
+
227
+ private routeIntersectsRemainingLabels(
228
+ tracePath: Point[],
229
+ candidate: CandidatePair,
230
+ ) {
231
+ for (const label of this.outputNetLabelPlacements) {
232
+ if (label === candidate.firstLabel || label === candidate.secondLabel) {
233
+ continue
234
+ }
235
+ if (pathIntersectsRenderedLabel(tracePath, label)) return true
236
+ }
237
+ return this.input.inlineNetLabelPlacements.some((label) =>
238
+ pathIntersectsRenderedLabel(tracePath, label),
239
+ )
240
+ }
241
+
242
+ private tryAcceptCurrentRoute() {
243
+ const candidate = this.currentCandidate
244
+ const tracePath = this.activeSubSolver?.solvedTracePath
245
+ if (!candidate || !tracePath) return
246
+
247
+ const retainedTraces = this.outputTraces.filter(
248
+ (trace) => !this.isSupersededConnectorTrace(trace, candidate),
249
+ )
250
+ if (
251
+ doesTraceOverlapWithExistingTraces(tracePath, retainedTraces) ||
252
+ this.routeIntersectsRemainingLabels(tracePath, candidate)
253
+ ) {
254
+ return
255
+ }
256
+
257
+ const [firstPin, secondPin] = candidate.pins
258
+ const mspPairId = `${RECOVERED_TRACE_PREFIX}${candidate.key}`
259
+ const recoveredTrace: SolvedTracePath = {
260
+ mspPairId,
261
+ dcConnNetId: candidate.firstLabel.globalConnNetId,
262
+ globalConnNetId: candidate.firstLabel.globalConnNetId,
263
+ pins: [firstPin, secondPin],
264
+ tracePath,
265
+ mspConnectionPairIds: [mspPairId],
266
+ pinIds: [firstPin.pinId, secondPin.pinId],
267
+ }
268
+
269
+ this.outputTraces = [...retainedTraces, recoveredTrace]
270
+ this.outputNetLabelPlacements = this.outputNetLabelPlacements.filter(
271
+ (label) =>
272
+ label !== candidate.firstLabel && label !== candidate.secondLabel,
273
+ )
274
+ this.stats.recoveredTraceCount++
275
+ }
276
+
277
+ override _step() {
278
+ if (this.activeSubSolver) {
279
+ this.activeSubSolver.step()
280
+ if (this.activeSubSolver.solved) {
281
+ this.tryAcceptCurrentRoute()
282
+ this.activeSubSolver = null
283
+ this.currentCandidate = null
284
+ } else if (this.activeSubSolver.failed) {
285
+ this.activeSubSolver = null
286
+ this.currentCandidate = null
287
+ }
288
+ return
289
+ }
290
+
291
+ let candidate = this.queuedCandidates.shift()
292
+ while (
293
+ candidate &&
294
+ (!this.outputNetLabelPlacements.includes(candidate.firstLabel) ||
295
+ !this.outputNetLabelPlacements.includes(candidate.secondLabel))
296
+ ) {
297
+ candidate = this.queuedCandidates.shift()
298
+ }
299
+
300
+ if (!candidate) {
301
+ this.solved = true
302
+ return
303
+ }
304
+
305
+ this.currentCandidate = candidate
306
+ this.activeSubSolver = new SchematicTraceSingleLineSolver2({
307
+ inputProblem: this.inputProblem,
308
+ pins: candidate.pins,
309
+ chipMap: this.chipMap,
310
+ })
311
+ }
312
+
313
+ getOutput() {
314
+ return {
315
+ traces: this.outputTraces,
316
+ netLabelPlacements: this.outputNetLabelPlacements,
317
+ inlineNetLabelPlacements: this.input.inlineNetLabelPlacements,
318
+ }
319
+ }
320
+
321
+ override visualize(): GraphicsObject {
322
+ if (this.activeSubSolver) return this.activeSubSolver.visualize()
323
+
324
+ const graphics = visualizeInlineNetLabelOutput({
325
+ inputProblem: this.inputProblem,
326
+ ...this.getOutput(),
327
+ })
328
+ for (const trace of this.outputTraces) {
329
+ if (!trace.mspPairId.startsWith(RECOVERED_TRACE_PREFIX)) continue
330
+ graphics.lines!.push({
331
+ points: trace.tracePath,
332
+ strokeColor: "green",
333
+ label: `recovered from net labels: ${trace.pinIds.join(" -> ")}`,
334
+ })
335
+ }
336
+ return graphics
337
+ }
338
+ }
@@ -0,0 +1,33 @@
1
+ import { getPinDirection } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection"
2
+ import type { MspConnectionPair } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
3
+ import type {
4
+ ChipId,
5
+ InputChip,
6
+ InputProblem,
7
+ PinId,
8
+ } from "lib/types/InputProblem"
9
+ import type { FacingDirection } from "lib/utils/dir"
10
+
11
+ export type TraceRecoveryPin = MspConnectionPair["pins"][number] & {
12
+ _facingDirection: FacingDirection
13
+ }
14
+
15
+ export const getTraceRecoveryConnectivityMaps = (
16
+ inputProblem: InputProblem,
17
+ ) => {
18
+ const chipMap: Record<ChipId, InputChip> = {}
19
+ const pinMap = new Map<PinId, TraceRecoveryPin>()
20
+
21
+ for (const chip of inputProblem.chips) {
22
+ chipMap[chip.chipId] = chip
23
+ for (const pin of chip.pins) {
24
+ pinMap.set(pin.pinId, {
25
+ ...pin,
26
+ chipId: chip.chipId,
27
+ _facingDirection: pin._facingDirection ?? getPinDirection(pin, chip),
28
+ })
29
+ }
30
+ }
31
+
32
+ return { chipMap, pinMap }
33
+ }
@@ -35,6 +35,7 @@ import { UnroutedTraceRecoverySolver } from "../UnroutedTraceRecoverySolver/Unro
35
35
  import { SameNetJunctionAlignmentSolver } from "../SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver"
36
36
  import { TraceElbowTransitionSimplificationSolver } from "../TraceElbowTransitionSimplificationSolver/TraceElbowTransitionSimplificationSolver"
37
37
  import { InlineNetLabelSolver } from "../InlineNetLabelSolver/InlineNetLabelSolver"
38
+ import { NetLabelToTraceSolver } from "../NetLabelToTraceSolver/NetLabelToTraceSolver"
38
39
  import { findPerpendicularPathCrossings } from "../TraceCleanupSolver/sub-solver/findIntersectionsWithObstacles"
39
40
 
40
41
  type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
@@ -103,6 +104,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
103
104
  finalTraceElbowTransitionSimplificationSolver?: TraceElbowTransitionSimplificationSolver
104
105
  sameNetJunctionAlignmentSolver?: SameNetJunctionAlignmentSolver
105
106
  inlineNetLabelSolver?: InlineNetLabelSolver
107
+ netLabelToTraceSolver?: NetLabelToTraceSolver
106
108
 
107
109
  startTimeOfPhase: Record<string, number>
108
110
  endTimeOfPhase: Record<string, number>
@@ -588,6 +590,19 @@ export class SchematicTracePipelineSolver extends BaseSolver {
588
590
  ]
589
591
  },
590
592
  ),
593
+ definePipelineStep(
594
+ "netLabelToTraceSolver",
595
+ NetLabelToTraceSolver,
596
+ (instance) => {
597
+ const inlineOutput = instance.inlineNetLabelSolver!.getOutput()
598
+ return [
599
+ {
600
+ inputProblem: instance.inputProblem,
601
+ ...inlineOutput,
602
+ },
603
+ ]
604
+ },
605
+ ),
591
606
  ]
592
607
 
593
608
  constructor(inputProblem: InputProblem, opts?: Options) {
@@ -44,7 +44,7 @@ export const visualizeInputProblem = (
44
44
 
45
45
  for (const pin of chip.pins) {
46
46
  graphics.points.push({
47
- label: `${pin.pinId}\n${pin._facingDirection ?? getPinDirection(pin, chip)}`,
47
+ label: `${pin.displayName ?? pin.pinId}\n${pin._facingDirection ?? getPinDirection(pin, chip)}`,
48
48
  x: pin.x,
49
49
  y: pin.y,
50
50
  color: getColorFromString(pin.pinId, 0.8),
@@ -8,6 +8,13 @@ export type SectionId = string
8
8
 
9
9
  export interface InputPin {
10
10
  pinId: PinId
11
+
12
+ /**
13
+ * User-facing label for this schematic port. `pinId` remains the stable,
14
+ * opaque routing identity and must not be shown in schematic output.
15
+ */
16
+ displayName?: string
17
+
11
18
  x: number
12
19
  y: number
13
20
 
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "https://github.com/tscircuit/schematic-trace-solver.git"
6
6
  },
7
7
  "main": "dist/index.js",
8
- "version": "0.0.150",
8
+ "version": "0.0.152",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "start": "cosmos",