623 Commits
Author SHA1 Message Date
PozzettiAndrea d0e9e8f540 446 2026-09-15 11:19:55 +02:00
PozzettiAndrea 18a288b609 l8test 2026-09-15 10:07:44 +02:00
PozzettiAndrea 2dde889a53 443 2026-09-13 03:05:36 +02:00
PozzettiAndrea 54df72317b 42 2026-09-08 09:42:11 +02:00
PozzettiAndrea bddef27969 new progressbar 2026-09-08 09:31:49 +02:00
PozzettiAndrea 3e50d0a8fa 40 2026-09-08 09:23:35 +02:00
PozzettiAndrea fc0c72d5c0 439 2026-09-08 09:12:11 +02:00
PozzettiAndrea ba8a793c8d LoadMesh: comfy-env markers, walker, validate and fingerprint all deleted
Replaced by one input_files() call (comfy-env >= 0.4.34). What left and why
each was safe to delete:

- The comfy_env_* marker dict: the recipe now travels as input_files'
  arguments, journaled by the metadata scan. No private vocabulary in
  /object_info.
- get_mesh_files(): a byte-for-byte sibling of comfy-env's parent-side
  scanner that had to be kept in sync by hand. input_files() IS that
  scanner -- one implementation, every process.
- validate_inputs(cls, **kwargs): the **kwargs form exempted EVERY input
  from execution.py's checks, numeric clamps included. comfy-env now
  synthesizes the narrow named-arg form covering exactly file_path.
- fingerprint_inputs: synthesized parent-side (mtime over the resolved
  path) under the subset gate; the hand-rolled copy was silently dropped
  at the isolation boundary anyway.

Under vanilla ComfyUI (no comfy-env) input_files() simply returns the live
list, so the node stays correct un-isolated.

Verified live: a mesh dropped into a new subfolder of input/3d appears in
the dropdown immediately; deleting it removes it and restores the
placeholder; the synthesized validate is (cls, file_path) with no varkw.
2026-08-28 00:11:27 +02:00
PozzettiAndrea 2b586883f6 Use ASCII in remesh tooltips for cp1252 consoles
The nine U+2248 ALMOST EQUAL TO characters in the remeshing tooltips cannot be
encoded in cp1252, which fails the syntax level on Windows. Spell them "~=".
2026-08-22 19:44:03 +02:00
PozzettiAndrea 670504cac9 removed comfy env copy files 2026-08-22 17:08:30 +02:00
PozzettiAndrea 5f077bd4e8 clear 2026-08-22 16:58:38 +02:00
PozzettiAndrea ee14401c32 instant_meshes: compensate the binding's uncompensated pure-quad subdivision (4x)
Field report: target 5000 vertices delivered ~19k. Expert audit ran the
pynanoinstantmeshes binding directly and PROVED the cause: the solver
receives the target correctly, computes the correct quad-lattice scale
(sqrt(A/V), verified to the digit), extracts on target -- and then the
binding always runs Instant Meshes' "pure quad mesh" subdivision pass
(each face -> 4 quads, V' = V+E+F ~= 4V) WITHOUT the GUI's compensating
scale adjustment. Measured 4.03-4.14x across a decade of targets: a
deterministic constant, not hierarchy snapping (the previous tooltip's
diagnosis was wrong), not a dropped parameter.

Fix: pre-divide the requested vertex count by the topologically pinned
factor 4 in the wrapper. Verified post-fix accuracy: 500->550, 1000->1052,
5000->5104, 12000->12134 (~±10%, normal Instant Meshes tolerance). The
dispatcher's faces path (V = F_tri/2) stays correct: output quads ~= V and
triangulation emits 2 tris/quad (confirmed 5104 v -> 10204 tris).

Tooltips updated accordingly (drop the "snaps 2-4x" folklore; ~±10%).
2026-08-14 18:39:43 +02:00
PozzettiAndrea b012b18b67 Remesh gating: judge manifoldness on RAW topology; demote clean-error backends to warnings
Field regression caught immediately: ArmoredWarrior_00005_.glb was rejected
for pymeshlab with "279 non-manifold edges" -- but the RAW connectivity the
backend receives has ZERO (verified: max edge incidence 2). The old check ran
on position-MERGED topology, where the asset's UV/normal seam splits (18061
verts, 9574 unique positions) manufacture non-manifold edges that don't
exist in the mesh the library processes.

