circuit-to-canvas 0.0.50 → 0.0.52

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 (53) hide show
  1. package/dist/index.d.ts +10 -4
  2. package/dist/index.js +2911 -2719
  3. package/lib/drawer/CircuitToCanvasDrawer.ts +317 -366
  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 +28 -13
  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 +3 -292
  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/package.json +1 -1
  20. package/tests/board-snapshot/__snapshots__/usb-c-flashlight-board.snap.png +0 -0
  21. package/tests/board-snapshot/usb-c-flashlight-board.test.ts +1 -0
  22. package/tests/elements/__snapshots__/board-with-elements.snap.png +0 -0
  23. package/tests/elements/__snapshots__/brep-copper-pours.snap.png +0 -0
  24. package/tests/elements/__snapshots__/custom-outline-board.snap.png +0 -0
  25. package/tests/elements/__snapshots__/oval-plated-hole.snap.png +0 -0
  26. package/tests/elements/__snapshots__/pcb-board.snap.png +0 -0
  27. package/tests/elements/__snapshots__/pcb-comprehensive-soldermask-margin.snap.png +0 -0
  28. package/tests/elements/__snapshots__/pcb-fabrication-note-dimension.snap.png +0 -0
  29. package/tests/elements/__snapshots__/pcb-hole-soldermask-margin.snap.png +0 -0
  30. package/tests/elements/__snapshots__/pcb-keepout-layer-filter.snap.png +0 -0
  31. package/tests/elements/__snapshots__/pcb-keepout-multiple-layers.snap.png +0 -0
  32. package/tests/elements/__snapshots__/pcb-keepout-rect-and-circle.snap.png +0 -0
  33. package/tests/elements/__snapshots__/pcb-keepout-with-group-id.snap.png +0 -0
  34. package/tests/elements/__snapshots__/pcb-no-soldermask.snap.png +0 -0
  35. package/tests/elements/__snapshots__/pcb-plated-hole-soldermask-margin.snap.png +0 -0
  36. package/tests/elements/__snapshots__/pcb-plated-hole.snap.png +0 -0
  37. package/tests/elements/__snapshots__/pcb-silkscreen-on-component.snap.png +0 -0
  38. package/tests/elements/__snapshots__/pcb-silkscreen-oval.snap.png +0 -0
  39. package/tests/elements/__snapshots__/pcb-smtpad-asymmetric-soldermask-margin.snap.png +0 -0
  40. package/tests/elements/__snapshots__/pcb-smtpad-soldermask-coverage.snap.png +0 -0
  41. package/tests/elements/__snapshots__/pcb-smtpad-soldermask-margin.snap.png +0 -0
  42. package/tests/elements/__snapshots__/pill-plated-hole.snap.png +0 -0
  43. package/tests/elements/pcb-board.test.ts +2 -2
  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-keepout-with-group-id.test.ts +1 -1
  47. package/tests/elements/pcb-no-soldermask.test.ts +1281 -0
  48. package/tests/elements/pcb-plated-hole-soldermask-margin.test.ts +1 -1
  49. package/tests/elements/pcb-plated-hole.test.ts +40 -4
  50. package/tests/elements/pcb-smtpad-asymmetric-soldermask-margin.test.ts +1 -1
  51. package/tests/elements/pcb-smtpad-soldermask-coverage.test.ts +1 -1
  52. package/tests/elements/pcb-smtpad-soldermask-margin.test.ts +1 -1
  53. package/tests/fixtures/getStackedPngSvgComparison.ts +10 -4
