def run(self):
        if self.config.has_param('path') and self.config.has_param('paths'):
            raise Exception(
                "Objectloader can not use path and paths in the same module!")
        if self.config.has_param('path'):
            file_path = resolve_path(self.config.get_string("path"))
            loaded_objects = load_obj(filepath=file_path)
        elif self.config.has_param('paths'):
            file_paths = self.config.get_list('paths')
            loaded_objects = []
            # the file paths are mapped here to object names
            cache_objects = {}
            for file_path in file_paths:
                resolved_file_path = resolve_path(file_path)
                current_objects = load_obj(filepath=resolved_file_path,
                                           cached_objects=cache_objects)
                loaded_objects.extend(current_objects)
        else:
            raise Exception(
                "Loader module needs either a path or paths config value")

        if not loaded_objects:
            raise Exception(
                "No objects have been loaded here, check the config.")

        # Set the add_properties of all imported objects
        self._set_properties(loaded_objects)
Exemple #2
0
def load_pix3d(used_category: str, data_path: str = 'resources/pix3d') -> List[MeshObject]:
    """ Loads one random Pix3D object from the given category.

    :param used_category: The category to use for example: 'bed', check the data_path/model folder for more categories.
                          Available: ['bed', 'bookcase', 'chair', 'desk', 'misc', 'sofa', 'table', 'tool', 'wardrobe']
    :param data_path: The path to the Pix3D folder.
    :return: The list of loaded mesh objects.
    """
    data_path = resolve_path(data_path)
    files_with_fitting_category = Pix3DLoader.get_files_with_category(used_category, data_path)

    selected_obj = random.choice(files_with_fitting_category)
    loaded_obj = load_obj(selected_obj)

    Pix3DLoader._correct_materials(loaded_obj)

    # removes the x axis rotation found in all ShapeNet objects, this is caused by importing .obj files
    # the object has the same pose as before, just that the rotation_euler is now [0, 0, 0]
    for obj in loaded_obj:
        obj.persist_transformation_into_mesh(location=False, rotation=True, scale=False)

    # move the origin of the object to the world origin and on top of the X-Y plane
    # makes it easier to place them later on, this does not change the `.location`
    for obj in loaded_obj:
        obj.move_origin_to_bottom_mean_point()
    bpy.ops.object.select_all(action='DESELECT')

    return loaded_obj
def load_scenenet(file_path: str, texture_folder: str, label_mapping: LabelIdMapping, unknown_texture_folder: str = None) -> List[MeshObject]:
    """ Loads all SceneNet objects at the given "file_path".

    The textures for each object are sampled based on the name of the object, if the name is not represented in the
    texture folder the unknown folder is used. This folder does not exists, after downloading the texture dataset.
    Make sure to create and put some textures, you want to use for these instances there.

    All objects get "category_id" set based on the data in the "resources/id_mappings/nyu_idset.csv"

    Each object will have the custom property "is_scene_net_obj".

    :param file_path: The path to the .obj file from SceneNet.
    :param texture_folder: The path to the texture folder used to sample the textures.
    :param unknown_texture_folder: The path to the textures, which are used if the the texture type is unknown. The default path does not
                                   exist if the dataset was just downloaded, it has to be created manually.
    :return: The list of loaded mesh objects.
    """
    if unknown_texture_folder is None:
        unknown_texture_folder = os.path.join(texture_folder, "unknown")

    # load the objects (Use use_image_search=False as some image names have a "/" prefix which will lead to blender search the whole root directory recursively!
    loaded_objects = load_obj(filepath=file_path, use_image_search=False)
    loaded_objects.sort(key=lambda ele: ele.get_name())
    # sample materials for each object
    SceneNetLoader._random_sample_materials_for_each_obj(loaded_objects, texture_folder, unknown_texture_folder)

    # set the category ids for each object
    SceneNetLoader._set_category_ids(loaded_objects, label_mapping)

    for obj in loaded_objects:
        obj.set_cp("is_scene_net_obj", True)

    return loaded_objects
