@tscircuit/schematic-trace-solver 0.0.149 → 0.0.151

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.
Files changed (162) hide show
  1. package/.agents/skills/schematic-trace-visualization/SKILL.md +7 -2
  2. package/README.md +6 -4
  3. package/dist/index.d.ts +64 -3
  4. package/dist/index.js +383 -66
  5. package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +123 -84
  6. package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +17 -1
  7. package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver.ts +2 -0
  8. package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/solvePortOnlyPin.ts +2 -0
  9. package/lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts +338 -0
  10. package/lib/solvers/NetLabelTraceRecovery/getTraceRecoveryConnectivityMaps.ts +33 -0
  11. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +15 -0
  12. package/lib/types/InputProblem.ts +27 -1
  13. package/package.json +8 -1
  14. package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +751 -5
  15. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +660 -5
  16. package/tests/bug-reports/bug-report-20260707T020342Z/__snapshots__/bug-report-20260707T020342Z.snap.svg +44 -5
  17. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +394 -5
  18. package/tests/bug-reports/bug-report-20260707T134549Z/__snapshots__/bug-report-20260707T134549Z.snap.svg +170 -5
  19. package/tests/bug-reports/bug-report-20260707T134722Z/__snapshots__/bug-report-20260707T134722Z.snap.svg +79 -5
  20. package/tests/bug-reports/bug-report-20260707T140410Z/__snapshots__/bug-report-20260707T140410Z.snap.svg +219 -5
  21. package/tests/bug-reports/bug-report-20260707T141025Z/__snapshots__/bug-report-20260707T141025Z.snap.svg +65 -5
  22. package/tests/bug-reports/bug-report-20260707T141421Z/__snapshots__/bug-report-20260707T141421Z.snap.svg +142 -5
  23. package/tests/bug-reports/bug-report-20260707T230831Z/__snapshots__/bug-report-20260707T230831Z.snap.svg +100 -5
  24. package/tests/bug-reports/bug-report-20260708T053736Z/__snapshots__/bug-report-20260708T053736Z.snap.svg +23 -5
  25. package/tests/bug-reports/bug-report-20260708T055430Z/__snapshots__/bug-report-20260708T055430Z.snap.svg +58 -5
  26. package/tests/bug-reports/bug-report-20260708T095725Z/__snapshots__/bug-report-20260708T095725Z.snap.svg +86 -5
  27. package/tests/bug-reports/bug-report-20260716T144856Z/__snapshots__/bug-report-20260716T144856Z.snap.svg +226 -5
  28. package/tests/bug-reports/bug-report-20260717T022934Z/__snapshots__/bug-report-20260717T022934Z.snap.svg +191 -5
  29. package/tests/bug-reports/bug-report-20260717T031704Z/__snapshots__/bug-report-20260717T031704Z.snap.svg +30 -5
  30. package/tests/bug-reports/bug-report-20260717T042845Z/__snapshots__/bug-report-20260717T042845Z.snap.svg +37 -5
  31. package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +580 -2
  32. package/tests/bug-reports/bug-report-20260724T175257Z/__snapshots__/bug-report-20260724T175257Z.snap.svg +44 -5
  33. package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +107 -5
  34. package/tests/bug-reports/bug-report-20260728T225606Z/__snapshots__/bug-report-20260728T225606Z.snap.svg +114 -5
  35. package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +121 -5
  36. package/tests/bug-reports/bug-report-20260731T052229Z/__snapshots__/bug-report-20260731T052229Z.snap.svg +51 -5
  37. package/tests/bug-reports/bug-report-20260804T095800Z/__snapshots__/bug-report-20260804T095800Z.snap.svg +212 -5
  38. package/tests/bug-reports/bug-report-20260804T171919Z/__snapshots__/bug-report-20260804T171919Z.snap.svg +569 -5
  39. package/tests/bug-reports/bug-report-20260804T225912Z/__snapshots__/bug-report-20260804T225912Z.snap.svg +37 -5
  40. package/tests/bug-reports/bug-report-20260805T061316Z/__snapshots__/bug-report-20260805T061316Z.snap.svg +289 -5
  41. package/tests/bug-reports/bug-report-20260806T061548Z/__snapshots__/bug-report-20260806T061548Z.snap.svg +289 -5
  42. package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +149 -5
  43. package/tests/bug-reports/bug-report-20260811T075142Z/__snapshots__/bug-report-20260811T075142Z.snap.svg +93 -5
  44. package/tests/bug-reports/bug-report-20260815T073240Z/__snapshots__/bug-report-20260815T073240Z.snap.svg +79 -5
  45. package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +244 -2
  46. package/tests/examples/__snapshots__/example01.snap.svg +44 -5
  47. package/tests/examples/__snapshots__/example02.snap.svg +51 -5
  48. package/tests/examples/__snapshots__/example03.snap.svg +86 -5
  49. package/tests/examples/__snapshots__/example04.snap.svg +30 -5
  50. package/tests/examples/__snapshots__/example05.snap.svg +30 -5
  51. package/tests/examples/__snapshots__/example06.snap.svg +30 -5
  52. package/tests/examples/__snapshots__/example07.snap.svg +58 -5
  53. package/tests/examples/__snapshots__/example08.snap.svg +37 -5
  54. package/tests/examples/__snapshots__/example09.snap.svg +149 -5
  55. package/tests/examples/__snapshots__/example10.snap.svg +65 -5
  56. package/tests/examples/__snapshots__/example11.snap.svg +51 -5
  57. package/tests/examples/__snapshots__/example12.snap.svg +51 -5
  58. package/tests/examples/__snapshots__/example13.snap.svg +72 -5
  59. package/tests/examples/__snapshots__/example14.snap.svg +79 -5
  60. package/tests/examples/__snapshots__/example15.snap.svg +86 -5
  61. package/tests/examples/__snapshots__/example16.snap.svg +51 -5
  62. package/tests/examples/__snapshots__/example17.snap.svg +51 -5
  63. package/tests/examples/__snapshots__/example18.snap.svg +58 -5
  64. package/tests/examples/__snapshots__/example19.snap.svg +44 -5
  65. package/tests/examples/__snapshots__/example20.snap.svg +44 -5
  66. package/tests/examples/__snapshots__/example21.snap.svg +72 -5
  67. package/tests/examples/__snapshots__/example22.snap.svg +51 -5
  68. package/tests/examples/__snapshots__/example23.snap.svg +51 -5
  69. package/tests/examples/__snapshots__/example24.snap.svg +51 -5
  70. package/tests/examples/__snapshots__/example25.snap.svg +86 -5
  71. package/tests/examples/__snapshots__/example26.snap.svg +51 -5
  72. package/tests/examples/__snapshots__/example27.snap.svg +72 -5
  73. package/tests/examples/__snapshots__/example28.snap.svg +44 -5
  74. package/tests/examples/__snapshots__/example29.snap.svg +324 -5
  75. package/tests/examples/__snapshots__/example30.snap.svg +107 -5
  76. package/tests/examples/__snapshots__/example31.snap.svg +37 -5
  77. package/tests/examples/__snapshots__/example32.snap.svg +149 -5
  78. package/tests/examples/__snapshots__/example33.snap.svg +93 -5
  79. package/tests/examples/__snapshots__/example34.snap.svg +51 -5
  80. package/tests/examples/__snapshots__/example35.snap.svg +58 -5
  81. package/tests/examples/__snapshots__/example36.snap.svg +44 -5
  82. package/tests/examples/__snapshots__/example37.snap.svg +72 -5
  83. package/tests/examples/__snapshots__/example38.snap.svg +30 -5
  84. package/tests/examples/__snapshots__/example39.snap.svg +37 -5
  85. package/tests/examples/__snapshots__/example40.snap.svg +100 -5
  86. package/tests/examples/__snapshots__/example41.snap.svg +44 -5
  87. package/tests/examples/__snapshots__/example42.snap.svg +65 -5
  88. package/tests/examples/__snapshots__/example43.snap.svg +58 -5
  89. package/tests/examples/__snapshots__/example44.snap.svg +107 -5
  90. package/tests/examples/__snapshots__/example45.snap.svg +51 -5
  91. package/tests/examples/__snapshots__/example46.snap.svg +93 -5
  92. package/tests/examples/__snapshots__/example47.snap.svg +93 -5
  93. package/tests/examples/__snapshots__/example48.snap.svg +65 -5
  94. package/tests/examples/__snapshots__/example49.snap.svg +177 -5
  95. package/tests/examples/__snapshots__/example50.snap.svg +93 -5
  96. package/tests/examples/__snapshots__/example51.snap.svg +594 -21
  97. package/tests/fixtures/convertSolverOutputToCircuitJson.test.ts +219 -0
  98. package/tests/fixtures/convertSolverOutputToCircuitJson.ts +1058 -0
  99. package/tests/fixtures/matcher.ts +45 -8
  100. package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +762 -2
  101. package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +111 -2
  102. package/tests/repros/__snapshots__/board1096-usb-label-overlap-iteration.snap.svg +170 -5
  103. package/tests/repros/__snapshots__/bugreport-001-gnd-overlap.snap.svg +65 -5
  104. package/tests/repros/__snapshots__/manufacturePartNumber-text-box.snap.svg +30 -5
  105. package/tests/repros/__snapshots__/netlabel-connector-through-rail-label.snap.svg +79 -5
  106. package/tests/repros/__snapshots__/repro-atmega328p-fault-pullup.snap.svg +65 -5
  107. package/tests/repros/__snapshots__/repro-atmega328p-missing-gnd-netlabel.snap.svg +149 -5
  108. package/tests/repros/__snapshots__/repro-board-589-regulator-section.snap.svg +55 -2
  109. package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +160 -2
  110. package/tests/repros/__snapshots__/repro-bq24074-battery-charger.snap.svg +135 -5
  111. package/tests/repros/__snapshots__/repro-bq25895-cross-net-trace-overlap.snap.svg +69 -2
  112. package/tests/repros/__snapshots__/repro-cc2340r5.snap.svg +86 -5
  113. package/tests/repros/__snapshots__/repro-core-555-power-cross-net-overlap.snap.svg +44 -5
  114. package/tests/repros/__snapshots__/repro-core-subcircuit-missing-ground.snap.svg +44 -5
  115. package/tests/repros/__snapshots__/repro-core-tb67s579ftg-inline-label-routing.snap.svg +30 -5
  116. package/tests/repros/__snapshots__/repro-example35-minimize-trace-crossing.snap.svg +335 -2
  117. package/tests/repros/__snapshots__/repro-ground-label-on-lower-rail-end.snap.svg +48 -2
  118. package/tests/repros/__snapshots__/repro-ina237-current-monitor.snap.svg +149 -5
  119. package/tests/repros/__snapshots__/repro-missing-trace-netlabel.snap.svg +72 -5
  120. package/tests/repros/__snapshots__/repro-netlabel-collision-687.snap.svg +100 -5
  121. package/tests/repros/__snapshots__/repro-netlabel-overlap-trace.snap.svg +51 -5
  122. package/tests/repros/__snapshots__/repro-nrf52810-clock-routing.snap.svg +90 -2
  123. package/tests/repros/__snapshots__/repro-power-group-rail-labels.snap.svg +37 -5
  124. package/tests/repros/__snapshots__/repro-rectifier-trace-overlap.snap.svg +44 -5
  125. package/tests/repros/__snapshots__/repro-rp2040-gamepad-trace-alignment.snap.svg +237 -2
  126. package/tests/repros/__snapshots__/repro-rp2040-usb-cc2-ground-overlap.snap.svg +72 -5
  127. package/tests/repros/__snapshots__/repro-rp2040-v3v3-label-placement.snap.svg +51 -5
  128. package/tests/repros/__snapshots__/repro-rp2040-zero-crystal-fallback-netlabels.snap.svg +30 -5
  129. package/tests/repros/__snapshots__/repro-ti-power-output-section.snap.svg +79 -5
  130. package/tests/repros/__snapshots__/repro-tida010076-j4-ground-bus.snap.svg +27 -2
  131. package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +1150 -5
  132. package/tests/repros/__snapshots__/repro-tps61222-trace-intersection.snap.svg +51 -5
  133. package/tests/repros/__snapshots__/repro-type-c-16pin-self-connection.snap.svg +30 -5
  134. package/tests/repros/__snapshots__/repro-type-c-16pin-to-resistor.snap.svg +30 -5
  135. package/tests/repros/__snapshots__/repro-type-c-vbus-led-ground-label.snap.svg +44 -5
  136. package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +685 -2
  137. package/tests/repros/__snapshots__/repro-vcc-pin1-detour.snap.svg +27 -2
  138. package/tests/repros/__snapshots__/repro129-host-custom-symbol-passives.snap.svg +51 -5
  139. package/tests/repros/__snapshots__/repro130-bq27441-fuel-gauge-trace-through-c1.snap.svg +100 -5
  140. package/tests/repros/__snapshots__/repro161-power-section-autolayout.snap.svg +107 -5
  141. package/tests/repros/__snapshots__/repro47-endpoint-obstacle-detour.snap.svg +51 -5
  142. package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +104 -2
  143. package/tests/repros/__snapshots__/repro5-escape-padded-text-obstacles.snap.svg +30 -5
  144. package/tests/repros/__snapshots__/repro51-overlap-junction-crossing.snap.svg +65 -5
  145. package/tests/repros/__snapshots__/repro84-stacked-gnd-label-orientation.snap.svg +27 -2
  146. package/tests/repros/__snapshots__/small-variant-resistor-facing-direction.snap.svg +58 -5
  147. package/tests/repros/__snapshots__/trace-overlap-box-resistor.snap.svg +37 -5
  148. package/tests/solvers/InlineNetLabelSolver/__snapshots__/fallback-net-label-width.snap.svg +25 -7
  149. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label-anchored-label-clearance.snap.svg +34 -2
  150. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label-port-stub.snap.svg +23 -5
  151. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label-two-pin-net.snap.svg +23 -5
  152. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label01.snap.svg +23 -5
  153. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label02.snap.svg +23 -5
  154. package/tests/solvers/InlineNetLabelSolver/__snapshots__/terminal-stub-trace-clearance.snap.svg +23 -5
  155. package/tests/solvers/InlineNetLabelSolver/fallback-net-label-width.test.ts +10 -1
  156. package/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/SchematicTraceSingleLineSolver2_01-example17-d1_1-u1_1.snap.svg +23 -5
  157. package/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/SchematicTraceSingleLineSolver2_repro47-short-opposing-pins.snap.svg +20 -2
  158. package/tests/solvers/TraceCleanupSolver/__snapshots__/TraceCleanupSolver.snap.svg +44 -5
  159. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +79 -5
  160. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/MergedNetLabelObstacles.snap.svg +44 -5
  161. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/OverlapAvoidanceStepSolver.snap.svg +23 -5
  162. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/SingleOverlapSolver.snap.svg +30 -5
