@tscircuit/schematic-trace-solver 0.0.80 → 0.0.82

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
@@ -694,8 +694,8 @@ type CandidateLabel = {
694
694
  width: number;
695
695
  height: number;
696
696
  };
697
- type CandidateStatus$2 = "valid" | "chip-collision" | "text-collision" | "trace-collision" | "netlabel-collision";
698
- type CandidatePhase = "rotate" | "shift" | "lateral-shift";
697
+ type CandidateStatus$2 = "valid" | "chip-collision" | "text-collision" | "trace-collision" | "trace-clearance-violation" | "netlabel-collision";
698
+ type CandidatePhase = "rotate" | "trace-anchor" | "shift" | "lateral-shift";
699
699
  type EvaluatedCandidate = CandidateLabel & {
700
700
  status: CandidateStatus$2;
701
701
  selected: boolean;
@@ -733,6 +733,8 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
733
733
  private setCurrentLabel;
734
734
  private findCorrectedCandidate;
735
735
  private findValidRotatedCandidate;
736
+ private findValidTraceAnchorCandidate;
737
+ private getTraceAnchorCandidatePoints;
736
738
  private getAvailableOrientations;
737
739
  private findValidShiftedCandidate;
738
740
  /**
@@ -757,7 +759,13 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
757
759
  private getNetLabelWidth;
758
760
  private getNetLabelHeight;
759
761
  private getCandidateStatus;
762
+ private isAcceptableTraceAnchorChipCollision;
760
763
  private getBoundsStatus;
764
+ private isTraceTooCloseToLabel;
765
+ private getLabelTraceClearanceBounds;
766
+ private shouldCheckTraceClearanceForLabel;
767
+ private hasRoutedLabelOnSameNet;
768
+ private isPortOnlyLabel;
761
769
  private intersectsAnyOtherNetLabel;
762
770
  private sharesChipBoundary;
763
771
  private getChipSideForPoint;
package/dist/index.js CHANGED
@@ -6167,6 +6167,7 @@ var ensureGraphicsArrays = (graphics) => {
6167
6167
  };
6168
6168
 
6169
6169
  // lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts
6170
+ var LABEL_TRACE_CLEARANCE = 0.1;
6170
6171
  var AvailableNetOrientationSolver = class extends BaseSolver {
6171
6172
  inputProblem;
6172
6173
  traces;
@@ -6245,6 +6246,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
6245
6246
  this.addConnectorTrace(label, candidate, labelIndex);
6246
6247
  }
6247
6248
  addConnectorTrace(label, candidate, labelIndex) {
6249
+ if (candidate.phase === "trace-anchor") return;
6248
6250
  const tracePath = this.getCandidateConnectorTrace(label, candidate);
6249
6251
  if (tracePath.length < 2) return;
6250
6252
  const mspPairId = `available-net-orientation-${labelIndex}-${label.netId ?? label.globalConnNetId}`;
@@ -6279,6 +6281,12 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
6279
6281
  orientations
6280
6282
  );
6281
6283
  if (rotatedCandidate) return rotatedCandidate;
6284
+ const traceAnchorCandidate = this.findValidTraceAnchorCandidate(
6285
+ label,
6286
+ orientations[0],
6287
+ labelIndex
6288
+ );
6289
+ if (traceAnchorCandidate) return traceAnchorCandidate;
6282
6290
  const shiftedCandidate = this.findValidShiftedCandidate(
6283
6291
  label,
6284
6292
  orientations[0],
@@ -6312,6 +6320,46 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
6312
6320
  }
6313
6321
  return null;
6314
6322
  }
6323
+ findValidTraceAnchorCandidate(label, orientation, labelIndex) {
6324
+ const direction = dir(orientation);
6325
+ const candidatePoints = this.getTraceAnchorCandidatePoints(label).sort(
6326
+ (a, b) => {
6327
+ const aAlongDirection = a.x * direction.x + a.y * direction.y;
6328
+ const bAlongDirection = b.x * direction.x + b.y * direction.y;
6329
+ return bAlongDirection - aAlongDirection;
6330
+ }
6331
+ );
6332
+ for (const anchorPoint of candidatePoints) {
6333
+ const candidate = this.createCandidate(label, anchorPoint, orientation);
6334
+ const result = this.evaluateCandidate(
6335
+ candidate,
6336
+ label,
6337
+ labelIndex,
6338
+ "trace-anchor"
6339
+ );
6340
+ this.currentCandidateResults.push(result);
6341
+ if (result.status === "valid") {
6342
+ result.selected = true;
6343
+ return result;
6344
+ }
6345
+ }
6346
+ return null;
6347
+ }
6348
+ getTraceAnchorCandidatePoints(label) {
6349
+ const seen = /* @__PURE__ */ new Set();
6350
+ const points = [];
6351
+ for (const traceId of label.mspConnectionPairIds ?? []) {
6352
+ const trace = this.traceMap[traceId];
6353
+ if (!trace) continue;
6354
+ for (const point of trace.tracePath) {
6355
+ const key = `${point.x.toFixed(9)},${point.y.toFixed(9)}`;
6356
+ if (seen.has(key)) continue;
6357
+ seen.add(key);
6358
+ points.push(point);
6359
+ }
6360
+ }
6361
+ return points;
6362
+ }
6315
6363
  getAvailableOrientations(label) {
6316
6364
  const effectiveNetId = label.netId ?? label.globalConnNetId;
6317
6365
  return this.inputProblem.availableNetLabelOrientations[effectiveNetId] ?? [];
@@ -6580,11 +6628,28 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
6580
6628
  }
