circuit-to-canvas 0.0.45 → 0.0.47

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 (27) hide show
  1. package/dist/index.js +530 -163
  2. package/lib/drawer/CircuitToCanvasDrawer.ts +1 -5
  3. package/lib/drawer/elements/pcb-hole.ts +248 -61
  4. package/lib/drawer/elements/pcb-plated-hole.ts +234 -107
  5. package/lib/drawer/elements/pcb-smtpad.ts +43 -21
  6. package/lib/drawer/elements/soldermask-margin.ts +130 -0
  7. package/package.json +1 -1
  8. package/tests/board-snapshot/__snapshots__/usb-c-flashlight-board.snap.png +0 -0
  9. package/tests/elements/__snapshots__/board-with-elements.snap.png +0 -0
  10. package/tests/elements/__snapshots__/custom-outline-board.snap.png +0 -0
  11. package/tests/elements/__snapshots__/pcb-board.snap.png +0 -0
  12. package/tests/elements/__snapshots__/pcb-comprehensive-soldermask-margin.snap.png +0 -0
  13. package/tests/elements/__snapshots__/pcb-fabrication-note-dimension.snap.png +0 -0
  14. package/tests/elements/__snapshots__/pcb-hole-soldermask-margin.snap.png +0 -0
  15. package/tests/elements/__snapshots__/pcb-keepout-layer-filter.snap.png +0 -0
  16. package/tests/elements/__snapshots__/pcb-keepout-multiple-layers.snap.png +0 -0
  17. package/tests/elements/__snapshots__/pcb-keepout-rect-and-circle.snap.png +0 -0
  18. package/tests/elements/__snapshots__/pcb-keepout-with-group-id.snap.png +0 -0
  19. package/tests/elements/__snapshots__/pcb-note-dimension-with-offset.snap.png +0 -0
  20. package/tests/elements/__snapshots__/pcb-plated-hole-soldermask-margin.snap.png +0 -0
  21. package/tests/elements/__snapshots__/pcb-silkscreen-oval.snap.png +0 -0
  22. package/tests/elements/__snapshots__/pcb-smtpad-soldermask-margin.snap.png +0 -0
  23. package/tests/elements/pcb-comprehensive-soldermask-margin.test.ts +1281 -0
  24. package/tests/elements/pcb-hole-soldermask-margin.test.ts +5 -5
  25. package/tests/elements/pcb-plated-hole-soldermask-margin.test.ts +8 -8
  26. package/tests/elements/pcb-smtpad-soldermask-margin.test.ts +0 -6
  27. package/tests/shapes/__snapshots__/dimension-line.snap.png +0 -0
package/dist/index.js CHANGED
@@ -456,6 +456,90 @@ function drawSoldermaskRingForOval(ctx, center, radius_x, radius_y, margin, rota
456
456
  }
457
457
  ctx.restore();
458
458
  }
459
+ function offsetPolygonPoints(points, offset) {
460
+ if (points.length < 3 || offset === 0) return points;
461
+ let centerX = 0;
462
+ let centerY = 0;
463
+ for (const point of points) {
464
+ centerX += point.x;
465
+ centerY += point.y;
466
+ }
467
+ centerX /= points.length;
468
+ centerY /= points.length;
469
+ const result = [];
470
+ for (const point of points) {
471
+ const vectorX = point.x - centerX;
472
+ const vectorY = point.y - centerY;
473
+ const distance = Math.sqrt(vectorX * vectorX + vectorY * vectorY);
474
+ if (distance > 0) {
475
+ const normalizedX = vectorX / distance;
476
+ const normalizedY = vectorY / distance;
477
+ result.push({
478
+ x: point.x + normalizedX * offset,
479
+ y: point.y + normalizedY * offset
480
+ });
481
+ } else {
482
+ result.push({
483
+ x: point.x + offset,
484
+ y: point.y
485
+ });
486
+ }
487
+ }
488
+ return result;
489
+ }
490
+ function drawSoldermaskRingForPolygon(ctx, points, margin, realToCanvasMat, soldermaskColor, padColor) {
491
+ if (points.length < 3 || margin >= 0) return;
492
+ const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
493
+ const prevCompositeOp = ctx.globalCompositeOperation;
494
+ if (ctx.globalCompositeOperation !== void 0) {
495
+ ctx.globalCompositeOperation = "source-atop";
496
+ }
497
+ ctx.beginPath();
498
+ const canvasPoints = points.map(
499
+ (p) => applyToPoint6(realToCanvasMat, [p.x, p.y])
500
+ );
501
+ const firstPoint = canvasPoints[0];
502
+ if (firstPoint) {
503
+ const [firstX, firstY] = firstPoint;
504
+ ctx.moveTo(firstX, firstY);
505
+ for (let i = 1; i < canvasPoints.length; i++) {
506
+ const point = canvasPoints[i];
507
+ if (point) {
508
+ const [x, y] = point;
509
+ ctx.lineTo(x, y);
510
+ }
511
+ }
512
+ ctx.closePath();
513
+ ctx.fillStyle = soldermaskColor;
514
+ ctx.fill();
515
+ }
516
+ if (ctx.globalCompositeOperation !== void 0) {
517
+ ctx.globalCompositeOperation = prevCompositeOp || "source-over";
518
+ }
519
+ const innerPoints = offsetPolygonPoints(points, margin);
520
+ if (innerPoints.length >= 3) {
521
+ ctx.beginPath();
522
+ const innerCanvasPoints = innerPoints.map(
523
+ (p) => applyToPoint6(realToCanvasMat, [p.x, p.y])
524
+ );
525
+ const firstInnerPoint = innerCanvasPoints[0];
526
+ if (firstInnerPoint) {
527
+ const [firstX, firstY] = firstInnerPoint;
528
+ ctx.moveTo(firstX, firstY);
529
+ for (let i = 1; i < innerCanvasPoints.length; i++) {
530
+ const point = innerCanvasPoints[i];
531
+ if (point) {
532
+ const [x, y] = point;
533
+ ctx.lineTo(x, y);
534
+ }
535
+ }
536
+ ctx.closePath();
537
+ ctx.fillStyle = padColor;
538
+ ctx.fill();
539
+ }
540
+ }
541
+ ctx.restore();
542
+ }
459
543
 
