@tscircuit/3d-viewer 0.0.583 → 0.0.585

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