@@ -1,80 +1,83 @@
1
1
  import type {
2
2
  AnyCircuitElement,
3
- PcbPlatedHole,
4
- PcbVia,
5
- PcbHole,
6
- PcbSmtPad,
7
- PcbTrace,
8
- PcbBoard,
9
- PcbSilkscreenText,
10
- PcbSilkscreenRect,
11
- PcbSilkscreenCircle,
12
- PcbSilkscreenLine,
13
- PcbSilkscreenOval,
14
- PcbSilkscreenPath,
15
- PcbSilkscreenPill,
16
- PcbCutout,
17
3
  PCBKeepout,
4
+ PcbBoard,
18
5
  PcbCopperPour,
19
6
  PcbCopperText,
20
- PcbFabricationNoteText,
21
- PcbFabricationNoteRect,
22
- PcbNoteRect,
7
+ PcbCutout,
8
+ PcbFabricationNoteDimension,
23
9
  PcbFabricationNotePath,
24
- PcbNotePath,
25
- PcbNoteText,
10
+ PcbFabricationNoteRect,
11
+ PcbFabricationNoteText,
12
+ PcbHole,
26
13
  PcbNoteDimension,
27
- PcbFabricationNoteDimension,
28
14
  PcbNoteLine,
15
+ PcbNotePath,
16
+ PcbNoteRect,
17
+ PcbNoteText,
18
+ PcbPlatedHole,
29
19
  PcbRenderLayer,
20
+ PcbSilkscreenCircle,
21
+ PcbSilkscreenLine,
22
+ PcbSilkscreenOval,
23
+ PcbSilkscreenPath,
24
+ PcbSilkscreenPill,
25
+ PcbSilkscreenRect,
26
+ PcbSilkscreenText,
27
+ PcbSmtPad,
28
+ PcbTrace,
29
+ PcbVia,
30
30
  } from "circuit-json"
31
+ import type { Matrix } from "transformation-matrix"
31
32
  import {
32
- identity,
33
+ applyToPoint,
33
34
  compose,
34
- translate,
35
+ identity,
35
36
  scale,
36
- applyToPoint,
37
+ translate,
37
38
  } from "transformation-matrix"
38
- import { shouldDrawElement } from "./pcb-render-layer-filter"
39
- import type { Matrix } from "transformation-matrix"
40
- import {
41
- type CanvasContext,
42
- type PcbColorMap,
43
- type DrawerConfig,
44
- type CameraBounds,
45
- DEFAULT_PCB_COLOR_MAP,
46
- } from "./types"
47
- import { drawPcbPlatedHole } from "./elements/pcb-plated-hole"
48
- import { drawPcbVia } from "./elements/pcb-via"
49
- import { drawPcbHole } from "./elements/pcb-hole"
50
- import { drawPcbSmtPad } from "./elements/pcb-smtpad"
51
- import { drawPcbTrace } from "./elements/pcb-trace"
52
39
  import { drawPcbBoard } from "./elements/pcb-board"
53
- import { drawPath } from "./shapes/path"
54
- import { drawRect } from "./shapes/rect"
55
- import { drawPcbSilkscreenText } from "./elements/pcb-silkscreen-text"
56
- import { drawPcbSilkscreenRect } from "./elements/pcb-silkscreen-rect"
57
- import { drawPcbSilkscreenCircle } from "./elements/pcb-silkscreen-circle"
58
- import { drawPcbSilkscreenLine } from "./elements/pcb-silkscreen-line"
59
- import { drawPcbSilkscreenPath } from "./elements/pcb-silkscreen-path"
60
- import { drawPcbSilkscreenOval } from "./elements/pcb-silkscreen-oval"
61
- import { drawPcbSilkscreenPill } from "./elements/pcb-silkscreen-pill"
62
- import { drawPcbCutout } from "./elements/pcb-cutout"
63
- import { drawPcbKeepout } from "./elements/pcb-keepout"
64
40
  import { drawPcbCopperPour } from "./elements/pcb-copper-pour"
65
41
  import { drawPcbCopperText } from "./elements/pcb-copper-text"
66
- import { drawPcbFabricationNoteText } from "./elements/pcb-fabrication-note-text"
67
- import { drawPcbFabricationNoteRect } from "./elements/pcb-fabrication-note-rect"
68
- import { drawPcbNoteRect } from "./elements/pcb-note-rect"
42
+ import { drawPcbCutout } from "./elements/pcb-cutout"
43
+ import { drawPcbFabricationNoteDimension } from "./elements/pcb-fabrication-note-dimension"
69
44
  import { drawPcbFabricationNotePath } from "./elements/pcb-fabrication-note-path"
70
- import { drawPcbNotePath } from "./elements/pcb-note-path"
71
- import { drawPcbNoteText } from "./elements/pcb-note-text"
45
+ import { drawPcbFabricationNoteRect } from "./elements/pcb-fabrication-note-rect"
46
+ import { drawPcbFabricationNoteText } from "./elements/pcb-fabrication-note-text"
47
+ import { drawPcbHole } from "./elements/pcb-hole"
48
+ import { drawPcbKeepout } from "./elements/pcb-keepout"
72
49
  import { drawPcbNoteDimension } from "./elements/pcb-note-dimension"
