circuit-to-canvas 0.0.47 → 0.0.48

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 CHANGED
@@ -8,10 +8,10 @@ import { Matrix } from 'transformation-matrix';
8
8
  interface CanvasContext {
9
9
  beginPath(): void;
10
10
  closePath(): void;
11
- arc(x: number, y: number, radius: number, startAngle: number, endAngle: number): void;
11
+ arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, counterclockwise?: boolean): void;
12
12
  arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void;
13
13
  ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number): void;
14
- fill(): void;
14
+ fill(fillRule?: "nonzero" | "evenodd"): void;
15
15
  stroke(): void;
16
16
  rect(x: number, y: number, w: number, h: number): void;
17
17
  lineTo(x: number, y: number): void;
@@ -25,6 +25,7 @@ interface CanvasContext {
25
25
  globalCompositeOperation?: string;
26
26
  fillStyle: string | CanvasGradient | CanvasPattern;
27
27
  strokeStyle: string | CanvasGradient | CanvasPattern;
28
+ globalAlpha: number;
28
29
  lineWidth: number;
29
30
  lineCap: "butt" | "round" | "square";
30
31
  lineJoin: "bevel" | "round" | "miter";
package/dist/index.js CHANGED
@@ -2154,6 +2154,128 @@ import { applyToPoint as applyToPoint12 } from "transformation-matrix";
2154
2154
  function layerToColor3(layer, colorMap) {
2155
2155
  return colorMap.copper[layer] ?? colorMap.copper.top;
2156
2156
  }
2157
+ function computeArcFromBulge(startX, startY, endX, endY, bulge) {
2158
+ if (Math.abs(bulge) < 1e-10) {
2159
+ return null;
2160
+ }
2161
+ const chordX = endX - startX;
2162
+ const chordY = endY - startY;
2163
+ const chordLength = Math.hypot(chordX, chordY);
2164
+ if (chordLength < 1e-10) {
2165
+ return null;
2166
+ }
2167
+ const sagitta = Math.abs(bulge) * (chordLength / 2);
2168
+ const halfChord = chordLength / 2;
2169
+ const radius = (sagitta * sagitta + halfChord * halfChord) / (2 * sagitta);
2170
+ const distToCenter = radius - sagitta;
2171
+ const midX = (startX + endX) / 2;
2172
+ const midY = (startY + endY) / 2;
2173
+ const perpX = -chordY / chordLength;
2174
+ const perpY = chordX / chordLength;
2175
+ const sign = bulge > 0 ? -1 : 1;
2176
+ const centerX = midX + sign * perpX * distToCenter;
2177
+ const centerY = midY + sign * perpY * distToCenter;
2178
+ return { centerX, centerY, radius };
2179
+ }
2180
+ function drawArcFromBulge(ctx, realStartX, realStartY, realEndX, realEndY, bulge, realToCanvasMat) {
2181
+ if (Math.abs(bulge) < 1e-10) {
2182
+ const [endX, endY] = applyToPoint12(realToCanvasMat, [realEndX, realEndY]);
2183
+ ctx.lineTo(endX, endY);
2184
+ return;
2185
+ }
2186
+ const arc = computeArcFromBulge(
2187
+ realStartX,
2188
+ realStartY,
2189
+ realEndX,
2190
+ realEndY,
2191
+ bulge
2192
+ );
2193
+ if (!arc) {
2194
+ const [endX, endY] = applyToPoint12(realToCanvasMat, [realEndX, realEndY]);
2195
+ ctx.lineTo(endX, endY);
2196
+ return;
2197
+ }
2198
+ const [canvasStartX, canvasStartY] = applyToPoint12(realToCanvasMat, [
2199
+ realStartX,
2200
+ realStartY
2201
+ ]);
2202
+ const [canvasEndX, canvasEndY] = applyToPoint12(realToCanvasMat, [
2203
+ realEndX,
2204
+ realEndY
2205
+ ]);
2206
+ const [canvasCenterX, canvasCenterY] = applyToPoint12(realToCanvasMat, [
2207
+ arc.centerX,
2208
+ arc.centerY
2209
+ ]);
2210
+ const canvasRadius = Math.hypot(
2211
+ canvasStartX - canvasCenterX,
2212
+ canvasStartY - canvasCenterY
2213
+ );
2214
+ const startAngle = Math.atan2(
2215
+ canvasStartY - canvasCenterY,
2216
+ canvasStartX - canvasCenterX
2217
+ );
2218
+ const endAngle = Math.atan2(
2219
+ canvasEndY - canvasCenterY,
2220
+ canvasEndX - canvasCenterX
2221
+ );
2222
+ const det = realToCanvasMat.a * realToCanvasMat.d - realToCanvasMat.b * realToCanvasMat.c;
2223
+ const isFlipped = det < 0;
2224
+ const counterclockwise = bulge > 0 ? !isFlipped : isFlipped;
2225
+ ctx.arc(
2226
+ canvasCenterX,
2227
+ canvasCenterY,
2228
+ canvasRadius,
2229
+ startAngle,
2230
+ endAngle,
2231
+ counterclockwise
2232
+ );
2233
+ }
2234
+ function drawRing(ctx, ring, realToCanvasMat) {
2235
+ if (ring.vertices.length < 2) return;
2236
+ if (ring.vertices.length === 2) {
2237
+ const v0 = ring.vertices[0];
2238
+ const v1 = ring.vertices[1];
2239
+ if (v0 && v1 && Math.abs((v0.bulge ?? 0) - 1) < 1e-10 && Math.abs((v1.bulge ?? 0) - 1) < 1e-10) {
2240
+ const [x0, y0] = applyToPoint12(realToCanvasMat, [v0.x, v0.y]);
2241
+ ctx.moveTo(x0, y0);
2242
+ drawArcFromBulge(ctx, v0.x, v0.y, v1.x, v1.y, 1, realToCanvasMat);
2243
+ drawArcFromBulge(ctx, v1.x, v1.y, v0.x, v0.y, 1, realToCanvasMat);
2244
+ return;
2245
+ }
2246
+ }
2247
+ const firstVertex = ring.vertices[0];
2248
+ if (!firstVertex) return;
2249
+ const [firstX, firstY] = applyToPoint12(realToCanvasMat, [
2250
+ firstVertex.x,
2251
+ firstVertex.y
2252
+ ]);
2253
+ ctx.moveTo(firstX, firstY);
2254
+ for (let i = 0; i < ring.vertices.length; i++) {
2255
+ const currentVertex = ring.vertices[i];
2256
+ const nextIndex = (i + 1) % ring.vertices.length;
2257
+ const nextVertex = ring.vertices[nextIndex];
2258
+ if (!currentVertex || !nextVertex) continue;
2259
+ const bulge = currentVertex.bulge ?? 0;
2260
+ if (Math.abs(bulge) < 1e-10) {
2261
+ const [nextX, nextY] = applyToPoint12(realToCanvasMat, [
2262
+ nextVertex.x,
2263
+ nextVertex.y
2264
+ ]);
2265
+ ctx.lineTo(nextX, nextY);
2266
+ } else {
2267
+ drawArcFromBulge(
2268
+ ctx,
2269
+ currentVertex.x,
2270
+ currentVertex.y,
2271
+ nextVertex.x,
2272
+ nextVertex.y,
2273
+ bulge,
2274
+ realToCanvasMat
2275
+ );
2276
+ }
2277
+ }
2278
+ }
2157
2279
  function drawPcbCopperPour(params) {
2158
2280
  const { ctx, pour, realToCanvasMat, colorMap } = params;
2159
2281
  const color = layerToColor3(pour.layer, colorMap);
@@ -2204,6 +2326,20 @@ function drawPcbCopperPour(params) {
2204
2326
  ctx.restore();
2205
2327
  return;
2206
2328
  }
2329
+ if (pour.shape === "brep") {
2330
+ ctx.beginPath();
2331
+ drawRing(ctx, pour.brep_shape.outer_ring, realToCanvasMat);
2332
+ if (pour.brep_shape.inner_rings) {
2333
+ for (const innerRing of pour.brep_shape.inner_rings) {
2334
+ drawRing(ctx, innerRing, realToCanvasMat);
2335
+ }
2336
+ }
2337
+ ctx.fillStyle = color;
2338
+ ctx.globalAlpha = 0.5;
2339
+ ctx.fill("evenodd");
2340
+ ctx.restore();
2341
+ return;
2342
+ }
2207
2343
  ctx.restore();
2208
2344
  }
2209
2345
 
@@ -4,6 +4,7 @@ import { applyToPoint } from "transformation-matrix"
4
4
  import type { PcbColorMap, CanvasContext } from "../types"
5
5
  import { drawRect } from "../shapes/rect"
6
6
  import { drawPolygon } from "../shapes/polygon"
7
+ import type { Ring } from "circuit-json"
7
8
 
8
9
  export interface DrawPcbCopperPourParams {
9
10
  ctx: CanvasContext
@@ -19,6 +20,219 @@ function layerToColor(layer: string, colorMap: PcbColorMap): string {
19
20
  )
20
21
  }
21
22
 
23
+ /**
24
+ * Compute arc center and radius from two points and a bulge value.
25
+ * Bulge is the tangent of 1/4 of the included angle.
26
+ * - bulge = 0: straight line
27
+ * - bulge = 1: semicircle (180 degrees)
28
+ * - bulge > 0: arc bulges to the left of the direction from start to end
29
+ * - bulge < 0: arc bulges to the right
30
+ */
31
+ function computeArcFromBulge(
32
+ startX: number,
33
+ startY: number,
34
+ endX: number,
35
+ endY: number,
36
+ bulge: number,
37
+ ): { centerX: number; centerY: number; radius: number } | null {
38
+ if (Math.abs(bulge) < 1e-10) {
39
+ return null
40
+ }
41
+
42
+ // Calculate chord vector and length
43
+ const chordX = endX - startX
44
+ const chordY = endY - startY
45
+ const chordLength = Math.hypot(chordX, chordY)
46
+
47
+ if (chordLength < 1e-10) {
48
+ return null
49
+ }
50
+
51
+ // The sagitta (bulge height) is: s = bulge * (chord_length / 2)
52
+ // This is because bulge = tan(theta/4) and s = r - r*cos(theta/2)
53
+ // After simplification: s = (chord/2) * tan(theta/4) = (chord/2) * bulge
54
+ const sagitta = Math.abs(bulge) * (chordLength / 2)
55
+
56
+ // Calculate radius from sagitta and chord:
57
+ // From geometry: r = (s^2 + (chord/2)^2) / (2*s)
58
+ const halfChord = chordLength / 2
59
+ const radius = (sagitta * sagitta + halfChord * halfChord) / (2 * sagitta)
60
+
61
+ // Distance from chord midpoint to center
62
+ const distToCenter = radius - sagitta
63
+
64
+ // Midpoint of the chord
65
+ const midX = (startX + endX) / 2
66
+ const midY = (startY + endY) / 2
67
+
68
+ // Unit vector perpendicular to chord
69
+ // "Left" of the direction from start to end (in standard Y-up coords)
70
+ const perpX = -chordY / chordLength
71
+ const perpY = chordX / chordLength
72
+
73
+ // For positive bulge, arc bulges left, so center is to the RIGHT of chord direction
74
+ // For negative bulge, arc bulges right, so center is to the LEFT of chord direction
75
+ const sign = bulge > 0 ? -1 : 1
76
+ const centerX = midX + sign * perpX * distToCenter
77
+ const centerY = midY + sign * perpY * distToCenter
78
+
79
+ return { centerX, centerY, radius }
80
+ }
81
+
82
+ /**
83
+ * Draws an arc between two points given a bulge value.
84
+ * Coordinates are in canvas space, but we need the original real-space points
85
+ * to correctly compute the arc direction.
86
+ */
87
+ function drawArcFromBulge(
88
+ ctx: CanvasContext,
89
+ realStartX: number,
90
+ realStartY: number,
91
+ realEndX: number,
92
+ realEndY: number,
93
+ bulge: number,
94
+ realToCanvasMat: Matrix,
95
+ ): void {
96
+ if (Math.abs(bulge) < 1e-10) {
97
+ const [endX, endY] = applyToPoint(realToCanvasMat, [realEndX, realEndY])
98
+ ctx.lineTo(endX, endY)
99
+ return
100
+ }
101
+
102
+ // Compute arc in real coordinates
103
+ const arc = computeArcFromBulge(
104
+ realStartX,
105
+ realStartY,
106
+ realEndX,
107
+ realEndY,
108
+ bulge,
109
+ )
110
+
111
+ if (!arc) {
112
+ const [endX, endY] = applyToPoint(realToCanvasMat, [realEndX, realEndY])
113
+ ctx.lineTo(endX, endY)
114
+ return
115
+ }
116
+
117
+ // Transform all points to canvas coordinates
118
+ const [canvasStartX, canvasStartY] = applyToPoint(realToCanvasMat, [
119
+ realStartX,
120
+ realStartY,
121
+ ])
122
+ const [canvasEndX, canvasEndY] = applyToPoint(realToCanvasMat, [
123
+ realEndX,
124
+ realEndY,
125
+ ])
126
+ const [canvasCenterX, canvasCenterY] = applyToPoint(realToCanvasMat, [
127
+ arc.centerX,
128
+ arc.centerY,
129
+ ])
130
+
131
+ // Calculate radius in canvas space (may be scaled)
132
+ const canvasRadius = Math.hypot(
133
+ canvasStartX - canvasCenterX,
134
+ canvasStartY - canvasCenterY,
135
+ )
136
+
137
+ // Calculate start and end angles in canvas space
138
+ const startAngle = Math.atan2(
139
+ canvasStartY - canvasCenterY,
140
+ canvasStartX - canvasCenterX,
141
+ )
142
+ const endAngle = Math.atan2(
143
+ canvasEndY - canvasCenterY,
144
+ canvasEndX - canvasCenterX,
145
+ )
146
+
147
+ // Determine arc direction
148
+ // In real coords: positive bulge = counterclockwise (left-hand rule)
149
+ // The transformation may flip Y, which reverses the apparent direction
150
+ // Check if transformation flips by looking at the determinant (a*d - b*c)
151
+ const det =
152
+ realToCanvasMat.a * realToCanvasMat.d -
153
+ realToCanvasMat.b * realToCanvasMat.c
154
+ const isFlipped = det < 0
155
+
156
+ // Positive bulge in real coords = counterclockwise in real coords
157
+ // If flipped, counterclockwise becomes clockwise in canvas coords
158
+ const counterclockwise = bulge > 0 ? !isFlipped : isFlipped
159
+
160
+ ctx.arc(
161
+ canvasCenterX,
162
+ canvasCenterY,
163
+ canvasRadius,
164
+ startAngle,
165
+ endAngle,
166
+ counterclockwise,
167
+ )
168
+ }
169
+
170
+ function drawRing(
171
+ ctx: CanvasContext,
172
+ ring: Ring,
173
+ realToCanvasMat: Matrix,
174
+ ): void {
175
+ if (ring.vertices.length < 2) return
176
+
177
+ // Special case: a circle defined by 2 vertices with bulge=1 each
178
+ if (ring.vertices.length === 2) {
179
+ const v0 = ring.vertices[0]
180
+ const v1 = ring.vertices[1]
181
+ if (
182
+ v0 &&
183
+ v1 &&
184
+ Math.abs((v0.bulge ?? 0) - 1) < 1e-10 &&
185
+ Math.abs((v1.bulge ?? 0) - 1) < 1e-10
186
+ ) {
187
+ // This is a full circle - draw two semicircles
188
+ const [x0, y0] = applyToPoint(realToCanvasMat, [v0.x, v0.y])
189
+
190
+ ctx.moveTo(x0, y0)
191
+ drawArcFromBulge(ctx, v0.x, v0.y, v1.x, v1.y, 1, realToCanvasMat)
192
+ drawArcFromBulge(ctx, v1.x, v1.y, v0.x, v0.y, 1, realToCanvasMat)
193
+ return
194
+ }
195
+ }
196
+
197
+ const firstVertex = ring.vertices[0]
198
+ if (!firstVertex) return
199
+ const [firstX, firstY] = applyToPoint(realToCanvasMat, [
200
+ firstVertex.x,
201
+ firstVertex.y,
202
+ ])
203
+ ctx.moveTo(firstX, firstY)
204
+
205
+ for (let i = 0; i < ring.vertices.length; i++) {
206
+ const currentVertex = ring.vertices[i]
207
+ const nextIndex = (i + 1) % ring.vertices.length
208
+ const nextVertex = ring.vertices[nextIndex]
209
+
210
+ if (!currentVertex || !nextVertex) continue
211
+
212
+ const bulge = currentVertex.bulge ?? 0
213
+
214
+ if (Math.abs(bulge) < 1e-10) {
215
+ // No bulge, draw straight line
216
+ const [nextX, nextY] = applyToPoint(realToCanvasMat, [
217
+ nextVertex.x,
218
+ nextVertex.y,
219
+ ])
220
+ ctx.lineTo(nextX, nextY)
221
+ } else {
222
+ // Draw arc based on bulge value (pass real coordinates)
223
+ drawArcFromBulge(
224
+ ctx,
225
+ currentVertex.x,
226
+ currentVertex.y,
227
+ nextVertex.x,
228
+ nextVertex.y,
229
+ bulge,
230
+ realToCanvasMat,
231
+ )
232
+ }
233
+ }
234
+ }
235
+
22
236
  export function drawPcbCopperPour(params: DrawPcbCopperPourParams): void {
23
237
  const { ctx, pour, realToCanvasMat, colorMap } = params
24
238
 
@@ -45,7 +259,7 @@ export function drawPcbCopperPour(params: DrawPcbCopperPourParams): void {
45
259
  ctx.beginPath()
46
260
  ctx.rect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight)
47
261
  ctx.fillStyle = color
48
- ;(ctx as any).globalAlpha = 0.5
262
+ ctx.globalAlpha = 0.5
49
263
  ctx.fill()
50
264
  ctx.restore()
51
265
  return
@@ -76,12 +290,31 @@ export function drawPcbCopperPour(params: DrawPcbCopperPourParams): void {
76
290
 
77
291
  ctx.closePath()
78
292
  ctx.fillStyle = color
79
- ;(ctx as any).globalAlpha = 0.5
293
+ ctx.globalAlpha = 0.5
80
294
  ctx.fill()
81
295
  }
82
296
  ctx.restore()
83
297
  return
84
298
  }
85
299
 
300
+ if (pour.shape === "brep") {
301
+ ctx.beginPath()
302
+ // Draw outer ring
303
+ drawRing(ctx, pour.brep_shape.outer_ring, realToCanvasMat)
304
+
305
+ // Draw inner rings (holes) - use evenodd fill rule to create holes
306
+ if (pour.brep_shape.inner_rings) {
307
+ for (const innerRing of pour.brep_shape.inner_rings) {
308
+ drawRing(ctx, innerRing, realToCanvasMat)
309
+ }
310
+ }
311
+
312
+ ctx.fillStyle = color
313
+ ctx.globalAlpha = 0.5
314
+ ctx.fill("evenodd")
315
+ ctx.restore()
316
+ return
317
+ }
318
+
86
319
  ctx.restore()
87
320
  }
