'is_data' since the move from 'cycles_shader_compat' (2.79) to 'PrincipledBSDFWrapper' (2.8) we lost the ability to (automatically) set this colorspace setting for imported textures. This was useful for e.g. normalmaps to always assume 'Non-Color' data. This adds the possibility to overide and uses this for normalmaps... Fixes T66368 Reviewers: mont29, brecht Maniphest Tasks: T66368 Differential Revision: https://developer.blender.org/D5176
		
			
				
	
	
		
			804 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			804 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# ##### 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>
 | 
						|
 | 
						|
from mathutils import Color, Vector
 | 
						|
 | 
						|
__all__ = (
 | 
						|
    "PrincipledBSDFWrapper",
 | 
						|
)
 | 
						|
 | 
						|
 | 
						|
def _set_check(func):
 | 
						|
    from functools import wraps
 | 
						|
 | 
						|
    @wraps(func)
 | 
						|
    def wrapper(self, *args, **kwargs):
 | 
						|
        if self.is_readonly:
 | 
						|
            assert(not "Trying to set value to read-only shader!")
 | 
						|
            return
 | 
						|
        return func(self, *args, **kwargs)
 | 
						|
    return wrapper
 | 
						|
 | 
						|
def rgb_to_rgba(rgb):
 | 
						|
    return list(rgb) + [1.0]
 | 
						|
 | 
						|
def rgba_to_rgb(rgba):
 | 
						|
    return Color((rgba[0], rgba[1], rgba[2]))
 | 
						|
 | 
						|
 | 
						|
class ShaderWrapper():
 | 
						|
    """
 | 
						|
    Base class with minimal common ground for all types of shader interfaces we may want/need to implement.
 | 
						|
    """
 | 
						|
 | 
						|
    # The two mandatory nodes any children class should support.
 | 
						|
    NODES_LIST = (
 | 
						|
        "node_out",
 | 
						|
 | 
						|
        "_node_texcoords",
 | 
						|
    )
 | 
						|
 | 
						|
    __slots__ = (
 | 
						|
        "is_readonly",
 | 
						|
        "material",
 | 
						|
        "_textures",
 | 
						|
        "_grid_locations",
 | 
						|
        *NODES_LIST,
 | 
						|
    )
 | 
						|
 | 
						|
    _col_size = 300
 | 
						|
    _row_size = 300
 | 
						|
 | 
						|
    def _grid_to_location(self, x, y, dst_node=None, ref_node=None):
 | 
						|
        if ref_node is not None:  # x and y are relative to this node location.
 | 
						|
            nx = round(ref_node.location.x / self._col_size)
 | 
						|
            ny = round(ref_node.location.y / self._row_size)
 | 
						|
            x += nx
 | 
						|
            y += ny
 | 
						|
        loc = None
 | 
						|
        while True:
 | 
						|
            loc = (x * self._col_size, y * self._row_size)
 | 
						|
            if loc not in self._grid_locations:
 | 
						|
                break
 | 
						|
            loc = (x * self._col_size, (y - 1) * self._row_size)
 | 
						|
            if loc not in self._grid_locations:
 | 
						|
                break
 | 
						|
            loc = (x * self._col_size, (y - 2) * self._row_size)
 | 
						|
            if loc not in self._grid_locations:
 | 
						|
                break
 | 
						|
            x -= 1
 | 
						|
        self._grid_locations.add(loc)
 | 
						|
        if dst_node is not None:
 | 
						|
            dst_node.location = loc
 | 
						|
            dst_node.width = min(dst_node.width, self._col_size - 20)
 | 
						|
        return loc
 | 
						|
 | 
						|
    def __init__(self, material, is_readonly=True, use_nodes=True):
 | 
						|
        self.is_readonly = is_readonly
 | 
						|
        self.material = material
 | 
						|
        if not is_readonly:
 | 
						|
            self.use_nodes = use_nodes
 | 
						|
        self.update()
 | 
						|
 | 
						|
    def update(self):  # Should be re-implemented by children classes...
 | 
						|
        for node in self.NODES_LIST:
 | 
						|
            setattr(self, node, None)
 | 
						|
        self._textures = {}
 | 
						|
        self._grid_locations = set()
 | 
						|
 | 
						|
 | 
						|
    def use_nodes_get(self):
 | 
						|
        return self.material.use_nodes
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def use_nodes_set(self, val):
 | 
						|
        self.material.use_nodes = val
 | 
						|
        self.update()
 | 
						|
 | 
						|
    use_nodes = property(use_nodes_get, use_nodes_set)
 | 
						|
 | 
						|
 | 
						|
    def node_texcoords_get(self):
 | 
						|
        if not self.use_nodes:
 | 
						|
            return None
 | 
						|
        if self._node_texcoords is ...:
 | 
						|
            # Running only once, trying to find a valid texcoords node.
 | 
						|
            for n in self.material.node_tree.nodes:
 | 
						|
                if n.bl_idname == 'ShaderNodeTexCoord':
 | 
						|
                    self._node_texcoords = n
 | 
						|
                    self._grid_to_location(0, 0, ref_node=n)
 | 
						|
                    break
 | 
						|
            if self._node_texcoords is ...:
 | 
						|
                self._node_texcoords = None
 | 
						|
        if self._node_texcoords is None and not self.is_readonly:
 | 
						|
            tree = self.material.node_tree
 | 
						|
            nodes = tree.nodes
 | 
						|
            # links = tree.links
 | 
						|
 | 
						|
            node_texcoords = nodes.new(type='ShaderNodeTexCoord')
 | 
						|
            node_texcoords.label = "Texture Coords"
 | 
						|
            self._grid_to_location(-5, 1, dst_node=node_texcoords)
 | 
						|
            self._node_texcoords = node_texcoords
 | 
						|
        return self._node_texcoords
 | 
						|
 | 
						|
    node_texcoords = property(node_texcoords_get)
 | 
						|
 | 
						|
 | 
						|
