Files
blender-portable-repo/scripts/addons/poliigon-addon-blender/material_import_utils_nodes.py
T
2026-03-17 14:30:01 -06:00

769 lines
20 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 #####
import mathutils
from typing import Optional
import bpy
from .material_import_utils import (
load_poliigon_node_group,
set_value)
def create_node(
group: bpy.types.Node,
bl_idname: str,
parent: Optional[bpy.types.Node],
name: Optional[str] = None,
location: Optional[mathutils.Vector] = None,
width: Optional[float] = None,
height: Optional[float] = None,
select: bool = False,
hide: bool = False
) -> bpy.types.Node:
"""Creates an arbitrary node of type bl_idname."""
node = group.node_tree.nodes.new(bl_idname)
if name is not None:
node.label = name
node.name = name
if parent is not None:
node.parent = parent
if location is not None:
node.location = location
if width is not None:
node.width = width
if height is not None:
node.height = height
node.select = select
node.hide = hide
return node
def create_node_socket(
node_group: bpy.types.Node,
*,
socket_type: str = "NodeSocketVector",
in_out: str = "INPUT",
name: str = "Vector",
description: str = ""
) -> None:
"""Creates a new input or output socket on a group node."""
if bpy.app.version >= (4, 0):
node_group.node_tree.interface.new_socket(
name,
description=description,
in_out=in_out,
socket_type=socket_type,
parent=None
)
elif bpy.app.version >= (3, 4):
if in_out == "INPUT":
node_group.node_tree.inputs.new(socket_type, name)
else:
node_group.node_tree.outputs.new(socket_type, name)
else:
if in_out == "INPUT":
node_group.inputs.new(socket_type, name)
else:
node_group.outputs.new(socket_type, name)
def create_frame(
group: bpy.types.Node,
parent: Optional[bpy.types.Node],
*,
name: Optional[str] = None,
location: Optional[mathutils.Vector] = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False
) -> bpy.types.Node:
"""Creates an arbitrary node of type bl_idname."""
frame = create_node(
group=group,
bl_idname="NodeFrame",
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
return frame
def create_add_shader_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
name: Optional[str] = None,
location: Optional[mathutils.Vector] = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False
) -> bpy.types.Node:
"""Creates an 'Add Shader' node."""
node_add_shader = create_node(
group=group,
bl_idname="ShaderNodeAddShader",
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
return node_add_shader
def create_color_invert_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
factor: Optional[float] = 1.0,
name: Optional[str] = None,
location: Optional[mathutils.Vector] = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False
) -> bpy.types.Node:
"""Creates an 'Color Invert' node."""
node_invert_color = create_node(
group=group,
bl_idname="ShaderNodeInvert",
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
if factor is not None:
set_value(
node=node_invert_color,
sock_name="Fac",
sock_bl_idname_expected="NodeSocketFloatFactor",
value=factor)
return node_invert_color
def create_combine_xyz_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
value_x: Optional[float] = 1.0,
value_y: Optional[float] = 1.0,
value_z: Optional[float] = 1.0,
name: Optional[str] = None,
location: Optional[mathutils.Vector] = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False
) -> bpy.types.Node:
"""Creates an 'Combine XYZ' node."""
node_combine_xyz = create_node(
group=group,
bl_idname="ShaderNodeCombineXYZ",
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
if value_x is not None:
set_value(
node=node_combine_xyz,
sock_name="X",
sock_bl_idname_expected="NodeSocketFloat",
value=value_x)
if value_y is not None:
set_value(
node=node_combine_xyz,
sock_name="Y",
sock_bl_idname_expected="NodeSocketFloat",
value=value_y)
if value_z is not None:
set_value(
node=node_combine_xyz,
sock_name="Z",
sock_bl_idname_expected="NodeSocketFloat",
value=value_z)
return node_combine_xyz
def create_displacement_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
midlevel: Optional[float] = 0.0,
scale: Optional[float] = 0.0,
name: Optional[str] = None,
location: Optional[mathutils.Vector] = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False
) -> bpy.types.Node:
"""Creates a 'Displacement' node."""
node_displacement = create_node(
group=group,
bl_idname="ShaderNodeDisplacement",
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
if midlevel is not None:
set_value(
node=node_displacement,
sock_name="Midlevel",
sock_bl_idname_expected="NodeSocketFloat",
value=midlevel)
if scale is not None:
set_value(
node=node_displacement,
sock_name="Scale",
sock_bl_idname_expected="NodeSocketFloat",
value=scale)
return node_displacement
def create_fresnel_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
ior: Optional[float] = 1.150,
name: Optional[str] = None,
location: Optional[mathutils.Vector] = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False
) -> bpy.types.Node:
"""Creates a 'Fresnel' node."""
node_fresnel = create_node(
group=group,
bl_idname="ShaderNodeFresnel",
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
if ior is not None:
set_value(
node=node_fresnel,
sock_name="IOR",
sock_bl_idname_expected="NodeSocketFloat",
value=ior)
return node_fresnel
def create_group_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
node_tree: Optional[bpy.types.NodeTree] = None,
name: Optional[str] = None,
location: Optional[mathutils.Vector] = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False
) -> bpy.types.Node:
"""Creates a 'Group' node."""
node_group = create_node(
group=group,
bl_idname="ShaderNodeGroup",
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
if node_tree is not None:
node_group.node_tree = node_tree
else:
node_tree = bpy.data.node_groups.new(name, "ShaderNodeTree")
node_group.node_tree = node_tree
node_inputs = node_group.node_tree.nodes.new("NodeGroupInput")
node_inputs.select = False
node_outputs = node_group.node_tree.nodes.new("NodeGroupOutput")
node_outputs.select = False
return node_group
def create_mapping_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
scale: float = 1.0,
name: Optional[str] = None,
location: mathutils.Vector = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False,
) -> bpy.types.Node:
"""Creates a 'Mapping' node."""
node_mapping = create_node(
group=group,
bl_idname="ShaderNodeMapping",
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
set_value(
node=node_mapping,
sock_name="Scale",
sock_bl_idname_expected="NodeSocketVectorXYZ",
value=[scale] * 3)
return node_mapping
def create_math_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
operation: Optional[str] = "MULTIPLY",
use_clamp: Optional[bool] = True,
value1: Optional[float] = None,
value2: Optional[float] = None,
name: Optional[str] = None,
location: mathutils.Vector = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False,
) -> bpy.types.Node:
"""Creates a 'Math' node."""
node_math = create_node(
group=group,
bl_idname="ShaderNodeMath",
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
if operation is not None:
node_math.operation = operation
if use_clamp is not None:
node_math.use_clamp = use_clamp
if value1 is not None:
set_value(
node=node_math,
sock_name="A",
sock_bl_idname_expected="NodeSocketFloat",
value=value1,
# Math node's input ports have identical names
allow_index=True)
if value2 is not None:
set_value(
node=node_math,
sock_name="B",
sock_bl_idname_expected="NodeSocketFloat",
value=value2,
# Math node's input ports have identical names
allow_index=True)
return node_math
def create_mix_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
data_type: Optional[str] = "RGBA",
use_clamp: Optional[bool] = True,
clamp_result: Optional[bool] = False,
blend_type: Optional[str] = "MULTIPLY",
blend_factor: Optional[float] = None,
name: Optional[str] = None,
location: mathutils.Vector = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False
) -> bpy.types.Node:
"""Creates a 'Mix' (or 'MixRGB') node."""
if bpy.app.version >= (3, 4):
bl_idname = "ShaderNodeMix"
else:
bl_idname = "ShaderNodeMixRGB"
node_mix = create_node(
group=group,
bl_idname=bl_idname,
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
if data_type is not None:
if bpy.app.version >= (3, 4):
node_mix.data_type = "RGBA"
if use_clamp is not None:
if bpy.app.version >= (3, 4):
node_mix.clamp_factor = use_clamp
else:
node_mix.use_clamp = use_clamp
if clamp_result is not None:
if bpy.app.version >= (3, 4):
node_mix.clamp_result = clamp_result
if blend_type is not None:
node_mix.blend_type = blend_type
if blend_factor is not None:
set_value(
node=node_mix,
sock_name="Factor",
sock_bl_idname_expected="NodeSocketFloatFactor",
value=blend_factor)
return node_mix
def create_mix_shader_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
name: Optional[str] = None,
location: Optional[mathutils.Vector] = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False
) -> bpy.types.Node:
"""Creates a 'Mix Shader' node."""
node_mix_shader = create_node(
group=group,
bl_idname="ShaderNodeMixShader",
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
return node_mix_shader
def create_mosaic_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
scale: float = 1.0,
name: Optional[str] = None,
location: Optional[mathutils.Vector] = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False
) -> bpy.types.Node:
"""Creates a 'Poliigon Mosaic' node group."""
node_group_mosaic = load_poliigon_node_group(
"Mosaic_UV_Mapping")
if name is None:
name = node_group_mosaic.name
node_mosaic = create_group_node(
group=group,
parent=parent,
node_tree=node_group_mosaic,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
set_value(
node=node_mosaic,
sock_name="Scale",
sock_bl_idname_expected="NodeSocketFloat",
value=scale)
return node_mosaic
def create_normal_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
space: Optional[str] = "TANGENT",
strength: Optional[float] = None,
name: Optional[str] = None,
location: Optional[mathutils.Vector] = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False
) -> bpy.types.Node:
"""Creates a 'Normal' node."""
node_normal = create_node(
group=group,
bl_idname="ShaderNodeNormalMap",
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
if space is not None:
node_normal.space = space
if strength is not None:
set_value(
node=node_normal,
sock_name="Strength",
sock_bl_idname_expected="NodeSocketFloat",
value=strength)
return node_normal
def create_texture_coordinate_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
name: Optional[str] = None,
location: Optional[mathutils.Vector] = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False
) -> bpy.types.Node:
"""Creates a 'Texture Coordinate' node."""
node_tex_coord = create_node(
group=group,
bl_idname="ShaderNodeTexCoord",
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
return node_tex_coord
def create_transparent_bsdf_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
name: Optional[str] = None,
location: Optional[mathutils.Vector] = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False
) -> bpy.types.Node:
"""Creates a 'Transparent BSDF' node."""
node_transparent_bsdf = create_node(
group=group,
bl_idname="ShaderNodeBsdfTransparent",
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
return node_transparent_bsdf
def create_translucent_bsdf_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
name: Optional[str] = None,
location: Optional[mathutils.Vector] = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False
) -> bpy.types.Node:
"""Creates a 'Translucent BSDF' node."""
node_translucent_bsdf = create_node(
group=group,
bl_idname="ShaderNodeBsdfTranslucent",
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
return node_translucent_bsdf
def create_value_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
value: Optional[float] = 0.0,
name: Optional[str] = None,
location: Optional[mathutils.Vector] = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False
) -> bpy.types.Node:
"""Creates a 'Value' node."""
node_value = create_node(
group=group,
bl_idname="ShaderNodeValue",
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
if value is not None:
# Note: This is an output being set.
# set_value() is only for inputs!
node_value.outputs[0].default_value = value
return node_value
def create_vector_math_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
operation: str = "MULTIPLY",
value1: Optional[mathutils.Vector] = None,
value2: Optional[mathutils.Vector] = None,
name: Optional[str] = None,
location: Optional[mathutils.Vector] = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False
) -> bpy.types.Node:
"""Creates a 'Vector Math' node."""
node_vector_math = create_node(
group=group,
bl_idname="ShaderNodeVectorMath",
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
if operation is not None:
node_vector_math.operation = operation
if value1 is not None:
set_value(
node=node_vector_math,
sock_name="A",
sock_bl_idname_expected="NodeSocketVector",
value=value1,
# Vector Math node's input ports have identical names
allow_index=True)
if value2 is not None:
set_value(
node=node_vector_math,
sock_name="B",
sock_bl_idname_expected="NodeSocketVector",
value=value2,
# Vector Math node's input ports have identical names
allow_index=True)
return node_vector_math
def create_vector_rotate_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
angle_rad: Optional[mathutils.Vector] = None,
name: Optional[str] = None,
location: Optional[mathutils.Vector] = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False
) -> bpy.types.Node:
"""Creates a 'Vector Rotate' node."""
node_vector_rotate = create_node(
group=group,
bl_idname="ShaderNodeVectorRotate",
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
if angle_rad is not None:
set_value(
node=node_vector_rotate,
sock_name="Angle",
sock_bl_idname_expected="NodeSocketVector",
value=angle_rad)
return node_vector_rotate
def create_volume_absorption_node(
group: bpy.types.Node,
parent: Optional[bpy.types.Node] = None,
*,
density: Optional[float] = 100.0,
name: Optional[str] = None,
location: Optional[mathutils.Vector] = None,
width: Optional[float] = None,
height: Optional[float] = None,
hide: bool = False
) -> bpy.types.Node:
"""Creates a 'Volume Absorption' node."""
node_vol_abs = create_node(
group=group,
bl_idname="ShaderNodeVolumeAbsorption",
parent=parent,
name=name,
location=location,
width=width,
height=height,
hide=hide
)
if density is not None:
set_value(
node=node_vol_abs,
sock_name="Density",
sock_bl_idname_expected="NodeSocketFloat",
value=density)
return node_vol_abs