circuit-to-canvas 0.0.110 → 0.0.111
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 +2 -1
- package/dist/index.js +52 -14
- package/lib/drawer/CircuitToCanvasDrawer.ts +5 -0
- package/lib/drawer/elements/helper-functions/pcb-smtpad-drill-cutouts.ts +69 -16
- package/lib/drawer/elements/pcb-smtpad.ts +10 -3
- package/package.json +1 -1
- package/tests/elements/__snapshots__/pcb-smtpad-cutout.snap.png +0 -0
- package/tests/elements/pcb-smtpad-cutout.test.ts +39 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnyCircuitElement, PcbRenderLayer, NinePointAnchor, PcbPlatedHole, PCBVia, PcbHole, PcbSmtPad, PcbVia, PcbTrace, PcbBoard, PcbPanel, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbSilkscreenPill, PcbSilkscreenOval,
|
|
1
|
+
import { AnyCircuitElement, PcbRenderLayer, NinePointAnchor, PcbPlatedHole, PCBVia, PcbHole, PcbSmtPad, PcbVia, PcbCutout, PcbTrace, PcbBoard, PcbPanel, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbSilkscreenPill, PcbSilkscreenOval, PCBKeepout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText, PcbNoteDimension, PcbFabricationNoteDimension, PcbCourtyardCircle } from 'circuit-json';
|
|
2
2
|
import { Matrix } from 'transformation-matrix';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -364,6 +364,7 @@ interface DrawPcbSmtPadParams {
|
|
|
364
364
|
holes?: PcbHole[];
|
|
365
365
|
platedHoles?: PcbPlatedHole[];
|
|
366
366
|
vias?: PcbVia[];
|
|
367
|
+
cutouts?: PcbCutout[];
|
|
367
368
|
}
|
|
368
369
|
declare function drawPcbSmtPad(params: DrawPcbSmtPadParams): void;
|
|
369
370
|
|
package/dist/index.js
CHANGED
|
@@ -1637,22 +1637,24 @@ function drawPadWithDrillCutouts(params) {
|
|
|
1637
1637
|
ctx.restore();
|
|
1638
1638
|
}
|
|
1639
1639
|
function getPadDrillCutouts(params) {
|
|
1640
|
-
const { pad, holes = [], platedHoles = [], vias = [] } = params;
|
|
1640
|
+
const { pad, holes = [], platedHoles = [], vias = [], cutouts = [] } = params;
|
|
1641
1641
|
const padBounds = getPadBounds(pad);
|
|
1642
1642
|
if (!padBounds) return [];
|
|
1643
|
-
const
|
|
1644
|
-
|
|
1645
|
-
const cutout = getHoleDrillCutout(hole);
|
|
1643
|
+
const drillCutouts = [];
|
|
1644
|
+
const addIfOverlapping = (cutout) => {
|
|
1646
1645
|
if (cutout && boundsOverlap(padBounds, getDrillCutoutBounds(cutout))) {
|
|
1647
|
-
|
|
1646
|
+
drillCutouts.push(cutout);
|
|
1648
1647
|
}
|
|
1648
|
+
};
|
|
1649
|
+
for (const cutout of cutouts) {
|
|
1650
|
+
addIfOverlapping(getBoardCutout(cutout));
|
|
1651
|
+
}
|
|
1652
|
+
for (const hole of holes) {
|
|
1653
|
+
addIfOverlapping(getHoleDrillCutout(hole));
|
|
1649
1654
|
}
|
|
1650
1655
|
for (const platedHole of platedHoles) {
|
|
1651
1656
|
if (!layersIncludePadLayer(platedHole.layers, pad.layer)) continue;
|
|
1652
|
-
|
|
1653
|
-
if (cutout && boundsOverlap(padBounds, getDrillCutoutBounds(cutout))) {
|
|
1654
|
-
cutouts.push(cutout);
|
|
1655
|
-
}
|
|
1657
|
+
addIfOverlapping(getPlatedHoleDrillCutout(platedHole));
|
|
1656
1658
|
}
|
|
1657
1659
|
for (const via of vias) {
|
|
1658
1660
|
if (!layersIncludePadLayer(via.layers, pad.layer)) continue;
|
|
@@ -1664,11 +1666,9 @@ function getPadDrillCutouts(params) {
|
|
|
1664
1666
|
y: via.y,
|
|
1665
1667
|
radius
|
|
1666
1668
|
};
|
|
1667
|
-
|
|
1668
|
-
cutouts.push(cutout);
|
|
1669
|
-
}
|
|
1669
|
+
addIfOverlapping(cutout);
|
|
1670
1670
|
}
|
|
1671
|
-
return
|
|
1671
|
+
return drillCutouts;
|
|
1672
1672
|
}
|
|
1673
1673
|
function appendPadPath(ctx, pad, realToCanvasMat) {
|
|
1674
1674
|
if (pad.shape === "rect" || pad.shape === "rotated_rect") {
|
|
@@ -1741,11 +1741,16 @@ function appendDrillCutoutPath(ctx, drillCutout, realToCanvasMat) {
|
|
|
1741
1741
|
});
|
|
1742
1742
|
return;
|
|
1743
1743
|
}
|
|
1744
|
+
if (drillCutout.shape === "polygon") {
|
|
1745
|
+
appendPolygonPath(ctx, drillCutout.points, realToCanvasMat);
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1744
1748
|
appendRectPath(ctx, {
|
|
1745
1749
|
x: drillCutout.x,
|
|
1746
1750
|
y: drillCutout.y,
|
|
1747
1751
|
width: drillCutout.width,
|
|
1748
1752
|
height: drillCutout.height,
|
|
1753
|
+
radius: drillCutout.radius,
|
|
1749
1754
|
rotation: drillCutout.rotation,
|
|
1750
1755
|
realToCanvasMat
|
|
1751
1756
|
});
|
|
@@ -1883,6 +1888,9 @@ function getPadBounds(pad) {
|
|
|
1883
1888
|
}
|
|
1884
1889
|
}
|
|
1885
1890
|
function getDrillCutoutBounds(cutout) {
|
|
1891
|
+
if (cutout.shape === "polygon") {
|
|
1892
|
+
return getPointBounds(cutout.points);
|
|
1893
|
+
}
|
|
1886
1894
|
if (cutout.shape === "circle") {
|
|
1887
1895
|
return boundsAroundCenter(cutout.x, cutout.y, cutout.radius, cutout.radius);
|
|
1888
1896
|
}
|
|
@@ -1906,6 +1914,32 @@ function getDrillCutoutBounds(cutout) {
|
|
|
1906
1914
|
}
|
|
1907
1915
|
return boundsAroundCenter(cutout.x, cutout.y, halfWidth, halfHeight);
|
|
1908
1916
|
}
|
|
1917
|
+
function getBoardCutout(cutout) {
|
|
1918
|
+
if (cutout.shape === "circle") {
|
|
1919
|
+
if (cutout.radius <= 0) return;
|
|
1920
|
+
return {
|
|
1921
|
+
shape: "circle",
|
|
1922
|
+
x: cutout.center.x,
|
|
1923
|
+
y: cutout.center.y,
|
|
1924
|
+
radius: cutout.radius
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
if (cutout.shape === "rect") {
|
|
1928
|
+
if (cutout.width <= 0 || cutout.height <= 0) return;
|
|
1929
|
+
return {
|
|
1930
|
+
shape: "rect",
|
|
1931
|
+
x: cutout.center.x,
|
|
1932
|
+
y: cutout.center.y,
|
|
1933
|
+
width: cutout.width,
|
|
1934
|
+
height: cutout.height,
|
|
1935
|
+
radius: cutout.corner_radius,
|
|
1936
|
+
rotation: cutout.rotation
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
if (cutout.shape === "polygon" && cutout.points.length >= 3) {
|
|
1940
|
+
return { shape: "polygon", points: cutout.points };
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1909
1943
|
function getPointBounds(points) {
|
|
1910
1944
|
let minX = Infinity;
|
|
1911
1945
|
let maxX = -Infinity;
|
|
@@ -5318,6 +5352,9 @@ var CircuitToCanvasDrawer = class {
|
|
|
5318
5352
|
const drawablePlatedHoles = elements.filter(
|
|
5319
5353
|
(el) => shouldDrawElement(el, options) && el.type === "pcb_plated_hole"
|
|
5320
5354
|
);
|
|
5355
|
+
const drawableCutouts = elements.filter(
|
|
5356
|
+
(el) => shouldDrawElement(el, options) && el.type === "pcb_cutout"
|
|
5357
|
+
);
|
|
5321
5358
|
if (board) {
|
|
5322
5359
|
drawPcbBoard({
|
|
5323
5360
|
ctx: this.ctx,
|
|
@@ -5349,7 +5386,8 @@ var CircuitToCanvasDrawer = class {
|
|
|
5349
5386
|
colorMap: this.colorMap,
|
|
5350
5387
|
holes: drawableHoles,
|
|
5351
5388
|
platedHoles: drawablePlatedHoles,
|
|
5352
|
-
vias: drawableVias
|
|
5389
|
+
vias: drawableVias,
|
|
5390
|
+
cutouts: drawableCutouts
|
|
5353
5391
|
});
|
|
5354
5392
|
}
|
|
5355
5393
|
if (element.type === "pcb_copper_text") {
|
|
@@ -243,6 +243,10 @@ export class CircuitToCanvasDrawer {
|
|
|
243
243
|
(el): el is PcbPlatedHole =>
|
|
244
244
|
shouldDrawElement(el, options) && el.type === "pcb_plated_hole",
|
|
245
245
|
)
|
|
246
|
+
const drawableCutouts = elements.filter(
|
|
247
|
+
(el): el is PcbCutout =>
|
|
248
|
+
shouldDrawElement(el, options) && el.type === "pcb_cutout",
|
|
249
|
+
)
|
|
246
250
|
|
|
247
251
|
// Step 2: Draw board outline/material (inner board)
|
|
248
252
|
if (board) {
|
|
@@ -283,6 +287,7 @@ export class CircuitToCanvasDrawer {
|
|
|
283
287
|
holes: drawableHoles,
|
|
284
288
|
platedHoles: drawablePlatedHoles,
|
|
285
289
|
vias: drawableVias,
|
|
290
|
+
cutouts: drawableCutouts,
|
|
286
291
|
})
|
|
287
292
|
}
|
|
288
293
|
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
PcbCutout,
|
|
3
|
+
PcbHole,
|
|
4
|
+
PcbPlatedHole,
|
|
5
|
+
PcbSmtPad,
|
|
6
|
+
PcbVia,
|
|
7
|
+
} from "circuit-json"
|
|
2
8
|
import type { Matrix } from "transformation-matrix"
|
|
3
9
|
import { applyToPoint } from "transformation-matrix"
|
|
4
10
|
import type { CanvasContext } from "../../types"
|
|
@@ -34,14 +40,21 @@ interface DrillCutoutRect {
|
|
|
34
40
|
y: number
|
|
35
41
|
width: number
|
|
36
42
|
height: number
|
|
43
|
+
radius?: number
|
|
37
44
|
rotation?: number
|
|
38
45
|
}
|
|
39
46
|
|
|
47
|
+
interface DrillCutoutPolygon {
|
|
48
|
+
shape: "polygon"
|
|
49
|
+
points: Array<{ x: number; y: number }>
|
|
50
|
+
}
|
|
51
|
+
|
|
40
52
|
type DrillCutout =
|
|
41
53
|
| DrillCutoutCircle
|
|
42
54
|
| DrillCutoutOval
|
|
43
55
|
| DrillCutoutPill
|
|
44
56
|
| DrillCutoutRect
|
|
57
|
+
| DrillCutoutPolygon
|
|
45
58
|
|
|
46
59
|
interface Bounds {
|
|
47
60
|
minX: number
|
|
@@ -55,6 +68,7 @@ export interface PcbSmtPadDrillCutoutParams {
|
|
|
55
68
|
holes?: PcbHole[]
|
|
56
69
|
platedHoles?: PcbPlatedHole[]
|
|
57
70
|
vias?: PcbVia[]
|
|
71
|
+
cutouts?: PcbCutout[]
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
export function drawPadWithDrillCutouts(params: {
|
|
@@ -87,26 +101,28 @@ export function drawPadWithDrillCutouts(params: {
|
|
|
87
101
|
export function getPadDrillCutouts(
|
|
88
102
|
params: PcbSmtPadDrillCutoutParams,
|
|
89
103
|
): DrillCutout[] {
|
|
90
|
-
const { pad, holes = [], platedHoles = [], vias = [] } = params
|
|
104
|
+
const { pad, holes = [], platedHoles = [], vias = [], cutouts = [] } = params
|
|
91
105
|
const padBounds = getPadBounds(pad)
|
|
92
106
|
if (!padBounds) return []
|
|
93
107
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
for (const hole of holes) {
|
|
97
|
-
const cutout = getHoleDrillCutout(hole)
|
|
108
|
+
const drillCutouts: DrillCutout[] = []
|
|
109
|
+
const addIfOverlapping = (cutout: DrillCutout | undefined): void => {
|
|
98
110
|
if (cutout && boundsOverlap(padBounds, getDrillCutoutBounds(cutout))) {
|
|
99
|
-
|
|
111
|
+
drillCutouts.push(cutout)
|
|
100
112
|
}
|
|
101
113
|
}
|
|
102
114
|
|
|
115
|
+
for (const cutout of cutouts) {
|
|
116
|
+
addIfOverlapping(getBoardCutout(cutout))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (const hole of holes) {
|
|
120
|
+
addIfOverlapping(getHoleDrillCutout(hole))
|
|
121
|
+
}
|
|
122
|
+
|
|
103
123
|
for (const platedHole of platedHoles) {
|
|
104
124
|
if (!layersIncludePadLayer(platedHole.layers, pad.layer)) continue
|
|
105
|
-
|
|
106
|
-
const cutout = getPlatedHoleDrillCutout(platedHole)
|
|
107
|
-
if (cutout && boundsOverlap(padBounds, getDrillCutoutBounds(cutout))) {
|
|
108
|
-
cutouts.push(cutout)
|
|
109
|
-
}
|
|
125
|
+
addIfOverlapping(getPlatedHoleDrillCutout(platedHole))
|
|
110
126
|
}
|
|
111
127
|
|
|
112
128
|
for (const via of vias) {
|
|
@@ -120,12 +136,10 @@ export function getPadDrillCutouts(
|
|
|
120
136
|
y: via.y,
|
|
121
137
|
radius,
|
|
122
138
|
}
|
|
123
|
-
|
|
124
|
-
cutouts.push(cutout)
|
|
125
|
-
}
|
|
139
|
+
addIfOverlapping(cutout)
|
|
126
140
|
}
|
|
127
141
|
|
|
128
|
-
return
|
|
142
|
+
return drillCutouts
|
|
129
143
|
}
|
|
130
144
|
|
|
131
145
|
function appendPadPath(
|
|
@@ -215,11 +229,17 @@ function appendDrillCutoutPath(
|
|
|
215
229
|
return
|
|
216
230
|
}
|
|
217
231
|
|
|
232
|
+
if (drillCutout.shape === "polygon") {
|
|
233
|
+
appendPolygonPath(ctx, drillCutout.points, realToCanvasMat)
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
|
|
218
237
|
appendRectPath(ctx, {
|
|
219
238
|
x: drillCutout.x,
|
|
220
239
|
y: drillCutout.y,
|
|
221
240
|
width: drillCutout.width,
|
|
222
241
|
height: drillCutout.height,
|
|
242
|
+
radius: drillCutout.radius,
|
|
223
243
|
rotation: drillCutout.rotation,
|
|
224
244
|
realToCanvasMat,
|
|
225
245
|
})
|
|
@@ -429,6 +449,10 @@ function getPadBounds(pad: PcbSmtPad): Bounds | undefined {
|
|
|
429
449
|
}
|
|
430
450
|
|
|
431
451
|
function getDrillCutoutBounds(cutout: DrillCutout): Bounds {
|
|
452
|
+
if (cutout.shape === "polygon") {
|
|
453
|
+
return getPointBounds(cutout.points)
|
|
454
|
+
}
|
|
455
|
+
|
|
432
456
|
if (cutout.shape === "circle") {
|
|
433
457
|
return boundsAroundCenter(cutout.x, cutout.y, cutout.radius, cutout.radius)
|
|
434
458
|
}
|
|
@@ -455,6 +479,35 @@ function getDrillCutoutBounds(cutout: DrillCutout): Bounds {
|
|
|
455
479
|
return boundsAroundCenter(cutout.x, cutout.y, halfWidth, halfHeight)
|
|
456
480
|
}
|
|
457
481
|
|
|
482
|
+
function getBoardCutout(cutout: PcbCutout): DrillCutout | undefined {
|
|
483
|
+
if (cutout.shape === "circle") {
|
|
484
|
+
if (cutout.radius <= 0) return
|
|
485
|
+
return {
|
|
486
|
+
shape: "circle",
|
|
487
|
+
x: cutout.center.x,
|
|
488
|
+
y: cutout.center.y,
|
|
489
|
+
radius: cutout.radius,
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (cutout.shape === "rect") {
|
|
494
|
+
if (cutout.width <= 0 || cutout.height <= 0) return
|
|
495
|
+
return {
|
|
496
|
+
shape: "rect",
|
|
497
|
+
x: cutout.center.x,
|
|
498
|
+
y: cutout.center.y,
|
|
499
|
+
width: cutout.width,
|
|
500
|
+
height: cutout.height,
|
|
501
|
+
radius: cutout.corner_radius,
|
|
502
|
+
rotation: cutout.rotation,
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (cutout.shape === "polygon" && cutout.points.length >= 3) {
|
|
507
|
+
return { shape: "polygon", points: cutout.points }
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
458
511
|
function getPointBounds(points: Array<{ x: number; y: number }>): Bounds {
|
|
459
512
|
let minX = Infinity
|
|
460
513
|
let maxX = -Infinity
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
PcbCutout,
|
|
3
|
+
PcbHole,
|
|
4
|
+
PcbPlatedHole,
|
|
5
|
+
PcbSmtPad,
|
|
6
|
+
PcbVia,
|
|
7
|
+
} from "circuit-json"
|
|
2
8
|
import type { Matrix } from "transformation-matrix"
|
|
3
|
-
import type { PcbColorMap, CanvasContext } from "../types"
|
|
4
9
|
import { drawCircle } from "../shapes/circle"
|
|
5
|
-
import { drawRect } from "../shapes/rect"
|
|
6
10
|
import { drawPill } from "../shapes/pill"
|
|
7
11
|
import { drawPolygon } from "../shapes/polygon"
|
|
12
|
+
import { drawRect } from "../shapes/rect"
|
|
13
|
+
import type { CanvasContext, PcbColorMap } from "../types"
|
|
8
14
|
import {
|
|
9
15
|
drawPadWithDrillCutouts,
|
|
10
16
|
getPadDrillCutouts,
|
|
@@ -18,6 +24,7 @@ export interface DrawPcbSmtPadParams {
|
|
|
18
24
|
holes?: PcbHole[]
|
|
19
25
|
platedHoles?: PcbPlatedHole[]
|
|
20
26
|
vias?: PcbVia[]
|
|
27
|
+
cutouts?: PcbCutout[]
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
function layerToColor(layer: string, colorMap: PcbColorMap): string {
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { createCanvas } from "@napi-rs/canvas"
|
|
3
|
+
import type { PcbCutout, PcbSmtPad } from "circuit-json"
|
|
4
|
+
import { CircuitToCanvasDrawer } from "../../lib/drawer"
|
|
5
|
+
|
|
6
|
+
test("cutout removes overlapping SMT pad copper", async () => {
|
|
7
|
+
const scale = 4
|
|
8
|
+
const canvas = createCanvas(100 * scale, 100 * scale)
|
|
9
|
+
const ctx = canvas.getContext("2d")
|
|
10
|
+
ctx.scale(scale, scale)
|
|
11
|
+
const drawer = new CircuitToCanvasDrawer(ctx)
|
|
12
|
+
drawer.configure({
|
|
13
|
+
colorOverrides: { drill: "rgba(0,0,0,0)" },
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const pad: PcbSmtPad = {
|
|
17
|
+
type: "pcb_smtpad",
|
|
18
|
+
pcb_smtpad_id: "pad1",
|
|
19
|
+
shape: "rect",
|
|
20
|
+
layer: "top",
|
|
21
|
+
x: 50,
|
|
22
|
+
y: 50,
|
|
23
|
+
width: 40,
|
|
24
|
+
height: 20,
|
|
25
|
+
}
|
|
26
|
+
const cutout: PcbCutout = {
|
|
27
|
+
type: "pcb_cutout",
|
|
28
|
+
pcb_cutout_id: "cutout1",
|
|
29
|
+
shape: "circle",
|
|
30
|
+
center: { x: 50, y: 50 },
|
|
31
|
+
radius: 5,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
drawer.drawElements([pad, cutout])
|
|
35
|
+
|
|
36
|
+
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
37
|
+
import.meta.path,
|
|
38
|
+
)
|
|
39
|
+
})
|