Example #1
0
    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"])
Example #2
0
    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)
Example #3
0
    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())
Example #4
0
    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
Example #6
0
    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
Example #7
0
    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
Example #8
0
    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."
            )
Example #9
0
    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
Example #10
0
    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
        )
Example #11
0
    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
        )
Example #14
0
    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)
Example #16
0
    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
Example #17
0
    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))
Example #18
0
    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
Example #19
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
Example #20
0
    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
Example #22
0
    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]
Example #23
0
    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)
Example #24
0
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(
Example #25
0
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()
Example #27
0
    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)
Example #28
0
    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
Example #29
0
 def run(self):
     self.point_sampler = Front3DPointInRoomSampler(
         MeshObject.convert_to_meshes(get_all_blender_mesh_objects()))
     super().run()
Example #30
0
    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")