73
- import { drawPcbFabricationNoteDimension } from "./elements/pcb-fabrication-note-dimension"
74
50
  import { drawPcbNoteLine } from "./elements/pcb-note-line"
51
+ import { drawPcbNotePath } from "./elements/pcb-note-path"
52
+ import { drawPcbNoteRect } from "./elements/pcb-note-rect"
53
+ import { drawPcbNoteText } from "./elements/pcb-note-text"
54
+ import { drawPcbPlatedHole } from "./elements/pcb-plated-hole"
55
+ import { drawPcbSilkscreenCircle } from "./elements/pcb-silkscreen-circle"
56
+ import { drawPcbSilkscreenLine } from "./elements/pcb-silkscreen-line"
57
+ import { drawPcbSilkscreenOval } from "./elements/pcb-silkscreen-oval"
58
+ import { drawPcbSilkscreenPath } from "./elements/pcb-silkscreen-path"
59
+ import { drawPcbSilkscreenPill } from "./elements/pcb-silkscreen-pill"
60
+ import { drawPcbSilkscreenRect } from "./elements/pcb-silkscreen-rect"
61
+ import { drawPcbSilkscreenText } from "./elements/pcb-silkscreen-text"
62
+ import { drawPcbSmtPad } from "./elements/pcb-smtpad"
63
+ import { drawPcbSoldermask } from "./elements/pcb-soldermask"
64
+ import { drawPcbTrace } from "./elements/pcb-trace"
65
+ import { drawPcbVia } from "./elements/pcb-via"
66
+ import { shouldDrawElement } from "./pcb-render-layer-filter"
67
+ import {
68
+ type CameraBounds,
69
+ type CanvasContext,
70
+ DEFAULT_PCB_COLOR_MAP,
71
+ type DrawerConfig,
72
+ type PcbColorMap,
73
+ } from "./types"
75
74
 
76
75
  export interface DrawElementsOptions {
77
76
  layers?: PcbRenderLayer[]
77
+ /** Whether to render the soldermask layer. Defaults to false. */
78
+ drawSoldermask?: boolean
79
+ /** Whether to render the board material (substrate fill). Defaults to false. */
80
+ drawBoardMaterial?: boolean
78
81
  }
79
82
 
