Skip to content

Commit

Permalink
check coplanar, add test case
Browse files Browse the repository at this point in the history
  • Loading branch information
karhankayan authored and pvl-bot committed Oct 3, 2024
1 parent 8900ceb commit f5c715c
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 2 deletions.
6 changes: 6 additions & 0 deletions infinigen/core/constraints/constraint_language/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,11 +390,17 @@ class SupportedBy(Touching):
class CoPlanar(GeometryRelation):
margin: float = 0

# rev_normal: if True, align the normals so they face the SAME direction, rather than two planes facing eachother.
# typical use is for sink embedded in countertop
rev_normal: bool = False

__repr__ = no_frozenset_repr


@dataclass(frozen=True)
class StableAgainst(GeometryRelation):
margin: float = 0

# check_ if False, only check x/z stability, z is allowed to overhand.
# typical use is chair-against-table relation
check_z: bool = True
Expand Down
8 changes: 8 additions & 0 deletions infinigen/core/constraints/example_solver/geometry/dof.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ def rotate_object_around_axis(obj, axis, std, angle=None):
def check_init_valid(
state: state_def.State, name: str, obj_planes: list, assigned_planes: list, margins
):
"""
Check that the plane assignments to the object is valid. First checks that the rotations can be satisfied, then
checks that the translations can be satisfied. Returns a boolean indicating if the assignments are valid, the number
of degrees of freedom remaining, and the translation vector if the assignments are valid.
"""
if len(obj_planes) == 0:
raise ValueError(f"{check_init_valid.__name__} for {name=} got {obj_planes=}")
if len(obj_planes) > 3:
Expand All @@ -117,6 +122,9 @@ def check_init_valid(
)

