circuit-json-to-gerber 0.0.81 → 0.0.83
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-LJRHQISL.js → chunk-46NSLLUV.js} +811 -212
- package/dist/chunk-46NSLLUV.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -1
- package/dist/chunk-LJRHQISL.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 {
|
|
@@ -1329,6 +1474,136 @@ var defineCommonMacros = (glayer) => {
|
|
|
1329
1474
|
);
|
|
1330
1475
|
};
|
|
1331
1476
|
|
|
1477
|
+
// src/gerber/convert-soup-to-gerber-commands/getSilkscreenShapeStroke.ts
|
|
1478
|
+
import {
|
|
1479
|
+
applyToPoint,
|
|
1480
|
+
compose,
|
|
1481
|
+
identity,
|
|
1482
|
+
rotate,
|
|
1483
|
+
translate
|
|
1484
|
+
} from "transformation-matrix";
|
|
1485
|
+
import { getBoundFromCenteredRect as getBoundFromCenteredRect2 } from "@tscircuit/math-utils";
|
|
1486
|
+
var SILKSCREEN_SHAPE_TYPES = [
|
|
1487
|
+
"pcb_silkscreen_line",
|
|
1488
|
+
"pcb_silkscreen_rect",
|
|
1489
|
+
"pcb_silkscreen_circle",
|
|
1490
|
+
"pcb_silkscreen_oval",
|
|
1491
|
+
"pcb_silkscreen_pill"
|
|
1492
|
+
];
|
|
1493
|
+
var isSilkscreenShape = (element) => SILKSCREEN_SHAPE_TYPES.includes(element.type);
|
|
1494
|
+
var DEFAULT_STROKE_WIDTH = 0.1;
|
|
1495
|
+
var ELLIPSE_SEGMENTS = 48;
|
|
1496
|
+
var CORNER_SEGMENTS = 12;
|
|
1497
|
+
var rotationAboutCenter = (center, ccwDegrees) => ccwDegrees ? compose(
|
|
1498
|
+
translate(center.x, center.y),
|
|
1499
|
+
rotate(ccwDegrees * Math.PI / 180),
|
|
1500
|
+
translate(-center.x, -center.y)
|
|
1501
|
+
) : identity();
|
|
1502
|
+
var ellipseRoute = (center, radiusX, radiusY, ccwDegrees = 0) => {
|
|
1503
|
+
const matrix = rotationAboutCenter(center, ccwDegrees);
|
|
1504
|
+
return Array.from({ length: ELLIPSE_SEGMENTS + 1 }, (_, i) => {
|
|
1505
|
+
const angle = i / ELLIPSE_SEGMENTS * Math.PI * 2;
|
|
1506
|
+
return applyToPoint(matrix, {
|
|
1507
|
+
x: center.x + radiusX * Math.cos(angle),
|
|
1508
|
+
y: center.y + radiusY * Math.sin(angle)
|
|
1509
|
+
});
|
|
1510
|
+
});
|
|
1511
|
+
};
|
|
1512
|
+
var rectangleRoute = (center, width, height, cornerRadius, ccwDegrees = 0) => {
|
|
1513
|
+
const halfW = width / 2;
|
|
1514
|
+
const halfH = height / 2;
|
|
1515
|
+
const route = [];
|
|
1516
|
+
if (cornerRadius <= 0) {
|
|
1517
|
+
const bounds = getBoundFromCenteredRect2({ center, width, height });
|
|
1518
|
+
route.push(
|
|
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 }
|
|
1523
|
+
);
|
|
1524
|
+
} else {
|
|
1525
|
+
const insetW = halfW - cornerRadius;
|
|
1526
|
+
const insetH = halfH - cornerRadius;
|
|
1527
|
+
const corners = [
|
|
1528
|
+
{ x: center.x + insetW, y: center.y + insetH, start: 0 },
|
|
1529
|
+
{ x: center.x - insetW, y: center.y + insetH, start: Math.PI / 2 },
|
|
1530
|
+
{ x: center.x - insetW, y: center.y - insetH, start: Math.PI },
|
|
1531
|
+
{ x: center.x + insetW, y: center.y - insetH, start: 3 * Math.PI / 2 }
|
|
1532
|
+
];
|
|
1533
|
+
for (const corner of corners) {
|
|
1534
|
+
for (let i = 0; i <= CORNER_SEGMENTS; i++) {
|
|
1535
|
+
const angle = corner.start + i / CORNER_SEGMENTS * (Math.PI / 2);
|
|
1536
|
+
route.push({
|
|
1537
|
+
x: corner.x + cornerRadius * Math.cos(angle),
|
|
1538
|
+
y: corner.y + cornerRadius * Math.sin(angle)
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
route.push(route[0]);
|
|
1544
|
+
const matrix = rotationAboutCenter(center, ccwDegrees);
|
|
1545
|
+
return route.map((point) => applyToPoint(matrix, point));
|
|
1546
|
+
};
|
|
1547
|
+
var clampCornerRadius = (width, height, cornerRadius) => {
|
|
1548
|
+
if (typeof cornerRadius !== "number" || cornerRadius <= 0) return 0;
|
|
1549
|
+
return Math.min(cornerRadius, width / 2, height / 2);
|
|
1550
|
+
};
|
|
1551
|
+
var getSilkscreenShapeStroke = (element) => {
|
|
1552
|
+
const strokeWidth = element.stroke_width ?? DEFAULT_STROKE_WIDTH;
|
|
1553
|
+
switch (element.type) {
|
|
1554
|
+
case "pcb_silkscreen_line":
|
|
1555
|
+
return {
|
|
1556
|
+
route: [
|
|
1557
|
+
{ x: element.x1, y: element.y1 },
|
|
1558
|
+
{ x: element.x2, y: element.y2 }
|
|
1559
|
+
],
|
|
1560
|
+
strokeWidth
|
|
1561
|
+
};
|
|
1562
|
+
case "pcb_silkscreen_rect":
|
|
1563
|
+
return {
|
|
1564
|
+
route: rectangleRoute(
|
|
1565
|
+
element.center,
|
|
1566
|
+
element.width,
|
|
1567
|
+
element.height,
|
|
1568
|
+
clampCornerRadius(
|
|
1569
|
+
element.width,
|
|
1570
|
+
element.height,
|
|
1571
|
+
element.corner_radius
|
|
1572
|
+
),
|
|
1573
|
+
element.ccw_rotation ?? 0
|
|
1574
|
+
),
|
|
1575
|
+
strokeWidth
|
|
1576
|
+
};
|
|
1577
|
+
case "pcb_silkscreen_circle":
|
|
1578
|
+
return {
|
|
1579
|
+
route: ellipseRoute(element.center, element.radius, element.radius),
|
|
1580
|
+
strokeWidth
|
|
1581
|
+
};
|
|
1582
|
+
case "pcb_silkscreen_oval":
|
|
1583
|
+
return {
|
|
1584
|
+
route: ellipseRoute(
|
|
1585
|
+
element.center,
|
|
1586
|
+
element.radius_x,
|
|
1587
|
+
element.radius_y,
|
|
1588
|
+
element.ccw_rotation ?? 0
|
|
1589
|
+
),
|
|
1590
|
+
strokeWidth
|
|
1591
|
+
};
|
|
1592
|
+
case "pcb_silkscreen_pill":
|
|
1593
|
+
return {
|
|
1594
|
+
route: rectangleRoute(
|
|
1595
|
+
element.center,
|
|
1596
|
+
element.width,
|
|
1597
|
+
element.height,
|
|
1598
|
+
Math.min(element.width, element.height) / 2
|
|
1599
|
+
),
|
|
1600
|
+
strokeWidth
|
|
1601
|
+
};
|
|
1602
|
+
default:
|
|
1603
|
+
return null;
|
|
1604
|
+
}
|
|
1605
|
+
};
|
|
1606
|
+
|
|
1332
1607
|
// src/gerber/convert-soup-to-gerber-commands/defineAperturesForLayer.ts
|
|
1333
1608
|
import stableStringify from "fast-json-stable-stringify";
|
|
1334
1609
|
|
|
@@ -1878,6 +2153,20 @@ function getAllApertureTemplateConfigsForLayer({
|
|
|
1878
2153
|
} else if (elm.type === "pcb_silkscreen_path") {
|
|
1879
2154
|
if (!isFabricationLayer)
|
|
1880
2155
|
addConfigIfNew(getApertureConfigFromPcbSilkscreenPath(elm));
|
|
2156
|
+
} else if (isSilkscreenShape(elm)) {
|
|
2157
|
+
if (!isFabricationLayer) {
|
|
2158
|
+
addConfigIfNew({
|
|
2159
|
+
standard_template_code: "C",
|
|
2160
|
+
diameter: elm.stroke_width ?? 0.1
|
|
2161
|
+
});
|
|
2162
|
+
if (elm.is_filled === true) {
|
|
2163
|
+
addConfigIfNew(REGION_APERTURE_CONFIG);
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
} else if (elm.type === "pcb_silkscreen_graphic") {
|
|
2167
|
+
if (glayer_name.endsWith("_SilkScreen")) {
|
|
2168
|
+
addConfigIfNew(REGION_APERTURE_CONFIG);
|
|
2169
|
+
}
|
|
1881
2170
|
} else if (elm.type === "pcb_silkscreen_text") {
|
|
1882
2171
|
if (!isFabricationLayer)
|
|
1883
2172
|
addConfigIfNew(getApertureConfigFromPcbSilkscreenText(elm));
|
|
@@ -1953,7 +2242,7 @@ var findApertureNumber = (glayer, search_params) => {
|
|
|
1953
2242
|
// package.json
|
|
1954
2243
|
var package_default = {
|
|
1955
2244
|
name: "circuit-json-to-gerber",
|
|
1956
|
-
version: "0.0.
|
|
2245
|
+
version: "0.0.82",
|
|
1957
2246
|
main: "dist/index.js",
|
|
1958
2247
|
type: "module",
|
|
1959
2248
|
scripts: {
|
|
@@ -1984,6 +2273,7 @@ var package_default = {
|
|
|
1984
2273
|
gerberts: "^0.0.3",
|
|
1985
2274
|
jszip: "^3.10.1",
|
|
1986
2275
|
"pcb-stackup": "^4.2.8",
|
|
2276
|
+
"polygon-clipping": "^0.15.7",
|
|
1987
2277
|
react: "^19.2.1",
|
|
1988
2278
|
"react-dom": "^19.2.1",
|
|
1989
2279
|
tscircuit: "^0.0.1776",
|
|
@@ -2164,11 +2454,11 @@ var offsetPolygonOutline = (points, offset) => {
|
|
|
2164
2454
|
// src/gerber/convert-soup-to-gerber-commands/index.ts
|
|
2165
2455
|
import { lineAlphabet } from "@tscircuit/alphabet";
|
|
2166
2456
|
import {
|
|
2167
|
-
applyToPoint,
|
|
2168
|
-
compose,
|
|
2169
|
-
identity,
|
|
2170
|
-
rotate,
|
|
2171
|
-
translate
|
|
2457
|
+
applyToPoint as applyToPoint4,
|
|
2458
|
+
compose as compose4,
|
|
2459
|
+
identity as identity3,
|
|
2460
|
+
rotate as rotate4,
|
|
2461
|
+
translate as translate4
|
|
2172
2462
|
} from "transformation-matrix";
|
|
2173
2463
|
|
|
2174
2464
|
// src/gerber/convert-soup-to-gerber-commands/fabricationLayerRefs.ts
|
|
@@ -2182,15 +2472,18 @@ var getFabricationLayerRefs = (circuitJson) => outerLayerRefs.filter(
|
|
|
2182
2472
|
);
|
|
2183
2473
|
|
|
2184
2474
|
// src/gerber/convert-soup-to-gerber-commands/getFabRectPoints.ts
|
|
2475
|
+
import { getBoundFromCenteredRect as getBoundFromCenteredRect3 } from "@tscircuit/math-utils";
|
|
2185
2476
|
var getFabRectPoints = (element) => {
|
|
2186
|
-
const
|
|
2187
|
-
|
|
2188
|
-
|
|
2477
|
+
const bounds = getBoundFromCenteredRect3({
|
|
2478
|
+
center: element.center,
|
|
2479
|
+
width: element.width,
|
|
2480
|
+
height: element.height
|
|
2481
|
+
});
|
|
2189
2482
|
return [
|
|
2190
|
-
{ x:
|
|
2191
|
-
{ x:
|
|
2192
|
-
{ x:
|
|
2193
|
-
{ x:
|
|
2483
|
+
{ x: bounds.minX, y: bounds.minY },
|
|
2484
|
+
{ x: bounds.maxX, y: bounds.minY },
|
|
2485
|
+
{ x: bounds.maxX, y: bounds.maxY },
|
|
2486
|
+
{ x: bounds.minX, y: bounds.maxY }
|
|
2194
2487
|
];
|
|
2195
2488
|
};
|
|
2196
2489
|
|
|
@@ -2340,6 +2633,353 @@ var renderFabricationDimension = ({
|
|
|
2340
2633
|
};
|
|
2341
2634
|
|
|
2342
2635
|
// src/gerber/convert-soup-to-gerber-commands/index.ts
|
|
2636
|
+
import polygonClipping3 from "polygon-clipping";
|
|
2637
|
+
|
|
2638
|
+
// src/gerber/utils/boardCutoutGeometry.ts
|
|
2639
|
+
import polygonClipping2 from "polygon-clipping";
|
|
2640
|
+
import { applyToPoint as applyToPoint2, compose as compose2, rotate as rotate2, translate as translate2 } from "transformation-matrix";
|
|
2641
|
+
import { getBoundFromCenteredRect as getBoundFromCenteredRect4 } from "@tscircuit/math-utils";
|
|
2642
|
+
var roundedRectCornerArcSegmentCount = 8;
|
|
2643
|
+
var circleCutoutPolygonSegmentCount2 = 64;
|
|
2644
|
+
var getBoardOutlinePolygon = (board) => {
|
|
2645
|
+
if (board.outline && board.outline.length > 2) {
|
|
2646
|
+
return [board.outline.map((point) => [point.x, point.y])];
|
|
2647
|
+
}
|
|
2648
|
+
const bounds = getBoundFromCenteredRect4({
|
|
2649
|
+
center: board.center,
|
|
2650
|
+
width: board.width,
|
|
2651
|
+
height: board.height
|
|
2652
|
+
});
|
|
2653
|
+
return [
|
|
2654
|
+
[
|
|
2655
|
+
[bounds.minX, bounds.minY],
|
|
2656
|
+
[bounds.maxX, bounds.minY],
|
|
2657
|
+
[bounds.maxX, bounds.maxY],
|
|
2658
|
+
[bounds.minX, bounds.maxY]
|
|
2659
|
+
]
|
|
2660
|
+
];
|
|
2661
|
+
};
|
|
2662
|
+
var getBoardOutlinePolygons2 = (circuitJson) => circuitJson.filter((element) => element.type === "pcb_board").map(getBoardOutlinePolygon);
|
|
2663
|
+
var getRectCutoutOutlinePoints = (cutout) => {
|
|
2664
|
+
const { center, width, height, rotation, corner_radius } = cutout;
|
|
2665
|
+
const w = width / 2;
|
|
2666
|
+
const h = height / 2;
|
|
2667
|
+
const r = Math.max(0, Math.min(corner_radius ?? 0, Math.abs(w), Math.abs(h)));
|
|
2668
|
+
let transformMatrix = translate2(center.x, center.y);
|
|
2669
|
+
if (rotation) {
|
|
2670
|
+
transformMatrix = compose2(
|
|
2671
|
+
transformMatrix,
|
|
2672
|
+
rotate2(rotation * Math.PI / 180)
|
|
2673
|
+
);
|
|
2674
|
+
}
|
|
2675
|
+
if (r === 0) {
|
|
2676
|
+
return [
|
|
2677
|
+
{ x: -w, y: h },
|
|
2678
|
+
{ x: w, y: h },
|
|
2679
|
+
{ x: w, y: -h },
|
|
2680
|
+
{ x: -w, y: -h }
|
|
2681
|
+
].map((point) => applyToPoint2(transformMatrix, point));
|
|
2682
|
+
}
|
|
2683
|
+
const points = [];
|
|
2684
|
+
const corners = [
|
|
2685
|
+
{ center: { x: w - r, y: h - r }, start: Math.PI / 2, end: 0 },
|
|
2686
|
+
{ center: { x: w - r, y: -h + r }, start: 0, end: -Math.PI / 2 },
|
|
2687
|
+
{ center: { x: -w + r, y: -h + r }, start: -Math.PI / 2, end: -Math.PI },
|
|
2688
|
+
{ center: { x: -w + r, y: h - r }, start: Math.PI, end: Math.PI / 2 }
|
|
2689
|
+
];
|
|
2690
|
+
for (const corner of corners) {
|
|
2691
|
+
for (let i = 0; i <= roundedRectCornerArcSegmentCount; i++) {
|
|
2692
|
+
const t = i / roundedRectCornerArcSegmentCount;
|
|
2693
|
+
const angle = corner.start + (corner.end - corner.start) * t;
|
|
2694
|
+
points.push({
|
|
2695
|
+
x: corner.center.x + r * Math.cos(angle),
|
|
2696
|
+
y: corner.center.y + r * Math.sin(angle)
|
|
2697
|
+
});
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
return points.map((point) => applyToPoint2(transformMatrix, point));
|
|
2701
|
+
};
|
|
2702
|
+
var getCircleCutoutOutlinePolygon = (cutout) => {
|
|
2703
|
+
const points = [];
|
|
2704
|
+
for (let i = 0; i < circleCutoutPolygonSegmentCount2; i++) {
|
|
2705
|
+
const angle = i / circleCutoutPolygonSegmentCount2 * Math.PI * 2;
|
|
2706
|
+
points.push([
|
|
2707
|
+
cutout.center.x + cutout.radius * Math.cos(angle),
|
|
2708
|
+
cutout.center.y + cutout.radius * Math.sin(angle)
|
|
2709
|
+
]);
|
|
2710
|
+
}
|
|
2711
|
+
return [points];
|
|
2712
|
+
};
|
|
2713
|
+
var getSolidCutoutOutlinePolygon = (cutout) => {
|
|
2714
|
+
if (cutout.shape === "rect") {
|
|
2715
|
+
return [
|
|
2716
|
+
getRectCutoutOutlinePoints(cutout).map((point) => [point.x, point.y])
|
|
2717
|
+
];
|
|
2718
|
+
}
|
|
2719
|
+
if (cutout.shape === "circle") {
|
|
2720
|
+
return getCircleCutoutOutlinePolygon(cutout);
|
|
2721
|
+
}
|
|
2722
|
+
if (cutout.shape === "polygon") {
|
|
2723
|
+
return [cutout.points.map((point) => [point.x, point.y])];
|
|
2724
|
+
}
|
|
2725
|
+
return null;
|
|
2726
|
+
};
|
|
2727
|
+
var doesSolidCutoutOverlapBoardEdge = ({
|
|
2728
|
+
cutout,
|
|
2729
|
+
boardOutlinePolygons
|
|
2730
|
+
}) => {
|
|
2731
|
+
const cutoutOutlinePolygon = getSolidCutoutOutlinePolygon(cutout);
|
|
2732
|
+
if (!cutoutOutlinePolygon) return false;
|
|
2733
|
+
return boardOutlinePolygons.some((boardOutlinePolygon) => {
|
|
2734
|
+
const overlapsBoard = polygonClipping2.intersection(boardOutlinePolygon, cutoutOutlinePolygon).length > 0;
|
|
2735
|
+
const extendsOutsideBoard = polygonClipping2.difference(cutoutOutlinePolygon, boardOutlinePolygon).length > 0;
|
|
2736
|
+
return overlapsBoard && extendsOutsideBoard;
|
|
2737
|
+
});
|
|
2738
|
+
};
|
|
2739
|
+
var isCutoutFullyInternal = ({
|
|
2740
|
+
cutout,
|
|
2741
|
+
boardOutlinePolygons
|
|
2742
|
+
}) => {
|
|
2743
|
+
const cutoutOutlinePolygon = getSolidCutoutOutlinePolygon(cutout);
|
|
2744
|
+
if (!cutoutOutlinePolygon) return false;
|
|
2745
|
+
return boardOutlinePolygons.some(
|
|
2746
|
+
(boardOutlinePolygon) => (
|
|
2747
|
+
// Has overlap with the board interior …
|
|
2748
|
+
polygonClipping2.intersection(boardOutlinePolygon, cutoutOutlinePolygon).length > 0 && // … but does not extend outside it at all.
|
|
2749
|
+
polygonClipping2.difference(cutoutOutlinePolygon, boardOutlinePolygon).length === 0
|
|
2750
|
+
)
|
|
2751
|
+
);
|
|
2752
|
+
};
|
|
2753
|
+
|
|
2754
|
+
// src/gerber/utils/emitCutoutEdgeCuts.ts
|
|
2755
|
+
import {
|
|
2756
|
+
applyToPoint as applyToPoint3,
|
|
2757
|
+
compose as compose3,
|
|
2758
|
+
identity as identity2,
|
|
2759
|
+
rotate as rotate3,
|
|
2760
|
+
translate as translate3
|
|
2761
|
+
} from "transformation-matrix";
|
|
2762
|
+
var emitCutoutEdgeCuts = ({
|
|
2763
|
+
cutout,
|
|
2764
|
+
builder,
|
|
2765
|
+
mfy,
|
|
2766
|
+
drawCw
|
|
2767
|
+
}) => {
|
|
2768
|
+
if (cutout.shape === "circle") {
|
|
2769
|
+
emitCircle({ cutout, builder, mfy, drawCw });
|
|
2770
|
+
} else if (cutout.shape === "rect") {
|
|
2771
|
+
emitRect({ cutout, builder, mfy, drawCw });
|
|
2772
|
+
} else if (cutout.shape === "polygon") {
|
|
2773
|
+
emitPolygon({ cutout, builder, mfy, drawCw });
|
|
2774
|
+
}
|
|
2775
|
+
};
|
|
2776
|
+
var emitCircle = ({
|
|
2777
|
+
cutout,
|
|
2778
|
+
builder,
|
|
2779
|
+
mfy,
|
|
2780
|
+
drawCw
|
|
2781
|
+
}) => {
|
|
2782
|
+
const { center, radius } = cutout;
|
|
2783
|
+
const p1 = { x: center.x + radius, y: center.y };
|
|
2784
|
+
const p2 = { x: center.x - radius, y: center.y };
|
|
2785
|
+
const isYFlipped = mfy(1) < 0;
|
|
2786
|
+
let isClockwise = drawCw;
|
|
2787
|
+
if (isYFlipped) {
|
|
2788
|
+
isClockwise = !isClockwise;
|
|
2789
|
+
}
|
|
2790
|
+
let arcMode = "set_movement_mode_to_counterclockwise_circular";
|
|
2791
|
+
if (isClockwise) {
|
|
2792
|
+
arcMode = "set_movement_mode_to_clockwise_circular";
|
|
2793
|
+
}
|
|
2794
|
+
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", {});
|
|
2795
|
+
};
|
|
2796
|
+
var emitRect = ({
|
|
2797
|
+
cutout,
|
|
2798
|
+
builder,
|
|
2799
|
+
mfy,
|
|
2800
|
+
drawCw
|
|
2801
|
+
}) => {
|
|
2802
|
+
const { center, width, height, rotation, corner_radius } = cutout;
|
|
2803
|
+
const w = width / 2;
|
|
2804
|
+
const h = height / 2;
|
|
2805
|
+
let cr = 0;
|
|
2806
|
+
if (corner_radius !== void 0) {
|
|
2807
|
+
cr = corner_radius;
|
|
2808
|
+
}
|
|
2809
|
+
const r = Math.max(0, Math.min(cr, Math.abs(w), Math.abs(h)));
|
|
2810
|
+
let rotationTransform = identity2();
|
|
2811
|
+
if (rotation) {
|
|
2812
|
+
rotationTransform = rotate3(rotation * Math.PI / 180);
|
|
2813
|
+
}
|
|
2814
|
+
const transformMatrix = compose3(
|
|
2815
|
+
translate3(center.x, center.y),
|
|
2816
|
+
rotationTransform
|
|
2817
|
+
);
|
|
2818
|
+
if (r > 0) {
|
|
2819
|
+
emitRoundedRect({ w, h, r, transformMatrix, builder, mfy, drawCw });
|
|
2820
|
+
} else {
|
|
2821
|
+
emitSharpRect({ w, h, transformMatrix, builder, mfy, drawCw });
|
|
2822
|
+
}
|
|
2823
|
+
};
|
|
2824
|
+
var emitSharpRect = ({
|
|
2825
|
+
w,
|
|
2826
|
+
h,
|
|
2827
|
+
transformMatrix,
|
|
2828
|
+
builder,
|
|
2829
|
+
mfy,
|
|
2830
|
+
drawCw
|
|
2831
|
+
}) => {
|
|
2832
|
+
const cwPoints = [
|
|
2833
|
+
{ x: -w, y: h },
|
|
2834
|
+
{ x: w, y: h },
|
|
2835
|
+
{ x: w, y: -h },
|
|
2836
|
+
{ x: -w, y: -h }
|
|
2837
|
+
];
|
|
2838
|
+
const isYFlipped = mfy(1) < 0;
|
|
2839
|
+
let shouldReverse = !drawCw;
|
|
2840
|
+
if (isYFlipped) {
|
|
2841
|
+
shouldReverse = !shouldReverse;
|
|
2842
|
+
}
|
|
2843
|
+
let pts = cwPoints;
|
|
2844
|
+
if (shouldReverse) {
|
|
2845
|
+
pts = [...cwPoints].reverse();
|
|
2846
|
+
}
|
|
2847
|
+
const tpts = pts.map((p) => applyToPoint3(transformMatrix, p));
|
|
2848
|
+
builder.add("move_operation", { x: tpts[0].x, y: mfy(tpts[0].y) });
|
|
2849
|
+
for (let i = 1; i < tpts.length; i++) {
|
|
2850
|
+
builder.add("plot_operation", { x: tpts[i].x, y: mfy(tpts[i].y) });
|
|
2851
|
+
}
|
|
2852
|
+
builder.add("plot_operation", { x: tpts[0].x, y: mfy(tpts[0].y) });
|
|
2853
|
+
};
|
|
2854
|
+
var emitRoundedRect = ({
|
|
2855
|
+
w,
|
|
2856
|
+
h,
|
|
2857
|
+
r,
|
|
2858
|
+
transformMatrix,
|
|
2859
|
+
builder,
|
|
2860
|
+
mfy,
|
|
2861
|
+
drawCw
|
|
2862
|
+
}) => {
|
|
2863
|
+
const isYFlipped = mfy(1) < 0;
|
|
2864
|
+
let isClockwise = drawCw;
|
|
2865
|
+
if (isYFlipped) {
|
|
2866
|
+
isClockwise = !isClockwise;
|
|
2867
|
+
}
|
|
2868
|
+
let arcMode = "set_movement_mode_to_counterclockwise_circular";
|
|
2869
|
+
if (isClockwise) {
|
|
2870
|
+
arcMode = "set_movement_mode_to_clockwise_circular";
|
|
2871
|
+
}
|
|
2872
|
+
let currentPoint;
|
|
2873
|
+
const addLine = (pt) => {
|
|
2874
|
+
const tpt = applyToPoint3(transformMatrix, pt);
|
|
2875
|
+
builder.add("plot_operation", { x: tpt.x, y: mfy(tpt.y) });
|
|
2876
|
+
currentPoint = tpt;
|
|
2877
|
+
};
|
|
2878
|
+
const addArc = (endPt, centerPt) => {
|
|
2879
|
+
const tEnd = applyToPoint3(transformMatrix, endPt);
|
|
2880
|
+
const tCenter = applyToPoint3(transformMatrix, centerPt);
|
|
2881
|
+
builder.add(arcMode, {}).add("plot_operation", {
|
|
2882
|
+
x: tEnd.x,
|
|
2883
|
+
y: mfy(tEnd.y),
|
|
2884
|
+
i: tCenter.x - currentPoint.x,
|
|
2885
|
+
j: mfy(tCenter.y) - mfy(currentPoint.y)
|
|
2886
|
+
}).add("set_movement_mode_to_linear", {});
|
|
2887
|
+
currentPoint = tEnd;
|
|
2888
|
+
};
|
|
2889
|
+
if (isClockwise) {
|
|
2890
|
+
const cwSegments = [
|
|
2891
|
+
{
|
|
2892
|
+
lineTo: { x: w - r, y: h },
|
|
2893
|
+
arcEnd: { x: w, y: h - r },
|
|
2894
|
+
arcCenter: { x: w - r, y: h - r }
|
|
2895
|
+
},
|
|
2896
|
+
{
|
|
2897
|
+
lineTo: { x: w, y: -h + r },
|
|
2898
|
+
arcEnd: { x: w - r, y: -h },
|
|
2899
|
+
arcCenter: { x: w - r, y: -h + r }
|
|
2900
|
+
},
|
|
2901
|
+
{
|
|
2902
|
+
lineTo: { x: -w + r, y: -h },
|
|
2903
|
+
arcEnd: { x: -w, y: -h + r },
|
|
2904
|
+
arcCenter: { x: -w + r, y: -h + r }
|
|
2905
|
+
},
|
|
2906
|
+
{
|
|
2907
|
+
lineTo: { x: -w, y: h - r },
|
|
2908
|
+
arcEnd: { x: -w + r, y: h },
|
|
2909
|
+
arcCenter: { x: -w + r, y: h - r }
|
|
2910
|
+
}
|
|
2911
|
+
];
|
|
2912
|
+
const startPt = { x: -w + r, y: h };
|
|
2913
|
+
const tStart = applyToPoint3(transformMatrix, startPt);
|
|
2914
|
+
builder.add("move_operation", { x: tStart.x, y: mfy(tStart.y) });
|
|
2915
|
+
currentPoint = tStart;
|
|
2916
|
+
for (const seg of cwSegments) {
|
|
2917
|
+
addLine(seg.lineTo);
|
|
2918
|
+
addArc(seg.arcEnd, seg.arcCenter);
|
|
2919
|
+
}
|
|
2920
|
+
} else {
|
|
2921
|
+
const startPt = { x: w - r, y: h };
|
|
2922
|
+
const tStart = applyToPoint3(transformMatrix, startPt);
|
|
2923
|
+
builder.add("move_operation", { x: tStart.x, y: mfy(tStart.y) });
|
|
2924
|
+
currentPoint = tStart;
|
|
2925
|
+
addLine({ x: -w + r, y: h });
|
|
2926
|
+
addArc({ x: -w, y: h - r }, { x: -w + r, y: h - r });
|
|
2927
|
+
addLine({ x: -w, y: -h + r });
|
|
2928
|
+
addArc({ x: -w + r, y: -h }, { x: -w + r, y: -h + r });
|
|
2929
|
+
addLine({ x: w - r, y: -h });
|
|
2930
|
+
addArc({ x: w, y: -h + r }, { x: w - r, y: -h + r });
|
|
2931
|
+
addLine({ x: w, y: h - r });
|
|
2932
|
+
addArc({ x: w - r, y: h }, { x: w - r, y: h - r });
|
|
2933
|
+
}
|
|
2934
|
+
};
|
|
2935
|
+
var emitPolygon = ({
|
|
2936
|
+
cutout,
|
|
2937
|
+
builder,
|
|
2938
|
+
mfy,
|
|
2939
|
+
drawCw
|
|
2940
|
+
}) => {
|
|
2941
|
+
const points = cutout.points;
|
|
2942
|
+
if (points.length === 0) return;
|
|
2943
|
+
const isYFlipped = mfy(1) < 0;
|
|
2944
|
+
let shouldReverse = !drawCw;
|
|
2945
|
+
if (isYFlipped) {
|
|
2946
|
+
shouldReverse = !shouldReverse;
|
|
2947
|
+
}
|
|
2948
|
+
let ordered = points;
|
|
2949
|
+
if (shouldReverse) {
|
|
2950
|
+
ordered = [...points].reverse();
|
|
2951
|
+
}
|
|
2952
|
+
builder.add("move_operation", { x: ordered[0].x, y: mfy(ordered[0].y) });
|
|
2953
|
+
for (let i = 1; i < ordered.length; i++) {
|
|
2954
|
+
builder.add("plot_operation", { x: ordered[i].x, y: mfy(ordered[i].y) });
|
|
2955
|
+
}
|
|
2956
|
+
builder.add("plot_operation", { x: ordered[0].x, y: mfy(ordered[0].y) });
|
|
2957
|
+
};
|
|
2958
|
+
|
|
2959
|
+
// src/gerber/convert-soup-to-gerber-commands/index.ts
|
|
2960
|
+
var closeRing = (ring) => {
|
|
2961
|
+
const first = ring[0];
|
|
2962
|
+
const last = ring[ring.length - 1];
|
|
2963
|
+
if (!first || !last) return ring;
|
|
2964
|
+
if (first[0] === last[0] && first[1] === last[1]) return ring;
|
|
2965
|
+
return [...ring, first];
|
|
2966
|
+
};
|
|
2967
|
+
var emitEdgeCutRing = (gerberBuild, ring, mfy) => {
|
|
2968
|
+
const closedRing = closeRing(ring);
|
|
2969
|
+
const firstPoint = closedRing[0];
|
|
2970
|
+
if (!firstPoint || closedRing.length < 4) return;
|
|
2971
|
+
gerberBuild.add("move_operation", {
|
|
2972
|
+
x: firstPoint[0],
|
|
2973
|
+
y: mfy(firstPoint[1])
|
|
2974
|
+
});
|
|
2975
|
+
for (let i = 1; i < closedRing.length; i++) {
|
|
2976
|
+
const point = closedRing[i];
|
|
2977
|
+
gerberBuild.add("plot_operation", {
|
|
2978
|
+
x: point[0],
|
|
2979
|
+
y: mfy(point[1])
|
|
2980
|
+
});
|
|
2981
|
+
}
|
|
2982
|
+
};
|
|
2343
2983
|
var getLayerCount2 = (circuitJson) => {
|
|
2344
2984
|
const board = circuitJson.find((element) => element.type === "pcb_board");
|
|
2345
2985
|
const numLayers = board?.num_layers ?? 2;
|
|
@@ -2355,6 +2995,7 @@ var getGerberInnerLayerName = (layerRef) => {
|
|
|
2355
2995
|
var convertSoupToGerberCommands = (circuitJson, opts = {}) => {
|
|
2356
2996
|
opts.flip_y_axis ??= false;
|
|
2357
2997
|
const hasPanel = circuitJson.some((e) => e.type === "pcb_panel");
|
|
2998
|
+
const hasBoard = circuitJson.some((e) => e.type === "pcb_board");
|
|
2358
2999
|
const layerCount = getLayerCount2(circuitJson);
|
|
2359
3000
|
const innerLayerRefs = getInnerLayerRefs(layerCount);
|
|
2360
3001
|
const copperLayerRefs = ["top", ...innerLayerRefs, "bottom"];
|
|
@@ -2444,7 +3085,16 @@ var convertSoupToGerberCommands = (circuitJson, opts = {}) => {
|
|
|
2444
3085
|
//mm
|
|
2445
3086
|
}).build()
|
|
2446
3087
|
);
|
|
2447
|
-
const mfy = (y) =>
|
|
3088
|
+
const mfy = (y) => {
|
|
3089
|
+
if (opts.flip_y_axis) {
|
|
3090
|
+
return -y;
|
|
3091
|
+
}
|
|
3092
|
+
return y;
|
|
3093
|
+
};
|
|
3094
|
+
const boardOutlinePolygons = getBoardOutlinePolygons2(circuitJson);
|
|
3095
|
+
const solidCutoutPolygons = circuitJson.filter((element) => element.type === "pcb_cutout").filter(
|
|
3096
|
+
(cutout) => doesSolidCutoutOverlapBoardEdge({ cutout, boardOutlinePolygons })
|
|
3097
|
+
).map(getSolidCutoutOutlinePolygon).filter((polygon) => polygon !== null);
|
|
2448
3098
|
const renderPillFlash = ({
|
|
2449
3099
|
glayer,
|
|
2450
3100
|
x,
|
|
@@ -2597,18 +3247,18 @@ var convertSoupToGerberCommands = (circuitJson, opts = {}) => {
|
|
|
2597
3247
|
const shouldMirror = element.is_mirrored !== void 0 ? element.is_mirrored : element.layer === "bottom";
|
|
2598
3248
|
if (shouldMirror) {
|
|
2599
3249
|
transforms.push(
|
|
2600
|
-
|
|
3250
|
+
translate4(cx, cy),
|
|
2601
3251
|
{ a: -1, b: 0, c: 0, d: 1, e: 0, f: 0 },
|
|
2602
|
-
|
|
3252
|
+
translate4(-cx, -cy)
|
|
2603
3253
|
);
|
|
2604
3254
|
rotation = -rotation;
|
|
2605
3255
|
}
|
|
2606
3256
|
if (rotation) {
|
|
2607
3257
|
const rad = rotation * Math.PI / 180;
|
|
2608
|
-
transforms.push(
|
|
3258
|
+
transforms.push(translate4(cx, cy), rotate4(rad), translate4(-cx, -cy));
|
|
2609
3259
|
}
|
|
2610
|
-
const transformMatrix = transforms.length > 0 ?
|
|
2611
|
-
const applyTransform = (point) => transformMatrix ?
|
|
3260
|
+
const transformMatrix = transforms.length > 0 ? compose4(...transforms) : void 0;
|
|
3261
|
+
const applyTransform = (point) => transformMatrix ? applyToPoint4(transformMatrix, point) : point;
|
|
2612
3262
|
if (element.is_knockout) {
|
|
2613
3263
|
const padding = element.knockout_padding ?? {
|
|
2614
3264
|
left: 0.2,
|
|
@@ -2797,17 +3447,17 @@ var convertSoupToGerberCommands = (circuitJson, opts = {}) => {
|
|
|
2797
3447
|
{ x: -w, y: -h }
|
|
2798
3448
|
// Bottom-left
|
|
2799
3449
|
];
|
|
2800
|
-
let transformMatrix =
|
|
3450
|
+
let transformMatrix = identity3();
|
|
2801
3451
|
if (rotation) {
|
|
2802
3452
|
const angle_rad = rotation * Math.PI / 180;
|
|
2803
|
-
transformMatrix =
|
|
3453
|
+
transformMatrix = rotate4(angle_rad);
|
|
2804
3454
|
}
|
|
2805
|
-
transformMatrix =
|
|
2806
|
-
|
|
3455
|
+
transformMatrix = compose4(
|
|
3456
|
+
translate4(center.x, center.y),
|
|
2807
3457
|
transformMatrix
|
|
2808
3458
|
);
|
|
2809
3459
|
const transformedPoints = points.map(
|
|
2810
|
-
(p) =>
|
|
3460
|
+
(p) => applyToPoint4(transformMatrix, p)
|
|
2811
3461
|
);
|
|
2812
3462
|
const regionApertureNumber = getRegionApertureNumber(copper_glayer);
|
|
2813
3463
|
const rect_builder = gerberBuilder().add("select_aperture", { aperture_number: regionApertureNumber }).add("start_region_statement", {});
|
|
@@ -2931,6 +3581,69 @@ var convertSoupToGerberCommands = (circuitJson, opts = {}) => {
|
|
|
2931
3581
|
}
|
|
2932
3582
|
glayer.push(...gerber.build());
|
|
2933
3583
|
}
|
|
3584
|
+
} else if (isSilkscreenShape(element) && isOuterLayerRef(layer)) {
|
|
3585
|
+
if (element.layer === layer) {
|
|
3586
|
+
const stroke = getSilkscreenShapeStroke(element);
|
|
3587
|
+
if (stroke) {
|
|
3588
|
+
const glayer = glayers[getGerberLayerName(layer, "silkscreen")];
|
|
3589
|
+
const isFilled = element.is_filled === true;
|
|
3590
|
+
const isStroked = element.has_stroke !== false;
|
|
3591
|
+
if (isFilled) {
|
|
3592
|
+
addClosedRegionFromPoints({
|
|
3593
|
+
target: glayer,
|
|
3594
|
+
apertureSource: glayer,
|
|
3595
|
+
points: stroke.route
|
|
3596
|
+
});
|
|
3597
|
+
}
|
|
3598
|
+
if (isStroked) {
|
|
3599
|
+
renderOpenPath({
|
|
3600
|
+
element: {
|
|
3601
|
+
type: element.type,
|
|
3602
|
+
stroke_width: stroke.strokeWidth,
|
|
3603
|
+
is_stroke_dashed: element.is_stroke_dashed
|
|
3604
|
+
},
|
|
3605
|
+
glayer,
|
|
3606
|
+
apertureConfig: {
|
|
3607
|
+
standard_template_code: "C",
|
|
3608
|
+
diameter: stroke.strokeWidth
|
|
3609
|
+
},
|
|
3610
|
+
route: stroke.route,
|
|
3611
|
+
mapY: mfy
|
|
3612
|
+
});
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
} else if (
|
|
3617
|
+
// pcb_silkscreen_graphic is newer than this repo's pinned circuit-json,
|
|
3618
|
+
// so it isn't in the AnyCircuitElement union yet — match it structurally.
|
|
3619
|
+
element.type === "pcb_silkscreen_graphic" && isOuterLayerRef(layer)
|
|
3620
|
+
) {
|
|
3621
|
+
const graphic = element;
|
|
3622
|
+
if (graphic.layer === layer && graphic.brep_shape) {
|
|
3623
|
+
const glayer = glayers[getGerberLayerName(layer, "silkscreen")];
|
|
3624
|
+
const { outer_ring, inner_rings } = graphic.brep_shape;
|
|
3625
|
+
const regionApertureNumber = getRegionApertureNumber(glayer);
|
|
3626
|
+
const outerBuilder = gerberBuilder().add("select_aperture", { aperture_number: regionApertureNumber }).add("start_region_statement", {});
|
|
3627
|
+
drawRingToBuilder(outerBuilder, reverseRing(outer_ring));
|
|
3628
|
+
outerBuilder.add("end_region_statement", {});
|
|
3629
|
+
glayer.push(...outerBuilder.build());
|
|
3630
|
+
if (inner_rings && inner_rings.length > 0) {
|
|
3631
|
+
glayer.push(
|
|
3632
|
+
...gerberBuilder().add("set_layer_polarity", { polarity: "C" }).build()
|
|
3633
|
+
);
|
|
3634
|
+
for (const inner_ring of inner_rings) {
|
|
3635
|
+
const innerBuilder = gerberBuilder().add("select_aperture", {
|
|
3636
|
+
aperture_number: regionApertureNumber
|
|
3637
|
+
}).add("start_region_statement", {});
|
|
3638
|
+
drawRingToBuilder(innerBuilder, inner_ring);
|
|
3639
|
+
innerBuilder.add("end_region_statement", {});
|
|
3640
|
+
glayer.push(...innerBuilder.build());
|
|
3641
|
+
}
|
|
3642
|
+
glayer.push(
|
|
3643
|
+
...gerberBuilder().add("set_layer_polarity", { polarity: "D" }).build()
|
|
3644
|
+
);
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
2934
3647
|
} else if (element.type === "pcb_silkscreen_text" && isOuterLayerRef(layer)) {
|
|
2935
3648
|
renderVectorText(
|
|
2936
3649
|
element,
|
|
@@ -3300,36 +4013,42 @@ var convertSoupToGerberCommands = (circuitJson, opts = {}) => {
|
|
|
3300
4013
|
aperture_number: 10
|
|
3301
4014
|
});
|
|
3302
4015
|
if (outline && outline.length > 2) {
|
|
3303
|
-
const
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
4016
|
+
const boardPolygon = [
|
|
4017
|
+
outline.map((point) => [point.x, point.y])
|
|
4018
|
+
];
|
|
4019
|
+
let boardGeometry = [boardPolygon];
|
|
4020
|
+
if (solidCutoutPolygons.length > 0) {
|
|
4021
|
+
boardGeometry = polygonClipping3.difference(
|
|
4022
|
+
boardPolygon,
|
|
4023
|
+
...solidCutoutPolygons
|
|
4024
|
+
);
|
|
3311
4025
|
}
|
|
3312
|
-
const
|
|
3313
|
-
|
|
3314
|
-
|
|
4026
|
+
for (const polygon of boardGeometry) {
|
|
4027
|
+
for (const ring of polygon) {
|
|
4028
|
+
emitEdgeCutRing(gerberBuild, ring, mfy);
|
|
4029
|
+
}
|
|
3315
4030
|
}
|
|
3316
4031
|
} else {
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
4032
|
+
const boardPolygon = [
|
|
4033
|
+
[
|
|
4034
|
+
[center.x - width / 2, center.y - height / 2],
|
|
4035
|
+
[center.x + width / 2, center.y - height / 2],
|
|
4036
|
+
[center.x + width / 2, center.y + height / 2],
|
|
4037
|
+
[center.x - width / 2, center.y + height / 2]
|
|
4038
|
+
]
|
|
4039
|
+
];
|
|
4040
|
+
let boardGeometry = [boardPolygon];
|
|
4041
|
+
if (solidCutoutPolygons.length > 0) {
|
|
4042
|
+
boardGeometry = polygonClipping3.difference(
|
|
4043
|
+
boardPolygon,
|
|
4044
|
+
...solidCutoutPolygons
|
|
4045
|
+
);
|
|
4046
|
+
}
|
|
4047
|
+
for (const polygon of boardGeometry) {
|
|
4048
|
+
for (const ring of polygon) {
|
|
4049
|
+
emitEdgeCutRing(gerberBuild, ring, mfy);
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
3333
4052
|
}
|
|
3334
4053
|
glayer.push(...gerberBuild.build());
|
|
3335
4054
|
} else if (element.type === "pcb_panel" && layer === "edgecut") {
|
|
@@ -3357,146 +4076,26 @@ var convertSoupToGerberCommands = (circuitJson, opts = {}) => {
|
|
|
3357
4076
|
glayer.push(...gerberBuild.build());
|
|
3358
4077
|
} else if (element.type === "pcb_cutout") {
|
|
3359
4078
|
if (layer === "edgecut") {
|
|
4079
|
+
if (hasBoard && !hasPanel && doesSolidCutoutOverlapBoardEdge({
|
|
4080
|
+
cutout: element,
|
|
4081
|
+
boardOutlinePolygons
|
|
4082
|
+
})) {
|
|
4083
|
+
continue;
|
|
4084
|
+
}
|
|
3360
4085
|
const ec_layer = glayers.Edge_Cuts;
|
|
3361
4086
|
const cutout_builder = gerberBuilder().add("select_aperture", {
|
|
3362
4087
|
aperture_number: 10
|
|
3363
4088
|
});
|
|
3364
|
-
const
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
let transformMatrix2 = identity();
|
|
3375
|
-
if (rotation) {
|
|
3376
|
-
const angle_rad = rotation * Math.PI / 180;
|
|
3377
|
-
transformMatrix2 = rotate(angle_rad);
|
|
3378
|
-
}
|
|
3379
|
-
return compose(translate(center.x, center.y), transformMatrix2);
|
|
3380
|
-
};
|
|
3381
|
-
const transformMatrix = makeTransformMatrix();
|
|
3382
|
-
if (r > 0) {
|
|
3383
|
-
const startPoint = { x: -w + r, y: h };
|
|
3384
|
-
let currentPoint = applyToPoint(transformMatrix, startPoint);
|
|
3385
|
-
cutout_builder.add("move_operation", {
|
|
3386
|
-
x: currentPoint.x,
|
|
3387
|
-
y: mfy(currentPoint.y)
|
|
3388
|
-
});
|
|
3389
|
-
const addLine = (point) => {
|
|
3390
|
-
const transformedPoint = applyToPoint(transformMatrix, point);
|
|
3391
|
-
cutout_builder.add("plot_operation", {
|
|
3392
|
-
x: transformedPoint.x,
|
|
3393
|
-
y: mfy(transformedPoint.y)
|
|
3394
|
-
});
|
|
3395
|
-
currentPoint = transformedPoint;
|
|
3396
|
-
};
|
|
3397
|
-
const addArc = (options) => {
|
|
3398
|
-
const transformedPoint = applyToPoint(
|
|
3399
|
-
transformMatrix,
|
|
3400
|
-
options.point
|
|
3401
|
-
);
|
|
3402
|
-
const transformedCenter = applyToPoint(
|
|
3403
|
-
transformMatrix,
|
|
3404
|
-
options.center
|
|
3405
|
-
);
|
|
3406
|
-
cutout_builder.add("set_movement_mode_to_clockwise_circular", {}).add("plot_operation", {
|
|
3407
|
-
x: transformedPoint.x,
|
|
3408
|
-
y: mfy(transformedPoint.y),
|
|
3409
|
-
i: transformedCenter.x - currentPoint.x,
|
|
3410
|
-
j: mfy(transformedCenter.y) - mfy(currentPoint.y)
|
|
3411
|
-
}).add("set_movement_mode_to_linear", {});
|
|
3412
|
-
currentPoint = transformedPoint;
|
|
3413
|
-
};
|
|
3414
|
-
addLine({ x: w - r, y: h });
|
|
3415
|
-
addArc({
|
|
3416
|
-
point: { x: w, y: h - r },
|
|
3417
|
-
center: { x: w - r, y: h - r }
|
|
3418
|
-
});
|
|
3419
|
-
addLine({ x: w, y: -h + r });
|
|
3420
|
-
addArc({
|
|
3421
|
-
point: { x: w - r, y: -h },
|
|
3422
|
-
center: { x: w - r, y: -h + r }
|
|
3423
|
-
});
|
|
3424
|
-
addLine({ x: -w + r, y: -h });
|
|
3425
|
-
addArc({
|
|
3426
|
-
point: { x: -w, y: -h + r },
|
|
3427
|
-
center: { x: -w + r, y: -h + r }
|
|
3428
|
-
});
|
|
3429
|
-
addLine({ x: -w, y: h - r });
|
|
3430
|
-
addArc({
|
|
3431
|
-
point: { x: -w + r, y: h },
|
|
3432
|
-
center: { x: -w + r, y: h - r }
|
|
3433
|
-
});
|
|
3434
|
-
} else {
|
|
3435
|
-
const points = [
|
|
3436
|
-
{ x: -w, y: h },
|
|
3437
|
-
// Top-left
|
|
3438
|
-
{ x: w, y: h },
|
|
3439
|
-
// Top-right
|
|
3440
|
-
{ x: w, y: -h },
|
|
3441
|
-
// Bottom-right
|
|
3442
|
-
{ x: -w, y: -h }
|
|
3443
|
-
// Bottom-left
|
|
3444
|
-
];
|
|
3445
|
-
const transformedPoints = points.map(
|
|
3446
|
-
(p) => applyToPoint(transformMatrix, p)
|
|
3447
|
-
);
|
|
3448
|
-
cutout_builder.add("move_operation", {
|
|
3449
|
-
x: transformedPoints[0].x,
|
|
3450
|
-
y: mfy(transformedPoints[0].y)
|
|
3451
|
-
});
|
|
3452
|
-
for (let i = 1; i < transformedPoints.length; i++) {
|
|
3453
|
-
cutout_builder.add("plot_operation", {
|
|
3454
|
-
x: transformedPoints[i].x,
|
|
3455
|
-
y: mfy(transformedPoints[i].y)
|
|
3456
|
-
});
|
|
3457
|
-
}
|
|
3458
|
-
cutout_builder.add("plot_operation", {
|
|
3459
|
-
x: transformedPoints[0].x,
|
|
3460
|
-
y: mfy(transformedPoints[0].y)
|
|
3461
|
-
});
|
|
3462
|
-
}
|
|
3463
|
-
} else if (el.shape === "circle") {
|
|
3464
|
-
const { center, radius } = el;
|
|
3465
|
-
const p1 = { x: center.x + radius, y: center.y };
|
|
3466
|
-
const p2 = { x: center.x - radius, y: center.y };
|
|
3467
|
-
cutout_builder.add("move_operation", {
|
|
3468
|
-
x: p1.x,
|
|
3469
|
-
y: mfy(p1.y)
|
|
3470
|
-
}).add("set_movement_mode_to_counterclockwise_circular", {}).add("plot_operation", {
|
|
3471
|
-
x: p2.x,
|
|
3472
|
-
y: mfy(p2.y),
|
|
3473
|
-
i: -radius,
|
|
3474
|
-
j: 0
|
|
3475
|
-
}).add("plot_operation", {
|
|
3476
|
-
x: p1.x,
|
|
3477
|
-
y: mfy(p1.y),
|
|
3478
|
-
i: radius,
|
|
3479
|
-
j: 0
|
|
3480
|
-
}).add("set_movement_mode_to_linear", {});
|
|
3481
|
-
} else if (el.shape === "polygon") {
|
|
3482
|
-
const { points } = el;
|
|
3483
|
-
if (points.length > 0) {
|
|
3484
|
-
cutout_builder.add("move_operation", {
|
|
3485
|
-
x: points[0].x,
|
|
3486
|
-
y: mfy(points[0].y)
|
|
3487
|
-
});
|
|
3488
|
-
for (let i = 1; i < points.length; i++) {
|
|
3489
|
-
cutout_builder.add("plot_operation", {
|
|
3490
|
-
x: points[i].x,
|
|
3491
|
-
y: mfy(points[i].y)
|
|
3492
|
-
});
|
|
3493
|
-
}
|
|
3494
|
-
cutout_builder.add("plot_operation", {
|
|
3495
|
-
x: points[0].x,
|
|
3496
|
-
y: mfy(points[0].y)
|
|
3497
|
-
});
|
|
3498
|
-
}
|
|
3499
|
-
}
|
|
4089
|
+
const drawCw = hasBoard && !hasPanel && isCutoutFullyInternal({
|
|
4090
|
+
cutout: element,
|
|
4091
|
+
boardOutlinePolygons
|
|
4092
|
+
});
|
|
4093
|
+
emitCutoutEdgeCuts({
|
|
4094
|
+
cutout: element,
|
|
4095
|
+
builder: cutout_builder,
|
|
4096
|
+
mfy,
|
|
4097
|
+
drawCw
|
|
4098
|
+
});
|
|
3500
4099
|
ec_layer.push(...cutout_builder.build());
|
|
3501
4100
|
}
|
|
3502
4101
|
}
|
|
@@ -3521,4 +4120,4 @@ export {
|
|
|
3521
4120
|
stringifyGerberCommands,
|
|
3522
4121
|
convertSoupToGerberCommands
|
|
3523
4122
|
};
|
|
3524
|
-
//# sourceMappingURL=chunk-
|
|
4123
|
+
//# sourceMappingURL=chunk-46NSLLUV.js.map
|