@tscircuit/schematic-trace-solver 0.0.20 → 0.0.22

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.
@@ -4,6 +4,9 @@ on:
4
4
  push:
5
5
  branches:
6
6
  - main
7
+ paths:
8
+ - 'lib/**'
9
+ workflow_dispatch:
7
10
  jobs:
8
11
  publish:
9
12
  runs-on: ubuntu-latest
package/dist/index.d.ts CHANGED
@@ -161,6 +161,7 @@ declare class SchematicTraceSingleLineSolver extends BaseSolver {
161
161
  chipMap: Record<string, InputChip>;
162
162
  movableSegments: Array<MovableSegment>;
163
163
  baseElbow: Point[];
164
+ allCandidatePaths: Array<Point[]>;
164
165
  queuedCandidatePaths: Array<Point[]>;
165
166
  chipObstacleSpatialIndex: ChipObstacleSpatialIndex;
166
167
  solvedTracePath: {
@@ -343,6 +344,7 @@ type OverlappingSameNetTraceGroup = {
343
344
  netId?: string;
344
345
  overlappingTraces?: SolvedTracePath;
345
346
  portOnlyPinId?: string;
347
+ mspConnectionPairIds?: MspConnectionPairId[];
346
348
  };
347
349
  interface NetLabelPlacement {
348
350
  globalConnNetId: string;
package/dist/index.js CHANGED
@@ -6520,81 +6520,167 @@ var dir = (facingDirection) => {
6520
6520
  };
6521
6521
 
6522
6522
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/generateElbowVariants.ts
6523
- var generateElbowVariants = ({
6524
- baseElbow,
6525
- guidelines
6526
- }) => {
6527
- const movableSegments = [];
6528
- for (let i = 1; i < baseElbow.length - 2; i++) {
6529
- const prev = baseElbow[i - 1];
6530
- const start = baseElbow[i];
6531
- const end = baseElbow[i + 1];
6532
- const isHorz = Math.abs(start.y - end.y) < 1e-6;
6533
- let freedom;
6534
- if (isHorz) {
6535
- freedom = prev.y <= start.y ? "y+" : "y-";
6536
- } else {
6537
- freedom = prev.x <= start.x ? "x+" : "x-";
6523
+ var EPS = 1e-6;
6524
+ var MIN_LEN = 1e-6;
6525
+ var isAxisAligned = (a, b) => Math.abs(a.x - b.x) < EPS || Math.abs(a.y - b.y) < EPS;
6526
+ var orientationOf = (a, b) => {
6527
+ const dx = Math.abs(a.x - b.x);
6528
+ const dy = Math.abs(a.y - b.y);
6529
+ if (dx < EPS && dy >= EPS) return "vertical";
6530
+ if (dy < EPS && dx >= EPS) return "horizontal";
6531
+ throw new Error("Non-orthogonal or degenerate segment detected.");
6532
+ };
6533
+ var assertOrthogonalPolyline = (pts) => {
6534
+ if (pts.length < 2) {
6535
+ throw new Error("Polyline must have at least two points.");
6536
+ }
6537
+ for (let i = 0; i < pts.length - 1; i++) {
6538
+ const a = pts[i];
6539
+ const b = pts[i + 1];
6540
+ if (!isAxisAligned(a, b)) {
6541
+ throw new Error("Polyline contains a non-orthogonal segment.");
6542
+ }
6543
+ const manhattan = Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
6544
+ if (manhattan < MIN_LEN) {
6545
+ throw new Error("Polyline contains a near-zero length segment.");
6538
6546
  }
6539
- movableSegments.push({ start, end, freedom, dir: dir(freedom) });
6540
6547
  }
6541
- const segmentGuidelineOptions = [];
6542
- for (const segment of movableSegments) {
6543
- const relevantPositions = [];
6544
- if (segment.freedom === "x+" || segment.freedom === "x-") {
6545
- for (const guideline of guidelines) {
6546
- if (guideline.orientation === "vertical" && guideline.x !== void 0) {
6547
- relevantPositions.push(guideline.x);
6548
- }
6548
+ };
6549
+ var uniqSortedWithTol = (vals, tol = EPS) => {
6550
+ const sorted = vals.slice().sort((a, b) => a - b);
6551
+ const out = [];
6552
+ for (const v of sorted) {
6553
+ if (out.length === 0 || Math.abs(v - out[out.length - 1]) > tol) out.push(v);
6554
+ }
6555
+ return out;
6556
+ };
6557
+ var keyForPolyline = (pts, decimals = 9) => pts.map((p) => `${p.x.toFixed(decimals)},${p.y.toFixed(decimals)}`).join("|");
6558
+ var preprocessElbow = (pts, tol = MIN_LEN) => {
6559
+ if (pts.length === 0) return [];
6560
+ const out = [{ ...pts[0] }];
6561
+ for (let i = 1; i < pts.length; i++) {
6562
+ const p = pts[i];
6563
+ const last = out[out.length - 1];
6564
+ const manhattan = Math.abs(p.x - last.x) + Math.abs(p.y - last.y);
6565
+ if (manhattan > tol) out.push({ ...p });
6566
+ }
6567
+ return out;
6568
+ };
6569
+ var collectAxisCandidates = (axis, guidelines, currentCoord) => {
6570
+ const positions = [];
6571
+ for (const g of guidelines) {
6572
+ if (axis === "x") {
6573
+ if (g.orientation === "vertical" && typeof g.x === "number") {
6574
+ positions.push(g.x);
6549
6575
  }
6550
- relevantPositions.push(segment.start.x);
6551
6576
  } else {
6552
- for (const guideline of guidelines) {
6553
- if (guideline.orientation === "horizontal" && guideline.y !== void 0) {
6554
- relevantPositions.push(guideline.y);
6555
- }
6577
+ if (g.orientation === "horizontal" && typeof g.y === "number") {
6578
+ positions.push(g.y);
6556
6579
  }
6557
- relevantPositions.push(segment.start.y);
6558
6580
  }
6559
- segmentGuidelineOptions.push(
6560
- [...new Set(relevantPositions)].sort((a, b) => a - b)
6561
- );
6562
6581
  }
6563
- const generateCombinations = (options) => {
6564
- if (options.length === 0) return [[]];
6565
- if (options.length === 1) return options[0].map((pos) => [pos]);
6566
- const combinations = [];
6567
- const firstOptions = options[0];
6568
- const restCombinations = generateCombinations(options.slice(1));
6569
- for (const firstOption of firstOptions) {
6570
- for (const restCombination of restCombinations) {
6571
- combinations.push([firstOption, ...restCombination]);
6582
+ positions.push(currentCoord);
6583
+ return uniqSortedWithTol(positions);
6584
+ };
6585
+ var computeFreedom = (prev, start, end) => {
6586
+ const orient = orientationOf(start, end);
6587
+ if (orient === "horizontal") {
6588
+ const facing = prev.y <= start.y ? "y+" : "y-";
6589
+ return { axis: "y", facing };
6590
+ } else {
6591
+ const facing = prev.x <= start.x ? "x+" : "x-";
6592
+ return { axis: "x", facing };
6593
+ }
6594
+ };
6595
+ var cartesian = (arrays) => arrays.length === 0 ? [[]] : arrays.reduce(
6596
+ (acc, curr) => acc.flatMap((a) => curr.map((c) => [...a, c])),
6597
+ [[]]
6598
+ );
6599
+ var generateElbowVariants = ({
6600
+ baseElbow,
6601
+ guidelines
6602
+ }) => {
6603
+ const elbow = preprocessElbow(baseElbow);
6604
+ assertOrthogonalPolyline(elbow);
6605
+ const nPts = elbow.length;
6606
+ const nSegs = nPts - 1;
6607
+ if (nSegs < 5) {
6608
+ return {
6609
+ elbowVariants: [elbow.map((p) => ({ ...p }))],
6610
+ movableSegments: []
6611
+ };
6612
+ }
6613
+ const firstMovableIndex = 2;
6614
+ const lastMovableIndex = nPts - 4;
6615
+ const movableSegments = [];
6616
+ const movableIdx = [];
6617
+ const axes = [];
6618
+ const optionsPerSegment = [];
6619
+ for (let i = firstMovableIndex; i <= lastMovableIndex; i++) {
6620
+ const prev = elbow[i - 1];
6621
+ const start = elbow[i];
6622
+ const end = elbow[i + 1];
6623
+ const next2 = elbow[i + 2];
6624
+ const { axis, facing } = computeFreedom(prev, start, end);
6625
+ movableSegments.push({
6626
+ start: { ...start },
6627
+ end: { ...end },
6628
+ freedom: facing,
6629
+ dir: dir(facing)
6630
+ });
6631
+ movableIdx.push(i);
6632
+ axes.push(axis);
6633
+ const currentCoord = axis === "x" ? start.x : start.y;
6634
+ const rawCandidates = collectAxisCandidates(axis, guidelines, currentCoord);
6635
+ const filtered = rawCandidates.filter((pos) => {
6636
+ if (axis === "y") {
6637
+ const lenPrev = Math.abs(prev.y - pos);
6638
+ const lenNext = Math.abs(next2.y - pos);
6639
+ return lenPrev > MIN_LEN && lenNext > MIN_LEN;
6640
+ } else {
6641
+ const lenPrev = Math.abs(prev.x - pos);
6642
+ const lenNext = Math.abs(next2.x - pos);
6643
+ return lenPrev > MIN_LEN && lenNext > MIN_LEN;
6572
6644
  }
6573
- }
6574
- return combinations;
6575
- };
6576
- const positionCombinations = generateCombinations(segmentGuidelineOptions);
6645
+ });
6646
+ optionsPerSegment.push(filtered);
6647
+ }
6648
+ const combos = cartesian(optionsPerSegment);
6649
+ const seen = /* @__PURE__ */ new Set();
6577
6650
  const elbowVariants = [];
6578
- for (const combination of positionCombinations) {
6579
- const variant = [...baseElbow];
6580
- for (let segmentIndex = 0; segmentIndex < movableSegments.length; segmentIndex++) {
6581
- const segment = movableSegments[segmentIndex];
6582
- const newPosition = combination[segmentIndex];
6583
- const elbowIndex = segmentIndex + 1;
6584
- if (segment.freedom === "x+" || segment.freedom === "x-") {
6585
- variant[elbowIndex] = { ...variant[elbowIndex], x: newPosition };
6586
- variant[elbowIndex + 1] = { ...variant[elbowIndex + 1], x: newPosition };
6651
+ {
6652
+ const key = keyForPolyline(elbow);
6653
+ seen.add(key);
6654
+ elbowVariants.push(elbow.map((p) => ({ ...p })));
6655
+ }
6656
+ for (const combo of combos) {
6657
+ if (combo.length === 0) continue;
6658
+ const variant = elbow.map((p) => ({ ...p }));
6659
+ for (let k = 0; k < movableIdx.length; k++) {
6660
+ const segIndex = movableIdx[k];
6661
+ const axis = axes[k];
6662
+ const newPos = combo[k];
6663
+ if (typeof newPos !== "number" || Number.isNaN(newPos)) continue;
6664
+ if (axis === "y") {
6665
+ variant[segIndex] = { ...variant[segIndex], y: newPos };
6666
+ variant[segIndex + 1] = { ...variant[segIndex + 1], y: newPos };
6587
6667
  } else {
6588
- variant[elbowIndex] = { ...variant[elbowIndex], y: newPosition };
6589
- variant[elbowIndex + 1] = { ...variant[elbowIndex + 1], y: newPosition };
6668
+ variant[segIndex] = { ...variant[segIndex], x: newPos };
6669
+ variant[segIndex + 1] = { ...variant[segIndex + 1], x: newPos };
6590
6670
  }
6591
6671
  }
6592
- elbowVariants.push(variant);
6672
+ try {
6673
+ assertOrthogonalPolyline(variant);
6674
+ } catch {
6675
+ continue;
6676
+ }
6677
+ const key = keyForPolyline(variant);
6678
+ if (!seen.has(key)) {
6679
+ seen.add(key);
6680
+ elbowVariants.push(variant);
6681
+ }
6593
6682
  }
6594
- return {
6595
- elbowVariants,
6596
- movableSegments
6597
- };
6683
+ return { elbowVariants, movableSegments };
6598
6684
  };
6599
6685
 
6600
6686
  // lib/solvers/GuidelinesSolver/visualizeGuidelines.ts
@@ -6642,6 +6728,7 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
6642
6728
  chipMap;
6643
6729
  movableSegments;
6644
6730
  baseElbow;
6731
+ allCandidatePaths;
6645
6732
  queuedCandidatePaths;
6646
6733
  chipObstacleSpatialIndex;
6647
6734
  solvedTracePath = null;
@@ -6691,7 +6778,8 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
6691
6778
  }
6692
6779
  return len;
6693
6780
  };
6694
- this.queuedCandidatePaths = [this.baseElbow, ...elbowVariants].sort(
6781
+ this.allCandidatePaths = [this.baseElbow, ...elbowVariants];
6782
+ this.queuedCandidatePaths = [...this.allCandidatePaths].sort(
6695
6783
  (a, b) => getPathLength(a) - getPathLength(b)
6696
6784
  );
6697
6785
  }
@@ -6728,12 +6816,12 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
6728
6816
  const bounds = getInputChipBounds(this.chipMap[startPin.chipId]);
6729
6817
  const dx = end.x - start.x;
6730
6818
  const dy = end.y - start.y;
6731
- const EPS = 1e-9;
6819
+ const EPS2 = 1e-9;
6732
6820
  const onLeft = Math.abs(start.x - bounds.minX) < 1e-9;
6733
6821
  const onRight = Math.abs(start.x - bounds.maxX) < 1e-9;
6734
6822
  const onBottom = Math.abs(start.y - bounds.minY) < 1e-9;
6735
6823
  const onTop = Math.abs(start.y - bounds.maxY) < 1e-9;
6736
- const entersInterior = onLeft && dx > EPS || onRight && dx < -EPS || onBottom && dy > EPS || onTop && dy < -EPS;
6824
+ const entersInterior = onLeft && dx > EPS2 || onRight && dx < -EPS2 || onBottom && dy > EPS2 || onTop && dy < -EPS2;
6737
6825
  if (entersInterior) return;
6738
6826
  if (!excludeChipIds.includes(startPin.chipId)) {
6739
6827
  excludeChipIds.push(startPin.chipId);
@@ -6748,12 +6836,12 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
6748
6836
  const bounds = getInputChipBounds(this.chipMap[endPin.chipId]);
6749
6837
  const dx = start.x - end.x;
6750
6838
  const dy = start.y - end.y;
6751
- const EPS = 1e-9;
6839
+ const EPS2 = 1e-9;
6752
6840
  const onLeft = Math.abs(end.x - bounds.minX) < 1e-9;
6753
6841
  const onRight = Math.abs(end.x - bounds.maxX) < 1e-9;
6754
6842
  const onBottom = Math.abs(end.y - bounds.minY) < 1e-9;
6755
6843
  const onTop = Math.abs(end.y - bounds.maxY) < 1e-9;
6756
- const entersInterior = onLeft && dx > EPS || onRight && dx < -EPS || onBottom && dy > EPS || onTop && dy < -EPS;
6844
+ const entersInterior = onLeft && dx > EPS2 || onRight && dx < -EPS2 || onBottom && dy > EPS2 || onTop && dy < -EPS2;
6757
6845
  if (entersInterior) return;
6758
6846
  if (!excludeChipIds.includes(endPin.chipId)) {
6759
6847
  excludeChipIds.push(endPin.chipId);
@@ -6956,13 +7044,13 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
6956
7044
  }
6957
7045
  }
6958
7046
  _step() {
6959
- const EPS = 1e-6;
7047
+ const EPS2 = 1e-6;
6960
7048
  const offsets = this.overlappingTraceSegments.map((_, idx) => {
6961
7049
  const n = Math.floor(idx / 2) + 1;
6962
7050
  const signed = idx % 2 === 0 ? -n : n;
6963
7051
  return signed * this.SHIFT_DISTANCE;
6964
7052
  });
6965
- const eq = (a, b) => Math.abs(a - b) < EPS;
7053
+ const eq = (a, b) => Math.abs(a - b) < EPS2;
6966
7054
  const samePoint = (p, q) => !!p && !!q && eq(p.x, q.x) && eq(p.y, q.y);
6967
7055
  this.overlappingTraceSegments.forEach((group, gidx) => {
6968
7056
  const offset = offsets[gidx];
@@ -6985,8 +7073,8 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
6985
7073
  if (si === 0 || si === pts.length - 2) continue;
6986
7074
  const start = pts[si];
6987
7075
  const end = pts[si + 1];
6988
- const isVertical = Math.abs(start.x - end.x) < EPS;
6989
- const isHorizontal = Math.abs(start.y - end.y) < EPS;
7076
+ const isVertical = Math.abs(start.x - end.x) < EPS2;
7077
+ const isHorizontal = Math.abs(start.y - end.y) < EPS2;
6990
7078
  if (!isVertical && !isHorizontal) continue;
6991
7079
  if (isVertical) {
6992
7080
  if (!appliedX.has(si)) {
@@ -7095,7 +7183,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
7095
7183
  return islands;
7096
7184
  }
7097
7185
  findNextOverlapIssue() {
7098
- const EPS = 1e-6;
7186
+ const EPS2 = 1e-6;
7099
7187
  const netIds = Object.keys(this.traceNetIslands);
7100
7188
  for (let i = 0; i < netIds.length; i++) {
7101
7189
  for (let j = i + 1; j < netIds.length; j++) {
@@ -7113,7 +7201,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
7113
7201
  const minB = Math.min(b1, b2);
7114
7202
  const maxB = Math.max(b1, b2);
7115
7203
  const overlap = Math.min(maxA, maxB) - Math.max(minA, minB);
7116
- return overlap > EPS;
7204
+ return overlap > EPS2;
7117
7205
  };
7118
7206
  for (let pa = 0; pa < pathsA.length; pa++) {
7119
7207
  const pathA = pathsA[pa];
@@ -7121,8 +7209,8 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
7121
7209
  for (let sa = 0; sa < ptsA.length - 1; sa++) {
7122
7210
  const a1 = ptsA[sa];
7123
7211
  const a2 = ptsA[sa + 1];
7124
- const aVert = Math.abs(a1.x - a2.x) < EPS;
7125
- const aHorz = Math.abs(a1.y - a2.y) < EPS;
7212
+ const aVert = Math.abs(a1.x - a2.x) < EPS2;
7213
+ const aHorz = Math.abs(a1.y - a2.y) < EPS2;
7126
7214
  if (!aVert && !aHorz) continue;
7127
7215
  for (let pb = 0; pb < pathsB.length; pb++) {
7128
7216
  const pathB = pathsB[pb];
@@ -7130,11 +7218,11 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
7130
7218
  for (let sb = 0; sb < ptsB.length - 1; sb++) {
7131
7219
  const b1 = ptsB[sb];
7132
7220
  const b2 = ptsB[sb + 1];
7133
- const bVert = Math.abs(b1.x - b2.x) < EPS;
7134
- const bHorz = Math.abs(b1.y - b2.y) < EPS;
7221
+ const bVert = Math.abs(b1.x - b2.x) < EPS2;
7222
+ const bHorz = Math.abs(b1.y - b2.y) < EPS2;
7135
7223
  if (!bVert && !bHorz) continue;
7136
7224
  if (aVert && bVert) {
7137
- if (Math.abs(a1.x - b1.x) < EPS) {
7225
+ if (Math.abs(a1.x - b1.x) < EPS2) {
7138
7226
  if (overlaps1D(a1.y, a2.y, b1.y, b2.y)) {
7139
7227
  const keyA = `${pa}:${sa}`;
7140
7228
  const keyB = `${pb}:${sb}`;
@@ -7155,7 +7243,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
7155
7243
  }
7156
7244
  }
7157
7245
  } else if (aHorz && bHorz) {
7158
- if (Math.abs(a1.y - b1.y) < EPS) {
7246
+ if (Math.abs(a1.y - b1.y) < EPS2) {
7159
7247
  if (overlaps1D(a1.x, a2.x, b1.x, b2.x)) {
7160
7248
  const keyA = `${pa}:${sa}`;
7161
7249
  const keyB = `${pb}:${sb}`;
@@ -7270,24 +7358,24 @@ function getRectBounds(center, w, h) {
7270
7358
  }
7271
7359
 
7272
7360
  // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions.ts
7273
- function segmentIntersectsRect(p1, p2, rect, EPS = 1e-9) {
7274
- const isVert = Math.abs(p1.x - p2.x) < EPS;
7275
- const isHorz = Math.abs(p1.y - p2.y) < EPS;
7361
+ function segmentIntersectsRect(p1, p2, rect, EPS2 = 1e-9) {
7362
+ const isVert = Math.abs(p1.x - p2.x) < EPS2;
7363
+ const isHorz = Math.abs(p1.y - p2.y) < EPS2;
7276
7364
  if (!isVert && !isHorz) return false;
7277
7365
  if (isVert) {
7278
7366
  const x = p1.x;
7279
- if (x < rect.minX - EPS || x > rect.maxX + EPS) return false;
7367
+ if (x < rect.minX - EPS2 || x > rect.maxX + EPS2) return false;
7280
7368
  const segMinY = Math.min(p1.y, p2.y);
7281
7369
  const segMaxY = Math.max(p1.y, p2.y);
7282
7370
  const overlap = Math.min(segMaxY, rect.maxY) - Math.max(segMinY, rect.minY);
7283
- return overlap > EPS;
7371
+ return overlap > EPS2;
7284
7372
  } else {
7285
7373
  const y = p1.y;
7286
- if (y < rect.minY - EPS || y > rect.maxY + EPS) return false;
7374
+ if (y < rect.minY - EPS2 || y > rect.maxY + EPS2) return false;
7287
7375
  const segMinX = Math.min(p1.x, p2.x);
7288
7376
  const segMaxX = Math.max(p1.x, p2.x);
7289
7377
  const overlap = Math.min(segMaxX, rect.maxX) - Math.max(segMinX, rect.minX);
7290
- return overlap > EPS;
7378
+ return overlap > EPS2;
7291
7379
  }
7292
7380
  }
7293
7381
  function rectIntersectsAnyTrace(bounds, inputTraceMap, hostPathId, hostSegIndex) {
@@ -7311,13 +7399,25 @@ function lengthOfTrace(path) {
7311
7399
  return sum;
7312
7400
  }
7313
7401
  function chooseHostTraceForGroup(params) {
7314
- const { inputProblem, inputTraceMap, globalConnNetId, fallbackTrace } = params;
7402
+ const {
7403
+ inputProblem,
7404
+ inputTraceMap,
7405
+ globalConnNetId,
7406
+ fallbackTrace,
7407
+ mspConnectionPairIds
7408
+ } = params;
7315
7409
  const chipsById = Object.fromEntries(
7316
7410
  inputProblem.chips.map((c) => [c.chipId, c])
7317
7411
  );
7318
- const groupTraces = Object.values(inputTraceMap).filter(
7412
+ let groupTraces = Object.values(inputTraceMap).filter(
7319
7413
  (t) => t.globalConnNetId === globalConnNetId
7320
7414
  );
7415
+ if (mspConnectionPairIds && mspConnectionPairIds.length > 0) {
7416
+ const idSet = new Set(mspConnectionPairIds);
7417
+ groupTraces = groupTraces.filter(
7418
+ (t) => t.mspConnectionPairIds.some((id) => idSet.has(id))
7419
+ );
7420
+ }
7321
7421
  const chipIdsInGroup = /* @__PURE__ */ new Set();
7322
7422
  for (const t of groupTraces) {
7323
7423
  chipIdsInGroup.add(t.pins[0].chipId);
@@ -7566,15 +7666,19 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
7566
7666
  inputProblem: this.inputProblem,
7567
7667
  inputTraceMap: this.inputTraceMap,
7568
7668
  globalConnNetId: groupId,
7569
- fallbackTrace: this.overlappingSameNetTraceGroup.overlappingTraces
7669
+ fallbackTrace: this.overlappingSameNetTraceGroup.overlappingTraces,
7670
+ mspConnectionPairIds: this.overlappingSameNetTraceGroup.mspConnectionPairIds
7570
7671
  });
7571
7672
  if (!host) {
7572
7673
  this.failed = true;
7573
7674
  this.error = "No host trace found for net label placement";
7574
7675
  return;
7575
7676
  }
7677
+ const traceIdSet = new Set(
7678
+ this.overlappingSameNetTraceGroup.mspConnectionPairIds ?? []
7679
+ );
7576
7680
  const tracesToScanBase = Object.values(this.inputTraceMap).filter(
7577
- (t) => t.globalConnNetId === groupId
7681
+ (t) => t.globalConnNetId === groupId && (traceIdSet.size === 0 || t.mspConnectionPairIds.some((id) => traceIdSet.has(id)))
7578
7682
  );
7579
7683
  const tracesToScan = this.availableOrientations.length === 1 ? [
7580
7684
  host,
@@ -7596,14 +7700,14 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
7596
7700
  };
7597
7701
  let bestCandidate = null;
7598
7702
  let bestScore = -Infinity;
7599
- const EPS = 1e-6;
7703
+ const EPS2 = 1e-6;
7600
7704
  for (const curr of tracesToScan) {
7601
7705
  const pts = curr.tracePath.slice();
7602
7706
  for (let si = 0; si < pts.length - 1; si++) {
7603
7707
  const a = pts[si];
7604
7708
  const b = pts[si + 1];
7605
- const isH = Math.abs(a.y - b.y) < EPS;
7606
- const isV = Math.abs(a.x - b.x) < EPS;
7709
+ const isH = Math.abs(a.y - b.y) < EPS2;
7710
+ const isV = Math.abs(a.x - b.x) < EPS2;
7607
7711
  if (!isH && !isV) continue;
7608
7712
  const segmentAllowed = isH ? ["y+", "y-"] : ["x+", "x-"];
7609
7713
  const candidateOrients = orientations.filter(
@@ -7831,10 +7935,18 @@ var NetLabelPlacementSolver = class extends BaseSolver {
7831
7935
  }
7832
7936
  }
7833
7937
  }
7938
+ const mspConnectionPairIds = Array.from(
7939
+ new Set(
7940
+ compTraces.flatMap(
7941
+ (t) => t.mspConnectionPairIds ?? [t.mspPairId]
7942
+ )
7943
+ )
7944
+ );
7834
7945
  groups.push({
7835
7946
  globalConnNetId,
7836
7947
  netId: userNetId,
7837
- overlappingTraces: rep
7948
+ overlappingTraces: rep,
7949
+ mspConnectionPairIds
7838
7950
  });
7839
7951
  } else {
7840
7952
  for (const p of component) {
@@ -19,6 +19,7 @@ export type OverlappingSameNetTraceGroup = {
19
19
  netId?: string
20
20
  overlappingTraces?: SolvedTracePath
21
21
  portOnlyPinId?: string
22
+ mspConnectionPairIds?: MspConnectionPairId[]
22
23
  }
23
24
 
24
25
  export interface NetLabelPlacement {
@@ -196,11 +197,19 @@ export class NetLabelPlacementSolver extends BaseSolver {
196
197
  }
197
198
  }
198
199
  }
200
+ const mspConnectionPairIds = Array.from(
201
+ new Set(
202
+ compTraces.flatMap(
203
+ (t) => t.mspConnectionPairIds ?? [t.mspPairId],
204
+ ),
205
+ ),
206
+ )
199
207
 
200
208
  groups.push({
201
209
  globalConnNetId,
202
210
  netId: userNetId,
203
211
  overlappingTraces: rep,
212
+ mspConnectionPairIds,
204
213
  })
205
214
  } else {
206
215
  // No traces in this component: place label at each pin that has a user net id
@@ -121,6 +121,8 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
121
121
  inputTraceMap: this.inputTraceMap,
122
122
  globalConnNetId: groupId,
123
123
  fallbackTrace: this.overlappingSameNetTraceGroup.overlappingTraces,
124
+ mspConnectionPairIds:
125
+ this.overlappingSameNetTraceGroup.mspConnectionPairIds,
124
126
  })
125
127
 
126
128
  if (!host) {
@@ -130,8 +132,14 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
130
132
  }
131
133
 
132
134
  // Ensure we traverse the host path starting at the segment attached to the largest chip's pin
135
+ const traceIdSet = new Set(
136
+ this.overlappingSameNetTraceGroup.mspConnectionPairIds ?? [],
137
+ )
133
138
  const tracesToScanBase = Object.values(this.inputTraceMap).filter(
134
- (t) => t.globalConnNetId === groupId,
139
+ (t) =>
140
+ t.globalConnNetId === groupId &&
141
+ (traceIdSet.size === 0 ||
142
+ t.mspConnectionPairIds.some((id) => traceIdSet.has(id))),
135
143
  )
136
144
  const tracesToScan =
137
145
  this.availableOrientations.length === 1
@@ -17,17 +17,29 @@ export function chooseHostTraceForGroup(params: {
17
17
  inputTraceMap: Record<MspConnectionPairId, SolvedTracePath>
18
18
  globalConnNetId: string
19
19
  fallbackTrace?: SolvedTracePath
20
+ mspConnectionPairIds?: MspConnectionPairId[]
20
21
  }): SolvedTracePath | undefined {
21
- const { inputProblem, inputTraceMap, globalConnNetId, fallbackTrace } = params
22
-
22
+ const {
23
+ inputProblem,
24
+ inputTraceMap,
25
+ globalConnNetId,
26
+ fallbackTrace,
27
+ mspConnectionPairIds,
28
+ } = params
23
29
  const chipsById: Record<string, InputChip> = Object.fromEntries(
24
30
  inputProblem.chips.map((c) => [c.chipId, c]),
25
31
  )
26
32
 
27
- const groupTraces = Object.values(inputTraceMap).filter(
33
+ let groupTraces = Object.values(inputTraceMap).filter(
28
34
  (t) => t.globalConnNetId === globalConnNetId,
29
35
  )
30
36
 
37
+ if (mspConnectionPairIds && mspConnectionPairIds.length > 0) {
38
+ const idSet = new Set(mspConnectionPairIds)
39
+ groupTraces = groupTraces.filter((t) =>
40
+ t.mspConnectionPairIds.some((id) => idSet.has(id)),
41
+ )
42
+ }
31
43
  const chipIdsInGroup = new Set<string>()
32
44
  for (const t of groupTraces) {
33
45
  chipIdsInGroup.add(t.pins[0].chipId)
@@ -24,6 +24,7 @@ export class SchematicTraceSingleLineSolver extends BaseSolver {
24
24
  movableSegments: Array<MovableSegment>
25
25
  baseElbow: Point[]
26
26
 
27
+ allCandidatePaths: Array<Point[]>
27
28
  queuedCandidatePaths: Array<Point[]>
28
29
 
29
30
  chipObstacleSpatialIndex: ChipObstacleSpatialIndex
@@ -91,7 +92,8 @@ export class SchematicTraceSingleLineSolver extends BaseSolver {
91
92
  return len
92
93
  }
93
94
 
94
- this.queuedCandidatePaths = [this.baseElbow, ...elbowVariants].sort(
95
+ this.allCandidatePaths = [this.baseElbow, ...elbowVariants]
96
+ this.queuedCandidatePaths = [...this.allCandidatePaths].sort(
95
97
  (a, b) => getPathLength(a) - getPathLength(b),
96
98
  )
97
99
  }