def build_matcap_nodes(node_tree: bpy.types.NodeTree, image_path: str) -> None: tex_coord_node = node_tree.nodes.new(type='ShaderNodeTexCoord') vector_transform_node = node_tree.nodes.new(type='ShaderNodeVectorTransform') mapping_node = node_tree.nodes.new(type='ShaderNodeMapping') texture_image_node = create_texture_node(node_tree, image_path, True) emmission_node = node_tree.nodes.new(type='ShaderNodeEmission') output_node = node_tree.nodes.new(type='ShaderNodeOutputMaterial') create_frame_node(node_tree, (tex_coord_node, vector_transform_node, mapping_node), name="MatCap UV", label="MatCap UV") vector_transform_node.vector_type = "VECTOR" vector_transform_node.convert_from = "OBJECT" vector_transform_node.convert_to = "CAMERA" mapping_node.vector_type = "TEXTURE" if bpy.app.version >= (2, 81, 0): mapping_node.inputs["Location"].default_value = (1.0, 1.0, 0.0) mapping_node.inputs["Scale"].default_value = (2.0, 2.0, 1.0) else: mapping_node.translation = (1.0, 1.0, 0.0) mapping_node.scale = (2.0, 2.0, 1.0) node_tree.links.new(tex_coord_node.outputs['Normal'], vector_transform_node.inputs['Vector']) node_tree.links.new(vector_transform_node.outputs['Vector'], mapping_node.inputs['Vector']) node_tree.links.new(mapping_node.outputs['Vector'], texture_image_node.inputs['Vector']) node_tree.links.new(texture_image_node.outputs['Color'], emmission_node.inputs['Color']) node_tree.links.new(emmission_node.outputs['Emission'], output_node.inputs['Surface']) arrange_nodes(node_tree)
def build_environment_texture_background(world: bpy.types.World, hdri_path: str, rotation: float = 0.0) -> None: world.use_nodes = True node_tree = world.node_tree environment_texture_node = node_tree.nodes.new( type="ShaderNodeTexEnvironment") environment_texture_node.image = bpy.data.images.load(hdri_path) mapping_node = node_tree.nodes.new(type="ShaderNodeMapping") if bpy.app.version >= (2, 81, 0): mapping_node.inputs["Rotation"].default_value = (0.0, 0.0, rotation) else: mapping_node.rotation[2] = rotation tex_coord_node = node_tree.nodes.new(type="ShaderNodeTexCoord") node_tree.links.new(tex_coord_node.outputs["Generated"], mapping_node.inputs["Vector"]) node_tree.links.new(mapping_node.outputs["Vector"], environment_texture_node.inputs["Vector"]) node_tree.links.new(environment_texture_node.outputs["Color"], node_tree.nodes["Background"].inputs["Color"]) arrange_nodes(node_tree)
def add_parametric_color_ramp() -> bpy.types.NodeGroup: group = bpy.data.node_groups.new(type="ShaderNodeTree", name="Parametric Color Ramp") # Input input_node = group.nodes.new(type="NodeGroupInput") group.inputs.new("NodeSocketFloatFactor", "Fac") group.inputs.new("NodeSocketColor", "Color1") group.inputs.new("NodeSocketColor", "Color2") group.inputs.new("NodeSocketFloatFactor", "Pos1") group.inputs.new("NodeSocketFloatFactor", "Pos2") set_socket_value_range(group.inputs["Fac"], default_value=0.5) set_socket_value_range(group.inputs["Pos1"], default_value=0.0) set_socket_value_range(group.inputs["Pos2"], default_value=1.0) # Math denominator_subtract_node = group.nodes.new(type="ShaderNodeMath") denominator_subtract_node.operation = "SUBTRACT" denominator_subtract_node.use_clamp = True numerator_subtract_node = group.nodes.new(type="ShaderNodeMath") numerator_subtract_node.operation = "SUBTRACT" numerator_subtract_node.use_clamp = True divide_node = group.nodes.new(type="ShaderNodeMath") divide_node.operation = "DIVIDE" divide_node.use_clamp = True group.links.new(input_node.outputs["Pos2"], denominator_subtract_node.inputs[0]) group.links.new(input_node.outputs["Fac"], denominator_subtract_node.inputs[1]) group.links.new(input_node.outputs["Pos2"], numerator_subtract_node.inputs[0]) group.links.new(input_node.outputs["Pos1"], numerator_subtract_node.inputs[1]) group.links.new(denominator_subtract_node.outputs["Value"], divide_node.inputs[0]) group.links.new(numerator_subtract_node.outputs["Value"], divide_node.inputs[1]) # Mixing mix_node = group.nodes.new(type="ShaderNodeMixRGB") group.links.new(divide_node.outputs["Value"], mix_node.inputs["Fac"]) group.links.new(input_node.outputs["Color2"], mix_node.inputs[1]) group.links.new(input_node.outputs["Color1"], mix_node.inputs[2]) # Output output_node = group.nodes.new(type="NodeGroupOutput") group.outputs.new("NodeSocketColor", "Color") group.links.new(mix_node.outputs["Color"], output_node.inputs["Color"]) # Return arrange_nodes(group) return group
def build_pbr_textured_nodes(node_tree: bpy.types.NodeTree, color_texture_path: str = "", metallic_texture_path: str = "", roughness_texture_path: str = "", normal_texture_path: str = "", displacement_texture_path: str = "", ambient_occlusion_texture_path: str = "", scale: Tuple[float, float, float] = (1.0, 1.0, 1.0)) -> None: output_node = node_tree.nodes.new(type='ShaderNodeOutputMaterial') principled_node = node_tree.nodes.new(type='ShaderNodeBsdfPrincipled') node_tree.links.new(principled_node.outputs['BSDF'], output_node.inputs['Surface']) coord_node = node_tree.nodes.new(type='ShaderNodeTexCoord') mapping_node = node_tree.nodes.new(type='ShaderNodeMapping') mapping_node.vector_type = 'TEXTURE' mapping_node.scale = scale node_tree.links.new(coord_node.outputs['UV'], mapping_node.inputs['Vector']) if color_texture_path != "": texture_node = create_texture_node(node_tree, color_texture_path, True) node_tree.links.new(mapping_node.outputs['Vector'], texture_node.inputs['Vector']) if ambient_occlusion_texture_path != "": ao_texture_node = create_texture_node(node_tree, ambient_occlusion_texture_path, False) node_tree.links.new(mapping_node.outputs['Vector'], ao_texture_node.inputs['Vector']) mix_node = node_tree.nodes.new(type='ShaderNodeMixRGB') mix_node.blend_type = 'MULTIPLY' node_tree.links.new(texture_node.outputs['Color'], mix_node.inputs['Color1']) node_tree.links.new(ao_texture_node.outputs['Color'], mix_node.inputs['Color2']) node_tree.links.new(mix_node.outputs['Color'], principled_node.inputs['Base Color']) else: node_tree.links.new(texture_node.outputs['Color'], principled_node.inputs['Base Color']) if metallic_texture_path != "": texture_node = create_texture_node(node_tree, metallic_texture_path, False) node_tree.links.new(mapping_node.outputs['Vector'], texture_node.inputs['Vector']) node_tree.links.new(texture_node.outputs['Color'], principled_node.inputs['Metallic']) if roughness_texture_path != "": texture_node = create_texture_node(node_tree, roughness_texture_path, False) node_tree.links.new(mapping_node.outputs['Vector'], texture_node.inputs['Vector']) node_tree.links.new(texture_node.outputs['Color'], principled_node.inputs['Roughness']) if normal_texture_path != "": texture_node = create_texture_node(node_tree, normal_texture_path, False) node_tree.links.new(mapping_node.outputs['Vector'], texture_node.inputs['Vector']) normal_map_node = node_tree.nodes.new(type='ShaderNodeNormalMap') node_tree.links.new(texture_node.outputs['Color'], normal_map_node.inputs['Color']) node_tree.links.new(normal_map_node.outputs['Normal'], principled_node.inputs['Normal']) if displacement_texture_path != "": texture_node = create_texture_node(node_tree, displacement_texture_path, False) node_tree.links.new(mapping_node.outputs['Vector'], texture_node.inputs['Vector']) node_tree.links.new(texture_node.outputs['Color'], output_node.inputs['Displacement']) arrange_nodes(node_tree)
def build_checker_board_nodes(node_tree: bpy.types.NodeTree, size: float) -> None: output_node = node_tree.nodes.new(type='ShaderNodeOutputMaterial') principled_node = node_tree.nodes.new(type='ShaderNodeBsdfPrincipled') checker_texture_node = node_tree.nodes.new(type='ShaderNodeTexChecker') set_principled_node(principled_node=principled_node) checker_texture_node.inputs['Scale'].default_value = size node_tree.links.new(checker_texture_node.outputs['Color'], principled_node.inputs['Base Color']) node_tree.links.new(principled_node.outputs['BSDF'], output_node.inputs['Surface']) arrange_nodes(node_tree)
def build_peeling_paint_metal_nodes(node_tree: bpy.types.NodeTree) -> None: output_node = node_tree.nodes.new(type='ShaderNodeOutputMaterial') principled_node = node_tree.nodes.new(type='ShaderNodeBsdfPrincipled') peeling_paint_metal_node = create_peeling_paint_metal_node_group(node_tree) node_tree.links.new(peeling_paint_metal_node.outputs['Color'], principled_node.inputs['Base Color']) node_tree.links.new(peeling_paint_metal_node.outputs['Metallic'], principled_node.inputs['Metallic']) node_tree.links.new(peeling_paint_metal_node.outputs['Roughness'], principled_node.inputs['Roughness']) node_tree.links.new(peeling_paint_metal_node.outputs['Bump'], principled_node.inputs['Normal']) node_tree.links.new(principled_node.outputs['BSDF'], output_node.inputs['Surface']) arrange_nodes(node_tree)
def build_rgb_background(world: bpy.types.World, rgb: Tuple[float, float, float, float] = (0.9, 0.9, 0.9, 1.0), strength: float = 1.0) -> None: world.use_nodes = True node_tree = world.node_tree rgb_node = node_tree.nodes.new(type="ShaderNodeRGB") rgb_node.outputs["Color"].default_value = rgb node_tree.nodes["Background"].inputs["Strength"].default_value = strength node_tree.links.new(rgb_node.outputs["Color"], node_tree.nodes["Background"].inputs["Color"]) arrange_nodes(node_tree)
def build_emission_nodes(node_tree: bpy.types.NodeTree, color: Tuple[float, float, float] = (0.0, 0.0, 0.0), strength: float = 1.0) -> None: ''' https://docs.blender.org/api/current/bpy.types.ShaderNodeEmission.html ''' output_node = node_tree.nodes.new(type='ShaderNodeOutputMaterial') emission_node = node_tree.nodes.new(type='ShaderNodeEmission') emission_node.inputs["Color"].default_value = color + (1.0, ) emission_node.inputs["Strength"].default_value = strength node_tree.links.new(emission_node.outputs['Emission'], output_node.inputs['Surface']) arrange_nodes(node_tree)
def build_scene_composition(scene, vignette=0.20, dispersion=0.050, gain=1.10, saturation=1.10): scene.use_nodes = True clean_nodes(scene.node_tree.nodes) render_layer_node = scene.node_tree.nodes.new(type="CompositorNodeRLayers") vignette_node = create_vignette_node(scene.node_tree) vignette_node.inputs["Amount"].default_value = vignette lens_distortion_node = scene.node_tree.nodes.new( type="CompositorNodeLensdist") lens_distortion_node.inputs["Distort"].default_value = -dispersion * 0.40 lens_distortion_node.inputs["Dispersion"].default_value = dispersion color_correction_node = scene.node_tree.nodes.new( type="CompositorNodeColorCorrection") color_correction_node.master_saturation = saturation color_correction_node.master_gain = gain split_tone_node = create_split_tone_node(scene.node_tree) glare_node = scene.node_tree.nodes.new(type="CompositorNodeGlare") glare_node.glare_type = 'FOG_GLOW' glare_node.quality = 'HIGH' composite_node = scene.node_tree.nodes.new(type="CompositorNodeComposite") scene.node_tree.links.new(render_layer_node.outputs['Image'], vignette_node.inputs['Image']) scene.node_tree.links.new(vignette_node.outputs['Image'], lens_distortion_node.inputs['Image']) scene.node_tree.links.new(lens_distortion_node.outputs['Image'], color_correction_node.inputs['Image']) scene.node_tree.links.new(color_correction_node.outputs['Image'], split_tone_node.inputs['Image']) scene.node_tree.links.new(split_tone_node.outputs['Image'], glare_node.inputs['Image']) scene.node_tree.links.new(glare_node.outputs['Image'], composite_node.inputs['Image']) arrange_nodes(scene.node_tree)
def build_pbr_nodes(node_tree: bpy.types.NodeTree, base_color: Tuple[float, float, float, float] = (0.6, 0.6, 0.6, 1.0), metallic: float = 0.0, specular: float = 0.5, roughness: float = 0.5, sheen: float = 0.0) -> None: output_node = node_tree.nodes.new(type='ShaderNodeOutputMaterial') principled_node = node_tree.nodes.new(type='ShaderNodeBsdfPrincipled') node_tree.links.new(principled_node.outputs['BSDF'], output_node.inputs['Surface']) set_principled_node(principled_node=principled_node, base_color=base_color, metallic=metallic, specular=specular, roughness=roughness, sheen=sheen) arrange_nodes(node_tree)
def add_vignette_node_group(): group = bpy.data.node_groups.new(type="CompositorNodeTree", name="Vignette") input_node = group.nodes.new("NodeGroupInput") group.inputs.new("NodeSocketColor", "Image") group.inputs.new("NodeSocketFloat", "Amount") group.inputs["Amount"].default_value = 0.2 group.inputs["Amount"].min_value = 0.0 group.inputs["Amount"].max_value = 1.0 lens_distortion_node = group.nodes.new(type="CompositorNodeLensdist") lens_distortion_node.inputs["Distort"].default_value = 1.000 separate_rgba_node = group.nodes.new(type="CompositorNodeSepRGBA") blur_node = group.nodes.new(type="CompositorNodeBlur") blur_node.filter_type = 'GAUSS' blur_node.size_x = 300 blur_node.size_y = 300 blur_node.use_extended_bounds = True mix_node = group.nodes.new(type="CompositorNodeMixRGB") mix_node.blend_type = 'MULTIPLY' output_node = group.nodes.new("NodeGroupOutput") group.outputs.new("NodeSocketColor", "Image") group.links.new(input_node.outputs["Amount"], mix_node.inputs["Fac"]) group.links.new(input_node.outputs["Image"], mix_node.inputs[1]) group.links.new(input_node.outputs["Image"], lens_distortion_node.inputs["Image"]) group.links.new(lens_distortion_node.outputs["Image"], separate_rgba_node.inputs["Image"]) group.links.new(separate_rgba_node.outputs["A"], blur_node.inputs["Image"]) group.links.new(blur_node.outputs["Image"], mix_node.inputs[2]) group.links.new(mix_node.outputs["Image"], output_node.inputs["Image"]) arrange_nodes(group) return group
def add_peeling_paint_metal_node_group() -> bpy.types.NodeGroup: group = bpy.data.node_groups.new(type="ShaderNodeTree", name="Peeling Paint Metal") input_node = group.nodes.new(type="NodeGroupInput") group.inputs.new("NodeSocketColor", "Paint Color") group.inputs.new("NodeSocketColor", "Metal Color") group.inputs.new("NodeSocketFloat", "Scale") group.inputs.new("NodeSocketFloat", "Detail") group.inputs.new("NodeSocketFloat", "Distortion") group.inputs.new("NodeSocketFloatFactor", "Threshold") set_socket_value_range(group.inputs["Scale"], default_value=4.5, min_value=0.0, max_value=1000.0) set_socket_value_range(group.inputs["Detail"], default_value=8.0, min_value=0.0, max_value=16.0) set_socket_value_range(group.inputs["Distortion"], default_value=0.5, min_value=0.0, max_value=1000.0) set_socket_value_range(group.inputs["Threshold"], default_value=0.42) group.inputs["Paint Color"].default_value = (0.152, 0.524, 0.067, 1.000) group.inputs["Metal Color"].default_value = (0.062, 0.015, 0.011, 1.000) tex_coord_node = group.nodes.new(type="ShaderNodeTexCoord") mapping_node = group.nodes.new(type="ShaderNodeMapping") group.links.new(tex_coord_node.outputs["Object"], mapping_node.inputs["Vector"]) # Peeling region segmentation peeling_noise_node = group.nodes.new(type="ShaderNodeTexNoise") group.links.new(mapping_node.outputs["Vector"], peeling_noise_node.inputs["Vector"]) group.links.new(input_node.outputs["Scale"], peeling_noise_node.inputs["Scale"]) group.links.new(input_node.outputs["Detail"], peeling_noise_node.inputs["Detail"]) group.links.new(input_node.outputs["Distortion"], peeling_noise_node.inputs["Distortion"]) peeling_threshold_node = create_parametric_color_ramp_node(group) peeling_threshold_node.inputs["Color1"].default_value = (0.0, 0.0, 0.0, 1.0) peeling_threshold_node.inputs["Color2"].default_value = (1.0, 1.0, 1.0, 1.0) # Base color epsilon_subtract_node = group.nodes.new(type="ShaderNodeMath") epsilon_subtract_node.operation = "SUBTRACT" epsilon_subtract_node.inputs[1].default_value = 0.001 group.links.new(input_node.outputs["Threshold"], epsilon_subtract_node.inputs[0]) group.links.new(peeling_noise_node.outputs["Fac"], peeling_threshold_node.inputs["Fac"]) group.links.new(epsilon_subtract_node.outputs["Value"], peeling_threshold_node.inputs["Pos1"]) group.links.new(input_node.outputs["Threshold"], peeling_threshold_node.inputs["Pos2"]) color_mix_node = group.nodes.new(type="ShaderNodeMixRGB") group.links.new(peeling_threshold_node.outputs["Color"], color_mix_node.inputs["Fac"]) group.links.new(input_node.outputs["Metal Color"], color_mix_node.inputs[1]) group.links.new(input_node.outputs["Paint Color"], color_mix_node.inputs[2]) # Ambient occulusion epsilon_add_node = group.nodes.new(type="ShaderNodeMath") epsilon_add_node.operation = "ADD" epsilon_add_node.inputs[1].default_value = 0.010 group.links.new(input_node.outputs["Threshold"], epsilon_add_node.inputs[0]) fallout_subtract_node = group.nodes.new(type="ShaderNodeMath") fallout_subtract_node.operation = "SUBTRACT" fallout_subtract_node.inputs[1].default_value = 0.060 group.links.new(input_node.outputs["Threshold"], fallout_subtract_node.inputs[0]) ao_node = create_tri_parametric_color_ramp_node(group) ao_node.inputs["Color1"].default_value = (1.0, 1.0, 1.0, 1.0) ao_node.inputs["Color2"].default_value = (0.0, 0.0, 0.0, 1.0) ao_node.inputs["Color3"].default_value = (1.0, 1.0, 1.0, 1.0) group.links.new(peeling_noise_node.outputs["Fac"], ao_node.inputs["Fac"]) group.links.new(fallout_subtract_node.outputs["Value"], ao_node.inputs["Pos1"]) group.links.new(input_node.outputs["Threshold"], ao_node.inputs["Pos2"]) group.links.new(epsilon_add_node.outputs["Value"], ao_node.inputs["Pos3"]) ao_mix_node = group.nodes.new(type="ShaderNodeMixRGB") ao_mix_node.blend_type = "MULTIPLY" ao_mix_node.inputs["Fac"].default_value = 1.0 group.links.new(color_mix_node.outputs["Color"], ao_mix_node.inputs[1]) group.links.new(ao_node.outputs["Color"], ao_mix_node.inputs[2]) # Metallic metallic_node = group.nodes.new(type="ShaderNodeMixRGB") metallic_node.inputs["Color1"].default_value = (1.0, 1.0, 1.0, 1.0) metallic_node.inputs["Color2"].default_value = (0.0, 0.0, 0.0, 1.0) group.links.new(peeling_threshold_node.outputs["Color"], metallic_node.inputs["Fac"]) # Roughness roughness_node = group.nodes.new(type="ShaderNodeMixRGB") roughness_node.inputs["Color1"].default_value = (0.50, 0.50, 0.50, 1.0) roughness_node.inputs["Color2"].default_value = (0.05, 0.05, 0.05, 1.0) group.links.new(peeling_threshold_node.outputs["Color"], roughness_node.inputs["Fac"]) # Bump height_node = create_tri_parametric_color_ramp_node(group) height_node.inputs["Color1"].default_value = (0.0, 0.0, 0.0, 1.0) height_node.inputs["Color2"].default_value = (1.0, 1.0, 1.0, 1.0) height_node.inputs["Color3"].default_value = (0.5, 0.5, 0.5, 1.0) height_peak_add_node = group.nodes.new(type="ShaderNodeMath") height_peak_add_node.operation = "ADD" height_peak_add_node.inputs[1].default_value = 0.005 height_tail_add_node = group.nodes.new(type="ShaderNodeMath") height_tail_add_node.operation = "ADD" height_tail_add_node.inputs[1].default_value = 0.025 group.links.new(input_node.outputs["Threshold"], height_peak_add_node.inputs[0]) group.links.new(input_node.outputs["Threshold"], height_tail_add_node.inputs[0]) group.links.new(peeling_noise_node.outputs["Fac"], height_node.inputs["Fac"]) group.links.new(input_node.outputs["Threshold"], height_node.inputs["Pos1"]) group.links.new(height_peak_add_node.outputs["Value"], height_node.inputs["Pos2"]) group.links.new(height_tail_add_node.outputs["Value"], height_node.inputs["Pos3"]) bump_node = group.nodes.new(type="ShaderNodeBump") group.links.new(height_node.outputs["Color"], bump_node.inputs["Height"]) # Output output_node = group.nodes.new("NodeGroupOutput") group.outputs.new("NodeSocketColor", "Color") group.outputs.new("NodeSocketColor", "Metallic") group.outputs.new("NodeSocketColor", "Roughness") group.outputs.new("NodeSocketVectorDirection", "Bump") group.links.new(ao_mix_node.outputs["Color"], output_node.inputs["Color"]) group.links.new(metallic_node.outputs["Color"], output_node.inputs["Metallic"]) group.links.new(roughness_node.outputs["Color"], output_node.inputs["Roughness"]) group.links.new(bump_node.outputs["Normal"], output_node.inputs["Bump"]) arrange_nodes(group) return group
def add_split_tone_node_group() -> bpy.types.NodeGroup: group = bpy.data.node_groups.new(type="CompositorNodeTree", name="SplitToneSub") input_node = group.nodes.new("NodeGroupInput") group.inputs.new("NodeSocketColor", "Image") group.inputs.new("NodeSocketFloat", "Hue") group.inputs.new("NodeSocketFloat", "Saturation") solid_node = group.nodes.new(type="CompositorNodeCombHSVA") solid_node.inputs["S"].default_value = 1.0 solid_node.inputs["V"].default_value = 1.0 solid_node.inputs["A"].default_value = 1.0 input_sep_node = group.nodes.new(type="CompositorNodeSepHSVA") overlay_node = group.nodes.new(type="CompositorNodeMixRGB") overlay_node.blend_type = 'OVERLAY' overlay_sep_node = group.nodes.new(type="CompositorNodeSepHSVA") comb_node = group.nodes.new(type="CompositorNodeCombHSVA") output_node = group.nodes.new("NodeGroupOutput") group.outputs.new("NodeSocketColor", "Image") group.links.new(input_node.outputs["Hue"], solid_node.inputs["H"]) group.links.new(input_node.outputs["Saturation"], overlay_node.inputs["Fac"]) group.links.new(input_node.outputs["Image"], overlay_node.inputs[1]) group.links.new(solid_node.outputs["Image"], overlay_node.inputs[2]) group.links.new(overlay_node.outputs["Image"], overlay_sep_node.inputs["Image"]) group.links.new(input_node.outputs["Image"], input_sep_node.inputs["Image"]) group.links.new(overlay_sep_node.outputs["H"], comb_node.inputs["H"]) group.links.new(overlay_sep_node.outputs["S"], comb_node.inputs["S"]) group.links.new(input_sep_node.outputs["V"], comb_node.inputs["V"]) group.links.new(input_sep_node.outputs["A"], comb_node.inputs["A"]) group.links.new(comb_node.outputs["Image"], output_node.inputs["Image"]) arrange_nodes(group) # -------------------------------------------------------------------------- group = bpy.data.node_groups.new(type="CompositorNodeTree", name="SplitTone") input_node = group.nodes.new("NodeGroupInput") group.inputs.new("NodeSocketColor", "Image") group.inputs.new("NodeSocketFloat", "HighlightsHue") group.inputs.new("NodeSocketFloat", "HighlightsSaturation") group.inputs.new("NodeSocketFloat", "ShadowsHue") group.inputs.new("NodeSocketFloat", "ShadowsSaturation") group.inputs.new("NodeSocketFloatFactor", "Balance") set_socket_value_range(group.inputs["HighlightsHue"]) set_socket_value_range(group.inputs["HighlightsSaturation"]) set_socket_value_range(group.inputs["ShadowsHue"]) set_socket_value_range(group.inputs["ShadowsSaturation"]) set_socket_value_range(group.inputs["Balance"], default_value=0.5) input_sep_node = group.nodes.new(type="CompositorNodeSepHSVA") subtract_node = group.nodes.new(type="CompositorNodeMath") subtract_node.inputs[0].default_value = 1.0 subtract_node.operation = 'SUBTRACT' subtract_node.use_clamp = True multiply_node = group.nodes.new(type="CompositorNodeMath") multiply_node.inputs[1].default_value = 2.0 multiply_node.operation = 'MULTIPLY' multiply_node.use_clamp = False power_node = group.nodes.new(type="CompositorNodeMath") power_node.operation = 'POWER' power_node.use_clamp = True shadows_node = group.nodes.new(type='CompositorNodeGroup') shadows_node.name = "Shadows" shadows_node.node_tree = bpy.data.node_groups["SplitToneSub"] highlights_node = group.nodes.new(type='CompositorNodeGroup') highlights_node.name = "Highlights" highlights_node.node_tree = bpy.data.node_groups["SplitToneSub"] comb_node = group.nodes.new(type="CompositorNodeMixRGB") comb_node.use_clamp = False output_node = group.nodes.new("NodeGroupOutput") group.outputs.new("NodeSocketColor", "Image") group.links.new(input_node.outputs["Image"], input_sep_node.inputs["Image"]) group.links.new(input_node.outputs["Image"], shadows_node.inputs["Image"]) group.links.new(input_node.outputs["ShadowsHue"], shadows_node.inputs["Hue"]) group.links.new(input_node.outputs["ShadowsSaturation"], shadows_node.inputs["Saturation"]) group.links.new(input_node.outputs["Image"], highlights_node.inputs["Image"]) group.links.new(input_node.outputs["HighlightsHue"], highlights_node.inputs["Hue"]) group.links.new(input_node.outputs["HighlightsSaturation"], highlights_node.inputs["Saturation"]) group.links.new(input_node.outputs["Balance"], subtract_node.inputs[1]) group.links.new(subtract_node.outputs["Value"], multiply_node.inputs[0]) group.links.new(input_sep_node.outputs["V"], power_node.inputs[0]) group.links.new(multiply_node.outputs["Value"], power_node.inputs[1]) group.links.new(power_node.outputs["Value"], comb_node.inputs["Fac"]) group.links.new(shadows_node.outputs["Image"], comb_node.inputs[1]) group.links.new(highlights_node.outputs["Image"], comb_node.inputs[2]) group.links.new(comb_node.outputs["Image"], output_node.inputs["Image"]) arrange_nodes(group) return group