circuit-to-canvas 0.0.30 → 0.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +41 -5
- package/dist/index.js +456 -41
- package/lib/drawer/CircuitToCanvasDrawer.ts +116 -2
- package/lib/drawer/elements/index.ts +10 -0
- package/lib/drawer/elements/pcb-hole.ts +2 -2
- package/lib/drawer/elements/pcb-plated-hole.ts +6 -6
- package/lib/drawer/elements/pcb-silkscreen-oval.ts +35 -0
- package/lib/drawer/elements/pcb-smtpad.ts +174 -0
- package/lib/drawer/elements/soldermask-margin.ts +266 -0
- package/lib/drawer/shapes/oval.ts +25 -10
- package/package.json +3 -3
- package/tests/elements/__snapshots__/pcb-silkscreen-oval.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-smtpad-soldermask-margin.snap.png +0 -0
- package/tests/elements/pcb-silkscreen-oval.test.ts +37 -0
- package/tests/elements/pcb-smtpad-soldermask-margin.test.ts +163 -0
- package/tests/shapes/__snapshots__/oval.snap.png +0 -0
- package/tests/shapes/oval.test.ts +3 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
// lib/drawer/CircuitToCanvasDrawer.ts
|
|
2
|
+
import {
|
|
3
|
+
identity,
|
|
4
|
+
compose,
|
|
5
|
+
translate,
|
|
6
|
+
scale,
|
|
7
|
+
applyToPoint as applyToPoint15
|
|
8
|
+
} from "transformation-matrix";
|
|
9
|
+
|
|
1
10
|
// lib/drawer/pcb-render-layer-filter.ts
|
|
2
11
|
import { getElementRenderLayers } from "@tscircuit/circuit-json-util";
|
|
3
12
|
function shouldDrawElement(element, options) {
|
|
@@ -11,9 +20,6 @@ function shouldDrawElement(element, options) {
|
|
|
11
20
|
return elementLayers.some((layer) => options.layers.includes(layer));
|
|
12
21
|
}
|
|
13
22
|
|
|
14
|
-
// lib/drawer/CircuitToCanvasDrawer.ts
|
|
15
|
-
import { identity, compose, translate, scale } from "transformation-matrix";
|
|
16
|
-
|
|
17
23
|
// lib/drawer/types.ts
|
|
18
24
|
var DEFAULT_PCB_COLOR_MAP = {
|
|
19
25
|
copper: {
|
|
@@ -132,24 +138,34 @@ function drawOval(params) {
|
|
|
132
138
|
const {
|
|
133
139
|
ctx,
|
|
134
140
|
center,
|
|
135
|
-
|
|
136
|
-
|
|
141
|
+
radius_x,
|
|
142
|
+
radius_y,
|
|
137
143
|
fill,
|
|
144
|
+
stroke,
|
|
145
|
+
strokeWidth = 0.1,
|
|
138
146
|
realToCanvasMat,
|
|
139
147
|
rotation = 0
|
|
140
148
|
} = params;
|
|
141
149
|
const [cx, cy] = applyToPoint3(realToCanvasMat, [center.x, center.y]);
|
|
142
|
-
const
|
|
143
|
-
const
|
|
150
|
+
const scaledRadiusX = radius_x * Math.abs(realToCanvasMat.a);
|
|
151
|
+
const scaledRadiusY = radius_y * Math.abs(realToCanvasMat.a);
|
|
152
|
+
const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
|
|
144
153
|
ctx.save();
|
|
145
154
|
ctx.translate(cx, cy);
|
|
146
155
|
if (rotation !== 0) {
|
|
147
156
|
ctx.rotate(-rotation * (Math.PI / 180));
|
|
148
157
|
}
|
|
149
158
|
ctx.beginPath();
|
|
150
|
-
ctx.ellipse(0, 0,
|
|
151
|
-
|
|
152
|
-
|
|
159
|
+
ctx.ellipse(0, 0, scaledRadiusX, scaledRadiusY, 0, 0, Math.PI * 2);
|
|
160
|
+
if (fill) {
|
|
161
|
+
ctx.fillStyle = fill;
|
|
162
|
+
ctx.fill();
|
|
163
|
+
}
|
|
164
|
+
if (stroke) {
|
|
165
|
+
ctx.strokeStyle = stroke;
|
|
166
|
+
ctx.lineWidth = scaledStrokeWidth;
|
|
167
|
+
ctx.stroke();
|
|
168
|
+
}
|
|
153
169
|
ctx.restore();
|
|
154
170
|
}
|
|
155
171
|
|
|
@@ -257,8 +273,8 @@ function drawPcbPlatedHole(params) {
|
|
|
257
273
|
drawOval({
|
|
258
274
|
ctx,
|
|
259
275
|
center: { x: hole.x, y: hole.y },
|
|
260
|
-
|
|
261
|
-
|
|
276
|
+
radius_x: hole.outer_width / 2,
|
|
277
|
+
radius_y: hole.outer_height / 2,
|
|
262
278
|
fill: colorMap.copper.top,
|
|
263
279
|
realToCanvasMat,
|
|
264
280
|
rotation: hole.ccw_rotation
|
|
@@ -266,8 +282,8 @@ function drawPcbPlatedHole(params) {
|
|
|
266
282
|
drawOval({
|
|
267
283
|
ctx,
|
|
268
284
|
center: { x: hole.x, y: hole.y },
|
|
269
|
-
|
|
270
|
-
|
|
285
|
+
radius_x: hole.hole_width / 2,
|
|
286
|
+
radius_y: hole.hole_height / 2,
|
|
271
287
|
fill: colorMap.drill,
|
|
272
288
|
realToCanvasMat,
|
|
273
289
|
rotation: hole.ccw_rotation
|
|
@@ -391,8 +407,8 @@ function drawPcbPlatedHole(params) {
|
|
|
391
407
|
drawOval({
|
|
392
408
|
ctx,
|
|
393
409
|
center: { x: holeX, y: holeY },
|
|
394
|
-
|
|
395
|
-
|
|
410
|
+
radius_x: (hole.hole_width ?? 0) / 2,
|
|
411
|
+
radius_y: (hole.hole_height ?? 0) / 2,
|
|
396
412
|
fill: colorMap.drill,
|
|
397
413
|
realToCanvasMat
|
|
398
414
|
});
|
|
@@ -466,8 +482,8 @@ function drawPcbHole(params) {
|
|
|
466
482
|
drawOval({
|
|
467
483
|
ctx,
|
|
468
484
|
center: { x: hole.x, y: hole.y },
|
|
469
|
-
|
|
470
|
-
|
|
485
|
+
radius_x: hole.hole_width / 2,
|
|
486
|
+
radius_y: hole.hole_height / 2,
|
|
471
487
|
fill: colorMap.drill,
|
|
472
488
|
realToCanvasMat
|
|
473
489
|
});
|
|
@@ -509,14 +525,207 @@ function drawPcbHole(params) {
|
|
|
509
525
|
}
|
|
510
526
|
}
|
|
511
527
|
|
|
528
|
+
// lib/drawer/elements/soldermask-margin.ts
|
|
529
|
+
import { applyToPoint as applyToPoint6 } from "transformation-matrix";
|
|
530
|
+
function drawSoldermaskRingForRect(ctx, center, width, height, margin, borderRadius, rotation, realToCanvasMat, soldermaskColor, padColor) {
|
|
531
|
+
const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
|
|
532
|
+
const scaledWidth = width * Math.abs(realToCanvasMat.a);
|
|
533
|
+
const scaledHeight = height * Math.abs(realToCanvasMat.a);
|
|
534
|
+
const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
|
|
535
|
+
const scaledRadius = borderRadius * Math.abs(realToCanvasMat.a);
|
|
536
|
+
ctx.save();
|
|
537
|
+
ctx.translate(cx, cy);
|
|
538
|
+
if (rotation !== 0) {
|
|
539
|
+
ctx.rotate(-rotation * (Math.PI / 180));
|
|
540
|
+
}
|
|
541
|
+
const prevCompositeOp = ctx.globalCompositeOperation;
|
|
542
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
543
|
+
ctx.globalCompositeOperation = "source-atop";
|
|
544
|
+
}
|
|
545
|
+
const outerWidth = scaledWidth;
|
|
546
|
+
const outerHeight = scaledHeight;
|
|
547
|
+
const outerRadius = scaledRadius;
|
|
548
|
+
ctx.beginPath();
|
|
549
|
+
if (outerRadius > 0) {
|
|
550
|
+
const x = -outerWidth / 2;
|
|
551
|
+
const y = -outerHeight / 2;
|
|
552
|
+
const r = Math.min(outerRadius, outerWidth / 2, outerHeight / 2);
|
|
553
|
+
ctx.moveTo(x + r, y);
|
|
554
|
+
ctx.lineTo(x + outerWidth - r, y);
|
|
555
|
+
ctx.arcTo(x + outerWidth, y, x + outerWidth, y + r, r);
|
|
556
|
+
ctx.lineTo(x + outerWidth, y + outerHeight - r);
|
|
557
|
+
ctx.arcTo(
|
|
558
|
+
x + outerWidth,
|
|
559
|
+
y + outerHeight,
|
|
560
|
+
x + outerWidth - r,
|
|
561
|
+
y + outerHeight,
|
|
562
|
+
r
|
|
563
|
+
);
|
|
564
|
+
ctx.lineTo(x + r, y + outerHeight);
|
|
565
|
+
ctx.arcTo(x, y + outerHeight, x, y + outerHeight - r, r);
|
|
566
|
+
ctx.lineTo(x, y + r);
|
|
567
|
+
ctx.arcTo(x, y, x + r, y, r);
|
|
568
|
+
} else {
|
|
569
|
+
ctx.rect(-outerWidth / 2, -outerHeight / 2, outerWidth, outerHeight);
|
|
570
|
+
}
|
|
571
|
+
ctx.fillStyle = soldermaskColor;
|
|
572
|
+
ctx.fill();
|
|
573
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
574
|
+
ctx.globalCompositeOperation = prevCompositeOp || "source-over";
|
|
575
|
+
}
|
|
576
|
+
const innerWidth = scaledWidth - scaledMargin * 2;
|
|
577
|
+
const innerHeight = scaledHeight - scaledMargin * 2;
|
|
578
|
+
const innerRadius = Math.max(0, scaledRadius - scaledMargin);
|
|
579
|
+
if (innerWidth > 0 && innerHeight > 0) {
|
|
580
|
+
ctx.beginPath();
|
|
581
|
+
if (innerRadius > 0) {
|
|
582
|
+
const x = -innerWidth / 2;
|
|
583
|
+
const y = -innerHeight / 2;
|
|
584
|
+
const r = Math.min(innerRadius, innerWidth / 2, innerHeight / 2);
|
|
585
|
+
ctx.moveTo(x + r, y);
|
|
586
|
+
ctx.lineTo(x + innerWidth - r, y);
|
|
587
|
+
ctx.arcTo(x + innerWidth, y, x + innerWidth, y + r, r);
|
|
588
|
+
ctx.lineTo(x + innerWidth, y + innerHeight - r);
|
|
589
|
+
ctx.arcTo(
|
|
590
|
+
x + innerWidth,
|
|
591
|
+
y + innerHeight,
|
|
592
|
+
x + innerWidth - r,
|
|
593
|
+
y + innerHeight,
|
|
594
|
+
r
|
|
595
|
+
);
|
|
596
|
+
ctx.lineTo(x + r, y + innerHeight);
|
|
597
|
+
ctx.arcTo(x, y + innerHeight, x, y + innerHeight - r, r);
|
|
598
|
+
ctx.lineTo(x, y + r);
|
|
599
|
+
ctx.arcTo(x, y, x + r, y, r);
|
|
600
|
+
} else {
|
|
601
|
+
ctx.rect(-innerWidth / 2, -innerHeight / 2, innerWidth, innerHeight);
|
|
602
|
+
}
|
|
603
|
+
ctx.fillStyle = padColor;
|
|
604
|
+
ctx.fill();
|
|
605
|
+
}
|
|
606
|
+
ctx.restore();
|
|
607
|
+
}
|
|
608
|
+
function drawSoldermaskRingForCircle(ctx, center, radius, margin, realToCanvasMat, soldermaskColor, padColor) {
|
|
609
|
+
const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
|
|
610
|
+
const scaledRadius = radius * Math.abs(realToCanvasMat.a);
|
|
611
|
+
const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
|
|
612
|
+
ctx.save();
|
|
613
|
+
const prevCompositeOp = ctx.globalCompositeOperation;
|
|
614
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
615
|
+
ctx.globalCompositeOperation = "source-atop";
|
|
616
|
+
}
|
|
617
|
+
ctx.beginPath();
|
|
618
|
+
ctx.arc(cx, cy, scaledRadius, 0, Math.PI * 2);
|
|
619
|
+
ctx.fillStyle = soldermaskColor;
|
|
620
|
+
ctx.fill();
|
|
621
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
622
|
+
ctx.globalCompositeOperation = prevCompositeOp || "source-over";
|
|
623
|
+
}
|
|
624
|
+
const innerRadius = Math.max(0, scaledRadius - scaledMargin);
|
|
625
|
+
if (innerRadius > 0) {
|
|
626
|
+
ctx.beginPath();
|
|
627
|
+
ctx.arc(cx, cy, innerRadius, 0, Math.PI * 2);
|
|
628
|
+
ctx.fillStyle = padColor;
|
|
629
|
+
ctx.fill();
|
|
630
|
+
}
|
|
631
|
+
ctx.restore();
|
|
632
|
+
}
|
|
633
|
+
function drawSoldermaskRingForPill(ctx, center, width, height, margin, rotation, realToCanvasMat, soldermaskColor, padColor) {
|
|
634
|
+
const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
|
|
635
|
+
const scaledWidth = width * Math.abs(realToCanvasMat.a);
|
|
636
|
+
const scaledHeight = height * Math.abs(realToCanvasMat.a);
|
|
637
|
+
const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
|
|
638
|
+
ctx.save();
|
|
639
|
+
ctx.translate(cx, cy);
|
|
640
|
+
if (rotation !== 0) {
|
|
641
|
+
ctx.rotate(-rotation * (Math.PI / 180));
|
|
642
|
+
}
|
|
643
|
+
const prevCompositeOp = ctx.globalCompositeOperation;
|
|
644
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
645
|
+
ctx.globalCompositeOperation = "source-atop";
|
|
646
|
+
}
|
|
647
|
+
const outerWidth = scaledWidth;
|
|
648
|
+
const outerHeight = scaledHeight;
|
|
649
|
+
ctx.beginPath();
|
|
650
|
+
if (outerWidth > outerHeight) {
|
|
651
|
+
const radius = outerHeight / 2;
|
|
652
|
+
const straightLength = outerWidth - outerHeight;
|
|
653
|
+
ctx.moveTo(-straightLength / 2, -radius);
|
|
654
|
+
ctx.lineTo(straightLength / 2, -radius);
|
|
655
|
+
ctx.arc(straightLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2);
|
|
656
|
+
ctx.lineTo(-straightLength / 2, radius);
|
|
657
|
+
ctx.arc(-straightLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2);
|
|
658
|
+
} else if (outerHeight > outerWidth) {
|
|
659
|
+
const radius = outerWidth / 2;
|
|
660
|
+
const straightLength = outerHeight - outerWidth;
|
|
661
|
+
ctx.moveTo(radius, -straightLength / 2);
|
|
662
|
+
ctx.lineTo(radius, straightLength / 2);
|
|
663
|
+
ctx.arc(0, straightLength / 2, radius, 0, Math.PI);
|
|
664
|
+
ctx.lineTo(-radius, -straightLength / 2);
|
|
665
|
+
ctx.arc(0, -straightLength / 2, radius, Math.PI, 0);
|
|
666
|
+
} else {
|
|
667
|
+
ctx.arc(0, 0, outerWidth / 2, 0, Math.PI * 2);
|
|
668
|
+
}
|
|
669
|
+
ctx.fillStyle = soldermaskColor;
|
|
670
|
+
ctx.fill();
|
|
671
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
672
|
+
ctx.globalCompositeOperation = prevCompositeOp || "source-over";
|
|
673
|
+
}
|
|
674
|
+
const innerWidth = scaledWidth - scaledMargin * 2;
|
|
675
|
+
const innerHeight = scaledHeight - scaledMargin * 2;
|
|
676
|
+
if (innerWidth > 0 && innerHeight > 0) {
|
|
677
|
+
ctx.beginPath();
|
|
678
|
+
if (innerWidth > innerHeight) {
|
|
679
|
+
const radius = innerHeight / 2;
|
|
680
|
+
const straightLength = innerWidth - innerHeight;
|
|
681
|
+
ctx.moveTo(-straightLength / 2, -radius);
|
|
682
|
+
ctx.lineTo(straightLength / 2, -radius);
|
|
683
|
+
ctx.arc(straightLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2);
|
|
684
|
+
ctx.lineTo(-straightLength / 2, radius);
|
|
685
|
+
ctx.arc(-straightLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2);
|
|
686
|
+
} else if (innerHeight > innerWidth) {
|
|
687
|
+
const radius = innerWidth / 2;
|
|
688
|
+
const straightLength = innerHeight - innerWidth;
|
|
689
|
+
ctx.moveTo(radius, -straightLength / 2);
|
|
690
|
+
ctx.lineTo(radius, straightLength / 2);
|
|
691
|
+
ctx.arc(0, straightLength / 2, radius, 0, Math.PI);
|
|
692
|
+
ctx.lineTo(-radius, -straightLength / 2);
|
|
693
|
+
ctx.arc(0, -straightLength / 2, radius, Math.PI, 0);
|
|
694
|
+
} else {
|
|
695
|
+
ctx.arc(0, 0, innerWidth / 2, 0, Math.PI * 2);
|
|
696
|
+
}
|
|
697
|
+
ctx.fillStyle = padColor;
|
|
698
|
+
ctx.fill();
|
|
699
|
+
}
|
|
700
|
+
ctx.restore();
|
|
701
|
+
}
|
|
702
|
+
|
|
512
703
|
// lib/drawer/elements/pcb-smtpad.ts
|
|
513
704
|
function layerToColor(layer, colorMap) {
|
|
514
705
|
return colorMap.copper[layer] ?? colorMap.copper.top;
|
|
515
706
|
}
|
|
707
|
+
function getSoldermaskColor(layer, colorMap) {
|
|
708
|
+
return colorMap.soldermaskOverCopper[layer] ?? colorMap.soldermaskOverCopper.top;
|
|
709
|
+
}
|
|
516
710
|
function drawPcbSmtPad(params) {
|
|
517
711
|
const { ctx, pad, realToCanvasMat, colorMap } = params;
|
|
518
712
|
const color = layerToColor(pad.layer, colorMap);
|
|
713
|
+
const hasSoldermask = pad.is_covered_with_solder_mask === true && pad.soldermask_margin !== void 0 && pad.soldermask_margin !== 0;
|
|
714
|
+
const margin = hasSoldermask ? pad.soldermask_margin : 0;
|
|
715
|
+
const soldermaskRingColor = getSoldermaskColor(pad.layer, colorMap);
|
|
716
|
+
const positiveMarginColor = colorMap.substrate;
|
|
519
717
|
if (pad.shape === "rect") {
|
|
718
|
+
if (hasSoldermask && margin > 0) {
|
|
719
|
+
drawRect({
|
|
720
|
+
ctx,
|
|
721
|
+
center: { x: pad.x, y: pad.y },
|
|
722
|
+
width: pad.width + margin * 2,
|
|
723
|
+
height: pad.height + margin * 2,
|
|
724
|
+
fill: positiveMarginColor,
|
|
725
|
+
realToCanvasMat,
|
|
726
|
+
borderRadius: (pad.corner_radius ?? pad.rect_border_radius ?? 0) + margin
|
|
727
|
+
});
|
|
728
|
+
}
|
|
520
729
|
drawRect({
|
|
521
730
|
ctx,
|
|
522
731
|
center: { x: pad.x, y: pad.y },
|
|
@@ -526,9 +735,35 @@ function drawPcbSmtPad(params) {
|
|
|
526
735
|
realToCanvasMat,
|
|
527
736
|
borderRadius: pad.corner_radius ?? pad.rect_border_radius ?? 0
|
|
528
737
|
});
|
|
738
|
+
if (hasSoldermask && margin < 0) {
|
|
739
|
+
drawSoldermaskRingForRect(
|
|
740
|
+
ctx,
|
|
741
|
+
{ x: pad.x, y: pad.y },
|
|
742
|
+
pad.width,
|
|
743
|
+
pad.height,
|
|
744
|
+
margin,
|
|
745
|
+
pad.corner_radius ?? pad.rect_border_radius ?? 0,
|
|
746
|
+
0,
|
|
747
|
+
realToCanvasMat,
|
|
748
|
+
soldermaskRingColor,
|
|
749
|
+
color
|
|
750
|
+
);
|
|
751
|
+
}
|
|
529
752
|
return;
|
|
530
753
|
}
|
|
531
754
|
if (pad.shape === "rotated_rect") {
|
|
755
|
+
if (hasSoldermask && margin > 0) {
|
|
756
|
+
drawRect({
|
|
757
|
+
ctx,
|
|
758
|
+
center: { x: pad.x, y: pad.y },
|
|
759
|
+
width: pad.width + margin * 2,
|
|
760
|
+
height: pad.height + margin * 2,
|
|
761
|
+
fill: positiveMarginColor,
|
|
762
|
+
realToCanvasMat,
|
|
763
|
+
borderRadius: (pad.corner_radius ?? pad.rect_border_radius ?? 0) + margin,
|
|
764
|
+
rotation: pad.ccw_rotation ?? 0
|
|
765
|
+
});
|
|
766
|
+
}
|
|
532
767
|
drawRect({
|
|
533
768
|
ctx,
|
|
534
769
|
center: { x: pad.x, y: pad.y },
|
|
@@ -539,9 +774,32 @@ function drawPcbSmtPad(params) {
|
|
|
539
774
|
borderRadius: pad.corner_radius ?? pad.rect_border_radius ?? 0,
|
|
540
775
|
rotation: pad.ccw_rotation ?? 0
|
|
541
776
|
});
|
|
777
|
+
if (hasSoldermask && margin < 0) {
|
|
778
|
+
drawSoldermaskRingForRect(
|
|
779
|
+
ctx,
|
|
780
|
+
{ x: pad.x, y: pad.y },
|
|
781
|
+
pad.width,
|
|
782
|
+
pad.height,
|
|
783
|
+
margin,
|
|
784
|
+
pad.corner_radius ?? pad.rect_border_radius ?? 0,
|
|
785
|
+
pad.ccw_rotation ?? 0,
|
|
786
|
+
realToCanvasMat,
|
|
787
|
+
soldermaskRingColor,
|
|
788
|
+
color
|
|
789
|
+
);
|
|
790
|
+
}
|
|
542
791
|
return;
|
|
543
792
|
}
|
|
544
793
|
if (pad.shape === "circle") {
|
|
794
|
+
if (hasSoldermask && margin > 0) {
|
|
795
|
+
drawCircle({
|
|
796
|
+
ctx,
|
|
797
|
+
center: { x: pad.x, y: pad.y },
|
|
798
|
+
radius: pad.radius + margin,
|
|
799
|
+
fill: positiveMarginColor,
|
|
800
|
+
realToCanvasMat
|
|
801
|
+
});
|
|
802
|
+
}
|
|
545
803
|
drawCircle({
|
|
546
804
|
ctx,
|
|
547
805
|
center: { x: pad.x, y: pad.y },
|
|
@@ -549,9 +807,30 @@ function drawPcbSmtPad(params) {
|
|
|
549
807
|
fill: color,
|
|
550
808
|
realToCanvasMat
|
|
551
809
|
});
|
|
810
|
+
if (hasSoldermask && margin < 0) {
|
|
811
|
+
drawSoldermaskRingForCircle(
|
|
812
|
+
ctx,
|
|
813
|
+
{ x: pad.x, y: pad.y },
|
|
814
|
+
pad.radius,
|
|
815
|
+
margin,
|
|
816
|
+
realToCanvasMat,
|
|
817
|
+
soldermaskRingColor,
|
|
818
|
+
color
|
|
819
|
+
);
|
|
820
|
+
}
|
|
552
821
|
return;
|
|
553
822
|
}
|
|
554
823
|
if (pad.shape === "pill") {
|
|
824
|
+
if (hasSoldermask && margin > 0) {
|
|
825
|
+
drawPill({
|
|
826
|
+
ctx,
|
|
827
|
+
center: { x: pad.x, y: pad.y },
|
|
828
|
+
width: pad.width + margin * 2,
|
|
829
|
+
height: pad.height + margin * 2,
|
|
830
|
+
fill: positiveMarginColor,
|
|
831
|
+
realToCanvasMat
|
|
832
|
+
});
|
|
833
|
+
}
|
|
555
834
|
drawPill({
|
|
556
835
|
ctx,
|
|
557
836
|
center: { x: pad.x, y: pad.y },
|
|
@@ -560,9 +839,33 @@ function drawPcbSmtPad(params) {
|
|
|
560
839
|
fill: color,
|
|
561
840
|
realToCanvasMat
|
|
562
841
|
});
|
|
842
|
+
if (hasSoldermask && margin < 0) {
|
|
843
|
+
drawSoldermaskRingForPill(
|
|
844
|
+
ctx,
|
|
845
|
+
{ x: pad.x, y: pad.y },
|
|
846
|
+
pad.width,
|
|
847
|
+
pad.height,
|
|
848
|
+
margin,
|
|
849
|
+
0,
|
|
850
|
+
realToCanvasMat,
|
|
851
|
+
soldermaskRingColor,
|
|
852
|
+
color
|
|
853
|
+
);
|
|
854
|
+
}
|
|
563
855
|
return;
|
|
564
856
|
}
|
|
565
857
|
if (pad.shape === "rotated_pill") {
|
|
858
|
+
if (hasSoldermask && margin > 0) {
|
|
859
|
+
drawPill({
|
|
860
|
+
ctx,
|
|
861
|
+
center: { x: pad.x, y: pad.y },
|
|
862
|
+
width: pad.width + margin * 2,
|
|
863
|
+
height: pad.height + margin * 2,
|
|
864
|
+
fill: positiveMarginColor,
|
|
865
|
+
realToCanvasMat,
|
|
866
|
+
rotation: pad.ccw_rotation ?? 0
|
|
867
|
+
});
|
|
868
|
+
}
|
|
566
869
|
drawPill({
|
|
567
870
|
ctx,
|
|
568
871
|
center: { x: pad.x, y: pad.y },
|
|
@@ -572,6 +875,19 @@ function drawPcbSmtPad(params) {
|
|
|
572
875
|
realToCanvasMat,
|
|
573
876
|
rotation: pad.ccw_rotation ?? 0
|
|
574
877
|
});
|
|
878
|
+
if (hasSoldermask && margin < 0) {
|
|
879
|
+
drawSoldermaskRingForPill(
|
|
880
|
+
ctx,
|
|
881
|
+
{ x: pad.x, y: pad.y },
|
|
882
|
+
pad.width,
|
|
883
|
+
pad.height,
|
|
884
|
+
margin,
|
|
885
|
+
pad.ccw_rotation ?? 0,
|
|
886
|
+
realToCanvasMat,
|
|
887
|
+
soldermaskRingColor,
|
|
888
|
+
color
|
|
889
|
+
);
|
|
890
|
+
}
|
|
575
891
|
return;
|
|
576
892
|
}
|
|
577
893
|
if (pad.shape === "polygon") {
|
|
@@ -588,7 +904,7 @@ function drawPcbSmtPad(params) {
|
|
|
588
904
|
}
|
|
589
905
|
|
|
590
906
|
// lib/drawer/shapes/line.ts
|
|
591
|
-
import { applyToPoint as
|
|
907
|
+
import { applyToPoint as applyToPoint7 } from "transformation-matrix";
|
|
592
908
|
function drawLine(params) {
|
|
593
909
|
const {
|
|
594
910
|
ctx,
|
|
@@ -599,8 +915,8 @@ function drawLine(params) {
|
|
|
599
915
|
realToCanvasMat,
|
|
600
916
|
lineCap = "round"
|
|
601
917
|
} = params;
|
|
602
|
-
const [x1, y1] =
|
|
603
|
-
const [x2, y2] =
|
|
918
|
+
const [x1, y1] = applyToPoint7(realToCanvasMat, [start.x, start.y]);
|
|
919
|
+
const [x2, y2] = applyToPoint7(realToCanvasMat, [end.x, end.y]);
|
|
604
920
|
const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
|
|
605
921
|
ctx.beginPath();
|
|
606
922
|
ctx.moveTo(x1, y1);
|
|
@@ -641,7 +957,7 @@ function drawPcbTrace(params) {
|
|
|
641
957
|
}
|
|
642
958
|
|
|
643
959
|
// lib/drawer/shapes/path.ts
|
|
644
|
-
import { applyToPoint as
|
|
960
|
+
import { applyToPoint as applyToPoint8 } from "transformation-matrix";
|
|
645
961
|
function drawPath(params) {
|
|
646
962
|
const {
|
|
647
963
|
ctx,
|
|
@@ -655,7 +971,7 @@ function drawPath(params) {
|
|
|
655
971
|
if (points.length < 2) return;
|
|
656
972
|
ctx.beginPath();
|
|
657
973
|
const canvasPoints = points.map(
|
|
658
|
-
(p) =>
|
|
974
|
+
(p) => applyToPoint8(realToCanvasMat, [p.x, p.y])
|
|
659
975
|
);
|
|
660
976
|
const firstPoint = canvasPoints[0];
|
|
661
977
|
if (!firstPoint) return;
|
|
@@ -726,11 +1042,11 @@ function drawPcbBoard(params) {
|
|
|
726
1042
|
}
|
|
727
1043
|
|
|
728
1044
|
// lib/drawer/elements/pcb-silkscreen-text.ts
|
|
729
|
-
import { applyToPoint as
|
|
1045
|
+
import { applyToPoint as applyToPoint10 } from "transformation-matrix";
|
|
730
1046
|
|
|
731
1047
|
// lib/drawer/shapes/text/text.ts
|
|
732
1048
|
import { lineAlphabet } from "@tscircuit/alphabet";
|
|
733
|
-
import { applyToPoint as
|
|
1049
|
+
import { applyToPoint as applyToPoint9 } from "transformation-matrix";
|
|
734
1050
|
|
|
735
1051
|
// lib/drawer/shapes/text/getAlphabetLayout.ts
|
|
736
1052
|
var GLYPH_WIDTH_RATIO = 0.62;
|
|
@@ -827,7 +1143,7 @@ function drawText(params) {
|
|
|
827
1143
|
rotation = 0
|
|
828
1144
|
} = params;
|
|
829
1145
|
if (!text) return;
|
|
830
|
-
const [canvasX, canvasY] =
|
|
1146
|
+
const [canvasX, canvasY] = applyToPoint9(realToCanvasMat, [x, y]);
|
|
831
1147
|
const scale2 = Math.abs(realToCanvasMat.a);
|
|
832
1148
|
const scaledFontSize = fontSize * scale2;
|
|
833
1149
|
const layout = getAlphabetLayout(text, scaledFontSize);
|
|
@@ -864,7 +1180,7 @@ function drawPcbSilkscreenText(params) {
|
|
|
864
1180
|
const content = text.text ?? "";
|
|
865
1181
|
if (!content) return;
|
|
866
1182
|
const color = layerToSilkscreenColor(text.layer, colorMap);
|
|
867
|
-
const [x, y] =
|
|
1183
|
+
const [x, y] = applyToPoint10(realToCanvasMat, [
|
|
868
1184
|
text.anchor_position.x,
|
|
869
1185
|
text.anchor_position.y
|
|
870
1186
|
]);
|
|
@@ -969,13 +1285,32 @@ function drawPcbSilkscreenPath(params) {
|
|
|
969
1285
|
}
|
|
970
1286
|
}
|
|
971
1287
|
|
|
972
|
-
// lib/drawer/elements/pcb-silkscreen-
|
|
1288
|
+
// lib/drawer/elements/pcb-silkscreen-oval.ts
|
|
973
1289
|
function layerToSilkscreenColor6(layer, colorMap) {
|
|
974
1290
|
return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
|
|
975
1291
|
}
|
|
1292
|
+
function drawPcbSilkscreenOval(params) {
|
|
1293
|
+
const { ctx, oval, realToCanvasMat, colorMap } = params;
|
|
1294
|
+
const color = layerToSilkscreenColor6(oval.layer, colorMap);
|
|
1295
|
+
drawOval({
|
|
1296
|
+
ctx,
|
|
1297
|
+
center: oval.center,
|
|
1298
|
+
radius_x: oval.radius_x,
|
|
1299
|
+
radius_y: oval.radius_y,
|
|
1300
|
+
stroke: color,
|
|
1301
|
+
strokeWidth: 0.1,
|
|
1302
|
+
realToCanvasMat,
|
|
1303
|
+
rotation: oval.ccw_rotation ?? 0
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// lib/drawer/elements/pcb-silkscreen-pill.ts
|
|
1308
|
+
function layerToSilkscreenColor7(layer, colorMap) {
|
|
1309
|
+
return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
|
|
1310
|
+
}
|
|
976
1311
|
function drawPcbSilkscreenPill(params) {
|
|
977
1312
|
const { ctx, pill, realToCanvasMat, colorMap } = params;
|
|
978
|
-
const color =
|
|
1313
|
+
const color = layerToSilkscreenColor7(pill.layer, colorMap);
|
|
979
1314
|
const strokeWidth = 0.2;
|
|
980
1315
|
drawPill({
|
|
981
1316
|
ctx,
|
|
@@ -1027,7 +1362,7 @@ function drawPcbCutout(params) {
|
|
|
1027
1362
|
}
|
|
1028
1363
|
|
|
1029
1364
|
// lib/drawer/elements/pcb-copper-pour.ts
|
|
1030
|
-
import { applyToPoint as
|
|
1365
|
+
import { applyToPoint as applyToPoint11 } from "transformation-matrix";
|
|
1031
1366
|
function layerToColor3(layer, colorMap) {
|
|
1032
1367
|
return colorMap.copper[layer] ?? colorMap.copper.top;
|
|
1033
1368
|
}
|
|
@@ -1036,7 +1371,7 @@ function drawPcbCopperPour(params) {
|
|
|
1036
1371
|
const color = layerToColor3(pour.layer, colorMap);
|
|
1037
1372
|
ctx.save();
|
|
1038
1373
|
if (pour.shape === "rect") {
|
|
1039
|
-
const [cx, cy] =
|
|
1374
|
+
const [cx, cy] = applyToPoint11(realToCanvasMat, [
|
|
1040
1375
|
pour.center.x,
|
|
1041
1376
|
pour.center.y
|
|
1042
1377
|
]);
|
|
@@ -1057,7 +1392,7 @@ function drawPcbCopperPour(params) {
|
|
|
1057
1392
|
if (pour.shape === "polygon") {
|
|
1058
1393
|
if (pour.points && pour.points.length >= 3) {
|
|
1059
1394
|
const canvasPoints = pour.points.map(
|
|
1060
|
-
(p) =>
|
|
1395
|
+
(p) => applyToPoint11(realToCanvasMat, [p.x, p.y])
|
|
1061
1396
|
);
|
|
1062
1397
|
const firstPoint = canvasPoints[0];
|
|
1063
1398
|
if (!firstPoint) {
|
|
@@ -1085,7 +1420,7 @@ function drawPcbCopperPour(params) {
|
|
|
1085
1420
|
}
|
|
1086
1421
|
|
|
1087
1422
|
// lib/drawer/elements/pcb-copper-text.ts
|
|
1088
|
-
import { applyToPoint as
|
|
1423
|
+
import { applyToPoint as applyToPoint12 } from "transformation-matrix";
|
|
1089
1424
|
var DEFAULT_PADDING = { left: 0.2, right: 0.2, top: 0.2, bottom: 0.2 };
|
|
1090
1425
|
function layerToCopperColor(layer, colorMap) {
|
|
1091
1426
|
return colorMap.copper[layer] ?? colorMap.copper.top;
|
|
@@ -1100,7 +1435,7 @@ function drawPcbCopperText(params) {
|
|
|
1100
1435
|
const { ctx, text, realToCanvasMat, colorMap } = params;
|
|
1101
1436
|
const content = text.text ?? "";
|
|
1102
1437
|
if (!content) return;
|
|
1103
|
-
const [x, y] =
|
|
1438
|
+
const [x, y] = applyToPoint12(realToCanvasMat, [
|
|
1104
1439
|
text.anchor_position.x,
|
|
1105
1440
|
text.anchor_position.y
|
|
1106
1441
|
]);
|
|
@@ -1284,7 +1619,7 @@ function drawPcbNoteText(params) {
|
|
|
1284
1619
|
}
|
|
1285
1620
|
|
|
1286
1621
|
// lib/drawer/elements/pcb-note-dimension.ts
|
|
1287
|
-
import { applyToPoint as
|
|
1622
|
+
import { applyToPoint as applyToPoint13 } from "transformation-matrix";
|
|
1288
1623
|
|
|
1289
1624
|
// lib/drawer/shapes/arrow.ts
|
|
1290
1625
|
function drawArrow(params) {
|
|
@@ -1369,11 +1704,11 @@ function drawPcbNoteDimension(params) {
|
|
|
1369
1704
|
stroke: color,
|
|
1370
1705
|
realToCanvasMat
|
|
1371
1706
|
});
|
|
1372
|
-
const [canvasFromX, canvasFromY] =
|
|
1707
|
+
const [canvasFromX, canvasFromY] = applyToPoint13(realToCanvasMat, [
|
|
1373
1708
|
fromX,
|
|
1374
1709
|
fromY
|
|
1375
1710
|
]);
|
|
1376
|
-
const [canvasToX, canvasToY] =
|
|
1711
|
+
const [canvasToX, canvasToY] = applyToPoint13(realToCanvasMat, [toX, toY]);
|
|
1377
1712
|
const canvasDx = canvasToX - canvasFromX;
|
|
1378
1713
|
const canvasDy = canvasToY - canvasFromY;
|
|
1379
1714
|
const lineAngle = Math.atan2(canvasDy, canvasDx);
|
|
@@ -1434,15 +1769,15 @@ function drawPcbNoteDimension(params) {
|
|
|
1434
1769
|
}
|
|
1435
1770
|
|
|
1436
1771
|
// lib/drawer/elements/pcb-note-line.ts
|
|
1437
|
-
import { applyToPoint as
|
|
1772
|
+
import { applyToPoint as applyToPoint14 } from "transformation-matrix";
|
|
1438
1773
|
function drawPcbNoteLine(params) {
|
|
1439
1774
|
const { ctx, line, realToCanvasMat, colorMap } = params;
|
|
1440
1775
|
const defaultColor = "rgb(89, 148, 220)";
|
|
1441
1776
|
const color = line.color ?? defaultColor;
|
|
1442
1777
|
const strokeWidth = line.stroke_width ?? 0.1;
|
|
1443
1778
|
const isDashed = line.is_dashed ?? false;
|
|
1444
|
-
const [x1, y1] =
|
|
1445
|
-
const [x2, y2] =
|
|
1779
|
+
const [x1, y1] = applyToPoint14(realToCanvasMat, [line.x1, line.y1]);
|
|
1780
|
+
const [x2, y2] = applyToPoint14(realToCanvasMat, [line.x2, line.y2]);
|
|
1446
1781
|
const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
|
|
1447
1782
|
ctx.save();
|
|
1448
1783
|
if (isDashed) {
|
|
@@ -1524,8 +1859,76 @@ var CircuitToCanvasDrawer = class {
|
|
|
1524
1859
|
);
|
|
1525
1860
|
}
|
|
1526
1861
|
drawElements(elements, options = {}) {
|
|
1862
|
+
const hasSoldermaskPads = elements.some(
|
|
1863
|
+
(el) => el.type === "pcb_smtpad" && el.is_covered_with_solder_mask === true
|
|
1864
|
+
);
|
|
1527
1865
|
for (const element of elements) {
|
|
1528
|
-
|
|
1866
|
+
if (element.type === "pcb_board" && hasSoldermaskPads) {
|
|
1867
|
+
this.drawBoardWithSoldermask(element);
|
|
1868
|
+
} else {
|
|
1869
|
+
this.drawElement(element, options);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
drawBoardWithSoldermask(board) {
|
|
1874
|
+
const { width, height, center, outline } = board;
|
|
1875
|
+
const layer = "top";
|
|
1876
|
+
if (outline && Array.isArray(outline) && outline.length >= 3) {
|
|
1877
|
+
const soldermaskColor = this.colorMap.soldermask[layer] ?? this.colorMap.soldermask.top;
|
|
1878
|
+
const canvasPoints = outline.map((p) => {
|
|
1879
|
+
const [x, y] = applyToPoint15(this.realToCanvasMat, [p.x, p.y]);
|
|
1880
|
+
return { x, y };
|
|
1881
|
+
});
|
|
1882
|
+
this.ctx.beginPath();
|
|
1883
|
+
const firstPoint = canvasPoints[0];
|
|
1884
|
+
if (firstPoint) {
|
|
1885
|
+
this.ctx.moveTo(firstPoint.x, firstPoint.y);
|
|
1886
|
+
for (let i = 1; i < canvasPoints.length; i++) {
|
|
1887
|
+
const point = canvasPoints[i];
|
|
1888
|
+
if (point) {
|
|
1889
|
+
this.ctx.lineTo(point.x, point.y);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
this.ctx.closePath();
|
|
1893
|
+
}
|
|
1894
|
+
this.ctx.fillStyle = soldermaskColor;
|
|
1895
|
+
this.ctx.fill();
|
|
1896
|
+
drawPath({
|
|
1897
|
+
ctx: this.ctx,
|
|
1898
|
+
points: outline.map((p) => ({ x: p.x, y: p.y })),
|
|
1899
|
+
stroke: this.colorMap.boardOutline,
|
|
1900
|
+
strokeWidth: 0.1,
|
|
1901
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
1902
|
+
closePath: true
|
|
1903
|
+
});
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
if (width !== void 0 && height !== void 0 && center) {
|
|
1907
|
+
const soldermaskColor = this.colorMap.soldermask[layer] ?? this.colorMap.soldermask.top;
|
|
1908
|
+
drawRect({
|
|
1909
|
+
ctx: this.ctx,
|
|
1910
|
+
center,
|
|
1911
|
+
width,
|
|
1912
|
+
height,
|
|
1913
|
+
fill: soldermaskColor,
|
|
1914
|
+
realToCanvasMat: this.realToCanvasMat
|
|
1915
|
+
});
|
|
1916
|
+
const halfWidth = width / 2;
|
|
1917
|
+
const halfHeight = height / 2;
|
|
1918
|
+
const corners = [
|
|
1919
|
+
{ x: center.x - halfWidth, y: center.y - halfHeight },
|
|
1920
|
+
{ x: center.x + halfWidth, y: center.y - halfHeight },
|
|
1921
|
+
{ x: center.x + halfWidth, y: center.y + halfHeight },
|
|
1922
|
+
{ x: center.x - halfWidth, y: center.y + halfHeight }
|
|
1923
|
+
];
|
|
1924
|
+
drawPath({
|
|
1925
|
+
ctx: this.ctx,
|
|
1926
|
+
points: corners,
|
|
1927
|
+
stroke: this.colorMap.boardOutline,
|
|
1928
|
+
strokeWidth: 0.1,
|
|
1929
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
1930
|
+
closePath: true
|
|
1931
|
+
});
|
|
1529
1932
|
}
|
|
1530
1933
|
}
|
|
1531
1934
|
drawElement(element, options) {
|
|
@@ -1628,6 +2031,14 @@ var CircuitToCanvasDrawer = class {
|
|
|
1628
2031
|
colorMap: this.colorMap
|
|
1629
2032
|
});
|
|
1630
2033
|
}
|
|
2034
|
+
if (element.type === "pcb_silkscreen_oval") {
|
|
2035
|
+
drawPcbSilkscreenOval({
|
|
2036
|
+
ctx: this.ctx,
|
|
2037
|
+
oval: element,
|
|
2038
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
2039
|
+
colorMap: this.colorMap
|
|
2040
|
+
});
|
|
2041
|
+
}
|
|
1631
2042
|
if (element.type === "pcb_cutout") {
|
|
1632
2043
|
drawPcbCutout({
|
|
1633
2044
|
ctx: this.ctx,
|
|
@@ -1741,6 +2152,7 @@ export {
|
|
|
1741
2152
|
drawPcbPlatedHole,
|
|
1742
2153
|
drawPcbSilkscreenCircle,
|
|
1743
2154
|
drawPcbSilkscreenLine,
|
|
2155
|
+
drawPcbSilkscreenOval,
|
|
1744
2156
|
drawPcbSilkscreenPath,
|
|
1745
2157
|
drawPcbSilkscreenPill,
|
|
1746
2158
|
drawPcbSilkscreenRect,
|
|
@@ -1751,6 +2163,9 @@ export {
|
|
|
1751
2163
|
drawPill,
|
|
1752
2164
|
drawPolygon,
|
|
1753
2165
|
drawRect,
|
|
2166
|
+
drawSoldermaskRingForCircle,
|
|
2167
|
+
drawSoldermaskRingForPill,
|
|
2168
|
+
drawSoldermaskRingForRect,
|
|
1754
2169
|
drawText,
|
|
1755
2170
|
getAlphabetLayout,
|
|
1756
2171
|
getTextStartPosition,
|