Fix, two parts:
- _edge_stats now returns BOTH views. Manifold questions use RAW (what the
  backend consumes); open-mesh questions use MERGED (raw view misreads
  duplicated-vertex soup as 100% boundary -- the same asset reads 13763
  boundary edges raw vs 9 merged).
- Hard REJECT shrinks to the catastrophic-failure trio (quadriflow segfault,
  quadwild hang, cgal UB). pymeshlab / PMP / MMG fail cleanly on their own,
  so genuine raw non-manifoldness now WARNS with a repair route instead of
  blocking input the library may handle.

Also: instant_meshes target tooltips updated from "~±2x" to the observed
reality -- the solver snaps to its multiresolution hierarchy and can land
2-4x off (field report: 5k requested -> 19k delivered); treat as an
order-of-magnitude knob and read the achieved count from the info output.

Tests: coincident stacked boxes (the armor-plate case) pass clean for
pymeshlab; genuine 3-fan non-manifold warns on pymeshlab and rejects on
quadriflow; soup box still not misread as open; truly open mesh still
rejected for blender_voxel.
2026-08-14 18:32:07 +02:00
PozzettiAndrea 4bca1ee8e3 Remesh: explicit target dropdown per backend + input gating
Replaces the hidden "0 = off" sentinel precedence (which was inconsistent
across backends: pymeshlab resolved faces>vertices>edge, quadwild the
opposite) with a `target` DynamicCombo nested in each backend option -- pick
ONE driver, see ONE input. Design and conversion math vetted by two
independent expert reviews; conversions that would lie are simply not
offered.

Per-backend target menus (first input under the backend selector):
- pymeshlab: faces|vertices|edge_length (native back-solve, forwarded)
- pmp_uniform: faces(F->V=F/2)|vertices|edge_length
- cgal: faces|vertices (equilateral back-solve e=sqrt(4A/(sqrt3 F)))|edge
- geogram_smooth: vertices(nb_points, near-exact)|faces|edge_length
- geogram_aniso: vertices|faces only (no scalar edge length exists)
- instant_meshes: vertices|faces (labeled ~±2x)
- quadriflow: faces(TRIANGLES; quad budget = F/2)|vertices|edge(Q=A/e^2)
- quadwild: faces|vertices|edge_length|scale_factor (~±50%, labeled)
- gpu_cumesh: faces|vertices only (simplifier hits counts; edge refused --
  adaptive simplification leaves no uniform edge length). Default 100k
  (was 500k).
- blender_voxel: faces|vertices|edge_length|voxel_size (square-cell
  constants; ±2x-on-open/thin caveat in tooltips)
- blender_sharp: octree_depth|edge_length (snapped to power of 2);
  counts refused (4x quantization)
- mmg: `sizing` combo: adaptive[hmin,hmax]|uniform_edge_length[hsiz]|
  uniform_vertices|uniform_faces (->hsiz); hausd stays outside (always used)
- blender_blocks, pmp_adaptive, faithc: unchanged (counts are emergent /
  discrete there)