460
544
  // lib/drawer/elements/pcb-plated-hole.ts
461
545
  function getSoldermaskColor(layers, colorMap) {
@@ -464,8 +548,9 @@ function getSoldermaskColor(layers, colorMap) {
464
548
  }
465
549
  function drawPcbPlatedHole(params) {
466
550
  const { ctx, hole, realToCanvasMat, colorMap } = params;
467
- const hasSoldermask = hole.is_covered_with_solder_mask === true && hole.soldermask_margin !== void 0 && hole.soldermask_margin !== 0;
468
- const margin = hasSoldermask ? hole.soldermask_margin : 0;
551
+ const isCoveredWithSoldermask = hole.is_covered_with_solder_mask === true;
552
+ const margin = isCoveredWithSoldermask ? 0 : hole.soldermask_margin ?? 0;
553
+ const hasSoldermask = !isCoveredWithSoldermask && hole.soldermask_margin !== void 0 && hole.soldermask_margin !== 0;
469
554
  const soldermaskRingColor = getSoldermaskColor(hole.layers, colorMap);
470
555
  const positiveMarginColor = colorMap.substrate;
471
556
  const copperColor = colorMap.copper.top;
@@ -497,13 +582,24 @@ function drawPcbPlatedHole(params) {
497
582
  copperColor
498
583
  );
499
584
  }
500
- drawCircle({
501
- ctx,
502
- center: { x: hole.x, y: hole.y },
503
- radius: hole.hole_diameter / 2,
504
- fill: colorMap.drill,
505
- realToCanvasMat
506
- });
585
+ if (isCoveredWithSoldermask) {
586
+ drawCircle({
587
+ ctx,
588
+ center: { x: hole.x, y: hole.y },
589
+ radius: hole.outer_diameter / 2,
590
+ fill: soldermaskRingColor,
591
+ realToCanvasMat
592
+ });
593
+ }
594
+ if (!isCoveredWithSoldermask) {
595
+ drawCircle({
596
+ ctx,
597
+ center: { x: hole.x, y: hole.y },
598
+ radius: hole.hole_diameter / 2,
599
+ fill: colorMap.drill,
600
+ realToCanvasMat
601
+ });
602
+ }
507
603
  return;
508
604
  }
509
605
  if (hole.shape === "oval") {
@@ -540,15 +636,28 @@ function drawPcbPlatedHole(params) {
540
636
  copperColor
541
637
  );
542
638
  }
543
- drawOval({
544
- ctx,
545
- center: { x: hole.x, y: hole.y },
546
- radius_x: hole.hole_width / 2,
547
- radius_y: hole.hole_height / 2,
548
- fill: colorMap.drill,
549
- realToCanvasMat,
550
- rotation: hole.ccw_rotation
551
- });
639
+ if (isCoveredWithSoldermask) {
640
+ drawOval({
641
+ ctx,
642
+ center: { x: hole.x, y: hole.y },
643
+ radius_x: hole.outer_width / 2,
644
+ radius_y: hole.outer_height / 2,
645
+ fill: soldermaskRingColor,
646
+ realToCanvasMat,
647
+ rotation: hole.ccw_rotation
648
+ });
649
+ }
650
+ if (!isCoveredWithSoldermask) {
651
+ drawOval({
652
+ ctx,
653
+ center: { x: hole.x, y: hole.y },
654
+ radius_x: hole.hole_width / 2,
655
+ radius_y: hole.hole_height / 2,
656
+ fill: colorMap.drill,
657
+ realToCanvasMat,
658
+ rotation: hole.ccw_rotation
659
+ });
660
+ }
552
661
  return;
