@tscircuit/3d-viewer 0.0.411 → 0.0.413

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 +509 -186
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -16636,8 +16636,8 @@ var pcb_fabrication_note_dimension = z93.object({
16636
16636
  pcb_group_id: z93.string().optional(),
16637
16637
  subcircuit_id: z93.string().optional(),
16638
16638
  layer: visible_layer,
16639
- from: point.or(z93.string()),
16640
- to: point.or(z93.string()),
16639
+ from: point,
16640
+ to: point,
16641
16641
  text: z93.string().optional(),
16642
16642
  offset: length.optional(),
16643
16643
  font: z93.literal("tscircuit2024").default("tscircuit2024"),
@@ -26576,7 +26576,7 @@ import * as THREE13 from "three";
26576
26576
  // package.json
26577
26577
  var package_default = {
26578
26578
  name: "@tscircuit/3d-viewer",
26579
- version: "0.0.410",
26579
+ version: "0.0.412",
26580
26580
  main: "./dist/index.js",
26581
26581
  module: "./dist/index.js",
26582
26582
  type: "module",
@@ -26623,8 +26623,8 @@ var package_default = {
26623
26623
  "@storybook/blocks": "9.0.0-alpha.17",
26624
26624
  "@storybook/react-vite": "^9.1.5",
26625
26625
  "@tscircuit/circuit-json-util": "^0.0.72",
26626
- "@tscircuit/core": "^0.0.783",
26627
- "@tscircuit/props": "^0.0.360",
26626
+ "@tscircuit/core": "^0.0.787",
26627
+ "@tscircuit/props": "^0.0.364",
26628
26628
  "@tscircuit/checks": "^0.0.85",
26629
26629
  "@tscircuit/math-utils": "^0.0.27",
26630
26630
  "@tscircuit/capacity-autorouter": "^0.0.131",
@@ -26636,7 +26636,7 @@ var package_default = {
26636
26636
  "@vitejs/plugin-react": "^4.3.4",
26637
26637
  "bun-match-svg": "^0.0.9",
26638
26638
  "bun-types": "1.2.1",
26639
- "circuit-json": "0.0.278",
26639
+ "circuit-json": "0.0.279",
26640
26640
  "circuit-to-svg": "^0.0.179",
26641
26641
  debug: "^4.4.0",
26642
26642
  "jscad-electronics": "^0.0.48",
@@ -27519,38 +27519,67 @@ var platedHole = (plated_hole, ctx, options = {}) => {
27519
27519
  return (0, import_colors2.colorize)(colors.copper, (0, import_booleans.subtract)(copperSolid, drill));
27520
27520
  }
27521
27521
  if (plated_hole.shape === "circular_hole_with_rect_pad") {
27522
+ const holeOffsetX = plated_hole.hole_offset_x || 0;
27523
+ const holeOffsetY = plated_hole.hole_offset_y || 0;
27522
27524
  const padWidth = plated_hole.rect_pad_width || plated_hole.hole_diameter;
27523
27525
  const padHeight = plated_hole.rect_pad_height || plated_hole.hole_diameter;
27524
27526
  const rectBorderRadius = extractRectBorderRadius(plated_hole);
27525
27527
  const copperSolid = maybeClip(
27526
27528
  (0, import_booleans.union)(
27527
- // Top rectangular pad
27529
+ // Top rectangular pad (thicker to ensure connection)
27528
27530
  createRectPadGeom({
27529
27531
  width: padWidth,
27530
27532
  height: padHeight,
27531
- thickness: platedHoleLipHeight,
27533
+ thickness: platedHoleLipHeight + 0.1,
27534
+ // Slightly thicker to ensure connection
27532
27535
  center: [
27533
27536
  plated_hole.x,
27534
27537
  plated_hole.y,
27535
- ctx.pcbThickness / 2 + platedHoleLipHeight / 2 + M
27538
+ ctx.pcbThickness / 2 + platedHoleLipHeight / 2 + M - 0.05
27539
+ // Adjusted for thickness
27536
27540
  ],
27537
27541
  borderRadius: rectBorderRadius
27538
27542
  }),
27539
- // Bottom rectangular pad
27543
+ // Bottom rectangular pad (thicker to ensure connection)
27540
27544
  createRectPadGeom({
27541
27545
  width: padWidth,
27542
27546
  height: padHeight,
27543
- thickness: platedHoleLipHeight,
27547
+ thickness: platedHoleLipHeight + 0.1,
27548
+ // Slightly thicker to ensure connection
27544
27549
  center: [
27545
27550
  plated_hole.x,
27546
27551
  plated_hole.y,
27547
- -ctx.pcbThickness / 2 - platedHoleLipHeight / 2 - M
27552
+ -ctx.pcbThickness / 2 - platedHoleLipHeight / 2 - M + 0.05
27553
+ // Adjusted for thickness
27548
27554
  ],
27549
27555
  borderRadius: rectBorderRadius
27550
27556
  }),
27551
- // Plated barrel around hole
27557
+ // Main copper fill between pads with rounded corners
27558
+ (() => {
27559
+ const height10 = ctx.pcbThickness - platedHoleLipHeight * 2 - M * 2 + 0.1;
27560
+ const rect2d = (0, import_primitives3.roundedRectangle)({
27561
+ size: [padWidth, padHeight],
27562
+ roundRadius: rectBorderRadius || 0,
27563
+ segments: RECT_PAD_SEGMENTS
27564
+ });
27565
+ const extruded = (0, import_extrusions2.extrudeLinear)({ height: height10 }, rect2d);
27566
+ return (0, import_transforms2.translate)(
27567
+ [
27568
+ plated_hole.x,
27569
+ plated_hole.y,
27570
+ -height10 / 2
27571
+ // Center vertically
27572
+ ],
27573
+ extruded
27574
+ );
27575
+ })(),
27576
+ // Plated barrel around hole (ensured connection with pads)
27552
27577
  (0, import_primitives3.cylinder)({
27553
- center: [plated_hole.x, plated_hole.y, 0],
27578
+ center: [
27579
+ plated_hole.x + (holeOffsetX || 0),
27580
+ plated_hole.y + (holeOffsetY || 0),
27581
+ 0
27582
+ ],
27554
27583
  radius: plated_hole.hole_diameter / 2,
27555
27584
  height: ctx.pcbThickness
27556
27585
  })
@@ -27558,11 +27587,35 @@ var platedHole = (plated_hole, ctx, options = {}) => {
27558
27587
  clipGeom
27559
27588
  );
27560
27589
  const drill = (0, import_primitives3.cylinder)({
27561
- center: [plated_hole.x, plated_hole.y, 0],
27590
+ center: [
27591
+ plated_hole.x + (holeOffsetX || 0),
27592
+ plated_hole.y + (holeOffsetY || 0),
27593
+ 0
27594
+ ],
27562
27595
  radius: Math.max(plated_hole.hole_diameter / 2 - M, 0.01),
27563
27596
  height: throughDrillHeight
27564
27597
  });
27565
- return (0, import_colors2.colorize)(colors.copper, (0, import_booleans.subtract)(copperSolid, drill));
27598
+ const barrel = (0, import_primitives3.cylinder)({
27599
+ center: [
27600
+ plated_hole.x + (holeOffsetX || 0),
27601
+ plated_hole.y + (holeOffsetY || 0),
27602
+ 0
27603
+ ],
27604
+ radius: plated_hole.hole_diameter / 2,
27605
+ height: ctx.pcbThickness
27606
+ });
27607
+ let finalCopper = (0, import_booleans.union)(
27608
+ (0, import_booleans.subtract)(copperSolid, barrel),
27609
+ // Subtract the barrel from the main shape
27610
+ barrel
27611
+ // Add the barrel back to ensure proper connection
27612
+ );
27613
+ if (options.clipGeom) {
27614
+ finalCopper = (0, import_booleans.subtract)(finalCopper, drill);
27615
+ finalCopper = (0, import_booleans.intersect)(finalCopper, options.clipGeom);
27616
+ return (0, import_colors2.colorize)(colors.copper, finalCopper);
27617
+ }
27618
+ return (0, import_colors2.colorize)(colors.copper, (0, import_booleans.subtract)(finalCopper, drill));
27566
27619
  }
27567
27620
  if (plated_hole.shape === "pill") {
27568
27621
  const shouldRotate = plated_hole.hole_height > plated_hole.hole_width;
@@ -27626,6 +27679,8 @@ var platedHole = (plated_hole, ctx, options = {}) => {
27626
27679
  return (0, import_colors2.colorize)(colors.copper, (0, import_booleans.subtract)(copperSolid, drill));
27627
27680
  }
27628
27681
  if (plated_hole.shape === "pill_hole_with_rect_pad") {
27682
+ const holeOffsetX = plated_hole.hole_offset_x || 0;
27683
+ const holeOffsetY = plated_hole.hole_offset_y || 0;
27629
27684
  const shouldRotate = plated_hole.hole_height > plated_hole.hole_width;
27630
27685
  const holeWidth = shouldRotate ? plated_hole.hole_height : plated_hole.hole_width;
27631
27686
  const holeHeight = shouldRotate ? plated_hole.hole_width : plated_hole.hole_height;
@@ -27634,64 +27689,140 @@ var platedHole = (plated_hole, ctx, options = {}) => {
27634
27689
  const padWidth = plated_hole.rect_pad_width || holeWidth + 0.2;
27635
27690
  const padHeight = plated_hole.rect_pad_height || holeHeight + 0.2;
27636
27691
  const rectBorderRadius = extractRectBorderRadius(plated_hole);
27637
- const mainRect = (0, import_primitives3.cuboid)({
27638
- center: [plated_hole.x, plated_hole.y, 0],
27639
- size: shouldRotate ? [holeHeight, rectLength, ctx.pcbThickness] : [rectLength, holeHeight, ctx.pcbThickness]
27640
- });
27641
- const leftCap = (0, import_primitives3.cylinder)({
27642
- center: shouldRotate ? [plated_hole.x, plated_hole.y - rectLength / 2, 0] : [plated_hole.x - rectLength / 2, plated_hole.y, 0],
27643
- radius: holeRadius,
27644
- height: ctx.pcbThickness
27645
- });
27646
- const rightCap = (0, import_primitives3.cylinder)({
27647
- center: shouldRotate ? [plated_hole.x, plated_hole.y + rectLength / 2, 0] : [plated_hole.x + rectLength / 2, plated_hole.y, 0],
27648
- radius: holeRadius,
27649
- height: ctx.pcbThickness
27650
- });
27651
- const topPad = createRectPadGeom({
27652
- width: padWidth,
27653
- height: padHeight,
27654
- thickness: platedHoleLipHeight,
27655
- center: [
27656
- plated_hole.x,
27657
- plated_hole.y,
27658
- ctx.pcbThickness / 2 + platedHoleLipHeight / 2 + M
27659
- ],
27660
- borderRadius: rectBorderRadius
27661
- });
27662
- const bottomPad = createRectPadGeom({
27692
+ const barrelMargin = 0.03;
27693
+ const barrel = (0, import_booleans.union)(
27694
+ (0, import_primitives3.cuboid)({
27695
+ center: [plated_hole.x + holeOffsetX, plated_hole.y + holeOffsetY, 0],
27696
+ size: shouldRotate ? [
27697
+ holeHeight + 2 * barrelMargin,
27698
+ rectLength + 2 * barrelMargin,
27699
+ ctx.pcbThickness + 0.2
27700
+ ] : [
27701
+ rectLength + 2 * barrelMargin,
27702
+ holeHeight + 2 * barrelMargin,
27703
+ ctx.pcbThickness + 0.2
27704
+ ]
27705
+ }),
27706
+ (0, import_primitives3.cylinder)({
27707
+ center: shouldRotate ? [
27708
+ plated_hole.x + holeOffsetX,
27709
+ plated_hole.y + holeOffsetY - rectLength / 2,
27710
+ 0
27711
+ ] : [
27712
+ plated_hole.x + holeOffsetX - rectLength / 2,
27713
+ plated_hole.y + holeOffsetY,
27714
+ 0
27715
+ ],
27716
+ radius: holeRadius + barrelMargin,
27717
+ height: ctx.pcbThickness + 0.2
27718
+ // extend slightly above/below PCB
27719
+ }),
27720
+ (0, import_primitives3.cylinder)({
27721
+ center: shouldRotate ? [
27722
+ plated_hole.x + holeOffsetX,
27723
+ plated_hole.y + holeOffsetY + rectLength / 2,
27724
+ 0
27725
+ ] : [
27726
+ plated_hole.x + holeOffsetX + rectLength / 2,
27727
+ plated_hole.y + holeOffsetY,
27728
+ 0
27729
+ ],
27730
+ radius: holeRadius + barrelMargin,
27731
+ height: ctx.pcbThickness + 0.2
27732
+ })
27733
+ );
27734
+ const holeCut = (0, import_booleans.union)(
27735
+ (0, import_primitives3.cuboid)({
27736
+ center: [plated_hole.x + holeOffsetX, plated_hole.y + holeOffsetY, 0],
27737
+ size: shouldRotate ? [holeHeight, rectLength, throughDrillHeight * 1.1] : [rectLength, holeHeight, throughDrillHeight * 1.1]
27738
+ }),
27739
+ (0, import_primitives3.cylinder)({
27740
+ center: shouldRotate ? [
27741
+ plated_hole.x + holeOffsetX,
27742
+ plated_hole.y + holeOffsetY - rectLength / 2,
27743
+ 0
27744
+ ] : [
27745
+ plated_hole.x + holeOffsetX - rectLength / 2,
27746
+ plated_hole.y + holeOffsetY,
27747
+ 0
27748
+ ],
27749
+ radius: holeRadius,
27750
+ height: throughDrillHeight * 1.1
27751
+ }),
27752
+ (0, import_primitives3.cylinder)({
27753
+ center: shouldRotate ? [
27754
+ plated_hole.x + holeOffsetX,
27755
+ plated_hole.y + holeOffsetY + rectLength / 2,
27756
+ 0
27757
+ ] : [
27758
+ plated_hole.x + holeOffsetX + rectLength / 2,
27759
+ plated_hole.y + holeOffsetY,
27760
+ 0
27761
+ ],
27762
+ radius: holeRadius,
27763
+ height: throughDrillHeight * 1.1
27764
+ })
27765
+ );
27766
+ const mainFill = createRectPadGeom({
27663
27767
  width: padWidth,
27664
27768
  height: padHeight,
27665
- thickness: platedHoleLipHeight,
27666
- center: [
27667
- plated_hole.x,
27668
- plated_hole.y,
27669
- -ctx.pcbThickness / 2 - platedHoleLipHeight / 2 - M
27670
- ],
27769
+ thickness: ctx.pcbThickness - 2 * platedHoleLipHeight - M * 2 + 0.1,
27770
+ center: [plated_hole.x, plated_hole.y, 0],
27671
27771
  borderRadius: rectBorderRadius
27672
27772
  });
27673
- const holeCut = (0, import_booleans.union)(
27773
+ const createPadWithHole = (zOffset) => {
27774
+ const pad2 = createRectPadGeom({
27775
+ width: padWidth,
27776
+ height: padHeight,
27777
+ thickness: platedHoleLipHeight + 0.1,
27778
+ center: [plated_hole.x, plated_hole.y, zOffset],
27779
+ borderRadius: rectBorderRadius
27780
+ });
27781
+ return (0, import_booleans.subtract)(pad2, holeCut);
27782
+ };
27783
+ const topPad = createPadWithHole(
27784
+ ctx.pcbThickness / 2 - platedHoleLipHeight / 2 + 0.05
27785
+ );
27786
+ const bottomPad = createPadWithHole(
27787
+ -ctx.pcbThickness / 2 + platedHoleLipHeight / 2 - 0.05
27788
+ );
27789
+ const filledArea = (0, import_booleans.subtract)(mainFill, holeCut);
27790
+ const barrelHoleCut = (0, import_booleans.union)(
27674
27791
  (0, import_primitives3.cuboid)({
27675
- center: [plated_hole.x, plated_hole.y, 0],
27676
- size: shouldRotate ? [holeHeight - platedHoleLipHeight, rectLength, throughDrillHeight] : [rectLength, holeHeight - platedHoleLipHeight, throughDrillHeight]
27792
+ center: [plated_hole.x + holeOffsetX, plated_hole.y + holeOffsetY, 0],
27793
+ size: shouldRotate ? [holeHeight - 2 * M, rectLength - 2 * M, throughDrillHeight * 1.1] : [rectLength - 2 * M, holeHeight - 2 * M, throughDrillHeight * 1.1]
27677
27794
  }),
27678
27795
  (0, import_primitives3.cylinder)({
27679
- center: shouldRotate ? [plated_hole.x, plated_hole.y - rectLength / 2, 0] : [plated_hole.x - rectLength / 2, plated_hole.y, 0],
27680
- radius: holeRadius - platedHoleLipHeight,
27681
- height: throughDrillHeight
27796
+ center: shouldRotate ? [
27797
+ plated_hole.x + holeOffsetX,
27798
+ plated_hole.y + holeOffsetY - rectLength / 2,
27799
+ 0
27800
+ ] : [
27801
+ plated_hole.x + holeOffsetX - rectLength / 2,
27802
+ plated_hole.y + holeOffsetY,
27803
+ 0
27804
+ ],
27805
+ radius: holeRadius - M,
27806
+ height: throughDrillHeight * 1.1
27682
27807
  }),
27683
27808
  (0, import_primitives3.cylinder)({
27684
- center: shouldRotate ? [plated_hole.x, plated_hole.y + rectLength / 2, 0] : [plated_hole.x + rectLength / 2, plated_hole.y, 0],
27685
- radius: holeRadius - platedHoleLipHeight,
27686
- height: throughDrillHeight
27809
+ center: shouldRotate ? [
27810
+ plated_hole.x + holeOffsetX,
27811
+ plated_hole.y + holeOffsetY + rectLength / 2,
27812
+ 0
27813
+ ] : [
27814
+ plated_hole.x + holeOffsetX + rectLength / 2,
27815
+ plated_hole.y + holeOffsetY,
27816
+ 0
27817
+ ],
27818
+ radius: holeRadius - M,
27819
+ height: throughDrillHeight * 1.1
27687
27820
  })
27688
27821
  );
27689
- const copperSolid = maybeClip(
27690
- (0, import_booleans.union)(mainRect, leftCap, rightCap, topPad, bottomPad),
27691
- clipGeom
27692
- );
27693
- const drill = holeCut;
27694
- return (0, import_colors2.colorize)(colors.copper, (0, import_booleans.subtract)(copperSolid, drill));
27822
+ const barrelWithHole = (0, import_booleans.subtract)(barrel, barrelHoleCut);
27823
+ const finalCopper = (0, import_booleans.union)(filledArea, barrelWithHole, topPad, bottomPad);
27824
+ let result = maybeClip(finalCopper, clipGeom);
27825
+ return (0, import_colors2.colorize)(colors.copper, result);
27695
27826
  } else {
27696
27827
  throw new Error(`Unsupported plated hole shape: ${plated_hole.shape}`);
27697
27828
  }
@@ -28421,13 +28552,24 @@ var BoardGeomBuilder = class {
28421
28552
  processPlatedHole(ph, opts = {}) {
28422
28553
  if (!this.boardGeom) return;
28423
28554
  if (ph.shape === "circle" || ph.shape === "circular_hole_with_rect_pad") {
28424
- const cyGeom = (0, import_primitives6.cylinder)({
28425
- center: [ph.x, ph.y, 0],
28426
- radius: ph.hole_diameter / 2 + M,
28427
- // Add margin for subtraction
28428
- height: this.ctx.pcbThickness * 1.5
28429
- // Ensure it cuts through
28430
- });
28555
+ let cyGeom = null;
28556
+ if (ph.shape === "circular_hole_with_rect_pad") {
28557
+ cyGeom = (0, import_primitives6.cylinder)({
28558
+ center: [ph.x + ph.hole_offset_x, ph.y + ph.hole_offset_y, 0],
28559
+ radius: ph.hole_diameter / 2 + M,
28560
+ // Add margin for subtraction
28561
+ height: this.ctx.pcbThickness * 1.5
28562
+ // Ensure it cuts through
28563
+ });
28564
+ } else {
28565
+ cyGeom = (0, import_primitives6.cylinder)({
28566
+ center: [ph.x, ph.y, 0],
28567
+ radius: ph.hole_diameter / 2 + M,
28568
+ // Add margin for subtraction
28569
+ height: this.ctx.pcbThickness * 1.5
28570
+ // Ensure it cuts through
28571
+ });
28572
+ }
28431
28573
  if (!opts.dontCutBoard) {
28432
28574
  this.boardGeom = (0, import_booleans3.subtract)(this.boardGeom, cyGeom);
28433
28575
  }
@@ -28444,22 +28586,58 @@ var BoardGeomBuilder = class {
28444
28586
  const holeHeight = shouldRotate ? ph.hole_width : ph.hole_height;
28445
28587
  const holeRadius = holeHeight / 2;
28446
28588
  const rectLength = Math.abs(holeWidth - holeHeight);
28447
- const pillHole = (0, import_booleans3.union)(
28448
- (0, import_primitives6.cuboid)({
28449
- center: [ph.x, ph.y, 0],
28450
- size: shouldRotate ? [holeHeight, rectLength, this.ctx.pcbThickness * 1.5] : [rectLength, holeHeight, this.ctx.pcbThickness * 1.5]
28451
- }),
28452
- (0, import_primitives6.cylinder)({
28453
- center: shouldRotate ? [ph.x, ph.y - rectLength / 2, 0] : [ph.x - rectLength / 2, ph.y, 0],
28454
- radius: holeRadius,
28455
- height: this.ctx.pcbThickness * 1.5
28456
- }),
28457
- (0, import_primitives6.cylinder)({
28458
- center: shouldRotate ? [ph.x, ph.y + rectLength / 2, 0] : [ph.x + rectLength / 2, ph.y, 0],
28459
- radius: holeRadius,
28460
- height: this.ctx.pcbThickness * 1.5
28461
- })
28462
- );
28589
+ let pillHole;
28590
+ if (ph.shape === "pill_hole_with_rect_pad") {
28591
+ pillHole = (0, import_booleans3.union)(
28592
+ (0, import_primitives6.cuboid)({
28593
+ center: [ph.x + ph.hole_offset_x, ph.y + ph.hole_offset_y, 0],
28594
+ size: shouldRotate ? [holeHeight, rectLength, this.ctx.pcbThickness * 1.5] : [rectLength, holeHeight, this.ctx.pcbThickness * 1.5]
28595
+ }),
28596
+ (0, import_primitives6.cylinder)({
28597
+ center: shouldRotate ? [
28598
+ ph.x + ph.hole_offset_x,
28599
+ ph.y + ph.hole_offset_y - rectLength / 2,
28600
+ 0
28601
+ ] : [
28602
+ ph.x + ph.hole_offset_x - rectLength / 2,
28603
+ ph.y + ph.hole_offset_y,
28604
+ 0
28605
+ ],
28606
+ radius: holeRadius,
28607
+ height: this.ctx.pcbThickness * 1.5
28608
+ }),
28609
+ (0, import_primitives6.cylinder)({
28610
+ center: shouldRotate ? [
28611
+ ph.x + ph.hole_offset_x,
28612
+ ph.y + ph.hole_offset_y + rectLength / 2,
28613
+ 0
28614
+ ] : [
28615
+ ph.x + ph.hole_offset_x + rectLength / 2,
28616
+ ph.y + ph.hole_offset_y,
28617
+ 0
28618
+ ],
28619
+ radius: holeRadius,
28620
+ height: this.ctx.pcbThickness * 1.5
28621
+ })
28622
+ );
28623
+ } else {
28624
+ pillHole = (0, import_booleans3.union)(
28625
+ (0, import_primitives6.cuboid)({
28626
+ center: [ph.x, ph.y, 0],
28627
+ size: shouldRotate ? [holeHeight, rectLength, this.ctx.pcbThickness * 1.5] : [rectLength, holeHeight, this.ctx.pcbThickness * 1.5]
28628
+ }),
28629
+ (0, import_primitives6.cylinder)({
28630
+ center: shouldRotate ? [ph.x, ph.y - rectLength / 2, 0] : [ph.x - rectLength / 2, ph.y, 0],
28631
+ radius: holeRadius,
28632
+ height: this.ctx.pcbThickness * 1.5
28633
+ }),
28634
+ (0, import_primitives6.cylinder)({
28635
+ center: shouldRotate ? [ph.x, ph.y + rectLength / 2, 0] : [ph.x + rectLength / 2, ph.y, 0],
28636
+ radius: holeRadius,
28637
+ height: this.ctx.pcbThickness * 1.5
28638
+ })
28639
+ );
28640
+ }
28463
28641
  if (!opts.dontCutBoard) {
28464
28642
  this.boardGeom = (0, import_booleans3.subtract)(this.boardGeom, pillHole);
28465
28643
  }
@@ -28486,6 +28664,99 @@ var BoardGeomBuilder = class {
28486
28664
  this.padGeoms = this.padGeoms.map(
28487
28665
  (pg) => (0, import_colors4.colorize)(colors.copper, (0, import_booleans3.subtract)(pg, cyGeom))
28488
28666
  );
28667
+ } else if (hole.hole_shape === "pill") {
28668
+ const holeWidth = hole.hole_width;
28669
+ const holeHeight = hole.hole_height;
28670
+ const holeRadius = Math.min(holeWidth, holeHeight) / 2;
28671
+ const rectLength = Math.abs(holeWidth - holeHeight);
28672
+ let pillHole;
28673
+ if (holeWidth > holeHeight) {
28674
+ pillHole = (0, import_booleans3.union)(
28675
+ (0, import_primitives6.cuboid)({
28676
+ center: [hole.x, hole.y, 0],
28677
+ size: [rectLength, holeHeight, this.ctx.pcbThickness * 1.5]
28678
+ }),
28679
+ (0, import_primitives6.cylinder)({
28680
+ center: [hole.x - rectLength / 2, hole.y, 0],
28681
+ radius: holeRadius,
28682
+ height: this.ctx.pcbThickness * 1.5
28683
+ }),
28684
+ (0, import_primitives6.cylinder)({
28685
+ center: [hole.x + rectLength / 2, hole.y, 0],
28686
+ radius: holeRadius,
28687
+ height: this.ctx.pcbThickness * 1.5
28688
+ })
28689
+ );
28690
+ } else {
28691
+ pillHole = (0, import_booleans3.union)(
28692
+ (0, import_primitives6.cuboid)({
28693
+ center: [hole.x, hole.y, 0],
28694
+ size: [holeWidth, rectLength, this.ctx.pcbThickness * 1.5]
28695
+ }),
28696
+ (0, import_primitives6.cylinder)({
28697
+ center: [hole.x, hole.y - rectLength / 2, 0],
28698
+ radius: holeRadius,
28699
+ height: this.ctx.pcbThickness * 1.5
28700
+ }),
28701
+ (0, import_primitives6.cylinder)({
28702
+ center: [hole.x, hole.y + rectLength / 2, 0],
28703
+ radius: holeRadius,
28704
+ height: this.ctx.pcbThickness * 1.5
28705
+ })
28706
+ );
28707
+ }
28708
+ this.boardGeom = (0, import_booleans3.subtract)(this.boardGeom, pillHole);
28709
+ this.padGeoms = this.padGeoms.map(
28710
+ (pg) => (0, import_colors4.colorize)(colors.copper, (0, import_booleans3.subtract)(pg, pillHole))
28711
+ );
28712
+ } else if (hole.hole_shape === "rotated_pill") {
28713
+ const holeWidth = hole.hole_width;
28714
+ const holeHeight = hole.hole_height;
28715
+ const holeRadius = Math.min(holeWidth, holeHeight) / 2;
28716
+ const rectLength = Math.abs(holeWidth - holeHeight);
28717
+ let pillHole;
28718
+ if (holeWidth > holeHeight) {
28719
+ pillHole = (0, import_booleans3.union)(
28720
+ (0, import_primitives6.cuboid)({
28721
+ center: [0, 0, 0],
28722
+ size: [rectLength, holeHeight, this.ctx.pcbThickness * 1.5]
28723
+ }),
28724
+ (0, import_primitives6.cylinder)({
28725
+ center: [-rectLength / 2, 0, 0],
28726
+ radius: holeRadius,
28727
+ height: this.ctx.pcbThickness * 1.5
28728
+ }),
28729
+ (0, import_primitives6.cylinder)({
28730
+ center: [rectLength / 2, 0, 0],
28731
+ radius: holeRadius,
28732
+ height: this.ctx.pcbThickness * 1.5
28733
+ })
28734
+ );
28735
+ } else {
28736
+ pillHole = (0, import_booleans3.union)(
28737
+ (0, import_primitives6.cuboid)({
28738
+ center: [0, 0, 0],
28739
+ size: [holeWidth, rectLength, this.ctx.pcbThickness * 1.5]
28740
+ }),
28741
+ (0, import_primitives6.cylinder)({
28742
+ center: [0, -rectLength / 2, 0],
28743
+ radius: holeRadius,
28744
+ height: this.ctx.pcbThickness * 1.5
28745
+ }),
28746
+ (0, import_primitives6.cylinder)({
28747
+ center: [0, rectLength / 2, 0],
28748
+ radius: holeRadius,
28749
+ height: this.ctx.pcbThickness * 1.5
28750
+ })
28751
+ );
28752
+ }
28753
+ const rotationRadians = hole.ccw_rotation * Math.PI / 180;
28754
+ pillHole = (0, import_transforms4.rotateZ)(rotationRadians, pillHole);
28755
+ pillHole = (0, import_transforms4.translate)([hole.x, hole.y, 0], pillHole);
28756
+ this.boardGeom = (0, import_booleans3.subtract)(this.boardGeom, pillHole);
28757
+ this.padGeoms = this.padGeoms.map(
28758
+ (pg) => (0, import_colors4.colorize)(colors.copper, (0, import_booleans3.subtract)(pg, pillHole))
28759
+ );
28489
28760
  }
28490
28761
  }
28491
28762
  processPad(pad2) {
@@ -29348,34 +29619,6 @@ function createPlatedHoleDrill({
29348
29619
  return drill.translate([x, y, 0]);
29349
29620
  }
29350
29621
 
29351
- // src/utils/manifold/process-non-plated-holes.ts
29352
- function isCircleHole(hole) {
29353
- return (hole.shape === "circle" || hole.hole_shape === "circle") && typeof hole.hole_diameter === "number";
29354
- }
29355
- function processNonPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup) {
29356
- const nonPlatedHoleBoardDrills = [];
29357
- const pcbHoles = su7(circuitJson).pcb_hole.list();
29358
- pcbHoles.forEach((hole) => {
29359
- if (isCircleHole(hole)) {
29360
- const translatedDrill = createCircleHoleDrill({
29361
- Manifold,
29362
- x: hole.x,
29363
- y: hole.y,
29364
- diameter: hole.hole_diameter,
29365
- thickness: pcbThickness,
29366
- segments: SMOOTH_CIRCLE_SEGMENTS
29367
- });
29368
- manifoldInstancesForCleanup.push(translatedDrill);
29369
- nonPlatedHoleBoardDrills.push(translatedDrill);
29370
- }
29371
- });
29372
- return { nonPlatedHoleBoardDrills };
29373
- }
29374
-
29375
- // src/utils/manifold/process-plated-holes.ts
29376
- import { su as su8 } from "@tscircuit/circuit-json-util";
29377
- import * as THREE19 from "three";
29378
-
29379
29622
  // src/utils/pad-geoms.ts
29380
29623
  var RECT_PAD_SEGMENTS2 = 64;
29381
29624
  function createRoundedRectPrism({
@@ -29451,7 +29694,69 @@ function createPadManifoldOp({
29451
29694
  return null;
29452
29695
  }
29453
29696
 
29697
+ // src/utils/manifold/process-non-plated-holes.ts
29698
+ function isCircleHole(hole) {
29699
+ return (hole.shape === "circle" || hole.hole_shape === "circle") && typeof hole.hole_diameter === "number";
29700
+ }
29701
+ function isPillHole(hole) {
29702
+ return (hole.shape === "pill" || hole.hole_shape === "pill") && typeof hole.hole_width === "number" && typeof hole.hole_height === "number";
29703
+ }
29704
+ function isRotatedPillHole(hole) {
29705
+ return (hole.shape === "rotated_pill" || hole.hole_shape === "rotated_pill") && typeof hole.hole_width === "number" && typeof hole.hole_height === "number" && typeof hole.ccw_rotation === "number";
29706
+ }
29707
+ function processNonPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup) {
29708
+ const nonPlatedHoleBoardDrills = [];
29709
+ const pcbHoles = su7(circuitJson).pcb_hole.list();
29710
+ const createPillOp = (width10, height10, depth) => {
29711
+ const pillOp = createRoundedRectPrism({
29712
+ Manifold,
29713
+ width: width10,
29714
+ height: height10,
29715
+ thickness: depth,
29716
+ borderRadius: Math.min(width10, height10) / 2
29717
+ });
29718
+ manifoldInstancesForCleanup.push(pillOp);
29719
+ return pillOp;
29720
+ };
29721
+ pcbHoles.forEach((hole) => {
29722
+ if (isCircleHole(hole)) {
29723
+ const translatedDrill = createCircleHoleDrill({
29724
+ Manifold,
29725
+ x: hole.x,
29726
+ y: hole.y,
29727
+ diameter: hole.hole_diameter,
29728
+ thickness: pcbThickness,
29729
+ segments: SMOOTH_CIRCLE_SEGMENTS
29730
+ });
29731
+ manifoldInstancesForCleanup.push(translatedDrill);
29732
+ nonPlatedHoleBoardDrills.push(translatedDrill);
29733
+ } else if (isPillHole(hole)) {
29734
+ const holeW = hole.hole_width;
29735
+ const holeH = hole.hole_height;
29736
+ const drillDepth = pcbThickness * 1.2;
29737
+ const pillDrillOp = createPillOp(holeW, holeH, drillDepth);
29738
+ const translatedPillDrill = pillDrillOp.translate([hole.x, hole.y, 0]);
29739
+ manifoldInstancesForCleanup.push(translatedPillDrill);
29740
+ nonPlatedHoleBoardDrills.push(translatedPillDrill);
29741
+ } else if (isRotatedPillHole(hole)) {
29742
+ const holeW = hole.hole_width;
29743
+ const holeH = hole.hole_height;
29744
+ const drillDepth = pcbThickness * 1.2;
29745
+ let pillDrillOp = createPillOp(holeW, holeH, drillDepth);
29746
+ const rotatedOp = pillDrillOp.rotate([0, 0, hole.ccw_rotation]);
29747
+ manifoldInstancesForCleanup.push(rotatedOp);
29748
+ pillDrillOp = rotatedOp;
29749
+ const translatedPillDrill = pillDrillOp.translate([hole.x, hole.y, 0]);
29750
+ manifoldInstancesForCleanup.push(translatedPillDrill);
29751
+ nonPlatedHoleBoardDrills.push(translatedPillDrill);
29752
+ }
29753
+ });
29754
+ return { nonPlatedHoleBoardDrills };
29755
+ }
29756
+
29454
29757
  // src/utils/manifold/process-plated-holes.ts
29758
+ import { su as su8 } from "@tscircuit/circuit-json-util";
29759
+ import * as THREE19 from "three";
29455
29760
  var COPPER_COLOR = new THREE19.Color(...colors.copper);
29456
29761
  var PLATED_HOLE_LIP_HEIGHT = 0.05;
29457
29762
  function processPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup, boardClipVolume) {
@@ -29576,6 +29881,8 @@ function processPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, mani
29576
29881
  } else if (ph.shape === "pill_hole_with_rect_pad") {
29577
29882
  const holeW = ph.hole_width;
29578
29883
  const holeH = ph.hole_height;
29884
+ const holeOffsetX = ph.hole_offset_x || 0;
29885
+ const holeOffsetY = ph.hole_offset_y || 0;
29579
29886
  const padWidth = ph.rect_pad_width;
29580
29887
  const padHeight = ph.rect_pad_height;
29581
29888
  const rectBorderRadius = extractRectBorderRadius(ph);
@@ -29583,7 +29890,9 @@ function processPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, mani
29583
29890
  const drillW = holeW + 2 * MANIFOLD_Z_OFFSET;
29584
29891
  const drillH = holeH + 2 * MANIFOLD_Z_OFFSET;
29585
29892
  const drillDepth = pcbThickness * 1.2;
29586
- let boardPillDrillOp = createPillOp(drillW, drillH, drillDepth);
29893
+ let boardPillDrillOp = createPillOp(drillW, drillH, drillDepth).translate(
29894
+ [holeOffsetX, holeOffsetY, 0]
29895
+ );
29587
29896
  const translatedBoardPillDrill = boardPillDrillOp.translate([
29588
29897
  ph.x,
29589
29898
  ph.y,
@@ -29591,51 +29900,59 @@ function processPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, mani
29591
29900
  ]);
29592
29901
  manifoldInstancesForCleanup.push(translatedBoardPillDrill);
29593
29902
  platedHoleBoardDrills.push(translatedBoardPillDrill);
29594
- let copperBarrelOp = createPillOp(
29595
- holeW,
29596
- holeH,
29597
- pcbThickness + 2 * MANIFOLD_Z_OFFSET
29598
- );
29599
- let topPadOp = createRoundedRectPrism({
29903
+ const mainFill = createRoundedRectPrism({
29600
29904
  Manifold,
29601
29905
  width: padWidth,
29602
29906
  height: padHeight,
29603
- thickness: padThickness,
29907
+ thickness: pcbThickness - 2 * padThickness + 0.1,
29908
+ // Fill between pads
29604
29909
  borderRadius: rectBorderRadius
29605
29910
  });
29606
- manifoldInstancesForCleanup.push(topPadOp);
29607
- let bottomPadOp = createRoundedRectPrism({
29911
+ manifoldInstancesForCleanup.push(mainFill);
29912
+ const topPad = createRoundedRectPrism({
29608
29913
  Manifold,
29609
29914
  width: padWidth,
29610
29915
  height: padHeight,
29611
- thickness: padThickness,
29916
+ thickness: padThickness + 0.1,
29612
29917
  borderRadius: rectBorderRadius
29613
- });
29614
- manifoldInstancesForCleanup.push(bottomPadOp);
29615
- const topPadZ = pcbThickness / 2 + padThickness / 2 + MANIFOLD_Z_OFFSET;
29616
- const bottomPadZ = -pcbThickness / 2 - padThickness / 2 - MANIFOLD_Z_OFFSET;
29617
- topPadOp = topPadOp.translate([0, 0, topPadZ]);
29618
- manifoldInstancesForCleanup.push(topPadOp);
29619
- bottomPadOp = bottomPadOp.translate([0, 0, bottomPadZ]);
29620
- manifoldInstancesForCleanup.push(bottomPadOp);
29621
- const copperUnionBeforeCut = Manifold.union([
29622
- copperBarrelOp,
29623
- topPadOp,
29624
- bottomPadOp
29918
+ }).translate([0, 0, pcbThickness / 2 - padThickness / 2 + 0.05]);
29919
+ const bottomPad = createRoundedRectPrism({
29920
+ Manifold,
29921
+ width: padWidth,
29922
+ height: padHeight,
29923
+ thickness: padThickness + 0.1,
29924
+ borderRadius: rectBorderRadius
29925
+ }).translate([0, 0, -pcbThickness / 2 + padThickness / 2 - 0.05]);
29926
+ manifoldInstancesForCleanup.push(topPad, bottomPad);
29927
+ const barrelPill = createPillOp(
29928
+ holeW,
29929
+ holeH,
29930
+ pcbThickness * 1.02
29931
+ // Slightly taller than board
29932
+ ).translate([holeOffsetX, holeOffsetY, 0]);
29933
+ manifoldInstancesForCleanup.push(barrelPill);
29934
+ const copperUnion = Manifold.union([
29935
+ mainFill,
29936
+ topPad,
29937
+ bottomPad,
29938
+ barrelPill
29625
29939
  ]);
29626
- manifoldInstancesForCleanup.push(copperUnionBeforeCut);
29627
- const holeCutWidth = Math.max(holeW - 2 * PLATED_HOLE_LIP_HEIGHT, 0.01);
29628
- const holeCutHeight = Math.max(holeH - 2 * PLATED_HOLE_LIP_HEIGHT, 0.01);
29629
- const holeCutDepth = pcbThickness + 2 * padThickness + 4 * MANIFOLD_Z_OFFSET;
29630
- let holeCutOp = createPillOp(holeCutWidth, holeCutHeight, holeCutDepth);
29631
- const finalPlatedPartOp = copperUnionBeforeCut.subtract(holeCutOp);
29632
- manifoldInstancesForCleanup.push(finalPlatedPartOp);
29633
- const translatedPlatedPart = finalPlatedPartOp.translate([ph.x, ph.y, 0]);
29634
- manifoldInstancesForCleanup.push(translatedPlatedPart);
29635
- let finalCopperOp = translatedPlatedPart;
29940
+ manifoldInstancesForCleanup.push(copperUnion);
29941
+ const holeCutOp = createPillOp(
29942
+ Math.max(holeW - 2 * PLATED_HOLE_LIP_HEIGHT, 0.01),
29943
+ Math.max(holeH - 2 * PLATED_HOLE_LIP_HEIGHT, 0.01),
29944
+ pcbThickness * 1.2
29945
+ // Ensure it cuts through
29946
+ ).translate([holeOffsetX, holeOffsetY, 0]);
29947
+ manifoldInstancesForCleanup.push(holeCutOp);
29948
+ const finalCopper = copperUnion.subtract(holeCutOp);
29949
+ manifoldInstancesForCleanup.push(finalCopper);
29950
+ const translatedCopper = finalCopper.translate([ph.x, ph.y, 0]);
29951
+ manifoldInstancesForCleanup.push(translatedCopper);
29952
+ let finalCopperOp = translatedCopper;
29636
29953
  if (boardClipVolume) {
29637
29954
  const clipped = Manifold.intersection([
29638
- translatedPlatedPart,
29955
+ translatedCopper,
29639
29956
  boardClipVolume
29640
29957
  ]);
29641
29958
  manifoldInstancesForCleanup.push(clipped);
@@ -29648,71 +29965,77 @@ function processPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, mani
29648
29965
  color: COPPER_COLOR
29649
29966
  });
29650
29967
  } else if (ph.shape === "circular_hole_with_rect_pad") {
29968
+ const holeOffsetX = ph.hole_offset_x || 0;
29969
+ const holeOffsetY = ph.hole_offset_y || 0;
29651
29970
  const translatedDrill = createCircleHoleDrill({
29652
29971
  Manifold,
29653
- x: ph.x,
29654
- y: ph.y,
29972
+ x: ph.x + holeOffsetX,
29973
+ // Apply hole offset
29974
+ y: ph.y + holeOffsetY,
29975
+ // Apply hole offset
29655
29976
  diameter: ph.hole_diameter,
29656
29977
  thickness: pcbThickness,
29657
29978
  segments: SMOOTH_CIRCLE_SEGMENTS
29658
29979
  });
29659
29980
  manifoldInstancesForCleanup.push(translatedDrill);
29660
29981
  platedHoleBoardDrills.push(translatedDrill);
29661
- const copperPartThickness = pcbThickness + 2 * MANIFOLD_Z_OFFSET;
29662
- const holeRadius = ph.hole_diameter / 2;
29663
- const barrelCylinder = Manifold.cylinder(
29664
- copperPartThickness,
29665
- holeRadius,
29666
- holeRadius,
29667
- SMOOTH_CIRCLE_SEGMENTS,
29668
- true
29669
- );
29670
- manifoldInstancesForCleanup.push(barrelCylinder);
29671
29982
  const padWidth = ph.rect_pad_width ?? ph.hole_diameter;
29672
29983
  const padHeight = ph.rect_pad_height ?? ph.hole_diameter;
29673
29984
  const rectBorderRadius = extractRectBorderRadius(ph);
29674
29985
  const padThickness = DEFAULT_SMT_PAD_THICKNESS;
29986
+ const holeRadius = ph.hole_diameter / 2;
29987
+ const mainFill = createRoundedRectPrism({
29988
+ Manifold,
29989
+ width: padWidth,
29990
+ height: padHeight,
29991
+ thickness: pcbThickness - 2 * padThickness + 0.1,
29992
+ // Fill between pads
29993
+ borderRadius: rectBorderRadius
29994
+ });
29995
+ manifoldInstancesForCleanup.push(mainFill);
29675
29996
  const topPad = createRoundedRectPrism({
29676
29997
  Manifold,
29677
29998
  width: padWidth,
29678
29999
  height: padHeight,
29679
- thickness: padThickness,
30000
+ thickness: padThickness + 0.1,
29680
30001
  borderRadius: rectBorderRadius
29681
- }).translate([
29682
- 0,
29683
- 0,
29684
- pcbThickness / 2 + padThickness / 2 + MANIFOLD_Z_OFFSET
29685
- ]);
30002
+ }).translate([0, 0, pcbThickness / 2 - padThickness / 2 + 0.05]);
29686
30003
  const bottomPad = createRoundedRectPrism({
29687
30004
  Manifold,
29688
30005
  width: padWidth,
29689
30006
  height: padHeight,
29690
- thickness: padThickness,
30007
+ thickness: padThickness + 0.1,
29691
30008
  borderRadius: rectBorderRadius
29692
- }).translate([
29693
- 0,
29694
- 0,
29695
- -pcbThickness / 2 - padThickness / 2 - MANIFOLD_Z_OFFSET
29696
- ]);
30009
+ }).translate([0, 0, -pcbThickness / 2 + padThickness / 2 - 0.05]);
29697
30010
  manifoldInstancesForCleanup.push(topPad, bottomPad);
29698
- const copperUnionUncut = Manifold.union([
29699
- barrelCylinder,
30011
+ const barrelCylinder = Manifold.cylinder(
30012
+ pcbThickness * 1.02,
30013
+ // Slightly taller than board
30014
+ holeRadius,
30015
+ holeRadius,
30016
+ SMOOTH_CIRCLE_SEGMENTS,
30017
+ true
30018
+ ).translate([holeOffsetX, holeOffsetY, 0]);
30019
+ manifoldInstancesForCleanup.push(barrelCylinder);
30020
+ const copperUnion = Manifold.union([
30021
+ mainFill,
29700
30022
  topPad,
29701
- bottomPad
30023
+ bottomPad,
30024
+ barrelCylinder
29702
30025
  ]);
29703
- manifoldInstancesForCleanup.push(copperUnionUncut);
29704
- const centerHoleRadius = Math.max(holeRadius - M, 0.01);
29705
- const centerHole = Manifold.cylinder(
29706
- copperPartThickness * 1.1,
29707
- centerHoleRadius,
29708
- centerHoleRadius,
30026
+ manifoldInstancesForCleanup.push(copperUnion);
30027
+ const holeDrill = Manifold.cylinder(
30028
+ pcbThickness * 1.2,
30029
+ // Ensure it cuts through
30030
+ Math.max(holeRadius - M, 0.01),
30031
+ Math.max(holeRadius - M, 0.01),
29709
30032
  SMOOTH_CIRCLE_SEGMENTS,
29710
30033
  true
29711
- );
29712
- manifoldInstancesForCleanup.push(centerHole);
29713
- const copperUnion = copperUnionUncut.subtract(centerHole);
29714
- manifoldInstancesForCleanup.push(copperUnion);
29715
- const translatedCopper = copperUnion.translate([ph.x, ph.y, 0]);
30034
+ ).translate([holeOffsetX, holeOffsetY, 0]);
30035
+ manifoldInstancesForCleanup.push(holeDrill);
30036
+ const finalCopper = copperUnion.subtract(holeDrill);
30037
+ manifoldInstancesForCleanup.push(finalCopper);
30038
+ const translatedCopper = finalCopper.translate([ph.x, ph.y, 0]);
29716
30039
  manifoldInstancesForCleanup.push(translatedCopper);
29717
30040
  let finalCopperOp = translatedCopper;
29718
30041
  if (boardClipVolume) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/3d-viewer",
3
- "version": "0.0.411",
3
+ "version": "0.0.413",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",
@@ -47,8 +47,8 @@
47
47
  "@storybook/blocks": "9.0.0-alpha.17",
48
48
  "@storybook/react-vite": "^9.1.5",
49
49
  "@tscircuit/circuit-json-util": "^0.0.72",
50
- "@tscircuit/core": "^0.0.783",
51
- "@tscircuit/props": "^0.0.360",
50
+ "@tscircuit/core": "^0.0.787",
51
+ "@tscircuit/props": "^0.0.364",
52
52
  "@tscircuit/checks": "^0.0.85",
53
53
  "@tscircuit/math-utils": "^0.0.27",
54
54
  "@tscircuit/capacity-autorouter": "^0.0.131",
@@ -60,7 +60,7 @@
60
60
  "@vitejs/plugin-react": "^4.3.4",
61
61
  "bun-match-svg": "^0.0.9",
62
62
  "bun-types": "1.2.1",
63
- "circuit-json": "0.0.278",
63
+ "circuit-json": "0.0.279",
64
64
  "circuit-to-svg": "^0.0.179",
65
65
  "debug": "^4.4.0",
66
66
  "jscad-electronics": "^0.0.48",