@vctrl/hooks 0.25.0 → 1.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/CHANGELOG.md +58 -0
- package/README.md +110 -67
- package/index.cjs +1 -0
- package/index.d.ts +2 -1
- package/index.es.js +4 -4
- package/package.json +18 -10
- package/{chunk-CMqjfN_6.cjs → rolldown-runtime-BocRIvOZ.cjs} +1 -1
- package/use-export-model/use-export-model.d.ts +1 -1
- package/use-export-model-CORoAKbf.cjs +1 -0
- package/{use-export-model-efFsSWe-.js → use-export-model-DXesLypI.js} +1 -1
- package/use-export-model.cjs +1 -0
- 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 +11 -12
- package/use-load-model/index.d.ts +1 -1
- package/use-load-model/load-context.d.ts +17 -0
- package/use-load-model/optimizer-ingest.d.ts +8 -0
- package/use-load-model/optimizer-integration.d.ts +3 -4
- package/use-load-model/scene-loaders.d.ts +15 -0
- package/use-load-model/state.d.ts +14 -14
- package/use-load-model/types.d.ts +154 -131
- package/use-load-model/use-load-model.d.ts +12 -8
- package/use-load-model/utils/index.d.ts +1 -1
- package/use-load-model/utils/reconstruct-files.d.ts +27 -5
- package/use-load-model/utils/resolve-scene-payload.d.ts +9 -0
- package/use-load-model-CRDVzrPC.js +635 -0
- package/use-load-model-CZCi6bPM.cjs +1 -0
- package/use-load-model.cjs +1 -0
- package/use-load-model.es.js +1 -1
- package/use-optimize-model/index.d.ts +1 -0
- package/use-optimize-model/types.d.ts +3 -1
- package/use-optimize-model/use-optimize-model.d.ts +11 -6
- package/use-optimize-model-Fwew6qD8.cjs +1 -0
- package/{use-optimize-model-D16-4Zwm.js → use-optimize-model-sNXkORNg.js} +94 -76
- package/use-optimize-model.cjs +1 -0
- package/use-optimize-model.es.js +2 -2
- package/utils/server-communication.d.ts +0 -5
- package/index.cjs.js +0 -1
- package/use-export-model-yQWV3O3H.cjs +0 -1
- package/use-export-model.cjs.js +0 -1
- package/use-load-model/event-system.d.ts +0 -7
- package/use-load-model-B8oqou4I.cjs +0 -1
- package/use-load-model-C88r8-HE.js +0 -585
- package/use-load-model.cjs.js +0 -1
- package/use-optimize-model-CD5zqTLK.cjs +0 -1
- package/use-optimize-model.cjs.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.0](https://github.com/Vectreal/vectreal-platform/compare/hooks-v0.25.1...hooks-v1.0.0) (2026-09-25)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* **core:** `OptimizationStats` fields are renamed. `vertices` -> `verticesCount`, `triangles` -> `primitivesCount`, `materials` -> `materialsCount`, `textures` -> `textureBytes`, `meshes` -> `meshBytes`. `texturesCount`, `meshesCount` and `textureResolutions` are unchanged.
|
|
9
|
+
* **publisher:** `@vctrl/core` makes `ModelOptimizer.ensureModelLoaded` private (`hasModel()` answers the same question) and widens `ExportResult` to include `USDZExportResult`, which an exhaustive consumer switch will notice. `@vctrl/hooks` drops `info` from `OptimizationState`; `useOptimizeModel` still returns it, derived.
|
|
10
|
+
* **hooks:** `ShadowsProps` is a plain interface rather than a union discriminated on `type`, so a consumer still passing `type: 'accumulative'` is passing an unknown property.
|
|
11
|
+
* **publisher:** `@vctrl/viewer` reads `ao` off the shadow options directly rather than only when they were tagged accumulative, so a consumer passing `{ enabled: true, ao: true }` gains the ambient-occlusion pass and its per-frame cost.
|
|
12
|
+
* **publisher:** `@vctrl/core` removes `GridProps`, `ShadowType`, `ShadowTypePropBase`, `ContactShadowProps`, `AccumulativeShadowsProps`, `ExportOptions`, `ExportModelOptions`, `ModifiedTextureResources` and `OptimizationStats.nodes`; `exportThreeJSGLB` takes only the object; `optimizeAll` compresses textures unless `textures: false`. `@vctrl/viewer` removes the `gridOptions` prop, `SceneGrid` and `defaultGridOptions`, and renames `defaultShadowOptions` to `defaultShadowsOptions`. `@vctrl/hooks` replaces `load(files)`, `loadFromData` and `loadFromServer` with a single `load(source)`, replaces `isFileLoading` with a `status` union, and removes the `on`/`off` event bus along with `EventTypes`, `EventHandler`, `LoadData`, `SceneLoadOptions` and `SceneDataLoadOptions`.
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* **core,hooks:** the loader reads six formats and resolves every reference through one rule ([#882](https://github.com/Vectreal/vectreal-platform/issues/882)) ([7b52540](https://github.com/Vectreal/vectreal-platform/commit/7b52540233c8258da852be6d3d4374c119e09b16))
|
|
17
|
+
* **scene:** a saved scene stores each asset under the name its glTF uses ([#883](https://github.com/Vectreal/vectreal-platform/issues/883)) ([b0f98cc](https://github.com/Vectreal/vectreal-platform/commit/b0f98cc965d67d8afe77cae1ef432adb54b572ac))
|
|
18
|
+
* **viewer:** play glTF animation clips ([11ff088](https://github.com/Vectreal/vectreal-platform/commit/11ff0880eb32ef0bda03196fe74de4955452d2f1))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Bug Fixes
|
|
22
|
+
|
|
23
|
+
* **convert:** the page describes the model that is on the stage ([#891](https://github.com/Vectreal/vectreal-platform/issues/891)) ([c2376d9](https://github.com/Vectreal/vectreal-platform/commit/c2376d974f6e7ec16ba6984836fc069059ff2153))
|
|
24
|
+
* **core:** count meshes instead of weighing them ([#783](https://github.com/Vectreal/vectreal-platform/issues/783)) ([9000982](https://github.com/Vectreal/vectreal-platform/commit/900098243213a7e8eb862a77f34ef113c21acb19))
|
|
25
|
+
* **core:** the loader stops claiming it can read USDZ ([#890](https://github.com/Vectreal/vectreal-platform/issues/890)) ([2a3770c](https://github.com/Vectreal/vectreal-platform/commit/2a3770c7039638cf8dee8f80a4da141c00e7a0c3))
|
|
26
|
+
* **embed:** serve embeds the published GLB instead of the editor's assets ([#734](https://github.com/Vectreal/vectreal-platform/issues/734)) ([e5a0bf5](https://github.com/Vectreal/vectreal-platform/commit/e5a0bf5a77d1288abe08303ec5b547416a0ffd32))
|
|
27
|
+
* **hooks:** keep a failed upload from hiding a failed scene load ([5e9676f](https://github.com/Vectreal/vectreal-platform/commit/5e9676f01f61aa53ab74ec3e3fb2d78283fe7045))
|
|
28
|
+
* **publisher:** a failed load costs nothing ([90cf7be](https://github.com/Vectreal/vectreal-platform/commit/90cf7be0f81dbc9e897192c4333f85ec06cf7d25))
|
|
29
|
+
* **publisher:** apply a saved scene once, not on every revalidation ([d903e6d](https://github.com/Vectreal/vectreal-platform/commit/d903e6db95d6876f1c369042fffc8cb5dd6ae860))
|
|
30
|
+
* **publisher:** one answer to which scene is open ([cbcc863](https://github.com/Vectreal/vectreal-platform/commit/cbcc8632e8137e9aba446293367d6d90f1796146))
|
|
31
|
+
* signup return path, search leak, field surfaces and quick wins ([#897](https://github.com/Vectreal/vectreal-platform/issues/897)) ([c3111d8](https://github.com/Vectreal/vectreal-platform/commit/c3111d84b3980d1daf0b7aaa3f4169693051c853))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Code Refactoring
|
|
35
|
+
|
|
36
|
+
* **core:** name every metric field by its unit ([#784](https://github.com/Vectreal/vectreal-platform/issues/784)) ([2f7c7d3](https://github.com/Vectreal/vectreal-platform/commit/2f7c7d377a7bfb51199663ad1648895867ebc447))
|
|
37
|
+
* **publisher:** leave each write to the one place that owns it ([0b04d08](https://github.com/Vectreal/vectreal-platform/commit/0b04d08075d455d3a963be1553d2f2e52d5c61dc))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
### Dependencies
|
|
41
|
+
|
|
42
|
+
* The following workspace dependencies were updated
|
|
43
|
+
* dependencies
|
|
44
|
+
* @vctrl/core bumped to 1.0.0
|
|
45
|
+
|
|
46
|
+
## [0.25.1](https://github.com/Vectreal/vectreal-platform/compare/hooks-v0.25.0...hooks-v0.25.1) (2026-08-08)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
### Bug Fixes
|
|
50
|
+
|
|
51
|
+
* **core:** keep the pristine baseline when syncing the worker's output ([a2e5654](https://github.com/Vectreal/vectreal-platform/commit/a2e5654f6a2acd8dc95cb2e9dace3c32436fc826))
|
|
52
|
+
* **publisher:** stale scene state across navigation and nonsensical optimization stats ([4152ab1](https://github.com/Vectreal/vectreal-platform/commit/4152ab146770f86930a53b892900978a163ae167))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
### Dependencies
|
|
56
|
+
|
|
57
|
+
* The following workspace dependencies were updated
|
|
58
|
+
* dependencies
|
|
59
|
+
* @vctrl/core bumped to 0.25.1
|
|
60
|
+
|
|
3
61
|
## [0.25.0](https://github.com/Vectreal/vectreal-platform/compare/hooks-v0.24.1...hooks-v0.25.0) (2026-08-04)
|
|
4
62
|
|
|
5
63
|
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ pnpm add @vctrl/hooks
|
|
|
20
20
|
|
|
21
21
|
| Hook | Import path | Description |
|
|
22
22
|
| ------------------ | --------------------------------- | ------------------------------------------------------------------------------- |
|
|
23
|
-
| `useLoadModel` | `@vctrl/hooks/use-load-model` | Load and parse
|
|
23
|
+
| `useLoadModel` | `@vctrl/hooks/use-load-model` | Load and parse every format `@vctrl/core/model-formats` marks importable, from a file list, a dropped folder or a saved scene. `supportedFileTypes` on the return value is that list |
|
|
24
24
|
| `useOptimizeModel` | `@vctrl/hooks/use-optimize-model` | Run glTF-Transform optimizations on loaded models |
|
|
25
25
|
| `useExportModel` | `@vctrl/hooks/use-export-model` | Export the current scene to GLB or glTF |
|
|
26
26
|
|
|
@@ -31,18 +31,19 @@ pnpm add @vctrl/hooks
|
|
|
31
31
|
Loads 3D files and exposes the parsed Three.js `Object3D` scene.
|
|
32
32
|
|
|
33
33
|
```tsx
|
|
34
|
-
import {
|
|
34
|
+
import { useLoadModel } from '@vctrl/hooks/use-load-model'
|
|
35
35
|
|
|
36
36
|
function Uploader() {
|
|
37
|
-
const { load, file,
|
|
37
|
+
const { load, status, file, error } = useLoadModel()
|
|
38
38
|
|
|
39
39
|
const handleDrop = (e: React.DragEvent) => {
|
|
40
40
|
e.preventDefault()
|
|
41
|
-
void load(Array.from(e.dataTransfer.files))
|
|
41
|
+
void load({ kind: 'files', files: Array.from(e.dataTransfer.files) })
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
if (
|
|
45
|
-
if (
|
|
44
|
+
if (status === 'loading') return <p>Loading...</p>
|
|
45
|
+
if (status === 'error') return <p>{error.message}</p>
|
|
46
|
+
if (status === 'ready') return <p>Model loaded: {file.name}</p>
|
|
46
47
|
|
|
47
48
|
return (
|
|
48
49
|
<div onDrop={handleDrop} onDragOver={(e) => e.preventDefault()}>
|
|
@@ -50,16 +51,70 @@ function Uploader() {
|
|
|
50
51
|
</div>
|
|
51
52
|
)
|
|
52
53
|
}
|
|
54
|
+
```
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
`useLoadModel` holds its own model. To share one across a tree, mount
|
|
57
|
+
`ModelProvider` and read it with `useModelContext`; see below.
|
|
58
|
+
|
|
59
|
+
### One entry point, one state
|
|
60
|
+
|
|
61
|
+
`load` handles every way a model arrives, and the hook's state is a
|
|
62
|
+
discriminated union on `status`. `status === 'ready'` and a non-null `file` are
|
|
63
|
+
the same fact, so there is no separate loading flag to fall out of step with
|
|
64
|
+
what is on screen.
|
|
65
|
+
|
|
66
|
+
`load` never rejects. It resolves to the terminal state, and for every source
|
|
67
|
+
but `files` that is also the state you are rendering from.
|
|
68
|
+
|
|
69
|
+
A rejected `files` load is the exception, on purpose: when a model was already
|
|
70
|
+
on screen it stays exactly as it was, so dropping the wrong file does not cost
|
|
71
|
+
the user their scene, and the failure is reported only through the resolved
|
|
72
|
+
value. With nothing on screen there is nothing to protect, and the state is the
|
|
73
|
+
error as usual. Branch on what `load` returns to react to an upload that did
|
|
74
|
+
not take; `reset()` is how you clear a model deliberately.
|
|
75
|
+
|
|
76
|
+
A newer `load` supersedes an older one, so a slow response can never overwrite
|
|
77
|
+
the load that replaced it. The superseded call still resolves, with the state it
|
|
78
|
+
would have produced, so a caller acting on the resolved value rather than on the
|
|
79
|
+
rendered state asks `stillCurrent()` before writing anything down - and asks it
|
|
80
|
+
again after any further await, because a drop can land during an encode as
|
|
81
|
+
easily as during a parse. `reset()` claims a token too, so a load in flight when
|
|
82
|
+
the user clears the model also reports `false`.
|
|
83
|
+
|
|
84
|
+
Resolving is not the same moment as reaching the screen. With an optimizer
|
|
85
|
+
attached, the loaders publish the parsed model first and then await its ingest,
|
|
86
|
+
so `load` resolves one ingest after the viewer changed. A caller that adopts on
|
|
87
|
+
the resolved value therefore keeps printing the previous model's name and byte
|
|
88
|
+
figures around the new one for as long as that ingest takes, and
|
|
89
|
+
`stillCurrent()` cannot close the window, because during it the new load
|
|
90
|
+
genuinely is the current one. Pass `onPublish` to adopt when the stage changes
|
|
91
|
+
instead:
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
await load({ kind: 'files', files }, {
|
|
95
|
+
onPublish: (published) => adopt(published)
|
|
96
|
+
})
|
|
61
97
|
```
|
|
62
98
|
|
|
99
|
+
It receives the same `LoadOutcome` the promise resolves to, carrying the same
|
|
100
|
+
`stillCurrent()`. A load superseded before it published never calls it at all,
|
|
101
|
+
so there is nothing to re-check on entry; ask again only after an await of your
|
|
102
|
+
own, for the reason above. Where there is no ingest to wait on (no optimizer
|
|
103
|
+
attached, or a scene source that has none), publishing and resolving are the
|
|
104
|
+
same moment and `onPublish` gains nothing.
|
|
105
|
+
|
|
106
|
+
### Sources
|
|
107
|
+
|
|
108
|
+
| `kind` | Fields | Use |
|
|
109
|
+
| ------------ | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
110
|
+
| `files` | `files: (File \| FileSystemDirectoryHandle)[]` | A user upload: a single model, or a bundle with the siblings it refers to. A dropped folder arrives here as plain `File`s; the handle branch is for a caller that opened `showDirectoryPicker()` itself |
|
|
111
|
+
| `scene-data` | `sceneData: ServerScenePayload`, `sceneId?`, `parseMode?` | A payload you already hold. Binary assets may be referenced rather than inlined; the loader fetches them |
|
|
112
|
+
| `server` | `sceneId: string`, `serverOptions?`, `parseMode?` | Fetch a scene from an API endpoint by id |
|
|
113
|
+
|
|
114
|
+
`parseMode: 'direct'` parses the glTF JSON with its assets in memory and skips
|
|
115
|
+
the optimizer: the read-only fast path for viewers. The default reconstructs
|
|
116
|
+
files so the optimizer can ingest exactly what the viewer renders.
|
|
117
|
+
|
|
63
118
|
### Context and direct usage
|
|
64
119
|
|
|
65
120
|
`useLoadModel` can be used in two ways:
|
|
@@ -69,52 +124,27 @@ export default function App() {
|
|
|
69
124
|
|
|
70
125
|
### Return values
|
|
71
126
|
|
|
72
|
-
| Value
|
|
73
|
-
|
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
| `load-progress` | `number` |
|
|
94
|
-
| `load-complete` | `ModelFile \| null` |
|
|
95
|
-
| `load-reset` | `null` |
|
|
96
|
-
| `load-error` | `Error \| unknown` |
|
|
97
|
-
| `server-load-start` | `string` |
|
|
98
|
-
| `server-load-complete` | `SceneLoadResult` |
|
|
99
|
-
| `server-load-error` | `Error \| unknown` |
|
|
100
|
-
|
|
101
|
-
### Scene loading option types
|
|
102
|
-
|
|
103
|
-
`loadFromServer(options)` uses:
|
|
104
|
-
|
|
105
|
-
| Field | Type | Description |
|
|
106
|
-
| --------------- | --------------- | --------------------------------------------- |
|
|
107
|
-
| `sceneId` | `string` | Scene identifier to fetch |
|
|
108
|
-
| `serverOptions` | `ServerOptions` | Endpoint, auth, and header configuration |
|
|
109
|
-
| `applySettings` | `boolean` | Whether scene settings are applied after load |
|
|
110
|
-
|
|
111
|
-
`loadFromData(options)` uses:
|
|
112
|
-
|
|
113
|
-
| Field | Type | Description |
|
|
114
|
-
| --------------- | --------------------- | -------------------------------------------------------------- |
|
|
115
|
-
| `sceneId` | `string \| undefined` | Optional scene identifier |
|
|
116
|
-
| `sceneData` | `ServerSceneData` | Already-resolved payload containing glTF, settings, and assets |
|
|
117
|
-
| `applySettings` | `boolean` | Whether scene settings are applied after load |
|
|
127
|
+
| Value | Type | Description |
|
|
128
|
+
| -------------- | ------------------------------------------------- | ----------------------------------------------------------------- |
|
|
129
|
+
| `status` | `'empty' \| 'loading' \| 'ready' \| 'error'` | The load, as one value |
|
|
130
|
+
| `file` | `ModelFile \| null` | Loaded file metadata and Three.js model. Non-null exactly when `status` is `'ready'` |
|
|
131
|
+
| `error` | `StructuredLoadError \| null` | Non-null exactly when `status` is `'error'` |
|
|
132
|
+
| `sceneData` | `ServerSceneData \| undefined` | The resolved payload, for scene sources |
|
|
133
|
+
| `progress` | `number` | Progress value from 0 to 100 |
|
|
134
|
+
| `source` | `'files' \| 'scene-data' \| 'server' \| null` | What the current state came from |
|
|
135
|
+
| `load(source, options?)` | `Promise<LoadOutcome>` | Load a model; resolves to the terminal state plus `stillCurrent()`. `options.onPublish` fires earlier, when the model reaches the screen |
|
|
136
|
+
| `supportedFileTypes` | `ModelFileTypes[]` | Every format the loader accepts, from `@vctrl/core/model-formats` |
|
|
137
|
+
| `reset` | `() => void` | Clear the current model and retire any load in flight |
|
|
138
|
+
| `optimizer` | `OptimizerIntegrationReturn<true> \| null` | Populated when the hook is called with `useOptimizeModel()` |
|
|
139
|
+
|
|
140
|
+
### Error codes
|
|
141
|
+
|
|
142
|
+
`error.code` is one of `unsupported_format`, `multiple_models`,
|
|
143
|
+
`binary_load_failed`, `gltf_load_failed`, `missing_assets`,
|
|
144
|
+
`server_load_failed`, `not_found`, `quota_exceeded`, `unknown`. Optimizer
|
|
145
|
+
ingest is deliberately not among them: the model is already on screen by then,
|
|
146
|
+
so a failure there costs the optimize step and is reported on
|
|
147
|
+
`optimizer.error`.
|
|
118
148
|
|
|
119
149
|
---
|
|
120
150
|
|
|
@@ -200,20 +230,28 @@ function ExportButton({ file }: { file: ModelFile | null }) {
|
|
|
200
230
|
|
|
201
231
|
### Methods
|
|
202
232
|
|
|
203
|
-
| Method | Description
|
|
204
|
-
| -------------------------------------------------------------- |
|
|
205
|
-
| `handleThreeGltfExport(file, binary)` | Export a loaded Three.js model to `.glb` or a zipped `.gltf` bundle
|
|
206
|
-
| `
|
|
233
|
+
| Method | Description |
|
|
234
|
+
| -------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
235
|
+
| `handleThreeGltfExport(file, binary)` | Export a loaded Three.js model to `.glb` or a zipped `.gltf` bundle |
|
|
236
|
+
| `handleThreeUsdzExport(file)` | Export a loaded Three.js model to `.usdz` for AR QuickLook |
|
|
237
|
+
| `handleDocumentGltfExport(document, file, binary?, download?)` | Export from a glTF-Transform `Document` |
|
|
238
|
+
| `handleDocumentGlbDracoExport(document, file)` | Export a `Document` to `.glb` with Draco geometry compression |
|
|
207
239
|
|
|
208
|
-
`binary = true` writes `.glb`; `binary = false` writes a zipped `.gltf` package.
|
|
240
|
+
`binary = true` writes `.glb`; `binary = false` writes a zipped `.gltf` package. Pass
|
|
241
|
+
`download = false` to `handleDocumentGltfExport` to get the `GLTFExportResult` back
|
|
242
|
+
instead of saving a file.
|
|
209
243
|
|
|
210
244
|
---
|
|
211
245
|
|
|
212
246
|
## Additional exports
|
|
213
247
|
|
|
214
|
-
- `reconstructGltfFiles` from `@vctrl/hooks`
|
|
248
|
+
- `reconstructGltfFiles` and `createBrowserTextureEncoder` from `@vctrl/hooks`
|
|
215
249
|
- `ModelProvider` and `useModelContext` from `@vctrl/hooks/use-load-model`
|
|
216
|
-
- Shared types such as `ModelFile`, `SceneLoadResult`, and `
|
|
250
|
+
- Shared types such as `ModelFile`, `SceneLoadResult`, `ServerSceneData`, and `OptimizerIntegrationReturn`
|
|
251
|
+
|
|
252
|
+
`createBrowserTextureEncoder()` returns the `OffscreenCanvas` encoder the hook injects
|
|
253
|
+
into `texturesOptimization`. Pass it as `ModelOptimizer#compressTextures`'s
|
|
254
|
+
`encoder` option when driving the optimizer directly.
|
|
217
255
|
|
|
218
256
|
---
|
|
219
257
|
|
|
@@ -222,7 +260,12 @@ function ExportButton({ file }: { file: ModelFile | null }) {
|
|
|
222
260
|
| Package | Version |
|
|
223
261
|
| ------- | -------------- |
|
|
224
262
|
| `react` | `^18 \|\| ^19` |
|
|
225
|
-
| `three` |
|
|
263
|
+
| `three` | see below |
|
|
264
|
+
|
|
265
|
+
Install `three` yourself and declare it explicitly in your own `package.json`, so your
|
|
266
|
+
project resolves exactly one copy: Three.js uses global singletons internally and
|
|
267
|
+
duplicate instances produce subtle rendering bugs. Let your package manager resolve the
|
|
268
|
+
version against the declared peer range rather than pinning one from this document.
|
|
226
269
|
|
|
227
270
|
---
|
|
228
271
|
|
package/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./use-load-model-CZCi6bPM.cjs"),t=require("./use-export-model-CORoAKbf.cjs"),n=require("./use-optimize-model-Fwew6qD8.cjs");exports.createBrowserTextureEncoder=n.n,exports.reconstructGltfFiles=e.r,exports.useExportModel=t.t,exports.useLoadModel=e.t,exports.useOptimizeModel=n.t;
|
package/index.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ export { default as useLoadModel } from './use-load-model/use-load-model';
|
|
|
2
2
|
export { default as useExportModel } from './use-export-model/use-export-model';
|
|
3
3
|
export { default as useOptimizeModel } from './use-optimize-model/use-optimize-model';
|
|
4
4
|
export { reconstructGltfFiles } from './use-load-model/utils/reconstruct-files';
|
|
5
|
-
export
|
|
5
|
+
export { createBrowserTextureEncoder } from './use-optimize-model/utils';
|
|
6
|
+
export type { LoadOptions, LoadOutcome, ModelFile, ModelSource, ModelSourceKind, ModelState, OptimizerIntegrationReturn, UseLoadModelReturn, ServerSceneData, SceneLoadResult } from './use-load-model/types';
|
package/index.es.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as e, t } from "./use-load-model-
|
|
2
|
-
import { t as n } from "./use-export-model-
|
|
3
|
-
import {
|
|
4
|
-
export { e as reconstructGltfFiles, n as useExportModel, t as useLoadModel,
|
|
1
|
+
import { r as e, t } from "./use-load-model-CRDVzrPC.js";
|
|
2
|
+
import { t as n } from "./use-export-model-DXesLypI.js";
|
|
3
|
+
import { n as r, t as i } from "./use-optimize-model-sNXkORNg.js";
|
|
4
|
+
export { r as createBrowserTextureEncoder, e as reconstructGltfFiles, n as useExportModel, t as useLoadModel, i as useOptimizeModel };
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "1.0.0",
|
|
3
3
|
"name": "@vctrl/hooks",
|
|
4
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
5
|
"bugs": {
|
|
@@ -29,40 +29,48 @@
|
|
|
29
29
|
},
|
|
30
30
|
"license": "AGPL-3.0-only",
|
|
31
31
|
"types": "./index.d.ts",
|
|
32
|
-
"main": "./index.cjs
|
|
32
|
+
"main": "./index.cjs",
|
|
33
33
|
"module": "./index.es.js",
|
|
34
34
|
"type": "module",
|
|
35
35
|
"publishConfig": {
|
|
36
|
-
"access": "public"
|
|
36
|
+
"access": "public",
|
|
37
|
+
"directory": "../../build/packages/vctrl/hooks"
|
|
37
38
|
},
|
|
38
39
|
"exports": {
|
|
39
40
|
".": {
|
|
40
41
|
"types": "./index.d.ts",
|
|
41
42
|
"import": "./index.es.js",
|
|
42
|
-
"require": "./index.cjs
|
|
43
|
+
"require": "./index.cjs"
|
|
43
44
|
},
|
|
44
45
|
"./use-load-model": {
|
|
45
46
|
"types": "./use-load-model/index.d.ts",
|
|
46
47
|
"import": "./use-load-model.es.js",
|
|
47
|
-
"require": "./use-load-model.cjs
|
|
48
|
+
"require": "./use-load-model.cjs"
|
|
48
49
|
},
|
|
49
50
|
"./use-export-model": {
|
|
50
51
|
"types": "./use-export-model/index.d.ts",
|
|
51
52
|
"import": "./use-export-model.es.js",
|
|
52
|
-
"require": "./use-export-model.cjs
|
|
53
|
+
"require": "./use-export-model.cjs"
|
|
53
54
|
},
|
|
54
55
|
"./use-optimize-model": {
|
|
55
56
|
"types": "./use-optimize-model/index.d.ts",
|
|
56
57
|
"import": "./use-optimize-model.es.js",
|
|
57
|
-
"require": "./use-optimize-model.cjs
|
|
58
|
+
"require": "./use-optimize-model.cjs"
|
|
58
59
|
}
|
|
59
60
|
},
|
|
60
61
|
"peerDependencies": {
|
|
61
62
|
"react": "^18.0.0 || ^19.0.0",
|
|
62
|
-
"three": "^0.
|
|
63
|
+
"three": "^0.185.1"
|
|
63
64
|
},
|
|
64
65
|
"dependencies": {
|
|
65
|
-
"@
|
|
66
|
-
"file-saver": "^2.0.5"
|
|
66
|
+
"@gltf-transform/core": "^4.4.2",
|
|
67
|
+
"file-saver": "^2.0.5",
|
|
68
|
+
"@vctrl/core": "^1.0.0"
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@nx/vite": "23.1.1",
|
|
72
|
+
"@vitejs/plugin-react": "^6.0.5",
|
|
73
|
+
"vite": "^8.2.1",
|
|
74
|
+
"vite-plugin-dts": "5.0.3"
|
|
67
75
|
}
|
|
68
76
|
}
|
|
@@ -1 +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,
|
|
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,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return s}});
|
|
@@ -9,7 +9,7 @@ import { ModelFile } from '../use-load-model';
|
|
|
9
9
|
*/
|
|
10
10
|
declare const useExportModel: (onSaved?: () => void, onError?: (error: Error) => void) => {
|
|
11
11
|
handleThreeGltfExport: (file: ModelFile | null, binary: boolean) => Promise<void>;
|
|
12
|
-
handleDocumentGltfExport: (jsonDocument: Document, file: ModelFile | null, binary?: boolean, download?: boolean) => Promise<import('../../../core/src/
|
|
12
|
+
handleDocumentGltfExport: (jsonDocument: Document, file: ModelFile | null, binary?: boolean, download?: boolean) => Promise<import('../../../core/src/index.ts').GLTFExportResult | undefined>;
|
|
13
13
|
handleThreeUsdzExport: (file: ModelFile | null) => Promise<void>;
|
|
14
14
|
handleDocumentGlbDracoExport: (jsonDocument: Document, file: ModelFile | null) => Promise<void>;
|
|
15
15
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=require("./rolldown-runtime-BocRIvOZ.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),o=(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]),s=(0,t.useCallback)(async t=>{let n=t?.model;if(!n){console.error(`Scene not initialized`);return}try{let i=a.current,o=t.name.replace(/\.[^/.]+$/,``),s=await i.exportThreeJSUSDZ(n);r.default.saveAs(new Blob([new Uint8Array(s.data)]),`${o}.usdz`),e&&e()}catch(e){console.error(`USDZ export failed:`,e),i&&i(e)}},[e,i]),c=(0,t.useCallback)(async(t,n)=>{try{let i=a.current,o=n?.name.replace(/\.[^/.]+$/,``)||`model`,s=await i.exportDocumentGLBDraco(t);r.default.saveAs(new Blob([new Uint8Array(s.data)]),`${o}.glb`),e&&e()}catch(e){console.error(`Draco GLB export failed:`,e),i&&i(e)}},[e,i]);return{handleThreeGltfExport:o,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]),handleThreeUsdzExport:s,handleDocumentGlbDracoExport:c}};Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return i}});
|
|
@@ -12,7 +12,7 @@ var i = (i, a) => {
|
|
|
12
12
|
try {
|
|
13
13
|
let a = o.current, s = e.name.replace(/\.[^/.]+$/, "");
|
|
14
14
|
if (t) {
|
|
15
|
-
let e = await a.exportThreeJSGLB(n
|
|
15
|
+
let e = await a.exportThreeJSGLB(n);
|
|
16
16
|
if (!(e.data instanceof Uint8Array)) throw Error("Expected GLB export to return Uint8Array");
|
|
17
17
|
r.saveAs(new Blob([new Uint8Array(e.data)]), `${s}.glb`);
|
|
18
18
|
} else {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./use-export-model-CORoAKbf.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-DXesLypI.js";
|
|
2
2
|
export { e as useExportModel };
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { StructuredLoadError } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* A failure that already describes itself. The loaders throw these deliberately,
|
|
4
|
+
* so normalizing one again would flatten its code and message into "unknown".
|
|
5
|
+
*/
|
|
6
|
+
export declare const isStructuredLoadError: (error: unknown) => error is StructuredLoadError;
|
|
2
7
|
export declare const createStructuredLoadError: ({ code, message, recoverable, source, cause, context }: StructuredLoadError) => StructuredLoadError;
|
|
3
8
|
export declare const toErrorMessage: (error: unknown) => string;
|
|
4
9
|
export declare const normalizeLocalLoadError: (error: unknown, code: StructuredLoadError["code"], context?: Record<string, unknown>) => StructuredLoadError;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export {};
|
|
1
|
+
import { InputFileOrDirectory, LoadedModel } from './types';
|
|
2
|
+
import { LoadContext } from './load-context';
|
|
3
|
+
/**
|
|
4
|
+
* Loads a model the user picked: a single file, or a bundle with the siblings
|
|
5
|
+
* it refers to.
|
|
6
|
+
*
|
|
7
|
+
* Throws a `StructuredLoadError` for every rejection, including "nothing usable
|
|
8
|
+
* in here" and "more than one model in here". Those used to be silent returns,
|
|
9
|
+
* which left the caller waiting on a load that would never finish.
|
|
10
|
+
*/
|
|
11
|
+
export declare const loadModelFromFiles: (filesOrDirectories: InputFileOrDirectory, ctx: LoadContext) => Promise<LoadedModel>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { default as useLoadModel } from './use-load-model';
|
|
2
|
-
export { type
|
|
2
|
+
export { type InputFileOrDirectory, type LoadedModel, type LoadOptions, type LoadOutcome, type ModelFile, type ModelSource, type ModelSourceKind, type ModelState, type OptimizerIntegrationReturn, type SceneLoadResult, type ServerSceneData, type StructuredLoadError, type UseLoadModelReturn, type ViewerLoadErrorCode } from './types';
|
|
3
3
|
export { reconstructGltfFiles } from './utils/reconstruct-files';
|
|
4
4
|
export { fetchManifestAssetData, type FetchManifestAssetsOptions } from './utils/fetch-manifest-assets';
|
|
5
5
|
export * from './model-context';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { LoadedModel } from './types';
|
|
2
|
+
import { useOptimizeModel } from '../use-optimize-model';
|
|
3
|
+
import { ModelLoader } from '../../../core/src/model-loader/index.ts';
|
|
4
|
+
export type Optimizer = ReturnType<typeof useOptimizeModel> | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* What every per-source loader needs, and nothing else.
|
|
7
|
+
*
|
|
8
|
+
* `publish` exists because a load has two useful moments: the model is parsed
|
|
9
|
+
* and can be rendered, and the optimizer has finished ingesting it. Publishing
|
|
10
|
+
* the first means the viewer never waits on the second.
|
|
11
|
+
*/
|
|
12
|
+
export interface LoadContext {
|
|
13
|
+
modelLoader: ModelLoader;
|
|
14
|
+
optimizer: Optimizer;
|
|
15
|
+
publish: (loaded: LoadedModel) => void;
|
|
16
|
+
onProgress: (progress: number) => void;
|
|
17
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optimizer ingest is not part of "is the model on screen".
|
|
3
|
+
*
|
|
4
|
+
* The model is always published before this runs, so a failure here must not
|
|
5
|
+
* turn a visible scene into an error state. It costs the optimize step, which
|
|
6
|
+
* `optimizer.error` and `optimizer.isPreparing` already report.
|
|
7
|
+
*/
|
|
8
|
+
export declare const ingestIntoOptimizer: (ingest: () => Promise<void>, fallback?: () => Promise<void>) => Promise<void>;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ModelLoader } from '../../../core/src/model-loader/index.ts';
|
|
2
|
-
import {
|
|
3
|
-
import { Action, ModelFile, OptimizerIntegrationReturn } from './types';
|
|
2
|
+
import { ModelFile, OptimizerIntegrationReturn } from './types';
|
|
4
3
|
import { useOptimizeModel } from '../use-optimize-model';
|
|
5
4
|
/**
|
|
6
5
|
* Integrates the optimizer into the model loading process.
|
|
@@ -9,8 +8,8 @@ import { useOptimizeModel } from '../use-optimize-model';
|
|
|
9
8
|
* 1. Runs an optional optimization function
|
|
10
9
|
* 2. Exports the optimized model as GLB
|
|
11
10
|
* 3. Reloads it into Three.js
|
|
12
|
-
* 4.
|
|
11
|
+
* 4. Swaps it into the loaded model, leaving the load state itself untouched
|
|
13
12
|
*
|
|
14
13
|
* Returns `null` when no optimizer instance is provided.
|
|
15
14
|
*/
|
|
16
|
-
export declare function useOptimizerIntegration(instance: ReturnType<typeof useOptimizeModel> | undefined,
|
|
15
|
+
export declare function useOptimizerIntegration(instance: ReturnType<typeof useOptimizeModel> | undefined, replaceModel: (file: ModelFile) => void, file: ModelFile | null, modelLoader: ModelLoader): OptimizerIntegrationReturn<boolean>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { LoadedModel, ModelSource } from './types';
|
|
2
|
+
import { LoadContext } from './load-context';
|
|
3
|
+
type SceneDataSource = Extract<ModelSource, {
|
|
4
|
+
kind: 'scene-data';
|
|
5
|
+
}>;
|
|
6
|
+
type ServerSource = Extract<ModelSource, {
|
|
7
|
+
kind: 'server';
|
|
8
|
+
}>;
|
|
9
|
+
export declare const loadModelFromSceneData: (source: SceneDataSource, ctx: LoadContext, assetHeaders?: HeadersInit) => Promise<LoadedModel>;
|
|
10
|
+
/**
|
|
11
|
+
* Fetches a scene by id and loads it. Prefers the manifest endpoint and falls
|
|
12
|
+
* back to the legacy form-post contract for scenes the manifest cannot serve.
|
|
13
|
+
*/
|
|
14
|
+
export declare const loadModelFromServer: (source: ServerSource, ctx: LoadContext) => Promise<LoadedModel>;
|
|
15
|
+
export {};
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ModelFileTypes } from '../../../core/src/model-loader/index.ts';
|
|
2
|
+
import { LoadedModel, ModelSourceKind, ModelState, StructuredLoadError } from './types';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
+
* Model formats the loader accepts, reported on `useLoadModel`'s return.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* It was a hand-maintained array restating the three ids - one of the nine
|
|
7
|
+
* independent statements of a set nothing kept in agreement. It is now read
|
|
8
|
+
* from the owner. The export stays because it is part of `@vctrl/hooks`'
|
|
9
|
+
* published return type, and no app code reads it.
|
|
9
10
|
*/
|
|
10
|
-
export declare const
|
|
11
|
+
export declare const supportedFileTypes: ModelFileTypes[];
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* @param state - The current state of the reducer
|
|
15
|
-
* @param action - The action to be performed
|
|
16
|
-
* @returns The updated state
|
|
13
|
+
* The four states a load can be in. Building them here rather than spreading
|
|
14
|
+
* partial updates is what keeps `status`, `file` and `error` from drifting apart.
|
|
17
15
|
*/
|
|
18
|
-
declare
|
|
19
|
-
export
|
|
16
|
+
export declare const emptyModelState: ModelState;
|
|
17
|
+
export declare const loadingModelState: (source: ModelSourceKind, progress?: number) => ModelState;
|
|
18
|
+
export declare const readyModelState: (source: ModelSourceKind, loaded: LoadedModel) => ModelState;
|
|
19
|
+
export declare const errorModelState: (source: ModelSourceKind, error: StructuredLoadError) => ModelState;
|