circuit-to-canvas 0.0.41 → 0.0.42

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, PCBTrace, PcbBoard, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbSilkscreenPill, PcbSilkscreenOval, PcbCutout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText, PcbNoteDimension, PcbFabricationNoteDimension } from 'circuit-json';
1
+ import { AnyCircuitElement, PcbRenderLayer, NinePointAnchor, PcbPlatedHole, PCBVia, PCBHole, PcbSmtPad, PCBTrace, PcbBoard, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbSilkscreenPill, PcbSilkscreenOval, PcbCutout, PCBKeepout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText, PcbNoteDimension, PcbFabricationNoteDimension } from 'circuit-json';
2
2
  import { Matrix } from 'transformation-matrix';
3
3
 
4
4
  /**
@@ -18,6 +18,7 @@ interface CanvasContext {
18
18
  moveTo(x: number, y: number): void;
19
19
  save(): void;
20
20
  restore(): void;
21
+ clip(): void;
21
22
  translate(x: number, y: number): void;
22
23
  rotate(angle: number): void;
23
24
  scale(x: number, y: number): void;
@@ -68,6 +69,7 @@ interface PcbColorMap {
68
69
  };
69
70
  substrate: string;
70
71
  courtyard: string;
72
+ keepout: string;
71
73
  }
72
74
  declare const DEFAULT_PCB_COLOR_MAP: PcbColorMap;
73
75
  interface DrawerConfig {
@@ -430,6 +432,14 @@ interface DrawPcbCutoutParams {
430
432
  }
431
433
  declare function drawPcbCutout(params: DrawPcbCutoutParams): void;
432
434
 
435
+ interface DrawPcbKeepoutParams {
436
+ ctx: CanvasContext;
437
+ keepout: PCBKeepout;
438
+ realToCanvasMat: Matrix;
439
+ colorMap: PcbColorMap;
440
+ }
441
+ declare function drawPcbKeepout(params: DrawPcbKeepoutParams): void;
442
+
433
443
  interface DrawPcbCopperPourParams {
434
444
  ctx: CanvasContext;
435
445
  pour: PcbCopperPour;
@@ -510,4 +520,4 @@ interface DrawPcbFabricationNoteDimensionParams {
510
520
  }
511
521
  declare function drawPcbFabricationNoteDimension(params: DrawPcbFabricationNoteDimensionParams): void;
512
522
 
513
- export { type AlphabetLayout, type AnchorAlignment, type CameraBounds, type CanvasContext, CircuitToCanvasDrawer, type CopperColorMap, type CopperLayerName, DEFAULT_PCB_COLOR_MAP, type DrawArrowParams, type DrawCircleParams, type DrawContext, type DrawDimensionLineParams, type DrawElementsOptions, type DrawLineParams, type DrawOvalParams, type DrawPathParams, type DrawPcbBoardParams, type DrawPcbCopperPourParams, type DrawPcbCopperTextParams, type DrawPcbCutoutParams, type DrawPcbFabricationNoteDimensionParams, type DrawPcbFabricationNotePathParams, type DrawPcbFabricationNoteRectParams, type DrawPcbFabricationNoteTextParams, type DrawPcbHoleParams, type DrawPcbNoteDimensionParams, type DrawPcbNotePathParams, type DrawPcbNoteRectParams, type DrawPcbNoteTextParams, type DrawPcbPlatedHoleParams, type DrawPcbSilkscreenCircleParams, type DrawPcbSilkscreenLineParams, type DrawPcbSilkscreenOvalParams, type DrawPcbSilkscreenPathParams, type DrawPcbSilkscreenPillParams, type DrawPcbSilkscreenRectParams, type DrawPcbSilkscreenTextParams, type DrawPcbSmtPadParams, type DrawPcbTraceParams, type DrawPcbViaParams, type DrawPillParams, type DrawPolygonParams, type DrawRectParams, type DrawTextParams, type DrawerConfig, type PcbColorMap, drawArrow, drawCircle, drawDimensionLine, drawLine, drawOval, drawPath, drawPcbBoard, drawPcbCopperPour, drawPcbCopperText, drawPcbCutout, drawPcbFabricationNoteDimension, drawPcbFabricationNotePath, drawPcbFabricationNoteRect, drawPcbFabricationNoteText, drawPcbHole, drawPcbNoteDimension, drawPcbNotePath, drawPcbNoteRect, drawPcbNoteText, drawPcbPlatedHole, drawPcbSilkscreenCircle, drawPcbSilkscreenLine, drawPcbSilkscreenOval, drawPcbSilkscreenPath, drawPcbSilkscreenPill, drawPcbSilkscreenRect, drawPcbSilkscreenText, drawPcbSmtPad, drawPcbTrace, drawPcbVia, drawPill, drawPolygon, drawRect, drawSoldermaskRingForCircle, drawSoldermaskRingForOval, drawSoldermaskRingForPill, drawSoldermaskRingForRect, drawText, getAlphabetLayout, getTextStartPosition, strokeAlphabetText };
523
+ export { type AlphabetLayout, type AnchorAlignment, type CameraBounds, type CanvasContext, CircuitToCanvasDrawer, type CopperColorMap, type CopperLayerName, DEFAULT_PCB_COLOR_MAP, type DrawArrowParams, type DrawCircleParams, type DrawContext, type DrawDimensionLineParams, type DrawElementsOptions, type DrawLineParams, type DrawOvalParams, type DrawPathParams, type DrawPcbBoardParams, type DrawPcbCopperPourParams, type DrawPcbCopperTextParams, type DrawPcbCutoutParams, type DrawPcbFabricationNoteDimensionParams, type DrawPcbFabricationNotePathParams, type DrawPcbFabricationNoteRectParams, type DrawPcbFabricationNoteTextParams, type DrawPcbHoleParams, type DrawPcbKeepoutParams, type DrawPcbNoteDimensionParams, type DrawPcbNotePathParams, type DrawPcbNoteRectParams, type DrawPcbNoteTextParams, type DrawPcbPlatedHoleParams, type DrawPcbSilkscreenCircleParams, type DrawPcbSilkscreenLineParams, type DrawPcbSilkscreenOvalParams, type DrawPcbSilkscreenPathParams, type DrawPcbSilkscreenPillParams, type DrawPcbSilkscreenRectParams, type DrawPcbSilkscreenTextParams, type DrawPcbSmtPadParams, type DrawPcbTraceParams, type DrawPcbViaParams, type DrawPillParams, type DrawPolygonParams, type DrawRectParams, type DrawTextParams, type DrawerConfig, type PcbColorMap, drawArrow, drawCircle, drawDimensionLine, drawLine, drawOval, drawPath, drawPcbBoard, drawPcbCopperPour, drawPcbCopperText, drawPcbCutout, drawPcbFabricationNoteDimension, drawPcbFabricationNotePath, drawPcbFabricationNoteRect, drawPcbFabricationNoteText, drawPcbHole, drawPcbKeepout, drawPcbNoteDimension, drawPcbNotePath, drawPcbNoteRect, drawPcbNoteText, drawPcbPlatedHole, drawPcbSilkscreenCircle, drawPcbSilkscreenLine, drawPcbSilkscreenOval, drawPcbSilkscreenPath, drawPcbSilkscreenPill, drawPcbSilkscreenRect, drawPcbSilkscreenText, drawPcbSmtPad, drawPcbTrace, drawPcbVia, drawPill, drawPolygon, drawRect, drawSoldermaskRingForCircle, drawSoldermaskRingForOval, drawSoldermaskRingForPill, drawSoldermaskRingForRect, drawText, getAlphabetLayout, getTextStartPosition, strokeAlphabetText };
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  compose,
5
5
  translate,
6
6
  scale,
7
- applyToPoint as applyToPoint15
7
+ applyToPoint as applyToPoint16
8
8
  } from "transformation-matrix";
9
9
 
10
10
  // lib/drawer/pcb-render-layer-filter.ts
@@ -51,7 +51,9 @@ var DEFAULT_PCB_COLOR_MAP = {
51
51
  bottom: "#5da9e9"
52
52
  },
53
53
  boardOutline: "rgba(255, 255, 255, 0.5)",
54
- courtyard: "#FF00FF"
54
+ courtyard: "#FF00FF",
55
+ keepout: "#FF6B6B"
56
+ // Red color for keepout zones
55
57
  };
56
58
 
57
59
  // lib/drawer/shapes/circle.ts
@@ -92,11 +94,6 @@ function drawRect(params) {
92
94
  if (rotation !== 0) {
93
95
  ctx.rotate(-rotation * (Math.PI / 180));
94
96
  }
95
- if (isStrokeDashed && scaledStrokeWidth) {
96
- ctx.setLineDash([scaledStrokeWidth * 2, scaledStrokeWidth * 2]);
97
- } else {
98
- ctx.setLineDash([]);
99
- }
100
97
  ctx.beginPath();
101
98
  if (scaledRadius > 0) {
102
99
  const x = -scaledWidth / 2;
@@ -125,6 +122,11 @@ function drawRect(params) {
125
122
  ctx.fill();
126
123
  }
127
124
  if (stroke && scaledStrokeWidth) {
125
+ if (isStrokeDashed) {
126
+ ctx.setLineDash([scaledStrokeWidth * 3, scaledStrokeWidth * 2]);
127
+ } else {
128
+ ctx.setLineDash([]);
129
+ }
128
130
  ctx.strokeStyle = stroke;
129
131
  ctx.lineWidth = scaledStrokeWidth;
130
132
  ctx.stroke();
@@ -1673,8 +1675,94 @@ function drawPcbCutout(params) {
1673
1675
  }
1674
1676
  }
1675
1677
 
1676
- // lib/drawer/elements/pcb-copper-pour.ts
1678
+ // lib/drawer/elements/pcb-keepout.ts
1677
1679
  import { applyToPoint as applyToPoint11 } from "transformation-matrix";
1680
+ function hexToRgba(hex, alpha) {
1681
+ const r = parseInt(hex.slice(1, 3), 16);
1682
+ const g = parseInt(hex.slice(3, 5), 16);
1683
+ const b = parseInt(hex.slice(5, 7), 16);
1684
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
1685
+ }
1686
+ function drawPcbKeepout(params) {
1687
+ const { ctx, keepout, realToCanvasMat, colorMap } = params;
1688
+ const strokeColor = colorMap.keepout;
1689
+ const fillColor = hexToRgba(colorMap.keepout, 0.2);
1690
+ const hatchSpacing = 1;
1691
+ if (keepout.shape === "rect") {
1692
+ const [cx, cy] = applyToPoint11(realToCanvasMat, [
1693
+ keepout.center.x,
1694
+ keepout.center.y
1695
+ ]);
1696
+ const scaledWidth = keepout.width * Math.abs(realToCanvasMat.a);
1697
+ const scaledHeight = keepout.height * Math.abs(realToCanvasMat.a);
1698
+ const rotation = keepout.rotation ?? 0;
1699
+ ctx.save();
1700
+ ctx.translate(cx, cy);
1701
+ if (rotation !== 0) {
1702
+ ctx.rotate(-rotation * (Math.PI / 180));
1703
+ }
1704
+ ctx.beginPath();
1705
+ ctx.rect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight);
1706
+ ctx.fillStyle = fillColor;
1707
+ ctx.fill();
1708
+ ctx.beginPath();
1709
+ ctx.rect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight);
1710
+ ctx.clip();
1711
+ const scaledSpacing = hatchSpacing * Math.abs(realToCanvasMat.a);
1712
+ const diagonal = Math.sqrt(
1713
+ scaledWidth * scaledWidth + scaledHeight * scaledHeight
1714
+ );
1715
+ const halfWidth = scaledWidth / 2;
1716
+ const halfHeight = scaledHeight / 2;
1717
+ ctx.strokeStyle = strokeColor;
1718
+ ctx.lineWidth = 0.15 * Math.abs(realToCanvasMat.a);
1719
+ ctx.setLineDash([]);
1720
+ for (let offset = -diagonal; offset < diagonal * 2; offset += scaledSpacing) {
1721
+ ctx.beginPath();
1722
+ const startX = -halfWidth + offset;
1723
+ const startY = -halfHeight;
1724
+ const endX = -halfWidth + offset + diagonal;
1725
+ const endY = -halfHeight + diagonal;
1726
+ ctx.moveTo(startX, startY);
1727
+ ctx.lineTo(endX, endY);
1728
+ ctx.stroke();
1729
+ }
1730
+ ctx.restore();
1731
+ return;
1732
+ }
1733
+ if (keepout.shape === "circle") {
1734
+ const [cx, cy] = applyToPoint11(realToCanvasMat, [
1735
+ keepout.center.x,
1736
+ keepout.center.y
1737
+ ]);
1738
+ const scaledRadius = keepout.radius * Math.abs(realToCanvasMat.a);
1739
+ const scaledSpacing = hatchSpacing * Math.abs(realToCanvasMat.a);
1740
+ ctx.save();
1741
+ ctx.translate(cx, cy);
1742
+ ctx.beginPath();
1743
+ ctx.arc(0, 0, scaledRadius, 0, Math.PI * 2);
1744
+ ctx.fillStyle = fillColor;
1745
+ ctx.fill();
1746
+ ctx.beginPath();
1747
+ ctx.arc(0, 0, scaledRadius, 0, Math.PI * 2);
1748
+ ctx.clip();
1749
+ const diagonal = scaledRadius * 2;
1750
+ ctx.strokeStyle = strokeColor;
1751
+ ctx.lineWidth = 0.15 * Math.abs(realToCanvasMat.a);
1752
+ ctx.setLineDash([]);
1753
+ for (let offset = -diagonal; offset < diagonal * 2; offset += scaledSpacing) {
1754
+ ctx.beginPath();
1755
+ ctx.moveTo(offset - diagonal, -diagonal);
1756
+ ctx.lineTo(offset + diagonal, diagonal);
1757
+ ctx.stroke();
1758
+ }
1759
+ ctx.restore();
1760
+ return;
1761
+ }
1762
+ }
1763
+
1764
+ // lib/drawer/elements/pcb-copper-pour.ts
1765
+ import { applyToPoint as applyToPoint12 } from "transformation-matrix";
1678
1766
  function layerToColor3(layer, colorMap) {
1679
1767
  return colorMap.copper[layer] ?? colorMap.copper.top;
1680
1768
  }
@@ -1683,7 +1771,7 @@ function drawPcbCopperPour(params) {
1683
1771
  const color = layerToColor3(pour.layer, colorMap);
1684
1772
  ctx.save();
1685
1773
  if (pour.shape === "rect") {
1686
- const [cx, cy] = applyToPoint11(realToCanvasMat, [
1774
+ const [cx, cy] = applyToPoint12(realToCanvasMat, [
1687
1775
  pour.center.x,
1688
1776
  pour.center.y
1689
1777
  ]);
@@ -1704,7 +1792,7 @@ function drawPcbCopperPour(params) {
1704
1792
  if (pour.shape === "polygon") {
1705
1793
  if (pour.points && pour.points.length >= 3) {
1706
1794
  const canvasPoints = pour.points.map(
1707
- (p) => applyToPoint11(realToCanvasMat, [p.x, p.y])
1795
+ (p) => applyToPoint12(realToCanvasMat, [p.x, p.y])
1708
1796
  );
1709
1797
  const firstPoint = canvasPoints[0];
1710
1798
  if (!firstPoint) {
@@ -1732,7 +1820,7 @@ function drawPcbCopperPour(params) {
1732
1820
  }
1733
1821
 
1734
1822
  // lib/drawer/elements/pcb-copper-text.ts
1735
- import { applyToPoint as applyToPoint12 } from "transformation-matrix";
1823
+ import { applyToPoint as applyToPoint13 } from "transformation-matrix";
1736
1824
  var DEFAULT_PADDING = { left: 0.2, right: 0.2, top: 0.2, bottom: 0.2 };
1737
1825
  function layerToCopperColor(layer, colorMap) {
1738
1826
  return colorMap.copper[layer] ?? colorMap.copper.top;
@@ -1747,7 +1835,7 @@ function drawPcbCopperText(params) {
1747
1835
  const { ctx, text, realToCanvasMat, colorMap } = params;
1748
1836
  const content = text.text ?? "";
1749
1837
  if (!content) return;
1750
- const [x, y] = applyToPoint12(realToCanvasMat, [
1838
+ const [x, y] = applyToPoint13(realToCanvasMat, [
1751
1839
  text.anchor_position.x,
1752
1840
  text.anchor_position.y
1753
1841
  ]);
@@ -1931,7 +2019,7 @@ function drawPcbNoteText(params) {
1931
2019
  }
1932
2020
 
1933
2021
  // lib/drawer/shapes/dimension-line.ts
1934
- import { applyToPoint as applyToPoint13 } from "transformation-matrix";
2022
+ import { applyToPoint as applyToPoint14 } from "transformation-matrix";
1935
2023
  var TEXT_OFFSET_MULTIPLIER = 1.5;
1936
2024
  var CHARACTER_WIDTH_MULTIPLIER = 0.6;
1937
2025
  var TEXT_INTERSECTION_PADDING_MULTIPLIER = 0.3;
@@ -2064,11 +2152,11 @@ function drawDimensionLine(params) {
2064
2152
  x: (from.x + to.x) / 2 + offsetVector.x,
2065
2153
  y: (from.y + to.y) / 2 + offsetVector.y
2066
2154
  };
2067
- const [screenFromX, screenFromY] = applyToPoint13(realToCanvasMat, [
2155
+ const [screenFromX, screenFromY] = applyToPoint14(realToCanvasMat, [
2068
2156
  fromOffset.x,
2069
2157
  fromOffset.y
2070
2158
  ]);
2071
- const [screenToX, screenToY] = applyToPoint13(realToCanvasMat, [
2159
+ const [screenToX, screenToY] = applyToPoint14(realToCanvasMat, [
2072
2160
  toOffset.x,
2073
2161
  toOffset.y
2074
2162
  ]);
@@ -2158,15 +2246,15 @@ function drawPcbFabricationNoteDimension(params) {
2158
2246
  }
2159
2247
 
2160
2248
  // lib/drawer/elements/pcb-note-line.ts
2161
- import { applyToPoint as applyToPoint14 } from "transformation-matrix";
2249
+ import { applyToPoint as applyToPoint15 } from "transformation-matrix";
2162
2250
  function drawPcbNoteLine(params) {
2163
2251
  const { ctx, line, realToCanvasMat, colorMap } = params;
2164
2252
  const defaultColor = "rgb(89, 148, 220)";
2165
2253
  const color = line.color ?? defaultColor;
2166
2254
  const strokeWidth = line.stroke_width ?? 0.1;
2167
2255
  const isDashed = line.is_dashed ?? false;
2168
- const [x1, y1] = applyToPoint14(realToCanvasMat, [line.x1, line.y1]);
2169
- const [x2, y2] = applyToPoint14(realToCanvasMat, [line.x2, line.y2]);
2256
+ const [x1, y1] = applyToPoint15(realToCanvasMat, [line.x1, line.y1]);
2257
+ const [x2, y2] = applyToPoint15(realToCanvasMat, [line.x2, line.y2]);
2170
2258
  const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
2171
2259
  ctx.save();
2172
2260
  if (isDashed) {
@@ -2271,7 +2359,7 @@ var CircuitToCanvasDrawer = class {
2271
2359
  if (outline && Array.isArray(outline) && outline.length >= 3) {
2272
2360
  const soldermaskColor = this.colorMap.soldermask[layer] ?? this.colorMap.soldermask.top;
2273
2361
  const canvasPoints = outline.map((p) => {
2274
- const [x, y] = applyToPoint15(this.realToCanvasMat, [p.x, p.y]);
2362
+ const [x, y] = applyToPoint16(this.realToCanvasMat, [p.x, p.y]);
2275
2363
  return { x, y };
2276
2364
  });
2277
2365
  this.ctx.beginPath();
@@ -2442,6 +2530,14 @@ var CircuitToCanvasDrawer = class {
2442
2530
  colorMap: this.colorMap
2443
2531
  });
2444
2532
  }
2533
+ if (element.type === "pcb_keepout") {
2534
+ drawPcbKeepout({
2535
+ ctx: this.ctx,
2536
+ keepout: element,
2537
+ realToCanvasMat: this.realToCanvasMat,
2538
+ colorMap: this.colorMap
2539
+ });
2540
+ }
2445
2541
  if (element.type === "pcb_copper_pour") {
2446
2542
  drawPcbCopperPour({
2447
2543
  ctx: this.ctx,
@@ -2566,6 +2662,7 @@ export {
2566
2662
  drawPcbFabricationNoteRect,
2567
2663
  drawPcbFabricationNoteText,
2568
2664
  drawPcbHole,
2665
+ drawPcbKeepout,
2569
2666
  drawPcbNoteDimension,
2570
2667
  drawPcbNotePath,
2571
2668
  drawPcbNoteRect,
@@ -14,6 +14,7 @@ import type {
14
14
  PcbSilkscreenPath,
15
15
  PcbSilkscreenPill,
16
16
  PcbCutout,
17
+ PCBKeepout,
17
18
  PcbCopperPour,
18
19
  PcbCopperText,
19
20
  PcbFabricationNoteText,
@@ -59,6 +60,7 @@ import { drawPcbSilkscreenPath } from "./elements/pcb-silkscreen-path"
59
60
  import { drawPcbSilkscreenOval } from "./elements/pcb-silkscreen-oval"
60
61
  import { drawPcbSilkscreenPill } from "./elements/pcb-silkscreen-pill"
61
62
  import { drawPcbCutout } from "./elements/pcb-cutout"
63
+ import { drawPcbKeepout } from "./elements/pcb-keepout"
62
64
  import { drawPcbCopperPour } from "./elements/pcb-copper-pour"
63
65
  import { drawPcbCopperText } from "./elements/pcb-copper-text"
64
66
  import { drawPcbFabricationNoteText } from "./elements/pcb-fabrication-note-text"
@@ -410,6 +412,15 @@ export class CircuitToCanvasDrawer {
410
412
  })
411
413
  }
412
414
 
415
+ if (element.type === "pcb_keepout") {
416
+ drawPcbKeepout({
417
+ ctx: this.ctx,
418
+ keepout: element as PCBKeepout,
419
+ realToCanvasMat: this.realToCanvasMat,
420
+ colorMap: this.colorMap,
421
+ })
422
+ }
423
+
413
424
  if (element.type === "pcb_copper_pour") {
414
425
  drawPcbCopperPour({
415
426
  ctx: this.ctx,
@@ -56,6 +56,8 @@ export {
56
56
 
57
57
  export { drawPcbCutout, type DrawPcbCutoutParams } from "./pcb-cutout"
58
58
 
59
+ export { drawPcbKeepout, type DrawPcbKeepoutParams } from "./pcb-keepout"
60
+
59
61
  export {
60
62
  drawPcbCopperPour,
61
63
  type DrawPcbCopperPourParams,
@@ -0,0 +1,136 @@
1
+ import type { PCBKeepout } from "circuit-json"
2
+ import type { Matrix } from "transformation-matrix"
3
+ import { applyToPoint } from "transformation-matrix"
4
+ import type { PcbColorMap, CanvasContext } from "../types"
5
+
6
+ export interface DrawPcbKeepoutParams {
7
+ ctx: CanvasContext
8
+ keepout: PCBKeepout
9
+ realToCanvasMat: Matrix
10
+ colorMap: PcbColorMap
11
+ }
12
+
13
+ /**
14
+ * Converts hex color to rgba with transparency
15
+ */
16
+ function hexToRgba(hex: string, alpha: number): string {
17
+ const r = parseInt(hex.slice(1, 3), 16)
18
+ const g = parseInt(hex.slice(3, 5), 16)
19
+ const b = parseInt(hex.slice(5, 7), 16)
20
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`
21
+ }
22
+
23
+ export function drawPcbKeepout(params: DrawPcbKeepoutParams): void {
24
+ const { ctx, keepout, realToCanvasMat, colorMap } = params
25
+
26
+ // Keepout zones are shown with transparent red background and diagonal red lines pattern
27
+ const strokeColor = colorMap.keepout
28
+ const fillColor = hexToRgba(colorMap.keepout, 0.2) // Slight transparent red background
29
+ const hatchSpacing = 1.0 // Spacing between diagonal lines
30
+
31
+ if (keepout.shape === "rect") {
32
+ const [cx, cy] = applyToPoint(realToCanvasMat, [
33
+ keepout.center.x,
34
+ keepout.center.y,
35
+ ])
36
+ const scaledWidth = keepout.width * Math.abs(realToCanvasMat.a)
37
+ const scaledHeight = keepout.height * Math.abs(realToCanvasMat.a)
38
+ const rotation = (keepout as { rotation?: number }).rotation ?? 0
39
+
40
+ ctx.save()
41
+ ctx.translate(cx, cy)
42
+
43
+ if (rotation !== 0) {
44
+ ctx.rotate(-rotation * (Math.PI / 180))
45
+ }
46
+
47
+ // Draw transparent red background
48
+ ctx.beginPath()
49
+ ctx.rect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight)
50
+ ctx.fillStyle = fillColor
51
+ ctx.fill()
52
+
53
+ // Set up clipping path for the rectangle
54
+ ctx.beginPath()
55
+ ctx.rect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight)
56
+ ctx.clip()
57
+
58
+ // Draw diagonal lines pattern at 45 degrees
59
+ const scaledSpacing = hatchSpacing * Math.abs(realToCanvasMat.a)
60
+ const diagonal = Math.sqrt(
61
+ scaledWidth * scaledWidth + scaledHeight * scaledHeight,
62
+ )
63
+ const halfWidth = scaledWidth / 2
64
+ const halfHeight = scaledHeight / 2
65
+
66
+ ctx.strokeStyle = strokeColor
67
+ ctx.lineWidth = 0.15 * Math.abs(realToCanvasMat.a)
68
+ ctx.setLineDash([])
69
+
70
+ // Draw diagonal lines from top-left to bottom-right
71
+ for (
72
+ let offset = -diagonal;
73
+ offset < diagonal * 2;
74
+ offset += scaledSpacing
75
+ ) {
76
+ ctx.beginPath()
77
+ // Line goes from left edge to right edge (or top to bottom)
78
+ const startX = -halfWidth + offset
79
+ const startY = -halfHeight
80
+ const endX = -halfWidth + offset + diagonal
81
+ const endY = -halfHeight + diagonal
82
+
83
+ ctx.moveTo(startX, startY)
84
+ ctx.lineTo(endX, endY)
85
+ ctx.stroke()
86
+ }
87
+
88
+ ctx.restore()
89
+ return
90
+ }
91
+
92
+ if (keepout.shape === "circle") {
93
+ const [cx, cy] = applyToPoint(realToCanvasMat, [
94
+ keepout.center.x,
95
+ keepout.center.y,
96
+ ])
97
+ const scaledRadius = keepout.radius * Math.abs(realToCanvasMat.a)
98
+ const scaledSpacing = hatchSpacing * Math.abs(realToCanvasMat.a)
99
+
100
+ ctx.save()
101
+ ctx.translate(cx, cy)
102
+
103
+ // Draw transparent red background
104
+ ctx.beginPath()
105
+ ctx.arc(0, 0, scaledRadius, 0, Math.PI * 2)
106
+ ctx.fillStyle = fillColor
107
+ ctx.fill()
108
+
109
+ // Set up clipping path for the circle
110
+ ctx.beginPath()
111
+ ctx.arc(0, 0, scaledRadius, 0, Math.PI * 2)
112
+ ctx.clip()
113
+
114
+ // Draw diagonal lines pattern at 45 degrees
115
+ const diagonal = scaledRadius * 2
116
+
117
+ ctx.strokeStyle = strokeColor
118
+ ctx.lineWidth = 0.15 * Math.abs(realToCanvasMat.a)
119
+ ctx.setLineDash([])
120
+
121
+ // Draw diagonal lines from top-left to bottom-right
122
+ for (
123
+ let offset = -diagonal;
124
+ offset < diagonal * 2;
125
+ offset += scaledSpacing
126
+ ) {
127
+ ctx.beginPath()
128
+ ctx.moveTo(offset - diagonal, -diagonal)
129
+ ctx.lineTo(offset + diagonal, diagonal)
130
+ ctx.stroke()
131
+ }
132
+
133
+ ctx.restore()
134
+ return
135
+ }
136
+ }
@@ -46,13 +46,6 @@ export function drawRect(params: DrawRectParams): void {
46
46
  ctx.rotate(-rotation * (Math.PI / 180))
47
47
  }
48
48
 
49
- // Set up dashed line if needed
50
- if (isStrokeDashed && scaledStrokeWidth) {
51
- ctx.setLineDash([scaledStrokeWidth * 2, scaledStrokeWidth * 2])
52
- } else {
53
- ctx.setLineDash([])
54
- }
55
-
56
49
  ctx.beginPath()
57
50
 
58
51
  if (scaledRadius > 0) {
@@ -85,6 +78,12 @@ export function drawRect(params: DrawRectParams): void {
85
78
  }
86
79
 
87
80
  if (stroke && scaledStrokeWidth) {
81
+ // Set up dashed line if needed (after path is drawn, before stroke)
82
+ if (isStrokeDashed) {
83
+ ctx.setLineDash([scaledStrokeWidth * 3, scaledStrokeWidth * 2])
84
+ } else {
85
+ ctx.setLineDash([])
86
+ }
88
87
  ctx.strokeStyle = stroke
89
88
  ctx.lineWidth = scaledStrokeWidth
90
89
  ctx.stroke()
@@ -31,6 +31,7 @@ export interface CanvasContext {
31
31
  moveTo(x: number, y: number): void
32
32
  save(): void
33
33
  restore(): void
34
+ clip(): void
34
35
  translate(x: number, y: number): void
35
36
  rotate(angle: number): void
36
37
  scale(x: number, y: number): void
@@ -89,6 +90,7 @@ export interface PcbColorMap {
89
90
  }
90
91
  substrate: string
91
92
  courtyard: string
93
+ keepout: string
92
94
  }
93
95
 
94
96
  export const DEFAULT_PCB_COLOR_MAP: PcbColorMap = {
@@ -122,6 +124,7 @@ export const DEFAULT_PCB_COLOR_MAP: PcbColorMap = {
122
124
  },
123
125
  boardOutline: "rgba(255, 255, 255, 0.5)",
124
126
  courtyard: "#FF00FF",
127
+ keepout: "#FF6B6B", // Red color for keepout zones
125
128
  }
126
129
 
127
130
  export interface DrawerConfig {
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.41",
4
+ "version": "0.0.42",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsup-node ./lib/index.ts --format esm --dts",
@@ -0,0 +1,51 @@
1
+ import { expect, test } from "bun:test"
2
+ import { createCanvas } from "@napi-rs/canvas"
3
+ import type { AnyCircuitElement } from "circuit-json"
4
+ import { CircuitToCanvasDrawer } from "../../lib/drawer"
5
+
6
+ test("pcb keepout with layer filter", async () => {
7
+ const canvas = createCanvas(2000, 1600)
8
+ const ctx = canvas.getContext("2d")
9
+ const drawer = new CircuitToCanvasDrawer(ctx)
10
+
11
+ drawer.setCameraBounds({ minX: -25, maxX: 25, minY: -20, maxY: 20 })
12
+
13
+ ctx.fillStyle = "#1a1a1a"
14
+ ctx.fillRect(0, 0, canvas.width, canvas.height)
15
+
16
+ const elements: AnyCircuitElement[] = [
17
+ {
18
+ type: "pcb_board",
19
+ pcb_board_id: "pcb_board_0",
20
+ center: { x: 0, y: 0 },
21
+ width: 50,
22
+ height: 40,
23
+ material: "fr1",
24
+ num_layers: 2,
25
+ thickness: 1.2,
26
+ },
27
+ {
28
+ type: "pcb_keepout",
29
+ shape: "rect",
30
+ pcb_keepout_id: "pcb_keepout_top",
31
+ center: { x: -10, y: 0 },
32
+ width: 8,
33
+ height: 8,
34
+ layers: ["top"],
35
+ },
36
+ {
37
+ type: "pcb_keepout",
38
+ shape: "circle",
39
+ pcb_keepout_id: "pcb_keepout_bottom",
40
+ center: { x: 10, y: 0 },
41
+ radius: 4,
42
+ layers: ["bottom"],
43
+ },
44
+ ]
45
+
46
+ drawer.drawElements(elements, { layers: ["top_copper"] })
47
+
48
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
49
+ import.meta.path,
50
+ )
51
+ })
@@ -0,0 +1,51 @@
1
+ import { expect, test } from "bun:test"
2
+ import { createCanvas } from "@napi-rs/canvas"
3
+ import type { AnyCircuitElement } from "circuit-json"
4
+ import { CircuitToCanvasDrawer } from "../../lib/drawer"
5
+
6
+ test("pcb keepout multiple layers", async () => {
7
+ const canvas = createCanvas(2000, 1600)
8
+ const ctx = canvas.getContext("2d")
9
+ const drawer = new CircuitToCanvasDrawer(ctx)
10
+
11
+ drawer.setCameraBounds({ minX: -25, maxX: 25, minY: -20, maxY: 20 })
12
+
13
+ ctx.fillStyle = "#1a1a1a"
14
+ ctx.fillRect(0, 0, canvas.width, canvas.height)
15
+
16
+ const elements: AnyCircuitElement[] = [
17
+ {
18
+ type: "pcb_board",
19
+ pcb_board_id: "pcb_board_0",
20
+ center: { x: 0, y: 0 },
21
+ width: 50,
22
+ height: 40,
23
+ material: "fr1",
24
+ num_layers: 4,
25
+ thickness: 1.2,
26
+ },
27
+ {
28
+ type: "pcb_keepout",
29
+ shape: "rect",
30
+ pcb_keepout_id: "pcb_keepout_multi_layer",
31
+ center: { x: 0, y: 0 },
32
+ width: 10,
33
+ height: 10,
34
+ layers: ["top", "bottom", "inner1"],
35
+ },
36
+ {
37
+ type: "pcb_keepout",
38
+ shape: "circle",
39
+ pcb_keepout_id: "pcb_keepout_circle_multi",
40
+ center: { x: 15, y: 15 },
41
+ radius: 5,
42
+ layers: ["top", "bottom"],
43
+ },
44
+ ]
45
+
46
+ drawer.drawElements(elements)
47
+
48
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
49
+ import.meta.path,
50
+ )
51
+ })
@@ -0,0 +1,60 @@
1
+ import { expect, test } from "bun:test"
2
+ import { createCanvas } from "@napi-rs/canvas"
3
+ import type { AnyCircuitElement } from "circuit-json"
4
+ import { CircuitToCanvasDrawer } from "../../lib/drawer"
5
+
6
+ test("pcb keepout rect and circle", async () => {
7
+ const canvas = createCanvas(2000, 1600)
8
+ const ctx = canvas.getContext("2d")
9
+ const drawer = new CircuitToCanvasDrawer(ctx)
10
+
11
+ drawer.setCameraBounds({ minX: -25, maxX: 25, minY: -20, maxY: 20 })
12
+
13
+ ctx.fillStyle = "#1a1a1a"
14
+ ctx.fillRect(0, 0, canvas.width, canvas.height)
15
+
16
+ const elements: AnyCircuitElement[] = [
17
+ {
18
+ type: "pcb_board",
19
+ pcb_board_id: "pcb_board_0",
20
+ center: { x: 0, y: 0 },
21
+ width: 50,
22
+ height: 40,
23
+ material: "fr1",
24
+ num_layers: 2,
25
+ thickness: 1.2,
26
+ },
27
+ {
28
+ type: "pcb_keepout",
29
+ shape: "rect",
30
+ pcb_keepout_id: "pcb_keepout_rect_0",
31
+ center: { x: -10, y: 10 },
32
+ width: 8,
33
+ height: 5,
34
+ layers: ["top"],
35
+ },
36
+ {
37
+ type: "pcb_keepout",
38
+ shape: "circle",
39
+ pcb_keepout_id: "pcb_keepout_circle_0",
40
+ center: { x: 0, y: 0 },
41
+ radius: 4,
42
+ layers: ["top"],
43
+ },
44
+ {
45
+ type: "pcb_keepout",
46
+ shape: "rect",
47
+ pcb_keepout_id: "pcb_keepout_rect_1",
48
+ center: { x: 10, y: -10 },
49
+ width: 6,
50
+ height: 6,
51
+ layers: ["bottom"],
52
+ },
53
+ ]
54
+
55
+ drawer.drawElements(elements)
56
+
57
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
58
+ import.meta.path,
59
+ )
60
+ })
@@ -0,0 +1,45 @@
1
+ import { expect, test } from "bun:test"
2
+ import { createCanvas } from "@napi-rs/canvas"
3
+ import type { AnyCircuitElement } from "circuit-json"
4
+ import { CircuitToCanvasDrawer } from "../../lib/drawer"
5
+
6
+ test("pcb keepout with pcb_group_id and subcircuit_id", async () => {
7
+ const canvas = createCanvas(2000, 1600)
8
+ const ctx = canvas.getContext("2d")
9
+ const drawer = new CircuitToCanvasDrawer(ctx)
10
+
11
+ drawer.setCameraBounds({ minX: -25, maxX: 25, minY: -20, maxY: 20 })
12
+
13
+ ctx.fillStyle = "#1a1a1a"
14
+ ctx.fillRect(0, 0, canvas.width, canvas.height)
15
+
16
+ const elements: AnyCircuitElement[] = [
17
+ {
18
+ type: "pcb_board",
19
+ pcb_board_id: "pcb_board_0",
20
+ center: { x: 0, y: 0 },
21
+ width: 50,
22
+ height: 40,
23
+ material: "fr1",
24
+ num_layers: 2,
25
+ thickness: 1.2,
26
+ },
27
+ {
28
+ type: "pcb_keepout",
29
+ shape: "rect",
30
+ pcb_keepout_id: "pcb_keepout_grouped",
31
+ center: { x: 0, y: 0 },
32
+ width: 10,
33
+ height: 10,
34
+ layers: ["top"],
35
+ pcb_group_id: "group_1",
36
+ subcircuit_id: "subcircuit_1",
37
+ },
38
+ ]
39
+
40
+ drawer.drawElements(elements)
41
+
42
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
43
+ import.meta.path,
44
+ )
45
+ })