@vctrl/hooks 0.17.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.
- package/CHANGELOG.md +74 -0
- package/README.md +9 -9
- package/chunk-CMqjfN_6.cjs +1 -0
- package/index.cjs.js +1 -1
- package/index.es.js +3 -3
- package/package.json +67 -67
- package/use-export-model-8JbUalyf.cjs +1 -0
- package/{use-export-model-DzVKdqnY.js → use-export-model-BXg6L_W5.js} +5 -5
- package/use-export-model.cjs.js +1 -1
- package/use-export-model.es.js +1 -1
- package/use-load-model/error-helpers.d.ts +5 -0
- package/use-load-model/file-loaders.d.ts +12 -0
- package/use-load-model/index.d.ts +1 -1
- package/use-load-model/optimizer-integration.d.ts +16 -0
- package/use-load-model/scene-data-builder.d.ts +3 -0
- package/use-load-model/types.d.ts +13 -4
- package/use-load-model/use-load-model.d.ts +5 -16
- package/use-load-model-B9M4cF4B.cjs +1 -0
- package/use-load-model-BDCy9n2U.js +504 -0
- package/use-load-model.cjs.js +1 -1
- package/use-load-model.es.js +1 -1
- package/use-optimize-model/use-optimize-model.d.ts +14 -12
- package/use-optimize-model/utils/browser-texture-encoder.d.ts +41 -0
- package/use-optimize-model/utils/index.d.ts +2 -2
- package/use-optimize-model/utils/texture-optimization.d.ts +3 -13
- package/use-optimize-model-CPGezwrM.cjs +1 -0
- package/use-optimize-model-KAGxtv_k.js +296 -0
- package/use-optimize-model.cjs.js +1 -1
- package/use-optimize-model.es.js +1 -1
- package/GLTFLoader-CygFefRr.cjs +0 -1
- package/GLTFLoader-NiJ7qzh1.js +0 -1381
- package/__vite-browser-external-Bn_csQuM.js +0 -7
- package/__vite-browser-external-CVmj8TrW.cjs +0 -1
- package/chunk-foFoljWS.js +0 -23
- package/chunk-gGpyby2o.cjs +0 -1
- package/lib-DKn2MJ9X.js +0 -4111
- package/lib-DZ-MihdJ.cjs +0 -2
- package/model-exporter-SMSAY90N.cjs +0 -1
- package/model-exporter-cvguDGnn.js +0 -6512
- package/server-communication-Bj6ZxFw2.js +0 -4659
- package/server-communication-yXZR8eso.cjs +0 -18
- package/use-export-model-CaijtW1c.cjs +0 -1
- package/use-load-model-B6UXiFWh.js +0 -517
- package/use-load-model-CH-WPQ_s.cjs +0 -1
- package/use-optimize-model/utils/validation.d.ts +0 -8
- package/use-optimize-model-C-fzzKh6.cjs +0 -1
- package/use-optimize-model-Cb3WMvDZ.js +0 -299
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,79 @@
|
|
|
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
|
+
|
|
63
|
+
## [0.18.0](https://github.com/Vectreal/vectreal-platform/compare/hooks-v0.17.0...hooks-v0.18.0) (2026-04-30)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
### Features
|
|
67
|
+
|
|
68
|
+
* add camera transition and snapshot functionality ([#435](https://github.com/Vectreal/vectreal-platform/issues/435)) ([da2de12](https://github.com/Vectreal/vectreal-platform/commit/da2de12c51376877b1cbf6e87066383024aff78a))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
### Dependencies
|
|
72
|
+
|
|
73
|
+
* The following workspace dependencies were updated
|
|
74
|
+
* dependencies
|
|
75
|
+
* @vctrl/core bumped to 0.18.0
|
|
76
|
+
|
|
3
77
|
## [0.17.0](https://github.com/Vectreal/vectreal-platform/compare/hooks-v0.16.2...hooks-v0.17.0) (2026-04-13)
|
|
4
78
|
|
|
5
79
|
|
package/README.md
CHANGED
|
@@ -120,7 +120,7 @@ export default function App() {
|
|
|
120
120
|
|
|
121
121
|
## `useOptimizeModel`
|
|
122
122
|
|
|
123
|
-
Runs mesh simplification and
|
|
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
|
|
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
|
-
|
|
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`})
|
|
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-
|
|
2
|
-
import { t as n } from "./use-export-model-
|
|
3
|
-
import { t as r } from "./use-optimize-model-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
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 =
|
|
6
|
+
let o = t(new n());
|
|
7
7
|
return {
|
|
8
|
-
handleThreeGltfExport:
|
|
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:
|
|
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) {
|
package/use-export-model.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`})
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./use-export-model-8JbUalyf.cjs");exports.useExportModel=e.t;
|
package/use-export-model.es.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as e } from "./use-export-model-
|
|
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>;
|
|
@@ -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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
-
* -
|
|
12
|
-
* -
|
|
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}});
|