553
662
  }
554
663
  if (hole.shape === "pill") {
@@ -585,15 +694,28 @@ function drawPcbPlatedHole(params) {
585
694
  copperColor
586
695
  );
587
696
  }
588
- drawPill({
589
- ctx,
590
- center: { x: hole.x, y: hole.y },
591
- width: hole.hole_width,
592
- height: hole.hole_height,
593
- fill: colorMap.drill,
594
- realToCanvasMat,
595
- rotation: hole.ccw_rotation
596
- });
697
+ if (isCoveredWithSoldermask) {
698
+ drawPill({
699
+ ctx,
700
+ center: { x: hole.x, y: hole.y },
701
+ width: hole.outer_width,
702
+ height: hole.outer_height,
703
+ fill: soldermaskRingColor,
704
+ realToCanvasMat,
705
+ rotation: hole.ccw_rotation
706
+ });
707
+ }
708
+ if (!isCoveredWithSoldermask) {
709
+ drawPill({
710
+ ctx,
711
+ center: { x: hole.x, y: hole.y },
712
+ width: hole.hole_width,
713
+ height: hole.hole_height,
714
+ fill: colorMap.drill,
715
+ realToCanvasMat,
716
+ rotation: hole.ccw_rotation
717
+ });
718
+ }
597
719
  return;
598
720
  }
599
721
  if (hole.shape === "circular_hole_with_rect_pad") {
@@ -605,7 +727,7 @@ function drawPcbPlatedHole(params) {
605
727
  height: hole.rect_pad_height + margin * 2,
606
728
  fill: positiveMarginColor,
607
729
  realToCanvasMat,
608
- borderRadius: (hole.rect_border_radius ?? 0) + margin
730
+ borderRadius: hole.rect_border_radius ?? 0
609
731
  });
610
732
  }
611
733
  drawRect({
@@ -631,15 +753,28 @@ function drawPcbPlatedHole(params) {
631
753
  copperColor
632
754
  );
633
755
  }
634
- const holeX = hole.x + (hole.hole_offset_x ?? 0);
635
- const holeY = hole.y + (hole.hole_offset_y ?? 0);
636
- drawCircle({
637
- ctx,
638
- center: { x: holeX, y: holeY },
639
- radius: hole.hole_diameter / 2,
640
- fill: colorMap.drill,
641
- realToCanvasMat
642
- });
756
+ if (isCoveredWithSoldermask) {
757
+ drawRect({
758
+ ctx,
759
+ center: { x: hole.x, y: hole.y },
760
+ width: hole.rect_pad_width,
761
+ height: hole.rect_pad_height,
762
+ fill: soldermaskRingColor,
763
+ realToCanvasMat,
764
+ borderRadius: hole.rect_border_radius ?? 0
765
+ });
766
+ }
767
+ if (!isCoveredWithSoldermask) {
768
+ const holeX = hole.x + (hole.hole_offset_x ?? 0);
769
+ const holeY = hole.y + (hole.hole_offset_y ?? 0);
770
+ drawCircle({
771
+ ctx,
772
+ center: { x: holeX, y: holeY },
773
+ radius: hole.hole_diameter / 2,
774
+ fill: colorMap.drill,
775
+ realToCanvasMat
776
+ });
777
+ }
643
778
  return;
644
779
  }
645
780
  if (hole.shape === "pill_hole_with_rect_pad") {
@@ -651,7 +786,7 @@ function drawPcbPlatedHole(params) {
651
786
  height: hole.rect_pad_height + margin * 2,
652
787
  fill: positiveMarginColor,
653
788
  realToCanvasMat,
654
- borderRadius: (hole.rect_border_radius ?? 0) + margin
789
+ borderRadius: hole.rect_border_radius ?? 0
655
790
  });
656
791
  }
657
792
  drawRect({
@@ -677,16 +812,29 @@ function drawPcbPlatedHole(params) {
677
812
  copperColor
678
813
  );
679
814
  }
680
- const holeX = hole.x + (hole.hole_offset_x ?? 0);
681
- const holeY = hole.y + (hole.hole_offset_y ?? 0);
682
- drawPill({
683
- ctx,
684
- center: { x: holeX, y: holeY },
685
- width: hole.hole_width,
686
- height: hole.hole_height,
687
- fill: colorMap.drill,
688
- realToCanvasMat
689
- });
815
+ if (isCoveredWithSoldermask) {
816
+ drawRect({
817
+ ctx,
818
+ center: { x: hole.x, y: hole.y },
819
+ width: hole.rect_pad_width,
820
+ height: hole.rect_pad_height,
821
+ fill: soldermaskRingColor,
822
+ realToCanvasMat,
823
+ borderRadius: hole.rect_border_radius ?? 0
824
+ });
825
+ }
826
+ if (!isCoveredWithSoldermask) {
827
+ const holeX = hole.x + (hole.hole_offset_x ?? 0);
828
+ const holeY = hole.y + (hole.hole_offset_y ?? 0);
829
+ drawPill({
830
+ ctx,
831
+ center: { x: holeX, y: holeY },
832
+ width: hole.hole_width,
833
+ height: hole.hole_height,
834
+ fill: colorMap.drill,
835
+ realToCanvasMat
836
+ });
837
+ }
690
838
  return;
691
839
  }
