circuit-to-canvas 0.0.50 → 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 (51) hide show
  1. package/dist/index.d.ts +7 -4
  2. package/dist/index.js +1425 -1240
  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 +3 -292
  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/package.json +1 -1
  20. package/tests/board-snapshot/__snapshots__/usb-c-flashlight-board.snap.png +0 -0
  21. package/tests/board-snapshot/usb-c-flashlight-board.test.ts +1 -0
  22. package/tests/elements/__snapshots__/board-with-elements.snap.png +0 -0
  23. package/tests/elements/__snapshots__/brep-copper-pours.snap.png +0 -0
  24. package/tests/elements/__snapshots__/custom-outline-board.snap.png +0 -0
  25. package/tests/elements/__snapshots__/oval-plated-hole.snap.png +0 -0
  26. package/tests/elements/__snapshots__/pcb-board.snap.png +0 -0
  27. package/tests/elements/__snapshots__/pcb-comprehensive-soldermask-margin.snap.png +0 -0
  28. package/tests/elements/__snapshots__/pcb-fabrication-note-dimension.snap.png +0 -0
  29. package/tests/elements/__snapshots__/pcb-hole-soldermask-margin.snap.png +0 -0
  30. package/tests/elements/__snapshots__/pcb-keepout-layer-filter.snap.png +0 -0
  31. package/tests/elements/__snapshots__/pcb-keepout-multiple-layers.snap.png +0 -0
  32. package/tests/elements/__snapshots__/pcb-keepout-rect-and-circle.snap.png +0 -0
  33. package/tests/elements/__snapshots__/pcb-keepout-with-group-id.snap.png +0 -0
  34. package/tests/elements/__snapshots__/pcb-no-soldermask.snap.png +0 -0
  35. package/tests/elements/__snapshots__/pcb-plated-hole-soldermask-margin.snap.png +0 -0
  36. package/tests/elements/__snapshots__/pcb-plated-hole.snap.png +0 -0
  37. package/tests/elements/__snapshots__/pcb-silkscreen-on-component.snap.png +0 -0
  38. package/tests/elements/__snapshots__/pcb-silkscreen-oval.snap.png +0 -0
  39. package/tests/elements/__snapshots__/pcb-smtpad-asymmetric-soldermask-margin.snap.png +0 -0
  40. package/tests/elements/__snapshots__/pcb-smtpad-soldermask-coverage.snap.png +0 -0
  41. package/tests/elements/__snapshots__/pcb-smtpad-soldermask-margin.snap.png +0 -0
  42. package/tests/elements/__snapshots__/pill-plated-hole.snap.png +0 -0
  43. package/tests/elements/pcb-comprehensive-soldermask-margin.test.ts +2 -2
  44. package/tests/elements/pcb-hole-soldermask-margin.test.ts +155 -2
  45. package/tests/elements/pcb-no-soldermask.test.ts +1281 -0
  46. package/tests/elements/pcb-plated-hole-soldermask-margin.test.ts +1 -1
  47. package/tests/elements/pcb-plated-hole.test.ts +40 -4
  48. package/tests/elements/pcb-smtpad-asymmetric-soldermask-margin.test.ts +1 -1
  49. package/tests/elements/pcb-smtpad-soldermask-coverage.test.ts +1 -1
  50. package/tests/elements/pcb-smtpad-soldermask-margin.test.ts +1 -1
  51. 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
@@ -503,418 +502,146 @@ function offsetPolygonPoints(points, offset) {
503
502
  }
504
503
  return result;
505
504
  }
506
- function drawSoldermaskRingForPolygon(ctx, points, margin, realToCanvasMat, soldermaskColor, padColor) {
507
- if (points.length < 3 || margin >= 0) return;
508
- const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
509
- const prevCompositeOp = ctx.globalCompositeOperation;
510
- if (ctx.globalCompositeOperation !== void 0) {
511
- ctx.globalCompositeOperation = "source-atop";
512
- }
513
- ctx.beginPath();
514
- const canvasPoints = points.map(
515
- (p) => applyToPoint6(realToCanvasMat, [p.x, p.y])
516
- );
517
- const firstPoint = canvasPoints[0];
518
- if (firstPoint) {
519
- const [firstX, firstY] = firstPoint;
520
- ctx.moveTo(firstX, firstY);
521
- for (let i = 1; i < canvasPoints.length; i++) {
522
- const point = canvasPoints[i];
523
- if (point) {
524
- const [x, y] = point;
525
- ctx.lineTo(x, y);
526
- }
527
- }
528
- ctx.closePath();
529
- ctx.fillStyle = soldermaskColor;
530
- ctx.fill();
531
- }
532
- if (ctx.globalCompositeOperation !== void 0) {
533
- ctx.globalCompositeOperation = prevCompositeOp || "source-over";
534
- }
535
- const innerPoints = offsetPolygonPoints(points, margin);
536
- if (innerPoints.length >= 3) {
537
- ctx.beginPath();
538
- const innerCanvasPoints = innerPoints.map(
539
- (p) => applyToPoint6(realToCanvasMat, [p.x, p.y])
540
- );
541
- const firstInnerPoint = innerCanvasPoints[0];
542
- if (firstInnerPoint) {
543
- const [firstX, firstY] = firstInnerPoint;
544
- ctx.moveTo(firstX, firstY);
545
- for (let i = 1; i < innerCanvasPoints.length; i++) {
546
- const point = innerCanvasPoints[i];
547
- if (point) {
548
- const [x, y] = point;
549
- ctx.lineTo(x, y);
550
- }
551
- }
552
- ctx.closePath();
553
- ctx.fillStyle = padColor;
554
- ctx.fill();
555
- }
556
- }
557
- ctx.restore();
558
- }
559
505
 
560
506
  // lib/drawer/elements/pcb-plated-hole.ts
561
- function getSoldermaskColor(layers, colorMap) {
562
- const layer = layers?.includes("top") ? "top" : "bottom";
563
- return colorMap.soldermaskOverCopper[layer] ?? colorMap.soldermaskOverCopper.top;
564
- }
565
507
  function drawPcbPlatedHole(params) {
566
- const { ctx, hole, realToCanvasMat, colorMap } = params;
567
- const isCoveredWithSoldermask = hole.is_covered_with_solder_mask === true;
568
- const margin = isCoveredWithSoldermask ? 0 : hole.soldermask_margin ?? 0;
569
- const hasSoldermask = !isCoveredWithSoldermask && hole.soldermask_margin !== void 0 && hole.soldermask_margin !== 0;
570
- const soldermaskRingColor = getSoldermaskColor(hole.layers, colorMap);
571
- 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
+ }
572
519
  const copperColor = colorMap.copper.top;
520
+ const copperInset = soldermaskMargin < 0 ? Math.abs(soldermaskMargin) : 0;
573
521
  if (hole.shape === "circle") {
574
- if (hasSoldermask && margin > 0) {
575
- drawCircle({
576
- ctx,
577
- center: { x: hole.x, y: hole.y },
578
- radius: hole.outer_diameter / 2 + margin,
579
- fill: positiveMarginColor,
580
- realToCanvasMat
581
- });
582
- }
583
522
  drawCircle({
584
523
  ctx,
585
524
  center: { x: hole.x, y: hole.y },
586
- radius: hole.outer_diameter / 2,
525
+ radius: hole.outer_diameter / 2 - copperInset,
587
526
  fill: copperColor,
588
527
  realToCanvasMat
589
528
  });
590
- if (hasSoldermask && margin < 0) {
591
- drawSoldermaskRingForCircle(
592
- ctx,
593
- { x: hole.x, y: hole.y },
594
- hole.outer_diameter / 2,
595
- margin,
596
- realToCanvasMat,
597
- soldermaskRingColor,
598
- copperColor
599
- );
600
- }
601
- if (isCoveredWithSoldermask) {
602
- drawCircle({
603
- ctx,
604
- center: { x: hole.x, y: hole.y },
605
- radius: hole.outer_diameter / 2,
606
- fill: soldermaskRingColor,
607
- realToCanvasMat
608
- });
609
- }
610
- if (!isCoveredWithSoldermask) {
611
- drawCircle({
612
- ctx,
613
- center: { x: hole.x, y: hole.y },
614
- radius: hole.hole_diameter / 2,
615
- fill: colorMap.drill,
616
- realToCanvasMat
617
- });
618
- }
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
+ });
619
536
  return;