Exemple #4
0
def load_shapenet(data_path: str,
                  used_synset_id: str,
                  used_source_id: str = "",
                  move_object_origin: bool = True) -> MeshObject:
    """ This loads an object from ShapeNet based on the given synset_id, which specifies the category of objects to use.

    From these objects one is randomly sampled and loaded.

    Todo: not good:
    Note: if this module is used with another loader that loads objects with semantic mapping, make sure the other module is loaded first in the config file.

    :param data_path: The path to the ShapeNetCore.v2 folder.
    :param used_synset_id: The synset id for example: '02691156', check the data_path folder for more ids.
    :param used_source_id: Object identifier of the a particular ShapeNet category, see inside any ShapeNet category for identifiers
    :param move_object_origin: Moves the object center to the bottom of the bounding box in Z direction and also in the middle of the X and Y plane, this does not change the `.location` of the object. Default: True
    :return: The loaded mesh object.
    """
    data_path = resolve_path(data_path)
    taxonomy_file_path = os.path.join(data_path, "taxonomy.json")

    files_with_fitting_synset = ShapeNetLoader._get_files_with_synset(
        used_synset_id, used_source_id, taxonomy_file_path, data_path)
    selected_obj = random.choice(files_with_fitting_synset)
    loaded_objects = load_obj(selected_obj)

    # In shapenet every .obj file only contains one object, make sure that is the case
    if len(loaded_objects) != 1:
        raise Exception(
            "The ShapeNetLoader expects every .obj file to contain exactly one object, however the file "
            + selected_obj + " contained " + str(len(loaded_objects)) +
            " objects.")
    obj = loaded_objects[0]

    obj.set_cp("used_synset_id", used_synset_id)
    obj.set_cp("used_source_id", pathlib.PurePath(selected_obj).parts[-3])

    ShapeNetLoader._correct_materials(obj)

    # removes the x axis rotation found in all ShapeNet objects, this is caused by importing .obj files
    # the object has the same pose as before, just that the rotation_euler is now [0, 0, 0]
    obj.persist_transformation_into_mesh(location=False,
                                         rotation=True,
                                         scale=False)

    # check if the move_to_world_origin flag is set
    if move_object_origin:
        # move the origin of the object to the world origin and on top of the X-Y plane
        # makes it easier to place them later on, this does not change the `.location`
        obj.move_origin_to_bottom_mean_point()
    bpy.ops.object.select_all(action='DESELECT')

    return obj
Exemple #5
0
def load_replica(data_path: str,
                 data_set_name: str,
                 use_smooth_shading: bool = False) -> List[MeshObject]:
    """ Just imports the configured .ply file straight into blender for the replica case.

    :param data_path: The path to the data folder, where all rooms are saved.
    :param data_set_name: Name of the room (for example: apartment_0).
    :param use_smooth_shading: Enable smooth shading on all surfaces, instead of flat shading.
    :return: The list of loaded mesh objects.
    """
    file_path = os.path.join(data_path, data_set_name, 'mesh.ply')
    loaded_objects = load_obj(file_path)

    if use_smooth_shading:
        for obj in loaded_objects:
            obj.set_shading_mode("SMOOTH")

    return loaded_objects
Exemple #6
0
    def _load_obj(path: str,
                  metadata: Dict[str, Union[str, int]],
                  material_adjustments: List[Dict[str, str]],
                  transform: Optional[Matrix] = None,
                  parent: Optional[Entity] = None) -> List[MeshObject]:
        """ Load the wavefront object file from the given path and adjust according to the given arguments.

        :param path: The path to the .obj file.
        :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 parent: The parent object to which the object should be linked
        :return: The list of loaded mesh objects.
        """
        if not os.path.exists(path):
            print("Warning: " + path + " is missing")
            return []
        else:
            object_already_loaded = path in SuncgLoader._collection_of_loaded_objs
            loaded_objects = load_obj(
                filepath=path,
                cached_objects=SuncgLoader._collection_of_loaded_objs)
            if object_already_loaded:
                print("Duplicate object: {}".format(path))
                for object in loaded_objects:
                    # the original object matrix from the .obj loader -> is not an identity matrix
                    object.set_local2world_mat(
                        Matrix([[1, 0, 0, 0], [0, 0, -1, 0], [0, 1, 0, 0],
                                [0, 0, 0, 1]]))
                    # remove all custom properties
                    object.clear_all_cps()
            # Go through all imported objects
            for object in loaded_objects:
                for key in metadata.keys():
                    object.set_cp(key, metadata[key])

                SuncgLoader._transform_and_colorize_object(
                    object, material_adjustments, transform, parent)

            return loaded_objects