6581
6629
  getCandidateStatus(params) {
6582
6630
  const { candidate, label, labelIndex, phase } = params;
6583
- const boundsStatus = this.getBoundsStatus(
6584
- getRectBounds(candidate.center, candidate.width, candidate.height),
6585
- labelIndex
6631
+ const bounds = getRectBounds(
6632
+ candidate.center,
6633
+ candidate.width,
6634
+ candidate.height
6586
6635
  );
6587
- if (boundsStatus !== "valid") return boundsStatus;
6636
+ const boundsStatus = this.getBoundsStatus({
6637
+ bounds,
6638
+ labelIndex,
6639
+ label
6640
+ });
6641
+ if (boundsStatus !== "valid") {
6642
+ if (phase !== "trace-anchor" || boundsStatus !== "chip-collision" || !this.isAcceptableTraceAnchorChipCollision(candidate, label, bounds)) {
6643
+ return boundsStatus;
6644
+ }
6645
+ const nonChipBoundsStatus = this.getBoundsStatus({
6646
+ bounds,
6647
+ labelIndex,
6648
+ label,
6649
+ ignoreChipCollisions: true
6650
+ });
6651
+ if (nonChipBoundsStatus !== "valid") return nonChipBoundsStatus;
6652
+ }
6588
6653
  const connectorTrace = this.getCandidateConnectorTrace(label, {
6589
6654
  anchorPoint: candidate.anchorPoint,
6590
6655
  orientation: candidate.orientation,
@@ -6607,24 +6672,81 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
6607
6672
  }
6608
6673
  return "valid";
6609
6674
  }
6610
- getBoundsStatus(bounds, labelIndex) {
6611
- if (this.chipObstacleSpatialIndex.getChipsInBounds(bounds).length > 0) {
6612
- return "chip-collision";
6675
+ isAcceptableTraceAnchorChipCollision(candidate, label, bounds) {
6676
+ const collidingChips = this.chipObstacleSpatialIndex.getChipsInBounds(bounds);
6677
+ if (collidingChips.length === 0) return false;
6678
+ const labelChipIds = new Set(
6679
+ label.pinIds.map((pinId) => this.pinMap[pinId]?.chipId).filter((chipId) => Boolean(chipId))
6680
+ );
6681
+ return collidingChips.every((chip) => {
6682
+ if (!labelChipIds.has(chip.chipId)) return false;
6683
+ const { anchorPoint, orientation } = candidate;
6684
+ const chipBounds = chip.bounds;
6685
+ if (isYOrientation(orientation)) {
6686
+ return anchorPoint.x < chipBounds.minX - EPS8 || anchorPoint.x > chipBounds.maxX + EPS8;
6687
+ }
6688
+ return anchorPoint.y < chipBounds.minY - EPS8 || anchorPoint.y > chipBounds.maxY + EPS8;
6689
+ });
6690
+ }
6691
+ getBoundsStatus(candidateBoundsCheck) {
6692
+ const { bounds, labelIndex, label, ignoreChipCollisions } = candidateBoundsCheck;
6693
+ if (!ignoreChipCollisions) {
6694
+ if (this.chipObstacleSpatialIndex.getChipsInBounds(bounds).length > 0) {
6695
+ return "chip-collision";
6696
+ }
6697
+ if (this.sharesChipBoundary(bounds)) {
6698
+ return "chip-collision";
6699
+ }
6613
6700
  }
6614
6701
  if (rectIntersectsAnyTextBox(bounds, this.inputProblem)) {
6615
6702
  return "text-collision";
6616
6703
  }
6617
- if (this.sharesChipBoundary(bounds)) {
6618
- return "chip-collision";
6619
- }
6620
6704
  if (traceCrossesBoundsInterior(bounds, this.traceMap)) {
6621
6705
  return "trace-collision";
6622
6706
  }
6707
+ if (this.isTraceTooCloseToLabel(bounds, label)) {
6708
+ return "trace-clearance-violation";
6709
+ }
6623
6710
  if (this.intersectsAnyOtherNetLabel(bounds, labelIndex)) {
6624
6711
  return "netlabel-collision";
6625
6712
  }
6626
6713
  return "valid";
6627
6714
  }
6715
+ isTraceTooCloseToLabel(bounds, label) {
6716
+ if (!this.shouldCheckTraceClearanceForLabel(label)) return false;
6717
+ const clearanceBounds = this.getLabelTraceClearanceBounds(bounds);
6718
+ for (const trace of Object.values(this.traceMap)) {
6719
+ if (trace.globalConnNetId === label.globalConnNetId) continue;
6720
+ if (tracePathIntersectsBounds(trace.tracePath, clearanceBounds)) {
6721
+ return true;
6722
+ }
6723
+ }
6724
+ return false;
6725
+ }
6726
+ getLabelTraceClearanceBounds(bounds) {
6727
+ return {
6728
+ minX: bounds.minX - LABEL_TRACE_CLEARANCE,
6729
+ minY: bounds.minY - LABEL_TRACE_CLEARANCE,
6730
+ maxX: bounds.maxX + LABEL_TRACE_CLEARANCE,
6731
+ maxY: bounds.maxY + LABEL_TRACE_CLEARANCE
6732
+ };
6733
+ }
6734
+ shouldCheckTraceClearanceForLabel(label) {
6735
+ if (!this.isPortOnlyLabel(label)) return false;
6736
+ if (!this.hasRoutedLabelOnSameNet(label)) return false;
6737
+ const orientations = this.getAvailableOrientations(label);
6738
+ return orientations.length === 1;
6739
+ }
6740
+ hasRoutedLabelOnSameNet(label) {
6741
+ for (const otherLabel of this.outputNetLabelPlacements) {
6742
+ if (otherLabel.globalConnNetId !== label.globalConnNetId) continue;
6743
+ if (otherLabel.mspConnectionPairIds.length > 0) return true;
6744
+ }
6745
+ return false;
6746
+ }
6747
+ isPortOnlyLabel(label) {
6748
+ return label.mspConnectionPairIds.length === 0;
6749
+ }
6628
6750
  intersectsAnyOtherNetLabel(bounds, labelIndex) {
6629
6751
  for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
6630
6752
  if (i === labelIndex) continue;
@@ -38,6 +38,8 @@ import type {
38
38
  import { visualizeAvailableNetOrientationSolver } from "./visualize"
39
39
  import { rectIntersectsAnyTextBox } from "lib/utils/textBoxBounds"
40
40
 
41
+ const LABEL_TRACE_CLEARANCE = 0.1
42
+
41
43
  export class AvailableNetOrientationSolver extends BaseSolver {
42
44
  inputProblem: InputProblem
43
45
  traces: SolvedTracePath[]
@@ -144,6 +146,8 @@ export class AvailableNetOrientationSolver extends BaseSolver {
144
146
  candidate: EvaluatedCandidate,
145
147
  labelIndex: number,
146
148
  ) {
149
+ if (candidate.phase === "trace-anchor") return
150
+
147
151
  const tracePath = this.getCandidateConnectorTrace(label, candidate)
148
152
  if (tracePath.length < 2) return
149
153
 
@@ -185,6 +189,13 @@ export class AvailableNetOrientationSolver extends BaseSolver {
185
189
  )
186
190
  if (rotatedCandidate) return rotatedCandidate
187
191
 
192
+ const traceAnchorCandidate = this.findValidTraceAnchorCandidate(
193
+ label,
194
+ orientations[0]!,
195
+ labelIndex,
196
+ )
197
+ if (traceAnchorCandidate) return traceAnchorCandidate
198
+
188
199
  const shiftedCandidate = this.findValidShiftedCandidate(
189
200
  label,
190
201
  orientations[0]!,
@@ -226,6 +237,57 @@ export class AvailableNetOrientationSolver extends BaseSolver {
226
237
  return null
227
238
  }
228
239
 
240
+ private findValidTraceAnchorCandidate(
241
+ label: NetLabelPlacement,
242
+ orientation: FacingDirection,
243
+ labelIndex: number,
244
+ ) {
245
+ const direction = dir(orientation)
246
+ const candidatePoints = this.getTraceAnchorCandidatePoints(label).sort(
247
+ (a, b) => {
248
+ const aAlongDirection = a.x * direction.x + a.y * direction.y
249
+ const bAlongDirection = b.x * direction.x + b.y * direction.y
250
+ return bAlongDirection - aAlongDirection
251
+ },
252
+ )
253
+
254
+ for (const anchorPoint of candidatePoints) {
255
+ const candidate = this.createCandidate(label, anchorPoint, orientation)
256
+ const result = this.evaluateCandidate(
257
+ candidate,
258
+ label,
259
+ labelIndex,
260
+ "trace-anchor",
261
+ )
262
+ this.currentCandidateResults.push(result)
263
+
264
+ if (result.status === "valid") {
265
+ result.selected = true
266
+ return result
267
+ }
268
+ }
269
+
270
+ return null
271
+ }
272
+
273
+ private getTraceAnchorCandidatePoints(label: NetLabelPlacement) {
274
+ const seen = new Set<string>()
275
+ const points: Point[] = []
276
+
277
+ for (const traceId of label.mspConnectionPairIds ?? []) {
278
+ const trace = this.traceMap[traceId]
279
+ if (!trace) continue
280
+ for (const point of trace.tracePath) {
281
+ const key = `${point.x.toFixed(9)},${point.y.toFixed(9)}`
282
+ if (seen.has(key)) continue
283
+ seen.add(key)
284
+ points.push(point)
285
+ }
286
+ }
287
+
288
+ return points
289
+ }
290
+
229
291
  private getAvailableOrientations(label: NetLabelPlacement) {
230
292
  const effectiveNetId = label.netId ?? label.globalConnNetId
231
293
  return this.inputProblem.availableNetLabelOrientations[effectiveNetId] ?? []
@@ -606,11 +668,33 @@ export class AvailableNetOrientationSolver extends BaseSolver {
606
668
  phase: CandidatePhase
607
669
  }) {
608
670
  const { candidate, label, labelIndex, phase } = params
609
- const boundsStatus = this.getBoundsStatus(
610
- getRectBounds(candidate.center, candidate.width, candidate.height),
611
- labelIndex,
671
+ const bounds = getRectBounds(
672
+ candidate.center,
673
+ candidate.width,
674
+ candidate.height,
612
675
  )
613
- if (boundsStatus !== "valid") return boundsStatus
676
+ const boundsStatus = this.getBoundsStatus({
677
+ bounds,
678
+ labelIndex,
679
+ label,
680
+ })
681
+ if (boundsStatus !== "valid") {
682
+ if (
683
+ phase !== "trace-anchor" ||
684
+ boundsStatus !== "chip-collision" ||
685
+ !this.isAcceptableTraceAnchorChipCollision(candidate, label, bounds)
686
+ ) {
687
+ return boundsStatus
688
+ }
689
+
690
+ const nonChipBoundsStatus = this.getBoundsStatus({
691
+ bounds,
692
+ labelIndex,
693
+ label,
694
+ ignoreChipCollisions: true,
695
+ })
696
+ if (nonChipBoundsStatus !== "valid") return nonChipBoundsStatus
697
+ }
614
698
 
615
699
  const connectorTrace = this.getCandidateConnectorTrace(label, {
616
700
  anchorPoint: candidate.anchorPoint,
@@ -640,25 +724,115 @@ export class AvailableNetOrientationSolver extends BaseSolver {
640
724
  return "valid"
641
725
  }
642
726
 
643
- private getBoundsStatus(bounds: Bounds, labelIndex: number): CandidateStatus {
644
- if (this.chipObstacleSpatialIndex.getChipsInBounds(bounds).length > 0) {
645
- return "chip-collision"
727
+ private isAcceptableTraceAnchorChipCollision(
728
+ candidate: CandidateLabel,
729
+ label: NetLabelPlacement,
730
+ bounds: Bounds,
731
+ ) {
732
+ const collidingChips =
733
+ this.chipObstacleSpatialIndex.getChipsInBounds(bounds)
734
+ if (collidingChips.length === 0) return false
735
+
736
+ const labelChipIds = new Set(
737
+ label.pinIds
738
+ .map((pinId) => this.pinMap[pinId]?.chipId)
739
+ .filter((chipId): chipId is string => Boolean(chipId)),
740
+ )
741
+
742
+ return collidingChips.every((chip) => {
743
+ if (!labelChipIds.has(chip.chipId)) return false
744
+
745
+ const { anchorPoint, orientation } = candidate
746
+ const chipBounds = chip.bounds
747
+ if (isYOrientation(orientation)) {
748
+ return (
749
+ anchorPoint.x < chipBounds.minX - EPS ||
750
+ anchorPoint.x > chipBounds.maxX + EPS
751
+ )
752
+ }
753
+ return (
754
+ anchorPoint.y < chipBounds.minY - EPS ||
755
+ anchorPoint.y > chipBounds.maxY + EPS
756
+ )
757
+ })
758
+ }
759
+
760
+ private getBoundsStatus(candidateBoundsCheck: {
761
+ bounds: Bounds
762
+ labelIndex: number
763
+ label: NetLabelPlacement
764
+ ignoreChipCollisions?: boolean
765
+ }): CandidateStatus {
766
+ const { bounds, labelIndex, label, ignoreChipCollisions } =
767
+ candidateBoundsCheck
768
+
769
+ if (!ignoreChipCollisions) {
770
+ if (this.chipObstacleSpatialIndex.getChipsInBounds(bounds).length > 0) {
771
+ return "chip-collision"
772
+ }
773
+ if (this.sharesChipBoundary(bounds)) {
774
+ return "chip-collision"
775
+ }
646
776
  }
647
777
  if (rectIntersectsAnyTextBox(bounds, this.inputProblem)) {
648
778
  return "text-collision"
649
779
  }
650
- if (this.sharesChipBoundary(bounds)) {
651
- return "chip-collision"
652
- }
653
780
  if (traceCrossesBoundsInterior(bounds, this.traceMap)) {
654
781
  return "trace-collision"
655
782
  }
783
+ if (this.isTraceTooCloseToLabel(bounds, label)) {
784
+ return "trace-clearance-violation"
785
+ }
656
786
  if (this.intersectsAnyOtherNetLabel(bounds, labelIndex)) {
657
787
  return "netlabel-collision"
658
788
  }
659
789
  return "valid"
660
790
  }
661
791
 
792
+ private isTraceTooCloseToLabel(bounds: Bounds, label: NetLabelPlacement) {
793
+ if (!this.shouldCheckTraceClearanceForLabel(label)) return false
794
+
795
+ const clearanceBounds = this.getLabelTraceClearanceBounds(bounds)
796
+ for (const trace of Object.values(this.traceMap)) {
797
+ if (trace.globalConnNetId === label.globalConnNetId) continue
798
+ if (tracePathIntersectsBounds(trace.tracePath, clearanceBounds)) {
799
+ return true
800
+ }
801
+ }
802
+
803
+ return false
804
+ }
805
+
806
+ private getLabelTraceClearanceBounds(bounds: Bounds): Bounds {
807
+ return {
808
+ minX: bounds.minX - LABEL_TRACE_CLEARANCE,
809
+ minY: bounds.minY - LABEL_TRACE_CLEARANCE,
810
+ maxX: bounds.maxX + LABEL_TRACE_CLEARANCE,
811
+ maxY: bounds.maxY + LABEL_TRACE_CLEARANCE,
812
+ }
813
+ }
814
+
815
+ private shouldCheckTraceClearanceForLabel(label: NetLabelPlacement) {
816
+ if (!this.isPortOnlyLabel(label)) return false
817
+ if (!this.hasRoutedLabelOnSameNet(label)) return false
818
+
819
+ const orientations = this.getAvailableOrientations(label)
820
+ return orientations.length === 1
821
+ }
822
+
823
+ private hasRoutedLabelOnSameNet(label: NetLabelPlacement) {
824
+ for (const otherLabel of this.outputNetLabelPlacements) {
825
+ if (otherLabel.globalConnNetId !== label.globalConnNetId) continue
826
+ if (otherLabel.mspConnectionPairIds.length > 0) return true
827
+ }
828
+
829
+ return false
830
+ }
831
+
832
+ private isPortOnlyLabel(label: NetLabelPlacement) {
833
+ return label.mspConnectionPairIds.length === 0
834
+ }
835
+
662
836
  private intersectsAnyOtherNetLabel(bounds: Bounds, labelIndex: number) {
663
837
  for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
664
838
  if (i === labelIndex) continue
@@ -32,9 +32,14 @@ export type CandidateStatus =
32
32
  | "chip-collision"
33
33
  | "text-collision"
34
34
  | "trace-collision"
35
+ | "trace-clearance-violation"
35
36
  | "netlabel-collision"
36
37
 
37
- export type CandidatePhase = "rotate" | "shift" | "lateral-shift"
38
+ export type CandidatePhase =
39
+ | "rotate"
40
+ | "trace-anchor"
41
+ | "shift"
42
+ | "lateral-shift"
38
43
 
39
44
  export type EvaluatedCandidate = CandidateLabel & {
40
45
  status: CandidateStatus
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.80",
4
+ "version": "0.0.82",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -86,7 +86,7 @@ orientation: x-" data-x="-1.485" data-y="-0.2" cx="256.11911623439005" cy="288.2
86
86
  </g>
87
87
  <g>
88
88
  <circle data-type="point" data-label="anchorPoint
89
- orientation: y-" data-x="-1.3860000000000001" data-y="-0.301" cx="261.4447646493756" cy="293.6945244956772" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
89
+ orientation: y-" data-x="-1.3860000000000001" data-y="-0.601" cx="261.4447646493756" cy="309.8328530259365" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
90
90
  </g>
91
91
  <g>
92
92
  <circle data-type="point" data-label="anchorPoint
@@ -218,10 +218,10 @@ orientation: x-" data-x="2.6749999999999994" data-y="-2.7362499999999996" cx="47
218
218
  <polyline data-points="-1.2850000000000001,0.20000000000000018 -1.866,0.20000000000000018 -1.866,-4.440892098500626e-16 -2.87,-4.440892098500626e-16 -2.87,0.19999999999999973" data-type="line" data-label="" points="266.8780019212296,266.74351585014404 235.62343900096062,266.74351585014404 235.62343900096062,277.5024015369836 181.61383285302594,277.5024015369836 181.61383285302594,266.74351585014404" fill="none" stroke="purple" stroke-width="1" />
219
219
  </g>
220
220
  <g>
221
- <polyline data-points="-1.2850000000000001,-0.2 -1.485,-0.2 -1.485,-0.20099999999999993 -2.166,-0.20099999999999993 -2.166,0.5 -1.485,0.5 -1.485,0.2 -1.2850000000000001,0.2" data-type="line" data-label="" points="266.8780019212296,288.26128722382316 256.11911623439005,288.26128722382316 256.11911623439005,288.3150816522574 219.48511047070127,288.3150816522574 219.48511047070127,250.60518731988466 256.11911623439005,250.60518731988466 256.11911623439005,266.74351585014404 266.8780019212296,266.74351585014404" fill="none" stroke="purple" stroke-width="1" />
221
+ <polyline data-points="-1.2850000000000001,-0.2 -1.485,-0.2 -1.485,-0.5 -2.166,-0.5 -2.166,0.5 -1.485,0.5 -1.485,0.2 -1.2850000000000001,0.2" data-type="line" data-label="" points="266.8780019212296,288.26128722382316 256.11911623439005,288.26128722382316 256.11911623439005,304.39961575408256 219.48511047070127,304.39961575408256 219.48511047070127,250.60518731988466 256.11911623439005,250.60518731988466 256.11911623439005,266.74351585014404 266.8780019212296,266.74351585014404" fill="none" stroke="purple" stroke-width="1" />
222
222
  </g>
223
223
  <g>
224
- <polyline data-points="-1.2850000000000001,0 -1.3860000000000001,0 -1.3860000000000001,-0.301" data-type="line" data-label="" points="266.8780019212296,277.5024015369836 261.4447646493756,277.5024015369836 261.4447646493756,293.6945244956772" fill="none" stroke="purple" stroke-width="1" />
224
+ <polyline data-points="-1.2850000000000001,0 -1.3860000000000001,0 -1.3860000000000001,-0.601" data-type="line" data-label="" points="266.8780019212296,277.5024015369836 261.4447646493756,277.5024015369836 261.4447646493756,309.8328530259365" fill="none" stroke="purple" stroke-width="1" />
225
225
  </g>
226
226
  <g>
227
227
  <rect data-type="rect" data-label="schematic_component_0" data-x="-2.71" data-y="0.7199999999999998" x="167.08933717579254" y="210.79731027857824" width="46.26320845341016" height="55.9462055715658" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.018589285714285714" />
@@ -257,7 +257,7 @@ globalConnNetId: connectivity_net1" data-x="-1.725" data-y="-0.2" x="230.2977905
257
257
  </g>
258
258
  <g>
259
259
  <rect data-type="rect" data-label="netId: GND
260
- globalConnNetId: connectivity_net2" data-x="-1.3860000000000001" data-y="-0.5409999999999999" x="256.0653218059558" y="293.6945244956772" width="10.758885686839562" height="25.821325648414984" fill="#00000066" stroke="#000000" stroke-width="0.018589285714285714" />
260
+ globalConnNetId: connectivity_net2" data-x="-1.3860000000000001" data-y="-0.841" x="256.0653218059558" y="309.8328530259365" width="10.758885686839562" height="25.821325648414984" fill="#00000066" stroke="#000000" stroke-width="0.018589285714285714" />
261
261
  </g>
262
262
  <g>
263
263
  <rect data-type="rect" data-label="netId: GND
@@ -0,0 +1,219 @@
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="BT1.1
5
+ x+" data-x="-2.6" data-y="2.2" cx="84.13793103448276" cy="230.89655172413794" r="3" fill="hsl(114, 100%, 50%, 0.8)" />
6
+ </g>
7
+ <g>
8
+ <circle data-type="point" data-label="BT1.2
9
+ x+" data-x="-2.6" data-y="1.8" cx="84.13793103448276" cy="252.9655172413793" r="3" fill="hsl(115, 100%, 50%, 0.8)" />
10
+ </g>
11
+ <g>
12
+ <circle data-type="point" data-label="SW1.1
13
+ x-" data-x="-0.5" data-y="2" cx="200" cy="241.93103448275863" r="3" fill="hsl(104, 100%, 50%, 0.8)" />
14
+ </g>
15
+ <g>
16
+ <circle data-type="point" data-label="SW1.2
17
+ x+" data-x="0.5" data-y="2" cx="255.17241379310343" cy="241.93103448275863" r="3" fill="hsl(105, 100%, 50%, 0.8)" />
18
+ </g>
19
+ <g>
20
+ <circle data-type="point" data-label="R1.1
21
+ x-" data-x="2.45" data-y="1" cx="362.7586206896552" cy="297.1034482758621" r="3" fill="hsl(226, 100%, 50%, 0.8)" />
22
+ </g>
23
+ <g>
24
+ <circle data-type="point" data-label="R1.2
25
+ x+" data-x="3.55" data-y="1" cx="423.44827586206895" cy="297.1034482758621" r="3" fill="hsl(227, 100%, 50%, 0.8)" />
26
+ </g>
27
+ <g>
28
+ <circle data-type="point" data-label="R2.1
29
+ x-" data-x="2.45" data-y="-1" cx="362.7586206896552" cy="407.44827586206895" r="3" fill="hsl(107, 100%, 50%, 0.8)" />
30
+ </g>
31
+ <g>
32
+ <circle data-type="point" data-label="R2.2
33
+ x+" data-x="3.55" data-y="-1" cx="423.44827586206895" cy="407.44827586206895" r="3" fill="hsl(108, 100%, 50%, 0.8)" />
34
+ </g>
35
+ <g>
36
+ <circle data-type="point" data-label="D1.1
37
+ x-" data-x="5.45" data-y="1" cx="528.2758620689655" cy="297.1034482758621" r="3" fill="hsl(32, 100%, 50%, 0.8)" />
38
+ </g>
39
+ <g>
40
+ <circle data-type="point" data-label="D1.2
41
+ x+" data-x="6.55" data-y="1" cx="588.9655172413793" cy="297.1034482758621" r="3" fill="hsl(33, 100%, 50%, 0.8)" />
42
+ </g>
43
+ <g>
44
+ <circle data-type="point" data-label="D2.1
45
+ x-" data-x="5.45" data-y="-1" cx="528.2758620689655" cy="407.44827586206895" r="3" fill="hsl(273, 100%, 50%, 0.8)" />
46
+ </g>
47
+ <g>
48
+ <circle data-type="point" data-label="D2.2
49
+ x+" data-x="6.55" data-y="-1" cx="588.9655172413793" cy="407.44827586206895" r="3" fill="hsl(274, 100%, 50%, 0.8)" />
50
+ </g>
51
+ <g>
52
+ <circle data-type="point" data-label="anchorPoint
53
+ orientation: y+" data-x="-2.075" data-y="2.2" cx="113.10344827586206" cy="230.89655172413794" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
54
+ </g>
55
+ <g>
56
+ <circle data-type="point" data-label="anchorPoint
57
+ orientation: y-" data-x="-2.499" data-y="1.5990000000000002" cx="89.7103448275862" cy="264.0551724137931" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
58
+ </g>
59
+ <g>
60
+ <circle data-type="point" data-label="anchorPoint
61
+ orientation: y-" data-x="6.65" data-y="-1" cx="594.4827586206897" cy="407.44827586206895" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
62
+ </g>
63
+ <g>
64
+ <circle data-type="point" data-label="anchorPoint
65
+ orientation: y-" data-x="2.25" data-y="-1" cx="351.7241379310345" cy="407.44827586206895" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
66
+ </g>
67
+ <g>
68
+ <circle data-type="point" data-label="anchorPoint
69
+ orientation: y+" data-x="4.5" data-y="1" cx="475.8620689655172" cy="297.1034482758621" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
70
+ </g>
71
+ <g>
72
+ <circle data-type="point" data-label="anchorPoint
73
+ orientation: y+" data-x="4.5" data-y="-1" cx="475.8620689655172" cy="407.44827586206895" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
74
+ </g>
75
+ <g>
76
+ <polyline data-points="-2.6,2.2 -0.5,2" data-type="line" data-label="" points="84.13793103448276,230.89655172413794 200,241.93103448275863" fill="none" stroke="hsl(0, 100%, 50%, 0.8)" stroke-width="1" />
77
+ </g>
78
+ <g>
79
+ <polyline data-points="0.5,2 2.45,1" data-type="line" data-label="" points="255.17241379310343,241.93103448275863 362.7586206896552,297.1034482758621" fill="none" stroke="hsl(304, 100%, 50%, 0.8)" stroke-width="1" />
80
+ </g>
81
+ <g>
82
+ <polyline data-points="0.5,2 2.45,-1" data-type="line" data-label="" points="255.17241379310343,241.93103448275863 362.7586206896552,407.44827586206895" fill="none" stroke="hsl(304, 100%, 50%, 0.8)" stroke-width="1" />
83
+ </g>
84
+ <g>
85
+ <polyline data-points="3.55,1 5.45,1" data-type="line" data-label="" points="423.44827586206895,297.1034482758621 528.2758620689655,297.1034482758621" fill="none" stroke="hsl(312, 100%, 50%, 0.8)" stroke-width="1" />
86
+ </g>
87
+ <g>
88
+ <polyline data-points="3.55,-1 5.45,-1" data-type="line" data-label="" points="423.44827586206895,407.44827586206895 528.2758620689655,407.44827586206895" fill="none" stroke="hsl(320, 100%, 50%, 0.8)" stroke-width="1" />
89
+ </g>
90
+ <g>
91
+ <polyline data-points="-2.6,1.8 6.55,1" data-type="line" data-label="" points="84.13793103448276,252.9655172413793 588.9655172413793,297.1034482758621" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
92
+ </g>
93
+ <g>
94
+ <polyline data-points="-2.6,1.8 6.55,-1" data-type="line" data-label="" points="84.13793103448276,252.9655172413793 588.9655172413793,407.44827586206895" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
95
+ </g>
96
+ <g>
97
+ <polyline data-points="6.55,1 6.55,-1" data-type="line" data-label="" points="588.9655172413793,297.1034482758621 588.9655172413793,407.44827586206895" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
98
+ </g>
99
+ <g>
100
+ <polyline data-points="-2.6,2.2 -1.55,2.2 -1.55,2 -0.5,2" data-type="line" data-label="" points="84.13793103448276,230.89655172413794 142.06896551724137,230.89655172413794 142.06896551724137,241.93103448275863 200,241.93103448275863" fill="none" stroke="purple" stroke-width="1" />
101
+ </g>
102
+ <g>
103
+ <polyline data-points="2.45,-1 2.25,-1 2.25,1 2.45,1" data-type="line" data-label="" points="362.7586206896552,407.44827586206895 351.7241379310345,407.44827586206895 351.7241379310345,297.1034482758621 362.7586206896552,297.1034482758621" fill="none" stroke="purple" stroke-width="1" />
104
+ </g>
105
+ <g>
106
+ <polyline data-points="0.5,2 1.475,2 1.475,1 2.45,1" data-type="line" data-label="" points="255.17241379310343,241.93103448275863 308.9655172413793,241.93103448275863 308.9655172413793,297.1034482758621 362.7586206896552,297.1034482758621" fill="none" stroke="purple" stroke-width="1" />
107
+ </g>
108
+ <g>
109
+ <polyline data-points="3.55,1 5.45,1" data-type="line" data-label="" points="423.44827586206895,297.1034482758621 528.2758620689655,297.1034482758621" fill="none" stroke="purple" stroke-width="1" />
110
+ </g>
111
+ <g>
112
+ <polyline data-points="3.55,-1 5.45,-1" data-type="line" data-label="" points="423.44827586206895,407.44827586206895 528.2758620689655,407.44827586206895" fill="none" stroke="purple" stroke-width="1" />
113
+ </g>
114
+ <g>
115
+ <polyline data-points="6.55,-1 6.75,-1 6.75,1 6.55,1" data-type="line" data-label="" points="588.9655172413793,407.44827586206895 600,407.44827586206895 600,297.1034482758621 588.9655172413793,297.1034482758621" fill="none" stroke="purple" stroke-width="1" />
116
+ </g>
117
+ <g>
118
+ <polyline data-points="-2.6,1.8 -2.499,1.8 -2.499,1.5990000000000002" data-type="line" data-label="" points="84.13793103448276,252.9655172413793 89.7103448275862,252.9655172413793 89.7103448275862,264.0551724137931" fill="none" stroke="purple" stroke-width="1" />
119
+ </g>
120
+ <g>
121
+ <rect data-type="rect" data-label="BT1" data-x="-3" data-y="2" x="40" y="219.86206896551727" width="44.13793103448276" height="44.13793103448273" fill="hsl(199, 100%, 50%, 0.8)" stroke="black" stroke-width="0.018125000000000002" />
122
+ </g>
123
+ <g>
124
+ <rect data-type="rect" data-label="SW1" data-x="0" data-y="2" x="200" y="219.86206896551727" width="55.17241379310343" height="44.13793103448273" fill="hsl(69, 100%, 50%, 0.8)" stroke="black" stroke-width="0.018125000000000002" />
125
+ </g>
126
+ <g>
127
+ <rect data-type="rect" data-label="R1" data-x="3" data-y="1" x="362.7586206896552" y="286.3748772413793" width="60.68965517241378" height="21.45714206896554" fill="hsl(71, 100%, 50%, 0.8)" stroke="black" stroke-width="0.018125000000000002" />
128
+ </g>
129
+ <g>
130
+ <rect data-type="rect" data-label="R2" data-x="3" data-y="-1" x="362.7586206896552" y="396.7197048275862" width="60.68965517241378" height="21.45714206896554" fill="hsl(72, 100%, 50%, 0.8)" stroke="black" stroke-width="0.018125000000000002" />
131
+ </g>
132
+ <g>
133
+ <rect data-type="rect" data-label="D1" data-x="6" data-y="1" x="528.2758620689655" y="286.3748772413793" width="60.689655172413836" height="21.45714206896554" fill="hsl(357, 100%, 50%, 0.8)" stroke="black" stroke-width="0.018125000000000002" />
134
+ </g>
135
+ <g>
136
+ <rect data-type="rect" data-label="D2" data-x="6" data-y="-1" x="528.2758620689655" y="396.7197048275862" width="60.689655172413836" height="21.45714206896554" fill="hsl(358, 100%, 50%, 0.8)" stroke="black" stroke-width="0.018125000000000002" />
137
+ </g>
138
+ <g>
139
+ <rect data-type="rect" data-label="netId: BT1.plus to SW1.in
140
+ globalConnNetId: connectivity_net0" data-x="-2.075" data-y="2.4250000000000003" x="107.5862068965517" y="206.06896551724137" width="11.034482758620712" height="24.82758620689657" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.018125000000000002" />
141
+ </g>
142
+ <g>
143
+ <rect data-type="rect" data-label="netId: GND
144
+ globalConnNetId: connectivity_net4" data-x="-2.499" data-y="1.3590000000000002" x="84.19310344827585" y="264.0551724137931" width="11.034482758620697" height="26.48275862068965" fill="#00000066" stroke="#000000" stroke-width="0.018125000000000002" />
145
+ </g>
146
+ <g>
147
+ <rect data-type="rect" data-label="netId: GND
148
+ globalConnNetId: connectivity_net4" data-x="6.65" data-y="-1.24" x="588.9655172413793" y="407.44827586206895" width="11.034482758620697" height="26.48275862068965" fill="#00000066" stroke="#000000" stroke-width="0.018125000000000002" />
149
+ </g>
150
+ <g>
151
+ <rect data-type="rect" data-label="netId: SW1.out to R2.left
152
+ globalConnNetId: connectivity_net1" data-x="2.25" data-y="-1.225" x="346.2068965517241" y="407.44827586206895" width="11.034482758620697" height="24.82758620689657" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.018125000000000002" />
153
+ </g>
154
+ <g>
155
+ <rect data-type="rect" data-label="netId: R1.right to D1.a
156
+ globalConnNetId: connectivity_net2" data-x="4.5" data-y="1.225" x="470.34482758620686" y="272.2758620689655" width="11.034482758620697" height="24.82758620689657" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.018125000000000002" />
157
+ </g>
158
+ <g>
159
+ <rect data-type="rect" data-label="netId: R2.right to D2.a
160
+ globalConnNetId: connectivity_net3" data-x="4.5" data-y="-0.775" x="470.34482758620686" y="382.62068965517244" width="11.034482758620697" height="24.827586206896513" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.018125000000000002" />
161
+ </g>
162
+ <g id="crosshair" style="display: none">
163
+ <line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5" />
164
+ <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>
165
+ </g>
166
+ <script>
167
+ <![CDATA[
168
+ document.currentScript.parentElement.addEventListener('mousemove', (e) => {
169
+ const svg = e.currentTarget;
170
+ const rect = svg.getBoundingClientRect();
171
+ const x = e.clientX - rect.left;
172
+ const y = e.clientY - rect.top;
173
+ const crosshair = svg.getElementById('crosshair');
174
+ const h = svg.getElementById('crosshair-h');
175
+ const v = svg.getElementById('crosshair-v');
176
+ const coords = svg.getElementById('coordinates');
177
+
178
+ crosshair.style.display = 'block';
179
+ h.setAttribute('x1', '0');
180
+ h.setAttribute('x2', '640');
181
+ h.setAttribute('y1', y);
182
+ h.setAttribute('y2', y);
183
+ v.setAttribute('x1', x);
184
+ v.setAttribute('x2', x);
185
+ v.setAttribute('y1', '0');
186
+ v.setAttribute('y2', '640');
187
+
188
+ // Calculate real coordinates using inverse transformation
189
+ const matrix = {
190
+ "a": 55.172413793103445,
191
+ "c": 0,
192
+ "e": 227.58620689655172,
193
+ "b": 0,
194
+ "d": -55.172413793103445,
195
+ "f": 352.2758620689655
196
+ };
197
+ // Manually invert and apply the affine transform
198
+ // Since we only use translate and scale, we can directly compute:
199
+ // x' = (x - tx) / sx
200
+ // y' = (y - ty) / sy
201
+ const sx = matrix.a;
202
+ const sy = matrix.d;
203
+ const tx = matrix.e;
204
+ const ty = matrix.f;
205
+ const realPoint = {
206
+ x: (x - tx) / sx,
207
+ y: (y - ty) / sy // Flip y back since we used negative scale
208
+ }
209
+
210
+ coords.textContent = `(${realPoint.x.toFixed(2)}, ${realPoint.y.toFixed(2)})`;
211
+ coords.setAttribute('x', (x + 5).toString());
212
+ coords.setAttribute('y', (y - 5).toString());
213
+ });
214
+ document.currentScript.parentElement.addEventListener('mouseleave', () => {
215
+ document.currentScript.parentElement.getElementById('crosshair').style.display = 'none';
216
+ });
217
+ ]]>
218
+ </script>
219
+ </svg>
@@ -0,0 +1,163 @@
1
+ {
2
+ "chips": [
3
+ {
4
+ "chipId": "BT1",
5
+ "center": {
6
+ "x": -3,
7
+ "y": 2
8
+ },
9
+ "width": 0.8,
10
+ "height": 0.8,
11
+ "pins": [
12
+ {
13
+ "pinId": "BT1.1",
14
+ "x": -2.6,
15
+ "y": 2.2
16
+ },
17
+ {
18
+ "pinId": "BT1.2",
19
+ "x": -2.6,
20
+ "y": 1.8
21
+ }
22
+ ]
23
+ },
24
+ {
25
+ "chipId": "SW1",
26
+ "center": {
27
+ "x": 0,
28
+ "y": 2
29
+ },
30
+ "width": 1,
31
+ "height": 0.8,
32
+ "pins": [
33
+ {
34
+ "pinId": "SW1.1",
35
+ "x": -0.5,
36
+ "y": 2
37
+ },
38
+ {
39
+ "pinId": "SW1.2",
40
+ "x": 0.5,
41
+ "y": 2
42
+ }
43
+ ]
44
+ },
45
+ {
46
+ "chipId": "R1",
47
+ "center": {
48
+ "x": 3,
49
+ "y": 1
50
+ },
51
+ "width": 1.06,
52
+ "height": 0.3889107,
53
+ "pins": [
54
+ {
55
+ "pinId": "R1.1",
56
+ "x": 2.45,
57
+ "y": 1
58
+ },
59
+ {
60
+ "pinId": "R1.2",
61
+ "x": 3.55,
62
+ "y": 1
63
+ }
64
+ ]
65
+ },
66
+ {
67
+ "chipId": "R2",
68
+ "center": {
69
+ "x": 3,
70
+ "y": -1
71
+ },
72
+ "width": 1.06,
73
+ "height": 0.3889107,
74
+ "pins": [
75
+ {
76
+ "pinId": "R2.1",
77
+ "x": 2.45,
78
+ "y": -1
79
+ },
80
+ {
81
+ "pinId": "R2.2",
82
+ "x": 3.55,
83
+ "y": -1
84
+ }
85
+ ]
86
+ },
87
+ {
88
+ "chipId": "D1",
89
+ "center": {
90
+ "x": 6,
91
+ "y": 1
92
+ },
93
+ "width": 1.06,
94
+ "height": 0.3889107,
95
+ "pins": [
96
+ {
97
+ "pinId": "D1.1",
98
+ "x": 5.45,
99
+ "y": 1
100
+ },
101
+ {
102
+ "pinId": "D1.2",
103
+ "x": 6.55,
104
+ "y": 1
105
+ }
106
+ ]
107
+ },
108
+ {
109
+ "chipId": "D2",
110
+ "center": {
111
+ "x": 6,
112
+ "y": -1
113
+ },
114
+ "width": 1.06,
115
+ "height": 0.3889107,
116
+ "pins": [
117
+ {
118
+ "pinId": "D2.1",
119
+ "x": 5.45,
120
+ "y": -1
121
+ },
122
+ {
123
+ "pinId": "D2.2",
124
+ "x": 6.55,
125
+ "y": -1
126
+ }
127
+ ]
128
+ }
129
+ ],
130
+ "directConnections": [
131
+ {
132
+ "pinIds": ["BT1.1", "SW1.1"],
133
+ "netId": "BT1.plus to SW1.in"
134
+ },
135
+ {
136
+ "pinIds": ["SW1.2", "R1.1"],
137
+ "netId": "SW1.out to R1.left"
138
+ },
139
+ {
140
+ "pinIds": ["SW1.2", "R2.1"],
141
+ "netId": "SW1.out to R2.left"
142
+ },
143
+ {
144
+ "pinIds": ["R1.2", "D1.1"],
145
+ "netId": "R1.right to D1.a"
146
+ },
147
+ {
148
+ "pinIds": ["R2.2", "D2.1"],
149
+ "netId": "R2.right to D2.a"
150
+ }
151
+ ],
152
+ "netConnections": [
153
+ {
154
+ "netId": "GND",
155
+ "pinIds": ["BT1.2", "D1.2", "D2.2"],
156
+ "netLabelWidth": 0.48
157
+ }
158
+ ],
159
+ "availableNetLabelOrientations": {
160
+ "GND": ["y-"]
161
+ },
162
+ "maxMspPairDistance": 8
163
+ }
@@ -0,0 +1,16 @@
1
+ import { expect, test } from "bun:test"
2
+ import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
3
+ import inputProblem from "./assets/bugreport-001-gnd-overlap.input.json"
4
+ import "tests/fixtures/matcher"
5
+
6
+ test("bugreport-001-gnd-overlap", () => {
7
+ const solver = new SchematicTracePipelineSolver(inputProblem as any)
8
+ solver.solve()
9
+
10
+ expect(
11
+ solver.netLabelPlacementSolver!.netLabelPlacements.some(
12
+ (placement) => placement.netId === "GND",
13
+ ),
14
+ ).toBe(true)
15
+ expect(solver).toMatchSolverSnapshot(import.meta.path)
16
+ })
@@ -0,0 +1,68 @@
1
+ import { expect, test } from "bun:test"
2
+ import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
3
+ import type { InputProblem } from "lib/types/InputProblem"
4
+
5
+ const inputProblem: InputProblem = {
6
+ chips: [
7
+ {
8
+ chipId: "D1",
9
+ center: { x: 7.9, y: 0 },
10
+ width: 0.7400000000000002,
11
+ height: 1.04,
12
+ pins: [
13
+ { pinId: "D1.1", x: 8, y: -0.52 },
14
+ { pinId: "D1.2", x: 8, y: 0.52 },
15
+ ],
16
+ },
17
+ {
18
+ chipId: "D2",
19
+ center: { x: 10, y: 0.07 },
20
+ width: 1.0399999999999991,
21
+ height: 0.68,
22
+ pins: [
23
+ { pinId: "D2.1", x: 9.48, y: 0 },
24
+ { pinId: "D2.2", x: 10.52, y: 0 },
25
+ ],
26
+ },
27
+ ],
28
+ directConnections: [],
29
+ netConnections: [
30
+ {
31
+ netId: "VCC",
32
+ pinIds: ["D1.1", "D2.1"],
33
+ netLabelWidth: 0.48,
34
+ netLabelHeight: 0.42,
35
+ },
36
+ {
37
+ netId: "GND",
38
+ pinIds: ["D1.2", "D2.2"],
39
+ netLabelWidth: 0.48,
40
+ netLabelHeight: 0.42,
41
+ },
42
+ ],
43
+ textBoxes: [],
44
+ availableNetLabelOrientations: {
45
+ GND: ["y-"],
46
+ VCC: ["y+"],
47
+ },
48
+ maxMspPairDistance: 0.1,
49
+ }
50
+
51
+ test("rotated component rail label stays on the existing trace", () => {
52
+ const solver = new SchematicTracePipelineSolver(inputProblem)
53
+ solver.solve()
54
+
55
+ const traces = solver.netLabelTraceCollisionSolver!.getOutput().traces
56
+ const labels =
57
+ solver.netLabelNetLabelCollisionSolver!.getOutput().netLabelPlacements
58
+ const gndLabel = labels.find((label) => label.netId === "GND")
59
+
60
+ expect(
61
+ traces.some((trace) =>
62
+ trace.mspPairId.startsWith("available-net-orientation"),
63
+ ),
64
+ ).toBe(false)
65
+ expect(gndLabel?.orientation).toBe("y-")
66
+ expect(gndLabel?.anchorPoint.x).toBeCloseTo(10.72)
67
+ expect(gndLabel?.anchorPoint.y).toBeCloseTo(0)
68
+ })