@@ -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"
@@ -52,6 +52,8 @@ export const INLINE_NET_LABEL_MAX_SPAN_JOG = 0.4
52
52
  export interface InlineNetLabelPlacement {
53
53
  globalConnNetId: string
54
54
  netId?: string
55
+ /** User-facing label content, separate from the connectivity identifier. */
56
+ netLabelText?: string
55
57
  mspPairId?: string
56
58
  pinIds: PinId[]
57
59
 
@@ -90,6 +92,13 @@ interface InlineNetLabelSolverInput {
90
92
  netLabelPlacements: NetLabelPlacement[]
91
93
  }
92
94
 
95
+ export interface InlineNetLabelOutput {
96
+ inputProblem: InputProblem
97
+ traces: SolvedTracePath[]
98
+ netLabelPlacements: NetLabelPlacement[]
99
+ inlineNetLabelPlacements: InlineNetLabelPlacement[]
100
+ }
101
+
93
102
  type InlineEligibleConnection = InputDirectConnection | InputNetConnection
94
103
 
95
104
  const getPinPairKey = (pinIds: readonly string[]) =>
@@ -236,10 +245,11 @@ export class InlineNetLabelSolver extends BaseSolver {
236
245
 
237
246
  const height =
238
247
  connection.inlineNetLabelHeight ?? DEFAULT_INLINE_NET_LABEL_HEIGHT
248
+ const netLabelText = connection.netLabelText?.trim() || undefined
239
249
  const width =
240
250
  connection.inlineNetLabelWidth ??
241
251
  connection.netLabelWidth ??
242
- estimateInlineNetLabelWidth(connection.netId, height)
252
+ estimateInlineNetLabelWidth(netLabelText ?? connection.netId, height)
243
253
 
244
254
  // Leave a small wire tail at both ends of the text so it unmistakably
245
255
  // reads as a label on a trace rather than free-standing text.
@@ -310,6 +320,7 @@ export class InlineNetLabelSolver extends BaseSolver {
310
320
  return {
311
321
  globalConnNetId: anchoredPlacement.globalConnNetId,
312
322
  netId: connection.netId,
323
+ netLabelText,
313
324
  pinIds: [pinId],
314
325
  stubTracePath: [start, end],
315
326
  axis,
@@ -336,10 +347,11 @@ export class InlineNetLabelSolver extends BaseSolver {
336
347
 
337
348
  const height =
338
349
  connection.inlineNetLabelHeight ?? DEFAULT_INLINE_NET_LABEL_HEIGHT
350
+ const netLabelText = connection.netLabelText?.trim() || undefined
339
351
  const width =
340
352
  connection.inlineNetLabelWidth ??
341
353
  connection.netLabelWidth ??
342
- estimateInlineNetLabelWidth(connection.netId!, height)
354
+ estimateInlineNetLabelWidth(netLabelText ?? connection.netId!, height)
343
355
 
344
356
  const offset = height / 2 + INLINE_NET_LABEL_TRACE_MARGIN
345
357
 
@@ -397,6 +409,7 @@ export class InlineNetLabelSolver extends BaseSolver {
397
409
  return {
398
410
  globalConnNetId: trace.globalConnNetId,
399
411
  netId: connection.netId,
412
+ netLabelText,
400
413
  mspPairId: trace.mspPairId,
401
414
  pinIds: [...connection.pinIds],
402
415
  axis: segment.axis,
@@ -417,6 +430,7 @@ export class InlineNetLabelSolver extends BaseSolver {
417
430
  const spanPlacement = this.computeSpanPlacement({
418
431
  trace,
419
432
  connection,
433
+ netLabelText,
420
434
  width,
421
435
  height,
422
436
  offset,
@@ -437,12 +451,14 @@ export class InlineNetLabelSolver extends BaseSolver {
437
451
  private computeSpanPlacement({
438
452
  trace,
439
453
  connection,
454
+ netLabelText,
440
455
  width,
441
456
  height,
442
457
  offset,
443
458
  }: {
444
459
  trace: SolvedTracePath
445
460
  connection: InlineEligibleConnection
461
+ netLabelText?: string
446
462
  width: number
447
463
  height: number
448
464
  offset: number
@@ -533,6 +549,7 @@ export class InlineNetLabelSolver extends BaseSolver {
533
549
  return {
534
550
  globalConnNetId: trace.globalConnNetId,
535
551
  netId: connection.netId,
552
+ netLabelText,
536
553
  mspPairId: trace.mspPairId,
537
554
  pinIds: [...connection.pinIds],
538
555
  axis,
@@ -636,95 +653,117 @@ export class InlineNetLabelSolver extends BaseSolver {
636
653
  // Mirrors the previous pipeline stage's visualization so that a problem
637
654
  // with no inline labels renders identically, then layers the inline labels
638
655
  // on top.
639
- const graphics = visualizeInputProblem(this.inputProblem)
640
- graphics.lines ??= []
641
- graphics.rects ??= []
642
- graphics.points ??= []
643
- graphics.texts ??= []
644
-
645
- const output = this.getOutput()
646
- 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) {
647
701
  graphics.lines.push({
648
- points: trace.tracePath,
702
+ points: inlineLabel.stubTracePath,
649
703
  strokeColor: "purple",
650
704
  })
651
705
  }
652
-
653
- const { netLabelPlacements } = this.getOutput()
654
- for (const label of netLabelPlacements) {
655
- graphics.rects.push({
656
- center: label.center,
657
- width: label.width,
658
- height: label.height,
659
- fill: getColorFromString(label.globalConnNetId, 0.35),
660
- strokeColor: getColorFromString(label.globalConnNetId, 0.9),
661
- label: `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`,
662
- } as Rect & { strokeColor: string })
663
- graphics.points.push({
664
- x: label.anchorPoint.x,
665
- y: label.anchorPoint.y,
666
- color: getColorFromString(label.globalConnNetId, 0.9),
667
- label: `anchorPoint\norientation: ${label.orientation}`,
668
- })
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
669
712
  }
670
-
671
- for (const inlineLabel of this.inlineNetLabelPlacements) {
672
- if (inlineLabel.stubTracePath) {
673
- graphics.lines.push({
674
- points: inlineLabel.stubTracePath,
675
- strokeColor: "purple",
676
- })
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
677
744
  }
678
- const isHorizontal = inlineLabel.axis === "x"
679
- graphics.rects.push({
680
- center: inlineLabel.center,
681
- width: isHorizontal ? inlineLabel.width : inlineLabel.height,
682
- height: isHorizontal ? inlineLabel.height : inlineLabel.width,
683
- fill: getColorFromString(inlineLabel.globalConnNetId, 0.35),
684
- strokeColor: "green",
685
- label: [
686
- `INLINE netId: ${inlineLabel.netId}`,
687
- `axis: ${inlineLabel.axis}`,
688
- `side: ${inlineLabel.side}`,
689
- ].join("\n"),
690
- } as Rect & { strokeColor: string })
691
- graphics.texts.push({
692
- x:
693
- inlineLabel.stubTracePath && inlineLabel.axis === "x"
694
- ? inlineLabel.center.x +
695
- (inlineLabel.stubTracePath[1].x > inlineLabel.stubTracePath[0].x
696
- ? -inlineLabel.width / 2
697
- : inlineLabel.width / 2)
698
- : inlineLabel.center.x,
699
- y:
700
- inlineLabel.stubTracePath && inlineLabel.axis === "y"
701
- ? inlineLabel.center.y +
702
- (inlineLabel.stubTracePath[1].y > inlineLabel.stubTracePath[0].y
703
- ? -inlineLabel.width / 2
704
- : inlineLabel.width / 2)
705
- : inlineLabel.center.y,
706
- text: inlineLabel.netId ?? "",
707
- color: "green",
708
- fontSize: inlineLabel.height,
709
- anchorSide: inlineLabel.stubTracePath
710
- ? inlineLabel.stubTracePath[1][inlineLabel.axis] >
711
- inlineLabel.stubTracePath[0][inlineLabel.axis]
712
- ? "center_left"
713
- : "center_right"
714
- : "center",
715
- // Vertical labels read bottom-to-top, alongside the wire they name.
716
- rotation: inlineLabel.axis === "y" ? 90 : undefined,
717
- })
718
- graphics.points.push({
719
- x: inlineLabel.anchorPoint.x,
720
- y: inlineLabel.anchorPoint.y,
721
- color: "green",
722
- label: `inline anchor\n${inlineLabel.netId}`,
723
- })
724
745
  }
725
-
726
- 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
+ })
727
764
  }
765
+
766
+ return graphics
728
767
  }
729
768
 
730
769
  /**
@@ -18,6 +18,7 @@ import { getNetLabelWidthForConnection } from "lib/utils/getNetLabelWidthForConn
18
18
  export type OverlappingSameNetTraceGroup = {
19
19
  globalConnNetId: string
20
20
  netId?: string
21
+ netLabelText?: string
21
22
  overlappingTraces?: SolvedTracePath
22
23
  portOnlyPinId?: string
23
24
  mspConnectionPairIds?: MspConnectionPairId[]
@@ -30,6 +31,8 @@ export interface NetLabelPlacement {
30
31
  * Optional user-provided net identifier (if present in the input problem).
31
32
  */
32
33
  netId?: string
34
+ /** User-facing label content, separate from the connectivity identifier. */
35
+ netLabelText?: string
33
36
  /**
34
37
  * MSP pair ids that the label is associated with. Port-only labels use [].
35
38
  */
@@ -116,18 +119,27 @@ export class NetLabelPlacementSolver extends BaseSolver {
116
119
  }
117
120
  }
118
121
 
119
- // Map pins to user-provided netIds (if any)
122
+ // Map pins to user-provided connectivity ids and display text (if any).
120
123
  const userNetIdByPinId: Record<string, string | undefined> = {}
124
+ const netLabelTextByPinId: Record<string, string | undefined> = {}
121
125
  for (const dc of this.inputProblem.directConnections) {
122
126
  if (dc.netId) {
123
127
  const [a, b] = dc.pinIds
124
128
  userNetIdByPinId[a] = dc.netId
125
129
  userNetIdByPinId[b] = dc.netId
126
130
  }
131
+ const netLabelText = dc.netLabelText?.trim()
132
+ if (netLabelText) {
133
+ const [a, b] = dc.pinIds
134
+ netLabelTextByPinId[a] = netLabelText
135
+ netLabelTextByPinId[b] = netLabelText
136
+ }
127
137
  }
128
138
  for (const nc of this.inputProblem.netConnections) {
129
139
  for (const pid of nc.pinIds) {
130
140
  userNetIdByPinId[pid] = nc.netId
141
+ const netLabelText = nc.netLabelText?.trim()
142
+ if (netLabelText) netLabelTextByPinId[pid] = netLabelText
131
143
  }
132
144
  }
133
145
 
@@ -237,6 +249,9 @@ export class NetLabelPlacementSolver extends BaseSolver {
237
249
  const group = {
238
250
  globalConnNetId,
239
251
  netId: userNetId,
252
+ netLabelText: [...component]
253
+ .map((pinId) => netLabelTextByPinId[pinId])
254
+ .find((text): text is string => Boolean(text)),
240
255
  overlappingTraces: rep,
241
256
  mspConnectionPairIds,
242
257
  }
@@ -249,6 +264,7 @@ export class NetLabelPlacementSolver extends BaseSolver {
249
264
  groups.push({
250
265
  globalConnNetId,
251
266
  netId: userNetId,
267
+ netLabelText: netLabelTextByPinId[p],
252
268
  portOnlyPinId: p,
253
269
  })
254
270
  }
@@ -354,6 +354,7 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
354
354
  this.overlappingSameNetTraceGroup.globalConnNetId,
355
355
  dcConnNetId: curr.dcConnNetId,
356
356
  netId: this.overlappingSameNetTraceGroup.netId,
357
+ netLabelText: this.overlappingSameNetTraceGroup.netLabelText,
357
358
  mspConnectionPairIds: [curr.mspPairId],
358
359
  pinIds: [curr.pins[0].pinId, curr.pins[1].pinId],
359
360
  orientation,
@@ -374,6 +375,7 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
374
375
  globalConnNetId: this.overlappingSameNetTraceGroup.globalConnNetId,
375
376
  dcConnNetId: bestCandidate.dcConnNetId,
376
377
  netId: this.overlappingSameNetTraceGroup.netId,
378
+ netLabelText: this.overlappingSameNetTraceGroup.netLabelText,
377
379
  mspConnectionPairIds: [bestCandidate.mspPairId],
378
380
  pinIds: bestCandidate.pinIds,
379
381
  orientation: bestCandidate.orientation,
@@ -195,6 +195,7 @@ export function solveNetLabelPlacementForPortOnlyPin(params: {
195
195
  globalConnNetId: overlappingSameNetTraceGroup.globalConnNetId,
196
196
  dcConnNetId: undefined,
197
197
  netId: overlappingSameNetTraceGroup.netId,
198
+ netLabelText: overlappingSameNetTraceGroup.netLabelText,
198
199
  mspConnectionPairIds: [],
199
200
  pinIds: [pinId],
200
201
  orientation,
@@ -230,6 +231,7 @@ export function solveNetLabelPlacementForPortOnlyPin(params: {
230
231
  globalConnNetId: overlappingSameNetTraceGroup.globalConnNetId,
231
232
  dcConnNetId: undefined,
232
233
  netId: overlappingSameNetTraceGroup.netId,
234
+ netLabelText: overlappingSameNetTraceGroup.netLabelText,
233
235
  mspConnectionPairIds: [],
234
236
  pinIds: [pinId],
235
237
  orientation: fallbackOrientation,