14 Commits
Author SHA1 Message Date
PozzettiAndrea dabd3a5527 4 workflows 2026-06-07 09:00:00 +02:00
PozzettiAndrea 73b37ef2f8 update 2026-06-02 02:52:08 +02:00
PozzettiAndrea 09a97e1c09 ga 2026-05-29 19:53:20 +02:00
PozzettiAndrea b8de26b68c Drop comfy-env dependency: stdlib + relative import only
User wants GaussianPack fully self-contained — no comfy-env in the
runtime path. Three swaps + one orphan deletion:

1. prestartup_script.py: `comfy_env.copy_files(src, dst)` -> stdlib
   shutil.copy2 loop. Same semantics (top-level files, non-clobbering),
   6 lines. Fixes the NameError that snuck in earlier when the
   `from comfy_env import copy_files` line was removed without
   replacement; in the test workspace `apple.ply` actually lands in
   `input/` now.

2. __init__.py: `comfy_env.register_nodes()` -> `from .nodes import
   NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS`. That's literally
   what register_nodes() boils down to when no nodes/comfy-env.toml is
   present (the no-isolation branch). Hyphenated parent package names
   are fine for relative imports — verified earlier in this session
   with a Foo-Bar fixture via importlib.import_module.

3. nodes/__init__.py: docstring update — drop the "calls
   comfy_env.register_nodes()" mention now that the root does the
   relative import directly.

4. comfy-env-root.toml: deleted. The [node_reqs] section only had
   meaning for comfy-env's isolation discovery; with no comfy-env in
   play it was dead metadata.

`comfy-test.toml` stays (that's a different tool — comfy-test, not
comfy-env). `requirements.txt` stays (numpy + plyfile + NanoGS git).

`grep -rn comfy_env` across the repo returns 0 hits. Import smoke
test in the worker pixi env resolves all 5 nodes
(PreviewGaussians, GaussianMerge, LoadPLY, GaussianAnalysis,
GaussianExport).
2026-05-16 19:55:52 +02:00
PozzettiAndrea 6bac79935e Reorganize: python in nodes/, non-python in web/, root __init__ uses register_nodes
Match LiTo's project layout. Net file moves:

  spz_route.py             -> nodes/spz_route.py
  install.py               -> root (was briefly in nodes/)
  server_tools/            -> web/server_tools/    (Node JS subprocess
                                                    tool — non-python
                                                    asset, sits next to
                                                    browser-served web/)

Root and nodes/ now mirror ComfyUI-LiTo:

  Root: __init__.py, install.py, pyproject.toml, comfy-env-root.toml,
        comfy-test.toml, README.md, LICENSE, web/, scripts/, nodes/
  nodes/: __init__.py, comfy-env.toml, common.py, preview_gaussian.py,
          merge_gaussians.py, load_ply.py, spz_route.py

## Root __init__.py — LiTo-style

  import logging
  log = logging.getLogger("comfyui-gaussianpack")
  from comfy_env import register_nodes
  NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS = register_nodes()
  WEB_DIRECTORY = "./web"

Delegates node discovery to `comfy_env.register_nodes()`, which scans
`nodes/__init__.py` for the mappings. WEB_DIRECTORY stays at root
(points at top-level web/) so ComfyUI's static handler doesn't have to
walk into nodes/.

## nodes/__init__.py — clean child package

Imports PreviewGaussians, GaussianMerge, LoadPLY from sibling modules,
calls register_routes() for the SPZ HTTP route, exposes the two
mapping dicts. Dropped `WEB_DIRECTORY` (it belongs at root).

## nodes/comfy-env.toml — pixi env declaration

numpy + plyfile + nanogs (git pip). Same content as before — was
previously at root, where comfy-env's discovery wouldn't pick it up
for isolation purposes. Now lives where comfy-env expects.

## spz_route.py — path fix

NODE_SCRIPT was `THIS_DIR / "server_tools" / "ply_to_spz.mjs"` when the
file was at root. Now at nodes/ with server_tools moved to web/, so:

  NODE_SCRIPT = THIS_DIR.parent / "web" / "server_tools" / "ply_to_spz.mjs"