class PrincipledBSDFWrapper(ShaderWrapper):
 | 
						|
    """
 | 
						|
    Hard coded shader setup, based in Principled BSDF.
 | 
						|
    Should cover most common cases on import, and gives a basic nodal shaders support for export.
 | 
						|
    Supports basic: diffuse/spec/reflect/transparency/normal, with texturing.
 | 
						|
    """
 | 
						|
    NODES_LIST = (
 | 
						|
        "node_out",
 | 
						|
        "node_principled_bsdf",
 | 
						|
 | 
						|
        "_node_normalmap",
 | 
						|
        "_node_texcoords",
 | 
						|
    )
 | 
						|
 | 
						|
    __slots__ = (
 | 
						|
        "is_readonly",
 | 
						|
        "material",
 | 
						|
        *NODES_LIST,
 | 
						|
    )
 | 
						|
 | 
						|
    NODES_LIST = ShaderWrapper.NODES_LIST + NODES_LIST
 | 
						|
 | 
						|
    def __init__(self, material, is_readonly=True, use_nodes=True):
 | 
						|
        super(PrincipledBSDFWrapper, self).__init__(material, is_readonly, use_nodes)
 | 
						|
 | 
						|
 | 
						|
    def update(self):
 | 
						|
        super(PrincipledBSDFWrapper, self).update()
 | 
						|
 | 
						|
        if not self.use_nodes:
 | 
						|
            return
 | 
						|
 | 
						|
        tree = self.material.node_tree
 | 
						|
 | 
						|
        nodes = tree.nodes
 | 
						|
        links = tree.links
 | 
						|
 | 
						|
        # --------------------------------------------------------------------
 | 
						|
        # Main output and shader.
 | 
						|
        node_out = None
 | 
						|
        node_principled = None
 | 
						|
        for n in nodes:
 | 
						|
            if n.bl_idname == 'ShaderNodeOutputMaterial' and n.inputs[0].is_linked:
 | 
						|
                node_out = n
 | 
						|
                node_principled = n.inputs[0].links[0].from_node
 | 
						|
            elif n.bl_idname == 'ShaderNodeBsdfPrincipled' and n.outputs[0].is_linked:
 | 
						|
                node_principled = n
 | 
						|
                for lnk in n.outputs[0].links:
 | 
						|
                    node_out = lnk.to_node
 | 
						|
                    if node_out.bl_idname == 'ShaderNodeOutputMaterial':
 | 
						|
                        break
 | 
						|
            if (
 | 
						|
                    node_out is not None and node_principled is not None and
 | 
						|
                    node_out.bl_idname == 'ShaderNodeOutputMaterial' and
 | 
						|
                    node_principled.bl_idname == 'ShaderNodeBsdfPrincipled'
 | 
						|
            ):
 | 
						|
                break
 | 
						|
            node_out = node_principled = None  # Could not find a valid pair, let's try again
 | 
						|
 | 
						|
        if node_out is not None:
 | 
						|
            self._grid_to_location(0, 0, ref_node=node_out)
 | 
						|
        elif not self.is_readonly:
 | 
						|
            node_out = nodes.new(type='ShaderNodeOutputMaterial')
 | 
						|
            node_out.label = "Material Out"
 | 
						|
            node_out.target = 'ALL'
 | 
						|
            self._grid_to_location(1, 1, dst_node=node_out)
 | 
						|
        self.node_out = node_out
 | 
						|
 | 
						|
        if node_principled is not None:
 | 
						|
            self._grid_to_location(0, 0, ref_node=node_principled)
 | 
						|
        elif not self.is_readonly:
 | 
						|
            node_principled = nodes.new(type='ShaderNodeBsdfPrincipled')
 | 
						|
            node_principled.label = "Principled BSDF"
 | 
						|
            self._grid_to_location(0, 1, dst_node=node_principled)
 | 
						|
            # Link
 | 
						|
            links.new(node_principled.outputs["BSDF"], self.node_out.inputs["Surface"])
 | 
						|
        self.node_principled_bsdf = node_principled
 | 
						|
 | 
						|
        # --------------------------------------------------------------------
 | 
						|
        # Normal Map, lazy initialization...
 | 
						|
        self._node_normalmap = ...
 | 
						|
 | 
						|
        # --------------------------------------------------------------------
 | 
						|
        # Tex Coords, lazy initialization...
 | 
						|
        self._node_texcoords = ...
 | 
						|
 | 
						|
 | 
						|
    def node_normalmap_get(self):
 | 
						|
        if not self.use_nodes or self.node_principled_bsdf is None:
 | 
						|
            return None
 | 
						|
        node_principled = self.node_principled_bsdf
 | 
						|
        if self._node_normalmap is ...:
 | 
						|
            # Running only once, trying to find a valid normalmap node.
 | 
						|
            if node_principled.inputs["Normal"].is_linked:
 | 
						|
                node_normalmap = node_principled.inputs["Normal"].links[0].from_node
 | 
						|
                if node_normalmap.bl_idname == 'ShaderNodeNormalMap':
 | 
						|
                    self._node_normalmap = node_normalmap
 | 
						|
                    self._grid_to_location(0, 0, ref_node=node_normalmap)
 | 
						|
            if self._node_normalmap is ...:
 | 
						|
                self._node_normalmap = None
 | 
						|
        if self._node_normalmap is None and not self.is_readonly:
 | 
						|
            tree = self.material.node_tree
 | 
						|
            nodes = tree.nodes
 | 
						|
            links = tree.links
 | 
						|
 | 
						|
            node_normalmap = nodes.new(type='ShaderNodeNormalMap')
 | 
						|
            node_normalmap.label = "Normal/Map"
 | 
						|
            self._grid_to_location(-1, -2, dst_node=node_normalmap, ref_node=node_principled)
 | 
						|
            # Link
 | 
						|
            links.new(node_normalmap.outputs["Normal"], node_principled.inputs["Normal"])
 | 
						|
            self._node_normalmap = node_normalmap
 | 
						|
        return self._node_normalmap
 | 
						|
 | 
						|
    node_normalmap = property(node_normalmap_get)
 | 
						|
 | 
						|
 | 
						|
    # --------------------------------------------------------------------
 | 
						|
    # Base Color.
 | 
						|
 | 
						|
    def base_color_get(self):
 | 
						|
        if not self.use_nodes or self.node_principled_bsdf is None:
 | 
						|
            return self.material.diffuse_color
 | 
						|
        return rgba_to_rgb(self.node_principled_bsdf.inputs["Base Color"].default_value)
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def base_color_set(self, color):
 | 
						|
        color = rgb_to_rgba(color)
 | 
						|
        self.material.diffuse_color = color
 | 
						|
        if self.use_nodes and self.node_principled_bsdf is not None:
 | 
						|
            self.node_principled_bsdf.inputs["Base Color"].default_value = color
 | 
						|
 | 
						|
    base_color = property(base_color_get, base_color_set)
 | 
						|
 | 
						|
 | 
						|
    def base_color_texture_get(self):
 | 
						|
        if not self.use_nodes or self.node_principled_bsdf is None:
 | 
						|
            return None
 | 
						|
        return ShaderImageTextureWrapper(
 | 
						|
            self, self.node_principled_bsdf,
 | 
						|
            self.node_principled_bsdf.inputs["Base Color"],
 | 
						|
            grid_row_diff=1,
 | 
						|
        )
 | 
						|
 | 
						|
    base_color_texture = property(base_color_texture_get)
 | 
						|
 | 
						|
 | 
						|
    # --------------------------------------------------------------------
 | 
						|
    # Specular.
 | 
						|
 | 
						|
    def specular_get(self):
 | 
						|
        if not self.use_nodes or self.node_principled_bsdf is None:
 | 
						|
            return self.material.specular_intensity
 | 
						|
        return self.node_principled_bsdf.inputs["Specular"].default_value
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def specular_set(self, value):
 | 
						|
        self.material.specular_intensity = value
 | 
						|
        if self.use_nodes and self.node_principled_bsdf is not None:
 | 
						|
            self.node_principled_bsdf.inputs["Specular"].default_value = value
 | 
						|
 | 
						|
    specular = property(specular_get, specular_set)
 | 
						|
 | 
						|
 | 
						|
    def specular_tint_get(self):
 | 
						|
        if not self.use_nodes or self.node_principled_bsdf is None:
 | 
						|
            return 0.0
 | 
						|
        return self.node_principled_bsdf.inputs["Specular Tint"].default_value
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def specular_tint_set(self, value):
 | 
						|
        if self.use_nodes and self.node_principled_bsdf is not None:
 | 
						|
            self.node_principled_bsdf.inputs["Specular Tint"].default_value = value
 | 
						|
 | 
						|
    specular_tint = property(specular_tint_get, specular_tint_set)
 | 
						|
 | 
						|
 | 
						|
    # Will only be used as gray-scale one...
 | 
						|
    def specular_texture_get(self):
 | 
						|
        if not self.use_nodes or self.node_principled_bsdf is None:
 | 
						|
            print("NO NODES!")
 | 
						|
            return None
 | 
						|
        return ShaderImageTextureWrapper(
 | 
						|
            self, self.node_principled_bsdf,
 | 
						|
            self.node_principled_bsdf.inputs["Specular"],
 | 
						|
            grid_row_diff=0,
 | 
						|
        )
 | 
						|
 | 
						|
    specular_texture = property(specular_texture_get)
 | 
						|
 | 
						|
 | 
						|
    # --------------------------------------------------------------------
 | 
						|
    # Roughness (also sort of inverse of specular hardness...).
 | 
						|
 | 
						|
    def roughness_get(self):
 | 
						|
        if not self.use_nodes or self.node_principled_bsdf is None:
 | 
						|
            return self.material.roughness
 | 
						|
        return self.node_principled_bsdf.inputs["Roughness"].default_value
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def roughness_set(self, value):
 | 
						|
        self.material.roughness = value
 | 
						|
        if self.use_nodes and self.node_principled_bsdf is not None:
 | 
						|
            self.node_principled_bsdf.inputs["Roughness"].default_value = value
 | 
						|
 | 
						|
    roughness = property(roughness_get, roughness_set)
 | 
						|
 | 
						|
 | 
						|
    # Will only be used as gray-scale one...
 | 
						|
    def roughness_texture_get(self):
 | 
						|
        if not self.use_nodes or self.node_principled_bsdf is None:
 | 
						|
            return None
 | 
						|
        return ShaderImageTextureWrapper(
 | 
						|
            self, self.node_principled_bsdf,
 | 
						|
            self.node_principled_bsdf.inputs["Roughness"],
 | 
						|
            grid_row_diff=0,
 | 
						|
        )
 | 
						|
 | 
						|
    roughness_texture = property(roughness_texture_get)
 | 
						|
 | 
						|
 | 
						|
    # --------------------------------------------------------------------
 | 
						|
    # Metallic (a.k.a reflection, mirror).
 | 
						|
 | 
						|
    def metallic_get(self):
 | 
						|
        if not self.use_nodes or self.node_principled_bsdf is None:
 | 
						|
            return self.material.metallic
 | 
						|
        return self.node_principled_bsdf.inputs["Metallic"].default_value
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def metallic_set(self, value):
 | 
						|
        self.material.metallic = value
 | 
						|
        if self.use_nodes and self.node_principled_bsdf is not None:
 | 
						|
            self.node_principled_bsdf.inputs["Metallic"].default_value = value
 | 
						|
 | 
						|
    metallic = property(metallic_get, metallic_set)
 | 
						|
 | 
						|
 | 
						|
    # Will only be used as gray-scale one...
 | 
						|
    def metallic_texture_get(self):
 | 
						|
        if not self.use_nodes or self.node_principled_bsdf is None:
 | 
						|
            return None
 | 
						|
        return ShaderImageTextureWrapper(
 | 
						|
            self, self.node_principled_bsdf,
 | 
						|
            self.node_principled_bsdf.inputs["Metallic"],
 | 
						|
            grid_row_diff=0,
 | 
						|
        )
 | 
						|
 | 
						|
    metallic_texture = property(metallic_texture_get)
 | 
						|
 | 
						|
 | 
						|
    # --------------------------------------------------------------------
 | 
						|
    # Transparency settings.
 | 
						|
 | 
						|
    def ior_get(self):
 | 
						|
        if not self.use_nodes or self.node_principled_bsdf is None:
 | 
						|
            return 1.0
 | 
						|
        return self.node_principled_bsdf.inputs["IOR"].default_value
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def ior_set(self, value):
 | 
						|
        if self.use_nodes and self.node_principled_bsdf is not None:
 | 
						|
            self.node_principled_bsdf.inputs["IOR"].default_value = value
 | 
						|
 | 
						|
    ior = property(ior_get, ior_set)
 | 
						|
 | 
						|
 | 
						|
    # Will only be used as gray-scale one...
 | 
						|
    def ior_texture_get(self):
 | 
						|
        if not self.use_nodes or self.node_principled_bsdf is None:
 | 
						|
            return None
 | 
						|
        return ShaderImageTextureWrapper(
 | 
						|
            self, self.node_principled_bsdf,
 | 
						|
            self.node_principled_bsdf.inputs["IOR"],
 | 
						|
            grid_row_diff=-1,
 | 
						|
        )
 | 
						|
 | 
						|
    ior_texture = property(ior_texture_get)
 | 
						|
 | 
						|
 | 
						|
    def transmission_get(self):
 | 
						|
        if not self.use_nodes or self.node_principled_bsdf is None:
 | 
						|
            return 0.0
 | 
						|
        return self.node_principled_bsdf.inputs["Transmission"].default_value
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def transmission_set(self, value):
 | 
						|
        if self.use_nodes and self.node_principled_bsdf is not None:
 | 
						|
            self.node_principled_bsdf.inputs["Transmission"].default_value = value
 | 
						|
 | 
						|
    transmission = property(transmission_get, transmission_set)
 | 
						|
 | 
						|
 | 
						|
    # Will only be used as gray-scale one...
 | 
						|
    def transmission_texture_get(self):
 | 
						|
        if not self.use_nodes or self.node_principled_bsdf is None:
 | 
						|
            return None
 | 
						|
        return ShaderImageTextureWrapper(
 | 
						|
            self, self.node_principled_bsdf,
 | 
						|
            self.node_principled_bsdf.inputs["Transmission"],
 | 
						|
            grid_row_diff=-1,
 | 
						|
        )
 | 
						|
 | 
						|
    transmission_texture = property(transmission_texture_get)
 | 
						|
 | 
						|
 | 
						|
    def alpha_get(self):
 | 
						|
        if not self.use_nodes or self.node_principled_bsdf is None:
 | 
						|
            return 1.0
 | 
						|
        return self.node_principled_bsdf.inputs["Alpha"].default_value
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def alpha_set(self, value):
 | 
						|
        if self.use_nodes and self.node_principled_bsdf is not None:
 | 
						|
            self.node_principled_bsdf.inputs["Alpha"].default_value = value
 | 
						|
 | 
						|
    alpha = property(alpha_get, alpha_set)
 | 
						|
 | 
						|
 | 
						|
    # Will only be used as gray-scale one...
 | 
						|
    def alpha_texture_get(self):
 | 
						|
        if not self.use_nodes or self.node_principled_bsdf is None:
 | 
						|
            return None
 | 
						|
        return ShaderImageTextureWrapper(
 | 
						|
            self, self.node_principled_bsdf,
 | 
						|
            self.node_principled_bsdf.inputs["Alpha"],
 | 
						|
            grid_row_diff=-1,
 | 
						|
        )
 | 
						|
 | 
						|
    alpha_texture = property(alpha_texture_get)
 | 
						|
 | 
						|
 | 
						|
 | 
						|
    # --------------------------------------------------------------------
 | 
						|
    # Normal map.
 | 
						|
 | 
						|
    def normalmap_strength_get(self):
 | 
						|
        if not self.use_nodes or self.node_normalmap is None:
 | 
						|
            return 0.0
 | 
						|
        return self.node_normalmap.inputs["Strength"].default_value
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def normalmap_strength_set(self, value):
 | 
						|
        if self.use_nodes and self.node_normalmap is not None:
 | 
						|
            self.node_normalmap.inputs["Strength"].default_value = value
 | 
						|
 | 
						|
    normalmap_strength = property(normalmap_strength_get, normalmap_strength_set)
 | 
						|
 | 
						|
 | 
						|
    def normalmap_texture_get(self):
 | 
						|
        if not self.use_nodes or self.node_normalmap is None:
 | 
						|
            return None
 | 
						|
        return ShaderImageTextureWrapper(
 | 
						|
            self, self.node_normalmap,
 | 
						|
            self.node_normalmap.inputs["Color"],
 | 
						|
            grid_row_diff=-2,
 | 
						|
            colorspace_is_data=True,
 | 
						|
        )
 | 
						|
 | 
						|
    normalmap_texture = property(normalmap_texture_get)
 | 
						|
 | 
						|
