circuit-to-canvas 0.0.50 → 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 +7 -4
- package/dist/index.js +1425 -1240
- 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 +3 -292
- 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/package.json +1 -1
- 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 +1 -1
- package/tests/elements/pcb-smtpad-soldermask-coverage.test.ts +1 -1
- package/tests/elements/pcb-smtpad-soldermask-margin.test.ts +1 -1
- package/tests/fixtures/getStackedPngSvgComparison.ts +8 -2
|
@@ -72,9 +72,12 @@ import { drawPcbNoteText } from "./elements/pcb-note-text"
|
|
|
72
72
|
import { drawPcbNoteDimension } from "./elements/pcb-note-dimension"
|
|
73
73
|
import { drawPcbFabricationNoteDimension } from "./elements/pcb-fabrication-note-dimension"
|
|
74
74
|
import { drawPcbNoteLine } from "./elements/pcb-note-line"
|
|
75
|
+
import { drawPcbSoldermask } from "./elements/pcb-soldermask"
|
|
75
76
|
|
|
76
77
|
export interface DrawElementsOptions {
|
|
77
78
|
layers?: PcbRenderLayer[]
|
|
79
|
+
/** Whether to render the soldermask layer. Defaults to false. */
|
|
80
|
+
showSoldermask?: boolean
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
interface CanvasLike {
|
|
@@ -162,358 +165,305 @@ export class CircuitToCanvasDrawer {
|
|
|
162
165
|
elements: AnyCircuitElement[],
|
|
163
166
|
options: DrawElementsOptions = {},
|
|
164
167
|
): void {
|
|
165
|
-
//
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
for (const element of elements) {
|
|
185
|
-
if (element.type === "pcb_board") {
|
|
186
|
-
this.drawBoardWithSoldermask(element as PcbBoard)
|
|
187
|
-
} else {
|
|
188
|
-
this.drawElement(element, options)
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
private drawBoardWithSoldermask(board: PcbBoard): void {
|
|
194
|
-
const { width, height, center, outline } = board
|
|
195
|
-
const layer = "top" // Default to top layer for soldermask color
|
|
196
|
-
|
|
197
|
-
// If the board has a custom outline, draw it as a path with soldermask fill
|
|
198
|
-
if (outline && Array.isArray(outline) && outline.length >= 3) {
|
|
199
|
-
const soldermaskColor =
|
|
200
|
-
this.colorMap.soldermask[
|
|
201
|
-
layer as keyof typeof this.colorMap.soldermask
|
|
202
|
-
] ?? this.colorMap.soldermask.top
|
|
203
|
-
|
|
204
|
-
// Draw filled path
|
|
205
|
-
const canvasPoints = outline.map((p) => {
|
|
206
|
-
const [x, y] = applyToPoint(this.realToCanvasMat, [p.x, p.y])
|
|
207
|
-
return { x, y }
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
this.ctx.beginPath()
|
|
211
|
-
const firstPoint = canvasPoints[0]
|
|
212
|
-
if (firstPoint) {
|
|
213
|
-
this.ctx.moveTo(firstPoint.x, firstPoint.y)
|
|
214
|
-
for (let i = 1; i < canvasPoints.length; i++) {
|
|
215
|
-
const point = canvasPoints[i]
|
|
216
|
-
if (point) {
|
|
217
|
-
this.ctx.lineTo(point.x, point.y)
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
this.ctx.closePath()
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
this.ctx.fillStyle = soldermaskColor
|
|
224
|
-
this.ctx.fill()
|
|
225
|
-
|
|
226
|
-
// Draw outline stroke
|
|
227
|
-
drawPath({
|
|
228
|
-
ctx: this.ctx,
|
|
229
|
-
points: outline.map((p) => ({ x: p.x, y: p.y })),
|
|
230
|
-
stroke: this.colorMap.boardOutline,
|
|
231
|
-
strokeWidth: 0.1,
|
|
232
|
-
realToCanvasMat: this.realToCanvasMat,
|
|
233
|
-
closePath: true,
|
|
234
|
-
})
|
|
235
|
-
return
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Otherwise draw a rectangle with soldermask fill
|
|
239
|
-
if (width !== undefined && height !== undefined && center) {
|
|
240
|
-
const soldermaskColor =
|
|
241
|
-
this.colorMap.soldermask[
|
|
242
|
-
layer as keyof typeof this.colorMap.soldermask
|
|
243
|
-
] ?? this.colorMap.soldermask.top
|
|
244
|
-
|
|
245
|
-
// Draw filled rectangle
|
|
246
|
-
drawRect({
|
|
247
|
-
ctx: this.ctx,
|
|
248
|
-
center,
|
|
249
|
-
width,
|
|
250
|
-
height,
|
|
251
|
-
fill: soldermaskColor,
|
|
252
|
-
realToCanvasMat: this.realToCanvasMat,
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
// Draw the outline stroke separately using path
|
|
256
|
-
const halfWidth = width / 2
|
|
257
|
-
const halfHeight = height / 2
|
|
258
|
-
const corners = [
|
|
259
|
-
{ x: center.x - halfWidth, y: center.y - halfHeight },
|
|
260
|
-
{ x: center.x + halfWidth, y: center.y - halfHeight },
|
|
261
|
-
{ x: center.x + halfWidth, y: center.y + halfHeight },
|
|
262
|
-
{ x: center.x - halfWidth, y: center.y + halfHeight },
|
|
263
|
-
]
|
|
264
|
-
|
|
265
|
-
drawPath({
|
|
266
|
-
ctx: this.ctx,
|
|
267
|
-
points: corners,
|
|
268
|
-
stroke: this.colorMap.boardOutline,
|
|
269
|
-
strokeWidth: 0.1,
|
|
270
|
-
realToCanvasMat: this.realToCanvasMat,
|
|
271
|
-
closePath: true,
|
|
272
|
-
})
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
private drawElement(
|
|
277
|
-
element: AnyCircuitElement,
|
|
278
|
-
options: DrawElementsOptions,
|
|
279
|
-
): void {
|
|
280
|
-
// Check if element should be drawn based on layer options
|
|
281
|
-
if (!shouldDrawElement(element, options)) {
|
|
282
|
-
return
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (element.type === "pcb_plated_hole") {
|
|
286
|
-
drawPcbPlatedHole({
|
|
168
|
+
// Find the board element
|
|
169
|
+
const board = elements.find((el) => el.type === "pcb_board") as
|
|
170
|
+
| PcbBoard
|
|
171
|
+
| undefined
|
|
172
|
+
|
|
173
|
+
// Drawing order:
|
|
174
|
+
// 1. Board outline (just the outline stroke, no fill)
|
|
175
|
+
// 2. Copper elements underneath soldermask (pads, copper text)
|
|
176
|
+
// 3. Soldermask (covers everything except openings)
|
|
177
|
+
// 4. Silkscreen (on soldermask, under top copper layers)
|
|
178
|
+
// 5. Copper pour and traces (drawn on top of soldermask and silkscreen)
|
|
179
|
+
// 6. Plated holes, vias (copper ring + drill hole on top of soldermask)
|
|
180
|
+
// 7. Holes and cutouts (punch through everything)
|
|
181
|
+
// 8. Other annotations
|
|
182
|
+
|
|
183
|
+
// Step 1: Draw board outline
|
|
184
|
+
if (board) {
|
|
185
|
+
drawPcbBoard({
|
|
287
186
|
ctx: this.ctx,
|
|
288
|
-
|
|
187
|
+
board,
|
|
289
188
|
realToCanvasMat: this.realToCanvasMat,
|
|
290
189
|
colorMap: this.colorMap,
|
|
291
190
|
})
|
|
292
191
|
}
|
|
293
192
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
193
|
+
// Step 2: Draw copper elements underneath soldermask (pads, copper text)
|
|
194
|
+
for (const element of elements) {
|
|
195
|
+
if (!shouldDrawElement(element, options)) continue
|
|
196
|
+
|
|
197
|
+
if (element.type === "pcb_smtpad") {
|
|
198
|
+
drawPcbSmtPad({
|
|
199
|
+
ctx: this.ctx,
|
|
200
|
+
pad: element as PcbSmtPad,
|
|
201
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
202
|
+
colorMap: this.colorMap,
|
|
203
|
+
})
|
|
204
|
+
}
|
|
302
205
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
206
|
+
if (element.type === "pcb_copper_text") {
|
|
207
|
+
drawPcbCopperText({
|
|
208
|
+
ctx: this.ctx,
|
|
209
|
+
text: element as PcbCopperText,
|
|
210
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
211
|
+
colorMap: this.colorMap,
|
|
212
|
+
})
|
|
213
|
+
}
|
|
310
214
|
}
|
|
311
215
|
|
|
312
|
-
|
|
313
|
-
|
|
216
|
+
// Step 3: Draw soldermask layer (only if showSoldermask is true)
|
|
217
|
+
const showSoldermask = options.showSoldermask ?? false
|
|
218
|
+
if (board) {
|
|
219
|
+
drawPcbSoldermask({
|
|
314
220
|
ctx: this.ctx,
|
|
315
|
-
|
|
221
|
+
board,
|
|
222
|
+
elements,
|
|
316
223
|
realToCanvasMat: this.realToCanvasMat,
|
|
317
224
|
colorMap: this.colorMap,
|
|
225
|
+
layer: "top",
|
|
226
|
+
showSoldermask,
|
|
318
227
|
})
|
|
319
228
|
}
|
|
320
229
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
230
|
+
// Step 4: Draw silkscreen (on soldermask, under top copper layers)
|
|
231
|
+
for (const element of elements) {
|
|
232
|
+
if (!shouldDrawElement(element, options)) continue
|
|
233
|
+
|
|
234
|
+
if (element.type === "pcb_silkscreen_text") {
|
|
235
|
+
drawPcbSilkscreenText({
|
|
236
|
+
ctx: this.ctx,
|
|
237
|
+
text: element as PcbSilkscreenText,
|
|
238
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
239
|
+
colorMap: this.colorMap,
|
|
240
|
+
})
|
|
241
|
+
}
|
|
329
242
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
243
|
+
if (element.type === "pcb_silkscreen_rect") {
|
|
244
|
+
drawPcbSilkscreenRect({
|
|
245
|
+
ctx: this.ctx,
|
|
246
|
+
rect: element as PcbSilkscreenRect,
|
|
247
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
248
|
+
colorMap: this.colorMap,
|
|
249
|
+
})
|
|
250
|
+
}
|
|
338
251
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
252
|
+
if (element.type === "pcb_silkscreen_circle") {
|
|
253
|
+
drawPcbSilkscreenCircle({
|
|
254
|
+
ctx: this.ctx,
|
|
255
|
+
circle: element as PcbSilkscreenCircle,
|
|
256
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
257
|
+
colorMap: this.colorMap,
|
|
258
|
+
})
|
|
259
|
+
}
|
|
347
260
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
261
|
+
if (element.type === "pcb_silkscreen_line") {
|
|
262
|
+
drawPcbSilkscreenLine({
|
|
263
|
+
ctx: this.ctx,
|
|
264
|
+
line: element as PcbSilkscreenLine,
|
|
265
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
266
|
+
colorMap: this.colorMap,
|
|
267
|
+
})
|
|
268
|
+
}
|
|
356
269
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
270
|
+
if (element.type === "pcb_silkscreen_path") {
|
|
271
|
+
drawPcbSilkscreenPath({
|
|
272
|
+
ctx: this.ctx,
|
|
273
|
+
path: element as PcbSilkscreenPath,
|
|
274
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
275
|
+
colorMap: this.colorMap,
|
|
276
|
+
})
|
|
277
|
+
}
|
|
365
278
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
279
|
+
if (element.type === "pcb_silkscreen_pill") {
|
|
280
|
+
drawPcbSilkscreenPill({
|
|
281
|
+
ctx: this.ctx,
|
|
282
|
+
pill: element as PcbSilkscreenPill,
|
|
283
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
284
|
+
colorMap: this.colorMap,
|
|
285
|
+
})
|
|
286
|
+
}
|
|
374
287
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
288
|
+
if (element.type === "pcb_silkscreen_oval") {
|
|
289
|
+
drawPcbSilkscreenOval({
|
|
290
|
+
ctx: this.ctx,
|
|
291
|
+
oval: element as PcbSilkscreenOval,
|
|
292
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
293
|
+
colorMap: this.colorMap,
|
|
294
|
+
})
|
|
295
|
+
}
|
|
382
296
|
}
|
|
383
297
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
298
|
+
// Step 5: Draw copper pour and traces (on top of soldermask and silkscreen)
|
|
299
|
+
for (const element of elements) {
|
|
300
|
+
if (!shouldDrawElement(element, options)) continue
|
|
301
|
+
|
|
302
|
+
if (element.type === "pcb_copper_pour") {
|
|
303
|
+
drawPcbCopperPour({
|
|
304
|
+
ctx: this.ctx,
|
|
305
|
+
pour: element as PcbCopperPour,
|
|
306
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
307
|
+
colorMap: this.colorMap,
|
|
308
|
+
})
|
|
309
|
+
}
|
|
392
310
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
311
|
+
if (element.type === "pcb_trace") {
|
|
312
|
+
drawPcbTrace({
|
|
313
|
+
ctx: this.ctx,
|
|
314
|
+
trace: element as PcbTrace,
|
|
315
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
316
|
+
colorMap: this.colorMap,
|
|
317
|
+
})
|
|
318
|
+
}
|
|
400
319
|
}
|
|
401
320
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
321
|
+
// Step 6: Draw plated holes, vias (copper ring + drill hole on top of soldermask)
|
|
322
|
+
for (const element of elements) {
|
|
323
|
+
if (!shouldDrawElement(element, options)) continue
|
|
324
|
+
|
|
325
|
+
if (element.type === "pcb_plated_hole") {
|
|
326
|
+
drawPcbPlatedHole({
|
|
327
|
+
ctx: this.ctx,
|
|
328
|
+
hole: element as PcbPlatedHole,
|
|
329
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
330
|
+
colorMap: this.colorMap,
|
|
331
|
+
soldermaskMargin: showSoldermask
|
|
332
|
+
? (element as PcbPlatedHole).soldermask_margin
|
|
333
|
+
: undefined,
|
|
334
|
+
showSoldermask,
|
|
335
|
+
})
|
|
336
|
+
}
|
|
410
337
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
338
|
+
if (element.type === "pcb_via") {
|
|
339
|
+
drawPcbVia({
|
|
340
|
+
ctx: this.ctx,
|
|
341
|
+
via: element as PcbVia,
|
|
342
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
343
|
+
colorMap: this.colorMap,
|
|
344
|
+
})
|
|
345
|
+
}
|
|
418
346
|
}
|
|
419
347
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
348
|
+
// Step 7: Draw holes and cutouts (these punch through everything)
|
|
349
|
+
for (const element of elements) {
|
|
350
|
+
if (!shouldDrawElement(element, options)) continue
|
|
351
|
+
|
|
352
|
+
if (element.type === "pcb_hole") {
|
|
353
|
+
drawPcbHole({
|
|
354
|
+
ctx: this.ctx,
|
|
355
|
+
hole: element as PcbHole,
|
|
356
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
357
|
+
colorMap: this.colorMap,
|
|
358
|
+
soldermaskMargin: showSoldermask
|
|
359
|
+
? element.soldermask_margin
|
|
360
|
+
: undefined,
|
|
361
|
+
})
|
|
362
|
+
}
|
|
428
363
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
364
|
+
if (element.type === "pcb_cutout") {
|
|
365
|
+
drawPcbCutout({
|
|
366
|
+
ctx: this.ctx,
|
|
367
|
+
cutout: element as PcbCutout,
|
|
368
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
369
|
+
colorMap: this.colorMap,
|
|
370
|
+
})
|
|
371
|
+
}
|
|
436
372
|
}
|
|
437
373
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
374
|
+
// Step 8: Draw other annotations
|
|
375
|
+
for (const element of elements) {
|
|
376
|
+
if (!shouldDrawElement(element, options)) continue
|
|
377
|
+
|
|
378
|
+
if (element.type === "pcb_keepout") {
|
|
379
|
+
drawPcbKeepout({
|
|
380
|
+
ctx: this.ctx,
|
|
381
|
+
keepout: element as PCBKeepout,
|
|
382
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
383
|
+
colorMap: this.colorMap,
|
|
384
|
+
})
|
|
385
|
+
}
|
|
446
386
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
387
|
+
if (element.type === "pcb_fabrication_note_text") {
|
|
388
|
+
drawPcbFabricationNoteText({
|
|
389
|
+
ctx: this.ctx,
|
|
390
|
+
text: element as PcbFabricationNoteText,
|
|
391
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
392
|
+
colorMap: this.colorMap,
|
|
393
|
+
})
|
|
394
|
+
}
|
|
455
395
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
396
|
+
if (element.type === "pcb_fabrication_note_rect") {
|
|
397
|
+
drawPcbFabricationNoteRect({
|
|
398
|
+
ctx: this.ctx,
|
|
399
|
+
rect: element as PcbFabricationNoteRect,
|
|
400
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
401
|
+
colorMap: this.colorMap,
|
|
402
|
+
})
|
|
403
|
+
}
|
|
464
404
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
405
|
+
if (element.type === "pcb_note_rect") {
|
|
406
|
+
drawPcbNoteRect({
|
|
407
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
408
|
+
colorMap: this.colorMap,
|
|
409
|
+
ctx: this.ctx,
|
|
410
|
+
rect: element as PcbNoteRect,
|
|
411
|
+
})
|
|
412
|
+
}
|
|
473
413
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
414
|
+
if (element.type === "pcb_fabrication_note_path") {
|
|
415
|
+
drawPcbFabricationNotePath({
|
|
416
|
+
ctx: this.ctx,
|
|
417
|
+
path: element as PcbFabricationNotePath,
|
|
418
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
419
|
+
colorMap: this.colorMap,
|
|
420
|
+
})
|
|
421
|
+
}
|
|
482
422
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
423
|
+
if (element.type === "pcb_note_path") {
|
|
424
|
+
drawPcbNotePath({
|
|
425
|
+
ctx: this.ctx,
|
|
426
|
+
path: element as PcbNotePath,
|
|
427
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
428
|
+
colorMap: this.colorMap,
|
|
429
|
+
})
|
|
430
|
+
}
|
|
491
431
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
432
|
+
if (element.type === "pcb_note_text") {
|
|
433
|
+
drawPcbNoteText({
|
|
434
|
+
ctx: this.ctx,
|
|
435
|
+
text: element as PcbNoteText,
|
|
436
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
437
|
+
colorMap: this.colorMap,
|
|
438
|
+
})
|
|
439
|
+
}
|
|
500
440
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
441
|
+
if (element.type === "pcb_note_line") {
|
|
442
|
+
drawPcbNoteLine({
|
|
443
|
+
ctx: this.ctx,
|
|
444
|
+
line: element as PcbNoteLine,
|
|
445
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
446
|
+
colorMap: this.colorMap,
|
|
447
|
+
})
|
|
448
|
+
}
|
|
509
449
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
450
|
+
if (element.type === "pcb_note_dimension") {
|
|
451
|
+
drawPcbNoteDimension({
|
|
452
|
+
ctx: this.ctx,
|
|
453
|
+
pcbNoteDimension: element as PcbNoteDimension,
|
|
454
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
455
|
+
colorMap: this.colorMap,
|
|
456
|
+
})
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (element.type === "pcb_fabrication_note_dimension") {
|
|
460
|
+
drawPcbFabricationNoteDimension({
|
|
461
|
+
ctx: this.ctx,
|
|
462
|
+
pcbFabricationNoteDimension: element as PcbFabricationNoteDimension,
|
|
463
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
464
|
+
colorMap: this.colorMap,
|
|
465
|
+
})
|
|
466
|
+
}
|
|
517
467
|
}
|
|
518
468
|
}
|
|
519
469
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { CanvasContext } from "../../types"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Draws a pill/stadium shape path centered at (cx, cy).
|
|
5
|
+
* A pill is a rectangle with fully rounded ends (semicircular caps).
|
|
6
|
+
* The path is not filled or stroked - call ctx.fill() or ctx.stroke() after.
|
|
7
|
+
*/
|
|
8
|
+
export function drawPillPath(params: {
|
|
9
|
+
ctx: CanvasContext
|
|
10
|
+
cx: number
|
|
11
|
+
cy: number
|
|
12
|
+
width: number
|
|
13
|
+
height: number
|
|
14
|
+
}): void {
|
|
15
|
+
const { ctx, cx, cy, width, height } = params
|
|
16
|
+
if (width > height) {
|
|
17
|
+
// Horizontal pill
|
|
18
|
+
const radius = height / 2
|
|
19
|
+
const straightLength = width - height
|
|
20
|
+
ctx.moveTo(cx - straightLength / 2, cy - radius)
|
|
21
|
+
ctx.lineTo(cx + straightLength / 2, cy - radius)
|
|
22
|
+
ctx.arc(cx + straightLength / 2, cy, radius, -Math.PI / 2, Math.PI / 2)
|
|
23
|
+
ctx.lineTo(cx - straightLength / 2, cy + radius)
|
|
24
|
+
ctx.arc(cx - straightLength / 2, cy, radius, Math.PI / 2, -Math.PI / 2)
|
|
25
|
+
} else if (height > width) {
|
|
26
|
+
// Vertical pill
|
|
27
|
+
const radius = width / 2
|
|
28
|
+
const straightLength = height - width
|
|
29
|
+
ctx.moveTo(cx + radius, cy - straightLength / 2)
|
|
30
|
+
ctx.lineTo(cx + radius, cy + straightLength / 2)
|
|
31
|
+
ctx.arc(cx, cy + straightLength / 2, radius, 0, Math.PI)
|
|
32
|
+
ctx.lineTo(cx - radius, cy - straightLength / 2)
|
|
33
|
+
ctx.arc(cx, cy - straightLength / 2, radius, Math.PI, 0)
|
|
34
|
+
} else {
|
|
35
|
+
// Square dimensions = circle
|
|
36
|
+
ctx.arc(cx, cy, width / 2, 0, Math.PI * 2)
|
|
37
|
+
}
|
|
38
|
+
ctx.closePath()
|
|
39
|
+
}
|