circuit-json-to-lbrn 0.0.16 → 0.0.18

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.
Files changed (34) hide show
  1. package/dist/index.js +478 -2
  2. package/lib/element-handlers/addPcbCutout/addCirclePcbCutout.ts +52 -0
  3. package/lib/element-handlers/addPcbCutout/addPathPcbCutout.ts +73 -0
  4. package/lib/element-handlers/addPcbCutout/addPolygonPcbCutout.ts +68 -0
  5. package/lib/element-handlers/addPcbCutout/addRectPcbCutout.ts +71 -0
  6. package/lib/element-handlers/addPcbCutout/index.ts +31 -0
  7. package/lib/element-handlers/addPcbHole/addCirclePcbHole.ts +51 -0
  8. package/lib/element-handlers/addPcbHole/addOvalPcbHole.ts +59 -0
  9. package/lib/element-handlers/addPcbHole/addPillPcbHole.ts +59 -0
  10. package/lib/element-handlers/addPcbHole/addRectPcbHole.ts +61 -0
  11. package/lib/element-handlers/addPcbHole/addRotatedPillPcbHole.ts +62 -0
  12. package/lib/element-handlers/addPcbHole/index.ts +36 -0
  13. package/lib/index.ts +10 -0
  14. package/package.json +1 -1
  15. package/tests/examples/addPcbCutout/__snapshots__/pcb-cutout-circle.snap.svg +8 -0
  16. package/tests/examples/addPcbCutout/__snapshots__/pcb-cutout-path.snap.svg +8 -0
  17. package/tests/examples/addPcbCutout/__snapshots__/pcb-cutout-polygon.snap.svg +8 -0
  18. package/tests/examples/addPcbCutout/__snapshots__/pcb-cutout-rect.snap.svg +8 -0
  19. package/tests/examples/addPcbCutout/pcb-cutout-circle.test.ts +49 -0
  20. package/tests/examples/addPcbCutout/pcb-cutout-path.test.ts +44 -0
  21. package/tests/examples/addPcbCutout/pcb-cutout-polygon.test.ts +54 -0
  22. package/tests/examples/addPcbCutout/pcb-cutout-rect.test.ts +48 -0
  23. package/tests/examples/addPcbHole/__snapshots__/pcb-hole-circle.snap.svg +8 -0
  24. package/tests/examples/addPcbHole/__snapshots__/pcb-hole-oval.snap.svg +8 -0
  25. package/tests/examples/addPcbHole/__snapshots__/pcb-hole-pill.snap.svg +8 -0
  26. package/tests/examples/addPcbHole/__snapshots__/pcb-hole-rect.snap.svg +8 -0
  27. package/tests/examples/addPcbHole/__snapshots__/pcb-hole-rotated-pill.snap.svg +8 -0
  28. package/tests/examples/addPcbHole/__snapshots__/pcb-hole-with-soldermask.snap.svg +8 -0
  29. package/tests/examples/addPcbHole/pcb-hole-circle.test.ts +47 -0
  30. package/tests/examples/addPcbHole/pcb-hole-oval.test.ts +49 -0
  31. package/tests/examples/addPcbHole/pcb-hole-pill.test.ts +49 -0
  32. package/tests/examples/addPcbHole/pcb-hole-rect.test.ts +49 -0
  33. package/tests/examples/addPcbHole/pcb-hole-rotated-pill.test.ts +61 -0
  34. package/tests/examples/addPcbHole/pcb-hole-with-soldermask.test.ts +60 -0
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // lib/index.ts
2
- import { LightBurnProject, CutSetting, ShapePath as ShapePath16 } from "lbrnts";
2
+ import { LightBurnProject, CutSetting, ShapePath as ShapePath25 } from "lbrnts";
3
3
  import { cju as cju2 } from "@tscircuit/circuit-json-util";
4
4
 
5
5
  // lib/element-handlers/addPlatedHole/addCirclePlatedHole.ts
