@vctrl/hooks 0.18.0 → 0.22.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +9 -9
  3. package/chunk-CMqjfN_6.cjs +1 -0
  4. package/index.cjs.js +1 -1
  5. package/index.es.js +3 -3
  6. package/package.json +67 -67
  7. package/use-export-model-8JbUalyf.cjs +1 -0
  8. package/{use-export-model-DzVKdqnY.js → use-export-model-BXg6L_W5.js} +5 -5
  9. package/use-export-model.cjs.js +1 -1
  10. package/use-export-model.es.js +1 -1
  11. package/use-load-model/error-helpers.d.ts +5 -0
  12. package/use-load-model/file-loaders.d.ts +12 -0
  13. package/use-load-model/index.d.ts +1 -1
  14. package/use-load-model/optimizer-integration.d.ts +16 -0
  15. package/use-load-model/scene-data-builder.d.ts +3 -0
  16. package/use-load-model/types.d.ts +13 -4
  17. package/use-load-model/use-load-model.d.ts +5 -16
  18. package/use-load-model-B9M4cF4B.cjs +1 -0
  19. package/use-load-model-BDCy9n2U.js +504 -0
  20. package/use-load-model.cjs.js +1 -1
  21. package/use-load-model.es.js +1 -1
  22. package/use-optimize-model/use-optimize-model.d.ts +14 -12
  23. package/use-optimize-model/utils/browser-texture-encoder.d.ts +41 -0
  24. package/use-optimize-model/utils/index.d.ts +2 -2
  25. package/use-optimize-model/utils/texture-optimization.d.ts +3 -13
  26. package/use-optimize-model-CPGezwrM.cjs +1 -0
  27. package/use-optimize-model-KAGxtv_k.js +296 -0
  28. package/use-optimize-model.cjs.js +1 -1
  29. package/use-optimize-model.es.js +1 -1
  30. package/GLTFLoader-CygFefRr.cjs +0 -1
  31. package/GLTFLoader-NiJ7qzh1.js +0 -1381
  32. package/__vite-browser-external-Bn_csQuM.js +0 -7
  33. package/__vite-browser-external-CVmj8TrW.cjs +0 -1
  34. package/chunk-foFoljWS.js +0 -23
  35. package/chunk-gGpyby2o.cjs +0 -1
  36. package/lib-DKn2MJ9X.js +0 -4111
  37. package/lib-DZ-MihdJ.cjs +0 -2
  38. package/model-exporter-SMSAY90N.cjs +0 -1
  39. package/model-exporter-cvguDGnn.js +0 -6512
  40. package/server-communication-Bj6ZxFw2.js +0 -4659
  41. package/server-communication-yXZR8eso.cjs +0 -18
  42. package/use-export-model-CaijtW1c.cjs +0 -1
  43. package/use-load-model-B6UXiFWh.js +0 -517
  44. package/use-load-model-CH-WPQ_s.cjs +0 -1
  45. package/use-optimize-model/utils/validation.d.ts +0 -8
  46. package/use-optimize-model-C-fzzKh6.cjs +0 -1
  47. package/use-optimize-model-Cb3WMvDZ.js +0 -299
