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
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
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)
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)
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)
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
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