80
83
  interface CanvasLike {
@@ -162,358 +165,306 @@ export class CircuitToCanvasDrawer {
162
165
  elements: AnyCircuitElement[],
163
166
  options: DrawElementsOptions = {},
164
167
  ): void {
165
- // Check if any pad or hole has is_covered_with_solder_mask: true
166
- const hasSoldermaskPads = elements.some(
167
- (el) =>
168
- el.type === "pcb_smtpad" &&
169
- (el as PcbSmtPad).is_covered_with_solder_mask === true,
170
- )
171
- const hasSoldermaskHoles = elements.some(
172
- (el) =>
173
- el.type === "pcb_hole" &&
174
- (el as PcbHole & { is_covered_with_solder_mask?: boolean })
175
- .is_covered_with_solder_mask === true,
176
- )
177
- const hasSoldermaskPlatedHoles = elements.some(
178
- (el) =>
179
- el.type === "pcb_plated_hole" &&
180
- (el as PcbPlatedHole & { is_covered_with_solder_mask?: boolean })
181
- .is_covered_with_solder_mask === true,
182
- )
183
-
184
- for (const element of elements) {
185
- if (element.type === "pcb_board") {
186
- this.drawBoardWithSoldermask(element as PcbBoard)
187
- } else {
188
- this.drawElement(element, options)
189
- }
190
- }
191
- }
192
-
193
- private drawBoardWithSoldermask(board: PcbBoard): void {
194
- const { width, height, center, outline } = board
195
- const layer = "top" // Default to top layer for soldermask color
196
-
197
- // If the board has a custom outline, draw it as a path with soldermask fill
198
- if (outline && Array.isArray(outline) && outline.length >= 3) {
199
- const soldermaskColor =
200
- this.colorMap.soldermask[
201
- layer as keyof typeof this.colorMap.soldermask
202
- ] ?? this.colorMap.soldermask.top
203
-
204
- // Draw filled path
205
- const canvasPoints = outline.map((p) => {
206
- const [x, y] = applyToPoint(this.realToCanvasMat, [p.x, p.y])
207
- return { x, y }
208
- })
209
-
210
- this.ctx.beginPath()
211
- const firstPoint = canvasPoints[0]
212
- if (firstPoint) {
213
- this.ctx.moveTo(firstPoint.x, firstPoint.y)
214
- for (let i = 1; i < canvasPoints.length; i++) {
215
- const point = canvasPoints[i]
216
- if (point) {
217
- this.ctx.lineTo(point.x, point.y)
218
- }
219
- }
220
- this.ctx.closePath()
221
- }
222
-
223
- this.ctx.fillStyle = soldermaskColor
224
- this.ctx.fill()
225
-
226
- // Draw outline stroke
227
- drawPath({
228
- ctx: this.ctx,
229
- points: outline.map((p) => ({ x: p.x, y: p.y })),
230
- stroke: this.colorMap.boardOutline,
231
- strokeWidth: 0.1,
232
- realToCanvasMat: this.realToCanvasMat,
233
- closePath: true,
234
- })
235
- return
236
- }
237
-
238
- // Otherwise draw a rectangle with soldermask fill
239
- if (width !== undefined && height !== undefined && center) {
240
- const soldermaskColor =
241
- this.colorMap.soldermask[
242
- layer as keyof typeof this.colorMap.soldermask
243
- ] ?? this.colorMap.soldermask.top
244
-
245
- // Draw filled rectangle
246
- drawRect({
247
- ctx: this.ctx,
248
- center,
249
- width,
250
- height,
251
- fill: soldermaskColor,
252
- realToCanvasMat: this.realToCanvasMat,
253
- })
254
-
255
- // Draw the outline stroke separately using path
256
- const halfWidth = width / 2
257
- const halfHeight = height / 2
258
- const corners = [
259
- { x: center.x - halfWidth, y: center.y - halfHeight },
260
- { x: center.x + halfWidth, y: center.y - halfHeight },
261
- { x: center.x + halfWidth, y: center.y + halfHeight },
262
- { x: center.x - halfWidth, y: center.y + halfHeight },
263
- ]
264
-
265
- drawPath({
266
- ctx: this.ctx,
267
- points: corners,
268
- stroke: this.colorMap.boardOutline,
269
- strokeWidth: 0.1,
270
- realToCanvasMat: this.realToCanvasMat,
271
- closePath: true,
272
- })
273
- }
274
- }
275
-
276
- private drawElement(
277
- element: AnyCircuitElement,
278
- options: DrawElementsOptions,
279
- ): void {
280
- // Check if element should be drawn based on layer options
281
- if (!shouldDrawElement(element, options)) {
282
- return
283
- }
284
-
285
- if (element.type === "pcb_plated_hole") {
286
- drawPcbPlatedHole({
168
+ // Find the board element
169
+ const board = elements.find((el) => el.type === "pcb_board") as
170
+ | PcbBoard
171
+ | undefined
172
+
173
+ // Drawing order:
174
+ // 1. Board outline (just the outline stroke, no fill)
175
+ // 2. Copper elements underneath soldermask (pads, copper text)
176
+ // 3. Soldermask (covers everything except openings)
177
+ // 4. Silkscreen (on soldermask, under top copper layers)
178
+ // 5. Copper pour and traces (drawn on top of soldermask and silkscreen)
179
+ // 6. Plated holes, vias (copper ring + drill hole on top of soldermask)
180
+ // 7. Holes and cutouts (punch through everything)
181
+ // 8. Other annotations
182
+
183
+ // Step 1: Draw board outline
184
+ if (board) {
185
+ drawPcbBoard({
287
186
  ctx: this.ctx,
288
- hole: element as PcbPlatedHole,
187
+ board,
289
188
  realToCanvasMat: this.realToCanvasMat,
290
189
  colorMap: this.colorMap,
190
+ drawBoardMaterial: options.drawBoardMaterial ?? false,
291
191
  })
292
192
  }
293
193
 
294
- if (element.type === "pcb_via") {
295
- drawPcbVia({
296
- ctx: this.ctx,
297
- via: element as PcbVia,
298
- realToCanvasMat: this.realToCanvasMat,
299
- colorMap: this.colorMap,
300
- })
301
- }
194
+ // Step 2: Draw copper elements underneath soldermask (pads, copper text)
195
+ for (const element of elements) {
196
+ if (!shouldDrawElement(element, options)) continue
197
+
198
+ if (element.type === "pcb_smtpad") {
199
+ drawPcbSmtPad({
200
+ ctx: this.ctx,
201
+ pad: element as PcbSmtPad,
202
+ realToCanvasMat: this.realToCanvasMat,
203
+ colorMap: this.colorMap,
204
+ })
205
+ }
302
206
 
303
- if (element.type === "pcb_hole") {
304
- drawPcbHole({
305
- ctx: this.ctx,
306
- hole: element as PcbHole,
307
- realToCanvasMat: this.realToCanvasMat,
308
- colorMap: this.colorMap,
309
- })
207
+ if (element.type === "pcb_copper_text") {
208
+ drawPcbCopperText({
209
+ ctx: this.ctx,
210
+ text: element as PcbCopperText,
211
+ realToCanvasMat: this.realToCanvasMat,
212
+ colorMap: this.colorMap,
213
+ })
214
+ }
310
215
  }
311
216
 
312
- if (element.type === "pcb_smtpad") {
313
- drawPcbSmtPad({
217
+ // Step 3: Draw soldermask layer (only if showSoldermask is true)
218
+ const drawSoldermask = options.drawSoldermask ?? false
219
+ if (board) {
220
+ drawPcbSoldermask({
314
221
  ctx: this.ctx,
315
- pad: element as PcbSmtPad,
222
+ board,
223
+ elements,
316
224
  realToCanvasMat: this.realToCanvasMat,
317
225
  colorMap: this.colorMap,
226
+ layer: "top",
227
+ drawSoldermask,
318
228
  })
319
229
  }
320
230
 
321
- if (element.type === "pcb_trace") {
322
- drawPcbTrace({
323
- ctx: this.ctx,
324
- trace: element as PcbTrace,
325
- realToCanvasMat: this.realToCanvasMat,
326
- colorMap: this.colorMap,
327
- })
328
- }
231
+ // Step 4: Draw silkscreen (on soldermask, under top copper layers)
232
+ for (const element of elements) {
233
+ if (!shouldDrawElement(element, options)) continue
234
+
235
+ if (element.type === "pcb_silkscreen_text") {
236
+ drawPcbSilkscreenText({
237
+ ctx: this.ctx,
238
+ text: element as PcbSilkscreenText,
239
+ realToCanvasMat: this.realToCanvasMat,
240
+ colorMap: this.colorMap,
241
+ })
242
+ }
329
243
 
330
- if (element.type === "pcb_board") {
331
- drawPcbBoard({
332
- ctx: this.ctx,
333
- board: element as PcbBoard,
334
- realToCanvasMat: this.realToCanvasMat,
335
- colorMap: this.colorMap,
336
- })
337
- }
244
+ if (element.type === "pcb_silkscreen_rect") {
245
+ drawPcbSilkscreenRect({
246
+ ctx: this.ctx,
247
+ rect: element as PcbSilkscreenRect,
248
+ realToCanvasMat: this.realToCanvasMat,
249
+ colorMap: this.colorMap,
250
+ })
251
+ }
338
252
 
339
- if (element.type === "pcb_silkscreen_text") {
340
- drawPcbSilkscreenText({
341
- ctx: this.ctx,
342
- text: element as PcbSilkscreenText,
343
- realToCanvasMat: this.realToCanvasMat,
344
- colorMap: this.colorMap,
345
- })
346
- }
253
+ if (element.type === "pcb_silkscreen_circle") {
254
+ drawPcbSilkscreenCircle({
255
+ ctx: this.ctx,
256
+ circle: element as PcbSilkscreenCircle,
257
+ realToCanvasMat: this.realToCanvasMat,
258
+ colorMap: this.colorMap,
259
+ })
260
+ }
347
261
 
348
- if (element.type === "pcb_silkscreen_rect") {
349
- drawPcbSilkscreenRect({
350
- ctx: this.ctx,
351
- rect: element as PcbSilkscreenRect,
352
- realToCanvasMat: this.realToCanvasMat,
353
- colorMap: this.colorMap,
354
- })
355
- }
262
+ if (element.type === "pcb_silkscreen_line") {
263
+ drawPcbSilkscreenLine({
264
+ ctx: this.ctx,
265
+ line: element as PcbSilkscreenLine,
266
+ realToCanvasMat: this.realToCanvasMat,
267
+ colorMap: this.colorMap,
268
+ })
269
+ }
356
270
 
357
- if (element.type === "pcb_silkscreen_circle") {
358
- drawPcbSilkscreenCircle({
359
- ctx: this.ctx,
360
- circle: element as PcbSilkscreenCircle,
361
- realToCanvasMat: this.realToCanvasMat,
362
- colorMap: this.colorMap,
363
- })
364
- }
271
+ if (element.type === "pcb_silkscreen_path") {
272
+ drawPcbSilkscreenPath({
273
+ ctx: this.ctx,
274
+ path: element as PcbSilkscreenPath,
275
+ realToCanvasMat: this.realToCanvasMat,
276
+ colorMap: this.colorMap,
277
+ })
278
+ }
365
279
 
366
- if (element.type === "pcb_silkscreen_line") {
367
- drawPcbSilkscreenLine({
368
- ctx: this.ctx,
369
- line: element as PcbSilkscreenLine,
370
- realToCanvasMat: this.realToCanvasMat,
371
- colorMap: this.colorMap,
372
- })
373
- }
280
+ if (element.type === "pcb_silkscreen_pill") {
281
+ drawPcbSilkscreenPill({
282
+ ctx: this.ctx,
283
+ pill: element as PcbSilkscreenPill,
284
+ realToCanvasMat: this.realToCanvasMat,
285
+ colorMap: this.colorMap,
286
+ })
287
+ }
374
288
 
375
- if (element.type === "pcb_silkscreen_path") {
376
- drawPcbSilkscreenPath({
377
- ctx: this.ctx,
378
- path: element as PcbSilkscreenPath,
379
- realToCanvasMat: this.realToCanvasMat,
380
- colorMap: this.colorMap,
381
- })
289
+ if (element.type === "pcb_silkscreen_oval") {
290
+ drawPcbSilkscreenOval({
291
+ ctx: this.ctx,
292
+ oval: element as PcbSilkscreenOval,
293
+ realToCanvasMat: this.realToCanvasMat,
294
+ colorMap: this.colorMap,
295
+ })
296
+ }
382
297
  }
383
298
 
384
- if (element.type === "pcb_silkscreen_pill") {
385
- drawPcbSilkscreenPill({
386
- ctx: this.ctx,
387
- pill: element as PcbSilkscreenPill,
388
- realToCanvasMat: this.realToCanvasMat,
389
- colorMap: this.colorMap,
390
- })
391
- }
299
+ // Step 5: Draw copper pour and traces (on top of soldermask and silkscreen)
300
+ for (const element of elements) {
301
+ if (!shouldDrawElement(element, options)) continue
302
+
303
+ if (element.type === "pcb_copper_pour") {
304
+ drawPcbCopperPour({
305
+ ctx: this.ctx,
306
+ pour: element as PcbCopperPour,
307
+ realToCanvasMat: this.realToCanvasMat,
308
+ colorMap: this.colorMap,
309
+ })
310
+ }
392
311
 
393
- if (element.type === "pcb_silkscreen_oval") {
394
- drawPcbSilkscreenOval({
395
- ctx: this.ctx,
396
- oval: element as PcbSilkscreenOval,
397
- realToCanvasMat: this.realToCanvasMat,
398
- colorMap: this.colorMap,
399
- })
312
+ if (element.type === "pcb_trace") {
313
+ drawPcbTrace({
314
+ ctx: this.ctx,
315
+ trace: element as PcbTrace,
316
+ realToCanvasMat: this.realToCanvasMat,
317
+ colorMap: this.colorMap,
318
+ })
319
+ }
400
320
  }
401
321
 
402
- if (element.type === "pcb_cutout") {
403
- drawPcbCutout({
404
- ctx: this.ctx,
405
- cutout: element as PcbCutout,
406
- realToCanvasMat: this.realToCanvasMat,
407
- colorMap: this.colorMap,
408
- })
409
- }
322
+ // Step 6: Draw plated holes, vias (copper ring + drill hole on top of soldermask)
323
+ for (const element of elements) {
324
+ if (!shouldDrawElement(element, options)) continue
325
+
326
+ if (element.type === "pcb_plated_hole") {
327
+ drawPcbPlatedHole({
328
+ ctx: this.ctx,
329
+ hole: element as PcbPlatedHole,
330
+ realToCanvasMat: this.realToCanvasMat,
331
+ colorMap: this.colorMap,
332
+ soldermaskMargin: drawSoldermask
333
+ ? (element as PcbPlatedHole).soldermask_margin
334
+ : undefined,
335
+ drawSoldermask,
336
+ })
337
+ }
410
338
 
411
- if (element.type === "pcb_keepout") {
412
- drawPcbKeepout({
413
- ctx: this.ctx,
414
- keepout: element as PCBKeepout,
415
- realToCanvasMat: this.realToCanvasMat,
416
- colorMap: this.colorMap,
417
- })
339
+ if (element.type === "pcb_via") {
340
+ drawPcbVia({
341
+ ctx: this.ctx,
342
+ via: element as PcbVia,
343
+ realToCanvasMat: this.realToCanvasMat,
344
+ colorMap: this.colorMap,
345
+ })
346
+ }
418
347
  }
419
348
 
420
- if (element.type === "pcb_copper_pour") {
421
- drawPcbCopperPour({
422
- ctx: this.ctx,
423
- pour: element as PcbCopperPour,
424
- realToCanvasMat: this.realToCanvasMat,
425
- colorMap: this.colorMap,
426
- })
427
- }
349
+ // Step 7: Draw holes and cutouts (these punch through everything)
350
+ for (const element of elements) {
351
+ if (!shouldDrawElement(element, options)) continue
352
+
353
+ if (element.type === "pcb_hole") {
354
+ drawPcbHole({
355
+ ctx: this.ctx,
356
+ hole: element as PcbHole,
357
+ realToCanvasMat: this.realToCanvasMat,
358
+ colorMap: this.colorMap,
359
+ soldermaskMargin: drawSoldermask
360
+ ? element.soldermask_margin
361
+ : undefined,
362
+ })
363
+ }
428
364
 
429
- if (element.type === "pcb_copper_text") {
430
- drawPcbCopperText({
431
- ctx: this.ctx,
432
- text: element as PcbCopperText,
433
- realToCanvasMat: this.realToCanvasMat,
434
- colorMap: this.colorMap,
435
- })
365
+ if (element.type === "pcb_cutout") {
366
+ drawPcbCutout({
367
+ ctx: this.ctx,
368
+ cutout: element as PcbCutout,
369
+ realToCanvasMat: this.realToCanvasMat,
370
+ colorMap: this.colorMap,
371
+ })
372
+ }
436
373
  }
437
374
 
438
- if (element.type === "pcb_fabrication_note_text") {
439
- drawPcbFabricationNoteText({
440
- ctx: this.ctx,
441
- text: element as PcbFabricationNoteText,
442
- realToCanvasMat: this.realToCanvasMat,
443
- colorMap: this.colorMap,
444
- })
445
- }
375
+ // Step 8: Draw other annotations
376
+ for (const element of elements) {
377
+ if (!shouldDrawElement(element, options)) continue
378
+
379
+ if (element.type === "pcb_keepout") {
380
+ drawPcbKeepout({
381
+ ctx: this.ctx,
382
+ keepout: element as PCBKeepout,
383
+ realToCanvasMat: this.realToCanvasMat,
384
+ colorMap: this.colorMap,
385
+ })
386
+ }
446
387
 
447
- if (element.type === "pcb_fabrication_note_rect") {
448
- drawPcbFabricationNoteRect({
449
- ctx: this.ctx,
450
- rect: element as PcbFabricationNoteRect,
451
- realToCanvasMat: this.realToCanvasMat,
452
- colorMap: this.colorMap,
453
- })
454
- }
388
+ if (element.type === "pcb_fabrication_note_text") {
389
+ drawPcbFabricationNoteText({
390
+ ctx: this.ctx,
391
+ text: element as PcbFabricationNoteText,
392
+ realToCanvasMat: this.realToCanvasMat,
393
+ colorMap: this.colorMap,
394
+ })
395
+ }
455
396
 
456
- if (element.type === "pcb_note_rect") {
457
- drawPcbNoteRect({
458
- realToCanvasMat: this.realToCanvasMat,
459
- colorMap: this.colorMap,
460
- ctx: this.ctx,
461
- rect: element as PcbNoteRect,
462
- })
463
- }
397
+ if (element.type === "pcb_fabrication_note_rect") {
398
+ drawPcbFabricationNoteRect({
399
+ ctx: this.ctx,
400
+ rect: element as PcbFabricationNoteRect,
401
+ realToCanvasMat: this.realToCanvasMat,
402
+ colorMap: this.colorMap,
403
+ })
404
+ }
464
405
 
465
- if (element.type === "pcb_fabrication_note_path") {
466
- drawPcbFabricationNotePath({
467
- ctx: this.ctx,
468
- path: element as PcbFabricationNotePath,
469
- realToCanvasMat: this.realToCanvasMat,
470
- colorMap: this.colorMap,
471
- })
472
- }
406
+ if (element.type === "pcb_note_rect") {
407
+ drawPcbNoteRect({
408
+ realToCanvasMat: this.realToCanvasMat,
409
+ colorMap: this.colorMap,
410
+ ctx: this.ctx,
411
+ rect: element as PcbNoteRect,
412
+ })
413
+ }
473
414
 
474
- if (element.type === "pcb_note_path") {
475
- drawPcbNotePath({
476
- ctx: this.ctx,
477
- path: element as PcbNotePath,
478
- realToCanvasMat: this.realToCanvasMat,
479
- colorMap: this.colorMap,
480
- })
481
- }
415
+ if (element.type === "pcb_fabrication_note_path") {
416
+ drawPcbFabricationNotePath({
417
+ ctx: this.ctx,
418
+ path: element as PcbFabricationNotePath,
419
+ realToCanvasMat: this.realToCanvasMat,
420
+ colorMap: this.colorMap,
421
+ })
422
+ }
482
423
 
483
- if (element.type === "pcb_note_text") {
484
- drawPcbNoteText({
485
- ctx: this.ctx,
486
- text: element as PcbNoteText,
487
- realToCanvasMat: this.realToCanvasMat,
488
- colorMap: this.colorMap,
489
- })
490
- }
424
+ if (element.type === "pcb_note_path") {
425
+ drawPcbNotePath({
426
+ ctx: this.ctx,
427
+ path: element as PcbNotePath,
428
+ realToCanvasMat: this.realToCanvasMat,
429
+ colorMap: this.colorMap,
430
+ })
431
+ }
491
432
 
492
- if (element.type === "pcb_note_line") {
493
- drawPcbNoteLine({
494
- ctx: this.ctx,
495
- line: element as PcbNoteLine,
496
- realToCanvasMat: this.realToCanvasMat,
497
- colorMap: this.colorMap,
498
- })
499
- }
433
+ if (element.type === "pcb_note_text") {
434
+ drawPcbNoteText({
435
+ ctx: this.ctx,
436
+ text: element as PcbNoteText,
437
+ realToCanvasMat: this.realToCanvasMat,
438
+ colorMap: this.colorMap,
439
+ })
440
+ }
500
441
 
501
- if (element.type === "pcb_note_dimension") {
502
- drawPcbNoteDimension({
503
- ctx: this.ctx,
504
- pcbNoteDimension: element as PcbNoteDimension,
505
- realToCanvasMat: this.realToCanvasMat,
506
- colorMap: this.colorMap,
507
- })
508
- }
442
+ if (element.type === "pcb_note_line") {
443
+ drawPcbNoteLine({
444
+ ctx: this.ctx,
445
+ line: element as PcbNoteLine,
446
+ realToCanvasMat: this.realToCanvasMat,
447
+ colorMap: this.colorMap,
448
+ })
449
+ }
509
450
 
510
- if (element.type === "pcb_fabrication_note_dimension") {
511
- drawPcbFabricationNoteDimension({
512
- ctx: this.ctx,
513
- pcbFabricationNoteDimension: element as PcbFabricationNoteDimension,
514
- realToCanvasMat: this.realToCanvasMat,
515
- colorMap: this.colorMap,
516
- })
451
+ if (element.type === "pcb_note_dimension") {
452
+ drawPcbNoteDimension({
453
+ ctx: this.ctx,
454
+ pcbNoteDimension: element as PcbNoteDimension,
455
+ realToCanvasMat: this.realToCanvasMat,
456
+ colorMap: this.colorMap,
457
+ })
458
+ }
459
+
460
+ if (element.type === "pcb_fabrication_note_dimension") {
461
+ drawPcbFabricationNoteDimension({
462
+ ctx: this.ctx,
463
+ pcbFabricationNoteDimension: element as PcbFabricationNoteDimension,
464
+ realToCanvasMat: this.realToCanvasMat,
465
+ colorMap: this.colorMap,
466
+ })
467
+ }
517
468
  }
518
469
  }
519
470
  }