circuit-json-to-lbrn 0.0.39 → 0.0.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +388 -374
  3. package/lib/ConvertContext.ts +3 -0
  4. package/lib/element-handlers/addPcbVia/index.ts +11 -2
  5. package/lib/element-handlers/addPlatedHole/addCirclePlatedHole.ts +9 -3
  6. package/lib/element-handlers/addPlatedHole/addCircularHoleWithRectPad.ts +33 -30
  7. package/lib/element-handlers/addPlatedHole/addHoleWithPolygonPad.ts +13 -22
  8. package/lib/element-handlers/addPlatedHole/addOvalPlatedHole.ts +20 -4
  9. package/lib/element-handlers/addPlatedHole/addPillHoleWithRectPad.ts +32 -30
  10. package/lib/element-handlers/addPlatedHole/addPillPlatedHole.ts +21 -8
  11. package/lib/element-handlers/addPlatedHole/addRotatedPillHoleWithRectPad.ts +32 -30
  12. package/lib/element-handlers/addSmtPad/addCircleSmtPad.ts +33 -38
  13. package/lib/element-handlers/addSmtPad/addPillSmtPad.ts +27 -37
  14. package/lib/element-handlers/addSmtPad/addPolygonSmtPad.ts +9 -29
  15. package/lib/element-handlers/addSmtPad/addRectSmtPad.ts +29 -51
  16. package/lib/element-handlers/addSmtPad/addRotatedPillSmtPad.ts +27 -37
  17. package/lib/element-handlers/addSmtPad/addRotatedRectSmtPad.ts +27 -37
  18. package/lib/helpers/addCopperGeometryToNetOrProject.ts +48 -0
  19. package/lib/index.ts +3 -0
  20. package/package.json +1 -1
  21. package/tests/basics/soldermask-margin/__snapshots__/negative-soldermask-margin.snap.svg +1 -1
  22. package/tests/basics/soldermask-margin/__snapshots__/percent-soldermask-margin.snap.svg +8 -0
  23. package/tests/basics/soldermask-margin/percent-soldermask-margin.test.ts +90 -0
  24. package/tests/examples/example02/__snapshots__/example02.snap.svg +1 -1
  25. package/tests/examples/example04/1206x4_3216metric.json +2973 -0
  26. package/tests/examples/example04/__snapshots__/example04.snap.svg +1 -0
  27. package/tests/examples/example04/example04.test.ts +32 -0
@@ -43,4 +43,7 @@ export interface ConvertContext {
43
43
  // Cut settings for trace clearance areas
44
44
  topTraceClearanceAreaCutSetting?: CutSetting
45
45
  bottomTraceClearanceAreaCutSetting?: CutSetting
46
+
47
+ // Percent-based solder mask margin (scales with element size)
48
+ solderMaskMarginPercent: number
46
49
  }
@@ -20,6 +20,7 @@ export const addPcbVia = (via: PcbVia, ctx: ConvertContext): void => {
20
20
  includeSoldermask,
21
21
  connMap,
22
22
  globalCopperSoldermaskMarginAdjustment,
23
+ solderMaskMarginPercent,
23
24
  includeLayers,
24
25
  } = ctx
25
26
  const centerX = via.x + origin.x
