@kite3d/engine 0.17.0 → 0.19.0-alpha.1

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.
@@ -1,4 +1,4 @@
1
- import type {ThreeViewer} from 'threepipe'
1
+ import type {IObject3D, ThreeViewer} from 'threepipe'
2
2
 
3
3
  export interface SerializeSceneGltfOptions {
4
4
  scenePath?: string
@@ -51,13 +51,21 @@ export async function serializeSceneGltf(
51
51
  preserveUUIDs: true,
52
52
  viewerConfig: true,
53
53
  embedUrlImages: false,
54
- onlyVisible: true,
54
+ onlyVisible: false,
55
+ shouldExportObject: isAuthoredSceneObject,
55
56
  jsonSpaces: 2,
56
57
  }, false)
57
58
  if (!blob) throw new Error('The scene exporter returned no glTF data')
58
59
  return serializeSceneGltfDocument(JSON.parse(await blob.text()), options)
59
60
  }
60
61
 
62
+ function isAuthoredSceneObject(object: IObject3D): boolean {
63
+ return object.userData?.excludeFromExport !== true
64
+ && object.userData?.isWidgetRoot !== true
65
+ && object.isWidget !== true
66
+ && object.assetType !== 'widget'
67
+ }
68
+
61
69
  /** Canonicalize JSON glTF and extract every embedded resource. */