class ShaderImageTextureWrapper():
 | 
						|
    """
 | 
						|
    Generic 'image texture'-like wrapper, handling image node, some mapping (texture coordinates transformations),
 | 
						|
    and texture coordinates source.
 | 
						|
    """
 | 
						|
 | 
						|
    # Note: this class assumes we are using nodes, otherwise it should never be used...
 | 
						|
 | 
						|
    NODES_LIST = (
 | 
						|
        "node_dst",
 | 
						|
        "socket_dst",
 | 
						|
 | 
						|
        "_node_image",
 | 
						|
        "_node_mapping",
 | 
						|
    )
 | 
						|
 | 
						|
    __slots__ = (
 | 
						|
        "owner_shader",
 | 
						|
        "is_readonly",
 | 
						|
        "grid_row_diff",
 | 
						|
        "use_alpha",
 | 
						|
        "colorspace_is_data",
 | 
						|
        *NODES_LIST,
 | 
						|
    )
 | 
						|
 | 
						|
    def __new__(cls, owner_shader: ShaderWrapper, node_dst, socket_dst, *_args, **_kwargs):
 | 
						|
        instance = owner_shader._textures.get((node_dst, socket_dst), None)
 | 
						|
        if instance is not None:
 | 
						|
            return instance
 | 
						|
        instance = super(ShaderImageTextureWrapper, cls).__new__(cls)
 | 
						|
        owner_shader._textures[(node_dst, socket_dst)] = instance
 | 
						|
        return instance
 | 
						|
 | 
						|
    def __init__(self, owner_shader: ShaderWrapper, node_dst, socket_dst, grid_row_diff=0,
 | 
						|
                 use_alpha=False, colorspace_is_data=...):
 | 
						|
        self.owner_shader = owner_shader
 | 
						|
        self.is_readonly = owner_shader.is_readonly
 | 
						|
        self.node_dst = node_dst
 | 
						|
        self.socket_dst = socket_dst
 | 
						|
        self.grid_row_diff = grid_row_diff
 | 
						|
        self.use_alpha = use_alpha
 | 
						|
        self.colorspace_is_data = colorspace_is_data
 | 
						|
 | 
						|
        self._node_image = ...
 | 
						|
        self._node_mapping = ...
 | 
						|
 | 
						|
        # tree = node_dst.id_data
 | 
						|
        # nodes = tree.nodes
 | 
						|
        # links = tree.links
 | 
						|
 | 
						|
        if socket_dst.is_linked:
 | 
						|
            from_node = socket_dst.links[0].from_node
 | 
						|
            if from_node.bl_idname == 'ShaderNodeTexImage':
 | 
						|
                self._node_image = from_node
 | 
						|
 | 
						|
        if self.node_image is not None:
 | 
						|
            socket_dst = self.node_image.inputs["Vector"]
 | 
						|
            if socket_dst.is_linked:
 | 
						|
                from_node = socket_dst.links[0].from_node
 | 
						|
                if from_node.bl_idname == 'ShaderNodeMapping':
 | 
						|
                    self._node_mapping = from_node
 | 
						|
 | 
						|
 | 
						|
    def copy_from(self, tex):
 | 
						|
        # Avoid generating any node in source texture.
 | 
						|
        is_readonly_back = tex.is_readonly
 | 
						|
        tex.is_readonly = True
 | 
						|
 | 
						|
        if tex.node_image is not None:
 | 
						|
            self.image = tex.image
 | 
						|
            self.projection = tex.projection
 | 
						|
            self.texcoords = tex.texcoords
 | 
						|
            self.copy_mapping_from(tex)
 | 
						|
 | 
						|
        tex.is_readonly = is_readonly_back
 | 
						|
 | 
						|
 | 
						|
    def copy_mapping_from(self, tex):
 | 
						|
        # Avoid generating any node in source texture.
 | 
						|
        is_readonly_back = tex.is_readonly
 | 
						|
        tex.is_readonly = True
 | 
						|
 | 
						|
        if tex.node_mapping is None:  # Used to actually remove mapping node.
 | 
						|
            if self.has_mapping_node():
 | 
						|
                # We assume node_image can never be None in that case...
 | 
						|
                # Find potential existing link into image's Vector input.
 | 
						|
                socket_dst = socket_src = None
 | 
						|
                if self.node_mapping.inputs["Vector"].is_linked:
 | 
						|
                    socket_dst = self.node_image.inputs["Vector"]
 | 
						|
                    socket_src = self.node_mapping.inputs["Vector"].links[0].from_socket
 | 
						|
 | 
						|
                tree = self.owner_shader.material.node_tree
 | 
						|
                tree.nodes.remove(self.node_mapping)
 | 
						|
                self._node_mapping = None
 | 
						|
 | 
						|
                # If previously existing, re-link texcoords -> image
 | 
						|
                if socket_src is not None:
 | 
						|
                    tree.links.new(socket_src, socket_dst)
 | 
						|
        elif self.node_mapping is not None:
 | 
						|
            self.translation = tex.translation
 | 
						|
            self.rotation = tex.rotation
 | 
						|
            self.scale = tex.scale
 | 
						|
            self.use_min = tex.use_min
 | 
						|
            self.use_max = tex.use_max
 | 
						|
            self.min = tex.min
 | 
						|
            self.max = tex.max
 | 
						|
 | 
						|
        tex.is_readonly = is_readonly_back
 | 
						|
 | 
						|
 | 
						|
    # --------------------------------------------------------------------
 | 
						|
    # Image.
 | 
						|
 | 
						|
    def node_image_get(self):
 | 
						|
        if self._node_image is ...:
 | 
						|
            # Running only once, trying to find a valid image node.
 | 
						|
            if self.socket_dst.is_linked:
 | 
						|
                node_image = self.socket_dst.links[0].from_node
 | 
						|
                if node_image.bl_idname == 'ShaderNodeTexImage':
 | 
						|
                    self._node_image = node_image
 | 
						|
                    self.owner_shader._grid_to_location(0, 0, ref_node=node_image)
 | 
						|
            if self._node_image is ...:
 | 
						|
                self._node_image = None
 | 
						|
        if self._node_image is None and not self.is_readonly:
 | 
						|
            tree = self.owner_shader.material.node_tree
 | 
						|
 | 
						|
            node_image = tree.nodes.new(type='ShaderNodeTexImage')
 | 
						|
            self.owner_shader._grid_to_location(-1, 0 + self.grid_row_diff, dst_node=node_image, ref_node=self.node_dst)
 | 
						|
 | 
						|
            tree.links.new(node_image.outputs["Alpha" if self.use_alpha else "Color"], self.socket_dst)
 | 
						|
 | 
						|
            self._node_image = node_image
 | 
						|
        return self._node_image
 | 
						|
 | 
						|
    node_image = property(node_image_get)
 | 
						|
 | 
						|
 | 
						|
    def image_get(self):
 | 
						|
        return self.node_image.image if self.node_image is not None else None
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def image_set(self, image):
 | 
						|
        if self.colorspace_is_data is not ...:
 | 
						|
            image.colorspace_settings.is_data = self.colorspace_is_data
 | 
						|
        self.node_image.image = image
 | 
						|
 | 
						|
    image = property(image_get, image_set)
 | 
						|
 | 
						|
 | 
						|
    def projection_get(self):
 | 
						|
        return self.node_image.projection if self.node_image is not None else 'FLAT'
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def projection_set(self, projection):
 | 
						|
        self.node_image.projection = projection
 | 
						|
 | 
						|
    projection = property(projection_get, projection_set)
 | 
						|
 | 
						|
 | 
						|
    def texcoords_get(self):
 | 
						|
        if self.node_image is not None:
 | 
						|
            socket = (self.node_mapping if self.has_mapping_node() else self.node_image).inputs["Vector"]
 | 
						|
            if socket.is_linked:
 | 
						|
                return socket.links[0].from_socket.name
 | 
						|
        return 'UV'
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def texcoords_set(self, texcoords):
 | 
						|
        # Image texture node already defaults to UVs, no extra node needed.
 | 
						|
        # ONLY in case we do not have any texcoords mapping!!!
 | 
						|
        if texcoords == 'UV' and not self.has_mapping_node():
 | 
						|
            return
 | 
						|
        tree = self.node_image.id_data
 | 
						|
        links = tree.links
 | 
						|
        node_dst = self.node_mapping if self.has_mapping_node() else self.node_image
 | 
						|
        socket_src = self.owner_shader.node_texcoords.outputs[texcoords]
 | 
						|
        links.new(socket_src, node_dst.inputs["Vector"])
 | 
						|
 | 
						|
    texcoords = property(texcoords_get, texcoords_set)
 | 
						|
 | 
						|
 | 
						|
    def extension_get(self):
 | 
						|
        return self.node_image.extension if self.node_image is not None else 'REPEAT'
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def extension_set(self, extension):
 | 
						|
        self.node_image.extension = extension
 | 
						|
 | 
						|
    extension = property(extension_get, extension_set)
 | 
						|
 | 
						|
 | 
						|
    # --------------------------------------------------------------------
 | 
						|
    # Mapping.
 | 
						|
 | 
						|
    def has_mapping_node(self):
 | 
						|
        return self._node_mapping not in {None, ...}
 | 
						|
 | 
						|
    def node_mapping_get(self):
 | 
						|
        if self._node_mapping is ...:
 | 
						|
            # Running only once, trying to find a valid mapping node.
 | 
						|
            if self.node_image is None:
 | 
						|
                return None
 | 
						|
            if self.node_image.inputs["Vector"].is_linked:
 | 
						|
                node_mapping = self.node_image.inputs["Vector"].links[0].from_node
 | 
						|
                if node_mapping.bl_idname == 'ShaderNodeMapping':
 | 
						|
                    self._node_mapping = node_mapping
 | 
						|
                    self.owner_shader._grid_to_location(0, 0 + self.grid_row_diff, ref_node=node_mapping)
 | 
						|
            if self._node_mapping is ...:
 | 
						|
                self._node_mapping = None
 | 
						|
        if self._node_mapping is None and not self.is_readonly:
 | 
						|
            # Find potential existing link into image's Vector input.
 | 
						|
            socket_dst = self.node_image.inputs["Vector"]
 | 
						|
            # If not already existing, we need to create texcoords -> mapping link (from UV).
 | 
						|
            socket_src = (socket_dst.links[0].from_socket if socket_dst.is_linked
 | 
						|
                                                          else self.owner_shader.node_texcoords.outputs['UV'])
 | 
						|
 | 
						|
            tree = self.owner_shader.material.node_tree
 | 
						|
            node_mapping = tree.nodes.new(type='ShaderNodeMapping')
 | 
						|
            node_mapping.vector_type = 'TEXTURE'
 | 
						|
            self.owner_shader._grid_to_location(-1, 0, dst_node=node_mapping, ref_node=self.node_image)
 | 
						|
 | 
						|
            # Link mapping -> image node.
 | 
						|
            tree.links.new(node_mapping.outputs["Vector"], socket_dst)
 | 
						|
            # Link texcoords -> mapping.
 | 
						|
            tree.links.new(socket_src, node_mapping.inputs["Vector"])
 | 
						|
 | 
						|
            self._node_mapping = node_mapping
 | 
						|
        return self._node_mapping
 | 
						|
 | 
						|
    node_mapping = property(node_mapping_get)
 | 
						|
 | 
						|
 | 
						|
    def translation_get(self):
 | 
						|
        return self.node_mapping.translation if self.node_mapping is not None else Vector((0.0, 0.0, 0.0))
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def translation_set(self, translation):
 | 
						|
        self.node_mapping.translation = translation
 | 
						|
 | 
						|
    translation = property(translation_get, translation_set)
 | 
						|
 | 
						|
 | 
						|
    def rotation_get(self):
 | 
						|
        return self.node_mapping.rotation if self.node_mapping is not None else Vector((0.0, 0.0, 0.0))
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def rotation_set(self, rotation):
 | 
						|
        self.node_mapping.rotation = rotation
 | 
						|
 | 
						|
    rotation = property(rotation_get, rotation_set)
 | 
						|
 | 
						|
 | 
						|
    def scale_get(self):
 | 
						|
        return self.node_mapping.scale if self.node_mapping is not None else Vector((1.0, 1.0, 1.0))
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def scale_set(self, scale):
 | 
						|
        self.node_mapping.scale = scale
 | 
						|
 | 
						|
    scale = property(scale_get, scale_set)
 | 
						|
 | 
						|
 | 
						|
    def use_min_get(self):
 | 
						|
        return self.node_mapping.use_min if self.node_mapping is not None else False
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def use_min_set(self, use_min):
 | 
						|
        self.node_mapping.use_min = use_min
 | 
						|
 | 
						|
    use_min = property(use_min_get, use_min_set)
 | 
						|
 | 
						|
 | 
						|
    def use_max_get(self):
 | 
						|
        return self.node_mapping.use_max if self.node_mapping is not None else False
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def use_max_set(self, use_max):
 | 
						|
        self.node_mapping.use_max = use_max
 | 
						|
 | 
						|
    use_max = property(use_max_get, use_max_set)
 | 
						|
 | 
						|
 | 
						|
    def min_get(self):
 | 
						|
        return self.node_mapping.min if self.node_mapping is not None else Vector((0.0, 0.0, 0.0))
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def min_set(self, min):
 | 
						|
        self.node_mapping.min = min
 | 
						|
 | 
						|
    min = property(min_get, min_set)
 | 
						|
 | 
						|
 | 
						|
    def max_get(self):
 | 
						|
        return self.node_mapping.max if self.node_mapping is not None else Vector((0.0, 0.0, 0.0))
 | 
						|
 | 
						|
    @_set_check
 | 
						|
    def max_set(self, max):
 | 
						|
        self.node_mapping.max = max
 | 
						|
 | 
						|
    max = property(max_get, max_set)
 |