692
840
  if (hole.shape === "rotated_pill_hole_with_rect_pad") {
@@ -698,7 +846,7 @@ function drawPcbPlatedHole(params) {
698
846
  height: hole.rect_pad_height + margin * 2,
699
847
  fill: positiveMarginColor,
700
848
  realToCanvasMat,
701
- borderRadius: (hole.rect_border_radius ?? 0) + margin,
849
+ borderRadius: hole.rect_border_radius ?? 0,
702
850
  rotation: hole.rect_ccw_rotation
703
851
  });
704
852
  }
@@ -726,17 +874,31 @@ function drawPcbPlatedHole(params) {
726
874
  copperColor
727
875
  );
728
876
  }
729
- const holeX = hole.x + (hole.hole_offset_x ?? 0);
730
- const holeY = hole.y + (hole.hole_offset_y ?? 0);
731
- drawPill({
732
- ctx,
733
- center: { x: holeX, y: holeY },
734
- width: hole.hole_width,
735
- height: hole.hole_height,
736
- fill: colorMap.drill,
737
- realToCanvasMat,
738
- rotation: hole.hole_ccw_rotation
739
- });
877
+ if (isCoveredWithSoldermask) {
878
+ drawRect({
879
+ ctx,
880
+ center: { x: hole.x, y: hole.y },
881
+ width: hole.rect_pad_width,
882
+ height: hole.rect_pad_height,
883
+ fill: soldermaskRingColor,
884
+ realToCanvasMat,
885
+ borderRadius: hole.rect_border_radius ?? 0,
886
+ rotation: hole.rect_ccw_rotation
887
+ });
888
+ }
889
+ if (!isCoveredWithSoldermask) {
890
+ const holeX = hole.x + (hole.hole_offset_x ?? 0);
891
+ const holeY = hole.y + (hole.hole_offset_y ?? 0);
892
+ drawPill({
893
+ ctx,
894
+ center: { x: holeX, y: holeY },
895
+ width: hole.hole_width,
896
+ height: hole.hole_height,
897
+ fill: colorMap.drill,
898
+ realToCanvasMat,
899
+ rotation: hole.hole_ccw_rotation
900
+ });
901
+ }
740
902
  return;
741
903
  }
742
904
  if (hole.shape === "hole_with_polygon_pad") {
@@ -746,51 +908,80 @@ function drawPcbPlatedHole(params) {
746
908
  x: hole.x + point.x,
747
909
  y: hole.y + point.y
748
910
  }));
911
+ if (hasSoldermask && margin > 0) {
912
+ const expandedPoints = offsetPolygonPoints(padPoints, margin);
913
+ drawPolygon({
914
+ ctx,
915
+ points: expandedPoints,
916
+ fill: positiveMarginColor,
917
+ realToCanvasMat
918
+ });
919
+ }
749
920
  drawPolygon({
750
921
  ctx,
751
922
  points: padPoints,
752
923
  fill: copperColor,
753
924
  realToCanvasMat
754
925
  });
926
+ if (hasSoldermask && margin < 0) {
927
+ drawSoldermaskRingForPolygon(
928
+ ctx,
929
+ padPoints,
930
+ margin,
931
+ realToCanvasMat,
932
+ soldermaskRingColor,
933
+ copperColor
934
+ );
935
+ }
936
+ if (isCoveredWithSoldermask) {
937
+ drawPolygon({
938
+ ctx,
939
+ points: padPoints,
940
+ fill: soldermaskRingColor,
941
+ realToCanvasMat
942
+ });
943
+ }
755
944
  }
