from typing import Literal, NamedTuple
import numpy as np
from procfunc import types as pt
from procfunc.nodes import types as nt
from procfunc.nodes.util.bindings_util import ContextualNode
from procfunc.nodes.util.bpy_node_info import NodeDataType
TColorMixType = Literal[
"MIX",
"DARKEN",
"MULTIPLY",
"BURN",
"LIGHTEN",
"SCREEN",
"DODGE",
"ADD",
"OVERLAY",
"SOFT_LIGHT",
"LINEAR_LIGHT",
"DIFFERENCE",
"EXCLUSION",
"SUBTRACT",
"DIVIDE",
"HUE",
"SATURATION",
"COLOR",
"VALUE",
]
[docs]
def mix_rgb(
factor: nt.SocketOrVal[float],
a: nt.SocketOrVal[pt.Color],
b: nt.SocketOrVal[pt.Color],
blend_type: TColorMixType = "MIX",
clamp_result: bool = False,
clamp_factor: bool = True,
) -> nt.ProcNode[pt.Color]:
"""
Uses a Mix Node with datatype Color
NOTE: separated from float/vector mix() due to extra arguments
See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/color/mix.html
"""
return nt.ProcNode.from_nodetype(
node_type=ContextualNode.MIX_RGB.value,
inputs={"Factor": factor, "A": a, "B": b},
attrs={
"blend_type": blend_type,
"clamp_result": clamp_result,
"clamp_factor": clamp_factor,
"data_type": NodeDataType.RGBA,
},
)
[docs]
def rgb_curve(
fac: nt.SocketOrVal[float],
color: nt.SocketOrVal[pt.Color],
curves: list[np.ndarray] | np.ndarray | None = None,
) -> nt.ProcNode:
"""
Uses a RGBCurve Shader Node.
See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/color/rgb_curves.html
"""
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeRGBCurve",
inputs={"Fac": fac, "Color": color},
attrs={"curves": curves},
)
[docs]
def combine_rgb(
red: nt.SocketOrVal[float] = 0.0,
green: nt.SocketOrVal[float] = 0.0,
blue: nt.SocketOrVal[float] = 0.0,
) -> nt.ProcNode[pt.Color]:
"""
Uses a CombineColor Shader Node.
See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/converter/combine_color.html
"""
return nt.ProcNode.from_nodetype(
node_type=ContextualNode.COMBINE_COLOR.value,
inputs={"Red": red, "Green": green, "Blue": blue},
attrs={"mode": "RGB"},
)
[docs]
def combine_hsv(
hue: nt.SocketOrVal[float] = 0.0,
saturation: nt.SocketOrVal[float] = 0.0,
value: nt.SocketOrVal[float] = 0.0,
) -> nt.ProcNode[pt.Color]:
"""
Uses a CombineColor Shader Node.
See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/converter/combine_color.html
"""
# Blender socket identifiers stay as Red/Green/Blue regardless of mode
return nt.ProcNode.from_nodetype(
node_type=ContextualNode.COMBINE_COLOR.value,
inputs={"Red": hue, "Green": saturation, "Blue": value},
attrs={"mode": "HSV"},
)
[docs]
def combine_hsl(
hue: nt.SocketOrVal[float] = 0.0,
saturation: nt.SocketOrVal[float] = 0.0,
lightness: nt.SocketOrVal[float] = 0.0,
) -> nt.ProcNode[pt.Color]:
"""
Uses a CombineHSV Shader Node.
See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/converter/combine_color.html
"""
# Blender socket identifiers stay as Red/Green/Blue regardless of mode
return nt.ProcNode.from_nodetype(
node_type=ContextualNode.COMBINE_COLOR.value,
inputs={"Red": hue, "Green": saturation, "Blue": lightness},
attrs={"mode": "HSL"},
)
[docs]
class SeparateRgbResult(NamedTuple):
red: nt.ProcNode[float]
green: nt.ProcNode[float]
blue: nt.ProcNode[float]
alpha: nt.ProcNode[float]
[docs]
def separate_rgb(
color: nt.SocketOrVal[pt.Color] = (0.8, 0.8, 0.8, 1),
) -> SeparateRgbResult:
"""
Uses a SeparateColor Shader Node in RGB mode.
See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/converter/separate_color.html
"""
res = nt.ProcNode.from_nodetype(
node_type=ContextualNode.SEPARATE_COLOR.value,
inputs={"Color": color},
attrs={"mode": "RGB"},
)
return SeparateRgbResult(
red=res._output_socket("red"),
green=res._output_socket("green"),
blue=res._output_socket("blue"),
alpha=res._output_socket("alpha"),
)
[docs]
class SeparateHsvResult(NamedTuple):
hue: nt.ProcNode[float]
saturation: nt.ProcNode[float]
value: nt.ProcNode[float]
alpha: nt.ProcNode[float]
[docs]
def separate_hsv(
color: nt.SocketOrVal[pt.Color] = (0.8, 0.8, 0.8, 1),
) -> SeparateHsvResult:
"""
Uses a SeparateColor Shader Node in HSV mode.
See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/converter/separate_color.html
"""
res = nt.ProcNode.from_nodetype(
node_type=ContextualNode.SEPARATE_COLOR.value,
inputs={"Color": color},
attrs={"mode": "HSV"},
)
return SeparateHsvResult(
hue=res._output_socket("red"),
saturation=res._output_socket("green"),
value=res._output_socket("blue"),
alpha=res._output_socket("alpha"),
)
[docs]
class SeparateHslResult(NamedTuple):
hue: nt.ProcNode[float]
saturation: nt.ProcNode[float]
lightness: nt.ProcNode[float]
alpha: nt.ProcNode[float]
[docs]
def separate_hsl(
color: nt.SocketOrVal[pt.Color] = (0.8, 0.8, 0.8, 1),
) -> SeparateHslResult:
"""
Uses a SeparateColor Shader Node in HSL mode.
See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/converter/separate_color.html
"""
res = nt.ProcNode.from_nodetype(
node_type=ContextualNode.SEPARATE_COLOR.value,
inputs={"Color": color},
attrs={"mode": "HSL"},
)
return SeparateHslResult(
hue=res._output_socket("red"),
saturation=res._output_socket("green"),
lightness=res._output_socket("blue"),
alpha=res._output_socket("alpha"),
)
# ---- ColorRamp -------------------------------------------------------------
[docs]
class ColorRampResult(NamedTuple):
color: nt.ProcNode[pt.Color]
alpha: nt.ProcNode[float]
TRampInterpolationType = Literal["EASE", "CARDINAL", "LINEAR", "B_SPLINE", "CONSTANT"]
[docs]
def color_ramp(
fac: nt.SocketOrVal[float] = 0.5,
points: list[tuple[float, pt.Color]] | None = None,
mode: Literal["RGB", "HSV", "HSL"] = "RGB",
interpolation: TRampInterpolationType = "LINEAR",
hue_interpolation: Literal["NEAR", "FAR", "CW", "CCW"] = "NEAR",
) -> ColorRampResult:
"""
Uses a ValToRGB (ColorRamp) Shader Node with points support.
`hue_interpolation` only applies to mode="HSV" / "HSL"; passing a
non-default value with mode="RGB" raises rather than being silently unused.
See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/converter/color_ramp.html
"""
if mode == "RGB" and hue_interpolation != "NEAR":
raise ValueError(
f"hue_interpolation={hue_interpolation!r} has no effect with "
f"mode='RGB'; it only applies to HSV/HSL color modes"
)
res = nt.ProcNode.from_nodetype(
node_type=ContextualNode.COLOR_RAMP.value,
inputs={"Fac": fac},
attrs={
"points": points,
"color_mode": mode,
"interpolation": interpolation,
"hue_interpolation": hue_interpolation,
},
)
return ColorRampResult(
color=res._output_socket("Color"),
alpha=res._output_socket("Alpha"),
)
[docs]
def blackbody(temperature: nt.SocketOrVal[float] = 1500.0) -> nt.ProcNode[pt.Color]:
"""
Uses a Blackbody Shader Node.
See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/converter/blackbody.html
"""
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeBlackbody",
inputs={"Temperature": temperature},
attrs={},
)
[docs]
def bright_contrast(
color: nt.SocketOrVal[pt.Color] = (1, 1, 1, 1),
bright: nt.SocketOrVal[float] = 0.0,
contrast: nt.SocketOrVal[float] = 0.0,
) -> nt.ProcNode[pt.Color]:
"""
Uses a BrightContrast Shader Node.
See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/color/bright_contrast.html
"""
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeBrightContrast",
inputs={"Color": color, "Bright": bright, "Contrast": contrast},
attrs={},
)
[docs]
def gamma(
color: nt.SocketOrVal[pt.Color] = (1, 1, 1, 1), gamma: nt.SocketOrVal[float] = 1.0
) -> nt.ProcNode[pt.Color]:
"""
Uses a Gamma Shader Node.
See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/color/gamma.html
"""
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeGamma",
inputs={"Color": color, "Gamma": gamma},
attrs={},
)
[docs]
def hue_saturation(
color: nt.SocketOrVal[pt.Color],
fac: nt.SocketOrVal[float],
hue: nt.SocketOrVal[float] = 0.5,
saturation: nt.SocketOrVal[float] = 1.0,
value: nt.SocketOrVal[float] = 1.0,
) -> nt.ProcNode[pt.Color]:
"""
Uses a HueSaturation Shader Node.
See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/color/hue_saturation.html
"""
return nt.ProcNode.from_nodetype(
node_type=ContextualNode.HUE_SATURATION.value,
inputs={
"Hue": hue,
"Saturation": saturation,
"Value": value,
"Fac": fac,
"Color": color,
},
attrs={},
)
[docs]
def rgb_to_bw(
color: nt.SocketOrVal[pt.Color] = (0.5, 0.5, 0.5, 1),
) -> nt.ProcNode[float]:
"""
Uses a RGBToBW Shader Node.
See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/converter/rgb_to_bw.html
"""
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeRGBToBW",
inputs={"Color": color},
attrs={},
)