circuit-to-canvas 0.0.27 → 0.0.29
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/README.md +1 -0
- package/dist/index.d.ts +13 -3
- package/dist/index.js +119 -26
- package/lib/drawer/CircuitToCanvasDrawer.ts +11 -0
- package/lib/drawer/elements/index.ts +5 -0
- package/lib/drawer/elements/pcb-plated-hole.ts +71 -9
- package/lib/drawer/elements/pcb-silkscreen-pill.ts +37 -0
- package/lib/drawer/shapes/pill.ts +20 -3
- package/package.json +2 -2
- package/tests/elements/__snapshots__/hole-with-polygon-pad-circle.snap.png +0 -0
- package/tests/elements/__snapshots__/hole-with-polygon-pad-pill-offset.snap.png +0 -0
- package/tests/elements/__snapshots__/hole-with-polygon-pad-pill.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-silkscreen-pill.snap.png +0 -0
- package/tests/elements/__snapshots__/silkscreen-pill-bottom.snap.png +0 -0
- package/tests/elements/pcb-plated-hole-polygon.test.ts +111 -0
- package/tests/elements/pcb-silkscreen-pill.test.ts +55 -0
package/README.md
CHANGED
|
@@ -53,6 +53,7 @@ This checklist tracks PCB drawing features from [circuit-to-svg](https://github.
|
|
|
53
53
|
- [x] `pcb_silkscreen_circle` - Circles on silkscreen
|
|
54
54
|
- [x] `pcb_silkscreen_line` - Lines on silkscreen
|
|
55
55
|
- [x] `pcb_silkscreen_path` - Paths/routes on silkscreen
|
|
56
|
+
- [x] `pcb_silkscreen_pill` - Pill shapes (rounded rectangles) on silkscreen
|
|
56
57
|
|
|
57
58
|
### Copper Text
|
|
58
59
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnyCircuitElement, NinePointAnchor, PcbPlatedHole, PCBVia, PCBHole, PcbSmtPad, PCBTrace, PcbBoard, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbCutout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText, PcbNoteDimension } from 'circuit-json';
|
|
1
|
+
import { AnyCircuitElement, NinePointAnchor, PcbPlatedHole, PCBVia, PCBHole, PcbSmtPad, PCBTrace, PcbBoard, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbSilkscreenPill, PcbCutout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText, PcbNoteDimension } from 'circuit-json';
|
|
2
2
|
import { Matrix } from 'transformation-matrix';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -154,9 +154,11 @@ interface DrawPillParams {
|
|
|
154
154
|
};
|
|
155
155
|
width: number;
|
|
156
156
|
height: number;
|
|
157
|
-
fill
|
|
157
|
+
fill?: string;
|
|
158
158
|
realToCanvasMat: Matrix;
|
|
159
159
|
rotation?: number;
|
|
160
|
+
stroke?: string;
|
|
161
|
+
strokeWidth?: number;
|
|
160
162
|
}
|
|
161
163
|
declare function drawPill(params: DrawPillParams): void;
|
|
162
164
|
|
|
@@ -341,6 +343,14 @@ interface DrawPcbSilkscreenPathParams {
|
|
|
341
343
|
}
|
|
342
344
|
declare function drawPcbSilkscreenPath(params: DrawPcbSilkscreenPathParams): void;
|
|
343
345
|
|
|
346
|
+
interface DrawPcbSilkscreenPillParams {
|
|
347
|
+
ctx: CanvasContext;
|
|
348
|
+
pill: PcbSilkscreenPill;
|
|
349
|
+
realToCanvasMat: Matrix;
|
|
350
|
+
colorMap: PcbColorMap;
|
|
351
|
+
}
|
|
352
|
+
declare function drawPcbSilkscreenPill(params: DrawPcbSilkscreenPillParams): void;
|
|
353
|
+
|
|
344
354
|
interface DrawPcbCutoutParams {
|
|
345
355
|
ctx: CanvasContext;
|
|
346
356
|
cutout: PcbCutout;
|
|
@@ -421,4 +431,4 @@ interface DrawPcbNoteDimensionParams {
|
|
|
421
431
|
}
|
|
422
432
|
declare function drawPcbNoteDimension(params: DrawPcbNoteDimensionParams): void;
|
|
423
433
|
|
|
424
|
-
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 DrawElementsOptions, type DrawLineParams, type DrawOvalParams, type DrawPathParams, type DrawPcbBoardParams, type DrawPcbCopperPourParams, type DrawPcbCopperTextParams, type DrawPcbCutoutParams, type DrawPcbFabricationNotePathParams, type DrawPcbFabricationNoteRectParams, type DrawPcbFabricationNoteTextParams, type DrawPcbHoleParams, type DrawPcbNoteDimensionParams, type DrawPcbNotePathParams, type DrawPcbNoteRectParams, type DrawPcbNoteTextParams, type DrawPcbPlatedHoleParams, type DrawPcbSilkscreenCircleParams, type DrawPcbSilkscreenLineParams, type DrawPcbSilkscreenPathParams, type DrawPcbSilkscreenRectParams, type DrawPcbSilkscreenTextParams, type DrawPcbSmtPadParams, type DrawPcbTraceParams, type DrawPcbViaParams, type DrawPillParams, type DrawPolygonParams, type DrawRectParams, type DrawTextParams, type DrawerConfig, type PcbColorMap, drawArrow, drawCircle, drawLine, drawOval, drawPath, drawPcbBoard, drawPcbCopperPour, drawPcbCopperText, drawPcbCutout, drawPcbFabricationNotePath, drawPcbFabricationNoteRect, drawPcbFabricationNoteText, drawPcbHole, drawPcbNoteDimension, drawPcbNotePath, drawPcbNoteRect, drawPcbNoteText, drawPcbPlatedHole, drawPcbSilkscreenCircle, drawPcbSilkscreenLine, drawPcbSilkscreenPath, drawPcbSilkscreenRect, drawPcbSilkscreenText, drawPcbSmtPad, drawPcbTrace, drawPcbVia, drawPill, drawPolygon, drawRect, drawText, getAlphabetLayout, getTextStartPosition, strokeAlphabetText };
|
|
434
|
+
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 DrawElementsOptions, type DrawLineParams, type DrawOvalParams, type DrawPathParams, type DrawPcbBoardParams, type DrawPcbCopperPourParams, type DrawPcbCopperTextParams, type DrawPcbCutoutParams, type DrawPcbFabricationNotePathParams, type DrawPcbFabricationNoteRectParams, type DrawPcbFabricationNoteTextParams, type DrawPcbHoleParams, type DrawPcbNoteDimensionParams, type DrawPcbNotePathParams, type DrawPcbNoteRectParams, type DrawPcbNoteTextParams, type DrawPcbPlatedHoleParams, type DrawPcbSilkscreenCircleParams, type DrawPcbSilkscreenLineParams, 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, drawLine, drawOval, drawPath, drawPcbBoard, drawPcbCopperPour, drawPcbCopperText, drawPcbCutout, drawPcbFabricationNotePath, drawPcbFabricationNoteRect, drawPcbFabricationNoteText, drawPcbHole, drawPcbNoteDimension, drawPcbNotePath, drawPcbNoteRect, drawPcbNoteText, drawPcbPlatedHole, drawPcbSilkscreenCircle, drawPcbSilkscreenLine, drawPcbSilkscreenPath, drawPcbSilkscreenPill, drawPcbSilkscreenRect, drawPcbSilkscreenText, drawPcbSmtPad, drawPcbTrace, drawPcbVia, drawPill, drawPolygon, drawRect, drawText, getAlphabetLayout, getTextStartPosition, strokeAlphabetText };
|
package/dist/index.js
CHANGED
|
@@ -150,11 +150,14 @@ function drawPill(params) {
|
|
|
150
150
|
height,
|
|
151
151
|
fill,
|
|
152
152
|
realToCanvasMat,
|
|
153
|
-
rotation = 0
|
|
153
|
+
rotation = 0,
|
|
154
|
+
stroke,
|
|
155
|
+
strokeWidth
|
|
154
156
|
} = params;
|
|
155
157
|
const [cx, cy] = applyToPoint4(realToCanvasMat, [center.x, center.y]);
|
|
156
158
|
const scaledWidth = width * Math.abs(realToCanvasMat.a);
|
|
157
159
|
const scaledHeight = height * Math.abs(realToCanvasMat.a);
|
|
160
|
+
const scaledStrokeWidth = strokeWidth ? strokeWidth * Math.abs(realToCanvasMat.a) : void 0;
|
|
158
161
|
ctx.save();
|
|
159
162
|
ctx.translate(cx, cy);
|
|
160
163
|
if (rotation !== 0) {
|
|
@@ -181,9 +184,40 @@ function drawPill(params) {
|
|
|
181
184
|
ctx.arc(0, 0, scaledWidth / 2, 0, Math.PI * 2);
|
|
182
185
|
}
|
|
183
186
|
ctx.closePath();
|
|
187
|
+
if (fill) {
|
|
188
|
+
ctx.fillStyle = fill;
|
|
189
|
+
ctx.fill();
|
|
190
|
+
}
|
|
191
|
+
if (stroke && scaledStrokeWidth) {
|
|
192
|
+
ctx.strokeStyle = stroke;
|
|
193
|
+
ctx.lineWidth = scaledStrokeWidth;
|
|
194
|
+
ctx.stroke();
|
|
195
|
+
}
|
|
196
|
+
ctx.restore();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// lib/drawer/shapes/polygon.ts
|
|
200
|
+
import { applyToPoint as applyToPoint5 } from "transformation-matrix";
|
|
201
|
+
function drawPolygon(params) {
|
|
202
|
+
const { ctx, points, fill, realToCanvasMat } = params;
|
|
203
|
+
if (points.length < 3) return;
|
|
204
|
+
ctx.beginPath();
|
|
205
|
+
const canvasPoints = points.map(
|
|
206
|
+
(p) => applyToPoint5(realToCanvasMat, [p.x, p.y])
|
|
207
|
+
);
|
|
208
|
+
const firstPoint = canvasPoints[0];
|
|
209
|
+
if (!firstPoint) return;
|
|
210
|
+
const [firstX, firstY] = firstPoint;
|
|
211
|
+
ctx.moveTo(firstX, firstY);
|
|
212
|
+
for (let i = 1; i < canvasPoints.length; i++) {
|
|
213
|
+
const point = canvasPoints[i];
|
|
214
|
+
if (!point) continue;
|
|
215
|
+
const [x, y] = point;
|
|
216
|
+
ctx.lineTo(x, y);
|
|
217
|
+
}
|
|
218
|
+
ctx.closePath();
|
|
184
219
|
ctx.fillStyle = fill;
|
|
185
220
|
ctx.fill();
|
|
186
|
-
ctx.restore();
|
|
187
221
|
}
|
|
188
222
|
|
|
189
223
|
// lib/drawer/elements/pcb-plated-hole.ts
|
|
@@ -315,6 +349,61 @@ function drawPcbPlatedHole(params) {
|
|
|
315
349
|
});
|
|
316
350
|
return;
|
|
317
351
|
}
|
|
352
|
+
if (hole.shape === "hole_with_polygon_pad") {
|
|
353
|
+
const padOutline = hole.pad_outline;
|
|
354
|
+
if (padOutline && padOutline.length >= 3) {
|
|
355
|
+
const padPoints = padOutline.map((point) => ({
|
|
356
|
+
x: hole.x + point.x,
|
|
357
|
+
y: hole.y + point.y
|
|
358
|
+
}));
|
|
359
|
+
drawPolygon({
|
|
360
|
+
ctx,
|
|
361
|
+
points: padPoints,
|
|
362
|
+
fill: colorMap.copper.top,
|
|
363
|
+
realToCanvasMat
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
const holeX = hole.x + (hole.hole_offset_x ?? 0);
|
|
367
|
+
const holeY = hole.y + (hole.hole_offset_y ?? 0);
|
|
368
|
+
const holeShape = hole.hole_shape;
|
|
369
|
+
if (holeShape === "circle") {
|
|
370
|
+
drawCircle({
|
|
371
|
+
ctx,
|
|
372
|
+
center: { x: holeX, y: holeY },
|
|
373
|
+
radius: (hole.hole_diameter ?? 0) / 2,
|
|
374
|
+
fill: colorMap.drill,
|
|
375
|
+
realToCanvasMat
|
|
376
|
+
});
|
|
377
|
+
} else if (holeShape === "oval") {
|
|
378
|
+
drawOval({
|
|
379
|
+
ctx,
|
|
380
|
+
center: { x: holeX, y: holeY },
|
|
381
|
+
width: hole.hole_width ?? 0,
|
|
382
|
+
height: hole.hole_height ?? 0,
|
|
383
|
+
fill: colorMap.drill,
|
|
384
|
+
realToCanvasMat
|
|
385
|
+
});
|
|
386
|
+
} else if (holeShape === "pill") {
|
|
387
|
+
drawPill({
|
|
388
|
+
ctx,
|
|
389
|
+
center: { x: holeX, y: holeY },
|
|
390
|
+
width: hole.hole_width ?? 0,
|
|
391
|
+
height: hole.hole_height ?? 0,
|
|
392
|
+
fill: colorMap.drill,
|
|
393
|
+
realToCanvasMat
|
|
394
|
+
});
|
|
395
|
+
} else if (holeShape === "rotated_pill") {
|
|
396
|
+
drawPill({
|
|
397
|
+
ctx,
|
|
398
|
+
center: { x: holeX, y: holeY },
|
|
399
|
+
width: hole.hole_width ?? 0,
|
|
400
|
+
height: hole.hole_height ?? 0,
|
|
401
|
+
fill: colorMap.drill,
|
|
402
|
+
realToCanvasMat
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
318
407
|
}
|
|
319
408
|
|
|
320
409
|
// lib/drawer/elements/pcb-via.ts
|
|
@@ -407,30 +496,6 @@ function drawPcbHole(params) {
|
|
|
407
496
|
}
|
|
408
497
|
}
|
|
409
498
|
|
|
410
|
-
// lib/drawer/shapes/polygon.ts
|
|
411
|
-
import { applyToPoint as applyToPoint5 } from "transformation-matrix";
|
|
412
|
-
function drawPolygon(params) {
|
|
413
|
-
const { ctx, points, fill, realToCanvasMat } = params;
|
|
414
|
-
if (points.length < 3) return;
|
|
415
|
-
ctx.beginPath();
|
|
416
|
-
const canvasPoints = points.map(
|
|
417
|
-
(p) => applyToPoint5(realToCanvasMat, [p.x, p.y])
|
|
418
|
-
);
|
|
419
|
-
const firstPoint = canvasPoints[0];
|
|
420
|
-
if (!firstPoint) return;
|
|
421
|
-
const [firstX, firstY] = firstPoint;
|
|
422
|
-
ctx.moveTo(firstX, firstY);
|
|
423
|
-
for (let i = 1; i < canvasPoints.length; i++) {
|
|
424
|
-
const point = canvasPoints[i];
|
|
425
|
-
if (!point) continue;
|
|
426
|
-
const [x, y] = point;
|
|
427
|
-
ctx.lineTo(x, y);
|
|
428
|
-
}
|
|
429
|
-
ctx.closePath();
|
|
430
|
-
ctx.fillStyle = fill;
|
|
431
|
-
ctx.fill();
|
|
432
|
-
}
|
|
433
|
-
|
|
434
499
|
// lib/drawer/elements/pcb-smtpad.ts
|
|
435
500
|
function layerToColor(layer, colorMap) {
|
|
436
501
|
return colorMap.copper[layer] ?? colorMap.copper.top;
|
|
@@ -891,6 +956,25 @@ function drawPcbSilkscreenPath(params) {
|
|
|
891
956
|
}
|
|
892
957
|
}
|
|
893
958
|
|
|
959
|
+
// lib/drawer/elements/pcb-silkscreen-pill.ts
|
|
960
|
+
function layerToSilkscreenColor6(layer, colorMap) {
|
|
961
|
+
return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
|
|
962
|
+
}
|
|
963
|
+
function drawPcbSilkscreenPill(params) {
|
|
964
|
+
const { ctx, pill, realToCanvasMat, colorMap } = params;
|
|
965
|
+
const color = layerToSilkscreenColor6(pill.layer, colorMap);
|
|
966
|
+
const strokeWidth = 0.2;
|
|
967
|
+
drawPill({
|
|
968
|
+
ctx,
|
|
969
|
+
center: pill.center,
|
|
970
|
+
width: pill.width,
|
|
971
|
+
height: pill.height,
|
|
972
|
+
stroke: color,
|
|
973
|
+
strokeWidth,
|
|
974
|
+
realToCanvasMat
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
|
|
894
978
|
// lib/drawer/elements/pcb-cutout.ts
|
|
895
979
|
function drawPcbCutout(params) {
|
|
896
980
|
const { ctx, cutout, realToCanvasMat, colorMap } = params;
|
|
@@ -1520,6 +1604,14 @@ var CircuitToCanvasDrawer = class {
|
|
|
1520
1604
|
colorMap: this.colorMap
|
|
1521
1605
|
});
|
|
1522
1606
|
}
|
|
1607
|
+
if (element.type === "pcb_silkscreen_pill") {
|
|
1608
|
+
drawPcbSilkscreenPill({
|
|
1609
|
+
ctx: this.ctx,
|
|
1610
|
+
pill: element,
|
|
1611
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
1612
|
+
colorMap: this.colorMap
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1523
1615
|
if (element.type === "pcb_cutout") {
|
|
1524
1616
|
drawPcbCutout({
|
|
1525
1617
|
ctx: this.ctx,
|
|
@@ -1634,6 +1726,7 @@ export {
|
|
|
1634
1726
|
drawPcbSilkscreenCircle,
|
|
1635
1727
|
drawPcbSilkscreenLine,
|
|
1636
1728
|
drawPcbSilkscreenPath,
|
|
1729
|
+
drawPcbSilkscreenPill,
|
|
1637
1730
|
drawPcbSilkscreenRect,
|
|
1638
1731
|
drawPcbSilkscreenText,
|
|
1639
1732
|
drawPcbSmtPad,
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
PcbSilkscreenCircle,
|
|
12
12
|
PcbSilkscreenLine,
|
|
13
13
|
PcbSilkscreenPath,
|
|
14
|
+
PcbSilkscreenPill,
|
|
14
15
|
PcbCutout,
|
|
15
16
|
PcbCopperPour,
|
|
16
17
|
PcbCopperText,
|
|
@@ -43,6 +44,7 @@ import { drawPcbSilkscreenRect } from "./elements/pcb-silkscreen-rect"
|
|
|
43
44
|
import { drawPcbSilkscreenCircle } from "./elements/pcb-silkscreen-circle"
|
|
44
45
|
import { drawPcbSilkscreenLine } from "./elements/pcb-silkscreen-line"
|
|
45
46
|
import { drawPcbSilkscreenPath } from "./elements/pcb-silkscreen-path"
|
|
47
|
+
import { drawPcbSilkscreenPill } from "./elements/pcb-silkscreen-pill"
|
|
46
48
|
import { drawPcbCutout } from "./elements/pcb-cutout"
|
|
47
49
|
import { drawPcbCopperPour } from "./elements/pcb-copper-pour"
|
|
48
50
|
import { drawPcbCopperText } from "./elements/pcb-copper-text"
|
|
@@ -252,6 +254,15 @@ export class CircuitToCanvasDrawer {
|
|
|
252
254
|
})
|
|
253
255
|
}
|
|
254
256
|
|
|
257
|
+
if (element.type === "pcb_silkscreen_pill") {
|
|
258
|
+
drawPcbSilkscreenPill({
|
|
259
|
+
ctx: this.ctx,
|
|
260
|
+
pill: element as PcbSilkscreenPill,
|
|
261
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
262
|
+
colorMap: this.colorMap,
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
255
266
|
if (element.type === "pcb_cutout") {
|
|
256
267
|
drawPcbCutout({
|
|
257
268
|
ctx: this.ctx,
|
|
@@ -38,6 +38,11 @@ export {
|
|
|
38
38
|
type DrawPcbSilkscreenPathParams,
|
|
39
39
|
} from "./pcb-silkscreen-path"
|
|
40
40
|
|
|
41
|
+
export {
|
|
42
|
+
drawPcbSilkscreenPill,
|
|
43
|
+
type DrawPcbSilkscreenPillParams,
|
|
44
|
+
} from "./pcb-silkscreen-pill"
|
|
45
|
+
|
|
41
46
|
export { drawPcbCutout, type DrawPcbCutoutParams } from "./pcb-cutout"
|
|
42
47
|
|
|
43
48
|
export {
|
|
@@ -5,6 +5,7 @@ import { drawCircle } from "../shapes/circle"
|
|
|
5
5
|
import { drawRect } from "../shapes/rect"
|
|
6
6
|
import { drawOval } from "../shapes/oval"
|
|
7
7
|
import { drawPill } from "../shapes/pill"
|
|
8
|
+
import { drawPolygon } from "../shapes/polygon"
|
|
8
9
|
|
|
9
10
|
export interface DrawPcbPlatedHoleParams {
|
|
10
11
|
ctx: CanvasContext
|
|
@@ -96,12 +97,12 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
|
|
|
96
97
|
height: hole.rect_pad_height,
|
|
97
98
|
fill: colorMap.copper.top,
|
|
98
99
|
realToCanvasMat,
|
|
99
|
-
borderRadius:
|
|
100
|
+
borderRadius: hole.rect_border_radius ?? 0,
|
|
100
101
|
})
|
|
101
102
|
|
|
102
103
|
// Draw circular drill hole (with offset)
|
|
103
|
-
const holeX = hole.x + (
|
|
104
|
-
const holeY = hole.y + (
|
|
104
|
+
const holeX = hole.x + (hole.hole_offset_x ?? 0)
|
|
105
|
+
const holeY = hole.y + (hole.hole_offset_y ?? 0)
|
|
105
106
|
drawCircle({
|
|
106
107
|
ctx,
|
|
107
108
|
center: { x: holeX, y: holeY },
|
|
@@ -121,12 +122,12 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
|
|
|
121
122
|
height: hole.rect_pad_height,
|
|
122
123
|
fill: colorMap.copper.top,
|
|
123
124
|
realToCanvasMat,
|
|
124
|
-
borderRadius:
|
|
125
|
+
borderRadius: hole.rect_border_radius ?? 0,
|
|
125
126
|
})
|
|
126
127
|
|
|
127
128
|
// Draw pill drill hole (with offset)
|
|
128
|
-
const holeX = hole.x + (
|
|
129
|
-
const holeY = hole.y + (
|
|
129
|
+
const holeX = hole.x + (hole.hole_offset_x ?? 0)
|
|
130
|
+
const holeY = hole.y + (hole.hole_offset_y ?? 0)
|
|
130
131
|
drawPill({
|
|
131
132
|
ctx,
|
|
132
133
|
center: { x: holeX, y: holeY },
|
|
@@ -147,13 +148,13 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
|
|
|
147
148
|
height: hole.rect_pad_height,
|
|
148
149
|
fill: colorMap.copper.top,
|
|
149
150
|
realToCanvasMat,
|
|
150
|
-
borderRadius:
|
|
151
|
+
borderRadius: hole.rect_border_radius ?? 0,
|
|
151
152
|
rotation: hole.rect_ccw_rotation,
|
|
152
153
|
})
|
|
153
154
|
|
|
154
155
|
// Draw rotated pill drill hole (with offset)
|
|
155
|
-
const holeX = hole.x + (
|
|
156
|
-
const holeY = hole.y + (
|
|
156
|
+
const holeX = hole.x + (hole.hole_offset_x ?? 0)
|
|
157
|
+
const holeY = hole.y + (hole.hole_offset_y ?? 0)
|
|
157
158
|
drawPill({
|
|
158
159
|
ctx,
|
|
159
160
|
center: { x: holeX, y: holeY },
|
|
@@ -165,4 +166,65 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
|
|
|
165
166
|
})
|
|
166
167
|
return
|
|
167
168
|
}
|
|
169
|
+
|
|
170
|
+
if (hole.shape === "hole_with_polygon_pad") {
|
|
171
|
+
// Draw polygon pad
|
|
172
|
+
const padOutline = hole.pad_outline
|
|
173
|
+
if (padOutline && padOutline.length >= 3) {
|
|
174
|
+
// Transform pad_outline points to be relative to hole.x, hole.y
|
|
175
|
+
const padPoints = padOutline.map((point: { x: number; y: number }) => ({
|
|
176
|
+
x: hole.x + point.x,
|
|
177
|
+
y: hole.y + point.y,
|
|
178
|
+
}))
|
|
179
|
+
drawPolygon({
|
|
180
|
+
ctx,
|
|
181
|
+
points: padPoints,
|
|
182
|
+
fill: colorMap.copper.top,
|
|
183
|
+
realToCanvasMat,
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Draw drill hole (with offset)
|
|
188
|
+
const holeX = hole.x + (hole.hole_offset_x ?? 0)
|
|
189
|
+
const holeY = hole.y + (hole.hole_offset_y ?? 0)
|
|
190
|
+
const holeShape = hole.hole_shape
|
|
191
|
+
|
|
192
|
+
if (holeShape === "circle") {
|
|
193
|
+
drawCircle({
|
|
194
|
+
ctx,
|
|
195
|
+
center: { x: holeX, y: holeY },
|
|
196
|
+
radius: (hole.hole_diameter ?? 0) / 2,
|
|
197
|
+
fill: colorMap.drill,
|
|
198
|
+
realToCanvasMat,
|
|
199
|
+
})
|
|
200
|
+
} else if (holeShape === "oval") {
|
|
201
|
+
drawOval({
|
|
202
|
+
ctx,
|
|
203
|
+
center: { x: holeX, y: holeY },
|
|
204
|
+
width: hole.hole_width ?? 0,
|
|
205
|
+
height: hole.hole_height ?? 0,
|
|
206
|
+
fill: colorMap.drill,
|
|
207
|
+
realToCanvasMat,
|
|
208
|
+
})
|
|
209
|
+
} else if (holeShape === "pill") {
|
|
210
|
+
drawPill({
|
|
211
|
+
ctx,
|
|
212
|
+
center: { x: holeX, y: holeY },
|
|
213
|
+
width: hole.hole_width ?? 0,
|
|
214
|
+
height: hole.hole_height ?? 0,
|
|
215
|
+
fill: colorMap.drill,
|
|
216
|
+
realToCanvasMat,
|
|
217
|
+
})
|
|
218
|
+
} else if (holeShape === "rotated_pill") {
|
|
219
|
+
drawPill({
|
|
220
|
+
ctx,
|
|
221
|
+
center: { x: holeX, y: holeY },
|
|
222
|
+
width: hole.hole_width ?? 0,
|
|
223
|
+
height: hole.hole_height ?? 0,
|
|
224
|
+
fill: colorMap.drill,
|
|
225
|
+
realToCanvasMat,
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
return
|
|
229
|
+
}
|
|
168
230
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { PcbSilkscreenPill } from "circuit-json"
|
|
2
|
+
import type { Matrix } from "transformation-matrix"
|
|
3
|
+
import type { PcbColorMap, CanvasContext } from "../types"
|
|
4
|
+
import { drawPill } from "../shapes/pill"
|
|
5
|
+
|
|
6
|
+
export interface DrawPcbSilkscreenPillParams {
|
|
7
|
+
ctx: CanvasContext
|
|
8
|
+
pill: PcbSilkscreenPill
|
|
9
|
+
realToCanvasMat: Matrix
|
|
10
|
+
colorMap: PcbColorMap
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function layerToSilkscreenColor(layer: string, colorMap: PcbColorMap): string {
|
|
14
|
+
return layer === "bottom"
|
|
15
|
+
? colorMap.silkscreen.bottom
|
|
16
|
+
: colorMap.silkscreen.top
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function drawPcbSilkscreenPill(
|
|
20
|
+
params: DrawPcbSilkscreenPillParams,
|
|
21
|
+
): void {
|
|
22
|
+
const { ctx, pill, realToCanvasMat, colorMap } = params
|
|
23
|
+
|
|
24
|
+
const color = layerToSilkscreenColor(pill.layer, colorMap)
|
|
25
|
+
const strokeWidth = 0.2
|
|
26
|
+
|
|
27
|
+
// Draw as boundary/outline, not filled
|
|
28
|
+
drawPill({
|
|
29
|
+
ctx,
|
|
30
|
+
center: pill.center,
|
|
31
|
+
width: pill.width,
|
|
32
|
+
height: pill.height,
|
|
33
|
+
stroke: color,
|
|
34
|
+
strokeWidth,
|
|
35
|
+
realToCanvasMat,
|
|
36
|
+
})
|
|
37
|
+
}
|
|
@@ -7,9 +7,11 @@ export interface DrawPillParams {
|
|
|
7
7
|
center: { x: number; y: number }
|
|
8
8
|
width: number
|
|
9
9
|
height: number
|
|
10
|
-
fill
|
|
10
|
+
fill?: string
|
|
11
11
|
realToCanvasMat: Matrix
|
|
12
12
|
rotation?: number
|
|
13
|
+
stroke?: string
|
|
14
|
+
strokeWidth?: number
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export function drawPill(params: DrawPillParams): void {
|
|
@@ -21,11 +23,16 @@ export function drawPill(params: DrawPillParams): void {
|
|
|
21
23
|
fill,
|
|
22
24
|
realToCanvasMat,
|
|
23
25
|
rotation = 0,
|
|
26
|
+
stroke,
|
|
27
|
+
strokeWidth,
|
|
24
28
|
} = params
|
|
25
29
|
|
|
26
30
|
const [cx, cy] = applyToPoint(realToCanvasMat, [center.x, center.y])
|
|
27
31
|
const scaledWidth = width * Math.abs(realToCanvasMat.a)
|
|
28
32
|
const scaledHeight = height * Math.abs(realToCanvasMat.a)
|
|
33
|
+
const scaledStrokeWidth = strokeWidth
|
|
34
|
+
? strokeWidth * Math.abs(realToCanvasMat.a)
|
|
35
|
+
: undefined
|
|
29
36
|
|
|
30
37
|
ctx.save()
|
|
31
38
|
ctx.translate(cx, cy)
|
|
@@ -62,7 +69,17 @@ export function drawPill(params: DrawPillParams): void {
|
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
ctx.closePath()
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
|
|
73
|
+
if (fill) {
|
|
74
|
+
ctx.fillStyle = fill
|
|
75
|
+
ctx.fill()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (stroke && scaledStrokeWidth) {
|
|
79
|
+
ctx.strokeStyle = stroke
|
|
80
|
+
ctx.lineWidth = scaledStrokeWidth
|
|
81
|
+
ctx.stroke()
|
|
82
|
+
}
|
|
83
|
+
|
|
67
84
|
ctx.restore()
|
|
68
85
|
}
|
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.
|
|
4
|
+
"version": "0.0.29",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsup-node ./lib/index.ts --format esm --dts",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"@tscircuit/math-utils": "^0.0.29",
|
|
18
18
|
"@types/bun": "latest",
|
|
19
19
|
"bun-match-svg": "^0.0.14",
|
|
20
|
-
"circuit-json": "^0.0.
|
|
20
|
+
"circuit-json": "^0.0.342",
|
|
21
21
|
"circuit-json-to-connectivity-map": "^0.0.23",
|
|
22
22
|
"circuit-to-svg": "^0.0.297",
|
|
23
23
|
"looks-same": "^10.0.1",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { createCanvas } from "@napi-rs/canvas"
|
|
3
|
+
import type { PcbPlatedHole } from "circuit-json"
|
|
4
|
+
import { CircuitToCanvasDrawer } from "../../lib/drawer"
|
|
5
|
+
|
|
6
|
+
test("draw hole with polygon pad - circle hole", 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 hole: PcbPlatedHole = {
|
|
15
|
+
type: "pcb_plated_hole",
|
|
16
|
+
pcb_plated_hole_id: "hole1",
|
|
17
|
+
shape: "hole_with_polygon_pad",
|
|
18
|
+
x: 50,
|
|
19
|
+
y: 50,
|
|
20
|
+
hole_shape: "circle",
|
|
21
|
+
hole_diameter: 15,
|
|
22
|
+
pad_outline: [
|
|
23
|
+
{ x: -20, y: -20 },
|
|
24
|
+
{ x: 20, y: -20 },
|
|
25
|
+
{ x: 25, y: 0 },
|
|
26
|
+
{ x: 20, y: 20 },
|
|
27
|
+
{ x: -20, y: 20 },
|
|
28
|
+
{ x: -25, y: 0 },
|
|
29
|
+
],
|
|
30
|
+
layers: ["top", "bottom"],
|
|
31
|
+
} as any
|
|
32
|
+
|
|
33
|
+
drawer.drawElements([hole])
|
|
34
|
+
|
|
35
|
+
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
36
|
+
import.meta.path,
|
|
37
|
+
"hole-with-polygon-pad-circle",
|
|
38
|
+
)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test("draw hole with polygon pad - pill hole with offset", async () => {
|
|
42
|
+
const canvas = createCanvas(100, 100)
|
|
43
|
+
const ctx = canvas.getContext("2d")
|
|
44
|
+
const drawer = new CircuitToCanvasDrawer(ctx)
|
|
45
|
+
|
|
46
|
+
ctx.fillStyle = "#1a1a1a"
|
|
47
|
+
ctx.fillRect(0, 0, 100, 100)
|
|
48
|
+
|
|
49
|
+
const hole: PcbPlatedHole = {
|
|
50
|
+
type: "pcb_plated_hole",
|
|
51
|
+
pcb_plated_hole_id: "hole1",
|
|
52
|
+
shape: "hole_with_polygon_pad",
|
|
53
|
+
x: 50,
|
|
54
|
+
y: 50,
|
|
55
|
+
hole_shape: "pill",
|
|
56
|
+
hole_width: 12,
|
|
57
|
+
hole_height: 8,
|
|
58
|
+
hole_offset_x: 5,
|
|
59
|
+
hole_offset_y: -3,
|
|
60
|
+
pad_outline: [
|
|
61
|
+
{ x: -25, y: -15 },
|
|
62
|
+
{ x: 25, y: -15 },
|
|
63
|
+
{ x: 30, y: 0 },
|
|
64
|
+
{ x: 25, y: 15 },
|
|
65
|
+
{ x: -25, y: 15 },
|
|
66
|
+
{ x: -30, y: 0 },
|
|
67
|
+
],
|
|
68
|
+
layers: ["top", "bottom"],
|
|
69
|
+
} as any
|
|
70
|
+
|
|
71
|
+
drawer.drawElements([hole])
|
|
72
|
+
|
|
73
|
+
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
74
|
+
import.meta.path,
|
|
75
|
+
"hole-with-polygon-pad-pill-offset",
|
|
76
|
+
)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test("draw hole with polygon pad - pill hole", async () => {
|
|
80
|
+
const canvas = createCanvas(100, 100)
|
|
81
|
+
const ctx = canvas.getContext("2d")
|
|
82
|
+
const drawer = new CircuitToCanvasDrawer(ctx)
|
|
83
|
+
|
|
84
|
+
ctx.fillStyle = "#1a1a1a"
|
|
85
|
+
ctx.fillRect(0, 0, 100, 100)
|
|
86
|
+
|
|
87
|
+
const hole: PcbPlatedHole = {
|
|
88
|
+
type: "pcb_plated_hole",
|
|
89
|
+
pcb_plated_hole_id: "hole1",
|
|
90
|
+
shape: "hole_with_polygon_pad",
|
|
91
|
+
x: 50,
|
|
92
|
+
y: 50,
|
|
93
|
+
hole_shape: "rotated_pill",
|
|
94
|
+
hole_width: 15,
|
|
95
|
+
hole_height: 10,
|
|
96
|
+
pad_outline: [
|
|
97
|
+
{ x: -20, y: -20 },
|
|
98
|
+
{ x: 20, y: -20 },
|
|
99
|
+
{ x: 20, y: 20 },
|
|
100
|
+
{ x: -20, y: 20 },
|
|
101
|
+
],
|
|
102
|
+
layers: ["top", "bottom"],
|
|
103
|
+
} as any
|
|
104
|
+
|
|
105
|
+
drawer.drawElements([hole])
|
|
106
|
+
|
|
107
|
+
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
108
|
+
import.meta.path,
|
|
109
|
+
"hole-with-polygon-pad-pill",
|
|
110
|
+
)
|
|
111
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { createCanvas } from "@napi-rs/canvas"
|
|
3
|
+
import type { PcbSilkscreenPill } from "circuit-json"
|
|
4
|
+
import { CircuitToCanvasDrawer } from "../../lib/drawer"
|
|
5
|
+
|
|
6
|
+
test("draw silkscreen pill", async () => {
|
|
7
|
+
const canvas = createCanvas(400, 400)
|
|
8
|
+
const ctx = canvas.getContext("2d")
|
|
9
|
+
const drawer = new CircuitToCanvasDrawer(ctx)
|
|
10
|
+
|
|
11
|
+
ctx.fillStyle = "#1a1a1a"
|
|
12
|
+
ctx.fillRect(0, 0, 400, 400)
|
|
13
|
+
|
|
14
|
+
const pill: PcbSilkscreenPill = {
|
|
15
|
+
type: "pcb_silkscreen_pill",
|
|
16
|
+
pcb_silkscreen_pill_id: "pill1",
|
|
17
|
+
pcb_component_id: "component1",
|
|
18
|
+
layer: "top",
|
|
19
|
+
center: { x: 200, y: 200 },
|
|
20
|
+
width: 240,
|
|
21
|
+
height: 80,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
drawer.drawElements([pill])
|
|
25
|
+
|
|
26
|
+
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
27
|
+
import.meta.path,
|
|
28
|
+
)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test("draw silkscreen pill bottom layer", async () => {
|
|
32
|
+
const canvas = createCanvas(400, 400)
|
|
33
|
+
const ctx = canvas.getContext("2d")
|
|
34
|
+
const drawer = new CircuitToCanvasDrawer(ctx)
|
|
35
|
+
|
|
36
|
+
ctx.fillStyle = "#1a1a1a"
|
|
37
|
+
ctx.fillRect(0, 0, 400, 400)
|
|
38
|
+
|
|
39
|
+
const pill: PcbSilkscreenPill = {
|
|
40
|
+
type: "pcb_silkscreen_pill",
|
|
41
|
+
pcb_silkscreen_pill_id: "pill1",
|
|
42
|
+
pcb_component_id: "component1",
|
|
43
|
+
layer: "bottom",
|
|
44
|
+
center: { x: 200, y: 200 },
|
|
45
|
+
width: 160,
|
|
46
|
+
height: 120,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
drawer.drawElements([pill])
|
|
50
|
+
|
|
51
|
+
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
52
|
+
import.meta.path,
|
|
53
|
+
"silkscreen-pill-bottom",
|
|
54
|
+
)
|
|
55
|
+
})
|