620
537
  }
621
538
  if (hole.shape === "oval") {
622
- if (hasSoldermask && margin > 0) {
623
- drawOval({
624
- ctx,
625
- center: { x: hole.x, y: hole.y },
626
- radius_x: hole.outer_width / 2 + margin,
627
- radius_y: hole.outer_height / 2 + margin,
628
- fill: positiveMarginColor,
629
- realToCanvasMat,
630
- rotation: hole.ccw_rotation
631
- });
632
- }
633
539
  drawOval({
634
540
  ctx,
635
541
  center: { x: hole.x, y: hole.y },
636
- radius_x: hole.outer_width / 2,
637
- radius_y: hole.outer_height / 2,
542
+ radius_x: hole.outer_width / 2 - copperInset,
543
+ radius_y: hole.outer_height / 2 - copperInset,
638
544
  fill: copperColor,
639
545
  realToCanvasMat,
640
546
  rotation: hole.ccw_rotation
641
547
  });
642
- if (hasSoldermask && margin < 0) {
643
- drawSoldermaskRingForOval(
644
- ctx,
645
- { x: hole.x, y: hole.y },
646
- hole.outer_width / 2,
647
- hole.outer_height / 2,
648
- margin,
649
- hole.ccw_rotation ?? 0,
650
- realToCanvasMat,
651
- soldermaskRingColor,
652
- copperColor
653
- );
654
- }
655
- if (isCoveredWithSoldermask) {
656
- drawOval({
657
- ctx,
658
- center: { x: hole.x, y: hole.y },
659
- radius_x: hole.outer_width / 2,
660
- radius_y: hole.outer_height / 2,
661
- fill: soldermaskRingColor,
662
- realToCanvasMat,
663
- rotation: hole.ccw_rotation
664
- });
665
- }
666
- if (!isCoveredWithSoldermask) {
667
- drawOval({
668
- ctx,
669
- center: { x: hole.x, y: hole.y },
670
- radius_x: hole.hole_width / 2,
671
- radius_y: hole.hole_height / 2,
672
- fill: colorMap.drill,
673
- realToCanvasMat,
674
- rotation: hole.ccw_rotation
675
- });
676
- }
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
+ });
677
557
  return;
678
558
  }
679
559
  if (hole.shape === "pill") {
680
- if (hasSoldermask && margin > 0) {
681
- drawPill({
682
- ctx,
683
- center: { x: hole.x, y: hole.y },
684
- width: hole.outer_width + margin * 2,
685
- height: hole.outer_height + margin * 2,
686
- fill: positiveMarginColor,
687
- realToCanvasMat,
688
- rotation: hole.ccw_rotation
689
- });
690
- }
691
560
  drawPill({
692
561
  ctx,
693
562
  center: { x: hole.x, y: hole.y },
694
- width: hole.outer_width,
695
- height: hole.outer_height,
563
+ width: hole.outer_width - copperInset * 2,
564
+ height: hole.outer_height - copperInset * 2,
696
565
  fill: copperColor,
697
566
  realToCanvasMat,
698
567
  rotation: hole.ccw_rotation
699
568
  });
700
- if (hasSoldermask && margin < 0) {
701
- drawSoldermaskRingForPill(
702
- ctx,
703
- { x: hole.x, y: hole.y },
704
- hole.outer_width,
705
- hole.outer_height,
706
- margin,
707
- hole.ccw_rotation ?? 0,
708
- realToCanvasMat,
709
- soldermaskRingColor,
710
- copperColor
711
- );
712
- }
713
- if (isCoveredWithSoldermask) {
714
- drawPill({
715
- ctx,
716
- center: { x: hole.x, y: hole.y },
717
- width: hole.outer_width,
718
- height: hole.outer_height,
719
- fill: soldermaskRingColor,
720
- realToCanvasMat,
721
- rotation: hole.ccw_rotation
722
- });
723
- }
724
- if (!isCoveredWithSoldermask) {
725
- drawPill({
726
- ctx,
727
- center: { x: hole.x, y: hole.y },
728
- width: hole.hole_width,
729
- height: hole.hole_height,
730
- fill: colorMap.drill,
731
- realToCanvasMat,
732
- rotation: hole.ccw_rotation
733
- });
734
- }
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
+ });
735
578
  return;
736
579
  }
737
580
  if (hole.shape === "circular_hole_with_rect_pad") {
738
- if (hasSoldermask && margin > 0) {
739
- drawRect({
740
- ctx,
741
- center: { x: hole.x, y: hole.y },
742
- width: hole.rect_pad_width + margin * 2,
743
- height: hole.rect_pad_height + margin * 2,
744
- fill: positiveMarginColor,
745
- realToCanvasMat,
746
- borderRadius: hole.rect_border_radius ?? 0
747
- });
748
- }
749
581
  drawRect({
750
582
  ctx,
751
583
  center: { x: hole.x, y: hole.y },
752
- width: hole.rect_pad_width,
753
- height: hole.rect_pad_height,
584
+ width: hole.rect_pad_width - copperInset * 2,
585
+ height: hole.rect_pad_height - copperInset * 2,
754
586
  fill: copperColor,
755
587
  realToCanvasMat,
756
- 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
757
598
  });
758
- if (hasSoldermask && margin < 0) {
759
- drawSoldermaskRingForRect(
760
- ctx,
761
- { x: hole.x, y: hole.y },
762
- hole.rect_pad_width,
763
- hole.rect_pad_height,
764
- margin,
765
- hole.rect_border_radius ?? 0,
766
- 0,
767
- realToCanvasMat,
768
- soldermaskRingColor,
769
- copperColor
770
- );
771
- }
772
- if (isCoveredWithSoldermask) {
773
- drawRect({
774
- ctx,
775
- center: { x: hole.x, y: hole.y },
776
- width: hole.rect_pad_width,
777
- height: hole.rect_pad_height,
778
- fill: soldermaskRingColor,
779
- realToCanvasMat,
780
- borderRadius: hole.rect_border_radius ?? 0
781
- });
782
- }
783
- if (!isCoveredWithSoldermask) {
784
- const holeX = hole.x + (hole.hole_offset_x ?? 0);
785
- const holeY = hole.y + (hole.hole_offset_y ?? 0);
786
- drawCircle({
787
- ctx,
788
- center: { x: holeX, y: holeY },
789
- radius: hole.hole_diameter / 2,
790
- fill: colorMap.drill,
791
- realToCanvasMat
792
- });
793
- }
794
599
  return;
795
600
  }