def load_ikea(data_dir: str = 'resources/IKEA',
              obj_categories: Union[list, str] = None,
              obj_style: str = None) -> List[MeshObject]:
    """ Loads ikea objects based on selected type and style.

    If there are multiple options it picks one randomly or if style or type is None it picks one randomly.

    :param data_dir: The directory with all the IKEA models.
    :param obj_categories: The category to use for example: 'bookcase'. This can also be a list of elements. Available: ['bed', 'bookcase', 'chair', 'desk', 'sofa', 'table', 'wardrobe']
    :param obj_style: The IKEA style to use for example: 'hemnes'. See data_dir for other options.
    :return: The list of loaded mesh objects.
    """
    obj_dict = IKEALoader._generate_object_dict(data_dir)

    # If obj_categories is given, make sure it is a list
    if obj_categories is not None and not isinstance(obj_categories, list):
        obj_categories = [obj_categories]

    if obj_categories is not None and obj_style is not None:
        object_lst = []
        for obj_category in obj_categories:
            object_lst.extend([obj[0] for (key, obj) in obj_dict.items() \
                               if obj_style in key.lower() and obj_category in key])
        if not object_lst:
            selected_obj = random.choice(
                obj_dict.get(random.choice(list(obj_dict.keys()))))
            warnings.warn(
                "Could not find object of type: {}, and style: {}. Selecting random object..."
                .format(obj_categories, obj_style),
                category=Warning)
        else:
            # Multiple objects with same type and style are possible: select randomly from list.
            selected_obj = random.choice(object_lst)
    elif obj_categories is not None:
        object_lst = []
        for obj_category in obj_categories:
            object_lst.extend(
                IKEALoader._get_object_by_type(obj_category, obj_dict))
        selected_obj = random.choice(object_lst)
    elif obj_style is not None:
        object_lst = IKEALoader._get_object_by_style(obj_style, obj_dict)
        selected_obj = random.choice(object_lst)
    else:
        random_key = random.choice(list(obj_dict.keys()))
        # One key can have multiple object files as value: select randomly from list.
        selected_obj = random.choice(obj_dict.get(random_key))

    print("Selected object: ", os.path.basename(selected_obj))
    loaded_obj = load_obj(selected_obj)

    # extract the name from the path:
    selected_dir_name = os.path.dirname(selected_obj)
    selected_name = ""
    if os.path.basename(selected_dir_name).startswith("IKEA_"):
        selected_name = os.path.basename(selected_dir_name)
    else:
        selected_dir_name = os.path.dirname(selected_dir_name)
        if os.path.basename(selected_dir_name).startswith("IKEA_"):
            selected_name = os.path.basename(selected_dir_name)
    if selected_name:
        for obj in loaded_obj:
            obj.set_name(selected_name)

    # extract the file unit from the .obj file to convert every object to meters
    file_unit = ""
    with open(selected_obj, "r") as file:
        first_lines = [next(file) for x in range(5)]
        for line in first_lines:
            if "File units" in line:
                file_unit = line.strip().split(" ")[-1]
                if file_unit not in [
                        "inches", "meters", "centimeters", "millimeters"
                ]:
                    raise Exception(
                        "The file unit type could not be found, check the selected "
                        "file: {}".format(selected_obj))
                break

    for obj in loaded_obj:
        # convert all objects to meters
        if file_unit == "inches":
            scale = 0.0254
        elif file_unit == "centimeters":
            scale = 0.01
        elif file_unit == "millimeters":
            scale = 0.001
        elif file_unit == "meters":
            scale = 1.0
        else:
            raise Exception(
                "The file unit type: {} is not defined".format(file_unit))
        if scale != 1.0:
            # move all object centers to the world origin and set the bounding box correctly
            bpy.ops.object.select_all(action='DESELECT')
            obj.select()
            bpy.context.view_layer.objects.active = obj.blender_obj
            # scale object down
            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.transform.resize(value=(scale, scale, scale))
            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.select_all(action='DESELECT')

    # removes the x axis rotation found in all ShapeNet objects, this is caused by importing .obj files
    # the object has the same pose as before, just that the rotation_euler is now [0, 0, 0]
    for obj in loaded_obj:
        obj.persist_transformation_into_mesh(location=False,
                                             rotation=True,
                                             scale=False)

    # move the origin of the object to the world origin and on top of the X-Y plane
    # makes it easier to place them later on, this does not change the `.location`
    for obj in loaded_obj:
        obj.move_origin_to_bottom_mean_point()
    bpy.ops.object.select_all(action='DESELECT')
    return loaded_obj
    def _load_furniture_objs(
            data: dict, future_model_path: str, lamp_light_strength: float,
            label_mapping: LabelIdMapping) -> List[MeshObject]:
        """
        Load all furniture objects specified in the json file, these objects are stored as "raw_model.obj" in the
        3D_future_model_path. For lamp the lamp_light_strength value can be changed via the config.

        :param data: json data dir. Should contain "furniture"
        :param future_model_path: Path to the models used in the 3D-Front dataset.
        :param lamp_light_strength: Strength of the emission shader used in each lamp.
        :param label_mapping: A dict which maps the names of the objects to ids.
        :return: The list of loaded mesh objects.
        """
        # collect all loaded furniture objects
        all_objs = []
        # for each furniture element
        for ele in data["furniture"]:
            # create the paths based on the "jid"
            folder_path = os.path.join(future_model_path, ele["jid"])
            obj_file = os.path.join(folder_path, "raw_model.obj")
            # if the object exists load it -> a lot of object do not exist
            # we are unsure why this is -> we assume that not all objects have been made public
            if os.path.exists(
                    obj_file
            ) and not "7e101ef3-7722-4af8-90d5-7c562834fabd" in obj_file:
                # load all objects from this .obj file
                objs = load_obj(filepath=obj_file)
                # extract the name, which serves as category id
                used_obj_name = ""
                if "category" in ele:
                    used_obj_name = ele["category"]
                elif "title" in ele:
                    used_obj_name = ele["title"]
                    if "/" in used_obj_name:
                        used_obj_name = used_obj_name.split("/")[0]
                if used_obj_name == "":
                    used_obj_name = "others"
                for obj in objs:
                    obj.hide(True)
                    obj.set_name(used_obj_name)
                    # add some custom properties
                    obj.set_cp("uid", ele["uid"])
                    obj.set_cp("jid", ele["jid"])
                    # this custom property determines if the object was used before
                    # is needed to only clone the second appearance of this object
                    obj.set_cp("is_used", False)
                    obj.set_cp("is_3D_future", True)
                    obj.set_cp(
                        "type", "Non-Object"
                    )  # is an non object used for the interesting score
                    # set the category id based on the used obj name
                    obj.set_cp(
                        "category_id",
                        label_mapping.id_from_label(used_obj_name.lower()))
                    # walk over all materials
                    for mat in obj.get_materials():
                        if mat is None:
                            continue
                        principled_node = mat.get_nodes_with_type(
                            "BsdfPrincipled")
                        if "bed" in used_obj_name.lower(
                        ) or "sofa" in used_obj_name.lower():
                            if len(principled_node) == 1:
                                principled_node[0].inputs[
                                    "Roughness"].default_value = 0.5
                        is_lamp = "lamp" in used_obj_name.lower()
                        if len(principled_node) == 0 and is_lamp:
                            # this material has already been transformed
                            continue
                        elif len(principled_node) == 1:
                            principled_node = principled_node[0]
                        else:
                            raise Exception(
                                "The amount of principle nodes can not be more than 1, "
                                "for obj: {}!".format(obj.get_name()))

                        # Front3d .mtl files contain emission color which make the object mistakenly emissive
                        # => Reset the emission color
                        principled_node.inputs[
                            "Emission"].default_value[:3] = [0, 0, 0]

                        # For each a texture node
                        image_node = mat.new_node('ShaderNodeTexImage')
                        # and load the texture.png
                        base_image_path = os.path.join(folder_path,
                                                       "texture.png")
                        image_node.image = bpy.data.images.load(
                            base_image_path, check_existing=True)
                        mat.link(image_node.outputs['Color'],
                                 principled_node.inputs['Base Color'])
                        # if the object is a lamp, do the same as for the ceiling and add an emission shader
                        if is_lamp:
                            mat.make_emissive(lamp_light_strength)

                all_objs.extend(objs)
            elif "7e101ef3-7722-4af8-90d5-7c562834fabd" in obj_file:
                warnings.warn(
                    f"This file {obj_file} was skipped as it can not be read by blender."
                )
        return all_objs
