@tscircuit/schematic-trace-solver 0.0.125 → 0.0.127

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 (28) hide show
  1. package/README.md +28 -0
  2. package/dist/index.d.ts +159 -2
  3. package/dist/index.js +739 -36
  4. package/lib/index.ts +2 -0
  5. package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +424 -0
  6. package/lib/solvers/InlineNetLabelSolver/getAxisAlignedSegments.ts +73 -0
  7. package/lib/solvers/NetLabelTraceCollisionSolver/NetLabelTraceCollisionSolver.ts +15 -0
  8. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +86 -11
  9. package/lib/solvers/TraceElbowTransitionSimplificationSolver/TraceElbowTransitionSimplificationSolver.ts +216 -0
  10. package/lib/solvers/TraceElbowTransitionSimplificationSolver/generateElbowTransitionSimplificationCandidates.ts +186 -0
  11. package/lib/solvers/TraceElbowTransitionSimplificationSolver/types.ts +10 -0
  12. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +3 -0
  13. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +16 -0
  14. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +56 -13
  15. package/lib/types/InputProblem.ts +24 -0
  16. package/package.json +1 -1
  17. package/site/bug-reports/bug-report-20260806T093501Z.page.tsx +4 -0
  18. package/site/examples/inline-net-label01.page.tsx +6 -0
  19. package/tests/assets/inline-net-label01.json +47 -0
  20. package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +3 -3
  21. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +6 -6
  22. package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +135 -0
  23. package/tests/bug-reports/bug-report-20260806T093501Z/bug-report-20260806T093501Z.json +505 -0
  24. package/tests/bug-reports/bug-report-20260806T093501Z/bug-report-20260806T093501Z.test.ts +66 -0
  25. package/tests/examples/__snapshots__/example33.snap.svg +1 -1
  26. package/tests/functions/getAxisAlignedSegments.test.ts +56 -0
  27. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label01.snap.svg +54 -0
  28. package/tests/solvers/InlineNetLabelSolver/inline-net-label01.test.ts +44 -0
package/lib/index.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export * from "./solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
2
2
  export * from "./types/InputProblem"
3
+ export * from "./solvers/InlineNetLabelSolver/InlineNetLabelSolver"
4
+ export type { NetLabelPlacement } from "./solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
5
  export { SchematicTraceSingleLineSolver2 } from "./solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2"
