Check what holds every object up, so nothing floats - #37
Open
ZheningHuang wants to merge 1 commit into
Open
Conversation
The clash check asks whether two things occupy the same volume. This asks
the opposite question: does every object TOUCH the thing that holds it up.
Indoors there are only four answers -- the floor, a wall, the top of another
object, or the ceiling -- and an object matching none of them is floating.
The support relation is derived, never declared, so the scene graph falls
out of the same pass: the surface an object rests on IS its parent. That
turns "everything traces back to structure" into a graph check rather than
more geometry.
Rays, not boxes. An object's box top is not its support surface: a table's
box top is the tabletop, but a chair's is the BACKREST, and nothing rests on
a backrest. Rays start from the object's own extreme vertices rather than a
grid over its footprint, because a grid sends most of its rays through the
empty air between a chair's legs while the extreme vertices ARE the feet.
Which face probes which support matters as much as the rays. The bottom face
only answers the DOWNWARD question -- a picture on a wall has nothing at all
beneath it -- so each hypothesis probes the face pointing at its own
candidate: bottom for floor/on-object, back for a wall, top for the ceiling.
Walls and the ceiling are planes we already know from the SHELL, so those
two are exact arithmetic and cost nothing.
Report only. A floating object is a MISSING SUPPORT, not a misplaced one --
the shelf it belongs on was never built -- so dropping it to the floor would
hide the real defect. Nothing here writes Room.py.
Three thresholds were set against a real room (Elliott-Studio) rather than
guessed:
* wall mounts get their own, much looser tolerance. A bracket or batten
legitimately holds a thing off the wall; a wall-hung TV and a wall
cabinet both stand 6 cm proud and at the resting tolerance read as
floating in mid-air. Safe to loosen because the downward test runs first
and wins, so nothing standing on the floor can be captured by it.
* the contact-face band is 2 cm, not razor-thin: a base a millimetre out of
level would otherwise offer a hairline strip of feet. Widening is free,
since a candidate that does not reach its support is dropped by its gap.
* buried rays count toward the stability hull. A face sunk into its support
is still HELD UP by it and its burial is already its own finding; judging
stability on the unburied rays alone turned one defect into two.
Two more cases the first real room forced:
* objects can rest on SEVERAL bodies -- a plank across two boxes, a tray
bridging two shelf levels. Keeping only the modal one puts a steady
plank's centre in the gap between the patches and calls it unstable.
* a pair buried in each other is reported from both sides, and some burial
is correct (a sink in its counter). One finding per pair, filtered
through the same EXPECTED_CONTAINMENT table the clash check uses, so the
two gates cannot contradict each other.
`support_pairs` exposes the graph as an allow-list: resting on something is
touching it, so a book on a shelf is contact the clash check should expect.
That is the job EXPECTED_CONTAINMENT does by category, except derived from
geometry, so it covers pairs nobody thought to enumerate.
On Elliott-Studio (20 objects) this resolves four objects that the collision
gate reports as floating -- Storage1/2/8/9 are wall-mounted cabinets, and
check_all exempts only _FLOOR_STANDING categories, which `storage` is in.
Relocking for rtree also picks up coacd, which #36 added to pyproject but
never locked.
Docs in doc/QC/support_check.md.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #36.
The clash check asks whether two things occupy the same volume. This asks the opposite question: does every object touch the thing that holds it up. Indoors there are only four answers — the floor, a wall, the top of another object, or the ceiling — and an object matching none of them is floating.
The support relation is derived, never declared, so the scene graph falls out of the same pass: the surface an object rests on IS its parent. That turns "everything traces back to structure" into a graph check rather than more geometry.
Mug0 → Shelf0 → Wall0never touches the floor and is still correct — which is why the rule is "the chain ends at structure", not "the object is above the floor".How
Rays, not boxes. An object's box top is not its support surface: a table's box top is the tabletop, but a chair's is the backrest, and nothing rests on a backrest. Rays start from the object's own extreme vertices rather than a grid over its footprint — a grid sends most of its rays through the empty air between a chair's legs, while the extreme vertices are the feet.
Which face probes which support. The bottom face only answers the downward question; a picture on a wall has nothing at all beneath it. So each hypothesis probes the face pointing at its own candidate:
Walls and the ceiling are planes we already know from the SHELL, so only the downward case needs rays — the only case where the identity of the supporting body is in question.
Report only. A floating object is a missing support, not a misplaced one — the shelf it belongs on was never built — so dropping it to the floor would hide the real defect. Nothing here writes
Room.py.Consumes the same
bodiesdict ascollision_mesh.build_bodies, so the two checks share one decomposition of the room and cannot disagree about what an object is.Thresholds set against a real room, not guessed
Two cases the first real room forced
EXPECTED_CONTAINMENTtable the clash check uses, so the two gates cannot contradict each other.support_pairs()exposes the graph as an allow-list: resting on something is touching it, so a book on a shelf is contact the clash check should expect. That is the jobEXPECTED_CONTAINMENTdoes by category, except derived from geometry — so it covers pairs nobody thought to enumerate.It finds a bug in the current gate
On Elliott-Studio (20 objects) the collision gate reports four objects floating —
Storage1/2/8/9. All four are wall-mounted cabinets:check_allexempts only_FLOOR_STANDINGcategories andstorageis in that set. The support check resolves all four to their walls.I have not wired
collision.py's grounding section to defer to this, since #36 is still in flight there. Happy to as a follow-up.Two findings survive on that room, both real:
Sofa0rests on the floor along a single 5 cm strip (the mesh is tilted ~2.7°), andRefrigerator0is 0.092 m intoStorageRun0— which the collision gate independently reports as a 0.771 m overlap, so it is a genuine clash andEXPECTED_CONTAINMENTwas left alone.Notes
rtree— trimesh's ray caster needs it for its broad phase, and this check is all ray casts. Small pure wheel, no compiler.coacd, which Measure collision fixes from convex pieces, and gate the glb on its own #36 added topyproject.tomlbut never locked —uv sync --lockedwould fail on Measure collision fixes from convex pieces, and gate the glb on its own #36 as it stands.Test plan
tests/test_support.py— 10 tests, synthetic boxes, no capture/Blender/glb needed: the ordinary floor→table→book chain; a wall-hung shelf with a mug on it (the case the bottom face cannot answer); a plank across two boxes (must read stable); a cup half off the table edge (must read unstable); a bowl sunk into the tabletop; a lamp in mid-air; a support cycle.578 passed, 2 skipped.ruff checkclean on the new files.