circuit-to-canvas 0.0.49 → 0.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +13 -5
- package/dist/index.js +1450 -1226
- package/lib/drawer/CircuitToCanvasDrawer.ts +262 -312
- package/lib/drawer/elements/helper-functions/draw-pill.ts +39 -0
- package/lib/drawer/elements/helper-functions/draw-polygon.ts +25 -0
- package/lib/drawer/elements/helper-functions/draw-rounded-rect.ts +34 -0
- package/lib/drawer/elements/helper-functions/index.ts +3 -0
- package/lib/drawer/elements/pcb-board.ts +13 -3
- package/lib/drawer/elements/pcb-hole.ts +56 -338
- package/lib/drawer/elements/pcb-plated-hole.ts +154 -442
- package/lib/drawer/elements/pcb-smtpad.ts +5 -271
- package/lib/drawer/elements/pcb-soldermask/board.ts +44 -0
- package/lib/drawer/elements/pcb-soldermask/cutout.ts +74 -0
- package/lib/drawer/elements/pcb-soldermask/hole.ts +288 -0
- package/lib/drawer/elements/pcb-soldermask/index.ts +140 -0
- package/lib/drawer/elements/pcb-soldermask/plated-hole.ts +365 -0
- package/lib/drawer/elements/pcb-soldermask/smt-pad.ts +354 -0
- package/lib/drawer/elements/pcb-soldermask/via.ts +27 -0
- package/lib/drawer/elements/soldermask-margin.ts +39 -8
- package/package.json +2 -2
- package/tests/board-snapshot/__snapshots__/usb-c-flashlight-board.snap.png +0 -0
- package/tests/board-snapshot/usb-c-flashlight-board.test.ts +1 -0
- package/tests/elements/__snapshots__/board-with-elements.snap.png +0 -0
- package/tests/elements/__snapshots__/brep-copper-pours.snap.png +0 -0
- package/tests/elements/__snapshots__/custom-outline-board.snap.png +0 -0
- package/tests/elements/__snapshots__/oval-plated-hole.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-board.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-comprehensive-soldermask-margin.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-fabrication-note-dimension.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-hole-soldermask-margin.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-keepout-layer-filter.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-keepout-multiple-layers.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-keepout-rect-and-circle.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-keepout-with-group-id.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-no-soldermask.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-plated-hole-soldermask-margin.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-plated-hole.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-silkscreen-on-component.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-silkscreen-oval.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-smtpad-asymmetric-soldermask-margin.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-smtpad-soldermask-coverage.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-smtpad-soldermask-margin.snap.png +0 -0
- package/tests/elements/__snapshots__/pill-plated-hole.snap.png +0 -0
- package/tests/elements/pcb-comprehensive-soldermask-margin.test.ts +2 -2
- package/tests/elements/pcb-hole-soldermask-margin.test.ts +155 -2
- package/tests/elements/pcb-no-soldermask.test.ts +1281 -0
- package/tests/elements/pcb-plated-hole-soldermask-margin.test.ts +1 -1
- package/tests/elements/pcb-plated-hole.test.ts +40 -4
- package/tests/elements/pcb-smtpad-asymmetric-soldermask-margin.test.ts +140 -0
- package/tests/elements/pcb-smtpad-soldermask-coverage.test.ts +1 -1
- package/tests/elements/pcb-smtpad-soldermask-margin.test.ts +18 -2
- package/tests/fixtures/getStackedPngSvgComparison.ts +8 -2
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PcbVia } from "circuit-json"
|
|
2
|
+
import type { Matrix } from "transformation-matrix"
|
|
3
|
+
import { applyToPoint } from "transformation-matrix"
|
|
4
|
+
import type { CanvasContext, PcbColorMap } from "../../types"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Process soldermask for a via.
|
|
8
|
+
* Vias typically have soldermask openings to expose the copper ring.
|
|
9
|
+
*/
|
|
10
|
+
export function processViaSoldermask(params: {
|
|
11
|
+
ctx: CanvasContext
|
|
12
|
+
via: PcbVia
|
|
13
|
+
realToCanvasMat: Matrix
|
|
14
|
+
colorMap: PcbColorMap
|
|
15
|
+
}): void {
|
|
16
|
+
const { ctx, via, realToCanvasMat, colorMap } = params
|
|
17
|
+
// Vias typically have soldermask openings to expose the copper ring
|
|
18
|
+
// Draw substrate color to simulate the cutout
|
|
19
|
+
const [cx, cy] = applyToPoint(realToCanvasMat, [via.x, via.y])
|
|
20
|
+
const scaledRadius = (via.outer_diameter / 2) * Math.abs(realToCanvasMat.a)
|
|
21
|
+
|
|
22
|
+
ctx.fillStyle = colorMap.substrate
|
|
23
|
+
ctx.beginPath()
|
|
24
|
+
ctx.arc(cx, cy, scaledRadius, 0, Math.PI * 2)
|
|
25
|
+
ctx.closePath()
|
|
26
|
+
ctx.fill()
|
|
27
|
+
}
|
|
@@ -17,13 +17,29 @@ export function drawSoldermaskRingForRect(
|
|
|
17
17
|
realToCanvasMat: Matrix,
|
|
18
18
|
soldermaskColor: string,
|
|
19
19
|
padColor: string,
|
|
20
|
+
asymmetricMargins?: {
|
|
21
|
+
left: number
|
|
22
|
+
right: number
|
|
23
|
+
top: number
|
|
24
|
+
bottom: number
|
|
25
|
+
},
|
|
20
26
|
): void {
|
|
21
27
|
const [cx, cy] = applyToPoint(realToCanvasMat, [center.x, center.y])
|
|
22
28
|
const scaledWidth = width * Math.abs(realToCanvasMat.a)
|
|
23
29
|
const scaledHeight = height * Math.abs(realToCanvasMat.a)
|
|
24
|
-
const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a)
|
|
25
30
|
const scaledRadius = borderRadius * Math.abs(realToCanvasMat.a)
|
|
26
31
|
|
|
32
|
+
const ml = asymmetricMargins?.left ?? margin
|
|
33
|
+
const mr = asymmetricMargins?.right ?? margin
|
|
34
|
+
const mt = asymmetricMargins?.top ?? margin
|
|
35
|
+
const mb = asymmetricMargins?.bottom ?? margin
|
|
36
|
+
|
|
37
|
+
// Thickness of the soldermask ring (only if negative margin)
|
|
38
|
+
const scaledThicknessL = Math.max(0, -ml) * Math.abs(realToCanvasMat.a)
|
|
39
|
+
const scaledThicknessR = Math.max(0, -mr) * Math.abs(realToCanvasMat.a)
|
|
40
|
+
const scaledThicknessT = Math.max(0, -mt) * Math.abs(realToCanvasMat.a)
|
|
41
|
+
const scaledThicknessB = Math.max(0, -mb) * Math.abs(realToCanvasMat.a)
|
|
42
|
+
|
|
27
43
|
ctx.save()
|
|
28
44
|
ctx.translate(cx, cy)
|
|
29
45
|
|
|
@@ -76,16 +92,31 @@ export function drawSoldermaskRingForRect(
|
|
|
76
92
|
ctx.globalCompositeOperation = prevCompositeOp || "source-over"
|
|
77
93
|
}
|
|
78
94
|
|
|
79
|
-
// Restore pad color in inner rectangle (reduced by
|
|
80
|
-
const innerWidth =
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
// Restore pad color in inner rectangle (reduced by margins)
|
|
96
|
+
const innerWidth = Math.max(
|
|
97
|
+
0,
|
|
98
|
+
scaledWidth - (scaledThicknessL + scaledThicknessR),
|
|
99
|
+
)
|
|
100
|
+
const innerHeight = Math.max(
|
|
101
|
+
0,
|
|
102
|
+
scaledHeight - (scaledThicknessT + scaledThicknessB),
|
|
103
|
+
)
|
|
104
|
+
const innerRadius = Math.max(
|
|
105
|
+
0,
|
|
106
|
+
scaledRadius -
|
|
107
|
+
(scaledThicknessL +
|
|
108
|
+
scaledThicknessR +
|
|
109
|
+
scaledThicknessT +
|
|
110
|
+
scaledThicknessB) /
|
|
111
|
+
4,
|
|
112
|
+
)
|
|
83
113
|
|
|
84
114
|
if (innerWidth > 0 && innerHeight > 0) {
|
|
85
115
|
ctx.beginPath()
|
|
116
|
+
const x = -scaledWidth / 2 + scaledThicknessL
|
|
117
|
+
const y = -scaledHeight / 2 + scaledThicknessT
|
|
118
|
+
|
|
86
119
|
if (innerRadius > 0) {
|
|
87
|
-
const x = -innerWidth / 2
|
|
88
|
-
const y = -innerHeight / 2
|
|
89
120
|
const r = Math.min(innerRadius, innerWidth / 2, innerHeight / 2)
|
|
90
121
|
|
|
91
122
|
ctx.moveTo(x + r, y)
|
|
@@ -104,7 +135,7 @@ export function drawSoldermaskRingForRect(
|
|
|
104
135
|
ctx.lineTo(x, y + r)
|
|
105
136
|
ctx.arcTo(x, y, x + r, y, r)
|
|
106
137
|
} else {
|
|
107
|
-
ctx.rect(
|
|
138
|
+
ctx.rect(x, y, innerWidth, innerHeight)
|
|
108
139
|
}
|
|
109
140
|
|
|
110
141
|
ctx.fillStyle = padColor
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "circuit-to-canvas",
|
|
3
3
|
"main": "dist/index.js",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.51",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsup-node ./lib/index.ts --format esm --dts",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"@tscircuit/math-utils": "^0.0.29",
|
|
18
18
|
"@types/bun": "latest",
|
|
19
19
|
"bun-match-svg": "^0.0.14",
|
|
20
|
-
"circuit-json": "^0.0.
|
|
20
|
+
"circuit-json": "^0.0.356",
|
|
21
21
|
"circuit-json-to-connectivity-map": "^0.0.23",
|
|
22
22
|
"circuit-to-svg": "^0.0.303",
|
|
23
23
|
"looks-same": "^10.0.1",
|
|
Binary file
|
|
@@ -9,6 +9,7 @@ test("USB-C flashlight - comprehensive comparison (circuit-to-canvas vs circuit-
|
|
|
9
9
|
const stackedPng = await getStackedPngSvgComparison(circuitElements, {
|
|
10
10
|
width: 400,
|
|
11
11
|
height: 800,
|
|
12
|
+
showSoldermask: true,
|
|
12
13
|
})
|
|
13
14
|
|
|
14
15
|
await expect(stackedPng).toMatchPngSnapshot(import.meta.path)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { expect, test } from "bun:test"
|
|
2
2
|
import { createCanvas } from "@napi-rs/canvas"
|
|
3
|
-
import { CircuitToCanvasDrawer } from "../../lib/drawer"
|
|
4
3
|
import type { AnyCircuitElement } from "circuit-json"
|
|
4
|
+
import { CircuitToCanvasDrawer } from "../../lib/drawer"
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Comprehensive test for soldermask margin functionality:
|
|
@@ -1273,7 +1273,7 @@ test("comprehensive soldermask margin test", async () => {
|
|
|
1273
1273
|
]
|
|
1274
1274
|
|
|
1275
1275
|
drawer.setCameraBounds({ minX: -95, maxX: 90, minY: -22, maxY: 22 })
|
|
1276
|
-
drawer.drawElements(circuit)
|
|
1276
|
+
drawer.drawElements(circuit, { showSoldermask: true })
|
|
1277
1277
|
|
|
1278
1278
|
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
1279
1279
|
import.meta.path,
|
|
@@ -2,7 +2,7 @@ import { expect, test } from "bun:test"
|
|
|
2
2
|
import { createCanvas } from "@napi-rs/canvas"
|
|
3
3
|
import { CircuitToCanvasDrawer } from "../../lib/drawer"
|
|
4
4
|
|
|
5
|
-
test("draw holes with positive soldermask margins", async () => {
|
|
5
|
+
test("draw holes with positive and negative soldermask margins", async () => {
|
|
6
6
|
const canvas = createCanvas(800, 600)
|
|
7
7
|
const ctx = canvas.getContext("2d")
|
|
8
8
|
const drawer = new CircuitToCanvasDrawer(ctx)
|
|
@@ -29,6 +29,122 @@ test("draw holes with positive soldermask margins", async () => {
|
|
|
29
29
|
|
|
30
30
|
soldermask_margin: 0.2,
|
|
31
31
|
},
|
|
32
|
+
// Circle with negative margin
|
|
33
|
+
{
|
|
34
|
+
type: "pcb_hole",
|
|
35
|
+
pcb_hole_id: "hole_circle_negative",
|
|
36
|
+
hole_shape: "circle",
|
|
37
|
+
x: -4,
|
|
38
|
+
y: -2,
|
|
39
|
+
hole_diameter: 1.0,
|
|
40
|
+
|
|
41
|
+
soldermask_margin: -0.15,
|
|
42
|
+
},
|
|
43
|
+
// Square with positive margin
|
|
44
|
+
{
|
|
45
|
+
type: "pcb_hole",
|
|
46
|
+
pcb_hole_id: "hole_square_positive",
|
|
47
|
+
hole_shape: "square",
|
|
48
|
+
x: -1,
|
|
49
|
+
y: 2,
|
|
50
|
+
hole_diameter: 1.0,
|
|
51
|
+
|
|
52
|
+
soldermask_margin: 0.15,
|
|
53
|
+
},
|
|
54
|
+
// Square with negative margin
|
|
55
|
+
{
|
|
56
|
+
type: "pcb_hole",
|
|
57
|
+
pcb_hole_id: "hole_square_negative",
|
|
58
|
+
hole_shape: "square",
|
|
59
|
+
x: -1,
|
|
60
|
+
y: -2,
|
|
61
|
+
hole_diameter: 1.0,
|
|
62
|
+
|
|
63
|
+
soldermask_margin: -0.1,
|
|
64
|
+
},
|
|
65
|
+
// Oval with positive margin
|
|
66
|
+
{
|
|
67
|
+
type: "pcb_hole",
|
|
68
|
+
pcb_hole_id: "hole_oval_positive",
|
|
69
|
+
hole_shape: "oval",
|
|
70
|
+
x: 2,
|
|
71
|
+
y: 2,
|
|
72
|
+
hole_width: 1.5,
|
|
73
|
+
hole_height: 0.8,
|
|
74
|
+
|
|
75
|
+
soldermask_margin: 0.1,
|
|
76
|
+
},
|
|
77
|
+
// Oval with negative margin
|
|
78
|
+
{
|
|
79
|
+
type: "pcb_hole",
|
|
80
|
+
pcb_hole_id: "hole_oval_negative",
|
|
81
|
+
hole_shape: "oval",
|
|
82
|
+
x: 2,
|
|
83
|
+
y: -2,
|
|
84
|
+
hole_width: 1.5,
|
|
85
|
+
hole_height: 0.8,
|
|
86
|
+
|
|
87
|
+
soldermask_margin: -0.12,
|
|
88
|
+
},
|
|
89
|
+
// Rect with positive margin
|
|
90
|
+
{
|
|
91
|
+
type: "pcb_hole",
|
|
92
|
+
pcb_hole_id: "hole_rect_positive",
|
|
93
|
+
hole_shape: "rect",
|
|
94
|
+
x: 5,
|
|
95
|
+
y: 2,
|
|
96
|
+
hole_width: 1.5,
|
|
97
|
+
hole_height: 0.8,
|
|
98
|
+
|
|
99
|
+
soldermask_margin: 0.15,
|
|
100
|
+
},
|
|
101
|
+
// Pill with positive margin
|
|
102
|
+
{
|
|
103
|
+
type: "pcb_hole",
|
|
104
|
+
pcb_hole_id: "hole_pill_positive",
|
|
105
|
+
hole_shape: "pill",
|
|
106
|
+
x: -2.5,
|
|
107
|
+
y: 0,
|
|
108
|
+
hole_width: 1.5,
|
|
109
|
+
hole_height: 0.8,
|
|
110
|
+
|
|
111
|
+
soldermask_margin: 0.1,
|
|
112
|
+
},
|
|
113
|
+
// Pill with negative margin
|
|
114
|
+
{
|
|
115
|
+
type: "pcb_hole",
|
|
116
|
+
pcb_hole_id: "hole_pill_negative",
|
|
117
|
+
hole_shape: "pill",
|
|
118
|
+
x: -2.5,
|
|
119
|
+
y: -4,
|
|
120
|
+
hole_width: 1.5,
|
|
121
|
+
hole_height: 0.8,
|
|
122
|
+
|
|
123
|
+
soldermask_margin: -0.08,
|
|
124
|
+
},
|
|
125
|
+
// Pill with negative margin
|
|
126
|
+
{
|
|
127
|
+
type: "pcb_hole",
|
|
128
|
+
pcb_hole_id: "hole_pill_negative",
|
|
129
|
+
hole_shape: "pill",
|
|
130
|
+
x: -2.5,
|
|
131
|
+
y: -4,
|
|
132
|
+
hole_width: 1.5,
|
|
133
|
+
hole_height: 0.8,
|
|
134
|
+
|
|
135
|
+
soldermask_margin: -0.08,
|
|
136
|
+
},
|
|
137
|
+
// Circle with positive margin (mask extends beyond hole)
|
|
138
|
+
{
|
|
139
|
+
type: "pcb_hole",
|
|
140
|
+
pcb_hole_id: "hole_circle_positive",
|
|
141
|
+
hole_shape: "circle",
|
|
142
|
+
x: -4,
|
|
143
|
+
y: 2,
|
|
144
|
+
hole_diameter: 1.0,
|
|
145
|
+
|
|
146
|
+
soldermask_margin: 0.2,
|
|
147
|
+
},
|
|
32
148
|
// Square with positive margin
|
|
33
149
|
{
|
|
34
150
|
type: "pcb_hole",
|
|
@@ -122,10 +238,47 @@ test("draw holes with positive soldermask margins", async () => {
|
|
|
122
238
|
text: "+0.1mm",
|
|
123
239
|
font_size: 0.4,
|
|
124
240
|
},
|
|
241
|
+
// Labels for negative margin holes (bottom row)
|
|
242
|
+
{
|
|
243
|
+
type: "pcb_silkscreen_text",
|
|
244
|
+
pcb_silkscreen_text_id: "text_circle_neg",
|
|
245
|
+
layer: "top",
|
|
246
|
+
anchor_position: { x: -4, y: -0.8 },
|
|
247
|
+
anchor_alignment: "center",
|
|
248
|
+
text: "-0.15mm",
|
|
249
|
+
font_size: 0.4,
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
type: "pcb_silkscreen_text",
|
|
253
|
+
pcb_silkscreen_text_id: "text_square_neg",
|
|
254
|
+
layer: "top",
|
|
255
|
+
anchor_position: { x: -1, y: -0.8 },
|
|
256
|
+
anchor_alignment: "center",
|
|
257
|
+
text: "-0.1mm",
|
|
258
|
+
font_size: 0.4,
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
type: "pcb_silkscreen_text",
|
|
262
|
+
pcb_silkscreen_text_id: "text_oval_neg",
|
|
263
|
+
layer: "top",
|
|
264
|
+
anchor_position: { x: 2, y: -0.8 },
|
|
265
|
+
anchor_alignment: "center",
|
|
266
|
+
text: "-0.12mm",
|
|
267
|
+
font_size: 0.4,
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
type: "pcb_silkscreen_text",
|
|
271
|
+
pcb_silkscreen_text_id: "text_pill_neg",
|
|
272
|
+
layer: "top",
|
|
273
|
+
anchor_position: { x: -2.5, y: -2.8 },
|
|
274
|
+
anchor_alignment: "center",
|
|
275
|
+
text: "-0.08mm",
|
|
276
|
+
font_size: 0.4,
|
|
277
|
+
},
|
|
125
278
|
]
|
|
126
279
|
|
|
127
280
|
drawer.setCameraBounds({ minX: -7, maxX: 7, minY: -5, maxY: 5 })
|
|
128
|
-
drawer.drawElements(circuit)
|
|
281
|
+
drawer.drawElements(circuit, { showSoldermask: true })
|
|
129
282
|
|
|
130
283
|
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
131
284
|
import.meta.path,
|