def load_AMASS(data_path: str,
               sub_dataset_id: str,
               temp_dir: str = None,
               body_model_gender: str = None,
               subject_id: str = "",
               sequence_id: int = -1,
               frame_id: int = -1,
               num_betas: int = 10,
               num_dmpls: int = 8) -> List[MeshObject]:
    """
    use the pose parameters to generate the mesh and loads it to the scene.

    :param data_path: The path to the AMASS Dataset folder in resources folder.
    :param sub_dataset_id: Identifier for the sub dataset, the dataset which the human pose object should be extracted from.
                                Available: ['CMU', 'Transitions_mocap', 'MPI_Limits', 'SSM_synced', 'TotalCapture',
                                'Eyes_Japan_Dataset', 'MPI_mosh', 'MPI_HDM05', 'HumanEva', 'ACCAD', 'EKUT', 'SFU', 'KIT', 'H36M', 'TCD_handMocap', 'BML']
    :param temp_dir: A temp directory which is used for writing the temporary .obj file.
    :param body_model_gender: The model gender, pose will represented using male, female or neutral body shape.
                                   Available:[male, female, neutral]. If None is selected a random one is choosen.
    :param subject_id: Type of motion from which the pose should be extracted, this is dataset dependent parameter.
                            If left empty a random subject id is picked.
    :param sequence_id: Sequence id in the dataset, sequences are the motion recorded to represent certain action.
                             If set to -1 a random sequence id is selected.
    :param frame_id: Frame id in a selected motion sequence. If none is selected a random one is picked
    :param num_betas: Number of body parameters
    :param num_dmpls: Number of DMPL parameters
    :return: The list of loaded mesh objects.
    """
    if body_model_gender is None:
        body_model_gender = random.choice(["male", "female", "neutral"])
    if temp_dir is None:
        temp_dir = Utility.get_temporary_directory()

    # Install required additonal packages
    SetupUtility.setup_pip([
        "git+https://github.com/abahnasy/smplx",
        "git+https://github.com/abahnasy/human_body_prior"
    ])

    # Get the currently supported mocap datasets by this loader
    taxonomy_file_path = os.path.join(data_path, "taxonomy.json")
    supported_mocap_datasets = AMASSLoader._get_supported_mocap_datasets(
        taxonomy_file_path, data_path)

    # selected_obj = self._files_with_fitting_ids
    pose_body, betas = AMASSLoader._get_pose_parameters(
        supported_mocap_datasets, num_betas, sub_dataset_id, subject_id,
        sequence_id, frame_id)
    # load parametric Model
    body_model, faces = AMASSLoader._load_parametric_body_model(
        data_path, body_model_gender, num_betas, num_dmpls)
    # Generate Body representations using SMPL model
    body_repr = body_model(pose_body=pose_body, betas=betas)
    # Generate .obj file represents the selected pose
    generated_obj = AMASSLoader._write_body_mesh_to_obj_file(
        body_repr, faces, temp_dir)

    loaded_obj = load_obj(generated_obj)

    AMASSLoader._correct_materials(loaded_obj)

    # set the shading mode explicitly to smooth
    for obj in loaded_obj:
        obj.set_shading_mode("SMOOTH")

    # removes the x axis rotation found in all ShapeNet objects, this is caused by importing .obj files
    # the object has the same pose as before, just that the rotation_euler is now [0, 0, 0]
    for obj in loaded_obj:
        obj.persist_transformation_into_mesh(location=False,
                                             rotation=True,
                                             scale=False)

    # move the origin of the object to the world origin and on top of the X-Y plane
    # makes it easier to place them later on, this does not change the `.location`
    for obj in loaded_obj:
        obj.move_origin_to_bottom_mean_point()
    bpy.ops.object.select_all(action='DESELECT')

    return loaded_obj