@tscircuit/schematic-trace-solver 0.0.95 → 0.0.97
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 -6
- package/dist/index.js +271 -221
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +55 -14
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions.ts +31 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +11 -7
- package/lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem.ts +47 -40
- package/lib/types/InputProblem.ts +1 -0
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +1 -1
- package/tests/bug-reports/bug-report-20260706T213649Z/bug-report-20260706T213649Z.test.ts +3 -1
- package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +1 -1
- package/tests/bug-reports/bug-report-20260707T092615Z/bug-report-20260707T092615Z.test.ts +3 -1
- package/tests/repros/__snapshots__/repro-rp2040-gamepad-trace-alignment.snap.svg +178 -0
- package/tests/repros/__snapshots__/repro-rp2040-zero-crystal-fallback-netlabels.snap.svg +54 -0
- package/tests/repros/assets/repro-rp2040-gamepad-trace-alignment.input.json +237 -0
- package/tests/repros/assets/repro-rp2040-zero-crystal-fallback-netlabels.input.json +121 -0
- package/tests/repros/repro-rp2040-gamepad-trace-alignment.test.ts +14 -0
- package/tests/repros/repro-rp2040-zero-crystal-fallback-netlabels.test.ts +16 -0
- package/tests/solvers/SchematicTraceSingleLineSolver2/segment-intersects-rect-interior.test.ts +15 -0
- package/tests/solvers/SchematicTraceSingleLineSolver2/segment-overlaps-rect-boundary-boundary.test.ts +18 -0
- package/tests/solvers/SchematicTraceSingleLineSolver2/segment-overlaps-rect-boundary-interior.test.ts +18 -0
package/dist/index.d.ts
CHANGED
|
@@ -103,6 +103,7 @@ interface InputProblem {
|
|
|
103
103
|
availableNetLabelOrientations: Record<NetId, FacingDirection[]>;
|
|
104
104
|
maxMspPairDistance?: number;
|
|
105
105
|
_chipObstacleSpatialIndex?: ChipObstacleSpatialIndex;
|
|
106
|
+
_hideRatsNet?: boolean;
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
type MspConnectionPairId = string;
|
|
@@ -1012,9 +1013,8 @@ type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
|
|
|
1012
1013
|
onSolved?: (instance: SchematicTracePipelineSolver) => void;
|
|
1013
1014
|
shouldSkip?: (instance: SchematicTracePipelineSolver) => boolean;
|
|
1014
1015
|
};
|
|
1015
|
-
interface
|
|
1016
|
-
|
|
1017
|
-
allowLongDistanceTraces?: boolean;
|
|
1016
|
+
interface Options {
|
|
1017
|
+
hideRatsNet?: boolean;
|
|
1018
1018
|
}
|
|
1019
1019
|
declare class SchematicTracePipelineSolver extends BaseSolver {
|
|
1020
1020
|
mspConnectionPairSolver?: MspConnectionPairSolver;
|
|
@@ -1036,9 +1036,10 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
1036
1036
|
timeSpentOnPhase: Record<string, number>;
|
|
1037
1037
|
firstIterationOfPhase: Record<string, number>;
|
|
1038
1038
|
inputProblem: InputProblem;
|
|
1039
|
+
hideRatsNet: boolean;
|
|
1039
1040
|
pipelineDef: (PipelineStep<typeof MspConnectionPairSolver> | PipelineStep<typeof SchematicTraceLinesSolver> | PipelineStep<typeof LongDistancePairSolver> | PipelineStep<typeof TraceOverlapShiftSolver> | PipelineStep<typeof NetLabelPlacementSolver> | PipelineStep<typeof TraceLabelOverlapAvoidanceSolver> | PipelineStep<typeof TraceCleanupSolver> | PipelineStep<typeof Example28Solver> | PipelineStep<typeof AvailableNetOrientationSolver> | PipelineStep<typeof RailNetLabelCornerPlacementSolver> | PipelineStep<typeof TraceAnchoredNetLabelOverlapSolver> | PipelineStep<typeof NetLabelTraceCollisionSolver> | PipelineStep<typeof NetLabelNetLabelCollisionSolver>)[];
|
|
1040
|
-
constructor(inputProblem: InputProblem);
|
|
1041
|
-
getConstructorParams(): ConstructorParameters<typeof SchematicTracePipelineSolver
|
|
1041
|
+
constructor(inputProblem: InputProblem, opts?: Options);
|
|
1042
|
+
getConstructorParams(): ConstructorParameters<typeof SchematicTracePipelineSolver>;
|
|
1042
1043
|
currentPipelineStepIndex: number;
|
|
1043
1044
|
private cloneAndCorrectInputProblem;
|
|
1044
1045
|
_step(): void;
|
|
@@ -1052,4 +1053,4 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
1052
1053
|
preview(): GraphicsObject;
|
|
1053
1054
|
}
|
|
1054
1055
|
|
|
1055
|
-
export { type ChipId, type InputChip, type InputDirectConnection, type InputNetConnection, type InputPin, type InputProblem, type NetId, type PinId, SchematicTracePipelineSolver,
|
|
1056
|
+
export { type ChipId, type InputChip, type InputDirectConnection, type InputNetConnection, type InputPin, type InputProblem, type NetId, type PinId, SchematicTracePipelineSolver, SchematicTraceSingleLineSolver2, type SectionId, type TextBoxes };
|
package/dist/index.js
CHANGED
|
@@ -327,7 +327,11 @@ var arePinsInDifferentSchematicSections = (inputProblem, p1, p2) => {
|
|
|
327
327
|
|
|
328
328
|
// lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem.ts
|
|
329
329
|
var visualizeInputProblem = (inputProblem, opts = {}) => {
|
|
330
|
-
const {
|
|
330
|
+
const {
|
|
331
|
+
connectionAlpha = 0.8,
|
|
332
|
+
chipAlpha = 0.8,
|
|
333
|
+
hideRatsNet = inputProblem._hideRatsNet ?? false
|
|
334
|
+
} = opts;
|
|
331
335
|
const graphics = {
|
|
332
336
|
lines: [],
|
|
333
337
|
points: [],
|
|
@@ -367,47 +371,49 @@ ${pin._facingDirection ?? getPinDirection(pin, chip)}`,
|
|
|
367
371
|
strokeColor: "rgba(160, 0, 220, 0.9)"
|
|
368
372
|
});
|
|
369
373
|
}
|
|
370
|
-
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
374
|
+
if (!hideRatsNet) {
|
|
375
|
+
for (const directConn of inputProblem.directConnections) {
|
|
376
|
+
const [pinId1, pinId2] = directConn.pinIds;
|
|
377
|
+
const pin1 = pinIdMap.get(pinId1);
|
|
378
|
+
const pin2 = pinIdMap.get(pinId2);
|
|
379
|
+
if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
graphics.lines.push({
|
|
383
|
+
points: [
|
|
384
|
+
{
|
|
385
|
+
x: pin1.x,
|
|
386
|
+
y: pin1.y
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
x: pin2.x,
|
|
390
|
+
y: pin2.y
|
|
391
|
+
}
|
|
392
|
+
],
|
|
393
|
+
strokeColor: getColorFromString(
|
|
394
|
+
directConn.netId ?? `${pinId1}-${pinId2}`,
|
|
395
|
+
connectionAlpha
|
|
396
|
+
)
|
|
397
|
+
});
|
|
376
398
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
for (const netConn of inputProblem.netConnections) {
|
|
395
|
-
const pins = netConn.pinIds.map((pinId) => pinIdMap.get(pinId));
|
|
396
|
-
for (let i = 0; i < pins.length - 1; i++) {
|
|
397
|
-
for (let j = i + 1; j < pins.length; j++) {
|
|
398
|
-
const pin1 = pins[i];
|
|
399
|
-
const pin2 = pins[j];
|
|
400
|
-
if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
|
|
401
|
-
continue;
|
|
399
|
+
for (const netConn of inputProblem.netConnections) {
|
|
400
|
+
const pins = netConn.pinIds.map((pinId) => pinIdMap.get(pinId));
|
|
401
|
+
for (let i = 0; i < pins.length - 1; i++) {
|
|
402
|
+
for (let j = i + 1; j < pins.length; j++) {
|
|
403
|
+
const pin1 = pins[i];
|
|
404
|
+
const pin2 = pins[j];
|
|
405
|
+
if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
graphics.lines.push({
|
|
409
|
+
points: [
|
|
410
|
+
{ x: pin1.x, y: pin1.y },
|
|
411
|
+
{ x: pin2.x, y: pin2.y }
|
|
412
|
+
],
|
|
413
|
+
strokeColor: getColorFromString(netConn.netId, connectionAlpha),
|
|
414
|
+
strokeDash: "4 2"
|
|
415
|
+
});
|
|
402
416
|
}
|
|
403
|
-
graphics.lines.push({
|
|
404
|
-
points: [
|
|
405
|
-
{ x: pin1.x, y: pin1.y },
|
|
406
|
-
{ x: pin2.x, y: pin2.y }
|
|
407
|
-
],
|
|
408
|
-
strokeColor: getColorFromString(netConn.netId, connectionAlpha),
|
|
409
|
-
strokeDash: "4 2"
|
|
410
|
-
});
|
|
411
417
|
}
|
|
412
418
|
}
|
|
413
419
|
}
|
|
@@ -579,54 +585,150 @@ import "graphics-debug";
|
|
|
579
585
|
// lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts
|
|
580
586
|
import { calculateElbow as calculateElbow2 } from "calculate-elbow";
|
|
581
587
|
|
|
582
|
-
// lib/solvers/
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
588
|
+
// lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry.ts
|
|
589
|
+
var NET_LABEL_HORIZONTAL_WIDTH = 0.45;
|
|
590
|
+
var NET_LABEL_HORIZONTAL_HEIGHT = 0.2;
|
|
591
|
+
function getDimsForOrientation(params) {
|
|
592
|
+
const { orientation, netLabelWidth, netLabelHeight } = params;
|
|
593
|
+
const horizWidth = typeof netLabelWidth === "number" ? netLabelWidth : NET_LABEL_HORIZONTAL_WIDTH;
|
|
594
|
+
const horizHeight = typeof netLabelHeight === "number" ? netLabelHeight : NET_LABEL_HORIZONTAL_HEIGHT;
|
|
595
|
+
if (orientation === "y+" || orientation === "y-") {
|
|
596
|
+
return {
|
|
597
|
+
// Rotated: horizontal length = netLabelHeight, vertical length = netLabelWidth
|
|
598
|
+
width: horizHeight,
|
|
599
|
+
height: horizWidth
|
|
600
|
+
};
|
|
601
|
+
}
|
|
586
602
|
return {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
minY: chip.center.y - halfHeight,
|
|
590
|
-
maxY: chip.center.y + halfHeight
|
|
603
|
+
width: horizWidth,
|
|
604
|
+
height: horizHeight
|
|
591
605
|
};
|
|
592
606
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
607
|
+
function getCenterFromAnchor(anchor, orientation, width, height) {
|
|
608
|
+
switch (orientation) {
|
|
609
|
+
case "x+":
|
|
610
|
+
return { x: anchor.x + width / 2, y: anchor.y };
|
|
611
|
+
case "x-":
|
|
612
|
+
return { x: anchor.x - width / 2, y: anchor.y };
|
|
613
|
+
case "y+":
|
|
614
|
+
return { x: anchor.x, y: anchor.y + height / 2 };
|
|
615
|
+
case "y-":
|
|
616
|
+
return { x: anchor.x, y: anchor.y - height / 2 };
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
function getRectBounds(center, w, h) {
|
|
596
620
|
return {
|
|
597
|
-
minX:
|
|
598
|
-
minY:
|
|
599
|
-
maxX:
|
|
600
|
-
maxY:
|
|
621
|
+
minX: center.x - w / 2,
|
|
622
|
+
minY: center.y - h / 2,
|
|
623
|
+
maxX: center.x + w / 2,
|
|
624
|
+
maxY: center.y + h / 2
|
|
601
625
|
};
|
|
602
626
|
}
|
|
603
|
-
|
|
604
|
-
|
|
627
|
+
|
|
628
|
+
// lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/calculateDirectShortPath.ts
|
|
629
|
+
import { calculateElbow } from "calculate-elbow";
|
|
630
|
+
var MAX_SHORT_TRACE_DISTANCE = 0.15;
|
|
631
|
+
var SHORT_TRACE_OVERSHOOT = MAX_SHORT_TRACE_DISTANCE / 7.5;
|
|
632
|
+
var FALLBACK_ELBOW_MAX_OVERSHOOT = 0.2;
|
|
633
|
+
function segmentDirection(from, to) {
|
|
634
|
+
if (to.x > from.x) return "x+";
|
|
635
|
+
if (to.x < from.x) return "x-";
|
|
636
|
+
if (to.y > from.y) return "y+";
|
|
637
|
+
if (to.y < from.y) return "y-";
|
|
638
|
+
return null;
|
|
605
639
|
}
|
|
606
|
-
function
|
|
607
|
-
|
|
608
|
-
|
|
640
|
+
function pathMatchesPinDirections({
|
|
641
|
+
path,
|
|
642
|
+
pin1,
|
|
643
|
+
pin2
|
|
644
|
+
}) {
|
|
645
|
+
const firstDirection = segmentDirection(path[0], path[1]);
|
|
646
|
+
const lastDirection = segmentDirection(
|
|
647
|
+
path[path.length - 1],
|
|
648
|
+
path[path.length - 2]
|
|
649
|
+
);
|
|
650
|
+
return firstDirection === pin1._facingDirection && lastDirection === pin2._facingDirection;
|
|
651
|
+
}
|
|
652
|
+
function calculateShortOrthogonalRoute(pin1, pin2) {
|
|
653
|
+
if (pin1.x === pin2.x || pin1.y === pin2.y) return null;
|
|
654
|
+
const firstDir = pin1._facingDirection;
|
|
655
|
+
const lastDir = pin2._facingDirection;
|
|
656
|
+
const start = { x: pin1.x, y: pin1.y };
|
|
657
|
+
const end = { x: pin2.x, y: pin2.y };
|
|
658
|
+
let path = null;
|
|
659
|
+
if (firstDir?.startsWith("y") && lastDir?.startsWith("x")) {
|
|
660
|
+
let yOffset = -SHORT_TRACE_OVERSHOOT;
|
|
661
|
+
if (firstDir === "y+") {
|
|
662
|
+
yOffset = SHORT_TRACE_OVERSHOOT;
|
|
663
|
+
}
|
|
664
|
+
const routeY = start.y + yOffset;
|
|
665
|
+
const routeX = (start.x + end.x) / 2;
|
|
666
|
+
path = [
|
|
667
|
+
start,
|
|
668
|
+
{ x: start.x, y: routeY },
|
|
669
|
+
{ x: routeX, y: routeY },
|
|
670
|
+
{ x: routeX, y: end.y },
|
|
671
|
+
end
|
|
672
|
+
];
|
|
673
|
+
} else if (firstDir?.startsWith("x") && lastDir?.startsWith("y")) {
|
|
674
|
+
let xOffset = -SHORT_TRACE_OVERSHOOT;
|
|
675
|
+
if (firstDir === "x+") {
|
|
676
|
+
xOffset = SHORT_TRACE_OVERSHOOT;
|
|
677
|
+
}
|
|
678
|
+
const routeX = start.x + xOffset;
|
|
679
|
+
const routeY = (start.y + end.y) / 2;
|
|
680
|
+
path = [
|
|
681
|
+
start,
|
|
682
|
+
{ x: routeX, y: start.y },
|
|
683
|
+
{ x: routeX, y: routeY },
|
|
684
|
+
{ x: end.x, y: routeY },
|
|
685
|
+
end
|
|
686
|
+
];
|
|
609
687
|
}
|
|
610
|
-
return
|
|
688
|
+
if (!path) return null;
|
|
689
|
+
if (pathMatchesPinDirections({ path, pin1, pin2 })) {
|
|
690
|
+
return path;
|
|
691
|
+
}
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
function calculateDirectShortPath(pin1, pin2) {
|
|
695
|
+
const routingDistance = Math.abs(pin1.x - pin2.x) + Math.abs(pin1.y - pin2.y);
|
|
696
|
+
if (routingDistance > MAX_SHORT_TRACE_DISTANCE) return null;
|
|
697
|
+
const start = { x: pin1.x, y: pin1.y };
|
|
698
|
+
const end = { x: pin2.x, y: pin2.y };
|
|
699
|
+
const orthogonalRoute = calculateShortOrthogonalRoute(pin1, pin2);
|
|
700
|
+
if (orthogonalRoute) return orthogonalRoute;
|
|
701
|
+
let candidatePaths = [];
|
|
702
|
+
if (pin1.x !== pin2.x && pin1.y !== pin2.y) {
|
|
703
|
+
candidatePaths = [
|
|
704
|
+
[start, { x: pin2.x, y: pin1.y }, end],
|
|
705
|
+
[start, { x: pin1.x, y: pin2.y }, end]
|
|
706
|
+
];
|
|
707
|
+
} else {
|
|
708
|
+
candidatePaths = [[start, end]];
|
|
709
|
+
}
|
|
710
|
+
for (const path of candidatePaths) {
|
|
711
|
+
if (pathMatchesPinDirections({ path, pin1, pin2 })) return path;
|
|
712
|
+
}
|
|
713
|
+
return calculateElbow(
|
|
714
|
+
{
|
|
715
|
+
x: pin1.x,
|
|
716
|
+
y: pin1.y,
|
|
717
|
+
facingDirection: pin1._facingDirection
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
x: pin2.x,
|
|
721
|
+
y: pin2.y,
|
|
722
|
+
facingDirection: pin2._facingDirection
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
overshoot: Math.min(
|
|
726
|
+
FALLBACK_ELBOW_MAX_OVERSHOOT,
|
|
727
|
+
Math.max(SHORT_TRACE_OVERSHOOT, routingDistance / 4)
|
|
728
|
+
)
|
|
729
|
+
}
|
|
730
|
+
);
|
|
611
731
|
}
|
|
612
|
-
|
|
613
|
-
// lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect.ts
|
|
614
|
-
var chipToRect = (chip) => {
|
|
615
|
-
const b = getInputChipBounds(chip);
|
|
616
|
-
return { kind: "chip", chipId: chip.chipId, ...b };
|
|
617
|
-
};
|
|
618
|
-
var getObstacleRects = (problem, opts = {}) => {
|
|
619
|
-
const chipRects = problem.chips.map(chipToRect);
|
|
620
|
-
const textBoxRects = (problem.textBoxes ?? []).map((textBox) => {
|
|
621
|
-
const b = getTextBoxBounds(textBox, opts.textBoxPadding);
|
|
622
|
-
return {
|
|
623
|
-
kind: "text_box",
|
|
624
|
-
textBox,
|
|
625
|
-
...b
|
|
626
|
-
};
|
|
627
|
-
});
|
|
628
|
-
return [...chipRects, ...textBoxRects];
|
|
629
|
-
};
|
|
630
732
|
|
|
631
733
|
// lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions.ts
|
|
632
734
|
var EPS = 1e-9;
|
|
@@ -652,6 +754,21 @@ var segmentIntersectsRect = (a, b, r, eps = EPS) => {
|
|
|
652
754
|
return overlap > eps;
|
|
653
755
|
}
|
|
654
756
|
};
|
|
757
|
+
var segmentOverlapsRectBoundary = (a, b, r, eps = EPS) => {
|
|
758
|
+
if (isVertical(a, b, eps)) {
|
|
759
|
+
const onVerticalBoundary = Math.abs(a.x - r.minX) <= eps || Math.abs(a.x - r.maxX) <= eps;
|
|
760
|
+
if (!onVerticalBoundary) return false;
|
|
761
|
+
const overlap = Math.min(Math.max(a.y, b.y), r.maxY) - Math.max(Math.min(a.y, b.y), r.minY);
|
|
762
|
+
return overlap > eps;
|
|
763
|
+
}
|
|
764
|
+
if (isHorizontal(a, b, eps)) {
|
|
765
|
+
const onHorizontalBoundary = Math.abs(a.y - r.minY) <= eps || Math.abs(a.y - r.maxY) <= eps;
|
|
766
|
+
if (!onHorizontalBoundary) return false;
|
|
767
|
+
const overlap = Math.min(Math.max(a.x, b.x), r.maxX) - Math.max(Math.min(a.x, b.x), r.minX);
|
|
768
|
+
return overlap > eps;
|
|
769
|
+
}
|
|
770
|
+
return false;
|
|
771
|
+
};
|
|
655
772
|
var findFirstCollision = (pts, rects, opts = {}) => {
|
|
656
773
|
for (let i = 0; i < pts.length - 1; i++) {
|
|
657
774
|
const a = pts[i];
|
|
@@ -806,151 +923,55 @@ var pathKey = (pts, decimals = 6) => {
|
|
|
806
923
|
return key;
|
|
807
924
|
};
|
|
808
925
|
|
|
809
|
-
// lib/solvers/
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
const { orientation, netLabelWidth, netLabelHeight } = params;
|
|
814
|
-
const horizWidth = typeof netLabelWidth === "number" ? netLabelWidth : NET_LABEL_HORIZONTAL_WIDTH;
|
|
815
|
-
const horizHeight = typeof netLabelHeight === "number" ? netLabelHeight : NET_LABEL_HORIZONTAL_HEIGHT;
|
|
816
|
-
if (orientation === "y+" || orientation === "y-") {
|
|
817
|
-
return {
|
|
818
|
-
// Rotated: horizontal length = netLabelHeight, vertical length = netLabelWidth
|
|
819
|
-
width: horizHeight,
|
|
820
|
-
height: horizWidth
|
|
821
|
-
};
|
|
822
|
-
}
|
|
926
|
+
// lib/solvers/GuidelinesSolver/getInputChipBounds.ts
|
|
927
|
+
function getInputChipBounds(chip) {
|
|
928
|
+
const halfWidth = chip.width / 2;
|
|
929
|
+
const halfHeight = chip.height / 2;
|
|
823
930
|
return {
|
|
824
|
-
|
|
825
|
-
|
|
931
|
+
minX: chip.center.x - halfWidth,
|
|
932
|
+
maxX: chip.center.x + halfWidth,
|
|
933
|
+
minY: chip.center.y - halfHeight,
|
|
934
|
+
maxY: chip.center.y + halfHeight
|
|
826
935
|
};
|
|
827
936
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
return { x: anchor.x + width / 2, y: anchor.y };
|
|
832
|
-
case "x-":
|
|
833
|
-
return { x: anchor.x - width / 2, y: anchor.y };
|
|
834
|
-
case "y+":
|
|
835
|
-
return { x: anchor.x, y: anchor.y + height / 2 };
|
|
836
|
-
case "y-":
|
|
837
|
-
return { x: anchor.x, y: anchor.y - height / 2 };
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
function getRectBounds(center, w, h) {
|
|
937
|
+
|
|
938
|
+
// lib/utils/textBoxBounds.ts
|
|
939
|
+
function getTextBoxBounds(textBox, padding = {}) {
|
|
841
940
|
return {
|
|
842
|
-
minX: center.x -
|
|
843
|
-
minY: center.y -
|
|
844
|
-
maxX: center.x +
|
|
845
|
-
maxY: center.y +
|
|
941
|
+
minX: textBox.center.x - textBox.width / 2 - (padding.minX ?? 0),
|
|
942
|
+
minY: textBox.center.y - textBox.height / 2 - (padding.minY ?? 0),
|
|
943
|
+
maxX: textBox.center.x + textBox.width / 2 + (padding.maxX ?? 0),
|
|
944
|
+
maxY: textBox.center.y + textBox.height / 2 + (padding.maxY ?? 0)
|
|
846
945
|
};
|
|
847
946
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
import { calculateElbow } from "calculate-elbow";
|
|
851
|
-
var MAX_SHORT_TRACE_DISTANCE = 0.15;
|
|
852
|
-
var SHORT_TRACE_OVERSHOOT = MAX_SHORT_TRACE_DISTANCE / 7.5;
|
|
853
|
-
var FALLBACK_ELBOW_MAX_OVERSHOOT = 0.2;
|
|
854
|
-
function segmentDirection(from, to) {
|
|
855
|
-
if (to.x > from.x) return "x+";
|
|
856
|
-
if (to.x < from.x) return "x-";
|
|
857
|
-
if (to.y > from.y) return "y+";
|
|
858
|
-
if (to.y < from.y) return "y-";
|
|
859
|
-
return null;
|
|
860
|
-
}
|
|
861
|
-
function pathMatchesPinDirections({
|
|
862
|
-
path,
|
|
863
|
-
pin1,
|
|
864
|
-
pin2
|
|
865
|
-
}) {
|
|
866
|
-
const firstDirection = segmentDirection(path[0], path[1]);
|
|
867
|
-
const lastDirection = segmentDirection(
|
|
868
|
-
path[path.length - 1],
|
|
869
|
-
path[path.length - 2]
|
|
870
|
-
);
|
|
871
|
-
return firstDirection === pin1._facingDirection && lastDirection === pin2._facingDirection;
|
|
872
|
-
}
|
|
873
|
-
function calculateShortOrthogonalRoute(pin1, pin2) {
|
|
874
|
-
if (pin1.x === pin2.x || pin1.y === pin2.y) return null;
|
|
875
|
-
const firstDir = pin1._facingDirection;
|
|
876
|
-
const lastDir = pin2._facingDirection;
|
|
877
|
-
const start = { x: pin1.x, y: pin1.y };
|
|
878
|
-
const end = { x: pin2.x, y: pin2.y };
|
|
879
|
-
let path = null;
|
|
880
|
-
if (firstDir?.startsWith("y") && lastDir?.startsWith("x")) {
|
|
881
|
-
let yOffset = -SHORT_TRACE_OVERSHOOT;
|
|
882
|
-
if (firstDir === "y+") {
|
|
883
|
-
yOffset = SHORT_TRACE_OVERSHOOT;
|
|
884
|
-
}
|
|
885
|
-
const routeY = start.y + yOffset;
|
|
886
|
-
const routeX = (start.x + end.x) / 2;
|
|
887
|
-
path = [
|
|
888
|
-
start,
|
|
889
|
-
{ x: start.x, y: routeY },
|
|
890
|
-
{ x: routeX, y: routeY },
|
|
891
|
-
{ x: routeX, y: end.y },
|
|
892
|
-
end
|
|
893
|
-
];
|
|
894
|
-
} else if (firstDir?.startsWith("x") && lastDir?.startsWith("y")) {
|
|
895
|
-
let xOffset = -SHORT_TRACE_OVERSHOOT;
|
|
896
|
-
if (firstDir === "x+") {
|
|
897
|
-
xOffset = SHORT_TRACE_OVERSHOOT;
|
|
898
|
-
}
|
|
899
|
-
const routeX = start.x + xOffset;
|
|
900
|
-
const routeY = (start.y + end.y) / 2;
|
|
901
|
-
path = [
|
|
902
|
-
start,
|
|
903
|
-
{ x: routeX, y: start.y },
|
|
904
|
-
{ x: routeX, y: routeY },
|
|
905
|
-
{ x: end.x, y: routeY },
|
|
906
|
-
end
|
|
907
|
-
];
|
|
908
|
-
}
|
|
909
|
-
if (!path) return null;
|
|
910
|
-
if (pathMatchesPinDirections({ path, pin1, pin2 })) {
|
|
911
|
-
return path;
|
|
912
|
-
}
|
|
913
|
-
return null;
|
|
947
|
+
function boundsOverlap(a, b, eps = 1e-9) {
|
|
948
|
+
return a.minX < b.maxX - eps && a.maxX > b.minX + eps && a.minY < b.maxY - eps && a.maxY > b.minY + eps;
|
|
914
949
|
}
|
|
915
|
-
function
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
const start = { x: pin1.x, y: pin1.y };
|
|
919
|
-
const end = { x: pin2.x, y: pin2.y };
|
|
920
|
-
const orthogonalRoute = calculateShortOrthogonalRoute(pin1, pin2);
|
|
921
|
-
if (orthogonalRoute) return orthogonalRoute;
|
|
922
|
-
let candidatePaths = [];
|
|
923
|
-
if (pin1.x !== pin2.x && pin1.y !== pin2.y) {
|
|
924
|
-
candidatePaths = [
|
|
925
|
-
[start, { x: pin2.x, y: pin1.y }, end],
|
|
926
|
-
[start, { x: pin1.x, y: pin2.y }, end]
|
|
927
|
-
];
|
|
928
|
-
} else {
|
|
929
|
-
candidatePaths = [[start, end]];
|
|
930
|
-
}
|
|
931
|
-
for (const path of candidatePaths) {
|
|
932
|
-
if (pathMatchesPinDirections({ path, pin1, pin2 })) return path;
|
|
950
|
+
function rectIntersectsAnyTextBox(bounds, inputProblem) {
|
|
951
|
+
for (const textBox of inputProblem.textBoxes ?? []) {
|
|
952
|
+
if (boundsOverlap(bounds, getTextBoxBounds(textBox))) return true;
|
|
933
953
|
}
|
|
934
|
-
return
|
|
935
|
-
{
|
|
936
|
-
x: pin1.x,
|
|
937
|
-
y: pin1.y,
|
|
938
|
-
facingDirection: pin1._facingDirection
|
|
939
|
-
},
|
|
940
|
-
{
|
|
941
|
-
x: pin2.x,
|
|
942
|
-
y: pin2.y,
|
|
943
|
-
facingDirection: pin2._facingDirection
|
|
944
|
-
},
|
|
945
|
-
{
|
|
946
|
-
overshoot: Math.min(
|
|
947
|
-
FALLBACK_ELBOW_MAX_OVERSHOOT,
|
|
948
|
-
Math.max(SHORT_TRACE_OVERSHOOT, routingDistance / 4)
|
|
949
|
-
)
|
|
950
|
-
}
|
|
951
|
-
);
|
|
954
|
+
return false;
|
|
952
955
|
}
|
|
953
956
|
|
|
957
|
+
// lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect.ts
|
|
958
|
+
var chipToRect = (chip) => {
|
|
959
|
+
const b = getInputChipBounds(chip);
|
|
960
|
+
return { kind: "chip", chipId: chip.chipId, ...b };
|
|
961
|
+
};
|
|
962
|
+
var getObstacleRects = (problem, opts = {}) => {
|
|
963
|
+
const chipRects = problem.chips.map(chipToRect);
|
|
964
|
+
const textBoxRects = (problem.textBoxes ?? []).map((textBox) => {
|
|
965
|
+
const b = getTextBoxBounds(textBox, opts.textBoxPadding);
|
|
966
|
+
return {
|
|
967
|
+
kind: "text_box",
|
|
968
|
+
textBox,
|
|
969
|
+
...b
|
|
970
|
+
};
|
|
971
|
+
});
|
|
972
|
+
return [...chipRects, ...textBoxRects];
|
|
973
|
+
};
|
|
974
|
+
|
|
954
975
|
// lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts
|
|
955
976
|
var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
|
|
956
977
|
pins;
|
|
@@ -1137,17 +1158,41 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
|
|
|
1137
1158
|
const collision = findFirstCollision(path, this.obstacles, {
|
|
1138
1159
|
excludeRectsForSegment: (segIndex2) => {
|
|
1139
1160
|
const lastSegIndex2 = path.length - 2;
|
|
1161
|
+
const excludedRects = /* @__PURE__ */ new Set();
|
|
1140
1162
|
if (segIndex2 === 0 || segIndex2 === lastSegIndex2) {
|
|
1141
|
-
|
|
1163
|
+
for (const textObstacle of this.textObstacles) {
|
|
1164
|
+
excludedRects.add(textObstacle);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
const segmentStart = path[segIndex2];
|
|
1168
|
+
const segmentEnd = path[segIndex2 + 1];
|
|
1169
|
+
const endpointEpsilon = 1e-9;
|
|
1170
|
+
const segmentTouchesPin = (pin) => [segmentStart, segmentEnd].some(
|
|
1171
|
+
(point) => Math.abs(point.x - pin.x) <= endpointEpsilon && Math.abs(point.y - pin.y) <= endpointEpsilon
|
|
1172
|
+
);
|
|
1173
|
+
for (const pin of this.pins) {
|
|
1174
|
+
if (!segmentTouchesPin(pin)) continue;
|
|
1175
|
+
const endpointChipObstacle = this.obstacles.find(
|
|
1176
|
+
(obstacle) => obstacle.kind === "chip" && obstacle.chipId === pin.chipId
|
|
1177
|
+
);
|
|
1178
|
+
if (endpointChipObstacle && segmentOverlapsRectBoundary(
|
|
1179
|
+
segmentStart,
|
|
1180
|
+
segmentEnd,
|
|
1181
|
+
endpointChipObstacle
|
|
1182
|
+
)) {
|
|
1183
|
+
excludedRects.add(endpointChipObstacle);
|
|
1184
|
+
}
|
|
1142
1185
|
}
|
|
1143
1186
|
const adjacentEndpointSegIndex = segIndex2 === 1 ? 2 : segIndex2 === lastSegIndex2 - 1 ? lastSegIndex2 - 2 : null;
|
|
1144
1187
|
if (adjacentEndpointSegIndex !== null && adjacentEndpointSegIndex >= 0 && adjacentEndpointSegIndex < lastSegIndex2 && this.isSegmentOutsidePinBand(
|
|
1145
1188
|
path[adjacentEndpointSegIndex],
|
|
1146
1189
|
path[adjacentEndpointSegIndex + 1]
|
|
1147
1190
|
)) {
|
|
1148
|
-
|
|
1191
|
+
for (const textObstacle of this.endpointTextObstacles) {
|
|
1192
|
+
excludedRects.add(textObstacle);
|
|
1193
|
+
}
|
|
1149
1194
|
}
|
|
1150
|
-
return
|
|
1195
|
+
return excludedRects;
|
|
1151
1196
|
}
|
|
1152
1197
|
});
|
|
1153
1198
|
if (!collision) {
|
|
@@ -9034,6 +9079,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
9034
9079
|
timeSpentOnPhase;
|
|
9035
9080
|
firstIterationOfPhase;
|
|
9036
9081
|
inputProblem;
|
|
9082
|
+
hideRatsNet;
|
|
9037
9083
|
pipelineDef = [
|
|
9038
9084
|
definePipelineStep(
|
|
9039
9085
|
"mspConnectionPairSolver",
|
|
@@ -9233,8 +9279,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
9233
9279
|
]
|
|
9234
9280
|
)
|
|
9235
9281
|
];
|
|
9236
|
-
constructor(inputProblem) {
|
|
9282
|
+
constructor(inputProblem, opts) {
|
|
9237
9283
|
super();
|
|
9284
|
+
this.hideRatsNet = opts?.hideRatsNet ?? false;
|
|
9238
9285
|
this.inputProblem = this.cloneAndCorrectInputProblem(inputProblem);
|
|
9239
9286
|
this.MAX_ITERATIONS = 1e6;
|
|
9240
9287
|
this.startTimeOfPhase = {};
|
|
@@ -9243,13 +9290,14 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
9243
9290
|
this.firstIterationOfPhase = {};
|
|
9244
9291
|
}
|
|
9245
9292
|
getConstructorParams() {
|
|
9246
|
-
return this.inputProblem;
|
|
9293
|
+
return [this.inputProblem, { hideRatsNet: this.hideRatsNet }];
|
|
9247
9294
|
}
|
|
9248
9295
|
currentPipelineStepIndex = 0;
|
|
9249
9296
|
cloneAndCorrectInputProblem(original) {
|
|
9250
9297
|
const cloned = structuredClone({
|
|
9251
9298
|
...original,
|
|
9252
|
-
_chipObstacleSpatialIndex: void 0
|
|
9299
|
+
_chipObstacleSpatialIndex: void 0,
|
|
9300
|
+
_hideRatsNet: this.hideRatsNet
|
|
9253
9301
|
});
|
|
9254
9302
|
expandChipsToFitPins(cloned);
|
|
9255
9303
|
correctPinsInsideChips(cloned);
|
|
@@ -9295,7 +9343,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
9295
9343
|
if (!this.solved && this.activeSubSolver)
|
|
9296
9344
|
return this.activeSubSolver.visualize();
|
|
9297
9345
|
const visualizations = [
|
|
9298
|
-
visualizeInputProblem(this.inputProblem
|
|
9346
|
+
visualizeInputProblem(this.inputProblem, {
|
|
9347
|
+
hideRatsNet: this.hideRatsNet
|
|
9348
|
+
}),
|
|
9299
9349
|
...this.pipelineDef.map((p) => this[p.solverName]?.visualize()).filter(Boolean).map((viz, stepIndex) => {
|
|
9300
9350
|
for (const rect of viz.rects ?? []) {
|
|
9301
9351
|
rect.step = stepIndex;
|