kubakub mesh turntable rule check
Walk around the mesh and find every foothold
- overlays
- report
- passes
- flagged
Public art calls have a clause that kills more submissions than budget does: no surfaces a person could climb, sit or lie on. A ledge 30 mm deep, 200 mm wide and below head height is a foothold by the letter of the brief, and on a relief panel full of window sills you will have dozens of them. kubakub mesh turntable check walks around your finished mesh, runs that rule set from every direction, and draws the offending geometry in red, orange and yellow.
What it checks
For each view it rasterises the mesh and evaluates the surface against the same rules as the pack's relief safety check:
- Foothold (red) - a step that sticks out at least foothold_depth_mm (default 25 mm) beyond everything within clearance_mm (default 150 mm) above it, runs at least min_width_mm (default 80 mm) continuously, and sits below reach_mm (default 2500 mm) above the ground.
- Seat (orange) - around 150 mm deep, between roughly 300 and 750 mm above the ground. Comfortable sitting height, which is exactly the problem.
- Lying surface (yellow) - flat, wide enough, at whatever height.
Then it rotates to the next azimuth and does it again. views (default 8, up to 24) is how many directions, evenly spaced; start_azimuth_deg is where the first one points. The default 8 covers every 45 degrees, which is right for a conceptual check. Bump it to 16 or 24 once the geometry is nearly final, because a shallow step that's invisible from the front is a proud ledge from the side.
The inputs that decide the verdict
mesh_path - OBJ always; GLB, GLTF, PLY and STL need trimesh. Empty runs the built-in sample facade (metres, Y up), so you can see what a failing panel looks like before you have one.
mesh_units (mm / cm / m / inch) and up_axis (Y or Z) have to be right or every threshold is meaningless. ground_offset_mm is how high the mesh's lowest point sits above the actual ground - this is what makes reach_mm a real height rather than a distance from an arbitrary model origin.
resolution (512 px default per view) is detail. Raise it for fine work: at 512 px across a 2 m panel, one pixel is about 4 mm, and a 25 mm foothold is six pixels of signal. That's enough, but not by a lot.
reach_mm, foothold_depth_mm, min_width_mm and clearance_mm are the rule set, and their defaults are a reasonable reading of a typical brief. Change them to match the actual document you're submitting against - that's the point of making the rules parameters.
Outputs
overlays is one image per view with the flagged areas coloured, which is what you put in the submission next to the fixed version. report is JSON: failing views, foothold count, the largest undercut share, and a per-view result. passes is a single BOOLEAN - true when every view passes - and that's a branchable output, so you can wire it into a decision rather than reading it. flagged is one mask per view, white where something was flagged, if you want to composite the check onto your own render.
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 for non-OBJ meshes
No Blender required. If trimesh is missing and your mesh is a GLB, the console told you at startup - [Kub] not installed (only these features are off): ....
How this actually changes your design
The workflow that matters is running the check twice. Once before the fix, so you have the overlay to show what was wrong, and once after, so you have the overlay to show it's fixed. The pack's own numbers make the case: on a test facade with protruding window sills, the anti-perch operation took 220 flagged footholds to zero while keeping 113.9 of 115 mm of relief depth. Losing 1 mm of depth to pass the rule is a trade you'd take every time, and without the check you'd never have known there were 220 problems.
Two honest limitations. This is a raster check at your chosen resolution - a step narrower than a pixel can slip between the posts, so don't treat a pass as certification. And it checks the sampled geometry from outside; it doesn't know about material behaviour, weathering, or the fact that an inspector might measure differently than you did. It's a design tool that catches the mistakes you'd otherwise catch in a rejection letter, and that's already worth the install.
Inputs (11)
| 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 check works 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). |
| views | INT | 81–24 | How many directions around the mesh are checked, evenly spaced (8 = every 45 degrees). |
| start_azimuth_deg | FLOAT | 0-360–360 | Direction of the first view around the up axis, in degrees. 0 = from the front. |
| resolution | INT | 512128–2048 | Width of each view in pixels. 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. 0 = as modelled. |
| reach_mm | FLOAT | 2500500–6000 | Height above the ground, in mm, up to which a foothold counts as reachable. |
| foothold_depth_mm | FLOAT | 255–300 | A step outward at least this deep, in mm, counts as a foothold. |
| min_width_mm | FLOAT | 8010–1000 | A foothold must run at least this wide, in mm, to count. |
| clearance_mmopt | FLOAT | 15020–1000 | Band above a step, in mm, that it is measured against: the step must stick out beyond everything in this band. |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| overlays | IMAGE | One image per view with the flagged areas coloured. |
| report | STRING | Failing views, footholds, the largest undercut share and the result per view (json). |
| passes | BOOLEAN | True when every view passes. |
| flagged | MASK | One mask per view, white where a foothold, seat or lying surface was flagged. |