circuit-to-canvas 0.0.49 → 0.0.51

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 (52) hide show
  1. package/dist/index.d.ts +13 -5
  2. package/dist/index.js +1450 -1226
  3. package/lib/drawer/CircuitToCanvasDrawer.ts +262 -312
  4. package/lib/drawer/elements/helper-functions/draw-pill.ts +39 -0
  5. package/lib/drawer/elements/helper-functions/draw-polygon.ts +25 -0
  6. package/lib/drawer/elements/helper-functions/draw-rounded-rect.ts +34 -0
  7. package/lib/drawer/elements/helper-functions/index.ts +3 -0
  8. package/lib/drawer/elements/pcb-board.ts +13 -3
  9. package/lib/drawer/elements/pcb-hole.ts +56 -338
  10. package/lib/drawer/elements/pcb-plated-hole.ts +154 -442
  11. package/lib/drawer/elements/pcb-smtpad.ts +5 -271
  12. package/lib/drawer/elements/pcb-soldermask/board.ts +44 -0
  13. package/lib/drawer/elements/pcb-soldermask/cutout.ts +74 -0
  14. package/lib/drawer/elements/pcb-soldermask/hole.ts +288 -0
  15. package/lib/drawer/elements/pcb-soldermask/index.ts +140 -0
  16. package/lib/drawer/elements/pcb-soldermask/plated-hole.ts +365 -0
  17. package/lib/drawer/elements/pcb-soldermask/smt-pad.ts +354 -0
  18. package/lib/drawer/elements/pcb-soldermask/via.ts +27 -0
  19. package/lib/drawer/elements/soldermask-margin.ts +39 -8
  20. package/package.json +2 -2
  21. package/tests/board-snapshot/__snapshots__/usb-c-flashlight-board.snap.png +0 -0
  22. package/tests/board-snapshot/usb-c-flashlight-board.test.ts +1 -0
  23. package/tests/elements/__snapshots__/board-with-elements.snap.png +0 -0
  24. package/tests/elements/__snapshots__/brep-copper-pours.snap.png +0 -0
  25. package/tests/elements/__snapshots__/custom-outline-board.snap.png +0 -0
  26. package/tests/elements/__snapshots__/oval-plated-hole.snap.png +0 -0
  27. package/tests/elements/__snapshots__/pcb-board.snap.png +0 -0
  28. package/tests/elements/__snapshots__/pcb-comprehensive-soldermask-margin.snap.png +0 -0
  29. package/tests/elements/__snapshots__/pcb-fabrication-note-dimension.snap.png +0 -0
  30. package/tests/elements/__snapshots__/pcb-hole-soldermask-margin.snap.png +0 -0
  31. package/tests/elements/__snapshots__/pcb-keepout-layer-filter.snap.png +0 -0
  32. package/tests/elements/__snapshots__/pcb-keepout-multiple-layers.snap.png +0 -0
  33. package/tests/elements/__snapshots__/pcb-keepout-rect-and-circle.snap.png +0 -0
  34. package/tests/elements/__snapshots__/pcb-keepout-with-group-id.snap.png +0 -0
  35. package/tests/elements/__snapshots__/pcb-no-soldermask.snap.png +0 -0
  36. package/tests/elements/__snapshots__/pcb-plated-hole-soldermask-margin.snap.png +0 -0
  37. package/tests/elements/__snapshots__/pcb-plated-hole.snap.png +0 -0
  38. package/tests/elements/__snapshots__/pcb-silkscreen-on-component.snap.png +0 -0
  39. package/tests/elements/__snapshots__/pcb-silkscreen-oval.snap.png +0 -0
  40. package/tests/elements/__snapshots__/pcb-smtpad-asymmetric-soldermask-margin.snap.png +0 -0
  41. package/tests/elements/__snapshots__/pcb-smtpad-soldermask-coverage.snap.png +0 -0
  42. package/tests/elements/__snapshots__/pcb-smtpad-soldermask-margin.snap.png +0 -0
  43. package/tests/elements/__snapshots__/pill-plated-hole.snap.png +0 -0
  44. package/tests/elements/pcb-comprehensive-soldermask-margin.test.ts +2 -2
  45. package/tests/elements/pcb-hole-soldermask-margin.test.ts +155 -2
  46. package/tests/elements/pcb-no-soldermask.test.ts +1281 -0
  47. package/tests/elements/pcb-plated-hole-soldermask-margin.test.ts +1 -1
  48. package/tests/elements/pcb-plated-hole.test.ts +40 -4
  49. package/tests/elements/pcb-smtpad-asymmetric-soldermask-margin.test.ts +140 -0
  50. package/tests/elements/pcb-smtpad-soldermask-coverage.test.ts +1 -1
  51. package/tests/elements/pcb-smtpad-soldermask-margin.test.ts +18 -2
  52. package/tests/fixtures/getStackedPngSvgComparison.ts +8 -2
