@tscircuit/schematic-trace-solver 0.0.65 → 0.0.67

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
@@ -384,6 +384,7 @@ declare class NetLabelPlacementSolver extends BaseSolver {
384
384
  queuedOverlappingSameNetTraceGroups: Array<OverlappingSameNetTraceGroup>;
385
385
  activeSubSolver: SingleNetLabelPlacementSolver | null;
386
386
  netLabelPlacements: Array<NetLabelPlacement>;
387
+ failedGroups: Array<OverlappingSameNetTraceGroup>;
387
388
  currentGroup: OverlappingSameNetTraceGroup | null;
388
389
  triedAnyOrientationFallbackForCurrentGroup: boolean;
389
390
  constructor(params: {
@@ -434,6 +435,7 @@ interface SingleOverlapSolverInput {
434
435
  problem: InputProblem;
435
436
  paddingBuffer: number;
436
437
  detourCount: number;
438
+ tracesToAvoidOverlapping?: SolvedTracePath[];
437
439
  }
438
440
  /**
439
441
  * This solver attempts to find a valid rerouting for a single trace that is
@@ -447,6 +449,7 @@ declare class SingleOverlapSolver extends BaseSolver {
447
449
  problem: InputProblem;
448
450
  obstacles: ReturnType<typeof getObstacleRects>;
449
451
  label: NetLabelPlacement;
452
+ tracesToAvoidOverlapping: SolvedTracePath[];
450
453
  _tried: number;
451
454
  constructor(solverInput: SingleOverlapSolverInput);
452
455
  _step(): void;
@@ -460,6 +463,7 @@ interface OverlapCollectionSolverInput {
460
463
  mergedNetLabelPlacements: NetLabelPlacement[];
461
464
  mergedLabelNetIdMap: Record<string, Set<string>>;
462
465
  detourCounts: Map<string, number>;
466
+ tracesToAvoidOverlapping?: SolvedTracePath[];
463
467
  }
464
468
  /**
465
469
  * This is an internal solver that manages the step-by-step process of avoiding
@@ -471,6 +475,7 @@ declare class OverlapAvoidanceStepSolver extends BaseSolver {
471
475
  mergedNetLabelPlacements: NetLabelPlacement[];
472
476
  mergedLabelNetIdMap: Record<string, Set<string>>;
473
477
  allTraces: SolvedTracePath[];
478
+ tracesToAvoidOverlapping: SolvedTracePath[];
474
479
  modifiedTraces: SolvedTracePath[];
475
480
  private readonly PADDING_BUFFER;
476
481
  private detourCounts;
@@ -517,6 +522,7 @@ declare class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
517
522
  private phase;
518
523
  detourCounts: Map<string, number>;
519
524
  labelMergingSolver?: MergedNetLabelObstacleSolver;
525
+ allInputTraces: SolvedTracePath[];
520
526
  constructor(solverInput: TraceLabelOverlapAvoidanceSolverInput);
521
527
  _step(): void;
522
528
  getOutput(): {
package/dist/index.js CHANGED
@@ -1059,6 +1059,32 @@ var applyJogToTerminalSegment = ({
1059
1059
  const isHorizontal3 = Math.abs(start.y - end.y) < EPS11;
1060
1060
  if (!isVertical4 && !isHorizontal3) return;
1061
1061
  const segDir = isVertical4 ? end.y > start.y ? 1 : -1 : end.x > start.x ? 1 : -1;
1062
+ if (pts.length === 2) {
1063
+ if (isVertical4) {
1064
+ const jogYNearStart = start.y + segDir * JOG_SIZE;
1065
+ const jogYNearEnd = end.y - segDir * JOG_SIZE;
1066
+ pts.splice(
1067
+ 1,
1068
+ 0,
1069
+ { x: start.x, y: jogYNearStart },
1070
+ { x: start.x + offset, y: jogYNearStart },
1071
+ { x: end.x + offset, y: jogYNearEnd },
1072
+ { x: end.x, y: jogYNearEnd }
1073
+ );
1074
+ } else {
1075
+ const jogXNearStart = start.x + segDir * JOG_SIZE;
1076
+ const jogXNearEnd = end.x - segDir * JOG_SIZE;
1077
+ pts.splice(
1078
+ 1,
1079
+ 0,
1080
+ { x: jogXNearStart, y: start.y },
1081
+ { x: jogXNearStart, y: start.y + offset },
1082
+ { x: jogXNearEnd, y: end.y + offset },
1083
+ { x: jogXNearEnd, y: end.y }
1084
+ );
1085
+ }
1086
+ return;
1087
+ }
1062
1088
  if (si === 0) {
1063
1089
  if (isVertical4) {
1064
1090
  const jogY = start.y + segDir * JOG_SIZE;
@@ -1126,7 +1152,18 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1126
1152
  }
1127
1153
  }
1128
1154
  _step() {
1155
+ const containsStraightPinToPinTrace = (group) => group.pathsWithOverlap.some(({ solvedTracePathIndex }) => {
1156
+ const path = this.traceNetIslands[group.connNetId][solvedTracePathIndex];
1157
+ return path.tracePath.length === 2;
1158
+ });
1159
+ const groupShouldStayInPlace = this.overlappingTraceSegments.map(
1160
+ containsStraightPinToPinTrace
1161
+ );
1162
+ const someGroupCanShift = groupShouldStayInPlace.some(
1163
+ (shouldStay) => !shouldStay
1164
+ );
1129
1165
  const offsets = this.overlappingTraceSegments.map((group, idx) => {
1166
+ if (someGroupCanShift && groupShouldStayInPlace[idx]) return 0;
1130
1167
  const n = Math.floor(idx / 2) + 1;
1131
1168
  const signed = idx % 2 === 0 ? -n : n;
1132
1169
  return this.getObstacleAwareOffset({
@@ -1138,6 +1175,7 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1138
1175
  const samePoint = (p, q) => !!p && !!q && eq(p.x, q.x) && eq(p.y, q.y);
1139
1176
  this.overlappingTraceSegments.forEach((group, gidx) => {
1140
1177
  const offset = offsets[gidx];
1178
+ if (offset === 0) return;
1141
1179
  const byPath = /* @__PURE__ */ new Map();
1142
1180
  for (const loc of group.pathsWithOverlap) {
1143
1181
  if (!byPath.has(loc.solvedTracePathIndex)) {
@@ -2146,6 +2184,7 @@ var NetLabelPlacementSolver = class extends BaseSolver {
2146
2184
  overlappingSameNetTraceGroups;
2147
2185
  queuedOverlappingSameNetTraceGroups;
2148
2186
  netLabelPlacements = [];
2187
+ failedGroups = [];
2149
2188
  currentGroup = null;
2150
2189
  triedAnyOrientationFallbackForCurrentGroup = false;
2151
2190
  constructor(params) {
@@ -2351,8 +2390,12 @@ var NetLabelPlacementSolver = class extends BaseSolver {
2351
2390
  });
2352
2391
  return;
2353
2392
  }
2354
- this.failed = true;
2355
- this.error = this.activeSubSolver.error;
2393
+ if (this.currentGroup) {
2394
+ this.failedGroups.push(this.currentGroup);
2395
+ }
2396
+ this.activeSubSolver = null;
2397
+ this.currentGroup = null;
2398
+ this.triedAnyOrientationFallbackForCurrentGroup = false;
2356
2399
  return;
2357
2400
  }
2358
2401
  if (this.activeSubSolver) {
@@ -3013,6 +3056,38 @@ var generateRerouteCandidates = ({
3013
3056
 
3014
3057
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts
3015
3058
  var MAX_TRIES = 5;
3059
+ var COINCIDENT_EPS = 2e-3;
3060
+ var doesPathCoincideWithTraces = (path, traces) => {
3061
+ const rangesOverlap1D = (a1, a2, b1, b2) => Math.min(Math.max(a1, a2), Math.max(b1, b2)) - Math.max(Math.min(a1, a2), Math.min(b1, b2)) > COINCIDENT_EPS;
3062
+ for (let i = 0; i < path.length - 1; i++) {
3063
+ const pathSegStart = path[i];
3064
+ const pathSegEnd = path[i + 1];
3065
+ const isVertical4 = Math.abs(pathSegStart.x - pathSegEnd.x) < COINCIDENT_EPS;
3066
+ const isHorizontal3 = Math.abs(pathSegStart.y - pathSegEnd.y) < COINCIDENT_EPS;
3067
+ if (!isVertical4 && !isHorizontal3) continue;
3068
+ const crossAxis = isVertical4 ? "x" : "y";
3069
+ const alongAxis = isVertical4 ? "y" : "x";
3070
+ for (const trace of traces) {
3071
+ for (let j = 0; j < trace.tracePath.length - 1; j++) {
3072
+ const traceSegStart = trace.tracePath[j];
3073
+ const traceSegEnd = trace.tracePath[j + 1];
3074
+ const isParallel = Math.abs(traceSegStart[crossAxis] - traceSegEnd[crossAxis]) < COINCIDENT_EPS;
3075
+ if (!isParallel) continue;
3076
+ const isCoincident = Math.abs(pathSegStart[crossAxis] - traceSegStart[crossAxis]) < COINCIDENT_EPS;
3077
+ if (!isCoincident) continue;
3078
+ if (rangesOverlap1D(
3079
+ pathSegStart[alongAxis],
3080
+ pathSegEnd[alongAxis],
3081
+ traceSegStart[alongAxis],
3082
+ traceSegEnd[alongAxis]
3083
+ )) {
3084
+ return true;
3085
+ }
3086
+ }
3087
+ }
3088
+ }
3089
+ return false;
3090
+ };
3016
3091
  var SingleOverlapSolver = class extends BaseSolver {
3017
3092
  queuedCandidatePaths;
3018
3093
  solvedTracePath = null;
@@ -3020,12 +3095,14 @@ var SingleOverlapSolver = class extends BaseSolver {
3020
3095
  problem;
3021
3096
  obstacles;
3022
3097
  label;
3098
+ tracesToAvoidOverlapping;
3023
3099
  _tried = 0;
3024
3100
  constructor(solverInput) {
3025
3101
  super();
3026
3102
  this.initialTrace = solverInput.trace;
3027
3103
  this.problem = solverInput.problem;
3028
3104
  this.label = solverInput.label;
3105
+ this.tracesToAvoidOverlapping = (solverInput.tracesToAvoidOverlapping ?? []).filter((t) => t.globalConnNetId !== solverInput.trace.globalConnNetId);
3029
3106
  const effectivePadding = solverInput.paddingBuffer + solverInput.detourCount * solverInput.paddingBuffer;
3030
3107
  const candidates = generateRerouteCandidates({
3031
3108
  ...solverInput,
@@ -3054,7 +3131,7 @@ var SingleOverlapSolver = class extends BaseSolver {
3054
3131
  this._tried++;
3055
3132
  const nextCandidatePath = this.queuedCandidatePaths.shift();
3056
3133
  const simplifiedPath = simplifyPath(nextCandidatePath);
3057
- if (!isPathCollidingWithObstacles(simplifiedPath, this.obstacles)) {
3134
+ if (!isPathCollidingWithObstacles(simplifiedPath, this.obstacles) && !doesPathCoincideWithTraces(simplifiedPath, this.tracesToAvoidOverlapping)) {
3058
3135
  this.solvedTracePath = simplifiedPath;
3059
3136
  this.solved = true;
3060
3137
  }
@@ -3159,6 +3236,7 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
3159
3236
  mergedNetLabelPlacements;
3160
3237
  mergedLabelNetIdMap;
3161
3238
  allTraces;
3239
+ tracesToAvoidOverlapping;
3162
3240
  modifiedTraces = [];
3163
3241
  PADDING_BUFFER = 0.1;
3164
3242
  detourCounts = /* @__PURE__ */ new Map();
@@ -3174,6 +3252,7 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
3174
3252
  this.mergedNetLabelPlacements = solverInput.mergedNetLabelPlacements;
3175
3253
  this.mergedLabelNetIdMap = solverInput.mergedLabelNetIdMap;
3176
3254
  this.allTraces = [...solverInput.traces];
3255
+ this.tracesToAvoidOverlapping = solverInput.tracesToAvoidOverlapping ?? [];
3177
3256
  this.detourCounts = solverInput.detourCounts;
3178
3257
  }
3179
3258
  _step() {
@@ -3255,7 +3334,8 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
3255
3334
  label: actualOverlapLabel,
3256
3335
  problem: this.inputProblem,
3257
3336
  paddingBuffer: this.PADDING_BUFFER,
3258
- detourCount: detourCount2
3337
+ detourCount: detourCount2,
3338
+ tracesToAvoidOverlapping: this.tracesToAvoidOverlapping
3259
3339
  });
3260
3340
  } else {
3261
3341
  const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`;
@@ -3290,7 +3370,8 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
3290
3370
  label: actualOverlapLabel,
3291
3371
  problem: this.inputProblem,
3292
3372
  paddingBuffer: this.PADDING_BUFFER,
3293
- detourCount: detourCount2
3373
+ detourCount: detourCount2,
3374
+ tracesToAvoidOverlapping: this.tracesToAvoidOverlapping
3294
3375
  });
3295
3376
  } else {
3296
3377
  const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`;
@@ -3305,7 +3386,8 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
3305
3386
  label: labelToAvoid,
3306
3387
  problem: this.inputProblem,
3307
3388
  paddingBuffer: this.PADDING_BUFFER,
3308
- detourCount
3389
+ detourCount,
3390
+ tracesToAvoidOverlapping: this.tracesToAvoidOverlapping
3309
3391
  });
3310
3392
  }
3311
3393
  }
@@ -3373,10 +3455,12 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
3373
3455
  phase = "searching_for_overlaps";
3374
3456
  detourCounts = /* @__PURE__ */ new Map();
3375
3457
  labelMergingSolver;
3458
+ allInputTraces;
3376
3459
  constructor(solverInput) {
3377
3460
  super();
3378
3461
  this.inputProblem = solverInput.inputProblem;
3379
3462
  this.unprocessedTraces = [...solverInput.traces];
3463
+ this.allInputTraces = solverInput.traces;
3380
3464
  this.netLabelPlacements = solverInput.netLabelPlacements;
3381
3465
  this.cleanTraces = [];
3382
3466
  this.subSolvers = [];
@@ -3412,7 +3496,10 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
3412
3496
  initialNetLabelPlacements: this.netLabelPlacements,
3413
3497
  mergedNetLabelPlacements: mergingOutput.netLabelPlacements,
3414
3498
  mergedLabelNetIdMap: mergingOutput.mergedLabelNetIdMap,
3415
- detourCounts: this.detourCounts
3499
+ detourCounts: this.detourCounts,
3500
+ tracesToAvoidOverlapping: this.allInputTraces.filter(
3501
+ (t) => t.mspPairId !== currentTargetTrace.mspPairId
3502
+ )
3416
3503
  });
3417
3504
  this.subSolvers.push(subSolver);
3418
3505
  }
@@ -6198,28 +6285,29 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
6198
6285
  getSideOffsetAnchor(params) {
6199
6286
  const { anchorPoint, labelAnchorPoint, orientation, width, height } = params;
6200
6287
  const chipSide = this.getChipSideForPoint(labelAnchorPoint);
6288
+ const sideClearance = this.getContainingChipSide(labelAnchorPoint) ? WICK_CLEARANCE : LABEL_SEARCH_STEP;
6201
6289
  if (isYOrientation(orientation) && chipSide === "left") {
6202
6290
  return {
6203
- x: anchorPoint.x - width / 2 - WICK_CLEARANCE,
6291
+ x: anchorPoint.x - width / 2 - sideClearance,
6204
6292
  y: anchorPoint.y
6205
6293
  };
6206
6294
  }
6207
6295
  if (isYOrientation(orientation) && chipSide === "right") {
6208
6296
  return {
6209
- x: anchorPoint.x + width / 2 + WICK_CLEARANCE,
6297
+ x: anchorPoint.x + width / 2 + sideClearance,
6210
6298
  y: anchorPoint.y
6211
6299
  };
6212
6300
  }
6213
6301
  if (isXOrientation(orientation) && chipSide === "bottom") {
6214
6302
  return {
6215
6303
  x: anchorPoint.x,
6216
- y: anchorPoint.y - height / 2 - WICK_CLEARANCE
6304
+ y: anchorPoint.y - height / 2 - sideClearance
6217
6305
  };
6218
6306
  }
6219
6307
  if (isXOrientation(orientation) && chipSide === "top") {
6220
6308
  return {
6221
6309
  x: anchorPoint.x,
6222
- y: anchorPoint.y + height / 2 + WICK_CLEARANCE
6310
+ y: anchorPoint.y + height / 2 + sideClearance
6223
6311
  };
6224
6312
  }
6225
6313
  return anchorPoint;
@@ -474,28 +474,31 @@ export class AvailableNetOrientationSolver extends BaseSolver {
474
474
  }) {
475
475
  const { anchorPoint, labelAnchorPoint, orientation, width, height } = params
476
476
  const chipSide = this.getChipSideForPoint(labelAnchorPoint)
477
+ const sideClearance = this.getContainingChipSide(labelAnchorPoint)
478
+ ? WICK_CLEARANCE
479
+ : LABEL_SEARCH_STEP
477
480
  if (isYOrientation(orientation) && chipSide === "left") {
478
481
  return {
479
- x: anchorPoint.x - width / 2 - WICK_CLEARANCE,
482
+ x: anchorPoint.x - width / 2 - sideClearance,
480
483
  y: anchorPoint.y,
481
484
  }
482
485
  }
483
486
  if (isYOrientation(orientation) && chipSide === "right") {
484
487
  return {
485
- x: anchorPoint.x + width / 2 + WICK_CLEARANCE,
488
+ x: anchorPoint.x + width / 2 + sideClearance,
486
489
  y: anchorPoint.y,
487
490
  }
488
491
  }
489
492
  if (isXOrientation(orientation) && chipSide === "bottom") {
490
493
  return {
491
494
  x: anchorPoint.x,
492
- y: anchorPoint.y - height / 2 - WICK_CLEARANCE,
495
+ y: anchorPoint.y - height / 2 - sideClearance,
493
496
  }
494
497
  }
495
498
  if (isXOrientation(orientation) && chipSide === "top") {
496
499
  return {
497
500
  x: anchorPoint.x,
498
- y: anchorPoint.y + height / 2 + WICK_CLEARANCE,
501
+ y: anchorPoint.y + height / 2 + sideClearance,
499
502
  }
500
503
  }
501
504
  return anchorPoint
@@ -74,6 +74,7 @@ export class NetLabelPlacementSolver extends BaseSolver {
74
74
  declare activeSubSolver: SingleNetLabelPlacementSolver | null
75
75
 
76
76
  netLabelPlacements: Array<NetLabelPlacement> = []
77
+ failedGroups: Array<OverlappingSameNetTraceGroup> = []
77
78
  currentGroup: OverlappingSameNetTraceGroup | null = null
78
79
  triedAnyOrientationFallbackForCurrentGroup = false
79
80
 
@@ -336,8 +337,13 @@ export class NetLabelPlacementSolver extends BaseSolver {
336
337
  return
337
338
  }
338
339
 
339
- this.failed = true
340
- this.error = this.activeSubSolver.error
340
+ // Record the failure for this group and continue to the next one
341
+ if (this.currentGroup) {
342
+ this.failedGroups.push(this.currentGroup)
343
+ }
344
+ this.activeSubSolver = null
345
+ this.currentGroup = null
346
+ this.triedAnyOrientationFallbackForCurrentGroup = false
341
347
  return
342
348
  }
343
349
 
@@ -42,10 +42,13 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
42
42
 
43
43
  labelMergingSolver?: MergedNetLabelObstacleSolver
44
44
 
45
+ allInputTraces: SolvedTracePath[]
46
+
45
47
  constructor(solverInput: TraceLabelOverlapAvoidanceSolverInput) {
46
48
  super()
47
49
  this.inputProblem = solverInput.inputProblem
48
50
  this.unprocessedTraces = [...solverInput.traces]
51
+ this.allInputTraces = solverInput.traces
49
52
  this.netLabelPlacements = solverInput.netLabelPlacements
50
53
  this.cleanTraces = []
51
54
  this.subSolvers = []
@@ -88,6 +91,9 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
88
91
  mergedNetLabelPlacements: mergingOutput.netLabelPlacements,
89
92
  mergedLabelNetIdMap: mergingOutput.mergedLabelNetIdMap,
90
93
  detourCounts: this.detourCounts,
94
+ tracesToAvoidOverlapping: this.allInputTraces.filter(
95
+ (t) => t.mspPairId !== currentTargetTrace.mspPairId,
96
+ ),
91
97
  })
92
98
  this.subSolvers.push(subSolver)
93
99
  }
@@ -19,6 +19,7 @@ export interface OverlapCollectionSolverInput {
19
19
  mergedNetLabelPlacements: NetLabelPlacement[]
20
20
  mergedLabelNetIdMap: Record<string, Set<string>>
21
21
  detourCounts: Map<string, number>
22
+ tracesToAvoidOverlapping?: SolvedTracePath[]
22
23
  }
23
24
 
24
25
  /**
@@ -32,6 +33,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
32
33
  mergedLabelNetIdMap: Record<string, Set<string>>
33
34
 
34
35
  allTraces: SolvedTracePath[]
36
+ tracesToAvoidOverlapping: SolvedTracePath[]
35
37
  modifiedTraces: SolvedTracePath[] = []
36
38
 
37
39
  private readonly PADDING_BUFFER = 0.1
@@ -51,6 +53,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
51
53
  this.mergedNetLabelPlacements = solverInput.mergedNetLabelPlacements
52
54
  this.mergedLabelNetIdMap = solverInput.mergedLabelNetIdMap
53
55
  this.allTraces = [...solverInput.traces]
56
+ this.tracesToAvoidOverlapping = solverInput.tracesToAvoidOverlapping ?? []
54
57
  this.detourCounts = solverInput.detourCounts
55
58
  }
56
59
 
@@ -150,6 +153,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
150
153
  problem: this.inputProblem,
151
154
  paddingBuffer: this.PADDING_BUFFER,
152
155
  detourCount: detourCount,
156
+ tracesToAvoidOverlapping: this.tracesToAvoidOverlapping,
153
157
  })
154
158
  } else {
155
159
  const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`
@@ -192,6 +196,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
192
196
  problem: this.inputProblem,
193
197
  paddingBuffer: this.PADDING_BUFFER,
194
198
  detourCount: detourCount,
199
+ tracesToAvoidOverlapping: this.tracesToAvoidOverlapping,
195
200
  })
196
201
  } else {
197
202
  const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`
@@ -211,6 +216,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
211
216
  problem: this.inputProblem,
212
217
  paddingBuffer: this.PADDING_BUFFER,
213
218
  detourCount: detourCount,
219
+ tracesToAvoidOverlapping: this.tracesToAvoidOverlapping,
214
220
  })
215
221
  }
216
222
  }
@@ -16,9 +16,69 @@ interface SingleOverlapSolverInput {
16
16
  problem: InputProblem
17
17
  paddingBuffer: number
18
18
  detourCount: number
19
+ tracesToAvoidOverlapping?: SolvedTracePath[]
19
20
  }
20
21
 
21
22
  const MAX_TRIES = 5
23
+ const COINCIDENT_EPS = 2e-3
24
+
25
+ /**
26
+ * Returns true if any segment of the path is parallel and coincident
27
+ * (within COINCIDENT_EPS) with a segment of one of the given traces.
28
+ * Perpendicular crossings are allowed.
29
+ */
30
+ const doesPathCoincideWithTraces = (
31
+ path: Point[],
32
+ traces: SolvedTracePath[],
33
+ ): boolean => {
34
+ const rangesOverlap1D = (a1: number, a2: number, b1: number, b2: number) =>
35
+ Math.min(Math.max(a1, a2), Math.max(b1, b2)) -
36
+ Math.max(Math.min(a1, a2), Math.min(b1, b2)) >
37
+ COINCIDENT_EPS
38
+
39
+ for (let i = 0; i < path.length - 1; i++) {
40
+ const pathSegStart = path[i]!
41
+ const pathSegEnd = path[i + 1]!
42
+ const isVertical = Math.abs(pathSegStart.x - pathSegEnd.x) < COINCIDENT_EPS
43
+ const isHorizontal =
44
+ Math.abs(pathSegStart.y - pathSegEnd.y) < COINCIDENT_EPS
45
+ if (!isVertical && !isHorizontal) continue
46
+
47
+ // For a vertical segment, coincidence is measured in x and overlap in y
48
+ // (and vice versa for horizontal)
49
+ const crossAxis = isVertical ? "x" : "y"
50
+ const alongAxis = isVertical ? "y" : "x"
51
+
52
+ for (const trace of traces) {
53
+ for (let j = 0; j < trace.tracePath.length - 1; j++) {
54
+ const traceSegStart = trace.tracePath[j]!
55
+ const traceSegEnd = trace.tracePath[j + 1]!
56
+
57
+ const isParallel =
58
+ Math.abs(traceSegStart[crossAxis] - traceSegEnd[crossAxis]) <
59
+ COINCIDENT_EPS
60
+ if (!isParallel) continue
61
+
62
+ const isCoincident =
63
+ Math.abs(pathSegStart[crossAxis] - traceSegStart[crossAxis]) <
64
+ COINCIDENT_EPS
65
+ if (!isCoincident) continue
66
+
67
+ if (
68
+ rangesOverlap1D(
69
+ pathSegStart[alongAxis],
70
+ pathSegEnd[alongAxis],
71
+ traceSegStart[alongAxis],
72
+ traceSegEnd[alongAxis],
73
+ )
74
+ ) {
75
+ return true
76
+ }
77
+ }
78
+ }
79
+ }
80
+ return false
81
+ }
22
82
 
23
83
  /**
24
84
  * This solver attempts to find a valid rerouting for a single trace that is
@@ -32,6 +92,7 @@ export class SingleOverlapSolver extends BaseSolver {
32
92
  problem: InputProblem
33
93
  obstacles: ReturnType<typeof getObstacleRects>
34
94
  label: NetLabelPlacement
95
+ tracesToAvoidOverlapping: SolvedTracePath[]
35
96
  _tried: number = 0
36
97
 
37
98
  constructor(solverInput: SingleOverlapSolverInput) {
@@ -39,6 +100,9 @@ export class SingleOverlapSolver extends BaseSolver {
39
100
  this.initialTrace = solverInput.trace
40
101
  this.problem = solverInput.problem
41
102
  this.label = solverInput.label
103
+ this.tracesToAvoidOverlapping = (
104
+ solverInput.tracesToAvoidOverlapping ?? []
105
+ ).filter((t) => t.globalConnNetId !== solverInput.trace.globalConnNetId)
42
106
 
43
107
  // Calculate an effective padding for this specific run based on the detourCount.
44
108
  const effectivePadding =
@@ -77,7 +141,10 @@ export class SingleOverlapSolver extends BaseSolver {
77
141
  const nextCandidatePath = this.queuedCandidatePaths.shift()!
78
142
  const simplifiedPath = simplifyPath(nextCandidatePath)
79
143
 
80
- if (!isPathCollidingWithObstacles(simplifiedPath, this.obstacles)) {
144
+ if (
145
+ !isPathCollidingWithObstacles(simplifiedPath, this.obstacles) &&
146
+ !doesPathCoincideWithTraces(simplifiedPath, this.tracesToAvoidOverlapping)
147
+ ) {
81
148
  this.solvedTracePath = simplifiedPath
82
149
  this.solved = true
83
150
  }
@@ -57,8 +57,28 @@ export class TraceOverlapIssueSolver extends BaseSolver {
57
57
  // Shift only the overlapping segments, and move the shared endpoints
58
58
  // (the last point of the previous segment and the first point of the next
59
59
  // segment) so the polyline remains orthogonal without self-overlap.
60
+ // Groups containing a straight pin-to-pin trace (2 points, both endpoints
61
+ // are pins) keep their position when another group can shift instead:
62
+ // shifting such a trace would force jogs on an otherwise straight line.
63
+ const containsStraightPinToPinTrace = (
64
+ group: OverlappingTraceSegmentLocator,
65
+ ) =>
66
+ group.pathsWithOverlap.some(({ solvedTracePathIndex }) => {
67
+ const path =
68
+ this.traceNetIslands[group.connNetId][solvedTracePathIndex]!
69
+ return path.tracePath.length === 2
70
+ })
71
+
72
+ const groupShouldStayInPlace = this.overlappingTraceSegments.map(
73
+ containsStraightPinToPinTrace,
74
+ )
75
+ const someGroupCanShift = groupShouldStayInPlace.some(
76
+ (shouldStay) => !shouldStay,
77
+ )
78
+
60
79
  // Compute offsets for each island involved: alternate directions
61
80
  const offsets = this.overlappingTraceSegments.map((group, idx) => {
81
+ if (someGroupCanShift && groupShouldStayInPlace[idx]) return 0
62
82
  const n = Math.floor(idx / 2) + 1
63
83
  const signed = idx % 2 === 0 ? -n : n
64
84
  return this.getObstacleAwareOffset({
@@ -76,6 +96,7 @@ export class TraceOverlapIssueSolver extends BaseSolver {
76
96
  // For each net island group, shift only its overlapping segments and adjust adjacent joints
77
97
  this.overlappingTraceSegments.forEach((group, gidx) => {
78
98
  const offset = offsets[gidx]!
99
+ if (offset === 0) return
79
100
 
80
101
  // Gather unique segment indices per path
81
102
  const byPath: Map<number, Set<number>> = new Map()
@@ -29,6 +29,36 @@ export const applyJogToTerminalSegment = ({
29
29
  ? 1
30
30
  : -1
31
31
 
32
+ if (pts.length === 2) {
33
+ // The segment is both the first and last segment: both endpoints are
34
+ // pins, so jog inward on both sides to keep the endpoints anchored
35
+ if (isVertical) {
36
+ const jogYNearStart = start.y + segDir * JOG_SIZE
37
+ const jogYNearEnd = end.y - segDir * JOG_SIZE
38
+ pts.splice(
39
+ 1,
40
+ 0,
41
+ { x: start.x, y: jogYNearStart },
42
+ { x: start.x + offset, y: jogYNearStart },
43
+ { x: end.x + offset, y: jogYNearEnd },
44
+ { x: end.x, y: jogYNearEnd },
45
+ )
46
+ } else {
47
+ // Horizontal
48
+ const jogXNearStart = start.x + segDir * JOG_SIZE
49
+ const jogXNearEnd = end.x - segDir * JOG_SIZE
50
+ pts.splice(
51
+ 1,
52
+ 0,
53
+ { x: jogXNearStart, y: start.y },
54
+ { x: jogXNearStart, y: start.y + offset },
55
+ { x: jogXNearEnd, y: end.y + offset },
56
+ { x: jogXNearEnd, y: end.y },
57
+ )
58
+ }
59
+ return
60
+ }
61
+
32
62
  if (si === 0) {
33
63
  if (isVertical) {
34
64
  const jogY = start.y + segDir * JOG_SIZE
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.65",
4
+ "version": "0.0.67",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",