@tscircuit/checks 0.0.125 → 0.0.126
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 +205 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -243,7 +243,7 @@ import {
|
|
|
243
243
|
segmentToBoundsMinDistance,
|
|
244
244
|
segmentToCircleMinDistance
|
|
245
245
|
} from "@tscircuit/math-utils";
|
|
246
|
-
import { segmentToSegmentMinDistance } from "@tscircuit/math-utils";
|
|
246
|
+
import { segmentToSegmentMinDistance as segmentToSegmentMinDistance2 } from "@tscircuit/math-utils";
|
|
247
247
|
import {
|
|
248
248
|
getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson2
|
|
249
249
|
} from "circuit-json-to-connectivity-map";
|
|
@@ -636,6 +636,156 @@ var clamp = (value, min, max) => {
|
|
|
636
636
|
|
|
637
637
|
// lib/check-each-pcb-trace-non-overlapping/getCollidableBounds.ts
|
|
638
638
|
import { getBoundsOfPcbElements as getBoundsOfPcbElements2 } from "@tscircuit/circuit-json-util";
|
|
639
|
+
|
|
640
|
+
// lib/check-each-pcb-trace-non-overlapping/segment-to-polygon-clearance.ts
|
|
641
|
+
import {
|
|
642
|
+
distSq,
|
|
643
|
+
getSegmentIntersection,
|
|
644
|
+
isPointInsidePolygon,
|
|
645
|
+
pointToSegmentClosestPoint,
|
|
646
|
+
segmentToSegmentMinDistance
|
|
647
|
+
} from "@tscircuit/math-utils";
|
|
648
|
+
var rotatePoint = (point, angleDegrees) => {
|
|
649
|
+
const angle = angleDegrees * Math.PI / 180;
|
|
650
|
+
return {
|
|
651
|
+
x: point.x * Math.cos(angle) - point.y * Math.sin(angle),
|
|
652
|
+
y: point.x * Math.sin(angle) + point.y * Math.cos(angle)
|
|
653
|
+
};
|
|
654
|
+
};
|
|
655
|
+
var getRotatedRectPoints = ({
|
|
656
|
+
x,
|
|
657
|
+
y,
|
|
658
|
+
width,
|
|
659
|
+
height,
|
|
660
|
+
ccwRotation
|
|
661
|
+
}) => {
|
|
662
|
+
const halfWidth = width / 2;
|
|
663
|
+
const halfHeight = height / 2;
|
|
664
|
+
return [
|
|
665
|
+
{ x: -halfWidth, y: -halfHeight },
|
|
666
|
+
{ x: halfWidth, y: -halfHeight },
|
|
667
|
+
{ x: halfWidth, y: halfHeight },
|
|
668
|
+
{ x: -halfWidth, y: halfHeight }
|
|
669
|
+
].map((point) => {
|
|
670
|
+
const rotated = rotatePoint(point, ccwRotation);
|
|
671
|
+
return { x: x + rotated.x, y: y + rotated.y };
|
|
672
|
+
});
|
|
673
|
+
};
|
|
674
|
+
var getPolygonPointsForPad = (pad) => {
|
|
675
|
+
if (pad.type === "pcb_smtpad") {
|
|
676
|
+
if (pad.shape === "polygon") return pad.points;
|
|
677
|
+
if (pad.shape === "rotated_rect") {
|
|
678
|
+
return getRotatedRectPoints({
|
|
679
|
+
x: pad.x,
|
|
680
|
+
y: pad.y,
|
|
681
|
+
width: pad.width,
|
|
682
|
+
height: pad.height,
|
|
683
|
+
ccwRotation: pad.ccw_rotation
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (pad.type === "pcb_plated_hole" && "rect_pad_width" in pad && "rect_pad_height" in pad) {
|
|
688
|
+
return getRotatedRectPoints({
|
|
689
|
+
x: pad.x,
|
|
690
|
+
y: pad.y,
|
|
691
|
+
width: pad.rect_pad_width,
|
|
692
|
+
height: pad.rect_pad_height,
|
|
693
|
+
ccwRotation: "rect_ccw_rotation" in pad && typeof pad.rect_ccw_rotation === "number" ? pad.rect_ccw_rotation : 0
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
throw new Error(
|
|
697
|
+
`Expected polygonal pad geometry, got ${pad.type} with shape "${pad.shape}"`
|
|
698
|
+
);
|
|
699
|
+
};
|
|
700
|
+
var getPolygonEdges = (points) => points.map(
|
|
701
|
+
(point, index) => [point, points[(index + 1) % points.length]]
|
|
702
|
+
);
|
|
703
|
+
var getClosestPointsBetweenSegments = (a1, a2, b1, b2) => {
|
|
704
|
+
const intersection = getSegmentIntersection(a1, a2, b1, b2);
|
|
705
|
+
if (intersection) {
|
|
706
|
+
return {
|
|
707
|
+
distance: 0,
|
|
708
|
+
pointOnA: intersection,
|
|
709
|
+
pointOnB: intersection,
|
|
710
|
+
center: intersection
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
const candidates = [
|
|
714
|
+
{ pointOnA: a1, pointOnB: pointToSegmentClosestPoint(a1, b1, b2) },
|
|
715
|
+
{ pointOnA: a2, pointOnB: pointToSegmentClosestPoint(a2, b1, b2) },
|
|
716
|
+
{ pointOnA: pointToSegmentClosestPoint(b1, a1, a2), pointOnB: b1 },
|
|
717
|
+
{ pointOnA: pointToSegmentClosestPoint(b2, a1, a2), pointOnB: b2 }
|
|
718
|
+
];
|
|
719
|
+
let best = candidates[0];
|
|
720
|
+
let bestDistanceSquared = distSq(best.pointOnA, best.pointOnB);
|
|
721
|
+
for (const candidate of candidates.slice(1)) {
|
|
722
|
+
const candidateDistanceSquared = distSq(
|
|
723
|
+
candidate.pointOnA,
|
|
724
|
+
candidate.pointOnB
|
|
725
|
+
);
|
|
726
|
+
if (candidateDistanceSquared < bestDistanceSquared) {
|
|
727
|
+
best = candidate;
|
|
728
|
+
bestDistanceSquared = candidateDistanceSquared;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return {
|
|
732
|
+
distance: segmentToSegmentMinDistance(a1, a2, b1, b2),
|
|
733
|
+
pointOnA: best.pointOnA,
|
|
734
|
+
pointOnB: best.pointOnB,
|
|
735
|
+
center: {
|
|
736
|
+
x: (best.pointOnA.x + best.pointOnB.x) / 2,
|
|
737
|
+
y: (best.pointOnA.y + best.pointOnB.y) / 2
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
};
|
|
741
|
+
var getSegmentToPolygonClearance = (segment, polygon) => {
|
|
742
|
+
const start = { x: segment.x1, y: segment.y1 };
|
|
743
|
+
const end = { x: segment.x2, y: segment.y2 };
|
|
744
|
+
if (polygon.length < 3) {
|
|
745
|
+
return { distance: Number.POSITIVE_INFINITY, center: start };
|
|
746
|
+
}
|
|
747
|
+
const intersections = getPolygonEdges(polygon).map(
|
|
748
|
+
([edgeStart, edgeEnd]) => getSegmentIntersection(start, end, edgeStart, edgeEnd)
|
|
749
|
+
).filter((point) => point !== null);
|
|
750
|
+
if (intersections.length > 0) {
|
|
751
|
+
const dx = end.x - start.x;
|
|
752
|
+
const dy = end.y - start.y;
|
|
753
|
+
const lengthSquared = dx * dx + dy * dy;
|
|
754
|
+
intersections.sort((a, b) => {
|
|
755
|
+
const ta = ((a.x - start.x) * dx + (a.y - start.y) * dy) / lengthSquared;
|
|
756
|
+
const tb = ((b.x - start.x) * dx + (b.y - start.y) * dy) / lengthSquared;
|
|
757
|
+
return ta - tb;
|
|
758
|
+
});
|
|
759
|
+
return { distance: 0, center: intersections[0] };
|
|
760
|
+
}
|
|
761
|
+
if (isPointInsidePolygon(start, polygon) || isPointInsidePolygon(end, polygon)) {
|
|
762
|
+
return {
|
|
763
|
+
distance: 0,
|
|
764
|
+
center: {
|
|
765
|
+
x: (start.x + end.x) / 2,
|
|
766
|
+
y: (start.y + end.y) / 2
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
let best = getClosestPointsBetweenSegments(
|
|
771
|
+
start,
|
|
772
|
+
end,
|
|
773
|
+
polygon[0],
|
|
774
|
+
polygon[1]
|
|
775
|
+
);
|
|
776
|
+
for (const [edgeStart, edgeEnd] of getPolygonEdges(polygon).slice(1)) {
|
|
777
|
+
const candidate = getClosestPointsBetweenSegments(
|
|
778
|
+
start,
|
|
779
|
+
end,
|
|
780
|
+
edgeStart,
|
|
781
|
+
edgeEnd
|
|
782
|
+
);
|
|
783
|
+
if (candidate.distance < best.distance) best = candidate;
|
|
784
|
+
}
|
|
785
|
+
return { distance: best.distance, center: best.center };
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
// lib/check-each-pcb-trace-non-overlapping/getCollidableBounds.ts
|
|
639
789
|
var getCollidableBounds = (collidable) => {
|
|
640
790
|
if (collidable.type === "pcb_trace_segment") {
|
|
641
791
|
return {
|
|
@@ -645,6 +795,18 @@ var getCollidableBounds = (collidable) => {
|
|
|
645
795
|
maxY: Math.max(collidable.y1, collidable.y2)
|
|
646
796
|
};
|
|
647
797
|
}
|
|
798
|
+
if (collidable.type === "pcb_smtpad" || collidable.type === "pcb_plated_hole") {
|
|
799
|
+
const isPolygon = collidable.type === "pcb_smtpad" && (collidable.shape === "rotated_rect" || collidable.shape === "polygon") || collidable.type === "pcb_plated_hole" && "rect_pad_width" in collidable && "rect_pad_height" in collidable;
|
|
800
|
+
if (isPolygon) {
|
|
801
|
+
const polygonPoints = getPolygonPointsForPad(collidable);
|
|
802
|
+
return {
|
|
803
|
+
minX: Math.min(...polygonPoints.map((point) => point.x)),
|
|
804
|
+
minY: Math.min(...polygonPoints.map((point) => point.y)),
|
|
805
|
+
maxX: Math.max(...polygonPoints.map((point) => point.x)),
|
|
806
|
+
maxY: Math.max(...polygonPoints.map((point) => point.y))
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
}
|
|
648
810
|
return getBoundsOfPcbElements2([collidable]);
|
|
649
811
|
};
|
|
650
812
|
|
|
@@ -763,7 +925,7 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
|
|
|
763
925
|
if (segmentA.layer !== segmentB.layer) continue;
|
|
764
926
|
if (connMap.areIdsConnected(segmentA.pcb_trace_id, segmentB.pcb_trace_id))
|
|
765
927
|
continue;
|
|
766
|
-
const gap2 =
|
|
928
|
+
const gap2 = segmentToSegmentMinDistance2(
|
|
767
929
|
{ x: segmentA.x1, y: segmentA.y1 },
|
|
768
930
|
{ x: segmentA.x2, y: segmentA.y2 },
|
|
769
931
|
{ x: segmentB.x1, y: segmentB.y1 },
|
|
@@ -838,6 +1000,40 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
|
|
|
838
1000
|
].filter(Boolean)
|
|
839
1001
|
});
|
|
840
1002
|
}
|
|
1003
|
+
const isPolygon = obj.type === "pcb_smtpad" && (obj.shape === "rotated_rect" || obj.shape === "polygon") || obj.type === "pcb_plated_hole" && "rect_pad_width" in obj && "rect_pad_height" in obj;
|
|
1004
|
+
if (isPolygon) {
|
|
1005
|
+
const padOutline = getPolygonPointsForPad(obj);
|
|
1006
|
+
const { distance: distance4, center } = getSegmentToPolygonClearance(
|
|
1007
|
+
segmentA,
|
|
1008
|
+
padOutline
|
|
1009
|
+
);
|
|
1010
|
+
const gap2 = distance4 - segmentA.thickness / 2;
|
|
1011
|
+
if (gap2 > minClearance - EPSILON) continue;
|
|
1012
|
+
const pcb_trace_error_id = `overlap_${segmentA.pcb_trace_id}_${primaryObjId}`;
|
|
1013
|
+
if (errorIds.has(pcb_trace_error_id)) continue;
|
|
1014
|
+
errorIds.add(pcb_trace_error_id);
|
|
1015
|
+
errors.push({
|
|
1016
|
+
type: "pcb_trace_error",
|
|
1017
|
+
error_type: "pcb_trace_error",
|
|
1018
|
+
message: constructErrorMessage(
|
|
1019
|
+
getReadableName(segmentA.pcb_trace_id),
|
|
1020
|
+
`${obj.type} "${getReadableName(getPrimaryId(obj))}"`,
|
|
1021
|
+
gap2
|
|
1022
|
+
),
|
|
1023
|
+
pcb_trace_id: segmentA.pcb_trace_id,
|
|
1024
|
+
center,
|
|
1025
|
+
source_trace_id: "",
|
|
1026
|
+
pcb_trace_error_id,
|
|
1027
|
+
pcb_component_ids: [
|
|
1028
|
+
"pcb_component_id" in obj ? obj.pcb_component_id : void 0
|
|
1029
|
+
].filter(Boolean),
|
|
1030
|
+
pcb_port_ids: [
|
|
1031
|
+
...getPcbPortIdsConnectedToTraces([segmentA._pcbTrace]),
|
|
1032
|
+
"pcb_port_id" in obj ? obj.pcb_port_id : void 0
|
|
1033
|
+
].filter(Boolean)
|
|
1034
|
+
});
|
|
1035
|
+
continue;
|
|
1036
|
+
}
|
|
841
1037
|
const gap = segmentToBoundsMinDistance(
|
|
842
1038
|
{ x: segmentA.x1, y: segmentA.y1 },
|
|
843
1039
|
{ x: segmentA.x2, y: segmentA.y2 },
|
|
@@ -1041,11 +1237,11 @@ function computeOverlapDistance(compPoly, boardPoly, componentCenter, componentW
|
|
|
1041
1237
|
});
|
|
1042
1238
|
}
|
|
1043
1239
|
const matrix = rotateDEG(rotationDeg, componentCenter.x, componentCenter.y);
|
|
1044
|
-
const
|
|
1240
|
+
const rotatePoint2 = (pt) => {
|
|
1045
1241
|
const p = applyToPoint(matrix, pt);
|
|
1046
1242
|
return new Flatten.Point(p.x, p.y);
|
|
1047
1243
|
};
|
|
1048
|
-
const rotatedPoints = corners.concat(midpoints).map(
|
|
1244
|
+
const rotatedPoints = corners.concat(midpoints).map(rotatePoint2);
|
|
1049
1245
|
let maxDistance = 0;
|
|
1050
1246
|
for (const pt of rotatedPoints) {
|
|
1051
1247
|
if (!boardPoly.contains(pt)) {
|
|
@@ -1420,7 +1616,7 @@ function checkSourceTracesHavePcbTraces(circuitJson) {
|
|
|
1420
1616
|
|
|
1421
1617
|
// lib/check-trace-out-of-board/checkTraceOutOfBoard.ts
|
|
1422
1618
|
import { cju as cju3 } from "@tscircuit/circuit-json-util";
|
|
1423
|
-
import { segmentToSegmentMinDistance as
|
|
1619
|
+
import { segmentToSegmentMinDistance as segmentToSegmentMinDistance3 } from "@tscircuit/math-utils";
|
|
1424
1620
|
function getBoardPolygonPoints(board) {
|
|
1425
1621
|
if (board.outline && board.outline.length > 0) {
|
|
1426
1622
|
return board.outline.map((p) => ({ x: p.x, y: p.y }));
|
|
@@ -1464,7 +1660,7 @@ function checkPcbTracesOutOfBoard(circuitJson, config = {}) {
|
|
|
1464
1660
|
for (let j = 0; j < boardPoints.length; j++) {
|
|
1465
1661
|
const edgeStart = boardPoints[j];
|
|
1466
1662
|
const edgeEnd = boardPoints[(j + 1) % boardPoints.length];
|
|
1467
|
-
const distance4 =
|
|
1663
|
+
const distance4 = segmentToSegmentMinDistance3(
|
|
1468
1664
|
segmentStart,
|
|
1469
1665
|
segmentEnd,
|
|
1470
1666
|
edgeStart,
|
|
@@ -2245,7 +2441,7 @@ function checkConnectorAccessibleOrientation(circuitJson) {
|
|
|
2245
2441
|
// lib/check-courtyard-overlap/checkCourtyardOverlap.ts
|
|
2246
2442
|
import {
|
|
2247
2443
|
doSegmentsIntersect,
|
|
2248
|
-
isPointInsidePolygon
|
|
2444
|
+
isPointInsidePolygon as isPointInsidePolygon2
|
|
2249
2445
|
} from "@tscircuit/math-utils";
|
|
2250
2446
|
function getCourtyardPolygon(el) {
|
|
2251
2447
|
if (el.type === "pcb_courtyard_rect") {
|
|
@@ -2291,8 +2487,8 @@ function getComponentName2(circuitJson, pcbComponentId) {
|
|
|
2291
2487
|
return pcbComponentId;
|
|
2292
2488
|
}
|
|
2293
2489
|
function polygonsOverlap(polyA, polyB) {
|
|
2294
|
-
if (polyA.some((p) =>
|
|
2295
|
-
if (polyB.some((p) =>
|
|
2490
|
+
if (polyA.some((p) => isPointInsidePolygon2(p, polyB))) return true;
|
|
2491
|
+
if (polyB.some((p) => isPointInsidePolygon2(p, polyA))) return true;
|
|
2296
2492
|
for (let i = 0; i < polyA.length; i++) {
|
|
2297
2493
|
const a1 = polyA[i];
|
|
2298
2494
|
const a2 = polyA[(i + 1) % polyA.length];
|