@tscircuit/schematic-trace-solver 0.0.55 → 0.0.57

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 CHANGED
@@ -820,6 +820,32 @@ declare class TraceAnchoredNetLabelOverlapSolver extends BaseSolver {
820
820
  visualize(): GraphicsObject;
821
821
  }
822
822
 
823
+ interface NetLabelTraceCollisionSolverParams {
824
+ inputProblem: InputProblem;
825
+ traces: SolvedTracePath[];
826
+ netLabelPlacements: NetLabelPlacement[];
827
+ }
828
+ declare class NetLabelTraceCollisionSolver extends BaseSolver {
829
+ inputProblem: InputProblem;
830
+ traces: SolvedTracePath[];
831
+ netLabelPlacements: NetLabelPlacement[];
832
+ outputTraces: SolvedTracePath[];
833
+ outputNetLabelPlacements: NetLabelPlacement[];
834
+ activeSubSolver: SingleOverlapSolver | null;
835
+ private recentlyFailed;
836
+ private detourCounts;
837
+ private currentOverlap;
838
+ constructor(params: NetLabelTraceCollisionSolverParams);
839
+ getConstructorParams(): ConstructorParameters<typeof NetLabelTraceCollisionSolver>[0];
840
+ _step(): void;
841
+ getOutput(): {
842
+ traces: SolvedTracePath[];
843
+ netLabelPlacements: NetLabelPlacement[];
844
+ };
845
+ private getOverlapKey;
846
+ visualize(): GraphicsObject;
847
+ }
848
+
823
849
  /**
824
850
  * Pipeline solver that runs a series of solvers to find the best schematic layout.
825
851
  * Coordinates the entire layout process from chip partitioning through final packing.
@@ -849,12 +875,13 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
849
875
  availableNetOrientationSolver?: AvailableNetOrientationSolver;
850
876
  vccNetLabelCornerPlacementSolver?: VccNetLabelCornerPlacementSolver;
851
877
  traceAnchoredNetLabelOverlapSolver?: TraceAnchoredNetLabelOverlapSolver;
878
+ netLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver;
852
879
  startTimeOfPhase: Record<string, number>;
853
880
  endTimeOfPhase: Record<string, number>;
854
881
  timeSpentOnPhase: Record<string, number>;
855
882
  firstIterationOfPhase: Record<string, number>;
856
883
  inputProblem: InputProblem;
857
- pipelineDef: (PipelineStep<typeof MspConnectionPairSolver> | PipelineStep<typeof SchematicTraceLinesSolver> | PipelineStep<typeof LongDistancePairSolver> | PipelineStep<typeof TraceOverlapShiftSolver> | PipelineStep<typeof NetLabelPlacementSolver> | PipelineStep<typeof TraceLabelOverlapAvoidanceSolver> | PipelineStep<typeof TraceCleanupSolver> | PipelineStep<typeof Example28Solver> | PipelineStep<typeof AvailableNetOrientationSolver> | PipelineStep<typeof VccNetLabelCornerPlacementSolver> | PipelineStep<typeof TraceAnchoredNetLabelOverlapSolver>)[];
884
+ pipelineDef: (PipelineStep<typeof MspConnectionPairSolver> | PipelineStep<typeof SchematicTraceLinesSolver> | PipelineStep<typeof LongDistancePairSolver> | PipelineStep<typeof TraceOverlapShiftSolver> | PipelineStep<typeof NetLabelPlacementSolver> | PipelineStep<typeof TraceLabelOverlapAvoidanceSolver> | PipelineStep<typeof TraceCleanupSolver> | PipelineStep<typeof Example28Solver> | PipelineStep<typeof AvailableNetOrientationSolver> | PipelineStep<typeof VccNetLabelCornerPlacementSolver> | PipelineStep<typeof TraceAnchoredNetLabelOverlapSolver> | PipelineStep<typeof NetLabelTraceCollisionSolver>)[];
858
885
  constructor(inputProblem: InputProblem);
859
886
  getConstructorParams(): ConstructorParameters<typeof SchematicTracePipelineSolver>[0];
860
887
  currentPipelineStepIndex: number;
package/dist/index.js CHANGED
@@ -5487,6 +5487,14 @@ var segmentCrossesBoundsInterior = (p1, p2, bounds) => {
5487
5487
  }
5488
5488
  return false;
5489
5489
  };
5490
+ var tracePathCrossesAnyBounds = (tracePath, bounds) => {
5491
+ for (let i = 0; i < tracePath.length - 1; i++) {
5492
+ if (segmentCrossesBoundsInterior(tracePath[i], tracePath[i + 1], bounds)) {
5493
+ return true;
5494
+ }
5495
+ }
5496
+ return false;
5497
+ };
5490
5498
  var tracePathCrossesAnyTrace = (tracePath, traceMap) => {
5491
5499
  for (const trace of Object.values(traceMap)) {
5492
5500
  const points = trace.tracePath;
@@ -5968,16 +5976,24 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
5968
5976
  labelIndex
5969
5977
  );
5970
5978
  if (boundsStatus !== "valid") return boundsStatus;
5971
- if (tracePathCrossesAnyTrace(
5972
- getConnectorTracePath(
5973
- label.anchorPoint,
5974
- candidate.anchorPoint,
5975
- candidate.orientation
5976
- ),
5977
- this.traceMap
5978
- )) {
5979
+ const connectorTrace = getConnectorTracePath(
5980
+ label.anchorPoint,
5981
+ candidate.anchorPoint,
5982
+ candidate.orientation
5983
+ );
5984
+ if (tracePathCrossesAnyTrace(connectorTrace, this.traceMap)) {
5979
5985
  return "trace-collision";
5980
5986
  }
5987
+ for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
5988
+ if (i === labelIndex) continue;
5989
+ const otherLabel = this.outputNetLabelPlacements[i];
5990
+ if (tracePathCrossesAnyBounds(
5991
+ connectorTrace,
5992
+ getRectBounds(otherLabel.center, otherLabel.width, otherLabel.height)
5993
+ )) {
5994
+ return "netlabel-collision";
5995
+ }
5996
+ }
5981
5997
  return "valid";
5982
5998
  }
5983
5999
  getBoundsStatus(bounds, labelIndex) {
@@ -7150,6 +7166,214 @@ var TraceAnchoredNetLabelOverlapSolver = class extends BaseSolver {
7150
7166
  }
7151
7167
  };
7152
7168
 
7169
+ // lib/solvers/NetLabelTraceCollisionSolver/NetLabelTraceCollisionSolver.ts
7170
+ function buildMergedObstacleLabel(seedLabel, allLabels) {
7171
+ const TOUCH_MARGIN = 0.01;
7172
+ const getBounds3 = (l) => getRectBounds(l.center, l.width, l.height);
7173
+ const touches = (a, b) => {
7174
+ const ba = getBounds3(a);
7175
+ const bb = getBounds3(b);
7176
+ return ba.minX <= bb.maxX + TOUCH_MARGIN && ba.maxX >= bb.minX - TOUCH_MARGIN && ba.minY <= bb.maxY + TOUCH_MARGIN && ba.maxY >= bb.minY - TOUCH_MARGIN;
7177
+ };
7178
+ const group = /* @__PURE__ */ new Set([seedLabel]);
7179
+ let changed = true;
7180
+ while (changed) {
7181
+ changed = false;
7182
+ for (const l of allLabels) {
7183
+ if (group.has(l)) continue;
7184
+ if ([...group].some((g) => touches(g, l))) {
7185
+ group.add(l);
7186
+ changed = true;
7187
+ }
7188
+ }
7189
+ }
7190
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
7191
+ for (const l of group) {
7192
+ const b = getBounds3(l);
7193
+ if (b.minX < minX) minX = b.minX;
7194
+ if (b.minY < minY) minY = b.minY;
7195
+ if (b.maxX > maxX) maxX = b.maxX;
7196
+ if (b.maxY > maxY) maxY = b.maxY;
7197
+ }
7198
+ const cx = (minX + maxX) / 2;
7199
+ const cy = (minY + maxY) / 2;
7200
+ const w = maxX - minX;
7201
+ const h = maxY - minY;
7202
+ return {
7203
+ ...seedLabel,
7204
+ center: { x: cx, y: cy },
7205
+ width: w,
7206
+ height: h
7207
+ };
7208
+ }
7209
+ function detectInnerTraceLabelOverlaps(traces, labels) {
7210
+ const overlaps = [];
7211
+ for (const trace of traces) {
7212
+ for (const label of labels) {
7213
+ if (trace.globalConnNetId === label.globalConnNetId) continue;
7214
+ const bounds = getRectBounds(label.center, label.width, label.height);
7215
+ const path = trace.tracePath;
7216
+ const innerStart = 1;
7217
+ const innerEnd = path.length - 2;
7218
+ for (let i = innerStart; i < innerEnd; i++) {
7219
+ if (segmentIntersectsRect2(path[i], path[i + 1], bounds)) {
7220
+ overlaps.push({ trace, label });
7221
+ break;
7222
+ }
7223
+ }
7224
+ }
7225
+ }
7226
+ return overlaps;
7227
+ }
7228
+ var PADDING_BUFFER = 0.1;
7229
+ var MAX_DETOUR_ATTEMPTS = 3;
7230
+ var NetLabelTraceCollisionSolver = class extends BaseSolver {
7231
+ inputProblem;
7232
+ traces;
7233
+ netLabelPlacements;
7234
+ outputTraces;
7235
+ outputNetLabelPlacements;
7236
+ activeSubSolver = null;
7237
+ recentlyFailed = /* @__PURE__ */ new Set();
7238
+ detourCounts = /* @__PURE__ */ new Map();
7239
+ currentOverlap = null;
7240
+ constructor(params) {
7241
+ super();
7242
+ this.inputProblem = params.inputProblem;
7243
+ this.traces = params.traces;
7244
+ this.netLabelPlacements = params.netLabelPlacements;
7245
+ this.outputTraces = [...params.traces];
7246
+ this.outputNetLabelPlacements = [...params.netLabelPlacements];
7247
+ }
7248
+ getConstructorParams() {
7249
+ return {
7250
+ inputProblem: this.inputProblem,
7251
+ traces: this.traces,
7252
+ netLabelPlacements: this.netLabelPlacements
7253
+ };
7254
+ }
7255
+ _step() {
7256
+ if (this.activeSubSolver) {
7257
+ this.activeSubSolver.step();
7258
+ if (this.activeSubSolver.solved) {
7259
+ const solvedPath = this.activeSubSolver.solvedTracePath;
7260
+ if (solvedPath) {
7261
+ const idx = this.outputTraces.findIndex(
7262
+ (t) => t.mspPairId === this.activeSubSolver.initialTrace.mspPairId
7263
+ );
7264
+ if (idx !== -1) {
7265
+ this.outputTraces[idx] = {
7266
+ ...this.outputTraces[idx],
7267
+ tracePath: solvedPath
7268
+ };
7269
+ }
7270
+ }
7271
+ this.activeSubSolver = null;
7272
+ this.currentOverlap = null;
7273
+ } else if (this.activeSubSolver.failed) {
7274
+ const key = this.getOverlapKey(
7275
+ this.activeSubSolver.initialTrace,
7276
+ this.activeSubSolver.label
7277
+ );
7278
+ this.recentlyFailed.add(key);
7279
+ this.activeSubSolver = null;
7280
+ this.currentOverlap = null;
7281
+ }
7282
+ return;
7283
+ }
7284
+ const overlaps = detectInnerTraceLabelOverlaps(
7285
+ this.outputTraces,
7286
+ this.outputNetLabelPlacements
7287
+ );
7288
+ const actionable = overlaps.filter((o) => {
7289
+ const key = this.getOverlapKey(o.trace, o.label);
7290
+ return !this.recentlyFailed.has(key);
7291
+ });
7292
+ if (actionable.length === 0) {
7293
+ this.solved = true;
7294
+ return;
7295
+ }
7296
+ const next = actionable[0];
7297
+ const traceToFix = this.outputTraces.find(
7298
+ (t) => t.mspPairId === next.trace.mspPairId
7299
+ );
7300
+ this.currentOverlap = next;
7301
+ const mergedLabel = buildMergedObstacleLabel(
7302
+ next.label,
7303
+ this.outputNetLabelPlacements.filter(
7304
+ (l) => l.globalConnNetId !== traceToFix.globalConnNetId
7305
+ )
7306
+ );
7307
+ const mergeKey = `${next.trace.mspPairId}::merged::${mergedLabel.center.x},${mergedLabel.center.y},${mergedLabel.width},${mergedLabel.height}`;
7308
+ const detourCount = this.detourCounts.get(mergeKey) ?? 0;
7309
+ if (detourCount >= MAX_DETOUR_ATTEMPTS) {
7310
+ this.recentlyFailed.add(this.getOverlapKey(next.trace, next.label));
7311
+ this.currentOverlap = null;
7312
+ return;
7313
+ }
7314
+ this.detourCounts.set(mergeKey, detourCount + 1);
7315
+ this.activeSubSolver = new SingleOverlapSolver({
7316
+ trace: traceToFix,
7317
+ label: mergedLabel,
7318
+ problem: this.inputProblem,
7319
+ paddingBuffer: PADDING_BUFFER,
7320
+ detourCount
7321
+ });
7322
+ }
7323
+ getOutput() {
7324
+ return {
7325
+ traces: this.outputTraces,
7326
+ netLabelPlacements: this.outputNetLabelPlacements
7327
+ };
7328
+ }
7329
+ getOverlapKey(trace, label) {
7330
+ return `${trace.mspPairId}::${label.globalConnNetId}`;
7331
+ }
7332
+ visualize() {
7333
+ if (this.activeSubSolver) return this.activeSubSolver.visualize();
7334
+ const graphics = visualizeInputProblem(this.inputProblem);
7335
+ if (!graphics.lines) graphics.lines = [];
7336
+ if (!graphics.rects) graphics.rects = [];
7337
+ if (!graphics.points) graphics.points = [];
7338
+ for (const trace of this.outputTraces) {
7339
+ graphics.lines.push({
7340
+ points: trace.tracePath,
7341
+ strokeColor: "purple"
7342
+ });
7343
+ }
7344
+ for (const label of this.outputNetLabelPlacements) {
7345
+ graphics.rects.push({
7346
+ center: label.center,
7347
+ width: label.width,
7348
+ height: label.height,
7349
+ fill: getColorFromString(label.globalConnNetId, 0.35),
7350
+ strokeColor: getColorFromString(label.globalConnNetId, 0.9),
7351
+ label: `netId: ${label.netId}
7352
+ globalConnNetId: ${label.globalConnNetId}`
7353
+ });
7354
+ graphics.points.push({
7355
+ x: label.anchorPoint.x,
7356
+ y: label.anchorPoint.y,
7357
+ color: getColorFromString(label.globalConnNetId, 0.9)
7358
+ });
7359
+ }
7360
+ if (this.currentOverlap) {
7361
+ graphics.lines.push({
7362
+ points: this.currentOverlap.trace.tracePath,
7363
+ strokeColor: "red"
7364
+ });
7365
+ graphics.rects.push({
7366
+ center: this.currentOverlap.label.center,
7367
+ width: this.currentOverlap.label.width,
7368
+ height: this.currentOverlap.label.height,
7369
+ fill: "rgba(255,0,0,0.3)",
7370
+ strokeColor: "red"
7371
+ });
7372
+ }
7373
+ return graphics;
7374
+ }
7375
+ };
7376
+
7153
7377
  // lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts
7154
7378
  function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
7155
7379
  return {
@@ -7174,6 +7398,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
7174
7398
  availableNetOrientationSolver;
7175
7399
  vccNetLabelCornerPlacementSolver;
7176
7400
  traceAnchoredNetLabelOverlapSolver;
7401
+ netLabelTraceCollisionSolver;
7177
7402
  startTimeOfPhase;
7178
7403
  endTimeOfPhase;
7179
7404
  timeSpentOnPhase;
@@ -7354,6 +7579,17 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
7354
7579
  netLabelPlacements: instance.vccNetLabelCornerPlacementSolver.outputNetLabelPlacements
7355
7580
  }
7356
7581
  ]
7582
+ ),
7583
+ definePipelineStep(
7584
+ "netLabelTraceCollisionSolver",
7585
+ NetLabelTraceCollisionSolver,
7586
+ (instance) => [
7587
+ {
7588
+ inputProblem: instance.inputProblem,
7589
+ traces: instance.availableNetOrientationSolver.traces,
7590
+ netLabelPlacements: instance.traceAnchoredNetLabelOverlapSolver.outputNetLabelPlacements
7591
+ }
7592
+ ]
7357
7593
  )
7358
7594
  ];
7359
7595
  constructor(inputProblem) {
@@ -21,6 +21,7 @@ import {
21
21
  rangesOverlap,
22
22
  rectsOverlap,
23
23
  traceCrossesBoundsInterior,
24
+ tracePathCrossesAnyBounds,
24
25
  tracePathCrossesAnyTrace,
25
26
  } from "./geometry"
26
27
  import { getPinMap, getTracePins, toNetLabelPlacementPatch } from "./traces"
@@ -447,19 +448,29 @@ export class AvailableNetOrientationSolver extends BaseSolver {
447
448
  )
448
449
  if (boundsStatus !== "valid") return boundsStatus
449
450
 
450
- if (
451
- tracePathCrossesAnyTrace(
452
- getConnectorTracePath(
453
- label.anchorPoint,
454
- candidate.anchorPoint,
455
- candidate.orientation,
456
- ),
457
- this.traceMap,
458
- )
459
- ) {
451
+ const connectorTrace = getConnectorTracePath(
452
+ label.anchorPoint,
453
+ candidate.anchorPoint,
454
+ candidate.orientation,
455
+ )
456
+
457
+ if (tracePathCrossesAnyTrace(connectorTrace, this.traceMap)) {
460
458
  return "trace-collision"
461
459
  }
462
460
 
461
+ for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
462
+ if (i === labelIndex) continue
463
+ const otherLabel = this.outputNetLabelPlacements[i]!
464
+ if (
465
+ tracePathCrossesAnyBounds(
466
+ connectorTrace,
467
+ getRectBounds(otherLabel.center, otherLabel.width, otherLabel.height),
468
+ )
469
+ ) {
470
+ return "netlabel-collision"
471
+ }
472
+ }
473
+
463
474
  return "valid"
464
475
  }
