circuit-to-canvas 0.0.49 → 0.0.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/index.d.ts +13 -5
  2. package/dist/index.js +1450 -1226
  3. package/lib/drawer/CircuitToCanvasDrawer.ts +262 -312
  4. package/lib/drawer/elements/helper-functions/draw-pill.ts +39 -0
  5. package/lib/drawer/elements/helper-functions/draw-polygon.ts +25 -0
  6. package/lib/drawer/elements/helper-functions/draw-rounded-rect.ts +34 -0
  7. package/lib/drawer/elements/helper-functions/index.ts +3 -0
  8. package/lib/drawer/elements/pcb-board.ts +13 -3
  9. package/lib/drawer/elements/pcb-hole.ts +56 -338
  10. package/lib/drawer/elements/pcb-plated-hole.ts +154 -442
  11. package/lib/drawer/elements/pcb-smtpad.ts +5 -271
  12. package/lib/drawer/elements/pcb-soldermask/board.ts +44 -0
  13. package/lib/drawer/elements/pcb-soldermask/cutout.ts +74 -0
  14. package/lib/drawer/elements/pcb-soldermask/hole.ts +288 -0
  15. package/lib/drawer/elements/pcb-soldermask/index.ts +140 -0
  16. package/lib/drawer/elements/pcb-soldermask/plated-hole.ts +365 -0
  17. package/lib/drawer/elements/pcb-soldermask/smt-pad.ts +354 -0
  18. package/lib/drawer/elements/pcb-soldermask/via.ts +27 -0
  19. package/lib/drawer/elements/soldermask-margin.ts +39 -8
  20. package/package.json +2 -2
  21. package/tests/board-snapshot/__snapshots__/usb-c-flashlight-board.snap.png +0 -0
  22. package/tests/board-snapshot/usb-c-flashlight-board.test.ts +1 -0
  23. package/tests/elements/__snapshots__/board-with-elements.snap.png +0 -0
  24. package/tests/elements/__snapshots__/brep-copper-pours.snap.png +0 -0
  25. package/tests/elements/__snapshots__/custom-outline-board.snap.png +0 -0
  26. package/tests/elements/__snapshots__/oval-plated-hole.snap.png +0 -0
  27. package/tests/elements/__snapshots__/pcb-board.snap.png +0 -0
  28. package/tests/elements/__snapshots__/pcb-comprehensive-soldermask-margin.snap.png +0 -0
  29. package/tests/elements/__snapshots__/pcb-fabrication-note-dimension.snap.png +0 -0
  30. package/tests/elements/__snapshots__/pcb-hole-soldermask-margin.snap.png +0 -0
  31. package/tests/elements/__snapshots__/pcb-keepout-layer-filter.snap.png +0 -0
  32. package/tests/elements/__snapshots__/pcb-keepout-multiple-layers.snap.png +0 -0
  33. package/tests/elements/__snapshots__/pcb-keepout-rect-and-circle.snap.png +0 -0
  34. package/tests/elements/__snapshots__/pcb-keepout-with-group-id.snap.png +0 -0
  35. package/tests/elements/__snapshots__/pcb-no-soldermask.snap.png +0 -0
  36. package/tests/elements/__snapshots__/pcb-plated-hole-soldermask-margin.snap.png +0 -0
  37. package/tests/elements/__snapshots__/pcb-plated-hole.snap.png +0 -0
  38. package/tests/elements/__snapshots__/pcb-silkscreen-on-component.snap.png +0 -0
  39. package/tests/elements/__snapshots__/pcb-silkscreen-oval.snap.png +0 -0
  40. package/tests/elements/__snapshots__/pcb-smtpad-asymmetric-soldermask-margin.snap.png +0 -0
  41. package/tests/elements/__snapshots__/pcb-smtpad-soldermask-coverage.snap.png +0 -0
  42. package/tests/elements/__snapshots__/pcb-smtpad-soldermask-margin.snap.png +0 -0
  43. package/tests/elements/__snapshots__/pill-plated-hole.snap.png +0 -0
  44. package/tests/elements/pcb-comprehensive-soldermask-margin.test.ts +2 -2
  45. package/tests/elements/pcb-hole-soldermask-margin.test.ts +155 -2
  46. package/tests/elements/pcb-no-soldermask.test.ts +1281 -0
  47. package/tests/elements/pcb-plated-hole-soldermask-margin.test.ts +1 -1
  48. package/tests/elements/pcb-plated-hole.test.ts +40 -4
  49. package/tests/elements/pcb-smtpad-asymmetric-soldermask-margin.test.ts +140 -0
  50. package/tests/elements/pcb-smtpad-soldermask-coverage.test.ts +1 -1
  51. package/tests/elements/pcb-smtpad-soldermask-margin.test.ts +18 -2
  52. package/tests/fixtures/getStackedPngSvgComparison.ts +8 -2
package/dist/index.js CHANGED
@@ -3,8 +3,7 @@ import {
3
3
  identity,
4
4
  compose,
5
5
  translate,
6
- scale,
7
- applyToPoint as applyToPoint16
6
+ scale
8
7
  } from "transformation-matrix";
9
8
 
10
9
  // lib/drawer/pcb-render-layer-filter.ts
