Skip to content

Commit 71d93b7

Browse files
committed
Initial release: PlyPreview nodes and viewer
0 parents  commit 71d93b7

12 files changed

+4166
-0
lines changed

LICENSE

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
GNU GENERAL PUBLIC LICENSE
2+
Version 3, 29 June 2007
3+
4+
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5+
Everyone is permitted to copy and distribute verbatim copies
6+
of this license document, but changing it is not allowed.
7+
8+
[Full GPLv3 text omitted for brevity. Obtain the complete license at https://www.gnu.org/licenses/gpl-3.0.txt]

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# ComfyUI PlyPreview
2+
3+
Gaussian Splat PLY loader + preview nodes for ComfyUI. Forked from GeometryPack and repackaged to avoid upstream overwrites. Includes front-end viewer (gsplat.js) bundled locally.
4+
5+
## Nodes
6+
- Load Gaussian PLY — dropdown from `input/`, `input/3d`, `output/` with auto-resolution, FOV 10–180°, optional opacity filter.
7+
- Load Gaussian PLY (Path) — manual path entry with the same camera/auto-res options.
8+
- Process Gaussian PLY — accept upstream `ply_path` (e.g., SHARP Predict) with camera override/opacity filter.
9+
- Preview Gaussian — gsplat.js WebGL viewer with scale slider, reset, screenshot, info panel.
10+
11+
## Features
12+
- Auto resolution from FOV + target scale (rounded to 16px, calibration factor 0.8).
13+
- Opacity filtering (sigmoid) with threshold.
14+
- Camera intrinsics/extrinsics outputs for consistent preview.
15+
- Wide FOV (10–180°) including fisheye cases.
16+
- Fully self-contained: viewer HTML/JS bundled under `web/`.
17+
18+
## Install (Git clone)
19+
```bash
20+
cd ComfyUI/custom_nodes
21+
git clone https://github.com/XuanYu-github/comfyui-PlyPreview.git
22+
```
23+
Restart ComfyUI.
24+
25+
## Install (ComfyUI Manager)
26+
Search for “comfyui-PlyPreview” in Manager and install. (This repo ships `comfyui_extension.json` so Manager can detect it.)
27+
28+
## Usage
29+
1) Use one of the Load nodes or Process node to produce `ply_path`, `extrinsics`, `intrinsics`.
30+
2) Connect to Preview Gaussian. After execution the viewer iframe shows controls (Scale, Reset View, Screenshot) and info panel.
31+
3) If opacity filtering is enabled, a filtered PLY is written alongside the source (suffix `_opacity{threshold}.ply`).
32+
33+
## Requirements
34+
- ComfyUI recent build with DOM widgets enabled (standard).
35+
- Python deps: `plyfile`, `numpy` (install via your ComfyUI environment). Most ComfyUI setups already have numpy; install plyfile if missing: `pip install plyfile`.
36+
37+
## Notes
38+
- WEB_DIRECTORY is set to `web` so ComfyUI loads the bundled frontend automatically.
39+
- Namespaces are prefixed `PlyPreview*` to avoid collision with GeometryPack.
40+
- Original GeometryPack license is GPL-3.0; this fork keeps the same license.
41+
42+
## License
43+
GPL-3.0-or-later. See [LICENSE](LICENSE).

__init__.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# SPDX-License-Identifier: GPL-3.0-or-later
2+
# Copyright (C) 2025 comfyui-PlyPreview Contributors
3+
4+
"""ComfyUI PLY Preview - Gaussian splat PLY file loading and preview nodes."""
5+
6+
from .load_gaussian_ply import LoadGaussianPLY
7+
from .load_gaussian_ply_path import LoadGaussianPLYPath
8+
from .process_gaussian_ply import ProcessGaussianPLY
9+
from .preview_gaussian import PreviewGaussianNode
10+
11+
WEB_DIRECTORY = "web"
12+
13+
NODE_CLASS_MAPPINGS = {
14+
"PlyPreviewLoadGaussianPLY": LoadGaussianPLY,
15+
"PlyPreviewLoadGaussianPLYPath": LoadGaussianPLYPath,
16+
"PlyPreviewProcessGaussianPLY": ProcessGaussianPLY,
17+
"PlyPreviewPreviewGaussian": PreviewGaussianNode,
18+
}
19+
20+
NODE_DISPLAY_NAME_MAPPINGS = {
21+
"PlyPreviewLoadGaussianPLY": "Load Gaussian PLY",
22+
"PlyPreviewLoadGaussianPLYPath": "Load Gaussian PLY (Path)",
23+
"PlyPreviewProcessGaussianPLY": "Process Gaussian PLY",
24+
"PlyPreviewPreviewGaussian": "Preview Gaussian",
25+
}
26+
27+
__all__ = [
28+
"NODE_CLASS_MAPPINGS",
29+
"NODE_DISPLAY_NAME_MAPPINGS",
30+
"LoadGaussianPLY",
31+
"LoadGaussianPLYPath",
32+
"ProcessGaussianPLY",
33+
"PreviewGaussianNode",
34+
]

