easyeda 0.0.229 → 0.0.231

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.
@@ -14,7 +14,7 @@ import {
14
14
  toSVG,
15
15
  toString,
16
16
  translate
17
- } from "./chunk-6S7LH5YP.js";
17
+ } from "./chunk-U5UUPCU3.js";
18
18
 
19
19
  // node_modules/circuit-to-svg/dist/index.js
20
20
  var import_svgson = __toESM(require_svgson_umd(), 1);
@@ -1560,9 +1560,14 @@ function createSvgObjectsFromPcbNoteLine(noteLine, ctx) {
1560
1560
  return [svgObject];
1561
1561
  }
1562
1562
  function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1563
- const { transform, colorMap: colorMap2 } = ctx;
1563
+ const { transform, colorMap: colorMap2, showSolderMask } = ctx;
1564
1564
  const [x, y] = applyToPoint(transform, [hole.x, hole.y]);
1565
- const copperLayer = Array.isArray(hole.layers) && hole.layers[0] || hole.layer || "top";
1565
+ const layer = Array.isArray(hole.layers) && hole.layers[0] || hole.layer || "top";
1566
+ const maskLayer = layer;
1567
+ const isCoveredWithSolderMask = Boolean(hole.is_covered_with_solder_mask);
1568
+ const soldermaskMargin = (hole.soldermask_margin ?? 0) * Math.abs(transform.a);
1569
+ const shouldShowSolderMask = showSolderMask && isCoveredWithSolderMask && soldermaskMargin !== 0;
1570
+ const solderMaskColor = colorMap2.soldermaskWithCopperUnderneath.top;
1566
1571
  if (hole.shape === "pill") {
1567
1572
  const scaledOuterWidth = hole.outer_width * Math.abs(transform.a);
1568
1573
  const scaledOuterHeight = hole.outer_height * Math.abs(transform.a);
@@ -1585,46 +1590,161 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1585
1590
  return `M${-radius},0 a${radius},${radius} 0 0 1 ${width},0 a${radius},${radius} 0 0 1 ${-width},0 z`;
1586
1591
  }
1587
1592
  };
1588
- return [
1593
+ let children = [
1594
+ // Outer pill shape
1589
1595
  {
1590
- name: "g",
1596
+ name: "path",
1591
1597
  type: "element",
1592
1598
  attributes: {
1599
+ class: "pcb-hole-outer",
1600
+ fill: colorMap2.copper.top,
1601
+ d: createPillPath(scaledOuterWidth, scaledOuterHeight),
1602
+ transform: outerTransform,
1593
1603
  "data-type": "pcb_plated_hole",
1594
- "data-pcb-layer": "through"
1604
+ "data-pcb-layer": layer
1595
1605
  },
1596
- children: [
1597
- // Outer pill shape
1606
+ value: "",
1607
+ children: []
1608
+ },
1609
+ // Inner pill shape
1610
+ {
1611
+ name: "path",
1612
+ type: "element",
1613
+ attributes: {
1614
+ class: "pcb-hole-inner",
1615
+ fill: colorMap2.drill,
1616
+ d: createPillPath(scaledHoleWidth, scaledHoleHeight),
1617
+ transform: innerTransform,
1618
+ "data-type": "pcb_plated_hole_drill",
1619
+ "data-pcb-layer": "drill"
1620
+ },
1621
+ value: "",
1622
+ children: []
1623
+ }
1624
+ ];
1625
+ if (shouldShowSolderMask) {
1626
+ const maskWidth = scaledOuterWidth + 2 * soldermaskMargin;
1627
+ const maskHeight = scaledOuterHeight + 2 * soldermaskMargin;
1628
+ if (soldermaskMargin < 0) {
1629
+ children = [
1630
+ // 1. Draw the outer pad in soldermask color (covered)
1598
1631
  {
1599
1632
  name: "path",
1600
1633
  type: "element",
1601
1634
  attributes: {
1602
- class: "pcb-hole-outer",
1603
- fill: colorMap2.copper.top,
1635
+ class: "pcb-hole-outer-covered",
1636
+ fill: solderMaskColor,
1604
1637
  d: createPillPath(scaledOuterWidth, scaledOuterHeight),
1605
1638
  transform: outerTransform,
1606
1639
  "data-type": "pcb_plated_hole",
1607
- "data-pcb-layer": copperLayer
1640
+ "data-pcb-layer": layer
1608
1641
  },
1609
1642
  value: "",
1610
1643
  children: []
1611
1644
  },
1612
- // Inner pill shape
1645
+ // 2. Draw the exposed opening in copper color
1613
1646
  {
1614
1647
  name: "path",
1615
1648
  type: "element",
1616
1649
  attributes: {
1617
- class: "pcb-hole-inner",
1618
- fill: colorMap2.drill,
1619
- d: createPillPath(scaledHoleWidth, scaledHoleHeight),
1620
- transform: innerTransform,
1621
- "data-type": "pcb_plated_hole_drill",
1622
- "data-pcb-layer": "drill"
1650
+ class: "pcb-hole-outer-exposed",
1651
+ fill: colorMap2.copper.top,
1652
+ d: createPillPath(maskWidth, maskHeight),
1653
+ transform: outerTransform,
1654
+ "data-type": "pcb_soldermask",
1655
+ "data-pcb-layer": maskLayer
1623
1656
  },
1624
1657
  value: "",
1625
1658
  children: []
1626
- }
1627
- ],
1659
+ },
1660
+ // 3. Draw the drill hole on top
1661
+ children[1]
1662
+ // Original inner hole
1663
+ ];
1664
+ } else {
1665
+ children.unshift({
1666
+ name: "path",
1667
+ type: "element",
1668
+ attributes: {
1669
+ class: "pcb-soldermask-cutout",
1670
+ fill: colorMap2.substrate,
1671
+ d: createPillPath(maskWidth, maskHeight),
1672
+ transform: outerTransform,
1673
+ "data-type": "pcb_soldermask_opening",
1674
+ "data-pcb-layer": maskLayer
1675
+ },
1676
+ value: "",
1677
+ children: []
1678
+ });
1679
+ }
1680
+ }
1681
+ return [
1682
+ {
1683
+ name: "g",
1684
+ type: "element",
1685
+ attributes: {
1686
+ "data-type": "pcb_plated_hole",
1687
+ "data-pcb-layer": "through"
1688
+ },
1689
+ children,
1690
+ value: ""
1691
+ }
1692
+ ];
1693
+ }
1694
+ if (hole.shape === "oval") {
1695
+ const scaledOuterWidth = hole.outer_width * Math.abs(transform.a);
1696
+ const scaledOuterHeight = hole.outer_height * Math.abs(transform.a);
1697
+ const scaledHoleWidth = hole.hole_width * Math.abs(transform.a);
1698
+ const scaledHoleHeight = hole.hole_height * Math.abs(transform.a);
1699
+ const rotation = hole.ccw_rotation || 0;
1700
+ const transformStr = rotation ? `translate(${x} ${y}) rotate(${-rotation})` : `translate(${x} ${y})`;
1701
+ const children = [
1702
+ // Outer oval shape
1703
+ {
1704
+ name: "ellipse",
1705
+ type: "element",
1706
+ attributes: {
1707
+ class: "pcb-hole-outer",
1708
+ fill: colorMap2.copper.top,
1709
+ cx: "0",
1710
+ cy: "0",
1711
+ rx: (scaledOuterWidth / 2).toString(),
1712
+ ry: (scaledOuterHeight / 2).toString(),
1713
+ transform: transformStr,
1714
+ "data-type": "pcb_plated_hole",
1715
+ "data-pcb-layer": layer
1716
+ },
1717
+ value: "",
1718
+ children: []
1719
+ },
1720
+ // Inner oval shape
1721
+ {
1722
+ name: "ellipse",
1723
+ type: "element",
1724
+ attributes: {
1725
+ class: "pcb-hole-inner",
1726
+ fill: colorMap2.drill,
1727
+ cx: "0",
1728
+ cy: "0",
1729
+ rx: (scaledHoleWidth / 2).toString(),
1730
+ ry: (scaledHoleHeight / 2).toString(),
1731
+ transform: transformStr,
1732
+ "data-type": "pcb_plated_hole_drill",
1733
+ "data-pcb-layer": "drill"
1734
+ },
1735
+ value: "",
1736
+ children: []
1737
+ }
1738
+ ];
1739
+ return [
1740
+ {
1741
+ name: "g",
1742
+ type: "element",
1743
+ attributes: {
1744
+ "data-type": "pcb_plated_hole",
1745
+ "data-pcb-layer": "through"
1746
+ },
1747
+ children,
1628
1748
  value: ""
1629
1749
  }
1630
1750
  ];
@@ -1636,46 +1756,105 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1636
1756
  const scaledHoleHeight = hole.hole_diameter * Math.abs(transform.a);
1637
1757
  const outerRadius = Math.min(scaledOuterWidth, scaledOuterHeight) / 2;
1638
1758
  const innerRadius = Math.min(scaledHoleWidth, scaledHoleHeight) / 2;
1639
- return [
1759
+ let children = [
1640
1760
  {
1641
- name: "g",
1761
+ name: "circle",
1642
1762
  type: "element",
1643
1763
  attributes: {
1764
+ class: "pcb-hole-outer",
1765
+ fill: colorMap2.copper.top,
1766
+ cx: x.toString(),
1767
+ cy: y.toString(),
1768
+ r: outerRadius.toString(),
1644
1769
  "data-type": "pcb_plated_hole",
1645
- "data-pcb-layer": "through"
1770
+ "data-pcb-layer": layer
1646
1771
  },
1647
- children: [
1772
+ value: "",
1773
+ children: []
1774
+ },
1775
+ {
1776
+ name: "circle",
1777
+ type: "element",
1778
+ attributes: {
1779
+ class: "pcb-hole-inner",
1780
+ fill: colorMap2.drill,
1781
+ cx: x.toString(),
1782
+ cy: y.toString(),
1783
+ r: innerRadius.toString(),
1784
+ "data-type": "pcb_plated_hole_drill",
1785
+ "data-pcb-layer": "drill"
1786
+ },
1787
+ value: "",
1788
+ children: []
1789
+ }
1790
+ ];
1791
+ if (shouldShowSolderMask) {
1792
+ const maskRadius = outerRadius + soldermaskMargin;
1793
+ if (soldermaskMargin < 0) {
1794
+ children = [
1795
+ // 1. Draw the outer ring in soldermask color (covered)
1648
1796
  {
1649
1797
  name: "circle",
1650
1798
  type: "element",
1651
1799
  attributes: {
1652
- class: "pcb-hole-outer",
1653
- fill: colorMap2.copper.top,
1800
+ class: "pcb-hole-outer-covered",
1801
+ fill: solderMaskColor,
1654
1802
  cx: x.toString(),
1655
1803
  cy: y.toString(),
1656
1804
  r: outerRadius.toString(),
1657
1805
  "data-type": "pcb_plated_hole",
1658
- "data-pcb-layer": copperLayer
1806
+ "data-pcb-layer": layer
1659
1807
  },
1660
1808
  value: "",
1661
1809
  children: []
1662
1810
  },
1811
+ // 2. Draw the exposed opening in copper color
1663
1812
  {
1664
1813
  name: "circle",
1665
1814
  type: "element",
1666
1815
  attributes: {
1667
- class: "pcb-hole-inner",
1668
- fill: colorMap2.drill,
1816
+ class: "pcb-hole-outer-exposed",
1817
+ fill: colorMap2.copper.top,
1669
1818
  cx: x.toString(),
1670
1819
  cy: y.toString(),
1671
- r: innerRadius.toString(),
1672
- "data-type": "pcb_plated_hole_drill",
1673
- "data-pcb-layer": "drill"
1820
+ r: maskRadius.toString(),
1821
+ "data-type": "pcb_soldermask",
1822
+ "data-pcb-layer": maskLayer
1674
1823
  },
1675
1824
  value: "",
1676
1825
  children: []
1677
- }
1678
- ],
1826
+ },
1827
+ // 3. Draw the drill hole on top
1828
+ children[1]
1829
+ // Original inner hole
1830
+ ];
1831
+ } else {
1832
+ children.unshift({
1833
+ name: "circle",
1834
+ type: "element",
1835
+ attributes: {
1836
+ class: "pcb-soldermask-cutout",
1837
+ fill: colorMap2.substrate,
1838
+ cx: x.toString(),
1839
+ cy: y.toString(),
1840
+ r: maskRadius.toString(),
1841
+ "data-type": "pcb_soldermask_opening",
1842
+ "data-pcb-layer": maskLayer
1843
+ },
1844
+ value: "",
1845
+ children: []
1846
+ });
1847
+ }
1848
+ }
1849
+ return [
1850
+ {
1851
+ name: "g",
1852
+ type: "element",
1853
+ attributes: {
1854
+ "data-type": "pcb_plated_hole",
1855
+ "data-pcb-layer": "through"
1856
+ },
1857
+ children,
1679
1858
  value: ""
1680
1859
  }
1681
1860
  ];
@@ -1691,22 +1870,58 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1691
1870
  h.x + (h.hole_offset_x ?? 0),
1692
1871
  h.y + (h.hole_offset_y ?? 0)
1693
1872
  ]);
1694
- return [
1873
+ let children = [
1874
+ // Rectangular pad (outer shape)
1695
1875
  {
1696
- name: "g",
1876
+ name: "rect",
1697
1877
  type: "element",
1698
1878
  attributes: {
1879
+ class: "pcb-hole-outer-pad",
1880
+ fill: colorMap2.copper.top,
1881
+ x: (x - scaledRectPadWidth / 2).toString(),
1882
+ y: (y - scaledRectPadHeight / 2).toString(),
1883
+ width: scaledRectPadWidth.toString(),
1884
+ height: scaledRectPadHeight.toString(),
1885
+ ...scaledRectBorderRadius ? {
1886
+ rx: scaledRectBorderRadius.toString(),
1887
+ ry: scaledRectBorderRadius.toString()
1888
+ } : {},
1699
1889
  "data-type": "pcb_plated_hole",
1700
- "data-pcb-layer": "through"
1890
+ "data-pcb-layer": layer
1701
1891
  },
1702
- children: [
1703
- // Rectangular pad (outer shape)
1892
+ value: "",
1893
+ children: []
1894
+ },
1895
+ // Circular hole inside the rectangle (with optional offset)
1896
+ {
1897
+ name: "circle",
1898
+ type: "element",
1899
+ attributes: {
1900
+ class: "pcb-hole-inner",
1901
+ fill: colorMap2.drill,
1902
+ cx: holeCx.toString(),
1903
+ cy: holeCy.toString(),
1904
+ r: holeRadius.toString(),
1905
+ "data-type": "pcb_plated_hole_drill",
1906
+ "data-pcb-layer": "drill"
1907
+ },
1908
+ value: "",
1909
+ children: []
1910
+ }
1911
+ ];
1912
+ if (shouldShowSolderMask) {
1913
+ const maskWidth = scaledRectPadWidth + 2 * soldermaskMargin;
1914
+ const maskHeight = scaledRectPadHeight + 2 * soldermaskMargin;
1915
+ const maskBorderRadius = scaledRectBorderRadius + soldermaskMargin;
1916
+ if (soldermaskMargin < 0) {
1917
+ children = [
1918
+ // 1. Draw the outer pad in soldermask color (covered)
1704
1919
  {
1705
1920
  name: "rect",
1706
1921
  type: "element",
1707
1922
  attributes: {
1708
- class: "pcb-hole-outer-pad",
1709
- fill: colorMap2.copper.top,
1923
+ class: "pcb-hole-outer-covered",
1924
+ fill: solderMaskColor,
1710
1925
  x: (x - scaledRectPadWidth / 2).toString(),
1711
1926
  y: (y - scaledRectPadHeight / 2).toString(),
1712
1927
  width: scaledRectPadWidth.toString(),
@@ -1716,28 +1931,68 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1716
1931
  ry: scaledRectBorderRadius.toString()
1717
1932
  } : {},
1718
1933
  "data-type": "pcb_plated_hole",
1719
- "data-pcb-layer": copperLayer
1934
+ "data-pcb-layer": layer
1720
1935
  },
1721
1936
  value: "",
1722
1937
  children: []
1723
1938
  },
1724
- // Circular hole inside the rectangle (with optional offset)
1939
+ // 2. Draw the exposed opening in copper color
1725
1940
  {
1726
- name: "circle",
1941
+ name: "rect",
1727
1942
  type: "element",
1728
1943
  attributes: {
1729
- class: "pcb-hole-inner",
1730
- fill: colorMap2.drill,
1731
- cx: holeCx.toString(),
1732
- cy: holeCy.toString(),
1733
- r: holeRadius.toString(),
1734
- "data-type": "pcb_plated_hole_drill",
1735
- "data-pcb-layer": "drill"
1944
+ class: "pcb-hole-outer-exposed",
1945
+ fill: colorMap2.copper.top,
1946
+ x: (x - maskWidth / 2).toString(),
1947
+ y: (y - maskHeight / 2).toString(),
1948
+ width: maskWidth.toString(),
1949
+ height: maskHeight.toString(),
1950
+ ...maskBorderRadius > 0 ? {
1951
+ rx: maskBorderRadius.toString(),
1952
+ ry: maskBorderRadius.toString()
1953
+ } : {},
1954
+ "data-type": "pcb_soldermask",
1955
+ "data-pcb-layer": maskLayer
1736
1956
  },
1737
1957
  value: "",
1738
1958
  children: []
1739
- }
1740
- ],
1959
+ },
1960
+ // 3. Draw the drill hole on top
1961
+ children[1]
1962
+ // Original hole
1963
+ ];
1964
+ } else {
1965
+ children.unshift({
1966
+ name: "rect",
1967
+ type: "element",
1968
+ attributes: {
1969
+ class: "pcb-soldermask-cutout",
1970
+ fill: colorMap2.substrate,
1971
+ x: (x - maskWidth / 2).toString(),
1972
+ y: (y - maskHeight / 2).toString(),
1973
+ width: maskWidth.toString(),
1974
+ height: maskHeight.toString(),
1975
+ ...scaledRectBorderRadius ? {
1976
+ rx: maskBorderRadius.toString(),
1977
+ ry: maskBorderRadius.toString()
1978
+ } : {},
1979
+ "data-type": "pcb_soldermask_opening",
1980
+ "data-pcb-layer": maskLayer
1981
+ },
1982
+ value: "",
1983
+ children: []
1984
+ });
1985
+ }
1986
+ }
1987
+ return [
1988
+ {
1989
+ name: "g",
1990
+ type: "element",
1991
+ attributes: {
1992
+ "data-type": "pcb_plated_hole",
1993
+ "data-pcb-layer": "through"
1994
+ },
1995
+ children,
1741
1996
  value: ""
1742
1997
  }
1743
1998
  ];
@@ -1757,22 +2012,61 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1757
2012
  pillHole.y + holeOffsetY
1758
2013
  ]);
