def _infuse_material(material: Material, config: Config): """ Infuse a material inside of another material. The given material, will be adapted and the used material, will be added, depending on the mode either as add or as mix. This change is applied to all outputs of the material, this include the Surface (Color) and also the displacement and volume. For displacement mix means multiply. :param material: Used material :param config: Used config """ # determine the mode used_mode = config.get_string("mode", "mix") mix_strength = 0.0 if used_mode == "mix": mix_strength = config.get_float("mix_strength", 0.5) elif used_mode == "add" and config.has_param("mix_strength"): raise Exception( "The mix_strength only works in the mix mode not in the add mode!" ) # get the material, which will be used to infuse the given material used_materials = config.get_list("used_material") used_material = random.choice(used_materials) material.infuse_material(material=Material(used_material), mode=used_mode, mix_strength=mix_strength)
def run(self): """ Returns the result of processing of the list of values. """ transform_by = self.config.get_string("transform_by") elements = self.config.get_list("elements") raw_result = [] for element in elements: element_conf = Config({"element": element}) if isinstance(element, list): raw_result.append(element_conf.get_vector3d("element")) else: raw_result.append(element_conf.get_raw_value("element")) if len(raw_result) > 0: if self._check_compatibility(raw_result): if transform_by == "sum": ref_result = self._sum(raw_result) elif transform_by == "avg": ref_result = self._avg(raw_result) else: raise RuntimeError("Unknown 'transform_by' operation: " + transform_by) else: raise RuntimeError( "Provider output types don't match. All must either int, float, or mathutils.Vector." ) else: raise RuntimeError( "List of resulting values of `elements` is empty. Please, check Provider configuration." ) return ref_result
def run(self): """ Extracts floors in the following steps: 1. Searchs for the specified object. 2. Splits the surfaces which point upwards at a specified level away. """ mesh_objects = [] for obj in self.config.get_list("selector"): if obj.type != "MESH": warnings.warn( "The object: {} is not a mesh but was selected in the FloorExtractor!" .format(obj.name)) continue mesh_objects.append(MeshObject(obj)) floors = extract_floor( mesh_objects=mesh_objects, compare_angle_degrees=radians( self.config.get_float('compare_angle_degrees', 7.5)), compare_height=self.config.get_float('compare_height', 0.15), new_name_for_object=self.config.get_string("name_for_split_obj", "Floor"), should_skip_if_object_is_already_there=self.config.get_bool( "should_skip_if_object_is_already_there", False)) add_properties = self.config.get_raw_dict("add_properties", {}) if add_properties: config = Config({"add_properties": add_properties}) loader_interface = LoaderInterface(config) loader_interface._set_properties(floors)
def run(self): # Set intrinsics self._set_cam_intrinsics(bpy.context.scene.camera.data, Config(self.config.get_raw_dict("intrinsics", {}))) self.cam_pose_collection.add_items_from_dicts(self.config.get_list("cam_poses", [])) self.cam_pose_collection.add_items_from_file(self.config.get_string("path", ""), self.config.get_string("file_format", ""), self.number_of_arguments_per_parameter)
def _infuse_texture(material: Material, config: Config): """ Overlays the selected material with a texture, this can be either a color texture like for example dirt or it can be a texture, which is used as an input to the Principled BSDF of the given material. :param material: Material, which will be changed :param config: containing the config information """ used_mode = config.get_string("mode", "overlay") used_textures = config.get_list("used_texture") invert_texture = config.get_bool("invert_texture", False) used_connector = config.get_string("connection", "Base Color") texture_scale = config.get_float("texture_scale", 0.05) strength = config.get_float("strength", 0.5) if config.has_param("strength") and used_mode == "set": raise Exception( "The strength can only be used if the mode is not \"set\"!") if len(used_textures) == 0: raise Exception( f"You have to select a texture, which is {used_mode} over the material!" ) texture = random.choice(used_textures) material.infuse_texture(texture=texture, mode=used_mode, invert_texture=invert_texture, connection=used_connector, texture_scale=texture_scale, strength=strength)
def _get_random_texture(self, textures): """ Chooses a random texture data from the provided list. :param textures: Texture data. Type: list. :return: Selected texture data. Type: Config. """ selected_dict = choice(textures) selected_texture = Config(selected_dict) return selected_texture
def _add_dust_to_material(self, material: Material, value: dict): """ Adds a dust film to the material, where the strength determines how much dust is used. This will be added right before the output of the material. :param material: Used material :param value: dict with all used keys """ # extract values from the config, like strength, texture_scale and used_dust_texture config = Config(value) strength = config.get_float("strength") texture_scale = config.get_float("texture_scale", 0.1) # if no texture is used, a random noise texture is generated texture_nodes = config.get_list("used_dust_texture", None) Dust.add_dust(material=material, strength=strength, texture_nodes=texture_nodes, texture_scale=texture_scale)
def run(self): """ Adds specified basic mesh objects to the scene and sets at least their names to the user-defined ones. 1. Get configuration parameters' values. 2. Add an object. 3. Set attribute values. 4. Initialize a material, if needed. """ meshes_to_add = self.config.get_list("meshes_to_add") init_objs_mats = self.config.get_bool("init_materials", True) for mesh in meshes_to_add: mesh_conf = Config(mesh) obj_type = mesh_conf.get_string("type") obj_name = mesh_conf.get_string("name") obj_location = mesh_conf.get_vector3d("location", [0, 0, 0]) obj_rotation = mesh_conf.get_vector3d("rotation", [0, 0, 0]) obj_scale = mesh_conf.get_vector3d("scale", [1, 1, 1]) new_obj = self._add_obj(obj_type) self._set_attrs(new_obj, obj_name, obj_location, obj_rotation, obj_scale) if init_objs_mats: self._init_material(obj_name)
def __init__(self, config): Module.__init__(self, config) # Clean up example scene or scene created by last run when debugging pipeline inside blender cleanup() # setting up the GlobalStorage global_config = Config(self.config.get_raw_dict("global", {})) GlobalStorage.init_global(global_config) # call the init again to make sure all values from the global config where read correctly, too self._default_init()
def add_item(self, parameters): """ Adds a new item based on the given parameters. :param parameters: A dict specifying the parameters. """ # Start with the default parameters data = deepcopy(self.default_item_parameters) # Overwrite default parameter values with specific parameters for this item data = Utility.merge_dicts(parameters, data) # Create config object config = Config(data) # Call function to add new item self.add_item_func(config)
def run(self): """ Constructs a ground plane. 1. Get configuration parameters. 2. Load shader. 3. Construct ground plane and it's material node tree. """ tiles = self.config.get_list("tiles") for tile in tiles: if tile: ground_config = Config(tile) self._load_shader(ground_config) self._construct_ground_plane(ground_config)
def _cam2world_matrix_from_cam_extrinsics(self, config: Config) -> np.ndarray: """ Determines camera extrinsics by using the given config and returns them in form of a cam to world frame transformation matrix. :param config: The configuration object. :return: The 4x4 cam to world transformation matrix. """ if not config.has_param("cam2world_matrix"): # Print warning if local_frame_change is used with other attributes than cam2world_matrix if self.local_frame_change != ["X", "Y", "Z"]: print( "Warning: The local_frame_change parameter is at the moment only supported when setting the cam2world_matrix attribute." ) position = change_coordinate_frame_of_point( config.get_vector3d("location", [0, 0, 0]), self.world_frame_change) # Rotation rotation_format = config.get_string("rotation/format", "euler") value = config.get_vector3d("rotation/value", [0, 0, 0]) # Transform to blender coord frame value = change_coordinate_frame_of_point(value, self.world_frame_change) if rotation_format == "euler": # Rotation, specified as euler angles rotation_matrix = Euler(value, 'XYZ').to_matrix() elif rotation_format == "forward_vec": # Convert forward vector to euler angle (Assume Up = Z) rotation_matrix = CameraUtility.rotation_from_forward_vec( value) elif rotation_format == "look_at": # Convert forward vector to euler angle (Assume Up = Z) rotation_matrix = CameraUtility.rotation_from_forward_vec( value - position) else: raise Exception("No such rotation format:" + str(rotation_format)) if rotation_format == "look_at" or rotation_format == "forward_vec": inplane_rot = config.get_float("rotation/inplane_rot", 0.0) rotation_matrix = np.matmul( rotation_matrix, Euler((0.0, 0.0, inplane_rot)).to_matrix()) cam2world_matrix = build_transformation_mat( position, rotation_matrix) else: cam2world_matrix = np.array( config.get_list("cam2world_matrix")).reshape(4, 4).astype( np.float32) cam2world_matrix = change_source_coordinate_frame_of_transformation_matrix( cam2world_matrix, self.local_frame_change) cam2world_matrix = change_target_coordinate_frame_of_transformation_matrix( cam2world_matrix, self.world_frame_change) return cam2world_matrix
def run(self): """ Adds specified basic empty objects to the scene and sets at least their names to the user-defined ones. 1. Get configuration parameters' values. 2. Add an object. 3. Set attribute values. """ empties_to_add = self.config.get_list("empties_to_add") for empty in empties_to_add: empty_conf = Config(empty) obj_name = empty_conf.get_string("name") obj_type = empty_conf.get_string("type", "plain_axes") entity = create_empty(obj_name, obj_type) entity.set_location(empty_conf.get_vector3d("location", [0, 0, 0])) entity.set_rotation_euler( empty_conf.get_vector3d("rotation", [0, 0, 0])) entity.set_scale(empty_conf.get_vector3d("scale", [1, 1, 1]))
def _get_the_set_params(self, params_conf: Config): """ Extracts actual values to set from a Config object. :param params_conf: Object with all user-defined data. Type: Config. :return: Parameters to set as {name of the parameter: it's value} pairs. Type: dict. """ params = {} for key in params_conf.data.keys(): if key == "cf_add_modifier": modifier_config = Config(params_conf.get_raw_dict(key)) # instruction about unpacking the data: key, corresponding Config method to extract the value, # it's default value and a postproc function instructions = { "name": (Config.get_string, None, str.upper), "thickness": (Config.get_float, None, None) } # unpack result = self._unpack_params(modifier_config, instructions) elif key == "cf_set_shading": result = { "shading_mode": params_conf.get_string("cf_set_shading"), "angle_value": params_conf.get_float( "cf_shading_auto_smooth_angle_in_deg", 30) } elif key == "cf_add_displace_modifier_with_texture": displace_config = Config(params_conf.get_raw_dict(key)) # instruction about unpacking the data: key, corresponding Config method to extract the value, # it's default value and a postproc function instructions = { "texture": (Config.get_raw_value, [], None), "mid_level": (Config.get_float, 0.5, None), "subdiv_level": (Config.get_int, 2, None), "strength": (Config.get_float, 0.1, None), "min_vertices_for_subdiv": (Config.get_int, 10000, None) } # unpack result = self._unpack_params(displace_config, instructions) elif key == "cf_add_uv_mapping": uv_config = Config(params_conf.get_raw_dict(key)) # instruction about unpacking the data: key, corresponding Config method to extract the value, # it's default value and a postproc function instructions = { "projection": (Config.get_string, None, str.lower), "forced_recalc_of_uv_maps": (Config.get_bool, False, None) } # unpack result = self._unpack_params(uv_config, instructions) elif key == "cf_randomize_materials": rand_config = Config(params_conf.get_raw_dict(key)) # instruction about unpacking the data: key, corresponding Config method to extract the value, # it's default value and a postproc function instructions = { "randomization_level": (Config.get_float, 0.2, None), "add_to_objects_without_material": (Config.get_bool, False, None), "materials_to_replace_with": (Config.get_list, BlenderUtility.get_all_materials(), None), "obj_materials_cond_to_be_replaced": (Config.get_raw_dict, {}, None) } result = self._unpack_params(rand_config, instructions) result["material_to_replace_with"] = choice( result["materials_to_replace_with"]) else: result = params_conf.get_raw_value(key) params.update({key: result}) return params
def run(self): """ Sets according values of defined attributes/custom properties or applies custom functions to the selected entities. """ # separating defined part with the selector from ambiguous part with attribute names and their values to set set_params = {} sel_objs = {} for key in self.config.data.keys(): if key != 'selector' and key != "mode": # if its not a selector -> to the set parameters dict set_params[key] = self.config.data[key] else: sel_objs[key] = self.config.data[key] # create Config objects params_conf = Config(set_params) sel_conf = Config(sel_objs) # invoke a Getter, get a list of entities to manipulate entities = sel_conf.get_list("selector") op_mode = self.config.get_string("mode", "once_for_each") if not entities: warnings.warn( "Warning: No entities are selected. Check Providers conditions." ) return else: print("Amount of objects to modify: {}.".format(len(entities))) # get raw value from the set parameters if it is to be sampled once for all selected entities if op_mode == "once_for_all": params = self._get_the_set_params(params_conf) for entity in entities: # get raw value from the set parameters if it is to be sampled anew for each selected entity if op_mode == "once_for_each": params = self._get_the_set_params(params_conf) for key, value in params.items(): # used so we don't modify original key when having more than one entity key_copy = key # check if the key is a requested custom property requested_cp = False if key.startswith('cp_'): requested_cp = True key_copy = key[3:] requested_cf = False if key.startswith('cf_'): requested_cf = True key_copy = key[3:] # as an attribute of this value if hasattr(entity, key_copy) and not requested_cp: # Some properties like matrix_world would interpret numpy arrays / lists as column-wise matrices. # To make sure matrices are always interpreted row-wise, we first convert them to a mathutils matrix. if isinstance(getattr(entity, key_copy), Matrix): value = Matrix(value) setattr(entity, key_copy, value) # custom functions elif key_copy == "add_modifier" and requested_cf: self._add_modifier(entity, value) elif key_copy == "set_shading" and requested_cf: self._set_shading(entity, value) elif key_copy == "add_displace_modifier_with_texture" and requested_cf: self._add_displace(entity, value) elif key_copy == "add_uv_mapping" and requested_cf: self._add_uv_mapping(entity, value) elif key_copy == "randomize_materials" and requested_cf: self._randomize_materials(entity, value) # if key had a cp_ prefix - treat it as a custom property # values will be overwritten for existing custom property, # but if the name is new then new custom property will be created elif requested_cp: entity[key_copy] = value
def run(self): """ Sets according values of defined attributes or applies custom functions to the selected materials. 1. Select materials. 2. For each parameter to modify, set it's value to all selected objects. """ set_params = {} sel_objs = {} for key in self.config.data.keys(): # if its not a selector -> to the set parameters dict if key != 'selector': set_params[key] = self.config.data[key] else: sel_objs[key] = self.config.data[key] # create Config objects params_conf = Config(set_params) sel_conf = Config(sel_objs) # invoke a Getter, get a list of entities to manipulate materials = sel_conf.get_list("selector") materials = MaterialLoaderUtility.convert_to_materials(materials) op_mode = self.config.get_string("mode", "once_for_each") if not materials: warnings.warn( "Warning: No materials selected inside of the MaterialManipulator" ) return if op_mode == "once_for_all": # get values to set if they are to be set/sampled once for all selected materials params = self._get_the_set_params(params_conf) for material in materials: if not material.blender_obj.use_nodes: raise Exception( "This material does not use nodes -> not supported here.") if op_mode == "once_for_each": # get values to set if they are to be set/sampled anew for each selected entity params = self._get_the_set_params(params_conf) for key, value in params.items(): # used so we don't modify original key when having more than one material key_copy = key requested_cf = False if key.startswith('cf_'): requested_cf = True key_copy = key[3:] # if an attribute with such name exists for this entity if key_copy == "color_link_to_displacement" and requested_cf: MaterialManipulator._link_color_to_displacement_for_mat( material, value) elif key_copy == "change_to_vertex_color" and requested_cf: MaterialManipulator._map_vertex_color(material, value, active_shading=True) elif key_copy == "change_to_vertex_color_no_shading" and requested_cf: MaterialManipulator._map_vertex_color(material, value, active_shading=False) elif key_copy == "textures" and requested_cf: loaded_textures = self._load_textures(value) self._set_textures(loaded_textures, material) elif key_copy == "switch_to_emission_shader" and requested_cf: self._switch_to_emission_shader(material, value) elif key_copy == "infuse_texture" and requested_cf: MaterialManipulator._infuse_texture(material, value) elif key_copy == "infuse_material" and requested_cf: MaterialManipulator._infuse_material(material, value) elif key_copy == "add_dust" and requested_cf: self._add_dust_to_material(material, value) elif "set_" in key_copy and requested_cf: # sets the value of the principled shader self._op_principled_shader_value(material, key_copy[len("set_"):], value, "set") elif "add_" in key_copy and requested_cf: # sets the value of the principled shader self._op_principled_shader_value(material, key_copy[len("add_"):], value, "add") elif hasattr(material, key_copy): # set the value setattr(material, key_copy, value)
def _get_the_set_params(self, params_conf: Config) -> dict: """ Extracts actual values to set from a Config object. :param params_conf: Object with all user-defined data. :return: Parameters to set as {name of the parameter: it's value} pairs. """ params = {} for key in params_conf.data.keys(): result = None if key == "cf_color_link_to_displacement": result = params_conf.get_float(key) elif key == "cf_change_to_vertex_color": result = params_conf.get_string(key) elif key == "cf_textures": result = {} paths_conf = Config(params_conf.get_raw_dict(key)) for text_key in paths_conf.data.keys(): text_path = paths_conf.get_string(text_key) result.update({text_key: text_path}) elif key == "cf_switch_to_emission_shader": result = {} emission_conf = Config(params_conf.get_raw_dict(key)) for emission_key in emission_conf.data.keys(): if emission_key == "color": attr_val = emission_conf.get_list( "color", [1, 1, 1, 1]) elif emission_key == "strength": attr_val = emission_conf.get_float("strength", 1.0) result.update({emission_key: attr_val}) elif key == "cf_infuse_texture": result = Config(params_conf.get_raw_dict(key)) elif key == "cf_infuse_material": result = Config(params_conf.get_raw_dict(key)) elif key == "cf_add_dust": result = params_conf.get_raw_dict(key) elif "cf_set_" in key or "cf_add_" in key: result = params_conf.get_raw_value(key) else: result = params_conf.get_raw_value(key) params.update({key: result}) return params
def run(self): """ Loads rocks.""" rocks_settings = self.config.get_list("batches", []) for subsec_num, subsec_settings in enumerate(rocks_settings): subsec_config = Config(subsec_settings) subsec_objects = RockEssentialsRockLoader.load_rocks( path=subsec_config.get_string("path"), subsec_num=subsec_num, objects=subsec_config.get_list("objects", []), sample_objects=subsec_config.get_bool("sample_objects", False), amount=subsec_config.get_int("amount", None) ) RockEssentialsRockLoader.set_rocks_properties( objects=subsec_objects, physics=subsec_config.get_bool("physics", False), render_levels=subsec_config.get_int("render_levels", 3), high_detail_mode=subsec_config.get_bool("high_detail_mode", False), scale=subsec_config.get_vector3d("scale", [1, 1, 1]), reflection_amount=subsec_config.get_float("reflection_amount", None), reflection_roughness=subsec_config.get_float("reflection_roughness", None), hsv=subsec_config.get_list("HSV", None) )
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 = convert_to_meshes( config.get_list("check_if_pose_above_object_list", [])) self.check_visible_objects = 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 = 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")
def _set_cam_intrinsics(self, cam, config): """ Sets camera intrinsics from a source with following priority 1. from config function parameter if defined 2. from custom properties of cam if set in Loader 3. default config: resolution_x/y: 512 pixel_aspect_x: 1 clip_start: : 0.1 clip_end : 1000 fov : 0.691111 :param cam: The camera which contains only camera specific attributes. :param config: A configuration object with cam intrinsics. """ if config.is_empty(): return width = config.get_int("resolution_x", bpy.context.scene.render.resolution_x) height = config.get_int("resolution_y", bpy.context.scene.render.resolution_y) # Clipping clip_start = config.get_float("clip_start", cam.clip_start) clip_end = config.get_float("clip_end", cam.clip_end) if config.has_param("cam_K"): if config.has_param("fov"): print( 'WARNING: FOV defined in config is ignored. Mutually exclusive with cam_K' ) if config.has_param("pixel_aspect_x"): print( 'WARNING: pixel_aspect_x defined in config is ignored. Mutually exclusive with cam_K' ) cam_K = np.array(config.get_list("cam_K")).reshape(3, 3).astype( np.float32) CameraUtility.set_intrinsics_from_K_matrix(cam_K, width, height, clip_start, clip_end) else: # Set FOV fov = config.get_float("fov", cam.angle) # Set Pixel Aspect Ratio pixel_aspect_x = config.get_float( "pixel_aspect_x", bpy.context.scene.render.pixel_aspect_x) pixel_aspect_y = config.get_float( "pixel_aspect_y", bpy.context.scene.render.pixel_aspect_y) # Set camera shift shift_x = config.get_float("shift_x", cam.shift_x) shift_y = config.get_float("shift_y", cam.shift_y) CameraUtility.set_intrinsics_from_blender_params(fov, width, height, clip_start, clip_end, pixel_aspect_x, pixel_aspect_y, shift_x, shift_y, lens_unit="FOV") CameraUtility.set_stereo_parameters( config.get_string("stereo_convergence_mode", cam.stereo.convergence_mode), config.get_float("convergence_distance", cam.stereo.convergence_distance), config.get_float("interocular_distance", cam.stereo.interocular_distance)) if config.has_param("depth_of_field"): depth_of_field_config = Config( config.get_raw_dict("depth_of_field")) fstop_value = depth_of_field_config.get_float("fstop", 2.4) aperture_blades = depth_of_field_config.get_int( "aperture_blades", 0) aperture_ratio = depth_of_field_config.get_float( "aperture_ratio", 1.0) aperture_rotation = depth_of_field_config.get_float( "aperture_rotation_in_rad", 0.0) if depth_of_field_config.has_param( "depth_of_field_dist") and depth_of_field_config.has_param( "focal_object"): raise RuntimeError( "You can only use either depth_of_field_dist or a focal_object but not both!" ) if depth_of_field_config.has_param("depth_of_field_dist"): depth_of_field_dist = depth_of_field_config.get_float( "depth_of_field_dist") CameraUtility.add_depth_of_field(cam, None, fstop_value, aperture_blades, aperture_rotation, aperture_ratio, depth_of_field_dist) elif depth_of_field_config.has_param("focal_object"): focal_object = depth_of_field_config.get_list("focal_object") if len(focal_object) != 1: raise RuntimeError( f"There has to be exactly one focal object, use 'random_samples: 1' or change " f"the selector. Found {len(focal_object)}.") CameraUtility.add_depth_of_field(Entity(focal_object[0]), fstop_value, aperture_blades, aperture_rotation, aperture_ratio) else: raise RuntimeError( "The depth_of_field dict must contain either a focal_object definition or " "a depth_of_field_dist")