const $=s=>document.querySelector(s),$$=s=>[...document.querySelectorAll(s)];const escape=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));const bytes=n=>n<1024?`${n} B`:n<1048576?`${(n/1024).toFixed(1)} KB`:`${(n/1048576).toFixed(1)} MB`;const pathUrl=p=>p.split('/').map(encodeURIComponent).join('/'); let config,assets=[],selected=null,category='All assets',query='',sort='name',limit=12,viewer=null,viewVersion=0,gridVersion=0; function showTab(id){$$('[data-tab]').forEach(b=>b.setAttribute('aria-selected',String(b.dataset.tab===id)));$$('.tab-panel').forEach(p=>p.hidden=p.id!==id);if(id==='files')renderFiles();if(id==='about')renderAbout();if(id==='setup')renderSetup()} $$('[data-tab]').forEach((b,i,buttons)=>{b.onclick=()=>showTab(b.dataset.tab);b.onkeydown=e=>{if(!['ArrowLeft','ArrowRight','Home','End'].includes(e.key))return;e.preventDefault();const n=e.key==='Home'?0:e.key==='End'?buttons.length-1:(i+(e.key==='ArrowRight'?1:buttons.length-1))%buttons.length;buttons[n].focus();showTab(buttons[n].dataset.tab)}}); $('#template-button').onclick=()=>showTab('setup');$('#setup-button').onclick=()=>showTab('setup');$('#close-modal').onclick=()=>$('#modal').close();$('#modal').onclick=e=>{if(e.target===$('#modal'))$('#modal').close()};$('#fullscreen').onclick=()=>document.fullscreenElement?document.exitFullscreen():$('#viewport').requestFullscreen?.(); $('#asset-search').oninput=e=>{query=e.target.value;limit=12;renderGrid()};$('#sort').onchange=e=>{sort=e.target.value;renderGrid()};$('#show-more').onclick=()=>{limit+=12;renderGrid()}; function categories(){const cats=['All assets',...new Set(assets.map(a=>a.category))];if(!cats.includes(category))category='All assets';$('#categories').innerHTML=cats.map(c=>``).join('');$$('[data-category]').forEach(b=>b.onclick=()=>{category=b.dataset.category;limit=12;categories();renderGrid()})} async function renderGrid(){const generation=++gridVersion;const list=assets.filter(a=>(category==='All assets'||a.category===category)&&`${a.name} ${a.path}`.toLowerCase().includes(query.toLowerCase())).sort(sort==='size'?(a,b)=>b.size-a.size:(a,b)=>a.name.localeCompare(b.name));$('#result-count').textContent=`${list.length} ${list.length===1?'asset':'assets'}`;$('#show-more').hidden=list.length<=limit;$('#asset-grid').innerHTML=list.slice(0,limit).map(a=>``).join('')||`

${assets.length?'No matching assets':'Your library starts here'}

${assets.length?'Try another name or category.':'Upload USD files and their dependencies into assets/ in your Space repository. They will appear here after the Space restarts.'}

`;$$('[data-asset]').forEach(b=>b.onclick=()=>select(b.dataset.asset));$('#empty-setup')?.addEventListener('click',()=>showTab('setup'));if(viewer)for(const a of list.slice(0,limit)){if(generation!==gridVersion)return;if(a.thumbnail||a.previewStatus!=='ready')continue;try{const url=await viewer.thumbnail(a);if(generation!==gridVersion)return;const img=$(`[data-asset="${a.id}"] img`);if(img){img.src=url;img.hidden=false}}catch{}}} async function select(id){const a=assets.find(a=>a.id===id);if(!a)return;selected=a;const version=++viewVersion;$('#inspector').hidden=false;$('#selected-name').textContent=a.name;$('#selected-path').textContent='assets / '+a.path.replaceAll('/',' / ');$('#format').textContent=a.format;$('#asset-download').href=a.physics?`${a.packages.all}`:`${a.downloadUrl}`;$('#asset-download').firstChild.textContent=a.physics?'Download USD + MJCF ':'Download original USD ';$('#asset-source').hidden=!config.repoUrl;$('#asset-source').href=config.repoUrl?`${config.repoUrl}/blob/main/assets/${pathUrl(a.path)}`:'#';$('#asset-meta').innerHTML=`
Original size${bytes(a.size)}
Category${escape(a.category)}
${a.geometry?`
Source coordinates${escape(a.geometry.axis)}-up · ${a.geometry.units} m/unit
Preview${a.geometry.meshes} meshes · ${a.geometry.triangles.toLocaleString()} triangles
`:''}${physicsCard(a)}`;$('#preview-message').hidden=false;$('#preview-message').textContent=a.previewStatus==='queued'?'Preparing this asset’s preview…':a.previewStatus==='unavailable'?(a.previewError||'Preview unavailable. The original USD can still be downloaded.'):'Loading 3D preview…';$('#preview-image').hidden=!a.thumbnail;if(a.thumbnail)$('#preview-image').src=`${a.thumbnailUrl}`;$('#viewer-state').textContent=a.previewStatus==='ready'?'Interactive 3D':'Original available';renderGrid();viewer?.clear();if(a.previewStatus==='ready'&&viewer){try{await viewer.show(a);if(version===viewVersion){$('#preview-message').hidden=true;$('#preview-image').hidden=true}}catch{if(version===viewVersion)$('#preview-message').textContent='3D preview could not load. Download the original USD to inspect it.'}}else if(!viewer&&a.previewStatus==='ready')$('#preview-message').textContent='WebGL preview unavailable in this browser. The original file is available below.'} const g=kg=>kg==null?'—':kg<1?`${(kg*1000).toFixed(kg<0.1?1:0)} g`:`${kg.toFixed(2)} kg`; function physicsCard(a){const p=a.physics;if(!p)return'';const d=p.dimensions_m||{},ref=p.reference,err=ref&&p.mass_kg!=null?Math.abs(p.mass_kg-ref.mass_kg)/ref.mass_kg*100:null; const rows=[['Mass',`${g(p.mass_kg)}${p.low_kg!=null?` (${g(p.low_kg)}–${g(p.high_kg)})`:''}`], ref?['YCB published mass',`${g(ref.mass_kg)} · ${err.toFixed(1)}% off`]:null, p.confidence!=null?['Mass confidence',p.confidence.toFixed(2)]:null, ['Size (L × W × H)',['length_m','width_m','height_m'].map(k=>d[k]!=null?(d[k]*1000).toFixed(1):'?').join(' × ')+' mm'], ['Materials',escape((p.materials||[]).join(', ')||'—')], ['Friction · restitution',`${p.static_friction??'—'} · ${p.restitution??'—'}`], ['Collision',`${p.hulls} convex hulls (⬢ in the viewer)`], p.com_m?['Centre of mass',p.com_m.map(v=>(v*1000).toFixed(1)).join(', ')+' mm (x, y, z; Z up · ⊕ in the viewer)']:null, ['SimReady profiles',escape((p.profiles||[]).map(x=>x.replace('Prop-Robotics-','')).join(' · ')||'—')], p.mjcf?['MuJoCo',`MJCF package ↓ · USD only ↓`]:null].filter(Boolean); return `
Physics
`+rows.map(([k,v])=>`
${k}${v}
`).join('')} function renderFiles(){$('#files').innerHTML=`
⑂ Repository main${assets.length} USD assets${config.repoUrl?`View real commit history ↗`:''}

Source files live in this Space’s assets/ folder. Get the collection ZIP to preserve relative dependencies.

${assets.map(a=>``).join('')||''}
FileSizeDownload
${escape(a.path)}${bytes(a.size)}Original ↓
No USD files have been uploaded yet.

Use this collection from Python

Download the complete assets folder so referenced layers and textures keep their relative paths. To pin a release, replace main with a commit SHA from the Space’s history.

from huggingface_hub import snapshot_download\n\nfolder = snapshot_download(\n    repo_id=${escape(JSON.stringify(config.repoId||'your-org/your-space'))},\n    repo_type="space",\n    revision="main",\n    allow_patterns=["assets/**"],\n)
`} function renderAbout(){$('#about').innerHTML=`
COLLECTION CARD

${escape(config.title)}

${escape(config.about)}

Maintained by ${escape(config.owner)}

Files, revisions, membership, and access are managed in this organization’s Hugging Face Space. Visitors download originals directly from this instance.

Asset licensing

${escape(config.license)}. Check the license documents included with the collection before reuse. The bundled Examples folder contains an MIT-licensed calibration cube; uploaded assets retain their owners’ chosen licenses.

Preview fidelity

Previews show mesh geometry at the default time and selected USD variants, using simplified shading and authored display colors when available. Full MDL materials, texture rendering, animation, skeletal deformation, and procedural primitives are not reproduced. Files without a supported preview remain downloadable.

Complete projects

Upload referenced layers, textures, and licenses with the same folder layout. Remote references and dependencies outside assets/ are not bundled. The collection ZIP includes the assets/ folder and its relative contents.

`} function renderSetup(){const repo=config.repoUrl,template=config.templateUrl;$('#setup').innerHTML=`
FOR ORGANIZATION MAINTAINERS

One Space. Your organization’s assets.

Use this template to publish a library under your own namespace, with your own files, permissions, and revision history.

01 / DUPLICATE

Make it yours

Open the template on Hugging Face, choose “Duplicate this Space” from its menu, and select your organization as owner.

Duplicate the template ↗
02 / UPLOAD

Add your USD folder

In your copy’s Files tab, upload your USD files, layers, textures, thumbnails, and licenses into assets/. Keep their relative folder structure.

${repo?`Open this Space’s assets folder ↗`:''}
03 / DISTRIBUTE

Share your library

The Space restarts and builds its catalog automatically. Update collection.json with your title and license, then share your organization’s Space URL.

${repo?`View collection settings ↗`:''}

Upload an existing collection from your computer

hf auth login\nhf upload your-org/your-space ./your-usd-folder assets --repo-type space

Replace your-org/your-space with your duplicated Space. Hugging Face checks your write access. There are no public upload endpoints or stored access tokens in this app.

Suggested folder structure

assets/\n  LICENSE.txt\n  Seating/\n    Chair.usd\n    Chair.png              # optional thumbnail\n    Materials/\n      ChairMaterial.usda\n    Textures/\n      Chair_BaseColor.png

Remove the Examples folder when you add your own collection. USD, USDA, USDC, and USDZ files are indexed automatically. Supply a neighboring PNG or JPG for a custom thumbnail. Each preview has a 120-second conversion limit; unsupported or very complex assets remain available as downloads.

This is a CPU Basic Docker Space. Hugging Face currently requires a paid account or organization plan to create or duplicate Docker Spaces; CPU Basic has no hourly compute fee. No GPU, external database, or shared central asset service is required.

`} async function refresh(){try{const r=await fetch('catalog.json');if(!r.ok)throw new Error();const data=await r.json();const old=selected;assets=data.assets;$('#total-count').textContent=assets.length;$('#index-status').textContent=data.error|| (data.indexing?`Preparing previews · ${assets.filter(a=>a.previewStatus==='ready').length} of ${assets.length} ready`:`${assets.length} source ${assets.length===1?'asset':'assets'} · ${assets.filter(a=>a.previewStatus==='ready').length} interactive previews`);$('#template-callout').hidden=assets.length>0&&!assets.every(a=>a.path.startsWith('Examples/'));categories();if(!selected&&assets.length)await select(assets[0].id);else if(selected){const next=assets.find(a=>a.id===selected.id);if(next&&next.previewStatus!==old.previewStatus)await select(next.id);else renderGrid()}else renderGrid();if(data.indexing)setTimeout(refresh,3000)}catch{$('#index-status').textContent='The library could not be loaded. Refresh the page to retry.'}} try{config=await fetch('config.json').then(r=>{if(!r.ok)throw new Error();return r.json()});$('#owner').textContent=config.owner;$('#collection-title').textContent=config.title;$('#collection-description').textContent=config.description;$('#license').textContent=config.license;document.title=config.title;document.documentElement.style.setProperty('--accent',config.accent);for(const [id,suffix] of [['source-link','/tree/main'],['community-link','/discussions'],['manage-assets','/tree/main/assets']]){const a=$('#'+id);if(config.repoUrl)a.href=config.repoUrl+suffix;else{a.removeAttribute('target');a.href='#';a.onclick=e=>{e.preventDefault();showTab('setup')}}}try{viewer=await import('./viewer.js')}catch{}await refresh()}catch{$('#index-status').textContent='Collection settings could not be loaded. Check collection.json and the Space logs.'}