Example #1
0
    def set_camera_location(self, name, location):
        """
        Set locations for selected cameras

        Args:
            name(str): camera name
            location(array-like): camera location
        """
        # select camera
        blnd.select_object(name)
        # set pose
        bpy.data.objects[name].location = location
Example #2
0
 def set_camera_location(self, cam_name: str, location):
     """
     Set location of selected camera
     
     Args:
         cam_name(str): actual name of selected bpy camera object
         location(array): camera location
     """
     # select the camera. Blender often operates on the active object, to
     # make sure that this happens here, we select it
     blnd.select_object(cam_name)
     # set camera location
     bpy.data.objects[cam_name].location = location
Example #3
0
    def setup_cameras(self):
        """Set up all cameras.

        Note that this does not select a camera for which to render. This will
        be selected elsewhere.
        """
        scene = bpy.context.scene
        for cam in self.config.scene_setup.cameras:
            # first get the camera name. This depends on the scene (blend file)
            cam_name = self.get_camera_name(cam)
            # select the camera. Blender often operates on the active object, to
            # make sure that this happens here, we select it
            blnd.select_object(cam_name)
            # modify camera according to the intrinsics
            blender_camera = bpy.data.objects[cam_name].data
            # set the calibration matrix
            camera_utils.set_camera_info(scene, blender_camera, self.config.camera_info)
Example #4
0
def _save_camera_locations_to_blend(name: str, locations: list, filepath: str):
    """Save a given list of camera locations to blend.

    Args:
        name(str): camera name as in the prescribed blender file
        locations(list): list of camera locations
        filepath(str): path where .blend file is saved
    """
    if name is None or locations is None:
        logger.warn(
            'Either given camera name or locations are None. To dump both must be given. Skipping'
        )
        return

    # create and link temporary collection
    tmp_cam_coll = bpy.data.collections.new('TemporaryCameras')
    bpy.context.scene.collection.children.link(tmp_cam_coll)

    tmp_cameras = []
    for location in locations:
        blnd.select_object(name)
        bpy.ops.object.duplicate()
        # TODO: remove from original collection to avoid name clutter in .blend
        tmp_cam_obj = bpy.context.object
        tmp_cam_obj.location = location
        tmp_cam_coll.objects.link(tmp_cam_obj)
        tmp_cameras.append(tmp_cam_obj)
    bpy.context.evaluated_depsgraph_get().update()

    logger.info(
        f"Saving camera locations to blender file {filepath} for debugging")
    bpy.ops.wm.save_as_mainfile(filepath=filepath)

    # clear objects and collection
    bpy.ops.object.select_all(action='DESELECT')
    for tmp_cam in tmp_cameras:
        bpy.data.objects.remove(tmp_cam)
    bpy.data.collections.remove(tmp_cam_coll)
Example #5
0
    def _setup_material(self):
        """Setup object material"""
        available_materials = {
            'metal': abr_nodes.material_metal_tool_cap,
            'plastic': abr_nodes.material_3Dprinted_plastic
        }

        material = self.config.scenario_setup.object_material.lower()
        if material not in available_materials:
            raise ValueError(f'Requested material "{material}" is not supported')

        # make sure cap is selected
        blnd.select_object(self.obj.name)

        # remove any material that's currently assigned to the object and then
        # setup the metal for the cap
        blnd.remove_material_nodes(self.obj)
        blnd.clear_orphaned_materials()

        # add default material and setup nodes (without specifying empty, to get
        # it created automatically)
        self.obj_mat = blnd.add_default_material(self.obj)
        available_materials[material].setup_material(self.obj_mat)