62
70
  export async function serializeSceneGltfDocument(
63
71
  input: unknown,
@@ -1,40 +0,0 @@
1
- import { ComponentDefn, IObject3D, Object3DComponent, ThreeViewer } from 'threepipe';
2
- export interface GeneratorParams {
3
- [key: string]: unknown;
4
- }
5
- export interface GeneratorContext {
6
- node: IObject3D;
7
- params: GeneratorParams;
8
- viewer: ThreeViewer;
9
- engine: Record<string, unknown>;
10
- }
11
- export interface GeneratorModule {
12
- default?: (context: GeneratorContext) => unknown | Promise<unknown>;
13
- }
14
- export interface GeneratorViewerOptions {
15
- base: string | URL;
16
- onError?: (error: unknown) => void;
17
- }
18
- export declare class GeneratorComponent extends Object3DComponent {
19
- static ComponentType: string;
20
- static StateProperties: ComponentDefn['StateProperties'];
21
- module: string;
22
- params: GeneratorParams;
23
- private runRevision;
24
- static configureViewer(viewer: ThreeViewer, options: GeneratorViewerOptions): void;
25
- static waitForViewer(viewer: ThreeViewer): Promise<void>;
26
- init(object: IObject3D, state: Record<string, unknown>): void;
27
- run(): Promise<void>;
28
- bake(): Promise<number>;
29
- destroy(): Record<string, unknown>;
30
- }
31
- export interface RunGeneratorOptions extends Omit<GeneratorContext, 'engine'> {
32
- module: string;
33
- base: URL;
34
- revision?: number;
35
- isCurrent?: () => boolean;
36
- }
37
- export declare function runGenerator({ node, params, viewer, module, base, revision, isCurrent, }: RunGeneratorOptions): Promise<IObject3D[]>;
38
- export declare function removeGeneratedChildren(node: IObject3D): void;
39
- export declare function markGenerated(object: IObject3D, sourceId?: string, outputIndex?: number): void;
40
- export declare function resolveGeneratorModule(module: string, base: URL): URL;
@@ -1,275 +0,0 @@
1
- import * as ThreePipe from 'threepipe'
2
- import {
3
- type ComponentDefn,
4
- type IObject3D,
5
- Object3DComponent,
6
- type ThreeViewer,
7
- } from 'threepipe'
8
- import {getAuthoringMetadata, setAuthoringMetadata, type AuthoringMetadata} from '../authoring.ts'
9
-
10
- export interface GeneratorParams {
11
- [key: string]: unknown
12
- }
13
-
14
- export interface GeneratorContext {
15
- node: IObject3D
16
- params: GeneratorParams
17
- viewer: ThreeViewer
18
- engine: Record<string, unknown>
19
- }
20
-
21
- export interface GeneratorModule {
22
- default?: (context: GeneratorContext) => unknown | Promise<unknown>
23
- }
24
-
25
- export interface GeneratorViewerOptions {
26
- base: string | URL
27
- onError?: (error: unknown) => void
28
- }
29
-
30
- interface GeneratorViewerConfig {
31
- base: URL
32
- onError?: (error: unknown) => void
33
- pending: Set<Promise<void>>
34
- }
35
-
36
- const viewerConfigs = new WeakMap<ThreeViewer, GeneratorViewerConfig>()
37
- let generatorImportRevision = 0
38
-
39
- export class GeneratorComponent extends Object3DComponent {
40
- static ComponentType = 'Generator'
41
- static StateProperties: ComponentDefn['StateProperties'] = ['module', 'params']
42
-
43
- module = ''
44
- params: GeneratorParams = {}
45
- private runRevision = 0
46
-
47
- static configureViewer(viewer: ThreeViewer, options: GeneratorViewerOptions): void {
48
- viewerConfigs.set(viewer, {
49
- base: typeof options.base === 'string' ? new URL(options.base) : options.base,
50
- onError: options.onError,
51
- pending: new Set(),
52
- })
53
- }
54
-
55
- static async waitForViewer(viewer: ThreeViewer): Promise<void> {
56
- const config = viewerConfigs.get(viewer)
57
- while (config?.pending.size) await Promise.all([...config.pending])
58
- }
59
-
60
- init(object: IObject3D, state: Record<string, unknown>): void {
61
- super.init(object, state)
62
- const run = () => { void this.run().catch((error) => reportGeneratorError(this.ctx.viewer, error)) }
63
- this.onStateChange('module', run)
64
- this.onStateChange('params', run)
65
- run()
66
- }
67
-
68
- async run(): Promise<void> {
69
- const viewer = this.ctx.viewer
70
- const config = viewerConfigs.get(viewer)
71
- if (!config) throw new Error('Generator viewer is not configured')
72
- const revision = ++this.runRevision
73
- const task = runGenerator({
74
- node: this.object,
75
- params: this.params,
76
- viewer,
77
- module: this.module,
78
- base: config.base,
79
- revision: ++generatorImportRevision,
80
- isCurrent: () => revision === this.runRevision,
81
- }).then(() => {
82
- if (revision === this.runRevision) viewer.setDirty(this)
83
- }).finally(() => config.pending.delete(task))
84
- config.pending.add(task)
85
- await task
86
- }
87
-
88
- async bake(): Promise<number> {
89
- await this.run()
90
- const node = this.object
91
- const generated = node.children.filter((child) => child.userData.kite3dGenerated === true)
92
- const bakedFrom = {
93
- module: this.module,
94
- params: JSON.parse(JSON.stringify(this.params)) as GeneratorParams,
95
- ts: new Date().toISOString(),
96
- }
97
- for (const child of generated) unmarkGenerated(child as IObject3D)
98
- this.ctx.ecp.removeComponent(node, this.uuid)
99
- const metadata = getAuthoringMetadata(node)
100
- if (metadata?.role === 'generator' && !metadata.sourceId) {
101
- setAuthoringMetadata(node, {
102
- role: 'direct',
103
- id: metadata.id,
104
- ...(metadata.allowCameraInside !== undefined ? {allowCameraInside: metadata.allowCameraInside} : {}),
105
- })
106
- }
107
- node.userData.kite3dBakedFrom = bakedFrom
108
- node._sChildren = [...node.children]
109
- node.setDirty?.({change: 'userData.kite3dBakedFrom', source: 'kite3d bake'})
110
- return generated.length
111
- }
112
-
113
- destroy(): Record<string, unknown> {
114
- this.runRevision += 1
115
- removeGeneratedChildren(this.object)
116
- return super.destroy()
117
- }
118
- }
119
-
120
- export interface RunGeneratorOptions extends Omit<GeneratorContext, 'engine'> {
121
- module: string
122
- base: URL
123
- revision?: number
124
- isCurrent?: () => boolean
125
- }
126
-
127
- export async function runGenerator({
128
- node,
129
- params,
130
- viewer,
131
- module,
132
- base,
133
- revision = 0,
134
- isCurrent,
135
- }: RunGeneratorOptions): Promise<IObject3D[]> {
136
- removeGeneratedChildren(node)
137
- if (!module) return []
138
- const source = ensureGeneratorMetadata(node)
139
- const moduleUrl = resolveGeneratorModule(module, base)
140
- if (revision) moduleUrl.searchParams.set('kite3d-generator', String(revision))
141
- const loaded = await importGeneratorModule(moduleUrl.href)
142
- if (typeof loaded.default !== 'function') {
143
- throw new Error(`Generator module must have a default generate function: ${module}`)
144
- }
145
-
146
- const existingChildren = new Set(node.children)
147
- const returned = await loaded.default({
148
- node,
149
- params,
150
- viewer,
151
- engine: ThreePipe as unknown as Record<string, unknown>,
152
- })
153
- const returnedObjects = normalizeGeneratedResult(returned)
154
- for (const [index, child] of returnedObjects.entries()) {
155
- if (child.parent === node) continue
156
- markGenerated(child, source.id, index)
157
- node.add(child)
158
- }
159
-
160
- const generated = node.children.filter((child) => !existingChildren.has(child)) as IObject3D[]
161
- if (isCurrent && !isCurrent()) {
162
- for (const child of generated) removeGeneratedObject(child)
163
- return []
164
- }
165
- generated.forEach((child, index) => markGenerated(child, source.id, index))
166
- return generated
167
- }
168
-
169
- export function removeGeneratedChildren(node: IObject3D): void {
170
- for (const child of [...node.children] as IObject3D[]) {
171
- if (child.userData.kite3dGenerated !== true) continue
172
- removeGeneratedObject(child)
173
- }
174
- }
175
-
176
- export function markGenerated(object: IObject3D, sourceId?: string, outputIndex = 0): void {
177
- let descendantIndex = 0
178
- object.traverse((child: IObject3D) => {
179
- child.userData.kite3dGenerated = true
180
- child.userData.excludeFromExport = true
181
- if (sourceId) {
182
- child.userData.kite3dAuthoring = {
183
- role: 'generator',
184
- id: `${sourceId}:preview:${outputIndex}:${descendantIndex++}`,
185
- sourceId,
186
- } satisfies AuthoringMetadata
187
- }
188
- })
189
- }
190
-
191
- function unmarkGenerated(object: IObject3D): void {
192
- object.traverse((child: IObject3D) => {
193
- delete child.userData.kite3dGenerated
194
- delete child.userData.excludeFromExport
195
- const metadata = getAuthoringMetadata(child)
196
- if (metadata?.role === 'generator' && metadata.sourceId) delete child.userData.kite3dAuthoring
197
- })
198
- }
199
-
200
- function ensureGeneratorMetadata(node: IObject3D): AuthoringMetadata {
201
- const current = getAuthoringMetadata(node)
202
- if (current?.role === 'generator' && !current.sourceId) return current
203
- const savedId = typeof node.userData.gltfUUID === 'string' && node.userData.gltfUUID.trim()
204
- ? node.userData.gltfUUID.trim()
205
- : current?.id || generatorPathId(node)
206
- setAuthoringMetadata(node, {role: 'generator', id: savedId})
207
- return getAuthoringMetadata(node)!
208
- }
209
-
210
- function generatorPathId(node: IObject3D): string {
211
- const parts: string[] = []
212
- for (let current: IObject3D | null = node; current?.parent; current = current.parent as IObject3D) {
213
- const index = current.parent.children.indexOf(current)
214
- parts.push(`${current.name || current.type}:${index}`)
215
- if (current.parent.userData?.rootSceneModelRoot) break
216
- }
217
- return `generator:${parts.reverse().join('/')}`
218
- }
219
-
220
- function removeGeneratedObject(object: IObject3D): void {
221
- object.removeFromParent()
222
- const disposed = new Set<object>()
223
- object.traverse((child) => {
224
- const renderable = child as IObject3D & {geometry?: {dispose?(): void}, material?: unknown | unknown[]}
225
- disposeResource(renderable.geometry, disposed)
226
- const materials = Array.isArray(renderable.material) ? renderable.material : [renderable.material]
227
- for (const material of materials) {
228
- if (!material || typeof material !== 'object') continue
229
- for (const value of Object.values(material)) {
230
- if (value && typeof value === 'object' && 'isTexture' in value) disposeResource(value, disposed)
231
- }
232
- disposeResource(material, disposed)
233
- }
234
- })
235
- }
236
-
237
- function disposeResource(resource: unknown, disposed: Set<object>): void {
238
- if (!resource || typeof resource !== 'object' || disposed.has(resource)) return
239
- disposed.add(resource)
240
- ;(resource as {dispose?(): void}).dispose?.()
241
- }
242
-
243
- export function resolveGeneratorModule(module: string, base: URL): URL {
244
- if (!module || module.startsWith('/') || /^[a-z][a-z\d+.-]*:/i.test(module)) {
245
- throw new Error(`Generator module must be a project-relative path: ${module}`)
246
- }
247
- const resolved = new URL(module, base)
248
- if (resolved.origin !== base.origin) {
249
- throw new Error(`Generator module must be same-origin: ${resolved.href}`)
250
- }
251
- return resolved
252
- }
253
-
254
- function normalizeGeneratedResult(value: unknown): IObject3D[] {
255
- if (!value) return []
256
- const values = Array.isArray(value) ? value : [value]
257
- const objects: IObject3D[] = []
258
- for (const candidate of values) {
259
- if (!(candidate as IObject3D | undefined)?.isObject3D) {
260
- throw new Error('Generator output must be an Object3D, an array of Object3D values, or undefined')
261
- }
262
- objects.push(candidate as IObject3D)
263
- }
264
- return objects
265
- }
266
-
267
- async function importGeneratorModule(url: string): Promise<GeneratorModule> {
268
- return import(/* @vite-ignore */ url) as Promise<GeneratorModule>
269
- }
270
-
271
- function reportGeneratorError(viewer: ThreeViewer, error: unknown): void {
272
- const onError = viewerConfigs.get(viewer)?.onError
273
- if (onError) onError(error)
274
- else console.error('[kite3d] Generator error', error)
275
- }