1759
2014
  const holeRadius = Math.min(scaledHoleHeight, scaledHoleWidth) / 2;
1760
- return [
2015
+ let children = [
2016
+ // Rectangular pad (outer shape)
1761
2017
  {
1762
- name: "g",
2018
+ name: "rect",
1763
2019
  type: "element",
1764
2020
  attributes: {
2021
+ class: "pcb-hole-outer-pad",
2022
+ fill: colorMap2.copper.top,
2023
+ x: (x - scaledRectPadWidth / 2).toString(),
2024
+ y: (y - scaledRectPadHeight / 2).toString(),
2025
+ width: scaledRectPadWidth.toString(),
2026
+ height: scaledRectPadHeight.toString(),
2027
+ ...scaledRectBorderRadius ? {
2028
+ rx: scaledRectBorderRadius.toString(),
2029
+ ry: scaledRectBorderRadius.toString()
2030
+ } : {},
1765
2031
  "data-type": "pcb_plated_hole",
1766
- "data-pcb-layer": "through"
2032
+ "data-pcb-layer": layer
1767
2033
  },
1768
- children: [
1769
- // Rectangular pad (outer shape)
2034
+ value: "",
2035
+ children: []
2036
+ },
2037
+ // pill hole inside the rectangle
2038
+ {
2039
+ name: "rect",
2040
+ type: "element",
2041
+ attributes: {
2042
+ class: "pcb-hole-inner",
2043
+ fill: colorMap2.drill,
2044
+ x: (holeCenterX - scaledHoleWidth / 2).toString(),
2045
+ y: (holeCenterY - scaledHoleHeight / 2).toString(),
2046
+ width: scaledHoleWidth.toString(),
2047
+ height: scaledHoleHeight.toString(),
2048
+ rx: holeRadius.toString(),
2049
+ ry: holeRadius.toString(),
2050
+ "data-type": "pcb_plated_hole_drill",
2051
+ "data-pcb-layer": "drill"
2052
+ },
2053
+ value: "",
2054
+ children: []
2055
+ }
2056
+ ];
2057
+ if (shouldShowSolderMask) {
2058
+ const maskWidth = scaledRectPadWidth + 2 * soldermaskMargin;
2059
+ const maskHeight = scaledRectPadHeight + 2 * soldermaskMargin;
2060
+ const maskBorderRadius = scaledRectBorderRadius + soldermaskMargin;
2061
+ if (soldermaskMargin < 0) {
2062
+ children = [
2063
+ // 1. Draw the outer pad in soldermask color (covered)
1770
2064
  {
1771
2065
  name: "rect",
1772
2066
  type: "element",
1773
2067
  attributes: {
1774
- class: "pcb-hole-outer-pad",
1775
- fill: colorMap2.copper.top,
2068
+ class: "pcb-hole-outer-covered",
2069
+ fill: solderMaskColor,
1776
2070
  x: (x - scaledRectPadWidth / 2).toString(),
1777
2071
  y: (y - scaledRectPadHeight / 2).toString(),
1778
2072
  width: scaledRectPadWidth.toString(),
@@ -1782,31 +2076,68 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1782
2076
  ry: scaledRectBorderRadius.toString()
1783
2077
  } : {},
1784
2078
  "data-type": "pcb_plated_hole",
1785
- "data-pcb-layer": copperLayer
2079
+ "data-pcb-layer": layer
1786
2080
  },
1787
2081
  value: "",
1788
2082
  children: []
1789
2083
  },
1790
- // pill hole inside the rectangle
2084
+ // 2. Draw the exposed opening in copper color
1791
2085
  {
1792
2086
  name: "rect",
1793
2087
  type: "element",
1794
2088
  attributes: {
1795
- class: "pcb-hole-inner",
1796
- fill: colorMap2.drill,
1797
- x: (holeCenterX - scaledHoleWidth / 2).toString(),
1798
- y: (holeCenterY - scaledHoleHeight / 2).toString(),
1799
- width: scaledHoleWidth.toString(),
1800
- height: scaledHoleHeight.toString(),
1801
- rx: holeRadius.toString(),
1802
- ry: holeRadius.toString(),
1803
- "data-type": "pcb_plated_hole_drill",
1804
- "data-pcb-layer": "drill"
2089
+ class: "pcb-hole-outer-exposed",
2090
+ fill: colorMap2.copper.top,
2091
+ x: (x - maskWidth / 2).toString(),
2092
+ y: (y - maskHeight / 2).toString(),
2093
+ width: maskWidth.toString(),
2094
+ height: maskHeight.toString(),
2095
+ ...maskBorderRadius > 0 ? {
2096
+ rx: maskBorderRadius.toString(),
2097
+ ry: maskBorderRadius.toString()
2098
+ } : {},
2099
+ "data-type": "pcb_soldermask",
2100
+ "data-pcb-layer": maskLayer
1805
2101
  },
1806
2102
  value: "",
1807
2103
  children: []
1808
- }
1809
- ],
2104
+ },
2105
+ // 3. Draw the drill hole on top
2106
+ children[1]
2107
+ // Original hole
2108
+ ];
2109
+ } else {
2110
+ children.unshift({
2111
+ name: "rect",
2112
+ type: "element",
2113
+ attributes: {
2114
+ class: "pcb-soldermask-cutout",
2115
+ fill: colorMap2.substrate,
2116
+ x: (x - maskWidth / 2).toString(),
2117
+ y: (y - maskHeight / 2).toString(),
2118
+ width: maskWidth.toString(),
2119
+ height: maskHeight.toString(),
2120
+ ...scaledRectBorderRadius ? {
2121
+ rx: maskBorderRadius.toString(),
2122
+ ry: maskBorderRadius.toString()
2123
+ } : {},
2124
+ "data-type": "pcb_soldermask_opening",
2125
+ "data-pcb-layer": maskLayer
2126
+ },
2127
+ value: "",
2128
+ children: []
2129
+ });
2130
+ }
2131
+ }
2132
+ return [
2133
+ {
2134
+ name: "g",
2135
+ type: "element",
2136
+ attributes: {
2137
+ "data-type": "pcb_plated_hole",
2138
+ "data-pcb-layer": "through"
2139
+ },
2140
+ children,
1810
2141
  value: ""
1811
2142
  }
1812
2143
  ];
@@ -1826,21 +2157,61 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1826
2157
  rotatedHole.y + holeOffsetY
1827
2158
  ]);
1828
2159
  const holeRadius = Math.min(scaledHoleHeight, scaledHoleWidth) / 2;
