circuit-to-canvas 0.0.37 → 0.0.38
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 +372 -216
- package/lib/drawer/CircuitToCanvasDrawer.ts +7 -1
- package/lib/drawer/elements/pcb-plated-hole.ts +203 -7
- package/package.json +1 -1
- package/tests/elements/__snapshots__/pcb-plated-hole-soldermask-margin.snap.png +0 -0
- package/tests/elements/pcb-plated-hole-soldermask-margin.test.ts +225 -0
package/dist/index.js
CHANGED
|
@@ -249,17 +249,252 @@ function drawPolygon(params) {
|
|
|
249
249
|
ctx.fill();
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
+
// lib/drawer/elements/soldermask-margin.ts
|
|
253
|
+
import { applyToPoint as applyToPoint6 } from "transformation-matrix";
|
|
254
|
+
function drawSoldermaskRingForRect(ctx, center, width, height, margin, borderRadius, rotation, realToCanvasMat, soldermaskColor, padColor) {
|
|
255
|
+
const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
|
|
256
|
+
const scaledWidth = width * Math.abs(realToCanvasMat.a);
|
|
257
|
+
const scaledHeight = height * Math.abs(realToCanvasMat.a);
|
|
258
|
+
const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
|
|
259
|
+
const scaledRadius = borderRadius * Math.abs(realToCanvasMat.a);
|
|
260
|
+
ctx.save();
|
|
261
|
+
ctx.translate(cx, cy);
|
|
262
|
+
if (rotation !== 0) {
|
|
263
|
+
ctx.rotate(-rotation * (Math.PI / 180));
|
|
264
|
+
}
|
|
265
|
+
const prevCompositeOp = ctx.globalCompositeOperation;
|
|
266
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
267
|
+
ctx.globalCompositeOperation = "source-atop";
|
|
268
|
+
}
|
|
269
|
+
const outerWidth = scaledWidth;
|
|
270
|
+
const outerHeight = scaledHeight;
|
|
271
|
+
const outerRadius = scaledRadius;
|
|
272
|
+
ctx.beginPath();
|
|
273
|
+
if (outerRadius > 0) {
|
|
274
|
+
const x = -outerWidth / 2;
|
|
275
|
+
const y = -outerHeight / 2;
|
|
276
|
+
const r = Math.min(outerRadius, outerWidth / 2, outerHeight / 2);
|
|
277
|
+
ctx.moveTo(x + r, y);
|
|
278
|
+
ctx.lineTo(x + outerWidth - r, y);
|
|
279
|
+
ctx.arcTo(x + outerWidth, y, x + outerWidth, y + r, r);
|
|
280
|
+
ctx.lineTo(x + outerWidth, y + outerHeight - r);
|
|
281
|
+
ctx.arcTo(
|
|
282
|
+
x + outerWidth,
|
|
283
|
+
y + outerHeight,
|
|
284
|
+
x + outerWidth - r,
|
|
285
|
+
y + outerHeight,
|
|
286
|
+
r
|
|
287
|
+
);
|
|
288
|
+
ctx.lineTo(x + r, y + outerHeight);
|
|
289
|
+
ctx.arcTo(x, y + outerHeight, x, y + outerHeight - r, r);
|
|
290
|
+
ctx.lineTo(x, y + r);
|
|
291
|
+
ctx.arcTo(x, y, x + r, y, r);
|
|
292
|
+
} else {
|
|
293
|
+
ctx.rect(-outerWidth / 2, -outerHeight / 2, outerWidth, outerHeight);
|
|
294
|
+
}
|
|
295
|
+
ctx.fillStyle = soldermaskColor;
|
|
296
|
+
ctx.fill();
|
|
297
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
298
|
+
ctx.globalCompositeOperation = prevCompositeOp || "source-over";
|
|
299
|
+
}
|
|
300
|
+
const innerWidth = scaledWidth - scaledMargin * 2;
|
|
301
|
+
const innerHeight = scaledHeight - scaledMargin * 2;
|
|
302
|
+
const innerRadius = Math.max(0, scaledRadius - scaledMargin);
|
|
303
|
+
if (innerWidth > 0 && innerHeight > 0) {
|
|
304
|
+
ctx.beginPath();
|
|
305
|
+
if (innerRadius > 0) {
|
|
306
|
+
const x = -innerWidth / 2;
|
|
307
|
+
const y = -innerHeight / 2;
|
|
308
|
+
const r = Math.min(innerRadius, innerWidth / 2, innerHeight / 2);
|
|
309
|
+
ctx.moveTo(x + r, y);
|
|
310
|
+
ctx.lineTo(x + innerWidth - r, y);
|
|
311
|
+
ctx.arcTo(x + innerWidth, y, x + innerWidth, y + r, r);
|
|
312
|
+
ctx.lineTo(x + innerWidth, y + innerHeight - r);
|
|
313
|
+
ctx.arcTo(
|
|
314
|
+
x + innerWidth,
|
|
315
|
+
y + innerHeight,
|
|
316
|
+
x + innerWidth - r,
|
|
317
|
+
y + innerHeight,
|
|
318
|
+
r
|
|
319
|
+
);
|
|
320
|
+
ctx.lineTo(x + r, y + innerHeight);
|
|
321
|
+
ctx.arcTo(x, y + innerHeight, x, y + innerHeight - r, r);
|
|
322
|
+
ctx.lineTo(x, y + r);
|
|
323
|
+
ctx.arcTo(x, y, x + r, y, r);
|
|
324
|
+
} else {
|
|
325
|
+
ctx.rect(-innerWidth / 2, -innerHeight / 2, innerWidth, innerHeight);
|
|
326
|
+
}
|
|
327
|
+
ctx.fillStyle = padColor;
|
|
328
|
+
ctx.fill();
|
|
329
|
+
}
|
|
330
|
+
ctx.restore();
|
|
331
|
+
}
|
|
332
|
+
function drawSoldermaskRingForCircle(ctx, center, radius, margin, realToCanvasMat, soldermaskColor, padColor) {
|
|
333
|
+
const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
|
|
334
|
+
const scaledRadius = radius * Math.abs(realToCanvasMat.a);
|
|
335
|
+
const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
|
|
336
|
+
ctx.save();
|
|
337
|
+
const prevCompositeOp = ctx.globalCompositeOperation;
|
|
338
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
339
|
+
ctx.globalCompositeOperation = "source-atop";
|
|
340
|
+
}
|
|
341
|
+
ctx.beginPath();
|
|
342
|
+
ctx.arc(cx, cy, scaledRadius, 0, Math.PI * 2);
|
|
343
|
+
ctx.fillStyle = soldermaskColor;
|
|
344
|
+
ctx.fill();
|
|
345
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
346
|
+
ctx.globalCompositeOperation = prevCompositeOp || "source-over";
|
|
347
|
+
}
|
|
348
|
+
const innerRadius = Math.max(0, scaledRadius - scaledMargin);
|
|
349
|
+
if (innerRadius > 0) {
|
|
350
|
+
ctx.beginPath();
|
|
351
|
+
ctx.arc(cx, cy, innerRadius, 0, Math.PI * 2);
|
|
352
|
+
ctx.fillStyle = padColor;
|
|
353
|
+
ctx.fill();
|
|
354
|
+
}
|
|
355
|
+
ctx.restore();
|
|
356
|
+
}
|
|
357
|
+
function drawSoldermaskRingForPill(ctx, center, width, height, margin, rotation, realToCanvasMat, soldermaskColor, padColor) {
|
|
358
|
+
const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
|
|
359
|
+
const scaledWidth = width * Math.abs(realToCanvasMat.a);
|
|
360
|
+
const scaledHeight = height * Math.abs(realToCanvasMat.a);
|
|
361
|
+
const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
|
|
362
|
+
ctx.save();
|
|
363
|
+
ctx.translate(cx, cy);
|
|
364
|
+
if (rotation !== 0) {
|
|
365
|
+
ctx.rotate(-rotation * (Math.PI / 180));
|
|
366
|
+
}
|
|
367
|
+
const prevCompositeOp = ctx.globalCompositeOperation;
|
|
368
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
369
|
+
ctx.globalCompositeOperation = "source-atop";
|
|
370
|
+
}
|
|
371
|
+
const outerWidth = scaledWidth;
|
|
372
|
+
const outerHeight = scaledHeight;
|
|
373
|
+
ctx.beginPath();
|
|
374
|
+
if (outerWidth > outerHeight) {
|
|
375
|
+
const radius = outerHeight / 2;
|
|
376
|
+
const straightLength = outerWidth - outerHeight;
|
|
377
|
+
ctx.moveTo(-straightLength / 2, -radius);
|
|
378
|
+
ctx.lineTo(straightLength / 2, -radius);
|
|
379
|
+
ctx.arc(straightLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2);
|
|
380
|
+
ctx.lineTo(-straightLength / 2, radius);
|
|
381
|
+
ctx.arc(-straightLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2);
|
|
382
|
+
} else if (outerHeight > outerWidth) {
|
|
383
|
+
const radius = outerWidth / 2;
|
|
384
|
+
const straightLength = outerHeight - outerWidth;
|
|
385
|
+
ctx.moveTo(radius, -straightLength / 2);
|
|
386
|
+
ctx.lineTo(radius, straightLength / 2);
|
|
387
|
+
ctx.arc(0, straightLength / 2, radius, 0, Math.PI);
|
|
388
|
+
ctx.lineTo(-radius, -straightLength / 2);
|
|
389
|
+
ctx.arc(0, -straightLength / 2, radius, Math.PI, 0);
|
|
390
|
+
} else {
|
|
391
|
+
ctx.arc(0, 0, outerWidth / 2, 0, Math.PI * 2);
|
|
392
|
+
}
|
|
393
|
+
ctx.fillStyle = soldermaskColor;
|
|
394
|
+
ctx.fill();
|
|
395
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
396
|
+
ctx.globalCompositeOperation = prevCompositeOp || "source-over";
|
|
397
|
+
}
|
|
398
|
+
const innerWidth = scaledWidth - scaledMargin * 2;
|
|
399
|
+
const innerHeight = scaledHeight - scaledMargin * 2;
|
|
400
|
+
if (innerWidth > 0 && innerHeight > 0) {
|
|
401
|
+
ctx.beginPath();
|
|
402
|
+
if (innerWidth > innerHeight) {
|
|
403
|
+
const radius = innerHeight / 2;
|
|
404
|
+
const straightLength = innerWidth - innerHeight;
|
|
405
|
+
ctx.moveTo(-straightLength / 2, -radius);
|
|
406
|
+
ctx.lineTo(straightLength / 2, -radius);
|
|
407
|
+
ctx.arc(straightLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2);
|
|
408
|
+
ctx.lineTo(-straightLength / 2, radius);
|
|
409
|
+
ctx.arc(-straightLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2);
|
|
410
|
+
} else if (innerHeight > innerWidth) {
|
|
411
|
+
const radius = innerWidth / 2;
|
|
412
|
+
const straightLength = innerHeight - innerWidth;
|
|
413
|
+
ctx.moveTo(radius, -straightLength / 2);
|
|
414
|
+
ctx.lineTo(radius, straightLength / 2);
|
|
415
|
+
ctx.arc(0, straightLength / 2, radius, 0, Math.PI);
|
|
416
|
+
ctx.lineTo(-radius, -straightLength / 2);
|
|
417
|
+
ctx.arc(0, -straightLength / 2, radius, Math.PI, 0);
|
|
418
|
+
} else {
|
|
419
|
+
ctx.arc(0, 0, innerWidth / 2, 0, Math.PI * 2);
|
|
420
|
+
}
|
|
421
|
+
ctx.fillStyle = padColor;
|
|
422
|
+
ctx.fill();
|
|
423
|
+
}
|
|
424
|
+
ctx.restore();
|
|
425
|
+
}
|
|
426
|
+
function drawSoldermaskRingForOval(ctx, center, radius_x, radius_y, margin, rotation, realToCanvasMat, soldermaskColor, holeColor) {
|
|
427
|
+
const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
|
|
428
|
+
const scaledRadiusX = radius_x * Math.abs(realToCanvasMat.a);
|
|
429
|
+
const scaledRadiusY = radius_y * Math.abs(realToCanvasMat.a);
|
|
430
|
+
const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
|
|
431
|
+
ctx.save();
|
|
432
|
+
ctx.translate(cx, cy);
|
|
433
|
+
if (rotation !== 0) {
|
|
434
|
+
ctx.rotate(-rotation * (Math.PI / 180));
|
|
435
|
+
}
|
|
436
|
+
const prevCompositeOp = ctx.globalCompositeOperation;
|
|
437
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
438
|
+
ctx.globalCompositeOperation = "source-atop";
|
|
439
|
+
}
|
|
440
|
+
ctx.beginPath();
|
|
441
|
+
ctx.ellipse(0, 0, scaledRadiusX, scaledRadiusY, 0, 0, Math.PI * 2);
|
|
442
|
+
ctx.fillStyle = soldermaskColor;
|
|
443
|
+
ctx.fill();
|
|
444
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
445
|
+
ctx.globalCompositeOperation = prevCompositeOp || "source-over";
|
|
446
|
+
}
|
|
447
|
+
const innerRadiusX = Math.max(0, scaledRadiusX - scaledMargin);
|
|
448
|
+
const innerRadiusY = Math.max(0, scaledRadiusY - scaledMargin);
|
|
449
|
+
if (innerRadiusX > 0 && innerRadiusY > 0) {
|
|
450
|
+
ctx.beginPath();
|
|
451
|
+
ctx.ellipse(0, 0, innerRadiusX, innerRadiusY, 0, 0, Math.PI * 2);
|
|
452
|
+
ctx.fillStyle = holeColor;
|
|
453
|
+
ctx.fill();
|
|
454
|
+
}
|
|
455
|
+
ctx.restore();
|
|
456
|
+
}
|
|
457
|
+
|
|
252
458
|
// lib/drawer/elements/pcb-plated-hole.ts
|
|
459
|
+
function getSoldermaskColor(layers, colorMap) {
|
|
460
|
+
const layer = layers?.includes("top") ? "top" : "bottom";
|
|
461
|
+
return colorMap.soldermaskOverCopper[layer] ?? colorMap.soldermaskOverCopper.top;
|
|
462
|
+
}
|
|
253
463
|
function drawPcbPlatedHole(params) {
|
|
254
464
|
const { ctx, hole, realToCanvasMat, colorMap } = params;
|
|
465
|
+
const hasSoldermask = hole.is_covered_with_solder_mask === true && hole.soldermask_margin !== void 0 && hole.soldermask_margin !== 0;
|
|
466
|
+
const margin = hasSoldermask ? hole.soldermask_margin : 0;
|
|
467
|
+
const soldermaskRingColor = getSoldermaskColor(hole.layers, colorMap);
|
|
468
|
+
const positiveMarginColor = colorMap.substrate;
|
|
469
|
+
const copperColor = colorMap.copper.top;
|
|
255
470
|
if (hole.shape === "circle") {
|
|
471
|
+
if (hasSoldermask && margin > 0) {
|
|
472
|
+
drawCircle({
|
|
473
|
+
ctx,
|
|
474
|
+
center: { x: hole.x, y: hole.y },
|
|
475
|
+
radius: hole.outer_diameter / 2 + margin,
|
|
476
|
+
fill: positiveMarginColor,
|
|
477
|
+
realToCanvasMat
|
|
478
|
+
});
|
|
479
|
+
}
|
|
256
480
|
drawCircle({
|
|
257
481
|
ctx,
|
|
258
482
|
center: { x: hole.x, y: hole.y },
|
|
259
483
|
radius: hole.outer_diameter / 2,
|
|
260
|
-
fill:
|
|
484
|
+
fill: copperColor,
|
|
261
485
|
realToCanvasMat
|
|
262
486
|
});
|
|
487
|
+
if (hasSoldermask && margin < 0) {
|
|
488
|
+
drawSoldermaskRingForCircle(
|
|
489
|
+
ctx,
|
|
490
|
+
{ x: hole.x, y: hole.y },
|
|
491
|
+
hole.outer_diameter / 2,
|
|
492
|
+
margin,
|
|
493
|
+
realToCanvasMat,
|
|
494
|
+
soldermaskRingColor,
|
|
495
|
+
copperColor
|
|
496
|
+
);
|
|
497
|
+
}
|
|
263
498
|
drawCircle({
|
|
264
499
|
ctx,
|
|
265
500
|
center: { x: hole.x, y: hole.y },
|
|
@@ -270,15 +505,39 @@ function drawPcbPlatedHole(params) {
|
|
|
270
505
|
return;
|
|
271
506
|
}
|
|
272
507
|
if (hole.shape === "oval") {
|
|
508
|
+
if (hasSoldermask && margin > 0) {
|
|
509
|
+
drawOval({
|
|
510
|
+
ctx,
|
|
511
|
+
center: { x: hole.x, y: hole.y },
|
|
512
|
+
radius_x: hole.outer_width / 2 + margin,
|
|
513
|
+
radius_y: hole.outer_height / 2 + margin,
|
|
514
|
+
fill: positiveMarginColor,
|
|
515
|
+
realToCanvasMat,
|
|
516
|
+
rotation: hole.ccw_rotation
|
|
517
|
+
});
|
|
518
|
+
}
|
|
273
519
|
drawOval({
|
|
274
520
|
ctx,
|
|
275
521
|
center: { x: hole.x, y: hole.y },
|
|
276
522
|
radius_x: hole.outer_width / 2,
|
|
277
523
|
radius_y: hole.outer_height / 2,
|
|
278
|
-
fill:
|
|
524
|
+
fill: copperColor,
|
|
279
525
|
realToCanvasMat,
|
|
280
526
|
rotation: hole.ccw_rotation
|
|
281
527
|
});
|
|
528
|
+
if (hasSoldermask && margin < 0) {
|
|
529
|
+
drawSoldermaskRingForOval(
|
|
530
|
+
ctx,
|
|
531
|
+
{ x: hole.x, y: hole.y },
|
|
532
|
+
hole.outer_width / 2,
|
|
533
|
+
hole.outer_height / 2,
|
|
534
|
+
margin,
|
|
535
|
+
hole.ccw_rotation ?? 0,
|
|
536
|
+
realToCanvasMat,
|
|
537
|
+
soldermaskRingColor,
|
|
538
|
+
copperColor
|
|
539
|
+
);
|
|
540
|
+
}
|
|
282
541
|
drawOval({
|
|
283
542
|
ctx,
|
|
284
543
|
center: { x: hole.x, y: hole.y },
|
|
@@ -291,15 +550,39 @@ function drawPcbPlatedHole(params) {
|
|
|
291
550
|
return;
|
|
292
551
|
}
|
|
293
552
|
if (hole.shape === "pill") {
|
|
553
|
+
if (hasSoldermask && margin > 0) {
|
|
554
|
+
drawPill({
|
|
555
|
+
ctx,
|
|
556
|
+
center: { x: hole.x, y: hole.y },
|
|
557
|
+
width: hole.outer_width + margin * 2,
|
|
558
|
+
height: hole.outer_height + margin * 2,
|
|
559
|
+
fill: positiveMarginColor,
|
|
560
|
+
realToCanvasMat,
|
|
561
|
+
rotation: hole.ccw_rotation
|
|
562
|
+
});
|
|
563
|
+
}
|
|
294
564
|
drawPill({
|
|
295
565
|
ctx,
|
|
296
566
|
center: { x: hole.x, y: hole.y },
|
|
297
567
|
width: hole.outer_width,
|
|
298
568
|
height: hole.outer_height,
|
|
299
|
-
fill:
|
|
569
|
+
fill: copperColor,
|
|
300
570
|
realToCanvasMat,
|
|
301
571
|
rotation: hole.ccw_rotation
|
|
302
572
|
});
|
|
573
|
+
if (hasSoldermask && margin < 0) {
|
|
574
|
+
drawSoldermaskRingForPill(
|
|
575
|
+
ctx,
|
|
576
|
+
{ x: hole.x, y: hole.y },
|
|
577
|
+
hole.outer_width,
|
|
578
|
+
hole.outer_height,
|
|
579
|
+
margin,
|
|
580
|
+
hole.ccw_rotation ?? 0,
|
|
581
|
+
realToCanvasMat,
|
|
582
|
+
soldermaskRingColor,
|
|
583
|
+
copperColor
|
|
584
|
+
);
|
|
585
|
+
}
|
|
303
586
|
drawPill({
|
|
304
587
|
ctx,
|
|
305
588
|
center: { x: hole.x, y: hole.y },
|
|
@@ -312,15 +595,40 @@ function drawPcbPlatedHole(params) {
|
|
|
312
595
|
return;
|
|
313
596
|
}
|
|
314
597
|
if (hole.shape === "circular_hole_with_rect_pad") {
|
|
598
|
+
if (hasSoldermask && margin > 0) {
|
|
599
|
+
drawRect({
|
|
600
|
+
ctx,
|
|
601
|
+
center: { x: hole.x, y: hole.y },
|
|
602
|
+
width: hole.rect_pad_width + margin * 2,
|
|
603
|
+
height: hole.rect_pad_height + margin * 2,
|
|
604
|
+
fill: positiveMarginColor,
|
|
605
|
+
realToCanvasMat,
|
|
606
|
+
borderRadius: (hole.rect_border_radius ?? 0) + margin
|
|
607
|
+
});
|
|
608
|
+
}
|
|
315
609
|
drawRect({
|
|
316
610
|
ctx,
|
|
317
611
|
center: { x: hole.x, y: hole.y },
|
|
318
612
|
width: hole.rect_pad_width,
|
|
319
613
|
height: hole.rect_pad_height,
|
|
320
|
-
fill:
|
|
614
|
+
fill: copperColor,
|
|
321
615
|
realToCanvasMat,
|
|
322
616
|
borderRadius: hole.rect_border_radius ?? 0
|
|
323
617
|
});
|
|
618
|
+
if (hasSoldermask && margin < 0) {
|
|
619
|
+
drawSoldermaskRingForRect(
|
|
620
|
+
ctx,
|
|
621
|
+
{ x: hole.x, y: hole.y },
|
|
622
|
+
hole.rect_pad_width,
|
|
623
|
+
hole.rect_pad_height,
|
|
624
|
+
margin,
|
|
625
|
+
hole.rect_border_radius ?? 0,
|
|
626
|
+
0,
|
|
627
|
+
realToCanvasMat,
|
|
628
|
+
soldermaskRingColor,
|
|
629
|
+
copperColor
|
|
630
|
+
);
|
|
631
|
+
}
|
|
324
632
|
const holeX = hole.x + (hole.hole_offset_x ?? 0);
|
|
325
633
|
const holeY = hole.y + (hole.hole_offset_y ?? 0);
|
|
326
634
|
drawCircle({
|
|
@@ -333,15 +641,40 @@ function drawPcbPlatedHole(params) {
|
|
|
333
641
|
return;
|
|
334
642
|
}
|
|
335
643
|
if (hole.shape === "pill_hole_with_rect_pad") {
|
|
644
|
+
if (hasSoldermask && margin > 0) {
|
|
645
|
+
drawRect({
|
|
646
|
+
ctx,
|
|
647
|
+
center: { x: hole.x, y: hole.y },
|
|
648
|
+
width: hole.rect_pad_width + margin * 2,
|
|
649
|
+
height: hole.rect_pad_height + margin * 2,
|
|
650
|
+
fill: positiveMarginColor,
|
|
651
|
+
realToCanvasMat,
|
|
652
|
+
borderRadius: (hole.rect_border_radius ?? 0) + margin
|
|
653
|
+
});
|
|
654
|
+
}
|
|
336
655
|
drawRect({
|
|
337
656
|
ctx,
|
|
338
657
|
center: { x: hole.x, y: hole.y },
|
|
339
658
|
width: hole.rect_pad_width,
|
|
340
659
|
height: hole.rect_pad_height,
|
|
341
|
-
fill:
|
|
660
|
+
fill: copperColor,
|
|
342
661
|
realToCanvasMat,
|
|
343
662
|
borderRadius: hole.rect_border_radius ?? 0
|
|
344
663
|
});
|
|
664
|
+
if (hasSoldermask && margin < 0) {
|
|
665
|
+
drawSoldermaskRingForRect(
|
|
666
|
+
ctx,
|
|
667
|
+
{ x: hole.x, y: hole.y },
|
|
668
|
+
hole.rect_pad_width,
|
|
669
|
+
hole.rect_pad_height,
|
|
670
|
+
margin,
|
|
671
|
+
hole.rect_border_radius ?? 0,
|
|
672
|
+
0,
|
|
673
|
+
realToCanvasMat,
|
|
674
|
+
soldermaskRingColor,
|
|
675
|
+
copperColor
|
|
676
|
+
);
|
|
677
|
+
}
|
|
345
678
|
const holeX = hole.x + (hole.hole_offset_x ?? 0);
|
|
346
679
|
const holeY = hole.y + (hole.hole_offset_y ?? 0);
|
|
347
680
|
drawPill({
|
|
@@ -355,16 +688,42 @@ function drawPcbPlatedHole(params) {
|
|
|
355
688
|
return;
|
|
356
689
|
}
|
|
357
690
|
if (hole.shape === "rotated_pill_hole_with_rect_pad") {
|
|
691
|
+
if (hasSoldermask && margin > 0) {
|
|
692
|
+
drawRect({
|
|
693
|
+
ctx,
|
|
694
|
+
center: { x: hole.x, y: hole.y },
|
|
695
|
+
width: hole.rect_pad_width + margin * 2,
|
|
696
|
+
height: hole.rect_pad_height + margin * 2,
|
|
697
|
+
fill: positiveMarginColor,
|
|
698
|
+
realToCanvasMat,
|
|
699
|
+
borderRadius: (hole.rect_border_radius ?? 0) + margin,
|
|
700
|
+
rotation: hole.rect_ccw_rotation
|
|
701
|
+
});
|
|
702
|
+
}
|
|
358
703
|
drawRect({
|
|
359
704
|
ctx,
|
|
360
705
|
center: { x: hole.x, y: hole.y },
|
|
361
706
|
width: hole.rect_pad_width,
|
|
362
707
|
height: hole.rect_pad_height,
|
|
363
|
-
fill:
|
|
708
|
+
fill: copperColor,
|
|
364
709
|
realToCanvasMat,
|
|
365
710
|
borderRadius: hole.rect_border_radius ?? 0,
|
|
366
711
|
rotation: hole.rect_ccw_rotation
|
|
367
712
|
});
|
|
713
|
+
if (hasSoldermask && margin < 0) {
|
|
714
|
+
drawSoldermaskRingForRect(
|
|
715
|
+
ctx,
|
|
716
|
+
{ x: hole.x, y: hole.y },
|
|
717
|
+
hole.rect_pad_width,
|
|
718
|
+
hole.rect_pad_height,
|
|
719
|
+
margin,
|
|
720
|
+
hole.rect_border_radius ?? 0,
|
|
721
|
+
hole.rect_ccw_rotation ?? 0,
|
|
722
|
+
realToCanvasMat,
|
|
723
|
+
soldermaskRingColor,
|
|
724
|
+
copperColor
|
|
725
|
+
);
|
|
726
|
+
}
|
|
368
727
|
const holeX = hole.x + (hole.hole_offset_x ?? 0);
|
|
369
728
|
const holeY = hole.y + (hole.hole_offset_y ?? 0);
|
|
370
729
|
drawPill({
|
|
@@ -388,7 +747,7 @@ function drawPcbPlatedHole(params) {
|
|
|
388
747
|
drawPolygon({
|
|
389
748
|
ctx,
|
|
390
749
|
points: padPoints,
|
|
391
|
-
fill:
|
|
750
|
+
fill: copperColor,
|
|
392
751
|
realToCanvasMat
|
|
393
752
|
});
|
|
394
753
|
}
|
|
@@ -589,217 +948,11 @@ function drawPcbHole(params) {
|
|
|
589
948
|
}
|
|
590
949
|
}
|
|
591
950
|
|
|
592
|
-
// lib/drawer/elements/soldermask-margin.ts
|
|
593
|
-
import { applyToPoint as applyToPoint6 } from "transformation-matrix";
|
|
594
|
-
function drawSoldermaskRingForRect(ctx, center, width, height, margin, borderRadius, rotation, realToCanvasMat, soldermaskColor, padColor) {
|
|
595
|
-
const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
|
|
596
|
-
const scaledWidth = width * Math.abs(realToCanvasMat.a);
|
|
597
|
-
const scaledHeight = height * Math.abs(realToCanvasMat.a);
|
|
598
|
-
const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
|
|
599
|
-
const scaledRadius = borderRadius * Math.abs(realToCanvasMat.a);
|
|
600
|
-
ctx.save();
|
|
601
|
-
ctx.translate(cx, cy);
|
|
602
|
-
if (rotation !== 0) {
|
|
603
|
-
ctx.rotate(-rotation * (Math.PI / 180));
|
|
604
|
-
}
|
|
605
|
-
const prevCompositeOp = ctx.globalCompositeOperation;
|
|
606
|
-
if (ctx.globalCompositeOperation !== void 0) {
|
|
607
|
-
ctx.globalCompositeOperation = "source-atop";
|
|
608
|
-
}
|
|
609
|
-
const outerWidth = scaledWidth;
|
|
610
|
-
const outerHeight = scaledHeight;
|
|
611
|
-
const outerRadius = scaledRadius;
|
|
612
|
-
ctx.beginPath();
|
|
613
|
-
if (outerRadius > 0) {
|
|
614
|
-
const x = -outerWidth / 2;
|
|
615
|
-
const y = -outerHeight / 2;
|
|
616
|
-
const r = Math.min(outerRadius, outerWidth / 2, outerHeight / 2);
|
|
617
|
-
ctx.moveTo(x + r, y);
|
|
618
|
-
ctx.lineTo(x + outerWidth - r, y);
|
|
619
|
-
ctx.arcTo(x + outerWidth, y, x + outerWidth, y + r, r);
|
|
620
|
-
ctx.lineTo(x + outerWidth, y + outerHeight - r);
|
|
621
|
-
ctx.arcTo(
|
|
622
|
-
x + outerWidth,
|
|
623
|
-
y + outerHeight,
|
|
624
|
-
x + outerWidth - r,
|
|
625
|
-
y + outerHeight,
|
|
626
|
-
r
|
|
627
|
-
);
|
|
628
|
-
ctx.lineTo(x + r, y + outerHeight);
|
|
629
|
-
ctx.arcTo(x, y + outerHeight, x, y + outerHeight - r, r);
|
|
630
|
-
ctx.lineTo(x, y + r);
|
|
631
|
-
ctx.arcTo(x, y, x + r, y, r);
|
|
632
|
-
} else {
|
|
633
|
-
ctx.rect(-outerWidth / 2, -outerHeight / 2, outerWidth, outerHeight);
|
|
634
|
-
}
|
|
635
|
-
ctx.fillStyle = soldermaskColor;
|
|
636
|
-
ctx.fill();
|
|
637
|
-
if (ctx.globalCompositeOperation !== void 0) {
|
|
638
|
-
ctx.globalCompositeOperation = prevCompositeOp || "source-over";
|
|
639
|
-
}
|
|
640
|
-
const innerWidth = scaledWidth - scaledMargin * 2;
|
|
641
|
-
const innerHeight = scaledHeight - scaledMargin * 2;
|
|
642
|
-
const innerRadius = Math.max(0, scaledRadius - scaledMargin);
|
|
643
|
-
if (innerWidth > 0 && innerHeight > 0) {
|
|
644
|
-
ctx.beginPath();
|
|
645
|
-
if (innerRadius > 0) {
|
|
646
|
-
const x = -innerWidth / 2;
|
|
647
|
-
const y = -innerHeight / 2;
|
|
648
|
-
const r = Math.min(innerRadius, innerWidth / 2, innerHeight / 2);
|
|
649
|
-
ctx.moveTo(x + r, y);
|
|
650
|
-
ctx.lineTo(x + innerWidth - r, y);
|
|
651
|
-
ctx.arcTo(x + innerWidth, y, x + innerWidth, y + r, r);
|
|
652
|
-
ctx.lineTo(x + innerWidth, y + innerHeight - r);
|
|
653
|
-
ctx.arcTo(
|
|
654
|
-
x + innerWidth,
|
|
655
|
-
y + innerHeight,
|
|
656
|
-
x + innerWidth - r,
|
|
657
|
-
y + innerHeight,
|
|
658
|
-
r
|
|
659
|
-
);
|
|
660
|
-
ctx.lineTo(x + r, y + innerHeight);
|
|
661
|
-
ctx.arcTo(x, y + innerHeight, x, y + innerHeight - r, r);
|
|
662
|
-
ctx.lineTo(x, y + r);
|
|
663
|
-
ctx.arcTo(x, y, x + r, y, r);
|
|
664
|
-
} else {
|
|
665
|
-
ctx.rect(-innerWidth / 2, -innerHeight / 2, innerWidth, innerHeight);
|
|
666
|
-
}
|
|
667
|
-
ctx.fillStyle = padColor;
|
|
668
|
-
ctx.fill();
|
|
669
|
-
}
|
|
670
|
-
ctx.restore();
|
|
671
|
-
}
|
|
672
|
-
function drawSoldermaskRingForCircle(ctx, center, radius, margin, realToCanvasMat, soldermaskColor, padColor) {
|
|
673
|
-
const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
|
|
674
|
-
const scaledRadius = radius * Math.abs(realToCanvasMat.a);
|
|
675
|
-
const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
|
|
676
|
-
ctx.save();
|
|
677
|
-
const prevCompositeOp = ctx.globalCompositeOperation;
|
|
678
|
-
if (ctx.globalCompositeOperation !== void 0) {
|
|
679
|
-
ctx.globalCompositeOperation = "source-atop";
|
|
680
|
-
}
|
|
681
|
-
ctx.beginPath();
|
|
682
|
-
ctx.arc(cx, cy, scaledRadius, 0, Math.PI * 2);
|
|
683
|
-
ctx.fillStyle = soldermaskColor;
|
|
684
|
-
ctx.fill();
|
|
685
|
-
if (ctx.globalCompositeOperation !== void 0) {
|
|
686
|
-
ctx.globalCompositeOperation = prevCompositeOp || "source-over";
|
|
687
|
-
}
|
|
688
|
-
const innerRadius = Math.max(0, scaledRadius - scaledMargin);
|
|
689
|
-
if (innerRadius > 0) {
|
|
690
|
-
ctx.beginPath();
|
|
691
|
-
ctx.arc(cx, cy, innerRadius, 0, Math.PI * 2);
|
|
692
|
-
ctx.fillStyle = padColor;
|
|
693
|
-
ctx.fill();
|
|
694
|
-
}
|
|
695
|
-
ctx.restore();
|
|
696
|
-
}
|
|
697
|
-
function drawSoldermaskRingForPill(ctx, center, width, height, margin, rotation, realToCanvasMat, soldermaskColor, padColor) {
|
|
698
|
-
const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
|
|
699
|
-
const scaledWidth = width * Math.abs(realToCanvasMat.a);
|
|
700
|
-
const scaledHeight = height * Math.abs(realToCanvasMat.a);
|
|
701
|
-
const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
|
|
702
|
-
ctx.save();
|
|
703
|
-
ctx.translate(cx, cy);
|
|
704
|
-
if (rotation !== 0) {
|
|
705
|
-
ctx.rotate(-rotation * (Math.PI / 180));
|
|
706
|
-
}
|
|
707
|
-
const prevCompositeOp = ctx.globalCompositeOperation;
|
|
708
|
-
if (ctx.globalCompositeOperation !== void 0) {
|
|
709
|
-
ctx.globalCompositeOperation = "source-atop";
|
|
710
|
-
}
|
|
711
|
-
const outerWidth = scaledWidth;
|
|
712
|
-
const outerHeight = scaledHeight;
|
|
713
|
-
ctx.beginPath();
|
|
714
|
-
if (outerWidth > outerHeight) {
|
|
715
|
-
const radius = outerHeight / 2;
|
|
716
|
-
const straightLength = outerWidth - outerHeight;
|
|
717
|
-
ctx.moveTo(-straightLength / 2, -radius);
|
|
718
|
-
ctx.lineTo(straightLength / 2, -radius);
|
|
719
|
-
ctx.arc(straightLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2);
|
|
720
|
-
ctx.lineTo(-straightLength / 2, radius);
|
|
721
|
-
ctx.arc(-straightLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2);
|
|
722
|
-
} else if (outerHeight > outerWidth) {
|
|
723
|
-
const radius = outerWidth / 2;
|
|
724
|
-
const straightLength = outerHeight - outerWidth;
|
|
725
|
-
ctx.moveTo(radius, -straightLength / 2);
|
|
726
|
-
ctx.lineTo(radius, straightLength / 2);
|
|
727
|
-
ctx.arc(0, straightLength / 2, radius, 0, Math.PI);
|
|
728
|
-
ctx.lineTo(-radius, -straightLength / 2);
|
|
729
|
-
ctx.arc(0, -straightLength / 2, radius, Math.PI, 0);
|
|
730
|
-
} else {
|
|
731
|
-
ctx.arc(0, 0, outerWidth / 2, 0, Math.PI * 2);
|
|
732
|
-
}
|
|
733
|
-
ctx.fillStyle = soldermaskColor;
|
|
734
|
-
ctx.fill();
|
|
735
|
-
if (ctx.globalCompositeOperation !== void 0) {
|
|
736
|
-
ctx.globalCompositeOperation = prevCompositeOp || "source-over";
|
|
737
|
-
}
|
|
738
|
-
const innerWidth = scaledWidth - scaledMargin * 2;
|
|
739
|
-
const innerHeight = scaledHeight - scaledMargin * 2;
|
|
740
|
-
if (innerWidth > 0 && innerHeight > 0) {
|
|
741
|
-
ctx.beginPath();
|
|
742
|
-
if (innerWidth > innerHeight) {
|
|
743
|
-
const radius = innerHeight / 2;
|
|
744
|
-
const straightLength = innerWidth - innerHeight;
|
|
745
|
-
ctx.moveTo(-straightLength / 2, -radius);
|
|
746
|
-
ctx.lineTo(straightLength / 2, -radius);
|
|
747
|
-
ctx.arc(straightLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2);
|
|
748
|
-
ctx.lineTo(-straightLength / 2, radius);
|
|
749
|
-
ctx.arc(-straightLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2);
|
|
750
|
-
} else if (innerHeight > innerWidth) {
|
|
751
|
-
const radius = innerWidth / 2;
|
|
752
|
-
const straightLength = innerHeight - innerWidth;
|
|
753
|
-
ctx.moveTo(radius, -straightLength / 2);
|
|
754
|
-
ctx.lineTo(radius, straightLength / 2);
|
|
755
|
-
ctx.arc(0, straightLength / 2, radius, 0, Math.PI);
|
|
756
|
-
ctx.lineTo(-radius, -straightLength / 2);
|
|
757
|
-
ctx.arc(0, -straightLength / 2, radius, Math.PI, 0);
|
|
758
|
-
} else {
|
|
759
|
-
ctx.arc(0, 0, innerWidth / 2, 0, Math.PI * 2);
|
|
760
|
-
}
|
|
761
|
-
ctx.fillStyle = padColor;
|
|
762
|
-
ctx.fill();
|
|
763
|
-
}
|
|
764
|
-
ctx.restore();
|
|
765
|
-
}
|
|
766
|
-
function drawSoldermaskRingForOval(ctx, center, radius_x, radius_y, margin, rotation, realToCanvasMat, soldermaskColor, holeColor) {
|
|
767
|
-
const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
|
|
768
|
-
const scaledRadiusX = radius_x * Math.abs(realToCanvasMat.a);
|
|
769
|
-
const scaledRadiusY = radius_y * Math.abs(realToCanvasMat.a);
|
|
770
|
-
const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
|
|
771
|
-
ctx.save();
|
|
772
|
-
ctx.translate(cx, cy);
|
|
773
|
-
if (rotation !== 0) {
|
|
774
|
-
ctx.rotate(-rotation * (Math.PI / 180));
|
|
775
|
-
}
|
|
776
|
-
const prevCompositeOp = ctx.globalCompositeOperation;
|
|
777
|
-
if (ctx.globalCompositeOperation !== void 0) {
|
|
778
|
-
ctx.globalCompositeOperation = "source-atop";
|
|
779
|
-
}
|
|
780
|
-
ctx.beginPath();
|
|
781
|
-
ctx.ellipse(0, 0, scaledRadiusX, scaledRadiusY, 0, 0, Math.PI * 2);
|
|
782
|
-
ctx.fillStyle = soldermaskColor;
|
|
783
|
-
ctx.fill();
|
|
784
|
-
if (ctx.globalCompositeOperation !== void 0) {
|
|
785
|
-
ctx.globalCompositeOperation = prevCompositeOp || "source-over";
|
|
786
|
-
}
|
|
787
|
-
const innerRadiusX = Math.max(0, scaledRadiusX - scaledMargin);
|
|
788
|
-
const innerRadiusY = Math.max(0, scaledRadiusY - scaledMargin);
|
|
789
|
-
if (innerRadiusX > 0 && innerRadiusY > 0) {
|
|
790
|
-
ctx.beginPath();
|
|
791
|
-
ctx.ellipse(0, 0, innerRadiusX, innerRadiusY, 0, 0, Math.PI * 2);
|
|
792
|
-
ctx.fillStyle = holeColor;
|
|
793
|
-
ctx.fill();
|
|
794
|
-
}
|
|
795
|
-
ctx.restore();
|
|
796
|
-
}
|
|
797
|
-
|
|
798
951
|
// lib/drawer/elements/pcb-smtpad.ts
|
|
799
952
|
function layerToColor(layer, colorMap) {
|
|
800
953
|
return colorMap.copper[layer] ?? colorMap.copper.top;
|
|
801
954
|
}
|
|
802
|
-
function
|
|
955
|
+
function getSoldermaskColor2(layer, colorMap) {
|
|
803
956
|
return colorMap.soldermaskOverCopper[layer] ?? colorMap.soldermaskOverCopper.top;
|
|
804
957
|
}
|
|
805
958
|
function drawPcbSmtPad(params) {
|
|
@@ -807,7 +960,7 @@ function drawPcbSmtPad(params) {
|
|
|
807
960
|
const color = layerToColor(pad.layer, colorMap);
|
|
808
961
|
const hasSoldermask = pad.is_covered_with_solder_mask === true && pad.soldermask_margin !== void 0 && pad.soldermask_margin !== 0;
|
|
809
962
|
const margin = hasSoldermask ? pad.soldermask_margin : 0;
|
|
810
|
-
const soldermaskRingColor =
|
|
963
|
+
const soldermaskRingColor = getSoldermaskColor2(pad.layer, colorMap);
|
|
811
964
|
const positiveMarginColor = colorMap.substrate;
|
|
812
965
|
if (pad.shape === "rect") {
|
|
813
966
|
if (hasSoldermask && margin > 0) {
|
|
@@ -2037,8 +2190,11 @@ var CircuitToCanvasDrawer = class {
|
|
|
2037
2190
|
const hasSoldermaskHoles = elements.some(
|
|
2038
2191
|
(el) => el.type === "pcb_hole" && el.is_covered_with_solder_mask === true
|
|
2039
2192
|
);
|
|
2193
|
+
const hasSoldermaskPlatedHoles = elements.some(
|
|
2194
|
+
(el) => el.type === "pcb_plated_hole" && el.is_covered_with_solder_mask === true
|
|
2195
|
+
);
|
|
2040
2196
|
for (const element of elements) {
|
|
2041
|
-
if (element.type === "pcb_board" && (hasSoldermaskPads || hasSoldermaskHoles)) {
|
|
2197
|
+
if (element.type === "pcb_board" && (hasSoldermaskPads || hasSoldermaskHoles || hasSoldermaskPlatedHoles)) {
|
|
2042
2198
|
this.drawBoardWithSoldermask(element);
|
|
2043
2199
|
} else {
|
|
2044
2200
|
this.drawElement(element, options);
|
|
@@ -172,11 +172,17 @@ export class CircuitToCanvasDrawer {
|
|
|
172
172
|
(el as PcbHole & { is_covered_with_solder_mask?: boolean })
|
|
173
173
|
.is_covered_with_solder_mask === true,
|
|
174
174
|
)
|
|
175
|
+
const hasSoldermaskPlatedHoles = elements.some(
|
|
176
|
+
(el) =>
|
|
177
|
+
el.type === "pcb_plated_hole" &&
|
|
178
|
+
(el as PcbPlatedHole & { is_covered_with_solder_mask?: boolean })
|
|
179
|
+
.is_covered_with_solder_mask === true,
|
|
180
|
+
)
|
|
175
181
|
|
|
176
182
|
for (const element of elements) {
|
|
177
183
|
if (
|
|
178
184
|
element.type === "pcb_board" &&
|
|
179
|
-
(hasSoldermaskPads || hasSoldermaskHoles)
|
|
185
|
+
(hasSoldermaskPads || hasSoldermaskHoles || hasSoldermaskPlatedHoles)
|
|
180
186
|
) {
|
|
181
187
|
// Draw board with soldermask fill when pads or holes have soldermask
|
|
182
188
|
this.drawBoardWithSoldermask(element as PcbBoard)
|
|
@@ -6,6 +6,12 @@ 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
|
+
} from "./soldermask-margin"
|
|
9
15
|
|
|
10
16
|
export interface DrawPcbPlatedHoleParams {
|
|
11
17
|
ctx: CanvasContext
|
|
@@ -14,19 +20,64 @@ export interface DrawPcbPlatedHoleParams {
|
|
|
14
20
|
colorMap: PcbColorMap
|
|
15
21
|
}
|
|
16
22
|
|
|
23
|
+
function getSoldermaskColor(
|
|
24
|
+
layers: string[] | undefined,
|
|
25
|
+
colorMap: PcbColorMap,
|
|
26
|
+
): string {
|
|
27
|
+
const layer = layers?.includes("top") ? "top" : "bottom"
|
|
28
|
+
return (
|
|
29
|
+
colorMap.soldermaskOverCopper[
|
|
30
|
+
layer as keyof typeof colorMap.soldermaskOverCopper
|
|
31
|
+
] ?? colorMap.soldermaskOverCopper.top
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
17
35
|
export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
|
|
18
36
|
const { ctx, hole, realToCanvasMat, colorMap } = params
|
|
19
37
|
|
|
38
|
+
const hasSoldermask =
|
|
39
|
+
hole.is_covered_with_solder_mask === true &&
|
|
40
|
+
hole.soldermask_margin !== undefined &&
|
|
41
|
+
hole.soldermask_margin !== 0
|
|
42
|
+
const margin = hasSoldermask ? hole.soldermask_margin! : 0
|
|
43
|
+
const soldermaskRingColor = getSoldermaskColor(hole.layers, colorMap)
|
|
44
|
+
const positiveMarginColor = colorMap.substrate
|
|
45
|
+
const copperColor = colorMap.copper.top
|
|
46
|
+
|
|
20
47
|
if (hole.shape === "circle") {
|
|
48
|
+
// For positive margins, draw extended mask area first
|
|
49
|
+
if (hasSoldermask && margin > 0) {
|
|
50
|
+
drawCircle({
|
|
51
|
+
ctx,
|
|
52
|
+
center: { x: hole.x, y: hole.y },
|
|
53
|
+
radius: hole.outer_diameter / 2 + margin,
|
|
54
|
+
fill: positiveMarginColor,
|
|
55
|
+
realToCanvasMat,
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
21
59
|
// Draw outer copper ring
|
|
22
60
|
drawCircle({
|
|
23
61
|
ctx,
|
|
24
62
|
center: { x: hole.x, y: hole.y },
|
|
25
63
|
radius: hole.outer_diameter / 2,
|
|
26
|
-
fill:
|
|
64
|
+
fill: copperColor,
|
|
27
65
|
realToCanvasMat,
|
|
28
66
|
})
|
|
29
67
|
|
|
68
|
+
// For negative margins, draw soldermask ring on top of the copper ring
|
|
69
|
+
if (hasSoldermask && margin < 0) {
|
|
70
|
+
drawSoldermaskRingForCircle(
|
|
71
|
+
ctx,
|
|
72
|
+
{ x: hole.x, y: hole.y },
|
|
73
|
+
hole.outer_diameter / 2,
|
|
74
|
+
margin,
|
|
75
|
+
realToCanvasMat,
|
|
76
|
+
soldermaskRingColor,
|
|
77
|
+
copperColor,
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
30
81
|
// Draw inner drill hole
|
|
31
82
|
drawCircle({
|
|
32
83
|
ctx,
|
|
@@ -39,17 +90,45 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
|
|
|
39
90
|
}
|
|
40
91
|
|
|
41
92
|
if (hole.shape === "oval") {
|
|
93
|
+
// For positive margins, draw extended mask area first
|
|
94
|
+
if (hasSoldermask && margin > 0) {
|
|
95
|
+
drawOval({
|
|
96
|
+
ctx,
|
|
97
|
+
center: { x: hole.x, y: hole.y },
|
|
98
|
+
radius_x: hole.outer_width / 2 + margin,
|
|
99
|
+
radius_y: hole.outer_height / 2 + margin,
|
|
100
|
+
fill: positiveMarginColor,
|
|
101
|
+
realToCanvasMat,
|
|
102
|
+
rotation: hole.ccw_rotation,
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
42
106
|
// Draw outer copper oval
|
|
43
107
|
drawOval({
|
|
44
108
|
ctx,
|
|
45
109
|
center: { x: hole.x, y: hole.y },
|
|
46
110
|
radius_x: hole.outer_width / 2,
|
|
47
111
|
radius_y: hole.outer_height / 2,
|
|
48
|
-
fill:
|
|
112
|
+
fill: copperColor,
|
|
49
113
|
realToCanvasMat,
|
|
50
114
|
rotation: hole.ccw_rotation,
|
|
51
115
|
})
|
|
52
116
|
|
|
117
|
+
// For negative margins, draw soldermask ring on top of the copper oval
|
|
118
|
+
if (hasSoldermask && margin < 0) {
|
|
119
|
+
drawSoldermaskRingForOval(
|
|
120
|
+
ctx,
|
|
121
|
+
{ x: hole.x, y: hole.y },
|
|
122
|
+
hole.outer_width / 2,
|
|
123
|
+
hole.outer_height / 2,
|
|
124
|
+
margin,
|
|
125
|
+
hole.ccw_rotation ?? 0,
|
|
126
|
+
realToCanvasMat,
|
|
127
|
+
soldermaskRingColor,
|
|
128
|
+
copperColor,
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
53
132
|
// Draw inner drill hole
|
|
54
133
|
drawOval({
|
|
55
134
|
ctx,
|
|
@@ -64,17 +143,45 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
|
|
|
64
143
|
}
|
|
65
144
|
|
|
66
145
|
if (hole.shape === "pill") {
|
|
146
|
+
// For positive margins, draw extended mask area first
|
|
147
|
+
if (hasSoldermask && margin > 0) {
|
|
148
|
+
drawPill({
|
|
149
|
+
ctx,
|
|
150
|
+
center: { x: hole.x, y: hole.y },
|
|
151
|
+
width: hole.outer_width + margin * 2,
|
|
152
|
+
height: hole.outer_height + margin * 2,
|
|
153
|
+
fill: positiveMarginColor,
|
|
154
|
+
realToCanvasMat,
|
|
155
|
+
rotation: hole.ccw_rotation,
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
67
159
|
// Draw outer copper pill
|
|
68
160
|
drawPill({
|
|
69
161
|
ctx,
|
|
70
162
|
center: { x: hole.x, y: hole.y },
|
|
71
163
|
width: hole.outer_width,
|
|
72
164
|
height: hole.outer_height,
|
|
73
|
-
fill:
|
|
165
|
+
fill: copperColor,
|
|
74
166
|
realToCanvasMat,
|
|
75
167
|
rotation: hole.ccw_rotation,
|
|
76
168
|
})
|
|
77
169
|
|
|
170
|
+
// For negative margins, draw soldermask ring on top of the copper pill
|
|
171
|
+
if (hasSoldermask && margin < 0) {
|
|
172
|
+
drawSoldermaskRingForPill(
|
|
173
|
+
ctx,
|
|
174
|
+
{ x: hole.x, y: hole.y },
|
|
175
|
+
hole.outer_width,
|
|
176
|
+
hole.outer_height,
|
|
177
|
+
margin,
|
|
178
|
+
hole.ccw_rotation ?? 0,
|
|
179
|
+
realToCanvasMat,
|
|
180
|
+
soldermaskRingColor,
|
|
181
|
+
copperColor,
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
78
185
|
// Draw inner drill hole
|
|
79
186
|
drawPill({
|
|
80
187
|
ctx,
|
|
@@ -89,17 +196,46 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
|
|
|
89
196
|
}
|
|
90
197
|
|
|
91
198
|
if (hole.shape === "circular_hole_with_rect_pad") {
|
|
199
|
+
// For positive margins, draw extended mask area first
|
|
200
|
+
if (hasSoldermask && margin > 0) {
|
|
201
|
+
drawRect({
|
|
202
|
+
ctx,
|
|
203
|
+
center: { x: hole.x, y: hole.y },
|
|
204
|
+
width: hole.rect_pad_width + margin * 2,
|
|
205
|
+
height: hole.rect_pad_height + margin * 2,
|
|
206
|
+
fill: positiveMarginColor,
|
|
207
|
+
realToCanvasMat,
|
|
208
|
+
borderRadius: (hole.rect_border_radius ?? 0) + margin,
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
|
|
92
212
|
// Draw rectangular pad
|
|
93
213
|
drawRect({
|
|
94
214
|
ctx,
|
|
95
215
|
center: { x: hole.x, y: hole.y },
|
|
96
216
|
width: hole.rect_pad_width,
|
|
97
217
|
height: hole.rect_pad_height,
|
|
98
|
-
fill:
|
|
218
|
+
fill: copperColor,
|
|
99
219
|
realToCanvasMat,
|
|
100
220
|
borderRadius: hole.rect_border_radius ?? 0,
|
|
101
221
|
})
|
|
102
222
|
|
|
223
|
+
// For negative margins, draw soldermask ring on top of the pad
|
|
224
|
+
if (hasSoldermask && margin < 0) {
|
|
225
|
+
drawSoldermaskRingForRect(
|
|
226
|
+
ctx,
|
|
227
|
+
{ x: hole.x, y: hole.y },
|
|
228
|
+
hole.rect_pad_width,
|
|
229
|
+
hole.rect_pad_height,
|
|
230
|
+
margin,
|
|
231
|
+
hole.rect_border_radius ?? 0,
|
|
232
|
+
0,
|
|
233
|
+
realToCanvasMat,
|
|
234
|
+
soldermaskRingColor,
|
|
235
|
+
copperColor,
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
103
239
|
// Draw circular drill hole (with offset)
|
|
104
240
|
const holeX = hole.x + (hole.hole_offset_x ?? 0)
|
|
105
241
|
const holeY = hole.y + (hole.hole_offset_y ?? 0)
|
|
@@ -114,17 +250,46 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
|
|
|
114
250
|
}
|
|
115
251
|
|
|
116
252
|
if (hole.shape === "pill_hole_with_rect_pad") {
|
|
253
|
+
// For positive margins, draw extended mask area first
|
|
254
|
+
if (hasSoldermask && margin > 0) {
|
|
255
|
+
drawRect({
|
|
256
|
+
ctx,
|
|
257
|
+
center: { x: hole.x, y: hole.y },
|
|
258
|
+
width: hole.rect_pad_width + margin * 2,
|
|
259
|
+
height: hole.rect_pad_height + margin * 2,
|
|
260
|
+
fill: positiveMarginColor,
|
|
261
|
+
realToCanvasMat,
|
|
262
|
+
borderRadius: (hole.rect_border_radius ?? 0) + margin,
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
117
266
|
// Draw rectangular pad
|
|
118
267
|
drawRect({
|
|
119
268
|
ctx,
|
|
120
269
|
center: { x: hole.x, y: hole.y },
|
|
121
270
|
width: hole.rect_pad_width,
|
|
122
271
|
height: hole.rect_pad_height,
|
|
123
|
-
fill:
|
|
272
|
+
fill: copperColor,
|
|
124
273
|
realToCanvasMat,
|
|
125
274
|
borderRadius: hole.rect_border_radius ?? 0,
|
|
126
275
|
})
|
|
127
276
|
|
|
277
|
+
// For negative margins, draw soldermask ring on top of the pad
|
|
278
|
+
if (hasSoldermask && margin < 0) {
|
|
279
|
+
drawSoldermaskRingForRect(
|
|
280
|
+
ctx,
|
|
281
|
+
{ x: hole.x, y: hole.y },
|
|
282
|
+
hole.rect_pad_width,
|
|
283
|
+
hole.rect_pad_height,
|
|
284
|
+
margin,
|
|
285
|
+
hole.rect_border_radius ?? 0,
|
|
286
|
+
0,
|
|
287
|
+
realToCanvasMat,
|
|
288
|
+
soldermaskRingColor,
|
|
289
|
+
copperColor,
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
|
|
128
293
|
// Draw pill drill hole (with offset)
|
|
129
294
|
const holeX = hole.x + (hole.hole_offset_x ?? 0)
|
|
130
295
|
const holeY = hole.y + (hole.hole_offset_y ?? 0)
|
|
@@ -140,18 +305,48 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
|
|
|
140
305
|
}
|
|
141
306
|
|
|
142
307
|
if (hole.shape === "rotated_pill_hole_with_rect_pad") {
|
|
308
|
+
// For positive margins, draw extended mask area first
|
|
309
|
+
if (hasSoldermask && margin > 0) {
|
|
310
|
+
drawRect({
|
|
311
|
+
ctx,
|
|
312
|
+
center: { x: hole.x, y: hole.y },
|
|
313
|
+
width: hole.rect_pad_width + margin * 2,
|
|
314
|
+
height: hole.rect_pad_height + margin * 2,
|
|
315
|
+
fill: positiveMarginColor,
|
|
316
|
+
realToCanvasMat,
|
|
317
|
+
borderRadius: (hole.rect_border_radius ?? 0) + margin,
|
|
318
|
+
rotation: hole.rect_ccw_rotation,
|
|
319
|
+
})
|
|
320
|
+
}
|
|
321
|
+
|
|
143
322
|
// Draw rotated rectangular pad
|
|
144
323
|
drawRect({
|
|
145
324
|
ctx,
|
|
146
325
|
center: { x: hole.x, y: hole.y },
|
|
147
326
|
width: hole.rect_pad_width,
|
|
148
327
|
height: hole.rect_pad_height,
|
|
149
|
-
fill:
|
|
328
|
+
fill: copperColor,
|
|
150
329
|
realToCanvasMat,
|
|
151
330
|
borderRadius: hole.rect_border_radius ?? 0,
|
|
152
331
|
rotation: hole.rect_ccw_rotation,
|
|
153
332
|
})
|
|
154
333
|
|
|
334
|
+
// For negative margins, draw soldermask ring on top of the pad
|
|
335
|
+
if (hasSoldermask && margin < 0) {
|
|
336
|
+
drawSoldermaskRingForRect(
|
|
337
|
+
ctx,
|
|
338
|
+
{ x: hole.x, y: hole.y },
|
|
339
|
+
hole.rect_pad_width,
|
|
340
|
+
hole.rect_pad_height,
|
|
341
|
+
margin,
|
|
342
|
+
hole.rect_border_radius ?? 0,
|
|
343
|
+
hole.rect_ccw_rotation ?? 0,
|
|
344
|
+
realToCanvasMat,
|
|
345
|
+
soldermaskRingColor,
|
|
346
|
+
copperColor,
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
|
|
155
350
|
// Draw rotated pill drill hole (with offset)
|
|
156
351
|
const holeX = hole.x + (hole.hole_offset_x ?? 0)
|
|
157
352
|
const holeY = hole.y + (hole.hole_offset_y ?? 0)
|
|
@@ -168,6 +363,7 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
|
|
|
168
363
|
}
|
|
169
364
|
|
|
170
365
|
if (hole.shape === "hole_with_polygon_pad") {
|
|
366
|
+
// Note: Polygon pads don't support soldermask margins (similar to SMT polygon pads)
|
|
171
367
|
// Draw polygon pad
|
|
172
368
|
const padOutline = hole.pad_outline
|
|
173
369
|
if (padOutline && padOutline.length >= 3) {
|
|
@@ -179,7 +375,7 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
|
|
|
179
375
|
drawPolygon({
|
|
180
376
|
ctx,
|
|
181
377
|
points: padPoints,
|
|
182
|
-
fill:
|
|
378
|
+
fill: copperColor,
|
|
183
379
|
realToCanvasMat,
|
|
184
380
|
})
|
|
185
381
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { createCanvas } from "@napi-rs/canvas"
|
|
3
|
+
import type { PcbPlatedHole } from "circuit-json"
|
|
4
|
+
import { CircuitToCanvasDrawer } from "../../lib/drawer"
|
|
5
|
+
|
|
6
|
+
test("draw plated holes with positive and negative 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: any = [
|
|
15
|
+
{
|
|
16
|
+
type: "pcb_board",
|
|
17
|
+
pcb_board_id: "board0",
|
|
18
|
+
center: { x: 0, y: 0 },
|
|
19
|
+
width: 16,
|
|
20
|
+
height: 14,
|
|
21
|
+
},
|
|
22
|
+
// Circle with positive margin (mask extends beyond pad)
|
|
23
|
+
{
|
|
24
|
+
type: "pcb_plated_hole",
|
|
25
|
+
pcb_plated_hole_id: "hole_circle_positive",
|
|
26
|
+
shape: "circle",
|
|
27
|
+
x: -5,
|
|
28
|
+
y: 3.5,
|
|
29
|
+
outer_diameter: 2,
|
|
30
|
+
hole_diameter: 1,
|
|
31
|
+
layers: ["top", "bottom"],
|
|
32
|
+
is_covered_with_solder_mask: true,
|
|
33
|
+
soldermask_margin: 0.2,
|
|
34
|
+
},
|
|
35
|
+
// Circle with negative margin (spacing around copper, copper visible)
|
|
36
|
+
{
|
|
37
|
+
type: "pcb_plated_hole",
|
|
38
|
+
pcb_plated_hole_id: "hole_circle_negative",
|
|
39
|
+
shape: "circle",
|
|
40
|
+
x: -5,
|
|
41
|
+
y: -3.5,
|
|
42
|
+
outer_diameter: 2,
|
|
43
|
+
hole_diameter: 1,
|
|
44
|
+
layers: ["top", "bottom"],
|
|
45
|
+
is_covered_with_solder_mask: true,
|
|
46
|
+
soldermask_margin: -0.15,
|
|
47
|
+
},
|
|
48
|
+
// Oval with positive margin
|
|
49
|
+
{
|
|
50
|
+
type: "pcb_plated_hole",
|
|
51
|
+
pcb_plated_hole_id: "hole_oval_positive",
|
|
52
|
+
shape: "oval",
|
|
53
|
+
x: 0,
|
|
54
|
+
y: 3.5,
|
|
55
|
+
outer_width: 2.4,
|
|
56
|
+
outer_height: 1.6,
|
|
57
|
+
hole_width: 1.6,
|
|
58
|
+
hole_height: 0.8,
|
|
59
|
+
layers: ["top", "bottom"],
|
|
60
|
+
ccw_rotation: 0,
|
|
61
|
+
is_covered_with_solder_mask: true,
|
|
62
|
+
soldermask_margin: 0.15,
|
|
63
|
+
},
|
|
64
|
+
// Oval with negative margin
|
|
65
|
+
{
|
|
66
|
+
type: "pcb_plated_hole",
|
|
67
|
+
pcb_plated_hole_id: "hole_oval_negative",
|
|
68
|
+
shape: "oval",
|
|
69
|
+
x: 0,
|
|
70
|
+
y: -3.5,
|
|
71
|
+
outer_width: 2.4,
|
|
72
|
+
outer_height: 1.6,
|
|
73
|
+
hole_width: 1.6,
|
|
74
|
+
hole_height: 0.8,
|
|
75
|
+
layers: ["top", "bottom"],
|
|
76
|
+
ccw_rotation: 0,
|
|
77
|
+
is_covered_with_solder_mask: true,
|
|
78
|
+
soldermask_margin: -0.2,
|
|
79
|
+
},
|
|
80
|
+
// Pill with positive margin
|
|
81
|
+
{
|
|
82
|
+
type: "pcb_plated_hole",
|
|
83
|
+
pcb_plated_hole_id: "hole_pill_positive",
|
|
84
|
+
shape: "pill",
|
|
85
|
+
x: 5,
|
|
86
|
+
y: 3.5,
|
|
87
|
+
outer_width: 3,
|
|
88
|
+
outer_height: 1.5,
|
|
89
|
+
hole_width: 2,
|
|
90
|
+
hole_height: 1,
|
|
91
|
+
layers: ["top", "bottom"],
|
|
92
|
+
ccw_rotation: 0,
|
|
93
|
+
is_covered_with_solder_mask: true,
|
|
94
|
+
soldermask_margin: 0.1,
|
|
95
|
+
},
|
|
96
|
+
// Pill with negative margin
|
|
97
|
+
{
|
|
98
|
+
type: "pcb_plated_hole",
|
|
99
|
+
pcb_plated_hole_id: "hole_pill_negative",
|
|
100
|
+
shape: "pill",
|
|
101
|
+
x: 5,
|
|
102
|
+
y: -3.5,
|
|
103
|
+
outer_width: 3,
|
|
104
|
+
outer_height: 1.5,
|
|
105
|
+
hole_width: 2,
|
|
106
|
+
hole_height: 1,
|
|
107
|
+
layers: ["top", "bottom"],
|
|
108
|
+
ccw_rotation: 0,
|
|
109
|
+
is_covered_with_solder_mask: true,
|
|
110
|
+
soldermask_margin: -0.12,
|
|
111
|
+
},
|
|
112
|
+
// Rectangular pad with circular hole - positive margin
|
|
113
|
+
{
|
|
114
|
+
type: "pcb_plated_hole",
|
|
115
|
+
pcb_plated_hole_id: "hole_rect_circle_positive",
|
|
116
|
+
shape: "circular_hole_with_rect_pad",
|
|
117
|
+
x: -2.5,
|
|
118
|
+
y: 0,
|
|
119
|
+
rect_pad_width: 2.4,
|
|
120
|
+
rect_pad_height: 1.6,
|
|
121
|
+
rect_border_radius: 0.2,
|
|
122
|
+
hole_diameter: 1,
|
|
123
|
+
layers: ["top", "bottom"],
|
|
124
|
+
is_covered_with_solder_mask: true,
|
|
125
|
+
soldermask_margin: 0.15,
|
|
126
|
+
},
|
|
127
|
+
// Rectangular pad with circular hole - negative margin
|
|
128
|
+
{
|
|
129
|
+
type: "pcb_plated_hole",
|
|
130
|
+
pcb_plated_hole_id: "hole_rect_circle_negative",
|
|
131
|
+
shape: "circular_hole_with_rect_pad",
|
|
132
|
+
x: 2.5,
|
|
133
|
+
y: 0,
|
|
134
|
+
rect_pad_width: 2.4,
|
|
135
|
+
rect_pad_height: 1.6,
|
|
136
|
+
rect_border_radius: 0.2,
|
|
137
|
+
hole_diameter: 1,
|
|
138
|
+
layers: ["top", "bottom"],
|
|
139
|
+
is_covered_with_solder_mask: true,
|
|
140
|
+
soldermask_margin: -0.1,
|
|
141
|
+
},
|
|
142
|
+
// Silkscreen labels for positive margin holes (top row)
|
|
143
|
+
{
|
|
144
|
+
type: "pcb_silkscreen_text",
|
|
145
|
+
pcb_silkscreen_text_id: "text_circle_pos",
|
|
146
|
+
layer: "top",
|
|
147
|
+
anchor_position: { x: -5, y: 5.2 },
|
|
148
|
+
anchor_alignment: "center",
|
|
149
|
+
text: "+0.2mm",
|
|
150
|
+
font_size: 0.4,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
type: "pcb_silkscreen_text",
|
|
154
|
+
pcb_silkscreen_text_id: "text_oval_pos",
|
|
155
|
+
layer: "top",
|
|
156
|
+
anchor_position: { x: 0, y: 5.2 },
|
|
157
|
+
anchor_alignment: "center",
|
|
158
|
+
text: "+0.15mm",
|
|
159
|
+
font_size: 0.4,
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
type: "pcb_silkscreen_text",
|
|
163
|
+
pcb_silkscreen_text_id: "text_pill_pos",
|
|
164
|
+
layer: "top",
|
|
165
|
+
anchor_position: { x: 5, y: 5.2 },
|
|
166
|
+
anchor_alignment: "center",
|
|
167
|
+
text: "+0.1mm",
|
|
168
|
+
font_size: 0.4,
|
|
169
|
+
},
|
|
170
|
+
// Silkscreen labels for middle row (rectangular pads)
|
|
171
|
+
{
|
|
172
|
+
type: "pcb_silkscreen_text",
|
|
173
|
+
pcb_silkscreen_text_id: "text_rect_pos",
|
|
174
|
+
layer: "top",
|
|
175
|
+
anchor_position: { x: -2.5, y: 1.8 },
|
|
176
|
+
anchor_alignment: "center",
|
|
177
|
+
text: "+0.15mm",
|
|
178
|
+
font_size: 0.4,
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
type: "pcb_silkscreen_text",
|
|
182
|
+
pcb_silkscreen_text_id: "text_rect_neg",
|
|
183
|
+
layer: "top",
|
|
184
|
+
anchor_position: { x: 2.5, y: -1.8 },
|
|
185
|
+
anchor_alignment: "center",
|
|
186
|
+
text: "-0.1mm",
|
|
187
|
+
font_size: 0.4,
|
|
188
|
+
},
|
|
189
|
+
// Silkscreen labels for negative margin holes (bottom row)
|
|
190
|
+
{
|
|
191
|
+
type: "pcb_silkscreen_text",
|
|
192
|
+
pcb_silkscreen_text_id: "text_circle_neg",
|
|
193
|
+
layer: "top",
|
|
194
|
+
anchor_position: { x: -5, y: -5.2 },
|
|
195
|
+
anchor_alignment: "center",
|
|
196
|
+
text: "-0.15mm",
|
|
197
|
+
font_size: 0.4,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
type: "pcb_silkscreen_text",
|
|
201
|
+
pcb_silkscreen_text_id: "text_oval_neg",
|
|
202
|
+
layer: "top",
|
|
203
|
+
anchor_position: { x: 0, y: -5.2 },
|
|
204
|
+
anchor_alignment: "center",
|
|
205
|
+
text: "-0.2mm",
|
|
206
|
+
font_size: 0.4,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
type: "pcb_silkscreen_text",
|
|
210
|
+
pcb_silkscreen_text_id: "text_pill_neg",
|
|
211
|
+
layer: "top",
|
|
212
|
+
anchor_position: { x: 5, y: -5.2 },
|
|
213
|
+
anchor_alignment: "center",
|
|
214
|
+
text: "-0.12mm",
|
|
215
|
+
font_size: 0.4,
|
|
216
|
+
},
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
drawer.setCameraBounds({ minX: -8, maxX: 8, minY: -6.5, maxY: 6.5 })
|
|
220
|
+
drawer.drawElements(circuit)
|
|
221
|
+
|
|
222
|
+
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
223
|
+
import.meta.path,
|
|
224
|
+
)
|
|
225
|
+
})
|