796
601
  if (hole.shape === "pill_hole_with_rect_pad") {
797
- if (hasSoldermask && margin > 0) {
798
- drawRect({
799
- ctx,
800
- center: { x: hole.x, y: hole.y },
801
- width: hole.rect_pad_width + margin * 2,
802
- height: hole.rect_pad_height + margin * 2,
803
- fill: positiveMarginColor,
804
- realToCanvasMat,
805
- borderRadius: hole.rect_border_radius ?? 0
806
- });
807
- }
808
602
  drawRect({
809
603
  ctx,
810
604
  center: { x: hole.x, y: hole.y },
811
- width: hole.rect_pad_width,
812
- height: hole.rect_pad_height,
605
+ width: hole.rect_pad_width - copperInset * 2,
606
+ height: hole.rect_pad_height - copperInset * 2,
813
607
  fill: copperColor,
814
608
  realToCanvasMat,
815
- 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
816
620
  });
817
- if (hasSoldermask && margin < 0) {
818
- drawSoldermaskRingForRect(
819
- ctx,
820
- { x: hole.x, y: hole.y },
821
- hole.rect_pad_width,
822
- hole.rect_pad_height,
823
- margin,
824
- hole.rect_border_radius ?? 0,
825
- 0,
826
- realToCanvasMat,
827
- soldermaskRingColor,
828
- copperColor
829
- );
830
- }
831
- if (isCoveredWithSoldermask) {
832
- drawRect({
833
- ctx,
834
- center: { x: hole.x, y: hole.y },
835
- width: hole.rect_pad_width,
836
- height: hole.rect_pad_height,
837
- fill: soldermaskRingColor,
838
- realToCanvasMat,
839
- borderRadius: hole.rect_border_radius ?? 0
840
- });
841
- }
842
- if (!isCoveredWithSoldermask) {
843
- const holeX = hole.x + (hole.hole_offset_x ?? 0);
844
- const holeY = hole.y + (hole.hole_offset_y ?? 0);
845
- drawPill({
846
- ctx,
847
- center: { x: holeX, y: holeY },
848
- width: hole.hole_width,
849
- height: hole.hole_height,
850
- fill: colorMap.drill,
851
- realToCanvasMat
852
- });
853
- }
854
621
  return;
855
622
  }
856
623
  if (hole.shape === "rotated_pill_hole_with_rect_pad") {
857
- if (hasSoldermask && margin > 0) {
858
- drawRect({
859
- ctx,
860
- center: { x: hole.x, y: hole.y },
861
- width: hole.rect_pad_width + margin * 2,
862
- height: hole.rect_pad_height + margin * 2,
863
- fill: positiveMarginColor,
864
- realToCanvasMat,
865
- borderRadius: hole.rect_border_radius ?? 0,
866
- rotation: hole.rect_ccw_rotation
867
- });
868
- }
869
624
  drawRect({
870
625
  ctx,
871
626
  center: { x: hole.x, y: hole.y },
872
- width: hole.rect_pad_width,
873
- height: hole.rect_pad_height,
627
+ width: hole.rect_pad_width - copperInset * 2,
628
+ height: hole.rect_pad_height - copperInset * 2,
874
629
  fill: copperColor,
875
630
  realToCanvasMat,
876
- borderRadius: hole.rect_border_radius ?? 0,
631
+ borderRadius: hole.rect_border_radius ? Math.max(0, hole.rect_border_radius - copperInset) : 0,
877
632
  rotation: hole.rect_ccw_rotation
878
633
  });
879
- if (hasSoldermask && margin < 0) {
880
- drawSoldermaskRingForRect(
881
- ctx,
882
- { x: hole.x, y: hole.y },
883
- hole.rect_pad_width,
884
- hole.rect_pad_height,
885
- margin,
886
- hole.rect_border_radius ?? 0,
887
- hole.rect_ccw_rotation ?? 0,
888
- realToCanvasMat,
889
- soldermaskRingColor,
890
- copperColor
891
- );
892
- }
893
- if (isCoveredWithSoldermask) {
894
- drawRect({
895
- ctx,
896
- center: { x: hole.x, y: hole.y },
897
- width: hole.rect_pad_width,
898
- height: hole.rect_pad_height,
899
- fill: soldermaskRingColor,
900
- realToCanvasMat,
901
- borderRadius: hole.rect_border_radius ?? 0,
902
- rotation: hole.rect_ccw_rotation
903
- });
904
- }
905
- if (!isCoveredWithSoldermask) {
906
- const holeX = hole.x + (hole.hole_offset_x ?? 0);
907
- const holeY = hole.y + (hole.hole_offset_y ?? 0);
908
- drawPill({
909
- ctx,
910
- center: { x: holeX, y: holeY },
911
- width: hole.hole_width,
912
- height: hole.hole_height,
913
- fill: colorMap.drill,
914
- realToCanvasMat,
915
- rotation: hole.hole_ccw_rotation
916
- });
917
- }
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
+ });
918
645
  return;
919
646
  }
920
647
  if (hole.shape === "hole_with_polygon_pad") {
@@ -924,80 +651,54 @@ function drawPcbPlatedHole(params) {
924
651
  x: hole.x + point.x,
925
652
  y: hole.y + point.y
926
653
  }));
927
- if (hasSoldermask && margin > 0) {
928
- const expandedPoints = offsetPolygonPoints(padPoints, margin);
654
+ const copperPoints = copperInset > 0 ? offsetPolygonPoints(padPoints, -copperInset) : padPoints;
655
+ if (copperPoints.length >= 3) {
929
656
  drawPolygon({
930
657
  ctx,
931
- points: expandedPoints,
932
- fill: positiveMarginColor,
658
+ points: copperPoints,
659
+ fill: copperColor,
933
660
  realToCanvasMat
934
661
  });
935
662
  }
936
- 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({
937
669
  ctx,
938
- points: padPoints,
939
- 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,
940
700
  realToCanvasMat
941
701
  });
942
- if (hasSoldermask && margin < 0) {
943
- drawSoldermaskRingForPolygon(
944
- ctx,
945
- padPoints,
946
- margin,
947
- realToCanvasMat,
948
- soldermaskRingColor,
949
- copperColor
950
- );
951
- }
952
- if (isCoveredWithSoldermask) {
953
- drawPolygon({
954
- ctx,
955
- points: padPoints,
956
- fill: soldermaskRingColor,
957
- realToCanvasMat
958
- });
959
- }
960
- }
961
- if (!isCoveredWithSoldermask) {
962
- const holeX = hole.x + (hole.hole_offset_x ?? 0);
963
- const holeY = hole.y + (hole.hole_offset_y ?? 0);
964
- const holeShape = hole.hole_shape;
965
- if (holeShape === "circle") {
966
- drawCircle({
967
- ctx,
968
- center: { x: holeX, y: holeY },
969
- radius: (hole.hole_diameter ?? 0) / 2,
970
- fill: colorMap.drill,
971
- realToCanvasMat
972
- });
973
- } else if (holeShape === "oval") {
974
- drawOval({
975
- ctx,
976
- center: { x: holeX, y: holeY },
977
- radius_x: (hole.hole_width ?? 0) / 2,
978
- radius_y: (hole.hole_height ?? 0) / 2,
979
- fill: colorMap.drill,
980
- realToCanvasMat
981
- });
982
- } else if (holeShape === "pill") {
983
- drawPill({
984
- ctx,
985
- center: { x: holeX, y: holeY },
986
- width: hole.hole_width ?? 0,
987
- height: hole.hole_height ?? 0,
988
- fill: colorMap.drill,
989
- realToCanvasMat
990
- });
991
- } else if (holeShape === "rotated_pill") {
992
- drawPill({
993
- ctx,
994
- center: { x: holeX, y: holeY },
995
- width: hole.hole_width ?? 0,
996
- height: hole.hole_height ?? 0,
997
- fill: colorMap.drill,
998
- realToCanvasMat
999
- });
1000
- }
1001
702
  }
1002
703
  return;
1003
704
  }
@@ -1030,304 +731,71 @@ function getRotation(hole) {
1030
731
  return 0;
1031
732
  }