Smoke test (worker pixi env):
  PYTHONPATH=.../ComfyUI:. python -c "import nodes; print(nodes.NODE_CLASS_MAPPINGS)"
  -> {'PreviewGaussians': ..., 'GaussianMerge': ..., 'LoadPLY': ...}
2026-05-16 14:49:10 +02:00
PozzettiAndrea 0ce11ce8a4 Add LoadPLY file-picker node
User asked to "copy the Load PLY node from HallettVisual/comfyui-
GaussianViewer". After cloning + reading the source, that repo doesn't
ship a LoadPLY node — every PLY input there is forceInput-only
(GaussianViewer / GeomPackRenderGaussian / ExtrinsicsToPose). So
writing one from scratch in the spirit of the request.

`load_ply.py` scans both ComfyUI's input/ AND output/ directories
recursively for .ply files; the combo entries are prefixed with the
folder kind ("input/foo.ply", "output/lito_output.ply") to disambiguate
collisions. LiTo's LiToExportPLY writes to output/, so its files appear
in the dropdown directly — no manual copying.

Implements:
- INPUT_TYPES re-scans on each schema evaluation (no stale lists).
- IS_CHANGED keys on the picked file's mtime so re-runs catch edits.
- VALIDATE_INPUTS rejects entries that disappeared between schema
  fetch and execute.

Output is a STRING `ply_path` (absolute), so it slots directly into
PreviewGaussians / GaussianMerge's `ply_path` forceInput. Lets users
preview an arbitrary PLY without re-running LiTo, or pipe a
hand-edited PLY into GaussianMerge.
2026-05-15 18:32:25 +02:00
PozzettiAndrea 9137508fba Re-add GaussianMerge + Lito structure; rename to GaussianPack; trackball camera
The prior commit (6908dbe) was lost when the local repo was re-cloned
before its push could land. Redoing the full GaussianPack-side work
in one pass, plus two new asks from this session:

1. Display name: "Preview Gaussians Turntable" -> "Preview Gaussians".
2. Camera: OrbitControls -> TrackballControls. ParaView-style free
   rotation — no fixed up vector, no gimbal lock at the poles, can
   roll the camera. Replaces the "bound at azimuth and bottom" feel.

## Rename (mechanical sed pass)

  ComfyUI-GSViewer -> ComfyUI-GaussianPack
  GSViewer         -> GaussianPack         (log tags, extension name)
  gsviewer         -> gaussianpack         (route path, logger name)
  GSVIEWER         -> GAUSSIANPACK

Hit __init__.py, spz_route.py, pyproject.toml, comfyui_extension.json,
README.md, web/js/gaussian_preview.js.

## LiTo-style project structure

- comfy-test.toml       — test matrix; workflow-level testing
                           happens downstream in ComfyUI-LiTo.
- comfy-env-root.toml   — empty [node_reqs]; GaussianPack has no
                           downstream custom-node deps.
- comfy-env.toml        — numpy + plyfile + NanoGS via pypi-git.
- install.py            — two-liner mirroring LiTo's.
- requirements.txt      — DELETED, superseded by comfy-env.toml.

## GaussianMerge node

