def _set_shading(self, entity, value): """ Switches shading mode of the selected entity. :param entity: An entity to modify. Type: bpy.types.Object :param value: Configuration data. Type: dict. """ MeshObject(entity).set_shading_mode(value["shading_mode"], value["angle_value"])
def run(self): """ Run this current module. """ # get all objects which material should be changed objects = MeshObject.convert_to_meshes( self.config.get_list("selector")) self.add_emission_to_materials(objects)
def sample(objects_to_sample: [MeshObject], sample_pose_func: Callable[[MeshObject], None], objects_to_check_collisions: [MeshObject] = None, max_tries: int = 1000): """ Samples positions and rotations of selected object inside the sampling volume while performing mesh and bounding box collision checks. :param objects_to_sample: A list of mesh objects whose poses are sampled based on the given function. :param sample_pose_func: The function to use for sampling the pose of a given object. :param objects_to_check_collisions: A list of mesh objects who should not be considered when checking for collisions. :param max_tries: Amount of tries before giving up on an object and moving to the next one. """ # After this many tries we give up on current object and continue with the rest if objects_to_check_collisions is None: objects_to_check_collisions = MeshObject.convert_to_meshes(get_all_blender_mesh_objects()) # Among objects_to_sample only check collisions against already placed objects cur_objects_to_check_collisions = list(set(objects_to_check_collisions) - set(objects_to_sample)) if max_tries <= 0: raise ValueError("The value of max_tries must be greater than zero: {}".format(max_tries)) if not objects_to_sample: raise Exception("The list of objects_to_sample can not be empty!") # cache to fasten collision detection bvh_cache = {} # for every selected object for obj in objects_to_sample: no_collision = True amount_of_tries_done = -1 # Try max_iter amount of times for i in range(max_tries): # Put the top object in queue at the sampled point in space sample_pose_func(obj) bpy.context.view_layer.update() # Remove bvh cache, as object has changed if obj.get_name() in bvh_cache: del bvh_cache[obj.get_name()] no_collision = CollisionUtility.check_intersections(obj, bvh_cache, cur_objects_to_check_collisions, []) # If no collision then keep the position if no_collision: amount_of_tries_done = i break # After placing an object, we will check collisions with it cur_objects_to_check_collisions.append(obj) if amount_of_tries_done == -1: amount_of_tries_done = max_tries if not no_collision: print("Could not place " + obj.get_name() + " without a collision.") else: print("It took " + str(amount_of_tries_done + 1) + " tries to place " + obj.get_name())
def _make_lamp_emissive(obj: MeshObject, light: dict[list[str], list[str]], collection_of_mats: dict[dict[Material]], lightbulb_emission_strength: float = 15, lampshade_emission_strength: float = 7): """ Adds an emission shader to the object materials which are specified in the light dictionary :param obj: The blender object. :param light: A dictionary of two lists. The first list specifies all materials which should act as a lightbulb, the second one lists all materials corresponding to lampshades. :param collection_of_mats: A dictionary that contains materials for lamps, windows and ceilings. :param lightbulb_emission_strength: The emission strength that should be used for light bulbs. Default: 15 :param lampshade_emission_strength: The emission strength that should be used for lamp shades. Default: 7 """ for i, m in enumerate(obj.get_materials()): mat_name = m.get_name() if "." in mat_name: mat_name = mat_name[:mat_name.find(".")] if mat_name in light[0] or mat_name in light[1]: old_mat_name = m.get_name() if old_mat_name in collection_of_mats["lamp"]: # this material was used as a ceiling before use that one obj.set_material(i, collection_of_mats["lamp"][old_mat_name]) continue # copy the material if more than one users is using it if m.get_users() > 1: m = m.duplicate() obj.set_material(i, m) # rename the material m.set_name(m.get_name() + "_emission") emission = m.get_nodes_with_type("Emission") if not emission: if mat_name in light[0]: # If the material corresponds to light bulb emission_strength = lightbulb_emission_strength else: # If the material corresponds to a lampshade emission_strength = lampshade_emission_strength m.make_emissive(emission_strength, keep_using_base_color=False, emission_color=m.blender_obj.diffuse_color) collection_of_mats["lamp"][old_mat_name] = m
def simulate(min_simulation_time: float = 4.0, max_simulation_time: float = 40.0, check_object_interval: float = 2.0, object_stopped_location_threshold: float = 0.01, object_stopped_rotation_threshold: float = 0.1, substeps_per_frame: int = 10, solver_iters: int = 10) -> dict: """ Simulates the current scene. The simulation is run for at least `min_simulation_time` seconds and at a maximum `max_simulation_time` seconds. Every `check_object_interval` seconds, it is checked if the maximum object movement in the last second is below a given threshold. If that is the case, the simulation is stopped. The origin of all objects is set to their center of mass in this function which is necessary to achieve a realistic simulation in blender (see https://blender.stackexchange.com/questions/167488/physics-not-working-as-expected) Also the scale of each participating object is persisted as scale != 1 can make the simulation unstable. :param min_simulation_time: The minimum number of seconds to simulate. :param max_simulation_time: The maximum number of seconds to simulate. :param check_object_interval: The interval in seconds at which all objects should be checked if they are still moving. If all objects have stopped moving, than the simulation will be stopped. :param object_stopped_location_threshold: The maximum difference per second and per coordinate in the rotation Euler vector that is allowed. such that an object is still recognized as 'stopped moving'. :param object_stopped_rotation_threshold: The maximum difference per second and per coordinate in the rotation Euler vector that is allowed. such that an object is still recognized as 'stopped moving'. :param substeps_per_frame: Number of simulation steps taken per frame. :param solver_iters: Number of constraint solver iterations made per simulation step. :return: A dict containing for every active object the shift that was added to their origins. """ # Shift the origin of all objects to their center of mass to make the simulation more realistic origin_shift = {} objects_with_physics = [ MeshObject(obj) for obj in get_all_blender_mesh_objects() if obj.rigid_body is not None ] for obj in objects_with_physics: prev_origin = obj.get_origin() new_origin = obj.set_origin(mode="CENTER_OF_VOLUME") origin_shift[obj.get_name()] = new_origin - prev_origin # Persist mesh scaling as having a scale != 1 can make the simulation unstable obj.persist_transformation_into_mesh(location=False, rotation=False, scale=True) # Configure simulator bpy.context.scene.rigidbody_world.substeps_per_frame = substeps_per_frame bpy.context.scene.rigidbody_world.solver_iterations = solver_iters # Perform simulation PhysicsSimulation._do_simulation(min_simulation_time, max_simulation_time, check_object_interval, object_stopped_location_threshold, object_stopped_rotation_threshold) return origin_shift
def _make_window_emissive(obj: MeshObject, collection_of_mats: dict[dict[Material]]): """ Makes the given window object emissive. For each material with alpha < 1. Uses a light path node to make it emit light, but at the same time look like a principle material. Otherwise windows would be completely white. :param obj: A window object. :param collection_of_mats: A dictionary that contains materials for lamps, windows and ceilings. """ for i, m in enumerate(obj.get_materials()): # All parameters imported from the .mtl file are stored inside the principled bsdf node principled_node = m.get_the_one_node_with_type("BsdfPrincipled") alpha = principled_node.inputs['Alpha'].default_value if alpha < 1: mat_name = m.get_name() if mat_name in collection_of_mats["window"]: # this material was used as a ceiling before use that one obj.set_material(i, collection_of_mats["window"][mat_name]) continue # copy the material if more than one users is using it if m.get_users() > 1: m = m.duplicate() obj.set_material(i, m) # rename the material m.set_name(m.get_name() + "_emission") if not m.get_nodes_with_type('Emission'): transparent_node = m.new_node('ShaderNodeBsdfDiffuse') transparent_node.inputs['Color'].default_value[:3] = ( 0.285, 0.5, 0.48) m.make_emissive(emission_strength=10, keep_using_base_color=False, emission_color=(1, 1, 1, 1), non_emissive_color_socket=transparent_node. outputs['BSDF']) collection_of_mats["window"][mat_name] = m
def _make_ceiling_emissive(self, obj: MeshObject): """ Makes the given ceiling object emissive, s.t. there is always a little bit ambient light. :param obj: The ceiling object. """ for i, m in enumerate(obj.get_materials()): mat_name = m.get_name() if mat_name in self._collection_of_mats["ceiling"]: # this material was used as a ceiling before use that one obj.set_material(i, self._collection_of_mats["ceiling"][mat_name]) continue # copy the material if more than one users is using it if m.get_users() > 1: m = m.duplicate() obj.set_material(i, m) # rename the material m.set_name(m.get_name() + "_emission") if not m.get_nodes_with_type("Emission") and m.get_nodes_with_type("BsdfPrincipled"): m.make_emissive(emission_strength=self.config.get_float("ceiling_emission_strength", 1.5), emission_color=(1, 1, 1, 1), keep_using_base_color=False) self._collection_of_mats["ceiling"][mat_name] = m
def assign_materials_to_floor_wall_ceiling( floor_obj: MeshObject, wall_obj: MeshObject, ceiling_obj: MeshObject, assign_material_to_ceiling: bool, materials: [Material]): """ Assigns materials to the floor, wall and ceiling. These are randomly selected from the CCMaterials. This means it is required that the CCMaterialLoader has been executed before, this module is run. """ # first create a uv mapping for each of the three objects for obj in [floor_obj, wall_obj, ceiling_obj]: if obj is not None: obj.edit_mode() bpy.ops.mesh.select_all(action='SELECT') bpy.ops.uv.cube_project(cube_size=1.0) obj.object_mode() if materials: floor_obj.replace_materials(random.choice(materials)) wall_obj.replace_materials(random.choice(materials)) if ceiling_obj is not None and assign_material_to_ceiling: ceiling_obj.replace_materials(random.choice(materials)) else: warnings.warn( "There were no CCMaterials found, which means the CCMaterialLoader was not executed first!" "No materials have been assigned to the walls, floors and possible ceiling." )
def sample_new_object_poses_on_face(current_obj: MeshObject, face_bb, bvh_cache_for_intersection: dict, placed_objects: [MeshObject], wall_obj: MeshObject): """ Sample new object poses on the current `floor_obj`. :param face_bb: :return: True, if there is no collision """ random_placed_value = [ random.uniform(face_bb[0][i], face_bb[1][i]) for i in range(2) ] random_placed_value.append(0.0) # floor z value random_placed_rotation = [0, 0, random.uniform(0, np.pi * 2.0)] current_obj.set_location(random_placed_value) current_obj.set_rotation_euler(random_placed_rotation) bpy.context.view_layer.update() # Remove bvh cache, as object has changed if current_obj.get_name() in bvh_cache_for_intersection: del bvh_cache_for_intersection[current_obj.get_name()] # perform check if object can be placed there no_collision = CollisionUtility.check_intersections( current_obj, bvh_cache=bvh_cache_for_intersection, objects_to_check_against=placed_objects, list_of_objects_with_no_inside_check=[wall_obj]) return no_collision
def run(self): """ Run this current module. """ # get all objects which material should be changed objects = MeshObject.convert_to_meshes(self.config.get_list("selector")) SurfaceLighting.run( objects, emission_strength=self.emission_strength, keep_using_base_color=self.keep_using_base_color, emission_color=self.emission_color )
def run(self): """ Samples positions and rotations of selected object inside the sampling volume while performing mesh and bounding box collision checks in the following steps: 1. While we have objects remaining and have not run out of tries - sample a point. 2. If no collisions are found keep the point. """ objects_to_sample = self.config.get_list( "objects_to_sample", get_all_blender_mesh_objects()) objects_to_check_collisions = self.config.get_list( "objects_to_check_collisions", get_all_blender_mesh_objects()) max_tries = self.config.get_int("max_iterations", 1000) def sample_pose(obj: MeshObject): obj.set_location(self.config.get_vector3d("pos_sampler")) obj.set_rotation_euler(self.config.get_vector3d("rot_sampler")) ObjectPoseSampler.sample( objects_to_sample=MeshObject.convert_to_meshes(objects_to_sample), sample_pose_func=sample_pose, objects_to_check_collisions=MeshObject.convert_to_meshes( objects_to_check_collisions), max_tries=max_tries)
def run(self): # Load the height levels of this scene if not self.config.get_bool('is_replica_object', False): file_path = self.config.get_string('height_list_path') else: folder_path = os.path.join('resources', 'replica', 'height_levels', self.config.get_string('data_set_name')) file_path = Utility.resolve_path( os.path.join(folder_path, 'height_list_values.txt')) if 'mesh' in bpy.data.objects: mesh = MeshObject(bpy.data.objects['mesh']) else: raise Exception("Mesh object is not defined!") # Find floor object if 'floor' in bpy.data.objects: floor_object = MeshObject(bpy.data.objects['floor']) else: raise Exception("No floor object is defined!") self.point_sampler = ReplicaPointInRoomSampler(mesh, floor_object, file_path) super().run()
def run(self): """ Replaces mesh objects with another mesh objects and scales them accordingly, the replaced objects and the objects to replace with in following steps: 1. Find which object to replace. 2. Place the new object in place of the object to be replaced and scale accordingly. 2. If there is no collision, between that object and the objects in the scene, then do replace and delete the original object. """ if self.config.has_param("relative_rotation_sampler"): def relative_pose_sampler(obj): # Sample random rotation and apply it to the objects pose obj.blender_obj.rotation_euler.rotate(Euler(self.config.get_list("relative_rotation_sampler"))) else: relative_pose_sampler = None ObjectReplacer.replace_multiple( objects_to_be_replaced=MeshObject.convert_to_meshes(self.config.get_list("objects_to_be_replaced", [])), objects_to_replace_with=MeshObject.convert_to_meshes(self.config.get_list("objects_to_replace_with", [])), ignore_collision_with=MeshObject.convert_to_meshes(self.config.get_list("ignore_collision_with", [])), replace_ratio=self.config.get_float("replace_ratio", 1), copy_properties=self.config.get_float("copy_properties", 1), max_tries=self.config.get_int("max_tries", 100), relative_pose_sampler=relative_pose_sampler )
def __init__(self, replica_mesh: MeshObject, replica_floor: MeshObject, height_list_file_path: str): """ Collect object containing all floors of all rooms and read in text file containing possible height values. :param replica_mesh: The replica mesh object. :param replica_floor: The replica floor object. :param height_list_file_path: The path to the file containing possible height values. """ # Determine bounding box of the scene bounding_box = replica_mesh.get_bound_box() self.bounding_box = {"min": bounding_box[0], "max": bounding_box[-2]} self.floor_object = replica_floor with open(height_list_file_path) as file: self.floor_height_values = [ float(val) for val in ast.literal_eval(file.read()) ]
def run(self): """ Samples based on the description above. :return: Sampled value. Type: mathutils.Vector """ # invoke a Getter, get a list of objects to manipulate objects = MeshObject.convert_to_meshes( self.config.get_list("to_sample_on")) if len(objects) == 0: raise Exception( "The used selector returns an empty list, check the self.config value: \"to_sample_on\"" ) # relative area on selected face where to sample points face_sample_range = self.config.get_vector2d("face_sample_range", [0.0, 1.0]) # min and max distance to the bounding box min_height = self.config.get_float("min_height", 0.0) max_height = self.config.get_float("max_height", 1.0) if max_height < min_height: raise Exception("The minimum height ({}) must be smaller " "than the maximum height ({})!".format( min_height, max_height)) use_ray_trace_check = self.config.get_bool('use_ray_trace_check', False) # the upper direction, to define what is up in the scene # is used to selected the correct face upper_dir = self.config.get_vector3d("upper_dir", [0.0, 0.0, 1.0]) upper_dir.normalize() # if this is true the up direction is determined by the upper_dir vector, if it is false the # face normal is used use_upper_dir = self.config.get_bool("use_upper_dir", True) return UpperRegionSampler.sample( objects_to_sample_on=objects, face_sample_range=face_sample_range, min_height=min_height, max_height=max_height, use_ray_trace_check=use_ray_trace_check, upper_dir=upper_dir, use_upper_dir=use_upper_dir)
def _load_room(node: dict, metadata: dict, material_adjustments: list, transform: Matrix, house_id: str, parent: MeshObject, room_per_object: dict) -> List[MeshObject]: """ Load the room specified in the given node. :param node: The node dict which contains information from house.json.. :param metadata: A dict of metadata which will be written into the object's custom data. :param material_adjustments: Adjustments to the materials which were specified inside house.json. :param transform: The transformation that should be applied to the loaded objects. :param house_id: The id of the current house. :param parent: The parent object to which the room should be linked :param room_per_object: A dict for object -> room lookup (Will be written into) :return: The list of loaded mesh objects. """ # Build empty room object which acts as a parent for all objects inside room_obj = MeshObject.create_empty("Room#" + node["id"]) room_obj.set_cp("type", "Room") room_obj.set_cp("bbox", SuncgLoader._correct_bbox_frame(node["bbox"])) room_obj.set_cp("roomTypes", node["roomTypes"]) room_obj.set_parent(parent) loaded_objects = [room_obj] # Store indices of all contained objects in if "nodeIndices" in node: for child_id in node["nodeIndices"]: room_per_object[child_id] = room_obj if "hideFloor" not in node or node["hideFloor"] != 1: metadata["type"] = "Floor" metadata["category_id"] = LabelIdMapping.label_id_map["floor"] metadata["fine_grained_class"] = "floor" loaded_objects += SuncgLoader._load_obj(os.path.join(SuncgLoader._suncg_dir, "room", house_id, node["modelId"] + "f.obj"), metadata, material_adjustments, transform, room_obj) if "hideCeiling" not in node or node["hideCeiling"] != 1: metadata["type"] = "Ceiling" metadata["category_id"] = LabelIdMapping.label_id_map["ceiling"] metadata["fine_grained_class"] = "ceiling" loaded_objects += SuncgLoader._load_obj(os.path.join(SuncgLoader._suncg_dir, "room", house_id, node["modelId"] + "c.obj"), metadata, material_adjustments, transform, room_obj) if "hideWalls" not in node or node["hideWalls"] != 1: metadata["type"] = "Wall" metadata["category_id"] = LabelIdMapping.label_id_map["wall"] metadata["fine_grained_class"] = "wall" loaded_objects += SuncgLoader._load_obj(os.path.join(SuncgLoader._suncg_dir, "room", house_id, node["modelId"] + "w.obj"), metadata, material_adjustments, transform, room_obj) return loaded_objects
def load(filepath: str, cached_objects: dict = None, **kwargs) -> List[MeshObject]: """ Import all objects for the given file and returns the loaded objects In .obj files a list of objects can be saved in. In .ply files only one object can saved so the list has always at most one element :param filepath: the filepath to the location where the data is stored :param cached_objects: a dict of filepath to objects, which have been loaded before, to avoid reloading (the dict is updated in this function) :param kwargs: all other params are handed directly to the bpy loading fct. check the corresponding documentation :return: The list of loaded mesh objects. """ if os.path.exists(filepath): if cached_objects is not None and isinstance(cached_objects, dict): if filepath in cached_objects.keys(): created_obj = [] for obj in cached_objects[filepath]: # duplicate the object created_obj.append(obj.duplicate()) return created_obj else: loaded_objects = ObjectLoader.load(filepath, cached_objects=None, **kwargs) cached_objects[filepath] = loaded_objects return loaded_objects else: # save all selected objects previously_selected_objects = set(bpy.context.selected_objects) if filepath.endswith('.obj'): # load an .obj file: bpy.ops.import_scene.obj(filepath=filepath, **kwargs) elif filepath.endswith('.ply'): # load a .ply mesh bpy.ops.import_mesh.ply(filepath=filepath, **kwargs) # add a default material to ply file mat = bpy.data.materials.new(name="ply_material") mat.use_nodes = True loaded_objects = list(set(bpy.context.selected_objects) - previously_selected_objects) for obj in loaded_objects: obj.data.materials.append(mat) # return all currently selected objects return MeshObject.convert_to_meshes(list(set(bpy.context.selected_objects) - previously_selected_objects)) else: raise Exception("The given filepath does not exist: {}".format(filepath))
def is_point_inside_object(obj: MeshObject, obj_BVHtree: mathutils.bvhtree.BVHTree, point: Vector): """ Checks whether the given point is inside the given object. This only works if the given object is watertight and has correct normals :param obj: The object :param obj_BVHtree: A bvh tree of the object :param point: The point to check :return: True, if the point is inside the object """ # Look for closest point on object nearest, normal, _, _ = obj_BVHtree.find_nearest(point) # Compute direction p2 = nearest - point # Compute dot product between direction and normal vector a = p2.normalized().dot( (obj.get_rotation().to_matrix() @ normal).normalized()) return a >= 0.0
def _transform_and_colorize_object(object: MeshObject, material_adjustments: list, transform: Matrix = None, parent: MeshObject = None): """ Applies the given transformation to the object and refactors its materials. Material is replaced with an existing material if possible or is changed according to the material_adjustments :param object: The object to use. :param material_adjustments: A list of adjustments to make. (Each element i corresponds to material_i) :param transform: The transformation matrix to apply :param parent: The parent object to which the object should be linked """ if parent is not None: object.set_parent(parent) if transform is not None: # Apply transformation object.apply_T(transform) for i, mat in enumerate(object.get_materials()): # the material name of an object contains a nr, which is mentioned in the material_adjustments index = mat.get_name()[mat.get_name().find("_") + 1:] if "." in index: index = index[:index.find(".")] index = int(index) # check if this index is mentioned in material_adjustments and if a texture is necessary force_texture = index < len( material_adjustments ) and "texture" in material_adjustments[index] SuncgLoader._recreate_material_nodes(mat, force_texture) if index < len(material_adjustments): SuncgLoader._adjust_material_nodes(mat, material_adjustments[index]) mat_type, value = SuncgLoader._get_type_and_value_from_mat(mat) current_mats = SuncgLoader._collection_of_loaded_mats[mat_type] if value in current_mats: object.set_material(i, current_mats[value]) else: # save the current material for later current_mats[value] = mat
def _add_rigidbody(self): """ Adds a rigidbody element to all mesh objects and sets their physics attributes depending on their custom properties """ # Temporary function which returns either the value set in the custom properties (if set) or the fallback value. def get_physics_attribute(obj, cp_name, default_value): if cp_name in obj: return obj[cp_name] else: return default_value # Go over all mesh objects and set their physics attributes based on the custom properties or (if not set) based on the module config for obj in get_all_blender_mesh_objects(): mesh_obj = MeshObject(obj) # Skip if the object has already an active rigid body component if mesh_obj.get_rigidbody() is None: if "physics" not in obj: raise Exception("The obj: '{}' has no physics attribute, each object needs one.".format(obj.name)) # Collect physics attributes collision_shape = get_physics_attribute(obj, "physics_collision_shape", self.collision_shape) collision_margin = get_physics_attribute(obj, "physics_collision_margin", self.collision_margin) mass = get_physics_attribute(obj, "physics_mass", None if self.mass_scaling else 1) collision_mesh_source = get_physics_attribute(obj, "physics_collision_mesh_source", self.collision_mesh_source) friction = get_physics_attribute(obj, "physics_friction", self.friction) angular_damping = get_physics_attribute(obj, "physics_angular_damping", self.angular_damping) linear_damping = get_physics_attribute(obj, "physics_linear_damping", self.linear_damping) # Set physics attributes mesh_obj.enable_rigidbody( active=obj["physics"], collision_shape="COMPOUND" if collision_shape == "CONVEX_DECOMPOSITION" else collision_shape, collision_margin=collision_margin, mass=mass, mass_factor=self.mass_factor, collision_mesh_source=collision_mesh_source, friction=friction, angular_damping=angular_damping, linear_damping=linear_damping ) # Check if object needs decomposition if collision_shape == "CONVEX_DECOMPOSITION": mesh_obj.build_convex_decomposition_collision_shape(self._temp_dir, self.convex_decomposition_cache_path)
def visible_objects(cam2world_matrix: Union[Matrix, np.ndarray], sqrt_number_of_rays: int) -> [MeshObject]: """ Returns a set of objects visible from the given camera pose. Sends a grid of rays through the camera frame and returns all objects hit by at least one ray. :param cam2world_matrix: The world matrix which describes the camera orientation to check. :param sqrt_number_of_rays: The square root of the number of rays which will be used to determine the visible objects. :return: A set of objects visible hit by the sent rays. """ cam2world_matrix = Matrix(cam2world_matrix) visible_objects = set() cam_ob = bpy.context.scene.camera cam = cam_ob.data # Get position of the corners of the near plane frame = cam.view_frame(scene=bpy.context.scene) # Bring to world space frame = [cam2world_matrix @ v for v in frame] # Compute vectors along both sides of the plane vec_x = frame[1] - frame[0] vec_y = frame[3] - frame[0] # Go in discrete grid-like steps over plane position = cam2world_matrix.to_translation() for x in range(0, sqrt_number_of_rays): for y in range(0, sqrt_number_of_rays): # Compute current point on plane end = frame[0] + vec_x * x / float(sqrt_number_of_rays - 1) + vec_y * y / float( sqrt_number_of_rays - 1) # Send ray from the camera position through the current point on the plane _, _, _, _, hit_object, _ = bpy.context.scene.ray_cast( bpy.context.view_layer.depsgraph, position, end - position) # Add hit object to set visible_objects.add(MeshObject(hit_object)) return visible_objects
def _load_box(node: dict, material_adjustments: list, transform: Matrix, parent: MeshObject, label_mapping: LabelIdMapping) -> List[MeshObject]: """ Creates a cube inside blender which follows the specifications of the given node. :param node: The node dict which contains information from house.json.. :param material_adjustments: Adjustments to the materials which were specified inside house.json. :param transform: The transformation that should be applied to the loaded objects. :param parent: The parent object to which the ground should be linked :return: The list of loaded mesh objects. """ box = MeshObject.create_primitive("CUBE") box.set_name("Box#" + node["id"]) # Scale the cube to the required dimensions box.set_local2world_mat( Matrix.Scale(node["dimensions"][0] / 2, 4, (1.0, 0.0, 0.0)) @ Matrix.Scale(node["dimensions"][1] / 2, 4, (0.0, 1.0, 0.0)) @ Matrix.Scale( node["dimensions"][2] / 2, 4, (0.0, 0.0, 1.0))) # Create UV mapping (beforehand we apply the scaling from the previous step, such that the resulting uv mapping has the correct aspect) bpy.ops.object.transform_apply(scale=True) bpy.ops.object.editmode_toggle() bpy.ops.uv.cube_project() bpy.ops.object.editmode_toggle() # Create an empty material which is filled in the next step box.new_material("material_0") SuncgLoader._transform_and_colorize_object(box, material_adjustments, transform, parent) # set class to void box.set_cp("category_id", label_mapping.id_from_label("void")) # Rotate cube to match objects loaded from .obj, has to be done after transformations have been applied box.set_local2world_mat( Matrix.Rotation(math.radians(90), 4, "X") @ Matrix( box.get_local2world_mat())) return [box]
def run(self): # use a loader module to load objects bpy.ops.object.select_all(action='SELECT') previously_selected_objects = set(bpy.context.selected_objects) module_list_config = self.config.get_list("used_loader_config") modules = Utility.initialize_modules(module_list_config) for module in modules: print("Running module " + module.__class__.__name__) module.run() bpy.ops.object.select_all(action='SELECT') loaded_objects = list( set(bpy.context.selected_objects) - previously_selected_objects) # only select non see through materials config = { "conditions": { "cp_is_cc_texture": True, "cf_principled_bsdf_Alpha_eq": 1.0 } } material_getter = MaterialProvider(Config(config)) all_cc_materials = Material.convert_to_materials(material_getter.run()) RandomRoomConstructor.construct( used_floor_area=self.used_floor_area, interior_objects=MeshObject.convert_to_meshes(loaded_objects), materials=all_cc_materials, amount_of_extrusions=self.amount_of_extrusions, fac_from_square_room=self.fac_from_square_room, corridor_width=self.corridor_width, wall_height=self.wall_height, amount_of_floor_cuts=self.amount_of_floor_cuts, only_use_big_edges=self.only_use_big_edges, create_ceiling=self.create_ceiling, assign_material_to_ceiling=self.assign_material_to_ceiling, placement_tries_per_face=self.tries_per_face, amount_of_objects_per_sq_meter=self.amount_of_objects_per_sq_meter)
Initializer.init() # load the objects into the scene label_mapping = LabelIdMapping.from_csv( Utility.resolve_path( os.path.join('resources', 'id_mappings', 'nyu_idset.csv'))) objs = SuncgLoader.load(args.house, label_mapping) # makes Suncg objects emit light SuncgLighting.light() # Init sampler for sampling locations inside the loaded suncg house point_sampler = SuncgPointInRoomSampler(objs) # Init bvh tree containing all mesh objects bvh_tree = MeshObject.create_bvh_tree_multi_objects( [o for o in objs if isinstance(o, MeshObject)]) poses = 0 tries = 0 while tries < 10000 and poses < 5: # Sample point inside house height = np.random.uniform(0.5, 2) location, _ = point_sampler.sample(height) # Sample rotation (fix around X and Y axis) euler_rotation = np.random.uniform([1.2217, 0, 0], [1.2217, 0, 6.283185307]) cam2world_matrix = MathUtility.build_transformation_mat( location, euler_rotation) # Check that obstacles are at least 1 meter away from the camera and make sure the view interesting enough if CameraValidation.perform_obstacle_in_view_check(
for i in range(15): interior_objects.extend( IKEALoader.load(args.ikea_path, ["bed", "chair", "desk", "bookshelf"])) # Construct random room and fill with interior_objects objects = RandomRoomConstructor.construct(25, interior_objects, materials, amount_of_extrusions=5) # Bring light into the room SurfaceLighting.run([obj for obj in objects if obj.get_name() == "Ceiling"], emission_strength=4.0) # Init bvh tree containing all mesh objects bvh_tree = MeshObject.create_bvh_tree_multi_objects(objects) floor = [obj for obj in objects if obj.get_name() == "Floor"][0] poses = 0 tries = 0 while tries < 10000 and poses < 5: # Sample point location = UpperRegionSampler.sample(floor, min_height=1.5, max_height=1.8) # Sample rotation rotation = np.random.uniform([1.0, 0, 0], [1.4217, 0, 6.283185307]) cam2world_matrix = MathUtility.build_transformation_mat(location, rotation) # Check that obstacles are at least 1 meter away from the camera and make sure the view interesting enough if CameraValidation.perform_obstacle_in_view_check(cam2world_matrix, {"min": 1.2}, bvh_tree) and \ CameraValidation.scene_coverage_score(cam2world_matrix) > 0.4 and \ floor.position_is_above_object(location): # Persist camera pose
def run(self): # construct a random room self.construct_random_room() self.placed_objects.append(MeshObject(self.wall_obj)) if self.ceiling_obj is not None: self.placed_objects.append(MeshObject(self.ceiling_obj)) # assign materials to all existing objects self.assign_materials_to_floor_wall_ceiling() # use a loader module to load objects bpy.ops.object.select_all(action='SELECT') previously_selected_objects = set(bpy.context.selected_objects) module_list_config = self.config.get_list("used_loader_config") modules = Utility.initialize_modules(module_list_config) for module in modules: print("Running module " + module.__class__.__name__) module.run() bpy.ops.object.select_all(action='SELECT') loaded_objects = list( set(bpy.context.selected_objects) - previously_selected_objects) # get all floor faces and save their size and bounding box for the round robin bpy.ops.object.select_all(action='DESELECT') self.floor_obj.select_set(True) bpy.ops.object.mode_set(mode='EDIT') mesh = self.floor_obj.data bm = bmesh.from_edit_mesh(mesh) bm.faces.ensure_lookup_table() list_of_face_sizes = [] list_of_face_bb = [] for face in bm.faces: list_of_face_sizes.append(face.calc_area()) list_of_verts = [v.co for v in face.verts] bb_min_point, bb_max_point = np.min(list_of_verts, axis=0), np.max(list_of_verts, axis=0) list_of_face_bb.append((bb_min_point, bb_max_point)) bpy.ops.object.mode_set(mode='OBJECT') bm.free() mesh.update() bpy.ops.object.select_all(action='DESELECT') total_face_size = sum(list_of_face_sizes) # sort them after size loaded_objects.sort(key=lambda obj: get_bound_volume(obj)) loaded_objects.reverse() list_of_deleted_objects = [] step_size = 1.0 / self.amount_of_objects_per_sq_meter * float( len(loaded_objects)) current_step_size_counter = random.uniform(-step_size, step_size) for selected_obj in loaded_objects: current_obj = selected_obj is_duplicated = False def duplicate_obj(cur_obj): # object was placed before needs to be duplicated first bpy.ops.object.duplicate({ "object": cur_obj, "selected_objects": [cur_obj] }) cur_obj = bpy.context.selected_objects[-1] return cur_obj # if the step size is bigger than the room size, certain objects need to be skipped if step_size > total_face_size: current_step_size_counter += total_face_size if current_step_size_counter > step_size: current_step_size_counter = random.uniform( -step_size, step_size) continue # walk over all faces in a round robin fashion total_acc_size = 0 # select a random start point current_i = random.randrange(len(list_of_face_sizes)) current_accumulated_face_size = random.uniform(0, step_size + 1e-7) # check if the accumulation of all visited faces is bigger than the sum of all of them while total_acc_size < total_face_size: face_size = list_of_face_sizes[current_i] face_bb = list_of_face_bb[current_i] if face_size < step_size: # face size is bigger than one step current_accumulated_face_size += face_size if current_accumulated_face_size > step_size: for _ in range(self.tries_per_face): found_spot = self.sample_new_object_poses_on_face( current_obj, face_bb) if found_spot: self.placed_objects.append( MeshObject(current_obj)) current_obj = duplicate_obj( cur_obj=current_obj) is_duplicated = True break current_accumulated_face_size -= step_size else: # face size is bigger than one step amount_of_steps = int( (face_size + current_accumulated_face_size) / step_size) for i in range(amount_of_steps): for _ in range(self.tries_per_face): found_spot = self.sample_new_object_poses_on_face( current_obj, face_bb) if found_spot: self.placed_objects.append( MeshObject(current_obj)) current_obj = duplicate_obj( cur_obj=current_obj) is_duplicated = True break # left over value is used in next round current_accumulated_face_size = face_size - ( amount_of_steps * step_size) current_i = (current_i + 1) % len(list_of_face_sizes) total_acc_size += face_size # remove current obj from the bvh cache if current_obj.name in self.bvh_cache_for_intersection: del self.bvh_cache_for_intersection[current_obj.name] # if there was no collision save the object in the placed list if is_duplicated: # delete the duplicated object list_of_deleted_objects.append(current_obj) # delete all objects, which have not been placed during the operation bpy.ops.object.select_all(action='DESELECT') for obj in list_of_deleted_objects: obj.select_set(True) # delete the loaded objects, which couldn't be placed for obj in loaded_objects: if MeshObject(obj) not in self.placed_objects: obj.select_set(True) bpy.ops.object.delete()
def _load_mesh(obj_id: int, model_p: dict, bop_dataset_name: str, has_external_texture: bool, temp_dir: str, allow_duplication: bool, scale: float = 1) -> MeshObject: """ Loads BOP mesh and sets category_id. :param obj_id: The obj_id of the BOP Object. :param model_p: model parameters defined in dataset_params.py in bop_toolkit. :param bop_dataset_name: The name of the used bop dataset. :param has_external_texture: Set to True, if the object has an external texture. :param temp_dir: A temp directory which is used for writing the temporary .ply file. :param allow_duplication: If True, the object is duplicated if it already exists. :param scale: factor to transform set pose in mm or meters. :return: Loaded mesh object. """ model_path = model_p['model_tpath'].format(**{'obj_id': obj_id}) texture_file_path = "" # only needed for ycbv objects # Gets the objects if it is already loaded cur_obj = BopLoader._get_loaded_obj(model_path) # if the object was not previously loaded - load it, if duplication is allowed - duplicate it if cur_obj is None: if has_external_texture: if os.path.exists(model_path): new_file_ply_content = "" with open(model_path, "r") as file: new_file_ply_content = file.read() texture_pos = new_file_ply_content.find( "comment TextureFile ") + len( "comment TextureFile ") texture_file_name = new_file_ply_content[ texture_pos:new_file_ply_content. find("\n", texture_pos)] texture_file_path = os.path.join( os.path.dirname(model_path), texture_file_name) new_file_ply_content = new_file_ply_content.replace( "property float texture_u", "property float s") new_file_ply_content = new_file_ply_content.replace( "property float texture_v", "property float t") model_name = os.path.basename(model_path) tmp_ply_file = os.path.join(temp_dir, model_name) with open(tmp_ply_file, "w") as file: file.write(new_file_ply_content) bpy.ops.import_mesh.ply(filepath=tmp_ply_file) cur_obj = bpy.context.selected_objects[-1] else: bpy.ops.import_mesh.ply(filepath=model_path) cur_obj = bpy.context.selected_objects[-1] elif allow_duplication: bpy.ops.object.duplicate({ "object": cur_obj, "selected_objects": [cur_obj] }) cur_obj = bpy.context.selected_objects[-1] cur_obj.scale = Vector((scale, scale, scale)) cur_obj['category_id'] = obj_id cur_obj['model_path'] = model_path if not has_external_texture: mat = BopLoader._load_materials(cur_obj, bop_dataset_name) BopLoader._link_col_node(mat) elif texture_file_path != "": # ycbv objects contain normal image textures, which should be used instead of the vertex colors BopLoader._load_texture(cur_obj, texture_file_path, bop_dataset_name) cur_obj["is_bop_object"] = True cur_obj["bop_dataset_name"] = bop_dataset_name return MeshObject(cur_obj)
def load(path: str, obj_types: Union[list, str] = ["mesh", "empty"], name_regrex: str = None, data_blocks: Union[list, str] = "objects") -> List[MeshObject]: """ Loads entities (everything that can be stored in a .blend file's folders, see Blender's documentation for bpy.types.ID for more info) that match a name pattern from a specified .blend file's section/datablock. :param path: Path to a .blend file. :param obj_types: The type of objects to load. This parameter is only relevant when `data_blocks` is set to `"objects"`. Available options are: ['mesh', 'curve', 'hair', 'armature', 'empty', 'light', 'camera'] :param name_regrex: Regular expression representing a name pattern of entities' (everything that can be stored in a .blend file's folders, see Blender's documentation for bpy.types.ID for more info) names. :param data_blocks: The datablock or a list of datablocks which should be loaded from the given .blend file. Available options are: ['armatures', 'cameras', 'curves', 'hairs', 'images', 'lights', 'materials', 'meshes', 'objects', 'textures'] :return: The list of loaded mesh objects. """ # get a path to a .blend file path = Utility.resolve_path(path) data_blocks = BlendLoader._validate_and_standardizes_configured_list( data_blocks, BlendLoader.valid_datablocks, "data block") obj_types = BlendLoader._validate_and_standardizes_configured_list( obj_types, BlendLoader.valid_object_types, "object type") # Remember which orphans existed beforehand orphans_before = collect_all_orphan_datablocks() # Start importing blend file. All objects that should be imported need to be copied from "data_from" to "data_to" with bpy.data.libraries.load(path) as (data_from, data_to): for data_block in data_blocks: # Verify that the given data block is valid if hasattr(data_from, data_block): # Find all entities of this data block that match the specified pattern data_to_entities = [] for entity_name in getattr(data_from, data_block): if not name_regrex or re.fullmatch( name_regrex, entity_name) is not None: data_to_entities.append(entity_name) # Import them setattr(data_to, data_block, data_to_entities) print("Imported " + str(len(data_to_entities)) + " " + data_block) else: raise Exception("No such data block: " + data_block) # Go over all imported objects again loaded_objects = [] for data_block in data_blocks: # Some adjustments that only affect objects if data_block == "objects": for obj in getattr(data_to, data_block): # Check that the object type is desired if obj.type.lower() in obj_types: # Link objects to the scene bpy.context.collection.objects.link(obj) if obj.type == 'MESH': loaded_objects.append(MeshObject(obj)) elif obj.type == 'LIGHT': loaded_objects.append(Light(obj)) else: loaded_objects.append(Entity(obj)) # If a camera was imported if obj.type == 'CAMERA': # Make it the active camera in the scene bpy.context.scene.camera = obj # Find the maximum frame number of its key frames max_keyframe = -1 if obj.animation_data is not None: fcurves = obj.animation_data.action.fcurves for curve in fcurves: keyframe_points = curve.keyframe_points for keyframe in keyframe_points: max_keyframe = max( max_keyframe, keyframe.co[0]) # Set frame_end to the next free keyframe bpy.context.scene.frame_end = max_keyframe + 1 else: # Remove object again if its type is not desired bpy.data.objects.remove(obj, do_unlink=True) print("Selected " + str(len(loaded_objects)) + " of the loaded objects by type") else: loaded_objects.extend(getattr(data_to, data_block)) # As some loaded objects were deleted again due to their type, we need also to remove the dependent datablocks that were also loaded and are now orphans BlendLoader._purge_added_orphans(orphans_before, data_to) return loaded_objects
def run(self): self.point_sampler = Front3DPointInRoomSampler( MeshObject.convert_to_meshes(get_all_blender_mesh_objects())) super().run()
def _sample_cam_poses(self, config): """ Samples camera poses according to the given config :param config: The config object """ cam_ob = bpy.context.scene.camera cam = cam_ob.data # Set global parameters self.sqrt_number_of_rays = config.get_int("sqrt_number_of_rays", 10) self.max_tries = config.get_int("max_tries", 10000) self.proximity_checks = config.get_raw_dict("proximity_checks", {}) self.excluded_objects_in_proximity_check = config.get_list( "excluded_objs_in_proximity_check", []) self.min_interest_score = config.get_float("min_interest_score", 0.0) self.interest_score_range = config.get_float("interest_score_range", self.min_interest_score) self.interest_score_step = config.get_float("interest_score_step", 0.1) self.special_objects = config.get_list("special_objects", []) self.special_objects_weight = config.get_float( "special_objects_weight", 2) self._above_objects = MeshObject.convert_to_meshes( config.get_list("check_if_pose_above_object_list", [])) self.check_visible_objects = MeshObject.convert_to_meshes( config.get_list("check_if_objects_visible", [])) # Set camera intrinsics self._set_cam_intrinsics( cam, Config(self.config.get_raw_dict("intrinsics", {}))) if self.proximity_checks: # needs to build an bvh tree mesh_objects = [ MeshObject(obj) for obj in get_all_blender_mesh_objects() if obj not in self.excluded_objects_in_proximity_check ] self.bvh_tree = MeshObject.create_bvh_tree_multi_objects( mesh_objects) if self.interest_score_step <= 0.0: raise Exception( "Must have an interest score step size bigger than 0") # Determine the number of camera poses to sample number_of_poses = config.get_int("number_of_samples", 1) print("Sampling " + str(number_of_poses) + " cam poses") # Start with max interest score self.interest_score = self.interest_score_range # Init all_tries = 0 tries = 0 existing_poses = [] for i in range(number_of_poses): # Do until a valid pose has been found or the max number of tries has been reached while tries < self.max_tries: tries += 1 all_tries += 1 # Sample a new cam pose and check if its valid if self.sample_and_validate_cam_pose(config, existing_poses): break # If max tries has been reached if tries >= self.max_tries: # Decrease interest score and try again, if we have not yet reached minimum continue_trying, self.interest_score = CameraValidation.decrease_interest_score( self.interest_score, self.min_interest_score, self.interest_score_step) if continue_trying: tries = 0 print(str(all_tries) + " tries were necessary")