1032
733
  function drawPcbHole(params) {
1033
- const { ctx, hole, realToCanvasMat, colorMap } = params;
1034
- const isCoveredWithSoldermask = hole.is_covered_with_solder_mask === true;
1035
- const margin = isCoveredWithSoldermask ? 0 : hole.soldermask_margin ?? 0;
1036
- const hasSoldermask = !isCoveredWithSoldermask && hole.soldermask_margin !== void 0 && hole.soldermask_margin !== 0;
1037
- const positiveMarginColor = colorMap.substrate;
1038
- const soldermaskOverlayColor = colorMap.soldermaskOverCopper.top;
1039
- 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;
1040
739
  if (hole.hole_shape === "circle") {
1041
- if (hasSoldermask && margin > 0) {
1042
- drawCircle({
1043
- ctx,
1044
- center: { x: hole.x, y: hole.y },
1045
- radius: hole.hole_diameter / 2 + margin,
1046
- fill: positiveMarginColor,
1047
- realToCanvasMat
1048
- });
1049
- }
1050
- if (!isCoveredWithSoldermask) {
1051
- drawCircle({
1052
- ctx,
1053
- center: { x: hole.x, y: hole.y },
1054
- radius: hole.hole_diameter / 2,
1055
- fill: colorMap.drill,
1056
- realToCanvasMat
1057
- });
1058
- if (hasSoldermask && margin < 0) {
1059
- drawSoldermaskRingForCircle(
1060
- ctx,
1061
- { x: hole.x, y: hole.y },
1062
- hole.hole_diameter / 2,
1063
- margin,
1064
- realToCanvasMat,
1065
- soldermaskRingColor,
1066
- colorMap.drill
1067
- );
1068
- }
1069
- }
1070
- if (isCoveredWithSoldermask) {
1071
- drawCircle({
1072
- ctx,
1073
- center: { x: hole.x, y: hole.y },
1074
- radius: hole.hole_diameter / 2,
1075
- fill: soldermaskOverlayColor,
1076
- realToCanvasMat
1077
- });
1078
- }
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
+ });
1079
747
  return;
1080
748
  }
1081
749
  if (hole.hole_shape === "square") {
1082
750
  const rotation = getRotation(hole);
1083
- if (hasSoldermask && margin > 0) {
1084
- drawRect({
1085
- ctx,
1086
- center: { x: hole.x, y: hole.y },
1087
- width: hole.hole_diameter + margin * 2,
1088
- height: hole.hole_diameter + margin * 2,
1089
- fill: positiveMarginColor,
1090
- realToCanvasMat,
1091
- rotation
1092
- });
1093
- }
1094
- if (!isCoveredWithSoldermask) {
1095
- drawRect({
1096
- ctx,
1097
- center: { x: hole.x, y: hole.y },
1098
- width: hole.hole_diameter,
1099
- height: hole.hole_diameter,
1100
- fill: colorMap.drill,
1101
- realToCanvasMat,
1102
- rotation
1103
- });
1104
- if (hasSoldermask && margin < 0) {
1105
- drawSoldermaskRingForRect(
1106
- ctx,
1107
- { x: hole.x, y: hole.y },
1108
- hole.hole_diameter,
1109
- hole.hole_diameter,
1110
- margin,
1111
- 0,
1112
- rotation,
1113
- realToCanvasMat,
1114
- soldermaskRingColor,
1115
- colorMap.drill
1116
- );
1117
- }
1118
- }
1119
- if (isCoveredWithSoldermask) {
1120
- drawRect({
1121
- ctx,
1122
- center: { x: hole.x, y: hole.y },
1123
- width: hole.hole_diameter,
1124
- height: hole.hole_diameter,
1125
- fill: soldermaskOverlayColor,
1126
- realToCanvasMat,
1127
- rotation
1128
- });
1129
- }
1130
- return;
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
+ });
760
+ return;
1131
761
  }
1132
762
  if (hole.hole_shape === "oval") {
1133
763
  const rotation = getRotation(hole);
1134
- if (hasSoldermask && margin > 0) {
1135
- drawOval({
1136
- ctx,
1137
- center: { x: hole.x, y: hole.y },
1138
- radius_x: hole.hole_width / 2 + margin,
1139
- radius_y: hole.hole_height / 2 + margin,
1140
- fill: positiveMarginColor,
1141
- realToCanvasMat,
1142
- rotation
1143
- });
1144
- }
1145
- if (!isCoveredWithSoldermask) {
1146
- drawOval({
1147
- ctx,
1148
- center: { x: hole.x, y: hole.y },
1149
- radius_x: hole.hole_width / 2,
1150
- radius_y: hole.hole_height / 2,
1151
- fill: colorMap.drill,
1152
- realToCanvasMat,
1153
- rotation
1154
- });
1155
- if (hasSoldermask && margin < 0) {
1156
- drawSoldermaskRingForOval(
1157
- ctx,
1158
- { x: hole.x, y: hole.y },
1159
- hole.hole_width / 2,
1160
- hole.hole_height / 2,
1161
- margin,
1162
- rotation,
1163
- realToCanvasMat,
1164
- soldermaskRingColor,
1165
- colorMap.drill
1166
- );
1167
- }
1168
- }
1169
- if (isCoveredWithSoldermask) {
1170
- drawOval({
1171
- ctx,
1172
- center: { x: hole.x, y: hole.y },
1173
- radius_x: hole.hole_width / 2,
1174
- radius_y: hole.hole_height / 2,
1175
- fill: soldermaskOverlayColor,
1176
- realToCanvasMat,
1177
- rotation
1178
- });
1179
- }
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
+ });
1180
773
  return;
1181
774
  }
1182
775
  if (hole.hole_shape === "rect") {
1183
776
  const rotation = getRotation(hole);
1184
- if (hasSoldermask && margin > 0) {
1185
- drawRect({
1186
- ctx,
1187
- center: { x: hole.x, y: hole.y },
1188
- width: hole.hole_width + margin * 2,
1189
- height: hole.hole_height + margin * 2,
1190
- fill: positiveMarginColor,
1191
- realToCanvasMat,
1192
- rotation
1193
- });
1194
- }
1195
- if (!isCoveredWithSoldermask) {
1196
- drawRect({
1197
- ctx,
1198
- center: { x: hole.x, y: hole.y },
1199
- width: hole.hole_width,
1200
- height: hole.hole_height,
1201
- fill: colorMap.drill,
1202
- realToCanvasMat,
1203
- rotation
1204
- });
1205
- if (hasSoldermask && margin < 0) {
1206
- drawSoldermaskRingForRect(
1207
- ctx,
1208
- { x: hole.x, y: hole.y },
1209
- hole.hole_width,
1210
- hole.hole_height,
1211
- margin,
1212
- 0,
1213
- rotation,
1214
- realToCanvasMat,
1215
- soldermaskRingColor,
1216
- colorMap.drill
1217
- );
1218
- }
1219
- }
1220
- if (isCoveredWithSoldermask) {
1221
- drawRect({
1222
- ctx,
1223
- center: { x: hole.x, y: hole.y },
1224
- width: hole.hole_width,
1225
- height: hole.hole_height,
1226
- fill: soldermaskOverlayColor,
1227
- realToCanvasMat,
1228
- rotation
1229
- });
1230
- }
1231
- return;
1232
- }
1233
- if (hole.hole_shape === "pill") {
1234
- const rotation = getRotation(hole);
1235
- if (hasSoldermask && margin > 0) {
1236
- drawPill({
1237
- ctx,
1238
- center: { x: hole.x, y: hole.y },
1239
- width: hole.hole_width + margin * 2,
1240
- height: hole.hole_height + margin * 2,
1241
- fill: positiveMarginColor,
1242
- realToCanvasMat,
1243
- rotation
1244
- });
1245
- }
1246
- if (!isCoveredWithSoldermask) {
1247
- drawPill({
1248
- ctx,
1249
- center: { x: hole.x, y: hole.y },
1250
- width: hole.hole_width,
1251
- height: hole.hole_height,
1252
- fill: colorMap.drill,
1253
- realToCanvasMat,
1254
- rotation
1255
- });
1256
- if (hasSoldermask && margin < 0) {
1257
- drawSoldermaskRingForPill(
1258
- ctx,
1259
- { x: hole.x, y: hole.y },
1260
- hole.hole_width,
1261
- hole.hole_height,
1262
- margin,
1263
- rotation,
1264
- realToCanvasMat,
1265
- soldermaskRingColor,
1266
- colorMap.drill
1267
- );
1268
- }
1269
- }
1270
- if (isCoveredWithSoldermask) {
1271
- drawPill({
1272
- ctx,
1273
- center: { x: hole.x, y: hole.y },
1274
- width: hole.hole_width,
1275
- height: hole.hole_height,
1276
- fill: soldermaskOverlayColor,
1277
- realToCanvasMat,
1278
- rotation
1279
- });
1280
- }
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
+ });
1281
786
  return;
