Example #1
0
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)
Example #2
0
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)
Example #3
0
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
Example #4
0
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)
Example #5
0
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)
Example #6
0
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)
Example #7
0
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)
Example #8
0
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)
Example #10
0
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
Example #12
0
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
Example #13
0
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