From 9d8344b8e85a20391adffd6bff1ce4b947736558 Mon Sep 17 00:00:00 2001 From: DavidYan-1 Date: Mon, 15 Jul 2024 14:46:46 -0400 Subject: [PATCH 1/9] Create floating file --- infinigen_examples/generate_floating.py | 488 ++++++++++++++++++++++++ 1 file changed, 488 insertions(+) create mode 100644 infinigen_examples/generate_floating.py diff --git a/infinigen_examples/generate_floating.py b/infinigen_examples/generate_floating.py new file mode 100644 index 000000000..1ec8823a6 --- /dev/null +++ b/infinigen_examples/generate_floating.py @@ -0,0 +1,488 @@ +# Copyright (c) Princeton University. +# This source code is licensed under the BSD 3-Clause license found in the LICENSE file in the root directory +# of this source tree. + +import argparse +import logging +from pathlib import Path + +from numpy import deg2rad + +# ruff: noqa: E402 +# NOTE: logging config has to be before imports that use logging +logging.basicConfig( + format="[%(asctime)s.%(msecs)03d] [%(module)s] [%(levelname)s] | %(message)s", + datefmt="%H:%M:%S", + level=logging.INFO, +) + +import bpy +import gin +import numpy as np + +from infinigen.assets import lighting +from infinigen.assets.materials import invisible_to_camera +from infinigen.assets.objects.wall_decorations.skirting_board import make_skirting_board +from infinigen.assets.utils.decorate import read_co +from infinigen.core import execute_tasks, init, placement, surface, tagging +from infinigen.core import tags as t +from infinigen.core.constraints import checks +from infinigen.core.constraints import constraint_language as cl +from infinigen.core.constraints import reasoning as r +from infinigen.core.constraints.example_solver import ( + Solver, + greedy, + populate, + state_def, +) +from infinigen.core.constraints.example_solver.room import constants +from infinigen.core.constraints.example_solver.room import decorate as room_dec +from infinigen.core.constraints.example_solver.room.constants import WALL_HEIGHT +from infinigen.core.placement import camera as cam_util +from infinigen.core.util import blender as butil +from infinigen.core.util import pipeline +from infinigen.core.util.camera import points_inview +from infinigen.terrain import Terrain +from infinigen_examples.indoor_constraint_examples import home_constraints +from infinigen_examples.util import constraint_util as cu +from infinigen_examples.util.generate_indoors_util import ( + apply_greedy_restriction, + create_outdoor_backdrop, + hide_other_rooms, + place_cam_overhead, + restrict_solving, +) + +from . import generate_nature # noqa F401 # needed for nature gin configs to load + +logger = logging.getLogger(__name__) + + +def default_greedy_stages(): + """Returns descriptions of what will be covered by each greedy stage of the solver. + + Any domain containing one or more VariableTags is greedy: it produces many separate domains, + one for each possible assignment of the unresolved variables. + """ + + on_floor = cl.StableAgainst({}, cu.floortags) + on_wall = cl.StableAgainst({}, cu.walltags) + on_ceiling = cl.StableAgainst({}, cu.ceilingtags) + side = cl.StableAgainst({}, cu.side) + + all_room = r.Domain({t.Semantics.Room, -t.Semantics.Object}) + all_obj = r.Domain({t.Semantics.Object, -t.Semantics.Room}) + + all_obj_in_room = all_obj.with_relation( + cl.AnyRelation(), all_room.with_tags(cu.variable_room) + ) + primary = all_obj_in_room.with_relation(-cl.AnyRelation(), all_obj) + + greedy_stages = {} + + greedy_stages["rooms"] = all_room + + greedy_stages["on_floor"] = primary.with_relation(on_floor, all_room) + greedy_stages["on_wall"] = ( + primary.with_relation(-on_floor, all_room) + .with_relation(-on_ceiling, all_room) + .with_relation(on_wall, all_room) + ) + greedy_stages["on_ceiling"] = ( + primary.with_relation(-on_floor, all_room) + .with_relation(on_ceiling, all_room) + .with_relation(-on_wall, all_room) + ) + + secondary = all_obj.with_relation( + cl.AnyRelation(), primary.with_tags(cu.variable_obj) + ) + + greedy_stages["side_obj"] = secondary.with_relation(side, all_obj) + nonside = secondary.with_relation(-side, all_obj) + + greedy_stages["obj_ontop_obj"] = nonside.with_relation( + cu.ontop, all_obj + ).with_relation(-cu.on, all_obj) + greedy_stages["obj_on_support"] = nonside.with_relation( + cu.on, all_obj + ).with_relation(-cu.ontop, all_obj) + + return greedy_stages + + +all_vars = [cu.variable_room, cu.variable_obj] + + +@gin.configurable +def compose_indoors(output_folder: Path, scene_seed: int, **overrides): + p = pipeline.RandomStageExecutor(scene_seed, output_folder, overrides) + + logger.debug(overrides) + + def add_coarse_terrain(): + terrain = Terrain( + scene_seed, + surface.registry, + task="coarse", + on_the_fly_asset_folder=output_folder / "assets", + ) + terrain_mesh = terrain.coarse_terrain() + # placement.density.set_tag_dict(terrain.tag_dict) + return terrain, terrain_mesh + + terrain, terrain_mesh = p.run_stage( + "terrain", add_coarse_terrain, use_chance=False, default=(None, None) + ) + + p.run_stage("sky_lighting", lighting.sky_lighting.add_lighting, use_chance=False) + + consgraph = home_constraints() + stages = default_greedy_stages() + checks.check_all(consgraph, stages, all_vars) + + stages, consgraph, limits = restrict_solving(stages, consgraph) + + if overrides.get("restrict_single_supported_roomtype", False): + restrict_parent_rooms = { + np.random.choice( + [ + # Only these roomtypes have constraints written in home_constraints. + # Others will be empty-ish besides maybe storage and plants + # TODO: add constraints to home_constraints for garages, offices, balconies, etc + t.Semantics.Bedroom, + t.Semantics.LivingRoom, + t.Semantics.Kitchen, + t.Semantics.Bathroom, + t.Semantics.DiningRoom, + ] + ) + } + logger.info(f"Restricting to {restrict_parent_rooms}") + apply_greedy_restriction(stages, restrict_parent_rooms, cu.variable_room) + + solver = Solver(output_folder=output_folder) + + def solve_rooms(): + return solver.solve_rooms(scene_seed, consgraph, stages["rooms"]) + + state: state_def.State = p.run_stage("solve_rooms", solve_rooms, use_chance=False) + + def solve_large(): + assignments = greedy.iterate_assignments( + stages["on_floor"], state, all_vars, limits, nonempty=True + ) + for i, vars in enumerate(assignments): + solver.solve_objects( + consgraph, + stages["on_floor"], + var_assignments=vars, + n_steps=overrides["solve_steps_large"], + desc=f"on_floor_{i}", + abort_unsatisfied=overrides.get("abort_unsatisfied_large", False), + ) + return solver.state + + state = p.run_stage("solve_large", solve_large, use_chance=False, default=state) + + solved_rooms = [ + state.objs[assignment[cu.variable_room]].obj + for assignment in greedy.iterate_assignments( + stages["on_floor"], state, [cu.variable_room], limits + ) + ] + solved_bound_points = np.concatenate([butil.bounds(r) for r in solved_rooms]) + solved_bbox = ( + np.min(solved_bound_points, axis=0), + np.max(solved_bound_points, axis=0), + ) + + house_bbox = np.concatenate( + [ + butil.bounds(obj) + for obj in solver.get_bpy_objects(r.Domain({t.Semantics.Room})) + ] + ) + house_bbox = (np.min(house_bbox, axis=0), np.max(house_bbox, axis=0)) + + camera_rigs = placement.camera.spawn_camera_rigs() + + def pose_cameras(): + nonroom_objs = [ + o.obj for o in state.objs.values() if t.Semantics.Room not in o.tags + ] + scene_objs = solved_rooms + nonroom_objs + + scene_preprocessed = placement.camera.camera_selection_preprocessing( + terrain=None, scene_objs=scene_objs + ) + + solved_floor_surface = butil.join_objects( + [ + tagging.extract_tagged_faces(o, {t.Subpart.SupportSurface}) + for o in solved_rooms + ] + ) + + placement.camera.configure_cameras( + camera_rigs, + scene_preprocessed=scene_preprocessed, + init_surfaces=solved_floor_surface, + ) + + return scene_preprocessed + + scene_preprocessed = p.run_stage("pose_cameras", pose_cameras, use_chance=False) + + def animate_cameras(): + cam_util.animate_cameras(camera_rigs, solved_bbox, scene_preprocessed, pois=[]) + + p.run_stage( + "animate_cameras", animate_cameras, use_chance=False, prereq="pose_cameras" + ) + + p.run_stage( + "populate_intermediate_pholders", + populate.populate_state_placeholders, + solver.state, + filter=t.Semantics.AssetPlaceholderForChildren, + final=False, + use_chance=False, + ) + + p.run_stage( + "populate_assets", populate.populate_state_placeholders, state, use_chance=False + ) + + door_filter = r.Domain({t.Semantics.Door}, [(cl.AnyRelation(), stages["rooms"])]) + window_filter = r.Domain( + {t.Semantics.Window}, [(cl.AnyRelation(), stages["rooms"])] + ) + p.run_stage( + "room_doors", + lambda: room_dec.populate_doors(solver.get_bpy_objects(door_filter)), + use_chance=False, + ) + p.run_stage( + "room_windows", + lambda: room_dec.populate_windows(solver.get_bpy_objects(window_filter)), + use_chance=False, + ) + + room_meshes = solver.get_bpy_objects(r.Domain({t.Semantics.Room})) + p.run_stage( + "room_stairs", + lambda: room_dec.room_stairs(state, room_meshes), + use_chance=False, + ) + p.run_stage( + "skirting_floor", + lambda: make_skirting_board(room_meshes, t.Subpart.SupportSurface), + ) + p.run_stage( + "skirting_ceiling", lambda: make_skirting_board(room_meshes, t.Subpart.Ceiling) + ) + + rooms_meshed = butil.get_collection("placeholders:room_meshes") + rooms_split = room_dec.split_rooms(list(rooms_meshed.objects)) + + p.run_stage( + "room_walls", room_dec.room_walls, rooms_split["wall"].objects, use_chance=False + ) + p.run_stage( + "room_pillars", + room_dec.room_pillars, + state, + rooms_split["wall"].objects, + use_chance=False, + ) + p.run_stage( + "room_floors", + room_dec.room_floors, + rooms_split["floor"].objects, + use_chance=False, + ) + p.run_stage( + "room_ceilings", + room_dec.room_ceilings, + rooms_split["ceiling"].objects, + use_chance=False, + ) + + # state.print() + state.to_json(output_folder / "solve_state.json") + + cam = cam_util.get_camera(0, 0) + + def turn_off_lights(): + for o in bpy.data.objects: + if o.type == "LIGHT" and not o.data.cycles.is_portal: + print(f"Deleting {o.name}") + butil.delete(o) + + p.run_stage("lights_off", turn_off_lights) + + def invisible_room_ceilings(): + rooms_split["exterior"].hide_viewport = True + rooms_split["exterior"].hide_render = True + invisible_to_camera.apply(list(rooms_split["ceiling"].objects)) + invisible_to_camera.apply( + [o for o in bpy.data.objects if "CeilingLight" in o.name] + ) + + p.run_stage("invisible_room_ceilings", invisible_room_ceilings, use_chance=False) + + p.run_stage( + "overhead_cam", + place_cam_overhead, + cam=camera_rigs[0], + bbox=solved_bbox, + use_chance=False, + ) + + p.run_stage( + "hide_other_rooms", + hide_other_rooms, + state, + rooms_split, + keep_rooms=[r.name for r in solved_rooms], + use_chance=False, + ) + + height = p.run_stage( + "nature_backdrop", + create_outdoor_backdrop, + terrain, + house_bbox=house_bbox, + cam=cam, + p=p, + params=overrides, + use_chance=False, + prereq="terrain", + default=0, + ) + + if overrides.get("topview", False): + rooms_split["exterior"].hide_viewport = True + rooms_split["ceiling"].hide_viewport = True + rooms_split["exterior"].hide_render = True + rooms_split["ceiling"].hide_render = True + for group in ["wall", "floor"]: + for wall in rooms_split[group].objects: + for mat in wall.data.materials: + for n in mat.node_tree.nodes: + if n.type == "BSDF_PRINCIPLED": + n.inputs["Alpha"].default_value = overrides.get( + "alpha_walls", 1.0 + ) + bbox = np.concatenate( + [ + read_co(r) + np.array(r.location)[np.newaxis, :] + for r in rooms_meshed.objects + ] + ) + camera = camera_rigs[0].children[0] + camera_rigs[0].location = 0, 0, 0 + camera_rigs[0].rotation_euler = 0, 0, 0 + bpy.contexScene.camera = camera + rot_x = deg2rad(overrides.get("topview_rot_x", 0)) + rot_z = deg2rad(overrides.get("topview_rot_z", 0)) + camera.rotation_euler = rot_x, 0, rot_z + mean = np.mean(bbox, 0) + for cam_dist in np.exp(np.linspace(1.0, 5.0, 500)): + camera.location = ( + mean[0] + cam_dist * np.sin(rot_x) * np.sin(rot_z), + mean[1] - cam_dist * np.sin(rot_x) * np.cos(rot_z), + mean[2] - WALL_HEIGHT / 2 + cam_dist * np.cos(rot_x), + ) + bpy.context.view_layer.update() + inview = points_inview(bbox, camera) + if inview.all(): + for area in bpy.contexScreen.areas: + if area.type == "VIEW_3D": + area.spaces.active.region_3d.view_perspective = "CAMERA" + break + break + + return { + "height_offset": height, + "whole_bbox": house_bbox, + } + + +def main(args): + scene_seed = init.apply_scene_seed(args.seed) + init.apply_gin_configs( + configs=["base_indoors.gin"] + args.configs, + overrides=args.overrides, + config_folders=[ + "infinigen_examples/configs_indoor", + "infinigen_examples/configs_nature", + ], + ) + constants.initialize_constants() + + execute_tasks.main( + compose_scene_func=compose_indoors, + populate_scene_func=None, + input_folder=args.input_folder, + output_folder=args.output_folder, + task=args.task, + task_uniqname=args.task_uniqname, + scene_seed=scene_seed, + ) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--output_folder", type=Path) + parser.add_argument("--input_folder", type=Path, default=None) + parser.add_argument( + "-s", "--seed", default=None, help="The seed used to generate the scene" + ) + parser.add_argument( + "-t", + "--task", + nargs="+", + default=["coarse"], + choices=[ + "coarse", + "populate", + "fine_terrain", + "ground_truth", + "render", + "mesh_save", + "export", + ], + ) + parser.add_argument( + "-g", + "--configs", + nargs="+", + default=["base"], + help="Set of config files for gin (separated by spaces) " + "e.g. --gin_config file1 file2 (exclude .gin from path)", + ) + parser.add_argument( + "-p", + "--overrides", + nargs="+", + default=[], + help="Parameter settings that override config defaults " + "e.g. --gin_param module_1.a=2 module_2.b=3", + ) + parser.add_argument("--task_uniqname", type=str, default=None) + parser.add_argument("-d", "--debug", type=str, nargs="*", default=None) + + args = init.parse_args_blender(parser) + logging.getLogger("infinigen").setLevel(logging.INFO) + logging.getLogger("infinigen.core.nodes.node_wrangler").setLevel(logging.CRITICAL) + + if args.debug is not None: + for name in logging.root.manager.loggerDict: + if not name.startswith("infinigen"): + continue + if len(args.debug) == 0 or any(name.endswith(x) for x in args.debug): + logging.getLogger(name).setLevel(logging.DEBUG) + + main(args) From d0a4fd9e4c2589857e4e4b10b3f9b81871d1ef88 Mon Sep 17 00:00:00 2001 From: David-Yan1 Date: Mon, 15 Jul 2024 22:52:52 -0400 Subject: [PATCH 2/9] Minimal working version --- infinigen_examples/generate_floating.py | 101 +++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/infinigen_examples/generate_floating.py b/infinigen_examples/generate_floating.py index 1ec8823a6..a60b244c3 100644 --- a/infinigen_examples/generate_floating.py +++ b/infinigen_examples/generate_floating.py @@ -241,6 +241,105 @@ def animate_cameras(): "animate_cameras", animate_cameras, use_chance=False, prereq="pose_cameras" ) + pholder_rooms = bpy.data.collections.get('placeholders:room_meshes') + pholder_cutters = bpy.data.collections.get('placeholders:portal_cutters') + + meshes_to_join = [] + + for obj in pholder_cutters.objects: + meshes_to_join.append(butil.copy(obj)) + + for obj in pholder_rooms.objects: + meshes_to_join.append(butil.copy(obj)) + + joined_room = butil.join_objects(meshes_to_join) + + pholder_objs = bpy.data.collections.get('placeholders') + objs_to_join = [] + + for obj in pholder_objs.objects: + objs_to_join.append(butil.copy(obj)) + + joined_objs = butil.join_objects(objs_to_join) + + import bmesh + from mathutils.bvhtree import BVHTree + + def create_bvh_tree_from_object(obj): + bm = bmesh.new() + bm.from_mesh(obj.data) + bm.transform(obj.matrix_world) + bvh = BVHTree.FromBMesh(bm) + bm.free() + return bvh + + def check_bvh_intersection(bvh1, bvh2): + if type(bvh2) is list: + return any([check_bvh_intersection(bvh1, bvh) for bvh in bvh2]) + else: + return bvh1.overlap(bvh2) + + room_bvh = create_bvh_tree_from_object(joined_room) + existing_obj_bvh = create_bvh_tree_from_object(joined_objs) + + def sample_point(camera, bvhtree): + from infinigen.core.placement.camera import get_sensor_coords + + cam_location = camera.matrix_world.to_translation() + sensor_coords, pix_it = get_sensor_coords(camera, sparse=False) + + for _ in range(1500): + x = pix_it[np.random.randint(0, pix_it.shape[0])][0] + y = pix_it[np.random.randint(0, pix_it.shape[0])][1] + + direction = (sensor_coords[y, x] - camera.matrix_world.translation).normalized() + + location, normal, index, distance = bvhtree.ray_cast(cam_location, direction) + + if location: + if distance <= 2: + continue + random_distance = np.random.uniform(2, distance) + sampled_point = cam_location + direction.normalized() * random_distance + return sampled_point + + return None + + camera = bpy.data.objects['CameraRigs/0/0'] + + from infinigen.assets.objects.seating import chairs + + print('Raycasting...') + + placed_obj_bvhs = [] + + num_objs = 25 + sample_retries = 20 + + for i in range(num_objs): + fac = chairs.ChairFactory(i) + asset = fac.spawn_asset(0) + + for j in range(sample_retries): + point = sample_point(camera, room_bvh) + if point is None: + continue + asset.rotation_mode = "XYZ" + asset.rotation_euler = np.random.uniform(-np.pi, np.pi, 3) + asset.location = point + + bpy.context.view_layer.update() # i can redo this later without view updates if necessary, bit currently it doesn't incur significant overhead + bvh = create_bvh_tree_from_object(asset) + + if check_bvh_intersection(bvh, existing_obj_bvh) or check_bvh_intersection(bvh, room_bvh) or check_bvh_intersection(bvh, placed_obj_bvhs): + print(f"Sample {j} of asset {i} rejected, resampling...") + if i == sample_retries - 1: + butil.delete(obj) + else: + print(f"Placing object {asset.name}") + placed_obj_bvhs.append(bvh) + break + p.run_stage( "populate_intermediate_pholders", populate.populate_state_placeholders, @@ -309,7 +408,7 @@ def animate_cameras(): use_chance=False, ) - # state.print() + state.print() state.to_json(output_folder / "solve_state.json") cam = cam_util.get_camera(0, 0) From b2dfb76875e00042cc47f4a7331a43f53bb16caf Mon Sep 17 00:00:00 2001 From: David-Yan1 Date: Tue, 16 Jul 2024 14:51:01 -0400 Subject: [PATCH 3/9] Created FloatingObjectPlacement class and made some easy optimizations --- infinigen_examples/generate_floating.py | 175 ++++++++++++++---------- 1 file changed, 102 insertions(+), 73 deletions(-) diff --git a/infinigen_examples/generate_floating.py b/infinigen_examples/generate_floating.py index a60b244c3..10c6a3ce7 100644 --- a/infinigen_examples/generate_floating.py +++ b/infinigen_examples/generate_floating.py @@ -57,6 +57,102 @@ logger = logging.getLogger(__name__) +import bmesh +from mathutils.bvhtree import BVHTree + +from infinigen.core.placement.factory import AssetFactory + + +def create_bvh_tree_from_object(obj): + bm = bmesh.new() + bm.from_mesh(obj.data) + bm.transform(obj.matrix_world) + bvh = BVHTree.FromBMesh(bm) + bm.free() + return bvh + + + +def check_bvh_intersection(bvh1, bvh2): + if type(bvh2) is list: + return any([check_bvh_intersection(bvh1, bvh) for bvh in bvh2]) + else: + return bvh1.overlap(bvh2) + +def raycast_sample(min_dist, sensor_coords, pix_it, camera, bvhtree): + cam_location = camera.matrix_world.to_translation() + + for _ in range(1500): + x = pix_it[np.random.randint(0, pix_it.shape[0])][0] + y = pix_it[np.random.randint(0, pix_it.shape[0])][1] + + direction = (sensor_coords[y, x] - camera.matrix_world.translation).normalized() + + location, normal, index, distance = bvhtree.ray_cast(cam_location, direction) + + if location: + if distance <= min_dist: + continue + random_distance = np.random.uniform(min_dist, distance) + sampled_point = cam_location + direction.normalized() * random_distance + return sampled_point + + logger.info('Couldnt find far enough away pixel to raycast to') + return None + +def bbox_sample(bbox): + pass + +class FloatingObjectPlacement: + + def __init__(self, asset_factories : list[AssetFactory], camera, room_mesh, existing_objs, bbox = None): + + self.assets = asset_factories + self.room = room_mesh + self.obj_meshes = existing_objs + self.camera = camera + self.bbox = bbox + + def place_objs(self, num_objs, min_dist = 1.5, sample_retries = 20, raycast = True, collision_placed = False, collision_existing = False): + + room_bvh = create_bvh_tree_from_object(self.room) + existing_obj_bvh = create_bvh_tree_from_object(self.obj_meshes) + + placed_obj_bvhs = [] + + from infinigen.core.placement.camera import get_sensor_coords + sensor_coords, pix_it = get_sensor_coords(self.camera, sparse=False) + for i in range(num_objs): + + fac = np.random.choice(self.assets)(np.random.randint(1,2**28)) + asset = fac.spawn_asset(0) + + for j in range(sample_retries): + + if raycast: + point = raycast_sample(min_dist, sensor_coords, pix_it, self.camera, room_bvh) + else: + point = raycast_sample(min_dist, sensor_coords, pix_it, self.camera, room_bvh) + + if point is None: + continue + asset.rotation_mode = "XYZ" + asset.rotation_euler = np.random.uniform(-np.pi, np.pi, 3) + asset.location = point + + bpy.context.view_layer.update() # i can redo this later without view updates if necessary, but currently it doesn't incur significant overhead + bvh = create_bvh_tree_from_object(asset) + + if check_bvh_intersection(bvh, room_bvh) or (not collision_existing and check_bvh_intersection(bvh, existing_obj_bvh)) or (not collision_placed and check_bvh_intersection(bvh, placed_obj_bvhs)): + logger.info(f"Sample {j} of asset {i} rejected, resampling...") + if i == sample_retries - 1: + butil.delete(asset) + else: + logger.info(f"Placing object {asset.name}") + placed_obj_bvhs.append(bvh) + break + + def default_greedy_stages(): """Returns descriptions of what will be covered by each greedy stage of the solver. @@ -262,83 +358,16 @@ def animate_cameras(): joined_objs = butil.join_objects(objs_to_join) - import bmesh - from mathutils.bvhtree import BVHTree - def create_bvh_tree_from_object(obj): - bm = bmesh.new() - bm.from_mesh(obj.data) - bm.transform(obj.matrix_world) - bvh = BVHTree.FromBMesh(bm) - bm.free() - return bvh - - def check_bvh_intersection(bvh1, bvh2): - if type(bvh2) is list: - return any([check_bvh_intersection(bvh1, bvh) for bvh in bvh2]) - else: - return bvh1.overlap(bvh2) - - room_bvh = create_bvh_tree_from_object(joined_room) - existing_obj_bvh = create_bvh_tree_from_object(joined_objs) - - def sample_point(camera, bvhtree): - from infinigen.core.placement.camera import get_sensor_coords - - cam_location = camera.matrix_world.to_translation() - sensor_coords, pix_it = get_sensor_coords(camera, sparse=False) + from infinigen.assets.objects.seating.chairs import ChairFactory + from infinigen.assets.objects.tableware.bottle import BottleFactory - for _ in range(1500): - x = pix_it[np.random.randint(0, pix_it.shape[0])][0] - y = pix_it[np.random.randint(0, pix_it.shape[0])][1] + placer = FloatingObjectPlacement([ChairFactory, BottleFactory], bpy.data.objects['CameraRigs/0/0'], joined_room, joined_objs) - direction = (sensor_coords[y, x] - camera.matrix_world.translation).normalized() + placer.place_objs(15) - location, normal, index, distance = bvhtree.ray_cast(cam_location, direction) - - if location: - if distance <= 2: - continue - random_distance = np.random.uniform(2, distance) - sampled_point = cam_location + direction.normalized() * random_distance - return sampled_point - - return None - - camera = bpy.data.objects['CameraRigs/0/0'] - - from infinigen.assets.objects.seating import chairs - - print('Raycasting...') - - placed_obj_bvhs = [] - - num_objs = 25 - sample_retries = 20 - - for i in range(num_objs): - fac = chairs.ChairFactory(i) - asset = fac.spawn_asset(0) - - for j in range(sample_retries): - point = sample_point(camera, room_bvh) - if point is None: - continue - asset.rotation_mode = "XYZ" - asset.rotation_euler = np.random.uniform(-np.pi, np.pi, 3) - asset.location = point - - bpy.context.view_layer.update() # i can redo this later without view updates if necessary, bit currently it doesn't incur significant overhead - bvh = create_bvh_tree_from_object(asset) - - if check_bvh_intersection(bvh, existing_obj_bvh) or check_bvh_intersection(bvh, room_bvh) or check_bvh_intersection(bvh, placed_obj_bvhs): - print(f"Sample {j} of asset {i} rejected, resampling...") - if i == sample_retries - 1: - butil.delete(obj) - else: - print(f"Placing object {asset.name}") - placed_obj_bvhs.append(bvh) - break + butil.delete(joined_room) + butil.delete(joined_objs) p.run_stage( "populate_intermediate_pholders", From fcac483fdee38af108d63dcf4f978255b38fa4d1 Mon Sep 17 00:00:00 2001 From: David-Yan1 Date: Tue, 23 Jul 2024 16:20:05 -0400 Subject: [PATCH 4/9] Moved class to asset folder --- infinigen_examples/generate_floating.py | 221 ++++++++++-------------- 1 file changed, 94 insertions(+), 127 deletions(-) diff --git a/infinigen_examples/generate_floating.py b/infinigen_examples/generate_floating.py index 10c6a3ce7..0f4b5b5a3 100644 --- a/infinigen_examples/generate_floating.py +++ b/infinigen_examples/generate_floating.py @@ -52,106 +52,16 @@ place_cam_overhead, restrict_solving, ) +from infinigen.assets.floating_placement import FloatingObjectPlacement +from infinigen_examples.util.test_utils import ( + import_item, + load_txt_list, +) from . import generate_nature # noqa F401 # needed for nature gin configs to load logger = logging.getLogger(__name__) -import bmesh -from mathutils.bvhtree import BVHTree - -from infinigen.core.placement.factory import AssetFactory - - -def create_bvh_tree_from_object(obj): - bm = bmesh.new() - bm.from_mesh(obj.data) - bm.transform(obj.matrix_world) - bvh = BVHTree.FromBMesh(bm) - bm.free() - return bvh - - - -def check_bvh_intersection(bvh1, bvh2): - if type(bvh2) is list: - return any([check_bvh_intersection(bvh1, bvh) for bvh in bvh2]) - else: - return bvh1.overlap(bvh2) - -def raycast_sample(min_dist, sensor_coords, pix_it, camera, bvhtree): - cam_location = camera.matrix_world.to_translation() - - for _ in range(1500): - x = pix_it[np.random.randint(0, pix_it.shape[0])][0] - y = pix_it[np.random.randint(0, pix_it.shape[0])][1] - - direction = (sensor_coords[y, x] - camera.matrix_world.translation).normalized() - - location, normal, index, distance = bvhtree.ray_cast(cam_location, direction) - - if location: - if distance <= min_dist: - continue - random_distance = np.random.uniform(min_dist, distance) - sampled_point = cam_location + direction.normalized() * random_distance - return sampled_point - - logger.info('Couldnt find far enough away pixel to raycast to') - return None - -def bbox_sample(bbox): - pass - -class FloatingObjectPlacement: - - def __init__(self, asset_factories : list[AssetFactory], camera, room_mesh, existing_objs, bbox = None): - - self.assets = asset_factories - self.room = room_mesh - self.obj_meshes = existing_objs - self.camera = camera - self.bbox = bbox - - def place_objs(self, num_objs, min_dist = 1.5, sample_retries = 20, raycast = True, collision_placed = False, collision_existing = False): - - room_bvh = create_bvh_tree_from_object(self.room) - existing_obj_bvh = create_bvh_tree_from_object(self.obj_meshes) - - placed_obj_bvhs = [] - - from infinigen.core.placement.camera import get_sensor_coords - sensor_coords, pix_it = get_sensor_coords(self.camera, sparse=False) - for i in range(num_objs): - - fac = np.random.choice(self.assets)(np.random.randint(1,2**28)) - asset = fac.spawn_asset(0) - - for j in range(sample_retries): - - if raycast: - point = raycast_sample(min_dist, sensor_coords, pix_it, self.camera, room_bvh) - else: - point = raycast_sample(min_dist, sensor_coords, pix_it, self.camera, room_bvh) - - if point is None: - continue - asset.rotation_mode = "XYZ" - asset.rotation_euler = np.random.uniform(-np.pi, np.pi, 3) - asset.location = point - - bpy.context.view_layer.update() # i can redo this later without view updates if necessary, but currently it doesn't incur significant overhead - bvh = create_bvh_tree_from_object(asset) - - if check_bvh_intersection(bvh, room_bvh) or (not collision_existing and check_bvh_intersection(bvh, existing_obj_bvh)) or (not collision_placed and check_bvh_intersection(bvh, placed_obj_bvhs)): - logger.info(f"Sample {j} of asset {i} rejected, resampling...") - if i == sample_retries - 1: - butil.delete(asset) - else: - logger.info(f"Placing object {asset.name}") - placed_obj_bvhs.append(bvh) - break - def default_greedy_stages(): @@ -337,37 +247,6 @@ def animate_cameras(): "animate_cameras", animate_cameras, use_chance=False, prereq="pose_cameras" ) - pholder_rooms = bpy.data.collections.get('placeholders:room_meshes') - pholder_cutters = bpy.data.collections.get('placeholders:portal_cutters') - - meshes_to_join = [] - - for obj in pholder_cutters.objects: - meshes_to_join.append(butil.copy(obj)) - - for obj in pholder_rooms.objects: - meshes_to_join.append(butil.copy(obj)) - - joined_room = butil.join_objects(meshes_to_join) - - pholder_objs = bpy.data.collections.get('placeholders') - objs_to_join = [] - - for obj in pholder_objs.objects: - objs_to_join.append(butil.copy(obj)) - - joined_objs = butil.join_objects(objs_to_join) - - - from infinigen.assets.objects.seating.chairs import ChairFactory - from infinigen.assets.objects.tableware.bottle import BottleFactory - - placer = FloatingObjectPlacement([ChairFactory, BottleFactory], bpy.data.objects['CameraRigs/0/0'], joined_room, joined_objs) - - placer.place_objs(15) - - butil.delete(joined_room) - butil.delete(joined_objs) p.run_stage( "populate_intermediate_pholders", @@ -378,10 +257,98 @@ def animate_cameras(): use_chance=False, ) + def solve_medium(): + n_steps = overrides["solve_steps_medium"] + for i, vars in enumerate( + greedy.iterate_assignments(stages["on_wall"], state, all_vars, limits) + ): + solver.solve_objects( + consgraph, stages["on_wall"], vars, n_steps, desc=f"on_wall_{i}" + ) + for i, vars in enumerate( + greedy.iterate_assignments(stages["on_ceiling"], state, all_vars, limits) + ): + solver.solve_objects( + consgraph, stages["on_ceiling"], vars, n_steps, desc=f"on_ceiling_{i}" + ) + for i, vars in enumerate( + greedy.iterate_assignments(stages["side_obj"], state, all_vars, limits) + ): + solver.solve_objects( + consgraph, stages["side_obj"], vars, n_steps, desc=f"side_obj_{i}" + ) + return solver.state + + state = p.run_stage("solve_medium", solve_medium, use_chance=False, default=state) + + def solve_small(): + n_steps = overrides["solve_steps_small"] + for i, vars in enumerate( + greedy.iterate_assignments(stages["obj_ontop_obj"], state, all_vars, limits) + ): + solver.solve_objects( + consgraph, + stages["obj_ontop_obj"], + vars, + n_steps, + desc=f"obj_ontop_obj_{i}", + ) + for i, vars in enumerate( + greedy.iterate_assignments( + stages["obj_on_support"], state, all_vars, limits + ) + ): + solver.solve_objects( + consgraph, + stages["obj_on_support"], + vars, + n_steps, + desc=f"obj_on_support_{i}", + ) + # for i, vars in enumerate(greedy.iterate_assignments(stages['tertiary'], state, all_vars, limits)): + # solver.solve_objects(consgraph, stages['tertiary'], vars, n_steps, desc=f"tertiary_{i}") + return solver.state + + state = p.run_stage("solve_small", solve_small, use_chance=False, default=state) + p.run_stage( "populate_assets", populate.populate_state_placeholders, state, use_chance=False ) + def place_floating(): + pholder_rooms = bpy.data.collections.get('placeholders:room_meshes') + pholder_cutters = bpy.data.collections.get('placeholders:portal_cutters') + + meshes_to_join = [] + + for obj in pholder_cutters.objects: + meshes_to_join.append(butil.copy(obj)) + + for obj in pholder_rooms.objects: + meshes_to_join.append(butil.copy(obj)) + + joined_room = butil.join_objects(meshes_to_join) + + pholder_objs = bpy.data.collections.get('placeholders') + objs_to_join = [] + + for obj in pholder_objs.objects: + objs_to_join.append(butil.copy(obj)) + + joined_objs = butil.join_objects(objs_to_join) + + floating_paths = load_txt_list(Path(__file__).parent.parent / 'tests' / 'assets' / "list_indoor_meshes.txt") + facs = [import_item(path) for path in floating_paths] + + placer = FloatingObjectPlacement(facs, cam_util.get_camera(0, 0), joined_room, joined_objs) + + placer.place_objs(num_objs = np.random.randint(15, 25), normalize=True) + + butil.delete(joined_room) + butil.delete(joined_objs) + + p.run_stage("floating_objs", place_floating, use_chance=False, default=state) + door_filter = r.Domain({t.Semantics.Door}, [(cl.AnyRelation(), stages["rooms"])]) window_filter = r.Domain( {t.Semantics.Window}, [(cl.AnyRelation(), stages["rooms"])] @@ -437,7 +404,7 @@ def animate_cameras(): use_chance=False, ) - state.print() + # state.print() state.to_json(output_folder / "solve_state.json") cam = cam_util.get_camera(0, 0) From e0a46d84cde6344ac1b2e0ecc6c5ef7ee2432f18 Mon Sep 17 00:00:00 2001 From: David-Yan1 Date: Tue, 23 Jul 2024 16:20:23 -0400 Subject: [PATCH 5/9] Integrate Into generate_indoors --- infinigen/assets/floating_placement.py | 108 ++++ .../configs_indoor/base_indoors.gin | 4 +- infinigen_examples/generate_floating.py | 583 ------------------ infinigen_examples/generate_indoors.py | 42 ++ 4 files changed, 153 insertions(+), 584 deletions(-) create mode 100644 infinigen/assets/floating_placement.py delete mode 100644 infinigen_examples/generate_floating.py diff --git a/infinigen/assets/floating_placement.py b/infinigen/assets/floating_placement.py new file mode 100644 index 000000000..368e5fddd --- /dev/null +++ b/infinigen/assets/floating_placement.py @@ -0,0 +1,108 @@ +import logging +import bmesh +from mathutils.bvhtree import BVHTree + +from infinigen.core.placement.factory import AssetFactory +import numpy as np +import bpy +from infinigen.core.util import blender as butil + +logger = logging.getLogger(__name__) + +def create_bvh_tree_from_object(obj): + bm = bmesh.new() + bm.from_mesh(obj.data) + bm.transform(obj.matrix_world) + bvh = BVHTree.FromBMesh(bm) + bm.free() + return bvh + +def check_bvh_intersection(bvh1, bvh2): + if type(bvh2) is list: + return any([check_bvh_intersection(bvh1, bvh) for bvh in bvh2]) + else: + return bvh1.overlap(bvh2) + +def raycast_sample(min_dist, sensor_coords, pix_it, camera, bvhtree): + cam_location = camera.matrix_world.to_translation() + + for _ in range(1500): + x = pix_it[np.random.randint(0, pix_it.shape[0])][0] + y = pix_it[np.random.randint(0, pix_it.shape[0])][1] + + direction = (sensor_coords[y, x] - camera.matrix_world.translation).normalized() + + location, normal, index, distance = bvhtree.ray_cast(cam_location, direction) + + if location: + if distance <= min_dist: + continue + random_distance = np.random.uniform(min_dist, distance) + sampled_point = cam_location + direction.normalized() * random_distance + return sampled_point + + logger.info('Couldnt find far enough away pixel to raycast to') + return None + +def bbox_sample(bbox): + pass + +class FloatingObjectPlacement: + + def __init__(self, asset_factories : list[AssetFactory], camera, room_mesh, existing_objs, bbox = None): + + self.assets = asset_factories + self.room = room_mesh + self.obj_meshes = existing_objs + self.camera = camera + self.bbox = bbox + + def place_objs(self, num_objs, min_dist = 1, sample_retries = 200, raycast = True, normalize = False, collision_placed = False, collision_existing = False): + + room_bvh = create_bvh_tree_from_object(self.room) + existing_obj_bvh = create_bvh_tree_from_object(self.obj_meshes) + + placed_obj_bvhs = [] + + from infinigen.core.placement.camera import get_sensor_coords + sensor_coords, pix_it = get_sensor_coords(self.camera, sparse=False) + for i in range(num_objs): + + fac = np.random.choice(self.assets)(np.random.randint(1,2**28)) + asset = fac.spawn_asset(0) + fac.finalize_assets([asset]) + max_dim = max(asset.dimensions.x, asset.dimensions.y, asset.dimensions.z) + + if normalize: + if max_dim != 0: + normalize_scale = 0.5 / max(asset.dimensions.x, asset.dimensions.y, asset.dimensions.z) + else: + normalize_scale = 1 + + asset.scale = (normalize_scale, normalize_scale, normalize_scale) + + for j in range(sample_retries): + + if raycast: + point = raycast_sample(min_dist, sensor_coords, pix_it, self.camera, room_bvh) + else: + point = raycast_sample(min_dist, sensor_coords, pix_it, self.camera, room_bvh) + + if point is None: + continue + + asset.rotation_mode = "XYZ" + asset.rotation_euler = np.random.uniform(-np.pi, np.pi, 3) + asset.location = point + + bpy.context.view_layer.update() # i can redo this later without view updates if necessary, but currently it doesn't incur significant overhead + bvh = create_bvh_tree_from_object(asset) + + if check_bvh_intersection(bvh, room_bvh) or (not collision_existing and check_bvh_intersection(bvh, existing_obj_bvh)) or (not collision_placed and check_bvh_intersection(bvh, placed_obj_bvhs)): + logger.info(f"Sample {j} of asset {i} rejected, resampling...") + if i == sample_retries - 1: + butil.delete(asset) + else: + logger.info(f"Placing object {asset.name}") + placed_obj_bvhs.append(bvh) + break \ No newline at end of file diff --git a/infinigen_examples/configs_indoor/base_indoors.gin b/infinigen_examples/configs_indoor/base_indoors.gin index 4b69c72ec..d0499f61d 100644 --- a/infinigen_examples/configs_indoor/base_indoors.gin +++ b/infinigen_examples/configs_indoor/base_indoors.gin @@ -60,4 +60,6 @@ compose_indoors.hide_other_rooms_enabled = False compose_indoors.fancy_clouds_chance = 0.5 compose_indoors.grass_chance = 0.5 compose_indoors.rocks_chance = 0.5 -compose_indoors.near_distance = 20 \ No newline at end of file +compose_indoors.near_distance = 20 + +compose_indoors.floating_objs_enabled = False \ No newline at end of file diff --git a/infinigen_examples/generate_floating.py b/infinigen_examples/generate_floating.py deleted file mode 100644 index 0f4b5b5a3..000000000 --- a/infinigen_examples/generate_floating.py +++ /dev/null @@ -1,583 +0,0 @@ -# Copyright (c) Princeton University. -# This source code is licensed under the BSD 3-Clause license found in the LICENSE file in the root directory -# of this source tree. - -import argparse -import logging -from pathlib import Path - -from numpy import deg2rad - -# ruff: noqa: E402 -# NOTE: logging config has to be before imports that use logging -logging.basicConfig( - format="[%(asctime)s.%(msecs)03d] [%(module)s] [%(levelname)s] | %(message)s", - datefmt="%H:%M:%S", - level=logging.INFO, -) - -import bpy -import gin -import numpy as np - -from infinigen.assets import lighting -from infinigen.assets.materials import invisible_to_camera -from infinigen.assets.objects.wall_decorations.skirting_board import make_skirting_board -from infinigen.assets.utils.decorate import read_co -from infinigen.core import execute_tasks, init, placement, surface, tagging -from infinigen.core import tags as t -from infinigen.core.constraints import checks -from infinigen.core.constraints import constraint_language as cl -from infinigen.core.constraints import reasoning as r -from infinigen.core.constraints.example_solver import ( - Solver, - greedy, - populate, - state_def, -) -from infinigen.core.constraints.example_solver.room import constants -from infinigen.core.constraints.example_solver.room import decorate as room_dec -from infinigen.core.constraints.example_solver.room.constants import WALL_HEIGHT -from infinigen.core.placement import camera as cam_util -from infinigen.core.util import blender as butil -from infinigen.core.util import pipeline -from infinigen.core.util.camera import points_inview -from infinigen.terrain import Terrain -from infinigen_examples.indoor_constraint_examples import home_constraints -from infinigen_examples.util import constraint_util as cu -from infinigen_examples.util.generate_indoors_util import ( - apply_greedy_restriction, - create_outdoor_backdrop, - hide_other_rooms, - place_cam_overhead, - restrict_solving, -) -from infinigen.assets.floating_placement import FloatingObjectPlacement -from infinigen_examples.util.test_utils import ( - import_item, - load_txt_list, -) - -from . import generate_nature # noqa F401 # needed for nature gin configs to load - -logger = logging.getLogger(__name__) - - - -def default_greedy_stages(): - """Returns descriptions of what will be covered by each greedy stage of the solver. - - Any domain containing one or more VariableTags is greedy: it produces many separate domains, - one for each possible assignment of the unresolved variables. - """ - - on_floor = cl.StableAgainst({}, cu.floortags) - on_wall = cl.StableAgainst({}, cu.walltags) - on_ceiling = cl.StableAgainst({}, cu.ceilingtags) - side = cl.StableAgainst({}, cu.side) - - all_room = r.Domain({t.Semantics.Room, -t.Semantics.Object}) - all_obj = r.Domain({t.Semantics.Object, -t.Semantics.Room}) - - all_obj_in_room = all_obj.with_relation( - cl.AnyRelation(), all_room.with_tags(cu.variable_room) - ) - primary = all_obj_in_room.with_relation(-cl.AnyRelation(), all_obj) - - greedy_stages = {} - - greedy_stages["rooms"] = all_room - - greedy_stages["on_floor"] = primary.with_relation(on_floor, all_room) - greedy_stages["on_wall"] = ( - primary.with_relation(-on_floor, all_room) - .with_relation(-on_ceiling, all_room) - .with_relation(on_wall, all_room) - ) - greedy_stages["on_ceiling"] = ( - primary.with_relation(-on_floor, all_room) - .with_relation(on_ceiling, all_room) - .with_relation(-on_wall, all_room) - ) - - secondary = all_obj.with_relation( - cl.AnyRelation(), primary.with_tags(cu.variable_obj) - ) - - greedy_stages["side_obj"] = secondary.with_relation(side, all_obj) - nonside = secondary.with_relation(-side, all_obj) - - greedy_stages["obj_ontop_obj"] = nonside.with_relation( - cu.ontop, all_obj - ).with_relation(-cu.on, all_obj) - greedy_stages["obj_on_support"] = nonside.with_relation( - cu.on, all_obj - ).with_relation(-cu.ontop, all_obj) - - return greedy_stages - - -all_vars = [cu.variable_room, cu.variable_obj] - - -@gin.configurable -def compose_indoors(output_folder: Path, scene_seed: int, **overrides): - p = pipeline.RandomStageExecutor(scene_seed, output_folder, overrides) - - logger.debug(overrides) - - def add_coarse_terrain(): - terrain = Terrain( - scene_seed, - surface.registry, - task="coarse", - on_the_fly_asset_folder=output_folder / "assets", - ) - terrain_mesh = terrain.coarse_terrain() - # placement.density.set_tag_dict(terrain.tag_dict) - return terrain, terrain_mesh - - terrain, terrain_mesh = p.run_stage( - "terrain", add_coarse_terrain, use_chance=False, default=(None, None) - ) - - p.run_stage("sky_lighting", lighting.sky_lighting.add_lighting, use_chance=False) - - consgraph = home_constraints() - stages = default_greedy_stages() - checks.check_all(consgraph, stages, all_vars) - - stages, consgraph, limits = restrict_solving(stages, consgraph) - - if overrides.get("restrict_single_supported_roomtype", False): - restrict_parent_rooms = { - np.random.choice( - [ - # Only these roomtypes have constraints written in home_constraints. - # Others will be empty-ish besides maybe storage and plants - # TODO: add constraints to home_constraints for garages, offices, balconies, etc - t.Semantics.Bedroom, - t.Semantics.LivingRoom, - t.Semantics.Kitchen, - t.Semantics.Bathroom, - t.Semantics.DiningRoom, - ] - ) - } - logger.info(f"Restricting to {restrict_parent_rooms}") - apply_greedy_restriction(stages, restrict_parent_rooms, cu.variable_room) - - solver = Solver(output_folder=output_folder) - - def solve_rooms(): - return solver.solve_rooms(scene_seed, consgraph, stages["rooms"]) - - state: state_def.State = p.run_stage("solve_rooms", solve_rooms, use_chance=False) - - def solve_large(): - assignments = greedy.iterate_assignments( - stages["on_floor"], state, all_vars, limits, nonempty=True - ) - for i, vars in enumerate(assignments): - solver.solve_objects( - consgraph, - stages["on_floor"], - var_assignments=vars, - n_steps=overrides["solve_steps_large"], - desc=f"on_floor_{i}", - abort_unsatisfied=overrides.get("abort_unsatisfied_large", False), - ) - return solver.state - - state = p.run_stage("solve_large", solve_large, use_chance=False, default=state) - - solved_rooms = [ - state.objs[assignment[cu.variable_room]].obj - for assignment in greedy.iterate_assignments( - stages["on_floor"], state, [cu.variable_room], limits - ) - ] - solved_bound_points = np.concatenate([butil.bounds(r) for r in solved_rooms]) - solved_bbox = ( - np.min(solved_bound_points, axis=0), - np.max(solved_bound_points, axis=0), - ) - - house_bbox = np.concatenate( - [ - butil.bounds(obj) - for obj in solver.get_bpy_objects(r.Domain({t.Semantics.Room})) - ] - ) - house_bbox = (np.min(house_bbox, axis=0), np.max(house_bbox, axis=0)) - - camera_rigs = placement.camera.spawn_camera_rigs() - - def pose_cameras(): - nonroom_objs = [ - o.obj for o in state.objs.values() if t.Semantics.Room not in o.tags - ] - scene_objs = solved_rooms + nonroom_objs - - scene_preprocessed = placement.camera.camera_selection_preprocessing( - terrain=None, scene_objs=scene_objs - ) - - solved_floor_surface = butil.join_objects( - [ - tagging.extract_tagged_faces(o, {t.Subpart.SupportSurface}) - for o in solved_rooms - ] - ) - - placement.camera.configure_cameras( - camera_rigs, - scene_preprocessed=scene_preprocessed, - init_surfaces=solved_floor_surface, - ) - - return scene_preprocessed - - scene_preprocessed = p.run_stage("pose_cameras", pose_cameras, use_chance=False) - - def animate_cameras(): - cam_util.animate_cameras(camera_rigs, solved_bbox, scene_preprocessed, pois=[]) - - p.run_stage( - "animate_cameras", animate_cameras, use_chance=False, prereq="pose_cameras" - ) - - - p.run_stage( - "populate_intermediate_pholders", - populate.populate_state_placeholders, - solver.state, - filter=t.Semantics.AssetPlaceholderForChildren, - final=False, - use_chance=False, - ) - - def solve_medium(): - n_steps = overrides["solve_steps_medium"] - for i, vars in enumerate( - greedy.iterate_assignments(stages["on_wall"], state, all_vars, limits) - ): - solver.solve_objects( - consgraph, stages["on_wall"], vars, n_steps, desc=f"on_wall_{i}" - ) - for i, vars in enumerate( - greedy.iterate_assignments(stages["on_ceiling"], state, all_vars, limits) - ): - solver.solve_objects( - consgraph, stages["on_ceiling"], vars, n_steps, desc=f"on_ceiling_{i}" - ) - for i, vars in enumerate( - greedy.iterate_assignments(stages["side_obj"], state, all_vars, limits) - ): - solver.solve_objects( - consgraph, stages["side_obj"], vars, n_steps, desc=f"side_obj_{i}" - ) - return solver.state - - state = p.run_stage("solve_medium", solve_medium, use_chance=False, default=state) - - def solve_small(): - n_steps = overrides["solve_steps_small"] - for i, vars in enumerate( - greedy.iterate_assignments(stages["obj_ontop_obj"], state, all_vars, limits) - ): - solver.solve_objects( - consgraph, - stages["obj_ontop_obj"], - vars, - n_steps, - desc=f"obj_ontop_obj_{i}", - ) - for i, vars in enumerate( - greedy.iterate_assignments( - stages["obj_on_support"], state, all_vars, limits - ) - ): - solver.solve_objects( - consgraph, - stages["obj_on_support"], - vars, - n_steps, - desc=f"obj_on_support_{i}", - ) - # for i, vars in enumerate(greedy.iterate_assignments(stages['tertiary'], state, all_vars, limits)): - # solver.solve_objects(consgraph, stages['tertiary'], vars, n_steps, desc=f"tertiary_{i}") - return solver.state - - state = p.run_stage("solve_small", solve_small, use_chance=False, default=state) - - p.run_stage( - "populate_assets", populate.populate_state_placeholders, state, use_chance=False - ) - - def place_floating(): - pholder_rooms = bpy.data.collections.get('placeholders:room_meshes') - pholder_cutters = bpy.data.collections.get('placeholders:portal_cutters') - - meshes_to_join = [] - - for obj in pholder_cutters.objects: - meshes_to_join.append(butil.copy(obj)) - - for obj in pholder_rooms.objects: - meshes_to_join.append(butil.copy(obj)) - - joined_room = butil.join_objects(meshes_to_join) - - pholder_objs = bpy.data.collections.get('placeholders') - objs_to_join = [] - - for obj in pholder_objs.objects: - objs_to_join.append(butil.copy(obj)) - - joined_objs = butil.join_objects(objs_to_join) - - floating_paths = load_txt_list(Path(__file__).parent.parent / 'tests' / 'assets' / "list_indoor_meshes.txt") - facs = [import_item(path) for path in floating_paths] - - placer = FloatingObjectPlacement(facs, cam_util.get_camera(0, 0), joined_room, joined_objs) - - placer.place_objs(num_objs = np.random.randint(15, 25), normalize=True) - - butil.delete(joined_room) - butil.delete(joined_objs) - - p.run_stage("floating_objs", place_floating, use_chance=False, default=state) - - door_filter = r.Domain({t.Semantics.Door}, [(cl.AnyRelation(), stages["rooms"])]) - window_filter = r.Domain( - {t.Semantics.Window}, [(cl.AnyRelation(), stages["rooms"])] - ) - p.run_stage( - "room_doors", - lambda: room_dec.populate_doors(solver.get_bpy_objects(door_filter)), - use_chance=False, - ) - p.run_stage( - "room_windows", - lambda: room_dec.populate_windows(solver.get_bpy_objects(window_filter)), - use_chance=False, - ) - - room_meshes = solver.get_bpy_objects(r.Domain({t.Semantics.Room})) - p.run_stage( - "room_stairs", - lambda: room_dec.room_stairs(state, room_meshes), - use_chance=False, - ) - p.run_stage( - "skirting_floor", - lambda: make_skirting_board(room_meshes, t.Subpart.SupportSurface), - ) - p.run_stage( - "skirting_ceiling", lambda: make_skirting_board(room_meshes, t.Subpart.Ceiling) - ) - - rooms_meshed = butil.get_collection("placeholders:room_meshes") - rooms_split = room_dec.split_rooms(list(rooms_meshed.objects)) - - p.run_stage( - "room_walls", room_dec.room_walls, rooms_split["wall"].objects, use_chance=False - ) - p.run_stage( - "room_pillars", - room_dec.room_pillars, - state, - rooms_split["wall"].objects, - use_chance=False, - ) - p.run_stage( - "room_floors", - room_dec.room_floors, - rooms_split["floor"].objects, - use_chance=False, - ) - p.run_stage( - "room_ceilings", - room_dec.room_ceilings, - rooms_split["ceiling"].objects, - use_chance=False, - ) - - # state.print() - state.to_json(output_folder / "solve_state.json") - - cam = cam_util.get_camera(0, 0) - - def turn_off_lights(): - for o in bpy.data.objects: - if o.type == "LIGHT" and not o.data.cycles.is_portal: - print(f"Deleting {o.name}") - butil.delete(o) - - p.run_stage("lights_off", turn_off_lights) - - def invisible_room_ceilings(): - rooms_split["exterior"].hide_viewport = True - rooms_split["exterior"].hide_render = True - invisible_to_camera.apply(list(rooms_split["ceiling"].objects)) - invisible_to_camera.apply( - [o for o in bpy.data.objects if "CeilingLight" in o.name] - ) - - p.run_stage("invisible_room_ceilings", invisible_room_ceilings, use_chance=False) - - p.run_stage( - "overhead_cam", - place_cam_overhead, - cam=camera_rigs[0], - bbox=solved_bbox, - use_chance=False, - ) - - p.run_stage( - "hide_other_rooms", - hide_other_rooms, - state, - rooms_split, - keep_rooms=[r.name for r in solved_rooms], - use_chance=False, - ) - - height = p.run_stage( - "nature_backdrop", - create_outdoor_backdrop, - terrain, - house_bbox=house_bbox, - cam=cam, - p=p, - params=overrides, - use_chance=False, - prereq="terrain", - default=0, - ) - - if overrides.get("topview", False): - rooms_split["exterior"].hide_viewport = True - rooms_split["ceiling"].hide_viewport = True - rooms_split["exterior"].hide_render = True - rooms_split["ceiling"].hide_render = True - for group in ["wall", "floor"]: - for wall in rooms_split[group].objects: - for mat in wall.data.materials: - for n in mat.node_tree.nodes: - if n.type == "BSDF_PRINCIPLED": - n.inputs["Alpha"].default_value = overrides.get( - "alpha_walls", 1.0 - ) - bbox = np.concatenate( - [ - read_co(r) + np.array(r.location)[np.newaxis, :] - for r in rooms_meshed.objects - ] - ) - camera = camera_rigs[0].children[0] - camera_rigs[0].location = 0, 0, 0 - camera_rigs[0].rotation_euler = 0, 0, 0 - bpy.contexScene.camera = camera - rot_x = deg2rad(overrides.get("topview_rot_x", 0)) - rot_z = deg2rad(overrides.get("topview_rot_z", 0)) - camera.rotation_euler = rot_x, 0, rot_z - mean = np.mean(bbox, 0) - for cam_dist in np.exp(np.linspace(1.0, 5.0, 500)): - camera.location = ( - mean[0] + cam_dist * np.sin(rot_x) * np.sin(rot_z), - mean[1] - cam_dist * np.sin(rot_x) * np.cos(rot_z), - mean[2] - WALL_HEIGHT / 2 + cam_dist * np.cos(rot_x), - ) - bpy.context.view_layer.update() - inview = points_inview(bbox, camera) - if inview.all(): - for area in bpy.contexScreen.areas: - if area.type == "VIEW_3D": - area.spaces.active.region_3d.view_perspective = "CAMERA" - break - break - - return { - "height_offset": height, - "whole_bbox": house_bbox, - } - - -def main(args): - scene_seed = init.apply_scene_seed(args.seed) - init.apply_gin_configs( - configs=["base_indoors.gin"] + args.configs, - overrides=args.overrides, - config_folders=[ - "infinigen_examples/configs_indoor", - "infinigen_examples/configs_nature", - ], - ) - constants.initialize_constants() - - execute_tasks.main( - compose_scene_func=compose_indoors, - populate_scene_func=None, - input_folder=args.input_folder, - output_folder=args.output_folder, - task=args.task, - task_uniqname=args.task_uniqname, - scene_seed=scene_seed, - ) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("--output_folder", type=Path) - parser.add_argument("--input_folder", type=Path, default=None) - parser.add_argument( - "-s", "--seed", default=None, help="The seed used to generate the scene" - ) - parser.add_argument( - "-t", - "--task", - nargs="+", - default=["coarse"], - choices=[ - "coarse", - "populate", - "fine_terrain", - "ground_truth", - "render", - "mesh_save", - "export", - ], - ) - parser.add_argument( - "-g", - "--configs", - nargs="+", - default=["base"], - help="Set of config files for gin (separated by spaces) " - "e.g. --gin_config file1 file2 (exclude .gin from path)", - ) - parser.add_argument( - "-p", - "--overrides", - nargs="+", - default=[], - help="Parameter settings that override config defaults " - "e.g. --gin_param module_1.a=2 module_2.b=3", - ) - parser.add_argument("--task_uniqname", type=str, default=None) - parser.add_argument("-d", "--debug", type=str, nargs="*", default=None) - - args = init.parse_args_blender(parser) - logging.getLogger("infinigen").setLevel(logging.INFO) - logging.getLogger("infinigen.core.nodes.node_wrangler").setLevel(logging.CRITICAL) - - if args.debug is not None: - for name in logging.root.manager.loggerDict: - if not name.startswith("infinigen"): - continue - if len(args.debug) == 0 or any(name.endswith(x) for x in args.debug): - logging.getLogger(name).setLevel(logging.DEBUG) - - main(args) diff --git a/infinigen_examples/generate_indoors.py b/infinigen_examples/generate_indoors.py index a557f4232..0f4b5b5a3 100644 --- a/infinigen_examples/generate_indoors.py +++ b/infinigen_examples/generate_indoors.py @@ -52,12 +52,18 @@ place_cam_overhead, restrict_solving, ) +from infinigen.assets.floating_placement import FloatingObjectPlacement +from infinigen_examples.util.test_utils import ( + import_item, + load_txt_list, +) from . import generate_nature # noqa F401 # needed for nature gin configs to load logger = logging.getLogger(__name__) + def default_greedy_stages(): """Returns descriptions of what will be covered by each greedy stage of the solver. @@ -134,6 +140,7 @@ def add_coarse_terrain(): terrain, terrain_mesh = p.run_stage( "terrain", add_coarse_terrain, use_chance=False, default=(None, None) ) + p.run_stage("sky_lighting", lighting.sky_lighting.add_lighting, use_chance=False) consgraph = home_constraints() @@ -240,6 +247,7 @@ def animate_cameras(): "animate_cameras", animate_cameras, use_chance=False, prereq="pose_cameras" ) + p.run_stage( "populate_intermediate_pholders", populate.populate_state_placeholders, @@ -307,6 +315,40 @@ def solve_small(): "populate_assets", populate.populate_state_placeholders, state, use_chance=False ) + def place_floating(): + pholder_rooms = bpy.data.collections.get('placeholders:room_meshes') + pholder_cutters = bpy.data.collections.get('placeholders:portal_cutters') + + meshes_to_join = [] + + for obj in pholder_cutters.objects: + meshes_to_join.append(butil.copy(obj)) + + for obj in pholder_rooms.objects: + meshes_to_join.append(butil.copy(obj)) + + joined_room = butil.join_objects(meshes_to_join) + + pholder_objs = bpy.data.collections.get('placeholders') + objs_to_join = [] + + for obj in pholder_objs.objects: + objs_to_join.append(butil.copy(obj)) + + joined_objs = butil.join_objects(objs_to_join) + + floating_paths = load_txt_list(Path(__file__).parent.parent / 'tests' / 'assets' / "list_indoor_meshes.txt") + facs = [import_item(path) for path in floating_paths] + + placer = FloatingObjectPlacement(facs, cam_util.get_camera(0, 0), joined_room, joined_objs) + + placer.place_objs(num_objs = np.random.randint(15, 25), normalize=True) + + butil.delete(joined_room) + butil.delete(joined_objs) + + p.run_stage("floating_objs", place_floating, use_chance=False, default=state) + door_filter = r.Domain({t.Semantics.Door}, [(cl.AnyRelation(), stages["rooms"])]) window_filter = r.Domain( {t.Semantics.Window}, [(cl.AnyRelation(), stages["rooms"])] From 50a520e1552a03a0307a61a3b6116a6c539e5937 Mon Sep 17 00:00:00 2001 From: David-Yan1 Date: Tue, 23 Jul 2024 17:30:32 -0400 Subject: [PATCH 6/9] Add documentation and gin overrides --- docs/HelloRoom.md | 9 +++++++++ infinigen/assets/floating_placement.py | 9 +++++---- infinigen_examples/configs_indoor/base_indoors.gin | 6 +++++- infinigen_examples/generate_indoors.py | 3 ++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/docs/HelloRoom.md b/docs/HelloRoom.md index aaeea6cfa..fb4cadffd 100644 --- a/docs/HelloRoom.md +++ b/docs/HelloRoom.md @@ -120,6 +120,15 @@ These settings are intended for debugging or for generating tailored datasets. I If you are using the commands from [Creating large datasets](#creating-large-datasets) you will instead add these configs as `--overrides` to the end of your command, rather than `-p` +### Generate Rooms with Floating Objects + +To enable floating objects in a room, add the override `compose_indoors.floating_objs_enabled=True`. For example: +``` +python -m infinigen_examples.generate_indoors --seed 0 --task coarse --output_folder outputs/indoors/coarse -g fast_solve.gin singleroom.gin -p compose_indoors.floating_objs_enabled=True compose_indoors.terrain_enabled=False restrict_solving.restrict_parent_rooms=\[\"DiningRoom\"\] + +``` +By default, between 15 and 25 objects are generated and have their size normalized to fit within a 0.5m cube. The number of objects can be configured with `compose_indoors.num_floating` and normalization can be disabled with `compose_indoors.norm_floating=False`. Collisions/intersections between floating objects and existing solved objects are off by default and can be enabled with `compose_indoors.enable_collision_floating=True` and `compose_indoors.enable_collision_solved=True`. + ## Run unit tests ``` pytest tests/ --disable-warnings diff --git a/infinigen/assets/floating_placement.py b/infinigen/assets/floating_placement.py index 368e5fddd..aa7fc16f8 100644 --- a/infinigen/assets/floating_placement.py +++ b/infinigen/assets/floating_placement.py @@ -6,6 +6,7 @@ import numpy as np import bpy from infinigen.core.util import blender as butil +from infinigen.core.util.random import random_general as rg logger = logging.getLogger(__name__) @@ -45,7 +46,7 @@ def raycast_sample(min_dist, sensor_coords, pix_it, camera, bvhtree): return None def bbox_sample(bbox): - pass + raise NotImplementedError class FloatingObjectPlacement: @@ -56,7 +57,7 @@ def __init__(self, asset_factories : list[AssetFactory], camera, room_mesh, exis self.obj_meshes = existing_objs self.camera = camera self.bbox = bbox - + def place_objs(self, num_objs, min_dist = 1, sample_retries = 200, raycast = True, normalize = False, collision_placed = False, collision_existing = False): room_bvh = create_bvh_tree_from_object(self.room) @@ -66,7 +67,7 @@ def place_objs(self, num_objs, min_dist = 1, sample_retries = 200, raycast = Tru from infinigen.core.placement.camera import get_sensor_coords sensor_coords, pix_it = get_sensor_coords(self.camera, sparse=False) - for i in range(num_objs): + for i in range(rg(num_objs)): fac = np.random.choice(self.assets)(np.random.randint(1,2**28)) asset = fac.spawn_asset(0) @@ -86,7 +87,7 @@ def place_objs(self, num_objs, min_dist = 1, sample_retries = 200, raycast = Tru if raycast: point = raycast_sample(min_dist, sensor_coords, pix_it, self.camera, room_bvh) else: - point = raycast_sample(min_dist, sensor_coords, pix_it, self.camera, room_bvh) + point = bbox_sample() if point is None: continue diff --git a/infinigen_examples/configs_indoor/base_indoors.gin b/infinigen_examples/configs_indoor/base_indoors.gin index d0499f61d..f0f07059a 100644 --- a/infinigen_examples/configs_indoor/base_indoors.gin +++ b/infinigen_examples/configs_indoor/base_indoors.gin @@ -62,4 +62,8 @@ compose_indoors.grass_chance = 0.5 compose_indoors.rocks_chance = 0.5 compose_indoors.near_distance = 20 -compose_indoors.floating_objs_enabled = False \ No newline at end of file +compose_indoors.floating_objs_enabled = False +compose_indoors.num_floating = ('discrete_uniform', 15, 25) +compose_indoors.norm_floating_size = True +compose_indoors.enable_collision_floating = False +compose_indoors.enable_collision_solved = False \ No newline at end of file diff --git a/infinigen_examples/generate_indoors.py b/infinigen_examples/generate_indoors.py index 0f4b5b5a3..b4a0afaed 100644 --- a/infinigen_examples/generate_indoors.py +++ b/infinigen_examples/generate_indoors.py @@ -342,7 +342,8 @@ def place_floating(): placer = FloatingObjectPlacement(facs, cam_util.get_camera(0, 0), joined_room, joined_objs) - placer.place_objs(num_objs = np.random.randint(15, 25), normalize=True) + placer.place_objs(num_objs = overrides.get('num_floating', 20), normalize=overrides.get('norm_floating_size', True), + collision_placed = overrides.get('enable_collision_floating', False), collision_existing=overrides.get('enable_collision_solved', False)) butil.delete(joined_room) butil.delete(joined_objs) From 0efc14d7f7e2d78462e2bda6a038d0b895dc25d7 Mon Sep 17 00:00:00 2001 From: David-Yan1 Date: Tue, 23 Jul 2024 17:32:22 -0400 Subject: [PATCH 7/9] Ruff fixes --- infinigen/assets/floating_placement.py | 7 ++++--- infinigen_examples/generate_indoors.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/infinigen/assets/floating_placement.py b/infinigen/assets/floating_placement.py index aa7fc16f8..8c1c22a62 100644 --- a/infinigen/assets/floating_placement.py +++ b/infinigen/assets/floating_placement.py @@ -1,10 +1,11 @@ import logging + import bmesh +import bpy +import numpy as np from mathutils.bvhtree import BVHTree from infinigen.core.placement.factory import AssetFactory -import numpy as np -import bpy from infinigen.core.util import blender as butil from infinigen.core.util.random import random_general as rg @@ -19,7 +20,7 @@ def create_bvh_tree_from_object(obj): return bvh def check_bvh_intersection(bvh1, bvh2): - if type(bvh2) is list: + if isinstance(bvh2, list): return any([check_bvh_intersection(bvh1, bvh) for bvh in bvh2]) else: return bvh1.overlap(bvh2) diff --git a/infinigen_examples/generate_indoors.py b/infinigen_examples/generate_indoors.py index b4a0afaed..c5056abe9 100644 --- a/infinigen_examples/generate_indoors.py +++ b/infinigen_examples/generate_indoors.py @@ -21,6 +21,7 @@ import numpy as np from infinigen.assets import lighting +from infinigen.assets.floating_placement import FloatingObjectPlacement from infinigen.assets.materials import invisible_to_camera from infinigen.assets.objects.wall_decorations.skirting_board import make_skirting_board from infinigen.assets.utils.decorate import read_co @@ -52,7 +53,6 @@ place_cam_overhead, restrict_solving, ) -from infinigen.assets.floating_placement import FloatingObjectPlacement from infinigen_examples.util.test_utils import ( import_item, load_txt_list, From aae952967852df527a838d4057291ffe5e37b1c6 Mon Sep 17 00:00:00 2001 From: pvl-bot Date: Wed, 24 Jul 2024 12:00:03 -0400 Subject: [PATCH 8/9] Move to assets/placement --- infinigen/OcMesher | 2 +- .../floating_objects.py} | 75 +++++++++++++------ infinigen_examples/generate_indoors.py | 32 ++++---- 3 files changed, 73 insertions(+), 36 deletions(-) rename infinigen/assets/{floating_placement.py => placement/floating_objects.py} (67%) diff --git a/infinigen/OcMesher b/infinigen/OcMesher index d3d1441ab..2cdcbacbe 160000 --- a/infinigen/OcMesher +++ b/infinigen/OcMesher @@ -1 +1 @@ -Subproject commit d3d1441ab57c48db3ec40c621fc3d0c323579e8a +Subproject commit 2cdcbacbe62ef79dc6031e0131f916266b7372e3 diff --git a/infinigen/assets/floating_placement.py b/infinigen/assets/placement/floating_objects.py similarity index 67% rename from infinigen/assets/floating_placement.py rename to infinigen/assets/placement/floating_objects.py index 8c1c22a62..d54ccadc1 100644 --- a/infinigen/assets/floating_placement.py +++ b/infinigen/assets/placement/floating_objects.py @@ -11,6 +11,7 @@ logger = logging.getLogger(__name__) + def create_bvh_tree_from_object(obj): bm = bmesh.new() bm.from_mesh(obj.data) @@ -19,12 +20,14 @@ def create_bvh_tree_from_object(obj): bm.free() return bvh + def check_bvh_intersection(bvh1, bvh2): - if isinstance(bvh2, list): + if isinstance(bvh2, list): return any([check_bvh_intersection(bvh1, bvh) for bvh in bvh2]) else: return bvh1.overlap(bvh2) + def raycast_sample(min_dist, sensor_coords, pix_it, camera, bvhtree): cam_location = camera.matrix_world.to_translation() @@ -35,58 +38,76 @@ def raycast_sample(min_dist, sensor_coords, pix_it, camera, bvhtree): direction = (sensor_coords[y, x] - camera.matrix_world.translation).normalized() location, normal, index, distance = bvhtree.ray_cast(cam_location, direction) - + if location: if distance <= min_dist: continue random_distance = np.random.uniform(min_dist, distance) sampled_point = cam_location + direction.normalized() * random_distance return sampled_point - - logger.info('Couldnt find far enough away pixel to raycast to') + + logger.info("Couldnt find far enough away pixel to raycast to") return None + def bbox_sample(bbox): raise NotImplementedError -class FloatingObjectPlacement: - def __init__(self, asset_factories : list[AssetFactory], camera, room_mesh, existing_objs, bbox = None): - +class FloatingObjectPlacement: + def __init__( + self, + asset_factories: list[AssetFactory], + camera, + room_mesh, + existing_objs, + bbox=None, + ): self.assets = asset_factories self.room = room_mesh self.obj_meshes = existing_objs self.camera = camera self.bbox = bbox - - def place_objs(self, num_objs, min_dist = 1, sample_retries = 200, raycast = True, normalize = False, collision_placed = False, collision_existing = False): + def place_objs( + self, + num_objs, + min_dist=1, + sample_retries=200, + raycast=True, + normalize=False, + collision_placed=False, + collision_existing=False, + ): room_bvh = create_bvh_tree_from_object(self.room) existing_obj_bvh = create_bvh_tree_from_object(self.obj_meshes) placed_obj_bvhs = [] from infinigen.core.placement.camera import get_sensor_coords + sensor_coords, pix_it = get_sensor_coords(self.camera, sparse=False) for i in range(rg(num_objs)): - - fac = np.random.choice(self.assets)(np.random.randint(1,2**28)) + fac = np.random.choice(self.assets)(np.random.randint(1, 2**28)) asset = fac.spawn_asset(0) fac.finalize_assets([asset]) max_dim = max(asset.dimensions.x, asset.dimensions.y, asset.dimensions.z) - + if normalize: if max_dim != 0: - normalize_scale = 0.5 / max(asset.dimensions.x, asset.dimensions.y, asset.dimensions.z) - else: + normalize_scale = 0.5 / max( + asset.dimensions.x, asset.dimensions.y, asset.dimensions.z + ) + else: normalize_scale = 1 - + asset.scale = (normalize_scale, normalize_scale, normalize_scale) for j in range(sample_retries): - if raycast: - point = raycast_sample(min_dist, sensor_coords, pix_it, self.camera, room_bvh) + point = raycast_sample( + min_dist, sensor_coords, pix_it, self.camera, room_bvh + ) else: point = bbox_sample() @@ -97,14 +118,24 @@ def place_objs(self, num_objs, min_dist = 1, sample_retries = 200, raycast = Tru asset.rotation_euler = np.random.uniform(-np.pi, np.pi, 3) asset.location = point - bpy.context.view_layer.update() # i can redo this later without view updates if necessary, but currently it doesn't incur significant overhead - bvh = create_bvh_tree_from_object(asset) - - if check_bvh_intersection(bvh, room_bvh) or (not collision_existing and check_bvh_intersection(bvh, existing_obj_bvh)) or (not collision_placed and check_bvh_intersection(bvh, placed_obj_bvhs)): + bpy.context.view_layer.update() # i can redo this later without view updates if necessary, but currently it doesn't incur significant overhead + bvh = create_bvh_tree_from_object(asset) + + if ( + check_bvh_intersection(bvh, room_bvh) + or ( + not collision_existing + and check_bvh_intersection(bvh, existing_obj_bvh) + ) + or ( + not collision_placed + and check_bvh_intersection(bvh, placed_obj_bvhs) + ) + ): logger.info(f"Sample {j} of asset {i} rejected, resampling...") if i == sample_retries - 1: butil.delete(asset) else: logger.info(f"Placing object {asset.name}") placed_obj_bvhs.append(bvh) - break \ No newline at end of file + break diff --git a/infinigen_examples/generate_indoors.py b/infinigen_examples/generate_indoors.py index c5056abe9..e05dc0d99 100644 --- a/infinigen_examples/generate_indoors.py +++ b/infinigen_examples/generate_indoors.py @@ -21,9 +21,9 @@ import numpy as np from infinigen.assets import lighting -from infinigen.assets.floating_placement import FloatingObjectPlacement from infinigen.assets.materials import invisible_to_camera from infinigen.assets.objects.wall_decorations.skirting_board import make_skirting_board +from infinigen.assets.placement.floating_objects import FloatingObjectPlacement from infinigen.assets.utils.decorate import read_co from infinigen.core import execute_tasks, init, placement, surface, tagging from infinigen.core import tags as t @@ -63,7 +63,6 @@ logger = logging.getLogger(__name__) - def default_greedy_stages(): """Returns descriptions of what will be covered by each greedy stage of the solver. @@ -247,7 +246,6 @@ def animate_cameras(): "animate_cameras", animate_cameras, use_chance=False, prereq="pose_cameras" ) - p.run_stage( "populate_intermediate_pholders", populate.populate_state_placeholders, @@ -316,9 +314,9 @@ def solve_small(): ) def place_floating(): - pholder_rooms = bpy.data.collections.get('placeholders:room_meshes') - pholder_cutters = bpy.data.collections.get('placeholders:portal_cutters') - + pholder_rooms = bpy.data.collections.get("placeholders:room_meshes") + pholder_cutters = bpy.data.collections.get("placeholders:portal_cutters") + meshes_to_join = [] for obj in pholder_cutters.objects: @@ -329,27 +327,35 @@ def place_floating(): joined_room = butil.join_objects(meshes_to_join) - pholder_objs = bpy.data.collections.get('placeholders') + pholder_objs = bpy.data.collections.get("placeholders") objs_to_join = [] for obj in pholder_objs.objects: objs_to_join.append(butil.copy(obj)) joined_objs = butil.join_objects(objs_to_join) - - floating_paths = load_txt_list(Path(__file__).parent.parent / 'tests' / 'assets' / "list_indoor_meshes.txt") + + floating_paths = load_txt_list( + Path(__file__).parent.parent / "tests" / "assets" / "list_indoor_meshes.txt" + ) facs = [import_item(path) for path in floating_paths] - placer = FloatingObjectPlacement(facs, cam_util.get_camera(0, 0), joined_room, joined_objs) + placer = FloatingObjectPlacement( + facs, cam_util.get_camera(0, 0), joined_room, joined_objs + ) - placer.place_objs(num_objs = overrides.get('num_floating', 20), normalize=overrides.get('norm_floating_size', True), - collision_placed = overrides.get('enable_collision_floating', False), collision_existing=overrides.get('enable_collision_solved', False)) + placer.place_objs( + num_objs=overrides.get("num_floating", 20), + normalize=overrides.get("norm_floating_size", True), + collision_placed=overrides.get("enable_collision_floating", False), + collision_existing=overrides.get("enable_collision_solved", False), + ) butil.delete(joined_room) butil.delete(joined_objs) p.run_stage("floating_objs", place_floating, use_chance=False, default=state) - + door_filter = r.Domain({t.Semantics.Door}, [(cl.AnyRelation(), stages["rooms"])]) window_filter = r.Domain( {t.Semantics.Window}, [(cl.AnyRelation(), stages["rooms"])] From c5405e972d0282ff6790d23a3969e3b11243195c Mon Sep 17 00:00:00 2001 From: Alexander Raistrick Date: Wed, 24 Jul 2024 12:37:39 -0400 Subject: [PATCH 9/9] Move copy/join logic into floating placement class --- docs/CHANGELOG.md | 3 +- .../assets/placement/floating_objects.py | 49 ++++++++++++++----- infinigen_examples/generate_indoors.py | 38 +++++--------- 3 files changed, 49 insertions(+), 41 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 15cad92d3..83a1cee07 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -78,4 +78,5 @@ v1.5.1 v1.6.0 - Add geometric tile pattern materials -- Tune window parameters and materials \ No newline at end of file +- Tune window parameters and materials +- Add floating object placement generator and example command \ No newline at end of file diff --git a/infinigen/assets/placement/floating_objects.py b/infinigen/assets/placement/floating_objects.py index d54ccadc1..21e47f1ed 100644 --- a/infinigen/assets/placement/floating_objects.py +++ b/infinigen/assets/placement/floating_objects.py @@ -57,16 +57,23 @@ def bbox_sample(bbox): class FloatingObjectPlacement: def __init__( self, - asset_factories: list[AssetFactory], - camera, - room_mesh, - existing_objs, + generators: list[AssetFactory], + camera: bpy.types.Object, + background_objs: bpy.types.Object | list[bpy.types.Object], + collision_objs: bpy.types.Object | list[bpy.types.Object], bbox=None, ): - self.assets = asset_factories - self.room = room_mesh - self.obj_meshes = existing_objs + self.generators = generators self.camera = camera + + if not isinstance(background_objs, list): + background_objs = [background_objs] + self.background_objs = background_objs + + if not isinstance(collision_objs, list): + collision_objs = [collision_objs] + self.collision_objs = collision_objs + self.bbox = bbox def place_objs( @@ -79,16 +86,29 @@ def place_objs( collision_placed=False, collision_existing=False, ): - room_bvh = create_bvh_tree_from_object(self.room) - existing_obj_bvh = create_bvh_tree_from_object(self.obj_meshes) + background_copied = butil.join_objects( + [butil.copy(obj) for obj in self.background_objs] + ) + room_bvh = create_bvh_tree_from_object(background_copied) + butil.delete(background_copied) + + if len(self.collision_objs) > 0: + objs_copied = butil.join_objects( + [butil.copy(obj) for obj in self.collision_objs] + ) + existing_obj_bvh = create_bvh_tree_from_object(objs_copied) + butil.delete(objs_copied) + else: + existing_obj_bvh = None placed_obj_bvhs = [] from infinigen.core.placement.camera import get_sensor_coords sensor_coords, pix_it = get_sensor_coords(self.camera, sparse=False) - for i in range(rg(num_objs)): - fac = np.random.choice(self.assets)(np.random.randint(1, 2**28)) + num_place = rg(num_objs) + for i in range(num_place): + fac = np.random.choice(self.generators)(np.random.randint(1e7)) asset = fac.spawn_asset(0) fac.finalize_assets([asset]) max_dim = max(asset.dimensions.x, asset.dimensions.y, asset.dimensions.z) @@ -125,6 +145,7 @@ def place_objs( check_bvh_intersection(bvh, room_bvh) or ( not collision_existing + and existing_obj_bvh is not None and check_bvh_intersection(bvh, existing_obj_bvh) ) or ( @@ -132,10 +153,12 @@ def place_objs( and check_bvh_intersection(bvh, placed_obj_bvhs) ) ): - logger.info(f"Sample {j} of asset {i} rejected, resampling...") + logger.debug(f"Sample {j} of asset {i} rejected, resampling...") if i == sample_retries - 1: butil.delete(asset) else: - logger.info(f"Placing object {asset.name}") + logger.info( + f"{self.__class__.__name__} placing object {i}/{num_place}, {asset.name=}" + ) placed_obj_bvhs.append(bvh) break diff --git a/infinigen_examples/generate_indoors.py b/infinigen_examples/generate_indoors.py index e05dc0d99..cc9ef165d 100644 --- a/infinigen_examples/generate_indoors.py +++ b/infinigen_examples/generate_indoors.py @@ -20,6 +20,7 @@ import gin import numpy as np +from infinigen import repo_root from infinigen.assets import lighting from infinigen.assets.materials import invisible_to_camera from infinigen.assets.objects.wall_decorations.skirting_board import make_skirting_board @@ -314,34 +315,20 @@ def solve_small(): ) def place_floating(): - pholder_rooms = bpy.data.collections.get("placeholders:room_meshes") - pholder_cutters = bpy.data.collections.get("placeholders:portal_cutters") + pholder_rooms = butil.get_collection("placeholders:room_meshes") + pholder_cutters = butil.get_collection("placeholders:portal_cutters") + pholder_objs = butil.get_collection("placeholders") - meshes_to_join = [] - - for obj in pholder_cutters.objects: - meshes_to_join.append(butil.copy(obj)) - - for obj in pholder_rooms.objects: - meshes_to_join.append(butil.copy(obj)) - - joined_room = butil.join_objects(meshes_to_join) - - pholder_objs = bpy.data.collections.get("placeholders") - objs_to_join = [] - - for obj in pholder_objs.objects: - objs_to_join.append(butil.copy(obj)) - - joined_objs = butil.join_objects(objs_to_join) - - floating_paths = load_txt_list( - Path(__file__).parent.parent / "tests" / "assets" / "list_indoor_meshes.txt" + obj_fac_names = load_txt_list( + repo_root() / "tests" / "assets" / "list_indoor_meshes.txt" ) - facs = [import_item(path) for path in floating_paths] + facs = [import_item(path) for path in obj_fac_names] placer = FloatingObjectPlacement( - facs, cam_util.get_camera(0, 0), joined_room, joined_objs + generators=facs, + camera=cam_util.get_camera(0, 0), + background_objs=list(pholder_cutters.objects) + list(pholder_rooms.objects), + collision_objs=list(pholder_objs.objects), ) placer.place_objs( @@ -351,9 +338,6 @@ def place_floating(): collision_existing=overrides.get("enable_collision_solved", False), ) - butil.delete(joined_room) - butil.delete(joined_objs) - p.run_stage("floating_objs", place_floating, use_chance=False, default=state) door_filter = r.Domain({t.Semantics.Door}, [(cl.AnyRelation(), stages["rooms"])])