def get_rot(ind):
"""
Get the rotation axis and angle needed to align the object's plane with the assigned plane.
"""
try:
a = obj_planes[ind][0]
b = assigned_planes[ind][0]
Expand Down
45 changes: 45 additions & 0 deletions infinigen/core/constraints/example_solver/geometry/stability.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,51 @@ def stable_against(
return True


@gin.configurable
def coplanar(
state: state_def.State,
obj_name: str,
relation_state: state_def.RelationState,
):
"""
check that the object's tagged surface is coplanar with the target object's tagged surface translated with margin.
"""

relation = relation_state.relation
assert isinstance(relation, cl.CoPlanar)

logger.debug(f"coplanar {obj_name=} {relation_state=}")
a_blender_obj = state.objs[obj_name].obj
b_blender_obj = state.objs[relation_state.target_name].obj

pa, pb = state.planes.get_rel_state_planes(state, obj_name, relation_state)

poly_a = state.planes.planerep_to_poly(pa)
poly_b = state.planes.planerep_to_poly(pb)

normal_a = iu.global_polygon_normal(a_blender_obj, poly_a)
normal_b = iu.global_polygon_normal(b_blender_obj, poly_b)
dot = np.array(normal_a).dot(normal_b)
if not (np.isclose(np.abs(dot), 1, atol=1e-2) or np.isclose(dot, -1, atol=1e-2)):
logger.debug(f"coplanar failed, not parallel {dot=}")
return False

origin_b = iu.global_vertex_coordinates(
b_blender_obj, b_blender_obj.data.vertices[poly_b.vertices[0]]
)

for vertex in poly_a.vertices:
vertex_global = iu.global_vertex_coordinates(
a_blender_obj, a_blender_obj.data.vertices[vertex]
)
distance = iu.distance_to_plane(vertex_global, origin_b, normal_b)
if not np.isclose(distance, relation_state.relation.margin, atol=1e-2):
logger.debug(f"coplanar failed, not close to {distance=}")
return False

return True


def snap_against(scene, a, b, a_plane, b_plane, margin=0):
"""
snap a against b with some margin.
Expand Down
13 changes: 12 additions & 1 deletion infinigen/core/constraints/example_solver/geometry/validity.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
any_touching,
constrain_contact,
)
from infinigen.core.constraints.example_solver.geometry.stability import stable_against
from infinigen.core.constraints.example_solver.geometry.stability import (
coplanar,
stable_against,
)
from infinigen.core.constraints.example_solver.state_def import State
from infinigen.core.util import blender as butil

Expand Down Expand Up @@ -76,6 +79,14 @@ def all_relations_valid(state, name):
f"{name} failed relation {i=}/{len(rels)} {relation_state.relation} on {relation_state.target_name}"
)
return False

case cl.CoPlanar(_child_tags, _parent_tags, _margin):
res = coplanar(state, name, relation_state)
if not res:
logger.debug(
f"{name} failed relation {i=}/{len(rels)} {relation_state.relation} on {relation_state.target_name}"
)
return False
case _:
raise TypeError(f"Unhandled {relation_state.relation}")

Expand Down
82 changes: 81 additions & 1 deletion tests/solver/test_stable_against.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,50 @@ def make_scene(loc2):
return state_def.State(objs=objs)


def make_scene_coplanar(loc2):
"""Create a scene with a table and a cup, and return the state."""
butil.clear_scene()
objs = {}

table = butil.spawn_cube(scale=(5, 5, 1), name="table")
cup = butil.spawn_cube(scale=(1, 1, 1), name="cup", location=loc2)

for o in [table, cup]:
butil.apply_transform(o)
parse_scene.preprocess_obj(o)
tagging.tag_canonical_surfaces(o)

assert table.scale == Vector((1, 1, 1))
assert cup.location != Vector((0, 0, 0))

bpy.context.view_layer.update()

objs["table"] = state_def.ObjectState(table)
objs["cup"] = state_def.ObjectState(cup)
objs["cup"].relations.append(
state_def.RelationState(
cl.StableAgainst({t.Subpart.Bottom}, {t.Subpart.Top}),
target_name="table",
child_plane_idx=0,
parent_plane_idx=0,
)
)
back = {t.Subpart.Back, -t.Subpart.Top, -t.Subpart.Front}
back_coplanar_back = cl.CoPlanar(back, back, margin=0)

objs["cup"].relations.append(
state_def.RelationState(
back_coplanar_back,
target_name="table",
child_plane_idx=0,
parent_plane_idx=0,
)
)
butil.save_blend("test.blend")

return state_def.State(objs=objs)


def test_stable_against():
# too low, intersects ground
assert not validity.check_post_move_validity(make_scene((0, 0, 0.5)), "cup")
Expand Down Expand Up @@ -150,5 +194,41 @@ def test_horizontal_stability():
# butil.save_blend('test.blend')


def test_coplanar():
# Test case 1: Cup is stable against but not coplanar (should be invalid)
assert not validity.check_post_move_validity(make_scene_coplanar((0, 0, 1)), "cup")

# Test case 2: Cup is stable against and coplanar with the table (should be valid)
assert validity.check_post_move_validity(make_scene_coplanar((-2, 0, 1)), "cup")

# Test case 3: Cup is coplanar but not stable against (should be invalid)
assert not validity.check_post_move_validity(
make_scene_coplanar((-5.2, 0, 1)), "cup"
)

# Test case 4: Cup is neither stable against nor coplanar (should be invalid)
assert not validity.check_post_move_validity(
make_scene_coplanar((2, 2, 1.1)), "cup"
)

# Test case 5: Cup is at the back edge, stable against and coplanar (should be valid)
assert validity.check_post_move_validity(make_scene_coplanar((-2, 2, 1)), "cup")

# Test case 6: Cup is slightly off the back edge, not stable against but coplanar (should be invalid)
assert not validity.check_post_move_validity(
make_scene_coplanar((-2.1, 2, 1)), "cup"
)

# Test case 7: Cup is far from the table (should be invalid)
assert not validity.check_post_move_validity(
make_scene_coplanar((10, 10, 10)), "cup"
)

# Test case 8: Cup is inside the table, not stable against but coplanar (should be invalid)
assert not validity.check_post_move_validity(make_scene_coplanar((-2, 0, 0)), "cup")

print("All test cases for coplanar constraint passed successfully.")


if __name__ == "__main__":
test_horizontal_stability()
test_coplanar()

0 comments on commit f5c715c

Please sign in to comment.