Spaces:
Running
Running
Download app.js from rigyd/ycb-simready: direct link, hf CLI and curl.
- Browser
- Download file 14.7 kB
-
https://huggingface.co/spaces/rigyd/ycb-simready/resolve/main/app.js
- Command line
-
hf download hf://spaces/rigyd/ycb-simready/app.js
-
curl -L -o app.js https://huggingface.co/spaces/rigyd/ycb-simready/resolve/main/app.js
14.7 kB
| 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=>`<button data-category="${escape(c)}" class="${category===c?'active':''}">${escape(c)}<span>${c==='All assets'?assets.length:assets.filter(a=>a.category===c).length}</span></button>`).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=>`<button class="asset-card ${a.id===selected?.id?'selected':''}" data-asset="${a.id}" aria-label="Preview ${escape(a.name)}" aria-pressed="${a.id===selected?.id}"><div class="thumb"><span class="usd-label">${escape(a.format)}</span><span class="thumb-symbol" aria-hidden="true">⬡</span><img class="thumb-image" ${a.thumbnail?`src="${a.thumbnailUrl}"`:'hidden'} alt="${escape(a.name)}" style="position:absolute">${a.id===selected?.id?'<span class="selected-check">✓</span>':''}</div><div class="card-info"><h3>${escape(a.name)}</h3><div class="category">${escape(a.category)}</div><p><span>${a.previewStatus==='ready'?'3D preview':a.previewStatus==='queued'?'Preparing preview':'Original available'}</span><span>${bytes(a.size)}</span></p></div></button>`).join('')||`<div class="empty"><h2>${assets.length?'No matching assets':'Your library starts here'}</h2><p>${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.'}</p><button id="empty-setup">Set up your library →</button></div>`;$$('[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=`<div><span>Original size</span><b>${bytes(a.size)}</b></div><div><span>Category</span><b>${escape(a.category)}</b></div>${a.geometry?`<div><span>Source coordinates</span><b>${escape(a.geometry.axis)}-up · ${a.geometry.units} m/unit</b></div><div><span>Preview</span><b>${a.geometry.meshes} meshes · ${a.geometry.triangles.toLocaleString()} triangles</b></div>`:''}${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?` <small>(${g(p.low_kg)}–${g(p.high_kg)})</small>`:''}`], | |
| 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 <small>(⬢ in the viewer)</small>`], | |
| p.com_m?['Centre of mass',p.com_m.map(v=>(v*1000).toFixed(1)).join(', ')+' mm <small>(x, y, z; Z up · ⊕ in the viewer)</small>']:null, | |
| ['SimReady profiles',escape((p.profiles||[]).map(x=>x.replace('Prop-Robotics-','')).join(' · ')||'—')], | |
| p.mjcf?['MuJoCo',`<a href="${a.packages.mjcf}">MJCF package ↓</a> · <a href="${a.packages.usd}">USD only ↓</a>`]:null].filter(Boolean); | |
| return `<div class="physics-head"><span>Physics</span></div>`+rows.map(([k,v])=>`<div><span>${k}</span><b>${v}</b></div>`).join('')} | |
| function renderFiles(){$('#files').innerHTML=`<div class="file-toolbar"><span class="tag">⑂ Repository main</span><span class="tag">${assets.length} USD assets</span>${config.repoUrl?`<a class="space-link history-button" href="${config.repoUrl}/commits/main" target="_blank" rel="noreferrer">View real commit history ↗</a>`:''}</div><p class="files-note">Source files live in this Space’s assets/ folder. Get the collection ZIP to preserve relative dependencies.</p><div class="file-scroll"><table class="file-table"><thead><tr><th>File</th><th>Size</th><th>Download</th></tr></thead><tbody>${assets.map(a=>`<tr><td>${escape(a.path)}</td><td>${bytes(a.size)}</td><td><a href="${a.downloadUrl}">Original ↓</a></td></tr>`).join('')||'<tr><td colspan="3">No USD files have been uploaded yet.</td></tr>'}</tbody></table></div><article class="article"><h3>Use this collection from Python</h3><p>Download the complete assets folder so referenced layers and textures keep their relative paths. To pin a release, replace <code>main</code> with a commit SHA from the Space’s history.</p><pre class="setup-code">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)</pre></article>`} | |
| function renderAbout(){$('#about').innerHTML=`<article class="article"><span class="tag amber">COLLECTION CARD</span><h2 style="margin-top:18px">${escape(config.title)}</h2><p>${escape(config.about)}</p><h3>Maintained by ${escape(config.owner)}</h3><p>Files, revisions, membership, and access are managed in this organization’s Hugging Face Space. Visitors download originals directly from this instance.</p><h3>Asset licensing</h3><p>${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.</p><h3>Preview fidelity</h3><p>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.</p><h3>Complete projects</h3><p>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.</p></article>`} | |
| function renderSetup(){const repo=config.repoUrl,template=config.templateUrl;$('#setup').innerHTML=`<div class="setup-header"><span class="tag amber">FOR ORGANIZATION MAINTAINERS</span><h2 style="margin-top:15px">One Space. Your organization’s assets.</h2><p>Use this template to publish a library under your own namespace, with your own files, permissions, and revision history.</p></div><div class="setup-steps"><div class="setup-step"><span class="step-number">01 / DUPLICATE</span><h3>Make it yours</h3><p>Open the template on Hugging Face, choose “Duplicate this Space” from its menu, and select your organization as owner.</p><a href="${template}?duplicate=true" target="_blank" rel="noreferrer">Duplicate the template ↗</a></div><div class="setup-step"><span class="step-number">02 / UPLOAD</span><h3>Add your USD folder</h3><p>In your copy’s Files tab, upload your USD files, layers, textures, thumbnails, and licenses into assets/. Keep their relative folder structure.</p>${repo?`<a href="${repo}/tree/main/assets" target="_blank" rel="noreferrer">Open this Space’s assets folder ↗</a>`:''}</div><div class="setup-step"><span class="step-number">03 / DISTRIBUTE</span><h3>Share your library</h3><p>The Space restarts and builds its catalog automatically. Update collection.json with your title and license, then share your organization’s Space URL.</p>${repo?`<a href="${repo}/blob/main/collection.json" target="_blank" rel="noreferrer">View collection settings ↗</a>`:''}</div></div><h3>Upload an existing collection from your computer</h3><pre class="setup-code">hf auth login\nhf upload your-org/your-space ./your-usd-folder assets --repo-type space</pre><p class="setup-note">Replace <code>your-org/your-space</code> with your duplicated Space. Hugging Face checks your write access. There are no public upload endpoints or stored access tokens in this app.</p><h3>Suggested folder structure</h3><pre class="setup-code">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</pre><p class="setup-note">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.</p><p class="setup-note">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.</p>`} | |
| 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.'} | |