@@ -1436,6 +1436,476 @@ var addPcbVia = (via, ctx) => {
1436
1436
  }
1437
1437
  };
1438
1438
 
1439
+ // lib/element-handlers/addPcbHole/addCirclePcbHole.ts
1440
+ import { ShapePath as ShapePath16 } from "lbrnts";
1441
+ var addCirclePcbHole = (hole, ctx) => {
1442
+ const {
1443
+ project,
1444
+ throughBoardCutSetting,
1445
+ soldermaskCutSetting,
1446
+ origin,
1447
+ includeSoldermask,
1448
+ soldermaskMargin
1449
+ } = ctx;
1450
+ const centerX = hole.x + origin.x;
1451
+ const centerY = hole.y + origin.y;
1452
+ if (hole.hole_diameter > 0 && includeSoldermask) {
1453
+ const smRadius = hole.hole_diameter / 2 + soldermaskMargin;
1454
+ const soldermaskPath = createCirclePath(centerX, centerY, smRadius);
1455
+ project.children.push(
1456
+ new ShapePath16({
1457
+ cutIndex: soldermaskCutSetting.index,
1458
+ verts: soldermaskPath.verts,
1459
+ prims: soldermaskPath.prims,
1460
+ isClosed: true
1461
+ })
1462
+ );
1463
+ }
1464
+ if (hole.hole_diameter > 0) {
1465
+ const radius = hole.hole_diameter / 2;
1466
+ const circlePath = createCirclePath(centerX, centerY, radius);
1467
+ project.children.push(
1468
+ new ShapePath16({
1469
+ cutIndex: throughBoardCutSetting.index,
1470
+ verts: circlePath.verts,
1471
+ prims: circlePath.prims,
1472
+ isClosed: true
1473
+ })
1474
+ );
1475
+ }
1476
+ };
1477
+
1478
+ // lib/element-handlers/addPcbHole/addRectPcbHole.ts
1479
+ import { ShapePath as ShapePath17 } from "lbrnts";
1480
+ var addRectPcbHole = (hole, ctx) => {
1481
+ const {
1482
+ project,
1483
+ throughBoardCutSetting,
1484
+ soldermaskCutSetting,
1485
+ origin,
1486
+ includeSoldermask,
1487
+ soldermaskMargin
1488
+ } = ctx;
1489
+ const centerX = hole.x + origin.x;
1490
+ const centerY = hole.y + origin.y;
1491
+ if (hole.hole_width > 0 && hole.hole_height > 0 && includeSoldermask) {
1492
+ const soldermaskPath = createRoundedRectPath(
1493
+ centerX,
1494
+ centerY,
1495
+ hole.hole_width + soldermaskMargin * 2,
1496
+ hole.hole_height + soldermaskMargin * 2,
1497
+ 0
1498
+ // no border radius for rect holes
1499
+ );
1500
+ project.children.push(
1501
+ new ShapePath17({
1502
+ cutIndex: soldermaskCutSetting.index,
1503
+ verts: soldermaskPath.verts,
1504
+ prims: soldermaskPath.prims,
1505
+ isClosed: true
1506
+ })
1507
+ );
1508
+ }
1509
+ if (hole.hole_width > 0 && hole.hole_height > 0) {
1510
+ const rectPath = createRoundedRectPath(
1511
+ centerX,
1512
+ centerY,
1513
+ hole.hole_width,
1514
+ hole.hole_height,
1515
+ 0
1516
+ // no border radius for rect holes
1517
+ );
1518
+ project.children.push(
1519
+ new ShapePath17({
1520
+ cutIndex: throughBoardCutSetting.index,
1521
+ verts: rectPath.verts,
1522
+ prims: rectPath.prims,
1523
+ isClosed: true
1524
+ })
1525
+ );
1526
+ }
1527
+ };
1528
+
1529
+ // lib/element-handlers/addPcbHole/addOvalPcbHole.ts
1530
+ import { ShapePath as ShapePath18 } from "lbrnts";
1531
+ var addOvalPcbHole = (hole, ctx) => {
1532
+ const {
1533
+ project,
1534
+ throughBoardCutSetting,
1535
+ soldermaskCutSetting,
1536
+ origin,
1537
+ includeSoldermask,
1538
+ soldermaskMargin
1539
+ } = ctx;
1540
+ const centerX = hole.x + origin.x;
1541
+ const centerY = hole.y + origin.y;
1542
+ if (hole.hole_width > 0 && hole.hole_height > 0 && includeSoldermask) {
1543
+ const soldermaskPath = createOvalPath(
1544
+ centerX,
1545
+ centerY,
1546
+ hole.hole_width + soldermaskMargin * 2,
1547
+ hole.hole_height + soldermaskMargin * 2
1548
+ );
1549
+ project.children.push(
1550
+ new ShapePath18({
1551
+ cutIndex: soldermaskCutSetting.index,
1552
+ verts: soldermaskPath.verts,
1553
+ prims: soldermaskPath.prims,
1554
+ isClosed: true
1555
+ })
1556
+ );
1557
+ }
1558
+ if (hole.hole_width > 0 && hole.hole_height > 0) {
1559
+ const ovalPath = createOvalPath(
1560
+ centerX,
1561
+ centerY,
1562
+ hole.hole_width,
1563
+ hole.hole_height
1564
+ );
1565
+ project.children.push(
1566
+ new ShapePath18({
1567
+ cutIndex: throughBoardCutSetting.index,
1568
+ verts: ovalPath.verts,
1569
+ prims: ovalPath.prims,
1570
+ isClosed: true
1571
+ })
1572
+ );
1573
+ }
1574
+ };
1575
+
1576
+ // lib/element-handlers/addPcbHole/addPillPcbHole.ts
1577
+ import { ShapePath as ShapePath19 } from "lbrnts";
1578
+ var addPillPcbHole = (hole, ctx) => {
1579
+ const {
1580
+ project,
1581
+ throughBoardCutSetting,
1582
+ soldermaskCutSetting,
1583
+ origin,
1584
+ includeSoldermask,
1585
+ soldermaskMargin
1586
+ } = ctx;
1587
+ const centerX = hole.x + origin.x;
1588
+ const centerY = hole.y + origin.y;
1589
+ if (hole.hole_width > 0 && hole.hole_height > 0 && includeSoldermask) {
1590
+ const soldermaskPath = createPillPath(
1591
+ centerX,
1592
+ centerY,
1593
+ hole.hole_width + soldermaskMargin * 2,
1594
+ hole.hole_height + soldermaskMargin * 2
1595
+ );
1596
+ project.children.push(
1597
+ new ShapePath19({
1598
+ cutIndex: soldermaskCutSetting.index,
1599
+ verts: soldermaskPath.verts,
1600
+ prims: soldermaskPath.prims,
1601
+ isClosed: true
1602
+ })
1603
+ );
1604
+ }
1605
+ if (hole.hole_width > 0 && hole.hole_height > 0) {
1606
+ const pillPath = createPillPath(
1607
+ centerX,
1608
+ centerY,
1609
+ hole.hole_width,
1610
+ hole.hole_height
1611
+ );
1612
+ project.children.push(
1613
+ new ShapePath19({
1614
+ cutIndex: throughBoardCutSetting.index,
1615
+ verts: pillPath.verts,
1616
+ prims: pillPath.prims,
1617
+ isClosed: true
1618
+ })
1619
+ );
1620
+ }
1621
+ };
1622
+
1623
+ // lib/element-handlers/addPcbHole/addRotatedPillPcbHole.ts
1624
+ import { ShapePath as ShapePath20 } from "lbrnts";
1625
+ var addRotatedPillPcbHole = (hole, ctx) => {
1626
+ const {
1627
+ project,
1628
+ throughBoardCutSetting,
1629
+ soldermaskCutSetting,
1630
+ origin,
1631
+ includeSoldermask,
1632
+ soldermaskMargin
1633
+ } = ctx;
1634
+ const centerX = hole.x + origin.x;
1635
+ const centerY = hole.y + origin.y;
1636
+ const rotation = (hole.ccw_rotation || 0) * (Math.PI / 180);
1637
+ if (hole.hole_width > 0 && hole.hole_height > 0 && includeSoldermask) {
1638
+ const soldermaskPath = createPillPath(
1639
+ centerX,
1640
+ centerY,
1641
+ hole.hole_width + soldermaskMargin * 2,
1642
+ hole.hole_height + soldermaskMargin * 2,
1643
+ rotation
1644
+ );
1645
+ project.children.push(
1646
+ new ShapePath20({
1647
+ cutIndex: soldermaskCutSetting.index,
1648
+ verts: soldermaskPath.verts,
1649
+ prims: soldermaskPath.prims,
1650
+ isClosed: true
1651
+ })
1652
+ );
1653
+ }
1654
+ if (hole.hole_width > 0 && hole.hole_height > 0) {
1655
+ const pillPath = createPillPath(
1656
+ centerX,
1657
+ centerY,
1658
+ hole.hole_width,
1659
+ hole.hole_height,
1660
+ rotation
1661
+ );
1662
+ project.children.push(
1663
+ new ShapePath20({
1664
+ cutIndex: throughBoardCutSetting.index,
1665
+ verts: pillPath.verts,
1666
+ prims: pillPath.prims,
1667
+ isClosed: true
1668
+ })
1669
+ );
1670
+ }
1671
+ };
1672
+
1673
+ // lib/element-handlers/addPcbHole/index.ts
1674
+ var addPcbHole = (hole, ctx) => {
1675
+ switch (hole.hole_shape) {
1676
+ case "circle":
1677
+ case "square":
1678
+ return addCirclePcbHole(hole, ctx);
1679
+ case "rect":
1680
+ return addRectPcbHole(hole, ctx);
1681
+ case "oval":
1682
+ return addOvalPcbHole(hole, ctx);
1683
+ case "pill":
1684
+ return addPillPcbHole(hole, ctx);
1685
+ case "rotated_pill":
1686
+ return addRotatedPillPcbHole(hole, ctx);
1687
+ default:
1688
+ const _exhaustive = hole;
1689
+ console.warn(`Unknown hole shape: ${hole.hole_shape}`);
1690
+ }
1691
+ };
1692
+
1693
+ // lib/element-handlers/addPcbCutout/addCirclePcbCutout.ts
1694
+ import { ShapePath as ShapePath21 } from "lbrnts";
1695
+ var addCirclePcbCutout = (cutout, ctx) => {
1696
+ const {
1697
+ project,
1698
+ throughBoardCutSetting,
1699
+ origin,
1700
+ includeCopper,
1701
+ includeSoldermask,
1702
+ soldermaskMargin,
1703
+ soldermaskCutSetting
1704
+ } = ctx;
1705
+ const centerX = cutout.center.x + origin.x;
1706
+ const centerY = cutout.center.y + origin.y;
1707
+ if (cutout.radius > 0 && includeCopper) {
1708
+ const circlePath = createCirclePath(centerX, centerY, cutout.radius);
1709
+ project.children.push(
1710
+ new ShapePath21({
1711
+ cutIndex: throughBoardCutSetting.index,
1712
+ verts: circlePath.verts,
1713
+ prims: circlePath.prims,
1714
+ isClosed: true
1715
+ })
1716
+ );
1717
+ }
1718
+ if (cutout.radius > 0 && includeSoldermask) {
1719
+ const smRadius = cutout.radius + soldermaskMargin;
1720
+ const outer = createCirclePath(centerX, centerY, smRadius);
1721
+ project.children.push(
1722
+ new ShapePath21({
1723
+ cutIndex: soldermaskCutSetting.index,
1724
+ verts: outer.verts,
1725
+ prims: outer.prims,
1726
+ isClosed: true
1727
+ })
1728
+ );
1729
+ }
1730
+ };
1731
+
1732
+ // lib/element-handlers/addPcbCutout/addRectPcbCutout.ts
1733
+ import { ShapePath as ShapePath22 } from "lbrnts";
1734
+ var addRectPcbCutout = (cutout, ctx) => {
1735
+ const {
1736
+ project,
1737
+ throughBoardCutSetting,
1738
+ origin,
1739
+ includeCopper,
1740
+ includeSoldermask,
1741
+ soldermaskMargin,
1742
+ soldermaskCutSetting
1743
+ } = ctx;
1744
+ const centerX = cutout.center.x + origin.x;
1745
+ const centerY = cutout.center.y + origin.y;
1746
+ if (cutout.width > 0 && cutout.height > 0 && includeCopper) {
1747
+ const rotation = (cutout.rotation ?? 0) * (Math.PI / 180);
1748
+ const rectPath = createRoundedRectPath(
1749
+ centerX,
1750
+ centerY,
1751
+ cutout.width,
1752
+ cutout.height,
1753
+ 0,
1754
+ // no border radius for cutouts
1755
+ 4,
1756
+ // segments
1757
+ rotation
1758
+ );
1759
+ project.children.push(
1760
+ new ShapePath22({
1761
+ cutIndex: throughBoardCutSetting.index,
1762
+ verts: rectPath.verts,
1763
+ prims: rectPath.prims,
1764
+ isClosed: true
1765
+ })
1766
+ );
1767
+ }
1768
+ if (cutout.width > 0 && cutout.height > 0 && includeSoldermask) {
1769
+ const rotation = (cutout.rotation ?? 0) * (Math.PI / 180);
1770
+ const smWidth = cutout.width + 2 * soldermaskMargin;
1771
+ const smHeight = cutout.height + 2 * soldermaskMargin;
1772
+ const rectPath = createRoundedRectPath(
1773
+ centerX,
1774
+ centerY,
1775
+ smWidth,
1776
+ smHeight,
1777
+ 0,
1778
+ // no border radius for cutouts
1779
+ 4,
1780
+ // segments
1781
+ rotation
1782
+ );
1783
+ project.children.push(
1784
+ new ShapePath22({
1785
+ cutIndex: soldermaskCutSetting.index,
1786
+ verts: rectPath.verts,
1787
+ prims: rectPath.prims,
1788
+ isClosed: true
1789
+ })
1790
+ );
1791
+ }
1792
+ };
1793
+
1794
+ // lib/element-handlers/addPcbCutout/addPolygonPcbCutout.ts
1795
+ import { ShapePath as ShapePath23 } from "lbrnts";
1796
+ var addPolygonPcbCutout = (cutout, ctx) => {
1797
+ const {
1798
+ project,
1799
+ throughBoardCutSetting,
1800
+ origin,
1801
+ includeCopper,
1802
+ includeSoldermask,
1803
+ soldermaskCutSetting,
1804
+ soldermaskMargin
1805
+ } = ctx;
1806
+ if (cutout.points.length >= 3 && includeCopper) {
1807
+ const polygonPath = createPolygonPathFromOutline(
1808
+ cutout.points,
1809
+ origin.x,
1810
+ origin.y
1811
+ );
1812
+ project.children.push(
1813
+ new ShapePath23({
1814
+ cutIndex: throughBoardCutSetting.index,
1815
+ verts: polygonPath.verts,
1816
+ prims: polygonPath.prims,
1817
+ isClosed: true
1818
+ })
1819
+ );
1820
+ }
1821
+ if (cutout.points.length >= 3 && includeSoldermask) {
1822
+ const points = soldermaskMargin && soldermaskMargin > 0 ? cutout.points.map((p) => ({
1823
+ x: (p.x ?? 0) + (p.x ?? 0) > 0 ? soldermaskMargin : -soldermaskMargin,
1824
+ y: (p.y ?? 0) + (p.y ?? 0) > 0 ? soldermaskMargin : -soldermaskMargin
1825
+ })) : cutout.points;
1826
+ const polygonPath = createPolygonPathFromOutline(points, origin.x, origin.y);
1827
+ project.children.push(
1828
+ new ShapePath23({
1829
+ cutIndex: soldermaskCutSetting.index,
1830
+ verts: polygonPath.verts,
1831
+ prims: polygonPath.prims,
1832
+ isClosed: true
1833
+ })
1834
+ );
1835
+ }
1836
+ };
1837
+
1838
+ // lib/element-handlers/addPcbCutout/addPathPcbCutout.ts
1839
+ import { ShapePath as ShapePath24 } from "lbrnts";
1840
+ var addPathPcbCutout = (cutout, ctx) => {
1841
+ const {
1842
+ project,
1843
+ throughBoardCutSetting,
1844
+ origin,
1845
+ includeCopper,
1846
+ includeSoldermask,
1847
+ soldermaskMargin,
1848
+ soldermaskCutSetting
1849
+ } = ctx;
1850
+ if (cutout.route.length >= 2 && includeCopper) {
1851
+ const verts = [];
1852
+ const prims = [];
1853
+ for (const point6 of cutout.route) {
1854
+ verts.push({
1855
+ x: point6.x + origin.x,
1856
+ y: point6.y + origin.y
1857
+ });
1858
+ prims.push({ type: 0 });
1859
+ }
1860
+ project.children.push(
1861
+ new ShapePath24({
1862
+ cutIndex: throughBoardCutSetting.index,
1863
+ verts,
1864
+ prims,
1865
+ isClosed: false
1866
+ // Paths are typically not closed
1867
+ })
1868
+ );
1869
+ }
1870
+ if (cutout.route.length >= 2 && includeSoldermask) {
1871
+ const verts = [];
1872
+ const prims = [];
1873
+ for (const point6 of cutout.route) {
1874
+ verts.push({
1875
+ x: point6.x + origin.x,
1876
+ y: point6.y + origin.y
1877
+ });
1878
+ prims.push({ type: 0 });
1879
+ }
1880
+ project.children.push(
1881
+ new ShapePath24({
1882
+ cutIndex: soldermaskCutSetting.index,
1883
+ verts,
1884
+ prims,
1885
+ isClosed: false
1886
+ // Match the same path as the copper cutout
1887
+ })
1888
+ );
1889
+ }
1890
+ };
1891
+
1892
+ // lib/element-handlers/addPcbCutout/index.ts
1893
+ var addPcbCutout = (cutout, ctx) => {
1894
+ switch (cutout.shape) {
1895
+ case "circle":
1896
+ return addCirclePcbCutout(cutout, ctx);
1897
+ case "rect":
1898
+ return addRectPcbCutout(cutout, ctx);
1899
+ case "polygon":
1900
+ return addPolygonPcbCutout(cutout, ctx);
1901
+ case "path":
1902
+ return addPathPcbCutout(cutout, ctx);
1903
+ default:
1904
+ const _exhaustive = cutout;
1905
+ console.warn(`Unknown cutout shape: ${cutout.shape}`);
1906
+ }
1907
+ };
1908
+
1439
1909
  // lib/index.ts