@@ -253,12 +252,19 @@ function drawPolygon(params) {
253
252
 
254
253
  // lib/drawer/elements/soldermask-margin.ts
255
254
  import { applyToPoint as applyToPoint6 } from "transformation-matrix";
256
- function drawSoldermaskRingForRect(ctx, center, width, height, margin, borderRadius, rotation, realToCanvasMat, soldermaskColor, padColor) {
255
+ function drawSoldermaskRingForRect(ctx, center, width, height, margin, borderRadius, rotation, realToCanvasMat, soldermaskColor, padColor, asymmetricMargins) {
257
256
  const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
258
257
  const scaledWidth = width * Math.abs(realToCanvasMat.a);
259
258
  const scaledHeight = height * Math.abs(realToCanvasMat.a);
260
- const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
261
259
  const scaledRadius = borderRadius * Math.abs(realToCanvasMat.a);
260
+ const ml = asymmetricMargins?.left ?? margin;
261
+ const mr = asymmetricMargins?.right ?? margin;
262
+ const mt = asymmetricMargins?.top ?? margin;
263
+ const mb = asymmetricMargins?.bottom ?? margin;
264
+ const scaledThicknessL = Math.max(0, -ml) * Math.abs(realToCanvasMat.a);
265
+ const scaledThicknessR = Math.max(0, -mr) * Math.abs(realToCanvasMat.a);
266
+ const scaledThicknessT = Math.max(0, -mt) * Math.abs(realToCanvasMat.a);
267
+ const scaledThicknessB = Math.max(0, -mb) * Math.abs(realToCanvasMat.a);
262
268
  ctx.save();
263
269
  ctx.translate(cx, cy);
264
270
  if (rotation !== 0) {
@@ -299,14 +305,23 @@ function drawSoldermaskRingForRect(ctx, center, width, height, margin, borderRad
299
305
  if (ctx.globalCompositeOperation !== void 0) {
300
306
  ctx.globalCompositeOperation = prevCompositeOp || "source-over";
301
307
  }
302
- const innerWidth = scaledWidth - scaledMargin * 2;
303
- const innerHeight = scaledHeight - scaledMargin * 2;
304
- const innerRadius = Math.max(0, scaledRadius - scaledMargin);
308
+ const innerWidth = Math.max(
309
+ 0,
310
+ scaledWidth - (scaledThicknessL + scaledThicknessR)
311
+ );
312
+ const innerHeight = Math.max(
313
+ 0,
314
+ scaledHeight - (scaledThicknessT + scaledThicknessB)
315
+ );
316
+ const innerRadius = Math.max(
317
+ 0,
318
+ scaledRadius - (scaledThicknessL + scaledThicknessR + scaledThicknessT + scaledThicknessB) / 4
319
+ );
305
320
  if (innerWidth > 0 && innerHeight > 0) {
306
321
  ctx.beginPath();
322
+ const x = -scaledWidth / 2 + scaledThicknessL;
323
+ const y = -scaledHeight / 2 + scaledThicknessT;
307
324
  if (innerRadius > 0) {
308
- const x = -innerWidth / 2;
309
- const y = -innerHeight / 2;
310
325
  const r = Math.min(innerRadius, innerWidth / 2, innerHeight / 2);
311
326
  ctx.moveTo(x + r, y);
312
327
  ctx.lineTo(x + innerWidth - r, y);
@@ -324,7 +339,7 @@ function drawSoldermaskRingForRect(ctx, center, width, height, margin, borderRad
324
339
  ctx.lineTo(x, y + r);
325
340
  ctx.arcTo(x, y, x + r, y, r);
326
341
  } else {
327
- ctx.rect(-innerWidth / 2, -innerHeight / 2, innerWidth, innerHeight);
342
+ ctx.rect(x, y, innerWidth, innerHeight);
328
343
  }
329
344
  ctx.fillStyle = padColor;
330
345
  ctx.fill();
@@ -487,418 +502,146 @@ function offsetPolygonPoints(points, offset) {
487
502
  }
488
503
  return result;
489
504
  }
490
- function drawSoldermaskRingForPolygon(ctx, points, margin, realToCanvasMat, soldermaskColor, padColor) {
491
- if (points.length < 3 || margin >= 0) return;
492
- const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
493
- const prevCompositeOp = ctx.globalCompositeOperation;
494
- if (ctx.globalCompositeOperation !== void 0) {
495
- ctx.globalCompositeOperation = "source-atop";
496
- }
497
- ctx.beginPath();
498
- const canvasPoints = points.map(
499
- (p) => applyToPoint6(realToCanvasMat, [p.x, p.y])
500
- );
501
- const firstPoint = canvasPoints[0];
502
- if (firstPoint) {
503
- const [firstX, firstY] = firstPoint;
504
- ctx.moveTo(firstX, firstY);
505
- for (let i = 1; i < canvasPoints.length; i++) {
506
- const point = canvasPoints[i];
507
- if (point) {
508
- const [x, y] = point;
509
- ctx.lineTo(x, y);
510
- }
511
- }
512
- ctx.closePath();
513
- ctx.fillStyle = soldermaskColor;
514
- ctx.fill();
515
- }
516
- if (ctx.globalCompositeOperation !== void 0) {
517
- ctx.globalCompositeOperation = prevCompositeOp || "source-over";
518
- }
519
- const innerPoints = offsetPolygonPoints(points, margin);
520
- if (innerPoints.length >= 3) {
521
- ctx.beginPath();
522
- const innerCanvasPoints = innerPoints.map(
523
- (p) => applyToPoint6(realToCanvasMat, [p.x, p.y])
524
- );
525
- const firstInnerPoint = innerCanvasPoints[0];
526
- if (firstInnerPoint) {
527
- const [firstX, firstY] = firstInnerPoint;
528
- ctx.moveTo(firstX, firstY);
529
- for (let i = 1; i < innerCanvasPoints.length; i++) {
530
- const point = innerCanvasPoints[i];
531
- if (point) {
532
- const [x, y] = point;
533
- ctx.lineTo(x, y);
534
- }
535
- }
536
- ctx.closePath();
537
- ctx.fillStyle = padColor;
538
- ctx.fill();
539
- }
540
- }
541
- ctx.restore();
542
- }
543
505
 
544
506
  // lib/drawer/elements/pcb-plated-hole.ts
545
- function getSoldermaskColor(layers, colorMap) {
546
- const layer = layers?.includes("top") ? "top" : "bottom";
547
- return colorMap.soldermaskOverCopper[layer] ?? colorMap.soldermaskOverCopper.top;
548
- }
549
507
  function drawPcbPlatedHole(params) {
550
- const { ctx, hole, realToCanvasMat, colorMap } = params;
551
- const isCoveredWithSoldermask = hole.is_covered_with_solder_mask === true;
552
- const margin = isCoveredWithSoldermask ? 0 : hole.soldermask_margin ?? 0;
553
- const hasSoldermask = !isCoveredWithSoldermask && hole.soldermask_margin !== void 0 && hole.soldermask_margin !== 0;
554
- const soldermaskRingColor = getSoldermaskColor(hole.layers, colorMap);
555
- const positiveMarginColor = colorMap.substrate;
508
+ const {
509
+ ctx,
510
+ hole,
511
+ realToCanvasMat,
512
+ colorMap,
513
+ soldermaskMargin = 0,
514
+ showSoldermask
515
+ } = params;
516
+ if (hole.is_covered_with_solder_mask === true && showSoldermask) {
517
+ return;
518
+ }
556
519
  const copperColor = colorMap.copper.top;
520
+ const copperInset = soldermaskMargin < 0 ? Math.abs(soldermaskMargin) : 0;
557
521
  if (hole.shape === "circle") {
558
- if (hasSoldermask && margin > 0) {
559
- drawCircle({
560
- ctx,
561
- center: { x: hole.x, y: hole.y },
562
- radius: hole.outer_diameter / 2 + margin,
563
- fill: positiveMarginColor,
564
- realToCanvasMat
565
- });
566
- }
567
522
  drawCircle({
568
523
  ctx,
569
524
  center: { x: hole.x, y: hole.y },
570
- radius: hole.outer_diameter / 2,
525
+ radius: hole.outer_diameter / 2 - copperInset,
571
526
  fill: copperColor,
572
527
  realToCanvasMat
573
528
  });
574
- if (hasSoldermask && margin < 0) {
575
- drawSoldermaskRingForCircle(
576
- ctx,
577
- { x: hole.x, y: hole.y },
578
- hole.outer_diameter / 2,
579
- margin,
580
- realToCanvasMat,
581
- soldermaskRingColor,
582
- copperColor
583
- );
584
- }
585
- if (isCoveredWithSoldermask) {
586
- drawCircle({
587
- ctx,
588
- center: { x: hole.x, y: hole.y },
589
- radius: hole.outer_diameter / 2,
590
- fill: soldermaskRingColor,
591
- realToCanvasMat
592
- });
593
- }
594
- if (!isCoveredWithSoldermask) {
595
- drawCircle({
596
- ctx,
597
- center: { x: hole.x, y: hole.y },
598
- radius: hole.hole_diameter / 2,
599
- fill: colorMap.drill,
600
- realToCanvasMat
601
- });
602
- }
529
+ drawCircle({
530
+ ctx,
531
+ center: { x: hole.x, y: hole.y },
532
+ radius: hole.hole_diameter / 2,
533
+ fill: colorMap.drill,
534
+ realToCanvasMat
535
+ });
603
536
  return;
604
537
  }
605
538
  if (hole.shape === "oval") {
606
- if (hasSoldermask && margin > 0) {
607
- drawOval({
608
- ctx,
609
- center: { x: hole.x, y: hole.y },
610
- radius_x: hole.outer_width / 2 + margin,
611
- radius_y: hole.outer_height / 2 + margin,
612
- fill: positiveMarginColor,
613
- realToCanvasMat,
614
- rotation: hole.ccw_rotation
615
- });
616
- }
617
539
  drawOval({
618
540
  ctx,
619
541
  center: { x: hole.x, y: hole.y },
620
- radius_x: hole.outer_width / 2,
621
- radius_y: hole.outer_height / 2,
542
+ radius_x: hole.outer_width / 2 - copperInset,
543
+ radius_y: hole.outer_height / 2 - copperInset,
622
544
  fill: copperColor,
623
545
  realToCanvasMat,
624
546
  rotation: hole.ccw_rotation
625
547
  });
626
- if (hasSoldermask && margin < 0) {
627
- drawSoldermaskRingForOval(
628
- ctx,
629
- { x: hole.x, y: hole.y },
630
- hole.outer_width / 2,
631
- hole.outer_height / 2,
632
- margin,
633
- hole.ccw_rotation ?? 0,
634
- realToCanvasMat,
635
- soldermaskRingColor,
636
- copperColor
637
- );
638
- }
639
- if (isCoveredWithSoldermask) {
640
- drawOval({
641
- ctx,
642
- center: { x: hole.x, y: hole.y },
643
- radius_x: hole.outer_width / 2,
644
- radius_y: hole.outer_height / 2,
645
- fill: soldermaskRingColor,
646
- realToCanvasMat,
647
- rotation: hole.ccw_rotation
648
- });
649
- }
650
- if (!isCoveredWithSoldermask) {
651
- drawOval({
652
- ctx,
653
- center: { x: hole.x, y: hole.y },
654
- radius_x: hole.hole_width / 2,
655
- radius_y: hole.hole_height / 2,
656
- fill: colorMap.drill,
657
- realToCanvasMat,
658
- rotation: hole.ccw_rotation
659
- });
660
- }
548
+ drawOval({
549
+ ctx,
550
+ center: { x: hole.x, y: hole.y },
551
+ radius_x: hole.hole_width / 2,
552
+ radius_y: hole.hole_height / 2,
553
+ fill: colorMap.drill,
554
+ realToCanvasMat,
555
+ rotation: hole.ccw_rotation
556
+ });
661
557
  return;
662
558
  }
663
559
  if (hole.shape === "pill") {
664
- if (hasSoldermask && margin > 0) {
665
- drawPill({
666
- ctx,
667
- center: { x: hole.x, y: hole.y },
668
- width: hole.outer_width + margin * 2,
669
- height: hole.outer_height + margin * 2,
670
- fill: positiveMarginColor,
671
- realToCanvasMat,
672
- rotation: hole.ccw_rotation
673
- });
674
- }
675
560
  drawPill({
676
561
  ctx,
677
562
  center: { x: hole.x, y: hole.y },
678
- width: hole.outer_width,
679
- height: hole.outer_height,
563
+ width: hole.outer_width - copperInset * 2,
564
+ height: hole.outer_height - copperInset * 2,
680
565
  fill: copperColor,
681
566
  realToCanvasMat,
682
567
  rotation: hole.ccw_rotation
683
568
  });
684
- if (hasSoldermask && margin < 0) {
685
- drawSoldermaskRingForPill(
686
- ctx,
687
- { x: hole.x, y: hole.y },
688
- hole.outer_width,
689
- hole.outer_height,
690
- margin,
691
- hole.ccw_rotation ?? 0,
692
- realToCanvasMat,
693
- soldermaskRingColor,
694
- copperColor
695
- );
696
- }
697
- if (isCoveredWithSoldermask) {
698
- drawPill({
699
- ctx,
700
- center: { x: hole.x, y: hole.y },
701
- width: hole.outer_width,
702
- height: hole.outer_height,
703
- fill: soldermaskRingColor,
704
- realToCanvasMat,
705
- rotation: hole.ccw_rotation
706
- });
707
- }
708
- if (!isCoveredWithSoldermask) {
709
- drawPill({
710
- ctx,
711
- center: { x: hole.x, y: hole.y },
712
- width: hole.hole_width,
713
- height: hole.hole_height,
714
- fill: colorMap.drill,
715
- realToCanvasMat,
716
- rotation: hole.ccw_rotation
717
- });
718
- }
569
+ drawPill({
570
+ ctx,
571
+ center: { x: hole.x, y: hole.y },
572
+ width: hole.hole_width,
573
+ height: hole.hole_height,
574
+ fill: colorMap.drill,
575
+ realToCanvasMat,
576
+ rotation: hole.ccw_rotation
577
+ });
719
578
  return;
720
579
  }
721
580
  if (hole.shape === "circular_hole_with_rect_pad") {
722
- if (hasSoldermask && margin > 0) {
723
- drawRect({
724
- ctx,
725
- center: { x: hole.x, y: hole.y },
726
- width: hole.rect_pad_width + margin * 2,
727
- height: hole.rect_pad_height + margin * 2,
728
- fill: positiveMarginColor,
729
- realToCanvasMat,
730
- borderRadius: hole.rect_border_radius ?? 0
731
- });
732
- }
733
581
  drawRect({
734
582
  ctx,
735
583
  center: { x: hole.x, y: hole.y },
736
- width: hole.rect_pad_width,
737
- height: hole.rect_pad_height,
584
+ width: hole.rect_pad_width - copperInset * 2,
585
+ height: hole.rect_pad_height - copperInset * 2,
738
586
  fill: copperColor,
739
587
  realToCanvasMat,
740
- borderRadius: hole.rect_border_radius ?? 0
588
+ borderRadius: hole.rect_border_radius ? Math.max(0, hole.rect_border_radius - copperInset) : 0
589
+ });
590
+ const holeX = hole.x + (hole.hole_offset_x ?? 0);
591
+ const holeY = hole.y + (hole.hole_offset_y ?? 0);
592
+ drawCircle({
593
+ ctx,
594
+ center: { x: holeX, y: holeY },
595
+ radius: hole.hole_diameter / 2,
596
+ fill: colorMap.drill,
597
+ realToCanvasMat
741
598
  });
742
- if (hasSoldermask && margin < 0) {
743
- drawSoldermaskRingForRect(
744
- ctx,
745
- { x: hole.x, y: hole.y },
746
- hole.rect_pad_width,
747
- hole.rect_pad_height,
748
- margin,
749
- hole.rect_border_radius ?? 0,
750
- 0,
751
- realToCanvasMat,
752
- soldermaskRingColor,
753
- copperColor
754
- );
755
- }
756
- if (isCoveredWithSoldermask) {
757
- drawRect({
758
- ctx,
759
- center: { x: hole.x, y: hole.y },
760
- width: hole.rect_pad_width,
761
- height: hole.rect_pad_height,
762
- fill: soldermaskRingColor,
763
- realToCanvasMat,
764
- borderRadius: hole.rect_border_radius ?? 0
765
- });
766
- }
767
- if (!isCoveredWithSoldermask) {
768
- const holeX = hole.x + (hole.hole_offset_x ?? 0);
769
- const holeY = hole.y + (hole.hole_offset_y ?? 0);
770
- drawCircle({
771
- ctx,
772
- center: { x: holeX, y: holeY },
773
- radius: hole.hole_diameter / 2,
774
- fill: colorMap.drill,
775
- realToCanvasMat
776
- });
777
- }
778
599
  return;
779
600
  }
780
601
  if (hole.shape === "pill_hole_with_rect_pad") {
781
- if (hasSoldermask && margin > 0) {
782
- drawRect({
783
- ctx,
784
- center: { x: hole.x, y: hole.y },
785
- width: hole.rect_pad_width + margin * 2,
786
- height: hole.rect_pad_height + margin * 2,
787
- fill: positiveMarginColor,
788
- realToCanvasMat,
789
- borderRadius: hole.rect_border_radius ?? 0
790
- });
791
- }
792
602
  drawRect({
793
603
  ctx,
794
604
  center: { x: hole.x, y: hole.y },
795
- width: hole.rect_pad_width,
796
- height: hole.rect_pad_height,
605
+ width: hole.rect_pad_width - copperInset * 2,
606
+ height: hole.rect_pad_height - copperInset * 2,
797
607
  fill: copperColor,
798
608
  realToCanvasMat,
799
- borderRadius: hole.rect_border_radius ?? 0
609
+ borderRadius: hole.rect_border_radius ? Math.max(0, hole.rect_border_radius - copperInset) : 0
610
+ });
611
+ const holeX = hole.x + (hole.hole_offset_x ?? 0);
612
+ const holeY = hole.y + (hole.hole_offset_y ?? 0);
613
+ drawPill({
614
+ ctx,
615
+ center: { x: holeX, y: holeY },
616
+ width: hole.hole_width,
617
+ height: hole.hole_height,
618
+ fill: colorMap.drill,
619
+ realToCanvasMat
800
620
  });
801
- if (hasSoldermask && margin < 0) {
802
- drawSoldermaskRingForRect(
803
- ctx,
804
- { x: hole.x, y: hole.y },
805
- hole.rect_pad_width,
806
- hole.rect_pad_height,
807
- margin,
808
- hole.rect_border_radius ?? 0,
809
- 0,
810
- realToCanvasMat,
811
- soldermaskRingColor,
812
- copperColor
813
- );
814
- }
815
- if (isCoveredWithSoldermask) {
816
- drawRect({
817
- ctx,
818
- center: { x: hole.x, y: hole.y },
819
- width: hole.rect_pad_width,
820
- height: hole.rect_pad_height,
821
- fill: soldermaskRingColor,
822
- realToCanvasMat,
823
- borderRadius: hole.rect_border_radius ?? 0
824
- });
825
- }
826
- if (!isCoveredWithSoldermask) {
827
- const holeX = hole.x + (hole.hole_offset_x ?? 0);
828
- const holeY = hole.y + (hole.hole_offset_y ?? 0);
829
- drawPill({
830
- ctx,
831
- center: { x: holeX, y: holeY },
832
- width: hole.hole_width,
833
- height: hole.hole_height,
834
- fill: colorMap.drill,
835
- realToCanvasMat
836
- });
837
- }
838
621
  return;
839
622
  }
840
623
  if (hole.shape === "rotated_pill_hole_with_rect_pad") {
841
- if (hasSoldermask && margin > 0) {
842
- drawRect({
843
- ctx,
844
- center: { x: hole.x, y: hole.y },
845
- width: hole.rect_pad_width + margin * 2,
846
- height: hole.rect_pad_height + margin * 2,
847
- fill: positiveMarginColor,
848
- realToCanvasMat,
849
- borderRadius: hole.rect_border_radius ?? 0,
850
- rotation: hole.rect_ccw_rotation
851
- });
852
- }
853
624
  drawRect({
854
625
  ctx,
855
626
  center: { x: hole.x, y: hole.y },
856
- width: hole.rect_pad_width,
857
- height: hole.rect_pad_height,
627
+ width: hole.rect_pad_width - copperInset * 2,
628
+ height: hole.rect_pad_height - copperInset * 2,
858
629
  fill: copperColor,
859
630
  realToCanvasMat,
860
- borderRadius: hole.rect_border_radius ?? 0,
631
+ borderRadius: hole.rect_border_radius ? Math.max(0, hole.rect_border_radius - copperInset) : 0,
861
632
  rotation: hole.rect_ccw_rotation
862
633
  });
863
- if (hasSoldermask && margin < 0) {
864
- drawSoldermaskRingForRect(
865
- ctx,
866
- { x: hole.x, y: hole.y },
867
- hole.rect_pad_width,
868
- hole.rect_pad_height,
869
- margin,
870
- hole.rect_border_radius ?? 0,
871
- hole.rect_ccw_rotation ?? 0,
872
- realToCanvasMat,
873
- soldermaskRingColor,
874
- copperColor
875
- );
876
- }
877
- if (isCoveredWithSoldermask) {
878
- drawRect({
879
- ctx,
880
- center: { x: hole.x, y: hole.y },
881
- width: hole.rect_pad_width,
882
- height: hole.rect_pad_height,
883
- fill: soldermaskRingColor,
884
- realToCanvasMat,
885
- borderRadius: hole.rect_border_radius ?? 0,
886
- rotation: hole.rect_ccw_rotation
887
- });
888
- }
889
- if (!isCoveredWithSoldermask) {
890
- const holeX = hole.x + (hole.hole_offset_x ?? 0);
891
- const holeY = hole.y + (hole.hole_offset_y ?? 0);
892
- drawPill({
893
- ctx,
894
- center: { x: holeX, y: holeY },
895
- width: hole.hole_width,
896
- height: hole.hole_height,
897
- fill: colorMap.drill,
898
- realToCanvasMat,
899
- rotation: hole.hole_ccw_rotation
900
- });
901
- }
634
+ const holeX = hole.x + (hole.hole_offset_x ?? 0);
635
+ const holeY = hole.y + (hole.hole_offset_y ?? 0);
636
+ drawPill({
637
+ ctx,
638
+ center: { x: holeX, y: holeY },
639
+ width: hole.hole_width,
640
+ height: hole.hole_height,
641
+ fill: colorMap.drill,
642
+ realToCanvasMat,
643
+ rotation: hole.hole_ccw_rotation
644
+ });
902
645
  return;
903
646
  }
904
647
  if (hole.shape === "hole_with_polygon_pad") {
@@ -908,80 +651,54 @@ function drawPcbPlatedHole(params) {
908
651
  x: hole.x + point.x,
909
652
  y: hole.y + point.y
910
653
  }));
911
- if (hasSoldermask && margin > 0) {
912
- const expandedPoints = offsetPolygonPoints(padPoints, margin);
654
+ const copperPoints = copperInset > 0 ? offsetPolygonPoints(padPoints, -copperInset) : padPoints;
655
+ if (copperPoints.length >= 3) {
913
656
  drawPolygon({
914
657
  ctx,
915
- points: expandedPoints,
916
- fill: positiveMarginColor,
658
+ points: copperPoints,
659
+ fill: copperColor,
917
660
  realToCanvasMat
918
661
  });
919
662
  }
920
- drawPolygon({
663
+ }
664
+ const holeX = hole.x + (hole.hole_offset_x ?? 0);
665
+ const holeY = hole.y + (hole.hole_offset_y ?? 0);
666
+ const holeShape = hole.hole_shape;
667
+ if (holeShape === "circle") {
668
+ drawCircle({
921
669
  ctx,
922
- points: padPoints,
923
- fill: copperColor,
670
+ center: { x: holeX, y: holeY },
671
+ radius: (hole.hole_diameter ?? 0) / 2,
672
+ fill: colorMap.drill,
673
+ realToCanvasMat
674
+ });
675
+ } else if (holeShape === "oval") {
676
+ drawOval({
677
+ ctx,
678
+ center: { x: holeX, y: holeY },
679
+ radius_x: (hole.hole_width ?? 0) / 2,
680
+ radius_y: (hole.hole_height ?? 0) / 2,
681
+ fill: colorMap.drill,
682
+ realToCanvasMat
683
+ });
684
+ } else if (holeShape === "pill") {
685
+ drawPill({
686
+ ctx,
687
+ center: { x: holeX, y: holeY },
688
+ width: hole.hole_width ?? 0,
689
+ height: hole.hole_height ?? 0,
690
+ fill: colorMap.drill,
691
+ realToCanvasMat
692
+ });
693
+ } else if (holeShape === "rotated_pill") {
694
+ drawPill({
695
+ ctx,
696
+ center: { x: holeX, y: holeY },
697
+ width: hole.hole_width ?? 0,
698
+ height: hole.hole_height ?? 0,
699
+ fill: colorMap.drill,
924
700
  realToCanvasMat
925
701
  });
926
- if (hasSoldermask && margin < 0) {
927
- drawSoldermaskRingForPolygon(
928
- ctx,
929
- padPoints,
930
- margin,
931
- realToCanvasMat,
932
- soldermaskRingColor,
933
- copperColor
934
- );
935
- }
936
- if (isCoveredWithSoldermask) {
937
- drawPolygon({
938
- ctx,
939
- points: padPoints,
940
- fill: soldermaskRingColor,
941
- realToCanvasMat
942
- });
943
- }
944
- }
945
- if (!isCoveredWithSoldermask) {
946
- const holeX = hole.x + (hole.hole_offset_x ?? 0);
947
- const holeY = hole.y + (hole.hole_offset_y ?? 0);
948
- const holeShape = hole.hole_shape;
949
- if (holeShape === "circle") {
950
- drawCircle({
951
- ctx,
952
- center: { x: holeX, y: holeY },
953
- radius: (hole.hole_diameter ?? 0) / 2,
954
- fill: colorMap.drill,
955
- realToCanvasMat
956
- });
957
- } else if (holeShape === "oval") {
958
- drawOval({
959
- ctx,
960
- center: { x: holeX, y: holeY },
961
- radius_x: (hole.hole_width ?? 0) / 2,
962
- radius_y: (hole.hole_height ?? 0) / 2,
963
- fill: colorMap.drill,
964
- realToCanvasMat
965
- });
966
- } else if (holeShape === "pill") {
967
- drawPill({
968
- ctx,
969
- center: { x: holeX, y: holeY },
970
- width: hole.hole_width ?? 0,
971
- height: hole.hole_height ?? 0,
972
- fill: colorMap.drill,
973
- realToCanvasMat
974
- });
975
- } else if (holeShape === "rotated_pill") {
976
- drawPill({
977
- ctx,
978
- center: { x: holeX, y: holeY },
979
- width: hole.hole_width ?? 0,
980
- height: hole.hole_height ?? 0,
981
- fill: colorMap.drill,
982
- realToCanvasMat
983
- });
984
- }
985
702
  }
986
703
  return;
987
704
  }
@@ -1014,304 +731,71 @@ function getRotation(hole) {
1014
731
  return 0;
1015
732
  }
1016
733
  function drawPcbHole(params) {
1017
- const { ctx, hole, realToCanvasMat, colorMap } = params;
1018
- const isCoveredWithSoldermask = hole.is_covered_with_solder_mask === true;
1019
- const margin = isCoveredWithSoldermask ? 0 : hole.soldermask_margin ?? 0;
1020
- const hasSoldermask = !isCoveredWithSoldermask && hole.soldermask_margin !== void 0 && hole.soldermask_margin !== 0;
1021
- const positiveMarginColor = colorMap.substrate;
1022
- const soldermaskOverlayColor = colorMap.soldermaskOverCopper.top;
1023
- const soldermaskRingColor = colorMap.soldermaskOverCopper.top;
734
+ const { ctx, hole, realToCanvasMat, colorMap, soldermaskMargin = 0 } = params;
735
+ if (hole.is_covered_with_solder_mask === true) {
736
+ return;
737
+ }
738
+ const holeInset = soldermaskMargin < 0 ? Math.abs(soldermaskMargin) : 0;
1024
739
  if (hole.hole_shape === "circle") {
1025
- if (hasSoldermask && margin > 0) {
1026
- drawCircle({
1027
- ctx,
1028
- center: { x: hole.x, y: hole.y },
1029
- radius: hole.hole_diameter / 2 + margin,
1030
- fill: positiveMarginColor,
1031
- realToCanvasMat
1032
- });
1033
- }
1034
- if (!isCoveredWithSoldermask) {
1035
- drawCircle({
1036
- ctx,
1037
- center: { x: hole.x, y: hole.y },
1038
- radius: hole.hole_diameter / 2,
1039
- fill: colorMap.drill,
1040
- realToCanvasMat
1041
- });
1042
- if (hasSoldermask && margin < 0) {
1043
- drawSoldermaskRingForCircle(
1044
- ctx,
1045
- { x: hole.x, y: hole.y },
1046
- hole.hole_diameter / 2,
1047
- margin,
1048
- realToCanvasMat,
1049
- soldermaskRingColor,
1050
- colorMap.drill
1051
- );
1052
- }
1053
- }
1054
- if (isCoveredWithSoldermask) {
1055
- drawCircle({
1056
- ctx,
1057
- center: { x: hole.x, y: hole.y },
1058
- radius: hole.hole_diameter / 2,
1059
- fill: soldermaskOverlayColor,
1060
- realToCanvasMat
1061
- });
1062
- }
740
+ drawCircle({
741
+ ctx,
742
+ center: { x: hole.x, y: hole.y },
743
+ radius: hole.hole_diameter / 2 - holeInset,
744
+ fill: colorMap.drill,
745
+ realToCanvasMat
746
+ });
1063
747
  return;
1064
748
  }
1065
749
  if (hole.hole_shape === "square") {
1066
750
  const rotation = getRotation(hole);
1067
- if (hasSoldermask && margin > 0) {
1068
- drawRect({
1069
- ctx,
1070
- center: { x: hole.x, y: hole.y },
1071
- width: hole.hole_diameter + margin * 2,
1072
- height: hole.hole_diameter + margin * 2,
1073
- fill: positiveMarginColor,
1074
- realToCanvasMat,
1075
- rotation
1076
- });
1077
- }
1078
- if (!isCoveredWithSoldermask) {
1079
- drawRect({
1080
- ctx,
1081
- center: { x: hole.x, y: hole.y },
1082
- width: hole.hole_diameter,
1083
- height: hole.hole_diameter,
1084
- fill: colorMap.drill,
1085
- realToCanvasMat,
1086
- rotation
1087
- });
1088
- if (hasSoldermask && margin < 0) {
1089
- drawSoldermaskRingForRect(
1090
- ctx,
1091
- { x: hole.x, y: hole.y },
1092
- hole.hole_diameter,
1093
- hole.hole_diameter,
1094
- margin,
1095
- 0,
1096
- rotation,
1097
- realToCanvasMat,
1098
- soldermaskRingColor,
1099
- colorMap.drill
1100
- );
1101
- }
1102
- }
1103
- if (isCoveredWithSoldermask) {
1104
- drawRect({
1105
- ctx,
1106
- center: { x: hole.x, y: hole.y },
1107
- width: hole.hole_diameter,
1108
- height: hole.hole_diameter,
1109
- fill: soldermaskOverlayColor,
1110
- realToCanvasMat,
1111
- rotation
1112
- });
1113
- }
751
+ drawRect({
752
+ ctx,
753
+ center: { x: hole.x, y: hole.y },
754
+ width: hole.hole_diameter - holeInset * 2,
755
+ height: hole.hole_diameter - holeInset * 2,
756
+ fill: colorMap.drill,
757
+ realToCanvasMat,
758
+ rotation
759
+ });
1114
760
  return;
1115
761
  }
1116
762
  if (hole.hole_shape === "oval") {
1117
763
  const rotation = getRotation(hole);
1118
- if (hasSoldermask && margin > 0) {
1119
- drawOval({
1120
- ctx,
1121
- center: { x: hole.x, y: hole.y },
1122
- radius_x: hole.hole_width / 2 + margin,
1123
- radius_y: hole.hole_height / 2 + margin,
1124
- fill: positiveMarginColor,
1125
- realToCanvasMat,
1126
- rotation
1127
- });
1128
- }
1129
- if (!isCoveredWithSoldermask) {
1130
- drawOval({
1131
- ctx,
1132
- center: { x: hole.x, y: hole.y },
1133
- radius_x: hole.hole_width / 2,
1134
- radius_y: hole.hole_height / 2,
1135
- fill: colorMap.drill,
1136
- realToCanvasMat,
1137
- rotation
1138
- });
1139
- if (hasSoldermask && margin < 0) {
1140
- drawSoldermaskRingForOval(
1141
- ctx,
1142
- { x: hole.x, y: hole.y },
1143
- hole.hole_width / 2,
1144
- hole.hole_height / 2,
1145
- margin,
1146
- rotation,
1147
- realToCanvasMat,
1148
- soldermaskRingColor,
1149
- colorMap.drill
1150
- );
1151
- }
1152
- }
1153
- if (isCoveredWithSoldermask) {
1154
- drawOval({
1155
- ctx,
1156
- center: { x: hole.x, y: hole.y },
1157
- radius_x: hole.hole_width / 2,
1158
- radius_y: hole.hole_height / 2,
1159
- fill: soldermaskOverlayColor,
1160
- realToCanvasMat,
1161
- rotation
1162
- });
1163
- }
764
+ drawOval({
765
+ ctx,
766
+ center: { x: hole.x, y: hole.y },
767
+ radius_x: hole.hole_width / 2 - holeInset,
768
+ radius_y: hole.hole_height / 2 - holeInset,
769
+ fill: colorMap.drill,
770
+ realToCanvasMat,
771
+ rotation
772
+ });
1164
773
  return;
1165
774
  }
1166
775
  if (hole.hole_shape === "rect") {
1167
776
  const rotation = getRotation(hole);
1168
- if (hasSoldermask && margin > 0) {
1169
- drawRect({
1170
- ctx,
1171
- center: { x: hole.x, y: hole.y },
1172
- width: hole.hole_width + margin * 2,
1173
- height: hole.hole_height + margin * 2,
1174
- fill: positiveMarginColor,
1175
- realToCanvasMat,
1176
- rotation
1177
- });
1178
- }
1179
- if (!isCoveredWithSoldermask) {
1180
- drawRect({
1181
- ctx,
1182
- center: { x: hole.x, y: hole.y },
1183
- width: hole.hole_width,
1184
- height: hole.hole_height,
1185
- fill: colorMap.drill,
1186
- realToCanvasMat,
1187
- rotation
1188
- });
1189
- if (hasSoldermask && margin < 0) {
1190
- drawSoldermaskRingForRect(
1191
- ctx,
1192
- { x: hole.x, y: hole.y },
1193
- hole.hole_width,
1194
- hole.hole_height,
1195
- margin,
1196
- 0,
1197
- rotation,
1198
- realToCanvasMat,
1199
- soldermaskRingColor,
1200
- colorMap.drill
1201
- );
1202
- }
1203
- }
1204
- if (isCoveredWithSoldermask) {
1205
- drawRect({
1206
- ctx,
1207
- center: { x: hole.x, y: hole.y },
1208
- width: hole.hole_width,
1209
- height: hole.hole_height,
1210
- fill: soldermaskOverlayColor,
1211
- realToCanvasMat,
1212
- rotation
1213
- });
1214
- }
1215
- return;
1216
- }
1217
- if (hole.hole_shape === "pill") {
1218
- const rotation = getRotation(hole);
1219
- if (hasSoldermask && margin > 0) {
1220
- drawPill({
1221
- ctx,
1222
- center: { x: hole.x, y: hole.y },
1223
- width: hole.hole_width + margin * 2,
1224
- height: hole.hole_height + margin * 2,
1225
- fill: positiveMarginColor,
1226
- realToCanvasMat,
1227
- rotation
1228
- });
1229
- }
1230
- if (!isCoveredWithSoldermask) {
1231
- drawPill({
1232
- ctx,
1233
- center: { x: hole.x, y: hole.y },
1234
- width: hole.hole_width,
1235
- height: hole.hole_height,
1236
- fill: colorMap.drill,
1237
- realToCanvasMat,
1238
- rotation
1239
- });
1240
- if (hasSoldermask && margin < 0) {
1241
- drawSoldermaskRingForPill(
1242
- ctx,
1243
- { x: hole.x, y: hole.y },
1244
- hole.hole_width,
1245
- hole.hole_height,
1246
- margin,
1247
- rotation,
1248
- realToCanvasMat,
1249
- soldermaskRingColor,
1250
- colorMap.drill
1251
- );
1252
- }
1253
- }
1254
- if (isCoveredWithSoldermask) {
1255
- drawPill({
1256
- ctx,
1257
- center: { x: hole.x, y: hole.y },
1258
- width: hole.hole_width,
1259
- height: hole.hole_height,
1260
- fill: soldermaskOverlayColor,
1261
- realToCanvasMat,
1262
- rotation
1263
- });
1264
- }
777
+ drawRect({
778
+ ctx,
779
+ center: { x: hole.x, y: hole.y },
780
+ width: hole.hole_width - holeInset * 2,
781
+ height: hole.hole_height - holeInset * 2,
782
+ fill: colorMap.drill,
783
+ realToCanvasMat,
784
+ rotation
785
+ });
1265
786
  return;
1266
787
  }
1267
- if (hole.hole_shape === "rotated_pill") {
788
+ if (hole.hole_shape === "pill" || hole.hole_shape === "rotated_pill") {
1268
789
  const rotation = getRotation(hole);
1269
- if (hasSoldermask && margin > 0) {
1270
- drawPill({
1271
- ctx,
1272
- center: { x: hole.x, y: hole.y },
1273
- width: hole.hole_width + margin * 2,
1274
- height: hole.hole_height + margin * 2,
1275
- fill: positiveMarginColor,
1276
- realToCanvasMat,
1277
- rotation
1278
- });
1279
- }
1280
- if (!isCoveredWithSoldermask) {
1281
- drawPill({
1282
- ctx,
1283
- center: { x: hole.x, y: hole.y },
1284
- width: hole.hole_width,
1285
- height: hole.hole_height,
1286
- fill: colorMap.drill,
1287
- realToCanvasMat,
1288
- rotation
1289
- });
1290
- if (hasSoldermask && margin < 0) {
1291
- drawSoldermaskRingForPill(
1292
- ctx,
1293
- { x: hole.x, y: hole.y },
1294
- hole.hole_width,
1295
- hole.hole_height,
1296
- margin,
1297
- rotation,
1298
- realToCanvasMat,
1299
- soldermaskRingColor,
1300
- colorMap.drill
1301
- );
1302
- }
1303
- }
1304
- if (isCoveredWithSoldermask) {
1305
- drawPill({
1306
- ctx,
1307
- center: { x: hole.x, y: hole.y },
1308
- width: hole.hole_width,
1309
- height: hole.hole_height,
1310
- fill: soldermaskOverlayColor,
1311
- realToCanvasMat,
1312
- rotation
1313
- });
1314
- }
790
+ drawPill({
791
+ ctx,
792
+ center: { x: hole.x, y: hole.y },
793
+ width: hole.hole_width - holeInset * 2,
794
+ height: hole.hole_height - holeInset * 2,
795
+ fill: colorMap.drill,
796
+ realToCanvasMat,
797
+ rotation
798
+ });
1315
799
  return;
1316
800
  }
1317
801
  }
@@ -1320,33 +804,16 @@ function drawPcbHole(params) {
1320
804
  function layerToColor(layer, colorMap) {
1321
805
  return colorMap.copper[layer] ?? colorMap.copper.top;
1322
806
  }
1323
- function getSoldermaskColor2(layer, colorMap) {
1324
- return colorMap.soldermaskOverCopper[layer] ?? colorMap.soldermaskOverCopper.top;
1325
- }
1326
- function getBorderRadius(pad, margin = 0) {
1327
- return (pad.corner_radius ?? pad.rect_border_radius ?? 0) + margin;
807
+ function getBorderRadius(pad) {
808
+ if (pad.shape === "rect" || pad.shape === "rotated_rect") {
809
+ return pad.corner_radius ?? pad.rect_border_radius ?? 0;
810
+ }
811
+ return 0;
1328
812
  }
1329
813
  function drawPcbSmtPad(params) {
1330
814
  const { ctx, pad, realToCanvasMat, colorMap } = params;
1331
815
  const color = layerToColor(pad.layer, colorMap);
1332
- const isCoveredWithSoldermask = pad.is_covered_with_solder_mask === true;
1333
- const margin = isCoveredWithSoldermask ? 0 : pad.soldermask_margin ?? 0;
1334
- const hasSoldermask = !isCoveredWithSoldermask && pad.soldermask_margin !== void 0 && pad.soldermask_margin !== 0;
1335
- const soldermaskRingColor = getSoldermaskColor2(pad.layer, colorMap);
1336
- const positiveMarginColor = colorMap.substrate;
1337
- const soldermaskOverlayColor = getSoldermaskColor2(pad.layer, colorMap);
1338
816
  if (pad.shape === "rect") {
1339
- if (hasSoldermask && margin > 0) {
1340
- drawRect({
1341
- ctx,
1342
- center: { x: pad.x, y: pad.y },
1343
- width: pad.width + margin * 2,
1344
- height: pad.height + margin * 2,
1345
- fill: positiveMarginColor,
1346
- realToCanvasMat,
1347
- borderRadius: getBorderRadius(pad)
1348
- });
1349
- }
1350
817
  drawRect({
1351
818
  ctx,
1352
819
  center: { x: pad.x, y: pad.y },
@@ -1356,46 +823,9 @@ function drawPcbSmtPad(params) {
1356
823
  realToCanvasMat,
1357
824
  borderRadius: getBorderRadius(pad)
1358
825
  });
1359
- if (hasSoldermask && margin < 0) {
1360
- drawSoldermaskRingForRect(
1361
- ctx,
1362
- { x: pad.x, y: pad.y },
1363
- pad.width,
1364
- pad.height,
1365
- margin,
1366
- getBorderRadius(pad),
1367
- 0,
1368
- realToCanvasMat,
1369
- soldermaskRingColor,
1370
- color
1371
- );
1372
- }
1373
- if (isCoveredWithSoldermask && margin === 0) {
1374
- drawRect({
1375
- ctx,
1376
- center: { x: pad.x, y: pad.y },
1377
- width: pad.width,
1378
- height: pad.height,
1379
- fill: soldermaskOverlayColor,
1380
- realToCanvasMat,
1381
- borderRadius: getBorderRadius(pad)
1382
- });
1383
- }
1384
826
  return;
1385
827
  }
1386
828
  if (pad.shape === "rotated_rect") {
1387
- if (hasSoldermask && margin > 0) {
1388
- drawRect({
1389
- ctx,
1390
- center: { x: pad.x, y: pad.y },
1391
- width: pad.width + margin * 2,
1392
- height: pad.height + margin * 2,
1393
- fill: positiveMarginColor,
1394
- realToCanvasMat,
1395
- borderRadius: getBorderRadius(pad),
1396
- rotation: pad.ccw_rotation ?? 0
1397
- });
1398
- }
1399
829
  drawRect({
1400
830
  ctx,
1401
831
  center: { x: pad.x, y: pad.y },
@@ -1406,44 +836,9 @@ function drawPcbSmtPad(params) {
1406
836
  borderRadius: getBorderRadius(pad),
1407
837
  rotation: pad.ccw_rotation ?? 0
1408
838
  });
1409
- if (hasSoldermask && margin < 0) {
1410
- drawSoldermaskRingForRect(
1411
- ctx,
1412
- { x: pad.x, y: pad.y },
1413
- pad.width,
1414
- pad.height,
1415
- margin,
1416
- getBorderRadius(pad),
1417
- pad.ccw_rotation ?? 0,
1418
- realToCanvasMat,
1419
- soldermaskRingColor,
1420
- color
1421
- );
1422
- }
1423
- if (isCoveredWithSoldermask && margin === 0) {
1424
- drawRect({
1425
- ctx,
1426
- center: { x: pad.x, y: pad.y },
1427
- width: pad.width,
1428
- height: pad.height,
1429
- fill: soldermaskOverlayColor,
1430
- realToCanvasMat,
1431
- borderRadius: getBorderRadius(pad),
1432
- rotation: pad.ccw_rotation ?? 0
1433
- });
1434
- }
1435
839
  return;
1436
840
  }
1437
841
  if (pad.shape === "circle") {
1438
- if (hasSoldermask && margin > 0) {
1439
- drawCircle({
1440
- ctx,
1441
- center: { x: pad.x, y: pad.y },
1442
- radius: pad.radius + margin,
1443
- fill: positiveMarginColor,
1444
- realToCanvasMat
1445
- });
1446
- }
1447
842
  drawCircle({
1448
843
  ctx,
1449
844
  center: { x: pad.x, y: pad.y },
@@ -1451,39 +846,9 @@ function drawPcbSmtPad(params) {
1451
846
  fill: color,
1452
847
  realToCanvasMat
1453
848
  });
1454
- if (hasSoldermask && margin < 0) {
1455
- drawSoldermaskRingForCircle(
1456
- ctx,
1457
- { x: pad.x, y: pad.y },
1458
- pad.radius,
1459
- margin,
1460
- realToCanvasMat,
1461
- soldermaskRingColor,
1462
- color
1463
- );
1464
- }
1465
- if (isCoveredWithSoldermask && margin === 0) {
1466
- drawCircle({
1467
- ctx,
1468
- center: { x: pad.x, y: pad.y },
1469
- radius: pad.radius,
1470
- fill: soldermaskOverlayColor,
1471
- realToCanvasMat
1472
- });
1473
- }
1474
849
  return;
1475
850
  }
1476
851
  if (pad.shape === "pill") {
1477
- if (hasSoldermask && margin > 0) {
1478
- drawPill({
1479
- ctx,
1480
- center: { x: pad.x, y: pad.y },
1481
- width: pad.width + margin * 2,
1482
- height: pad.height + margin * 2,
1483
- fill: positiveMarginColor,
1484
- realToCanvasMat
1485
- });
1486
- }
1487
852
  drawPill({
1488
853
  ctx,
1489
854
  center: { x: pad.x, y: pad.y },
@@ -1492,43 +857,9 @@ function drawPcbSmtPad(params) {
1492
857
  fill: color,
1493
858
  realToCanvasMat
1494
859
  });
1495
- if (hasSoldermask && margin < 0) {
1496
- drawSoldermaskRingForPill(
1497
- ctx,
1498
- { x: pad.x, y: pad.y },
1499
- pad.width,
1500
- pad.height,
1501
- margin,
1502
- 0,
1503
- realToCanvasMat,
1504
- soldermaskRingColor,
1505
- color
1506
- );
1507
- }
1508
- if (isCoveredWithSoldermask && margin === 0) {
1509
- drawPill({
1510
- ctx,
1511
- center: { x: pad.x, y: pad.y },
1512
- width: pad.width,
1513
- height: pad.height,
1514
- fill: soldermaskOverlayColor,
1515
- realToCanvasMat
1516
- });
1517
- }
1518
860
  return;
1519
861
  }
1520
862
  if (pad.shape === "rotated_pill") {
1521
- if (hasSoldermask && margin > 0) {
1522
- drawPill({
1523
- ctx,
1524
- center: { x: pad.x, y: pad.y },
1525
- width: pad.width + margin * 2,
1526
- height: pad.height + margin * 2,
1527
- fill: positiveMarginColor,
1528
- realToCanvasMat,
1529
- rotation: pad.ccw_rotation ?? 0
1530
- });
1531
- }
1532
863
  drawPill({
1533
864
  ctx,
1534
865
  center: { x: pad.x, y: pad.y },
@@ -1538,67 +869,16 @@ function drawPcbSmtPad(params) {
1538
869
  realToCanvasMat,
1539
870
  rotation: pad.ccw_rotation ?? 0
1540
871
  });
1541
- if (hasSoldermask && margin < 0) {
1542
- drawSoldermaskRingForPill(
1543
- ctx,
1544
- { x: pad.x, y: pad.y },
1545
- pad.width,
1546
- pad.height,
1547
- margin,
1548
- pad.ccw_rotation ?? 0,
1549
- realToCanvasMat,
1550
- soldermaskRingColor,
1551
- color
1552
- );
1553
- }
1554
- if (isCoveredWithSoldermask && margin === 0) {
1555
- drawPill({
1556
- ctx,
1557
- center: { x: pad.x, y: pad.y },
1558
- width: pad.width,
1559
- height: pad.height,
1560
- fill: soldermaskOverlayColor,
1561
- realToCanvasMat,
1562
- rotation: pad.ccw_rotation ?? 0
1563
- });
1564
- }
1565
872
  return;
1566
873
  }
1567
874
  if (pad.shape === "polygon") {
1568
875
  if (pad.points && pad.points.length >= 3) {
1569
- if (hasSoldermask && margin > 0) {
1570
- const expandedPoints = offsetPolygonPoints(pad.points, margin);
1571
- drawPolygon({
1572
- ctx,
1573
- points: expandedPoints,
1574
- fill: positiveMarginColor,
1575
- realToCanvasMat
1576
- });
1577
- }
1578
876
  drawPolygon({
1579
877
  ctx,
1580
878
  points: pad.points,
1581
879
  fill: color,
1582
880
  realToCanvasMat
1583
881
  });
1584
- if (hasSoldermask && margin < 0) {
1585
- drawSoldermaskRingForPolygon(
1586
- ctx,
1587
- pad.points,
1588
- margin,
1589
- realToCanvasMat,
1590
- soldermaskRingColor,
1591
- color
1592
- );
1593
- }
1594
- if (isCoveredWithSoldermask && margin === 0) {
1595
- drawPolygon({
1596
- ctx,
1597
- points: pad.points,
1598
- fill: soldermaskOverlayColor,
1599
- realToCanvasMat
1600
- });
1601
- }
1602
882
  }
1603
883
  return;
1604
884
  }
@@ -1704,6 +984,13 @@ function drawPcbBoard(params) {
1704
984
  const { ctx, board, realToCanvasMat, colorMap } = params;
1705
985
  const { width, height, center, outline } = board;
1706
986
  if (outline && Array.isArray(outline) && outline.length >= 3) {
987
+ drawPath({
988
+ ctx,
989
+ points: outline.map((p) => ({ x: p.x, y: p.y })),
990
+ fill: colorMap.substrate,
991
+ realToCanvasMat,
992
+ closePath: true
993
+ });
1707
994
  drawPath({
1708
995
  ctx,
1709
996
  points: outline.map((p) => ({ x: p.x, y: p.y })),
@@ -1720,7 +1007,7 @@ function drawPcbBoard(params) {
1720
1007
  center,
1721
1008
  width,
1722
1009
  height,
1723
- fill: "transparent",
1010
+ fill: colorMap.substrate,
1724
1011
  realToCanvasMat
1725
1012
  });
1726
1013
  const halfWidth = width / 2;
@@ -2784,359 +2071,1296 @@ function drawPcbNoteLine(params) {
2784
2071
  ctx.restore();
2785
2072
  }
2786
2073
 
2787
- // lib/drawer/CircuitToCanvasDrawer.ts
2788
- var CircuitToCanvasDrawer = class {
2789
- ctx;
2790
- colorMap;
2791
- realToCanvasMat;
2792
- constructor(canvasOrContext) {
2793
- if ("getContext" in canvasOrContext && typeof canvasOrContext.getContext === "function") {
2794
- const ctx = canvasOrContext.getContext("2d");
2795
- if (!ctx) {
2796
- throw new Error("Failed to get 2D rendering context from canvas");
2074
+ // lib/drawer/elements/pcb-soldermask/board.ts
2075
+ import { applyToPoint as applyToPoint16 } from "transformation-matrix";
2076
+
2077
+ // lib/drawer/elements/helper-functions/draw-polygon.ts
2078
+ function drawPolygonPath(params) {
2079
+ const { ctx, points } = params;
2080
+ if (points.length < 3) return;
2081
+ const firstPoint = points[0];
2082
+ if (firstPoint) {
2083
+ ctx.moveTo(firstPoint.x, firstPoint.y);
2084
+ for (let i = 1; i < points.length; i++) {
2085
+ const point = points[i];
2086
+ if (point) {
2087
+ ctx.lineTo(point.x, point.y);
2797
2088
  }
2798
- this.ctx = ctx;
2799
- } else {
2800
- this.ctx = canvasOrContext;
2801
- }
2802
- this.colorMap = { ...DEFAULT_PCB_COLOR_MAP };
2803
- this.realToCanvasMat = identity();
2804
- }
2805
- configure(config) {
2806
- if (config.colorOverrides) {
2807
- this.colorMap = {
2808
- ...this.colorMap,
2809
- ...config.colorOverrides,
2810
- copper: {
2811
- ...this.colorMap.copper,
2812
- ...config.colorOverrides.copper
2813
- },
2814
- silkscreen: {
2815
- ...this.colorMap.silkscreen,
2816
- ...config.colorOverrides.silkscreen
2817
- },
2818
- soldermask: {
2819
- ...this.colorMap.soldermask,
2820
- ...config.colorOverrides.soldermask
2821
- },
2822
- soldermaskWithCopperUnderneath: {
2823
- ...this.colorMap.soldermaskWithCopperUnderneath,
2824
- ...config.colorOverrides.soldermaskWithCopperUnderneath
2825
- },
2826
- soldermaskOverCopper: {
2827
- ...this.colorMap.soldermaskOverCopper,
2828
- ...config.colorOverrides.soldermaskOverCopper
2829
- }
2830
- };
2831
2089
  }
2090
+ ctx.closePath();
2832
2091
  }
2833
- setCameraBounds(bounds) {
2834
- const canvas = this.ctx.canvas;
2835
- const canvasWidth = canvas.width;
2836
- const canvasHeight = canvas.height;
2837
- const realWidth = bounds.maxX - bounds.minX;
2838
- const realHeight = bounds.maxY - bounds.minY;
2839
- const scaleX = canvasWidth / realWidth;
2840
- const scaleY = canvasHeight / realHeight;
2841
- const uniformScale = Math.min(scaleX, scaleY);
2842
- const offsetX = (canvasWidth - realWidth * uniformScale) / 2;
2843
- const offsetY = (canvasHeight - realHeight * uniformScale) / 2;
2844
- this.realToCanvasMat = compose(
2845
- translate(offsetX, offsetY),
2846
- scale(uniformScale, -uniformScale),
2847
- translate(-bounds.minX, -bounds.maxY)
2092
+ }
2093
+
2094
+ // lib/drawer/elements/pcb-soldermask/board.ts
2095
+ function drawBoardSoldermask(params) {
2096
+ const { ctx, board, realToCanvasMat, soldermaskColor } = params;
2097
+ const { width, height, center, outline } = board;
2098
+ if (outline && Array.isArray(outline) && outline.length >= 3) {
2099
+ const canvasPoints = outline.map((p) => {
2100
+ const [x, y] = applyToPoint16(realToCanvasMat, [p.x, p.y]);
2101
+ return { x, y };
2102
+ });
2103
+ ctx.beginPath();
2104
+ drawPolygonPath({ ctx, points: canvasPoints });
2105
+ ctx.fillStyle = soldermaskColor;
2106
+ ctx.fill();
2107
+ } else if (width !== void 0 && height !== void 0 && center) {
2108
+ const [cx, cy] = applyToPoint16(realToCanvasMat, [center.x, center.y]);
2109
+ const scaledWidth = width * Math.abs(realToCanvasMat.a);
2110
+ const scaledHeight = height * Math.abs(realToCanvasMat.a);
2111
+ ctx.fillStyle = soldermaskColor;
2112
+ ctx.fillRect(
2113
+ cx - scaledWidth / 2,
2114
+ cy - scaledHeight / 2,
2115
+ scaledWidth,
2116
+ scaledHeight
2848
2117
  );
2849
2118
  }
2850
- drawElements(elements, options = {}) {
2851
- const hasSoldermaskPads = elements.some(
2852
- (el) => el.type === "pcb_smtpad" && el.is_covered_with_solder_mask === true
2853
- );
2854
- const hasSoldermaskHoles = elements.some(
2855
- (el) => el.type === "pcb_hole" && el.is_covered_with_solder_mask === true
2856
- );
2857
- const hasSoldermaskPlatedHoles = elements.some(
2858
- (el) => el.type === "pcb_plated_hole" && el.is_covered_with_solder_mask === true
2859
- );
2860
- for (const element of elements) {
2861
- if (element.type === "pcb_board") {
2862
- this.drawBoardWithSoldermask(element);
2863
- } else {
2864
- this.drawElement(element, options);
2865
- }
2866
- }
2119
+ }
2120
+
2121
+ // lib/drawer/elements/pcb-soldermask/cutout.ts
2122
+ import { applyToPoint as applyToPoint17 } from "transformation-matrix";
2123
+
2124
+ // lib/drawer/elements/helper-functions/draw-rounded-rect.ts
2125
+ function drawRoundedRectPath(params) {
2126
+ const { ctx, cx, cy, width, height, radius } = params;
2127
+ const x = cx - width / 2;
2128
+ const y = cy - height / 2;
2129
+ const r = Math.min(radius, width / 2, height / 2);
2130
+ if (r > 0) {
2131
+ ctx.moveTo(x + r, y);
2132
+ ctx.lineTo(x + width - r, y);
2133
+ ctx.arcTo(x + width, y, x + width, y + r, r);
2134
+ ctx.lineTo(x + width, y + height - r);
2135
+ ctx.arcTo(x + width, y + height, x + width - r, y + height, r);
2136
+ ctx.lineTo(x + r, y + height);
2137
+ ctx.arcTo(x, y + height, x, y + height - r, r);
2138
+ ctx.lineTo(x, y + r);
2139
+ ctx.arcTo(x, y, x + r, y, r);
2140
+ } else {
2141
+ ctx.rect(x, y, width, height);
2867
2142
  }
2868
- drawBoardWithSoldermask(board) {
2869
- const { width, height, center, outline } = board;
2870
- const layer = "top";
2871
- if (outline && Array.isArray(outline) && outline.length >= 3) {
2872
- const soldermaskColor = this.colorMap.soldermask[layer] ?? this.colorMap.soldermask.top;
2873
- const canvasPoints = outline.map((p) => {
2874
- const [x, y] = applyToPoint16(this.realToCanvasMat, [p.x, p.y]);
2875
- return { x, y };
2876
- });
2877
- this.ctx.beginPath();
2878
- const firstPoint = canvasPoints[0];
2879
- if (firstPoint) {
2880
- this.ctx.moveTo(firstPoint.x, firstPoint.y);
2881
- for (let i = 1; i < canvasPoints.length; i++) {
2882
- const point = canvasPoints[i];
2883
- if (point) {
2884
- this.ctx.lineTo(point.x, point.y);
2885
- }
2886
- }
2887
- this.ctx.closePath();
2888
- }
2889
- this.ctx.fillStyle = soldermaskColor;
2890
- this.ctx.fill();
2891
- drawPath({
2892
- ctx: this.ctx,
2893
- points: outline.map((p) => ({ x: p.x, y: p.y })),
2894
- stroke: this.colorMap.boardOutline,
2895
- strokeWidth: 0.1,
2896
- realToCanvasMat: this.realToCanvasMat,
2897
- closePath: true
2898
- });
2899
- return;
2143
+ ctx.closePath();
2144
+ }
2145
+
2146
+ // lib/drawer/elements/pcb-soldermask/cutout.ts
2147
+ function processCutoutSoldermask(params) {
2148
+ const { ctx, cutout, realToCanvasMat, colorMap } = params;
2149
+ ctx.fillStyle = colorMap.drill;
2150
+ if (cutout.shape === "rect") {
2151
+ const [cx, cy] = applyToPoint17(realToCanvasMat, [
2152
+ cutout.center?.x ?? 0,
2153
+ cutout.center?.y ?? 0
2154
+ ]);
2155
+ const scaledWidth = cutout.width * Math.abs(realToCanvasMat.a);
2156
+ const scaledHeight = cutout.height * Math.abs(realToCanvasMat.a);
2157
+ const scaledRadius = (cutout.corner_radius ?? 0) * Math.abs(realToCanvasMat.a);
2158
+ ctx.save();
2159
+ ctx.translate(cx, cy);
2160
+ if (cutout.rotation) {
2161
+ ctx.rotate(-cutout.rotation * Math.PI / 180);
2900
2162
  }
2901
- if (width !== void 0 && height !== void 0 && center) {
2902
- const soldermaskColor = this.colorMap.soldermask[layer] ?? this.colorMap.soldermask.top;
2903
- drawRect({
2904
- ctx: this.ctx,
2905
- center,
2906
- width,
2907
- height,
2908
- fill: soldermaskColor,
2909
- realToCanvasMat: this.realToCanvasMat
2910
- });
2911
- const halfWidth = width / 2;
2912
- const halfHeight = height / 2;
2913
- const corners = [
2914
- { x: center.x - halfWidth, y: center.y - halfHeight },
2915
- { x: center.x + halfWidth, y: center.y - halfHeight },
2916
- { x: center.x + halfWidth, y: center.y + halfHeight },
2917
- { x: center.x - halfWidth, y: center.y + halfHeight }
2918
- ];
2919
- drawPath({
2920
- ctx: this.ctx,
2921
- points: corners,
2922
- stroke: this.colorMap.boardOutline,
2923
- strokeWidth: 0.1,
2924
- realToCanvasMat: this.realToCanvasMat,
2925
- closePath: true
2926
- });
2163
+ ctx.beginPath();
2164
+ drawRoundedRectPath({
2165
+ ctx,
2166
+ cx: 0,
2167
+ cy: 0,
2168
+ width: scaledWidth,
2169
+ height: scaledHeight,
2170
+ radius: scaledRadius
2171
+ });
2172
+ ctx.restore();
2173
+ ctx.fill();
2174
+ } else if (cutout.shape === "circle") {
2175
+ const [cx, cy] = applyToPoint17(realToCanvasMat, [
2176
+ cutout.center?.x ?? 0,
2177
+ cutout.center?.y ?? 0
2178
+ ]);
2179
+ const scaledRadius = cutout.radius * Math.abs(realToCanvasMat.a);
2180
+ ctx.beginPath();
2181
+ ctx.arc(cx, cy, scaledRadius, 0, Math.PI * 2);
2182
+ ctx.closePath();
2183
+ ctx.fill();
2184
+ } else if (cutout.shape === "polygon" && cutout.points && cutout.points.length >= 3) {
2185
+ const canvasPoints = cutout.points.map((p) => {
2186
+ const [x, y] = applyToPoint17(realToCanvasMat, [p.x, p.y]);
2187
+ return { x, y };
2188
+ });
2189
+ ctx.beginPath();
2190
+ drawPolygonPath({ ctx, points: canvasPoints });
2191
+ ctx.fill();
2192
+ }
2193
+ }
2194
+
2195
+ // lib/drawer/elements/pcb-soldermask/hole.ts
2196
+ import { applyToPoint as applyToPoint18 } from "transformation-matrix";
2197
+
2198
+ // lib/drawer/elements/helper-functions/draw-pill.ts
2199
+ function drawPillPath(params) {
2200
+ const { ctx, cx, cy, width, height } = params;
2201
+ if (width > height) {
2202
+ const radius = height / 2;
2203
+ const straightLength = width - height;
2204
+ ctx.moveTo(cx - straightLength / 2, cy - radius);
2205
+ ctx.lineTo(cx + straightLength / 2, cy - radius);
2206
+ ctx.arc(cx + straightLength / 2, cy, radius, -Math.PI / 2, Math.PI / 2);
2207
+ ctx.lineTo(cx - straightLength / 2, cy + radius);
2208
+ ctx.arc(cx - straightLength / 2, cy, radius, Math.PI / 2, -Math.PI / 2);
2209
+ } else if (height > width) {
2210
+ const radius = width / 2;
2211
+ const straightLength = height - width;
2212
+ ctx.moveTo(cx + radius, cy - straightLength / 2);
2213
+ ctx.lineTo(cx + radius, cy + straightLength / 2);
2214
+ ctx.arc(cx, cy + straightLength / 2, radius, 0, Math.PI);
2215
+ ctx.lineTo(cx - radius, cy - straightLength / 2);
2216
+ ctx.arc(cx, cy - straightLength / 2, radius, Math.PI, 0);
2217
+ } else {
2218
+ ctx.arc(cx, cy, width / 2, 0, Math.PI * 2);
2219
+ }
2220
+ ctx.closePath();
2221
+ }
2222
+
2223
+ // lib/drawer/elements/pcb-soldermask/hole.ts
2224
+ function processHoleSoldermask(params) {
2225
+ const {
2226
+ ctx,
2227
+ hole,
2228
+ realToCanvasMat,
2229
+ colorMap,
2230
+ soldermaskOverCopperColor,
2231
+ showSoldermask
2232
+ } = params;
2233
+ const isCoveredWithSoldermask = showSoldermask && hole.is_covered_with_solder_mask === true;
2234
+ const margin = showSoldermask ? hole.soldermask_margin ?? 0 : 0;
2235
+ if (isCoveredWithSoldermask) {
2236
+ ctx.fillStyle = soldermaskOverCopperColor;
2237
+ drawHoleShapePath({ ctx, hole, realToCanvasMat, margin: 0 });
2238
+ ctx.fill();
2239
+ } else if (margin < 0) {
2240
+ ctx.fillStyle = colorMap.drill;
2241
+ drawHoleShapePath({ ctx, hole, realToCanvasMat, margin: 0 });
2242
+ ctx.fill();
2243
+ drawNegativeMarginRingForHole({
2244
+ ctx,
2245
+ hole,
2246
+ realToCanvasMat,
2247
+ soldermaskOverCopperColor,
2248
+ margin
2249
+ });
2250
+ } else if (margin > 0) {
2251
+ ctx.fillStyle = colorMap.substrate;
2252
+ drawHoleShapePath({ ctx, hole, realToCanvasMat, margin });
2253
+ ctx.fill();
2254
+ ctx.fillStyle = colorMap.drill;
2255
+ drawHoleShapePath({ ctx, hole, realToCanvasMat, margin: 0 });
2256
+ ctx.fill();
2257
+ } else {
2258
+ ctx.fillStyle = colorMap.drill;
2259
+ drawHoleShapePath({ ctx, hole, realToCanvasMat, margin: 0 });
2260
+ ctx.fill();
2261
+ }
2262
+ }
2263
+ function getHoleRotation(hole) {
2264
+ if ("ccw_rotation" in hole && typeof hole.ccw_rotation === "number") {
2265
+ return hole.ccw_rotation;
2266
+ }
2267
+ return 0;
2268
+ }
2269
+ function drawHoleShapePath(params) {
2270
+ const { ctx, hole, realToCanvasMat, margin } = params;
2271
+ const rotation = getHoleRotation(hole);
2272
+ if (hole.hole_shape === "circle") {
2273
+ const [cx, cy] = applyToPoint18(realToCanvasMat, [hole.x, hole.y]);
2274
+ const scaledRadius = (hole.hole_diameter / 2 + margin) * Math.abs(realToCanvasMat.a);
2275
+ ctx.beginPath();
2276
+ ctx.arc(cx, cy, scaledRadius, 0, Math.PI * 2);
2277
+ ctx.closePath();
2278
+ } else if (hole.hole_shape === "square") {
2279
+ const [cx, cy] = applyToPoint18(realToCanvasMat, [hole.x, hole.y]);
2280
+ const scaledSize = (hole.hole_diameter + margin * 2) * Math.abs(realToCanvasMat.a);
2281
+ ctx.save();
2282
+ ctx.translate(cx, cy);
2283
+ if (rotation !== 0) {
2284
+ ctx.rotate(-rotation * Math.PI / 180);
2927
2285
  }
2286
+ ctx.beginPath();
2287
+ ctx.rect(-scaledSize / 2, -scaledSize / 2, scaledSize, scaledSize);
2288
+ ctx.closePath();
2289
+ ctx.restore();
2290
+ } else if (hole.hole_shape === "oval") {
2291
+ const [cx, cy] = applyToPoint18(realToCanvasMat, [hole.x, hole.y]);
2292
+ const scaledRadiusX = (hole.hole_width / 2 + margin) * Math.abs(realToCanvasMat.a);
2293
+ const scaledRadiusY = (hole.hole_height / 2 + margin) * Math.abs(realToCanvasMat.a);
2294
+ ctx.save();
2295
+ ctx.translate(cx, cy);
2296
+ if (rotation !== 0) {
2297
+ ctx.rotate(-rotation * Math.PI / 180);
2298
+ }
2299
+ ctx.beginPath();
2300
+ ctx.ellipse(0, 0, scaledRadiusX, scaledRadiusY, 0, 0, Math.PI * 2);
2301
+ ctx.closePath();
2302
+ ctx.restore();
2303
+ } else if (hole.hole_shape === "rect") {
2304
+ const [cx, cy] = applyToPoint18(realToCanvasMat, [hole.x, hole.y]);
2305
+ const scaledWidth = (hole.hole_width + margin * 2) * Math.abs(realToCanvasMat.a);
2306
+ const scaledHeight = (hole.hole_height + margin * 2) * Math.abs(realToCanvasMat.a);
2307
+ ctx.save();
2308
+ ctx.translate(cx, cy);
2309
+ if (rotation !== 0) {
2310
+ ctx.rotate(-rotation * Math.PI / 180);
2311
+ }
2312
+ ctx.beginPath();
2313
+ ctx.rect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight);
2314
+ ctx.closePath();
2315
+ ctx.restore();
2316
+ } else if (hole.hole_shape === "pill" || hole.hole_shape === "rotated_pill") {
2317
+ const [cx, cy] = applyToPoint18(realToCanvasMat, [hole.x, hole.y]);
2318
+ const scaledWidth = (hole.hole_width + margin * 2) * Math.abs(realToCanvasMat.a);
2319
+ const scaledHeight = (hole.hole_height + margin * 2) * Math.abs(realToCanvasMat.a);
2320
+ ctx.save();
2321
+ ctx.translate(cx, cy);
2322
+ if (rotation !== 0) {
2323
+ ctx.rotate(-rotation * Math.PI / 180);
2324
+ }
2325
+ ctx.beginPath();
2326
+ drawPillPath({
2327
+ ctx,
2328
+ cx: 0,
2329
+ cy: 0,
2330
+ width: scaledWidth,
2331
+ height: scaledHeight
2332
+ });
2333
+ ctx.restore();
2928
2334
  }
2929
- drawElement(element, options) {
2930
- if (!shouldDrawElement(element, options)) {
2931
- return;
2335
+ }
2336
+ function drawNegativeMarginRingForHole(params) {
2337
+ const { ctx, hole, realToCanvasMat, soldermaskOverCopperColor, margin } = params;
2338
+ const thickness = Math.abs(margin);
2339
+ const rotation = getHoleRotation(hole);
2340
+ ctx.fillStyle = soldermaskOverCopperColor;
2341
+ if (hole.hole_shape === "circle") {
2342
+ const [cx, cy] = applyToPoint18(realToCanvasMat, [hole.x, hole.y]);
2343
+ const scaledRadius = hole.hole_diameter / 2 * Math.abs(realToCanvasMat.a);
2344
+ const scaledThickness = thickness * Math.abs(realToCanvasMat.a);
2345
+ const innerRadius = Math.max(0, scaledRadius - scaledThickness);
2346
+ ctx.save();
2347
+ ctx.beginPath();
2348
+ ctx.arc(cx, cy, scaledRadius, 0, Math.PI * 2);
2349
+ if (innerRadius > 0) {
2350
+ ctx.arc(cx, cy, innerRadius, 0, Math.PI * 2, true);
2932
2351
  }
2933
- if (element.type === "pcb_plated_hole") {
2934
- drawPcbPlatedHole({
2935
- ctx: this.ctx,
2936
- hole: element,
2937
- realToCanvasMat: this.realToCanvasMat,
2938
- colorMap: this.colorMap
2939
- });
2352
+ ctx.fill("evenodd");
2353
+ ctx.restore();
2354
+ } else if (hole.hole_shape === "square") {
2355
+ const [cx, cy] = applyToPoint18(realToCanvasMat, [hole.x, hole.y]);
2356
+ const scaledSize = hole.hole_diameter * Math.abs(realToCanvasMat.a);
2357
+ const scaledThickness = thickness * Math.abs(realToCanvasMat.a);
2358
+ const innerSize = Math.max(0, scaledSize - scaledThickness * 2);
2359
+ ctx.save();
2360
+ ctx.translate(cx, cy);
2361
+ if (rotation !== 0) {
2362
+ ctx.rotate(-rotation * Math.PI / 180);
2940
2363
  }
2941
- if (element.type === "pcb_via") {
2942
- drawPcbVia({
2943
- ctx: this.ctx,
2944
- via: element,
2945
- realToCanvasMat: this.realToCanvasMat,
2946
- colorMap: this.colorMap
2947
- });
2364
+ ctx.beginPath();
2365
+ ctx.rect(-scaledSize / 2, -scaledSize / 2, scaledSize, scaledSize);
2366
+ if (innerSize > 0) {
2367
+ ctx.rect(-innerSize / 2, -innerSize / 2, innerSize, innerSize);
2948
2368
  }
2949
- if (element.type === "pcb_hole") {
2950
- drawPcbHole({
2951
- ctx: this.ctx,
2952
- hole: element,
2953
- realToCanvasMat: this.realToCanvasMat,
2954
- colorMap: this.colorMap
2955
- });
2369
+ ctx.fill("evenodd");
2370
+ ctx.restore();
2371
+ } else if (hole.hole_shape === "oval") {
2372
+ const [cx, cy] = applyToPoint18(realToCanvasMat, [hole.x, hole.y]);
2373
+ const scaledRadiusX = hole.hole_width / 2 * Math.abs(realToCanvasMat.a);
2374
+ const scaledRadiusY = hole.hole_height / 2 * Math.abs(realToCanvasMat.a);
2375
+ const scaledThickness = thickness * Math.abs(realToCanvasMat.a);
2376
+ const innerRadiusX = Math.max(0, scaledRadiusX - scaledThickness);
2377
+ const innerRadiusY = Math.max(0, scaledRadiusY - scaledThickness);
2378
+ ctx.save();
2379
+ ctx.translate(cx, cy);
2380
+ if (rotation !== 0) {
2381
+ ctx.rotate(-rotation * Math.PI / 180);
2956
2382
  }
2957
- if (element.type === "pcb_smtpad") {
2958
- drawPcbSmtPad({
2959
- ctx: this.ctx,
2960
- pad: element,
2961
- realToCanvasMat: this.realToCanvasMat,
2962
- colorMap: this.colorMap
2963
- });
2383
+ ctx.beginPath();
2384
+ ctx.ellipse(0, 0, scaledRadiusX, scaledRadiusY, 0, 0, Math.PI * 2);
2385
+ if (innerRadiusX > 0 && innerRadiusY > 0) {
2386
+ ctx.moveTo(innerRadiusX, 0);
2387
+ ctx.ellipse(0, 0, innerRadiusX, innerRadiusY, 0, 0, Math.PI * 2);
2964
2388
  }
2965
- if (element.type === "pcb_trace") {
2966
- drawPcbTrace({
2967
- ctx: this.ctx,
2968
- trace: element,
2969
- realToCanvasMat: this.realToCanvasMat,
2970
- colorMap: this.colorMap
2971
- });
2389
+ ctx.fill("evenodd");
2390
+ ctx.restore();
2391
+ } else if (hole.hole_shape === "rect") {
2392
+ const [cx, cy] = applyToPoint18(realToCanvasMat, [hole.x, hole.y]);
2393
+ const scaledWidth = hole.hole_width * Math.abs(realToCanvasMat.a);
2394
+ const scaledHeight = hole.hole_height * Math.abs(realToCanvasMat.a);
2395
+ const scaledThickness = thickness * Math.abs(realToCanvasMat.a);
2396
+ const innerWidth = Math.max(0, scaledWidth - scaledThickness * 2);
2397
+ const innerHeight = Math.max(0, scaledHeight - scaledThickness * 2);
2398
+ ctx.save();
2399
+ ctx.translate(cx, cy);
2400
+ if (rotation !== 0) {
2401
+ ctx.rotate(-rotation * Math.PI / 180);
2972
2402
  }
2973
- if (element.type === "pcb_board") {
2974
- drawPcbBoard({
2975
- ctx: this.ctx,
2976
- board: element,
2977
- realToCanvasMat: this.realToCanvasMat,
2978
- colorMap: this.colorMap
2403
+ ctx.beginPath();
2404
+ ctx.rect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight);
2405
+ if (innerWidth > 0 && innerHeight > 0) {
2406
+ ctx.rect(-innerWidth / 2, -innerHeight / 2, innerWidth, innerHeight);
2407
+ }
2408
+ ctx.fill("evenodd");
2409
+ ctx.restore();
2410
+ } else if (hole.hole_shape === "pill" || hole.hole_shape === "rotated_pill") {
2411
+ const [cx, cy] = applyToPoint18(realToCanvasMat, [hole.x, hole.y]);
2412
+ const scaledWidth = hole.hole_width * Math.abs(realToCanvasMat.a);
2413
+ const scaledHeight = hole.hole_height * Math.abs(realToCanvasMat.a);
2414
+ const scaledThickness = thickness * Math.abs(realToCanvasMat.a);
2415
+ ctx.save();
2416
+ ctx.translate(cx, cy);
2417
+ if (rotation !== 0) {
2418
+ ctx.rotate(-rotation * Math.PI / 180);
2419
+ }
2420
+ ctx.beginPath();
2421
+ drawPillPath({
2422
+ ctx,
2423
+ cx: 0,
2424
+ cy: 0,
2425
+ width: scaledWidth,
2426
+ height: scaledHeight
2427
+ });
2428
+ const innerWidth = scaledWidth - scaledThickness * 2;
2429
+ const innerHeight = scaledHeight - scaledThickness * 2;
2430
+ if (innerWidth > 0 && innerHeight > 0) {
2431
+ drawPillPath({
2432
+ ctx,
2433
+ cx: 0,
2434
+ cy: 0,
2435
+ width: innerWidth,
2436
+ height: innerHeight
2979
2437
  });
2980
2438
  }
2981
- if (element.type === "pcb_silkscreen_text") {
2982
- drawPcbSilkscreenText({
2983
- ctx: this.ctx,
2984
- text: element,
2985
- realToCanvasMat: this.realToCanvasMat,
2986
- colorMap: this.colorMap
2439
+ ctx.fill("evenodd");
2440
+ ctx.restore();
2441
+ }
2442
+ }
2443
+
2444
+ // lib/drawer/elements/pcb-soldermask/plated-hole.ts
2445
+ import { applyToPoint as applyToPoint19 } from "transformation-matrix";
2446
+ function processPlatedHoleSoldermask(params) {
2447
+ const {
2448
+ ctx,
2449
+ hole,
2450
+ realToCanvasMat,
2451
+ colorMap,
2452
+ soldermaskOverCopperColor,
2453
+ layer,
2454
+ showSoldermask
2455
+ } = params;
2456
+ if (hole.layers && !hole.layers.includes(layer)) return;
2457
+ const isCoveredWithSoldermask = showSoldermask && hole.is_covered_with_solder_mask === true;
2458
+ const margin = showSoldermask ? hole.soldermask_margin ?? 0 : 0;
2459
+ const copperColor = colorMap.copper.top;
2460
+ if (isCoveredWithSoldermask) {
2461
+ ctx.fillStyle = soldermaskOverCopperColor;
2462
+ drawPlatedHoleShapePath({ ctx, hole, realToCanvasMat, margin: 0 });
2463
+ ctx.fill();
2464
+ } else if (margin < 0) {
2465
+ ctx.fillStyle = copperColor;
2466
+ drawPlatedHoleShapePath({ ctx, hole, realToCanvasMat, margin: 0 });
2467
+ ctx.fill();
2468
+ drawNegativeMarginRingForPlatedHole({
2469
+ ctx,
2470
+ hole,
2471
+ realToCanvasMat,
2472
+ soldermaskOverCopperColor,
2473
+ margin
2474
+ });
2475
+ } else if (margin > 0) {
2476
+ ctx.fillStyle = colorMap.substrate;
2477
+ drawPlatedHoleShapePath({ ctx, hole, realToCanvasMat, margin });
2478
+ ctx.fill();
2479
+ ctx.fillStyle = copperColor;
2480
+ drawPlatedHoleShapePath({ ctx, hole, realToCanvasMat, margin: 0 });
2481
+ ctx.fill();
2482
+ } else {
2483
+ ctx.fillStyle = copperColor;
2484
+ drawPlatedHoleShapePath({ ctx, hole, realToCanvasMat, margin: 0 });
2485
+ ctx.fill();
2486
+ }
2487
+ }
2488
+ function drawPlatedHoleShapePath(params) {
2489
+ const { ctx, hole, realToCanvasMat, margin } = params;
2490
+ if (hole.shape === "circle") {
2491
+ const [cx, cy] = applyToPoint19(realToCanvasMat, [hole.x, hole.y]);
2492
+ const scaledRadius = (hole.outer_diameter / 2 + margin) * Math.abs(realToCanvasMat.a);
2493
+ ctx.beginPath();
2494
+ ctx.arc(cx, cy, scaledRadius, 0, Math.PI * 2);
2495
+ ctx.closePath();
2496
+ } else if (hole.shape === "oval") {
2497
+ const [cx, cy] = applyToPoint19(realToCanvasMat, [hole.x, hole.y]);
2498
+ const scaledRadiusX = (hole.outer_width / 2 + margin) * Math.abs(realToCanvasMat.a);
2499
+ const scaledRadiusY = (hole.outer_height / 2 + margin) * Math.abs(realToCanvasMat.a);
2500
+ ctx.save();
2501
+ ctx.translate(cx, cy);
2502
+ if (hole.ccw_rotation && hole.ccw_rotation !== 0) {
2503
+ ctx.rotate(-(hole.ccw_rotation * Math.PI) / 180);
2504
+ }
2505
+ ctx.beginPath();
2506
+ ctx.ellipse(0, 0, scaledRadiusX, scaledRadiusY, 0, 0, Math.PI * 2);
2507
+ ctx.closePath();
2508
+ ctx.restore();
2509
+ } else if (hole.shape === "pill") {
2510
+ const [cx, cy] = applyToPoint19(realToCanvasMat, [hole.x, hole.y]);
2511
+ const scaledWidth = (hole.outer_width + margin * 2) * Math.abs(realToCanvasMat.a);
2512
+ const scaledHeight = (hole.outer_height + margin * 2) * Math.abs(realToCanvasMat.a);
2513
+ ctx.save();
2514
+ ctx.translate(cx, cy);
2515
+ if (hole.ccw_rotation && hole.ccw_rotation !== 0) {
2516
+ ctx.rotate(-(hole.ccw_rotation * Math.PI) / 180);
2517
+ }
2518
+ ctx.beginPath();
2519
+ drawPillPath({
2520
+ ctx,
2521
+ cx: 0,
2522
+ cy: 0,
2523
+ width: scaledWidth,
2524
+ height: scaledHeight
2525
+ });
2526
+ ctx.restore();
2527
+ } else if (hole.shape === "circular_hole_with_rect_pad" || hole.shape === "pill_hole_with_rect_pad") {
2528
+ const [cx, cy] = applyToPoint19(realToCanvasMat, [hole.x, hole.y]);
2529
+ const scaledWidth = (hole.rect_pad_width + margin * 2) * Math.abs(realToCanvasMat.a);
2530
+ const scaledHeight = (hole.rect_pad_height + margin * 2) * Math.abs(realToCanvasMat.a);
2531
+ const scaledRadius = (hole.rect_border_radius ?? 0) * Math.abs(realToCanvasMat.a);
2532
+ ctx.beginPath();
2533
+ drawRoundedRectPath({
2534
+ ctx,
2535
+ cx,
2536
+ cy,
2537
+ width: scaledWidth,
2538
+ height: scaledHeight,
2539
+ radius: scaledRadius
2540
+ });
2541
+ } else if (hole.shape === "rotated_pill_hole_with_rect_pad") {
2542
+ const [cx, cy] = applyToPoint19(realToCanvasMat, [hole.x, hole.y]);
2543
+ const scaledWidth = (hole.rect_pad_width + margin * 2) * Math.abs(realToCanvasMat.a);
2544
+ const scaledHeight = (hole.rect_pad_height + margin * 2) * Math.abs(realToCanvasMat.a);
2545
+ const scaledRadius = (hole.rect_border_radius ?? 0) * Math.abs(realToCanvasMat.a);
2546
+ ctx.save();
2547
+ ctx.translate(cx, cy);
2548
+ if (hole.rect_ccw_rotation) {
2549
+ ctx.rotate(-hole.rect_ccw_rotation * Math.PI / 180);
2550
+ }
2551
+ ctx.beginPath();
2552
+ drawRoundedRectPath({
2553
+ ctx,
2554
+ cx: 0,
2555
+ cy: 0,
2556
+ width: scaledWidth,
2557
+ height: scaledHeight,
2558
+ radius: scaledRadius
2559
+ });
2560
+ ctx.restore();
2561
+ } else if (hole.shape === "hole_with_polygon_pad" && hole.pad_outline && hole.pad_outline.length >= 3) {
2562
+ const padPoints = hole.pad_outline.map(
2563
+ (point) => ({
2564
+ x: hole.x + point.x,
2565
+ y: hole.y + point.y
2566
+ })
2567
+ );
2568
+ const points = margin !== 0 ? offsetPolygonPoints(padPoints, margin) : padPoints;
2569
+ const canvasPoints = points.map((p) => {
2570
+ const [x, y] = applyToPoint19(realToCanvasMat, [p.x, p.y]);
2571
+ return { x, y };
2572
+ });
2573
+ ctx.beginPath();
2574
+ drawPolygonPath({ ctx, points: canvasPoints });
2575
+ }
2576
+ }
2577
+ function drawNegativeMarginRingForPlatedHole(params) {
2578
+ const { ctx, hole, realToCanvasMat, soldermaskOverCopperColor, margin } = params;
2579
+ const thickness = Math.abs(margin);
2580
+ ctx.fillStyle = soldermaskOverCopperColor;
2581
+ if (hole.shape === "circle") {
2582
+ const [cx, cy] = applyToPoint19(realToCanvasMat, [hole.x, hole.y]);
2583
+ const scaledOuterRadius = hole.outer_diameter / 2 * Math.abs(realToCanvasMat.a);
2584
+ const scaledThickness = thickness * Math.abs(realToCanvasMat.a);
2585
+ const innerRadius = Math.max(0, scaledOuterRadius - scaledThickness);
2586
+ ctx.save();
2587
+ ctx.beginPath();
2588
+ ctx.arc(cx, cy, scaledOuterRadius, 0, Math.PI * 2);
2589
+ if (innerRadius > 0) {
2590
+ ctx.arc(cx, cy, innerRadius, 0, Math.PI * 2, true);
2591
+ }
2592
+ ctx.fill("evenodd");
2593
+ ctx.restore();
2594
+ } else if (hole.shape === "oval") {
2595
+ const [cx, cy] = applyToPoint19(realToCanvasMat, [hole.x, hole.y]);
2596
+ const scaledRadiusX = hole.outer_width / 2 * Math.abs(realToCanvasMat.a);
2597
+ const scaledRadiusY = hole.outer_height / 2 * Math.abs(realToCanvasMat.a);
2598
+ const scaledThickness = thickness * Math.abs(realToCanvasMat.a);
2599
+ const innerRadiusX = Math.max(0, scaledRadiusX - scaledThickness);
2600
+ const innerRadiusY = Math.max(0, scaledRadiusY - scaledThickness);
2601
+ ctx.save();
2602
+ ctx.translate(cx, cy);
2603
+ if (hole.ccw_rotation && hole.ccw_rotation !== 0) {
2604
+ ctx.rotate(-(hole.ccw_rotation * Math.PI) / 180);
2605
+ }
2606
+ ctx.beginPath();
2607
+ ctx.ellipse(0, 0, scaledRadiusX, scaledRadiusY, 0, 0, Math.PI * 2);
2608
+ if (innerRadiusX > 0 && innerRadiusY > 0) {
2609
+ ctx.moveTo(innerRadiusX, 0);
2610
+ ctx.ellipse(0, 0, innerRadiusX, innerRadiusY, 0, 0, Math.PI * 2);
2611
+ }
2612
+ ctx.fill("evenodd");
2613
+ ctx.restore();
2614
+ } else if (hole.shape === "pill") {
2615
+ const [cx, cy] = applyToPoint19(realToCanvasMat, [hole.x, hole.y]);
2616
+ const scaledWidth = hole.outer_width * Math.abs(realToCanvasMat.a);
2617
+ const scaledHeight = hole.outer_height * Math.abs(realToCanvasMat.a);
2618
+ const scaledThickness = thickness * Math.abs(realToCanvasMat.a);
2619
+ ctx.save();
2620
+ ctx.translate(cx, cy);
2621
+ if (hole.ccw_rotation && hole.ccw_rotation !== 0) {
2622
+ ctx.rotate(-(hole.ccw_rotation * Math.PI) / 180);
2623
+ }
2624
+ ctx.beginPath();
2625
+ drawPillPath({
2626
+ ctx,
2627
+ cx: 0,
2628
+ cy: 0,
2629
+ width: scaledWidth,
2630
+ height: scaledHeight
2631
+ });
2632
+ const innerWidth = scaledWidth - scaledThickness * 2;
2633
+ const innerHeight = scaledHeight - scaledThickness * 2;
2634
+ if (innerWidth > 0 && innerHeight > 0) {
2635
+ drawPillPath({
2636
+ ctx,
2637
+ cx: 0,
2638
+ cy: 0,
2639
+ width: innerWidth,
2640
+ height: innerHeight
2987
2641
  });
2988
2642
  }
2989
- if (element.type === "pcb_silkscreen_rect") {
2990
- drawPcbSilkscreenRect({
2991
- ctx: this.ctx,
2992
- rect: element,
2993
- realToCanvasMat: this.realToCanvasMat,
2994
- colorMap: this.colorMap
2643
+ ctx.fill("evenodd");
2644
+ ctx.restore();
2645
+ } else if (hole.shape === "circular_hole_with_rect_pad" || hole.shape === "pill_hole_with_rect_pad" || hole.shape === "rotated_pill_hole_with_rect_pad") {
2646
+ const [cx, cy] = applyToPoint19(realToCanvasMat, [hole.x, hole.y]);
2647
+ const scaledWidth = hole.rect_pad_width * Math.abs(realToCanvasMat.a);
2648
+ const scaledHeight = hole.rect_pad_height * Math.abs(realToCanvasMat.a);
2649
+ const scaledThickness = thickness * Math.abs(realToCanvasMat.a);
2650
+ ctx.save();
2651
+ ctx.translate(cx, cy);
2652
+ if (hole.shape === "rotated_pill_hole_with_rect_pad" && hole.rect_ccw_rotation) {
2653
+ ctx.rotate(-hole.rect_ccw_rotation * Math.PI / 180);
2654
+ }
2655
+ const outerRadius = hole.rect_border_radius ? hole.rect_border_radius * Math.abs(realToCanvasMat.a) : 0;
2656
+ ctx.beginPath();
2657
+ drawRoundedRectPath({
2658
+ ctx,
2659
+ cx: 0,
2660
+ cy: 0,
2661
+ width: scaledWidth,
2662
+ height: scaledHeight,
2663
+ radius: outerRadius
2664
+ });
2665
+ const innerWidth = scaledWidth - scaledThickness * 2;
2666
+ const innerHeight = scaledHeight - scaledThickness * 2;
2667
+ if (innerWidth > 0 && innerHeight > 0) {
2668
+ const innerRadius = Math.max(0, outerRadius - scaledThickness);
2669
+ drawRoundedRectPath({
2670
+ ctx,
2671
+ cx: 0,
2672
+ cy: 0,
2673
+ width: innerWidth,
2674
+ height: innerHeight,
2675
+ radius: innerRadius
2995
2676
  });
2996
2677
  }
2997
- if (element.type === "pcb_silkscreen_circle") {
2998
- drawPcbSilkscreenCircle({
2999
- ctx: this.ctx,
3000
- circle: element,
3001
- realToCanvasMat: this.realToCanvasMat,
3002
- colorMap: this.colorMap
2678
+ ctx.fill("evenodd");
2679
+ ctx.restore();
2680
+ } else if (hole.shape === "hole_with_polygon_pad" && hole.pad_outline && hole.pad_outline.length >= 3) {
2681
+ const padPoints = hole.pad_outline.map(
2682
+ (point) => ({
2683
+ x: hole.x + point.x,
2684
+ y: hole.y + point.y
2685
+ })
2686
+ );
2687
+ ctx.save();
2688
+ ctx.beginPath();
2689
+ const canvasPoints = padPoints.map((p) => {
2690
+ const [x, y] = applyToPoint19(realToCanvasMat, [p.x, p.y]);
2691
+ return { x, y };
2692
+ });
2693
+ drawPolygonPath({ ctx, points: canvasPoints });
2694
+ const innerPoints = offsetPolygonPoints(padPoints, -thickness);
2695
+ if (innerPoints.length >= 3) {
2696
+ const innerCanvasPoints = innerPoints.map((p) => {
2697
+ const [x, y] = applyToPoint19(realToCanvasMat, [p.x, p.y]);
2698
+ return { x, y };
3003
2699
  });
2700
+ drawPolygonPath({ ctx, points: innerCanvasPoints });
2701
+ }
2702
+ ctx.fill("evenodd");
2703
+ ctx.restore();
2704
+ }
2705
+ }
2706
+
2707
+ // lib/drawer/elements/pcb-soldermask/smt-pad.ts
2708
+ import { applyToPoint as applyToPoint20 } from "transformation-matrix";
2709
+ function processSmtPadSoldermask(params) {
2710
+ const {
2711
+ ctx,
2712
+ pad,
2713
+ realToCanvasMat,
2714
+ colorMap,
2715
+ soldermaskOverCopperColor,
2716
+ layer,
2717
+ showSoldermask
2718
+ } = params;
2719
+ if (pad.layer !== layer) return;
2720
+ const isCoveredWithSoldermask = showSoldermask && pad.is_covered_with_solder_mask === true;
2721
+ const margin = showSoldermask ? pad.soldermask_margin ?? 0 : 0;
2722
+ let ml = margin;
2723
+ let mr = margin;
2724
+ let mt = margin;
2725
+ let mb = margin;
2726
+ if (pad.shape === "rect" || pad.shape === "rotated_rect") {
2727
+ ml = pad.soldermask_margin_left ?? margin;
2728
+ mr = pad.soldermask_margin_right ?? margin;
2729
+ mt = pad.soldermask_margin_top ?? margin;
2730
+ mb = pad.soldermask_margin_bottom ?? margin;
2731
+ }
2732
+ const copperColor = colorMap.copper[pad.layer] || colorMap.copper.top;
2733
+ if (isCoveredWithSoldermask) {
2734
+ ctx.fillStyle = soldermaskOverCopperColor;
2735
+ drawPadShapePath({ ctx, pad, realToCanvasMat, ml: 0, mr: 0, mt: 0, mb: 0 });
2736
+ ctx.fill();
2737
+ } else if (ml < 0 || mr < 0 || mt < 0 || mb < 0) {
2738
+ ctx.fillStyle = copperColor;
2739
+ drawPadShapePath({ ctx, pad, realToCanvasMat, ml: 0, mr: 0, mt: 0, mb: 0 });
2740
+ ctx.fill();
2741
+ drawNegativeMarginRingForPad({
2742
+ ctx,
2743
+ pad,
2744
+ realToCanvasMat,
2745
+ soldermaskOverCopperColor,
2746
+ ml,
2747
+ mr,
2748
+ mt,
2749
+ mb
2750
+ });
2751
+ } else if (ml > 0 || mr > 0 || mt > 0 || mb > 0) {
2752
+ ctx.fillStyle = colorMap.substrate;
2753
+ drawPadShapePath({ ctx, pad, realToCanvasMat, ml, mr, mt, mb });
2754
+ ctx.fill();
2755
+ ctx.fillStyle = copperColor;
2756
+ drawPadShapePath({ ctx, pad, realToCanvasMat, ml: 0, mr: 0, mt: 0, mb: 0 });
2757
+ ctx.fill();
2758
+ } else {
2759
+ ctx.fillStyle = copperColor;
2760
+ drawPadShapePath({ ctx, pad, realToCanvasMat, ml: 0, mr: 0, mt: 0, mb: 0 });
2761
+ ctx.fill();
2762
+ }
2763
+ }
2764
+ function drawPadShapePath(params) {
2765
+ const { ctx, pad, realToCanvasMat, ml, mr, mt, mb } = params;
2766
+ const rotation = pad.shape === "rotated_rect" || pad.shape === "rotated_pill" ? pad.ccw_rotation ?? 0 : 0;
2767
+ if (pad.shape === "rect" || pad.shape === "rotated_rect") {
2768
+ const borderRadius = pad.corner_radius ?? pad.rect_border_radius ?? 0;
2769
+ const radians = rotation * Math.PI / 180;
2770
+ const dxLocal = (mr - ml) / 2;
2771
+ const dyLocal = (mt - mb) / 2;
2772
+ const dxGlobal = dxLocal * Math.cos(radians) - dyLocal * Math.sin(radians);
2773
+ const dyGlobal = dxLocal * Math.sin(radians) + dyLocal * Math.cos(radians);
2774
+ const [cx, cy] = applyToPoint20(realToCanvasMat, [
2775
+ pad.x + dxGlobal,
2776
+ pad.y + dyGlobal
2777
+ ]);
2778
+ const scaledWidth = (pad.width + ml + mr) * Math.abs(realToCanvasMat.a);
2779
+ const scaledHeight = (pad.height + mt + mb) * Math.abs(realToCanvasMat.a);
2780
+ const scaledRadius = borderRadius * Math.abs(realToCanvasMat.a);
2781
+ ctx.save();
2782
+ ctx.translate(cx, cy);
2783
+ if (rotation !== 0) {
2784
+ ctx.rotate(-rotation * Math.PI / 180);
2785
+ }
2786
+ ctx.beginPath();
2787
+ drawRoundedRectPath({
2788
+ ctx,
2789
+ cx: 0,
2790
+ cy: 0,
2791
+ width: scaledWidth,
2792
+ height: scaledHeight,
2793
+ radius: scaledRadius
2794
+ });
2795
+ ctx.restore();
2796
+ } else if (pad.shape === "circle") {
2797
+ const avgMargin = (ml + mr + mt + mb) / 4;
2798
+ const [cx, cy] = applyToPoint20(realToCanvasMat, [pad.x, pad.y]);
2799
+ const scaledRadius = (pad.radius + avgMargin) * Math.abs(realToCanvasMat.a);
2800
+ ctx.beginPath();
2801
+ ctx.arc(cx, cy, scaledRadius, 0, Math.PI * 2);
2802
+ ctx.closePath();
2803
+ } else if (pad.shape === "pill" || pad.shape === "rotated_pill") {
2804
+ const avgMargin = (ml + mr) / 2;
2805
+ const [cx, cy] = applyToPoint20(realToCanvasMat, [pad.x, pad.y]);
2806
+ const scaledWidth = (pad.width + avgMargin * 2) * Math.abs(realToCanvasMat.a);
2807
+ const scaledHeight = (pad.height + avgMargin * 2) * Math.abs(realToCanvasMat.a);
2808
+ ctx.save();
2809
+ ctx.translate(cx, cy);
2810
+ if (rotation !== 0) {
2811
+ ctx.rotate(-rotation * Math.PI / 180);
2812
+ }
2813
+ ctx.beginPath();
2814
+ drawPillPath({
2815
+ ctx,
2816
+ cx: 0,
2817
+ cy: 0,
2818
+ width: scaledWidth,
2819
+ height: scaledHeight
2820
+ });
2821
+ ctx.restore();
2822
+ } else if (pad.shape === "polygon" && pad.points && pad.points.length >= 3) {
2823
+ const avgMargin = (ml + mr + mt + mb) / 4;
2824
+ const points = avgMargin !== 0 ? offsetPolygonPoints(pad.points, avgMargin) : pad.points;
2825
+ const canvasPoints = points.map((p) => {
2826
+ const [x, y] = applyToPoint20(realToCanvasMat, [p.x, p.y]);
2827
+ return { x, y };
2828
+ });
2829
+ ctx.beginPath();
2830
+ drawPolygonPath({ ctx, points: canvasPoints });
2831
+ }
2832
+ }
2833
+ function drawNegativeMarginRingForPad(params) {
2834
+ const {
2835
+ ctx,
2836
+ pad,
2837
+ realToCanvasMat,
2838
+ soldermaskOverCopperColor,
2839
+ ml,
2840
+ mr,
2841
+ mt,
2842
+ mb
2843
+ } = params;
2844
+ const rotation = pad.shape === "rotated_rect" || pad.shape === "rotated_pill" ? pad.ccw_rotation ?? 0 : 0;
2845
+ const thicknessL = Math.max(0, -ml);
2846
+ const thicknessR = Math.max(0, -mr);
2847
+ const thicknessT = Math.max(0, -mt);
2848
+ const thicknessB = Math.max(0, -mb);
2849
+ ctx.fillStyle = soldermaskOverCopperColor;
2850
+ if (pad.shape === "rect" || pad.shape === "rotated_rect") {
2851
+ const borderRadius = pad.corner_radius ?? pad.rect_border_radius ?? 0;
2852
+ const [cx, cy] = applyToPoint20(realToCanvasMat, [pad.x, pad.y]);
2853
+ const scaledWidth = pad.width * Math.abs(realToCanvasMat.a);
2854
+ const scaledHeight = pad.height * Math.abs(realToCanvasMat.a);
2855
+ const scaledRadius = borderRadius * Math.abs(realToCanvasMat.a);
2856
+ const scaledThicknessL = thicknessL * Math.abs(realToCanvasMat.a);
2857
+ const scaledThicknessR = thicknessR * Math.abs(realToCanvasMat.a);
2858
+ const scaledThicknessT = thicknessT * Math.abs(realToCanvasMat.a);
2859
+ const scaledThicknessB = thicknessB * Math.abs(realToCanvasMat.a);
2860
+ ctx.save();
2861
+ ctx.translate(cx, cy);
2862
+ if (rotation !== 0) {
2863
+ ctx.rotate(-rotation * Math.PI / 180);
3004
2864
  }
3005
- if (element.type === "pcb_silkscreen_line") {
3006
- drawPcbSilkscreenLine({
3007
- ctx: this.ctx,
3008
- line: element,
3009
- realToCanvasMat: this.realToCanvasMat,
3010
- colorMap: this.colorMap
2865
+ ctx.beginPath();
2866
+ drawRoundedRectPath({
2867
+ ctx,
2868
+ cx: 0,
2869
+ cy: 0,
2870
+ width: scaledWidth,
2871
+ height: scaledHeight,
2872
+ radius: scaledRadius
2873
+ });
2874
+ const innerWidth = scaledWidth - scaledThicknessL - scaledThicknessR;
2875
+ const innerHeight = scaledHeight - scaledThicknessT - scaledThicknessB;
2876
+ const innerOffsetX = (scaledThicknessL - scaledThicknessR) / 2;
2877
+ const innerOffsetY = (scaledThicknessT - scaledThicknessB) / 2;
2878
+ const innerRadius = Math.max(
2879
+ 0,
2880
+ scaledRadius - (scaledThicknessL + scaledThicknessR + scaledThicknessT + scaledThicknessB) / 4
2881
+ );
2882
+ if (innerWidth > 0 && innerHeight > 0) {
2883
+ drawRoundedRectPath({
2884
+ ctx,
2885
+ cx: innerOffsetX,
2886
+ cy: innerOffsetY,
2887
+ width: innerWidth,
2888
+ height: innerHeight,
2889
+ radius: innerRadius
3011
2890
  });
3012
2891
  }
3013
- if (element.type === "pcb_silkscreen_path") {
3014
- drawPcbSilkscreenPath({
3015
- ctx: this.ctx,
3016
- path: element,
3017
- realToCanvasMat: this.realToCanvasMat,
3018
- colorMap: this.colorMap
3019
- });
2892
+ ctx.fill("evenodd");
2893
+ ctx.restore();
2894
+ } else if (pad.shape === "circle") {
2895
+ const thickness = Math.max(thicknessL, thicknessR, thicknessT, thicknessB);
2896
+ const [cx, cy] = applyToPoint20(realToCanvasMat, [pad.x, pad.y]);
2897
+ const scaledRadius = pad.radius * Math.abs(realToCanvasMat.a);
2898
+ const scaledThickness = thickness * Math.abs(realToCanvasMat.a);
2899
+ const innerRadius = Math.max(0, scaledRadius - scaledThickness);
2900
+ ctx.save();
2901
+ ctx.beginPath();
2902
+ ctx.arc(cx, cy, scaledRadius, 0, Math.PI * 2);
2903
+ if (innerRadius > 0) {
2904
+ ctx.arc(cx, cy, innerRadius, 0, Math.PI * 2, true);
3020
2905
  }
3021
- if (element.type === "pcb_silkscreen_pill") {
3022
- drawPcbSilkscreenPill({
3023
- ctx: this.ctx,
3024
- pill: element,
3025
- realToCanvasMat: this.realToCanvasMat,
3026
- colorMap: this.colorMap
3027
- });
2906
+ ctx.fill("evenodd");
2907
+ ctx.restore();
2908
+ } else if (pad.shape === "pill" || pad.shape === "rotated_pill") {
2909
+ const thickness = Math.max(thicknessL, thicknessR, thicknessT, thicknessB);
2910
+ const [cx, cy] = applyToPoint20(realToCanvasMat, [pad.x, pad.y]);
2911
+ const scaledWidth = pad.width * Math.abs(realToCanvasMat.a);
2912
+ const scaledHeight = pad.height * Math.abs(realToCanvasMat.a);
2913
+ const scaledThickness = thickness * Math.abs(realToCanvasMat.a);
2914
+ ctx.save();
2915
+ ctx.translate(cx, cy);
2916
+ if (rotation !== 0) {
2917
+ ctx.rotate(-rotation * Math.PI / 180);
3028
2918
  }
3029
- if (element.type === "pcb_silkscreen_oval") {
3030
- drawPcbSilkscreenOval({
3031
- ctx: this.ctx,
3032
- oval: element,
3033
- realToCanvasMat: this.realToCanvasMat,
3034
- colorMap: this.colorMap
2919
+ ctx.beginPath();
2920
+ drawPillPath({
2921
+ ctx,
2922
+ cx: 0,
2923
+ cy: 0,
2924
+ width: scaledWidth,
2925
+ height: scaledHeight
2926
+ });
2927
+ const innerWidth = scaledWidth - scaledThickness * 2;
2928
+ const innerHeight = scaledHeight - scaledThickness * 2;
2929
+ if (innerWidth > 0 && innerHeight > 0) {
2930
+ drawPillPath({
2931
+ ctx,
2932
+ cx: 0,
2933
+ cy: 0,
2934
+ width: innerWidth,
2935
+ height: innerHeight
3035
2936
  });
3036
2937
  }
3037
- if (element.type === "pcb_cutout") {
3038
- drawPcbCutout({
3039
- ctx: this.ctx,
3040
- cutout: element,
3041
- realToCanvasMat: this.realToCanvasMat,
3042
- colorMap: this.colorMap
2938
+ ctx.fill("evenodd");
2939
+ ctx.restore();
2940
+ } else if (pad.shape === "polygon" && pad.points && pad.points.length >= 3) {
2941
+ const thickness = Math.max(thicknessL, thicknessR, thicknessT, thicknessB);
2942
+ ctx.save();
2943
+ ctx.beginPath();
2944
+ const canvasPoints = pad.points.map((p) => {
2945
+ const [x, y] = applyToPoint20(realToCanvasMat, [p.x, p.y]);
2946
+ return { x, y };
2947
+ });
2948
+ drawPolygonPath({ ctx, points: canvasPoints });
2949
+ const innerPoints = offsetPolygonPoints(pad.points, -thickness);
2950
+ if (innerPoints.length >= 3) {
2951
+ const innerCanvasPoints = innerPoints.map((p) => {
2952
+ const [x, y] = applyToPoint20(realToCanvasMat, [p.x, p.y]);
2953
+ return { x, y };
3043
2954
  });
2955
+ drawPolygonPath({ ctx, points: innerCanvasPoints });
3044
2956
  }
3045
- if (element.type === "pcb_keepout") {
3046
- drawPcbKeepout({
3047
- ctx: this.ctx,
3048
- keepout: element,
3049
- realToCanvasMat: this.realToCanvasMat,
3050
- colorMap: this.colorMap
3051
- });
2957
+ ctx.fill("evenodd");
2958
+ ctx.restore();
2959
+ }
2960
+ }
2961
+
2962
+ // lib/drawer/elements/pcb-soldermask/via.ts
2963
+ import { applyToPoint as applyToPoint21 } from "transformation-matrix";
2964
+ function processViaSoldermask(params) {
2965
+ const { ctx, via, realToCanvasMat, colorMap } = params;
2966
+ const [cx, cy] = applyToPoint21(realToCanvasMat, [via.x, via.y]);
2967
+ const scaledRadius = via.outer_diameter / 2 * Math.abs(realToCanvasMat.a);
2968
+ ctx.fillStyle = colorMap.substrate;
2969
+ ctx.beginPath();
2970
+ ctx.arc(cx, cy, scaledRadius, 0, Math.PI * 2);
2971
+ ctx.closePath();
2972
+ ctx.fill();
2973
+ }
2974
+
2975
+ // lib/drawer/elements/pcb-soldermask/index.ts
2976
+ function drawPcbSoldermask(params) {
2977
+ const {
2978
+ ctx,
2979
+ board,
2980
+ elements,
2981
+ realToCanvasMat,
2982
+ colorMap,
2983
+ layer,
2984
+ showSoldermask
2985
+ } = params;
2986
+ const soldermaskColor = colorMap.soldermask[layer] ?? colorMap.soldermask.top;
2987
+ const soldermaskOverCopperColor = colorMap.soldermaskOverCopper[layer] ?? colorMap.soldermaskOverCopper.top;
2988
+ if (showSoldermask) {
2989
+ drawBoardSoldermask({ ctx, board, realToCanvasMat, soldermaskColor });
2990
+ }
2991
+ for (const element of elements) {
2992
+ processElementSoldermask({
2993
+ ctx,
2994
+ element,
2995
+ realToCanvasMat,
2996
+ colorMap,
2997
+ soldermaskOverCopperColor,
2998
+ layer,
2999
+ showSoldermask
3000
+ });
3001
+ }
3002
+ }
3003
+ function processElementSoldermask(params) {
3004
+ const {
3005
+ ctx,
3006
+ element,
3007
+ realToCanvasMat,
3008
+ colorMap,
3009
+ soldermaskOverCopperColor,
3010
+ layer,
3011
+ showSoldermask
3012
+ } = params;
3013
+ if (element.type === "pcb_smtpad") {
3014
+ processSmtPadSoldermask({
3015
+ ctx,
3016
+ pad: element,
3017
+ realToCanvasMat,
3018
+ colorMap,
3019
+ soldermaskOverCopperColor,
3020
+ layer,
3021
+ showSoldermask
3022
+ });
3023
+ } else if (element.type === "pcb_plated_hole") {
3024
+ processPlatedHoleSoldermask({
3025
+ ctx,
3026
+ hole: element,
3027
+ realToCanvasMat,
3028
+ colorMap,
3029
+ soldermaskOverCopperColor,
3030
+ layer,
3031
+ showSoldermask
3032
+ });
3033
+ } else if (element.type === "pcb_hole") {
3034
+ processHoleSoldermask({
3035
+ ctx,
3036
+ hole: element,
3037
+ realToCanvasMat,
3038
+ colorMap,
3039
+ soldermaskOverCopperColor,
3040
+ showSoldermask
3041
+ });
3042
+ } else if (element.type === "pcb_via") {
3043
+ processViaSoldermask({
3044
+ ctx,
3045
+ via: element,
3046
+ realToCanvasMat,
3047
+ colorMap
3048
+ });
3049
+ } else if (element.type === "pcb_cutout") {
3050
+ processCutoutSoldermask({
3051
+ ctx,
3052
+ cutout: element,
3053
+ realToCanvasMat,
3054
+ colorMap
3055
+ });
3056
+ }
3057
+ }
3058
+
3059
+ // lib/drawer/CircuitToCanvasDrawer.ts
3060
+ var CircuitToCanvasDrawer = class {
3061
+ ctx;
3062
+ colorMap;
3063
+ realToCanvasMat;
3064
+ constructor(canvasOrContext) {
3065
+ if ("getContext" in canvasOrContext && typeof canvasOrContext.getContext === "function") {
3066
+ const ctx = canvasOrContext.getContext("2d");
3067
+ if (!ctx) {
3068
+ throw new Error("Failed to get 2D rendering context from canvas");
3069
+ }
3070
+ this.ctx = ctx;
3071
+ } else {
3072
+ this.ctx = canvasOrContext;
3052
3073
  }
3053
- if (element.type === "pcb_copper_pour") {
3054
- drawPcbCopperPour({
3055
- ctx: this.ctx,
3056
- pour: element,
3057
- realToCanvasMat: this.realToCanvasMat,
3058
- colorMap: this.colorMap
3059
- });
3074
+ this.colorMap = { ...DEFAULT_PCB_COLOR_MAP };
3075
+ this.realToCanvasMat = identity();
3076
+ }
3077
+ configure(config) {
3078
+ if (config.colorOverrides) {
3079
+ this.colorMap = {
3080
+ ...this.colorMap,
3081
+ ...config.colorOverrides,
3082
+ copper: {
3083
+ ...this.colorMap.copper,
3084
+ ...config.colorOverrides.copper
3085
+ },
3086
+ silkscreen: {
3087
+ ...this.colorMap.silkscreen,
3088
+ ...config.colorOverrides.silkscreen
3089
+ },
3090
+ soldermask: {
3091
+ ...this.colorMap.soldermask,
3092
+ ...config.colorOverrides.soldermask
3093
+ },
3094
+ soldermaskWithCopperUnderneath: {
3095
+ ...this.colorMap.soldermaskWithCopperUnderneath,
3096
+ ...config.colorOverrides.soldermaskWithCopperUnderneath
3097
+ },
3098
+ soldermaskOverCopper: {
3099
+ ...this.colorMap.soldermaskOverCopper,
3100
+ ...config.colorOverrides.soldermaskOverCopper
3101
+ }
3102
+ };
3060
3103
  }
3061
- if (element.type === "pcb_copper_text") {
3062
- drawPcbCopperText({
3104
+ }
3105
+ setCameraBounds(bounds) {
3106
+ const canvas = this.ctx.canvas;
3107
+ const canvasWidth = canvas.width;
3108
+ const canvasHeight = canvas.height;
3109
+ const realWidth = bounds.maxX - bounds.minX;
3110
+ const realHeight = bounds.maxY - bounds.minY;
3111
+ const scaleX = canvasWidth / realWidth;
3112
+ const scaleY = canvasHeight / realHeight;
3113
+ const uniformScale = Math.min(scaleX, scaleY);
3114
+ const offsetX = (canvasWidth - realWidth * uniformScale) / 2;
3115
+ const offsetY = (canvasHeight - realHeight * uniformScale) / 2;
3116
+ this.realToCanvasMat = compose(
3117
+ translate(offsetX, offsetY),
3118
+ scale(uniformScale, -uniformScale),
3119
+ translate(-bounds.minX, -bounds.maxY)
3120
+ );
3121
+ }
3122
+ drawElements(elements, options = {}) {
3123
+ const board = elements.find((el) => el.type === "pcb_board");
3124
+ if (board) {
3125
+ drawPcbBoard({
3063
3126
  ctx: this.ctx,
3064
- text: element,
3127
+ board,
3065
3128
  realToCanvasMat: this.realToCanvasMat,
3066
3129
  colorMap: this.colorMap
3067
3130
  });
3068
3131
  }
3069
- if (element.type === "pcb_fabrication_note_text") {
3070
- drawPcbFabricationNoteText({
3071
- ctx: this.ctx,
3072
- text: element,
3073
- realToCanvasMat: this.realToCanvasMat,
3074
- colorMap: this.colorMap
3075
- });
3132
+ for (const element of elements) {
3133
+ if (!shouldDrawElement(element, options)) continue;
3134
+ if (element.type === "pcb_smtpad") {
3135
+ drawPcbSmtPad({
3136
+ ctx: this.ctx,
3137
+ pad: element,
3138
+ realToCanvasMat: this.realToCanvasMat,
3139
+ colorMap: this.colorMap
3140
+ });
3141
+ }
3142
+ if (element.type === "pcb_copper_text") {
3143
+ drawPcbCopperText({
3144
+ ctx: this.ctx,
3145
+ text: element,
3146
+ realToCanvasMat: this.realToCanvasMat,
3147
+ colorMap: this.colorMap
3148
+ });
3149
+ }
3076
3150
  }
3077
- if (element.type === "pcb_fabrication_note_rect") {
3078
- drawPcbFabricationNoteRect({
3151
+ const showSoldermask = options.showSoldermask ?? false;
3152
+ if (board) {
3153
+ drawPcbSoldermask({
3079
3154
  ctx: this.ctx,
3080
- rect: element,
3081
- realToCanvasMat: this.realToCanvasMat,
3082
- colorMap: this.colorMap
3083
- });
3084
- }
3085
- if (element.type === "pcb_note_rect") {
3086
- drawPcbNoteRect({
3155
+ board,
3156
+ elements,
3087
3157
  realToCanvasMat: this.realToCanvasMat,
3088
3158
  colorMap: this.colorMap,
3089
- ctx: this.ctx,
3090
- rect: element
3091
- });
3092
- }
3093
- if (element.type === "pcb_fabrication_note_path") {
3094
- drawPcbFabricationNotePath({
3095
- ctx: this.ctx,
3096
- path: element,
3097
- realToCanvasMat: this.realToCanvasMat,
3098
- colorMap: this.colorMap
3159
+ layer: "top",
3160
+ showSoldermask
3099
3161
  });
3100
3162
  }
3101
- if (element.type === "pcb_note_path") {
3102
- drawPcbNotePath({
3103
- ctx: this.ctx,
3104
- path: element,
3105
- realToCanvasMat: this.realToCanvasMat,
3106
- colorMap: this.colorMap
3107
- });
3163
+ for (const element of elements) {
3164
+ if (!shouldDrawElement(element, options)) continue;
3165
+ if (element.type === "pcb_silkscreen_text") {
3166
+ drawPcbSilkscreenText({
3167
+ ctx: this.ctx,
3168
+ text: element,
3169
+ realToCanvasMat: this.realToCanvasMat,
3170
+ colorMap: this.colorMap
3171
+ });
3172
+ }
3173
+ if (element.type === "pcb_silkscreen_rect") {
3174
+ drawPcbSilkscreenRect({
3175
+ ctx: this.ctx,
3176
+ rect: element,
3177
+ realToCanvasMat: this.realToCanvasMat,
3178
+ colorMap: this.colorMap
3179
+ });
3180
+ }
3181
+ if (element.type === "pcb_silkscreen_circle") {
3182
+ drawPcbSilkscreenCircle({
3183
+ ctx: this.ctx,
3184
+ circle: element,
3185
+ realToCanvasMat: this.realToCanvasMat,
3186
+ colorMap: this.colorMap
3187
+ });
3188
+ }
3189
+ if (element.type === "pcb_silkscreen_line") {
3190
+ drawPcbSilkscreenLine({
3191
+ ctx: this.ctx,
3192
+ line: element,
3193
+ realToCanvasMat: this.realToCanvasMat,
3194
+ colorMap: this.colorMap
3195
+ });
3196
+ }
3197
+ if (element.type === "pcb_silkscreen_path") {
3198
+ drawPcbSilkscreenPath({
3199
+ ctx: this.ctx,
3200
+ path: element,
3201
+ realToCanvasMat: this.realToCanvasMat,
3202
+ colorMap: this.colorMap
3203
+ });
3204
+ }
3205
+ if (element.type === "pcb_silkscreen_pill") {
3206
+ drawPcbSilkscreenPill({
3207
+ ctx: this.ctx,
3208
+ pill: element,
3209
+ realToCanvasMat: this.realToCanvasMat,
3210
+ colorMap: this.colorMap
3211
+ });
3212
+ }
3213
+ if (element.type === "pcb_silkscreen_oval") {
3214
+ drawPcbSilkscreenOval({
3215
+ ctx: this.ctx,
3216
+ oval: element,
3217
+ realToCanvasMat: this.realToCanvasMat,
3218
+ colorMap: this.colorMap
3219
+ });
3220
+ }
3108
3221
  }
3109
- if (element.type === "pcb_note_text") {
3110
- drawPcbNoteText({
3111
- ctx: this.ctx,
3112
- text: element,
3113
- realToCanvasMat: this.realToCanvasMat,
3114
- colorMap: this.colorMap
3115
- });
3222
+ for (const element of elements) {
3223
+ if (!shouldDrawElement(element, options)) continue;
3224
+ if (element.type === "pcb_copper_pour") {
3225
+ drawPcbCopperPour({
3226
+ ctx: this.ctx,
3227
+ pour: element,
3228
+ realToCanvasMat: this.realToCanvasMat,
3229
+ colorMap: this.colorMap
3230
+ });
3231
+ }
3232
+ if (element.type === "pcb_trace") {
3233
+ drawPcbTrace({
3234
+ ctx: this.ctx,
3235
+ trace: element,
3236
+ realToCanvasMat: this.realToCanvasMat,
3237
+ colorMap: this.colorMap
3238
+ });
3239
+ }
3116
3240
  }
3117
- if (element.type === "pcb_note_line") {
3118
- drawPcbNoteLine({
3119
- ctx: this.ctx,
3120
- line: element,
3121
- realToCanvasMat: this.realToCanvasMat,
3122
- colorMap: this.colorMap
3123
- });
3241
+ for (const element of elements) {
3242
+ if (!shouldDrawElement(element, options)) continue;
3243
+ if (element.type === "pcb_plated_hole") {
3244
+ drawPcbPlatedHole({
3245
+ ctx: this.ctx,
3246
+ hole: element,
3247
+ realToCanvasMat: this.realToCanvasMat,
3248
+ colorMap: this.colorMap,
3249
+ soldermaskMargin: showSoldermask ? element.soldermask_margin : void 0,
3250
+ showSoldermask
3251
+ });
3252
+ }
3253
+ if (element.type === "pcb_via") {
3254
+ drawPcbVia({
3255
+ ctx: this.ctx,
3256
+ via: element,
3257
+ realToCanvasMat: this.realToCanvasMat,
3258
+ colorMap: this.colorMap
3259
+ });
3260
+ }
3124
3261
  }
3125
- if (element.type === "pcb_note_dimension") {
3126
- drawPcbNoteDimension({
3127
- ctx: this.ctx,
3128
- pcbNoteDimension: element,
3129
- realToCanvasMat: this.realToCanvasMat,
3130
- colorMap: this.colorMap
3131
- });
3262
+ for (const element of elements) {
3263
+ if (!shouldDrawElement(element, options)) continue;
3264
+ if (element.type === "pcb_hole") {
3265
+ drawPcbHole({
3266
+ ctx: this.ctx,
3267
+ hole: element,
3268
+ realToCanvasMat: this.realToCanvasMat,
3269
+ colorMap: this.colorMap,
3270
+ soldermaskMargin: showSoldermask ? element.soldermask_margin : void 0
3271
+ });
3272
+ }
3273
+ if (element.type === "pcb_cutout") {
3274
+ drawPcbCutout({
3275
+ ctx: this.ctx,
3276
+ cutout: element,
3277
+ realToCanvasMat: this.realToCanvasMat,
3278
+ colorMap: this.colorMap
3279
+ });
3280
+ }
3132
3281
  }
3133
- if (element.type === "pcb_fabrication_note_dimension") {
3134
- drawPcbFabricationNoteDimension({
3135
- ctx: this.ctx,
3136
- pcbFabricationNoteDimension: element,
3137
- realToCanvasMat: this.realToCanvasMat,
3138
- colorMap: this.colorMap
3139
- });
3282
+ for (const element of elements) {
3283
+ if (!shouldDrawElement(element, options)) continue;
3284
+ if (element.type === "pcb_keepout") {
3285
+ drawPcbKeepout({
3286
+ ctx: this.ctx,
3287
+ keepout: element,
3288
+ realToCanvasMat: this.realToCanvasMat,
3289
+ colorMap: this.colorMap
3290
+ });
3291
+ }
3292
+ if (element.type === "pcb_fabrication_note_text") {
3293
+ drawPcbFabricationNoteText({
3294
+ ctx: this.ctx,
3295
+ text: element,
3296
+ realToCanvasMat: this.realToCanvasMat,
3297
+ colorMap: this.colorMap
3298
+ });
3299
+ }
3300
+ if (element.type === "pcb_fabrication_note_rect") {
3301
+ drawPcbFabricationNoteRect({
3302
+ ctx: this.ctx,
3303
+ rect: element,
3304
+ realToCanvasMat: this.realToCanvasMat,
3305
+ colorMap: this.colorMap
3306
+ });
3307
+ }
3308
+ if (element.type === "pcb_note_rect") {
3309
+ drawPcbNoteRect({
3310
+ realToCanvasMat: this.realToCanvasMat,
3311
+ colorMap: this.colorMap,
3312
+ ctx: this.ctx,
3313
+ rect: element
3314
+ });
3315
+ }
3316
+ if (element.type === "pcb_fabrication_note_path") {
3317
+ drawPcbFabricationNotePath({
3318
+ ctx: this.ctx,
3319
+ path: element,
3320
+ realToCanvasMat: this.realToCanvasMat,
3321
+ colorMap: this.colorMap
3322
+ });
3323
+ }
3324
+ if (element.type === "pcb_note_path") {
3325
+ drawPcbNotePath({
3326
+ ctx: this.ctx,
3327
+ path: element,
3328
+ realToCanvasMat: this.realToCanvasMat,
3329
+ colorMap: this.colorMap
3330
+ });
3331
+ }
3332
+ if (element.type === "pcb_note_text") {
3333
+ drawPcbNoteText({
3334
+ ctx: this.ctx,
3335
+ text: element,
3336
+ realToCanvasMat: this.realToCanvasMat,
3337
+ colorMap: this.colorMap
3338
+ });
3339
+ }
3340
+ if (element.type === "pcb_note_line") {
3341
+ drawPcbNoteLine({
3342
+ ctx: this.ctx,
3343
+ line: element,
3344
+ realToCanvasMat: this.realToCanvasMat,
3345
+ colorMap: this.colorMap
3346
+ });
3347
+ }
3348
+ if (element.type === "pcb_note_dimension") {
3349
+ drawPcbNoteDimension({
3350
+ ctx: this.ctx,
3351
+ pcbNoteDimension: element,
3352
+ realToCanvasMat: this.realToCanvasMat,
3353
+ colorMap: this.colorMap
3354
+ });
3355
+ }
3356
+ if (element.type === "pcb_fabrication_note_dimension") {
3357
+ drawPcbFabricationNoteDimension({
3358
+ ctx: this.ctx,
3359
+ pcbFabricationNoteDimension: element,
3360
+ realToCanvasMat: this.realToCanvasMat,
3361
+ colorMap: this.colorMap
3362
+ });
3363
+ }
3140
3364
  }
3141
3365
  }
3142
3366
  };