1282
787
  }
1283
- if (hole.hole_shape === "rotated_pill") {
788
+ if (hole.hole_shape === "pill" || hole.hole_shape === "rotated_pill") {
1284
789
  const rotation = getRotation(hole);
1285
- if (hasSoldermask && margin > 0) {
1286
- drawPill({
1287
- ctx,
1288
- center: { x: hole.x, y: hole.y },
1289
- width: hole.hole_width + margin * 2,
1290
- height: hole.hole_height + margin * 2,
1291
- fill: positiveMarginColor,
1292
- realToCanvasMat,
1293
- rotation
1294
- });
1295
- }
1296
- if (!isCoveredWithSoldermask) {
1297
- drawPill({
1298
- ctx,
1299
- center: { x: hole.x, y: hole.y },
1300
- width: hole.hole_width,
1301
- height: hole.hole_height,
1302
- fill: colorMap.drill,
1303
- realToCanvasMat,
1304
- rotation
1305
- });
1306
- if (hasSoldermask && margin < 0) {
1307
- drawSoldermaskRingForPill(
1308
- ctx,
1309
- { x: hole.x, y: hole.y },
1310
- hole.hole_width,
1311
- hole.hole_height,
1312
- margin,
1313
- rotation,
1314
- realToCanvasMat,
1315
- soldermaskRingColor,
1316
- colorMap.drill
1317
- );
1318
- }
1319
- }
1320
- if (isCoveredWithSoldermask) {
1321
- drawPill({
1322
- ctx,
1323
- center: { x: hole.x, y: hole.y },
1324
- width: hole.hole_width,
1325
- height: hole.hole_height,
1326
- fill: soldermaskOverlayColor,
1327
- realToCanvasMat,
1328
- rotation
1329
- });
1330
- }
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
+ });
1331
799
  return;
1332
800
  }
1333
801
  }
