Adds a fullscreen button to the nav bar that expands the whole widget (viewer
+ nav bar), so the < > arrows and the dropdown stay visible and clickable in
fullscreen. Arrow KEYS (left/right) also navigate while the widget has focus
(container is made focusable and focused on entering fullscreen).
Navigation is already client-side (showIndex), so it works identically in
fullscreen with no re-execution. All listeners are on the pack's own
container element (never document), keeping it isolation-clean.
Caveat: when the mouse focus is inside the iframe 3D view, arrow KEYS go to
the iframe, not the container -- the on-screen arrow buttons always work; key
nav works when the nav bar/container holds focus. (Forwarding keys out of the
iframe would be a follow-up.)
Loading a point cloud correctly locks the shared actor to Points
representation, but the surface-mesh branch of the LOAD_MESH handler never
reset it -- so navigating from a point cloud back to a surface mesh (e.g.
scrolling a batch) left the mesh stuck rendering as points until you manually
picked Surface in settings.
Fix: in the non-point-cloud branch, restore property.setRepresentation to the
user's chosen mode (currentSettings.representation, default Surface). Point
clouds still switch to Points; switching back now returns to the chosen mode.
- "Overlay" is the tiled grid viewer, so rename it to "Grid" and make it the
default layout (was "wipe"). Saved workflows carrying the old "overlay" value
migrate to "grid" on load.
- Grid gets Cols / Rows number inputs (shown only for the grid layout). They
default to the auto dims Python computes and, when set, override them; the
viewer (viewer_multi.html) already honours gridCols/gridRows from the
LOAD_MULTI_MESH message, so changing them re-tiles the SAME iframe with no
reload and no Python/viewer change.
- The control bar is now collapsible via a small arrow (state persists in the
widget's serialized viewerState).
Client-side only (mesh_preview_multi.js). JS parses, isolation lint 0 errors.
Needs a runtime pass in ComfyUI.
The dropdown showed positions ("3 / 16"); show the source name ("apple.ply")
instead. Loaders already stash the basename in mesh.metadata['file_name']
(mesh_io.py) and it survives the worker boundary (serialization.py forwards
metadata), so the node just reads it per mesh and returns a mesh_names array.
- Python: collect names (metadata['file_name'] -> metadata['name'] -> a
positional fallback) and add "mesh_names" to the UI arrays.
- JS: dropdown options are now built as real <option> elements with
textContent = name (falling back to "k / N"), so a filename cannot inject
markup. Also added a "Name" row to the info panel (escaped).
Verified: python compiles, JS parses, isolation lint still 0 errors.
Bug: clicking a navigation arrow called app.queuePrompt() (a whole-graph
re-run). Preview nodes are V3 output nodes with no fingerprint_inputs, so
they re-execute on every queue -- which is why navigating this node reloaded
OTHER preview nodes (Preview Mesh Multi, etc.).
Fix: navigate the batch entirely client-side. The Python node now exports
EVERY mesh in the batch once and returns per-index metadata arrays
(mesh_files, vertex_counts, bounds, field_names, ...); the widget stores them
and switches meshes by posting LOAD_MESH to its OWN iframe -- no
app.queuePrompt(), so navigation never re-runs the graph or touches another
node. viewer_type is global (a function of the mode input), so switching
never needs an iframe reload either.
Also adds the requested dropdown next to the arrows: one option per mesh,
jump to any index. Arrows, dropdown, and the index widget all route through a
single client-side showIndex(); the index widget value still persists via
serialization (guarded against a re-entrant callback).
Trade-off (per the chosen approach): the batch is exported up front, so first
render is heavier for very large batches -- but navigation is then instant and
fully decoupled from the rest of the graph.
Verified: python compiles, JS parses, isolation lint still 0 errors. Needs a
runtime pass in ComfyUI (I cannot drive the node graph here).
- Delete 7 dead-squat widgets (registered UI only for other packs' nodes:
UniRig/SAM3D/Mocap/FBXPreview/BVH/pointcloud) + their 6 orphaned viewer
HTMLs + the entire unused js/viewer tree (21 files, imported by nothing;
only ui/styles.css survives) + unused utils barrel.
- Rename namespace geompack -> geometrypack (= DisplayName, lowercased);
all 17 surviving extensions now geometrypack.*.
- Add event.source provenance guards to every message listener (2 inline,
2 factory handlers via new iframe param).
- Stop touching document.body: upload file input stays detached (click()
works detached in modern browsers); folder autocomplete dropdown becomes
a manual popover inside the widget's own DOM (top layer escapes canvas
transforms without shared-DOM appends).
Passes comfy-test's javascript isolation lint with 0 errors.
ComfyUI auto-imports every web/**/*.js into the shared main page; the four
big bundles (three, vtk-gltf, viewer-bundle-vtk, gsplat) only ever run
inside the viewer iframes but were getting pulled into the main document
too, where they wrote window.THREE / window.vtk / globalThis.* and could
collide with any other 3D pack. The **/*.js glob only matches .js, so
renaming to .mjs drops them from the auto-import while the iframes still
load them via classic <script src> (mime resolves to text/javascript).
window.* pollution from these bundles is gone from the main realm.
The viewer JS/HTML/bundles are now committed in javascript/ (declared via
pyproject [tool.comfy] web = "javascript") instead of being copied from the
shared comfy-3d-viewers pip package at prestartup. Consequences:
- requirements.txt is now just comfy-env (host env installs nothing else,
per the never-touch-the-host-env principle)
- prestartup_script.py no longer copies viewers or web_src; it keeps only
setup_env() and the input-asset copies
- WEB_DIRECTORY attribute removed; the pyproject declaration is the single
source (served at /extensions/comfyui-geometrypack/, keyed by project name)
- web_src/ deleted; its 6 widgets now live in javascript/js/ as the tracked
source, edited directly
- viewer_pbr.html hardcoded-fallback URL updated to the new mount name
Iframe URLs auto-detect the mount from import.meta.url, so the folder rename
and mount-name change are transparent at runtime.