circuit-json-to-gerber 0.0.69 → 0.0.71

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.
@@ -1112,7 +1112,17 @@ var define_aperture_template = defineGerberCommand({
1112
1112
  if (macro_name === "HORZPILL" || macro_name === "VERTPILL") {
1113
1113
  commandString += `${props.x_size.toFixed(6)}X${props.y_size.toFixed(6)}X${props.circle_diameter.toFixed(6)}X${props.circle_center_offset.toFixed(6)}`;
1114
1114
  } else if (macro_name === "ROUNDRECT") {
1115
- throw new Error("ROUNDRECT macro not implemented yet");
1115
+ commandString += [
1116
+ props.corner_radius,
1117
+ props.corner_1_x,
1118
+ props.corner_1_y,
1119
+ props.corner_2_x,
1120
+ props.corner_2_y,
1121
+ props.corner_3_x,
1122
+ props.corner_3_y,
1123
+ props.corner_4_x,
1124
+ props.corner_4_y
1125
+ ].map((value) => value.toFixed(6)).join("X");
1116
1126
  }
1117
1127
  commandString += "*%";
1118
1128
  return commandString;
@@ -1270,7 +1280,7 @@ var defineCommonMacros = (glayer) => {
1270
1280
  0 $4 = Circle center offset
1271
1281
  0 21 = Center Line(Exposure, Width, Height, Center X, Center Y, Rotation)*
1272
1282
  0 1 = Circle(Exposure, Diameter, Center X, Center Y, Rotation)*
1273
- 21,1,$1,$2,0.0,0.0,0.0*
1283
+ 21,1,$1-$3,$2,0.0,0.0,0.0*
1274
1284
  1,1,$3,0.0-$4,0.0*
1275
1285
  1,1,$3,$4,0.0*
1276
1286
  `.trim()
@@ -1284,12 +1294,12 @@ var defineCommonMacros = (glayer) => {
1284
1294
  0 $3 = Circle diameter (equal to width)*
1285
1295
  0 $4 = Circle center offset
1286
1296
  0 21 = Center Line(Exposure, Width, Height, Center X, Center Y, Rotation)*
1287
- 21,1,$1,$2,0.0,0.0,0.0*
1297
+ 21,1,$1,$2-$3,0.0,0.0,0.0*
1288
1298
  1,1,$3,0.0,0.0-$4*
1289
1299
  1,1,$3,0.0,$4*
1290
1300
  `.trim()
1291
1301
  }).add("define_macro_aperture_template", {
1292
- macro_name: "RoundRect",
1302
+ macro_name: "ROUNDRECT",
1293
1303
  template_code: `
1294
1304
  0 Rectangle with rounded corners*
1295
1305
  0 $1 Corner radius*
@@ -1355,6 +1365,26 @@ var getLayerRefFromGerberLayerName = (glayer_name) => {
1355
1365
  if (innerLayerMatch) return `inner${innerLayerMatch[1]}`;
1356
1366
  throw new Error(`Could not infer layer ref from ${glayer_name}`);
1357
1367
  };
1368
+ var getClampedRoundedRectCornerRadius = (width, height, cornerRadius) => {
1369
+ if (typeof cornerRadius !== "number") return 0;
1370
+ return Math.max(0, Math.min(cornerRadius, width / 2, height / 2));
1371
+ };
1372
+ var getRoundedRectApertureConfig = (width, height, cornerRadius) => {
1373
+ const halfWidth = width / 2;
1374
+ const halfHeight = height / 2;
1375
+ return {
1376
+ macro_name: "ROUNDRECT",
1377
+ corner_radius: cornerRadius,
1378
+ corner_1_x: -halfWidth + cornerRadius,
1379
+ corner_1_y: halfHeight - cornerRadius,
1380
+ corner_2_x: halfWidth - cornerRadius,
1381
+ corner_2_y: halfHeight - cornerRadius,
1382
+ corner_3_x: halfWidth - cornerRadius,
1383
+ corner_3_y: -halfHeight + cornerRadius,
1384
+ corner_4_x: -halfWidth + cornerRadius,
1385
+ corner_4_y: -halfHeight + cornerRadius
1386
+ };
1387
+ };
1358
1388
  function defineAperturesForLayer({
1359
1389
  glayer,
1360
1390
  circuitJson,
@@ -1408,6 +1438,16 @@ var REGION_APERTURE_CONFIG = {
1408
1438
  diameter: 1e-3
1409
1439
  };
1410
1440
  var getApertureConfigFromPcbSmtpad = (elm) => {
1441
+ if (elm.shape === "rect" || elm.shape === "rotated_rect") {
1442
+ const cornerRadius = getClampedRoundedRectCornerRadius(
1443
+ elm.width,
1444
+ elm.height,
1445
+ "corner_radius" in elm ? elm.corner_radius : void 0
1446
+ );
1447
+ if (cornerRadius > 0) {
1448
+ return getRoundedRectApertureConfig(elm.width, elm.height, cornerRadius);
1449
+ }
1450
+ }
1411
1451
  if (elm.shape === "rect") {
1412
1452
  return {
1413
1453
  standard_template_code: "R",
@@ -1428,27 +1468,15 @@ var getApertureConfigFromPcbSmtpad = (elm) => {
1428
1468
  y_size: elm.height
1429
1469
  };
1430
1470
  }
1431
- if (elm.shape === "pill") {
1471
+ if (elm.shape === "pill" || elm.shape === "rotated_pill") {
1432
1472
  if (!("width" in elm && "height" in elm)) {
1433
1473
  throw new Error(
1434
1474
  "Invalid pill shape in getApertureConfigFromPcbSmtpad: missing dimensions"
1435
1475
  );
1436
1476
  }
1437
- if (elm.width >= elm.height) {
1438
- return {
1439
- macro_name: "HORZPILL",
1440
- x_size: elm.width,
1441
- y_size: elm.height,
1442
- circle_diameter: Math.min(elm.width, elm.height),
1443
- circle_center_offset: elm.width / 2
1444
- };
1445
- }
1446
1477
  return {
1447
- macro_name: "VERTPILL",
1448
- x_size: elm.width,
1449
- y_size: elm.height,
1450
- circle_diameter: Math.min(elm.width, elm.height),
1451
- circle_center_offset: elm.height / 2
1478
+ standard_template_code: "C",
1479
+ diameter: Math.min(elm.width, elm.height)
1452
1480
  };
1453
1481
  }
1454
1482
  if (elm.shape === "polygon") {
@@ -1461,6 +1489,18 @@ var getApertureConfigFromPcbSmtpadSoldermask = (elm) => {
1461
1489
  if ("soldermask_margin" in elm && typeof elm.soldermask_margin === "number") {
1462
1490
  soldermaskMargin = elm.soldermask_margin;
1463
1491
  }
1492
+ if (elm.shape === "rect" || elm.shape === "rotated_rect") {
1493
+ const width = elm.width + soldermaskMargin * 2;
1494
+ const height = elm.height + soldermaskMargin * 2;
1495
+ const cornerRadius = getClampedRoundedRectCornerRadius(
1496
+ width,
1497
+ height,
1498
+ "corner_radius" in elm && typeof elm.corner_radius === "number" ? elm.corner_radius + soldermaskMargin : void 0
1499
+ );
1500
+ if (cornerRadius > 0) {
1501
+ return getRoundedRectApertureConfig(width, height, cornerRadius);
1502
+ }
1503
+ }
1464
1504
  if (elm.shape === "rect") {
1465
1505
  return {
1466
1506
  standard_template_code: "R",
@@ -1481,7 +1521,7 @@ var getApertureConfigFromPcbSmtpadSoldermask = (elm) => {
1481
1521
  y_size: elm.height + soldermaskMargin * 2
1482
1522
  };
1483
1523
  }
1484
- if (elm.shape === "pill") {
1524
+ if (elm.shape === "pill" || elm.shape === "rotated_pill") {
1485
1525
  if (!("width" in elm && "height" in elm)) {
1486
1526
  throw new Error(
1487
1527
  "Invalid pill shape in getApertureConfigFromPcbSmtpadSoldermask: missing dimensions"
@@ -1489,21 +1529,9 @@ var getApertureConfigFromPcbSmtpadSoldermask = (elm) => {
1489
1529
  }
1490
1530
  const width = elm.width + soldermaskMargin * 2;
1491
1531
  const height = elm.height + soldermaskMargin * 2;
1492
- if (width >= height) {
1493
- return {
1494
- macro_name: "HORZPILL",
1495
- x_size: width,
1496
- y_size: height,
1497
- circle_diameter: Math.min(width, height),
1498
- circle_center_offset: width / 2
1499
- };
1500
- }
1501
1532
  return {
1502
- macro_name: "VERTPILL",
1503
- x_size: width,
1504
- y_size: height,
1505
- circle_diameter: Math.min(width, height),
1506
- circle_center_offset: height / 2
1533
+ standard_template_code: "C",
1534
+ diameter: Math.min(width, height)
1507
1535
  };
1508
1536
  }
1509
1537
  if (elm.shape === "polygon") {
@@ -1573,7 +1601,7 @@ var getApertureConfigFromPcbSolderPaste = (elm) => {
1573
1601
  x_size: elm.width,
1574
1602
  y_size: elm.height,
1575
1603
  circle_diameter: Math.min(elm.width, elm.height),
1576
- circle_center_offset: elm.width / 2
1604
+ circle_center_offset: (elm.width - elm.height) / 2
1577
1605
  };
1578
1606
  }
1579
1607
  return {
@@ -1581,7 +1609,7 @@ var getApertureConfigFromPcbSolderPaste = (elm) => {
1581
1609
  x_size: elm.width,
1582
1610
  y_size: elm.height,
1583
1611
  circle_diameter: Math.min(elm.width, elm.height),
1584
- circle_center_offset: elm.height / 2
1612
+ circle_center_offset: (elm.height - elm.width) / 2
1585
1613
  };
1586
1614
  }
1587
1615
  throw new Error(`Unsupported shape ${elm.shape}`);
@@ -1610,7 +1638,7 @@ var getApertureConfigFromPcbPlatedHole = (elm) => {
1610
1638
  x_size: elm.outer_width,
1611
1639
  y_size: elm.outer_height,
1612
1640
  circle_diameter: Math.min(elm.outer_width, elm.outer_height),
1613
- circle_center_offset: elm.outer_width / 2
1641
+ circle_center_offset: (elm.outer_width - elm.outer_height) / 2
1614
1642
  };
1615
1643
  }
1616
1644
  return {
@@ -1618,7 +1646,7 @@ var getApertureConfigFromPcbPlatedHole = (elm) => {
1618
1646
  x_size: elm.outer_width,
1619
1647
  y_size: elm.outer_height,
1620
1648
  circle_diameter: Math.min(elm.outer_width, elm.outer_height),
1621
- circle_center_offset: elm.outer_height / 2
1649
+ circle_center_offset: (elm.outer_height - elm.outer_width) / 2
1622
1650
  };
1623
1651
  }
1624
1652
  const shape = elm.shape;
@@ -1668,7 +1696,7 @@ var getApertureConfigFromPcbPlatedHoleSoldermask = (elm) => {
1668
1696
  x_size: outerWidth,
1669
1697
  y_size: outerHeight,
1670
1698
  circle_diameter: Math.min(outerWidth, outerHeight),
1671
- circle_center_offset: outerWidth / 2
1699
+ circle_center_offset: (outerWidth - outerHeight) / 2
1672
1700
  };
1673
1701
  }
1674
1702
  return {
@@ -1676,7 +1704,7 @@ var getApertureConfigFromPcbPlatedHoleSoldermask = (elm) => {
1676
1704
  x_size: outerWidth,
1677
1705
  y_size: outerHeight,
1678
1706
  circle_diameter: Math.min(outerWidth, outerHeight),
1679
- circle_center_offset: outerHeight / 2
1707
+ circle_center_offset: (outerHeight - outerWidth) / 2
1680
1708
  };
1681
1709
  }
1682
1710
  const shape = elm.shape;
@@ -1864,7 +1892,7 @@ var findApertureNumber = (glayer, search_params) => {
1864
1892
  // package.json
1865
1893
  var package_default = {
1866
1894
  name: "circuit-json-to-gerber",
1867
- version: "0.0.68",
1895
+ version: "0.0.70",
1868
1896
  main: "dist/index.js",
1869
1897
  type: "module",
1870
1898
  scripts: {
@@ -2097,6 +2125,55 @@ var convertSoupToGerberCommands = (circuitJson, opts = {}) => {
2097
2125
  }).build()
2098
2126
  );
2099
2127
  const mfy = (y) => opts.flip_y_axis ? -y : y;
2128
+ const renderPillFlash = ({
2129
+ glayer,
2130
+ x,
2131
+ y,
2132
+ width,
2133
+ height,
2134
+ rotationDegrees = 0
2135
+ }) => {
2136
+ const circleApertureConfig = {
2137
+ standard_template_code: "C",
2138
+ diameter: Math.min(width, height)
2139
+ };
2140
+ const apertureNumber = findApertureNumber(glayer, circleApertureConfig);
2141
+ const gb = gerberBuilder().add("select_aperture", {
2142
+ aperture_number: apertureNumber
2143
+ });
2144
+ const offset = Math.abs(width - height) / 2;
2145
+ const rotationRadians = rotationDegrees * Math.PI / 180;
2146
+ const cosTheta = Math.cos(rotationRadians);
2147
+ const sinTheta = Math.sin(rotationRadians);
2148
+ const rotateAndTranslate = (dx, dy) => ({
2149
+ x: x + dx * cosTheta - dy * sinTheta,
2150
+ y: y + dx * sinTheta + dy * cosTheta
2151
+ });
2152
+ if (offset <= 1e-9) {
2153
+ gb.add("flash_operation", { x, y: mfy(y) });
2154
+ glayer.push(...gb.build());
2155
+ return;
2156
+ }
2157
+ const isHorizontal = width >= height;
2158
+ const startRelative = isHorizontal ? { x: -offset, y: 0 } : { x: 0, y: -offset };
2159
+ const endRelative = isHorizontal ? { x: offset, y: 0 } : { x: 0, y: offset };
2160
+ const startPoint = rotateAndTranslate(startRelative.x, startRelative.y);
2161
+ const endPoint = rotateAndTranslate(endRelative.x, endRelative.y);
2162
+ gb.add("flash_operation", {
2163
+ x: startPoint.x,
2164
+ y: mfy(startPoint.y)
2165
+ }).add("move_operation", {
2166
+ x: startPoint.x,
2167
+ y: mfy(startPoint.y)
2168
+ }).add("plot_operation", {
2169
+ x: endPoint.x,
2170
+ y: mfy(endPoint.y)
2171
+ }).add("flash_operation", {
2172
+ x: endPoint.x,
2173
+ y: mfy(endPoint.y)
2174
+ });
2175
+ glayer.push(...gb.build());
2176
+ };
2100
2177
  const getRegionApertureNumber = (glayer) => findApertureNumber(glayer, REGION_APERTURE_CONFIG);
2101
2178
  const addClosedRegionFromPoints = ({
2102
2179
  target,
@@ -2550,6 +2627,28 @@ var convertSoupToGerberCommands = (circuitJson, opts = {}) => {
2550
2627
  );
2551
2628
  } else if (element.type === "pcb_smtpad" && element.shape !== "polygon") {
2552
2629
  if (element.layer === layer && outerLayerRefs.includes(layer)) {
2630
+ if (element.shape === "pill" || element.shape === "rotated_pill") {
2631
+ const soldermaskMargin = typeof element.soldermask_margin === "number" ? element.soldermask_margin : 0;
2632
+ const rotation2 = element.shape === "rotated_pill" && typeof element.ccw_rotation === "number" ? element.ccw_rotation : 0;
2633
+ renderPillFlash({
2634
+ glayer: glayers[getGerberLayerName(layer, "copper")],
2635
+ x: element.x,
2636
+ y: element.y,
2637
+ width: element.width,
2638
+ height: element.height,
2639
+ rotationDegrees: rotation2
2640
+ });
2641
+ renderPillFlash({
2642
+ glayer: glayers[getGerberLayerName(layer, "soldermask")],
2643
+ x: element.x,
2644
+ y: element.y,
2645
+ width: element.width + soldermaskMargin * 2,
2646
+ height: element.height + soldermaskMargin * 2,
2647
+ rotationDegrees: rotation2
2648
+ });
2649
+ continue;
2650
+ }
2651
+ const rotation = element.shape === "rotated_rect" && typeof element.ccw_rotation === "number" ? element.ccw_rotation : 0;
2553
2652
  for (const { glayer, apertureConfig } of [
2554
2653
  {
2555
2654
  glayer: glayers[getGerberLayerName(layer, "copper")],
@@ -2564,13 +2663,13 @@ var convertSoupToGerberCommands = (circuitJson, opts = {}) => {
2564
2663
  const gb = gerberBuilder().add("select_aperture", {
2565
2664
  aperture_number: apertureNumber
2566
2665
  });
2567
- if (element.shape === "rotated_rect" && element.ccw_rotation) {
2666
+ if (rotation) {
2568
2667
  gb.add("load_rotation", {
2569
- rotation_degrees: element.ccw_rotation
2668
+ rotation_degrees: rotation
2570
2669
  });
2571
2670
  }
2572
2671
  gb.add("flash_operation", { x: element.x, y: mfy(element.y) });
2573
- if (element.shape === "rotated_rect" && element.ccw_rotation) {
2672
+ if (rotation) {
2574
2673
  gb.add("load_rotation", { rotation_degrees: 0 });
2575
2674
  }
2576
2675
  glayer.push(...gb.build());
@@ -3032,4 +3131,4 @@ export {
3032
3131
  stringifyGerberCommands,
3033
3132
  convertSoupToGerberCommands
3034
3133
  };
3035
- //# sourceMappingURL=chunk-ERVO7VGV.js.map
3134
+ //# sourceMappingURL=chunk-4OKFNZE6.js.map