circuit-to-canvas 0.0.45 → 0.0.46

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 (26) hide show
  1. package/dist/index.js +394 -157
  2. package/lib/drawer/CircuitToCanvasDrawer.ts +1 -5
  3. package/lib/drawer/elements/pcb-hole.ts +248 -61
  4. package/lib/drawer/elements/pcb-plated-hole.ts +194 -102
  5. package/lib/drawer/elements/pcb-smtpad.ts +14 -17
  6. package/package.json +1 -1
  7. package/tests/board-snapshot/__snapshots__/usb-c-flashlight-board.snap.png +0 -0
  8. package/tests/elements/__snapshots__/board-with-elements.snap.png +0 -0
  9. package/tests/elements/__snapshots__/custom-outline-board.snap.png +0 -0
  10. package/tests/elements/__snapshots__/pcb-board.snap.png +0 -0
  11. package/tests/elements/__snapshots__/pcb-comprehensive-soldermask-margin.snap.png +0 -0
  12. package/tests/elements/__snapshots__/pcb-fabrication-note-dimension.snap.png +0 -0
  13. package/tests/elements/__snapshots__/pcb-hole-soldermask-margin.snap.png +0 -0
  14. package/tests/elements/__snapshots__/pcb-keepout-layer-filter.snap.png +0 -0
  15. package/tests/elements/__snapshots__/pcb-keepout-multiple-layers.snap.png +0 -0
  16. package/tests/elements/__snapshots__/pcb-keepout-rect-and-circle.snap.png +0 -0
  17. package/tests/elements/__snapshots__/pcb-keepout-with-group-id.snap.png +0 -0
  18. package/tests/elements/__snapshots__/pcb-note-dimension-with-offset.snap.png +0 -0
  19. package/tests/elements/__snapshots__/pcb-plated-hole-soldermask-margin.snap.png +0 -0
  20. package/tests/elements/__snapshots__/pcb-silkscreen-oval.snap.png +0 -0
  21. package/tests/elements/__snapshots__/pcb-smtpad-soldermask-margin.snap.png +0 -0
  22. package/tests/elements/pcb-comprehensive-soldermask-margin.test.ts +330 -0
  23. package/tests/elements/pcb-hole-soldermask-margin.test.ts +5 -5
  24. package/tests/elements/pcb-plated-hole-soldermask-margin.test.ts +8 -8
  25. package/tests/elements/pcb-smtpad-soldermask-margin.test.ts +0 -6
  26. package/tests/shapes/__snapshots__/dimension-line.snap.png +0 -0
