kubakub mesh to relief field
Turning a model into a millable relief in millimetres
- field
- preview
- undercut
- report
- height
Here's a use case almost nobody plans for and a surprising number of people end up with: you generated a facade design, the client liked it, and now there's a call for a physical relief panel at a scale in millimetres. Suddenly you need a heightfield, not a mesh, and the rules of the competition care about footholds. kubakub mesh to relief field is the bridge from one to the other.
It takes a finished mesh - your own, or one that came out of an image-to-3D tool - looks at it from one direction, and flattens what it sees into a relief field measured in real millimetres, ready for the rest of the pack's fabrication nodes: relief safety check, relief mould prep, relief mass estimate, relief export panel (binary STL) and relief export prow (OBJ).
The mechanism
It rasterises the mesh orthographically from your chosen direction, keeping a depth buffer: for each output pixel, the distance to the nearest surface. That's the great advantage of this approach over image-to-3D - you never generate geometry, you only sample geometry you already have, so there's no invented detail and no triangle soup to clean up. Undercuts are found by counting how many layers of geometry sit behind that first hit: more than one means the geometry behind is hidden, and a one-piece mould or a single milling setup can't reach it.
Depth values are converted to millimetres using mesh_units, and the back of the relief is set to the lowest hit, so 0 mm is the back plane. clamp_relief_mm cuts it off at a given depth if the model has a spire you don't want to mill.
The inputs you set
mesh_path is the file. OBJ always works; GLB, GLTF, PLY and STL need trimesh installed. Empty means the built-in sample facade - metres, Y up - so you can run the chain with no file at all. The node tracks the file's timestamp, so editing and re-exporting the mesh re-runs it.
mesh_units (mm, cm, m, inch) is what makes this a fabrication tool rather than a preview: it's how the relief comes out in real millimetres. up_axis picks Y or Z - Y for most OBJ and GLB exports, Z for some CAD tools. Get either of these wrong and your panel is the right shape at the wrong size, which is worse than being obviously broken.
azimuth_deg and elevation_deg are the viewing direction: 0 degrees looks from the front, and elevation above 0 looks down from above. For a facade panel, that's usually 0 and 0 - you want the elevation, not the model's three-quarter view. resolution (default 768 px wide, height follows the mesh) is your detail budget; higher is finer and slower.
ground_offset_mm tells the pack how high the model's lowest point sits above the ground. It exists for the safety check rather than for this node: a foothold at 2 m is reachable, the same foothold at 4 m isn't, and the check can only know if you've told it where the ground is.
Outputs
field is the relief, in millimetres, for the other fabrication nodes. preview is a grey look at it, white = nearest to the viewer - the fastest way to spot an inverted up-axis, because the whole thing comes out upside down. undercut is the mask of everything a one-piece mould can't reach; that's the honest conversation-starter for whether this design can be made as one piece at all. report is JSON: size, depth, height above ground, and the share of undercuts. height is the relief as a mask, 0 = the back and 1 = the nearest point, with the real range in millimetres in the report (height_range_mm) - which makes it usable as a displacement or depth map in an ordinary ComfyUI graph, not just in the fabrication chain.
Install
cd ComfyUI/custom_nodes && git clone https://github.com/kubakubkub/ComfyUI_KubaNodes
python -m pip install -r ComfyUI_KubaNodes/requirements.txt
python -m pip install trimesh # only if your mesh isn't an OBJ
trimesh is deliberately not in the pack's requirements.txt. The author's reasoning is good: it's needed by exactly one node, so it's optional, and the console tells you at startup which features are off - you'll see a line like [Kub] not installed (only these features are off): mesh to field -> pip install trimesh. No Blender needed for this node; it's all numpy.
Where it fits, and the honest caveat
This is the node that makes "I generated a 3D model, can I build it" answerable. Generated meshes are fine for this job in a way they aren't for most: a relief panel has no rig, no animation, no UV expectations. You need a silhouette and a surface, and you have both. The topology being ugly is irrelevant when all you're doing is measuring depth from one direction.
Two things to watch. First, direction matters more than you think: sample a facade from the front and you get a clean elevation with sensible depths; sample it from 30 degrees and you get an undercut mask full of holes, because every window reveal now hides part of the wall behind it. Pick the direction the piece will actually be milled and viewed from.
Second, the relief field carries its own millimetre scale from here on. You won't type a dimension again in the chain - which is good, and also means an error in mesh_units propagates silently all the way to an STL. Check the report's size numbers against the client's brief before you let it run to the export.
Inputs (8)
| Name | Type | Default | Description |
|---|---|---|---|
| mesh_path | STRING | Full path of the mesh file. OBJ always works; GLB, GLTF, PLY and STL need trimesh installed. The node re-runs when the file changes. Empty = the built-in sample facade (metres, Y up). | |
| mesh_units | COMBO | m | The unit the mesh was modelled in, so the relief comes out in real millimetres. |
| up_axis | COMBO | Y | Which axis points up in the mesh file (Y for most OBJ / GLB exports, Z for some CAD tools). |
| azimuth_deg | FLOAT | 0-360–360 | Direction you look at the mesh from, turning around the up axis, in degrees. 0 = from the front (+Z for Y up). |
| elevation_deg | FLOAT | 0-89–89 | Tilt of the view, in degrees: above 0 looks down from above, 0 = straight on. |
| resolution | INT | 768128–4096 | Width of the relief in pixels; the height follows the mesh. Higher = finer but slower. |
| ground_offset_mm | FLOAT | 0-5000–5000 | Added to the mesh's own height, in mm: how high its lowest point sits above the ground (for the safety check's reach). 0 = as modelled. |
| clamp_relief_mm | FLOAT | 00–5000 | Cut the relief off at this depth, in mm, measured from the back. 0 = no limit. |
Outputs (5)
| Name | Type | Description |
|---|---|---|
| field | RELIEFFORGE_FIELD | The mesh as a relief in mm, for the other relief nodes. |
| preview | IMAGE | Grey preview of the relief (white = nearest to the viewer). |
| undercut | MASK | White where geometry hides behind other geometry from this direction (an undercut). |
| report | STRING | Size, depth, height above ground and the share of undercuts (json). |
| height | MASK | The relief as a mask: 0 = the back, 1 = the nearest point. The report gives the range in mm (height_range_mm), to use it as a depth or displacement map. |