Add Normals to PointCloud
Point clouds without normals are half a cloud
- pointcloud
- pointcloud_with_normals
- info
A raw point cloud is just positions - a pile of coordinates with no idea which way the surface faces. A lot of downstream work needs those normals: Poisson surface reconstruction, curvature estimation, normal-based filtering, shading. This node estimates surface normals for a point cloud, which is the difference between "a cloud" and "a cloud that knows which way is out."
One important note up front: the input is called pointcloud and the tooltip is explicit - it will reject meshes with faces. This node is for true point clouds, and it's the natural follow-on to the pack's Mesh to Point Cloud (which outputs a face-less TRIMESH). Give it that, and you get back pointcloud_with_normals plus an info string.
The method dropdown picks the estimation strategy, each with its own parameters:
open3d_knn(default) - fits a local plane via PCA to each point'sk_neighborsnearest neighbors (default 30) and takes the plane normal. The workhorse: fast, robust, good on evenly-sampled clouds.open3d_radius- same PCA idea but using all points withinsearch_radiusinstead of a fixed count. Better on clouds with uneven density, where a fixed neighbor count misbehaves.pymeshlab_mls- moving least squares, driven bymls_smoothingiterations. Smoothes the local surface estimate as it goes, which helps on noisy capture data.
Two toggles finish it off:
orient_normals- try to orient all normals consistently (all pointing "out"). On its own, PCA can only estimate the normal line, not the direction - this is what disambiguates. Keep it on.add_as_attributes- also store the normals as per-vertex attributes (normal_x/y/z) so VTK-based visualization tools can read them directly.
Where it fits
The classic chain: Mesh to Point Cloud → this node → reconstruction or analysis. If your cloud came from a scan, run this before Poisson reconstruction - Poisson assumes oriented normals and will silently produce garbage without them. That's the failure mode to know: not an error, just a lumpy, inside-out surface.
Install
Part of the pack; install once, get all nodes:
cd ComfyUI/custom_nodes
git clone https://github.com/PozzettiAndrea/ComfyUI-GeometryPack.git
cd ComfyUI-GeometryPack
pip install -r requirements.txt --upgrade
python install.py
Or ComfyUI Manager → GeometryPack. The method dropdown depends on the bundled environments (Open3D and PyMeshLab both arrive via the pack's experimental comfy-env/pixi install, which can be a big first download). Fast-moving repo, reports to Discussions, all the usual.
The honest advice
Start with open3d_knn and only switch if the result looks wrong - the neighbor-count method is the default for a reason. If your cloud has wildly uneven density (dense clusters and sparse gaps), open3d_radius is the more principled pick. And don't skip orient_normals thinking you'll fix direction later - you usually can't, cheaply. Normals are the one thing everyone assumes you already did.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| pointcloud | TRIMESH | Input point cloud (will reject meshes with faces) | |
| method | COMBO | open3d_knn | Normal estimation method |
| k_neighborsopt | INT | 303–100 | [open3d_knn] Number of nearest neighbors for PCA |
| search_radiusopt | FLOAT | 0.0500.001–1 | [open3d_radius] Search radius for neighborhood (in normalized space) |
| mls_smoothingopt | INT | 51–20 | [pymeshlab_mls] MLS smoothing iterations |
| orient_normalsopt | BOOLEAN | true | Orient normals consistently across surface |
| add_as_attributesopt | BOOLEAN | true | Also store normals as vertex_attributes (normal_x/y/z) for VTK visualization |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| pointcloud_with_normals | TRIMESH | — |
| info | STRING | — |