-
Aaron Carlisle authoredAaron Carlisle authored
cycles_shader_compat.py 19.43 KiB
# ##### 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_hard",
"node_mix_color_refl",
"node_mix_color_bump",
"node_normalmap",
"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
del node
# Link
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
self.node_normalmap = node
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!
@staticmethod
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"],
socket)
return node
@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'])
node_map = None
# 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.vector_type = 'TEXTURE'
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):
self.node_bsdf_alpha.mute = False
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 grayscale 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):
# XXX, remove?
tree = self.node_mix_color_diff.id_data
links = tree.links
self.node_bsdf_alpha.mute = False
node_image = self.node_image_diff
node = self.node_mix_color_alpha
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 = self.node_normalmap
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")