@roomle/web-sdk 4.0.0 → 4.1.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.
- package/lib/{BufferGeometry-CnbfgBmz.mjs → BufferGeometry-DcRNeLZD.mjs} +2 -2
- package/lib/{BufferGeometry-CnbfgBmz.mjs.map → BufferGeometry-DcRNeLZD.mjs.map} +1 -1
- package/lib/{GLTFExporter-BxcjPMva.mjs → GLTFExporter-DdzTpCjW.mjs} +46 -46
- package/lib/{GLTFExporter-BxcjPMva.mjs.map → GLTFExporter-DdzTpCjW.mjs.map} +1 -1
- package/lib/{GaussianBlurNode-8gO-I1qG.mjs → GaussianBlurNode-CLuzhfDu.mjs} +19 -19
- package/lib/{GaussianBlurNode-8gO-I1qG.mjs.map → GaussianBlurNode-CLuzhfDu.mjs.map} +1 -1
- package/lib/{Object3D-DMaDdv-D.mjs → Object3D-CEXK2mMb.mjs} +1 -1
- package/lib/{Object3D-DMaDdv-D.mjs.map → Object3D-CEXK2mMb.mjs.map} +1 -1
- package/lib/Pass-C7MZilDW.mjs +53 -0
- package/lib/Pass-C7MZilDW.mjs.map +1 -0
- package/lib/{PointLightHelper-CahXSxiA.mjs → PointLightHelper-dDeo1kUI.mjs} +4 -4
- package/lib/{PointLightHelper-CahXSxiA.mjs.map → PointLightHelper-dDeo1kUI.mjs.map} +1 -1
- package/lib/{SpotLightHelper-BEqeYq1p.mjs → SpotLightHelper-BsMEPgVo.mjs} +3 -3
- package/lib/{SpotLightHelper-BEqeYq1p.mjs.map → SpotLightHelper-BsMEPgVo.mjs.map} +1 -1
- package/lib/{USDZExporter-ZZoG1mne.mjs → USDZExporter-BIDQSKG4.mjs} +3 -3
- package/lib/{USDZExporter-ZZoG1mne.mjs.map → USDZExporter-BIDQSKG4.mjs.map} +1 -1
- package/lib/{Vector4-BkA_VGid.mjs → Vector4-D-4p8rfn.mjs} +2 -2
- package/lib/{Vector4-BkA_VGid.mjs.map → Vector4-D-4p8rfn.mjs.map} +1 -1
- package/lib/{api-CUyBdNba.mjs → api-BEszDVqh.mjs} +10 -10
- package/lib/{api-CUyBdNba.mjs.map → api-BEszDVqh.mjs.map} +1 -1
- package/lib/{asset-loader.worker-C_5RQYHp.mjs → asset-loader.worker-eqHzNAwa.mjs} +1 -1
- package/lib/asset-loader.worker-eqHzNAwa.mjs.map +1 -0
- package/lib/{banana-for-scale-B77S3z9R.mjs → banana-for-scale-CiCouumi.mjs} +4 -4
- package/lib/{banana-for-scale-B77S3z9R.mjs.map → banana-for-scale-CiCouumi.mjs.map} +1 -1
- package/lib/{budgeteer.sw-DxeAvd0v.mjs → budgeteer.sw-BUMElkO1.mjs} +1 -1
- package/lib/budgeteer.sw-BUMElkO1.mjs.map +1 -0
- package/lib/{common-utils-BiOW17Ao.mjs → common-utils-CCzT4Gnn.mjs} +2 -2
- package/lib/{common-utils-BiOW17Ao.mjs.map → common-utils-CCzT4Gnn.mjs.map} +1 -1
- package/lib/{component-dimensioning-DPyPzSsa.mjs → component-dimensioning-DWEZ4jZg.mjs} +7 -7
- package/lib/{component-dimensioning-DPyPzSsa.mjs.map → component-dimensioning-DWEZ4jZg.mjs.map} +1 -1
- package/lib/{component-raycast-helper-RiQIybsX.mjs → component-raycast-helper-D2hyLpdX.mjs} +17 -17
- package/lib/{component-raycast-helper-RiQIybsX.mjs.map → component-raycast-helper-D2hyLpdX.mjs.map} +1 -1
- package/lib/configurator-BENT3shs.mjs +2 -0
- package/lib/{configurator-C_rsqn9l.mjs → configurator-BjLLdSf8.mjs} +16 -16
- package/lib/{configurator-C_rsqn9l.mjs.map → configurator-BjLLdSf8.mjs.map} +1 -1
- package/lib/{continuous-drawing-helper-C_Yc0uuv.mjs → continuous-drawing-helper-CCuU99Vh.mjs} +3 -3
- package/lib/{continuous-drawing-helper-C_Yc0uuv.mjs.map → continuous-drawing-helper-CCuU99Vh.mjs.map} +1 -1
- package/lib/{dat.gui.module-BLjoST8r.mjs → dat.gui.module-B2PXSbre.mjs} +1 -1
- package/lib/{dat.gui.module-BLjoST8r.mjs.map → dat.gui.module-B2PXSbre.mjs.map} +1 -1
- package/lib/{decorate-BAtqSPNS.mjs → decorate-CX1ZGuFY.mjs} +1 -1
- package/lib/{decorate-BAtqSPNS.mjs.map → decorate-CX1ZGuFY.mjs.map} +1 -1
- package/lib/{dimensioning-helper-D9oxGkry.mjs → dimensioning-helper-CjuQqjBn.mjs} +14 -14
- package/lib/{dimensioning-helper-D9oxGkry.mjs.map → dimensioning-helper-CjuQqjBn.mjs.map} +1 -1
- package/lib/dimensioning-helper-ImsWfczi.mjs +2 -0
- package/lib/{dist-DGov-RNo.mjs → dist-Blgk0vU0.mjs} +1 -1
- package/lib/{dist-DGov-RNo.mjs.map → dist-Blgk0vU0.mjs.map} +1 -1
- package/lib/{fflate.module-CTnSdWOb.mjs → fflate.module-DTkA0kF8.mjs} +1 -1
- package/lib/{fflate.module-CTnSdWOb.mjs.map → fflate.module-DTkA0kF8.mjs.map} +1 -1
- package/lib/{glb-viewer-A5vSLFZ5.mjs → glb-viewer-9Ata6ZNJ.mjs} +59 -45
- package/lib/glb-viewer-9Ata6ZNJ.mjs.map +1 -0
- package/lib/glb-viewer-CGqSGT38.mjs +2 -0
- package/lib/{highlight-coordinator-CZEJGeXA.mjs → highlight-coordinator-PyWNABN9.mjs} +3 -3
- package/lib/{highlight-coordinator-CZEJGeXA.mjs.map → highlight-coordinator-PyWNABN9.mjs.map} +1 -1
- package/lib/{homag-intelligence-BdCDX7LF.mjs → homag-intelligence-BNqPqVgE.mjs} +3 -3
- package/lib/{homag-intelligence-BdCDX7LF.mjs.map → homag-intelligence-BNqPqVgE.mjs.map} +1 -1
- package/lib/{imos-ix-poc-export-helper-CaXXDNvP.mjs → imos-ix-poc-export-helper-C1LfrtjM.mjs} +3 -3
- package/lib/{imos-ix-poc-export-helper-CaXXDNvP.mjs.map → imos-ix-poc-export-helper-C1LfrtjM.mjs.map} +1 -1
- package/lib/{kernel-utils-BYEsHVYJ.mjs → kernel-utils-Cn4evFqF.mjs} +2 -2
- package/lib/{kernel-utils-BYEsHVYJ.mjs.map → kernel-utils-Cn4evFqF.mjs.map} +1 -1
- package/lib/{layer-CIWL5ZW3.mjs → layer-DvZF9ur3.mjs} +1 -1
- package/lib/{layer-CIWL5ZW3.mjs.map → layer-DvZF9ur3.mjs.map} +1 -1
- package/lib/{main-BUl8iCi1.mjs → main-n3n3jdja.mjs} +1097 -1017
- package/lib/{main-BUl8iCi1.mjs.map → main-n3n3jdja.mjs.map} +1 -1
- package/lib/{material-viewer-C4SqVaGG.mjs → material-viewer-Cv5Awgwm.mjs} +24 -24
- package/lib/{material-viewer-C4SqVaGG.mjs.map → material-viewer-Cv5Awgwm.mjs.map} +1 -1
- package/lib/{packages-C9gO5mKk.mjs → packages-B3bi3adH.mjs} +5 -5
- package/lib/{packages-C9gO5mKk.mjs.map → packages-B3bi3adH.mjs.map} +1 -1
- package/lib/planner-DIT2VvMG.mjs +44 -0
- package/lib/{planner-t4QH26WW.mjs.map → planner-DIT2VvMG.mjs.map} +1 -1
- package/lib/planner-DaI8ZwEe.mjs +2 -0
- package/lib/{plugin-system-COZJdJH_.mjs → plugin-system-FuZpDWqv.mjs} +3 -3
- package/lib/{plugin-system-COZJdJH_.mjs.map → plugin-system-FuZpDWqv.mjs.map} +1 -1
- package/lib/roomle-headless-setup-CMHahWyC.mjs +612 -0
- package/lib/roomle-headless-setup-CMHahWyC.mjs.map +1 -0
- package/lib/roomle-headless.d.ts +113 -34
- package/lib/roomle-headless.js +433 -84
- package/lib/roomle-headless.js.map +1 -1
- package/lib/{roomle-renderer-DhLUaHJZ.mjs → roomle-renderer-BR0UKz2y.mjs} +2 -2
- package/lib/{roomle-renderer-DhLUaHJZ.mjs.map → roomle-renderer-BR0UKz2y.mjs.map} +1 -1
- package/lib/roomle-sdk.d.ts +37 -9
- package/lib/roomle-sdk.js +8 -8
- package/lib/{roomle-webgpu-renderer-CRGcdahn.mjs → roomle-webgpu-renderer-fOYJoKJU.mjs} +175 -173
- package/lib/roomle-webgpu-renderer-fOYJoKJU.mjs.map +1 -0
- package/lib/{scene-renderer-D5CfxCag.mjs → scene-renderer-CAzAJ4pS.mjs} +212 -211
- package/lib/{scene-renderer-D5CfxCag.mjs.map → scene-renderer-CAzAJ4pS.mjs.map} +1 -1
- package/lib/{script-loader-D6IuUFjw.mjs → script-loader-BAWHLt9q.mjs} +3 -3
- package/lib/{script-loader-D6IuUFjw.mjs.map → script-loader-BAWHLt9q.mjs.map} +1 -1
- package/lib/{shading-settings-BEMqEGzB.mjs → shading-settings-BPpgt0Fq.mjs} +101 -146
- package/lib/shading-settings-BPpgt0Fq.mjs.map +1 -0
- package/lib/{stats-helper-DAfbSToL.mjs → stats-helper-CqiTcJuc.mjs} +3 -3
- package/lib/{stats-helper-DAfbSToL.mjs.map → stats-helper-CqiTcJuc.mjs.map} +1 -1
- package/lib/{svgo.browser-DYieWzyw.mjs → svgo.browser-EB1zkPfG.mjs} +1 -1
- package/lib/{svgo.browser-DYieWzyw.mjs.map → svgo.browser-EB1zkPfG.mjs.map} +1 -1
- package/lib/{three.core-DjCexDCp.mjs → three.core-CmmDf_BZ.mjs} +73 -38
- package/lib/{three.core-DjCexDCp.mjs.map → three.core-CmmDf_BZ.mjs.map} +1 -1
- package/lib/{three.module-CuRNgijg.mjs → three.module-BkkLbA6n.mjs} +857 -857
- package/lib/{three.module-CuRNgijg.mjs.map → three.module-BkkLbA6n.mjs.map} +1 -1
- package/lib/three.tsl-8HwZJGJ8.mjs +2 -0
- package/lib/{three.tsl-BIBER8nk.mjs → three.tsl-Cw9IeeKW.mjs} +64 -52
- package/lib/{three.tsl-BIBER8nk.mjs.map → three.tsl-Cw9IeeKW.mjs.map} +1 -1
- package/lib/three.webgpu-Bw4_U6Q1.mjs +3 -0
- package/lib/{three.webgpu-Bz4Xtk0X.mjs → three.webgpu-DL5l_0xI.mjs} +1742 -1728
- package/lib/{three.webgpu-Bz4Xtk0X.mjs.map → three.webgpu-DL5l_0xI.mjs.map} +1 -1
- package/lib/{threejs-utils-Dpvg3Czz.mjs → threejs-utils-PRhv_eX3.mjs} +63 -63
- package/lib/{threejs-utils-Dpvg3Czz.mjs.map → threejs-utils-PRhv_eX3.mjs.map} +1 -1
- package/lib/{tools-core-BDOFf9le.mjs → tools-core-CmrXkw3J.mjs} +3 -3
- package/lib/{tools-core-BDOFf9le.mjs.map → tools-core-CmrXkw3J.mjs.map} +1 -1
- package/lib/webgpu-wkXAkJ6p.mjs +5084 -0
- package/lib/webgpu-wkXAkJ6p.mjs.map +1 -0
- package/lib/workers-YGDNEfpU.mjs +6 -0
- package/lib/{workers-DcgrO-z7.mjs.map → workers-YGDNEfpU.mjs.map} +1 -1
- package/package.json +5 -4
- package/lib/asset-loader.worker-C_5RQYHp.mjs.map +0 -1
- package/lib/budgeteer.sw-DxeAvd0v.mjs.map +0 -1
- package/lib/configurator-BBlZVIId.mjs +0 -2
- package/lib/dimensioning-helper-Bzf1sbpy.mjs +0 -2
- package/lib/glb-viewer-A5vSLFZ5.mjs.map +0 -1
- package/lib/glb-viewer-B4ILlvw0.mjs +0 -2
- package/lib/planner-49YwTdiA.mjs +0 -2
- package/lib/planner-t4QH26WW.mjs +0 -44
- package/lib/roomle-headless-setup-Cc8EAnVe.mjs +0 -1204
- package/lib/roomle-headless-setup-Cc8EAnVe.mjs.map +0 -1
- package/lib/roomle-webgpu-renderer-CRGcdahn.mjs.map +0 -1
- package/lib/shading-settings-BEMqEGzB.mjs.map +0 -1
- package/lib/three.tsl-Q-21y0pO.mjs +0 -2
- package/lib/three.webgpu-dtN0kDKB.mjs +0 -3
- package/lib/workers-DcgrO-z7.mjs +0 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roomle-headless.js","names":[],"sources":["../../packages/headless-core/src/headless-script-loader.ts","../../packages/headless-core/src/stubs.ts","../../packages/headless-core/src/headless-dom-helper.ts","../../packages/headless-core/src/headless-data-syncer.ts","../../packages/headless-core/src/headless-planner-kernel-access.ts","../../packages/headless-core/src/headless-kernel-callback-guard.ts","../../packages/headless-core/src/headless-tween-stub.ts","../../packages/headless-core/src/headless-sdk.ts"],"sourcesContent":["/**\n * Replaces the browser ScriptLoader for headless mode.\n * Instead of injecting <script> tags, directly imports the Emscripten JS modules.\n */\nimport { createRequire } from 'module';\nimport type { Context } from '../../common-core/src/di/context';\nimport { getHeadlessWasmPath } from '../../common-core/src/static-files/roomle-core';\n\n// createRequire is needed because the Emscripten kernel files are CommonJS\n// and must be loaded synchronously\nconst _require = createRequire(import.meta.url);\n\nlet _idCache: { [key: string]: boolean } = {};\n\n// Maps a .wasm filename found in the Emscripten factory's source to its global name.\n// Each Emscripten factory references its own .wasm file (e.g. ConfiguratorKernel.wasm).\nconst WASM_TO_GLOBAL: Array<\n [wasmFile: string, globalName: string, modulePath: string]\n> = [\n [\n 'ConfiguratorKernel.wasm',\n 'ConfiguratorKernel',\n 'roomle-core-hsc/wasm_modern/ConfiguratorKernel.js',\n ],\n [\n 'RoomleCore.wasm',\n 'RoomleCore',\n 'roomle-core-hsc/wasm_modern/RoomleCore.js',\n ],\n [\n 'RoomleToolsCore.wasm',\n 'RoomleToolsCore',\n 'roomle-core-hsc/wasm_modern/RoomleToolsCore.js',\n ],\n];\n\nexport default class HeadlessScriptLoader implements Context {\n public _creator_: string;\n\n constructor(creator: string) {\n this._creator_ = creator;\n }\n\n public async fetch(url: string, options: { id: string }) {\n if (_idCache[options.id]) {\n return;\n }\n\n // In Bun, Vite's ?no-inline?url imports resolve to the module's default\n // export (a function) rather than a URL string. Handle both cases.\n if (typeof url === 'function') {\n this._registerFactory(url);\n _idCache[options.id] = true;\n return;\n }\n\n const entry = this._resolveEntry(url);\n if (entry) {\n const [, globalName, modulePath] = entry;\n // If url is an absolute filesystem path (from getHeadlessWasmPath),\n // require it directly. Otherwise fall back to the module specifier\n // (works from source where roomle-core-hsc is in node_modules).\n const pathToLoad = url.startsWith('/') ? url : modulePath;\n const mod = _require(pathToLoad);\n (globalThis as any)[globalName] = mod.default || mod;\n _idCache[options.id] = true;\n }\n }\n\n // Identify an Emscripten factory function by checking its source for the\n // unique .wasm filename it references, then register it on globalThis\n // under the name the kernel-access classes expect (e.g. window.ConfiguratorKernel).\n private _registerFactory(factory: () => void) {\n const source = factory.toString();\n for (const [wasmFile, globalName] of WASM_TO_GLOBAL) {\n if (source.includes(wasmFile)) {\n (globalThis as any)[globalName] = factory;\n return;\n }\n }\n console.warn(\n 'HeadlessScriptLoader: Could not identify Emscripten factory function',\n );\n }\n\n private _resolveEntry(url: string) {\n for (const entry of WASM_TO_GLOBAL) {\n const [wasmFile, globalName] = entry;\n const baseName = globalName; // e.g. \"ConfiguratorKernel\"\n if (url.includes(baseName) || url.includes(wasmFile)) {\n return entry;\n }\n }\n // Fallback: looser matching for generic URLs\n if (url.includes('configurator')) {\n return WASM_TO_GLOBAL[0];\n }\n if (url.includes('planner')) {\n return WASM_TO_GLOBAL[1];\n }\n if (url.includes('tools')) {\n return WASM_TO_GLOBAL[2];\n }\n console.warn(\n `HeadlessScriptLoader: Could not resolve module for URL: ${url}`,\n );\n return null;\n }\n\n public loadScripts(scripts: Array<{ id: string; name: string }>) {\n const promises = scripts.map((script) =>\n this.fetch(script.name, { id: script.id }),\n );\n return Promise.all(promises);\n }\n\n public resolveKernelPaths(type: 'configurator' | 'planner' | 'tools'): {\n wasm: string;\n loader: string;\n } {\n return getHeadlessWasmPath(type);\n }\n\n public cleanUp() {\n _idCache = {};\n }\n}\n","export const noop = () => {};\n","/**\n * Stub DomHelper for headless mode.\n * Returns fixed dimensions since we don't render to a real canvas.\n */\nimport { Vector2 } from 'three';\nimport { noop } from './stubs';\nimport type {\n LifeCycleCallbacks,\n LifeCycleManager,\n} from '../../common-core/src/life-cycle-manager';\nimport type { Context } from '../../common-core/src/di/context';\n\nexport default class HeadlessDomHelper implements LifeCycleCallbacks, Context {\n public _creator_: string;\n\n // Manual DI lookup instead of @inject decorator (avoids legacy/TC39 decorator mismatch)\n private get _lifeCycleManager(): LifeCycleManager | undefined {\n return (globalThis as any).__RML__DI__?.lookup(\n 'life-cycle-manager',\n this._creator_,\n );\n }\n\n private _width: number;\n private _height: number;\n private _stubElement: any;\n\n get element(): any {\n return this._stubElement;\n }\n\n constructor(creator: string, width = 1024, height = 768) {\n this._creator_ = creator;\n this._width = width;\n this._height = height;\n\n const w = this._width;\n const h = this._height;\n this._stubElement = {\n clientWidth: w,\n clientHeight: h,\n appendChild() {},\n removeChild() {},\n style: {},\n classList: {\n add() {},\n remove() {},\n contains() {\n return false;\n },\n },\n setAttribute() {},\n getAttribute(): string | null {\n return null;\n },\n children: [] as any[],\n getBoundingClientRect() {\n return {\n x: 0,\n y: 0,\n width: w,\n height: h,\n top: 0,\n left: 0,\n bottom: h,\n right: w,\n };\n },\n addEventListener() {},\n removeEventListener() {},\n dispatchEvent() {\n return true;\n },\n offsetWidth: w,\n offsetHeight: h,\n scrollWidth: w,\n scrollHeight: h,\n contains() {\n return false;\n },\n };\n }\n\n public setDomElement(_element: any) {\n noop();\n }\n\n public getClientDimensions() {\n return new Vector2(this._width, this._height);\n }\n\n public getAspectRatio() {\n return this._width / this._height;\n }\n\n public ensureCanvasFocus() {}\n public reset() {}\n public pause() {}\n public resume() {}\n public destroy() {}\n}\n","/**\n * Replaces the Worker-based DataSyncer with a no-op for headless mode.\n * In headless mode, assets are loaded on-demand via RapiAccess rather than\n * being pre-synced with a Web Worker.\n */\nimport type { RapiId } from '@roomle/web-sdk';\nimport { noop } from './stubs';\nimport type { Context } from '../../common-core/src/di/context';\n\nexport default class HeadlessDataSyncer implements Context {\n public _creator_: string;\n\n constructor(creator: string) {\n this._creator_ = creator;\n }\n\n public start(_catalogId: RapiId) {\n return Promise.resolve();\n }\n\n public syncCatalog(_catalogId: RapiId): Promise<void> {\n return Promise.resolve();\n }\n\n public syncFloorTag(_tagId: RapiId) {\n return Promise.resolve();\n }\n\n public syncTypeChangeTag(_tagId: RapiId) {\n return Promise.resolve();\n }\n\n public _syncTypeChangeTag(_tagId: RapiId): Promise<any> {\n return Promise.resolve();\n }\n\n public getIsCatalogSynced(_catalogId: RapiId) {\n return false;\n }\n\n public onCommand(_command: number, _conversationId: number, _data: any) {\n noop();\n }\n\n public requestAsset(url: string, _urlAsFallback: boolean = false): string {\n return url;\n }\n\n public setAlwaysUseCache(_alwaysUseCache: boolean): void {\n noop();\n }\n\n public preFillAssetCache(_key: string, _url: string) {\n noop();\n }\n}\n","/**\n * Headless override of `PlannerKernelAccess`.\n */\nimport { KERNEL_TYPE } from '../../common-core/src/services/common-kernel-access';\nimport PlannerKernelAccess from '../../planner-core/src/services/planner-kernel-access';\n\nexport default class HeadlessPlannerKernelAccess extends PlannerKernelAccess {\n protected _getScriptLoaderKernelId(kernelType: KERNEL_TYPE): string {\n return kernelType === KERNEL_TYPE.PLANNER ? 'kernel-planner' : 'kernel';\n }\n}\n","import type { KernelCallbackGuard } from '../../common-core/src/services/kernel-callback-guard';\n\n/**\n * Headless implementation: wraps the kernel callback target in a Proxy so that\n * any JS exception thrown inside a callback invoked from C++ (via Embind) is\n * caught and logged rather than propagating into C++, which would call\n * std::terminate and abort the process.\n */\nexport default class HeadlessKernelCallbackGuard\n implements KernelCallbackGuard\n{\n wrap<T extends object>(target: T): T {\n return new Proxy(target, {\n get(t: any, prop) {\n const val = t[prop];\n if (typeof val === 'function') {\n return function (...args: any[]) {\n try {\n return val.apply(t, args);\n } catch (e) {\n console.error(\n `[Headless] Kernel callback '${String(prop)}' threw:`,\n e,\n );\n }\n };\n }\n return val;\n },\n });\n }\n}\n","// Prevents tween effects from happening in headless mode, we don't ever want\n// things to fade in or out headlessly because it creates delays and causes\n// elements to show up white or half-visible as they tween in while taking a screenshot.\n\ninterface MinimalTween {\n end?: () => void;\n}\n\ninterface MinimalTweenGroup {\n add(tween: MinimalTween): void;\n remove(_tween: MinimalTween): void;\n removeAll(): void;\n getAll(): MinimalTween[];\n update(_time?: number): void;\n}\n\nconst noopGroup: MinimalTweenGroup = {\n add(tween) {\n // Synchronously fast-forward. Wrapped in try/catch so a single\n // misbehaving tween can't poison the whole queue — at worst that\n // tween silently doesn't apply, exactly as in browser mode if it\n // never advances.\n try {\n tween.end?.();\n } catch {\n // ignore\n }\n },\n remove() {},\n removeAll() {},\n getAll() {\n return [];\n },\n update() {},\n};\n\nexport const installHeadlessTweenStub = (): void => {\n const target = window as unknown as { TWEEN?: MinimalTweenGroup };\n // Always overwrite — if a previous SDK boot left the real Group here,\n // we want our noop in its place for the new instance.\n target.TWEEN = noopGroup;\n};\n","/**\n * Main entry point for the headless Roomle SDK.\n * Provides the ability to run the Roomle web-sdk in Node.js/Bun\n * for loading configurations/plans and exporting them to GLB/USDZ.\n */\nimport HeadlessScriptLoader from './headless-script-loader';\nimport HeadlessDomHelper from './headless-dom-helper';\nimport HeadlessDataSyncer from './headless-data-syncer';\nimport HeadlessPlannerKernelAccess from './headless-planner-kernel-access';\nimport HeadlessKernelCallbackGuard from './headless-kernel-callback-guard';\nimport { installHeadlessTweenStub } from './headless-tween-stub';\nimport type {\n RoomlePlanner,\n Planner,\n RoomleConfigurator,\n Configurator,\n InitDataDefinition,\n GlobalInitDataDefinition,\n RapiId,\n GlbViewer,\n RoomleGLBViewer,\n} from '../../index';\nimport { RoomleSdk } from '../../index';\nimport {\n Container,\n DependencyInjectionAssignment,\n DI_TYPE,\n} from '../../common-core/src/di/container';\nimport { INJECTABLES } from '../../common-core/src/di/injectables';\nimport {\n isIdAComponent,\n isIdAConfigurationString,\n isIdAPlan,\n isIdAnItem,\n} from '#/common/src/utils/id-inferrer';\nimport type { Mesh } from 'three';\n\nconst INLINE_PLAN_XML_ID_PREFIX = 'inline-plan-xml#';\nconst INLINE_CONFIGURATION_ID_PREFIX = 'inline-configuration#';\n\nexport interface HeadlessInitData\n extends Partial<InitDataDefinition>,\n Partial<GlobalInitDataDefinition> {\n configuratorId: string;\n /** Canvas width in pixels. Defaults to 1024. */\n width?: number;\n /** Canvas height in pixels. Defaults to 768. */\n height?: number;\n}\n\nexport interface ExportResult {\n buffer: ArrayBuffer;\n}\n\nexport interface ImageExportOptions {\n /** Supersampling anti-aliasing factor. Defaults to 2. */\n ssaa?: number;\n /** Output image size in pixels. Defaults to 1024. */\n size?: number;\n}\n\n/** Mixin for image exports that frame the scene from a rotatable camera. */\nexport interface RotatableImageExportOptions {\n /** Camera rotation around the Y axis, in radians. */\n rotationY?: number;\n}\n\nexport interface ScreenshotExportOptions\n extends ImageExportOptions,\n RotatableImageExportOptions {\n /** JPEG quality (0–1). Defaults to 1. */\n quality?: number;\n}\n\nexport interface PerspectiveImageExportOptions\n extends ImageExportOptions,\n RotatableImageExportOptions {\n showDimensions?: boolean;\n}\n\nexport interface TopImageExportOptions extends ImageExportOptions {\n showDimensions?: boolean;\n}\n\nexport class HeadlessRoomleSdk {\n private _planner: RoomlePlanner;\n private _plannerMain: Planner;\n private _configurator: RoomleConfigurator;\n private _configuratorMain: Configurator;\n private _glbViewer: RoomleGLBViewer;\n private _glbViewerMain: GlbViewer;\n private _initialized = false;\n private _configuratorReady = false;\n private _plannerReady = false;\n private _glbViewerReady = false;\n private _loadedConfigurationId: RapiId | null = null;\n private _loadedPlanId: string | null = null;\n private _loadedStaticItemId: string | null = null;\n private _width = 1024;\n private _height = 768;\n private _loaderQueue: Promise<void> = Promise.resolve();\n\n /**\n * Create and initialize a headless SDK instance.\n * @param initData Configurator id plus optional canvas size and global init fields.\n * @returns Initialized SDK instance.\n */\n public static async create(\n initData: HeadlessInitData,\n ): Promise<HeadlessRoomleSdk> {\n const instance = new HeadlessRoomleSdk();\n await instance._init(initData);\n return instance;\n }\n\n private async _init(initData: HeadlessInitData) {\n this._width = initData.width ?? 1024;\n this._height = initData.height ?? 768;\n\n installHeadlessTweenStub();\n\n // Register headless service overrides BEFORE the DI container initializes.\n // This monkey-patches Container so that headless overrides always win,\n // even after boot() re-registers the default INJECTABLES.\n this._registerHeadlessServices(this._width, this._height);\n\n await RoomleSdk.setGlobalInitData({\n configuratorId: initData.configuratorId,\n locale: initData.locale,\n // @ts-ignore -- tenant is untyped, but we still need it.\n tenant: initData.tenant,\n overrideCountry: initData.overrideCountry,\n animateCamera: false,\n });\n\n this._initialized = true;\n }\n\n private _registerHeadlessServices(width: number, height: number) {\n // Create the DI container early. When roomle-dependency-injection.ts is\n // later loaded transitively, its _ensureContainer() checks if __RML__DI__\n // already exists and skips creating a new one — so we must register the\n // full INJECTABLES here ourselves.\n const container = new Container();\n (globalThis as any).__RML__DI__ = container;\n\n // Capture width/height in a subclass so the DI container instantiates\n // HeadlessDomHelper with the correct dimensions (DI only passes `creator`).\n class SizedHeadlessDomHelper extends HeadlessDomHelper {\n constructor(creator: string) {\n super(creator, width, height);\n }\n }\n\n // override: true marks each key so that subsequent boot() calls that\n // re-register the default INJECTABLES cannot overwrite these entries.\n const headlessOverrides = [\n new DependencyInjectionAssignment(\n 'script-loader',\n HeadlessScriptLoader,\n DI_TYPE.CONTEXT,\n true,\n ),\n new DependencyInjectionAssignment(\n 'dom-helper',\n SizedHeadlessDomHelper,\n DI_TYPE.CONTEXT,\n true,\n ),\n new DependencyInjectionAssignment(\n 'data-syncer',\n HeadlessDataSyncer,\n DI_TYPE.GLOBAL,\n true,\n ),\n new DependencyInjectionAssignment(\n 'planner-kernel-access',\n HeadlessPlannerKernelAccess,\n DI_TYPE.GLOBAL,\n true,\n ),\n new DependencyInjectionAssignment(\n 'kernel-callback-guard',\n HeadlessKernelCallbackGuard,\n DI_TYPE.GLOBAL,\n true,\n ),\n ];\n\n // Register defaults first, then overlay headless overrides.\n // The override: true flag on each headless entry prevents later boot() calls\n // from overwriting them when they re-register the default INJECTABLES.\n container.addDependencyInjectionAssignments(INJECTABLES);\n container.addDependencyInjectionAssignments(headlessOverrides);\n }\n\n private _makeStubElement() {\n return {\n clientWidth: this._width,\n clientHeight: this._height,\n appendChild() {},\n removeChild() {},\n style: {},\n classList: { add() {} },\n };\n }\n\n /**\n * Boot and initialize the configurator module ahead of first load.\n * @param initData Optional per-module init overrides.\n */\n public async prewarmConfigurator(\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n this._ensureInitialized();\n const configuratorModule = await RoomleSdk.getConfigurator(initData);\n await configuratorModule.boot();\n this._configuratorMain = configuratorModule;\n this._configurator = configuratorModule.getApi();\n await this._configurator.init(this._makeStubElement() as any);\n this._configuratorReady = true;\n }\n\n /**\n * Boot and initialize the planner module ahead of first load.\n * @param initData Optional per-module init overrides.\n */\n public async prewarmPlanner(\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n this._ensureInitialized();\n const plannerModule = await RoomleSdk.getPlanner({\n moc: true,\n ...initData,\n });\n if (initData?.moc === false) {\n throw new Error('HeadlessRoomleSdk: planner requires moc:true');\n }\n await plannerModule.boot();\n this._plannerMain = plannerModule;\n this._planner = plannerModule.getApi();\n await this._planner.init(this._makeStubElement() as any);\n this._plannerReady = true;\n }\n\n /**\n * Boot and initialize the GLB viewer module ahead of first load.\n * @param initData Optional per-module init overrides.\n */\n public async prewarmGlbViewer(\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n this._ensureInitialized();\n const glbModule = await RoomleSdk.getGlbViewer(initData);\n await glbModule.boot();\n this._glbViewerMain = glbModule;\n this._glbViewer = glbModule.getApi();\n await this._glbViewer.init(this._makeStubElement() as any);\n this._glbViewerReady = true;\n }\n\n /**\n * Load a saved configuration by **id**, via the RAPI `/configurations/`\n * endpoint. For an inline configuration string use\n * {@link loadConfigurationString} instead.\n * @param configurationId Multi-segment id like `catalog:item:HASH`.\n * @param initData Optional per-module init overrides for prewarm.\n */\n public async loadConfiguration(\n configurationId: RapiId,\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n return this._loadIntoConfigurator(\n configurationId,\n () => this._configurator.loadConfigurationById(configurationId),\n initData,\n );\n }\n\n /**\n * Load a configuration string directly without querying the RAPI (`{\"componentId\":\"catalog:item\",…}`)\n * To load a configuration by ID, use {@link loadConfiguration}\n */\n public async loadConfigurationString(\n configuration: string,\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n const identity = await this._inlineSceneId(\n INLINE_CONFIGURATION_ID_PREFIX,\n configuration,\n );\n return this._loadIntoConfigurator(\n identity,\n () => this._configurator.loadConfiguration(configuration),\n initData,\n );\n }\n\n /**\n * Load a configurable item via the RAPI `/items/` endpoint.\n * @param itemId Single-segment id like `catalog:item`.\n * @param initData Optional per-module init overrides for prewarm.\n */\n public async loadConfigurableItemById(\n itemId: RapiId,\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n return this._loadIntoConfigurator(\n itemId,\n () => this._configurator.loadConfigurableItemById(itemId),\n initData,\n );\n }\n\n private async _loadIntoConfigurator(\n identity: string,\n load: () => Promise<unknown>,\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n this._ensureInitialized();\n return this._withLoader(async () => {\n if (!this._configuratorReady) {\n await this.prewarmConfigurator(initData);\n }\n if (this._loadedConfigurationId === identity) {\n return;\n }\n // Temporarily remove the planner view model from PKA listeners so it\n // doesn't process configurator kernel callbacks (avoids listener\n // cross-contamination when both configurator and planner are pre-warmed).\n const { pka, plannerVM } = this._plannerReady\n ? this._getPlannerListenerInfo()\n : {};\n if (pka && plannerVM) {\n pka.removeConfiguratorListener(plannerVM);\n }\n try {\n await this._armOnReadyForRender();\n await load();\n this._loadedConfigurationId = identity;\n this._loadedPlanId = null;\n this._loadedStaticItemId = null;\n await this._settleScene();\n } finally {\n if (pka && plannerVM) {\n pka.addConfiguratorListener(plannerVM);\n }\n }\n });\n }\n\n /**\n * Load a static (non-configurable) item via the GLB viewer.\n * @param staticItemId Catalog item id whose RAPI record has no `configuration`.\n * @param initData Optional per-module init overrides for prewarm.\n */\n public async loadStaticItem(\n staticItemId: string,\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n this._ensureInitialized();\n return this._withLoader(async () => {\n if (!this._glbViewerReady) {\n await this.prewarmGlbViewer(initData);\n }\n if (this._loadedStaticItemId === staticItemId) {\n return;\n }\n await this._armOnReadyForRender();\n await this._glbViewer.loadStaticItem(staticItemId);\n this._suppressGlbSelfShadowing();\n this._loadedStaticItemId = staticItemId;\n this._loadedConfigurationId = null;\n this._loadedPlanId = null;\n await this._settleScene();\n });\n }\n\n /**\n * Temporary workaround:\n * GLB-loaded scenes commonly contain hundreds of thousands of triangles\n * (high-poly furniture/cars). With the default 1024² shadow map those\n * triangles cast a self-shadow pattern back onto themselves that the\n * shadow comparison can't resolve cleanly — it appears as a dense\n * triangle/herringbone overlay covering every PBR-shaded surface\n * (visible on flat-coloured car bodies, wood slats, etc.). The\n * artifact is specific to headless's GL backend; in the browser the\n * same scene renders fine.\n *\n * Walking the freshly-loaded scene and clearing `castShadow` on every\n * mesh removes the cause of the acne while keeping `receiveShadow`,\n * so static items still pick up shadows from external casters and\n * full PBR direct-light shading is preserved.\n */\n private _suppressGlbSelfShadowing(): void {\n const sceneManager = this._glbViewer.getSceneManager();\n const root = sceneManager.getScene();\n\n if (!root) {\n return;\n }\n\n root.traverse((node) => {\n if ((node as Mesh).isMesh) {\n (node as Mesh).castShadow = false;\n }\n });\n }\n\n /**\n * Auto-dispatch by id shape using the shared id-inferrer utilities, closely\n * following the UI's `SdkConnector.loadObject`:\n * - `component@…` → not supported in headless, throws\n * - `{…}` (config string) → {@link loadConfigurationString}\n * - 0 colons (plan id) → {@link loadPlan}\n * - 1 colon (item id) → {@link loadStaticItem} if RAPI says the\n * item has no `configuration`, otherwise\n * {@link loadConfigurableItemById}\n * - 2+ colons (config id) → {@link loadConfiguration}\n *\n * RAPI lookup failures during the item branch are propagated rather than\n * swallowed — a transient 5xx routing the call to the wrong loader would\n * surface as an unrelated downstream error.\n */\n async loadObject(\n id: string,\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n this._ensureInitialized();\n if (isIdAComponent(id)) {\n throw new Error(\n `HeadlessRoomleSdk: component ids (\"${id}\") are not supported. Use loadConfigurableItemById, loadConfiguration, or loadPlan directly.`,\n );\n }\n if (isIdAConfigurationString(id)) {\n return this.loadConfigurationString(id, initData);\n }\n if (isIdAPlan(id)) {\n return this.loadPlan(id, initData);\n }\n if (isIdAnItem(id)) {\n const rapi = await RoomleSdk.getRapiAccess();\n const item = await rapi.getItem(id as RapiId);\n const isStatic = !item?.configuration;\n if (isStatic) {\n return this.loadStaticItem(id, initData);\n }\n return this.loadConfigurableItemById(id as RapiId, initData);\n }\n return this.loadConfiguration(id as RapiId, initData);\n }\n\n /**\n * Load a plan into the planner.\n * @param planId Plan id (no colons).\n * @param initData Optional per-module init overrides for prewarm.\n */\n async loadPlan(\n planId: string,\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n this._ensureInitialized();\n return this._loadIntoPlanner(\n planId,\n () => this._planner.loadPlan(planId),\n initData,\n );\n }\n\n /**\n * Load plan XML string directly in the planner.\n * @param xml Plan XML (`<plan version=\"2\">…</plan>`).\n * @param initData Optional per-module init overrides for prewarm.\n */\n async loadPlanXml(\n xml: string,\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n this._ensureInitialized();\n const identity = await this._inlineSceneId(INLINE_PLAN_XML_ID_PREFIX, xml);\n return this._loadIntoPlanner(\n identity,\n () => this._planner.loadPlanXml(xml),\n initData,\n );\n }\n\n private async _loadIntoPlanner(\n identity: string,\n load: () => Promise<unknown>,\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n return this._withLoader(async () => {\n if (!this._plannerReady) {\n await this.prewarmPlanner(initData);\n }\n // Skip reload if the same plan is already loaded.\n if (this._loadedPlanId === identity) {\n return;\n }\n // Temporarily remove the configurator view model from PKA listeners so it\n // doesn't process planner kernel callbacks.\n const { pka, configuratorVM } = this._configuratorReady\n ? this._getConfiguratorListenerInfo()\n : {};\n if (pka && configuratorVM) {\n pka.removeConfiguratorListener(configuratorVM);\n }\n try {\n await this._armOnReadyForRender();\n await load();\n this._loadedPlanId = identity;\n // A freshly-loaded plan scene invalidates any previously-loaded configuration.\n this._loadedConfigurationId = null;\n this._loadedStaticItemId = null;\n await this._settleScene();\n } finally {\n if (pka && configuratorVM) {\n pka.addConfiguratorListener(configuratorVM);\n }\n }\n });\n }\n\n private async _inlineSceneId(\n prefix: string,\n payload: string,\n ): Promise<string> {\n const digest = await crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(payload),\n );\n const hex = Array.from(new Uint8Array(digest), (byte) =>\n byte.toString(16).padStart(2, '0'),\n ).join('');\n return prefix + hex.slice(0, 32);\n }\n\n /**\n * Serialises concurrent load operations using a promise-chain queue.\n * Each call chains its task onto the previous queue tail so they run\n * one at a time — listener mutations never overlap across an await.\n * Errors in one task do not stall subsequent ones (the queue tail is\n * always advanced in a settled state).\n */\n private _withLoader<T>(task: () => Promise<T>): Promise<T> {\n const next = this._loaderQueue.then(task);\n // Advance the queue unconditionally so a failing task never blocks\n // future callers.\n this._loaderQueue = next.then(\n () => {},\n () => {},\n );\n return next;\n }\n\n /**\n * Wait for the active scene to fire its onReadyForRender callback —\n * fired by each scene manager once `_itemsLoaded` (planner) /\n * `loadConfiguration` (configurator) / `loadStaticItem` (GLB viewer)\n * has finished constructing the scene and queued its materials. The\n * callback is armed via {@link _armOnReadyForRender} *before* the load\n * begins so synchronous firings (the GLB viewer does this) are still\n * caught.\n */\n private async _settleScene(): Promise<void> {\n if (!this._onReadyForRenderArmed) {\n return;\n }\n await this._onReadyForRenderArmed;\n this._onReadyForRenderArmed = null;\n }\n\n /**\n * Pre-arms an onReadyForRender listener on the *global* callback (shared\n * across configurator/planner/GLB viewer) so that it captures even\n * synchronous firings during the upcoming load. Restores the previous\n * callback after firing. Using the global callback rather than each\n * module's api.callbacks is necessary because the GLB viewer only fires\n * the global one ([glb-viewer-scene-manager.ts:355](packages/web-sdk/packages/glb-viewer-core/src/webgl/glb-viewer-scene-manager.ts:355)).\n */\n private async _armOnReadyForRender(): Promise<void> {\n const globalCallback = (await RoomleSdk.callbacks) as unknown as {\n onReadyForRender?: () => void;\n };\n if (!globalCallback) {\n this._onReadyForRenderArmed = null;\n return;\n }\n const previous = globalCallback.onReadyForRender;\n this._onReadyForRenderArmed = new Promise<void>((resolve) => {\n globalCallback.onReadyForRender = () => {\n try {\n previous?.();\n } finally {\n globalCallback.onReadyForRender = previous;\n resolve();\n }\n };\n });\n }\n\n private _onReadyForRenderArmed: Promise<void> | null = null;\n\n private _getPlannerListenerInfo() {\n try {\n const pka = (globalThis as any).__RML__DI__?.lookup?.(\n 'planner-kernel-access',\n );\n const plannerVM = this._planner?.getSceneManager()?.viewModel;\n return { pka, plannerVM };\n } catch {\n return {};\n }\n }\n\n private _getConfiguratorListenerInfo() {\n try {\n const pka = (globalThis as any).__RML__DI__?.lookup?.(\n 'planner-kernel-access',\n );\n const configuratorVM = this._configurator?.getSceneManager()?.viewModel;\n return { pka, configuratorVM };\n } catch {\n return {};\n }\n }\n\n /**\n * Export the active scene as GLB.\n * @param outputPath Optional file path to write the buffer to.\n * @returns The GLB bytes as an ArrayBuffer.\n */\n async exportGLB(outputPath?: string): Promise<ExportResult> {\n return this._export(outputPath, 'glb');\n }\n\n /**\n * Export the active scene as USDZ.\n * @param outputPath Optional file path to write the buffer to.\n * @returns The USDZ bytes as an ArrayBuffer.\n */\n async exportUSDZ(outputPath?: string): Promise<ExportResult> {\n return this._export(outputPath, 'usdz');\n }\n\n /**\n * Returns the active top-level API (planner or configurator) based on what\n * was last loaded. `loadPlan()` and `loadConfiguration()` are mutually\n * exclusive — each one nulls the other's id — so this resolves cleanly.\n */\n private _getActiveApi():\n | RoomlePlanner\n | RoomleConfigurator\n | RoomleGLBViewer {\n if (this._loadedPlanId) {\n return this._planner;\n }\n if (this._loadedConfigurationId) {\n return this._configurator;\n }\n if (this._loadedStaticItemId) {\n return this._glbViewer;\n }\n throw new Error(\n 'HeadlessRoomleSdk: nothing loaded — call loadPlan(), loadConfiguration(), loadConfigurableItemById(), loadStaticItem() or loadObject() first',\n );\n }\n\n /**\n * Returns the scene manager of whichever module is active. All exporters\n * route through here so plan-mode and config-mode share a single code path.\n */\n private _getActiveSceneManager() {\n if (this._loadedStaticItemId) {\n // The GLB viewer doesn't expose a sceneManager getter; reach into the\n // private field. The methods we use here (createExportGLB,\n // createUSDZExport, exportCanvasScreenshot) are shared with the\n // configurator/planner scene managers.\n return this._glbViewer.getSceneManager();\n }\n return this._getActiveApi().getSceneManager();\n }\n\n private async _export(\n outputPath: string | undefined,\n type: 'glb' | 'usdz',\n ): Promise<ExportResult> {\n this._ensureInitialized();\n\n const activeSceneManager = this._getActiveSceneManager();\n const buffer =\n type === 'glb'\n ? await activeSceneManager.createExportGLB()\n : await activeSceneManager.createUSDZExport();\n\n if (!buffer) {\n throw new Error(`${type.toUpperCase()} export produced no data`);\n }\n\n // The buffer is either ArrayBuffer (GLB) or Uint8Array (USDZ).\n // Extract the underlying ArrayBuffer in both cases.\n const arrayBuffer =\n buffer instanceof ArrayBuffer\n ? buffer\n : ((buffer as Uint8Array).buffer as ArrayBuffer);\n\n if (outputPath) {\n const fs = await import('fs');\n fs.writeFileSync(outputPath, Buffer.from(arrayBuffer));\n }\n\n return { buffer: arrayBuffer };\n }\n\n /**\n * Render a perspective screenshot preserving the scene background.\n * @param outputPath Optional file path to write the PNG to.\n * @param options ssaa, size, quality, rotationY.\n * @returns Data URL of the rendered image.\n */\n public async exportScreenshot(\n outputPath?: string,\n options: ScreenshotExportOptions = {},\n ): Promise<{ dataUrl: string }> {\n this._ensureInitialized();\n\n // Reuse the deterministic preparePerspectiveImage pipeline — it clones\n // the live camera and frames it synchronously off the bounding box,\n // sidestepping the zoomToFitBounds tween race that the previous\n // exportCanvasScreenshot path suffered from. The only behavioural\n // difference vs. exportPerspectiveImage is preserveSceneBackground:true,\n // so the scene's sky/floor/skybox stays visible.\n const ssaa = options.ssaa ?? 2;\n const targetSize = options.size ?? 1024;\n const renderSize = targetSize * ssaa;\n\n const result = await this._getActiveApi().preparePerspectiveImage({\n rotationY: options.rotationY,\n size: renderSize,\n preserveSceneBackground: true,\n });\n\n let dataUrl = result.image;\n if (ssaa > 1) {\n dataUrl = await this._ssaaDownscale(dataUrl, targetSize, targetSize);\n }\n\n if (outputPath) {\n const fs = await import('fs');\n const base64 = dataUrl.replace(/^data:image\\/\\w+;base64,/, '');\n fs.writeFileSync(outputPath, Buffer.from(base64, 'base64'));\n }\n\n return { dataUrl };\n }\n\n /**\n * Render a framed perspective image of the scene.\n * @param outputPath Optional file path to write the PNG to.\n * @param options ssaa, size, showDimensions, rotationY.\n * @returns Data URL plus final image dimensions.\n */\n public async exportPerspectiveImage(\n outputPath?: string,\n options: PerspectiveImageExportOptions = {},\n ): Promise<{ dataUrl: string; width: number; height: number }> {\n this._ensureInitialized();\n\n const ssaa = options.ssaa ?? 2;\n const targetSize = options.size ?? 1024;\n const renderSize = targetSize * ssaa;\n\n const result = await this._getActiveApi().preparePerspectiveImage({\n ...options,\n size: renderSize,\n });\n\n let finalDataUrl = result.image;\n let finalWidth = result.width;\n let finalHeight = result.height;\n\n // Downscale with SSAA if rendering at higher resolution, achieves antialiasing effect.\n if (ssaa > 1) {\n finalDataUrl = await this._ssaaDownscale(\n finalDataUrl,\n targetSize,\n targetSize,\n );\n finalWidth = targetSize;\n finalHeight = targetSize;\n }\n\n if (outputPath) {\n const fs = await import('fs');\n const base64 = finalDataUrl.replace(/^data:image\\/\\w+;base64,/, '');\n fs.writeFileSync(outputPath, Buffer.from(base64, 'base64'));\n }\n\n return { dataUrl: finalDataUrl, width: finalWidth, height: finalHeight };\n }\n\n /**\n * Render a top-down image of the scene. Not supported for static items.\n * @param outputPath Optional file path to write the PNG to.\n * @param options ssaa, size, showDimensions.\n * @returns Data URL plus final image dimensions.\n */\n public async exportTopImage(\n outputPath?: string,\n options: TopImageExportOptions = {},\n ): Promise<{ dataUrl: string; width: number; height: number }> {\n this._ensureInitialized();\n\n if (this._loadedStaticItemId) {\n throw new Error(\n 'HeadlessRoomleSdk: exportTopImage is not supported for static items',\n );\n }\n const api = this._getActiveApi() as RoomlePlanner | RoomleConfigurator;\n\n const ssaa = options.ssaa ?? 2;\n const result = await api.prepareTopImage({\n ...options,\n size: (options.size ?? 1024) * ssaa,\n showDimensions: options.showDimensions ?? false,\n });\n\n let finalDataUrl = result.image;\n let finalWidth = result.width;\n let finalHeight = result.height;\n\n if (ssaa > 1) {\n const targetWidth = Math.round(finalWidth / ssaa);\n const targetHeight = Math.round(finalHeight / ssaa);\n finalDataUrl = await this._ssaaDownscale(\n finalDataUrl,\n targetWidth,\n targetHeight,\n );\n finalWidth = targetWidth;\n finalHeight = targetHeight;\n }\n\n if (outputPath) {\n const fs = await import('fs');\n const base64 = finalDataUrl.replace(/^data:image\\/\\w+;base64,/, '');\n fs.writeFileSync(outputPath, Buffer.from(base64, 'base64'));\n }\n\n return { dataUrl: finalDataUrl, width: finalWidth, height: finalHeight };\n }\n\n /** Downscale a data URL image to target dimensions using high-quality filtering. */\n private async _ssaaDownscale(\n dataUrl: string,\n targetWidth: number,\n targetHeight: number,\n ): Promise<string> {\n const { createCanvas, loadImage } = await import('@napi-rs/canvas');\n const imgBuffer = Buffer.from(\n dataUrl.replace(/^data:image\\/\\w+;base64,/, ''),\n 'base64',\n );\n const img = await loadImage(imgBuffer);\n const outCanvas = createCanvas(targetWidth, targetHeight);\n const ctx = outCanvas.getContext('2d');\n (ctx as any).imageSmoothingEnabled = true;\n (ctx as any).imageSmoothingQuality = 'high';\n ctx.drawImage(img, 0, 0, targetWidth, targetHeight);\n return outCanvas.toDataURL('image/png');\n }\n\n getConfiguratorApi() {\n return this._configurator;\n }\n\n getPlannerApi() {\n return this._planner;\n }\n\n /** Destroy any prewarmed configurator, planner, and GLB viewer modules. */\n public async destroy() {\n if (this._configuratorMain) {\n this._configuratorMain.destroy();\n }\n if (this._plannerMain) {\n this._plannerMain.destroy();\n }\n if (this._glbViewerMain) {\n this._glbViewerMain.destroy();\n }\n }\n\n private _ensureInitialized() {\n if (!this._initialized) {\n throw new Error(\n 'HeadlessRoomleSdk not initialized. Call HeadlessRoomleSdk.create() first.',\n );\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"roomle-headless.js","names":[],"sources":["../../packages/headless-core/src/headless-script-loader.ts","../../packages/headless-core/src/stubs.ts","../../packages/headless-core/src/headless-dom-helper.ts","../../packages/headless-core/src/headless-data-syncer.ts","../../packages/headless-core/src/headless-planner-kernel-access.ts","../../packages/headless-core/src/headless-kernel-callback-guard.ts","../../packages/headless-core/src/headless-web-gpu-ground-shadow-container.ts","../../packages/headless-core/src/headless-web-gpu-path-tracer-renderer.ts","../../packages/headless-core/src/headless-image-renderer.ts","../../packages/headless-core/src/headless-tween-stub.ts","../../packages/headless-core/src/headless-hdr-environment-loader.ts","../../packages/headless-core/src/headless-sdk.ts","../../packages/headless-core/src/model-export-state-access.ts"],"sourcesContent":["/**\n * Replaces the browser ScriptLoader for headless mode.\n * Instead of injecting <script> tags, directly imports the Emscripten JS modules.\n */\nimport { createRequire } from 'module';\nimport type { Context } from '../../common-core/src/di/context';\nimport { getHeadlessWasmPath } from '../../common-core/src/static-files/roomle-core';\n\n// createRequire is needed because the Emscripten kernel files are CommonJS\n// and must be loaded synchronously\nconst _require = createRequire(import.meta.url);\n\nlet _idCache: { [key: string]: boolean } = {};\n\n// Maps a .wasm filename found in the Emscripten factory's source to its global name.\n// Each Emscripten factory references its own .wasm file (e.g. ConfiguratorKernel.wasm).\nconst WASM_TO_GLOBAL: Array<\n [wasmFile: string, globalName: string, modulePath: string]\n> = [\n [\n 'ConfiguratorKernel.wasm',\n 'ConfiguratorKernel',\n 'roomle-core-hsc/wasm_modern/ConfiguratorKernel.js',\n ],\n [\n 'RoomleCore.wasm',\n 'RoomleCore',\n 'roomle-core-hsc/wasm_modern/RoomleCore.js',\n ],\n [\n 'RoomleToolsCore.wasm',\n 'RoomleToolsCore',\n 'roomle-core-hsc/wasm_modern/RoomleToolsCore.js',\n ],\n];\n\nexport default class HeadlessScriptLoader implements Context {\n public _creator_: string;\n\n constructor(creator: string) {\n this._creator_ = creator;\n }\n\n public async fetch(url: string, options: { id: string }) {\n if (_idCache[options.id]) {\n return;\n }\n\n // In Bun, Vite's ?no-inline?url imports resolve to the module's default\n // export (a function) rather than a URL string. Handle both cases.\n if (typeof url === 'function') {\n this._registerFactory(url);\n _idCache[options.id] = true;\n return;\n }\n\n const entry = this._resolveEntry(url);\n if (entry) {\n const [, globalName, modulePath] = entry;\n // If url is an absolute filesystem path (from getHeadlessWasmPath),\n // require it directly. Otherwise fall back to the module specifier\n // (works from source where roomle-core-hsc is in node_modules).\n const pathToLoad = url.startsWith('/') ? url : modulePath;\n const mod = _require(pathToLoad);\n (globalThis as any)[globalName] = mod.default || mod;\n _idCache[options.id] = true;\n }\n }\n\n // Identify an Emscripten factory function by checking its source for the\n // unique .wasm filename it references, then register it on globalThis\n // under the name the kernel-access classes expect (e.g. window.ConfiguratorKernel).\n private _registerFactory(factory: () => void) {\n const source = factory.toString();\n for (const [wasmFile, globalName] of WASM_TO_GLOBAL) {\n if (source.includes(wasmFile)) {\n (globalThis as any)[globalName] = factory;\n return;\n }\n }\n console.warn(\n 'HeadlessScriptLoader: Could not identify Emscripten factory function',\n );\n }\n\n private _resolveEntry(url: string) {\n for (const entry of WASM_TO_GLOBAL) {\n const [wasmFile, globalName] = entry;\n const baseName = globalName; // e.g. \"ConfiguratorKernel\"\n if (url.includes(baseName) || url.includes(wasmFile)) {\n return entry;\n }\n }\n // Fallback: looser matching for generic URLs\n if (url.includes('configurator')) {\n return WASM_TO_GLOBAL[0];\n }\n if (url.includes('planner')) {\n return WASM_TO_GLOBAL[1];\n }\n if (url.includes('tools')) {\n return WASM_TO_GLOBAL[2];\n }\n console.warn(\n `HeadlessScriptLoader: Could not resolve module for URL: ${url}`,\n );\n return null;\n }\n\n public loadScripts(scripts: Array<{ id: string; name: string }>) {\n const promises = scripts.map((script) =>\n this.fetch(script.name, { id: script.id }),\n );\n return Promise.all(promises);\n }\n\n public resolveKernelPaths(type: 'configurator' | 'planner' | 'tools'): {\n wasm: string;\n loader: string;\n } {\n return getHeadlessWasmPath(type);\n }\n\n public cleanUp() {\n _idCache = {};\n }\n}\n","export const noop = () => {};\n","/**\n * Stub DomHelper for headless mode.\n * Returns fixed dimensions since we don't render to a real canvas.\n */\nimport { Vector2 } from 'three';\nimport { noop } from './stubs';\nimport type {\n LifeCycleCallbacks,\n LifeCycleManager,\n} from '../../common-core/src/life-cycle-manager';\nimport type { Context } from '../../common-core/src/di/context';\n\nexport default class HeadlessDomHelper implements LifeCycleCallbacks, Context {\n public _creator_: string;\n\n // Manual DI lookup instead of @inject decorator (avoids legacy/TC39 decorator mismatch)\n private get _lifeCycleManager(): LifeCycleManager | undefined {\n return (globalThis as any).__RML__DI__?.lookup(\n 'life-cycle-manager',\n this._creator_,\n );\n }\n\n private _width: number;\n private _height: number;\n private _stubElement: any;\n\n get element(): any {\n return this._stubElement;\n }\n\n constructor(creator: string, width = 1024, height = 768) {\n this._creator_ = creator;\n this._width = width;\n this._height = height;\n\n const w = this._width;\n const h = this._height;\n this._stubElement = {\n clientWidth: w,\n clientHeight: h,\n appendChild() {},\n removeChild() {},\n style: {},\n classList: {\n add() {},\n remove() {},\n contains() {\n return false;\n },\n },\n setAttribute() {},\n getAttribute(): string | null {\n return null;\n },\n children: [] as any[],\n getBoundingClientRect() {\n return {\n x: 0,\n y: 0,\n width: w,\n height: h,\n top: 0,\n left: 0,\n bottom: h,\n right: w,\n };\n },\n addEventListener() {},\n removeEventListener() {},\n dispatchEvent() {\n return true;\n },\n offsetWidth: w,\n offsetHeight: h,\n scrollWidth: w,\n scrollHeight: h,\n contains() {\n return false;\n },\n };\n }\n\n public setDomElement(_element: any) {\n noop();\n }\n\n public getClientDimensions() {\n return new Vector2(this._width, this._height);\n }\n\n public getAspectRatio() {\n return this._width / this._height;\n }\n\n public ensureCanvasFocus() {}\n public reset() {}\n public pause() {}\n public resume() {}\n public destroy() {}\n}\n","/**\n * Replaces the Worker-based DataSyncer with a no-op for headless mode.\n * In headless mode, assets are loaded on-demand via RapiAccess rather than\n * being pre-synced with a Web Worker.\n */\nimport type { RapiId } from '@roomle/web-sdk';\nimport { noop } from './stubs';\nimport type { Context } from '../../common-core/src/di/context';\n\nexport default class HeadlessDataSyncer implements Context {\n public _creator_: string;\n\n constructor(creator: string) {\n this._creator_ = creator;\n }\n\n public start(_catalogId: RapiId) {\n return Promise.resolve();\n }\n\n public syncCatalog(_catalogId: RapiId): Promise<void> {\n return Promise.resolve();\n }\n\n public syncFloorTag(_tagId: RapiId) {\n return Promise.resolve();\n }\n\n public syncTypeChangeTag(_tagId: RapiId) {\n return Promise.resolve();\n }\n\n public _syncTypeChangeTag(_tagId: RapiId): Promise<any> {\n return Promise.resolve();\n }\n\n public getIsCatalogSynced(_catalogId: RapiId) {\n return false;\n }\n\n public onCommand(_command: number, _conversationId: number, _data: any) {\n noop();\n }\n\n public requestAsset(url: string, _urlAsFallback: boolean = false): string {\n return url;\n }\n\n public setAlwaysUseCache(_alwaysUseCache: boolean): void {\n noop();\n }\n\n public preFillAssetCache(_key: string, _url: string) {\n noop();\n }\n}\n","/**\n * Headless override of `PlannerKernelAccess`.\n */\nimport { KERNEL_TYPE } from '../../common-core/src/services/common-kernel-access';\nimport PlannerKernelAccess from '../../planner-core/src/services/planner-kernel-access';\n\nexport default class HeadlessPlannerKernelAccess extends PlannerKernelAccess {\n protected _getScriptLoaderKernelId(kernelType: KERNEL_TYPE): string {\n return kernelType === KERNEL_TYPE.PLANNER ? 'kernel-planner' : 'kernel';\n }\n}\n","import type { KernelCallbackGuard } from '../../common-core/src/services/kernel-callback-guard';\n\n/**\n * Headless implementation: wraps the kernel callback target in a Proxy so that\n * any JS exception thrown inside a callback invoked from C++ (via Embind) is\n * caught and logged rather than propagating into C++, which would call\n * std::terminate and abort the process.\n */\nexport default class HeadlessKernelCallbackGuard\n implements KernelCallbackGuard\n{\n wrap<T extends object>(target: T): T {\n return new Proxy(target, {\n get(t: any, prop) {\n const val = t[prop];\n if (typeof val === 'function') {\n return function (...args: any[]) {\n try {\n return val.apply(t, args);\n } catch (e) {\n console.error(\n `[Headless] Kernel callback '${String(prop)}' threw:`,\n e,\n );\n }\n };\n }\n return val;\n },\n });\n }\n}\n","import type { Scene } from 'three';\nimport { WebGpuGroundShadowReflectionContainer } from '../../common-core/src/webgpu/web-gpu-ground-shadow-reflection-container';\n\nexport class HeadlessWebGpuGroundShadowContainer extends WebGpuGroundShadowReflectionContainer {\n protected override async addReflectiveFloor(_scene: Scene): Promise<void> {\n // noop\n }\n}\n","import type {\n BufferGeometry,\n Camera,\n Material,\n Mesh,\n Object3D,\n RenderTarget,\n Scene,\n Side,\n Texture,\n} from 'three';\nimport { BufferAttribute, DoubleSide, FrontSide } from 'three';\nimport type { WebGPURenderer } from 'three/webgpu';\nimport type { WebGPUPathTracer } from 'three-gpu-pathtracer/webgpu';\nimport type { WebGPUMockContext } from './setup-environment';\n\nexport interface PathTracerSettings {\n bounces?: number;\n renderScale?: number;\n denoise?: boolean;\n}\n\n// External denoiser hook (host wires it to Intel OIDN). Takes/returns tight\n// interleaved linear-HDR RGBA float rows. See .agents/headless-sdk.md § OIDN denoising.\nexport type PathTracerExternalDenoiser = (\n rgba: Float32Array,\n width: number,\n height: number,\n) => Promise<Float32Array>;\n\nlet externalDenoiser: PathTracerExternalDenoiser | null = null;\n\nexport function setPathTracerExternalDenoiser(\n fn: PathTracerExternalDenoiser | null,\n): void {\n externalDenoiser = fn;\n}\n\nexport type PathTracerProgressHook = (\n samples: number,\n totalSamples: number,\n) => void;\n\nlet progressHook: PathTracerProgressHook | null = null;\n\n// WebGPU spec-default maxBufferSize, enforced by Dawn regardless of the larger\n// reported limit. See .agents/headless-sdk.md § 256 MiB maxBufferSize banding.\nconst MAX_GPU_BUFFER_BYTES = 256 * 1024 * 1024;\nconst DEFAULT_PATH_TRACER_RENDER_SCALE = 1;\nconst DEFAULT_PREVIEW_MIN_INTERVAL_MS = 500;\nconst DEFAULT_PREVIEW_MAX_SIZE = 512;\n\n// Rows per band keeping a bytesPerRow-strided staging buffer under the limit\n// (5% headroom, at least one row).\nfunction getBandRowCount(bytesPerRow: number): number {\n return Math.max(1, Math.floor((MAX_GPU_BUFFER_BYTES * 0.95) / bytesPerRow));\n}\n\nexport const setPathTracerProgressHook = (\n hook: PathTracerProgressHook | null,\n): void => {\n progressHook = hook;\n};\n\nexport interface PathTracerPreviewFrame {\n // Encoded PNG bytes of the downscaled, tone-mapped frame.\n png: Uint8Array;\n width: number;\n height: number;\n samples: number;\n totalSamples: number;\n}\n\ninterface WebGPUBackendInternals {\n device?: GPUDevice;\n get?: (target: Texture) => { texture?: GPUTexture } | undefined;\n}\n\ninterface PreviewCanvas {\n width: number;\n height: number;\n getContext?: (contextId: string) => WebGPUMockContext | null;\n}\n\ninterface PathTracingSceneState {\n materialSides: Map<Material, Side>;\n hiddenMeshes: Mesh[];\n geometryAttributes: Map<BufferGeometry, Set<string>>;\n detachedMeshes: Array<{ object: Mesh; parent: Object3D; index: number }>;\n}\n\nexport type PathTracerPreviewHook = (frame: PathTracerPreviewFrame) => void;\n\nexport interface PathTracerPreviewOptions {\n minIntervalMs?: number;\n maxSize?: number;\n}\n\nlet previewHook: PathTracerPreviewHook | null = null;\nlet previewOptions: Required<PathTracerPreviewOptions> = {\n minIntervalMs: DEFAULT_PREVIEW_MIN_INTERVAL_MS,\n maxSize: DEFAULT_PREVIEW_MAX_SIZE,\n};\n\n// Registers a throttled preview callback receiving PNG snapshots of the\n// accumulating frame (requires the canvas context's readbackScaled).\nexport const setPathTracerPreviewHook = (\n hook: PathTracerPreviewHook | null,\n options?: PathTracerPreviewOptions,\n): void => {\n previewHook = hook;\n previewOptions = {\n minIntervalMs: options?.minIntervalMs ?? DEFAULT_PREVIEW_MIN_INTERVAL_MS,\n maxSize: options?.maxSize ?? DEFAULT_PREVIEW_MAX_SIZE,\n };\n};\n\n// Progressive path tracing on three-gpu-pathtracer's WIP webgpu-pathtracer\n// branch. See .agents/headless-sdk.md § Path tracing.\nexport class WebGPUPathTracerRenderer {\n private _pathTracer: WebGPUPathTracer | null = null;\n private _renderer: WebGPURenderer | null = null;\n\n // Accumulates `samples` samples onto the renderer's canvas; on resolve the\n // canvas holds the final tone-mapped image.\n public async render(\n renderer: WebGPURenderer,\n scene: Scene,\n camera: Camera,\n samples: number = 10,\n settings: PathTracerSettings = {},\n ): Promise<void> {\n const pathTracer = await this._getPathTracer(renderer);\n\n if (settings.bounces !== undefined) {\n pathTracer.bounces = settings.bounces;\n }\n pathTracer.renderScale =\n settings.renderScale ?? DEFAULT_PATH_TRACER_RENDER_SCALE;\n\n const patchedSceneState = this._applyScenePatches(scene);\n const rendererState = {\n autoClear: renderer.autoClear,\n toneMapping: renderer.toneMapping,\n toneMappingExposure: renderer.toneMappingExposure,\n outputColorSpace: renderer.outputColorSpace,\n };\n try {\n pathTracer.setScene(scene, camera);\n pathTracer.reset();\n\n const totalSamples = Math.max(1, Math.floor(samples));\n let lastPreviewAt = 0;\n while (pathTracer.samples < totalSamples) {\n pathTracer.renderSample();\n progressHook?.(pathTracer.samples, totalSamples);\n // Throttled live preview (awaited inline so readbacks never overlap).\n if (\n previewHook &&\n pathTracer.samples >= 2 &&\n performance.now() - lastPreviewAt >= previewOptions.minIntervalMs\n ) {\n await this._emitPreview(renderer, pathTracer.samples, totalSamples);\n lastPreviewAt = performance.now();\n }\n // Yield so GPU work and progress output flush between samples.\n await new Promise<void>((resolve) => setTimeout(resolve, 0));\n }\n\n pathTracer.renderSample();\n\n if (settings.denoise) {\n await this._denoiseAndBlit(renderer, pathTracer);\n }\n } finally {\n this._undoScenePatches(patchedSceneState);\n renderer.autoClear = rendererState.autoClear;\n renderer.toneMapping = rendererState.toneMapping;\n renderer.toneMappingExposure = rendererState.toneMappingExposure;\n renderer.outputColorSpace = rendererState.outputColorSpace;\n }\n }\n\n // Applies patches to the scene to work around gaps in the tracer's scene ingestion and\n // returns object containing the patched scene items. See .agents/headless-sdk.md § tracer scene workarounds.\n private _applyScenePatches(scene: Scene): PathTracingSceneState {\n const sceneState: PathTracingSceneState = {\n materialSides: new Map(),\n hiddenMeshes: [],\n geometryAttributes: new Map(),\n detachedMeshes: [],\n };\n\n scene.traverse((object) =>\n this._prepareObjectForPathTracing(object, sceneState),\n );\n\n // Detach after traversal (mutating children mid-traverse skips siblings);\n // splice in reverse / restore forward for exact index restore, no events.\n for (let i = sceneState.detachedMeshes.length - 1; i >= 0; i--) {\n const { object, parent, index } = sceneState.detachedMeshes[i]!;\n parent.children.splice(index, 1);\n object.parent = null;\n }\n\n return sceneState;\n }\n\n private _prepareObjectForPathTracing(\n object: Object3D,\n sceneState: PathTracingSceneState,\n ): void {\n const geometry = (object as Partial<Mesh>).geometry;\n const mesh = object as Mesh;\n\n if (geometry?.isBufferGeometry) {\n // Some geometries are generated by a shader, mainly lights, these are generated without\n // attributes.position, which three-gpu-pathtracer looks for, but this crashes with lights on.\n if (!geometry.attributes.position && object.parent) {\n sceneState.detachedMeshes.push({\n object: mesh,\n parent: object.parent,\n index: object.parent.children.indexOf(object),\n });\n }\n // Snapshot + tangent patch for EVERY buffer geometry (the tracer preps\n // lines/points too, not just meshes).\n if (!sceneState.geometryAttributes.has(geometry)) {\n sceneState.geometryAttributes.set(\n geometry,\n new Set(Object.keys(geometry.attributes)),\n );\n // After the snapshot, so the restore deletes the injected tangents.\n this._patchInvalidTangents(geometry, !mesh.isMesh);\n }\n }\n\n if (!mesh.isMesh) {\n return;\n }\n\n if (\n mesh.visible &&\n (mesh.userData?.isBoundingGeometry || mesh.name?.match('bounding box'))\n ) {\n mesh.visible = false;\n sceneState.hiddenMeshes.push(mesh);\n return;\n }\n\n this._forceDoubleSidedMaterials(mesh, sceneState.materialSides);\n }\n\n private _forceDoubleSidedMaterials(\n mesh: Mesh,\n materialSides: Map<Material, Side>,\n ): void {\n const materials = Array.isArray(mesh.material)\n ? mesh.material\n : [mesh.material];\n for (const material of materials) {\n if (material?.side === FrontSide && !materialSides.has(material)) {\n materialSides.set(material, material.side);\n material.side = DoubleSide;\n }\n }\n }\n\n private _undoScenePatches(sceneState: PathTracingSceneState): void {\n for (const { object, parent, index } of sceneState.detachedMeshes) {\n parent.children.splice(index, 0, object);\n object.parent = parent;\n }\n for (const [material, side] of sceneState.materialSides) {\n material.side = side;\n }\n for (const mesh of sceneState.hiddenMeshes) {\n mesh.visible = true;\n }\n for (const [\n geometry,\n originalAttributes,\n ] of sceneState.geometryAttributes) {\n Object.keys(geometry.attributes)\n .filter((name) => !originalAttributes.has(name))\n .forEach((name) => geometry.deleteAttribute(name));\n }\n }\n\n // Pre-fill zero tangents on geometry that would crash the tracer's\n // computeTangents. See .agents/headless-sdk.md § computeTangents crash guard.\n private _patchInvalidTangents(\n geometry: BufferGeometry,\n forceZeroTangents: boolean,\n ): void {\n const position = geometry.attributes.position;\n if (!position || geometry.attributes.tangent) {\n return;\n }\n if (!this._hasInvalidTriangleLayout(geometry, forceZeroTangents)) {\n return;\n }\n geometry.setAttribute(\n 'tangent',\n new BufferAttribute(new Float32Array(position.count * 4), 4),\n );\n }\n\n private _hasInvalidTriangleLayout(\n geometry: BufferGeometry,\n forceZeroTangents: boolean,\n ): boolean {\n const positionCount = geometry.attributes.position.count;\n // No index is NOT safe: the tracer's setCommonAttributes generates a\n // sequential index (length = position.count) before computing tangents.\n const indexArray = geometry.index?.array;\n const indexLength = indexArray ? indexArray.length : positionCount;\n\n // Non-mesh geometry (lines/points) is never triangle-shaped, always zero-fill.\n if (forceZeroTangents || indexLength % 3 !== 0) {\n return true;\n }\n if (!indexArray) {\n return false;\n }\n return (\n geometry.groups.some(\n (group) => group.start + group.count > indexArray.length,\n ) || indexArray.some((index) => index >= positionCount)\n );\n }\n\n public dispose(): void {\n this._pathTracer?.dispose();\n this._pathTracer = null;\n this._renderer = null;\n }\n\n // Any failure is swallowed so a preview never fails the render.\n private async _emitPreview(\n renderer: WebGPURenderer,\n samples: number,\n totalSamples: number,\n ): Promise<void> {\n try {\n const canvas = renderer.domElement as PreviewCanvas;\n const webGpuContext = canvas.getContext?.('webgpu');\n if (!webGpuContext?.readbackScaled) {\n return; // bun-webgpu extension unavailable so previews silently off\n }\n // Fit the longest edge to maxSize without upscaling.\n const scale = Math.min(\n 1,\n previewOptions.maxSize /\n Math.max(1, Math.max(canvas.width, canvas.height)),\n );\n const previewWidth = Math.max(1, Math.round(canvas.width * scale));\n const previewHeight = Math.max(1, Math.round(canvas.height * scale));\n const scaledFrame = await webGpuContext.readbackScaled(\n previewWidth,\n previewHeight,\n );\n if (!scaledFrame) {\n return;\n }\n const { createCanvas, ImageData: NapiImageData } = await import(\n '@napi-rs/canvas'\n );\n const previewCanvas = createCanvas(scaledFrame.width, scaledFrame.height);\n previewCanvas\n .getContext('2d')\n .putImageData(\n new NapiImageData(\n new Uint8ClampedArray(\n scaledFrame.data.buffer,\n scaledFrame.data.byteOffset,\n scaledFrame.data.byteLength,\n ),\n scaledFrame.width,\n scaledFrame.height,\n ),\n 0,\n 0,\n );\n const png = await previewCanvas.encode('png');\n previewHook?.({\n png: new Uint8Array(png.buffer, png.byteOffset, png.byteLength),\n width: scaledFrame.width,\n height: scaledFrame.height,\n samples,\n totalSamples,\n });\n } catch {\n // never let a preview failure break the accumulation loop.\n }\n }\n\n // Read an rgba32float target to a tight interleaved RGBA float buffer,\n // row-band tiled under maxBufferSize with per-row padding stripped.\n private async _readHdrTargetTiled(\n renderer: WebGPURenderer,\n outputTarget: Texture,\n width: number,\n height: number,\n ): Promise<Float32Array> {\n const tightRowFloats = width * 4;\n // Readback rows are padded to a 256-byte multiple (16 rgba32float texels).\n const paddedRowFloats = Math.ceil(width / 16) * 16 * 4;\n const bytesPerRow = paddedRowFloats * 4;\n const rowsPerBand = getBandRowCount(bytesPerRow);\n\n const hdrPixels = new Float32Array(tightRowFloats * height);\n for (\n let bandStartRow = 0;\n bandStartRow < height;\n bandStartRow += rowsPerBand\n ) {\n const bandHeight = Math.min(rowsPerBand, height - bandStartRow);\n const bandPixels = (await renderer.readRenderTargetPixelsAsync(\n { textures: [outputTarget] } as unknown as RenderTarget,\n 0,\n bandStartRow,\n width,\n bandHeight,\n )) as Float32Array;\n if (bandPixels.length === tightRowFloats * bandHeight) {\n hdrPixels.set(bandPixels, bandStartRow * tightRowFloats);\n } else {\n // Strip per-row padding.\n for (let rowInBand = 0; rowInBand < bandHeight; rowInBand++) {\n hdrPixels.set(\n bandPixels.subarray(\n rowInBand * paddedRowFloats,\n rowInBand * paddedRowFloats + tightRowFloats,\n ),\n (bandStartRow + rowInBand) * tightRowFloats,\n );\n }\n }\n }\n return hdrPixels;\n }\n\n // Run the external denoiser over the accumulated HDR texture, write the\n // result back into it, and re-blit to the canvas.\n private async _denoiseAndBlit(\n renderer: WebGPURenderer,\n pathTracer: WebGPUPathTracer,\n ): Promise<void> {\n if (!externalDenoiser) {\n throw new Error(\n 'WebGPUPathTracerRenderer: pathTracerSettings.denoise=true but no ' +\n 'external denoiser is registered. Use setPathTracerExternalDenoiser() with Intel OIDN binaries',\n );\n }\n\n const outputTarget = pathTracer._pathTracer.outputTarget;\n const { width, height } = outputTarget;\n\n const hdrPixels = await this._readHdrTargetTiled(\n renderer,\n outputTarget,\n width,\n height,\n );\n\n const denoisedPixels = await externalDenoiser(hdrPixels, width, height);\n\n // Write back into the tracer's own texture because a fresh full-frame\n // DataTexture would overflow maxBufferSize at 8K.\n this._writeDenoisedIntoTarget(\n renderer,\n outputTarget,\n denoisedPixels,\n width,\n height,\n );\n\n // Blit through the tracer's own quad (tone mapping happens in the blit\n // shader, so the renderer's own tone mapping must be off meanwhile).\n const blitQuad = pathTracer._blitQuad;\n const originalToneMapping = renderer.toneMapping;\n const originalExposure = renderer.toneMappingExposure;\n const originalAutoClear = renderer.autoClear;\n const originalTarget = renderer.getRenderTarget();\n renderer.toneMapping = 0; // NoToneMapping\n renderer.toneMappingExposure = 1.0;\n renderer.autoClear = true;\n renderer.setRenderTarget(null);\n\n blitQuad.material.transition = 1.0;\n blitQuad.material.opacity = 1.0;\n blitQuad.material.texture = outputTarget;\n blitQuad.material.toneMapping = originalToneMapping;\n blitQuad.material.toneMappingExposure = originalExposure;\n blitQuad.render(renderer);\n\n renderer.autoClear = originalAutoClear;\n renderer.toneMapping = originalToneMapping;\n renderer.toneMappingExposure = originalExposure;\n renderer.setRenderTarget(originalTarget);\n }\n\n // Write a tight RGBA-float image into the accumulation texture via\n // queue.writeTexture, row-band tiled under maxBufferSize (Dawn stages it too).\n private _writeDenoisedIntoTarget(\n renderer: WebGPURenderer,\n outputTarget: Texture,\n denoisedPixels: Float32Array,\n width: number,\n height: number,\n ): void {\n const backend = renderer.backend as unknown as WebGPUBackendInternals;\n const gpuTexture = backend?.get?.(outputTarget)?.texture;\n const queue = backend?.device?.queue;\n if (!gpuTexture || !queue) {\n throw new Error(\n 'WebGPUPathTracerRenderer: could not access the backend GPU texture for the denoise write-back.',\n );\n }\n\n const bytesPerRow = width * 16; // rgba32float, tight rows\n const floatsPerRow = width * 4;\n const rowsPerBand = getBandRowCount(bytesPerRow);\n for (\n let bandStartRow = 0;\n bandStartRow < height;\n bandStartRow += rowsPerBand\n ) {\n const bandHeight = Math.min(rowsPerBand, height - bandStartRow);\n const bandPixels = denoisedPixels.subarray(\n bandStartRow * floatsPerRow,\n (bandStartRow + bandHeight) * floatsPerRow,\n );\n queue.writeTexture(\n { texture: gpuTexture, origin: { x: 0, y: bandStartRow, z: 0 } },\n bandPixels,\n { offset: 0, bytesPerRow, rowsPerImage: bandHeight },\n { width, height: bandHeight, depthOrArrayLayers: 1 },\n );\n }\n }\n\n private async _getPathTracer(\n renderer: WebGPURenderer,\n ): Promise<WebGPUPathTracer> {\n if (this._pathTracer && this._renderer === renderer) {\n return this._pathTracer;\n }\n this._pathTracer?.dispose();\n\n const { WebGPUPathTracer: WebGPUPathTracerClass } = await import(\n 'three-gpu-pathtracer/webgpu'\n );\n const pathTracer = new WebGPUPathTracerClass(renderer);\n\n // This wires the BVH data AND the material into\n // the kernel, without it the first compute-pipeline build crashes.\n pathTracer.useMegakernel(true);\n\n pathTracer.dynamicLowRes = false;\n pathTracer.renderDelay = 0;\n pathTracer.fadeDuration = 0;\n pathTracer.minSamples = 1;\n\n this._pathTracer = pathTracer;\n this._renderer = renderer;\n return pathTracer;\n }\n}\n","import type { Camera, PerspectiveCamera, Scene, WebGLRenderer } from 'three';\nimport { Euler } from 'three';\nimport type { WebGPURenderer } from 'three/webgpu';\nimport ImageRenderer, {\n type CameraTarget,\n type ImageFrame,\n} from '../../common-core/src/webgl/image-renderer';\nimport type { PrepareImageOptionsPlanner } from '../../planner-core/src/webgl/planner-scene-manager';\nimport type { GenericRoomleRenderer } from '../../common-core/src/roomle-renderer';\nimport { RoomleRenderer } from '../../common-core/src/roomle-renderer';\nimport {\n WebGPUPathTracerRenderer,\n type PathTracerSettings,\n} from './headless-web-gpu-path-tracer-renderer';\nimport { type WebGPUMockContext } from './setup-environment';\nimport type { RawImagePixels } from '../../common-core/src/common-interfaces';\n\nexport interface HeadlessPrepareImageOptions\n extends PrepareImageOptionsPlanner {\n width?: number;\n height?: number;\n cameraPosition?: { x: number; y: number; z: number };\n cameraPitch?: number;\n cameraYaw?: number;\n fov?: number;\n pathTracer?: boolean;\n pathTracerSamples?: number;\n pathTracerSettings?: PathTracerSettings;\n preferRawPixels?: boolean;\n rawPixelsScale?: number;\n}\n\nexport class HeadlessImageRenderer extends ImageRenderer {\n private _headlessPathTracerRenderer: WebGPUPathTracerRenderer | null = null;\n\n protected override async _createThreejsImageRenderer(): Promise<\n WebGLRenderer | WebGPURenderer\n > {\n if (!this._initData.featureFlags.webGpu) {\n return super._createThreejsImageRenderer();\n }\n const { WebGPURenderer } = await import('three/webgpu');\n\n // antialias don't work with ssgi\n const imageRenderer = new WebGPURenderer({ antialias: false, alpha: true });\n await imageRenderer.init();\n return imageRenderer;\n }\n\n protected override async _createRoomleRenderer(\n imageRenderer: WebGLRenderer | WebGPURenderer,\n ): Promise<GenericRoomleRenderer> {\n if (!(imageRenderer as WebGPURenderer).isWebGPURenderer) {\n return super._createRoomleRenderer(imageRenderer);\n }\n const { RoomleWebGPURenderer } = await import(\n '../../common-core/src/webgpu/roomle-webgpu-renderer'\n );\n return new RoomleWebGPURenderer(\n this._creator_,\n imageRenderer as WebGPURenderer,\n true,\n undefined,\n false,\n );\n }\n\n public static hasExplicitCameraPlacement(\n options: HeadlessPrepareImageOptions,\n ): boolean {\n return (\n options.cameraPosition !== undefined ||\n options.cameraPitch !== undefined ||\n options.cameraYaw !== undefined\n );\n }\n public static placeCameraExplicitly(\n camera: PerspectiveCamera,\n options: HeadlessPrepareImageOptions,\n ) {\n camera.near = 0.01;\n camera.far = 1000;\n if (options.cameraPosition) {\n camera.position.set(\n options.cameraPosition.x,\n options.cameraPosition.y,\n options.cameraPosition.z,\n );\n }\n if (options.cameraPitch !== undefined || options.cameraYaw !== undefined) {\n // handles the case where the camera was set in first person mode\n camera.quaternion.setFromEuler(\n new Euler(\n -(options.cameraPitch ?? 0),\n options.cameraYaw ?? 0,\n 0,\n 'ZYX',\n ),\n );\n }\n camera.updateProjectionMatrix();\n }\n\n protected override _getRenderSize(options: HeadlessPrepareImageOptions): {\n width: number;\n height: number;\n } {\n const { width, height } = super._getRenderSize(options);\n return { width: options.width ?? width, height: options.height ?? height };\n }\n\n protected override _placeCamera(\n camera: PerspectiveCamera,\n cameraTarget: CameraTarget | undefined,\n angleY: number,\n options: HeadlessPrepareImageOptions,\n ): void {\n if (HeadlessImageRenderer.hasExplicitCameraPlacement(options)) {\n HeadlessImageRenderer.placeCameraExplicitly(camera, options);\n } else {\n super._placeCamera(camera, cameraTarget, angleY, options);\n if (cameraTarget) {\n camera.near = Math.max(0.01, cameraTarget.diagonal * 0.5);\n }\n }\n\n if (options.fov !== undefined) {\n camera.fov = options.fov;\n }\n const { width, height } = this._getRenderSize(options);\n camera.aspect = width / height;\n camera.updateProjectionMatrix();\n }\n\n protected override async _renderFrame(\n renderInstance: GenericRoomleRenderer | WebGLRenderer | WebGPURenderer,\n scene: Scene,\n camera: Camera,\n options: HeadlessPrepareImageOptions,\n ): Promise<ImageFrame> {\n if (options.pathTracer) {\n const imageDataUrl = await this._renderPathTracedFrame(\n renderInstance,\n scene,\n camera,\n {\n samples: options.pathTracerSamples,\n settings: options.pathTracerSettings,\n },\n );\n return { imageDataUrl };\n }\n return super._renderFrame(renderInstance, scene, camera, options);\n }\n\n private async _renderPathTracedFrame(\n renderInstance: GenericRoomleRenderer | WebGLRenderer | WebGPURenderer,\n scene: Scene,\n camera: Camera,\n options: { samples?: number; settings?: PathTracerSettings },\n ): Promise<string> {\n const renderer =\n renderInstance instanceof RoomleRenderer\n ? renderInstance.renderer\n : renderInstance;\n if (!(renderer as WebGPURenderer).isWebGPURenderer) {\n throw new Error(\n 'HeadlessImageRenderer: path tracing requires the WebGPU renderer (enable featureFlags.webGpu)',\n );\n }\n if (!this._headlessPathTracerRenderer) {\n this._headlessPathTracerRenderer = new WebGPUPathTracerRenderer();\n }\n await this._headlessPathTracerRenderer.render(\n renderer as WebGPURenderer,\n scene,\n camera,\n options.samples,\n options.settings,\n );\n const { imageDataUrl } = await this._readFrameAndClear(\n renderer as WebGLRenderer | WebGPURenderer,\n );\n return imageDataUrl;\n }\n\n protected override async _readFrameAndClear(\n renderer: WebGLRenderer | WebGPURenderer,\n options?: HeadlessPrepareImageOptions,\n ): Promise<ImageFrame> {\n const webGpuContext = this._getWebGpuMockContext(renderer);\n if (!webGpuContext) {\n return super._readFrameAndClear(renderer, options);\n }\n\n // dimensions compositing needs a data url, so raw pixels are only honored without it\n const preferRawPixels =\n !!options?.preferRawPixels && !options?.showDimensions;\n\n const scaledPixels = preferRawPixels\n ? await this._readScaledRawPixels(\n renderer,\n webGpuContext,\n options?.rawPixelsScale,\n )\n : null;\n if (scaledPixels) {\n renderer.clear();\n return { imageDataUrl: '', pixels: scaledPixels };\n }\n\n const pixels = await webGpuContext.flushReadback();\n if (preferRawPixels && pixels) {\n const { width, height } = renderer.domElement;\n renderer.clear();\n return { imageDataUrl: '', pixels: { data: pixels, width, height } };\n }\n return super._readFrameAndClear(renderer, options);\n }\n\n private _getWebGpuMockContext(\n renderer: WebGLRenderer | WebGPURenderer,\n ): WebGPUMockContext | null {\n if (!(renderer as WebGPURenderer).isWebGPURenderer) {\n return null;\n }\n return (\n ((renderer.domElement as HTMLCanvasElement)?.getContext?.(\n 'webgpu',\n ) as unknown as WebGPUMockContext) ?? null\n );\n }\n\n private async _readScaledRawPixels(\n renderer: WebGLRenderer | WebGPURenderer,\n webGpuContext: WebGPUMockContext,\n rawPixelsScale: number | undefined,\n ): Promise<RawImagePixels | null> {\n if (!rawPixelsScale || rawPixelsScale <= 0 || rawPixelsScale >= 1) {\n return null;\n }\n const { width, height } = renderer.domElement;\n return webGpuContext.readbackScaled(\n Math.max(1, Math.round(width * rawPixelsScale)),\n Math.max(1, Math.round(height * rawPixelsScale)),\n );\n }\n}\n","// Prevents tween effects from happening in headless mode, we don't ever want\n// things to fade in or out headlessly because it creates delays and causes\n// elements to show up white or half-visible as they tween in while taking a screenshot.\n\ninterface MinimalTween {\n end?: () => void;\n}\n\ninterface MinimalTweenGroup {\n add(tween: MinimalTween): void;\n remove(_tween: MinimalTween): void;\n removeAll(): void;\n getAll(): MinimalTween[];\n update(_time?: number): void;\n}\n\nconst noopGroup: MinimalTweenGroup = {\n add(tween) {\n // Synchronously fast-forward. Wrapped in try/catch so a single\n // misbehaving tween can't poison the whole queue — at worst that\n // tween silently doesn't apply, exactly as in browser mode if it\n // never advances.\n try {\n tween.end?.();\n } catch {\n // ignore\n }\n },\n remove() {},\n removeAll() {},\n getAll() {\n return [];\n },\n update() {},\n};\n\nexport const installHeadlessTweenStub = (): void => {\n const target = window as unknown as { TWEEN?: MinimalTweenGroup };\n // Always overwrite — if a previous SDK boot left the real Group here,\n // we want our noop in its place for the new instance.\n target.TWEEN = noopGroup;\n};\n","import type { Texture } from 'three';\nimport { EquirectangularReflectionMapping } from 'three';\nimport { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';\nimport { HDRLoader } from 'three/examples/jsm/loaders/HDRLoader.js';\nimport HdrEnvironmentLoader, {\n type EnvironmentMapResult,\n} from '../../common-core/src/services/hdr-environment-loader';\n\nexport class HeadlessHdrEnvironmentLoader extends HdrEnvironmentLoader {\n private _callbacks = new Set<(maps: EnvironmentMapResult) => void>();\n private _dispatcherInstalled = false;\n\n public override registerEnvironmentChangedCallback(\n callBack: (maps: EnvironmentMapResult) => void,\n ): void {\n this._callbacks.add(callBack);\n if (!this._dispatcherInstalled) {\n // Install our dispatcher into the base's single slot once; later\n // registrations only grow the set (no unregister path needed headlessly).\n super.registerEnvironmentChangedCallback((maps) => {\n this._callbacks.forEach((cb) => cb(maps));\n });\n this._dispatcherInstalled = true;\n }\n }\n\n public static async loadEquirectTexture(url: string): Promise<Texture> {\n const loader = url.toLowerCase().endsWith('.exr')\n ? new EXRLoader()\n : new HDRLoader();\n const texture = await loader.loadAsync(url);\n texture.mapping = EquirectangularReflectionMapping;\n return texture;\n }\n}\n","/**\n * Main entry point for the headless Roomle SDK.\n * Provides the ability to run the Roomle web-sdk in Node.js/Bun\n * for loading configurations/plans and exporting them to GLB/USDZ.\n */\nimport HeadlessScriptLoader from './headless-script-loader';\nimport HeadlessDomHelper from './headless-dom-helper';\nimport HeadlessDataSyncer from './headless-data-syncer';\nimport HeadlessPlannerKernelAccess from './headless-planner-kernel-access';\nimport HeadlessKernelCallbackGuard from './headless-kernel-callback-guard';\nimport { HeadlessWebGpuGroundShadowContainer } from './headless-web-gpu-ground-shadow-container';\nimport type { PathTracerSettings } from './headless-web-gpu-path-tracer-renderer';\nimport {\n HeadlessImageRenderer,\n type HeadlessPrepareImageOptions,\n} from './headless-image-renderer';\nimport { installHeadlessTweenStub } from './headless-tween-stub';\nimport type {\n Configurator,\n FeatureFlags,\n GlbViewer,\n GlobalInitDataDefinition,\n InitDataDefinition,\n Planner,\n RapiId,\n RoomleConfigurator,\n RoomleGLBViewer,\n RoomlePlanner,\n} from '../../index';\nimport { RoomleSdk } from '../../index';\nimport type { WebGpuConfigDefinition } from '../../common-core/src/utils/shims';\nimport {\n Container,\n DependencyInjectionAssignment,\n DI_TYPE,\n} from '../../common-core/src/di/container';\nimport { INJECTABLES } from '../../common-core/src/di/injectables';\nimport {\n isIdAComponent,\n isIdAConfigurationString,\n isIdAnItem,\n isIdAPlan,\n} from '#/common/src/utils/id-inferrer';\nimport type {\n Base64Image,\n RawImagePixels,\n} from '../../common-core/src/common-interfaces';\nimport { HeadlessHdrEnvironmentLoader } from './headless-hdr-environment-loader';\nimport { ENV_HDR_256_FILE_NAME } from '../../common-core/src/static-files/environment-maps';\nimport type { Mesh } from 'three';\n\nconst DEFAULT_IMAGE_SIZE = 1024;\nconst INLINE_PLAN_XML_ID_PREFIX = 'inline-plan-xml#';\nconst INLINE_CONFIGURATION_ID_PREFIX = 'inline-configuration#';\n\nexport interface HeadlessInitData\n extends Partial<InitDataDefinition>,\n Partial<GlobalInitDataDefinition> {\n configuratorId: string;\n width?: number; // default 1024\n height?: number; // default 768\n featureFlags?: Partial<FeatureFlags>;\n}\n\nexport interface ExportResult {\n buffer: ArrayBuffer;\n}\n\nexport interface EnvironmentMapOptions {\n intensity?: number;\n rotation?: number;\n}\n\nexport interface ImageExportOptions {\n ssaa?: number; // Default 2 samples for rasterized, 1 for path-traced\n size?: number; // Default 1024, ignored when width and height are set.\n width?: number;\n height?: number;\n pathTracer?: boolean;\n pathTracerSamples?: number; // Default 10\n pathTracerSettings?: PathTracerSettings;\n}\n\nexport interface RotatableImageExportOptions {\n rotationY?: number;\n cameraPosition?: { x: number; y: number; z: number };\n cameraPitch?: number;\n cameraYaw?: number;\n fov?: number;\n showWallsAndCeiling?: boolean; // Planner only.\n}\n\nexport interface ScreenshotExportOptions\n extends ImageExportOptions,\n RotatableImageExportOptions {\n quality?: number; // Jpeg quality, 0 to 1, default 1\n}\n\nexport interface PerspectiveImageExportOptions\n extends ImageExportOptions,\n RotatableImageExportOptions {\n showDimensions?: boolean;\n}\n\nexport interface TopImageExportOptions extends ImageExportOptions {\n showDimensions?: boolean;\n}\n\nexport class HeadlessRoomleSdk {\n private _planner: RoomlePlanner;\n private _plannerMain: Planner;\n private _configurator: RoomleConfigurator;\n private _configuratorMain: Configurator;\n private _glbViewer: RoomleGLBViewer;\n private _glbViewerMain: GlbViewer;\n private _initialized = false;\n private _configuratorReady = false;\n private _plannerReady = false;\n private _glbViewerReady = false;\n private _loadedConfigurationId: RapiId | null = null;\n private _loadedPlanId: string | null = null;\n private _loadedStaticItemId: string | null = null;\n private _width = 1024;\n private _height = 768;\n private _featureFlags?: Partial<FeatureFlags>;\n private _webGpuConfig?: Partial<WebGpuConfigDefinition>;\n private _loaderQueue: Promise<void> = Promise.resolve();\n // Pre-skybox background per scene, captured on first setSkybox() so\n // resetSkybox() can restore it (scenes are long-lived headlessly).\n private _skyboxOriginal = new WeakMap<\n object,\n { background: unknown; showEnvironmentBackground: unknown }\n >();\n\n /**\n * Create and initialize a headless SDK instance.\n * @param initData Configurator id plus optional canvas size and global init fields.\n * @returns Initialized SDK instance.\n */\n public static async create(\n initData: HeadlessInitData,\n ): Promise<HeadlessRoomleSdk> {\n const instance = new HeadlessRoomleSdk();\n await instance._init(initData);\n return instance;\n }\n\n private async _init(initData: HeadlessInitData) {\n this._width = initData.width ?? 1024;\n this._height = initData.height ?? 768;\n this._featureFlags = initData.featureFlags;\n if (!initData.featureFlags?.webGpu) {\n console.warn(\n 'HeadlessRoomleSdk: WebGPU is required, forcing featureFlags.webGpu on.',\n );\n this._featureFlags = { ...initData.featureFlags, webGpu: true };\n }\n // Pin the render quality: adaptive quality makes a one-shot export depend\n // on how fast the machine happened to be, which is not reproducible.\n // See .agents/headless-sdk.md § Render quality is pinned headlessly.\n // Spread last so an explicit caller value still wins.\n this._webGpuConfig = {\n autoDetectQualityLevel: false,\n autoQualityMinFps: 0,\n autoQualityMaxFps: Number.MAX_SAFE_INTEGER,\n ...initData.webGpuConfig,\n };\n\n installHeadlessTweenStub();\n\n // Register headless overrides before the DI container initializes\n this._registerHeadlessServices(this._width, this._height);\n\n await RoomleSdk.setGlobalInitData({\n configuratorId: initData.configuratorId,\n locale: initData.locale,\n // @ts-ignore -- tenant is untyped, but we still need it.\n tenant: initData.tenant,\n overrideCountry: initData.overrideCountry,\n customApiUrl: initData.customApiUrl,\n overrideRapi: initData.overrideRapi,\n animateCamera: false,\n });\n\n this._initialized = true;\n }\n\n private _deepMergeHeadlessInitDefaults(\n initData?: Partial<InitDataDefinition>,\n ): Partial<InitDataDefinition> | undefined {\n if (!this._featureFlags && !this._webGpuConfig) {\n return initData;\n }\n const incoming = (initData as { featureFlags?: Partial<FeatureFlags> })\n ?.featureFlags;\n return {\n ...initData,\n featureFlags: { ...this._featureFlags, ...incoming },\n webGpuConfig: { ...this._webGpuConfig, ...initData?.webGpuConfig },\n } as Partial<InitDataDefinition>;\n }\n\n private _registerHeadlessServices(width: number, height: number) {\n // Create the DI container early and register the full INJECTABLES here.\n // roomle-dependency-injection's _ensureContainer() skips it if it exists.\n const container = new Container();\n (globalThis as any).__RML__DI__ = container;\n\n // Capture width/height in a subclass so the DI container instantiates\n // HeadlessDomHelper with the correct dimensions (DI only passes `creator`).\n class SizedHeadlessDomHelper extends HeadlessDomHelper {\n constructor(creator: string) {\n super(creator, width, height);\n }\n }\n\n // override: true marks each key so that subsequent boot() calls that\n // re-register the default INJECTABLES cannot overwrite these entries.\n const headlessOverrides = [\n new DependencyInjectionAssignment(\n 'script-loader',\n HeadlessScriptLoader,\n DI_TYPE.CONTEXT,\n true,\n ),\n new DependencyInjectionAssignment(\n 'dom-helper',\n SizedHeadlessDomHelper,\n DI_TYPE.CONTEXT,\n true,\n ),\n new DependencyInjectionAssignment(\n 'data-syncer',\n HeadlessDataSyncer,\n DI_TYPE.GLOBAL,\n true,\n ),\n new DependencyInjectionAssignment(\n 'planner-kernel-access',\n HeadlessPlannerKernelAccess,\n DI_TYPE.GLOBAL,\n true,\n ),\n new DependencyInjectionAssignment(\n 'kernel-callback-guard',\n HeadlessKernelCallbackGuard,\n DI_TYPE.GLOBAL,\n true,\n ),\n new DependencyInjectionAssignment(\n 'web-gpu-ground-shadow-reflection',\n HeadlessWebGpuGroundShadowContainer,\n DI_TYPE.CONTEXT,\n true,\n ),\n new DependencyInjectionAssignment(\n 'image-renderer',\n HeadlessImageRenderer,\n DI_TYPE.CONTEXT,\n true,\n ),\n // GLOBAL to mirror the base registration: the loader fans one resolved\n // environment map out to every registered callback, which only works if\n // configurator and planner keep sharing ONE loader instance.\n new DependencyInjectionAssignment(\n 'hdr-environment-loader',\n HeadlessHdrEnvironmentLoader,\n DI_TYPE.GLOBAL,\n true,\n ),\n ];\n\n // Defaults first, then headless overrides (override: true keeps later\n // boot() re-registrations from breaking them).\n container.addDependencyInjectionAssignments(INJECTABLES);\n container.addDependencyInjectionAssignments(headlessOverrides);\n }\n\n private _makeStubElement() {\n return {\n clientWidth: this._width,\n clientHeight: this._height,\n appendChild() {},\n removeChild() {},\n style: {},\n classList: { add() {} },\n };\n }\n\n /**\n * Boot and initialize the configurator module ahead of first load.\n * @param initData Optional per-module init overrides.\n */\n public async prewarmConfigurator(\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n this._ensureInitialized();\n const configuratorModule = await RoomleSdk.getConfigurator(\n this._deepMergeHeadlessInitDefaults(initData),\n );\n await configuratorModule.boot();\n this._configuratorMain = configuratorModule;\n this._configurator = configuratorModule.getApi();\n await this._configurator.init(this._makeStubElement() as any);\n this._configuratorReady = true;\n }\n\n /**\n * Boot and initialize the planner module ahead of first load.\n * @param initData Optional per-module init overrides.\n */\n public async prewarmPlanner(\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n this._ensureInitialized();\n const plannerModule = await RoomleSdk.getPlanner({\n moc: true,\n ...this._deepMergeHeadlessInitDefaults(initData),\n });\n if (initData?.moc === false) {\n throw new Error('HeadlessRoomleSdk: planner requires moc:true');\n }\n await plannerModule.boot();\n this._plannerMain = plannerModule;\n this._planner = plannerModule.getApi();\n await this._planner.init(this._makeStubElement() as any);\n this._plannerReady = true;\n }\n\n /**\n * Boot and initialize the GLB viewer module ahead of first load.\n * @param initData Optional per-module init overrides.\n */\n public async prewarmGlbViewer(\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n this._ensureInitialized();\n const glbModule = await RoomleSdk.getGlbViewer(\n this._deepMergeHeadlessInitDefaults(initData),\n );\n await glbModule.boot();\n this._glbViewerMain = glbModule;\n this._glbViewer = glbModule.getApi();\n await this._glbViewer.init(this._makeStubElement() as any);\n this._glbViewerReady = true;\n }\n\n /**\n * Load a saved configuration by **id**, via the RAPI `/configurations/`\n * endpoint. For an inline configuration string use\n * {@link loadConfigurationString} instead.\n * @param configurationId Multi-segment id like `catalog:item:HASH`.\n * @param initData Optional per-module init overrides for prewarm.\n */\n public async loadConfiguration(\n configurationId: RapiId,\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n return this._loadIntoConfigurator(\n configurationId,\n () => this._configurator.loadConfigurationById(configurationId),\n initData,\n );\n }\n\n /**\n * Load a configuration string directly without querying the RAPI (`{\"componentId\":\"catalog:item\",…}`)\n * To load a configuration by ID, use {@link loadConfiguration}\n */\n public async loadConfigurationString(\n configuration: string,\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n const identity = await this._inlineSceneId(\n INLINE_CONFIGURATION_ID_PREFIX,\n configuration,\n );\n return this._loadIntoConfigurator(\n identity,\n () => this._configurator.loadConfiguration(configuration),\n initData,\n );\n }\n\n /**\n * Load a configurable item via the RAPI `/items/` endpoint.\n * @param itemId Single-segment id like `catalog:item`.\n * @param initData Optional per-module init overrides for prewarm.\n */\n public async loadConfigurableItemById(\n itemId: RapiId,\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n return this._loadIntoConfigurator(\n itemId,\n () => this._configurator.loadConfigurableItemById(itemId),\n initData,\n );\n }\n\n private async _loadIntoConfigurator(\n identity: string,\n load: () => Promise<unknown>,\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n this._ensureInitialized();\n return this._withLoader(async () => {\n if (!this._configuratorReady) {\n await this.prewarmConfigurator(initData);\n }\n if (this._loadedConfigurationId === identity) {\n return;\n }\n // Remove the planner VM from PKA listeners so it doesn't process\n // configurator kernel callbacks (both are pre-warmed in one process).\n const { pka, plannerVM } = this._plannerReady\n ? this._getPlannerListenerInfo()\n : {};\n if (pka && plannerVM) {\n pka.removeConfiguratorListener(plannerVM);\n }\n try {\n await this._armOnReadyForRender();\n await load();\n this._loadedConfigurationId = identity;\n this._loadedPlanId = null;\n this._loadedStaticItemId = null;\n await this._settleScene();\n } finally {\n if (pka && plannerVM) {\n pka.addConfiguratorListener(plannerVM);\n }\n }\n });\n }\n\n /**\n * Load a static (non-configurable) item via the GLB viewer.\n * @param staticItemId Catalog item id whose RAPI record has no `configuration`.\n * @param initData Optional per-module init overrides for prewarm.\n */\n public async loadStaticItem(\n staticItemId: string,\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n this._ensureInitialized();\n return this._withLoader(async () => {\n if (!this._glbViewerReady) {\n await this.prewarmGlbViewer(initData);\n }\n if (this._loadedStaticItemId === staticItemId) {\n return;\n }\n await this._armOnReadyForRender();\n await this._glbViewer.loadStaticItem(staticItemId);\n this._suppressGlbSelfShadowing();\n this._loadedStaticItemId = staticItemId;\n this._loadedConfigurationId = null;\n this._loadedPlanId = null;\n await this._settleScene();\n });\n }\n\n /**\n * GLB-loaded scenes commonly contain hundreds of thousands of triangles\n * (high-poly furniture/cars). With the default 1024² shadow map those\n * triangles cast a self-shadow pattern back onto themselves that the\n * shadow comparison can't resolve cleanly — it appears as a dense\n * triangle/herringbone overlay covering every PBR-shaded surface\n * (visible on flat-coloured car bodies, wood slats, etc.). The\n * artifact is specific to headless's GL backend; in the browser the\n * same scene renders fine.\n *\n * Walking the freshly-loaded scene and clearing `castShadow` on every\n * mesh removes the cause of the acne while keeping `receiveShadow`,\n * so static items still pick up shadows from external casters and\n * full PBR direct-light shading is preserved.\n */\n private _suppressGlbSelfShadowing(): void {\n const sceneManager = this._glbViewer.getSceneManager();\n const root = sceneManager.getScene();\n\n if (!root) {\n return;\n }\n\n root.traverse((node) => {\n if ((node as Mesh).isMesh) {\n (node as Mesh).castShadow = false;\n }\n });\n }\n\n async loadObject(\n id: string,\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n this._ensureInitialized();\n if (isIdAComponent(id)) {\n throw new Error(\n `HeadlessRoomleSdk: component ids (\"${id}\") are not supported. Use loadConfigurableItemById, loadConfiguration, or loadPlan directly.`,\n );\n }\n if (isIdAConfigurationString(id)) {\n return this.loadConfigurationString(id, initData);\n }\n if (isIdAPlan(id)) {\n return this.loadPlan(id, initData);\n }\n if (isIdAnItem(id)) {\n const rapi = await RoomleSdk.getRapiAccess();\n const item = await rapi.getItem(id as RapiId);\n const isStatic = !item?.configuration;\n if (isStatic) {\n return this.loadStaticItem(id, initData);\n }\n return this.loadConfigurableItemById(id as RapiId, initData);\n }\n return this.loadConfiguration(id as RapiId, initData);\n }\n\n /**\n * Load a plan into the planner.\n * @param planId Plan id (no colons).\n * @param initData Optional per-module init overrides for prewarm.\n */\n async loadPlan(\n planId: string,\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n this._ensureInitialized();\n return this._loadIntoPlanner(\n planId,\n () => this._planner.loadPlan(planId),\n initData,\n );\n }\n\n /**\n * Load plan XML string directly in the planner.\n * @param xml Plan XML (`<plan version=\"2\">…</plan>`).\n * @param initData Optional per-module init overrides for prewarm.\n */\n async loadPlanXml(\n xml: string,\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n this._ensureInitialized();\n const identity = await this._inlineSceneId(INLINE_PLAN_XML_ID_PREFIX, xml);\n return this._loadIntoPlanner(\n identity,\n () => this._planner.loadPlanXml(xml),\n initData,\n );\n }\n\n private async _loadIntoPlanner(\n identity: string,\n load: () => Promise<unknown>,\n initData?: Partial<InitDataDefinition>,\n ): Promise<void> {\n return this._withLoader(async () => {\n if (!this._plannerReady) {\n await this.prewarmPlanner(initData);\n }\n // Skip reload if the same plan is already loaded.\n if (this._loadedPlanId === identity) {\n return;\n }\n // Temporarily remove the configurator view model from PKA listeners so it\n // doesn't process planner kernel callbacks.\n const { pka, configuratorVM } = this._configuratorReady\n ? this._getConfiguratorListenerInfo()\n : {};\n if (pka && configuratorVM) {\n pka.removeConfiguratorListener(configuratorVM);\n }\n try {\n await this._armOnReadyForRender();\n await load();\n this._loadedPlanId = identity;\n // A freshly-loaded plan scene invalidates any previously-loaded configuration.\n this._loadedConfigurationId = null;\n this._loadedStaticItemId = null;\n await this._settleScene();\n } finally {\n if (pka && configuratorVM) {\n pka.addConfiguratorListener(configuratorVM);\n }\n }\n });\n }\n\n private async _inlineSceneId(\n prefix: string,\n payload: string,\n ): Promise<string> {\n const digest = await crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(payload),\n );\n const hex = Array.from(new Uint8Array(digest), (byte) =>\n byte.toString(16).padStart(2, '0'),\n ).join('');\n return prefix + hex.slice(0, 32);\n }\n\n /**\n * Serialises concurrent load operations using a promise-chain queue.\n * Each call chains its task onto the previous queue tail so they run\n * one at a time — listener mutations never overlap across an await.\n * Errors in one task do not stall subsequent ones (the queue tail is\n * always advanced in a settled state).\n */\n private _withLoader<T>(task: () => Promise<T>): Promise<T> {\n const next = this._loaderQueue.then(task);\n // Advance the queue unconditionally so a failing task never blocks\n // future callers.\n this._loaderQueue = next.then(\n () => {},\n () => {},\n );\n return next;\n }\n\n /**\n * Wait for the active scene to fire its onReadyForRender callback —\n * fired by each scene manager once `_itemsLoaded` (planner) /\n * `loadConfiguration` (configurator) / `loadStaticItem` (GLB viewer)\n * has finished constructing the scene and queued its materials. The\n * callback is armed via {@link _armOnReadyForRender} *before* the load\n * begins so synchronous firings (the GLB viewer does this) are still\n * caught.\n */\n private async _settleScene(): Promise<void> {\n if (!this._onReadyForRenderArmed) {\n return;\n }\n await this._onReadyForRenderArmed;\n this._onReadyForRenderArmed = null;\n }\n\n /**\n * Pre-arms an onReadyForRender listener on the *global* callback (shared\n * across configurator/planner/GLB viewer) so that it captures even\n * synchronous firings during the upcoming load. Restores the previous\n * callback after firing. Using the global callback rather than each\n * module's api.callbacks is necessary because the GLB viewer only fires\n * the global one ([glb-viewer-scene-manager.ts:355](packages/web-sdk/packages/glb-viewer-core/src/webgl/glb-viewer-scene-manager.ts:355)).\n */\n private async _armOnReadyForRender(): Promise<void> {\n const globalCallback = (await RoomleSdk.callbacks) as unknown as {\n onReadyForRender?: () => void;\n };\n if (!globalCallback) {\n this._onReadyForRenderArmed = null;\n return;\n }\n const previous = globalCallback.onReadyForRender;\n this._onReadyForRenderArmed = new Promise<void>((resolve) => {\n globalCallback.onReadyForRender = () => {\n try {\n previous?.();\n } finally {\n globalCallback.onReadyForRender = previous;\n resolve();\n }\n };\n });\n }\n\n private _onReadyForRenderArmed: Promise<void> | null = null;\n\n private _getPlannerListenerInfo() {\n try {\n const pka = (globalThis as any).__RML__DI__?.lookup?.(\n 'planner-kernel-access',\n );\n const plannerVM = this._planner?.getSceneManager()?.viewModel;\n return { pka, plannerVM };\n } catch {\n return {};\n }\n }\n\n private _getConfiguratorListenerInfo() {\n try {\n const pka = (globalThis as any).__RML__DI__?.lookup?.(\n 'planner-kernel-access',\n );\n const configuratorVM = this._configurator?.getSceneManager()?.viewModel;\n return { pka, configuratorVM };\n } catch {\n return {};\n }\n }\n\n /**\n * Export the active scene as GLB.\n * @param outputPath Optional file path to write the buffer to.\n * @returns The GLB bytes as an ArrayBuffer.\n */\n async exportGLB(outputPath?: string): Promise<ExportResult> {\n return this._export(outputPath, 'glb');\n }\n\n /**\n * Export the active scene as USDZ.\n * @param outputPath Optional file path to write the buffer to.\n * @returns The USDZ bytes as an ArrayBuffer.\n */\n async exportUSDZ(outputPath?: string): Promise<ExportResult> {\n return this._export(outputPath, 'usdz');\n }\n\n /**\n * Returns the active top-level API (planner or configurator) based on what\n * was last loaded. `loadPlan()` and `loadConfiguration()` are mutually\n * exclusive — each one nulls the other's id — so this resolves cleanly.\n */\n private _getActiveApi():\n | RoomlePlanner\n | RoomleConfigurator\n | RoomleGLBViewer {\n if (this._loadedPlanId) {\n return this._planner;\n }\n if (this._loadedConfigurationId) {\n return this._configurator;\n }\n if (this._loadedStaticItemId) {\n return this._glbViewer;\n }\n throw new Error(\n 'HeadlessRoomleSdk: nothing loaded — call loadPlan(), loadConfiguration(), loadConfigurableItemById(), loadStaticItem() or loadObject() first',\n );\n }\n\n /**\n * Returns the scene manager of whichever module is active. All exporters\n * route through here so plan-mode and config-mode share a single code path.\n */\n private _getActiveSceneManager() {\n if (this._loadedStaticItemId) {\n // GLB viewer has no sceneManager getter; reach into the private field\n // (the export methods used here are shared across scene managers).\n return this._glbViewer.getSceneManager();\n }\n return this._getActiveApi().getSceneManager();\n }\n\n public async setEnvironmentMap(\n url: string,\n options: EnvironmentMapOptions = {},\n ): Promise<void> {\n this._ensureInitialized();\n await this._getEnvironmentSceneManager().loadEnvMap(url);\n this.setEnvironmentMapOptions(options);\n }\n\n public async resetEnvironmentMap(\n options: EnvironmentMapOptions = {},\n ): Promise<void> {\n this._ensureInitialized();\n const sceneManager = this._getEnvironmentSceneManager();\n await sceneManager.loadEnvMap(\n sceneManager.initData?.envMapUrl ?? ENV_HDR_256_FILE_NAME,\n );\n this.setEnvironmentMapOptions(options);\n }\n\n public setEnvironmentMapOptions(options: EnvironmentMapOptions): void {\n if (options.intensity === undefined && options.rotation === undefined) {\n return;\n }\n this._getEnvironmentSceneManager().setEnvironmentMap?.(\n undefined,\n options.intensity,\n options.rotation,\n );\n }\n\n private _getEnvironmentSceneManager() {\n const environmentSceneManager = this._getActiveSceneManager();\n if (typeof environmentSceneManager.loadEnvMap !== 'function') {\n throw new Error(\n 'HeadlessRoomleSdk: setEnvironmentMap is not supported for the active module',\n );\n }\n return environmentSceneManager;\n }\n\n public async setSkybox(url: string): Promise<void> {\n this._ensureInitialized();\n const scene = this._getActiveSceneManager().getScene();\n if (!this._skyboxOriginal.has(scene)) {\n this._skyboxOriginal.set(scene, {\n background: scene.background,\n showEnvironmentBackground: scene.userData.showEnvironmentBackground,\n });\n }\n scene.background =\n await HeadlessHdrEnvironmentLoader.loadEquirectTexture(url);\n // The background is an explicit texture, not the environment map, so the\n // WebGL renderer's env→background sync must not clear or overwrite it.\n scene.userData.showEnvironmentBackground = false;\n }\n\n public resetSkybox(): void {\n this._ensureInitialized();\n const scene = this._getActiveSceneManager().getScene();\n const original = this._skyboxOriginal.get(scene);\n if (!original) {\n return;\n }\n scene.background = original.background as typeof scene.background;\n scene.userData.showEnvironmentBackground =\n original.showEnvironmentBackground;\n this._skyboxOriginal.delete(scene);\n }\n\n private async _export(\n outputPath: string | undefined,\n type: 'glb' | 'usdz',\n ): Promise<ExportResult> {\n this._ensureInitialized();\n\n const activeSceneManager = this._getActiveSceneManager();\n const buffer =\n type === 'glb'\n ? await activeSceneManager.createExportGLB()\n : await activeSceneManager.createUSDZExport();\n\n if (!buffer) {\n throw new Error(`${type.toUpperCase()} export produced no data`);\n }\n\n // The buffer is either ArrayBuffer (GLB) or Uint8Array (USDZ).\n // Extract the underlying ArrayBuffer in both cases.\n const arrayBuffer =\n buffer instanceof ArrayBuffer\n ? buffer\n : ((buffer as Uint8Array).buffer as ArrayBuffer);\n\n if (outputPath) {\n const fs = await import('fs');\n fs.writeFileSync(outputPath, Buffer.from(arrayBuffer));\n }\n\n return { buffer: arrayBuffer };\n }\n\n /**\n * Render a perspective screenshot preserving the scene background.\n * @param outputPath Optional file path to write the PNG to.\n * @param options ssaa, size, quality, rotationY, plus explicit camera\n * placement (cameraPosition, cameraPitch, cameraYaw, fov).\n * @returns Data URL of the rendered image.\n */\n public async exportScreenshot(\n outputPath?: string,\n options: ScreenshotExportOptions = {},\n ): Promise<{ dataUrl: string }> {\n this._ensureInitialized();\n\n const { ssaa, targetWidth, targetHeight, renderWidth, renderHeight } =\n this._getRenderDimensions(options);\n\n const prepareImageOptions: HeadlessPrepareImageOptions = {\n rotationY: options.rotationY,\n cameraPosition: options.cameraPosition,\n cameraPitch: options.cameraPitch,\n cameraYaw: options.cameraYaw,\n fov: options.fov,\n showWallsAndCeiling: options.showWallsAndCeiling,\n width: renderWidth,\n height: renderHeight,\n preserveSceneBackground: true,\n pathTracer: options.pathTracer,\n pathTracerSamples: options.pathTracerSamples,\n pathTracerSettings: options.pathTracerSettings,\n preferRawPixels: true,\n rawPixelsScale: 1 / ssaa,\n };\n const result =\n await this._getActiveApi().preparePerspectiveImage(prepareImageOptions);\n\n const dataUrl = await this._resolveImageDataUrl(\n result,\n targetWidth,\n targetHeight,\n ssaa,\n );\n await this._writeDataUrlToFile(dataUrl, outputPath);\n return { dataUrl };\n }\n\n /**\n * Render a framed perspective image of the scene.\n * @param outputPath Optional file path to write the PNG to.\n * @param options ssaa, size, showDimensions, rotationY, plus explicit\n * camera placement (cameraPosition, cameraPitch, cameraYaw, fov).\n * @returns Data URL plus final image dimensions.\n */\n public async exportPerspectiveImage(\n outputPath?: string,\n options: PerspectiveImageExportOptions = {},\n ): Promise<{ dataUrl: string; width: number; height: number }> {\n this._ensureInitialized();\n\n const { ssaa, targetWidth, targetHeight, renderWidth, renderHeight } =\n this._getRenderDimensions(options);\n\n const prepareImageOptions: HeadlessPrepareImageOptions = {\n ...options,\n width: renderWidth,\n height: renderHeight,\n preferRawPixels: true,\n rawPixelsScale: 1 / ssaa,\n };\n const result =\n await this._getActiveApi().preparePerspectiveImage(prepareImageOptions);\n\n const isResized = !!result.pixels || ssaa > 1;\n const dataUrl = await this._resolveImageDataUrl(\n result,\n targetWidth,\n targetHeight,\n ssaa,\n );\n await this._writeDataUrlToFile(dataUrl, outputPath);\n\n return {\n dataUrl,\n width: isResized ? targetWidth : result.width,\n height: isResized ? targetHeight : result.height,\n };\n }\n\n /**\n * Render a top-down image of the scene. Not supported for static items.\n * @param outputPath Optional file path to write the PNG to.\n * @param options ssaa, size, showDimensions.\n * @returns Data URL plus final image dimensions.\n */\n public async exportTopImage(\n outputPath?: string,\n options: TopImageExportOptions = {},\n ): Promise<{ dataUrl: string; width: number; height: number }> {\n this._ensureInitialized();\n\n if (this._loadedStaticItemId) {\n throw new Error(\n 'HeadlessRoomleSdk: exportTopImage is not supported for static items',\n );\n }\n const api = this._getActiveApi() as RoomlePlanner | RoomleConfigurator;\n\n const ssaa = options.ssaa ?? 2;\n const prepareImageOptions: HeadlessPrepareImageOptions = {\n ...options,\n size: (options.size ?? DEFAULT_IMAGE_SIZE) * ssaa,\n showDimensions: options.showDimensions ?? false,\n preferRawPixels: true,\n // SSAA resolve on the GPU (see exportScreenshot).\n rawPixelsScale: 1 / ssaa,\n };\n const result = await api.prepareTopImage(prepareImageOptions);\n\n const topImage = await this._resolveTopImage(result, ssaa);\n await this._writeDataUrlToFile(topImage.dataUrl, outputPath);\n\n return topImage;\n }\n\n private async _resolveTopImage(\n result: Base64Image,\n ssaa: number,\n ): Promise<{ dataUrl: string; width: number; height: number }> {\n if (result.pixels) {\n // Raw-pixel fast path (top images keep the source aspect from plan\n // bounds); only divide by ssaa when the GPU hasn't already resolved it.\n const isResolvedOnGpu = result.pixels.width < result.width;\n const width = isResolvedOnGpu\n ? result.pixels.width\n : Math.round(result.pixels.width / ssaa);\n const height = isResolvedOnGpu\n ? result.pixels.height\n : Math.round(result.pixels.height / ssaa);\n return {\n dataUrl: await this._rawPixelsToPngDataUrl(\n result.pixels,\n width,\n height,\n ),\n width,\n height,\n };\n }\n if (ssaa > 1) {\n const width = Math.round(result.width / ssaa);\n const height = Math.round(result.height / ssaa);\n return {\n dataUrl: await this._ssaaDownscale(result.image, width, height),\n width,\n height,\n };\n }\n return {\n dataUrl: result.image,\n width: result.width,\n height: result.height,\n };\n }\n\n private _getRenderDimensions(options: ImageExportOptions): {\n ssaa: number;\n targetWidth: number;\n targetHeight: number;\n renderWidth: number;\n renderHeight: number;\n } {\n const ssaa = options.ssaa ?? (options.pathTracer ? 1 : 2);\n const targetWidth = options.width ?? options.size ?? DEFAULT_IMAGE_SIZE;\n const targetHeight = options.height ?? options.size ?? DEFAULT_IMAGE_SIZE;\n return {\n ssaa,\n targetWidth,\n targetHeight,\n renderWidth: targetWidth * ssaa,\n renderHeight: targetHeight * ssaa,\n };\n }\n\n private async _resolveImageDataUrl(\n result: Base64Image,\n targetWidth: number,\n targetHeight: number,\n ssaa: number,\n ): Promise<string> {\n if (result.pixels) {\n return this._rawPixelsToPngDataUrl(\n result.pixels,\n targetWidth,\n targetHeight,\n );\n }\n if (ssaa > 1) {\n return this._ssaaDownscale(result.image, targetWidth, targetHeight);\n }\n return result.image;\n }\n\n private async _writeDataUrlToFile(\n dataUrl: string,\n outputPath?: string,\n ): Promise<void> {\n if (!outputPath) {\n return;\n }\n const fs = await import('fs');\n const base64 = dataUrl.replace(/^data:image\\/\\w+;base64,/, '');\n fs.writeFileSync(outputPath, Buffer.from(base64, 'base64'));\n }\n\n private async _rawPixelsToPngDataUrl(\n pixels: RawImagePixels,\n targetWidth: number,\n targetHeight: number,\n ): Promise<string> {\n const { createCanvas, ImageData: NapiImageData } = await import(\n '@napi-rs/canvas'\n );\n const sourceCanvas = createCanvas(pixels.width, pixels.height);\n sourceCanvas\n .getContext('2d')\n .putImageData(\n new NapiImageData(\n new Uint8ClampedArray(\n pixels.data.buffer,\n pixels.data.byteOffset,\n pixels.data.byteLength,\n ),\n pixels.width,\n pixels.height,\n ),\n 0,\n 0,\n );\n if (targetWidth === pixels.width && targetHeight === pixels.height) {\n return sourceCanvas.toDataURLAsync('image/png');\n }\n const outputCanvas = createCanvas(targetWidth, targetHeight);\n const outputContext = outputCanvas.getContext('2d');\n outputContext.imageSmoothingEnabled = true;\n outputContext.imageSmoothingQuality = 'high';\n outputContext.drawImage(sourceCanvas, 0, 0, targetWidth, targetHeight);\n return outputCanvas.toDataURLAsync('image/png');\n }\n\n /** Downscale a data URL image to target dimensions using high-quality filtering. */\n private async _ssaaDownscale(\n dataUrl: string,\n targetWidth: number,\n targetHeight: number,\n ): Promise<string> {\n const { createCanvas, loadImage } = await import('@napi-rs/canvas');\n const imageBuffer = Buffer.from(\n dataUrl.replace(/^data:image\\/\\w+;base64,/, ''),\n 'base64',\n );\n const image = await loadImage(imageBuffer);\n const outputCanvas = createCanvas(targetWidth, targetHeight);\n const outputContext = outputCanvas.getContext('2d');\n outputContext.imageSmoothingEnabled = true;\n outputContext.imageSmoothingQuality = 'high';\n outputContext.drawImage(image, 0, 0, targetWidth, targetHeight);\n return outputCanvas.toDataURLAsync('image/png');\n }\n\n getConfiguratorApi() {\n return this._configurator;\n }\n\n getPlannerApi() {\n return this._planner;\n }\n\n /** Destroy any prewarmed configurator, planner, and GLB viewer modules. */\n public async destroy() {\n if (this._configuratorMain) {\n this._configuratorMain.destroy();\n }\n if (this._plannerMain) {\n this._plannerMain.destroy();\n }\n if (this._glbViewerMain) {\n this._glbViewerMain.destroy();\n }\n }\n\n private _ensureInitialized() {\n if (!this._initialized) {\n throw new Error(\n 'HeadlessRoomleSdk not initialized. Call HeadlessRoomleSdk.create() first.',\n );\n }\n }\n}\n","import type ModelExportState from '../../common-core/src/services/model-export-state';\n\nconst getModelExportState = (): ModelExportState | undefined =>\n (globalThis as any).__RML__DI__?.lookup('model-export-state');\n\nexport const setModelExportOngoing = (modelExportOngoing: boolean): void => {\n getModelExportState()?.setModelExportOngoing(modelExportOngoing);\n};\n\nexport const isModelExportOngoing = (): boolean =>\n getModelExportState()?.isModelExportOngoing() ?? false;\n"],"mappings":";;;;;;;;;;;;;;AAUA,IAAM,KAAW,GAAc,OAAO,KAAK,IAAI,EAE3C,IAAuC,EAAE,EAIvC,IAEF;CACF;EACE;EACA;EACA;EACD;CACD;EACE;EACA;EACA;EACD;CACD;EACE;EACA;EACA;;CAEH,EAEoB,KAArB,MAA6D;CAG3D,YAAY,GAAiB;AAC3B,OAAK,YAAY;;CAGnB,MAAa,MAAM,GAAa,GAAyB;AACvD,MAAI,EAAS,EAAQ,IACnB;AAKF,MAAI,OAAO,KAAQ,YAAY;AAE7B,GADA,KAAK,iBAAiB,EAAI,EAC1B,EAAS,EAAQ,MAAM;AACvB;;EAGF,IAAM,IAAQ,KAAK,cAAc,EAAI;AACrC,MAAI,GAAO;GACT,IAAM,GAAG,GAAY,KAAc,GAK7B,IAAM,GADO,EAAI,WAAW,IAAI,GAAG,IAAM,EACf;AAEhC,GADC,WAAmB,KAAc,EAAI,WAAW,GACjD,EAAS,EAAQ,MAAM;;;CAO3B,iBAAyB,GAAqB;EAC5C,IAAM,IAAS,EAAQ,UAAU;AACjC,OAAK,IAAM,CAAC,GAAU,MAAe,EACnC,KAAI,EAAO,SAAS,EAAS,EAAE;AAC5B,cAAmB,KAAc;AAClC;;AAGJ,UAAQ,KACN,uEACD;;CAGH,cAAsB,GAAa;AACjC,OAAK,IAAM,KAAS,GAAgB;GAClC,IAAM,CAAC,GAAU,KAAc,GACzB,IAAW;AACjB,OAAI,EAAI,SAAS,EAAS,IAAI,EAAI,SAAS,EAAS,CAClD,QAAO;;AAgBX,SAZI,EAAI,SAAS,eAAe,GACvB,EAAe,KAEpB,EAAI,SAAS,UAAU,GAClB,EAAe,KAEpB,EAAI,SAAS,QAAQ,GAChB,EAAe,MAExB,QAAQ,KACN,2DAA2D,IAC5D,EACM;;CAGT,YAAmB,GAA8C;EAC/D,IAAM,IAAW,EAAQ,KAAK,MAC5B,KAAK,MAAM,EAAO,MAAM,EAAE,IAAI,EAAO,IAAI,CAAC,CAC3C;AACD,SAAO,QAAQ,IAAI,EAAS;;CAG9B,mBAA0B,GAGxB;AACA,SAAO,EAAoB,EAAK;;CAGlC,UAAiB;AACf,MAAW,EAAE;;GEhHI,KAArB,MAA8E;CAI5E,IAAY,oBAAkD;AAC5D,SAAQ,WAAmB,aAAa,OACtC,sBACA,KAAK,UACN;;CAOH,IAAI,UAAe;AACjB,SAAO,KAAK;;CAGd,YAAY,GAAiB,IAAQ,MAAM,IAAS,KAAK;AAGvD,EAFA,KAAK,YAAY,GACjB,KAAK,SAAS,GACd,KAAK,UAAU;EAEf,IAAM,IAAI,KAAK,QACT,IAAI,KAAK;AACf,OAAK,eAAe;GAClB,aAAa;GACb,cAAc;GACd,cAAc;GACd,cAAc;GACd,OAAO,EAAE;GACT,WAAW;IACT,MAAM;IACN,SAAS;IACT,WAAW;AACT,YAAO;;IAEV;GACD,eAAe;GACf,eAA8B;AAC5B,WAAO;;GAET,UAAU,EAAE;GACZ,wBAAwB;AACtB,WAAO;KACL,GAAG;KACH,GAAG;KACH,OAAO;KACP,QAAQ;KACR,KAAK;KACL,MAAM;KACN,QAAQ;KACR,OAAO;KACR;;GAEH,mBAAmB;GACnB,sBAAsB;GACtB,gBAAgB;AACd,WAAO;;GAET,aAAa;GACb,cAAc;GACd,aAAa;GACb,cAAc;GACd,WAAW;AACT,WAAO;;GAEV;;CAGH,cAAqB,GAAe;CAIpC,sBAA6B;AAC3B,SAAO,IAAI,EAAQ,KAAK,QAAQ,KAAK,QAAQ;;CAG/C,iBAAwB;AACtB,SAAO,KAAK,SAAS,KAAK;;CAG5B,oBAA2B;CAC3B,QAAe;CACf,QAAe;CACf,SAAgB;CAChB,UAAiB;GC1FE,KAArB,MAA2D;CAGzD,YAAY,GAAiB;AAC3B,OAAK,YAAY;;CAGnB,MAAa,GAAoB;AAC/B,SAAO,QAAQ,SAAS;;CAG1B,YAAmB,GAAmC;AACpD,SAAO,QAAQ,SAAS;;CAG1B,aAAoB,GAAgB;AAClC,SAAO,QAAQ,SAAS;;CAG1B,kBAAyB,GAAgB;AACvC,SAAO,QAAQ,SAAS;;CAG1B,mBAA0B,GAA8B;AACtD,SAAO,QAAQ,SAAS;;CAG1B,mBAA0B,GAAoB;AAC5C,SAAO;;CAGT,UAAiB,GAAkB,GAAyB,GAAY;CAIxE,aAAoB,GAAa,IAA0B,IAAe;AACxE,SAAO;;CAGT,kBAAyB,GAAgC;CAIzD,kBAAyB,GAAc,GAAc;GC9ClC,KAArB,cAAyD,EAAoB;CAC3E,yBAAmC,GAAiC;AAClE,SAAO,MAAe,GAAY,UAAU,mBAAmB;;GCA9C,KAArB,MAEA;CACE,KAAuB,GAAc;AACnC,SAAO,IAAI,MAAM,GAAQ,EACvB,IAAI,GAAQ,GAAM;GAChB,IAAM,IAAM,EAAE;AAad,UAZI,OAAO,KAAQ,aACV,SAAU,GAAG,GAAa;AAC/B,QAAI;AACF,YAAO,EAAI,MAAM,GAAG,EAAK;aAClB,GAAG;AACV,aAAQ,MACN,+BAA+B,OAAO,EAAK,CAAC,WAC5C,EACD;;OAIA;KAEV,CAAC;;GC1BO,KAAb,cAAyD,GAAsC;CAC7F,MAAyB,mBAAmB,GAA8B;GC0BxE,IAAsD;AAE1D,SAAgB,GACd,GACM;AACN,KAAmB;;AAQrB,IAAI,IAA8C,MAI5C,KAAuB,MAAM,OAAO,MACpC,KAAmC,GACnC,IAAkC,KAClC,IAA2B;AAIjC,SAAS,EAAgB,GAA6B;AACpD,QAAO,KAAK,IAAI,GAAG,KAAK,MAAO,KAAuB,MAAQ,EAAY,CAAC;;AAG7E,IAAa,MACX,MACS;AACT,KAAe;GAqCb,IAA4C,MAC5C,IAAqD;CACvD,eAAe;CACf,SAAS;CACV,EAIY,MACX,GACA,MACS;AAET,CADA,IAAc,GACd,IAAiB;EACf,eAAe,GAAS,iBAAiB;EACzC,SAAS,GAAS,WAAW;EAC9B;GAKU,IAAb,MAAsC;;qBACW,uBACJ;;CAI3C,MAAa,OACX,GACA,GACA,GACA,IAAkB,IAClB,IAA+B,EAAE,EAClB;EACf,IAAM,IAAa,MAAM,KAAK,eAAe,EAAS;AAKtD,EAHI,EAAS,YAAY,KAAA,MACvB,EAAW,UAAU,EAAS,UAEhC,EAAW,cACT,EAAS,eAAe;EAE1B,IAAM,IAAoB,KAAK,mBAAmB,EAAM,EAClD,IAAgB;GACpB,WAAW,EAAS;GACpB,aAAa,EAAS;GACtB,qBAAqB,EAAS;GAC9B,kBAAkB,EAAS;GAC5B;AACD,MAAI;AAEF,GADA,EAAW,SAAS,GAAO,EAAO,EAClC,EAAW,OAAO;GAElB,IAAM,IAAe,KAAK,IAAI,GAAG,KAAK,MAAM,EAAQ,CAAC,EACjD,IAAgB;AACpB,UAAO,EAAW,UAAU,GAa1B,CAZA,EAAW,cAAc,EACzB,IAAe,EAAW,SAAS,EAAa,EAG9C,KACA,EAAW,WAAW,KACtB,YAAY,KAAK,GAAG,KAAiB,EAAe,kBAEpD,MAAM,KAAK,aAAa,GAAU,EAAW,SAAS,EAAa,EACnE,IAAgB,YAAY,KAAK,GAGnC,MAAM,IAAI,SAAe,MAAY,WAAW,GAAS,EAAE,CAAC;AAK9D,GAFA,EAAW,cAAc,EAErB,EAAS,WACX,MAAM,KAAK,gBAAgB,GAAU,EAAW;YAE1C;AAKR,GAJA,KAAK,kBAAkB,EAAkB,EACzC,EAAS,YAAY,EAAc,WACnC,EAAS,cAAc,EAAc,aACrC,EAAS,sBAAsB,EAAc,qBAC7C,EAAS,mBAAmB,EAAc;;;CAM9C,mBAA2B,GAAqC;EAC9D,IAAM,IAAoC;GACxC,+BAAe,IAAI,KAAK;GACxB,cAAc,EAAE;GAChB,oCAAoB,IAAI,KAAK;GAC7B,gBAAgB,EAAA;GACjB;AAED,IAAM,UAAU,MACd,KAAK,6BAA6B,GAAQ,EAAW,CACtD;AAID,OAAK,IAAI,IAAI,EAAW,eAAe,SAAS,GAAG,KAAK,GAAG,KAAK;GAC9D,IAAM,EAAE,WAAQ,WAAQ,aAAU,EAAW,eAAe;AAE5D,GADA,EAAO,SAAS,OAAO,GAAO,EAAE,EAChC,EAAO,SAAS;;AAGlB,SAAO;;CAGT,6BACE,GACA,GACM;EACN,IAAM,IAAY,EAAyB,UACrC,IAAO;AAEb,MAAI,GAAU,qBAGR,CAAC,EAAS,WAAW,YAAY,EAAO,UAC1C,EAAW,eAAe,KAAK;GAC7B,QAAQ;GACR,QAAQ,EAAO;GACf,OAAO,EAAO,OAAO,SAAS,QAAQ,EAAA;GACvC,CAAC,EAIC,EAAW,mBAAmB,IAAI,EAAS,KAC9C,EAAW,mBAAmB,IAC5B,GACA,IAAI,IAAI,OAAO,KAAK,EAAS,WAAW,CAAC,CAC1C,EAED,KAAK,sBAAsB,GAAU,CAAC,EAAK,OAAO,IAIjD,EAAK,QAIV;OACE,EAAK,YACJ,EAAK,UAAU,sBAAsB,EAAK,MAAM,MAAM,eAAe,GACtE;AAEA,IADA,EAAK,UAAU,IACf,EAAW,aAAa,KAAK,EAAK;AAClC;;AAGF,QAAK,2BAA2B,GAAM,EAAW,cAAc;;;CAGjE,2BACE,GACA,GACM;EACN,IAAM,IAAY,MAAM,QAAQ,EAAK,SAAS,GAC1C,EAAK,WACL,CAAC,EAAK,SAAS;AACnB,OAAK,IAAM,KAAY,EACrB,CAAI,GAAU,SAAA,KAAsB,CAAC,EAAc,IAAI,EAAS,KAC9D,EAAc,IAAI,GAAU,EAAS,KAAK,EAC1C,EAAS,OAAA;;CAKf,kBAA0B,GAAyC;AACjE,OAAK,IAAM,EAAE,WAAQ,WAAQ,cAAW,EAAW,eAEjD,CADA,EAAO,SAAS,OAAO,GAAO,GAAG,EAAO,EACxC,EAAO,SAAS;AAElB,OAAK,IAAM,CAAC,GAAU,MAAS,EAAW,cACxC,GAAS,OAAO;AAElB,OAAK,IAAM,KAAQ,EAAW,aAC5B,GAAK,UAAU;AAEjB,OAAK,IAAM,CACT,GACA,MACG,EAAW,mBACd,QAAO,KAAK,EAAS,WAAW,CAC7B,QAAQ,MAAS,CAAC,EAAmB,IAAI,EAAK,CAAC,CAC/C,SAAS,MAAS,EAAS,gBAAgB,EAAK,CAAC;;CAMxD,sBACE,GACA,GACM;EACN,IAAM,IAAW,EAAS,WAAW;AACjC,GAAC,KAAY,EAAS,WAAW,WAGhC,KAAK,0BAA0B,GAAU,EAAkB,IAGhE,EAAS,aACP,WACA,IAAI,EAAgB,IAAI,aAAa,EAAS,QAAQ,EAAE,EAAE,EAAE,CAC7D;;CAGH,0BACE,GACA,GACS;EACT,IAAM,IAAgB,EAAS,WAAW,SAAS,OAG7C,IAAa,EAAS,OAAO,OAC7B,IAAc,IAAa,EAAW,SAAS;AASrD,SANI,KAAqB,IAAc,KAAM,IACpC,KAEJ,IAIH,EAAS,OAAO,MACb,MAAU,EAAM,QAAQ,EAAM,QAAQ,EAAW,OACnD,IAAI,EAAW,MAAM,MAAU,KAAS,EAAc,GALhD;;CASX,UAAuB;AAGrB,EAFA,KAAK,aAAa,SAAS,EAC3B,KAAK,cAAc,MACnB,KAAK,YAAY;;CAInB,MAAc,aACZ,GACA,GACA,GACe;AACf,MAAI;GACF,IAAM,IAAS,EAAS,YAClB,IAAgB,EAAO,aAAa,SAAS;AACnD,OAAI,CAAC,GAAe,eAClB;GAGF,IAAM,IAAQ,KAAK,IACjB,GACA,EAAe,UACb,KAAK,IAAI,GAAG,KAAK,IAAI,EAAO,OAAO,EAAO,OAAO,CAAC,CACrD,EACK,IAAe,KAAK,IAAI,GAAG,KAAK,MAAM,EAAO,QAAQ,EAAM,CAAC,EAC5D,IAAgB,KAAK,IAAI,GAAG,KAAK,MAAM,EAAO,SAAS,EAAM,CAAC,EAC9D,IAAc,MAAM,EAAc,eACtC,GACA,EACD;AACD,OAAI,CAAC,EACH;GAEF,IAAM,EAAE,iBAAc,WAAW,MAAkB,MAAM,OACvD,oBAEI,IAAgB,EAAa,EAAY,OAAO,EAAY,OAAO;AACzE,KACG,WAAW,KAAK,CAChB,aACC,IAAI,EACF,IAAI,kBACF,EAAY,KAAK,QACjB,EAAY,KAAK,YACjB,EAAY,KAAK,WAClB,EACD,EAAY,OACZ,EAAY,OACb,EACD,GACA,EACD;GACH,IAAM,IAAM,MAAM,EAAc,OAAO,MAAM;AAC7C,OAAc;IACZ,KAAK,IAAI,WAAW,EAAI,QAAQ,EAAI,YAAY,EAAI,WAAW;IAC/D,OAAO,EAAY;IACnB,QAAQ,EAAY;IACpB;IACA;IACD,CAAC;UACI;;CAOV,MAAc,oBACZ,GACA,GACA,GACA,GACuB;EACvB,IAAM,IAAiB,IAAQ,GAEzB,IAAkB,KAAK,KAAK,IAAQ,GAAG,GAAG,KAAK,GAE/C,IAAc,EADA,IAAkB,EACU,EAE1C,IAAY,IAAI,aAAa,IAAiB,EAAO;AAC3D,OACE,IAAI,IAAe,GACnB,IAAe,GACf,KAAgB,GAChB;GACA,IAAM,IAAa,KAAK,IAAI,GAAa,IAAS,EAAa,EACzD,IAAc,MAAM,EAAS,4BACjC,EAAE,UAAU,CAAC,EAAa,EAAE,EAC5B,GACA,GACA,GACA,EACD;AACD,OAAI,EAAW,WAAW,IAAiB,EACzC,GAAU,IAAI,GAAY,IAAe,EAAe;OAGxD,MAAK,IAAI,IAAY,GAAG,IAAY,GAAY,IAC9C,GAAU,IACR,EAAW,SACT,IAAY,GACZ,IAAY,IAAkB,EAC/B,GACA,IAAe,KAAa,EAC9B;;AAIP,SAAO;;CAKT,MAAc,gBACZ,GACA,GACe;AACf,MAAI,CAAC,EACH,OAAU,MACR,iKAED;EAGH,IAAM,IAAe,EAAW,YAAY,cACtC,EAAE,UAAO,cAAW,GAEpB,IAAY,MAAM,KAAK,oBAC3B,GACA,GACA,GACA,EACD,EAEK,IAAiB,MAAM,EAAiB,GAAW,GAAO,EAAO;AAIvE,OAAK,yBACH,GACA,GACA,GACA,GACA,EACD;EAID,IAAM,IAAW,EAAW,WACtB,IAAsB,EAAS,aAC/B,IAAmB,EAAS,qBAC5B,IAAoB,EAAS,WAC7B,IAAiB,EAAS,iBAAiB;AAgBjD,EAfA,EAAS,cAAc,GACvB,EAAS,sBAAsB,GAC/B,EAAS,YAAY,IACrB,EAAS,gBAAgB,KAAK,EAE9B,EAAS,SAAS,aAAa,GAC/B,EAAS,SAAS,UAAU,GAC5B,EAAS,SAAS,UAAU,GAC5B,EAAS,SAAS,cAAc,GAChC,EAAS,SAAS,sBAAsB,GACxC,EAAS,OAAO,EAAS,EAEzB,EAAS,YAAY,GACrB,EAAS,cAAc,GACvB,EAAS,sBAAsB,GAC/B,EAAS,gBAAgB,EAAe;;CAK1C,yBACE,GACA,GACA,GACA,GACA,GACM;EACN,IAAM,IAAU,EAAS,SACnB,IAAa,GAAS,MAAM,EAAa,EAAE,SAC3C,IAAQ,GAAS,QAAQ;AAC/B,MAAI,CAAC,KAAc,CAAC,EAClB,OAAU,MACR,iGACD;EAGH,IAAM,IAAc,IAAQ,IACtB,IAAe,IAAQ,GACvB,IAAc,EAAgB,EAAY;AAChD,OACE,IAAI,IAAe,GACnB,IAAe,GACf,KAAgB,GAChB;GACA,IAAM,IAAa,KAAK,IAAI,GAAa,IAAS,EAAa,EACzD,IAAa,EAAe,SAChC,IAAe,IACd,IAAe,KAAc,EAC/B;AACD,KAAM,aACJ;IAAE,SAAS;IAAY,QAAQ;KAAE,GAAG;KAAG,GAAG;KAAc,GAAG;;IAAK,EAChE,GACA;IAAE,QAAQ;IAAG;IAAa,cAAc;IAAY,EACpD;IAAE;IAAO,QAAQ;IAAY,oBAAoB;IAAG,CACrD;;;CAIL,MAAc,eACZ,GAC2B;AAC3B,MAAI,KAAK,eAAe,KAAK,cAAc,EACzC,QAAO,KAAK;AAEd,OAAK,aAAa,SAAS;EAE3B,IAAM,EAAE,kBAAkB,MAA0B,MAAM,OACxD,0BAEI,IAAa,IAAI,EAAsB,EAAS;AAatD,SATA,EAAW,cAAc,GAAK,EAE9B,EAAW,gBAAgB,IAC3B,EAAW,cAAc,GACzB,EAAW,eAAe,GAC1B,EAAW,aAAa,GAExB,KAAK,cAAc,GACnB,KAAK,YAAY,GACV;;GCvhBE,KAAb,MAAa,UAA8B,GAAc;;kDACgB;;CAEvE,MAAyB,8BAEvB;AACA,MAAI,CAAC,KAAK,UAAU,aAAa,OAC/B,QAAO,MAAM,6BAA6B;EAE5C,IAAM,EAAE,sBAAmB,MAAM,OAAO,gCAGlC,IAAgB,IAAI,EAAe;GAAE,WAAW;GAAO,OAAO;GAAM,CAAC;AAE3E,SADA,MAAM,EAAc,MAAM,EACnB;;CAGT,MAAyB,sBACvB,GACgC;AAChC,MAAI,CAAE,EAAiC,iBACrC,QAAO,MAAM,sBAAsB,EAAc;EAEnD,IAAM,EAAE,4BAAyB,MAAM,OACrC;AAEF,SAAO,IAAI,EACT,KAAK,WACL,GACA,IACA,KAAA,GACA,GACD;;CAGH,OAAc,2BACZ,GACS;AACT,SACE,EAAQ,mBAAmB,KAAA,KAC3B,EAAQ,gBAAgB,KAAA,KACxB,EAAQ,cAAc,KAAA;;CAG1B,OAAc,sBACZ,GACA,GACA;AAqBA,EApBA,EAAO,OAAO,KACd,EAAO,MAAM,KACT,EAAQ,kBACV,EAAO,SAAS,IACd,EAAQ,eAAe,GACvB,EAAQ,eAAe,GACvB,EAAQ,eAAe,EACxB,GAEC,EAAQ,gBAAgB,KAAA,KAAa,EAAQ,cAAc,KAAA,MAE7D,EAAO,WAAW,aAChB,IAAI,EACF,EAAE,EAAQ,eAAe,IACzB,EAAQ,aAAa,GACrB,GACA,MACD,CACF,EAEH,EAAO,wBAAwB;;CAGjC,eAAkC,GAGhC;EACA,IAAM,EAAE,UAAO,cAAW,MAAM,eAAe,EAAQ;AACvD,SAAO;GAAE,OAAO,EAAQ,SAAS;GAAO,QAAQ,EAAQ,UAAU;GAAQ;;CAG5E,aACE,GACA,GACA,GACA,GACM;AAUN,EATI,EAAsB,2BAA2B,EAAQ,GAC3D,EAAsB,sBAAsB,GAAQ,EAAQ,IAE5D,MAAM,aAAa,GAAQ,GAAc,GAAQ,EAAQ,EACrD,MACF,EAAO,OAAO,KAAK,IAAI,KAAM,EAAa,WAAW,GAAI,IAIzD,EAAQ,QAAQ,KAAA,MAClB,EAAO,MAAM,EAAQ;EAEvB,IAAM,EAAE,UAAO,cAAW,KAAK,eAAe,EAAQ;AAEtD,EADA,EAAO,SAAS,IAAQ,GACxB,EAAO,wBAAwB;;CAGjC,MAAyB,aACvB,GACA,GACA,GACA,GACqB;AAarB,SAZI,EAAQ,aAUH,EAAE,cATY,MAAM,KAAK,uBAC9B,GACA,GACA,GACA;GACE,SAAS,EAAQ;GACjB,UAAU,EAAQ;GACnB,CACF,EACsB,GAElB,MAAM,aAAa,GAAgB,GAAO,GAAQ,EAAQ;;CAGnE,MAAc,uBACZ,GACA,GACA,GACA,GACiB;EACjB,IAAM,IACJ,aAA0B,IACtB,EAAe,WACf;AACN,MAAI,CAAE,EAA4B,iBAChC,OAAU,MACR,gGACD;AAKH,EAHA,AACE,KAAK,gCAA8B,IAAI,GAA0B,EAEnE,MAAM,KAAK,4BAA4B,OACrC,GACA,GACA,GACA,EAAQ,SACR,EAAQ,SACT;EACD,IAAM,EAAE,oBAAiB,MAAM,KAAK,mBAClC,EACD;AACD,SAAO;;CAGT,MAAyB,mBACvB,GACA,GACqB;EACrB,IAAM,IAAgB,KAAK,sBAAsB,EAAS;AAC1D,MAAI,CAAC,EACH,QAAO,MAAM,mBAAmB,GAAU,EAAQ;EAIpD,IAAM,IACJ,CAAC,CAAC,GAAS,mBAAmB,CAAC,GAAS,gBAEpC,IAAe,IACjB,MAAM,KAAK,qBACT,GACA,GACA,GAAS,eACV,GACD;AACJ,MAAI,EAEF,QADA,EAAS,OAAO,EACT;GAAE,cAAc;GAAI,QAAQ;GAAc;EAGnD,IAAM,IAAS,MAAM,EAAc,eAAe;AAClD,MAAI,KAAmB,GAAQ;GAC7B,IAAM,EAAE,UAAO,cAAW,EAAS;AAEnC,UADA,EAAS,OAAO,EACT;IAAE,cAAc;IAAI,QAAQ;KAAE,MAAM;KAAQ;KAAO;;IAAU;;AAEtE,SAAO,MAAM,mBAAmB,GAAU,EAAQ;;CAGpD,sBACE,GAC0B;AAI1B,SAHM,EAA4B,mBAI9B,EAAS,YAAkC,aAC3C,SACD,IAAqC,OAL/B;;CASX,MAAc,qBACZ,GACA,GACA,GACgC;AAChC,MAAI,CAAC,KAAkB,KAAkB,KAAK,KAAkB,EAC9D,QAAO;EAET,IAAM,EAAE,UAAO,cAAW,EAAS;AACnC,SAAO,EAAc,eACnB,KAAK,IAAI,GAAG,KAAK,MAAM,IAAQ,EAAe,CAAC,EAC/C,KAAK,IAAI,GAAG,KAAK,MAAM,IAAS,EAAe,CAAC,CACjD;;GCrOC,KAA+B;CACnC,IAAI,GAAO;AAKT,MAAI;AACF,KAAM,OAAO;UACP;;CAIV,SAAS;CACT,YAAY;CACZ,SAAS;AACP,SAAO,EAAE;;CAEX,SAAS;CACV,EAEY,WAAuC;CAClD,IAAM,IAAS;AAGf,GAAO,QAAQ;GChCJ,IAAb,cAAkD,EAAqB;;iDAChD,IAAI,KAA2C,8BACrC;;CAE/B,mCACE,GACM;AAEN,EADA,KAAK,WAAW,IAAI,EAAS,EAC7B,AAME,KAAK,0BAHL,MAAM,oCAAoC,MAAS;AACjD,QAAK,WAAW,SAAS,MAAO,EAAG,EAAK,CAAC;IACzC,EAC0B;;CAIhC,aAAoB,oBAAoB,GAA+B;EAIrE,IAAM,IAAU,OAHD,EAAI,aAAa,CAAC,SAAS,OAAO,GAC7C,IAAI,GAAW,GACf,IAAI,GAAW,EACU,UAAU,EAAI;AAE3C,SADA,EAAQ,UAAA,KACD;;GCmBL,IAAqB,MACrB,KAA4B,oBAC5B,KAAiC,yBAuD1B,KAAb,MAAa,EAAkB;;sBAON,8BACM,yBACL,2BACE,kCACsB,2BACT,iCACM,oBAC5B,qBACC,yBAGoB,QAAQ,SAAS,yCAG7B,IAAI,SAG3B,gCA2hBoD;;CAphBvD,aAAoB,OAClB,GAC4B;EAC5B,IAAM,IAAW,IAAI,GAAmB;AAExC,SADA,MAAM,EAAS,MAAM,EAAS,EACvB;;CAGT,MAAc,MAAM,GAA4B;AAqC9C,EApCA,KAAK,SAAS,EAAS,SAAS,MAChC,KAAK,UAAU,EAAS,UAAU,KAClC,KAAK,gBAAgB,EAAS,cACzB,EAAS,cAAc,WAC1B,QAAQ,KACN,yEACD,EACD,KAAK,gBAAgB;GAAE,GAAG,EAAS;GAAc,QAAQ;GAAM,GAMjE,KAAK,gBAAgB;GACnB,wBAAwB;GACxB,mBAAmB;GACnB;GACA,GAAG,EAAS;GACb,EAED,IAA0B,EAG1B,KAAK,0BAA0B,KAAK,QAAQ,KAAK,QAAQ,EAEzD,MAAM,EAAU,kBAAkB;GAChC,gBAAgB,EAAS;GACzB,QAAQ,EAAS;GAEjB,QAAQ,EAAS;GACjB,iBAAiB,EAAS;GAC1B,cAAc,EAAS;GACvB,cAAc,EAAS;GACvB,eAAe;GAChB,CAAC,EAEF,KAAK,eAAe;;CAGtB,+BACE,GACyC;AACzC,MAAI,CAAC,KAAK,iBAAiB,CAAC,KAAK,cAC/B,QAAO;EAET,IAAM,IAAY,GACd;AACJ,SAAO;GACL,GAAG;GACH,cAAc;IAAE,GAAG,KAAK;IAAe,GAAG;IAAU;GACpD,cAAc;IAAE,GAAG,KAAK;IAAe,GAAG,GAAU;;GACrD;;CAGH,0BAAkC,GAAe,GAAgB;EAG/D,IAAM,IAAY,IAAI,GAAW;AAChC,aAAmB,cAAc;EAIlC,MAAM,UAA+B,GAAkB;GACrD,YAAY,GAAiB;AAC3B,UAAM,GAAS,GAAO,EAAO;;;EAMjC,IAAM,IAAoB;GACxB,IAAI,EACF,iBACA,IACA,EAAQ,SACR,GACD;GACD,IAAI,EACF,cACA,GACA,EAAQ,SACR,GACD;GACD,IAAI,EACF,eACA,IACA,EAAQ,QACR,GACD;GACD,IAAI,EACF,yBACA,IACA,EAAQ,QACR,GACD;GACD,IAAI,EACF,yBACA,IACA,EAAQ,QACR,GACD;GACD,IAAI,EACF,oCACA,IACA,EAAQ,SACR,GACD;GACD,IAAI,EACF,kBACA,IACA,EAAQ,SACR,GACD;GAID,IAAI,EACF,0BACA,GACA,EAAQ,QACR,GAAA;GAEH;AAKD,EADA,EAAU,kCAAkC,EAAY,EACxD,EAAU,kCAAkC,EAAkB;;CAGhE,mBAA2B;AACzB,SAAO;GACL,aAAa,KAAK;GAClB,cAAc,KAAK;GACnB,cAAc;GACd,cAAc;GACd,OAAO,EAAE;GACT,WAAW,EAAE,MAAM,IAAA;GACpB;;CAOH,MAAa,oBACX,GACe;AACf,OAAK,oBAAoB;EACzB,IAAM,IAAqB,MAAM,EAAU,gBACzC,KAAK,+BAA+B,EAAS,CAC9C;AAKD,EAJA,MAAM,EAAmB,MAAM,EAC/B,KAAK,oBAAoB,GACzB,KAAK,gBAAgB,EAAmB,QAAQ,EAChD,MAAM,KAAK,cAAc,KAAK,KAAK,kBAAkB,CAAQ,EAC7D,KAAK,qBAAqB;;CAO5B,MAAa,eACX,GACe;AACf,OAAK,oBAAoB;EACzB,IAAM,IAAgB,MAAM,EAAU,WAAW;GAC/C,KAAK;GACL,GAAG,KAAK,+BAA+B,EAAA;GACxC,CAAC;AACF,MAAI,GAAU,QAAQ,GACpB,OAAU,MAAM,+CAA+C;AAMjE,EAJA,MAAM,EAAc,MAAM,EAC1B,KAAK,eAAe,GACpB,KAAK,WAAW,EAAc,QAAQ,EACtC,MAAM,KAAK,SAAS,KAAK,KAAK,kBAAkB,CAAQ,EACxD,KAAK,gBAAgB;;CAOvB,MAAa,iBACX,GACe;AACf,OAAK,oBAAoB;EACzB,IAAM,IAAY,MAAM,EAAU,aAChC,KAAK,+BAA+B,EAAS,CAC9C;AAKD,EAJA,MAAM,EAAU,MAAM,EACtB,KAAK,iBAAiB,GACtB,KAAK,aAAa,EAAU,QAAQ,EACpC,MAAM,KAAK,WAAW,KAAK,KAAK,kBAAkB,CAAQ,EAC1D,KAAK,kBAAkB;;CAUzB,MAAa,kBACX,GACA,GACe;AACf,SAAO,KAAK,sBACV,SACM,KAAK,cAAc,sBAAsB,EAAgB,EAC/D,EACD;;CAOH,MAAa,wBACX,GACA,GACe;EACf,IAAM,IAAW,MAAM,KAAK,eAC1B,IACA,EACD;AACD,SAAO,KAAK,sBACV,SACM,KAAK,cAAc,kBAAkB,EAAc,EACzD,EACD;;CAQH,MAAa,yBACX,GACA,GACe;AACf,SAAO,KAAK,sBACV,SACM,KAAK,cAAc,yBAAyB,EAAO,EACzD,EACD;;CAGH,MAAc,sBACZ,GACA,GACA,GACe;AAEf,SADA,KAAK,oBAAoB,EAClB,KAAK,YAAY,YAAY;AAIlC,OAHK,KAAK,sBACR,MAAM,KAAK,oBAAoB,EAAS,EAEtC,KAAK,2BAA2B,EAClC;GAIF,IAAM,EAAE,QAAK,iBAAc,KAAK,gBAC5B,KAAK,yBAAyB,GAC9B,EAAE;AACN,GAAI,KAAO,KACT,EAAI,2BAA2B,EAAU;AAE3C,OAAI;AAMF,IALA,MAAM,KAAK,sBAAsB,EACjC,MAAM,GAAM,EACZ,KAAK,yBAAyB,GAC9B,KAAK,gBAAgB,MACrB,KAAK,sBAAsB,MAC3B,MAAM,KAAK,cAAc;aACjB;AACR,IAAI,KAAO,KACT,EAAI,wBAAwB,EAAU;;IAG1C;;CAQJ,MAAa,eACX,GACA,GACe;AAEf,SADA,KAAK,oBAAoB,EAClB,KAAK,YAAY,YAAY;AAClC,GAAK,KAAK,mBACR,MAAM,KAAK,iBAAiB,EAAS,EAEnC,KAAK,wBAAwB,MAGjC,MAAM,KAAK,sBAAsB,EACjC,MAAM,KAAK,WAAW,eAAe,EAAa,EAClD,KAAK,2BAA2B,EAChC,KAAK,sBAAsB,GAC3B,KAAK,yBAAyB,MAC9B,KAAK,gBAAgB,MACrB,MAAM,KAAK,cAAc;IACzB;;CAkBJ,4BAA0C;EAExC,IAAM,IADe,KAAK,WAAW,iBAAiB,CAC5B,UAAU;AAE/B,OAIL,EAAK,UAAU,MAAS;AACtB,GAAK,EAAc,WAChB,EAAc,aAAa;IAE9B;;CAGJ,MAAM,WACJ,GACA,GACe;AAEf,MADA,KAAK,oBAAoB,EACrB,EAAe,EAAG,CACpB,OAAU,MACR,sCAAsC,EAAG,8FAC1C;AAiBH,SAfI,EAAyB,EAAG,GACvB,KAAK,wBAAwB,GAAI,EAAS,GAE/C,EAAU,EAAG,GACR,KAAK,SAAS,GAAI,EAAS,GAEhC,EAAW,EAAG,IAEH,OADA,MAAM,EAAU,eAAe,EACpB,QAAQ,EAAa,GACrB,gBAIjB,KAAK,yBAAyB,GAAc,EAAS,GAFnD,KAAK,eAAe,GAAI,EAAS,GAIrC,KAAK,kBAAkB,GAAc,EAAS;;CAQvD,MAAM,SACJ,GACA,GACe;AAEf,SADA,KAAK,oBAAoB,EAClB,KAAK,iBACV,SACM,KAAK,SAAS,SAAS,EAAO,EACpC,EACD;;CAQH,MAAM,YACJ,GACA,GACe;AACf,OAAK,oBAAoB;EACzB,IAAM,IAAW,MAAM,KAAK,eAAe,IAA2B,EAAI;AAC1E,SAAO,KAAK,iBACV,SACM,KAAK,SAAS,YAAY,EAAI,EACpC,EACD;;CAGH,MAAc,iBACZ,GACA,GACA,GACe;AACf,SAAO,KAAK,YAAY,YAAY;AAKlC,OAJK,KAAK,iBACR,MAAM,KAAK,eAAe,EAAS,EAGjC,KAAK,kBAAkB,EACzB;GAIF,IAAM,EAAE,QAAK,sBAAmB,KAAK,qBACjC,KAAK,8BAA8B,GACnC,EAAE;AACN,GAAI,KAAO,KACT,EAAI,2BAA2B,EAAe;AAEhD,OAAI;AAOF,IANA,MAAM,KAAK,sBAAsB,EACjC,MAAM,GAAM,EACZ,KAAK,gBAAgB,GAErB,KAAK,yBAAyB,MAC9B,KAAK,sBAAsB,MAC3B,MAAM,KAAK,cAAc;aACjB;AACR,IAAI,KAAO,KACT,EAAI,wBAAwB,EAAe;;IAG/C;;CAGJ,MAAc,eACZ,GACA,GACiB;EACjB,IAAM,IAAS,MAAM,OAAO,OAAO,OACjC,WACA,IAAI,aAAa,CAAC,OAAO,EAAQ,CAClC;AAID,SAAO,IAHK,MAAM,KAAK,IAAI,WAAW,EAAO,GAAG,MAC9C,EAAK,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,CACnC,CAAC,KAAK,GAAG,CACU,MAAM,GAAG,GAAG;;CAUlC,YAAuB,GAAoC;EACzD,IAAM,IAAO,KAAK,aAAa,KAAK,EAAK;AAOzC,SAJA,KAAK,eAAe,EAAK,WACjB,UACA,GACP,EACM;;CAYT,MAAc,eAA8B;AACrC,EAIL,KAAK,4BADL,MAAM,KAAK,wBACmB;;CAWhC,MAAc,uBAAsC;EAClD,IAAM,IAAkB,MAAM,EAAU;AAGxC,MAAI,CAAC,GAAgB;AACnB,QAAK,yBAAyB;AAC9B;;EAEF,IAAM,IAAW,EAAe;AAChC,OAAK,yBAAyB,IAAI,SAAe,MAAY;AAC3D,KAAe,yBAAyB;AACtC,QAAI;AACF,UAAY;cACJ;AAER,KADA,EAAe,mBAAmB,GAClC,GAAS;;;IAGb;;CAKJ,0BAAkC;AAChC,MAAI;AAKF,UAAO;IAAE,KAJI,WAAmB,aAAa,SAC3C,wBACD;IAEa,WADI,KAAK,UAAU,iBAAiB,EAAE;IAC3B;UACnB;AACN,UAAO,EAAE;;;CAIb,+BAAuC;AACrC,MAAI;AAKF,UAAO;IAAE,KAJI,WAAmB,aAAa,SAC3C,wBACD;IAEa,gBADS,KAAK,eAAe,iBAAiB,EAAE;IAChC;UACxB;AACN,UAAO,EAAE;;;CASb,MAAM,UAAU,GAA4C;AAC1D,SAAO,KAAK,QAAQ,GAAY,MAAM;;CAQxC,MAAM,WAAW,GAA4C;AAC3D,SAAO,KAAK,QAAQ,GAAY,OAAO;;CAQzC,gBAGoB;AAClB,MAAI,KAAK,cACP,QAAO,KAAK;AAEd,MAAI,KAAK,uBACP,QAAO,KAAK;AAEd,MAAI,KAAK,oBACP,QAAO,KAAK;AAEd,QAAU,MACR,+IACD;;CAOH,yBAAiC;AAM/B,SALI,KAAK,sBAGA,KAAK,WAAW,iBAAiB,GAEnC,KAAK,eAAe,CAAC,iBAAiB;;CAG/C,MAAa,kBACX,GACA,IAAiC,EAAE,EACpB;AAGf,EAFA,KAAK,oBAAoB,EACzB,MAAM,KAAK,6BAA6B,CAAC,WAAW,EAAI,EACxD,KAAK,yBAAyB,EAAQ;;CAGxC,MAAa,oBACX,IAAiC,EAAE,EACpB;AACf,OAAK,oBAAoB;EACzB,IAAM,IAAe,KAAK,6BAA6B;AAIvD,EAHA,MAAM,EAAa,WACjB,EAAa,UAAU,aAAA,KAAA,IAAA,IAAA,mBAAA,OAAA,KAAA,IAAA,CAAA,KACxB,EACD,KAAK,yBAAyB,EAAQ;;CAGxC,yBAAgC,GAAsC;AAChE,IAAQ,cAAc,KAAA,KAAa,EAAQ,aAAa,KAAA,KAG5D,KAAK,6BAA6B,CAAC,oBACjC,KAAA,GACA,EAAQ,WACR,EAAQ,SACT;;CAGH,8BAAsC;EACpC,IAAM,IAA0B,KAAK,wBAAwB;AAC7D,MAAI,OAAO,EAAwB,cAAe,WAChD,OAAU,MACR,8EACD;AAEH,SAAO;;CAGT,MAAa,UAAU,GAA4B;AACjD,OAAK,oBAAoB;EACzB,IAAM,IAAQ,KAAK,wBAAwB,CAAC,UAAU;AAWtD,EAVK,KAAK,gBAAgB,IAAI,EAAM,IAClC,KAAK,gBAAgB,IAAI,GAAO;GAC9B,YAAY,EAAM;GAClB,2BAA2B,EAAM,SAAS;GAC3C,CAAC,EAEJ,EAAM,aACJ,MAAM,EAA6B,oBAAoB,EAAI,EAG7D,EAAM,SAAS,4BAA4B;;CAG7C,cAA2B;AACzB,OAAK,oBAAoB;EACzB,IAAM,IAAQ,KAAK,wBAAwB,CAAC,UAAU,EAChD,IAAW,KAAK,gBAAgB,IAAI,EAAM;AAC3C,QAGL,EAAM,aAAa,EAAS,YAC5B,EAAM,SAAS,4BACb,EAAS,2BACX,KAAK,gBAAgB,OAAO,EAAM;;CAGpC,MAAc,QACZ,GACA,GACuB;AACvB,OAAK,oBAAoB;EAEzB,IAAM,IAAqB,KAAK,wBAAwB,EAClD,IACJ,MAAS,QACL,MAAM,EAAmB,iBAAiB,GAC1C,MAAM,EAAmB,kBAAkB;AAEjD,MAAI,CAAC,EACH,OAAU,MAAM,GAAG,EAAK,aAAa,CAAC,0BAA0B;EAKlE,IAAM,IACJ,aAAkB,cACd,IACE,EAAsB;AAO9B,SALI,MACS,MAAM,OAAO,OACrB,cAAc,GAAY,OAAO,KAAK,EAAY,CAAC,EAGjD,EAAE,QAAQ,GAAa;;CAUhC,MAAa,iBACX,GACA,IAAmC,EAAE,EACP;AAC9B,OAAK,oBAAoB;EAEzB,IAAM,EAAE,SAAM,gBAAa,iBAAc,gBAAa,oBACpD,KAAK,qBAAqB,EAAQ,EAE9B,IAAmD;GACvD,WAAW,EAAQ;GACnB,gBAAgB,EAAQ;GACxB,aAAa,EAAQ;GACrB,WAAW,EAAQ;GACnB,KAAK,EAAQ;GACb,qBAAqB,EAAQ;GAC7B,OAAO;GACP,QAAQ;GACR,yBAAyB;GACzB,YAAY,EAAQ;GACpB,mBAAmB,EAAQ;GAC3B,oBAAoB,EAAQ;GAC5B,iBAAiB;GACjB,gBAAgB,IAAI;GACrB,EACK,IACJ,MAAM,KAAK,eAAe,CAAC,wBAAwB,EAAoB,EAEnE,IAAU,MAAM,KAAK,qBACzB,GACA,GACA,GACA,EACD;AAED,SADA,MAAM,KAAK,oBAAoB,GAAS,EAAW,EAC5C,EAAE,YAAS;;CAUpB,MAAa,uBACX,GACA,IAAyC,EAAE,EACkB;AAC7D,OAAK,oBAAoB;EAEzB,IAAM,EAAE,SAAM,gBAAa,iBAAc,gBAAa,oBACpD,KAAK,qBAAqB,EAAQ,EAE9B,IAAmD;GACvD,GAAG;GACH,OAAO;GACP,QAAQ;GACR,iBAAiB;GACjB,gBAAgB,IAAI;GACrB,EACK,IACJ,MAAM,KAAK,eAAe,CAAC,wBAAwB,EAAoB,EAEnE,IAAY,CAAC,CAAC,EAAO,UAAU,IAAO,GACtC,IAAU,MAAM,KAAK,qBACzB,GACA,GACA,GACA,EACD;AAGD,SAFA,MAAM,KAAK,oBAAoB,GAAS,EAAW,EAE5C;GACL;GACA,OAAO,IAAY,IAAc,EAAO;GACxC,QAAQ,IAAY,IAAe,EAAO;GAC3C;;CASH,MAAa,eACX,GACA,IAAiC,EAAE,EAC0B;AAG7D,MAFA,KAAK,oBAAoB,EAErB,KAAK,oBACP,OAAU,MACR,sEACD;EAEH,IAAM,IAAM,KAAK,eAAe,EAE1B,IAAO,EAAQ,QAAQ,GACvB,IAAmD;GACvD,GAAG;GACH,OAAO,EAAQ,QAAQ,KAAsB;GAC7C,gBAAgB,EAAQ,kBAAkB;GAC1C,iBAAiB;GAEjB,gBAAgB,IAAI;GACrB,EACK,IAAS,MAAM,EAAI,gBAAgB,EAAoB,EAEvD,IAAW,MAAM,KAAK,iBAAiB,GAAQ,EAAK;AAG1D,SAFA,MAAM,KAAK,oBAAoB,EAAS,SAAS,EAAW,EAErD;;CAGT,MAAc,iBACZ,GACA,GAC6D;AAC7D,MAAI,EAAO,QAAQ;GAGjB,IAAM,IAAkB,EAAO,OAAO,QAAQ,EAAO,OAC/C,IAAQ,IACV,EAAO,OAAO,QACd,KAAK,MAAM,EAAO,OAAO,QAAQ,EAAK,EACpC,IAAS,IACX,EAAO,OAAO,SACd,KAAK,MAAM,EAAO,OAAO,SAAS,EAAK;AAC3C,UAAO;IACL,SAAS,MAAM,KAAK,uBAClB,EAAO,QACP,GACA,EACD;IACD;IACA;IACD;;AAEH,MAAI,IAAO,GAAG;GACZ,IAAM,IAAQ,KAAK,MAAM,EAAO,QAAQ,EAAK,EACvC,IAAS,KAAK,MAAM,EAAO,SAAS,EAAK;AAC/C,UAAO;IACL,SAAS,MAAM,KAAK,eAAe,EAAO,OAAO,GAAO,EAAO;IAC/D;IACA;IACD;;AAEH,SAAO;GACL,SAAS,EAAO;GAChB,OAAO,EAAO;GACd,QAAQ,EAAO;GAChB;;CAGH,qBAA6B,GAM3B;EACA,IAAM,IAAO,EAAQ,SAAS,EAAQ,aAAa,IAAI,IACjD,IAAc,EAAQ,SAAS,EAAQ,QAAQ,GAC/C,IAAe,EAAQ,UAAU,EAAQ,QAAQ;AACvD,SAAO;GACL;GACA;GACA;GACA,aAAa,IAAc;GAC3B,cAAc,IAAe;GAC9B;;CAGH,MAAc,qBACZ,GACA,GACA,GACA,GACiB;AAWjB,SAVI,EAAO,SACF,KAAK,uBACV,EAAO,QACP,GACA,EACD,GAEC,IAAO,IACF,KAAK,eAAe,EAAO,OAAO,GAAa,EAAa,GAE9D,EAAO;;CAGhB,MAAc,oBACZ,GACA,GACe;AACf,MAAI,CAAC,EACH;EAEF,IAAM,IAAK,MAAM,OAAO,OAClB,IAAS,EAAQ,QAAQ,4BAA4B,GAAG;AAC9D,IAAG,cAAc,GAAY,OAAO,KAAK,GAAQ,SAAS,CAAC;;CAG7D,MAAc,uBACZ,GACA,GACA,GACiB;EACjB,IAAM,EAAE,iBAAc,WAAW,MAAkB,MAAM,OACvD,oBAEI,IAAe,EAAa,EAAO,OAAO,EAAO,OAAO;AAgB9D,MAfA,EACG,WAAW,KAAK,CAChB,aACC,IAAI,EACF,IAAI,kBACF,EAAO,KAAK,QACZ,EAAO,KAAK,YACZ,EAAO,KAAK,WACb,EACD,EAAO,OACP,EAAO,OACR,EACD,GACA,EACD,EACC,MAAgB,EAAO,SAAS,MAAiB,EAAO,OAC1D,QAAO,EAAa,eAAe,YAAY;EAEjD,IAAM,IAAe,EAAa,GAAa,EAAa,EACtD,IAAgB,EAAa,WAAW,KAAK;AAInD,SAHA,EAAc,wBAAwB,IACtC,EAAc,wBAAwB,QACtC,EAAc,UAAU,GAAc,GAAG,GAAG,GAAa,EAAa,EAC/D,EAAa,eAAe,YAAY;;CAIjD,MAAc,eACZ,GACA,GACA,GACiB;EACjB,IAAM,EAAE,iBAAc,iBAAc,MAAM,OAAO,oBAK3C,IAAQ,MAAM,EAJA,OAAO,KACzB,EAAQ,QAAQ,4BAA4B,GAAG,EAC/C,SACD,CACyC,EACpC,IAAe,EAAa,GAAa,EAAa,EACtD,IAAgB,EAAa,WAAW,KAAK;AAInD,SAHA,EAAc,wBAAwB,IACtC,EAAc,wBAAwB,QACtC,EAAc,UAAU,GAAO,GAAG,GAAG,GAAa,EAAa,EACxD,EAAa,eAAe,YAAY;;CAGjD,qBAAqB;AACnB,SAAO,KAAK;;CAGd,gBAAgB;AACd,SAAO,KAAK;;CAId,MAAa,UAAU;AAOrB,EANI,KAAK,qBACP,KAAK,kBAAkB,SAAS,EAE9B,KAAK,gBACP,KAAK,aAAa,SAAS,EAEzB,KAAK,kBACP,KAAK,eAAe,SAAS;;CAIjC,qBAA6B;AAC3B,MAAI,CAAC,KAAK,aACR,OAAU,MACR,4EACD;;GCvnCD,UACH,WAAmB,aAAa,OAAO,qBAAqB,EAElD,MAAyB,MAAsC;AAC1E,IAAqB,EAAE,sBAAsB,EAAmB;GAGrD,WACX,GAAqB,EAAE,sBAAsB,IAAI"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { f as e } from "./three.core-
|
|
1
|
+
import { f as e } from "./three.core-CmmDf_BZ.mjs";
|
|
2
2
|
//#region packages/common-core/src/utils/shims.ts
|
|
3
3
|
var t = /* @__PURE__ */ function(e) {
|
|
4
4
|
return e.RESIZE = "resize", e.KEY_DOWN = "keydown", e.KEY_UP = "keyup", e.VISIBILITY_CHANGE = "visibilitychange", e;
|
|
@@ -77,4 +77,4 @@ var t = /* @__PURE__ */ function(e) {
|
|
|
77
77
|
//#endregion
|
|
78
78
|
export { r as a, n as i, c as n, t as o, o as r, s as t };
|
|
79
79
|
|
|
80
|
-
//# sourceMappingURL=roomle-renderer-
|
|
80
|
+
//# sourceMappingURL=roomle-renderer-BR0UKz2y.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roomle-renderer-DhLUaHJZ.mjs","names":[],"sources":["../../packages/common-core/src/utils/shims.ts","../../packages/common-core/src/utils/fps-counter.ts","../../packages/common-core/src/roomle-renderer.ts"],"sourcesContent":["import type { ROOMLE_COLORS } from './common-utils';\nimport type {\n HexColorNumber,\n HexColorString,\n RapiId,\n RapiServerUrlType,\n} from '../../../typings/rapi-types';\nimport type { CAMERA_TYPE } from '../cameracontrol/camera-type';\nimport type { Enumify } from '#/common/src/utils/types';\nimport type { ROOMLE_HIGHLIGHTNG } from './common-utils';\nimport type { DefaultEnvironmentSceneType } from '../webgl/renderer/environment-scene';\nimport type {\n CustomShadingParameters,\n SceneShadingType,\n} from '../webgl/renderer/shading-settings';\nimport type { Color } from 'three';\nimport type { HomagIntelligenceInitData } from '../../../homag-intelligence/src/hi-api-types';\n\nexport const enum WINDOW_EVENT {\n RESIZE = 'resize',\n KEY_DOWN = 'keydown',\n KEY_UP = 'keyup',\n VISIBILITY_CHANGE = 'visibilitychange',\n}\n\nexport const DRAW_PREVIEW_MODES = {\n DEFAULT: 'default',\n VECTOR: 'vector',\n} as const;\n\nexport const WEBGPU_TONE_MAPPINGS = {\n NONE: 'none',\n LINEAR: 'linear',\n REINHARD: 'reinhard',\n CINEON: 'cineon',\n ACES_FILMIC: 'acesFilmic',\n AGX: 'agx',\n NEUTRAL: 'neutral',\n} as const;\n\nexport type WebGpuToneMapping = Enumify<typeof WEBGPU_TONE_MAPPINGS>;\n\n/**\n * Init data parameters which are not context specific\n */\nexport interface GlobalInitDataDefinition {\n /**\n * @description locale to use for the\n * embedding instance\n */\n locale?: string;\n\n /**\n * @internal\n * @deprecated\n * @description Can be used for content testing\n */\n overrideTenant?: number;\n\n /**\n * @description Set country to use for the Roomle price service. If not used the customer IP address will be used to determine the location. usage: overrideCountry=de\n */\n overrideCountry?: string | null;\n\n /**\n * @internal\n * @description Set which RAPI (Roomle API) backend should be used options: live, test\n */\n overrideRapi?: RapiServerUrlType;\n\n /**\n * @description The id which should be used for this embedding instance. When this id is set all InitData parameter can be set and changed in Rubens Admin. This is the way we recommend to set init data params. Ask your Roomle contact for your configuratorId.\n */\n configuratorId?: string;\n\n /**\n * @description Set which RAPI (Roomle API) backend should be used usage: customApiUrl=https://www.roomle.com/api/v2\n */\n customApiUrl?: string;\n\n /**\n * @internal\n * @description Can be set to true if started as an end-to-end test. This will disable post-processing\n */\n e2e?: boolean;\n\n /**\n * @internal\n * @description If set to true, the Roomle Renderer is used for perspective and top images this should help the E2E tests of Joe to migrate from comparing Roomle Renderer images to Three.js Renderer images\n */\n e2eRoomleRendererForImages?: boolean;\n\n /**\n * @internal\n * @description When loading a plan, the external objects from the plan XML are discarded\n */\n discardExternalObjectsWhenLoadingPlan?: boolean;\n\n /**\n * @description If set to true, the plan will be locked and no changes can be made to walls, floors or construction elements.\n * @default false\n */\n lockPlan?: boolean;\n\n /**\n * @description If set to true, the plan is in read-only mode. Hides all editing UI and prevents interaction with plan elements.\n * Automatically sets edit=false and lockPlan=true and saveToIdb=false\n * @default false\n */\n readonly?: boolean;\n}\n\nexport interface WebGpuTopLightConfigDefinition {\n /**\n * @description Intensity (brightness) of the WebGPU top light.\n * @default 1\n */\n intensity?: number;\n\n /**\n * @description Intensity of the shadow cast by the top light.\n * 0 means no shadow, 1 means a fully opaque shadow.\n * @default 1\n */\n shadowIntensity?: number;\n\n /**\n * @description Softness (blur radius) of the top light's shadow edges.\n * Higher values produce softer shadows.\n * @default 1.1\n */\n shadowRadius?: number;\n}\n\nexport interface WebGpuGroundReflectionConfigDefinition {\n /**\n * @description Enables or disables the ground reflection entirely.\n * When disabled the floor still receives shadows but does not render a reflection,\n * which also avoids the extra reflection render pass.\n * @default true\n */\n enabled?: boolean;\n\n /**\n * @description Strength of the ground reflection.\n * 0 means no reflection, 1 means a full mirror-like reflection.\n * @default 0.1\n */\n intensity?: number;\n\n /**\n * @description Depth at which the reflection starts to fade out.\n * @default 0.05\n */\n startFadeout?: number;\n\n /**\n * @description Depth at which the reflection is fully faded out.\n * @default 0.15\n */\n endFadeout?: number;\n}\n\nexport interface WebGpuFogConfigDefinition {\n /**\n * @description If true, a fog is added to the scene in webGPU mode.\n * This is mainly so you cant see the outer edge of the\n * floor (which makes it blends smoothly into the background).\n * This fog exists in a cylindrical shape around the origin and the\n * farBlend and nearBlend parameters indirectly control the radius\n * at which the fog starts and ends.\n * @default true\n */\n enabled?: boolean;\n\n /**\n * @description Radial distance from the start of the fog to the end of the fog.\n * This creates a smooth transition from full fog to no fog.\n * Larger values produce a softer, more gradual fog transition.\n * @default 7\n */\n nearBlend?: number;\n\n /**\n * @description Distance from the far end of the camera view/floor boundaries\n * at which the fully opaque fog should start to appear.\n * Larger values move the fully opaque(/end of the) fog\n * closer to the center of the scene.\n * @default 10\n */\n farBlend?: number;\n}\n\nexport interface WebGpuConfigDefinition {\n /**\n * @description If true, the initial webgpu quality level is autodetected based on\n * a benchmark of the graphics hardware. If false, the default quality level is used.\n * @default true\n */\n autoDetectQualityLevel?: boolean;\n\n /**\n * @description This can be used to configure the lower bound of webgpu auto quality\n * meaning the fps bound which, when hit, will set the quality setting lower\n * To disable: set to 0\n * @default 24\n */\n autoQualityMinFps?: number;\n\n /**\n * @description This can be used to configure the upper bound of webgpu auto quality\n * meaning the fps bound which, when hit, will set the quality setting higher\n * This should be sufficiently larger than the minFps to not cause the quality to go up and down all the time\n * To disable: set to something very high\n * @default 59\n */\n autoQualityMaxFps?: number;\n\n /**\n * @description Visual configuration for the WebGPU top light and its shadow.\n */\n topLight?: WebGpuTopLightConfigDefinition;\n\n /**\n * @description Visual configuration for the WebGPU ground reflection.\n */\n groundReflection?: WebGpuGroundReflectionConfigDefinition;\n\n /**\n * @description Visual configuration for the WebGPU fog.\n */\n fog?: WebGpuFogConfigDefinition;\n\n /**\n * @description Tone mapping operator applied by the WebGPU renderer.\n * @default 'none'\n */\n toneMapping?: WebGpuToneMapping;\n\n /**\n * @description Exposure (intensity) of the tone mapping applied by the\n * WebGPU renderer. Higher values produce a brighter image.\n * @default 1\n */\n toneMappingExposure?: number;\n}\n\nexport interface FeatureFlags {\n pulsePreview?: boolean;\n reDock?: boolean;\n\n /**\n * @description Sets whether the snapping behavior of objects in the planner is initially enabled.\n */\n enableSnapping?: boolean;\n\n /**\n * @internal\n * @description If true, the advanced docking behavior is enabled (for test purposes only)\n */\n advancedDocking?: boolean;\n\n /**\n * @internal\n * @description If true, when inserting new objects in the planner, we will try to merge them into existing groups if possible.\n * This is for test purposes only and should not be enabled in production yet, as it can lead to unexpected behavior.\n */\n mergeOnInsert?: boolean;\n\n /**\n * @internal\n * @description If “vector”, the vector preview is activated when drawing walls (for test purposes only).\n * By default, the walls are immediately drawn with their actual thickness,\n * but this leads to problems when displaying the dimensions of the walls,\n * as it is not possible to guess which side of the wall of the room currently being entered is the inside of the wall.\n */\n drawPreviewMode?: Enumify<typeof DRAW_PREVIEW_MODES>;\n /**\n * @internal\n * @description TO BE REMOVED ONCE RML-13218 IS RESOLVED.\n * Forces new thumbnails to be generated on configuration save\n */\n forceSaveThumbnails?: boolean;\n\n /**\n * @internal\n * @description Enables dynamically placed interior lights in the room planner.\n */\n interiorLights?: boolean;\n\n /**\n * @internal\n * @description If true, the advanced dimensioning is enabled (for test purposes only)\n */\n advancedDimensioning?: boolean;\n\n /**\n * @internal\n * @description If true, the scene will be rendered with WebGPU instead of WebGL\n */\n webGpu?: boolean;\n\n /**\n * @internal\n * @description If true, the advanced highlighting behavior is enabled (for test purposes only)\n */\n advancedHighlighting?: boolean;\n\n /**\n * @internal\n * @description If true, enables the POC for planning fences (e.g. garden fences) in the room planner.\n */\n fence?: boolean;\n\n /**\n * @internal\n * @description If true, `onRequestPlan` waits until every pending configuration\n * save in the plan has been persisted to the Roomle backend before firing.\n * See RML-17394\n */\n awaitConfigurationSavesBeforeRequestPlan?: boolean;\n}\n\nexport type RoomleColors = keyof typeof ROOMLE_COLORS;\n\nexport type ColorsIn3dScene = Record<\n RoomleColors,\n HexColorString | HexColorNumber\n>;\n\nexport type ThreeJsColorsIn3dScene = Record<keyof typeof ROOMLE_COLORS, Color>;\n\n/**\n * Init data parameter which can be used in configurator AND planner\n */\nexport interface CommonInitData {\n /**\n * @description If set to true the configurator will make sure every device has great user experience with fluid frame rate.\n * If set to false the configurator will always show the highest quality possible.\n * That could lead to stutter and other problems on low-end devices. We advise you to only turn off \"autoQuality\" if you really know what you are doing.\n * usage: autoQuality=false\n * @groups scene-settings\n */\n autoQuality?: boolean;\n\n /**\n * @description Can be used to personalize colors used in the 3D scene\n * @groups scene-settings\n */\n colors?: ColorsIn3dScene;\n\n /**\n * @description The item id or configuration id of the item which should be loaded.\n * Examples:\n * Configuration id: usm:frame:2449773A7F54278EBB61A67A10EF6A10E8BB6F38BE775B028AC050B438414507\n * Item id: usm:frame\n */\n id?: RapiId;\n\n /**\n * @internal\n * @deprecated\n * @description Use prices which are hardcoded in the configuration script.\n * Only used for trade show demos.\n */\n useRoomlePrice?: boolean;\n\n /**\n * @description Set how many positions after the decimal point should be shown.\n * For example:\n * 8' 1.68\" -> precisionCm=2 -> 8' 1.68\"\n * 8' 1.68\" -> precisionCm=1 -> 8' 1.7\"\n * 8' 1.68\" -> precisionCm=0 -> 8' 2\"\n */\n precisionInch?: number;\n\n /**\n * @description Set how many positions after the decimal point should be shown.\n * For example:\n * 123,45 -> precisionCm=2 -> 123,45\n * 123,45 -> precisionCm=1 -> 123,5\n * 123,45 -> precisionCm=0 -> 124\n */\n precisionCm?: number;\n\n /**\n * @description Defines which unit should be used for measurements/dimensions\n * Options: 'cm', 'mm', 'inchfeet'\n */\n unit?: string;\n\n /**\n * @description How the current unit should be formatted\n * Options:\n * - 'none': No unit string added, for example '200'\n * - 'long': Long unit string, for example '200 meters'\n * - 'short': Short unit string, for example '200 m'\n */\n unitStringType?: string;\n\n /**\n * @description When mesh selection is enabled, the meshes of the object are used directly for highlighting.\n * Setting it to false could be useful for square/rectangular (shelf) items.\n */\n meshSelection?: boolean;\n\n /**\n * @description if set to true it shows a little overlay with performance stats\n */\n stats?: boolean;\n\n /**\n * @description This flag enables:\n * - Scripter debug logs (e.g. which parameter is not assigned to a parameter group)\n * - Free camera movement (no restrictions, camera stays after reload, panning using Shift key)\n */\n debug?: boolean;\n\n /**\n * @description This flag enables:\n * - External object docking vector debug geometry\n */\n debugGeometry?: boolean;\n\n /**\n * @description If true this flag shows component bounding boxes\n */\n debugBoundingBox?: boolean;\n\n /**\n * @description This flag enables:\n * - Help line geometry in the planner\n * @internal\n */\n debugLines?: boolean;\n\n /**\n * @description If set to true, prices are calculated using the Roomle price service.\n * For more information ask your Roomle contact.\n */\n usePriceService?: boolean;\n\n /**\n * @description Url to an exr or hdr env map\n * @groups scene-settings\n */\n envMapUrl?: string;\n\n /**\n * @description Set the intensity of the environment map\n * @groups scene-settings\n */\n envMapIntensity?: number;\n\n /**\n * @description Set the rotation of the environment map in radians, between 0 and 2 * PI, rotates counter-clockwise\n * @groups scene-settings\n */\n envMapRotation?: number;\n\n /**\n * @description Set the maximum number of light sources extracted from the environment map\n * @groups scene-settings\n */\n maxLightSources?: number;\n\n /**\n * @description Use old light setting instead of env maps. Ignored when using the WebGPU renderer\n * @groups scene-settings\n */\n legacyLight?: boolean;\n\n /**\n * @description Generate environment map from a scene instead of env maps\n * use all_around for an surrounding light\n * use front for a mainly frontal light\n * @groups scene-settings\n */\n sceneLight?: DefaultEnvironmentSceneType;\n\n /**\n * @description General shading properties for the overall appearance\n * default: for planner and a configurator with colorful, metallic or wooden materials\n * bright: for a configurator with white and bright materials\n * @groups scene-settings\n */\n shading?: SceneShadingType;\n\n /**\n * @description Customized shading properties for the overall appearance\n * @groups scene-settings\n */\n customShading?: CustomShadingParameters;\n\n /**\n * @description Activates the linear AO filter mode to reduce AO anti-aliasing artifacts\n * @groups scene-settings\n */\n linearAoFilter?: boolean;\n\n /**\n * @description Customized shading properties for the overall appearance in MOC\n * @groups scene-settings\n */\n customShadingMoc?: CustomShadingParameters;\n\n /**\n * @description restrictionLevel defines visibility of parameters and possible children\n */\n restrictionLevel?: number;\n\n /**\n * @description Settings for Homag Intelligence\n * @internal\n */\n hi?: HomagIntelligenceInitData;\n\n /**\n * @description disable injection of default styles for measurements and component dimension labels\n * to enable easier customization of these styles (via the assigned classes)\n */\n disableDefaultLabelStyles?: boolean;\n}\n\nexport interface ConfiguratorInitData {\n /**\n * @description Sets the behavior of the highlighting of a hovered component.\n * usage: highlighting=smart\n */\n highlighting?: Enumify<typeof ROOMLE_HIGHLIGHTNG>;\n\n /**\n * @description Set the floor material which is used when loading the configurator\n * For different options see {@link @roomle/web-sdk/configurator-core/src/environment/dynamic-environment-setting-loader#EnvironmentSetting}\n */\n initialFloorMaterial?: string;\n\n /**\n * @internal\n * @description Load a light setting using an URL.\n * Check lights in {@link @roomle/web-sdk/configurator-core/src/scene-settings-loader#SceneSettings} for an example.\n * value is a dynamic light setting url\n */\n dls?: string;\n\n /**\n * @description Load a predefined light setting, eg: ls=sofa\n * @groups scene-settings\n */\n ls?: string;\n\n /**\n * @description Set the distance to which the user can zoom (in centimeters).\n * default: object size divided by 10 or 10 cm\n * usage: zoomDistance=3\n * @groups scene-settings\n */\n zoomDistance?: number;\n\n /**\n * @description When set this param restricts the camera rotation to the given angle (e.g. 45 degree), this overrides minHorizontalCameraAngle and maxHorizontalCameraAngle\n * default: not set -> no camera restriction\n * usage: cameraRestriction=45\n * @groups scene-settings\n */\n cameraRestriction?: number;\n\n /**\n * @description Sets the default/initial vertical camera angle (pitch)\n * default: 10\n * usage: verticalCameraAngle=45\n * @groups scene-settings\n */\n verticalCameraAngle?: number;\n\n /**\n * @description When set this param restricts the down/vertical camera rotation to the given angle (e.g. 45 degree)\n * default: not set -> no camera angle restriction\n * usage: minVerticalCameraAngle=45\n * @groups scene-settings\n */\n minVerticalCameraAngle?: number;\n\n /**\n * @description When set this param restricts the up/vertical camera rotation to the given angle (e.g. 45 degree)\n * default: not set -> no camera angle restriction\n * usage: maxVerticalCameraAngle=45\n * @groups scene-settings\n */\n maxVerticalCameraAngle?: number;\n\n /**\n * @description Sets the default/initial horizontal camera angle (yaw)\n * default: -30\n * usage: horizontalCameraAngle=45\n * @groups scene-settings\n */\n horizontalCameraAngle?: number;\n\n /**\n * @description When set this param restricts the left/horizontal camera rotation to the given angle (e.g. 45 degree)\n * default: not set -> no camera angle restriction\n * usage: minHorizontalCameraAngle=45\n * @groups scene-settings\n */\n minHorizontalCameraAngle?: number;\n\n /**\n * @description When set this param restricts the right/horizontal camera rotation to the given angle (e.g. 45 degree)\n * default: not set -> no camera angle restriction\n * usage: maxHorizontalCameraAngle=45\n * @groups scene-settings\n */\n maxHorizontalCameraAngle?: number;\n\n /**\n * @description If set to false the camera will just jump to a new position\n * default: true\n * usage: animateCamera=false\n * @groups scene-settings\n */\n animateCamera?: boolean;\n\n /**\n * @internal\n * @description Easter Egg\n * */\n bananaForScale?: boolean;\n\n /**\n * @description If set to true, components which are between the camera and the selected component/preview will be rendered transparent\n * usage: transparentHighlighting=true\n * @groups scene-settings\n */\n transparentHighlighting?: boolean;\n\n /**\n * @description If set to true, the background is transparent and allows elements behind the configurator to be visible through the scene.\n * usage: transparentBackground=true\n * @groups scene-settings\n */\n transparentBackground?: boolean;\n\n /**\n * @description If set to true it will try to apply all current global parameters to the loaded object\n */\n applyCurrentGlobalParameters?: boolean;\n\n /**\n * @description Adds the given spacing (in centimeters) to the loaded object. This is then used to calculate the initial camera distance\n * default: 0\n * usage:\n * cameraSpacing=100, zoom out further by increasing camera spacing by 100 centimeters\n * cameraSpacing=-30, zoom in further by decreasing camera spacing by 30 centimeters\n * @groups scene-settings\n */\n cameraSpacing?: number;\n\n /**\n * @internal\n * @description If set to true, screen space reflections are enabled\n */\n ssr?: boolean;\n\n /**\n * @internal\n * @description features that can be enabled/disabled\n */\n featureFlags?: FeatureFlags;\n\n /**\n * @internal\n * @description Configuration for the WebGPU renderer (e.g. auto quality fps bounds).\n */\n webGpuConfig?: WebGpuConfigDefinition;\n\n /**\n * @description if true, the meshes of the component will be used, in addition to the bounding box, for raycasting (selecting)\n * Warning: this will decrease performance when hovering over components with complex meshes\n * @default false\n * usage: meshRaycasting=true\n */\n meshRaycasting?: boolean;\n}\n\nexport interface PlannerInitData {\n /**\n * @description Sets the default floor material when creating a new plan or drawing.\n * Can be a number (RGB color) or a string (material ID).\n */\n defaultFloorMaterial?: number | string;\n\n /**\n * @description Select with which camera mode to start initially.\n * usage: mode=2D\n * options: 2D, 3D, FP\n */\n mode?: Enumify<typeof CAMERA_TYPE>;\n\n /**\n * @internal\n * @description If set to true the plan can be edited.\n */\n edit?: boolean;\n\n /**\n * @description If set to true it will treat the planner as a multi object configurator (moc).\n * This means:\n * - do not merge geometries\n * - set configurator environment & lights in planner\n * - reduce initial spacing of camera (higher zoom)\n */\n moc?: boolean;\n\n /**\n * @description If set to true we will not hide other items while configuring in the planner\n */\n configureInRoom?: boolean;\n\n /**\n * @description If set to true docking of configurable objects in the planner is enabled.\n * The default value is true.\n */\n dockInRoom?: boolean;\n}\n\nexport interface InitDataDefinition\n extends CommonInitData,\n ConfiguratorInitData,\n PlannerInitData {}\n","const FPS_LIST_SIZE = 10;\nconst FPS_MEASUREMENT_INTERVAL_MS = 20;\n\nexport default class FpsCounter {\n private _index: number = 0;\n private _fpsList: number[];\n private _prevTime: number = 0;\n private _frames: number = 0;\n private _fps: number = -1;\n\n constructor() {\n this._fpsList = Array(FPS_LIST_SIZE).fill(0);\n this._prevTime = (performance || Date).now();\n this.reset();\n }\n\n public update() {\n this._frames++;\n const time = (performance || Date).now();\n\n if (time >= this._prevTime + FPS_MEASUREMENT_INTERVAL_MS) {\n this._fpsList[this._index % FPS_LIST_SIZE] =\n (this._frames * 1000) / (time - this._prevTime);\n\n if (this._index >= FPS_LIST_SIZE) {\n this._fps =\n this._fpsList.reduce((pv, cv) => pv + cv) / this._fpsList.length;\n }\n\n this._prevTime = time;\n this._frames = 0;\n this._index++;\n }\n }\n\n public start() {\n this._prevTime = (performance || Date).now();\n this._frames = 0;\n }\n\n public reset() {\n this._fps = -1;\n this._fpsList = Array(FPS_LIST_SIZE).fill(0);\n this._index = 0;\n }\n\n public getFps() {\n return this._fps;\n }\n}\n","import type { Camera, Object3D, Scene, Vector2, WebGLRenderer } from 'three';\nimport { Box3 } from 'three';\nimport type { Enumify } from '#/common/src/utils/types';\nimport type { CAMERA_TYPE } from './cameracontrol/camera-type';\nimport type { WebGPURenderer } from 'three/webgpu';\nimport type { SceneShadingType } from './webgl/renderer/shading-settings';\nimport type { OutlineCustomParameters } from './webgl/renderer/scene-renderer';\n\nexport type GenericRoomleRenderer = RoomleRenderer<\n WebGLRenderer | WebGPURenderer\n>;\n\nexport enum PlannerRenderMode {\n Mode3D,\n Mode2D,\n}\n\nexport abstract class RoomleRenderer<\n RendererType extends WebGLRenderer | WebGPURenderer,\n> {\n public _creator_: string;\n public renderer: RendererType;\n protected isConfiguratorMode: boolean = true;\n protected isCameraMoving: boolean = false;\n protected cameraType: Enumify<typeof CAMERA_TYPE>;\n protected shadowNeedsUpdate: boolean = false;\n protected boundingBox: Box3 = new Box3();\n protected boundingBoxNeedsUpdate: boolean = false;\n\n protected constructor(creator: string, renderer: RendererType) {\n this._creator_ = creator;\n this.renderer = renderer;\n }\n\n public get isConfiguring(): boolean {\n return this.isConfiguratorMode;\n }\n\n public dispose(): void {\n this.renderer.dispose();\n }\n\n public setSize(width: number, height: number): void {\n this.renderer.setSize(width, height);\n }\n\n public getSize(target: Vector2): Vector2 {\n return this.renderer.getSize(target);\n }\n\n public setCameraType(cameraType: Enumify<typeof CAMERA_TYPE>) {\n this.cameraType = cameraType;\n }\n\n public movingCameraStarts(): void {\n this.isCameraMoving = true;\n }\n\n public movingCameraStops(): void {\n this.isCameraMoving = false;\n }\n\n public forceShadowUpdates(_updateBakedGroundShadow: boolean): void {\n this.shadowNeedsUpdate = true;\n }\n\n public clear() {\n this.renderer.clear();\n }\n\n public updateBounds(geometryBounds: Box3) {\n this.boundingBox.copy(geometryBounds);\n this.boundingBoxNeedsUpdate = true;\n }\n\n public switchToConfigurator(): void {\n this.isConfiguratorMode = true;\n }\n\n public switchToPlanner(_renderMode: PlannerRenderMode) {\n this.isConfiguratorMode = false;\n }\n\n public abstract setShadingType(shadingType: SceneShadingType): void;\n\n public abstract setCustomOutlineParameters(\n outlineCustomParameters: OutlineCustomParameters,\n ): void;\n\n public abstract render(scene: Scene, camera: Camera): void;\n\n public abstract showGUI(updateCallback: () => void): void;\n\n public abstract clearCache(): void;\n\n public abstract highlightObjects(selectedMeshes: Object3D[]): void;\n\n public abstract setGroundShadow(enabled: boolean, scene: Scene): void;\n\n public abstract enableUiInteractionMode(): void;\n\n public abstract disableUiInteractionMode(): void;\n}\n"],"mappings":";;AAkBA,IAAkB,IAAX,yBAAA,GAAA;QACL,EAAA,SAAA,UACA,EAAA,WAAA,WACA,EAAA,SAAA,SACA,EAAA,oBAAA;KACD,EAEY,IAAqB;CAChC,SAAS;CACT,QAAQ;CACT,EAEY,IAAuB;CAClC,MAAM;CACN,QAAQ;CACR,UAAU;CACV,QAAQ;CACR,aAAa;CACb,KAAK;CACL,SAAS;CACV,ECtCK,IAAgB,IAChB,IAA8B,IAEf,IAArB,MAAgC;CAO9B,cAAc;AAGZ,gBATuB,oBAEG,kBACF,eACH,IAGrB,KAAK,WAAW,MAAM,EAAc,CAAC,KAAK,EAAE,EAC5C,KAAK,aAAa,eAAe,MAAM,KAAK,EAC5C,KAAK,OAAO;;CAGd,SAAgB;AACd,OAAK;EACL,IAAM,KAAQ,eAAe,MAAM,KAAK;AAExC,EAAI,KAAQ,KAAK,YAAY,MAC3B,KAAK,SAAS,KAAK,SAAS,KACzB,KAAK,UAAU,OAAS,IAAO,KAAK,YAEnC,KAAK,UAAU,MACjB,KAAK,OACH,KAAK,SAAS,QAAQ,GAAI,MAAO,IAAK,EAAG,GAAG,KAAK,SAAS,SAG9D,KAAK,YAAY,GACjB,KAAK,UAAU,GACf,KAAK;;CAIT,QAAe;AAEb,EADA,KAAK,aAAa,eAAe,MAAM,KAAK,EAC5C,KAAK,UAAU;;CAGjB,QAAe;AAGb,EAFA,KAAK,OAAO,IACZ,KAAK,WAAW,MAAM,EAAc,CAAC,KAAK,EAAE,EAC5C,KAAK,SAAS;;CAGhB,SAAgB;AACd,SAAO,KAAK;;GCnCJ,IAAL,yBAAA,GAAA;QACL,EAAA,EAAA,SAAA,KAAA,UACA,EAAA,EAAA,SAAA,KAAA;KACD,EAEqB,IAAtB,MAEE;CAUA,YAAsB,GAAiB,GAAwB;AAE7D,4BATsC,0BACJ,6BAEG,uBACT,IAAI,GAAM,gCACI,IAG1C,KAAK,YAAY,GACjB,KAAK,WAAW;;CAGlB,IAAW,gBAAyB;AAClC,SAAO,KAAK;;CAGd,UAAuB;AACrB,OAAK,SAAS,SAAS;;CAGzB,QAAe,GAAe,GAAsB;AAClD,OAAK,SAAS,QAAQ,GAAO,EAAO;;CAGtC,QAAe,GAA0B;AACvC,SAAO,KAAK,SAAS,QAAQ,EAAO;;CAGtC,cAAqB,GAAyC;AAC5D,OAAK,aAAa;;CAGpB,qBAAkC;AAChC,OAAK,iBAAiB;;CAGxB,oBAAiC;AAC/B,OAAK,iBAAiB;;CAGxB,mBAA0B,GAAyC;AACjE,OAAK,oBAAoB;;CAG3B,QAAe;AACb,OAAK,SAAS,OAAO;;CAGvB,aAAoB,GAAsB;AAExC,EADA,KAAK,YAAY,KAAK,EAAe,EACrC,KAAK,yBAAyB;;CAGhC,uBAAoC;AAClC,OAAK,qBAAqB;;CAG5B,gBAAuB,GAAgC;AACrD,OAAK,qBAAqB"}
|
|
1
|
+
{"version":3,"file":"roomle-renderer-BR0UKz2y.mjs","names":[],"sources":["../../packages/common-core/src/utils/shims.ts","../../packages/common-core/src/utils/fps-counter.ts","../../packages/common-core/src/roomle-renderer.ts"],"sourcesContent":["import type { ROOMLE_COLORS } from './common-utils';\nimport type {\n HexColorNumber,\n HexColorString,\n RapiId,\n RapiServerUrlType,\n} from '../../../typings/rapi-types';\nimport type { CAMERA_TYPE } from '../cameracontrol/camera-type';\nimport type { Enumify } from '#/common/src/utils/types';\nimport type { ROOMLE_HIGHLIGHTNG } from './common-utils';\nimport type { DefaultEnvironmentSceneType } from '../webgl/renderer/environment-scene';\nimport type {\n CustomShadingParameters,\n SceneShadingType,\n} from '../webgl/renderer/shading-settings';\nimport type { Color } from 'three';\nimport type { HomagIntelligenceInitData } from '../../../homag-intelligence/src/hi-api-types';\n\nexport const enum WINDOW_EVENT {\n RESIZE = 'resize',\n KEY_DOWN = 'keydown',\n KEY_UP = 'keyup',\n VISIBILITY_CHANGE = 'visibilitychange',\n}\n\nexport const DRAW_PREVIEW_MODES = {\n DEFAULT: 'default',\n VECTOR: 'vector',\n} as const;\n\nexport const WEBGPU_TONE_MAPPINGS = {\n NONE: 'none',\n LINEAR: 'linear',\n REINHARD: 'reinhard',\n CINEON: 'cineon',\n ACES_FILMIC: 'acesFilmic',\n AGX: 'agx',\n NEUTRAL: 'neutral',\n} as const;\n\nexport type WebGpuToneMapping = Enumify<typeof WEBGPU_TONE_MAPPINGS>;\n\n/**\n * Init data parameters which are not context specific\n */\nexport interface GlobalInitDataDefinition {\n /**\n * @description locale to use for the\n * embedding instance\n */\n locale?: string;\n\n /**\n * @internal\n * @deprecated\n * @description Can be used for content testing\n */\n overrideTenant?: number;\n\n /**\n * @description Set country to use for the Roomle price service. If not used the customer IP address will be used to determine the location. usage: overrideCountry=de\n */\n overrideCountry?: string | null;\n\n /**\n * @internal\n * @description Set which RAPI (Roomle API) backend should be used options: live, test\n */\n overrideRapi?: RapiServerUrlType;\n\n /**\n * @description The id which should be used for this embedding instance. When this id is set all InitData parameter can be set and changed in Rubens Admin. This is the way we recommend to set init data params. Ask your Roomle contact for your configuratorId.\n */\n configuratorId?: string;\n\n /**\n * @description Set which RAPI (Roomle API) backend should be used usage: customApiUrl=https://www.roomle.com/api/v2\n */\n customApiUrl?: string;\n\n /**\n * @internal\n * @description Can be set to true if started as an end-to-end test. This will disable post-processing\n */\n e2e?: boolean;\n\n /**\n * @internal\n * @description If set to true, the Roomle Renderer is used for perspective and top images this should help the E2E tests of Joe to migrate from comparing Roomle Renderer images to Three.js Renderer images\n */\n e2eRoomleRendererForImages?: boolean;\n\n /**\n * @internal\n * @description When loading a plan, the external objects from the plan XML are discarded\n */\n discardExternalObjectsWhenLoadingPlan?: boolean;\n\n /**\n * @description If set to true, the plan will be locked and no changes can be made to walls, floors or construction elements.\n * @default false\n */\n lockPlan?: boolean;\n\n /**\n * @description If set to true, the plan is in read-only mode. Hides all editing UI and prevents interaction with plan elements.\n * Automatically sets edit=false and lockPlan=true and saveToIdb=false\n * @default false\n */\n readonly?: boolean;\n}\n\nexport interface WebGpuTopLightConfigDefinition {\n /**\n * @description Intensity (brightness) of the WebGPU top light.\n * @default 1\n */\n intensity?: number;\n\n /**\n * @description Intensity of the shadow cast by the top light.\n * 0 means no shadow, 1 means a fully opaque shadow.\n * @default 1\n */\n shadowIntensity?: number;\n\n /**\n * @description Softness (blur radius) of the top light's shadow edges.\n * Higher values produce softer shadows.\n * @default 1.1\n */\n shadowRadius?: number;\n}\n\nexport interface WebGpuGroundReflectionConfigDefinition {\n /**\n * @description Enables or disables the ground reflection entirely.\n * When disabled the floor still receives shadows but does not render a reflection,\n * which also avoids the extra reflection render pass.\n * @default true\n */\n enabled?: boolean;\n\n /**\n * @description Strength of the ground reflection.\n * 0 means no reflection, 1 means a full mirror-like reflection.\n * @default 0.1\n */\n intensity?: number;\n\n /**\n * @description Depth at which the reflection starts to fade out.\n * @default 0.05\n */\n startFadeout?: number;\n\n /**\n * @description Depth at which the reflection is fully faded out.\n * @default 0.15\n */\n endFadeout?: number;\n}\n\nexport interface WebGpuFogConfigDefinition {\n /**\n * @description If true, a fog is added to the scene in webGPU mode.\n * This is mainly so you cant see the outer edge of the\n * floor (which makes it blends smoothly into the background).\n * This fog exists in a cylindrical shape around the origin and the\n * farBlend and nearBlend parameters indirectly control the radius\n * at which the fog starts and ends.\n * @default true\n */\n enabled?: boolean;\n\n /**\n * @description Radial distance from the start of the fog to the end of the fog.\n * This creates a smooth transition from full fog to no fog.\n * Larger values produce a softer, more gradual fog transition.\n * @default 7\n */\n nearBlend?: number;\n\n /**\n * @description Distance from the far end of the camera view/floor boundaries\n * at which the fully opaque fog should start to appear.\n * Larger values move the fully opaque(/end of the) fog\n * closer to the center of the scene.\n * @default 10\n */\n farBlend?: number;\n}\n\nexport interface WebGpuConfigDefinition {\n /**\n * @description If true, the initial webgpu quality level is autodetected based on\n * a benchmark of the graphics hardware. If false, the default quality level is used.\n * @default true\n */\n autoDetectQualityLevel?: boolean;\n\n /**\n * @description This can be used to configure the lower bound of webgpu auto quality\n * meaning the fps bound which, when hit, will set the quality setting lower\n * To disable: set to 0\n * @default 24\n */\n autoQualityMinFps?: number;\n\n /**\n * @description This can be used to configure the upper bound of webgpu auto quality\n * meaning the fps bound which, when hit, will set the quality setting higher\n * This should be sufficiently larger than the minFps to not cause the quality to go up and down all the time\n * To disable: set to something very high\n * @default 59\n */\n autoQualityMaxFps?: number;\n\n /**\n * @description Visual configuration for the WebGPU top light and its shadow.\n */\n topLight?: WebGpuTopLightConfigDefinition;\n\n /**\n * @description Visual configuration for the WebGPU ground reflection.\n */\n groundReflection?: WebGpuGroundReflectionConfigDefinition;\n\n /**\n * @description Visual configuration for the WebGPU fog.\n */\n fog?: WebGpuFogConfigDefinition;\n\n /**\n * @description Tone mapping operator applied by the WebGPU renderer.\n * @default 'none'\n */\n toneMapping?: WebGpuToneMapping;\n\n /**\n * @description Exposure (intensity) of the tone mapping applied by the\n * WebGPU renderer. Higher values produce a brighter image.\n * @default 1\n */\n toneMappingExposure?: number;\n}\n\nexport interface FeatureFlags {\n pulsePreview?: boolean;\n reDock?: boolean;\n\n /**\n * @description Sets whether the snapping behavior of objects in the planner is initially enabled.\n */\n enableSnapping?: boolean;\n\n /**\n * @internal\n * @description If true, the advanced docking behavior is enabled (for test purposes only)\n */\n advancedDocking?: boolean;\n\n /**\n * @internal\n * @description If true, when inserting new objects in the planner, we will try to merge them into existing groups if possible.\n * This is for test purposes only and should not be enabled in production yet, as it can lead to unexpected behavior.\n */\n mergeOnInsert?: boolean;\n\n /**\n * @internal\n * @description If “vector”, the vector preview is activated when drawing walls (for test purposes only).\n * By default, the walls are immediately drawn with their actual thickness,\n * but this leads to problems when displaying the dimensions of the walls,\n * as it is not possible to guess which side of the wall of the room currently being entered is the inside of the wall.\n */\n drawPreviewMode?: Enumify<typeof DRAW_PREVIEW_MODES>;\n /**\n * @internal\n * @description TO BE REMOVED ONCE RML-13218 IS RESOLVED.\n * Forces new thumbnails to be generated on configuration save\n */\n forceSaveThumbnails?: boolean;\n\n /**\n * @internal\n * @description Enables dynamically placed interior lights in the room planner.\n */\n interiorLights?: boolean;\n\n /**\n * @internal\n * @description If true, the advanced dimensioning is enabled (for test purposes only)\n */\n advancedDimensioning?: boolean;\n\n /**\n * @internal\n * @description If true, the scene will be rendered with WebGPU instead of WebGL\n */\n webGpu?: boolean;\n\n /**\n * @internal\n * @description If true, the advanced highlighting behavior is enabled (for test purposes only)\n */\n advancedHighlighting?: boolean;\n\n /**\n * @internal\n * @description If true, enables the POC for planning fences (e.g. garden fences) in the room planner.\n */\n fence?: boolean;\n\n /**\n * @internal\n * @description If true, `onRequestPlan` waits until every pending configuration\n * save in the plan has been persisted to the Roomle backend before firing.\n * See RML-17394\n */\n awaitConfigurationSavesBeforeRequestPlan?: boolean;\n}\n\nexport type RoomleColors = keyof typeof ROOMLE_COLORS;\n\nexport type ColorsIn3dScene = Record<\n RoomleColors,\n HexColorString | HexColorNumber\n>;\n\nexport type ThreeJsColorsIn3dScene = Record<keyof typeof ROOMLE_COLORS, Color>;\n\n/**\n * Init data parameter which can be used in configurator AND planner\n */\nexport interface CommonInitData {\n /**\n * @description If set to true the configurator will make sure every device has great user experience with fluid frame rate.\n * If set to false the configurator will always show the highest quality possible.\n * That could lead to stutter and other problems on low-end devices. We advise you to only turn off \"autoQuality\" if you really know what you are doing.\n * usage: autoQuality=false\n * @groups scene-settings\n */\n autoQuality?: boolean;\n\n /**\n * @description Can be used to personalize colors used in the 3D scene\n * @groups scene-settings\n */\n colors?: ColorsIn3dScene;\n\n /**\n * @description The item id or configuration id of the item which should be loaded.\n * Examples:\n * Configuration id: usm:frame:2449773A7F54278EBB61A67A10EF6A10E8BB6F38BE775B028AC050B438414507\n * Item id: usm:frame\n */\n id?: RapiId;\n\n /**\n * @internal\n * @deprecated\n * @description Use prices which are hardcoded in the configuration script.\n * Only used for trade show demos.\n */\n useRoomlePrice?: boolean;\n\n /**\n * @description Set how many positions after the decimal point should be shown.\n * For example:\n * 8' 1.68\" -> precisionCm=2 -> 8' 1.68\"\n * 8' 1.68\" -> precisionCm=1 -> 8' 1.7\"\n * 8' 1.68\" -> precisionCm=0 -> 8' 2\"\n */\n precisionInch?: number;\n\n /**\n * @description Set how many positions after the decimal point should be shown.\n * For example:\n * 123,45 -> precisionCm=2 -> 123,45\n * 123,45 -> precisionCm=1 -> 123,5\n * 123,45 -> precisionCm=0 -> 124\n */\n precisionCm?: number;\n\n /**\n * @description Defines which unit should be used for measurements/dimensions\n * Options: 'cm', 'mm', 'inchfeet'\n */\n unit?: string;\n\n /**\n * @description How the current unit should be formatted\n * Options:\n * - 'none': No unit string added, for example '200'\n * - 'long': Long unit string, for example '200 meters'\n * - 'short': Short unit string, for example '200 m'\n */\n unitStringType?: string;\n\n /**\n * @description When mesh selection is enabled, the meshes of the object are used directly for highlighting.\n * Setting it to false could be useful for square/rectangular (shelf) items.\n */\n meshSelection?: boolean;\n\n /**\n * @description if set to true it shows a little overlay with performance stats\n */\n stats?: boolean;\n\n /**\n * @description This flag enables:\n * - Scripter debug logs (e.g. which parameter is not assigned to a parameter group)\n * - Free camera movement (no restrictions, camera stays after reload, panning using Shift key)\n */\n debug?: boolean;\n\n /**\n * @description This flag enables:\n * - External object docking vector debug geometry\n */\n debugGeometry?: boolean;\n\n /**\n * @description If true this flag shows component bounding boxes\n */\n debugBoundingBox?: boolean;\n\n /**\n * @description This flag enables:\n * - Help line geometry in the planner\n * @internal\n */\n debugLines?: boolean;\n\n /**\n * @description If set to true, prices are calculated using the Roomle price service.\n * For more information ask your Roomle contact.\n */\n usePriceService?: boolean;\n\n /**\n * @description Url to an exr or hdr env map\n * @groups scene-settings\n */\n envMapUrl?: string;\n\n /**\n * @description Set the intensity of the environment map\n * @groups scene-settings\n */\n envMapIntensity?: number;\n\n /**\n * @description Set the rotation of the environment map in radians, between 0 and 2 * PI, rotates counter-clockwise\n * @groups scene-settings\n */\n envMapRotation?: number;\n\n /**\n * @description Set the maximum number of light sources extracted from the environment map\n * @groups scene-settings\n */\n maxLightSources?: number;\n\n /**\n * @description Use old light setting instead of env maps. Ignored when using the WebGPU renderer\n * @groups scene-settings\n */\n legacyLight?: boolean;\n\n /**\n * @description Generate environment map from a scene instead of env maps\n * use all_around for an surrounding light\n * use front for a mainly frontal light\n * @groups scene-settings\n */\n sceneLight?: DefaultEnvironmentSceneType;\n\n /**\n * @description General shading properties for the overall appearance\n * default: for planner and a configurator with colorful, metallic or wooden materials\n * bright: for a configurator with white and bright materials\n * @groups scene-settings\n */\n shading?: SceneShadingType;\n\n /**\n * @description Customized shading properties for the overall appearance\n * @groups scene-settings\n */\n customShading?: CustomShadingParameters;\n\n /**\n * @description Activates the linear AO filter mode to reduce AO anti-aliasing artifacts\n * @groups scene-settings\n */\n linearAoFilter?: boolean;\n\n /**\n * @description Customized shading properties for the overall appearance in MOC\n * @groups scene-settings\n */\n customShadingMoc?: CustomShadingParameters;\n\n /**\n * @description restrictionLevel defines visibility of parameters and possible children\n */\n restrictionLevel?: number;\n\n /**\n * @description Settings for Homag Intelligence\n * @internal\n */\n hi?: HomagIntelligenceInitData;\n\n /**\n * @description disable injection of default styles for measurements and component dimension labels\n * to enable easier customization of these styles (via the assigned classes)\n */\n disableDefaultLabelStyles?: boolean;\n}\n\nexport interface ConfiguratorInitData {\n /**\n * @description Sets the behavior of the highlighting of a hovered component.\n * usage: highlighting=smart\n */\n highlighting?: Enumify<typeof ROOMLE_HIGHLIGHTNG>;\n\n /**\n * @description Set the floor material which is used when loading the configurator\n * For different options see {@link @roomle/web-sdk/configurator-core/src/environment/dynamic-environment-setting-loader#EnvironmentSetting}\n */\n initialFloorMaterial?: string;\n\n /**\n * @internal\n * @description Load a light setting using an URL.\n * Check lights in {@link @roomle/web-sdk/configurator-core/src/scene-settings-loader#SceneSettings} for an example.\n * value is a dynamic light setting url\n */\n dls?: string;\n\n /**\n * @description Load a predefined light setting, eg: ls=sofa\n * @groups scene-settings\n */\n ls?: string;\n\n /**\n * @description Set the distance to which the user can zoom (in centimeters).\n * default: object size divided by 10 or 10 cm\n * usage: zoomDistance=3\n * @groups scene-settings\n */\n zoomDistance?: number;\n\n /**\n * @description When set this param restricts the camera rotation to the given angle (e.g. 45 degree), this overrides minHorizontalCameraAngle and maxHorizontalCameraAngle\n * default: not set -> no camera restriction\n * usage: cameraRestriction=45\n * @groups scene-settings\n */\n cameraRestriction?: number;\n\n /**\n * @description Sets the default/initial vertical camera angle (pitch)\n * default: 10\n * usage: verticalCameraAngle=45\n * @groups scene-settings\n */\n verticalCameraAngle?: number;\n\n /**\n * @description When set this param restricts the down/vertical camera rotation to the given angle (e.g. 45 degree)\n * default: not set -> no camera angle restriction\n * usage: minVerticalCameraAngle=45\n * @groups scene-settings\n */\n minVerticalCameraAngle?: number;\n\n /**\n * @description When set this param restricts the up/vertical camera rotation to the given angle (e.g. 45 degree)\n * default: not set -> no camera angle restriction\n * usage: maxVerticalCameraAngle=45\n * @groups scene-settings\n */\n maxVerticalCameraAngle?: number;\n\n /**\n * @description Sets the default/initial horizontal camera angle (yaw)\n * default: -30\n * usage: horizontalCameraAngle=45\n * @groups scene-settings\n */\n horizontalCameraAngle?: number;\n\n /**\n * @description When set this param restricts the left/horizontal camera rotation to the given angle (e.g. 45 degree)\n * default: not set -> no camera angle restriction\n * usage: minHorizontalCameraAngle=45\n * @groups scene-settings\n */\n minHorizontalCameraAngle?: number;\n\n /**\n * @description When set this param restricts the right/horizontal camera rotation to the given angle (e.g. 45 degree)\n * default: not set -> no camera angle restriction\n * usage: maxHorizontalCameraAngle=45\n * @groups scene-settings\n */\n maxHorizontalCameraAngle?: number;\n\n /**\n * @description If set to false the camera will just jump to a new position\n * default: true\n * usage: animateCamera=false\n * @groups scene-settings\n */\n animateCamera?: boolean;\n\n /**\n * @internal\n * @description Easter Egg\n * */\n bananaForScale?: boolean;\n\n /**\n * @description If set to true, components which are between the camera and the selected component/preview will be rendered transparent\n * usage: transparentHighlighting=true\n * @groups scene-settings\n */\n transparentHighlighting?: boolean;\n\n /**\n * @description If set to true, the background is transparent and allows elements behind the configurator to be visible through the scene.\n * usage: transparentBackground=true\n * @groups scene-settings\n */\n transparentBackground?: boolean;\n\n /**\n * @description If set to true it will try to apply all current global parameters to the loaded object\n */\n applyCurrentGlobalParameters?: boolean;\n\n /**\n * @description Adds the given spacing (in centimeters) to the loaded object. This is then used to calculate the initial camera distance\n * default: 0\n * usage:\n * cameraSpacing=100, zoom out further by increasing camera spacing by 100 centimeters\n * cameraSpacing=-30, zoom in further by decreasing camera spacing by 30 centimeters\n * @groups scene-settings\n */\n cameraSpacing?: number;\n\n /**\n * @internal\n * @description If set to true, screen space reflections are enabled\n */\n ssr?: boolean;\n\n /**\n * @internal\n * @description features that can be enabled/disabled\n */\n featureFlags?: FeatureFlags;\n\n /**\n * @internal\n * @description Configuration for the WebGPU renderer (e.g. auto quality fps bounds).\n */\n webGpuConfig?: WebGpuConfigDefinition;\n\n /**\n * @description if true, the meshes of the component will be used, in addition to the bounding box, for raycasting (selecting)\n * Warning: this will decrease performance when hovering over components with complex meshes\n * @default false\n * usage: meshRaycasting=true\n */\n meshRaycasting?: boolean;\n}\n\nexport interface PlannerInitData {\n /**\n * @description Sets the default floor material when creating a new plan or drawing.\n * Can be a number (RGB color) or a string (material ID).\n */\n defaultFloorMaterial?: number | string;\n\n /**\n * @description Select with which camera mode to start initially.\n * usage: mode=2D\n * options: 2D, 3D, FP\n */\n mode?: Enumify<typeof CAMERA_TYPE>;\n\n /**\n * @internal\n * @description If set to true the plan can be edited.\n */\n edit?: boolean;\n\n /**\n * @description If set to true it will treat the planner as a multi object configurator (moc).\n * This means:\n * - do not merge geometries\n * - set configurator environment & lights in planner\n * - reduce initial spacing of camera (higher zoom)\n */\n moc?: boolean;\n\n /**\n * @description If set to true we will not hide other items while configuring in the planner\n */\n configureInRoom?: boolean;\n\n /**\n * @description If set to true docking of configurable objects in the planner is enabled.\n * The default value is true.\n */\n dockInRoom?: boolean;\n}\n\nexport interface InitDataDefinition\n extends CommonInitData,\n ConfiguratorInitData,\n PlannerInitData {}\n","const FPS_LIST_SIZE = 10;\nconst FPS_MEASUREMENT_INTERVAL_MS = 20;\n\nexport default class FpsCounter {\n private _index: number = 0;\n private _fpsList: number[];\n private _prevTime: number = 0;\n private _frames: number = 0;\n private _fps: number = -1;\n\n constructor() {\n this._fpsList = Array(FPS_LIST_SIZE).fill(0);\n this._prevTime = (performance || Date).now();\n this.reset();\n }\n\n public update() {\n this._frames++;\n const time = (performance || Date).now();\n\n if (time >= this._prevTime + FPS_MEASUREMENT_INTERVAL_MS) {\n this._fpsList[this._index % FPS_LIST_SIZE] =\n (this._frames * 1000) / (time - this._prevTime);\n\n if (this._index >= FPS_LIST_SIZE) {\n this._fps =\n this._fpsList.reduce((pv, cv) => pv + cv) / this._fpsList.length;\n }\n\n this._prevTime = time;\n this._frames = 0;\n this._index++;\n }\n }\n\n public start() {\n this._prevTime = (performance || Date).now();\n this._frames = 0;\n }\n\n public reset() {\n this._fps = -1;\n this._fpsList = Array(FPS_LIST_SIZE).fill(0);\n this._index = 0;\n }\n\n public getFps() {\n return this._fps;\n }\n}\n","import type { Camera, Object3D, Scene, Vector2, WebGLRenderer } from 'three';\nimport { Box3 } from 'three';\nimport type { Enumify } from '#/common/src/utils/types';\nimport type { CAMERA_TYPE } from './cameracontrol/camera-type';\nimport type { WebGPURenderer } from 'three/webgpu';\nimport type { SceneShadingType } from './webgl/renderer/shading-settings';\nimport type { OutlineCustomParameters } from './webgl/renderer/scene-renderer';\n\nexport type GenericRoomleRenderer = RoomleRenderer<\n WebGLRenderer | WebGPURenderer\n>;\n\nexport enum PlannerRenderMode {\n Mode3D,\n Mode2D,\n}\n\nexport abstract class RoomleRenderer<\n RendererType extends WebGLRenderer | WebGPURenderer,\n> {\n public _creator_: string;\n public renderer: RendererType;\n protected isConfiguratorMode: boolean = true;\n protected isCameraMoving: boolean = false;\n protected cameraType: Enumify<typeof CAMERA_TYPE>;\n protected shadowNeedsUpdate: boolean = false;\n protected boundingBox: Box3 = new Box3();\n protected boundingBoxNeedsUpdate: boolean = false;\n\n protected constructor(creator: string, renderer: RendererType) {\n this._creator_ = creator;\n this.renderer = renderer;\n }\n\n public get isConfiguring(): boolean {\n return this.isConfiguratorMode;\n }\n\n public dispose(): void {\n this.renderer.dispose();\n }\n\n public setSize(width: number, height: number): void {\n this.renderer.setSize(width, height);\n }\n\n public getSize(target: Vector2): Vector2 {\n return this.renderer.getSize(target);\n }\n\n public setCameraType(cameraType: Enumify<typeof CAMERA_TYPE>) {\n this.cameraType = cameraType;\n }\n\n public movingCameraStarts(): void {\n this.isCameraMoving = true;\n }\n\n public movingCameraStops(): void {\n this.isCameraMoving = false;\n }\n\n public forceShadowUpdates(_updateBakedGroundShadow: boolean): void {\n this.shadowNeedsUpdate = true;\n }\n\n public clear() {\n this.renderer.clear();\n }\n\n public updateBounds(geometryBounds: Box3) {\n this.boundingBox.copy(geometryBounds);\n this.boundingBoxNeedsUpdate = true;\n }\n\n public switchToConfigurator(): void {\n this.isConfiguratorMode = true;\n }\n\n public switchToPlanner(_renderMode: PlannerRenderMode) {\n this.isConfiguratorMode = false;\n }\n\n public abstract setShadingType(shadingType: SceneShadingType): void;\n\n public abstract setCustomOutlineParameters(\n outlineCustomParameters: OutlineCustomParameters,\n ): void;\n\n public abstract render(scene: Scene, camera: Camera): void;\n\n public abstract showGUI(updateCallback: () => void): void;\n\n public abstract clearCache(): void;\n\n public abstract highlightObjects(selectedMeshes: Object3D[]): void;\n\n public abstract setGroundShadow(enabled: boolean, scene: Scene): void;\n\n public abstract enableUiInteractionMode(): void;\n\n public abstract disableUiInteractionMode(): void;\n}\n"],"mappings":";;AAkBA,IAAkB,IAAX,yBAAA,GAAA;QACL,EAAA,SAAA,UACA,EAAA,WAAA,WACA,EAAA,SAAA,SACA,EAAA,oBAAA;KACD,EAEY,IAAqB;CAChC,SAAS;CACT,QAAQ;CACT,EAEY,IAAuB;CAClC,MAAM;CACN,QAAQ;CACR,UAAU;CACV,QAAQ;CACR,aAAa;CACb,KAAK;CACL,SAAS;CACV,ECtCK,IAAgB,IAChB,IAA8B,IAEf,IAArB,MAAgC;CAO9B,cAAc;AAGZ,gBATuB,oBAEG,kBACF,eACH,IAGrB,KAAK,WAAW,MAAM,EAAc,CAAC,KAAK,EAAE,EAC5C,KAAK,aAAa,eAAe,MAAM,KAAK,EAC5C,KAAK,OAAO;;CAGd,SAAgB;AACd,OAAK;EACL,IAAM,KAAQ,eAAe,MAAM,KAAK;AAExC,EAAI,KAAQ,KAAK,YAAY,MAC3B,KAAK,SAAS,KAAK,SAAS,KACzB,KAAK,UAAU,OAAS,IAAO,KAAK,YAEnC,KAAK,UAAU,MACjB,KAAK,OACH,KAAK,SAAS,QAAQ,GAAI,MAAO,IAAK,EAAG,GAAG,KAAK,SAAS,SAG9D,KAAK,YAAY,GACjB,KAAK,UAAU,GACf,KAAK;;CAIT,QAAe;AAEb,EADA,KAAK,aAAa,eAAe,MAAM,KAAK,EAC5C,KAAK,UAAU;;CAGjB,QAAe;AAGb,EAFA,KAAK,OAAO,IACZ,KAAK,WAAW,MAAM,EAAc,CAAC,KAAK,EAAE,EAC5C,KAAK,SAAS;;CAGhB,SAAgB;AACd,SAAO,KAAK;;GCnCJ,IAAL,yBAAA,GAAA;QACL,EAAA,EAAA,SAAA,KAAA,UACA,EAAA,EAAA,SAAA,KAAA;KACD,EAEqB,IAAtB,MAEE;CAUA,YAAsB,GAAiB,GAAwB;AAE7D,4BATsC,0BACJ,6BAEG,uBACT,IAAI,GAAM,gCACI,IAG1C,KAAK,YAAY,GACjB,KAAK,WAAW;;CAGlB,IAAW,gBAAyB;AAClC,SAAO,KAAK;;CAGd,UAAuB;AACrB,OAAK,SAAS,SAAS;;CAGzB,QAAe,GAAe,GAAsB;AAClD,OAAK,SAAS,QAAQ,GAAO,EAAO;;CAGtC,QAAe,GAA0B;AACvC,SAAO,KAAK,SAAS,QAAQ,EAAO;;CAGtC,cAAqB,GAAyC;AAC5D,OAAK,aAAa;;CAGpB,qBAAkC;AAChC,OAAK,iBAAiB;;CAGxB,oBAAiC;AAC/B,OAAK,iBAAiB;;CAGxB,mBAA0B,GAAyC;AACjE,OAAK,oBAAoB;;CAG3B,QAAe;AACb,OAAK,SAAS,OAAO;;CAGvB,aAAoB,GAAsB;AAExC,EADA,KAAK,YAAY,KAAK,EAAe,EACrC,KAAK,yBAAyB;;CAGhC,uBAAoC;AAClC,OAAK,qBAAqB;;CAG5B,gBAAuB,GAAgC;AACrD,OAAK,qBAAqB"}
|