@tscircuit/3d-viewer 0.0.407 → 0.0.409

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.
Files changed (2) hide show
  1. package/dist/index.js +397 -249
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6436,7 +6436,7 @@ var require_interpolateBetween2DPointsForY = __commonJS({
6436
6436
  var require_intersect = __commonJS({
6437
6437
  "node_modules/@jscad/modeling/src/maths/utils/intersect.js"(exports, module) {
6438
6438
  "use strict";
6439
- var intersect = (p1, p2, p3, p4) => {
6439
+ var intersect3 = (p1, p2, p3, p4) => {
6440
6440
  if (p1[0] === p2[0] && p1[1] === p2[1] || p3[0] === p4[0] && p3[1] === p4[1]) {
6441
6441
  return void 0;
6442
6442
  }
@@ -6453,7 +6453,7 @@ var require_intersect = __commonJS({
6453
6453
  const y = p1[1] + ua * (p2[1] - p1[1]);
6454
6454
  return [x, y];
6455
6455
  };
6456
- module.exports = intersect;
6456
+ module.exports = intersect3;
6457
6457
  }
6458
6458
  });
6459
6459
 
@@ -11424,7 +11424,7 @@ var require_intersectGeom3 = __commonJS({
11424
11424
  var flatten = require_flatten();
11425
11425
  var retessellate = require_retessellate();
11426
11426
  var intersectSub = require_intersectGeom3Sub();
11427
- var intersect = (...geometries2) => {
11427
+ var intersect3 = (...geometries2) => {
11428
11428
  geometries2 = flatten(geometries2);
11429
11429
  let newgeometry = geometries2.shift();
11430
11430
  geometries2.forEach((geometry) => {
@@ -11433,7 +11433,7 @@ var require_intersectGeom3 = __commonJS({
11433
11433
  newgeometry = retessellate(newgeometry);
11434
11434
  return newgeometry;
11435
11435
  };
11436
- module.exports = intersect;
11436
+ module.exports = intersect3;
11437
11437
  }
11438
11438
  });
11439
11439
 
@@ -11447,14 +11447,14 @@ var require_intersectGeom2 = __commonJS({
11447
11447
  var fromFakePolygons = require_fromFakePolygons();
11448
11448
  var to3DWalls = require_to3DWalls();
11449
11449
  var intersectGeom3 = require_intersectGeom3();
11450
- var intersect = (...geometries2) => {
11450
+ var intersect3 = (...geometries2) => {
11451
11451
  geometries2 = flatten(geometries2);
11452
11452
  const newgeometries = geometries2.map((geometry) => to3DWalls({ z0: -1, z1: 1 }, geometry));
11453
11453
  const newgeom3 = intersectGeom3(newgeometries);
11454
11454
  const epsilon = measureEpsilon(newgeom3);
11455
11455
  return fromFakePolygons(epsilon, geom3.toPolygons(newgeom3));
11456
11456
  };
11457
- module.exports = intersect;
11457
+ module.exports = intersect3;
11458
11458
  }
11459
11459
  });
11460
11460
 
@@ -11468,7 +11468,7 @@ var require_intersect2 = __commonJS({
11468
11468
  var geom3 = require_geom3();
11469
11469
  var intersectGeom2 = require_intersectGeom2();
11470
11470
  var intersectGeom3 = require_intersectGeom3();
11471
- var intersect = (...geometries2) => {
11471
+ var intersect3 = (...geometries2) => {
11472
11472
  geometries2 = flatten(geometries2);
11473
11473
  if (geometries2.length === 0) throw new Error("wrong number of arguments");
11474
11474
  if (!areAllShapesTheSameType(geometries2)) {
@@ -11479,7 +11479,7 @@ var require_intersect2 = __commonJS({
11479
11479
  if (geom3.isA(geometry)) return intersectGeom3(geometries2);
11480
11480
  return geometry;
11481
11481
  };
11482
- module.exports = intersect;
11482
+ module.exports = intersect3;
11483
11483
  }
11484
11484
  });
11485
11485
 
@@ -11787,7 +11787,7 @@ var require_offsetFromPoints = __commonJS({
11787
11787
  "node_modules/@jscad/modeling/src/operations/expansions/offsetFromPoints.js"(exports, module) {
11788
11788
  "use strict";
11789
11789
  var { EPS, TAU } = require_constants();
11790
- var intersect = require_intersect();
11790
+ var intersect3 = require_intersect();
11791
11791
  var line22 = require_line2();
11792
11792
  var vec2 = require_vec2();
11793
11793
  var area = require_area();
@@ -11822,7 +11822,7 @@ var require_offsetFromPoints = __commonJS({
11822
11822
  const currentSegment = [n0, n1];
11823
11823
  if (previousSegment != null) {
11824
11824
  if (closed || !closed && j !== 0) {
11825
- const ip = intersect(previousSegment[0], previousSegment[1], currentSegment[0], currentSegment[1]);
11825
+ const ip = intersect3(previousSegment[0], previousSegment[1], currentSegment[0], currentSegment[1]);
11826
11826
  if (ip) {
11827
11827
  newPoints.pop();
11828
11828
  currentSegment[0] = ip;
@@ -11839,7 +11839,7 @@ var require_offsetFromPoints = __commonJS({
11839
11839
  if (closed && previousSegment != null) {
11840
11840
  const n0 = newPoints[0];
11841
11841
  const n1 = newPoints[1];
11842
- const ip = intersect(previousSegment[0], previousSegment[1], n0, n1);
11842
+ const ip = intersect3(previousSegment[0], previousSegment[1], n0, n1);
11843
11843
  if (ip) {
11844
11844
  newPoints[0] = ip;
11845
11845
  newPoints.pop();
@@ -12269,7 +12269,7 @@ var require_expand = __commonJS({
12269
12269
  var expandGeom2 = require_expandGeom2();
12270
12270
  var expandGeom3 = require_expandGeom3();
12271
12271
  var expandPath2 = require_expandPath2();
12272
- var expand3 = (options, ...objects) => {
12272
+ var expand4 = (options, ...objects) => {
12273
12273
  objects = flatten(objects);
12274
12274
  if (objects.length === 0) throw new Error("wrong number of arguments");
12275
12275
  const results = objects.map((object) => {
@@ -12280,7 +12280,7 @@ var require_expand = __commonJS({
12280
12280
  });
12281
12281
  return results.length === 1 ? results[0] : results;
12282
12282
  };
12283
- module.exports = expand3;
12283
+ module.exports = expand4;
12284
12284
  }
12285
12285
  });
12286
12286
 
@@ -12478,7 +12478,7 @@ var require_extrudeRectangularPath2 = __commonJS({
12478
12478
  "node_modules/@jscad/modeling/src/operations/extrusions/extrudeRectangularPath2.js"(exports, module) {
12479
12479
  "use strict";
12480
12480
  var path2 = require_path2();
12481
- var expand3 = require_expand();
12481
+ var expand4 = require_expand();
12482
12482
  var extrudeLinearGeom2 = require_extrudeLinearGeom2();
12483
12483
  var extrudeRectangularPath2 = (options, geometry) => {
12484
12484
  const defaults = {
@@ -12490,7 +12490,7 @@ var require_extrudeRectangularPath2 = __commonJS({
12490
12490
  options.offset = [0, 0, height10];
12491
12491
  const points = path2.toPoints(geometry);
12492
12492
  if (points.length === 0) throw new Error("the given geometry cannot be empty");
12493
- const newgeometry = expand3(options, geometry);
12493
+ const newgeometry = expand4(options, geometry);
12494
12494
  return extrudeLinearGeom2(options, newgeometry);
12495
12495
  };
12496
12496
  module.exports = extrudeRectangularPath2;
@@ -12504,7 +12504,7 @@ var require_extrudeRectangularGeom2 = __commonJS({
12504
12504
  var { area } = require_utils();
12505
12505
  var geom2 = require_geom2();
12506
12506
  var path2 = require_path2();
12507
- var expand3 = require_expand();
12507
+ var expand4 = require_expand();
12508
12508
  var extrudeLinearGeom2 = require_extrudeLinearGeom2();
12509
12509
  var extrudeRectangularGeom2 = (options, geometry) => {
12510
12510
  const defaults = {
@@ -12518,7 +12518,7 @@ var require_extrudeRectangularGeom2 = __commonJS({
12518
12518
  if (outlines.length === 0) throw new Error("the given geometry cannot be empty");
12519
12519
  const newparts = outlines.map((outline) => {
12520
12520
  if (area(outline) < 0) outline.reverse();
12521
- return expand3(options, path2.fromPoints({ closed: true }, outline));
12521
+ return expand4(options, path2.fromPoints({ closed: true }, outline));
12522
12522
  });
12523
12523
  const allsides = newparts.reduce((sides, part) => sides.concat(geom2.toSides(part)), []);
12524
12524
  const newgeometry = geom2.create(allsides);
@@ -26100,7 +26100,7 @@ import * as THREE13 from "three";
26100
26100
  // package.json
26101
26101
  var package_default = {
26102
26102
  name: "@tscircuit/3d-viewer",
26103
- version: "0.0.406",
26103
+ version: "0.0.408",
26104
26104
  main: "./dist/index.js",
26105
26105
  module: "./dist/index.js",
26106
26106
  type: "module",
@@ -26901,6 +26901,7 @@ var tracesMaterialColors = {
26901
26901
  var import_extrusions = __toESM(require_extrusions(), 1);
26902
26902
  var import_primitives = __toESM(require_primitives(), 1);
26903
26903
  var import_transforms = __toESM(require_transforms(), 1);
26904
+ var import_expansions = __toESM(require_expansions(), 1);
26904
26905
  var arePointsClockwise = (points) => {
26905
26906
  let area = 0;
26906
26907
  for (let i = 0; i < points.length; i++) {
@@ -26911,13 +26912,17 @@ var arePointsClockwise = (points) => {
26911
26912
  const signedArea = area / 2;
26912
26913
  return signedArea <= 0;
26913
26914
  };
26914
- var createBoardGeomWithOutline = (board, depth = 1.2) => {
26915
+ var createBoardGeomWithOutline = (board, depth = 1.2, options = {}) => {
26916
+ const { xyOutset = 0 } = options;
26915
26917
  const { outline } = board;
26916
26918
  let outlineVec2 = outline.map((point2) => [point2.x, point2.y]);
26917
26919
  if (arePointsClockwise(outlineVec2)) {
26918
26920
  outlineVec2 = outlineVec2.reverse();
26919
26921
  }
26920
- const shape = (0, import_primitives.polygon)({ points: outlineVec2 });
26922
+ let shape = (0, import_primitives.polygon)({ points: outlineVec2 });
26923
+ if (xyOutset !== 0) {
26924
+ shape = (0, import_expansions.expand)({ delta: xyOutset, corners: "edge" }, shape);
26925
+ }
26921
26926
  let boardGeom = (0, import_extrusions.extrudeLinear)({ height: depth }, shape);
26922
26927
  boardGeom = (0, import_transforms.translate)([0, 0, -depth / 2], boardGeom);
26923
26928
  return boardGeom;
@@ -26992,6 +26997,7 @@ function extractRectBorderRadius(source) {
26992
26997
  // src/geoms/plated-hole.ts
26993
26998
  var platedHoleLipHeight = 0.05;
26994
26999
  var RECT_PAD_SEGMENTS = 64;
27000
+ var maybeClip = (geom, clipGeom) => clipGeom ? (0, import_booleans.intersect)(clipGeom, geom) : geom;
26995
27001
  var createRectPadGeom = ({
26996
27002
  width: width10,
26997
27003
  height: height10,
@@ -27012,84 +27018,71 @@ var createRectPadGeom = ({
27012
27018
  const offsetZ = center[2] - thickness / 2;
27013
27019
  return (0, import_transforms2.translate)([center[0], center[1], offsetZ], extruded);
27014
27020
  };
27015
- var platedHole = (plated_hole, ctx) => {
27021
+ var platedHole = (plated_hole, ctx, options = {}) => {
27022
+ const { clipGeom } = options;
27016
27023
  if (!plated_hole.shape) plated_hole.shape = "circle";
27024
+ const throughDrillHeight = ctx.pcbThickness + 2 * platedHoleLipHeight + 4 * M;
27017
27025
  if (plated_hole.shape === "circle") {
27018
- return (0, import_colors2.colorize)(
27019
- colors.copper,
27020
- (0, import_booleans.subtract)(
27021
- (0, import_booleans.union)(
27022
- (0, import_primitives3.cylinder)({
27023
- center: [plated_hole.x, plated_hole.y, 0],
27024
- radius: plated_hole.hole_diameter / 2,
27025
- height: ctx.pcbThickness
27026
- }),
27027
- (0, import_primitives3.cylinder)({
27028
- center: [
27029
- plated_hole.x,
27030
- plated_hole.y,
27031
- ctx.pcbThickness / 2 + platedHoleLipHeight / 2 + M
27032
- ],
27033
- radius: plated_hole.outer_diameter / 2,
27034
- height: platedHoleLipHeight
27035
- }),
27036
- (0, import_primitives3.cylinder)({
27037
- center: [
27038
- plated_hole.x,
27039
- plated_hole.y,
27040
- -ctx.pcbThickness / 2 - platedHoleLipHeight / 2 - M
27041
- ],
27042
- radius: plated_hole.outer_diameter / 2,
27043
- height: platedHoleLipHeight
27044
- })
27045
- ),
27046
- (0, import_primitives3.cylinder)({
27047
- center: [plated_hole.x, plated_hole.y, 0],
27048
- radius: plated_hole.hole_diameter / 2 - M,
27049
- height: 1.5
27050
- })
27051
- )
27052
- );
27026
+ const outerDiameter = plated_hole.outer_diameter ?? Math.max(plated_hole.hole_diameter, 0);
27027
+ const copperHeight = ctx.pcbThickness + 2 * (platedHoleLipHeight + M);
27028
+ const copperBody = (0, import_primitives3.cylinder)({
27029
+ center: [plated_hole.x, plated_hole.y, 0],
27030
+ radius: outerDiameter / 2,
27031
+ height: copperHeight
27032
+ });
27033
+ const copperSolid = maybeClip(copperBody, clipGeom);
27034
+ const drill = (0, import_primitives3.cylinder)({
27035
+ center: [plated_hole.x, plated_hole.y, 0],
27036
+ radius: Math.max(plated_hole.hole_diameter / 2, 0.01),
27037
+ height: throughDrillHeight
27038
+ });
27039
+ return (0, import_colors2.colorize)(colors.copper, (0, import_booleans.subtract)(copperSolid, drill));
27053
27040
  }
27054
27041
  if (plated_hole.shape === "circular_hole_with_rect_pad") {
27055
27042
  const padWidth = plated_hole.rect_pad_width || plated_hole.hole_diameter;
27056
27043
  const padHeight = plated_hole.rect_pad_height || plated_hole.hole_diameter;
27057
27044
  const rectBorderRadius = extractRectBorderRadius(plated_hole);
27058
- return (0, import_colors2.colorize)(
27059
- colors.copper,
27060
- (0, import_booleans.subtract)(
27061
- (0, import_booleans.union)(
27062
- // Top rectangular pad
27063
- createRectPadGeom({
27064
- width: padWidth,
27065
- height: padHeight,
27066
- thickness: platedHoleLipHeight,
27067
- center: [plated_hole.x, plated_hole.y, 1.2 / 2],
27068
- borderRadius: rectBorderRadius
27069
- }),
27070
- // Bottom rectangular pad
27071
- createRectPadGeom({
27072
- width: padWidth,
27073
- height: padHeight,
27074
- thickness: platedHoleLipHeight,
27075
- center: [plated_hole.x, plated_hole.y, -1.2 / 2],
27076
- borderRadius: rectBorderRadius
27077
- }),
27078
- // Plated barrel around hole
27079
- (0, import_primitives3.cylinder)({
27080
- center: [plated_hole.x, plated_hole.y, 0],
27081
- radius: plated_hole.hole_diameter / 2,
27082
- height: 1.2
27083
- })
27084
- ),
27085
- // Subtract actual hole through
27045
+ const copperSolid = maybeClip(
27046
+ (0, import_booleans.union)(
27047
+ // Top rectangular pad
27048
+ createRectPadGeom({
27049
+ width: padWidth,
27050
+ height: padHeight,
27051
+ thickness: platedHoleLipHeight,
27052
+ center: [
27053
+ plated_hole.x,
27054
+ plated_hole.y,
27055
+ ctx.pcbThickness / 2 + platedHoleLipHeight / 2 + M
27056
+ ],
27057
+ borderRadius: rectBorderRadius
27058
+ }),
27059
+ // Bottom rectangular pad
27060
+ createRectPadGeom({
27061
+ width: padWidth,
27062
+ height: padHeight,
27063
+ thickness: platedHoleLipHeight,
27064
+ center: [
27065
+ plated_hole.x,
27066
+ plated_hole.y,
27067
+ -ctx.pcbThickness / 2 - platedHoleLipHeight / 2 - M
27068
+ ],
27069
+ borderRadius: rectBorderRadius
27070
+ }),
27071
+ // Plated barrel around hole
27086
27072
  (0, import_primitives3.cylinder)({
27087
27073
  center: [plated_hole.x, plated_hole.y, 0],
27088
- radius: Math.max(plated_hole.hole_diameter / 2 - M, 0.01),
27089
- height: 1.5
27074
+ radius: plated_hole.hole_diameter / 2,
27075
+ height: ctx.pcbThickness
27090
27076
  })
27091
- )
27077
+ ),
27078
+ clipGeom
27092
27079
  );
27080
+ const drill = (0, import_primitives3.cylinder)({
27081
+ center: [plated_hole.x, plated_hole.y, 0],
27082
+ radius: Math.max(plated_hole.hole_diameter / 2 - M, 0.01),
27083
+ height: throughDrillHeight
27084
+ });
27085
+ return (0, import_colors2.colorize)(colors.copper, (0, import_booleans.subtract)(copperSolid, drill));
27093
27086
  }
27094
27087
  if (plated_hole.shape === "pill") {
27095
27088
  const shouldRotate = plated_hole.hole_height > plated_hole.hole_width;
@@ -27101,76 +27094,56 @@ var platedHole = (plated_hole, ctx) => {
27101
27094
  const outerRadius = outerPillHeight / 2;
27102
27095
  const rectLength = Math.abs(holeWidth - holeHeight);
27103
27096
  const outerRectLength = Math.abs(outerPillWidth - outerPillHeight);
27104
- const mainRectBarrel = (0, import_primitives3.cuboid)({
27105
- center: [plated_hole.x, plated_hole.y, 0],
27106
- size: shouldRotate ? [holeHeight, rectLength, ctx.pcbThickness] : [rectLength, holeHeight, ctx.pcbThickness]
27107
- });
27108
- const leftCapBarrel = (0, import_primitives3.cylinder)({
27109
- center: shouldRotate ? [plated_hole.x, plated_hole.y - rectLength / 2, 0] : [plated_hole.x - rectLength / 2, plated_hole.y, 0],
27110
- radius: holeRadius,
27111
- height: ctx.pcbThickness
27112
- });
27113
- const rightCapBarrel = (0, import_primitives3.cylinder)({
27114
- center: shouldRotate ? [plated_hole.x, plated_hole.y + rectLength / 2, 0] : [plated_hole.x + rectLength / 2, plated_hole.y, 0],
27115
- radius: holeRadius,
27116
- height: ctx.pcbThickness
27117
- });
27118
- const barrelUnion = (0, import_booleans.union)(mainRectBarrel, leftCapBarrel, rightCapBarrel);
27119
- const topLipZ = ctx.pcbThickness / 2 + platedHoleLipHeight / 2 + M;
27120
- const topLipRect = (0, import_primitives3.cuboid)({
27121
- center: [plated_hole.x, plated_hole.y, topLipZ],
27122
- size: shouldRotate ? [outerPillHeight, outerRectLength, platedHoleLipHeight] : [outerRectLength, outerPillHeight, platedHoleLipHeight]
27123
- });
27124
- const topLipLeftCap = (0, import_primitives3.cylinder)({
27125
- center: shouldRotate ? [plated_hole.x, plated_hole.y - outerRectLength / 2, topLipZ] : [plated_hole.x - outerRectLength / 2, plated_hole.y, topLipZ],
27126
- radius: outerRadius,
27127
- height: platedHoleLipHeight
27128
- });
27129
- const topLipRightCap = (0, import_primitives3.cylinder)({
27130
- center: shouldRotate ? [plated_hole.x, plated_hole.y + outerRectLength / 2, topLipZ] : [plated_hole.x + outerRectLength / 2, plated_hole.y, topLipZ],
27131
- radius: outerRadius,
27132
- height: platedHoleLipHeight
27133
- });
27134
- const topLipUnion = (0, import_booleans.union)(topLipRect, topLipLeftCap, topLipRightCap);
27135
- const bottomLipZ = -ctx.pcbThickness / 2 - platedHoleLipHeight / 2 - M;
27136
- const bottomLipRect = (0, import_primitives3.cuboid)({
27137
- center: [plated_hole.x, plated_hole.y, bottomLipZ],
27138
- size: shouldRotate ? [outerPillHeight, outerRectLength, platedHoleLipHeight] : [outerRectLength, outerPillHeight, platedHoleLipHeight]
27139
- });
27140
- const bottomLipLeftCap = (0, import_primitives3.cylinder)({
27141
- center: shouldRotate ? [plated_hole.x, plated_hole.y - outerRectLength / 2, bottomLipZ] : [plated_hole.x - outerRectLength / 2, plated_hole.y, bottomLipZ],
27142
- radius: outerRadius,
27143
- height: platedHoleLipHeight
27144
- });
27145
- const bottomLipRightCap = (0, import_primitives3.cylinder)({
27146
- center: shouldRotate ? [plated_hole.x, plated_hole.y + outerRectLength / 2, bottomLipZ] : [plated_hole.x + outerRectLength / 2, plated_hole.y, bottomLipZ],
27147
- radius: outerRadius,
27148
- height: platedHoleLipHeight
27149
- });
27150
- const bottomLipUnion = (0, import_booleans.union)(
27151
- bottomLipRect,
27152
- bottomLipLeftCap,
27153
- bottomLipRightCap
27097
+ const copperHeight = ctx.pcbThickness + 2 * (platedHoleLipHeight + M);
27098
+ const createPillSection = (width10, height10, thickness) => {
27099
+ const radius = height10 / 2;
27100
+ const length2 = Math.abs(width10 - height10);
27101
+ if (length2 <= 1e-6) {
27102
+ return (0, import_primitives3.cylinder)({
27103
+ center: [plated_hole.x, plated_hole.y, 0],
27104
+ radius,
27105
+ height: thickness
27106
+ });
27107
+ }
27108
+ const rect = (0, import_primitives3.cuboid)({
27109
+ center: [plated_hole.x, plated_hole.y, 0],
27110
+ size: shouldRotate ? [height10, length2, thickness] : [length2, height10, thickness]
27111
+ });
27112
+ const leftCap = (0, import_primitives3.cylinder)({
27113
+ center: shouldRotate ? [plated_hole.x, plated_hole.y - length2 / 2, 0] : [plated_hole.x - length2 / 2, plated_hole.y, 0],
27114
+ radius,
27115
+ height: thickness
27116
+ });
27117
+ const rightCap = (0, import_primitives3.cylinder)({
27118
+ center: shouldRotate ? [plated_hole.x, plated_hole.y + length2 / 2, 0] : [plated_hole.x + length2 / 2, plated_hole.y, 0],
27119
+ radius,
27120
+ height: thickness
27121
+ });
27122
+ return (0, import_booleans.union)(rect, leftCap, rightCap);
27123
+ };
27124
+ const outerBarrel = createPillSection(
27125
+ outerPillWidth,
27126
+ outerPillHeight,
27127
+ copperHeight
27154
27128
  );
27155
27129
  const drillRect = (0, import_primitives3.cuboid)({
27156
27130
  center: [plated_hole.x, plated_hole.y, 0],
27157
- size: shouldRotate ? [holeHeight - 2 * M, rectLength, ctx.pcbThickness + 2 * M] : [rectLength, holeHeight - 2 * M, ctx.pcbThickness + 2 * M]
27131
+ size: shouldRotate ? [holeHeight - 2 * M, rectLength, throughDrillHeight] : [rectLength, holeHeight - 2 * M, throughDrillHeight]
27158
27132
  });
27159
27133
  const drillLeftCap = (0, import_primitives3.cylinder)({
27160
27134
  center: shouldRotate ? [plated_hole.x, plated_hole.y - rectLength / 2, 0] : [plated_hole.x - rectLength / 2, plated_hole.y, 0],
27161
27135
  radius: holeRadius - M,
27162
- height: ctx.pcbThickness + 2 * M
27136
+ height: throughDrillHeight
27163
27137
  });
27164
27138
  const drillRightCap = (0, import_primitives3.cylinder)({
27165
27139
  center: shouldRotate ? [plated_hole.x, plated_hole.y + rectLength / 2, 0] : [plated_hole.x + rectLength / 2, plated_hole.y, 0],
27166
27140
  radius: holeRadius - M,
27167
- height: ctx.pcbThickness + 2 * M
27141
+ height: throughDrillHeight
27168
27142
  });
27169
27143
  const drillUnion = (0, import_booleans.union)(drillRect, drillLeftCap, drillRightCap);
27170
- return (0, import_colors2.colorize)(
27171
- colors.copper,
27172
- (0, import_booleans.subtract)((0, import_booleans.union)(barrelUnion, topLipUnion, bottomLipUnion), drillUnion)
27173
- );
27144
+ const copperSolid = maybeClip(outerBarrel, clipGeom);
27145
+ const drill = drillUnion;
27146
+ return (0, import_colors2.colorize)(colors.copper, (0, import_booleans.subtract)(copperSolid, drill));
27174
27147
  }
27175
27148
  if (plated_hole.shape === "pill_hole_with_rect_pad") {
27176
27149
  const shouldRotate = plated_hole.hole_height > plated_hole.hole_width;
@@ -27183,52 +27156,62 @@ var platedHole = (plated_hole, ctx) => {
27183
27156
  const rectBorderRadius = extractRectBorderRadius(plated_hole);
27184
27157
  const mainRect = (0, import_primitives3.cuboid)({
27185
27158
  center: [plated_hole.x, plated_hole.y, 0],
27186
- size: shouldRotate ? [holeHeight, rectLength, 1.2] : [rectLength, holeHeight, 1.2]
27159
+ size: shouldRotate ? [holeHeight, rectLength, ctx.pcbThickness] : [rectLength, holeHeight, ctx.pcbThickness]
27187
27160
  });
27188
27161
  const leftCap = (0, import_primitives3.cylinder)({
27189
27162
  center: shouldRotate ? [plated_hole.x, plated_hole.y - rectLength / 2, 0] : [plated_hole.x - rectLength / 2, plated_hole.y, 0],
27190
27163
  radius: holeRadius,
27191
- height: 1.2
27164
+ height: ctx.pcbThickness
27192
27165
  });
27193
27166
  const rightCap = (0, import_primitives3.cylinder)({
27194
27167
  center: shouldRotate ? [plated_hole.x, plated_hole.y + rectLength / 2, 0] : [plated_hole.x + rectLength / 2, plated_hole.y, 0],
27195
27168
  radius: holeRadius,
27196
- height: 1.2
27169
+ height: ctx.pcbThickness
27197
27170
  });
27198
27171
  const topPad = createRectPadGeom({
27199
27172
  width: padWidth,
27200
27173
  height: padHeight,
27201
27174
  thickness: platedHoleLipHeight,
27202
- center: [plated_hole.x, plated_hole.y, 1.2 / 2],
27175
+ center: [
27176
+ plated_hole.x,
27177
+ plated_hole.y,
27178
+ ctx.pcbThickness / 2 + platedHoleLipHeight / 2 + M
27179
+ ],
27203
27180
  borderRadius: rectBorderRadius
27204
27181
  });
27205
27182
  const bottomPad = createRectPadGeom({
27206
27183
  width: padWidth,
27207
27184
  height: padHeight,
27208
27185
  thickness: platedHoleLipHeight,
27209
- center: [plated_hole.x, plated_hole.y, -1.2 / 2],
27186
+ center: [
27187
+ plated_hole.x,
27188
+ plated_hole.y,
27189
+ -ctx.pcbThickness / 2 - platedHoleLipHeight / 2 - M
27190
+ ],
27210
27191
  borderRadius: rectBorderRadius
27211
27192
  });
27212
27193
  const holeCut = (0, import_booleans.union)(
27213
27194
  (0, import_primitives3.cuboid)({
27214
27195
  center: [plated_hole.x, plated_hole.y, 0],
27215
- size: shouldRotate ? [holeHeight - platedHoleLipHeight, rectLength, 1.5] : [rectLength, holeHeight - platedHoleLipHeight, 1.5]
27196
+ size: shouldRotate ? [holeHeight - platedHoleLipHeight, rectLength, throughDrillHeight] : [rectLength, holeHeight - platedHoleLipHeight, throughDrillHeight]
27216
27197
  }),
27217
27198
  (0, import_primitives3.cylinder)({
27218
27199
  center: shouldRotate ? [plated_hole.x, plated_hole.y - rectLength / 2, 0] : [plated_hole.x - rectLength / 2, plated_hole.y, 0],
27219
27200
  radius: holeRadius - platedHoleLipHeight,
27220
- height: 1.5
27201
+ height: throughDrillHeight
27221
27202
  }),
27222
27203
  (0, import_primitives3.cylinder)({
27223
27204
  center: shouldRotate ? [plated_hole.x, plated_hole.y + rectLength / 2, 0] : [plated_hole.x + rectLength / 2, plated_hole.y, 0],
27224
27205
  radius: holeRadius - platedHoleLipHeight,
27225
- height: 1.5
27206
+ height: throughDrillHeight
27226
27207
  })
27227
27208
  );
27228
- return (0, import_colors2.colorize)(
27229
- colors.copper,
27230
- (0, import_booleans.subtract)((0, import_booleans.union)(mainRect, leftCap, rightCap, topPad, bottomPad), holeCut)
27209
+ const copperSolid = maybeClip(
27210
+ (0, import_booleans.union)(mainRect, leftCap, rightCap, topPad, bottomPad),
27211
+ clipGeom
27231
27212
  );
27213
+ const drill = holeCut;
27214
+ return (0, import_colors2.colorize)(colors.copper, (0, import_booleans.subtract)(copperSolid, drill));
27232
27215
  } else {
27233
27216
  throw new Error(`Unsupported plated hole shape: ${plated_hole.shape}`);
27234
27217
  }
@@ -27236,7 +27219,7 @@ var platedHole = (plated_hole, ctx) => {
27236
27219
 
27237
27220
  // src/BoardGeomBuilder.ts
27238
27221
  var import_extrusions4 = __toESM(require_extrusions(), 1);
27239
- var import_expansions2 = __toESM(require_expansions(), 1);
27222
+ var import_expansions3 = __toESM(require_expansions(), 1);
27240
27223
 
27241
27224
  // src/geoms/create-geoms-for-silkscreen-text.ts
27242
27225
  var import_text = __toESM(require_text(), 1);
@@ -27541,7 +27524,7 @@ function createSilkscreenTextGeoms(silkscreenText) {
27541
27524
 
27542
27525
  // src/geoms/create-geoms-for-silkscreen-path.ts
27543
27526
  var import_primitives4 = __toESM(require_primitives(), 1);
27544
- var import_expansions = __toESM(require_expansions(), 1);
27527
+ var import_expansions2 = __toESM(require_expansions(), 1);
27545
27528
  var import_extrusions3 = __toESM(require_extrusions(), 1);
27546
27529
  var import_transforms3 = __toESM(require_transforms(), 1);
27547
27530
  var import_colors3 = __toESM(require_colors(), 1);
@@ -27550,7 +27533,7 @@ function createSilkscreenPathGeom(sp, ctx) {
27550
27533
  const routePoints = sp.route.map((p) => [p.x, p.y]);
27551
27534
  const pathLine = (0, import_primitives4.line)(routePoints);
27552
27535
  const strokeWidth = sp.stroke_width || 0.1;
27553
- const expandedPath = (0, import_expansions.expand)(
27536
+ const expandedPath = (0, import_expansions2.expand)(
27554
27537
  { delta: strokeWidth / 2, corners: "round" },
27555
27538
  pathLine
27556
27539
  );
@@ -27652,6 +27635,8 @@ function createGeom2FromBRep(brep, arcSegments = 16) {
27652
27635
 
27653
27636
  // src/BoardGeomBuilder.ts
27654
27637
  var PAD_ROUNDED_SEGMENTS = 64;
27638
+ var BOARD_CLIP_Z_MARGIN = 1;
27639
+ var BOARD_CLIP_XY_OUTSET = 0.05;
27655
27640
  var createCenteredRectPadGeom = (width10, height10, thickness, rectBorderRadius) => {
27656
27641
  const clampedRadius = clampRectBorderRadius(width10, height10, rectBorderRadius);
27657
27642
  if (clampedRadius <= 0) {
@@ -27702,6 +27687,7 @@ var BoardGeomBuilder = class {
27702
27687
  silkscreenTextGeoms = [];
27703
27688
  silkscreenPathGeoms = [];
27704
27689
  copperPourGeoms = [];
27690
+ boardClipGeom = null;
27705
27691
  state = "initializing";
27706
27692
  currentIndex = 0;
27707
27693
  ctx;
@@ -27737,10 +27723,11 @@ var BoardGeomBuilder = class {
27737
27723
  this.pcb_copper_pours = circuitJson.filter(
27738
27724
  (e) => e.type === "pcb_copper_pour"
27739
27725
  );
27740
- this.ctx = { pcbThickness: 1.2 };
27726
+ this.ctx = { pcbThickness: this.board.thickness ?? 1.2 };
27741
27727
  this.initializeBoard();
27742
27728
  }
27743
27729
  initializeBoard() {
27730
+ const clipDepth = this.ctx.pcbThickness + 2 * BOARD_CLIP_Z_MARGIN;
27744
27731
  if (this.board.outline && this.board.outline.length > 0) {
27745
27732
  this.boardGeom = createBoardGeomWithOutline(
27746
27733
  {
@@ -27748,11 +27735,26 @@ var BoardGeomBuilder = class {
27748
27735
  },
27749
27736
  this.ctx.pcbThickness
27750
27737
  );
27738
+ this.boardClipGeom = createBoardGeomWithOutline(
27739
+ {
27740
+ outline: this.board.outline
27741
+ },
27742
+ clipDepth,
27743
+ { xyOutset: BOARD_CLIP_XY_OUTSET }
27744
+ );
27751
27745
  } else {
27752
27746
  this.boardGeom = (0, import_primitives6.cuboid)({
27753
27747
  size: [this.board.width, this.board.height, this.ctx.pcbThickness],
27754
27748
  center: [this.board.center.x, this.board.center.y, 0]
27755
27749
  });
27750
+ this.boardClipGeom = (0, import_primitives6.cuboid)({
27751
+ size: [
27752
+ this.board.width + 2 * BOARD_CLIP_XY_OUTSET,
27753
+ this.board.height + 2 * BOARD_CLIP_XY_OUTSET,
27754
+ clipDepth
27755
+ ],
27756
+ center: [this.board.center.x, this.board.center.y, 0]
27757
+ });
27756
27758
  }
27757
27759
  this.state = "processing_pads";
27758
27760
  this.currentIndex = 0;
@@ -27929,6 +27931,9 @@ var BoardGeomBuilder = class {
27929
27931
  pourGeom = (0, import_transforms4.translate)([0, 0, zPos], pourGeom);
27930
27932
  }
27931
27933
  if (pourGeom) {
27934
+ if (this.boardClipGeom) {
27935
+ pourGeom = (0, import_booleans3.intersect)(this.boardClipGeom, pourGeom);
27936
+ }
27932
27937
  const coloredPourGeom = (0, import_colors4.colorize)(colors.copper, pourGeom);
27933
27938
  this.copperPourGeoms.push(coloredPourGeom);
27934
27939
  }
@@ -27949,7 +27954,9 @@ var BoardGeomBuilder = class {
27949
27954
  this.padGeoms = this.padGeoms.map(
27950
27955
  (pg) => (0, import_colors4.colorize)(colors.copper, (0, import_booleans3.subtract)(pg, cyGeom))
27951
27956
  );
27952
- const platedHoleGeom = platedHole(ph, this.ctx);
27957
+ const platedHoleGeom = platedHole(ph, this.ctx, {
27958
+ clipGeom: this.boardClipGeom
27959
+ });
27953
27960
  this.platedHoleGeoms.push(platedHoleGeom);
27954
27961
  } else if (ph.shape === "pill" || ph.shape === "pill_hole_with_rect_pad") {
27955
27962
  const shouldRotate = ph.hole_height > ph.hole_width;
@@ -27979,7 +27986,9 @@ var BoardGeomBuilder = class {
27979
27986
  this.padGeoms = this.padGeoms.map(
27980
27987
  (pg) => (0, import_colors4.colorize)(colors.copper, (0, import_booleans3.subtract)(pg, pillHole))
27981
27988
  );
27982
- const platedHoleGeom = platedHole(ph, this.ctx);
27989
+ const platedHoleGeom = platedHole(ph, this.ctx, {
27990
+ clipGeom: this.boardClipGeom
27991
+ });
27983
27992
  this.platedHoleGeoms.push(platedHoleGeom);
27984
27993
  }
27985
27994
  }
@@ -28011,8 +28020,12 @@ var BoardGeomBuilder = class {
28011
28020
  rectBorderRadius
28012
28021
  );
28013
28022
  const positionedPadGeom = (0, import_transforms4.translate)([pad2.x, pad2.y, zPos], basePadGeom);
28014
- const padGeom = (0, import_colors4.colorize)(colors.copper, positionedPadGeom);
28015
- this.padGeoms.push(padGeom);
28023
+ let finalPadGeom = positionedPadGeom;
28024
+ if (this.boardClipGeom) {
28025
+ finalPadGeom = (0, import_booleans3.intersect)(this.boardClipGeom, finalPadGeom);
28026
+ }
28027
+ finalPadGeom = (0, import_colors4.colorize)(colors.copper, finalPadGeom);
28028
+ this.padGeoms.push(finalPadGeom);
28016
28029
  } else if (pad2.shape === "rotated_rect") {
28017
28030
  let basePadGeom = createCenteredRectPadGeom(
28018
28031
  pad2.width,
@@ -28023,17 +28036,22 @@ var BoardGeomBuilder = class {
28023
28036
  const rotationRadians = pad2.ccw_rotation * Math.PI / 180;
28024
28037
  basePadGeom = (0, import_transforms4.rotateZ)(rotationRadians, basePadGeom);
28025
28038
  const positionedPadGeom = (0, import_transforms4.translate)([pad2.x, pad2.y, zPos], basePadGeom);
28026
- const padGeom = (0, import_colors4.colorize)(colors.copper, positionedPadGeom);
28027
- this.padGeoms.push(padGeom);
28039
+ let finalPadGeom = positionedPadGeom;
28040
+ if (this.boardClipGeom) {
28041
+ finalPadGeom = (0, import_booleans3.intersect)(this.boardClipGeom, finalPadGeom);
28042
+ }
28043
+ finalPadGeom = (0, import_colors4.colorize)(colors.copper, finalPadGeom);
28044
+ this.padGeoms.push(finalPadGeom);
28028
28045
  } else if (pad2.shape === "circle") {
28029
- const padGeom = (0, import_colors4.colorize)(
28030
- colors.copper,
28031
- (0, import_primitives6.cylinder)({
28032
- center: [pad2.x, pad2.y, zPos],
28033
- radius: pad2.radius,
28034
- height: M
28035
- })
28036
- );
28046
+ let padGeom = (0, import_primitives6.cylinder)({
28047
+ center: [pad2.x, pad2.y, zPos],
28048
+ radius: pad2.radius,
28049
+ height: M
28050
+ });
28051
+ if (this.boardClipGeom) {
28052
+ padGeom = (0, import_booleans3.intersect)(this.boardClipGeom, padGeom);
28053
+ }
28054
+ padGeom = (0, import_colors4.colorize)(colors.copper, padGeom);
28037
28055
  this.padGeoms.push(padGeom);
28038
28056
  }
28039
28057
  }
@@ -28048,7 +28066,7 @@ var BoardGeomBuilder = class {
28048
28066
  const layerSign = currentLayer === "bottom" ? -1 : 1;
28049
28067
  const zPos = layerSign * this.ctx.pcbThickness / 2 + layerSign * M;
28050
28068
  const linePath = (0, import_primitives6.line)(currentSegmentPoints);
28051
- const expandedPath = (0, import_expansions2.expand)(
28069
+ const expandedPath = (0, import_expansions3.expand)(
28052
28070
  { delta: currentWidth / 2, corners: "round" },
28053
28071
  linePath
28054
28072
  );
@@ -28080,6 +28098,9 @@ var BoardGeomBuilder = class {
28080
28098
  traceGeom = (0, import_booleans3.subtract)(traceGeom, cuttingCylinder);
28081
28099
  }
28082
28100
  const tracesMaterialColor = tracesMaterialColors[this.board.material] ?? colors.fr4GreenSolderWithMask;
28101
+ if (this.boardClipGeom) {
28102
+ traceGeom = (0, import_booleans3.intersect)(this.boardClipGeom, traceGeom);
28103
+ }
28083
28104
  traceGeom = (0, import_colors4.colorize)(tracesMaterialColor, traceGeom);
28084
28105
  this.traceGeoms.push(traceGeom);
28085
28106
  }
@@ -28153,7 +28174,7 @@ var BoardGeomBuilder = class {
28153
28174
  Math.max(0.01, fontSize * 0.1),
28154
28175
  fontSize * 0.05
28155
28176
  );
28156
- const expandedPath = (0, import_expansions2.expand)(
28177
+ const expandedPath = (0, import_expansions3.expand)(
28157
28178
  { delta: expansionDelta, corners: "round" },
28158
28179
  textPath
28159
28180
  );
@@ -28930,6 +28951,20 @@ function createPadManifoldOp({
28930
28951
  thickness: padBaseThickness,
28931
28952
  borderRadius: rectBorderRadius
28932
28953
  });
28954
+ } else if (pad2.shape === "rotated_rect") {
28955
+ const rectBorderRadius = extractRectBorderRadius(pad2);
28956
+ let padOp = createRoundedRectPrism({
28957
+ Manifold,
28958
+ width: pad2.width,
28959
+ height: pad2.height,
28960
+ thickness: padBaseThickness,
28961
+ borderRadius: rectBorderRadius
28962
+ });
28963
+ const rotation2 = pad2.ccw_rotation ?? 0;
28964
+ if (rotation2) {
28965
+ padOp = padOp.rotate([0, 0, rotation2]);
28966
+ }
28967
+ return padOp;
28933
28968
  } else if (pad2.shape === "circle" && pad2.radius) {
28934
28969
  return Manifold.cylinder(padBaseThickness, pad2.radius, -1, 32, true);
28935
28970
  }
@@ -28938,10 +28973,22 @@ function createPadManifoldOp({
28938
28973
 
28939
28974
  // src/utils/manifold/process-plated-holes.ts
28940
28975
  var COPPER_COLOR = new THREE19.Color(...colors.copper);
28941
- function processPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup) {
28976
+ var PLATED_HOLE_LIP_HEIGHT = 0.05;
28977
+ function processPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup, boardClipVolume) {
28942
28978
  const platedHoleBoardDrills = [];
28943
28979
  const pcbPlatedHoles = su8(circuitJson).pcb_plated_hole.list();
28944
28980
  const platedHoleCopperGeoms = [];
28981
+ const createPillOp = (width10, height10, depth) => {
28982
+ const pillOp = createRoundedRectPrism({
28983
+ Manifold,
28984
+ width: width10,
28985
+ height: height10,
28986
+ thickness: depth,
28987
+ borderRadius: Math.min(width10, height10) / 2
28988
+ });
28989
+ manifoldInstancesForCleanup.push(pillOp);
28990
+ return pillOp;
28991
+ };
28945
28992
  pcbPlatedHoles.forEach((ph, index) => {
28946
28993
  if (ph.shape === "circle") {
28947
28994
  const translatedDrill = createPlatedHoleDrill({
@@ -28978,69 +29025,31 @@ function processPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, mani
28978
29025
  manifoldInstancesForCleanup.push(finalPlatedPartOp);
28979
29026
  const translatedPlatedPart = finalPlatedPartOp.translate([ph.x, ph.y, 0]);
28980
29027
  manifoldInstancesForCleanup.push(translatedPlatedPart);
28981
- const threeGeom = manifoldMeshToThreeGeometry(
28982
- translatedPlatedPart.getMesh()
28983
- );
29028
+ let finalCopperOp = translatedPlatedPart;
29029
+ if (boardClipVolume) {
29030
+ const clipped = Manifold.intersection([
29031
+ translatedPlatedPart,
29032
+ boardClipVolume
29033
+ ]);
29034
+ manifoldInstancesForCleanup.push(clipped);
29035
+ finalCopperOp = clipped;
29036
+ }
29037
+ const threeGeom = manifoldMeshToThreeGeometry(finalCopperOp.getMesh());
28984
29038
  platedHoleCopperGeoms.push({
28985
29039
  key: `ph-${ph.pcb_plated_hole_id || index}`,
28986
29040
  geometry: threeGeom,
28987
29041
  color: COPPER_COLOR
28988
29042
  });
28989
29043
  } else if (ph.shape === "pill") {
28990
- const holeWidthRaw = ph.hole_width;
28991
- const holeHeightRaw = ph.hole_height;
28992
- const shouldRotate = holeHeightRaw > holeWidthRaw;
28993
- const holeW = shouldRotate ? holeHeightRaw : holeWidthRaw;
28994
- const holeH = shouldRotate ? holeWidthRaw : holeHeightRaw;
29044
+ const holeW = ph.hole_width;
29045
+ const holeH = ph.hole_height;
28995
29046
  const defaultPadExtension = 0.4;
28996
- const outerW = shouldRotate ? ph.outer_height ?? holeH + defaultPadExtension / 2 : ph.outer_width ?? holeW + defaultPadExtension / 2;
28997
- const outerH = shouldRotate ? ph.outer_width ?? holeW + defaultPadExtension / 2 : ph.outer_height ?? holeH + defaultPadExtension / 2;
28998
- const createPill = (width10, height10, depth) => {
28999
- const radius = height10 / 2;
29000
- const rectLength = width10 - height10;
29001
- let pillOp;
29002
- if (rectLength < 1e-9) {
29003
- pillOp = Manifold.cylinder(
29004
- depth,
29005
- radius,
29006
- radius,
29007
- SMOOTH_CIRCLE_SEGMENTS,
29008
- true
29009
- );
29010
- } else {
29011
- const rect = Manifold.cube(
29012
- [Math.max(0, rectLength), height10, depth],
29013
- true
29014
- );
29015
- const cap1 = Manifold.cylinder(
29016
- depth,
29017
- radius,
29018
- radius,
29019
- SMOOTH_CIRCLE_SEGMENTS,
29020
- true
29021
- ).translate([-rectLength / 2, 0, 0]);
29022
- const cap2 = Manifold.cylinder(
29023
- depth,
29024
- radius,
29025
- radius,
29026
- SMOOTH_CIRCLE_SEGMENTS,
29027
- true
29028
- ).translate([rectLength / 2, 0, 0]);
29029
- pillOp = Manifold.union([rect, cap1, cap2]);
29030
- manifoldInstancesForCleanup.push(rect, cap1, cap2);
29031
- }
29032
- manifoldInstancesForCleanup.push(pillOp);
29033
- return pillOp;
29034
- };
29047
+ const outerW = ph.outer_width ?? holeW + defaultPadExtension;
29048
+ const outerH = ph.outer_height ?? holeH + defaultPadExtension;
29035
29049
  const drillW = holeW + 2 * MANIFOLD_Z_OFFSET;
29036
29050
  const drillH = holeH + 2 * MANIFOLD_Z_OFFSET;
29037
29051
  const drillDepth = pcbThickness * 1.2;
29038
- let boardPillDrillOp = createPill(drillW, drillH, drillDepth);
29039
- if (shouldRotate) {
29040
- const rotatedOp = boardPillDrillOp.rotate([0, 0, 90]);
29041
- manifoldInstancesForCleanup.push(rotatedOp);
29042
- boardPillDrillOp = rotatedOp;
29043
- }
29052
+ let boardPillDrillOp = createPillOp(drillW, drillH, drillDepth);
29044
29053
  if (ph.ccw_rotation) {
29045
29054
  const rotatedOp = boardPillDrillOp.rotate([0, 0, ph.ccw_rotation]);
29046
29055
  manifoldInstancesForCleanup.push(rotatedOp);
@@ -29054,12 +29063,12 @@ function processPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, mani
29054
29063
  manifoldInstancesForCleanup.push(translatedBoardPillDrill);
29055
29064
  platedHoleBoardDrills.push(translatedBoardPillDrill);
29056
29065
  const copperPartThickness = pcbThickness + 2 * MANIFOLD_Z_OFFSET;
29057
- const outerCopperOpUnrotated = createPill(
29066
+ const outerCopperOpUnrotated = createPillOp(
29058
29067
  outerW,
29059
29068
  outerH,
29060
29069
  copperPartThickness
29061
29070
  );
29062
- const innerDrillOpUnrotated = createPill(
29071
+ const innerDrillOpUnrotated = createPillOp(
29063
29072
  holeW,
29064
29073
  holeH,
29065
29074
  copperPartThickness * 1.05
@@ -29069,11 +29078,6 @@ function processPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, mani
29069
29078
  innerDrillOpUnrotated
29070
29079
  );
29071
29080
  manifoldInstancesForCleanup.push(finalPlatedPartOp);
29072
- if (shouldRotate) {
29073
- const rotatedOp = finalPlatedPartOp.rotate([0, 0, 90]);
29074
- manifoldInstancesForCleanup.push(rotatedOp);
29075
- finalPlatedPartOp = rotatedOp;
29076
- }
29077
29081
  if (ph.ccw_rotation) {
29078
29082
  const rotatedOp = finalPlatedPartOp.rotate([0, 0, ph.ccw_rotation]);
29079
29083
  manifoldInstancesForCleanup.push(rotatedOp);
@@ -29089,6 +29093,80 @@ function processPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, mani
29089
29093
  geometry: threeGeom,
29090
29094
  color: COPPER_COLOR
29091
29095
  });
29096
+ } else if (ph.shape === "pill_hole_with_rect_pad") {
29097
+ const holeW = ph.hole_width;
29098
+ const holeH = ph.hole_height;
29099
+ const padWidth = ph.rect_pad_width;
29100
+ const padHeight = ph.rect_pad_height;
29101
+ const rectBorderRadius = extractRectBorderRadius(ph);
29102
+ const padThickness = DEFAULT_SMT_PAD_THICKNESS;
29103
+ const drillW = holeW + 2 * MANIFOLD_Z_OFFSET;
29104
+ const drillH = holeH + 2 * MANIFOLD_Z_OFFSET;
29105
+ const drillDepth = pcbThickness * 1.2;
29106
+ let boardPillDrillOp = createPillOp(drillW, drillH, drillDepth);
29107
+ const translatedBoardPillDrill = boardPillDrillOp.translate([
29108
+ ph.x,
29109
+ ph.y,
29110
+ 0
29111
+ ]);
29112
+ manifoldInstancesForCleanup.push(translatedBoardPillDrill);
29113
+ platedHoleBoardDrills.push(translatedBoardPillDrill);
29114
+ let copperBarrelOp = createPillOp(
29115
+ holeW,
29116
+ holeH,
29117
+ pcbThickness + 2 * MANIFOLD_Z_OFFSET
29118
+ );
29119
+ let topPadOp = createRoundedRectPrism({
29120
+ Manifold,
29121
+ width: padWidth,
29122
+ height: padHeight,
29123
+ thickness: padThickness,
29124
+ borderRadius: rectBorderRadius
29125
+ });
29126
+ manifoldInstancesForCleanup.push(topPadOp);
29127
+ let bottomPadOp = createRoundedRectPrism({
29128
+ Manifold,
29129
+ width: padWidth,
29130
+ height: padHeight,
29131
+ thickness: padThickness,
29132
+ borderRadius: rectBorderRadius
29133
+ });
29134
+ manifoldInstancesForCleanup.push(bottomPadOp);
29135
+ const topPadZ = pcbThickness / 2 + padThickness / 2 + MANIFOLD_Z_OFFSET;
29136
+ const bottomPadZ = -pcbThickness / 2 - padThickness / 2 - MANIFOLD_Z_OFFSET;
29137
+ topPadOp = topPadOp.translate([0, 0, topPadZ]);
29138
+ manifoldInstancesForCleanup.push(topPadOp);
29139
+ bottomPadOp = bottomPadOp.translate([0, 0, bottomPadZ]);
29140
+ manifoldInstancesForCleanup.push(bottomPadOp);
29141
+ const copperUnionBeforeCut = Manifold.union([
29142
+ copperBarrelOp,
29143
+ topPadOp,
29144
+ bottomPadOp
29145
+ ]);
29146
+ manifoldInstancesForCleanup.push(copperUnionBeforeCut);
29147
+ const holeCutWidth = Math.max(holeW - 2 * PLATED_HOLE_LIP_HEIGHT, 0.01);
29148
+ const holeCutHeight = Math.max(holeH - 2 * PLATED_HOLE_LIP_HEIGHT, 0.01);
29149
+ const holeCutDepth = pcbThickness + 2 * padThickness + 4 * MANIFOLD_Z_OFFSET;
29150
+ let holeCutOp = createPillOp(holeCutWidth, holeCutHeight, holeCutDepth);
29151
+ const finalPlatedPartOp = copperUnionBeforeCut.subtract(holeCutOp);
29152
+ manifoldInstancesForCleanup.push(finalPlatedPartOp);
29153
+ const translatedPlatedPart = finalPlatedPartOp.translate([ph.x, ph.y, 0]);
29154
+ manifoldInstancesForCleanup.push(translatedPlatedPart);
29155
+ let finalCopperOp = translatedPlatedPart;
29156
+ if (boardClipVolume) {
29157
+ const clipped = Manifold.intersection([
29158
+ translatedPlatedPart,
29159
+ boardClipVolume
29160
+ ]);
29161
+ manifoldInstancesForCleanup.push(clipped);
29162
+ finalCopperOp = clipped;
29163
+ }
29164
+ const threeGeom = manifoldMeshToThreeGeometry(finalCopperOp.getMesh());
29165
+ platedHoleCopperGeoms.push({
29166
+ key: `ph-${ph.pcb_plated_hole_id || index}`,
29167
+ geometry: threeGeom,
29168
+ color: COPPER_COLOR
29169
+ });
29092
29170
  } else if (ph.shape === "circular_hole_with_rect_pad") {
29093
29171
  const translatedDrill = createCircleHoleDrill({
29094
29172
  Manifold,
@@ -29156,7 +29234,16 @@ function processPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, mani
29156
29234
  manifoldInstancesForCleanup.push(copperUnion);
29157
29235
  const translatedCopper = copperUnion.translate([ph.x, ph.y, 0]);
29158
29236
  manifoldInstancesForCleanup.push(translatedCopper);
29159
- const threeGeom = manifoldMeshToThreeGeometry(translatedCopper.getMesh());
29237
+ let finalCopperOp = translatedCopper;
29238
+ if (boardClipVolume) {
29239
+ const clipped = Manifold.intersection([
29240
+ translatedCopper,
29241
+ boardClipVolume
29242
+ ]);
29243
+ manifoldInstancesForCleanup.push(clipped);
29244
+ finalCopperOp = clipped;
29245
+ }
29246
+ const threeGeom = manifoldMeshToThreeGeometry(finalCopperOp.getMesh());
29160
29247
  platedHoleCopperGeoms.push({
29161
29248
  key: `ph-${ph.pcb_plated_hole_id || index}`,
29162
29249
  geometry: threeGeom,
@@ -29203,7 +29290,7 @@ function createViaCopper({
29203
29290
 
29204
29291
  // src/utils/manifold/process-vias.ts
29205
29292
  var COPPER_COLOR2 = new THREE20.Color(...colors.copper);
29206
- function processViasForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup) {
29293
+ function processViasForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup, boardClipVolume) {
29207
29294
  const viaBoardDrills = [];
29208
29295
  const pcbVias = su9(circuitJson).pcb_via.list();
29209
29296
  const viaCopperGeoms = [];
@@ -29233,9 +29320,16 @@ function processViasForManifold(Manifold, circuitJson, pcbThickness, manifoldIns
29233
29320
  segments: SMOOTH_CIRCLE_SEGMENTS
29234
29321
  });
29235
29322
  manifoldInstancesForCleanup.push(translatedViaCopper);
29236
- const threeGeom = manifoldMeshToThreeGeometry(
29237
- translatedViaCopper.getMesh()
29238
- );
29323
+ let finalCopperOp = translatedViaCopper;
29324
+ if (boardClipVolume) {
29325
+ const clipped = Manifold.intersection([
29326
+ translatedViaCopper,
29327
+ boardClipVolume
29328
+ ]);
29329
+ manifoldInstancesForCleanup.push(clipped);
29330
+ finalCopperOp = clipped;
29331
+ }
29332
+ const threeGeom = manifoldMeshToThreeGeometry(finalCopperOp.getMesh());
29239
29333
  viaCopperGeoms.push({
29240
29334
  key: `via-${via.pcb_via_id || index}`,
29241
29335
  geometry: threeGeom,
@@ -29250,7 +29344,7 @@ function processViasForManifold(Manifold, circuitJson, pcbThickness, manifoldIns
29250
29344
  import { su as su10 } from "@tscircuit/circuit-json-util";
29251
29345
  import * as THREE21 from "three";
29252
29346
  var COPPER_COLOR3 = new THREE21.Color(...colors.copper);
29253
- function processSmtPadsForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup, holeUnion) {
29347
+ function processSmtPadsForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup, holeUnion, boardClipVolume) {
29254
29348
  const smtPadGeoms = [];
29255
29349
  const smtPads = su10(circuitJson).pcb_smtpad.list();
29256
29350
  smtPads.forEach((pad2, index) => {
@@ -29270,6 +29364,11 @@ function processSmtPadsForManifold(Manifold, circuitJson, pcbThickness, manifold
29270
29364
  finalPadOp = translatedPad.subtract(holeUnion);
29271
29365
  manifoldInstancesForCleanup.push(finalPadOp);
29272
29366
  }
29367
+ if (boardClipVolume) {
29368
+ const clipped = Manifold.intersection([finalPadOp, boardClipVolume]);
29369
+ manifoldInstancesForCleanup.push(clipped);
29370
+ finalPadOp = clipped;
29371
+ }
29273
29372
  const threeGeom = manifoldMeshToThreeGeometry(finalPadOp.getMesh());
29274
29373
  smtPadGeoms.push({
29275
29374
  key: `pad-${pad2.pcb_smtpad_id || index}`,
@@ -29296,6 +29395,7 @@ var arePointsClockwise3 = (points) => {
29296
29395
  };
29297
29396
  function createManifoldBoard(Manifold, CrossSection, boardData, pcbThickness, manifoldInstancesForCleanup) {
29298
29397
  let boardOp;
29398
+ let outlineCrossSection = null;
29299
29399
  if (boardData.outline && boardData.outline.length >= 3) {
29300
29400
  let outlineVec2 = boardData.outline.map((p) => [
29301
29401
  p.x,
@@ -29306,6 +29406,7 @@ function createManifoldBoard(Manifold, CrossSection, boardData, pcbThickness, ma
29306
29406
  }
29307
29407
  const crossSection = CrossSection.ofPolygons([outlineVec2]);
29308
29408
  manifoldInstancesForCleanup.push(crossSection);
29409
+ outlineCrossSection = crossSection;
29309
29410
  boardOp = Manifold.extrude(
29310
29411
  crossSection,
29311
29412
  pcbThickness,
@@ -29334,7 +29435,7 @@ function createManifoldBoard(Manifold, CrossSection, boardData, pcbThickness, ma
29334
29435
  boardOp = boardOp.translate([boardData.center.x, boardData.center.y, 0]);
29335
29436
  manifoldInstancesForCleanup.push(boardOp);
29336
29437
  }
29337
- return boardOp;
29438
+ return { boardOp, outlineCrossSection };
29338
29439
  }
29339
29440
 
29340
29441
  // src/utils/manifold/process-copper-pours.ts
@@ -29405,7 +29506,7 @@ function ringToPoints2(ring2, arcSegments) {
29405
29506
  }
29406
29507
  return allPoints;
29407
29508
  }
29408
- function processCopperPoursForManifold(Manifold, CrossSection, circuitJson, pcbThickness, manifoldInstancesForCleanup, holeUnion) {
29509
+ function processCopperPoursForManifold(Manifold, CrossSection, circuitJson, pcbThickness, manifoldInstancesForCleanup, holeUnion, boardClipVolume) {
29409
29510
  const copperPourGeoms = [];
29410
29511
  const copperPours = circuitJson.filter(
29411
29512
  (e) => e.type === "pcb_copper_pour"
@@ -29492,6 +29593,11 @@ function processCopperPoursForManifold(Manifold, CrossSection, circuitJson, pcbT
29492
29593
  manifoldInstancesForCleanup.push(withHoles);
29493
29594
  pourOp = withHoles;
29494
29595
  }
29596
+ if (boardClipVolume) {
29597
+ const clipped = Manifold.intersection([pourOp, boardClipVolume]);
29598
+ manifoldInstancesForCleanup.push(clipped);
29599
+ pourOp = clipped;
29600
+ }
29495
29601
  const threeGeom = manifoldMeshToThreeGeometry(pourOp.getMesh());
29496
29602
  copperPourGeoms.push({
29497
29603
  key: `coppour-${pour.pcb_copper_pour_id}`,
@@ -29644,13 +29750,51 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
29644
29750
  try {
29645
29751
  const currentPcbThickness = boardData.thickness || 1.6;
29646
29752
  setPcbThickness(currentPcbThickness);
29647
- let currentBoardOp = createManifoldBoard(
29753
+ const { boardOp: initialBoardOp, outlineCrossSection } = createManifoldBoard(
29648
29754
  Manifold,
29649
29755
  CrossSection,
29650
29756
  boardData,
29651
29757
  currentPcbThickness,
29652
29758
  manifoldInstancesForCleanup.current
29653
29759
  );
29760
+ let currentBoardOp = initialBoardOp;
29761
+ const BOARD_CLIP_Z_MARGIN2 = 1;
29762
+ const clipThickness = currentPcbThickness + 2 * BOARD_CLIP_Z_MARGIN2;
29763
+ let boardClipVolume = null;
29764
+ const BOARD_CLIP_XY_OUTSET2 = 0.01;
29765
+ if (outlineCrossSection) {
29766
+ let clipCrossSection = outlineCrossSection;
29767
+ if (BOARD_CLIP_XY_OUTSET2 > 0) {
29768
+ const inflatedCrossSection = outlineCrossSection.offset(BOARD_CLIP_XY_OUTSET2);
29769
+ manifoldInstancesForCleanup.current.push(inflatedCrossSection);
29770
+ clipCrossSection = inflatedCrossSection;
29771
+ }
29772
+ const clipOp = Manifold.extrude(
29773
+ clipCrossSection,
29774
+ clipThickness,
29775
+ void 0,
29776
+ void 0,
29777
+ void 0,
29778
+ true
29779
+ );
29780
+ manifoldInstancesForCleanup.current.push(clipOp);
29781
+ boardClipVolume = clipOp;
29782
+ } else {
29783
+ const clipWidth = (boardData.width || 0) + 2 * BOARD_CLIP_XY_OUTSET2;
29784
+ const clipHeight = (boardData.height || 0) + 2 * BOARD_CLIP_XY_OUTSET2;
29785
+ const clipCube = Manifold.cube(
29786
+ [clipWidth, clipHeight, clipThickness],
29787
+ true
29788
+ );
29789
+ manifoldInstancesForCleanup.current.push(clipCube);
29790
+ const translatedClipCube = clipCube.translate([
29791
+ boardData.center.x,
29792
+ boardData.center.y,
29793
+ 0
29794
+ ]);
29795
+ manifoldInstancesForCleanup.current.push(translatedClipCube);
29796
+ boardClipVolume = translatedClipCube;
29797
+ }
29654
29798
  const allBoardDrills = [];
29655
29799
  let holeUnion = null;
29656
29800
  const { nonPlatedHoleBoardDrills } = processNonPlatedHolesForManifold(
@@ -29664,7 +29808,8 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
29664
29808
  Manifold,
29665
29809
  circuitJson,
29666
29810
  currentPcbThickness,
29667
- manifoldInstancesForCleanup.current
29811
+ manifoldInstancesForCleanup.current,
29812
+ boardClipVolume
29668
29813
  );
29669
29814
  allBoardDrills.push(...platedHoleBoardDrills);
29670
29815
  currentGeoms.platedHoles = platedHoleCopperGeoms;
@@ -29672,7 +29817,8 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
29672
29817
  Manifold,
29673
29818
  circuitJson,
29674
29819
  currentPcbThickness,
29675
- manifoldInstancesForCleanup.current
29820
+ manifoldInstancesForCleanup.current,
29821
+ boardClipVolume
29676
29822
  );
29677
29823
  allBoardDrills.push(...viaBoardDrills);
29678
29824
  currentGeoms.vias = viaCopperGeoms;
@@ -29717,7 +29863,8 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
29717
29863
  circuitJson,
29718
29864
  currentPcbThickness,
29719
29865
  manifoldInstancesForCleanup.current,
29720
- holeUnion
29866
+ holeUnion,
29867
+ boardClipVolume
29721
29868
  );
29722
29869
  currentGeoms.smtPads = smtPadGeoms;
29723
29870
  const { copperPourGeoms } = processCopperPoursForManifold(
@@ -29726,7 +29873,8 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
29726
29873
  circuitJson,
29727
29874
  currentPcbThickness,
29728
29875
  manifoldInstancesForCleanup.current,
29729
- holeUnion
29876
+ holeUnion,
29877
+ boardClipVolume
29730
29878
  );
29731
29879
  currentGeoms.copperPours = copperPourGeoms;
29732
29880
  setGeoms(currentGeoms);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/3d-viewer",
3
- "version": "0.0.407",
3
+ "version": "0.0.409",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",