756
- const holeX = hole.x + (hole.hole_offset_x ?? 0);
757
- const holeY = hole.y + (hole.hole_offset_y ?? 0);
758
- const holeShape = hole.hole_shape;
759
- if (holeShape === "circle") {
760
- drawCircle({
761
- ctx,
762
- center: { x: holeX, y: holeY },
763
- radius: (hole.hole_diameter ?? 0) / 2,
764
- fill: colorMap.drill,
765
- realToCanvasMat
766
- });
767
- } else if (holeShape === "oval") {
768
- drawOval({
769
- ctx,
770
- center: { x: holeX, y: holeY },
771
- radius_x: (hole.hole_width ?? 0) / 2,
772
- radius_y: (hole.hole_height ?? 0) / 2,
773
- fill: colorMap.drill,
774
- realToCanvasMat
775
- });
776
- } else if (holeShape === "pill") {
777
- drawPill({
778
- ctx,
779
- center: { x: holeX, y: holeY },
780
- width: hole.hole_width ?? 0,
781
- height: hole.hole_height ?? 0,
782
- fill: colorMap.drill,
783
- realToCanvasMat
784
- });
785
- } else if (holeShape === "rotated_pill") {
786
- drawPill({
787
- ctx,
788
- center: { x: holeX, y: holeY },
789
- width: hole.hole_width ?? 0,
790
- height: hole.hole_height ?? 0,
791
- fill: colorMap.drill,
792
- realToCanvasMat
793
- });
945
+ if (!isCoveredWithSoldermask) {
946
+ const holeX = hole.x + (hole.hole_offset_x ?? 0);
947
+ const holeY = hole.y + (hole.hole_offset_y ?? 0);
948
+ const holeShape = hole.hole_shape;
949
+ if (holeShape === "circle") {
950
+ drawCircle({
951
+ ctx,
952
+ center: { x: holeX, y: holeY },
953
+ radius: (hole.hole_diameter ?? 0) / 2,
954
+ fill: colorMap.drill,
955
+ realToCanvasMat
956
+ });
957
+ } else if (holeShape === "oval") {
958
+ drawOval({
959
+ ctx,
960
+ center: { x: holeX, y: holeY },
961
+ radius_x: (hole.hole_width ?? 0) / 2,
962
+ radius_y: (hole.hole_height ?? 0) / 2,
963
+ fill: colorMap.drill,
964
+ realToCanvasMat
965
+ });
966
+ } else if (holeShape === "pill") {
967
+ drawPill({
968
+ ctx,
969
+ center: { x: holeX, y: holeY },
970
+ width: hole.hole_width ?? 0,
971
+ height: hole.hole_height ?? 0,
972
+ fill: colorMap.drill,
973
+ realToCanvasMat
974
+ });
975
+ } else if (holeShape === "rotated_pill") {
976
+ drawPill({
977
+ ctx,
978
+ center: { x: holeX, y: holeY },
979
+ width: hole.hole_width ?? 0,
980
+ height: hole.hole_height ?? 0,
981
+ fill: colorMap.drill,
982
+ realToCanvasMat
983
+ });
984
+ }
794
985
  }
795
986
  return;
796
987
  }
@@ -824,9 +1015,12 @@ function getRotation(hole) {
824
1015
  }
825
1016
  function drawPcbHole(params) {
826
1017
  const { ctx, hole, realToCanvasMat, colorMap } = params;
827
- const hasSoldermask = hole.is_covered_with_solder_mask === true && hole.soldermask_margin !== void 0 && hole.soldermask_margin > 0;
828
- const margin = hasSoldermask ? hole.soldermask_margin : 0;
1018
+ const isCoveredWithSoldermask = hole.is_covered_with_solder_mask === true;
1019
+ const margin = isCoveredWithSoldermask ? 0 : hole.soldermask_margin ?? 0;
1020
+ const hasSoldermask = !isCoveredWithSoldermask && hole.soldermask_margin !== void 0 && hole.soldermask_margin !== 0;
829
1021
  const positiveMarginColor = colorMap.substrate;
1022
+ const soldermaskOverlayColor = colorMap.soldermask.top;
1023
+ const soldermaskRingColor = colorMap.soldermask.top;
830
1024
  if (hole.hole_shape === "circle") {
831
1025
  if (hasSoldermask && margin > 0) {
832
1026
  drawCircle({
@@ -837,13 +1031,35 @@ function drawPcbHole(params) {
837
1031
  realToCanvasMat
838
1032
  });
839
1033
  }
840
- drawCircle({
841
- ctx,
842
- center: { x: hole.x, y: hole.y },
843
- radius: hole.hole_diameter / 2,
844
- fill: colorMap.drill,
845
- realToCanvasMat
846
- });
1034
+ if (!isCoveredWithSoldermask) {
1035
+ drawCircle({
1036
+ ctx,
1037
+ center: { x: hole.x, y: hole.y },
1038
+ radius: hole.hole_diameter / 2,
1039
+ fill: colorMap.drill,
1040
+ realToCanvasMat
1041
+ });
1042
+ if (hasSoldermask && margin < 0) {
1043
+ drawSoldermaskRingForCircle(
1044
+ ctx,
1045
+ { x: hole.x, y: hole.y },
1046
+ hole.hole_diameter / 2,
1047
+ margin,
1048
+ realToCanvasMat,
1049
+ soldermaskRingColor,
1050
+ colorMap.drill
1051
+ );
1052
+ }
1053
+ }
1054
+ if (isCoveredWithSoldermask) {
1055
+ drawCircle({
1056
+ ctx,
1057
+ center: { x: hole.x, y: hole.y },
1058
+ radius: hole.hole_diameter / 2,
1059
+ fill: soldermaskOverlayColor,
1060
+ realToCanvasMat
1061
+ });
1062
+ }
847
1063
  return;
848
1064
  }
849
1065
  if (hole.hole_shape === "square") {
@@ -859,15 +1075,42 @@ function drawPcbHole(params) {
859
1075
  rotation
860
1076
  });
861
1077
  }
862
- drawRect({
863
- ctx,
864
- center: { x: hole.x, y: hole.y },
865
- width: hole.hole_diameter,
866
- height: hole.hole_diameter,
867
- fill: colorMap.drill,
868
- realToCanvasMat,
869
- rotation
870
- });
1078
+ if (!isCoveredWithSoldermask) {
1079
+ drawRect({
1080
+ ctx,
1081
+ center: { x: hole.x, y: hole.y },
1082
+ width: hole.hole_diameter,
1083
+ height: hole.hole_diameter,
1084
+ fill: colorMap.drill,
1085
+ realToCanvasMat,
1086
+ rotation
1087
+ });
1088
+ if (hasSoldermask && margin < 0) {
1089
+ drawSoldermaskRingForRect(
1090
+ ctx,
1091
+ { x: hole.x, y: hole.y },
1092
+ hole.hole_diameter,
1093
+ hole.hole_diameter,
1094
+ margin,
1095
+ 0,
1096
+ rotation,
1097
+ realToCanvasMat,
1098
+ soldermaskRingColor,
1099
+ colorMap.drill
1100
+ );
1101
+ }
1102
+ }
1103
+ if (isCoveredWithSoldermask) {
1104
+ drawRect({
1105
+ ctx,
1106
+ center: { x: hole.x, y: hole.y },
1107
+ width: hole.hole_diameter,
1108
+ height: hole.hole_diameter,
1109
+ fill: soldermaskOverlayColor,
1110
+ realToCanvasMat,
1111
+ rotation
1112
+ });
1113
+ }
871
1114
  return;
872
1115
  }