@@ -6,451 +6,192 @@ import { drawRect } from "../shapes/rect"
6
6
  import { drawOval } from "../shapes/oval"
7
7
  import { drawPill } from "../shapes/pill"
8
8
  import { drawPolygon } from "../shapes/polygon"
9
- import {
10
- drawSoldermaskRingForCircle,
11
- drawSoldermaskRingForOval,
12
- drawSoldermaskRingForPill,
13
- drawSoldermaskRingForRect,
14
- drawSoldermaskRingForPolygon,
15
- offsetPolygonPoints,
16
- } from "./soldermask-margin"
9
+ import { offsetPolygonPoints } from "./soldermask-margin"
17
10
 
18
11
  export interface DrawPcbPlatedHoleParams {
19
12
  ctx: CanvasContext
20
13
  hole: PcbPlatedHole
21
14
  realToCanvasMat: Matrix
22
15
  colorMap: PcbColorMap
23
- }
24
-
25
- function getSoldermaskColor(
26
- layers: string[] | undefined,
27
- colorMap: PcbColorMap,
28
- ): string {
29
- const layer = layers?.includes("top") ? "top" : "bottom"
30
- return (
31
- colorMap.soldermaskOverCopper[
32
- layer as keyof typeof colorMap.soldermaskOverCopper
33
- ] ?? colorMap.soldermaskOverCopper.top
34
- )
16
+ soldermaskMargin?: number
17
+ showSoldermask?: boolean
35
18
  }
36
19
 
37
20
  export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
38
- const { ctx, hole, realToCanvasMat, colorMap } = params
21
+ const {
22
+ ctx,
23
+ hole,
24
+ realToCanvasMat,
25
+ colorMap,
26
+ soldermaskMargin = 0,
27
+ showSoldermask,
28
+ } = params
29
+
30
+ // Skip holes that are fully covered with soldermask when soldermask is enabled,
31
+ // as they should have been handled by soldermask processing.
32
+ // When soldermask is disabled, fully covered holes should be drawn as normal copper.
33
+ if (hole.is_covered_with_solder_mask === true && showSoldermask) {
34
+ return
35
+ }
39
36
 
40
- const isCoveredWithSoldermask = hole.is_covered_with_solder_mask === true
41
- const margin = isCoveredWithSoldermask ? 0 : (hole.soldermask_margin ?? 0)
42
- const hasSoldermask =
43
- !isCoveredWithSoldermask &&
44
- hole.soldermask_margin !== undefined &&
45
- hole.soldermask_margin !== 0
46
- const soldermaskRingColor = getSoldermaskColor(hole.layers, colorMap)
47
- const positiveMarginColor = colorMap.substrate
48
37
  const copperColor = colorMap.copper.top
49
38
 