Defaults normalized (were 5000 quads vs 500000 faces across backends);
0-sentinels removed from the UI (they live on inside hidden backend nodes,
resolved by construction: only the chosen driver's param is forwarded).

Dispatcher additions (hidden backend nodes untouched):
- _CHOICE_COMBOS registry distinguishes choice combos from on/off toggles
  (no value-shape sniffing).
- _resolve_target(): equilateral constants for triangle backends, square-
  cell for quad/voxel; logs requested -> native for every conversion;
  sharp's depth uses the sibling `scale` and reports the achieved cell.
- _preflight() input gating (expert-audited): rejects 0-face/point-cloud,
  NaN/Inf vertices, zero-extent bbox; non-manifold edges for the halfedge +
  quad-parametrization backends (segfault/hang class); open meshes for
  blender_voxel (silent empty output); grid-explosion guards for voxel
  sizes and absolute hausd (the hang/OOM class), also applied to CONVERTED
  voxel sizes. Non-manifold detection runs on MERGED-VIEW topology (vertex-
  position hash) so STL-style duplicated-vertex soup isn't misread as all-
  boundary. Warnings (not fatal): open mesh on cumesh/faithc, inconsistent
  winding on volumetric backends, degenerate-face fraction, and count-
  voiding settings (pymeshlab adaptive, voxel adaptivity, quadriflow
  adaptive_scale). Every rejection names an alternative backend or repair
  node.

Unit-tested via stubbed comfy_api import of the real module: 10 conversion
checks (constants exact) + 12 gating checks all pass. Old Remesh workflows
need re-picking (user remaking them).
2026-08-14 18:16:53 +02:00
PozzettiAndrea 3e6195eaf8 Preview Mesh Slicer: also output the untouched original mesh
Third TRIMESH output (appended last so existing sliced_mesh/info links keep
their indices). sliced_mesh stays the transform result; mesh is the pure
passthrough.
2026-08-14 17:04:43 +02:00
PozzettiAndrea 8d8ca54955 Preview Mesh UV + Boundaries: TRIMESH passthrough output
Both were dead-end previews (UV declared no outputs; Boundaries an explicit
outputs=[]). Each now declares a TRIMESH `mesh` output and returns its input
mesh alongside the UI data, so they can sit mid-graph like every other
preview node. UV's point-cloud early-return passes the mesh through too.
Boundaries' vertex-merge already operates on a copy, so the passthrough is
the untouched original.
2026-08-14 17:03:54 +02:00
PozzettiAndrea a939374186 Get Mesh From Batch: accept slices; delete obsolete Preview Mesh Multi (Batch)
Get Mesh From Batch: `index` becomes `selection`, accepting an index ("2",
"-1", clamped) or a Python slice ("1:5", ":3", "::2", "-3:", self-clamping).
Output is now a TRIMESH list (a single index yields a batch of one, which
maps identically downstream). Parser covered by exhaustive standalone tests
(indices, negative, out-of-range, all slice forms, error cases).

Preview Mesh Multi (Batch) deleted (node, widget JS, registration): the
autogrown Preview Mesh Multi subsumes it -- batch-accepting sockets, better
auto grid, client-side dims, wipe layout, full-list passthrough vs its silent
rows*cols truncation. workflows/extract_connected_components.json still
references the deleted node; CI breakage accepted by user, workflow to be
migrated separately.
2026-08-14 17:00:20 +02:00
PozzettiAndrea ec5d99433d Preview Mesh Multi: autogrow inputs, batch-accepting, any mesh count
Replaces the fixed mesh_1..mesh_4 sockets with an io.Autogrow input (same
pattern as core's MergeSplat): a fresh socket appears as you connect each
mesh, up to 16. INPUT_IS_LIST makes every socket accept a single mesh OR a
whole batch; everything is flattened in socket order -- so a batch of 15 plus
one extra mesh previews all 16 together.

Auto grid is now near-square ceil(sqrt(n)) (1x1, 2x1, 3x1, 2x2, 3x2, 3x3,
4x3, 4x4) instead of capping at 2x2; the widget's client-side Cols/Rows still
override. Display capped at 16 viewports with a loud log: each viewport is
its own WebGL context and browsers kill contexts beyond ~16/page. The FULL
flattened list (beyond the cap too) passes through the output.

Breaking for saved workflows that linked mesh_1..mesh_4 (socket names are now
mesh_0..): links need reconnecting. Widget JS unchanged (reads mesh_files).
2026-08-14 16:55:57 +02:00
PozzettiAndrea 1721e2bf4e Rename GeomPackPreviewMesh -> GeomPackPreviewMeshThree
The three.js preview's generic name made it unclear next to the VTK/Dual/
Multi/Batch previews. Renamed the node id (schema + both mappings), the
widget file (mesh_preview.js -> mesh_preview_three.js, extension name
geometrypack.meshpreview -> geometrypack.meshpreview.three), and the four
test workflows that reference the node type.

Breaking for external saved workflows that used GeomPackPreviewMesh (node
type no longer exists) -- accepted; the pack's own workflows are updated.
2026-08-14 16:49:59 +02:00
PozzettiAndrea 15e6ffa02a Batch + Multi bars: remove Mode selector
Mode is controlled solely by each node's own mode input widget, matching the
Dual and VTK preview policy. Bars keep navigation/layout controls + fullscreen.
2026-08-14 15:32:32 +02:00
PozzettiAndrea 7f927324b6 Control bars move below the canvas; Dual bar loses Mode
- Dual / Multi / VTK preview: the control bar now sits BETWEEN the canvas and
  the info panel (canvas on top, controls below), matching the batch node's
  nav bar. Border flipped bottom->top to suit the new position.
- Preview Mesh Dual: Mode selector removed from the bar -- the node's own
  `mode` input widget is the only mode control (same policy as Preview Mesh
  VTK). Bar keeps Layout, per-mesh color/opacity (overlay), and fullscreen.
2026-08-14 14:57:45 +02:00
PozzettiAndrea 9dbd869c9d Dual overlay: per-mesh color + opacity via two actors; VTK preview bar loses Mode
Preview Mesh Dual (fields overlay):
- The overlay now renders from the TWO per-mesh files (already exported every
  run) as two actors in one renderer, replacing the combined-file single
  actor. Per-mesh opacity finally works for real (the old path could only
  apply the AVERAGE of both opacities to one merged actor).
- New color pickers (1: / 2:) next to the opacity inputs in the bar, overlay
  only. Colors + opacities are pure client-side (viewerState-persisted,
  re-posted on change); defaults red/blue match the old baked colors.
- Viewer (viewer_dual.html): second overlay actor; layout-aware mapper +
  render helpers (mapper1Active/mapper2Active/renderActiveViews) so field
  selection, colormaps, and edge toggles now work in overlay too (previously
  field application only targeted the side-by-side mappers); settings apply
  point size/edges/representation to both overlay actors (color stays
  per-mesh). Legacy combined-file overlay path kept for old widgets; the
  textured overlay still uses the combined GLB (colors don't apply to
  textures).

Preview Mesh (VTK): Mode selector removed from the JS bar -- the node's own
`mode` input is the only control (bar keeps just the fullscreen button).
2026-08-14 14:45:43 +02:00
PozzettiAndrea fe3c01f585 Preview Mesh Dual: remove layout + opacity from Python inputs
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.
2026-08-14 14:27:20 +02:00
PozzettiAndrea 9d25f10b25 Delete dead widget mesh_preview_vtk_textured.js
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.
2026-08-14 14:26:35 +02:00
PozzettiAndrea 4b5486076a Revert "Hide bar-driven widgets from node bodies (JS only; Python untouched)"
This reverts commit 54c3ad01aa.
2026-08-14 14:21:57 +02:00
PozzettiAndrea 54c3ad01aa Hide bar-driven widgets from node bodies (JS only; Python untouched)
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.
2026-08-14 14:18:15 +02:00
PozzettiAndrea 6047a30e1b Dual/VTK/Multi preview bars: layout+mode selectors, opacity gating, shared fullscreen
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.
2026-08-14 14:01:37 +02:00
PozzettiAndrea e9c7bbceaf Preview Mesh Batch + Multi: mode selector (Fields/Texture) in the control bar
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.
2026-08-14 13:57:18 +02:00
PozzettiAndrea 19487d73b1 Remove save-preview route/button; batch preview passes TRIMESH through; add Get Mesh From Batch
- 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.
2026-08-14 13:51:37 +02:00
PozzettiAndrea ce11ca310d Fix save-mesh 405 (missing route) + Multi grid dims not matching inputs
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.
2026-08-14 13:47:14 +02:00
PozzettiAndrea 78b0378f1d Preview Mesh Batch (VTK): fullscreen with in-fullscreen navigation
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.)
2026-08-14 13:38:37 +02:00
PozzettiAndrea a9e14f5dc2 VTK viewer: restore Surface after a point cloud forces Points mode
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.
2026-08-14 13:35:17 +02:00
PozzettiAndrea 7f0c97cd89 Preview Mesh Multi: rename Overlay->Grid (default), add cols/rows + collapsible bar
- "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.
2026-08-14 13:33:45 +02:00
PozzettiAndrea 7ce6f73269 Preview Mesh Batch (VTK): label the dropdown with mesh names
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.
2026-08-14 13:26:35 +02:00
PozzettiAndrea 923da524b9 Preview Mesh Batch (VTK): client-side navigation + dropdown
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).
2026-08-13 17:22:14 +02:00
PozzettiAndrea e463099eec serialization.py: tighten Trimesh serializer, same behavior
Collapse the repeated best-effort try/except into _opt/_put helpers, flatten
the visual/metadata handling, and compress the docstring. 117 -> 86 lines
(85 -> 57 code lines). Verified behaviorally identical to the prior version
by round-tripping real trimesh meshes (plain / textured uv+material+metadata /
no-faces) through both old and new serialize+deserialize with identity recurse.

