@liveroom-tech/react-immersive 2.2.0 → 3.0.0

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.
package/dist/index.mjs CHANGED
@@ -174,7 +174,7 @@ import {
174
174
  Raycaster,
175
175
  Sphere as Sphere3,
176
176
  Vector2 as Vector26,
177
- Vector3 as Vector38
177
+ Vector3 as Vector39
178
178
  } from "three";
179
179
 
180
180
  // #style-inject:#style-inject
@@ -2053,6 +2053,28 @@ function Controls({ controlsRef, onCameraChange }) {
2053
2053
  // src/components/Lights.tsx
2054
2054
  import { useRef as useRef4, useMemo as useMemo5, useEffect as useEffect6 } from "react";
2055
2055
  import { useFrame as useFrame3, useThree as useThree5 } from "@react-three/fiber";
2056
+ import {
2057
+ Vector3 as Vector32
2058
+ } from "three";
2059
+
2060
+ // src/utils/resolveBindingKey.ts
2061
+ function resolveBindingKey(objectBindings, objectId) {
2062
+ if (objectBindings[objectId]) {
2063
+ return objectId;
2064
+ }
2065
+ const idMatch = Object.entries(objectBindings).find(
2066
+ ([, binding]) => binding.id === objectId
2067
+ )?.[0];
2068
+ if (idMatch) {
2069
+ return idMatch;
2070
+ }
2071
+ const modelObjectMatch = Object.entries(objectBindings).find(
2072
+ ([, binding]) => binding.modelObjectId === objectId
2073
+ )?.[0];
2074
+ return modelObjectMatch ?? null;
2075
+ }
2076
+
2077
+ // src/components/Lights.tsx
2056
2078
  import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
2057
2079
  function rotatePositionY(pos, deg) {
2058
2080
  if (!deg) return pos;
@@ -2132,21 +2154,42 @@ function LightInstance({
2132
2154
  shadows,
2133
2155
  shadowsEnabled,
2134
2156
  environmentRotation,
2135
- modelBounds
2157
+ modelBounds,
2158
+ objectBindings,
2159
+ nodeRefs
2136
2160
  }) {
2137
2161
  const dirRef = useRef4(null);
2138
2162
  const pointRef = useRef4(null);
2139
2163
  const spotRef = useRef4(null);
2140
2164
  const camera = useThree5((s) => s.camera);
2165
+ const rootScene = useThree5((s) => s.scene);
2166
+ const attachedPosition = useMemo5(() => new Vector32(), []);
2141
2167
  const position = useMemo5(
2142
- () => light.attachedToCamera ? light.position : rotatePositionY(light.position, environmentRotation),
2143
- [light.position, light.attachedToCamera, environmentRotation]
2168
+ () => light.attachedToCamera || light.attachment ? light.position : rotatePositionY(light.position, environmentRotation),
2169
+ [
2170
+ light.position,
2171
+ light.attachedToCamera,
2172
+ light.attachment,
2173
+ environmentRotation
2174
+ ]
2144
2175
  );
2145
2176
  useFrame3(() => {
2146
- if (!light.attachedToCamera) return;
2147
- if (dirRef.current) dirRef.current.position.copy(camera.position);
2148
- if (pointRef.current) pointRef.current.position.copy(camera.position);
2149
- if (spotRef.current) spotRef.current.position.copy(camera.position);
2177
+ const instance = dirRef.current ?? pointRef.current ?? spotRef.current;
2178
+ if (!instance) return;
2179
+ if (light.attachedToCamera) {
2180
+ instance.position.copy(camera.position);
2181
+ return;
2182
+ }
2183
+ const attachment = light.attachment;
2184
+ if (!attachment) return;
2185
+ const bindingKey = objectBindings ? resolveBindingKey(objectBindings, attachment.objectId) : null;
2186
+ const binding = bindingKey ? objectBindings?.[bindingKey] : void 0;
2187
+ const modelObjectId = binding?.modelObjectId ?? attachment.objectId;
2188
+ const target = (bindingKey ? nodeRefs?.current[bindingKey] : void 0) ?? rootScene.getObjectByName(modelObjectId);
2189
+ if (!target) return;
2190
+ attachedPosition.fromArray(attachment.offset);
2191
+ target.localToWorld(attachedPosition);
2192
+ instance.position.copy(attachedPosition);
2150
2193
  });
2151
2194
  const castShadow = shadowsEnabled && shadows.enabled && light.castShadow;
2152
2195
  const fit = useMemo5(
@@ -2192,6 +2235,8 @@ function LightInstance({
2192
2235
  color: light.color,
2193
2236
  intensity: light.intensity,
2194
2237
  position,
2238
+ distance: light.distance,
2239
+ decay: light.decay,
2195
2240
  castShadow,
2196
2241
  "shadow-mapSize-width": shadows.mapSize,
2197
2242
  "shadow-mapSize-height": shadows.mapSize,
@@ -2217,7 +2262,9 @@ function Lights({
2217
2262
  shadowsEnabled = true,
2218
2263
  environmentRotation = 0,
2219
2264
  environmentConfig,
2220
- modelBounds
2265
+ modelBounds,
2266
+ objectBindings,
2267
+ nodeRefs
2221
2268
  }) {
2222
2269
  const lighting = lightingConfig ?? DEFAULT_LIGHTING;
2223
2270
  const shadows = shadowConfig ?? DEFAULT_SHADOWS;
@@ -2238,7 +2285,9 @@ function Lights({
2238
2285
  shadows,
2239
2286
  shadowsEnabled,
2240
2287
  environmentRotation,
2241
- modelBounds
2288
+ modelBounds,
2289
+ objectBindings,
2290
+ nodeRefs
2242
2291
  },
2243
2292
  light.id
2244
2293
  )),
@@ -2259,7 +2308,9 @@ function Lights({
2259
2308
  shadows,
2260
2309
  shadowsEnabled,
2261
2310
  environmentRotation: 0,
2262
- modelBounds
2311
+ modelBounds,
2312
+ objectBindings,
2313
+ nodeRefs
2263
2314
  }
2264
2315
  )
2265
2316
  ] });
@@ -2368,7 +2419,7 @@ import {
2368
2419
  LoopOnce,
2369
2420
  LoopRepeat,
2370
2421
  Box3 as Box32,
2371
- Vector3 as Vector32,
2422
+ Vector3 as Vector33,
2372
2423
  Matrix4,
2373
2424
  MathUtils as MathUtils2
2374
2425
  } from "three";
@@ -2984,6 +3035,24 @@ function refreshSkinnedBounds(root) {
2984
3035
 
2985
3036
  // src/components/Model.tsx
2986
3037
  import { jsx as jsx8 } from "react/jsx-runtime";
3038
+ function applyBindingTransform(node, transform, original) {
3039
+ if (!original) return;
3040
+ if (transform?.position) node.position.fromArray(transform.position);
3041
+ else node.position.copy(original.position);
3042
+ if (transform?.rotation) {
3043
+ node.rotation.set(
3044
+ transform.rotation[0],
3045
+ transform.rotation[1],
3046
+ transform.rotation[2],
3047
+ original.rotation.order
3048
+ );
3049
+ } else {
3050
+ node.rotation.copy(original.rotation);
3051
+ }
3052
+ if (transform?.scale) node.scale.fromArray(transform.scale);
3053
+ else node.scale.copy(original.scale);
3054
+ node.updateMatrix();
3055
+ }
2987
3056
  function firstMaterial(node) {
2988
3057
  const mat = node.material;
2989
3058
  if (!mat) return void 0;
@@ -3037,6 +3106,17 @@ function Model({
3037
3106
  });
3038
3107
  return map;
3039
3108
  }, [scene]);
3109
+ const originalNodeTransforms = useMemo7(() => {
3110
+ const transforms = /* @__PURE__ */ new WeakMap();
3111
+ scene.traverse((node) => {
3112
+ transforms.set(node, {
3113
+ position: node.position.clone(),
3114
+ rotation: node.rotation.clone(),
3115
+ scale: node.scale.clone()
3116
+ });
3117
+ });
3118
+ return transforms;
3119
+ }, [scene]);
3040
3120
  const shellNodeNames = useMemo7(() => {
3041
3121
  const shell = /* @__PURE__ */ new Set();
3042
3122
  const sceneBox = new Box32();
@@ -3048,8 +3128,8 @@ function Model({
3048
3128
  meshNodes.push({ name, box });
3049
3129
  sceneBox.union(box);
3050
3130
  }
3051
- const sceneSize = sceneBox.getSize(new Vector32());
3052
- const size = new Vector32();
3131
+ const sceneSize = sceneBox.getSize(new Vector33());
3132
+ const size = new Vector33();
3053
3133
  for (const { name, box } of meshNodes) {
3054
3134
  box.getSize(size);
3055
3135
  const isShell = size.x > sceneSize.x * 0.7 && size.z > sceneSize.z * 0.7 && size.y > sceneSize.y * 0.4;
@@ -3089,6 +3169,43 @@ function Model({
3089
3169
  ).sort().join(""),
3090
3170
  [objectBindings]
3091
3171
  );
3172
+ const bindingTransformSignature = useMemo7(
3173
+ () => Object.entries(objectBindings).map(
3174
+ ([nodeName, binding]) => JSON.stringify([
3175
+ nodeName,
3176
+ binding.modelObjectId ?? nodeName,
3177
+ binding.transform ?? null
3178
+ ])
3179
+ ).sort().join(""),
3180
+ [objectBindings]
3181
+ );
3182
+ const transformedNodesRef = useRef5(/* @__PURE__ */ new Set());
3183
+ useEffect7(() => {
3184
+ for (const node of transformedNodesRef.current) {
3185
+ applyBindingTransform(node, void 0, originalNodeTransforms.get(node));
3186
+ }
3187
+ const nextTransformedNodes = /* @__PURE__ */ new Set();
3188
+ for (const [nodeName, binding] of Object.entries(objectBindings)) {
3189
+ if (!binding.transform) continue;
3190
+ const node = nodeMap[binding.modelObjectId ?? nodeName];
3191
+ if (!node) continue;
3192
+ applyBindingTransform(
3193
+ node,
3194
+ binding.transform,
3195
+ originalNodeTransforms.get(node)
3196
+ );
3197
+ nextTransformedNodes.add(node);
3198
+ }
3199
+ transformedNodesRef.current = nextTransformedNodes;
3200
+ scene.updateMatrixWorld(true);
3201
+ invalidate();
3202
+ }, [
3203
+ scene,
3204
+ nodeMap,
3205
+ originalNodeTransforms,
3206
+ bindingTransformSignature,
3207
+ invalidate
3208
+ ]);
3092
3209
  const perNodeMaterials = useMemo7(() => {
3093
3210
  const map = {};
3094
3211
  for (const [nodeName, binding] of Object.entries(objectBindings)) {
@@ -3592,27 +3709,30 @@ function Model({
3592
3709
  const explodePartsRef = useRef5([]);
3593
3710
  const explodeStrengthRef = useRef5(0);
3594
3711
  const currentExplodeRef = useRef5(0);
3595
- const explodeTmp = useMemo7(() => new Vector32(), []);
3712
+ const explodeTmp = useMemo7(() => new Vector33(), []);
3596
3713
  useEffect7(() => {
3714
+ const bindingByNode = /* @__PURE__ */ new Map();
3715
+ for (const [nodeName, binding] of Object.entries(objectBindings)) {
3716
+ const node = nodeMap[binding.modelObjectId ?? nodeName];
3717
+ if (node) bindingByNode.set(node, binding);
3718
+ }
3597
3719
  for (const part of explodePartsRef.current) {
3598
- part.node.position.copy(part.restLocalPos);
3720
+ applyBindingTransform(
3721
+ part.node,
3722
+ bindingByNode.get(part.node)?.transform,
3723
+ originalNodeTransforms.get(part.node)
3724
+ );
3599
3725
  }
3600
3726
  currentExplodeRef.current = 0;
3601
3727
  scene.updateWorldMatrix(true, true);
3602
3728
  const sceneBox = new Box32().setFromObject(scene);
3603
- const modelCenter = sceneBox.getCenter(new Vector32());
3604
- const radius = sceneBox.getSize(new Vector32()).length() * 0.5;
3729
+ const modelCenter = sceneBox.getCenter(new Vector33());
3730
+ const radius = sceneBox.getSize(new Vector33()).length() * 0.5;
3605
3731
  explodeStrengthRef.current = Number.isFinite(radius) ? radius : 0;
3606
- const targets = [];
3607
- const targetSet = /* @__PURE__ */ new Set();
3608
- for (const [nodeName, binding] of Object.entries(objectBindings)) {
3609
- const node = nodeMap[binding.modelObjectId ?? nodeName];
3610
- if (!node || targetSet.has(node)) continue;
3611
- targetSet.add(node);
3612
- targets.push(node);
3613
- }
3732
+ const targets = [...bindingByNode.keys()];
3733
+ const targetSet = new Set(targets);
3614
3734
  const parts = [];
3615
- const partCenter = new Vector32();
3735
+ const partCenter = new Vector33();
3616
3736
  for (const node of targets) {
3617
3737
  if (!node.parent) continue;
3618
3738
  let ancestor = node.parent;
@@ -3634,14 +3754,21 @@ function Model({
3634
3754
  parts.push({
3635
3755
  node,
3636
3756
  restLocalPos: node.position.clone(),
3637
- restWorldPos: node.getWorldPosition(new Vector32()),
3757
+ restWorldPos: node.getWorldPosition(new Vector33()),
3638
3758
  worldDir,
3639
3759
  parentInverse: new Matrix4().copy(node.parent.matrixWorld).invert()
3640
3760
  });
3641
3761
  }
3642
3762
  explodePartsRef.current = parts;
3643
3763
  invalidate();
3644
- }, [scene, nodeMap, nodeBindingSignature, invalidate]);
3764
+ }, [
3765
+ scene,
3766
+ nodeMap,
3767
+ nodeBindingSignature,
3768
+ bindingTransformSignature,
3769
+ originalNodeTransforms,
3770
+ invalidate
3771
+ ]);
3645
3772
  useEffect7(() => {
3646
3773
  invalidate();
3647
3774
  }, [explodeFactor, invalidate]);
@@ -3733,7 +3860,7 @@ import {
3733
3860
  } from "react";
3734
3861
  import { Html } from "@react-three/drei";
3735
3862
  import { useFrame as useFrame5, useThree as useThree8 } from "@react-three/fiber";
3736
- import { MathUtils as MathUtils3, Vector3 as Vector33 } from "three";
3863
+ import { MathUtils as MathUtils3, Vector3 as Vector34 } from "three";
3737
3864
 
3738
3865
  // src/styles/AnnotationsLayer.css
3739
3866
  styleInject(".ri-annotationMarker--hovered {\n transform: scale(1.08);\n box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);\n background: rgba(40, 40, 40, 0.92);\n}\n.ri-viewAnnotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n.ri-viewAnnotationTitle {\n margin: 0;\n color: #e2e8f0;\n font-size: 14px;\n font-weight: 600;\n}\n");
@@ -3746,7 +3873,7 @@ function useCameraBiasedAnchor(annotation, worldPositionsRef) {
3746
3873
  const scene = useThree8((state) => state.scene);
3747
3874
  const anchorNodeRef = useRef6(null);
3748
3875
  const baseLocalPosition = useMemo8(
3749
- () => new Vector33(
3876
+ () => new Vector34(
3750
3877
  ...annotation.anchorLocalPosition ?? annotation.localPosition ?? annotation.worldPosition
3751
3878
  ),
3752
3879
  [
@@ -3755,9 +3882,9 @@ function useCameraBiasedAnchor(annotation, worldPositionsRef) {
3755
3882
  annotation.worldPosition
3756
3883
  ]
3757
3884
  );
3758
- const worldPosition = useMemo8(() => new Vector33(), []);
3759
- const biasedWorldPosition = useMemo8(() => new Vector33(), []);
3760
- const toCamera = useMemo8(() => new Vector33(), []);
3885
+ const worldPosition = useMemo8(() => new Vector34(), []);
3886
+ const biasedWorldPosition = useMemo8(() => new Vector34(), []);
3887
+ const toCamera = useMemo8(() => new Vector34(), []);
3761
3888
  useEffect8(() => {
3762
3889
  anchorNodeRef.current = null;
3763
3890
  }, [annotation.anchorNodeName]);
@@ -3780,7 +3907,7 @@ function useCameraBiasedAnchor(annotation, worldPositionsRef) {
3780
3907
  if (worldPositionsRef) {
3781
3908
  let stored = worldPositionsRef.current.get(annotation.id);
3782
3909
  if (!stored) {
3783
- stored = new Vector33();
3910
+ stored = new Vector34();
3784
3911
  worldPositionsRef.current.set(annotation.id, stored);
3785
3912
  }
3786
3913
  stored.copy(worldPosition);
@@ -4178,7 +4305,7 @@ function AnnotationsLayer({
4178
4305
  // src/components/ModelViewer/MeasurementLayer.tsx
4179
4306
  import { useEffect as useEffect9, useMemo as useMemo9 } from "react";
4180
4307
  import { Html as Html2, Line } from "@react-three/drei";
4181
- import { BoxGeometry, EdgesGeometry, Vector3 as Vector34 } from "three";
4308
+ import { BoxGeometry, EdgesGeometry, Vector3 as Vector35 } from "three";
4182
4309
  import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
4183
4310
  var LINE_COLOR = "#22d3ee";
4184
4311
  var BOX_COLOR = "#facc15";
@@ -4253,7 +4380,7 @@ function MeasurementLayer({
4253
4380
  points.slice(1).map((point, index) => {
4254
4381
  const a = points[index].position;
4255
4382
  const b = point.position;
4256
- const distance = new Vector34(...a).distanceTo(new Vector34(...b));
4383
+ const distance = new Vector35(...a).distanceTo(new Vector35(...b));
4257
4384
  return /* @__PURE__ */ jsxs6("group", { children: [
4258
4385
  /* @__PURE__ */ jsx10(Line, { points: [a, b], color: LINE_COLOR, lineWidth: 2 }),
4259
4386
  /* @__PURE__ */ jsx10(MeasureLabel, { position: midpoint(a, b), children: formatMeasurement(distance, unit) })
@@ -4274,7 +4401,7 @@ import {
4274
4401
  } from "react";
4275
4402
  import { useXR as useXR2, useXRHitTest, useXRInputSourceEvent } from "@react-three/xr";
4276
4403
  import { useFrame as useFrame6 } from "@react-three/fiber";
4277
- import { Matrix4 as Matrix42, Vector3 as Vector35 } from "three";
4404
+ import { Matrix4 as Matrix42, Vector3 as Vector36 } from "three";
4278
4405
  import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
4279
4406
  var TARGET_SIZE = 1;
4280
4407
  var FORWARD_DISTANCE = 1;
@@ -4311,13 +4438,13 @@ var XRModelPresenter = forwardRef2(
4311
4438
  () => box ? computeNormalization(box, scaleMode) : null,
4312
4439
  [box, scaleMode]
4313
4440
  );
4314
- const placedPosition = useRef7(new Vector35(0, 0, -FORWARD_DISTANCE));
4441
+ const placedPosition = useRef7(new Vector36(0, 0, -FORWARD_DISTANCE));
4315
4442
  const userScale = useRef7(1);
4316
4443
  const rotationY = useRef7(0);
4317
4444
  const placementMode = useRef7(true);
4318
4445
  const modelPlaced = useRef7(false);
4319
4446
  const hitMatrix = useMemo10(() => new Matrix42(), []);
4320
- const hitPosition = useRef7(new Vector35());
4447
+ const hitPosition = useRef7(new Vector36());
4321
4448
  const hasHit = useRef7(false);
4322
4449
  const lastARState = useRef7(null);
4323
4450
  const gesturing = useRef7(false);
@@ -4515,7 +4642,7 @@ import {
4515
4642
  CatmullRomCurve3,
4516
4643
  Sphere,
4517
4644
  Spherical,
4518
- Vector3 as Vector36
4645
+ Vector3 as Vector37
4519
4646
  } from "three";
4520
4647
  var ENTER_BLEND_SECONDS = 1.2;
4521
4648
  function CinematicCamera({
@@ -4596,13 +4723,13 @@ function CinematicCamera({
4596
4723
  if (!hasPath) return null;
4597
4724
  return {
4598
4725
  position: new CatmullRomCurve3(
4599
- waypoints.map((w) => new Vector36(...w.position)),
4726
+ waypoints.map((w) => new Vector37(...w.position)),
4600
4727
  loop,
4601
4728
  "catmullrom",
4602
4729
  0.5
4603
4730
  ),
4604
4731
  target: new CatmullRomCurve3(
4605
- waypoints.map((w) => new Vector36(...w.target)),
4732
+ waypoints.map((w) => new Vector37(...w.target)),
4606
4733
  loop,
4607
4734
  "catmullrom",
4608
4735
  0.5
@@ -4618,8 +4745,8 @@ function CinematicCamera({
4618
4745
  const points = waypoints.map((waypoint) => ({
4619
4746
  azimuth: waypoint.azimuthAngle,
4620
4747
  polar: waypoint.polarAngle,
4621
- distance: new Vector36(...waypoint.position).distanceTo(
4622
- new Vector36(...waypoint.target)
4748
+ distance: new Vector37(...waypoint.position).distanceTo(
4749
+ new Vector37(...waypoint.target)
4623
4750
  )
4624
4751
  }));
4625
4752
  for (let index = 1; index < points.length; index += 1) {
@@ -4631,14 +4758,14 @@ function CinematicCamera({
4631
4758
  }, [signature, hasPath]);
4632
4759
  const elapsedRef = useRef8(0);
4633
4760
  const enterElapsedRef = useRef8(0);
4634
- const enterPos = useRef8(new Vector36());
4635
- const enterTarget = useRef8(new Vector36());
4761
+ const enterPos = useRef8(new Vector37());
4762
+ const enterTarget = useRef8(new Vector37());
4636
4763
  const startedRef = useRef8(false);
4637
4764
  const pausedElapsedRef = useRef8(0);
4638
4765
  const hasFinishedRef = useRef8(false);
4639
4766
  const orbitRef = useRef8(null);
4640
- const tmpPos = useMemo11(() => new Vector36(), []);
4641
- const tmpTarget = useMemo11(() => new Vector36(), []);
4767
+ const tmpPos = useMemo11(() => new Vector37(), []);
4768
+ const tmpTarget = useMemo11(() => new Vector37(), []);
4642
4769
  const tmpSpherical = useMemo11(() => new Spherical(), []);
4643
4770
  useEffect11(() => {
4644
4771
  elapsedRef.current = 0;
@@ -5877,27 +6004,35 @@ import { useRef as useRef11, useEffect as useEffect15, useMemo as useMemo13, use
5877
6004
  import { useThree as useThree10, useFrame as useFrame8 } from "@react-three/fiber";
5878
6005
  import { Sphere as Sphere2, Line as Line2 } from "@react-three/drei";
5879
6006
  import {
5880
- Vector3 as Vector37,
6007
+ Vector3 as Vector38,
5881
6008
  Vector2 as Vector25,
5882
6009
  AdditiveBlending as AdditiveBlending2,
5883
6010
  Plane,
5884
6011
  TextureLoader as TextureLoader2
5885
6012
  } from "three";
6013
+
6014
+ // src/assets/light-gizmos/sun.png
6015
+ var sun_default = "data:image/png;base64,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";
6016
+
6017
+ // src/assets/light-gizmos/bulb.png
6018
+ var bulb_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAAACXBIWXMAAC4gAAAuIAHVHB4bAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAIABJREFUeJzt3Xd4HOXVBfBzZ1eSe6djYqoNwloZDJgaJ0AgIZ2IAJINIcS00D5CCASCCaQBAULHAWIsyQYEhEAILQ6mmRI3ych0MGCae5VVdud+f0gQbFyk3Zm5U87veXgSZM3MAaSZszPzvq+AiBJD761IYVjxN+DK4RDZFnBbAXkb6jwk5ZNmW+cjouCIdQAiCobWj9kPcO8ApHQj3/IYnOw4GX73B4EGIyITLABECaD1Y74O6CMAum3mWxchp6Nlr9p5QeQiIjssAEQxp40VA5AteQPAwE5u8jbWtAyXA+rW+pmLiGw51gGIyGe5bmej8xd/ANgZPYpP9ysOEYUDCwBR3Kn+sMvbiHOcD0mIKERYAIhiTBUCYGgeW5Z2bEtEMcUCQBRnDWN6ACjKY8tuHdsSUUyxABDFWbo5/0/xhWxLRKHHAkBERJRALABEREQJxAJARESUQCwARERECcQCQERElEAsAERERAnEAkBERJRALABEREQJxAJARESUQCwARERECcQCQERElEAsAERERAnEAkBERJRALABEREQJxAJARESUQCwARERECcQCQERElEAsAERERAnEAkBERJRALABEREQJxAJARESUQCwARERECcQCQERElEAsAERERAmUtg5A9rSxohjZ4uMAORaCDFRKIPo2VP8NOH+VTPW71hmJqGu04fidoHIyRA6Dys4QbYFiDoC7kW65W0rrWq0zki2xDkC2dO6xg6Hp+6HYZyPf0gLgtyir+YMINMhsVDhtrOiFbMmqvDZOt/SW0rrVHkcin6lCUF91MQS/BlCy4e+SlwH3aMnULgg0HIUKHwEkmDZWDICbfnITF3+g/QTyOzRU3hxULiIqQH3VrRD8Fhu9+AOA7gvIE9pwfP/AclHosAAkWVvJ1QCGdu6b5VSdU3mSr3mIqCA6p/IkCMZ18tt3hzpX+hqIQo0FIKG0vnJ7CMZ2aSNxrtJXxw70KRIRFUAbju8PkT91cbOTdO6xg30JRKHHApBYcjSAVNe20QFo1Yt9iUNEhdHUbwAM6uJWDnKp7/sRh8KPBSC5hue3mZ6h9VWdfGxAREFo/53UM/La2JFSj+NQRLAAJJYW57lhEQR/9jQKERWm/XeyKK9tXeR7LqCIYwFIKpH5eW+rOErrq470LgwR5UvrKw+F4qgC9jDfszAUKSwASaV4ssAdXK1PjeZEUkSGOn4H/1LYTjDVmzQUNSwACSWZmmcBmVnAHkoxcPAp3iUioi4bOPgUoJBn+DJTRtQ+710gihIWgCRz3bOBAmb3U72cwwKJbGjD8f2hOr6gnQjO8yYNRRELQILJiNrnIagrYBf9OSyQyIg6l6Lrw/6+QO6RsuqnPctDkcMCkHSq5wFYU8AOfq5zTuAwIqIAdQzFPb2AXaxFLneBV3komlgAEq5jMZCrC9hFGk6uq7OPEVFhrkG+w/7aXSl7TX7PqzAUTSwABKxp+ROA/E8GHBZIFBitrzwUwLcK2MUCQK7yKg9FFwsAQQ6oWwvgwgJ3c43OGFfIJxIi2gy9tyIFyHWF7UXOl0x1AY/9KC5YAAgAIJmaKQCeKWAXu6N4bWdXISOifAwrOQ3Annlvr5iOsup7vAtEUcYCQP+jzjkAcvlvz2GBRH5pH/aHSwvYhYuUnC1SwNBfihUWAPqclE+aDeBvBeyCwwKJ/FL4sL87ZHj1DM/yUOSxANB65NcAVuS/PYcFhsrCRc0A3Dy2zHVsSyGgDccNQ2HD/lYhnfqNV3koHlgAaB2SqV4I1csL2AWHBYaIfG1aFsBHeWz6Xse2FAaayn+1PwBQjJfSiZ94F4jigAWAvizb83oAr+e9PYcFhs3jeWzzsOcpKC8FD/tTvIWe/W/yLhHFBQsAfYmMnNAGt+A5wjksMDyuAtCVT/NrkXau8SsMdZ4nw/4cOVt2vaHFo0gUIywAtEEyouYRAI8WsAsOCwwJydS8DnT67XGF6jgpnfS+n5mokwof9veklFX/y7tAFCcsALRxaZwFoDXv7TksMDQkU/N7iFwAoG0T37YMqkdLeW1NULlo4zwY9tcGwZmeBaLYYQGgjZLSmregKOTZIYcFhoiUVV8J1y0F9HoI5qF9EajFAF4E5CKIu7OU1/7dOCZ9ptBhf4IbOu7+EG2QWAegcNMXK/ugu7wOYOs8d5GFpsql/K5GL3MRxZk2HDcMmmpA/m/+L4SbHiojJi73MhfFC+8A0CbJqNqVECnkNiSHBRJ1VaHD/kQv5sWfNocFgDZv+M63A8h/BjEOCyTqNA9W+5uD11rv9CoPxRcLAG2WyHgXrp4DFDSHOIcFEm2GPjU6XfCwP5Fz5Ji6/Nf0oMRgAaBOkRG1z0NQV8Audkdx0ymeBSKKo/7bFTbsD3KPlFU/7VkeijUWAOo81fPQ/uZ4ntvjtxwWSLRh2nB8f4gUMl//WuRyF3gWiGKPBYA6TTK1CwBcXcAuOCyQaGMKXu0PV8pek9/zKg7FHwsAdc2alj8BKOAkw9UCidbnwWp/CwC5yqs8lAwsANQlckDdWgAXFrALDgskWl+hw/4g50umOv/Hc5RInAiI8qL1VU8DOKSAXXxTMjWPeZWHKKrah/3Jv/PfAaYjU3OQSEGjdCiBeAeA8qPOOQAKGWrEYYGUeB4M+3ORkrN58ad8sABQXqR80mwAfytgFxwWSFT4sL87ZHh1/pN0UaKxAFAB5NcAVuS9OYcFUoJ5MOxvFdKpQranhGMBoLxJpnohgCsK2EV/tLqXeJWHKFJUxqOQYX+ql0npxE88y0OJwwJAhUm3XA+gkCVHz+CwQEqa9mF/clr+O8Bb6DngRg8jUQKxAFBBpLSuFS7OK2AXHBZIyVPosD9HzpZdb2jxLhAlEYcBkie0vupfAL6Z9w7EOVzKJuU/FIooInRu1eFw8UQBu3hUMjWFrBZIBIB3AMgrkvs/AG15b6/upd6FIQoxVy8rYOu2jt81ooLxDgB5RudUXQPBuflujnR6W77UlB+dUdEX3Uu2QFYGQjAQLgZC3IFQDIRICRR9AQCCEgA9OjbrjfbS1tzx9yugcCHIQbES0CY4zhIolsDRJci6i5B2l6ClzxIZOaEp+H/K6NNZx26LVPrDvHcguEbKagp55Eb0ubR1AIoRTf8Wkq0CsEUeWwvaWncHwAKwAfpiZR+UYChSzhCoDgEwBMCOX/jfbsgCgAKKjmov/6v4na3663y/ANoxv4wLwHEA1wGKmqD1VcsAzIdgPlzMh8h8AO8CeBfp5jektK61gH/c+EoX7wF18916EXLpy72MQ8nGAkCekRETl2vDmIuhelt+OxA+ksJnnxKL9gZ0bwB7QFAKxTAAzucXZHv9AfSHYkR7WfhCrmxJVuur3gdkHqAzIdII15mHzF3zEj9jnZtzIHneeBW5WEZMXO5tIEoyFgDy1mvNd2BoyWkAyvPYupDhhJGkr44diJbc/nCcA6F6IIC9AfRY54IavUtmGsBOgO4E4NtQBSQHNFQt03q8AOh0uPI8cj1eTtyjBFdfRyqvAjAHrzXf4XUcSja+A0Ce04bjD4E6T3dxs5ckUzPKl0AhonOP2wpu6ggABwM4EMAwJPf3MAvBbCimQ/VpNGOqjKpdaR3Kb9pQ9TIU+3RpI3G/KmWTn/EpEiVUUk885DOtr5oI4ITOfjugh0umdqqPkUzovRUpDCsqB5zDoPgOgP3B0TcbkwNkDqD/hLgPY/jkWXF8ZJDHMMC7JFNzol95KLlYAMgXWj+mJ6AvolMLnchFkqn+g++hAqKNFb2QLfkOgB8AOAztz8up6z4C8ChEHkBr9ydl5IT8h5mGTMe7Mp15oa8BkAMkU73G91CUOCwA5BttOL4/1KkBsJFJS2QpgDMkU313kLn8oO+e2A0rc4cDbgUgPwDQyzpTzCwH9GHAqUNb98fiUAa0vuo4QG4EdMAGv0HwCHLpKr74R35hASBfqUJQX3k4HDkVigyAAYC8DXWnQpw/dywoFEl6b0UKQ7t9C9DjAXwHQE/rTAmxGIoHIKiRTM2z1mEKofVjtgT0fABfB7ATgKUQ1ENwC/as+XccH4FQeLAAEHWRzhm7HRy3CorTAHzFOk/CvQHBnVD5W5TLJJEFFgCiTmh/ma/716DuOLQ/2+cQ2nBpBeQJCCbhteYH5Ji6nHUgorBjASDaBG2sGIBst1MgeiYU21jnoU5QvAXRa9HWc2Li5hkg6gIWAKIN0PoxOwLuOYD8FHy2H1UrAL0LwFWSqV1gHYYobFgAiL5A6ytHAfILAN8HkLLOQ55ogUgtsu6fZa/aedZhiMKCBYAIgM4dWwbXvRhAhXUW8o0L4H4Al0imJnHTThOtjwWAEk3rx+wJ6G8A/Aj8fUiK9iLgur+WEZPftA5DZIUnPEoknTtmd7h6GXjhT7I2AHch7VwupZPetw5DFDSe+ChR2mcnlPGAnA4O5aN2rYDeipKSi2XYnauswxAFhQWAEkGfGp3GwMEnQfUKAFtY56FQ+ggil2H4zreLjHetwxD5jQWAYk/rKw8F5FoAw62zUBTITKicI+WTnrNOQuQnFgCKLZ0zdjvAvRGC71tnochRqN6FotbzpLRuqXUYIj+wAFDsqELQUDWm/VP/RlZaI+qchQDOl0zNJOsgRF5jAaBY0bkn7Aw3NwHtq6sReeVfSDuncbQAxQkLAMWCPjU6jQHbXQDIxQC6WeehWFoFwUUYXnMTl+mlOGABoMjT2ScOgZOtBnCQdZZQSfcGUr06/uoGON3bv+4UAVLc/v+1FWj5BGh6B+A1rbOmQp0TpHzSh9ZBiArBAkCRpg1jKqA6AUA/6yyBcoqBkm2A4i2A4i2gRYOAokFA8ZZAug+Q7oUu/XpnV0AWPQIseQJQjoDrhOWAnCaZ6rutgxDliwWAIklnVPRFuuQmCCqts/gu3RfosRPQbQdoyfZA98FA8daAON4fa/U8yHvXAW6z9/uOJa1GuvV0Ka1bbZ2EqKtYAChytL7qYEBqAR1sncV7AnTbDui5G7T7rkDPXds/1Qdp5WzIe9cGe8xoewPAcZKpmWUdhKgrWAAoUrRhzDio3gigyDqLZ9K9gV7Dob2GA73L2v/emLx/PbBihnWMKGkB8HPJ1NxuHYSos1gAKBJ0ekV39Ci+GSInWmfxRMnWQN/9oL3L22/vh+1XcVU9ZP6frVNEkFZjTespckDdWuskRJsTsrMO0ZdpY9UuyOIBRH0q3+ItgH6joH32Bbp/xTrNpuWaIPNOtU4RVbPgpo+WERPnWwch2hQWAAo1nVP5bYjUAOhrnSUvTkn7Rb//V4Eeu1in6RJ55SeA5qxjRNUiuDhGRtRMsw5CtDEsABRaHc/7b0IUl+3tPgTafzTQb38g1d06TdfxDoAXslD8XMprbrMOQrQhLAAUOh1z+V8K4FLrLF0iDtB3H+jAI4EeO1unKQxHAnhIr0dZ7TmcPZDChgWAQkXrx/QE3FpAvmedpdOcbsCAQ6ADj2h/zh8DMv/PwKp66xhxUoc+6bGy40ROsEChwQJAoaGNJ26NXPYhKPaxztIpqR7tF/1B3wBSPa3TeGf5S5APbrJOEUdPI93yQy4vTGHBAkChoI1jd0CbOxWC8L8p53QDBn0DOujI9nn242Tl7PaLv9tqnSSuXoU6h3MdAQoDFgAyp/VjdgR0KoAdrbNskqSBQYdDB307FJP1eKp1IWTRw8DSZ8BFgXz3LsQ9TMomv2MdhJKNBYBM6dwxu8PVfwPY1jrLJvXZG7rNccFPy5sPtxVoWwJkVwG51UB2NZBbBcmtaf9zzQJuS/v/z64AWhcCzQvs8ibT+3Ddw2TE5Detg1BysQCQGa2v2gvA4wAGWWfZqG6DodscD/QqtU6yLs21L+Pb/AGk5QOgdVHHX4vbL+oUBZ/Ccb4hwyc1WAehZGIBIBNaXzkKkMcQ1gl+nG7QrX4EDDzMn1X3uqp1IbDmDUjTm8Dad9s/sWvWOhUVbknHOwGzrYNQ8rAAUOD0laoMcvIfQAdYZ9mg3hnoticAxYY3JtqWAqsaIKtfAZreANqW22Uhvy2GpkZL+V2N1kEoWVgAKFBaXzUUwNMAtrLO8iXp3tCtjwf6H2hwcAWa3oasnNU+/r75A4MMZGghgEMkU/O6dRBKDhYACkzHoj5PI4wv/PUZAd3up0C6T7DHXfsuZMVLwPKX2l/cowSTD+CmDuEiQhSUyBQAnXvcVlDpiVzxUhkxkfdDI0bnHjsYbuoZQIYYR1mXUwzd+jhg4NcR2K9DdiWw7FnIsqfbX+Qj+oziLbjZr8ped39kHYW6RuvHbAnJ9UJr2xIZWReJN3FDXQD0xco+6I5fATIWwHYdX3YBNEDkFnTvd5fsekOLYUTqBJ1x3CAUpaYD2NU6yzq6fwU6+DSgJIgbEgqsngdZNg1YMZMv8NGmvIK2loOichFJMn3zzBKsXfYzqJwB6LAv/NFciNyGJR/cJl+bFtpf9tAWAJ1VuQdSzmOADt7Et70NwS+krObBwIJRl+i7J3bDiuxUCA6wzrKOgV+HblPVPrmPnzQLLH8RsvhRPtenzhNMQ/f+R/IDTnhpQ9X3obgawKZW/pqBNnxXRtZ8HFSurghlAdBXxw5EmzsXim06uclUiJ4rZbVzfQ1GXdK+qt+YGkCPt87yOSlqf8N/wCH+HsdtBpZMhSx5Amhb5u+xKK6moKymkqsIhos2VA6HyrUADu3kJq9jre4ro2pX+pkrH+EsAPVjJgD6sy5ulgXwVxQ7l8juk/g2VQhofdWVAM63zvG5ooHQHX7u71K9mm1/vv/pA5yQh7wwXjI1l1mHoI4Ppq3u5QB+BqCrtw5vlkzNGT7EKkjoCoA2nrg1stn3ABTnuYtlAC5DW4+bZeSENg+jURdofdXJAP5qneNzPYdBdzjTvzn8NQssndY+nz4/8ZN3FNCfSKb2LusgSaUzxhWhqOl0AJcC6J/nblZhTctWckDdWg+jFSwEU5ytp63tMOR/8Qfa/wNdh6KmV3RO5bc9SkVdoHOrDgdwi3WOz/XdF7rj+f5d/Fc3Qt66BPLRJF78yWsCyAStrzrYOkgSacPYw1DUNAvAdcj/4g8AvdG9eC+PYnkmfAXAkd082tNuEHlYG6r+2TH5DAVAG8fuABeT0fVbZP4YdAR0hzMAKfJ+3y2fQN67FvLun4Bmru5KvikGUKdzxm632e8kT2h91VBtqPon1H0SwJ6e7DQlQzzZj4fCVwDU44nXFUcBmKtzqq7R2Sf283TftA5998RuyLr3IwyL+0gKut3J0G0q4fmTLm2DfHof5M0LgZWcwp0CsRXEvU/fPLPEOkic6ewT++mcqmsAzO24dsRa+AoA9A0fdloEwblwsm/onKpT9N6KlA/HoFW5mwCMtI4BSUMHn+HPm/5Nb0LevARY+FD7inxEwRmFpmXXWIeII723IqVzqk6Bk30DgnMBeH/L0HVDt/Rz+ApAsfMIAL/Gvm4Bwa0YWjJX66uO9OkYiaT1ladD9STrHHCKoV85B+jrcQ9xWyEfTYK8fQXQwknayMzpOqfS/vcsRnR21WjsVjITglsBbOHTYRbgjbaZPu07b6EbBQAAOqfqmo4W5jP5JyR3tpRNfsf/Y8WXzjl+X4jzLAp7ebNwTkn7xb9Xqbf7bV4A+eAWTuRDYbEWKewve9bUWweJso7pyX8HyBj/D6Y/lfLaO30/TheFswDMGNcDRU2PAzgogMM1A3ot0q2/l9K61QEcL1a0fkxPQGcB8Orlzfw43aA7/hLosYuHO1Vg0b8gn97PqXspXATz0NpjHxk5ock6StRoY0UvZIsvAuRcAN0COOTtKKsZF8YJncL3CACAjJzQhHTLNwH5K+D7v7RugFyIXMkbWl95gur4UP47CS3VG2B+8S+GDvk/by/+udWQ+VdDPrmHF38KH8UeSK+50jpGlKiOd7S+8gTkSt4A5EL4f/FfDdWzwnrxB0J6B+CLdHblPnDwF0D2D+aIMhMq50j5pOeCOV506ZwxP4To/aYhJA3d4SygT7l3+1z7HuT964HWRd7tk8h7CuD7kql5yDpI2Onsyn0gcl1Aa5IogPuQdn4hpZPeD+B4eQt9AQA+m1O+agyAPwHYOpijyj/hps7k2twbpnPGbgdx6wEMNAshDnTw6UDffb3b5/LpkA/vBNxW7/ZJ5J9FaEMmrIvNWGs/T+X+AEgVgrnezYI6Z0flA2QkCsBnOp43nw/gVwCCGA/bBMUNKGq5gu8H/I/qeAcNbz0J4OumObY72cOhfgr55F5g0SMe7Y8oMI+hrOZbYb3NbEGnV3RHr5KzoPg1AJ+mAF3HEgCX4/WWG+WYusiMD45UAfiMNlbtgix+D6AioEN+COAilNVU85cM0IYxZ0D1RtMQW30fuuUPvdmXZiELbgeWT/dmf0SBk5MlU32HdYow0Pox3wHc6wEZEsDh2gC9BW2tv5GRdZFb/SuSBeAzWl95KCDXwaupGjdLXobIOVI26YVgjhc+OuvYbZFKzwPQ1yxEv1HQwafBkx/f3GrIe9cDa14rfF9EdlZAnVIpn5TYOam1vmovtM/ZH9S6CVMBOUcy1a8EdDzPRboAAIA+NTqNgYNPgurvEMwUtApoDdJFv5TSiZ8EcLxQ0fqqfwD4rlmAnkOhO14AiAdLDWRXdMzjv6DwfRHZu18yNT+yDhE0nXHcIBQ5lwByBoAgZnl9GyIXSll1XQDH8lXkC8BntLFiALLFlwb4Q7AGwNXok/6j7DixOYDjmdOGyuOhUmsWoGggdJfferOqX9uS9ot/S+I6HMWZytFSXv2AdYwgfL5Mr+C3UPQJ4JBNAK6K0zk/NgXgMzp3zO5w3WsBOSKgQ74P4BLJ1EwK6Hgm9NWxA9HqzgOwpUkAKYLufDHQfcfC99W6GPLuH4HWhYXviyhcPoG4e0jZ5FivS93+nF+vBbBzEIdDRIb1dVXsCsBnOn5A/gLAgytGZ8hTcOQcGT6pIZjjBUvrqyYCOMHs+Nv/DOjvwaO91oWQd34HtMX6/EhJJrhNympOtY7hB204bhg0dQ2AbwZ0yEgN6+uq2BYAANDGimJkS04DcDmCGQriAloLOL+QTHVsPl5qw/F7Q52XYTVz5MBDodt60D3alkHeuYIT/FDcuRB3XymbHLrFZ/L1hUe8pwPw4AWgzYrksL6uinUB+IzWV24POH8C9DgE88+8DILfYHjNTVEfNtgxCdOzAA40CdD9K9CdLy38pb/sCsg7vwdaOF8KJcLzKKs5OPrnn/EO6t88AyKXAegfwCHbILgBrS2/jeKwvq5KRAH4jM6tOgAu/oKg1qxXPIie/Y+VXW/wa3lj3+mcMWMgavN+g1Pc/tJfybaF7Se3tv22f3OsHt8RbZrIMVF+U11nVPRFUcn9AA4N6IiPQ9xzpGxKYsYEJ2rhGxleMx1lu+wH1Z8C8P/1b8H3sWZZZCfn0BnjekD0CrPjb31c4Rd/zUE+uJEXf0oexZ91xrge1jHyoU+NTqOo5CEEcfFXvAXIdyVTe2SSLv5AwgoAAIiMd6W89k6s1aEArgLg76TvgkqdXXWUr8fwS3rNhQB2MDl27wwwsPCZhuXjGmDVXA8CEUWNDkbx2v+zTpGXAdufAcCreb43ZjVUL0TP/ntKpvphn48VSol6BLAhAU0r/B/J1AR0G8sbHTP+vQWge+AHT/eG7vrHwsf7L/pn+/z+RMm1GpCdo/RScsd7R28B2MmvQyCmw/q6KnF3ANYnpTVvSabmGEAPA+DXlI6jtbGil0/79oeTuggWF38Auk1l4Rf/1a9APr3Pm0BE0dULohdYh+iSOcfvAv8u/rOgziGSqTkm6Rd/gAXgc5KpnYq2HnsBOAeA14PEHWS7DfF4n77RxrE7QORkk4P3zgD9Clyyu3Ux5P2bAXW9yUQUZYrT20dCRYSTGuzDXj8F5GSU7bJPXMf054MF4Atk5IQ2ydT8BW253SC4DYB34z/Vic5wnKz7GwSz3PK6nBLotmML24e2Qd6/Hshx9WaiDt2g+JV1iE5z1MtpdtsguAZtLUMlU32HyHh+KviCxL8DsCkeTivchj7pPlGYP7rjnYh5AIoCP/Y2VcCgbxS0D1nwV2DZsx4lIoqNNkCGSqb6Xesgm6OvndQbLa3LUPiaLlOhqbOl/K5GL3LFEe8AbIIMr35VMrVHAvJdAIX84jwWhYs/ACCHS2Fw8Ue37YCBBb4nueK/vPgTbVgRoBdZh+gMGXbnKgBP5r0DxVsQOUYyNYfx4r9pLACdIJnqh7GmpRQil6B9FcCuaIW6ZmPpu0LnnrAzFMeZHHubSkAKKPxtSyEf/s27QETxc6I2jrUZ1ttV4v4B6PIshv8b1hfhCZCCxALQSXJA3Vopq74C0GGATEZnfzgVZ0n55Jf9TeeRXPZcBLOU8rr6jgR67VnADhSyYAKf+xNtWhpt7jnWITpDyiY/A8G1nfx2BbQauexQKa/9Y5RnXg0a3wHIUyemFV4FkZ9GpYm2L7ZR8j6AnoEeWNLQ3f4IFBewyvDixyAfT/YuE1F8rYKb3kFGTFxuHWRzVCGorzoXgj9h4wsAzYA4Z0nZpBeCzBYXQayqFEsyvGa6KvZFQ9VBUFRBkEH7uPn3AEyFuJMitSZ3rtvpgAZ78QfaX/or5OLfuhjy6f3e5SGKt95IZU8B8CfrIJvTvpBRzTVaX3kvICcDOgoig6FYDKAR6kxGZtLzUV/wyBLvABD0zTNLsHbZu1BsE+iBnW7QoVcD6T5570LmX8Wpfom65iOkW3aU0jp/p0Gn0OM7AAQ0LRsT+MUfAAYdUdDFH8ue48WfqOu2RbbY5GVfChcWAAKAMwM/YqoHdFAB0yu4zZBP7vEuD1GiyNnWCcgeC0DCaX3lKABlgR934BFAKv/lEWThg0B2hYeJiBJlhDYcv7d1CLLFApB04vws8GM63dpv/+erdSGw+Anv8hAlksHvPoUKC0CC6Wsn9YZm1+SxAAAgAElEQVSqn8sgb9iA0UCqR96by8eTAc16l4coiRSV+tpJBS67SVHGApBkzW2VAII9AYgDHXhY/ts3vQmsnOVdHqLk6oWWtmOsQ5AdFoAkEw3+FmDffQsa988x/0ReMjgHUGiwACSUzj6+HMBegR934JH5b7x6XvtfROSV/bR+TCHzcFOEsQAklUjw44C77wj02CnvzWXhgx6GISIAgLo/to5ANlgAkkrk6KAPqQNG57/x6nnAmtc8y0JEHSw+DFAosAAkkNaP2Q/AzoEe1OkG9B2V9+ay5DEPwxDRF+ys9VWBPw4keywASaQa/C2/fvsDqe75bdvyMbCy3ts8RPQ/FucEMscCkDCqEAiCv/3f/5C8t5XFjwFc8IvIPyLHqnJxuKRhAUiaOZUHANgh0GMWbwn0yPOJQ241sPx5b/MQ0fp2QMOYfa1DULBYAJJG5DuBH7Pf/vlvu+x5wOWqpUS+U/fb1hEoWCwASSP4ZtCH1L75f7CQZU97mISINkqcwM8NZIsFIEF0RtU2AIYHetCSbYBug/PbtulNoHmBt3mIaCN0r45zBCUEC0CSFMm3gIBf9Om3X96byrJnPAxCRJshKMLh1iEoOCwAiaLB3/7vVZ7nhjlgxUxvwxDR5vAxQIKwACSEPjU6DeDQQA+a7g302DG/bVc1tI8AIKIgHaH3VqSsQ1AwWACSYtD2+wLoF+gxe5ch3ycOsvJlb7MQUWf0x25Fe1uHoGCwACSF4uDAD9krz/cNNQusnO1tGCLqJOcg6wQUDBaApFAcGOwBBci3AKx+Fcg1eRuHiDpHNOBzBVlhAUiAjik+81+JJx/dtm9/ByAPsnqux2GIqPOEdwASggUgCeYeNxTAFoEes+du+W+7igv/EBnaUhurdrEOQf5jAUgCTQd+S0977Jrfhq2L2lf/IyI7Od4FSAIWgCQQHBD4MfMtAKt4+5/InPI9gCRgAUgC1X0CPV5Rf6A4vycOsvZNj8MQUR5GWgcg/7EAxJw2VhQDGBroQbvnOfkPAKxhASAKgd11xrgi6xDkLxaAuNOS3QEUB3rM7l/Jb7vsCqB1obdZiCgfJUi3FPAmL0UBC0DcZaUs6ENqyXb5bbjmDW+DEFH+JBf4uYOCxQIQd6LB/xJ32yGvzaR5vrc5iKgAGuzS4RQ4FoC4EwRbAJxioHjL/LZd+763WYgofxL83UMKFgtA3Cn2DPR4JdsBkuePVfMCb7MQUf6UBSDuWABiTOvH9ASwbaAHzXP4H3KrgbYl3mYhogLo9vruid2sU5B/WADiTJ0hgR+zeFB+2zV/6G0OIiqUYIWb3ws9FAksALGWLWBAfn60KM8CwOF/RCGkgZ9DKDgsAHHmyJDAj5nvI4DWRd7mICIvsADEGAtAnKkMCfyYed4BED7/JwohlwUgxlgAYs3gl7eoX37b8Q4AUfiIsADEGAtArDl5zsmbJ3GAVI/8tm1b6m0WIiqcYIh1BPIPC0Cs6VaBHi7VE4Dkt212padRiMgDijxn9aIoYAGIt4GBHi3VO7/tNAu4Ld5mISIvBHsOoUCxAMSUzhjXA0D3QA+a7pXfdtnVANTTKETkiV6cDCi+WADiqntzngPyC5Dv8//cam9zEJF3Vri8CxBTLABx1WZw606K8tvObfY2BxF5J8XHAHHFAhBXKTf4OwCSzm87t83bHETkHVeDP5dQIFgA4iqH/oEfM98CoFlvcxCRh9wB1gnIHywAcSUa7AuAAAsAURwp+BJgTLEAxJU4JcEfM5XfdiwAROFlcS6hQLAAxJYWB37IfF/my67yNgcRechlAYgpFoC4chF8AWj5JK/NpPUjj4MQkYeCP5dQIFgA4kok+Na+9h2gbVnXttEcsGKmP3mIqHAW5xIKBAtAbBk8AlAXsugfXdtm8eMAlwImCi9XWQBiigUgtvKdladAS54Clr/Yue9d/Qrk0/v8zUNEhRGHjwBiKs9xWxR+Vq/WK2TBrdDWT4Etjtrw0EDNAosfg3x6f/sjACIKMeVMXTHFAhBfrWZHVrf94r50GtBvFLTbkPaFgrKrIGvfAVa8DLQtNYtHRF2gyqU6Y4oFIK5UWyBim6FtCbDoERinIKJCOA4LQEzxHYD4srsDQETxoS4LQEyxAMQVWzsReYLnkrhiAYgrVa6xS0SFE55L4ooFIK5U+ZYdERXOlcXWEcgfLABxJeAvLREVTnSRdQTyBwtAXDlp/tISUeGcLM8lMcUCEFctTbwDQESFW5XjuSSmOEQ7xrS+qhkA5/EmonytlkxNb+sQ5A/eAYi3T60DEFGkfWwdgPzDAhBngnesIxBRpL1tHYD8wwIQa8ICQET5U7xlHYH8wwIQZ6ps70SUP+E5JM5YAGKNdwCIqAC8AxBrLABx5vCXl4gK4Lg8h8QYC0CcrWpuBJC1jkFEkdSE17JvWocg/7AAxJgcULcWwGvWOYgoghRz5Ji6nHUM8g8LQPzNsg5ARBHkyEzrCOQvFoDY09nWCYgoglyeO+KOBSDuRHkHgIi6Ls27h3HHAhB3rb1mAGixjkFEkbIc81pesQ5B/mIBiDkZOaEJwH+tcxBRhCim8QXA+GMBSALVadYRiChCRJ6yjkD+YwFIAsE06whEFCn/sQ5A/mMBSII1rdPB9wCIqHMWoqy60ToE+Y8FIAHaJwTiYwAi6gSRf4pArWOQ/1gAEkP+YZ2AiCJA8aB1BApGenPfoLNP7Adky5GSLYII5DlVF8BCQGZJpnqNdRwzuew/kErfBECsoxBRaK3GmuZ/W4ewpDOqtkFah8Nx+lpnyY+2QeVjLP1gpnxt2ibXgtnoxUDnjN0N4v4OwPcAFHkd0UAzFPcjlbpUht+VyDWutaHqZSj2sc5BRKF1n2RqKqxDWNA5Yw/quOYdjHh8UFoByO1IN/9eSuuWbugbNvgIQOeMOQLizgDwI8Tj4g8A3SCohJubrXMqv20dxgRv7RHRpoj+3TqCBZ1TeSbEnQbgEMTj4g8AfQE9D9mSWTp3bNmGvuFLBUDnjtkdovcB6O17PBu9IVKn9WP2sw4SODc9GeDLPUS0QauRan3IOkTQtL7quxD5C4CUdRaffAWu+6jOOnbb9f/gy3cAXFwJoFcQqQx1A/RW1fGJeglSRkycD+A56xxEFEIi90pp3WrrGEHSGeOKAFyL+Hzq35htkUr9cf0vrnMB1PoxWwL6reAymSrHK2+Nsg4RONFq6whEFEa5u6wTBK6kaTSAnaxjBEOO1caKAV/8yvqfgPfbwNfiK6eHWEcIXBPuAbDWOgYRhYnOx/DJz1qnCFwOSfoQWIRc8TqPvte92KsOCjSONXG2so4QNBlVuxKQRL7oQ0QbIc4diZz8RzDQOkKw1r3mrVsAHCwLNIs5d7F1AhOO3mQdgYhCowWS/at1CCPJuuYplnzxb9ctANnsy0jSW+Kq060jWJDhNdMBzLDOQUShcLcMn/KpdQgTbqLOgzkAL33xC+sUANnr7o8AmRZkIkNvYNlHyXvm9RnVW6wjEFEIuAm+I9gvPRWCj61jBELwkGSqF37xS19+4c/BLwG0BpXJSA7inLG5aRJjral1CrDu7SAiShp9QUbU/tc6hRXZcWIzXLnAOkcAlgHuL9b/4pcKgAyvngHRnyC+JaAVIidL2aREz3ctB9SthUhymz8RAZAvjQ1PGimvrgbkd9Y5fLQEwPekbPI76//BBof8SVntZKh7MIDn/U4WsKkQ9wApq55oHSQUUs1/AZCoiT+I6DPaiLJd/mmdIgwkU30xVH8I4A3rLB7KQVGLtLOXZGo2+Lh7s7Mf6dwTdobmRgGyJaDF3mf0maIZqp8glZsuw+/+wDpO2OicqmsgONc6BxEFTPVYKa+9xzpGmKhC0FA1AsCeALaERHJ64DWA+wFSbc9ubBGgz8R9+kPaDJ0zdjuI+zaAEussRBQQxVt4o2WYHFOXs45CdpIz6x9tkJRP+hDQv1nnIKIg6WW8+BMLAAG53OUAmqxjEFEg5iKz62TrEGSPBYA65n/AzdY5iCgAqheJjHetY5A9FgBqV+z8EYKV1jGIyFcvIVP7iHUICgcWAAIAyO6TlsDF1dY5iMg3CnV+kchFf2iDWADof5pargbwvnUMIvKDTJHySc9Zp6DwYAGgz8kBdWuh+mvrHETkudVQ+aV1CAoXFgBaV6a2FvGbAZIo2QRXtA/5JfofFgBahwi0Y0EoPickioc3kGq51joEhQ8LAH2JDK+ZDqDaOgcReUDkXCmti+viblQAFgDaCDkfwDLrFERUCP2HlFX/yzoFhRMLAG2QZKoXQuQS6xxElLcWuHq+dQgKLxYA2rjXmm8FMMM6BhHlQXCljJj8pnUMCi8WANqojsVCTgGQtc5CRF3yOnqnf28dgsKNBYA2STI1swC9yjoHEXWaC+BnsuPEZusgFG4sALR5PQZcBsE86xhE1AmKmyVT86x1DAo/FgDaLNn1hhao/hQA1w8nCrf30K34IusQFA0sANQpkql9EcCN1jmIaBNUTpFhd66yjkHRwAJAndfW4yIAb1vHIKINUPxNyqsft45B0cECQJ0mIyc0Qd2fgdMEE4XNJ3Dc86xDULSwAFCXSPnkpwC53ToHEX2ByhlSNpkzd1KXsABQ17U1nw/IB9YxiAiA4F4pr37AOgZFDwsAdZmMrFsBuD8BHwUQWVsElTOtQ1A0sQBQXiRTOxXQG6xzECWayqmSqV5oHYOiiQWA8ten6AJAG61jECVS+1v/vPVPeWMBoLy1TzUqYwG0WWchSpgP+dY/FYoFgArSvlYAuOgIUXAUwMl8658KxQJAhVu64ApAXraOQZQIqjdLpuYx6xgUfSwAVDD52rQsJHsCgLXWWYhi7l0Utf7KOgTFAwsAeULKprwG4ELrHEQx5kKdsVJat9o6CMUDCwB5p6zmeiietI5BFEuKq6R80nPWMSg+WADIMyJQaHocAK5GRuQpbUTf9HjrFBQvLADkKRkxcT6AC6xzEMVIC5zU8e3Dbom8wwJA3iuruRVQLktK5I2LZfikBusQFD8sAOS59kcBqZ8CWG6dhSjinsPrLddah6B4YgEgX0j5pA8h+kvrHESRJVgJNz1GjqnLWUeheGIBIN9IWe1fATxqnYMoklTP6ninhsgXLADkL3V+BoBTlhJ1if5DMrV3WaegeGMBIF9J+aQPofoL6xxEEbIIjnuKdQiKPxYA8p2U194JxYPWOYgiQfUkGT7lU+sYFH8sABQQ5+fgowCizblDymv/aR2CkoEFgALR/igAXL+caOM+hJvm4zIKjFgHoGTRhqrHofiGdQ6iEPoml/mlIPEOAAUr644DwNXMiNZ1By/+FDQWAAqU7DX5PUAvsc5BFCIfQdzzrUNQ8rAAUPDKdr0ewPPWMYhCQfRkKZvMF2QpcCwAFDiR8S5yOg5Aq3UWIltaLWW1nC2TTLAAkAnZq3YeIFdZ5yAytARw+NY/mWEBIDs9+l0O4HXrGEQ29DzJVC+0TkHJxWGAZErnVH4DIo9b5yAK2NMoq/maCNQ6CCUX7wCQKSmvfQLAFOscRAFqgeRO5cWfrLEAkD0ndy44TTAlhl4uZVNes05BxAJA5joWPvm1dQ6iALyOdCtffqVQYAGgcCjb5TYI/msdg8hXoudKaR2Hv1IosABQKIiMdyE4B+BzUYorfYBj/ilMWAAoNGR4zXQoJlvnIPLBWo75p7BhAaBwKUr/AoKV1jGIPKX4g2Sq37WOQfRFLAAUKlI68ROo/NE6B5GH3kHfNF/8o9BhAaDw6dHvGijeso5B5AnBebLjxGbrGETrYwGg0JFdb2iB4FfWOYg88KyU1TxoHYJoQ1gAKJQkU3M/uGQwRZvCYZGl8GIBoBDTX4DDAimy5F4ZXjPdOgXRxrAAUGhJpvZFKP5hnYMoD61I68XWIYg2hQWAwq39XYA26xhEXaK4SUpr+CIrhRoLAIWaZGpeB/QO6xxEXbAcJc7vrEMQbQ4LAIVfuugyAGusYxB1iuIa2X3SEusYRJvDAkChJ6UTPwFwi3UOok5YDk3fYB2CqDNYACga2nJ/ArDaOgbRZlwlIyYutw5B1BksABQJMnLKYvAuAIXbEpQU89M/RQYLAEVHW+5KAKusYxBtkOrVMuxO/nxSZLAAUGTIyCmLocq7ABRGi9Gt5CbrEERdwQJA0eIWXw2+C0BhI3IVP/1T1LAAUKTIXn9bBL4LQOGyAk3urdYhiLqKBYCiJ5e9DkCrdQwiAIDiVhlVu9I6BlFXsQBQ5Mhed38EYIp1DiIAbRC90ToEUT5YACii5GpwpUCyplormdoF1jGI8sECQJEkmepXADxmnYMSLi3XWUcgyhcLAEWYXG2dgBLtUdmzpt46BFG+WAAosiRT/R8As6xzUFKxgFK0sQBQ1PEkTBYaOgooUWSxAFC0tfW4D8BH1jEocTjunyKPBYAiTUZOaAMw0ToHJcpqrNVa6xBEhWIBoOhLO7cByFnHoKTQGk78Q3HAAkCRJ6WT3odwSCAFRf5qnYDICywAFA+u8pksBUBfkEwNR55QLLAAUDxkdv0XoPOtY1DcCYsmxQYLAMWCyHgX4txhnYNibXnHqBOiWGABoPhIpW4HkLWOQTGlqJGRE5qsYxB5hQWAYkNKJ34C4AnrHBRTIjXWEYi8xAJA8SIcn00+ULyFsuqXrWMQeYkFgOKlteeDAFZZx6CYEdSIcPlpihcWAIqVjme0D1jnoJhRZ4p1BCKvsQBQ/DjgYwDykL4g5ZPesE5B5DUWAIqfPXeZCmCBdQyKCXFYKCmWWAAodkTGuwDuts5BsdCG1uw91iGI/MACQPGUAodskReelJFTFluHIPIDCwDFkuxZUw/gdescFHXCF0optlgAKL5U/2EdgSLNRTr1iHUIIr+wAFB8OakHrSNQpD3fMbskUSyxAFB8Dd/pJQg+to5BEcU7SBRzLAAUWx2jAR6yzkERpcqfHYo1FgCKOX6Ko7zMlRGT37QOQeQnFgCKt+4D/gPBSusYFDECvj9CsccCQLEmu97QApVHrXNQ1Li8c0SxxwJAScBPc9QVH2H45FnWIYj8xgJA8Se5xwHkrGNQZDzJpX8pCVgAKPakbPIyALOtc1BEqEy1jkAUBBYASgh90joBRYX8xzoBURBYACgZlAWAOkMbpXzSh9YpiILAAkDJUNT2PIA11jEo7BwWRUoMFgBKBCmtawXwrHUOCjl1+fyfEoMFgJJD9d/WESjUsmjGM9YhiILCAkDJkUrx9i5tyosyqpazRlJisABQcuw5aS4ALu9KG8Pb/5QoLACUGB2TuzxlnYPCSnj7nxKFBYCSRTDdOgKFUg7p5petQxAFiQWAEsZ9wToBhVKDlNattg5BFCQWAEqWJR/VA+CJntbHYkiJwwJAiSJfm5YFMNM6B4WMKgsAJQ4LACWQ8j0AWlcqzQJAicMCQAnk8GRPX7RQht/1tnUIoqCxAFDytGVfALjeO32Gt/8pmVgAKHFk5JTFAN6yzkEhIcICQInEAkBJxZM+deDQUEomFgBKqhetA1AouEi1zbIOQWSBBYCSSZw51hEoFN7mBECUVCwAlEyqDQBc6xhkrt46AJEVFgBKJMlUr4HiHescZEx1rnUEIissAJRcwk9/iedIg3UEIissAJRc7Y8BKMlUWAIpsVgAKLkc5ck/2VahrHq+dQgiKywAlFy5YhaAZGsQ4YyQlFwsAJRc5RPfA7DcOgaZ4SMgSjQWAEqsjk9/r1jnICPKFwAp2VgAKNmU7wEkVoovgVKysQBQwgnHgSdVNj3POgKRJRYASjaRN60jkIlFMmIi3/+gRGMBoGRLC5cFTiTlf3dKPBYASrY9dloAYK11DAocCwAlHgsAJZrIeBeCd61zUNDkbesERNZYAIhU+R5A0gjvABCxABDB4cUgaVyXpY8SjwWASPlpMHGK2vjfnBKPBYBI+GkwYZZJad1S6xBE1lgAiHIcEpYsnPuBCGABIAJG7PYBOBQwSVj4iMACQMShgEkjyiGARGABIPoMC0BSqLxnHYEoDNLWAYiCUlFRkZq/0h0GV7ZOOSpf/LNH/pvFln1lY5tSjDz3mrvFqCOOPuyLX3PVdVWcT4f0cV6rq6vLWWUjChLPeBR7Bx11VP+2tuILIfITKAZZ56FQWwjVW9dKrysbnqheYx2GyE8sABRr+x7xg4yoPAxgsHUWig4BXtEUvvnSow8ssM5C5BcWAIqtA771w6/ksngZwJbWWSiKZI6zyjnghRfqOEKEYokvAVJs5bJyC3jxp7xpudvbPds6BZFfWAAolkZ+4wd7AvpN6xwUdXoWeKeUYooFgGIpLTjKOgPFwjb7H/a9Pa1DEPmBBYDiyXWGWEegeMilUjtYZyDyAwsAxZRmrRNQPIjrtFlnIPIDCwDFkuvI69YZKB5S6SynDqZYYgGgWBJHHwTAT25UqMbpjz7IAkCxxAJAsdQxgctN1jko2kTkt9YZiPzCAkCxNSC19lcKPG2dg6JJBbe/+Pj991rnIPILCwDF1qOPPtrSo7XPkRC5CQBfCqTOahXgspf3LzvFOgiRnzjBBSXCfocfvSscHAfV/QBsCciXf/YdZ0sRh2sGxFku+4oCLV/+A1UACwUyHU6u9sXHHpwfdDSioLEAEHU4+MfjzoHKtdY5yD850V2m3zOBL/URgY8AiIiIEokFgIiIKIFYAIiIiBKIBYCIiCiBWACIiIgSiAWAiIgogVgAiIiIEogFgIiIKIFYAIiIiBKIBYCIiCiBOBUwJcb+R1TsklP3QKhu5Tj6pZ99KSo5yEkVfdsiGwUj29J0lai7ZP2vu64oHP0Emn725Sfq3rXIRhQ0FgCKvX0PP3qkiP4ZwCHWWSgCBI/nJHX2jMfqXreOQuQnFgCKtf0OP7pKRW8XoMQ6C0XKCkf1ey88+fenrYMQ+YUFgGJrn8N/dKgj7mMA0tZZKJKWuMiN+O8T//jAOgiRH/gSIMXS3nuPK3LEvRW8+FP+BjpIXWEdgsgvLAAUS6mBi44AsIt1Doo2BX584He/29s6B5EfWAAolhyRg60zUPQJUNLanB5hnYPIDywAFE+KgdYRKB4c0f7WGYj8wAJA8ST41DoCxYOjqUXWGYj8wAJAsSTQZ60zUCysbS1pmWMdgsgPLAAUS62Lt5iqwNvWOSjiFPfNfPjhJusYRH5gAaBYmjlzQpuonAXAtc5CUaUrVVKXWqcg8kvKOgCRXz5859U3B+88bDkgR4CTXlEXKNDiwDnupSfue8k6C5FfWAAo1ha8/dpLg3ce+l9A9gU4MoA6Q+bC0R+99PgDU62TEPmJn4ooEUaPHp1eUzToqyLuQY7K1ir6pcdfTio9HKn0/hb5KBhutvVuuO7K9b8uKq46+FhEnhncW56tq6vLWeQjChILAFGHg3887hyoXGudg/yTE91l+j0T+HIoEfgSIBERUSKxABARESUQCwAREVECsQAQERElEAsAERFRArEAEBERJRALABERUQKxABARESUQCwAREVECsQAQERElEKcCpkQ46Kij+re2dfsuoPs5IoM29D2SSu0KJ1UedDYKjra1PQro6g39mau6WFSmI9300EuPPvql9QKI4oYFgOJORh3xg1+q4iJA+liHoUhYAsgvX3ri/jutgxD5icsBU3yNH+/sVzxgCiDnAFJiHYcioweA72238+748O1Xn7YOQ+QXFgCKrf2KBlwMwZnWOSiaBPjq9jsPm/7h26+9Y52FyA98CZBiae/R3xkEwa+sc1CkCSCXW4cg8gsLAMVSUVHR99F+K5eoEPvu980fbm8dgsgPLAAUSyoy3DoDxYKIK8OsQxD5gQWA4qq7dQCKCeXPEsUTCwDFkkDft85AMSH6oXUEIj+wAFAsKdxHrTNQDAgWD+6TqreOQeQHFgCKpZeeeHAmBI9b56Cokxvq6upy1imI/MACQLGlmjoNwKfWOSiaVDA7W9x6tXUOIr+wAFBsvfxE3bvi6tcAvGadhSLnBU2lvjXz4YebrIMQ+YUFgGLtxX///dUBqbXlUP05gJcAuNaZKLRcBaYrcPIOfVMH//dfdZ9YByLyExcDokSpqKhIvbMyt6WTk40O7Srq02+848iYIHORv9xc20ltq1ZvdF5/N6Vrd+qTWsjn/ZQkLABE6znkx6f8UBX3W+cgz7S5zW0Dn3/ozlXWQYjChI8AiNbTrbjpcQAbXDOeIulJXvyJvowFgGg9T1RXr4Ho361zkDcUqLHOQBRGLABEG+C4Um2dgTyxpkdJ00PWIYjCiAWAaAOeLt1mKoAPrHNQoeS+J6qr11inIAojFgCiDRk/3hXRSdYxqDDqYKJ1BqKwYgEg2gjHwXUA+Okxul5+7u5bp1mHIAorFgCijZg2ZcJigdxhnYPyo8AV1hmIwowFgGgTnGz2KgCt1jmoixSNz+2xzSPWMYjCLGUdgCjM5r86a+WQPUcOBrC3dRbqPAHOeu/mP79inYMozHgHgGgzWpzcbwAst85BnaQy/Zm62+61jkEUdiwARJvx0pTbP4XgN9Y5qFNyksLPAah1EKKwYwEg6oSt3aU3QzHbOgdt1s3P3H0r/zsRdQILAFEn1NXV5VzHOR1cTji0FPg01drCOzVEncSXAIk66YPGGQuG7Ll3MSCHWGehL1FHncqnH7i93joIUVTwDgBRFzyz+7a/AfAf6xy0HsHVz9Tdwjn/ibqABYCoK8aPd7MqYwEsto5Cn5H/LnWXXmydgihqWACIuuiFuls/VMUYADnrLIQlKQfHNNbVcbImoi7iOwBEeXh/3sy3dthj5Cci+I51lgRbq6pHPXPvbQ3WQYiiiAWAKE/vz5s5c4fSvSGQ0dZZEiinKsc+V3fbE9ZBiKKKBYCoAO83znx6SOk+AwDsZ50lQVQU456tu22ydRCiKOM7AEQF2kqX/J8KuGpgMFQU5z5Tdxv/fRMVSKwDEMWEHHLMqZcq9FLrIDGWVcgpz9176wgWYhIAAAIFSURBVJ3WQYjigI8AiDzyXuOMaV/Zc+RSAEeA5dprTQL88Nl7ucgPkVdYAIg89H7jzJeH7DHydQiOBFBsnScmFqijRz17z4Rp1kGI4oSfUoh8cGDFaUMd5O6FSJl1lkhTTG1N5ypfmnL7p9ZRiOKGdwCIfPDBvBlLdtp3+ES0FfUBRwjkIyeQy58t3ebkD2+8ZrV1GKI44h0AIp8d9ONxFaK4DpBtrbNExFxx3FOfufuv062DEMUZ7wAQ+ez9xpnzhu21x225XFEKwChw+O3GNAnk90t16ZgZ91bPtw5DFHe8A0AUoAMqflaeEucGAAdZZwkRFch9cJzznrn75g+swxAlBQsAkYGDKsYdJJALIPi2dRZDLhT/gqOXPnvPhFnWYYiShgWAyNDBx5x6IKAXATgSyXk00ALI3a7KH56vu+V16zBEScUCQBQC+1ecul0R9Ecq+AmAjHUen8wURbWT1tppUyYstg5DlHQsAEQhc3DFqfvC0R9BcSiAckT3zkALIC+q4sk0nHum1d38lnUgIvofFgCiEDv0B2cMbC3KjRbo11WxNwRDAfSzzrURHyvwGkReSrn6n9XN+vzMhyc0WYciog1jASCKmAN+cOqWqXRud4Uz1IEOgEg/AL1VtLeo083fo7urAGc1xF3lqqxyRBYIcm80p7u9/lLtDSv9PTYReen/AbeNKKToOb16AAAAAElFTkSuQmCC";
6019
+
6020
+ // src/components/BindingBuilder/components/LightHelpers.tsx
5886
6021
  import { Fragment as Fragment7, jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
5887
6022
  var POSITION_MIN = -100;
5888
6023
  var POSITION_MAX = 100;
5889
6024
  var SUN_SCREEN_SIZE = 0.03;
5890
6025
  var BULB_SCREEN_SIZE = 0.03;
5891
6026
  var MIN_LINE_DISTANCE = 0.5;
5892
- var SUN_ICON_PATH = "/sun.png";
5893
- var BULB_ICON_PATH = "/bulb.png";
6027
+ var SUN_ICON_PATH = sun_default;
6028
+ var BULB_ICON_PATH = bulb_default;
5894
6029
  var RING_RADIUS = 0.4;
5895
6030
  var RING_SEGMENTS = 64;
5896
6031
  var RING_SCREEN_SIZE = SUN_SCREEN_SIZE * 3;
5897
6032
  var RING_COLOR = "#4d7dff";
5898
6033
  var LINE_COLOR2 = "#ffcc00";
5899
6034
  var LINE_GLOW_RADIUS = 0.02;
5900
- var UP_AXIS = new Vector37(0, 1, 0);
6035
+ var UP_AXIS = new Vector38(0, 1, 0);
5901
6036
  var AXIS_ARROW_LENGTH = 1.4;
5902
6037
  var AXIS_CONE_LENGTH = 0.35;
5903
6038
  var AXIS_CONE_RADIUS = 0.13;
@@ -5909,17 +6044,17 @@ var AXIS_COLORS = {
5909
6044
  z: "#4444ff"
5910
6045
  };
5911
6046
  function axisVector(axis) {
5912
- return new Vector37(axis === "x" ? 1 : 0, axis === "y" ? 1 : 0, axis === "z" ? 1 : 0);
6047
+ return new Vector38(axis === "x" ? 1 : 0, axis === "y" ? 1 : 0, axis === "z" ? 1 : 0);
5913
6048
  }
5914
6049
  function computeAxisDragPlaneNormal(axis, eye) {
5915
6050
  const axisVec = axisVector(axis);
5916
6051
  if (eye.lengthSq() < 1e-8) {
5917
6052
  eye = axisVec.clone();
5918
6053
  }
5919
- let normal = new Vector37().crossVectors(axisVec, new Vector37().crossVectors(eye, axisVec));
6054
+ let normal = new Vector38().crossVectors(axisVec, new Vector38().crossVectors(eye, axisVec));
5920
6055
  if (normal.lengthSq() < 1e-8) {
5921
- const helper = Math.abs(axisVec.y) < 0.9 ? UP_AXIS : new Vector37(1, 0, 0);
5922
- normal = new Vector37().crossVectors(axisVec, helper);
6056
+ const helper = Math.abs(axisVec.y) < 0.9 ? UP_AXIS : new Vector38(1, 0, 0);
6057
+ normal = new Vector38().crossVectors(axisVec, helper);
5923
6058
  }
5924
6059
  return normal.normalize();
5925
6060
  }
@@ -5992,17 +6127,17 @@ function LightHelpers({
5992
6127
  const shaftZRef = useRef11(null);
5993
6128
  const axisDragRef = useRef11({ dragging: false, axis: null });
5994
6129
  const axisDragPlaneRef = useRef11(new Plane());
5995
- const dragStartPosRef = useRef11(new Vector37());
6130
+ const dragStartPosRef = useRef11(new Vector38());
5996
6131
  const dragLineRef = useRef11(null);
5997
6132
  const dragLineGeomRef = useRef11(null);
5998
- const currentPosRef = useRef11(new Vector37());
6133
+ const currentPosRef = useRef11(new Vector38());
5999
6134
  const isDraggingRef = useRef11(false);
6000
6135
  const dragPlaneRef = useRef11(new Plane());
6001
6136
  const onDraggingChangeRef = useRef11(onDraggingChange);
6002
6137
  useEffect15(() => {
6003
6138
  onDraggingChangeRef.current = onDraggingChange;
6004
6139
  }, [onDraggingChange]);
6005
- const lightPos = useMemo13(() => new Vector37(position[0], position[1], position[2]), [position]);
6140
+ const lightPos = useMemo13(() => new Vector38(position[0], position[1], position[2]), [position]);
6006
6141
  const linePositions = useMemo13(() => new Float32Array(6), []);
6007
6142
  const dragLinePositions = useMemo13(() => new Float32Array(6), []);
6008
6143
  const ringXPositions = useMemo13(() => makeRingPositions("x"), []);
@@ -6161,12 +6296,12 @@ function LightHelpers({
6161
6296
  const axis = axisDragRef.current.axis;
6162
6297
  const currentPos = currentPosRef.current;
6163
6298
  const axisVec = axisVector(axis);
6164
- const eye = new Vector37().subVectors(camera.position, currentPos);
6299
+ const eye = new Vector38().subVectors(camera.position, currentPos);
6165
6300
  const planeNormal = computeAxisDragPlaneNormal(axis, eye);
6166
6301
  axisDragPlaneRef.current.setFromNormalAndCoplanarPoint(planeNormal, currentPos);
6167
6302
  const mouse2 = getMouseNdc(event);
6168
6303
  raycaster.setFromCamera(mouse2, camera);
6169
- const intersection2 = new Vector37();
6304
+ const intersection2 = new Vector38();
6170
6305
  const hitPoint2 = raycaster.ray.intersectPlane(axisDragPlaneRef.current, intersection2);
6171
6306
  if (!hitPoint2) return;
6172
6307
  const toPoint = hitPoint2.clone().sub(currentPos);
@@ -6185,7 +6320,7 @@ function LightHelpers({
6185
6320
  if (!isDraggingRef.current) return;
6186
6321
  const mouse = getMouseNdc(event);
6187
6322
  raycaster.setFromCamera(mouse, camera);
6188
- const intersection = new Vector37();
6323
+ const intersection = new Vector38();
6189
6324
  const hitPoint = raycaster.ray.intersectPlane(dragPlaneRef.current, intersection);
6190
6325
  if (!hitPoint) return;
6191
6326
  const newPos = [
@@ -6232,7 +6367,7 @@ function LightHelpers({
6232
6367
  setActiveTab?.("scene");
6233
6368
  setActiveSubTab?.("Lighting");
6234
6369
  }
6235
- const cameraDir = new Vector37();
6370
+ const cameraDir = new Vector38();
6236
6371
  camera.getWorldDirection(cameraDir);
6237
6372
  dragPlaneRef.current.setFromNormalAndCoplanarPoint(cameraDir, currentPosRef.current);
6238
6373
  isDraggingRef.current = true;
@@ -6365,17 +6500,17 @@ function LightHelpers({
6365
6500
  ] }),
6366
6501
  gizmoVisible && /* @__PURE__ */ jsx19("group", { ref: axisGroupRef, children: axisArrows.map((axis) => {
6367
6502
  const axisColor = AXIS_COLORS[axis];
6368
- const end = new Vector37(
6503
+ const end = new Vector38(
6369
6504
  axis === "x" ? AXIS_ARROW_LENGTH : 0,
6370
6505
  axis === "y" ? AXIS_ARROW_LENGTH : 0,
6371
6506
  axis === "z" ? AXIS_ARROW_LENGTH : 0
6372
6507
  );
6373
- const conePos = new Vector37(
6508
+ const conePos = new Vector38(
6374
6509
  axis === "x" ? AXIS_ARROW_LENGTH - AXIS_CONE_LENGTH / 2 : 0,
6375
6510
  axis === "y" ? AXIS_ARROW_LENGTH - AXIS_CONE_LENGTH / 2 : 0,
6376
6511
  axis === "z" ? AXIS_ARROW_LENGTH - AXIS_CONE_LENGTH / 2 : 0
6377
6512
  );
6378
- const shaftPos = new Vector37(
6513
+ const shaftPos = new Vector38(
6379
6514
  axis === "x" ? AXIS_ARROW_LENGTH / 2 : 0,
6380
6515
  axis === "y" ? AXIS_ARROW_LENGTH / 2 : 0,
6381
6516
  axis === "z" ? AXIS_ARROW_LENGTH / 2 : 0
@@ -6387,7 +6522,7 @@ function LightHelpers({
6387
6522
  /* @__PURE__ */ jsx19(
6388
6523
  Line2,
6389
6524
  {
6390
- points: [new Vector37(0, 0, 0), end],
6525
+ points: [new Vector38(0, 0, 0), end],
6391
6526
  color: axisColor,
6392
6527
  transparent: true,
6393
6528
  opacity: 0.9,
@@ -6476,7 +6611,7 @@ function findInfluencingBone(hit) {
6476
6611
  if (!skinIndex || !skinWeight || !position) return null;
6477
6612
  const localHit = mesh.worldToLocal(hit.point.clone());
6478
6613
  const vertices = [hit.face.a, hit.face.b, hit.face.c];
6479
- const vertex = new Vector38();
6614
+ const vertex = new Vector39();
6480
6615
  let nearest = vertices[0];
6481
6616
  let nearestDist = Infinity;
6482
6617
  for (const vi of vertices) {
@@ -6526,9 +6661,9 @@ function GuidedTourCameraFollow({
6526
6661
  const scene = useThree11((state) => state.scene);
6527
6662
  const nodeRef = useRef12(null);
6528
6663
  const hasPrev = useRef12(false);
6529
- const live = useMemo14(() => new Vector38(), []);
6530
- const prev = useMemo14(() => new Vector38(), []);
6531
- const goal = useMemo14(() => new Vector38(), []);
6664
+ const live = useMemo14(() => new Vector39(), []);
6665
+ const prev = useMemo14(() => new Vector39(), []);
6666
+ const goal = useMemo14(() => new Vector39(), []);
6532
6667
  const nodeName = annotation?.anchorNodeName;
6533
6668
  const localPosition = annotation?.anchorLocalPosition;
6534
6669
  useEffect16(() => {
@@ -6568,7 +6703,7 @@ var DEFAULT_AR_OVERLAY_STATE = {
6568
6703
  };
6569
6704
  function computeMeasureBox(scene) {
6570
6705
  const box = new Box34().setFromObject(scene);
6571
- const size = new Vector38();
6706
+ const size = new Vector39();
6572
6707
  box.getSize(size);
6573
6708
  return {
6574
6709
  min: [box.min.x, box.min.y, box.min.z],
@@ -6589,8 +6724,8 @@ function computeUvMeshStats(scene) {
6589
6724
  function computeModelBounds(scene) {
6590
6725
  const box = new Box34().setFromObject(scene);
6591
6726
  if (box.isEmpty()) return null;
6592
- const center = box.getCenter(new Vector38());
6593
- const radius = box.getSize(new Vector38()).length() / 2;
6727
+ const center = box.getCenter(new Vector39());
6728
+ const radius = box.getSize(new Vector39()).length() / 2;
6594
6729
  if (!Number.isFinite(radius) || radius <= 0) return null;
6595
6730
  return { center: [center.x, center.y, center.z], radius };
6596
6731
  }
@@ -6598,7 +6733,7 @@ function fitClippingPlanesToScene(scene, controls) {
6598
6733
  const camera = controls?.camera;
6599
6734
  if (!(camera instanceof PerspectiveCamera3)) return;
6600
6735
  const box = new Box34().setFromObject(scene);
6601
- const size = box.getSize(new Vector38()).length();
6736
+ const size = box.getSize(new Vector39()).length();
6602
6737
  if (!Number.isFinite(size) || size <= 0) return;
6603
6738
  camera.near = size / 100;
6604
6739
  camera.far = size * 100;
@@ -6676,7 +6811,8 @@ function ModelViewer({
6676
6811
  showAnnotationNavigation = true,
6677
6812
  showAnnotationOnHover = false
6678
6813
  }) {
6679
- const { activeTab, activeSubTab } = useBindingBuilderContext();
6814
+ const { isInsideEditor, activeTab, activeSubTab } = useBindingBuilderContext();
6815
+ const showLightHelpers = isInsideEditor && activeTab === "scene" && activeSubTab === "Lighting";
6680
6816
  const license = useLicense(licenseKey);
6681
6817
  const resolvedSceneConfig = useMemo14(
6682
6818
  () => sceneConfig ?? DEFAULT_SCENE_CONFIG,
@@ -6782,8 +6918,8 @@ function ModelViewer({
6782
6918
  if (!controls || initialCameraStateRef.current) {
6783
6919
  return;
6784
6920
  }
6785
- const position = controls.getPosition(new Vector38());
6786
- const target = controls.getTarget(new Vector38());
6921
+ const position = controls.getPosition(new Vector39());
6922
+ const target = controls.getTarget(new Vector39());
6787
6923
  initialCameraStateRef.current = {
6788
6924
  position: [position.x, position.y, position.z],
6789
6925
  target: [target.x, target.y, target.z]
@@ -7041,7 +7177,7 @@ function ModelViewer({
7041
7177
  const controls = controlsRef.current;
7042
7178
  const scene = sceneRef.current;
7043
7179
  if (!controls) return;
7044
- const target = new Vector38();
7180
+ const target = new Vector39();
7045
7181
  const livePosition = annotationWorldPositionsRef.current.get(
7046
7182
  annotation.id
7047
7183
  );
@@ -7060,14 +7196,14 @@ function ModelViewer({
7060
7196
  const storedNormal = annotation.worldNormal;
7061
7197
  if (scene) {
7062
7198
  const box = new Box34().setFromObject(scene);
7063
- const modelCenter = new Vector38();
7199
+ const modelCenter = new Vector39();
7064
7200
  box.getCenter(modelCenter);
7065
- const modelSize = new Vector38();
7201
+ const modelSize = new Vector39();
7066
7202
  box.getSize(modelSize);
7067
7203
  const modelRadius = modelSize.length() * 0.2;
7068
7204
  let outwardDir;
7069
7205
  if (storedNormal) {
7070
- outwardDir = new Vector38(
7206
+ outwardDir = new Vector39(
7071
7207
  storedNormal[0],
7072
7208
  storedNormal[1],
7073
7209
  storedNormal[2]
@@ -7218,6 +7354,16 @@ function ModelViewer({
7218
7354
  const isControlledBindings = onObjectBindingsChange !== void 0;
7219
7355
  const currentObjectBindings = isControlledBindings ? objectBindings : internalObjectBindings;
7220
7356
  currentObjectBindingsRef.current = currentObjectBindings;
7357
+ const currentBindingTransformSignature = useMemo14(
7358
+ () => Object.entries(currentObjectBindings).map(
7359
+ ([bindingKey, binding]) => JSON.stringify([
7360
+ bindingKey,
7361
+ binding.modelObjectId,
7362
+ binding.transform ?? null
7363
+ ])
7364
+ ).sort().join(""),
7365
+ [currentObjectBindings]
7366
+ );
7221
7367
  const currentHiddenObjects = useMemo14(
7222
7368
  () => Object.fromEntries(
7223
7369
  Object.entries(currentObjectBindings).filter(([, binding]) => binding.visible === false).map(([bindingKey]) => [bindingKey, true])
@@ -7357,7 +7503,7 @@ function ModelViewer({
7357
7503
  const fallbackY = Math.max(minOffset, (rect.height - height) / 2);
7358
7504
  const objectKey = resolveObjectKey(objectId, binding);
7359
7505
  const object = objectKey ? nodeRefsMap.current[objectKey] : null;
7360
- const clamp2 = (value, max) => {
7506
+ const clamp3 = (value, max) => {
7361
7507
  if (max <= minOffset) {
7362
7508
  return minOffset;
7363
7509
  }
@@ -7365,23 +7511,23 @@ function ModelViewer({
7365
7511
  };
7366
7512
  if (canvasContainer && cameraControls && object) {
7367
7513
  const canvasRect = canvasContainer.getBoundingClientRect();
7368
- const objectCenter = new Vector38();
7514
+ const objectCenter = new Vector39();
7369
7515
  new Box34().setFromObject(object).getCenter(objectCenter);
7370
7516
  objectCenter.project(cameraControls);
7371
7517
  const projectedX = (objectCenter.x + 1) / 2 * canvasRect.width + (canvasRect.left - rect.left);
7372
7518
  const projectedY = (1 - objectCenter.y) / 2 * canvasRect.height + (canvasRect.top - rect.top);
7373
7519
  return {
7374
7520
  objectId,
7375
- x: clamp2(projectedX + 24, maxX),
7376
- y: clamp2(projectedY - height / 2, maxY)
7521
+ x: clamp3(projectedX + 24, maxX),
7522
+ y: clamp3(projectedY - height / 2, maxY)
7377
7523
  };
7378
7524
  }
7379
7525
  const rawX = typeof fallbackCoords?.screenX === "number" ? fallbackCoords.screenX - rect.left : fallbackX;
7380
7526
  const rawY = typeof fallbackCoords?.screenY === "number" ? fallbackCoords.screenY - rect.top : fallbackY;
7381
7527
  return {
7382
7528
  objectId,
7383
- x: clamp2(rawX, maxX),
7384
- y: clamp2(rawY, maxY)
7529
+ x: clamp3(rawX, maxX),
7530
+ y: clamp3(rawY, maxY)
7385
7531
  };
7386
7532
  },
7387
7533
  [resolveObjectKey]
@@ -7390,7 +7536,6 @@ function ModelViewer({
7390
7536
  (event) => {
7391
7537
  const { objectId, action, binding } = event;
7392
7538
  setCurrentAction(event);
7393
- onAction?.(event);
7394
7539
  switch (action.id) {
7395
7540
  case "toggle-visibility": {
7396
7541
  let targetKey = objectId;
@@ -7438,6 +7583,7 @@ function ModelViewer({
7438
7583
  default:
7439
7584
  break;
7440
7585
  }
7586
+ onAction?.(event);
7441
7587
  },
7442
7588
  [
7443
7589
  applyObjectBindings,
@@ -7578,7 +7724,8 @@ function ModelViewer({
7578
7724
  objectBindings: currentObjectBindingsRef.current,
7579
7725
  nodeRefs: nodeRefsMap.current,
7580
7726
  homeCameraState: initialCameraStateRef.current,
7581
- captureImage: (options) => captureImage(rendererCaptureRef.current, options)
7727
+ captureImage: (options) => captureImage(rendererCaptureRef.current, options),
7728
+ invalidate: () => rendererCaptureRef.current?.invalidate()
7582
7729
  });
7583
7730
  }, []);
7584
7731
  const fitScene = useCallback4(
@@ -7590,8 +7737,8 @@ function ModelViewer({
7590
7737
  }
7591
7738
  try {
7592
7739
  if (flyIn) {
7593
- const target = controls.getTarget(new Vector38());
7594
- const position = controls.getPosition(new Vector38());
7740
+ const target = controls.getTarget(new Vector39());
7741
+ const position = controls.getPosition(new Vector39());
7595
7742
  const direction = position.clone().sub(target);
7596
7743
  if (direction.lengthSq() < 1e-6) {
7597
7744
  direction.set(0, 0, 1);
@@ -7909,6 +8056,13 @@ function ModelViewer({
7909
8056
  );
7910
8057
  setBindingsMismatch(!hasAnyMatch);
7911
8058
  }, [modelLoaded, currentObjectBindings]);
8059
+ useEffect16(() => {
8060
+ const scene = sceneRef.current;
8061
+ if (!modelLoaded || !scene) return;
8062
+ scene.updateMatrixWorld(true);
8063
+ setMeasureBox(computeMeasureBox(scene));
8064
+ setModelBounds(computeModelBounds(scene));
8065
+ }, [modelLoaded, currentBindingTransformSignature]);
7912
8066
  const clearMeasurements = useCallback4(() => setMeasurePoints([]), []);
7913
8067
  const handleMeasurePointerDown = useCallback4(
7914
8068
  (event) => {
@@ -7952,7 +8106,11 @@ function ModelViewer({
7952
8106
  );
7953
8107
  useEffect16(() => {
7954
8108
  setMeasurePoints([]);
7955
- }, [modelUrl, resolvedSceneConfig.model.rotation]);
8109
+ }, [
8110
+ modelUrl,
8111
+ resolvedSceneConfig.model.rotation,
8112
+ currentBindingTransformSignature
8113
+ ]);
7956
8114
  const handleDownloadMesh = useCallback4(() => {
7957
8115
  const matRefs = materialRefsOut.current;
7958
8116
  const scene = sceneRef.current;
@@ -8295,33 +8453,28 @@ function ModelViewer({
8295
8453
  shadowsEnabled: shadows,
8296
8454
  environmentRotation: resolvedSceneConfig.environment.rotation,
8297
8455
  environmentConfig: resolvedSceneConfig.environment,
8298
- modelBounds
8456
+ modelBounds,
8457
+ objectBindings: currentObjectBindings,
8458
+ nodeRefs: nodeRefsMap
8299
8459
  }
8300
8460
  ),
8301
- (() => {
8302
- const currentConfig = sceneConfig || DEFAULT_SCENE_CONFIG;
8303
- const activeLights = currentConfig.lighting.lights.filter(
8304
- (light) => light.visible === true
8461
+ showLightHelpers && resolvedSceneConfig.lighting.lights.map((light) => {
8462
+ if (!light.visible || light.type === "none" || light.type === "hemisphere" || light.attachment) {
8463
+ return null;
8464
+ }
8465
+ return /* @__PURE__ */ jsx20(
8466
+ LightHelpers,
8467
+ {
8468
+ light,
8469
+ position: light.position,
8470
+ color: light.color,
8471
+ intensity: light.intensity,
8472
+ visible: light.visible,
8473
+ onPositionChange: (newPosition) => handleLightPositionChange(light.id, newPosition)
8474
+ },
8475
+ light.id
8305
8476
  );
8306
- return activeLights.map((light) => {
8307
- if (light.type === "none" || light.type === "hemisphere") {
8308
- return null;
8309
- }
8310
- const uniqueKey = light.id;
8311
- return /* @__PURE__ */ jsx20(
8312
- LightHelpers,
8313
- {
8314
- light,
8315
- position: light.position,
8316
- color: light.color,
8317
- intensity: light.intensity,
8318
- visible: light.visible,
8319
- onPositionChange: (newPos) => handleLightPositionChange(light.id, newPos)
8320
- },
8321
- uniqueKey
8322
- );
8323
- });
8324
- })()
8477
+ })
8325
8478
  ] }),
8326
8479
  /* @__PURE__ */ jsx20(
8327
8480
  Controls,
@@ -8948,7 +9101,7 @@ function ModelViewer({
8948
9101
  }
8949
9102
 
8950
9103
  // src/components/BindingBuilder/BindingBuilder.tsx
8951
- import { useCallback as useCallback9, useEffect as useEffect22, useMemo as useMemo17, useRef as useRef19, useState as useState26 } from "react";
9104
+ import { useCallback as useCallback9, useEffect as useEffect23, useMemo as useMemo17, useRef as useRef19, useState as useState27 } from "react";
8952
9105
  import {
8953
9106
  Redo2,
8954
9107
  Undo2,
@@ -8963,32 +9116,13 @@ styleInject('.bb-root {\n --bb-font-family:\n Inter,\n "Segoe UI",\n H
8963
9116
 
8964
9117
  // src/hooks/useViewerCamera.ts
8965
9118
  import { useCallback as useCallback5, useRef as useRef13, useState as useState11 } from "react";
8966
- import { Vector3 as Vector39 } from "three";
8967
-
8968
- // src/utils/resolveBindingKey.ts
8969
- function resolveBindingKey(objectBindings, objectId) {
8970
- if (objectBindings[objectId]) {
8971
- return objectId;
8972
- }
8973
- const idMatch = Object.entries(objectBindings).find(
8974
- ([, binding]) => binding.id === objectId
8975
- )?.[0];
8976
- if (idMatch) {
8977
- return idMatch;
8978
- }
8979
- const modelObjectMatch = Object.entries(objectBindings).find(
8980
- ([, binding]) => binding.modelObjectId === objectId
8981
- )?.[0];
8982
- return modelObjectMatch ?? null;
8983
- }
8984
-
8985
- // src/hooks/useViewerCamera.ts
9119
+ import { Vector3 as Vector310 } from "three";
8986
9120
  function toTuple(vector) {
8987
9121
  return [vector.x, vector.y, vector.z];
8988
9122
  }
8989
9123
  function buildCameraState(camera, controls) {
8990
- const position = new Vector39();
8991
- const target = new Vector39();
9124
+ const position = new Vector310();
9125
+ const target = new Vector310();
8992
9126
  controls.getPosition(position);
8993
9127
  controls.getTarget(target);
8994
9128
  return {
@@ -9015,11 +9149,16 @@ async function runSmoothCameraTransition2(controls, action, smoothTime = 0.6) {
9015
9149
  controls.smoothTime = originalSmoothTime;
9016
9150
  }
9017
9151
  }
9018
- function useViewerCamera() {
9152
+ function useViewerCamera(options = {}) {
9153
+ const optionsRef = useRef13(options);
9154
+ optionsRef.current = options;
9019
9155
  const viewerRef = useRef13(null);
9020
9156
  const [cameraState, setCameraStateValue] = useState11(
9021
9157
  null
9022
9158
  );
9159
+ const [cameraPresets, setCameraPresets] = useState11(() => ({ ...options.presets }));
9160
+ const cameraPresetsRef = useRef13(cameraPresets);
9161
+ cameraPresetsRef.current = cameraPresets;
9023
9162
  const updateCameraState = useCallback5((nextState) => {
9024
9163
  setCameraStateValue(
9025
9164
  (previousState) => isSameCameraState(previousState, nextState) ? previousState : nextState
@@ -9033,16 +9172,56 @@ function useViewerCamera() {
9033
9172
  );
9034
9173
  const homeCameraStateRef = useRef13(null);
9035
9174
  const homeSceneRef = useRef13(null);
9175
+ const initializedControlsRef = useRef13(null);
9036
9176
  const handleViewerReady = useCallback5((viewer) => {
9037
9177
  viewerRef.current = viewer;
9038
- updateCameraState(buildCameraState(viewer.controls.camera, viewer.controls));
9178
+ const currentState = buildCameraState(
9179
+ viewer.controls.camera,
9180
+ viewer.controls
9181
+ );
9182
+ updateCameraState(currentState);
9039
9183
  if (viewer.scene !== homeSceneRef.current) {
9040
9184
  homeSceneRef.current = viewer.scene;
9041
9185
  homeCameraStateRef.current = null;
9186
+ initializedControlsRef.current = null;
9042
9187
  }
9043
9188
  if (viewer.homeCameraState && !homeCameraStateRef.current) {
9044
9189
  homeCameraStateRef.current = viewer.homeCameraState;
9045
9190
  }
9191
+ const { initialPosition, initialTarget, initialTransition = false } = optionsRef.current;
9192
+ if ((initialPosition || initialTarget) && viewer.scene && // A target-only setup should preserve ModelViewer's fitted or authored
9193
+ // camera position. Wait until that initial framing has settled instead
9194
+ // of cancelling auto-fit while the camera is still at its fallback.
9195
+ (initialPosition || viewer.homeCameraState) && initializedControlsRef.current !== viewer.controls) {
9196
+ initializedControlsRef.current = viewer.controls;
9197
+ if (!viewer.homeCameraState) {
9198
+ markInitialAutoFitOverride(viewer.controls);
9199
+ }
9200
+ const position = initialPosition ?? currentState.position;
9201
+ const target = initialTarget ?? currentState.target;
9202
+ void viewer.controls.setLookAt(
9203
+ position[0],
9204
+ position[1],
9205
+ position[2],
9206
+ target[0],
9207
+ target[1],
9208
+ target[2],
9209
+ initialTransition
9210
+ ).then(() => {
9211
+ if (viewerRef.current?.controls !== viewer.controls) {
9212
+ return;
9213
+ }
9214
+ const nextState = buildCameraState(
9215
+ viewer.controls.camera,
9216
+ viewer.controls
9217
+ );
9218
+ homeCameraStateRef.current = {
9219
+ position: nextState.position,
9220
+ target: nextState.target
9221
+ };
9222
+ updateCameraState(nextState);
9223
+ }).catch(() => void 0);
9224
+ }
9046
9225
  }, [updateCameraState]);
9047
9226
  const resetView = useCallback5(async () => {
9048
9227
  const viewer = viewerRef.current;
@@ -9138,27 +9317,33 @@ function useViewerCamera() {
9138
9317
  return false;
9139
9318
  }
9140
9319
  try {
9320
+ if (!viewerRef.current?.homeCameraState) {
9321
+ markInitialAutoFitOverride(controls);
9322
+ }
9141
9323
  await controls.setTarget(
9142
9324
  target[0],
9143
9325
  target[1],
9144
9326
  target[2],
9145
9327
  enableTransition
9146
9328
  );
9329
+ updateCameraState(buildCameraState(controls.camera, controls));
9147
9330
  return true;
9148
9331
  } catch {
9149
9332
  return false;
9150
9333
  }
9151
9334
  },
9152
- []
9335
+ [updateCameraState]
9153
9336
  );
9154
- const setCameraState = useCallback5(
9337
+ const applyCameraPreset = useCallback5(
9155
9338
  async (state, enableTransition = true) => {
9156
9339
  const controls = viewerRef.current?.controls;
9157
9340
  if (!controls) {
9158
9341
  return false;
9159
9342
  }
9160
9343
  try {
9161
- markInitialAutoFitOverride(controls);
9344
+ if (!viewerRef.current?.homeCameraState) {
9345
+ markInitialAutoFitOverride(controls);
9346
+ }
9162
9347
  const transitions = [
9163
9348
  controls.setLookAt(
9164
9349
  state.position[0],
@@ -9182,11 +9367,11 @@ function useViewerCamera() {
9182
9367
  await Promise.all(transitions);
9183
9368
  const camera = controls.camera;
9184
9369
  let projectionChanged = false;
9185
- if (state.fov !== null && "fov" in camera && typeof camera.fov === "number") {
9370
+ if (state.fov != null && "fov" in camera && typeof camera.fov === "number") {
9186
9371
  camera.fov = state.fov;
9187
9372
  projectionChanged = true;
9188
9373
  }
9189
- if ("zoom" in camera && typeof camera.zoom === "number") {
9374
+ if (state.zoom != null && "zoom" in camera && typeof camera.zoom === "number") {
9190
9375
  camera.zoom = state.zoom;
9191
9376
  projectionChanged = true;
9192
9377
  }
@@ -9201,13 +9386,94 @@ function useViewerCamera() {
9201
9386
  },
9202
9387
  [updateCameraState]
9203
9388
  );
9389
+ const setCameraState = useCallback5(
9390
+ (state, enableTransition = true) => applyCameraPreset(state, enableTransition),
9391
+ [applyCameraPreset]
9392
+ );
9393
+ const lookAt = useCallback5(
9394
+ (position, target, enableTransition = true) => applyCameraPreset({ position, target }, enableTransition),
9395
+ [applyCameraPreset]
9396
+ );
9397
+ const orbitTo = useCallback5(
9398
+ async (azimuthAngle, polarAngle, enableTransition = true) => {
9399
+ const controls = viewerRef.current?.controls;
9400
+ if (!controls) {
9401
+ return false;
9402
+ }
9403
+ try {
9404
+ if (!viewerRef.current?.homeCameraState) {
9405
+ markInitialAutoFitOverride(controls);
9406
+ }
9407
+ await controls.rotateTo(
9408
+ azimuthAngle,
9409
+ polarAngle,
9410
+ enableTransition
9411
+ );
9412
+ updateCameraState(buildCameraState(controls.camera, controls));
9413
+ return true;
9414
+ } catch {
9415
+ return false;
9416
+ }
9417
+ },
9418
+ [updateCameraState]
9419
+ );
9420
+ const dollyTo = useCallback5(
9421
+ async (distance, enableTransition = true) => {
9422
+ const controls = viewerRef.current?.controls;
9423
+ if (!controls) {
9424
+ return false;
9425
+ }
9426
+ try {
9427
+ if (!viewerRef.current?.homeCameraState) {
9428
+ markInitialAutoFitOverride(controls);
9429
+ }
9430
+ await controls.dollyTo(distance, enableTransition);
9431
+ updateCameraState(buildCameraState(controls.camera, controls));
9432
+ return true;
9433
+ } catch {
9434
+ return false;
9435
+ }
9436
+ },
9437
+ [updateCameraState]
9438
+ );
9439
+ const savePreset = useCallback5((name) => {
9440
+ const controls = viewerRef.current?.controls;
9441
+ const trimmedName = name.trim();
9442
+ if (!controls || !trimmedName) {
9443
+ return null;
9444
+ }
9445
+ const nextState = buildCameraState(controls.camera, controls);
9446
+ cameraPresetsRef.current = {
9447
+ ...cameraPresetsRef.current,
9448
+ [trimmedName]: nextState
9449
+ };
9450
+ setCameraPresets((current) => ({
9451
+ ...current,
9452
+ [trimmedName]: nextState
9453
+ }));
9454
+ updateCameraState(nextState);
9455
+ return nextState;
9456
+ }, [updateCameraState]);
9457
+ const goToPreset = useCallback5(
9458
+ (name, enableTransition = true) => {
9459
+ const preset = cameraPresetsRef.current[name];
9460
+ return preset ? applyCameraPreset(preset, enableTransition) : Promise.resolve(false);
9461
+ },
9462
+ [applyCameraPreset]
9463
+ );
9204
9464
  return {
9205
9465
  cameraState,
9466
+ cameraPresets,
9206
9467
  resetView,
9207
9468
  focusObject,
9208
9469
  fitScene,
9209
9470
  setCameraTarget,
9210
9471
  setCameraState,
9472
+ lookAt,
9473
+ orbitTo,
9474
+ dollyTo,
9475
+ savePreset,
9476
+ goToPreset,
9211
9477
  handleCameraChange,
9212
9478
  handleViewerReady
9213
9479
  };
@@ -15076,6 +15342,9 @@ function IdentityPanel({
15076
15342
  ] }) });
15077
15343
  }
15078
15344
 
15345
+ // src/components/BindingBuilder/components/BasicsPanel.tsx
15346
+ import { useEffect as useEffect21, useState as useState24 } from "react";
15347
+
15079
15348
  // src/components/BindingBuilder/utils/constants.ts
15080
15349
  var typeSuggestions = [
15081
15350
  "other",
@@ -15115,6 +15384,31 @@ function BasicsPanel({
15115
15384
  onToggle,
15116
15385
  updateBinding
15117
15386
  }) {
15387
+ const [groupDraft, setGroupDraft] = useState24(selectedBinding.group ?? "");
15388
+ const [tagsDraft, setTagsDraft] = useState24(
15389
+ selectedBinding.tags?.join(", ") ?? ""
15390
+ );
15391
+ useEffect21(() => {
15392
+ setGroupDraft(selectedBinding.group ?? "");
15393
+ setTagsDraft(selectedBinding.tags?.join(", ") ?? "");
15394
+ }, [selectedItem, selectedBinding.group, selectedBinding.tags]);
15395
+ const commitGroup = () => {
15396
+ updateBinding(selectedItem, (binding) => ({
15397
+ ...binding,
15398
+ group: groupDraft.trim() || void 0
15399
+ }));
15400
+ };
15401
+ const commitTags = () => {
15402
+ const tags = [
15403
+ ...new Set(
15404
+ tagsDraft.split(",").map((tag) => tag.trim()).filter(Boolean)
15405
+ )
15406
+ ];
15407
+ updateBinding(selectedItem, (binding) => ({
15408
+ ...binding,
15409
+ tags: tags.length > 0 ? tags : void 0
15410
+ }));
15411
+ };
15118
15412
  return /* @__PURE__ */ jsxs36(BBSection, { title: "Basics", open, onToggle, children: [
15119
15413
  /* @__PURE__ */ jsxs36("div", { className: "bb-grid", children: [
15120
15414
  /* @__PURE__ */ jsxs36("div", { className: "bb-field", children: [
@@ -15161,6 +15455,38 @@ function BasicsPanel({
15161
15455
  children: statusOptions.map((option) => /* @__PURE__ */ jsx41("option", { value: option, children: option }, option))
15162
15456
  }
15163
15457
  )
15458
+ ] }),
15459
+ /* @__PURE__ */ jsxs36("div", { className: "bb-field", children: [
15460
+ /* @__PURE__ */ jsx41("label", { className: "bb-label", children: "Group" }),
15461
+ /* @__PURE__ */ jsx41(
15462
+ "input",
15463
+ {
15464
+ className: "bb-input",
15465
+ placeholder: "paint",
15466
+ value: groupDraft,
15467
+ onChange: (event) => setGroupDraft(event.target.value),
15468
+ onBlur: commitGroup,
15469
+ onKeyDown: (event) => {
15470
+ if (event.key === "Enter") event.currentTarget.blur();
15471
+ }
15472
+ }
15473
+ )
15474
+ ] }),
15475
+ /* @__PURE__ */ jsxs36("div", { className: "bb-field", children: [
15476
+ /* @__PURE__ */ jsx41("label", { className: "bb-label", children: "Tags" }),
15477
+ /* @__PURE__ */ jsx41(
15478
+ "input",
15479
+ {
15480
+ className: "bb-input",
15481
+ placeholder: "exterior, configurable",
15482
+ value: tagsDraft,
15483
+ onChange: (event) => setTagsDraft(event.target.value),
15484
+ onBlur: commitTags,
15485
+ onKeyDown: (event) => {
15486
+ if (event.key === "Enter") event.currentTarget.blur();
15487
+ }
15488
+ }
15489
+ )
15164
15490
  ] })
15165
15491
  ] }),
15166
15492
  /* @__PURE__ */ jsxs36("div", { className: "bb-checkboxRow", style: { marginTop: 12 }, children: [
@@ -15211,7 +15537,7 @@ function BasicsPanel({
15211
15537
  }
15212
15538
 
15213
15539
  // src/components/BindingBuilder/components/ActionsPanel.tsx
15214
- import { useEffect as useEffect21, useState as useState24 } from "react";
15540
+ import { useEffect as useEffect22, useState as useState25 } from "react";
15215
15541
 
15216
15542
  // src/components/BindingBuilder/utils/actionCatalog.ts
15217
15543
  var ACTION_CATALOG = [
@@ -15237,19 +15563,19 @@ var ACTION_CATALOG = [
15237
15563
  id: "toggle-light",
15238
15564
  label: "Toggle Light",
15239
15565
  handling: "host",
15240
- description: "Seeded on light bindings. Handle it in your onAction callback."
15566
+ description: "Seeded on light bindings. Add declarative effects for useViewerActions, or handle it in onAction."
15241
15567
  },
15242
15568
  {
15243
15569
  id: "change-light-color",
15244
15570
  label: "Change Light Color",
15245
15571
  handling: "host",
15246
- description: "Seeded on light bindings. Handle it in your onAction callback."
15572
+ description: "Seeded on light bindings. Add declarative effects for useViewerActions, or handle it in onAction."
15247
15573
  },
15248
15574
  {
15249
15575
  id: "set-brightness",
15250
15576
  label: "Set Brightness",
15251
15577
  handling: "host",
15252
- description: "Seeded on light bindings. Handle it in your onAction callback."
15578
+ description: "Seeded on light bindings. Add declarative effects for useViewerActions, or handle it in onAction."
15253
15579
  }
15254
15580
  ];
15255
15581
  var CUSTOM_ACTION_ID = "__custom__";
@@ -15310,8 +15636,8 @@ function ActionsPanel({
15310
15636
  onToggle,
15311
15637
  updateBinding
15312
15638
  }) {
15313
- const [customRows, setCustomRows] = useState24(/* @__PURE__ */ new Set());
15314
- useEffect21(() => {
15639
+ const [customRows, setCustomRows] = useState25(/* @__PURE__ */ new Set());
15640
+ useEffect22(() => {
15315
15641
  setCustomRows(/* @__PURE__ */ new Set());
15316
15642
  }, [selectedItem]);
15317
15643
  const actions = selectedBinding.actions ?? [];
@@ -15427,8 +15753,8 @@ function ActionsPanel({
15427
15753
  }
15428
15754
  )
15429
15755
  ] }),
15430
- catalogEntry ? /* @__PURE__ */ jsx42("span", { className: "bb-note", children: catalogEntry.description }) : trimmedId.length === 0 ? /* @__PURE__ */ jsx42("span", { className: "bb-note", style: { color: "#fca5a5" }, children: "An action ID is required \u2014 the button renders but can never be identified in onAction." }) : /* @__PURE__ */ jsxs37("span", { className: "bb-note", style: { color: "#fcd34d" }, children: [
15431
- "Unrecognised ID \u2014 the viewer has no built-in behaviour for it, so your app must handle it in onAction.",
15756
+ catalogEntry ? /* @__PURE__ */ jsx42("span", { className: "bb-note", children: catalogEntry.description }) : trimmedId.length === 0 ? /* @__PURE__ */ jsx42("span", { className: "bb-note", style: { color: "#fca5a5" }, children: "An action ID is required, the button renders but can never be identified in onAction." }) : /* @__PURE__ */ jsxs37("span", { className: "bb-note", style: { color: "#fcd34d" }, children: [
15757
+ "Unrecognised ID, the viewer has no built-in behavior for it. Add declarative effects and execute them with useViewerActions, or handle it in onAction.",
15432
15758
  suggestion ? /* @__PURE__ */ jsxs37(Fragment13, { children: [
15433
15759
  " Did you mean ",
15434
15760
  /* @__PURE__ */ jsx42(
@@ -15451,7 +15777,13 @@ function ActionsPanel({
15451
15777
  "?"
15452
15778
  ] }) : null
15453
15779
  ] }),
15454
- isDuplicate ? /* @__PURE__ */ jsx42("span", { className: "bb-note", style: { color: "#fcd34d" }, children: "Another action on this binding uses the same ID \u2014 they will render as two identical buttons." }) : null
15780
+ action.effects?.length ? /* @__PURE__ */ jsxs37("span", { className: "bb-note", children: [
15781
+ action.effects.length,
15782
+ " declarative effect",
15783
+ action.effects.length === 1 ? "" : "s",
15784
+ ", execute with useViewerActions."
15785
+ ] }) : null,
15786
+ isDuplicate ? /* @__PURE__ */ jsx42("span", { className: "bb-note", style: { color: "#fcd34d" }, children: "Another action on this binding uses the same ID, they will render as two identical buttons." }) : null
15455
15787
  ] }),
15456
15788
  /* @__PURE__ */ jsx42("div", { className: "bb-actionFooter", children: /* @__PURE__ */ jsx42(
15457
15789
  "button",
@@ -15600,7 +15932,7 @@ function CameraPositionPanel({
15600
15932
  }
15601
15933
 
15602
15934
  // src/components/BindingBuilder/components/SceneNodesPanel.tsx
15603
- import { useMemo as useMemo16, useState as useState25 } from "react";
15935
+ import { useMemo as useMemo16, useState as useState26 } from "react";
15604
15936
 
15605
15937
  // src/components/BindingBuilder/utils/bindingFactory.ts
15606
15938
  function slugify2(value) {
@@ -15689,7 +16021,7 @@ function SceneNodesPanel({
15689
16021
  isOpen = true,
15690
16022
  onToggle
15691
16023
  }) {
15692
- const [search, setSearch] = useState25("");
16024
+ const [search, setSearch] = useState26("");
15693
16025
  const nodeKeys = useMemo16(() => Object.keys(bindings), [bindings]);
15694
16026
  const filteredNodeKeys = useMemo16(() => {
15695
16027
  const query = search.trim().toLowerCase();
@@ -15920,9 +16252,9 @@ function toBindingsJson(bindings) {
15920
16252
  }
15921
16253
  function toBindingsTypeScript(bindings) {
15922
16254
  return [
15923
- 'import type { ObjectBinding } from "@liveroom-tech/react-immersive";',
16255
+ 'import { defineObjectBindings } from "@liveroom-tech/react-immersive";',
15924
16256
  "",
15925
- `export const objectBindings: Record<string, ObjectBinding> = ${JSON.stringify(bindings, null, 2)};`,
16257
+ `export const objectBindings = defineObjectBindings(${JSON.stringify(bindings, null, 2)});`,
15926
16258
  ""
15927
16259
  ].join("\n");
15928
16260
  }
@@ -16145,27 +16477,27 @@ function BindingBuilder({ licenseKey }) {
16145
16477
  const modelInputRef = useRef19(null);
16146
16478
  const bindingsInputRef = useRef19(null);
16147
16479
  const sceneConfigInputRef = useRef19(null);
16148
- const [modelUrl, setModelUrl] = useState26(null);
16149
- const [modelName, setModelName] = useState26("");
16150
- const [selectedItem, setSelectedItem] = useState26(null);
16480
+ const [modelUrl, setModelUrl] = useState27(null);
16481
+ const [modelName, setModelName] = useState27("");
16482
+ const [selectedItem, setSelectedItem] = useState27(null);
16151
16483
  const bindingsHistory = useUndoableState({});
16152
16484
  const bindings = bindingsHistory.value;
16153
16485
  const setBindings = bindingsHistory.setValue;
16154
- const [loading, setLoading] = useState26(false);
16155
- const [isExporting, setIsExporting] = useState26(false);
16156
- const [statusMessage, setStatusMessage] = useState26(
16486
+ const [loading, setLoading] = useState27(false);
16487
+ const [isExporting, setIsExporting] = useState27(false);
16488
+ const [statusMessage, setStatusMessage] = useState27(
16157
16489
  "Load the demo model or upload a GLB/GLTF model to generate bindings."
16158
16490
  );
16159
- const [statusTone, setStatusTone] = useState26("default");
16160
- const [statusBannerVisible, setStatusBannerVisible] = useState26(false);
16161
- const [metricsDraft, setMetricsDraft] = useState26("{}");
16162
- const [metadataDraft, setMetadataDraft] = useState26("{}");
16163
- const [metricsError, setMetricsError] = useState26(null);
16164
- const [metadataError, setMetadataError] = useState26(null);
16165
- const [isPreviewDragActive, setIsPreviewDragActive] = useState26(false);
16166
- const [isEditorCollapsed, setIsEditorCollapsed] = useState26(false);
16167
- const [isNodesCollapsed, setIsNodesCollapsed] = useState26(false);
16168
- const [openObjectSections, setOpenObjectSections] = useState26({
16491
+ const [statusTone, setStatusTone] = useState27("default");
16492
+ const [statusBannerVisible, setStatusBannerVisible] = useState27(false);
16493
+ const [metricsDraft, setMetricsDraft] = useState27("{}");
16494
+ const [metadataDraft, setMetadataDraft] = useState27("{}");
16495
+ const [metricsError, setMetricsError] = useState27(null);
16496
+ const [metadataError, setMetadataError] = useState27(null);
16497
+ const [isPreviewDragActive, setIsPreviewDragActive] = useState27(false);
16498
+ const [isEditorCollapsed, setIsEditorCollapsed] = useState27(false);
16499
+ const [isNodesCollapsed, setIsNodesCollapsed] = useState27(false);
16500
+ const [openObjectSections, setOpenObjectSections] = useState27({
16169
16501
  identity: true,
16170
16502
  basics: true,
16171
16503
  material: false,
@@ -16174,7 +16506,7 @@ function BindingBuilder({ licenseKey }) {
16174
16506
  metrics: false,
16175
16507
  metadata: false
16176
16508
  });
16177
- const [openMaterialPanels, setOpenMaterialPanels] = useState26({
16509
+ const [openMaterialPanels, setOpenMaterialPanels] = useState27({
16178
16510
  pbr: true,
16179
16511
  roughness: false,
16180
16512
  emission: false,
@@ -16190,24 +16522,24 @@ function BindingBuilder({ licenseKey }) {
16190
16522
  });
16191
16523
  const toggleObjectSection = (key) => setOpenObjectSections((prev) => ({ ...prev, [key]: !prev[key] }));
16192
16524
  const toggleMaterialPanel = (key) => setOpenMaterialPanels((prev) => ({ ...prev, [key]: !prev[key] }));
16193
- const [activeEditorTab, setActiveEditorTab] = useState26(
16525
+ const [activeEditorTab, setActiveEditorTab] = useState27(
16194
16526
  "object"
16195
16527
  );
16196
- const [activeSubTab, setActiveSubTab] = useState26("General");
16197
- const [pbrWorkflow, setPbrWorkflow] = useState26("metalness");
16198
- const [materialPanelEnabled, setMaterialPanelEnabled] = useState26(createMaterialPanelEnabledState());
16528
+ const [activeSubTab, setActiveSubTab] = useState27("General");
16529
+ const [pbrWorkflow, setPbrWorkflow] = useState27("metalness");
16530
+ const [materialPanelEnabled, setMaterialPanelEnabled] = useState27(createMaterialPanelEnabledState());
16199
16531
  const materialPanelItemRef = useRef19(null);
16200
- const [zoomOnSelected, setZoomOnSelected] = useState26(false);
16201
- const [highlightOnHover, setHighlightOnHover] = useState26(true);
16202
- const [resetDialogOpen, setResetDialogOpen] = useState26(false);
16203
- const [resetTarget, setResetTarget] = useState26(
16532
+ const [zoomOnSelected, setZoomOnSelected] = useState27(false);
16533
+ const [highlightOnHover, setHighlightOnHover] = useState27(true);
16534
+ const [resetDialogOpen, setResetDialogOpen] = useState27(false);
16535
+ const [resetTarget, setResetTarget] = useState27(
16204
16536
  null
16205
16537
  );
16206
16538
  const sceneConfigHistory = useUndoableState(DEFAULT_SCENE_CONFIG);
16207
16539
  const sceneConfig = sceneConfigHistory.value;
16208
16540
  const setSceneConfig = sceneConfigHistory.setValue;
16209
- const [activeAnnotation, setActiveAnnotation] = useState26(null);
16210
- const [panelHoveredBinding, setPanelHoveredBinding] = useState26(null);
16541
+ const [activeAnnotation, setActiveAnnotation] = useState27(null);
16542
+ const [panelHoveredBinding, setPanelHoveredBinding] = useState27(null);
16211
16543
  const license = useLicense(licenseKey);
16212
16544
  const tier = normalizeTier(license.tier);
16213
16545
  const bindingBuilderContextValue = useMemo17(
@@ -16285,7 +16617,7 @@ function BindingBuilder({ licenseKey }) {
16285
16617
  },
16286
16618
  []
16287
16619
  );
16288
- useEffect22(() => {
16620
+ useEffect23(() => {
16289
16621
  if (!selectedBinding) {
16290
16622
  setMetricsDraft("{}");
16291
16623
  setMetadataDraft("{}");
@@ -16298,7 +16630,7 @@ function BindingBuilder({ licenseKey }) {
16298
16630
  setMetricsError(null);
16299
16631
  setMetadataError(null);
16300
16632
  }, [selectedBinding]);
16301
- useEffect22(() => {
16633
+ useEffect23(() => {
16302
16634
  const derived = createMaterialPanelEnabledState(selectedMaterial);
16303
16635
  if (materialPanelItemRef.current !== selectedItem) {
16304
16636
  materialPanelItemRef.current = selectedItem;
@@ -16317,12 +16649,12 @@ function BindingBuilder({ licenseKey }) {
16317
16649
  return changed ? next : prev;
16318
16650
  });
16319
16651
  }, [selectedItem, selectedMaterial]);
16320
- useEffect22(() => {
16652
+ useEffect23(() => {
16321
16653
  return () => {
16322
16654
  uploadedModelRef.current?.cleanup();
16323
16655
  };
16324
16656
  }, []);
16325
- useEffect22(() => {
16657
+ useEffect23(() => {
16326
16658
  const handleKeyDown = (event) => {
16327
16659
  if (event.key.toLowerCase() !== "z" || !(event.metaKey || event.ctrlKey)) {
16328
16660
  return;
@@ -16343,7 +16675,7 @@ function BindingBuilder({ licenseKey }) {
16343
16675
  window.addEventListener("keydown", handleKeyDown);
16344
16676
  return () => window.removeEventListener("keydown", handleKeyDown);
16345
16677
  }, [activeEditorTab, bindingsHistory, sceneConfigHistory]);
16346
- useEffect22(() => {
16678
+ useEffect23(() => {
16347
16679
  if (loading || statusTone === "default" || statusTone === "success") return;
16348
16680
  setStatusBannerVisible(true);
16349
16681
  const timeout = window.setTimeout(
@@ -17373,7 +17705,7 @@ function BindingBuilder({ licenseKey }) {
17373
17705
  /* @__PURE__ */ jsx46("strong", { children: resetTarget === "bindings" ? "Object Bindings" : "Scene Config" }),
17374
17706
  "?"
17375
17707
  ] }),
17376
- /* @__PURE__ */ jsx46("p", { className: "bb-reset-details", children: resetTarget === "bindings" ? "This will remove all your custom changes to object properties, materials, actions, camera positions, metrics, and metadata." : "This will restore all scene settings (camera, lighting, environment, background, animations, annotations, etc.) to their default values." }),
17708
+ /* @__PURE__ */ jsx46("p", { className: "bb-reset-details", children: resetTarget === "bindings" ? "This will remove all your custom changes to object properties, groups, tags, transforms, materials, actions, camera positions, metrics, and metadata." : "This will restore all scene settings (camera, lighting, environment, background, animations, annotations, etc.) to their default values." }),
17377
17709
  /* @__PURE__ */ jsx46("p", { className: "bb-reset-warning", children: "This action cannot be undone!" })
17378
17710
  ] }),
17379
17711
  /* @__PURE__ */ jsxs41("div", { className: "bb-reset-footer", children: [
@@ -17387,9 +17719,9 @@ function BindingBuilder({ licenseKey }) {
17387
17719
  // src/components/SimpleModelViewer/SimpleModelViewer.tsx
17388
17720
  import {
17389
17721
  useRef as useRef20,
17390
- useState as useState27,
17722
+ useState as useState28,
17391
17723
  useCallback as useCallback10,
17392
- useEffect as useEffect23,
17724
+ useEffect as useEffect24,
17393
17725
  useMemo as useMemo18,
17394
17726
  Component as Component3,
17395
17727
  Suspense as Suspense3
@@ -17400,7 +17732,7 @@ import { Canvas as Canvas3 } from "@react-three/fiber";
17400
17732
  import {
17401
17733
  Box3 as Box35,
17402
17734
  PerspectiveCamera as PerspectiveCamera4,
17403
- Vector3 as Vector310
17735
+ Vector3 as Vector311
17404
17736
  } from "three";
17405
17737
  import { SkeletonUtils as SkeletonUtils2 } from "three-stdlib";
17406
17738
 
@@ -17439,7 +17771,7 @@ function applyClippingPlanes(scene, controls) {
17439
17771
  if (!controls) return;
17440
17772
  const camera = controls.camera;
17441
17773
  if (!(camera instanceof PerspectiveCamera4)) return;
17442
- const size = new Box35().setFromObject(scene).getSize(new Vector310()).length();
17774
+ const size = new Box35().setFromObject(scene).getSize(new Vector311()).length();
17443
17775
  if (!Number.isFinite(size) || size <= 0) return;
17444
17776
  camera.near = size / 100;
17445
17777
  camera.far = size * 100;
@@ -17478,7 +17810,7 @@ function InnerModel({
17478
17810
  meshopt: true
17479
17811
  });
17480
17812
  const invalidate = useThree12((state) => state.invalidate);
17481
- const [hoveredKey, setHoveredKey] = useState27(null);
17813
+ const [hoveredKey, setHoveredKey] = useState28(null);
17482
17814
  const scene = useMemo18(
17483
17815
  () => SkeletonUtils2.clone(rawScene),
17484
17816
  [rawScene]
@@ -17508,14 +17840,14 @@ function InnerModel({
17508
17840
  }
17509
17841
  return map;
17510
17842
  }, [meshMap]);
17511
- useEffect23(
17843
+ useEffect24(
17512
17844
  () => () => {
17513
17845
  Object.values(perKeyMaterials).forEach((m) => m.dispose());
17514
17846
  },
17515
17847
  [perKeyMaterials]
17516
17848
  );
17517
17849
  const notifiedUrl = useRef20(null);
17518
- useEffect23(() => {
17850
+ useEffect24(() => {
17519
17851
  if (notifiedUrl.current === url) return;
17520
17852
  notifiedUrl.current = url;
17521
17853
  scene.updateWorldMatrix(true, true);
@@ -17523,7 +17855,7 @@ function InnerModel({
17523
17855
  onMeshesReady(meshMap);
17524
17856
  onModelLoaded(scene);
17525
17857
  }, [url, scene, meshMap, onMeshesReady, onModelLoaded]);
17526
- useEffect23(() => {
17858
+ useEffect24(() => {
17527
17859
  const controller = new AbortController();
17528
17860
  void buildSceneBVH(scene, controller.signal);
17529
17861
  return () => {
@@ -17535,22 +17867,22 @@ function InnerModel({
17535
17867
  () => !exceedsBvhBudget(scene),
17536
17868
  [scene]
17537
17869
  );
17538
- useEffect23(() => {
17870
+ useEffect24(() => {
17539
17871
  for (const [key, { ref }] of Object.entries(meshMap)) {
17540
17872
  nodeRefsOut.current[key] = ref;
17541
17873
  }
17542
17874
  }, [meshMap, nodeRefsOut]);
17543
- useEffect23(() => {
17875
+ useEffect24(() => {
17544
17876
  if (!highlightOnHover && hoveredKey !== null) {
17545
17877
  setHoveredKey(null);
17546
17878
  }
17547
17879
  }, [highlightOnHover, hoveredKey]);
17548
- useEffect23(() => {
17880
+ useEffect24(() => {
17549
17881
  return () => {
17550
17882
  document.body.style.cursor = "default";
17551
17883
  };
17552
17884
  }, []);
17553
- useEffect23(() => {
17885
+ useEffect24(() => {
17554
17886
  for (const [key, { ref }] of Object.entries(meshMap)) {
17555
17887
  ref.visible = !hiddenKeys.has(key);
17556
17888
  const mat = perKeyMaterials[key];
@@ -17624,7 +17956,7 @@ function CameraFitter({
17624
17956
  const invalidate = useThree12((state) => state.invalidate);
17625
17957
  const size = useThree12((state) => state.size);
17626
17958
  const resizeTimerRef = useRef20(null);
17627
- useEffect23(() => {
17959
+ useEffect24(() => {
17628
17960
  fitted.current = false;
17629
17961
  invalidate();
17630
17962
  }, [invalidate, scene]);
@@ -17644,7 +17976,7 @@ function CameraFitter({
17644
17976
  }).catch(() => {
17645
17977
  }).finally(onInitialFitComplete);
17646
17978
  });
17647
- useEffect23(() => {
17979
+ useEffect24(() => {
17648
17980
  if (!refitOnResize || !fitted.current || !scene || !controlsRef.current) {
17649
17981
  return;
17650
17982
  }
@@ -17672,7 +18004,7 @@ function CameraFitter({
17672
18004
  function ExposureController({ exposure }) {
17673
18005
  const gl = useThree12((state) => state.gl);
17674
18006
  const invalidate = useThree12((state) => state.invalidate);
17675
- useEffect23(() => {
18007
+ useEffect24(() => {
17676
18008
  gl.toneMappingExposure = exposure;
17677
18009
  invalidate();
17678
18010
  }, [gl, exposure, invalidate]);
@@ -17780,7 +18112,7 @@ function EnvironmentSection({
17780
18112
  title,
17781
18113
  children
17782
18114
  }) {
17783
- const [collapsed, setCollapsed] = useState27(false);
18115
+ const [collapsed, setCollapsed] = useState28(false);
17784
18116
  return /* @__PURE__ */ jsxs42("div", { className: "env-section", children: [
17785
18117
  /* @__PURE__ */ jsxs42(
17786
18118
  "button",
@@ -17945,8 +18277,8 @@ function SceneObjectsPanel2({
17945
18277
  onHover,
17946
18278
  onToggleVisibility
17947
18279
  }) {
17948
- const [search, setSearch] = useState27("");
17949
- const [rowHovered, setRowHovered] = useState27(null);
18280
+ const [search, setSearch] = useState28("");
18281
+ const [rowHovered, setRowHovered] = useState28(null);
17950
18282
  const entries = Object.values(meshKeys).filter(
17951
18283
  (m) => m.label.toLowerCase().includes(search.toLowerCase())
17952
18284
  );
@@ -18075,30 +18407,30 @@ function SimpleModelViewer({
18075
18407
  const fileInputRef = useRef20(null);
18076
18408
  const uploadedModelRef = useRef20(null);
18077
18409
  const dragDepthRef = useRef20(0);
18078
- const [loadedScene, setLoadedScene] = useState27(null);
18079
- const [largeModelTriangles, setLargeModelTriangles] = useState27(
18410
+ const [loadedScene, setLoadedScene] = useState28(null);
18411
+ const [largeModelTriangles, setLargeModelTriangles] = useState28(
18080
18412
  null
18081
18413
  );
18082
- const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] = useState27(false);
18083
- const [meshKeys, setMeshKeys] = useState27({});
18084
- const [hiddenKeys, setHiddenKeys] = useState27(/* @__PURE__ */ new Set());
18085
- const [selectedKey, setSelectedKey] = useState27(null);
18086
- const [panelHoveredKey, setPanelHoveredKey] = useState27(null);
18087
- const [uploadedModelUrl, setUploadedModelUrl] = useState27(null);
18088
- const [uploadedModelName, setUploadedModelName] = useState27(
18414
+ const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] = useState28(false);
18415
+ const [meshKeys, setMeshKeys] = useState28({});
18416
+ const [hiddenKeys, setHiddenKeys] = useState28(/* @__PURE__ */ new Set());
18417
+ const [selectedKey, setSelectedKey] = useState28(null);
18418
+ const [panelHoveredKey, setPanelHoveredKey] = useState28(null);
18419
+ const [uploadedModelUrl, setUploadedModelUrl] = useState28(null);
18420
+ const [uploadedModelName, setUploadedModelName] = useState28(
18089
18421
  null
18090
18422
  );
18091
- const [modelLoadFailed, setModelLoadFailed] = useState27(false);
18092
- const [isResolvingUpload, setIsResolvingUpload] = useState27(false);
18093
- const [initialFitComplete, setInitialFitComplete] = useState27(false);
18094
- const [isDragActive, setIsDragActive] = useState27(false);
18095
- const [isPanelOpen, setIsPanelOpen] = useState27(
18423
+ const [modelLoadFailed, setModelLoadFailed] = useState28(false);
18424
+ const [isResolvingUpload, setIsResolvingUpload] = useState28(false);
18425
+ const [initialFitComplete, setInitialFitComplete] = useState28(false);
18426
+ const [isDragActive, setIsDragActive] = useState28(false);
18427
+ const [isPanelOpen, setIsPanelOpen] = useState28(
18096
18428
  () => typeof window === "undefined" ? true : window.innerWidth > 900
18097
18429
  );
18098
- const [isEnvPanelOpen, setIsEnvPanelOpen] = useState27(
18430
+ const [isEnvPanelOpen, setIsEnvPanelOpen] = useState28(
18099
18431
  () => typeof window === "undefined" ? true : window.innerWidth > 900
18100
18432
  );
18101
- const [envSettings, setEnvSettings] = useState27({
18433
+ const [envSettings, setEnvSettings] = useState28({
18102
18434
  backgroundEnabled,
18103
18435
  backgroundColor,
18104
18436
  autoRotate,
@@ -18181,7 +18513,7 @@ function SimpleModelViewer({
18181
18513
  },
18182
18514
  [handleUploadedModel]
18183
18515
  );
18184
- useEffect23(() => {
18516
+ useEffect24(() => {
18185
18517
  return () => {
18186
18518
  revokeUploadedModelUrl();
18187
18519
  };
@@ -18192,10 +18524,10 @@ function SimpleModelViewer({
18192
18524
  },
18193
18525
  []
18194
18526
  );
18195
- useEffect23(() => {
18527
+ useEffect24(() => {
18196
18528
  setEnvSettings((prev) => ({ ...prev, backgroundColor }));
18197
18529
  }, [backgroundColor]);
18198
- useEffect23(() => {
18530
+ useEffect24(() => {
18199
18531
  resetModelState();
18200
18532
  }, [activeModelUrl, resetModelState]);
18201
18533
  const handleMeshesReady = useCallback10((meshes) => {
@@ -18511,9 +18843,9 @@ function SimpleModelViewer({
18511
18843
  }
18512
18844
 
18513
18845
  // src/hooks/useViewerSelection.ts
18514
- import { useCallback as useCallback11, useState as useState28 } from "react";
18846
+ import { useCallback as useCallback11, useState as useState29 } from "react";
18515
18847
  function useViewerSelection(initialSelectedObjectBinding = null) {
18516
- const [selectedObjectBinding, setSelectedObjectBinding] = useState28(
18848
+ const [selectedObjectBinding, setSelectedObjectBinding] = useState29(
18517
18849
  initialSelectedObjectBinding
18518
18850
  );
18519
18851
  const handleObjectSelect = useCallback11(
@@ -18560,7 +18892,7 @@ function useObjectBindingIds(objectBindings) {
18560
18892
  }
18561
18893
 
18562
18894
  // src/hooks/useObjectBindings.ts
18563
- import { useCallback as useCallback12, useMemo as useMemo21, useState as useState29 } from "react";
18895
+ import { useCallback as useCallback12, useMemo as useMemo21, useState as useState30 } from "react";
18564
18896
 
18565
18897
  // src/utils/deepMerge.ts
18566
18898
  function isPlainObject2(value) {
@@ -18578,6 +18910,27 @@ function deepMerge(base, patch) {
18578
18910
  return target;
18579
18911
  }
18580
18912
 
18913
+ // src/utils/selectBindings.ts
18914
+ function isSelector(target) {
18915
+ return typeof target === "object" && !Array.isArray(target);
18916
+ }
18917
+ function matchesSelector(binding, selector) {
18918
+ if ("group" in selector) return binding.group === selector.group;
18919
+ if ("tag" in selector) return binding.tags?.includes(selector.tag) ?? false;
18920
+ return binding.type === selector.type;
18921
+ }
18922
+ function selectBindingKeys(bindings, target) {
18923
+ if (isSelector(target)) {
18924
+ return Object.entries(bindings).filter(([, binding]) => matchesSelector(binding, target)).map(([bindingKey]) => bindingKey);
18925
+ }
18926
+ const ids = typeof target === "string" ? [target] : target;
18927
+ const keys = ids.map((id) => resolveBindingKey(bindings, id)).filter((key) => key !== null);
18928
+ return [...new Set(keys)];
18929
+ }
18930
+ function selectBindings(bindings, target) {
18931
+ return selectBindingKeys(bindings, target).map((key) => bindings[key]).filter((binding) => binding !== void 0);
18932
+ }
18933
+
18581
18934
  // src/utils/patchBindings.ts
18582
18935
  function isBindingEditArray(value) {
18583
18936
  return Array.isArray(value) && value.length > 0 && typeof value[0] !== "string";
@@ -18586,10 +18939,7 @@ function patchBindings(bindings, objectIdOrEdits, patch) {
18586
18939
  const edits = isBindingEditArray(objectIdOrEdits) ? objectIdOrEdits : [{ ids: objectIdOrEdits, patch: patch ?? {} }];
18587
18940
  let next = bindings;
18588
18941
  for (const edit of edits) {
18589
- const ids = Array.isArray(edit.ids) ? edit.ids : [edit.ids];
18590
- for (const id of ids) {
18591
- const bindingKey = resolveBindingKey(next, id);
18592
- if (!bindingKey) continue;
18942
+ for (const bindingKey of selectBindingKeys(next, edit.ids)) {
18593
18943
  const binding = next[bindingKey];
18594
18944
  if (!binding) continue;
18595
18945
  const patchedBinding = deepMerge(binding, edit.patch);
@@ -18604,13 +18954,43 @@ function material(value) {
18604
18954
  }
18605
18955
 
18606
18956
  // src/hooks/useObjectBindings.ts
18957
+ var TEXTURE_PROPERTY_BY_CHANNEL = {
18958
+ baseColor: "texture",
18959
+ metalness: "metalnessMap",
18960
+ roughness: "roughnessMap",
18961
+ emissive: "emissiveMap",
18962
+ alpha: "alphaMap",
18963
+ normal: "normalMap",
18964
+ bump: "bumpMap",
18965
+ ao: "aoMap",
18966
+ displacement: "displacementMap",
18967
+ clearcoat: "clearcoatMap",
18968
+ clearcoatRoughness: "clearcoatRoughnessMap",
18969
+ clearcoatNormal: "clearcoatNormalMap",
18970
+ anisotropy: "anisotropyMap",
18971
+ transmission: "transmissionMap",
18972
+ thickness: "thicknessMap",
18973
+ sheenColor: "sheenColorMap",
18974
+ sheenRoughness: "sheenRoughnessMap",
18975
+ specularIntensity: "specularIntensityMap",
18976
+ specularColor: "specularColorMap"
18977
+ };
18978
+ function cloneMaterial(value) {
18979
+ if (!value) return void 0;
18980
+ return Object.fromEntries(
18981
+ Object.entries(value).map(([key, field]) => [
18982
+ key,
18983
+ field && typeof field === "object" ? { ...field } : field
18984
+ ])
18985
+ );
18986
+ }
18607
18987
  function getHiddenObjects(objectBindings) {
18608
18988
  return Object.fromEntries(
18609
18989
  Object.entries(objectBindings).filter(([, binding]) => binding.visible === false).map(([bindingKey]) => [bindingKey, true])
18610
18990
  );
18611
18991
  }
18612
18992
  function useObjectBindings(initialBindings) {
18613
- const [objectBindings, setObjectBindings] = useState29(initialBindings);
18993
+ const [objectBindings, setObjectBindings] = useState30(initialBindings);
18614
18994
  const updateObjectBindings = useCallback12(
18615
18995
  (objectIdOrEdits, patchValue) => {
18616
18996
  setObjectBindings(
@@ -18624,17 +19004,86 @@ function useObjectBindings(initialBindings) {
18624
19004
  []
18625
19005
  );
18626
19006
  const setMaterial = useCallback12(
18627
- (objectId, value) => updateObjectBindings(objectId, material(value)),
19007
+ (target, value) => updateObjectBindings(target, material(value)),
18628
19008
  [updateObjectBindings]
18629
19009
  );
18630
19010
  const setBaseColor = useCallback12(
18631
- (objectId, color) => setMaterial(objectId, { baseColor: color }),
19011
+ (target, color) => setMaterial(target, { baseColor: color }),
18632
19012
  [setMaterial]
18633
19013
  );
19014
+ const setTexture = useCallback12(
19015
+ (target, texture, channel = "baseColor") => {
19016
+ const property = TEXTURE_PROPERTY_BY_CHANNEL[channel];
19017
+ const slot = typeof texture === "string" ? { path: texture } : texture;
19018
+ updateObjectBindings(target, {
19019
+ style: { material: { [property]: slot } }
19020
+ });
19021
+ },
19022
+ [updateObjectBindings]
19023
+ );
19024
+ const clearTexture = useCallback12(
19025
+ (target, channel = "baseColor") => {
19026
+ const property = TEXTURE_PROPERTY_BY_CHANNEL[channel];
19027
+ updateObjectBindings(target, {
19028
+ style: { material: { [property]: void 0 } }
19029
+ });
19030
+ },
19031
+ [updateObjectBindings]
19032
+ );
19033
+ const copyMaterial = useCallback12(
19034
+ (source, target) => {
19035
+ setObjectBindings((current) => {
19036
+ const sourceKey = resolveBindingKey(current, source);
19037
+ if (!sourceKey) return current;
19038
+ const sourceMaterial = cloneMaterial(
19039
+ current[sourceKey]?.style?.material
19040
+ );
19041
+ const targetKeys = selectBindingKeys(current, target);
19042
+ if (targetKeys.length === 0) return current;
19043
+ let next = current;
19044
+ for (const targetKey of targetKeys) {
19045
+ const binding = current[targetKey];
19046
+ if (!binding) continue;
19047
+ if (next === current) next = { ...current };
19048
+ next[targetKey] = {
19049
+ ...binding,
19050
+ style: {
19051
+ ...binding.style,
19052
+ material: cloneMaterial(sourceMaterial)
19053
+ }
19054
+ };
19055
+ }
19056
+ return next;
19057
+ });
19058
+ },
19059
+ []
19060
+ );
18634
19061
  const resetMaterial = useCallback12(
18635
- (objectId) => updateObjectBindings(objectId, { style: { material: void 0 } }),
19062
+ (target) => updateObjectBindings(target, { style: { material: void 0 } }),
19063
+ [updateObjectBindings]
19064
+ );
19065
+ const setTransform = useCallback12(
19066
+ (target, transform) => updateObjectBindings(target, { transform }),
19067
+ [updateObjectBindings]
19068
+ );
19069
+ const resetTransform = useCallback12(
19070
+ (target) => updateObjectBindings(target, { transform: void 0 }),
19071
+ [updateObjectBindings]
19072
+ );
19073
+ const updateMetadata = useCallback12(
19074
+ (target, metadata) => updateObjectBindings(target, { metadata }),
18636
19075
  [updateObjectBindings]
18637
19076
  );
19077
+ const selectObjectBindings = useCallback12(
19078
+ (target) => selectBindings(objectBindings, target),
19079
+ [objectBindings]
19080
+ );
19081
+ const selectObjectBindingIds = useCallback12(
19082
+ (target) => selectBindingKeys(objectBindings, target).map(
19083
+ (bindingKey) => objectBindings[bindingKey]?.id ?? bindingKey
19084
+ ),
19085
+ [objectBindings]
19086
+ );
18638
19087
  const getMaterial = useCallback12(
18639
19088
  (objectId) => {
18640
19089
  const bindingKey = resolveBindingKey(objectBindings, objectId);
@@ -18643,27 +19092,28 @@ function useObjectBindings(initialBindings) {
18643
19092
  [objectBindings]
18644
19093
  );
18645
19094
  const hideObject = useCallback12(
18646
- (objectId) => updateObjectBindings(objectId, { visible: false }),
19095
+ (target) => updateObjectBindings(target, { visible: false }),
18647
19096
  [updateObjectBindings]
18648
19097
  );
18649
19098
  const showObject = useCallback12(
18650
- (objectId) => updateObjectBindings(objectId, { visible: true }),
19099
+ (target) => updateObjectBindings(target, { visible: true }),
18651
19100
  [updateObjectBindings]
18652
19101
  );
18653
19102
  const toggleObjectVisibility = useCallback12(
18654
- (objectId) => {
19103
+ (target) => {
18655
19104
  setObjectBindings((current) => {
18656
- const objectIds = Array.isArray(objectId) ? objectId : [objectId];
18657
- const edits = objectIds.reduce((acc, id) => {
18658
- const bindingKey = resolveBindingKey(current, id);
18659
- const binding = bindingKey ? current[bindingKey] : null;
18660
- if (!bindingKey || !binding) return acc;
18661
- acc.push({
18662
- ids: bindingKey,
18663
- patch: { visible: binding.visible === false }
18664
- });
18665
- return acc;
18666
- }, []);
19105
+ const edits = selectBindingKeys(current, target).reduce(
19106
+ (acc, bindingKey) => {
19107
+ const binding = current[bindingKey];
19108
+ if (!binding) return acc;
19109
+ acc.push({
19110
+ ids: bindingKey,
19111
+ patch: { visible: binding.visible === false }
19112
+ });
19113
+ return acc;
19114
+ },
19115
+ []
19116
+ );
18667
19117
  return patchBindings(current, edits);
18668
19118
  });
18669
19119
  },
@@ -18697,8 +19147,16 @@ function useObjectBindings(initialBindings) {
18697
19147
  updateObjectBindings,
18698
19148
  setMaterial,
18699
19149
  setBaseColor,
19150
+ setTexture,
19151
+ clearTexture,
19152
+ copyMaterial,
18700
19153
  resetMaterial,
18701
19154
  getMaterial,
19155
+ setTransform,
19156
+ resetTransform,
19157
+ updateMetadata,
19158
+ selectObjectBindings,
19159
+ selectObjectBindingIds,
18702
19160
  hiddenObjects,
18703
19161
  hiddenObjectIds,
18704
19162
  hideObject,
@@ -18709,8 +19167,13 @@ function useObjectBindings(initialBindings) {
18709
19167
  };
18710
19168
  }
18711
19169
 
19170
+ // src/utils/defineObjectBindings.ts
19171
+ function defineObjectBindings(bindings) {
19172
+ return bindings;
19173
+ }
19174
+
18712
19175
  // src/hooks/useSceneConfig.ts
18713
- import { useCallback as useCallback13, useRef as useRef21, useState as useState30 } from "react";
19176
+ import { useCallback as useCallback13, useMemo as useMemo22, useRef as useRef21, useState as useState31 } from "react";
18714
19177
 
18715
19178
  // src/utils/patchSceneConfig.ts
18716
19179
  function patchSceneConfig(sceneConfig, patch) {
@@ -18718,9 +19181,21 @@ function patchSceneConfig(sceneConfig, patch) {
18718
19181
  }
18719
19182
 
18720
19183
  // src/hooks/useSceneConfig.ts
19184
+ var DEFAULT_LIGHT = {
19185
+ type: "point",
19186
+ color: "#ffffff",
19187
+ intensity: 1,
19188
+ position: [0, 0, 0],
19189
+ distance: 0,
19190
+ decay: 2,
19191
+ attachedToCamera: false,
19192
+ castShadow: false,
19193
+ shadowBias: -5e-4,
19194
+ visible: true
19195
+ };
18721
19196
  function useSceneConfig(initialSceneConfig) {
18722
19197
  const initialSceneConfigRef = useRef21(null);
18723
- const [sceneConfig, setSceneConfig] = useState30(() => {
19198
+ const [sceneConfig, setSceneConfig] = useState31(() => {
18724
19199
  const initial = typeof initialSceneConfig === "function" ? initialSceneConfig() : initialSceneConfig;
18725
19200
  initialSceneConfigRef.current = initial;
18726
19201
  return initial;
@@ -18735,18 +19210,124 @@ function useSceneConfig(initialSceneConfig) {
18735
19210
  const initial = initialSceneConfigRef.current;
18736
19211
  if (initial) setSceneConfig(initial);
18737
19212
  }, []);
19213
+ const updateLight = useCallback13(
19214
+ (id, patch) => {
19215
+ updateSceneConfig((current) => ({
19216
+ lighting: {
19217
+ lights: current.lighting.lights.map(
19218
+ (light) => light.id === id ? { ...light, ...patch } : light
19219
+ )
19220
+ }
19221
+ }));
19222
+ },
19223
+ [updateSceneConfig]
19224
+ );
19225
+ const addLight = useCallback13(
19226
+ (light) => {
19227
+ updateSceneConfig((current) => {
19228
+ if (current.lighting.lights.some((item) => item.id === light.id)) {
19229
+ return {};
19230
+ }
19231
+ return {
19232
+ lighting: {
19233
+ lights: [
19234
+ ...current.lighting.lights,
19235
+ { ...DEFAULT_LIGHT, ...light }
19236
+ ]
19237
+ }
19238
+ };
19239
+ });
19240
+ },
19241
+ [updateSceneConfig]
19242
+ );
19243
+ const removeLight = useCallback13(
19244
+ (id) => {
19245
+ updateSceneConfig((current) => ({
19246
+ lighting: {
19247
+ lights: current.lighting.lights.filter((light) => light.id !== id)
19248
+ }
19249
+ }));
19250
+ },
19251
+ [updateSceneConfig]
19252
+ );
19253
+ const setLightIntensity = useCallback13(
19254
+ (id, intensity) => updateLight(id, { intensity }),
19255
+ [updateLight]
19256
+ );
19257
+ const setLightColor = useCallback13(
19258
+ (id, color) => updateLight(id, { color }),
19259
+ [updateLight]
19260
+ );
19261
+ const showLight = useCallback13(
19262
+ (id) => updateLight(id, { visible: true }),
19263
+ [updateLight]
19264
+ );
19265
+ const hideLight = useCallback13(
19266
+ (id) => updateLight(id, { visible: false }),
19267
+ [updateLight]
19268
+ );
19269
+ const toggleLight = useCallback13(
19270
+ (id) => {
19271
+ updateSceneConfig((current) => ({
19272
+ lighting: {
19273
+ lights: current.lighting.lights.map(
19274
+ (light) => light.id === id ? { ...light, visible: !light.visible } : light
19275
+ )
19276
+ }
19277
+ }));
19278
+ },
19279
+ [updateSceneConfig]
19280
+ );
19281
+ const attachLight = useCallback13(
19282
+ (id, attachment) => updateLight(id, { attachment, attachedToCamera: false }),
19283
+ [updateLight]
19284
+ );
19285
+ const detachLight = useCallback13(
19286
+ (id) => updateLight(id, { attachment: void 0 }),
19287
+ [updateLight]
19288
+ );
19289
+ const lights = useMemo22(
19290
+ () => ({
19291
+ items: sceneConfig.lighting.lights,
19292
+ get: (id) => sceneConfig.lighting.lights.find((light) => light.id === id),
19293
+ add: addLight,
19294
+ update: updateLight,
19295
+ remove: removeLight,
19296
+ setIntensity: setLightIntensity,
19297
+ setColor: setLightColor,
19298
+ show: showLight,
19299
+ hide: hideLight,
19300
+ toggle: toggleLight,
19301
+ attach: attachLight,
19302
+ detach: detachLight
19303
+ }),
19304
+ [
19305
+ sceneConfig.lighting.lights,
19306
+ addLight,
19307
+ updateLight,
19308
+ removeLight,
19309
+ setLightIntensity,
19310
+ setLightColor,
19311
+ showLight,
19312
+ hideLight,
19313
+ toggleLight,
19314
+ attachLight,
19315
+ detachLight
19316
+ ]
19317
+ );
18738
19318
  return {
18739
19319
  sceneConfig,
18740
19320
  setSceneConfig,
18741
19321
  updateSceneConfig,
18742
- resetSceneConfig
19322
+ resetSceneConfig,
19323
+ lights
18743
19324
  };
18744
19325
  }
18745
19326
 
18746
19327
  // src/hooks/useViewerHover.ts
18747
- import { useCallback as useCallback14, useState as useState31 } from "react";
19328
+ import { useCallback as useCallback14, useState as useState32 } from "react";
18748
19329
  function useViewerHover() {
18749
- const [hoveredObjectBinding, setHoveredObjectBinding] = useState31(null);
19330
+ const [hoveredObjectBinding, setHoveredObjectBinding] = useState32(null);
18750
19331
  const handleHoveredObject = useCallback14(
18751
19332
  (binding) => {
18752
19333
  setHoveredObjectBinding(binding);
@@ -18760,23 +19341,23 @@ function useViewerHover() {
18760
19341
  }
18761
19342
 
18762
19343
  // src/hooks/useViewerModel.ts
18763
- import { useCallback as useCallback15, useState as useState32 } from "react";
18764
- import { Box3 as Box36, Vector3 as Vector311 } from "three";
19344
+ import { useCallback as useCallback15, useState as useState33 } from "react";
19345
+ import { Box3 as Box36, Vector3 as Vector312 } from "three";
18765
19346
  function toTuple2(vector) {
18766
19347
  return [vector.x, vector.y, vector.z];
18767
19348
  }
18768
19349
  function useViewerModel() {
18769
- const [isLoading, setIsLoading] = useState32(true);
18770
- const [isReady, setIsReady] = useState32(false);
18771
- const [error, setError] = useState32(null);
18772
- const [bounds, setBounds] = useState32(null);
18773
- const [objectCount, setObjectCount] = useState32(0);
19350
+ const [isLoading, setIsLoading] = useState33(true);
19351
+ const [isReady, setIsReady] = useState33(false);
19352
+ const [error, setError] = useState33(null);
19353
+ const [bounds, setBounds] = useState33(null);
19354
+ const [objectCount, setObjectCount] = useState33(0);
18774
19355
  const handleModelLoaded = useCallback15((scene) => {
18775
19356
  const box = new Box36().setFromObject(scene);
18776
19357
  const min = box.min.clone();
18777
19358
  const max = box.max.clone();
18778
- const center = new Vector311();
18779
- const size = new Vector311();
19359
+ const center = new Vector312();
19360
+ const size = new Vector312();
18780
19361
  let nextObjectCount = 0;
18781
19362
  scene.traverse((child) => {
18782
19363
  if ("isMesh" in child && child.isMesh) {
@@ -18824,61 +19405,777 @@ function useViewerModel() {
18824
19405
 
18825
19406
  // src/hooks/useViewerActions.ts
18826
19407
  import { useCallback as useCallback16 } from "react";
18827
- function useViewerActions(objectBindings, options = {}) {
18828
- const { onAction, onObjectBindingsChange } = options;
19408
+ function useViewerActions({
19409
+ bindings,
19410
+ scene,
19411
+ camera,
19412
+ animations,
19413
+ onAction
19414
+ }) {
19415
+ const {
19416
+ objectBindings,
19417
+ updateObjectBindings,
19418
+ showObject,
19419
+ hideObject,
19420
+ toggleObjectVisibility
19421
+ } = bindings;
19422
+ const updateSceneConfig = scene?.updateSceneConfig;
19423
+ const focusObject = camera?.focusObject;
19424
+ const fitScene = camera?.fitScene;
19425
+ const resetView = camera?.resetView;
19426
+ const setCameraTarget = camera?.setCameraTarget;
19427
+ const setCameraState = camera?.setCameraState;
19428
+ const playAnimation = animations?.play;
19429
+ const pauseAnimation = animations?.pause;
19430
+ const stopAnimation = animations?.stop;
19431
+ const setAnimationSpeed = animations?.setSpeed;
19432
+ const seekAnimation = animations?.seek;
18829
19433
  const getActionsForObject = useCallback16(
18830
19434
  (objectId) => {
18831
19435
  const bindingKey = resolveBindingKey(objectBindings, objectId);
18832
- if (!bindingKey) {
18833
- return [];
18834
- }
18835
- return objectBindings[bindingKey]?.actions ?? [];
19436
+ return bindingKey ? objectBindings[bindingKey]?.actions ?? [] : [];
18836
19437
  },
18837
19438
  [objectBindings]
18838
19439
  );
19440
+ const handleAction = useCallback16(
19441
+ async (event) => {
19442
+ for (const effect of event.action.effects ?? []) {
19443
+ if ("objectBindings" in effect) {
19444
+ updateObjectBindings(effect.target, effect.objectBindings);
19445
+ continue;
19446
+ }
19447
+ if ("sceneConfig" in effect) {
19448
+ updateSceneConfig?.(effect.sceneConfig);
19449
+ continue;
19450
+ }
19451
+ if ("visibility" in effect) {
19452
+ if (effect.visibility === "show") showObject(effect.target);
19453
+ else if (effect.visibility === "hide") hideObject(effect.target);
19454
+ else toggleObjectVisibility(effect.target);
19455
+ continue;
19456
+ }
19457
+ if ("camera" in effect) {
19458
+ const action2 = effect.camera;
19459
+ if (action2.type === "focus-object") {
19460
+ await focusObject?.(action2.objectId ?? event.objectId);
19461
+ } else if (action2.type === "fit-scene") {
19462
+ await fitScene?.();
19463
+ } else if (action2.type === "reset") {
19464
+ await resetView?.();
19465
+ } else if (action2.type === "set-target") {
19466
+ await setCameraTarget?.(action2.target, action2.transition ?? true);
19467
+ } else {
19468
+ await setCameraState?.(action2.state, action2.transition ?? true);
19469
+ }
19470
+ continue;
19471
+ }
19472
+ const action = effect.animation;
19473
+ if (action.type === "play") playAnimation?.(action.clip);
19474
+ else if (action.type === "pause") pauseAnimation?.();
19475
+ else if (action.type === "stop") stopAnimation?.();
19476
+ else if (action.type === "set-speed") {
19477
+ setAnimationSpeed?.(action.speed);
19478
+ } else {
19479
+ seekAnimation?.(action.time);
19480
+ }
19481
+ }
19482
+ await onAction?.(event);
19483
+ },
19484
+ [
19485
+ fitScene,
19486
+ focusObject,
19487
+ hideObject,
19488
+ onAction,
19489
+ pauseAnimation,
19490
+ playAnimation,
19491
+ resetView,
19492
+ seekAnimation,
19493
+ setAnimationSpeed,
19494
+ setCameraState,
19495
+ setCameraTarget,
19496
+ showObject,
19497
+ stopAnimation,
19498
+ toggleObjectVisibility,
19499
+ updateObjectBindings,
19500
+ updateSceneConfig
19501
+ ]
19502
+ );
18839
19503
  const runAction = useCallback16(
18840
- (objectId, actionId) => {
19504
+ async (objectId, actionId) => {
18841
19505
  const bindingKey = resolveBindingKey(objectBindings, objectId);
18842
- if (!bindingKey) {
18843
- return null;
18844
- }
19506
+ if (!bindingKey) return null;
18845
19507
  const binding = objectBindings[bindingKey];
18846
- if (!binding) {
18847
- return null;
18848
- }
18849
- const action = (binding.actions ?? []).find(
18850
- (entry) => entry.id === actionId
18851
- );
18852
- if (!action) {
18853
- return null;
18854
- }
19508
+ const action = binding?.actions?.find((entry) => entry.id === actionId);
19509
+ if (!binding || !action) return null;
18855
19510
  const event = {
18856
19511
  objectId: bindingKey,
18857
19512
  action,
18858
19513
  binding
18859
19514
  };
18860
- if (action.id === "toggle-visibility" && onObjectBindingsChange) {
18861
- onObjectBindingsChange({
18862
- ...objectBindings,
18863
- [bindingKey]: {
18864
- ...binding,
18865
- visible: binding.visible === false
18866
- }
18867
- });
18868
- }
18869
- onAction?.(event);
19515
+ await handleAction(event);
18870
19516
  return event;
18871
19517
  },
18872
- [objectBindings, onAction, onObjectBindingsChange]
19518
+ [handleAction, objectBindings]
18873
19519
  );
18874
19520
  return {
18875
19521
  getActionsForObject,
19522
+ handleAction,
18876
19523
  runAction
18877
19524
  };
18878
19525
  }
18879
19526
 
19527
+ // src/hooks/useViewerConnection.ts
19528
+ import { useCallback as useCallback17, useRef as useRef22 } from "react";
19529
+ function getReadyHandler(consumer) {
19530
+ if (typeof consumer === "function") {
19531
+ return consumer;
19532
+ }
19533
+ return consumer?.handleViewerReady ?? null;
19534
+ }
19535
+ function useViewerConnection(...consumers) {
19536
+ const viewerRef = useRef22(null);
19537
+ const consumersRef = useRef22(consumers);
19538
+ consumersRef.current = consumers;
19539
+ const handleViewerReady = useCallback17((viewer) => {
19540
+ viewerRef.current = viewer;
19541
+ const invoked = /* @__PURE__ */ new Set();
19542
+ for (const consumer of consumersRef.current) {
19543
+ const handler = getReadyHandler(consumer);
19544
+ if (handler && !invoked.has(handler)) {
19545
+ invoked.add(handler);
19546
+ handler(viewer);
19547
+ }
19548
+ }
19549
+ }, []);
19550
+ const getViewer = useCallback17(() => viewerRef.current, []);
19551
+ const captureImage2 = useCallback17((options) => {
19552
+ const viewer = viewerRef.current;
19553
+ if (!viewer) {
19554
+ return Promise.reject(new Error("ModelViewer is not ready yet."));
19555
+ }
19556
+ return viewer.captureImage(options);
19557
+ }, []);
19558
+ const invalidate = useCallback17(() => {
19559
+ const viewer = viewerRef.current;
19560
+ if (!viewer) {
19561
+ return false;
19562
+ }
19563
+ viewer.invalidate();
19564
+ return true;
19565
+ }, []);
19566
+ return {
19567
+ handleViewerReady,
19568
+ getViewer,
19569
+ captureImage: captureImage2,
19570
+ invalidate
19571
+ };
19572
+ }
19573
+
19574
+ // src/hooks/useViewerEffects.ts
19575
+ import { useCallback as useCallback18, useEffect as useEffect25, useRef as useRef23 } from "react";
19576
+ import {
19577
+ Material as Material3,
19578
+ PointLight,
19579
+ Vector3 as Vector313
19580
+ } from "three";
19581
+ var NUMERIC_MATERIAL_KEYS = [
19582
+ "emissiveIntensity",
19583
+ "opacity",
19584
+ "metalness",
19585
+ "roughness",
19586
+ "transmission",
19587
+ "clearcoat",
19588
+ "clearcoatRoughness",
19589
+ "ior",
19590
+ "thickness"
19591
+ ];
19592
+ var nextEffectId = 1;
19593
+ function stringList(target) {
19594
+ return typeof target === "string" ? [target] : target;
19595
+ }
19596
+ function clamp2(value, min = 0, max = 1) {
19597
+ return Math.min(max, Math.max(min, value));
19598
+ }
19599
+ function randomBetween(min, max) {
19600
+ return min + Math.random() * (max - min);
19601
+ }
19602
+ function interpolate([from, to], level) {
19603
+ return from + (to - from) * level;
19604
+ }
19605
+ function interpolateVector([from, to], level) {
19606
+ return [
19607
+ from[0] + (to[0] - from[0]) * level,
19608
+ from[1] + (to[1] - from[1]) * level,
19609
+ from[2] + (to[2] - from[2]) * level
19610
+ ];
19611
+ }
19612
+ function ease(value, easing) {
19613
+ const t = clamp2(value);
19614
+ switch (easing) {
19615
+ case "ease-in":
19616
+ return t * t;
19617
+ case "ease-out":
19618
+ return 1 - (1 - t) * (1 - t);
19619
+ case "ease-in-out":
19620
+ return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
19621
+ default:
19622
+ return t;
19623
+ }
19624
+ }
19625
+ function materialList(node) {
19626
+ const value = node.material;
19627
+ if (!value) return [];
19628
+ return (Array.isArray(value) ? value : [value]).filter(
19629
+ (entry) => entry instanceof Material3
19630
+ );
19631
+ }
19632
+ function readMaterial(material2) {
19633
+ const snapshot = {
19634
+ opacity: material2.opacity,
19635
+ transparent: material2.transparent
19636
+ };
19637
+ if (material2.color) snapshot.baseColor = `#${material2.color.getHexString()}`;
19638
+ if (material2.emissive) snapshot.emissive = `#${material2.emissive.getHexString()}`;
19639
+ for (const key of NUMERIC_MATERIAL_KEYS) {
19640
+ const value = material2[key];
19641
+ if (typeof value === "number") snapshot[key] = value;
19642
+ }
19643
+ return snapshot;
19644
+ }
19645
+ function applyMaterial(material2, patch) {
19646
+ if (patch.baseColor !== void 0 && material2.color) {
19647
+ material2.color.set(patch.baseColor);
19648
+ }
19649
+ if (patch.emissive !== void 0 && material2.emissive) {
19650
+ material2.emissive.set(patch.emissive);
19651
+ }
19652
+ for (const key of NUMERIC_MATERIAL_KEYS) {
19653
+ const value = patch[key];
19654
+ if (value !== void 0 && typeof material2[key] === "number") {
19655
+ material2[key] = value;
19656
+ }
19657
+ }
19658
+ if (patch.transparent !== void 0) material2.transparent = patch.transparent;
19659
+ material2.needsUpdate = true;
19660
+ }
19661
+ function readTransform(node) {
19662
+ return {
19663
+ position: [node.position.x, node.position.y, node.position.z],
19664
+ rotation: [node.rotation.x, node.rotation.y, node.rotation.z],
19665
+ scale: [node.scale.x, node.scale.y, node.scale.z],
19666
+ visible: node.visible
19667
+ };
19668
+ }
19669
+ function applyTransform(node, patch) {
19670
+ if (patch.position) node.position.set(...patch.position);
19671
+ if (patch.rotation) node.rotation.set(...patch.rotation);
19672
+ if (patch.scale !== void 0) {
19673
+ const scale = typeof patch.scale === "number" ? [patch.scale, patch.scale, patch.scale] : patch.scale;
19674
+ node.scale.set(...scale);
19675
+ }
19676
+ if (patch.visible !== void 0) node.visible = patch.visible;
19677
+ node.updateMatrix();
19678
+ node.updateMatrixWorld(true);
19679
+ }
19680
+ function useViewerEffects(options = {}) {
19681
+ const optionsRef = useRef23(options);
19682
+ optionsRef.current = options;
19683
+ const viewerRef = useRef23(null);
19684
+ const sceneRef = useRef23(null);
19685
+ const frameRef = useRef23(null);
19686
+ const effectsRef = useRef23(/* @__PURE__ */ new Map());
19687
+ const lightsRef = useRef23(/* @__PURE__ */ new Map());
19688
+ const materialSnapshotsRef = useRef23(
19689
+ /* @__PURE__ */ new Map()
19690
+ );
19691
+ const transformSnapshotsRef = useRef23(/* @__PURE__ */ new Map());
19692
+ const resolveObject = useCallback18((target) => {
19693
+ const viewer = viewerRef.current;
19694
+ if (!viewer) return null;
19695
+ const bindingKey = resolveBindingKey(viewer.objectBindings, target);
19696
+ return viewer.nodeRefs[target] ?? (bindingKey ? viewer.nodeRefs[bindingKey] : null) ?? viewer.scene?.getObjectByName(target) ?? null;
19697
+ }, []);
19698
+ const resolveTargets = useCallback18(
19699
+ (target) => {
19700
+ const viewer = viewerRef.current;
19701
+ if (!viewer) {
19702
+ return typeof target === "string" ? [target] : Array.isArray(target) ? target : [];
19703
+ }
19704
+ if (typeof target === "object" && !Array.isArray(target)) {
19705
+ return selectBindingKeys(viewer.objectBindings, target);
19706
+ }
19707
+ return stringList(target).map(
19708
+ (id) => resolveBindingKey(viewer.objectBindings, id) ?? id
19709
+ );
19710
+ },
19711
+ []
19712
+ );
19713
+ const invalidate = useCallback18(() => {
19714
+ viewerRef.current?.invalidate();
19715
+ }, []);
19716
+ const setMaterialInternal = useCallback18(
19717
+ (target, patch) => {
19718
+ let changed = false;
19719
+ for (const id of resolveTargets(target)) {
19720
+ const node = resolveObject(id);
19721
+ if (!node) continue;
19722
+ for (const entry of materialList(node)) {
19723
+ if (!materialSnapshotsRef.current.has(entry)) {
19724
+ materialSnapshotsRef.current.set(entry, readMaterial(entry));
19725
+ }
19726
+ applyMaterial(entry, patch);
19727
+ changed = true;
19728
+ }
19729
+ }
19730
+ return changed;
19731
+ },
19732
+ [resolveObject, resolveTargets]
19733
+ );
19734
+ const setMaterial = useCallback18(
19735
+ (target, patch) => {
19736
+ const changed = setMaterialInternal(target, patch);
19737
+ if (changed) invalidate();
19738
+ return changed;
19739
+ },
19740
+ [invalidate, setMaterialInternal]
19741
+ );
19742
+ const setTransformInternal = useCallback18(
19743
+ (target, patch) => {
19744
+ let changed = false;
19745
+ for (const id of resolveTargets(target)) {
19746
+ const node = resolveObject(id);
19747
+ if (!node) continue;
19748
+ if (!transformSnapshotsRef.current.has(node)) {
19749
+ transformSnapshotsRef.current.set(node, readTransform(node));
19750
+ }
19751
+ applyTransform(node, patch);
19752
+ changed = true;
19753
+ }
19754
+ return changed;
19755
+ },
19756
+ [resolveObject, resolveTargets]
19757
+ );
19758
+ const setTransform = useCallback18(
19759
+ (target, patch) => {
19760
+ const changed = setTransformInternal(target, patch);
19761
+ if (changed) invalidate();
19762
+ return changed;
19763
+ },
19764
+ [invalidate, setTransformInternal]
19765
+ );
19766
+ const getWorldPosition = useCallback18(
19767
+ (target) => {
19768
+ const node = resolveObject(target);
19769
+ if (!node) return null;
19770
+ node.updateWorldMatrix(true, false);
19771
+ const position = node.getWorldPosition(new Vector313());
19772
+ return [position.x, position.y, position.z];
19773
+ },
19774
+ [resolveObject]
19775
+ );
19776
+ const removePointLight = useCallback18(
19777
+ (id) => {
19778
+ const runtime = lightsRef.current.get(id);
19779
+ if (!runtime) return false;
19780
+ runtime.light.removeFromParent();
19781
+ runtime.light.dispose();
19782
+ lightsRef.current.delete(id);
19783
+ invalidate();
19784
+ return true;
19785
+ },
19786
+ [invalidate]
19787
+ );
19788
+ const addPointLight = useCallback18(
19789
+ (lightOptions) => {
19790
+ const target = resolveObject(lightOptions.target);
19791
+ if (!target) return false;
19792
+ const existing = lightsRef.current.get(lightOptions.id);
19793
+ const light = existing?.light ?? new PointLight();
19794
+ if (existing && existing.target !== lightOptions.target) {
19795
+ light.removeFromParent();
19796
+ }
19797
+ light.name = `react-immersive-runtime-light:${lightOptions.id}`;
19798
+ light.color.set(lightOptions.color ?? "#ffffff");
19799
+ light.intensity = lightOptions.intensity ?? 1;
19800
+ light.distance = lightOptions.distance ?? 0;
19801
+ light.decay = lightOptions.decay ?? 2;
19802
+ light.castShadow = lightOptions.castShadow ?? false;
19803
+ light.position.set(...lightOptions.offset ?? [0, 0, 0]);
19804
+ if (light.parent !== target) target.add(light);
19805
+ lightsRef.current.set(lightOptions.id, {
19806
+ light,
19807
+ target: lightOptions.target
19808
+ });
19809
+ invalidate();
19810
+ return true;
19811
+ },
19812
+ [invalidate, resolveObject]
19813
+ );
19814
+ const updatePointLight = useCallback18(
19815
+ (id, patch) => {
19816
+ const runtime = lightsRef.current.get(id);
19817
+ if (!runtime) return false;
19818
+ if (patch.target && patch.target !== runtime.target) {
19819
+ const target = resolveObject(patch.target);
19820
+ if (!target) return false;
19821
+ runtime.light.removeFromParent();
19822
+ target.add(runtime.light);
19823
+ runtime.target = patch.target;
19824
+ }
19825
+ if (patch.color !== void 0) runtime.light.color.set(patch.color);
19826
+ if (patch.intensity !== void 0) {
19827
+ runtime.light.intensity = patch.intensity;
19828
+ }
19829
+ if (patch.distance !== void 0) runtime.light.distance = patch.distance;
19830
+ if (patch.decay !== void 0) runtime.light.decay = patch.decay;
19831
+ if (patch.offset !== void 0) {
19832
+ runtime.light.position.set(...patch.offset);
19833
+ }
19834
+ if (patch.castShadow !== void 0) {
19835
+ runtime.light.castShadow = patch.castShadow;
19836
+ }
19837
+ invalidate();
19838
+ return true;
19839
+ },
19840
+ [invalidate, resolveObject]
19841
+ );
19842
+ const applyChannels = useCallback18(
19843
+ (channels, levels) => {
19844
+ const levelFor = (index) => levels[index % levels.length] ?? 0;
19845
+ for (const channel of channels.materials ?? []) {
19846
+ resolveTargets(channel.targets).forEach((target, index) => {
19847
+ const level = levelFor(index);
19848
+ const patch = {};
19849
+ for (const key of NUMERIC_MATERIAL_KEYS) {
19850
+ const range = channel.values[key];
19851
+ if (range) patch[key] = interpolate(range, level);
19852
+ }
19853
+ setMaterialInternal(target, patch);
19854
+ });
19855
+ }
19856
+ for (const channel of channels.transforms ?? []) {
19857
+ resolveTargets(channel.targets).forEach((target, index) => {
19858
+ const level = levelFor(index);
19859
+ const patch = {};
19860
+ if (channel.values.position) {
19861
+ patch.position = interpolateVector(channel.values.position, level);
19862
+ }
19863
+ if (channel.values.rotation) {
19864
+ patch.rotation = interpolateVector(channel.values.rotation, level);
19865
+ }
19866
+ if (channel.values.scale) {
19867
+ patch.scale = interpolateVector(channel.values.scale, level);
19868
+ }
19869
+ setTransformInternal(target, patch);
19870
+ });
19871
+ }
19872
+ for (const channel of channels.lights ?? []) {
19873
+ stringList(channel.lights).forEach((id, index) => {
19874
+ const runtime = lightsRef.current.get(id);
19875
+ if (runtime) {
19876
+ runtime.light.intensity = interpolate(
19877
+ channel.intensity,
19878
+ levelFor(index)
19879
+ );
19880
+ }
19881
+ });
19882
+ }
19883
+ },
19884
+ [resolveTargets, setMaterialInternal, setTransformInternal]
19885
+ );
19886
+ const scheduleFrameRef = useRef23(() => void 0);
19887
+ const runFrameRef = useRef23(() => void 0);
19888
+ runFrameRef.current = (now) => {
19889
+ frameRef.current = null;
19890
+ let rendered = false;
19891
+ for (const [id, effect] of effectsRef.current) {
19892
+ if (effect.startedAt === null) effect.startedAt = now;
19893
+ const previous = effect.lastFrameAt ?? now;
19894
+ effect.lastFrameAt = now;
19895
+ const override = effect.override && effect.override.until > now ? effect.override.level : null;
19896
+ if (effect.override && override === null) effect.override = null;
19897
+ const keepRunning = effect.update(
19898
+ now,
19899
+ now - effect.startedAt,
19900
+ Math.min(100, now - previous),
19901
+ override
19902
+ );
19903
+ rendered = true;
19904
+ if (!keepRunning) {
19905
+ effectsRef.current.delete(id);
19906
+ effect.onComplete?.();
19907
+ }
19908
+ }
19909
+ if (rendered) invalidate();
19910
+ if (effectsRef.current.size > 0) scheduleFrameRef.current();
19911
+ };
19912
+ scheduleFrameRef.current = () => {
19913
+ if (frameRef.current !== null || typeof requestAnimationFrame === "undefined") {
19914
+ return;
19915
+ }
19916
+ frameRef.current = requestAnimationFrame((now) => runFrameRef.current(now));
19917
+ };
19918
+ const registerEffect = useCallback18((effect) => {
19919
+ effectsRef.current.set(effect.id, effect);
19920
+ scheduleFrameRef.current();
19921
+ return effect.id;
19922
+ }, []);
19923
+ const transition = useCallback18(
19924
+ (channels, transitionOptions = {}) => {
19925
+ const id = transitionOptions.id ?? `transition-${nextEffectId++}`;
19926
+ const duration = Math.max(1, transitionOptions.duration ?? 400);
19927
+ const easing = transitionOptions.easing ?? "ease-in-out";
19928
+ return registerEffect({
19929
+ id,
19930
+ startedAt: null,
19931
+ lastFrameAt: null,
19932
+ override: null,
19933
+ onComplete: transitionOptions.onComplete,
19934
+ update: (_now, elapsed, _delta, override) => {
19935
+ const progress = override ?? ease(elapsed / duration, easing);
19936
+ applyChannels(channels, [progress]);
19937
+ return elapsed < duration;
19938
+ }
19939
+ });
19940
+ },
19941
+ [applyChannels, registerEffect]
19942
+ );
19943
+ const pulse = useCallback18(
19944
+ (channels, pulseOptions = {}) => {
19945
+ const id = pulseOptions.id ?? `pulse-${nextEffectId++}`;
19946
+ const duration = Math.max(1, pulseOptions.duration ?? 1e3);
19947
+ const minLevel = pulseOptions.minLevel ?? 0;
19948
+ const maxLevel = pulseOptions.maxLevel ?? 1;
19949
+ const repeat = pulseOptions.repeat ?? Infinity;
19950
+ const synchronized = pulseOptions.synchronized ?? true;
19951
+ const unitCount = Math.max(
19952
+ 1,
19953
+ ...[...channels.materials ?? [], ...channels.transforms ?? []].map(
19954
+ (channel) => resolveTargets(channel.targets).length
19955
+ ),
19956
+ ...(channels.lights ?? []).map(
19957
+ (channel) => stringList(channel.lights).length
19958
+ )
19959
+ );
19960
+ return registerEffect({
19961
+ id,
19962
+ startedAt: null,
19963
+ lastFrameAt: null,
19964
+ override: null,
19965
+ onComplete: pulseOptions.onComplete,
19966
+ update: (_now, elapsed, _delta, override) => {
19967
+ const cycles = elapsed / duration;
19968
+ const levels = Array.from(
19969
+ { length: synchronized ? 1 : unitCount },
19970
+ (_, index) => {
19971
+ if (override !== null) return override;
19972
+ const phase = cycles + (synchronized ? 0 : index / unitCount);
19973
+ const wave = 0.5 - Math.cos(phase % 1 * Math.PI * 2) / 2;
19974
+ return minLevel + (maxLevel - minLevel) * wave;
19975
+ }
19976
+ );
19977
+ applyChannels(channels, levels);
19978
+ return repeat === Infinity || cycles < repeat;
19979
+ }
19980
+ });
19981
+ },
19982
+ [applyChannels, registerEffect, resolveTargets]
19983
+ );
19984
+ const timeline = useCallback18(
19985
+ (steps, timelineOptions = {}) => {
19986
+ const id = timelineOptions.id ?? `timeline-${nextEffectId++}`;
19987
+ const repeat = timelineOptions.repeat ?? 1;
19988
+ const normalizedSteps = steps.map((step) => ({
19989
+ ...step,
19990
+ duration: Math.max(1, step.duration),
19991
+ hold: Math.max(0, step.hold ?? 0)
19992
+ }));
19993
+ const cycleDuration = Math.max(
19994
+ 1,
19995
+ normalizedSteps.reduce(
19996
+ (total, step) => total + step.duration + step.hold,
19997
+ 0
19998
+ )
19999
+ );
20000
+ return registerEffect({
20001
+ id,
20002
+ startedAt: null,
20003
+ lastFrameAt: null,
20004
+ override: null,
20005
+ onComplete: timelineOptions.onComplete,
20006
+ update: (_now, elapsed, _delta, override) => {
20007
+ if (normalizedSteps.length === 0 || repeat <= 0) return false;
20008
+ const cycles = elapsed / cycleDuration;
20009
+ if (repeat !== Infinity && cycles >= repeat) {
20010
+ for (const step of normalizedSteps) {
20011
+ applyChannels(step.channels, [1]);
20012
+ }
20013
+ return false;
20014
+ }
20015
+ const cycleTime = elapsed % cycleDuration;
20016
+ let cursor = 0;
20017
+ for (const step of normalizedSteps) {
20018
+ const transitionEnd = cursor + step.duration;
20019
+ const stepEnd = transitionEnd + step.hold;
20020
+ if (cycleTime >= transitionEnd) {
20021
+ applyChannels(step.channels, [override ?? 1]);
20022
+ cursor = stepEnd;
20023
+ continue;
20024
+ }
20025
+ if (cycleTime >= cursor) {
20026
+ const progress = override ?? ease(
20027
+ (cycleTime - cursor) / step.duration,
20028
+ step.easing ?? "ease-in-out"
20029
+ );
20030
+ applyChannels(step.channels, [progress]);
20031
+ break;
20032
+ }
20033
+ }
20034
+ return true;
20035
+ }
20036
+ });
20037
+ },
20038
+ [applyChannels, registerEffect]
20039
+ );
20040
+ const flicker = useCallback18(
20041
+ (channels, flickerOptions = {}) => {
20042
+ const id = flickerOptions.id ?? `flicker-${nextEffectId++}`;
20043
+ const synchronized = flickerOptions.synchronized ?? true;
20044
+ const minLevel = flickerOptions.minLevel ?? 0.15;
20045
+ const maxLevel = flickerOptions.maxLevel ?? 1;
20046
+ const interval = flickerOptions.interval ?? [45, 160];
20047
+ const rawIntervalRange = typeof interval === "number" ? [interval, interval] : interval;
20048
+ const intervalRange = [
20049
+ Math.max(1, Math.min(...rawIntervalRange)),
20050
+ Math.max(1, Math.max(...rawIntervalRange))
20051
+ ];
20052
+ const response = clamp2(flickerOptions.response ?? 0.7, 0.01, 1);
20053
+ const blackoutChance = clamp2(flickerOptions.blackoutChance ?? 0.08);
20054
+ const flashChance = clamp2(flickerOptions.flashChance ?? 0.12);
20055
+ const flashLevel = flickerOptions.flashLevel ?? 1.15;
20056
+ const unitCount = Math.max(
20057
+ 1,
20058
+ ...[...channels.materials ?? [], ...channels.transforms ?? []].map(
20059
+ (channel) => resolveTargets(channel.targets).length
20060
+ ),
20061
+ ...(channels.lights ?? []).map(
20062
+ (channel) => stringList(channel.lights).length
20063
+ )
20064
+ );
20065
+ const count = synchronized ? 1 : unitCount;
20066
+ const levels = Array.from({ length: count }, () => minLevel);
20067
+ const targets = Array.from({ length: count }, () => minLevel);
20068
+ const nextChanges = Array.from({ length: count }, () => 0);
20069
+ return registerEffect({
20070
+ id,
20071
+ startedAt: null,
20072
+ lastFrameAt: null,
20073
+ override: null,
20074
+ onComplete: flickerOptions.onComplete,
20075
+ update: (now, elapsed, delta, override) => {
20076
+ for (let index = 0; index < count; index += 1) {
20077
+ if (override !== null) {
20078
+ targets[index] = override;
20079
+ } else if (now >= nextChanges[index]) {
20080
+ const roll = Math.random();
20081
+ targets[index] = roll < blackoutChance ? randomBetween(0, Math.min(0.04, minLevel)) : roll < blackoutChance + flashChance ? flashLevel : randomBetween(minLevel, maxLevel);
20082
+ nextChanges[index] = now + randomBetween(...intervalRange);
20083
+ }
20084
+ const frameResponse = 1 - Math.pow(1 - response, Math.max(1, delta) / 16.67);
20085
+ levels[index] += (targets[index] - levels[index]) * frameResponse;
20086
+ }
20087
+ applyChannels(channels, levels);
20088
+ return flickerOptions.duration === void 0 || elapsed < flickerOptions.duration;
20089
+ }
20090
+ });
20091
+ },
20092
+ [applyChannels, registerEffect, resolveTargets]
20093
+ );
20094
+ const overrideEffect = useCallback18(
20095
+ (id, level, duration) => {
20096
+ const effect = effectsRef.current.get(id);
20097
+ if (!effect) return false;
20098
+ const now = typeof performance === "undefined" ? Date.now() : performance.now();
20099
+ effect.override = { level, until: now + Math.max(0, duration) };
20100
+ scheduleFrameRef.current();
20101
+ return true;
20102
+ },
20103
+ []
20104
+ );
20105
+ const stopEffect = useCallback18((id) => {
20106
+ return effectsRef.current.delete(id);
20107
+ }, []);
20108
+ const stopAllEffects = useCallback18(() => {
20109
+ effectsRef.current.clear();
20110
+ if (frameRef.current !== null && typeof cancelAnimationFrame !== "undefined") {
20111
+ cancelAnimationFrame(frameRef.current);
20112
+ frameRef.current = null;
20113
+ }
20114
+ }, []);
20115
+ const restoreRuntimeValues = useCallback18(() => {
20116
+ for (const [entry, snapshot] of materialSnapshotsRef.current) {
20117
+ applyMaterial(entry, snapshot);
20118
+ }
20119
+ materialSnapshotsRef.current.clear();
20120
+ for (const [node, snapshot] of transformSnapshotsRef.current) {
20121
+ node.position.set(...snapshot.position);
20122
+ node.rotation.set(...snapshot.rotation);
20123
+ node.scale.set(...snapshot.scale);
20124
+ node.visible = snapshot.visible;
20125
+ node.updateMatrix();
20126
+ node.updateMatrixWorld(true);
20127
+ }
20128
+ transformSnapshotsRef.current.clear();
20129
+ for (const runtime of lightsRef.current.values()) {
20130
+ runtime.light.removeFromParent();
20131
+ runtime.light.dispose();
20132
+ }
20133
+ lightsRef.current.clear();
20134
+ invalidate();
20135
+ }, [invalidate]);
20136
+ const resetRuntime = useCallback18(() => {
20137
+ stopAllEffects();
20138
+ restoreRuntimeValues();
20139
+ }, [restoreRuntimeValues, stopAllEffects]);
20140
+ const handleViewerReady = useCallback18(
20141
+ (viewer) => {
20142
+ if (sceneRef.current && sceneRef.current !== viewer.scene) {
20143
+ restoreRuntimeValues();
20144
+ }
20145
+ viewerRef.current = viewer;
20146
+ sceneRef.current = viewer.scene;
20147
+ optionsRef.current.onViewerReady?.(viewer);
20148
+ },
20149
+ [restoreRuntimeValues]
20150
+ );
20151
+ useEffect25(() => {
20152
+ return () => {
20153
+ resetRuntime();
20154
+ viewerRef.current = null;
20155
+ sceneRef.current = null;
20156
+ };
20157
+ }, [resetRuntime]);
20158
+ return {
20159
+ handleViewerReady,
20160
+ setMaterial,
20161
+ setTransform,
20162
+ getWorldPosition,
20163
+ addPointLight,
20164
+ updatePointLight,
20165
+ removePointLight,
20166
+ transition,
20167
+ timeline,
20168
+ pulse,
20169
+ flicker,
20170
+ overrideEffect,
20171
+ stopEffect,
20172
+ stopAllEffects,
20173
+ resetRuntime
20174
+ };
20175
+ }
20176
+
18880
20177
  // src/hooks/useShareableViewerState.ts
18881
- import { useCallback as useCallback17, useEffect as useEffect24, useRef as useRef22, useState as useState33 } from "react";
20178
+ import { useCallback as useCallback19, useEffect as useEffect26, useRef as useRef24, useState as useState34 } from "react";
18882
20179
  function serializeState(cameraState, objectBindings, selectedObjectBinding) {
18883
20180
  const hidden = Object.values(objectBindings).filter((binding) => binding.visible === false).map((binding) => binding.id);
18884
20181
  return {
@@ -18898,8 +20195,8 @@ function useShareableViewerState({
18898
20195
  debounceMs = 400,
18899
20196
  autoSync = true
18900
20197
  }) {
18901
- const lastWrittenRawRef = useRef22(null);
18902
- const buildHash = useCallback17(() => {
20198
+ const lastWrittenRawRef = useRef24(null);
20199
+ const buildHash = useCallback19(() => {
18903
20200
  const state = serializeState(
18904
20201
  cameraState,
18905
20202
  objectBindings,
@@ -18907,12 +20204,12 @@ function useShareableViewerState({
18907
20204
  );
18908
20205
  return `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
18909
20206
  }, [cameraState, objectBindings, selectedObjectBinding, paramKey]);
18910
- const getShareUrl = useCallback17(() => {
20207
+ const getShareUrl = useCallback19(() => {
18911
20208
  const url = new URL(window.location.href);
18912
20209
  url.hash = buildHash();
18913
20210
  return url.toString();
18914
20211
  }, [buildHash]);
18915
- const syncUrl = useCallback17(() => {
20212
+ const syncUrl = useCallback19(() => {
18916
20213
  const url = new URL(window.location.href);
18917
20214
  url.hash = buildHash();
18918
20215
  window.history.replaceState(null, "", url.toString());
@@ -18920,7 +20217,7 @@ function useShareableViewerState({
18920
20217
  paramKey
18921
20218
  );
18922
20219
  }, [buildHash, paramKey]);
18923
- const latestStateRef = useRef22({
20220
+ const latestStateRef = useRef24({
18924
20221
  cameraState,
18925
20222
  objectBindings,
18926
20223
  selectedObjectBinding
@@ -18930,9 +20227,9 @@ function useShareableViewerState({
18930
20227
  objectBindings,
18931
20228
  selectedObjectBinding
18932
20229
  };
18933
- const [restoreAttempted, setRestoreAttempted] = useState33(false);
18934
- const lastSyncedHashRef = useRef22(null);
18935
- useEffect24(() => {
20230
+ const [restoreAttempted, setRestoreAttempted] = useState34(false);
20231
+ const lastSyncedHashRef = useRef24(null);
20232
+ useEffect26(() => {
18936
20233
  if (!autoSync || !restoreAttempted) return;
18937
20234
  const tick = () => {
18938
20235
  const current = latestStateRef.current;
@@ -18954,9 +20251,9 @@ function useShareableViewerState({
18954
20251
  const id = window.setInterval(tick, debounceMs);
18955
20252
  return () => window.clearInterval(id);
18956
20253
  }, [autoSync, restoreAttempted, debounceMs, paramKey]);
18957
- const lastRestoredRawRef = useRef22(null);
18958
- const restoringRawRef = useRef22(null);
18959
- const restoreFromUrl = useCallback17(async () => {
20254
+ const lastRestoredRawRef = useRef24(null);
20255
+ const restoringRawRef = useRef24(null);
20256
+ const restoreFromUrl = useCallback19(async () => {
18960
20257
  setRestoreAttempted(true);
18961
20258
  const rawHash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
18962
20259
  const raw = new URLSearchParams(rawHash).get(paramKey);
@@ -19009,7 +20306,7 @@ function useShareableViewerState({
19009
20306
  setCameraState,
19010
20307
  setSelectedObjectBinding
19011
20308
  ]);
19012
- useEffect24(() => {
20309
+ useEffect26(() => {
19013
20310
  const handleHashChange = () => {
19014
20311
  void restoreFromUrl();
19015
20312
  };
@@ -19022,13 +20319,17 @@ function useShareableViewerState({
19022
20319
  }
19023
20320
  export {
19024
20321
  BindingBuilder,
20322
+ DEFAULT_SCENE_CONFIG,
19025
20323
  MATERIAL_BLENDING_MODES,
19026
20324
  MATERIAL_SIDES,
19027
20325
  ModelViewer,
19028
20326
  SimpleModelViewer,
20327
+ defineObjectBindings,
19029
20328
  material,
19030
20329
  patchBindings,
19031
20330
  patchSceneConfig,
20331
+ selectBindingKeys,
20332
+ selectBindings,
19032
20333
  useObjectBinding,
19033
20334
  useObjectBindingIds,
19034
20335
  useObjectBindings,
@@ -19037,6 +20338,8 @@ export {
19037
20338
  useViewerActions,
19038
20339
  useViewerAnimations,
19039
20340
  useViewerCamera,
20341
+ useViewerConnection,
20342
+ useViewerEffects,
19040
20343
  useViewerHover,
19041
20344
  useViewerModel,
19042
20345
  useViewerSelection