@@ -1336,49 +804,16 @@ function drawPcbHole(params) {
1336
804
  function layerToColor(layer, colorMap) {
1337
805
  return colorMap.copper[layer] ?? colorMap.copper.top;
1338
806
  }
1339
- function getSoldermaskColor2(layer, colorMap) {
1340
- return colorMap.soldermaskOverCopper[layer] ?? colorMap.soldermaskOverCopper.top;
1341
- }
1342
- function getBorderRadius(pad, margin = 0) {
1343
- let r = 0;
807
+ function getBorderRadius(pad) {
1344
808
  if (pad.shape === "rect" || pad.shape === "rotated_rect") {
1345
- r = pad.corner_radius ?? pad.rect_border_radius ?? 0;
809
+ return pad.corner_radius ?? pad.rect_border_radius ?? 0;
1346
810
  }
1347
- return r + margin;
811
+ return 0;
1348
812
  }
1349
813
  function drawPcbSmtPad(params) {
1350
814
  const { ctx, pad, realToCanvasMat, colorMap } = params;
1351
815
  const color = layerToColor(pad.layer, colorMap);
1352
- const isCoveredWithSoldermask = pad.is_covered_with_solder_mask === true;
1353
- const margin = isCoveredWithSoldermask ? 0 : pad.soldermask_margin ?? 0;
1354
- const soldermaskRingColor = getSoldermaskColor2(pad.layer, colorMap);
1355
- const positiveMarginColor = colorMap.substrate;
1356
- const soldermaskOverlayColor = getSoldermaskColor2(pad.layer, colorMap);
1357
- const hasSoldermask = !isCoveredWithSoldermask && margin !== 0;
1358
- let ml = margin;
1359
- let mr = margin;
1360
- let mt = margin;
1361
- let mb = margin;
1362
- let hasAnySoldermask = hasSoldermask;
1363
- if (!isCoveredWithSoldermask && (pad.shape === "rect" || pad.shape === "rotated_rect")) {
1364
- ml = pad.soldermask_margin_left ?? pad.soldermask_margin ?? 0;
1365
- mr = pad.soldermask_margin_right ?? pad.soldermask_margin ?? 0;
1366
- mt = pad.soldermask_margin_top ?? pad.soldermask_margin ?? 0;
1367
- mb = pad.soldermask_margin_bottom ?? pad.soldermask_margin ?? 0;
1368
- hasAnySoldermask = ml !== 0 || mr !== 0 || mt !== 0 || mb !== 0;
1369
- }
1370
816
  if (pad.shape === "rect") {
1371
- if (hasAnySoldermask && (ml > 0 || mr > 0 || mt > 0 || mb > 0)) {
1372
- drawRect({
1373
- ctx,
1374
- center: { x: pad.x + (mr - ml) / 2, y: pad.y + (mt - mb) / 2 },
1375
- width: pad.width + ml + mr,
1376
- height: pad.height + mt + mb,
1377
- fill: positiveMarginColor,
1378
- realToCanvasMat,
1379
- borderRadius: getBorderRadius(pad)
1380
- });
1381
- }
1382
817
  drawRect({
1383
818
  ctx,
1384
819
  center: { x: pad.x, y: pad.y },
@@ -1388,52 +823,9 @@ function drawPcbSmtPad(params) {
1388
823
  realToCanvasMat,
1389
824
  borderRadius: getBorderRadius(pad)
1390
825
  });
1391
- if (hasAnySoldermask && (ml < 0 || mr < 0 || mt < 0 || mb < 0)) {
1392
- drawSoldermaskRingForRect(
1393
- ctx,
1394
- { x: pad.x, y: pad.y },
1395
- pad.width,
1396
- pad.height,
1397
- pad.soldermask_margin ?? 0,
1398
- getBorderRadius(pad),
1399
- 0,
1400
- realToCanvasMat,
1401
- soldermaskRingColor,
1402
- color,
1403
- { left: ml, right: mr, top: mt, bottom: mb }
1404
- );
1405
- }
1406
- if (isCoveredWithSoldermask) {
1407
- drawRect({
1408
- ctx,
1409
- center: { x: pad.x, y: pad.y },
1410
- width: pad.width,
1411
- height: pad.height,
1412
- fill: soldermaskOverlayColor,
1413
- realToCanvasMat,
1414
- borderRadius: getBorderRadius(pad)
1415
- });
1416
- }
1417
826
  return;
1418
827
  }
1419
828
  if (pad.shape === "rotated_rect") {
1420
- const radians = (pad.ccw_rotation ?? 0) * Math.PI / 180;
1421
- const dxLocal = (mr - ml) / 2;
1422
- const dyLocal = (mt - mb) / 2;
1423
- const dxGlobal = dxLocal * Math.cos(radians) - dyLocal * Math.sin(radians);
1424
- const dyGlobal = dxLocal * Math.sin(radians) + dyLocal * Math.cos(radians);
1425
- if (hasAnySoldermask && (ml > 0 || mr > 0 || mt > 0 || mb > 0)) {
1426
- drawRect({
1427
- ctx,
1428
- center: { x: pad.x + dxGlobal, y: pad.y + dyGlobal },
1429
- width: pad.width + ml + mr,
1430
- height: pad.height + mt + mb,
1431
- fill: positiveMarginColor,
1432
- realToCanvasMat,
1433
- borderRadius: getBorderRadius(pad),
1434
- rotation: pad.ccw_rotation ?? 0
1435
- });
1436
- }
1437
829
  drawRect({
1438
830
  ctx,
1439
831
  center: { x: pad.x, y: pad.y },
@@ -1444,45 +836,9 @@ function drawPcbSmtPad(params) {
1444
836
  borderRadius: getBorderRadius(pad),
1445
837
  rotation: pad.ccw_rotation ?? 0
1446
838
  });
1447
- if (hasAnySoldermask && (ml < 0 || mr < 0 || mt < 0 || mb < 0)) {
1448
- drawSoldermaskRingForRect(
1449
- ctx,
1450
- { x: pad.x, y: pad.y },
1451
- pad.width,
1452
- pad.height,
1453
- pad.soldermask_margin ?? 0,
1454
- getBorderRadius(pad),
1455
- pad.ccw_rotation ?? 0,
1456
- realToCanvasMat,
1457
- soldermaskRingColor,
1458
- color,
1459
- { left: ml, right: mr, top: mt, bottom: mb }
1460
- );
1461
- }
1462
- if (isCoveredWithSoldermask) {
1463
- drawRect({
1464
- ctx,
1465
- center: { x: pad.x, y: pad.y },
1466
- width: pad.width,
1467
- height: pad.height,
1468
- fill: soldermaskOverlayColor,
1469
- realToCanvasMat,
1470
- borderRadius: getBorderRadius(pad),
1471
- rotation: pad.ccw_rotation ?? 0
1472
- });
1473
- }
1474
839
  return;
1475
840
  }
1476
841
  if (pad.shape === "circle") {
1477
- if (hasSoldermask && margin > 0) {
1478
- drawCircle({
1479
- ctx,
1480
- center: { x: pad.x, y: pad.y },
1481
- radius: pad.radius + margin,
1482
- fill: positiveMarginColor,
1483
- realToCanvasMat
1484
- });
1485
- }
1486
842
  drawCircle({
1487
843
  ctx,
1488
844
  center: { x: pad.x, y: pad.y },
@@ -1490,39 +846,9 @@ function drawPcbSmtPad(params) {
1490
846
  fill: color,
1491
847
  realToCanvasMat
1492
848
  });
1493
- if (hasSoldermask && margin < 0) {
1494
- drawSoldermaskRingForCircle(
1495
- ctx,
1496
- { x: pad.x, y: pad.y },
1497
- pad.radius,
1498
- margin,
1499
- realToCanvasMat,
1500
- soldermaskRingColor,
1501
- color
1502
- );
1503
- }
1504
- if (isCoveredWithSoldermask && margin === 0) {
1505
- drawCircle({
1506
- ctx,
1507
- center: { x: pad.x, y: pad.y },
1508
- radius: pad.radius,
1509
- fill: soldermaskOverlayColor,
1510
- realToCanvasMat
1511
- });
1512
- }
1513
849
  return;
1514
850
  }
1515
851
  if (pad.shape === "pill") {
1516
- if (hasSoldermask && margin > 0) {
1517
- drawPill({
1518
- ctx,
1519
- center: { x: pad.x, y: pad.y },
1520
- width: pad.width + margin * 2,
1521
- height: pad.height + margin * 2,
1522
- fill: positiveMarginColor,
1523
- realToCanvasMat
1524
- });
1525
- }
1526
852
  drawPill({
1527
853
  ctx,
1528
854
  center: { x: pad.x, y: pad.y },
@@ -1531,43 +857,9 @@ function drawPcbSmtPad(params) {
1531
857
  fill: color,
1532
858
  realToCanvasMat
1533
859
  });
1534
- if (hasSoldermask && margin < 0) {
1535
- drawSoldermaskRingForPill(
1536
- ctx,
1537
- { x: pad.x, y: pad.y },
1538
- pad.width,
1539
- pad.height,
1540
- margin,
1541
- 0,
1542
- realToCanvasMat,
1543
- soldermaskRingColor,
1544
- color
1545
- );
1546
- }
1547
- if (isCoveredWithSoldermask && margin === 0) {
1548
- drawPill({
1549
- ctx,
1550
- center: { x: pad.x, y: pad.y },
1551
- width: pad.width,
1552
- height: pad.height,
1553
- fill: soldermaskOverlayColor,
1554
- realToCanvasMat
1555
- });
1556
- }
1557
860
  return;
1558
861
  }
1559
862
  if (pad.shape === "rotated_pill") {
1560
- if (hasSoldermask && margin > 0) {
1561
- drawPill({
1562
- ctx,
1563
- center: { x: pad.x, y: pad.y },
1564
- width: pad.width + margin * 2,
1565
- height: pad.height + margin * 2,
1566
- fill: positiveMarginColor,
1567
- realToCanvasMat,
1568
- rotation: pad.ccw_rotation ?? 0
1569
- });
1570
- }
1571
863
  drawPill({
1572
864
  ctx,
1573
865
  center: { x: pad.x, y: pad.y },
@@ -1577,67 +869,16 @@ function drawPcbSmtPad(params) {
1577
869
  realToCanvasMat,
1578
870
  rotation: pad.ccw_rotation ?? 0
1579
871
  });
1580
- if (hasSoldermask && margin < 0) {
1581
- drawSoldermaskRingForPill(
1582
- ctx,
1583
- { x: pad.x, y: pad.y },
1584
- pad.width,
1585
- pad.height,
1586
- margin,
1587
- pad.ccw_rotation ?? 0,
1588
- realToCanvasMat,
1589
- soldermaskRingColor,
1590
- color
1591
- );
1592
- }
1593
- if (isCoveredWithSoldermask && margin === 0) {
1594
- drawPill({
1595
- ctx,
1596
- center: { x: pad.x, y: pad.y },
1597
- width: pad.width,
1598
- height: pad.height,
1599
- fill: soldermaskOverlayColor,
1600
- realToCanvasMat,
1601
- rotation: pad.ccw_rotation ?? 0
1602
- });
1603
- }
1604
872
  return;
1605
873
  }
1606
874
  if (pad.shape === "polygon") {
1607
875
  if (pad.points && pad.points.length >= 3) {
1608
- if (hasSoldermask && margin > 0) {
1609
- const expandedPoints = offsetPolygonPoints(pad.points, margin);
1610
- drawPolygon({
1611
- ctx,
1612
- points: expandedPoints,
1613
- fill: positiveMarginColor,
1614
- realToCanvasMat
1615
- });
1616
- }
1617
876
  drawPolygon({
1618
877
  ctx,
1619
878
  points: pad.points,
1620
879
  fill: color,
1621
880
  realToCanvasMat
1622
881
  });
1623
- if (hasSoldermask && margin < 0) {
1624
- drawSoldermaskRingForPolygon(
1625
- ctx,
1626
- pad.points,
1627
- margin,
1628
- realToCanvasMat,
1629
- soldermaskRingColor,
1630
- color
1631
- );
1632
- }
1633
- if (isCoveredWithSoldermask && margin === 0) {
1634
- drawPolygon({
1635
- ctx,
1636
- points: pad.points,
1637
- fill: soldermaskOverlayColor,
1638
- realToCanvasMat
1639
- });
1640
- }
1641
882
  }
1642
883
  return;
1643
884
  }
@@ -1743,6 +984,13 @@ function drawPcbBoard(params) {
1743
984
  const { ctx, board, realToCanvasMat, colorMap } = params;
1744
985
  const { width, height, center, outline } = board;
1745
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
+ });
1746
994
  drawPath({
1747
995
  ctx,
1748
996
  points: outline.map((p) => ({ x: p.x, y: p.y })),
@@ -1759,7 +1007,7 @@ function drawPcbBoard(params) {
1759
1007
  center,
1760
1008
  width,
1761
1009
  height,
1762
- fill: "transparent",
1010
+ fill: colorMap.substrate,
1763
1011
  realToCanvasMat
1764
1012
  });
1765
1013
  const halfWidth = width / 2;
@@ -2823,359 +2071,1296 @@ function drawPcbNoteLine(params) {
2823
2071
  ctx.restore();
2824
2072
  }
2825
2073
 
2826
- // lib/drawer/CircuitToCanvasDrawer.ts
2827
- var CircuitToCanvasDrawer = class {
2828
- ctx;
2829
- colorMap;
2830
- realToCanvasMat;
2831
- constructor(canvasOrContext) {
2832
- if ("getContext" in canvasOrContext && typeof canvasOrContext.getContext === "function") {
2833
- const ctx = canvasOrContext.getContext("2d");
2834
- if (!ctx) {
2835
- 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);
2836
2088
  }
2837
- this.ctx = ctx;
2838
- } else {
2839
- this.ctx = canvasOrContext;
2840
- }
2841
- this.colorMap = { ...DEFAULT_PCB_COLOR_MAP };
2842
- this.realToCanvasMat = identity();
2843
- }
2844
- configure(config) {
2845
- if (config.colorOverrides) {
2846
- this.colorMap = {
2847
- ...this.colorMap,
2848
- ...config.colorOverrides,
2849
- copper: {
2850
- ...this.colorMap.copper,
2851
- ...config.colorOverrides.copper
2852
- },
2853
- silkscreen: {
2854
- ...this.colorMap.silkscreen,
2855
- ...config.colorOverrides.silkscreen
2856
- },
2857
- soldermask: {
2858
- ...this.colorMap.soldermask,
2859
- ...config.colorOverrides.soldermask
2860
- },
2861
- soldermaskWithCopperUnderneath: {
2862
- ...this.colorMap.soldermaskWithCopperUnderneath,
2863
- ...config.colorOverrides.soldermaskWithCopperUnderneath
2864
- },
2865
- soldermaskOverCopper: {
2866
- ...this.colorMap.soldermaskOverCopper,
2867
- ...config.colorOverrides.soldermaskOverCopper
2868
- }
2869
- };
2870
2089
  }
2090
+ ctx.closePath();
2871
2091
  }
2872
- setCameraBounds(bounds) {
2873
- const canvas = this.ctx.canvas;
2874
- const canvasWidth = canvas.width;
2875
- const canvasHeight = canvas.height;
2876
- const realWidth = bounds.maxX - bounds.minX;
2877
- const realHeight = bounds.maxY - bounds.minY;
2878
- const scaleX = canvasWidth / realWidth;
2879
- const scaleY = canvasHeight / realHeight;
2880
- const uniformScale = Math.min(scaleX, scaleY);
2881
- const offsetX = (canvasWidth - realWidth * uniformScale) / 2;
2882
- const offsetY = (canvasHeight - realHeight * uniformScale) / 2;
2883
- this.realToCanvasMat = compose(
2884
- translate(offsetX, offsetY),
2885
- scale(uniformScale, -uniformScale),
2886
- 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
2887
2117
  );
2888
2118
  }
2889
- drawElements(elements, options = {}) {
2890
- const hasSoldermaskPads = elements.some(
2891
- (el) => el.type === "pcb_smtpad" && el.is_covered_with_solder_mask === true
2892
- );
2893
- const hasSoldermaskHoles = elements.some(
2894
- (el) => el.type === "pcb_hole" && el.is_covered_with_solder_mask === true
2895
- );
2896
- const hasSoldermaskPlatedHoles = elements.some(
2897
- (el) => el.type === "pcb_plated_hole" && el.is_covered_with_solder_mask === true
2898
- );
2899
- for (const element of elements) {
2900
- if (element.type === "pcb_board") {
2901
- this.drawBoardWithSoldermask(element);
2902
- } else {
2903
- this.drawElement(element, options);
2904
- }
2905
- }
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);
2906
2142
  }
2907
- drawBoardWithSoldermask(board) {
2908
- const { width, height, center, outline } = board;
2909
- const layer = "top";
2910
- if (outline && Array.isArray(outline) && outline.length >= 3) {
2911
- const soldermaskColor = this.colorMap.soldermask[layer] ?? this.colorMap.soldermask.top;
2912
- const canvasPoints = outline.map((p) => {
2913
- const [x, y] = applyToPoint16(this.realToCanvasMat, [p.x, p.y]);
2914
- return { x, y };
2915
- });
2916
- this.ctx.beginPath();
2917
- const firstPoint = canvasPoints[0];
2918
- if (firstPoint) {
2919
- this.ctx.moveTo(firstPoint.x, firstPoint.y);
2920
- for (let i = 1; i < canvasPoints.length; i++) {
2921
- const point = canvasPoints[i];
2922
- if (point) {
2923
- this.ctx.lineTo(point.x, point.y);
2924
- }
2925
- }
2926
- this.ctx.closePath();
2927
- }
2928
- this.ctx.fillStyle = soldermaskColor;
2929
- this.ctx.fill();
2930
- drawPath({
2931
- ctx: this.ctx,
2932
- points: outline.map((p) => ({ x: p.x, y: p.y })),
2933
- stroke: this.colorMap.boardOutline,
2934
- strokeWidth: 0.1,
2935
- realToCanvasMat: this.realToCanvasMat,
2936
- closePath: true
2937
- });
2938
- 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);
2939
2162
  }
2940
- if (width !== void 0 && height !== void 0 && center) {
2941
- const soldermaskColor = this.colorMap.soldermask[layer] ?? this.colorMap.soldermask.top;
2942
- drawRect({
2943
- ctx: this.ctx,
2944
- center,
2945
- width,
2946
- height,
2947
- fill: soldermaskColor,
2948
- realToCanvasMat: this.realToCanvasMat
2949
- });
2950
- const halfWidth = width / 2;
2951
- const halfHeight = height / 2;
2952
- const corners = [
2953
- { x: center.x - halfWidth, y: center.y - halfHeight },
2954
- { x: center.x + halfWidth, y: center.y - halfHeight },
2955
- { x: center.x + halfWidth, y: center.y + halfHeight },
2956
- { x: center.x - halfWidth, y: center.y + halfHeight }
2957
- ];
2958
- drawPath({
2959
- ctx: this.ctx,
2960
- points: corners,
2961
- stroke: this.colorMap.boardOutline,
2962
- strokeWidth: 0.1,
2963
- realToCanvasMat: this.realToCanvasMat,
2964
- closePath: true
2965
- });
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);
2966
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();
2967
2334
  }
2968
- drawElement(element, options) {
2969
- if (!shouldDrawElement(element, options)) {
2970
- 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);
2971
2351
  }
2972
- if (element.type === "pcb_plated_hole") {
2973
- drawPcbPlatedHole({
2974
- ctx: this.ctx,
2975
- hole: element,
2976
- realToCanvasMat: this.realToCanvasMat,
2977
- colorMap: this.colorMap
2978
- });
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);
2979
2363
  }
2980
- if (element.type === "pcb_via") {
2981
- drawPcbVia({
2982
- ctx: this.ctx,
2983
- via: element,
2984
- realToCanvasMat: this.realToCanvasMat,
2985
- colorMap: this.colorMap
2986
- });
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);
2987
2368
  }
2988
- if (element.type === "pcb_hole") {
2989
- drawPcbHole({
2990
- ctx: this.ctx,
2991
- hole: element,
2992
- realToCanvasMat: this.realToCanvasMat,
2993
- colorMap: this.colorMap
2994
- });
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);
2995
2382
  }
2996
- if (element.type === "pcb_smtpad") {
2997
- drawPcbSmtPad({
2998
- ctx: this.ctx,
2999
- pad: element,
3000
- realToCanvasMat: this.realToCanvasMat,
3001
- colorMap: this.colorMap
3002
- });
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);
3003
2388
  }
3004
- if (element.type === "pcb_trace") {
3005
- drawPcbTrace({
3006
- ctx: this.ctx,
3007
- trace: element,
3008
- realToCanvasMat: this.realToCanvasMat,
3009
- colorMap: this.colorMap
3010
- });
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);
3011
2402
  }
3012
- if (element.type === "pcb_board") {
3013
- drawPcbBoard({
3014
- ctx: this.ctx,
3015
- board: element,
3016
- realToCanvasMat: this.realToCanvasMat,
3017
- 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
3018
2437
  });
3019
2438
  }
3020
- if (element.type === "pcb_silkscreen_text") {
3021
- drawPcbSilkscreenText({
3022
- ctx: this.ctx,
3023
- text: element,
3024
- realToCanvasMat: this.realToCanvasMat,
3025
- 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
3026
2641
  });
3027
2642
  }
3028
- if (element.type === "pcb_silkscreen_rect") {
3029
- drawPcbSilkscreenRect({
3030
- ctx: this.ctx,
3031
- rect: element,
3032
- realToCanvasMat: this.realToCanvasMat,
3033
- 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
3034
2676
  });
3035
2677
  }
3036
- if (element.type === "pcb_silkscreen_circle") {
3037
- drawPcbSilkscreenCircle({
3038
- ctx: this.ctx,
3039
- circle: element,
3040
- realToCanvasMat: this.realToCanvasMat,
3041
- 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 };
3042
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);
3043
2864
  }
3044
- if (element.type === "pcb_silkscreen_line") {
3045
- drawPcbSilkscreenLine({
3046
- ctx: this.ctx,
3047
- line: element,
3048
- realToCanvasMat: this.realToCanvasMat,
3049
- 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
3050
2890
  });
3051
2891
  }
3052
- if (element.type === "pcb_silkscreen_path") {
3053
- drawPcbSilkscreenPath({
3054
- ctx: this.ctx,
3055
- path: element,
3056
- realToCanvasMat: this.realToCanvasMat,
3057
- colorMap: this.colorMap
3058
- });
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);
3059
2905
  }
