circuit-json-to-gerber 0.0.82 → 0.0.84
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/{chunk-7LG6NVYJ.js → chunk-23TRHNW3.js} +625 -239
- package/dist/chunk-23TRHNW3.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +4 -2
- package/dist/chunk-7LG6NVYJ.js.map +0 -1
|
@@ -292,6 +292,8 @@ var ExcellonDrillBuilder = class {
|
|
|
292
292
|
var excellonDrill = () => new ExcellonDrillBuilder();
|
|
293
293
|
|
|
294
294
|
// src/excellon-drill/convert-soup-to-excellon-drill-commands.ts
|
|
295
|
+
import polygonClipping from "polygon-clipping";
|
|
296
|
+
import { getBoundFromCenteredRect } from "@tscircuit/math-utils";
|
|
295
297
|
var getLayerCount = (circuitJson) => {
|
|
296
298
|
const board = circuitJson.find((element) => element.type === "pcb_board");
|
|
297
299
|
if (!board || typeof board.num_layers !== "number") {
|
|
@@ -426,6 +428,149 @@ var getDrillableElements = (circuitJson) => [
|
|
|
426
428
|
...circuitJson,
|
|
427
429
|
...getTraceRouteViaElements(circuitJson)
|
|
428
430
|
];
|
|
431
|
+
var circleCutoutPolygonSegmentCount = 64;
|
|
432
|
+
var getBoardOutlinePolygons = (circuitJson) => {
|
|
433
|
+
return circuitJson.flatMap((element) => {
|
|
434
|
+
if (element.type !== "pcb_board") {
|
|
435
|
+
return [];
|
|
436
|
+
}
|
|
437
|
+
if (element.outline && element.outline.length > 2) {
|
|
438
|
+
return [[element.outline.map((point) => [point.x, point.y])]];
|
|
439
|
+
}
|
|
440
|
+
const bounds = getBoundFromCenteredRect({
|
|
441
|
+
center: element.center,
|
|
442
|
+
width: element.width,
|
|
443
|
+
height: element.height
|
|
444
|
+
});
|
|
445
|
+
return [
|
|
446
|
+
[
|
|
447
|
+
[
|
|
448
|
+
[bounds.minX, bounds.minY],
|
|
449
|
+
[bounds.maxX, bounds.minY],
|
|
450
|
+
[bounds.maxX, bounds.maxY],
|
|
451
|
+
[bounds.minX, bounds.maxY]
|
|
452
|
+
]
|
|
453
|
+
]
|
|
454
|
+
];
|
|
455
|
+
});
|
|
456
|
+
};
|
|
457
|
+
var getCircleCutoutPolygon = (cutout) => {
|
|
458
|
+
const points = [];
|
|
459
|
+
for (let i = 0; i < circleCutoutPolygonSegmentCount; i++) {
|
|
460
|
+
const angle = i / circleCutoutPolygonSegmentCount * Math.PI * 2;
|
|
461
|
+
points.push([
|
|
462
|
+
cutout.center.x + cutout.radius * Math.cos(angle),
|
|
463
|
+
cutout.center.y + cutout.radius * Math.sin(angle)
|
|
464
|
+
]);
|
|
465
|
+
}
|
|
466
|
+
return [points];
|
|
467
|
+
};
|
|
468
|
+
var isInternalCircularCutout = ({
|
|
469
|
+
cutout,
|
|
470
|
+
boardOutlinePolygons
|
|
471
|
+
}) => {
|
|
472
|
+
const cutoutPolygon = getCircleCutoutPolygon(cutout);
|
|
473
|
+
return boardOutlinePolygons.some((boardOutlinePolygon) => {
|
|
474
|
+
const overlapsBoard = polygonClipping.intersection(boardOutlinePolygon, cutoutPolygon).length > 0;
|
|
475
|
+
const extendsOutsideBoard = polygonClipping.difference(cutoutPolygon, boardOutlinePolygon).length > 0;
|
|
476
|
+
return overlapsBoard && !extendsOutsideBoard;
|
|
477
|
+
});
|
|
478
|
+
};
|
|
479
|
+
var getInternalCircularCutoutDrills = (circuitJson) => {
|
|
480
|
+
const boardOutlinePolygons = getBoardOutlinePolygons(circuitJson);
|
|
481
|
+
return circuitJson.filter(
|
|
482
|
+
(element) => {
|
|
483
|
+
if (element.type !== "pcb_cutout") {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
if (element.shape !== "circle") {
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
return isInternalCircularCutout({
|
|
490
|
+
cutout: element,
|
|
491
|
+
boardOutlinePolygons
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
);
|
|
495
|
+
};
|
|
496
|
+
var getDrillElements = ({
|
|
497
|
+
circuitJson,
|
|
498
|
+
is_plated
|
|
499
|
+
}) => {
|
|
500
|
+
const drillableElements = getDrillableElements(circuitJson).filter(
|
|
501
|
+
(element) => element.type !== "pcb_cutout"
|
|
502
|
+
);
|
|
503
|
+
if (is_plated) {
|
|
504
|
+
return drillableElements;
|
|
505
|
+
}
|
|
506
|
+
const internalCircularCutoutDrills = getInternalCircularCutoutDrills(circuitJson);
|
|
507
|
+
return [...drillableElements, ...internalCircularCutoutDrills];
|
|
508
|
+
};
|
|
509
|
+
var getHoleDiameter = (element) => {
|
|
510
|
+
if ("hole_diameter" in element && typeof element.hole_diameter === "number") {
|
|
511
|
+
return element.hole_diameter;
|
|
512
|
+
}
|
|
513
|
+
if (element.type === "pcb_cutout") {
|
|
514
|
+
return element.radius * 2;
|
|
515
|
+
}
|
|
516
|
+
if ("hole_width" in element && typeof element.hole_width === "number" && "hole_height" in element && typeof element.hole_height === "number") {
|
|
517
|
+
return Math.min(element.hole_width, element.hole_height);
|
|
518
|
+
}
|
|
519
|
+
return void 0;
|
|
520
|
+
};
|
|
521
|
+
var getDrillCenter = (element) => {
|
|
522
|
+
if (element.type === "pcb_cutout") {
|
|
523
|
+
return element.center;
|
|
524
|
+
}
|
|
525
|
+
let x = 0;
|
|
526
|
+
let y = 0;
|
|
527
|
+
let holeOffsetX = 0;
|
|
528
|
+
let holeOffsetY = 0;
|
|
529
|
+
if ("x" in element && typeof element.x === "number") {
|
|
530
|
+
x = element.x;
|
|
531
|
+
}
|
|
532
|
+
if ("y" in element && typeof element.y === "number") {
|
|
533
|
+
y = element.y;
|
|
534
|
+
}
|
|
535
|
+
if ("hole_offset_x" in element && typeof element.hole_offset_x === "number") {
|
|
536
|
+
holeOffsetX = element.hole_offset_x;
|
|
537
|
+
}
|
|
538
|
+
if ("hole_offset_y" in element && typeof element.hole_offset_y === "number") {
|
|
539
|
+
holeOffsetY = element.hole_offset_y;
|
|
540
|
+
}
|
|
541
|
+
return {
|
|
542
|
+
x: x + holeOffsetX,
|
|
543
|
+
y: y + holeOffsetY
|
|
544
|
+
};
|
|
545
|
+
};
|
|
546
|
+
var getYMultiplier = (flip_y_axis) => {
|
|
547
|
+
if (flip_y_axis) {
|
|
548
|
+
return -1;
|
|
549
|
+
}
|
|
550
|
+
return 1;
|
|
551
|
+
};
|
|
552
|
+
var getSlotEndpoints = ({
|
|
553
|
+
holeWidth,
|
|
554
|
+
holeHeight,
|
|
555
|
+
slotHalfLength
|
|
556
|
+
}) => {
|
|
557
|
+
let startRelative = { x: 0, y: -slotHalfLength };
|
|
558
|
+
let endRelative = { x: 0, y: slotHalfLength };
|
|
559
|
+
if (holeWidth >= holeHeight) {
|
|
560
|
+
startRelative = { x: -slotHalfLength, y: 0 };
|
|
561
|
+
endRelative = { x: slotHalfLength, y: 0 };
|
|
562
|
+
}
|
|
563
|
+
return { startRelative, endRelative };
|
|
564
|
+
};
|
|
565
|
+
var getHoleRotationDegrees = (element) => {
|
|
566
|
+
if ("hole_ccw_rotation" in element && typeof element.hole_ccw_rotation === "number") {
|
|
567
|
+
return element.hole_ccw_rotation;
|
|
568
|
+
}
|
|
569
|
+
if ("ccw_rotation" in element && typeof element.ccw_rotation === "number") {
|
|
570
|
+
return element.ccw_rotation;
|
|
571
|
+
}
|
|
572
|
+
return 0;
|
|
573
|
+
};
|
|
429
574
|
var convertSoupToExcellonDrillCommands = ({
|
|
430
575
|
circuitJson,
|
|
431
576
|
is_plated,
|
|
@@ -434,7 +579,10 @@ var convertSoupToExcellonDrillCommands = ({
|
|
|
434
579
|
}) => {
|
|
435
580
|
const builder = excellonDrill();
|
|
436
581
|
const layerCount = getLayerCount(circuitJson);
|
|
437
|
-
const
|
|
582
|
+
const drillElements = getDrillElements({
|
|
583
|
+
circuitJson,
|
|
584
|
+
is_plated
|
|
585
|
+
});
|
|
438
586
|
builder.add("M48", {});
|
|
439
587
|
const date_str = (/* @__PURE__ */ new Date()).toISOString();
|
|
440
588
|
builder.add("header_comment", {
|
|
@@ -457,8 +605,8 @@ var convertSoupToExcellonDrillCommands = ({
|
|
|
457
605
|
}).add("FMAT", { format: 2 }).add("unit_format", { unit: "METRIC", lz: null });
|
|
458
606
|
let tool_counter = 10;
|
|
459
607
|
const diameterToToolNumber = {};
|
|
460
|
-
for (const element of
|
|
461
|
-
if (!shouldIncludeElement({
|
|
608
|
+
for (const element of drillElements) {
|
|
609
|
+
if (element.type !== "pcb_cutout" && !shouldIncludeElement({
|
|
462
610
|
element,
|
|
463
611
|
is_plated,
|
|
464
612
|
layer_span,
|
|
@@ -469,7 +617,7 @@ var convertSoupToExcellonDrillCommands = ({
|
|
|
469
617
|
if (is_plated && element.type === "pcb_hole") {
|
|
470
618
|
continue;
|
|
471
619
|
}
|
|
472
|
-
const holeDiameter =
|
|
620
|
+
const holeDiameter = getHoleDiameter(element);
|
|
473
621
|
if (!holeDiameter) continue;
|
|
474
622
|
if (!diameterToToolNumber[holeDiameter]) {
|
|
475
623
|
builder.add("aper_function_header", {
|
|
@@ -488,9 +636,9 @@ var convertSoupToExcellonDrillCommands = ({
|
|
|
488
636
|
builder.add("G05", {});
|
|
489
637
|
for (let i = 10; i < tool_counter; i++) {
|
|
490
638
|
builder.add("use_tool", { tool_number: i });
|
|
491
|
-
for (const element of
|
|
492
|
-
if (element.type === "pcb_plated_hole" || element.type === "pcb_hole" || element.type === "pcb_via") {
|
|
493
|
-
if (!shouldIncludeElement({
|
|
639
|
+
for (const element of drillElements) {
|
|
640
|
+
if (element.type === "pcb_plated_hole" || element.type === "pcb_hole" || element.type === "pcb_via" || element.type === "pcb_cutout") {
|
|
641
|
+
if (element.type !== "pcb_cutout" && !shouldIncludeElement({
|
|
494
642
|
element,
|
|
495
643
|
is_plated,
|
|
496
644
|
layer_span,
|
|
@@ -501,17 +649,14 @@ var convertSoupToExcellonDrillCommands = ({
|
|
|
501
649
|
if (is_plated && element.type === "pcb_hole") {
|
|
502
650
|
continue;
|
|
503
651
|
}
|
|
504
|
-
const holeDiameter =
|
|
652
|
+
const holeDiameter = getHoleDiameter(element);
|
|
505
653
|
if (!holeDiameter || diameterToToolNumber[holeDiameter] !== i) {
|
|
506
654
|
continue;
|
|
507
655
|
}
|
|
508
|
-
const
|
|
509
|
-
const
|
|
510
|
-
const
|
|
511
|
-
const
|
|
512
|
-
const centerX = elementX + offsetX;
|
|
513
|
-
const centerY = elementY + offsetY;
|
|
514
|
-
const yMultiplier = flip_y_axis ? -1 : 1;
|
|
656
|
+
const drillCenter = getDrillCenter(element);
|
|
657
|
+
const centerX = drillCenter.x;
|
|
658
|
+
const centerY = drillCenter.y;
|
|
659
|
+
const yMultiplier = getYMultiplier(flip_y_axis);
|
|
515
660
|
if ("hole_width" in element && typeof element.hole_width === "number" && "hole_height" in element && typeof element.hole_height === "number") {
|
|
516
661
|
const holeWidth = element.hole_width;
|
|
517
662
|
const holeHeight = element.hole_height;
|
|
@@ -524,14 +669,16 @@ var convertSoupToExcellonDrillCommands = ({
|
|
|
524
669
|
});
|
|
525
670
|
continue;
|
|
526
671
|
}
|
|
527
|
-
const rotationDegrees =
|
|
672
|
+
const rotationDegrees = getHoleRotationDegrees(element);
|
|
528
673
|
const rotationRadians = rotationDegrees * Math.PI / 180;
|
|
529
674
|
const cosTheta = Math.cos(rotationRadians);
|
|
530
675
|
const sinTheta = Math.sin(rotationRadians);
|
|
531
|
-
const isWidthMajor = holeWidth >= holeHeight;
|
|
532
676
|
const slotHalfLength = (maxDim - minDim) / 2;
|
|
533
|
-
const
|
|
534
|
-
|
|
677
|
+
const { startRelative, endRelative } = getSlotEndpoints({
|
|
678
|
+
holeWidth,
|
|
679
|
+
holeHeight,
|
|
680
|
+
slotHalfLength
|
|
681
|
+
});
|
|
535
682
|
const rotatePoint = ({ x, y }) => ({
|
|
536
683
|
x: x * cosTheta - y * sinTheta,
|
|
537
684
|
y: x * sinTheta + y * cosTheta
|
|
@@ -600,12 +747,10 @@ var convertSoupToExcellonDrillCommandLayers = ({
|
|
|
600
747
|
])
|
|
601
748
|
);
|
|
602
749
|
const hasNonPlatedDrill = circuitJson.some((element) => {
|
|
603
|
-
|
|
604
|
-
return false;
|
|
605
|
-
}
|
|
606
|
-
return hasDrillGeometry(element);
|
|
750
|
+
return element.type === "pcb_hole" && hasDrillGeometry(element);
|
|
607
751
|
});
|
|
608
|
-
|
|
752
|
+
const hasInternalCircularCutoutDrill = getInternalCircularCutoutDrills(circuitJson).length > 0;
|
|
753
|
+
if (!hasNonPlatedDrill && !hasInternalCircularCutoutDrill) {
|
|
609
754
|
return platedDrillLayers;
|
|
610
755
|
}
|
|
611
756
|
return {
|
|
@@ -1337,6 +1482,7 @@ import {
|
|
|
1337
1482
|
rotate,
|
|
1338
1483
|
translate
|
|
1339
1484
|
} from "transformation-matrix";
|
|
1485
|
+
import { getBoundFromCenteredRect as getBoundFromCenteredRect2 } from "@tscircuit/math-utils";
|
|
1340
1486
|
var SILKSCREEN_SHAPE_TYPES = [
|
|
1341
1487
|
"pcb_silkscreen_line",
|
|
1342
1488
|
"pcb_silkscreen_rect",
|
|
@@ -1368,11 +1514,12 @@ var rectangleRoute = (center, width, height, cornerRadius, ccwDegrees = 0) => {
|
|
|
1368
1514
|
const halfH = height / 2;
|
|
1369
1515
|
const route = [];
|
|
1370
1516
|
if (cornerRadius <= 0) {
|
|
1517
|
+
const bounds = getBoundFromCenteredRect2({ center, width, height });
|
|
1371
1518
|
route.push(
|
|
1372
|
-
{ x:
|
|
1373
|
-
{ x:
|
|
1374
|
-
{ x:
|
|
1375
|
-
{ x:
|
|
1519
|
+
{ x: bounds.minX, y: bounds.minY },
|
|
1520
|
+
{ x: bounds.maxX, y: bounds.minY },
|
|
1521
|
+
{ x: bounds.maxX, y: bounds.maxY },
|
|
1522
|
+
{ x: bounds.minX, y: bounds.maxY }
|
|
1376
1523
|
);
|
|
1377
1524
|
} else {
|
|
1378
1525
|
const insetW = halfW - cornerRadius;
|
|
@@ -1481,23 +1628,19 @@ function getAllTraceWidths(soup) {
|
|
|
1481
1628
|
}
|
|
1482
1629
|
}
|
|
1483
1630
|
}
|
|
1484
|
-
return
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
inner5: Array.from(widths.inner5 || []),
|
|
1491
|
-
inner6: Array.from(widths.inner6 || []),
|
|
1492
|
-
bottom: Array.from(widths.bottom || [])
|
|
1493
|
-
};
|
|
1631
|
+
return Object.fromEntries(
|
|
1632
|
+
Object.entries(widths).map(([layer, layerWidths]) => [
|
|
1633
|
+
layer,
|
|
1634
|
+
Array.from(layerWidths)
|
|
1635
|
+
])
|
|
1636
|
+
);
|
|
1494
1637
|
}
|
|
1495
1638
|
|
|
1496
1639
|
// src/gerber/convert-soup-to-gerber-commands/defineAperturesForLayer.ts
|
|
1497
1640
|
var getLayerRefFromGerberLayerName = (glayer_name) => {
|
|
1498
1641
|
if (glayer_name.startsWith("F_")) return "top";
|
|
1499
1642
|
if (glayer_name.startsWith("B_")) return "bottom";
|
|
1500
|
-
const innerLayerMatch = glayer_name.match(/^In(
|
|
1643
|
+
const innerLayerMatch = glayer_name.match(/^In(\d+)_/);
|
|
1501
1644
|
if (innerLayerMatch) return `inner${innerLayerMatch[1]}`;
|
|
1502
1645
|
throw new Error(`Could not infer layer ref from ${glayer_name}`);
|
|
1503
1646
|
};
|
|
@@ -1543,7 +1686,7 @@ function defineAperturesForLayer({
|
|
|
1543
1686
|
);
|
|
1544
1687
|
const traceWidths = getAllTraceWidths(circuitJson);
|
|
1545
1688
|
const layerRef = getLayerRefFromGerberLayerName(glayer_name);
|
|
1546
|
-
for (const width of traceWidths[layerRef]) {
|
|
1689
|
+
for (const width of traceWidths[layerRef] ?? []) {
|
|
1547
1690
|
glayer.push(
|
|
1548
1691
|
...gerberBuilder().add("define_aperture_template", {
|
|
1549
1692
|
aperture_number: getNextApertureNumber(),
|
|
@@ -2095,7 +2238,7 @@ var findApertureNumber = (glayer, search_params) => {
|
|
|
2095
2238
|
// package.json
|
|
2096
2239
|
var package_default = {
|
|
2097
2240
|
name: "circuit-json-to-gerber",
|
|
2098
|
-
version: "0.0.
|
|
2241
|
+
version: "0.0.83",
|
|
2099
2242
|
main: "dist/index.js",
|
|
2100
2243
|
type: "module",
|
|
2101
2244
|
scripts: {
|
|
@@ -2120,8 +2263,9 @@ var package_default = {
|
|
|
2120
2263
|
"@types/react-dom": "^19.1.5",
|
|
2121
2264
|
archiver: "^7.0.1",
|
|
2122
2265
|
"bun-match-svg": "^0.0.13",
|
|
2123
|
-
"circuit-json": "^0.0.
|
|
2266
|
+
"circuit-json": "^0.0.450",
|
|
2124
2267
|
commander: "^12.1.0",
|
|
2268
|
+
"format-si-unit": "^0.0.7",
|
|
2125
2269
|
"gerber-to-svg": "^4.2.8",
|
|
2126
2270
|
gerberts: "^0.0.3",
|
|
2127
2271
|
jszip: "^3.10.1",
|
|
@@ -2139,6 +2283,7 @@ var package_default = {
|
|
|
2139
2283
|
dependencies: {
|
|
2140
2284
|
"@tscircuit/alphabet": "^0.0.25",
|
|
2141
2285
|
"fast-json-stable-stringify": "^2.1.0",
|
|
2286
|
+
"polygon-clipping": "^0.15.7",
|
|
2142
2287
|
"transformation-matrix": "^3.0.0"
|
|
2143
2288
|
}
|
|
2144
2289
|
};
|
|
@@ -2201,15 +2346,9 @@ var getCommandHeaders = (opts) => {
|
|
|
2201
2346
|
};
|
|
2202
2347
|
|
|
2203
2348
|
// src/gerber/convert-soup-to-gerber-commands/getGerberLayerName.ts
|
|
2204
|
-
var
|
|
2349
|
+
var outerLayerRefToGerberPrefix = {
|
|
2205
2350
|
top: "F_",
|
|
2206
|
-
bottom: "B_"
|
|
2207
|
-
inner1: "In1_",
|
|
2208
|
-
inner2: "In2_",
|
|
2209
|
-
inner3: "In3_",
|
|
2210
|
-
inner4: "In4_",
|
|
2211
|
-
inner5: "In5_",
|
|
2212
|
-
inner6: "In6_"
|
|
2351
|
+
bottom: "B_"
|
|
2213
2352
|
};
|
|
2214
2353
|
var layerTypeToGerberSuffix = {
|
|
2215
2354
|
copper: "Cu",
|
|
@@ -2224,7 +2363,8 @@ var getGerberLayerName = (layer_ref, layer_type) => {
|
|
|
2224
2363
|
if (layer_ref.startsWith("inner") && layer_type !== "copper") {
|
|
2225
2364
|
throw new Error(`Inner layer ${layer_ref} only supports copper gerbers`);
|
|
2226
2365
|
}
|
|
2227
|
-
|
|
2366
|
+
const prefix = layer_ref.startsWith("inner") ? `In${layer_ref.slice("inner".length)}_` : outerLayerRefToGerberPrefix[layer_ref];
|
|
2367
|
+
return `${prefix}${layerTypeToGerberSuffix[layer_type]}`;
|
|
2228
2368
|
};
|
|
2229
2369
|
|
|
2230
2370
|
// src/gerber/convert-soup-to-gerber-commands/offsetPolygonOutline.ts
|
|
@@ -2306,11 +2446,11 @@ var offsetPolygonOutline = (points, offset) => {
|
|
|
2306
2446
|
// src/gerber/convert-soup-to-gerber-commands/index.ts
|
|
2307
2447
|
import { lineAlphabet } from "@tscircuit/alphabet";
|
|
2308
2448
|
import {
|
|
2309
|
-
applyToPoint as
|
|
2310
|
-
compose as
|
|
2311
|
-
identity as
|
|
2312
|
-
rotate as
|
|
2313
|
-
translate as
|
|
2449
|
+
applyToPoint as applyToPoint4,
|
|
2450
|
+
compose as compose4,
|
|
2451
|
+
identity as identity3,
|
|
2452
|
+
rotate as rotate4,
|
|
2453
|
+
translate as translate4
|
|
2314
2454
|
} from "transformation-matrix";
|
|
2315
2455
|
|
|
2316
2456
|
// src/gerber/convert-soup-to-gerber-commands/fabricationLayerRefs.ts
|
|
@@ -2324,15 +2464,18 @@ var getFabricationLayerRefs = (circuitJson) => outerLayerRefs.filter(
|
|
|
2324
2464
|
);
|
|
2325
2465
|
|
|
2326
2466
|
// src/gerber/convert-soup-to-gerber-commands/getFabRectPoints.ts
|
|
2467
|
+
import { getBoundFromCenteredRect as getBoundFromCenteredRect3 } from "@tscircuit/math-utils";
|
|
2327
2468
|
var getFabRectPoints = (element) => {
|
|
2328
|
-
const
|
|
2329
|
-
|
|
2330
|
-
|
|
2469
|
+
const bounds = getBoundFromCenteredRect3({
|
|
2470
|
+
center: element.center,
|
|
2471
|
+
width: element.width,
|
|
2472
|
+
height: element.height
|
|
2473
|
+
});
|
|
2331
2474
|
return [
|
|
2332
|
-
{ x:
|
|
2333
|
-
{ x:
|
|
2334
|
-
{ x:
|
|
2335
|
-
{ x:
|
|
2475
|
+
{ x: bounds.minX, y: bounds.minY },
|
|
2476
|
+
{ x: bounds.maxX, y: bounds.minY },
|
|
2477
|
+
{ x: bounds.maxX, y: bounds.maxY },
|
|
2478
|
+
{ x: bounds.minX, y: bounds.maxY }
|
|
2336
2479
|
];
|
|
2337
2480
|
};
|
|
2338
2481
|
|
|
@@ -2482,10 +2625,357 @@ var renderFabricationDimension = ({
|
|
|
2482
2625
|
};
|
|
2483
2626
|
|
|
2484
2627
|
// src/gerber/convert-soup-to-gerber-commands/index.ts
|
|
2628
|
+
import polygonClipping3 from "polygon-clipping";
|
|
2629
|
+
|
|
2630
|
+
// src/gerber/utils/boardCutoutGeometry.ts
|
|
2631
|
+
import polygonClipping2 from "polygon-clipping";
|
|
2632
|
+
import { applyToPoint as applyToPoint2, compose as compose2, rotate as rotate2, translate as translate2 } from "transformation-matrix";
|
|
2633
|
+
import { getBoundFromCenteredRect as getBoundFromCenteredRect4 } from "@tscircuit/math-utils";
|
|
2634
|
+
var roundedRectCornerArcSegmentCount = 8;
|
|
2635
|
+
var circleCutoutPolygonSegmentCount2 = 64;
|
|
2636
|
+
var getBoardOutlinePolygon = (board) => {
|
|
2637
|
+
if (board.outline && board.outline.length > 2) {
|
|
2638
|
+
return [board.outline.map((point) => [point.x, point.y])];
|
|
2639
|
+
}
|
|
2640
|
+
const bounds = getBoundFromCenteredRect4({
|
|
2641
|
+
center: board.center,
|
|
2642
|
+
width: board.width,
|
|
2643
|
+
height: board.height
|
|
2644
|
+
});
|
|
2645
|
+
return [
|
|
2646
|
+
[
|
|
2647
|
+
[bounds.minX, bounds.minY],
|
|
2648
|
+
[bounds.maxX, bounds.minY],
|
|
2649
|
+
[bounds.maxX, bounds.maxY],
|
|
2650
|
+
[bounds.minX, bounds.maxY]
|
|
2651
|
+
]
|
|
2652
|
+
];
|
|
2653
|
+
};
|
|
2654
|
+
var getBoardOutlinePolygons2 = (circuitJson) => circuitJson.filter((element) => element.type === "pcb_board").map(getBoardOutlinePolygon);
|
|
2655
|
+
var getRectCutoutOutlinePoints = (cutout) => {
|
|
2656
|
+
const { center, width, height, rotation, corner_radius } = cutout;
|
|
2657
|
+
const w = width / 2;
|
|
2658
|
+
const h = height / 2;
|
|
2659
|
+
const r = Math.max(0, Math.min(corner_radius ?? 0, Math.abs(w), Math.abs(h)));
|
|
2660
|
+
let transformMatrix = translate2(center.x, center.y);
|
|
2661
|
+
if (rotation) {
|
|
2662
|
+
transformMatrix = compose2(
|
|
2663
|
+
transformMatrix,
|
|
2664
|
+
rotate2(rotation * Math.PI / 180)
|
|
2665
|
+
);
|
|
2666
|
+
}
|
|
2667
|
+
if (r === 0) {
|
|
2668
|
+
return [
|
|
2669
|
+
{ x: -w, y: h },
|
|
2670
|
+
{ x: w, y: h },
|
|
2671
|
+
{ x: w, y: -h },
|
|
2672
|
+
{ x: -w, y: -h }
|
|
2673
|
+
].map((point) => applyToPoint2(transformMatrix, point));
|
|
2674
|
+
}
|
|
2675
|
+
const points = [];
|
|
2676
|
+
const corners = [
|
|
2677
|
+
{ center: { x: w - r, y: h - r }, start: Math.PI / 2, end: 0 },
|
|
2678
|
+
{ center: { x: w - r, y: -h + r }, start: 0, end: -Math.PI / 2 },
|
|
2679
|
+
{ center: { x: -w + r, y: -h + r }, start: -Math.PI / 2, end: -Math.PI },
|
|
2680
|
+
{ center: { x: -w + r, y: h - r }, start: Math.PI, end: Math.PI / 2 }
|
|
2681
|
+
];
|
|
2682
|
+
for (const corner of corners) {
|
|
2683
|
+
for (let i = 0; i <= roundedRectCornerArcSegmentCount; i++) {
|
|
2684
|
+
const t = i / roundedRectCornerArcSegmentCount;
|
|
2685
|
+
const angle = corner.start + (corner.end - corner.start) * t;
|
|
2686
|
+
points.push({
|
|
2687
|
+
x: corner.center.x + r * Math.cos(angle),
|
|
2688
|
+
y: corner.center.y + r * Math.sin(angle)
|
|
2689
|
+
});
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
return points.map((point) => applyToPoint2(transformMatrix, point));
|
|
2693
|
+
};
|
|
2694
|
+
var getCircleCutoutOutlinePolygon = (cutout) => {
|
|
2695
|
+
const points = [];
|
|
2696
|
+
for (let i = 0; i < circleCutoutPolygonSegmentCount2; i++) {
|
|
2697
|
+
const angle = i / circleCutoutPolygonSegmentCount2 * Math.PI * 2;
|
|
2698
|
+
points.push([
|
|
2699
|
+
cutout.center.x + cutout.radius * Math.cos(angle),
|
|
2700
|
+
cutout.center.y + cutout.radius * Math.sin(angle)
|
|
2701
|
+
]);
|
|
2702
|
+
}
|
|
2703
|
+
return [points];
|
|
2704
|
+
};
|
|
2705
|
+
var getSolidCutoutOutlinePolygon = (cutout) => {
|
|
2706
|
+
if (cutout.shape === "rect") {
|
|
2707
|
+
return [
|
|
2708
|
+
getRectCutoutOutlinePoints(cutout).map((point) => [point.x, point.y])
|
|
2709
|
+
];
|
|
2710
|
+
}
|
|
2711
|
+
if (cutout.shape === "circle") {
|
|
2712
|
+
return getCircleCutoutOutlinePolygon(cutout);
|
|
2713
|
+
}
|
|
2714
|
+
if (cutout.shape === "polygon") {
|
|
2715
|
+
return [cutout.points.map((point) => [point.x, point.y])];
|
|
2716
|
+
}
|
|
2717
|
+
return null;
|
|
2718
|
+
};
|
|
2719
|
+
var doesSolidCutoutOverlapBoardEdge = ({
|
|
2720
|
+
cutout,
|
|
2721
|
+
boardOutlinePolygons
|
|
2722
|
+
}) => {
|
|
2723
|
+
const cutoutOutlinePolygon = getSolidCutoutOutlinePolygon(cutout);
|
|
2724
|
+
if (!cutoutOutlinePolygon) return false;
|
|
2725
|
+
return boardOutlinePolygons.some((boardOutlinePolygon) => {
|
|
2726
|
+
const overlapsBoard = polygonClipping2.intersection(boardOutlinePolygon, cutoutOutlinePolygon).length > 0;
|
|
2727
|
+
const extendsOutsideBoard = polygonClipping2.difference(cutoutOutlinePolygon, boardOutlinePolygon).length > 0;
|
|
2728
|
+
return overlapsBoard && extendsOutsideBoard;
|
|
2729
|
+
});
|
|
2730
|
+
};
|
|
2731
|
+
var isCutoutFullyInternal = ({
|
|
2732
|
+
cutout,
|
|
2733
|
+
boardOutlinePolygons
|
|
2734
|
+
}) => {
|
|
2735
|
+
const cutoutOutlinePolygon = getSolidCutoutOutlinePolygon(cutout);
|
|
2736
|
+
if (!cutoutOutlinePolygon) return false;
|
|
2737
|
+
return boardOutlinePolygons.some(
|
|
2738
|
+
(boardOutlinePolygon) => (
|
|
2739
|
+
// Has overlap with the board interior …
|
|
2740
|
+
polygonClipping2.intersection(boardOutlinePolygon, cutoutOutlinePolygon).length > 0 && // … but does not extend outside it at all.
|
|
2741
|
+
polygonClipping2.difference(cutoutOutlinePolygon, boardOutlinePolygon).length === 0
|
|
2742
|
+
)
|
|
2743
|
+
);
|
|
2744
|
+
};
|
|
2745
|
+
|
|
2746
|
+
// src/gerber/utils/emitCutoutEdgeCuts.ts
|
|
2747
|
+
import {
|
|
2748
|
+
applyToPoint as applyToPoint3,
|
|
2749
|
+
compose as compose3,
|
|
2750
|
+
identity as identity2,
|
|
2751
|
+
rotate as rotate3,
|
|
2752
|
+
translate as translate3
|
|
2753
|
+
} from "transformation-matrix";
|
|
2754
|
+
var emitCutoutEdgeCuts = ({
|
|
2755
|
+
cutout,
|
|
2756
|
+
builder,
|
|
2757
|
+
mfy,
|
|
2758
|
+
drawCw
|
|
2759
|
+
}) => {
|
|
2760
|
+
if (cutout.shape === "circle") {
|
|
2761
|
+
emitCircle({ cutout, builder, mfy, drawCw });
|
|
2762
|
+
} else if (cutout.shape === "rect") {
|
|
2763
|
+
emitRect({ cutout, builder, mfy, drawCw });
|
|
2764
|
+
} else if (cutout.shape === "polygon") {
|
|
2765
|
+
emitPolygon({ cutout, builder, mfy, drawCw });
|
|
2766
|
+
}
|
|
2767
|
+
};
|
|
2768
|
+
var emitCircle = ({
|
|
2769
|
+
cutout,
|
|
2770
|
+
builder,
|
|
2771
|
+
mfy,
|
|
2772
|
+
drawCw
|
|
2773
|
+
}) => {
|
|
2774
|
+
const { center, radius } = cutout;
|
|
2775
|
+
const p1 = { x: center.x + radius, y: center.y };
|
|
2776
|
+
const p2 = { x: center.x - radius, y: center.y };
|
|
2777
|
+
const isYFlipped = mfy(1) < 0;
|
|
2778
|
+
let isClockwise = drawCw;
|
|
2779
|
+
if (isYFlipped) {
|
|
2780
|
+
isClockwise = !isClockwise;
|
|
2781
|
+
}
|
|
2782
|
+
let arcMode = "set_movement_mode_to_counterclockwise_circular";
|
|
2783
|
+
if (isClockwise) {
|
|
2784
|
+
arcMode = "set_movement_mode_to_clockwise_circular";
|
|
2785
|
+
}
|
|
2786
|
+
builder.add("move_operation", { x: p1.x, y: mfy(p1.y) }).add(arcMode, {}).add("plot_operation", { x: p2.x, y: mfy(p2.y), i: -radius, j: 0 }).add("plot_operation", { x: p1.x, y: mfy(p1.y), i: radius, j: 0 }).add("set_movement_mode_to_linear", {});
|
|
2787
|
+
};
|
|
2788
|
+
var emitRect = ({
|
|
2789
|
+
cutout,
|
|
2790
|
+
builder,
|
|
2791
|
+
mfy,
|
|
2792
|
+
drawCw
|
|
2793
|
+
}) => {
|
|
2794
|
+
const { center, width, height, rotation, corner_radius } = cutout;
|
|
2795
|
+
const w = width / 2;
|
|
2796
|
+
const h = height / 2;
|
|
2797
|
+
let cr = 0;
|
|
2798
|
+
if (corner_radius !== void 0) {
|
|
2799
|
+
cr = corner_radius;
|
|
2800
|
+
}
|
|
2801
|
+
const r = Math.max(0, Math.min(cr, Math.abs(w), Math.abs(h)));
|
|
2802
|
+
let rotationTransform = identity2();
|
|
2803
|
+
if (rotation) {
|
|
2804
|
+
rotationTransform = rotate3(rotation * Math.PI / 180);
|
|
2805
|
+
}
|
|
2806
|
+
const transformMatrix = compose3(
|
|
2807
|
+
translate3(center.x, center.y),
|
|
2808
|
+
rotationTransform
|
|
2809
|
+
);
|
|
2810
|
+
if (r > 0) {
|
|
2811
|
+
emitRoundedRect({ w, h, r, transformMatrix, builder, mfy, drawCw });
|
|
2812
|
+
} else {
|
|
2813
|
+
emitSharpRect({ w, h, transformMatrix, builder, mfy, drawCw });
|
|
2814
|
+
}
|
|
2815
|
+
};
|
|
2816
|
+
var emitSharpRect = ({
|
|
2817
|
+
w,
|
|
2818
|
+
h,
|
|
2819
|
+
transformMatrix,
|
|
2820
|
+
builder,
|
|
2821
|
+
mfy,
|
|
2822
|
+
drawCw
|
|
2823
|
+
}) => {
|
|
2824
|
+
const cwPoints = [
|
|
2825
|
+
{ x: -w, y: h },
|
|
2826
|
+
{ x: w, y: h },
|
|
2827
|
+
{ x: w, y: -h },
|
|
2828
|
+
{ x: -w, y: -h }
|
|
2829
|
+
];
|
|
2830
|
+
const isYFlipped = mfy(1) < 0;
|
|
2831
|
+
let shouldReverse = !drawCw;
|
|
2832
|
+
if (isYFlipped) {
|
|
2833
|
+
shouldReverse = !shouldReverse;
|
|
2834
|
+
}
|
|
2835
|
+
let pts = cwPoints;
|
|
2836
|
+
if (shouldReverse) {
|
|
2837
|
+
pts = [...cwPoints].reverse();
|
|
2838
|
+
}
|
|
2839
|
+
const tpts = pts.map((p) => applyToPoint3(transformMatrix, p));
|
|
2840
|
+
builder.add("move_operation", { x: tpts[0].x, y: mfy(tpts[0].y) });
|
|
2841
|
+
for (let i = 1; i < tpts.length; i++) {
|
|
2842
|
+
builder.add("plot_operation", { x: tpts[i].x, y: mfy(tpts[i].y) });
|
|
2843
|
+
}
|
|
2844
|
+
builder.add("plot_operation", { x: tpts[0].x, y: mfy(tpts[0].y) });
|
|
2845
|
+
};
|
|
2846
|
+
var emitRoundedRect = ({
|
|
2847
|
+
w,
|
|
2848
|
+
h,
|
|
2849
|
+
r,
|
|
2850
|
+
transformMatrix,
|
|
2851
|
+
builder,
|
|
2852
|
+
mfy,
|
|
2853
|
+
drawCw
|
|
2854
|
+
}) => {
|
|
2855
|
+
const isYFlipped = mfy(1) < 0;
|
|
2856
|
+
let isClockwise = drawCw;
|
|
2857
|
+
if (isYFlipped) {
|
|
2858
|
+
isClockwise = !isClockwise;
|
|
2859
|
+
}
|
|
2860
|
+
let arcMode = "set_movement_mode_to_counterclockwise_circular";
|
|
2861
|
+
if (isClockwise) {
|
|
2862
|
+
arcMode = "set_movement_mode_to_clockwise_circular";
|
|
2863
|
+
}
|
|
2864
|
+
let currentPoint;
|
|
2865
|
+
const addLine = (pt) => {
|
|
2866
|
+
const tpt = applyToPoint3(transformMatrix, pt);
|
|
2867
|
+
builder.add("plot_operation", { x: tpt.x, y: mfy(tpt.y) });
|
|
2868
|
+
currentPoint = tpt;
|
|
2869
|
+
};
|
|
2870
|
+
const addArc = (endPt, centerPt) => {
|
|
2871
|
+
const tEnd = applyToPoint3(transformMatrix, endPt);
|
|
2872
|
+
const tCenter = applyToPoint3(transformMatrix, centerPt);
|
|
2873
|
+
builder.add(arcMode, {}).add("plot_operation", {
|
|
2874
|
+
x: tEnd.x,
|
|
2875
|
+
y: mfy(tEnd.y),
|
|
2876
|
+
i: tCenter.x - currentPoint.x,
|
|
2877
|
+
j: mfy(tCenter.y) - mfy(currentPoint.y)
|
|
2878
|
+
}).add("set_movement_mode_to_linear", {});
|
|
2879
|
+
currentPoint = tEnd;
|
|
2880
|
+
};
|
|
2881
|
+
if (isClockwise) {
|
|
2882
|
+
const cwSegments = [
|
|
2883
|
+
{
|
|
2884
|
+
lineTo: { x: w - r, y: h },
|
|
2885
|
+
arcEnd: { x: w, y: h - r },
|
|
2886
|
+
arcCenter: { x: w - r, y: h - r }
|
|
2887
|
+
},
|
|
2888
|
+
{
|
|
2889
|
+
lineTo: { x: w, y: -h + r },
|
|
2890
|
+
arcEnd: { x: w - r, y: -h },
|
|
2891
|
+
arcCenter: { x: w - r, y: -h + r }
|
|
2892
|
+
},
|
|
2893
|
+
{
|
|
2894
|
+
lineTo: { x: -w + r, y: -h },
|
|
2895
|
+
arcEnd: { x: -w, y: -h + r },
|
|
2896
|
+
arcCenter: { x: -w + r, y: -h + r }
|
|
2897
|
+
},
|
|
2898
|
+
{
|
|
2899
|
+
lineTo: { x: -w, y: h - r },
|
|
2900
|
+
arcEnd: { x: -w + r, y: h },
|
|
2901
|
+
arcCenter: { x: -w + r, y: h - r }
|
|
2902
|
+
}
|
|
2903
|
+
];
|
|
2904
|
+
const startPt = { x: -w + r, y: h };
|
|
2905
|
+
const tStart = applyToPoint3(transformMatrix, startPt);
|
|
2906
|
+
builder.add("move_operation", { x: tStart.x, y: mfy(tStart.y) });
|
|
2907
|
+
currentPoint = tStart;
|
|
2908
|
+
for (const seg of cwSegments) {
|
|
2909
|
+
addLine(seg.lineTo);
|
|
2910
|
+
addArc(seg.arcEnd, seg.arcCenter);
|
|
2911
|
+
}
|
|
2912
|
+
} else {
|
|
2913
|
+
const startPt = { x: w - r, y: h };
|
|
2914
|
+
const tStart = applyToPoint3(transformMatrix, startPt);
|
|
2915
|
+
builder.add("move_operation", { x: tStart.x, y: mfy(tStart.y) });
|
|
2916
|
+
currentPoint = tStart;
|
|
2917
|
+
addLine({ x: -w + r, y: h });
|
|
2918
|
+
addArc({ x: -w, y: h - r }, { x: -w + r, y: h - r });
|
|
2919
|
+
addLine({ x: -w, y: -h + r });
|
|
2920
|
+
addArc({ x: -w + r, y: -h }, { x: -w + r, y: -h + r });
|
|
2921
|
+
addLine({ x: w - r, y: -h });
|
|
2922
|
+
addArc({ x: w, y: -h + r }, { x: w - r, y: -h + r });
|
|
2923
|
+
addLine({ x: w, y: h - r });
|
|
2924
|
+
addArc({ x: w - r, y: h }, { x: w - r, y: h - r });
|
|
2925
|
+
}
|
|
2926
|
+
};
|
|
2927
|
+
var emitPolygon = ({
|
|
2928
|
+
cutout,
|
|
2929
|
+
builder,
|
|
2930
|
+
mfy,
|
|
2931
|
+
drawCw
|
|
2932
|
+
}) => {
|
|
2933
|
+
const points = cutout.points;
|
|
2934
|
+
if (points.length === 0) return;
|
|
2935
|
+
const isYFlipped = mfy(1) < 0;
|
|
2936
|
+
let shouldReverse = !drawCw;
|
|
2937
|
+
if (isYFlipped) {
|
|
2938
|
+
shouldReverse = !shouldReverse;
|
|
2939
|
+
}
|
|
2940
|
+
let ordered = points;
|
|
2941
|
+
if (shouldReverse) {
|
|
2942
|
+
ordered = [...points].reverse();
|
|
2943
|
+
}
|
|
2944
|
+
builder.add("move_operation", { x: ordered[0].x, y: mfy(ordered[0].y) });
|
|
2945
|
+
for (let i = 1; i < ordered.length; i++) {
|
|
2946
|
+
builder.add("plot_operation", { x: ordered[i].x, y: mfy(ordered[i].y) });
|
|
2947
|
+
}
|
|
2948
|
+
builder.add("plot_operation", { x: ordered[0].x, y: mfy(ordered[0].y) });
|
|
2949
|
+
};
|
|
2950
|
+
|
|
2951
|
+
// src/gerber/convert-soup-to-gerber-commands/index.ts
|
|
2952
|
+
var closeRing = (ring) => {
|
|
2953
|
+
const first = ring[0];
|
|
2954
|
+
const last = ring[ring.length - 1];
|
|
2955
|
+
if (!first || !last) return ring;
|
|
2956
|
+
if (first[0] === last[0] && first[1] === last[1]) return ring;
|
|
2957
|
+
return [...ring, first];
|
|
2958
|
+
};
|
|
2959
|
+
var emitEdgeCutRing = (gerberBuild, ring, mfy) => {
|
|
2960
|
+
const closedRing = closeRing(ring);
|
|
2961
|
+
const firstPoint = closedRing[0];
|
|
2962
|
+
if (!firstPoint || closedRing.length < 4) return;
|
|
2963
|
+
gerberBuild.add("move_operation", {
|
|
2964
|
+
x: firstPoint[0],
|
|
2965
|
+
y: mfy(firstPoint[1])
|
|
2966
|
+
});
|
|
2967
|
+
for (let i = 1; i < closedRing.length; i++) {
|
|
2968
|
+
const point = closedRing[i];
|
|
2969
|
+
gerberBuild.add("plot_operation", {
|
|
2970
|
+
x: point[0],
|
|
2971
|
+
y: mfy(point[1])
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
};
|
|
2485
2975
|
var getLayerCount2 = (circuitJson) => {
|
|
2486
2976
|
const board = circuitJson.find((element) => element.type === "pcb_board");
|
|
2487
2977
|
const numLayers = board?.num_layers ?? 2;
|
|
2488
|
-
return Math.max(2, Math.min(
|
|
2978
|
+
return Math.max(2, Math.min(10, numLayers));
|
|
2489
2979
|
};
|
|
2490
2980
|
var getInnerLayerRefs = (layerCount) => Array.from({ length: layerCount - 2 }, (_, i) => `inner${i + 1}`);
|
|
2491
2981
|
var getGerberInnerLayerName = (layerRef) => {
|
|
@@ -2497,6 +2987,7 @@ var getGerberInnerLayerName = (layerRef) => {
|
|
|
2497
2987
|
var convertSoupToGerberCommands = (circuitJson, opts = {}) => {
|
|
2498
2988
|
opts.flip_y_axis ??= false;
|
|
2499
2989
|
const hasPanel = circuitJson.some((e) => e.type === "pcb_panel");
|
|
2990
|
+
const hasBoard = circuitJson.some((e) => e.type === "pcb_board");
|
|
2500
2991
|
const layerCount = getLayerCount2(circuitJson);
|
|
2501
2992
|
const innerLayerRefs = getInnerLayerRefs(layerCount);
|
|
2502
2993
|
const copperLayerRefs = ["top", ...innerLayerRefs, "bottom"];
|
|
@@ -2586,7 +3077,16 @@ var convertSoupToGerberCommands = (circuitJson, opts = {}) => {
|
|
|
2586
3077
|
//mm
|
|
2587
3078
|
}).build()
|
|
2588
3079
|
);
|
|
2589
|
-
const mfy = (y) =>
|
|
3080
|
+
const mfy = (y) => {
|
|
3081
|
+
if (opts.flip_y_axis) {
|
|
3082
|
+
return -y;
|
|
3083
|
+
}
|
|
3084
|
+
return y;
|
|
3085
|
+
};
|
|
3086
|
+
const boardOutlinePolygons = getBoardOutlinePolygons2(circuitJson);
|
|
3087
|
+
const solidCutoutPolygons = circuitJson.filter((element) => element.type === "pcb_cutout").filter(
|
|
3088
|
+
(cutout) => doesSolidCutoutOverlapBoardEdge({ cutout, boardOutlinePolygons })
|
|
3089
|
+
).map(getSolidCutoutOutlinePolygon).filter((polygon) => polygon !== null);
|
|
2590
3090
|
const renderPillFlash = ({
|
|
2591
3091
|
glayer,
|
|
2592
3092
|
x,
|
|
@@ -2739,18 +3239,18 @@ var convertSoupToGerberCommands = (circuitJson, opts = {}) => {
|
|
|
2739
3239
|
const shouldMirror = element.is_mirrored !== void 0 ? element.is_mirrored : element.layer === "bottom";
|
|
2740
3240
|
if (shouldMirror) {
|
|
2741
3241
|
transforms.push(
|
|
2742
|
-
|
|
3242
|
+
translate4(cx, cy),
|
|
2743
3243
|
{ a: -1, b: 0, c: 0, d: 1, e: 0, f: 0 },
|
|
2744
|
-
|
|
3244
|
+
translate4(-cx, -cy)
|
|
2745
3245
|
);
|
|
2746
3246
|
rotation = -rotation;
|
|
2747
3247
|
}
|
|
2748
3248
|
if (rotation) {
|
|
2749
3249
|
const rad = rotation * Math.PI / 180;
|
|
2750
|
-
transforms.push(
|
|
3250
|
+
transforms.push(translate4(cx, cy), rotate4(rad), translate4(-cx, -cy));
|
|
2751
3251
|
}
|
|
2752
|
-
const transformMatrix = transforms.length > 0 ?
|
|
2753
|
-
const applyTransform = (point) => transformMatrix ?
|
|
3252
|
+
const transformMatrix = transforms.length > 0 ? compose4(...transforms) : void 0;
|
|
3253
|
+
const applyTransform = (point) => transformMatrix ? applyToPoint4(transformMatrix, point) : point;
|
|
2754
3254
|
if (element.is_knockout) {
|
|
2755
3255
|
const padding = element.knockout_padding ?? {
|
|
2756
3256
|
left: 0.2,
|
|
@@ -2939,17 +3439,17 @@ var convertSoupToGerberCommands = (circuitJson, opts = {}) => {
|
|
|
2939
3439
|
{ x: -w, y: -h }
|
|
2940
3440
|
// Bottom-left
|
|
2941
3441
|
];
|
|
2942
|
-
let transformMatrix =
|
|
3442
|
+
let transformMatrix = identity3();
|
|
2943
3443
|
if (rotation) {
|
|
2944
3444
|
const angle_rad = rotation * Math.PI / 180;
|
|
2945
|
-
transformMatrix =
|
|
3445
|
+
transformMatrix = rotate4(angle_rad);
|
|
2946
3446
|
}
|
|
2947
|
-
transformMatrix =
|
|
2948
|
-
|
|
3447
|
+
transformMatrix = compose4(
|
|
3448
|
+
translate4(center.x, center.y),
|
|
2949
3449
|
transformMatrix
|
|
2950
3450
|
);
|
|
2951
3451
|
const transformedPoints = points.map(
|
|
2952
|
-
(p) =>
|
|
3452
|
+
(p) => applyToPoint4(transformMatrix, p)
|
|
2953
3453
|
);
|
|
2954
3454
|
const regionApertureNumber = getRegionApertureNumber(copper_glayer);
|
|
2955
3455
|
const rect_builder = gerberBuilder().add("select_aperture", { aperture_number: regionApertureNumber }).add("start_region_statement", {});
|
|
@@ -3505,36 +4005,42 @@ var convertSoupToGerberCommands = (circuitJson, opts = {}) => {
|
|
|
3505
4005
|
aperture_number: 10
|
|
3506
4006
|
});
|
|
3507
4007
|
if (outline && outline.length > 2) {
|
|
3508
|
-
const
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
4008
|
+
const boardPolygon = [
|
|
4009
|
+
outline.map((point) => [point.x, point.y])
|
|
4010
|
+
];
|
|
4011
|
+
let boardGeometry = [boardPolygon];
|
|
4012
|
+
if (solidCutoutPolygons.length > 0) {
|
|
4013
|
+
boardGeometry = polygonClipping3.difference(
|
|
4014
|
+
boardPolygon,
|
|
4015
|
+
...solidCutoutPolygons
|
|
4016
|
+
);
|
|
3516
4017
|
}
|
|
3517
|
-
const
|
|
3518
|
-
|
|
3519
|
-
|
|
4018
|
+
for (const polygon of boardGeometry) {
|
|
4019
|
+
for (const ring of polygon) {
|
|
4020
|
+
emitEdgeCutRing(gerberBuild, ring, mfy);
|
|
4021
|
+
}
|
|
3520
4022
|
}
|
|
3521
4023
|
} else {
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
4024
|
+
const boardPolygon = [
|
|
4025
|
+
[
|
|
4026
|
+
[center.x - width / 2, center.y - height / 2],
|
|
4027
|
+
[center.x + width / 2, center.y - height / 2],
|
|
4028
|
+
[center.x + width / 2, center.y + height / 2],
|
|
4029
|
+
[center.x - width / 2, center.y + height / 2]
|
|
4030
|
+
]
|
|
4031
|
+
];
|
|
4032
|
+
let boardGeometry = [boardPolygon];
|
|
4033
|
+
if (solidCutoutPolygons.length > 0) {
|
|
4034
|
+
boardGeometry = polygonClipping3.difference(
|
|
4035
|
+
boardPolygon,
|
|
4036
|
+
...solidCutoutPolygons
|
|
4037
|
+
);
|
|
4038
|
+
}
|
|
4039
|
+
for (const polygon of boardGeometry) {
|
|
4040
|
+
for (const ring of polygon) {
|
|
4041
|
+
emitEdgeCutRing(gerberBuild, ring, mfy);
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
3538
4044
|
}
|
|
3539
4045
|
glayer.push(...gerberBuild.build());
|
|
3540
4046
|
} else if (element.type === "pcb_panel" && layer === "edgecut") {
|
|
@@ -3562,146 +4068,26 @@ var convertSoupToGerberCommands = (circuitJson, opts = {}) => {
|
|
|
3562
4068
|
glayer.push(...gerberBuild.build());
|
|
3563
4069
|
} else if (element.type === "pcb_cutout") {
|
|
3564
4070
|
if (layer === "edgecut") {
|
|
4071
|
+
if (hasBoard && !hasPanel && doesSolidCutoutOverlapBoardEdge({
|
|
4072
|
+
cutout: element,
|
|
4073
|
+
boardOutlinePolygons
|
|
4074
|
+
})) {
|
|
4075
|
+
continue;
|
|
4076
|
+
}
|
|
3565
4077
|
const ec_layer = glayers.Edge_Cuts;
|
|
3566
4078
|
const cutout_builder = gerberBuilder().add("select_aperture", {
|
|
3567
4079
|
aperture_number: 10
|
|
3568
4080
|
});
|
|
3569
|
-
const
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
let transformMatrix2 = identity2();
|
|
3580
|
-
if (rotation) {
|
|
3581
|
-
const angle_rad = rotation * Math.PI / 180;
|
|
3582
|
-
transformMatrix2 = rotate2(angle_rad);
|
|
3583
|
-
}
|
|
3584
|
-
return compose2(translate2(center.x, center.y), transformMatrix2);
|
|
3585
|
-
};
|
|
3586
|
-
const transformMatrix = makeTransformMatrix();
|
|
3587
|
-
if (r > 0) {
|
|
3588
|
-
const startPoint = { x: -w + r, y: h };
|
|
3589
|
-
let currentPoint = applyToPoint2(transformMatrix, startPoint);
|
|
3590
|
-
cutout_builder.add("move_operation", {
|
|
3591
|
-
x: currentPoint.x,
|
|
3592
|
-
y: mfy(currentPoint.y)
|
|
3593
|
-
});
|
|
3594
|
-
const addLine = (point) => {
|
|
3595
|
-
const transformedPoint = applyToPoint2(transformMatrix, point);
|
|
3596
|
-
cutout_builder.add("plot_operation", {
|
|
3597
|
-
x: transformedPoint.x,
|
|
3598
|
-
y: mfy(transformedPoint.y)
|
|
3599
|
-
});
|
|
3600
|
-
currentPoint = transformedPoint;
|
|
3601
|
-
};
|
|
3602
|
-
const addArc = (options) => {
|
|
3603
|
-
const transformedPoint = applyToPoint2(
|
|
3604
|
-
transformMatrix,
|
|
3605
|
-
options.point
|
|
3606
|
-
);
|
|
3607
|
-
const transformedCenter = applyToPoint2(
|
|
3608
|
-
transformMatrix,
|
|
3609
|
-
options.center
|
|
3610
|
-
);
|
|
3611
|
-
cutout_builder.add("set_movement_mode_to_clockwise_circular", {}).add("plot_operation", {
|
|
3612
|
-
x: transformedPoint.x,
|
|
3613
|
-
y: mfy(transformedPoint.y),
|
|
3614
|
-
i: transformedCenter.x - currentPoint.x,
|
|
3615
|
-
j: mfy(transformedCenter.y) - mfy(currentPoint.y)
|
|
3616
|
-
}).add("set_movement_mode_to_linear", {});
|
|
3617
|
-
currentPoint = transformedPoint;
|
|
3618
|
-
};
|
|
3619
|
-
addLine({ x: w - r, y: h });
|
|
3620
|
-
addArc({
|
|
3621
|
-
point: { x: w, y: h - r },
|
|
3622
|
-
center: { x: w - r, y: h - r }
|
|
3623
|
-
});
|
|
3624
|
-
addLine({ x: w, y: -h + r });
|
|
3625
|
-
addArc({
|
|
3626
|
-
point: { x: w - r, y: -h },
|
|
3627
|
-
center: { x: w - r, y: -h + r }
|
|
3628
|
-
});
|
|
3629
|
-
addLine({ x: -w + r, y: -h });
|
|
3630
|
-
addArc({
|
|
3631
|
-
point: { x: -w, y: -h + r },
|
|
3632
|
-
center: { x: -w + r, y: -h + r }
|
|
3633
|
-
});
|
|
3634
|
-
addLine({ x: -w, y: h - r });
|
|
3635
|
-
addArc({
|
|
3636
|
-
point: { x: -w + r, y: h },
|
|
3637
|
-
center: { x: -w + r, y: h - r }
|
|
3638
|
-
});
|
|
3639
|
-
} else {
|
|
3640
|
-
const points = [
|
|
3641
|
-
{ x: -w, y: h },
|
|
3642
|
-
// Top-left
|
|
3643
|
-
{ x: w, y: h },
|
|
3644
|
-
// Top-right
|
|
3645
|
-
{ x: w, y: -h },
|
|
3646
|
-
// Bottom-right
|
|
3647
|
-
{ x: -w, y: -h }
|
|
3648
|
-
// Bottom-left
|
|
3649
|
-
];
|
|
3650
|
-
const transformedPoints = points.map(
|
|
3651
|
-
(p) => applyToPoint2(transformMatrix, p)
|
|
3652
|
-
);
|
|
3653
|
-
cutout_builder.add("move_operation", {
|
|
3654
|
-
x: transformedPoints[0].x,
|
|
3655
|
-
y: mfy(transformedPoints[0].y)
|
|
3656
|
-
});
|
|
3657
|
-
for (let i = 1; i < transformedPoints.length; i++) {
|
|
3658
|
-
cutout_builder.add("plot_operation", {
|
|
3659
|
-
x: transformedPoints[i].x,
|
|
3660
|
-
y: mfy(transformedPoints[i].y)
|
|
3661
|
-
});
|
|
3662
|
-
}
|
|
3663
|
-
cutout_builder.add("plot_operation", {
|
|
3664
|
-
x: transformedPoints[0].x,
|
|
3665
|
-
y: mfy(transformedPoints[0].y)
|
|
3666
|
-
});
|
|
3667
|
-
}
|
|
3668
|
-
} else if (el.shape === "circle") {
|
|
3669
|
-
const { center, radius } = el;
|
|
3670
|
-
const p1 = { x: center.x + radius, y: center.y };
|
|
3671
|
-
const p2 = { x: center.x - radius, y: center.y };
|
|
3672
|
-
cutout_builder.add("move_operation", {
|
|
3673
|
-
x: p1.x,
|
|
3674
|
-
y: mfy(p1.y)
|
|
3675
|
-
}).add("set_movement_mode_to_counterclockwise_circular", {}).add("plot_operation", {
|
|
3676
|
-
x: p2.x,
|
|
3677
|
-
y: mfy(p2.y),
|
|
3678
|
-
i: -radius,
|
|
3679
|
-
j: 0
|
|
3680
|
-
}).add("plot_operation", {
|
|
3681
|
-
x: p1.x,
|
|
3682
|
-
y: mfy(p1.y),
|
|
3683
|
-
i: radius,
|
|
3684
|
-
j: 0
|
|
3685
|
-
}).add("set_movement_mode_to_linear", {});
|
|
3686
|
-
} else if (el.shape === "polygon") {
|
|
3687
|
-
const { points } = el;
|
|
3688
|
-
if (points.length > 0) {
|
|
3689
|
-
cutout_builder.add("move_operation", {
|
|
3690
|
-
x: points[0].x,
|
|
3691
|
-
y: mfy(points[0].y)
|
|
3692
|
-
});
|
|
3693
|
-
for (let i = 1; i < points.length; i++) {
|
|
3694
|
-
cutout_builder.add("plot_operation", {
|
|
3695
|
-
x: points[i].x,
|
|
3696
|
-
y: mfy(points[i].y)
|
|
3697
|
-
});
|
|
3698
|
-
}
|
|
3699
|
-
cutout_builder.add("plot_operation", {
|
|
3700
|
-
x: points[0].x,
|
|
3701
|
-
y: mfy(points[0].y)
|
|
3702
|
-
});
|
|
3703
|
-
}
|
|
3704
|
-
}
|
|
4081
|
+
const drawCw = hasBoard && !hasPanel && isCutoutFullyInternal({
|
|
4082
|
+
cutout: element,
|
|
4083
|
+
boardOutlinePolygons
|
|
4084
|
+
});
|
|
4085
|
+
emitCutoutEdgeCuts({
|
|
4086
|
+
cutout: element,
|
|
4087
|
+
builder: cutout_builder,
|
|
4088
|
+
mfy,
|
|
4089
|
+
drawCw
|
|
4090
|
+
});
|
|
3705
4091
|
ec_layer.push(...cutout_builder.build());
|
|
3706
4092
|
}
|
|
3707
4093
|
}
|
|
@@ -3726,4 +4112,4 @@ export {
|
|
|
3726
4112
|
stringifyGerberCommands,
|
|
3727
4113
|
convertSoupToGerberCommands
|
|
3728
4114
|
};
|
|
3729
|
-
//# sourceMappingURL=chunk-
|
|
4115
|
+
//# sourceMappingURL=chunk-23TRHNW3.js.map
|