layout and opacity_1/opacity_2 are gone from the schema; mode stays a real
node input (as directed).
Python: with no layout to branch on, every run exports BOTH view sets -- the
separate pair (side_by_side / slider) and the combined overlay file -- using
the same untouched export helpers (_export_mesh, _export_combined_mesh; the
combined helper just loses its never-used opacity params). ui_data carries
both file sets; opacity keys dropped (Python only ever echoed them);
common_fields_overlay added (the combined export injects mesh_id).
JS: layout and opacities move into viewerState (persisted via the DOM
widget's serialization, like show_edges already was). Every layout switch is
now CLIENT-SIDE -- overlay included, since its file always exists -- and
opacity changes re-post immediately. Only a mode change re-runs (different
export format). No widgets are hidden; mode renders as a normal node input.
Cost: one extra combined export per run. Old workflows whose saved widget
values included layout/opacity load with those values ignored.
It registered geometrypack.meshpreview.vtk.textured for node
GeomPackPreviewMeshVTKWithTexture, which the pack no longer ships (textured
preview is now a `mode` on GeomPackPreviewMeshVTK / ...VTKBatch). So its
beforeRegisterNodeDef never matched -- dead weight that squatted a removed
node. Surfaced by comfy-test's new foreign-node-hook rule; missed in the
earlier squat cleanup because its GeomPack-prefixed name looked like ours.
Nothing imports it; viewer_vtk_textured.html stays (still used by the VTK
single + batch widgets for texture mode). Pack is back to 0 lint errors.
The control bars are now the ONLY visible controls; the node input widgets
they drive are hidden via a new hideWidgets() util (uiComponents.js). The
widgets keep existing -- values still serialize into the workflow and flow to
Python as inputs -- they just stop rendering on the node body.
- Multi / VTK preview / Batch: `mode` widget hidden (bar Mode selector drives
it). Batch keeps its `index` widget visible (useful for typing an exact
index).
- Dual: `layout`, `mode`, `opacity_1`, `opacity_2` all hidden. Opacity moves
into the bar (shown only for the Overlay layout) and is applied
CLIENT-SIDE: changing it updates the hidden widgets (persistence + next
run) and re-posts to the viewer immediately -- no re-run. render() now
prefers widget values over the last message's, which also fixes the
`|| 1.0` bug that made opacity 0 impossible.
No Python or export-format changes, per direction.
Preview Mesh Dual:
- New control bar with Layout (Side by side | Overlay | Slider) and Mode
(Fields | Texture), two-way synced with the node widgets.
- side_by_side <-> slider reuse the SAME exported files, so that switch is
CLIENT-SIDE: onExecuted was refactored into render(message, layoutOverride)
and the last message is stored -- switching re-renders instantly, no re-run.
Overlay needs the combined export and mode changes the export format, so
those queue a re-run (same cost as editing the widget).
- opacity_1/opacity_2 widgets are hidden unless layout is Overlay (the
hidden-type trick from preview_mesh_batch_render.js), restored when it is.
Preview Mesh (VTK): new bar with Mode (Fields | Texture | Texture (PBR)),
two-way synced, re-runs on change.
Fullscreen: new createFullscreenButton(container) in utils/uiComponents.js
(fullscreens the whole widget so bar controls stay usable; listeners on the
container only, isolation-clean). Wired into Dual, VTK, and Multi bars (in
Multi it sits outside the collapsible group so it survives collapse). The
batch node keeps its own earlier implementation with arrow-key nav.
Note: plain Preview Mesh (three.js, GeomPackPreviewMesh) has no mode input on
the Python side, so there is nothing to select there; the VTK preview is the
one with modes. comfy-3d-viewers was checked for fullscreen prior art -- its
"FullScreen" hits are VTK class names, not browser fullscreen; this is new.
Both bars get a Mode dropdown mirroring the node's `mode` input, two-way
synced with the widget. Unlike index navigation this is NOT client-side: mode
changes what gets exported (VTP/STL vs GLB), so changing it queues a re-run --
the same cost as editing the node widget, just reachable from the bar. After
a run the bar reflects the mode actually executed.
- Drop /gpack/save_preview and the VTK viewer's "Save Mesh" button (added one
commit ago): previews are temp exports, and persisting a mesh is what the
SaveMesh nodes are for. The textured viewer's "Save GLB" stays -- it is a
pure client-side download, no route.
- Preview Mesh Batch (VTK) now outputs the TRIMESH batch (is_output_list),
same passthrough pattern as Preview Mesh Multi, so it can sit mid-graph.
- New GeomPackGetMeshFromBatch (nodes/io/get_mesh_from_batch.py): extract one
mesh from a TRIMESH batch by index (clamped), logging the source file_name
when present. Registered in nodes/io/__init__.py.
Save mesh: the VTK viewer's Save button POSTed to /geometrypack/save_preview,
a route that was never ported into GeometryPack (inherited caller from
comfy-3d-viewers; only /gpack/getpath existed). ComfyUI's GET catch-all
matched the path, hence 405 instead of 404. Added POST /gpack/save_preview in
__init__.py next to getpath: basename()-sanitized lookup of the temp preview
export in ComfyUI output/ (tempdir fallback), copied to output/saved_<name>;
returns {success, saved_filename}. Viewer fetch updated to the new path.
Multi cols/rows: entering dims whose product was smaller than the mesh count
(e.g. 1x1 for 2 meshes) overflowed viewports into IMPLICIT CSS grid rows
(auto height), so the rendered layout stopped matching the inputs. Now the
edited field is authoritative and the other dimension auto-raises to fit
(ceil(n/edited)); both inputs are updated to show the grid actually in
effect. Oversized grids (empty cells) remain allowed.
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.