3060
- if (element.type === "pcb_silkscreen_pill") {
3061
- drawPcbSilkscreenPill({
3062
- ctx: this.ctx,
3063
- pill: element,
3064
- realToCanvasMat: this.realToCanvasMat,
3065
- colorMap: this.colorMap
3066
- });
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);
3067
2918
  }
3068
- if (element.type === "pcb_silkscreen_oval") {
3069
- drawPcbSilkscreenOval({
3070
- ctx: this.ctx,
3071
- oval: element,
3072
- realToCanvasMat: this.realToCanvasMat,
3073
- 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
3074
2936
  });
3075
2937
  }
3076
- if (element.type === "pcb_cutout") {
3077
- drawPcbCutout({
3078
- ctx: this.ctx,
3079
- cutout: element,
3080
- realToCanvasMat: this.realToCanvasMat,
3081
- 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 };
3082
2954
  });
2955
+ drawPolygonPath({ ctx, points: innerCanvasPoints });
3083
2956
  }
3084
- if (element.type === "pcb_keepout") {
3085
- drawPcbKeepout({
3086
- ctx: this.ctx,
3087
- keepout: element,
3088
- realToCanvasMat: this.realToCanvasMat,
3089
- colorMap: this.colorMap
3090
- });
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;
3091
3073
  }
