CV Annotate Points On Model
Click anchors onto a 3D model, in its own coordinate frame
- model_3d
- points_3d
- count
You have a GLB, and you need to say "this bolt, that corner, the middle of that panel" in a way the rest of the graph can use as numbers. Typing 3D coordinates by hand is guesswork; this node hands you an orbit viewer, you click the surface, and the picks come out as an Nx3 array in the model's own frame - which is what makes them ride the object once something downstream moves it.
Why you'd reach for it
3D work in ComfyUI is a set of hard geometric problems - pose solving, ICP, point-pair feature matching, projection through a camera - and nearly all of them need object-frame points as their starting point. This is how you get them without a measuring tool.
- Feed
CV Project Points (Sequence)to see where those anchors land in each frame of a solved pose track, and check the pose is right. - Seed a tracker or a fit with correspondences you chose by eye, rather than ones an automatic detector offered. You know which features are stable on this model; the detector doesn't.
- Pair it with
CV Mesh From 3D Modelwhen you need geometry-derived points - but note the tooltip's warning, and use the same file for both.
The viewer is deliberately uncalibrated: no camera intrinsics, no distortion model, no pose. For pointing at parts of an object that's a feature, not a limitation - a free camera is the fastest way to reach the far side of a model to click a face you can't see from the front.
How it works
The model loads into a three.js viewport inside the node (GLB via GLTFLoader, the same viewer machinery as the pack's calibrated preview). Orbit to the angle you want, LEFT-CLICK the surface to drop an anchor, SHIFT+CLICK an existing marker to remove it. Dragging orbits rather than adding points, so you can never accidentally litter the model while looking around.
Picks are stored in the points widget as JSON - [[x, y, z], ...] - in the coordinates the file stores (glTF is Y-up). That's what makes them saveable, pasteable, diffable and hand-editable: if you know a vertex should be at an exact position, type it.
The axis_convention combo decides the frame the output is expressed in. The default, OpenCV (Y down, Z into the scene), matches what CV Mesh From 3D Model emits by default, so anchors and mesh geometry land in the same frame as a pose solved against them. Get that wrong and your anchors mirror through the object - a failure that looks like a solver bug and isn't.
The inputs and outputs
model_3d- aFILE_3D_GLB/FILE_3D_GLTF/FILE_3Dfrom a 3D loader node. Use the same file you feed the mesh node, or the anchors refer to different geometry.points- the JSON anchors, multiline, written by the viewer. Empty or[]is a valid resting state, not an error.axis_convention- which convention the output is in, default OpenCV.
Outputs:
points_3d- Nx3 float32 anchors in the model's frame, in click order. That ordering is the contract: the Nth anchor keeps the Nth caption if you're labelling with something likeCV Text To Array.count- how many, with 0 being legitimate. The node emits a(0, 3)array rather than failing, so a graph executes before you've clicked anything - which matters when you're wiring a pipeline and don't want to click first.
Install
ComfyUI Manager → ComfyUI CV, or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
Restart, and hard-reload the page - the viewer is front-end code and won't appear on a stale frontend. Dependency is opencv-contrib-python-headless~=5.0.0.93, plus Python 3.12+ and a V3-API ComfyUI. Your GLB is just a file in input/3d, the folder core's Load 3D nodes list.
Traps
- Same file, both nodes. Anchors fitted against a mesh from a different export are subtly wrong, not obviously wrong - and the coordinates still look plausible.
- Axis mismatch mirrors the model. If your anchors come out flipped through the object, check
axis_conventionbefore you debug anything else in the chain. - Click order is load-bearing.
points_3dis in the order you clicked, so re-clicking a point to fix its position moves it to the end of the array and desynchronises any caption list. Edit the JSON instead when the order matters. - No snapping to vertices. Surface picks land where your cursor ray hit the triangle, not on a vertex or an edge midpoint. If a downstream solver wants exact features, type the numbers in.
- GLB only. The viewer uses three.js's glTF loader; other formats won't load, whatever the socket type suggests.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| model_3d | FILE_3D_GLB,FILE_3D_GLTF,FILE_3D | Model to annotate, from a 3D loader node. Use the SAME file you feed 'CV Mesh From 3D Model', or the anchors will refer to different geometry. | |
| points | STRING | [] | The picked anchors as JSON, [[x, y, z], ...], in the coordinates the FILE stores (glTF's Y-up). The viewer writes this as you click; edit it by hand for exact values. Empty or '[]' means no anchors yet - a valid state, not an error. |
| axis_convention | COMBO | OpenCV (Y down, Z into the scene) | Which convention the OUTPUT is in. Leave it on OpenCV to match 'CV Mesh From 3D Model's default, so the anchors land in the same frame as the mesh a pose was solved against. Mismatch it and the anchors mirror through the object. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| points_3d | NPARRAY | Nx3 float32 anchors in the model's own frame, in the order they were clicked - so the Nth anchor keeps the Nth caption from 'CV Text To Array'. |
| count | INT | How many anchors are set. 0 is valid: the node emits an empty (0, 3) array rather than failing, so a graph still executes before you have clicked anything. |