1440
1910
  var convertCircuitJsonToLbrn = (circuitJson, options = {}) => {
1441
1911
  const db = cju2(circuitJson);
@@ -1510,6 +1980,12 @@ var convertCircuitJsonToLbrn = (circuitJson, options = {}) => {
1510
1980
  for (const via of db.pcb_via.list()) {
1511
1981
  addPcbVia(via, ctx);
1512
1982
  }
1983
+ for (const hole of db.pcb_hole.list()) {
1984
+ addPcbHole(hole, ctx);
1985
+ }
1986
+ for (const cutout of db.pcb_cutout.list()) {
1987
+ addPcbCutout(cutout, ctx);
1988
+ }
1513
1989
  if (ctx.includeCopper) {
1514
1990
  for (const net of Object.keys(connMap.netMap)) {
1515
1991
  const netGeoms = ctx.netGeoms.get(net);
@@ -1530,7 +2006,7 @@ var convertCircuitJsonToLbrn = (circuitJson, options = {}) => {
1530
2006
  for (const island of union.splitToIslands()) {
1531
2007
  const { verts, prims } = polygonToShapePathData(island);
1532
2008
  project.children.push(
1533
- new ShapePath16({
2009
+ new ShapePath25({
1534
2010
  cutIndex: copperCutSetting.index,
1535
2011
  verts,
1536
2012
  prims,
@@ -0,0 +1,52 @@
1
+ import type { PcbCutoutCircle } from "circuit-json"
2
+ import type { ConvertContext } from "../../ConvertContext"
3
+ import { ShapePath } from "lbrnts"
4
+ import { createCirclePath } from "../../helpers/circleShape"
5
+
6
+ /**
7
+ * Adds a circular PCB cutout to the project
8
+ * Cutouts are regions removed from the board entirely
9
+ */
10
+ export const addCirclePcbCutout = (
11
+ cutout: PcbCutoutCircle,
12
+ ctx: ConvertContext,
13
+ ): void => {
14
+ const {
15
+ project,
16
+ throughBoardCutSetting,
17
+ origin,
18
+ includeCopper,
19
+ includeSoldermask,
20
+ soldermaskMargin,
21
+ soldermaskCutSetting,
22
+ } = ctx
23
+ const centerX = cutout.center.x + origin.x
24
+ const centerY = cutout.center.y + origin.y
25
+
26
+ // Add the cutout - cut through the board
27
+ if (cutout.radius > 0 && includeCopper) {
28
+ const circlePath = createCirclePath(centerX, centerY, cutout.radius)
29
+ project.children.push(
30
+ new ShapePath({
31
+ cutIndex: throughBoardCutSetting.index,
32
+ verts: circlePath.verts,
33
+ prims: circlePath.prims,
34
+ isClosed: true,
35
+ }),
36
+ )
37
+ }
38
+
39
+ // Add soldermask opening if drawing soldermask
40
+ if (cutout.radius > 0 && includeSoldermask) {
41
+ const smRadius = cutout.radius + soldermaskMargin
42
+ const outer = createCirclePath(centerX, centerY, smRadius)
43
+ project.children.push(
44
+ new ShapePath({
45
+ cutIndex: soldermaskCutSetting.index,
46
+ verts: outer.verts,
47
+ prims: outer.prims,
48
+ isClosed: true,
49
+ }),
50
+ )
51
+ }
52
+ }
@@ -0,0 +1,73 @@
1
+ import type { PcbCutoutPath } from "circuit-json"
2
+ import type { ConvertContext } from "../../ConvertContext"
3
+ import { ShapePath } from "lbrnts"
4
+
5
+ /**
6
+ * Adds a path-based PCB cutout to the project
7
+ * Path cutouts create slots along a route (e.g., for routed slots or complex cutout paths)
8
+ */
9
+ export const addPathPcbCutout = (
10
+ cutout: PcbCutoutPath,
11
+ ctx: ConvertContext,
12
+ ): void => {
13
+ const {
14
+ project,
15
+ throughBoardCutSetting,
16
+ origin,
17
+ includeCopper,
18
+ includeSoldermask,
19
+ soldermaskMargin,
20
+ soldermaskCutSetting,
21
+ } = ctx
22
+
23
+ // For path cutouts, we need to create a stroke along the route with the given width
24
+ // For simplicity, we'll convert the path to a polyline
25
+ if (cutout.route.length >= 2 && includeCopper) {
26
+ const verts: { x: number; y: number }[] = []
27
+ const prims: { type: number }[] = []
28
+
29
+ for (const point of cutout.route) {
30
+ verts.push({
31
+ x: point.x + origin.x,
32
+ y: point.y + origin.y,
33
+ })
34
+ prims.push({ type: 0 })
35
+ }
36
+
37
+ // Note: slot_width, slot_length, space_between_slots, and slot_corner_radius
38
+ // would require more complex path offsetting and aren't currently implemented
39
+ // For now, we just create a simple path along the route
40
+ project.children.push(
41
+ new ShapePath({
42
+ cutIndex: throughBoardCutSetting.index,
43
+ verts,
44
+ prims,
45
+ isClosed: false, // Paths are typically not closed
46
+ }),
47
+ )
48
+ }
49
+
50
+ // Add soldermask opening if drawing soldermask
51
+ if (cutout.route.length >= 2 && includeSoldermask) {
52
+ const verts: { x: number; y: number }[] = []
53
+ const prims: { type: number }[] = []
54
+
55
+ // Create path with soldermask margin
56
+ for (const point of cutout.route) {
57
+ verts.push({
58
+ x: point.x + origin.x,
59
+ y: point.y + origin.y,
60
+ })
61
+ prims.push({ type: 0 })
62
+ }
63
+
64
+ project.children.push(
65
+ new ShapePath({
66
+ cutIndex: soldermaskCutSetting.index,
67
+ verts,
68
+ prims,
69
+ isClosed: false, // Match the same path as the copper cutout
70
+ }),
71
+ )
72
+ }
73
+ }
@@ -0,0 +1,68 @@
1
+ import type { PcbCutoutPolygon } from "circuit-json"
2
+ import type { ConvertContext } from "../../ConvertContext"
3
+ import { ShapePath } from "lbrnts"
4
+ import { createPolygonPathFromOutline } from "../../helpers/polygonShape"
5
+
6
+ /**
7
+ * Adds a polygon PCB cutout to the project
8
+ * Cutouts are regions removed from the board entirely
9
+ */
10
+ export const addPolygonPcbCutout = (
11
+ cutout: PcbCutoutPolygon,
12
+ ctx: ConvertContext,
13
+ ): void => {
14
+ const {
15
+ project,
16
+ throughBoardCutSetting,
17
+ origin,
18
+ includeCopper,
19
+ includeSoldermask,
20
+ soldermaskCutSetting,
21
+ soldermaskMargin,
22
+ } = ctx
23
+
24
+ // Add the cutout - cut through the board
25
+ if (cutout.points.length >= 3 && includeCopper) {
26
+ const polygonPath = createPolygonPathFromOutline(
27
+ cutout.points,
28
+ origin.x,
29
+ origin.y,
30
+ )
31
+ project.children.push(
32
+ new ShapePath({
33
+ cutIndex: throughBoardCutSetting.index,
34
+ verts: polygonPath.verts,
35
+ prims: polygonPath.prims,
36
+ isClosed: true,
37
+ }),
38
+ )
39
+ }
40
+
41
+ // Add soldermask opening if drawing soldermask
42
+ if (cutout.points.length >= 3 && includeSoldermask) {
43
+ // Apply soldermask margin to the points if margin is specified
44
+ const points =
45
+ soldermaskMargin && soldermaskMargin > 0
46
+ ? cutout.points.map((p) => ({
47
+ x:
48
+ (p.x ?? 0) + (p.x ?? 0) > 0
49
+ ? soldermaskMargin
50
+ : -soldermaskMargin,
51
+ y:
52
+ (p.y ?? 0) + (p.y ?? 0) > 0
53
+ ? soldermaskMargin
54
+ : -soldermaskMargin,
55
+ }))
56
+ : cutout.points
57
+
58
+ const polygonPath = createPolygonPathFromOutline(points, origin.x, origin.y)
59
+ project.children.push(
60
+ new ShapePath({
61
+ cutIndex: soldermaskCutSetting.index,
62
+ verts: polygonPath.verts,
63
+ prims: polygonPath.prims,
64
+ isClosed: true,
65
+ }),
66
+ )
67
+ }
68
+ }