3092
- if (element.type === "pcb_copper_pour") {
3093
- drawPcbCopperPour({
3094
- ctx: this.ctx,
3095
- pour: element,
3096
- realToCanvasMat: this.realToCanvasMat,
3097
- colorMap: this.colorMap
3098
- });
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
+ };
3099
3103
  }
3100
- if (element.type === "pcb_copper_text") {
3101
- 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({
3102
3126
  ctx: this.ctx,
3103
- text: element,
3127
+ board,
3104
3128
  realToCanvasMat: this.realToCanvasMat,
3105
3129
  colorMap: this.colorMap
3106
3130
  });
3107
3131
  }
3108
- if (element.type === "pcb_fabrication_note_text") {
3109
- drawPcbFabricationNoteText({
3110
- ctx: this.ctx,
3111
- text: element,
3112
- realToCanvasMat: this.realToCanvasMat,
3113
- colorMap: this.colorMap
3114
- });
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
+ }
3115
3150
  }
3116
- if (element.type === "pcb_fabrication_note_rect") {
3117
- drawPcbFabricationNoteRect({
3151
+ const showSoldermask = options.showSoldermask ?? false;
3152
+ if (board) {
3153
+ drawPcbSoldermask({
3118
3154
  ctx: this.ctx,
3119
- rect: element,
3120
- realToCanvasMat: this.realToCanvasMat,
3121
- colorMap: this.colorMap
3122
- });
3123
- }
3124
- if (element.type === "pcb_note_rect") {
3125
- drawPcbNoteRect({
3155
+ board,
3156
+ elements,
3126
3157
  realToCanvasMat: this.realToCanvasMat,
3127
3158
  colorMap: this.colorMap,
3128
- ctx: this.ctx,
3129
- rect: element
3130
- });
3131
- }
3132
- if (element.type === "pcb_fabrication_note_path") {
3133
- drawPcbFabricationNotePath({
3134
- ctx: this.ctx,
3135
- path: element,
3136
- realToCanvasMat: this.realToCanvasMat,
3137
- colorMap: this.colorMap
3159
+ layer: "top",
3160
+ showSoldermask
3138
3161
  });
3139
3162
  }
3140
- if (element.type === "pcb_note_path") {
3141
- drawPcbNotePath({
3142
- ctx: this.ctx,
3143
- path: element,
3144
- realToCanvasMat: this.realToCanvasMat,
3145
- colorMap: this.colorMap
3146
- });
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
+ }
3147
3221
  }
3148
- if (element.type === "pcb_note_text") {
3149
- drawPcbNoteText({
3150
- ctx: this.ctx,
3151
- text: element,
3152
- realToCanvasMat: this.realToCanvasMat,
3153
- colorMap: this.colorMap
3154
- });
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
+ }
3155
3240
  }
3156
- if (element.type === "pcb_note_line") {
3157
- drawPcbNoteLine({
3158
- ctx: this.ctx,
3159
- line: element,
3160
- realToCanvasMat: this.realToCanvasMat,
3161
- colorMap: this.colorMap
3162
- });
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
+ }
3163
3261
  }
3164
- if (element.type === "pcb_note_dimension") {
3165
- drawPcbNoteDimension({
3166
- ctx: this.ctx,
3167
- pcbNoteDimension: element,
3168
- realToCanvasMat: this.realToCanvasMat,
3169
- colorMap: this.colorMap
3170
- });
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
+ }
3171
3281
  }
3172
- if (element.type === "pcb_fabrication_note_dimension") {
3173
- drawPcbFabricationNoteDimension({
3174
- ctx: this.ctx,
3175
- pcbFabricationNoteDimension: element,
3176
- realToCanvasMat: this.realToCanvasMat,
3177
- colorMap: this.colorMap
3178
- });
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
+ }
3179
3364
  }
3180
3365
  }
3181
3366
  };