Transform Gaussian
Rotate a gaussian splat without breaking its headlights
- ply_path
Your trained gaussian splat is lying on its side, or facing the wrong way, or doesn't line up with the camera path you want to fly through it. Rotating a mesh is a one-liner; rotating a gaussian splat is a trap. A splat isn't just points - every gaussian also has an orientation (a quaternion), and most carry view-dependent spherical-harmonic colors. Rotate the positions and ignore the rest, and you get a scene where specular highlights slide across surfaces like a broken lighthouse. TransformGaussian rotates the whole thing correctly.
It's part of the GaussianPack family (GitHub repo ComfyUI-TurntableGSViewer, self-labeled "GaussianPack" - same author as ComfyUI's HyWorld2 and TRELLIS.2 wrappers). The rotation order deliberately matches GeometryPack's TransformMesh._rotate, so mesh and gaussian versions of the same scene behave identically for the same angle inputs.
What "rotating a gaussian" actually means
Under the hood it does four things at once:
- rotates the
(x, y, z)positions by the XYZ Euler rotation - composes each per-splat quaternion with the global rotation (Hamilton product)
- rotates the surface normals, if the PLY carries them
- rotates the view-dependent SH AC coefficients (
f_rest_*) in the SH basis using Wigner D-matrices computed by Ivanic-Ruedenberg recursion
The DC term (f_dc_*, the base color) is rotation-invariant, so it's left alone - that's mathematically correct, not an oversight. The result is a genuinely re-oriented scene: turn it 90° and the highlights rotate with the geometry instead of smearing.
Inputs and output
ply_path- the 3DGS PLY to transform.rotate_x,rotate_y,rotate_z- degrees around each axis, each −360 to +360, applied in X→Y→Z order. 0 means no rotation on that axis.
The single output, ply_path, is the absolute path to a new PLY written into ComfyUI's output folder. The filename encodes the rotation plus a short hash of the inputs, so re-running with the same settings doesn't collide and two different rotations don't overwrite each other. Wire the result into PreviewGaussians or PreviewGaussianDual to check it, then on into a merge or export.
Installing it
cd ComfyUI/custom_nodes
git clone https://github.com/PozzettiAndrea/ComfyUI-TurntableGSViewer.git
cd ComfyUI-TurntableGSViewer
pip install -r requirements.txt --upgrade
python install.py
Restart ComfyUI. Manager users: search "GaussianPack". The pack's requirements include the experimental comfy-env installer (it fetches the pixi package manager on first install - let it finish), plus plyfile and a NanoGS git dependency. No model weights to download.
Gotchas
The output is a full rewritten PLY, so on a large scene this is a read-everything, write-everything operation - not instant, and it'll eat disk with a second copy of the file. Use it once to fix orientation, not in a tight loop. And if you need to compare against the original after rotating, that's precisely what PreviewGaussianDual is for - wire the source into one side and the transform output into the other, and the synchronized camera will show you that the rotation is clean and the highlights stayed put.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| ply_path | STRING | Path to a 3D Gaussian Splatting PLY file. | |
| rotate_x | FLOAT | 0-360–360 | Rotation around X axis (degrees). |
| rotate_y | FLOAT | 0-360–360 | Rotation around Y axis (degrees). |
| rotate_z | FLOAT | 0-360–360 | Rotation around Z axis (degrees). |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| ply_path | STRING | — |