예제 #1
0
    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)
예제 #2
0
    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"])
예제 #3
0
 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
예제 #4
0
 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])
예제 #6
0
    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"])
예제 #7
0
    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."
                )
예제 #8
0
    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"
        })
예제 #9
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
예제 #10
0
    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)
예제 #11
0
    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)
예제 #12
0
    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!"
            )
예제 #13
0
    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"])
예제 #14
0
    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
예제 #15
0
    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)
예제 #16
0
    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"
        })
예제 #17
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"])
예제 #18
0
    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()
예제 #19
0
    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)
예제 #20
0
    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)
예제 #21
0
    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)
예제 #22
0
    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]
예제 #24
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)
예제 #25
0
    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)
예제 #26
0
    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)
예제 #27
0
    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
예제 #28
0
    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])
예제 #29
0
    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)
예제 #30
0
    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)