@@ -93,8 +94,16 @@ export const addPcbVia = (via: PcbVia, ctx: ConvertContext): void => {
93
94
 
94
95
  // Add soldermask opening if drawing soldermask
95
96
  if (via.outer_diameter > 0 && includeSoldermask) {
96
- const smRadius =
97
- via.outer_diameter / 2 + globalCopperSoldermaskMarginAdjustment
97
+ // Percent margin is additive and may be negative.
98
+ // Global adjustment is always applied.
99
+ // Percent margin is applied to full diameter
100
+ const percentMargin = (solderMaskMarginPercent / 100) * via.outer_diameter
101
+
102
+ // Total margin is additive
103
+ const totalMargin = globalCopperSoldermaskMarginAdjustment + percentMargin
104
+
105
+ // Clamp so radius never goes below zero
106
+ const smRadius = Math.max(via.outer_diameter / 2 + totalMargin, 0)
98
107
  const outer = createCirclePath({
99
108
  centerX,
100
109
  centerY,
@@ -22,6 +22,7 @@ export const addCirclePlatedHole = (
22
22
  includeSoldermask,
23
23
  connMap,
24
24
  globalCopperSoldermaskMarginAdjustment,
25
+ solderMaskMarginPercent,
25
26
  includeLayers,
26
27
  } = ctx
27
28
  const centerX = platedHole.x + origin.x
@@ -75,10 +76,15 @@ export const addCirclePlatedHole = (
75
76
 
76
77
  // Add soldermask opening if drawing soldermask
77
78
  if (platedHole.outer_diameter > 0 && includeSoldermask) {
78
- const smRadius =
79
- platedHole.outer_diameter / 2 +
79
+ // Percent margin is additive and may be negative.
80
+ // Absolute per-element margin and global adjustment are always applied.
81
+ const percentMargin =
82
+ (solderMaskMarginPercent / 100) * platedHole.outer_diameter
83
+ const totalMargin =
80
84
  globalCopperSoldermaskMarginAdjustment +
81
- (platedHole.soldermask_margin ?? 0)
85
+ (platedHole.soldermask_margin ?? 0) +
86
+ percentMargin
87
+ const smRadius = Math.max(platedHole.outer_diameter / 2 + totalMargin, 0)
82
88
  const outer = createCirclePath({
83
89
  centerX,
84
90
  centerY,
@@ -3,6 +3,7 @@ import type { ConvertContext } from "../../ConvertContext"
3
3
  import { ShapePath } from "lbrnts"
4
4
  import { createRoundedRectPath } from "../../helpers/roundedRectShape"
5
5
  import { createCirclePath } from "../../helpers/circleShape"
6
+ import { addCopperGeometryToNetOrProject } from "../../helpers/addCopperGeometryToNetOrProject"
6
7
 
7
8
  export const addCircularHoleWithRectPad = (
8
9
  platedHole: PcbHoleCircularWithRectPad,
@@ -10,14 +11,13 @@ export const addCircularHoleWithRectPad = (
10
11
  ): void => {
11
12
  const {
12
13
  project,
13
- topCopperCutSetting,
14
- bottomCopperCutSetting,
15
14
  soldermaskCutSetting,
16
15
  throughBoardCutSetting,
17
16
  origin,
18
17
  includeCopper,
19
18
  includeSoldermask,
20
19
  globalCopperSoldermaskMarginAdjustment,
20
+ solderMaskMarginPercent,
21
21
  includeLayers,
22
22
  } = ctx
23
23
  const centerX = platedHole.x + origin.x
@@ -39,38 +39,41 @@ export const addCircularHoleWithRectPad = (
39
39
  // Add the rectangular pad if drawing copper
40
40
  // Plated holes go through all layers, so add to both top and bottom
41
41
  if (includeCopper) {
42
- if (includeLayers.includes("top")) {
43
- project.children.push(
44
- new ShapePath({
45
- cutIndex: topCopperCutSetting.index,
46
- verts: padPath.verts,
47
- prims: padPath.prims,
48
- isClosed: true,
49
- }),
50
- )
51
- }
52
- if (includeLayers.includes("bottom")) {
53
- project.children.push(
54
- new ShapePath({
55
- cutIndex: bottomCopperCutSetting.index,
56
- verts: padPath.verts,
57
- prims: padPath.prims,
58
- isClosed: true,
59
- }),
60
- )
61
- }
42
+ addCopperGeometryToNetOrProject({
43
+ geometryId: platedHole.pcb_plated_hole_id,
44
+ path: padPath,
45
+ layer: "top",
46
+ ctx,
47
+ })
48
+ addCopperGeometryToNetOrProject({
49
+ geometryId: platedHole.pcb_plated_hole_id,
50
+ path: padPath,
51
+ layer: "bottom",
52
+ ctx,
53
+ })
62
54
  }
63
55
 
64
56
  // Add soldermask opening if drawing soldermask
65
57
  if (includeSoldermask) {
66
- const smPadWidth =
67
- padWidth +
68
- 2 * globalCopperSoldermaskMarginAdjustment +
69
- (platedHole.soldermask_margin ?? 0)
70
- const smPadHeight =
71
- padHeight +
72
- 2 * globalCopperSoldermaskMarginAdjustment +
73
- (platedHole.soldermask_margin ?? 0)
58
+ // Percent margin is additive and may be negative.
59
+ // Absolute per-element margin and global adjustment are always applied.
60
+ const percentMarginX = (solderMaskMarginPercent / 100) * padWidth
61
+ const percentMarginY = (solderMaskMarginPercent / 100) * padHeight
62
+
63
+ const totalMarginX = Math.max(
64
+ globalCopperSoldermaskMarginAdjustment +
65
+ (platedHole.soldermask_margin ?? 0) +
66
+ percentMarginX,
67
+ -padWidth / 2,
68
+ )
69
+ const totalMarginY = Math.max(
70
+ globalCopperSoldermaskMarginAdjustment +
71
+ (platedHole.soldermask_margin ?? 0) +
72
+ percentMarginY,
73
+ -padHeight / 2,
74
+ )
75
+ const smPadWidth = padWidth + 2 * totalMarginX
76
+ const smPadHeight = padHeight + 2 * totalMarginY
74
77
  const smPadPath = createRoundedRectPath({
75
78
  centerX,
76
79
  centerY,
@@ -3,6 +3,7 @@ import type { ConvertContext } from "../../ConvertContext"
3
3
  import { ShapePath } from "lbrnts"
4
4
  import { createPolygonPathFromOutline } from "../../helpers/polygonShape"
5
5
  import { createCirclePath } from "../../helpers/circleShape"
6
+ import { addCopperGeometryToNetOrProject } from "../../helpers/addCopperGeometryToNetOrProject"
6
7
 
7
8
  export const addHoleWithPolygonPad = (
8
9
  platedHole: PcbHoleWithPolygonPad,
@@ -10,8 +11,6 @@ export const addHoleWithPolygonPad = (
10
11
  ): void => {
11
12
  const {
12
13
  project,
13
- topCopperCutSetting,
14
- bottomCopperCutSetting,
15
14
  soldermaskCutSetting,
16
15
  throughBoardCutSetting,
17
16
  origin,
@@ -32,26 +31,18 @@ export const addHoleWithPolygonPad = (
32
31
  // Add the polygon pad if drawing copper
33
32
  // Plated holes go through all layers, so add to both top and bottom
34
33
  if (includeCopper) {
35
- if (includeLayers.includes("top")) {
36
- project.children.push(
37
- new ShapePath({
38
- cutIndex: topCopperCutSetting.index,
39
- verts: pad.verts,
40
- prims: pad.prims,
41
- isClosed: true,
42
- }),
43
- )
44
- }
45
- if (includeLayers.includes("bottom")) {
46
- project.children.push(
47
- new ShapePath({
48
- cutIndex: bottomCopperCutSetting.index,
49
- verts: pad.verts,
50
- prims: pad.prims,
51
- isClosed: true,
52
- }),
53
- )
54
- }
34
+ addCopperGeometryToNetOrProject({
35
+ geometryId: platedHole.pcb_plated_hole_id,
36
+ path: pad,
37
+ layer: "top",
38
+ ctx,
39
+ })
40
+ addCopperGeometryToNetOrProject({
41
+ geometryId: platedHole.pcb_plated_hole_id,
42
+ path: pad,
43
+ layer: "bottom",
44
+ ctx,
45
+ })
55
46
  }
56
47
 
57
48
  // Add soldermask opening if drawing soldermask
@@ -17,6 +17,7 @@ export const addOvalPlatedHole = (
17
17
  includeCopper,
18
18
  includeSoldermask,
19
19
  globalCopperSoldermaskMarginAdjustment,
20
+ solderMaskMarginPercent,
20
21
  includeLayers,
21
22
  } = ctx
22
23
 
@@ -70,11 +71,26 @@ export const addOvalPlatedHole = (
70
71
  platedHole.outer_height > 0 &&
71
72
  includeSoldermask
72
73
  ) {
73
- const soldermaskMargin =
74
+ // Percent margin is additive and may be negative.
75
+ // Absolute per-element margin and global adjustment are always applied.
76
+ const percentMarginX =
77
+ (solderMaskMarginPercent / 100) * platedHole.outer_width
78
+ const percentMarginY =
79
+ (solderMaskMarginPercent / 100) * platedHole.outer_height
80
+ const totalMarginX = Math.max(
74
81
  globalCopperSoldermaskMarginAdjustment +
75
- (platedHole.soldermask_margin ?? 0)
76
- const smWidth = platedHole.outer_width + 2 * soldermaskMargin
77
- const smHeight = platedHole.outer_height + 2 * soldermaskMargin
82
+ (platedHole.soldermask_margin ?? 0) +
83
+ percentMarginX,
84
+ -platedHole.outer_width / 2,
85
+ )
86
+ const totalMarginY = Math.max(
87
+ globalCopperSoldermaskMarginAdjustment +
88
+ (platedHole.soldermask_margin ?? 0) +
89
+ percentMarginY,
90
+ -platedHole.outer_height / 2,
91
+ )
92
+ const smWidth = platedHole.outer_width + 2 * totalMarginX
93
+ const smHeight = platedHole.outer_height + 2 * totalMarginY
78
94
  const outer = createOvalPath({
79
95
  centerX,
80
96
  centerY,
@@ -3,6 +3,7 @@ import type { ConvertContext } from "../../ConvertContext"
3
3
  import { ShapePath } from "lbrnts"
4
4
  import { createRoundedRectPath } from "../../helpers/roundedRectShape"
5
5
  import { createPillPath } from "../../helpers/pillShape"
6
+ import { addCopperGeometryToNetOrProject } from "../../helpers/addCopperGeometryToNetOrProject"
6
7
 
7
8
  export const addPillHoleWithRectPad = (
8
9
  platedHole: PcbHolePillWithRectPad,
@@ -10,14 +11,13 @@ export const addPillHoleWithRectPad = (
10
11
  ): void => {
11
12
  const {
12
13
  project,
13
- topCopperCutSetting,
14
- bottomCopperCutSetting,
15
14
  soldermaskCutSetting,
16
15
  throughBoardCutSetting,
17
16
  origin,
18
17
  includeCopper,
19
18
  includeSoldermask,
20
19
  globalCopperSoldermaskMarginAdjustment,
20
+ solderMaskMarginPercent,
21
21
  includeLayers,
22
22
  } = ctx
23
23
  const centerX = platedHole.x + origin.x
@@ -39,38 +39,40 @@ export const addPillHoleWithRectPad = (
39
39
  // Add the rectangular pad if drawing copper
40
40
  // Plated holes go through all layers, so add to both top and bottom
41
41
  if (includeCopper) {
42
- if (includeLayers.includes("top")) {
43
- project.children.push(
44
- new ShapePath({
45
- cutIndex: topCopperCutSetting.index,
46
- verts: padPath.verts,
47
- prims: padPath.prims,
48
- isClosed: true,
49
- }),
50
- )
51
- }
52
- if (includeLayers.includes("bottom")) {
53
- project.children.push(
54
- new ShapePath({
55
- cutIndex: bottomCopperCutSetting.index,
56
- verts: padPath.verts,
57
- prims: padPath.prims,
58
- isClosed: true,
59
- }),
60
- )
61
- }
42
+ addCopperGeometryToNetOrProject({
43
+ geometryId: platedHole.pcb_plated_hole_id,
44
+ path: padPath,
45
+ layer: "top",
46
+ ctx,
47
+ })
48
+ addCopperGeometryToNetOrProject({
49
+ geometryId: platedHole.pcb_plated_hole_id,
50
+ path: padPath,
51
+ layer: "bottom",
52
+ ctx,
53
+ })
62
54
  }
63
55
 
64
56
  // Add soldermask opening if drawing soldermask
65
57
  if (includeSoldermask) {
66
- const smPadWidth =
67
- padWidth +
68
- 2 * globalCopperSoldermaskMarginAdjustment +
69
- (platedHole.soldermask_margin ?? 0)
70
- const smPadHeight =
71
- padHeight +
72
- 2 * globalCopperSoldermaskMarginAdjustment +
73
- (platedHole.soldermask_margin ?? 0)
58
+ // Percent margin is additive and may be negative.
59
+ // Absolute per-element margin and global adjustment are always applied.
60
+ const percentMarginX = (solderMaskMarginPercent / 100) * padWidth
61
+ const percentMarginY = (solderMaskMarginPercent / 100) * padHeight
62
+ const totalMarginX = Math.max(
63
+ globalCopperSoldermaskMarginAdjustment +
64
+ (platedHole.soldermask_margin ?? 0) +
65
+ percentMarginX,
66
+ -padWidth / 2,
67
+ )
68
+ const totalMarginY = Math.max(
69
+ globalCopperSoldermaskMarginAdjustment +
70
+ (platedHole.soldermask_margin ?? 0) +
71
+ percentMarginY,
72
+ -padHeight / 2,
73
+ )
74
+ const smPadWidth = padWidth + 2 * totalMarginX
75
+ const smPadHeight = padHeight + 2 * totalMarginY
74
76
  const smPadPath = createRoundedRectPath({
75
77
  centerX,
76
78
  centerY,
@@ -17,6 +17,7 @@ export const addPcbPlatedHolePill = (
17
17
  includeCopper,
18
18
  includeSoldermask,
19
19
  globalCopperSoldermaskMarginAdjustment,
20
+ solderMaskMarginPercent,
20
21
  includeLayers,
21
22
  } = ctx
22
23
  const centerX = platedHole.x + origin.x
@@ -65,14 +66,26 @@ export const addPcbPlatedHolePill = (
65
66
  platedHole.outer_height > 0 &&
66
67
  includeSoldermask
67
68
  ) {
68
- const smWidth =
69
- platedHole.outer_width +
70
- 2 * globalCopperSoldermaskMarginAdjustment +
71
- (platedHole.soldermask_margin ?? 0)
72
- const smHeight =
73
- platedHole.outer_height +
74
- 2 * globalCopperSoldermaskMarginAdjustment +
75
- (platedHole.soldermask_margin ?? 0)
69
+ // Percent margin is additive and may be negative.
70
+ // Absolute per-element margin and global adjustment are always applied.
71
+ const percentMarginX =
72
+ (solderMaskMarginPercent / 100) * platedHole.outer_width
73
+ const percentMarginY =
74
+ (solderMaskMarginPercent / 100) * platedHole.outer_height
75
+ const totalMarginX = Math.max(
76
+ globalCopperSoldermaskMarginAdjustment +
77
+ (platedHole.soldermask_margin ?? 0) +
78
+ percentMarginX,
79
+ -platedHole.outer_width / 2,
80
+ )
81
+ const totalMarginY = Math.max(
82
+ globalCopperSoldermaskMarginAdjustment +
83
+ (platedHole.soldermask_margin ?? 0) +
84
+ percentMarginY,
85
+ -platedHole.outer_height / 2,
86
+ )
87
+ const smWidth = platedHole.outer_width + 2 * totalMarginX
88
+ const smHeight = platedHole.outer_height + 2 * totalMarginY
76
89
  const outer = createPillPath({
77
90
  centerX,
78
91
  centerY,
@@ -3,6 +3,7 @@ import type { ConvertContext } from "../../ConvertContext"
3
3
  import { ShapePath } from "lbrnts"
4
4
  import { createRoundedRectPath } from "../../helpers/roundedRectShape"
5
5
  import { createPillPath } from "../../helpers/pillShape"
6
+ import { addCopperGeometryToNetOrProject } from "../../helpers/addCopperGeometryToNetOrProject"
6
7
 
7
8
  export const addRotatedPillHoleWithRectPad = (
8
9
  platedHole: PcbHoleRotatedPillWithRectPad,
@@ -10,14 +11,13 @@ export const addRotatedPillHoleWithRectPad = (
10
11
  ): void => {
11
12
  const {
12
13
  project,
13
- topCopperCutSetting,
14
- bottomCopperCutSetting,
15
14
  soldermaskCutSetting,
16
15
  throughBoardCutSetting,
17
16
  origin,
18
17
  includeCopper,
19
18
  includeSoldermask,
20
19
  globalCopperSoldermaskMarginAdjustment,
20
+ solderMaskMarginPercent,
21
21
  includeLayers,
22
22
  } = ctx
23
23
  const centerX = platedHole.x + origin.x
@@ -42,38 +42,40 @@ export const addRotatedPillHoleWithRectPad = (
42
42
  // Add the rectangular pad if drawing copper
43
43
  // Plated holes go through all layers, so add to both top and bottom
44
44
  if (includeCopper) {
45
- if (includeLayers.includes("top")) {
46
- project.children.push(
47
- new ShapePath({
48
- cutIndex: topCopperCutSetting.index,
49
- verts: padPath.verts,
50
- prims: padPath.prims,
51
- isClosed: true,
52
- }),
53
- )
54
- }
55
- if (includeLayers.includes("bottom")) {
56
- project.children.push(
57
- new ShapePath({
58
- cutIndex: bottomCopperCutSetting.index,
59
- verts: padPath.verts,
60
- prims: padPath.prims,
61
- isClosed: true,
62
- }),
63
- )
64
- }
45
+ addCopperGeometryToNetOrProject({
46
+ geometryId: platedHole.pcb_plated_hole_id,
47
+ path: padPath,
48
+ layer: "top",
49
+ ctx,
50
+ })
51
+ addCopperGeometryToNetOrProject({
52
+ geometryId: platedHole.pcb_plated_hole_id,
53
+ path: padPath,
54
+ layer: "bottom",
55
+ ctx,
56
+ })
65
57
  }
66
58
 
67
59
  // Add soldermask opening if drawing soldermask
68
60
  if (includeSoldermask) {
69
- const smPadWidth =
70
- padWidth +
71
- 2 * globalCopperSoldermaskMarginAdjustment +
72
- (platedHole.soldermask_margin ?? 0)
73
- const smPadHeight =
74
- padHeight +
75
- 2 * globalCopperSoldermaskMarginAdjustment +
76
- (platedHole.soldermask_margin ?? 0)
61
+ // Percent margin is additive and may be negative.
62
+ // Absolute per-element margin and global adjustment are always applied.
63
+ const percentMarginX = (solderMaskMarginPercent / 100) * padWidth
64
+ const percentMarginY = (solderMaskMarginPercent / 100) * padHeight
65
+ const totalMarginX = Math.max(
66
+ globalCopperSoldermaskMarginAdjustment +
67
+ (platedHole.soldermask_margin ?? 0) +
68
+ percentMarginX,
69
+ -padWidth / 2,
70
+ )
71
+ const totalMarginY = Math.max(
72
+ globalCopperSoldermaskMarginAdjustment +
73
+ (platedHole.soldermask_margin ?? 0) +
74
+ percentMarginY,
75
+ -padHeight / 2,
76
+ )
77
+ const smPadWidth = padWidth + 2 * totalMarginX
78
+ const smPadHeight = padHeight + 2 * totalMarginY
77
79
  const smPadPath = createRoundedRectPath({
78
80
  centerX,
79
81
  centerY,
@@ -2,8 +2,7 @@ import type { PcbSmtPadCircle } from "circuit-json"
2
2
  import type { ConvertContext } from "../../ConvertContext"
3
3
  import { ShapePath } from "lbrnts"
4
4
  import { createCirclePath } from "../../helpers/circleShape"
5
- import { Circle, Polygon, point } from "@flatten-js/core"
6
- import { circleToPolygon } from "../addPcbTrace/circle-to-polygon"
5
+ import { addCopperGeometryToNetOrProject } from "../../helpers/addCopperGeometryToNetOrProject"
7
6
 
8
7
  export const addCircleSmtPad = (
9
8
  smtPad: PcbSmtPadCircle,
@@ -11,16 +10,12 @@ export const addCircleSmtPad = (
11
10
  ): void => {
12
11
  const {
13
12
  project,
14
- topCopperCutSetting,
15
- bottomCopperCutSetting,
16
13
  soldermaskCutSetting,
17
- topCutNetGeoms,
18
- bottomCutNetGeoms,
19
14
  origin,
20
15
  includeCopper,
21
16
  includeSoldermask,
22
- connMap,
23
17
  globalCopperSoldermaskMarginAdjustment,
18
+ solderMaskMarginPercent,
24
19
  includeLayers,
25
20
  } = ctx
26
21
 
@@ -33,50 +28,50 @@ export const addCircleSmtPad = (
33
28
  return
34
29
  }
35
30
 
36
- // Select the correct cut setting and net geoms based on layer
37
- const copperCutSetting =
38
- padLayer === "top" ? topCopperCutSetting : bottomCopperCutSetting
39
- const netGeoms = padLayer === "top" ? topCutNetGeoms : bottomCutNetGeoms
40
-
41
31
  const centerX = smtPad.x + origin.x
42
32
  const centerY = smtPad.y + origin.y
43
33
 
44
34
  if (smtPad.radius > 0) {
45
35
  const outerRadius = smtPad.radius
46
36
 
47
- // Add to netGeoms for copper (will be merged with traces)
37
+ // Add to copper geometry (will be merged with traces if connected)
48
38
  if (includeCopper) {
49
- const netId = connMap.getNetConnectedToId(smtPad.pcb_smtpad_id)
50
- const circle = new Circle(point(centerX, centerY), outerRadius)
51
- const polygon = circleToPolygon(circle)
39
+ const path = createCirclePath({
40
+ centerX,
41
+ centerY,
42
+ radius: outerRadius,
43
+ })
52
44
 
53
- if (netId) {
54
- // Add to netGeoms to be merged with other elements on the same net
55
- netGeoms.get(netId)?.push(polygon)
56
- } else {
57
- // No net connection - draw directly
58
- const outer = createCirclePath({
59
- centerX,
60
- centerY,
61
- radius: outerRadius,
62
- })
63
- project.children.push(
64
- new ShapePath({
65
- cutIndex: copperCutSetting.index,
66
- verts: outer.verts,
67
- prims: outer.prims,
68
- isClosed: true,
69
- }),
70
- )
71
- }
45
+ addCopperGeometryToNetOrProject({
46
+ geometryId: smtPad.pcb_smtpad_id,
47
+ path,
48
+ layer: padLayer,
49
+ ctx,
50
+ })
72
51
  }
73
52
 
74
53
  // Add soldermask opening if drawing soldermask
75
54
  if (includeSoldermask) {
76
- const smRadius =
77
- outerRadius +
55
+ // Percent margin is additive and may be negative.
56
+ // Absolute per-element margin and global adjustment are always applied.
57
+ const elementWidth = 2 * outerRadius
58
+ const elementHeight = 2 * outerRadius
59
+ const percentMarginX = (solderMaskMarginPercent / 100) * elementWidth
60
+ const percentMarginY = (solderMaskMarginPercent / 100) * elementHeight
61
+ const totalMarginX = Math.max(
62
+ globalCopperSoldermaskMarginAdjustment +
63
+ (smtPad.soldermask_margin ?? 0) +
64
+ percentMarginX,
65
+ -elementWidth / 2,
66
+ )
67
+ const totalMarginY = Math.max(
78
68
  globalCopperSoldermaskMarginAdjustment +
79
- (smtPad.soldermask_margin ?? 0)
69
+ (smtPad.soldermask_margin ?? 0) +
70
+ percentMarginY,
71
+ -elementHeight / 2,
72
+ )
73
+ // Since symmetric, use totalMarginX (same as totalMarginY)
74
+ const smRadius = outerRadius + totalMarginX
80
75
  const outer = createCirclePath({
81
76
  centerX,
82
77
  centerY,