465
476
 
@@ -88,6 +88,20 @@ const segmentCrossesBoundsInterior = (p1: Point, p2: Point, bounds: Bounds) => {
88
88
  return false
89
89
  }
90
90
 
91
+ export const tracePathCrossesAnyBounds = (
92
+ tracePath: Point[],
93
+ bounds: Bounds,
94
+ ) => {
95
+ for (let i = 0; i < tracePath.length - 1; i++) {
96
+ if (
97
+ segmentCrossesBoundsInterior(tracePath[i]!, tracePath[i + 1]!, bounds)
98
+ ) {
99
+ return true
100
+ }
101
+ }
102
+ return false
103
+ }
104
+
91
105
  export const tracePathCrossesAnyTrace = (
92
106
  tracePath: Point[],
93
107
  traceMap: Record<string, SolvedTracePath>,
@@ -0,0 +1,294 @@
1
+ import type { GraphicsObject } from "graphics-debug"
2
+ import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
3
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
4
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
5
+ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
6
+ import { SingleOverlapSolver } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver"
7
+ import { getRectBounds } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
8
+ import { segmentIntersectsRect } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions"
9
+ import { getColorFromString } from "lib/utils/getColorFromString"
10
+ import type { InputProblem } from "lib/types/InputProblem"
11
+
12
+ /**
13
+ * Merges all labels that touch/overlap with each other (transitively) around
14
+ * a seed label into a single bounding-box label. This prevents the situation
15
+ * where fixing one label collision re-routes the trace into an adjacent label.
16
+ */
17
+ function buildMergedObstacleLabel(
18
+ seedLabel: NetLabelPlacement,
19
+ allLabels: NetLabelPlacement[],
20
+ ): NetLabelPlacement {
21
+ const TOUCH_MARGIN = 0.01
22
+
23
+ const getBounds = (l: NetLabelPlacement) =>
24
+ getRectBounds(l.center, l.width, l.height)
25
+
26
+ const touches = (a: NetLabelPlacement, b: NetLabelPlacement) => {
27
+ const ba = getBounds(a)
28
+ const bb = getBounds(b)
29
+ return (
30
+ ba.minX <= bb.maxX + TOUCH_MARGIN &&
31
+ ba.maxX >= bb.minX - TOUCH_MARGIN &&
32
+ ba.minY <= bb.maxY + TOUCH_MARGIN &&
33
+ ba.maxY >= bb.minY - TOUCH_MARGIN
34
+ )
35
+ }
36
+
37
+ const group = new Set<NetLabelPlacement>([seedLabel])
38
+ let changed = true
39
+ while (changed) {
40
+ changed = false
41
+ for (const l of allLabels) {
42
+ if (group.has(l)) continue
43
+ if ([...group].some((g) => touches(g, l))) {
44
+ group.add(l)
45
+ changed = true
46
+ }
47
+ }
48
+ }
49
+
50
+ let minX = Infinity,
51
+ minY = Infinity,
52
+ maxX = -Infinity,
53
+ maxY = -Infinity
54
+ for (const l of group) {
55
+ const b = getBounds(l)
56
+ if (b.minX < minX) minX = b.minX
57
+ if (b.minY < minY) minY = b.minY
58
+ if (b.maxX > maxX) maxX = b.maxX
59
+ if (b.maxY > maxY) maxY = b.maxY
60
+ }
61
+
62
+ const cx = (minX + maxX) / 2
63
+ const cy = (minY + maxY) / 2
64
+ const w = maxX - minX
65
+ const h = maxY - minY
66
+
67
+ return {
68
+ ...seedLabel,
69
+ center: { x: cx, y: cy },
70
+ width: w,
71
+ height: h,
72
+ }
73
+ }
74
+
75
+ interface TraceInnerLabelOverlap {
76
+ trace: SolvedTracePath
77
+ label: NetLabelPlacement
78
+ }
79
+
80
+ /**
81
+ * Detects overlaps where a trace's INTERIOR (non-endpoint) segments cross a label.
82
+ * Traces are allowed to boundary a label at their endpoints (pin locations),
83
+ * just not allowed to cross through it in the middle.
84
+ */
85
+ function detectInnerTraceLabelOverlaps(
86
+ traces: SolvedTracePath[],
87
+ labels: NetLabelPlacement[],
88
+ ): TraceInnerLabelOverlap[] {
89
+ const overlaps: TraceInnerLabelOverlap[] = []
90
+ for (const trace of traces) {
91
+ for (const label of labels) {
92
+ if (trace.globalConnNetId === label.globalConnNetId) continue
93
+ const bounds = getRectBounds(label.center, label.width, label.height)
94
+ const path = trace.tracePath
95
+ // Skip first and last segments — trace endpoints (pins) may legitimately
96
+ // sit inside or adjacent to labels of neighbouring nets.
97
+ const innerStart = 1
98
+ const innerEnd = path.length - 2
99
+ for (let i = innerStart; i < innerEnd; i++) {
100
+ if (segmentIntersectsRect(path[i]!, path[i + 1]!, bounds)) {
101
+ overlaps.push({ trace, label })
102
+ break
103
+ }
104
+ }
105
+ }
106
+ }
107
+ return overlaps
108
+ }
109
+
110
+ export interface NetLabelTraceCollisionSolverParams {
111
+ inputProblem: InputProblem
112
+ traces: SolvedTracePath[]
113
+ netLabelPlacements: NetLabelPlacement[]
114
+ }
115
+
116
+ const PADDING_BUFFER = 0.1
117
+ const MAX_DETOUR_ATTEMPTS = 3
118
+
119
+ export class NetLabelTraceCollisionSolver extends BaseSolver {
120
+ inputProblem: InputProblem
121
+ traces: SolvedTracePath[]
122
+ netLabelPlacements: NetLabelPlacement[]
123
+
124
+ outputTraces: SolvedTracePath[]
125
+ outputNetLabelPlacements: NetLabelPlacement[]
126
+
127
+ override activeSubSolver: SingleOverlapSolver | null = null
128
+ private recentlyFailed = new Set<string>()
129
+ private detourCounts = new Map<string, number>()
130
+
131
+ private currentOverlap: {
132
+ trace: SolvedTracePath
133
+ label: NetLabelPlacement
134
+ } | null = null
135
+
136
+ constructor(params: NetLabelTraceCollisionSolverParams) {
137
+ super()
138
+ this.inputProblem = params.inputProblem
139
+ this.traces = params.traces
140
+ this.netLabelPlacements = params.netLabelPlacements
141
+ this.outputTraces = [...params.traces]
142
+ this.outputNetLabelPlacements = [...params.netLabelPlacements]
143
+ }
144
+
145
+ override getConstructorParams(): ConstructorParameters<
146
+ typeof NetLabelTraceCollisionSolver
147
+ >[0] {
148
+ return {
149
+ inputProblem: this.inputProblem,
150
+ traces: this.traces,
151
+ netLabelPlacements: this.netLabelPlacements,
152
+ }
153
+ }
154
+
155
+ override _step() {
156
+ if (this.activeSubSolver) {
157
+ this.activeSubSolver.step()
158
+
159
+ if (this.activeSubSolver.solved) {
160
+ const solvedPath = this.activeSubSolver.solvedTracePath
161
+ if (solvedPath) {
162
+ const idx = this.outputTraces.findIndex(
163
+ (t) => t.mspPairId === this.activeSubSolver!.initialTrace.mspPairId,
164
+ )
165
+ if (idx !== -1) {
166
+ this.outputTraces[idx] = {
167
+ ...this.outputTraces[idx],
168
+ tracePath: solvedPath,
169
+ }
170
+ }
171
+ }
172
+ this.activeSubSolver = null
173
+ this.currentOverlap = null
174
+ } else if (this.activeSubSolver.failed) {
175
+ const key = this.getOverlapKey(
176
+ this.activeSubSolver.initialTrace,
177
+ this.activeSubSolver.label,
178
+ )
179
+ this.recentlyFailed.add(key)
180
+ this.activeSubSolver = null
181
+ this.currentOverlap = null
182
+ }
183
+ return
184
+ }
185
+
186
+ const overlaps = detectInnerTraceLabelOverlaps(
187
+ this.outputTraces,
188
+ this.outputNetLabelPlacements,
189
+ )
190
+
191
+ const actionable = overlaps.filter((o) => {
192
+ const key = this.getOverlapKey(o.trace, o.label)
193
+ return !this.recentlyFailed.has(key)
194
+ })
195
+
196
+ if (actionable.length === 0) {
197
+ this.solved = true
198
+ return
199
+ }
200
+
201
+ const next = actionable[0]
202
+
203
+ const traceToFix = this.outputTraces.find(
204
+ (t) => t.mspPairId === next.trace.mspPairId,
205
+ )!
206
+
207
+ this.currentOverlap = next
208
+
209
+ // Merge all touching/overlapping labels into one obstacle so that fixing
210
+ // one collision doesn't immediately create another with an adjacent label.
211
+ const mergedLabel = buildMergedObstacleLabel(
212
+ next.label,
213
+ this.outputNetLabelPlacements.filter(
214
+ (l) => l.globalConnNetId !== traceToFix.globalConnNetId,
215
+ ),
216
+ )
217
+
218
+ const mergeKey = `${next.trace.mspPairId}::merged::${mergedLabel.center.x},${mergedLabel.center.y},${mergedLabel.width},${mergedLabel.height}`
219
+ const detourCount = this.detourCounts.get(mergeKey) ?? 0
220
+ if (detourCount >= MAX_DETOUR_ATTEMPTS) {
221
+ this.recentlyFailed.add(this.getOverlapKey(next.trace, next.label))
222
+ this.currentOverlap = null
223
+ return
224
+ }
225
+ this.detourCounts.set(mergeKey, detourCount + 1)
226
+
227
+ this.activeSubSolver = new SingleOverlapSolver({
228
+ trace: traceToFix,
229
+ label: mergedLabel,
230
+ problem: this.inputProblem,
231
+ paddingBuffer: PADDING_BUFFER,
232
+ detourCount,
233
+ })
234
+ }
235
+
236
+ getOutput() {
237
+ return {
238
+ traces: this.outputTraces,
239
+ netLabelPlacements: this.outputNetLabelPlacements,
240
+ }
241
+ }
242
+
243
+ private getOverlapKey(trace: SolvedTracePath, label: NetLabelPlacement) {
244
+ return `${trace.mspPairId}::${label.globalConnNetId}`
245
+ }
246
+
247
+ override visualize(): GraphicsObject {
248
+ if (this.activeSubSolver) return this.activeSubSolver.visualize()
249
+
250
+ const graphics = visualizeInputProblem(this.inputProblem)
251
+ if (!graphics.lines) graphics.lines = []
252
+ if (!graphics.rects) graphics.rects = []
253
+ if (!graphics.points) graphics.points = []
254
+
255
+ for (const trace of this.outputTraces) {
256
+ graphics.lines.push({
257
+ points: trace.tracePath,
258
+ strokeColor: "purple",
259
+ } as any)
260
+ }
261
+
262
+ for (const label of this.outputNetLabelPlacements) {
263
+ graphics.rects.push({
264
+ center: label.center,
265
+ width: label.width,
266
+ height: label.height,
267
+ fill: getColorFromString(label.globalConnNetId, 0.35),
268
+ strokeColor: getColorFromString(label.globalConnNetId, 0.9),
269
+ label: `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`,
270
+ } as any)
271
+ graphics.points.push({
272
+ x: label.anchorPoint.x,
273
+ y: label.anchorPoint.y,
274
+ color: getColorFromString(label.globalConnNetId, 0.9),
275
+ } as any)
276
+ }
277
+
278
+ if (this.currentOverlap) {
279
+ graphics.lines.push({
280
+ points: this.currentOverlap.trace.tracePath,
281
+ strokeColor: "red",
282
+ } as any)
283
+ graphics.rects.push({
284
+ center: this.currentOverlap.label.center,
285
+ width: this.currentOverlap.label.width,
286
+ height: this.currentOverlap.label.height,
287
+ fill: "rgba(255,0,0,0.3)",
288
+ strokeColor: "red",
289
+ } as any)
290
+ }
291
+
292
+ return graphics
293
+ }
294
+ }
@@ -25,6 +25,7 @@ import { Example28Solver } from "../Example28Solver/Example28Solver"
25
25
  import { AvailableNetOrientationSolver } from "../AvailableNetOrientationSolver/AvailableNetOrientationSolver"
26
26
  import { VccNetLabelCornerPlacementSolver } from "../VccNetLabelCornerPlacementSolver/VccNetLabelCornerPlacementSolver"
27
27
  import { TraceAnchoredNetLabelOverlapSolver } from "../TraceAnchoredNetLabelOverlapSolver/TraceAnchoredNetLabelOverlapSolver"
28
+ import { NetLabelTraceCollisionSolver } from "../NetLabelTraceCollisionSolver/NetLabelTraceCollisionSolver"
28
29
 
29
30
  type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
30
31
  solverName: string
@@ -78,6 +79,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
78
79
  availableNetOrientationSolver?: AvailableNetOrientationSolver
79
80
  vccNetLabelCornerPlacementSolver?: VccNetLabelCornerPlacementSolver
80
81
  traceAnchoredNetLabelOverlapSolver?: TraceAnchoredNetLabelOverlapSolver
82
+ netLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver
81
83
 
82
84
  startTimeOfPhase: Record<string, number>
83
85
  endTimeOfPhase: Record<string, number>
@@ -285,6 +287,19 @@ export class SchematicTracePipelineSolver extends BaseSolver {
285
287
  },
286
288
  ],