comfyui_extension.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "comfyui-PlyPreview",
3+
"display_name": "PlyPreview",
4+
"description": "Gaussian Splat PLY loader + preview nodes (Load, Load Path, Process, Preview) with bundled gsplat viewer.",
5+
"author": "XuanYu-github",
6+
"repo": "https://github.com/XuanYu-github/comfyui-PlyPreview",
7+
"license": "GPL-3.0-or-later"
8+
}

common.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
import os
4+
5+
# Optional ComfyUI helpers – avoid hard dependency for offline editing
6+
try:
7+
import importlib
8+
folder_paths = importlib.import_module("folder_paths") # type: ignore[import-not-found]
9+
COMFYUI_INPUT_FOLDER: str | None = folder_paths.get_input_directory()
10+
COMFYUI_OUTPUT_FOLDER: str | None = folder_paths.get_output_directory()
11+
except Exception: # pragma: no cover - environment-specific
12+
folder_paths = None # type: ignore[assignment]
13+
COMFYUI_INPUT_FOLDER = None
14+
COMFYUI_OUTPUT_FOLDER = None
15+
16+
17+
def get_default_extrinsics() -> list[list[float]]:
18+
"""Return default 4x4 identity extrinsics matrix (camera at origin)."""
19+
return [
20+
[1.0, 0.0, 0.0, 0.0],
21+
[0.0, 1.0, 0.0, 0.0],
22+
[0.0, 0.0, 1.0, 0.0],
23+
[0.0, 0.0, 0.0, 1.0],
24+
]
25+
26+
27+
def get_default_intrinsics(width: int = 512, height: int = 512, fov_degrees: float = 50.0) -> list[list[float]]:
28+
"""Return default 3x3 intrinsics matrix from FOV."""
29+
import math
30+
31+
fov_rad = math.radians(fov_degrees)
32+
fx = width / (2.0 * math.tan(fov_rad / 2.0))
33+
fy = fx # assume square pixels
34+
cx = width / 2.0
35+
cy = height / 2.0
36+
37+
return [
38+
[fx, 0.0, cx],
39+
[0.0, fy, cy],
40+
[0.0, 0.0, 1.0],
41+
]
42+
43+
44+
def get_recommended_resolution(fov_degrees: float, target_scale: float = 10.0) -> tuple[int, int]:
45+
"""Get recommended width/height based on FOV and target gaussian scale."""
46+
reference_points = [
47+
(10.0, 61440.0),
48+
(30.0, 15360.0),
49+
(50.0, 10240.0),
50+
(100.0, 4096.0),
51+
(120.0, 2560.0),
52+
(150.0, 1920.0),
53+
(180.0, 1600.0),
54+
]
55+
56+
fov_clamped = max(10.0, min(180.0, fov_degrees))
57+
58+
product = 0.0
59+
if fov_clamped <= reference_points[0][0]:
60+
product = reference_points[0][1]
61+
elif fov_clamped >= reference_points[-1][0]:
62+
product = reference_points[-1][1]
63+
else:
64+
for i in range(len(reference_points) - 1):
65+
fov1, prod1 = reference_points[i]
66+
fov2, prod2 = reference_points[i + 1]
67+
if fov1 <= fov_clamped <= fov2:
68+
t = (fov_clamped - fov1) / (fov2 - fov1)
69+
product = prod1 + t * (prod2 - prod1)
70+
break
71+
72+
base_resolution = product / 10.0
73+
target_resolution = base_resolution * (10.0 / target_scale)
74+
75+
calibration_factor = 0.8
76+
target_resolution = target_resolution * calibration_factor
77+
78+
resolution = int(target_resolution + 0.5)
79+
resolution = max(256, min(8192, resolution))
80+
resolution = ((resolution + 7) // 16) * 16
81+
82+
return (resolution, resolution)

0 commit comments

Comments
 (0)