circuit-to-canvas 0.0.49 → 0.0.50
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.d.ts +6 -1
- package/dist/index.js +65 -26
- package/lib/drawer/elements/pcb-smtpad.ts +49 -26
- package/lib/drawer/elements/soldermask-margin.ts +39 -8
- package/package.json +2 -2
- package/tests/elements/__snapshots__/pcb-smtpad-asymmetric-soldermask-margin.snap.png +0 -0
- package/tests/elements/pcb-smtpad-asymmetric-soldermask-margin.test.ts +140 -0
- package/tests/elements/pcb-smtpad-soldermask-margin.test.ts +17 -1
package/dist/index.d.ts
CHANGED
|
@@ -327,7 +327,12 @@ declare function drawPcbSmtPad(params: DrawPcbSmtPadParams): void;
|
|
|
327
327
|
declare function drawSoldermaskRingForRect(ctx: CanvasContext, center: {
|
|
328
328
|
x: number;
|
|
329
329
|
y: number;
|
|
330
|
-
}, width: number, height: number, margin: number, borderRadius: number, rotation: number, realToCanvasMat: Matrix, soldermaskColor: string, padColor: string
|
|
330
|
+
}, width: number, height: number, margin: number, borderRadius: number, rotation: number, realToCanvasMat: Matrix, soldermaskColor: string, padColor: string, asymmetricMargins?: {
|
|
331
|
+
left: number;
|
|
332
|
+
right: number;
|
|
333
|
+
top: number;
|
|
334
|
+
bottom: number;
|
|
335
|
+
}): void;
|
|
331
336
|
/**
|
|
332
337
|
* Draws a soldermask ring for circular shapes with negative margin
|
|
333
338
|
* (soldermask appears inside the pad boundary)
|
package/dist/index.js
CHANGED
|
@@ -253,12 +253,19 @@ function drawPolygon(params) {
|
|
|
253
253
|
|
|
254
254
|
// lib/drawer/elements/soldermask-margin.ts
|
|
255
255
|
import { applyToPoint as applyToPoint6 } from "transformation-matrix";
|
|
256
|
-
function drawSoldermaskRingForRect(ctx, center, width, height, margin, borderRadius, rotation, realToCanvasMat, soldermaskColor, padColor) {
|
|
256
|
+
function drawSoldermaskRingForRect(ctx, center, width, height, margin, borderRadius, rotation, realToCanvasMat, soldermaskColor, padColor, asymmetricMargins) {
|
|
257
257
|
const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
|
|
258
258
|
const scaledWidth = width * Math.abs(realToCanvasMat.a);
|
|
259
259
|
const scaledHeight = height * Math.abs(realToCanvasMat.a);
|
|
260
|
-
const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
|
|
261
260
|
const scaledRadius = borderRadius * Math.abs(realToCanvasMat.a);
|
|
261
|
+
const ml = asymmetricMargins?.left ?? margin;
|
|
262
|
+
const mr = asymmetricMargins?.right ?? margin;
|
|
263
|
+
const mt = asymmetricMargins?.top ?? margin;
|
|
264
|
+
const mb = asymmetricMargins?.bottom ?? margin;
|
|
265
|
+
const scaledThicknessL = Math.max(0, -ml) * Math.abs(realToCanvasMat.a);
|
|
266
|
+
const scaledThicknessR = Math.max(0, -mr) * Math.abs(realToCanvasMat.a);
|
|
267
|
+
const scaledThicknessT = Math.max(0, -mt) * Math.abs(realToCanvasMat.a);
|
|
268
|
+
const scaledThicknessB = Math.max(0, -mb) * Math.abs(realToCanvasMat.a);
|
|
262
269
|
ctx.save();
|
|
263
270
|
ctx.translate(cx, cy);
|
|
264
271
|
if (rotation !== 0) {
|
|
@@ -299,14 +306,23 @@ function drawSoldermaskRingForRect(ctx, center, width, height, margin, borderRad
|
|
|
299
306
|
if (ctx.globalCompositeOperation !== void 0) {
|
|
300
307
|
ctx.globalCompositeOperation = prevCompositeOp || "source-over";
|
|
301
308
|
}
|
|
302
|
-
const innerWidth =
|
|
303
|
-
|
|
304
|
-
|
|
309
|
+
const innerWidth = Math.max(
|
|
310
|
+
0,
|
|
311
|
+
scaledWidth - (scaledThicknessL + scaledThicknessR)
|
|
312
|
+
);
|
|
313
|
+
const innerHeight = Math.max(
|
|
314
|
+
0,
|
|
315
|
+
scaledHeight - (scaledThicknessT + scaledThicknessB)
|
|
316
|
+
);
|
|
317
|
+
const innerRadius = Math.max(
|
|
318
|
+
0,
|
|
319
|
+
scaledRadius - (scaledThicknessL + scaledThicknessR + scaledThicknessT + scaledThicknessB) / 4
|
|
320
|
+
);
|
|
305
321
|
if (innerWidth > 0 && innerHeight > 0) {
|
|
306
322
|
ctx.beginPath();
|
|
323
|
+
const x = -scaledWidth / 2 + scaledThicknessL;
|
|
324
|
+
const y = -scaledHeight / 2 + scaledThicknessT;
|
|
307
325
|
if (innerRadius > 0) {
|
|
308
|
-
const x = -innerWidth / 2;
|
|
309
|
-
const y = -innerHeight / 2;
|
|
310
326
|
const r = Math.min(innerRadius, innerWidth / 2, innerHeight / 2);
|
|
311
327
|
ctx.moveTo(x + r, y);
|
|
312
328
|
ctx.lineTo(x + innerWidth - r, y);
|
|
@@ -324,7 +340,7 @@ function drawSoldermaskRingForRect(ctx, center, width, height, margin, borderRad
|
|
|
324
340
|
ctx.lineTo(x, y + r);
|
|
325
341
|
ctx.arcTo(x, y, x + r, y, r);
|
|
326
342
|
} else {
|
|
327
|
-
ctx.rect(
|
|
343
|
+
ctx.rect(x, y, innerWidth, innerHeight);
|
|
328
344
|
}
|
|
329
345
|
ctx.fillStyle = padColor;
|
|
330
346
|
ctx.fill();
|
|
@@ -1324,24 +1340,40 @@ function getSoldermaskColor2(layer, colorMap) {
|
|
|
1324
1340
|
return colorMap.soldermaskOverCopper[layer] ?? colorMap.soldermaskOverCopper.top;
|
|
1325
1341
|
}
|
|
1326
1342
|
function getBorderRadius(pad, margin = 0) {
|
|
1327
|
-
|
|
1343
|
+
let r = 0;
|
|
1344
|
+
if (pad.shape === "rect" || pad.shape === "rotated_rect") {
|
|
1345
|
+
r = pad.corner_radius ?? pad.rect_border_radius ?? 0;
|
|
1346
|
+
}
|
|
1347
|
+
return r + margin;
|
|
1328
1348
|
}
|
|
1329
1349
|
function drawPcbSmtPad(params) {
|
|
1330
1350
|
const { ctx, pad, realToCanvasMat, colorMap } = params;
|
|
1331
1351
|
const color = layerToColor(pad.layer, colorMap);
|
|
1332
1352
|
const isCoveredWithSoldermask = pad.is_covered_with_solder_mask === true;
|
|
1333
1353
|
const margin = isCoveredWithSoldermask ? 0 : pad.soldermask_margin ?? 0;
|
|
1334
|
-
const hasSoldermask = !isCoveredWithSoldermask && pad.soldermask_margin !== void 0 && pad.soldermask_margin !== 0;
|
|
1335
1354
|
const soldermaskRingColor = getSoldermaskColor2(pad.layer, colorMap);
|
|
1336
1355
|
const positiveMarginColor = colorMap.substrate;
|
|
1337
1356
|
const soldermaskOverlayColor = getSoldermaskColor2(pad.layer, colorMap);
|
|
1357
|
+
const hasSoldermask = !isCoveredWithSoldermask && margin !== 0;
|
|
1358
|
+
let ml = margin;
|
|
1359
|
+
let mr = margin;
|
|
1360
|
+
let mt = margin;
|
|
1361
|
+
let mb = margin;
|
|
1362
|
+
let hasAnySoldermask = hasSoldermask;
|
|
1363
|
+
if (!isCoveredWithSoldermask && (pad.shape === "rect" || pad.shape === "rotated_rect")) {
|
|
1364
|
+
ml = pad.soldermask_margin_left ?? pad.soldermask_margin ?? 0;
|
|
1365
|
+
mr = pad.soldermask_margin_right ?? pad.soldermask_margin ?? 0;
|
|
1366
|
+
mt = pad.soldermask_margin_top ?? pad.soldermask_margin ?? 0;
|
|
1367
|
+
mb = pad.soldermask_margin_bottom ?? pad.soldermask_margin ?? 0;
|
|
1368
|
+
hasAnySoldermask = ml !== 0 || mr !== 0 || mt !== 0 || mb !== 0;
|
|
1369
|
+
}
|
|
1338
1370
|
if (pad.shape === "rect") {
|
|
1339
|
-
if (
|
|
1371
|
+
if (hasAnySoldermask && (ml > 0 || mr > 0 || mt > 0 || mb > 0)) {
|
|
1340
1372
|
drawRect({
|
|
1341
1373
|
ctx,
|
|
1342
|
-
center: { x: pad.x, y: pad.y },
|
|
1343
|
-
width: pad.width +
|
|
1344
|
-
height: pad.height +
|
|
1374
|
+
center: { x: pad.x + (mr - ml) / 2, y: pad.y + (mt - mb) / 2 },
|
|
1375
|
+
width: pad.width + ml + mr,
|
|
1376
|
+
height: pad.height + mt + mb,
|
|
1345
1377
|
fill: positiveMarginColor,
|
|
1346
1378
|
realToCanvasMat,
|
|
1347
1379
|
borderRadius: getBorderRadius(pad)
|
|
@@ -1356,21 +1388,22 @@ function drawPcbSmtPad(params) {
|
|
|
1356
1388
|
realToCanvasMat,
|
|
1357
1389
|
borderRadius: getBorderRadius(pad)
|
|
1358
1390
|
});
|
|
1359
|
-
if (
|
|
1391
|
+
if (hasAnySoldermask && (ml < 0 || mr < 0 || mt < 0 || mb < 0)) {
|
|
1360
1392
|
drawSoldermaskRingForRect(
|
|
1361
1393
|
ctx,
|
|
1362
1394
|
{ x: pad.x, y: pad.y },
|
|
1363
1395
|
pad.width,
|
|
1364
1396
|
pad.height,
|
|
1365
|
-
|
|
1397
|
+
pad.soldermask_margin ?? 0,
|
|
1366
1398
|
getBorderRadius(pad),
|
|
1367
1399
|
0,
|
|
1368
1400
|
realToCanvasMat,
|
|
1369
1401
|
soldermaskRingColor,
|
|
1370
|
-
color
|
|
1402
|
+
color,
|
|
1403
|
+
{ left: ml, right: mr, top: mt, bottom: mb }
|
|
1371
1404
|
);
|
|
1372
1405
|
}
|
|
1373
|
-
if (isCoveredWithSoldermask
|
|
1406
|
+
if (isCoveredWithSoldermask) {
|
|
1374
1407
|
drawRect({
|
|
1375
1408
|
ctx,
|
|
1376
1409
|
center: { x: pad.x, y: pad.y },
|
|
@@ -1384,12 +1417,17 @@ function drawPcbSmtPad(params) {
|
|
|
1384
1417
|
return;
|
|
1385
1418
|
}
|
|
1386
1419
|
if (pad.shape === "rotated_rect") {
|
|
1387
|
-
|
|
1420
|
+
const radians = (pad.ccw_rotation ?? 0) * Math.PI / 180;
|
|
1421
|
+
const dxLocal = (mr - ml) / 2;
|
|
1422
|
+
const dyLocal = (mt - mb) / 2;
|
|
1423
|
+
const dxGlobal = dxLocal * Math.cos(radians) - dyLocal * Math.sin(radians);
|
|
1424
|
+
const dyGlobal = dxLocal * Math.sin(radians) + dyLocal * Math.cos(radians);
|
|
1425
|
+
if (hasAnySoldermask && (ml > 0 || mr > 0 || mt > 0 || mb > 0)) {
|
|
1388
1426
|
drawRect({
|
|
1389
1427
|
ctx,
|
|
1390
|
-
center: { x: pad.x, y: pad.y },
|
|
1391
|
-
width: pad.width +
|
|
1392
|
-
height: pad.height +
|
|
1428
|
+
center: { x: pad.x + dxGlobal, y: pad.y + dyGlobal },
|
|
1429
|
+
width: pad.width + ml + mr,
|
|
1430
|
+
height: pad.height + mt + mb,
|
|
1393
1431
|
fill: positiveMarginColor,
|
|
1394
1432
|
realToCanvasMat,
|
|
1395
1433
|
borderRadius: getBorderRadius(pad),
|
|
@@ -1406,21 +1444,22 @@ function drawPcbSmtPad(params) {
|
|
|
1406
1444
|
borderRadius: getBorderRadius(pad),
|
|
1407
1445
|
rotation: pad.ccw_rotation ?? 0
|
|
1408
1446
|
});
|
|
1409
|
-
if (
|
|
1447
|
+
if (hasAnySoldermask && (ml < 0 || mr < 0 || mt < 0 || mb < 0)) {
|
|
1410
1448
|
drawSoldermaskRingForRect(
|
|
1411
1449
|
ctx,
|
|
1412
1450
|
{ x: pad.x, y: pad.y },
|
|
1413
1451
|
pad.width,
|
|
1414
1452
|
pad.height,
|
|
1415
|
-
|
|
1453
|
+
pad.soldermask_margin ?? 0,
|
|
1416
1454
|
getBorderRadius(pad),
|
|
1417
1455
|
pad.ccw_rotation ?? 0,
|
|
1418
1456
|
realToCanvasMat,
|
|
1419
1457
|
soldermaskRingColor,
|
|
1420
|
-
color
|
|
1458
|
+
color,
|
|
1459
|
+
{ left: ml, right: mr, top: mt, bottom: mb }
|
|
1421
1460
|
);
|
|
1422
1461
|
}
|
|
1423
|
-
if (isCoveredWithSoldermask
|
|
1462
|
+
if (isCoveredWithSoldermask) {
|
|
1424
1463
|
drawRect({
|
|
1425
1464
|
ctx,
|
|
1426
1465
|
center: { x: pad.x, y: pad.y },
|
|
@@ -36,11 +36,11 @@ function getSoldermaskColor(layer: string, colorMap: PcbColorMap): string {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function getBorderRadius(pad: PcbSmtPad, margin = 0): number {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
let r = 0
|
|
40
|
+
if (pad.shape === "rect" || pad.shape === "rotated_rect") {
|
|
41
|
+
r = pad.corner_radius ?? pad.rect_border_radius ?? 0
|
|
42
|
+
}
|
|
43
|
+
return r + margin
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export function drawPcbSmtPad(params: DrawPcbSmtPadParams): void {
|
|
@@ -48,25 +48,40 @@ export function drawPcbSmtPad(params: DrawPcbSmtPadParams): void {
|
|
|
48
48
|
|
|
49
49
|
const color = layerToColor(pad.layer, colorMap)
|
|
50
50
|
const isCoveredWithSoldermask = pad.is_covered_with_solder_mask === true
|
|
51
|
-
// If covered with soldermask, fully covered with no margin; otherwise use soldermask_margin if set
|
|
52
51
|
const margin = isCoveredWithSoldermask ? 0 : (pad.soldermask_margin ?? 0)
|
|
53
|
-
|
|
54
|
-
!isCoveredWithSoldermask &&
|
|
55
|
-
pad.soldermask_margin !== undefined &&
|
|
56
|
-
pad.soldermask_margin !== 0
|
|
52
|
+
|
|
57
53
|
const soldermaskRingColor = getSoldermaskColor(pad.layer, colorMap)
|
|
58
54
|
const positiveMarginColor = colorMap.substrate
|
|
59
55
|
const soldermaskOverlayColor = getSoldermaskColor(pad.layer, colorMap)
|
|
60
56
|
|
|
57
|
+
const hasSoldermask = !isCoveredWithSoldermask && margin !== 0
|
|
58
|
+
|
|
59
|
+
let ml = margin
|
|
60
|
+
let mr = margin
|
|
61
|
+
let mt = margin
|
|
62
|
+
let mb = margin
|
|
63
|
+
let hasAnySoldermask = hasSoldermask
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
!isCoveredWithSoldermask &&
|
|
67
|
+
(pad.shape === "rect" || pad.shape === "rotated_rect")
|
|
68
|
+
) {
|
|
69
|
+
ml = pad.soldermask_margin_left ?? pad.soldermask_margin ?? 0
|
|
70
|
+
mr = pad.soldermask_margin_right ?? pad.soldermask_margin ?? 0
|
|
71
|
+
mt = pad.soldermask_margin_top ?? pad.soldermask_margin ?? 0
|
|
72
|
+
mb = pad.soldermask_margin_bottom ?? pad.soldermask_margin ?? 0
|
|
73
|
+
hasAnySoldermask = ml !== 0 || mr !== 0 || mt !== 0 || mb !== 0
|
|
74
|
+
}
|
|
75
|
+
|
|
61
76
|
// Draw the copper pad
|
|
62
77
|
if (pad.shape === "rect") {
|
|
63
78
|
// For positive margins, draw extended mask area first
|
|
64
|
-
if (
|
|
79
|
+
if (hasAnySoldermask && (ml > 0 || mr > 0 || mt > 0 || mb > 0)) {
|
|
65
80
|
drawRect({
|
|
66
81
|
ctx,
|
|
67
|
-
center: { x: pad.x, y: pad.y },
|
|
68
|
-
width: pad.width +
|
|
69
|
-
height: pad.height +
|
|
82
|
+
center: { x: pad.x + (mr - ml) / 2, y: pad.y + (mt - mb) / 2 },
|
|
83
|
+
width: pad.width + ml + mr,
|
|
84
|
+
height: pad.height + mt + mb,
|
|
70
85
|
fill: positiveMarginColor,
|
|
71
86
|
realToCanvasMat,
|
|
72
87
|
borderRadius: getBorderRadius(pad),
|
|
@@ -85,23 +100,24 @@ export function drawPcbSmtPad(params: DrawPcbSmtPadParams): void {
|
|
|
85
100
|
})
|
|
86
101
|
|
|
87
102
|
// For negative margins, draw soldermask ring on top of the pad
|
|
88
|
-
if (
|
|
103
|
+
if (hasAnySoldermask && (ml < 0 || mr < 0 || mt < 0 || mb < 0)) {
|
|
89
104
|
drawSoldermaskRingForRect(
|
|
90
105
|
ctx,
|
|
91
106
|
{ x: pad.x, y: pad.y },
|
|
92
107
|
pad.width,
|
|
93
108
|
pad.height,
|
|
94
|
-
|
|
109
|
+
pad.soldermask_margin ?? 0,
|
|
95
110
|
getBorderRadius(pad),
|
|
96
111
|
0,
|
|
97
112
|
realToCanvasMat,
|
|
98
113
|
soldermaskRingColor,
|
|
99
114
|
color,
|
|
115
|
+
{ left: ml, right: mr, top: mt, bottom: mb },
|
|
100
116
|
)
|
|
101
117
|
}
|
|
102
118
|
|
|
103
|
-
// If covered with soldermask
|
|
104
|
-
if (isCoveredWithSoldermask
|
|
119
|
+
// If covered with soldermask, draw soldermaskOverCopper overlay
|
|
120
|
+
if (isCoveredWithSoldermask) {
|
|
105
121
|
drawRect({
|
|
106
122
|
ctx,
|
|
107
123
|
center: { x: pad.x, y: pad.y },
|
|
@@ -116,13 +132,19 @@ export function drawPcbSmtPad(params: DrawPcbSmtPadParams): void {
|
|
|
116
132
|
}
|
|
117
133
|
|
|
118
134
|
if (pad.shape === "rotated_rect") {
|
|
135
|
+
const radians = ((pad.ccw_rotation ?? 0) * Math.PI) / 180
|
|
136
|
+
const dxLocal = (mr - ml) / 2
|
|
137
|
+
const dyLocal = (mt - mb) / 2
|
|
138
|
+
const dxGlobal = dxLocal * Math.cos(radians) - dyLocal * Math.sin(radians)
|
|
139
|
+
const dyGlobal = dxLocal * Math.sin(radians) + dyLocal * Math.cos(radians)
|
|
140
|
+
|
|
119
141
|
// For positive margins, draw extended mask area first
|
|
120
|
-
if (
|
|
142
|
+
if (hasAnySoldermask && (ml > 0 || mr > 0 || mt > 0 || mb > 0)) {
|
|
121
143
|
drawRect({
|
|
122
144
|
ctx,
|
|
123
|
-
center: { x: pad.x, y: pad.y },
|
|
124
|
-
width: pad.width +
|
|
125
|
-
height: pad.height +
|
|
145
|
+
center: { x: pad.x + dxGlobal, y: pad.y + dyGlobal },
|
|
146
|
+
width: pad.width + ml + mr,
|
|
147
|
+
height: pad.height + mt + mb,
|
|
126
148
|
fill: positiveMarginColor,
|
|
127
149
|
realToCanvasMat,
|
|
128
150
|
borderRadius: getBorderRadius(pad),
|
|
@@ -143,23 +165,24 @@ export function drawPcbSmtPad(params: DrawPcbSmtPadParams): void {
|
|
|
143
165
|
})
|
|
144
166
|
|
|
145
167
|
// For negative margins, draw soldermask ring on top of the pad
|
|
146
|
-
if (
|
|
168
|
+
if (hasAnySoldermask && (ml < 0 || mr < 0 || mt < 0 || mb < 0)) {
|
|
147
169
|
drawSoldermaskRingForRect(
|
|
148
170
|
ctx,
|
|
149
171
|
{ x: pad.x, y: pad.y },
|
|
150
172
|
pad.width,
|
|
151
173
|
pad.height,
|
|
152
|
-
|
|
174
|
+
pad.soldermask_margin ?? 0,
|
|
153
175
|
getBorderRadius(pad),
|
|
154
176
|
pad.ccw_rotation ?? 0,
|
|
155
177
|
realToCanvasMat,
|
|
156
178
|
soldermaskRingColor,
|
|
157
179
|
color,
|
|
180
|
+
{ left: ml, right: mr, top: mt, bottom: mb },
|
|
158
181
|
)
|
|
159
182
|
}
|
|
160
183
|
|
|
161
|
-
// If covered with soldermask
|
|
162
|
-
if (isCoveredWithSoldermask
|
|
184
|
+
// If covered with soldermask, draw soldermaskOverCopper overlay
|
|
185
|
+
if (isCoveredWithSoldermask) {
|
|
163
186
|
drawRect({
|
|
164
187
|
ctx,
|
|
165
188
|
center: { x: pad.x, y: pad.y },
|
|
@@ -17,13 +17,29 @@ export function drawSoldermaskRingForRect(
|
|
|
17
17
|
realToCanvasMat: Matrix,
|
|
18
18
|
soldermaskColor: string,
|
|
19
19
|
padColor: string,
|
|
20
|
+
asymmetricMargins?: {
|
|
21
|
+
left: number
|
|
22
|
+
right: number
|
|
23
|
+
top: number
|
|
24
|
+
bottom: number
|
|
25
|
+
},
|
|
20
26
|
): void {
|
|
21
27
|
const [cx, cy] = applyToPoint(realToCanvasMat, [center.x, center.y])
|
|
22
28
|
const scaledWidth = width * Math.abs(realToCanvasMat.a)
|
|
23
29
|
const scaledHeight = height * Math.abs(realToCanvasMat.a)
|
|
24
|
-
const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a)
|
|
25
30
|
const scaledRadius = borderRadius * Math.abs(realToCanvasMat.a)
|
|
26
31
|
|
|
32
|
+
const ml = asymmetricMargins?.left ?? margin
|
|
33
|
+
const mr = asymmetricMargins?.right ?? margin
|
|
34
|
+
const mt = asymmetricMargins?.top ?? margin
|
|
35
|
+
const mb = asymmetricMargins?.bottom ?? margin
|
|
36
|
+
|
|
37
|
+
// Thickness of the soldermask ring (only if negative margin)
|
|
38
|
+
const scaledThicknessL = Math.max(0, -ml) * Math.abs(realToCanvasMat.a)
|
|
39
|
+
const scaledThicknessR = Math.max(0, -mr) * Math.abs(realToCanvasMat.a)
|
|
40
|
+
const scaledThicknessT = Math.max(0, -mt) * Math.abs(realToCanvasMat.a)
|
|
41
|
+
const scaledThicknessB = Math.max(0, -mb) * Math.abs(realToCanvasMat.a)
|
|
42
|
+
|
|
27
43
|
ctx.save()
|
|
28
44
|
ctx.translate(cx, cy)
|
|
29
45
|
|
|
@@ -76,16 +92,31 @@ export function drawSoldermaskRingForRect(
|
|
|
76
92
|
ctx.globalCompositeOperation = prevCompositeOp || "source-over"
|
|
77
93
|
}
|
|
78
94
|
|
|
79
|
-
// Restore pad color in inner rectangle (reduced by
|
|
80
|
-
const innerWidth =
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
// Restore pad color in inner rectangle (reduced by margins)
|
|
96
|
+
const innerWidth = Math.max(
|
|
97
|
+
0,
|
|
98
|
+
scaledWidth - (scaledThicknessL + scaledThicknessR),
|
|
99
|
+
)
|
|
100
|
+
const innerHeight = Math.max(
|
|
101
|
+
0,
|
|
102
|
+
scaledHeight - (scaledThicknessT + scaledThicknessB),
|
|
103
|
+
)
|
|
104
|
+
const innerRadius = Math.max(
|
|
105
|
+
0,
|
|
106
|
+
scaledRadius -
|
|
107
|
+
(scaledThicknessL +
|
|
108
|
+
scaledThicknessR +
|
|
109
|
+
scaledThicknessT +
|
|
110
|
+
scaledThicknessB) /
|
|
111
|
+
4,
|
|
112
|
+
)
|
|
83
113
|
|
|
84
114
|
if (innerWidth > 0 && innerHeight > 0) {
|
|
85
115
|
ctx.beginPath()
|
|
116
|
+
const x = -scaledWidth / 2 + scaledThicknessL
|
|
117
|
+
const y = -scaledHeight / 2 + scaledThicknessT
|
|
118
|
+
|
|
86
119
|
if (innerRadius > 0) {
|
|
87
|
-
const x = -innerWidth / 2
|
|
88
|
-
const y = -innerHeight / 2
|
|
89
120
|
const r = Math.min(innerRadius, innerWidth / 2, innerHeight / 2)
|
|
90
121
|
|
|
91
122
|
ctx.moveTo(x + r, y)
|
|
@@ -104,7 +135,7 @@ export function drawSoldermaskRingForRect(
|
|
|
104
135
|
ctx.lineTo(x, y + r)
|
|
105
136
|
ctx.arcTo(x, y, x + r, y, r)
|
|
106
137
|
} else {
|
|
107
|
-
ctx.rect(
|
|
138
|
+
ctx.rect(x, y, innerWidth, innerHeight)
|
|
108
139
|
}
|
|
109
140
|
|
|
110
141
|
ctx.fillStyle = padColor
|
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.
|
|
4
|
+
"version": "0.0.50",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsup-node ./lib/index.ts --format esm --dts",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"@tscircuit/math-utils": "^0.0.29",
|
|
18
18
|
"@types/bun": "latest",
|
|
19
19
|
"bun-match-svg": "^0.0.14",
|
|
20
|
-
"circuit-json": "^0.0.
|
|
20
|
+
"circuit-json": "^0.0.356",
|
|
21
21
|
"circuit-json-to-connectivity-map": "^0.0.23",
|
|
22
22
|
"circuit-to-svg": "^0.0.303",
|
|
23
23
|
"looks-same": "^10.0.1",
|
|
Binary file
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { createCanvas } from "@napi-rs/canvas"
|
|
3
|
+
import type { AnyCircuitElement } from "circuit-json"
|
|
4
|
+
import { CircuitToCanvasDrawer } from "../../lib/drawer"
|
|
5
|
+
|
|
6
|
+
test("draw smt pads with asymmetric soldermask margins", async () => {
|
|
7
|
+
const canvas = createCanvas(800, 600)
|
|
8
|
+
const ctx = canvas.getContext("2d")
|
|
9
|
+
const drawer = new CircuitToCanvasDrawer(ctx)
|
|
10
|
+
|
|
11
|
+
ctx.fillStyle = "#1a1a1a"
|
|
12
|
+
ctx.fillRect(0, 0, 800, 600)
|
|
13
|
+
|
|
14
|
+
const circuit: AnyCircuitElement[] = [
|
|
15
|
+
{
|
|
16
|
+
type: "pcb_board",
|
|
17
|
+
pcb_board_id: "board0",
|
|
18
|
+
center: { x: 0, y: 0 },
|
|
19
|
+
width: 14,
|
|
20
|
+
height: 10,
|
|
21
|
+
thickness: 1.6,
|
|
22
|
+
num_layers: 2,
|
|
23
|
+
material: "fr4",
|
|
24
|
+
},
|
|
25
|
+
// Rectangle with asymmetric positive margins
|
|
26
|
+
{
|
|
27
|
+
type: "pcb_smtpad",
|
|
28
|
+
pcb_smtpad_id: "pad_rect_pos_asym",
|
|
29
|
+
shape: "rect",
|
|
30
|
+
layer: "top",
|
|
31
|
+
x: -4,
|
|
32
|
+
y: 2,
|
|
33
|
+
width: 2,
|
|
34
|
+
height: 1,
|
|
35
|
+
soldermask_margin_left: 0.5,
|
|
36
|
+
soldermask_margin_right: 0.1,
|
|
37
|
+
soldermask_margin_top: 0.2,
|
|
38
|
+
soldermask_margin_bottom: 0.8,
|
|
39
|
+
},
|
|
40
|
+
// Rectangle with asymmetric negative margins
|
|
41
|
+
{
|
|
42
|
+
type: "pcb_smtpad",
|
|
43
|
+
pcb_smtpad_id: "pad_rect_neg_asym",
|
|
44
|
+
shape: "rect",
|
|
45
|
+
layer: "top",
|
|
46
|
+
x: -4,
|
|
47
|
+
y: -2,
|
|
48
|
+
width: 2,
|
|
49
|
+
height: 1.5,
|
|
50
|
+
soldermask_margin_left: -0.2,
|
|
51
|
+
soldermask_margin_right: -0.6,
|
|
52
|
+
soldermask_margin_top: -0.1,
|
|
53
|
+
soldermask_margin_bottom: -0.4,
|
|
54
|
+
},
|
|
55
|
+
// Rotated rectangle with asymmetric positive margins
|
|
56
|
+
{
|
|
57
|
+
type: "pcb_smtpad",
|
|
58
|
+
pcb_smtpad_id: "pad_rot_rect_pos_asym",
|
|
59
|
+
shape: "rotated_rect",
|
|
60
|
+
layer: "top",
|
|
61
|
+
x: 4,
|
|
62
|
+
y: 2,
|
|
63
|
+
width: 2,
|
|
64
|
+
height: 1,
|
|
65
|
+
ccw_rotation: 45,
|
|
66
|
+
soldermask_margin_left: 0.8,
|
|
67
|
+
soldermask_margin_right: 0.2,
|
|
68
|
+
soldermask_margin_top: 0.1,
|
|
69
|
+
soldermask_margin_bottom: 0.5,
|
|
70
|
+
},
|
|
71
|
+
// Rotated rectangle with asymmetric negative margins
|
|
72
|
+
{
|
|
73
|
+
type: "pcb_smtpad",
|
|
74
|
+
pcb_smtpad_id: "pad_rot_rect_neg_asym",
|
|
75
|
+
shape: "rotated_rect",
|
|
76
|
+
layer: "top",
|
|
77
|
+
x: 4,
|
|
78
|
+
y: -2,
|
|
79
|
+
width: 2,
|
|
80
|
+
height: 1.5,
|
|
81
|
+
ccw_rotation: 30,
|
|
82
|
+
soldermask_margin_left: -0.5,
|
|
83
|
+
soldermask_margin_right: -0.1,
|
|
84
|
+
soldermask_margin_top: -0.4,
|
|
85
|
+
soldermask_margin_bottom: -0.2,
|
|
86
|
+
},
|
|
87
|
+
// Labels
|
|
88
|
+
{
|
|
89
|
+
type: "pcb_silkscreen_text",
|
|
90
|
+
pcb_silkscreen_text_id: "text1",
|
|
91
|
+
pcb_component_id: "comp1",
|
|
92
|
+
layer: "top",
|
|
93
|
+
anchor_position: { x: -4, y: 3.5 },
|
|
94
|
+
anchor_alignment: "center",
|
|
95
|
+
text: "Rect Pos Asym",
|
|
96
|
+
font_size: 0.4,
|
|
97
|
+
font: "tscircuit2024",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
type: "pcb_silkscreen_text",
|
|
101
|
+
pcb_silkscreen_text_id: "text2",
|
|
102
|
+
pcb_component_id: "comp1",
|
|
103
|
+
layer: "top",
|
|
104
|
+
anchor_position: { x: -4, y: -3.8 },
|
|
105
|
+
anchor_alignment: "center",
|
|
106
|
+
text: "Rect Neg Asym",
|
|
107
|
+
font_size: 0.4,
|
|
108
|
+
font: "tscircuit2024",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
type: "pcb_silkscreen_text",
|
|
112
|
+
pcb_silkscreen_text_id: "text3",
|
|
113
|
+
pcb_component_id: "comp1",
|
|
114
|
+
layer: "top",
|
|
115
|
+
anchor_position: { x: 4, y: 3.8 },
|
|
116
|
+
anchor_alignment: "center",
|
|
117
|
+
text: "Rot Rect Pos Asym",
|
|
118
|
+
font_size: 0.4,
|
|
119
|
+
font: "tscircuit2024",
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: "pcb_silkscreen_text",
|
|
123
|
+
pcb_silkscreen_text_id: "text4",
|
|
124
|
+
pcb_component_id: "comp1",
|
|
125
|
+
layer: "top",
|
|
126
|
+
anchor_position: { x: 4, y: -3.8 },
|
|
127
|
+
anchor_alignment: "center",
|
|
128
|
+
text: "Rot Rect Neg Asym",
|
|
129
|
+
font_size: 0.4,
|
|
130
|
+
font: "tscircuit2024",
|
|
131
|
+
},
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
drawer.setCameraBounds({ minX: -7, maxX: 7, minY: -5, maxY: 5 })
|
|
135
|
+
drawer.drawElements(circuit)
|
|
136
|
+
|
|
137
|
+
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
138
|
+
import.meta.path,
|
|
139
|
+
)
|
|
140
|
+
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { expect, test } from "bun:test"
|
|
2
2
|
import { createCanvas } from "@napi-rs/canvas"
|
|
3
|
+
import type { AnyCircuitElement } from "circuit-json"
|
|
3
4
|
import { CircuitToCanvasDrawer } from "../../lib/drawer"
|
|
4
5
|
|
|
5
6
|
test("draw smt pads with positive and negative soldermask margins", async () => {
|
|
@@ -10,13 +11,16 @@ test("draw smt pads with positive and negative soldermask margins", async () =>
|
|
|
10
11
|
ctx.fillStyle = "#1a1a1a"
|
|
11
12
|
ctx.fillRect(0, 0, 800, 600)
|
|
12
13
|
|
|
13
|
-
const circuit:
|
|
14
|
+
const circuit: AnyCircuitElement[] = [
|
|
14
15
|
{
|
|
15
16
|
type: "pcb_board",
|
|
16
17
|
pcb_board_id: "board0",
|
|
17
18
|
center: { x: 0, y: 0 },
|
|
18
19
|
width: 14,
|
|
19
20
|
height: 10,
|
|
21
|
+
thickness: 1.6,
|
|
22
|
+
num_layers: 2,
|
|
23
|
+
material: "fr4",
|
|
20
24
|
},
|
|
21
25
|
// Rectangle with positive margin (mask extends beyond pad)
|
|
22
26
|
{
|
|
@@ -94,57 +98,69 @@ test("draw smt pads with positive and negative soldermask margins", async () =>
|
|
|
94
98
|
{
|
|
95
99
|
type: "pcb_silkscreen_text",
|
|
96
100
|
pcb_silkscreen_text_id: "text_rect_pos",
|
|
101
|
+
pcb_component_id: "comp1",
|
|
97
102
|
layer: "top",
|
|
98
103
|
anchor_position: { x: -4, y: 3.2 },
|
|
99
104
|
anchor_alignment: "center",
|
|
100
105
|
text: "+0.2mm",
|
|
101
106
|
font_size: 0.4,
|
|
107
|
+
font: "tscircuit2024",
|
|
102
108
|
},
|
|
103
109
|
{
|
|
104
110
|
type: "pcb_silkscreen_text",
|
|
105
111
|
pcb_silkscreen_text_id: "text_circle_pos",
|
|
112
|
+
pcb_component_id: "comp1",
|
|
106
113
|
layer: "top",
|
|
107
114
|
anchor_position: { x: 0, y: 3.2 },
|
|
108
115
|
anchor_alignment: "center",
|
|
109
116
|
text: "+0.15mm",
|
|
110
117
|
font_size: 0.4,
|
|
118
|
+
font: "tscircuit2024",
|
|
111
119
|
},
|
|
112
120
|
{
|
|
113
121
|
type: "pcb_silkscreen_text",
|
|
114
122
|
pcb_silkscreen_text_id: "text_pill_pos",
|
|
123
|
+
pcb_component_id: "comp1",
|
|
115
124
|
layer: "top",
|
|
116
125
|
anchor_position: { x: 4, y: 3.2 },
|
|
117
126
|
anchor_alignment: "center",
|
|
118
127
|
text: "+0.1mm",
|
|
119
128
|
font_size: 0.4,
|
|
129
|
+
font: "tscircuit2024",
|
|
120
130
|
},
|
|
121
131
|
// Silkscreen labels for negative margin pads (bottom row)
|
|
122
132
|
{
|
|
123
133
|
type: "pcb_silkscreen_text",
|
|
124
134
|
pcb_silkscreen_text_id: "text_rect_neg",
|
|
135
|
+
pcb_component_id: "comp1",
|
|
125
136
|
layer: "top",
|
|
126
137
|
anchor_position: { x: -4, y: -3.2 },
|
|
127
138
|
anchor_alignment: "center",
|
|
128
139
|
text: "-0.15mm",
|
|
129
140
|
font_size: 0.4,
|
|
141
|
+
font: "tscircuit2024",
|
|
130
142
|
},
|
|
131
143
|
{
|
|
132
144
|
type: "pcb_silkscreen_text",
|
|
133
145
|
pcb_silkscreen_text_id: "text_circle_neg",
|
|
146
|
+
pcb_component_id: "comp1",
|
|
134
147
|
layer: "top",
|
|
135
148
|
anchor_position: { x: 0, y: -3.2 },
|
|
136
149
|
anchor_alignment: "center",
|
|
137
150
|
text: "-0.2mm",
|
|
138
151
|
font_size: 0.4,
|
|
152
|
+
font: "tscircuit2024",
|
|
139
153
|
},
|
|
140
154
|
{
|
|
141
155
|
type: "pcb_silkscreen_text",
|
|
142
156
|
pcb_silkscreen_text_id: "text_pill_neg",
|
|
157
|
+
pcb_component_id: "comp1",
|
|
143
158
|
layer: "top",
|
|
144
159
|
anchor_position: { x: 4, y: -3.2 },
|
|
145
160
|
anchor_alignment: "center",
|
|
146
161
|
text: "-0.12mm",
|
|
147
162
|
font_size: 0.4,
|
|
163
|
+
font: "tscircuit2024",
|
|
148
164
|
},
|
|
149
165
|
]
|
|
150
166
|
|