873
1116
  if (hole.hole_shape === "oval") {
@@ -883,15 +1126,41 @@ function drawPcbHole(params) {
883
1126
  rotation
884
1127
  });
885
1128
  }
886
- drawOval({
887
- ctx,
888
- center: { x: hole.x, y: hole.y },
889
- radius_x: hole.hole_width / 2,
890
- radius_y: hole.hole_height / 2,
891
- fill: colorMap.drill,
892
- realToCanvasMat,
893
- rotation
894
- });
1129
+ if (!isCoveredWithSoldermask) {
1130
+ drawOval({
1131
+ ctx,
1132
+ center: { x: hole.x, y: hole.y },
1133
+ radius_x: hole.hole_width / 2,
1134
+ radius_y: hole.hole_height / 2,
1135
+ fill: colorMap.drill,
1136
+ realToCanvasMat,
1137
+ rotation
1138
+ });
1139
+ if (hasSoldermask && margin < 0) {
1140
+ drawSoldermaskRingForOval(
1141
+ ctx,
1142
+ { x: hole.x, y: hole.y },
1143
+ hole.hole_width / 2,
1144
+ hole.hole_height / 2,
1145
+ margin,
1146
+ rotation,
1147
+ realToCanvasMat,
1148
+ soldermaskRingColor,
1149
+ colorMap.drill
1150
+ );
1151
+ }
1152
+ }
1153
+ if (isCoveredWithSoldermask) {
1154
+ drawOval({
1155
+ ctx,
1156
+ center: { x: hole.x, y: hole.y },
1157
+ radius_x: hole.hole_width / 2,
1158
+ radius_y: hole.hole_height / 2,
1159
+ fill: soldermaskOverlayColor,
1160
+ realToCanvasMat,
1161
+ rotation
1162
+ });
1163
+ }
895
1164
  return;
896
1165
  }
897
1166
  if (hole.hole_shape === "rect") {
@@ -907,15 +1176,42 @@ function drawPcbHole(params) {
907
1176
  rotation
908
1177
  });
909
1178
  }
910
- drawRect({
911
- ctx,
912
- center: { x: hole.x, y: hole.y },
913
- width: hole.hole_width,
914
- height: hole.hole_height,
915
- fill: colorMap.drill,
916
- realToCanvasMat,
917
- rotation
918
- });
1179
+ if (!isCoveredWithSoldermask) {
1180
+ drawRect({
1181
+ ctx,
1182
+ center: { x: hole.x, y: hole.y },
1183
+ width: hole.hole_width,
1184
+ height: hole.hole_height,
1185
+ fill: colorMap.drill,
1186
+ realToCanvasMat,
1187
+ rotation
1188
+ });
1189
+ if (hasSoldermask && margin < 0) {
1190
+ drawSoldermaskRingForRect(
1191
+ ctx,
1192
+ { x: hole.x, y: hole.y },
1193
+ hole.hole_width,
1194
+ hole.hole_height,
1195
+ margin,
1196
+ 0,
1197
+ rotation,
1198
+ realToCanvasMat,
1199
+ soldermaskRingColor,
1200
+ colorMap.drill
1201
+ );
1202
+ }
1203
+ }
1204
+ if (isCoveredWithSoldermask) {
1205
+ drawRect({
1206
+ ctx,
1207
+ center: { x: hole.x, y: hole.y },
1208
+ width: hole.hole_width,
1209
+ height: hole.hole_height,
1210
+ fill: soldermaskOverlayColor,
1211
+ realToCanvasMat,
1212
+ rotation
1213
+ });
1214
+ }
919
1215
  return;
