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': ...}
Library to convert gaussian splats between PLY and SPZ formats
Based on the original C++ library.
Installation
npm install spz-js
Example (Node.js)
import {
createReadStream,
readFileSync,
writeFileSync,
unlinkSync
} from 'fs';
import { Readable } from 'stream';
import path from 'path';
import {
loadPly,
loadSpz,
serializePly,
serializeSpz
} from 'spz-js';
// Helper function to load either SPZ or PLY files
const loadFile = async (file) => {
const extension = path.extname(file);
if (extension === '.spz') {
const fileBuffer = readFileSync(file);
return await loadSpz(fileBuffer);
} else if (extension === '.ply') {
const fileStream = createReadStream(file);
const webStream = Readable.toWeb(fileStream);
return await loadPly(webStream);
}
throw new Error(`Unsupported file extension: ${extension}`);
};
const gs = await loadFile("gs.ply"); // or gs.spz
const plyData = serializePly(gs);
writeFileSync("gs.ply", Buffer.from(plyData));
const spzData = await serializeSpz(gs);
writeFileSync("gs.spz", Buffer.from(spzData));
Example (Browser)
import {
loadPly,
loadSpz
} from 'spz-js';
const loadFile = async (url) => {
const response = await fetch(url);
const extension = url.split('.').pop();
if (extension === 'spz') {
const buffer = await response.arrayBuffer();
return await loadSpz(buffer);
} else if (extension === 'ply') {
return await loadPly(response.body);
}
throw new Error(`Unsupported file extension: ${extension}`);
};
