@tscircuit/schematic-trace-solver 0.0.110 → 0.0.112
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 +52 -12
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +54 -15
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +28 -2
- package/package.json +1 -1
- package/tests/repros/__snapshots__/repro84-stacked-gnd-label-orientation.snap.svg +48 -0
- package/tests/repros/repro84-stacked-gnd-label-orientation.test.ts +69 -0
- package/tests/solvers/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2_repro47-short-opposing-pins.test.ts +63 -0
- package/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/SchematicTraceSingleLineSolver2_repro47-short-opposing-pins.snap.svg +46 -0
package/dist/index.js
CHANGED
|
@@ -1130,6 +1130,17 @@ var calculateElbowForPins = ({
|
|
|
1130
1130
|
},
|
|
1131
1131
|
{ overshoot }
|
|
1132
1132
|
);
|
|
1133
|
+
var pinsFaceEachOther = ({
|
|
1134
|
+
pin1,
|
|
1135
|
+
pin2
|
|
1136
|
+
}) => {
|
|
1137
|
+
const xDistance = pin2.x - pin1.x;
|
|
1138
|
+
const yDistance = pin2.y - pin1.y;
|
|
1139
|
+
if (Math.abs(xDistance) >= Math.abs(yDistance)) {
|
|
1140
|
+
return xDistance > 0 ? pin1._facingDirection === "x+" && pin2._facingDirection === "x-" : pin1._facingDirection === "x-" && pin2._facingDirection === "x+";
|
|
1141
|
+
}
|
|
1142
|
+
return yDistance > 0 ? pin1._facingDirection === "y+" && pin2._facingDirection === "y-" : pin1._facingDirection === "y-" && pin2._facingDirection === "y+";
|
|
1143
|
+
};
|
|
1133
1144
|
var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
|
|
1134
1145
|
pins;
|
|
1135
1146
|
connectionPair;
|
|
@@ -1178,7 +1189,9 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
|
|
|
1178
1189
|
pin2,
|
|
1179
1190
|
overshoot: Math.min(0.2, Math.max(0.02, routingDistance / 4))
|
|
1180
1191
|
});
|
|
1181
|
-
const
|
|
1192
|
+
const adaptiveElbowIsShorter = this.pathLength(adaptiveElbow) < this.pathLength(defaultElbow);
|
|
1193
|
+
const defaultElbowBacktracks = this.pathLength(defaultElbow) > routingDistance + 1e-9;
|
|
1194
|
+
const shouldUseAdaptiveElbow = findFirstCollision(adaptiveElbow, this.obstacles) === null && (pinsFaceEachOther({ pin1, pin2 }) && defaultElbowBacktracks && adaptiveElbowIsShorter || findFirstCollision(defaultElbow, this.obstacles) !== null);
|
|
1182
1195
|
this.baseElbow = defaultElbow;
|
|
1183
1196
|
if (shouldUseAdaptiveElbow) {
|
|
1184
1197
|
this.baseElbow = adaptiveElbow;
|
|
@@ -8154,17 +8167,44 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
8154
8167
|
return Math.min(this.maxSearchDistance, labelLength * 2);
|
|
8155
8168
|
}
|
|
8156
8169
|
getLateralColumnMaxDistance(label, orientation, baseAnchor) {
|
|
8157
|
-
const
|
|
8158
|
-
const
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
8170
|
+
const availableOrientations = this.getAvailableOrientations(label);
|
|
8171
|
+
const isSingleVerticalOrientation = availableOrientations.length === 1 && isYOrientation(availableOrientations[0]);
|
|
8172
|
+
const labelChipIds = new Set(
|
|
8173
|
+
label.pinIds.map((pinId) => this.pinMap[pinId]?.chipId).filter((chipId) => Boolean(chipId))
|
|
8174
|
+
);
|
|
8175
|
+
const connectedChips = this.chipObstacleSpatialIndex.chips.filter(
|
|
8176
|
+
(chip) => labelChipIds.has(chip.chipId)
|
|
8177
|
+
);
|
|
8178
|
+
const firstConnectedChipId = label.pinIds.map((pinId) => this.pinMap[pinId]?.chipId).find(Boolean);
|
|
8179
|
+
const firstConnectedChip = connectedChips.find(
|
|
8180
|
+
(chip) => chip.chipId === firstConnectedChipId
|
|
8181
|
+
);
|
|
8182
|
+
const labelChips = isSingleVerticalOrientation ? connectedChips : firstConnectedChip ? [firstConnectedChip] : [];
|
|
8183
|
+
if (labelChips.length > 0) {
|
|
8184
|
+
if (orientation === "y-") {
|
|
8185
|
+
return Math.max(
|
|
8186
|
+
0,
|
|
8187
|
+
...labelChips.map((chip) => baseAnchor.y - chip.bounds.minY)
|
|
8188
|
+
);
|
|
8189
|
+
}
|
|
8190
|
+
if (orientation === "y+") {
|
|
8191
|
+
return Math.max(
|
|
8192
|
+
0,
|
|
8193
|
+
...labelChips.map((chip) => chip.bounds.maxY - baseAnchor.y)
|
|
8194
|
+
);
|
|
8195
|
+
}
|
|
8196
|
+
if (orientation === "x-") {
|
|
8197
|
+
return Math.max(
|
|
8198
|
+
0,
|
|
8199
|
+
...labelChips.map((chip) => baseAnchor.x - chip.bounds.minX)
|
|
8200
|
+
);
|
|
8201
|
+
}
|
|
8202
|
+
if (orientation === "x+") {
|
|
8203
|
+
return Math.max(
|
|
8204
|
+
0,
|
|
8205
|
+
...labelChips.map((chip) => chip.bounds.maxX - baseAnchor.x)
|
|
8206
|
+
);
|
|
8207
|
+
}
|
|
8168
8208
|
}
|
|
8169
8209
|
return this.getSearchDistanceLimit(label, orientation);
|
|
8170
8210
|
}
|
|
@@ -640,22 +640,61 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
640
640
|
orientation: FacingDirection,
|
|
641
641
|
baseAnchor: Point,
|
|
642
642
|
) {
|
|
643
|
-
const
|
|
644
|
-
|
|
643
|
+
const availableOrientations = this.getAvailableOrientations(label)
|
|
644
|
+
const isSingleVerticalOrientation =
|
|
645
|
+
availableOrientations.length === 1 &&
|
|
646
|
+
isYOrientation(availableOrientations[0]!)
|
|
647
|
+
|
|
648
|
+
// A vertical rail between components may need to branch laterally and then
|
|
649
|
+
// search past either connected component before it can use its required
|
|
650
|
+
// orientation. Keep other labels scoped to their first connected component
|
|
651
|
+
// so an orientation correction does not move a signal label across the
|
|
652
|
+
// entire connection.
|
|
653
|
+
const labelChipIds = new Set(
|
|
654
|
+
label.pinIds
|
|
655
|
+
.map((pinId) => this.pinMap[pinId]?.chipId)
|
|
656
|
+
.filter((chipId): chipId is string => Boolean(chipId)),
|
|
657
|
+
)
|
|
658
|
+
const connectedChips = this.chipObstacleSpatialIndex.chips.filter((chip) =>
|
|
659
|
+
labelChipIds.has(chip.chipId),
|
|
660
|
+
)
|
|
661
|
+
const firstConnectedChipId = label.pinIds
|
|
662
|
+
.map((pinId) => this.pinMap[pinId]?.chipId)
|
|
645
663
|
.find(Boolean)
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
664
|
+
const firstConnectedChip = connectedChips.find(
|
|
665
|
+
(chip) => chip.chipId === firstConnectedChipId,
|
|
666
|
+
)
|
|
667
|
+
const labelChips = isSingleVerticalOrientation
|
|
668
|
+
? connectedChips
|
|
669
|
+
: firstConnectedChip
|
|
670
|
+
? [firstConnectedChip]
|
|
671
|
+
: []
|
|
672
|
+
|
|
673
|
+
if (labelChips.length > 0) {
|
|
674
|
+
if (orientation === "y-") {
|
|
675
|
+
return Math.max(
|
|
676
|
+
0,
|
|
677
|
+
...labelChips.map((chip) => baseAnchor.y - chip.bounds.minY),
|
|
678
|
+
)
|
|
679
|
+
}
|
|
680
|
+
if (orientation === "y+") {
|
|
681
|
+
return Math.max(
|
|
682
|
+
0,
|
|
683
|
+
...labelChips.map((chip) => chip.bounds.maxY - baseAnchor.y),
|
|
684
|
+
)
|
|
685
|
+
}
|
|
686
|
+
if (orientation === "x-") {
|
|
687
|
+
return Math.max(
|
|
688
|
+
0,
|
|
689
|
+
...labelChips.map((chip) => baseAnchor.x - chip.bounds.minX),
|
|
690
|
+
)
|
|
691
|
+
}
|
|
692
|
+
if (orientation === "x+") {
|
|
693
|
+
return Math.max(
|
|
694
|
+
0,
|
|
695
|
+
...labelChips.map((chip) => chip.bounds.maxX - baseAnchor.x),
|
|
696
|
+
)
|
|
697
|
+
}
|
|
659
698
|
}
|
|
660
699
|
|
|
661
700
|
return this.getSearchDistanceLimit(label, orientation)
|
|
@@ -58,6 +58,25 @@ const calculateElbowForPins = ({
|
|
|
58
58
|
{ overshoot },
|
|
59
59
|
)
|
|
60
60
|
|
|
61
|
+
const pinsFaceEachOther = ({
|
|
62
|
+
pin1,
|
|
63
|
+
pin2,
|
|
64
|
+
}: {
|
|
65
|
+
pin1: MspConnectionPair["pins"][number]
|
|
66
|
+
pin2: MspConnectionPair["pins"][number]
|
|
67
|
+
}) => {
|
|
68
|
+
const xDistance = pin2.x - pin1.x
|
|
69
|
+
const yDistance = pin2.y - pin1.y
|
|
70
|
+
if (Math.abs(xDistance) >= Math.abs(yDistance)) {
|
|
71
|
+
return xDistance > 0
|
|
72
|
+
? pin1._facingDirection === "x+" && pin2._facingDirection === "x-"
|
|
73
|
+
: pin1._facingDirection === "x-" && pin2._facingDirection === "x+"
|
|
74
|
+
}
|
|
75
|
+
return yDistance > 0
|
|
76
|
+
? pin1._facingDirection === "y+" && pin2._facingDirection === "y-"
|
|
77
|
+
: pin1._facingDirection === "y-" && pin2._facingDirection === "y+"
|
|
78
|
+
}
|
|
79
|
+
|
|
61
80
|
export class SchematicTraceSingleLineSolver2 extends BaseSolver {
|
|
62
81
|
pins: MspConnectionPair["pins"]
|
|
63
82
|
connectionPair?: MspConnectionPair
|
|
@@ -130,9 +149,16 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
|
|
|
130
149
|
pin2,
|
|
131
150
|
overshoot: Math.min(0.2, Math.max(0.02, routingDistance / 4)),
|
|
132
151
|
})
|
|
152
|
+
const adaptiveElbowIsShorter =
|
|
153
|
+
this.pathLength(adaptiveElbow) < this.pathLength(defaultElbow)
|
|
154
|
+
const defaultElbowBacktracks =
|
|
155
|
+
this.pathLength(defaultElbow) > routingDistance + 1e-9
|
|
133
156
|
const shouldUseAdaptiveElbow =
|
|
134
|
-
findFirstCollision(
|
|
135
|
-
|
|
157
|
+
findFirstCollision(adaptiveElbow, this.obstacles) === null &&
|
|
158
|
+
((pinsFaceEachOther({ pin1, pin2 }) &&
|
|
159
|
+
defaultElbowBacktracks &&
|
|
160
|
+
adaptiveElbowIsShorter) ||
|
|
161
|
+
findFirstCollision(defaultElbow, this.obstacles) !== null)
|
|
136
162
|
|
|
137
163
|
// Build initial elbow path
|
|
138
164
|
this.baseElbow = defaultElbow
|
package/package.json
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="white"/><g><polyline data-points="0.2,0.3 -0.2,-0.3999999999999999" data-type="line" data-label="" points="315.05882352941177,281.5686274509804 271.13725490196083,358.4313725490196" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1px" stroke-dasharray="4 2"/></g><g><polyline data-points="0.2,0.30000000000000004 0.2,-0.04999999999999993 -0.2,-0.04999999999999993 -0.2,-0.3999999999999999" data-type="line" data-label="" points="315.05882352941177,281.5686274509804 315.05882352941177,320 271.13725490196083,320 271.13725490196083,358.4313725490196" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="0.2,-0.04999999999999993 0.2,6.938893903907228e-17 0.65,6.938893903907228e-17 0.65,-0.10099999999999994" data-type="line" data-label="" points="315.05882352941177,320 315.05882352941177,314.5098039215686 364.47058823529414,314.5098039215686 364.47058823529414,325.59999999999997" fill="none" stroke="purple" stroke-width="1px"/></g><g><rect data-type="rect" data-label="TOP" data-x="0" data-y="1.4" x="249.1764705882353" y="40" width="87.84313725490199" height="241.5686274509804" fill="hsl(93, 100%, 50%, 0.8)" stroke="black" stroke-width="0.009107142857142857"/></g><g><rect data-type="rect" data-label="BOTTOM" data-x="0" data-y="-1.5" x="249.1764705882353" y="358.43137254901967" width="87.84313725490199" height="241.5686274509804" fill="hsl(259, 100%, 50%, 0.8)" stroke="black" stroke-width="0.009107142857142857"/></g><g><rect data-type="rect" data-label="netId: GND
|
|
2
|
+
globalConnNetId: connectivity_net0" data-x="0.65" data-y="-0.31099999999999994" x="338.11764705882354" y="325.59999999999997" width="52.70588235294122" height="46.117647058823536" fill="#00000066" stroke="#000000" stroke-width="0.009107142857142857"/></g><g><circle data-type="point" data-label="TOP.GND
|
|
3
|
+
y-" data-x="0.2" data-y="0.3" cx="315.05882352941177" cy="281.5686274509804" r="3" fill="hsl(116, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="BOTTOM.GND
|
|
4
|
+
y+" data-x="-0.2" data-y="-0.3999999999999999" cx="271.13725490196083" cy="358.4313725490196" r="3" fill="hsl(42, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="anchorPoint
|
|
5
|
+
orientation: y-" data-x="0.65" data-y="-0.10099999999999994" cx="364.47058823529414" cy="325.59999999999997" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g id="crosshair" style="display: none"><line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5"/><line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5"/><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text></g><script><![CDATA[
|
|
6
|
+
document.currentScript.parentElement.addEventListener('mousemove', (e) => {
|
|
7
|
+
const svg = e.currentTarget;
|
|
8
|
+
const rect = svg.getBoundingClientRect();
|
|
9
|
+
const x = e.clientX - rect.left;
|
|
10
|
+
const y = e.clientY - rect.top;
|
|
11
|
+
const crosshair = svg.getElementById('crosshair');
|
|
12
|
+
const h = svg.getElementById('crosshair-h');
|
|
13
|
+
const v = svg.getElementById('crosshair-v');
|
|
14
|
+
const coords = svg.getElementById('coordinates');
|
|
15
|
+
|
|
16
|
+
crosshair.style.display = 'block';
|
|
17
|
+
h.setAttribute('x1', '0');
|
|
18
|
+
h.setAttribute('x2', '640');
|
|
19
|
+
h.setAttribute('y1', y);
|
|
20
|
+
h.setAttribute('y2', y);
|
|
21
|
+
v.setAttribute('x1', x);
|
|
22
|
+
v.setAttribute('x2', x);
|
|
23
|
+
v.setAttribute('y1', '0');
|
|
24
|
+
v.setAttribute('y2', '640');
|
|
25
|
+
|
|
26
|
+
// Calculate real coordinates using inverse transformation
|
|
27
|
+
const matrix = {"a":109.80392156862746,"c":0,"e":293.0980392156863,"b":0,"d":-109.80392156862746,"f":314.5098039215686};
|
|
28
|
+
// Manually invert and apply the affine transform
|
|
29
|
+
// Since we only use translate and scale, we can directly compute:
|
|
30
|
+
// x' = (x - tx) / sx
|
|
31
|
+
// y' = (y - ty) / sy
|
|
32
|
+
const sx = matrix.a;
|
|
33
|
+
const sy = matrix.d;
|
|
34
|
+
const tx = matrix.e;
|
|
35
|
+
const ty = matrix.f;
|
|
36
|
+
const realPoint = {
|
|
37
|
+
x: (x - tx) / sx,
|
|
38
|
+
y: (y - ty) / sy // Flip y back since we used negative scale
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
coords.textContent = `(${realPoint.x.toFixed(2)}, ${realPoint.y.toFixed(2)})`;
|
|
42
|
+
coords.setAttribute('x', (x + 5).toString());
|
|
43
|
+
coords.setAttribute('y', (y - 5).toString());
|
|
44
|
+
});
|
|
45
|
+
document.currentScript.parentElement.addEventListener('mouseleave', () => {
|
|
46
|
+
document.currentScript.parentElement.getElementById('crosshair').style.display = 'none';
|
|
47
|
+
});
|
|
48
|
+
]]></script></svg>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
|
|
3
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
4
|
+
import "tests/fixtures/matcher"
|
|
5
|
+
|
|
6
|
+
const inputProblem: InputProblem = {
|
|
7
|
+
chips: [
|
|
8
|
+
{
|
|
9
|
+
chipId: "TOP",
|
|
10
|
+
center: { x: 0, y: 1.4 },
|
|
11
|
+
width: 0.8,
|
|
12
|
+
height: 1.4,
|
|
13
|
+
pins: [
|
|
14
|
+
{
|
|
15
|
+
pinId: "TOP.GND",
|
|
16
|
+
x: 0.2,
|
|
17
|
+
y: 0.3,
|
|
18
|
+
_facingDirection: "y-",
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
chipId: "BOTTOM",
|
|
24
|
+
center: { x: 0, y: -1.5 },
|
|
25
|
+
width: 0.8,
|
|
26
|
+
height: 1.4,
|
|
27
|
+
pins: [
|
|
28
|
+
{
|
|
29
|
+
pinId: "BOTTOM.GND",
|
|
30
|
+
x: -0.2,
|
|
31
|
+
y: -0.4,
|
|
32
|
+
_facingDirection: "y+",
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
directConnections: [],
|
|
38
|
+
netConnections: [
|
|
39
|
+
{
|
|
40
|
+
netId: "GND",
|
|
41
|
+
pinIds: ["TOP.GND", "BOTTOM.GND"],
|
|
42
|
+
netLabelWidth: 0.42,
|
|
43
|
+
netLabelHeight: 0.48,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
textBoxes: [],
|
|
47
|
+
availableNetLabelOrientations: { GND: ["y-"] },
|
|
48
|
+
maxMspPairDistance: 2.4,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
test("repro84 restores GND orientation between stacked components", () => {
|
|
52
|
+
const solver = new SchematicTracePipelineSolver(inputProblem)
|
|
53
|
+
|
|
54
|
+
solver.solve()
|
|
55
|
+
|
|
56
|
+
const initialGndLabel = solver.netLabelPlacementSolver!.netLabelPlacements[0]
|
|
57
|
+
const finalGndLabel =
|
|
58
|
+
solver.netLabelNetLabelCollisionSolver!.getOutput().netLabelPlacements[0]
|
|
59
|
+
const traces = solver.netLabelTraceCollisionSolver!.getOutput().traces
|
|
60
|
+
|
|
61
|
+
expect(initialGndLabel?.orientation).toBe("x+")
|
|
62
|
+
expect(finalGndLabel?.orientation).toBe("y-")
|
|
63
|
+
expect(
|
|
64
|
+
traces.some((trace) =>
|
|
65
|
+
trace.mspPairId.startsWith("available-net-orientation"),
|
|
66
|
+
),
|
|
67
|
+
).toBe(true)
|
|
68
|
+
expect(solver).toMatchSolverSnapshot(import.meta.path)
|
|
69
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { SchematicTraceSingleLineSolver2 } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2"
|
|
3
|
+
import type { InputChip, InputProblem } from "lib/types/InputProblem"
|
|
4
|
+
|
|
5
|
+
const getOrthogonalPathLength = (points: Array<{ x: number; y: number }>) =>
|
|
6
|
+
points.slice(0, -1).reduce((length, point, pointIndex) => {
|
|
7
|
+
const nextPoint = points[pointIndex + 1]!
|
|
8
|
+
return (
|
|
9
|
+
length + Math.abs(nextPoint.x - point.x) + Math.abs(nextPoint.y - point.y)
|
|
10
|
+
)
|
|
11
|
+
}, 0)
|
|
12
|
+
|
|
13
|
+
test("repro47 uses a minimal route between nearby opposing pins", async () => {
|
|
14
|
+
const diode: InputChip = {
|
|
15
|
+
chipId: "D2",
|
|
16
|
+
center: { x: 3.2, y: 0.095 },
|
|
17
|
+
width: 0.4,
|
|
18
|
+
height: 0.2,
|
|
19
|
+
pins: [{ pinId: "D2.cathode", x: 3.4, y: 0.095 }],
|
|
20
|
+
}
|
|
21
|
+
const pushButton: InputChip = {
|
|
22
|
+
chipId: "SW2",
|
|
23
|
+
center: { x: 3.81, y: 0.045 },
|
|
24
|
+
width: 0.4,
|
|
25
|
+
height: 0.2,
|
|
26
|
+
pins: [{ pinId: "SW2.pin1", x: 3.61, y: 0.045 }],
|
|
27
|
+
}
|
|
28
|
+
const pins: ConstructorParameters<
|
|
29
|
+
typeof SchematicTraceSingleLineSolver2
|
|
30
|
+
>[0]["pins"] = [
|
|
31
|
+
{
|
|
32
|
+
pinId: "D2.cathode",
|
|
33
|
+
chipId: diode.chipId,
|
|
34
|
+
x: 3.4,
|
|
35
|
+
y: 0.095,
|
|
36
|
+
_facingDirection: "x+" as const,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
pinId: "SW2.pin1",
|
|
40
|
+
chipId: pushButton.chipId,
|
|
41
|
+
x: 3.61,
|
|
42
|
+
y: 0.045,
|
|
43
|
+
_facingDirection: "x-" as const,
|
|
44
|
+
},
|
|
45
|
+
]
|
|
46
|
+
const inputProblem: InputProblem = {
|
|
47
|
+
chips: [diode, pushButton],
|
|
48
|
+
directConnections: [{ pinIds: [pins[0].pinId, pins[1].pinId] }],
|
|
49
|
+
netConnections: [],
|
|
50
|
+
availableNetLabelOrientations: {},
|
|
51
|
+
}
|
|
52
|
+
const solver = new SchematicTraceSingleLineSolver2({
|
|
53
|
+
pins,
|
|
54
|
+
chipMap: { [diode.chipId]: diode, [pushButton.chipId]: pushButton },
|
|
55
|
+
inputProblem,
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
solver.solve()
|
|
59
|
+
|
|
60
|
+
expect(solver.solved).toBe(true)
|
|
61
|
+
expect(getOrthogonalPathLength(solver.solvedTracePath!)).toBeCloseTo(0.26)
|
|
62
|
+
await expect(solver).toMatchSolverSnapshot(import.meta.path)
|
|
63
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="white"/><g><polyline data-points="3.4,0.095 3.61,0.045" data-type="line" data-label="" points="261.78217821782187,306.13861386138615 378.21782178217813,333.86138613861385" fill="none" stroke="hsl(112, 100%, 50%, 0.1)" stroke-width="1px"/></g><g><polyline data-points="3.4,0.095 3.465,0.095 3.505,0.095 3.505,0.045 3.545,0.045 3.61,0.045" data-type="line" data-label="" points="261.78217821782187,306.13861386138615 297.82178217821775,306.13861386138615 320,306.13861386138615 320,333.86138613861385 342.17821782178225,333.86138613861385 378.21782178217813,333.86138613861385" fill="none" stroke="red" stroke-width="1px" stroke-dasharray="4 4"/></g><g><polyline data-points="3.4,0.095 3.61,0.045" data-type="line" data-label="" points="261.78217821782187,306.13861386138615 378.21782178217813,333.86138613861385" fill="none" stroke="blue" stroke-width="1px" stroke-dasharray="5 5"/></g><g><polyline data-points="3.4,0.095 3.465,0.095 3.505,0.095 3.505,0.045 3.545,0.045 3.61,0.045" data-type="line" data-label="" points="261.78217821782187,306.13861386138615 297.82178217821775,306.13861386138615 320,306.13861386138615 320,333.86138613861385 342.17821782178225,333.86138613861385 378.21782178217813,333.86138613861385" fill="none" stroke="green" stroke-width="1px"/></g><g><rect data-type="rect" data-label="D2" data-x="3.2" data-y="0.095" x="40" y="250.69306930693068" width="221.7821782178221" height="110.89108910891093" fill="hsl(358, 100%, 50%, 0.1)" stroke="black" stroke-width="0.0018035714285714283"/></g><g><rect data-type="rect" data-label="SW2" data-x="3.81" data-y="0.045" x="378.21782178217825" y="278.41584158415844" width="221.78217821782187" height="110.89108910891088" fill="hsl(70, 100%, 50%, 0.1)" stroke="black" stroke-width="0.0018035714285714283"/></g><g><circle data-type="point" data-label="D2.cathode
|
|
2
|
+
x+" data-x="3.4" data-y="0.095" cx="261.78217821782187" cy="306.13861386138615" r="3" fill="hsl(22, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="SW2.pin1
|
|
3
|
+
x-" data-x="3.61" data-y="0.045" cx="378.21782178217813" cy="333.86138613861385" r="3" fill="hsl(12, 100%, 50%, 0.8)"/></g><g id="crosshair" style="display: none"><line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5"/><line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5"/><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text></g><script><![CDATA[
|
|
4
|
+
document.currentScript.parentElement.addEventListener('mousemove', (e) => {
|
|
5
|
+
const svg = e.currentTarget;
|
|
6
|
+
const rect = svg.getBoundingClientRect();
|
|
7
|
+
const x = e.clientX - rect.left;
|
|
8
|
+
const y = e.clientY - rect.top;
|
|
9
|
+
const crosshair = svg.getElementById('crosshair');
|
|
10
|
+
const h = svg.getElementById('crosshair-h');
|
|
11
|
+
const v = svg.getElementById('crosshair-v');
|
|
12
|
+
const coords = svg.getElementById('coordinates');
|
|
13
|
+
|
|
14
|
+
crosshair.style.display = 'block';
|
|
15
|
+
h.setAttribute('x1', '0');
|
|
16
|
+
h.setAttribute('x2', '640');
|
|
17
|
+
h.setAttribute('y1', y);
|
|
18
|
+
h.setAttribute('y2', y);
|
|
19
|
+
v.setAttribute('x1', x);
|
|
20
|
+
v.setAttribute('x2', x);
|
|
21
|
+
v.setAttribute('y1', '0');
|
|
22
|
+
v.setAttribute('y2', '640');
|
|
23
|
+
|
|
24
|
+
// Calculate real coordinates using inverse transformation
|
|
25
|
+
const matrix = {"a":554.4554455445545,"c":0,"e":-1623.3663366336636,"b":0,"d":-554.4554455445545,"f":358.81188118811883};
|
|
26
|
+
// Manually invert and apply the affine transform
|
|
27
|
+
// Since we only use translate and scale, we can directly compute:
|
|
28
|
+
// x' = (x - tx) / sx
|
|
29
|
+
// y' = (y - ty) / sy
|
|
30
|
+
const sx = matrix.a;
|
|
31
|
+
const sy = matrix.d;
|
|
32
|
+
const tx = matrix.e;
|
|
33
|
+
const ty = matrix.f;
|
|
34
|
+
const realPoint = {
|
|
35
|
+
x: (x - tx) / sx,
|
|
36
|
+
y: (y - ty) / sy // Flip y back since we used negative scale
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
coords.textContent = `(${realPoint.x.toFixed(2)}, ${realPoint.y.toFixed(2)})`;
|
|
40
|
+
coords.setAttribute('x', (x + 5).toString());
|
|
41
|
+
coords.setAttribute('y', (y - 5).toString());
|
|
42
|
+
});
|
|
43
|
+
document.currentScript.parentElement.addEventListener('mouseleave', () => {
|
|
44
|
+
document.currentScript.parentElement.getElementById('crosshair').style.display = 'none';
|
|
45
|
+
});
|
|
46
|
+
]]></script></svg>
|