circuit-to-canvas 0.0.29 → 0.0.31
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 +29 -3
- package/dist/index.js +415 -22
- package/lib/drawer/CircuitToCanvasDrawer.ts +119 -9
- package/lib/drawer/elements/index.ts +5 -0
- package/lib/drawer/elements/pcb-smtpad.ts +174 -0
- package/lib/drawer/elements/soldermask-margin.ts +266 -0
- package/lib/drawer/pcb-render-layer-filter.ts +30 -0
- package/package.json +3 -3
- package/tests/elements/__snapshots__/layer-filter.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-smtpad-soldermask-margin.snap.png +0 -0
- package/tests/elements/layer-filter.test.ts +42 -0
- package/tests/elements/pcb-smtpad-soldermask-margin.test.ts +163 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AnyCircuitElement } from "circuit-json"
|
|
2
|
+
import type { DrawElementsOptions } from "./CircuitToCanvasDrawer"
|
|
3
|
+
import { getElementRenderLayers } from "@tscircuit/circuit-json-util"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Gets the render layer(s) for an element based on its type and layer property
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Checks if an element should be drawn based on layer options
|
|
11
|
+
*/
|
|
12
|
+
export function shouldDrawElement(
|
|
13
|
+
element: AnyCircuitElement,
|
|
14
|
+
options: DrawElementsOptions,
|
|
15
|
+
): boolean {
|
|
16
|
+
// If no layers specified, draw all elements
|
|
17
|
+
if (!options.layers || options.layers.length === 0) {
|
|
18
|
+
return true
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const elementLayers = getElementRenderLayers(element)
|
|
22
|
+
|
|
23
|
+
// If element has no layer info (board, holes, etc.), always draw
|
|
24
|
+
if (elementLayers.length === 0) {
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Check if any of the element's layers match the requested layers
|
|
29
|
+
return elementLayers.some((layer) => options.layers!.includes(layer))
|
|
30
|
+
}
|
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.31",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsup-node ./lib/index.ts --format esm --dts",
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
"@napi-rs/canvas": "^0.1.84",
|
|
14
14
|
"@resvg/resvg-js": "^2.6.2",
|
|
15
15
|
"@tscircuit/alphabet": "^0.0.17",
|
|
16
|
-
"@tscircuit/circuit-json-util": "^0.0.
|
|
16
|
+
"@tscircuit/circuit-json-util": "^0.0.75",
|
|
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.346",
|
|
21
21
|
"circuit-json-to-connectivity-map": "^0.0.23",
|
|
22
22
|
"circuit-to-svg": "^0.0.297",
|
|
23
23
|
"looks-same": "^10.0.1",
|
|
Binary file
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { createCanvas } from "@napi-rs/canvas"
|
|
3
|
+
import type { PcbSmtPad } from "circuit-json"
|
|
4
|
+
import { CircuitToCanvasDrawer } from "../../lib/drawer"
|
|
5
|
+
|
|
6
|
+
test("layer filter only draws elements on specified layers", async () => {
|
|
7
|
+
const canvas = createCanvas(100, 100)
|
|
8
|
+
const ctx = canvas.getContext("2d")
|
|
9
|
+
const drawer = new CircuitToCanvasDrawer(ctx)
|
|
10
|
+
|
|
11
|
+
ctx.fillStyle = "#1a1a1a"
|
|
12
|
+
ctx.fillRect(0, 0, 100, 100)
|
|
13
|
+
|
|
14
|
+
const topPad: PcbSmtPad = {
|
|
15
|
+
type: "pcb_smtpad",
|
|
16
|
+
pcb_smtpad_id: "pad1",
|
|
17
|
+
shape: "rect",
|
|
18
|
+
x: 30,
|
|
19
|
+
y: 50,
|
|
20
|
+
width: 20,
|
|
21
|
+
height: 20,
|
|
22
|
+
layer: "top",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const bottomPad: PcbSmtPad = {
|
|
26
|
+
type: "pcb_smtpad",
|
|
27
|
+
pcb_smtpad_id: "pad2",
|
|
28
|
+
shape: "rect",
|
|
29
|
+
x: 70,
|
|
30
|
+
y: 50,
|
|
31
|
+
width: 20,
|
|
32
|
+
height: 20,
|
|
33
|
+
layer: "bottom",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Draw only top layer - should only show top pad (left side)
|
|
37
|
+
drawer.drawElements([topPad, bottomPad], { layers: ["top_copper"] })
|
|
38
|
+
|
|
39
|
+
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
40
|
+
import.meta.path,
|
|
41
|
+
)
|
|
42
|
+
})
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { createCanvas } from "@napi-rs/canvas"
|
|
3
|
+
import { CircuitToCanvasDrawer } from "../../lib/drawer"
|
|
4
|
+
|
|
5
|
+
test("draw smt pads with positive and negative soldermask margins", async () => {
|
|
6
|
+
const canvas = createCanvas(800, 600)
|
|
7
|
+
const ctx = canvas.getContext("2d")
|
|
8
|
+
const drawer = new CircuitToCanvasDrawer(ctx)
|
|
9
|
+
|
|
10
|
+
ctx.fillStyle = "#1a1a1a"
|
|
11
|
+
ctx.fillRect(0, 0, 800, 600)
|
|
12
|
+
|
|
13
|
+
const circuit: any = [
|
|
14
|
+
{
|
|
15
|
+
type: "pcb_board",
|
|
16
|
+
pcb_board_id: "board0",
|
|
17
|
+
center: { x: 0, y: 0 },
|
|
18
|
+
width: 14,
|
|
19
|
+
height: 10,
|
|
20
|
+
},
|
|
21
|
+
// Rectangle with positive margin (mask extends beyond pad)
|
|
22
|
+
{
|
|
23
|
+
type: "pcb_smtpad",
|
|
24
|
+
pcb_smtpad_id: "pad_rect_positive",
|
|
25
|
+
shape: "rect",
|
|
26
|
+
layer: "top",
|
|
27
|
+
x: -4,
|
|
28
|
+
y: 2,
|
|
29
|
+
width: 1.6,
|
|
30
|
+
height: 1.1,
|
|
31
|
+
is_covered_with_solder_mask: true,
|
|
32
|
+
soldermask_margin: 0.2,
|
|
33
|
+
},
|
|
34
|
+
// Rectangle with negative margin (spacing around copper, copper visible)
|
|
35
|
+
{
|
|
36
|
+
type: "pcb_smtpad",
|
|
37
|
+
pcb_smtpad_id: "pad_rect_negative",
|
|
38
|
+
shape: "rect",
|
|
39
|
+
layer: "top",
|
|
40
|
+
x: -4,
|
|
41
|
+
y: -2,
|
|
42
|
+
width: 1.6,
|
|
43
|
+
height: 1.1,
|
|
44
|
+
is_covered_with_solder_mask: true,
|
|
45
|
+
soldermask_margin: -0.15,
|
|
46
|
+
},
|
|
47
|
+
// Circle with positive margin
|
|
48
|
+
{
|
|
49
|
+
type: "pcb_smtpad",
|
|
50
|
+
pcb_smtpad_id: "pad_circle_positive",
|
|
51
|
+
shape: "circle",
|
|
52
|
+
layer: "top",
|
|
53
|
+
x: 0,
|
|
54
|
+
y: 2,
|
|
55
|
+
radius: 0.75,
|
|
56
|
+
is_covered_with_solder_mask: true,
|
|
57
|
+
soldermask_margin: 0.15,
|
|
58
|
+
},
|
|
59
|
+
// Circle with negative margin
|
|
60
|
+
{
|
|
61
|
+
type: "pcb_smtpad",
|
|
62
|
+
pcb_smtpad_id: "pad_circle_negative",
|
|
63
|
+
shape: "circle",
|
|
64
|
+
layer: "top",
|
|
65
|
+
x: 0,
|
|
66
|
+
y: -2,
|
|
67
|
+
radius: 0.75,
|
|
68
|
+
is_covered_with_solder_mask: true,
|
|
69
|
+
soldermask_margin: -0.2,
|
|
70
|
+
},
|
|
71
|
+
// Pill with positive margin
|
|
72
|
+
{
|
|
73
|
+
type: "pcb_smtpad",
|
|
74
|
+
pcb_smtpad_id: "pad_pill_positive",
|
|
75
|
+
shape: "pill",
|
|
76
|
+
layer: "top",
|
|
77
|
+
x: 4,
|
|
78
|
+
y: 2,
|
|
79
|
+
width: 2.4,
|
|
80
|
+
height: 1,
|
|
81
|
+
radius: 0.5,
|
|
82
|
+
is_covered_with_solder_mask: true,
|
|
83
|
+
soldermask_margin: 0.1,
|
|
84
|
+
},
|
|
85
|
+
// Pill with negative margin
|
|
86
|
+
{
|
|
87
|
+
type: "pcb_smtpad",
|
|
88
|
+
pcb_smtpad_id: "pad_pill_negative",
|
|
89
|
+
shape: "pill",
|
|
90
|
+
layer: "top",
|
|
91
|
+
x: 4,
|
|
92
|
+
y: -2,
|
|
93
|
+
width: 2.4,
|
|
94
|
+
height: 1,
|
|
95
|
+
radius: 0.5,
|
|
96
|
+
is_covered_with_solder_mask: true,
|
|
97
|
+
soldermask_margin: -0.12,
|
|
98
|
+
},
|
|
99
|
+
// Silkscreen labels for positive margin pads (top row)
|
|
100
|
+
{
|
|
101
|
+
type: "pcb_silkscreen_text",
|
|
102
|
+
pcb_silkscreen_text_id: "text_rect_pos",
|
|
103
|
+
layer: "top",
|
|
104
|
+
anchor_position: { x: -4, y: 3.2 },
|
|
105
|
+
anchor_alignment: "center",
|
|
106
|
+
text: "+0.2mm",
|
|
107
|
+
font_size: 0.4,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
type: "pcb_silkscreen_text",
|
|
111
|
+
pcb_silkscreen_text_id: "text_circle_pos",
|
|
112
|
+
layer: "top",
|
|
113
|
+
anchor_position: { x: 0, y: 3.2 },
|
|
114
|
+
anchor_alignment: "center",
|
|
115
|
+
text: "+0.15mm",
|
|
116
|
+
font_size: 0.4,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
type: "pcb_silkscreen_text",
|
|
120
|
+
pcb_silkscreen_text_id: "text_pill_pos",
|
|
121
|
+
layer: "top",
|
|
122
|
+
anchor_position: { x: 4, y: 3.2 },
|
|
123
|
+
anchor_alignment: "center",
|
|
124
|
+
text: "+0.1mm",
|
|
125
|
+
font_size: 0.4,
|
|
126
|
+
},
|
|
127
|
+
// Silkscreen labels for negative margin pads (bottom row)
|
|
128
|
+
{
|
|
129
|
+
type: "pcb_silkscreen_text",
|
|
130
|
+
pcb_silkscreen_text_id: "text_rect_neg",
|
|
131
|
+
layer: "top",
|
|
132
|
+
anchor_position: { x: -4, y: -3.2 },
|
|
133
|
+
anchor_alignment: "center",
|
|
134
|
+
text: "-0.15mm",
|
|
135
|
+
font_size: 0.4,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: "pcb_silkscreen_text",
|
|
139
|
+
pcb_silkscreen_text_id: "text_circle_neg",
|
|
140
|
+
layer: "top",
|
|
141
|
+
anchor_position: { x: 0, y: -3.2 },
|
|
142
|
+
anchor_alignment: "center",
|
|
143
|
+
text: "-0.2mm",
|
|
144
|
+
font_size: 0.4,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
type: "pcb_silkscreen_text",
|
|
148
|
+
pcb_silkscreen_text_id: "text_pill_neg",
|
|
149
|
+
layer: "top",
|
|
150
|
+
anchor_position: { x: 4, y: -3.2 },
|
|
151
|
+
anchor_alignment: "center",
|
|
152
|
+
text: "-0.12mm",
|
|
153
|
+
font_size: 0.4,
|
|
154
|
+
},
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
drawer.setCameraBounds({ minX: -7, maxX: 7, minY: -5, maxY: 5 })
|
|
158
|
+
drawer.drawElements(circuit)
|
|
159
|
+
|
|
160
|
+
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
161
|
+
import.meta.path,
|
|
162
|
+
)
|
|
163
|
+
})
|