merge_gaussians.py: takes a 3DGS .ply + target_count, writes a slimmer
.ply via NanoGS pairwise merging (MPMM — same algorithm
@playcanvas/splat-transform's `--decimate` uses). Pure Python, CPU only;
the upstream paper notes a CUDA port as future work. ~10-30s for 1M ->
200K splats.

Lazy-imports nanogs.simplification + nanogs.utils.params inside the
merge method to keep import cost off ComfyUI startup. Caches via
mtime + (target_count, opacity_threshold, k) sidecar — re-running with
the same inputs is a no-op.

Pass-through if target >= input. ratio is clamped to NanoGS's required
open (0, 1) interval.

Node parameters:
  ply_path           STRING, forceInput
  target_count       INT, default 200_000
  output_filename    STRING, default "merged"
  opacity_threshold  FLOAT, default 0.1
  k                  INT, default 16

## Camera: TrackballControls

Vendored web/js/TrackballControls.js (three@0.169.0, ~18 KB) +
importmap entry alongside OrbitControls. The viewer now constructs
TrackballControls with rotateSpeed=3.5, zoomSpeed=1.2, panSpeed=0.8,
staticMoving=true (no inertia drift). ResizeObserver now calls
controls.handleResize() so mouse-to-NDC mapping stays correct after
canvas resize. .target / .update() / .enabled API surface is identical
between OrbitControls and TrackballControls — no callsite changes
needed in gaussian_preview.js.

OrbitControls.js stays vendored (still in importmap) for any future
node that wants the constrained "preserve up" behavior.

Workflow chain (LiTo apple):
  LoadImage -> LiToImageTo3D -> LiToExportPLY (~225 MB raw)
                                       |
                               GaussianMerge(target=200_000)
                                       |
                               PreviewGaussians (~50 MB)
2026-05-15 18:28:38 +02:00
PozzettiAndrea b60d7f5b8d Rename to ComfyUI-GSViewer; robust onConfigure migration
* Bulk rename "ComfyUI-TurntableGSViewer" -> "ComfyUI-GSViewer" across
  package metadata, route prefix (/turntablegsviewer/spz ->
  /gsviewer/spz), logger name, extension id, and all console-log tags.
* Extend onConfigure to defend against stale workflow schemas: rescues
  stranded camera-state JSON into properties["Camera Config"] and
  resets combo widgets whose value isn't in the option list. Fixes the
  validator-reject when an old-schema basic.json (camera state glued
  into widgets_values[3]) is loaded against today's renderer combo.
2026-05-14 15:07:22 +02:00
PozzettiAndrea 0f48ff93d2 spz route 2026-05-14 14:23:25 +02:00
PozzettiAndrea d20556eb29 playcanvas/spark dual backend 2026-05-14 13:02:34 +02:00
PozzettiAndrea 2839459333 add spz option 2026-05-13 13:26:00 +02:00
PozzettiAndreaandClaude Opus 4.7 21957052a8 rename display name to "Preview Gaussians Turntable"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:23:24 +02:00
PozzettiAndreaandClaude Opus 4.7 f452fc506e single PreviewGaussians node + tiny readme
Collapse the 4-node PlyPreview surface to one node:
- PreviewGaussians: takes ply_path (STRING, forceInput) plus camera-
  intrinsics widgets (fov_degrees, image_width, image_height) directly
  on the node. NODE_CLASS_MAPPINGS = {"PreviewGaussians": ...}.
- Drop load_gaussian_ply.py, load_gaussian_ply_path.py,
  process_gaussian_ply.py and the /plypreview/files refresh route.
- common.py stays — get_default_intrinsics + get_default_extrinsics
  are reused.
- Frontend JS unchanged except the preview block targets
  "PreviewGaussians". The LoadPLY-dropdown refresh block is left in
  place as harmless dead code in case the loader node comes back.
- requirements.txt cleared (no Python deps; PLY parsing is in the
  browser via gsplat.js).
- README slimmed to a few lines.
- Removed Figure1/2 + image/ + README.en.md + README.zh-CN.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:19:47 +02:00
PozzettiAndreaandClaude Opus 4.7 1dd817b154 initial copy from comfyui-PlyPreview as ComfyUI-TurntableGSViewer base
Copy of XuanYu-github/comfyui-PlyPreview (GPL-3.0) repackaged under
new name and ownership. Metadata changes only:
- pyproject.toml: project name + URLs + tool.comfy.DisplayName
- comfyui_extension.json: name / author / repo
- removed .github/workflows/publish_action.yml (upstream's auto-publish)

Internal class names, NODE_CLASS_MAPPINGS keys, CATEGORY strings,
and source code are intact for further customization.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:12:59 +02:00