The load-bearing comments are kept verbatim in intent: the id()-reuse GC
hazard (pass mesh arrays directly, never np.asarray) and the decompose-visuals-
by-kind rationale (avoid PIL + the mesh back-reference re-serializing geometry).
Material without uv is now nested under uv -- deserialize ignores it anyway, so
the round-trip is unchanged and the wire gets slightly smaller.
2026-08-13 16:05:05 +02:00
PozzettiAndrea feb273ad0a Isolation-clean frontend: delete dead JS, guard listeners, leave shared DOM
- 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.
2026-08-13 14:31:51 +02:00
PozzettiAndrea 9ebf36b571 Rename viewer bundles .js -> .mjs so they stop leaking into the main realm
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.
2026-08-13 11:41:01 +02:00
PozzettiAndrea 4709c14fd4 update alpha wrap workflow 2026-08-13 11:19:33 +02:00
PozzettiAndrea 1716b3ec56 Declare wire types in comfy-env-root.toml [types]; serialization.py (ADR-0015)
- comfy-env-root.toml [types]: TRIMESH custom; SKELETON/INTRINSICS/
  EXTRINSICS/VOXELGRID builtin (explicit inventory, validated by
  comfy-env at register_nodes).
- geometrypack_wire_types.py -> serialization.py, loaded by file path
  on both sides (comfy-env 0.4.16). The manual OpaquePayload
  ImportError branch is gone: deserialize registers only where trimesh
  imports; a trimesh-less host holds materialized receipts owned by
  comfy-env (safe across worker restarts -- the WinError 2 fix).
