@tscircuit/3d-viewer 0.0.583 → 0.0.584

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 +941 -340
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -14432,12 +14432,12 @@ var require_browser = __commonJS({
14432
14432
  });
14433
14433
 
14434
14434
  // src/CadViewer.tsx
14435
- import { useCallback as useCallback24, useEffect as useEffect48, useRef as useRef29, useState as useState39 } from "react";
14436
- import * as THREE46 from "three";
14435
+ import { useCallback as useCallback24, useEffect as useEffect49, useRef as useRef29, useState as useState39 } from "react";
14436
+ import * as THREE48 from "three";
14437
14437
 
14438
14438
  // src/CadViewerJscad.tsx
14439
14439
  import { su as su11 } from "@tscircuit/circuit-json-util";
14440
- import { forwardRef as forwardRef3, useMemo as useMemo21 } from "react";
14440
+ import { forwardRef as forwardRef3, useMemo as useMemo22 } from "react";
14441
14441
 
14442
14442
  // src/AnyCadComponent.tsx
14443
14443
  import { su as su2 } from "@tscircuit/circuit-json-util";
@@ -32704,13 +32704,13 @@ var AnyCadComponent = ({
32704
32704
  };
32705
32705
 
32706
32706
  // src/CadViewerContainer.tsx
32707
- import { forwardRef as forwardRef2, useEffect as useEffect19, useMemo as useMemo15, useState as useState11 } from "react";
32708
- import * as THREE21 from "three";
32707
+ import { forwardRef as forwardRef2, useEffect as useEffect20, useMemo as useMemo16, useState as useState11 } from "react";
32708
+ import * as THREE23 from "three";
32709
32709
 
32710
32710
  // package.json
32711
32711
  var package_default = {
32712
32712
  name: "@tscircuit/3d-viewer",
32713
- version: "0.0.582",
32713
+ version: "0.0.583",
32714
32714
  main: "./dist/index.js",
32715
32715
  module: "./dist/index.js",
32716
32716
  type: "module",
@@ -33692,6 +33692,85 @@ var OrbitControls = ({
33692
33692
  return null;
33693
33693
  };
33694
33694
 
33695
+ // src/reference-objects/reference-object.ts
33696
+ var REFERENCE_OBJECT_CLEARANCE_MM = 10;
33697
+ var REFERENCE_OBJECT_SPECS = {
33698
+ banana: {
33699
+ type: "banana",
33700
+ label: "Show Banana",
33701
+ // USDA describes a medium banana as 7 to 7 7/8 inches long. The 190 mm
33702
+ // envelope is near the midpoint of that range.
33703
+ width: 190,
33704
+ height: 60,
33705
+ depth: 34,
33706
+ zCenter: 17
33707
+ },
33708
+ "credit-card": {
33709
+ type: "credit-card",
33710
+ label: "Show Credit Card",
33711
+ // ISO/IEC 7810 ID-1 dimensions.
33712
+ width: 85.6,
33713
+ height: 53.98,
33714
+ depth: 0.76,
33715
+ zCenter: 0
33716
+ },
33717
+ "14in-macbook": {
33718
+ type: "14in-macbook",
33719
+ label: "Show 14in Macbook",
33720
+ // Current 14-inch MacBook Pro exterior dimensions.
33721
+ width: 312.6,
33722
+ height: 221.2,
33723
+ depth: 15.5,
33724
+ zCenter: 0
33725
+ }
33726
+ };
33727
+ var REFERENCE_OBJECT_OPTIONS = Object.values(REFERENCE_OBJECT_SPECS);
33728
+ var safeDimension = (dimension) => Number.isFinite(dimension) ? Math.max(dimension ?? 0, 0) : 0;
33729
+ var safeCoordinate = (coordinate) => Number.isFinite(coordinate) ? coordinate ?? 0 : 0;
33730
+ var getReferenceObjectPosition = (type, boardDimensions, boardCenter) => {
33731
+ const spec = REFERENCE_OBJECT_SPECS[type];
33732
+ const boardWidth = safeDimension(boardDimensions?.width);
33733
+ const centerX = safeCoordinate(boardCenter?.x);
33734
+ const centerY = safeCoordinate(boardCenter?.y);
33735
+ return {
33736
+ x: centerX + boardWidth / 2 + REFERENCE_OBJECT_CLEARANCE_MM + spec.width / 2,
33737
+ y: centerY,
33738
+ z: 0
33739
+ };
33740
+ };
33741
+ var getComparisonBounds = (type, boardDimensions, boardCenter) => {
33742
+ const spec = REFERENCE_OBJECT_SPECS[type];
33743
+ const boardWidth = safeDimension(boardDimensions?.width);
33744
+ const boardHeight = safeDimension(boardDimensions?.height);
33745
+ const centerX = safeCoordinate(boardCenter?.x);
33746
+ const centerY = safeCoordinate(boardCenter?.y);
33747
+ const referencePosition = getReferenceObjectPosition(
33748
+ type,
33749
+ boardDimensions,
33750
+ boardCenter
33751
+ );
33752
+ return {
33753
+ minX: Math.min(
33754
+ centerX - boardWidth / 2,
33755
+ referencePosition.x - spec.width / 2
33756
+ ),
33757
+ maxX: Math.max(
33758
+ centerX + boardWidth / 2,
33759
+ referencePosition.x + spec.width / 2
33760
+ ),
33761
+ minY: Math.min(
33762
+ centerY - boardHeight / 2,
33763
+ referencePosition.y - spec.height / 2
33764
+ ),
33765
+ maxY: Math.max(
33766
+ centerY + boardHeight / 2,
33767
+ referencePosition.y + spec.height / 2
33768
+ ),
33769
+ minZ: Math.min(0, referencePosition.z + spec.zCenter - spec.depth / 2),
33770
+ maxZ: Math.max(0, referencePosition.z + spec.zCenter + spec.depth / 2)
33771
+ };
33772
+ };
33773
+
33695
33774
  // src/three-components/OrientationCubeCanvas.tsx
33696
33775
  import { useEffect as useEffect18, useRef as useRef7 } from "react";
33697
33776
  import * as THREE20 from "three";
@@ -33841,6 +33920,421 @@ var OrientationCubeCanvas = () => {
33841
33920
  );
33842
33921
  };
33843
33922
 
33923
+ // src/three-components/reference-object.tsx
33924
+ import { useEffect as useEffect19, useMemo as useMemo15 } from "react";
33925
+
33926
+ // src/reference-objects/create-reference-object.ts
33927
+ import * as THREE21 from "three";
33928
+ var createRoundedRectangleShape = (width10, height10, radius) => {
33929
+ const halfWidth = width10 / 2;
33930
+ const halfHeight = height10 / 2;
33931
+ const safeRadius = Math.min(radius, halfWidth, halfHeight);
33932
+ const shape = new THREE21.Shape();
33933
+ shape.moveTo(-halfWidth + safeRadius, -halfHeight);
33934
+ shape.lineTo(halfWidth - safeRadius, -halfHeight);
33935
+ shape.quadraticCurveTo(
33936
+ halfWidth,
33937
+ -halfHeight,
33938
+ halfWidth,
33939
+ -halfHeight + safeRadius
33940
+ );
33941
+ shape.lineTo(halfWidth, halfHeight - safeRadius);
33942
+ shape.quadraticCurveTo(
33943
+ halfWidth,
33944
+ halfHeight,
33945
+ halfWidth - safeRadius,
33946
+ halfHeight
33947
+ );
33948
+ shape.lineTo(-halfWidth + safeRadius, halfHeight);
33949
+ shape.quadraticCurveTo(
33950
+ -halfWidth,
33951
+ halfHeight,
33952
+ -halfWidth,
33953
+ halfHeight - safeRadius
33954
+ );
33955
+ shape.lineTo(-halfWidth, -halfHeight + safeRadius);
33956
+ shape.quadraticCurveTo(
33957
+ -halfWidth,
33958
+ -halfHeight,
33959
+ -halfWidth + safeRadius,
33960
+ -halfHeight
33961
+ );
33962
+ return shape;
33963
+ };
33964
+ var createRoundedPrismGeometry = (width10, height10, depth, radius) => {
33965
+ return createPrismGeometryFromShape(
33966
+ createRoundedRectangleShape(width10, height10, radius),
33967
+ depth
33968
+ );
33969
+ };
33970
+ var createPrismGeometryFromShape = (shape, depth) => {
33971
+ const geometry = new THREE21.ExtrudeGeometry(shape, {
33972
+ depth,
33973
+ bevelEnabled: false,
33974
+ curveSegments: 24
33975
+ });
33976
+ geometry.translate(0, 0, -depth / 2);
33977
+ geometry.computeVertexNormals();
33978
+ return geometry;
33979
+ };
33980
+ var normalizeObjectToSpec = (object, type) => {
33981
+ const spec = REFERENCE_OBJECT_SPECS[type];
33982
+ object.updateMatrixWorld(true);
33983
+ const initialBounds = new THREE21.Box3().setFromObject(object);
33984
+ const initialSize = initialBounds.getSize(new THREE21.Vector3());
33985
+ object.scale.set(
33986
+ spec.width / initialSize.x,
33987
+ spec.height / initialSize.y,
33988
+ spec.depth / initialSize.z
33989
+ );
33990
+ object.updateMatrixWorld(true);
33991
+ const scaledBounds = new THREE21.Box3().setFromObject(object);
33992
+ const scaledCenter = scaledBounds.getCenter(new THREE21.Vector3());
33993
+ object.position.set(
33994
+ -scaledCenter.x,
33995
+ -scaledCenter.y,
33996
+ spec.zCenter - scaledCenter.z
33997
+ );
33998
+ object.updateMatrixWorld(true);
33999
+ };
34000
+ var createBananaGeometry = () => {
34001
+ const curve = new THREE21.CatmullRomCurve3([
34002
+ new THREE21.Vector3(-82, 18, 16),
34003
+ new THREE21.Vector3(-67, 7, 15),
34004
+ new THREE21.Vector3(-43, -9, 14),
34005
+ new THREE21.Vector3(-15, -18, 14),
34006
+ new THREE21.Vector3(16, -18, 14),
34007
+ new THREE21.Vector3(47, -8, 14.5),
34008
+ new THREE21.Vector3(72, 8, 16),
34009
+ new THREE21.Vector3(84, 19, 17)
34010
+ ]);
34011
+ const tubularSegments = 84;
34012
+ const radialSegments = 30;
34013
+ const frames = curve.computeFrenetFrames(tubularSegments, false);
34014
+ const positions = [];
34015
+ const normals = [];
34016
+ const colors2 = [];
34017
+ const uvs = [];
34018
+ const indices = [];
34019
+ const ripeYellow = new THREE21.Color(16766023);
34020
+ const stemGreen = new THREE21.Color(12038965);
34021
+ for (let segment = 0; segment <= tubularSegments; segment++) {
34022
+ const t = segment / tubularSegments;
34023
+ const point = curve.getPointAt(t);
34024
+ const normal = frames.normals[segment];
34025
+ const binormal = frames.binormals[segment];
34026
+ const taper = Math.sin(Math.PI * t) ** 0.48;
34027
+ const radius = 3.4 + 12.4 * taper;
34028
+ const stemBlend = Math.max(0, (t - 0.82) / 0.18) * 0.38;
34029
+ const ringColor = ripeYellow.clone().lerp(stemGreen, stemBlend);
34030
+ for (let side = 0; side <= radialSegments; side++) {
34031
+ const angle = side / radialSegments * Math.PI * 2;
34032
+ const sin = Math.sin(angle);
34033
+ const cos = Math.cos(angle);
34034
+ const ridgeScale = 1 + Math.cos(angle * 5 + 0.35) * 0.035;
34035
+ const radialNormal = new THREE21.Vector3().addScaledVector(normal, cos).addScaledVector(binormal, sin * 0.9).normalize();
34036
+ const vertex = point.clone().addScaledVector(normal, cos * radius * ridgeScale).addScaledVector(binormal, sin * radius * 0.9 * ridgeScale);
34037
+ positions.push(vertex.x, vertex.y, vertex.z);
34038
+ normals.push(radialNormal.x, radialNormal.y, radialNormal.z);
34039
+ colors2.push(ringColor.r, ringColor.g, ringColor.b);
34040
+ uvs.push(t, side / radialSegments);
34041
+ }
34042
+ }
34043
+ for (let segment = 1; segment <= tubularSegments; segment++) {
34044
+ for (let side = 1; side <= radialSegments; side++) {
34045
+ const rowSize = radialSegments + 1;
34046
+ const a = rowSize * (segment - 1) + (side - 1);
34047
+ const b = rowSize * segment + (side - 1);
34048
+ const c = rowSize * segment + side;
34049
+ const d = rowSize * (segment - 1) + side;
34050
+ indices.push(a, d, b, b, d, c);
34051
+ }
34052
+ }
34053
+ const geometry = new THREE21.BufferGeometry();
34054
+ geometry.setIndex(indices);
34055
+ geometry.setAttribute(
34056
+ "position",
34057
+ new THREE21.Float32BufferAttribute(positions, 3)
34058
+ );
34059
+ geometry.setAttribute("normal", new THREE21.Float32BufferAttribute(normals, 3));
34060
+ geometry.setAttribute("color", new THREE21.Float32BufferAttribute(colors2, 3));
34061
+ geometry.setAttribute("uv", new THREE21.Float32BufferAttribute(uvs, 2));
34062
+ geometry.computeBoundingBox();
34063
+ geometry.computeBoundingSphere();
34064
+ return { geometry, curve };
34065
+ };
34066
+ var createBanana = () => {
34067
+ const group = new THREE21.Group();
34068
+ const banana = new THREE21.Group();
34069
+ const { geometry, curve } = createBananaGeometry();
34070
+ const peel = new THREE21.Mesh(
34071
+ geometry,
34072
+ new THREE21.MeshStandardMaterial({
34073
+ color: 16777215,
34074
+ roughness: 0.72,
34075
+ metalness: 0,
34076
+ vertexColors: true
34077
+ })
34078
+ );
34079
+ peel.name = "banana-peel";
34080
+ banana.add(peel);
34081
+ const blossomTip = new THREE21.Mesh(
34082
+ new THREE21.SphereGeometry(3.4, 20, 12),
34083
+ new THREE21.MeshStandardMaterial({
34084
+ color: 6242844,
34085
+ roughness: 0.92
34086
+ })
34087
+ );
34088
+ blossomTip.position.copy(curve.getPointAt(0));
34089
+ blossomTip.scale.set(1.15, 0.78, 0.78);
34090
+ blossomTip.name = "banana-blossom-tip";
34091
+ banana.add(blossomTip);
34092
+ const stemLength = 11;
34093
+ const stemTangent = curve.getTangentAt(1).normalize();
34094
+ const stem = new THREE21.Mesh(
34095
+ new THREE21.CylinderGeometry(2.5, 4.1, stemLength, 18),
34096
+ new THREE21.MeshStandardMaterial({
34097
+ color: 8483625,
34098
+ roughness: 0.82
34099
+ })
34100
+ );
34101
+ stem.quaternion.setFromUnitVectors(new THREE21.Vector3(0, 1, 0), stemTangent);
34102
+ stem.position.copy(curve.getPointAt(1)).addScaledVector(stemTangent, stemLength / 2);
34103
+ stem.name = "banana-stem";
34104
+ banana.add(stem);
34105
+ const stemTip = new THREE21.Mesh(
34106
+ new THREE21.SphereGeometry(2.55, 18, 10),
34107
+ new THREE21.MeshStandardMaterial({
34108
+ color: 5981470,
34109
+ roughness: 0.9
34110
+ })
34111
+ );
34112
+ stemTip.position.copy(curve.getPointAt(1)).addScaledVector(stemTangent, stemLength);
34113
+ stemTip.name = "banana-stem-tip";
34114
+ banana.add(stemTip);
34115
+ normalizeObjectToSpec(banana, "banana");
34116
+ group.add(banana);
34117
+ return group;
34118
+ };
34119
+ var createMacbookLidGeometry = (width10, height10, depth, radius, indentRadius) => {
34120
+ const lidShape = createRoundedRectangleShape(width10, height10, radius);
34121
+ const indent = new THREE21.Path();
34122
+ indent.absarc(0, 0, indentRadius, 0, Math.PI * 2, true);
34123
+ lidShape.holes.push(indent);
34124
+ return createPrismGeometryFromShape(lidShape, depth);
34125
+ };
34126
+ var createMacbookIndent = (topZ, radius) => {
34127
+ const indentDepth = 0.35;
34128
+ const diskDepth = 0.25;
34129
+ const disk = new THREE21.Mesh(
34130
+ new THREE21.CylinderGeometry(radius - 0.15, radius - 0.15, diskDepth, 48),
34131
+ new THREE21.MeshStandardMaterial({
34132
+ color: 10199202,
34133
+ roughness: 0.48,
34134
+ metalness: 0.28
34135
+ })
34136
+ );
34137
+ disk.rotation.x = Math.PI / 2;
34138
+ disk.position.z = topZ - indentDepth - diskDepth / 2;
34139
+ disk.name = "macbook-lid-indent";
34140
+ return disk;
34141
+ };
34142
+ var createCreditCard = () => {
34143
+ const spec = REFERENCE_OBJECT_SPECS["credit-card"];
34144
+ const group = new THREE21.Group();
34145
+ const body = new THREE21.Mesh(
34146
+ createRoundedPrismGeometry(spec.width, spec.height, spec.depth, 3.18),
34147
+ new THREE21.MeshStandardMaterial({
34148
+ color: 2383272,
34149
+ roughness: 0.38,
34150
+ metalness: 0.08
34151
+ })
34152
+ );
34153
+ body.name = "credit-card-body";
34154
+ group.add(body);
34155
+ const surfaceZ = spec.depth / 2 + 0.08;
34156
+ const chip = new THREE21.Mesh(
34157
+ createRoundedPrismGeometry(12, 9, 0.14, 1.2),
34158
+ new THREE21.MeshStandardMaterial({
34159
+ color: 14003525,
34160
+ roughness: 0.3,
34161
+ metalness: 0.62
34162
+ })
34163
+ );
34164
+ chip.position.set(-21, 3, surfaceZ);
34165
+ chip.name = "credit-card-chip";
34166
+ group.add(chip);
34167
+ const stripeMaterial = new THREE21.MeshStandardMaterial({
34168
+ color: 15331061,
34169
+ roughness: 0.45,
34170
+ metalness: 0.05
34171
+ });
34172
+ for (let index2 = 0; index2 < 4; index2++) {
34173
+ const stripe = new THREE21.Mesh(
34174
+ new THREE21.BoxGeometry(index2 === 3 ? 8 : 12, 0.8, 0.1),
34175
+ stripeMaterial
34176
+ );
34177
+ stripe.position.set(-20 + index2 * 14, -14, surfaceZ + 0.08);
34178
+ stripe.name = "credit-card-detail";
34179
+ group.add(stripe);
34180
+ }
34181
+ return group;
34182
+ };
34183
+ var createMacbook = () => {
34184
+ const spec = REFERENCE_OBJECT_SPECS["14in-macbook"];
34185
+ const group = new THREE21.Group();
34186
+ const aluminumMaterial = new THREE21.MeshStandardMaterial({
34187
+ color: 12896201,
34188
+ roughness: 0.3,
34189
+ metalness: 0.38
34190
+ });
34191
+ const body = new THREE21.Mesh(
34192
+ createRoundedPrismGeometry(spec.width, spec.height, 13.5, 9),
34193
+ aluminumMaterial
34194
+ );
34195
+ body.position.z = -1;
34196
+ body.name = "macbook-body";
34197
+ group.add(body);
34198
+ const lidDepth = 2;
34199
+ const lidCenterZ = 6.75;
34200
+ const lidTopZ = lidCenterZ + lidDepth / 2;
34201
+ const indentRadius = 12;
34202
+ const lid = new THREE21.Mesh(
34203
+ createMacbookLidGeometry(
34204
+ spec.width - 1.8,
34205
+ spec.height - 1.8,
34206
+ lidDepth,
34207
+ 8.2,
34208
+ indentRadius
34209
+ ),
34210
+ aluminumMaterial.clone()
34211
+ );
34212
+ lid.position.z = lidCenterZ;
34213
+ lid.name = "macbook-lid";
34214
+ group.add(lid);
34215
+ const hinge = new THREE21.Mesh(
34216
+ new THREE21.BoxGeometry(spec.width - 42, 3.2, 1.2),
34217
+ new THREE21.MeshStandardMaterial({
34218
+ color: 3158838,
34219
+ roughness: 0.55,
34220
+ metalness: 0.45
34221
+ })
34222
+ );
34223
+ hinge.position.set(0, spec.height / 2 - 2.4, 6.15);
34224
+ hinge.name = "macbook-hinge";
34225
+ group.add(hinge);
34226
+ group.add(createMacbookIndent(lidTopZ, indentRadius));
34227
+ const frontNotch = new THREE21.Mesh(
34228
+ new THREE21.BoxGeometry(54, 1.2, 0.8),
34229
+ new THREE21.MeshStandardMaterial({
34230
+ color: 6712174,
34231
+ roughness: 0.45,
34232
+ metalness: 0.5
34233
+ })
34234
+ );
34235
+ frontNotch.position.set(0, -spec.height / 2 + 0.5, 2.5);
34236
+ frontNotch.name = "macbook-front-notch";
34237
+ group.add(frontNotch);
34238
+ return group;
34239
+ };
34240
+ var createReferenceObject = (type) => {
34241
+ const group = type === "banana" ? createBanana() : type === "credit-card" ? createCreditCard() : createMacbook();
34242
+ group.name = `reference-object-${type}`;
34243
+ group.userData.isReferenceObject = true;
34244
+ group.userData.referenceObjectType = type;
34245
+ configureObjectShadows(group);
34246
+ return group;
34247
+ };
34248
+ var disposeReferenceObject = (object) => {
34249
+ const geometries = /* @__PURE__ */ new Set();
34250
+ const materials = /* @__PURE__ */ new Set();
34251
+ object.traverse((child) => {
34252
+ if (!(child instanceof THREE21.Mesh)) return;
34253
+ geometries.add(child.geometry);
34254
+ const childMaterials = Array.isArray(child.material) ? child.material : [child.material];
34255
+ for (const material of childMaterials) materials.add(material);
34256
+ });
34257
+ for (const geometry of geometries) geometry.dispose();
34258
+ for (const material of materials) material.dispose();
34259
+ };
34260
+
34261
+ // src/reference-objects/fit-camera-to-bounds.ts
34262
+ import * as THREE22 from "three";
34263
+ var fitCameraToBounds = (camera, controls, bounds, padding = 1.18) => {
34264
+ const width10 = Math.max(bounds.maxX - bounds.minX, 0);
34265
+ const height10 = Math.max(bounds.maxY - bounds.minY, 0);
34266
+ const depth = Math.max(bounds.maxZ - bounds.minZ, 0);
34267
+ const center = new THREE22.Vector3(
34268
+ (bounds.minX + bounds.maxX) / 2,
34269
+ (bounds.minY + bounds.maxY) / 2,
34270
+ (bounds.minZ + bounds.maxZ) / 2
34271
+ );
34272
+ const radius = Math.max(Math.hypot(width10, height10, depth) / 2, 1);
34273
+ const previousTarget = controls?.target ?? new THREE22.Vector3();
34274
+ const direction = new THREE22.Vector3(0, -0.75, 0.9).normalize();
34275
+ let distance5 = Math.max(camera.position.distanceTo(previousTarget), 5);
34276
+ if (camera instanceof THREE22.PerspectiveCamera) {
34277
+ const verticalFov = THREE22.MathUtils.degToRad(camera.fov);
34278
+ const horizontalFov = 2 * Math.atan(Math.tan(verticalFov / 2) * camera.aspect);
34279
+ const limitingFov = Math.min(verticalFov, horizontalFov);
34280
+ distance5 = radius * padding / Math.sin(limitingFov / 2);
34281
+ camera.far = Math.max(camera.far, distance5 + radius * padding * 3);
34282
+ camera.updateProjectionMatrix();
34283
+ } else if (camera instanceof THREE22.OrthographicCamera) {
34284
+ const halfViewWidth = Math.abs(camera.right - camera.left) / 2;
34285
+ const halfViewHeight = Math.abs(camera.top - camera.bottom) / 2;
34286
+ camera.zoom = Math.min(
34287
+ halfViewWidth / (radius * padding),
34288
+ halfViewHeight / (radius * padding)
34289
+ );
34290
+ distance5 = Math.max(distance5, radius * padding * 2);
34291
+ camera.updateProjectionMatrix();
34292
+ }
34293
+ camera.position.copy(center).addScaledVector(direction, distance5);
34294
+ camera.lookAt(center);
34295
+ camera.updateMatrixWorld();
34296
+ if (controls) {
34297
+ controls.target.copy(center);
34298
+ controls.update();
34299
+ }
34300
+ return { center, radius, distance: distance5 };
34301
+ };
34302
+
34303
+ // src/three-components/reference-object.tsx
34304
+ var ReferenceObject = ({
34305
+ type,
34306
+ boardDimensions,
34307
+ boardCenter
34308
+ }) => {
34309
+ const { scene } = useThree();
34310
+ const object = useMemo15(() => {
34311
+ const nextObject = createReferenceObject(type);
34312
+ const position = getReferenceObjectPosition(
34313
+ type,
34314
+ boardDimensions,
34315
+ boardCenter
34316
+ );
34317
+ nextObject.position.set(position.x, position.y, position.z);
34318
+ return nextObject;
34319
+ }, [type, boardDimensions, boardCenter]);
34320
+ useEffect19(() => {
34321
+ scene.add(object);
34322
+ return () => {
34323
+ scene.remove(object);
34324
+ disposeReferenceObject(object);
34325
+ };
34326
+ }, [scene, object]);
34327
+ return null;
34328
+ };
34329
+ var FitCameraToComparison = ({ bounds }) => {
34330
+ const { camera } = useThree();
34331
+ const { controlsRef } = useCameraController();
34332
+ useEffect19(() => {
34333
+ fitCameraToBounds(camera, controlsRef.current, bounds);
34334
+ }, [bounds, camera, controlsRef]);
34335
+ return null;
34336
+ };
34337
+
33844
34338
  // src/CadViewerContainer.tsx
33845
34339
  import { jsx as jsx15, jsxs as jsxs3 } from "react/jsx-runtime";
33846
34340
  var RotationTracker = () => {
@@ -33861,6 +34355,7 @@ var CadViewerContainer = forwardRef2(
33861
34355
  clickToInteractEnabled = false,
33862
34356
  boardDimensions,
33863
34357
  boardCenter,
34358
+ referenceObject,
33864
34359
  onUserInteraction,
33865
34360
  onCameraControllerReady
33866
34361
  }, ref) => {
@@ -33873,28 +34368,39 @@ var CadViewerContainer = forwardRef2(
33873
34368
  handleCameraCreated,
33874
34369
  handleControlsChange: handleSessionControlsChange
33875
34370
  } = useCameraSession();
33876
- useEffect19(() => {
34371
+ useEffect20(() => {
33877
34372
  if (onCameraControllerReady) {
33878
34373
  onCameraControllerReady(controller);
33879
34374
  }
33880
34375
  }, [controller, onCameraControllerReady]);
33881
- const gridSectionSize = useMemo15(() => {
34376
+ const comparisonBounds = useMemo16(() => {
34377
+ if (!referenceObject) return void 0;
34378
+ return getComparisonBounds(referenceObject, boardDimensions, boardCenter);
34379
+ }, [referenceObject, boardDimensions, boardCenter]);
34380
+ const gridSectionSize = useMemo16(() => {
33882
34381
  if (!boardDimensions) return 10;
33883
34382
  const width10 = boardDimensions.width ?? 0;
33884
34383
  const height10 = boardDimensions.height ?? 0;
33885
34384
  return Math.max(Math.max(width10, height10) * 1.5, 10);
33886
34385
  }, [boardDimensions]);
33887
- const orbitTarget = useMemo15(() => {
34386
+ const orbitTarget = useMemo16(() => {
34387
+ if (comparisonBounds) {
34388
+ return [
34389
+ (comparisonBounds.minX + comparisonBounds.maxX) / 2,
34390
+ (comparisonBounds.minY + comparisonBounds.maxY) / 2,
34391
+ (comparisonBounds.minZ + comparisonBounds.maxZ) / 2
34392
+ ];
34393
+ }
33888
34394
  if (!boardCenter) return void 0;
33889
34395
  return [boardCenter.x, boardCenter.y, 0];
33890
- }, [boardCenter]);
34396
+ }, [boardCenter, comparisonBounds]);
33891
34397
  return /* @__PURE__ */ jsxs3("div", { style: { position: "relative", width: "100%", height: "100%" }, children: [
33892
34398
  /* @__PURE__ */ jsx15(OrientationCubeCanvas, {}),
33893
34399
  /* @__PURE__ */ jsxs3(
33894
34400
  Canvas,
33895
34401
  {
33896
34402
  ref,
33897
- scene: { up: new THREE21.Vector3(0, 0, 1) },
34403
+ scene: { up: new THREE23.Vector3(0, 0, 1) },
33898
34404
  camera: { up: [0, 0, 1], position: initialCameraPosition },
33899
34405
  onCreated: ({ camera }) => {
33900
34406
  mainCameraRef.current = camera;
@@ -33930,6 +34436,15 @@ var CadViewerContainer = forwardRef2(
33930
34436
  shadowsEnabled: lightingEnabled
33931
34437
  }
33932
34438
  ),
34439
+ referenceObject && /* @__PURE__ */ jsx15(
34440
+ ReferenceObject,
34441
+ {
34442
+ type: referenceObject,
34443
+ boardDimensions,
34444
+ boardCenter
34445
+ }
34446
+ ),
34447
+ comparisonBounds && /* @__PURE__ */ jsx15(FitCameraToComparison, { bounds: comparisonBounds }),
33933
34448
  gridEnabled && /* @__PURE__ */ jsx15(
33934
34449
  Grid,
33935
34450
  {
@@ -34003,9 +34518,9 @@ var CadViewerContainer = forwardRef2(
34003
34518
 
34004
34519
  // src/hooks/use-convert-children-to-soup.ts
34005
34520
  import { Circuit } from "@tscircuit/core";
34006
- import { useMemo as useMemo16 } from "react";
34521
+ import { useMemo as useMemo17 } from "react";
34007
34522
  var useConvertChildrenToCircuitJson = (children) => {
34008
- return useMemo16(() => {
34523
+ return useMemo17(() => {
34009
34524
  if (!children) return [];
34010
34525
  const circuit = new Circuit();
34011
34526
  circuit.add(children);
@@ -34015,12 +34530,12 @@ var useConvertChildrenToCircuitJson = (children) => {
34015
34530
  };
34016
34531
 
34017
34532
  // src/hooks/use-stls-from-geom.ts
34018
- import { useState as useState12, useEffect as useEffect20 } from "react";
34533
+ import { useState as useState12, useEffect as useEffect21 } from "react";
34019
34534
  import stlSerializer from "@jscad/stl-serializer";
34020
34535
  var useStlsFromGeom = (geom) => {
34021
34536
  const [stls, setStls] = useState12([]);
34022
34537
  const [loading, setLoading] = useState12(true);
34023
- useEffect20(() => {
34538
+ useEffect21(() => {
34024
34539
  if (!geom) return;
34025
34540
  const generateStls = async () => {
34026
34541
  setLoading(true);
@@ -34049,7 +34564,7 @@ var useStlsFromGeom = (geom) => {
34049
34564
  };
34050
34565
 
34051
34566
  // src/hooks/useBoardGeomBuilder.ts
34052
- import { useState as useState13, useEffect as useEffect21, useRef as useRef8 } from "react";
34567
+ import { useState as useState13, useEffect as useEffect22, useRef as useRef8 } from "react";
34053
34568
 
34054
34569
  // src/soup-to-3d/index.ts
34055
34570
  var import_primitives2 = __toESM(require_primitives(), 1);
@@ -35363,7 +35878,7 @@ var BoardGeomBuilder = class {
35363
35878
  var useBoardGeomBuilder = (circuitJson) => {
35364
35879
  const [boardGeom, setBoardGeom] = useState13(null);
35365
35880
  const isProcessingRef = useRef8(false);
35366
- useEffect21(() => {
35881
+ useEffect22(() => {
35367
35882
  let isCancelled = false;
35368
35883
  if (!circuitJson) {
35369
35884
  setBoardGeom(null);
@@ -35409,10 +35924,10 @@ var useBoardGeomBuilder = (circuitJson) => {
35409
35924
  };
35410
35925
 
35411
35926
  // src/three-components/Error3d.tsx
35412
- import { useState as useState14, useCallback as useCallback8, useEffect as useEffect23, useMemo as useMemo18 } from "react";
35927
+ import { useState as useState14, useCallback as useCallback8, useEffect as useEffect24, useMemo as useMemo19 } from "react";
35413
35928
 
35414
35929
  // src/react-three/Text.tsx
35415
- import { useEffect as useEffect22, useMemo as useMemo17 } from "react";
35930
+ import { useEffect as useEffect23, useMemo as useMemo18 } from "react";
35416
35931
  import { Text as TroikaText2 } from "troika-three-text";
35417
35932
  var Text = ({
35418
35933
  children,
@@ -35427,7 +35942,7 @@ var Text = ({
35427
35942
  depthOffset
35428
35943
  }) => {
35429
35944
  const { rootObject } = useThree();
35430
- const mesh = useMemo17(() => {
35945
+ const mesh = useMemo18(() => {
35431
35946
  const textMesh = new TroikaText2();
35432
35947
  textMesh.text = children;
35433
35948
  if (position) textMesh.position.fromArray(position);
@@ -35452,7 +35967,7 @@ var Text = ({
35452
35967
  anchorY,
35453
35968
  depthOffset
35454
35969
  ]);
35455
- useEffect22(() => {
35970
+ useEffect23(() => {
35456
35971
  const parentObject = parent || rootObject;
35457
35972
  if (!parentObject || !mesh) return;
35458
35973
  parentObject.add(mesh);
@@ -35465,7 +35980,7 @@ var Text = ({
35465
35980
  };
35466
35981
 
35467
35982
  // src/three-components/Error3d.tsx
35468
- import * as THREE22 from "three";
35983
+ import * as THREE24 from "three";
35469
35984
  import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs4 } from "react/jsx-runtime";
35470
35985
  var Error3d = ({
35471
35986
  error,
@@ -35487,7 +36002,7 @@ var Error3d = ({
35487
36002
  setIsHovered(false);
35488
36003
  setHoverPosition(null);
35489
36004
  }, []);
35490
- const position = useMemo18(() => {
36005
+ const position = useMemo19(() => {
35491
36006
  if (cad_component?.position) {
35492
36007
  const p = [
35493
36008
  cad_component.position.x,
@@ -35498,12 +36013,12 @@ var Error3d = ({
35498
36013
  }
35499
36014
  return [0, 0, 0];
35500
36015
  }, [cad_component]);
35501
- const group = useMemo18(() => {
35502
- const g = new THREE22.Group();
36016
+ const group = useMemo19(() => {
36017
+ const g = new THREE24.Group();
35503
36018
  g.position.fromArray(position);
35504
36019
  return g;
35505
36020
  }, [position]);
35506
- useEffect23(() => {
36021
+ useEffect24(() => {
35507
36022
  if (!rootObject) return;
35508
36023
  rootObject.add(group);
35509
36024
  return () => {
@@ -35558,10 +36073,10 @@ var Error3d = ({
35558
36073
  ] });
35559
36074
  };
35560
36075
  var ErrorBox = ({ parent }) => {
35561
- const mesh = useMemo18(() => {
35562
- const m = new THREE22.Mesh(
35563
- new THREE22.BoxGeometry(0.5, 0.5, 0.5),
35564
- new THREE22.MeshStandardMaterial({
36076
+ const mesh = useMemo19(() => {
36077
+ const m = new THREE24.Mesh(
36078
+ new THREE24.BoxGeometry(0.5, 0.5, 0.5),
36079
+ new THREE24.MeshStandardMaterial({
35565
36080
  depthTest: false,
35566
36081
  transparent: true,
35567
36082
  color: "red",
@@ -35572,7 +36087,7 @@ var ErrorBox = ({ parent }) => {
35572
36087
  m.rotation.fromArray([Math.PI / 4, Math.PI / 4, 0]);
35573
36088
  return m;
35574
36089
  }, []);
35575
- useEffect23(() => {
36090
+ useEffect24(() => {
35576
36091
  parent.add(mesh);
35577
36092
  return () => {
35578
36093
  parent.remove(mesh);
@@ -35581,114 +36096,12 @@ var ErrorBox = ({ parent }) => {
35581
36096
  return null;
35582
36097
  };
35583
36098
 
35584
- // src/three-components/STLModel.tsx
35585
- import { useState as useState15, useEffect as useEffect24, useMemo as useMemo19 } from "react";
35586
- import * as THREE23 from "three";
35587
- import { STLLoader } from "three-stdlib";
35588
- function STLModel({
35589
- stlUrl,
35590
- stlData,
35591
- mtlUrl,
35592
- color,
35593
- opacity = 1,
35594
- layerType
35595
- }) {
35596
- const { rootObject } = useThree();
35597
- const [geom, setGeom] = useState15(null);
35598
- useEffect24(() => {
35599
- const loader = new STLLoader();
35600
- if (stlData) {
35601
- try {
35602
- const geometry = loader.parse(stlData);
35603
- setGeom(geometry);
35604
- } catch (e) {
35605
- console.error("Failed to parse STL data", e);
35606
- setGeom(null);
35607
- }
35608
- return;
35609
- }
35610
- if (stlUrl) {
35611
- loader.load(stlUrl, (geometry) => {
35612
- setGeom(geometry);
35613
- });
35614
- }
35615
- }, [stlUrl, stlData]);
35616
- const mesh = useMemo19(() => {
35617
- if (!geom) return null;
35618
- const isBoardLayer = layerType === "board";
35619
- const material = new THREE23.MeshStandardMaterial({
35620
- color: Array.isArray(color) ? new THREE23.Color(color[0], color[1], color[2]) : color,
35621
- transparent: opacity !== 1,
35622
- opacity,
35623
- polygonOffset: isBoardLayer,
35624
- polygonOffsetFactor: isBoardLayer ? 6 : 0,
35625
- polygonOffsetUnits: isBoardLayer ? 6 : 0
35626
- });
35627
- const createdMesh = new THREE23.Mesh(geom, material);
35628
- createdMesh.renderOrder = isBoardLayer ? -1 : 1;
35629
- configureObjectShadows(createdMesh, {
35630
- castShadow: !isBoardLayer && opacity === 1,
35631
- receiveShadow: true
35632
- });
35633
- return createdMesh;
35634
- }, [geom, color, opacity, layerType]);
35635
- useEffect24(() => {
35636
- if (!rootObject || !mesh) return;
35637
- rootObject.add(mesh);
35638
- return () => {
35639
- rootObject.remove(mesh);
35640
- mesh.geometry.dispose();
35641
- if (Array.isArray(mesh.material)) {
35642
- mesh.material.forEach((m) => m.dispose());
35643
- } else {
35644
- mesh.material.dispose();
35645
- }
35646
- };
35647
- }, [rootObject, mesh]);
35648
- return null;
35649
- }
35650
-
35651
- // src/three-components/VisibleSTLModel.tsx
35652
- import { jsx as jsx17 } from "react/jsx-runtime";
35653
- function VisibleSTLModel({
35654
- stlData,
35655
- color,
35656
- opacity = 1,
35657
- layerType
35658
- }) {
35659
- const { visibility } = useLayerVisibility();
35660
- let shouldShow = true;
35661
- if (layerType === "board") {
35662
- shouldShow = visibility.boardBody;
35663
- } else if (layerType === "top-copper") {
35664
- shouldShow = visibility.topCopper;
35665
- } else if (layerType === "bottom-copper") {
35666
- shouldShow = visibility.bottomCopper;
35667
- } else if (layerType === "top-silkscreen") {
35668
- shouldShow = visibility.topSilkscreen;
35669
- } else if (layerType === "bottom-silkscreen") {
35670
- shouldShow = visibility.bottomSilkscreen;
35671
- }
35672
- if (!shouldShow) {
35673
- return null;
35674
- }
35675
- return /* @__PURE__ */ jsx17(
35676
- STLModel,
35677
- {
35678
- stlData,
35679
- color,
35680
- opacity,
35681
- layerType
35682
- }
35683
- );
35684
- }
35685
-
35686
36099
  // src/three-components/JscadBoardTextures.tsx
35687
36100
  import { su as su8 } from "@tscircuit/circuit-json-util";
35688
36101
  import { useEffect as useEffect25, useMemo as useMemo20 } from "react";
35689
36102
 
35690
36103
  // src/textures/create-combined-board-textures.ts
35691
- import * as THREE35 from "three";
36104
+ import * as THREE36 from "three";
35692
36105
 
35693
36106
  // node_modules/@tscircuit/math-utils/dist/chunk-5N7UJNVK.js
35694
36107
  var getBoundsFromPoints = (points) => {
@@ -35743,7 +36156,7 @@ function calculateOutlineBounds(boardData) {
35743
36156
  // src/utils/pad-texture.ts
35744
36157
  import { su as su5 } from "@tscircuit/circuit-json-util";
35745
36158
  import { CircuitToCanvasDrawer } from "circuit-to-canvas";
35746
- import * as THREE24 from "three";
36159
+ import * as THREE25 from "three";
35747
36160
  function createPadTextureForLayer({
35748
36161
  layer,
35749
36162
  circuitJson,
@@ -35821,10 +36234,10 @@ function createPadTextureForLayer({
35821
36234
  drawSoldermaskTop: false,
35822
36235
  drawSoldermaskBottom: false
35823
36236
  });
35824
- const texture = new THREE24.CanvasTexture(canvas);
36237
+ const texture = new THREE25.CanvasTexture(canvas);
35825
36238
  texture.generateMipmaps = true;
35826
- texture.minFilter = THREE24.LinearMipmapLinearFilter;
35827
- texture.magFilter = THREE24.LinearFilter;
36239
+ texture.minFilter = THREE25.LinearMipmapLinearFilter;
36240
+ texture.magFilter = THREE25.LinearFilter;
35828
36241
  texture.anisotropy = 16;
35829
36242
  texture.needsUpdate = true;
35830
36243
  return texture;
@@ -35832,7 +36245,7 @@ function createPadTextureForLayer({
35832
36245
 
35833
36246
  // src/utils/panel-outline-texture.ts
35834
36247
  import { su as su6 } from "@tscircuit/circuit-json-util";
35835
- import * as THREE25 from "three";
36248
+ import * as THREE26 from "three";
35836
36249
  var resolvePanelIdForTexture = (circuitJson) => {
35837
36250
  const panels = circuitJson.filter(
35838
36251
  (e) => e.type === "pcb_panel"
@@ -35900,17 +36313,17 @@ function createPanelOutlineTextureForLayer({
35900
36313
  );
35901
36314
  }
35902
36315
  });
35903
- const texture = new THREE25.CanvasTexture(canvas);
36316
+ const texture = new THREE26.CanvasTexture(canvas);
35904
36317
  texture.generateMipmaps = true;
35905
- texture.minFilter = THREE25.LinearMipmapLinearFilter;
35906
- texture.magFilter = THREE25.LinearFilter;
36318
+ texture.minFilter = THREE26.LinearMipmapLinearFilter;
36319
+ texture.magFilter = THREE26.LinearFilter;
35907
36320
  texture.anisotropy = 16;
35908
36321
  texture.needsUpdate = true;
35909
36322
  return texture;
35910
36323
  }
35911
36324
 
35912
36325
  // src/utils/trace-texture.ts
35913
- import * as THREE26 from "three";
36326
+ import * as THREE27 from "three";
35914
36327
  import { CircuitToCanvasDrawer as CircuitToCanvasDrawer2 } from "circuit-to-canvas";
35915
36328
  import { getElementRenderLayers, su as su7 } from "@tscircuit/circuit-json-util";
35916
36329
 
@@ -36028,10 +36441,10 @@ function createTraceTextureForLayer({
36028
36441
  drawSoldermaskTop: false,
36029
36442
  drawSoldermaskBottom: false
36030
36443
  });
36031
- const texture = new THREE26.CanvasTexture(canvas);
36444
+ const texture = new THREE27.CanvasTexture(canvas);
36032
36445
  texture.generateMipmaps = true;
36033
- texture.minFilter = THREE26.LinearMipmapLinearFilter;
36034
- texture.magFilter = THREE26.LinearFilter;
36446
+ texture.minFilter = THREE27.LinearMipmapLinearFilter;
36447
+ texture.magFilter = THREE27.LinearFilter;
36035
36448
  texture.anisotropy = 16;
36036
36449
  texture.needsUpdate = true;
36037
36450
  return texture;
@@ -36039,7 +36452,7 @@ function createTraceTextureForLayer({
36039
36452
 
36040
36453
  // src/textures/create-copper-pour-texture-for-layer.ts
36041
36454
  import { CircuitToCanvasDrawer as CircuitToCanvasDrawer3 } from "circuit-to-canvas";
36042
- import * as THREE27 from "three";
36455
+ import * as THREE28 from "three";
36043
36456
  var toRgb = (colorArr) => {
36044
36457
  const [r = 0, g = 0, b = 0] = colorArr;
36045
36458
  return `rgb(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(
@@ -36161,17 +36574,17 @@ function createCopperPourTextureForLayer({
36161
36574
  if (!onlyCoveredBySoldermask) {
36162
36575
  setColorAndDraw(uncoveredPours, uncoveredColor);
36163
36576
  }
36164
- const texture = new THREE27.CanvasTexture(canvas);
36577
+ const texture = new THREE28.CanvasTexture(canvas);
36165
36578
  texture.generateMipmaps = true;
36166
- texture.minFilter = THREE27.LinearMipmapLinearFilter;
36167
- texture.magFilter = THREE27.LinearFilter;
36579
+ texture.minFilter = THREE28.LinearMipmapLinearFilter;
36580
+ texture.magFilter = THREE28.LinearFilter;
36168
36581
  texture.anisotropy = 16;
36169
36582
  texture.needsUpdate = true;
36170
36583
  return texture;
36171
36584
  }
36172
36585
 
36173
36586
  // src/textures/create-copper-text-texture-for-layer.ts
36174
- import * as THREE28 from "three";
36587
+ import * as THREE29 from "three";
36175
36588
 
36176
36589
  // src/textures/copper-text/copper-text-drawing.ts
36177
36590
  import { CircuitToCanvasDrawer as CircuitToCanvasDrawer4 } from "circuit-to-canvas";
@@ -36282,17 +36695,17 @@ function createCopperTextTextureForLayer({
36282
36695
  elements,
36283
36696
  copperColor
36284
36697
  });
36285
- const texture = new THREE28.CanvasTexture(canvas);
36698
+ const texture = new THREE29.CanvasTexture(canvas);
36286
36699
  texture.generateMipmaps = true;
36287
- texture.minFilter = THREE28.LinearMipmapLinearFilter;
36288
- texture.magFilter = THREE28.LinearFilter;
36700
+ texture.minFilter = THREE29.LinearMipmapLinearFilter;
36701
+ texture.magFilter = THREE29.LinearFilter;
36289
36702
  texture.anisotropy = 16;
36290
36703
  texture.needsUpdate = true;
36291
36704
  return texture;
36292
36705
  }
36293
36706
 
36294
36707
  // src/textures/create-fabrication-note-texture-for-layer.ts
36295
- import * as THREE29 from "three";
36708
+ import * as THREE30 from "three";
36296
36709
 
36297
36710
  // src/textures/fabrication-note/fabrication-note-drawing.ts
36298
36711
  import { CircuitToCanvasDrawer as CircuitToCanvasDrawer5 } from "circuit-to-canvas";
@@ -36520,17 +36933,17 @@ function createFabricationNoteTextureForLayer({
36520
36933
  bounds,
36521
36934
  elements
36522
36935
  });
36523
- const texture = new THREE29.CanvasTexture(canvas);
36936
+ const texture = new THREE30.CanvasTexture(canvas);
36524
36937
  texture.generateMipmaps = true;
36525
- texture.minFilter = THREE29.LinearMipmapLinearFilter;
36526
- texture.magFilter = THREE29.LinearFilter;
36938
+ texture.minFilter = THREE30.LinearMipmapLinearFilter;
36939
+ texture.magFilter = THREE30.LinearFilter;
36527
36940
  texture.anisotropy = 16;
36528
36941
  texture.needsUpdate = true;
36529
36942
  return texture;
36530
36943
  }
36531
36944
 
36532
36945
  // src/textures/create-keepout-texture-for-layer.ts
36533
- import * as THREE30 from "three";
36946
+ import * as THREE31 from "three";
36534
36947
 
36535
36948
  // src/textures/keepout/keepout-drawing.ts
36536
36949
  import { CircuitToCanvasDrawer as CircuitToCanvasDrawer6 } from "circuit-to-canvas";
@@ -36605,17 +37018,17 @@ function createKeepoutTextureForLayer({
36605
37018
  elements,
36606
37019
  keepoutColor
36607
37020
  });
36608
- const texture = new THREE30.CanvasTexture(canvas);
37021
+ const texture = new THREE31.CanvasTexture(canvas);
36609
37022
  texture.generateMipmaps = true;
36610
- texture.minFilter = THREE30.LinearMipmapLinearFilter;
36611
- texture.magFilter = THREE30.LinearFilter;
37023
+ texture.minFilter = THREE31.LinearMipmapLinearFilter;
37024
+ texture.magFilter = THREE31.LinearFilter;
36612
37025
  texture.anisotropy = 16;
36613
37026
  texture.needsUpdate = true;
36614
37027
  return texture;
36615
37028
  }
36616
37029
 
36617
37030
  // src/textures/create-pcb-note-texture-for-layer.ts
36618
- import * as THREE31 from "three";
37031
+ import * as THREE32 from "three";
36619
37032
 
36620
37033
  // src/textures/pcb-note/pcb-note-drawing.ts
36621
37034
  import { CircuitToCanvasDrawer as CircuitToCanvasDrawer7 } from "circuit-to-canvas";
@@ -36766,17 +37179,17 @@ function createPcbNoteTextureForLayer({
36766
37179
  bounds,
36767
37180
  elements
36768
37181
  });
36769
- const texture = new THREE31.CanvasTexture(canvas);
37182
+ const texture = new THREE32.CanvasTexture(canvas);
36770
37183
  texture.generateMipmaps = true;
36771
- texture.minFilter = THREE31.LinearMipmapLinearFilter;
36772
- texture.magFilter = THREE31.LinearFilter;
37184
+ texture.minFilter = THREE32.LinearMipmapLinearFilter;
37185
+ texture.magFilter = THREE32.LinearFilter;
36773
37186
  texture.anisotropy = 16;
36774
37187
  texture.needsUpdate = true;
36775
37188
  return texture;
36776
37189
  }
36777
37190
 
36778
37191
  // src/textures/create-silkscreen-texture-for-layer.ts
36779
- import * as THREE32 from "three";
37192
+ import * as THREE33 from "three";
36780
37193
 
36781
37194
  // src/textures/silkscreen/silkscreen-drawing.ts
36782
37195
  import { CircuitToCanvasDrawer as CircuitToCanvasDrawer8 } from "circuit-to-canvas";
@@ -36885,17 +37298,17 @@ function createSilkscreenTextureForLayer({
36885
37298
  elements,
36886
37299
  silkscreenColor
36887
37300
  });
36888
- const texture = new THREE32.CanvasTexture(canvas);
37301
+ const texture = new THREE33.CanvasTexture(canvas);
36889
37302
  texture.generateMipmaps = true;
36890
- texture.minFilter = THREE32.LinearMipmapLinearFilter;
36891
- texture.magFilter = THREE32.LinearFilter;
37303
+ texture.minFilter = THREE33.LinearMipmapLinearFilter;
37304
+ texture.magFilter = THREE33.LinearFilter;
36892
37305
  texture.anisotropy = 16;
36893
37306
  texture.needsUpdate = true;
36894
37307
  return texture;
36895
37308
  }
36896
37309
 
36897
37310
  // src/textures/create-soldermask-texture-for-layer.ts
36898
- import * as THREE33 from "three";
37311
+ import * as THREE34 from "three";
36899
37312
 
36900
37313
  // src/textures/soldermask/soldermask-drawing.ts
36901
37314
  import { CircuitToCanvasDrawer as CircuitToCanvasDrawer9 } from "circuit-to-canvas";
@@ -37030,17 +37443,17 @@ function createSoldermaskTextureForLayer({
37030
37443
  elements,
37031
37444
  boardMaterial: boardData.material
37032
37445
  });
37033
- const texture = new THREE33.CanvasTexture(canvas);
37446
+ const texture = new THREE34.CanvasTexture(canvas);
37034
37447
  texture.generateMipmaps = true;
37035
- texture.minFilter = THREE33.LinearMipmapLinearFilter;
37036
- texture.magFilter = THREE33.LinearFilter;
37448
+ texture.minFilter = THREE34.LinearMipmapLinearFilter;
37449
+ texture.magFilter = THREE34.LinearFilter;
37037
37450
  texture.anisotropy = 16;
37038
37451
  texture.needsUpdate = true;
37039
37452
  return texture;
37040
37453
  }
37041
37454
 
37042
37455
  // src/textures/create-through-hole-texture-for-layer.ts
37043
- import * as THREE34 from "three";
37456
+ import * as THREE35 from "three";
37044
37457
 
37045
37458
  // src/textures/through-hole/through-hole-drawing.ts
37046
37459
  import { CircuitToCanvasDrawer as CircuitToCanvasDrawer10 } from "circuit-to-canvas";
@@ -37133,10 +37546,10 @@ function createThroughHoleTextureForLayer({
37133
37546
  elements,
37134
37547
  copperColor
37135
37548
  });
37136
- const texture = new THREE34.CanvasTexture(canvas);
37549
+ const texture = new THREE35.CanvasTexture(canvas);
37137
37550
  texture.generateMipmaps = true;
37138
- texture.minFilter = THREE34.LinearMipmapLinearFilter;
37139
- texture.magFilter = THREE34.LinearFilter;
37551
+ texture.minFilter = THREE35.LinearMipmapLinearFilter;
37552
+ texture.magFilter = THREE35.LinearFilter;
37140
37553
  texture.anisotropy = 16;
37141
37554
  texture.needsUpdate = true;
37142
37555
  return texture;
@@ -37224,10 +37637,10 @@ var createCombinedTexture = ({
37224
37637
  applySoldermaskSurfaceFilter(ctx, canvasWidth, canvasHeight, {
37225
37638
  includeReflection: layer === "top"
37226
37639
  });
37227
- const combinedTexture = new THREE35.CanvasTexture(canvas);
37640
+ const combinedTexture = new THREE36.CanvasTexture(canvas);
37228
37641
  combinedTexture.generateMipmaps = false;
37229
- combinedTexture.minFilter = THREE35.LinearFilter;
37230
- combinedTexture.magFilter = THREE35.LinearFilter;
37642
+ combinedTexture.minFilter = THREE36.LinearFilter;
37643
+ combinedTexture.magFilter = THREE36.LinearFilter;
37231
37644
  combinedTexture.premultiplyAlpha = true;
37232
37645
  combinedTexture.anisotropy = 16;
37233
37646
  combinedTexture.needsUpdate = true;
@@ -37252,11 +37665,11 @@ var createMaskedCopperMask = ({
37252
37665
  if (!texture?.image) continue;
37253
37666
  ctx.drawImage(texture.image, 0, 0, width10, height10);
37254
37667
  }
37255
- const maskTexture = new THREE35.CanvasTexture(canvas);
37256
- maskTexture.colorSpace = THREE35.NoColorSpace;
37668
+ const maskTexture = new THREE36.CanvasTexture(canvas);
37669
+ maskTexture.colorSpace = THREE36.NoColorSpace;
37257
37670
  maskTexture.generateMipmaps = false;
37258
- maskTexture.minFilter = THREE35.LinearFilter;
37259
- maskTexture.magFilter = THREE35.LinearFilter;
37671
+ maskTexture.minFilter = THREE36.LinearFilter;
37672
+ maskTexture.magFilter = THREE36.LinearFilter;
37260
37673
  maskTexture.premultiplyAlpha = false;
37261
37674
  maskTexture.needsUpdate = true;
37262
37675
  return maskTexture;
@@ -37394,7 +37807,7 @@ function createCombinedBoardTextures({
37394
37807
  }
37395
37808
 
37396
37809
  // src/textures/create-three-texture-meshes.ts
37397
- import * as THREE37 from "three";
37810
+ import * as THREE38 from "three";
37398
37811
 
37399
37812
  // src/board-surface-textures.ts
37400
37813
  var REALISTIC_BOARD_SURFACE_MATERIAL = {
@@ -37417,7 +37830,7 @@ var PAD_COPPER_TEXTURE_MATERIAL = {
37417
37830
  };
37418
37831
 
37419
37832
  // src/utils/create-board-relief-textures.ts
37420
- import * as THREE36 from "three";
37833
+ import * as THREE37 from "three";
37421
37834
  var PLAIN_SOLDERMASK_HEIGHT = 0.22;
37422
37835
  var MASKED_COPPER_HEIGHT = 0.7;
37423
37836
  var EXPOSED_COPPER_HEIGHT = 0.86;
@@ -37506,13 +37919,13 @@ var getBoardSurfaceProfile = (r, g, b, hasMaskedCopper) => {
37506
37919
  };
37507
37920
  };
37508
37921
  var createDataTexture = (canvas) => {
37509
- const texture = new THREE36.CanvasTexture(canvas);
37510
- texture.colorSpace = THREE36.NoColorSpace;
37922
+ const texture = new THREE37.CanvasTexture(canvas);
37923
+ texture.colorSpace = THREE37.NoColorSpace;
37511
37924
  texture.generateMipmaps = false;
37512
- texture.minFilter = THREE36.LinearFilter;
37513
- texture.magFilter = THREE36.LinearFilter;
37514
- texture.wrapS = THREE36.ClampToEdgeWrapping;
37515
- texture.wrapT = THREE36.ClampToEdgeWrapping;
37925
+ texture.minFilter = THREE37.LinearFilter;
37926
+ texture.magFilter = THREE37.LinearFilter;
37927
+ texture.wrapS = THREE37.ClampToEdgeWrapping;
37928
+ texture.wrapT = THREE37.ClampToEdgeWrapping;
37516
37929
  texture.needsUpdate = true;
37517
37930
  return texture;
37518
37931
  };
@@ -37619,7 +38032,7 @@ var createBoardReliefTextures = (texture, maskedCopperMask) => {
37619
38032
  for (let x = 0; x < sourceCanvas.width; x++) {
37620
38033
  const dx = (getHeight4(x + 1, y) - getHeight4(x - 1, y)) * 4;
37621
38034
  const dy = (getHeight4(x, y + 1) - getHeight4(x, y - 1)) * 4;
37622
- const normal = new THREE36.Vector3(-dx, -dy, 1).normalize();
38035
+ const normal = new THREE37.Vector3(-dx, -dy, 1).normalize();
37623
38036
  const i = (y * sourceCanvas.width + x) * 4;
37624
38037
  normalData[i] = (normal.x * 0.5 + 0.5) * 255;
37625
38038
  normalData[i + 1] = (normal.y * 0.5 + 0.5) * 255;
@@ -37649,16 +38062,16 @@ function createTexturePlane(config, boardData) {
37649
38062
  } = config;
37650
38063
  if (!texture) return null;
37651
38064
  const boardOutlineBounds = calculateOutlineBounds(boardData);
37652
- const planeGeom = new THREE37.PlaneGeometry(
38065
+ const planeGeom = new THREE38.PlaneGeometry(
37653
38066
  boardOutlineBounds.width,
37654
38067
  boardOutlineBounds.height
37655
38068
  );
37656
- texture.colorSpace = THREE37.SRGBColorSpace;
38069
+ texture.colorSpace = THREE38.SRGBColorSpace;
37657
38070
  const sharedMaterialOptions = {
37658
38071
  map: texture,
37659
38072
  transparent: true,
37660
38073
  alphaTest: 0.08,
37661
- side: THREE37.FrontSide,
38074
+ side: THREE38.FrontSide,
37662
38075
  depthWrite: true,
37663
38076
  polygonOffset: usePolygonOffset,
37664
38077
  polygonOffsetFactor: usePolygonOffset ? -4 : 0,
@@ -37667,12 +38080,12 @@ function createTexturePlane(config, boardData) {
37667
38080
  opacity: isFaux ? FAUX_BOARD_OPACITY : 1
37668
38081
  };
37669
38082
  const reliefTextures = createBoardReliefTextures(texture, maskedCopperMask);
37670
- const material = new THREE37.MeshPhysicalMaterial({
38083
+ const material = new THREE38.MeshPhysicalMaterial({
37671
38084
  ...sharedMaterialOptions,
37672
38085
  bumpMap: reliefTextures?.bumpMap ?? null,
37673
38086
  bumpScale: REALISTIC_BOARD_SURFACE_MATERIAL.bumpScale,
37674
38087
  normalMap: reliefTextures?.normalMap ?? null,
37675
- normalScale: new THREE37.Vector2(
38088
+ normalScale: new THREE38.Vector2(
37676
38089
  REALISTIC_BOARD_SURFACE_MATERIAL.normalScale,
37677
38090
  REALISTIC_BOARD_SURFACE_MATERIAL.normalScale
37678
38091
  ),
@@ -37686,7 +38099,7 @@ function createTexturePlane(config, boardData) {
37686
38099
  clearcoatRoughness: REALISTIC_BOARD_SURFACE_MATERIAL.clearcoatRoughness,
37687
38100
  envMapIntensity: 0.18
37688
38101
  });
37689
- const mesh = new THREE37.Mesh(planeGeom, material);
38102
+ const mesh = new THREE38.Mesh(planeGeom, material);
37690
38103
  mesh.position.set(
37691
38104
  boardOutlineBounds.centerX,
37692
38105
  boardOutlineBounds.centerY,
@@ -37734,7 +38147,7 @@ function createTextureMeshes(textures, boardData, pcbThickness, isFaux = false)
37734
38147
  }
37735
38148
 
37736
38149
  // src/three-components/JscadBoardTextures.tsx
37737
- import * as THREE38 from "three";
38150
+ import * as THREE39 from "three";
37738
38151
 
37739
38152
  // src/utils/layer-texture-resolution.ts
37740
38153
  var DEFAULT_MAX_TEXTURE_PIXELS = 4e6;
@@ -37830,7 +38243,7 @@ function JscadBoardTextures({
37830
38243
  const typedMaterial = material;
37831
38244
  for (const prop of textureProps) {
37832
38245
  const texture = typedMaterial[prop];
37833
- if (texture && texture instanceof THREE38.Texture) {
38246
+ if (texture && texture instanceof THREE39.Texture) {
37834
38247
  texture.dispose();
37835
38248
  typedMaterial[prop] = null;
37836
38249
  }
@@ -37849,16 +38262,16 @@ function JscadBoardTextures({
37849
38262
  }) => {
37850
38263
  if (!texture) return null;
37851
38264
  const boardOutlineBounds = calculateOutlineBounds(boardData);
37852
- const planeGeom = new THREE38.PlaneGeometry(
38265
+ const planeGeom = new THREE39.PlaneGeometry(
37853
38266
  boardOutlineBounds.width,
37854
38267
  boardOutlineBounds.height
37855
38268
  );
37856
- texture.colorSpace = THREE38.SRGBColorSpace;
38269
+ texture.colorSpace = THREE39.SRGBColorSpace;
37857
38270
  const sharedMaterialOptions = {
37858
38271
  map: texture,
37859
38272
  transparent: true,
37860
38273
  alphaTest: 0.08,
37861
- side: THREE38.FrontSide,
38274
+ side: THREE39.FrontSide,
37862
38275
  depthWrite,
37863
38276
  polygonOffset: usePolygonOffset,
37864
38277
  polygonOffsetFactor: usePolygonOffset ? -4 : 0,
@@ -37869,12 +38282,12 @@ function JscadBoardTextures({
37869
38282
  texture,
37870
38283
  maskedCopperMask
37871
38284
  );
37872
- const material = new THREE38.MeshPhysicalMaterial({
38285
+ const material = new THREE39.MeshPhysicalMaterial({
37873
38286
  ...sharedMaterialOptions,
37874
38287
  bumpMap: reliefTextures?.bumpMap ?? null,
37875
38288
  bumpScale: REALISTIC_BOARD_SURFACE_MATERIAL.bumpScale,
37876
38289
  normalMap: reliefTextures?.normalMap ?? null,
37877
- normalScale: new THREE38.Vector2(
38290
+ normalScale: new THREE39.Vector2(
37878
38291
  REALISTIC_BOARD_SURFACE_MATERIAL.normalScale,
37879
38292
  REALISTIC_BOARD_SURFACE_MATERIAL.normalScale
37880
38293
  ),
@@ -37886,7 +38299,7 @@ function JscadBoardTextures({
37886
38299
  clearcoatRoughness: REALISTIC_BOARD_SURFACE_MATERIAL.clearcoatRoughness,
37887
38300
  envMapIntensity: 0.18
37888
38301
  });
37889
- const mesh = new THREE38.Mesh(planeGeom, material);
38302
+ const mesh = new THREE39.Mesh(planeGeom, material);
37890
38303
  mesh.position.set(
37891
38304
  boardOutlineBounds.centerX,
37892
38305
  boardOutlineBounds.centerY,
@@ -37936,7 +38349,7 @@ function JscadBoardTextures({
37936
38349
  for (const material of mesh.material) {
37937
38350
  disposeTextureMaterial(material);
37938
38351
  }
37939
- } else if (mesh.material instanceof THREE38.Material) {
38352
+ } else if (mesh.material instanceof THREE39.Material) {
37940
38353
  disposeTextureMaterial(mesh.material);
37941
38354
  }
37942
38355
  }
@@ -37947,6 +38360,108 @@ function JscadBoardTextures({
37947
38360
  return null;
37948
38361
  }
37949
38362
 
38363
+ // src/three-components/STLModel.tsx
38364
+ import { useState as useState15, useEffect as useEffect26, useMemo as useMemo21 } from "react";
38365
+ import * as THREE40 from "three";
38366
+ import { STLLoader } from "three-stdlib";
38367
+ function STLModel({
38368
+ stlUrl,
38369
+ stlData,
38370
+ mtlUrl,
38371
+ color,
38372
+ opacity = 1,
38373
+ layerType
38374
+ }) {
38375
+ const { rootObject } = useThree();
38376
+ const [geom, setGeom] = useState15(null);
38377
+ useEffect26(() => {
38378
+ const loader = new STLLoader();
38379
+ if (stlData) {
38380
+ try {
38381
+ const geometry = loader.parse(stlData);
38382
+ setGeom(geometry);
38383
+ } catch (e) {
38384
+ console.error("Failed to parse STL data", e);
38385
+ setGeom(null);
38386
+ }
38387
+ return;
38388
+ }
38389
+ if (stlUrl) {
38390
+ loader.load(stlUrl, (geometry) => {
38391
+ setGeom(geometry);
38392
+ });
38393
+ }
38394
+ }, [stlUrl, stlData]);
38395
+ const mesh = useMemo21(() => {
38396
+ if (!geom) return null;
38397
+ const isBoardLayer = layerType === "board";
38398
+ const material = new THREE40.MeshStandardMaterial({
38399
+ color: Array.isArray(color) ? new THREE40.Color(color[0], color[1], color[2]) : color,
38400
+ transparent: opacity !== 1,
38401
+ opacity,
38402
+ polygonOffset: isBoardLayer,
38403
+ polygonOffsetFactor: isBoardLayer ? 6 : 0,
38404
+ polygonOffsetUnits: isBoardLayer ? 6 : 0
38405
+ });
38406
+ const createdMesh = new THREE40.Mesh(geom, material);
38407
+ createdMesh.renderOrder = isBoardLayer ? -1 : 1;
38408
+ configureObjectShadows(createdMesh, {
38409
+ castShadow: !isBoardLayer && opacity === 1,
38410
+ receiveShadow: true
38411
+ });
38412
+ return createdMesh;
38413
+ }, [geom, color, opacity, layerType]);
38414
+ useEffect26(() => {
38415
+ if (!rootObject || !mesh) return;
38416
+ rootObject.add(mesh);
38417
+ return () => {
38418
+ rootObject.remove(mesh);
38419
+ mesh.geometry.dispose();
38420
+ if (Array.isArray(mesh.material)) {
38421
+ mesh.material.forEach((m) => m.dispose());
38422
+ } else {
38423
+ mesh.material.dispose();
38424
+ }
38425
+ };
38426
+ }, [rootObject, mesh]);
38427
+ return null;
38428
+ }
38429
+
38430
+ // src/three-components/VisibleSTLModel.tsx
38431
+ import { jsx as jsx17 } from "react/jsx-runtime";
38432
+ function VisibleSTLModel({
38433
+ stlData,
38434
+ color,
38435
+ opacity = 1,
38436
+ layerType
38437
+ }) {
38438
+ const { visibility } = useLayerVisibility();
38439
+ let shouldShow = true;
38440
+ if (layerType === "board") {
38441
+ shouldShow = visibility.boardBody;
38442
+ } else if (layerType === "top-copper") {
38443
+ shouldShow = visibility.topCopper;
38444
+ } else if (layerType === "bottom-copper") {
38445
+ shouldShow = visibility.bottomCopper;
38446
+ } else if (layerType === "top-silkscreen") {
38447
+ shouldShow = visibility.topSilkscreen;
38448
+ } else if (layerType === "bottom-silkscreen") {
38449
+ shouldShow = visibility.bottomSilkscreen;
38450
+ }
38451
+ if (!shouldShow) {
38452
+ return null;
38453
+ }
38454
+ return /* @__PURE__ */ jsx17(
38455
+ STLModel,
38456
+ {
38457
+ stlData,
38458
+ color,
38459
+ opacity,
38460
+ layerType
38461
+ }
38462
+ );
38463
+ }
38464
+
37950
38465
  // src/utils/preprocess-circuit-json.ts
37951
38466
  import { su as su10 } from "@tscircuit/circuit-json-util";
37952
38467
 
@@ -38045,16 +38560,17 @@ var CadViewerJscad = forwardRef3(
38045
38560
  clickToInteractEnabled,
38046
38561
  onUserInteraction,
38047
38562
  onCameraControllerReady,
38048
- resolveStaticAsset
38563
+ resolveStaticAsset,
38564
+ referenceObject
38049
38565
  }, ref) => {
38050
38566
  const childrenSoup = useConvertChildrenToCircuitJson(children);
38051
- const internalCircuitJson = useMemo21(() => {
38567
+ const internalCircuitJson = useMemo22(() => {
38052
38568
  return addFauxBoardIfNeeded(
38053
38569
  circuitJson ?? childrenSoup
38054
38570
  );
38055
38571
  }, [circuitJson, childrenSoup]);
38056
38572
  const boardGeom = useBoardGeomBuilder(internalCircuitJson);
38057
- const initialCameraPosition = useMemo21(() => {
38573
+ const initialCameraPosition = useMemo22(() => {
38058
38574
  if (!internalCircuitJson) return [5, -5, 5];
38059
38575
  try {
38060
38576
  const board = su11(internalCircuitJson).pcb_board.list()[0];
@@ -38080,7 +38596,7 @@ var CadViewerJscad = forwardRef3(
38080
38596
  return [5, -5, 5];
38081
38597
  }
38082
38598
  }, [internalCircuitJson]);
38083
- const isFauxBoard = useMemo21(() => {
38599
+ const isFauxBoard = useMemo22(() => {
38084
38600
  if (!internalCircuitJson) return false;
38085
38601
  try {
38086
38602
  const board = su11(internalCircuitJson).pcb_board.list()[0];
@@ -38089,23 +38605,25 @@ var CadViewerJscad = forwardRef3(
38089
38605
  return false;
38090
38606
  }
38091
38607
  }, [internalCircuitJson]);
38092
- const boardDimensions = useMemo21(() => {
38608
+ const boardDimensions = useMemo22(() => {
38093
38609
  if (!internalCircuitJson) return void 0;
38094
38610
  try {
38095
38611
  const board = su11(internalCircuitJson).pcb_board.list()[0];
38096
38612
  if (!board) return void 0;
38097
- return { width: board.width ?? 0, height: board.height ?? 0 };
38613
+ const bounds = calculateOutlineBounds(board);
38614
+ return { width: bounds.width, height: bounds.height };
38098
38615
  } catch (e) {
38099
38616
  console.error(e);
38100
38617
  return void 0;
38101
38618
  }
38102
38619
  }, [internalCircuitJson]);
38103
- const boardCenter = useMemo21(() => {
38620
+ const boardCenter = useMemo22(() => {
38104
38621
  if (!internalCircuitJson) return void 0;
38105
38622
  try {
38106
38623
  const board = su11(internalCircuitJson).pcb_board.list()[0];
38107
- if (!board || !board.center) return void 0;
38108
- return { x: board.center.x, y: board.center.y };
38624
+ if (!board) return void 0;
38625
+ const bounds = calculateOutlineBounds(board);
38626
+ return { x: bounds.centerX, y: bounds.centerY };
38109
38627
  } catch (e) {
38110
38628
  console.error(e);
38111
38629
  return void 0;
@@ -38123,6 +38641,7 @@ var CadViewerJscad = forwardRef3(
38123
38641
  clickToInteractEnabled,
38124
38642
  boardDimensions,
38125
38643
  boardCenter,
38644
+ referenceObject,
38126
38645
  onUserInteraction,
38127
38646
  onCameraControllerReady,
38128
38647
  children: [
@@ -38168,13 +38687,13 @@ var CadViewerJscad = forwardRef3(
38168
38687
 
38169
38688
  // src/CadViewerManifold.tsx
38170
38689
  import { su as su17 } from "@tscircuit/circuit-json-util";
38171
- import { useEffect as useEffect27, useMemo as useMemo23, useState as useState17 } from "react";
38172
- import * as THREE45 from "three";
38690
+ import { useEffect as useEffect28, useMemo as useMemo24, useState as useState17 } from "react";
38691
+ import * as THREE47 from "three";
38173
38692
 
38174
38693
  // src/hooks/useManifoldBoardBuilder.ts
38175
38694
  import { su as su16 } from "@tscircuit/circuit-json-util";
38176
- import { useEffect as useEffect26, useMemo as useMemo22, useRef as useRef9, useState as useState16 } from "react";
38177
- import * as THREE42 from "three";
38695
+ import { useEffect as useEffect27, useMemo as useMemo23, useRef as useRef9, useState as useState16 } from "react";
38696
+ import * as THREE44 from "three";
38178
38697
 
38179
38698
  // src/utils/manifold/create-manifold-board.ts
38180
38699
  var arePointsClockwise2 = (points) => {
@@ -38523,17 +39042,17 @@ function processNonPlatedHolesForManifold(Manifold, CrossSection, circuitJson, p
38523
39042
 
38524
39043
  // src/utils/manifold/process-plated-holes.ts
38525
39044
  import { su as su14 } from "@tscircuit/circuit-json-util";
38526
- import * as THREE40 from "three";
39045
+ import * as THREE42 from "three";
38527
39046
 
38528
39047
  // src/utils/manifold-mesh-to-three-geometry.ts
38529
- import * as THREE39 from "three";
39048
+ import * as THREE41 from "three";
38530
39049
  function manifoldMeshToThreeGeometry(manifoldMesh) {
38531
- const geometry = new THREE39.BufferGeometry();
39050
+ const geometry = new THREE41.BufferGeometry();
38532
39051
  geometry.setAttribute(
38533
39052
  "position",
38534
- new THREE39.Float32BufferAttribute(manifoldMesh.vertProperties, 3)
39053
+ new THREE41.Float32BufferAttribute(manifoldMesh.vertProperties, 3)
38535
39054
  );
38536
- geometry.setIndex(new THREE39.Uint32BufferAttribute(manifoldMesh.triVerts, 1));
39055
+ geometry.setIndex(new THREE41.Uint32BufferAttribute(manifoldMesh.triVerts, 1));
38537
39056
  if (manifoldMesh.runIndex && manifoldMesh.runIndex.length > 1 && manifoldMesh.runOriginalID) {
38538
39057
  for (let i = 0; i < manifoldMesh.runIndex.length - 1; i++) {
38539
39058
  const start = manifoldMesh.runIndex[i];
@@ -38567,7 +39086,7 @@ var createEllipsePoints = (width10, height10, segments) => {
38567
39086
  }
38568
39087
  return points;
38569
39088
  };
38570
- var COPPER_COLOR = new THREE40.Color(...colors.copper);
39089
+ var COPPER_COLOR = new THREE42.Color(...colors.copper);
38571
39090
  var PLATED_HOLE_LIP_HEIGHT = 0.05;
38572
39091
  var PLATED_HOLE_PAD_THICKNESS = 3e-3;
38573
39092
  var PLATED_HOLE_SURFACE_CLEARANCE = 5e-4;
@@ -39198,7 +39717,7 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
39198
39717
 
39199
39718
  // src/utils/manifold/process-vias.ts
39200
39719
  import { su as su15 } from "@tscircuit/circuit-json-util";
39201
- import * as THREE41 from "three";
39720
+ import * as THREE43 from "three";
39202
39721
 
39203
39722
  // src/utils/via-geoms.ts
39204
39723
  function createViaCopper2({
@@ -39236,7 +39755,7 @@ function createViaCopper2({
39236
39755
  }
39237
39756
 
39238
39757
  // src/utils/manifold/process-vias.ts
39239
- var COPPER_COLOR2 = new THREE41.Color(...colors.copper);
39758
+ var COPPER_COLOR2 = new THREE43.Color(...colors.copper);
39240
39759
  function processViasForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup, boardClipVolume) {
39241
39760
  const viaBoardDrills = [];
39242
39761
  const pcbVias = su15(circuitJson).pcb_via.list();
@@ -39294,7 +39813,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson, visibility) => {
39294
39813
  const [error, setError] = useState16(null);
39295
39814
  const [isLoading, setIsLoading] = useState16(true);
39296
39815
  const manifoldInstancesForCleanup = useRef9([]);
39297
- const boardData = useMemo22(() => {
39816
+ const boardData = useMemo23(() => {
39298
39817
  const panels = circuitJson.filter(
39299
39818
  (e) => e.type === "pcb_panel"
39300
39819
  );
@@ -39318,15 +39837,15 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson, visibility) => {
39318
39837
  const boardsNotInPanel = boards.filter((b) => !b.pcb_panel_id);
39319
39838
  return boardsNotInPanel.length > 0 ? boardsNotInPanel[0] : null;
39320
39839
  }, [circuitJson]);
39321
- const isFauxBoard = useMemo22(() => {
39840
+ const isFauxBoard = useMemo23(() => {
39322
39841
  const boards = su16(circuitJson).pcb_board.list();
39323
39842
  return boards.length > 0 && boards[0].pcb_board_id === "faux-board";
39324
39843
  }, [circuitJson]);
39325
- const traceTextureResolution = useMemo22(() => {
39844
+ const traceTextureResolution = useMemo23(() => {
39326
39845
  if (!boardData) return TRACE_TEXTURE_RESOLUTION;
39327
39846
  return getLayerTextureResolution(boardData, TRACE_TEXTURE_RESOLUTION);
39328
39847
  }, [boardData]);
39329
- useEffect26(() => {
39848
+ useEffect27(() => {
39330
39849
  if (!manifoldJSModule || !boardData) {
39331
39850
  setGeoms(null);
39332
39851
  setPcbThickness(null);
@@ -39456,7 +39975,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson, visibility) => {
39456
39975
  {
39457
39976
  key: "plated-holes-union",
39458
39977
  geometry: cutPlatedGeom,
39459
- color: new THREE42.Color(
39978
+ color: new THREE44.Color(
39460
39979
  colors.copper[0],
39461
39980
  colors.copper[1],
39462
39981
  colors.copper[2]
@@ -39486,7 +40005,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson, visibility) => {
39486
40005
  const matColorArray = boardMaterialColors[boardData.material] ?? colors.fr4Tan;
39487
40006
  currentGeoms.board = {
39488
40007
  geometry: finalBoardGeom,
39489
- color: new THREE42.Color(
40008
+ color: new THREE44.Color(
39490
40009
  matColorArray[0],
39491
40010
  matColorArray[1],
39492
40011
  matColorArray[2]
@@ -39510,7 +40029,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson, visibility) => {
39510
40029
  manifoldInstancesForCleanup.current = [];
39511
40030
  };
39512
40031
  }, [manifoldJSModule, circuitJson, boardData]);
39513
- const textures = useMemo22(() => {
40032
+ const textures = useMemo23(() => {
39514
40033
  if (!boardData || !traceTextureResolution) return null;
39515
40034
  return createCombinedBoardTextures({
39516
40035
  circuitJson,
@@ -39531,11 +40050,11 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson, visibility) => {
39531
40050
  };
39532
40051
 
39533
40052
  // src/utils/manifold/create-three-geometry-meshes.ts
39534
- import * as THREE44 from "three";
40053
+ import * as THREE46 from "three";
39535
40054
 
39536
40055
  // src/utils/create-board-material.ts
39537
- import * as THREE43 from "three";
39538
- var DEFAULT_SIDE = THREE43.DoubleSide;
40056
+ import * as THREE45 from "three";
40057
+ var DEFAULT_SIDE = THREE45.DoubleSide;
39539
40058
  var createBoardMaterial = ({
39540
40059
  material,
39541
40060
  color,
@@ -39543,7 +40062,7 @@ var createBoardMaterial = ({
39543
40062
  isFaux = false
39544
40063
  }) => {
39545
40064
  if (material === "fr4") {
39546
- return new THREE43.MeshPhysicalMaterial({
40065
+ return new THREE45.MeshPhysicalMaterial({
39547
40066
  // A dark edge lets the green solder mask read as a finished PCB rather
39548
40067
  // than a tan substrate with a decal placed on top.
39549
40068
  color: 1063462,
@@ -39563,7 +40082,7 @@ var createBoardMaterial = ({
39563
40082
  polygonOffsetUnits: 1
39564
40083
  });
39565
40084
  }
39566
- return new THREE43.MeshStandardMaterial({
40085
+ return new THREE45.MeshStandardMaterial({
39567
40086
  color,
39568
40087
  side,
39569
40088
  flatShading: true,
@@ -39582,12 +40101,12 @@ function createGeometryMeshes(geoms) {
39582
40101
  const meshes = [];
39583
40102
  if (!geoms) return meshes;
39584
40103
  if (geoms.board?.geometry) {
39585
- const mesh = new THREE44.Mesh(
40104
+ const mesh = new THREE46.Mesh(
39586
40105
  geoms.board.geometry,
39587
40106
  createBoardMaterial({
39588
40107
  material: geoms.board.material,
39589
40108
  color: geoms.board.color,
39590
- side: THREE44.DoubleSide,
40109
+ side: THREE46.DoubleSide,
39591
40110
  isFaux: geoms.board.isFaux
39592
40111
  })
39593
40112
  );
@@ -39598,11 +40117,11 @@ function createGeometryMeshes(geoms) {
39598
40117
  const createMeshesFromArray = (geomArray) => {
39599
40118
  if (geomArray) {
39600
40119
  for (const comp of geomArray) {
39601
- const mesh = new THREE44.Mesh(
40120
+ const mesh = new THREE46.Mesh(
39602
40121
  comp.geometry,
39603
- new THREE44.MeshStandardMaterial({
40122
+ new THREE46.MeshStandardMaterial({
39604
40123
  color: comp.color,
39605
- side: THREE44.DoubleSide,
40124
+ side: THREE46.DoubleSide,
39606
40125
  flatShading: true
39607
40126
  // Consistent with board
39608
40127
  })
@@ -39647,14 +40166,14 @@ var BoardMeshes = ({
39647
40166
  const typedMaterial = material;
39648
40167
  for (const prop of textureProps) {
39649
40168
  const texture = typedMaterial[prop];
39650
- if (texture && texture instanceof THREE45.Texture) {
40169
+ if (texture && texture instanceof THREE47.Texture) {
39651
40170
  texture.dispose();
39652
40171
  }
39653
40172
  }
39654
40173
  material.dispose();
39655
40174
  }
39656
40175
  };
39657
- useEffect27(() => {
40176
+ useEffect28(() => {
39658
40177
  if (!rootObject) return;
39659
40178
  for (const mesh of geometryMeshes) {
39660
40179
  let shouldShow = true;
@@ -39676,7 +40195,7 @@ var BoardMeshes = ({
39676
40195
  }
39677
40196
  };
39678
40197
  }, [rootObject, geometryMeshes, visibility]);
39679
- useEffect27(() => {
40198
+ useEffect28(() => {
39680
40199
  if (!rootObject) return;
39681
40200
  for (const mesh of textureMeshes) {
39682
40201
  rootObject.add(mesh);
@@ -39700,17 +40219,18 @@ var CadViewerManifold = ({
39700
40219
  onUserInteraction,
39701
40220
  children,
39702
40221
  onCameraControllerReady,
39703
- resolveStaticAsset
40222
+ resolveStaticAsset,
40223
+ referenceObject
39704
40224
  }) => {
39705
40225
  const childrenCircuitJson = useConvertChildrenToCircuitJson(children);
39706
- const circuitJson = useMemo23(() => {
40226
+ const circuitJson = useMemo24(() => {
39707
40227
  const rawCircuitJson = circuitJsonProp ?? childrenCircuitJson;
39708
40228
  return addFauxBoardIfNeeded(rawCircuitJson);
39709
40229
  }, [circuitJsonProp, childrenCircuitJson]);
39710
40230
  const [manifoldJSModule, setManifoldJSModule] = useState17(null);
39711
40231
  const [manifoldLoadingError, setManifoldLoadingError] = useState17(null);
39712
40232
  const { visibility } = useLayerVisibility();
39713
- useEffect27(() => {
40233
+ useEffect28(() => {
39714
40234
  if (window.ManifoldModule && typeof window.ManifoldModule === "object" && window.ManifoldModule.setup) {
39715
40235
  setManifoldJSModule(window.ManifoldModule);
39716
40236
  return;
@@ -39781,27 +40301,26 @@ try {
39781
40301
  boardData,
39782
40302
  isFauxBoard
39783
40303
  } = useManifoldBoardBuilder(manifoldJSModule, circuitJson, visibility);
39784
- const geometryMeshes = useMemo23(() => createGeometryMeshes(geoms), [geoms]);
39785
- const textureMeshes = useMemo23(
40304
+ const geometryMeshes = useMemo24(() => createGeometryMeshes(geoms), [geoms]);
40305
+ const textureMeshes = useMemo24(
39786
40306
  () => createTextureMeshes(textures, boardData, pcbThickness, isFauxBoard),
39787
40307
  [textures, boardData, pcbThickness, isFauxBoard]
39788
40308
  );
39789
- const cadComponents = useMemo23(
40309
+ const cadComponents = useMemo24(
39790
40310
  () => su17(circuitJson).cad_component.list(),
39791
40311
  [circuitJson]
39792
40312
  );
39793
- const boardDimensions = useMemo23(() => {
40313
+ const boardDimensions = useMemo24(() => {
39794
40314
  if (!boardData) return void 0;
39795
- const { width: width10 = 0, height: height10 = 0 } = boardData;
39796
- return { width: width10, height: height10 };
40315
+ const bounds = calculateOutlineBounds(boardData);
40316
+ return { width: bounds.width, height: bounds.height };
39797
40317
  }, [boardData]);
39798
- const boardCenter = useMemo23(() => {
40318
+ const boardCenter = useMemo24(() => {
39799
40319
  if (!boardData) return void 0;
39800
- const { center } = boardData;
39801
- if (!center) return void 0;
39802
- return { x: center.x, y: center.y };
40320
+ const bounds = calculateOutlineBounds(boardData);
40321
+ return { x: bounds.centerX, y: bounds.centerY };
39803
40322
  }, [boardData]);
39804
- const initialCameraPosition = useMemo23(() => {
40323
+ const initialCameraPosition = useMemo24(() => {
39805
40324
  if (!boardData) return [5, -5, 5];
39806
40325
  const { width: width10 = 0, height: height10 = 0 } = boardData;
39807
40326
  const safeWidth = Math.max(width10, 1);
@@ -39864,6 +40383,7 @@ try {
39864
40383
  clickToInteractEnabled,
39865
40384
  boardDimensions,
39866
40385
  boardCenter,
40386
+ referenceObject,
39867
40387
  onUserInteraction,
39868
40388
  onCameraControllerReady,
39869
40389
  children: [
@@ -39895,9 +40415,6 @@ try {
39895
40415
  };
39896
40416
  var CadViewerManifold_default = CadViewerManifold;
39897
40417
 
39898
- // src/components/ContextMenu.tsx
39899
- import { useState as useState35 } from "react";
39900
-
39901
40418
  // node_modules/@radix-ui/react-dropdown-menu/dist/index.mjs
39902
40419
  import * as React42 from "react";
39903
40420
 
@@ -46072,6 +46589,9 @@ var Sub2 = DropdownMenuSub;
46072
46589
  var SubTrigger2 = DropdownMenuSubTrigger;
46073
46590
  var SubContent2 = DropdownMenuSubContent;
46074
46591
 
46592
+ // src/components/ContextMenu.tsx
46593
+ import { useState as useState35 } from "react";
46594
+
46075
46595
  // src/components/AppearanceMenu.tsx
46076
46596
  import { useState as useState34 } from "react";
46077
46597
 
@@ -46588,15 +47108,19 @@ var ContextMenu = ({
46588
47108
  engine,
46589
47109
  cameraPreset,
46590
47110
  autoRotate,
47111
+ referenceObject = null,
46591
47112
  onEngineSwitch,
46592
47113
  onCameraPresetSelect,
46593
47114
  onAutoRotateToggle,
47115
+ onReferenceObjectSelect = () => {
47116
+ },
46594
47117
  onDownloadGltf,
46595
47118
  onOpenKeyboardShortcuts
46596
47119
  }) => {
46597
47120
  const { cameraType, setCameraType } = useCameraController();
46598
47121
  const { gridEnabled, setGridEnabled } = useAppearance();
46599
47122
  const [cameraSubOpen, setCameraSubOpen] = useState35(false);
47123
+ const [referenceObjectSubOpen, setReferenceObjectSubOpen] = useState35(false);
46600
47124
  const [hoveredItem, setHoveredItem] = useState35(null);
46601
47125
  return /* @__PURE__ */ jsx34(
46602
47126
  "div",
@@ -46733,6 +47257,72 @@ var ContextMenu = ({
46733
47257
  ]
46734
47258
  }
46735
47259
  ),
47260
+ /* @__PURE__ */ jsxs8(Sub2, { onOpenChange: setReferenceObjectSubOpen, children: [
47261
+ /* @__PURE__ */ jsxs8(
47262
+ SubTrigger2,
47263
+ {
47264
+ style: {
47265
+ ...itemStyles2,
47266
+ ...itemPaddingStyles2,
47267
+ backgroundColor: referenceObjectSubOpen || hoveredItem === "reference-object" ? "#404040" : "transparent"
47268
+ },
47269
+ onMouseEnter: () => setHoveredItem("reference-object"),
47270
+ onMouseLeave: () => setHoveredItem(null),
47271
+ onTouchStart: () => setHoveredItem("reference-object"),
47272
+ children: [
47273
+ /* @__PURE__ */ jsx34(
47274
+ "span",
47275
+ {
47276
+ style: { flex: 1, display: "flex", alignItems: "center" },
47277
+ children: "Show Reference Object"
47278
+ }
47279
+ ),
47280
+ /* @__PURE__ */ jsx34(
47281
+ "div",
47282
+ {
47283
+ style: {
47284
+ display: "flex",
47285
+ alignItems: "flex-end",
47286
+ gap: 6,
47287
+ marginLeft: "auto"
47288
+ },
47289
+ children: /* @__PURE__ */ jsx34(ChevronRightIcon, { isOpen: referenceObjectSubOpen })
47290
+ }
47291
+ )
47292
+ ]
47293
+ }
47294
+ ),
47295
+ /* @__PURE__ */ jsx34(Portal22, { children: /* @__PURE__ */ jsx34(
47296
+ SubContent2,
47297
+ {
47298
+ style: { ...contentStyles2, marginLeft: -2 },
47299
+ collisionPadding: 10,
47300
+ avoidCollisions: true,
47301
+ children: REFERENCE_OBJECT_OPTIONS.map((option) => /* @__PURE__ */ jsxs8(
47302
+ Item22,
47303
+ {
47304
+ style: {
47305
+ ...itemStyles2,
47306
+ backgroundColor: hoveredItem === option.type ? "#404040" : "transparent"
47307
+ },
47308
+ onSelect: (event) => event.preventDefault(),
47309
+ onPointerDown: (event) => {
47310
+ event.preventDefault();
47311
+ onReferenceObjectSelect(option.type);
47312
+ },
47313
+ onMouseEnter: () => setHoveredItem(option.type),
47314
+ onMouseLeave: () => setHoveredItem(null),
47315
+ onTouchStart: () => setHoveredItem(option.type),
47316
+ children: [
47317
+ /* @__PURE__ */ jsx34("span", { style: iconContainerStyles2, children: referenceObject === option.type && /* @__PURE__ */ jsx34(DotIcon, {}) }),
47318
+ /* @__PURE__ */ jsx34("span", { style: { display: "flex", alignItems: "center" }, children: option.label })
47319
+ ]
47320
+ },
47321
+ option.type
47322
+ ))
47323
+ }
47324
+ ) })
47325
+ ] }),
46736
47326
  /* @__PURE__ */ jsxs8(
46737
47327
  Item22,
46738
47328
  {
@@ -46876,10 +47466,10 @@ var ContextMenu = ({
46876
47466
  };
46877
47467
 
46878
47468
  // src/components/KeyboardShortcutsDialog.tsx
46879
- import { useEffect as useEffect46, useMemo as useMemo31, useRef as useRef27, useState as useState37 } from "react";
47469
+ import { useEffect as useEffect47, useMemo as useMemo32, useRef as useRef27, useState as useState37 } from "react";
46880
47470
 
46881
47471
  // src/hooks/useRegisteredHotkey.ts
46882
- import { useEffect as useEffect45, useMemo as useMemo30, useRef as useRef26, useState as useState36 } from "react";
47472
+ import { useEffect as useEffect46, useMemo as useMemo31, useRef as useRef26, useState as useState36 } from "react";
46883
47473
  var hotkeyRegistry = /* @__PURE__ */ new Map();
46884
47474
  var subscribers = /* @__PURE__ */ new Set();
46885
47475
  var isListenerAttached = false;
@@ -47003,14 +47593,14 @@ var subscribeToRegistry = (subscriber) => {
47003
47593
  var useRegisteredHotkey = (id, handler, metadata) => {
47004
47594
  const handlerRef = useRef26(handler);
47005
47595
  handlerRef.current = handler;
47006
- const normalizedMetadata = useMemo30(
47596
+ const normalizedMetadata = useMemo31(
47007
47597
  () => ({
47008
47598
  shortcut: metadata.shortcut,
47009
47599
  description: metadata.description
47010
47600
  }),
47011
47601
  [metadata.shortcut, metadata.description]
47012
47602
  );
47013
- useEffect45(() => {
47603
+ useEffect46(() => {
47014
47604
  const registration = {
47015
47605
  id,
47016
47606
  ...normalizedMetadata,
@@ -47026,7 +47616,7 @@ var useHotkeyRegistry = () => {
47026
47616
  const [entries, setEntries] = useState36(
47027
47617
  () => Array.from(hotkeyRegistry.values())
47028
47618
  );
47029
- useEffect45(() => subscribeToRegistry(setEntries), []);
47619
+ useEffect46(() => subscribeToRegistry(setEntries), []);
47030
47620
  return entries;
47031
47621
  };
47032
47622
  var registerHotkeyViewer = (element) => {
@@ -47043,7 +47633,7 @@ var KeyboardShortcutsDialog = ({
47043
47633
  const [query, setQuery] = useState37("");
47044
47634
  const inputRef = useRef27(null);
47045
47635
  const hotkeys = useHotkeyRegistry();
47046
- useEffect46(() => {
47636
+ useEffect47(() => {
47047
47637
  if (!open) return void 0;
47048
47638
  const handleKeyDown = (event) => {
47049
47639
  if (event.key === "Escape") {
@@ -47054,14 +47644,14 @@ var KeyboardShortcutsDialog = ({
47054
47644
  window.addEventListener("keydown", handleKeyDown);
47055
47645
  return () => window.removeEventListener("keydown", handleKeyDown);
47056
47646
  }, [open, onClose]);
47057
- useEffect46(() => {
47647
+ useEffect47(() => {
47058
47648
  if (open) {
47059
47649
  setTimeout(() => {
47060
47650
  inputRef.current?.focus();
47061
47651
  }, 0);
47062
47652
  }
47063
47653
  }, [open]);
47064
- const filteredHotkeys = useMemo31(() => {
47654
+ const filteredHotkeys = useMemo32(() => {
47065
47655
  const normalizedQuery = query.trim().toLowerCase();
47066
47656
  if (!normalizedQuery) {
47067
47657
  return hotkeys;
@@ -47283,7 +47873,7 @@ function useCameraPreset({
47283
47873
  }
47284
47874
 
47285
47875
  // src/hooks/useContextMenu.ts
47286
- import { useState as useState38, useCallback as useCallback22, useRef as useRef28, useEffect as useEffect47 } from "react";
47876
+ import { useState as useState38, useCallback as useCallback22, useRef as useRef28, useEffect as useEffect48 } from "react";
47287
47877
  var useContextMenu = ({ containerRef }) => {
47288
47878
  const [menuVisible, setMenuVisible] = useState38(false);
47289
47879
  const [menuPos, setMenuPos] = useState38({
@@ -47398,7 +47988,7 @@ var useContextMenu = ({ containerRef }) => {
47398
47988
  }
47399
47989
  setMenuVisible(false);
47400
47990
  }, []);
47401
- useEffect47(() => {
47991
+ useEffect48(() => {
47402
47992
  if (menuVisible) {
47403
47993
  document.addEventListener("mousedown", handleClickAway);
47404
47994
  document.addEventListener("touchstart", handleClickAway);
@@ -47462,7 +48052,7 @@ var useGlobalDownloadGltf = () => {
47462
48052
 
47463
48053
  // src/CadViewer.tsx
47464
48054
  import { jsx as jsx37, jsxs as jsxs11 } from "react/jsx-runtime";
47465
- var DEFAULT_TARGET = new THREE46.Vector3(0, 0, 0);
48055
+ var DEFAULT_TARGET = new THREE48.Vector3(0, 0, 0);
47466
48056
  var INITIAL_CAMERA_POSITION = [5, -5, 5];
47467
48057
  var readStoredCameraType = () => {
47468
48058
  if (typeof window === "undefined") return void 0;
@@ -47485,6 +48075,7 @@ var CadViewerInner = (props) => {
47485
48075
  return stored === "true";
47486
48076
  });
47487
48077
  const [cameraPreset, setCameraPreset] = useState39("Custom");
48078
+ const [referenceObject, setReferenceObject] = useState39(null);
47488
48079
  const { cameraType } = useCameraController();
47489
48080
  const { visibility, setLayerVisibility } = useLayerVisibility();
47490
48081
  const { showToast } = useToast();
@@ -47595,24 +48186,24 @@ var CadViewerInner = (props) => {
47595
48186
  description: "Toggle translucent components"
47596
48187
  }
47597
48188
  );
47598
- useEffect48(() => {
48189
+ useEffect49(() => {
47599
48190
  if (containerRef.current) {
47600
48191
  registerHotkeyViewer(containerRef.current);
47601
48192
  }
47602
48193
  }, []);
47603
- useEffect48(() => {
48194
+ useEffect49(() => {
47604
48195
  window.localStorage.setItem("cadViewerEngine", engine);
47605
48196
  }, [engine]);
47606
- useEffect48(() => {
48197
+ useEffect49(() => {
47607
48198
  window.localStorage.setItem("cadViewerAutoRotate", String(autoRotate));
47608
48199
  }, [autoRotate]);
47609
- useEffect48(() => {
48200
+ useEffect49(() => {
47610
48201
  window.localStorage.setItem(
47611
48202
  "cadViewerAutoRotateUserToggled",
47612
48203
  String(autoRotateUserToggled)
47613
48204
  );
47614
48205
  }, [autoRotateUserToggled]);
47615
- useEffect48(() => {
48206
+ useEffect49(() => {
47616
48207
  window.localStorage.setItem("cadViewerCameraType", cameraType);
47617
48208
  }, [cameraType]);
47618
48209
  const viewerKey = props.circuitJson ? JSON.stringify(props.circuitJson) : void 0;
@@ -47639,7 +48230,8 @@ var CadViewerInner = (props) => {
47639
48230
  autoRotateDisabled: props.autoRotateDisabled || !autoRotate,
47640
48231
  cameraType,
47641
48232
  onUserInteraction: handleUserInteraction,
47642
- onCameraControllerReady: handleCameraControllerReady
48233
+ onCameraControllerReady: handleCameraControllerReady,
48234
+ referenceObject
47643
48235
  }
47644
48236
  ) : /* @__PURE__ */ jsx37(
47645
48237
  CadViewerManifold_default,
@@ -47648,7 +48240,8 @@ var CadViewerInner = (props) => {
47648
48240
  autoRotateDisabled: props.autoRotateDisabled || !autoRotate,
47649
48241
  cameraType,
47650
48242
  onUserInteraction: handleUserInteraction,
47651
- onCameraControllerReady: handleCameraControllerReady
48243
+ onCameraControllerReady: handleCameraControllerReady,
48244
+ referenceObject
47652
48245
  }
47653
48246
  ),
47654
48247
  /* @__PURE__ */ jsxs11(
@@ -47680,6 +48273,7 @@ var CadViewerInner = (props) => {
47680
48273
  engine,
47681
48274
  cameraPreset,
47682
48275
  autoRotate,
48276
+ referenceObject,
47683
48277
  onEngineSwitch: (newEngine) => {
47684
48278
  setEngine(newEngine);
47685
48279
  closeMenu();
@@ -47689,6 +48283,13 @@ var CadViewerInner = (props) => {
47689
48283
  toggleAutoRotate();
47690
48284
  closeMenu();
47691
48285
  },
48286
+ onReferenceObjectSelect: (nextReferenceObject) => {
48287
+ setReferenceObject(nextReferenceObject);
48288
+ setAutoRotate(false);
48289
+ setAutoRotateUserToggled(true);
48290
+ setCameraPreset("Custom");
48291
+ closeMenu();
48292
+ },
47692
48293
  onDownloadGltf: () => {
47693
48294
  downloadGltf();
47694
48295
  closeMenu();
@@ -47726,14 +48327,14 @@ var CadViewer = (props) => {
47726
48327
  // src/convert-circuit-json-to-3d-svg.ts
47727
48328
  var import_debug = __toESM(require_browser(), 1);
47728
48329
  import { su as su18 } from "@tscircuit/circuit-json-util";
47729
- import * as THREE50 from "three";
48330
+ import * as THREE52 from "three";
47730
48331
  import { SVGRenderer } from "three/examples/jsm/renderers/SVGRenderer.js";
47731
48332
 
47732
48333
  // src/utils/create-geometry-from-polygons.ts
47733
- import * as THREE47 from "three";
47734
- import { BufferGeometry as BufferGeometry4, Float32BufferAttribute as Float32BufferAttribute3 } from "three";
48334
+ import * as THREE49 from "three";
48335
+ import { BufferGeometry as BufferGeometry5, Float32BufferAttribute as Float32BufferAttribute4 } from "three";
47735
48336
  function createGeometryFromPolygons(polygons) {
47736
- const geometry = new BufferGeometry4();
48337
+ const geometry = new BufferGeometry5();
47737
48338
  const vertices = [];
47738
48339
  const normals = [];
47739
48340
  for (const polygon2 of polygons) {
@@ -47744,12 +48345,12 @@ function createGeometryFromPolygons(polygons) {
47744
48345
  ...polygon2.vertices[i + 1]
47745
48346
  // Third vertex
47746
48347
  );
47747
- const v1 = new THREE47.Vector3(...polygon2.vertices[0]);
47748
- const v2 = new THREE47.Vector3(...polygon2.vertices[i]);
47749
- const v3 = new THREE47.Vector3(...polygon2.vertices[i + 1]);
47750
- const normal = new THREE47.Vector3().crossVectors(
47751
- new THREE47.Vector3().subVectors(v2, v1),
47752
- new THREE47.Vector3().subVectors(v3, v1)
48348
+ const v1 = new THREE49.Vector3(...polygon2.vertices[0]);
48349
+ const v2 = new THREE49.Vector3(...polygon2.vertices[i]);
48350
+ const v3 = new THREE49.Vector3(...polygon2.vertices[i + 1]);
48351
+ const normal = new THREE49.Vector3().crossVectors(
48352
+ new THREE49.Vector3().subVectors(v2, v1),
48353
+ new THREE49.Vector3().subVectors(v3, v1)
47753
48354
  ).normalize();
47754
48355
  normals.push(
47755
48356
  normal.x,
@@ -47764,8 +48365,8 @@ function createGeometryFromPolygons(polygons) {
47764
48365
  );
47765
48366
  }
47766
48367
  }
47767
- geometry.setAttribute("position", new Float32BufferAttribute3(vertices, 3));
47768
- geometry.setAttribute("normal", new Float32BufferAttribute3(normals, 3));
48368
+ geometry.setAttribute("position", new Float32BufferAttribute4(vertices, 3));
48369
+ geometry.setAttribute("normal", new Float32BufferAttribute4(normals, 3));
47769
48370
  return geometry;
47770
48371
  }
47771
48372
 
@@ -47773,10 +48374,10 @@ function createGeometryFromPolygons(polygons) {
47773
48374
  var import_modeling2 = __toESM(require_src(), 1);
47774
48375
  var import_jscad_planner2 = __toESM(require_dist(), 1);
47775
48376
  var jscadModeling2 = __toESM(require_src(), 1);
47776
- import * as THREE49 from "three";
48377
+ import * as THREE51 from "three";
47777
48378
 
47778
48379
  // src/utils/load-model.ts
47779
- import * as THREE48 from "three";
48380
+ import * as THREE50 from "three";
47780
48381
  import { GLTFLoader as GLTFLoader2 } from "three/examples/jsm/loaders/GLTFLoader.js";
47781
48382
  import { OBJLoader as OBJLoader2 } from "three/examples/jsm/loaders/OBJLoader.js";
47782
48383
  import { STLLoader as STLLoader2 } from "three/examples/jsm/loaders/STLLoader.js";
@@ -47784,12 +48385,12 @@ async function load3DModel(url) {
47784
48385
  if (url.endsWith(".stl")) {
47785
48386
  const loader = new STLLoader2();
47786
48387
  const geometry = await loader.loadAsync(url);
47787
- const material = new THREE48.MeshStandardMaterial({
48388
+ const material = new THREE50.MeshStandardMaterial({
47788
48389
  color: 8947848,
47789
48390
  metalness: 0.5,
47790
48391
  roughness: 0.5
47791
48392
  });
47792
- return new THREE48.Mesh(geometry, material);
48393
+ return new THREE50.Mesh(geometry, material);
47793
48394
  }
47794
48395
  if (url.endsWith(".obj")) {
47795
48396
  const loader = new OBJLoader2();
@@ -47822,9 +48423,9 @@ async function renderComponent(component, scene) {
47822
48423
  }
47823
48424
  if (component.rotation) {
47824
48425
  model.rotation.set(
47825
- THREE49.MathUtils.degToRad(component.rotation.x ?? 0),
47826
- THREE49.MathUtils.degToRad(component.rotation.y ?? 0),
47827
- THREE49.MathUtils.degToRad(component.rotation.z ?? 0)
48426
+ THREE51.MathUtils.degToRad(component.rotation.x ?? 0),
48427
+ THREE51.MathUtils.degToRad(component.rotation.y ?? 0),
48428
+ THREE51.MathUtils.degToRad(component.rotation.z ?? 0)
47828
48429
  );
47829
48430
  }
47830
48431
  scene.add(model);
@@ -47838,13 +48439,13 @@ async function renderComponent(component, scene) {
47838
48439
  );
47839
48440
  if (jscadObject && (jscadObject.polygons || jscadObject.sides)) {
47840
48441
  const threeGeom = convertCSGToThreeGeom(jscadObject);
47841
- const material2 = new THREE49.MeshStandardMaterial({
48442
+ const material2 = new THREE51.MeshStandardMaterial({
47842
48443
  color: 8947848,
47843
48444
  metalness: 0.5,
47844
48445
  roughness: 0.5,
47845
- side: THREE49.DoubleSide
48446
+ side: THREE51.DoubleSide
47846
48447
  });
47847
- const mesh2 = new THREE49.Mesh(threeGeom, material2);
48448
+ const mesh2 = new THREE51.Mesh(threeGeom, material2);
47848
48449
  if (component.position) {
47849
48450
  mesh2.position.set(
47850
48451
  component.position.x ?? 0,
@@ -47854,9 +48455,9 @@ async function renderComponent(component, scene) {
47854
48455
  }
47855
48456
  if (component.rotation) {
47856
48457
  mesh2.rotation.set(
47857
- THREE49.MathUtils.degToRad(component.rotation.x ?? 0),
47858
- THREE49.MathUtils.degToRad(component.rotation.y ?? 0),
47859
- THREE49.MathUtils.degToRad(component.rotation.z ?? 0)
48458
+ THREE51.MathUtils.degToRad(component.rotation.x ?? 0),
48459
+ THREE51.MathUtils.degToRad(component.rotation.y ?? 0),
48460
+ THREE51.MathUtils.degToRad(component.rotation.z ?? 0)
47860
48461
  );
47861
48462
  }
47862
48463
  scene.add(mesh2);
@@ -47873,17 +48474,17 @@ async function renderComponent(component, scene) {
47873
48474
  if (!geom || !geom.polygons && !geom.sides) {
47874
48475
  continue;
47875
48476
  }
47876
- const color = new THREE49.Color(geomInfo.color);
48477
+ const color = new THREE51.Color(geomInfo.color);
47877
48478
  color.convertLinearToSRGB();
47878
48479
  const geomWithColor = { ...geom, color: [color.r, color.g, color.b] };
47879
48480
  const threeGeom = convertCSGToThreeGeom(geomWithColor);
47880
- const material2 = new THREE49.MeshStandardMaterial({
48481
+ const material2 = new THREE51.MeshStandardMaterial({
47881
48482
  vertexColors: true,
47882
48483
  metalness: 0.2,
47883
48484
  roughness: 0.8,
47884
- side: THREE49.DoubleSide
48485
+ side: THREE51.DoubleSide
47885
48486
  });
47886
- const mesh2 = new THREE49.Mesh(threeGeom, material2);
48487
+ const mesh2 = new THREE51.Mesh(threeGeom, material2);
47887
48488
  if (component.position) {
47888
48489
  mesh2.position.set(
47889
48490
  component.position.x ?? 0,
@@ -47893,22 +48494,22 @@ async function renderComponent(component, scene) {
47893
48494
  }
47894
48495
  if (component.rotation) {
47895
48496
  mesh2.rotation.set(
47896
- THREE49.MathUtils.degToRad(component.rotation.x ?? 0),
47897
- THREE49.MathUtils.degToRad(component.rotation.y ?? 0),
47898
- THREE49.MathUtils.degToRad(component.rotation.z ?? 0)
48497
+ THREE51.MathUtils.degToRad(component.rotation.x ?? 0),
48498
+ THREE51.MathUtils.degToRad(component.rotation.y ?? 0),
48499
+ THREE51.MathUtils.degToRad(component.rotation.z ?? 0)
47899
48500
  );
47900
48501
  }
47901
48502
  scene.add(mesh2);
47902
48503
  }
47903
48504
  return;
47904
48505
  }
47905
- const geometry = new THREE49.BoxGeometry(0.5, 0.5, 0.5);
47906
- const material = new THREE49.MeshStandardMaterial({
48506
+ const geometry = new THREE51.BoxGeometry(0.5, 0.5, 0.5);
48507
+ const material = new THREE51.MeshStandardMaterial({
47907
48508
  color: 16711680,
47908
48509
  transparent: true,
47909
48510
  opacity: 0.25
47910
48511
  });
47911
- const mesh = new THREE49.Mesh(geometry, material);
48512
+ const mesh = new THREE51.Mesh(geometry, material);
47912
48513
  if (component.position) {
47913
48514
  mesh.position.set(
47914
48515
  component.position.x ?? 0,
@@ -47929,11 +48530,11 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
47929
48530
  padding = 20,
47930
48531
  zoom = 1.5
47931
48532
  } = options;
47932
- const scene = new THREE50.Scene();
48533
+ const scene = new THREE52.Scene();
47933
48534
  const renderer = new SVGRenderer();
47934
48535
  renderer.setSize(width10, height10);
47935
- renderer.setClearColor(new THREE50.Color(backgroundColor), 1);
47936
- const camera = new THREE50.OrthographicCamera();
48536
+ renderer.setClearColor(new THREE52.Color(backgroundColor), 1);
48537
+ const camera = new THREE52.OrthographicCamera();
47937
48538
  const aspect = width10 / height10;
47938
48539
  const frustumSize = 100;
47939
48540
  const halfFrustumSize = frustumSize / 2 / zoom;
@@ -47947,11 +48548,11 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
47947
48548
  camera.position.set(position.x, position.y, position.z);
47948
48549
  camera.up.set(0, 1, 0);
47949
48550
  const lookAt = options.camera?.lookAt ?? { x: 0, y: 0, z: 0 };
47950
- camera.lookAt(new THREE50.Vector3(lookAt.x, lookAt.y, lookAt.z));
48551
+ camera.lookAt(new THREE52.Vector3(lookAt.x, lookAt.y, lookAt.z));
47951
48552
  camera.updateProjectionMatrix();
47952
- const ambientLight = new THREE50.AmbientLight(16777215, Math.PI / 2);
48553
+ const ambientLight = new THREE52.AmbientLight(16777215, Math.PI / 2);
47953
48554
  scene.add(ambientLight);
47954
- const pointLight = new THREE50.PointLight(16777215, Math.PI / 4);
48555
+ const pointLight = new THREE52.PointLight(16777215, Math.PI / 4);
47955
48556
  pointLight.position.set(-10, -10, 10);
47956
48557
  scene.add(pointLight);
47957
48558
  const components = su18(circuitJson).cad_component.list();
@@ -47962,7 +48563,7 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
47962
48563
  const boardGeom = createSimplifiedBoardGeom(circuitJson);
47963
48564
  if (boardGeom) {
47964
48565
  const solderMaskColor = colors.fr4SolderMaskGreen;
47965
- const baseColor = new THREE50.Color(
48566
+ const baseColor = new THREE52.Color(
47966
48567
  solderMaskColor[0],
47967
48568
  solderMaskColor[1],
47968
48569
  solderMaskColor[2]
@@ -47974,28 +48575,28 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
47974
48575
  const material = createBoardMaterial({
47975
48576
  material: boardData?.material,
47976
48577
  color: baseColor,
47977
- side: THREE50.DoubleSide
48578
+ side: THREE52.DoubleSide
47978
48579
  });
47979
- const mesh = new THREE50.Mesh(geometry, material);
48580
+ const mesh = new THREE52.Mesh(geometry, material);
47980
48581
  scene.add(mesh);
47981
48582
  }
47982
48583
  }
47983
- const gridColor = new THREE50.Color(8947848);
47984
- const gridHelper = new THREE50.GridHelper(100, 100, gridColor, gridColor);
48584
+ const gridColor = new THREE52.Color(8947848);
48585
+ const gridHelper = new THREE52.GridHelper(100, 100, gridColor, gridColor);
47985
48586
  gridHelper.rotation.x = Math.PI / 2;
47986
48587
  const materials = Array.isArray(gridHelper.material) ? gridHelper.material : [gridHelper.material];
47987
48588
  for (const mat of materials) {
47988
48589
  mat.transparent = true;
47989
48590
  mat.opacity = 0.3;
47990
- if (mat instanceof THREE50.LineBasicMaterial) {
48591
+ if (mat instanceof THREE52.LineBasicMaterial) {
47991
48592
  mat.color = gridColor;
47992
48593
  mat.vertexColors = false;
47993
48594
  }
47994
48595
  }
47995
48596
  scene.add(gridHelper);
47996
- const box = new THREE50.Box3().setFromObject(scene);
47997
- const center = box.getCenter(new THREE50.Vector3());
47998
- const size4 = box.getSize(new THREE50.Vector3());
48597
+ const box = new THREE52.Box3().setFromObject(scene);
48598
+ const center = box.getCenter(new THREE52.Vector3());
48599
+ const size4 = box.getSize(new THREE52.Vector3());
47999
48600
  scene.position.sub(center);
48000
48601
  const maxDim = Math.max(size4.x, size4.y, size4.z);
48001
48602
  if (maxDim > 0) {
@@ -48013,10 +48614,10 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
48013
48614
 
48014
48615
  // src/hooks/exporter/gltf.ts
48015
48616
  import { GLTFExporter as GLTFExporter3 } from "three-stdlib";
48016
- import { useEffect as useEffect49, useState as useState40, useMemo as useMemo32, useCallback as useCallback25 } from "react";
48617
+ import { useEffect as useEffect50, useState as useState40, useMemo as useMemo33, useCallback as useCallback25 } from "react";
48017
48618
  function useSaveGltfAs(options = {}) {
48018
48619
  const parse2 = useParser(options);
48019
- const link = useMemo32(() => document.createElement("a"), []);
48620
+ const link = useMemo33(() => document.createElement("a"), []);
48020
48621
  const saveAs = async (filename) => {
48021
48622
  const name = filename ?? options.filename ?? "";
48022
48623
  if (options.binary == null) options.binary = name.endsWith(".glb");
@@ -48026,7 +48627,7 @@ function useSaveGltfAs(options = {}) {
48026
48627
  link.dispatchEvent(new MouseEvent("click"));
48027
48628
  URL.revokeObjectURL(url);
48028
48629
  };
48029
- useEffect49(
48630
+ useEffect50(
48030
48631
  () => () => {
48031
48632
  link.remove();
48032
48633
  instance = null;
@@ -48047,11 +48648,11 @@ function useExportGltfUrl(options = {}) {
48047
48648
  (instance) => parse2(instance).then(setUrl).catch(setError),
48048
48649
  []
48049
48650
  );
48050
- useEffect49(() => () => URL.revokeObjectURL(url), [url]);
48651
+ useEffect50(() => () => URL.revokeObjectURL(url), [url]);
48051
48652
  return [ref, url, error];
48052
48653
  }
48053
48654
  function useParser(options = {}) {
48054
- const exporter = useMemo32(() => new GLTFExporter3(), []);
48655
+ const exporter = useMemo33(() => new GLTFExporter3(), []);
48055
48656
  return (instance) => {
48056
48657
  const { promise, resolve, reject } = Promise.withResolvers();
48057
48658
  exporter.parse(