Example #6
0
    def setup_objects(self,
                      objects: list,
                      bpy_collection: str = 'TargetObjects'):
        """This method populates the scene with objects.

        Object types and number of objects will be taken from the configuration.
        The format to specify objects is
            ObjectType:Number
        where ObjectType should be the name of an object that exists in the
        blender file, and number indicates how often the object shall be
        duplicated.
        
        Args:
            objects(list): list of ObjectType:Number to setup
            bpy_collection(str): Name of bpy collection the given objects are
                linked to in the .blend file. Default: TargetObjects
                If the given objects are non-target (i.e., they populate the scene but
                no information regarding them are stored) use a different collection.

        Returns:
            objs(list): list of dict to handle desired objects
        """
        # let's start with an empty list
        objs = []
        obk = ObjectBookkeeper()

        # first reset the render pass index for all panda model objects (links,
        # hand, etc)
        links = [f'Link-{i}'
                 for i in range(8)] + ['Finger-Left', 'Finger-Right', 'Hand']
        for link in links:
            bpy.data.objects[link].pass_index = 0

        # extract all objects from the configuration. An object has a certain
        # type, as well as an own id. this information is storeed in the objs
        # list, which contains a dict. The dict contains the following keys:
        #       id_mask             used for mask computation, computed below
        #       object_class_name   type-name of the object
        #       object_class_id     model type ID (simply incremental numbers)
        #       object_id   instance ID of the object
        #       bpy         blender object reference
        for class_id, obj_spec in enumerate(objects):
            if obj_spec is None or obj_spec == '':
                return

            class_name, obj_count = obj_spec.split(':')

            # here we distinguish if we copy a part from the proto objects
            # within a scene, or if we have to load it from file
            is_proto_object = not class_name.startswith('parts.')
            if not is_proto_object:
                # split off the prefix for all files that we load from blender
                class_name = class_name[6:]

            # TODO: file loading happens only very late in this loop. This might
            #       be an issue for large object counts and could be changed to
            #       load-once copy-often.
            for j in range(int(obj_count)):
                # First, deselect everything
                bpy.ops.object.select_all(action='DESELECT')
                if is_proto_object:
                    # duplicate proto-object
                    blnd.select_object(class_name)
                    bpy.ops.object.duplicate()
                    new_obj = bpy.context.object
                else:
                    # we need to load this object from file. This could be
                    # either a blender file, or a PLY file
                    blendfile = expandpath(self.config.parts[class_name],
                                           check_file=False)
                    if os.path.exists(blendfile):
                        # this is a blender file, so we should load it
                        # we can now load the object into blender
                        # try-except logic to handle objects from same blend file but different
                        # class names to allow loading same objects with e.g., different scales
                        try:
                            bpy_obj_name = self.config.parts['name'][
                                class_name]
                        except KeyError:
                            bpy_obj_name = class_name
                        blnd.append_object(blendfile, bpy_obj_name)
                        # NOTE: bpy.context.object is **not** the object that we are
                        # interested in here! We need to select it via original name
                        # first, then we rename it to be able to select additional
                        # objects later on
                        new_obj = bpy.data.objects[bpy_obj_name]
                        new_obj.name = f'{class_name}.{j:03d}'
                        # try to rescale object according to its blend_scale if given in the config
                        try:
                            new_obj.scale = Vector(
                                self.config.parts.blend_scale[class_name])
                            bpy.ops.object.transform_apply(location=False,
                                                           rotation=False,
                                                           scale=True,
                                                           properties=False)
                        except KeyError:
                            # log and keep going
                            self.logger.info(
                                f'No blend_scale for obj {class_name} given. Skipping!'
                            )
                    else:
                        # no blender file given, so we will load the PLY file
                        # NOTE: no try-except logic for ply since we are not binded to object names as for .blend
                        ply_path = expandpath(
                            self.config.parts.ply[class_name], check_file=True)
                        bpy.ops.import_mesh.ply(filepath=ply_path)
                        # here we can use bpy.context.object!
                        new_obj = bpy.context.object
                        new_obj.name = f'{class_name}.{j:03d}'
                        # try to rescale object according to its ply_scale if given in the config
                        try:
                            new_obj.scale = Vector(
                                self.config.parts.ply_scale[class_name])
                            bpy.ops.object.transform_apply(location=False,
                                                           rotation=False,
                                                           scale=True,
                                                           properties=False)
                        except KeyError:
                            # log and keep going
                            self.logger.info(
                                f'No ply_scale for obj {class_name} given. Skipping!'
                            )

                # move object to collection: in case of debugging
                try:
                    collection = bpy.data.collections[bpy_collection]
                except KeyError:
                    collection = bpy.data.collections.new(bpy_collection)
                    bpy.context.scene.collection.children.link(collection)

                if new_obj.name not in collection.objects:
                    collection.objects.link(new_obj)

                # bookkeep instance
                obk.add(class_name)

                # append all information
                objs.append({
                    'id_mask': '',
                    'object_class_name': class_name,
                    'object_class_id': class_id,
                    'object_id': j,
                    'bpy': new_obj,
                    'visible': None
                })

        # build masks id for compositor of the format _N_M, where N is the model
        # id, and M is the object id
        w_class = ceil(log(
            len(obk),
            10)) if len(obk) else 0  # format width for number of model types
        for i, obj in enumerate(objs):
            w_obj = ceil(log(obk[obj['object_class_name']]['instances'],
                             10))  # format width for objs with same model
            id_mask = f"_{obj['object_class_id']:0{w_class}}_{obj['object_id']:0{w_obj}}"
            obj['id_mask'] = id_mask

        return objs
