circuit-to-canvas 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.
package/dist/index.js CHANGED
@@ -958,12 +958,14 @@ function getSoldermaskColor2(layer, colorMap) {
958
958
  function drawPcbSmtPad(params) {
959
959
  const { ctx, pad, realToCanvasMat, colorMap } = params;
960
960
  const color = layerToColor(pad.layer, colorMap);
961
- const hasSoldermask = pad.is_covered_with_solder_mask === true && pad.soldermask_margin !== void 0 && pad.soldermask_margin !== 0;
962
- const margin = hasSoldermask ? pad.soldermask_margin : 0;
961
+ const isCoveredWithSoldermask = pad.is_covered_with_solder_mask === true;
962
+ const margin = isCoveredWithSoldermask && pad.soldermask_margin !== void 0 ? pad.soldermask_margin : 0;
963
+ const hasSoldermask = isCoveredWithSoldermask && pad.soldermask_margin !== void 0 && pad.soldermask_margin !== 0;
963
964
  const soldermaskRingColor = getSoldermaskColor2(pad.layer, colorMap);
964
965
  const positiveMarginColor = colorMap.substrate;
966
+ const soldermaskOverlayColor = getSoldermaskColor2(pad.layer, colorMap);
965
967
  if (pad.shape === "rect") {
966
- if (hasSoldermask && margin > 0) {
968
+ if (isCoveredWithSoldermask && margin >= 0) {
967
969
  drawRect({
968
970
  ctx,
969
971
  center: { x: pad.x, y: pad.y },
@@ -997,10 +999,21 @@ function drawPcbSmtPad(params) {
997
999
  color
998
1000
  );
999
1001
  }
1002
+ if (isCoveredWithSoldermask && margin === 0) {
1003
+ drawRect({
1004
+ ctx,
1005
+ center: { x: pad.x, y: pad.y },
1006
+ width: pad.width,
1007
+ height: pad.height,
1008
+ fill: soldermaskOverlayColor,
1009
+ realToCanvasMat,
1010
+ borderRadius: pad.corner_radius ?? pad.rect_border_radius ?? 0
1011
+ });
1012
+ }
1000
1013
  return;
1001
1014
  }
1002
1015
  if (pad.shape === "rotated_rect") {
1003
- if (hasSoldermask && margin > 0) {
1016
+ if (isCoveredWithSoldermask && margin >= 0) {
1004
1017
  drawRect({
1005
1018
  ctx,
1006
1019
  center: { x: pad.x, y: pad.y },
@@ -1036,10 +1049,22 @@ function drawPcbSmtPad(params) {
1036
1049
  color
1037
1050
  );
1038
1051
  }
1052
+ if (isCoveredWithSoldermask && margin === 0) {
1053
+ drawRect({
1054
+ ctx,
1055
+ center: { x: pad.x, y: pad.y },
1056
+ width: pad.width,
1057
+ height: pad.height,
1058
+ fill: soldermaskOverlayColor,
1059
+ realToCanvasMat,
1060
+ borderRadius: pad.corner_radius ?? pad.rect_border_radius ?? 0,
1061
+ rotation: pad.ccw_rotation ?? 0
1062
+ });
1063
+ }
1039
1064
  return;
1040
1065
  }
1041
1066
  if (pad.shape === "circle") {
1042
- if (hasSoldermask && margin > 0) {
1067
+ if (isCoveredWithSoldermask && margin >= 0) {
1043
1068
  drawCircle({
1044
1069
  ctx,
1045
1070
  center: { x: pad.x, y: pad.y },
@@ -1066,10 +1091,19 @@ function drawPcbSmtPad(params) {
1066
1091
  color
1067
1092
  );
1068
1093
  }
1094
+ if (isCoveredWithSoldermask && margin === 0) {
1095
+ drawCircle({
1096
+ ctx,
1097
+ center: { x: pad.x, y: pad.y },
1098
+ radius: pad.radius,
1099
+ fill: soldermaskOverlayColor,
1100
+ realToCanvasMat
1101
+ });
1102
+ }
1069
1103
  return;
1070
1104
  }
1071
1105
  if (pad.shape === "pill") {
1072
- if (hasSoldermask && margin > 0) {
1106
+ if (isCoveredWithSoldermask && margin >= 0) {
1073
1107
  drawPill({
1074
1108
  ctx,
1075
1109
  center: { x: pad.x, y: pad.y },
@@ -1100,10 +1134,20 @@ function drawPcbSmtPad(params) {
1100
1134
  color
1101
1135
  );
1102
1136
  }
1137
+ if (isCoveredWithSoldermask && margin === 0) {
1138
+ drawPill({
1139
+ ctx,
1140
+ center: { x: pad.x, y: pad.y },
1141
+ width: pad.width,
1142
+ height: pad.height,
1143
+ fill: soldermaskOverlayColor,
1144
+ realToCanvasMat
1145
+ });
1146
+ }
1103
1147
  return;
1104
1148
  }
1105
1149
  if (pad.shape === "rotated_pill") {
1106
- if (hasSoldermask && margin > 0) {
1150
+ if (isCoveredWithSoldermask && margin >= 0) {
1107
1151
  drawPill({
1108
1152
  ctx,
1109
1153
  center: { x: pad.x, y: pad.y },
@@ -1136,6 +1180,17 @@ function drawPcbSmtPad(params) {
1136
1180
  color
1137
1181
  );
1138
1182
  }
1183
+ if (isCoveredWithSoldermask && margin === 0) {
1184
+ drawPill({
1185
+ ctx,
1186
+ center: { x: pad.x, y: pad.y },
1187
+ width: pad.width,
1188
+ height: pad.height,
1189
+ fill: soldermaskOverlayColor,
1190
+ realToCanvasMat,
1191
+ rotation: pad.ccw_rotation ?? 0
1192
+ });
1193
+ }
1139
1194
  return;
1140
1195
  }
1141
1196
  if (pad.shape === "polygon") {
@@ -1146,6 +1201,14 @@ function drawPcbSmtPad(params) {
1146
1201
  fill: color,
1147
1202
  realToCanvasMat
1148
1203
  });
1204
+ if (isCoveredWithSoldermask && margin >= 0) {
1205
+ drawPolygon({
1206
+ ctx,
1207
+ points: pad.points,
1208
+ fill: soldermaskOverlayColor,
1209
+ realToCanvasMat
1210
+ });
1211
+ }
1149
1212
  }
1150
1213
  return;
1151
1214
  }
@@ -1582,7 +1645,8 @@ function drawPcbCutout(params) {
1582
1645
  height: cutout.height,
1583
1646
  fill: colorMap.drill,
1584
1647
  realToCanvasMat,
1585
- rotation: cutout.rotation ?? 0
1648
+ rotation: cutout.rotation ?? 0,
1649
+ borderRadius: cutout.corner_radius ?? 0
1586
1650
  });
1587
1651
  return;
1588
1652
  }
@@ -24,6 +24,7 @@ export function drawPcbCutout(params: DrawPcbCutoutParams): void {
24
24
  fill: colorMap.drill,
25
25
  realToCanvasMat,
26
26
  rotation: cutout.rotation ?? 0,
27
+ borderRadius: cutout.corner_radius ?? 0,
27
28
  })
28
29
  return
29
30
  }
@@ -37,18 +37,24 @@ export function drawPcbSmtPad(params: DrawPcbSmtPadParams): void {
37
37
  const { ctx, pad, realToCanvasMat, colorMap } = params
38
38
 
39
39
  const color = layerToColor(pad.layer, colorMap)
40
+ const isCoveredWithSoldermask = pad.is_covered_with_solder_mask === true
41
+ // When covered with soldermask, treat margin 0 or undefined as 0 positive margin
42
+ const margin =
43
+ isCoveredWithSoldermask && pad.soldermask_margin !== undefined
44
+ ? pad.soldermask_margin
45
+ : 0
40
46
  const hasSoldermask =
41
- pad.is_covered_with_solder_mask === true &&
47
+ isCoveredWithSoldermask &&
42
48
  pad.soldermask_margin !== undefined &&
43
49
  pad.soldermask_margin !== 0
44
- const margin = hasSoldermask ? pad.soldermask_margin! : 0
45
50
  const soldermaskRingColor = getSoldermaskColor(pad.layer, colorMap)
46
51
  const positiveMarginColor = colorMap.substrate
52
+ const soldermaskOverlayColor = getSoldermaskColor(pad.layer, colorMap)
47
53
 
48
54
  // Draw the copper pad
49
55
  if (pad.shape === "rect") {
50
- // For positive margins, draw extended mask area first
51
- if (hasSoldermask && margin > 0) {
56
+ // For positive margins (including 0), draw extended mask area first
57
+ if (isCoveredWithSoldermask && margin >= 0) {
52
58
  drawRect({
53
59
  ctx,
54
60
  center: { x: pad.x, y: pad.y },
@@ -94,12 +100,28 @@ export function drawPcbSmtPad(params: DrawPcbSmtPadParams): void {
94
100
  color,
95
101
  )
96
102
  }
103
+
104
+ // If covered with soldermask and margin == 0 (treat as 0 positive margin), draw soldermaskOverCopper overlay
105
+ if (isCoveredWithSoldermask && margin === 0) {
106
+ drawRect({
107
+ ctx,
108
+ center: { x: pad.x, y: pad.y },
109
+ width: pad.width,
110
+ height: pad.height,
111
+ fill: soldermaskOverlayColor,
112
+ realToCanvasMat,
113
+ borderRadius:
114
+ (pad as { corner_radius?: number }).corner_radius ??
115
+ pad.rect_border_radius ??
116
+ 0,
117
+ })
118
+ }
97
119
  return
98
120
  }
99
121
 
100
122
  if (pad.shape === "rotated_rect") {
101
- // For positive margins, draw extended mask area first
102
- if (hasSoldermask && margin > 0) {
123
+ // For positive margins (including 0), draw extended mask area first
124
+ if (isCoveredWithSoldermask && margin >= 0) {
103
125
  drawRect({
104
126
  ctx,
105
127
  center: { x: pad.x, y: pad.y },
@@ -147,12 +169,29 @@ export function drawPcbSmtPad(params: DrawPcbSmtPadParams): void {
147
169
  color,
148
170
  )
149
171
  }
172
+
173
+ // If covered with soldermask and margin == 0 (treat as 0 positive margin), draw soldermaskOverCopper overlay
174
+ if (isCoveredWithSoldermask && margin === 0) {
175
+ drawRect({
176
+ ctx,
177
+ center: { x: pad.x, y: pad.y },
178
+ width: pad.width,
179
+ height: pad.height,
180
+ fill: soldermaskOverlayColor,
181
+ realToCanvasMat,
182
+ borderRadius:
183
+ (pad as { corner_radius?: number }).corner_radius ??
184
+ pad.rect_border_radius ??
185
+ 0,
186
+ rotation: pad.ccw_rotation ?? 0,
187
+ })
188
+ }
150
189
  return
151
190
  }
152
191
 
153
192
  if (pad.shape === "circle") {
154
- // For positive margins, draw extended mask area first
155
- if (hasSoldermask && margin > 0) {
193
+ // For positive margins (including 0), draw extended mask area first
194
+ if (isCoveredWithSoldermask && margin >= 0) {
156
195
  drawCircle({
157
196
  ctx,
158
197
  center: { x: pad.x, y: pad.y },
@@ -183,12 +222,23 @@ export function drawPcbSmtPad(params: DrawPcbSmtPadParams): void {
183
222
  color,
184
223
  )
185
224
  }
225
+
226
+ // If covered with soldermask and margin == 0 (treat as 0 positive margin), draw soldermaskOverCopper overlay
227
+ if (isCoveredWithSoldermask && margin === 0) {
228
+ drawCircle({
229
+ ctx,
230
+ center: { x: pad.x, y: pad.y },
231
+ radius: pad.radius,
232
+ fill: soldermaskOverlayColor,
233
+ realToCanvasMat,
234
+ })
235
+ }
186
236
  return
187
237
  }
188
238
 
189
239
  if (pad.shape === "pill") {
190
- // For positive margins, draw extended mask area first
191
- if (hasSoldermask && margin > 0) {
240
+ // For positive margins (including 0), draw extended mask area first
241
+ if (isCoveredWithSoldermask && margin >= 0) {
192
242
  drawPill({
193
243
  ctx,
194
244
  center: { x: pad.x, y: pad.y },
@@ -223,12 +273,24 @@ export function drawPcbSmtPad(params: DrawPcbSmtPadParams): void {
223
273
  color,
224
274
  )
225
275
  }
276
+
277
+ // If covered with soldermask and margin == 0 (treat as 0 positive margin), draw soldermaskOverCopper overlay
278
+ if (isCoveredWithSoldermask && margin === 0) {
279
+ drawPill({
280
+ ctx,
281
+ center: { x: pad.x, y: pad.y },
282
+ width: pad.width,
283
+ height: pad.height,
284
+ fill: soldermaskOverlayColor,
285
+ realToCanvasMat,
286
+ })
287
+ }
226
288
  return
227
289
  }
228
290
 
229
291
  if (pad.shape === "rotated_pill") {
230
- // For positive margins, draw extended mask area first
231
- if (hasSoldermask && margin > 0) {
292
+ // For positive margins (including 0), draw extended mask area first
293
+ if (isCoveredWithSoldermask && margin >= 0) {
232
294
  drawPill({
233
295
  ctx,
234
296
  center: { x: pad.x, y: pad.y },
@@ -265,17 +327,41 @@ export function drawPcbSmtPad(params: DrawPcbSmtPadParams): void {
265
327
  color,
266
328
  )
267
329
  }
330
+
331
+ // If covered with soldermask and margin == 0 (treat as 0 positive margin), draw soldermaskOverCopper overlay
332
+ if (isCoveredWithSoldermask && margin === 0) {
333
+ drawPill({
334
+ ctx,
335
+ center: { x: pad.x, y: pad.y },
336
+ width: pad.width,
337
+ height: pad.height,
338
+ fill: soldermaskOverlayColor,
339
+ realToCanvasMat,
340
+ rotation: pad.ccw_rotation ?? 0,
341
+ })
342
+ }
268
343
  return
269
344
  }
270
345
 
271
346
  if (pad.shape === "polygon") {
272
347
  if (pad.points && pad.points.length >= 3) {
348
+ // Draw the copper pad
273
349
  drawPolygon({
274
350
  ctx,
275
351
  points: pad.points,
276
352
  fill: color,
277
353
  realToCanvasMat,
278
354
  })
355
+
356
+ // If covered with soldermask and margin >= 0, draw soldermaskOverCopper overlay
357
+ if (isCoveredWithSoldermask && margin >= 0) {
358
+ drawPolygon({
359
+ ctx,
360
+ points: pad.points,
361
+ fill: soldermaskOverlayColor,
362
+ realToCanvasMat,
363
+ })
364
+ }
279
365
  }
280
366
  return
281
367
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "circuit-to-canvas",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.39",
4
+ "version": "0.0.41",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsup-node ./lib/index.ts --format esm --dts",
@@ -26,7 +26,31 @@ test("draw rectangular cutout", async () => {
26
26
  import.meta.path,
27
27
  )
28
28
  })
29
+ test("draw rectangular cutout with corner radius", async () => {
30
+ const canvas = createCanvas(100, 100)
31
+ const ctx = canvas.getContext("2d")
32
+ const drawer = new CircuitToCanvasDrawer(ctx)
33
+
34
+ ctx.fillStyle = "#1a1a1a"
35
+ ctx.fillRect(0, 0, 100, 100)
29
36
 
37
+ const cutout: PcbCutout = {
38
+ type: "pcb_cutout",
39
+ pcb_cutout_id: "cutout1",
40
+ shape: "rect",
41
+ center: { x: 50, y: 50 },
42
+ width: 30,
43
+ height: 20,
44
+ corner_radius: 10,
45
+ }
46
+
47
+ drawer.drawElements([cutout])
48
+
49
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
50
+ import.meta.path,
51
+ "rect-cutout-with-corner-radius",
52
+ )
53
+ })
30
54
  test("draw circular cutout", async () => {
31
55
  const canvas = createCanvas(100, 100)
32
56
  const ctx = canvas.getContext("2d")
@@ -0,0 +1,162 @@
1
+ import { expect, test } from "bun:test"
2
+ import { createCanvas } from "@napi-rs/canvas"
3
+ import { CircuitToCanvasDrawer } from "../../lib/drawer"
4
+
5
+ test("draw smt pads fully covered with soldermask and board with soldermask", async () => {
6
+ const canvas = createCanvas(800, 600)
7
+ const ctx = canvas.getContext("2d")
8
+ const drawer = new CircuitToCanvasDrawer(ctx)
9
+
10
+ ctx.fillStyle = "#1a1a1a"
11
+ ctx.fillRect(0, 0, 800, 600)
12
+
13
+ const circuit: any = [
14
+ {
15
+ type: "pcb_board",
16
+ pcb_board_id: "board0",
17
+ center: { x: 0, y: 0 },
18
+ width: 14,
19
+ height: 10,
20
+ },
21
+ // Rectangle pad fully covered with soldermask (no margin)
22
+ {
23
+ type: "pcb_smtpad",
24
+ pcb_smtpad_id: "pad_rect_covered",
25
+ shape: "rect",
26
+ layer: "top",
27
+ x: -4,
28
+ y: 2,
29
+ width: 1.6,
30
+ height: 1.1,
31
+ is_covered_with_solder_mask: true,
32
+ },
33
+ // Circle pad fully covered with soldermask (no margin)
34
+ {
35
+ type: "pcb_smtpad",
36
+ pcb_smtpad_id: "pad_circle_covered",
37
+ shape: "circle",
38
+ layer: "top",
39
+ x: 0,
40
+ y: 2,
41
+ radius: 0.75,
42
+ is_covered_with_solder_mask: true,
43
+ },
44
+ // Pill fully covered with soldermask (no margin)
45
+ {
46
+ type: "pcb_smtpad",
47
+ pcb_smtpad_id: "pad_pill_covered",
48
+ shape: "pill",
49
+ layer: "top",
50
+ x: 4,
51
+ y: 2,
52
+ width: 2.4,
53
+ height: 1,
54
+ is_covered_with_solder_mask: true,
55
+ },
56
+ // Rotated rectangle pad fully covered with soldermask
57
+ {
58
+ type: "pcb_smtpad",
59
+ pcb_smtpad_id: "pad_rotated_rect_covered",
60
+ shape: "rotated_rect",
61
+ layer: "top",
62
+ x: -4,
63
+ y: -2,
64
+ width: 1.6,
65
+ height: 1.1,
66
+ ccw_rotation: 45,
67
+ is_covered_with_solder_mask: true,
68
+ },
69
+ // Rotated pill pad fully covered with soldermask
70
+ {
71
+ type: "pcb_smtpad",
72
+ pcb_smtpad_id: "pad_rotated_pill_covered",
73
+ shape: "rotated_pill",
74
+ layer: "top",
75
+ x: 0,
76
+ y: -2,
77
+ width: 2.4,
78
+ height: 1,
79
+ ccw_rotation: 30,
80
+ is_covered_with_solder_mask: true,
81
+ },
82
+ // Polygon pad fully covered with soldermask
83
+ {
84
+ type: "pcb_smtpad",
85
+ pcb_smtpad_id: "pad_polygon_covered",
86
+ shape: "polygon",
87
+ layer: "top",
88
+ x: 4,
89
+ y: -2,
90
+ points: [
91
+ { x: 3.5, y: -2.5 },
92
+ { x: 4.5, y: -2.5 },
93
+ { x: 4.5, y: -1.5 },
94
+ { x: 4, y: -1 },
95
+ { x: 3.5, y: -1.5 },
96
+ ],
97
+ is_covered_with_solder_mask: true,
98
+ },
99
+ // Silkscreen labels
100
+ {
101
+ type: "pcb_silkscreen_text",
102
+ pcb_silkscreen_text_id: "text_rect",
103
+ layer: "top",
104
+ anchor_position: { x: -4, y: 3.2 },
105
+ anchor_alignment: "center",
106
+ text: "Rect",
107
+ font_size: 0.4,
108
+ },
109
+ {
110
+ type: "pcb_silkscreen_text",
111
+ pcb_silkscreen_text_id: "text_circle",
112
+ layer: "top",
113
+ anchor_position: { x: 0, y: 3.2 },
114
+ anchor_alignment: "center",
115
+ text: "Circle",
116
+ font_size: 0.4,
117
+ },
118
+ {
119
+ type: "pcb_silkscreen_text",
120
+ pcb_silkscreen_text_id: "text_pill",
121
+ layer: "top",
122
+ anchor_position: { x: 4, y: 3.2 },
123
+ anchor_alignment: "center",
124
+ text: "Pill",
125
+ font_size: 0.4,
126
+ },
127
+ {
128
+ type: "pcb_silkscreen_text",
129
+ pcb_silkscreen_text_id: "text_rotated_rect",
130
+ layer: "top",
131
+ anchor_position: { x: -4, y: -3.2 },
132
+ anchor_alignment: "center",
133
+ text: "Rotated Rect",
134
+ font_size: 0.4,
135
+ },
136
+ {
137
+ type: "pcb_silkscreen_text",
138
+ pcb_silkscreen_text_id: "text_rotated_pill",
139
+ layer: "top",
140
+ anchor_position: { x: 0, y: -3.2 },
141
+ anchor_alignment: "center",
142
+ text: "Rot Pill",
143
+ font_size: 0.4,
144
+ },
145
+ {
146
+ type: "pcb_silkscreen_text",
147
+ pcb_silkscreen_text_id: "text_polygon",
148
+ layer: "top",
149
+ anchor_position: { x: 4, y: -3.2 },
150
+ anchor_alignment: "center",
151
+ text: "Polygon",
152
+ font_size: 0.4,
153
+ },
154
+ ]
155
+
156
+ drawer.setCameraBounds({ minX: -7, maxX: 7, minY: -5, maxY: 5 })
157
+ drawer.drawElements(circuit)
158
+
159
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
160
+ import.meta.path,
161
+ )
162
+ })