@vctrl/hooks
Version:
vctrl/hooks is a React hooks package designed to simplify 3D model loading and management within React applications. It's part of the vectreal-core ecosystem and is primarily used in the vctrl/viewer React component and the official website application.
1 lines • 13.3 kB
JavaScript
let e=require("@vctrl/core"),t=require("@vctrl/core/model-loader"),n=require("react");var r=({code:e,message:t,recoverable:n,source:r,cause:i,context:a})=>({code:e,message:t,recoverable:n,source:r,cause:i,context:a}),i=e=>e instanceof Error&&e.message?e.message:typeof e==`string`&&e.trim().length>0?e:`Unknown error`,a=(e,t,n)=>{let a=i(e);return r({code:a.includes(`missing required referenced assets`)?`missing_assets`:t,message:a,recoverable:!0,source:`local-upload`,cause:e,context:n})},o=(e,t)=>{let n=i(e),a=`server_load_failed`;return n.includes(`Server responded with 404`)?a=`not_found`:(n.includes(`Server responded with 402`)||n.includes(`Server responded with 403`))&&(a=`quota_exceeded`),r({code:a,message:n,recoverable:a!==`not_found`,source:`server-load`,cause:e,context:{sceneId:t}})},s={},c={emit(e,t){let n=s[e];console.debug(`Event emitted: ${e}`,t),n&&n.forEach(e=>e(t))},on(e,t){s[e]||(s[e]=[]),s[e].push(t)},off(e,t){let n=s[e];n&&(s[e]=n.filter(e=>e!==t))}},l=t=>{try{return(0,e.normalizeAssetUri)(t)}catch{return t}},u=e=>{let t=new Set,n=e,r=Array.isArray(n.images)?n.images:[];for(let e of r)typeof e.uri==`string`&&!e.uri.startsWith(`data:`)&&t.add(e.uri);let i=Array.isArray(n.buffers)?n.buffers:[];for(let e of i)typeof e.uri==`string`&&!e.uri.startsWith(`data:`)&&t.add(e.uri);return t},d=async(t,n)=>{let r=JSON.parse(await t.text()),i=u(r),a={},o=new Map,s=new Map,c=new Set,d=async e=>{let t=s.get(e);if(t)return t;let n=new Uint8Array(await e.arrayBuffer());return s.set(e,n),n};for(let t of n){for(let n of(0,e.buildAssetLookupKeys)(t.name))o.set(n,t);if(t.webkitRelativePath)for(let n of(0,e.buildAssetLookupKeys)(t.webkitRelativePath))o.set(n,t)}let f=[];for(let e of i){let t=l(e),n=t.split(`/`).pop()||t,r=o.get(e)||o.get(t)||o.get(n);if(!r){f.push(e);continue}c.add(r);let i=await d(r);a[t]={data:Array.from(i),fileName:t,mimeType:r.type||`application/octet-stream`}}if(f.length>0)throw Error(`Scene payload is missing required referenced assets: ${f.slice(0,5).join(`, `)}`);for(let[e,t]of n.entries()){if(c.has(t))continue;let n=l(t.name),r=n.split(`/`).pop()||n,i=await d(t);a[`extra-${e}-${r}`]={data:Array.from(i),fileName:r,mimeType:t.type||`application/octet-stream`}}return{gltfJson:r,assetData:a}},f=async(e,n,r,i,o)=>{try{let a={model:(await r.loadToThreeJS(e)).scene,type:n,name:e.name};if(o({type:`set-file`,payload:a}),o({type:`set-file-loading`,payload:!1}),i&&n!==t.ModelFileTypes.usdz){let t=new Uint8Array(await e.arrayBuffer());await i.loadFromGlbBuffer(t)}return a}catch(t){return console.error(`Error loading binary model:`,t),o({type:`set-file-loading`,payload:!1}),c.emit(`load-error`,a(t,`binary_load_failed`,{fileName:e.name,fileType:n,fileSize:e.size})),null}},p=async(e,n,r,i,o,s)=>{try{let{sourcePackageBytes:a,textureBytes:c}=await s(e,n),l=await r.loadGLTFWithAssetsToThreeJS(e,n),u={model:l.scene,type:t.ModelFileTypes.gltf,name:e.name,sourcePackageBytes:a,sourceTextureBytes:c};if(o({type:`set-file`,payload:u}),o({type:`set-file-loading`,payload:!1}),i)try{let t=await d(e,n);await i.loadFromServerSceneData(t)}catch(t){console.warn(`Failed to initialize optimizer from source GLTF payload; trying direct asset load.`,t);try{if(`loadFromGLTFWithAssets`in i){let t=new Uint8Array(await e.arrayBuffer()),r=new Map;for(let e of n){let t=new Uint8Array(await e.arrayBuffer());r.set(e.name,t),e.webkitRelativePath&&r.set(e.webkitRelativePath,t)}await i.loadFromGLTFWithAssets(t,r)}else throw Error(`loadFromGLTFWithAssets not available`)}catch(e){console.warn(`Direct asset load also failed; falling back to Three.js scene import. Texture names may be generic.`,e),await i.load(l.scene)}}return u}catch(t){return console.error(`Error loading GLTF model:`,t),console.error(`GLTF file:`,e.name),console.error(`Asset files:`,n.map(e=>e.name)),o({type:`set-file-loading`,payload:!1}),c.emit(`load-error`,a(t,`gltf_load_failed`,{fileName:e.name,fileSize:e.size,assetCount:n.length})),null}};function m(e,r,i,a){let o=(0,n.useCallback)(async(n,o)=>{if(!e){console.warn(`Optimizer is not available`);return}try{n&&await n(o);let s=await e.getModel();if(!s){console.warn(`No optimized model available after optimization`);return}let c=s instanceof Uint8Array?s.slice():new Uint8Array(s).slice(),l=new File([c],i?.name||`optimized_model.glb`,{type:`model/gltf-binary`});r({type:`set-file`,payload:{model:(await a.loadToThreeJS(l)).scene,type:t.ModelFileTypes.glb,name:l.name}})}catch(e){console.error(`Optimization failed:`,e)}},[e,a,i,r]);if(!e)return null;let s=e.isReady,c=!!i?.model&&!s;return{...e,isReady:s,isPreparing:c,applyOptimization:o,reset:e?.reset,error:e?.error,loading:e?.loading}}var h={file:null,isFileLoading:!1,progress:0,supportedFileTypes:[t.ModelFileTypes.gltf,t.ModelFileTypes.glb,t.ModelFileTypes.usdz]};function g(e,t){switch(t.type){case`set-file`:return{...e,file:t.payload};case`set-file-loading`:return{...e,isFileLoading:t.payload};case`set-progress`:return{...e,progress:t.payload};case`reset-state`:return{...h};default:return e}}async function _(e){let t=[];async function*n(e){let t=e;for await(let[,e]of t)e.kind===`file`?yield await e.getFile():e.kind===`directory`&&(yield*n(e))}for await(let r of n(e))t.push(r);return t}function v(t){let n=[];if(t.assetData&&typeof t.assetData==`object`)for(let[,r]of Object.entries(t.assetData)){let{fileName:t,mimeType:i}=r;if(t===e.PERSISTED_BAKE_FILENAME)continue;let a=(0,e.toSerializedAssetBytes)(r),o=new Uint8Array(a.byteLength);o.set(a);let s=new Blob([o],{type:i}),c=new File([s],t,{type:i});n.push(c)}let r=JSON.stringify(t.gltfJson),i=new Blob([r],{type:`model/gltf+json`}),a=t.meta?.name,o=a?`${a}.gltf`:`scene.gltf`;return[new File([i],o,{type:`model/gltf+json`}),...n]}var y=(t,n)=>{let r=(0,e.normalizeAssetUri)(t),i=r.split(`/`).pop()||r;for(let t of n){let n=(0,e.buildAssetLookupKeys)(t.fileName);if(n.has(r)||n.has(i))return t.size}return 0},b=e=>{let t=new Set,n=new Set,r=e,i=Array.isArray(r.images)?r.images:[];for(let e of i)typeof e.uri==`string`&&!e.uri.startsWith(`data:`)&&n.add(e.uri);let a=Array.isArray(r.buffers)?r.buffers:[];for(let e of a)typeof e.uri==`string`&&!e.uri.startsWith(`data:`)&&t.add(e.uri);return{bufferUris:t,imageUris:n}};async function x(e,t){let n=await e.text(),r;try{r=JSON.parse(n)}catch{return{sourcePackageBytes:e.size,textureBytes:0}}let i=t.map(e=>({fileName:e.name,size:e.size})),{bufferUris:a,imageUris:o}=b(r),s=Array.from(a).reduce((e,t)=>e+y(t,i),0),c=Array.from(o).reduce((e,t)=>e+y(t,i),0);return{sourcePackageBytes:e.size+s+c,textureBytes:c}}function S(t){let n=t.gltfJson,r=Object.values(t.assetData||{}).map(t=>({fileName:t.fileName,size:(0,e.getSerializedAssetByteSize)(t.data)})),i=new TextEncoder().encode(JSON.stringify(n)).byteLength,{bufferUris:a,imageUris:o}=b(n),s=Array.from(a).reduce((e,t)=>e+y(t,r),0),c=Array.from(o).reduce((e,t)=>e+y(t,r),0);return{sourcePackageBytes:i+s+c,textureBytes:c}}function C(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function w(t){let n=C(t.settings)?t.settings:{},r=(0,e.normalizeCameraSettings)(t.camera??n.camera);return{...n,bounds:t.bounds??n.bounds,camera:r,interactions:(0,e.normalizeSceneInteractions)(n.interactions,{camera:r}),controls:t.controls??n.controls,environment:t.environment??n.environment,shadows:t.shadows??n.shadows}}function T(e){let t=new Set,n=e,r=Array.isArray(n.images)?n.images:[];for(let e of r)typeof e.uri==`string`&&!e.uri.startsWith(`data:`)&&t.add(e.uri);let i=Array.isArray(n.buffers)?n.buffers:[];for(let e of i)typeof e.uri==`string`&&!e.uri.startsWith(`data:`)&&t.add(e.uri);return t}function E(t){for(let[n,r]of Object.entries(t)){if(!r||typeof r!=`object`)throw Error(`Scene payload contains invalid asset entry for ${n}`);if(typeof r.fileName!=`string`||r.fileName.trim().length===0)throw Error(`Scene payload asset ${n} is missing a valid fileName`);if(typeof r.mimeType!=`string`||r.mimeType.trim().length===0)throw Error(`Scene payload asset ${n} is missing a valid mimeType`);if(typeof r.data==`string`&&r.encoding===`base64`&&!(0,e.isValidBase64)(r.data))throw Error(`Scene payload asset ${r.fileName} has invalid base64 data`)}}function D(t,n){let r=T(t);if(r.size===0)return;let i=new Set;for(let t of Object.values(n))for(let n of(0,e.buildAssetLookupKeys)(t.fileName))i.add(n);let a=[];for(let t of r){let n=(0,e.normalizeAssetUri)(t),r=n.split(`/`).pop()||n;!i.has(t)&&!i.has(n)&&!i.has(r)&&a.push(t)}if(a.length>0)throw Error(`Scene payload is missing required referenced assets: ${a.slice(0,5).join(`, `)}`)}function O(e){if(!C(e.gltfJson))throw Error(`Scene payload is missing a valid glTF document`);let t=e.assetData??{};return E(t),D(e.gltfJson,t),{meta:e.meta,gltfJson:e.gltfJson,assetData:t,...w(e)}}async function k(e,t={}){let n=Object.entries(e);if(n.length===0)return t.onProgress?.(1),{};let r=n.every(([,e])=>e.byteSize!=null),i=r?n.reduce((e,[,t])=>e+(t.byteSize??0),0):0,a=0,o=0,s={},c=new AbortController;return await Promise.all(n.map(async([e,l])=>{try{let u=await fetch(l.url,{headers:t.headers,credentials:`same-origin`,signal:c.signal});if(!u.ok)throw c.abort(),Error(`Failed to fetch scene asset ${l.fileName}: ${u.status}`);s[e]={data:new Uint8Array(await u.arrayBuffer()),fileName:l.fileName,mimeType:l.mimeType},o+=1,a+=l.byteSize??0,t.onProgress?.(r&&i>0?a/i:o/n.length)}catch(e){throw c.abort(),e}})),s}async function A(e,t){try{let n=await fetch(e,{method:`GET`,headers:{Accept:`application/json`,...t}});if(!n.ok)return null;let r=await n.json(),i=r.data??r;return!i||typeof i!=`object`||!i.gltfJson||!i.assetRefs&&!i.assetData?null:i}catch{return null}}async function j(e,t,n){let r=new FormData;r.append(`action`,`get-scene-settings`),r.append(`sceneId`,n);let i=await fetch(e,{method:`POST`,headers:t,body:r});if(!i.ok)throw Error(`Server responded with ${i.status} ${i.statusText}`);let a=await i.json();return a.data??a}function M(r){let i=(0,n.useRef)(!1),a=(0,n.useRef)(null),[s,l]=(0,n.useReducer)(g,h),u=(0,n.useMemo)(()=>{let e=new t.ModelLoader;return e.onProgress(e=>{l({type:`set-progress`,payload:e.progress}),e.progress!==100&&c.emit(`load-progress`,e.progress)}),e},[]),d=(0,n.useCallback)(e=>{l({type:`set-progress`,payload:e}),c.emit(`load-progress`,e)},[]),y=(0,n.useCallback)(()=>{l({type:`reset-state`}),c.emit(`load-reset`)},[]),b=(0,n.useCallback)((e,t)=>e.find(e=>e.name.endsWith(`.`+t)),[]),C=(0,n.useCallback)(async e=>{if(e.length===0)return;let n=b(e,t.ModelFileTypes.gltf),o=b(e,t.ModelFileTypes.glb),s=b(e,t.ModelFileTypes.usdz),d=[n,o,s].filter(Boolean);if(d.length>1){c.emit(`multiple-models`,d);return}let m=e.filter(e=>e!==n&&e!==o&&e!==s),h=()=>{i.current=!0,a.current&&(c.emit(`load-complete`,a.current),i.current=!1)};if(n){let e=await p(n,m,u,r,l,x);e&&(a.current=e),h()}else if(o){let e=await f(o,t.ModelFileTypes.glb,u,r,l);e&&(a.current=e),h()}else if(s){let e=await f(s,t.ModelFileTypes.usdz,u,r,l);e&&(a.current=e),h()}else c.emit(`not-loaded-files`,e),l({type:`set-file-loading`,payload:!1})},[b,u,r]),w=(0,n.useCallback)(async e=>{let t=[];c.emit(`load-start`),l({type:`reset-state`}),l({type:`set-file-loading`,payload:!0}),d(0),i.current=!1;for(let n of e)if(n instanceof File)t.push(n);else if(`kind`in n&&n.kind===`directory`){let e=await _(n);t.push(...e)}await C(t)},[C,d]),T=(0,n.useCallback)(async n=>{let{sceneData:i,sceneId:o}=n;if(d(40),n.parseMode===`direct`&&i.gltfJson){let n=new Map;for(let t of Object.values(i.assetData??{}))t.fileName!==e.PERSISTED_BAKE_FILENAME&&n.set(t.fileName,(0,e.toSerializedAssetBytes)(t));let{sourcePackageBytes:r,textureBytes:s}=S(i);d(60);let c={model:(await u.parseGLTFJsonToThreeJS(i.gltfJson,n)).scene,type:t.ModelFileTypes.gltf,name:i.meta?.name||`scene`,sourcePackageBytes:r,sourceTextureBytes:s};return l({type:`set-file`,payload:c}),l({type:`set-file-loading`,payload:!1}),a.current=c,d(100),{file:c,sceneId:o,...i}}let s=v(i),{sourcePackageBytes:c,textureBytes:f}=S(i);d(60);let p=s[0],m=s.slice(1),h={model:(await u.loadGLTFWithAssetsToThreeJS(p,m)).scene,type:t.ModelFileTypes.gltf,name:p.name,sourcePackageBytes:c,sourceTextureBytes:f};return l({type:`set-file`,payload:h}),l({type:`set-file-loading`,payload:!1}),a.current=h,r&&await r.loadFromServerSceneData(i),d(100),{file:h,sceneId:o,...i}},[u,r,d]),E=(0,n.useCallback)(async e=>{let{sceneId:t,serverOptions:n,parseMode:r}=e;try{c.emit(`server-load-start`,t),l({type:`reset-state`}),l({type:`set-file-loading`,payload:!0}),d(0);let e=n?.endpoint??`/api/scenes/${t}`,i=n?.apiKey?{Authorization:`Bearer ${n.apiKey}`,...n.headers}:{...n?.headers},a=await A(e,i);if(a?.assetRefs&&!a.assetData){d(10);let e=await k(a.assetRefs,{headers:i,onProgress:e=>d(10+Math.round(e*30))});a={...a,assetData:e}}a||=await j(e,i,t);let o=await T({sceneId:t,sceneData:O(a),parseMode:r});return c.emit(`server-load-complete`,o),o}catch(e){console.error(`Server scene loading failed:`,e),l({type:`set-file-loading`,payload:!1});let n=o(e,t);throw c.emit(`server-load-error`,n),n}},[T,d]),D=m(r,l,s.file,u);return{...s,on:c.on,off:c.off,load:w,loadFromData:T,loadFromServer:E,reset:y,optimizer:D}}Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return k}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return v}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return M}});