Skip to content
Snippets Groups Projects
cycles_shader_compat.py 19.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### BEGIN GPL LICENSE BLOCK #####
    #
    #  This program is free software; you can redistribute it and/or
    #  modify it under the terms of the GNU General Public License
    #  as published by the Free Software Foundation; either version 2
    #  of the License, or (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program; if not, write to the Free Software Foundation,
    #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    #
    # ##### END GPL LICENSE BLOCK #####
    
    # <pep8 compliant>
    
    import bpy
    
    __all__ = (
        "CyclesShaderWrapper",
        )
    
    
    class CyclesShaderWrapper():
        """
        Hard coded shader setup.
        Suitable for importers, adds basic:
        diffuse/spec/alpha/normal/bump/reflect.
        """
        __slots__ = (
            "material",
    
            "node_out",
            "node_mix_shader_spec",
            "node_mix_shader_alpha",
            "node_mix_shader_refl",
    
            "node_bsdf_alpha",
            "node_bsdf_diff",
            "node_bsdf_spec",
            "node_bsdf_refl",
    
            "node_mix_color_alpha",
            "node_mix_color_diff",
            "node_mix_color_spec",
    
            "node_mix_color_refl",
            "node_mix_color_bump",
    
    
            "node_texcoords",
    
            "node_image_alpha",
            "node_image_diff",
            "node_image_spec",
            "node_image_hard",
            "node_image_refl",
            "node_image_bump",
            "node_image_normalmap",
    
            )
    
        _col_size = 200
        _row_size = 220
    
        def __init__(self, material):
    
            COLOR_WHITE = 1.0, 1.0, 1.0, 1.0
            COLOR_BLACK = 0.0, 0.0, 0.0, 1.0
    
            self.material = material
            self.material.use_nodes = True
    
            tree = self.material.node_tree
    
            nodes = tree.nodes
            links = tree.links
            nodes.clear()
    
            # ----
            # Add shaders
            node = nodes.new(type='ShaderNodeOutputMaterial')
            node.label = "Material Out"
            node.location = self._grid_location(6, 4)
            self.node_out = node
            del node
    
            node = nodes.new(type='ShaderNodeAddShader')
            node.label = "Shader Add Refl"
            node.location = self._grid_location(5, 4)
            self.node_mix_shader_refl = node
            del node
            # Link
            links.new(self.node_mix_shader_refl.outputs["Shader"],
                      self.node_out.inputs["Surface"])
    
            node = nodes.new(type='ShaderNodeAddShader')
            node.label = "Shader Add Spec"
            node.location = self._grid_location(4, 4)
            self.node_mix_shader_spec = node
            del node
            # Link
            links.new(self.node_mix_shader_spec.outputs["Shader"],
                      self.node_mix_shader_refl.inputs[0])
    
            # --------------------------------------------------------------------
            # Reflection
            node = nodes.new(type='ShaderNodeBsdfRefraction')
            node.label = "Refl BSDF"
            node.location = self._grid_location(6, 1)
            node.mute = True  # unmute on use
            self.node_bsdf_refl = node
            del node
            # Link
            links.new(self.node_bsdf_refl.outputs["BSDF"],
                      self.node_mix_shader_refl.inputs[1])
    
            # Mix Refl Color
            node = nodes.new(type='ShaderNodeMixRGB')
            node.label = "Mix Color/Refl"
            node.location = self._grid_location(5, 1)
            node.blend_type = 'MULTIPLY'
            node.inputs["Fac"].default_value = 1.0
            # reverse of most other mix nodes
            node.inputs["Color1"].default_value = COLOR_WHITE  # color
            node.inputs["Color2"].default_value = COLOR_BLACK  # factor
            self.node_mix_color_refl = node
            del node
            # Link
            links.new(self.node_mix_color_refl.outputs["Color"],
                      self.node_bsdf_refl.inputs["Color"])
    
            # --------------------------------------------------------------------
            # Alpha
    
            # ----
            # Mix shader
            node = nodes.new(type='ShaderNodeMixShader')
            node.label = "Shader Mix Alpha"
            node.location = self._grid_location(3, 4)
            node.inputs["Fac"].default_value = 1.0  # no alpha by default
            self.node_mix_shader_alpha = node
            del node
            # Link
            links.new(self.node_mix_shader_alpha.outputs["Shader"],
                      self.node_mix_shader_spec.inputs[0])
    
            # Alpha BSDF
            node = nodes.new(type='ShaderNodeBsdfTransparent')
            node.label = "Alpha BSDF"
            node.location = self._grid_location(2, 4)
            node.mute = True  # unmute on use
            self.node_bsdf_alpha = node
            del node
            # Link
            links.new(self.node_bsdf_alpha.outputs["BSDF"],
                      self.node_mix_shader_alpha.inputs[1])  # first 'Shader'
    
            # Mix Alpha Color
            node = nodes.new(type='ShaderNodeMixRGB')
            node.label = "Mix Color/Alpha"
            node.location = self._grid_location(1, 5)
            node.blend_type = 'MULTIPLY'
            node.inputs["Fac"].default_value = 1.0
            node.inputs["Color1"].default_value = COLOR_WHITE
            node.inputs["Color2"].default_value = COLOR_WHITE
            self.node_mix_color_alpha = node
            del node
            # Link
            links.new(self.node_mix_color_alpha.outputs["Color"],
                      self.node_mix_shader_alpha.inputs["Fac"])
    
            # --------------------------------------------------------------------
            # Diffuse
    
            # Diffuse BSDF
            node = nodes.new(type='ShaderNodeBsdfDiffuse')
            node.label = "Diff BSDF"
            node.location = self._grid_location(2, 3)
            self.node_bsdf_diff = node
            del node
            # Link
            links.new(self.node_bsdf_diff.outputs["BSDF"],
                      self.node_mix_shader_alpha.inputs[2])  # first 'Shader'
    
            # Mix Diffuse Color
            node = nodes.new(type='ShaderNodeMixRGB')
            node.label = "Mix Color/Diffuse"
            node.location = self._grid_location(1, 3)
            node.blend_type = 'MULTIPLY'
            node.inputs["Fac"].default_value = 1.0
            node.inputs["Color1"].default_value = COLOR_WHITE
            node.inputs["Color2"].default_value = COLOR_WHITE
            self.node_mix_color_diff = node
            del node
            # Link
            links.new(self.node_mix_color_diff.outputs["Color"],
                      self.node_bsdf_diff.inputs["Color"])
    
            # --------------------------------------------------------------------
            # Specular
            node = nodes.new(type='ShaderNodeBsdfGlossy')
            node.label = "Spec BSDF"
            node.location = self._grid_location(2, 1)
            node.mute = True  # unmute on use
            self.node_bsdf_spec = node
            del node
            # Link (with add shader)
            links.new(self.node_bsdf_spec.outputs["BSDF"],
                      self.node_mix_shader_spec.inputs[1])  # second 'Shader' slot
    
            node = nodes.new(type='ShaderNodeMixRGB')
    
            node.label = "Mix Color/Spec"
    
            node.location = self._grid_location(1, 1)
            node.blend_type = 'MULTIPLY'
            node.inputs["Fac"].default_value = 1.0
            node.inputs["Color1"].default_value = COLOR_WHITE
            node.inputs["Color2"].default_value = COLOR_BLACK
            self.node_mix_color_spec = node
            del node
            # Link
            links.new(self.node_mix_color_spec.outputs["Color"],
                      self.node_bsdf_spec.inputs["Color"])
    
            node = nodes.new(type='ShaderNodeMixRGB')
            node.label = "Mix Color/Hardness"
    
            node.location = self._grid_location(1, 0)
    
            node.blend_type = 'MULTIPLY'
            node.inputs["Fac"].default_value = 1.0
            node.inputs["Color1"].default_value = COLOR_WHITE
    
            node.inputs["Color2"].default_value = COLOR_WHITE
    
            self.node_mix_color_hard = node
    
            links.new(self.node_mix_color_hard.outputs["Color"],
    
                      self.node_bsdf_spec.inputs["Roughness"])
    
            # --------------------------------------------------------------------
            # Normal Map
            node = nodes.new(type='ShaderNodeNormalMap')
            node.label = "Normal/Map"
            node.location = self._grid_location(1, 2)
            node.mute = True  # unmute on use
    
            del node
    
            # Link (with diff shader)
    
            socket_src = self.node_normalmap.outputs["Normal"]
    
            links.new(socket_src,
                      self.node_bsdf_diff.inputs["Normal"])
            # Link (with spec shader)
            links.new(socket_src,
                      self.node_bsdf_spec.inputs["Normal"])
            # Link (with refl shader)
            links.new(socket_src,
                      self.node_bsdf_refl.inputs["Normal"])
            del socket_src
    
            # --------------------------------------------------------------------
            # Bump Map
            # Mix Refl Color
            node = nodes.new(type='ShaderNodeMixRGB')
            node.label = "Bump/Map"
            node.location = self._grid_location(5, 3)
            node.mute = True  # unmute on use
            node.blend_type = 'MULTIPLY'
            node.inputs["Fac"].default_value = 1.0
            # reverse of most other mix nodes
            node.inputs["Color1"].default_value = COLOR_WHITE  # color
            node.inputs["Color2"].default_value = COLOR_BLACK  # factor
            self.node_mix_color_bump = node
            del node
            # Link
            links.new(self.node_mix_color_bump.outputs["Color"],
                      self.node_out.inputs["Displacement"])
    
    
            # --------------------------------------------------------------------
            # Tex Coords
            node = nodes.new(type='ShaderNodeTexCoord')
            node.label = "Texture Coords"
            node.location = self._grid_location(-3, 3)
            self.node_texcoords = node
            del node
            # no links, only use when needed!
    
    
        def _image_create_helper(image, node_dst, sockets_dst, use_alpha=False):
    
            tree = node_dst.id_data
            nodes = tree.nodes
            links = tree.links
    
            node = nodes.new(type='ShaderNodeTexImage')
            node.image = image
            node.location = node_dst.location
            node.location.x -= CyclesShaderWrapper._col_size
            for socket in sockets_dst:
    
                links.new(node.outputs["Alpha" if use_alpha else "Color"],
    
        @staticmethod
        def _mapping_create_helper(node_dst, socket_src,
    
                                   translation, rotation, scale, clamp):
    
            tree = node_dst.id_data
            nodes = tree.nodes
            links = tree.links
    
            # in most cases:
            # (socket_src == self.node_texcoords.outputs['UV'])
    
    
            # find an existing mapping node (allows multiple calls)
            if node_dst.inputs["Vector"].links:
                node_map = node_dst.inputs["Vector"].links[0].from_node
    
            if node_map is None:
                node_map = nodes.new(type='ShaderNodeMapping')
    
                node_map.location = node_dst.location
                node_map.location.x -= CyclesShaderWrapper._col_size
    
                node_map.width = 160.0
    
                # link mapping -> image node
                links.new(node_map.outputs["Vector"],
                          node_dst.inputs["Vector"])
    
                # link coord -> mapping
                links.new(socket_src,
                          node_map.inputs["Vector"])
    
    
            if translation is not None:
                node_map.translation = translation
            if scale is not None:
                node_map.scale = scale
            if rotation is not None:
                node_map.rotation = rotation
    
            if clamp is not None:
                # awkward conversion UV clamping to minmax
                node_map.min = (0.0, 0.0, 0.0)
                node_map.max = (1.0, 1.0, 1.0)
    
                if clamp in {(False, False), (True, True)}:
                    node_map.use_min = node_map.use_max = clamp[0]
                else:
                    node_map.use_min = node_map.use_max = True
                    # use bool as index
                    node_map.min[not clamp[0]] = -1000000000.0
                    node_map.max[not clamp[0]] = 1000000000.0
    
    
            return node_map
    
        # note, all ***_mapping_set() functions currenly work the same way
        # (only with different image arg), could generalize.
    
    
        @staticmethod
        def _grid_location(x, y):
            return (x * CyclesShaderWrapper._col_size,
                    y * CyclesShaderWrapper._row_size)
    
        def diffuse_color_set(self, color):
            self.node_mix_color_diff.inputs["Color1"].default_value[0:3] = color
    
        def diffuse_image_set(self, image):
            node = self.node_mix_color_diff
    
            self.node_image_diff = (
                self._image_create_helper(image, node, (node.inputs["Color2"],)))
    
        def diffuse_mapping_set(self, coords='UV',
    
                                translation=None, rotation=None, scale=None, clamp=None):
    
            return self._mapping_create_helper(
    
                self.node_image_diff, self.node_texcoords.outputs[coords], translation, rotation, scale, clamp)
    
    
        def specular_color_set(self, color):
            self.node_bsdf_spec.mute = max(color) <= 0.0
            self.node_mix_color_spec.inputs["Color1"].default_value[0:3] = color
    
        def specular_image_set(self, image):
            node = self.node_mix_color_spec
    
            self.node_image_spec = (
                self._image_create_helper(image, node, (node.inputs["Color2"],)))
    
        def specular_mapping_set(self, coords='UV',
    
                                 translation=None, rotation=None, scale=None, clamp=None):
    
            return self._mapping_create_helper(
    
                self.node_image_spec, self.node_texcoords.outputs[coords], translation, rotation, scale, clamp)
    
    
        def hardness_value_set(self, value):
    
            node = self.node_mix_color_hard
    
            node.inputs["Color1"].default_value = (value,) * 4
    
        def hardness_image_set(self, image):
    
            node = self.node_mix_color_hard
    
            self.node_image_hard = (
                self._image_create_helper(image, node, (node.inputs["Color2"],)))
    
        def hardness_mapping_set(self, coords='UV',
    
                                 translation=None, rotation=None, scale=None, clamp=None):
    
            return self._mapping_create_helper(
    
                self.node_image_hard, self.node_texcoords.outputs[coords], translation, rotation, scale, clamp)
    
    
        def reflect_color_set(self, color):
            node = self.node_mix_color_refl
            node.inputs["Color1"].default_value[0:3] = color
    
        def reflect_factor_set(self, value):
            # XXX, conflicts with image
            self.node_bsdf_refl.mute = value <= 0.0
            node = self.node_mix_color_refl
            node.inputs["Color2"].default_value = (value,) * 4
    
        def reflect_image_set(self, image):
            self.node_bsdf_refl.mute = False
            node = self.node_mix_color_refl
    
            self.node_image_refl = (
                self._image_create_helper(image, node, (node.inputs["Color2"],)))
    
        def reflect_mapping_set(self, coords='UV',
    
                                translation=None, rotation=None, scale=None, clamp=None):
    
            return self._mapping_create_helper(
    
                self.node_image_refl, self.node_texcoords.outputs[coords], translation, rotation, scale, clamp)
    
    
        def alpha_value_set(self, value):
    
            self.node_bsdf_alpha.mute &= (value >= 1.0)
    
            node = self.node_mix_color_alpha
            node.inputs["Color1"].default_value = (value,) * 4
    
        def alpha_image_set(self, image):
    
            node = self.node_mix_color_alpha
    
            # note: use_alpha may need to be configurable
            # its not always the case that alpha channels use the image alpha
            # a greyscale image may also be used.
    
            self.node_image_alpha = (
                self._image_create_helper(image, node, (node.inputs["Color2"],), use_alpha=True))
    
        def alpha_mapping_set(self, coords='UV',
    
                              translation=None, rotation=None, scale=None, clamp=None):
    
            return self._mapping_create_helper(
    
                self.node_image_alpha, self.node_texcoords.outputs[coords], translation, rotation, scale, clamp)
    
        def alpha_image_set_from_diffuse(self):
    
            tree = self.node_mix_color_diff.id_data
            links = tree.links
    
            self.node_bsdf_alpha.mute = False
    
            node_image = self.node_image_diff
    
            if 1:
                links.new(node_image.outputs["Alpha"],
                          node.inputs["Color2"])
            else:
                self.alpha_image_set(node_image.image)
                self.node_image_alpha.label = "Image Texture_ALPHA"
    
        def normal_factor_set(self, value):
    
            node.inputs["Strength"].default_value = value
    
        def normal_image_set(self, image):
    
            self.node_normalmap.mute = False
            node = self.node_normalmap
    
            self.node_image_normalmap = (
                self._image_create_helper(image, node, (node.inputs["Color"],)))
    
            self.node_image_normalmap.color_space = 'NONE'
    
        def normal_mapping_set(self, coords='UV',
    
                               translation=None, rotation=None, scale=None, clamp=None):
    
            return self._mapping_create_helper(
    
                self.node_image_normalmap, self.node_texcoords.outputs[coords], translation, rotation, scale, clamp)
    
        def bump_factor_set(self, value):
            node = self.node_mix_color_bump
            node.mute = (value <= 0.0)
            node.inputs["Color1"].default_value = (value,) * 4
    
        def bump_image_set(self, image):
            node = self.node_mix_color_bump
    
            self.node_image_bump = (
                self._image_create_helper(image, node, (node.inputs["Color2"],)))
    
        def bump_mapping_set(self, coords='UV',
    
                             translation=None, rotation=None, scale=None, clamp=None):
    
            return self._mapping_create_helper(
    
                self.node_image_bump, self.node_texcoords.outputs[coords], translation, rotation, scale, clamp)
    
    
        def mapping_set_from_diffuse(self,
                                     specular=True,
                                     hardness=True,
                                     reflect=True,
                                     alpha=True,
                                     normal=True,
                                     bump=True):
            """
            Set all mapping based on diffuse
            (sometimes we want to assume default mapping follows diffuse).
            """
            # get mapping from diffuse
    
            if not hasattr(self, "node_image_diff"):
                return
    
    
            links = self.node_image_diff.inputs["Vector"].links
            if not links:
                return
    
            mapping_out_socket = links[0].from_socket
    
            tree = self.material.node_tree
            links = tree.links
    
            def node_image_mapping_apply(node_image_attr):
                # ensure strings are valid attrs
                assert(node_image_attr in self.__slots__)
    
                node_image = getattr(self, node_image_attr, None)
    
                if node_image is not None:
                    node_image_input_socket = node_image.inputs["Vector"]
                    # don't overwrite existing sockets
                    if not node_image_input_socket.links:
                        links.new(mapping_out_socket,
                                  node_image_input_socket)
    
            if specular:
                node_image_mapping_apply("node_image_spec")
            if hardness:
                node_image_mapping_apply("node_image_hard")
            if reflect:
                node_image_mapping_apply("node_image_refl")
            if alpha:
                node_image_mapping_apply("node_image_alpha")
            if normal:
                node_image_mapping_apply("node_image_normalmap")
            if bump:
                node_image_mapping_apply("node_image_bump")