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
|
@@ -1,4 +1,19 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AnyCircuitElement,
|
|
3
|
+
PcbPlatedHole,
|
|
4
|
+
PCBVia,
|
|
5
|
+
PCBHole,
|
|
6
|
+
PcbSmtPad,
|
|
7
|
+
PCBTrace,
|
|
8
|
+
PcbBoard,
|
|
9
|
+
PcbSilkscreenText,
|
|
10
|
+
PcbSilkscreenRect,
|
|
11
|
+
PcbSilkscreenCircle,
|
|
12
|
+
PcbSilkscreenLine,
|
|
13
|
+
PcbSilkscreenPath,
|
|
14
|
+
PcbCutout,
|
|
15
|
+
PcbCopperPour,
|
|
16
|
+
} from "circuit-json"
|
|
2
17
|
import { identity, compose, translate, scale } from "transformation-matrix"
|
|
3
18
|
import type { Matrix } from "transformation-matrix"
|
|
4
19
|
import {
|
|
@@ -9,6 +24,20 @@ import {
|
|
|
9
24
|
DEFAULT_PCB_COLOR_MAP,
|
|
10
25
|
} from "./types"
|
|
11
26
|
import { drawPcbPlatedHole } from "./elements/pcb-plated-hole"
|
|
27
|
+
import { drawPcbVia } from "./elements/pcb-via"
|
|
28
|
+
import { drawPcbHole } from "./elements/pcb-hole"
|
|
29
|
+
import { drawPcbSmtPad } from "./elements/pcb-smtpad"
|
|
30
|
+
import { drawPcbTrace } from "./elements/pcb-trace"
|
|
31
|
+
import { drawPcbBoard } from "./elements/pcb-board"
|
|
32
|
+
import {
|
|
33
|
+
drawPcbSilkscreenText,
|
|
34
|
+
drawPcbSilkscreenRect,
|
|
35
|
+
drawPcbSilkscreenCircle,
|
|
36
|
+
drawPcbSilkscreenLine,
|
|
37
|
+
drawPcbSilkscreenPath,
|
|
38
|
+
} from "./elements/pcb-silkscreen"
|
|
39
|
+
import { drawPcbCutout } from "./elements/pcb-cutout"
|
|
40
|
+
import { drawPcbCopperPour } from "./elements/pcb-copper-pour"
|
|
12
41
|
|
|
13
42
|
export interface DrawElementsOptions {
|
|
14
43
|
layers?: string[]
|
|
@@ -115,5 +144,113 @@ export class CircuitToCanvasDrawer {
|
|
|
115
144
|
colorMap: this.colorMap,
|
|
116
145
|
})
|
|
117
146
|
}
|
|
147
|
+
|
|
148
|
+
if (element.type === "pcb_via") {
|
|
149
|
+
drawPcbVia({
|
|
150
|
+
ctx: this.ctx,
|
|
151
|
+
via: element as PCBVia,
|
|
152
|
+
transform: this.realToCanvasMat,
|
|
153
|
+
colorMap: this.colorMap,
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (element.type === "pcb_hole") {
|
|
158
|
+
drawPcbHole({
|
|
159
|
+
ctx: this.ctx,
|
|
160
|
+
hole: element as PCBHole,
|
|
161
|
+
transform: this.realToCanvasMat,
|
|
162
|
+
colorMap: this.colorMap,
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (element.type === "pcb_smtpad") {
|
|
167
|
+
drawPcbSmtPad({
|
|
168
|
+
ctx: this.ctx,
|
|
169
|
+
pad: element as PcbSmtPad,
|
|
170
|
+
transform: this.realToCanvasMat,
|
|
171
|
+
colorMap: this.colorMap,
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (element.type === "pcb_trace") {
|
|
176
|
+
drawPcbTrace({
|
|
177
|
+
ctx: this.ctx,
|
|
178
|
+
trace: element as PCBTrace,
|
|
179
|
+
transform: this.realToCanvasMat,
|
|
180
|
+
colorMap: this.colorMap,
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (element.type === "pcb_board") {
|
|
185
|
+
drawPcbBoard({
|
|
186
|
+
ctx: this.ctx,
|
|
187
|
+
board: element as PcbBoard,
|
|
188
|
+
transform: this.realToCanvasMat,
|
|
189
|
+
colorMap: this.colorMap,
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (element.type === "pcb_silkscreen_text") {
|
|
194
|
+
drawPcbSilkscreenText({
|
|
195
|
+
ctx: this.ctx,
|
|
196
|
+
text: element as PcbSilkscreenText,
|
|
197
|
+
transform: this.realToCanvasMat,
|
|
198
|
+
colorMap: this.colorMap,
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (element.type === "pcb_silkscreen_rect") {
|
|
203
|
+
drawPcbSilkscreenRect({
|
|
204
|
+
ctx: this.ctx,
|
|
205
|
+
rect: element as PcbSilkscreenRect,
|
|
206
|
+
transform: this.realToCanvasMat,
|
|
207
|
+
colorMap: this.colorMap,
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (element.type === "pcb_silkscreen_circle") {
|
|
212
|
+
drawPcbSilkscreenCircle({
|
|
213
|
+
ctx: this.ctx,
|
|
214
|
+
circle: element as PcbSilkscreenCircle,
|
|
215
|
+
transform: this.realToCanvasMat,
|
|
216
|
+
colorMap: this.colorMap,
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (element.type === "pcb_silkscreen_line") {
|
|
221
|
+
drawPcbSilkscreenLine({
|
|
222
|
+
ctx: this.ctx,
|
|
223
|
+
line: element as PcbSilkscreenLine,
|
|
224
|
+
transform: this.realToCanvasMat,
|
|
225
|
+
colorMap: this.colorMap,
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (element.type === "pcb_silkscreen_path") {
|
|
230
|
+
drawPcbSilkscreenPath({
|
|
231
|
+
ctx: this.ctx,
|
|
232
|
+
path: element as PcbSilkscreenPath,
|
|
233
|
+
transform: this.realToCanvasMat,
|
|
234
|
+
colorMap: this.colorMap,
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (element.type === "pcb_cutout") {
|
|
239
|
+
drawPcbCutout({
|
|
240
|
+
ctx: this.ctx,
|
|
241
|
+
cutout: element as PcbCutout,
|
|
242
|
+
transform: this.realToCanvasMat,
|
|
243
|
+
colorMap: this.colorMap,
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (element.type === "pcb_copper_pour") {
|
|
248
|
+
drawPcbCopperPour({
|
|
249
|
+
ctx: this.ctx,
|
|
250
|
+
pour: element as PcbCopperPour,
|
|
251
|
+
transform: this.realToCanvasMat,
|
|
252
|
+
colorMap: this.colorMap,
|
|
253
|
+
})
|
|
254
|
+
}
|
|
118
255
|
}
|
|
119
256
|
}
|
|
@@ -2,3 +2,33 @@ export {
|
|
|
2
2
|
drawPcbPlatedHole,
|
|
3
3
|
type DrawPcbPlatedHoleParams,
|
|
4
4
|
} from "./pcb-plated-hole"
|
|
5
|
+
|
|
6
|
+
export { drawPcbVia, type DrawPcbViaParams } from "./pcb-via"
|
|
7
|
+
|
|
8
|
+
export { drawPcbHole, type DrawPcbHoleParams } from "./pcb-hole"
|
|
9
|
+
|
|
10
|
+
export { drawPcbSmtPad, type DrawPcbSmtPadParams } from "./pcb-smtpad"
|
|
11
|
+
|
|
12
|
+
export { drawPcbTrace, type DrawPcbTraceParams } from "./pcb-trace"
|
|
13
|
+
|
|
14
|
+
export { drawPcbBoard, type DrawPcbBoardParams } from "./pcb-board"
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
drawPcbSilkscreenText,
|
|
18
|
+
drawPcbSilkscreenRect,
|
|
19
|
+
drawPcbSilkscreenCircle,
|
|
20
|
+
drawPcbSilkscreenLine,
|
|
21
|
+
drawPcbSilkscreenPath,
|
|
22
|
+
type DrawPcbSilkscreenTextParams,
|
|
23
|
+
type DrawPcbSilkscreenRectParams,
|
|
24
|
+
type DrawPcbSilkscreenCircleParams,
|
|
25
|
+
type DrawPcbSilkscreenLineParams,
|
|
26
|
+
type DrawPcbSilkscreenPathParams,
|
|
27
|
+
} from "./pcb-silkscreen"
|
|
28
|
+
|
|
29
|
+
export { drawPcbCutout, type DrawPcbCutoutParams } from "./pcb-cutout"
|
|
30
|
+
|
|
31
|
+
export {
|
|
32
|
+
drawPcbCopperPour,
|
|
33
|
+
type DrawPcbCopperPourParams,
|
|
34
|
+
} from "./pcb-copper-pour"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { PcbBoard } from "circuit-json"
|
|
2
|
+
import type { Matrix } from "transformation-matrix"
|
|
3
|
+
import type { PcbColorMap, CanvasContext } from "../types"
|
|
4
|
+
import { drawPath } from "../shapes/path"
|
|
5
|
+
import { drawRect } from "../shapes/rect"
|
|
6
|
+
|
|
7
|
+
export interface DrawPcbBoardParams {
|
|
8
|
+
ctx: CanvasContext
|
|
9
|
+
board: PcbBoard
|
|
10
|
+
transform: Matrix
|
|
11
|
+
colorMap: PcbColorMap
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function drawPcbBoard(params: DrawPcbBoardParams): void {
|
|
15
|
+
const { ctx, board, transform, colorMap } = params
|
|
16
|
+
const { width, height, center, outline } = board
|
|
17
|
+
|
|
18
|
+
// If the board has a custom outline, draw it as a path
|
|
19
|
+
if (outline && Array.isArray(outline) && outline.length >= 3) {
|
|
20
|
+
drawPath({
|
|
21
|
+
ctx,
|
|
22
|
+
points: outline.map((p) => ({ x: p.x, y: p.y })),
|
|
23
|
+
stroke: colorMap.boardOutline,
|
|
24
|
+
strokeWidth: 0.1,
|
|
25
|
+
transform,
|
|
26
|
+
closePath: true,
|
|
27
|
+
})
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Otherwise draw a rectangle
|
|
32
|
+
if (width !== undefined && height !== undefined && center) {
|
|
33
|
+
// Draw the board outline as a rectangle stroke
|
|
34
|
+
drawRect({
|
|
35
|
+
ctx,
|
|
36
|
+
center,
|
|
37
|
+
width,
|
|
38
|
+
height,
|
|
39
|
+
fill: "transparent",
|
|
40
|
+
transform,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// Draw the outline stroke separately using path
|
|
44
|
+
const halfWidth = width / 2
|
|
45
|
+
const halfHeight = height / 2
|
|
46
|
+
const corners = [
|
|
47
|
+
{ x: center.x - halfWidth, y: center.y - halfHeight },
|
|
48
|
+
{ x: center.x + halfWidth, y: center.y - halfHeight },
|
|
49
|
+
{ x: center.x + halfWidth, y: center.y + halfHeight },
|
|
50
|
+
{ x: center.x - halfWidth, y: center.y + halfHeight },
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
drawPath({
|
|
54
|
+
ctx,
|
|
55
|
+
points: corners,
|
|
56
|
+
stroke: colorMap.boardOutline,
|
|
57
|
+
strokeWidth: 0.1,
|
|
58
|
+
transform,
|
|
59
|
+
closePath: true,
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { PcbCopperPour } from "circuit-json"
|
|
2
|
+
import type { Matrix } from "transformation-matrix"
|
|
3
|
+
import { applyToPoint } from "transformation-matrix"
|
|
4
|
+
import type { PcbColorMap, CanvasContext } from "../types"
|
|
5
|
+
import { drawRect } from "../shapes/rect"
|
|
6
|
+
import { drawPolygon } from "../shapes/polygon"
|
|
7
|
+
|
|
8
|
+
export interface DrawPcbCopperPourParams {
|
|
9
|
+
ctx: CanvasContext
|
|
10
|
+
pour: PcbCopperPour
|
|
11
|
+
transform: Matrix
|
|
12
|
+
colorMap: PcbColorMap
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function layerToColor(layer: string, colorMap: PcbColorMap): string {
|
|
16
|
+
return (
|
|
17
|
+
colorMap.copper[layer as keyof typeof colorMap.copper] ??
|
|
18
|
+
colorMap.copper.top
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function drawPcbCopperPour(params: DrawPcbCopperPourParams): void {
|
|
23
|
+
const { ctx, pour, transform, colorMap } = params
|
|
24
|
+
|
|
25
|
+
const color = layerToColor(pour.layer, colorMap)
|
|
26
|
+
|
|
27
|
+
// Save context to apply opacity
|
|
28
|
+
ctx.save()
|
|
29
|
+
|
|
30
|
+
if (pour.shape === "rect") {
|
|
31
|
+
// Draw the copper pour rectangle with 50% opacity
|
|
32
|
+
const [cx, cy] = applyToPoint(transform, [pour.center.x, pour.center.y])
|
|
33
|
+
const scaledWidth = pour.width * Math.abs(transform.a)
|
|
34
|
+
const scaledHeight = pour.height * Math.abs(transform.a)
|
|
35
|
+
|
|
36
|
+
ctx.translate(cx, cy)
|
|
37
|
+
|
|
38
|
+
if (pour.rotation) {
|
|
39
|
+
ctx.rotate(-pour.rotation * (Math.PI / 180))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
ctx.beginPath()
|
|
43
|
+
ctx.rect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight)
|
|
44
|
+
ctx.fillStyle = color
|
|
45
|
+
;(ctx as any).globalAlpha = 0.5
|
|
46
|
+
ctx.fill()
|
|
47
|
+
ctx.restore()
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (pour.shape === "polygon") {
|
|
52
|
+
if (pour.points && pour.points.length >= 3) {
|
|
53
|
+
const transformedPoints = pour.points.map((p: { x: number; y: number }) =>
|
|
54
|
+
applyToPoint(transform, [p.x, p.y]),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const firstPoint = transformedPoints[0]
|
|
58
|
+
if (!firstPoint) {
|
|
59
|
+
ctx.restore()
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
ctx.beginPath()
|
|
64
|
+
const [firstX, firstY] = firstPoint
|
|
65
|
+
ctx.moveTo(firstX, firstY)
|
|
66
|
+
|
|
67
|
+
for (let i = 1; i < transformedPoints.length; i++) {
|
|
68
|
+
const point = transformedPoints[i]
|
|
69
|
+
if (!point) continue
|
|
70
|
+
const [x, y] = point
|
|
71
|
+
ctx.lineTo(x, y)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
ctx.closePath()
|
|
75
|
+
ctx.fillStyle = color
|
|
76
|
+
;(ctx as any).globalAlpha = 0.5
|
|
77
|
+
ctx.fill()
|
|
78
|
+
}
|
|
79
|
+
ctx.restore()
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
ctx.restore()
|
|
84
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { PcbCutout } from "circuit-json"
|
|
2
|
+
import type { Matrix } from "transformation-matrix"
|
|
3
|
+
import type { PcbColorMap, CanvasContext } from "../types"
|
|
4
|
+
import { drawRect } from "../shapes/rect"
|
|
5
|
+
import { drawCircle } from "../shapes/circle"
|
|
6
|
+
import { drawPolygon } from "../shapes/polygon"
|
|
7
|
+
|
|
8
|
+
export interface DrawPcbCutoutParams {
|
|
9
|
+
ctx: CanvasContext
|
|
10
|
+
cutout: PcbCutout
|
|
11
|
+
transform: Matrix
|
|
12
|
+
colorMap: PcbColorMap
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function drawPcbCutout(params: DrawPcbCutoutParams): void {
|
|
16
|
+
const { ctx, cutout, transform, colorMap } = params
|
|
17
|
+
|
|
18
|
+
if (cutout.shape === "rect") {
|
|
19
|
+
drawRect({
|
|
20
|
+
ctx,
|
|
21
|
+
center: cutout.center,
|
|
22
|
+
width: cutout.width,
|
|
23
|
+
height: cutout.height,
|
|
24
|
+
fill: colorMap.drill,
|
|
25
|
+
transform,
|
|
26
|
+
rotation: cutout.rotation ?? 0,
|
|
27
|
+
})
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (cutout.shape === "circle") {
|
|
32
|
+
drawCircle({
|
|
33
|
+
ctx,
|
|
34
|
+
center: cutout.center,
|
|
35
|
+
radius: cutout.radius,
|
|
36
|
+
fill: colorMap.drill,
|
|
37
|
+
transform,
|
|
38
|
+
})
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (cutout.shape === "polygon") {
|
|
43
|
+
if (cutout.points && cutout.points.length >= 3) {
|
|
44
|
+
drawPolygon({
|
|
45
|
+
ctx,
|
|
46
|
+
points: cutout.points,
|
|
47
|
+
fill: colorMap.drill,
|
|
48
|
+
transform,
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { PCBHole } 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 { drawOval } from "../shapes/oval"
|
|
7
|
+
import { drawPill } from "../shapes/pill"
|
|
8
|
+
|
|
9
|
+
export interface DrawPcbHoleParams {
|
|
10
|
+
ctx: CanvasContext
|
|
11
|
+
hole: PCBHole
|
|
12
|
+
transform: Matrix
|
|
13
|
+
colorMap: PcbColorMap
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function drawPcbHole(params: DrawPcbHoleParams): void {
|
|
17
|
+
const { ctx, hole, transform, colorMap } = params
|
|
18
|
+
|
|
19
|
+
if (hole.hole_shape === "circle") {
|
|
20
|
+
drawCircle({
|
|
21
|
+
ctx,
|
|
22
|
+
center: { x: hole.x, y: hole.y },
|
|
23
|
+
radius: hole.hole_diameter / 2,
|
|
24
|
+
fill: colorMap.drill,
|
|
25
|
+
transform,
|
|
26
|
+
})
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (hole.hole_shape === "square") {
|
|
31
|
+
drawRect({
|
|
32
|
+
ctx,
|
|
33
|
+
center: { x: hole.x, y: hole.y },
|
|
34
|
+
width: hole.hole_diameter,
|
|
35
|
+
height: hole.hole_diameter,
|
|
36
|
+
fill: colorMap.drill,
|
|
37
|
+
transform,
|
|
38
|
+
})
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (hole.hole_shape === "oval") {
|
|
43
|
+
drawOval({
|
|
44
|
+
ctx,
|
|
45
|
+
center: { x: hole.x, y: hole.y },
|
|
46
|
+
width: hole.hole_width,
|
|
47
|
+
height: hole.hole_height,
|
|
48
|
+
fill: colorMap.drill,
|
|
49
|
+
transform,
|
|
50
|
+
})
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (hole.hole_shape === "rect") {
|
|
55
|
+
drawRect({
|
|
56
|
+
ctx,
|
|
57
|
+
center: { x: hole.x, y: hole.y },
|
|
58
|
+
width: hole.hole_width,
|
|
59
|
+
height: hole.hole_height,
|
|
60
|
+
fill: colorMap.drill,
|
|
61
|
+
transform,
|
|
62
|
+
})
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (hole.hole_shape === "pill") {
|
|
67
|
+
drawPill({
|
|
68
|
+
ctx,
|
|
69
|
+
center: { x: hole.x, y: hole.y },
|
|
70
|
+
width: hole.hole_width,
|
|
71
|
+
height: hole.hole_height,
|
|
72
|
+
fill: colorMap.drill,
|
|
73
|
+
transform,
|
|
74
|
+
})
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (hole.hole_shape === "rotated_pill") {
|
|
79
|
+
drawPill({
|
|
80
|
+
ctx,
|
|
81
|
+
center: { x: hole.x, y: hole.y },
|
|
82
|
+
width: hole.hole_width,
|
|
83
|
+
height: hole.hole_height,
|
|
84
|
+
fill: colorMap.drill,
|
|
85
|
+
transform,
|
|
86
|
+
rotation: (hole as any).ccw_rotation ?? 0,
|
|
87
|
+
})
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PcbSilkscreenText,
|
|
3
|
+
PcbSilkscreenRect,
|
|
4
|
+
PcbSilkscreenCircle,
|
|
5
|
+
PcbSilkscreenLine,
|
|
6
|
+
PcbSilkscreenPath,
|
|
7
|
+
} from "circuit-json"
|
|
8
|
+
import type { Matrix } from "transformation-matrix"
|
|
9
|
+
import { applyToPoint } from "transformation-matrix"
|
|
10
|
+
import type { PcbColorMap, CanvasContext } from "../types"
|
|
11
|
+
import { drawRect } from "../shapes/rect"
|
|
12
|
+
import { drawCircle } from "../shapes/circle"
|
|
13
|
+
import { drawLine } from "../shapes/line"
|
|
14
|
+
import { drawPath } from "../shapes/path"
|
|
15
|
+
|
|
16
|
+
export interface DrawPcbSilkscreenTextParams {
|
|
17
|
+
ctx: CanvasContext
|
|
18
|
+
text: PcbSilkscreenText
|
|
19
|
+
transform: Matrix
|
|
20
|
+
colorMap: PcbColorMap
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DrawPcbSilkscreenRectParams {
|
|
24
|
+
ctx: CanvasContext
|
|
25
|
+
rect: PcbSilkscreenRect
|
|
26
|
+
transform: Matrix
|
|
27
|
+
colorMap: PcbColorMap
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface DrawPcbSilkscreenCircleParams {
|
|
31
|
+
ctx: CanvasContext
|
|
32
|
+
circle: PcbSilkscreenCircle
|
|
33
|
+
transform: Matrix
|
|
34
|
+
colorMap: PcbColorMap
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DrawPcbSilkscreenLineParams {
|
|
38
|
+
ctx: CanvasContext
|
|
39
|
+
line: PcbSilkscreenLine
|
|
40
|
+
transform: Matrix
|
|
41
|
+
colorMap: PcbColorMap
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface DrawPcbSilkscreenPathParams {
|
|
45
|
+
ctx: CanvasContext
|
|
46
|
+
path: PcbSilkscreenPath
|
|
47
|
+
transform: Matrix
|
|
48
|
+
colorMap: PcbColorMap
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function layerToSilkscreenColor(layer: string, colorMap: PcbColorMap): string {
|
|
52
|
+
return layer === "bottom"
|
|
53
|
+
? colorMap.silkscreen.bottom
|
|
54
|
+
: colorMap.silkscreen.top
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function mapAnchorAlignment(
|
|
58
|
+
alignment?: string,
|
|
59
|
+
): "start" | "end" | "left" | "right" | "center" {
|
|
60
|
+
if (!alignment) return "center"
|
|
61
|
+
if (alignment.includes("left")) return "left"
|
|
62
|
+
if (alignment.includes("right")) return "right"
|
|
63
|
+
return "center"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function drawPcbSilkscreenText(
|
|
67
|
+
params: DrawPcbSilkscreenTextParams,
|
|
68
|
+
): void {
|
|
69
|
+
const { ctx, text, transform, colorMap } = params
|
|
70
|
+
|
|
71
|
+
const color = layerToSilkscreenColor(text.layer, colorMap)
|
|
72
|
+
const [x, y] = applyToPoint(transform, [
|
|
73
|
+
text.anchor_position.x,
|
|
74
|
+
text.anchor_position.y,
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
const fontSize = (text.font_size ?? 1) * Math.abs(transform.a)
|
|
78
|
+
const rotation = text.ccw_rotation ?? 0
|
|
79
|
+
|
|
80
|
+
ctx.save()
|
|
81
|
+
ctx.translate(x, y)
|
|
82
|
+
|
|
83
|
+
// Apply rotation (CCW rotation in degrees)
|
|
84
|
+
if (rotation !== 0) {
|
|
85
|
+
ctx.rotate(-rotation * (Math.PI / 180))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
ctx.font = `${fontSize}px sans-serif`
|
|
89
|
+
ctx.fillStyle = color
|
|
90
|
+
ctx.textAlign = mapAnchorAlignment(text.anchor_alignment)
|
|
91
|
+
ctx.textBaseline = "middle"
|
|
92
|
+
ctx.fillText(text.text, 0, 0)
|
|
93
|
+
ctx.restore()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function drawPcbSilkscreenRect(
|
|
97
|
+
params: DrawPcbSilkscreenRectParams,
|
|
98
|
+
): void {
|
|
99
|
+
const { ctx, rect, transform, colorMap } = params
|
|
100
|
+
|
|
101
|
+
const color = layerToSilkscreenColor(rect.layer, colorMap)
|
|
102
|
+
|
|
103
|
+
drawRect({
|
|
104
|
+
ctx,
|
|
105
|
+
center: rect.center,
|
|
106
|
+
width: rect.width,
|
|
107
|
+
height: rect.height,
|
|
108
|
+
fill: color,
|
|
109
|
+
transform,
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function drawPcbSilkscreenCircle(
|
|
114
|
+
params: DrawPcbSilkscreenCircleParams,
|
|
115
|
+
): void {
|
|
116
|
+
const { ctx, circle, transform, colorMap } = params
|
|
117
|
+
|
|
118
|
+
const color = layerToSilkscreenColor(circle.layer, colorMap)
|
|
119
|
+
|
|
120
|
+
drawCircle({
|
|
121
|
+
ctx,
|
|
122
|
+
center: circle.center,
|
|
123
|
+
radius: circle.radius,
|
|
124
|
+
fill: color,
|
|
125
|
+
transform,
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function drawPcbSilkscreenLine(
|
|
130
|
+
params: DrawPcbSilkscreenLineParams,
|
|
131
|
+
): void {
|
|
132
|
+
const { ctx, line, transform, colorMap } = params
|
|
133
|
+
|
|
134
|
+
const color = layerToSilkscreenColor(line.layer, colorMap)
|
|
135
|
+
|
|
136
|
+
drawLine({
|
|
137
|
+
ctx,
|
|
138
|
+
start: { x: line.x1, y: line.y1 },
|
|
139
|
+
end: { x: line.x2, y: line.y2 },
|
|
140
|
+
strokeWidth: line.stroke_width ?? 0.1,
|
|
141
|
+
stroke: color,
|
|
142
|
+
transform,
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function drawPcbSilkscreenPath(
|
|
147
|
+
params: DrawPcbSilkscreenPathParams,
|
|
148
|
+
): void {
|
|
149
|
+
const { ctx, path, transform, colorMap } = params
|
|
150
|
+
|
|
151
|
+
const color = layerToSilkscreenColor(path.layer, colorMap)
|
|
152
|
+
|
|
153
|
+
if (!path.route || path.route.length < 2) return
|
|
154
|
+
|
|
155
|
+
// Draw each segment of the path
|
|
156
|
+
for (let i = 0; i < path.route.length - 1; i++) {
|
|
157
|
+
const start = path.route[i]
|
|
158
|
+
const end = path.route[i + 1]
|
|
159
|
+
|
|
160
|
+
if (!start || !end) continue
|
|
161
|
+
|
|
162
|
+
drawLine({
|
|
163
|
+
ctx,
|
|
164
|
+
start: { x: start.x, y: start.y },
|
|
165
|
+
end: { x: end.x, y: end.y },
|
|
166
|
+
strokeWidth: path.stroke_width ?? 0.1,
|
|
167
|
+
stroke: color,
|
|
168
|
+
transform,
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
}
|