920
1216
  }
921
1217
  if (hole.hole_shape === "pill") {
@@ -931,15 +1227,41 @@ function drawPcbHole(params) {
931
1227
  rotation
932
1228
  });
933
1229
  }
934
- drawPill({
935
- ctx,
936
- center: { x: hole.x, y: hole.y },
937
- width: hole.hole_width,
938
- height: hole.hole_height,
939
- fill: colorMap.drill,
940
- realToCanvasMat,
941
- rotation
942
- });
1230
+ if (!isCoveredWithSoldermask) {
1231
+ drawPill({
1232
+ ctx,
1233
+ center: { x: hole.x, y: hole.y },
1234
+ width: hole.hole_width,
1235
+ height: hole.hole_height,
1236
+ fill: colorMap.drill,
1237
+ realToCanvasMat,
1238
+ rotation
1239
+ });
1240
+ if (hasSoldermask && margin < 0) {
1241
+ drawSoldermaskRingForPill(
1242
+ ctx,
1243
+ { x: hole.x, y: hole.y },
1244
+ hole.hole_width,
1245
+ hole.hole_height,
1246
+ margin,
1247
+ rotation,
1248
+ realToCanvasMat,
1249
+ soldermaskRingColor,
1250
+ colorMap.drill
1251
+ );
1252
+ }
1253
+ }
1254
+ if (isCoveredWithSoldermask) {
1255
+ drawPill({
1256
+ ctx,
1257
+ center: { x: hole.x, y: hole.y },
1258
+ width: hole.hole_width,
1259
+ height: hole.hole_height,
1260
+ fill: soldermaskOverlayColor,
1261
+ realToCanvasMat,
1262
+ rotation
1263
+ });
1264
+ }
943
1265
  return;
944
1266
  }
945
1267
  if (hole.hole_shape === "rotated_pill") {
@@ -955,15 +1277,41 @@ function drawPcbHole(params) {
955
1277
  rotation
956
1278
  });
957
1279
  }
958
- drawPill({
959
- ctx,
960
- center: { x: hole.x, y: hole.y },
961
- width: hole.hole_width,
962
- height: hole.hole_height,
963
- fill: colorMap.drill,
964
- realToCanvasMat,
965
- rotation
966
- });
1280
+ if (!isCoveredWithSoldermask) {
1281
+ drawPill({
1282
+ ctx,
1283
+ center: { x: hole.x, y: hole.y },
1284
+ width: hole.hole_width,
1285
+ height: hole.hole_height,
1286
+ fill: colorMap.drill,
1287
+ realToCanvasMat,
1288
+ rotation
1289
+ });
1290
+ if (hasSoldermask && margin < 0) {
1291
+ drawSoldermaskRingForPill(
1292
+ ctx,
1293
+ { x: hole.x, y: hole.y },
1294
+ hole.hole_width,
1295
+ hole.hole_height,
1296
+ margin,
1297
+ rotation,
1298
+ realToCanvasMat,
1299
+ soldermaskRingColor,
1300
+ colorMap.drill
1301
+ );
1302
+ }
1303
+ }
1304
+ if (isCoveredWithSoldermask) {
1305
+ drawPill({
1306
+ ctx,
1307
+ center: { x: hole.x, y: hole.y },
1308
+ width: hole.hole_width,
1309
+ height: hole.hole_height,
1310
+ fill: soldermaskOverlayColor,
1311
+ realToCanvasMat,
1312
+ rotation
1313
+ });
1314
+ }
967
1315
  return;
968
1316
  }
969
1317
  }
