circuit-to-svg 0.0.277 → 0.0.278

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1586,7 +1586,7 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1586
1586
  const isCoveredWithSolderMask = Boolean(hole.is_covered_with_solder_mask);
1587
1587
  const soldermaskMargin = (hole.soldermask_margin ?? 0) * Math.abs(transform.a);
1588
1588
  const shouldShowSolderMask = showSolderMask && isCoveredWithSolderMask && soldermaskMargin !== 0;
1589
- const solderMaskColor = colorMap2.soldermask.top;
1589
+ const solderMaskColor = colorMap2.soldermaskWithCopperUnderneath.top;
1590
1590
  if (hole.shape === "pill") {
1591
1591
  const scaledOuterWidth = hole.outer_width * Math.abs(transform.a);
1592
1592
  const scaledOuterHeight = hole.outer_height * Math.abs(transform.a);
@@ -1609,7 +1609,7 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1609
1609
  return `M${-radius},0 a${radius},${radius} 0 0 1 ${width},0 a${radius},${radius} 0 0 1 ${-width},0 z`;
1610
1610
  }
1611
1611
  };
1612
- const children = [
1612
+ let children = [
1613
1613
  // Outer pill shape
1614
1614
  {
1615
1615
  name: "path",
@@ -1644,19 +1644,57 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1644
1644
  if (shouldShowSolderMask) {
1645
1645
  const maskWidth = scaledOuterWidth + 2 * soldermaskMargin;
1646
1646
  const maskHeight = scaledOuterHeight + 2 * soldermaskMargin;
1647
- children.push({
1648
- name: "path",
1649
- type: "element",
1650
- attributes: {
1651
- class: "pcb-solder-mask",
1652
- fill: solderMaskColor,
1653
- d: createPillPath(maskWidth, maskHeight),
1654
- transform: outerTransform,
1655
- "data-type": "pcb_soldermask"
1656
- },
1657
- value: "",
1658
- children: []
1659
- });
1647
+ if (soldermaskMargin < 0) {
1648
+ children = [
1649
+ // 1. Draw the outer pad in soldermask color (covered)
1650
+ {
1651
+ name: "path",
1652
+ type: "element",
1653
+ attributes: {
1654
+ class: "pcb-hole-outer-covered",
1655
+ fill: solderMaskColor,
1656
+ d: createPillPath(scaledOuterWidth, scaledOuterHeight),
1657
+ transform: outerTransform,
1658
+ "data-type": "pcb_plated_hole",
1659
+ "data-pcb-layer": copperLayer
1660
+ },
1661
+ value: "",
1662
+ children: []
1663
+ },
1664
+ // 2. Draw the exposed opening in copper color
1665
+ {
1666
+ name: "path",
1667
+ type: "element",
1668
+ attributes: {
1669
+ class: "pcb-hole-outer-exposed",
1670
+ fill: colorMap2.copper.top,
1671
+ d: createPillPath(maskWidth, maskHeight),
1672
+ transform: outerTransform,
1673
+ "data-type": "pcb_soldermask"
1674
+ },
1675
+ value: "",
1676
+ children: []
1677
+ },
1678
+ // 3. Draw the drill hole on top
1679
+ children[1]
1680
+ // Original inner hole
1681
+ ];
1682
+ } else {
1683
+ children.unshift({
1684
+ name: "path",
1685
+ type: "element",
1686
+ attributes: {
1687
+ class: "pcb-soldermask-cutout",
1688
+ fill: colorMap2.substrate,
1689
+ d: createPillPath(maskWidth, maskHeight),
1690
+ transform: outerTransform,
1691
+ "data-type": "pcb_soldermask_opening",
1692
+ "data-pcb-layer": "soldermask-top"
1693
+ },
1694
+ value: "",
1695
+ children: []
1696
+ });
1697
+ }
1660
1698
  }
1661
1699
  return [
1662
1700
  {
@@ -1736,7 +1774,7 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1736
1774
  const scaledHoleHeight = hole.hole_diameter * Math.abs(transform.a);
1737
1775
  const outerRadius = Math.min(scaledOuterWidth, scaledOuterHeight) / 2;
1738
1776
  const innerRadius = Math.min(scaledHoleWidth, scaledHoleHeight) / 2;
1739
- const children = [
1777
+ let children = [
1740
1778
  {
1741
1779
  name: "circle",
1742
1780
  type: "element",
@@ -1770,20 +1808,60 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1770
1808
  ];
1771
1809
  if (shouldShowSolderMask) {
1772
1810
  const maskRadius = outerRadius + soldermaskMargin;
1773
- children.push({
1774
- name: "circle",
1775
- type: "element",
1776
- attributes: {
1777
- class: "pcb-solder-mask",
1778
- fill: solderMaskColor,
1779
- cx: x.toString(),
1780
- cy: y.toString(),
1781
- r: maskRadius.toString(),
1782
- "data-type": "pcb_soldermask"
1783
- },
1784
- value: "",
1785
- children: []
1786
- });
1811
+ if (soldermaskMargin < 0) {
1812
+ children = [
1813
+ // 1. Draw the outer ring in soldermask color (covered)
1814
+ {
1815
+ name: "circle",
1816
+ type: "element",
1817
+ attributes: {
1818
+ class: "pcb-hole-outer-covered",
1819
+ fill: solderMaskColor,
1820
+ cx: x.toString(),
1821
+ cy: y.toString(),
1822
+ r: outerRadius.toString(),
1823
+ "data-type": "pcb_plated_hole",
1824
+ "data-pcb-layer": copperLayer
1825
+ },
1826
+ value: "",
1827
+ children: []
1828
+ },
1829
+ // 2. Draw the exposed opening in copper color
1830
+ {
1831
+ name: "circle",
1832
+ type: "element",
1833
+ attributes: {
1834
+ class: "pcb-hole-outer-exposed",
1835
+ fill: colorMap2.copper.top,
1836
+ cx: x.toString(),
1837
+ cy: y.toString(),
1838
+ r: maskRadius.toString(),
1839
+ "data-type": "pcb_soldermask"
1840
+ },
1841
+ value: "",
1842
+ children: []
1843
+ },
1844
+ // 3. Draw the drill hole on top
1845
+ children[1]
1846
+ // Original inner hole
1847
+ ];
1848
+ } else {
1849
+ children.unshift({
1850
+ name: "circle",
1851
+ type: "element",
1852
+ attributes: {
1853
+ class: "pcb-soldermask-cutout",
1854
+ fill: colorMap2.substrate,
1855
+ cx: x.toString(),
1856
+ cy: y.toString(),
1857
+ r: maskRadius.toString(),
1858
+ "data-type": "pcb_soldermask_opening",
1859
+ "data-pcb-layer": "soldermask-top"
1860
+ },
1861
+ value: "",
1862
+ children: []
1863
+ });
1864
+ }
1787
1865
  }
1788
1866
  return [
1789
1867
  {
@@ -1809,7 +1887,7 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1809
1887
  h.x + (h.hole_offset_x ?? 0),
1810
1888
  h.y + (h.hole_offset_y ?? 0)
1811
1889
  ]);
1812
- const children = [
1890
+ let children = [
1813
1891
  // Rectangular pad (outer shape)
1814
1892
  {
1815
1893
  name: "rect",
@@ -1852,25 +1930,75 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1852
1930
  const maskWidth = scaledRectPadWidth + 2 * soldermaskMargin;
1853
1931
  const maskHeight = scaledRectPadHeight + 2 * soldermaskMargin;
1854
1932
  const maskBorderRadius = scaledRectBorderRadius + soldermaskMargin;
1855
- children.push({
1856
- name: "rect",
1857
- type: "element",
1858
- attributes: {
1859
- class: "pcb-solder-mask",
1860
- fill: solderMaskColor,
1861
- x: (x - maskWidth / 2).toString(),
1862
- y: (y - maskHeight / 2).toString(),
1863
- width: maskWidth.toString(),
1864
- height: maskHeight.toString(),
1865
- ...scaledRectBorderRadius ? {
1866
- rx: maskBorderRadius.toString(),
1867
- ry: maskBorderRadius.toString()
1868
- } : {},
1869
- "data-type": "pcb_soldermask"
1870
- },
1871
- value: "",
1872
- children: []
1873
- });
1933
+ if (soldermaskMargin < 0) {
1934
+ children = [
1935
+ // 1. Draw the outer pad in soldermask color (covered)
1936
+ {
1937
+ name: "rect",
1938
+ type: "element",
1939
+ attributes: {
1940
+ class: "pcb-hole-outer-covered",
1941
+ fill: solderMaskColor,
1942
+ x: (x - scaledRectPadWidth / 2).toString(),
1943
+ y: (y - scaledRectPadHeight / 2).toString(),
1944
+ width: scaledRectPadWidth.toString(),
1945
+ height: scaledRectPadHeight.toString(),
1946
+ ...scaledRectBorderRadius ? {
1947
+ rx: scaledRectBorderRadius.toString(),
1948
+ ry: scaledRectBorderRadius.toString()
1949
+ } : {},
1950
+ "data-type": "pcb_plated_hole",
1951
+ "data-pcb-layer": copperLayer
1952
+ },
1953
+ value: "",
1954
+ children: []
1955
+ },
1956
+ // 2. Draw the exposed opening in copper color
1957
+ {
1958
+ name: "rect",
1959
+ type: "element",
1960
+ attributes: {
1961
+ class: "pcb-hole-outer-exposed",
1962
+ fill: colorMap2.copper.top,
1963
+ x: (x - maskWidth / 2).toString(),
1964
+ y: (y - maskHeight / 2).toString(),
1965
+ width: maskWidth.toString(),
1966
+ height: maskHeight.toString(),
1967
+ ...maskBorderRadius > 0 ? {
1968
+ rx: maskBorderRadius.toString(),
1969
+ ry: maskBorderRadius.toString()
1970
+ } : {},
1971
+ "data-type": "pcb_soldermask"
1972
+ },
1973
+ value: "",
1974
+ children: []
1975
+ },
1976
+ // 3. Draw the drill hole on top
1977
+ children[1]
1978
+ // Original hole
1979
+ ];
1980
+ } else {
1981
+ children.unshift({
1982
+ name: "rect",
1983
+ type: "element",
1984
+ attributes: {
1985
+ class: "pcb-soldermask-cutout",
1986
+ fill: colorMap2.substrate,
1987
+ x: (x - maskWidth / 2).toString(),
1988
+ y: (y - maskHeight / 2).toString(),
1989
+ width: maskWidth.toString(),
1990
+ height: maskHeight.toString(),
1991
+ ...scaledRectBorderRadius ? {
1992
+ rx: maskBorderRadius.toString(),
1993
+ ry: maskBorderRadius.toString()
1994
+ } : {},
1995
+ "data-type": "pcb_soldermask_opening",
1996
+ "data-pcb-layer": "soldermask-top"
1997
+ },
1998
+ value: "",
1999
+ children: []
2000
+ });
2001
+ }
1874
2002
  }
1875
2003
  return [
1876
2004
  {
@@ -1900,7 +2028,7 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1900
2028
  pillHole.y + holeOffsetY
1901
2029
  ]);
1902
2030
  const holeRadius = Math.min(scaledHoleHeight, scaledHoleWidth) / 2;
1903
- const children = [
2031
+ let children = [
1904
2032
  // Rectangular pad (outer shape)
1905
2033
  {
1906
2034
  name: "rect",
@@ -1946,25 +2074,75 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1946
2074
  const maskWidth = scaledRectPadWidth + 2 * soldermaskMargin;
1947
2075
  const maskHeight = scaledRectPadHeight + 2 * soldermaskMargin;
1948
2076
  const maskBorderRadius = scaledRectBorderRadius + soldermaskMargin;
1949
- children.push({
1950
- name: "rect",
1951
- type: "element",
1952
- attributes: {
1953
- class: "pcb-solder-mask",
1954
- fill: solderMaskColor,
1955
- x: (x - maskWidth / 2).toString(),
1956
- y: (y - maskHeight / 2).toString(),
1957
- width: maskWidth.toString(),
1958
- height: maskHeight.toString(),
1959
- ...scaledRectBorderRadius ? {
1960
- rx: maskBorderRadius.toString(),
1961
- ry: maskBorderRadius.toString()
1962
- } : {},
1963
- "data-type": "pcb_soldermask"
1964
- },
1965
- value: "",
1966
- children: []
1967
- });
2077
+ if (soldermaskMargin < 0) {
2078
+ children = [
2079
+ // 1. Draw the outer pad in soldermask color (covered)
2080
+ {
2081
+ name: "rect",
2082
+ type: "element",
2083
+ attributes: {
2084
+ class: "pcb-hole-outer-covered",
2085
+ fill: solderMaskColor,
2086
+ x: (x - scaledRectPadWidth / 2).toString(),
2087
+ y: (y - scaledRectPadHeight / 2).toString(),
2088
+ width: scaledRectPadWidth.toString(),
2089
+ height: scaledRectPadHeight.toString(),
2090
+ ...scaledRectBorderRadius ? {
2091
+ rx: scaledRectBorderRadius.toString(),
2092
+ ry: scaledRectBorderRadius.toString()
2093
+ } : {},
2094
+ "data-type": "pcb_plated_hole",
2095
+ "data-pcb-layer": copperLayer
2096
+ },
2097
+ value: "",
2098
+ children: []
2099
+ },
2100
+ // 2. Draw the exposed opening in copper color
2101
+ {
2102
+ name: "rect",
2103
+ type: "element",
2104
+ attributes: {
2105
+ class: "pcb-hole-outer-exposed",
2106
+ fill: colorMap2.copper.top,
2107
+ x: (x - maskWidth / 2).toString(),
2108
+ y: (y - maskHeight / 2).toString(),
2109
+ width: maskWidth.toString(),
2110
+ height: maskHeight.toString(),
2111
+ ...maskBorderRadius > 0 ? {
2112
+ rx: maskBorderRadius.toString(),
2113
+ ry: maskBorderRadius.toString()
2114
+ } : {},
2115
+ "data-type": "pcb_soldermask"
2116
+ },
2117
+ value: "",
2118
+ children: []
2119
+ },
2120
+ // 3. Draw the drill hole on top
2121
+ children[1]
2122
+ // Original hole
2123
+ ];
2124
+ } else {
2125
+ children.unshift({
2126
+ name: "rect",
2127
+ type: "element",
2128
+ attributes: {
2129
+ class: "pcb-soldermask-cutout",
2130
+ fill: colorMap2.substrate,
2131
+ x: (x - maskWidth / 2).toString(),
2132
+ y: (y - maskHeight / 2).toString(),
2133
+ width: maskWidth.toString(),
2134
+ height: maskHeight.toString(),
2135
+ ...scaledRectBorderRadius ? {
2136
+ rx: maskBorderRadius.toString(),
2137
+ ry: maskBorderRadius.toString()
2138
+ } : {},
2139
+ "data-type": "pcb_soldermask_opening",
2140
+ "data-pcb-layer": "soldermask-top"
2141
+ },
2142
+ value: "",
2143
+ children: []
2144
+ });
2145
+ }
1968
2146
  }
1969
2147
  return [
1970
2148
  {
@@ -1994,7 +2172,7 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
1994
2172
  rotatedHole.y + holeOffsetY
1995
2173
  ]);
1996
2174
  const holeRadius = Math.min(scaledHoleHeight, scaledHoleWidth) / 2;
1997
- const children = [
2175
+ let children = [
1998
2176
  {
1999
2177
  name: "rect",
2000
2178
  type: "element",
@@ -2040,26 +2218,77 @@ function createSvgObjectsFromPcbPlatedHole(hole, ctx) {
2040
2218
  const maskWidth = scaledRectPadWidth + 2 * soldermaskMargin;
2041
2219
  const maskHeight = scaledRectPadHeight + 2 * soldermaskMargin;
2042
2220
  const maskBorderRadius = scaledRectBorderRadius + soldermaskMargin;
2043
- children.push({
2044
- name: "rect",
2045
- type: "element",
2046
- attributes: {
2047
- class: "pcb-solder-mask",
2048
- fill: solderMaskColor,
2049
- x: (-maskWidth / 2).toString(),
2050
- y: (-maskHeight / 2).toString(),
2051
- width: maskWidth.toString(),
2052
- height: maskHeight.toString(),
2053
- transform: `translate(${x} ${y}) rotate(${-rotatedHole.rect_ccw_rotation})`,
2054
- ...scaledRectBorderRadius ? {
2055
- rx: maskBorderRadius.toString(),
2056
- ry: maskBorderRadius.toString()
2057
- } : {},
2058
- "data-type": "pcb_soldermask"
2059
- },
2060
- value: "",
2061
- children: []
2062
- });
2221
+ if (soldermaskMargin < 0) {
2222
+ children = [
2223
+ // 1. Draw the outer pad in soldermask color (covered)
2224
+ {
2225
+ name: "rect",
2226
+ type: "element",
2227
+ attributes: {
2228
+ class: "pcb-hole-outer-covered",
2229
+ fill: solderMaskColor,
2230
+ x: (-scaledRectPadWidth / 2).toString(),
2231
+ y: (-scaledRectPadHeight / 2).toString(),
2232
+ width: scaledRectPadWidth.toString(),
2233
+ height: scaledRectPadHeight.toString(),
2234
+ transform: `translate(${x} ${y}) rotate(${-rotatedHole.rect_ccw_rotation})`,
2235
+ ...scaledRectBorderRadius ? {
2236
+ rx: scaledRectBorderRadius.toString(),
2237
+ ry: scaledRectBorderRadius.toString()
2238
+ } : {},
2239
+ "data-type": "pcb_plated_hole",
2240
+ "data-pcb-layer": copperLayer
2241
+ },
2242
+ value: "",
2243
+ children: []
2244
+ },
2245
+ // 2. Draw the exposed opening in copper color
2246
+ {
2247
+ name: "rect",
2248
+ type: "element",
2249
+ attributes: {
2250
+ class: "pcb-hole-outer-exposed",
2251
+ fill: colorMap2.copper.top,
2252
+ x: (-maskWidth / 2).toString(),
2253
+ y: (-maskHeight / 2).toString(),
2254
+ width: maskWidth.toString(),
2255
+ height: maskHeight.toString(),
2256
+ transform: `translate(${x} ${y}) rotate(${-rotatedHole.rect_ccw_rotation})`,
2257
+ ...maskBorderRadius > 0 ? {
2258
+ rx: maskBorderRadius.toString(),
2259
+ ry: maskBorderRadius.toString()
2260
+ } : {},
2261
+ "data-type": "pcb_soldermask"
2262
+ },
2263
+ value: "",
2264
+ children: []
2265
+ },
2266
+ // 3. Draw the drill hole on top
2267
+ children[1]
2268
+ // Original hole
2269
+ ];
2270
+ } else {
2271
+ children.push({
2272
+ name: "rect",
2273
+ type: "element",
2274
+ attributes: {
2275
+ class: "pcb-solder-mask",
2276
+ fill: solderMaskColor,
2277
+ x: (-maskWidth / 2).toString(),
2278
+ y: (-maskHeight / 2).toString(),
2279
+ width: maskWidth.toString(),
2280
+ height: maskHeight.toString(),
2281
+ transform: `translate(${x} ${y}) rotate(${-rotatedHole.rect_ccw_rotation})`,
2282
+ ...scaledRectBorderRadius ? {
2283
+ rx: maskBorderRadius.toString(),
2284
+ ry: maskBorderRadius.toString()
2285
+ } : {},
2286
+ "data-type": "pcb_soldermask"
2287
+ },
2288
+ value: "",
2289
+ children: []
2290
+ });
2291
+ }
2063
2292
  }
2064
2293
  return [
2065
2294
  {
@@ -2603,10 +2832,16 @@ var DEFAULT_PCB_COLOR_MAP = {
2603
2832
  inner6: "rgb(255, 105, 180)",
2604
2833
  bottom: "rgb(77, 127, 196)"
2605
2834
  },
2606
- soldermask: {
2835
+ soldermaskWithCopperUnderneath: {
2607
2836
  top: "rgb(18, 82, 50)",
2608
2837
  bottom: "rgb(77, 127, 196)"
2609
2838
  },
2839
+ soldermask: {
2840
+ top: "rgb(12, 55, 33)",
2841
+ bottom: "rgb(52, 87, 136)"
2842
+ },
2843
+ substrate: "rgb(201, 162, 110)",
2844
+ // FR4 substrate color (tan/beige)
2610
2845
  drill: "#FF26E2",
2611
2846
  silkscreen: {
2612
2847
  top: "#f2eda1",
@@ -2725,7 +2960,7 @@ function createSvgObjectsFromSmtPad(pad, ctx) {
2725
2960
  if (layerFilter && pad.layer !== layerFilter) return [];
2726
2961
  const isCoveredWithSolderMask = Boolean(pad?.is_covered_with_solder_mask);
2727
2962
  const shouldshowSolderMask = showSolderMask && isCoveredWithSolderMask;
2728
- const solderMaskColor = colorMap2.soldermask[pad.layer] ?? colorMap2.soldermask.top;
2963
+ const soldermaskWithCopperUnderneathColor = colorMap2.soldermaskWithCopperUnderneath[pad.layer] ?? colorMap2.soldermaskWithCopperUnderneath.top;
2729
2964
  const soldermaskMargin = (pad.soldermask_margin ?? 0) * Math.abs(transform.a);
2730
2965
  if (pad.shape === "rect" || pad.shape === "rotated_rect") {
2731
2966
  const width = pad.width * Math.abs(transform.a);
@@ -2761,27 +2996,97 @@ function createSvgObjectsFromSmtPad(pad, ctx) {
2761
2996
  const maskWidth2 = width + 2 * soldermaskMargin;
2762
2997
  const maskHeight2 = height + 2 * soldermaskMargin;
2763
2998
  const maskBorderRadius2 = scaledBorderRadius ? scaledBorderRadius + soldermaskMargin : 0;
2764
- const maskElement2 = {
2765
- name: padElement2.name,
2766
- type: padElement2.type,
2999
+ if (soldermaskMargin < 0) {
3000
+ const coveredPadElement = {
3001
+ name: "rect",
3002
+ type: "element",
3003
+ value: "",
3004
+ children: [],
3005
+ attributes: {
3006
+ class: "pcb-pad-covered",
3007
+ fill: soldermaskWithCopperUnderneathColor,
3008
+ x: (-width / 2).toString(),
3009
+ y: (-height / 2).toString(),
3010
+ width: width.toString(),
3011
+ height: height.toString(),
3012
+ transform: `translate(${x} ${y}) rotate(${-pad.ccw_rotation})`,
3013
+ "data-type": "pcb_smtpad",
3014
+ "data-pcb-layer": pad.layer,
3015
+ ...scaledBorderRadius ? {
3016
+ rx: scaledBorderRadius.toString(),
3017
+ ry: scaledBorderRadius.toString()
3018
+ } : {}
3019
+ }
3020
+ };
3021
+ const exposedOpeningElement = {
3022
+ name: "rect",
3023
+ type: "element",
3024
+ value: "",
3025
+ children: [],
3026
+ attributes: {
3027
+ class: "pcb-pad-exposed",
3028
+ fill: layerNameToColor(pad.layer, colorMap2),
3029
+ x: (-maskWidth2 / 2).toString(),
3030
+ y: (-maskHeight2 / 2).toString(),
3031
+ width: maskWidth2.toString(),
3032
+ height: maskHeight2.toString(),
3033
+ transform: `translate(${x} ${y}) rotate(${-pad.ccw_rotation})`,
3034
+ "data-type": "pcb_soldermask",
3035
+ "data-pcb-layer": pad.layer,
3036
+ ...maskBorderRadius2 > 0 ? {
3037
+ rx: maskBorderRadius2.toString(),
3038
+ ry: maskBorderRadius2.toString()
3039
+ } : {}
3040
+ }
3041
+ };
3042
+ return [coveredPadElement, exposedOpeningElement];
3043
+ }
3044
+ if (soldermaskMargin === 0) {
3045
+ const coveredPadElement = {
3046
+ name: "rect",
3047
+ type: "element",
3048
+ value: "",
3049
+ children: [],
3050
+ attributes: {
3051
+ class: "pcb-pad-covered",
3052
+ fill: soldermaskWithCopperUnderneathColor,
3053
+ x: (-width / 2).toString(),
3054
+ y: (-height / 2).toString(),
3055
+ width: width.toString(),
3056
+ height: height.toString(),
3057
+ transform: `translate(${x} ${y}) rotate(${-pad.ccw_rotation})`,
3058
+ "data-type": "pcb_smtpad",
3059
+ "data-pcb-layer": pad.layer,
3060
+ ...scaledBorderRadius ? {
3061
+ rx: scaledBorderRadius.toString(),
3062
+ ry: scaledBorderRadius.toString()
3063
+ } : {}
3064
+ }
3065
+ };
3066
+ return [coveredPadElement];
3067
+ }
3068
+ const substrateElement2 = {
3069
+ name: "rect",
3070
+ type: "element",
2767
3071
  value: "",
2768
3072
  children: [],
2769
3073
  attributes: {
2770
- ...padElement2.attributes,
2771
- class: "pcb-solder-mask",
2772
- fill: solderMaskColor,
2773
- "data-type": "pcb_soldermask",
3074
+ class: "pcb-soldermask-cutout",
3075
+ fill: colorMap2.substrate,
2774
3076
  x: (-maskWidth2 / 2).toString(),
2775
3077
  y: (-maskHeight2 / 2).toString(),
2776
3078
  width: maskWidth2.toString(),
2777
3079
  height: maskHeight2.toString(),
3080
+ transform: `translate(${x} ${y}) rotate(${-pad.ccw_rotation})`,
2778
3081
  ...maskBorderRadius2 > 0 ? {
2779
3082
  rx: maskBorderRadius2.toString(),
2780
3083
  ry: maskBorderRadius2.toString()
2781
- } : {}
3084
+ } : {},
3085
+ "data-type": "pcb_soldermask_opening",
3086
+ "data-pcb-layer": `soldermask-${pad.layer}`
2782
3087
  }
2783
3088
  };
2784
- return [padElement2, maskElement2];
3089
+ return [substrateElement2, padElement2];
2785
3090
  }
2786
3091
  const padElement = {
2787
3092
  name: "rect",
@@ -2809,16 +3114,80 @@ function createSvgObjectsFromSmtPad(pad, ctx) {
2809
3114
  const maskWidth = width + 2 * soldermaskMargin;
2810
3115
  const maskHeight = height + 2 * soldermaskMargin;
2811
3116
  const maskBorderRadius = scaledBorderRadius ? scaledBorderRadius + soldermaskMargin : 0;
2812
- const maskElement = {
2813
- name: padElement.name,
2814
- type: padElement.type,
3117
+ if (soldermaskMargin < 0) {
3118
+ const coveredPadElement = {
3119
+ name: "rect",
3120
+ type: "element",
3121
+ value: "",
3122
+ children: [],
3123
+ attributes: {
3124
+ class: "pcb-pad-covered",
3125
+ fill: soldermaskWithCopperUnderneathColor,
3126
+ x: (x - width / 2).toString(),
3127
+ y: (y - height / 2).toString(),
3128
+ width: width.toString(),
3129
+ height: height.toString(),
3130
+ "data-type": "pcb_smtpad",
3131
+ "data-pcb-layer": pad.layer,
3132
+ ...scaledBorderRadius ? {
3133
+ rx: scaledBorderRadius.toString(),
3134
+ ry: scaledBorderRadius.toString()
3135
+ } : {}
3136
+ }
3137
+ };
3138
+ const exposedOpeningElement = {
3139
+ name: "rect",
3140
+ type: "element",
3141
+ value: "",
3142
+ children: [],
3143
+ attributes: {
3144
+ class: "pcb-pad-exposed",
3145
+ fill: layerNameToColor(pad.layer, colorMap2),
3146
+ x: (x - maskWidth / 2).toString(),
3147
+ y: (y - maskHeight / 2).toString(),
3148
+ width: maskWidth.toString(),
3149
+ height: maskHeight.toString(),
3150
+ "data-type": "pcb_soldermask",
3151
+ "data-pcb-layer": pad.layer,
3152
+ ...maskBorderRadius > 0 ? {
3153
+ rx: maskBorderRadius.toString(),
3154
+ ry: maskBorderRadius.toString()
3155
+ } : {}
3156
+ }
3157
+ };
3158
+ return [coveredPadElement, exposedOpeningElement];
3159
+ }
3160
+ if (soldermaskMargin === 0) {
3161
+ const coveredPadElement = {
3162
+ name: "rect",
3163
+ type: "element",
3164
+ value: "",
3165
+ children: [],
3166
+ attributes: {
3167
+ class: "pcb-pad-covered",
3168
+ fill: soldermaskWithCopperUnderneathColor,
3169
+ x: (x - width / 2).toString(),
3170
+ y: (y - height / 2).toString(),
3171
+ width: width.toString(),
3172
+ height: height.toString(),
3173
+ ...maskBorderRadius > 0 ? {
3174
+ rx: scaledBorderRadius.toString(),
3175
+ ry: scaledBorderRadius.toString()
3176
+ } : {},
3177
+ "data-type": "pcb_smtpad",
3178
+ "data-pcb-layer": pad.layer
3179
+ }
3180
+ };
3181
+ return [coveredPadElement];
3182
+ }
3183
+ const substrateElement = {
3184
+ name: "rect",
3185
+ type: "element",
2815
3186
  value: "",
2816
3187
  children: [],
2817
3188
  attributes: {
2818
- ...padElement.attributes,
2819
- class: "pcb-solder-mask",
2820
- fill: solderMaskColor,
2821
- "data-type": "pcb_soldermask",
3189
+ class: "pcb-soldermask-cutout",
3190
+ fill: colorMap2.substrate,
2822
3191
  x: (x - maskWidth / 2).toString(),
2823
3192
  y: (y - maskHeight / 2).toString(),
2824
3193
  width: maskWidth.toString(),
@@ -2826,10 +3195,12 @@ function createSvgObjectsFromSmtPad(pad, ctx) {
2826
3195
  ...maskBorderRadius > 0 ? {
2827
3196
  rx: maskBorderRadius.toString(),
2828
3197
  ry: maskBorderRadius.toString()
2829
- } : {}
3198
+ } : {},
3199
+ "data-type": "pcb_soldermask_opening",
3200
+ "data-pcb-layer": `soldermask-${pad.layer}`
2830
3201
  }
2831
3202
  };
2832
- return [padElement, maskElement];
3203
+ return [substrateElement, padElement];
2833
3204
  }
2834
3205
  if (pad.shape === "pill") {
2835
3206
  const width = pad.width * Math.abs(transform.a);
@@ -2857,65 +3228,178 @@ function createSvgObjectsFromSmtPad(pad, ctx) {
2857
3228
  if (!shouldshowSolderMask) {
2858
3229
  return [padElement];
2859
3230
  }
2860
- const maskWidth = width + 2 * soldermaskMargin;
2861
- const maskHeight = height + 2 * soldermaskMargin;
2862
- const maskRadius = radius + soldermaskMargin;
2863
- const maskElement = {
2864
- name: padElement.name,
2865
- type: padElement.type,
2866
- value: "",
2867
- children: [],
2868
- attributes: {
2869
- ...padElement.attributes,
2870
- class: "pcb-solder-mask",
2871
- fill: solderMaskColor,
2872
- "data-type": "pcb_soldermask",
2873
- x: (x - maskWidth / 2).toString(),
2874
- y: (y - maskHeight / 2).toString(),
2875
- width: maskWidth.toString(),
2876
- height: maskHeight.toString(),
2877
- rx: maskRadius.toString(),
2878
- ry: maskRadius.toString()
2879
- }
2880
- };
2881
- return [padElement, maskElement];
2882
- }
2883
- if (pad.shape === "circle") {
2884
- const radius = pad.radius * Math.abs(transform.a);
2885
- const [x, y] = applyToPoint20(transform, [pad.x, pad.y]);
2886
- const padElement = {
3231
+ const maskWidth = width + 2 * soldermaskMargin;
3232
+ const maskHeight = height + 2 * soldermaskMargin;
3233
+ const maskRadius = radius + soldermaskMargin;
3234
+ if (soldermaskMargin < 0) {
3235
+ const coveredPadElement = {
3236
+ name: "rect",
3237
+ type: "element",
3238
+ value: "",
3239
+ children: [],
3240
+ attributes: {
3241
+ class: "pcb-pad-covered",
3242
+ fill: soldermaskWithCopperUnderneathColor,
3243
+ x: (x - width / 2).toString(),
3244
+ y: (y - height / 2).toString(),
3245
+ width: width.toString(),
3246
+ height: height.toString(),
3247
+ rx: radius.toString(),
3248
+ ry: radius.toString(),
3249
+ "data-type": "pcb_smtpad",
3250
+ "data-pcb-layer": pad.layer
3251
+ }
3252
+ };
3253
+ const exposedOpeningElement = {
3254
+ name: "rect",
3255
+ type: "element",
3256
+ value: "",
3257
+ children: [],
3258
+ attributes: {
3259
+ class: "pcb-pad-exposed",
3260
+ fill: layerNameToColor(pad.layer, colorMap2),
3261
+ x: (x - maskWidth / 2).toString(),
3262
+ y: (y - maskHeight / 2).toString(),
3263
+ width: maskWidth.toString(),
3264
+ height: maskHeight.toString(),
3265
+ rx: maskRadius.toString(),
3266
+ ry: maskRadius.toString(),
3267
+ "data-type": "pcb_soldermask",
3268
+ "data-pcb-layer": pad.layer
3269
+ }
3270
+ };
3271
+ return [coveredPadElement, exposedOpeningElement];
3272
+ }
3273
+ if (soldermaskMargin === 0) {
3274
+ const coveredPadElement = {
3275
+ name: "rect",
3276
+ type: "element",
3277
+ value: "",
3278
+ children: [],
3279
+ attributes: {
3280
+ class: "pcb-pad-covered",
3281
+ fill: soldermaskWithCopperUnderneathColor,
3282
+ x: (x - width / 2).toString(),
3283
+ y: (y - height / 2).toString(),
3284
+ width: width.toString(),
3285
+ height: height.toString(),
3286
+ rx: radius.toString(),
3287
+ ry: radius.toString(),
3288
+ "data-type": "pcb_smtpad",
3289
+ "data-pcb-layer": pad.layer
3290
+ }
3291
+ };
3292
+ return [coveredPadElement];
3293
+ }
3294
+ const substrateElement = {
3295
+ name: "rect",
3296
+ type: "element",
3297
+ value: "",
3298
+ children: [],
3299
+ attributes: {
3300
+ class: "pcb-soldermask-cutout",
3301
+ fill: colorMap2.substrate,
3302
+ x: (x - maskWidth / 2).toString(),
3303
+ y: (y - maskHeight / 2).toString(),
3304
+ width: maskWidth.toString(),
3305
+ height: maskHeight.toString(),
3306
+ rx: maskRadius.toString(),
3307
+ ry: maskRadius.toString(),
3308
+ "data-type": "pcb_soldermask_opening",
3309
+ "data-pcb-layer": `soldermask-${pad.layer}`
3310
+ }
3311
+ };
3312
+ return [substrateElement, padElement];
3313
+ }
3314
+ if (pad.shape === "circle") {
3315
+ const radius = pad.radius * Math.abs(transform.a);
3316
+ const [x, y] = applyToPoint20(transform, [pad.x, pad.y]);
3317
+ const padElement = {
3318
+ name: "circle",
3319
+ type: "element",
3320
+ value: "",
3321
+ children: [],
3322
+ attributes: {
3323
+ class: "pcb-pad",
3324
+ fill: layerNameToColor(pad.layer, colorMap2),
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
+ if (!shouldshowSolderMask) {
3333
+ return [padElement];
3334
+ }
3335
+ const maskRadius = radius + soldermaskMargin;
3336
+ if (soldermaskMargin < 0) {
3337
+ const coveredPadElement = {
3338
+ name: "circle",
3339
+ type: "element",
3340
+ value: "",
3341
+ children: [],
3342
+ attributes: {
3343
+ class: "pcb-pad-covered",
3344
+ fill: soldermaskWithCopperUnderneathColor,
3345
+ cx: x.toString(),
3346
+ cy: y.toString(),
3347
+ r: radius.toString(),
3348
+ "data-type": "pcb_smtpad",
3349
+ "data-pcb-layer": pad.layer
3350
+ }
3351
+ };
3352
+ const exposedOpeningElement = {
3353
+ name: "circle",
3354
+ type: "element",
3355
+ value: "",
3356
+ children: [],
3357
+ attributes: {
3358
+ class: "pcb-pad-exposed",
3359
+ fill: layerNameToColor(pad.layer, colorMap2),
3360
+ cx: x.toString(),
3361
+ cy: y.toString(),
3362
+ r: maskRadius.toString(),
3363
+ "data-type": "pcb_soldermask",
3364
+ "data-pcb-layer": pad.layer
3365
+ }
3366
+ };
3367
+ return [coveredPadElement, exposedOpeningElement];
3368
+ }
3369
+ if (soldermaskMargin === 0) {
3370
+ const coveredPadElement = {
3371
+ name: "circle",
3372
+ type: "element",
3373
+ value: "",
3374
+ children: [],
3375
+ attributes: {
3376
+ class: "pcb-pad-covered",
3377
+ fill: soldermaskWithCopperUnderneathColor,
3378
+ cx: x.toString(),
3379
+ cy: y.toString(),
3380
+ r: radius.toString(),
3381
+ "data-type": "pcb_smtpad",
3382
+ "data-pcb-layer": pad.layer
3383
+ }
3384
+ };
3385
+ return [coveredPadElement];
3386
+ }
3387
+ const substrateElement = {
2887
3388
  name: "circle",
2888
3389
  type: "element",
2889
3390
  value: "",
2890
3391
  children: [],
2891
3392
  attributes: {
2892
- class: "pcb-pad",
2893
- fill: layerNameToColor(pad.layer, colorMap2),
3393
+ class: "pcb-soldermask-cutout",
3394
+ fill: colorMap2.substrate,
2894
3395
  cx: x.toString(),
2895
3396
  cy: y.toString(),
2896
- r: radius.toString(),
2897
- "data-type": "pcb_smtpad",
2898
- "data-pcb-layer": pad.layer
2899
- }
2900
- };
2901
- if (!shouldshowSolderMask) {
2902
- return [padElement];
2903
- }
2904
- const maskRadius = radius + soldermaskMargin;
2905
- const maskElement = {
2906
- name: padElement.name,
2907
- type: padElement.type,
2908
- value: "",
2909
- children: [],
2910
- attributes: {
2911
- ...padElement.attributes,
2912
- class: "pcb-solder-mask",
2913
- fill: solderMaskColor,
2914
- "data-type": "pcb_soldermask",
2915
- r: maskRadius.toString()
3397
+ r: maskRadius.toString(),
3398
+ "data-type": "pcb_soldermask_opening",
3399
+ "data-pcb-layer": `soldermask-${pad.layer}`
2916
3400
  }
2917
3401
  };
2918
- return [padElement, maskElement];
3402
+ return [substrateElement, padElement];
2919
3403
  }
2920
3404
  if (pad.shape === "polygon") {
2921
3405
  const points = (pad.points ?? []).map(
@@ -2954,20 +3438,65 @@ function createSvgObjectsFromSmtPad(pad, ctx) {
2954
3438
  ];
2955
3439
  });
2956
3440
  }
2957
- const maskElement = {
2958
- name: padElement.name,
2959
- type: padElement.type,
3441
+ if (soldermaskMargin < 0) {
3442
+ const coveredPadElement = {
3443
+ name: "polygon",
3444
+ type: "element",
3445
+ value: "",
3446
+ children: [],
3447
+ attributes: {
3448
+ class: "pcb-pad-covered",
3449
+ fill: soldermaskWithCopperUnderneathColor,
3450
+ points: points.map((p) => p.join(",")).join(" "),
3451
+ "data-type": "pcb_smtpad",
3452
+ "data-pcb-layer": pad.layer
3453
+ }
3454
+ };
3455
+ const exposedOpeningElement = {
3456
+ name: "polygon",
3457
+ type: "element",
3458
+ value: "",
3459
+ children: [],
3460
+ attributes: {
3461
+ class: "pcb-pad-exposed",
3462
+ fill: layerNameToColor(pad.layer, colorMap2),
3463
+ points: maskPoints.map((p) => p.join(",")).join(" "),
3464
+ "data-type": "pcb_soldermask",
3465
+ "data-pcb-layer": pad.layer
3466
+ }
3467
+ };
3468
+ return [coveredPadElement, exposedOpeningElement];
3469
+ }
3470
+ if (soldermaskMargin === 0) {
3471
+ const coveredPadElement = {
3472
+ name: "polygon",
3473
+ type: "element",
3474
+ value: "",
3475
+ children: [],
3476
+ attributes: {
3477
+ class: "pcb-pad-covered",
3478
+ fill: soldermaskWithCopperUnderneathColor,
3479
+ points: points.map((p) => p.join(",")).join(" "),
3480
+ "data-type": "pcb_smtpad",
3481
+ "data-pcb-layer": pad.layer
3482
+ }
3483
+ };
3484
+ return [coveredPadElement];
3485
+ }
3486
+ const substrateElement = {
3487
+ name: "polygon",
3488
+ type: "element",
2960
3489
  value: "",
2961
3490
  children: [],
2962
3491
  attributes: {
2963
- ...padElement.attributes,
2964
- class: "pcb-solder-mask",
2965
- fill: solderMaskColor,
2966
- "data-type": "pcb_soldermask",
2967
- points: maskPoints.map((p) => p.join(",")).join(" ")
3492
+ class: "pcb-soldermask-cutout",
3493
+ fill: colorMap2.substrate,
3494
+ points: maskPoints.map((p) => p.join(",")).join(" "),
3495
+ "data-type": "pcb_soldermask_opening",
3496
+ "data-pcb-layer": `soldermask-${pad.layer}`
2968
3497
  }
2969
3498
  };
2970
- return [padElement, maskElement];
3499
+ return [substrateElement, padElement];
2971
3500
  }
2972
3501
  return [];
2973
3502
  }
@@ -2975,7 +3504,7 @@ function createSvgObjectsFromSmtPad(pad, ctx) {
2975
3504
  // lib/pcb/svg-object-fns/create-svg-objects-from-pcb-board.ts
2976
3505
  import { applyToPoint as applyToPoint21 } from "transformation-matrix";
2977
3506
  function createSvgObjectsFromPcbBoard(pcbBoard, ctx) {
2978
- const { transform, colorMap: colorMap2 } = ctx;
3507
+ const { transform, colorMap: colorMap2, showSolderMask } = ctx;
2979
3508
  const { width, height, center, outline } = pcbBoard;
2980
3509
  let path;
2981
3510
  if (outline && Array.isArray(outline) && outline.length >= 3) {
@@ -3005,23 +3534,40 @@ function createSvgObjectsFromPcbBoard(pcbBoard, ctx) {
3005
3534
  path = `M ${topLeft[0]} ${topLeft[1]} L ${topRight[0]} ${topRight[1]} L ${bottomRight[0]} ${bottomRight[1]} L ${bottomLeft[0]} ${bottomLeft[1]}`;
3006
3535
  }
3007
3536
  path += " Z";
3008
- return [
3009
- {
3537
+ const svgObjects = [];
3538
+ if (showSolderMask) {
3539
+ svgObjects.push({
3010
3540
  name: "path",
3011
3541
  type: "element",
3012
3542
  value: "",
3013
3543
  children: [],
3014
3544
  attributes: {
3015
- class: "pcb-board",
3545
+ class: "pcb-board-soldermask",
3016
3546
  d: path,
3017
- fill: "none",
3018
- stroke: colorMap2.boardOutline,
3019
- "stroke-width": (0.1 * Math.abs(transform.a)).toString(),
3020
- "data-type": "pcb_board",
3021
- "data-pcb-layer": "board"
3547
+ fill: colorMap2.soldermask.top,
3548
+ "fill-opacity": "0.8",
3549
+ stroke: "none",
3550
+ "data-type": "pcb_soldermask",
3551
+ "data-pcb-layer": "soldermask-top"
3022
3552
  }
3553
+ });
3554
+ }
3555
+ svgObjects.push({
3556
+ name: "path",
3557
+ type: "element",
3558
+ value: "",
3559
+ children: [],
3560
+ attributes: {
3561
+ class: "pcb-board",
3562
+ d: path,
3563
+ fill: "none",
3564
+ stroke: colorMap2.boardOutline,
3565
+ "stroke-width": (0.1 * Math.abs(transform.a)).toString(),
3566
+ "data-type": "pcb_board",
3567
+ "data-pcb-layer": "board"
3023
3568
  }
3024
- ];
3569
+ });
3570
+ return svgObjects;
3025
3571
  }
3026
3572
 
3027
3573
  // lib/pcb/svg-object-fns/create-svg-objects-from-pcb-panel.ts
@@ -3154,20 +3700,54 @@ function createSvgObjectsFromPcbHole(hole, ctx) {
3154
3700
  return [holeElement2];
3155
3701
  }
3156
3702
  const maskRadius = radius + soldermaskMargin;
3157
- const maskElement2 = {
3158
- name: holeElement2.name,
3159
- type: holeElement2.type,
3703
+ if (soldermaskMargin < 0) {
3704
+ const coveredElement = {
3705
+ name: "circle",
3706
+ type: "element",
3707
+ value: "",
3708
+ children: [],
3709
+ attributes: {
3710
+ class: "pcb-hole-covered",
3711
+ fill: solderMaskColor,
3712
+ cx: x.toString(),
3713
+ cy: y.toString(),
3714
+ r: radius.toString(),
3715
+ "data-type": "pcb_hole",
3716
+ "data-pcb-layer": "drill"
3717
+ }
3718
+ };
3719
+ const exposedElement = {
3720
+ name: "circle",
3721
+ type: "element",
3722
+ value: "",
3723
+ children: [],
3724
+ attributes: {
3725
+ class: "pcb-hole-exposed",
3726
+ fill: colorMap2.drill,
3727
+ cx: x.toString(),
3728
+ cy: y.toString(),
3729
+ r: maskRadius.toString(),
3730
+ "data-type": "pcb_soldermask"
3731
+ }
3732
+ };
3733
+ return [coveredElement, exposedElement];
3734
+ }
3735
+ const substrateElement2 = {
3736
+ name: "circle",
3737
+ type: "element",
3160
3738
  value: "",
3161
3739
  children: [],
3162
3740
  attributes: {
3163
- ...holeElement2.attributes,
3164
- class: "pcb-solder-mask",
3165
- fill: solderMaskColor,
3166
- "data-type": "pcb_soldermask",
3167
- r: maskRadius.toString()
3741
+ class: "pcb-soldermask-cutout",
3742
+ fill: colorMap2.substrate,
3743
+ cx: x.toString(),
3744
+ cy: y.toString(),
3745
+ r: maskRadius.toString(),
3746
+ "data-type": "pcb_soldermask_opening",
3747
+ "data-pcb-layer": "soldermask-top"
3168
3748
  }
3169
3749
  };
3170
- return [holeElement2, maskElement2];
3750
+ return [substrateElement2, holeElement2];
3171
3751
  }
3172
3752
  const holeElement = {
3173
3753
  name: "rect",
@@ -3189,23 +3769,57 @@ function createSvgObjectsFromPcbHole(hole, ctx) {
3189
3769
  return [holeElement];
3190
3770
  }
3191
3771
  const maskDiameter = scaledDiameter + 2 * soldermaskMargin;
3192
- const maskElement = {
3193
- name: holeElement.name,
3194
- type: holeElement.type,
3772
+ if (soldermaskMargin < 0) {
3773
+ const coveredElement = {
3774
+ name: "rect",
3775
+ type: "element",
3776
+ value: "",
3777
+ children: [],
3778
+ attributes: {
3779
+ class: "pcb-hole-covered",
3780
+ fill: solderMaskColor,
3781
+ x: (x - radius).toString(),
3782
+ y: (y - radius).toString(),
3783
+ width: scaledDiameter.toString(),
3784
+ height: scaledDiameter.toString(),
3785
+ "data-type": "pcb_hole",
3786
+ "data-pcb-layer": "drill"
3787
+ }
3788
+ };
3789
+ const exposedElement = {
3790
+ name: "rect",
3791
+ type: "element",
3792
+ value: "",
3793
+ children: [],
3794
+ attributes: {
3795
+ class: "pcb-hole-exposed",
3796
+ fill: colorMap2.drill,
3797
+ x: (x - maskDiameter / 2).toString(),
3798
+ y: (y - maskDiameter / 2).toString(),
3799
+ width: maskDiameter.toString(),
3800
+ height: maskDiameter.toString(),
3801
+ "data-type": "pcb_soldermask"
3802
+ }
3803
+ };
3804
+ return [coveredElement, exposedElement];
3805
+ }
3806
+ const substrateElement = {
3807
+ name: "rect",
3808
+ type: "element",
3195
3809
  value: "",
3196
3810
  children: [],
3197
3811
  attributes: {
3198
- ...holeElement.attributes,
3199
- class: "pcb-solder-mask",
3200
- fill: solderMaskColor,
3201
- "data-type": "pcb_soldermask",
3812
+ class: "pcb-soldermask-cutout",
3813
+ fill: colorMap2.substrate,
3202
3814
  x: (x - maskDiameter / 2).toString(),
3203
3815
  y: (y - maskDiameter / 2).toString(),
3204
3816
  width: maskDiameter.toString(),
3205
- height: maskDiameter.toString()
3817
+ height: maskDiameter.toString(),
3818
+ "data-type": "pcb_soldermask_opening",
3819
+ "data-pcb-layer": "soldermask-top"
3206
3820
  }
3207
3821
  };
3208
- return [holeElement, maskElement];
3822
+ return [substrateElement, holeElement];
3209
3823
  }
3210
3824
  if (hole.hole_shape === "oval") {
3211
3825
  const scaledWidth = hole.hole_width * Math.abs(transform.a);
@@ -3233,21 +3847,57 @@ function createSvgObjectsFromPcbHole(hole, ctx) {
3233
3847
  }
3234
3848
  const maskRx = rx + soldermaskMargin;
3235
3849
  const maskRy = ry + soldermaskMargin;
3236
- const maskElement = {
3237
- name: holeElement.name,
3238
- type: holeElement.type,
3850
+ if (soldermaskMargin < 0) {
3851
+ const coveredElement = {
3852
+ name: "ellipse",
3853
+ type: "element",
3854
+ value: "",
3855
+ children: [],
3856
+ attributes: {
3857
+ class: "pcb-hole-covered",
3858
+ fill: solderMaskColor,
3859
+ cx: x.toString(),
3860
+ cy: y.toString(),
3861
+ rx: rx.toString(),
3862
+ ry: ry.toString(),
3863
+ "data-type": "pcb_hole",
3864
+ "data-pcb-layer": "drill"
3865
+ }
3866
+ };
3867
+ const exposedElement = {
3868
+ name: "ellipse",
3869
+ type: "element",
3870
+ value: "",
3871
+ children: [],
3872
+ attributes: {
3873
+ class: "pcb-hole-exposed",
3874
+ fill: colorMap2.drill,
3875
+ cx: x.toString(),
3876
+ cy: y.toString(),
3877
+ rx: maskRx.toString(),
3878
+ ry: maskRy.toString(),
3879
+ "data-type": "pcb_soldermask"
3880
+ }
3881
+ };
3882
+ return [coveredElement, exposedElement];
3883
+ }
3884
+ const substrateElement = {
3885
+ name: "ellipse",
3886
+ type: "element",
3239
3887
  value: "",
3240
3888
  children: [],
3241
3889
  attributes: {
3242
- ...holeElement.attributes,
3243
- class: "pcb-solder-mask",
3244
- fill: solderMaskColor,
3245
- "data-type": "pcb_soldermask",
3890
+ class: "pcb-soldermask-cutout",
3891
+ fill: colorMap2.substrate,
3892
+ cx: x.toString(),
3893
+ cy: y.toString(),
3246
3894
  rx: maskRx.toString(),
3247
- ry: maskRy.toString()
3895
+ ry: maskRy.toString(),
3896
+ "data-type": "pcb_soldermask_opening",
3897
+ "data-pcb-layer": "soldermask-top"
3248
3898
  }
3249
3899
  };
3250
- return [holeElement, maskElement];
3900
+ return [substrateElement, holeElement];
3251
3901
  }
3252
3902
  if (hole.hole_shape === "rect") {
3253
3903
  const scaledWidth = hole.hole_width * Math.abs(transform.a);
@@ -3273,23 +3923,57 @@ function createSvgObjectsFromPcbHole(hole, ctx) {
3273
3923
  }
3274
3924
  const maskWidth = scaledWidth + 2 * soldermaskMargin;
3275
3925
  const maskHeight = scaledHeight + 2 * soldermaskMargin;
3276
- const maskElement = {
3277
- name: holeElement.name,
3278
- type: holeElement.type,
3926
+ if (soldermaskMargin < 0) {
3927
+ const coveredElement = {
3928
+ name: "rect",
3929
+ type: "element",
3930
+ value: "",
3931
+ children: [],
3932
+ attributes: {
3933
+ class: "pcb-hole-covered",
3934
+ fill: solderMaskColor,
3935
+ x: (x - scaledWidth / 2).toString(),
3936
+ y: (y - scaledHeight / 2).toString(),
3937
+ width: scaledWidth.toString(),
3938
+ height: scaledHeight.toString(),
3939
+ "data-type": "pcb_hole",
3940
+ "data-pcb-layer": "drill"
3941
+ }
3942
+ };
3943
+ const exposedElement = {
3944
+ name: "rect",
3945
+ type: "element",
3946
+ value: "",
3947
+ children: [],
3948
+ attributes: {
3949
+ class: "pcb-hole-exposed",
3950
+ fill: colorMap2.drill,
3951
+ x: (x - maskWidth / 2).toString(),
3952
+ y: (y - maskHeight / 2).toString(),
3953
+ width: maskWidth.toString(),
3954
+ height: maskHeight.toString(),
3955
+ "data-type": "pcb_soldermask"
3956
+ }
3957
+ };
3958
+ return [coveredElement, exposedElement];
3959
+ }
3960
+ const substrateElement = {
3961
+ name: "rect",
3962
+ type: "element",
3279
3963
  value: "",
3280
3964
  children: [],
3281
3965
  attributes: {
3282
- ...holeElement.attributes,
3283
- class: "pcb-solder-mask",
3284
- fill: solderMaskColor,
3285
- "data-type": "pcb_soldermask",
3966
+ class: "pcb-soldermask-cutout",
3967
+ fill: colorMap2.substrate,
3286
3968
  x: (x - maskWidth / 2).toString(),
3287
3969
  y: (y - maskHeight / 2).toString(),
3288
3970
  width: maskWidth.toString(),
3289
- height: maskHeight.toString()
3971
+ height: maskHeight.toString(),
3972
+ "data-type": "pcb_soldermask_opening",
3973
+ "data-pcb-layer": "soldermask-top"
3290
3974
  }
3291
3975
  };
3292
- return [holeElement, maskElement];
3976
+ return [substrateElement, holeElement];
3293
3977
  }
3294
3978
  if (hole.hole_shape === "pill") {
3295
3979
  const scaledWidth = hole.hole_width * Math.abs(transform.a);
@@ -3336,20 +4020,48 @@ function createSvgObjectsFromPcbHole(hole, ctx) {
3336
4020
  // Vertical pill (taller than wide)
3337
4021
  `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`
3338
4022
  );
3339
- const maskElement = {
3340
- name: holeElement.name,
3341
- type: holeElement.type,
4023
+ if (soldermaskMargin < 0) {
4024
+ const coveredElement = {
4025
+ name: "path",
4026
+ type: "element",
4027
+ value: "",
4028
+ children: [],
4029
+ attributes: {
4030
+ class: "pcb-hole-covered",
4031
+ fill: solderMaskColor,
4032
+ d: pathD,
4033
+ "data-type": "pcb_hole",
4034
+ "data-pcb-layer": "drill"
4035
+ }
4036
+ };
4037
+ const exposedElement = {
4038
+ name: "path",
4039
+ type: "element",
4040
+ value: "",
4041
+ children: [],
4042
+ attributes: {
4043
+ class: "pcb-hole-exposed",
4044
+ fill: colorMap2.drill,
4045
+ d: maskPathD,
4046
+ "data-type": "pcb_soldermask"
4047
+ }
4048
+ };
4049
+ return [coveredElement, exposedElement];
4050
+ }
4051
+ const substrateElement = {
4052
+ name: "path",
4053
+ type: "element",
3342
4054
  value: "",
3343
4055
  children: [],
3344
4056
  attributes: {
3345
- ...holeElement.attributes,
3346
- class: "pcb-solder-mask",
3347
- fill: solderMaskColor,
3348
- "data-type": "pcb_soldermask",
3349
- d: maskPathD
4057
+ class: "pcb-soldermask-cutout",
4058
+ fill: colorMap2.substrate,
4059
+ d: maskPathD,
4060
+ "data-type": "pcb_soldermask_opening",
4061
+ "data-pcb-layer": "soldermask-top"
3350
4062
  }
3351
4063
  };
3352
- return [holeElement, maskElement];
4064
+ return [substrateElement, holeElement];
3353
4065
  }
3354
4066
  if (hole.hole_shape === "rotated_pill") {
3355
4067
  const scaledWidth = hole.hole_width * Math.abs(transform.a);
@@ -3398,20 +4110,50 @@ function createSvgObjectsFromPcbHole(hole, ctx) {
3398
4110
  // Vertical pill (taller than wide)
3399
4111
  `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`
3400
4112
  );
3401
- const maskElement = {
3402
- name: holeElement.name,
3403
- type: holeElement.type,
4113
+ if (soldermaskMargin < 0) {
4114
+ const coveredElement = {
4115
+ name: "path",
4116
+ type: "element",
4117
+ value: "",
4118
+ children: [],
4119
+ attributes: {
4120
+ class: "pcb-hole-covered",
4121
+ fill: solderMaskColor,
4122
+ d: pathD,
4123
+ transform: `translate(${x} ${y}) rotate(${-rotation})`,
4124
+ "data-type": "pcb_hole",
4125
+ "data-pcb-layer": "drill"
4126
+ }
4127
+ };
4128
+ const exposedElement = {
4129
+ name: "path",
4130
+ type: "element",
4131
+ value: "",
4132
+ children: [],
4133
+ attributes: {
4134
+ class: "pcb-hole-exposed",
4135
+ fill: colorMap2.drill,
4136
+ d: maskPathD,
4137
+ transform: `translate(${x} ${y}) rotate(${-rotation})`,
4138
+ "data-type": "pcb_soldermask"
4139
+ }
4140
+ };
4141
+ return [coveredElement, exposedElement];
4142
+ }
4143
+ const substrateElement = {
4144
+ name: "path",
4145
+ type: "element",
3404
4146
  value: "",
3405
4147
  children: [],
3406
4148
  attributes: {
3407
- ...holeElement.attributes,
3408
- class: "pcb-solder-mask",
3409
- fill: solderMaskColor,
3410
- "data-type": "pcb_soldermask",
3411
- d: maskPathD
4149
+ class: "pcb-soldermask-cutout",
4150
+ fill: colorMap2.substrate,
4151
+ d: maskPathD,
4152
+ "data-type": "pcb_soldermask_opening",
4153
+ "data-pcb-layer": "soldermask-top"
3412
4154
  }
3413
4155
  };
3414
- return [holeElement, maskElement];
4156
+ return [substrateElement, holeElement];
3415
4157
  }
3416
4158
  return [];
3417
4159
  }
@@ -4350,7 +5092,7 @@ function getSoftwareUsedString(circuitJson) {
4350
5092
  var package_default = {
4351
5093
  name: "circuit-to-svg",
4352
5094
  type: "module",
4353
- version: "0.0.276",
5095
+ version: "0.0.277",
4354
5096
  description: "Convert Circuit JSON to SVG",
4355
5097
  main: "dist/index.js",
4356
5098
  files: [
@@ -4411,6 +5153,7 @@ var TYPE_PRIORITY = {
4411
5153
  pcb_copper_pour: 35,
4412
5154
  pcb_via: 36,
4413
5155
  pcb_soldermask: 40,
5156
+ pcb_soldermask_opening: 41,
4414
5157
  pcb_solder_paste: 45,
4415
5158
  pcb_silkscreen_text: 50,
4416
5159
  pcb_silkscreen_path: 50,
@@ -4457,6 +5200,8 @@ function getLayerPriority(layer) {
4457
5200
  if (normalized === "global") return -100;
4458
5201
  if (normalized === "bottom") return 0;
4459
5202
  if (normalized === "board") return 2;
5203
+ if (normalized === "soldermask-top" || normalized === "soldermask-bottom")
5204
+ return 3;
4460
5205
  if (normalized.startsWith("inner")) {
4461
5206
  const match = normalized.match(/\d+/);
4462
5207
  const layerIndex = match ? parseInt(match[0], 10) : 0;
@@ -4547,6 +5292,11 @@ function convertCircuitJsonToPcbSvg(circuitJson, options) {
4547
5292
  top: colorOverrides?.soldermask?.top ?? DEFAULT_PCB_COLOR_MAP.soldermask.top,
4548
5293
  bottom: colorOverrides?.soldermask?.bottom ?? DEFAULT_PCB_COLOR_MAP.soldermask.bottom
4549
5294
  },
5295
+ soldermaskWithCopperUnderneath: {
5296
+ top: colorOverrides?.soldermaskWithCopperUnderneath?.top ?? DEFAULT_PCB_COLOR_MAP.soldermaskWithCopperUnderneath.top,
5297
+ bottom: colorOverrides?.soldermaskWithCopperUnderneath?.bottom ?? DEFAULT_PCB_COLOR_MAP.soldermaskWithCopperUnderneath.bottom
5298
+ },
5299
+ substrate: colorOverrides?.substrate ?? DEFAULT_PCB_COLOR_MAP.substrate,
4550
5300
  courtyard: colorOverrides?.courtyard ?? DEFAULT_PCB_COLOR_MAP.courtyard,
4551
5301
  debugComponent: {
4552
5302
  fill: colorOverrides?.debugComponent?.fill ?? DEFAULT_PCB_COLOR_MAP.debugComponent.fill,