@@ -0,0 +1,330 @@
1
+ import { expect, test } from "bun:test"
2
+ import { createCanvas } from "@napi-rs/canvas"
3
+ import { CircuitToCanvasDrawer } from "../../lib/drawer"
4
+
5
+ /**
6
+ * Comprehensive test for soldermask margin functionality:
7
+ * - Row 1: Fully covered elements (is_covered_with_solder_mask = true)
8
+ * - Row 2: Positive margin (soldermask_margin = 0.5)
9
+ * - Row 3: Negative margin (soldermask_margin = -0.3)
10
+ * - Row 4: Default behavior (no margin specified)
11
+ *
12
+ * Tests different element types: SMT pads, plated holes, and holes
13
+ */
14
+ test("comprehensive soldermask margin test", async () => {
15
+ const canvas = createCanvas(1200, 1000)
16
+ const ctx = canvas.getContext("2d")
17
+ const drawer = new CircuitToCanvasDrawer(ctx)
18
+
19
+ ctx.fillStyle = "#1a1a1a"
20
+ ctx.fillRect(0, 0, 1200, 1000)
21
+
22
+ const circuit: any = [
23
+ {
24
+ type: "pcb_board",
25
+ pcb_board_id: "board_test",
26
+ center: { x: 0, y: 0 },
27
+ width: 50,
28
+ height: 40,
29
+ thickness: 1.6,
30
+ num_layers: 2,
31
+ material: "fr4",
32
+ },
33
+ // Dummy component for silkscreen elements
34
+ {
35
+ type: "pcb_component",
36
+ pcb_component_id: "dummy_component",
37
+ center: { x: 0, y: 0 },
38
+ width: 50,
39
+ height: 40,
40
+ rotation: 0,
41
+ layer: "top",
42
+ source_component_id: "dummy",
43
+ obstructs_within_bounds: false,
44
+ },
45
+ // Row 1: Fully covered elements (is_covered_with_solder_mask = true)
46
+ {
47
+ type: "pcb_smtpad",
48
+ pcb_smtpad_id: "pad_fully_covered_1",
49
+ shape: "circle",
50
+ x: -18,
51
+ y: 12,
52
+ radius: 1,
53
+ layer: "top",
54
+ is_covered_with_solder_mask: true,
55
+ },
56
+ {
57
+ type: "pcb_smtpad",
58
+ pcb_smtpad_id: "pad_fully_covered_square",
59
+ shape: "rect",
60
+ x: -12,
61
+ y: 12,
62
+ width: 2,
63
+ height: 2,
64
+ layer: "top",
65
+ is_covered_with_solder_mask: true,
66
+ },
67
+ {
68
+ type: "pcb_plated_hole",
69
+ pcb_plated_hole_id: "hole_fully_covered",
70
+ shape: "circle",
71
+ x: -5,
72
+ y: 12,
73
+ hole_diameter: 1,
74
+ outer_diameter: 2,
75
+ layers: ["top", "bottom"],
76
+ is_covered_with_solder_mask: true,
77
+ },
78
+ {
79
+ type: "pcb_hole",
80
+ pcb_hole_id: "np_hole_fully_covered",
81
+ hole_shape: "circle",
82
+ x: 5,
83
+ y: 12,
84
+ hole_diameter: 1,
85
+ is_covered_with_solder_mask: true,
86
+ },
87
+ // Row 2: Positive margin (soldermask_margin = 0.5)
88
+ {
89
+ type: "pcb_smtpad",
90
+ pcb_smtpad_id: "pad_positive_margin_1",
91
+ shape: "circle",
92
+ x: -18,
93
+ y: 4,
94
+ radius: 1,
95
+ layer: "top",
96
+ soldermask_margin: 0.5,
97
+ },
98
+ {
99
+ type: "pcb_smtpad",
100
+ pcb_smtpad_id: "pad_positive_margin_square",
101
+ shape: "rect",
102
+ x: -12,
103
+ y: 4,
104
+ width: 2,
105
+ height: 2,
106
+ layer: "top",
107
+ soldermask_margin: 0.5,
108
+ },
109
+ {
110
+ type: "pcb_plated_hole",
111
+ pcb_plated_hole_id: "hole_positive_margin",
112
+ shape: "circle",
113
+ x: -5,
114
+ y: 4,
115
+ hole_diameter: 1,
116
+ outer_diameter: 2,
117
+ layers: ["top", "bottom"],
118
+ soldermask_margin: 0.5,
119
+ },
120
+ {
121
+ type: "pcb_hole",
122
+ pcb_hole_id: "np_hole_positive_margin",
123
+ hole_shape: "circle",
124
+ x: 5,
125
+ y: 4,
126
+ hole_diameter: 1,
127
+ soldermask_margin: 0.5,
128
+ },
129
+ // Row 3: Negative margin (soldermask_margin = -0.3)
130
+ {
131
+ type: "pcb_smtpad",
132
+ pcb_smtpad_id: "pad_negative_margin_1",
133
+ shape: "circle",
134
+ x: -18,
135
+ y: -4,
136
+ radius: 1,
137
+ layer: "top",
138
+ soldermask_margin: -0.3,
139
+ },
140
+ {
141
+ type: "pcb_smtpad",
142
+ pcb_smtpad_id: "pad_negative_margin_square",
143
+ shape: "rect",
144
+ x: -12,
145
+ y: -4,
146
+ width: 2,
147
+ height: 2,
148
+ layer: "top",
149
+ soldermask_margin: -0.3,
150
+ },
151
+ {
152
+ type: "pcb_plated_hole",
153
+ pcb_plated_hole_id: "hole_negative_margin",
154
+ shape: "circle",
155
+ x: -5,
156
+ y: -4,
157
+ hole_diameter: 1,
158
+ outer_diameter: 2,
159
+ layers: ["top", "bottom"],
160
+ soldermask_margin: -0.3,
161
+ },
162
+ {
163
+ type: "pcb_hole",
164
+ pcb_hole_id: "np_hole_negative_margin",
165
+ hole_shape: "circle",
166
+ x: 5,
167
+ y: -4,
168
+ hole_diameter: 1,
169
+ soldermask_margin: -0.3,
170
+ },
171
+ // Row 4: Default behavior (no margin specified)
172
+ {
173
+ type: "pcb_smtpad",
174
+ pcb_smtpad_id: "pad_default_1",
175
+ shape: "circle",
176
+ x: -18,
177
+ y: -12,
178
+ radius: 1,
179
+ layer: "top",
180
+ },
181
+ {
182
+ type: "pcb_smtpad",
183
+ pcb_smtpad_id: "pad_default_square",
184
+ shape: "rect",
185
+ x: -12,
186
+ y: -12,
187
+ width: 2,
188
+ height: 2,
189
+ layer: "top",
190
+ },
191
+ {
192
+ type: "pcb_plated_hole",
193
+ pcb_plated_hole_id: "hole_default",
194
+ shape: "circle",
195
+ x: -5,
196
+ y: -12,
197
+ hole_diameter: 1,
198
+ outer_diameter: 2,
199
+ layers: ["top", "bottom"],
200
+ },
201
+ {
202
+ type: "pcb_hole",
203
+ pcb_hole_id: "np_hole_default",
204
+ hole_shape: "circle",
205
+ x: 5,
206
+ y: -12,
207
+ hole_diameter: 1,
208
+ },
209
+ // Silkscreen labels for each row
210
+ {
211
+ type: "pcb_silkscreen_text",
212
+ pcb_silkscreen_text_id: "label_fully_covered",
213
+ pcb_component_id: "dummy_component",
214
+ text: "FULLY COVERED",
215
+ layer: "top",
216
+ anchor_position: { x: 15, y: 12 },
217
+ anchor_alignment: "center",
218
+ font_size: 1,
219
+ font: "tscircuit2024",
220
+ },
221
+ {
222
+ type: "pcb_silkscreen_text",
223
+ pcb_silkscreen_text_id: "label_positive_margin",
224
+ pcb_component_id: "dummy_component",
225
+ text: "POSITIVE MARGIN",
226
+ layer: "top",
227
+ anchor_position: { x: 15, y: 4 },
228
+ anchor_alignment: "center",
229
+ font_size: 1,
230
+ font: "tscircuit2024",
231
+ },
232
+ {
233
+ type: "pcb_silkscreen_text",
234
+ pcb_silkscreen_text_id: "label_negative_margin",
235
+ pcb_component_id: "dummy_component",
236
+ text: "NEGATIVE MARGIN",
237
+ layer: "top",
238
+ anchor_position: { x: 15, y: -4 },
239
+ anchor_alignment: "center",
240
+ font_size: 1,
241
+ font: "tscircuit2024",
242
+ },
243
+ {
244
+ type: "pcb_silkscreen_text",
245
+ pcb_silkscreen_text_id: "label_default",
246
+ pcb_component_id: "dummy_component",
247
+ text: "DEFAULT",
248
+ layer: "top",
249
+ anchor_position: { x: 15, y: -12 },
250
+ anchor_alignment: "center",
251
+ font_size: 1,
252
+ font: "tscircuit2024",
253
+ },
254
+ // Silkscreen lines separating rows
255
+ {
256
+ type: "pcb_silkscreen_path",
257
+ pcb_silkscreen_path_id: "separator_1",
258
+ pcb_component_id: "dummy_component",
259
+ layer: "top",
260
+ stroke_width: 0.1,
261
+ route: [
262
+ { x: -25, y: 8 },
263
+ { x: 25, y: 8 },
264
+ ],
265
+ },
266
+ {
267
+ type: "pcb_silkscreen_path",
268
+ pcb_silkscreen_path_id: "separator_2",
269
+ pcb_component_id: "dummy_component",
270
+ layer: "top",
271
+ stroke_width: 0.1,
272
+ route: [
273
+ { x: -25, y: 0 },
274
+ { x: 25, y: 0 },
275
+ ],
276
+ },
277
+ {
278
+ type: "pcb_silkscreen_path",
279
+ pcb_silkscreen_path_id: "separator_3",
280
+ pcb_component_id: "dummy_component",
281
+ layer: "top",
282
+ stroke_width: 0.1,
283
+ route: [
284
+ { x: -25, y: -8 },
285
+ { x: 25, y: -8 },
286
+ ],
287
+ },
288
+ // Column labels
289
+ {
290
+ type: "pcb_silkscreen_text",
291
+ pcb_silkscreen_text_id: "label_pad",
292
+ pcb_component_id: "dummy_component",
293
+ text: "PADS",
294
+ layer: "top",
295
+ anchor_position: { x: -15, y: 16 },
296
+ anchor_alignment: "center",
297
+ font_size: 0.8,
298
+ font: "tscircuit2024",
299
+ },
300
+ {
301
+ type: "pcb_silkscreen_text",
302
+ pcb_silkscreen_text_id: "label_plated_hole",
303
+ pcb_component_id: "dummy_component",
304
+ text: "PLATED HOLE",
305
+ layer: "top",
306
+ anchor_position: { x: -5, y: 16 },
307
+ anchor_alignment: "center",
308
+ font_size: 0.8,
309
+ font: "tscircuit2024",
310
+ },
311
+ {
312
+ type: "pcb_silkscreen_text",
313
+ pcb_silkscreen_text_id: "label_hole",
314
+ pcb_component_id: "dummy_component",
315
+ text: "HOLE",
316
+ layer: "top",
317
+ anchor_position: { x: 5, y: 16 },
318
+ anchor_alignment: "center",
319
+ font_size: 0.8,
320
+ font: "tscircuit2024",
321
+ },
322
+ ]
323
+
324
+ drawer.setCameraBounds({ minX: -30, maxX: 30, minY: -20, maxY: 20 })
325
+ drawer.drawElements(circuit)
326
+
327
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
328
+ import.meta.path,
329
+ )
330
+ })
@@ -26,7 +26,7 @@ test("draw holes with positive soldermask margins", async () => {
26
26
  x: -4,
27
27
  y: 2,
28
28
  hole_diameter: 1.0,
29
- is_covered_with_solder_mask: true,
29
+
30
30
  soldermask_margin: 0.2,
31
31
  },
32
32
  // Square with positive margin
@@ -37,7 +37,7 @@ test("draw holes with positive soldermask margins", async () => {
37
37
  x: -1,
38
38
  y: 2,
39
39
  hole_diameter: 1.0,
40
- is_covered_with_solder_mask: true,
40
+
41
41
  soldermask_margin: 0.15,
42
42
  },
43
43
  // Oval with positive margin
@@ -49,7 +49,7 @@ test("draw holes with positive soldermask margins", async () => {
49
49
  y: 2,
50
50
  hole_width: 1.5,
51
51
  hole_height: 0.8,
52
- is_covered_with_solder_mask: true,
52
+
53
53
  soldermask_margin: 0.1,
54
54
  },
55
55
  // Rect with positive margin
@@ -61,7 +61,7 @@ test("draw holes with positive soldermask margins", async () => {
61
61
  y: 2,
62
62
  hole_width: 1.6,
63
63
  hole_height: 1.1,
64
- is_covered_with_solder_mask: true,
64
+
65
65
  soldermask_margin: 0.15,
66
66
  },
67
67
  // Pill with positive margin
@@ -73,7 +73,7 @@ test("draw holes with positive soldermask margins", async () => {
73
73
  y: 0,
74
74
  hole_width: 2.0,
75
75
  hole_height: 0.8,
76
- is_covered_with_solder_mask: true,
76
+
77
77
  soldermask_margin: 0.1,
78
78
  },
79
79
  // Silkscreen labels for positive margin holes
@@ -29,7 +29,7 @@ test("draw plated holes with positive and negative soldermask margins", async ()
29
29
  outer_diameter: 2,
30
30
  hole_diameter: 1,
31
31
  layers: ["top", "bottom"],
32
- is_covered_with_solder_mask: true,
32
+
33
33
  soldermask_margin: 0.2,
34
34
  },
35
35
  // Circle with negative margin (spacing around copper, copper visible)
@@ -42,7 +42,7 @@ test("draw plated holes with positive and negative soldermask margins", async ()
42
42
  outer_diameter: 2,
43
43
  hole_diameter: 1,
44
44
  layers: ["top", "bottom"],
45
- is_covered_with_solder_mask: true,
45
+
46
46
  soldermask_margin: -0.15,
47
47
  },
48
48
  // Oval with positive margin
@@ -58,7 +58,7 @@ test("draw plated holes with positive and negative soldermask margins", async ()
58
58
  hole_height: 0.8,
59
59
  layers: ["top", "bottom"],
60
60
  ccw_rotation: 0,
61
- is_covered_with_solder_mask: true,
61
+
62
62
  soldermask_margin: 0.15,
63
63
  },
64
64
  // Oval with negative margin
@@ -74,7 +74,7 @@ test("draw plated holes with positive and negative soldermask margins", async ()
74
74
  hole_height: 0.8,
75
75
  layers: ["top", "bottom"],
76
76
  ccw_rotation: 0,
77
- is_covered_with_solder_mask: true,
77
+
78
78
  soldermask_margin: -0.2,
79
79
  },
80
80
  // Pill with positive margin
@@ -90,7 +90,7 @@ test("draw plated holes with positive and negative soldermask margins", async ()
90
90
  hole_height: 1,
91
91
  layers: ["top", "bottom"],
92
92
  ccw_rotation: 0,
93
- is_covered_with_solder_mask: true,
93
+
94
94
  soldermask_margin: 0.1,
95
95
  },
96
96
  // Pill with negative margin
@@ -106,7 +106,7 @@ test("draw plated holes with positive and negative soldermask margins", async ()
106
106
  hole_height: 1,
107
107
  layers: ["top", "bottom"],
108
108
  ccw_rotation: 0,
109
- is_covered_with_solder_mask: true,
109
+
110
110
  soldermask_margin: -0.12,
111
111
  },
112
112
  // Rectangular pad with circular hole - positive margin
@@ -121,7 +121,7 @@ test("draw plated holes with positive and negative soldermask margins", async ()
121
121
  rect_border_radius: 0.2,
122
122
  hole_diameter: 1,
123
123
  layers: ["top", "bottom"],
124
- is_covered_with_solder_mask: true,
124
+
125
125
  soldermask_margin: 0.15,
126
126
  },
127
127
  // Rectangular pad with circular hole - negative margin
@@ -136,7 +136,7 @@ test("draw plated holes with positive and negative soldermask margins", async ()
136
136
  rect_border_radius: 0.2,
137
137
  hole_diameter: 1,
138
138
  layers: ["top", "bottom"],
139
- is_covered_with_solder_mask: true,
139
+
140
140
  soldermask_margin: -0.1,
141
141
  },
142
142
  // Silkscreen labels for positive margin holes (top row)
@@ -28,7 +28,6 @@ test("draw smt pads with positive and negative soldermask margins", async () =>
28
28
  y: 2,
29
29
  width: 1.6,
30
30
  height: 1.1,
31
- is_covered_with_solder_mask: true,
32
31
  soldermask_margin: 0.2,
33
32
  },
34
33
  // Rectangle with negative margin (spacing around copper, copper visible)
@@ -41,7 +40,6 @@ test("draw smt pads with positive and negative soldermask margins", async () =>
41
40
  y: -2,
42
41
  width: 1.6,
43
42
  height: 1.1,
44
- is_covered_with_solder_mask: true,
45
43
  soldermask_margin: -0.15,
46
44
  },
47
45
  // Circle with positive margin
@@ -53,7 +51,6 @@ test("draw smt pads with positive and negative soldermask margins", async () =>
53
51
  x: 0,
54
52
  y: 2,
55
53
  radius: 0.75,
56
- is_covered_with_solder_mask: true,
57
54
  soldermask_margin: 0.15,
58
55
  },
59
56
  // Circle with negative margin
@@ -65,7 +62,6 @@ test("draw smt pads with positive and negative soldermask margins", async () =>
65
62
  x: 0,
66
63
  y: -2,
67
64
  radius: 0.75,
68
- is_covered_with_solder_mask: true,
69
65
  soldermask_margin: -0.2,
70
66
  },
71
67
  // Pill with positive margin
@@ -79,7 +75,6 @@ test("draw smt pads with positive and negative soldermask margins", async () =>
79
75
  width: 2.4,
80
76
  height: 1,
81
77
  radius: 0.5,
82
- is_covered_with_solder_mask: true,
83
78
  soldermask_margin: 0.1,
84
79
  },
85
80
  // Pill with negative margin
@@ -93,7 +88,6 @@ test("draw smt pads with positive and negative soldermask margins", async () =>
93
88
  width: 2.4,
94
89
  height: 1,
95
90
  radius: 0.5,
96
- is_covered_with_solder_mask: true,
97
91
  soldermask_margin: -0.12,
98
92
  },
99
93
  // Silkscreen labels for positive margin pads (top row)