⚠️ GPT client not available. Search is disabled.
" ) with gr.Group(): gr.Markdown("### Select Asset Category") primary = gr.Dropdown( choices=primary_list, value=primary_val, label="🗂️ Primary Category", ) secondary = gr.Dropdown( choices=secondary_list, value=secondary_val, label="📂 Secondary Category", ) category = gr.Dropdown( choices=category_list, value=category_val, label="🏷️ Asset Category", ) with gr.Group(): initial_assets, _, initial_df = get_assets( primary_val, secondary_val, category_val ) gallery = gr.Gallery( value=initial_assets, label="🖼️ Asset Previews", columns=3, height="auto", allow_preview=True, elem_id="asset-gallery", interactive=bool(category_val), ) with gr.Column(scale=2, min_width=500): with gr.Group(): with gr.Tabs(): with gr.TabItem("Visual Mesh") as t1: viewer = gr.Model3D( label="🧊 3D Model Viewer", height=380, clear_color=[0.95, 0.95, 0.95], elem_id="visual_mesh", ) with gr.TabItem("Collision Mesh") as t2: collision_viewer_a = gr.Model3D( label="🧊 Collision Mesh", height=380, clear_color=[0.95, 0.95, 0.95], elem_id="collision_mesh_a", visible=True, ) collision_viewer_b = gr.Model3D( label="🧊 Collision Mesh", height=380, clear_color=[0.95, 0.95, 0.95], elem_id="collision_mesh_b", visible=False, ) t1.select( fn=lambda: None, js="() => { window.dispatchEvent(new Event('resize')); }", ) t2.select( fn=lambda: None, js="() => { window.dispatchEvent(new Event('resize')); }", ) with gr.Row(): est_type_text = gr.Textbox( label="Asset category", interactive=False ) est_height_text = gr.Textbox( label="Real height(.m)", interactive=False ) est_mass_text = gr.Textbox( label="Mass(.kg)", interactive=False ) est_mu_text = gr.Textbox( label="Friction coefficient", interactive=False ) with gr.Row(): desc_box = gr.Textbox( label="📝 Asset Description", interactive=False ) with gr.Accordion(label="Asset Details", open=False): urdf_file = gr.Textbox( label="URDF File Path", interactive=False, lines=2 ) with gr.Row(elem_id="download-row"): # DownloadButtons that build their zip into their own value # on click; a chained JS handler then triggers the browser # download from that value (one-click). If the JS ever # fails, the value is still populated, so a second manual # click downloads it via the button's native behavior. urdf_dl_btn = gr.DownloadButton( label="⬇️ Download URDF", variant="primary", interactive=False, ) usd_dl_btn = gr.DownloadButton( label="⬇️ Download USD", variant="primary", interactive=False, ) mjcf_dl_btn = gr.DownloadButton( label="⬇️ Download MJCF", variant="primary", interactive=False, ) affordance_dl_btn = gr.DownloadButton( label="⬇️ Affordance", variant="primary", interactive=False, ) search_button.click( fn=search_assets, inputs=[search_box, top_k_slider], outputs=[gallery, gallery, gallery_df_state], ) search_box.submit( fn=search_assets, inputs=[search_box, top_k_slider], outputs=[gallery, gallery, gallery_df_state], ) def update_on_primary_change(p): s_choices = get_secondary_categories(p) initial_assets, gallery_update, initial_df = get_assets(p, None, None) return ( gr.update(choices=s_choices, value=None), gr.update(choices=[], value=None), initial_assets, gallery_update, initial_df, ) def update_on_secondary_change(p, s): c_choices = get_categories(p, s) asset_previews, gallery_update, gallery_df = get_assets(p, s, None) return ( gr.update(choices=c_choices, value=None), asset_previews, gallery_update, gallery_df, ) def update_assets(p, s, c): asset_previews, gallery_update, gallery_df = get_assets(p, s, c) return asset_previews, gallery_update, gallery_df primary.change( fn=update_on_primary_change, inputs=[primary], outputs=[secondary, category, gallery, gallery, gallery_df_state], ) secondary.change( fn=update_on_secondary_change, inputs=[primary, secondary], outputs=[category, gallery, gallery, gallery_df_state], ) category.change( fn=update_assets, inputs=[primary, secondary, category], outputs=[gallery, gallery, gallery_df_state], ) gallery.select( fn=show_asset_from_gallery, inputs=[primary, secondary, category, search_box, gallery_df_state], concurrency_id=HF_IO_CONCURRENCY_ID, concurrency_limit=HF_IO_CONCURRENCY_LIMIT, outputs=[ (visual_path_state := gr.State()), (collision_path_state := gr.State()), desc_box, asset_folder, urdf_file, est_type_text, est_height_text, est_mass_text, est_mu_text, ], ).then( fn=render_meshes, inputs=[visual_path_state, collision_path_state, switch_viewer_state], outputs=[ viewer, collision_viewer_a, collision_viewer_b, switch_viewer_state, ], ).success( fn=_reset_downloads, outputs=[urdf_dl_btn, usd_dl_btn, mjcf_dl_btn, affordance_dl_btn], ) # After the zip is built into the button's own value, pass that file value # straight into the JS handler and trigger the browser download from its # URL. Reading the value via `inputs` avoids DOM-timing/selector issues. download_js = """ (f) => { if (f && f.url) { const a = document.createElement('a'); a.href = f.url; a.download = (f.orig_name || 'asset.zip').split('/').pop(); document.body.appendChild(a); a.click(); document.body.removeChild(a); } return []; } """ dl_btns = [urdf_dl_btn, usd_dl_btn, mjcf_dl_btn, affordance_dl_btn] # Each flow: lock all buttons (prevent spam clicks) -> build the zip -> # JS-trigger the browser download -> unlock. Selecting another asset # resets the buttons independently (see `.success` above). urdf_dl_btn.click( fn=lambda: _lock_downloads("urdf"), outputs=dl_btns, queue=False ).then( fn=download_urdf, inputs=[asset_folder], outputs=[urdf_dl_btn], concurrency_id=HF_IO_CONCURRENCY_ID, concurrency_limit=HF_IO_CONCURRENCY_LIMIT, ).then(fn=lambda *a: None, inputs=[urdf_dl_btn], js=download_js).then( fn=_unlock_downloads, outputs=dl_btns ) usd_dl_btn.click( fn=lambda: _lock_downloads("usd"), outputs=dl_btns, queue=False ).then( fn=download_usd, inputs=[asset_folder], outputs=[usd_dl_btn], concurrency_id=HF_IO_CONCURRENCY_ID, concurrency_limit=HF_IO_CONCURRENCY_LIMIT, ).then(fn=lambda *a: None, inputs=[usd_dl_btn], js=download_js).then( fn=_unlock_downloads, outputs=dl_btns ) mjcf_dl_btn.click( fn=lambda: _lock_downloads("mjcf"), outputs=dl_btns, queue=False ).then( fn=download_mjcf, inputs=[asset_folder], outputs=[mjcf_dl_btn], concurrency_id=HF_IO_CONCURRENCY_ID, concurrency_limit=HF_IO_CONCURRENCY_LIMIT, ).then(fn=lambda *a: None, inputs=[mjcf_dl_btn], js=download_js).then( fn=_unlock_downloads, outputs=dl_btns ) affordance_dl_btn.click( fn=lambda: _lock_downloads("affordance"), outputs=dl_btns, queue=False, ).then( fn=download_affordance, inputs=[asset_folder], outputs=[affordance_dl_btn], concurrency_id=HF_IO_CONCURRENCY_ID, concurrency_limit=HF_IO_CONCURRENCY_LIMIT, ).then( fn=lambda *a: None, inputs=[affordance_dl_btn], js=download_js ).then(fn=_unlock_downloads, outputs=dl_btns) demo.load(start_session) demo.unload(end_session) if __name__ == "__main__": # Serve gallery videos / meshes that live under DATA_ROOT (outside cwd). demo.launch()