# `@huggingface/usd-web-viewer`

[OpenUSD](https://openusd.org) en el navegador: composición de USD en WebAssembly, construido sobre [openusd](https://github.com/mxpv/openusd) (Rust puro), renderizado con [three.js](https://threejs.org). Lee `.usd`, `.usda`, `.usdc` y `.usdz`; las cargas se ejecutan en un Web Worker.

```sh
npm install @huggingface/usd-web-viewer three
```

## Uso

Un visor listo para usar (renderizador, iluminación, controles de órbita):

```js
import { createViewer } from '@huggingface/usd-web-viewer';

const viewer = createViewer(document.getElementById('viewer')); // a container element
const { info } = await viewer.load(
  'https://huggingface.co/datasets/Robotiq-Official/simready-assets/resolve/main/Robotiq_2F_85/simready_usd/Robotiq_2F_85.usda',
);
```

O en tu propia escena de three.js:

```js
import { loadUsd } from '@huggingface/usd-web-viewer';

const { root, complete, dispose } = await loadUsd(url);
scene.add(root); // a THREE.Group, Y-up, in meters
await complete; // textures stream in after the geometry
```

## Física y articulaciones

`info` also reports what the stage authors for simulation (UsdPhysics) and its UsdSemantics labels, as authored: nothing is estimated. Masses are in kilograms, densities in kg/m³, inertia in kg·m², joint limits and positions in degrees or meters; positions and matrices are in stage space, like the meshes under `root`.

```js
const { info, joints, setJointPositions, resetJointPositions, getJointPositions, getWorldMatrix } = await viewer.load(url);

info.physics.bodies; // rigid bodies: mass, center of mass, inertia, colliders, bodies fixed to them
info.physics.colliders; // shape, approximation (convexHull, sdf, ...), physics material
info.physics.materials; // density, friction, restitution
info.physics.joints; // revolute, prismatic, spherical, distance, fixed and generic joints with their limits and drives
info.physics.articulationRoots; // where articulations (bodies solved together, like a robot) start
info.physics.frames; // Xforms under a body, often points with no mesh, like a gripper's tip
info.labels; // e.g. { path: '/RootNode', taxonomy: 'wikidata_class', values: ['cabinet'] }
info.variantSelections; // e.g. { prim: '/Robotiq_2F_85', set: 'Physics', selection: 'Physx_parallel_grip' }
info.dependencies; // every layer and texture the stage read, with its size

joints; // the joints setJointPositions can move, e.g. to build sliders
setJointPositions({ '/Robotiq_2F_85/Robotiq_2F_85/Joints/finger_joint': 40 }); // revolute and prismatic joints, any number at once; what hangs from them and mimic joints follow
getJointPositions(); // where every revolute and prismatic joint is now
getWorldMatrix('/SO101/gripper/gripper_frame_link'); // where a body, frame or mesh under a body is now and which way it faces, as a THREE.Matrix4 in the scene; null if it is none of those
resetJointPositions(); // back to the authored pose
```

El posicionamiento es puramente cinemático (sin colisiones ni gravedad) y lo suficientemente económico para cada cuadro de un arrastre de control deslizante o de una grabación.

## Opciones

`loadUsd(url, options)` y `viewer.load(url, options)` aceptan:

| Opción                         | Predeterminado             |                                                                                 |
| ------------------------------ | ------------------- | ------------------------------------------------------------------------------- |
| `textures`                     | `'preview'`         | `'none'`, `'preview'` (color base, mapas de datos pequeños, sin mapas normales) o `'full'` |
| `maxTextureSize`               | `1024`              | Lado largo de las texturas decodificadas, en píxeles                                        |
| `signal`                       |                     | Una `AbortSignal`; la carga se rechaza con un `UsdLoadError` de código `aborted`      |
| `onProgress`                   |                     | Llamado con la etapa y una `fraction` global de 0 a 1                     |
| `files`                        |                     | Cargar archivos descartados o seleccionados en lugar de la red (ver más abajo)                 |
| `wasmUrl`, `workerUrl`         | junto al paquete | Desde dónde se sirven el binario WASM y el script del worker                     |
| `maxConcurrentFetches`         | `16`                | Solicitudes en curso simultáneamente                                                      |
| `maxLayerBytes`, `maxLayers`   | 768 MiB, 1024       | Límites de descarga de capas                                                           |
| `maxTextureBytes`              | 512 MiB             | Límite de descarga de texturas                                                          |
| `maxTriangles`, `maxInstances` | 20M, 100,000        | Lo que se dibuja como máximo                                                           |

`createViewer(container, options)` acepta `background` (transparente por defecto), `onContextLost` y `onContextRestored` (se llaman con una recarga del último modelo mostrado).

Los fallos son `UsdLoadError` con un `code` (`aborted`, `fetch`, `compose`, `worker`, `webgl`); todo lo que no se pudo mostrar de forma fiel se enumera en `info.warnings`. Cada opción, error y advertencia tiene un tipo definido.

## Carga de archivos no confiables

- Las solicitudes se envían como las de `<model-viewer>`: las cookies solo al origen de la página (`credentials: 'same-origin'`), sin encabezados personalizados y sin referrer.
- Las capas y texturas se obtienen solo a través de http(s).
- El análisis se ejecuta en Rust compilado a WebAssembly, en un Web Worker.
- Cada carga tiene un límite: `maxLayerBytes` (768 MiB), `maxLayers` (1024), `maxTextureBytes` (512 MiB), `maxTriangles` (20M) y `maxInstances` (100,000); imágenes de hasta 16384 px de lado y 8192 x 8192 px de área; prims anidados hasta 256 niveles de profundidad; entradas `.usdz` de hasta 512 MiB cada una y 1 GiB en total, almacenadas sin comprimir.
- Los archivos malformados provocan un error de carga con `UsdLoadError`.

## Archivos locales

Para mostrar los archivos soltados en la página o seleccionados con `<input type="file" webkitdirectory>`, páselos como `files`, con su clave según su ruta en el paquete, y carga la clave de la capa raíz. Las rutas relativas en los archivos se resuelven entre sí; no se realiza ninguna solicitud para ellas.

```js
const files = new Map([...input.files].map((file) => [file.webkitRelativePath || file.name, file]));
await viewer.load('chair/simready_usd/sm_chair.usd', { files });
```

## Empaquetadores

El worker y el binario WASM se encuentran con `new URL(..., import.meta.url)`, que Vite, webpack y otros bundlers resuelven y emiten. Con el servidor de desarrollo de Vite, excluya el paquete del pre-bundling de dependencias:

```js
// vite.config.js
export default { optimizeDeps: { exclude: ['@huggingface/usd-web-viewer'] } };
```

Licencia MIT. Las licencias de los crates de Rust compilados en el binario WASM están en `THIRD_PARTY_LICENSES`.

