circuit-to-canvas 0.0.110 → 0.0.111

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
@@ -1,4 +1,4 @@
1
- import { AnyCircuitElement, PcbRenderLayer, NinePointAnchor, PcbPlatedHole, PCBVia, PcbHole, PcbSmtPad, PcbVia, PcbTrace, PcbBoard, PcbPanel, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbSilkscreenPill, PcbSilkscreenOval, PcbCutout, PCBKeepout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText, PcbNoteDimension, PcbFabricationNoteDimension, PcbCourtyardCircle } from 'circuit-json';
1
+ import { AnyCircuitElement, PcbRenderLayer, NinePointAnchor, PcbPlatedHole, PCBVia, PcbHole, PcbSmtPad, PcbVia, PcbCutout, PcbTrace, PcbBoard, PcbPanel, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbSilkscreenPill, PcbSilkscreenOval, PCBKeepout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText, PcbNoteDimension, PcbFabricationNoteDimension, PcbCourtyardCircle } from 'circuit-json';
2
2
  import { Matrix } from 'transformation-matrix';
3
3
 
4
4
  /**
@@ -364,6 +364,7 @@ interface DrawPcbSmtPadParams {
364
364
  holes?: PcbHole[];
365
365
  platedHoles?: PcbPlatedHole[];
366
366
  vias?: PcbVia[];
367
+ cutouts?: PcbCutout[];
367
368
  }
368
369
  declare function drawPcbSmtPad(params: DrawPcbSmtPadParams): void;
369
370
 
package/dist/index.js CHANGED
@@ -1637,22 +1637,24 @@ function drawPadWithDrillCutouts(params) {
1637
1637
  ctx.restore();
1638
1638
  }
1639
1639
  function getPadDrillCutouts(params) {
1640
- const { pad, holes = [], platedHoles = [], vias = [] } = params;
1640
+ const { pad, holes = [], platedHoles = [], vias = [], cutouts = [] } = params;
1641
1641
  const padBounds = getPadBounds(pad);
1642
1642
  if (!padBounds) return [];
1643
- const cutouts = [];
1644
- for (const hole of holes) {
1645
- const cutout = getHoleDrillCutout(hole);
1643
+ const drillCutouts = [];
1644
+ const addIfOverlapping = (cutout) => {
1646
1645
  if (cutout && boundsOverlap(padBounds, getDrillCutoutBounds(cutout))) {
1647
- cutouts.push(cutout);
1646
+ drillCutouts.push(cutout);
1648
1647
  }
1648
+ };
1649
+ for (const cutout of cutouts) {
1650
+ addIfOverlapping(getBoardCutout(cutout));
1651
+ }
1652
+ for (const hole of holes) {
1653
+ addIfOverlapping(getHoleDrillCutout(hole));
1649
1654
  }
1650
1655
  for (const platedHole of platedHoles) {
1651
1656
  if (!layersIncludePadLayer(platedHole.layers, pad.layer)) continue;
1652
- const cutout = getPlatedHoleDrillCutout(platedHole);
1653
- if (cutout && boundsOverlap(padBounds, getDrillCutoutBounds(cutout))) {
1654
- cutouts.push(cutout);
1655
- }
1657
+ addIfOverlapping(getPlatedHoleDrillCutout(platedHole));
1656
1658
  }
1657
1659
  for (const via of vias) {
1658
1660
  if (!layersIncludePadLayer(via.layers, pad.layer)) continue;
@@ -1664,11 +1666,9 @@ function getPadDrillCutouts(params) {
1664
1666
  y: via.y,
1665
1667
  radius
1666
1668
  };
1667
- if (boundsOverlap(padBounds, getDrillCutoutBounds(cutout))) {
1668
- cutouts.push(cutout);
1669
- }
1669
+ addIfOverlapping(cutout);
1670
1670
  }
1671
- return cutouts;
1671
+ return drillCutouts;
1672
1672
  }
1673
1673
  function appendPadPath(ctx, pad, realToCanvasMat) {
1674
1674
  if (pad.shape === "rect" || pad.shape === "rotated_rect") {
@@ -1741,11 +1741,16 @@ function appendDrillCutoutPath(ctx, drillCutout, realToCanvasMat) {
1741
1741
  });
1742
1742
  return;
1743
1743
  }
1744
+ if (drillCutout.shape === "polygon") {
1745
+ appendPolygonPath(ctx, drillCutout.points, realToCanvasMat);
1746
+ return;
1747
+ }
1744
1748
  appendRectPath(ctx, {
1745
1749
  x: drillCutout.x,
1746
1750
  y: drillCutout.y,
1747
1751
  width: drillCutout.width,
1748
1752
  height: drillCutout.height,
1753
+ radius: drillCutout.radius,
1749
1754
  rotation: drillCutout.rotation,
1750
1755
  realToCanvasMat
1751
1756
  });
@@ -1883,6 +1888,9 @@ function getPadBounds(pad) {
1883
1888
  }
1884
1889
  }
1885
1890
  function getDrillCutoutBounds(cutout) {
1891
+ if (cutout.shape === "polygon") {
1892
+ return getPointBounds(cutout.points);
1893
+ }
1886
1894
  if (cutout.shape === "circle") {
1887
1895
  return boundsAroundCenter(cutout.x, cutout.y, cutout.radius, cutout.radius);
1888
1896
  }
@@ -1906,6 +1914,32 @@ function getDrillCutoutBounds(cutout) {
1906
1914
  }
1907
1915
  return boundsAroundCenter(cutout.x, cutout.y, halfWidth, halfHeight);
1908
1916
  }
1917
+ function getBoardCutout(cutout) {
1918
+ if (cutout.shape === "circle") {
1919
+ if (cutout.radius <= 0) return;
1920
+ return {
1921
+ shape: "circle",
1922
+ x: cutout.center.x,
1923
+ y: cutout.center.y,
1924
+ radius: cutout.radius
1925
+ };
1926
+ }
1927
+ if (cutout.shape === "rect") {
1928
+ if (cutout.width <= 0 || cutout.height <= 0) return;
1929
+ return {
1930
+ shape: "rect",
1931
+ x: cutout.center.x,
1932
+ y: cutout.center.y,
1933
+ width: cutout.width,
1934
+ height: cutout.height,
1935
+ radius: cutout.corner_radius,
1936
+ rotation: cutout.rotation
1937
+ };
1938
+ }
1939
+ if (cutout.shape === "polygon" && cutout.points.length >= 3) {
1940
+ return { shape: "polygon", points: cutout.points };
1941
+ }
1942
+ }
1909
1943
  function getPointBounds(points) {
1910
1944
  let minX = Infinity;
1911
1945
  let maxX = -Infinity;
@@ -5318,6 +5352,9 @@ var CircuitToCanvasDrawer = class {
5318
5352
  const drawablePlatedHoles = elements.filter(
5319
5353
  (el) => shouldDrawElement(el, options) && el.type === "pcb_plated_hole"
5320
5354
  );
5355
+ const drawableCutouts = elements.filter(
5356
+ (el) => shouldDrawElement(el, options) && el.type === "pcb_cutout"
5357
+ );
5321
5358
  if (board) {
5322
5359
  drawPcbBoard({
5323
5360
  ctx: this.ctx,
@@ -5349,7 +5386,8 @@ var CircuitToCanvasDrawer = class {
5349
5386
  colorMap: this.colorMap,
5350
5387
  holes: drawableHoles,
5351
5388
  platedHoles: drawablePlatedHoles,
5352
- vias: drawableVias
5389
+ vias: drawableVias,
5390
+ cutouts: drawableCutouts
5353
5391
  });
5354
5392
  }
5355
5393
  if (element.type === "pcb_copper_text") {
@@ -243,6 +243,10 @@ export class CircuitToCanvasDrawer {
243
243
  (el): el is PcbPlatedHole =>
244
244
  shouldDrawElement(el, options) && el.type === "pcb_plated_hole",
245
245
  )
246
+ const drawableCutouts = elements.filter(
247
+ (el): el is PcbCutout =>
248
+ shouldDrawElement(el, options) && el.type === "pcb_cutout",
249
+ )
246
250
 
247
251
  // Step 2: Draw board outline/material (inner board)
248
252
  if (board) {
@@ -283,6 +287,7 @@ export class CircuitToCanvasDrawer {
283
287
  holes: drawableHoles,
284
288
  platedHoles: drawablePlatedHoles,
285
289
  vias: drawableVias,
290
+ cutouts: drawableCutouts,
286
291
  })
287
292
  }
288
293
 
@@ -1,4 +1,10 @@
1
- import type { PcbHole, PcbPlatedHole, PcbSmtPad, PcbVia } from "circuit-json"
1
+ import type {
2
+ PcbCutout,
3
+ PcbHole,
4
+ PcbPlatedHole,
5
+ PcbSmtPad,
6
+ PcbVia,
7
+ } from "circuit-json"
2
8
  import type { Matrix } from "transformation-matrix"
3
9
  import { applyToPoint } from "transformation-matrix"
4
10
  import type { CanvasContext } from "../../types"
@@ -34,14 +40,21 @@ interface DrillCutoutRect {
34
40
  y: number
35
41
  width: number
36
42
  height: number
43
+ radius?: number
37
44
  rotation?: number
38
45
  }
39
46
 
47
+ interface DrillCutoutPolygon {
48
+ shape: "polygon"
49
+ points: Array<{ x: number; y: number }>
50
+ }
51
+
40
52
  type DrillCutout =
41
53
  | DrillCutoutCircle
42
54
  | DrillCutoutOval
43
55
  | DrillCutoutPill
44
56
  | DrillCutoutRect
57
+ | DrillCutoutPolygon
45
58
 
46
59
  interface Bounds {
47
60
  minX: number
@@ -55,6 +68,7 @@ export interface PcbSmtPadDrillCutoutParams {
55
68
  holes?: PcbHole[]
56
69
  platedHoles?: PcbPlatedHole[]
57
70
  vias?: PcbVia[]
71
+ cutouts?: PcbCutout[]
58
72
  }
59
73
 
60
74
  export function drawPadWithDrillCutouts(params: {
@@ -87,26 +101,28 @@ export function drawPadWithDrillCutouts(params: {
87
101
  export function getPadDrillCutouts(
88
102
  params: PcbSmtPadDrillCutoutParams,
89
103
  ): DrillCutout[] {
90
- const { pad, holes = [], platedHoles = [], vias = [] } = params
104
+ const { pad, holes = [], platedHoles = [], vias = [], cutouts = [] } = params
91
105
  const padBounds = getPadBounds(pad)
92
106
  if (!padBounds) return []
93
107
 
94
- const cutouts: DrillCutout[] = []
95
-
96
- for (const hole of holes) {
97
- const cutout = getHoleDrillCutout(hole)
108
+ const drillCutouts: DrillCutout[] = []
109
+ const addIfOverlapping = (cutout: DrillCutout | undefined): void => {
98
110
  if (cutout && boundsOverlap(padBounds, getDrillCutoutBounds(cutout))) {
99
- cutouts.push(cutout)
111
+ drillCutouts.push(cutout)
100
112
  }
101
113
  }
102
114
 
115
+ for (const cutout of cutouts) {
116
+ addIfOverlapping(getBoardCutout(cutout))
117
+ }
118
+
119
+ for (const hole of holes) {
120
+ addIfOverlapping(getHoleDrillCutout(hole))
121
+ }
122
+
103
123
  for (const platedHole of platedHoles) {
104
124
  if (!layersIncludePadLayer(platedHole.layers, pad.layer)) continue
105
-
106
- const cutout = getPlatedHoleDrillCutout(platedHole)
107
- if (cutout && boundsOverlap(padBounds, getDrillCutoutBounds(cutout))) {
108
- cutouts.push(cutout)
109
- }
125
+ addIfOverlapping(getPlatedHoleDrillCutout(platedHole))
110
126
  }
111
127
 
112
128
  for (const via of vias) {
@@ -120,12 +136,10 @@ export function getPadDrillCutouts(
120
136
  y: via.y,
121
137
  radius,
122
138
  }
123
- if (boundsOverlap(padBounds, getDrillCutoutBounds(cutout))) {
124
- cutouts.push(cutout)
125
- }
139
+ addIfOverlapping(cutout)
126
140
  }
127
141
 
128
- return cutouts
142
+ return drillCutouts
129
143
  }
130
144
 
131
145
  function appendPadPath(
@@ -215,11 +229,17 @@ function appendDrillCutoutPath(
215
229
  return
216
230
  }
217
231
 
232
+ if (drillCutout.shape === "polygon") {
233
+ appendPolygonPath(ctx, drillCutout.points, realToCanvasMat)
234
+ return
235
+ }
236
+
218
237
  appendRectPath(ctx, {
219
238
  x: drillCutout.x,
220
239
  y: drillCutout.y,
221
240
  width: drillCutout.width,
222
241
  height: drillCutout.height,
242
+ radius: drillCutout.radius,
223
243
  rotation: drillCutout.rotation,
224
244
  realToCanvasMat,
225
245
  })
@@ -429,6 +449,10 @@ function getPadBounds(pad: PcbSmtPad): Bounds | undefined {
429
449
  }
430
450
 
431
451
  function getDrillCutoutBounds(cutout: DrillCutout): Bounds {
452
+ if (cutout.shape === "polygon") {
453
+ return getPointBounds(cutout.points)
454
+ }
455
+
432
456
  if (cutout.shape === "circle") {
433
457
  return boundsAroundCenter(cutout.x, cutout.y, cutout.radius, cutout.radius)
434
458
  }
@@ -455,6 +479,35 @@ function getDrillCutoutBounds(cutout: DrillCutout): Bounds {
455
479
  return boundsAroundCenter(cutout.x, cutout.y, halfWidth, halfHeight)
456
480
  }
457
481
 
482
+ function getBoardCutout(cutout: PcbCutout): DrillCutout | undefined {
483
+ if (cutout.shape === "circle") {
484
+ if (cutout.radius <= 0) return
485
+ return {
486
+ shape: "circle",
487
+ x: cutout.center.x,
488
+ y: cutout.center.y,
489
+ radius: cutout.radius,
490
+ }
491
+ }
492
+
493
+ if (cutout.shape === "rect") {
494
+ if (cutout.width <= 0 || cutout.height <= 0) return
495
+ return {
496
+ shape: "rect",
497
+ x: cutout.center.x,
498
+ y: cutout.center.y,
499
+ width: cutout.width,
500
+ height: cutout.height,
501
+ radius: cutout.corner_radius,
502
+ rotation: cutout.rotation,
503
+ }
504
+ }
505
+
506
+ if (cutout.shape === "polygon" && cutout.points.length >= 3) {
507
+ return { shape: "polygon", points: cutout.points }
508
+ }
509
+ }
510
+
458
511
  function getPointBounds(points: Array<{ x: number; y: number }>): Bounds {
459
512
  let minX = Infinity
460
513
  let maxX = -Infinity
@@ -1,10 +1,16 @@
1
- import type { PcbHole, PcbPlatedHole, PcbSmtPad, PcbVia } from "circuit-json"
1
+ import type {
2
+ PcbCutout,
3
+ PcbHole,
4
+ PcbPlatedHole,
5
+ PcbSmtPad,
6
+ PcbVia,
7
+ } from "circuit-json"
2
8
  import type { Matrix } from "transformation-matrix"
3
- import type { PcbColorMap, CanvasContext } from "../types"
4
9
  import { drawCircle } from "../shapes/circle"
5
- import { drawRect } from "../shapes/rect"
6
10
  import { drawPill } from "../shapes/pill"
7
11
  import { drawPolygon } from "../shapes/polygon"
12
+ import { drawRect } from "../shapes/rect"
13
+ import type { CanvasContext, PcbColorMap } from "../types"
8
14
  import {
9
15
  drawPadWithDrillCutouts,
10
16
  getPadDrillCutouts,
@@ -18,6 +24,7 @@ export interface DrawPcbSmtPadParams {
18
24
  holes?: PcbHole[]
19
25
  platedHoles?: PcbPlatedHole[]
20
26
  vias?: PcbVia[]
27
+ cutouts?: PcbCutout[]
21
28
  }
22
29
 
23
30
  function layerToColor(layer: string, colorMap: PcbColorMap): string {
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.110",
4
+ "version": "0.0.111",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsup-node ./lib/index.ts --format esm --dts",
@@ -0,0 +1,39 @@
1
+ import { expect, test } from "bun:test"
2
+ import { createCanvas } from "@napi-rs/canvas"
3
+ import type { PcbCutout, PcbSmtPad } from "circuit-json"
4
+ import { CircuitToCanvasDrawer } from "../../lib/drawer"
5
+
6
+ test("cutout removes overlapping SMT pad copper", async () => {
7
+ const scale = 4
8
+ const canvas = createCanvas(100 * scale, 100 * scale)
9
+ const ctx = canvas.getContext("2d")
10
+ ctx.scale(scale, scale)
11
+ const drawer = new CircuitToCanvasDrawer(ctx)
12
+ drawer.configure({
13
+ colorOverrides: { drill: "rgba(0,0,0,0)" },
14
+ })
15
+
16
+ const pad: PcbSmtPad = {
17
+ type: "pcb_smtpad",
18
+ pcb_smtpad_id: "pad1",
19
+ shape: "rect",
20
+ layer: "top",
21
+ x: 50,
22
+ y: 50,
23
+ width: 40,
24
+ height: 20,
25
+ }
26
+ const cutout: PcbCutout = {
27
+ type: "pcb_cutout",
28
+ pcb_cutout_id: "cutout1",
29
+ shape: "circle",
30
+ center: { x: 50, y: 50 },
31
+ radius: 5,
32
+ }
33
+
34
+ drawer.drawElements([pad, cutout])
35
+
36
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
37
+ import.meta.path,
38
+ )
39
+ })