Example #7
0
    def setup_objects(self, objects: list):
        """This method retrieves objects info from the loaded bledner file.

        Being a static scene, it is assumed objects are already loaded in the blender file.

        Object types and number of objects will be taken from the configuration.
        The format to specify objects is
            ObjectType:Number
        where ObjectType should be the name of an object that exists in the
        blender file, and number indicates how often the object shall be
        duplicated.
        
        Args:
            objects(list): list of ObjectType:Number to setup

        Returns:
            objs(list): list of dict to handle desired objects
        """
        # let's start with an empty list
        objs = []
        obk = ObjectBookkeeper()

        # extract all objects from the configuration. An object has a certain
        # type, as well as an own id. this information is storeed in the objs
        # list, which contains a dict. The dict contains the following keys:
        #       id_mask             used for mask computation, computed below
        #       object_class_name   type-name of the object
        #       object_class_id     model type ID (simply incremental numbers)
        #       object_id   instance ID of the object
        #       bpy         blender object reference
        for class_id, obj_spec in enumerate(objects):
            if obj_spec is None or obj_spec == '':
                return

            class_name, obj_count = obj_spec.split(':')

            if class_name.startswith('parts.'):
                # split off the prefix for all files that we load from blender
                class_name = class_name[6:]

            # go over the object instances
            for j in range(int(obj_count)):
                # First, deselect everything
                bpy.ops.object.select_all(action='DESELECT')

                # retrieve object name. We assume object instances follow the standard convention
                # class_name.xxx where xxx is an increasing number starting at 000.
                bpy_obj_name = f'{class_name}.{j:03d}'
                blnd.select_object(bpy_obj_name)
                new_obj = bpy.context.object

                # bookkeep instance
                obk.add(class_name)

                # append all information
                objs.append({
                    'id_mask': '',
                    'object_class_name': class_name,
                    'object_class_id': class_id,
                    'object_id': j,
                    'bpy': new_obj,
                    'visible': None
                })

        # build masks id for compositor of the format _N_M, where N is the model
        # id, and M is the object id
        w_class = ceil(log(
            len(obk),
            10)) if len(obk) else 0  # format width for number of model types
        for i, obj in enumerate(objs):
            w_obj = ceil(log(obk[obj['object_class_name']]['instances'],
                             10))  # format width for objs with same model
            id_mask = f"_{obj['object_class_id']:0{w_class}}_{obj['object_id']:0{w_obj}}"
            obj['id_mask'] = id_mask

        return objs