@@ -982,13 +1330,13 @@ function drawPcbSmtPad(params) {
982
1330
  const { ctx, pad, realToCanvasMat, colorMap } = params;
983
1331
  const color = layerToColor(pad.layer, colorMap);
984
1332
  const isCoveredWithSoldermask = pad.is_covered_with_solder_mask === true;
985
- const margin = isCoveredWithSoldermask && pad.soldermask_margin !== void 0 ? pad.soldermask_margin : 0;
986
- const hasSoldermask = isCoveredWithSoldermask && pad.soldermask_margin !== void 0 && pad.soldermask_margin !== 0;
1333
+ const margin = isCoveredWithSoldermask ? 0 : pad.soldermask_margin ?? 0;
1334
+ const hasSoldermask = !isCoveredWithSoldermask && pad.soldermask_margin !== void 0 && pad.soldermask_margin !== 0;
987
1335
  const soldermaskRingColor = getSoldermaskColor2(pad.layer, colorMap);
988
1336
  const positiveMarginColor = colorMap.substrate;
989
1337
  const soldermaskOverlayColor = getSoldermaskColor2(pad.layer, colorMap);
990
1338
  if (pad.shape === "rect") {
991
- if (isCoveredWithSoldermask && margin >= 0) {
1339
+ if (hasSoldermask && margin > 0) {
992
1340
  drawRect({
993
1341
  ctx,
994
1342
  center: { x: pad.x, y: pad.y },
@@ -996,7 +1344,7 @@ function drawPcbSmtPad(params) {
996
1344
  height: pad.height + margin * 2,
997
1345
  fill: positiveMarginColor,
998
1346
  realToCanvasMat,
999
- borderRadius: getBorderRadius(pad, margin)
1347
+ borderRadius: getBorderRadius(pad)
1000
1348
  });
1001
1349
  }
1002
1350
  drawRect({
@@ -1036,7 +1384,7 @@ function drawPcbSmtPad(params) {
1036
1384
  return;
1037
1385
  }
1038
1386
  if (pad.shape === "rotated_rect") {
1039
- if (isCoveredWithSoldermask && margin >= 0) {
1387
+ if (hasSoldermask && margin > 0) {
1040
1388
  drawRect({
1041
1389
  ctx,
1042
1390
  center: { x: pad.x, y: pad.y },
@@ -1044,7 +1392,7 @@ function drawPcbSmtPad(params) {
1044
1392
  height: pad.height + margin * 2,
1045
1393
  fill: positiveMarginColor,
1046
1394
  realToCanvasMat,
1047
- borderRadius: getBorderRadius(pad, margin),
1395
+ borderRadius: getBorderRadius(pad),
1048
1396
  rotation: pad.ccw_rotation ?? 0
1049
1397
  });
1050
1398
  }
@@ -1087,7 +1435,7 @@ function drawPcbSmtPad(params) {
1087
1435
  return;
1088
1436
  }
1089
1437
  if (pad.shape === "circle") {
1090
- if (isCoveredWithSoldermask && margin >= 0) {
1438
+ if (hasSoldermask && margin > 0) {
1091
1439
  drawCircle({
1092
1440
  ctx,
1093
1441
  center: { x: pad.x, y: pad.y },
@@ -1126,7 +1474,7 @@ function drawPcbSmtPad(params) {
1126
1474
  return;
1127
1475
  }
1128
1476
  if (pad.shape === "pill") {
1129
- if (isCoveredWithSoldermask && margin >= 0) {
1477
+ if (hasSoldermask && margin > 0) {
1130
1478
  drawPill({
1131
1479
  ctx,
1132
1480
  center: { x: pad.x, y: pad.y },
@@ -1170,7 +1518,7 @@ function drawPcbSmtPad(params) {
1170
1518
  return;
1171
1519
  }
1172
1520
  if (pad.shape === "rotated_pill") {
1173
- if (isCoveredWithSoldermask && margin >= 0) {
1521
+ if (hasSoldermask && margin > 0) {
1174
1522
  drawPill({
1175
1523
  ctx,
1176
1524
  center: { x: pad.x, y: pad.y },
@@ -1218,13 +1566,32 @@ function drawPcbSmtPad(params) {
1218
1566
  }
1219
1567
  if (pad.shape === "polygon") {
1220
1568
  if (pad.points && pad.points.length >= 3) {
1569
+ if (hasSoldermask && margin > 0) {
1570
+ const expandedPoints = offsetPolygonPoints(pad.points, margin);
1571
+ drawPolygon({
1572
+ ctx,
1573
+ points: expandedPoints,
1574
+ fill: positiveMarginColor,
1575
+ realToCanvasMat
1576
+ });
1577
+ }
1221
1578
  drawPolygon({
1222
1579
  ctx,
1223
1580
  points: pad.points,
1224
1581
  fill: color,
1225
1582
  realToCanvasMat
1226
1583
  });
1227
- if (isCoveredWithSoldermask && margin >= 0) {
1584
+ if (hasSoldermask && margin < 0) {
1585
+ drawSoldermaskRingForPolygon(
1586
+ ctx,
1587
+ pad.points,
1588
+ margin,
1589
+ realToCanvasMat,
1590
+ soldermaskRingColor,
1591
+ color
1592
+ );
1593
+ }
1594
+ if (isCoveredWithSoldermask && margin === 0) {
1228
1595
  drawPolygon({
1229
1596
  ctx,
1230
1597
  points: pad.points,
@@ -2355,7 +2722,7 @@ var CircuitToCanvasDrawer = class {
2355
2722
  (el) => el.type === "pcb_plated_hole" && el.is_covered_with_solder_mask === true
2356
2723
  );
2357
2724
  for (const element of elements) {
2358
- if (element.type === "pcb_board" && (hasSoldermaskPads || hasSoldermaskHoles || hasSoldermaskPlatedHoles)) {
2725
+ if (element.type === "pcb_board") {
2359
2726
  this.drawBoardWithSoldermask(element);
2360
2727
  } else {
2361
2728
  this.drawElement(element, options);