CV Convert Axis Convention (3D)
The axis flip that quietly mirrors your 3D scene
- points
- points
What it's for
Two coordinate systems live inside ComfyUI's 3D corner and they disagree about which way is up. OpenCV - which is what every solver, camera matrix, pose and depth map in this pack speaks - has Y pointing down and Z pointing into the scene. glTF and Three.js - which is what core's Load3D, the preview widgets and the viewer gizmo speak - have Y pointing up and Z toward the viewer.
Cross the border between those worlds and your point cloud can come out mirrored. That's the specific failure this node exists to prevent, and it's a nasty one: the cloud has the right size, sits in the right place, lines up with its own scene, and is inside out. Nothing raises, nothing turns red. You notice a month later when the render shows the far side of the truck.
How it works
The conversion negates both Y and Z. That's the honest way to say it, because negating Y and Z together is exactly a 180° rotation about the X axis - a proper rotation. Negating only one of them is a reflection, and a reflected cloud is a mirror, not a turned one. The node's own source comment calls that out as the mistake it refuses to let you spell.
An Nx6 cloud (x, y, z, nx, ny, nz - the form the surface-matching nodes pass around) keeps its normals, and the normals get the same flip as the positions, because they're directions in the same space. An empty cloud comes back empty rather than raising, which is deliberate: a failed depth pass shouldn't kill the graph.
The inputs that matter
- points - an Nx3 cloud, an HxWx3 grid, or the Nx6 with normals.
- from_space -
OpenCV (Y down, Z into the scene)orglTF / Three.js (Y up). The author's own rule of thumb: a cloud fromCV Depth to 3D Points, a 3D loader or a PLY is in the glTF/Three.js convention; anything that's been through a pose, a camera matrix or a solver is in the OpenCV one. - to_space - the one you want out.
Output: points, float32, same shape as the input (Nx3 or Nx6), unchanged when the two spaces are the same.
Here's the part worth understanding: since the flip is its own inverse, only whether the two differ changes the numbers. Setting both correctly is documentation - it makes the graph state which way it's going instead of leaving the next reader to count minus signs. Set both anyway.
Install
ComfyUI Manager, search ComfyUI CV. Or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
Restart, then make sure the contrib OpenCV wheel is the one installed:
pip install "opencv-contrib-python-headless~=5.0.0.93"
Python ≥ 3.12 and a ComfyUI recent enough to have the V3 node API.
Where people get burned
A mirrored cloud passes every check. Right dimensions, right position, aligned to its own scene. In the 3D corner of ComfyUI, where the deliverable is often a point cloud or a splat that only ever gets looked at in a viewer, "the object is facing the wrong way" is the entire symptom. If a gizmo looks left-handed, suspect the axis convention before you suspect the model.
Two conversions is no conversion. Flip a cloud going into a solver, then flip it again on the way out to a viewer, and you're back where you started. If you converted twice and it still looks wrong, the problem is upstream of the axes.
Watch where you insert it. The author's placement advice is in front of CV Write PLY and behind CV Depth to 3D Points - i.e. at the boundary where OpenCV-flavoured data becomes viewer-flavoured data. Dropping it into the middle of a solver chain just flips data the solver wanted unflipped, and the solve will still "succeed".
The mesh loader is a separate setting. If your cloud comes from a mesh rather than depth, the convention is decided by the axis option on CV Mesh From 3D Model, not by this node. This one is the cloud-level counterpart.
Credit where it's due: the pack is a GPL-3.0 fork of geroldmeisinger's opencv-comfyui, written by bmad4ever as a personal project with heavy LLM assistance, and the author explicitly warns it isn't production-hardened. This node, though, is one of the small ones where the code is short enough to read in a minute - and you should, because the whole value is in which axes it touches.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| points | NPARRAY | Nx3 (or Nx1x3 / HxWx3) point cloud, or the Nx6 (x,y,z,nx,ny,nz) form the surface-matching nodes pass around - the normals are directions, so they get the same flip as the positions. | |
| from_space | COMBO | OpenCV (Y down, Z into the scene) | Which convention the incoming cloud is written in. A cloud from 'CV Depth to 3D Points', 'CV Write PLY' or a 3D loader is in the glTF/Three.js one; anything that has been through a pose, a camera matrix or a solver is in the OpenCV one. |
| to_space | COMBO | glTF / Three.js (Y up) | Which convention to leave it in. The flip is its own inverse, so only whether these two DIFFER changes the numbers - setting both is what makes the graph say which way it is going instead of leaving the reader to count minus signs. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| points | NPARRAY | The cloud in 'to_space', float32, same shape as the input (Nx3 or Nx6). Unchanged when the two spaces are the same. |