@tscircuit/schematic-trace-solver 0.0.199 → 0.0.201
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.js +92 -5
- package/lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts +6 -2
- package/lib/solvers/SameNetJunctionAlignmentSolver/shortenSameSideRailToLabelAnchor.ts +62 -0
- package/lib/solvers/UnroutedTraceRecoverySolver/UnroutedTraceRecoverySolver.ts +69 -0
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260916T054012Z/__snapshots__/bug-report-20260916T054012Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260916T054012Z/bug-report-20260916T054012Z.test.ts +5 -1
- package/tests/repros/__snapshots__/repro-acoustic-tuner-parallel-vref-traces.snap.svg +585 -0
- package/tests/repros/assets/repro-acoustic-tuner-parallel-vref-traces.input.json +1484 -0
- package/tests/repros/repro-acoustic-tuner-parallel-vref-traces.test.ts +50 -0
- package/tests/solvers/UnroutedTraceRecoverySolver/skip-connected-pair.test.ts +80 -0
package/dist/index.js
CHANGED
|
@@ -13243,6 +13243,52 @@ var getUnconnectedPins = ({
|
|
|
13243
13243
|
);
|
|
13244
13244
|
return connectionPair.pins.filter((pin) => !connectedPinIds.has(pin.pinId));
|
|
13245
13245
|
};
|
|
13246
|
+
var pathsIntersect = (firstPath, secondPath) => {
|
|
13247
|
+
for (let firstIndex = 0; firstIndex < firstPath.length - 1; firstIndex++) {
|
|
13248
|
+
for (let secondIndex = 0; secondIndex < secondPath.length - 1; secondIndex++) {
|
|
13249
|
+
if (doSegmentsIntersect3(
|
|
13250
|
+
firstPath[firstIndex],
|
|
13251
|
+
firstPath[firstIndex + 1],
|
|
13252
|
+
secondPath[secondIndex],
|
|
13253
|
+
secondPath[secondIndex + 1]
|
|
13254
|
+
)) {
|
|
13255
|
+
return true;
|
|
13256
|
+
}
|
|
13257
|
+
}
|
|
13258
|
+
}
|
|
13259
|
+
return false;
|
|
13260
|
+
};
|
|
13261
|
+
var connectionPairPinsAreAlreadyConnected = ({
|
|
13262
|
+
connectionPair,
|
|
13263
|
+
sameNetTraces
|
|
13264
|
+
}) => {
|
|
13265
|
+
const [firstPin, secondPin] = connectionPair.pins;
|
|
13266
|
+
const connectedTraceIndexes = new Set(
|
|
13267
|
+
sameNetTraces.flatMap(
|
|
13268
|
+
(trace, index) => trace.pinIds.includes(firstPin.pinId) ? [index] : []
|
|
13269
|
+
)
|
|
13270
|
+
);
|
|
13271
|
+
let previousSize = -1;
|
|
13272
|
+
while (connectedTraceIndexes.size !== previousSize) {
|
|
13273
|
+
previousSize = connectedTraceIndexes.size;
|
|
13274
|
+
for (let traceIndex = 0; traceIndex < sameNetTraces.length; traceIndex++) {
|
|
13275
|
+
if (connectedTraceIndexes.has(traceIndex)) continue;
|
|
13276
|
+
const trace = sameNetTraces[traceIndex];
|
|
13277
|
+
const connectsToComponent = [...connectedTraceIndexes].some(
|
|
13278
|
+
(connectedTraceIndex) => {
|
|
13279
|
+
const connectedTrace = sameNetTraces[connectedTraceIndex];
|
|
13280
|
+
return trace.pinIds.some(
|
|
13281
|
+
(pinId) => connectedTrace.pinIds.includes(pinId)
|
|
13282
|
+
) || pathsIntersect(trace.tracePath, connectedTrace.tracePath);
|
|
13283
|
+
}
|
|
13284
|
+
);
|
|
13285
|
+
if (connectsToComponent) connectedTraceIndexes.add(traceIndex);
|
|
13286
|
+
}
|
|
13287
|
+
}
|
|
13288
|
+
return [...connectedTraceIndexes].some(
|
|
13289
|
+
(traceIndex) => sameNetTraces[traceIndex].pinIds.includes(secondPin.pinId)
|
|
13290
|
+
);
|
|
13291
|
+
};
|
|
13246
13292
|
var getJunctionCandidates = ({
|
|
13247
13293
|
connectionPair,
|
|
13248
13294
|
sameNetTraces,
|
|
@@ -13507,6 +13553,12 @@ var UnroutedTraceRecoverySolver = class extends BaseSolver {
|
|
|
13507
13553
|
const sameNetTraces = existingTraces.filter(
|
|
13508
13554
|
(trace) => trace.globalConnNetId === connectionPair.globalConnNetId
|
|
13509
13555
|
);
|
|
13556
|
+
if (connectionPairPinsAreAlreadyConnected({
|
|
13557
|
+
connectionPair,
|
|
13558
|
+
sameNetTraces
|
|
13559
|
+
})) {
|
|
13560
|
+
return;
|
|
13561
|
+
}
|
|
13510
13562
|
const junctionCandidates = getJunctionCandidates({
|
|
13511
13563
|
connectionPair,
|
|
13512
13564
|
sameNetTraces,
|
|
@@ -14519,7 +14571,7 @@ var getAlignedAttachedLabelConnectorPath = ({
|
|
|
14519
14571
|
const reverseCandidate = findCandidate([...forwardPath].reverse());
|
|
14520
14572
|
return reverseCandidate ? reverseCandidate.reverse() : null;
|
|
14521
14573
|
};
|
|
14522
|
-
var
|
|
14574
|
+
var pathsIntersect2 = (firstPath, secondPath) => {
|
|
14523
14575
|
if (firstPath.some((point) => tracePathContainsPoint(secondPath, point)) || secondPath.some((point) => tracePathContainsPoint(firstPath, point))) {
|
|
14524
14576
|
return true;
|
|
14525
14577
|
}
|
|
@@ -14546,7 +14598,7 @@ var preserveAttachedTraceJunctions = ({
|
|
|
14546
14598
|
(trace) => trace.mspPairId === originalTrace.mspPairId ? candidateTrace : trace
|
|
14547
14599
|
);
|
|
14548
14600
|
for (const connector of traces) {
|
|
14549
|
-
if (connector.mspPairId === originalTrace.mspPairId || connector.globalConnNetId !== originalTrace.globalConnNetId || !
|
|
14601
|
+
if (connector.mspPairId === originalTrace.mspPairId || connector.globalConnNetId !== originalTrace.globalConnNetId || !pathsIntersect2(originalTrace.tracePath, connector.tracePath) || pathsIntersect2(candidateTrace.tracePath, connector.tracePath)) {
|
|
14550
14602
|
continue;
|
|
14551
14603
|
}
|
|
14552
14604
|
const endpointIndex = [0, connector.tracePath.length - 1].find(
|
|
@@ -14579,7 +14631,7 @@ var preserveAttachedTraceJunctions = ({
|
|
|
14579
14631
|
(point, index) => index === endpointIndex ? movedJunction : point
|
|
14580
14632
|
)
|
|
14581
14633
|
};
|
|
14582
|
-
if (!
|
|
14634
|
+
if (!pathsIntersect2(candidateTrace.tracePath, movedConnector.tracePath)) {
|
|
14583
14635
|
return null;
|
|
14584
14636
|
}
|
|
14585
14637
|
outputTraces = outputTraces.map(
|
|
@@ -15107,6 +15159,38 @@ var placeGroundRailLabelsAtOuterEnd = ({
|
|
|
15107
15159
|
return output;
|
|
15108
15160
|
};
|
|
15109
15161
|
|
|
15162
|
+
// lib/solvers/SameNetJunctionAlignmentSolver/shortenSameSideRailToLabelAnchor.ts
|
|
15163
|
+
var MAX_LOCAL_RAIL_SHIFT = 0.5;
|
|
15164
|
+
var MIN_ESCAPE_LENGTH = 1;
|
|
15165
|
+
var shortenSameSideRailToLabelAnchor = ({
|
|
15166
|
+
traces,
|
|
15167
|
+
netLabelPlacements
|
|
15168
|
+
}) => traces.map((trace) => {
|
|
15169
|
+
const path = trace.tracePath;
|
|
15170
|
+
const firstPin = path[0];
|
|
15171
|
+
const secondPin = path.at(-1);
|
|
15172
|
+
if (!firstPin || !secondPin || !nearlyEqual(firstPin.x, secondPin.x)) {
|
|
15173
|
+
return trace;
|
|
15174
|
+
}
|
|
15175
|
+
const railIndex = path.findIndex(
|
|
15176
|
+
(point, index) => index > 0 && index < path.length - 2 && isHorizontal2(path[index - 1], point) && isVertical2(point, path[index + 1]) && isHorizontal2(path[index + 1], path[index + 2])
|
|
15177
|
+
);
|
|
15178
|
+
if (railIndex < 0) return trace;
|
|
15179
|
+
const firstRail = path[railIndex];
|
|
15180
|
+
const secondRail = path[railIndex + 1];
|
|
15181
|
+
const escapeLength = Math.abs(firstRail.x - firstPin.x);
|
|
15182
|
+
const label = netLabelPlacements.find(
|
|
15183
|
+
({ anchorPoint, orientation, mspConnectionPairIds }) => mspConnectionPairIds.includes(trace.mspPairId) && (orientation === "y-" && anchorPoint.y < Math.min(firstPin.y, secondPin.y) || orientation === "y+" && anchorPoint.y > Math.max(firstPin.y, secondPin.y)) && anchorPoint.x >= Math.min(firstPin.x, firstRail.x) && anchorPoint.x <= Math.max(firstPin.x, firstRail.x) && Math.abs(anchorPoint.x - firstPin.x) < escapeLength && Math.abs(anchorPoint.x - firstRail.x) <= MAX_LOCAL_RAIL_SHIFT && escapeLength >= MIN_ESCAPE_LENGTH
|
|
15184
|
+
);
|
|
15185
|
+
if (!label) return trace;
|
|
15186
|
+
return {
|
|
15187
|
+
...trace,
|
|
15188
|
+
tracePath: path.map(
|
|
15189
|
+
(point, index) => index === railIndex || index === railIndex + 1 ? { ...point, x: label.anchorPoint.x } : point
|
|
15190
|
+
)
|
|
15191
|
+
};
|
|
15192
|
+
});
|
|
15193
|
+
|
|
15110
15194
|
// lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts
|
|
15111
15195
|
var SameNetJunctionAlignmentSolver = class extends BaseSolver {
|
|
15112
15196
|
input;
|
|
@@ -15120,10 +15204,13 @@ var SameNetJunctionAlignmentSolver = class extends BaseSolver {
|
|
|
15120
15204
|
}
|
|
15121
15205
|
_step() {
|
|
15122
15206
|
const alignment = alignSameNetJunctions(this.input);
|
|
15123
|
-
this.outputTraces =
|
|
15207
|
+
this.outputTraces = shortenSameSideRailToLabelAnchor({
|
|
15208
|
+
traces: alignment.traces,
|
|
15209
|
+
netLabelPlacements: alignment.netLabelPlacements
|
|
15210
|
+
});
|
|
15124
15211
|
this.outputNetLabelPlacements = placeGroundRailLabelsAtOuterEnd({
|
|
15125
15212
|
inputProblem: this.input.inputProblem,
|
|
15126
|
-
traces:
|
|
15213
|
+
traces: this.outputTraces,
|
|
15127
15214
|
netLabelPlacements: alignment.netLabelPlacements
|
|
15128
15215
|
});
|
|
15129
15216
|
this.stats.alignedJunctionCount = alignment.alignedJunctionCount;
|
|
@@ -8,6 +8,7 @@ import type { InputProblem } from "lib/types/InputProblem"
|
|
|
8
8
|
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
9
9
|
import { alignSameNetJunctions } from "./alignSameNetJunctions"
|
|
10
10
|
import { placeGroundRailLabelsAtOuterEnd } from "./placeGroundRailLabelsAtOuterEnd"
|
|
11
|
+
import { shortenSameSideRailToLabelAnchor } from "./shortenSameSideRailToLabelAnchor"
|
|
11
12
|
|
|
12
13
|
interface SameNetJunctionAlignmentSolverInput {
|
|
13
14
|
inputProblem: InputProblem
|
|
@@ -31,10 +32,13 @@ export class SameNetJunctionAlignmentSolver extends BaseSolver {
|
|
|
31
32
|
|
|
32
33
|
override _step() {
|
|
33
34
|
const alignment = alignSameNetJunctions(this.input)
|
|
34
|
-
this.outputTraces =
|
|
35
|
+
this.outputTraces = shortenSameSideRailToLabelAnchor({
|
|
36
|
+
traces: alignment.traces,
|
|
37
|
+
netLabelPlacements: alignment.netLabelPlacements,
|
|
38
|
+
})
|
|
35
39
|
this.outputNetLabelPlacements = placeGroundRailLabelsAtOuterEnd({
|
|
36
40
|
inputProblem: this.input.inputProblem,
|
|
37
|
-
traces:
|
|
41
|
+
traces: this.outputTraces,
|
|
38
42
|
netLabelPlacements: alignment.netLabelPlacements,
|
|
39
43
|
})
|
|
40
44
|
this.stats.alignedJunctionCount = alignment.alignedJunctionCount
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
2
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
3
|
+
import {
|
|
4
|
+
isHorizontal,
|
|
5
|
+
isVertical,
|
|
6
|
+
nearlyEqual,
|
|
7
|
+
} from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
|
|
8
|
+
|
|
9
|
+
const MAX_LOCAL_RAIL_SHIFT = 0.5
|
|
10
|
+
const MIN_ESCAPE_LENGTH = 1
|
|
11
|
+
|
|
12
|
+
export const shortenSameSideRailToLabelAnchor = ({
|
|
13
|
+
traces,
|
|
14
|
+
netLabelPlacements,
|
|
15
|
+
}: {
|
|
16
|
+
traces: SolvedTracePath[]
|
|
17
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
18
|
+
}) =>
|
|
19
|
+
traces.map((trace) => {
|
|
20
|
+
const path = trace.tracePath
|
|
21
|
+
const firstPin = path[0]
|
|
22
|
+
const secondPin = path.at(-1)
|
|
23
|
+
if (!firstPin || !secondPin || !nearlyEqual(firstPin.x, secondPin.x)) {
|
|
24
|
+
return trace
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const railIndex = path.findIndex(
|
|
28
|
+
(point, index) =>
|
|
29
|
+
index > 0 &&
|
|
30
|
+
index < path.length - 2 &&
|
|
31
|
+
isHorizontal(path[index - 1]!, point) &&
|
|
32
|
+
isVertical(point, path[index + 1]!) &&
|
|
33
|
+
isHorizontal(path[index + 1]!, path[index + 2]!),
|
|
34
|
+
)
|
|
35
|
+
if (railIndex < 0) return trace
|
|
36
|
+
const firstRail = path[railIndex]!
|
|
37
|
+
const secondRail = path[railIndex + 1]!
|
|
38
|
+
const escapeLength = Math.abs(firstRail.x - firstPin.x)
|
|
39
|
+
const label = netLabelPlacements.find(
|
|
40
|
+
({ anchorPoint, orientation, mspConnectionPairIds }) =>
|
|
41
|
+
mspConnectionPairIds.includes(trace.mspPairId) &&
|
|
42
|
+
((orientation === "y-" &&
|
|
43
|
+
anchorPoint.y < Math.min(firstPin.y, secondPin.y)) ||
|
|
44
|
+
(orientation === "y+" &&
|
|
45
|
+
anchorPoint.y > Math.max(firstPin.y, secondPin.y))) &&
|
|
46
|
+
anchorPoint.x >= Math.min(firstPin.x, firstRail.x) &&
|
|
47
|
+
anchorPoint.x <= Math.max(firstPin.x, firstRail.x) &&
|
|
48
|
+
Math.abs(anchorPoint.x - firstPin.x) < escapeLength &&
|
|
49
|
+
Math.abs(anchorPoint.x - firstRail.x) <= MAX_LOCAL_RAIL_SHIFT &&
|
|
50
|
+
escapeLength >= MIN_ESCAPE_LENGTH,
|
|
51
|
+
)
|
|
52
|
+
if (!label) return trace
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
...trace,
|
|
56
|
+
tracePath: path.map((point, index) =>
|
|
57
|
+
index === railIndex || index === railIndex + 1
|
|
58
|
+
? { ...point, x: label.anchorPoint.x }
|
|
59
|
+
: point,
|
|
60
|
+
),
|
|
61
|
+
}
|
|
62
|
+
})
|
|
@@ -204,6 +204,67 @@ const getUnconnectedPins = ({
|
|
|
204
204
|
return connectionPair.pins.filter((pin) => !connectedPinIds.has(pin.pinId))
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
const pathsIntersect = (firstPath: Point[], secondPath: Point[]): boolean => {
|
|
208
|
+
for (let firstIndex = 0; firstIndex < firstPath.length - 1; firstIndex++) {
|
|
209
|
+
for (
|
|
210
|
+
let secondIndex = 0;
|
|
211
|
+
secondIndex < secondPath.length - 1;
|
|
212
|
+
secondIndex++
|
|
213
|
+
) {
|
|
214
|
+
if (
|
|
215
|
+
doSegmentsIntersect(
|
|
216
|
+
firstPath[firstIndex]!,
|
|
217
|
+
firstPath[firstIndex + 1]!,
|
|
218
|
+
secondPath[secondIndex]!,
|
|
219
|
+
secondPath[secondIndex + 1]!,
|
|
220
|
+
)
|
|
221
|
+
) {
|
|
222
|
+
return true
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return false
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const connectionPairPinsAreAlreadyConnected = ({
|
|
230
|
+
connectionPair,
|
|
231
|
+
sameNetTraces,
|
|
232
|
+
}: {
|
|
233
|
+
connectionPair: MspConnectionPair
|
|
234
|
+
sameNetTraces: SolvedTracePath[]
|
|
235
|
+
}): boolean => {
|
|
236
|
+
const [firstPin, secondPin] = connectionPair.pins
|
|
237
|
+
const connectedTraceIndexes = new Set(
|
|
238
|
+
sameNetTraces.flatMap((trace, index) =>
|
|
239
|
+
trace.pinIds.includes(firstPin.pinId) ? [index] : [],
|
|
240
|
+
),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
let previousSize = -1
|
|
244
|
+
while (connectedTraceIndexes.size !== previousSize) {
|
|
245
|
+
previousSize = connectedTraceIndexes.size
|
|
246
|
+
for (let traceIndex = 0; traceIndex < sameNetTraces.length; traceIndex++) {
|
|
247
|
+
if (connectedTraceIndexes.has(traceIndex)) continue
|
|
248
|
+
const trace = sameNetTraces[traceIndex]!
|
|
249
|
+
const connectsToComponent = [...connectedTraceIndexes].some(
|
|
250
|
+
(connectedTraceIndex) => {
|
|
251
|
+
const connectedTrace = sameNetTraces[connectedTraceIndex]!
|
|
252
|
+
return (
|
|
253
|
+
trace.pinIds.some((pinId) =>
|
|
254
|
+
connectedTrace.pinIds.includes(pinId),
|
|
255
|
+
) || pathsIntersect(trace.tracePath, connectedTrace.tracePath)
|
|
256
|
+
)
|
|
257
|
+
},
|
|
258
|
+
)
|
|
259
|
+
if (connectsToComponent) connectedTraceIndexes.add(traceIndex)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return [...connectedTraceIndexes].some((traceIndex) =>
|
|
264
|
+
sameNetTraces[traceIndex]!.pinIds.includes(secondPin.pinId),
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
|
|
207
268
|
const getJunctionCandidates = ({
|
|
208
269
|
connectionPair,
|
|
209
270
|
sameNetTraces,
|
|
@@ -552,6 +613,14 @@ export class UnroutedTraceRecoverySolver extends BaseSolver {
|
|
|
552
613
|
const sameNetTraces = existingTraces.filter(
|
|
553
614
|
(trace) => trace.globalConnNetId === connectionPair.globalConnNetId,
|
|
554
615
|
)
|
|
616
|
+
if (
|
|
617
|
+
connectionPairPinsAreAlreadyConnected({
|
|
618
|
+
connectionPair,
|
|
619
|
+
sameNetTraces,
|
|
620
|
+
})
|
|
621
|
+
) {
|
|
622
|
+
return
|
|
623
|
+
}
|
|
555
624
|
const junctionCandidates = getJunctionCandidates({
|
|
556
625
|
connectionPair,
|
|
557
626
|
sameNetTraces,
|