287
289
  ),
290
+ definePipelineStep(
291
+ "netLabelTraceCollisionSolver",
292
+ NetLabelTraceCollisionSolver,
293
+ (instance) => [
294
+ {
295
+ inputProblem: instance.inputProblem,
296
+ traces: instance.availableNetOrientationSolver!.traces,
297
+ netLabelPlacements:
298
+ instance.traceAnchoredNetLabelOverlapSolver!
299
+ .outputNetLabelPlacements,
300
+ },
301
+ ],
302
+ ),
288
303
  ]
289
304
 
290
305
  constructor(inputProblem: InputProblem) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-trace-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.55",
4
+ "version": "0.0.57",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/assets/example33.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/assets/example34.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />
@@ -0,0 +1,132 @@
1
+ {
2
+ "chips": [
3
+ {
4
+ "chipId": "schematic_component_0",
5
+ "center": {
6
+ "x": -1.4,
7
+ "y": -6.6
8
+ },
9
+ "width": 1.1,
10
+ "height": 0.388910699999999,
11
+ "pins": [
12
+ {
13
+ "pinId": "R7.1",
14
+ "x": -1.95,
15
+ "y": -6.6
16
+ },
17
+ {
18
+ "pinId": "R7.2",
19
+ "x": -0.8499999999999999,
20
+ "y": -6.6
21
+ }
22
+ ],
23
+ "sectionId": "qspi"
24
+ },
25
+ {
26
+ "chipId": "schematic_component_1",
27
+ "center": {
28
+ "x": 0,
29
+ "y": -8.4
30
+ },
31
+ "width": 3.045,
32
+ "height": 1,
33
+ "pins": [
34
+ {
35
+ "pinId": "U5.1",
36
+ "x": -1.5225,
37
+ "y": -8.1
38
+ },
39
+ {
40
+ "pinId": "U5.2",
41
+ "x": -1.5225,
42
+ "y": -8.3
43
+ },
44
+ {
45
+ "pinId": "U5.3",
46
+ "x": -1.5225,
47
+ "y": -8.5
48
+ },
49
+ {
50
+ "pinId": "U5.4",
51
+ "x": -1.5225,
52
+ "y": -8.700000000000001
53
+ },
54
+ {
55
+ "pinId": "U5.5",
56
+ "x": 1.5225,
57
+ "y": -8.1
58
+ },
59
+ {
60
+ "pinId": "U5.6",
61
+ "x": 1.5225,
62
+ "y": -8.3
63
+ },
64
+ {
65
+ "pinId": "U5.7",
66
+ "x": 1.5225,
67
+ "y": -8.700000000000001
68
+ },
69
+ {
70
+ "pinId": "U5.8",
71
+ "x": 1.5225,
72
+ "y": -8.5
73
+ }
74
+ ],
75
+ "sectionId": "qspi"
76
+ }
77
+ ],
78
+ "directConnections": [],
79
+ "netConnections": [
80
+ {
81
+ "netId": "V3V3",
82
+ "pinIds": ["R7.1", "U5.8"],
83
+ "netLabelWidth": 0.4
84
+ },
85
+ {
86
+ "netId": "QSPI_CS",
87
+ "pinIds": ["R7.2", "U5.4"],
88
+ "netLabelWidth": 0.7
89
+ },
90
+ {
91
+ "netId": "QSPI_SCK",
92
+ "pinIds": ["U5.1"],
93
+ "netLabelWidth": 0.8
94
+ },
95
+ {
96
+ "netId": "QSPI_DATA0",
97
+ "pinIds": ["U5.2"],
98
+ "netLabelWidth": 1
99
+ },
100
+ {
101
+ "netId": "QSPI_DATA1",
102
+ "pinIds": ["U5.3"],
103
+ "netLabelWidth": 1
104
+ },
105
+ {
106
+ "netId": "QSPI_DATA2",
107
+ "pinIds": ["U5.5"],
108
+ "netLabelWidth": 1
109
+ },
110
+ {
111
+ "netId": "QSPI_DATA3",
112
+ "pinIds": ["U5.6"],
113
+ "netLabelWidth": 1
114
+ },
115
+ {
116
+ "netId": "GND",
117
+ "pinIds": ["U5.7"],
118
+ "netLabelWidth": 0.3
119
+ }
120
+ ],
121
+ "availableNetLabelOrientations": {
122
+ "V3V3": ["y+"],
123
+ "QSPI_CS": ["x-", "x+"],
124
+ "QSPI_SCK": ["x-", "x+"],
125
+ "QSPI_DATA0": ["x-", "x+"],
126
+ "QSPI_DATA1": ["x-", "x+"],
127
+ "QSPI_DATA2": ["x-", "x+"],
128
+ "QSPI_DATA3": ["x-", "x+"],
129
+ "GND": ["y-"]
130
+ },
131
+ "maxMspPairDistance": 2.4
132
+ }
@@ -0,0 +1,61 @@
1
+ {
2
+ "chips": [
3
+ {
4
+ "chipId": "schematic_component_0",
5
+ "center": {
6
+ "x": 0,
7
+ "y": 0
8
+ },
9
+ "width": 1,
10
+ "height": 1,
11
+ "pins": [
12
+ {
13
+ "pinId": "U1.1",
14
+ "x": 1.1,
15
+ "y": 0.30000000000000004
16
+ },
17
+ {
18
+ "pinId": "U1.2",
19
+ "x": 1.1,
20
+ "y": 0.09999999999999998
21
+ },
22
+ {
23
+ "pinId": "U1.3",
24
+ "x": 1.1,
25
+ "y": -0.10000000000000003
26
+ },
27
+ {
28
+ "pinId": "U1.4",
29
+ "x": 1.1,
30
+ "y": -0.30000000000000004
31
+ }
32
+ ]
33
+ }
34
+ ],
35
+ "directConnections": [],
36
+ "netConnections": [
37
+ {
38
+ "netId": "SCL",
39
+ "pinIds": ["U1.1"]
40
+ },
41
+ {
42
+ "netId": "SDA",
43
+ "pinIds": ["U1.2"]
44
+ },
45
+ {
46
+ "netId": "V3_3",
47
+ "pinIds": ["U1.3"]
48
+ },
49
+ {
50
+ "netId": "GND",
51
+ "pinIds": ["U1.4"]
52
+ }
53
+ ],
54
+ "availableNetLabelOrientations": {
55
+ "SCL": ["x-", "x+"],
56
+ "SDA": ["x-", "x+"],
57
+ "V3_3": ["y+"],
58
+ "GND": ["y-"]
59
+ },
60
+ "maxMspPairDistance": 2.4
61
+ }
@@ -0,0 +1,181 @@
1
+ <svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="100%" height="100%" fill="white" />
3
+ <g>
4
+ <circle data-type="point" data-label="R7.1
5
+ x-" data-x="-1.95" data-y="-6.6" cx="102.39751311443555" cy="200.26423159121816" r="3" fill="hsl(232, 100%, 50%, 0.8)" />
6
+ </g>
7
+ <g>
8
+ <circle data-type="point" data-label="R7.2
9
+ x+" data-x="-0.8499999999999999" data-y="-6.6" cx="222.07888090149598" cy="200.26423159121816" r="3" fill="hsl(233, 100%, 50%, 0.8)" />
10
+ </g>
11
+ <g>
12
+ <circle data-type="point" data-label="U5.1
13
+ x-" data-x="-1.5225" data-y="-8.1" cx="148.91004468622495" cy="363.4660967553915" r="3" fill="hsl(203, 100%, 50%, 0.8)" />
14
+ </g>
15
+ <g>
16
+ <circle data-type="point" data-label="U5.2
17
+ x-" data-x="-1.5225" data-y="-8.3" cx="148.91004468622495" cy="385.22634544394805" r="3" fill="hsl(204, 100%, 50%, 0.8)" />
18
+ </g>
19
+ <g>
20
+ <circle data-type="point" data-label="U5.3
21
+ x-" data-x="-1.5225" data-y="-8.5" cx="148.91004468622495" cy="406.98659413250437" r="3" fill="hsl(205, 100%, 50%, 0.8)" />
22
+ </g>
23
+ <g>
24
+ <circle data-type="point" data-label="U5.4
25
+ x-" data-x="-1.5225" data-y="-8.700000000000001" cx="148.91004468622495" cy="428.7468428210609" r="3" fill="hsl(206, 100%, 50%, 0.8)" />
26
+ </g>
27
+ <g>
28
+ <circle data-type="point" data-label="U5.5
29
+ x+" data-x="1.5225" data-y="-8.1" cx="480.2098309694967" cy="363.4660967553915" r="3" fill="hsl(207, 100%, 50%, 0.8)" />
30
+ </g>
31
+ <g>
32
+ <circle data-type="point" data-label="U5.6
33
+ x+" data-x="1.5225" data-y="-8.3" cx="480.2098309694967" cy="385.22634544394805" r="3" fill="hsl(208, 100%, 50%, 0.8)" />
34
+ </g>
35
+ <g>
36
+ <circle data-type="point" data-label="U5.7
37
+ x+" data-x="1.5225" data-y="-8.700000000000001" cx="480.2098309694967" cy="428.7468428210609" r="3" fill="hsl(209, 100%, 50%, 0.8)" />
38
+ </g>
39
+ <g>
40
+ <circle data-type="point" data-label="U5.8
41
+ x+" data-x="1.5225" data-y="-8.5" cx="480.2098309694967" cy="406.98659413250437" r="3" fill="hsl(210, 100%, 50%, 0.8)" />
42
+ </g>
43
+ <g>
44
+ <circle data-type="point" data-label="" data-x="-2.15" data-y="-6.6" cx="80.63726442587912" cy="200.26423159121816" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
45
+ </g>
46
+ <g>
47
+ <circle data-type="point" data-label="" data-x="-0.8499999999999999" data-y="-6.6" cx="222.07888090149598" cy="200.26423159121816" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
48
+ </g>
49
+ <g>
50
+ <circle data-type="point" data-label="" data-x="-1.5225" data-y="-8.700000000000001" cx="148.91004468622495" cy="428.7468428210609" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
51
+ </g>
52
+ <g>
53
+ <circle data-type="point" data-label="" data-x="-1.5225" data-y="-8.1" cx="148.91004468622495" cy="363.4660967553915" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
54
+ </g>
55
+ <g>
56
+ <circle data-type="point" data-label="" data-x="-1.5225" data-y="-8.3" cx="148.91004468622495" cy="385.22634544394805" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
57
+ </g>
58
+ <g>
59
+ <circle data-type="point" data-label="" data-x="-1.5225" data-y="-8.5" cx="148.91004468622495" cy="406.98659413250437" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
60
+ </g>
61
+ <g>
62
+ <circle data-type="point" data-label="" data-x="1.5225" data-y="-8.1" cx="480.2098309694967" cy="363.4660967553915" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
63
+ </g>
64
+ <g>
65
+ <circle data-type="point" data-label="" data-x="1.5225" data-y="-8.3" cx="480.2098309694967" cy="385.22634544394805" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
66
+ </g>
67
+ <g>
68
+ <circle data-type="point" data-label="" data-x="1.6235" data-y="-8.901" cx="491.19875655721773" cy="450.61589275306005" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
69
+ </g>
70
+ <g>
71
+ <polyline data-points="-1.95,-6.6 1.5225,-8.5" data-type="line" data-label="" points="102.39751311443555,200.26423159121816 480.2098309694967,406.98659413250437" fill="none" stroke="hsl(154, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
72
+ </g>
73
+ <g>
74
+ <polyline data-points="-0.8499999999999999,-6.6 -1.5225,-8.700000000000001" data-type="line" data-label="" points="222.07888090149598,200.26423159121816 148.91004468622495,428.7468428210609" fill="none" stroke="hsl(172, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
75
+ </g>
76
+ <g>
77
+ <polyline data-points="-1.95,-6.6 -2.15,-6.6 -2.15,-7.55 1.7225000000000004,-7.55 1.7225000000000004,-7.8999999999999995 2.6235,-7.8999999999999995 2.6235,-8.499999999999998 1.5225000000000002,-8.5" data-type="line" data-label="" points="102.39751311443555,200.26423159121816 80.63726442587912,200.26423159121816 80.63726442587912,303.62541286186126 501.97007965805324,303.62541286186126 501.97007965805324,341.70584806683496 600,341.70584806683496 600,406.98659413250414 480.20983096949675,406.98659413250437" fill="none" stroke="purple" stroke-width="1" />
78
+ </g>
79
+ <g>
80
+ <polyline data-points="1.5225,-8.700000000000001 1.6235,-8.700000000000001 1.6235,-8.901" data-type="line" data-label="" points="480.2098309694967,428.7468428210609 491.19875655721773,428.7468428210609 491.19875655721773,450.61589275306005" fill="none" stroke="purple" stroke-width="1" />
81
+ </g>
82
+ <g>
83
+ <rect data-type="rect" data-label="schematic_component_0" data-x="-1.4" data-y="-6.6" x="102.39751311443555" y="179.10724771711682" width="119.68136778706042" height="42.313967748202685" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.009191071428571429" />
84
+ </g>
85
+ <g>
86
+ <rect data-type="rect" data-label="schematic_component_1" data-x="0" data-y="-8.4" x="148.91004468622495" y="341.7058480668351" width="331.2997862832717" height="108.80124344278227" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.009191071428571429" />
87
+ </g>
88
+ <g>
89
+ <rect data-type="rect" data-label="netId: V3V3
90
+ globalConnNetId: connectivity_net0" data-x="-2.15" data-y="-6.3999999999999995" x="69.75714008160091" y="156.7437342141053" width="21.76024868855646" height="43.52049737711286" fill="#ef444466" stroke="#ef4444" stroke-width="0.009191071428571429" />
91
+ </g>
92
+ <g>
93
+ <rect data-type="rect" data-label="netId: QSPI_CS
94
+ globalConnNetId: connectivity_net1" data-x="-0.4989999999999999" data-y="-6.6" x="222.18768214493878" y="189.38410724693995" width="76.1608704099475" height="21.76024868855643" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.009191071428571429" />
95
+ </g>
96
+ <g>
97
+ <rect data-type="rect" data-label="netId: QSPI_CS
98
+ globalConnNetId: connectivity_net1" data-x="-1.8735" data-y="-8.700000000000001" x="72.64037303283462" y="417.8667184767828" width="76.16087040994756" height="21.760248688556317" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.009191071428571429" />
99
+ </g>
100
+ <g>
101
+ <rect data-type="rect" data-label="netId: QSPI_SCK
102
+ globalConnNetId: connectivity_net2" data-x="-1.9234999999999998" data-y="-8.1" x="61.76024868855643" y="352.5859724111133" width="87.04099475422575" height="21.760248688556317" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.009191071428571429" />
103
+ </g>
104
+ <g>
105
+ <rect data-type="rect" data-label="netId: QSPI_DATA0
106
+ globalConnNetId: connectivity_net3" data-x="-2.0235" data-y="-8.3" x="40" y="374.34622109966983" width="108.80124344278218" height="21.760248688556317" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.009191071428571429" />
107
+ </g>
108
+ <g>
109
+ <rect data-type="rect" data-label="netId: QSPI_DATA1
110
+ globalConnNetId: connectivity_net4" data-x="-2.0235" data-y="-8.5" x="40" y="396.10646978822615" width="108.80124344278218" height="21.76024868855643" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.009191071428571429" />
111
+ </g>
112
+ <g>
113
+ <rect data-type="rect" data-label="netId: QSPI_DATA2
114
+ globalConnNetId: connectivity_net5" data-x="2.0235" data-y="-8.1" x="480.3186322129395" y="352.5859724111133" width="108.80124344278215" height="21.760248688556317" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.009191071428571429" />
115
+ </g>
116
+ <g>
117
+ <rect data-type="rect" data-label="netId: QSPI_DATA3
118
+ globalConnNetId: connectivity_net6" data-x="2.0235" data-y="-8.3" x="480.3186322129395" y="374.34622109966983" width="108.80124344278215" height="21.760248688556317" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.009191071428571429" />
119
+ </g>
120
+ <g>
121
+ <rect data-type="rect" data-label="netId: GND
122
+ globalConnNetId: connectivity_net7" data-x="1.6235" data-y="-9.051" x="480.3186322129395" y="450.61589275306005" width="21.76024868855643" height="32.64037303283476" fill="#00000066" stroke="#000000" stroke-width="0.009191071428571429" />
123
+ </g>
124
+ <g id="crosshair" style="display: none">
125
+ <line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5" />
126
+ <line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5" /><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text>
127
+ </g>
128
+ <script>
129
+ <![CDATA[
130
+ document.currentScript.parentElement.addEventListener('mousemove', (e) => {
131
+ const svg = e.currentTarget;
132
+ const rect = svg.getBoundingClientRect();
133
+ const x = e.clientX - rect.left;
134
+ const y = e.clientY - rect.top;
135
+ const crosshair = svg.getElementById('crosshair');
136
+ const h = svg.getElementById('crosshair-h');
137
+ const v = svg.getElementById('crosshair-v');
138
+ const coords = svg.getElementById('coordinates');
139
+
140
+ crosshair.style.display = 'block';
141
+ h.setAttribute('x1', '0');
142
+ h.setAttribute('x2', '640');
143
+ h.setAttribute('y1', y);
144
+ h.setAttribute('y2', y);
145
+ v.setAttribute('x1', x);
146
+ v.setAttribute('x2', x);
147
+ v.setAttribute('y1', '0');
148
+ v.setAttribute('y2', '640');
149
+
150
+ // Calculate real coordinates using inverse transformation
151
+ const matrix = {
152
+ "a": 108.8012434427822,
153
+ "c": 0,
154
+ "e": 314.55993782786084,
155
+ "b": 0,
156
+ "d": -108.8012434427822,
157
+ "f": -517.8239751311443
158
+ };
159
+ // Manually invert and apply the affine transform
160
+ // Since we only use translate and scale, we can directly compute:
161
+ // x' = (x - tx) / sx
162
+ // y' = (y - ty) / sy
163
+ const sx = matrix.a;
164
+ const sy = matrix.d;
165
+ const tx = matrix.e;
166
+ const ty = matrix.f;
167
+ const realPoint = {
168
+ x: (x - tx) / sx,
169
+ y: (y - ty) / sy // Flip y back since we used negative scale
170
+ }
171
+
172
+ coords.textContent = `(${realPoint.x.toFixed(2)}, ${realPoint.y.toFixed(2)})`;
173
+ coords.setAttribute('x', (x + 5).toString());
174
+ coords.setAttribute('y', (y - 5).toString());
175
+ });
176
+ document.currentScript.parentElement.addEventListener('mouseleave', () => {
177
+ document.currentScript.parentElement.getElementById('crosshair').style.display = 'none';
178
+ });
179
+ ]]>
180
+ </script>
181
+ </svg>
@@ -0,0 +1,113 @@
1
+ <svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="100%" height="100%" fill="white" />
3
+ <g>
4
+ <circle data-type="point" data-label="U1.1
5
+ x+" data-x="1.1" data-y="0.30000000000000004" cx="487.83715012722644" cy="248.75318066157763" r="3" fill="hsl(319, 100%, 50%, 0.8)" />
6
+ </g>
7
+ <g>
8
+ <circle data-type="point" data-label="U1.2
9
+ x+" data-x="1.1" data-y="0.09999999999999998" cx="487.83715012722644" cy="289.46564885496184" r="3" fill="hsl(320, 100%, 50%, 0.8)" />
10
+ </g>
11
+ <g>
12
+ <circle data-type="point" data-label="U1.3
13
+ x+" data-x="1.1" data-y="-0.10000000000000003" cx="487.83715012722644" cy="330.17811704834605" r="3" fill="hsl(321, 100%, 50%, 0.8)" />
14
+ </g>
15
+ <g>
16
+ <circle data-type="point" data-label="U1.4
17
+ x+" data-x="1.1" data-y="-0.30000000000000004" cx="487.83715012722644" cy="370.89058524173026" r="3" fill="hsl(322, 100%, 50%, 0.8)" />
18
+ </g>
19
+ <g>
20
+ <circle data-type="point" data-label="" data-x="1.1" data-y="0.30000000000000004" cx="487.83715012722644" cy="248.75318066157763" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
21
+ </g>
22
+ <g>
23
+ <circle data-type="point" data-label="" data-x="1.1" data-y="0.09999999999999998" cx="487.83715012722644" cy="289.46564885496184" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
24
+ </g>
25
+ <g>
26
+ <circle data-type="point" data-label="" data-x="1.5510000000000002" data-y="0.4009999999999999" cx="579.6437659033079" cy="228.1933842239186" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
27
+ </g>
28
+ <g>
29
+ <circle data-type="point" data-label="" data-x="1.201" data-y="-0.5010000000000001" cx="508.39694656488547" cy="411.80661577608146" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
30
+ </g>
31
+ <g>
32
+ <polyline data-points="1.1,-0.10000000000000003 1.5510000000000002,-0.10000000000000003 1.5510000000000002,0.4009999999999999" data-type="line" data-label="" points="487.83715012722644,330.17811704834605 579.6437659033079,330.17811704834605 579.6437659033079,228.1933842239186" fill="none" stroke="purple" stroke-width="1" />
33
+ </g>
34
+ <g>
35
+ <polyline data-points="1.1,-0.30000000000000004 1.201,-0.30000000000000004 1.201,-0.5010000000000001" data-type="line" data-label="" points="487.83715012722644,370.89058524173026 508.39694656488547,370.89058524173026 508.39694656488547,411.80661577608146" fill="none" stroke="purple" stroke-width="1" />
36
+ </g>
37
+ <g>
38
+ <rect data-type="rect" data-label="schematic_component_0" data-x="0" data-y="0" x="40" y="208.04071246819342" width="447.83715012722644" height="203.56234096692106" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.004912500000000001" />
39
+ </g>
40
+ <g>
41
+ <rect data-type="rect" data-label="netId: SCL
42
+ globalConnNetId: connectivity_net0" data-x="1.326" data-y="0.30000000000000004" x="488.0407124681933" y="228.39694656488552" width="91.60305343511459" height="40.71246819338421" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.004912500000000001" />
43
+ </g>
44
+ <g>
45
+ <rect data-type="rect" data-label="netId: SDA
46
+ globalConnNetId: connectivity_net1" data-x="1.326" data-y="0.09999999999999998" x="488.0407124681933" y="269.10941475826974" width="91.60305343511459" height="40.71246819338421" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.004912500000000001" />
47
+ </g>
48
+ <g>
49
+ <rect data-type="rect" data-label="netId: V3_3
50
+ globalConnNetId: connectivity_net2" data-x="1.5510000000000002" data-y="0.6259999999999999" x="559.2875318066158" y="136.59033078880412" width="40.71246819338421" height="91.60305343511448" fill="#ef444466" stroke="#ef4444" stroke-width="0.004912500000000001" />
51
+ </g>
52
+ <g>
53
+ <rect data-type="rect" data-label="netId: GND
54
+ globalConnNetId: connectivity_net3" data-x="1.201" data-y="-0.7260000000000001" x="488.0407124681933" y="411.80661577608146" width="40.71246819338421" height="91.60305343511448" fill="#00000066" stroke="#000000" stroke-width="0.004912500000000001" />
55
+ </g>
56
+ <g id="crosshair" style="display: none">
57
+ <line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5" />
58
+ <line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5" /><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text>
59
+ </g>
60
+ <script>
61
+ <![CDATA[
62
+ document.currentScript.parentElement.addEventListener('mousemove', (e) => {
63
+ const svg = e.currentTarget;
64
+ const rect = svg.getBoundingClientRect();
65
+ const x = e.clientX - rect.left;
66
+ const y = e.clientY - rect.top;
67
+ const crosshair = svg.getElementById('crosshair');
68
+ const h = svg.getElementById('crosshair-h');
69
+ const v = svg.getElementById('crosshair-v');
70
+ const coords = svg.getElementById('coordinates');
71
+
72
+ crosshair.style.display = 'block';
73
+ h.setAttribute('x1', '0');
74
+ h.setAttribute('x2', '640');
75
+ h.setAttribute('y1', y);
76
+ h.setAttribute('y2', y);
77
+ v.setAttribute('x1', x);
78
+ v.setAttribute('x2', x);
79
+ v.setAttribute('y1', '0');
80
+ v.setAttribute('y2', '640');
81
+
82
+ // Calculate real coordinates using inverse transformation
83
+ const matrix = {
84
+ "a": 203.56234096692108,
85
+ "c": 0,
86
+ "e": 263.9185750636132,
87
+ "b": 0,
88
+ "d": -203.56234096692108,
89
+ "f": 309.82188295165395
90
+ };
91
+ // Manually invert and apply the affine transform
92
+ // Since we only use translate and scale, we can directly compute:
93
+ // x' = (x - tx) / sx
94
+ // y' = (y - ty) / sy
95
+ const sx = matrix.a;
96
+ const sy = matrix.d;
97
+ const tx = matrix.e;
98
+ const ty = matrix.f;
99
+ const realPoint = {
100
+ x: (x - tx) / sx,
101
+ y: (y - ty) / sy // Flip y back since we used negative scale
102
+ }
103
+
104
+ coords.textContent = `(${realPoint.x.toFixed(2)}, ${realPoint.y.toFixed(2)})`;
105
+ coords.setAttribute('x', (x + 5).toString());
106
+ coords.setAttribute('y', (y - 5).toString());
107
+ });
108
+ document.currentScript.parentElement.addEventListener('mouseleave', () => {
109
+ document.currentScript.parentElement.getElementById('crosshair').style.display = 'none';
110
+ });
111
+ ]]>
112
+ </script>
113
+ </svg>
@@ -0,0 +1,12 @@
1
+ import { test, expect } from "bun:test"
2
+ import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
3
+ import inputProblem from "../assets/example33.json"
4
+ import "tests/fixtures/matcher"
5
+
6
+ test("example33", () => {
7
+ const solver = new SchematicTracePipelineSolver(inputProblem as any)
8
+
9
+ solver.solve()
10
+
11
+ expect(solver).toMatchSolverSnapshot(import.meta.path)
12
+ })
@@ -0,0 +1,12 @@
1
+ import { test, expect } from "bun:test"
2
+ import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
3
+ import inputProblem from "../assets/example34.json"
4
+ import "tests/fixtures/matcher"
5
+
6
+ test("example34", () => {
7
+ const solver = new SchematicTracePipelineSolver(inputProblem as any)
8
+
9
+ solver.solve()
10
+
11
+ expect(solver).toMatchSolverSnapshot(import.meta.path)
12
+ })