circuit-to-canvas 0.0.51 → 0.0.52
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 +5 -2
- package/dist/index.js +1750 -1743
- package/lib/drawer/CircuitToCanvasDrawer.ts +61 -60
- package/lib/drawer/elements/pcb-board.ts +26 -21
- package/lib/drawer/elements/pcb-plated-hole.ts +3 -3
- package/lib/drawer/elements/pcb-soldermask/hole.ts +4 -4
- package/lib/drawer/elements/pcb-soldermask/index.ts +9 -9
- package/lib/drawer/elements/pcb-soldermask/plated-hole.ts +4 -4
- package/lib/drawer/elements/pcb-soldermask/smt-pad.ts +4 -4
- package/package.json +1 -1
- package/tests/board-snapshot/usb-c-flashlight-board.test.ts +1 -1
- package/tests/elements/__snapshots__/board-with-elements.snap.png +0 -0
- package/tests/elements/__snapshots__/brep-copper-pours.snap.png +0 -0
- package/tests/elements/__snapshots__/oval-plated-hole.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-fabrication-note-dimension.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-keepout-layer-filter.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-keepout-multiple-layers.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-keepout-rect-and-circle.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-no-soldermask.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-plated-hole.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-silkscreen-oval.snap.png +0 -0
- package/tests/elements/__snapshots__/pill-plated-hole.snap.png +0 -0
- package/tests/elements/pcb-board.test.ts +2 -2
- package/tests/elements/pcb-comprehensive-soldermask-margin.test.ts +1 -1
- package/tests/elements/pcb-hole-soldermask-margin.test.ts +1 -1
- package/tests/elements/pcb-keepout-with-group-id.test.ts +1 -1
- package/tests/elements/pcb-plated-hole-soldermask-margin.test.ts +1 -1
- package/tests/elements/pcb-plated-hole.test.ts +3 -3
- package/tests/elements/pcb-smtpad-asymmetric-soldermask-margin.test.ts +1 -1
- package/tests/elements/pcb-smtpad-soldermask-coverage.test.ts +1 -1
- package/tests/elements/pcb-smtpad-soldermask-margin.test.ts +1 -1
- package/tests/fixtures/getStackedPngSvgComparison.ts +5 -5
package/dist/index.js
CHANGED
|
@@ -1,70 +1,51 @@
|
|
|
1
1
|
// lib/drawer/CircuitToCanvasDrawer.ts
|
|
2
2
|
import {
|
|
3
|
-
identity,
|
|
4
3
|
compose,
|
|
5
|
-
|
|
6
|
-
scale
|
|
4
|
+
identity,
|
|
5
|
+
scale,
|
|
6
|
+
translate
|
|
7
7
|
} from "transformation-matrix";
|
|
8
8
|
|
|
9
|
-
// lib/drawer/
|
|
10
|
-
import { getElementRenderLayers } from "@tscircuit/circuit-json-util";
|
|
11
|
-
function shouldDrawElement(element, options) {
|
|
12
|
-
if (!options.layers || options.layers.length === 0) {
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
const elementLayers = getElementRenderLayers(element);
|
|
16
|
-
if (elementLayers.length === 0) {
|
|
17
|
-
return true;
|
|
18
|
-
}
|
|
19
|
-
return elementLayers.some((layer) => options.layers.includes(layer));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// lib/drawer/types.ts
|
|
23
|
-
var DEFAULT_PCB_COLOR_MAP = {
|
|
24
|
-
copper: {
|
|
25
|
-
top: "rgb(200, 52, 52)",
|
|
26
|
-
inner1: "rgb(255, 140, 0)",
|
|
27
|
-
inner2: "rgb(255, 215, 0)",
|
|
28
|
-
inner3: "rgb(50, 205, 50)",
|
|
29
|
-
inner4: "rgb(64, 224, 208)",
|
|
30
|
-
inner5: "rgb(138, 43, 226)",
|
|
31
|
-
inner6: "rgb(255, 105, 180)",
|
|
32
|
-
bottom: "rgb(77, 127, 196)"
|
|
33
|
-
},
|
|
34
|
-
soldermaskWithCopperUnderneath: {
|
|
35
|
-
top: "rgb(18, 82, 50)",
|
|
36
|
-
bottom: "rgb(77, 127, 196)"
|
|
37
|
-
},
|
|
38
|
-
soldermask: {
|
|
39
|
-
top: "rgb(12, 55, 33)",
|
|
40
|
-
bottom: "rgb(12, 55, 33)"
|
|
41
|
-
},
|
|
42
|
-
soldermaskOverCopper: {
|
|
43
|
-
top: "rgb(52, 135, 73)",
|
|
44
|
-
bottom: "rgb(52, 135, 73)"
|
|
45
|
-
},
|
|
46
|
-
substrate: "rgb(201, 162, 110)",
|
|
47
|
-
drill: "#FF26E2",
|
|
48
|
-
silkscreen: {
|
|
49
|
-
top: "#f2eda1",
|
|
50
|
-
bottom: "#5da9e9"
|
|
51
|
-
},
|
|
52
|
-
boardOutline: "rgba(255, 255, 255, 0.5)",
|
|
53
|
-
courtyard: "#FF00FF",
|
|
54
|
-
keepout: "#FF6B6B"
|
|
55
|
-
// Red color for keepout zones
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// lib/drawer/shapes/circle.ts
|
|
9
|
+
// lib/drawer/shapes/path.ts
|
|
59
10
|
import { applyToPoint } from "transformation-matrix";
|
|
60
|
-
function
|
|
61
|
-
const {
|
|
62
|
-
|
|
63
|
-
|
|
11
|
+
function drawPath(params) {
|
|
12
|
+
const {
|
|
13
|
+
ctx,
|
|
14
|
+
points,
|
|
15
|
+
fill,
|
|
16
|
+
stroke,
|
|
17
|
+
strokeWidth = 1,
|
|
18
|
+
realToCanvasMat,
|
|
19
|
+
closePath = false
|
|
20
|
+
} = params;
|
|
21
|
+
if (points.length < 2) return;
|
|
64
22
|
ctx.beginPath();
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
23
|
+
const canvasPoints = points.map(
|
|
24
|
+
(p) => applyToPoint(realToCanvasMat, [p.x, p.y])
|
|
25
|
+
);
|
|
26
|
+
const firstPoint = canvasPoints[0];
|
|
27
|
+
if (!firstPoint) return;
|
|
28
|
+
const [firstX, firstY] = firstPoint;
|
|
29
|
+
ctx.moveTo(firstX, firstY);
|
|
30
|
+
for (let i = 1; i < canvasPoints.length; i++) {
|
|
31
|
+
const point = canvasPoints[i];
|
|
32
|
+
if (!point) continue;
|
|
33
|
+
const [x, y] = point;
|
|
34
|
+
ctx.lineTo(x, y);
|
|
35
|
+
}
|
|
36
|
+
if (closePath) {
|
|
37
|
+
ctx.closePath();
|
|
38
|
+
}
|
|
39
|
+
if (fill) {
|
|
40
|
+
ctx.fillStyle = fill;
|
|
41
|
+
ctx.fill();
|
|
42
|
+
}
|
|
43
|
+
if (stroke) {
|
|
44
|
+
const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
|
|
45
|
+
ctx.strokeStyle = stroke;
|
|
46
|
+
ctx.lineWidth = scaledStrokeWidth;
|
|
47
|
+
ctx.stroke();
|
|
48
|
+
}
|
|
68
49
|
}
|
|
69
50
|
|
|
70
51
|
// lib/drawer/shapes/rect.ts
|
|
@@ -133,107 +114,71 @@ function drawRect(params) {
|
|
|
133
114
|
ctx.restore();
|
|
134
115
|
}
|
|
135
116
|
|
|
136
|
-
// lib/drawer/
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
ctx.beginPath();
|
|
160
|
-
ctx.ellipse(0, 0, scaledRadiusX, scaledRadiusY, 0, 0, Math.PI * 2);
|
|
161
|
-
if (fill) {
|
|
162
|
-
ctx.fillStyle = fill;
|
|
163
|
-
ctx.fill();
|
|
117
|
+
// lib/drawer/elements/pcb-board.ts
|
|
118
|
+
function drawPcbBoard(params) {
|
|
119
|
+
const { ctx, board, realToCanvasMat, colorMap, drawBoardMaterial } = params;
|
|
120
|
+
const { width, height, center, outline } = board;
|
|
121
|
+
if (outline && Array.isArray(outline) && outline.length >= 3) {
|
|
122
|
+
if (drawBoardMaterial) {
|
|
123
|
+
drawPath({
|
|
124
|
+
ctx,
|
|
125
|
+
points: outline.map((p) => ({ x: p.x, y: p.y })),
|
|
126
|
+
fill: colorMap.substrate,
|
|
127
|
+
realToCanvasMat,
|
|
128
|
+
closePath: true
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
drawPath({
|
|
132
|
+
ctx,
|
|
133
|
+
points: outline.map((p) => ({ x: p.x, y: p.y })),
|
|
134
|
+
stroke: colorMap.boardOutline,
|
|
135
|
+
strokeWidth: 0.1,
|
|
136
|
+
realToCanvasMat,
|
|
137
|
+
closePath: true
|
|
138
|
+
});
|
|
139
|
+
return;
|
|
164
140
|
}
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
141
|
+
if (width !== void 0 && height !== void 0 && center) {
|
|
142
|
+
if (drawBoardMaterial) {
|
|
143
|
+
drawRect({
|
|
144
|
+
ctx,
|
|
145
|
+
center,
|
|
146
|
+
width,
|
|
147
|
+
height,
|
|
148
|
+
fill: colorMap.substrate,
|
|
149
|
+
realToCanvasMat
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const halfWidth = width / 2;
|
|
153
|
+
const halfHeight = height / 2;
|
|
154
|
+
const corners = [
|
|
155
|
+
{ x: center.x - halfWidth, y: center.y - halfHeight },
|
|
156
|
+
{ x: center.x + halfWidth, y: center.y - halfHeight },
|
|
157
|
+
{ x: center.x + halfWidth, y: center.y + halfHeight },
|
|
158
|
+
{ x: center.x - halfWidth, y: center.y + halfHeight }
|
|
159
|
+
];
|
|
160
|
+
drawPath({
|
|
161
|
+
ctx,
|
|
162
|
+
points: corners,
|
|
163
|
+
stroke: colorMap.boardOutline,
|
|
164
|
+
strokeWidth: 0.1,
|
|
165
|
+
realToCanvasMat,
|
|
166
|
+
closePath: true
|
|
167
|
+
});
|
|
169
168
|
}
|
|
170
|
-
ctx.restore();
|
|
171
169
|
}
|
|
172
170
|
|
|
173
|
-
// lib/drawer/
|
|
171
|
+
// lib/drawer/elements/pcb-copper-pour.ts
|
|
174
172
|
import { applyToPoint as applyToPoint4 } from "transformation-matrix";
|
|
175
|
-
function drawPill(params) {
|
|
176
|
-
const {
|
|
177
|
-
ctx,
|
|
178
|
-
center,
|
|
179
|
-
width,
|
|
180
|
-
height,
|
|
181
|
-
fill,
|
|
182
|
-
realToCanvasMat,
|
|
183
|
-
rotation = 0,
|
|
184
|
-
stroke,
|
|
185
|
-
strokeWidth
|
|
186
|
-
} = params;
|
|
187
|
-
const [cx, cy] = applyToPoint4(realToCanvasMat, [center.x, center.y]);
|
|
188
|
-
const scaledWidth = width * Math.abs(realToCanvasMat.a);
|
|
189
|
-
const scaledHeight = height * Math.abs(realToCanvasMat.a);
|
|
190
|
-
const scaledStrokeWidth = strokeWidth ? strokeWidth * Math.abs(realToCanvasMat.a) : void 0;
|
|
191
|
-
ctx.save();
|
|
192
|
-
ctx.translate(cx, cy);
|
|
193
|
-
if (rotation !== 0) {
|
|
194
|
-
ctx.rotate(-rotation * (Math.PI / 180));
|
|
195
|
-
}
|
|
196
|
-
ctx.beginPath();
|
|
197
|
-
if (scaledWidth > scaledHeight) {
|
|
198
|
-
const radius = scaledHeight / 2;
|
|
199
|
-
const straightLength = scaledWidth - scaledHeight;
|
|
200
|
-
ctx.moveTo(-straightLength / 2, -radius);
|
|
201
|
-
ctx.lineTo(straightLength / 2, -radius);
|
|
202
|
-
ctx.arc(straightLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2);
|
|
203
|
-
ctx.lineTo(-straightLength / 2, radius);
|
|
204
|
-
ctx.arc(-straightLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2);
|
|
205
|
-
} else if (scaledHeight > scaledWidth) {
|
|
206
|
-
const radius = scaledWidth / 2;
|
|
207
|
-
const straightLength = scaledHeight - scaledWidth;
|
|
208
|
-
ctx.moveTo(radius, -straightLength / 2);
|
|
209
|
-
ctx.lineTo(radius, straightLength / 2);
|
|
210
|
-
ctx.arc(0, straightLength / 2, radius, 0, Math.PI);
|
|
211
|
-
ctx.lineTo(-radius, -straightLength / 2);
|
|
212
|
-
ctx.arc(0, -straightLength / 2, radius, Math.PI, 0);
|
|
213
|
-
} else {
|
|
214
|
-
ctx.arc(0, 0, scaledWidth / 2, 0, Math.PI * 2);
|
|
215
|
-
}
|
|
216
|
-
ctx.closePath();
|
|
217
|
-
if (fill) {
|
|
218
|
-
ctx.fillStyle = fill;
|
|
219
|
-
ctx.fill();
|
|
220
|
-
}
|
|
221
|
-
if (stroke && scaledStrokeWidth) {
|
|
222
|
-
ctx.strokeStyle = stroke;
|
|
223
|
-
ctx.lineWidth = scaledStrokeWidth;
|
|
224
|
-
ctx.stroke();
|
|
225
|
-
}
|
|
226
|
-
ctx.restore();
|
|
227
|
-
}
|
|
228
173
|
|
|
229
174
|
// lib/drawer/shapes/polygon.ts
|
|
230
|
-
import { applyToPoint as
|
|
175
|
+
import { applyToPoint as applyToPoint3 } from "transformation-matrix";
|
|
231
176
|
function drawPolygon(params) {
|
|
232
177
|
const { ctx, points, fill, realToCanvasMat } = params;
|
|
233
178
|
if (points.length < 3) return;
|
|
234
179
|
ctx.beginPath();
|
|
235
180
|
const canvasPoints = points.map(
|
|
236
|
-
(p) =>
|
|
181
|
+
(p) => applyToPoint3(realToCanvasMat, [p.x, p.y])
|
|
237
182
|
);
|
|
238
183
|
const firstPoint = canvasPoints[0];
|
|
239
184
|
if (!firstPoint) return;
|
|
@@ -250,452 +195,427 @@ function drawPolygon(params) {
|
|
|
250
195
|
ctx.fill();
|
|
251
196
|
}
|
|
252
197
|
|
|
253
|
-
// lib/drawer/elements/
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const ml = asymmetricMargins?.left ?? margin;
|
|
261
|
-
const mr = asymmetricMargins?.right ?? margin;
|
|
262
|
-
const mt = asymmetricMargins?.top ?? margin;
|
|
263
|
-
const mb = asymmetricMargins?.bottom ?? margin;
|
|
264
|
-
const scaledThicknessL = Math.max(0, -ml) * Math.abs(realToCanvasMat.a);
|
|
265
|
-
const scaledThicknessR = Math.max(0, -mr) * Math.abs(realToCanvasMat.a);
|
|
266
|
-
const scaledThicknessT = Math.max(0, -mt) * Math.abs(realToCanvasMat.a);
|
|
267
|
-
const scaledThicknessB = Math.max(0, -mb) * Math.abs(realToCanvasMat.a);
|
|
268
|
-
ctx.save();
|
|
269
|
-
ctx.translate(cx, cy);
|
|
270
|
-
if (rotation !== 0) {
|
|
271
|
-
ctx.rotate(-rotation * (Math.PI / 180));
|
|
198
|
+
// lib/drawer/elements/pcb-copper-pour.ts
|
|
199
|
+
function layerToColor(layer, colorMap) {
|
|
200
|
+
return colorMap.copper[layer] ?? colorMap.copper.top;
|
|
201
|
+
}
|
|
202
|
+
function computeArcFromBulge(startX, startY, endX, endY, bulge) {
|
|
203
|
+
if (Math.abs(bulge) < 1e-10) {
|
|
204
|
+
return null;
|
|
272
205
|
}
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
206
|
+
const chordX = endX - startX;
|
|
207
|
+
const chordY = endY - startY;
|
|
208
|
+
const chordLength = Math.hypot(chordX, chordY);
|
|
209
|
+
if (chordLength < 1e-10) {
|
|
210
|
+
return null;
|
|
276
211
|
}
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
);
|
|
296
|
-
ctx.lineTo(x + r, y + outerHeight);
|
|
297
|
-
ctx.arcTo(x, y + outerHeight, x, y + outerHeight - r, r);
|
|
298
|
-
ctx.lineTo(x, y + r);
|
|
299
|
-
ctx.arcTo(x, y, x + r, y, r);
|
|
300
|
-
} else {
|
|
301
|
-
ctx.rect(-outerWidth / 2, -outerHeight / 2, outerWidth, outerHeight);
|
|
212
|
+
const sagitta = Math.abs(bulge) * (chordLength / 2);
|
|
213
|
+
const halfChord = chordLength / 2;
|
|
214
|
+
const radius = (sagitta * sagitta + halfChord * halfChord) / (2 * sagitta);
|
|
215
|
+
const distToCenter = radius - sagitta;
|
|
216
|
+
const midX = (startX + endX) / 2;
|
|
217
|
+
const midY = (startY + endY) / 2;
|
|
218
|
+
const perpX = -chordY / chordLength;
|
|
219
|
+
const perpY = chordX / chordLength;
|
|
220
|
+
const sign = bulge > 0 ? -1 : 1;
|
|
221
|
+
const centerX = midX + sign * perpX * distToCenter;
|
|
222
|
+
const centerY = midY + sign * perpY * distToCenter;
|
|
223
|
+
return { centerX, centerY, radius };
|
|
224
|
+
}
|
|
225
|
+
function drawArcFromBulge(ctx, realStartX, realStartY, realEndX, realEndY, bulge, realToCanvasMat) {
|
|
226
|
+
if (Math.abs(bulge) < 1e-10) {
|
|
227
|
+
const [endX, endY] = applyToPoint4(realToCanvasMat, [realEndX, realEndY]);
|
|
228
|
+
ctx.lineTo(endX, endY);
|
|
229
|
+
return;
|
|
302
230
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
231
|
+
const arc = computeArcFromBulge(
|
|
232
|
+
realStartX,
|
|
233
|
+
realStartY,
|
|
234
|
+
realEndX,
|
|
235
|
+
realEndY,
|
|
236
|
+
bulge
|
|
237
|
+
);
|
|
238
|
+
if (!arc) {
|
|
239
|
+
const [endX, endY] = applyToPoint4(realToCanvasMat, [realEndX, realEndY]);
|
|
240
|
+
ctx.lineTo(endX, endY);
|
|
241
|
+
return;
|
|
307
242
|
}
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
243
|
+
const [canvasStartX, canvasStartY] = applyToPoint4(realToCanvasMat, [
|
|
244
|
+
realStartX,
|
|
245
|
+
realStartY
|
|
246
|
+
]);
|
|
247
|
+
const [canvasEndX, canvasEndY] = applyToPoint4(realToCanvasMat, [
|
|
248
|
+
realEndX,
|
|
249
|
+
realEndY
|
|
250
|
+
]);
|
|
251
|
+
const [canvasCenterX, canvasCenterY] = applyToPoint4(realToCanvasMat, [
|
|
252
|
+
arc.centerX,
|
|
253
|
+
arc.centerY
|
|
254
|
+
]);
|
|
255
|
+
const canvasRadius = Math.hypot(
|
|
256
|
+
canvasStartX - canvasCenterX,
|
|
257
|
+
canvasStartY - canvasCenterY
|
|
311
258
|
);
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
259
|
+
const startAngle = Math.atan2(
|
|
260
|
+
canvasStartY - canvasCenterY,
|
|
261
|
+
canvasStartX - canvasCenterX
|
|
315
262
|
);
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
263
|
+
const endAngle = Math.atan2(
|
|
264
|
+
canvasEndY - canvasCenterY,
|
|
265
|
+
canvasEndX - canvasCenterX
|
|
319
266
|
);
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
ctx.
|
|
340
|
-
ctx.
|
|
267
|
+
const det = realToCanvasMat.a * realToCanvasMat.d - realToCanvasMat.b * realToCanvasMat.c;
|
|
268
|
+
const isFlipped = det < 0;
|
|
269
|
+
const counterclockwise = bulge > 0 ? !isFlipped : isFlipped;
|
|
270
|
+
ctx.arc(
|
|
271
|
+
canvasCenterX,
|
|
272
|
+
canvasCenterY,
|
|
273
|
+
canvasRadius,
|
|
274
|
+
startAngle,
|
|
275
|
+
endAngle,
|
|
276
|
+
counterclockwise
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
function drawRing(ctx, ring, realToCanvasMat) {
|
|
280
|
+
if (ring.vertices.length < 2) return;
|
|
281
|
+
if (ring.vertices.length === 2) {
|
|
282
|
+
const v0 = ring.vertices[0];
|
|
283
|
+
const v1 = ring.vertices[1];
|
|
284
|
+
if (v0 && v1 && Math.abs((v0.bulge ?? 0) - 1) < 1e-10 && Math.abs((v1.bulge ?? 0) - 1) < 1e-10) {
|
|
285
|
+
const [x0, y0] = applyToPoint4(realToCanvasMat, [v0.x, v0.y]);
|
|
286
|
+
ctx.moveTo(x0, y0);
|
|
287
|
+
drawArcFromBulge(ctx, v0.x, v0.y, v1.x, v1.y, 1, realToCanvasMat);
|
|
288
|
+
drawArcFromBulge(ctx, v1.x, v1.y, v0.x, v0.y, 1, realToCanvasMat);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const firstVertex = ring.vertices[0];
|
|
293
|
+
if (!firstVertex) return;
|
|
294
|
+
const [firstX, firstY] = applyToPoint4(realToCanvasMat, [
|
|
295
|
+
firstVertex.x,
|
|
296
|
+
firstVertex.y
|
|
297
|
+
]);
|
|
298
|
+
ctx.moveTo(firstX, firstY);
|
|
299
|
+
for (let i = 0; i < ring.vertices.length; i++) {
|
|
300
|
+
const currentVertex = ring.vertices[i];
|
|
301
|
+
const nextIndex = (i + 1) % ring.vertices.length;
|
|
302
|
+
const nextVertex = ring.vertices[nextIndex];
|
|
303
|
+
if (!currentVertex || !nextVertex) continue;
|
|
304
|
+
const bulge = currentVertex.bulge ?? 0;
|
|
305
|
+
if (Math.abs(bulge) < 1e-10) {
|
|
306
|
+
const [nextX, nextY] = applyToPoint4(realToCanvasMat, [
|
|
307
|
+
nextVertex.x,
|
|
308
|
+
nextVertex.y
|
|
309
|
+
]);
|
|
310
|
+
ctx.lineTo(nextX, nextY);
|
|
341
311
|
} else {
|
|
342
|
-
|
|
312
|
+
drawArcFromBulge(
|
|
313
|
+
ctx,
|
|
314
|
+
currentVertex.x,
|
|
315
|
+
currentVertex.y,
|
|
316
|
+
nextVertex.x,
|
|
317
|
+
nextVertex.y,
|
|
318
|
+
bulge,
|
|
319
|
+
realToCanvasMat
|
|
320
|
+
);
|
|
343
321
|
}
|
|
344
|
-
ctx.fillStyle = padColor;
|
|
345
|
-
ctx.fill();
|
|
346
322
|
}
|
|
347
|
-
ctx.restore();
|
|
348
323
|
}
|
|
349
|
-
function
|
|
350
|
-
const
|
|
351
|
-
const
|
|
352
|
-
const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
|
|
324
|
+
function drawPcbCopperPour(params) {
|
|
325
|
+
const { ctx, pour, realToCanvasMat, colorMap } = params;
|
|
326
|
+
const color = layerToColor(pour.layer, colorMap);
|
|
353
327
|
ctx.save();
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
328
|
+
if (pour.shape === "rect") {
|
|
329
|
+
const [cx, cy] = applyToPoint4(realToCanvasMat, [
|
|
330
|
+
pour.center.x,
|
|
331
|
+
pour.center.y
|
|
332
|
+
]);
|
|
333
|
+
const scaledWidth = pour.width * Math.abs(realToCanvasMat.a);
|
|
334
|
+
const scaledHeight = pour.height * Math.abs(realToCanvasMat.a);
|
|
335
|
+
ctx.translate(cx, cy);
|
|
336
|
+
if (pour.rotation) {
|
|
337
|
+
ctx.rotate(-pour.rotation * (Math.PI / 180));
|
|
338
|
+
}
|
|
339
|
+
ctx.beginPath();
|
|
340
|
+
ctx.rect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight);
|
|
341
|
+
ctx.fillStyle = color;
|
|
342
|
+
ctx.globalAlpha = 0.5;
|
|
343
|
+
ctx.fill();
|
|
344
|
+
ctx.restore();
|
|
345
|
+
return;
|
|
357
346
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
347
|
+
if (pour.shape === "polygon") {
|
|
348
|
+
if (pour.points && pour.points.length >= 3) {
|
|
349
|
+
const canvasPoints = pour.points.map(
|
|
350
|
+
(p) => applyToPoint4(realToCanvasMat, [p.x, p.y])
|
|
351
|
+
);
|
|
352
|
+
const firstPoint = canvasPoints[0];
|
|
353
|
+
if (!firstPoint) {
|
|
354
|
+
ctx.restore();
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
ctx.beginPath();
|
|
358
|
+
const [firstX, firstY] = firstPoint;
|
|
359
|
+
ctx.moveTo(firstX, firstY);
|
|
360
|
+
for (let i = 1; i < canvasPoints.length; i++) {
|
|
361
|
+
const point = canvasPoints[i];
|
|
362
|
+
if (!point) continue;
|
|
363
|
+
const [x, y] = point;
|
|
364
|
+
ctx.lineTo(x, y);
|
|
365
|
+
}
|
|
366
|
+
ctx.closePath();
|
|
367
|
+
ctx.fillStyle = color;
|
|
368
|
+
ctx.globalAlpha = 0.5;
|
|
369
|
+
ctx.fill();
|
|
370
|
+
}
|
|
371
|
+
ctx.restore();
|
|
372
|
+
return;
|
|
364
373
|
}
|
|
365
|
-
|
|
366
|
-
if (innerRadius > 0) {
|
|
374
|
+
if (pour.shape === "brep") {
|
|
367
375
|
ctx.beginPath();
|
|
368
|
-
ctx
|
|
369
|
-
|
|
370
|
-
|
|
376
|
+
drawRing(ctx, pour.brep_shape.outer_ring, realToCanvasMat);
|
|
377
|
+
if (pour.brep_shape.inner_rings) {
|
|
378
|
+
for (const innerRing of pour.brep_shape.inner_rings) {
|
|
379
|
+
drawRing(ctx, innerRing, realToCanvasMat);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
ctx.fillStyle = color;
|
|
383
|
+
ctx.globalAlpha = 0.5;
|
|
384
|
+
ctx.fill("evenodd");
|
|
385
|
+
ctx.restore();
|
|
386
|
+
return;
|
|
371
387
|
}
|
|
372
388
|
ctx.restore();
|
|
373
389
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
390
|
+
|
|
391
|
+
// lib/drawer/elements/pcb-copper-text.ts
|
|
392
|
+
import { applyToPoint as applyToPoint6 } from "transformation-matrix";
|
|
393
|
+
|
|
394
|
+
// lib/drawer/shapes/text/text.ts
|
|
395
|
+
import { lineAlphabet } from "@tscircuit/alphabet";
|
|
396
|
+
import { applyToPoint as applyToPoint5 } from "transformation-matrix";
|
|
397
|
+
|
|
398
|
+
// lib/drawer/shapes/text/getAlphabetLayout.ts
|
|
399
|
+
var GLYPH_WIDTH_RATIO = 0.62;
|
|
400
|
+
var LETTER_SPACING_RATIO = 0.3;
|
|
401
|
+
var SPACE_WIDTH_RATIO = 1;
|
|
402
|
+
var STROKE_WIDTH_RATIO = 0.13;
|
|
403
|
+
function getAlphabetLayout(text, fontSize) {
|
|
404
|
+
const glyphWidth = fontSize * GLYPH_WIDTH_RATIO;
|
|
405
|
+
const letterSpacing = glyphWidth * LETTER_SPACING_RATIO;
|
|
406
|
+
const spaceWidth = glyphWidth * SPACE_WIDTH_RATIO;
|
|
407
|
+
const characters = Array.from(text);
|
|
408
|
+
let width = 0;
|
|
409
|
+
characters.forEach((char, index) => {
|
|
410
|
+
const advance = char === " " ? spaceWidth : glyphWidth;
|
|
411
|
+
width += advance;
|
|
412
|
+
if (index < characters.length - 1) width += letterSpacing;
|
|
413
|
+
});
|
|
414
|
+
const strokeWidth = Math.max(fontSize * STROKE_WIDTH_RATIO, 0.35);
|
|
415
|
+
return {
|
|
416
|
+
width,
|
|
417
|
+
height: fontSize,
|
|
418
|
+
glyphWidth,
|
|
419
|
+
letterSpacing,
|
|
420
|
+
spaceWidth,
|
|
421
|
+
strokeWidth
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// lib/drawer/shapes/text/getTextStartPosition.ts
|
|
426
|
+
function getTextStartPosition(alignment, layout) {
|
|
427
|
+
const totalWidth = layout.width + layout.strokeWidth;
|
|
428
|
+
const totalHeight = layout.height + layout.strokeWidth;
|
|
429
|
+
let x = 0;
|
|
430
|
+
let y = 0;
|
|
431
|
+
if (alignment === "center") {
|
|
432
|
+
x = -totalWidth / 2;
|
|
433
|
+
} else if (alignment === "top_left" || alignment === "bottom_left" || alignment === "center_left") {
|
|
434
|
+
x = 0;
|
|
435
|
+
} else if (alignment === "top_right" || alignment === "bottom_right" || alignment === "center_right") {
|
|
436
|
+
x = -totalWidth;
|
|
409
437
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
if (
|
|
413
|
-
|
|
438
|
+
if (alignment === "center") {
|
|
439
|
+
y = -totalHeight / 2;
|
|
440
|
+
} else if (alignment === "top_left" || alignment === "top_right" || alignment === "top_center") {
|
|
441
|
+
y = 0;
|
|
442
|
+
} else if (alignment === "bottom_left" || alignment === "bottom_right" || alignment === "bottom_center") {
|
|
443
|
+
y = -totalHeight;
|
|
444
|
+
} else {
|
|
445
|
+
y = 0;
|
|
414
446
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
ctx.
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
447
|
+
return { x, y };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// lib/drawer/shapes/text/text.ts
|
|
451
|
+
var getGlyphLines = (char) => lineAlphabet[char] ?? lineAlphabet[char.toUpperCase()];
|
|
452
|
+
function strokeAlphabetText(params) {
|
|
453
|
+
const { ctx, text, fontSize, startX, startY } = params;
|
|
454
|
+
const layout = getAlphabetLayout(text, fontSize);
|
|
455
|
+
const { glyphWidth, letterSpacing, spaceWidth, height, strokeWidth } = layout;
|
|
456
|
+
const topY = startY;
|
|
457
|
+
const characters = Array.from(text);
|
|
458
|
+
let cursor = startX + strokeWidth / 2;
|
|
459
|
+
characters.forEach((char, index) => {
|
|
460
|
+
const lines = getGlyphLines(char);
|
|
461
|
+
const advance = char === " " ? spaceWidth : glyphWidth;
|
|
462
|
+
if (lines?.length) {
|
|
463
|
+
ctx.beginPath();
|
|
464
|
+
for (const line of lines) {
|
|
465
|
+
const x1 = cursor + line.x1 * glyphWidth;
|
|
466
|
+
const y1 = topY + (1 - line.y1) * height;
|
|
467
|
+
const x2 = cursor + line.x2 * glyphWidth;
|
|
468
|
+
const y2 = topY + (1 - line.y2) * height;
|
|
469
|
+
ctx.moveTo(x1, y1);
|
|
470
|
+
ctx.lineTo(x2, y2);
|
|
471
|
+
}
|
|
472
|
+
ctx.stroke();
|
|
437
473
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
474
|
+
cursor += advance;
|
|
475
|
+
if (index < characters.length - 1) {
|
|
476
|
+
cursor += letterSpacing;
|
|
477
|
+
}
|
|
478
|
+
});
|
|
442
479
|
}
|
|
443
|
-
function
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
480
|
+
function drawText(params) {
|
|
481
|
+
const {
|
|
482
|
+
ctx,
|
|
483
|
+
text,
|
|
484
|
+
x,
|
|
485
|
+
y,
|
|
486
|
+
fontSize,
|
|
487
|
+
color,
|
|
488
|
+
realToCanvasMat,
|
|
489
|
+
anchorAlignment,
|
|
490
|
+
rotation = 0
|
|
491
|
+
} = params;
|
|
492
|
+
if (!text) return;
|
|
493
|
+
const [canvasX, canvasY] = applyToPoint5(realToCanvasMat, [x, y]);
|
|
494
|
+
const scale2 = Math.abs(realToCanvasMat.a);
|
|
495
|
+
const scaledFontSize = fontSize * scale2;
|
|
496
|
+
const layout = getAlphabetLayout(text, scaledFontSize);
|
|
497
|
+
const startPos = getTextStartPosition(anchorAlignment, layout);
|
|
448
498
|
ctx.save();
|
|
449
|
-
ctx.translate(
|
|
499
|
+
ctx.translate(canvasX, canvasY);
|
|
450
500
|
if (rotation !== 0) {
|
|
451
501
|
ctx.rotate(-rotation * (Math.PI / 180));
|
|
452
502
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
503
|
+
ctx.lineWidth = layout.strokeWidth;
|
|
504
|
+
ctx.lineCap = "round";
|
|
505
|
+
ctx.lineJoin = "round";
|
|
506
|
+
ctx.strokeStyle = color;
|
|
507
|
+
strokeAlphabetText({
|
|
508
|
+
ctx,
|
|
509
|
+
text,
|
|
510
|
+
fontSize: scaledFontSize,
|
|
511
|
+
startX: startPos.x,
|
|
512
|
+
startY: startPos.y
|
|
513
|
+
});
|
|
514
|
+
ctx.restore();
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// lib/drawer/elements/pcb-copper-text.ts
|
|
518
|
+
var DEFAULT_PADDING = { left: 0.2, right: 0.2, top: 0.2, bottom: 0.2 };
|
|
519
|
+
function mapAnchorAlignment(alignment) {
|
|
520
|
+
if (!alignment) return "center";
|
|
521
|
+
if (alignment.includes("left")) return "center_left";
|
|
522
|
+
if (alignment.includes("right")) return "center_right";
|
|
523
|
+
return "center";
|
|
524
|
+
}
|
|
525
|
+
function drawPcbCopperText(params) {
|
|
526
|
+
const { ctx, text, realToCanvasMat, colorMap } = params;
|
|
527
|
+
const content = text.text ?? "";
|
|
528
|
+
if (!content) return;
|
|
529
|
+
const [x, y] = applyToPoint6(realToCanvasMat, [
|
|
530
|
+
text.anchor_position.x,
|
|
531
|
+
text.anchor_position.y
|
|
532
|
+
]);
|
|
533
|
+
const scale2 = Math.abs(realToCanvasMat.a);
|
|
534
|
+
const fontSize = (text.font_size ?? 1) * scale2;
|
|
535
|
+
const rotation = text.ccw_rotation ?? 0;
|
|
536
|
+
const padding = {
|
|
537
|
+
...DEFAULT_PADDING,
|
|
538
|
+
...text.knockout_padding
|
|
539
|
+
};
|
|
540
|
+
const textColor = colorMap.copper[text.layer] ?? colorMap.copper.top;
|
|
541
|
+
const layout = getAlphabetLayout(content, fontSize);
|
|
542
|
+
const totalWidth = layout.width + layout.strokeWidth;
|
|
543
|
+
const alignment = mapAnchorAlignment(text.anchor_alignment);
|
|
544
|
+
const startPos = getTextStartPosition(alignment, layout);
|
|
545
|
+
ctx.save();
|
|
546
|
+
ctx.translate(x, y);
|
|
547
|
+
if (text.is_mirrored) ctx.scale(-1, 1);
|
|
548
|
+
if (rotation !== 0) ctx.rotate(-rotation * (Math.PI / 180));
|
|
549
|
+
ctx.lineWidth = layout.strokeWidth;
|
|
550
|
+
ctx.lineCap = "round";
|
|
551
|
+
ctx.lineJoin = "round";
|
|
552
|
+
if (text.is_knockout) {
|
|
553
|
+
const paddingLeft = padding.left * scale2;
|
|
554
|
+
const paddingRight = padding.right * scale2;
|
|
555
|
+
const paddingTop = padding.top * scale2;
|
|
556
|
+
const paddingBottom = padding.bottom * scale2;
|
|
557
|
+
const rectX = startPos.x - paddingLeft * 4;
|
|
558
|
+
const rectY = startPos.y - paddingTop * 4;
|
|
559
|
+
const rectWidth = totalWidth + paddingLeft * 2 + paddingRight * 2;
|
|
560
|
+
const rectHeight = layout.height + layout.strokeWidth + paddingTop * 2 + paddingBottom * 2;
|
|
561
|
+
ctx.fillStyle = textColor;
|
|
562
|
+
ctx.fillRect(rectX, rectY, rectWidth, rectHeight);
|
|
563
|
+
} else {
|
|
564
|
+
ctx.strokeStyle = textColor;
|
|
456
565
|
}
|
|
566
|
+
strokeAlphabetText({
|
|
567
|
+
ctx,
|
|
568
|
+
text: content,
|
|
569
|
+
fontSize,
|
|
570
|
+
startX: startPos.x,
|
|
571
|
+
startY: startPos.y
|
|
572
|
+
});
|
|
573
|
+
ctx.restore();
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// lib/drawer/shapes/circle.ts
|
|
577
|
+
import { applyToPoint as applyToPoint7 } from "transformation-matrix";
|
|
578
|
+
function drawCircle(params) {
|
|
579
|
+
const { ctx, center, radius, fill, realToCanvasMat } = params;
|
|
580
|
+
const [cx, cy] = applyToPoint7(realToCanvasMat, [center.x, center.y]);
|
|
581
|
+
const scaledRadius = radius * Math.abs(realToCanvasMat.a);
|
|
457
582
|
ctx.beginPath();
|
|
458
|
-
ctx.
|
|
459
|
-
ctx.fillStyle =
|
|
583
|
+
ctx.arc(cx, cy, scaledRadius, 0, Math.PI * 2);
|
|
584
|
+
ctx.fillStyle = fill;
|
|
460
585
|
ctx.fill();
|
|
461
|
-
if (ctx.globalCompositeOperation !== void 0) {
|
|
462
|
-
ctx.globalCompositeOperation = prevCompositeOp || "source-over";
|
|
463
|
-
}
|
|
464
|
-
const innerRadiusX = Math.max(0, scaledRadiusX - scaledMargin);
|
|
465
|
-
const innerRadiusY = Math.max(0, scaledRadiusY - scaledMargin);
|
|
466
|
-
if (innerRadiusX > 0 && innerRadiusY > 0) {
|
|
467
|
-
ctx.beginPath();
|
|
468
|
-
ctx.ellipse(0, 0, innerRadiusX, innerRadiusY, 0, 0, Math.PI * 2);
|
|
469
|
-
ctx.fillStyle = holeColor;
|
|
470
|
-
ctx.fill();
|
|
471
|
-
}
|
|
472
|
-
ctx.restore();
|
|
473
|
-
}
|
|
474
|
-
function offsetPolygonPoints(points, offset) {
|
|
475
|
-
if (points.length < 3 || offset === 0) return points;
|
|
476
|
-
let centerX = 0;
|
|
477
|
-
let centerY = 0;
|
|
478
|
-
for (const point of points) {
|
|
479
|
-
centerX += point.x;
|
|
480
|
-
centerY += point.y;
|
|
481
|
-
}
|
|
482
|
-
centerX /= points.length;
|
|
483
|
-
centerY /= points.length;
|
|
484
|
-
const result = [];
|
|
485
|
-
for (const point of points) {
|
|
486
|
-
const vectorX = point.x - centerX;
|
|
487
|
-
const vectorY = point.y - centerY;
|
|
488
|
-
const distance = Math.sqrt(vectorX * vectorX + vectorY * vectorY);
|
|
489
|
-
if (distance > 0) {
|
|
490
|
-
const normalizedX = vectorX / distance;
|
|
491
|
-
const normalizedY = vectorY / distance;
|
|
492
|
-
result.push({
|
|
493
|
-
x: point.x + normalizedX * offset,
|
|
494
|
-
y: point.y + normalizedY * offset
|
|
495
|
-
});
|
|
496
|
-
} else {
|
|
497
|
-
result.push({
|
|
498
|
-
x: point.x + offset,
|
|
499
|
-
y: point.y
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
return result;
|
|
504
586
|
}
|
|
505
587
|
|
|
506
|
-
// lib/drawer/elements/pcb-
|
|
507
|
-
function
|
|
508
|
-
const {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
realToCanvasMat,
|
|
512
|
-
colorMap,
|
|
513
|
-
soldermaskMargin = 0,
|
|
514
|
-
showSoldermask
|
|
515
|
-
} = params;
|
|
516
|
-
if (hole.is_covered_with_solder_mask === true && showSoldermask) {
|
|
517
|
-
return;
|
|
518
|
-
}
|
|
519
|
-
const copperColor = colorMap.copper.top;
|
|
520
|
-
const copperInset = soldermaskMargin < 0 ? Math.abs(soldermaskMargin) : 0;
|
|
521
|
-
if (hole.shape === "circle") {
|
|
522
|
-
drawCircle({
|
|
523
|
-
ctx,
|
|
524
|
-
center: { x: hole.x, y: hole.y },
|
|
525
|
-
radius: hole.outer_diameter / 2 - copperInset,
|
|
526
|
-
fill: copperColor,
|
|
527
|
-
realToCanvasMat
|
|
528
|
-
});
|
|
529
|
-
drawCircle({
|
|
530
|
-
ctx,
|
|
531
|
-
center: { x: hole.x, y: hole.y },
|
|
532
|
-
radius: hole.hole_diameter / 2,
|
|
533
|
-
fill: colorMap.drill,
|
|
534
|
-
realToCanvasMat
|
|
535
|
-
});
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
if (hole.shape === "oval") {
|
|
539
|
-
drawOval({
|
|
540
|
-
ctx,
|
|
541
|
-
center: { x: hole.x, y: hole.y },
|
|
542
|
-
radius_x: hole.outer_width / 2 - copperInset,
|
|
543
|
-
radius_y: hole.outer_height / 2 - copperInset,
|
|
544
|
-
fill: copperColor,
|
|
545
|
-
realToCanvasMat,
|
|
546
|
-
rotation: hole.ccw_rotation
|
|
547
|
-
});
|
|
548
|
-
drawOval({
|
|
549
|
-
ctx,
|
|
550
|
-
center: { x: hole.x, y: hole.y },
|
|
551
|
-
radius_x: hole.hole_width / 2,
|
|
552
|
-
radius_y: hole.hole_height / 2,
|
|
553
|
-
fill: colorMap.drill,
|
|
554
|
-
realToCanvasMat,
|
|
555
|
-
rotation: hole.ccw_rotation
|
|
556
|
-
});
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
if (hole.shape === "pill") {
|
|
560
|
-
drawPill({
|
|
561
|
-
ctx,
|
|
562
|
-
center: { x: hole.x, y: hole.y },
|
|
563
|
-
width: hole.outer_width - copperInset * 2,
|
|
564
|
-
height: hole.outer_height - copperInset * 2,
|
|
565
|
-
fill: copperColor,
|
|
566
|
-
realToCanvasMat,
|
|
567
|
-
rotation: hole.ccw_rotation
|
|
568
|
-
});
|
|
569
|
-
drawPill({
|
|
588
|
+
// lib/drawer/elements/pcb-cutout.ts
|
|
589
|
+
function drawPcbCutout(params) {
|
|
590
|
+
const { ctx, cutout, realToCanvasMat, colorMap } = params;
|
|
591
|
+
if (cutout.shape === "rect") {
|
|
592
|
+
drawRect({
|
|
570
593
|
ctx,
|
|
571
|
-
center:
|
|
572
|
-
width:
|
|
573
|
-
height:
|
|
594
|
+
center: cutout.center,
|
|
595
|
+
width: cutout.width,
|
|
596
|
+
height: cutout.height,
|
|
574
597
|
fill: colorMap.drill,
|
|
575
598
|
realToCanvasMat,
|
|
576
|
-
rotation:
|
|
599
|
+
rotation: cutout.rotation ?? 0,
|
|
600
|
+
borderRadius: cutout.corner_radius ?? 0
|
|
577
601
|
});
|
|
578
602
|
return;
|
|
579
603
|
}
|
|
580
|
-
if (
|
|
581
|
-
drawRect({
|
|
582
|
-
ctx,
|
|
583
|
-
center: { x: hole.x, y: hole.y },
|
|
584
|
-
width: hole.rect_pad_width - copperInset * 2,
|
|
585
|
-
height: hole.rect_pad_height - copperInset * 2,
|
|
586
|
-
fill: copperColor,
|
|
587
|
-
realToCanvasMat,
|
|
588
|
-
borderRadius: hole.rect_border_radius ? Math.max(0, hole.rect_border_radius - copperInset) : 0
|
|
589
|
-
});
|
|
590
|
-
const holeX = hole.x + (hole.hole_offset_x ?? 0);
|
|
591
|
-
const holeY = hole.y + (hole.hole_offset_y ?? 0);
|
|
604
|
+
if (cutout.shape === "circle") {
|
|
592
605
|
drawCircle({
|
|
593
606
|
ctx,
|
|
594
|
-
center:
|
|
595
|
-
radius:
|
|
596
|
-
fill: colorMap.drill,
|
|
597
|
-
realToCanvasMat
|
|
598
|
-
});
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
if (hole.shape === "pill_hole_with_rect_pad") {
|
|
602
|
-
drawRect({
|
|
603
|
-
ctx,
|
|
604
|
-
center: { x: hole.x, y: hole.y },
|
|
605
|
-
width: hole.rect_pad_width - copperInset * 2,
|
|
606
|
-
height: hole.rect_pad_height - copperInset * 2,
|
|
607
|
-
fill: copperColor,
|
|
608
|
-
realToCanvasMat,
|
|
609
|
-
borderRadius: hole.rect_border_radius ? Math.max(0, hole.rect_border_radius - copperInset) : 0
|
|
610
|
-
});
|
|
611
|
-
const holeX = hole.x + (hole.hole_offset_x ?? 0);
|
|
612
|
-
const holeY = hole.y + (hole.hole_offset_y ?? 0);
|
|
613
|
-
drawPill({
|
|
614
|
-
ctx,
|
|
615
|
-
center: { x: holeX, y: holeY },
|
|
616
|
-
width: hole.hole_width,
|
|
617
|
-
height: hole.hole_height,
|
|
607
|
+
center: cutout.center,
|
|
608
|
+
radius: cutout.radius,
|
|
618
609
|
fill: colorMap.drill,
|
|
619
610
|
realToCanvasMat
|
|
620
611
|
});
|
|
621
612
|
return;
|
|
622
613
|
}
|
|
623
|
-
if (
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
center: { x: hole.x, y: hole.y },
|
|
627
|
-
width: hole.rect_pad_width - copperInset * 2,
|
|
628
|
-
height: hole.rect_pad_height - copperInset * 2,
|
|
629
|
-
fill: copperColor,
|
|
630
|
-
realToCanvasMat,
|
|
631
|
-
borderRadius: hole.rect_border_radius ? Math.max(0, hole.rect_border_radius - copperInset) : 0,
|
|
632
|
-
rotation: hole.rect_ccw_rotation
|
|
633
|
-
});
|
|
634
|
-
const holeX = hole.x + (hole.hole_offset_x ?? 0);
|
|
635
|
-
const holeY = hole.y + (hole.hole_offset_y ?? 0);
|
|
636
|
-
drawPill({
|
|
637
|
-
ctx,
|
|
638
|
-
center: { x: holeX, y: holeY },
|
|
639
|
-
width: hole.hole_width,
|
|
640
|
-
height: hole.hole_height,
|
|
641
|
-
fill: colorMap.drill,
|
|
642
|
-
realToCanvasMat,
|
|
643
|
-
rotation: hole.hole_ccw_rotation
|
|
644
|
-
});
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
if (hole.shape === "hole_with_polygon_pad") {
|
|
648
|
-
const padOutline = hole.pad_outline;
|
|
649
|
-
if (padOutline && padOutline.length >= 3) {
|
|
650
|
-
const padPoints = padOutline.map((point) => ({
|
|
651
|
-
x: hole.x + point.x,
|
|
652
|
-
y: hole.y + point.y
|
|
653
|
-
}));
|
|
654
|
-
const copperPoints = copperInset > 0 ? offsetPolygonPoints(padPoints, -copperInset) : padPoints;
|
|
655
|
-
if (copperPoints.length >= 3) {
|
|
656
|
-
drawPolygon({
|
|
657
|
-
ctx,
|
|
658
|
-
points: copperPoints,
|
|
659
|
-
fill: copperColor,
|
|
660
|
-
realToCanvasMat
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
const holeX = hole.x + (hole.hole_offset_x ?? 0);
|
|
665
|
-
const holeY = hole.y + (hole.hole_offset_y ?? 0);
|
|
666
|
-
const holeShape = hole.hole_shape;
|
|
667
|
-
if (holeShape === "circle") {
|
|
668
|
-
drawCircle({
|
|
669
|
-
ctx,
|
|
670
|
-
center: { x: holeX, y: holeY },
|
|
671
|
-
radius: (hole.hole_diameter ?? 0) / 2,
|
|
672
|
-
fill: colorMap.drill,
|
|
673
|
-
realToCanvasMat
|
|
674
|
-
});
|
|
675
|
-
} else if (holeShape === "oval") {
|
|
676
|
-
drawOval({
|
|
677
|
-
ctx,
|
|
678
|
-
center: { x: holeX, y: holeY },
|
|
679
|
-
radius_x: (hole.hole_width ?? 0) / 2,
|
|
680
|
-
radius_y: (hole.hole_height ?? 0) / 2,
|
|
681
|
-
fill: colorMap.drill,
|
|
682
|
-
realToCanvasMat
|
|
683
|
-
});
|
|
684
|
-
} else if (holeShape === "pill") {
|
|
685
|
-
drawPill({
|
|
686
|
-
ctx,
|
|
687
|
-
center: { x: holeX, y: holeY },
|
|
688
|
-
width: hole.hole_width ?? 0,
|
|
689
|
-
height: hole.hole_height ?? 0,
|
|
690
|
-
fill: colorMap.drill,
|
|
691
|
-
realToCanvasMat
|
|
692
|
-
});
|
|
693
|
-
} else if (holeShape === "rotated_pill") {
|
|
694
|
-
drawPill({
|
|
614
|
+
if (cutout.shape === "polygon") {
|
|
615
|
+
if (cutout.points && cutout.points.length >= 3) {
|
|
616
|
+
drawPolygon({
|
|
695
617
|
ctx,
|
|
696
|
-
|
|
697
|
-
width: hole.hole_width ?? 0,
|
|
698
|
-
height: hole.hole_height ?? 0,
|
|
618
|
+
points: cutout.points,
|
|
699
619
|
fill: colorMap.drill,
|
|
700
620
|
realToCanvasMat
|
|
701
621
|
});
|
|
@@ -704,188 +624,213 @@ function drawPcbPlatedHole(params) {
|
|
|
704
624
|
}
|
|
705
625
|
}
|
|
706
626
|
|
|
707
|
-
// lib/drawer/
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
realToCanvasMat
|
|
716
|
-
});
|
|
717
|
-
drawCircle({
|
|
718
|
-
ctx,
|
|
719
|
-
center: { x: via.x, y: via.y },
|
|
720
|
-
radius: via.hole_diameter / 2,
|
|
721
|
-
fill: colorMap.drill,
|
|
722
|
-
realToCanvasMat
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
// lib/drawer/elements/pcb-hole.ts
|
|
727
|
-
function getRotation(hole) {
|
|
728
|
-
if ("ccw_rotation" in hole && typeof hole.ccw_rotation === "number") {
|
|
729
|
-
return hole.ccw_rotation;
|
|
730
|
-
}
|
|
731
|
-
return 0;
|
|
627
|
+
// lib/drawer/shapes/dimension-line.ts
|
|
628
|
+
import { applyToPoint as applyToPoint8 } from "transformation-matrix";
|
|
629
|
+
var TEXT_OFFSET_MULTIPLIER = 1.5;
|
|
630
|
+
var CHARACTER_WIDTH_MULTIPLIER = 0.6;
|
|
631
|
+
var TEXT_INTERSECTION_PADDING_MULTIPLIER = 0.3;
|
|
632
|
+
function normalize(v) {
|
|
633
|
+
const len = Math.hypot(v.x, v.y) || 1;
|
|
634
|
+
return { x: v.x / len, y: v.y / len };
|
|
732
635
|
}
|
|
733
|
-
function
|
|
734
|
-
const {
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
636
|
+
function drawDimensionLine(params) {
|
|
637
|
+
const {
|
|
638
|
+
ctx,
|
|
639
|
+
from,
|
|
640
|
+
to,
|
|
641
|
+
realToCanvasMat,
|
|
642
|
+
color,
|
|
643
|
+
fontSize,
|
|
644
|
+
arrowSize = 1,
|
|
645
|
+
strokeWidth: manualStrokeWidth,
|
|
646
|
+
text,
|
|
647
|
+
textRotation,
|
|
648
|
+
offset
|
|
649
|
+
} = params;
|
|
650
|
+
const direction = normalize({ x: to.x - from.x, y: to.y - from.y });
|
|
651
|
+
const perpendicular = { x: -direction.y, y: direction.x };
|
|
652
|
+
const hasOffsetDirection = offset?.direction && typeof offset.direction.x === "number" && typeof offset.direction.y === "number";
|
|
653
|
+
const normalizedOffsetDirection = hasOffsetDirection ? normalize(offset.direction) : { x: 0, y: 0 };
|
|
654
|
+
const offsetMagnitude = offset?.distance ?? 0;
|
|
655
|
+
const offsetVector = {
|
|
656
|
+
x: normalizedOffsetDirection.x * offsetMagnitude,
|
|
657
|
+
y: normalizedOffsetDirection.y * offsetMagnitude
|
|
658
|
+
};
|
|
659
|
+
const fromOffset = { x: from.x + offsetVector.x, y: from.y + offsetVector.y };
|
|
660
|
+
const toOffset = { x: to.x + offsetVector.x, y: to.y + offsetVector.y };
|
|
661
|
+
const fromBase = {
|
|
662
|
+
x: fromOffset.x + direction.x * arrowSize,
|
|
663
|
+
y: fromOffset.y + direction.y * arrowSize
|
|
664
|
+
};
|
|
665
|
+
const toBase = {
|
|
666
|
+
x: toOffset.x - direction.x * arrowSize,
|
|
667
|
+
y: toOffset.y - direction.y * arrowSize
|
|
668
|
+
};
|
|
669
|
+
const scaleValue = Math.abs(realToCanvasMat.a);
|
|
670
|
+
const strokeWidth = manualStrokeWidth ?? arrowSize / 5;
|
|
671
|
+
const lineColor = color || "rgba(255,255,255,0.5)";
|
|
672
|
+
const extensionDirection = hasOffsetDirection && (Math.abs(normalizedOffsetDirection.x) > Number.EPSILON || Math.abs(normalizedOffsetDirection.y) > Number.EPSILON) ? normalizedOffsetDirection : perpendicular;
|
|
673
|
+
const extensionLength = offsetMagnitude + 0.5;
|
|
674
|
+
const allPoints = [];
|
|
675
|
+
const getExtensionPoints = (anchor) => {
|
|
676
|
+
const endPoint = {
|
|
677
|
+
x: anchor.x + extensionDirection.x * extensionLength,
|
|
678
|
+
y: anchor.y + extensionDirection.y * extensionLength
|
|
679
|
+
};
|
|
680
|
+
const halfWidth = strokeWidth / 2;
|
|
681
|
+
const extPerpendicular = {
|
|
682
|
+
x: -extensionDirection.y,
|
|
683
|
+
y: extensionDirection.x
|
|
684
|
+
};
|
|
685
|
+
return [
|
|
686
|
+
{
|
|
687
|
+
x: anchor.x + extPerpendicular.x * halfWidth,
|
|
688
|
+
y: anchor.y + extPerpendicular.y * halfWidth
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
x: anchor.x - extPerpendicular.x * halfWidth,
|
|
692
|
+
y: anchor.y - extPerpendicular.y * halfWidth
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
x: endPoint.x - extPerpendicular.x * halfWidth,
|
|
696
|
+
y: endPoint.y - extPerpendicular.y * halfWidth
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
x: endPoint.x + extPerpendicular.x * halfWidth,
|
|
700
|
+
y: endPoint.y + extPerpendicular.y * halfWidth
|
|
701
|
+
}
|
|
702
|
+
];
|
|
703
|
+
};
|
|
704
|
+
allPoints.push(fromOffset);
|
|
705
|
+
allPoints.push({
|
|
706
|
+
x: fromBase.x + perpendicular.x * (arrowSize / 2),
|
|
707
|
+
y: fromBase.y + perpendicular.y * (arrowSize / 2)
|
|
708
|
+
});
|
|
709
|
+
allPoints.push({
|
|
710
|
+
x: fromBase.x + perpendicular.x * (strokeWidth / 2),
|
|
711
|
+
y: fromBase.y + perpendicular.y * (strokeWidth / 2)
|
|
712
|
+
});
|
|
713
|
+
allPoints.push({
|
|
714
|
+
x: toBase.x + perpendicular.x * (strokeWidth / 2),
|
|
715
|
+
y: toBase.y + perpendicular.y * (strokeWidth / 2)
|
|
716
|
+
});
|
|
717
|
+
allPoints.push({
|
|
718
|
+
x: toBase.x + perpendicular.x * (arrowSize / 2),
|
|
719
|
+
y: toBase.y + perpendicular.y * (arrowSize / 2)
|
|
720
|
+
});
|
|
721
|
+
allPoints.push(toOffset);
|
|
722
|
+
allPoints.push({
|
|
723
|
+
x: toBase.x - perpendicular.x * (arrowSize / 2),
|
|
724
|
+
y: toBase.y - perpendicular.y * (arrowSize / 2)
|
|
725
|
+
});
|
|
726
|
+
allPoints.push({
|
|
727
|
+
x: toBase.x - perpendicular.x * (strokeWidth / 2),
|
|
728
|
+
y: toBase.y - perpendicular.y * (strokeWidth / 2)
|
|
729
|
+
});
|
|
730
|
+
allPoints.push({
|
|
731
|
+
x: fromBase.x - perpendicular.x * (strokeWidth / 2),
|
|
732
|
+
y: fromBase.y - perpendicular.y * (strokeWidth / 2)
|
|
733
|
+
});
|
|
734
|
+
allPoints.push({
|
|
735
|
+
x: fromBase.x - perpendicular.x * (arrowSize / 2),
|
|
736
|
+
y: fromBase.y - perpendicular.y * (arrowSize / 2)
|
|
737
|
+
});
|
|
738
|
+
allPoints.push(fromOffset);
|
|
739
|
+
const startPoint = allPoints[0];
|
|
740
|
+
const addTick = (anchor) => {
|
|
741
|
+
const pts = getExtensionPoints(anchor);
|
|
742
|
+
allPoints.push(startPoint);
|
|
743
|
+
allPoints.push(pts[0]);
|
|
744
|
+
allPoints.push(...pts);
|
|
745
|
+
allPoints.push(pts[0]);
|
|
746
|
+
allPoints.push(startPoint);
|
|
747
|
+
};
|
|
748
|
+
addTick(from);
|
|
749
|
+
addTick(to);
|
|
750
|
+
drawPolygon({
|
|
751
|
+
ctx,
|
|
752
|
+
points: allPoints,
|
|
753
|
+
fill: lineColor,
|
|
754
|
+
realToCanvasMat
|
|
755
|
+
});
|
|
756
|
+
if (text) {
|
|
757
|
+
const midPoint = {
|
|
758
|
+
x: (from.x + to.x) / 2 + offsetVector.x,
|
|
759
|
+
y: (from.y + to.y) / 2 + offsetVector.y
|
|
760
|
+
};
|
|
761
|
+
const [screenFromX, screenFromY] = applyToPoint8(realToCanvasMat, [
|
|
762
|
+
fromOffset.x,
|
|
763
|
+
fromOffset.y
|
|
764
|
+
]);
|
|
765
|
+
const [screenToX, screenToY] = applyToPoint8(realToCanvasMat, [
|
|
766
|
+
toOffset.x,
|
|
767
|
+
toOffset.y
|
|
768
|
+
]);
|
|
769
|
+
const screenDirection = normalize({
|
|
770
|
+
x: screenToX - screenFromX,
|
|
771
|
+
y: screenToY - screenFromY
|
|
859
772
|
});
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
773
|
+
let textAngle = Math.atan2(screenDirection.y, screenDirection.x) * 180 / Math.PI;
|
|
774
|
+
if (textAngle > 90 || textAngle < -90) {
|
|
775
|
+
textAngle += 180;
|
|
776
|
+
}
|
|
777
|
+
const finalTextAngle = typeof textRotation === "number" && Number.isFinite(textRotation) ? textAngle - textRotation : textAngle;
|
|
778
|
+
let additionalOffset = 0;
|
|
779
|
+
if (text && typeof textRotation === "number" && Number.isFinite(textRotation)) {
|
|
780
|
+
const textWidth = text.length * fontSize * CHARACTER_WIDTH_MULTIPLIER;
|
|
781
|
+
const textHeight = fontSize;
|
|
782
|
+
const rotationRad = textRotation * Math.PI / 180;
|
|
783
|
+
const sinRot = Math.abs(Math.sin(rotationRad));
|
|
784
|
+
const cosRot = Math.abs(Math.cos(rotationRad));
|
|
785
|
+
const halfWidth = textWidth / 2;
|
|
786
|
+
const halfHeight = textHeight / 2;
|
|
787
|
+
const maxExtension = halfWidth * sinRot + halfHeight * cosRot;
|
|
788
|
+
additionalOffset = maxExtension + fontSize * TEXT_INTERSECTION_PADDING_MULTIPLIER;
|
|
789
|
+
}
|
|
790
|
+
const textOffset = arrowSize * TEXT_OFFSET_MULTIPLIER + additionalOffset;
|
|
791
|
+
const textPoint = {
|
|
792
|
+
x: midPoint.x + perpendicular.x * textOffset,
|
|
793
|
+
y: midPoint.y + perpendicular.y * textOffset
|
|
794
|
+
};
|
|
795
|
+
drawText({
|
|
864
796
|
ctx,
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
797
|
+
text,
|
|
798
|
+
x: textPoint.x,
|
|
799
|
+
y: textPoint.y,
|
|
800
|
+
fontSize,
|
|
801
|
+
color: lineColor,
|
|
869
802
|
realToCanvasMat,
|
|
870
|
-
|
|
803
|
+
anchorAlignment: "center",
|
|
804
|
+
rotation: -finalTextAngle
|
|
805
|
+
// drawText expects CCW rotation in degrees
|
|
871
806
|
});
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
if (pad.shape === "polygon") {
|
|
875
|
-
if (pad.points && pad.points.length >= 3) {
|
|
876
|
-
drawPolygon({
|
|
877
|
-
ctx,
|
|
878
|
-
points: pad.points,
|
|
879
|
-
fill: color,
|
|
880
|
-
realToCanvasMat
|
|
881
|
-
});
|
|
882
|
-
}
|
|
883
|
-
return;
|
|
884
807
|
}
|
|
885
808
|
}
|
|
886
809
|
|
|
810
|
+
// lib/drawer/elements/pcb-fabrication-note-dimension.ts
|
|
811
|
+
var DEFAULT_FABRICATION_NOTE_COLOR = "rgba(255,255,255,0.5)";
|
|
812
|
+
function drawPcbFabricationNoteDimension(params) {
|
|
813
|
+
const { ctx, pcbFabricationNoteDimension, realToCanvasMat } = params;
|
|
814
|
+
const color = pcbFabricationNoteDimension.color ?? DEFAULT_FABRICATION_NOTE_COLOR;
|
|
815
|
+
drawDimensionLine({
|
|
816
|
+
ctx,
|
|
817
|
+
from: pcbFabricationNoteDimension.from,
|
|
818
|
+
to: pcbFabricationNoteDimension.to,
|
|
819
|
+
realToCanvasMat,
|
|
820
|
+
color,
|
|
821
|
+
fontSize: pcbFabricationNoteDimension.font_size ?? 1,
|
|
822
|
+
arrowSize: pcbFabricationNoteDimension.arrow_size ?? 1,
|
|
823
|
+
text: pcbFabricationNoteDimension.text,
|
|
824
|
+
textRotation: pcbFabricationNoteDimension.text_ccw_rotation,
|
|
825
|
+
offset: pcbFabricationNoteDimension.offset_distance && pcbFabricationNoteDimension.offset_direction ? {
|
|
826
|
+
distance: pcbFabricationNoteDimension.offset_distance,
|
|
827
|
+
direction: pcbFabricationNoteDimension.offset_direction
|
|
828
|
+
} : void 0
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
|
|
887
832
|
// lib/drawer/shapes/line.ts
|
|
888
|
-
import { applyToPoint as
|
|
833
|
+
import { applyToPoint as applyToPoint9 } from "transformation-matrix";
|
|
889
834
|
function drawLine(params) {
|
|
890
835
|
const {
|
|
891
836
|
ctx,
|
|
@@ -896,8 +841,8 @@ function drawLine(params) {
|
|
|
896
841
|
realToCanvasMat,
|
|
897
842
|
lineCap = "round"
|
|
898
843
|
} = params;
|
|
899
|
-
const [x1, y1] =
|
|
900
|
-
const [x2, y2] =
|
|
844
|
+
const [x1, y1] = applyToPoint9(realToCanvasMat, [start.x, start.y]);
|
|
845
|
+
const [x2, y2] = applyToPoint9(realToCanvasMat, [end.x, end.y]);
|
|
901
846
|
const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
|
|
902
847
|
ctx.beginPath();
|
|
903
848
|
ctx.moveTo(x1, y1);
|
|
@@ -908,355 +853,381 @@ function drawLine(params) {
|
|
|
908
853
|
ctx.stroke();
|
|
909
854
|
}
|
|
910
855
|
|
|
911
|
-
// lib/drawer/elements/pcb-
|
|
912
|
-
function
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
for (let i = 0; i < trace.route.length - 1; i++) {
|
|
921
|
-
const start = trace.route[i];
|
|
922
|
-
const end = trace.route[i + 1];
|
|
856
|
+
// lib/drawer/elements/pcb-fabrication-note-path.ts
|
|
857
|
+
function drawPcbFabricationNotePath(params) {
|
|
858
|
+
const { ctx, path, realToCanvasMat, colorMap } = params;
|
|
859
|
+
const defaultColor = "rgba(255,255,255,0.5)";
|
|
860
|
+
const color = path.color ?? defaultColor;
|
|
861
|
+
if (!path.route || path.route.length < 2) return;
|
|
862
|
+
for (let i = 0; i < path.route.length - 1; i++) {
|
|
863
|
+
const start = path.route[i];
|
|
864
|
+
const end = path.route[i + 1];
|
|
923
865
|
if (!start || !end) continue;
|
|
924
|
-
const layer = "layer" in start ? start.layer : "layer" in end ? end.layer : null;
|
|
925
|
-
if (!layer) continue;
|
|
926
|
-
const color = layerToColor2(layer, colorMap);
|
|
927
|
-
const traceWidth = "width" in start ? start.width : "width" in end ? end.width : 0.1;
|
|
928
866
|
drawLine({
|
|
929
867
|
ctx,
|
|
930
868
|
start: { x: start.x, y: start.y },
|
|
931
869
|
end: { x: end.x, y: end.y },
|
|
932
|
-
strokeWidth:
|
|
870
|
+
strokeWidth: path.stroke_width ?? 0.1,
|
|
933
871
|
stroke: color,
|
|
934
|
-
realToCanvasMat
|
|
935
|
-
lineCap: "round"
|
|
872
|
+
realToCanvasMat
|
|
936
873
|
});
|
|
937
874
|
}
|
|
938
875
|
}
|
|
939
876
|
|
|
940
|
-
// lib/drawer/
|
|
941
|
-
|
|
942
|
-
|
|
877
|
+
// lib/drawer/elements/pcb-fabrication-note-rect.ts
|
|
878
|
+
function drawPcbFabricationNoteRect(params) {
|
|
879
|
+
const { ctx, rect, realToCanvasMat, colorMap } = params;
|
|
880
|
+
const defaultColor = "rgba(255,255,255,0.5)";
|
|
881
|
+
const color = rect.color ?? defaultColor;
|
|
882
|
+
const isFilled = rect.is_filled ?? false;
|
|
883
|
+
const hasStroke = rect.has_stroke ?? true;
|
|
884
|
+
const isStrokeDashed = rect.is_stroke_dashed ?? false;
|
|
885
|
+
drawRect({
|
|
886
|
+
ctx,
|
|
887
|
+
center: rect.center,
|
|
888
|
+
width: rect.width,
|
|
889
|
+
height: rect.height,
|
|
890
|
+
fill: isFilled ? color : void 0,
|
|
891
|
+
stroke: hasStroke ? color : void 0,
|
|
892
|
+
strokeWidth: hasStroke ? rect.stroke_width : void 0,
|
|
893
|
+
borderRadius: rect.corner_radius,
|
|
894
|
+
realToCanvasMat,
|
|
895
|
+
isStrokeDashed
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// lib/drawer/elements/pcb-fabrication-note-text.ts
|
|
900
|
+
var DEFAULT_FABRICATION_NOTE_COLOR2 = "rgba(255,255,255,0.5)";
|
|
901
|
+
function layerToColor2(layer, colorMap) {
|
|
902
|
+
return DEFAULT_FABRICATION_NOTE_COLOR2;
|
|
903
|
+
}
|
|
904
|
+
function drawPcbFabricationNoteText(params) {
|
|
905
|
+
const { ctx, text, realToCanvasMat, colorMap } = params;
|
|
906
|
+
const defaultColor = layerToColor2(text.layer, colorMap);
|
|
907
|
+
const color = text.color ?? defaultColor;
|
|
908
|
+
const fontSize = text.font_size;
|
|
909
|
+
drawText({
|
|
910
|
+
ctx,
|
|
911
|
+
text: text.text,
|
|
912
|
+
x: text.anchor_position.x,
|
|
913
|
+
y: text.anchor_position.y,
|
|
914
|
+
fontSize,
|
|
915
|
+
color,
|
|
916
|
+
realToCanvasMat,
|
|
917
|
+
anchorAlignment: text.anchor_alignment
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// lib/drawer/shapes/oval.ts
|
|
922
|
+
import { applyToPoint as applyToPoint10 } from "transformation-matrix";
|
|
923
|
+
function drawOval(params) {
|
|
943
924
|
const {
|
|
944
925
|
ctx,
|
|
945
|
-
|
|
926
|
+
center,
|
|
927
|
+
radius_x,
|
|
928
|
+
radius_y,
|
|
946
929
|
fill,
|
|
947
930
|
stroke,
|
|
948
|
-
strokeWidth = 1,
|
|
931
|
+
strokeWidth = 0.1,
|
|
949
932
|
realToCanvasMat,
|
|
950
|
-
|
|
933
|
+
rotation = 0
|
|
951
934
|
} = params;
|
|
952
|
-
|
|
935
|
+
const [cx, cy] = applyToPoint10(realToCanvasMat, [center.x, center.y]);
|
|
936
|
+
const scaledRadiusX = radius_x * Math.abs(realToCanvasMat.a);
|
|
937
|
+
const scaledRadiusY = radius_y * Math.abs(realToCanvasMat.a);
|
|
938
|
+
const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
|
|
939
|
+
ctx.save();
|
|
940
|
+
ctx.translate(cx, cy);
|
|
941
|
+
if (rotation !== 0) {
|
|
942
|
+
ctx.rotate(-rotation * (Math.PI / 180));
|
|
943
|
+
}
|
|
953
944
|
ctx.beginPath();
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
if (!firstPoint) return;
|
|
959
|
-
const [firstX, firstY] = firstPoint;
|
|
960
|
-
ctx.moveTo(firstX, firstY);
|
|
961
|
-
for (let i = 1; i < canvasPoints.length; i++) {
|
|
962
|
-
const point = canvasPoints[i];
|
|
963
|
-
if (!point) continue;
|
|
964
|
-
const [x, y] = point;
|
|
965
|
-
ctx.lineTo(x, y);
|
|
945
|
+
ctx.ellipse(0, 0, scaledRadiusX, scaledRadiusY, 0, 0, Math.PI * 2);
|
|
946
|
+
if (fill) {
|
|
947
|
+
ctx.fillStyle = fill;
|
|
948
|
+
ctx.fill();
|
|
966
949
|
}
|
|
967
|
-
if (
|
|
968
|
-
ctx.
|
|
950
|
+
if (stroke) {
|
|
951
|
+
ctx.strokeStyle = stroke;
|
|
952
|
+
ctx.lineWidth = scaledStrokeWidth;
|
|
953
|
+
ctx.stroke();
|
|
954
|
+
}
|
|
955
|
+
ctx.restore();
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// lib/drawer/shapes/pill.ts
|
|
959
|
+
import { applyToPoint as applyToPoint11 } from "transformation-matrix";
|
|
960
|
+
function drawPill(params) {
|
|
961
|
+
const {
|
|
962
|
+
ctx,
|
|
963
|
+
center,
|
|
964
|
+
width,
|
|
965
|
+
height,
|
|
966
|
+
fill,
|
|
967
|
+
realToCanvasMat,
|
|
968
|
+
rotation = 0,
|
|
969
|
+
stroke,
|
|
970
|
+
strokeWidth
|
|
971
|
+
} = params;
|
|
972
|
+
const [cx, cy] = applyToPoint11(realToCanvasMat, [center.x, center.y]);
|
|
973
|
+
const scaledWidth = width * Math.abs(realToCanvasMat.a);
|
|
974
|
+
const scaledHeight = height * Math.abs(realToCanvasMat.a);
|
|
975
|
+
const scaledStrokeWidth = strokeWidth ? strokeWidth * Math.abs(realToCanvasMat.a) : void 0;
|
|
976
|
+
ctx.save();
|
|
977
|
+
ctx.translate(cx, cy);
|
|
978
|
+
if (rotation !== 0) {
|
|
979
|
+
ctx.rotate(-rotation * (Math.PI / 180));
|
|
980
|
+
}
|
|
981
|
+
ctx.beginPath();
|
|
982
|
+
if (scaledWidth > scaledHeight) {
|
|
983
|
+
const radius = scaledHeight / 2;
|
|
984
|
+
const straightLength = scaledWidth - scaledHeight;
|
|
985
|
+
ctx.moveTo(-straightLength / 2, -radius);
|
|
986
|
+
ctx.lineTo(straightLength / 2, -radius);
|
|
987
|
+
ctx.arc(straightLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2);
|
|
988
|
+
ctx.lineTo(-straightLength / 2, radius);
|
|
989
|
+
ctx.arc(-straightLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2);
|
|
990
|
+
} else if (scaledHeight > scaledWidth) {
|
|
991
|
+
const radius = scaledWidth / 2;
|
|
992
|
+
const straightLength = scaledHeight - scaledWidth;
|
|
993
|
+
ctx.moveTo(radius, -straightLength / 2);
|
|
994
|
+
ctx.lineTo(radius, straightLength / 2);
|
|
995
|
+
ctx.arc(0, straightLength / 2, radius, 0, Math.PI);
|
|
996
|
+
ctx.lineTo(-radius, -straightLength / 2);
|
|
997
|
+
ctx.arc(0, -straightLength / 2, radius, Math.PI, 0);
|
|
998
|
+
} else {
|
|
999
|
+
ctx.arc(0, 0, scaledWidth / 2, 0, Math.PI * 2);
|
|
969
1000
|
}
|
|
1001
|
+
ctx.closePath();
|
|
970
1002
|
if (fill) {
|
|
971
1003
|
ctx.fillStyle = fill;
|
|
972
1004
|
ctx.fill();
|
|
973
1005
|
}
|
|
974
|
-
if (stroke) {
|
|
975
|
-
const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
|
|
1006
|
+
if (stroke && scaledStrokeWidth) {
|
|
976
1007
|
ctx.strokeStyle = stroke;
|
|
977
1008
|
ctx.lineWidth = scaledStrokeWidth;
|
|
978
1009
|
ctx.stroke();
|
|
979
1010
|
}
|
|
1011
|
+
ctx.restore();
|
|
980
1012
|
}
|
|
981
1013
|
|
|
982
|
-
// lib/drawer/elements/pcb-
|
|
983
|
-
function
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1014
|
+
// lib/drawer/elements/pcb-hole.ts
|
|
1015
|
+
function getRotation(hole) {
|
|
1016
|
+
if ("ccw_rotation" in hole && typeof hole.ccw_rotation === "number") {
|
|
1017
|
+
return hole.ccw_rotation;
|
|
1018
|
+
}
|
|
1019
|
+
return 0;
|
|
1020
|
+
}
|
|
1021
|
+
function drawPcbHole(params) {
|
|
1022
|
+
const { ctx, hole, realToCanvasMat, colorMap, soldermaskMargin = 0 } = params;
|
|
1023
|
+
if (hole.is_covered_with_solder_mask === true) {
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
const holeInset = soldermaskMargin < 0 ? Math.abs(soldermaskMargin) : 0;
|
|
1027
|
+
if (hole.hole_shape === "circle") {
|
|
1028
|
+
drawCircle({
|
|
988
1029
|
ctx,
|
|
989
|
-
|
|
990
|
-
|
|
1030
|
+
center: { x: hole.x, y: hole.y },
|
|
1031
|
+
radius: hole.hole_diameter / 2 - holeInset,
|
|
1032
|
+
fill: colorMap.drill,
|
|
1033
|
+
realToCanvasMat
|
|
1034
|
+
});
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
if (hole.hole_shape === "square") {
|
|
1038
|
+
const rotation = getRotation(hole);
|
|
1039
|
+
drawRect({
|
|
1040
|
+
ctx,
|
|
1041
|
+
center: { x: hole.x, y: hole.y },
|
|
1042
|
+
width: hole.hole_diameter - holeInset * 2,
|
|
1043
|
+
height: hole.hole_diameter - holeInset * 2,
|
|
1044
|
+
fill: colorMap.drill,
|
|
991
1045
|
realToCanvasMat,
|
|
992
|
-
|
|
1046
|
+
rotation
|
|
993
1047
|
});
|
|
994
|
-
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
if (hole.hole_shape === "oval") {
|
|
1051
|
+
const rotation = getRotation(hole);
|
|
1052
|
+
drawOval({
|
|
995
1053
|
ctx,
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1054
|
+
center: { x: hole.x, y: hole.y },
|
|
1055
|
+
radius_x: hole.hole_width / 2 - holeInset,
|
|
1056
|
+
radius_y: hole.hole_height / 2 - holeInset,
|
|
1057
|
+
fill: colorMap.drill,
|
|
999
1058
|
realToCanvasMat,
|
|
1000
|
-
|
|
1059
|
+
rotation
|
|
1001
1060
|
});
|
|
1002
1061
|
return;
|
|
1003
1062
|
}
|
|
1004
|
-
if (
|
|
1063
|
+
if (hole.hole_shape === "rect") {
|
|
1064
|
+
const rotation = getRotation(hole);
|
|
1005
1065
|
drawRect({
|
|
1006
1066
|
ctx,
|
|
1007
|
-
center,
|
|
1008
|
-
width,
|
|
1009
|
-
height,
|
|
1010
|
-
fill: colorMap.
|
|
1011
|
-
realToCanvasMat
|
|
1067
|
+
center: { x: hole.x, y: hole.y },
|
|
1068
|
+
width: hole.hole_width - holeInset * 2,
|
|
1069
|
+
height: hole.hole_height - holeInset * 2,
|
|
1070
|
+
fill: colorMap.drill,
|
|
1071
|
+
realToCanvasMat,
|
|
1072
|
+
rotation
|
|
1012
1073
|
});
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
{ x: center.x + halfWidth, y: center.y + halfHeight },
|
|
1019
|
-
{ x: center.x - halfWidth, y: center.y + halfHeight }
|
|
1020
|
-
];
|
|
1021
|
-
drawPath({
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
if (hole.hole_shape === "pill" || hole.hole_shape === "rotated_pill") {
|
|
1077
|
+
const rotation = getRotation(hole);
|
|
1078
|
+
drawPill({
|
|
1022
1079
|
ctx,
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1080
|
+
center: { x: hole.x, y: hole.y },
|
|
1081
|
+
width: hole.hole_width - holeInset * 2,
|
|
1082
|
+
height: hole.hole_height - holeInset * 2,
|
|
1083
|
+
fill: colorMap.drill,
|
|
1026
1084
|
realToCanvasMat,
|
|
1027
|
-
|
|
1085
|
+
rotation
|
|
1028
1086
|
});
|
|
1087
|
+
return;
|
|
1029
1088
|
}
|
|
1030
1089
|
}
|
|
1031
1090
|
|
|
1032
|
-
// lib/drawer/elements/pcb-
|
|
1033
|
-
import { applyToPoint as
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
// lib/drawer/shapes/text/getAlphabetLayout.ts
|
|
1040
|
-
var GLYPH_WIDTH_RATIO = 0.62;
|
|
1041
|
-
var LETTER_SPACING_RATIO = 0.3;
|
|
1042
|
-
var SPACE_WIDTH_RATIO = 1;
|
|
1043
|
-
var STROKE_WIDTH_RATIO = 0.13;
|
|
1044
|
-
function getAlphabetLayout(text, fontSize) {
|
|
1045
|
-
const glyphWidth = fontSize * GLYPH_WIDTH_RATIO;
|
|
1046
|
-
const letterSpacing = glyphWidth * LETTER_SPACING_RATIO;
|
|
1047
|
-
const spaceWidth = glyphWidth * SPACE_WIDTH_RATIO;
|
|
1048
|
-
const characters = Array.from(text);
|
|
1049
|
-
let width = 0;
|
|
1050
|
-
characters.forEach((char, index) => {
|
|
1051
|
-
const advance = char === " " ? spaceWidth : glyphWidth;
|
|
1052
|
-
width += advance;
|
|
1053
|
-
if (index < characters.length - 1) width += letterSpacing;
|
|
1054
|
-
});
|
|
1055
|
-
const strokeWidth = Math.max(fontSize * STROKE_WIDTH_RATIO, 0.35);
|
|
1056
|
-
return {
|
|
1057
|
-
width,
|
|
1058
|
-
height: fontSize,
|
|
1059
|
-
glyphWidth,
|
|
1060
|
-
letterSpacing,
|
|
1061
|
-
spaceWidth,
|
|
1062
|
-
strokeWidth
|
|
1063
|
-
};
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
// lib/drawer/shapes/text/getTextStartPosition.ts
|
|
1067
|
-
function getTextStartPosition(alignment, layout) {
|
|
1068
|
-
const totalWidth = layout.width + layout.strokeWidth;
|
|
1069
|
-
const totalHeight = layout.height + layout.strokeWidth;
|
|
1070
|
-
let x = 0;
|
|
1071
|
-
let y = 0;
|
|
1072
|
-
if (alignment === "center") {
|
|
1073
|
-
x = -totalWidth / 2;
|
|
1074
|
-
} else if (alignment === "top_left" || alignment === "bottom_left" || alignment === "center_left") {
|
|
1075
|
-
x = 0;
|
|
1076
|
-
} else if (alignment === "top_right" || alignment === "bottom_right" || alignment === "center_right") {
|
|
1077
|
-
x = -totalWidth;
|
|
1078
|
-
}
|
|
1079
|
-
if (alignment === "center") {
|
|
1080
|
-
y = -totalHeight / 2;
|
|
1081
|
-
} else if (alignment === "top_left" || alignment === "top_right" || alignment === "top_center") {
|
|
1082
|
-
y = 0;
|
|
1083
|
-
} else if (alignment === "bottom_left" || alignment === "bottom_right" || alignment === "bottom_center") {
|
|
1084
|
-
y = -totalHeight;
|
|
1085
|
-
} else {
|
|
1086
|
-
y = 0;
|
|
1087
|
-
}
|
|
1088
|
-
return { x, y };
|
|
1091
|
+
// lib/drawer/elements/pcb-keepout.ts
|
|
1092
|
+
import { applyToPoint as applyToPoint12 } from "transformation-matrix";
|
|
1093
|
+
function hexToRgba(hex, alpha) {
|
|
1094
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
1095
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
1096
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
1097
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
1089
1098
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
const
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1099
|
+
function drawPcbKeepout(params) {
|
|
1100
|
+
const { ctx, keepout, realToCanvasMat, colorMap } = params;
|
|
1101
|
+
const strokeColor = colorMap.keepout;
|
|
1102
|
+
const fillColor = hexToRgba(colorMap.keepout, 0.2);
|
|
1103
|
+
const hatchSpacing = 1;
|
|
1104
|
+
if (keepout.shape === "rect") {
|
|
1105
|
+
const [cx, cy] = applyToPoint12(realToCanvasMat, [
|
|
1106
|
+
keepout.center.x,
|
|
1107
|
+
keepout.center.y
|
|
1108
|
+
]);
|
|
1109
|
+
const scaledWidth = keepout.width * Math.abs(realToCanvasMat.a);
|
|
1110
|
+
const scaledHeight = keepout.height * Math.abs(realToCanvasMat.a);
|
|
1111
|
+
const rotation = keepout.rotation ?? 0;
|
|
1112
|
+
ctx.save();
|
|
1113
|
+
ctx.translate(cx, cy);
|
|
1114
|
+
if (rotation !== 0) {
|
|
1115
|
+
ctx.rotate(-rotation * (Math.PI / 180));
|
|
1116
|
+
}
|
|
1117
|
+
ctx.beginPath();
|
|
1118
|
+
ctx.rect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight);
|
|
1119
|
+
ctx.fillStyle = fillColor;
|
|
1120
|
+
ctx.fill();
|
|
1121
|
+
ctx.beginPath();
|
|
1122
|
+
ctx.rect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight);
|
|
1123
|
+
ctx.clip();
|
|
1124
|
+
const scaledSpacing = hatchSpacing * Math.abs(realToCanvasMat.a);
|
|
1125
|
+
const diagonal = Math.sqrt(
|
|
1126
|
+
scaledWidth * scaledWidth + scaledHeight * scaledHeight
|
|
1127
|
+
);
|
|
1128
|
+
const halfWidth = scaledWidth / 2;
|
|
1129
|
+
const halfHeight = scaledHeight / 2;
|
|
1130
|
+
ctx.strokeStyle = strokeColor;
|
|
1131
|
+
ctx.lineWidth = 0.15 * Math.abs(realToCanvasMat.a);
|
|
1132
|
+
ctx.setLineDash([]);
|
|
1133
|
+
for (let offset = -diagonal; offset < diagonal * 2; offset += scaledSpacing) {
|
|
1104
1134
|
ctx.beginPath();
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
ctx.lineTo(x2, y2);
|
|
1112
|
-
}
|
|
1135
|
+
const startX = -halfWidth + offset;
|
|
1136
|
+
const startY = -halfHeight;
|
|
1137
|
+
const endX = -halfWidth + offset + diagonal;
|
|
1138
|
+
const endY = -halfHeight + diagonal;
|
|
1139
|
+
ctx.moveTo(startX, startY);
|
|
1140
|
+
ctx.lineTo(endX, endY);
|
|
1113
1141
|
ctx.stroke();
|
|
1114
1142
|
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
cursor += letterSpacing;
|
|
1118
|
-
}
|
|
1119
|
-
});
|
|
1120
|
-
}
|
|
1121
|
-
function drawText(params) {
|
|
1122
|
-
const {
|
|
1123
|
-
ctx,
|
|
1124
|
-
text,
|
|
1125
|
-
x,
|
|
1126
|
-
y,
|
|
1127
|
-
fontSize,
|
|
1128
|
-
color,
|
|
1129
|
-
realToCanvasMat,
|
|
1130
|
-
anchorAlignment,
|
|
1131
|
-
rotation = 0
|
|
1132
|
-
} = params;
|
|
1133
|
-
if (!text) return;
|
|
1134
|
-
const [canvasX, canvasY] = applyToPoint9(realToCanvasMat, [x, y]);
|
|
1135
|
-
const scale2 = Math.abs(realToCanvasMat.a);
|
|
1136
|
-
const scaledFontSize = fontSize * scale2;
|
|
1137
|
-
const layout = getAlphabetLayout(text, scaledFontSize);
|
|
1138
|
-
const startPos = getTextStartPosition(anchorAlignment, layout);
|
|
1139
|
-
ctx.save();
|
|
1140
|
-
ctx.translate(canvasX, canvasY);
|
|
1141
|
-
if (rotation !== 0) {
|
|
1142
|
-
ctx.rotate(-rotation * (Math.PI / 180));
|
|
1143
|
-
}
|
|
1144
|
-
ctx.lineWidth = layout.strokeWidth;
|
|
1145
|
-
ctx.lineCap = "round";
|
|
1146
|
-
ctx.lineJoin = "round";
|
|
1147
|
-
ctx.strokeStyle = color;
|
|
1148
|
-
strokeAlphabetText({
|
|
1149
|
-
ctx,
|
|
1150
|
-
text,
|
|
1151
|
-
fontSize: scaledFontSize,
|
|
1152
|
-
startX: startPos.x,
|
|
1153
|
-
startY: startPos.y
|
|
1154
|
-
});
|
|
1155
|
-
ctx.restore();
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
// lib/drawer/elements/pcb-silkscreen-text.ts
|
|
1159
|
-
function layerToSilkscreenColor(layer, colorMap) {
|
|
1160
|
-
return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
|
|
1161
|
-
}
|
|
1162
|
-
function mapAnchorAlignment(alignment) {
|
|
1163
|
-
if (!alignment) return "center";
|
|
1164
|
-
return alignment;
|
|
1165
|
-
}
|
|
1166
|
-
function drawPcbSilkscreenText(params) {
|
|
1167
|
-
const { ctx, text, realToCanvasMat, colorMap } = params;
|
|
1168
|
-
const content = text.text ?? "";
|
|
1169
|
-
if (!content) return;
|
|
1170
|
-
const color = layerToSilkscreenColor(text.layer, colorMap);
|
|
1171
|
-
const [x, y] = applyToPoint10(realToCanvasMat, [
|
|
1172
|
-
text.anchor_position.x,
|
|
1173
|
-
text.anchor_position.y
|
|
1174
|
-
]);
|
|
1175
|
-
const scale2 = Math.abs(realToCanvasMat.a);
|
|
1176
|
-
const fontSize = (text.font_size ?? 1) * scale2;
|
|
1177
|
-
const rotation = text.ccw_rotation ?? 0;
|
|
1178
|
-
const layout = getAlphabetLayout(content, fontSize);
|
|
1179
|
-
const alignment = mapAnchorAlignment(text.anchor_alignment);
|
|
1180
|
-
const startPos = getTextStartPosition(alignment, layout);
|
|
1181
|
-
ctx.save();
|
|
1182
|
-
ctx.translate(x, y);
|
|
1183
|
-
if (rotation !== 0) {
|
|
1184
|
-
ctx.rotate(-rotation * (Math.PI / 180));
|
|
1143
|
+
ctx.restore();
|
|
1144
|
+
return;
|
|
1185
1145
|
}
|
|
1186
|
-
if (
|
|
1187
|
-
|
|
1146
|
+
if (keepout.shape === "circle") {
|
|
1147
|
+
const [cx, cy] = applyToPoint12(realToCanvasMat, [
|
|
1148
|
+
keepout.center.x,
|
|
1149
|
+
keepout.center.y
|
|
1150
|
+
]);
|
|
1151
|
+
const scaledRadius = keepout.radius * Math.abs(realToCanvasMat.a);
|
|
1152
|
+
const scaledSpacing = hatchSpacing * Math.abs(realToCanvasMat.a);
|
|
1153
|
+
ctx.save();
|
|
1154
|
+
ctx.translate(cx, cy);
|
|
1155
|
+
ctx.beginPath();
|
|
1156
|
+
ctx.arc(0, 0, scaledRadius, 0, Math.PI * 2);
|
|
1157
|
+
ctx.fillStyle = fillColor;
|
|
1158
|
+
ctx.fill();
|
|
1159
|
+
ctx.beginPath();
|
|
1160
|
+
ctx.arc(0, 0, scaledRadius, 0, Math.PI * 2);
|
|
1161
|
+
ctx.clip();
|
|
1162
|
+
const diagonal = scaledRadius * 2;
|
|
1163
|
+
ctx.strokeStyle = strokeColor;
|
|
1164
|
+
ctx.lineWidth = 0.15 * Math.abs(realToCanvasMat.a);
|
|
1165
|
+
ctx.setLineDash([]);
|
|
1166
|
+
for (let offset = -diagonal; offset < diagonal * 2; offset += scaledSpacing) {
|
|
1167
|
+
ctx.beginPath();
|
|
1168
|
+
ctx.moveTo(offset - diagonal, -diagonal);
|
|
1169
|
+
ctx.lineTo(offset + diagonal, diagonal);
|
|
1170
|
+
ctx.stroke();
|
|
1171
|
+
}
|
|
1172
|
+
ctx.restore();
|
|
1173
|
+
return;
|
|
1188
1174
|
}
|
|
1189
|
-
ctx.lineWidth = layout.strokeWidth;
|
|
1190
|
-
ctx.lineCap = "round";
|
|
1191
|
-
ctx.lineJoin = "round";
|
|
1192
|
-
ctx.strokeStyle = color;
|
|
1193
|
-
strokeAlphabetText({
|
|
1194
|
-
ctx,
|
|
1195
|
-
text: content,
|
|
1196
|
-
fontSize,
|
|
1197
|
-
startX: startPos.x,
|
|
1198
|
-
startY: startPos.y
|
|
1199
|
-
});
|
|
1200
|
-
ctx.restore();
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
// lib/drawer/elements/pcb-silkscreen-rect.ts
|
|
1204
|
-
function layerToSilkscreenColor2(layer, colorMap) {
|
|
1205
|
-
return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
|
|
1206
|
-
}
|
|
1207
|
-
function drawPcbSilkscreenRect(params) {
|
|
1208
|
-
const { ctx, rect, realToCanvasMat, colorMap } = params;
|
|
1209
|
-
const color = layerToSilkscreenColor2(rect.layer, colorMap);
|
|
1210
|
-
drawRect({
|
|
1211
|
-
ctx,
|
|
1212
|
-
center: rect.center,
|
|
1213
|
-
width: rect.width,
|
|
1214
|
-
height: rect.height,
|
|
1215
|
-
fill: color,
|
|
1216
|
-
realToCanvasMat
|
|
1217
|
-
});
|
|
1218
1175
|
}
|
|
1219
1176
|
|
|
1220
|
-
// lib/drawer/elements/pcb-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
const color = layerToSilkscreenColor3(circle.layer, colorMap);
|
|
1227
|
-
drawCircle({
|
|
1177
|
+
// lib/drawer/elements/pcb-note-dimension.ts
|
|
1178
|
+
var DEFAULT_NOTE_COLOR = "rgba(255,255,255,0.5)";
|
|
1179
|
+
function drawPcbNoteDimension(params) {
|
|
1180
|
+
const { ctx, pcbNoteDimension, realToCanvasMat } = params;
|
|
1181
|
+
const color = pcbNoteDimension.color ?? DEFAULT_NOTE_COLOR;
|
|
1182
|
+
drawDimensionLine({
|
|
1228
1183
|
ctx,
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1184
|
+
from: pcbNoteDimension.from,
|
|
1185
|
+
to: pcbNoteDimension.to,
|
|
1186
|
+
realToCanvasMat,
|
|
1187
|
+
color,
|
|
1188
|
+
fontSize: pcbNoteDimension.font_size,
|
|
1189
|
+
arrowSize: pcbNoteDimension.arrow_size,
|
|
1190
|
+
text: pcbNoteDimension.text,
|
|
1191
|
+
textRotation: pcbNoteDimension.text_ccw_rotation,
|
|
1192
|
+
offset: pcbNoteDimension.offset_distance && pcbNoteDimension.offset_direction ? {
|
|
1193
|
+
distance: pcbNoteDimension.offset_distance,
|
|
1194
|
+
direction: pcbNoteDimension.offset_direction
|
|
1195
|
+
} : void 0
|
|
1233
1196
|
});
|
|
1234
1197
|
}
|
|
1235
1198
|
|
|
1236
|
-
// lib/drawer/elements/pcb-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
}
|
|
1240
|
-
function drawPcbSilkscreenLine(params) {
|
|
1199
|
+
// lib/drawer/elements/pcb-note-line.ts
|
|
1200
|
+
import { applyToPoint as applyToPoint13 } from "transformation-matrix";
|
|
1201
|
+
function drawPcbNoteLine(params) {
|
|
1241
1202
|
const { ctx, line, realToCanvasMat, colorMap } = params;
|
|
1242
|
-
const
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1203
|
+
const defaultColor = "rgb(89, 148, 220)";
|
|
1204
|
+
const color = line.color ?? defaultColor;
|
|
1205
|
+
const strokeWidth = line.stroke_width ?? 0.1;
|
|
1206
|
+
const isDashed = line.is_dashed ?? false;
|
|
1207
|
+
const [x1, y1] = applyToPoint13(realToCanvasMat, [line.x1, line.y1]);
|
|
1208
|
+
const [x2, y2] = applyToPoint13(realToCanvasMat, [line.x2, line.y2]);
|
|
1209
|
+
const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
|
|
1210
|
+
ctx.save();
|
|
1211
|
+
if (isDashed) {
|
|
1212
|
+
ctx.setLineDash([scaledStrokeWidth * 2, scaledStrokeWidth * 2]);
|
|
1213
|
+
} else {
|
|
1214
|
+
ctx.setLineDash([]);
|
|
1215
|
+
}
|
|
1216
|
+
ctx.beginPath();
|
|
1217
|
+
ctx.moveTo(x1, y1);
|
|
1218
|
+
ctx.lineTo(x2, y2);
|
|
1219
|
+
ctx.lineWidth = scaledStrokeWidth;
|
|
1220
|
+
ctx.strokeStyle = color;
|
|
1221
|
+
ctx.lineCap = "round";
|
|
1222
|
+
ctx.stroke();
|
|
1223
|
+
ctx.restore();
|
|
1251
1224
|
}
|
|
1252
1225
|
|
|
1253
|
-
// lib/drawer/elements/pcb-
|
|
1254
|
-
function
|
|
1255
|
-
return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
|
|
1256
|
-
}
|
|
1257
|
-
function drawPcbSilkscreenPath(params) {
|
|
1226
|
+
// lib/drawer/elements/pcb-note-path.ts
|
|
1227
|
+
function drawPcbNotePath(params) {
|
|
1258
1228
|
const { ctx, path, realToCanvasMat, colorMap } = params;
|
|
1259
|
-
const
|
|
1229
|
+
const defaultColor = "rgb(89, 148, 220)";
|
|
1230
|
+
const color = path.color ?? defaultColor;
|
|
1260
1231
|
if (!path.route || path.route.length < 2) return;
|
|
1261
1232
|
for (let i = 0; i < path.route.length - 1; i++) {
|
|
1262
1233
|
const start = path.route[i];
|
|
@@ -1273,515 +1244,560 @@ function drawPcbSilkscreenPath(params) {
|
|
|
1273
1244
|
}
|
|
1274
1245
|
}
|
|
1275
1246
|
|
|
1276
|
-
// lib/drawer/elements/pcb-
|
|
1277
|
-
function
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
const
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1247
|
+
// lib/drawer/elements/pcb-note-rect.ts
|
|
1248
|
+
function drawPcbNoteRect(params) {
|
|
1249
|
+
const { ctx, rect, realToCanvasMat, colorMap } = params;
|
|
1250
|
+
const defaultColor = "rgb(89, 148, 220)";
|
|
1251
|
+
const color = rect.color ?? defaultColor;
|
|
1252
|
+
const isFilled = rect.is_filled ?? false;
|
|
1253
|
+
const hasStroke = rect.has_stroke ?? true;
|
|
1254
|
+
const isStrokeDashed = rect.is_stroke_dashed ?? false;
|
|
1255
|
+
drawRect({
|
|
1284
1256
|
ctx,
|
|
1285
|
-
center:
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1257
|
+
center: rect.center,
|
|
1258
|
+
width: rect.width,
|
|
1259
|
+
height: rect.height,
|
|
1260
|
+
fill: isFilled ? color : void 0,
|
|
1261
|
+
stroke: hasStroke ? color : void 0,
|
|
1262
|
+
strokeWidth: hasStroke ? rect.stroke_width : void 0,
|
|
1263
|
+
borderRadius: rect.corner_radius,
|
|
1290
1264
|
realToCanvasMat,
|
|
1291
|
-
|
|
1265
|
+
isStrokeDashed
|
|
1292
1266
|
});
|
|
1293
1267
|
}
|
|
1294
1268
|
|
|
1295
|
-
// lib/drawer/elements/pcb-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
const
|
|
1301
|
-
const
|
|
1302
|
-
|
|
1303
|
-
drawPill({
|
|
1269
|
+
// lib/drawer/elements/pcb-note-text.ts
|
|
1270
|
+
var DEFAULT_NOTE_TEXT_COLOR = "rgb(89, 148, 220)";
|
|
1271
|
+
function drawPcbNoteText(params) {
|
|
1272
|
+
const { ctx, text, realToCanvasMat, colorMap } = params;
|
|
1273
|
+
const defaultColor = DEFAULT_NOTE_TEXT_COLOR;
|
|
1274
|
+
const color = text.color ?? defaultColor;
|
|
1275
|
+
const fontSize = text.font_size ?? 1;
|
|
1276
|
+
drawText({
|
|
1304
1277
|
ctx,
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
realToCanvasMat
|
|
1278
|
+
text: text.text ?? "",
|
|
1279
|
+
x: text.anchor_position.x,
|
|
1280
|
+
y: text.anchor_position.y,
|
|
1281
|
+
fontSize,
|
|
1282
|
+
color,
|
|
1283
|
+
realToCanvasMat,
|
|
1284
|
+
anchorAlignment: text.anchor_alignment ?? "center"
|
|
1311
1285
|
});
|
|
1312
1286
|
}
|
|
1313
1287
|
|
|
1314
|
-
// lib/drawer/elements/
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1288
|
+
// lib/drawer/elements/soldermask-margin.ts
|
|
1289
|
+
import { applyToPoint as applyToPoint14 } from "transformation-matrix";
|
|
1290
|
+
function drawSoldermaskRingForRect(ctx, center, width, height, margin, borderRadius, rotation, realToCanvasMat, soldermaskColor, padColor, asymmetricMargins) {
|
|
1291
|
+
const [cx, cy] = applyToPoint14(realToCanvasMat, [center.x, center.y]);
|
|
1292
|
+
const scaledWidth = width * Math.abs(realToCanvasMat.a);
|
|
1293
|
+
const scaledHeight = height * Math.abs(realToCanvasMat.a);
|
|
1294
|
+
const scaledRadius = borderRadius * Math.abs(realToCanvasMat.a);
|
|
1295
|
+
const ml = asymmetricMargins?.left ?? margin;
|
|
1296
|
+
const mr = asymmetricMargins?.right ?? margin;
|
|
1297
|
+
const mt = asymmetricMargins?.top ?? margin;
|
|
1298
|
+
const mb = asymmetricMargins?.bottom ?? margin;
|
|
1299
|
+
const scaledThicknessL = Math.max(0, -ml) * Math.abs(realToCanvasMat.a);
|
|
1300
|
+
const scaledThicknessR = Math.max(0, -mr) * Math.abs(realToCanvasMat.a);
|
|
1301
|
+
const scaledThicknessT = Math.max(0, -mt) * Math.abs(realToCanvasMat.a);
|
|
1302
|
+
const scaledThicknessB = Math.max(0, -mb) * Math.abs(realToCanvasMat.a);
|
|
1303
|
+
ctx.save();
|
|
1304
|
+
ctx.translate(cx, cy);
|
|
1305
|
+
if (rotation !== 0) {
|
|
1306
|
+
ctx.rotate(-rotation * (Math.PI / 180));
|
|
1329
1307
|
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
center: cutout.center,
|
|
1334
|
-
radius: cutout.radius,
|
|
1335
|
-
fill: colorMap.drill,
|
|
1336
|
-
realToCanvasMat
|
|
1337
|
-
});
|
|
1338
|
-
return;
|
|
1308
|
+
const prevCompositeOp = ctx.globalCompositeOperation;
|
|
1309
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
1310
|
+
ctx.globalCompositeOperation = "source-atop";
|
|
1339
1311
|
}
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1312
|
+
const outerWidth = scaledWidth;
|
|
1313
|
+
const outerHeight = scaledHeight;
|
|
1314
|
+
const outerRadius = scaledRadius;
|
|
1315
|
+
ctx.beginPath();
|
|
1316
|
+
if (outerRadius > 0) {
|
|
1317
|
+
const x = -outerWidth / 2;
|
|
1318
|
+
const y = -outerHeight / 2;
|
|
1319
|
+
const r = Math.min(outerRadius, outerWidth / 2, outerHeight / 2);
|
|
1320
|
+
ctx.moveTo(x + r, y);
|
|
1321
|
+
ctx.lineTo(x + outerWidth - r, y);
|
|
1322
|
+
ctx.arcTo(x + outerWidth, y, x + outerWidth, y + r, r);
|
|
1323
|
+
ctx.lineTo(x + outerWidth, y + outerHeight - r);
|
|
1324
|
+
ctx.arcTo(
|
|
1325
|
+
x + outerWidth,
|
|
1326
|
+
y + outerHeight,
|
|
1327
|
+
x + outerWidth - r,
|
|
1328
|
+
y + outerHeight,
|
|
1329
|
+
r
|
|
1330
|
+
);
|
|
1331
|
+
ctx.lineTo(x + r, y + outerHeight);
|
|
1332
|
+
ctx.arcTo(x, y + outerHeight, x, y + outerHeight - r, r);
|
|
1333
|
+
ctx.lineTo(x, y + r);
|
|
1334
|
+
ctx.arcTo(x, y, x + r, y, r);
|
|
1335
|
+
} else {
|
|
1336
|
+
ctx.rect(-outerWidth / 2, -outerHeight / 2, outerWidth, outerHeight);
|
|
1337
|
+
}
|
|
1338
|
+
ctx.fillStyle = soldermaskColor;
|
|
1339
|
+
ctx.fill();
|
|
1340
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
1341
|
+
ctx.globalCompositeOperation = prevCompositeOp || "source-over";
|
|
1342
|
+
}
|
|
1343
|
+
const innerWidth = Math.max(
|
|
1344
|
+
0,
|
|
1345
|
+
scaledWidth - (scaledThicknessL + scaledThicknessR)
|
|
1346
|
+
);
|
|
1347
|
+
const innerHeight = Math.max(
|
|
1348
|
+
0,
|
|
1349
|
+
scaledHeight - (scaledThicknessT + scaledThicknessB)
|
|
1350
|
+
);
|
|
1351
|
+
const innerRadius = Math.max(
|
|
1352
|
+
0,
|
|
1353
|
+
scaledRadius - (scaledThicknessL + scaledThicknessR + scaledThicknessT + scaledThicknessB) / 4
|
|
1354
|
+
);
|
|
1355
|
+
if (innerWidth > 0 && innerHeight > 0) {
|
|
1356
|
+
ctx.beginPath();
|
|
1357
|
+
const x = -scaledWidth / 2 + scaledThicknessL;
|
|
1358
|
+
const y = -scaledHeight / 2 + scaledThicknessT;
|
|
1359
|
+
if (innerRadius > 0) {
|
|
1360
|
+
const r = Math.min(innerRadius, innerWidth / 2, innerHeight / 2);
|
|
1361
|
+
ctx.moveTo(x + r, y);
|
|
1362
|
+
ctx.lineTo(x + innerWidth - r, y);
|
|
1363
|
+
ctx.arcTo(x + innerWidth, y, x + innerWidth, y + r, r);
|
|
1364
|
+
ctx.lineTo(x + innerWidth, y + innerHeight - r);
|
|
1365
|
+
ctx.arcTo(
|
|
1366
|
+
x + innerWidth,
|
|
1367
|
+
y + innerHeight,
|
|
1368
|
+
x + innerWidth - r,
|
|
1369
|
+
y + innerHeight,
|
|
1370
|
+
r
|
|
1371
|
+
);
|
|
1372
|
+
ctx.lineTo(x + r, y + innerHeight);
|
|
1373
|
+
ctx.arcTo(x, y + innerHeight, x, y + innerHeight - r, r);
|
|
1374
|
+
ctx.lineTo(x, y + r);
|
|
1375
|
+
ctx.arcTo(x, y, x + r, y, r);
|
|
1376
|
+
} else {
|
|
1377
|
+
ctx.rect(x, y, innerWidth, innerHeight);
|
|
1348
1378
|
}
|
|
1349
|
-
|
|
1379
|
+
ctx.fillStyle = padColor;
|
|
1380
|
+
ctx.fill();
|
|
1350
1381
|
}
|
|
1382
|
+
ctx.restore();
|
|
1351
1383
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
const
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
]);
|
|
1371
|
-
const scaledWidth = keepout.width * Math.abs(realToCanvasMat.a);
|
|
1372
|
-
const scaledHeight = keepout.height * Math.abs(realToCanvasMat.a);
|
|
1373
|
-
const rotation = keepout.rotation ?? 0;
|
|
1374
|
-
ctx.save();
|
|
1375
|
-
ctx.translate(cx, cy);
|
|
1376
|
-
if (rotation !== 0) {
|
|
1377
|
-
ctx.rotate(-rotation * (Math.PI / 180));
|
|
1378
|
-
}
|
|
1384
|
+
function drawSoldermaskRingForCircle(ctx, center, radius, margin, realToCanvasMat, soldermaskColor, padColor) {
|
|
1385
|
+
const [cx, cy] = applyToPoint14(realToCanvasMat, [center.x, center.y]);
|
|
1386
|
+
const scaledRadius = radius * Math.abs(realToCanvasMat.a);
|
|
1387
|
+
const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
|
|
1388
|
+
ctx.save();
|
|
1389
|
+
const prevCompositeOp = ctx.globalCompositeOperation;
|
|
1390
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
1391
|
+
ctx.globalCompositeOperation = "source-atop";
|
|
1392
|
+
}
|
|
1393
|
+
ctx.beginPath();
|
|
1394
|
+
ctx.arc(cx, cy, scaledRadius, 0, Math.PI * 2);
|
|
1395
|
+
ctx.fillStyle = soldermaskColor;
|
|
1396
|
+
ctx.fill();
|
|
1397
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
1398
|
+
ctx.globalCompositeOperation = prevCompositeOp || "source-over";
|
|
1399
|
+
}
|
|
1400
|
+
const innerRadius = Math.max(0, scaledRadius - scaledMargin);
|
|
1401
|
+
if (innerRadius > 0) {
|
|
1379
1402
|
ctx.beginPath();
|
|
1380
|
-
ctx.
|
|
1381
|
-
ctx.fillStyle =
|
|
1403
|
+
ctx.arc(cx, cy, innerRadius, 0, Math.PI * 2);
|
|
1404
|
+
ctx.fillStyle = padColor;
|
|
1382
1405
|
ctx.fill();
|
|
1406
|
+
}
|
|
1407
|
+
ctx.restore();
|
|
1408
|
+
}
|
|
1409
|
+
function drawSoldermaskRingForPill(ctx, center, width, height, margin, rotation, realToCanvasMat, soldermaskColor, padColor) {
|
|
1410
|
+
const [cx, cy] = applyToPoint14(realToCanvasMat, [center.x, center.y]);
|
|
1411
|
+
const scaledWidth = width * Math.abs(realToCanvasMat.a);
|
|
1412
|
+
const scaledHeight = height * Math.abs(realToCanvasMat.a);
|
|
1413
|
+
const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
|
|
1414
|
+
ctx.save();
|
|
1415
|
+
ctx.translate(cx, cy);
|
|
1416
|
+
if (rotation !== 0) {
|
|
1417
|
+
ctx.rotate(-rotation * (Math.PI / 180));
|
|
1418
|
+
}
|
|
1419
|
+
const prevCompositeOp = ctx.globalCompositeOperation;
|
|
1420
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
1421
|
+
ctx.globalCompositeOperation = "source-atop";
|
|
1422
|
+
}
|
|
1423
|
+
const outerWidth = scaledWidth;
|
|
1424
|
+
const outerHeight = scaledHeight;
|
|
1425
|
+
ctx.beginPath();
|
|
1426
|
+
if (outerWidth > outerHeight) {
|
|
1427
|
+
const radius = outerHeight / 2;
|
|
1428
|
+
const straightLength = outerWidth - outerHeight;
|
|
1429
|
+
ctx.moveTo(-straightLength / 2, -radius);
|
|
1430
|
+
ctx.lineTo(straightLength / 2, -radius);
|
|
1431
|
+
ctx.arc(straightLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2);
|
|
1432
|
+
ctx.lineTo(-straightLength / 2, radius);
|
|
1433
|
+
ctx.arc(-straightLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2);
|
|
1434
|
+
} else if (outerHeight > outerWidth) {
|
|
1435
|
+
const radius = outerWidth / 2;
|
|
1436
|
+
const straightLength = outerHeight - outerWidth;
|
|
1437
|
+
ctx.moveTo(radius, -straightLength / 2);
|
|
1438
|
+
ctx.lineTo(radius, straightLength / 2);
|
|
1439
|
+
ctx.arc(0, straightLength / 2, radius, 0, Math.PI);
|
|
1440
|
+
ctx.lineTo(-radius, -straightLength / 2);
|
|
1441
|
+
ctx.arc(0, -straightLength / 2, radius, Math.PI, 0);
|
|
1442
|
+
} else {
|
|
1443
|
+
ctx.arc(0, 0, outerWidth / 2, 0, Math.PI * 2);
|
|
1444
|
+
}
|
|
1445
|
+
ctx.fillStyle = soldermaskColor;
|
|
1446
|
+
ctx.fill();
|
|
1447
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
1448
|
+
ctx.globalCompositeOperation = prevCompositeOp || "source-over";
|
|
1449
|
+
}
|
|
1450
|
+
const innerWidth = scaledWidth - scaledMargin * 2;
|
|
1451
|
+
const innerHeight = scaledHeight - scaledMargin * 2;
|
|
1452
|
+
if (innerWidth > 0 && innerHeight > 0) {
|
|
1383
1453
|
ctx.beginPath();
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
ctx.
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
ctx.
|
|
1402
|
-
ctx.lineTo(endX, endY);
|
|
1403
|
-
ctx.stroke();
|
|
1454
|
+
if (innerWidth > innerHeight) {
|
|
1455
|
+
const radius = innerHeight / 2;
|
|
1456
|
+
const straightLength = innerWidth - innerHeight;
|
|
1457
|
+
ctx.moveTo(-straightLength / 2, -radius);
|
|
1458
|
+
ctx.lineTo(straightLength / 2, -radius);
|
|
1459
|
+
ctx.arc(straightLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2);
|
|
1460
|
+
ctx.lineTo(-straightLength / 2, radius);
|
|
1461
|
+
ctx.arc(-straightLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2);
|
|
1462
|
+
} else if (innerHeight > innerWidth) {
|
|
1463
|
+
const radius = innerWidth / 2;
|
|
1464
|
+
const straightLength = innerHeight - innerWidth;
|
|
1465
|
+
ctx.moveTo(radius, -straightLength / 2);
|
|
1466
|
+
ctx.lineTo(radius, straightLength / 2);
|
|
1467
|
+
ctx.arc(0, straightLength / 2, radius, 0, Math.PI);
|
|
1468
|
+
ctx.lineTo(-radius, -straightLength / 2);
|
|
1469
|
+
ctx.arc(0, -straightLength / 2, radius, Math.PI, 0);
|
|
1470
|
+
} else {
|
|
1471
|
+
ctx.arc(0, 0, innerWidth / 2, 0, Math.PI * 2);
|
|
1404
1472
|
}
|
|
1405
|
-
ctx.
|
|
1406
|
-
|
|
1473
|
+
ctx.fillStyle = padColor;
|
|
1474
|
+
ctx.fill();
|
|
1407
1475
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1476
|
+
ctx.restore();
|
|
1477
|
+
}
|
|
1478
|
+
function drawSoldermaskRingForOval(ctx, center, radius_x, radius_y, margin, rotation, realToCanvasMat, soldermaskColor, holeColor) {
|
|
1479
|
+
const [cx, cy] = applyToPoint14(realToCanvasMat, [center.x, center.y]);
|
|
1480
|
+
const scaledRadiusX = radius_x * Math.abs(realToCanvasMat.a);
|
|
1481
|
+
const scaledRadiusY = radius_y * Math.abs(realToCanvasMat.a);
|
|
1482
|
+
const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
|
|
1483
|
+
ctx.save();
|
|
1484
|
+
ctx.translate(cx, cy);
|
|
1485
|
+
if (rotation !== 0) {
|
|
1486
|
+
ctx.rotate(-rotation * (Math.PI / 180));
|
|
1487
|
+
}
|
|
1488
|
+
const prevCompositeOp = ctx.globalCompositeOperation;
|
|
1489
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
1490
|
+
ctx.globalCompositeOperation = "source-atop";
|
|
1491
|
+
}
|
|
1492
|
+
ctx.beginPath();
|
|
1493
|
+
ctx.ellipse(0, 0, scaledRadiusX, scaledRadiusY, 0, 0, Math.PI * 2);
|
|
1494
|
+
ctx.fillStyle = soldermaskColor;
|
|
1495
|
+
ctx.fill();
|
|
1496
|
+
if (ctx.globalCompositeOperation !== void 0) {
|
|
1497
|
+
ctx.globalCompositeOperation = prevCompositeOp || "source-over";
|
|
1498
|
+
}
|
|
1499
|
+
const innerRadiusX = Math.max(0, scaledRadiusX - scaledMargin);
|
|
1500
|
+
const innerRadiusY = Math.max(0, scaledRadiusY - scaledMargin);
|
|
1501
|
+
if (innerRadiusX > 0 && innerRadiusY > 0) {
|
|
1417
1502
|
ctx.beginPath();
|
|
1418
|
-
ctx.
|
|
1419
|
-
ctx.fillStyle =
|
|
1503
|
+
ctx.ellipse(0, 0, innerRadiusX, innerRadiusY, 0, 0, Math.PI * 2);
|
|
1504
|
+
ctx.fillStyle = holeColor;
|
|
1420
1505
|
ctx.fill();
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1506
|
+
}
|
|
1507
|
+
ctx.restore();
|
|
1508
|
+
}
|
|
1509
|
+
function offsetPolygonPoints(points, offset) {
|
|
1510
|
+
if (points.length < 3 || offset === 0) return points;
|
|
1511
|
+
let centerX = 0;
|
|
1512
|
+
let centerY = 0;
|
|
1513
|
+
for (const point of points) {
|
|
1514
|
+
centerX += point.x;
|
|
1515
|
+
centerY += point.y;
|
|
1516
|
+
}
|
|
1517
|
+
centerX /= points.length;
|
|
1518
|
+
centerY /= points.length;
|
|
1519
|
+
const result = [];
|
|
1520
|
+
for (const point of points) {
|
|
1521
|
+
const vectorX = point.x - centerX;
|
|
1522
|
+
const vectorY = point.y - centerY;
|
|
1523
|
+
const distance = Math.sqrt(vectorX * vectorX + vectorY * vectorY);
|
|
1524
|
+
if (distance > 0) {
|
|
1525
|
+
const normalizedX = vectorX / distance;
|
|
1526
|
+
const normalizedY = vectorY / distance;
|
|
1527
|
+
result.push({
|
|
1528
|
+
x: point.x + normalizedX * offset,
|
|
1529
|
+
y: point.y + normalizedY * offset
|
|
1530
|
+
});
|
|
1531
|
+
} else {
|
|
1532
|
+
result.push({
|
|
1533
|
+
x: point.x + offset,
|
|
1534
|
+
y: point.y
|
|
1535
|
+
});
|
|
1433
1536
|
}
|
|
1434
|
-
ctx.restore();
|
|
1435
|
-
return;
|
|
1436
1537
|
}
|
|
1538
|
+
return result;
|
|
1437
1539
|
}
|
|
1438
1540
|
|
|
1439
|
-
// lib/drawer/elements/pcb-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1541
|
+
// lib/drawer/elements/pcb-plated-hole.ts
|
|
1542
|
+
function drawPcbPlatedHole(params) {
|
|
1543
|
+
const {
|
|
1544
|
+
ctx,
|
|
1545
|
+
hole,
|
|
1546
|
+
realToCanvasMat,
|
|
1547
|
+
colorMap,
|
|
1548
|
+
soldermaskMargin = 0,
|
|
1549
|
+
drawSoldermask
|
|
1550
|
+
} = params;
|
|
1551
|
+
if (hole.is_covered_with_solder_mask === true && drawSoldermask) {
|
|
1552
|
+
return;
|
|
1447
1553
|
}
|
|
1448
|
-
const
|
|
1449
|
-
const
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1554
|
+
const copperColor = colorMap.copper.top;
|
|
1555
|
+
const copperInset = soldermaskMargin < 0 ? Math.abs(soldermaskMargin) : 0;
|
|
1556
|
+
if (hole.shape === "circle") {
|
|
1557
|
+
drawCircle({
|
|
1558
|
+
ctx,
|
|
1559
|
+
center: { x: hole.x, y: hole.y },
|
|
1560
|
+
radius: hole.outer_diameter / 2 - copperInset,
|
|
1561
|
+
fill: copperColor,
|
|
1562
|
+
realToCanvasMat
|
|
1563
|
+
});
|
|
1564
|
+
drawCircle({
|
|
1565
|
+
ctx,
|
|
1566
|
+
center: { x: hole.x, y: hole.y },
|
|
1567
|
+
radius: hole.hole_diameter / 2,
|
|
1568
|
+
fill: colorMap.drill,
|
|
1569
|
+
realToCanvasMat
|
|
1570
|
+
});
|
|
1571
|
+
return;
|
|
1453
1572
|
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1573
|
+
if (hole.shape === "oval") {
|
|
1574
|
+
drawOval({
|
|
1575
|
+
ctx,
|
|
1576
|
+
center: { x: hole.x, y: hole.y },
|
|
1577
|
+
radius_x: hole.outer_width / 2 - copperInset,
|
|
1578
|
+
radius_y: hole.outer_height / 2 - copperInset,
|
|
1579
|
+
fill: copperColor,
|
|
1580
|
+
realToCanvasMat,
|
|
1581
|
+
rotation: hole.ccw_rotation
|
|
1582
|
+
});
|
|
1583
|
+
drawOval({
|
|
1584
|
+
ctx,
|
|
1585
|
+
center: { x: hole.x, y: hole.y },
|
|
1586
|
+
radius_x: hole.hole_width / 2,
|
|
1587
|
+
radius_y: hole.hole_height / 2,
|
|
1588
|
+
fill: colorMap.drill,
|
|
1589
|
+
realToCanvasMat,
|
|
1590
|
+
rotation: hole.ccw_rotation
|
|
1591
|
+
});
|
|
1471
1592
|
return;
|
|
1472
1593
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1594
|
+
if (hole.shape === "pill") {
|
|
1595
|
+
drawPill({
|
|
1596
|
+
ctx,
|
|
1597
|
+
center: { x: hole.x, y: hole.y },
|
|
1598
|
+
width: hole.outer_width - copperInset * 2,
|
|
1599
|
+
height: hole.outer_height - copperInset * 2,
|
|
1600
|
+
fill: copperColor,
|
|
1601
|
+
realToCanvasMat,
|
|
1602
|
+
rotation: hole.ccw_rotation
|
|
1603
|
+
});
|
|
1604
|
+
drawPill({
|
|
1605
|
+
ctx,
|
|
1606
|
+
center: { x: hole.x, y: hole.y },
|
|
1607
|
+
width: hole.hole_width,
|
|
1608
|
+
height: hole.hole_height,
|
|
1609
|
+
fill: colorMap.drill,
|
|
1610
|
+
realToCanvasMat,
|
|
1611
|
+
rotation: hole.ccw_rotation
|
|
1612
|
+
});
|
|
1483
1613
|
return;
|
|
1484
1614
|
}
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
const endAngle = Math.atan2(
|
|
1506
|
-
canvasEndY - canvasCenterY,
|
|
1507
|
-
canvasEndX - canvasCenterX
|
|
1508
|
-
);
|
|
1509
|
-
const det = realToCanvasMat.a * realToCanvasMat.d - realToCanvasMat.b * realToCanvasMat.c;
|
|
1510
|
-
const isFlipped = det < 0;
|
|
1511
|
-
const counterclockwise = bulge > 0 ? !isFlipped : isFlipped;
|
|
1512
|
-
ctx.arc(
|
|
1513
|
-
canvasCenterX,
|
|
1514
|
-
canvasCenterY,
|
|
1515
|
-
canvasRadius,
|
|
1516
|
-
startAngle,
|
|
1517
|
-
endAngle,
|
|
1518
|
-
counterclockwise
|
|
1519
|
-
);
|
|
1520
|
-
}
|
|
1521
|
-
function drawRing(ctx, ring, realToCanvasMat) {
|
|
1522
|
-
if (ring.vertices.length < 2) return;
|
|
1523
|
-
if (ring.vertices.length === 2) {
|
|
1524
|
-
const v0 = ring.vertices[0];
|
|
1525
|
-
const v1 = ring.vertices[1];
|
|
1526
|
-
if (v0 && v1 && Math.abs((v0.bulge ?? 0) - 1) < 1e-10 && Math.abs((v1.bulge ?? 0) - 1) < 1e-10) {
|
|
1527
|
-
const [x0, y0] = applyToPoint12(realToCanvasMat, [v0.x, v0.y]);
|
|
1528
|
-
ctx.moveTo(x0, y0);
|
|
1529
|
-
drawArcFromBulge(ctx, v0.x, v0.y, v1.x, v1.y, 1, realToCanvasMat);
|
|
1530
|
-
drawArcFromBulge(ctx, v1.x, v1.y, v0.x, v0.y, 1, realToCanvasMat);
|
|
1531
|
-
return;
|
|
1532
|
-
}
|
|
1615
|
+
if (hole.shape === "circular_hole_with_rect_pad") {
|
|
1616
|
+
drawRect({
|
|
1617
|
+
ctx,
|
|
1618
|
+
center: { x: hole.x, y: hole.y },
|
|
1619
|
+
width: hole.rect_pad_width - copperInset * 2,
|
|
1620
|
+
height: hole.rect_pad_height - copperInset * 2,
|
|
1621
|
+
fill: copperColor,
|
|
1622
|
+
realToCanvasMat,
|
|
1623
|
+
borderRadius: hole.rect_border_radius ? Math.max(0, hole.rect_border_radius - copperInset) : 0
|
|
1624
|
+
});
|
|
1625
|
+
const holeX = hole.x + (hole.hole_offset_x ?? 0);
|
|
1626
|
+
const holeY = hole.y + (hole.hole_offset_y ?? 0);
|
|
1627
|
+
drawCircle({
|
|
1628
|
+
ctx,
|
|
1629
|
+
center: { x: holeX, y: holeY },
|
|
1630
|
+
radius: hole.hole_diameter / 2,
|
|
1631
|
+
fill: colorMap.drill,
|
|
1632
|
+
realToCanvasMat
|
|
1633
|
+
});
|
|
1634
|
+
return;
|
|
1533
1635
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
const
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
ctx,
|
|
1556
|
-
currentVertex.x,
|
|
1557
|
-
currentVertex.y,
|
|
1558
|
-
nextVertex.x,
|
|
1559
|
-
nextVertex.y,
|
|
1560
|
-
bulge,
|
|
1561
|
-
realToCanvasMat
|
|
1562
|
-
);
|
|
1563
|
-
}
|
|
1636
|
+
if (hole.shape === "pill_hole_with_rect_pad") {
|
|
1637
|
+
drawRect({
|
|
1638
|
+
ctx,
|
|
1639
|
+
center: { x: hole.x, y: hole.y },
|
|
1640
|
+
width: hole.rect_pad_width - copperInset * 2,
|
|
1641
|
+
height: hole.rect_pad_height - copperInset * 2,
|
|
1642
|
+
fill: copperColor,
|
|
1643
|
+
realToCanvasMat,
|
|
1644
|
+
borderRadius: hole.rect_border_radius ? Math.max(0, hole.rect_border_radius - copperInset) : 0
|
|
1645
|
+
});
|
|
1646
|
+
const holeX = hole.x + (hole.hole_offset_x ?? 0);
|
|
1647
|
+
const holeY = hole.y + (hole.hole_offset_y ?? 0);
|
|
1648
|
+
drawPill({
|
|
1649
|
+
ctx,
|
|
1650
|
+
center: { x: holeX, y: holeY },
|
|
1651
|
+
width: hole.hole_width,
|
|
1652
|
+
height: hole.hole_height,
|
|
1653
|
+
fill: colorMap.drill,
|
|
1654
|
+
realToCanvasMat
|
|
1655
|
+
});
|
|
1656
|
+
return;
|
|
1564
1657
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
const
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
ctx
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1658
|
+
if (hole.shape === "rotated_pill_hole_with_rect_pad") {
|
|
1659
|
+
drawRect({
|
|
1660
|
+
ctx,
|
|
1661
|
+
center: { x: hole.x, y: hole.y },
|
|
1662
|
+
width: hole.rect_pad_width - copperInset * 2,
|
|
1663
|
+
height: hole.rect_pad_height - copperInset * 2,
|
|
1664
|
+
fill: copperColor,
|
|
1665
|
+
realToCanvasMat,
|
|
1666
|
+
borderRadius: hole.rect_border_radius ? Math.max(0, hole.rect_border_radius - copperInset) : 0,
|
|
1667
|
+
rotation: hole.rect_ccw_rotation
|
|
1668
|
+
});
|
|
1669
|
+
const holeX = hole.x + (hole.hole_offset_x ?? 0);
|
|
1670
|
+
const holeY = hole.y + (hole.hole_offset_y ?? 0);
|
|
1671
|
+
drawPill({
|
|
1672
|
+
ctx,
|
|
1673
|
+
center: { x: holeX, y: holeY },
|
|
1674
|
+
width: hole.hole_width,
|
|
1675
|
+
height: hole.hole_height,
|
|
1676
|
+
fill: colorMap.drill,
|
|
1677
|
+
realToCanvasMat,
|
|
1678
|
+
rotation: hole.hole_ccw_rotation
|
|
1679
|
+
});
|
|
1587
1680
|
return;
|
|
1588
1681
|
}
|
|
1589
|
-
if (
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
if (!point) continue;
|
|
1605
|
-
const [x, y] = point;
|
|
1606
|
-
ctx.lineTo(x, y);
|
|
1682
|
+
if (hole.shape === "hole_with_polygon_pad") {
|
|
1683
|
+
const padOutline = hole.pad_outline;
|
|
1684
|
+
if (padOutline && padOutline.length >= 3) {
|
|
1685
|
+
const padPoints = padOutline.map((point) => ({
|
|
1686
|
+
x: hole.x + point.x,
|
|
1687
|
+
y: hole.y + point.y
|
|
1688
|
+
}));
|
|
1689
|
+
const copperPoints = copperInset > 0 ? offsetPolygonPoints(padPoints, -copperInset) : padPoints;
|
|
1690
|
+
if (copperPoints.length >= 3) {
|
|
1691
|
+
drawPolygon({
|
|
1692
|
+
ctx,
|
|
1693
|
+
points: copperPoints,
|
|
1694
|
+
fill: copperColor,
|
|
1695
|
+
realToCanvasMat
|
|
1696
|
+
});
|
|
1607
1697
|
}
|
|
1608
|
-
ctx.closePath();
|
|
1609
|
-
ctx.fillStyle = color;
|
|
1610
|
-
ctx.globalAlpha = 0.5;
|
|
1611
|
-
ctx.fill();
|
|
1612
1698
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1699
|
+
const holeX = hole.x + (hole.hole_offset_x ?? 0);
|
|
1700
|
+
const holeY = hole.y + (hole.hole_offset_y ?? 0);
|
|
1701
|
+
const holeShape = hole.hole_shape;
|
|
1702
|
+
if (holeShape === "circle") {
|
|
1703
|
+
drawCircle({
|
|
1704
|
+
ctx,
|
|
1705
|
+
center: { x: holeX, y: holeY },
|
|
1706
|
+
radius: (hole.hole_diameter ?? 0) / 2,
|
|
1707
|
+
fill: colorMap.drill,
|
|
1708
|
+
realToCanvasMat
|
|
1709
|
+
});
|
|
1710
|
+
} else if (holeShape === "oval") {
|
|
1711
|
+
drawOval({
|
|
1712
|
+
ctx,
|
|
1713
|
+
center: { x: holeX, y: holeY },
|
|
1714
|
+
radius_x: (hole.hole_width ?? 0) / 2,
|
|
1715
|
+
radius_y: (hole.hole_height ?? 0) / 2,
|
|
1716
|
+
fill: colorMap.drill,
|
|
1717
|
+
realToCanvasMat
|
|
1718
|
+
});
|
|
1719
|
+
} else if (holeShape === "pill") {
|
|
1720
|
+
drawPill({
|
|
1721
|
+
ctx,
|
|
1722
|
+
center: { x: holeX, y: holeY },
|
|
1723
|
+
width: hole.hole_width ?? 0,
|
|
1724
|
+
height: hole.hole_height ?? 0,
|
|
1725
|
+
fill: colorMap.drill,
|
|
1726
|
+
realToCanvasMat
|
|
1727
|
+
});
|
|
1728
|
+
} else if (holeShape === "rotated_pill") {
|
|
1729
|
+
drawPill({
|
|
1730
|
+
ctx,
|
|
1731
|
+
center: { x: holeX, y: holeY },
|
|
1732
|
+
width: hole.hole_width ?? 0,
|
|
1733
|
+
height: hole.hole_height ?? 0,
|
|
1734
|
+
fill: colorMap.drill,
|
|
1735
|
+
realToCanvasMat
|
|
1736
|
+
});
|
|
1623
1737
|
}
|
|
1624
|
-
ctx.fillStyle = color;
|
|
1625
|
-
ctx.globalAlpha = 0.5;
|
|
1626
|
-
ctx.fill("evenodd");
|
|
1627
|
-
ctx.restore();
|
|
1628
1738
|
return;
|
|
1629
1739
|
}
|
|
1630
|
-
ctx.restore();
|
|
1631
1740
|
}
|
|
1632
1741
|
|
|
1633
|
-
// lib/drawer/elements/pcb-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
function mapAnchorAlignment2(alignment) {
|
|
1637
|
-
if (!alignment) return "center";
|
|
1638
|
-
if (alignment.includes("left")) return "center_left";
|
|
1639
|
-
if (alignment.includes("right")) return "center_right";
|
|
1640
|
-
return "center";
|
|
1742
|
+
// lib/drawer/elements/pcb-silkscreen-circle.ts
|
|
1743
|
+
function layerToSilkscreenColor(layer, colorMap) {
|
|
1744
|
+
return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
|
|
1641
1745
|
}
|
|
1642
|
-
function
|
|
1643
|
-
const { ctx,
|
|
1644
|
-
const
|
|
1645
|
-
|
|
1646
|
-
const [x, y] = applyToPoint13(realToCanvasMat, [
|
|
1647
|
-
text.anchor_position.x,
|
|
1648
|
-
text.anchor_position.y
|
|
1649
|
-
]);
|
|
1650
|
-
const scale2 = Math.abs(realToCanvasMat.a);
|
|
1651
|
-
const fontSize = (text.font_size ?? 1) * scale2;
|
|
1652
|
-
const rotation = text.ccw_rotation ?? 0;
|
|
1653
|
-
const padding = {
|
|
1654
|
-
...DEFAULT_PADDING,
|
|
1655
|
-
...text.knockout_padding
|
|
1656
|
-
};
|
|
1657
|
-
const textColor = colorMap.copper[text.layer] ?? colorMap.copper.top;
|
|
1658
|
-
const layout = getAlphabetLayout(content, fontSize);
|
|
1659
|
-
const totalWidth = layout.width + layout.strokeWidth;
|
|
1660
|
-
const alignment = mapAnchorAlignment2(text.anchor_alignment);
|
|
1661
|
-
const startPos = getTextStartPosition(alignment, layout);
|
|
1662
|
-
ctx.save();
|
|
1663
|
-
ctx.translate(x, y);
|
|
1664
|
-
if (text.is_mirrored) ctx.scale(-1, 1);
|
|
1665
|
-
if (rotation !== 0) ctx.rotate(-rotation * (Math.PI / 180));
|
|
1666
|
-
ctx.lineWidth = layout.strokeWidth;
|
|
1667
|
-
ctx.lineCap = "round";
|
|
1668
|
-
ctx.lineJoin = "round";
|
|
1669
|
-
if (text.is_knockout) {
|
|
1670
|
-
const paddingLeft = padding.left * scale2;
|
|
1671
|
-
const paddingRight = padding.right * scale2;
|
|
1672
|
-
const paddingTop = padding.top * scale2;
|
|
1673
|
-
const paddingBottom = padding.bottom * scale2;
|
|
1674
|
-
const rectX = startPos.x - paddingLeft * 4;
|
|
1675
|
-
const rectY = startPos.y - paddingTop * 4;
|
|
1676
|
-
const rectWidth = totalWidth + paddingLeft * 2 + paddingRight * 2;
|
|
1677
|
-
const rectHeight = layout.height + layout.strokeWidth + paddingTop * 2 + paddingBottom * 2;
|
|
1678
|
-
ctx.fillStyle = textColor;
|
|
1679
|
-
ctx.fillRect(rectX, rectY, rectWidth, rectHeight);
|
|
1680
|
-
} else {
|
|
1681
|
-
ctx.strokeStyle = textColor;
|
|
1682
|
-
}
|
|
1683
|
-
strokeAlphabetText({
|
|
1746
|
+
function drawPcbSilkscreenCircle(params) {
|
|
1747
|
+
const { ctx, circle, realToCanvasMat, colorMap } = params;
|
|
1748
|
+
const color = layerToSilkscreenColor(circle.layer, colorMap);
|
|
1749
|
+
drawCircle({
|
|
1684
1750
|
ctx,
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1751
|
+
center: circle.center,
|
|
1752
|
+
radius: circle.radius,
|
|
1753
|
+
fill: color,
|
|
1754
|
+
realToCanvasMat
|
|
1689
1755
|
});
|
|
1690
|
-
ctx.restore();
|
|
1691
1756
|
}
|
|
1692
1757
|
|
|
1693
|
-
// lib/drawer/elements/pcb-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
return DEFAULT_FABRICATION_NOTE_COLOR;
|
|
1697
|
-
}
|
|
1698
|
-
function drawPcbFabricationNoteText(params) {
|
|
1699
|
-
const { ctx, text, realToCanvasMat, colorMap } = params;
|
|
1700
|
-
const defaultColor = layerToColor4(text.layer, colorMap);
|
|
1701
|
-
const color = text.color ?? defaultColor;
|
|
1702
|
-
const fontSize = text.font_size;
|
|
1703
|
-
drawText({
|
|
1704
|
-
ctx,
|
|
1705
|
-
text: text.text,
|
|
1706
|
-
x: text.anchor_position.x,
|
|
1707
|
-
y: text.anchor_position.y,
|
|
1708
|
-
fontSize,
|
|
1709
|
-
color,
|
|
1710
|
-
realToCanvasMat,
|
|
1711
|
-
anchorAlignment: text.anchor_alignment
|
|
1712
|
-
});
|
|
1758
|
+
// lib/drawer/elements/pcb-silkscreen-line.ts
|
|
1759
|
+
function layerToSilkscreenColor2(layer, colorMap) {
|
|
1760
|
+
return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
|
|
1713
1761
|
}
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
const defaultColor = "rgba(255,255,255,0.5)";
|
|
1719
|
-
const color = rect.color ?? defaultColor;
|
|
1720
|
-
const isFilled = rect.is_filled ?? false;
|
|
1721
|
-
const hasStroke = rect.has_stroke ?? true;
|
|
1722
|
-
const isStrokeDashed = rect.is_stroke_dashed ?? false;
|
|
1723
|
-
drawRect({
|
|
1762
|
+
function drawPcbSilkscreenLine(params) {
|
|
1763
|
+
const { ctx, line, realToCanvasMat, colorMap } = params;
|
|
1764
|
+
const color = layerToSilkscreenColor2(line.layer, colorMap);
|
|
1765
|
+
drawLine({
|
|
1724
1766
|
ctx,
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
strokeWidth: hasStroke ? rect.stroke_width : void 0,
|
|
1731
|
-
borderRadius: rect.corner_radius,
|
|
1732
|
-
realToCanvasMat,
|
|
1733
|
-
isStrokeDashed
|
|
1767
|
+
start: { x: line.x1, y: line.y1 },
|
|
1768
|
+
end: { x: line.x2, y: line.y2 },
|
|
1769
|
+
strokeWidth: line.stroke_width ?? 0.1,
|
|
1770
|
+
stroke: color,
|
|
1771
|
+
realToCanvasMat
|
|
1734
1772
|
});
|
|
1735
1773
|
}
|
|
1736
1774
|
|
|
1737
|
-
// lib/drawer/elements/pcb-
|
|
1738
|
-
function
|
|
1739
|
-
|
|
1740
|
-
const defaultColor = "rgb(89, 148, 220)";
|
|
1741
|
-
const color = rect.color ?? defaultColor;
|
|
1742
|
-
const isFilled = rect.is_filled ?? false;
|
|
1743
|
-
const hasStroke = rect.has_stroke ?? true;
|
|
1744
|
-
const isStrokeDashed = rect.is_stroke_dashed ?? false;
|
|
1745
|
-
drawRect({
|
|
1746
|
-
ctx,
|
|
1747
|
-
center: rect.center,
|
|
1748
|
-
width: rect.width,
|
|
1749
|
-
height: rect.height,
|
|
1750
|
-
fill: isFilled ? color : void 0,
|
|
1751
|
-
stroke: hasStroke ? color : void 0,
|
|
1752
|
-
strokeWidth: hasStroke ? rect.stroke_width : void 0,
|
|
1753
|
-
borderRadius: rect.corner_radius,
|
|
1754
|
-
realToCanvasMat,
|
|
1755
|
-
isStrokeDashed
|
|
1756
|
-
});
|
|
1775
|
+
// lib/drawer/elements/pcb-silkscreen-oval.ts
|
|
1776
|
+
function layerToSilkscreenColor3(layer, colorMap) {
|
|
1777
|
+
return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
|
|
1757
1778
|
}
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
start: { x: start.x, y: start.y },
|
|
1772
|
-
end: { x: end.x, y: end.y },
|
|
1773
|
-
strokeWidth: path.stroke_width ?? 0.1,
|
|
1774
|
-
stroke: color,
|
|
1775
|
-
realToCanvasMat
|
|
1776
|
-
});
|
|
1777
|
-
}
|
|
1779
|
+
function drawPcbSilkscreenOval(params) {
|
|
1780
|
+
const { ctx, oval, realToCanvasMat, colorMap } = params;
|
|
1781
|
+
const color = layerToSilkscreenColor3(oval.layer, colorMap);
|
|
1782
|
+
drawOval({
|
|
1783
|
+
ctx,
|
|
1784
|
+
center: oval.center,
|
|
1785
|
+
radius_x: oval.radius_x,
|
|
1786
|
+
radius_y: oval.radius_y,
|
|
1787
|
+
stroke: color,
|
|
1788
|
+
strokeWidth: 0.1,
|
|
1789
|
+
realToCanvasMat,
|
|
1790
|
+
rotation: oval.ccw_rotation ?? 0
|
|
1791
|
+
});
|
|
1778
1792
|
}
|
|
1779
1793
|
|
|
1780
|
-
// lib/drawer/elements/pcb-
|
|
1781
|
-
function
|
|
1794
|
+
// lib/drawer/elements/pcb-silkscreen-path.ts
|
|
1795
|
+
function layerToSilkscreenColor4(layer, colorMap) {
|
|
1796
|
+
return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
|
|
1797
|
+
}
|
|
1798
|
+
function drawPcbSilkscreenPath(params) {
|
|
1782
1799
|
const { ctx, path, realToCanvasMat, colorMap } = params;
|
|
1783
|
-
const
|
|
1784
|
-
const color = path.color ?? defaultColor;
|
|
1800
|
+
const color = layerToSilkscreenColor4(path.layer, colorMap);
|
|
1785
1801
|
if (!path.route || path.route.length < 2) return;
|
|
1786
1802
|
for (let i = 0; i < path.route.length - 1; i++) {
|
|
1787
1803
|
const start = path.route[i];
|
|
@@ -1798,279 +1814,172 @@ function drawPcbNotePath(params) {
|
|
|
1798
1814
|
}
|
|
1799
1815
|
}
|
|
1800
1816
|
|
|
1801
|
-
// lib/drawer/elements/pcb-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
const { ctx, text, realToCanvasMat, colorMap } = params;
|
|
1805
|
-
const defaultColor = DEFAULT_NOTE_TEXT_COLOR;
|
|
1806
|
-
const color = text.color ?? defaultColor;
|
|
1807
|
-
const fontSize = text.font_size ?? 1;
|
|
1808
|
-
drawText({
|
|
1809
|
-
ctx,
|
|
1810
|
-
text: text.text ?? "",
|
|
1811
|
-
x: text.anchor_position.x,
|
|
1812
|
-
y: text.anchor_position.y,
|
|
1813
|
-
fontSize,
|
|
1814
|
-
color,
|
|
1815
|
-
realToCanvasMat,
|
|
1816
|
-
anchorAlignment: text.anchor_alignment ?? "center"
|
|
1817
|
-
});
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
// lib/drawer/shapes/dimension-line.ts
|
|
1821
|
-
import { applyToPoint as applyToPoint14 } from "transformation-matrix";
|
|
1822
|
-
var TEXT_OFFSET_MULTIPLIER = 1.5;
|
|
1823
|
-
var CHARACTER_WIDTH_MULTIPLIER = 0.6;
|
|
1824
|
-
var TEXT_INTERSECTION_PADDING_MULTIPLIER = 0.3;
|
|
1825
|
-
function normalize(v) {
|
|
1826
|
-
const len = Math.hypot(v.x, v.y) || 1;
|
|
1827
|
-
return { x: v.x / len, y: v.y / len };
|
|
1817
|
+
// lib/drawer/elements/pcb-silkscreen-pill.ts
|
|
1818
|
+
function layerToSilkscreenColor5(layer, colorMap) {
|
|
1819
|
+
return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
|
|
1828
1820
|
}
|
|
1829
|
-
function
|
|
1830
|
-
const {
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
realToCanvasMat,
|
|
1835
|
-
color,
|
|
1836
|
-
fontSize,
|
|
1837
|
-
arrowSize = 1,
|
|
1838
|
-
strokeWidth: manualStrokeWidth,
|
|
1839
|
-
text,
|
|
1840
|
-
textRotation,
|
|
1841
|
-
offset
|
|
1842
|
-
} = params;
|
|
1843
|
-
const direction = normalize({ x: to.x - from.x, y: to.y - from.y });
|
|
1844
|
-
const perpendicular = { x: -direction.y, y: direction.x };
|
|
1845
|
-
const hasOffsetDirection = offset?.direction && typeof offset.direction.x === "number" && typeof offset.direction.y === "number";
|
|
1846
|
-
const normalizedOffsetDirection = hasOffsetDirection ? normalize(offset.direction) : { x: 0, y: 0 };
|
|
1847
|
-
const offsetMagnitude = offset?.distance ?? 0;
|
|
1848
|
-
const offsetVector = {
|
|
1849
|
-
x: normalizedOffsetDirection.x * offsetMagnitude,
|
|
1850
|
-
y: normalizedOffsetDirection.y * offsetMagnitude
|
|
1851
|
-
};
|
|
1852
|
-
const fromOffset = { x: from.x + offsetVector.x, y: from.y + offsetVector.y };
|
|
1853
|
-
const toOffset = { x: to.x + offsetVector.x, y: to.y + offsetVector.y };
|
|
1854
|
-
const fromBase = {
|
|
1855
|
-
x: fromOffset.x + direction.x * arrowSize,
|
|
1856
|
-
y: fromOffset.y + direction.y * arrowSize
|
|
1857
|
-
};
|
|
1858
|
-
const toBase = {
|
|
1859
|
-
x: toOffset.x - direction.x * arrowSize,
|
|
1860
|
-
y: toOffset.y - direction.y * arrowSize
|
|
1861
|
-
};
|
|
1862
|
-
const scaleValue = Math.abs(realToCanvasMat.a);
|
|
1863
|
-
const strokeWidth = manualStrokeWidth ?? arrowSize / 5;
|
|
1864
|
-
const lineColor = color || "rgba(255,255,255,0.5)";
|
|
1865
|
-
const extensionDirection = hasOffsetDirection && (Math.abs(normalizedOffsetDirection.x) > Number.EPSILON || Math.abs(normalizedOffsetDirection.y) > Number.EPSILON) ? normalizedOffsetDirection : perpendicular;
|
|
1866
|
-
const extensionLength = offsetMagnitude + 0.5;
|
|
1867
|
-
const allPoints = [];
|
|
1868
|
-
const getExtensionPoints = (anchor) => {
|
|
1869
|
-
const endPoint = {
|
|
1870
|
-
x: anchor.x + extensionDirection.x * extensionLength,
|
|
1871
|
-
y: anchor.y + extensionDirection.y * extensionLength
|
|
1872
|
-
};
|
|
1873
|
-
const halfWidth = strokeWidth / 2;
|
|
1874
|
-
const extPerpendicular = {
|
|
1875
|
-
x: -extensionDirection.y,
|
|
1876
|
-
y: extensionDirection.x
|
|
1877
|
-
};
|
|
1878
|
-
return [
|
|
1879
|
-
{
|
|
1880
|
-
x: anchor.x + extPerpendicular.x * halfWidth,
|
|
1881
|
-
y: anchor.y + extPerpendicular.y * halfWidth
|
|
1882
|
-
},
|
|
1883
|
-
{
|
|
1884
|
-
x: anchor.x - extPerpendicular.x * halfWidth,
|
|
1885
|
-
y: anchor.y - extPerpendicular.y * halfWidth
|
|
1886
|
-
},
|
|
1887
|
-
{
|
|
1888
|
-
x: endPoint.x - extPerpendicular.x * halfWidth,
|
|
1889
|
-
y: endPoint.y - extPerpendicular.y * halfWidth
|
|
1890
|
-
},
|
|
1891
|
-
{
|
|
1892
|
-
x: endPoint.x + extPerpendicular.x * halfWidth,
|
|
1893
|
-
y: endPoint.y + extPerpendicular.y * halfWidth
|
|
1894
|
-
}
|
|
1895
|
-
];
|
|
1896
|
-
};
|
|
1897
|
-
allPoints.push(fromOffset);
|
|
1898
|
-
allPoints.push({
|
|
1899
|
-
x: fromBase.x + perpendicular.x * (arrowSize / 2),
|
|
1900
|
-
y: fromBase.y + perpendicular.y * (arrowSize / 2)
|
|
1901
|
-
});
|
|
1902
|
-
allPoints.push({
|
|
1903
|
-
x: fromBase.x + perpendicular.x * (strokeWidth / 2),
|
|
1904
|
-
y: fromBase.y + perpendicular.y * (strokeWidth / 2)
|
|
1905
|
-
});
|
|
1906
|
-
allPoints.push({
|
|
1907
|
-
x: toBase.x + perpendicular.x * (strokeWidth / 2),
|
|
1908
|
-
y: toBase.y + perpendicular.y * (strokeWidth / 2)
|
|
1909
|
-
});
|
|
1910
|
-
allPoints.push({
|
|
1911
|
-
x: toBase.x + perpendicular.x * (arrowSize / 2),
|
|
1912
|
-
y: toBase.y + perpendicular.y * (arrowSize / 2)
|
|
1913
|
-
});
|
|
1914
|
-
allPoints.push(toOffset);
|
|
1915
|
-
allPoints.push({
|
|
1916
|
-
x: toBase.x - perpendicular.x * (arrowSize / 2),
|
|
1917
|
-
y: toBase.y - perpendicular.y * (arrowSize / 2)
|
|
1918
|
-
});
|
|
1919
|
-
allPoints.push({
|
|
1920
|
-
x: toBase.x - perpendicular.x * (strokeWidth / 2),
|
|
1921
|
-
y: toBase.y - perpendicular.y * (strokeWidth / 2)
|
|
1922
|
-
});
|
|
1923
|
-
allPoints.push({
|
|
1924
|
-
x: fromBase.x - perpendicular.x * (strokeWidth / 2),
|
|
1925
|
-
y: fromBase.y - perpendicular.y * (strokeWidth / 2)
|
|
1926
|
-
});
|
|
1927
|
-
allPoints.push({
|
|
1928
|
-
x: fromBase.x - perpendicular.x * (arrowSize / 2),
|
|
1929
|
-
y: fromBase.y - perpendicular.y * (arrowSize / 2)
|
|
1930
|
-
});
|
|
1931
|
-
allPoints.push(fromOffset);
|
|
1932
|
-
const startPoint = allPoints[0];
|
|
1933
|
-
const addTick = (anchor) => {
|
|
1934
|
-
const pts = getExtensionPoints(anchor);
|
|
1935
|
-
allPoints.push(startPoint);
|
|
1936
|
-
allPoints.push(pts[0]);
|
|
1937
|
-
allPoints.push(...pts);
|
|
1938
|
-
allPoints.push(pts[0]);
|
|
1939
|
-
allPoints.push(startPoint);
|
|
1940
|
-
};
|
|
1941
|
-
addTick(from);
|
|
1942
|
-
addTick(to);
|
|
1943
|
-
drawPolygon({
|
|
1821
|
+
function drawPcbSilkscreenPill(params) {
|
|
1822
|
+
const { ctx, pill, realToCanvasMat, colorMap } = params;
|
|
1823
|
+
const color = layerToSilkscreenColor5(pill.layer, colorMap);
|
|
1824
|
+
const strokeWidth = 0.2;
|
|
1825
|
+
drawPill({
|
|
1944
1826
|
ctx,
|
|
1945
|
-
|
|
1946
|
-
|
|
1827
|
+
center: pill.center,
|
|
1828
|
+
width: pill.width,
|
|
1829
|
+
height: pill.height,
|
|
1830
|
+
stroke: color,
|
|
1831
|
+
strokeWidth,
|
|
1947
1832
|
realToCanvasMat
|
|
1948
1833
|
});
|
|
1949
|
-
if (text) {
|
|
1950
|
-
const midPoint = {
|
|
1951
|
-
x: (from.x + to.x) / 2 + offsetVector.x,
|
|
1952
|
-
y: (from.y + to.y) / 2 + offsetVector.y
|
|
1953
|
-
};
|
|
1954
|
-
const [screenFromX, screenFromY] = applyToPoint14(realToCanvasMat, [
|
|
1955
|
-
fromOffset.x,
|
|
1956
|
-
fromOffset.y
|
|
1957
|
-
]);
|
|
1958
|
-
const [screenToX, screenToY] = applyToPoint14(realToCanvasMat, [
|
|
1959
|
-
toOffset.x,
|
|
1960
|
-
toOffset.y
|
|
1961
|
-
]);
|
|
1962
|
-
const screenDirection = normalize({
|
|
1963
|
-
x: screenToX - screenFromX,
|
|
1964
|
-
y: screenToY - screenFromY
|
|
1965
|
-
});
|
|
1966
|
-
let textAngle = Math.atan2(screenDirection.y, screenDirection.x) * 180 / Math.PI;
|
|
1967
|
-
if (textAngle > 90 || textAngle < -90) {
|
|
1968
|
-
textAngle += 180;
|
|
1969
|
-
}
|
|
1970
|
-
const finalTextAngle = typeof textRotation === "number" && Number.isFinite(textRotation) ? textAngle - textRotation : textAngle;
|
|
1971
|
-
let additionalOffset = 0;
|
|
1972
|
-
if (text && typeof textRotation === "number" && Number.isFinite(textRotation)) {
|
|
1973
|
-
const textWidth = text.length * fontSize * CHARACTER_WIDTH_MULTIPLIER;
|
|
1974
|
-
const textHeight = fontSize;
|
|
1975
|
-
const rotationRad = textRotation * Math.PI / 180;
|
|
1976
|
-
const sinRot = Math.abs(Math.sin(rotationRad));
|
|
1977
|
-
const cosRot = Math.abs(Math.cos(rotationRad));
|
|
1978
|
-
const halfWidth = textWidth / 2;
|
|
1979
|
-
const halfHeight = textHeight / 2;
|
|
1980
|
-
const maxExtension = halfWidth * sinRot + halfHeight * cosRot;
|
|
1981
|
-
additionalOffset = maxExtension + fontSize * TEXT_INTERSECTION_PADDING_MULTIPLIER;
|
|
1982
|
-
}
|
|
1983
|
-
const textOffset = arrowSize * TEXT_OFFSET_MULTIPLIER + additionalOffset;
|
|
1984
|
-
const textPoint = {
|
|
1985
|
-
x: midPoint.x + perpendicular.x * textOffset,
|
|
1986
|
-
y: midPoint.y + perpendicular.y * textOffset
|
|
1987
|
-
};
|
|
1988
|
-
drawText({
|
|
1989
|
-
ctx,
|
|
1990
|
-
text,
|
|
1991
|
-
x: textPoint.x,
|
|
1992
|
-
y: textPoint.y,
|
|
1993
|
-
fontSize,
|
|
1994
|
-
color: lineColor,
|
|
1995
|
-
realToCanvasMat,
|
|
1996
|
-
anchorAlignment: "center",
|
|
1997
|
-
rotation: -finalTextAngle
|
|
1998
|
-
// drawText expects CCW rotation in degrees
|
|
1999
|
-
});
|
|
2000
|
-
}
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
// lib/drawer/elements/pcb-note-dimension.ts
|
|
2004
|
-
var DEFAULT_NOTE_COLOR = "rgba(255,255,255,0.5)";
|
|
2005
|
-
function drawPcbNoteDimension(params) {
|
|
2006
|
-
const { ctx, pcbNoteDimension, realToCanvasMat } = params;
|
|
2007
|
-
const color = pcbNoteDimension.color ?? DEFAULT_NOTE_COLOR;
|
|
2008
|
-
drawDimensionLine({
|
|
2009
|
-
ctx,
|
|
2010
|
-
from: pcbNoteDimension.from,
|
|
2011
|
-
to: pcbNoteDimension.to,
|
|
2012
|
-
realToCanvasMat,
|
|
2013
|
-
color,
|
|
2014
|
-
fontSize: pcbNoteDimension.font_size,
|
|
2015
|
-
arrowSize: pcbNoteDimension.arrow_size,
|
|
2016
|
-
text: pcbNoteDimension.text,
|
|
2017
|
-
textRotation: pcbNoteDimension.text_ccw_rotation,
|
|
2018
|
-
offset: pcbNoteDimension.offset_distance && pcbNoteDimension.offset_direction ? {
|
|
2019
|
-
distance: pcbNoteDimension.offset_distance,
|
|
2020
|
-
direction: pcbNoteDimension.offset_direction
|
|
2021
|
-
} : void 0
|
|
2022
|
-
});
|
|
2023
1834
|
}
|
|
2024
1835
|
|
|
2025
|
-
// lib/drawer/elements/pcb-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
1836
|
+
// lib/drawer/elements/pcb-silkscreen-rect.ts
|
|
1837
|
+
function layerToSilkscreenColor6(layer, colorMap) {
|
|
1838
|
+
return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
|
|
1839
|
+
}
|
|
1840
|
+
function drawPcbSilkscreenRect(params) {
|
|
1841
|
+
const { ctx, rect, realToCanvasMat, colorMap } = params;
|
|
1842
|
+
const color = layerToSilkscreenColor6(rect.layer, colorMap);
|
|
1843
|
+
drawRect({
|
|
2031
1844
|
ctx,
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
color,
|
|
2036
|
-
|
|
2037
|
-
arrowSize: pcbFabricationNoteDimension.arrow_size ?? 1,
|
|
2038
|
-
text: pcbFabricationNoteDimension.text,
|
|
2039
|
-
textRotation: pcbFabricationNoteDimension.text_ccw_rotation,
|
|
2040
|
-
offset: pcbFabricationNoteDimension.offset_distance && pcbFabricationNoteDimension.offset_direction ? {
|
|
2041
|
-
distance: pcbFabricationNoteDimension.offset_distance,
|
|
2042
|
-
direction: pcbFabricationNoteDimension.offset_direction
|
|
2043
|
-
} : void 0
|
|
1845
|
+
center: rect.center,
|
|
1846
|
+
width: rect.width,
|
|
1847
|
+
height: rect.height,
|
|
1848
|
+
fill: color,
|
|
1849
|
+
realToCanvasMat
|
|
2044
1850
|
});
|
|
2045
1851
|
}
|
|
2046
1852
|
|
|
2047
|
-
// lib/drawer/elements/pcb-
|
|
1853
|
+
// lib/drawer/elements/pcb-silkscreen-text.ts
|
|
2048
1854
|
import { applyToPoint as applyToPoint15 } from "transformation-matrix";
|
|
2049
|
-
function
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
const
|
|
1855
|
+
function layerToSilkscreenColor7(layer, colorMap) {
|
|
1856
|
+
return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
|
|
1857
|
+
}
|
|
1858
|
+
function mapAnchorAlignment2(alignment) {
|
|
1859
|
+
if (!alignment) return "center";
|
|
1860
|
+
return alignment;
|
|
1861
|
+
}
|
|
1862
|
+
function drawPcbSilkscreenText(params) {
|
|
1863
|
+
const { ctx, text, realToCanvasMat, colorMap } = params;
|
|
1864
|
+
const content = text.text ?? "";
|
|
1865
|
+
if (!content) return;
|
|
1866
|
+
const color = layerToSilkscreenColor7(text.layer, colorMap);
|
|
1867
|
+
const [x, y] = applyToPoint15(realToCanvasMat, [
|
|
1868
|
+
text.anchor_position.x,
|
|
1869
|
+
text.anchor_position.y
|
|
1870
|
+
]);
|
|
1871
|
+
const scale2 = Math.abs(realToCanvasMat.a);
|
|
1872
|
+
const fontSize = (text.font_size ?? 1) * scale2;
|
|
1873
|
+
const rotation = text.ccw_rotation ?? 0;
|
|
1874
|
+
const layout = getAlphabetLayout(content, fontSize);
|
|
1875
|
+
const alignment = mapAnchorAlignment2(text.anchor_alignment);
|
|
1876
|
+
const startPos = getTextStartPosition(alignment, layout);
|
|
2058
1877
|
ctx.save();
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
ctx.setLineDash([]);
|
|
1878
|
+
ctx.translate(x, y);
|
|
1879
|
+
if (rotation !== 0) {
|
|
1880
|
+
ctx.rotate(-rotation * (Math.PI / 180));
|
|
2063
1881
|
}
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
ctx.lineWidth =
|
|
2068
|
-
ctx.strokeStyle = color;
|
|
1882
|
+
if (text.layer === "bottom") {
|
|
1883
|
+
ctx.scale(-1, 1);
|
|
1884
|
+
}
|
|
1885
|
+
ctx.lineWidth = layout.strokeWidth;
|
|
2069
1886
|
ctx.lineCap = "round";
|
|
2070
|
-
ctx.
|
|
1887
|
+
ctx.lineJoin = "round";
|
|
1888
|
+
ctx.strokeStyle = color;
|
|
1889
|
+
strokeAlphabetText({
|
|
1890
|
+
ctx,
|
|
1891
|
+
text: content,
|
|
1892
|
+
fontSize,
|
|
1893
|
+
startX: startPos.x,
|
|
1894
|
+
startY: startPos.y
|
|
1895
|
+
});
|
|
2071
1896
|
ctx.restore();
|
|
2072
1897
|
}
|
|
2073
1898
|
|
|
1899
|
+
// lib/drawer/elements/pcb-smtpad.ts
|
|
1900
|
+
function layerToColor3(layer, colorMap) {
|
|
1901
|
+
return colorMap.copper[layer] ?? colorMap.copper.top;
|
|
1902
|
+
}
|
|
1903
|
+
function getBorderRadius(pad) {
|
|
1904
|
+
if (pad.shape === "rect" || pad.shape === "rotated_rect") {
|
|
1905
|
+
return pad.corner_radius ?? pad.rect_border_radius ?? 0;
|
|
1906
|
+
}
|
|
1907
|
+
return 0;
|
|
1908
|
+
}
|
|
1909
|
+
function drawPcbSmtPad(params) {
|
|
1910
|
+
const { ctx, pad, realToCanvasMat, colorMap } = params;
|
|
1911
|
+
const color = layerToColor3(pad.layer, colorMap);
|
|
1912
|
+
if (pad.shape === "rect") {
|
|
1913
|
+
drawRect({
|
|
1914
|
+
ctx,
|
|
1915
|
+
center: { x: pad.x, y: pad.y },
|
|
1916
|
+
width: pad.width,
|
|
1917
|
+
height: pad.height,
|
|
1918
|
+
fill: color,
|
|
1919
|
+
realToCanvasMat,
|
|
1920
|
+
borderRadius: getBorderRadius(pad)
|
|
1921
|
+
});
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
if (pad.shape === "rotated_rect") {
|
|
1925
|
+
drawRect({
|
|
1926
|
+
ctx,
|
|
1927
|
+
center: { x: pad.x, y: pad.y },
|
|
1928
|
+
width: pad.width,
|
|
1929
|
+
height: pad.height,
|
|
1930
|
+
fill: color,
|
|
1931
|
+
realToCanvasMat,
|
|
1932
|
+
borderRadius: getBorderRadius(pad),
|
|
1933
|
+
rotation: pad.ccw_rotation ?? 0
|
|
1934
|
+
});
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
if (pad.shape === "circle") {
|
|
1938
|
+
drawCircle({
|
|
1939
|
+
ctx,
|
|
1940
|
+
center: { x: pad.x, y: pad.y },
|
|
1941
|
+
radius: pad.radius,
|
|
1942
|
+
fill: color,
|
|
1943
|
+
realToCanvasMat
|
|
1944
|
+
});
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
if (pad.shape === "pill") {
|
|
1948
|
+
drawPill({
|
|
1949
|
+
ctx,
|
|
1950
|
+
center: { x: pad.x, y: pad.y },
|
|
1951
|
+
width: pad.width,
|
|
1952
|
+
height: pad.height,
|
|
1953
|
+
fill: color,
|
|
1954
|
+
realToCanvasMat
|
|
1955
|
+
});
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
if (pad.shape === "rotated_pill") {
|
|
1959
|
+
drawPill({
|
|
1960
|
+
ctx,
|
|
1961
|
+
center: { x: pad.x, y: pad.y },
|
|
1962
|
+
width: pad.width,
|
|
1963
|
+
height: pad.height,
|
|
1964
|
+
fill: color,
|
|
1965
|
+
realToCanvasMat,
|
|
1966
|
+
rotation: pad.ccw_rotation ?? 0
|
|
1967
|
+
});
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
if (pad.shape === "polygon") {
|
|
1971
|
+
if (pad.points && pad.points.length >= 3) {
|
|
1972
|
+
drawPolygon({
|
|
1973
|
+
ctx,
|
|
1974
|
+
points: pad.points,
|
|
1975
|
+
fill: color,
|
|
1976
|
+
realToCanvasMat
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
|
|
2074
1983
|
// lib/drawer/elements/pcb-soldermask/board.ts
|
|
2075
1984
|
import { applyToPoint as applyToPoint16 } from "transformation-matrix";
|
|
2076
1985
|
|
|
@@ -2228,10 +2137,10 @@ function processHoleSoldermask(params) {
|
|
|
2228
2137
|
realToCanvasMat,
|
|
2229
2138
|
colorMap,
|
|
2230
2139
|
soldermaskOverCopperColor,
|
|
2231
|
-
|
|
2140
|
+
drawSoldermask
|
|
2232
2141
|
} = params;
|
|
2233
|
-
const isCoveredWithSoldermask =
|
|
2234
|
-
const margin =
|
|
2142
|
+
const isCoveredWithSoldermask = drawSoldermask && hole.is_covered_with_solder_mask === true;
|
|
2143
|
+
const margin = drawSoldermask ? hole.soldermask_margin ?? 0 : 0;
|
|
2235
2144
|
if (isCoveredWithSoldermask) {
|
|
2236
2145
|
ctx.fillStyle = soldermaskOverCopperColor;
|
|
2237
2146
|
drawHoleShapePath({ ctx, hole, realToCanvasMat, margin: 0 });
|
|
@@ -2451,11 +2360,11 @@ function processPlatedHoleSoldermask(params) {
|
|
|
2451
2360
|
colorMap,
|
|
2452
2361
|
soldermaskOverCopperColor,
|
|
2453
2362
|
layer,
|
|
2454
|
-
|
|
2363
|
+
drawSoldermask
|
|
2455
2364
|
} = params;
|
|
2456
2365
|
if (hole.layers && !hole.layers.includes(layer)) return;
|
|
2457
|
-
const isCoveredWithSoldermask =
|
|
2458
|
-
const margin =
|
|
2366
|
+
const isCoveredWithSoldermask = drawSoldermask && hole.is_covered_with_solder_mask === true;
|
|
2367
|
+
const margin = drawSoldermask ? hole.soldermask_margin ?? 0 : 0;
|
|
2459
2368
|
const copperColor = colorMap.copper.top;
|
|
2460
2369
|
if (isCoveredWithSoldermask) {
|
|
2461
2370
|
ctx.fillStyle = soldermaskOverCopperColor;
|
|
@@ -2714,11 +2623,11 @@ function processSmtPadSoldermask(params) {
|
|
|
2714
2623
|
colorMap,
|
|
2715
2624
|
soldermaskOverCopperColor,
|
|
2716
2625
|
layer,
|
|
2717
|
-
|
|
2626
|
+
drawSoldermask
|
|
2718
2627
|
} = params;
|
|
2719
2628
|
if (pad.layer !== layer) return;
|
|
2720
|
-
const isCoveredWithSoldermask =
|
|
2721
|
-
const margin =
|
|
2629
|
+
const isCoveredWithSoldermask = drawSoldermask && pad.is_covered_with_solder_mask === true;
|
|
2630
|
+
const margin = drawSoldermask ? pad.soldermask_margin ?? 0 : 0;
|
|
2722
2631
|
let ml = margin;
|
|
2723
2632
|
let mr = margin;
|
|
2724
2633
|
let mt = margin;
|
|
@@ -2981,11 +2890,11 @@ function drawPcbSoldermask(params) {
|
|
|
2981
2890
|
realToCanvasMat,
|
|
2982
2891
|
colorMap,
|
|
2983
2892
|
layer,
|
|
2984
|
-
|
|
2893
|
+
drawSoldermask
|
|
2985
2894
|
} = params;
|
|
2986
2895
|
const soldermaskColor = colorMap.soldermask[layer] ?? colorMap.soldermask.top;
|
|
2987
2896
|
const soldermaskOverCopperColor = colorMap.soldermaskOverCopper[layer] ?? colorMap.soldermaskOverCopper.top;
|
|
2988
|
-
if (
|
|
2897
|
+
if (drawSoldermask) {
|
|
2989
2898
|
drawBoardSoldermask({ ctx, board, realToCanvasMat, soldermaskColor });
|
|
2990
2899
|
}
|
|
2991
2900
|
for (const element of elements) {
|
|
@@ -2996,7 +2905,7 @@ function drawPcbSoldermask(params) {
|
|
|
2996
2905
|
colorMap,
|
|
2997
2906
|
soldermaskOverCopperColor,
|
|
2998
2907
|
layer,
|
|
2999
|
-
|
|
2908
|
+
drawSoldermask
|
|
3000
2909
|
});
|
|
3001
2910
|
}
|
|
3002
2911
|
}
|
|
@@ -3008,7 +2917,7 @@ function processElementSoldermask(params) {
|
|
|
3008
2917
|
colorMap,
|
|
3009
2918
|
soldermaskOverCopperColor,
|
|
3010
2919
|
layer,
|
|
3011
|
-
|
|
2920
|
+
drawSoldermask
|
|
3012
2921
|
} = params;
|
|
3013
2922
|
if (element.type === "pcb_smtpad") {
|
|
3014
2923
|
processSmtPadSoldermask({
|
|
@@ -3018,7 +2927,7 @@ function processElementSoldermask(params) {
|
|
|
3018
2927
|
colorMap,
|
|
3019
2928
|
soldermaskOverCopperColor,
|
|
3020
2929
|
layer,
|
|
3021
|
-
|
|
2930
|
+
drawSoldermask
|
|
3022
2931
|
});
|
|
3023
2932
|
} else if (element.type === "pcb_plated_hole") {
|
|
3024
2933
|
processPlatedHoleSoldermask({
|
|
@@ -3028,7 +2937,7 @@ function processElementSoldermask(params) {
|
|
|
3028
2937
|
colorMap,
|
|
3029
2938
|
soldermaskOverCopperColor,
|
|
3030
2939
|
layer,
|
|
3031
|
-
|
|
2940
|
+
drawSoldermask
|
|
3032
2941
|
});
|
|
3033
2942
|
} else if (element.type === "pcb_hole") {
|
|
3034
2943
|
processHoleSoldermask({
|
|
@@ -3037,7 +2946,7 @@ function processElementSoldermask(params) {
|
|
|
3037
2946
|
realToCanvasMat,
|
|
3038
2947
|
colorMap,
|
|
3039
2948
|
soldermaskOverCopperColor,
|
|
3040
|
-
|
|
2949
|
+
drawSoldermask
|
|
3041
2950
|
});
|
|
3042
2951
|
} else if (element.type === "pcb_via") {
|
|
3043
2952
|
processViaSoldermask({
|
|
@@ -3056,6 +2965,103 @@ function processElementSoldermask(params) {
|
|
|
3056
2965
|
}
|
|
3057
2966
|
}
|
|
3058
2967
|
|
|
2968
|
+
// lib/drawer/elements/pcb-trace.ts
|
|
2969
|
+
function layerToColor4(layer, colorMap) {
|
|
2970
|
+
return colorMap.copper[layer] ?? colorMap.copper.top;
|
|
2971
|
+
}
|
|
2972
|
+
function drawPcbTrace(params) {
|
|
2973
|
+
const { ctx, trace, realToCanvasMat, colorMap } = params;
|
|
2974
|
+
if (!trace.route || !Array.isArray(trace.route) || trace.route.length < 2) {
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
for (let i = 0; i < trace.route.length - 1; i++) {
|
|
2978
|
+
const start = trace.route[i];
|
|
2979
|
+
const end = trace.route[i + 1];
|
|
2980
|
+
if (!start || !end) continue;
|
|
2981
|
+
const layer = "layer" in start ? start.layer : "layer" in end ? end.layer : null;
|
|
2982
|
+
if (!layer) continue;
|
|
2983
|
+
const color = layerToColor4(layer, colorMap);
|
|
2984
|
+
const traceWidth = "width" in start ? start.width : "width" in end ? end.width : 0.1;
|
|
2985
|
+
drawLine({
|
|
2986
|
+
ctx,
|
|
2987
|
+
start: { x: start.x, y: start.y },
|
|
2988
|
+
end: { x: end.x, y: end.y },
|
|
2989
|
+
strokeWidth: traceWidth,
|
|
2990
|
+
stroke: color,
|
|
2991
|
+
realToCanvasMat,
|
|
2992
|
+
lineCap: "round"
|
|
2993
|
+
});
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
// lib/drawer/elements/pcb-via.ts
|
|
2998
|
+
function drawPcbVia(params) {
|
|
2999
|
+
const { ctx, via, realToCanvasMat, colorMap } = params;
|
|
3000
|
+
drawCircle({
|
|
3001
|
+
ctx,
|
|
3002
|
+
center: { x: via.x, y: via.y },
|
|
3003
|
+
radius: via.outer_diameter / 2,
|
|
3004
|
+
fill: colorMap.copper.top,
|
|
3005
|
+
realToCanvasMat
|
|
3006
|
+
});
|
|
3007
|
+
drawCircle({
|
|
3008
|
+
ctx,
|
|
3009
|
+
center: { x: via.x, y: via.y },
|
|
3010
|
+
radius: via.hole_diameter / 2,
|
|
3011
|
+
fill: colorMap.drill,
|
|
3012
|
+
realToCanvasMat
|
|
3013
|
+
});
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
// lib/drawer/pcb-render-layer-filter.ts
|
|
3017
|
+
import { getElementRenderLayers } from "@tscircuit/circuit-json-util";
|
|
3018
|
+
function shouldDrawElement(element, options) {
|
|
3019
|
+
if (!options.layers || options.layers.length === 0) {
|
|
3020
|
+
return true;
|
|
3021
|
+
}
|
|
3022
|
+
const elementLayers = getElementRenderLayers(element);
|
|
3023
|
+
if (elementLayers.length === 0) {
|
|
3024
|
+
return true;
|
|
3025
|
+
}
|
|
3026
|
+
return elementLayers.some((layer) => options.layers.includes(layer));
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
// lib/drawer/types.ts
|
|
3030
|
+
var DEFAULT_PCB_COLOR_MAP = {
|
|
3031
|
+
copper: {
|
|
3032
|
+
top: "rgb(200, 52, 52)",
|
|
3033
|
+
inner1: "rgb(255, 140, 0)",
|
|
3034
|
+
inner2: "rgb(255, 215, 0)",
|
|
3035
|
+
inner3: "rgb(50, 205, 50)",
|
|
3036
|
+
inner4: "rgb(64, 224, 208)",
|
|
3037
|
+
inner5: "rgb(138, 43, 226)",
|
|
3038
|
+
inner6: "rgb(255, 105, 180)",
|
|
3039
|
+
bottom: "rgb(77, 127, 196)"
|
|
3040
|
+
},
|
|
3041
|
+
soldermaskWithCopperUnderneath: {
|
|
3042
|
+
top: "rgb(18, 82, 50)",
|
|
3043
|
+
bottom: "rgb(77, 127, 196)"
|
|
3044
|
+
},
|
|
3045
|
+
soldermask: {
|
|
3046
|
+
top: "rgb(12, 55, 33)",
|
|
3047
|
+
bottom: "rgb(12, 55, 33)"
|
|
3048
|
+
},
|
|
3049
|
+
soldermaskOverCopper: {
|
|
3050
|
+
top: "rgb(52, 135, 73)",
|
|
3051
|
+
bottom: "rgb(52, 135, 73)"
|
|
3052
|
+
},
|
|
3053
|
+
substrate: "rgb(201, 162, 110)",
|
|
3054
|
+
drill: "#FF26E2",
|
|
3055
|
+
silkscreen: {
|
|
3056
|
+
top: "#f2eda1",
|
|
3057
|
+
bottom: "#5da9e9"
|
|
3058
|
+
},
|
|
3059
|
+
boardOutline: "rgba(255, 255, 255, 0.5)",
|
|
3060
|
+
courtyard: "#FF00FF",
|
|
3061
|
+
keepout: "#FF6B6B"
|
|
3062
|
+
// Red color for keepout zones
|
|
3063
|
+
};
|
|
3064
|
+
|
|
3059
3065
|
// lib/drawer/CircuitToCanvasDrawer.ts
|
|
3060
3066
|
var CircuitToCanvasDrawer = class {
|
|
3061
3067
|
ctx;
|
|
@@ -3126,7 +3132,8 @@ var CircuitToCanvasDrawer = class {
|
|
|
3126
3132
|
ctx: this.ctx,
|
|
3127
3133
|
board,
|
|
3128
3134
|
realToCanvasMat: this.realToCanvasMat,
|
|
3129
|
-
colorMap: this.colorMap
|
|
3135
|
+
colorMap: this.colorMap,
|
|
3136
|
+
drawBoardMaterial: options.drawBoardMaterial ?? false
|
|
3130
3137
|
});
|
|
3131
3138
|
}
|
|
3132
3139
|
for (const element of elements) {
|
|
@@ -3148,7 +3155,7 @@ var CircuitToCanvasDrawer = class {
|
|
|
3148
3155
|
});
|
|
3149
3156
|
}
|
|
3150
3157
|
}
|
|
3151
|
-
const
|
|
3158
|
+
const drawSoldermask = options.drawSoldermask ?? false;
|
|
3152
3159
|
if (board) {
|
|
3153
3160
|
drawPcbSoldermask({
|
|
3154
3161
|
ctx: this.ctx,
|
|
@@ -3157,7 +3164,7 @@ var CircuitToCanvasDrawer = class {
|
|
|
3157
3164
|
realToCanvasMat: this.realToCanvasMat,
|
|
3158
3165
|
colorMap: this.colorMap,
|
|
3159
3166
|
layer: "top",
|
|
3160
|
-
|
|
3167
|
+
drawSoldermask
|
|
3161
3168
|
});
|
|
3162
3169
|
}
|
|
3163
3170
|
for (const element of elements) {
|
|
@@ -3246,8 +3253,8 @@ var CircuitToCanvasDrawer = class {
|
|
|
3246
3253
|
hole: element,
|
|
3247
3254
|
realToCanvasMat: this.realToCanvasMat,
|
|
3248
3255
|
colorMap: this.colorMap,
|
|
3249
|
-
soldermaskMargin:
|
|
3250
|
-
|
|
3256
|
+
soldermaskMargin: drawSoldermask ? element.soldermask_margin : void 0,
|
|
3257
|
+
drawSoldermask
|
|
3251
3258
|
});
|
|
3252
3259
|
}
|
|
3253
3260
|
if (element.type === "pcb_via") {
|
|
@@ -3267,7 +3274,7 @@ var CircuitToCanvasDrawer = class {
|
|
|
3267
3274
|
hole: element,
|
|
3268
3275
|
realToCanvasMat: this.realToCanvasMat,
|
|
3269
3276
|
colorMap: this.colorMap,
|
|
3270
|
-
soldermaskMargin:
|
|
3277
|
+
soldermaskMargin: drawSoldermask ? element.soldermask_margin : void 0
|
|
3271
3278
|
});
|
|
3272
3279
|
}
|
|
3273
3280
|
if (element.type === "pcb_cutout") {
|