50
- if (hole.shape === "circle") {
51
- // For positive margins, draw extended mask area first
52
- if (hasSoldermask && margin > 0) {
53
- drawCircle({
54
- ctx,
55
- center: { x: hole.x, y: hole.y },
56
- radius: hole.outer_diameter / 2 + margin,
57
- fill: positiveMarginColor,
58
- realToCanvasMat,
59
- })
60
- }
39
+ // For negative margins, draw smaller copper (inset by margin amount)
40
+ const copperInset = soldermaskMargin < 0 ? Math.abs(soldermaskMargin) : 0
61
41
 
62
- // Draw outer copper ring
42
+ if (hole.shape === "circle") {
43
+ // Draw outer copper ring (smaller if negative margin)
63
44
  drawCircle({
64
45
  ctx,
65
46
  center: { x: hole.x, y: hole.y },
66
- radius: hole.outer_diameter / 2,
47
+ radius: hole.outer_diameter / 2 - copperInset,
67
48
  fill: copperColor,
68
49
  realToCanvasMat,
69
50
  })
70
51
 
71
- // For negative margins, draw soldermask ring on top of the copper ring
72
- if (hasSoldermask && margin < 0) {
73
- drawSoldermaskRingForCircle(
74
- ctx,
75
- { x: hole.x, y: hole.y },
76
- hole.outer_diameter / 2,
77
- margin,
78
- realToCanvasMat,
79
- soldermaskRingColor,
80
- copperColor,
81
- )
82
- }
83
-
84
- // If fully covered, draw soldermask overlay
85
- if (isCoveredWithSoldermask) {
86
- drawCircle({
87
- ctx,
88
- center: { x: hole.x, y: hole.y },
89
- radius: hole.outer_diameter / 2,
90
- fill: soldermaskRingColor,
91
- realToCanvasMat,
92
- })
93
- }
94
-
95
- // Draw inner drill hole (only if not fully covered with soldermask)
96
- if (!isCoveredWithSoldermask) {
97
- drawCircle({
98
- ctx,
99
- center: { x: hole.x, y: hole.y },
100
- radius: hole.hole_diameter / 2,
101
- fill: colorMap.drill,
102
- realToCanvasMat,
103
- })
104
- }
52
+ // Draw inner drill hole
53
+ drawCircle({
54
+ ctx,
55
+ center: { x: hole.x, y: hole.y },
56
+ radius: hole.hole_diameter / 2,
57
+ fill: colorMap.drill,
58
+ realToCanvasMat,
59
+ })
105
60
  return
106
61
  }
107
62
 
108
63
  if (hole.shape === "oval") {
109
- // For positive margins, draw extended mask area first
110
- if (hasSoldermask && margin > 0) {
111
- drawOval({
112
- ctx,
113
- center: { x: hole.x, y: hole.y },
114
- radius_x: hole.outer_width / 2 + margin,
115
- radius_y: hole.outer_height / 2 + margin,
116
- fill: positiveMarginColor,
117
- realToCanvasMat,
118
- rotation: hole.ccw_rotation,
119
- })
120
- }
121
-
122
- // Draw outer copper oval
64
+ // Draw outer copper oval (smaller if negative margin)
123
65
  drawOval({
124
66
  ctx,
125
67
  center: { x: hole.x, y: hole.y },
126
- radius_x: hole.outer_width / 2,
127
- radius_y: hole.outer_height / 2,
68
+ radius_x: hole.outer_width / 2 - copperInset,
69
+ radius_y: hole.outer_height / 2 - copperInset,
128
70
  fill: copperColor,
129
71
  realToCanvasMat,
130
72
  rotation: hole.ccw_rotation,
131
73
  })
132
74
 
133
- // For negative margins, draw soldermask ring on top of the copper oval
134
- if (hasSoldermask && margin < 0) {
135
- drawSoldermaskRingForOval(
136
- ctx,
137
- { x: hole.x, y: hole.y },
138
- hole.outer_width / 2,
139
- hole.outer_height / 2,
140
- margin,
141
- hole.ccw_rotation ?? 0,
142
- realToCanvasMat,
143
- soldermaskRingColor,
144
- copperColor,
145
- )
146
- }
147
-
148
- // If fully covered, draw soldermask overlay
149
- if (isCoveredWithSoldermask) {
150
- drawOval({
151
- ctx,
152
- center: { x: hole.x, y: hole.y },
153
- radius_x: hole.outer_width / 2,
154
- radius_y: hole.outer_height / 2,
155
- fill: soldermaskRingColor,
156
- realToCanvasMat,
157
- rotation: hole.ccw_rotation,
158
- })
159
- }
160
-
161
- // Draw inner drill hole (only if not fully covered with soldermask)
162
- if (!isCoveredWithSoldermask) {
163
- drawOval({
164
- ctx,
165
- center: { x: hole.x, y: hole.y },
166
- radius_x: hole.hole_width / 2,
167
- radius_y: hole.hole_height / 2,
168
- fill: colorMap.drill,
169
- realToCanvasMat,
170
- rotation: hole.ccw_rotation,
171
- })
172
- }
75
+ // Draw inner drill hole
76
+ drawOval({
77
+ ctx,
78
+ center: { x: hole.x, y: hole.y },
79
+ radius_x: hole.hole_width / 2,
80
+ radius_y: hole.hole_height / 2,
81
+ fill: colorMap.drill,
82
+ realToCanvasMat,
83
+ rotation: hole.ccw_rotation,
84
+ })
173
85
  return
174
86
  }
175
87
 
176
88
  if (hole.shape === "pill") {
177
- // For positive margins, draw extended mask area first
178
- if (hasSoldermask && margin > 0) {
179
- drawPill({
180
- ctx,
181
- center: { x: hole.x, y: hole.y },
182
- width: hole.outer_width + margin * 2,
183
- height: hole.outer_height + margin * 2,
184
- fill: positiveMarginColor,
185
- realToCanvasMat,
186
- rotation: hole.ccw_rotation,
187
- })
188
- }
189
-
190
- // Draw outer copper pill
89
+ // Draw outer copper pill (smaller if negative margin)
191
90
  drawPill({
192
91
  ctx,
193
92
  center: { x: hole.x, y: hole.y },
194
- width: hole.outer_width,
195
- height: hole.outer_height,
93
+ width: hole.outer_width - copperInset * 2,
94
+ height: hole.outer_height - copperInset * 2,
196
95
  fill: copperColor,
197
96
  realToCanvasMat,
198
97
  rotation: hole.ccw_rotation,
199
98
  })
200
99
 
201
- // For negative margins, draw soldermask ring on top of the copper pill
202
- if (hasSoldermask && margin < 0) {
203
- drawSoldermaskRingForPill(
204
- ctx,
205
- { x: hole.x, y: hole.y },
206
- hole.outer_width,
207
- hole.outer_height,
208
- margin,
209
- hole.ccw_rotation ?? 0,
210
- realToCanvasMat,
211
- soldermaskRingColor,
212
- copperColor,
213
- )
214
- }
215
-
216
- // If fully covered, draw soldermask overlay
217
- if (isCoveredWithSoldermask) {
218
- drawPill({
219
- ctx,
220
- center: { x: hole.x, y: hole.y },
221
- width: hole.outer_width,
222
- height: hole.outer_height,
223
- fill: soldermaskRingColor,
224
- realToCanvasMat,
225
- rotation: hole.ccw_rotation,
226
- })
227
- }
228
-
229
- // Draw inner drill hole (only if not fully covered with soldermask)
230
- if (!isCoveredWithSoldermask) {
231
- drawPill({
232
- ctx,
233
- center: { x: hole.x, y: hole.y },
234
- width: hole.hole_width,
235
- height: hole.hole_height,
236
- fill: colorMap.drill,
237
- realToCanvasMat,
238
- rotation: hole.ccw_rotation,
239
- })
240
- }
100
+ // Draw inner drill hole
101
+ drawPill({
102
+ ctx,
103
+ center: { x: hole.x, y: hole.y },
104
+ width: hole.hole_width,
105
+ height: hole.hole_height,
106
+ fill: colorMap.drill,
107
+ realToCanvasMat,
108
+ rotation: hole.ccw_rotation,
109
+ })
241
110
  return
242
111
  }
243
112
 
244
113
  if (hole.shape === "circular_hole_with_rect_pad") {
245
- // For positive margins, draw extended mask area first
246
- if (hasSoldermask && margin > 0) {
247
- drawRect({
248
- ctx,
249
- center: { x: hole.x, y: hole.y },
250
- width: hole.rect_pad_width + margin * 2,
251
- height: hole.rect_pad_height + margin * 2,
252
- fill: positiveMarginColor,
253
- realToCanvasMat,
254
- borderRadius: hole.rect_border_radius ?? 0,
255
- })
256
- }
257
-
258
- // Draw rectangular pad
114
+ // Draw rectangular pad (smaller if negative margin)
259
115
  drawRect({
260
116
  ctx,
261
117
  center: { x: hole.x, y: hole.y },
262
- width: hole.rect_pad_width,
263
- height: hole.rect_pad_height,
118
+ width: hole.rect_pad_width - copperInset * 2,
119
+ height: hole.rect_pad_height - copperInset * 2,
264
120
  fill: copperColor,
265
121
  realToCanvasMat,
266
- borderRadius: hole.rect_border_radius ?? 0,
122
+ borderRadius: hole.rect_border_radius
123
+ ? Math.max(0, hole.rect_border_radius - copperInset)
124
+ : 0,
267
125
  })
268
126
 
269
- // For negative margins, draw soldermask ring on top of the pad
270
- if (hasSoldermask && margin < 0) {
271
- drawSoldermaskRingForRect(
272
- ctx,
273
- { x: hole.x, y: hole.y },
274
- hole.rect_pad_width,
275
- hole.rect_pad_height,
276
- margin,
277
- hole.rect_border_radius ?? 0,
278
- 0,
279
- realToCanvasMat,
280
- soldermaskRingColor,
281
- copperColor,
282
- )
283
- }
284
-
285
- // If fully covered, draw soldermask overlay
286
- if (isCoveredWithSoldermask) {
287
- drawRect({
288
- ctx,
289
- center: { x: hole.x, y: hole.y },
290
- width: hole.rect_pad_width,
291
- height: hole.rect_pad_height,
292
- fill: soldermaskRingColor,
293
- realToCanvasMat,
294
- borderRadius: hole.rect_border_radius ?? 0,
295
- })
296
- }
297
-
298
- // Draw circular drill hole (with offset, only if not fully covered with soldermask)
299
- if (!isCoveredWithSoldermask) {
300
- const holeX = hole.x + (hole.hole_offset_x ?? 0)
301
- const holeY = hole.y + (hole.hole_offset_y ?? 0)
302
- drawCircle({
303
- ctx,
304
- center: { x: holeX, y: holeY },
305
- radius: hole.hole_diameter / 2,
306
- fill: colorMap.drill,
307
- realToCanvasMat,
308
- })
309
- }
127
+ // Draw circular drill hole (with offset)
128
+ const holeX = hole.x + (hole.hole_offset_x ?? 0)
129
+ const holeY = hole.y + (hole.hole_offset_y ?? 0)
130
+ drawCircle({
131
+ ctx,
132
+ center: { x: holeX, y: holeY },
133
+ radius: hole.hole_diameter / 2,
134
+ fill: colorMap.drill,
135
+ realToCanvasMat,
136
+ })
310
137
  return
311
138
  }
312
139
 
313
140
  if (hole.shape === "pill_hole_with_rect_pad") {
314
- // For positive margins, draw extended mask area first
315
- if (hasSoldermask && margin > 0) {
316
- drawRect({
317
- ctx,
318
- center: { x: hole.x, y: hole.y },
319
- width: hole.rect_pad_width + margin * 2,
320
- height: hole.rect_pad_height + margin * 2,
321
- fill: positiveMarginColor,
322
- realToCanvasMat,
323
- borderRadius: hole.rect_border_radius ?? 0,
324
- })
325
- }
326
-
327
- // Draw rectangular pad
141
+ // Draw rectangular pad (smaller if negative margin)
328
142
  drawRect({
329
143
  ctx,
330
144
  center: { x: hole.x, y: hole.y },
331
- width: hole.rect_pad_width,
332
- height: hole.rect_pad_height,
145
+ width: hole.rect_pad_width - copperInset * 2,
146
+ height: hole.rect_pad_height - copperInset * 2,
333
147
  fill: copperColor,
334
148
  realToCanvasMat,
335
- borderRadius: hole.rect_border_radius ?? 0,
149
+ borderRadius: hole.rect_border_radius
150
+ ? Math.max(0, hole.rect_border_radius - copperInset)
151
+ : 0,
336
152
  })
337
153
 
338
- // For negative margins, draw soldermask ring on top of the pad
339
- if (hasSoldermask && margin < 0) {
340
- drawSoldermaskRingForRect(
341
- ctx,
342
- { x: hole.x, y: hole.y },
343
- hole.rect_pad_width,
344
- hole.rect_pad_height,
345
- margin,
346
- hole.rect_border_radius ?? 0,
347
- 0,
348
- realToCanvasMat,
349
- soldermaskRingColor,
350
- copperColor,
351
- )
352
- }
353
-
354
- // If fully covered, draw soldermask overlay
355
- if (isCoveredWithSoldermask) {
356
- drawRect({
357
- ctx,
358
- center: { x: hole.x, y: hole.y },
359
- width: hole.rect_pad_width,
360
- height: hole.rect_pad_height,
361
- fill: soldermaskRingColor,
362
- realToCanvasMat,
363
- borderRadius: hole.rect_border_radius ?? 0,
364
- })
365
- }
366
-
367
- // Draw pill drill hole (with offset, only if not fully covered with soldermask)
368
- if (!isCoveredWithSoldermask) {
369
- const holeX = hole.x + (hole.hole_offset_x ?? 0)
370
- const holeY = hole.y + (hole.hole_offset_y ?? 0)
371
- drawPill({
372
- ctx,
373
- center: { x: holeX, y: holeY },
374
- width: hole.hole_width,
375
- height: hole.hole_height,
376
- fill: colorMap.drill,
377
- realToCanvasMat,
378
- })
379
- }
154
+ // Draw pill drill hole (with offset)
155
+ const holeX = hole.x + (hole.hole_offset_x ?? 0)
156
+ const holeY = hole.y + (hole.hole_offset_y ?? 0)
157
+ drawPill({
158
+ ctx,
159
+ center: { x: holeX, y: holeY },
160
+ width: hole.hole_width,
161
+ height: hole.hole_height,
162
+ fill: colorMap.drill,
163
+ realToCanvasMat,
164
+ })
380
165
  return
381
166
  }
382
167
 
383
168
  if (hole.shape === "rotated_pill_hole_with_rect_pad") {
384
- // For positive margins, draw extended mask area first
385
- if (hasSoldermask && margin > 0) {
386
- drawRect({
387
- ctx,
388
- center: { x: hole.x, y: hole.y },
389
- width: hole.rect_pad_width + margin * 2,
390
- height: hole.rect_pad_height + margin * 2,
391
- fill: positiveMarginColor,
392
- realToCanvasMat,
393
- borderRadius: hole.rect_border_radius ?? 0,
394
- rotation: hole.rect_ccw_rotation,
395
- })
396
- }
397
-
398
- // Draw rotated rectangular pad
169
+ // Draw rotated rectangular pad (smaller if negative margin)
399
170
  drawRect({
400
171
  ctx,
401
172
  center: { x: hole.x, y: hole.y },
402
- width: hole.rect_pad_width,
403
- height: hole.rect_pad_height,
173
+ width: hole.rect_pad_width - copperInset * 2,
174
+ height: hole.rect_pad_height - copperInset * 2,
404
175
  fill: copperColor,
405
176
  realToCanvasMat,
406
- borderRadius: hole.rect_border_radius ?? 0,
177
+ borderRadius: hole.rect_border_radius
178
+ ? Math.max(0, hole.rect_border_radius - copperInset)
179
+ : 0,
407
180
  rotation: hole.rect_ccw_rotation,
408
181
  })
409
182
 
410
- // For negative margins, draw soldermask ring on top of the pad
411
- if (hasSoldermask && margin < 0) {
412
- drawSoldermaskRingForRect(
413
- ctx,
414
- { x: hole.x, y: hole.y },
415
- hole.rect_pad_width,
416
- hole.rect_pad_height,
417
- margin,
418
- hole.rect_border_radius ?? 0,
419
- hole.rect_ccw_rotation ?? 0,
420
- realToCanvasMat,
421
- soldermaskRingColor,
422
- copperColor,
423
- )
424
- }
425
-
426
- // If fully covered, draw soldermask overlay
427
- if (isCoveredWithSoldermask) {
428
- drawRect({
429
- ctx,
430
- center: { x: hole.x, y: hole.y },
431
- width: hole.rect_pad_width,
432
- height: hole.rect_pad_height,
433
- fill: soldermaskRingColor,
434
- realToCanvasMat,
435
- borderRadius: hole.rect_border_radius ?? 0,
436
- rotation: hole.rect_ccw_rotation,
437
- })
438
- }
439
-
440
- // Draw rotated pill drill hole (with offset, only if not fully covered with soldermask)
441
- if (!isCoveredWithSoldermask) {
442
- const holeX = hole.x + (hole.hole_offset_x ?? 0)
443
- const holeY = hole.y + (hole.hole_offset_y ?? 0)
444
- drawPill({
445
- ctx,
446
- center: { x: holeX, y: holeY },
447
- width: hole.hole_width,
448
- height: hole.hole_height,
449
- fill: colorMap.drill,
450
- realToCanvasMat,
451
- rotation: hole.hole_ccw_rotation,
452
- })
453
- }
183
+ // Draw rotated pill drill hole (with offset)
184
+ const holeX = hole.x + (hole.hole_offset_x ?? 0)
185
+ const holeY = hole.y + (hole.hole_offset_y ?? 0)
186
+ drawPill({
187
+ ctx,
188
+ center: { x: holeX, y: holeY },
189
+ width: hole.hole_width,
190
+ height: hole.hole_height,
191
+ fill: colorMap.drill,
192
+ realToCanvasMat,
193
+ rotation: hole.hole_ccw_rotation,
194
+ })
454
195
  return
455
196
  }
456
197
 
@@ -463,90 +204,61 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
463
204
  y: hole.y + point.y,
464
205
  }))
