@tscircuit/schematic-trace-solver 0.0.113 → 0.0.115
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 +7 -8
- package/dist/index.js +101 -7
- package/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts +55 -1
- package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +13 -4
- package/lib/solvers/MspConnectionPairSolver/isLabeledPeripheralConnection.ts +44 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +9 -9
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect.ts +25 -9
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +2 -0
- package/package.json +2 -2
- package/site/bug-reports/bug-report-20260728T144234Z.page.tsx +4 -0
- package/site/bug-reports/bug-report-20260728T225606Z.page.tsx +4 -0
- package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +1 -1
- package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +108 -0
- package/tests/bug-reports/bug-report-20260728T144234Z/bug-report-20260728T144234Z.json +518 -0
- package/tests/bug-reports/bug-report-20260728T144234Z/bug-report-20260728T144234Z.test.ts +12 -0
- package/tests/bug-reports/bug-report-20260728T225606Z/__snapshots__/bug-report-20260728T225606Z.snap.svg +131 -0
- package/tests/bug-reports/bug-report-20260728T225606Z/bug-report-20260728T225606Z.json +1127 -0
- package/tests/bug-reports/bug-report-20260728T225606Z/bug-report-20260728T225606Z.test.ts +35 -0
- package/tests/repros/__snapshots__/repro-type-c-16pin-self-connection.snap.svg +54 -0
- package/tests/repros/__snapshots__/repro-type-c-16pin-to-resistor.snap.svg +56 -0
- package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +87 -0
- package/tests/repros/assets/repro48-555-timer-vcc-rail-label.input.json +276 -0
- package/tests/repros/repro-type-c-16pin-self-connection.test.ts +97 -0
- package/tests/repros/repro-type-c-16pin-to-resistor.test.ts +120 -0
- package/tests/repros/repro48-555-timer-vcc-rail-label.test.ts +23 -0
- package/tests/solvers/SchematicTraceSingleLineSolver2/text-box-obstacle-gap.test.ts +43 -0
package/dist/index.d.ts
CHANGED
|
@@ -146,17 +146,11 @@ type RectPadding = {
|
|
|
146
146
|
maxY?: number;
|
|
147
147
|
};
|
|
148
148
|
|
|
149
|
-
type
|
|
150
|
-
minX: number;
|
|
151
|
-
minY: number;
|
|
152
|
-
maxX: number;
|
|
153
|
-
maxY: number;
|
|
154
|
-
};
|
|
155
|
-
type ChipObstacleRect = RectBounds & {
|
|
149
|
+
type ChipObstacleRect = Bounds & {
|
|
156
150
|
kind: "chip";
|
|
157
151
|
chipId: string;
|
|
158
152
|
};
|
|
159
|
-
type TextBoxObstacleRect =
|
|
153
|
+
type TextBoxObstacleRect = Bounds & {
|
|
160
154
|
kind: "text_box";
|
|
161
155
|
textBox: NonNullable<InputProblem["textBoxes"]>[number];
|
|
162
156
|
};
|
|
@@ -586,6 +580,8 @@ declare class LongDistancePairSolver extends BaseSolver {
|
|
|
586
580
|
solvedLongDistanceTraces: SolvedTracePath[];
|
|
587
581
|
private queuedCandidatePairs;
|
|
588
582
|
private currentCandidatePair;
|
|
583
|
+
private queuedFailedConnectionPairs;
|
|
584
|
+
private currentFailedConnectionPair;
|
|
589
585
|
private subSolver;
|
|
590
586
|
private chipMap;
|
|
591
587
|
private inputProblem;
|
|
@@ -596,12 +592,15 @@ declare class LongDistancePairSolver extends BaseSolver {
|
|
|
596
592
|
inputProblem: InputProblem;
|
|
597
593
|
alreadySolvedTraces: SolvedTracePath[];
|
|
598
594
|
primaryMspConnectionPairs: MspConnectionPair[];
|
|
595
|
+
failedConnectionPairs: MspConnectionPair[];
|
|
599
596
|
});
|
|
600
597
|
getConstructorParams(): {
|
|
601
598
|
inputProblem: InputProblem;
|
|
602
599
|
alreadySolvedTraces: SolvedTracePath[];
|
|
603
600
|
primaryMspConnectionPairs: MspConnectionPair[];
|
|
601
|
+
failedConnectionPairs: MspConnectionPair[];
|
|
604
602
|
};
|
|
603
|
+
private acceptFailedConnectionPair;
|
|
605
604
|
_step(): void;
|
|
606
605
|
visualize(): graphics_debug.GraphicsObject;
|
|
607
606
|
getOutput(): {
|
package/dist/index.js
CHANGED
|
@@ -420,6 +420,33 @@ function getOrthogonalMinimumSpanningTree(pins, opts = {}) {
|
|
|
420
420
|
return edges;
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
+
// lib/solvers/MspConnectionPairSolver/isLabeledPeripheralConnection.ts
|
|
424
|
+
var isLabeledPeripheralConnection = ({
|
|
425
|
+
inputProblem,
|
|
426
|
+
chipMap,
|
|
427
|
+
pins
|
|
428
|
+
}) => {
|
|
429
|
+
const [firstPin, secondPin] = pins;
|
|
430
|
+
const directConnection = inputProblem.directConnections.find(
|
|
431
|
+
(connection) => connection.pinIds.includes(firstPin.pinId) && connection.pinIds.includes(secondPin.pinId)
|
|
432
|
+
);
|
|
433
|
+
if (directConnection?.netLabelWidth === void 0) return false;
|
|
434
|
+
const firstChip = chipMap[firstPin.chipId];
|
|
435
|
+
const secondChip = chipMap[secondPin.chipId];
|
|
436
|
+
if (!firstChip || !secondChip) return false;
|
|
437
|
+
const hasSinglePinPeripheral = firstChip.pins.length === 1 || secondChip.pins.length === 1;
|
|
438
|
+
if (!hasSinglePinPeripheral) return false;
|
|
439
|
+
let firstFacingDirection = firstPin._facingDirection;
|
|
440
|
+
if (!firstFacingDirection) {
|
|
441
|
+
firstFacingDirection = getPinDirection(firstPin, firstChip);
|
|
442
|
+
}
|
|
443
|
+
let secondFacingDirection = secondPin._facingDirection;
|
|
444
|
+
if (!secondFacingDirection) {
|
|
445
|
+
secondFacingDirection = getPinDirection(secondPin, secondChip);
|
|
446
|
+
}
|
|
447
|
+
return firstFacingDirection === "x-" && secondFacingDirection === "x+" || firstFacingDirection === "x+" && secondFacingDirection === "x-";
|
|
448
|
+
};
|
|
449
|
+
|
|
423
450
|
// lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts
|
|
424
451
|
var DEFAULT_MAX_MSP_PAIR_DISTANCE = 1;
|
|
425
452
|
var getPinPairKey = (pinIds) => [...pinIds].sort().join("::");
|
|
@@ -489,8 +516,16 @@ var MspConnectionPairSolver = class extends BaseSolver {
|
|
|
489
516
|
const p1 = this.pinMap[pin1];
|
|
490
517
|
const p2 = this.pinMap[pin2];
|
|
491
518
|
const pinPairKey = getPinPairKey([pin1, pin2]);
|
|
492
|
-
|
|
493
|
-
if (
|
|
519
|
+
let pairDistance = Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y);
|
|
520
|
+
if (this.directConnectionPinPairKeys.has(pinPairKey)) {
|
|
521
|
+
pairDistance = distance(p1, p2);
|
|
522
|
+
}
|
|
523
|
+
const isLabeledPeripheral = isLabeledPeripheralConnection({
|
|
524
|
+
inputProblem: this.inputProblem,
|
|
525
|
+
chipMap: this.chipMap,
|
|
526
|
+
pins: [p1, p2]
|
|
527
|
+
});
|
|
528
|
+
if (pairDistance > this.maxMspPairDistance && !isLabeledPeripheral) {
|
|
494
529
|
return;
|
|
495
530
|
}
|
|
496
531
|
if (arePinsInDifferentSchematicSections(this.inputProblem, p1, p2)) {
|
|
@@ -1081,6 +1116,9 @@ var generateInternalSegmentCollisionDetours = ({
|
|
|
1081
1116
|
]).filter(hasOnlyNonzeroOrthogonalSegments);
|
|
1082
1117
|
};
|
|
1083
1118
|
|
|
1119
|
+
// lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect.ts
|
|
1120
|
+
import { boundsDistance } from "@tscircuit/math-utils";
|
|
1121
|
+
|
|
1084
1122
|
// lib/solvers/GuidelinesSolver/getInputChipBounds.ts
|
|
1085
1123
|
function getInputChipBounds(chip) {
|
|
1086
1124
|
const halfWidth = chip.width / 2;
|
|
@@ -1094,6 +1132,7 @@ function getInputChipBounds(chip) {
|
|
|
1094
1132
|
}
|
|
1095
1133
|
|
|
1096
1134
|
// lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect.ts
|
|
1135
|
+
var MIN_ROUTABLE_OBSTACLE_GAP = 0.05;
|
|
1097
1136
|
var isTextBoxObstacle = (obstacle) => obstacle.kind === "text_box";
|
|
1098
1137
|
var chipToRect = (chip) => {
|
|
1099
1138
|
const b = getInputChipBounds(chip);
|
|
@@ -1102,7 +1141,17 @@ var chipToRect = (chip) => {
|
|
|
1102
1141
|
var getObstacleRects = (problem, opts = {}) => {
|
|
1103
1142
|
const chipRects = problem.chips.map(chipToRect);
|
|
1104
1143
|
const textBoxRects = (problem.textBoxes ?? []).map((textBox) => {
|
|
1105
|
-
const
|
|
1144
|
+
const textBounds = getTextBoxBounds(textBox, opts.textBoxPadding);
|
|
1145
|
+
const attachedChipBounds = chipRects.find(
|
|
1146
|
+
(chip) => chip.chipId === textBox.chipId
|
|
1147
|
+
);
|
|
1148
|
+
const gap = attachedChipBounds ? boundsDistance(textBounds, attachedChipBounds) : Number.POSITIVE_INFINITY;
|
|
1149
|
+
const b = attachedChipBounds && gap > 0 && gap < MIN_ROUTABLE_OBSTACLE_GAP ? {
|
|
1150
|
+
minX: Math.min(textBounds.minX, attachedChipBounds.maxX),
|
|
1151
|
+
maxX: Math.max(textBounds.maxX, attachedChipBounds.minX),
|
|
1152
|
+
minY: Math.min(textBounds.minY, attachedChipBounds.maxY),
|
|
1153
|
+
maxY: Math.max(textBounds.maxY, attachedChipBounds.minY)
|
|
1154
|
+
} : textBounds;
|
|
1106
1155
|
return {
|
|
1107
1156
|
kind: "text_box",
|
|
1108
1157
|
textBox,
|
|
@@ -1172,9 +1221,9 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
|
|
|
1172
1221
|
this.textObstacles = new Set(this.obstacles.filter(isTextBoxObstacle));
|
|
1173
1222
|
const endpointChipIds = new Set(this.pins.map((pin) => pin.chipId));
|
|
1174
1223
|
this.endpointTextObstacles = new Set(
|
|
1175
|
-
|
|
1224
|
+
this.obstacles.filter(isTextBoxObstacle).filter(
|
|
1176
1225
|
(obstacle) => obstacle.textBox.chipId !== void 0 && endpointChipIds.has(obstacle.textBox.chipId)
|
|
1177
|
-
)
|
|
1226
|
+
)
|
|
1178
1227
|
);
|
|
1179
1228
|
const [pin1, pin2] = this.pins;
|
|
1180
1229
|
const directShortPath = calculateDirectShortPath(pin1, pin2);
|
|
@@ -4495,6 +4544,13 @@ var LongDistancePairSolver = class extends BaseSolver {
|
|
|
4495
4544
|
pinMap.set(pin.pinId, { ...pin, chipId: chip.chipId });
|
|
4496
4545
|
}
|
|
4497
4546
|
}
|
|
4547
|
+
this.queuedFailedConnectionPairs = this.params.failedConnectionPairs.filter(
|
|
4548
|
+
(connectionPair) => isLabeledPeripheralConnection({
|
|
4549
|
+
inputProblem: this.inputProblem,
|
|
4550
|
+
chipMap: this.chipMap,
|
|
4551
|
+
pins: connectionPair.pins
|
|
4552
|
+
})
|
|
4553
|
+
);
|
|
4498
4554
|
const candidatePairs = [];
|
|
4499
4555
|
const addedPairKeys = /* @__PURE__ */ new Set();
|
|
4500
4556
|
for (const netId of Object.keys(netConnMap.netMap)) {
|
|
@@ -4535,6 +4591,8 @@ var LongDistancePairSolver = class extends BaseSolver {
|
|
|
4535
4591
|
solvedLongDistanceTraces = [];
|
|
4536
4592
|
queuedCandidatePairs = [];
|
|
4537
4593
|
currentCandidatePair = null;
|
|
4594
|
+
queuedFailedConnectionPairs = [];
|
|
4595
|
+
currentFailedConnectionPair = null;
|
|
4538
4596
|
subSolver = null;
|
|
4539
4597
|
chipMap = {};
|
|
4540
4598
|
inputProblem;
|
|
@@ -4544,8 +4602,31 @@ var LongDistancePairSolver = class extends BaseSolver {
|
|
|
4544
4602
|
getConstructorParams() {
|
|
4545
4603
|
return this.params;
|
|
4546
4604
|
}
|
|
4605
|
+
acceptFailedConnectionPair(tracePath) {
|
|
4606
|
+
const connectionPair = this.currentFailedConnectionPair;
|
|
4607
|
+
if (!connectionPair) return;
|
|
4608
|
+
const solvedTrace = {
|
|
4609
|
+
...connectionPair,
|
|
4610
|
+
tracePath,
|
|
4611
|
+
mspConnectionPairIds: [connectionPair.mspPairId],
|
|
4612
|
+
pinIds: connectionPair.pins.map((pin) => pin.pinId)
|
|
4613
|
+
};
|
|
4614
|
+
this.solvedLongDistanceTraces.push(solvedTrace);
|
|
4615
|
+
this.allSolvedTraces.push(solvedTrace);
|
|
4616
|
+
for (const pin of connectionPair.pins) {
|
|
4617
|
+
this.newlyConnectedPinIds.add(pin.pinId);
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4547
4620
|
_step() {
|
|
4548
|
-
if (this.subSolver?.solved) {
|
|
4621
|
+
if (this.subSolver?.solved && this.currentFailedConnectionPair) {
|
|
4622
|
+
const tracePath = this.subSolver.solvedTracePath;
|
|
4623
|
+
if (tracePath) {
|
|
4624
|
+
this.acceptFailedConnectionPair(tracePath);
|
|
4625
|
+
}
|
|
4626
|
+
this.subSolver = null;
|
|
4627
|
+
this.currentCandidatePair = null;
|
|
4628
|
+
this.currentFailedConnectionPair = null;
|
|
4629
|
+
} else if (this.subSolver?.solved) {
|
|
4549
4630
|
const newTracePath = this.subSolver.solvedTracePath;
|
|
4550
4631
|
if (newTracePath && this.currentCandidatePair) {
|
|
4551
4632
|
const isTraceClear = !doesTraceOverlapWithExistingTraces(
|
|
@@ -4576,11 +4657,23 @@ var LongDistancePairSolver = class extends BaseSolver {
|
|
|
4576
4657
|
} else if (this.subSolver?.failed) {
|
|
4577
4658
|
this.subSolver = null;
|
|
4578
4659
|
this.currentCandidatePair = null;
|
|
4660
|
+
this.currentFailedConnectionPair = null;
|
|
4579
4661
|
}
|
|
4580
4662
|
if (this.subSolver) {
|
|
4581
4663
|
this.subSolver.step();
|
|
4582
4664
|
return;
|
|
4583
4665
|
}
|
|
4666
|
+
const failedConnectionPair = this.queuedFailedConnectionPairs.shift();
|
|
4667
|
+
if (failedConnectionPair) {
|
|
4668
|
+
this.currentFailedConnectionPair = failedConnectionPair;
|
|
4669
|
+
this.currentCandidatePair = failedConnectionPair.pins;
|
|
4670
|
+
this.subSolver = new SchematicTraceSingleLineSolver2({
|
|
4671
|
+
inputProblem: this.params.inputProblem,
|
|
4672
|
+
pins: failedConnectionPair.pins,
|
|
4673
|
+
chipMap: this.chipMap
|
|
4674
|
+
});
|
|
4675
|
+
return;
|
|
4676
|
+
}
|
|
4584
4677
|
while (this.queuedCandidatePairs.length > 0) {
|
|
4585
4678
|
const nextPair = this.queuedCandidatePairs.shift();
|
|
4586
4679
|
const [p1, p2] = nextPair;
|
|
@@ -10703,7 +10796,8 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
10703
10796
|
{
|
|
10704
10797
|
inputProblem: instance.inputProblem,
|
|
10705
10798
|
primaryMspConnectionPairs: instance.mspConnectionPairSolver.mspConnectionPairs,
|
|
10706
|
-
alreadySolvedTraces: instance.schematicTraceLinesSolver.solvedTracePaths
|
|
10799
|
+
alreadySolvedTraces: instance.schematicTraceLinesSolver.solvedTracePaths,
|
|
10800
|
+
failedConnectionPairs: instance.schematicTraceLinesSolver.failedConnectionPairs
|
|
10707
10801
|
}
|
|
10708
10802
|
],
|
|
10709
10803
|
{
|
|
@@ -13,6 +13,7 @@ import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualize
|
|
|
13
13
|
import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
14
14
|
import type { ConnectivityMap } from "connectivity-map"
|
|
15
15
|
import { arePinsInDifferentSchematicSections } from "../../utils/arePinsInDifferentSchematicSections"
|
|
16
|
+
import { isLabeledPeripheralConnection } from "../MspConnectionPairSolver/isLabeledPeripheralConnection"
|
|
16
17
|
|
|
17
18
|
const NEAREST_NEIGHBOR_COUNT = 3
|
|
18
19
|
|
|
@@ -28,6 +29,8 @@ export class LongDistancePairSolver extends BaseSolver {
|
|
|
28
29
|
private currentCandidatePair:
|
|
29
30
|
| [InputPin & { chipId: string }, InputPin & { chipId: string }]
|
|
30
31
|
| null = null
|
|
32
|
+
private queuedFailedConnectionPairs: MspConnectionPair[] = []
|
|
33
|
+
private currentFailedConnectionPair: MspConnectionPair | null = null
|
|
31
34
|
private subSolver: SchematicTraceSingleLineSolver2 | null = null
|
|
32
35
|
private chipMap: Record<string, InputChip> = {}
|
|
33
36
|
private inputProblem: InputProblem
|
|
@@ -40,6 +43,7 @@ export class LongDistancePairSolver extends BaseSolver {
|
|
|
40
43
|
inputProblem: InputProblem
|
|
41
44
|
alreadySolvedTraces: SolvedTracePath[]
|
|
42
45
|
primaryMspConnectionPairs: MspConnectionPair[]
|
|
46
|
+
failedConnectionPairs: MspConnectionPair[]
|
|
43
47
|
},
|
|
44
48
|
) {
|
|
45
49
|
super()
|
|
@@ -66,6 +70,16 @@ export class LongDistancePairSolver extends BaseSolver {
|
|
|
66
70
|
pinMap.set(pin.pinId, { ...pin, chipId: chip.chipId })
|
|
67
71
|
}
|
|
68
72
|
}
|
|
73
|
+
// Retry failed MSP pairs with their existing identity before creating
|
|
74
|
+
// new nearest-neighbor candidates.
|
|
75
|
+
this.queuedFailedConnectionPairs = this.params.failedConnectionPairs.filter(
|
|
76
|
+
(connectionPair) =>
|
|
77
|
+
isLabeledPeripheralConnection({
|
|
78
|
+
inputProblem: this.inputProblem,
|
|
79
|
+
chipMap: this.chipMap,
|
|
80
|
+
pins: connectionPair.pins,
|
|
81
|
+
}),
|
|
82
|
+
)
|
|
69
83
|
|
|
70
84
|
// 2. Generate candidate pairs using N-Nearest-Neighbors approach
|
|
71
85
|
const candidatePairs: Array<
|
|
@@ -129,9 +143,36 @@ export class LongDistancePairSolver extends BaseSolver {
|
|
|
129
143
|
return this.params
|
|
130
144
|
}
|
|
131
145
|
|
|
146
|
+
private acceptFailedConnectionPair(tracePath: SolvedTracePath["tracePath"]) {
|
|
147
|
+
const connectionPair = this.currentFailedConnectionPair
|
|
148
|
+
if (!connectionPair) return
|
|
149
|
+
|
|
150
|
+
// Reuse the original pair so downstream solvers retain its connectivity
|
|
151
|
+
// IDs, user net ID, pins, and MSP pair ID.
|
|
152
|
+
const solvedTrace: SolvedTracePath = {
|
|
153
|
+
...connectionPair,
|
|
154
|
+
tracePath,
|
|
155
|
+
mspConnectionPairIds: [connectionPair.mspPairId],
|
|
156
|
+
pinIds: connectionPair.pins.map((pin) => pin.pinId),
|
|
157
|
+
}
|
|
158
|
+
this.solvedLongDistanceTraces.push(solvedTrace)
|
|
159
|
+
this.allSolvedTraces.push(solvedTrace)
|
|
160
|
+
for (const pin of connectionPair.pins) {
|
|
161
|
+
this.newlyConnectedPinIds.add(pin.pinId)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
132
165
|
override _step() {
|
|
133
166
|
// 1. Check if a sub-solver has finished and process its result
|
|
134
|
-
if (this.subSolver?.solved) {
|
|
167
|
+
if (this.subSolver?.solved && this.currentFailedConnectionPair) {
|
|
168
|
+
const tracePath = this.subSolver.solvedTracePath
|
|
169
|
+
if (tracePath) {
|
|
170
|
+
this.acceptFailedConnectionPair(tracePath)
|
|
171
|
+
}
|
|
172
|
+
this.subSolver = null
|
|
173
|
+
this.currentCandidatePair = null
|
|
174
|
+
this.currentFailedConnectionPair = null
|
|
175
|
+
} else if (this.subSolver?.solved) {
|
|
135
176
|
const newTracePath = this.subSolver.solvedTracePath
|
|
136
177
|
if (newTracePath && this.currentCandidatePair) {
|
|
137
178
|
const isTraceClear = !doesTraceOverlapWithExistingTraces(
|
|
@@ -166,6 +207,7 @@ export class LongDistancePairSolver extends BaseSolver {
|
|
|
166
207
|
} else if (this.subSolver?.failed) {
|
|
167
208
|
this.subSolver = null
|
|
168
209
|
this.currentCandidatePair = null
|
|
210
|
+
this.currentFailedConnectionPair = null
|
|
169
211
|
}
|
|
170
212
|
|
|
171
213
|
// 2. If a sub-solver is already running, let it continue
|
|
@@ -174,6 +216,18 @@ export class LongDistancePairSolver extends BaseSolver {
|
|
|
174
216
|
return
|
|
175
217
|
}
|
|
176
218
|
|
|
219
|
+
const failedConnectionPair = this.queuedFailedConnectionPairs.shift()
|
|
220
|
+
if (failedConnectionPair) {
|
|
221
|
+
this.currentFailedConnectionPair = failedConnectionPair
|
|
222
|
+
this.currentCandidatePair = failedConnectionPair.pins
|
|
223
|
+
this.subSolver = new SchematicTraceSingleLineSolver2({
|
|
224
|
+
inputProblem: this.params.inputProblem,
|
|
225
|
+
pins: failedConnectionPair.pins,
|
|
226
|
+
chipMap: this.chipMap,
|
|
227
|
+
})
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
|
|
177
231
|
// 3. Find the next valid candidate pair and start a new sub-solver
|
|
178
232
|
while (this.queuedCandidatePairs.length > 0) {
|
|
179
233
|
const nextPair = this.queuedCandidatePairs.shift()!
|
|
@@ -14,6 +14,7 @@ import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualize
|
|
|
14
14
|
import { doesPairCrossRestrictedCenterLines } from "./doesPairCrossRestrictedCenterLines"
|
|
15
15
|
import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInputProblem"
|
|
16
16
|
import { getOrthogonalMinimumSpanningTree } from "./getMspConnectionPairsFromPins"
|
|
17
|
+
import { isLabeledPeripheralConnection } from "./isLabeledPeripheralConnection"
|
|
17
18
|
|
|
18
19
|
export type MspConnectionPairId = string
|
|
19
20
|
export const DEFAULT_MAX_MSP_PAIR_DISTANCE = 1
|
|
@@ -116,10 +117,18 @@ export class MspConnectionPairSolver extends BaseSolver {
|
|
|
116
117
|
const pinPairKey = getPinPairKey([pin1!, pin2!])
|
|
117
118
|
// Explicit source traces are classified by straight-line distance when
|
|
118
119
|
// their input is created; named nets retain the orthogonal route metric.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
let pairDistance = Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y)
|
|
121
|
+
if (this.directConnectionPinPairKeys.has(pinPairKey)) {
|
|
122
|
+
pairDistance = distance(p1, p2)
|
|
123
|
+
}
|
|
124
|
+
// Labeled one-pin peripherals need a real trace even when they are far
|
|
125
|
+
// apart; skipping the MSP pair would leave only the fallback path.
|
|
126
|
+
const isLabeledPeripheral = isLabeledPeripheralConnection({
|
|
127
|
+
inputProblem: this.inputProblem,
|
|
128
|
+
chipMap: this.chipMap,
|
|
129
|
+
pins: [p1, p2],
|
|
130
|
+
})
|
|
131
|
+
if (pairDistance > this.maxMspPairDistance && !isLabeledPeripheral) {
|
|
123
132
|
// Too far apart; skip creating an MSP pair for this net
|
|
124
133
|
return
|
|
125
134
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { InputChip, InputPin, InputProblem } from "lib/types/InputProblem"
|
|
2
|
+
import { getPinDirection } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection"
|
|
3
|
+
|
|
4
|
+
type PinWithChipId = InputPin & { chipId: string }
|
|
5
|
+
|
|
6
|
+
export const isLabeledPeripheralConnection = ({
|
|
7
|
+
inputProblem,
|
|
8
|
+
chipMap,
|
|
9
|
+
pins,
|
|
10
|
+
}: {
|
|
11
|
+
inputProblem: InputProblem
|
|
12
|
+
chipMap: Record<string, InputChip>
|
|
13
|
+
pins: [PinWithChipId, PinWithChipId]
|
|
14
|
+
}): boolean => {
|
|
15
|
+
const [firstPin, secondPin] = pins
|
|
16
|
+
const directConnection = inputProblem.directConnections.find(
|
|
17
|
+
(connection) =>
|
|
18
|
+
connection.pinIds.includes(firstPin.pinId) &&
|
|
19
|
+
connection.pinIds.includes(secondPin.pinId),
|
|
20
|
+
)
|
|
21
|
+
if (directConnection?.netLabelWidth === undefined) return false
|
|
22
|
+
|
|
23
|
+
const firstChip = chipMap[firstPin.chipId]
|
|
24
|
+
const secondChip = chipMap[secondPin.chipId]
|
|
25
|
+
if (!firstChip || !secondChip) return false
|
|
26
|
+
|
|
27
|
+
const hasSinglePinPeripheral =
|
|
28
|
+
firstChip.pins.length === 1 || secondChip.pins.length === 1
|
|
29
|
+
if (!hasSinglePinPeripheral) return false
|
|
30
|
+
|
|
31
|
+
let firstFacingDirection = firstPin._facingDirection
|
|
32
|
+
if (!firstFacingDirection) {
|
|
33
|
+
firstFacingDirection = getPinDirection(firstPin, firstChip)
|
|
34
|
+
}
|
|
35
|
+
let secondFacingDirection = secondPin._facingDirection
|
|
36
|
+
if (!secondFacingDirection) {
|
|
37
|
+
secondFacingDirection = getPinDirection(secondPin, secondChip)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
(firstFacingDirection === "x-" && secondFacingDirection === "x+") ||
|
|
42
|
+
(firstFacingDirection === "x+" && secondFacingDirection === "x-")
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -123,16 +123,16 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
|
|
|
123
123
|
})
|
|
124
124
|
this.textObstacles = new Set(this.obstacles.filter(isTextBoxObstacle))
|
|
125
125
|
const endpointChipIds = new Set(this.pins.map((pin) => pin.chipId))
|
|
126
|
+
// Same-chip routes also need these obstacles so they can escape around
|
|
127
|
+
// attached text after a narrow chip-to-text channel is closed.
|
|
126
128
|
this.endpointTextObstacles = new Set(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
)
|
|
135
|
-
: [],
|
|
129
|
+
this.obstacles
|
|
130
|
+
.filter(isTextBoxObstacle)
|
|
131
|
+
.filter(
|
|
132
|
+
(obstacle) =>
|
|
133
|
+
obstacle.textBox.chipId !== undefined &&
|
|
134
|
+
endpointChipIds.has(obstacle.textBox.chipId),
|
|
135
|
+
),
|
|
136
136
|
)
|
|
137
137
|
|
|
138
138
|
const [pin1, pin2] = this.pins
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
+
import { boundsDistance, type Bounds } from "@tscircuit/math-utils"
|
|
1
2
|
import { getInputChipBounds } from "lib/solvers/GuidelinesSolver/getInputChipBounds"
|
|
2
3
|
import type { InputChip, InputProblem } from "lib/types/InputProblem"
|
|
3
4
|
import { getTextBoxBounds, type RectPadding } from "lib/utils/textBoxBounds"
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
6
|
+
// Component text is normally placed 0.04 units from its chip. A trace
|
|
7
|
+
// centerline can fit mathematically, but its rendered stroke cannot usefully
|
|
8
|
+
// occupy that channel.
|
|
9
|
+
const MIN_ROUTABLE_OBSTACLE_GAP = 0.05
|
|
10
|
+
|
|
11
|
+
export type { Bounds as RectBounds } from "@tscircuit/math-utils"
|
|
11
12
|
|
|
12
|
-
export type ChipObstacleRect =
|
|
13
|
+
export type ChipObstacleRect = Bounds & {
|
|
13
14
|
kind: "chip"
|
|
14
15
|
chipId: string
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
export type TextBoxObstacleRect =
|
|
18
|
+
export type TextBoxObstacleRect = Bounds & {
|
|
18
19
|
kind: "text_box"
|
|
19
20
|
textBox: NonNullable<InputProblem["textBoxes"]>[number]
|
|
20
21
|
}
|
|
@@ -36,7 +37,22 @@ export const getObstacleRects = (
|
|
|
36
37
|
): ObstacleRect[] => {
|
|
37
38
|
const chipRects = problem.chips.map(chipToRect)
|
|
38
39
|
const textBoxRects = (problem.textBoxes ?? []).map((textBox) => {
|
|
39
|
-
const
|
|
40
|
+
const textBounds = getTextBoxBounds(textBox, opts.textBoxPadding)
|
|
41
|
+
const attachedChipBounds = chipRects.find(
|
|
42
|
+
(chip) => chip.chipId === textBox.chipId,
|
|
43
|
+
)
|
|
44
|
+
const gap = attachedChipBounds
|
|
45
|
+
? boundsDistance(textBounds, attachedChipBounds)
|
|
46
|
+
: Number.POSITIVE_INFINITY
|
|
47
|
+
const b =
|
|
48
|
+
attachedChipBounds && gap > 0 && gap < MIN_ROUTABLE_OBSTACLE_GAP
|
|
49
|
+
? {
|
|
50
|
+
minX: Math.min(textBounds.minX, attachedChipBounds.maxX),
|
|
51
|
+
maxX: Math.max(textBounds.maxX, attachedChipBounds.minX),
|
|
52
|
+
minY: Math.min(textBounds.minY, attachedChipBounds.maxY),
|
|
53
|
+
maxY: Math.max(textBounds.maxY, attachedChipBounds.minY),
|
|
54
|
+
}
|
|
55
|
+
: textBounds
|
|
40
56
|
return {
|
|
41
57
|
kind: "text_box" as const,
|
|
42
58
|
textBox,
|
|
@@ -140,6 +140,8 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
140
140
|
instance.mspConnectionPairSolver!.mspConnectionPairs,
|
|
141
141
|
alreadySolvedTraces:
|
|
142
142
|
instance.schematicTraceLinesSolver!.solvedTracePaths,
|
|
143
|
+
failedConnectionPairs:
|
|
144
|
+
instance.schematicTraceLinesSolver!.failedConnectionPairs,
|
|
143
145
|
},
|
|
144
146
|
],
|
|
145
147
|
{
|
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.
|
|
4
|
+
"version": "0.0.115",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"start": "cosmos",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"@biomejs/biome": "^2.2.2",
|
|
15
15
|
"@react-hook/resize-observer": "^2.0.2",
|
|
16
|
-
"@tscircuit/math-utils": "^0.0.
|
|
16
|
+
"@tscircuit/math-utils": "^0.0.36",
|
|
17
17
|
"@types/bun": "^1.2.21",
|
|
18
18
|
"bun-match-svg": "^0.0.13",
|
|
19
19
|
"calculate-elbow": "^0.0.12",
|