1829
- return [
2160
+ let children = [
1830
2161
  {
1831
- name: "g",
2162
+ name: "rect",
1832
2163
  type: "element",
1833
2164
  attributes: {
2165
+ class: "pcb-hole-outer-pad",
2166
+ fill: colorMap2.copper.top,
2167
+ x: (-scaledRectPadWidth / 2).toString(),
2168
+ y: (-scaledRectPadHeight / 2).toString(),
2169
+ width: scaledRectPadWidth.toString(),
2170
+ height: scaledRectPadHeight.toString(),
2171
+ transform: `translate(${x} ${y}) rotate(${-rotatedHole.rect_ccw_rotation})`,
2172
+ ...scaledRectBorderRadius ? {
2173
+ rx: scaledRectBorderRadius.toString(),
2174
+ ry: scaledRectBorderRadius.toString()
2175
+ } : {},
1834
2176
  "data-type": "pcb_plated_hole",
1835
- "data-pcb-layer": "through"
2177
+ "data-pcb-layer": layer
1836
2178
  },
1837
- children: [
2179
+ value: "",
2180
+ children: []
2181
+ },
2182
+ {
2183
+ name: "rect",
2184
+ type: "element",
2185
+ attributes: {
2186
+ class: "pcb-hole-inner",
2187
+ fill: colorMap2.drill,
2188
+ x: (-scaledHoleWidth / 2).toString(),
2189
+ y: (-scaledHoleHeight / 2).toString(),
2190
+ width: scaledHoleWidth.toString(),
2191
+ height: scaledHoleHeight.toString(),
2192
+ rx: holeRadius.toString(),
2193
+ ry: holeRadius.toString(),
2194
+ transform: `translate(${holeCenterX} ${holeCenterY}) rotate(${-rotatedHole.hole_ccw_rotation})`,
2195
+ "data-type": "pcb_plated_hole_drill",
2196
+ "data-pcb-layer": "drill"
2197
+ },
2198
+ value: "",
2199
+ children: []
2200
+ }
2201
+ ];
2202
+ if (shouldShowSolderMask) {
2203
+ const maskWidth = scaledRectPadWidth + 2 * soldermaskMargin;
2204
+ const maskHeight = scaledRectPadHeight + 2 * soldermaskMargin;
2205
+ const maskBorderRadius = scaledRectBorderRadius + soldermaskMargin;
2206
+ if (soldermaskMargin < 0) {
2207
+ children = [
2208
+ // 1. Draw the outer pad in soldermask color (covered)
1838
2209
  {
1839
2210
  name: "rect",
1840
2211
  type: "element",
1841
2212
  attributes: {
1842
- class: "pcb-hole-outer-pad",
1843
- fill: colorMap2.copper.top,
2213
+ class: "pcb-hole-outer-covered",
2214
+ fill: solderMaskColor,
1844
2215
  x: (-scaledRectPadWidth / 2).toString(),
1845
2216
  y: (-scaledRectPadHeight / 2).toString(),
1846
2217
  width: scaledRectPadWidth.toString(),
@@ -1851,30 +2222,186 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1851
2222
  ry: scaledRectBorderRadius.toString()
1852
2223
  } : {},
1853
2224
  "data-type": "pcb_plated_hole",
1854
- "data-pcb-layer": copperLayer
2225
+ "data-pcb-layer": layer
1855
2226
  },
1856
2227
  value: "",
1857
2228
  children: []
1858
2229
  },
2230
+ // 2. Draw the exposed opening in copper color
1859
2231
  {
1860
2232
  name: "rect",
1861
2233
  type: "element",
1862
2234
  attributes: {
1863
- class: "pcb-hole-inner",
1864
- fill: colorMap2.drill,
1865
- x: (-scaledHoleWidth / 2).toString(),
1866
- y: (-scaledHoleHeight / 2).toString(),
1867
- width: scaledHoleWidth.toString(),
1868
- height: scaledHoleHeight.toString(),
1869
- rx: holeRadius.toString(),
1870
- ry: holeRadius.toString(),
1871
- transform: `translate(${holeCenterX} ${holeCenterY}) rotate(${-rotatedHole.hole_ccw_rotation})`,
1872
- "data-type": "pcb_plated_hole_drill",
1873
- "data-pcb-layer": "drill"
2235
+ class: "pcb-hole-outer-exposed",
2236
+ fill: colorMap2.copper.top,
2237
+ x: (-maskWidth / 2).toString(),
2238
+ y: (-maskHeight / 2).toString(),
2239
+ width: maskWidth.toString(),
2240
+ height: maskHeight.toString(),
2241
+ transform: `translate(${x} ${y}) rotate(${-rotatedHole.rect_ccw_rotation})`,
2242
+ ...maskBorderRadius > 0 ? {
2243
+ rx: maskBorderRadius.toString(),
2244
+ ry: maskBorderRadius.toString()
2245
+ } : {},
2246
+ "data-type": "pcb_soldermask",
2247
+ "data-pcb-layer": maskLayer
1874
2248
  },
1875
2249
  value: "",
1876
2250
  children: []
1877
- }
2251
+ },
2252
+ // 3. Draw the drill hole on top
2253
+ children[1]
2254
+ // Original hole
2255
+ ];
2256
+ } else {
2257
+ children.push({
2258
+ name: "rect",
2259
+ type: "element",
2260
+ attributes: {
2261
+ class: "pcb-solder-mask",
2262
+ fill: solderMaskColor,
2263
+ x: (-maskWidth / 2).toString(),
2264
+ y: (-maskHeight / 2).toString(),
2265
+ width: maskWidth.toString(),
2266
+ height: maskHeight.toString(),
2267
+ transform: `translate(${x} ${y}) rotate(${-rotatedHole.rect_ccw_rotation})`,
2268
+ ...scaledRectBorderRadius ? {
2269
+ rx: maskBorderRadius.toString(),
2270
+ ry: maskBorderRadius.toString()
2271
+ } : {},
2272
+ "data-type": "pcb_soldermask",
2273
+ "data-pcb-layer": maskLayer
2274
+ },
2275
+ value: "",
2276
+ children: []
2277
+ });
2278
+ }
2279
+ }
2280
+ return [
2281
+ {
2282
+ name: "g",
2283
+ type: "element",
2284
+ attributes: {
2285
+ "data-type": "pcb_plated_hole",
2286
+ "data-pcb-layer": "through"
2287
+ },
2288
+ children,
2289
+ value: ""
2290
+ }
2291
+ ];
2292
+ }
2293
+ if (hole.shape === "hole_with_polygon_pad") {
2294
+ const polygonHole = hole;
2295
+ const padOutline = polygonHole.pad_outline || [];
2296
+ const holeX = polygonHole.x ?? 0;
2297
+ const holeY = polygonHole.y ?? 0;
2298
+ const padPoints = padOutline.map(
2299
+ (point) => applyToPoint(transform, [holeX + point.x, holeY + point.y])
2300
+ );
2301
+ const padPointsString = padPoints.map((p) => p.join(",")).join(" ");
2302
+ const [holeCenterX, holeCenterY] = applyToPoint(transform, [
2303
+ holeX + polygonHole.hole_offset_x,
2304
+ holeY + polygonHole.hole_offset_y
2305
+ ]);
2306
+ const createHoleSvgObject = () => {
2307
+ if (polygonHole.hole_shape === "circle") {
2308
+ const scaledDiameter = (polygonHole.hole_diameter ?? 0) * Math.abs(transform.a);
2309
+ const radius = scaledDiameter / 2;
2310
+ return {
2311
+ name: "circle",
2312
+ type: "element",
2313
+ attributes: {
2314
+ class: "pcb-hole-inner",
2315
+ fill: colorMap2.drill,
2316
+ cx: holeCenterX.toString(),
2317
+ cy: holeCenterY.toString(),
2318
+ r: radius.toString(),
2319
+ "data-type": "pcb_plated_hole_drill",
2320
+ "data-pcb-layer": "drill"
2321
+ },
2322
+ value: "",
2323
+ children: []
2324
+ };
2325
+ }
2326
+ if (polygonHole.hole_shape === "oval") {
2327
+ const scaledWidth = (polygonHole.hole_width ?? 0) * Math.abs(transform.a);
2328
+ const scaledHeight = (polygonHole.hole_height ?? 0) * Math.abs(transform.a);
2329
+ const rx = scaledWidth / 2;
2330
+ const ry = scaledHeight / 2;
2331
+ return {
2332
+ name: "ellipse",
2333
+ type: "element",
2334
+ attributes: {
2335
+ class: "pcb-hole-inner",
2336
+ fill: colorMap2.drill,
2337
+ cx: holeCenterX.toString(),
2338
+ cy: holeCenterY.toString(),
2339
+ rx: rx.toString(),
2340
+ ry: ry.toString(),
2341
+ "data-type": "pcb_plated_hole_drill",
2342
+ "data-pcb-layer": "drill"
2343
+ },
2344
+ value: "",
2345
+ children: []
2346
+ };
2347
+ }
2348
+ if (polygonHole.hole_shape === "pill" || polygonHole.hole_shape === "rotated_pill") {
2349
+ const scaledWidth = (polygonHole.hole_width ?? 0) * Math.abs(transform.a);
2350
+ const scaledHeight = (polygonHole.hole_height ?? 0) * Math.abs(transform.a);
2351
+ const isHorizontal = scaledWidth > scaledHeight;
2352
+ const radius = Math.min(scaledWidth, scaledHeight) / 2;
2353
+ const straightLength = Math.abs(
2354
+ isHorizontal ? scaledWidth - scaledHeight : scaledHeight - scaledWidth
2355
+ );
2356
+ const pathD = isHorizontal ? `M${-straightLength / 2},${-radius} h${straightLength} a${radius},${radius} 0 0 1 0,${scaledHeight} h-${straightLength} a${radius},${radius} 0 0 1 0,-${scaledHeight} z` : `M${-radius},${-straightLength / 2} v${straightLength} a${radius},${radius} 0 0 0 ${scaledWidth},0 v-${straightLength} a${radius},${radius} 0 0 0 -${scaledWidth},0 z`;
2357
+ return {
2358
+ name: "path",
2359
+ type: "element",
2360
+ attributes: {
2361
+ class: "pcb-hole-inner",
2362
+ fill: colorMap2.drill,
2363
+ d: pathD,
2364
+ transform: `translate(${holeCenterX} ${holeCenterY})`,
2365
+ "data-type": "pcb_plated_hole_drill",
2366
+ "data-pcb-layer": "drill"
2367
+ },
2368
+ value: "",
2369
+ children: []
2370
+ };
2371
+ }
2372
+ return {
2373
+ name: "g",
2374
+ type: "element",
2375
+ attributes: {},
2376
+ value: "",
2377
+ children: []
2378
+ };
2379
+ };
2380
+ return [
2381
+ {
2382
+ name: "g",
2383
+ type: "element",
2384
+ attributes: {
2385
+ "data-type": "pcb_plated_hole",
2386
+ "data-pcb-layer": "through"
2387
+ },
2388
+ children: [
2389
+ // Polygon pad (outer shape)
2390
+ {
2391
+ name: "polygon",
2392
+ type: "element",
2393
+ attributes: {
2394
+ class: "pcb-hole-outer-pad",
2395
+ fill: colorMap2.copper.top,
2396
+ points: padPointsString,
2397
+ "data-type": "pcb_plated_hole",
2398
+ "data-pcb-layer": layer
2399
+ },
2400
+ value: "",
2401
+ children: []
2402
+ },
2403
+ // Hole inside the polygon (with offset)
2404
+ createHoleSvgObject()
1878
2405
  ],
1879
2406
  value: ""
1880
2407
  }
@@ -1920,7 +2447,7 @@ function createSvgObjectsFromPcbSilkscreenPath(silkscreenPath, ctx) {
1920
2447
  ];
1921
2448
  }
1922
2449
  function createSvgObjectsFromPcbSilkscreenText(pcbSilkscreenText, ctx) {
1923
- const { transform, layer: layerFilter, colorMap: colorMap2 } = ctx;
2450
+ const { transform, layer: layerFilter, colorMap: colorMap2, circuitJson } = ctx;
1924
2451
  const {
1925
2452
  anchor_position,
1926
2453
  text,
@@ -2260,10 +2787,20 @@ var DEFAULT_PCB_COLOR_MAP = {
2260
2787
  inner6: "rgb(255, 105, 180)",
2261
2788
  bottom: "rgb(77, 127, 196)"
2262
2789
  },
2263
- soldermask: {
2790
+ soldermaskWithCopperUnderneath: {
2264
2791
  top: "rgb(18, 82, 50)",
2265
2792
  bottom: "rgb(77, 127, 196)"
2266
2793
  },
2794
+ soldermask: {
2795
+ top: "rgb(12, 55, 33)",
2796
+ bottom: "rgb(12, 55, 33)"
2797
+ },
2798
+ soldermaskOverCopper: {
2799
+ top: "rgb(52, 135, 73)",
2800
+ bottom: "rgb(52, 135, 73)"
2801
+ },
2802
+ substrate: "rgb(201, 162, 110)",
2803
+ // FR4 substrate color (tan/beige)
2267
2804
  drill: "#FF26E2",
2268
2805
  silkscreen: {
2269
2806
  top: "#f2eda1",
@@ -2304,29 +2841,12 @@ function createSvgObjectsFromPcbTrace(trace, ctx) {
2304
2841
  const layer = "layer" in start ? start.layer : "layer" in end ? end.layer : null;
2305
2842
  if (!layer) continue;
2306
2843
  if (layerFilter && layer !== layerFilter) continue;
2844
+ const maskLayer = layer;
2307
2845
  const copperColor = layerNameToColor(layer, colorMap2);
2308
- const maskColor = colorMap2.soldermask[layer] ?? copperColor;
2846
+ const maskColor = colorMap2.soldermaskWithCopperUnderneath[layer];
2309
2847
  const traceWidth = "width" in start ? start.width : "width" in end ? end.width : null;
2310
2848
  const width = traceWidth ? (traceWidth * Math.abs(transform.a)).toString() : "0.3";
2311
2849
  if (showSolderMask) {
2312
- const copperObject = {
2313
- name: "path",
2314
- type: "element",
2315
- value: "",
2316
- children: [],
2317
- attributes: {
2318
- class: "pcb-trace",
2319
- stroke: copperColor,
2320
- fill: "none",
2321
- d: `M ${startPoint[0]} ${startPoint[1]} L ${endPoint[0]} ${endPoint[1]}`,
2322
- "stroke-width": width,
2323
- "stroke-linecap": "round",
2324
- "stroke-linejoin": "round",
2325
- "shape-rendering": "crispEdges",
2326
- "data-type": "pcb_trace",
2327
- "data-pcb-layer": layer
2328
- }
2329
- };
2330
2850
  const maskObject = {
2331
2851
  name: "path",
2332
2852
  type: "element",
@@ -2341,11 +2861,11 @@ function createSvgObjectsFromPcbTrace(trace, ctx) {
2341
2861
  "stroke-linecap": "round",
2342
2862
  "stroke-linejoin": "round",
2343
2863
  "shape-rendering": "crispEdges",
2344
- "data-type": "pcb_soldermask",
2864
+ "data-type": "pcb_trace_soldermask",
2345
2865
  "data-pcb-layer": layer
2346
2866
  }
2347
2867
  };
2348
- svgObjects.push(maskObject, copperObject);
2868
+ svgObjects.push(maskObject);
2349
2869
  } else {
2350
2870
  const maskOnlyObject = {
2351
2871
  name: "path",
@@ -2374,8 +2894,9 @@ function createSvgObjectsFromSmtPad(pad, ctx) {
2374
2894
  const { transform, layer: layerFilter, colorMap: colorMap2, showSolderMask } = ctx;
2375
2895
  if (layerFilter && pad.layer !== layerFilter) return [];
2376
2896
  const isCoveredWithSolderMask = Boolean(pad?.is_covered_with_solder_mask);
2377
- const shouldshowSolderMask = showSolderMask && isCoveredWithSolderMask;
2378
- const solderMaskColor = colorMap2.soldermask[pad.layer] ?? colorMap2.soldermask.top;
2897
+ const shouldShowSolderMask = showSolderMask && isCoveredWithSolderMask;
2898
+ const soldermaskWithCopperUnderneathColor = colorMap2.soldermaskWithCopperUnderneath[pad.layer] ?? colorMap2.soldermaskWithCopperUnderneath.top;
2899
+ const soldermaskMargin = (pad.soldermask_margin ?? 0) * Math.abs(transform.a);
2379
2900
  if (pad.shape === "rect" || pad.shape === "rotated_rect") {
2380
2901
  const width = pad.width * Math.abs(transform.a);
2381
2902
  const height = pad.height * Math.abs(transform.d);
@@ -2404,22 +2925,103 @@ function createSvgObjectsFromSmtPad(pad, ctx) {
2404
2925
  } : {}
2405
2926
  }
2406
2927
  };
2407
- if (!shouldshowSolderMask) {
2928
+ if (!shouldShowSolderMask) {
2408
2929
  return [padElement2];
2409
2930
  }
2410
- const maskElement2 = {
2411
- name: padElement2.name,
2412
- type: padElement2.type,
2931
+ const maskWidth2 = width + 2 * soldermaskMargin;
2932
+ const maskHeight2 = height + 2 * soldermaskMargin;
2933
+ const maskBorderRadius2 = scaledBorderRadius ? scaledBorderRadius + soldermaskMargin : 0;
2934
+ if (soldermaskMargin < 0) {
2935
+ const coveredPadElement = {
2936
+ name: "rect",
2937
+ type: "element",
2938
+ value: "",
2939
+ children: [],
2940
+ attributes: {
2941
+ class: "pcb-pad-covered",
2942
+ fill: soldermaskWithCopperUnderneathColor,
2943
+ x: (-width / 2).toString(),
2944
+ y: (-height / 2).toString(),
2945
+ width: width.toString(),
2946
+ height: height.toString(),
2947
+ transform: `translate(${x} ${y}) rotate(${-pad.ccw_rotation})`,
2948
+ "data-type": "pcb_smtpad",
2949
+ "data-pcb-layer": pad.layer,
2950
+ ...scaledBorderRadius ? {
2951
+ rx: scaledBorderRadius.toString(),
2952
+ ry: scaledBorderRadius.toString()
2953
+ } : {}
2954
+ }
2955
+ };
2956
+ const exposedOpeningElement = {
2957
+ name: "rect",
2958
+ type: "element",
2959
+ value: "",
2960
+ children: [],
2961
+ attributes: {
2962
+ class: "pcb-pad-exposed",
2963
+ fill: layerNameToColor(pad.layer, colorMap2),
2964
+ x: (-maskWidth2 / 2).toString(),
2965
+ y: (-maskHeight2 / 2).toString(),
2966
+ width: maskWidth2.toString(),
2967
+ height: maskHeight2.toString(),
2968
+ transform: `translate(${x} ${y}) rotate(${-pad.ccw_rotation})`,
2969
+ "data-type": "pcb_soldermask",
2970
+ "data-pcb-layer": pad.layer,
2971
+ ...maskBorderRadius2 > 0 ? {
2972
+ rx: maskBorderRadius2.toString(),
2973
+ ry: maskBorderRadius2.toString()
2974
+ } : {}
2975
+ }
2976
+ };
2977
+ return [coveredPadElement, exposedOpeningElement];
2978
+ }
2979
+ if (soldermaskMargin === 0) {
2980
+ const coveredPadElement = {
2981
+ name: "rect",
2982
+ type: "element",
2983
+ value: "",
2984
+ children: [],
2985
+ attributes: {
2986
+ class: "pcb-pad-covered",
2987
+ fill: soldermaskWithCopperUnderneathColor,
2988
+ x: (-width / 2).toString(),
2989
+ y: (-height / 2).toString(),
2990
+ width: width.toString(),
2991
+ height: height.toString(),
2992
+ transform: `translate(${x} ${y}) rotate(${-pad.ccw_rotation})`,
2993
+ "data-type": "pcb_smtpad",
2994
+ "data-pcb-layer": pad.layer,
2995
+ ...scaledBorderRadius ? {
2996
+ rx: scaledBorderRadius.toString(),
2997
+ ry: scaledBorderRadius.toString()
2998
+ } : {}
2999
+ }
3000
+ };
3001
+ return [coveredPadElement];
3002
+ }
3003
+ const substrateElement2 = {
3004
+ name: "rect",
3005
+ type: "element",
2413
3006
  value: "",
2414
3007
  children: [],
2415
3008
  attributes: {
2416
- ...padElement2.attributes,
2417
- class: "pcb-solder-mask",
2418
- fill: solderMaskColor,
2419
- "data-type": "pcb_soldermask"
3009
+ class: "pcb-soldermask-cutout",
3010
+ fill: colorMap2.substrate,
3011
+ x: (-maskWidth2 / 2).toString(),
3012
+ y: (-maskHeight2 / 2).toString(),
3013
+ width: maskWidth2.toString(),
3014
+ height: maskHeight2.toString(),
3015
+ transform: `translate(${x} ${y}) rotate(${-pad.ccw_rotation})`,
3016
+ ...maskBorderRadius2 > 0 ? {
3017
+ rx: maskBorderRadius2.toString(),
3018
+ ry: maskBorderRadius2.toString()
3019
+ } : {},
3020
+ "data-type": "pcb_soldermask_opening",
3021
+ "data-pcb-layer": pad.layer
2420
3022
  }
2421
3023
  };
2422
- return [padElement2, maskElement2];
3024
+ return [substrateElement2, padElement2];
2423
3025
  }
2424
3026
  const padElement = {
2425
3027
  name: "rect",
@@ -2441,62 +3043,220 @@ function createSvgObjectsFromSmtPad(pad, ctx) {
2441
3043
  } : {}
2442
3044
  }
2443
3045
  };
2444
- if (!shouldshowSolderMask) {
3046
+ if (!shouldShowSolderMask) {
2445
3047
  return [padElement];
2446
3048
  }
2447
- const maskElement = {
2448
- name: padElement.name,
2449
- type: padElement.type,
3049
+ const maskWidth = width + 2 * soldermaskMargin;
3050
+ const maskHeight = height + 2 * soldermaskMargin;
3051
+ const maskBorderRadius = scaledBorderRadius ? scaledBorderRadius + soldermaskMargin : 0;
3052
+ if (soldermaskMargin < 0) {
3053
+ const coveredPadElement = {
3054
+ name: "rect",
3055
+ type: "element",
3056
+ value: "",
3057
+ children: [],
3058
+ attributes: {
3059
+ class: "pcb-pad-covered",
3060
+ fill: soldermaskWithCopperUnderneathColor,
3061
+ x: (x - width / 2).toString(),
3062
+ y: (y - height / 2).toString(),
3063
+ width: width.toString(),
3064
+ height: height.toString(),
3065
+ "data-type": "pcb_smtpad",
3066
+ "data-pcb-layer": pad.layer,
3067
+ ...scaledBorderRadius ? {
3068
+ rx: scaledBorderRadius.toString(),
3069
+ ry: scaledBorderRadius.toString()
3070
+ } : {}
3071
+ }
3072
+ };
3073
+ const exposedOpeningElement = {
3074
+ name: "rect",
3075
+ type: "element",
3076
+ value: "",
3077
+ children: [],
3078
+ attributes: {
3079
+ class: "pcb-pad-exposed",
3080
+ fill: layerNameToColor(pad.layer, colorMap2),
3081
+ x: (x - maskWidth / 2).toString(),
3082
+ y: (y - maskHeight / 2).toString(),
3083
+ width: maskWidth.toString(),
3084
+ height: maskHeight.toString(),
3085
+ "data-type": "pcb_soldermask",
3086
+ "data-pcb-layer": pad.layer,
3087
+ ...maskBorderRadius > 0 ? {
3088
+ rx: maskBorderRadius.toString(),
3089
+ ry: maskBorderRadius.toString()
3090
+ } : {}
3091
+ }
3092
+ };
3093
+ return [coveredPadElement, exposedOpeningElement];
3094
+ }
3095
+ if (soldermaskMargin === 0) {
3096
+ const coveredPadElement = {
3097
+ name: "rect",
3098
+ type: "element",
3099
+ value: "",
3100
+ children: [],
3101
+ attributes: {
3102
+ class: "pcb-pad-covered",
3103
+ fill: soldermaskWithCopperUnderneathColor,
3104
+ x: (x - width / 2).toString(),
3105
+ y: (y - height / 2).toString(),
3106
+ width: width.toString(),
3107
+ height: height.toString(),
3108
+ ...maskBorderRadius > 0 ? {
3109
+ rx: scaledBorderRadius.toString(),
3110
+ ry: scaledBorderRadius.toString()
3111
+ } : {},
3112
+ "data-type": "pcb_smtpad",
3113
+ "data-pcb-layer": pad.layer
3114
+ }
3115
+ };
3116
+ return [coveredPadElement];
3117
+ }
3118
+ const substrateElement = {
3119
+ name: "rect",
3120
+ type: "element",
2450
3121
  value: "",
2451
3122
  children: [],
2452
3123
  attributes: {
2453
- ...padElement.attributes,
2454
- class: "pcb-solder-mask",
2455
- fill: solderMaskColor,
2456
- "data-type": "pcb_soldermask"
3124
+ class: "pcb-soldermask-cutout",
3125
+ fill: colorMap2.substrate,
3126
+ x: (x - maskWidth / 2).toString(),
3127
+ y: (y - maskHeight / 2).toString(),
3128
+ width: maskWidth.toString(),
3129
+ height: maskHeight.toString(),
3130
+ ...maskBorderRadius > 0 ? {
3131
+ rx: maskBorderRadius.toString(),
3132
+ ry: maskBorderRadius.toString()
3133
+ } : {},
3134
+ "data-type": "pcb_soldermask_opening",
3135
+ "data-pcb-layer": pad.layer
2457
3136
  }
2458
3137
  };
2459
- return [padElement, maskElement];
3138
+ return [substrateElement, padElement];
2460
3139
  }
2461
- if (pad.shape === "pill") {
3140
+ if (pad.shape === "pill" || pad.shape === "rotated_pill") {
3141
+ const isRotated = pad.shape === "rotated_pill";
2462
3142
  const width = pad.width * Math.abs(transform.a);
2463
3143
  const height = pad.height * Math.abs(transform.d);
2464
3144
  const radius = pad.radius * Math.abs(transform.a);
2465
3145
  const [x, y] = applyToPoint(transform, [pad.x, pad.y]);
3146
+ const rotationTransformAttributes = isRotated ? {
3147
+ transform: `translate(${x} ${y}) rotate(${-(pad.ccw_rotation ?? 0)})`
3148
+ } : void 0;
3149
+ const baseAttributes = {
3150
+ class: "pcb-pad",
3151
+ fill: layerNameToColor(pad.layer, colorMap2),
3152
+ x: isRotated ? (-width / 2).toString() : (x - width / 2).toString(),
3153
+ y: isRotated ? (-height / 2).toString() : (y - height / 2).toString(),
3154
+ width: width.toString(),
3155
+ height: height.toString(),
3156
+ rx: radius.toString(),
3157
+ ry: radius.toString(),
3158
+ "data-type": "pcb_smtpad",
3159
+ "data-pcb-layer": pad.layer,
3160
+ ...rotationTransformAttributes ?? {}
3161
+ };
2466
3162
  const padElement = {
2467
3163
  name: "rect",
2468
3164
  type: "element",
2469
3165
  value: "",
2470
3166
  children: [],
2471
- attributes: {
2472
- class: "pcb-pad",
2473
- fill: layerNameToColor(pad.layer, colorMap2),
2474
- x: (x - width / 2).toString(),
2475
- y: (y - height / 2).toString(),
2476
- width: width.toString(),
2477
- height: height.toString(),
2478
- rx: radius.toString(),
2479
- ry: radius.toString(),
2480
- "data-type": "pcb_smtpad",
2481
- "data-pcb-layer": pad.layer
2482
- }
3167
+ attributes: baseAttributes
2483
3168
  };
2484
- if (!shouldshowSolderMask) {
3169
+ if (!shouldShowSolderMask) {
2485
3170
  return [padElement];
2486
3171
  }
2487
- const maskElement = {
2488
- name: padElement.name,
2489
- type: padElement.type,
3172
+ const maskWidth = width + 2 * soldermaskMargin;
3173
+ const maskHeight = height + 2 * soldermaskMargin;
3174
+ const maskRadius = radius + soldermaskMargin;
3175
+ if (soldermaskMargin < 0) {
3176
+ const coveredPadElement = {
3177
+ name: "rect",
3178
+ type: "element",
3179
+ value: "",
3180
+ children: [],
3181
+ attributes: {
3182
+ class: "pcb-pad-covered",
3183
+ fill: soldermaskWithCopperUnderneathColor,
3184
+ x: isRotated ? (-width / 2).toString() : (x - width / 2).toString(),
3185
+ y: isRotated ? (-height / 2).toString() : (y - height / 2).toString(),
3186
+ width: width.toString(),
3187
+ height: height.toString(),
3188
+ rx: radius.toString(),
3189
+ ry: radius.toString(),
3190
+ "data-type": "pcb_smtpad",
3191
+ "data-pcb-layer": pad.layer,
3192
+ ...rotationTransformAttributes ?? {}
3193
+ }
3194
+ };
3195
+ const exposedAttributes = {
3196
+ class: "pcb-pad-exposed",
3197
+ fill: layerNameToColor(pad.layer, colorMap2),
3198
+ x: isRotated ? (-maskWidth / 2).toString() : (x - maskWidth / 2).toString(),
3199
+ y: isRotated ? (-maskHeight / 2).toString() : (y - maskHeight / 2).toString(),
3200
+ width: maskWidth.toString(),
3201
+ height: maskHeight.toString(),
3202
+ rx: maskRadius.toString(),
3203
+ ry: maskRadius.toString(),
3204
+ "data-type": "pcb_soldermask",
3205
+ "data-pcb-layer": pad.layer,
3206
+ ...rotationTransformAttributes ?? {}
3207
+ };
3208
+ const exposedOpeningElement = {
3209
+ name: "rect",
3210
+ type: "element",
3211
+ value: "",
3212
+ children: [],
3213
+ attributes: exposedAttributes
3214
+ };
3215
+ return [coveredPadElement, exposedOpeningElement];
3216
+ }
3217
+ if (soldermaskMargin === 0) {
3218
+ const coveredPadElement = {
3219
+ name: "rect",
3220
+ type: "element",
3221
+ value: "",
3222
+ children: [],
3223
+ attributes: {
3224
+ class: "pcb-pad-covered",
3225
+ fill: soldermaskWithCopperUnderneathColor,
3226
+ x: isRotated ? (-width / 2).toString() : (x - width / 2).toString(),
3227
+ y: isRotated ? (-height / 2).toString() : (y - height / 2).toString(),
3228
+ width: width.toString(),
3229
+ height: height.toString(),
3230
+ rx: radius.toString(),
3231
+ ry: radius.toString(),
3232
+ "data-type": "pcb_smtpad",
3233
+ "data-pcb-layer": pad.layer,
3234
+ ...rotationTransformAttributes ?? {}
3235
+ }
3236
+ };
3237
+ return [coveredPadElement];
3238
+ }
3239
+ const substrateAttributes = {
3240
+ class: "pcb-soldermask-cutout",
3241
+ fill: colorMap2.substrate,
3242
+ x: isRotated ? (-maskWidth / 2).toString() : (x - maskWidth / 2).toString(),
3243
+ y: isRotated ? (-maskHeight / 2).toString() : (y - maskHeight / 2).toString(),
3244
+ width: maskWidth.toString(),
3245
+ height: maskHeight.toString(),
3246
+ rx: maskRadius.toString(),
3247
+ ry: maskRadius.toString(),
3248
+ "data-type": "pcb_soldermask_opening",
3249
+ "data-pcb-layer": pad.layer,
3250
+ ...rotationTransformAttributes ?? {}
3251
+ };
3252
+ const substrateElement = {
3253
+ name: "rect",
3254
+ type: "element",
2490
3255
  value: "",
2491
3256
  children: [],
2492
- attributes: {
2493
- ...padElement.attributes,
2494
- class: "pcb-solder-mask",
2495
- fill: solderMaskColor,
2496
- "data-type": "pcb_soldermask"
2497
- }
3257
+ attributes: substrateAttributes
2498
3258
  };
2499
- return [padElement, maskElement];
3259
+ return [substrateElement, padElement];
2500
3260
  }
2501
3261
  if (pad.shape === "circle") {
2502
3262
  const radius = pad.radius * Math.abs(transform.a);
@@ -2516,22 +3276,77 @@ function createSvgObjectsFromSmtPad(pad, ctx) {
2516
3276
  "data-pcb-layer": pad.layer
2517
3277
  }
2518
3278
  };
2519
- if (!shouldshowSolderMask) {
3279
+ if (!shouldShowSolderMask) {
2520
3280
  return [padElement];
2521
3281
  }
2522
- const maskElement = {
2523
- name: padElement.name,
2524
- type: padElement.type,
3282
+ const maskRadius = radius + soldermaskMargin;
3283
+ if (soldermaskMargin < 0) {
3284
+ const coveredPadElement = {
3285
+ name: "circle",
3286
+ type: "element",
3287
+ value: "",
3288
+ children: [],
3289
+ attributes: {
3290
+ class: "pcb-pad-covered",
3291
+ fill: soldermaskWithCopperUnderneathColor,
3292
+ cx: x.toString(),
3293
+ cy: y.toString(),
3294
+ r: radius.toString(),
3295
+ "data-type": "pcb_smtpad",
3296
+ "data-pcb-layer": pad.layer
3297
+ }
3298
+ };
3299
+ const exposedOpeningElement = {
3300
+ name: "circle",
3301
+ type: "element",
3302
+ value: "",
3303
+ children: [],
3304
+ attributes: {
3305
+ class: "pcb-pad-exposed",
3306
+ fill: layerNameToColor(pad.layer, colorMap2),
3307
+ cx: x.toString(),
3308
+ cy: y.toString(),
3309
+ r: maskRadius.toString(),
3310
+ "data-type": "pcb_soldermask",
3311
+ "data-pcb-layer": pad.layer
3312
+ }
3313
+ };
3314
+ return [coveredPadElement, exposedOpeningElement];
3315
+ }
3316
+ if (soldermaskMargin === 0) {
3317
+ const coveredPadElement = {
3318
+ name: "circle",
3319
+ type: "element",
3320
+ value: "",
3321
+ children: [],
3322
+ attributes: {
3323
+ class: "pcb-pad-covered",
3324
+ fill: soldermaskWithCopperUnderneathColor,
3325
+ cx: x.toString(),
3326
+ cy: y.toString(),
3327
+ r: radius.toString(),
3328
+ "data-type": "pcb_smtpad",
3329
+ "data-pcb-layer": pad.layer
3330
+ }
3331
+ };
3332
+ return [coveredPadElement];
3333
+ }
3334
+ const substrateElement = {
3335
+ name: "circle",
3336
+ type: "element",
2525
3337
  value: "",
2526
3338
  children: [],
2527
3339
  attributes: {
2528
- ...padElement.attributes,
2529
- class: "pcb-solder-mask",
2530
- fill: solderMaskColor,
2531
- "data-type": "pcb_soldermask"
3340
+ class: "pcb-soldermask-cutout",
3341
+ fill: colorMap2.substrate,
3342
+ cx: x.toString(),
3343
+ cy: y.toString(),
3344
+ r: maskRadius.toString(),
3345
+ "data-type": "pcb_soldermask_opening",
3346
+ "data-pcb-layer": pad.layer
2532
3347
  }
2533
3348
  };
2534
- return [padElement, maskElement];
3349
+ return [substrateElement, padElement];
2535
3350
  }
2536
3351
  if (pad.shape === "polygon") {
2537
3352
  const points = (pad.points ?? []).map(
@@ -2550,27 +3365,90 @@ function createSvgObjectsFromSmtPad(pad, ctx) {
2550
3365
  "data-pcb-layer": pad.layer
2551
3366
  }
2552
3367
  };
2553
- if (!shouldshowSolderMask) {
3368
+ if (!shouldShowSolderMask) {
2554
3369
  return [padElement];
2555
3370
  }
2556
- const maskElement = {
2557
- name: padElement.name,
2558
- type: padElement.type,
3371
+ let maskPoints = points;
3372
+ if (soldermaskMargin !== 0) {
3373
+ const centroidX = points.reduce((sum, p) => sum + p[0], 0) / points.length;
3374
+ const centroidY = points.reduce((sum, p) => sum + p[1], 0) / points.length;
3375
+ maskPoints = points.map(([px, py]) => {
3376
+ const dx = px - centroidX;
3377
+ const dy = py - centroidY;
3378
+ const distance3 = Math.sqrt(dx * dx + dy * dy);
3379
+ if (distance3 === 0) return [px, py];
3380
+ const normalizedDx = dx / distance3;
3381
+ const normalizedDy = dy / distance3;
3382
+ return [
3383
+ px + normalizedDx * soldermaskMargin,
3384
+ py + normalizedDy * soldermaskMargin
3385
+ ];
3386
+ });
3387
+ }
3388
+ if (soldermaskMargin < 0) {
3389
+ const coveredPadElement = {
3390
+ name: "polygon",
3391
+ type: "element",
3392
+ value: "",
3393
+ children: [],
3394
+ attributes: {
3395
+ class: "pcb-pad-covered",
3396
+ fill: soldermaskWithCopperUnderneathColor,
3397
+ points: points.map((p) => p.join(",")).join(" "),
3398
+ "data-type": "pcb_smtpad",
3399
+ "data-pcb-layer": pad.layer
3400
+ }
3401
+ };
3402
+ const exposedOpeningElement = {
3403
+ name: "polygon",
3404
+ type: "element",
3405
+ value: "",
3406
+ children: [],
3407
+ attributes: {
3408
+ class: "pcb-pad-exposed",
3409
+ fill: layerNameToColor(pad.layer, colorMap2),
3410
+ points: maskPoints.map((p) => p.join(",")).join(" "),
3411
+ "data-type": "pcb_soldermask",
3412
+ "data-pcb-layer": pad.layer
3413
+ }
3414
+ };
3415
+ return [coveredPadElement, exposedOpeningElement];
3416
+ }
3417
+ if (soldermaskMargin === 0) {
3418
+ const coveredPadElement = {
3419
+ name: "polygon",
3420
+ type: "element",
3421
+ value: "",
3422
+ children: [],
3423
+ attributes: {
3424
+ class: "pcb-pad-covered",
3425
+ fill: soldermaskWithCopperUnderneathColor,
3426
+ points: points.map((p) => p.join(",")).join(" "),
3427
+ "data-type": "pcb_smtpad",
3428
+ "data-pcb-layer": pad.layer
3429
+ }
3430
+ };
3431
+ return [coveredPadElement];
3432
+ }
3433
+ const substrateElement = {
3434
+ name: "polygon",
3435
+ type: "element",
2559
3436
  value: "",
2560
3437
  children: [],
2561
3438
  attributes: {
2562
- ...padElement.attributes,
2563
- class: "pcb-solder-mask",
2564
- fill: solderMaskColor,
2565
- "data-type": "pcb_soldermask"
3439
+ class: "pcb-soldermask-cutout",
3440
+ fill: colorMap2.substrate,
3441
+ points: maskPoints.map((p) => p.join(",")).join(" "),
3442
+ "data-type": "pcb_soldermask_opening",
3443
+ "data-pcb-layer": pad.layer
2566
3444
  }
2567
3445
  };
2568
- return [padElement, maskElement];
3446
+ return [substrateElement, padElement];
2569
3447
  }
2570
3448
  return [];
2571
3449
  }
2572
3450
  function createSvgObjectsFromPcbBoard(pcbBoard, ctx) {
2573
- const { transform, colorMap: colorMap2 } = ctx;
3451
+ const { transform, colorMap: colorMap2, showSolderMask } = ctx;
2574
3452
  const { width, height, center, outline } = pcbBoard;
2575
3453
  let path;
2576
3454
  if (outline && Array.isArray(outline) && outline.length >= 3) {
@@ -2600,35 +3478,69 @@ function createSvgObjectsFromPcbBoard(pcbBoard, ctx) {
2600
3478
  path = `M ${topLeft[0]} ${topLeft[1]} L ${topRight[0]} ${topRight[1]} L ${bottomRight[0]} ${bottomRight[1]} L ${bottomLeft[0]} ${bottomLeft[1]}`;
2601
3479
  }
2602
3480
  path += " Z";
2603
- return [
2604
- {
3481
+ const svgObjects = [];
3482
+ if (showSolderMask) {
3483
+ const layer = ctx.layer ?? "top";
3484
+ const maskLayer = layer === "bottom" ? "soldermask-bottom" : "soldermask-top";
3485
+ svgObjects.push({
2605
3486
  name: "path",
2606
3487
  type: "element",
2607
3488
  value: "",
2608
3489
  children: [],
2609
3490
  attributes: {
2610
- class: "pcb-board",
3491
+ class: "pcb-board-soldermask",
2611
3492
  d: path,
2612
- fill: "none",
2613
- stroke: colorMap2.boardOutline,
2614
- "stroke-width": (0.1 * Math.abs(transform.a)).toString(),
2615
- "data-type": "pcb_board",
2616
- "data-pcb-layer": "board"
3493
+ fill: colorMap2.soldermask.top,
3494
+ "fill-opacity": "0.8",
3495
+ stroke: "none",
3496
+ "data-type": "pcb_soldermask",
3497
+ "data-pcb-layer": maskLayer
2617
3498
  }
3499
+ });
3500
+ }
3501
+ svgObjects.push({
3502
+ name: "path",
3503
+ type: "element",
3504
+ value: "",
3505
+ children: [],
3506
+ attributes: {
3507
+ class: "pcb-board",
3508
+ d: path,
3509
+ fill: "none",
3510
+ stroke: colorMap2.boardOutline,
3511
+ "stroke-width": (0.1 * Math.abs(transform.a)).toString(),
3512
+ "data-type": "pcb_board",
3513
+ "data-pcb-layer": "board"
2618
3514
  }
2619
- ];
3515
+ });
3516
+ return svgObjects;
2620
3517
  }
2621
3518
  function createSvgObjectsFromPcbPanel(pcbPanel, ctx) {
2622
3519
  const { transform, colorMap: colorMap2, showSolderMask } = ctx;
2623
3520
  const width = Number(pcbPanel.width);
2624
3521
  const height = Number(pcbPanel.height);
2625
- const topLeft = applyToPoint(transform, [0, 0]);
2626
- const topRight = applyToPoint(transform, [width, 0]);
2627
- const bottomRight = applyToPoint(transform, [width, height]);
2628
- const bottomLeft = applyToPoint(transform, [0, height]);
3522
+ const center = pcbPanel.center ?? { x: width / 2, y: height / 2 };
3523
+ const halfWidth = width / 2;
3524
+ const halfHeight = height / 2;
3525
+ const topLeft = applyToPoint(transform, [
3526
+ center.x - halfWidth,
3527
+ center.y - halfHeight
3528
+ ]);
3529
+ const topRight = applyToPoint(transform, [
3530
+ center.x + halfWidth,
3531
+ center.y - halfHeight
3532
+ ]);
3533
+ const bottomRight = applyToPoint(transform, [
3534
+ center.x + halfWidth,
3535
+ center.y + halfHeight
3536
+ ]);
3537
+ const bottomLeft = applyToPoint(transform, [
3538
+ center.x - halfWidth,
3539
+ center.y + halfHeight
3540
+ ]);
2629
3541
  const path = `M ${topLeft[0]} ${topLeft[1]} L ${topRight[0]} ${topRight[1]} L ${bottomRight[0]} ${bottomRight[1]} L ${bottomLeft[0]} ${bottomLeft[1]} Z`;
2630
3542
  const isCoveredWithSolderMask = pcbPanel.covered_with_solder_mask !== false;
2631
- const shouldshowSolderMask = Boolean(
3543
+ const shouldShowSolderMask = Boolean(
2632
3544
  showSolderMask && isCoveredWithSolderMask
2633
3545
  );
2634
3546
  return [
@@ -2696,72 +3608,314 @@ function createSvgObjectsFromPcbVia(hole, ctx) {
2696
3608
  };
2697
3609
  }
2698
3610
  function createSvgObjectsFromPcbHole(hole, ctx) {
2699
- const { transform, colorMap: colorMap2 } = ctx;
3611
+ const { transform, colorMap: colorMap2, showSolderMask } = ctx;
3612
+ const layer = ctx.layer ?? "top";
2700
3613
  const [x, y] = applyToPoint(transform, [hole.x, hole.y]);
3614
+ const isCoveredWithSolderMask = Boolean(hole.is_covered_with_solder_mask);
3615
+ const soldermaskMargin = (hole.soldermask_margin ?? 0) * Math.abs(transform.a);
3616
+ const shouldShowSolderMask = showSolderMask && isCoveredWithSolderMask && soldermaskMargin !== 0;
3617
+ const solderMaskColor = colorMap2.soldermask.top;
2701
3618
  if (hole.hole_shape === "circle" || hole.hole_shape === "square") {
2702
3619
  const scaledDiameter = hole.hole_diameter * Math.abs(transform.a);
2703
3620
  const radius = scaledDiameter / 2;
2704
3621
  if (hole.hole_shape === "circle") {
2705
- return [
2706
- {
3622
+ const holeElement2 = {
3623
+ name: "circle",
3624
+ type: "element",
3625
+ attributes: {
3626
+ class: "pcb-hole",
3627
+ cx: x.toString(),
3628
+ cy: y.toString(),
3629
+ r: radius.toString(),
3630
+ fill: colorMap2.drill,
3631
+ "data-type": "pcb_hole",
3632
+ "data-pcb-layer": "drill"
3633
+ },
3634
+ children: [],
3635
+ value: ""
3636
+ };
3637
+ if (!shouldShowSolderMask) {
3638
+ return [holeElement2];
3639
+ }
3640
+ const maskRadius = radius + soldermaskMargin;
3641
+ if (soldermaskMargin < 0) {
3642
+ const coveredElement = {
2707
3643
  name: "circle",
2708
3644
  type: "element",
3645
+ value: "",
3646
+ children: [],
2709
3647
  attributes: {
2710
- class: "pcb-hole",
3648
+ class: "pcb-hole-covered",
3649
+ fill: solderMaskColor,
2711
3650
  cx: x.toString(),
2712
3651
  cy: y.toString(),
2713
3652
  r: radius.toString(),
2714
- fill: colorMap2.drill,
2715
3653
  "data-type": "pcb_hole",
2716
3654
  "data-pcb-layer": "drill"
2717
- },
3655
+ }
3656
+ };
3657
+ const exposedElement = {
3658
+ name: "circle",
3659
+ type: "element",
3660
+ value: "",
2718
3661
  children: [],
2719
- value: ""
3662
+ attributes: {
3663
+ class: "pcb-hole-exposed",
3664
+ fill: colorMap2.drill,
3665
+ cx: x.toString(),
3666
+ cy: y.toString(),
3667
+ r: maskRadius.toString(),
3668
+ "data-type": "pcb_soldermask",
3669
+ "data-pcb-layer": "drill"
3670
+ }
3671
+ };
3672
+ return [coveredElement, exposedElement];
3673
+ }
3674
+ const substrateElement2 = {
3675
+ name: "circle",
3676
+ type: "element",
3677
+ value: "",
3678
+ children: [],
3679
+ attributes: {
3680
+ class: "pcb-soldermask-cutout",
3681
+ fill: colorMap2.substrate,
3682
+ cx: x.toString(),
3683
+ cy: y.toString(),
3684
+ r: maskRadius.toString(),
3685
+ "data-type": "pcb_soldermask_opening",
3686
+ "data-pcb-layer": layer
2720
3687
  }
2721
- ];
3688
+ };
3689
+ return [substrateElement2, holeElement2];
2722
3690
  }
2723
- return [
2724
- {
3691
+ const holeElement = {
3692
+ name: "rect",
3693
+ type: "element",
3694
+ attributes: {
3695
+ class: "pcb-hole",
3696
+ x: (x - radius).toString(),
3697
+ y: (y - radius).toString(),
3698
+ width: scaledDiameter.toString(),
3699
+ height: scaledDiameter.toString(),
3700
+ fill: colorMap2.drill,
3701
+ "data-type": "pcb_hole",
3702
+ "data-pcb-layer": "drill"
3703
+ },
3704
+ children: [],
3705
+ value: ""
3706
+ };
3707
+ if (!shouldShowSolderMask) {
3708
+ return [holeElement];
3709
+ }
3710
+ const maskDiameter = scaledDiameter + 2 * soldermaskMargin;
3711
+ if (soldermaskMargin < 0) {
3712
+ const coveredElement = {
2725
3713
  name: "rect",
2726
3714
  type: "element",
3715
+ value: "",
3716
+ children: [],
2727
3717
  attributes: {
2728
- class: "pcb-hole",
3718
+ class: "pcb-hole-covered",
3719
+ fill: solderMaskColor,
2729
3720
  x: (x - radius).toString(),
2730
3721
  y: (y - radius).toString(),
2731
3722
  width: scaledDiameter.toString(),
2732
3723
  height: scaledDiameter.toString(),
2733
- fill: colorMap2.drill,
2734
3724
  "data-type": "pcb_hole",
2735
3725
  "data-pcb-layer": "drill"
2736
- },
3726
+ }
3727
+ };
3728
+ const exposedElement = {
3729
+ name: "rect",
3730
+ type: "element",
3731
+ value: "",
2737
3732
  children: [],
2738
- value: ""
3733
+ attributes: {
3734
+ class: "pcb-hole-exposed",
3735
+ fill: colorMap2.drill,
3736
+ x: (x - maskDiameter / 2).toString(),
3737
+ y: (y - maskDiameter / 2).toString(),
3738
+ width: maskDiameter.toString(),
3739
+ height: maskDiameter.toString(),
3740
+ "data-type": "pcb_soldermask",
3741
+ "data-pcb-layer": "drill"
3742
+ }
3743
+ };
3744
+ return [coveredElement, exposedElement];
3745
+ }
3746
+ const substrateElement = {
3747
+ name: "rect",
3748
+ type: "element",
3749
+ value: "",
3750
+ children: [],
3751
+ attributes: {
3752
+ class: "pcb-soldermask-cutout",
3753
+ fill: colorMap2.substrate,
3754
+ x: (x - maskDiameter / 2).toString(),
3755
+ y: (y - maskDiameter / 2).toString(),
3756
+ width: maskDiameter.toString(),
3757
+ height: maskDiameter.toString(),
3758
+ "data-type": "pcb_soldermask_opening",
3759
+ "data-pcb-layer": layer
2739
3760
  }
2740
- ];
3761
+ };
3762
+ return [substrateElement, holeElement];
2741
3763
  }
2742
3764
  if (hole.hole_shape === "oval") {
2743
3765
  const scaledWidth = hole.hole_width * Math.abs(transform.a);
2744
3766
  const scaledHeight = hole.hole_height * Math.abs(transform.a);
2745
3767
  const rx = scaledWidth / 2;
2746
3768
  const ry = scaledHeight / 2;
2747
- return [
2748
- {
3769
+ const holeElement = {
3770
+ name: "ellipse",
3771
+ type: "element",
3772
+ attributes: {
3773
+ class: "pcb-hole",
3774
+ cx: x.toString(),
3775
+ cy: y.toString(),
3776
+ rx: rx.toString(),
3777
+ ry: ry.toString(),
3778
+ fill: colorMap2.drill,
3779
+ "data-type": "pcb_hole",
3780
+ "data-pcb-layer": "drill"
3781
+ },
3782
+ children: [],
3783
+ value: ""
3784
+ };
3785
+ if (!shouldShowSolderMask) {
3786
+ return [holeElement];
3787
+ }
3788
+ const maskRx = rx + soldermaskMargin;
3789
+ const maskRy = ry + soldermaskMargin;
3790
+ if (soldermaskMargin < 0) {
3791
+ const coveredElement = {
2749
3792
  name: "ellipse",
2750
3793
  type: "element",
3794
+ value: "",
3795
+ children: [],
2751
3796
  attributes: {
2752
- class: "pcb-hole",
3797
+ class: "pcb-hole-covered",
3798
+ fill: solderMaskColor,
2753
3799
  cx: x.toString(),
2754
3800
  cy: y.toString(),
2755
3801
  rx: rx.toString(),
2756
3802
  ry: ry.toString(),
3803
+ "data-type": "pcb_hole",
3804
+ "data-pcb-layer": "drill"
3805
+ }
3806
+ };
3807
+ const exposedElement = {
3808
+ name: "ellipse",
3809
+ type: "element",
3810
+ value: "",
3811
+ children: [],
3812
+ attributes: {
3813
+ class: "pcb-hole-exposed",
2757
3814
  fill: colorMap2.drill,
3815
+ cx: x.toString(),
3816
+ cy: y.toString(),
3817
+ rx: maskRx.toString(),
3818
+ ry: maskRy.toString(),
3819
+ "data-type": "pcb_soldermask",
3820
+ "data-pcb-layer": "drill"
3821
+ }
3822
+ };
3823
+ return [coveredElement, exposedElement];
3824
+ }
3825
+ const substrateElement = {
3826
+ name: "ellipse",
3827
+ type: "element",
3828
+ value: "",
3829
+ children: [],
3830
+ attributes: {
3831
+ class: "pcb-soldermask-cutout",
3832
+ fill: colorMap2.substrate,
3833
+ cx: x.toString(),
3834
+ cy: y.toString(),
3835
+ rx: maskRx.toString(),
3836
+ ry: maskRy.toString(),
3837
+ "data-type": "pcb_soldermask_opening",
3838
+ "data-pcb-layer": layer
3839
+ }
3840
+ };
3841
+ return [substrateElement, holeElement];
3842
+ }
3843
+ if (hole.hole_shape === "rect") {
3844
+ const scaledWidth = hole.hole_width * Math.abs(transform.a);
3845
+ const scaledHeight = hole.hole_height * Math.abs(transform.a);
3846
+ const holeElement = {
3847
+ name: "rect",
3848
+ type: "element",
3849
+ attributes: {
3850
+ class: "pcb-hole",
3851
+ x: (x - scaledWidth / 2).toString(),
3852
+ y: (y - scaledHeight / 2).toString(),
3853
+ width: scaledWidth.toString(),
3854
+ height: scaledHeight.toString(),
3855
+ fill: colorMap2.drill,
3856
+ "data-type": "pcb_hole",
3857
+ "data-pcb-layer": "drill"
3858
+ },
3859
+ children: [],
3860
+ value: ""
3861
+ };
3862
+ if (!shouldShowSolderMask) {
3863
+ return [holeElement];
3864
+ }
3865
+ const maskWidth = scaledWidth + 2 * soldermaskMargin;
3866
+ const maskHeight = scaledHeight + 2 * soldermaskMargin;
3867
+ if (soldermaskMargin < 0) {
3868
+ const coveredElement = {
3869
+ name: "rect",
3870
+ type: "element",
3871
+ value: "",
3872
+ children: [],
3873
+ attributes: {
3874
+ class: "pcb-hole-covered",
3875
+ fill: solderMaskColor,
3876
+ x: (x - scaledWidth / 2).toString(),
3877
+ y: (y - scaledHeight / 2).toString(),
3878
+ width: scaledWidth.toString(),
3879
+ height: scaledHeight.toString(),
2758
3880
  "data-type": "pcb_hole",
2759
3881
  "data-pcb-layer": "drill"
2760
- },
3882
+ }
3883
+ };
3884
+ const exposedElement = {
3885
+ name: "rect",
3886
+ type: "element",
3887
+ value: "",
2761
3888
  children: [],
2762
- value: ""
3889
+ attributes: {
3890
+ class: "pcb-hole-exposed",
3891
+ fill: colorMap2.drill,
3892
+ x: (x - maskWidth / 2).toString(),
3893
+ y: (y - maskHeight / 2).toString(),
3894
+ width: maskWidth.toString(),
3895
+ height: maskHeight.toString(),
3896
+ "data-type": "pcb_soldermask",
3897
+ "data-pcb-layer": "drill"
3898
+ }
3899
+ };
3900
+ return [coveredElement, exposedElement];
3901
+ }
3902
+ const substrateElement = {
3903
+ name: "rect",
3904
+ type: "element",
3905
+ value: "",
3906
+ children: [],
3907
+ attributes: {
3908
+ class: "pcb-soldermask-cutout",
3909
+ fill: colorMap2.substrate,
3910
+ x: (x - maskWidth / 2).toString(),
3911
+ y: (y - maskHeight / 2).toString(),
3912
+ width: maskWidth.toString(),
3913
+ height: maskHeight.toString(),
3914
+ "data-type": "pcb_soldermask_opening",
3915
+ "data-pcb-layer": layer
2763
3916
  }
2764
- ];
3917
+ };
3918
+ return [substrateElement, holeElement];
2765
3919
  }
2766
3920
  if (hole.hole_shape === "pill") {
2767
3921
  const scaledWidth = hole.hole_width * Math.abs(transform.a);
@@ -2778,21 +3932,79 @@ function createSvgObjectsFromPcbHole(hole, ctx) {
2778
3932
  // Vertical pill (taller than wide)
2779
3933
  `M${x - radius},${y - straightLength / 2} v${straightLength} a${radius},${radius} 0 0 0 ${scaledWidth},0 v-${straightLength} a${radius},${radius} 0 0 0 -${scaledWidth},0 z`
2780
3934
  );
2781
- return [
2782
- {
3935
+ const holeElement = {
3936
+ name: "path",
3937
+ type: "element",
3938
+ attributes: {
3939
+ class: "pcb-hole",
3940
+ fill: colorMap2.drill,
3941
+ d: pathD,
3942
+ "data-type": "pcb_hole",
3943
+ "data-pcb-layer": "drill"
3944
+ },
3945
+ children: [],
3946
+ value: ""
3947
+ };
3948
+ if (!shouldShowSolderMask) {
3949
+ return [holeElement];
3950
+ }
3951
+ const maskWidth = scaledWidth + 2 * soldermaskMargin;
3952
+ const maskHeight = scaledHeight + 2 * soldermaskMargin;
3953
+ const maskIsHorizontal = maskWidth > maskHeight;
3954
+ const maskRadius = Math.min(maskWidth, maskHeight) / 2;
3955
+ const maskStraightLength = Math.abs(
3956
+ maskIsHorizontal ? maskWidth - maskHeight : maskHeight - maskWidth
3957
+ );
3958
+ const maskPathD = maskIsHorizontal ? (
3959
+ // Horizontal pill (wider than tall)
3960
+ `M${x - maskStraightLength / 2},${y - maskRadius} h${maskStraightLength} a${maskRadius},${maskRadius} 0 0 1 0,${maskHeight} h-${maskStraightLength} a${maskRadius},${maskRadius} 0 0 1 0,-${maskHeight} z`
3961
+ ) : (
3962
+ // Vertical pill (taller than wide)
3963
+ `M${x - maskRadius},${y - maskStraightLength / 2} v${maskStraightLength} a${maskRadius},${maskRadius} 0 0 0 ${maskWidth},0 v-${maskStraightLength} a${maskRadius},${maskRadius} 0 0 0 -${maskWidth},0 z`
3964
+ );
3965
+ if (soldermaskMargin < 0) {
3966
+ const coveredElement = {
2783
3967
  name: "path",
2784
3968
  type: "element",
3969
+ value: "",
3970
+ children: [],
2785
3971
  attributes: {
2786
- class: "pcb-hole",
2787
- fill: colorMap2.drill,
3972
+ class: "pcb-hole-covered",
3973
+ fill: solderMaskColor,
2788
3974
  d: pathD,
2789
3975
  "data-type": "pcb_hole",
2790
3976
  "data-pcb-layer": "drill"
2791
- },
3977
+ }
3978
+ };
3979
+ const exposedElement = {
3980
+ name: "path",
3981
+ type: "element",
3982
+ value: "",
2792
3983
  children: [],
2793
- value: ""
3984
+ attributes: {
3985
+ class: "pcb-hole-exposed",
3986
+ fill: colorMap2.drill,
3987
+ d: maskPathD,
3988
+ "data-type": "pcb_soldermask",
3989
+ "data-pcb-layer": "drill"
3990
+ }
3991
+ };
3992
+ return [coveredElement, exposedElement];
3993
+ }
3994
+ const substrateElement = {
3995
+ name: "path",
3996
+ type: "element",
3997
+ value: "",
3998
+ children: [],
3999
+ attributes: {
4000
+ class: "pcb-soldermask-cutout",
4001
+ fill: colorMap2.substrate,
4002
+ d: maskPathD,
4003
+ "data-type": "pcb_soldermask_opening",
4004
+ "data-pcb-layer": layer
2794
4005
  }
2795
- ];
4006
+ };
4007
+ return [substrateElement, holeElement];
2796
4008
  }
2797
4009
  if (hole.hole_shape === "rotated_pill") {
2798
4010
  const scaledWidth = hole.hole_width * Math.abs(transform.a);
@@ -2810,22 +4022,82 @@ function createSvgObjectsFromPcbHole(hole, ctx) {
2810
4022
  // Vertical pill (taller than wide)
2811
4023
  `M${-radius},${-straightLength / 2} v${straightLength} a${radius},${radius} 0 0 0 ${scaledWidth},0 v-${straightLength} a${radius},${radius} 0 0 0 -${scaledWidth},0 z`
2812
4024
  );
2813
- return [
2814
- {
4025
+ const holeElement = {
4026
+ name: "path",
4027
+ type: "element",
4028
+ attributes: {
4029
+ class: "pcb-hole",
4030
+ fill: colorMap2.drill,
4031
+ d: pathD,
4032
+ transform: `translate(${x} ${y}) rotate(${-rotation})`,
4033
+ "data-type": "pcb_hole",
4034
+ "data-pcb-layer": "drill"
4035
+ },
4036
+ children: [],
4037
+ value: ""
4038
+ };
4039
+ if (!shouldShowSolderMask) {
4040
+ return [holeElement];
4041
+ }
4042
+ const maskWidth = scaledWidth + 2 * soldermaskMargin;
4043
+ const maskHeight = scaledHeight + 2 * soldermaskMargin;
4044
+ const maskIsHorizontal = maskWidth > maskHeight;
4045
+ const maskRadius = Math.min(maskWidth, maskHeight) / 2;
4046
+ const maskStraightLength = Math.abs(
4047
+ maskIsHorizontal ? maskWidth - maskHeight : maskHeight - maskWidth
4048
+ );
4049
+ const maskPathD = maskIsHorizontal ? (
4050
+ // Horizontal pill (wider than tall)
4051
+ `M${-maskStraightLength / 2},${-maskRadius} h${maskStraightLength} a${maskRadius},${maskRadius} 0 0 1 0,${maskHeight} h-${maskStraightLength} a${maskRadius},${maskRadius} 0 0 1 0,-${maskHeight} z`
4052
+ ) : (
4053
+ // Vertical pill (taller than wide)
4054
+ `M${-maskRadius},${-maskStraightLength / 2} v${maskStraightLength} a${maskRadius},${maskRadius} 0 0 0 ${maskWidth},0 v-${maskStraightLength} a${maskRadius},${maskRadius} 0 0 0 -${maskWidth},0 z`
4055
+ );
4056
+ if (soldermaskMargin < 0) {
4057
+ const coveredElement = {
2815
4058
  name: "path",
2816
4059
  type: "element",
4060
+ value: "",
4061
+ children: [],
2817
4062
  attributes: {
2818
- class: "pcb-hole",
2819
- fill: colorMap2.drill,
4063
+ class: "pcb-hole-covered",
4064
+ fill: solderMaskColor,
2820
4065
  d: pathD,
2821
4066
  transform: `translate(${x} ${y}) rotate(${-rotation})`,
2822
4067
  "data-type": "pcb_hole",
2823
4068
  "data-pcb-layer": "drill"
2824
- },
4069
+ }
4070
+ };
4071
+ const exposedElement = {
4072
+ name: "path",
4073
+ type: "element",
4074
+ value: "",
2825
4075
  children: [],
2826
- value: ""
4076
+ attributes: {
4077
+ class: "pcb-hole-exposed",
4078
+ fill: colorMap2.drill,
4079
+ d: maskPathD,
4080
+ transform: `translate(${x} ${y}) rotate(${-rotation})`,
4081
+ "data-type": "pcb_soldermask",
4082
+ "data-pcb-layer": "drill"
4083
+ }
4084
+ };
4085
+ return [coveredElement, exposedElement];
4086
+ }
4087
+ const substrateElement = {
4088
+ name: "path",
4089
+ type: "element",
4090
+ value: "",
4091
+ children: [],
4092
+ attributes: {
4093
+ class: "pcb-soldermask-cutout",
4094
+ fill: colorMap2.substrate,
4095
+ d: maskPathD,
4096
+ "data-type": "pcb_soldermask_opening",
4097
+ "data-pcb-layer": layer
2827
4098
  }
2828
- ];
4099
+ };
4100
+ return [substrateElement, holeElement];
2829
4101
  }
2830
4102
  return [];
2831
4103
  }
@@ -2939,23 +4211,34 @@ function createSvgObjectsFromPcbCutout(cutout, ctx) {
2939
4211
  const scaledWidth = rectCutout.width * Math.abs(transform.a);
2940
4212
  const scaledHeight = rectCutout.height * Math.abs(transform.d);
2941
4213
  const svgRotation = -(rectCutout.rotation ?? 0);
4214
+ const { corner_radius } = rectCutout;
4215
+ const baseCornerRadius = typeof corner_radius === "number" && corner_radius > 0 ? corner_radius : 0;
4216
+ const transformedCornerRadiusX = baseCornerRadius * Math.abs(transform.a);
4217
+ const transformedCornerRadiusY = baseCornerRadius * Math.abs(transform.d);
4218
+ const attributes = {
4219
+ class: "pcb-cutout pcb-cutout-rect",
4220
+ x: (-scaledWidth / 2).toString(),
4221
+ y: (-scaledHeight / 2).toString(),
4222
+ width: scaledWidth.toString(),
4223
+ height: scaledHeight.toString(),
4224
+ fill: colorMap2.drill,
4225
+ transform: toString(
4226
+ compose(translate(cx, cy), rotate(svgRotation * Math.PI / 180))
4227
+ ),
4228
+ "data-type": "pcb_cutout",
4229
+ "data-pcb-layer": "drill"
4230
+ };
4231
+ if (transformedCornerRadiusX > 0) {
4232
+ attributes.rx = transformedCornerRadiusX.toString();
4233
+ }
4234
+ if (transformedCornerRadiusY > 0) {
4235
+ attributes.ry = transformedCornerRadiusY.toString();
4236
+ }
2942
4237
  return [
2943
4238
  {
2944
4239
  name: "rect",
2945
4240
  type: "element",
2946
- attributes: {
2947
- class: "pcb-cutout pcb-cutout-rect",
2948
- x: (-scaledWidth / 2).toString(),
2949
- y: (-scaledHeight / 2).toString(),
2950
- width: scaledWidth.toString(),
2951
- height: scaledHeight.toString(),
2952
- fill: colorMap2.drill,
2953
- transform: toString(
2954
- compose(translate(cx, cy), rotate(svgRotation * Math.PI / 180))
2955
- ),
2956
- "data-type": "pcb_cutout",
2957
- "data-pcb-layer": "drill"
2958
- },
4241
+ attributes,
2959
4242
  children: [],
2960
4243
  value: ""
2961
4244
  }
@@ -3038,39 +4321,113 @@ function ringToPathD(vertices, transform) {
3038
4321
  d += " Z";
3039
4322
  return d;
3040
4323
  }
4324
+ function createSoldermaskCutoutElement({
4325
+ elementType,
4326
+ shapeAttributes,
4327
+ layer,
4328
+ colorMap: colorMap2,
4329
+ additionalAttributes
4330
+ }) {
4331
+ const baseAttributes = {
4332
+ class: "pcb-soldermask-cutout",
4333
+ fill: colorMap2.substrate,
4334
+ "data-type": "pcb_soldermask_opening",
4335
+ "data-pcb-layer": layer,
4336
+ ...shapeAttributes,
4337
+ ...additionalAttributes
4338
+ };
4339
+ return {
4340
+ name: elementType,
4341
+ type: "element",
4342
+ value: "",
4343
+ children: [],
4344
+ attributes: baseAttributes
4345
+ };
4346
+ }
4347
+ function createSoldermaskOverlayElement({
4348
+ elementType,
4349
+ shapeAttributes,
4350
+ layer,
4351
+ fillColor,
4352
+ fillOpacity,
4353
+ className,
4354
+ additionalAttributes
4355
+ }) {
4356
+ const baseAttributes = {
4357
+ class: className,
4358
+ fill: fillColor,
4359
+ "fill-opacity": fillOpacity,
4360
+ "data-type": "pcb_soldermask",
4361
+ "data-pcb-layer": layer,
4362
+ ...shapeAttributes,
4363
+ ...additionalAttributes
4364
+ };
4365
+ return {
4366
+ name: elementType,
4367
+ type: "element",
4368
+ value: "",
4369
+ children: [],
4370
+ attributes: baseAttributes
4371
+ };
4372
+ }
3041
4373
  function createSvgObjectsFromPcbCopperPour(pour, ctx) {
3042
- const { transform, layer: layerFilter, colorMap: colorMap2 } = ctx;
4374
+ const { transform, layer: layerFilter, colorMap: colorMap2, showSolderMask } = ctx;
3043
4375
  const { layer } = pour;
3044
4376
  if (layerFilter && layer !== layerFilter) return [];
3045
4377
  const color = layerNameToColor(layer, colorMap2);
3046
4378
  const opacity = "0.5";
4379
+ const isCoveredWithSolderMask = pour.covered_with_solder_mask !== false;
4380
+ const maskOverlayColor = layer === "bottom" ? colorMap2.soldermaskOverCopper.bottom : colorMap2.soldermaskOverCopper.top;
4381
+ const maskOverlayOpacity = "0.9";
3047
4382
  if (pour.shape === "rect") {
3048
4383
  const [cx, cy] = applyToPoint(transform, [pour.center.x, pour.center.y]);
3049
4384
  const scaledWidth = pour.width * Math.abs(transform.a);
3050
4385
  const scaledHeight = pour.height * Math.abs(transform.d);
3051
4386
  const svgRotation = -(pour.rotation ?? 0);
3052
- return [
3053
- {
3054
- name: "rect",
3055
- type: "element",
3056
- attributes: {
3057
- class: "pcb-copper-pour pcb-copper-pour-rect",
3058
- x: (-scaledWidth / 2).toString(),
3059
- y: (-scaledHeight / 2).toString(),
3060
- width: scaledWidth.toString(),
3061
- height: scaledHeight.toString(),
3062
- fill: color,
3063
- "fill-opacity": opacity,
3064
- transform: toString(
3065
- compose(translate(cx, cy), rotate(svgRotation * Math.PI / 180))
3066
- ),
3067
- "data-type": "pcb_copper_pour",
3068
- "data-pcb-layer": layer
3069
- },
3070
- children: [],
3071
- value: ""
4387
+ const rectAttributes = {
4388
+ x: (-scaledWidth / 2).toString(),
4389
+ y: (-scaledHeight / 2).toString(),
4390
+ width: scaledWidth.toString(),
4391
+ height: scaledHeight.toString(),
4392
+ transform: toString(
4393
+ compose(translate(cx, cy), rotate(svgRotation * Math.PI / 180))
4394
+ )
4395
+ };
4396
+ const copperRect = {
4397
+ name: "rect",
4398
+ type: "element",
4399
+ value: "",
4400
+ children: [],
4401
+ attributes: {
4402
+ class: "pcb-copper-pour pcb-copper-pour-rect",
4403
+ ...rectAttributes,
4404
+ fill: color,
4405
+ "fill-opacity": opacity,
4406
+ "data-type": "pcb_copper_pour",
4407
+ "data-pcb-layer": layer
3072
4408
  }
3073
- ];
4409
+ };
4410
+ const maskRect = showSolderMask ? isCoveredWithSolderMask ? createSoldermaskOverlayElement({
4411
+ elementType: "rect",
4412
+ shapeAttributes: rectAttributes,
4413
+ layer,
4414
+ fillColor: maskOverlayColor,
4415
+ fillOpacity: maskOverlayOpacity,
4416
+ className: "pcb-soldermask-covered-pour"
4417
+ }) : createSoldermaskCutoutElement({
4418
+ elementType: "rect",
4419
+ shapeAttributes: rectAttributes,
4420
+ layer,
4421
+ colorMap: colorMap2
4422
+ }) : null;
4423
+ if (!maskRect) {
4424
+ return [copperRect];
4425
+ }
4426
+ const isSubstrateOnly = !isCoveredWithSolderMask && pour.pcb_copper_pour_id?.includes("substrate_only");
4427
+ if (isSubstrateOnly) {
4428
+ return [maskRect];
4429
+ }
4430
+ return [copperRect, maskRect];
3074
4431
  }
3075
4432
  if (pour.shape === "polygon") {
3076
4433
  if (!pour.points || pour.points.length === 0) return [];
@@ -3078,22 +4435,41 @@ function createSvgObjectsFromPcbCopperPour(pour, ctx) {
3078
4435
  (p) => applyToPoint(transform, [p.x, p.y])
3079
4436
  );
3080
4437
  const pointsString = transformedPoints.map((p) => `${p[0]},${p[1]}`).join(" ");
3081
- return [
3082
- {
3083
- name: "polygon",
3084
- type: "element",
3085
- attributes: {
3086
- class: "pcb-copper-pour pcb-copper-pour-polygon",
3087
- points: pointsString,
3088
- fill: color,
3089
- "fill-opacity": opacity,
3090
- "data-type": "pcb_copper_pour",
3091
- "data-pcb-layer": layer
3092
- },
3093
- children: [],
3094
- value: ""
4438
+ const copperPolygon = {
4439
+ name: "polygon",
4440
+ type: "element",
4441
+ value: "",
4442
+ children: [],
4443
+ attributes: {
4444
+ class: "pcb-copper-pour pcb-copper-pour-polygon",
4445
+ points: pointsString,
4446
+ fill: color,
4447
+ "fill-opacity": opacity,
4448
+ "data-type": "pcb_copper_pour",
4449
+ "data-pcb-layer": layer
3095
4450
  }
3096
- ];
4451
+ };
4452
+ const maskPolygon = showSolderMask ? isCoveredWithSolderMask ? createSoldermaskOverlayElement({
4453
+ elementType: "polygon",
4454
+ shapeAttributes: { points: pointsString },
4455
+ layer,
4456
+ fillColor: maskOverlayColor,
4457
+ fillOpacity: maskOverlayOpacity,
4458
+ className: "pcb-soldermask-covered-pour"
4459
+ }) : createSoldermaskCutoutElement({
4460
+ elementType: "polygon",
4461
+ shapeAttributes: { points: pointsString },
4462
+ layer,
4463
+ colorMap: colorMap2
4464
+ }) : null;
4465
+ if (!maskPolygon) {
4466
+ return [copperPolygon];
4467
+ }
4468
+ const isSubstrateOnly = !isCoveredWithSolderMask && pour.pcb_copper_pour_id?.includes("substrate_only");
4469
+ if (isSubstrateOnly) {
4470
+ return [maskPolygon];
4471
+ }
4472
+ return [copperPolygon, maskPolygon];
3097
4473
  }
3098
4474
  if (pour.shape === "brep") {
3099
4475
  const { brep_shape } = pour;
@@ -3101,23 +4477,42 @@ function createSvgObjectsFromPcbCopperPour(pour, ctx) {
3101
4477
  for (const inner_ring of brep_shape.inner_rings ?? []) {
3102
4478
  d += ` ${ringToPathD(inner_ring.vertices, transform)}`;
3103
4479
  }
3104
- return [
3105
- {
3106
- name: "path",
3107
- type: "element",
3108
- attributes: {
3109
- class: "pcb-copper-pour pcb-copper-pour-brep",
3110
- d,
3111
- fill: color,
3112
- "fill-rule": "evenodd",
3113
- "fill-opacity": opacity,
3114
- "data-type": "pcb_copper_pour",
3115
- "data-pcb-layer": layer
3116
- },
3117
- children: [],
3118
- value: ""
4480
+ const copperPath = {
4481
+ name: "path",
4482
+ type: "element",
4483
+ value: "",
4484
+ children: [],
4485
+ attributes: {
4486
+ class: "pcb-copper-pour pcb-copper-pour-brep",
4487
+ d,
4488
+ fill: color,
4489
+ "fill-rule": "evenodd",
4490
+ "fill-opacity": opacity,
4491
+ "data-type": "pcb_copper_pour",
4492
+ "data-pcb-layer": layer
3119
4493
  }
3120
- ];
4494
+ };
4495
+ const maskPath = showSolderMask ? isCoveredWithSolderMask ? createSoldermaskOverlayElement({
4496
+ elementType: "path",
4497
+ shapeAttributes: { d, "fill-rule": "evenodd" },
4498
+ layer,
4499
+ fillColor: maskOverlayColor,
4500
+ fillOpacity: maskOverlayOpacity,
4501
+ className: "pcb-soldermask-covered-pour"
4502
+ }) : createSoldermaskCutoutElement({
4503
+ elementType: "path",
4504
+ shapeAttributes: { d, "fill-rule": "evenodd" },
4505
+ layer,
4506
+ colorMap: colorMap2
4507
+ }) : null;
4508
+ if (!maskPath) {
4509
+ return [copperPath];
4510
+ }
4511
+ const isSubstrateOnly = !isCoveredWithSolderMask && pour.pcb_copper_pour_id?.includes("substrate_only");
4512
+ if (isSubstrateOnly) {
4513
+ return [maskPath];
4514
+ }
4515
+ return [copperPath, maskPath];
3121
4516
  }
3122
4517
  return [];
3123
4518
  }
@@ -3255,45 +4650,362 @@ function createMajorGridPatternChildren(cellSize, majorCellSize, lineColor, majo
3255
4650
  }
3256
4651
  return children;
3257
4652
  }
4653
+ var OFFSET_THRESHOLD_MM = 0.01;
4654
+ var TICK_SIZE_PX = 4;
4655
+ var LABEL_GAP_PX = 8;
4656
+ var LABEL_FONT_SIZE_PX = 11;
4657
+ var STROKE_WIDTH_PX = 1;
4658
+ var ANCHOR_MARKER_SIZE_PX = 5;
4659
+ var ANCHOR_MARKER_STROKE_WIDTH_PX = 1.5;
4660
+ var COMPONENT_GAP_PX = 15;
4661
+ var COMPONENT_SIDE_GAP_PX = 10;
4662
+ var DISTANCE_MULTIPLIER = 0.2;
4663
+ var MAX_OFFSET_PX = 50;
4664
+ function createAnchorOffsetIndicators(params) {
4665
+ const {
4666
+ groupAnchorPosition,
4667
+ componentPosition,
4668
+ transform,
4669
+ componentWidth = 0,
4670
+ componentHeight = 0
4671
+ } = params;
4672
+ const objects = [];
4673
+ const [screenGroupAnchorX, screenGroupAnchorY] = applyToPoint(transform, [
4674
+ groupAnchorPosition.x,
4675
+ groupAnchorPosition.y
4676
+ ]);
4677
+ const [screenComponentX, screenComponentY] = applyToPoint(transform, [
4678
+ componentPosition.x,
4679
+ componentPosition.y
4680
+ ]);
4681
+ const offsetX = componentPosition.x - groupAnchorPosition.x;
4682
+ const offsetY = componentPosition.y - groupAnchorPosition.y;
4683
+ const scale9 = Math.abs(transform.a);
4684
+ const screenComponentWidth = componentWidth * scale9;
4685
+ const screenComponentHeight = componentHeight * scale9;
4686
+ objects.push(createAnchorMarker(screenGroupAnchorX, screenGroupAnchorY));
4687
+ objects.push({
4688
+ name: "line",
4689
+ type: "element",
4690
+ attributes: {
4691
+ x1: screenGroupAnchorX.toString(),
4692
+ y1: screenGroupAnchorY.toString(),
4693
+ x2: screenComponentX.toString(),
4694
+ y2: screenComponentY.toString(),
4695
+ stroke: "#ffffff",
4696
+ "stroke-width": "0.5",
4697
+ "stroke-dasharray": "3,3",
4698
+ opacity: "0.5",
4699
+ class: "anchor-offset-connector"
4700
+ },
4701
+ children: [],
4702
+ value: ""
4703
+ });
4704
+ objects.push({
4705
+ name: "circle",
4706
+ type: "element",
4707
+ attributes: {
4708
+ cx: screenComponentX.toString(),
4709
+ cy: screenComponentY.toString(),
4710
+ r: "2",
4711
+ fill: "#ffffff",
4712
+ opacity: "0.7",
4713
+ class: "anchor-offset-component-marker"
4714
+ },
4715
+ children: [],
4716
+ value: ""
4717
+ });
4718
+ const yDistance = Math.abs(screenComponentY - screenGroupAnchorY);
4719
+ const xDistance = Math.abs(screenComponentX - screenGroupAnchorX);
4720
+ const totalDistance = Math.sqrt(xDistance * xDistance + yDistance * yDistance);
4721
+ const componentHeightOffset = screenComponentHeight / 2 + COMPONENT_GAP_PX;
4722
+ const dynamicOffset = Math.max(
4723
+ componentHeightOffset,
4724
+ Math.min(MAX_OFFSET_PX, totalDistance * DISTANCE_MULTIPLIER)
4725
+ );
4726
+ const horizontalLineY = offsetY > 0 ? screenComponentY - dynamicOffset : screenComponentY + dynamicOffset;
4727
+ const componentWidthOffset = screenComponentWidth / 2 + COMPONENT_SIDE_GAP_PX;
4728
+ const verticalLineX = offsetX > 0 ? screenComponentX + componentWidthOffset : screenComponentX - componentWidthOffset;
4729
+ if (Math.abs(offsetX) > OFFSET_THRESHOLD_MM) {
4730
+ objects.push(
4731
+ ...createHorizontalDimension({
4732
+ startX: screenGroupAnchorX,
4733
+ endX: screenComponentX,
4734
+ y: horizontalLineY,
4735
+ offsetMm: offsetX,
4736
+ offsetY
4737
+ })
4738
+ );
4739
+ }
4740
+ if (Math.abs(offsetY) > OFFSET_THRESHOLD_MM) {
4741
+ objects.push(
4742
+ ...createVerticalDimension({
4743
+ x: verticalLineX,
4744
+ startY: screenGroupAnchorY,
4745
+ endY: screenComponentY,
4746
+ offsetMm: -offsetY,
4747
+ offsetX
4748
+ })
4749
+ );
4750
+ }
4751
+ return objects;
4752
+ }
4753
+ function createAnchorMarker(x, y) {
4754
+ return {
4755
+ name: "g",
4756
+ type: "element",
4757
+ attributes: {
4758
+ class: "anchor-offset-marker",
4759
+ "data-type": "anchor_offset_marker"
4760
+ },
4761
+ children: [
4762
+ {
4763
+ name: "line",
4764
+ type: "element",
4765
+ attributes: {
4766
+ x1: x.toString(),
4767
+ y1: (y - ANCHOR_MARKER_SIZE_PX).toString(),
4768
+ x2: x.toString(),
4769
+ y2: (y + ANCHOR_MARKER_SIZE_PX).toString(),
4770
+ stroke: "#ffffff",
4771
+ "stroke-width": ANCHOR_MARKER_STROKE_WIDTH_PX.toString(),
4772
+ "stroke-linecap": "round"
4773
+ },
4774
+ children: [],
4775
+ value: ""
4776
+ },
4777
+ {
4778
+ name: "line",
4779
+ type: "element",
4780
+ attributes: {
4781
+ x1: (x - ANCHOR_MARKER_SIZE_PX).toString(),
4782
+ y1: y.toString(),
4783
+ x2: (x + ANCHOR_MARKER_SIZE_PX).toString(),
4784
+ y2: y.toString(),
4785
+ stroke: "#ffffff",
4786
+ "stroke-width": ANCHOR_MARKER_STROKE_WIDTH_PX.toString(),
4787
+ "stroke-linecap": "round"
4788
+ },
4789
+ children: [],
4790
+ value: ""
4791
+ }
4792
+ ],
4793
+ value: ""
4794
+ };
4795
+ }
4796
+ function createHorizontalDimension({
4797
+ startX,
4798
+ endX,
4799
+ y,
4800
+ offsetMm,
4801
+ offsetY
4802
+ }) {
4803
+ const objects = [];
4804
+ objects.push({
4805
+ name: "line",
4806
+ type: "element",
4807
+ attributes: {
4808
+ x1: startX.toString(),
4809
+ y1: y.toString(),
4810
+ x2: endX.toString(),
4811
+ y2: y.toString(),
4812
+ stroke: "#ffffff",
4813
+ "stroke-width": STROKE_WIDTH_PX.toString(),
4814
+ class: "anchor-offset-dimension-x"
4815
+ },
4816
+ children: [],
4817
+ value: ""
4818
+ });
4819
+ objects.push({
4820
+ name: "line",
4821
+ type: "element",
4822
+ attributes: {
4823
+ x1: startX.toString(),
4824
+ y1: (y - TICK_SIZE_PX).toString(),
4825
+ x2: startX.toString(),
4826
+ y2: (y + TICK_SIZE_PX).toString(),
4827
+ stroke: "#ffffff",
4828
+ "stroke-width": STROKE_WIDTH_PX.toString()
4829
+ },
4830
+ children: [],
4831
+ value: ""
4832
+ });
4833
+ objects.push({
4834
+ name: "line",
4835
+ type: "element",
4836
+ attributes: {
4837
+ x1: endX.toString(),
4838
+ y1: (y - TICK_SIZE_PX).toString(),
4839
+ x2: endX.toString(),
4840
+ y2: (y + TICK_SIZE_PX).toString(),
4841
+ stroke: "#ffffff",
4842
+ "stroke-width": STROKE_WIDTH_PX.toString()
4843
+ },
4844
+ children: [],
4845
+ value: ""
4846
+ });
4847
+ const midX = (startX + endX) / 2;
4848
+ const labelY = offsetY > 0 ? y - TICK_SIZE_PX - LABEL_GAP_PX : y + TICK_SIZE_PX + LABEL_GAP_PX;
4849
+ objects.push({
4850
+ name: "text",
4851
+ type: "element",
4852
+ attributes: {
4853
+ x: midX.toString(),
4854
+ y: labelY.toString(),
4855
+ fill: "#ffffff",
4856
+ "font-size": LABEL_FONT_SIZE_PX.toString(),
4857
+ "font-family": "Arial, sans-serif",
4858
+ "text-anchor": "middle",
4859
+ "dominant-baseline": offsetY > 0 ? "baseline" : "hanging",
4860
+ class: "anchor-offset-label"
4861
+ },
4862
+ children: [
4863
+ {
4864
+ type: "text",
4865
+ value: `X: ${offsetMm.toFixed(2)}mm`,
4866
+ name: "",
4867
+ attributes: {},
4868
+ children: []
4869
+ }
4870
+ ],
4871
+ value: ""
4872
+ });
4873
+ return objects;
4874
+ }
4875
+ function createVerticalDimension({
4876
+ x,
4877
+ startY,
4878
+ endY,
4879
+ offsetMm,
4880
+ offsetX
4881
+ }) {
4882
+ const objects = [];
4883
+ objects.push({
4884
+ name: "line",
4885
+ type: "element",
4886
+ attributes: {
4887
+ x1: x.toString(),
4888
+ y1: startY.toString(),
4889
+ x2: x.toString(),
4890
+ y2: endY.toString(),
4891
+ stroke: "#ffffff",
4892
+ "stroke-width": STROKE_WIDTH_PX.toString(),
4893
+ class: "anchor-offset-dimension-y"
4894
+ },
4895
+ children: [],
4896
+ value: ""
4897
+ });
4898
+ objects.push({
4899
+ name: "line",
4900
+ type: "element",
4901
+ attributes: {
4902
+ x1: (x - TICK_SIZE_PX).toString(),
4903
+ y1: startY.toString(),
4904
+ x2: (x + TICK_SIZE_PX).toString(),
4905
+ y2: startY.toString(),
4906
+ stroke: "#ffffff",
4907
+ "stroke-width": STROKE_WIDTH_PX.toString()
4908
+ },
4909
+ children: [],
4910
+ value: ""
4911
+ });
4912
+ objects.push({
4913
+ name: "line",
4914
+ type: "element",
4915
+ attributes: {
4916
+ x1: (x - TICK_SIZE_PX).toString(),
4917
+ y1: endY.toString(),
4918
+ x2: (x + TICK_SIZE_PX).toString(),
4919
+ y2: endY.toString(),
4920
+ stroke: "#ffffff",
4921
+ "stroke-width": STROKE_WIDTH_PX.toString()
4922
+ },
4923
+ children: [],
4924
+ value: ""
4925
+ });
4926
+ const midY = (startY + endY) / 2;
4927
+ const labelX = offsetX < 0 ? x - TICK_SIZE_PX - 4 : x + TICK_SIZE_PX + 4;
4928
+ objects.push({
4929
+ name: "text",
4930
+ type: "element",
4931
+ attributes: {
4932
+ x: labelX.toString(),
4933
+ y: midY.toString(),
4934
+ fill: "#ffffff",
4935
+ "font-size": LABEL_FONT_SIZE_PX.toString(),
4936
+ "font-family": "Arial, sans-serif",
4937
+ "text-anchor": offsetX < 0 ? "end" : "start",
4938
+ "dominant-baseline": "middle",
4939
+ class: "anchor-offset-label"
4940
+ },
4941
+ children: [
4942
+ {
4943
+ type: "text",
4944
+ value: `Y: ${offsetMm.toFixed(2)}mm`,
4945
+ name: "",
4946
+ attributes: {},
4947
+ children: []
4948
+ }
4949
+ ],
4950
+ value: ""
4951
+ });
4952
+ return objects;
4953
+ }
3258
4954
  function createSvgObjectsFromPcbComponent(component, ctx) {
3259
- const { transform } = ctx;
4955
+ const { transform, circuitJson } = ctx;
3260
4956
  const { center, width, height, rotation = 0 } = component;
3261
4957
  const [x, y] = applyToPoint(transform, [center.x, center.y]);
3262
4958
  const scaledWidth = width * Math.abs(transform.a);
3263
4959
  const scaledHeight = height * Math.abs(transform.d);
3264
4960
  const transformStr = `translate(${x}, ${y}) rotate(${-rotation}) scale(1, -1)`;
3265
- if (!ctx.colorMap.debugComponent?.fill && !ctx.colorMap.debugComponent?.stroke) {
3266
- return [];
3267
- }
3268
- return [
3269
- {
3270
- name: "g",
3271
- type: "element",
3272
- attributes: {
3273
- transform: transformStr,
3274
- "data-type": "pcb_component",
3275
- "data-pcb-layer": component.layer ?? "top"
3276
- },
3277
- children: [
3278
- {
3279
- name: "rect",
3280
- type: "element",
3281
- attributes: {
3282
- class: "pcb-component",
3283
- x: (-scaledWidth / 2).toString(),
3284
- y: (-scaledHeight / 2).toString(),
3285
- width: scaledWidth.toString(),
3286
- height: scaledHeight.toString(),
3287
- fill: ctx.colorMap.debugComponent.fill ?? "transparent",
3288
- stroke: ctx.colorMap.debugComponent.stroke ?? "transparent",
3289
- "data-type": "pcb_component",
3290
- "data-pcb-layer": component.layer ?? "top"
3291
- }
3292
- }
3293
- ],
3294
- value: ""
4961
+ const svgObjects = [];
4962
+ if (ctx.showAnchorOffsets && component.positioned_relative_to_pcb_group_id && component.position_mode === "relative" && circuitJson) {
4963
+ const pcbGroup = circuitJson.find(
4964
+ (elm) => elm.type === "pcb_group" && elm.pcb_group_id === component.positioned_relative_to_pcb_group_id
4965
+ );
4966
+ if (pcbGroup?.center) {
4967
+ svgObjects.push(
4968
+ ...createAnchorOffsetIndicators({
4969
+ groupAnchorPosition: pcbGroup.center,
4970
+ componentPosition: center,
4971
+ transform,
4972
+ componentWidth: width,
4973
+ componentHeight: height
4974
+ })
4975
+ );
3295
4976
  }
3296
- ];
4977
+ }
4978
+ if (!ctx.colorMap.debugComponent?.fill && !ctx.colorMap.debugComponent?.stroke) {
4979
+ return svgObjects;
4980
+ }
4981
+ svgObjects.push({
4982
+ name: "g",
4983
+ type: "element",
4984
+ attributes: {
4985
+ transform: transformStr,
4986
+ "data-type": "pcb_component",
4987
+ "data-pcb-layer": component.layer ?? "top"
4988
+ },
4989
+ children: [
4990
+ {
4991
+ name: "rect",
4992
+ type: "element",
4993
+ attributes: {
4994
+ class: "pcb-component",
4995
+ x: (-scaledWidth / 2).toString(),
4996
+ y: (-scaledHeight / 2).toString(),
4997
+ width: scaledWidth.toString(),
4998
+ height: scaledHeight.toString(),
4999
+ fill: ctx.colorMap.debugComponent.fill ?? "transparent",
5000
+ stroke: ctx.colorMap.debugComponent.stroke ?? "transparent",
5001
+ "data-type": "pcb_component",
5002
+ "data-pcb-layer": component.layer ?? "top"
5003
+ }
5004
+ }
5005
+ ],
5006
+ value: ""
5007
+ });
5008
+ return svgObjects;
3297
5009
  }
3298
5010
  var DEFAULT_GROUP_COLOR = "rgba(100, 200, 255, 0.6)";
3299
5011
  var DEFAULT_STROKE_WIDTH = 0.1;
@@ -3379,7 +5091,7 @@ function getSoftwareUsedString(circuitJson) {
3379
5091
  var package_default = {
3380
5092
  name: "circuit-to-svg",
3381
5093
  type: "module",
3382
- version: "0.0.259",
5094
+ version: "0.0.282",
3383
5095
  description: "Convert Circuit JSON to SVG",
3384
5096
  main: "dist/index.js",
3385
5097
  files: [
@@ -3403,12 +5115,12 @@ var package_default = {
3403
5115
  "bun-match-svg": "^0.0.12",
3404
5116
  esbuild: "^0.20.2",
3405
5117
  "performance-now": "^2.1.0",
3406
- "circuit-json": "^0.0.297",
5118
+ "circuit-json": "^0.0.319",
3407
5119
  react: "19.1.0",
3408
5120
  "react-cosmos": "7.0.0",
3409
5121
  "react-cosmos-plugin-vite": "7.0.0",
3410
5122
  "react-dom": "19.1.0",
3411
- tscircuit: "^0.0.827",
5123
+ tscircuit: "^0.0.937",
3412
5124
  tsup: "^8.0.2",
3413
5125
  typescript: "^5.4.5",
3414
5126
  "vite-tsconfig-paths": "^5.0.1"
@@ -3431,11 +5143,13 @@ var TYPE_PRIORITY = {
3431
5143
  pcb_hole: 18,
3432
5144
  pcb_plated_hole_drill: 19,
3433
5145
  pcb_plated_hole: 20,
5146
+ pcb_trace_soldermask: 25,
3434
5147
  pcb_trace: 30,
3435
5148
  pcb_smtpad: 30,
3436
5149
  pcb_copper_pour: 35,
3437
5150
  pcb_via: 36,
3438
5151
  pcb_soldermask: 40,
5152
+ pcb_soldermask_opening: 25,
3439
5153
  pcb_solder_paste: 45,
3440
5154
  pcb_silkscreen_text: 50,
3441
5155
  pcb_silkscreen_path: 50,
@@ -3480,15 +5194,17 @@ function getLayerPriority(layer) {
3480
5194
  if (!layer) return 500;
3481
5195
  const normalized = layer.toLowerCase();
3482
5196
  if (normalized === "global") return -100;
3483
- if (normalized === "bottom") return 0;
5197
+ if (normalized === "bottom") return 4;
3484
5198
  if (normalized === "board") return 2;
5199
+ if (normalized === "soldermask-top" || normalized === "soldermask-bottom")
5200
+ return 3;
3485
5201
  if (normalized.startsWith("inner")) {
3486
5202
  const match = normalized.match(/\d+/);
3487
- const layerIndex = match ? parseInt(match[0], 10) : 0;
5203
+ const layerIndex = match ? Number.parseInt(match[0], 10) : 0;
3488
5204
  return 5 + layerIndex;
3489
5205
  }
3490
5206
  if (normalized === "through") return 18;
3491
- if (normalized === "top") return 20;
5207
+ if (normalized === "top") return 17;
3492
5208
  if (normalized === "drill") return 30;
3493
5209
  if (normalized === "overlay") return 40;
3494
5210
  return 10;
@@ -3497,6 +5213,51 @@ function getTypePriority(type) {
3497
5213
  if (!type) return DEFAULT_TYPE_PRIORITY;
3498
5214
  return TYPE_PRIORITY[type] ?? DEFAULT_TYPE_PRIORITY;
3499
5215
  }
5216
+ function createErrorTextOverlay(circuitJson, dataType = "error_text_overlay") {
5217
+ const errorElms = circuitJson.filter(
5218
+ (elm) => elm.type.endsWith("_error")
5219
+ );
5220
+ if (errorElms.length === 0) {
5221
+ return null;
5222
+ }
5223
+ const errorMessages = errorElms.map((e) => e.message).filter((m) => !!m);
5224
+ if (errorMessages.length === 0) {
5225
+ return null;
5226
+ }
5227
+ const textBlock = {
5228
+ name: "text",
5229
+ type: "element",
5230
+ value: "",
5231
+ attributes: {
5232
+ x: "10",
5233
+ y: "20",
5234
+ fill: "red",
5235
+ "font-family": "monospace",
5236
+ "font-size": "12px",
5237
+ "data-type": dataType,
5238
+ "data-layer": "global"
5239
+ },
5240
+ children: errorMessages.map((msg, i) => ({
5241
+ name: "tspan",
5242
+ type: "element",
5243
+ value: "",
5244
+ attributes: {
5245
+ x: "10",
5246
+ dy: i === 0 ? "0" : "1.2em"
5247
+ },
5248
+ children: [
5249
+ {
5250
+ type: "text",
5251
+ value: msg,
5252
+ name: "",
5253
+ attributes: {},
5254
+ children: []
5255
+ }
5256
+ ]
5257
+ }))
5258
+ };
5259
+ return textBlock;
5260
+ }
3500
5261
  function convertCircuitJsonToPcbSvg(circuitJson, options) {
3501
5262
  const drawPaddingOutsideBoard = options?.drawPaddingOutsideBoard ?? true;
3502
5263
  const layer = options?.layer;
@@ -3523,6 +5284,15 @@ function convertCircuitJsonToPcbSvg(circuitJson, options) {
3523
5284
  top: colorOverrides?.soldermask?.top ?? DEFAULT_PCB_COLOR_MAP.soldermask.top,
3524
5285
  bottom: colorOverrides?.soldermask?.bottom ?? DEFAULT_PCB_COLOR_MAP.soldermask.bottom
3525
5286
  },
5287
+ soldermaskOverCopper: {
5288
+ top: colorOverrides?.soldermaskOverCopper?.top ?? DEFAULT_PCB_COLOR_MAP.soldermaskOverCopper.top,
5289
+ bottom: colorOverrides?.soldermaskOverCopper?.bottom ?? DEFAULT_PCB_COLOR_MAP.soldermaskOverCopper.bottom
5290
+ },
5291
+ soldermaskWithCopperUnderneath: {
5292
+ top: colorOverrides?.soldermaskWithCopperUnderneath?.top ?? DEFAULT_PCB_COLOR_MAP.soldermaskWithCopperUnderneath.top,
5293
+ bottom: colorOverrides?.soldermaskWithCopperUnderneath?.bottom ?? DEFAULT_PCB_COLOR_MAP.soldermaskWithCopperUnderneath.bottom
5294
+ },
5295
+ substrate: colorOverrides?.substrate ?? DEFAULT_PCB_COLOR_MAP.substrate,
3526
5296
  courtyard: colorOverrides?.courtyard ?? DEFAULT_PCB_COLOR_MAP.courtyard,
3527
5297
  debugComponent: {
3528
5298
  fill: colorOverrides?.debugComponent?.fill ?? DEFAULT_PCB_COLOR_MAP.debugComponent.fill,
@@ -3547,7 +5317,7 @@ function convertCircuitJsonToPcbSvg(circuitJson, options) {
3547
5317
  if (width === void 0 || height === void 0) {
3548
5318
  continue;
3549
5319
  }
3550
- const center = { x: width / 2, y: height / 2 };
5320
+ const center = panel.center ?? { x: width / 2, y: height / 2 };
3551
5321
  updateBounds(center, width, height);
3552
5322
  } else if (circuitJsonElm.type === "pcb_board") {
3553
5323
  if (circuitJsonElm.outline && Array.isArray(circuitJsonElm.outline) && circuitJsonElm.outline.length >= 3) {
@@ -3663,12 +5433,13 @@ function convertCircuitJsonToPcbSvg(circuitJson, options) {
3663
5433
  showPcbGroups: options?.showPcbGroups,
3664
5434
  drawPaddingOutsideBoard,
3665
5435
  colorMap: colorMap2,
3666
- showSolderMask: options?.showSolderMask
5436
+ showSolderMask: options?.showSolderMask,
5437
+ showAnchorOffsets: options?.showAnchorOffsets,
5438
+ circuitJson
3667
5439
  };
3668
- const unsortedSvgObjects = circuitJson.flatMap(
5440
+ let unsortedSvgObjects = circuitJson.flatMap(
3669
5441
  (elm) => createSvgObjects({ elm, circuitJson, ctx })
3670
5442
  );
3671
- let svgObjects = sortSvgObjectsByPcbLayer(unsortedSvgObjects);
3672
5443
  let strokeWidth = String(0.05 * scaleFactor);
3673
5444
  for (const element of circuitJson) {
3674
5445
  if ("stroke_width" in element) {
@@ -3678,8 +5449,9 @@ function convertCircuitJsonToPcbSvg(circuitJson, options) {
3678
5449
  }
3679
5450
  if (options?.shouldDrawRatsNest) {
3680
5451
  const ratsNestObjects = createSvgObjectsForRatsNest(circuitJson, ctx);
3681
- svgObjects = sortSvgObjectsByPcbLayer([...svgObjects, ...ratsNestObjects]);
5452
+ unsortedSvgObjects = [...unsortedSvgObjects, ...ratsNestObjects];
3682
5453
  }
5454
+ const svgObjects = sortSvgObjectsByPcbLayer(unsortedSvgObjects);
3683
5455
  const children = [
3684
5456
  {
3685
5457
  name: "style",
@@ -3730,6 +5502,15 @@ function convertCircuitJsonToPcbSvg(circuitJson, options) {
3730
5502
  if (gridObjects.rect) {
3731
5503
  children.push(gridObjects.rect);
3732
5504
  }
5505
+ if (options?.showErrorsInTextOverlay) {
5506
+ const errorOverlay = createErrorTextOverlay(
5507
+ circuitJson,
5508
+ "pcb_error_text_overlay"
5509
+ );
5510
+ if (errorOverlay) {
5511
+ children.push(errorOverlay);
5512
+ }
5513
+ }
3733
5514
  const softwareUsedString = getSoftwareUsedString(circuitJson);
3734
5515
  const version = CIRCUIT_TO_SVG_VERSION;
3735
5516
  const svgObject = {
@@ -4704,29 +6485,14 @@ function convertCircuitJsonToAssemblySvg(soup, options) {
4704
6485
  ).flatMap((item) => createSvgObjects2(item, ctx, soup));
4705
6486
  const softwareUsedString = getSoftwareUsedString(soup);
4706
6487
  const version = CIRCUIT_TO_SVG_VERSION;
4707
- const svgObject = {
4708
- name: "svg",
4709
- type: "element",
4710
- attributes: {
4711
- xmlns: "http://www.w3.org/2000/svg",
4712
- width: svgWidth.toString(),
4713
- height: svgHeight.toString(),
4714
- ...softwareUsedString && {
4715
- "data-software-used-string": softwareUsedString
4716
- },
4717
- ...options?.includeVersion && {
4718
- "data-circuit-to-svg-version": version
4719
- }
4720
- },
4721
- value: "",
4722
- children: [
4723
- {
4724
- name: "style",
4725
- type: "element",
4726
- children: [
4727
- {
4728
- type: "text",
4729
- value: `
6488
+ const children = [
6489
+ {
6490
+ name: "style",
6491
+ type: "element",
6492
+ children: [
6493
+ {
6494
+ type: "text",
6495
+ value: `
4730
6496
  .assembly-component {
4731
6497
  fill: none;
4732
6498
  stroke: #000;
@@ -4749,30 +6515,52 @@ function convertCircuitJsonToAssemblySvg(soup, options) {
4749
6515
  stroke-width: 0.2;
4750
6516
  }
4751
6517
  `,
4752
- name: "",
4753
- attributes: {},
4754
- children: []
4755
- }
4756
- ],
4757
- value: "",
4758
- attributes: {}
6518
+ name: "",
6519
+ attributes: {},
6520
+ children: []
6521
+ }
6522
+ ],
6523
+ value: "",
6524
+ attributes: {}
6525
+ },
6526
+ {
6527
+ name: "rect",
6528
+ type: "element",
6529
+ attributes: {
6530
+ fill: "#fff",
6531
+ x: "0",
6532
+ y: "0",
6533
+ width: svgWidth.toString(),
6534
+ height: svgHeight.toString()
4759
6535
  },
4760
- {
4761
- name: "rect",
4762
- type: "element",
4763
- attributes: {
4764
- fill: "#fff",
4765
- x: "0",
4766
- y: "0",
4767
- width: svgWidth.toString(),
4768
- height: svgHeight.toString()
4769
- },
4770
- value: "",
4771
- children: []
6536
+ value: "",
6537
+ children: []
6538
+ },
6539
+ createSvgObjectFromAssemblyBoundary(transform, minX, minY, maxX, maxY),
6540
+ ...svgObjects
6541
+ ].filter((child) => child !== null);
6542
+ if (options?.showErrorsInTextOverlay) {
6543
+ const errorOverlay = createErrorTextOverlay(soup);
6544
+ if (errorOverlay) {
6545
+ children.push(errorOverlay);
6546
+ }
6547
+ }
6548
+ const svgObject = {
6549
+ name: "svg",
6550
+ type: "element",
6551
+ attributes: {
6552
+ xmlns: "http://www.w3.org/2000/svg",
6553
+ width: svgWidth.toString(),
6554
+ height: svgHeight.toString(),
6555
+ ...softwareUsedString && {
6556
+ "data-software-used-string": softwareUsedString
4772
6557
  },
4773
- createSvgObjectFromAssemblyBoundary(transform, minX, minY, maxX, maxY),
4774
- ...svgObjects
4775
- ].filter((child) => child !== null)
6558
+ ...options?.includeVersion && {
6559
+ "data-circuit-to-svg-version": version
6560
+ }
6561
+ },
6562
+ value: "",
6563
+ children
4776
6564
  };
4777
6565
  return (0, import_svgson4.stringify)(svgObject);
4778
6566
  }
@@ -6024,6 +7812,28 @@ function convertCircuitJsonToPinoutSvg(soup, options) {
6024
7812
  ).flatMap((item) => createSvgObjects3(item, ctx, soup));
6025
7813
  const softwareUsedString = getSoftwareUsedString(soup);
6026
7814
  const version = CIRCUIT_TO_SVG_VERSION;
7815
+ const children = [
7816
+ {
7817
+ name: "rect",
7818
+ type: "element",
7819
+ attributes: {
7820
+ fill: "rgb(255, 255, 255)",
7821
+ x: "0",
7822
+ y: "0",
7823
+ width: svgWidth.toString(),
7824
+ height: svgHeight.toString()
7825
+ },
7826
+ value: "",
7827
+ children: []
7828
+ },
7829
+ ...svgObjects
7830
+ ].filter((child) => child !== null);
7831
+ if (options?.showErrorsInTextOverlay) {
7832
+ const errorOverlay = createErrorTextOverlay(soup);
7833
+ if (errorOverlay) {
7834
+ children.push(errorOverlay);
7835
+ }
7836
+ }
6027
7837
  const svgObject = {
6028
7838
  name: "svg",
6029
7839
  type: "element",
@@ -6039,22 +7849,7 @@ function convertCircuitJsonToPinoutSvg(soup, options) {
6039
7849
  }
6040
7850
  },
6041
7851
  value: "",
6042
- children: [
6043
- {
6044
- name: "rect",
6045
- type: "element",
6046
- attributes: {
6047
- fill: "rgb(255, 255, 255)",
6048
- x: "0",
6049
- y: "0",
6050
- width: svgWidth.toString(),
6051
- height: svgHeight.toString()
6052
- },
6053
- value: "",
6054
- children: []
6055
- },
6056
- ...svgObjects
6057
- ].filter((child) => child !== null)
7852
+ children
6058
7853
  };
6059
7854
  return (0, import_svgson5.stringify)(svgObject);
6060
7855
  }
@@ -8079,6 +9874,7 @@ function createSvgObjectsFromSchVoltageProbe({
8079
9874
  probe.position.x,
8080
9875
  probe.position.y
8081
9876
  ]);
9877
+ const probeColor = probe.color ?? colorMap2.schematic.reference;
8082
9878
  const arrowLength = Math.abs(transform.a) * 0.6;
8083
9879
  const arrowWidth = Math.abs(transform.a) * 0.28;
8084
9880
  const baseX = screenX + arrowLength * Math.cos(-50 * Math.PI / 180);
@@ -8093,14 +9889,68 @@ function createSvgObjectsFromSchVoltageProbe({
8093
9889
  `L ${tipX - arrowWidth * Math.cos((-50 + 210) * Math.PI / 180)},${tipY - arrowWidth * Math.sin((-50 + 210) * Math.PI / 180)}`,
8094
9890
  "Z"
8095
9891
  ].join(" ");
9892
+ const x = (baseX + 8 - (baseX - baseX)).toString();
9893
+ const textChildren = [];
9894
+ if (probe.name && probe.voltage !== void 0) {
9895
+ textChildren.push({
9896
+ type: "element",
9897
+ name: "tspan",
9898
+ value: "",
9899
+ attributes: {
9900
+ x
9901
+ },
9902
+ children: [
9903
+ {
9904
+ type: "text",
9905
+ value: probe.name,
9906
+ name: "",
9907
+ attributes: {},
9908
+ children: []
9909
+ }
9910
+ ]
9911
+ });
9912
+ textChildren.push({
9913
+ type: "element",
9914
+ name: "tspan",
9915
+ value: "",
9916
+ attributes: {
9917
+ x,
9918
+ dy: "1.2em"
9919
+ },
9920
+ children: [
9921
+ {
9922
+ type: "text",
9923
+ value: `${probe.voltage}V`,
9924
+ name: "",
9925
+ attributes: {},
9926
+ children: []
9927
+ }
9928
+ ]
9929
+ });
9930
+ } else {
9931
+ const textParts = [];
9932
+ if (probe.name) {
9933
+ textParts.push(probe.name);
9934
+ }
9935
+ if (probe.voltage !== void 0) {
9936
+ textParts.push(`${probe.voltage}V`);
9937
+ }
9938
+ textChildren.push({
9939
+ type: "text",
9940
+ value: textParts.join(" "),
9941
+ name: "",
9942
+ attributes: {},
9943
+ children: []
9944
+ });
9945
+ }
8096
9946
  return [
8097
9947
  {
8098
9948
  name: "path",
8099
9949
  type: "element",
8100
9950
  attributes: {
8101
9951
  d: arrowPath,
8102
- stroke: colorMap2.schematic.reference,
8103
- fill: colorMap2.schematic.reference,
9952
+ stroke: probeColor,
9953
+ fill: probeColor,
8104
9954
  "stroke-width": `${getSchStrokeSize(transform)}px`
8105
9955
  },
8106
9956
  value: "",
@@ -8111,25 +9961,17 @@ function createSvgObjectsFromSchVoltageProbe({
8111
9961
  name: "text",
8112
9962
  value: "",
8113
9963
  attributes: {
8114
- x: (baseX + 8 - (baseX - baseX)).toString(),
8115
- y: (baseY - 10 + (baseY - baseY)).toString(),
8116
- fill: colorMap2.schematic.reference,
8117
- "text-anchor": "middle",
9964
+ x,
9965
+ y: baseY.toString(),
9966
+ fill: probeColor,
9967
+ "text-anchor": "start",
8118
9968
  "dominant-baseline": "middle",
8119
9969
  "font-family": "sans-serif",
8120
9970
  "font-size": `${getSchScreenFontSize(transform, "reference_designator")}px`,
8121
9971
  "font-weight": "bold",
8122
9972
  "data-schematic-voltage-probe-id": probe.schematic_voltage_probe_id
8123
9973
  },
8124
- children: [
8125
- {
8126
- type: "text",
8127
- value: probe.voltage ? `${probe.voltage}V` : "",
8128
- name: "",
8129
- attributes: {},
8130
- children: []
8131
- }
8132
- ]
9974
+ children: textChildren
8133
9975
  }
8134
9976
  ];
8135
9977
  }
@@ -9442,6 +11284,12 @@ function convertCircuitJsonToSchematicSvg(circuitJson, options) {
9442
11284
  }
9443
11285
  const softwareUsedString = getSoftwareUsedString(circuitJson);
9444
11286
  const version = CIRCUIT_TO_SVG_VERSION;
11287
+ if (options?.showErrorsInTextOverlay) {
11288
+ const errorOverlay = createErrorTextOverlay(circuitJson);
11289
+ if (errorOverlay) {
11290
+ svgChildren.push(errorOverlay);
11291
+ }
11292
+ }
9445
11293
  const svgObject = {
9446
11294
  name: "svg",
9447
11295
  type: "element",
@@ -9549,7 +11397,10 @@ function convertCircuitJsonToSimulationGraphSvg({
9549
11397
  );
9550
11398
  }
9551
11399
  const timeAxis = buildAxisInfo(allPoints.map((point) => point.timeMs));
9552
- const voltageAxis = buildAxisInfo(allPoints.map((point) => point.voltage));
11400
+ const voltageAxis = buildAxisInfo(
11401
+ allPoints.map((point) => point.voltage),
11402
+ true
11403
+ );
9553
11404
  const plotWidth = Math.max(1, width - MARGIN.left - MARGIN.right);
9554
11405
  const plotHeight = Math.max(1, height - MARGIN.top - MARGIN.bottom);
9555
11406
  const scaleX = createLinearScale(
@@ -9620,19 +11471,23 @@ function convertCircuitJsonToSimulationGraphSvg({
9620
11471
  function prepareSimulationGraphs(graphs, circuitJson) {
9621
11472
  const palette = Array.isArray(colorMap.palette) ? colorMap.palette : [];
9622
11473
  const voltageProbes = circuitJson.filter(isSimulationVoltageProbe);
9623
- const probeIdToName = /* @__PURE__ */ new Map();
11474
+ const sourceComponentIdToProbeName = /* @__PURE__ */ new Map();
11475
+ const sourceComponentIdToProbeColor = /* @__PURE__ */ new Map();
9624
11476
  for (const probe of voltageProbes) {
9625
- if (probe.name && probe.simulation_voltage_probe_id) {
9626
- probeIdToName.set(probe.simulation_voltage_probe_id, probe.name);
11477
+ if (probe.name && probe.source_component_id) {
11478
+ sourceComponentIdToProbeName.set(probe.source_component_id, probe.name);
11479
+ }
11480
+ if (probe.color && probe.source_component_id) {
11481
+ sourceComponentIdToProbeColor.set(probe.source_component_id, probe.color);
9627
11482
  }
9628
11483
  }
9629
11484
  return graphs.map((graph, index) => {
9630
11485
  const points = createGraphPoints(graph);
9631
11486
  const paletteColor = palette.length > 0 ? palette[index % palette.length] : FALLBACK_LINE_COLOR;
9632
- const color = paletteColor ?? FALLBACK_LINE_COLOR;
9633
- const probeId = graph.simulation_voltage_probe_id ?? graph.schematic_voltage_probe_id;
9634
- const probeName = probeId ? probeIdToName.get(probeId) : void 0;
9635
- const label = probeName ? `V(${probeName})` : graph.name || (probeId ? `Probe ${probeId}` : graph.simulation_transient_voltage_graph_id);
11487
+ const probeColor = graph.source_component_id ? sourceComponentIdToProbeColor.get(graph.source_component_id) : void 0;
11488
+ const color = graph.color ?? probeColor ?? paletteColor ?? FALLBACK_LINE_COLOR;
11489
+ const probeName = graph.source_component_id ? sourceComponentIdToProbeName.get(graph.source_component_id) : void 0;
11490
+ const label = probeName ? `V(${probeName})` : graph.name || (graph.source_component_id ? `Probe ${graph.source_component_id}` : graph.simulation_transient_voltage_graph_id);
9636
11491
  return { graph, points, color, label };
9637
11492
  }).filter((entry) => entry.points.length > 0);
9638
11493
  }
@@ -9664,7 +11519,7 @@ function getTimestamps(graph) {
9664
11519
  }
9665
11520
  return timestamps;
9666
11521
  }
9667
- function buildAxisInfo(values) {
11522
+ function buildAxisInfo(values, applyPadding = false) {
9668
11523
  if (values.length === 0) {
9669
11524
  return {
9670
11525
  domainMin: 0,
@@ -9683,9 +11538,21 @@ function buildAxisInfo(values) {
9683
11538
  };
9684
11539
  }
9685
11540
  const ticks = generateTickValues(min, max);
9686
- const safeTicks = ticks.length > 0 ? ticks : [min, max];
9687
- const domainMin = safeTicks[0];
9688
- const domainMax = safeTicks[safeTicks.length - 1];
11541
+ const safeTicks = ticks.length > 0 ? [...ticks] : [min, max];
11542
+ let domainMin = safeTicks[0];
11543
+ let domainMax = safeTicks[safeTicks.length - 1];
11544
+ if (applyPadding && safeTicks.length > 1) {
11545
+ const tickStep = Math.abs(safeTicks[1] - safeTicks[0]);
11546
+ const PADDING_TOLERANCE_RATIO = 0.1;
11547
+ if (min < domainMin + tickStep * PADDING_TOLERANCE_RATIO) {
11548
+ domainMin -= tickStep;
11549
+ safeTicks.unshift(domainMin);
11550
+ }
11551
+ if (max > domainMax - tickStep * PADDING_TOLERANCE_RATIO) {
11552
+ domainMax += tickStep;
11553
+ safeTicks.push(domainMax);
11554
+ }
11555
+ }
9689
11556
  return { domainMin, domainMax, ticks: safeTicks };
9690
11557
  }
9691
11558
  function generateTickValues(min, max, desired = 6) {
@@ -10016,8 +11883,8 @@ function createDataGroup(graphs, clipPathId, scaleX, scaleY) {
10016
11883
  "clip-path": `url(#${clipPathId})`,
10017
11884
  "data-simulation-transient-voltage-graph-id": entry.graph.simulation_transient_voltage_graph_id
10018
11885
  };
10019
- if (entry.graph.schematic_voltage_probe_id) {
10020
- baseAttributes["data-schematic-voltage-probe-id"] = entry.graph.schematic_voltage_probe_id;
11886
+ if (entry.graph.source_component_id) {
11887
+ baseAttributes["data-source-component-id"] = entry.graph.source_component_id;
10021
11888
  }
10022
11889
  if (entry.graph.subcircuit_connectivity_map_key) {
10023
11890
  baseAttributes["data-subcircuit-connectivity-map-key"] = entry.graph.subcircuit_connectivity_map_key;
@@ -10125,7 +11992,8 @@ function convertCircuitJsonToSchematicSimulationSvg({
10125
11992
  height = DEFAULT_HEIGHT2,
10126
11993
  schematicHeightRatio = DEFAULT_SCHEMATIC_RATIO,
10127
11994
  schematicOptions,
10128
- includeVersion
11995
+ includeVersion,
11996
+ showErrorsInTextOverlay
10129
11997
  }) {
10130
11998
  const schematicElements = circuitJson.filter(
10131
11999
  (element) => !isSimulationExperiment(element) && !isSimulationTransientVoltageGraph(element)
@@ -10141,7 +12009,8 @@ function convertCircuitJsonToSchematicSimulationSvg({
10141
12009
  ...schematicOptions,
10142
12010
  width,
10143
12011
  height: schematicHeight,
10144
- includeVersion: false
12012
+ includeVersion: false,
12013
+ showErrorsInTextOverlay
10145
12014
  });
10146
12015
  const simulationSvg = convertCircuitJsonToSimulationGraphSvg({
10147
12016
  circuitJson,
@@ -10333,7 +12202,8 @@ function convertCircuitJsonToSolderPasteMask(circuitJson, options) {
10333
12202
  const width = distance.parse(panel.width);
10334
12203
  const height = distance.parse(panel.height);
10335
12204
  if (width !== void 0 && height !== void 0) {
10336
- updateBounds({ x: width / 2, y: height / 2 }, width, height);
12205
+ const center = panel.center ?? { x: width / 2, y: height / 2 };
12206
+ updateBounds(center, width, height);
10337
12207
  }
10338
12208
  } else if (item.type === "pcb_solder_paste" && "x" in item && "y" in item) {
10339
12209
  updateBounds({ x: item.x, y: item.y }, 0, 0);
@@ -10367,6 +12237,38 @@ function convertCircuitJsonToSolderPasteMask(circuitJson, options) {
10367
12237
  ).flatMap((item) => createSvgObjects4({ elm: item, ctx }));
10368
12238
  const softwareUsedString = getSoftwareUsedString(circuitJson);
10369
12239
  const version = CIRCUIT_TO_SVG_VERSION;
12240
+ const children = [
12241
+ {
12242
+ name: "style",
12243
+ type: "element",
12244
+ children: [
12245
+ {
12246
+ type: "text",
12247
+ value: ""
12248
+ }
12249
+ ]
12250
+ },
12251
+ {
12252
+ name: "rect",
12253
+ type: "element",
12254
+ attributes: {
12255
+ class: "boundary",
12256
+ x: "0",
12257
+ y: "0",
12258
+ fill: "#000",
12259
+ width: svgWidth.toString(),
12260
+ height: svgHeight.toString()
12261
+ }
12262
+ },
12263
+ createSvgObjectFromPcbBoundary2(transform, minX, minY, maxX, maxY),
12264
+ ...svgObjects
12265
+ ].filter((child) => child !== null);
12266
+ if (options?.showErrorsInTextOverlay) {
12267
+ const errorOverlay = createErrorTextOverlay(circuitJson);
12268
+ if (errorOverlay) {
12269
+ children.push(errorOverlay);
12270
+ }
12271
+ }
10370
12272
  const svgObject = {
10371
12273
  name: "svg",
10372
12274
  type: "element",
@@ -10382,32 +12284,7 @@ function convertCircuitJsonToSolderPasteMask(circuitJson, options) {
10382
12284
  }
10383
12285
  },
10384
12286
  value: "",
10385
- children: [
10386
- {
10387
- name: "style",
10388
- type: "element",
10389
- children: [
10390
- {
10391
- type: "text",
10392
- value: ""
10393
- }
10394
- ]
10395
- },
10396
- {
10397
- name: "rect",
10398
- type: "element",
10399
- attributes: {
10400
- class: "boundary",
10401
- x: "0",
10402
- y: "0",
10403
- fill: "#000",
10404
- width: svgWidth.toString(),
10405
- height: svgHeight.toString()
10406
- }
10407
- },
10408
- createSvgObjectFromPcbBoundary2(transform, minX, minY, maxX, maxY),
10409
- ...svgObjects
10410
- ].filter((child) => child !== null)
12287
+ children
10411
12288
  };
10412
12289
  try {
10413
12290
  return (0, import_svgson11.stringify)(svgObject);
@@ -10478,10 +12355,11 @@ export {
10478
12355
  convertCircuitJsonToSchematicSvg,
10479
12356
  convertCircuitJsonToSimulationGraphSvg,
10480
12357
  convertCircuitJsonToSolderPasteMask,
12358
+ createErrorTextOverlay,
10481
12359
  createSvgObjectsForSchComponentPortHovers,
10482
12360
  getSoftwareUsedString,
10483
12361
  isSimulationExperiment,
10484
12362
  isSimulationTransientVoltageGraph,
10485
12363
  isSimulationVoltageProbe
10486
12364
  };
10487
- //# sourceMappingURL=dist-K77PLYJN.js.map
12365
+ //# sourceMappingURL=dist-7RT7ZSAD.js.map