@@ -13,6 +13,7 @@ export interface CanvasContext {
13
13
  radius: number,
14
14
  startAngle: number,
15
15
  endAngle: number,
16
+ counterclockwise?: boolean,
16
17
  ): void
17
18
  arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void
18
19
  ellipse(
@@ -24,7 +25,7 @@ export interface CanvasContext {
24
25
  startAngle: number,
25
26
  endAngle: number,
26
27
  ): void
27
- fill(): void
28
+ fill(fillRule?: "nonzero" | "evenodd"): void
28
29
  stroke(): void
29
30
  rect(x: number, y: number, w: number, h: number): void
30
31
  lineTo(x: number, y: number): void
@@ -38,6 +39,7 @@ export interface CanvasContext {
38
39
  globalCompositeOperation?: string
39
40
  fillStyle: string | CanvasGradient | CanvasPattern
40
41
  strokeStyle: string | CanvasGradient | CanvasPattern
42
+ globalAlpha: number
41
43
  lineWidth: number
42
44
  lineCap: "butt" | "round" | "square"
43
45
  lineJoin: "bevel" | "round" | "miter"
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.47",
4
+ "version": "0.0.48",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsup-node ./lib/index.ts --format esm --dts",
@@ -119,3 +119,174 @@ test("draw copper pour with trace", async () => {
119
119
  "copper-pour-with-trace",
120
120
  )
121
121
  })
122
+
123
+ test("draw brep copper pours", async () => {
124
+ const canvas = createCanvas(200, 200)
125
+ const ctx = canvas.getContext("2d")
126
+ const drawer = new CircuitToCanvasDrawer(ctx)
127
+
128
+ // Set camera bounds to fit all elements
129
+ drawer.setCameraBounds({
130
+ minX: -100,
131
+ maxX: 100,
132
+ minY: -50,
133
+ maxY: 50,
134
+ })
135
+
136
+ ctx.fillStyle = "#1a1a1a"
137
+ ctx.fillRect(0, 0, 200, 200)
138
+
139
+ const soup: any[] = [
140
+ {
141
+ type: "pcb_board",
142
+ pcb_board_id: "board1",
143
+ center: { x: 0, y: 0 },
144
+ width: 200,
145
+ height: 100,
146
+ material: "fr4",
147
+ num_layers: 2,
148
+ thickness: 1.6,
149
+ },
150
+ // pour_brep_1: square with rounded-square hole
151
+ {
152
+ type: "pcb_copper_pour",
153
+ pcb_copper_pour_id: "pour_brep_1",
154
+ layer: "top",
155
+ shape: "brep",
156
+ source_net_id: "net1",
157
+ brep_shape: {
158
+ outer_ring: {
159
+ vertices: [
160
+ { x: -30, y: 30 },
161
+ { x: -50, y: 30 },
162
+ { x: -50, y: 10 },
163
+ { x: -30, y: 10 },
164
+ ],
165
+ },
166
+ inner_rings: [
167
+ {
168
+ vertices: [
169
+ { x: -35, y: 25, bulge: 0.5 },
170
+ { x: -45, y: 25 },
171
+ { x: -45, y: 15 },
172
+ { x: -35, y: 15 },
173
+ ],
174
+ },
175
+ ],
176
+ },
177
+ } as PcbCopperPour,
178
+ // pour_brep_2: Bulgy outer ring, two holes
179
+ {
180
+ type: "pcb_copper_pour",
181
+ pcb_copper_pour_id: "pour_brep_2",
182
+ layer: "top",
183
+ shape: "brep",
184
+ source_net_id: "net2",
185
+ brep_shape: {
186
+ outer_ring: {
187
+ vertices: [
188
+ { x: 10, y: 30, bulge: -0.5 },
189
+ { x: -10, y: 30 },
190
+ { x: -10, y: 10, bulge: 0.5 },
191
+ { x: 10, y: 10 },
192
+ ],
193
+ },
194
+ inner_rings: [
195
+ {
196
+ // square hole
197
+ vertices: [
198
+ { x: -5, y: 25 },
199
+ { x: -8, y: 25 },
200
+ { x: -8, y: 22 },
201
+ { x: -5, y: 22 },
202
+ ],
203
+ },
204
+ {
205
+ // triangular hole
206
+ vertices: [
207
+ { x: 5, y: 25 },
208
+ { x: 8, y: 22 },
209
+ { x: 5, y: 22 },
210
+ ],
211
+ },
212
+ ],
213
+ },
214
+ } as PcbCopperPour,
215
+ // pour_brep_3: Circular pour with square hole
216
+ {
217
+ type: "pcb_copper_pour",
218
+ pcb_copper_pour_id: "pour_brep_3",
219
+ layer: "top",
220
+ shape: "brep",
221
+ source_net_id: "net3",
222
+ brep_shape: {
223
+ outer_ring: {
224
+ vertices: [
225
+ { x: 30, y: 20, bulge: 1 },
226
+ { x: 50, y: 20, bulge: 1 },
227
+ ],
228
+ },
229
+ inner_rings: [
230
+ {
231
+ vertices: [
232
+ { x: 38, y: 22 },
233
+ { x: 42, y: 22 },
234
+ { x: 42, y: 18 },
235
+ { x: 38, y: 18 },
236
+ ],
237
+ },
238
+ ],
239
+ },
240
+ } as PcbCopperPour,
241
+ // pour_brep_4: bottom layer pour
242
+ {
243
+ type: "pcb_copper_pour",
244
+ pcb_copper_pour_id: "pour_brep_4",
245
+ layer: "bottom",
246
+ shape: "brep",
247
+ source_net_id: "net4",
248
+ brep_shape: {
249
+ outer_ring: {
250
+ vertices: [
251
+ { x: -30, y: -10 },
252
+ { x: -50, y: -10 },
253
+ { x: -50, y: -30 },
254
+ { x: -30, y: -30, bulge: 0.5 },
255
+ ],
256
+ },
257
+ },
258
+ } as PcbCopperPour,
259
+ // pour_rect_1: A rect pour with rotation
260
+ {
261
+ type: "pcb_copper_pour",
262
+ pcb_copper_pour_id: "pour_rect_1",
263
+ layer: "top",
264
+ shape: "rect",
265
+ source_net_id: "net5",
266
+ center: { x: 0, y: -20 },
267
+ width: 20,
268
+ height: 10,
269
+ rotation: 15,
270
+ } as PcbCopperPour,
271
+ // pour_polygon_1: A polygon pour (triangle)
272
+ {
273
+ type: "pcb_copper_pour",
274
+ pcb_copper_pour_id: "pour_polygon_1",
275
+ layer: "top",
276
+ shape: "polygon",
277
+ source_net_id: "net6",
278
+ points: [
279
+ { x: 30, y: -10 },
280
+ { x: 50, y: -30 },
281
+ { x: 30, y: -30 },
282
+ ],
283
+ } as PcbCopperPour,
284
+ ]
285
+
286
+ drawer.drawElements(soup)
287
+
288
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
289
+ import.meta.path,
290
+ "brep-copper-pours",
291
+ )
292
+ })