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