- Tag flips geompack.Trimesh -> trimesh.Trimesh (type-identity
  convention): packs that also declare trimesh.Trimesh interop without
  sharing code.
- [serializers] removed from nodes/comfy-env.toml (superseded, no
  backcompat); requirements pin comfy-env==0.4.16.

Verified end-to-end: textured 5k-vert mesh, bare-host parent (trimesh
blocked), producer worker killed, receipt forwarded to a fresh worker,
mesh rebuilt with uv + metadata intact.
2026-08-13 10:58:30 +02:00
PozzettiAndrea aebd6c43de immaculate requirements.txt 2026-08-12 17:43:21 +02:00
PozzettiAndrea 414f759791 Vendor frontend JS into javascript/; drop the prestartup copy dance
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.
2026-08-12 17:37:09 +02:00
PozzettiAndrea 541b4e5b88 adapted to new comfy-env 2026-08-11 20:33:08 +02:00
PozzettiAndreaandClaude Fable 5 eb9066ba26 wire_types: degrade to OpaquePayload when trimesh is absent this side
Prepares for removing trimesh from requirements.txt (host-env principle):
a trimesh-less host holds mesh frames opaquely and forwards them to
workers instead of crashing on ImportError.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 16:06:20 +02:00
PozzettiAndreaandClaude Fable 5 3704e4fff5 Adopt comfy-env serializer registry: Trimesh as shared-memory arrays
geompack.Trimesh wire type (ADR-0014): vertices/faces ride the
shared-memory path instead of the 3-copy pickle rung; UVs + texture
material preserved (material dropped gracefully on sides lacking its
deps); vertex colors TODO. Declared via [serializers].modules, loaded
parent-side and worker-side. Validated: wire-level frames + full worker
round-trip both directions.

Two transport hazards worked around and documented in-module: the
id()-keyed visited map serves wrong frames for GC'd temporaries (pass
long-lived arrays only), and the pickle fallback returns raw objects
instead of raising (verify frames before embedding).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 15:54:31 +02:00
PozzettiAndreaandClaude Fable 5 64cfa14c92 Declare ACCELERATOR='cuda' on the 7 CUDA-requiring leaf nodes
comfy-env accelerator rule: a node declares at most one accelerator,
meaning 'requires this backend at execution'. Dispatcher nodes stay
undeclared (accelerator-neutral); opportunistic-GPU backends with real
CPU fallbacks stay undeclared by design.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 13:51:11 +02:00
PozzettiAndrea 6ea98c3388 retired antiquated remeshblender node 2026-08-06 14:19:13 +02:00
PozzettiAndrea a0bd482f06 uv unwrap+remeshing workflows fixed 2026-08-06 14:10:08 +02:00
PozzettiAndrea a99f762556 added cuda testing 2026-08-06 12:29:39 +02:00
PozzettiAndrea 2a32ccd0bf cenv bump 2026-08-05 18:22:05 +02:00