@@ -0,0 +1,424 @@
1
+ import type { Bounds, Point } from "@tscircuit/math-utils"
2
+ import type { GraphicsObject, Rect } from "graphics-debug"
3
+ import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
4
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
5
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
6
+ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
7
+ import type {
8
+ InputDirectConnection,
9
+ InputProblem,
10
+ PinId,
11
+ } from "lib/types/InputProblem"
12
+ import { getColorFromString } from "lib/utils/getColorFromString"
13
+ import { boundsOverlap, getTextBoxBounds } from "lib/utils/textBoxBounds"
14
+ import {
15
+ type AxisAlignedSegment,
16
+ getAxisAlignedSegments,
17
+ } from "./getAxisAlignedSegments"
18
+
19
+ export const DEFAULT_INLINE_NET_LABEL_HEIGHT = 0.18
20
+
21
+ /**
22
+ * Gap between the trace and the near edge of the inline label text.
23
+ */
24
+ export const INLINE_NET_LABEL_TRACE_MARGIN = 0.05
25
+
26
+ /**
27
+ * How much of the label is allowed to hang off the end of the wire it names,
28
+ * as a fraction of the label's length. A short elbow stub is technically clear
29
+ * of every obstacle but reads as a label floating in space, so runs shorter
30
+ * than this are not considered.
31
+ */
32
+ export const MIN_INLINE_NET_LABEL_SEGMENT_RATIO = 0.5
33
+
34
+ /**
35
+ * A net label drawn parallel to the trace it names, rather than anchored to the
36
+ * end of it. Used for point-to-point signal traces, where an anchored label
37
+ * would break the visual line between the two pins.
38
+ *
39
+ * `center` is the center of the text box in the schematic's own (unrotated)
40
+ * coordinate space. `width` is the extent of the text along the trace and
41
+ * `height` is its extent perpendicular to the trace, so a vertical label has
42
+ * its `width` running along y.
43
+ */
44
+ export interface InlineNetLabelPlacement {
45
+ globalConnNetId: string
46
+ netId?: string
47
+ mspPairId: string
48
+ pinIds: PinId[]
49
+
50
+ /** Axis the text runs along: "x" reads left-to-right, "y" reads bottom-to-top */
51
+ axis: "x" | "y"
52
+
53
+ /** Midpoint of the trace segment the label is attached to */
54
+ anchorPoint: Point
55
+
56
+ /** Center of the label text, offset perpendicular to the trace */
57
+ center: Point
58
+
59
+ /** Extent along the trace */
60
+ width: number
61
+ /** Extent perpendicular to the trace */
62
+ height: number
63
+
64
+ /**
65
+ * Which side of the trace the label sits on. For a horizontal trace, "y+" is
66
+ * above; for a vertical trace, "x-" is the left side (which is "above" once
67
+ * the text is rotated).
68
+ */
69
+ side: "x+" | "x-" | "y+" | "y-"
70
+ }
71
+
72
+ interface InlineNetLabelSolverInput {
73
+ inputProblem: InputProblem
74
+ traces: SolvedTracePath[]
75
+ netLabelPlacements: NetLabelPlacement[]
76
+ }
77
+
78
+ const getPinPairKey = (pinIds: readonly string[]) =>
79
+ [...pinIds].sort().join("::")
80
+
81
+ /**
82
+ * Places "inline net labels" - net names drawn alongside the trace they belong
83
+ * to - for direct connections that opted in via `allowInlineNetLabel`.
84
+ *
85
+ * Any regular (anchored) net label placement for the same net is dropped, so a
86
+ * net is never labeled twice.
87
+ */
88
+ export class InlineNetLabelSolver extends BaseSolver {
89
+ inputProblem: InputProblem
90
+ traces: SolvedTracePath[]
91
+ inputNetLabelPlacements: NetLabelPlacement[]
92
+
93
+ inlineNetLabelPlacements: InlineNetLabelPlacement[] = []
94
+
95
+ /** Direct connections that opted in, still waiting to be processed */
96
+ queuedDirectConnections: InputDirectConnection[]
97
+
98
+ private tracesByPinPairKey: Map<string, SolvedTracePath[]>
99
+
100
+ constructor(input: InlineNetLabelSolverInput) {
101
+ super()
102
+ this.inputProblem = input.inputProblem
103
+ this.traces = input.traces
104
+ this.inputNetLabelPlacements = input.netLabelPlacements
105
+
106
+ this.queuedDirectConnections = this.inputProblem.directConnections.filter(
107
+ (dc) => dc.allowInlineNetLabel && dc.netId,
108
+ )
109
+
110
+ this.tracesByPinPairKey = new Map()
111
+ for (const trace of this.traces) {
112
+ const key = getPinPairKey(trace.pins.map((p) => p.pinId))
113
+ const existing = this.tracesByPinPairKey.get(key)
114
+ if (existing) {
115
+ existing.push(trace)
116
+ } else {
117
+ this.tracesByPinPairKey.set(key, [trace])
118
+ }
119
+ }
120
+ }
121
+
122
+ override getConstructorParams(): [InlineNetLabelSolverInput] {
123
+ return [
124
+ {
125
+ inputProblem: this.inputProblem,
126
+ traces: this.traces,
127
+ netLabelPlacements: this.inputNetLabelPlacements,
128
+ },
129
+ ]
130
+ }
131
+
132
+ override _step() {
133
+ const directConnection = this.queuedDirectConnections.shift()
134
+ if (!directConnection) {
135
+ this.solved = true
136
+ this.stats.inlineNetLabelCount = this.inlineNetLabelPlacements.length
137
+ return
138
+ }
139
+
140
+ const placement = this.computeInlinePlacement(directConnection)
141
+ if (placement) {
142
+ this.inlineNetLabelPlacements.push(placement)
143
+ }
144
+ }
145
+
146
+ private computeInlinePlacement(
147
+ directConnection: InputDirectConnection,
148
+ ): InlineNetLabelPlacement | null {
149
+ // Only connections the router actually drew a trace for can carry an inline
150
+ // label - there's nothing to run parallel to otherwise.
151
+ const traces =
152
+ this.tracesByPinPairKey.get(getPinPairKey(directConnection.pinIds)) ?? []
153
+ if (traces.length === 0) return null
154
+
155
+ const trace = traces[0]!
156
+ const segments = getAxisAlignedSegments(trace.tracePath)
157
+ if (segments.length === 0) return null
158
+
159
+ const height =
160
+ directConnection.inlineNetLabelHeight ?? DEFAULT_INLINE_NET_LABEL_HEIGHT
161
+ const width =
162
+ directConnection.inlineNetLabelWidth ??
163
+ directConnection.netLabelWidth ??
164
+ estimateInlineNetLabelWidth(directConnection.netId!, height)
165
+
166
+ const offset = height / 2 + INLINE_NET_LABEL_TRACE_MARGIN
167
+
168
+ // Runs the label fully fits on come first, then longer-to-shorter among the
169
+ // runs it may overhang. Within a run, try the preferred side before the far
170
+ // side, and positions near the middle before ones near the ends.
171
+ const usableSegments = segments
172
+ .filter(
173
+ (segment) =>
174
+ segment.length >= width * MIN_INLINE_NET_LABEL_SEGMENT_RATIO,
175
+ )
176
+ .sort((a, b) => {
177
+ const aFits = a.length >= width
178
+ const bFits = b.length >= width
179
+ if (aFits !== bFits) return aFits ? -1 : 1
180
+ return b.length - a.length
181
+ })
182
+
183
+ for (const segment of usableSegments) {
184
+ // "Above" the trace: +y for a horizontal trace, and -x for a vertical one
185
+ // (rotating the text 90deg counter-clockwise maps "above" onto the left).
186
+ const sides: InlineNetLabelPlacement["side"][] =
187
+ segment.axis === "x" ? ["y+", "y-"] : ["x-", "x+"]
188
+
189
+ for (const side of sides) {
190
+ for (const anchorPoint of getAnchorCandidates(segment, width)) {
191
+ const center =
192
+ side === "y+"
193
+ ? { x: anchorPoint.x, y: anchorPoint.y + offset }
194
+ : side === "y-"
195
+ ? { x: anchorPoint.x, y: anchorPoint.y - offset }
196
+ : side === "x-"
197
+ ? { x: anchorPoint.x - offset, y: anchorPoint.y }
198
+ : { x: anchorPoint.x + offset, y: anchorPoint.y }
199
+
200
+ const halfAlong = width / 2
201
+ const halfAcross = height / 2
202
+ const bounds: Bounds =
203
+ segment.axis === "x"
204
+ ? {
205
+ minX: center.x - halfAlong,
206
+ maxX: center.x + halfAlong,
207
+ minY: center.y - halfAcross,
208
+ maxY: center.y + halfAcross,
209
+ }
210
+ : {
211
+ minX: center.x - halfAcross,
212
+ maxX: center.x + halfAcross,
213
+ minY: center.y - halfAlong,
214
+ maxY: center.y + halfAlong,
215
+ }
216
+
217
+ if (this.isObstructed(bounds, trace)) continue
218
+
219
+ return {
220
+ globalConnNetId: trace.globalConnNetId,
221
+ netId: directConnection.netId,
222
+ mspPairId: trace.mspPairId,
223
+ pinIds: [...directConnection.pinIds],
224
+ axis: segment.axis,
225
+ anchorPoint,
226
+ center,
227
+ width,
228
+ height,
229
+ side,
230
+ }
231
+ }
232
+ }
233
+ }
234
+
235
+ // No room anywhere along the trace. Leave the net to the regular anchored
236
+ // net label rather than drawing the name over a chip.
237
+ return null
238
+ }
239
+
240
+ /**
241
+ * An inline label may not sit on top of a chip, a component's text, or a
242
+ * trace belonging to another net.
243
+ */
244
+ private isObstructed(bounds: Bounds, ownTrace: SolvedTracePath): boolean {
245
+ for (const chip of this.inputProblem.chips) {
246
+ const chipBounds: Bounds = {
247
+ minX: chip.center.x - chip.width / 2,
248
+ maxX: chip.center.x + chip.width / 2,
249
+ minY: chip.center.y - chip.height / 2,
250
+ maxY: chip.center.y + chip.height / 2,
251
+ }
252
+ if (boundsOverlap(bounds, chipBounds)) return true
253
+ }
254
+
255
+ for (const textBox of this.inputProblem.textBoxes ?? []) {
256
+ if (boundsOverlap(bounds, getTextBoxBounds(textBox))) return true
257
+ }
258
+
259
+ for (const trace of this.traces) {
260
+ if (trace.mspPairId === ownTrace.mspPairId) continue
261
+ if (trace.globalConnNetId === ownTrace.globalConnNetId) continue
262
+ if (doesPathIntersectBounds(trace.tracePath, bounds)) return true
263
+ }
264
+
265
+ return false
266
+ }
267
+
268
+ /**
269
+ * Net label placements superseded by an inline label. A net gets one label or
270
+ * the other, never both.
271
+ */
272
+ private getSupersededNetLabelKeys(): Set<string> {
273
+ const keys = new Set<string>()
274
+ for (const placement of this.inlineNetLabelPlacements) {
275
+ keys.add(placement.globalConnNetId)
276
+ }
277
+ return keys
278
+ }
279
+
280
+ getOutput() {
281
+ const superseded = this.getSupersededNetLabelKeys()
282
+ return {
283
+ traces: this.traces,
284
+ netLabelPlacements: this.inputNetLabelPlacements.filter(
285
+ (placement) => !superseded.has(placement.globalConnNetId),
286
+ ),
287
+ inlineNetLabelPlacements: this.inlineNetLabelPlacements,
288
+ }
289
+ }
290
+
291
+ override visualize(): GraphicsObject {
292
+ // Mirrors the previous pipeline stage's visualization so that a problem
293
+ // with no inline labels renders identically, then layers the inline labels
294
+ // on top.
295
+ const graphics = visualizeInputProblem(this.inputProblem)
296
+ graphics.lines ??= []
297
+ graphics.rects ??= []
298
+ graphics.points ??= []
299
+ graphics.texts ??= []
300
+
301
+ for (const trace of this.traces) {
302
+ graphics.lines.push({
303
+ points: trace.tracePath,
304
+ strokeColor: "purple",
305
+ })
306
+ }
307
+
308
+ const { netLabelPlacements } = this.getOutput()
309
+ for (const label of netLabelPlacements) {
310
+ graphics.rects.push({
311
+ center: label.center,
312
+ width: label.width,
313
+ height: label.height,
314
+ fill: getColorFromString(label.globalConnNetId, 0.35),
315
+ strokeColor: getColorFromString(label.globalConnNetId, 0.9),
316
+ label: `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`,
317
+ } as Rect & { strokeColor: string })
318
+ graphics.points.push({
319
+ x: label.anchorPoint.x,
320
+ y: label.anchorPoint.y,
321
+ color: getColorFromString(label.globalConnNetId, 0.9),
322
+ label: `anchorPoint\norientation: ${label.orientation}`,
323
+ })
324
+ }
325
+
326
+ for (const inlineLabel of this.inlineNetLabelPlacements) {
327
+ const isHorizontal = inlineLabel.axis === "x"
328
+ graphics.rects.push({
329
+ center: inlineLabel.center,
330
+ width: isHorizontal ? inlineLabel.width : inlineLabel.height,
331
+ height: isHorizontal ? inlineLabel.height : inlineLabel.width,
332
+ fill: getColorFromString(inlineLabel.globalConnNetId, 0.35),
333
+ strokeColor: "green",
334
+ label: [
335
+ `INLINE netId: ${inlineLabel.netId}`,
336
+ `axis: ${inlineLabel.axis}`,
337
+ `side: ${inlineLabel.side}`,
338
+ ].join("\n"),
339
+ } as Rect & { strokeColor: string })
340
+ graphics.texts.push({
341
+ x: inlineLabel.center.x,
342
+ y: inlineLabel.center.y,
343
+ text: inlineLabel.netId ?? "",
344
+ color: "green",
345
+ fontSize: inlineLabel.height,
346
+ anchorSide: "center",
347
+ })
348
+ graphics.points.push({
349
+ x: inlineLabel.anchorPoint.x,
350
+ y: inlineLabel.anchorPoint.y,
351
+ color: "green",
352
+ label: `inline anchor\n${inlineLabel.netId}`,
353
+ })
354
+ }
355
+
356
+ return graphics
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Mirrors the net label text width used by @tscircuit/core so a label that core
362
+ * did not measure for us still reserves a sane amount of space.
363
+ */
364
+ export const estimateInlineNetLabelWidth = (
365
+ text: string,
366
+ fontSize = DEFAULT_INLINE_NET_LABEL_HEIGHT,
367
+ ) => {
368
+ const fontScale = fontSize / 0.18
369
+ return text.length * 0.12 * fontScale + 0.12 * fontScale
370
+ }
371
+
372
+ /**
373
+ * Points along a segment where the label's midpoint could sit, ordered from the
374
+ * middle of the segment outwards. Sliding the label along the wire lets it
375
+ * dodge a chip that only crowds one end.
376
+ *
377
+ * When the label is shorter than the segment, candidates are limited to
378
+ * positions that keep it fully on the wire; when it is longer, only the
379
+ * midpoint is offered (it will overhang either way).
380
+ */
381
+ const getAnchorCandidates = (
382
+ segment: AxisAlignedSegment,
383
+ labelWidth: number,
384
+ step = 0.1,
385
+ ): Point[] => {
386
+ const along = segment.axis === "x" ? "x" : "y"
387
+ const start = segment.start[along]
388
+ const end = segment.end[along]
389
+ const mid = (start + end) / 2
390
+ const direction = Math.sign(end - start) || 1
391
+
392
+ const pointAt = (value: number): Point =>
393
+ segment.axis === "x"
394
+ ? { x: value, y: segment.start.y }
395
+ : { x: segment.start.x, y: segment.start.y + (value - start) }
396
+
397
+ const slack = segment.length - labelWidth
398
+ if (slack <= 0) return [pointAt(mid)]
399
+
400
+ const offsets: number[] = [0]
401
+ for (let offset = step; offset <= slack / 2 + 1e-9; offset += step) {
402
+ offsets.push(offset, -offset)
403
+ }
404
+ // Always consider both extremes, even when they fall between samples.
405
+ offsets.push(slack / 2, -slack / 2)
406
+
407
+ return offsets.map((offset) => pointAt(mid + offset * direction))
408
+ }
409
+
410
+ const doesPathIntersectBounds = (path: Point[], bounds: Bounds): boolean => {
411
+ for (let i = 0; i < path.length - 1; i++) {
412
+ const a = path[i]!
413
+ const b = path[i + 1]!
414
+ const segmentBounds: Bounds = {
415
+ minX: Math.min(a.x, b.x),
416
+ maxX: Math.max(a.x, b.x),
417
+ minY: Math.min(a.y, b.y),
418
+ maxY: Math.max(a.y, b.y),
419
+ }
420
+ // Trace segments are axis-aligned, so their bounding box is the segment.
421
+ if (boundsOverlap(segmentBounds, bounds)) return true
422
+ }
423
+ return false
424
+ }
@@ -0,0 +1,73 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+
3
+ export interface AxisAlignedSegment {
4
+ start: Point
5
+ end: Point
6
+ axis: "x" | "y"
7
+ length: number
8
+ }
9
+
10
+ /**
11
+ * Splits a trace path into its axis-aligned runs - the straight stretches of
12
+ * wire an inline net label can sit alongside without crossing a corner -
13
+ * ordered longest first.
14
+ *
15
+ * Segments that continue in the same direction are merged, so a straight
16
+ * stretch split into several points counts as one run. A reversal (a trace
17
+ * doubling back on itself) starts a new run, and diagonal segments are dropped
18
+ * since they have no well-defined "parallel" direction.
19
+ */
20
+ export const getAxisAlignedSegments = (
21
+ path: Point[],
22
+ epsilon = 1e-6,
23
+ ): AxisAlignedSegment[] => {
24
+ const segments: AxisAlignedSegment[] = []
25
+
26
+ let runStart: Point | null = null
27
+ let runAxis: "x" | "y" | null = null
28
+ let runSign = 0
29
+
30
+ const closeRun = (runEnd: Point) => {
31
+ if (runStart && runAxis) {
32
+ const length =
33
+ runAxis === "x"
34
+ ? Math.abs(runEnd.x - runStart.x)
35
+ : Math.abs(runEnd.y - runStart.y)
36
+ if (length > epsilon) {
37
+ segments.push({ start: runStart, end: runEnd, axis: runAxis, length })
38
+ }
39
+ }
40
+ runStart = null
41
+ runAxis = null
42
+ runSign = 0
43
+ }
44
+
45
+ for (let i = 0; i < path.length - 1; i++) {
46
+ const a = path[i]!
47
+ const b = path[i + 1]!
48
+ const dx = b.x - a.x
49
+ const dy = b.y - a.y
50
+
51
+ if (Math.abs(dx) <= epsilon && Math.abs(dy) <= epsilon) continue
52
+ const axis: "x" | "y" | null =
53
+ Math.abs(dy) <= epsilon ? "x" : Math.abs(dx) <= epsilon ? "y" : null
54
+ if (!axis) {
55
+ closeRun(a)
56
+ continue
57
+ }
58
+ const sign = Math.sign(axis === "x" ? dx : dy)
59
+
60
+ if (runStart && runAxis === axis && runSign === sign) continue
61
+
62
+ closeRun(a)
63
+ runStart = a
64
+ runAxis = axis
65
+ runSign = sign
66
+ }
67
+
68
+ if (path.length >= 2) {
69
+ closeRun(path[path.length - 1]!)
70
+ }
71
+
72
+ return segments.sort((a, b) => b.length - a.length)
73
+ }
@@ -12,6 +12,7 @@ import {
12
12
  import { segmentIntersectsRect } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions"
13
13
  import { getColorFromString } from "lib/utils/getColorFromString"
14
14
  import type { InputProblem } from "lib/types/InputProblem"
15
+ import type { CompletedTraceReroute } from "lib/solvers/TraceElbowTransitionSimplificationSolver/types"
15
16
 
16
17
  const ON_PATH_EPS = 1e-6
17
18
 
@@ -172,6 +173,7 @@ export class NetLabelTraceCollisionSolver extends BaseSolver {
172
173
 
173
174
  outputTraces: SolvedTracePath[]
174
175
  outputNetLabelPlacements: NetLabelPlacement[]
176
+ completedReroutes: CompletedTraceReroute[] = []
175
177
 
176
178
  override activeSubSolver: SingleOverlapSolver | null = null
177
179
  private recentlyFailed = new Set<string>()
@@ -208,6 +210,17 @@ export class NetLabelTraceCollisionSolver extends BaseSolver {
208
210
  if (this.activeSubSolver.solved) {
209
211
  const solvedPath = this.activeSubSolver.solvedTracePath
210
212
  if (solvedPath) {
213
+ this.completedReroutes.push({
214
+ initialTrace: {
215
+ ...this.activeSubSolver.initialTrace,
216
+ tracePath: this.activeSubSolver.initialTrace.tracePath.map(
217
+ (point) => ({ ...point }),
218
+ ),
219
+ },
220
+ reroutedTracePath: solvedPath.map((point) => ({ ...point })),
221
+ label: this.activeSubSolver.label,
222
+ detourCount: this.activeSubSolver.detourCount,
223
+ })
211
224
  const idx = this.outputTraces.findIndex(
212
225
  (t) => t.mspPairId === this.activeSubSolver!.initialTrace.mspPairId,
213
226
  )
@@ -282,6 +295,7 @@ export class NetLabelTraceCollisionSolver extends BaseSolver {
282
295
  paddingBuffer: PADDING_BUFFER,
283
296
  detourCount,
284
297
  tracesToAvoidOverlapping: this.outputTraces,
298
+ netLabelPlacements: this.outputNetLabelPlacements,
285
299
  })
286
300
  }
287
301
 
@@ -319,6 +333,7 @@ export class NetLabelTraceCollisionSolver extends BaseSolver {
319
333
  getOutput() {
320
334
  return {
321
335
  traces: this.outputTraces,
336
+ completedReroutes: this.completedReroutes,
322
337
  netLabelPlacements: this.outputNetLabelPlacements,
323
338
  }
324
339
  }
@@ -30,6 +30,8 @@ import { NetLabelTraceCollisionSolver } from "../NetLabelTraceCollisionSolver/Ne
30
30
  import { NetLabelNetLabelCollisionSolver } from "../NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver"
31
31
  import { UnroutedTraceRecoverySolver } from "../UnroutedTraceRecoverySolver/UnroutedTraceRecoverySolver"
32
32
  import { SameNetJunctionAlignmentSolver } from "../SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver"
33
+ import { TraceElbowTransitionSimplificationSolver } from "../TraceElbowTransitionSimplificationSolver/TraceElbowTransitionSimplificationSolver"
34
+ import { InlineNetLabelSolver } from "../InlineNetLabelSolver/InlineNetLabelSolver"
33
35
 
34
36
  type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
35
37
  solverName: string
@@ -88,7 +90,11 @@ export class SchematicTracePipelineSolver extends BaseSolver {
88
90
  netLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver
89
91
  traceCleanupSolver2?: TraceCleanupSolver
90
92
  netLabelNetLabelCollisionSolver?: NetLabelNetLabelCollisionSolver
93
+ traceElbowTransitionSimplificationSolver?: TraceElbowTransitionSimplificationSolver
94
+ preAlignmentTraceElbowTransitionSimplificationSolver?: TraceElbowTransitionSimplificationSolver
95
+ finalTraceElbowTransitionSimplificationSolver?: TraceElbowTransitionSimplificationSolver
91
96
  sameNetJunctionAlignmentSolver?: SameNetJunctionAlignmentSolver
97
+ inlineNetLabelSolver?: InlineNetLabelSolver
92
98
 
93
99
  startTimeOfPhase: Record<string, number>
94
100
  endTimeOfPhase: Record<string, number>
@@ -224,9 +230,27 @@ export class SchematicTracePipelineSolver extends BaseSolver {
224
230
  ]
225
231
  },
226
232
  ),
233
+ definePipelineStep(
234
+ "traceElbowTransitionSimplificationSolver",
235
+ TraceElbowTransitionSimplificationSolver,
236
+ (instance) => {
237
+ const overlapAvoidanceOutput =
238
+ instance.traceLabelOverlapAvoidanceSolver!.getOutput()
239
+ return [
240
+ {
241
+ inputProblem: instance.inputProblem,
242
+ traces: overlapAvoidanceOutput.traces,
243
+ completedReroutes: overlapAvoidanceOutput.completedReroutes,
244
+ netLabelPlacements:
245
+ instance.traceLabelOverlapAvoidanceSolver!.netLabelPlacements,
246
+ paddingBuffer: 0.1,
247
+ },
248
+ ]
249
+ },
250
+ ),
227
251
  definePipelineStep("traceCleanupSolver", TraceCleanupSolver, (instance) => {
228
252
  const prevSolverOutput =
229
- instance.traceLabelOverlapAvoidanceSolver!.getOutput()
253
+ instance.traceElbowTransitionSimplificationSolver!.getOutput()
230
254
  const traces = prevSolverOutput.traces
231
255
 
232
256
  const labelMergingOutput =
@@ -348,12 +372,29 @@ export class SchematicTracePipelineSolver extends BaseSolver {
348
372
  },
349
373
  ],
350
374
  ),
375
+ definePipelineStep(
376
+ "preAlignmentTraceElbowTransitionSimplificationSolver",
377
+ TraceElbowTransitionSimplificationSolver,
378
+ (instance) => {
379
+ const collisionOutput =
380
+ instance.preAlignmentNetLabelTraceCollisionSolver!.getOutput()
381
+ return [
382
+ {
383
+ inputProblem: instance.inputProblem,
384
+ traces: collisionOutput.traces,
385
+ completedReroutes: collisionOutput.completedReroutes,
386
+ netLabelPlacements: collisionOutput.netLabelPlacements,
387
+ paddingBuffer: 0.1,
388
+ },
389
+ ]
390
+ },
391
+ ),
351
392
  definePipelineStep(
352
393
  "traceCleanupSolver2",
353
394
  TraceCleanupSolver,
354
395
  (instance) => {
355
396
  const collisionOutput =
356
- instance.preAlignmentNetLabelTraceCollisionSolver!.getOutput()
397
+ instance.preAlignmentTraceElbowTransitionSimplificationSolver!.getOutput()
357
398
  const labelMergingOutput =
358
399
  instance.traceLabelOverlapAvoidanceSolver!.labelMergingSolver!.getOutput()
359
400
 
@@ -416,18 +457,37 @@ export class SchematicTracePipelineSolver extends BaseSolver {
416
457
  ]
417
458
  },
418
459
  ),
460
+ definePipelineStep(
461
+ "finalTraceElbowTransitionSimplificationSolver",
462
+ TraceElbowTransitionSimplificationSolver,
463
+ (instance) => {
464
+ const collisionOutput =
465
+ instance.netLabelTraceCollisionSolver!.getOutput()
466
+ return [
467
+ {
468
+ inputProblem: instance.inputProblem,
469
+ traces: collisionOutput.traces,
470
+ completedReroutes: collisionOutput.completedReroutes,
471
+ netLabelPlacements: collisionOutput.netLabelPlacements,
472
+ paddingBuffer: 0.1,
473
+ },
474
+ ]
475
+ },
476
+ ),
419
477
  definePipelineStep(
420
478
  "netLabelNetLabelCollisionSolver",
421
479
  NetLabelNetLabelCollisionSolver,
422
- (instance) => [
423
- {
424
- inputProblem: instance.inputProblem,
425
- traces: instance.netLabelTraceCollisionSolver!.getOutput().traces,
426
- netLabelPlacements:
427
- instance.netLabelTraceCollisionSolver!.getOutput()
428
- .netLabelPlacements,
429
- },
430
- ],
480
+ (instance) => {
481
+ const simplificationOutput =
482
+ instance.finalTraceElbowTransitionSimplificationSolver!.getOutput()
483
+ return [
484
+ {
485
+ inputProblem: instance.inputProblem,
486
+ traces: simplificationOutput.traces,
487
+ netLabelPlacements: simplificationOutput.netLabelPlacements,
488
+ },
489
+ ]
490
+ },
431
491
  ),
432
492
  definePipelineStep(
433
493
  "sameNetJunctionAlignmentSolver",
@@ -444,6 +504,21 @@ export class SchematicTracePipelineSolver extends BaseSolver {
444
504
  ]
445
505
  },
446
506
  ),
507
+ definePipelineStep(
508
+ "inlineNetLabelSolver",
509
+ InlineNetLabelSolver,
510
+ (instance) => {
511
+ const junctionOutput =
512
+ instance.sameNetJunctionAlignmentSolver!.getOutput()
513
+ return [
514
+ {
515
+ inputProblem: instance.inputProblem,
516
+ traces: junctionOutput.traces,
517
+ netLabelPlacements: junctionOutput.netLabelPlacements,
518
+ },
519
+ ]
520
+ },
521
+ ),
447
522
  ]
448
523
 
449
524
  constructor(inputProblem: InputProblem, opts?: Options) {