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.
@@ -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.29",
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.73",
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.342",
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",
@@ -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
+ })