def _adjust_material_nodes(mat: Material, adjustments: dict): """ Adjust the material node of the given material according to the given adjustments. Textures or diffuse colors will be changed according to the given material_adjustments. :param mat: The blender material. :param adjustments: A dict containing a new "diffuse" color or a new "texture" path """ if "diffuse" in adjustments: principle_node = mat.get_the_one_node_with_type("BsdfPrincipled") principle_node.inputs[ 'Base Color'].default_value = Utility.hex_to_rgba( adjustments["diffuse"]) if "texture" in adjustments: image_path = os.path.join(SuncgLoader._suncg_dir, "texture", adjustments["texture"]) image_path = Utility.resolve_path(image_path) if os.path.exists(image_path + ".png"): image_path += ".png" else: image_path += ".jpg" image_node = mat.get_the_one_node_with_type("ShaderNodeTexImage") if os.path.exists(image_path): image_node.image = bpy.data.images.load(image_path, check_existing=True) else: print( "Warning: Cannot load texture, path does not exist: {}, remove image node again" .format(image_path)) mat.remove_node(image_node)
def _correct_materials(self, objects): """ If the used material contains an alpha texture, the alpha texture has to be flipped to be correct :param objects: objects where the material maybe wrong """ for obj in objects: for mat_slot in obj.material_slots: material = mat_slot.material nodes = material.node_tree.nodes links = material.node_tree.links texture_nodes = Utility.get_nodes_with_type( nodes, "ShaderNodeTexImage") if texture_nodes and len(texture_nodes) > 1: principled_bsdf = Utility.get_the_one_node_with_type( nodes, "BsdfPrincipled") # find the image texture node which is connect to alpha node_connected_to_the_alpha = None for node_links in principled_bsdf.inputs["Alpha"].links: if "ShaderNodeTexImage" in node_links.from_node.bl_idname: node_connected_to_the_alpha = node_links.from_node # if a node was found which is connected to the alpha node, add an invert between the two if node_connected_to_the_alpha is not None: invert_node = nodes.new("ShaderNodeInvert") invert_node.inputs["Fac"].default_value = 1.0 Utility.insert_node_instead_existing_link( links, node_connected_to_the_alpha.outputs["Color"], invert_node.inputs["Color"], invert_node.outputs["Color"], principled_bsdf.inputs["Alpha"])
def _get_type_and_value_from_mat(self, mat): """ Returns the type of the material -> either diffuse or with texture (there are only two in SUNCG) :param mat: the material where the type and value should be determined :return mat_type, value: mat_type is either "diffuse" or "texture", the value contains either name of the image or the color mapped to an RGB string of the values """ nodes = mat.node_tree.nodes image_node = Utility.get_nodes_with_type(nodes, 'TexImage') if len(image_node) == 1: # there is an image node -> type texture mat_type = "texture" image_node = image_node[0] if image_node.image is None: raise Exception( "The image does not have a texture for material: {}". format(mat.name)) value = image_node.image.name if "." in value: value = value[:value.find(".")] else: mat_type = "diffuse" principled_node = Utility.get_the_one_node_with_type( nodes, "BsdfPrincipled") used_keys = list( principled_node.inputs["Base Color"].default_value) alpha = principled_node.inputs['Alpha'].default_value used_keys.append(alpha) value = "_".join([str(int(255. * ele)) for ele in used_keys]) return mat_type, value
def run(self): """ Runs each module and measuring their execution time. """ with Utility.BlockStopWatch("Running blender pipeline"): for module in self.modules: with Utility.BlockStopWatch("Running module " + module.__class__.__name__): module.run()
def _make_ceiling_emissive(self, obj): """ Makes the given ceiling object emissive, s.t. there is always a little bit ambient light. :param obj: The ceiling object. """ for m in obj.material_slots: nodes = m.material.node_tree.nodes links = m.material.node_tree.links output = nodes.get("Material Output") emission = nodes.get("Emission") if emission is None: diffuse = nodes.get("Diffuse BSDF") if diffuse is not None: mix_node = nodes.new(type='ShaderNodeMixShader') Utility.insert_node_instead_existing_link( links, diffuse.outputs['BSDF'], mix_node.inputs[2], mix_node.outputs['Shader'], output.inputs['Surface']) # The light path node returns 1, if the material is hit by a ray coming from the camera, else it returns 0. # In this way the mix shader will use the diffuse shader for rendering the color of the ceiling itself, while using the emission shader for lighting the scene. lightPath_node = nodes.new(type='ShaderNodeLightPath') links.new(lightPath_node.outputs['Is Camera Ray'], mix_node.inputs['Fac']) emission_node = nodes.new(type='ShaderNodeEmission') emission_node.inputs[ 'Strength'].default_value = self.config.get_float( "ceiling_emission_strength", 1.5) links.new(emission_node.outputs['Emission'], mix_node.inputs[1])
def __init__(self, config_path, args, working_dir, should_perform_clean_up=True, avoid_rendering=False): Utility.working_dir = working_dir # Clean up example scene or scene created by last run when debugging pipeline inside blender if should_perform_clean_up: self._cleanup() config_parser = ConfigParser(silent=True) config = config_parser.parse(Utility.resolve_path(config_path), args) config_object = Config(config) if not config_object.get_bool("avoid_rendering", False) and avoid_rendering: # avoid rendering is not already on, but should be: if "all" in config["global"].keys(): config["global"]["all"] = {} config["global"]["all"]["avoid_rendering"] = True self._do_clean_up_temp_dir = config_object.get_bool( "delete_temporary_files_afterwards", True) self._temp_dir = Utility.get_temporary_directory(config_object) os.makedirs(self._temp_dir, exist_ok=True) self.modules = Utility.initialize_modules(config["modules"], config["global"])
def _link_color_to_displacement_for_mat(material, multiply_factor): """ Link the output of the one texture image to the displacement Fails if there is more than one texture image :param material input material, which will be changed :param multiply_factor the factor with which the displacement is multiplied """ nodes = material.node_tree.nodes output = Utility.get_nodes_with_type(nodes, "OutputMaterial") texture = Utility.get_nodes_with_type(nodes, "TexImage") if output is not None and texture is not None: if len(output) == 1 and len(texture) == 1: output = output[0] texture = texture[0] math_node = nodes.new(type='ShaderNodeMath') math_node.operation = "MULTIPLY" math_node.inputs[1].default_value = multiply_factor material.node_tree.links.new(texture.outputs["Color"], math_node.inputs[0]) material.node_tree.links.new(math_node.outputs["Value"], output.inputs["Displacement"]) else: raise Exception( "The amount of output nodes and texture nodes does not work with the option." )
def enable_depth_output(output_dir, file_prefix="depth_", output_key="depth"): """ Enables writing depth images. Depth images will be written in the form of .exr files during the next rendering. :param output_dir: The directory to write files to. :param file_prefix: The prefix to use for writing the files. :param output_key: The key to use for registering the depth output. """ bpy.context.scene.render.use_compositing = True bpy.context.scene.use_nodes = True tree = bpy.context.scene.node_tree links = tree.links # Use existing render layer render_layer_node = Utility.get_the_one_node_with_type(tree.nodes, 'CompositorNodeRLayers') # Enable z-buffer pass bpy.context.view_layer.use_pass_z = True # Build output node output_file = tree.nodes.new("CompositorNodeOutputFile") output_file.base_path = output_dir output_file.format.file_format = "OPEN_EXR" output_file.file_slots.values()[0].path = file_prefix # Feed the Z-Buffer output of the render layer to the input of the file IO layer links.new(render_layer_node.outputs["Depth"], output_file.inputs['Image']) Utility.add_output_entry({ "key": output_key, "path": os.path.join(output_dir, file_prefix) + "%04d" + ".exr", "version": "2.0.0" })
def _make_lamp_emissive(self, obj, light): """ Adds an emission shader to the object materials which are specified in the light list :param obj: The blender object. :param light: A list of two lists. The first list specifies all materials which should act as a lightbulb, the second one lists all materials corresponding to lampshades. """ for m in obj.material_slots: mat_name = m.material.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.material.name if old_mat_name in self._collection_of_mats["lamp"]: # this material was used as a ceiling before use that one m.material = self._collection_of_mats["lamp"][old_mat_name] continue # copy the material if more than one users is using it if m.material.users > 1: new_mat = m.material.copy() m.material = new_mat # rename the material m.material.name += "_emission" nodes = m.material.node_tree.nodes links = m.material.node_tree.links output = Utility.get_the_one_node_with_type( nodes, 'OutputMaterial') emission = Utility.get_nodes_with_type(nodes, "Emission") if not emission: principled = Utility.get_the_one_node_with_type( nodes, "BsdfPrincipled") mix_node = nodes.new(type='ShaderNodeMixShader') Utility.insert_node_instead_existing_link( links, principled.outputs['BSDF'], mix_node.inputs[2], mix_node.outputs['Shader'], output.inputs['Surface']) # The light path node returns 1, if the material is hit by a ray coming from the camera, else it returns 0. # In this way the mix shader will use the principled shader for rendering the color of the lightbulb itself, while using the emission shader for lighting the scene. lightPath_node = nodes.new(type='ShaderNodeLightPath') links.new(lightPath_node.outputs['Is Camera Ray'], mix_node.inputs['Fac']) emission_node = nodes.new(type='ShaderNodeEmission') emission_node.inputs[ 'Color'].default_value = m.material.diffuse_color if mat_name in light[0]: # If the material corresponds to light bulb emission_node.inputs[ 'Strength'].default_value = self.config.get_float( "lightbulb_emission_strength", 15) else: # If the material corresponds to a lampshade emission_node.inputs[ 'Strength'].default_value = self.config.get_float( "lampshade_emission_strength", 7) links.new(emission_node.outputs["Emission"], mix_node.inputs[1]) self._collection_of_mats["lamp"][old_mat_name] = m.material
def write_attributes_to_file(self, item_writer, items, default_file_prefix, default_output_key, default_attributes, version="1.0.0"): """ Writes the state of the given items to a file with the configured prefix. This method also registers the corresponding output. :param item_writer: The item writer object to use. Type: object. :param items: The list of items. Type: list. :param default_file_prefix: The default file name prefix to use. Type: string. :param default_output_key: The default output key to use. Type: string. :param default_attributes: The default attributes to write, if no attributes are specified in the config. Type: list. :param version: The version to use when registering the output. Type: string. """ file_prefix = self.config.get_string("output_file_prefix", default_file_prefix) path_prefix = os.path.join(self._determine_output_dir(), file_prefix) item_writer.write_items_to_file( path_prefix, items, self.config.get_list("attributes_to_write", default_attributes)) Utility.register_output( self._determine_output_dir(), file_prefix, self.config.get_string("output_key", default_output_key), ".npy", version)
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 = Utility.resolve_path(self.config.get_string("path")) loaded_objects = Utility.import_objects(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 = Utility.resolve_path(file_path) current_objects = Utility.import_objects( 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)
def __init__(self, config): LoaderInterface.__init__(self, config) self._file_path = Utility.resolve_path( self.config.get_string("file_path")) self._texture_folder = Utility.resolve_path( self.config.get_string("texture_folder")) # the default unknown texture folder is not included inside of the scenenet texture folder default_unknown_texture_folder = os.path.join(self._texture_folder, "unknown") # the textures in this folder are used, if the object has no available texture self._unknown_texture_folder = Utility.resolve_path( self.config.get_string("unknown_texture_folder", default_unknown_texture_folder)) LabelIdMapping.assign_mapping( Utility.resolve_path( os.path.join('resources', 'id_mappings', 'nyu_idset.csv'))) if LabelIdMapping.label_id_map: bpy.context.scene.world[ "category_id"] = LabelIdMapping.label_id_map["void"] else: print( "Warning: The category labeling file could not be found -> no semantic segmentation available!" )
def __init__(self, config_path, args, working_dir, temp_dir, should_perform_clean_up=True, avoid_rendering=False): """ Inits the pipeline, by calling the constructors of all modules mentioned in the config. :param config_path: path to the config :param args: arguments which were provided to the run.py and are specified in the config file :param working_dir: the current working dir usually the place where the run.py sits :param working_dir: the directory where to put temporary files during the execution :param should_perform_clean_up: if the generated temp file should be deleted at the end :param avoid_rendering: if this is true all renderes are not executed (except the RgbRenderer, \ where only the rendering call to blender is avoided) with this it is possible to debug \ properly """ Utility.working_dir = working_dir # Clean up example scene or scene created by last run when debugging pipeline inside blender if should_perform_clean_up: self._cleanup() config_parser = ConfigParser(silent=True) config = config_parser.parse(Utility.resolve_path(config_path), args) if avoid_rendering: GlobalStorage.add_to_config_before_init("avoid_rendering", True) Utility.temp_dir = Utility.resolve_path(temp_dir) os.makedirs(Utility.temp_dir, exist_ok=True) self.modules = Utility.initialize_modules(config["modules"])
def _cam2world_matrix_from_cam_extrinsics(self, config): """ 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 cam to world transformation matrix. """ if not config.has_param("cam2world_matrix"): position = Utility.transform_point_to_blender_coord_frame(config.get_vector3d("location", [0, 0, 0]), self.source_frame) # Rotation rotation_format = config.get_string("rotation/format", "euler") value = config.get_vector3d("rotation/value", [0, 0, 0]) if rotation_format == "euler": # Rotation, specified as euler angles rotation_euler = Utility.transform_point_to_blender_coord_frame(value, self.source_frame) elif rotation_format == "forward_vec": # Rotation, specified as forward vector forward_vec = Vector(Utility.transform_point_to_blender_coord_frame(value, self.source_frame)) # Convert forward vector to euler angle (Assume Up = Z) rotation_euler = forward_vec.to_track_quat('-Z', 'Y').to_euler() elif rotation_format == "look_at": # Compute forward vector forward_vec = value - position forward_vec.normalize() # Convert forward vector to euler angle (Assume Up = Z) rotation_euler = forward_vec.to_track_quat('-Z', 'Y').to_euler() else: raise Exception("No such rotation format:" + str(rotation_format)) cam2world_matrix = Matrix.Translation(Vector(position)) @ Euler(rotation_euler, 'XYZ').to_matrix().to_4x4() else: cam2world_matrix = Matrix(np.array(config.get_list("cam2world_matrix")).reshape(4, 4).astype(np.float32)) return cam2world_matrix
def _adjust_material_nodes(self, mat, adjustments): """ Adjust the material node of the given material according to the given adjustments. Textures or diffuse colors will be changed according to the given material_adjustments. :param mat: The blender material. :param adjustments: A dict containing a new "diffuse" color or a new "texture" path """ nodes = mat.node_tree.nodes diffuse_node = nodes.get("Diffuse BSDF") image_node = nodes.get("Image Texture") if "diffuse" in adjustments: diffuse_node.inputs['Color'].default_value = Utility.hex_to_rgba( adjustments["diffuse"]) if "texture" in adjustments: image_path = os.path.join(self.suncg_dir, "texture", adjustments["texture"]) image_path = Utility.resolve_path(image_path) if os.path.exists(image_path + ".png"): image_path += ".png" else: image_path += ".jpg" if os.path.exists(image_path): image_node.image = bpy.data.images.load(image_path, check_existing=True) else: print("Warning: Cannot load texture, path does not exist: " + image_path)
def enable_diffuse_color_output(output_dir: str, file_prefix: str = "diffuse_", output_key: str = "diffuse"): """ Enables writing diffuse color (albedo) images. Diffuse color images will be written in the form of .png files during the next rendering. :param output_dir: The directory to write files to. :param file_prefix: The prefix to use for writing the files. :param output_key: The key to use for registering the diffuse color output. """ bpy.context.scene.render.use_compositing = True bpy.context.scene.use_nodes = True tree = bpy.context.scene.node_tree links = tree.links bpy.context.view_layer.use_pass_diffuse_color = True render_layer_node = Utility.get_the_one_node_with_type(tree.nodes, 'CompositorNodeRLayers') final_output = render_layer_node.outputs["DiffCol"] output_file = tree.nodes.new('CompositorNodeOutputFile') output_file.base_path = output_dir output_file.format.file_format = "PNG" output_file.file_slots.values()[0].path = file_prefix links.new(final_output, output_file.inputs['Image']) Utility.add_output_entry({ "key": output_key, "path": os.path.join(output_dir, file_prefix) + "%04d" + ".png", "version": "2.0.0" })
def __init__(self, config_path, args, working_dir, temp_dir, avoid_output=False): """ Inits the pipeline, by calling the constructors of all modules mentioned in the config. :param config_path: path to the config :param args: arguments which were provided to the run.py and are specified in the config file :param working_dir: the current working dir usually the place where the run.py sits :param working_dir: the directory where to put temporary files during the execution :param avoid_output: if this is true, all modules (renderers and writers) skip producing output. With this it is possible to debug \ properly. """ Utility.working_dir = working_dir config_parser = ConfigParser(silent=True) config = config_parser.parse(Utility.resolve_path(config_path), args) # Setup pip packages specified in config SetupUtility.setup_pip(config["setup"]["pip"] if "pip" in config["setup"] else []) if avoid_output: GlobalStorage.add_to_config_before_init("avoid_output", True) Utility.temp_dir = Utility.resolve_path(temp_dir) os.makedirs(Utility.temp_dir, exist_ok=True) self.modules = Utility.initialize_modules(config["modules"])
def _default_init(self): """ These operations are called during all modules inits """ self._output_dir = Utility.resolve_path(self.config.get_string("output_dir", "")) os.makedirs(self._output_dir, exist_ok=True) self._temp_dir = Utility.get_temporary_directory()
def set_type(self, type: str, frame: int = None): """ Sets the type of the light. :param type: The type to set, can be one of [POINT, SUN, SPOT, AREA]. :param frame: The frame number which the value should be set to. If None is given, the current frame number is used. """ self.light_obj.data.type = type Utility.insert_keyframe(self.light_obj.data, "type", frame)
def set_distance(self, distance: float, frame: int = None): """ Sets the falloff distance of the light = point where light is half the original intensity. :param distance: The falloff distance to set. :param frame: The frame number which the value should be set to. If None is given, the current frame number is used. """ self.light_obj.data.distance = distance Utility.insert_keyframe(self.light_obj.data, "distance", frame)
def set_color(self, color: Union[list, Color], frame: int = None): """ Sets the color of the light. :param color: The rgb color to set. :param frame: The frame number which the value should be set to. If None is given, the current frame number is used. """ self.light_obj.data.color = color Utility.insert_keyframe(self.light_obj.data, "color", frame)
def set_energy(self, energy: float, frame: int = None): """ Sets the energy of the light. :param energy: The energy to set. If the type is SUN this value is interpreted as Watt per square meter, otherwise it is interpreted as Watt. :param frame: The frame number which the value should be set to. If None is given, the current frame number is used. """ self.light_obj.data.energy = energy Utility.insert_keyframe(self.light_obj.data, "energy", frame)
def __init__(self, config): Module.__init__(self, config) object_pose_sampler_config = config.get_raw_dict("object_pose_sampler", {}) camera_pose_sampler_config = config.get_raw_dict("camera_pose_sampler", {}) self._object_pose_sampler = Utility.initialize_modules([object_pose_sampler_config], {})[0] self._camera_pose_sampler = Utility.initialize_modules([camera_pose_sampler_config], {})[0]
def set_rotation_euler(self, rotation_euler: Union[list, Euler], frame: int = None): """ Sets the rotation of the light in euler angles. :param rotation_euler: The euler angles to set. :param frame: The frame number which the value should be set to. If None is given, the current frame number is used. """ self.light_obj.rotation_euler = rotation_euler Utility.insert_keyframe(self.light_obj, "rotation_euler", frame)
def set_scale(self, scale: Union[list, np.ndarray, Vector], frame: int = None): """ Sets the scale of the entity along all three axes. :param scale: The scale to set. :param frame: The frame number which the value should be set to. If None is given, the current frame number is used. """ self.blender_obj.scale = scale Utility.insert_keyframe(self.blender_obj, "scale", frame)
def set_location(self, location: Union[list, Vector], frame: int = None): """ Sets the location of the light in 3D world coordinates. :param location: The location to set. :param frame: The frame number which the value should be set to. If None is given, the current frame number is used. """ self.light_obj.location = location Utility.insert_keyframe(self.light_obj, "location", frame)
def _recreate_material_nodes(self, mat, force_texture): """ Remove all nodes and recreate a diffuse node, optionally with texture. This will replace all material nodes with only a diffuse and a texturing node (to speedup rendering). :param mat: The blender material :param force_texture: True, if there always should be a texture node created even if the material has at the moment no texture """ links = mat.node_tree.links nodes = mat.node_tree.nodes # Make sure we have not changed this material already (materials can be shared between objects) if not Utility.get_nodes_with_type(nodes, "BsdfDiffuse"): # The principled BSDF node contains all imported material properties principled_node = Utility.get_nodes_with_type( nodes, "BsdfPrincipled") if len(principled_node) == 1: principled_node = principled_node[0] else: raise Exception( "This material has not one principled shader node, mat: {}" .format(mat.name)) diffuse_color = principled_node.inputs['Base Color'].default_value image_node = Utility.get_nodes_with_type(nodes, 'TexImage') if len(image_node) == 1: image_node = image_node[0] elif len(image_node) > 1: raise Exception( "There is more than one texture node in this material: {}". format(mat.name)) texture = image_node.image if image_node else None # Remove all nodes except the principled bsdf node (useful to lookup imported material properties in other modules) for node in nodes: if "BsdfPrincipled" not in node.bl_idname: nodes.remove(node) # Build output, diffuse and texture nodes output_node = nodes.new(type='ShaderNodeOutputMaterial') diffuse_node = nodes.new(type='ShaderNodeBsdfDiffuse') if texture is not None or force_texture: uv_node = nodes.new(type='ShaderNodeTexCoord') image_node = nodes.new(type='ShaderNodeTexImage') # Link them links.new(diffuse_node.outputs['BSDF'], output_node.inputs['Surface']) if texture is not None or force_texture: links.new(image_node.outputs['Color'], diffuse_node.inputs['Color']) links.new(uv_node.outputs['UV'], image_node.inputs['Vector']) # Set values from imported material properties diffuse_node.inputs['Color'].default_value = diffuse_color if texture is not None: image_node.image = texture
def add_emission_to_materials(self, objects): """ Add emission shader to the materials of the objects which are named either lamp or ceiling This will even work if the original materials have been replaced :param objects, to change the materials of """ # for each object add a material for obj in objects: for mat_slot in obj.material_slots: obj_name = obj.name if "." in obj_name: obj_name = obj_name[:obj_name.find(".")] obj_name = obj_name.replace("_", "") # remove all digits from the string obj_name = ''.join([i for i in obj_name if not i.isdigit()]) if "lamp" in obj_name or "ceiling" in obj_name: material = mat_slot.material # if there is more than one user make a copy and then use the new one if material.users > 1: new_mat = material.copy() mat_slot.material = new_mat material = mat_slot.material # rename the material material.name += "_emission" nodes = material.node_tree.nodes links = material.node_tree.links principled_bsdf = Utility.get_the_one_node_with_type(nodes, "BsdfPrincipled") output_node = Utility.get_the_one_node_with_type(nodes, "OutputMaterial") mix_node = nodes.new(type='ShaderNodeMixShader') Utility.insert_node_instead_existing_link(links, principled_bsdf.outputs['BSDF'], mix_node.inputs[2], mix_node.outputs['Shader'], output_node.inputs['Surface']) # The light path node returns 1, if the material is hit by a ray coming from the camera, else it returns 0. # In this way the mix shader will use the principled shader for rendering the color of the lightbulb itself, while using the emission shader for lighting the scene. lightPath_node = nodes.new(type='ShaderNodeLightPath') links.new(lightPath_node.outputs['Is Camera Ray'], mix_node.inputs['Fac']) emission_node = nodes.new(type='ShaderNodeEmission') if "lamp" in obj_name: if len(principled_bsdf.inputs["Base Color"].links) == 1: # get the node connected to the Base Color node_connected_to_the_base_color = principled_bsdf.inputs["Base Color"].links[0].from_node # use 0 as it is probably the first one links.new(node_connected_to_the_base_color.outputs[0], emission_node.inputs["Color"]) # If the material corresponds to a lampshade emission_node.inputs['Strength'].default_value = \ self.config.get_float("lampshade_emission_strength", 15) elif "ceiling" in obj_name: # If the material corresponds to a ceiling emission_node.inputs['Strength'].default_value = self.config.get_float("ceiling_emission_strength", 2) links.new(emission_node.outputs["Emission"], mix_node.inputs[1])
def __init__(self, config): self.config = config self._output_dir = Utility.resolve_path( self.config.get_string("output_dir", "")) os.makedirs(self._output_dir, exist_ok=True) self._temp_dir = Utility.get_temporary_directory(config) os.makedirs(self._temp_dir, exist_ok=True)
def run(self): """ For each object, 1- Place the object outside sampling volume 2- Until we have objects remaining and have not run out of tries, Sample a point 3- Put the top object in queue at the sampled point 4- If no collision then keep the position else reset Here we use any general sampling method supported by us """ pos_sampler_params = self.config.get_raw_dict("pos_sampler") rot_sampler_params = self.config.get_raw_dict("rot_sampler") # 2- Until we have objects remaining and have not run out of tries, Sample a point placed = [] # List of objects successfully placed max_tries = self.config.get_int( "max_iterations", 1000 ) # After this many tries we give up on current object and continue with rest cache = {} # cache to fasten collision detection for obj in bpy.context.scene.objects: # for each object if obj.type == "MESH": print("Trying to put ", obj.name) prior_location = obj.location prior_rotation = obj.rotation_euler no_collision = True for i in range(max_tries): # Try max_iter amount of times # 3- Put the top object in queue at the sampled point in space position = Utility.sample_based_on_config( pos_sampler_params) rotation = Utility.sample_based_on_config( rot_sampler_params) obj.location = position # assign it a new position obj.rotation_euler = rotation # and a rotation bpy.context.view_layer.update() # then udpate scene no_collision = True for already_placed in placed: # Now check for collisions intersection = check_bb_intersection( obj, already_placed ) # First check if bounding boxes collides if intersection: # if they do intersection, cache = check_intersection( obj, already_placed ) # then check for more refined collisions if intersection: no_collision = False break # 4- If no collision then keep the position else reset if no_collision: # if no collisions print("No collision detected, Moving forward!") placed.append(obj) break # then stop trying and keep assigned position and orientation else: # if any collisions then reset object to initial state print("collision detected, Retrying!!") obj.location = prior_location obj.rotation_euler = prior_rotation bpy.context.view_layer.update() if not no_collision: print("giving up on ", obj.name)