diff --git a/Dockerfile b/Dockerfile index c359f6ab3cf9181a9c1f3e6b81d422f4c0f3ac32..441e5099ebf45e0036c1868c1c55641954575fd0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# deploy: 20261005-ee37df6 (simready-assets) +# deploy: 20261012-9d9f42a (simready-assets) # GPU variant of the single-container MVP image for Hugging Face Spaces # (Docker SDK): everything docker/Dockerfile.hf-space has (OpenSearch + Neo4j # + Redis + deepsearch-api + asset-graph-service + nginx under supervisord), @@ -282,7 +282,7 @@ COPY docker/hf-space/search_fields.yaml /app/search_fields.yaml # own uvicorn; remapped to 8003 here). COPY docker/hf-space/supervisord-gpu.conf /etc/supervisor/conf.d/hf-space.conf -COPY docker/hf-space/nginx-glb-production.conf.template /etc/nginx/hf-space.conf.template +COPY docker/hf-space/nginx.conf.template /etc/nginx/hf-space.conf.template COPY services/explorer/dist /usr/share/nginx/explorer diff --git a/docker/hf-space/nginx-glb-production.conf.template b/docker/hf-space/nginx-glb-production.conf.template deleted file mode 100644 index 33c41a73ebd740190c69125029ea3dc984c12515..0000000000000000000000000000000000000000 --- a/docker/hf-space/nginx-glb-production.conf.template +++ /dev/null @@ -1,367 +0,0 @@ -# Friendly external paths remapped to deepsearch-api's/asset-graph-service's -# actual routes — same mapping as -# usdsearch/helm/usdsearch/templates/api_gateway_config_map.yaml. -# deepsearch-api listens on 127.0.0.1:8000, asset-graph-service on -# 127.0.0.1:8010 (see docker/hf-space/supervisord.conf) — nginx is the sole -# process bound to the externally proxied port, so it can front every route -# with these rewrites plus CORS. -# -# The Explorer UI is served at `/` (root), not proxied to deepsearch-api at -# all — served directly from the static bundle baked into this image at -# build time (see the Dockerfile). Real difference from the multi-service -# deployment, which proxies /ui and /static/ to a *separate* -# deepsearch-explorer container: that bundle is just static files with -# same-origin relative API calls (REACT_APP_API_URL baked empty) and its -# own assets already use root-absolute paths (/static/js/..., /ds.png), so -# serving it directly at `/` — where it already expects to live — needs no -# extra process and no /ui prefix-stripping trick. The tradeoff: unlike the -# old single catch-all proxy, deepsearch-api's own root-level routes -# (/health, /docs, /redoc, /openapi.json) now need to be listed -# explicitly below, since the final `location /` falls through to the SPA -# instead. Also sidesteps a real problem seen live: a Space deployed with -# `/` returning a 301 (redirecting to `/ui`, the previous approach) broke — -# root cause not fully confirmed, but consistent with Hugging Face's own -# readiness check wanting a real 200 at `/`, not a redirect. -# -# __PORT__ is substituted with $PORT at container start (supervisord.conf), -# matching the Spaces convention of an operator-configurable app_port. - -# >>> simready-central slim explorer (http) >>> -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# Included from nginx.conf's http{} (conf.d/*.conf) — an http-level directive is legal here. -# ONE level under /tmp on purpose: nginx mkdir()s only the last path component (no mkdir -p), and -# nothing in this image pre-creates a parent — /tmp/nginx-cache/slim-stats fails `nginx -t`. The -# `levels=` subdirectories are different: nginx creates those itself on the first write. -proxy_cache_path /tmp/slim-stats-cache levels=1 keys_zone=slim_stats_cache:2m max_size=256m inactive=1h use_temp_path=off; -# Thumbnails (/images): ~1,500 JPEGs of 27-52 KB (~75 MB) fit well inside 1g. Lives in the container's -# /tmp, so every deploy, restart or sleep starts it cold. -proxy_cache_path /tmp/slim-images-cache levels=1:2 keys_zone=slim_images_cache:10m max_size=1g inactive=7d use_temp_path=off; -# Library media (/library_media/ below): a file asked for under its ?v= (its content hash) never changes; the -# manifest (no ?v=) revalidates, so a new fetch at the next container start shows at once. -map $arg_v $slim_library_cache { - "" "no-cache"; - default "public, max-age=31536000, immutable"; -} -# <<< simready-central slim explorer (http) <<< -server { - listen __PORT__; - - proxy_http_version 1.1; - proxy_set_header Connection ""; - proxy_connect_timeout 5s; - proxy_send_timeout 60s; - proxy_read_timeout 60s; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - add_header Access-Control-Allow-Origin "*" always; - add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always; - add_header Access-Control-Allow-Headers "Content-Type, Authorization" always; - - location /search { - if ($request_method = OPTIONS) { add_header Content-Length 0 always; add_header Content-Type "text/plain" always; return 204; } - rewrite ^/search(.*)$ /v2/deepsearch/search$1 break; - proxy_pass http://127.0.0.1:8000; - } - - location /search_hybrid { - if ($request_method = OPTIONS) { add_header Content-Length 0 always; add_header Content-Type "text/plain" always; return 204; } - rewrite ^/search_hybrid(.*)$ /v3/deepsearch/search$1 break; - proxy_pass http://127.0.0.1:8000; - } - - location /vlm_validate/search_result { - if ($request_method = OPTIONS) { add_header Content-Length 0 always; add_header Content-Type "text/plain" always; return 204; } - rewrite ^/vlm_validate/search_result(.*)$ /v3/deepsearch/vlm_validate/search_result$1 break; - proxy_pass http://127.0.0.1:8000; - } - - location /llm_parse/ { - if ($request_method = OPTIONS) { add_header Content-Length 0 always; add_header Content-Type "text/plain" always; return 204; } - rewrite ^/llm_parse/(.*)$ /v3/deepsearch/llm_parse/$1 break; - proxy_pass http://127.0.0.1:8000; - } - - # package-download (127.0.0.1:8020, pipeline/package_download/), not deepsearch-api: it answers - # /download/asset with the asset's whole SimReady package folder, same response contract, and - # forwards scope=dependencies (and anything outside a catalogued package) to deepsearch-api's - # own /v3/download/asset. It serves the /download/... paths itself, so no rewrite. - location /download { - if ($request_method = OPTIONS) { add_header Content-Length 0 always; add_header Content-Type "text/plain" always; return 204; } - proxy_pass http://127.0.0.1:8020; - # Fetching a whole package (or a forwarded dependency bundle) can take longer than a single thumbnail. - proxy_read_timeout 300s; - } - - location /images { - if ($request_method = OPTIONS) { add_header Content-Length 0 always; add_header Content-Type "text/plain" always; return 204; } - rewrite ^/images(.*)$ /v3/images$1 break; - proxy_pass http://127.0.0.1:8000; - } - - location /static/ { - alias /usr/share/nginx/explorer/static/; - } - - # asset-graph-service routes (query-time graph reads only — see - # docker/Dockerfile.hf-space's header for why there's no graph-builder/ - # asset_graph_generation worker in this image). Same rewrites as the - # real gateway config's asset_graph_service locations, just proxying to - # our own internal port instead of a separate service/Space. - location /asset_graph/usd/scene_summary/ { - if ($request_method = OPTIONS) { add_header Content-Length 0 always; add_header Content-Type "text/plain" always; return 204; } - rewrite ^/asset_graph/usd/scene_summary/(.*)$ /asset_graph/usd/scene_summary/$1 break; - proxy_pass http://127.0.0.1:8010; - } - - location /asset_graph/usd/scene_summary { - if ($request_method = OPTIONS) { add_header Content-Length 0 always; add_header Content-Type "text/plain" always; return 204; } - rewrite ^/asset_graph/usd/scene_summary(.*)$ /asset_graph/usd/scene_summary/$1 break; - proxy_pass http://127.0.0.1:8010; - } - - location /asset_graph/usd/prims { - if ($request_method = OPTIONS) { add_header Content-Length 0 always; add_header Content-Type "text/plain" always; return 204; } - rewrite ^/asset_graph/usd/prims(.*)$ /asset_graph/usd/prims$1 break; - proxy_pass http://127.0.0.1:8010; - } - - location /dependency_graph { - if ($request_method = OPTIONS) { add_header Content-Length 0 always; add_header Content-Type "text/plain" always; return 204; } - rewrite ^/dependency_graph(.*)$ /dependency_graph$1 break; - proxy_pass http://127.0.0.1:8010; - } - - # deepsearch-api's own root-level routes — explicit now that `location /` - # below serves the Explorer SPA instead of proxying everything - # unmatched. /docs and /redoc are prefix matches, not exact: Swagger - # UI/ReDoc can request sub-resources under their own path. - location = /health { proxy_pass http://127.0.0.1:8000; } - location = /openapi.json { proxy_pass http://127.0.0.1:8000; } - # Prometheus /metrics stays private to the container (127.0.0.1:8000 - # directly): an explicit JSON 404 rather than no location, which would - # fall through to the SPA's index.html with a 200. - location = /metrics { - default_type application/json; - return 404 '{"detail":"Not Found"}'; - } - location /docs { proxy_pass http://127.0.0.1:8000; } - location /redoc { proxy_pass http://127.0.0.1:8000; } - - # The Explorer SPA (see header comment) at root: try_files serves any of - # its own literal files (index.html, ds.png, favicon.ico, manifest.json, - # ...) directly, and falls back to index.html for anything else - # (client-side routing) — anything actually meant for the API above was - # already matched by a more specific location before nginx gets here. - # >>> simready-central slim explorer >>> - # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. - # SPDX-License-Identifier: Apache-2.0 - # services/explorer/dist is SimReady Central's search-only Explorer (repo simready-central, - # branch slim, slim/devspace/). The locations here are additive — exact matches, longer - # prefixes or a regex — and none of his locations is redefined. This block carries: /config.js, - # a no-cache /index.html, a 10-min - # proxy cache on /search/stats/usd_properties, a shared proxy cache on /images, - # /wikidata_hierarchy.json, /library_media/ and JSON 404s for the routes this build does not have. What DOES - # reach beyond it: client_max_body_size 32m is server-wide (it applies to ALL his locations - # too); `= /images` takes the requests his `/images` prefix served before (same rewrite + - # proxy_pass, plus the cache); and an - # add_header inside one of our locations replaces his server-level headers for THOSE responses - # — /images and the stats cache re-declare his three CORS lines, /config.js and /index.html - # (same-origin UI files) do not. Applied by slim/devspace/deploy.sh. - client_max_body_size 32m; # query-image searches POST a base64 JPEG (nginx default 1 MB) - gzip_types text/css application/javascript application/json image/svg+xml; # stock gzip covers text/html only - - # Runtime index registry (blocking -// -// -// Attributes (and properties): src, textures (`none` | `preview` | `full`), -// max-texture-size (maxTextureSize), alt, touch-action (touchAction), -// loading (`lazy` | `eager`), poster, reveal (`auto` | `interaction`). -// Events: progress (detail: LoadProgress), load (detail: LoadInfo; the full -// result is `el.result`), error (an ErrorEvent whose `.error` is a UsdLoadError), -// context-lost (the model then loads again once the context is restored). -// -// Safe to import where there is no DOM (server rendering): the element is -// only defined in a browser. -import { createViewer, UsdLoadError } from './index.js'; -import { DEFAULTS } from './load-core.js'; - -const Base = globalThis.HTMLElement ?? class {}; -const ATTRIBUTES = { - src: 'src', - textures: 'textures', - maxTextureSize: 'max-texture-size', - alt: 'alt', - touchAction: 'touch-action', - loading: 'loading', - poster: 'poster', - reveal: 'reveal', -}; -// Property values of attributes that are missing or not one of their values. -const READ = { - textures: (value) => (['none', 'preview', 'full'].includes(value) ? value : DEFAULTS.textures), - maxTextureSize: (value) => Number(value) || DEFAULTS.maxTextureSize, - loading: (value) => (value === 'eager' ? 'eager' : 'lazy'), - reveal: (value) => (value === 'interaction' ? 'interaction' : 'auto'), -}; -// A lazy element starts once it is this close to the viewport, and releases -// its viewer (and WebGL context) once this far, so a long page of them keeps -// only those around the screen live. -const NEAR = '50%'; -const FAR = '200%'; - -const STYLE = ` -:host{display:block;position:relative;width:100%;height:100%;min-height:200px} -div,img,button{position:absolute;inset:0;width:100%;height:100%;box-sizing:border-box} -canvas{outline:none} -canvas:focus-visible,button:focus-visible{outline:2px solid;outline-offset:-2px} -img{object-fit:contain;pointer-events:none;transition:opacity .3s} -img.hidden{opacity:0} -button{border:0;padding:0;background:none;cursor:pointer;font:inherit;color:inherit} -span{display:inline-block;padding:.5em 1em;border-radius:2em;background:#000a;color:#fff}`; - -export class UsdViewerElement extends Base { - static observedAttributes = Object.values(ATTRIBUTES); - static #nearObserver = null; - static #farObserver = null; - - #viewer = null; - #result = null; - #abort = null; - #parts = null; - // Lazy: within NEAR of the viewport. Interaction: the reveal button was used. - #near = false; - #revealed = false; - #broken = false; - - /** The underlying viewer (renderer, scene, camera, controls) while one is live. */ - get viewer() { - return this.#viewer; - } - - /** The current load result (root, info, complete, dispose), once geometry shows. */ - get result() { - return this.#result; - } - - /** A fresh frame as an image (see `Viewer.toBlob`); rejects while there is no viewer. */ - toBlob(options) { - return this.#viewer - ? this.#viewer.toBlob(options) - : Promise.reject(new Error('no viewer to capture: the element has not started (see loading and reveal)')); - } - - connectedCallback() { - if (!this.#parts) this.#render(); - UsdViewerElement.#nearObserver ??= new IntersectionObserver((entries) => entries.forEach((e) => e.isIntersecting && e.target.#setNear(true)), { - rootMargin: NEAR, - }); - UsdViewerElement.#farObserver ??= new IntersectionObserver((entries) => entries.forEach((e) => e.isIntersecting || e.target.#setNear(false)), { - rootMargin: FAR, - }); - UsdViewerElement.#nearObserver.observe(this); - UsdViewerElement.#farObserver.observe(this); - this.#update(); - } - - disconnectedCallback() { - // Deferred so a move (disconnect then reconnect in one task) keeps the viewer. - queueMicrotask(() => { - if (this.isConnected) return; - UsdViewerElement.#nearObserver.unobserve(this); - UsdViewerElement.#farObserver.unobserve(this); - this.#near = false; - this.#broken = false; - this.#update(); - }); - } - - attributeChangedCallback(name, previous, value) { - if (!this.#parts || previous === value) return; - if (name === 'alt' || name === 'touch-action' || name === 'poster') return this.#decorate(); - const live = !!this.#viewer; - this.#update(); - if (live && this.#viewer && ['src', 'textures', 'max-texture-size'].includes(name)) this.#load(); - } - - #render() { - const root = this.shadowRoot ?? this.attachShadow({ mode: 'open' }); - root.innerHTML = `
`; - const [viewer, poster, reveal] = root.querySelectorAll('div, img, button'); - this.#parts = { viewer, poster, reveal }; - reveal.addEventListener('click', () => { - this.#revealed = true; - this.#update(); - this.#viewer?.renderer.domElement.focus(); - }); - this.#decorate(); - } - - #setNear(near) { - this.#near = near; - this.#update(); - } - - /** Starts or releases the viewer, per the attributes and where the element is. */ - #update() { - const interaction = this.reveal === 'interaction' && !this.#revealed; - const live = this.isConnected && !!this.getAttribute('src') && (this.loading === 'eager' || this.#near) && !interaction; - if (live && !this.#viewer && !this.#broken) this.#start(); - if (!live && this.#viewer) this.#release(); - this.#parts.reveal.hidden = !interaction; - } - - #start() { - try { - this.#viewer = createViewer(this.#parts.viewer, { - onContextLost: () => { - this.#result = null; - this.#showPoster(true); - this.dispatchEvent(new Event('context-lost')); - }, - onContextRestored: (reload) => reload && this.#track(reload, this.#abort), - }); - } catch (error) { - this.#broken = true; - this.#fail(error instanceof UsdLoadError ? error : new UsdLoadError('webgl', String(error?.message || error), { cause: error })); - return; - } - this.#decorate(); - this.#load(); - } - - #release() { - this.#abort?.abort(); - this.removeAttribute('aria-busy'); - this.#viewer.dispose(); - this.#viewer = null; - this.#result = null; - this.#showPoster(true); - this.#decorate(); - } - - #decorate() { - const alt = this.getAttribute('alt') || '3D model'; - const { poster, reveal } = this.#parts; - const src = this.getAttribute('poster'); - if (src) poster.src = src; - poster.hidden = !src; - // The poster speaks for the model only until there is a canvas to. - poster.alt = this.#viewer ? '' : alt; - reveal.setAttribute('aria-label', `View in 3D: ${alt}`); - if (!this.#viewer) return; - // On the canvas, which takes focus and the keys; not on the host, whose - // children an `img` role would hide. - const canvas = this.#viewer.renderer.domElement; - canvas.setAttribute('role', 'img'); - canvas.setAttribute('aria-label', alt); - // OrbitControls sets `none`; `pan-y` lets the page scroll on touch screens. - canvas.style.touchAction = this.getAttribute('touch-action') || 'pan-y'; - } - - #showPoster(shown) { - this.#parts.poster.classList.toggle('hidden', !shown); - } - - #load() { - this.#abort?.abort(); - const abort = (this.#abort = new AbortController()); - const loading = this.#viewer.load(this.getAttribute('src'), { - textures: this.textures, - maxTextureSize: this.maxTextureSize, - signal: abort.signal, - onProgress: (detail) => { - if (detail.stage === 'geometry' && detail.loaded === 1) this.#fadePoster(abort); - this.dispatchEvent(new CustomEvent('progress', { detail })); - }, - }); - this.#track(loading, abort); - } - - /** Follows a load of the viewer's (the element's own, or one after a restored context). */ - async #track(loading, abort) { - this.setAttribute('aria-busy', 'true'); - this.#showPoster(true); - try { - const result = await loading; - this.#result = result; - this.removeAttribute('aria-busy'); - this.#fadePoster(abort); - this.dispatchEvent(new CustomEvent('load', { detail: result.info })); - } catch (error) { - if (this.#abort === abort) this.removeAttribute('aria-busy'); - if (error?.code !== 'aborted') this.#fail(error); - } - } - - /** Fades the poster out after the next frame, which draws the geometry. */ - #fadePoster(abort) { - requestAnimationFrame(() => abort.signal.aborted || this.#showPoster(false)); - } - - #fail(error) { - this.dispatchEvent(new ErrorEvent('error', { error, message: error.message })); - } -} - -// Properties reflecting their attributes. -for (const [property, attribute] of Object.entries(ATTRIBUTES)) { - Object.defineProperty(UsdViewerElement.prototype, property, { - get() { - const value = this.getAttribute(attribute); - return READ[property] ? READ[property](value) : (value ?? ''); - }, - set(value) { - if (value == null || value === '') this.removeAttribute(attribute); - else this.setAttribute(attribute, String(value)); - }, - }); -} - -if (globalThis.customElements && !customElements.get('usd-viewer')) customElements.define('usd-viewer', UsdViewerElement); diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/element.js.gz b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/element.js.gz deleted file mode 100644 index 4b24853353bf674de5e1672fc4b467374307da4e..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/element.js.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:84a3baa62843ed2f5f2391a2878f54a2a79a6b73e82a6e3505659149c58a00eb -size 3477 diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/errors.js b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/errors.js deleted file mode 100644 index 6283e415ced0c9aa2948269e1e4e5d0cf57fea72..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/errors.js +++ /dev/null @@ -1,15 +0,0 @@ -/** Why a load failed: `aborted`, `fetch` (root layer or a resource limit), `compose`, `worker` or `webgl`. */ -export class UsdLoadError extends Error { - /** - * @param {'aborted' | 'fetch' | 'compose' | 'worker' | 'webgl'} code - * @param {string} message - * @param {{ url?: string, status?: number, cause?: unknown }} [details] - */ - constructor(code, message, { url, status, cause } = {}) { - super(message, cause === undefined ? undefined : { cause }); - this.name = 'UsdLoadError'; - this.code = code; - if (url !== undefined) this.url = url; - if (status !== undefined) this.status = status; - } -} diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/errors.js.gz b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/errors.js.gz deleted file mode 100644 index 218540dff3e054904bc81c6303a0515096f8da1e..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/errors.js.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7aba312f20935bfae478ad825ec77c370f9833cd680798312a641dfdd0360135 -size 347 diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/hub-prefetch.js b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/hub-prefetch.js deleted file mode 100644 index 48a794bcffb9f45abbd5ac832ad5aa95cf1f79fd..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/hub-prefetch.js +++ /dev/null @@ -1,86 +0,0 @@ -// The layers of a Hub package, listed with one tree-API call, so a load can -// request them all at once instead of discovering them a round trip at a time. - -const RESOLVE = /^(https:\/\/(?:huggingface\.co|hf\.co))\/((?:datasets\/|spaces\/)?)([^/?#]+\/[^/?#]+)\/resolve\/([^/?#]+)\/([^?#]+)$/; -const LAYER = /\.(usd|usda|usdc)$/i; -const SIMREADY_PACKAGE = 'com.nvidia.simready.packaging.json'; -const MAX_FILES = 256; -const MAX_BYTES = 64 * 2 ** 20; - -/** - * The USD layers of the package holding a Hub `resolve` URL, as `{ url, size }`: - * the directory with a SimReady packaging manifest among the root's parent - * and grandparent, else everything under the root's own directory. `eager` - * when a manifest declares the package, so its layers are worth requesting - * before the root is read. No layers, without a request, for a root at the - * top of its repo or any other URL, and none if the listing fails: the load then discovers layers as usual. - * - * @param {string} rootUrl - * @param {(url: string) => Promise} request the load's fetch, with its headers - * @returns {Promise<{ layers: { url: string, size: number }[], eager: boolean }>} - */ -export async function hubPackageLayers(rootUrl, request) { - const match = rootUrl.split(/[?#]/)[0].match(RESOLVE); - const none = { layers: [], eager: false }; - if (!match) return none; - const [, origin, prefix, repo, revision, path] = match; - const parent = path.split('/').slice(0, -1); - // A root at the top of a repo would list, and preload, the whole repo. - if (!parent.length) return none; - const listed = parent.length > 1 ? parent.slice(0, -1) : parent; - try { - const response = await request(`${origin}/api/${prefix || 'models/'}${repo}/tree/${revision}/${listed.join('/')}?recursive=true`); - if (!response.ok) return none; - const entries = await response.json(); - const decoded = parent.map(decodeURIComponent); - const manifest = [decoded, decoded.slice(0, -1)].find( - (dir) => dir.length >= listed.length && entries.some((e) => e.path === [...dir, SIMREADY_PACKAGE].join('/')), - ); - const depth = (manifest ?? decoded).length; - const packageDir = decoded - .slice(0, depth) - .map((name) => name + '/') - .join(''); - // As the root spells it, so URLs match the ones composition resolves. - const base = `${origin}/${prefix}${repo}/resolve/${revision}/${parent - .slice(0, depth) - .map((name) => name + '/') - .join('')}`; - const layers = []; - let bytes = 0; - for (const { type, path: file, size } of entries) { - if (type !== 'file' || !file.startsWith(packageDir) || !LAYER.test(file)) continue; - if (layers.length >= MAX_FILES || bytes + size > MAX_BYTES) continue; - bytes += size; - layers.push({ url: new URL(file.slice(packageDir.length).replace(/[%#?]/g, encodeURIComponent), base).href, size }); - } - return { layers, eager: !!manifest }; - } catch { - return none; - } -} - -/** - * A Hub `resolve` URL pinned to the commit its branch or tag points at now, - * so every layer and texture of one load (all resolved against the root) - * comes from the same commit even if the repo is pushed to meanwhile. Unchanged, - * without a request, for a commit or any other URL, and if the lookup fails. - * - * @param {string} rootUrl - * @param {(url: string) => Promise} request the load's fetch, with its headers - * @returns {Promise} - */ -export async function pinHubRevision(rootUrl, request) { - const match = rootUrl.split(/[?#]/)[0].match(RESOLVE); - if (!match) return rootUrl; - const [, origin, prefix, repo, revision] = match; - if (/^[0-9a-f]{40}$/.test(revision)) return rootUrl; - try { - const response = await request(`${origin}/api/${prefix || 'models/'}${repo}/revision/${revision}?expand%5B%5D=sha`); - const { sha } = response.ok ? await response.json() : {}; - if (!/^[0-9a-f]{40}$/.test(sha ?? '')) return rootUrl; - return rootUrl.replace(`/resolve/${revision}/`, `/resolve/${sha}/`); - } catch { - return rootUrl; - } -} diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/hub-prefetch.js.gz b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/hub-prefetch.js.gz deleted file mode 100644 index e973c71c8fc6a22ba1a6385f480b634a666dfc13..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/hub-prefetch.js.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1a65c758094e78f7acb876433cf70bf982f9290a20c82e79e5ad976da6520452 -size 1666 diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/hub.d.ts b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/hub.d.ts deleted file mode 100644 index ba48b0108e4f8fbad7dc1a6de40ebc34b8b0d95a..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/hub.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** The URL serving `path` from a Hugging Face Hub repo. Pass a commit sha as `revision` for reproducible multi-layer loads. */ -export function hubUrl( - repo: string, - path: string, - options?: { revision?: string | undefined; repoType?: 'dataset' | 'model' | 'space' | undefined; endpoint?: string | undefined }, -): string; diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/hub.js b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/hub.js deleted file mode 100644 index 28747ec8e3d5b8dd269e70cac841bc99127c8040..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/hub.js +++ /dev/null @@ -1,17 +0,0 @@ -// Hugging Face Hub file URLs: `import { hubUrl } from 'usd-web-viewer/hub'`. - -const PREFIX = { dataset: 'datasets/', model: '', space: 'spaces/' }; - -/** - * The URL that serves `path` from a Hub repo (it redirects to the CDN). Pass - * a commit sha as `revision` so every layer of a multi-layer stage comes from - * the same commit. - * @param {string} repo e.g. `nvidia/simready-assets` - * @param {string} path file path inside the repo - * @param {{ revision?: string, repoType?: 'dataset' | 'model' | 'space', endpoint?: string }} [options] - */ -export function hubUrl(repo, path, { revision = 'main', repoType = 'dataset', endpoint = 'https://huggingface.co' } = {}) { - if (!(repoType in PREFIX)) throw new TypeError(`repoType must be dataset, model or space, not ${repoType}`); - const encode = (p) => p.split('/').map(encodeURIComponent).join('/'); - return `${endpoint.replace(/\/+$/, '')}/${PREFIX[repoType]}${repo}/resolve/${encodeURIComponent(revision)}/${encode(path)}`; -} diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/hub.js.gz b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/hub.js.gz deleted file mode 100644 index ccd4f6588f2238a913fc4c71996e788f1b9f07db..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/hub.js.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:00e0fefda185ee2f817840020350ee3fbf623b34944d61996fca8ae39d9cef23 -size 575 diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/index.d.ts b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/index.d.ts deleted file mode 100644 index 3d1c36f8f0e030c5688acf3e741afb4cb39d2785..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/index.d.ts +++ /dev/null @@ -1,208 +0,0 @@ -import type * as THREE from 'three'; -import type { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - -/** `preview` (default): base color up to `maxTextureSize`, roughness / metallic / occlusion / opacity / emissive maps up to 512 px, no normal maps. `full`: every map, normals included, up to `maxTextureSize`. `none`: no textures. */ -export type TextureMode = 'none' | 'preview' | 'full'; - -export type LoadProgress = ( - | { readonly stage: 'layers'; readonly loaded: number; /** Grows while layer dependencies are discovered. */ readonly total: number; readonly bytes: number } - | { readonly stage: 'compose'; readonly round: number } - /** Meshes streaming out of the worker; the load resolves once `loaded === total`. */ - | { readonly stage: 'geometry'; readonly loaded: number; readonly total: number } - | { readonly stage: 'textures'; readonly loaded: number; readonly total: number; readonly bytes: number } -) & { - /** - * The whole load, 0 to 1, for a progress bar; it never decreases. Each stage spans a share: `layers` 0–0.4 (`loaded / (total + 1)`, - * as more layers may yet be found), `compose` 0.45, `geometry` 0.5–0.8 and `textures` 0.8–1 (`loaded / total`). It reaches 1 when the - * last texture settles, or with the `textures` stage when there are none. - */ - readonly fraction: number; -}; - -export interface LoadOptions { - /** Default `preview`. */ - textures?: TextureMode | undefined; - /** Long-side cap for decoded textures, in pixels. Default 1024. */ - maxTextureSize?: number | undefined; - /** Aborts fetches and the worker; the load rejects with a UsdLoadError of code `aborted`. */ - signal?: AbortSignal | undefined; - /** - * Sent only with the requests that carry credentials: those to the root URL's origin or, for a root on the Hugging Face Hub, to the root's own repo. - * For embedding on another site, e.g. `{ Authorization: 'Bearer hf_…' }`. Never sent through a redirect: off the Hub (whose redirects stay on - * its own hosts), a request that redirects is fetched again without headers or cookies, for the browser to follow. - */ - headers?: Record | undefined; - /** - * Your own fetch for every request the request policy allows (see `allowedOrigins`), run on the page (requests are proxied from the worker). - * `init.headers` is `headers` where credentials may go and absent elsewhere, with `init.redirect` `manual` when it is set off the Hub (a - * redirect, opaque or 3xx, is then fetched again without them); `init.credentials` and `init.referrerPolicy` are what the built-in fetch would - * use; `init.signal` aborts when the load stops, and bodies still open are cancelled then. - */ - fetch?: - | (( - url: string, - init: { - headers?: Record; - credentials: 'same-origin' | 'omit'; - referrerPolicy: 'no-referrer'; - redirect?: 'manual'; - signal: AbortSignal; - }, - ) => Promise) - | undefined; - onProgress?: ((progress: LoadProgress) => void) | undefined; - /** Where the `.wasm` binary is served from. Defaults to the copy next to the package. */ - wasmUrl?: string | URL | undefined; - /** Where the worker script is served from. Defaults to the copy next to the package. */ - workerUrl?: string | URL | undefined; - /** Requests in flight at once, a positive integer (anything else throws a RangeError). Default 16 (textures: at most 4 fetched and decoded at once). */ - maxConcurrentFetches?: number | undefined; - /** Total bytes of USD layers to fetch before failing with a `fetch` error. Default 768 MiB. */ - maxLayerBytes?: number | undefined; - /** Layer files to request before failing with a `fetch` error. Default 1024. Layers read from inside a `.usdz` package are not requested, so not counted: the package counts once, its contents are bounded by `maxLayerBytes` and the package expansion limits (512 MiB per file, 1 GiB in all). */ - maxLayers?: number | undefined; - /** - * Origins, besides the root URL's, that layers and textures may be fetched from, e.g. `['https://cdn.example.com']`; `['*']` allows any. - * A root on the Hugging Face Hub also allows the Hub's hosts and CDNs. Every request, a custom `fetch`'s too, also follows these rules: - * only http(s) URLs without user names; hf.co URLs are fetched (and checked) as the huggingface.co URLs they redirect to; on huggingface.co, - * only repo files (`…/resolve/…`) and tree listings under a user or organization, with no encoded slash outside the revision (a pull - * request's `refs%2Fpr%2F1`); cookies and `headers` only for the root's origin or, for a Hub root, the root's own repo; no referrer. Only the - * requested URL is checked, not where it redirects (`headers` never follow a redirect off the Hub). Anything refused is skipped with a - * `layer-missing` or `texture-failed` warning. - */ - allowedOrigins?: readonly string[] | undefined; - /** Triangles drawn across all meshes, counted once per instance; meshes past it are left out unread with a `triangle-limit` warning. Default 20 million. */ - maxTriangles?: number | undefined; - /** Mesh instances drawn, PointInstancer placements (nested ones expanded) included; the rest are left out with an `instance-limit` warning. Default 100,000. Many instances of one mesh with the same materials draw as one `THREE.InstancedMesh`. */ - maxInstances?: number | undefined; - /** Total bytes of texture files, read from packages or fetched (counted as they download); textures past it fail with a `texture-failed` warning. Default 512 MiB. Only PNG, JPEG and WebP up to 16384 px a side are decoded; others are refused from their header. */ - maxTextureBytes?: number | undefined; -} - -/** - * - `prim-unsupported`: visible geometry other than meshes and the implicit `Cube` / `Sphere` / `Cylinder` / `Cone` / `Capsule` / `Plane` (e.g. `BasisCurves`, `Points`, `Volume`, Gaussian splats) left out. - * - `nothing-drawable`: no visible mesh had anything to draw. - * - `layer-missing`: a layer could not be fetched, or the request policy refused it (see `allowedOrigins`). - * - `layer-unreadable`: a layer was fetched but could not be parsed; composition goes on without it. - * - `material-fallback`: materials with neither UsdPreviewSurface nor a supported MDL material (OmniPBR, glTF), e.g. MaterialX only or custom MDL modules, shown grey. - * - `composition`: what composition reported, e.g. an unresolved reference (the first 5, then a count). - * - `texture-failed`: an image could not be fetched or read, or was refused (by the request policy, past `maxTextureBytes`, larger than 16384 px a side, or not PNG, JPEG or WebP); its inputs show their own (authored or default) values. - * - `triangle-limit`: meshes left out past `maxTriangles`. - * - `instance-limit`: instances left out past `maxInstances`. - */ -export type WarningCode = - | 'layer-missing' - | 'layer-unreadable' - | 'prim-unsupported' - | 'nothing-drawable' - | 'material-fallback' - | 'composition' - | 'texture-failed' - | 'triangle-limit' - | 'instance-limit'; - -export interface LoadWarning { - readonly code: WarningCode; - readonly message: string; - /** The layer URL, texture path or an example prim / material path. */ - readonly path?: string | undefined; -} - -export type MaterialKind = 'preview' | 'omnipbr' | 'gltf-pbr' | 'displayColor' | 'fallback'; - -export interface LoadStats { - readonly layers: number; - readonly layerBytes: number; - readonly missing: number; - readonly rounds: number; - readonly fetchMs: number; - readonly parseMs: number; - readonly composeMs: number; - readonly initMs: number; - readonly totalMs: number; - readonly wasmMemoryBytes: number; -} - -export interface LoadInfo { - /** As authored, like Pixar's `UsdGeomGetStageUpAxis`: `Y` (default) or `Z`; anything else is drawn as `Y`. */ - readonly upAxis: string; - readonly metersPerUnit: number; - readonly meshes: number; - readonly geometries: number; - readonly triangles: number; - readonly materials: number; - readonly materialKinds: Readonly>>; - readonly stats: LoadStats; - /** What could not be shown faithfully. Grows (texture failures) until `complete` settles. */ - readonly warnings: readonly LoadWarning[]; -} - -export interface LoadResult { - /** Y-up, in meters. */ - readonly root: THREE.Group; - readonly info: LoadInfo; - /** Settles when textures have streamed in; rejects with a UsdLoadError if the load is aborted, disposed or the worker dies. */ - readonly complete: Promise<{ textures: number; failed: number }>; - /** Frees geometries, materials and textures and stops any textures still streaming. */ - dispose(): void; -} - -/** A `compose` error whose message starts with `scene too large to load` ran out of WebAssembly memory (4 GiB at most). */ -export type UsdLoadErrorCode = 'aborted' | 'fetch' | 'compose' | 'worker' | 'webgl'; - -export class UsdLoadError extends Error { - constructor(code: UsdLoadErrorCode, message: string, details?: { url?: string | undefined; status?: number | undefined; cause?: unknown }); - readonly name: 'UsdLoadError'; - readonly code: UsdLoadErrorCode; - /** The URL that failed, when there is one. */ - readonly url?: string | undefined; - /** HTTP status of a failed root layer fetch: 401 / 403 for gated or private repos, 404 when missing. */ - readonly status?: number | undefined; -} - -/** Rejects with a UsdLoadError of code `worker` where there are no Web Workers (e.g. on a server). */ -export function loadUsd(url: string, options?: LoadOptions): Promise; - -export interface ViewerOptions { - /** Canvas background. Default: transparent. */ - background?: THREE.ColorRepresentation | undefined; - /** The WebGL context was lost: the stage is gone (its geometry lived only on the GPU) and any load in flight aborted. */ - onContextLost?: (() => void) | undefined; - /** The context is back: `reload` loads the latest URL again with the same options (null if nothing was loaded). */ - onContextRestored?: ((reload: Promise | null) => void) | undefined; -} - -export interface ToBlobOptions { - type?: 'image/png' | 'image/webp' | undefined; - quality?: number | undefined; - width?: number | undefined; - height?: number | undefined; -} - -export interface Viewer { - readonly renderer: THREE.WebGLRenderer; - readonly scene: THREE.Scene; - readonly camera: THREE.PerspectiveCamera; - readonly controls: OrbitControls; - /** Loads a stage, replacing the current one once its geometry shows. A newer load aborts this one. */ - load(url: string, options?: LoadOptions): Promise; - /** - * Renders a fresh frame and encodes it, e.g. for a thumbnail: `type` `image/png` (default) or `image/webp` (PNG where the browser - * cannot encode WebP, as Safari), `quality` 0–1 for WebP, and an optional size in pixels (giving one keeps the aspect ratio; - * default: the canvas's). Rejects when the viewer is disposed or its WebGL context is lost. - */ - toBlob(options?: ToBlobOptions): Promise; - /** Removes and frees the current stage, and aborts any load in flight. */ - clear(): void; - /** Points the camera at `object`, by default the current stage. */ - frame(object?: THREE.Object3D): void; - requestRender(): void; - /** Frees the renderer, the GPU context, the current stage and any load in flight. Safe to call twice. */ - dispose(): void; -} - -/** Throws a UsdLoadError of code `webgl` when WebGL is unavailable. */ -export function createViewer(target: HTMLElement | HTMLCanvasElement, options?: ViewerOptions): Viewer; - -/** Points the camera at the visible bounds of `object`. */ -export function frame(camera: THREE.PerspectiveCamera, controls: OrbitControls | undefined, object: THREE.Object3D): void; diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/index.js b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/index.js deleted file mode 100644 index 8f6c5df5e800815246016581a24414e0dbb92dd1..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/index.js +++ /dev/null @@ -1,661 +0,0 @@ -import * as THREE from 'three'; -import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; -import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js'; -import { UsdLoadError } from './errors.js'; -import { OUT_OF_MEMORY, canonicalUrl, fetchWithPolicy, requestPolicy } from './load-core.js'; -import { applyFallback, attachTexture, configureTexture, createMaterial, variant } from './materials.js'; - -export { UsdLoadError }; - -// Lets the viewer show meshes as they stream in, before the load resolves. -const SHOW = Symbol('show'); - -// Instances of one geometry with the same materials drawn as one -// InstancedMesh past this many. -const INSTANCING_MIN = 4; - -// The share of the overall `fraction` each stage spans (see LoadProgress). -const STAGES = { layers: [0, 0.4], compose: [0.4, 0.5], geometry: [0.5, 0.8], textures: [0.8, 1] }; - -/** Where `progress` puts the load overall, before taking the maximum so far. */ -function stageFraction(progress) { - const [start, end] = STAGES[progress.stage]; - // More layers may yet be found; a compose round is halfway into its stage. - const done = - progress.stage === 'layers' - ? progress.loaded / (progress.total + 1) - : progress.stage === 'compose' - ? 0.5 - : progress.total - ? progress.loaded / progress.total - : 1; - return start + (end - start) * done; -} - -let wasmModule = null; - -/** Compiles the WASM module once per page; workers instantiate it without refetching. */ -function compileWasm(url) { - // Written inline so bundlers (Vite, webpack) emit the asset and rewrite the URL. - url ??= new URL('../wasm/usd_wasm_bg.wasm', import.meta.url); - wasmModule ||= compile(url).catch((error) => { - wasmModule = null; - throw new UsdLoadError('worker', `could not load the WASM module from ${url}: ${error.message}`, { url: String(url), cause: error }); - }); - return wasmModule; -} - -async function compile(url) { - const response = await fetch(url); - if (!response.ok) throw new Error(`HTTP ${response.status}`); - // Servers that do not send `application/wasm` break streaming compilation. - if (response.headers.get('content-type')?.startsWith('application/wasm')) return WebAssembly.compileStreaming(response); - return WebAssembly.compile(await response.arrayBuffer()); -} - -/** - * Loads a USD stage into a three.js group. Resolves once geometry is ready; - * `complete` settles when every texture has streamed in. - * - * @param {string} url root layer URL (relative URLs resolve against the page) - * @param {import('./index.js').LoadOptions} [options] - * @returns {Promise} - */ -export async function loadUsd(url, options = {}) { - const { maxTextureSize, textures, maxConcurrentFetches, maxLayerBytes, maxTextureBytes, maxLayers, maxInstances, maxTriangles, allowedOrigins } = options; - const { onProgress, signal, headers } = options; - if (maxConcurrentFetches !== undefined && !(Number.isInteger(maxConcurrentFetches) && maxConcurrentFetches > 0)) { - throw new RangeError(`maxConcurrentFetches must be a positive integer, got ${maxConcurrentFetches}`); - } - const aborted = () => new UsdLoadError('aborted', 'the load was aborted', { url, cause: signal?.reason }); - if (signal?.aborted) throw aborted(); - if (typeof Worker === 'undefined') throw new UsdLoadError('worker', 'loading needs Web Workers: call loadUsd in a browser', { url }); - const absoluteUrl = canonicalUrl(new URL(url, location.href).href); - // The compile is shared with other loads: aborting stops waiting for it, not it. - const module = await new Promise((resolve, reject) => { - const abortWait = () => reject(aborted()); - signal?.addEventListener('abort', abortWait, { once: true }); - compileWasm(options.wasmUrl) - .then(resolve, reject) - .finally(() => signal?.removeEventListener('abort', abortWait)); - }); - if (signal?.aborted) throw aborted(); - let worker; - try { - // Inline `new Worker(new URL(...))` is the pattern bundlers recognise and bundle. - worker = options.workerUrl ? new Worker(options.workerUrl, { type: 'module' }) : new Worker(new URL('./worker.js', import.meta.url), { type: 'module' }); - } catch (error) { - throw new UsdLoadError('worker', `could not start the worker: ${error.message}`, { url: options.workerUrl && String(options.workerUrl), cause: error }); - } - // Ends the caller's fetches (custom `fetch` requests) when the load stops, - // aborted or not. - const requests = new AbortController(); - // Bodies of custom-fetch responses the worker is reading, by request id. - const bodies = new Map(); - - let resolveScene, rejectScene, resolveComplete, rejectComplete; - const scenePromise = new Promise((resolve, reject) => ((resolveScene = resolve), (rejectScene = reject))); - const complete = new Promise((resolve, reject) => ((resolveComplete = resolve), (rejectComplete = reject))); - // A rejection nobody awaits (e.g. after dispose) must not surface as unhandled. - complete.catch(() => {}); - const counts = { textures: 0, failed: 0 }; - let fraction = 0; - let built = null; - let delivered = false; - let stopped = false; - // The one way out (done, error, dispose, abort): stop the worker, which - // frees its WASM memory, and settle both promises (a no-op if settled). - const stop = (error) => { - if (stopped) return; - stopped = true; - worker.terminate(); - requests.abort(); - // A custom fetch may ignore the signal once its body is returned. - for (const reader of bodies.values()) reader.cancel().catch(() => {}); - bodies.clear(); - signal?.removeEventListener('abort', abort); - rejectScene(error); - if (error) rejectComplete(error); - else resolveComplete({ ...counts }); - }; - function fail(error) { - // Before geometry resolves nothing has reached the caller: free it here. - if (built && !delivered) built.dispose(); - stop(error); - } - function abort() { - fail(aborted()); - } - signal?.addEventListener('abort', abort, { once: true }); - - worker.onmessage = ({ data }) => { - // Messages already queued when the load was stopped (disposed or aborted). - if (stopped) { - data.bitmap?.close(); - return; - } - try { - receive(data); - } catch (error) { - data.bitmap?.close(); - fail(new UsdLoadError('worker', `could not build the scene: ${error?.message || error}`, { url: absoluteUrl, cause: error })); - } - }; - function receive(data) { - switch (data.type) { - case 'fetch': - proxyFetch(data); - break; - case 'pull': - pullChunk(data.id); - break; - case 'cancel': - bodies.get(data.id)?.cancel(); - bodies.delete(data.id); - break; - case 'progress': - fraction = Math.max(fraction, stageFraction(data.progress)); - onProgress?.({ ...data.progress, fraction }); - break; - case 'meta': - if (signal?.aborted) return abort(); - built = buildScene(data.meta); - options[SHOW]?.(built.root, data.meta); - break; - case 'geometry': - built.addGeometry(data.index, data.meta, data.arrays); - break; - case 'scene': { - const { warnings, ...stats } = data.stats; - built.info.stats = stats; - built.info.warnings.push(...warnings); - delivered = true; - resolveScene(built); - break; - } - case 'texture': - if (data.bitmap) { - built.applyTexture(data.path, data.bitmap, data.color); - counts.textures++; - } else { - built.textureFailed(data.path); - counts.failed++; - built.info.warnings.push({ code: 'texture-failed', message: data.error, path: data.path }); - } - break; - case 'done': - stop(); - break; - case 'error': - fail(new UsdLoadError(data.code, data.message, { url: data.url, status: data.status })); - break; - } - } - worker.onerror = (event) => { - event.preventDefault?.(); - const message = event.message || 'the worker failed'; - // The worker itself ran out of memory, outside the handler that reports it. - if (OUT_OF_MEMORY.test(message)) fail(new UsdLoadError('compose', `scene too large to load: ran out of memory: ${message}`, { url: absoluteUrl })); - else fail(new UsdLoadError('worker', message, { url: absoluteUrl })); - }; - worker.postMessage({ - url: absoluteUrl, - wasmModule: module, - maxTextureSize, - textures, - maxConcurrentFetches, - maxLayerBytes, - maxTextureBytes, - maxLayers, - maxInstances, - maxTriangles, - allowedOrigins, - headers, - proxyFetch: !!options.fetch, - }); - - /** Runs one worker request through the caller's `fetch`; the body follows chunk by chunk. */ - async function proxyFetch({ id, url: target }) { - try { - const policy = requestPolicy(target, absoluteUrl, allowedOrigins); - if (policy.refused) throw new TypeError(`request refused: ${policy.refused}`); - const response = await fetchWithPolicy(options.fetch, target, policy, headers, { signal: requests.signal }); - if (stopped) return response.body?.cancel().catch(() => {}); - if (response.ok) bodies.set(id, (response.body ?? new Blob().stream()).getReader()); - else response.body?.cancel(); - worker.postMessage({ type: 'fetched', id, ok: response.ok, status: response.status }); - } catch (error) { - worker.postMessage({ type: 'fetched', id, error: String(error) }); - } - } - - /** Reads the next chunk of a custom-fetch body for the worker, which asks once per chunk. */ - async function pullChunk(id) { - try { - const { done, value } = await bodies.get(id).read(); - if (done) { - bodies.delete(id); - return worker.postMessage({ type: 'chunk', id, done }); - } - const chunk = value.byteLength === value.buffer.byteLength ? value : value.slice(); - worker.postMessage({ type: 'chunk', id, chunk }, [chunk.buffer]); - } catch (error) { - bodies.delete(id); - worker.postMessage({ type: 'chunk', id, error: String(error) }); - } - } - - const scene = await scenePromise; - return { - root: scene.root, - info: scene.info, - complete, - dispose() { - stop(aborted()); - scene.dispose(); - }, - }; -} - -function buildScene(meta) { - const info = { - upAxis: meta.upAxis, - metersPerUnit: meta.metersPerUnit, - meshes: 0, - geometries: 0, - triangles: 0, - materials: meta.materials.length, - materialKinds: countBy(meta.materials, (m) => m.kind), - warnings: [...meta.warnings], - }; - const root = new THREE.Group(); - root.name = 'usd'; - // three.js is Y-up in meters. - if (meta.upAxis === 'Z') root.rotation.x = -Math.PI / 2; - root.scale.setScalar(meta.metersPerUnit || 1); - - const materials = meta.materials.map((m) => createMaterial(m)); - const variants = new Map(); - const materialFor = (index, doubleSided, vertexColors, uvChannels) => { - const routed = Object.keys(uvChannels).length > 0; - if (!doubleSided && !vertexColors && !routed) return materials[index]; - const key = `${index}|${doubleSided}|${vertexColors}|${JSON.stringify(uvChannels)}`; - if (!variants.has(key)) variants.set(key, variant(materials[index], { doubleSided, vertexColors, uvChannels })); - return variants.get(key); - }; - - // Geometries stream in one at a time; each brings in the meshes that draw it. - const geometries = []; - const instancesOf = new Map(); - for (const inst of meta.instances) { - if (!instancesOf.has(inst.geometry)) instancesOf.set(inst.geometry, []); - instancesOf.get(inst.geometry).push(inst); - } - const instanced = []; - const addGeometry = (index, g, a) => { - if (!g) return; - const geometry = createGeometry(g, a); - geometries.push(geometry); - info.geometries++; - - const triangles = g.groups.reduce((n, [, count]) => n + count / 3, 0); - // Instances by the materials they draw with. - const batches = new Map(); - for (const inst of instancesOf.get(index) ?? []) { - // A group takes its GeomSubset's material, else the mesh's. - const mats = g.groups.map(([, , subset]) => { - const m = subset !== undefined && Object.hasOwn(inst.subsets, subset) ? inst.subsets[subset] : inst.material; - const usd = materials[m].userData.usd; - // Route each named UV set to its attribute on this geometry; meshes that - // lay their UV sets out differently get their own copy of the material. - const uvChannels = Object.create(null); - for (const ref of Object.values(usd.maps)) { - const k = g.uvSets.indexOf(ref.uvSet); - if (k > 0) uvChannels[ref.uvSet] = k; - } - return materialFor(m, inst.doubleSided, g.hasColors && !!usd.colorPrimvar, uvChannels); - }); - const key = mats.map((m) => m.uuid).join(); - if (!batches.has(key)) batches.set(key, { material: mats.length > 1 ? mats : mats[0], list: [] }); - batches.get(key).list.push(inst); - info.meshes++; - info.triangles += triangles; - } - // USD stores row-vector matrices row-major: the same numbers column-major for three.js. - // Set whole rather than decomposed, which would lose shear. - for (const { material, list } of batches.values()) { - if (list.length > INSTANCING_MIN) { - const mesh = new THREE.InstancedMesh(geometry, material, list.length); - mesh.name = list[0].path; - const matrix = new THREE.Matrix4(); - list.forEach((inst, k) => mesh.setMatrixAt(k, matrix.fromArray(inst.matrix))); - mesh.computeBoundingBox(); - mesh.computeBoundingSphere(); - instanced.push(mesh); - root.add(mesh); - continue; - } - for (const inst of list) { - const mesh = new THREE.Mesh(geometry, material); - mesh.name = inst.path; - mesh.matrix.fromArray(inst.matrix); - mesh.matrixAutoUpdate = false; - mesh.matrixWorldNeedsUpdate = true; - root.add(mesh); - } - } - }; - - const allMaterials = () => [...materials, ...variants.values()]; - const textures = new Map(); - const applyTexture = (path, bitmap, isColor) => { - const base = new THREE.Texture(bitmap); - base.flipY = false; - base.anisotropy = 4; - textures.set(path, base); - const textureFor = (ref, colorSpace, uvChannel) => configureTexture(base, ref, colorSpace, uvChannel); - for (const material of allMaterials()) attachTexture(material, path, isColor, textureFor); - }; - const textureFailed = (path) => allMaterials().forEach((material) => applyFallback(material, path)); - - const dispose = () => { - geometries.forEach((g) => g.dispose()); - instanced.forEach((mesh) => mesh.dispose()); - for (const m of allMaterials()) { - for (const key of ['map', 'emissiveMap', 'roughnessMap', 'metalnessMap', 'aoMap', 'alphaMap', 'normalMap']) m[key]?.dispose(); - m.dispose(); - } - for (const t of textures.values()) { - t.image?.close?.(); - t.dispose(); - } - }; - - return { root, info, addGeometry, applyTexture, textureFailed, dispose }; -} - -function createGeometry(g, a) { - const geometry = new THREE.BufferGeometry(); - geometry.setAttribute('position', new THREE.BufferAttribute(a.positions, 3)); - geometry.setAttribute('normal', new THREE.BufferAttribute(a.normals, 3)); - // UV set k is three.js attribute `uv`, `uv1`, `uv2`, ... (a texture's `channel`). - a.uvs.forEach((uv, k) => geometry.setAttribute(k ? `uv${k}` : 'uv', new THREE.BufferAttribute(uv, 2))); - if (a.colors) geometry.setAttribute('color', new THREE.BufferAttribute(a.colors, 3)); - geometry.setIndex(new THREE.BufferAttribute(a.indices, 1)); - // Once on the GPU the arrays are dead weight: bounds are precomputed, so - // framing and culling never read them. The cost: nothing to re-upload - // after a lost WebGL context, and no CPU raycasting against the mesh. - for (const attribute of [geometry.index, ...Object.values(geometry.attributes)]) attribute.onUpload(releaseArray); - // Bounds come from the worker, so framing and culling never rescan positions. - geometry.boundingBox = new THREE.Box3().setFromArray(g.bounds); - geometry.boundingSphere = geometry.boundingBox.getBoundingSphere(new THREE.Sphere()); - if (g.groups.length > 1) g.groups.forEach(([start, count], j) => geometry.addGroup(start, count, j)); - return geometry; -} - -/** `onUpload` callback: drops the CPU copy of an attribute three.js just uploaded. */ -function releaseArray() { - this.array = null; -} - -function countBy(list, key) { - const out = {}; - for (const item of list) out[key(item)] = (out[key(item)] || 0) + 1; - return out; -} - -/** - * A ready-made viewer: renderer, studio lighting, orbit controls and - * on-demand rendering around {@link loadUsd}. The canvas is transparent unless - * a `background` is given. Throws a `webgl` UsdLoadError without WebGL. - * - * @param {HTMLElement | HTMLCanvasElement} target a canvas, or a container to append one to - * @param {import('./index.js').ViewerOptions} [options] - * @returns {import('./index.js').Viewer} - */ -export function createViewer(target, options = {}) { - const ownsCanvas = !(target instanceof HTMLCanvasElement); - const canvas = ownsCanvas ? target.appendChild(document.createElement('canvas')) : target; - const container = ownsCanvas ? target : canvas.parentElement; - let renderer; - try { - renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true }); - } catch (error) { - if (ownsCanvas) canvas.remove(); - throw new UsdLoadError('webgl', `WebGL is not available: ${error.message}`, { cause: error }); - } - renderer.setPixelRatio(Math.min(devicePixelRatio, 2)); - renderer.outputColorSpace = THREE.SRGBColorSpace; - renderer.toneMapping = THREE.NeutralToneMapping; - - const scene = new THREE.Scene(); - scene.background = options.background == null ? null : new THREE.Color(options.background); - // Rendered on the GPU, so rendered again after a lost context. - const makeEnvironment = () => { - const pmrem = new THREE.PMREMGenerator(renderer); - scene.environment = pmrem.fromScene(new RoomEnvironment(), 0.04).texture; - pmrem.dispose(); - }; - makeEnvironment(); - scene.add(new THREE.HemisphereLight(0xffffff, 0x444444, 0.6)); - const sun = new THREE.DirectionalLight(0xffffff, 1.2); - sun.position.set(3, 5, 4); - scene.add(sun); - - const camera = new THREE.PerspectiveCamera(40, 1, 0.01, 1000); - camera.position.set(2, 1.5, 2); - const controls = new OrbitControls(camera, canvas); - controls.enableDamping = true; - // Focusable, with arrow keys panning the camera and + / - zooming. - canvas.tabIndex = 0; - controls.listenToKeyEvents(canvas); - const zoomKey = (event) => { - const scale = { '+': 0.8, '=': 0.8, '-': 1.25, _: 1.25 }[event.key]; - if (!scale || !controls.enabled || !controls.enableZoom || event.ctrlKey || event.metaKey || event.altKey) return; - event.preventDefault(); - camera.position.sub(controls.target).multiplyScalar(scale).add(controls.target); - controls.update(); - }; - canvas.addEventListener('keydown', zoomKey); - - let frameRequested = false; - let disposed = false; - const render = () => { - frameRequested = false; - if (disposed) return; - if (controls.update()) requestRender(); - renderer.render(scene, camera); - }; - const requestRender = () => { - if (frameRequested || disposed) return; - frameRequested = true; - requestAnimationFrame(render); - }; - controls.addEventListener('change', requestRender); - - const resize = () => { - const width = container?.clientWidth || canvas.clientWidth || 800; - const height = container?.clientHeight || canvas.clientHeight || 600; - renderer.setSize(width, height, ownsCanvas); - camera.aspect = width / height; - camera.updateProjectionMatrix(); - requestRender(); - }; - const observer = new ResizeObserver(resize); - observer.observe(container || canvas); - resize(); - - let current = null; - // The load in flight; a newer load or dispose aborts it. - let pending = null; - // The latest load's arguments, to load again after a lost context. - let last = null; - const clear = () => { - if (!current) return; - scene.remove(current.root); - current.dispose(); - current = null; - requestRender(); - }; - // Geometry lives only on the GPU (its arrays are released once uploaded), - // so a lost context loses the stage: it is loaded again once restored. - const contextLost = (event) => { - event.preventDefault(); - pending?.abort(); - clear(); - options.onContextLost?.(); - }; - const contextRestored = () => { - scene.environment.dispose(); - makeEnvironment(); - const reload = last && viewer.load(...last); - reload?.catch(() => {}); - options.onContextRestored?.(reload); - requestRender(); - }; - canvas.addEventListener('webglcontextlost', contextLost); - canvas.addEventListener('webglcontextrestored', contextRestored); - - const viewer = { - renderer, - scene, - camera, - controls, - requestRender, - /** Loads a stage, replacing the current one once its geometry shows. A newer load aborts this one. */ - async load(url, loadOptions = {}) { - if (disposed) throw new UsdLoadError('aborted', 'the viewer was disposed', { url }); - last = [url, loadOptions]; - pending?.abort(); - const controller = (pending = new AbortController()); - const { signal: callerSignal } = loadOptions; - if (callerSignal?.aborted) controller.abort(callerSignal.reason); - callerSignal?.addEventListener('abort', () => controller.abort(callerSignal.reason), { once: true, signal: controller.signal }); - // Meshes show as they stream in: the new stage replaces the old one as - // soon as it is composed. It is framed then on its authored extents when - // every mesh has them, else once complete (and again if a mesh in those - // extents was left out): framing the meshes streamed so far would zoom - // in on the first and jump out again. - let streaming = null; - let planned = 0; - const show = (root, meta) => { - if (controller.signal.aborted) return; - clear(); - streaming = root; - scene.add(root); - if (!meta.bounds) return; - root.updateMatrixWorld(); - frameBox(camera, controls, new THREE.Box3().setFromArray(meta.bounds).applyMatrix4(root.matrixWorld)); - planned = meta.instances.length; - }; - // Aborted (superseded, cleared or disposed): what streamed in goes at once. - const hide = () => { - if (streaming && streaming !== current?.root) scene.remove(streaming); - requestRender(); - }; - controller.signal.addEventListener('abort', hide, { once: true }); - const onProgress = (progress) => { - if (progress.stage === 'geometry' || progress.stage === 'textures') requestRender(); - loadOptions.onProgress?.(progress); - }; - try { - const result = await loadUsd(url, { ...loadOptions, signal: controller.signal, onProgress, [SHOW]: show }); - // Superseded (or the viewer disposed) as geometry arrived: drop it. - if (controller.signal.aborted || disposed) { - result.dispose(); - throw new UsdLoadError('aborted', 'superseded by a newer load', { url }); - } - current = result; - if (!streaming) scene.add(result.root); - if (result.info.meshes < planned || !planned) frame(camera, controls, result.root); - requestRender(); - result.complete.then(requestRender, () => {}); - return result; - } catch (error) { - hide(); - throw error; - } finally { - if (pending === controller) pending = null; - } - }, - /** - * An image of a freshly rendered frame, PNG by default, at the canvas size - * or `width` x `height` (one given keeps the aspect ratio). Rendered and - * read in the same task, so it needs no `preserveDrawingBuffer`. - */ - toBlob({ type = 'image/png', quality, width, height } = {}) { - if (disposed || renderer.getContext().isContextLost()) - return Promise.reject(new Error('no frame to capture: the viewer is disposed or its WebGL context is lost')); - const size = renderer.getSize(new THREE.Vector2()); - const ratio = renderer.getPixelRatio(); - const setSize = (w, h, pixelRatio) => { - renderer.setPixelRatio(pixelRatio); - renderer.setSize(w, h, false); - camera.aspect = w / h; - camera.updateProjectionMatrix(); - }; - const resized = width || height; - if (resized) setSize(width || Math.round((height * size.x) / size.y), height || Math.round((width * size.y) / size.x), 1); - renderer.render(scene, camera); - const blob = new Promise((resolve, reject) => - canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('the canvas could not be encoded'))), type, quality), - ); - if (resized) { - setSize(size.x, size.y, ratio); - renderer.render(scene, camera); - } - return blob; - }, - /** Removes and frees the current stage, and aborts any load in flight. */ - clear() { - last = null; - pending?.abort(); - clear(); - }, - /** Points the camera at `object`, by default the current stage. */ - frame(object = current?.root) { - if (object) frame(camera, controls, object); - requestRender(); - }, - /** Frees the renderer, the GPU context, the current stage and any load in flight. Safe to call twice. */ - dispose() { - if (disposed) return; - disposed = true; - pending?.abort(); - if (current) current.dispose(); - current = null; - observer.disconnect(); - canvas.removeEventListener('keydown', zoomKey); - canvas.removeEventListener('webglcontextlost', contextLost); - canvas.removeEventListener('webglcontextrestored', contextRestored); - controls.dispose(); - scene.environment.dispose(); - renderer.dispose(); - renderer.forceContextLoss(); - if (ownsCanvas) canvas.remove(); - }, - }; - return viewer; -} - -/** Points the camera at the visible bounds of `object`. */ -export function frame(camera, controls, object) { - frameBox(camera, controls, new THREE.Box3().setFromObject(object)); -} - -function frameBox(camera, controls, box) { - if (box.isEmpty()) return; - const center = box.getCenter(new THREE.Vector3()); - const radius = box.getSize(new THREE.Vector3()).length() / 2 || 1; - // `fov` is vertical: on a view narrower than it is tall, fit the horizontal angle. - const vertical = (camera.fov * Math.PI) / 180; - const horizontal = 2 * Math.atan(Math.tan(vertical / 2) * camera.aspect); - const distance = (radius / Math.sin(Math.min(vertical, horizontal) / 2)) * 1.05; - camera.position.copy(center).addScaledVector(new THREE.Vector3(1, 0.6, 1).normalize(), distance); - camera.lookAt(center); - camera.near = distance / 100; - camera.far = distance * 100; - camera.updateProjectionMatrix(); - controls?.target.copy(center); - controls?.update(); -} diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/index.js.gz b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/index.js.gz deleted file mode 100644 index 8d1e7acc9572c318f0d0fd1bf38b4e1c91c19afd..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/index.js.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c5de092da843ced083bfa868a231b3a6432f63d3db83b4cbaa3c6cf9458d13a4 -size 8830 diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/load-core.js b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/load-core.js deleted file mode 100644 index 04db4ad7f6a78aa59c4f2a28b1c20c68c49fca47..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/load-core.js +++ /dev/null @@ -1,643 +0,0 @@ -// Prefetch-then-compose loading, shared by the Web Worker and the Node test. -// -// Layers are identified by absolute URL without a query, exactly as requested -// or as the Rust side resolved them from authored paths, and fetched by that -// URL. Only the root keeps its query (a signed URL, say) for fetching: -// relative references do not inherit it. -import { UsdLoadError } from './errors.js'; - -/** The load options' defaults, and the largest image side read at all. SECURITY.md documents the limits. */ -export const DEFAULTS = { - textures: 'preview', - maxTextureSize: 1024, - maxConcurrentFetches: 16, - maxLayerBytes: 768 * 2 ** 20, - maxLayers: 1024, - maxInstances: 100000, - maxTriangles: 20e6, - maxTextureBytes: 512 * 2 ** 20, - maxImageSize: 16384, -}; - -/** - * Loads and composes the stage at `rootUrl`. - * - * @param {object} o - * @param {typeof import('../wasm/usd_wasm.js').UsdLoader} o.UsdLoader - * @param {(url: string, budget: (bytes: number) => void) => Promise} o.fetchBytes null - * or a `fetch` UsdLoadError when missing. Calling `budget` with each chunk's size as it - * arrives throws once all layers together pass `maxLayerBytes`; a body never passed - * through it is charged whole when it returns. - * @param {string} o.rootUrl absolute - * @param {number} [o.maxConcurrentFetches=16] layer requests in flight at once - * @param {number} [o.maxLayerBytes=768 MiB] total size of the distinct layers held for composition - * @param {number} [o.maxLayers=1024] distinct layer files requested (files read from inside a `.usdz` package are not requested, so not counted) - * @param {number} [o.maxInstances=100000] mesh instances drawn, PointInstancer placements included; the rest are left out - * with an `instance-limit` warning - * @param {string[]} [o.allowedOrigins] see {@link requestPolicy}; layers it refuses are left out with a warning - * @param {Promise<{ layers: { url: string, size: number }[], eager: boolean }>} [o.preload] layers likely - * to be needed: fetched ahead at lower priority, right away if `eager` or else once the root names a - * dependency, charged to `maxLayerBytes` like any layer, and used only if composition asks for them - * @param {(progress: { stage: 'layers', loaded: number, total: number, bytes: number } | { stage: 'compose', round: number }) => void} [o.onProgress] - * @returns {Promise<{ scene: import('../wasm/usd_wasm.js').UsdScene, meta: object, stats: object }>} - */ -export async function composeStage({ - UsdLoader, - fetchBytes, - rootUrl, - maxConcurrentFetches = DEFAULTS.maxConcurrentFetches, - maxLayerBytes = DEFAULTS.maxLayerBytes, - maxLayers = DEFAULTS.maxLayers, - maxInstances = DEFAULTS.maxInstances, - allowedOrigins, - preload, - onProgress = () => {}, -}) { - const root = rootUrl.split(/[?#]/)[0]; - const urlOf = (path) => (path === root ? rootUrl : path); - const loader = new UsdLoader(); - const stats = { layers: 0, layerBytes: 0, missing: 0, rounds: 0, fetchMs: 0, parseMs: 0, composeMs: 0, warnings: [] }; - const started = new Map(); - const progress = () => onProgress({ stage: 'layers', loaded: stats.layers + stats.missing, total: started.size, bytes: stats.layerBytes }); - const throttle = limiter(maxConcurrentFetches); - // One budget for every layer body, charged as its bytes arrive, so fetches - // in parallel cannot each count on the whole remainder. - let heldBytes = 0; - const charge = (bytes, path) => { - heldBytes += bytes; - if (heldBytes > maxLayerBytes) throw resourceLimit(`layers exceed maxLayerBytes (${maxLayerBytes} bytes) at ${path}`); - }; - // Re-fetched layers replace their earlier bytes, so count each path once. - const layerSizes = new Map(); - // Speculative fetches by URL, dropped once composition is done. One that - // has not started when its layer is asked for, or that failed, gives way to - // the regular fetch, so the result is the same as without them. Their - // bytes are charged as they arrive, and stay charged once used. - const preloaded = new Map(); - let composed = false; - // Every distinct layer URL requested, preloads included, for maxLayers. - const requested = new Set(); - const startPreload = (layers) => { - let bytes = 0; - for (const { url, size } of layers) { - const key = urlKey(url); - bytes += size; - if (composed || bytes > maxLayerBytes) break; - if (requested.has(key)) continue; - if (requested.size >= maxLayers) break; - requested.add(key); - const entry = { started: false, charged: 0 }; - preloaded.set(key, entry); - // Unused once composed: the next chunk cancels the body. - const budget = (bytes) => { - if (composed) throw new Error('composed without it'); - entry.charged += bytes; - charge(bytes, url); - }; - entry.bytes = throttle( - async () => { - if (composed || preloaded.get(key) !== entry) return null; - entry.started = true; - const bytes = await fetchBytes(url, budget); - if (bytes) budget(bytes.byteLength - entry.charged); - return bytes; - }, - { later: true }, - ).catch(() => { - heldBytes -= entry.charged; - entry.charged = 0; - return null; - }); - } - }; - const takePreloaded = (path) => { - const entry = preloaded.get(urlKey(path)); - preloaded.delete(urlKey(path)); - return entry?.started ? entry : null; - }; - - // Jobs in flight. A job queues its dependencies here rather than awaiting - // them, so layers that reference each other cannot wait on each other. - const pending = []; - const drain = async () => { - while (pending.length) await Promise.all(pending.splice(0)); - }; - const fetchLayer = (path) => { - if (loader.has(path) || started.has(path)) return; - requested.add(urlKey(path)); - if (requested.size > maxLayers) throw resourceLimit(`more than maxLayers (${maxLayers}) layer files at ${path}`); - const { refused } = requestPolicy(urlOf(path), rootUrl, allowedOrigins); - if (refused) { - started.set(path, Promise.resolve()); - stats.missing++; - stats.warnings.push({ code: 'layer-missing', message: `layer not fetched: ${refused}`, path }); - loader.markUnavailable(path); - progress(); - return; - } - const job = (async () => { - const t0 = performance.now(); - heldBytes -= layerSizes.get(path) ?? 0; - layerSizes.delete(path); - let charged = 0; - const budget = (bytes) => { - charged += bytes; - charge(bytes, path); - }; - // Any fetch failure, HTTP or network: the root fails the load, any - // other layer is left out with a warning. - let bytes = null; - let failure = null; - try { - const entry = takePreloaded(path); - bytes = entry && (await entry.bytes); - if (bytes) charged = entry.charged; - bytes ??= await throttle(() => fetchBytes(urlOf(path), budget)); - } catch (error) { - heldBytes -= charged; - // HTTP errors carry a status; network errors are TypeErrors (as from fetch). - if (error?.status === undefined && !(error instanceof TypeError)) throw error; - failure = error; - } - stats.fetchMs = Math.max(stats.fetchMs, performance.now() - t0); - if (!bytes) { - if (path === root) { - const status = failure?.status ?? (failure ? undefined : 404); - throw new UsdLoadError('fetch', `could not fetch ${rootUrl}${status ? ` (HTTP ${status})` : ''}`, { - url: rootUrl, - status, - cause: failure ?? undefined, - }); - } - stats.missing++; - stats.warnings.push({ code: 'layer-missing', message: `layer not found: ${failure?.message ?? urlOf(path)}`, path: urlOf(path) }); - loader.markUnavailable(path); - progress(); - return; - } - charge(bytes.byteLength - charged, path); - layerSizes.set(path, bytes.byteLength); - stats.layers++; - stats.layerBytes += bytes.byteLength; - const t1 = performance.now(); - let deps; - try { - deps = loader.addLayer(path, bytes); - } catch (error) { - // After a trap the module is unusable: the load fails. - if (isTrap(error)) throw error; - // An unreadable layer is left out; composition carries on without it. - stats.warnings.push({ code: 'layer-unreadable', message: String(error.message || error), path: urlOf(path) }); - loader.markUnavailable(path); - progress(); - return; - } finally { - stats.parseMs += performance.now() - t1; - } - progress(); - for (const dep of deps) fetchLayer(dep); - })(); - started.set(path, job); - pending.push(job); - }; - - fetchLayer(root); - // A root without dependencies needs nothing else from its directory. - const rootHasDependencies = () => - started.get(root).then( - () => started.size > 1, - () => false, - ); - preload?.then(async ({ layers, eager }) => (eager || (await rootHasDependencies())) && startPreload(layers)); - await drain(); - if (!loader.has(root)) { - const reason = stats.warnings.find((w) => w.path === rootUrl)?.message; - throw new UsdLoadError('compose', `could not read ${rootUrl}${reason ? `: ${reason}` : ''}`, { url: rootUrl }); - } - - for (;;) { - stats.rounds++; - onProgress({ stage: 'compose', round: stats.rounds }); - const t0 = performance.now(); - const missing = loader.compose(root, maxInstances); - stats.composeMs += performance.now() - t0; - if (!missing.length) break; - if (stats.rounds > 16) throw new UsdLoadError('compose', `composition still missing layers: ${missing.join(', ')}`); - // The list also names layers the failed attempt consumed; fetch them again. - for (const path of missing) started.delete(path); - missing.forEach(fetchLayer); - await drain(); - } - composed = true; - const scene = loader.takeScene(); - loader.free(); - const meta = JSON.parse(scene.meta()); - return { scene, meta, stats }; -} - -/** - * What the page is told about a failed load. Fetch and resource-limit errors - * keep their code, url and status; anything the WASM side throws is a - * composition failure. Running out of WASM memory (4 GiB at most) reads as a - * scene too large to load: an allocation that fails while reading a layer - * reports "out of memory", one that aborts traps as a bare `unreachable`. A - * Rust panic, which also traps as `unreachable`, gives its message instead, - * read with `lastPanic` (the module's export). Overflowing the stack, the WASM one (an - * out-of-bounds access) or the engine's, means layers nested too deeply to read. - */ -export function loadFailure(error, wasmMemoryBytes, lastPanic) { - const { code = 'compose', url, status } = error ?? {}; - const message = String(error?.message || error); - const failure = (detail) => ({ code, message: detail, url, status }); - if (code !== 'compose') return failure(message); - const tooLarge = () => - failure(`scene too large to load: ran out of memory (${(wasmMemoryBytes / 2 ** 30).toFixed(1)} GiB of WebAssembly memory in use): ${message}`); - if (OUT_OF_MEMORY.test(message) || (error instanceof WebAssembly.RuntimeError && wasmMemoryBytes > 3 * 2 ** 30)) return tooLarge(); - if (!isTrap(error)) return failure(message); - const panic = readPanic(lastPanic); - if (panic) return failure(`${message}: ${panic}`); - if (/out of bounds|call stack|recursion/i.test(message)) return failure(`stack overflow: the layers nest too deeply to read (${message})`); - // Rust aborts on a failed allocation without a panic: a bare `unreachable`. - if (/unreachable/i.test(message)) return tooLarge(); - return failure(message); -} - -/** Messages of allocations that failed, in Rust or in the engine. */ -export const OUT_OF_MEMORY = /out of memory|memory allocation|allocation failed|memory size exceeded|could not allocate/i; - -/** Reading the panic calls into the trapped module, which can trap again (an exhausted stack does). */ -function readPanic(lastPanic) { - try { - return lastPanic?.(); - } catch { - return undefined; - } -} - -/** A WebAssembly trap or an exhausted engine call stack: the module is no longer usable. */ -function isTrap(error) { - return error instanceof WebAssembly.RuntimeError || error instanceof RangeError || error?.name === 'InternalError'; -} - -function resourceLimit(detail) { - return new UsdLoadError('fetch', `resource limit exceeded: ${detail}`); -} - -/** Runs at most `max` of the given tasks at once; tasks marked `later` wait for the others. */ -export function limiter(max) { - let active = 0; - const queue = []; - const later = []; - const next = () => { - if (active >= max || !(queue.length || later.length)) return; - active++; - const { task, resolve, reject } = queue.shift() ?? later.shift(); - task() - .then(resolve, reject) - .finally(() => { - active--; - next(); - }); - }; - return (task, { later: low = false } = {}) => - new Promise((resolve, reject) => { - (low ? later : queue).push({ task, resolve, reject }); - next(); - }); -} - -/** A layer URL as fetch would spell it, so an authored `a b.usd` matches a listed `a%20b.usd`. */ -function urlKey(url) { - try { - return new URL(url).href; - } catch { - return url; - } -} - -/** - * Fetches a body, passing each chunk's size to `budget` as it arrives and - * giving up (cancelling the body) when `budget` throws. An HTTP error throws a - * `fetch` UsdLoadError carrying the status. `fetchFn` is the global fetch or a - * stand-in with the same contract. Rate limiting (429) and server errors - * (5xx) are retried with backoff, as the Hub answers bursts with 429. - */ -export async function fetchLimited(url, budget, { fetchFn = fetch, retries = 3, retryDelayMs = 500 } = {}) { - let response = await fetchFn(url); - for (let attempt = 0; attempt < retries && (response.status === 429 || response.status >= 500); attempt++) { - response.body?.cancel().catch(() => {}); - const retryAfterMs = Number(response.headers?.get('retry-after')) * 1000; - const delay = retryAfterMs > 0 ? Math.min(retryAfterMs, 10_000) : retryDelayMs * 2 ** attempt * (1 + Math.random()); - await new Promise((resolve) => setTimeout(resolve, delay)); - response = await fetchFn(url); - } - if (!response.ok) throw new UsdLoadError('fetch', `HTTP ${response.status} for ${url}`, { url, status: response.status }); - if (!response.body) return new Uint8Array(); - const reader = response.body.getReader(); - const chunks = []; - let size = 0; - for (;;) { - const { done, value } = await reader.read(); - if (done) break; - try { - budget(value.byteLength); - } catch (error) { - await reader.cancel(); - throw error; - } - size += value.byteLength; - chunks.push(value); - } - const out = new Uint8Array(size); - let offset = 0; - for (const chunk of chunks) { - out.set(chunk, offset); - offset += chunk.byteLength; - } - return out; -} - -/** - * Size of a PNG, JPEG or WebP from its header, without decoding, and whether - * it is color: 8-bit RGB(A) or palette, rather than greyscale or 16-bit data - * (the images `sourceColorSpace = "auto"` decodes as sRGB; WebP is always - * color). Null for other formats, whose size cannot be checked before decoding. - */ -export function imageInfo(b) { - const v = new DataView(b.buffer, b.byteOffset, b.byteLength); - const ascii = (at, text) => [...text].every((c, i) => b[at + i] === c.charCodeAt(0)); - if (b.length > 25 && b[0] === 0x89 && ascii(1, 'PNG')) { - const [bitDepth, colorType] = [b[24], b[25]]; - const color = colorType === 3 || (bitDepth === 8 && (colorType === 2 || colorType === 6)); - return { width: v.getUint32(16), height: v.getUint32(20), color }; - } - if (b[0] === 0xff && b[1] === 0xd8) { - let i = 2; - while (i + 9 < b.length) { - if (b[i] !== 0xff) return null; - const marker = b[i + 1]; - const length = (b[i + 2] << 8) | b[i + 3]; - const isFrame = marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc; - if (isFrame) return { height: (b[i + 5] << 8) | b[i + 6], width: (b[i + 7] << 8) | b[i + 8], color: b[i + 4] === 8 && b[i + 9] >= 3 }; - i += 2 + length; - } - return null; - } - if (b.length >= 30 && ascii(0, 'RIFF') && ascii(8, 'WEBP')) { - // The first chunk: lossy (VP8), lossless (VP8L) or extended (VP8X). - if (ascii(12, 'VP8 ')) return { width: v.getUint16(26, true) & 0x3fff, height: v.getUint16(28, true) & 0x3fff, color: true }; - if (ascii(12, 'VP8L')) { - const bits = v.getUint32(21, true); - return { width: (bits & 0x3fff) + 1, height: ((bits >>> 14) & 0x3fff) + 1, color: true }; - } - if (ascii(12, 'VP8X')) { - const u24 = (at) => b[at] | (b[at + 1] << 8) | (b[at + 2] << 16); - return { width: u24(24) + 1, height: u24(27) + 1, color: true }; - } - } - return null; -} - -// A trailing dot names the same host (`huggingface.co.`), so it matches too. -const HUB_HOST = /^(huggingface\.co|hf\.co)\.?$/; -const HUB_CDN = /\.hf\.co\.?$/; -// Paths of Hub repo files and of the tree listing the prefetch reads: the -// only Hub URLs a load requests, never `/api/*` endpoints or account pages. -// The last group is the revision. -const HUB_FILE = /^\/(?:(datasets|spaces)\/)?([\w.-]+)\/([\w.-]+)\/resolve\/([^/]*)/; -const HUB_TREE = /^\/api\/(datasets|models|spaces)\/([\w.-]+)\/([\w.-]+)\/tree\/([^/]*)/; -// First path segments of Hub pages and endpoints: never a model's owner. -const HUB_RESERVED = new Set([ - 'api', - 'oauth', - 'settings', - 'login', - 'logout', - 'join', - 'docs', - 'models', - 'datasets', - 'spaces', - 'organizations', - 'new', - 'pricing', - 'blog', - 'papers', - 'posts', - 'collections', - 'notifications', - 'search', - 'chat', - 'learn', - 'tasks', - 'enterprise', - 'billing', - 'static-proxy', - 'avatars', - 'front', -]); - -/** The path match of a Hub file or tree-listing URL (`[, type, owner, repo, revision]`), else null. */ -function hubMatch(url) { - if (!HUB_HOST.test(url.hostname) || url.protocol !== 'https:') return null; - const file = url.pathname.match(HUB_FILE); - if (file && (file[1] || !HUB_RESERVED.has(file[2].toLowerCase()))) return file; - return url.pathname.match(HUB_TREE); -} - -/** `type/owner/repo` of a Hub file or tree-listing URL, else null. */ -function hubRepo(url) { - const match = hubMatch(url); - return match && `${match[1] ?? 'models'}/${match[2]}/${match[3]}`; -} - -/** - * A URL on hf.co, the Hub's short domain, as the huggingface.co URL it - * redirects to: fetched directly, a request keeps its headers (a redirect - * to another origin drops them) and its credentials scope stays the same. - */ -export function canonicalUrl(url) { - try { - const parsed = new URL(url); - if (!/^hf\.co\.?$/.test(parsed.hostname)) return url; - parsed.hostname = 'huggingface.co'; - return parsed.href; - } catch { - return url; - } -} - -/** - * How a load rooted at `rootUrl` may request `url`: `{ refused }` with the - * reason, or the fetch options to use. Every request a load makes goes - * through this first, then through {@link fetchWithPolicy}. Only the - * requested URL is checked: fetches follow redirects (a Hub `resolve` URL - * redirects to its CDN, which the Hub controls), but never with the caller's - * headers off the Hub. - * - * - hf.co URLs are checked as the huggingface.co URLs they redirect to. - * - The root URL itself, as the caller gave it, is always allowed. - * - Only http(s), with no user name or password in the URL. - * - Origins: the root's, those in `allowedOrigins` (`'*'` for any) and, for a - * root on the Hugging Face Hub, the Hub and its CDN hosts. - * - On huggingface.co and hf.co, only repo files (`…/resolve/…`) and tree - * listings, under a user or organization name (not `/api/…`, `/oauth/…` - * and other Hub pages), with no encoded slashes outside the revision (a - * pull request's is `refs%2Fpr%2F1`). - * - Credentials (cookies and the caller's headers) go only to the root's own - * origin or, for a Hub root, only to the root's own repo. - */ -export function requestPolicy(url, rootUrl, allowedOrigins = []) { - const refused = refusal(canonicalUrl(url), canonicalUrl(rootUrl), allowedOrigins); - if (refused) return { refused }; - return { credentials: credentialed(new URL(canonicalUrl(url)), new URL(canonicalUrl(rootUrl))) ? 'same-origin' : 'omit', referrerPolicy: 'no-referrer' }; -} - -function refusal(url, rootUrl, allowedOrigins) { - if (url === rootUrl) return null; - let target; - try { - target = new URL(url); - } catch { - return `not a URL: ${url}`; - } - const root = new URL(rootUrl); - if (target.protocol !== 'http:' && target.protocol !== 'https:') return `only http(s) URLs are fetched: ${url}`; - if (target.username || target.password) return `URLs with credentials are not fetched: ${url}`; - const hub = HUB_HOST.test(target.hostname); - if (hub && !hubFile(target)) return `only Hub repo files are fetched from ${target.host}: ${url}`; - const fromHub = HUB_HOST.test(root.hostname) && (hub || HUB_CDN.test(target.hostname)) && target.protocol === 'https:'; - if (target.origin === root.origin || fromHub || allowedOrigins.includes('*') || allowedOrigins.includes(target.origin)) return null; - return `its origin is not in allowedOrigins: ${url}`; -} - -/** A Hub repo file or tree listing with no encoded slash or backslash, but for a `%2F` in its revision. */ -function hubFile(url) { - const match = hubMatch(url); - if (!match) return false; - const revisionStart = match[0].length - match[4].length; - const outside = url.pathname.slice(0, revisionStart) + url.pathname.slice(match[0].length); - return !/%2f|%5c/i.test(outside) && !/%5c/i.test(match[4]); -} - -function credentialed(target, root) { - if (target.origin !== root.origin) return false; - const repo = hubRepo(root); - return HUB_HOST.test(root.hostname) ? !!repo && hubRepo(target) === repo : true; -} - -/** - * Fetches `url` with `fetchFn` under `policy` (see {@link requestPolicy}), - * sending `headers` only where credentials may go. A request carrying them - * does not follow a redirect with them: unless it is on the Hub (whose - * redirects stay on its own hosts), it is not followed, and the URL is - * fetched again without headers or cookies, for the browser to follow. - * `init` adds fetch options (a `signal`). - */ -export async function fetchWithPolicy(fetchFn, url, policy, headers, init = {}) { - const sendHeaders = policy.credentials === 'same-origin' && !!headers && Object.keys(headers).length > 0; - if (!sendHeaders) return fetchFn(url, { ...policy, ...init }); - if (HUB_HOST.test(new URL(url).hostname)) return fetchFn(url, { ...policy, headers, ...init }); - const response = await fetchFn(url, { ...policy, headers, redirect: 'manual', ...init }); - if (response.type !== 'opaqueredirect' && !(response.status >= 300 && response.status < 400)) return response; - response.body?.cancel().catch(() => {}); - return fetchFn(url, { ...policy, credentials: 'omit', ...init }); -} - -/** - * Reads each geometry out of WASM in turn, so only one mesh's arrays are in - * WASM memory at a time, and calls `onGeometry(index, meta, arrays)` with - * JS-owned typed arrays (`meta` and `arrays` are null for a mesh with nothing - * drawable, or left out). Triangles count as drawn, once per instance: - * meshes that would take them past `maxTriangles` are left out unread. - * Releases the stage afterwards. Returns warnings: `triangle-limit` for - * meshes left out, `nothing-drawable` when no mesh had anything to draw. - */ -export function readGeometries(scene, meta, onGeometry, { maxTriangles = DEFAULTS.maxTriangles } = {}) { - const instances = Array.from({ length: meta.geometryCount }, () => 0); - for (const { geometry } of meta.instances) instances[geometry]++; - let drawn = 0; - let left = maxTriangles; - const skipped = []; - for (let i = 0; i < meta.geometryCount; i++) { - // Not drawn anywhere (e.g. all its instances past maxInstances). - if (!instances[i]) { - onGeometry(i, null, null); - continue; - } - const json = scene.read(i, Math.min(Math.floor(left / instances[i]), 2 ** 32 - 1)); - const g = json && JSON.parse(json); - if (!g || g.overBudget !== undefined) { - if (g) skipped.push(g.path); - onGeometry(i, null, null); - continue; - } - drawn++; - left = Math.max(0, left - instances[i] * g.groups.reduce((n, [, count]) => n + count / 3, 0)); - onGeometry(i, g, { - positions: scene.positions(), - normals: scene.normals(), - uvs: g.uvSets.map((_, k) => scene.uvs(k)), - colors: g.hasColors ? scene.colors() : null, - indices: g.maxIndex < 65536 ? scene.indices16() : scene.indices(), - }); - } - scene.finish(); - const warnings = []; - if (skipped.length) - warnings.push({ code: 'triangle-limit', message: `${skipped.length} meshes left out: past maxTriangles (${maxTriangles}) drawn`, path: skipped[0] }); - if (!drawn) warnings.push({ code: 'nothing-drawable', message: 'nothing to draw: the stage has no visible meshes with geometry' }); - return warnings; -} - -/** - * Images stored inside a USDZ package (they cannot be fetched by URL) among - * the texture `jobs` (see {@link textureJobs}), each read once, by path, all - * together at most `maxBytes`. One that cannot be read maps to its error, so - * it fails as that texture rather than the whole load. - */ -export function takePackagedTextures(scene, jobs, { maxBytes = Infinity } = {}) { - const out = new Map(); - let left = maxBytes; - for (const { path } of jobs) { - if (!path.includes('[')) continue; - try { - const bytes = scene.packagedFile(path, left); - if (bytes) out.set(path, bytes); - left -= bytes?.byteLength ?? 0; - } catch (error) { - out.set(path, error); - } - } - return out; -} - -/** Long-side cap for data maps (roughness, metallic, occlusion, ...) in `preview` mode. */ -const PREVIEW_DATA_SIZE = 512; - -/** - * The texture files to load and the size to decode each to, base colors first. - * `preview`: base color up to `maxSize`, other maps up to 512 px, no normal - * maps. `full`: every map, including normals, up to `maxSize`. `none`: nothing. - */ -export function textureJobs(meta, { textures, maxSize }) { - if (textures === 'none') return []; - const full = textures === 'full'; - const dataSize = full ? maxSize : Math.min(maxSize, PREVIEW_DATA_SIZE); - const tiers = [ - [['diffuseColor'], maxSize], - [['opacity', 'emissiveColor', 'roughness', 'metallic', 'occlusion'], dataSize], - [full ? ['normal'] : [], maxSize], - ]; - const jobs = new Map(); - for (const [inputs, size] of tiers) { - for (const m of meta.materials) { - for (const input of inputs) { - const path = m.maps[input]?.path; - // A file shared by several inputs is decoded once, at the largest size asked. - if (path) jobs.set(path, Math.max(jobs.get(path) ?? 0, size)); - } - } - } - return [...jobs].map(([path, size]) => ({ path, size })); -} diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/load-core.js.gz b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/load-core.js.gz deleted file mode 100644 index 7653e8a0036b69f315df37916d61280df4f2d4d3..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/load-core.js.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f3eb08e47ef57e44e8cfa7504b332377ecd0271188bbfa1633f146f4b0a5c780 -size 9562 diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/materials.js b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/materials.js deleted file mode 100644 index 00d4326fde331170f6b769433ce648311d2fbb76..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/materials.js +++ /dev/null @@ -1,175 +0,0 @@ -// UsdPreviewSurface on three.js MeshStandardMaterial. -// -// Each textured input becomes the matching three.js map. UsdUVTexture can read -// any channel (`r`, `g`, `b`, `a`, `rgb`) and remaps it as `texel * scale + bias`; -// three.js maps read fixed channels with no remap, so the sampling line of the -// map's shader chunk is rewritten to the authored channel, scale and bias. -import * as THREE from 'three'; - -// input -> three.js map and the shader chunk line to rewrite. -export const SLOTS = { - diffuseColor: { - map: 'map', - chunk: 'map_fragment', - from: 'diffuseColor *= sampledDiffuseColor;', - to: (e) => `diffuseColor.rgb *= ${e('sampledDiffuseColor', 3)};`, - }, - emissiveColor: { - map: 'emissiveMap', - chunk: 'emissivemap_fragment', - from: 'totalEmissiveRadiance *= emissiveColor.rgb;', - to: (e) => `totalEmissiveRadiance *= ${e('emissiveColor', 3)};`, - }, - roughness: { map: 'roughnessMap', chunk: 'roughnessmap_fragment', from: 'texelRoughness.g', to: (e) => e('texelRoughness', 1) }, - metallic: { map: 'metalnessMap', chunk: 'metalnessmap_fragment', from: 'texelMetalness.b', to: (e) => e('texelMetalness', 1) }, - occlusion: { map: 'aoMap', chunk: 'aomap_fragment', from: 'texture2D( aoMap, vAoMapUv ).r', to: (e) => e('texture2D( aoMap, vAoMapUv )', 1) }, - opacity: { map: 'alphaMap', chunk: 'alphamap_fragment', from: 'texture2D( alphaMap, vAlphaMapUv ).g', to: (e) => e('texture2D( alphaMap, vAlphaMapUv )', 1) }, - normal: { - map: 'normalMap', - chunk: 'normal_fragment_maps', - from: 'texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0', - to: (e) => e('texture2D( normalMap, vNormalMapUv )', 3), - }, -}; - -/** A material from the extracted parameters; maps attach later as textures arrive. */ -export function createMaterial(m) { - const linear = (c) => new THREE.Color().setRGB(c[0], c[1], c[2], THREE.LinearSRGBColorSpace); - const material = new THREE.MeshStandardMaterial({ - color: linear(m.color), - emissive: linear(m.emissive), - roughness: m.roughness, - metalness: m.metallic, - opacity: m.opacity, - }); - const textured = 'opacity' in m.maps; - if (m.opacityThreshold > 0 && (textured || m.opacity < 1)) { - // A threshold makes opacity a cutout mask, drawn in the opaque pass. - material.alphaTest = m.opacityThreshold; - } else if (textured || m.opacity < 1) { - material.transparent = true; - material.depthWrite = false; - } - material.name = m.path; - material.userData.usd = { maps: m.maps, colorPrimvar: m.colorPrimvar, uvChannels: Object.create(null), color: m.color }; - // Until its base color texture streams in, a textured surface shows mid grey - // (18%, the usual neutral) rather than the stark white texture multiplier. - if (m.maps.diffuseColor) material.color.setRGB(0.18, 0.18, 0.18, THREE.LinearSRGBColorSpace); - return prepare(material); -} - -/** A copy that reads per-vertex colors, is double-sided, or reads UV sets from other attributes. */ -export function variant(material, { doubleSided, vertexColors, uvChannels }) { - const copy = material.clone(); - copy.userData.usd.uvChannels = uvChannels; - if (doubleSided) copy.side = THREE.DoubleSide; - if (vertexColors) { - copy.vertexColors = true; - copy.color.setRGB(1, 1, 1); - } - return prepare(copy); -} - -/** Wires the shader rewrite; `userData.patches` grows as textured inputs attach. */ -function prepare(material) { - material.userData.patches = {}; - material.onBeforeCompile = (shader) => { - for (const [input, ref] of Object.entries(material.userData.patches)) { - const { chunk, from, to } = SLOTS[input]; - const code = THREE.ShaderChunk[chunk].replaceAll( - from, - to((base, size) => remap(base, size, ref)), - ); - shader.fragmentShader = shader.fragmentShader.replace(`#include <${chunk}>`, code); - } - }; - material.customProgramCacheKey = () => JSON.stringify(material.userData.patches); - return material; -} - -/** `texel. * scale + bias` as GLSL, sized to the slot (1 or 3 components). */ -function remap(base, size, ref) { - const f = (v) => v.toFixed(6); - // The channel is an output name from the file: only r, g, b or a reach the shader. - const channel = ['r', 'g', 'b', 'a'].includes(ref.channel) ? ref.channel : 'rgb'; - if (channel === 'rgb') { - const expr = `(${base}.rgb * vec3(${ref.scale.slice(0, 3).map(f)}) + vec3(${ref.bias.slice(0, 3).map(f)}))`; - return size === 3 ? expr : `${expr}.r`; - } - const i = 'rgba'.indexOf(channel); - const expr = `(${base}.${channel} * ${f(ref.scale[i])} + ${f(ref.bias[i])})`; - return size === 3 ? `vec3${expr}` : expr; -} - -/** - * Attaches a decoded image to every input of `material` that samples `path`. - * `isColor` says whether the image holds color (see `imageInfo`). - * `textureFor(ref, colorSpace, uvChannel)` returns a configured three.js texture. - */ -export function attachTexture(material, path, isColor, textureFor) { - const usd = material.userData.usd; - let changed = false; - for (const [input, ref] of Object.entries(usd.maps)) { - const slot = SLOTS[input]; - if (ref.path !== path || !slot) continue; - const texture = textureFor(ref, colorSpace(ref, isColor), Object.hasOwn(usd.uvChannels, ref.uvSet) ? usd.uvChannels[ref.uvSet] : 0); - material[slot.map] = texture; - material.userData.patches[input] = { channel: ref.channel, scale: ref.scale, bias: ref.bias }; - // The texture now carries the value: the constant factor becomes neutral. - if (input === 'diffuseColor') material.color.setRGB(...usd.color, THREE.LinearSRGBColorSpace); - if (input === 'roughness') material.roughness = 1; - if (input === 'metallic') material.metalness = 1; - if (input === 'emissiveColor') material.emissive.setRGB(1, 1, 1); - if (input === 'opacity') material.opacity = 1; - changed = true; - } - if (changed) material.needsUpdate = true; -} - -/** - * When an image cannot be read, each input it fed shows its own value (authored, - * or the shader's default), as if it had no texture. The texture's `fallback` - * is only for a texture node with no image at all (read when extracting). - */ -export function applyFallback(material, path) { - for (const [input, ref] of Object.entries(material.userData.usd.maps)) { - if (ref.path !== path || !ref.value) continue; - const [r, g, b] = ref.value; - if (input === 'diffuseColor') material.color.setRGB(r, g, b, THREE.LinearSRGBColorSpace); - if (input === 'emissiveColor') material.emissive.setRGB(r, g, b, THREE.LinearSRGBColorSpace); - if (input === 'roughness') material.roughness = r; - if (input === 'metallic') material.metalness = r; - if (input === 'opacity') material.opacity = r; - } -} - -/** `sourceColorSpace`: explicit `raw`/`sRGB` wins; `auto` follows the image, so every input sampling it agrees. */ -function colorSpace(ref, isColor) { - if (ref.colorSpace === 'raw') return THREE.NoColorSpace; - if (ref.colorSpace === 'sRGB') return THREE.SRGBColorSpace; - return isColor ? THREE.SRGBColorSpace : THREE.NoColorSpace; -} - -/** A texture for `ref` sharing the decoded image of `base`: color space, wrap, UV set and UsdTransform2d. */ -export function configureTexture(base, ref, colorSpaceValue, uvChannel) { - const texture = base.clone(); - texture.colorSpace = colorSpaceValue; - texture.channel = uvChannel; - // `black` has no three.js equivalent (no border color); clamp is closest. - const wrap = (token) => - ({ mirror: THREE.MirroredRepeatWrapping, clamp: THREE.ClampToEdgeWrapping, black: THREE.ClampToEdgeWrapping })[token] ?? THREE.RepeatWrapping; - texture.wrapS = wrap(ref.wrapS); - texture.wrapT = wrap(ref.wrapT); - const [sx, sy] = ref.uvScale; - const [tx, ty] = ref.uvTranslation; - const angle = (ref.uvRotation * Math.PI) / 180; - if (sx !== 1 || sy !== 1 || tx !== 0 || ty !== 0 || angle !== 0) { - // UsdTransform2d: st' = rotate(st * scale) + translation (counterclockwise degrees). - const c = Math.cos(angle); - const s = Math.sin(angle); - texture.matrixAutoUpdate = false; - texture.matrix.set(c * sx, -s * sy, tx, s * sx, c * sy, ty, 0, 0, 1); - } - texture.needsUpdate = true; - return texture; -} diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/materials.js.gz b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/materials.js.gz deleted file mode 100644 index 3085d689f374a79c543d44d374dd02ea9c822489..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/materials.js.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:22675c978aeb9a7b2ee1130638207b568d64bba1321fa5de96f3bdb13f4e7c00 -size 2936 diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/worker.js b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/worker.js deleted file mode 100644 index c212b062f6d5c02860c2e7ba91b71357ecfc50f5..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/worker.js +++ /dev/null @@ -1,203 +0,0 @@ -// Loads, composes and extracts a USD stage off the main thread, then streams -// textures as downscaled ImageBitmaps. One worker per load: the page -// terminates it when done, disposed or aborted, which releases all WASM memory. -import init, { lastPanic, UsdLoader } from '../wasm/usd_wasm.js'; -import { hubPackageLayers, pinHubRevision } from './hub-prefetch.js'; -import { - DEFAULTS, - canonicalUrl, - composeStage, - fetchLimited, - fetchWithPolicy, - imageInfo, - limiter, - loadFailure, - readGeometries, - requestPolicy, - takePackagedTextures, - textureJobs, -} from './load-core.js'; - -// Textures in flight at once (fetch and decode): decoding a large image -// briefly holds it at full size, so wide parallelism spikes memory. -const TEXTURE_CONCURRENCY = 4; -// Longest side of an image read at all: larger ones are refused from their -// header, before decoding (WebGL's own limit is commonly 16384 too). -const MAX_IMAGE_SIZE = DEFAULTS.maxImageSize; - -// Requests answered by the page, for a caller-supplied `fetch`. The page sends -// the body one chunk per pull, so byte budgets apply as it arrives and -// cancelling the body stops the caller's read. -const replies = new Map(); -const pulls = new Map(); -let nextId = 0; - -/** A fetch that asks the page (which runs the caller's `fetch`) and streams its reply. */ -function proxiedFetch(url) { - const id = nextId++; - self.postMessage({ type: 'fetch', id, url }); - return new Promise((resolve) => replies.set(id, resolve)).then(({ ok, status, error }) => { - // A network failure in the caller's fetch reads like one from fetch itself. - if (error) throw new TypeError(error); - if (!ok) return new Response(null, { status: status || 500 }); - const body = new ReadableStream( - { - pull: (controller) => - new Promise((resolve) => { - pulls.set(id, { controller, resolve }); - self.postMessage({ type: 'pull', id }); - }), - cancel: () => self.postMessage({ type: 'cancel', id }), - }, - { highWaterMark: 0 }, - ); - return new Response(body); - }); -} - -/** Delivers one chunk (or the end, or a failure) of a proxied body. */ -function receiveChunk({ id, chunk, done, error }) { - const { controller, resolve } = pulls.get(id); - pulls.delete(id); - if (error) controller.error(new TypeError(error)); - else if (done) controller.close(); - else controller.enqueue(new Uint8Array(chunk)); - resolve(); -} - -self.onmessage = async ({ data }) => { - if (data.type === 'fetched') { - replies.get(data.id)?.(data); - replies.delete(data.id); - return; - } - if (data.type === 'chunk') return receiveChunk(data); - const { - url, - wasmModule, - maxTextureSize = DEFAULTS.maxTextureSize, - textures = DEFAULTS.textures, - maxConcurrentFetches = DEFAULTS.maxConcurrentFetches, - maxLayerBytes, - maxTextureBytes = DEFAULTS.maxTextureBytes, - maxLayers, - maxInstances, - maxTriangles, - allowedOrigins, - headers, - proxyFetch, - } = data; - // Every request (layer, texture, Hub listing) goes through here, and - // through the request policy first. The caller's headers go only where - // cookies may, and never through a redirect off the Hub; a custom fetch on - // the page applies the same policy. - const request = async (target) => { - target = canonicalUrl(target); - const policy = requestPolicy(target, url, allowedOrigins); - // Reads like a network failure: a missing layer or a failed texture. - if (policy.refused) throw new TypeError(`request refused: ${policy.refused}`); - if (proxyFetch) return proxiedFetch(target); - return fetchWithPolicy(fetch, target, policy, headers); - }; - const fetchBytes = (target, budget) => fetchLimited(target, budget, { fetchFn: request }); - // One budget for every texture, packaged ones first, then each fetched - // body as its bytes arrive. Bytes of a body cut short stay charged: they - // were downloaded. - let textureBytes = 0; - const chargeTexture = (bytes) => { - textureBytes += bytes; - if (textureBytes > maxTextureBytes) throw new Error(`textures exceed maxTextureBytes (${maxTextureBytes} bytes)`); - }; - const progress = (p) => self.postMessage({ type: 'progress', progress: p }); - - let wasmMemory; - try { - const t0 = performance.now(); - // Looked up while the WASM module initializes. - const rootUrl = pinHubRevision(url, request); - const preload = rootUrl.then((root) => hubPackageLayers(root, request)); - const wasm = await init({ module_or_path: wasmModule }); - wasmMemory = wasm.memory; - const tInit = performance.now(); - - const { scene, meta, stats } = await composeStage({ - UsdLoader, - fetchBytes, - rootUrl: await rootUrl, - maxConcurrentFetches, - maxLayerBytes, - maxLayers, - maxInstances, - allowedOrigins, - preload, - onProgress: progress, - }); - stats.initMs = tInit - t0; - self.postMessage({ type: 'meta', meta }); - // One mesh at a time: each is transferred (not copied) as soon as it is read. - const onGeometry = (index, g, arrays) => { - const transfer = arrays ? [arrays.positions, arrays.normals, arrays.colors, arrays.indices, ...arrays.uvs].filter(Boolean).map((a) => a.buffer) : []; - self.postMessage({ type: 'geometry', index, meta: g, arrays }, transfer); - progress({ stage: 'geometry', loaded: index + 1, total: meta.geometryCount }); - }; - const warnings = readGeometries(scene, meta, onGeometry, { maxTriangles }); - const jobs = textureJobs(meta, { textures, maxSize: maxTextureSize }); - const packaged = takePackagedTextures(scene, jobs, { maxBytes: maxTextureBytes }); - for (const entry of packaged.values()) if (!(entry instanceof Error)) textureBytes += entry.byteLength; - scene.free(); - stats.warnings.push(...warnings); - stats.totalMs = performance.now() - t0; - stats.wasmMemoryBytes = wasm.memory.buffer.byteLength; - self.postMessage({ type: 'scene', stats }); - - let loaded = 0; - let bytes = 0; - progress({ stage: 'textures', loaded, total: jobs.length, bytes }); - // The limiter runs jobs in order, so base colors come first. - const throttle = limiter(Math.min(maxConcurrentFetches, TEXTURE_CONCURRENCY)); - await Promise.all( - jobs.map(({ path, size }) => - throttle(async () => { - try { - const entry = packaged.get(path); - if (entry instanceof Error) throw entry; - // A packaged path is never a URL to fetch. - if (!entry && path.includes('[')) throw new Error(`not found in its package: ${path}`); - const image = entry ?? (await fetchBytes(path, chargeTexture)); - bytes += image.byteLength; - const { bitmap, color } = await decodeTexture(image, size); - self.postMessage({ type: 'texture', path, bitmap, color }, [bitmap]); - } catch (error) { - self.postMessage({ type: 'texture', path, error: String(error?.message || error) }); - } - progress({ stage: 'textures', loaded: ++loaded, total: jobs.length, bytes }); - }), - ), - ); - self.postMessage({ type: 'done' }); - } catch (error) { - self.postMessage({ type: 'error', ...loadFailure(error, wasmMemory?.buffer.byteLength ?? 0, lastPanic) }); - } -}; - -/** - * Decodes a PNG, JPEG or WebP straight to at most `maxSize` px on its long - * side, and tells whether it holds color (what `sourceColorSpace = "auto"` - * decodes as sRGB). Images whose size the header does not give, or that are - * too large, are refused before decoding. - */ -async function decodeTexture(bytes, maxSize) { - const info = imageInfo(bytes); - if (!info) throw new Error('unsupported image format: only PNG, JPEG and WebP are read'); - if (Math.max(info.width, info.height) > MAX_IMAGE_SIZE) - throw new Error(`image too large: ${info.width}x${info.height} (at most ${MAX_IMAGE_SIZE} px a side)`); - // USD texture coordinates put (0,0) at the bottom-left, three.js's default. - const options = { imageOrientation: 'flipY', premultiplyAlpha: 'none', colorSpaceConversion: 'none' }; - const scale = Math.min(1, maxSize / Math.max(info.width, info.height)); - if (scale < 1) { - options.resizeWidth = Math.max(1, Math.round(info.width * scale)); - options.resizeHeight = Math.max(1, Math.round(info.height * scale)); - options.resizeQuality = 'high'; - } - return { bitmap: await createImageBitmap(new Blob([bytes]), options), color: info.color }; -} diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/worker.js.gz b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/worker.js.gz deleted file mode 100644 index 8141b3b15ac6c745048a9bb6abfd1fba5110fe16..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/src/worker.js.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d1aa80cadd2ec1fe4a8a1ff156e6f4ed011415870e0039fc43dabdf1cc2b870e -size 3282 diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/wasm/usd_wasm.js b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/wasm/usd_wasm.js deleted file mode 100644 index 33e999fb7c93eb5ce6a43278e54bac789198363b..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/wasm/usd_wasm.js +++ /dev/null @@ -1,681 +0,0 @@ -export class UsdLoader { - __destroy_into_raw() { - const ptr = this.__wbg_ptr; - this.__wbg_ptr = 0; - UsdLoaderFinalization.unregister(this); - return ptr; - } - free() { - const ptr = this.__destroy_into_raw(); - wasm.__wbg_usdloader_free(ptr, 0); - } - /** - * Stores a layer and returns the layers it names outside unselected - * variants (see [`Loader::add_layer`]). - * @param {string} path - * @param {Uint8Array} bytes - * @returns {string[]} - */ - addLayer(path, bytes) { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - const ptr0 = passStringToWasm0(path, wasm.__wbindgen_export2, wasm.__wbindgen_export3); - const len0 = WASM_VECTOR_LEN; - const ptr1 = passArray8ToWasm0(bytes, wasm.__wbindgen_export2); - const len1 = WASM_VECTOR_LEN; - wasm.usdloader_addLayer(retptr, this.__wbg_ptr, ptr0, len0, ptr1, len1); - var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); - var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); - var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); - var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); - if (r3) { - throw takeObject(r2); - } - var v3 = getArrayJsValueFromWasm0(r0, r1); - wasm.__wbindgen_export(r0, r1 * 4, 4); - return v3; - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - } - } - /** - * Composes the stage at `root`. Returns the layers still missing; when - * that is empty the scene is ready for [`take_scene`](Self::take_scene). - * The scene draws at most `max_instances` mesh instances. - * @param {string} root - * @param {number} max_instances - * @returns {string[]} - */ - compose(root, max_instances) { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - const ptr0 = passStringToWasm0(root, wasm.__wbindgen_export2, wasm.__wbindgen_export3); - const len0 = WASM_VECTOR_LEN; - wasm.usdloader_compose(retptr, this.__wbg_ptr, ptr0, len0, max_instances); - var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); - var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); - var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); - var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); - if (r3) { - throw takeObject(r2); - } - var v2 = getArrayJsValueFromWasm0(r0, r1); - wasm.__wbindgen_export(r0, r1 * 4, 4); - return v2; - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - } - } - /** - * @param {string} path - * @returns {boolean} - */ - has(path) { - const ptr0 = passStringToWasm0(path, wasm.__wbindgen_export2, wasm.__wbindgen_export3); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.usdloader_has(this.__wbg_ptr, ptr0, len0); - return ret !== 0; - } - /** - * @param {string} path - */ - markUnavailable(path) { - const ptr0 = passStringToWasm0(path, wasm.__wbindgen_export2, wasm.__wbindgen_export3); - const len0 = WASM_VECTOR_LEN; - wasm.usdloader_markUnavailable(this.__wbg_ptr, ptr0, len0); - } - constructor() { - const ret = wasm.usdloader_new(); - this.__wbg_ptr = ret; - UsdLoaderFinalization.register(this, this.__wbg_ptr, this); - return this; - } - /** - * Hands over the extracted scene and drops every stored layer. - * @returns {UsdScene} - */ - takeScene() { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - wasm.usdloader_takeScene(retptr, this.__wbg_ptr); - var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); - var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); - var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); - if (r2) { - throw takeObject(r1); - } - return UsdScene.__wrap(r0); - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - } - } -} -if (Symbol.dispose) UsdLoader.prototype[Symbol.dispose] = UsdLoader.prototype.free; - -/** - * A composed scene whose meshes are read one at a time: `read(i)` makes - * geometry `i` current, and the array getters move its data out (each once). - * Only the current mesh's arrays live in WASM memory at any time. - */ -export class UsdScene { - static __wrap(ptr) { - const obj = Object.create(UsdScene.prototype); - obj.__wbg_ptr = ptr; - UsdSceneFinalization.register(obj, obj.__wbg_ptr, obj); - return obj; - } - __destroy_into_raw() { - const ptr = this.__wbg_ptr; - this.__wbg_ptr = 0; - UsdSceneFinalization.unregister(this); - return ptr; - } - free() { - const ptr = this.__destroy_into_raw(); - wasm.__wbg_usdscene_free(ptr, 0); - } - /** - * @returns {Float32Array} - */ - colors() { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - wasm.usdscene_colors(retptr, this.__wbg_ptr); - var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); - var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); - var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); - var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); - if (r3) { - throw takeObject(r2); - } - var v1 = getArrayF32FromWasm0(r0, r1).slice(); - wasm.__wbindgen_export(r0, r1 * 4, 4); - return v1; - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - } - } - /** - * Releases the stage once every geometry has been read. - */ - finish() { - wasm.usdscene_finish(this.__wbg_ptr); - } - /** - * Indices as 16-bit, for geometries with fewer than 65536 vertices. - * @returns {Uint16Array} - */ - indices16() { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - wasm.usdscene_indices16(retptr, this.__wbg_ptr); - var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); - var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); - var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); - var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); - if (r3) { - throw takeObject(r2); - } - var v1 = getArrayU16FromWasm0(r0, r1).slice(); - wasm.__wbindgen_export(r0, r1 * 2, 2); - return v1; - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - } - } - /** - * @returns {Uint32Array} - */ - indices() { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - wasm.usdscene_indices(retptr, this.__wbg_ptr); - var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); - var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); - var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); - var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); - if (r3) { - throw takeObject(r2); - } - var v1 = getArrayU32FromWasm0(r0, r1).slice(); - wasm.__wbindgen_export(r0, r1 * 4, 4); - return v1; - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - } - } - /** - * Everything but the triangle data, as JSON. - * @returns {string} - */ - meta() { - let deferred1_0; - let deferred1_1; - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - wasm.usdscene_meta(retptr, this.__wbg_ptr); - var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); - var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); - deferred1_0 = r0; - deferred1_1 = r1; - return getStringFromWasm0(r0, r1); - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - wasm.__wbindgen_export(deferred1_0, deferred1_1, 1); - } - } - /** - * @returns {Float32Array} - */ - normals() { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - wasm.usdscene_normals(retptr, this.__wbg_ptr); - var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); - var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); - var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); - var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); - if (r3) { - throw takeObject(r2); - } - var v1 = getArrayF32FromWasm0(r0, r1).slice(); - wasm.__wbindgen_export(r0, r1 * 4, 4); - return v1; - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - } - } - /** - * A texture that lives inside a USDZ package, if `path` names one there. - * Fails rather than expand it past `limit` bytes. - * @param {string} path - * @param {number} limit - * @returns {Uint8Array | undefined} - */ - packagedFile(path, limit) { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - const ptr0 = passStringToWasm0(path, wasm.__wbindgen_export2, wasm.__wbindgen_export3); - const len0 = WASM_VECTOR_LEN; - wasm.usdscene_packagedFile(retptr, this.__wbg_ptr, ptr0, len0, limit); - var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); - var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); - var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); - var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); - if (r3) { - throw takeObject(r2); - } - let v2; - if (r0 !== 0) { - v2 = getArrayU8FromWasm0(r0, r1).slice(); - wasm.__wbindgen_export(r0, r1 * 1, 1); - } - return v2; - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - } - } - /** - * @returns {Float32Array} - */ - positions() { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - wasm.usdscene_positions(retptr, this.__wbg_ptr); - var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); - var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); - var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); - var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); - if (r3) { - throw takeObject(r2); - } - var v1 = getArrayF32FromWasm0(r0, r1).slice(); - wasm.__wbindgen_export(r0, r1 * 4, 4); - return v1; - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - } - } - /** - * Reads geometry `index` and returns its metadata as JSON, or `None` when - * the mesh has nothing drawable. A mesh with more than `max_triangles` is - * left unread: `{"overBudget": triangles, "path": prim path}`. - * @param {number} index - * @param {number} max_triangles - * @returns {string | undefined} - */ - read(index, max_triangles) { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - wasm.usdscene_read(retptr, this.__wbg_ptr, index, max_triangles); - var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); - var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); - var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); - var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); - if (r3) { - throw takeObject(r2); - } - let v1; - if (r0 !== 0) { - v1 = getStringFromWasm0(r0, r1); - wasm.__wbindgen_export(r0, r1 * 1, 1); - } - return v1; - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - } - } - /** - * UV set `set` (in the order of the geometry's `uvSets`). - * @param {number} set - * @returns {Float32Array} - */ - uvs(set) { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - wasm.usdscene_uvs(retptr, this.__wbg_ptr, set); - var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); - var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); - var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); - var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); - if (r3) { - throw takeObject(r2); - } - var v1 = getArrayF32FromWasm0(r0, r1).slice(); - wasm.__wbindgen_export(r0, r1 * 4, 4); - return v1; - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - } - } -} -if (Symbol.dispose) UsdScene.prototype[Symbol.dispose] = UsdScene.prototype.free; - -/** - * Takes the message of the last panic, if any. - * @returns {string | undefined} - */ -export function lastPanic() { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - wasm.lastPanic(retptr); - var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); - var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); - let v1; - if (r0 !== 0) { - v1 = getStringFromWasm0(r0, r1); - wasm.__wbindgen_export(r0, r1 * 1, 1); - } - return v1; - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - } -} - -export function start() { - wasm.start(); -} -function __wbg_get_imports() { - const import0 = { - __proto__: null, - __wbg_Error_30c8987f7c2ed4e2: function(arg0, arg1) { - const ret = Error(getStringFromWasm0(arg0, arg1)); - return addHeapObject(ret); - }, - __wbg___wbindgen_throw_41e9ee4f547fc59a: function(arg0, arg1) { - throw new Error(getStringFromWasm0(arg0, arg1)); - }, - __wbindgen_generic_0000000000000001: function(arg0, arg1) { - // Cast intrinsic for `Ref(String) -> Externref`. - const ret = getStringFromWasm0(arg0, arg1); - return addHeapObject(ret); - }, - }; - return { - __proto__: null, - "./usd_wasm_bg.js": import0, - }; -} - -const UsdLoaderFinalization = (typeof FinalizationRegistry === 'undefined') - ? { register: () => {}, unregister: () => {} } - : new FinalizationRegistry(ptr => wasm.__wbg_usdloader_free(ptr, 1)); -const UsdSceneFinalization = (typeof FinalizationRegistry === 'undefined') - ? { register: () => {}, unregister: () => {} } - : new FinalizationRegistry(ptr => wasm.__wbg_usdscene_free(ptr, 1)); - -function addHeapObject(obj) { - if (heap_next === heap.length) heap.push(heap.length + 1); - const idx = heap_next; - heap_next = heap[idx]; - - heap[idx] = obj; - return idx; -} - -function dropObject(idx) { - if (idx < 1028) return; - heap[idx] = heap_next; - heap_next = idx; -} - -function getArrayF32FromWasm0(ptr, len) { - ptr = ptr >>> 0; - return getFloat32ArrayMemory0().subarray(ptr / 4, ptr / 4 + len); -} - -function getArrayJsValueFromWasm0(ptr, len) { - ptr = ptr >>> 0; - const mem = getDataViewMemory0(); - const result = []; - for (let i = ptr; i < ptr + 4 * len; i += 4) { - result.push(takeObject(mem.getUint32(i, true))); - } - return result; -} - -function getArrayU16FromWasm0(ptr, len) { - ptr = ptr >>> 0; - return getUint16ArrayMemory0().subarray(ptr / 2, ptr / 2 + len); -} - -function getArrayU32FromWasm0(ptr, len) { - ptr = ptr >>> 0; - return getUint32ArrayMemory0().subarray(ptr / 4, ptr / 4 + len); -} - -function getArrayU8FromWasm0(ptr, len) { - ptr = ptr >>> 0; - return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len); -} - -let cachedDataViewMemory0 = null; -function getDataViewMemory0() { - if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { - cachedDataViewMemory0 = new DataView(wasm.memory.buffer); - } - return cachedDataViewMemory0; -} - -let cachedFloat32ArrayMemory0 = null; -function getFloat32ArrayMemory0() { - if (cachedFloat32ArrayMemory0 === null || cachedFloat32ArrayMemory0.byteLength === 0) { - cachedFloat32ArrayMemory0 = new Float32Array(wasm.memory.buffer); - } - return cachedFloat32ArrayMemory0; -} - -function getStringFromWasm0(ptr, len) { - return decodeText(ptr >>> 0, len); -} - -let cachedUint16ArrayMemory0 = null; -function getUint16ArrayMemory0() { - if (cachedUint16ArrayMemory0 === null || cachedUint16ArrayMemory0.byteLength === 0) { - cachedUint16ArrayMemory0 = new Uint16Array(wasm.memory.buffer); - } - return cachedUint16ArrayMemory0; -} - -let cachedUint32ArrayMemory0 = null; -function getUint32ArrayMemory0() { - if (cachedUint32ArrayMemory0 === null || cachedUint32ArrayMemory0.byteLength === 0) { - cachedUint32ArrayMemory0 = new Uint32Array(wasm.memory.buffer); - } - return cachedUint32ArrayMemory0; -} - -let cachedUint8ArrayMemory0 = null; -function getUint8ArrayMemory0() { - if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { - cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); - } - return cachedUint8ArrayMemory0; -} - -function getObject(idx) { return heap[idx]; } - -let heap = new Array(1024).fill(undefined); -heap.push(undefined, null, true, false); - -let heap_next = heap.length; - -function passArray8ToWasm0(arg, malloc) { - const ptr = malloc(arg.length * 1, 1) >>> 0; - getUint8ArrayMemory0().set(arg, ptr / 1); - WASM_VECTOR_LEN = arg.length; - return ptr; -} - -function passStringToWasm0(arg, malloc, realloc) { - if (realloc === undefined) { - const buf = cachedTextEncoder.encode(arg); - const ptr = malloc(buf.length, 1) >>> 0; - getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); - WASM_VECTOR_LEN = buf.length; - return ptr; - } - - let len = arg.length; - let ptr = malloc(len, 1) >>> 0; - - const mem = getUint8ArrayMemory0(); - - let offset = 0; - - for (; offset < len; offset++) { - const code = arg.charCodeAt(offset); - if (code > 0x7F) break; - mem[ptr + offset] = code; - } - if (offset !== len) { - if (offset !== 0) { - arg = arg.slice(offset); - } - ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; - const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); - const ret = cachedTextEncoder.encodeInto(arg, view); - - offset += ret.written; - ptr = realloc(ptr, len, offset, 1) >>> 0; - } - - WASM_VECTOR_LEN = offset; - return ptr; -} - -function takeObject(idx) { - const ret = getObject(idx); - dropObject(idx); - return ret; -} - -let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); -cachedTextDecoder.decode(); -const MAX_SAFARI_DECODE_BYTES = 2146435072; -let numBytesDecoded = 0; -function decodeText(ptr, len) { - numBytesDecoded += len; - if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) { - cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); - cachedTextDecoder.decode(); - numBytesDecoded = len; - } - return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); -} - -const cachedTextEncoder = new TextEncoder(); - -if (!('encodeInto' in cachedTextEncoder)) { - cachedTextEncoder.encodeInto = function (arg, view) { - const buf = cachedTextEncoder.encode(arg); - view.set(buf); - return { - read: arg.length, - written: buf.length - }; - }; -} - -let WASM_VECTOR_LEN = 0; - -let wasmModule, wasmInstance, wasm; -function __wbg_finalize_init(instance, module) { - wasmInstance = instance; - wasm = instance.exports; - wasmModule = module; - cachedDataViewMemory0 = null; - cachedFloat32ArrayMemory0 = null; - cachedUint16ArrayMemory0 = null; - cachedUint32ArrayMemory0 = null; - cachedUint8ArrayMemory0 = null; - wasm.__wbindgen_start(); - return wasm; -} - -async function __wbg_load(module, imports) { - if (typeof Response === 'function' && module instanceof Response) { - if (!module.ok) { - throw new Error(`failed to fetch Wasm: ${module.status} ${module.statusText} fetching '${module.url}'`); - } - - if (typeof WebAssembly.instantiateStreaming === 'function') { - try { - return await WebAssembly.instantiateStreaming(module, imports); - } catch (e) { - const validResponse = expectedResponseType(module.type); - - if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') { - console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); - - } else { throw e; } - } - } - - const bytes = await module.arrayBuffer(); - return await WebAssembly.instantiate(bytes, imports); - } else { - const instance = await WebAssembly.instantiate(module, imports); - - if (instance instanceof WebAssembly.Instance) { - return { instance, module }; - } else { - return instance; - } - } - - function expectedResponseType(type) { - switch (type) { - case 'basic': case 'cors': case 'default': return true; - } - return false; - } -} - -function initSync(module) { - if (wasm !== undefined) return wasm; - - - if (module !== undefined) { - if (Object.getPrototypeOf(module) === Object.prototype) { - ({module} = module) - } else { - console.warn('using deprecated parameters for `initSync()`; pass a single object instead') - } - } - - const imports = __wbg_get_imports(); - if (!(module instanceof WebAssembly.Module)) { - module = new WebAssembly.Module(module); - } - const instance = new WebAssembly.Instance(module, imports); - return __wbg_finalize_init(instance, module); -} - -async function __wbg_init(module_or_path) { - if (wasm !== undefined) return wasm; - - - if (module_or_path !== undefined) { - if (Object.getPrototypeOf(module_or_path) === Object.prototype) { - ({module_or_path} = module_or_path) - } else { - console.warn('using deprecated parameters for the initialization function; pass a single object instead') - } - } - - if (module_or_path === undefined) { - module_or_path = new URL('usd_wasm_bg.wasm', import.meta.url); - } - const imports = __wbg_get_imports(); - - if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { - module_or_path = fetch(module_or_path); - } - - const { instance, module } = await __wbg_load(await module_or_path, imports); - - return __wbg_finalize_init(instance, module); -} - -export { initSync, __wbg_init as default }; diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/wasm/usd_wasm.js.gz b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/wasm/usd_wasm.js.gz deleted file mode 100644 index 19d747256191eda67a998604a4b69f064d3b0a04..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/wasm/usd_wasm.js.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d63efc37ec959ed8356762237415c5ae602804e4556c18a91e14fc6517957f81 -size 4328 diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/wasm/usd_wasm_bg.wasm b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/wasm/usd_wasm_bg.wasm deleted file mode 100644 index efff01d3295d830856258415fabfbd131cb5f9d7..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/wasm/usd_wasm_bg.wasm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cdae4095f2771c45f9996956bdaa5eae105938544b8df7eeb3a92457a8716b33 -size 2322001 diff --git a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/wasm/usd_wasm_bg.wasm.gz b/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/wasm/usd_wasm_bg.wasm.gz deleted file mode 100644 index 3b43f200f2a07056a6cc8c0d6d4fdcc4b7338034..0000000000000000000000000000000000000000 --- a/services/explorer/dist/usd-rust/2c24c43293ecda70265d3655de31bcd0eef1d214/wasm/usd_wasm_bg.wasm.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0402181eef73be91f57233c8b98e3ae7aa80079daa86b8bad53becd0648a5859 -size 849462