package/CHANGELOG.md CHANGED
@@ -1,5 +1,65 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.22.0](https://github.com/Vectreal/vectreal-platform/compare/hooks-v0.21.0...hooks-v0.22.0) (2026-07-01)
4
+
5
+
6
+ ### Features
7
+
8
+ * enhance @vctrl/core documentation and improve package structure ([f74b8c8](https://github.com/Vectreal/vectreal-platform/commit/f74b8c881b0dad4ef35da0013492adfd5bde0ed9))
9
+ * enhance @vctrl/core documentation and improve package structure ([6599388](https://github.com/Vectreal/vectreal-platform/commit/65993883139d8fb0ab52839b14068276f482642a))
10
+ * Implement shadow bake persistence and garbage collection ([003f962](https://github.com/Vectreal/vectreal-platform/commit/003f962bcaf773fe58015412536fd5eb9b8c3f68))
11
+ * **optimization:** migrate to Supabase Edge Functions & Storage ([#554](https://github.com/Vectreal/vectreal-platform/issues/554)) ([420f94a](https://github.com/Vectreal/vectreal-platform/commit/420f94a5ee6c1cecf35b98d0bc9695922b2bf122))
12
+ * **viewer:** add accumulative shadow baking features and related components ([08427b6](https://github.com/Vectreal/vectreal-platform/commit/08427b6f4ea0e9ae2ea8849de2f0dd8627b0af07))
13
+
14
+
15
+ ### Dependencies
16
+
17
+ * The following workspace dependencies were updated
18
+ * dependencies
19
+ * @vctrl/core bumped to 0.22.0
20
+
21
+ ## [0.21.0](https://github.com/Vectreal/vectreal-platform/compare/hooks-v0.20.0...hooks-v0.21.0) (2026-05-21)
22
+
23
+
24
+ ### Features
25
+
26
+ * add local Google Cloud Storage setup instructions and configura… ([#499](https://github.com/Vectreal/vectreal-platform/issues/499)) ([8448285](https://github.com/Vectreal/vectreal-platform/commit/84482854455dcd1993e96fcad1275116df16a4b2))
27
+
28
+
29
+ ### Dependencies
30
+
31
+ * The following workspace dependencies were updated
32
+ * dependencies
33
+ * @vctrl/core bumped to 0.21.0
34
+
35
+ ## [0.20.0](https://github.com/Vectreal/vectreal-platform/compare/hooks-v0.19.0...hooks-v0.20.0) (2026-05-12)
36
+
37
+
38
+ ### Miscellaneous Chores
39
+
40
+ * **hooks:** Synchronize vectreal-monorepo versions
41
+
42
+
43
+ ### Dependencies
44
+
45
+ * The following workspace dependencies were updated
46
+ * dependencies
47
+ * @vctrl/core bumped to 0.20.0
48
+
49
+ ## [0.19.0](https://github.com/Vectreal/vectreal-platform/compare/hooks-v0.18.0...hooks-v0.19.0) (2026-05-10)
50
+
51
+
52
+ ### Bug Fixes
53
+
54
+ * eliminate Vite v8 build warnings — upgrade plugin-react to v6, suppress Node.js dynamic import noise ([#445](https://github.com/Vectreal/vectreal-platform/issues/445)) ([211e188](https://github.com/Vectreal/vectreal-platform/commit/211e188b00130486c38904fe2cff584261a6702f))
55
+
56
+
57
+ ### Dependencies
58
+
59
+ * The following workspace dependencies were updated
60
+ * dependencies
61
+ * @vctrl/core bumped to 0.19.0
62
+
3
63
  ## [0.18.0](https://github.com/Vectreal/vectreal-platform/compare/hooks-v0.17.0...hooks-v0.18.0) (2026-04-30)
4
64
 
5
65
 
package/README.md CHANGED
@@ -120,7 +120,7 @@ export default function App() {
120
120
 
121
121
  ## `useOptimizeModel`
122
122
 
123
- Runs mesh simplification and texture compression using [glTF-Transform](https://gltf-transform.dev) in a Web Worker.
123
+ Runs mesh simplification, deduplication, quantization, and normals optimization using [glTF-Transform](https://gltf-transform.dev) — geometry passes run in a Web Worker. Texture compression runs in the main thread via browser-native OffscreenCanvas encoding.
124
124
 
125
125
  ```tsx
126
126
  import { useOptimizeModel } from '@vctrl/hooks/use-optimize-model'
@@ -166,15 +166,15 @@ function Optimizer() {
166
166
 
167
167
  `useOptimizeModel` methods map directly to `@vctrl/core/model-optimizer` option types:
168
168
 
169
- | Method | Option fields |
170
- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
171
- | `simplifyOptimization` | `ratio?: number`, `error?: number` |
172
- | `dedupOptimization` | `textures?: boolean`, `materials?: boolean`, `meshes?: boolean`, `accessors?: boolean` |
173
- | `quantizeOptimization` | `quantizePosition?: number`, `quantizeNormal?: number`, `quantizeColor?: number`, `quantizeTexcoord?: number` |
174
- | `normalsOptimization` | `overwrite?: boolean` |
175
- | `texturesOptimization` | `resize?: [number, number]`, `targetFormat?: 'webp' \| 'jpeg' \| 'png'`, `quality?: number`, `requestTimeoutMs?: number`, `maxTextureUploadBytes?: number`, `maxRetries?: number`, `maxConcurrentRequests?: number`, `serverOptions?: ServerOptions` |
169
+ | Method | Option fields |
170
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------- |
171
+ | `simplifyOptimization` | `ratio?: number`, `error?: number` |
172
+ | `dedupOptimization` | `textures?: boolean`, `materials?: boolean`, `meshes?: boolean`, `accessors?: boolean` |
173
+ | `quantizeOptimization` | `quantizePosition?: number`, `quantizeNormal?: number`, `quantizeColor?: number`, `quantizeTexcoord?: number` |
174
+ | `normalsOptimization` | `overwrite?: boolean` |
175
+ | `texturesOptimization` | `resize?: [number, number]`, `targetFormat?: 'webp' \| 'jpeg' \| 'png'`, `quality?: number` |
176
176
 
177
- `serverOptions` is provided by `@vctrl/core` and supports endpoint, API key, and headers.
177
+ Texture compression runs browser-native via `OffscreenCanvas`. No server call is made.
178
178
 
179
179
  ---
180
180
 
@@ -0,0 +1 @@
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return s}});
package/index.cjs.js CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require(`./chunk-gGpyby2o.cjs`);const e=require(`./use-load-model-CH-WPQ_s.cjs`),t=require(`./use-export-model-CaijtW1c.cjs`),n=require(`./use-optimize-model-C-fzzKh6.cjs`);exports.reconstructGltfFiles=e.n,exports.useExportModel=t.t,exports.useLoadModel=e.t,exports.useOptimizeModel=n.t;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./use-load-model-B9M4cF4B.cjs"),t=require("./use-export-model-8JbUalyf.cjs"),n=require("./use-optimize-model-CPGezwrM.cjs");exports.reconstructGltfFiles=e.n,exports.useExportModel=t.t,exports.useLoadModel=e.t,exports.useOptimizeModel=n.t;
package/index.es.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as e, t } from "./use-load-model-B6UXiFWh.js";
2
- import { t as n } from "./use-export-model-DzVKdqnY.js";
3
- import { t as r } from "./use-optimize-model-Cb3WMvDZ.js";
1
+ import { n as e, t } from "./use-load-model-BDCy9n2U.js";
2
+ import { t as n } from "./use-export-model-BXg6L_W5.js";
3
+ import { t as r } from "./use-optimize-model-KAGxtv_k.js";
4
4
  export { e as reconstructGltfFiles, n as useExportModel, t as useLoadModel, r as useOptimizeModel };
package/package.json CHANGED
@@ -1,68 +1,68 @@
1
1
  {
2
- "version": "0.18.0",
3
- "name": "@vctrl/hooks",
4
- "description": "vctrl/hooks is a React hooks package designed to simplify 3D model loading and management within React applications. It's part of the vectreal-core ecosystem and is primarily used in the vctrl/viewer React component and the official website application.",
5
- "bugs": {
6
- "url": "https://github.com/vectreal/vectreal-platform/issues"
7
- },
8
- "homepage": "https://vectreal.com",
9
- "keywords": [
10
- "vectreal",
11
- "react",
12
- "3d",
13
- "model",
14
- "viewer",
15
- "react-component",
16
- "react-three-fiber",
17
- "react-three-drei",
18
- "threejs",
19
- "react-three-gltf",
20
- "gltf",
21
- "glb",
22
- "usdz",
23
- "three-model-loading",
24
- "model-viewer"
25
- ],
26
- "repository": {
27
- "type": "git",
28
- "url": "git+https://github.com/vectreal/vectreal-platform.git"
29
- },
30
- "license": "AGPL-3.0-only",
31
- "types": "./index.d.ts",
32
- "main": "./index.cjs.js",
33
- "module": "./index.es.js",
34
- "type": "module",
35
- "publishConfig": {
36
- "access": "public"
37
- },
38
- "exports": {
39
- "./use-load-model": {
40
- "types": "./use-load-model/index.d.ts",
41
- "import": "./use-load-model.es.js",
42
- "require": "./use-load-model.cjs.js"
43
- },
44
- "./use-export-model": {
45
- "types": "./use-export-model/index.d.ts",
46
- "import": "./use-export-model.es.js",
47
- "require": "./use-export-model.cjs.js"
48
- },
49
- "./use-optimize-model": {
50
- "types": "./use-optimize-model/index.d.ts",
51
- "import": "./use-optimize-model.es.js",
52
- "require": "./use-optimize-model.cjs.js"
53
- }
54
- },
55
- "peerDependencies": {
56
- "react": "^18.0.0 || ^19.0.0",
57
- "@gltf-transform/core": "^4.0.8",
58
- "file-saver": "^2.0.5",
59
- "@nx/vite": "22.5.0",
60
- "@vitejs/plugin-react": "^4.3.4",
61
- "vite": "^7.3.1",
62
- "vite-plugin-dts": "4.5.3"
63
- },
64
- "dependencies": {
65
- "@vctrl/core": "workspace:*",
66
- "three": "^0.177.0"
67
- }
68
- }
2
+ "version": "0.22.0",
3
+ "name": "@vctrl/hooks",
4
+ "description": "vctrl/hooks is a React hooks package designed to simplify 3D model loading and management within React applications. It's part of the vectreal-core ecosystem and is primarily used in the vctrl/viewer React component and the official website application.",
5
+ "bugs": {
6
+ "url": "https://github.com/vectreal/vectreal-platform/issues"
7
+ },
8
+ "homepage": "https://vectreal.com",
9
+ "keywords": [
10
+ "vectreal",
11
+ "react",
12
+ "3d",
13
+ "model",
14
+ "viewer",
15
+ "react-component",
16
+ "react-three-fiber",
17
+ "react-three-drei",
18
+ "threejs",
19
+ "react-three-gltf",
20
+ "gltf",
21
+ "glb",
22
+ "usdz",
23
+ "three-model-loading",
24
+ "model-viewer"
25
+ ],
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/vectreal/vectreal-platform.git"
29
+ },
30
+ "license": "AGPL-3.0-only",
31
+ "types": "./index.d.ts",
32
+ "main": "./index.cjs.js",
33
+ "module": "./index.es.js",
34
+ "type": "module",
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "exports": {
39
+ ".": {
40
+ "types": "./index.d.ts",
41
+ "import": "./index.es.js",
42
+ "require": "./index.cjs.js"
43
+ },
44
+ "./use-load-model": {
45
+ "types": "./use-load-model/index.d.ts",
46
+ "import": "./use-load-model.es.js",
47
+ "require": "./use-load-model.cjs.js"
48
+ },
49
+ "./use-export-model": {
50
+ "types": "./use-export-model/index.d.ts",
51
+ "import": "./use-export-model.es.js",
52
+ "require": "./use-export-model.cjs.js"
53
+ },
54
+ "./use-optimize-model": {
55
+ "types": "./use-optimize-model/index.d.ts",
56
+ "import": "./use-optimize-model.es.js",
57
+ "require": "./use-optimize-model.cjs.js"
58
+ }
59
+ },
60
+ "peerDependencies": {
61
+ "react": "^18.0.0 || ^19.0.0",
62
+ "three": "^0.177.0"
63
+ },
64
+ "dependencies": {
65
+ "@vctrl/core": "^0.22.0",
66
+ "file-saver": "^2.0.5"
67
+ }
68
+ }
@@ -0,0 +1 @@
1
+ const e=require("./chunk-CMqjfN_6.cjs");let t=require("react"),n=require("@vctrl/core/model-exporter"),r=require("file-saver");r=e.t(r,1);var i=(e,i)=>{let a=(0,t.useRef)(new n.ModelExporter);return{handleThreeGltfExport:(0,t.useCallback)(async(t,n)=>{let o=t?.model;if(!o){console.error(`Scene not initialized`);return}try{let i=a.current,s=t.name.replace(/\.[^/.]+$/,``);if(n){let e=await i.exportThreeJSGLB(o,{});if(!(e.data instanceof Uint8Array))throw Error(`Expected GLB export to return Uint8Array`);r.default.saveAs(new Blob([new Uint8Array(e.data)]),`${s}.glb`)}else{let e=await i.exportThreeJSGLTF(o),t=await i.createZIPArchive(e,s);r.default.saveAs(new Blob([new Uint8Array(t)]),`${s}.zip`)}e&&e()}catch(e){console.error(`Export failed:`,e),i&&i(e)}},[e,i]),handleDocumentGltfExport:(0,t.useCallback)(async function(t,n,o=!1,s=!0){try{let i=a.current,c=n?.name.replace(/\.[^/.]+$/,``)||`model`;if(o){let e=await i.exportDocumentGLB(t);r.default.saveAs(new Blob([new Uint8Array(e.data)]),`${c}.glb`)}else{let e=await i.exportDocumentGLTF(t);if(!s)return e;let n=await i.createZIPArchive(e,c);r.default.saveAs(new Blob([new Uint8Array(n)]),`${c}.zip`)}e&&e()}catch(e){console.error(`Export failed:`,e),i&&i(e)}},[i,e])}};Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return i}});
@@ -1,11 +1,11 @@
1
- import { t as e } from "./model-exporter-cvguDGnn.js";
2
- import { useCallback as t, useRef as n } from "react";
1
+ import { useCallback as e, useRef as t } from "react";
2
+ import { ModelExporter as n } from "@vctrl/core/model-exporter";
3
3
  import r from "file-saver";
4
4
  //#region packages/hooks/src/use-export-model/use-export-model.ts
5
5
  var i = (i, a) => {
6
- let o = n(new e());
6
+ let o = t(new n());
7
7
  return {
8
- handleThreeGltfExport: t(async (e, t) => {
8
+ handleThreeGltfExport: e(async (e, t) => {
9
9
  let n = e?.model;
10
10
  if (!n) {
11
11
  console.error("Scene not initialized");
@@ -26,7 +26,7 @@ var i = (i, a) => {
26
26
  console.error("Export failed:", e), a && a(e);
27
27
  }
28
28
  }, [i, a]),
29
- handleDocumentGltfExport: t(async function(e, t, n = !1, s = !0) {
29
+ handleDocumentGltfExport: e(async function(e, t, n = !1, s = !0) {
30
30
  try {
31
31
  let a = o.current, c = t?.name.replace(/\.[^/.]+$/, "") || "model";
32
32
  if (n) {
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require(`./chunk-gGpyby2o.cjs`);const e=require(`./use-export-model-CaijtW1c.cjs`);exports.useExportModel=e.t;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./use-export-model-8JbUalyf.cjs");exports.useExportModel=e.t;
@@ -1,2 +1,2 @@
1
- import { t as e } from "./use-export-model-DzVKdqnY.js";
1
+ import { t as e } from "./use-export-model-BXg6L_W5.js";
2
2
  export { e as useExportModel };
@@ -0,0 +1,5 @@
1
+ import { StructuredLoadError } from './types';
2
+ export declare const createStructuredLoadError: ({ code, message, recoverable, source, cause, context }: StructuredLoadError) => StructuredLoadError;
3
+ export declare const toErrorMessage: (error: unknown) => string;
4
+ export declare const normalizeLocalLoadError: (error: unknown, code: StructuredLoadError["code"], context?: Record<string, unknown>) => StructuredLoadError;
5
+ export declare const normalizeServerLoadError: (error: unknown, sceneId: string) => StructuredLoadError;
@@ -0,0 +1,12 @@
1
+ import { ModelFileTypes, ModelLoader } from '../../../core/src/model-loader/index.ts';
2
+ import { default as React } from 'react';
3
+ import { Action, ModelFile } from './types';
4
+ import { useOptimizeModel } from '../use-optimize-model';
5
+ type Optimizer = ReturnType<typeof useOptimizeModel> | undefined;
6
+ type Dispatch = React.Dispatch<Action>;
7
+ export declare const loadBinaryModel: (file: File, fileType: ModelFileTypes, modelLoader: ModelLoader, optimizer: Optimizer, dispatch: Dispatch) => Promise<ModelFile | null>;
8
+ export declare const loadGltfModel: (gltfFile: File, otherFiles: File[], modelLoader: ModelLoader, optimizer: Optimizer, dispatch: Dispatch, calculateReferencedBytesFromFiles: (gltfFile: File, otherFiles: File[]) => Promise<{
9
+ sourcePackageBytes: number;
10
+ textureBytes: number;
11
+ }>) => Promise<ModelFile | null>;
12
+ export {};
@@ -1,4 +1,4 @@
1
1
  export { default as useLoadModel } from './use-load-model';
2
- export { type EventHandler, type EventTypes, type InputFileOrDirectory, type LoadData, type ModelFile, type SceneDataLoadOptions, type SceneLoadOptions, type SceneLoadResult, type UseLoadModelReturn, type ServerSceneData } from './types';
2
+ export { type EventHandler, type EventTypes, type InputFileOrDirectory, type LoadData, type ModelFile, type SceneDataLoadOptions, type SceneLoadOptions, type SceneLoadResult, type StructuredLoadError, type UseLoadModelReturn, type ViewerLoadErrorCode, type ServerSceneData } from './types';
3
3
  export { reconstructGltfFiles } from './utils/reconstruct-files';
4
4
  export * from './model-context';
@@ -0,0 +1,16 @@
1
+ import { ModelLoader } from '../../../core/src/model-loader/index.ts';
2
+ import { default as React } from 'react';
3
+ import { Action, ModelFile, OptimizerIntegrationReturn } from './types';
4
+ import { useOptimizeModel } from '../use-optimize-model';
5
+ /**
6
+ * Integrates the optimizer into the model loading process.
7
+ *
8
+ * Provides the `applyOptimization` method that:
9
+ * 1. Runs an optional optimization function
10
+ * 2. Exports the optimized model as GLB
11
+ * 3. Reloads it into Three.js
12
+ * 4. Updates the model state
13
+ *
14
+ * Returns `null` when no optimizer instance is provided.
15
+ */
16
+ export declare function useOptimizerIntegration(instance: ReturnType<typeof useOptimizeModel> | undefined, dispatch: React.Dispatch<Action>, file: ModelFile | null, modelLoader: ModelLoader): OptimizerIntegrationReturn<boolean>;
@@ -0,0 +1,3 @@
1
+ import { ServerSceneData } from '../../../core/src/index.ts';
2
+ export declare const collectReferencedUris: (gltfJson: unknown) => Set<string>;
3
+ export declare const buildSceneDataFromLocalFiles: (gltfFile: File, assetFiles: File[]) => Promise<ServerSceneData>;
@@ -92,6 +92,15 @@ export type Action = {
92
92
  * Used for subscribing to various stages of the loading process.
93
93
  */
94
94
  export type EventTypes = 'multiple-models' | 'not-loaded-files' | 'load-start' | 'load-progress' | 'load-complete' | 'load-reset' | 'load-error' | 'server-load-start' | 'server-load-complete' | 'server-load-error';
95
+ export type ViewerLoadErrorCode = 'unsupported_format' | 'binary_load_failed' | 'gltf_load_failed' | 'missing_assets' | 'server_load_failed' | 'not_found' | 'quota_exceeded' | 'unknown';
96
+ export interface StructuredLoadError {
97
+ code: ViewerLoadErrorCode;
98
+ message: string;
99
+ recoverable: boolean;
100
+ source: 'local-upload' | 'server-load';
101
+ cause?: unknown;
102
+ context?: Record<string, unknown>;
103
+ }
95
104
  /**
96
105
  * Maps event types to their corresponding data payloads.
97
106
  * Ensures type safety when handling events.
@@ -109,14 +118,14 @@ export type EventData = {
109
118
  'load-complete': LoadData['file'];
110
119
  /** No data for reset event */
111
120
  'load-reset': null;
112
- /** Error object when loading fails */
113
- 'load-error': Error | unknown;
121
+ /** Normalized error payload when loading fails */
122
+ 'load-error': StructuredLoadError | Error | unknown;
114
123
  /** Scene ID being loaded from server */
115
124
  'server-load-start': string;
116
125
  /** Complete scene load result */
117
126
  'server-load-complete': SceneLoadResult;
118
- /** Error during server scene loading */
119
- 'server-load-error': Error | unknown;
127
+ /** Normalized error payload during server scene loading */
128
+ 'server-load-error': StructuredLoadError | Error | unknown;
120
129
  };
121
130
  /**
122
131
  * Type-safe event handler function.
@@ -3,24 +3,13 @@ import { useOptimizeModel } from '../use-optimize-model';
3
3
  /**
4
4
  * Custom hook to load and manage 3D models with optional optimization integration.
5
5
  *
6
- * This hook provides a complete solution for loading 3D models (GLTF, GLB, USDZ) with
7
- * optional integration of model optimization capabilities. The return type is conditionally
8
- * typed based on whether an optimizer is provided.
6
+ * Supports GLTF, GLB, and USDZ formats via file upload or server-side scene loading.
7
+ * When an optimizer is provided, each load path initialises the optimizer so texture
8
+ * names and URIs are preserved for subsequent compression or simplification passes.
9
9
  *
10
10
  * **Type Safety:**
11
- * - When called with an optimizer: `optimizer` property is fully typed with optimization methods
12
- * - When called without an optimizer: `optimizer` property is typed as `null`
13
- *
14
- * @example
15
- * // With optimizer integration
16
- * const optimizer = useOptimizeModel()
17
- * const model = useLoadModel(optimizer)
18
- * model.optimizer.applyOptimization() // ✅ Fully typed
19
- *
20
- * @example
21
- * // Without optimizer
22
- * const model = useLoadModel()
23
- * model.optimizer // ✅ Typed as null
11
+ * - With optimizer: `optimizer` property is fully typed with optimization methods
12
+ * - Without optimizer: `optimizer` property is typed as `null`
24
13
  *
25
14
  * @template T - The type of the optimizer parameter (inferred automatically)
26
15
  * @param optimizer - Optional optimizer hook returned from useOptimizeModel
@@ -0,0 +1 @@
1
+ let e=require("@vctrl/core/model-loader"),t=require("react"),n=require("@vctrl/core");var r=({code:e,message:t,recoverable:n,source:r,cause:i,context:a})=>({code:e,message:t,recoverable:n,source:r,cause:i,context:a}),i=e=>e instanceof Error&&e.message?e.message:typeof e==`string`&&e.trim().length>0?e:`Unknown error`,a=(e,t,n)=>{let a=i(e);return r({code:a.includes(`missing required referenced assets`)?`missing_assets`:t,message:a,recoverable:!0,source:`local-upload`,cause:e,context:n})},o=(e,t)=>{let n=i(e),a=`server_load_failed`;return n.includes(`Server responded with 404`)?a=`not_found`:(n.includes(`Server responded with 402`)||n.includes(`Server responded with 403`))&&(a=`quota_exceeded`),r({code:a,message:n,recoverable:a!==`not_found`,source:`server-load`,cause:e,context:{sceneId:t}})},s={},c={emit(e,t){let n=s[e];console.debug(`Event emitted: ${e}`,t),n&&n.forEach(e=>e(t))},on(e,t){s[e]||(s[e]=[]),s[e].push(t)},off(e,t){let n=s[e];n&&(s[e]=n.filter(e=>e!==t))}},l=e=>{try{return(0,n.normalizeAssetUri)(e)}catch{return e}},u=e=>{let t=new Set,n=e,r=Array.isArray(n.images)?n.images:[];for(let e of r)typeof e.uri==`string`&&!e.uri.startsWith(`data:`)&&t.add(e.uri);let i=Array.isArray(n.buffers)?n.buffers:[];for(let e of i)typeof e.uri==`string`&&!e.uri.startsWith(`data:`)&&t.add(e.uri);return t},d=async(e,t)=>{let r=JSON.parse(await e.text()),i=u(r),a={},o=new Map,s=new Map,c=new Set,d=async e=>{let t=s.get(e);if(t)return t;let n=new Uint8Array(await e.arrayBuffer());return s.set(e,n),n};for(let e of t){for(let t of(0,n.buildAssetLookupKeys)(e.name))o.set(t,e);if(e.webkitRelativePath)for(let t of(0,n.buildAssetLookupKeys)(e.webkitRelativePath))o.set(t,e)}let f=[];for(let e of i){let t=l(e),n=t.split(`/`).pop()||t,r=o.get(e)||o.get(t)||o.get(n);if(!r){f.push(e);continue}c.add(r);let i=await d(r);a[t]={data:Array.from(i),fileName:t,mimeType:r.type||`application/octet-stream`}}if(f.length>0)throw Error(`Scene payload is missing required referenced assets: ${f.slice(0,5).join(`, `)}`);for(let[e,n]of t.entries()){if(c.has(n))continue;let t=l(n.name),r=t.split(`/`).pop()||t,i=await d(n);a[`extra-${e}-${r}`]={data:Array.from(i),fileName:r,mimeType:n.type||`application/octet-stream`}}return{gltfJson:r,assetData:a}},f=async(t,n,r,i,o)=>{try{let a={model:(await r.loadToThreeJS(t)).scene,type:n,name:t.name};if(o({type:`set-file`,payload:a}),o({type:`set-file-loading`,payload:!1}),i&&n!==e.ModelFileTypes.usdz){let e=new Uint8Array(await t.arrayBuffer());await i.loadFromGlbBuffer(e)}return a}catch(e){return console.error(`Error loading binary model:`,e),o({type:`set-file-loading`,payload:!1}),c.emit(`load-error`,a(e,`binary_load_failed`,{fileName:t.name,fileType:n,fileSize:t.size})),null}},p=async(t,n,r,i,o,s)=>{try{let{sourcePackageBytes:a,textureBytes:c}=await s(t,n),l=await r.loadGLTFWithAssetsToThreeJS(t,n),u={model:l.scene,type:e.ModelFileTypes.gltf,name:t.name,sourcePackageBytes:a,sourceTextureBytes:c};if(o({type:`set-file`,payload:u}),o({type:`set-file-loading`,payload:!1}),i)try{let e=await d(t,n);await i.loadFromServerSceneData(e)}catch(e){console.warn(`Failed to initialize optimizer from source GLTF payload; trying direct asset load.`,e);try{if(`loadFromGLTFWithAssets`in i){let e=new Uint8Array(await t.arrayBuffer()),r=new Map;for(let e of n){let t=new Uint8Array(await e.arrayBuffer());r.set(e.name,t),e.webkitRelativePath&&r.set(e.webkitRelativePath,t)}await i.loadFromGLTFWithAssets(e,r)}else throw Error(`loadFromGLTFWithAssets not available`)}catch(e){console.warn(`Direct asset load also failed; falling back to Three.js scene import. Texture names may be generic.`,e),await i.load(l.scene)}}return u}catch(e){return console.error(`Error loading GLTF model:`,e),console.error(`GLTF file:`,t.name),console.error(`Asset files:`,n.map(e=>e.name)),o({type:`set-file-loading`,payload:!1}),c.emit(`load-error`,a(e,`gltf_load_failed`,{fileName:t.name,fileSize:t.size,assetCount:n.length})),null}};function m(n,r,i,a){let o=(0,t.useCallback)(async(t,o)=>{if(!n){console.warn(`Optimizer is not available`);return}try{t&&await t(o);let s=await n.getModel();if(!s){console.warn(`No optimized model available after optimization`);return}let c=s instanceof Uint8Array?s.slice():new Uint8Array(s).slice(),l=new File([c],i?.name||`optimized_model.glb`,{type:`model/gltf-binary`});r({type:`set-file`,payload:{model:(await a.loadToThreeJS(l)).scene,type:e.ModelFileTypes.glb,name:l.name}})}catch(e){console.error(`Optimization failed:`,e)}},[n,a,i,r]);if(!n)return null;let s=n.isReady,c=!!i?.model&&!s;return{...n,isReady:s,isPreparing:c,applyOptimization:o,reset:n?.reset,error:n?.error,loading:n?.loading}}var h={file:null,isFileLoading:!1,progress:0,supportedFileTypes:[e.ModelFileTypes.gltf,e.ModelFileTypes.glb,e.ModelFileTypes.usdz]};function g(e,t){switch(t.type){case`set-file`:return{...e,file:t.payload};case`set-file-loading`:return{...e,isFileLoading:t.payload};case`set-progress`:return{...e,progress:t.payload};case`reset-state`:return{...h};default:return e}}async function _(e){let t=[];async function*n(e){let t=e;for await(let[,e]of t)e.kind===`file`?yield await e.getFile():e.kind===`directory`&&(yield*n(e))}for await(let r of n(e))t.push(r);return t}function v(e){let t=[];if(e.assetData&&typeof e.assetData==`object`)for(let[,r]of Object.entries(e.assetData)){let{fileName:e,mimeType:i}=r;if(e===n.PERSISTED_BAKE_FILENAME)continue;let a=(0,n.toSerializedAssetBytes)(r),o=new Uint8Array(a.byteLength);o.set(a);let s=new Blob([o],{type:i}),c=new File([s],e,{type:i});t.push(c)}let r=JSON.stringify(e.gltfJson),i=new Blob([r],{type:`model/gltf+json`}),a=e.meta?.name,o=a?`${a}.gltf`:`scene.gltf`;return[new File([i],o,{type:`model/gltf+json`}),...t]}var y=(e,t)=>{let r=(0,n.normalizeAssetUri)(e),i=r.split(`/`).pop()||r;for(let e of t){let t=(0,n.buildAssetLookupKeys)(e.fileName);if(t.has(r)||t.has(i))return e.size}return 0},b=e=>{let t=new Set,n=new Set,r=e,i=Array.isArray(r.images)?r.images:[];for(let e of i)typeof e.uri==`string`&&!e.uri.startsWith(`data:`)&&n.add(e.uri);let a=Array.isArray(r.buffers)?r.buffers:[];for(let e of a)typeof e.uri==`string`&&!e.uri.startsWith(`data:`)&&t.add(e.uri);return{bufferUris:t,imageUris:n}};async function x(e,t){let n=await e.text(),r;try{r=JSON.parse(n)}catch{return{sourcePackageBytes:e.size,textureBytes:0}}let i=t.map(e=>({fileName:e.name,size:e.size})),{bufferUris:a,imageUris:o}=b(r),s=Array.from(a).reduce((e,t)=>e+y(t,i),0),c=Array.from(o).reduce((e,t)=>e+y(t,i),0);return{sourcePackageBytes:e.size+s+c,textureBytes:c}}function S(e){let t=e.gltfJson,r=Object.values(e.assetData||{}).map(e=>({fileName:e.fileName,size:(0,n.getSerializedAssetByteSize)(e.data)})),i=new TextEncoder().encode(JSON.stringify(t)).byteLength,{bufferUris:a,imageUris:o}=b(t),s=Array.from(a).reduce((e,t)=>e+y(t,r),0),c=Array.from(o).reduce((e,t)=>e+y(t,r),0);return{sourcePackageBytes:i+s+c,textureBytes:c}}function C(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function w(e){let t=C(e.settings)?e.settings:{},r=(0,n.normalizeCameraSettings)(e.camera??t.camera);return{...t,bounds:e.bounds??t.bounds,camera:r,interactions:(0,n.normalizeSceneInteractions)(t.interactions,{camera:r}),controls:e.controls??t.controls,environment:e.environment??t.environment,shadows:e.shadows??t.shadows}}function T(e){let t=new Set,n=e,r=Array.isArray(n.images)?n.images:[];for(let e of r)typeof e.uri==`string`&&!e.uri.startsWith(`data:`)&&t.add(e.uri);let i=Array.isArray(n.buffers)?n.buffers:[];for(let e of i)typeof e.uri==`string`&&!e.uri.startsWith(`data:`)&&t.add(e.uri);return t}function E(e){for(let[t,r]of Object.entries(e)){if(!r||typeof r!=`object`)throw Error(`Scene payload contains invalid asset entry for ${t}`);if(typeof r.fileName!=`string`||r.fileName.trim().length===0)throw Error(`Scene payload asset ${t} is missing a valid fileName`);if(typeof r.mimeType!=`string`||r.mimeType.trim().length===0)throw Error(`Scene payload asset ${t} is missing a valid mimeType`);if(typeof r.data==`string`&&r.encoding===`base64`&&!(0,n.isValidBase64)(r.data))throw Error(`Scene payload asset ${r.fileName} has invalid base64 data`)}}function D(e,t){let r=T(e);if(r.size===0)return;let i=new Set;for(let e of Object.values(t))for(let t of(0,n.buildAssetLookupKeys)(e.fileName))i.add(t);let a=[];for(let e of r){let t=(0,n.normalizeAssetUri)(e),r=t.split(`/`).pop()||t;!i.has(e)&&!i.has(t)&&!i.has(r)&&a.push(e)}if(a.length>0)throw Error(`Scene payload is missing required referenced assets: ${a.slice(0,5).join(`, `)}`)}function O(e){if(!C(e.gltfJson))throw Error(`Scene payload is missing a valid glTF document`);let t=e.assetData??{};return E(t),D(e.gltfJson,t),{meta:e.meta,gltfJson:e.gltfJson,assetData:t,...w(e)}}function k(n){let r=(0,t.useRef)(!1),i=(0,t.useRef)(null),[a,s]=(0,t.useReducer)(g,h),l=(0,t.useMemo)(()=>{let t=new e.ModelLoader;return t.onProgress(e=>{s({type:`set-progress`,payload:e.progress}),e.progress!==100&&c.emit(`load-progress`,e.progress)}),t},[]),u=(0,t.useCallback)(e=>{s({type:`set-progress`,payload:e}),c.emit(`load-progress`,e)},[]),d=(0,t.useCallback)(()=>{s({type:`reset-state`}),c.emit(`load-reset`)},[]),y=(0,t.useCallback)((e,t)=>e.find(e=>e.name.endsWith(`.`+t)),[]),b=(0,t.useCallback)(async t=>{if(t.length===0)return;let a=y(t,e.ModelFileTypes.gltf),o=y(t,e.ModelFileTypes.glb),u=y(t,e.ModelFileTypes.usdz),d=[a,o,u].filter(Boolean);if(d.length>1){c.emit(`multiple-models`,d);return}let m=t.filter(e=>e!==a&&e!==o&&e!==u),h=()=>{r.current=!0,i.current&&(c.emit(`load-complete`,i.current),r.current=!1)};if(a){let e=await p(a,m,l,n,s,x);e&&(i.current=e),h()}else if(o){let t=await f(o,e.ModelFileTypes.glb,l,n,s);t&&(i.current=t),h()}else if(u){let t=await f(u,e.ModelFileTypes.usdz,l,n,s);t&&(i.current=t),h()}else c.emit(`not-loaded-files`,t),s({type:`set-file-loading`,payload:!1})},[y,l,n]),C=(0,t.useCallback)(async e=>{let t=[];c.emit(`load-start`),s({type:`reset-state`}),s({type:`set-file-loading`,payload:!0}),u(0),r.current=!1;for(let n of e)if(n instanceof File)t.push(n);else if(`kind`in n&&n.kind===`directory`){let e=await _(n);t.push(...e)}await b(t)},[b,u]),w=(0,t.useCallback)(async t=>{let{sceneData:r,sceneId:a}=t;u(40);let o=v(r),{sourcePackageBytes:c,textureBytes:d}=S(r);u(60);let f=o[0],p=o.slice(1),m={model:(await l.loadGLTFWithAssetsToThreeJS(f,p)).scene,type:e.ModelFileTypes.gltf,name:f.name,sourcePackageBytes:c,sourceTextureBytes:d};return s({type:`set-file`,payload:m}),s({type:`set-file-loading`,payload:!1}),i.current=m,n&&await n.loadFromServerSceneData(r),u(100),{file:m,sceneId:a,...r}},[l,n,u]),T=(0,t.useCallback)(async e=>{let{sceneId:t,serverOptions:n}=e;try{c.emit(`server-load-start`,t),s({type:`reset-state`}),s({type:`set-file-loading`,payload:!0}),u(0);let e=new FormData;e.append(`action`,`get-scene-settings`),e.append(`sceneId`,t);let r=n?.endpoint??`/api/scenes/${t}`,i=n?.apiKey?{Authorization:`Bearer ${n.apiKey}`,...n.headers}:{...n?.headers},a=await fetch(r,{method:`POST`,headers:i,body:e});if(!a.ok)throw Error(`Server responded with ${a.status} ${a.statusText}`);let o=await a.json(),l=await w({sceneId:t,sceneData:O(o.data??o)});return c.emit(`server-load-complete`,l),l}catch(e){console.error(`Server scene loading failed:`,e),s({type:`set-file-loading`,payload:!1});let n=o(e,t);throw c.emit(`server-load-error`,n),n}},[w,u]),E=m(n,s,a.file,l);return{...a,on:c.on,off:c.off,load:C,loadFromData:w,loadFromServer:T,reset:d,optimizer:E}}Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return v}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return k}});