465
206
 
466
- // For positive margins, draw extended mask area first
467
- if (hasSoldermask && margin > 0) {
468
- const expandedPoints = offsetPolygonPoints(padPoints, margin)
207
+ // Draw polygon pad (smaller if negative margin)
208
+ const copperPoints =
209
+ copperInset > 0
210
+ ? offsetPolygonPoints(padPoints, -copperInset)
211
+ : padPoints
212
+ if (copperPoints.length >= 3) {
469
213
  drawPolygon({
470
214
  ctx,
471
- points: expandedPoints,
472
- fill: positiveMarginColor,
215
+ points: copperPoints,
216
+ fill: copperColor,
473
217
  realToCanvasMat,
474
218
  })
475
219
  }
220
+ }
221
+
222
+ // Draw drill hole (with offset)
223
+ const holeX = hole.x + (hole.hole_offset_x ?? 0)
224
+ const holeY = hole.y + (hole.hole_offset_y ?? 0)
225
+ const holeShape = hole.hole_shape
476
226
 
477
- // Draw polygon pad
478
- drawPolygon({
227
+ if (holeShape === "circle") {
228
+ drawCircle({
479
229
  ctx,
480
- points: padPoints,
481
- fill: copperColor,
230
+ center: { x: holeX, y: holeY },
231
+ radius: (hole.hole_diameter ?? 0) / 2,
232
+ fill: colorMap.drill,
233
+ realToCanvasMat,
234
+ })
235
+ } else if (holeShape === "oval") {
236
+ drawOval({
237
+ ctx,
238
+ center: { x: holeX, y: holeY },
239
+ radius_x: (hole.hole_width ?? 0) / 2,
240
+ radius_y: (hole.hole_height ?? 0) / 2,
241
+ fill: colorMap.drill,
242
+ realToCanvasMat,
243
+ })
244
+ } else if (holeShape === "pill") {
245
+ drawPill({
246
+ ctx,
247
+ center: { x: holeX, y: holeY },
248
+ width: hole.hole_width ?? 0,
249
+ height: hole.hole_height ?? 0,
250
+ fill: colorMap.drill,
251
+ realToCanvasMat,
252
+ })
253
+ } else if (holeShape === "rotated_pill") {
254
+ drawPill({
255
+ ctx,
256
+ center: { x: holeX, y: holeY },
257
+ width: hole.hole_width ?? 0,
258
+ height: hole.hole_height ?? 0,
259
+ fill: colorMap.drill,
482
260
  realToCanvasMat,
483
261
  })
484
-
485
- // For negative margins, draw soldermask ring on top of the pad
486
- if (hasSoldermask && margin < 0) {
487
- drawSoldermaskRingForPolygon(
488
- ctx,
489
- padPoints,
490
- margin,
491
- realToCanvasMat,
492
- soldermaskRingColor,
493
- copperColor,
494
- )
495
- }
496
-
497
- // If fully covered, draw soldermask overlay
498
- if (isCoveredWithSoldermask) {
499
- drawPolygon({
500
- ctx,
501
- points: padPoints,
502
- fill: soldermaskRingColor,
503
- realToCanvasMat,
504
- })
505
- }
506
- }
507
-
508
- // Draw drill hole (with offset, only if not fully covered with soldermask)
509
- if (!isCoveredWithSoldermask) {
510
- const holeX = hole.x + (hole.hole_offset_x ?? 0)
511
- const holeY = hole.y + (hole.hole_offset_y ?? 0)
512
- const holeShape = hole.hole_shape
513
-
514
- if (holeShape === "circle") {
515
- drawCircle({
516
- ctx,
517
- center: { x: holeX, y: holeY },
518
- radius: (hole.hole_diameter ?? 0) / 2,
519
- fill: colorMap.drill,
520
- realToCanvasMat,
521
- })
522
- } else if (holeShape === "oval") {
523
- drawOval({
524
- ctx,
525
- center: { x: holeX, y: holeY },
526
- radius_x: (hole.hole_width ?? 0) / 2,
527
- radius_y: (hole.hole_height ?? 0) / 2,
528
- fill: colorMap.drill,
529
- realToCanvasMat,
530
- })
531
- } else if (holeShape === "pill") {
532
- drawPill({
533
- ctx,
534
- center: { x: holeX, y: holeY },
535
- width: hole.hole_width ?? 0,
536
- height: hole.hole_height ?? 0,
537
- fill: colorMap.drill,
538
- realToCanvasMat,
539
- })
540
- } else if (holeShape === "rotated_pill") {
541
- drawPill({
542
- ctx,
543
- center: { x: holeX, y: holeY },
544
- width: hole.hole_width ?? 0,
545
- height: hole.hole_height ?? 0,
546
- fill: colorMap.drill,
547
- realToCanvasMat,
548
- })
549
- }
550
262
  }
551
263
  return
552
264
  }