@tscircuit/schematic-trace-solver 0.0.38 → 0.0.40

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
@@ -392,14 +392,13 @@ declare class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
392
392
  private netTempLabelPlacements;
393
393
  private netLabelPlacements;
394
394
  private updatedTraces;
395
- private updatedTracesMap;
396
395
  private mergedLabelNetIdMap;
397
396
  private detourCountByLabel;
398
397
  private readonly PADDING_BUFFER;
399
398
  constructor(solverInput: TraceLabelOverlapAvoidanceSolverInput);
400
399
  _step(): void;
401
400
  getOutput(): {
402
- traceMap: Map<string, SolvedTracePath>;
401
+ traces: SolvedTracePath[];
403
402
  netLabelPlacements: NetLabelPlacement[];
404
403
  };
405
404
  visualize(): GraphicsObject;
package/dist/index.js CHANGED
@@ -1015,12 +1015,12 @@ var applyJogToTerminalSegment = ({
1015
1015
  if (si !== 0 && si !== pts.length - 2) return;
1016
1016
  const start = pts[si];
1017
1017
  const end = pts[si + 1];
1018
- const isVertical3 = Math.abs(start.x - end.x) < EPS4;
1019
- const isHorizontal3 = Math.abs(start.y - end.y) < EPS4;
1020
- if (!isVertical3 && !isHorizontal3) return;
1021
- const segDir = isVertical3 ? end.y > start.y ? 1 : -1 : end.x > start.x ? 1 : -1;
1018
+ const isVertical2 = Math.abs(start.x - end.x) < EPS4;
1019
+ const isHorizontal2 = Math.abs(start.y - end.y) < EPS4;
1020
+ if (!isVertical2 && !isHorizontal2) return;
1021
+ const segDir = isVertical2 ? end.y > start.y ? 1 : -1 : end.x > start.x ? 1 : -1;
1022
1022
  if (si === 0) {
1023
- if (isVertical3) {
1023
+ if (isVertical2) {
1024
1024
  const jogY = start.y + segDir * JOG_SIZE;
1025
1025
  pts.splice(
1026
1026
  1,
@@ -1040,7 +1040,7 @@ var applyJogToTerminalSegment = ({
1040
1040
  );
1041
1041
  }
1042
1042
  } else {
1043
- if (isVertical3) {
1043
+ if (isVertical2) {
1044
1044
  const jogY = end.y - segDir * JOG_SIZE;
1045
1045
  pts.splice(
1046
1046
  si,
@@ -1120,10 +1120,10 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1120
1120
  } else {
1121
1121
  const start = pts[si];
1122
1122
  const end = pts[si + 1];
1123
- const isVertical3 = Math.abs(start.x - end.x) < EPS4;
1124
- const isHorizontal3 = Math.abs(start.y - end.y) < EPS4;
1125
- if (!isVertical3 && !isHorizontal3) continue;
1126
- if (isVertical3) {
1123
+ const isVertical2 = Math.abs(start.x - end.x) < EPS4;
1124
+ const isHorizontal2 = Math.abs(start.y - end.y) < EPS4;
1125
+ if (!isVertical2 && !isHorizontal2) continue;
1126
+ if (isVertical2) {
1127
1127
  start.x += offset;
1128
1128
  end.x += offset;
1129
1129
  } else {
@@ -2508,6 +2508,32 @@ var countTurns = (points) => {
2508
2508
  return turns;
2509
2509
  };
2510
2510
 
2511
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/tryConnectPoints.ts
2512
+ var tryConnectPoints = (start, end) => {
2513
+ const candidates = [];
2514
+ if (start.x === end.x || start.y === end.y) {
2515
+ candidates.push([start, end]);
2516
+ } else {
2517
+ candidates.push([start, { x: end.x, y: start.y }, end]);
2518
+ candidates.push([start, { x: start.x, y: end.y }, end]);
2519
+ }
2520
+ return candidates;
2521
+ };
2522
+
2523
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/hasCollisionsWithLabels.ts
2524
+ var hasCollisionsWithLabels = (pathSegments, labels) => {
2525
+ for (let i = 0; i < pathSegments.length - 1; i++) {
2526
+ const p1 = pathSegments[i];
2527
+ const p2 = pathSegments[i + 1];
2528
+ for (const label of labels) {
2529
+ if (segmentIntersectsRect(p1, p2, label)) {
2530
+ return true;
2531
+ }
2532
+ }
2533
+ }
2534
+ return false;
2535
+ };
2536
+
2511
2537
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/turnMinimization.ts
2512
2538
  var minimizeTurns = ({
2513
2539
  path,
@@ -2517,28 +2543,6 @@ var minimizeTurns = ({
2517
2543
  if (path.length <= 2) {
2518
2544
  return path;
2519
2545
  }
2520
- const hasCollisionsWithLabels = (pathSegments, labels) => {
2521
- for (let i = 0; i < pathSegments.length - 1; i++) {
2522
- const p1 = pathSegments[i];
2523
- const p2 = pathSegments[i + 1];
2524
- for (const label of labels) {
2525
- if (segmentIntersectsRect(p1, p2, label)) {
2526
- return true;
2527
- }
2528
- }
2529
- }
2530
- return false;
2531
- };
2532
- const tryConnectPoints = (start, end) => {
2533
- const candidates = [];
2534
- if (start.x === end.x || start.y === end.y) {
2535
- candidates.push([start, end]);
2536
- } else {
2537
- candidates.push([start, { x: end.x, y: start.y }, end]);
2538
- candidates.push([start, { x: start.x, y: end.y }, end]);
2539
- }
2540
- return candidates;
2541
- };
2542
2546
  const recognizeStairStepPattern = (pathToCheck, startIdx) => {
2543
2547
  if (startIdx >= pathToCheck.length - 3) return -1;
2544
2548
  let endIdx = startIdx;
@@ -2672,6 +2676,8 @@ var minimizeTurns = ({
2672
2676
  const finalSimplifiedPath = simplifyPath(optimizedPath);
2673
2677
  return finalSimplifiedPath;
2674
2678
  };
2679
+
2680
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/minimizeTurnsWithFilteredLabels.ts
2675
2681
  var minimizeTurnsWithFilteredLabels = ({
2676
2682
  traces,
2677
2683
  problem,
@@ -2684,7 +2690,7 @@ var minimizeTurnsWithFilteredLabels = ({
2684
2690
  const newTraces = traces.map((trace) => {
2685
2691
  const originalPath = trace.tracePath;
2686
2692
  const filteredLabels = allLabelPlacements.filter((label) => {
2687
- const originalNetIds = mergedLabelNetIdMap.get(label.globalConnNetId);
2693
+ const originalNetIds = mergedLabelNetIdMap[label.globalConnNetId];
2688
2694
  if (originalNetIds) {
2689
2695
  return !originalNetIds.has(trace.globalConnNetId);
2690
2696
  }
@@ -2865,7 +2871,6 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
2865
2871
  netTempLabelPlacements;
2866
2872
  netLabelPlacements;
2867
2873
  updatedTraces;
2868
- updatedTracesMap;
2869
2874
  mergedLabelNetIdMap;
2870
2875
  detourCountByLabel;
2871
2876
  PADDING_BUFFER = 0.1;
@@ -2874,28 +2879,27 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
2874
2879
  this.problem = solverInput.inputProblem;
2875
2880
  this.traces = solverInput.traces;
2876
2881
  this.updatedTraces = [...solverInput.traces];
2877
- this.updatedTracesMap = /* @__PURE__ */ new Map();
2878
- this.mergedLabelNetIdMap = /* @__PURE__ */ new Map();
2879
- this.detourCountByLabel = /* @__PURE__ */ new Map();
2882
+ this.mergedLabelNetIdMap = {};
2883
+ this.detourCountByLabel = {};
2880
2884
  const originalLabels = solverInput.netLabelPlacements;
2881
2885
  this.netLabelPlacements = originalLabels;
2882
2886
  if (!originalLabels || originalLabels.length === 0) {
2883
2887
  this.netTempLabelPlacements = [];
2884
2888
  return;
2885
2889
  }
2886
- const labelGroups = /* @__PURE__ */ new Map();
2890
+ const labelGroups = {};
2887
2891
  for (const p of originalLabels) {
2888
2892
  if (p.pinIds.length === 0) continue;
2889
2893
  const chipId = p.pinIds[0].split(".")[0];
2890
2894
  if (!chipId) continue;
2891
2895
  const key = `${chipId}-${p.orientation}`;
2892
- if (!labelGroups.has(key)) {
2893
- labelGroups.set(key, []);
2896
+ if (!(key in labelGroups)) {
2897
+ labelGroups[key] = [];
2894
2898
  }
2895
- labelGroups.get(key).push(p);
2899
+ labelGroups[key].push(p);
2896
2900
  }
2897
2901
  const finalPlacements = [];
2898
- for (const [key, group] of labelGroups.entries()) {
2902
+ for (const [key, group] of Object.entries(labelGroups)) {
2899
2903
  if (group.length <= 1) {
2900
2904
  finalPlacements.push(...group);
2901
2905
  continue;
@@ -2913,7 +2917,7 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
2913
2917
  const template = group[0];
2914
2918
  const syntheticId = `merged-group-${key}`;
2915
2919
  const originalNetIds = new Set(group.map((p) => p.globalConnNetId));
2916
- this.mergedLabelNetIdMap.set(syntheticId, originalNetIds);
2920
+ this.mergedLabelNetIdMap[syntheticId] = originalNetIds;
2917
2921
  finalPlacements.push({
2918
2922
  ...template,
2919
2923
  globalConnNetId: syntheticId,
@@ -2933,7 +2937,7 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
2933
2937
  this.solved = true;
2934
2938
  return;
2935
2939
  }
2936
- this.detourCountByLabel.clear();
2940
+ this.detourCountByLabel = {};
2937
2941
  const overlaps = detectTraceLabelOverlap(
2938
2942
  this.traces,
2939
2943
  this.netTempLabelPlacements
@@ -2943,9 +2947,7 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
2943
2947
  return;
2944
2948
  }
2945
2949
  const unfriendlyOverlaps = overlaps.filter((o) => {
2946
- const originalNetIds = this.mergedLabelNetIdMap.get(
2947
- o.label.globalConnNetId
2948
- );
2950
+ const originalNetIds = this.mergedLabelNetIdMap[o.label.globalConnNetId];
2949
2951
  if (originalNetIds) {
2950
2952
  return !originalNetIds.has(o.trace.globalConnNetId);
2951
2953
  }
@@ -2955,18 +2957,18 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
2955
2957
  this.solved = true;
2956
2958
  return;
2957
2959
  }
2958
- const updatedTracesMap = /* @__PURE__ */ new Map();
2960
+ const updatedTracesMap = {};
2959
2961
  for (const trace of this.traces) {
2960
- updatedTracesMap.set(trace.mspPairId, trace);
2962
+ updatedTracesMap[trace.mspPairId] = trace;
2961
2963
  }
2962
2964
  const processedTraceIds = /* @__PURE__ */ new Set();
2963
2965
  for (const overlap of unfriendlyOverlaps) {
2964
2966
  if (processedTraceIds.has(overlap.trace.mspPairId)) {
2965
2967
  continue;
2966
2968
  }
2967
- const currentTraceState = updatedTracesMap.get(overlap.trace.mspPairId);
2969
+ const currentTraceState = updatedTracesMap[overlap.trace.mspPairId];
2968
2970
  const labelId = overlap.label.globalConnNetId;
2969
- const detourCount = this.detourCountByLabel.get(labelId) || 0;
2971
+ const detourCount = this.detourCountByLabel[labelId] || 0;
2970
2972
  const newTrace = rerouteCollidingTrace({
2971
2973
  trace: currentTraceState,
2972
2974
  label: overlap.label,
@@ -2975,12 +2977,12 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
2975
2977
  detourCount
2976
2978
  });
2977
2979
  if (newTrace.tracePath !== currentTraceState.tracePath) {
2978
- this.detourCountByLabel.set(labelId, detourCount + 1);
2980
+ this.detourCountByLabel[labelId] = detourCount + 1;
2979
2981
  }
2980
- updatedTracesMap.set(currentTraceState.mspPairId, newTrace);
2982
+ updatedTracesMap[currentTraceState.mspPairId] = newTrace;
2981
2983
  processedTraceIds.add(currentTraceState.mspPairId);
2982
2984
  }
2983
- this.updatedTraces = Array.from(updatedTracesMap.values());
2985
+ this.updatedTraces = Object.values(updatedTracesMap);
2984
2986
  const minimizedTraces = minimizeTurnsWithFilteredLabels({
2985
2987
  traces: this.updatedTraces,
2986
2988
  problem: this.problem,
@@ -3000,21 +3002,21 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
3000
3002
  if (balancedTraces) {
3001
3003
  this.updatedTraces = balancedTraces;
3002
3004
  }
3003
- this.updatedTracesMap.clear();
3004
- for (const trace of this.updatedTraces) {
3005
- this.updatedTracesMap.set(trace.mspPairId, trace);
3006
- }
3007
3005
  const finalLabelPlacementSolver = new NetLabelPlacementSolver({
3008
3006
  inputProblem: this.problem,
3009
- inputTraceMap: Object.fromEntries(this.updatedTracesMap)
3007
+ inputTraceMap: Object.fromEntries(
3008
+ this.updatedTraces.map((trace) => [trace.mspPairId, trace])
3009
+ )
3010
3010
  });
3011
3011
  finalLabelPlacementSolver.solve();
3012
- this.netLabelPlacements = finalLabelPlacementSolver.netLabelPlacements;
3012
+ if (!finalLabelPlacementSolver.failed) {
3013
+ this.netLabelPlacements = finalLabelPlacementSolver.netLabelPlacements;
3014
+ }
3013
3015
  this.solved = true;
3014
3016
  }
3015
3017
  getOutput() {
3016
3018
  return {
3017
- traceMap: this.updatedTracesMap,
3019
+ traces: this.updatedTraces,
3018
3020
  netLabelPlacements: this.netLabelPlacements
3019
3021
  };
3020
3022
  }
@@ -3024,7 +3026,7 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
3024
3026
  if (!graphics.circles) graphics.circles = [];
3025
3027
  if (!graphics.texts) graphics.texts = [];
3026
3028
  if (!graphics.rects) graphics.rects = [];
3027
- for (const trace of Object.values(this.updatedTraces)) {
3029
+ for (const trace of this.updatedTraces) {
3028
3030
  graphics.lines.push({
3029
3031
  points: trace.tracePath,
3030
3032
  strokeColor: "purple"
@@ -8,7 +8,7 @@ import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualize
8
8
  import { getRectBounds } from "../NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
9
9
  import { getColorFromString } from "lib/utils/getColorFromString"
10
10
  import type { InputProblem } from "lib/types/InputProblem"
11
- import { minimizeTurnsWithFilteredLabels } from "./turnMinimization"
11
+ import { minimizeTurnsWithFilteredLabels } from "./minimizeTurnsWithFilteredLabels"
12
12
  import { balanceLShapes } from "./balanceLShapes"
13
13
  import { NetLabelPlacementSolver } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
14
14
 
@@ -24,9 +24,8 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
24
24
  private netTempLabelPlacements: NetLabelPlacement[]
25
25
  private netLabelPlacements: NetLabelPlacement[]
26
26
  private updatedTraces: SolvedTracePath[]
27
- private updatedTracesMap: Map<string, SolvedTracePath>
28
- private mergedLabelNetIdMap: Map<string, Set<string>>
29
- private detourCountByLabel: Map<string, number>
27
+ private mergedLabelNetIdMap: Record<string, Set<string>>
28
+ private detourCountByLabel: Record<string, number>
30
29
  private readonly PADDING_BUFFER = 0.1
31
30
 
32
31
  constructor(solverInput: TraceLabelOverlapAvoidanceSolverInput) {
@@ -34,9 +33,8 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
34
33
  this.problem = solverInput.inputProblem
35
34
  this.traces = solverInput.traces
36
35
  this.updatedTraces = [...solverInput.traces]
37
- this.updatedTracesMap = new Map()
38
- this.mergedLabelNetIdMap = new Map()
39
- this.detourCountByLabel = new Map()
36
+ this.mergedLabelNetIdMap = {}
37
+ this.detourCountByLabel = {}
40
38
 
41
39
  const originalLabels = solverInput.netLabelPlacements
42
40
  this.netLabelPlacements = originalLabels
@@ -45,21 +43,21 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
45
43
  return
46
44
  }
47
45
 
48
- const labelGroups = new Map<string, NetLabelPlacement[]>()
46
+ const labelGroups: Record<string, NetLabelPlacement[]> = {}
49
47
 
50
48
  for (const p of originalLabels) {
51
49
  if (p.pinIds.length === 0) continue
52
50
  const chipId = p.pinIds[0].split(".")[0]
53
51
  if (!chipId) continue
54
52
  const key = `${chipId}-${p.orientation}`
55
- if (!labelGroups.has(key)) {
56
- labelGroups.set(key, [])
53
+ if (!(key in labelGroups)) {
54
+ labelGroups[key] = []
57
55
  }
58
- labelGroups.get(key)!.push(p)
56
+ labelGroups[key]!.push(p)
59
57
  }
60
58
 
61
59
  const finalPlacements: NetLabelPlacement[] = []
62
- for (const [key, group] of labelGroups.entries()) {
60
+ for (const [key, group] of Object.entries(labelGroups)) {
63
61
  if (group.length <= 1) {
64
62
  finalPlacements.push(...group)
65
63
  continue
@@ -82,7 +80,7 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
82
80
  const template = group[0]!
83
81
  const syntheticId = `merged-group-${key}`
84
82
  const originalNetIds = new Set(group.map((p) => p.globalConnNetId))
85
- this.mergedLabelNetIdMap.set(syntheticId, originalNetIds)
83
+ this.mergedLabelNetIdMap[syntheticId] = originalNetIds
86
84
 
87
85
  finalPlacements.push({
88
86
  ...template,
@@ -111,7 +109,7 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
111
109
  return
112
110
  }
113
111
 
114
- this.detourCountByLabel.clear()
112
+ this.detourCountByLabel = {}
115
113
 
116
114
  const overlaps = detectTraceLabelOverlap(
117
115
  this.traces,
@@ -124,9 +122,7 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
124
122
  }
125
123
 
126
124
  const unfriendlyOverlaps = overlaps.filter((o) => {
127
- const originalNetIds = this.mergedLabelNetIdMap.get(
128
- o.label.globalConnNetId,
129
- )
125
+ const originalNetIds = this.mergedLabelNetIdMap[o.label.globalConnNetId]
130
126
  if (originalNetIds) {
131
127
  return !originalNetIds.has(o.trace.globalConnNetId)
132
128
  }
@@ -138,9 +134,9 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
138
134
  return
139
135
  }
140
136
 
141
- const updatedTracesMap = new Map<string, SolvedTracePath>()
137
+ const updatedTracesMap: Record<string, SolvedTracePath> = {}
142
138
  for (const trace of this.traces) {
143
- updatedTracesMap.set(trace.mspPairId, trace)
139
+ updatedTracesMap[trace.mspPairId] = trace
144
140
  }
145
141
 
146
142
  const processedTraceIds = new Set<string>()
@@ -150,9 +146,9 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
150
146
  continue
151
147
  }
152
148
 
153
- const currentTraceState = updatedTracesMap.get(overlap.trace.mspPairId)!
149
+ const currentTraceState = updatedTracesMap[overlap.trace.mspPairId]!
154
150
  const labelId = overlap.label.globalConnNetId
155
- const detourCount = this.detourCountByLabel.get(labelId) || 0
151
+ const detourCount = this.detourCountByLabel[labelId] || 0
156
152
 
157
153
  const newTrace = rerouteCollidingTrace({
158
154
  trace: currentTraceState,
@@ -163,14 +159,14 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
163
159
  })
164
160
 
165
161
  if (newTrace.tracePath !== currentTraceState.tracePath) {
166
- this.detourCountByLabel.set(labelId, detourCount + 1)
162
+ this.detourCountByLabel[labelId] = detourCount + 1
167
163
  }
168
164
 
169
- updatedTracesMap.set(currentTraceState.mspPairId, newTrace)
165
+ updatedTracesMap[currentTraceState.mspPairId] = newTrace
170
166
  processedTraceIds.add(currentTraceState.mspPairId)
171
167
  }
172
168
 
173
- this.updatedTraces = Array.from(updatedTracesMap.values())
169
+ this.updatedTraces = Object.values(updatedTracesMap)
174
170
 
175
171
  const minimizedTraces = minimizeTurnsWithFilteredLabels({
176
172
  traces: this.updatedTraces,
@@ -191,24 +187,23 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
191
187
  this.updatedTraces = balancedTraces
192
188
  }
193
189
 
194
- this.updatedTracesMap.clear()
195
- for (const trace of this.updatedTraces) {
196
- this.updatedTracesMap.set(trace.mspPairId, trace)
197
- }
198
-
199
190
  const finalLabelPlacementSolver = new NetLabelPlacementSolver({
200
191
  inputProblem: this.problem,
201
- inputTraceMap: Object.fromEntries(this.updatedTracesMap),
192
+ inputTraceMap: Object.fromEntries(
193
+ this.updatedTraces.map((trace) => [trace.mspPairId, trace]),
194
+ ),
202
195
  })
203
196
  finalLabelPlacementSolver.solve()
204
- this.netLabelPlacements = finalLabelPlacementSolver.netLabelPlacements
197
+ if (!finalLabelPlacementSolver.failed) {
198
+ this.netLabelPlacements = finalLabelPlacementSolver.netLabelPlacements
199
+ }
205
200
 
206
201
  this.solved = true
207
202
  }
208
203
 
209
204
  getOutput() {
210
205
  return {
211
- traceMap: this.updatedTracesMap,
206
+ traces: this.updatedTraces,
212
207
  netLabelPlacements: this.netLabelPlacements,
213
208
  }
214
209
  }
@@ -221,7 +216,7 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
221
216
  if (!graphics.texts) graphics.texts = []
222
217
  if (!graphics.rects) graphics.rects = []
223
218
 
224
- for (const trace of Object.values(this.updatedTraces)) {
219
+ for (const trace of this.updatedTraces) {
225
220
  graphics.lines!.push({
226
221
  points: trace.tracePath,
227
222
  strokeColor: "purple",
@@ -1,9 +1,5 @@
1
1
  import type { Point } from "graphics-debug"
2
- import {
3
- isVertical,
4
- isHorizontal,
5
- segmentIntersectsRect,
6
- } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
2
+ import { segmentIntersectsRect } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
7
3
 
8
4
  export const hasCollisions = (
9
5
  pathSegments: Point[],
@@ -0,0 +1,19 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import { segmentIntersectsRect } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
3
+
4
+ export const hasCollisionsWithLabels = (
5
+ pathSegments: Point[],
6
+ labels: any[],
7
+ ): boolean => {
8
+ for (let i = 0; i < pathSegments.length - 1; i++) {
9
+ const p1 = pathSegments[i]
10
+ const p2 = pathSegments[i + 1]
11
+
12
+ for (const label of labels) {
13
+ if (segmentIntersectsRect(p1, p2, label)) {
14
+ return true
15
+ }
16
+ }
17
+ }
18
+ return false
19
+ }
@@ -0,0 +1,66 @@
1
+ import type { InputProblem } from "lib/types/InputProblem"
2
+ import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
+ import { getObstacleRects } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
5
+ import { minimizeTurns } from "./turnMinimization"
6
+
7
+ export const minimizeTurnsWithFilteredLabels = ({
8
+ traces,
9
+ problem,
10
+ allLabelPlacements,
11
+ mergedLabelNetIdMap,
12
+ paddingBuffer,
13
+ }: {
14
+ traces: SolvedTracePath[]
15
+ problem: InputProblem
16
+ allLabelPlacements: NetLabelPlacement[]
17
+ mergedLabelNetIdMap: Record<string, Set<string>>
18
+ paddingBuffer: number
19
+ }): SolvedTracePath[] | null => {
20
+ let changesMade = false
21
+ const obstacles = getObstacleRects(problem)
22
+
23
+ const newTraces = traces.map((trace) => {
24
+ const originalPath = trace.tracePath
25
+ const filteredLabels = allLabelPlacements.filter((label) => {
26
+ const originalNetIds = mergedLabelNetIdMap[label.globalConnNetId]
27
+ if (originalNetIds) {
28
+ return !originalNetIds.has(trace.globalConnNetId)
29
+ }
30
+ return label.globalConnNetId !== trace.globalConnNetId
31
+ })
32
+
33
+ const labelBounds = filteredLabels.map((nl) => ({
34
+ minX: nl.center.x - nl.width / 2 - paddingBuffer,
35
+ maxX: nl.center.x + nl.width / 2 + paddingBuffer,
36
+ minY: nl.center.y - nl.height / 2 - paddingBuffer,
37
+ maxY: nl.center.y + nl.height / 2 + paddingBuffer,
38
+ }))
39
+
40
+ const newPath = minimizeTurns({
41
+ path: originalPath,
42
+ obstacles,
43
+ labelBounds,
44
+ })
45
+
46
+ if (
47
+ newPath.length !== originalPath.length ||
48
+ newPath.some(
49
+ (p, i) => p.x !== originalPath[i].x || p.y !== originalPath[i].y,
50
+ )
51
+ ) {
52
+ changesMade = true
53
+ }
54
+
55
+ return {
56
+ ...trace,
57
+ tracePath: newPath,
58
+ }
59
+ })
60
+
61
+ if (changesMade) {
62
+ return newTraces
63
+ } else {
64
+ return null
65
+ }
66
+ }
@@ -0,0 +1,14 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+
3
+ export const tryConnectPoints = (start: Point, end: Point): Point[][] => {
4
+ const candidates: Point[][] = []
5
+
6
+ if (start.x === end.x || start.y === end.y) {
7
+ candidates.push([start, end])
8
+ } else {
9
+ candidates.push([start, { x: end.x, y: start.y }, end])
10
+ candidates.push([start, { x: start.x, y: end.y }, end])
11
+ }
12
+
13
+ return candidates
14
+ }
@@ -1,14 +1,11 @@
1
1
  import type { Point } from "graphics-debug"
2
- import type { InputProblem } from "lib/types/InputProblem"
3
- import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
4
- import { getObstacleRects } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
5
- import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
6
- import { segmentIntersectsRect } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
7
2
  import { hasCollisions } from "./hasCollisions"
8
3
  import { countTurns } from "./countTurns"
9
4
  import { simplifyPath } from "./simplifyPath"
5
+ import { tryConnectPoints } from "./tryConnectPoints"
6
+ import { hasCollisionsWithLabels } from "./hasCollisionsWithLabels"
10
7
 
11
- const minimizeTurns = ({
8
+ export const minimizeTurns = ({
12
9
  path,
13
10
  obstacles,
14
11
  labelBounds,
@@ -21,36 +18,6 @@ const minimizeTurns = ({
21
18
  return path
22
19
  }
23
20
 
24
- const hasCollisionsWithLabels = (
25
- pathSegments: Point[],
26
- labels: any[],
27
- ): boolean => {
28
- for (let i = 0; i < pathSegments.length - 1; i++) {
29
- const p1 = pathSegments[i]
30
- const p2 = pathSegments[i + 1]
31
-
32
- for (const label of labels) {
33
- if (segmentIntersectsRect(p1, p2, label)) {
34
- return true
35
- }
36
- }
37
- }
38
- return false
39
- }
40
-
41
- const tryConnectPoints = (start: Point, end: Point): Point[][] => {
42
- const candidates: Point[][] = []
43
-
44
- if (start.x === end.x || start.y === end.y) {
45
- candidates.push([start, end])
46
- } else {
47
- candidates.push([start, { x: end.x, y: start.y }, end])
48
- candidates.push([start, { x: start.x, y: end.y }, end])
49
- }
50
-
51
- return candidates
52
- }
53
-
54
21
  const recognizeStairStepPattern = (
55
22
  pathToCheck: Point[],
56
23
  startIdx: number,
@@ -242,64 +209,3 @@ const minimizeTurns = ({
242
209
  const finalSimplifiedPath = simplifyPath(optimizedPath)
243
210
  return finalSimplifiedPath
244
211
  }
245
-
246
- export const minimizeTurnsWithFilteredLabels = ({
247
- traces,
248
- problem,
249
- allLabelPlacements,
250
- mergedLabelNetIdMap,
251
- paddingBuffer,
252
- }: {
253
- traces: SolvedTracePath[]
254
- problem: InputProblem
255
- allLabelPlacements: NetLabelPlacement[]
256
- mergedLabelNetIdMap: Map<string, Set<string>>
257
- paddingBuffer: number
258
- }): SolvedTracePath[] | null => {
259
- let changesMade = false
260
- const obstacles = getObstacleRects(problem)
261
-
262
- const newTraces = traces.map((trace) => {
263
- const originalPath = trace.tracePath
264
- const filteredLabels = allLabelPlacements.filter((label) => {
265
- const originalNetIds = mergedLabelNetIdMap.get(label.globalConnNetId)
266
- if (originalNetIds) {
267
- return !originalNetIds.has(trace.globalConnNetId)
268
- }
269
- return label.globalConnNetId !== trace.globalConnNetId
270
- })
271
-
272
- const labelBounds = filteredLabels.map((nl) => ({
273
- minX: nl.center.x - nl.width / 2 - paddingBuffer,
274
- maxX: nl.center.x + nl.width / 2 + paddingBuffer,
275
- minY: nl.center.y - nl.height / 2 - paddingBuffer,
276
- maxY: nl.center.y + nl.height / 2 + paddingBuffer,
277
- }))
278
-
279
- const newPath = minimizeTurns({
280
- path: originalPath,
281
- obstacles,
282
- labelBounds,
283
- })
284
-
285
- if (
286
- newPath.length !== originalPath.length ||
287
- newPath.some(
288
- (p, i) => p.x !== originalPath[i].x || p.y !== originalPath[i].y,
289
- )
290
- ) {
291
- changesMade = true
292
- }
293
-
294
- return {
295
- ...trace,
296
- tracePath: newPath,
297
- }
298
- })
299
-
300
- if (changesMade) {
301
- return newTraces
302
- } else {
303
- return null
304
- }
305
- }
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.38",
4
+ "version": "0.0.40",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -0,0 +1,281 @@
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
+ y+" data-x="-0.2" data-y="1.4" cx="307.82608695652175" cy="228.69565217391306" 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.3" data-y="0.09999999999999998" cx="240.8695652173913" cy="307.82608695652175" 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.3" data-y="-0.10000000000000009" cx="240.8695652173913" cy="320" 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.3" data-y="-0.30000000000000004" cx="240.8695652173913" cy="332.17391304347825" r="3" fill="hsl(322, 100%, 50%, 0.8)" />
18
+ </g>
19
+ <g>
20
+ <circle data-type="point" data-label="U1.5
21
+ x-" data-x="-1.3" data-y="-0.5" cx="240.8695652173913" cy="344.3478260869565" r="3" fill="hsl(323, 100%, 50%, 0.8)" />
22
+ </g>
23
+ <g>
24
+ <circle data-type="point" data-label="U1.7
25
+ y-" data-x="0" data-y="-1.4" cx="320" cy="399.1304347826087" r="3" fill="hsl(325, 100%, 50%, 0.8)" />
26
+ </g>
27
+ <g>
28
+ <circle data-type="point" data-label="U1.8
29
+ x-" data-x="-1.3" data-y="0.5" cx="240.8695652173913" cy="283.47826086956525" r="3" fill="hsl(326, 100%, 50%, 0.8)" />
30
+ </g>
31
+ <g>
32
+ <circle data-type="point" data-label="U1.10
33
+ x+" data-x="1.3" data-y="-0.5" cx="399.1304347826087" cy="344.3478260869565" r="3" fill="hsl(217, 100%, 50%, 0.8)" />
34
+ </g>
35
+ <g>
36
+ <circle data-type="point" data-label="U1.11
37
+ x+" data-x="1.3" data-y="-0.3" cx="399.1304347826087" cy="332.17391304347825" r="3" fill="hsl(218, 100%, 50%, 0.8)" />
38
+ </g>
39
+ <g>
40
+ <circle data-type="point" data-label="U1.12
41
+ x+" data-x="1.3" data-y="-0.09999999999999998" cx="399.1304347826087" cy="320" r="3" fill="hsl(219, 100%, 50%, 0.8)" />
42
+ </g>
43
+ <g>
44
+ <circle data-type="point" data-label="U1.13
45
+ x+" data-x="1.3" data-y="0.10000000000000009" cx="399.1304347826087" cy="307.82608695652175" r="3" fill="hsl(220, 100%, 50%, 0.8)" />
46
+ </g>
47
+ <g>
48
+ <circle data-type="point" data-label="U1.14
49
+ y+" data-x="0.2" data-y="1.4" cx="332.17391304347825" cy="228.69565217391306" r="3" fill="hsl(221, 100%, 50%, 0.8)" />
50
+ </g>
51
+ <g>
52
+ <circle data-type="point" data-label="JP1.1
53
+ x+" data-x="-3.4" data-y="-0.8999999999999999" cx="113.04347826086953" cy="368.69565217391306" r="3" fill="hsl(358, 100%, 50%, 0.8)" />
54
+ </g>
55
+ <g>
56
+ <circle data-type="point" data-label="JP1.2
57
+ x+" data-x="-3.4" data-y="-0.7" cx="113.04347826086953" cy="356.5217391304348" r="3" fill="hsl(359, 100%, 50%, 0.8)" />
58
+ </g>
59
+ <g>
60
+ <circle data-type="point" data-label="JP1.3
61
+ x+" data-x="-3.4" data-y="-0.49999999999999994" cx="113.04347826086953" cy="344.3478260869565" r="3" fill="hsl(0, 100%, 50%, 0.8)" />
62
+ </g>
63
+ <g>
64
+ <circle data-type="point" data-label="JP1.4
65
+ x+" data-x="-3.4" data-y="-0.2999999999999999" cx="113.04347826086953" cy="332.17391304347825" r="3" fill="hsl(1, 100%, 50%, 0.8)" />
66
+ </g>
67
+ <g>
68
+ <circle data-type="point" data-label="JP1.5
69
+ x+" data-x="-3.4" data-y="-0.09999999999999992" cx="113.04347826086953" cy="320" r="3" fill="hsl(2, 100%, 50%, 0.8)" />
70
+ </g>
71
+ <g>
72
+ <circle data-type="point" data-label="JP1.6
73
+ x+" data-x="-3.4" data-y="0.10000000000000003" cx="113.04347826086953" cy="307.82608695652175" r="3" fill="hsl(3, 100%, 50%, 0.8)" />
74
+ </g>
75
+ <g>
76
+ <circle data-type="point" data-label="JP1.7
77
+ x+" data-x="-3.4" data-y="0.3" cx="113.04347826086953" cy="295.6521739130435" r="3" fill="hsl(4, 100%, 50%, 0.8)" />
78
+ </g>
79
+ <g>
80
+ <circle data-type="point" data-label="JP2.1
81
+ x-" data-x="3.4" data-y="-0.8999999999999999" cx="526.9565217391305" cy="368.69565217391306" r="3" fill="hsl(239, 100%, 50%, 0.8)" />
82
+ </g>
83
+ <g>
84
+ <circle data-type="point" data-label="JP2.2
85
+ x-" data-x="3.4" data-y="-0.7" cx="526.9565217391305" cy="356.5217391304348" r="3" fill="hsl(240, 100%, 50%, 0.8)" />
86
+ </g>
87
+ <g>
88
+ <circle data-type="point" data-label="JP2.3
89
+ x-" data-x="3.4" data-y="-0.5" cx="526.9565217391305" cy="344.3478260869565" r="3" fill="hsl(241, 100%, 50%, 0.8)" />
90
+ </g>
91
+ <g>
92
+ <circle data-type="point" data-label="JP2.4
93
+ x-" data-x="3.4" data-y="-0.3000000000000001" cx="526.9565217391305" cy="332.17391304347825" r="3" fill="hsl(242, 100%, 50%, 0.8)" />
94
+ </g>
95
+ <g>
96
+ <circle data-type="point" data-label="JP2.5
97
+ x-" data-x="3.4" data-y="-0.10000000000000003" cx="526.9565217391305" cy="320" r="3" fill="hsl(243, 100%, 50%, 0.8)" />
98
+ </g>
99
+ <g>
100
+ <circle data-type="point" data-label="JP2.6
101
+ x-" data-x="3.4" data-y="0.09999999999999998" cx="526.9565217391305" cy="307.82608695652175" r="3" fill="hsl(244, 100%, 50%, 0.8)" />
102
+ </g>
103
+ <g>
104
+ <circle data-type="point" data-label="JP2.7
105
+ x-" data-x="3.4" data-y="0.3" cx="526.9565217391305" cy="295.6521739130435" r="3" fill="hsl(245, 100%, 50%, 0.8)" />
106
+ </g>
107
+ <g>
108
+ <circle data-type="point" data-label="" data-x="-3.4" data-y="-0.09999999999999992" cx="113.04347826086953" cy="320" r="3" fill="red" />
109
+ </g>
110
+ <g>
111
+ <circle data-type="point" data-label="" data-x="-3.4" data-y="-0.09999999999999992" cx="113.04347826086953" cy="320" r="3" fill="red" />
112
+ </g>
113
+ <g>
114
+ <circle data-type="point" data-label="" data-x="-2.35" data-y="-0.1" cx="176.9565217391304" cy="320" r="3" fill="orange" />
115
+ </g>
116
+ <g>
117
+ <circle data-type="point" data-label="" data-x="-2.35" data-y="-0.1" cx="176.9565217391304" cy="320" r="3" fill="orange" />
118
+ </g>
119
+ <g>
120
+ <circle data-type="point" data-label="" data-x="-1.3" data-y="-0.10000000000000009" cx="240.8695652173913" cy="320" r="3" fill="red" />
121
+ </g>
122
+ <g>
123
+ <circle data-type="point" data-label="" data-x="-1.3" data-y="-0.10000000000000009" cx="240.8695652173913" cy="320" r="3" fill="red" />
124
+ </g>
125
+ <g>
126
+ <polyline data-points="-3.4,-0.8999999999999999 0,-1.4" data-type="line" data-label="" points="113.04347826086953,368.69565217391306 320,399.1304347826087" fill="none" stroke="hsl(336, 100%, 50%, 0.8)" stroke-width="1" />
127
+ </g>
128
+ <g>
129
+ <polyline data-points="-3.4,-0.49999999999999994 -1.3,-0.5" data-type="line" data-label="" points="113.04347826086953,344.3478260869565 240.8695652173913,344.3478260869565" fill="none" stroke="hsl(336, 100%, 50%, 0.8)" stroke-width="1" />
130
+ </g>
131
+ <g>
132
+ <polyline data-points="-3.4,-0.2999999999999999 -1.3,-0.30000000000000004" data-type="line" data-label="" points="113.04347826086953,332.17391304347825 240.8695652173913,332.17391304347825" fill="none" stroke="hsl(336, 100%, 50%, 0.8)" stroke-width="1" />
133
+ </g>
134
+ <g>
135
+ <polyline data-points="-3.4,-0.09999999999999992 -1.3,-0.10000000000000009" data-type="line" data-label="" points="113.04347826086953,320 240.8695652173913,320" fill="none" stroke="hsl(336, 100%, 50%, 0.8)" stroke-width="1" />
136
+ </g>
137
+ <g>
138
+ <polyline data-points="-3.4,0.10000000000000003 -1.3,0.09999999999999998" data-type="line" data-label="" points="113.04347826086953,307.82608695652175 240.8695652173913,307.82608695652175" fill="none" stroke="hsl(336, 100%, 50%, 0.8)" stroke-width="1" />
139
+ </g>
140
+ <g>
141
+ <polyline data-points="3.4,-0.5 1.3,-0.5" data-type="line" data-label="" points="526.9565217391305,344.3478260869565 399.1304347826087,344.3478260869565" fill="none" stroke="hsl(24, 100%, 50%, 0.8)" stroke-width="1" />
142
+ </g>
143
+ <g>
144
+ <polyline data-points="3.4,-0.3000000000000001 1.3,-0.3" data-type="line" data-label="" points="526.9565217391305,332.17391304347825 399.1304347826087,332.17391304347825" fill="none" stroke="hsl(24, 100%, 50%, 0.8)" stroke-width="1" />
145
+ </g>
146
+ <g>
147
+ <polyline data-points="3.4,-0.10000000000000003 1.3,-0.09999999999999998" data-type="line" data-label="" points="526.9565217391305,320 399.1304347826087,320" fill="none" stroke="hsl(24, 100%, 50%, 0.8)" stroke-width="1" />
148
+ </g>
149
+ <g>
150
+ <polyline data-points="3.4,0.09999999999999998 1.3,0.10000000000000009" data-type="line" data-label="" points="526.9565217391305,307.82608695652175 399.1304347826087,307.82608695652175" fill="none" stroke="hsl(24, 100%, 50%, 0.8)" stroke-width="1" />
151
+ </g>
152
+ <g>
153
+ <polyline data-points="-0.2,1.4 -3.4,-0.7" data-type="line" data-label="" points="307.82608695652175,228.69565217391306 113.04347826086953,356.5217391304348" fill="none" stroke="hsl(73, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
154
+ </g>
155
+ <g>
156
+ <polyline data-points="0,-1.4 -3.4,-0.8999999999999999" data-type="line" data-label="" points="320,399.1304347826087 113.04347826086953,368.69565217391306" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
157
+ </g>
158
+ <g>
159
+ <polyline data-points="0,-1.4 3.4,-0.8999999999999999" data-type="line" data-label="" points="320,399.1304347826087 526.9565217391305,368.69565217391306" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
160
+ </g>
161
+ <g>
162
+ <polyline data-points="-3.4,-0.8999999999999999 3.4,-0.8999999999999999" data-type="line" data-label="" points="113.04347826086953,368.69565217391306 526.9565217391305,368.69565217391306" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
163
+ </g>
164
+ <g>
165
+ <polyline data-points="0.2,1.4 3.4,-0.7" data-type="line" data-label="" points="332.17391304347825,228.69565217391306 526.9565217391305,356.5217391304348" fill="none" stroke="hsl(199, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
166
+ </g>
167
+ <g>
168
+ <polyline data-points="0,-1.4 0,-1.5999999999999999 -1.7,-1.5999999999999999 -1.7,-0.8999999999999999 -3.4,-0.8999999999999999" data-type="line" data-label="" points="320,399.1304347826087 320,411.304347826087 216.52173913043475,411.304347826087 216.52173913043475,368.69565217391306 113.04347826086953,368.69565217391306" fill="none" stroke="black" stroke-width="1" />
169
+ </g>
170
+ <g>
171
+ <polyline data-points="3.4,-0.8999999999999999 1.6999999999999997,-0.8999999999999999 1.6999999999999997,-1.5999999999999999 0,-1.5999999999999999 0,-1.4" data-type="line" data-label="" points="526.9565217391305,368.69565217391306 423.47826086956525,368.69565217391306 423.47826086956525,411.304347826087 320,411.304347826087 320,399.1304347826087" fill="none" stroke="black" stroke-width="1" />
172
+ </g>
173
+ <g>
174
+ <polyline data-points="-3.4,-0.49999999999999994 -1.3,-0.5" data-type="line" data-label="" points="113.04347826086953,344.3478260869565 240.8695652173913,344.3478260869565" fill="none" stroke="black" stroke-width="1" />
175
+ </g>
176
+ <g>
177
+ <polyline data-points="-3.4,-0.2999999999999999 -1.3,-0.30000000000000004" data-type="line" data-label="" points="113.04347826086953,332.17391304347825 240.8695652173913,332.17391304347825" fill="none" stroke="black" stroke-width="1" />
178
+ </g>
179
+ <g>
180
+ <polyline data-points="-3.4,-0.09999999999999992 -1.3,-0.10000000000000009" data-type="line" data-label="" points="113.04347826086953,320 240.8695652173913,320" fill="none" stroke="black" stroke-width="1" />
181
+ </g>
182
+ <g>
183
+ <polyline data-points="-3.4,0.10000000000000003 -1.3,0.09999999999999998" data-type="line" data-label="" points="113.04347826086953,307.82608695652175 240.8695652173913,307.82608695652175" fill="none" stroke="black" stroke-width="1" />
184
+ </g>
185
+ <g>
186
+ <polyline data-points="3.4,-0.5 1.3,-0.5" data-type="line" data-label="" points="526.9565217391305,344.3478260869565 399.1304347826087,344.3478260869565" fill="none" stroke="black" stroke-width="1" />
187
+ </g>
188
+ <g>
189
+ <polyline data-points="3.4,-0.3000000000000001 1.3,-0.3" data-type="line" data-label="" points="526.9565217391305,332.17391304347825 399.1304347826087,332.17391304347825" fill="none" stroke="black" stroke-width="1" />
190
+ </g>
191
+ <g>
192
+ <polyline data-points="3.4,-0.10000000000000003 1.3,-0.09999999999999998" data-type="line" data-label="" points="526.9565217391305,320 399.1304347826087,320" fill="none" stroke="black" stroke-width="1" />
193
+ </g>
194
+ <g>
195
+ <polyline data-points="3.4,0.09999999999999998 1.3,0.10000000000000009" data-type="line" data-label="" points="526.9565217391305,307.82608695652175 399.1304347826087,307.82608695652175" fill="none" stroke="black" stroke-width="1" />
196
+ </g>
197
+ <g>
198
+ <rect data-type="rect" data-label="schematic_component_0" data-x="0" data-y="0" x="240.8695652173913" y="228.69565217391306" width="158.26086956521738" height="170.43478260869563" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.016428571428571428" />
199
+ </g>
200
+ <g>
201
+ <rect data-type="rect" data-label="schematic_component_1" data-x="-4" data-y="-0.3" x="40" y="283.47826086956525" width="73.04347826086953" height="97.39130434782606" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.016428571428571428" />
202
+ </g>
203
+ <g>
204
+ <rect data-type="rect" data-label="schematic_component_2" data-x="4" data-y="-0.3" x="526.9565217391305" y="283.47826086956525" width="73.0434782608695" height="97.39130434782606" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.016428571428571428" />
205
+ </g>
206
+ <g>
207
+ <rect data-type="rect" data-label="" data-x="-3.4" data-y="0.12510000000000007" x="106.95652173913041" y="292.6026086956522" width="12.17391304347828" height="27.391304347826065" fill="rgba(220, 0, 0, 0.25)" stroke="black" stroke-width="0.016428571428571428" />
208
+ </g>
209
+ <g>
210
+ <rect data-type="rect" data-label="" data-x="-3.4" data-y="-0.32509999999999994" x="106.95652173913041" y="320.00608695652176" width="12.17391304347828" height="27.391304347826065" fill="rgba(220, 0, 0, 0.25)" stroke="black" stroke-width="0.016428571428571428" />
211
+ </g>
212
+ <g>
213
+ <rect data-type="rect" data-label="" data-x="-2.35" data-y="0.1251" x="170.86956521739128" y="292.6026086956522" width="12.17391304347828" height="27.391304347826065" fill="rgba(220, 140, 0, 0.25)" stroke="black" stroke-width="0.016428571428571428" />
214
+ </g>
215
+ <g>
216
+ <rect data-type="rect" data-label="" data-x="-2.35" data-y="-0.3251" x="170.86956521739128" y="320.00608695652176" width="12.17391304347828" height="27.391304347826065" fill="rgba(220, 140, 0, 0.25)" stroke="black" stroke-width="0.016428571428571428" />
217
+ </g>
218
+ <g>
219
+ <rect data-type="rect" data-label="" data-x="-1.3" data-y="0.1250999999999999" x="234.78260869565216" y="292.6026086956522" width="12.17391304347828" height="27.39130434782612" fill="rgba(220, 0, 0, 0.25)" stroke="black" stroke-width="0.016428571428571428" />
220
+ </g>
221
+ <g>
222
+ <rect data-type="rect" data-label="" data-x="-1.3" data-y="-0.32510000000000006" x="234.78260869565216" y="320.00608695652176" width="12.17391304347828" height="27.391304347826065" fill="rgba(220, 0, 0, 0.25)" stroke="black" stroke-width="0.016428571428571428" />
223
+ </g>
224
+ <g id="crosshair" style="display: none">
225
+ <line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5" />
226
+ <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>
227
+ </g>
228
+ <script>
229
+ <![CDATA[
230
+ document.currentScript.parentElement.addEventListener('mousemove', (e) => {
231
+ const svg = e.currentTarget;
232
+ const rect = svg.getBoundingClientRect();
233
+ const x = e.clientX - rect.left;
234
+ const y = e.clientY - rect.top;
235
+ const crosshair = svg.getElementById('crosshair');
236
+ const h = svg.getElementById('crosshair-h');
237
+ const v = svg.getElementById('crosshair-v');
238
+ const coords = svg.getElementById('coordinates');
239
+
240
+ crosshair.style.display = 'block';
241
+ h.setAttribute('x1', '0');
242
+ h.setAttribute('x2', '640');
243
+ h.setAttribute('y1', y);
244
+ h.setAttribute('y2', y);
245
+ v.setAttribute('x1', x);
246
+ v.setAttribute('x2', x);
247
+ v.setAttribute('y1', '0');
248
+ v.setAttribute('y2', '640');
249
+
250
+ // Calculate real coordinates using inverse transformation
251
+ const matrix = {
252
+ "a": 60.86956521739131,
253
+ "c": 0,
254
+ "e": 320,
255
+ "b": 0,
256
+ "d": -60.86956521739131,
257
+ "f": 313.9130434782609
258
+ };
259
+ // Manually invert and apply the affine transform
260
+ // Since we only use translate and scale, we can directly compute:
261
+ // x' = (x - tx) / sx
262
+ // y' = (y - ty) / sy
263
+ const sx = matrix.a;
264
+ const sy = matrix.d;
265
+ const tx = matrix.e;
266
+ const ty = matrix.f;
267
+ const realPoint = {
268
+ x: (x - tx) / sx,
269
+ y: (y - ty) / sy // Flip y back since we used negative scale
270
+ }
271
+
272
+ coords.textContent = `(${realPoint.x.toFixed(2)}, ${realPoint.y.toFixed(2)})`;
273
+ coords.setAttribute('x', (x + 5).toString());
274
+ coords.setAttribute('y', (y - 5).toString());
275
+ });
276
+ document.currentScript.parentElement.addEventListener('mouseleave', () => {
277
+ document.currentScript.parentElement.getElementById('crosshair').style.display = 'none';
278
+ });
279
+ ]]>
280
+ </script>
281
+ </svg>
@@ -0,0 +1,241 @@
1
+ import { test, expect } from "bun:test"
2
+ import type { InputProblem } from "lib/index"
3
+ import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
4
+ import "tests/fixtures/matcher"
5
+
6
+ const inputProblem: InputProblem = {
7
+ chips: [
8
+ {
9
+ chipId: "schematic_component_0",
10
+ center: {
11
+ x: 0,
12
+ y: 0,
13
+ },
14
+ width: 1.8,
15
+ height: 2,
16
+ pins: [
17
+ {
18
+ pinId: "U1.1",
19
+ x: -0.2,
20
+ y: 1.4,
21
+ },
22
+ {
23
+ pinId: "U1.2",
24
+ x: -1.3,
25
+ y: 0.09999999999999998,
26
+ },
27
+ {
28
+ pinId: "U1.3",
29
+ x: -1.3,
30
+ y: -0.10000000000000009,
31
+ },
32
+ {
33
+ pinId: "U1.4",
34
+ x: -1.3,
35
+ y: -0.30000000000000004,
36
+ },
37
+ {
38
+ pinId: "U1.5",
39
+ x: -1.3,
40
+ y: -0.5,
41
+ },
42
+ {
43
+ pinId: "U1.7",
44
+ x: 0,
45
+ y: -1.4,
46
+ },
47
+ {
48
+ pinId: "U1.8",
49
+ x: -1.3,
50
+ y: 0.5,
51
+ },
52
+ {
53
+ pinId: "U1.10",
54
+ x: 1.3,
55
+ y: -0.5,
56
+ },
57
+ {
58
+ pinId: "U1.11",
59
+ x: 1.3,
60
+ y: -0.3,
61
+ },
62
+ {
63
+ pinId: "U1.12",
64
+ x: 1.3,
65
+ y: -0.09999999999999998,
66
+ },
67
+ {
68
+ pinId: "U1.13",
69
+ x: 1.3,
70
+ y: 0.10000000000000009,
71
+ },
72
+ {
73
+ pinId: "U1.14",
74
+ x: 0.2,
75
+ y: 1.4,
76
+ },
77
+ ],
78
+ },
79
+ {
80
+ chipId: "schematic_component_1",
81
+ center: {
82
+ x: -4,
83
+ y: -0.3,
84
+ },
85
+ width: 0.4,
86
+ height: 1.6,
87
+ pins: [
88
+ {
89
+ pinId: "JP1.1",
90
+ x: -3.4,
91
+ y: -0.8999999999999999,
92
+ },
93
+ {
94
+ pinId: "JP1.2",
95
+ x: -3.4,
96
+ y: -0.7,
97
+ },
98
+ {
99
+ pinId: "JP1.3",
100
+ x: -3.4,
101
+ y: -0.49999999999999994,
102
+ },
103
+ {
104
+ pinId: "JP1.4",
105
+ x: -3.4,
106
+ y: -0.2999999999999999,
107
+ },
108
+ {
109
+ pinId: "JP1.5",
110
+ x: -3.4,
111
+ y: -0.09999999999999992,
112
+ },
113
+ {
114
+ pinId: "JP1.6",
115
+ x: -3.4,
116
+ y: 0.10000000000000003,
117
+ },
118
+ {
119
+ pinId: "JP1.7",
120
+ x: -3.4,
121
+ y: 0.3,
122
+ },
123
+ ],
124
+ },
125
+ {
126
+ chipId: "schematic_component_2",
127
+ center: {
128
+ x: 4,
129
+ y: -0.3,
130
+ },
131
+ width: 0.4,
132
+ height: 1.6,
133
+ pins: [
134
+ {
135
+ pinId: "JP2.1",
136
+ x: 3.4,
137
+ y: -0.8999999999999999,
138
+ },
139
+ {
140
+ pinId: "JP2.2",
141
+ x: 3.4,
142
+ y: -0.7,
143
+ },
144
+ {
145
+ pinId: "JP2.3",
146
+ x: 3.4,
147
+ y: -0.5,
148
+ },
149
+ {
150
+ pinId: "JP2.4",
151
+ x: 3.4,
152
+ y: -0.3000000000000001,
153
+ },
154
+ {
155
+ pinId: "JP2.5",
156
+ x: 3.4,
157
+ y: -0.10000000000000003,
158
+ },
159
+ {
160
+ pinId: "JP2.6",
161
+ x: 3.4,
162
+ y: 0.09999999999999998,
163
+ },
164
+ {
165
+ pinId: "JP2.7",
166
+ x: 3.4,
167
+ y: 0.3,
168
+ },
169
+ ],
170
+ },
171
+ ],
172
+ directConnections: [
173
+ {
174
+ pinIds: ["JP1.1", "U1.7"],
175
+ netId: "pinheader.JP1 > port.pin1 to .U1 > .pin7",
176
+ },
177
+ {
178
+ pinIds: ["JP1.3", "U1.5"],
179
+ netId: "pinheader.JP1 > port.pin3 to .U1 > .pin5",
180
+ },
181
+ {
182
+ pinIds: ["JP1.4", "U1.4"],
183
+ netId: "pinheader.JP1 > port.pin4 to .U1 > .pin4",
184
+ },
185
+ {
186
+ pinIds: ["JP1.5", "U1.3"],
187
+ netId: "pinheader.JP1 > port.pin5 to .U1 > .pin3",
188
+ },
189
+ {
190
+ pinIds: ["JP1.6", "U1.2"],
191
+ netId: "pinheader.JP1 > port.pin6 to .U1 > .pin2",
192
+ },
193
+ {
194
+ pinIds: ["JP2.3", "U1.10"],
195
+ netId: "pinheader.JP2 > port.pin3 to .U1 > .pin10",
196
+ },
197
+ {
198
+ pinIds: ["JP2.4", "U1.11"],
199
+ netId: "pinheader.JP2 > port.pin4 to .U1 > .pin11",
200
+ },
201
+ {
202
+ pinIds: ["JP2.5", "U1.12"],
203
+ netId: "pinheader.JP2 > port.pin5 to .U1 > .pin12",
204
+ },
205
+ {
206
+ pinIds: ["JP2.6", "U1.13"],
207
+ netId: "pinheader.JP2 > port.pin6 to .U1 > .pin13",
208
+ },
209
+ ],
210
+ netConnections: [
211
+ {
212
+ netId: "V3_3",
213
+ pinIds: ["U1.1", "JP1.2"],
214
+ netLabelWidth: 0.4,
215
+ },
216
+ {
217
+ netId: "GND",
218
+ pinIds: ["U1.7", "JP1.1", "JP2.1"],
219
+ netLabelWidth: 0.3,
220
+ },
221
+ {
222
+ netId: "V5",
223
+ pinIds: ["U1.14", "JP2.2"],
224
+ netLabelWidth: 0.2,
225
+ },
226
+ ],
227
+ availableNetLabelOrientations: {
228
+ V3_3: ["y+"],
229
+ V5: ["y+"],
230
+ GND: ["y-"],
231
+ },
232
+ maxMspPairDistance: 5,
233
+ }
234
+
235
+ test("example01", () => {
236
+ const solver = new SchematicTracePipelineSolver(inputProblem)
237
+
238
+ solver.solve()
239
+
240
+ expect(solver).toMatchSolverSnapshot(import.meta.path)
241
+ })