circuit-to-canvas 0.0.1 → 0.0.2
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/.github/workflows/bun-formatcheck.yml +26 -0
- package/.github/workflows/bun-pver-release.yml +77 -0
- package/.github/workflows/bun-test.yml +31 -0
- package/.github/workflows/bun-typecheck.yml +26 -0
- package/LICENSE +21 -0
- package/README.md +2 -0
- package/dist/index.d.ts +146 -2
- package/dist/index.js +620 -0
- package/lib/drawer/CircuitToCanvasDrawer.ts +138 -1
- package/lib/drawer/elements/index.ts +30 -0
- package/lib/drawer/elements/pcb-board.ts +62 -0
- package/lib/drawer/elements/pcb-copper-pour.ts +84 -0
- package/lib/drawer/elements/pcb-cutout.ts +53 -0
- package/lib/drawer/elements/pcb-hole.ts +90 -0
- package/lib/drawer/elements/pcb-silkscreen.ts +171 -0
- package/lib/drawer/elements/pcb-smtpad.ts +108 -0
- package/lib/drawer/elements/pcb-trace.ts +59 -0
- package/lib/drawer/elements/pcb-via.ts +33 -0
- package/lib/drawer/shapes/index.ts +3 -0
- package/lib/drawer/shapes/line.ts +37 -0
- package/lib/drawer/shapes/path.ts +61 -0
- package/lib/drawer/shapes/polygon.ts +38 -0
- package/lib/drawer/types.ts +16 -0
- package/package.json +2 -2
- package/tests/elements/__snapshots__/board-with-elements.snap.png +0 -0
- package/tests/elements/__snapshots__/bottom-layer-pad.snap.png +0 -0
- package/tests/elements/__snapshots__/bottom-layer-trace.snap.png +0 -0
- package/tests/elements/__snapshots__/circular-cutout.snap.png +0 -0
- package/tests/elements/__snapshots__/circular-pad.snap.png +0 -0
- package/tests/elements/__snapshots__/copper-pour-with-trace.snap.png +0 -0
- package/tests/elements/__snapshots__/custom-outline-board.snap.png +0 -0
- package/tests/elements/__snapshots__/multi-segment-trace.snap.png +0 -0
- package/tests/elements/__snapshots__/multiple-traces.snap.png +0 -0
- package/tests/elements/__snapshots__/multiple-vias.snap.png +0 -0
- package/tests/elements/__snapshots__/oval-hole.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-board.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-copper-pour.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-cutout.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-hole.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-silkscreen.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-smtpad.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-trace.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-via.snap.png +0 -0
- package/tests/elements/__snapshots__/pill-hole.snap.png +0 -0
- package/tests/elements/__snapshots__/pill-pad.snap.png +0 -0
- package/tests/elements/__snapshots__/polygon-copper-pour.snap.png +0 -0
- package/tests/elements/__snapshots__/polygon-cutout.snap.png +0 -0
- package/tests/elements/__snapshots__/polygon-pad.snap.png +0 -0
- package/tests/elements/__snapshots__/rect-pad-with-border-radius.snap.png +0 -0
- package/tests/elements/__snapshots__/rotated-rect-pad.snap.png +0 -0
- package/tests/elements/__snapshots__/silkscreen-circle.snap.png +0 -0
- package/tests/elements/__snapshots__/silkscreen-line.snap.png +0 -0
- package/tests/elements/__snapshots__/silkscreen-on-component.snap.png +0 -0
- package/tests/elements/__snapshots__/silkscreen-path.snap.png +0 -0
- package/tests/elements/__snapshots__/silkscreen-rect.snap.png +0 -0
- package/tests/elements/__snapshots__/silkscreen-text-bottom.snap.png +0 -0
- package/tests/elements/__snapshots__/square-hole.snap.png +0 -0
- package/tests/elements/pcb-board.test.ts +155 -0
- package/tests/elements/pcb-copper-pour.test.ts +120 -0
- package/tests/elements/pcb-cutout.test.ts +82 -0
- package/tests/elements/pcb-hole.test.ts +105 -0
- package/tests/elements/pcb-silkscreen.test.ts +245 -0
- package/tests/elements/pcb-smtpad.test.ts +198 -0
- package/tests/elements/pcb-trace.test.ts +116 -0
- package/tests/elements/pcb-via.test.ts +75 -0
- package/.claude/settings.local.json +0 -7
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { PcbSmtPad } from "circuit-json"
|
|
2
|
+
import type { Matrix } from "transformation-matrix"
|
|
3
|
+
import type { PcbColorMap, CanvasContext } from "../types"
|
|
4
|
+
import { drawCircle } from "../shapes/circle"
|
|
5
|
+
import { drawRect } from "../shapes/rect"
|
|
6
|
+
import { drawPill } from "../shapes/pill"
|
|
7
|
+
import { drawPolygon } from "../shapes/polygon"
|
|
8
|
+
|
|
9
|
+
export interface DrawPcbSmtPadParams {
|
|
10
|
+
ctx: CanvasContext
|
|
11
|
+
pad: PcbSmtPad
|
|
12
|
+
transform: Matrix
|
|
13
|
+
colorMap: PcbColorMap
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function layerToColor(layer: string, colorMap: PcbColorMap): string {
|
|
17
|
+
return (
|
|
18
|
+
colorMap.copper[layer as keyof typeof colorMap.copper] ??
|
|
19
|
+
colorMap.copper.top
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function drawPcbSmtPad(params: DrawPcbSmtPadParams): void {
|
|
24
|
+
const { ctx, pad, transform, colorMap } = params
|
|
25
|
+
|
|
26
|
+
const color = layerToColor(pad.layer, colorMap)
|
|
27
|
+
|
|
28
|
+
if (pad.shape === "rect") {
|
|
29
|
+
drawRect({
|
|
30
|
+
ctx,
|
|
31
|
+
center: { x: pad.x, y: pad.y },
|
|
32
|
+
width: pad.width,
|
|
33
|
+
height: pad.height,
|
|
34
|
+
fill: color,
|
|
35
|
+
transform,
|
|
36
|
+
borderRadius:
|
|
37
|
+
(pad as { corner_radius?: number }).corner_radius ??
|
|
38
|
+
pad.rect_border_radius ??
|
|
39
|
+
0,
|
|
40
|
+
})
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (pad.shape === "rotated_rect") {
|
|
45
|
+
drawRect({
|
|
46
|
+
ctx,
|
|
47
|
+
center: { x: pad.x, y: pad.y },
|
|
48
|
+
width: pad.width,
|
|
49
|
+
height: pad.height,
|
|
50
|
+
fill: color,
|
|
51
|
+
transform,
|
|
52
|
+
borderRadius:
|
|
53
|
+
(pad as { corner_radius?: number }).corner_radius ??
|
|
54
|
+
pad.rect_border_radius ??
|
|
55
|
+
0,
|
|
56
|
+
rotation: pad.ccw_rotation ?? 0,
|
|
57
|
+
})
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (pad.shape === "circle") {
|
|
62
|
+
drawCircle({
|
|
63
|
+
ctx,
|
|
64
|
+
center: { x: pad.x, y: pad.y },
|
|
65
|
+
radius: pad.radius,
|
|
66
|
+
fill: color,
|
|
67
|
+
transform,
|
|
68
|
+
})
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (pad.shape === "pill") {
|
|
73
|
+
drawPill({
|
|
74
|
+
ctx,
|
|
75
|
+
center: { x: pad.x, y: pad.y },
|
|
76
|
+
width: pad.width,
|
|
77
|
+
height: pad.height,
|
|
78
|
+
fill: color,
|
|
79
|
+
transform,
|
|
80
|
+
})
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (pad.shape === "rotated_pill") {
|
|
85
|
+
drawPill({
|
|
86
|
+
ctx,
|
|
87
|
+
center: { x: pad.x, y: pad.y },
|
|
88
|
+
width: pad.width,
|
|
89
|
+
height: pad.height,
|
|
90
|
+
fill: color,
|
|
91
|
+
transform,
|
|
92
|
+
rotation: pad.ccw_rotation ?? 0,
|
|
93
|
+
})
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (pad.shape === "polygon") {
|
|
98
|
+
if (pad.points && pad.points.length >= 3) {
|
|
99
|
+
drawPolygon({
|
|
100
|
+
ctx,
|
|
101
|
+
points: pad.points,
|
|
102
|
+
fill: color,
|
|
103
|
+
transform,
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { PCBTrace } from "circuit-json"
|
|
2
|
+
import type { Matrix } from "transformation-matrix"
|
|
3
|
+
import type { PcbColorMap, CanvasContext } from "../types"
|
|
4
|
+
import { drawLine } from "../shapes/line"
|
|
5
|
+
|
|
6
|
+
export interface DrawPcbTraceParams {
|
|
7
|
+
ctx: CanvasContext
|
|
8
|
+
trace: PCBTrace
|
|
9
|
+
transform: Matrix
|
|
10
|
+
colorMap: PcbColorMap
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function layerToColor(layer: string, colorMap: PcbColorMap): string {
|
|
14
|
+
return (
|
|
15
|
+
colorMap.copper[layer as keyof typeof colorMap.copper] ??
|
|
16
|
+
colorMap.copper.top
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function drawPcbTrace(params: DrawPcbTraceParams): void {
|
|
21
|
+
const { ctx, trace, transform, colorMap } = params
|
|
22
|
+
|
|
23
|
+
if (!trace.route || !Array.isArray(trace.route) || trace.route.length < 2) {
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Draw each segment of the trace
|
|
28
|
+
for (let i = 0; i < trace.route.length - 1; i++) {
|
|
29
|
+
const start = trace.route[i]
|
|
30
|
+
const end = trace.route[i + 1]
|
|
31
|
+
|
|
32
|
+
if (!start || !end) continue
|
|
33
|
+
|
|
34
|
+
// Get the layer from either point
|
|
35
|
+
const layer =
|
|
36
|
+
"layer" in start
|
|
37
|
+
? start.layer
|
|
38
|
+
: "layer" in end
|
|
39
|
+
? (end as any).layer
|
|
40
|
+
: null
|
|
41
|
+
if (!layer) continue
|
|
42
|
+
|
|
43
|
+
const color = layerToColor(layer, colorMap)
|
|
44
|
+
|
|
45
|
+
// Get the trace width from either point
|
|
46
|
+
const traceWidth =
|
|
47
|
+
"width" in start ? start.width : "width" in end ? (end as any).width : 0.1
|
|
48
|
+
|
|
49
|
+
drawLine({
|
|
50
|
+
ctx,
|
|
51
|
+
start: { x: start.x, y: start.y },
|
|
52
|
+
end: { x: end.x, y: end.y },
|
|
53
|
+
strokeWidth: traceWidth,
|
|
54
|
+
stroke: color,
|
|
55
|
+
transform,
|
|
56
|
+
lineCap: "round",
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PCBVia } from "circuit-json"
|
|
2
|
+
import type { Matrix } from "transformation-matrix"
|
|
3
|
+
import type { PcbColorMap, CanvasContext } from "../types"
|
|
4
|
+
import { drawCircle } from "../shapes/circle"
|
|
5
|
+
|
|
6
|
+
export interface DrawPcbViaParams {
|
|
7
|
+
ctx: CanvasContext
|
|
8
|
+
via: PCBVia
|
|
9
|
+
transform: Matrix
|
|
10
|
+
colorMap: PcbColorMap
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function drawPcbVia(params: DrawPcbViaParams): void {
|
|
14
|
+
const { ctx, via, transform, colorMap } = params
|
|
15
|
+
|
|
16
|
+
// Draw outer copper ring
|
|
17
|
+
drawCircle({
|
|
18
|
+
ctx,
|
|
19
|
+
center: { x: via.x, y: via.y },
|
|
20
|
+
radius: via.outer_diameter / 2,
|
|
21
|
+
fill: colorMap.copper.top,
|
|
22
|
+
transform,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
// Draw inner drill hole
|
|
26
|
+
drawCircle({
|
|
27
|
+
ctx,
|
|
28
|
+
center: { x: via.x, y: via.y },
|
|
29
|
+
radius: via.hole_diameter / 2,
|
|
30
|
+
fill: colorMap.drill,
|
|
31
|
+
transform,
|
|
32
|
+
})
|
|
33
|
+
}
|
|
@@ -2,3 +2,6 @@ export { drawCircle, type DrawCircleParams } from "./circle"
|
|
|
2
2
|
export { drawRect, type DrawRectParams } from "./rect"
|
|
3
3
|
export { drawOval, type DrawOvalParams } from "./oval"
|
|
4
4
|
export { drawPill, type DrawPillParams } from "./pill"
|
|
5
|
+
export { drawPolygon, type DrawPolygonParams } from "./polygon"
|
|
6
|
+
export { drawLine, type DrawLineParams } from "./line"
|
|
7
|
+
export { drawPath, type DrawPathParams } from "./path"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Matrix } from "transformation-matrix"
|
|
2
|
+
import { applyToPoint } from "transformation-matrix"
|
|
3
|
+
import type { CanvasContext } from "../types"
|
|
4
|
+
|
|
5
|
+
export interface DrawLineParams {
|
|
6
|
+
ctx: CanvasContext
|
|
7
|
+
start: { x: number; y: number }
|
|
8
|
+
end: { x: number; y: number }
|
|
9
|
+
strokeWidth: number
|
|
10
|
+
stroke: string
|
|
11
|
+
transform: Matrix
|
|
12
|
+
lineCap?: "butt" | "round" | "square"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function drawLine(params: DrawLineParams): void {
|
|
16
|
+
const {
|
|
17
|
+
ctx,
|
|
18
|
+
start,
|
|
19
|
+
end,
|
|
20
|
+
strokeWidth,
|
|
21
|
+
stroke,
|
|
22
|
+
transform,
|
|
23
|
+
lineCap = "round",
|
|
24
|
+
} = params
|
|
25
|
+
|
|
26
|
+
const [x1, y1] = applyToPoint(transform, [start.x, start.y])
|
|
27
|
+
const [x2, y2] = applyToPoint(transform, [end.x, end.y])
|
|
28
|
+
const scaledStrokeWidth = strokeWidth * Math.abs(transform.a)
|
|
29
|
+
|
|
30
|
+
ctx.beginPath()
|
|
31
|
+
ctx.moveTo(x1, y1)
|
|
32
|
+
ctx.lineTo(x2, y2)
|
|
33
|
+
ctx.lineWidth = scaledStrokeWidth
|
|
34
|
+
ctx.strokeStyle = stroke
|
|
35
|
+
ctx.lineCap = lineCap
|
|
36
|
+
ctx.stroke()
|
|
37
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Matrix } from "transformation-matrix"
|
|
2
|
+
import { applyToPoint } from "transformation-matrix"
|
|
3
|
+
import type { CanvasContext } from "../types"
|
|
4
|
+
|
|
5
|
+
export interface DrawPathParams {
|
|
6
|
+
ctx: CanvasContext
|
|
7
|
+
points: Array<{ x: number; y: number }>
|
|
8
|
+
fill?: string
|
|
9
|
+
stroke?: string
|
|
10
|
+
strokeWidth?: number
|
|
11
|
+
transform: Matrix
|
|
12
|
+
closePath?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function drawPath(params: DrawPathParams): void {
|
|
16
|
+
const {
|
|
17
|
+
ctx,
|
|
18
|
+
points,
|
|
19
|
+
fill,
|
|
20
|
+
stroke,
|
|
21
|
+
strokeWidth = 1,
|
|
22
|
+
transform,
|
|
23
|
+
closePath = false,
|
|
24
|
+
} = params
|
|
25
|
+
|
|
26
|
+
if (points.length < 2) return
|
|
27
|
+
|
|
28
|
+
ctx.beginPath()
|
|
29
|
+
|
|
30
|
+
const transformedPoints = points.map((p) =>
|
|
31
|
+
applyToPoint(transform, [p.x, p.y]),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const firstPoint = transformedPoints[0]
|
|
35
|
+
if (!firstPoint) return
|
|
36
|
+
const [firstX, firstY] = firstPoint
|
|
37
|
+
ctx.moveTo(firstX, firstY)
|
|
38
|
+
|
|
39
|
+
for (let i = 1; i < transformedPoints.length; i++) {
|
|
40
|
+
const point = transformedPoints[i]
|
|
41
|
+
if (!point) continue
|
|
42
|
+
const [x, y] = point
|
|
43
|
+
ctx.lineTo(x, y)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (closePath) {
|
|
47
|
+
ctx.closePath()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (fill) {
|
|
51
|
+
ctx.fillStyle = fill
|
|
52
|
+
ctx.fill()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (stroke) {
|
|
56
|
+
const scaledStrokeWidth = strokeWidth * Math.abs(transform.a)
|
|
57
|
+
ctx.strokeStyle = stroke
|
|
58
|
+
ctx.lineWidth = scaledStrokeWidth
|
|
59
|
+
ctx.stroke()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Matrix } from "transformation-matrix"
|
|
2
|
+
import { applyToPoint } from "transformation-matrix"
|
|
3
|
+
import type { CanvasContext } from "../types"
|
|
4
|
+
|
|
5
|
+
export interface DrawPolygonParams {
|
|
6
|
+
ctx: CanvasContext
|
|
7
|
+
points: Array<{ x: number; y: number }>
|
|
8
|
+
fill: string
|
|
9
|
+
transform: Matrix
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function drawPolygon(params: DrawPolygonParams): void {
|
|
13
|
+
const { ctx, points, fill, transform } = params
|
|
14
|
+
|
|
15
|
+
if (points.length < 3) return
|
|
16
|
+
|
|
17
|
+
ctx.beginPath()
|
|
18
|
+
|
|
19
|
+
const transformedPoints = points.map((p) =>
|
|
20
|
+
applyToPoint(transform, [p.x, p.y]),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
const firstPoint = transformedPoints[0]
|
|
24
|
+
if (!firstPoint) return
|
|
25
|
+
const [firstX, firstY] = firstPoint
|
|
26
|
+
ctx.moveTo(firstX, firstY)
|
|
27
|
+
|
|
28
|
+
for (let i = 1; i < transformedPoints.length; i++) {
|
|
29
|
+
const point = transformedPoints[i]
|
|
30
|
+
if (!point) continue
|
|
31
|
+
const [x, y] = point
|
|
32
|
+
ctx.lineTo(x, y)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
ctx.closePath()
|
|
36
|
+
ctx.fillStyle = fill
|
|
37
|
+
ctx.fill()
|
|
38
|
+
}
|
package/lib/drawer/types.ts
CHANGED
|
@@ -25,6 +25,7 @@ export interface CanvasContext {
|
|
|
25
25
|
endAngle: number,
|
|
26
26
|
): void
|
|
27
27
|
fill(): void
|
|
28
|
+
stroke(): void
|
|
28
29
|
rect(x: number, y: number, w: number, h: number): void
|
|
29
30
|
lineTo(x: number, y: number): void
|
|
30
31
|
moveTo(x: number, y: number): void
|
|
@@ -32,8 +33,23 @@ export interface CanvasContext {
|
|
|
32
33
|
restore(): void
|
|
33
34
|
translate(x: number, y: number): void
|
|
34
35
|
rotate(angle: number): void
|
|
36
|
+
scale(x: number, y: number): void
|
|
35
37
|
fillStyle: string | CanvasGradient | CanvasPattern
|
|
38
|
+
strokeStyle: string | CanvasGradient | CanvasPattern
|
|
39
|
+
lineWidth: number
|
|
40
|
+
lineCap: "butt" | "round" | "square"
|
|
41
|
+
lineJoin: "bevel" | "round" | "miter"
|
|
36
42
|
canvas: { width: number; height: number }
|
|
43
|
+
fillText(text: string, x: number, y: number): void
|
|
44
|
+
font: string
|
|
45
|
+
textAlign: "start" | "end" | "left" | "right" | "center"
|
|
46
|
+
textBaseline:
|
|
47
|
+
| "top"
|
|
48
|
+
| "hanging"
|
|
49
|
+
| "middle"
|
|
50
|
+
| "alphabetic"
|
|
51
|
+
| "ideographic"
|
|
52
|
+
| "bottom"
|
|
37
53
|
}
|
|
38
54
|
|
|
39
55
|
export type CopperLayerName =
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "circuit-to-canvas",
|
|
3
3
|
"main": "dist/index.js",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsup-node ./lib/index.ts --format esm --dts",
|
|
8
8
|
"format": "biome format --write .",
|
|
9
|
-
"format:check": "biome format
|
|
9
|
+
"format:check": "biome format ."
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@biomejs/biome": "^2.3.8",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { createCanvas } from "canvas"
|
|
3
|
+
import type { PcbBoard } from "circuit-json"
|
|
4
|
+
import { CircuitToCanvasDrawer } from "../../lib/drawer"
|
|
5
|
+
|
|
6
|
+
test("draw rectangular board", 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 board: PcbBoard = {
|
|
15
|
+
type: "pcb_board",
|
|
16
|
+
pcb_board_id: "board1",
|
|
17
|
+
center: { x: 50, y: 50 },
|
|
18
|
+
width: 80,
|
|
19
|
+
height: 60,
|
|
20
|
+
thickness: 1.6,
|
|
21
|
+
num_layers: 2,
|
|
22
|
+
material: "fr4",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
drawer.drawElements([board])
|
|
26
|
+
|
|
27
|
+
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
28
|
+
import.meta.path,
|
|
29
|
+
)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test("draw board with custom outline", async () => {
|
|
33
|
+
const canvas = createCanvas(100, 100)
|
|
34
|
+
const ctx = canvas.getContext("2d")
|
|
35
|
+
const drawer = new CircuitToCanvasDrawer(ctx)
|
|
36
|
+
|
|
37
|
+
ctx.fillStyle = "#1a1a1a"
|
|
38
|
+
ctx.fillRect(0, 0, 100, 100)
|
|
39
|
+
|
|
40
|
+
// L-shaped board outline
|
|
41
|
+
const board: PcbBoard = {
|
|
42
|
+
type: "pcb_board",
|
|
43
|
+
pcb_board_id: "board1",
|
|
44
|
+
center: { x: 50, y: 50 },
|
|
45
|
+
width: 80,
|
|
46
|
+
height: 80,
|
|
47
|
+
thickness: 1.6,
|
|
48
|
+
num_layers: 2,
|
|
49
|
+
material: "fr4",
|
|
50
|
+
outline: [
|
|
51
|
+
{ x: 10, y: 10 },
|
|
52
|
+
{ x: 90, y: 10 },
|
|
53
|
+
{ x: 90, y: 50 },
|
|
54
|
+
{ x: 50, y: 50 },
|
|
55
|
+
{ x: 50, y: 90 },
|
|
56
|
+
{ x: 10, y: 90 },
|
|
57
|
+
],
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
drawer.drawElements([board])
|
|
61
|
+
|
|
62
|
+
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
63
|
+
import.meta.path,
|
|
64
|
+
"custom-outline-board",
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test("draw board with elements", async () => {
|
|
69
|
+
const canvas = createCanvas(200, 150)
|
|
70
|
+
const ctx = canvas.getContext("2d")
|
|
71
|
+
const drawer = new CircuitToCanvasDrawer(ctx)
|
|
72
|
+
|
|
73
|
+
ctx.fillStyle = "#1a1a1a"
|
|
74
|
+
ctx.fillRect(0, 0, 200, 150)
|
|
75
|
+
|
|
76
|
+
const elements = [
|
|
77
|
+
{
|
|
78
|
+
type: "pcb_board" as const,
|
|
79
|
+
pcb_board_id: "board1",
|
|
80
|
+
center: { x: 100, y: 75 },
|
|
81
|
+
width: 180,
|
|
82
|
+
height: 130,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: "pcb_smtpad" as const,
|
|
86
|
+
pcb_smtpad_id: "pad1",
|
|
87
|
+
shape: "rect" as const,
|
|
88
|
+
x: 50,
|
|
89
|
+
y: 50,
|
|
90
|
+
width: 20,
|
|
91
|
+
height: 10,
|
|
92
|
+
layer: "top" as const,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: "pcb_smtpad" as const,
|
|
96
|
+
pcb_smtpad_id: "pad2",
|
|
97
|
+
shape: "rect" as const,
|
|
98
|
+
x: 150,
|
|
99
|
+
y: 50,
|
|
100
|
+
width: 20,
|
|
101
|
+
height: 10,
|
|
102
|
+
layer: "top" as const,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
type: "pcb_trace" as const,
|
|
106
|
+
pcb_trace_id: "trace1",
|
|
107
|
+
route: [
|
|
108
|
+
{
|
|
109
|
+
route_type: "wire" as const,
|
|
110
|
+
x: 50,
|
|
111
|
+
y: 50,
|
|
112
|
+
width: 3,
|
|
113
|
+
layer: "top" as const,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
route_type: "wire" as const,
|
|
117
|
+
x: 100,
|
|
118
|
+
y: 50,
|
|
119
|
+
width: 3,
|
|
120
|
+
layer: "top" as const,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
route_type: "wire" as const,
|
|
124
|
+
x: 100,
|
|
125
|
+
y: 100,
|
|
126
|
+
width: 3,
|
|
127
|
+
layer: "top" as const,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
route_type: "wire" as const,
|
|
131
|
+
x: 150,
|
|
132
|
+
y: 100,
|
|
133
|
+
width: 3,
|
|
134
|
+
layer: "top" as const,
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: "pcb_via" as const,
|
|
140
|
+
pcb_via_id: "via1",
|
|
141
|
+
x: 100,
|
|
142
|
+
y: 75,
|
|
143
|
+
outer_diameter: 10,
|
|
144
|
+
hole_diameter: 5,
|
|
145
|
+
layers: ["top", "bottom"],
|
|
146
|
+
},
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
drawer.drawElements(elements)
|
|
150
|
+
|
|
151
|
+
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
152
|
+
import.meta.path,
|
|
153
|
+
"board-with-elements",
|
|
154
|
+
)
|
|
155
|
+
})
|