Tutorial: Examples

Example: Soft Approach Value

Code used in video:
# EXAMPLE: soft_approach_value

import node_calculator.core as noca

"""Task:
Approach a target value slowly, once the input value is getting close to it.
"""

"""
# Sudo-Code based on Harry Houghton's video: youtube.com/watch?v=xS1LpHE14Uk
in_value = <inputValue>
fade_in_range = <fadeInRange>
target_value = <targetValue>

if (in_value > (target_value - fade_in_range)):
    if (fade_in_range > 0):
        exponent = -(in_value - (target_value - fade_in_range)) / fade_in_range
        result = target_value - fade_in_range * exp(exponent)
    else:
        result = target_value
else:
    result = in_value

driven.attr = result
"""

import math

driver = noca.Node("driver")
in_value = driver.tx
driven = noca.Node("driven.tx")
fade_in_range = driver.add_float("transitionRange", value=1)
target_value = driver.add_float("targetValue", value=5)

# Note: Factoring the leading minus sign into the parenthesis requires one node
# less. I didn't do so to maintain the similarity to Harry's example.
# However; I'm using the optimized version in noca.Op.soft_approach()
exponent = -(in_value - (target_value - fade_in_range)) / fade_in_range
soft_approach_value = target_value - fade_in_range * math.e ** exponent

is_range_valid_condition = noca.Op.condition(
    fade_in_range > 0,
    soft_approach_value,
    target_value
)

is_in_range_condition = noca.Op.condition(
    in_value > (target_value - fade_in_range),
    is_range_valid_condition,
    in_value
)

driven.attrs = is_in_range_condition

# NOTE: This setup is now a standard operator: noca.Op.soft_approach()
Code used in video (example extension):
# DON'T import node_calculator.core as noca! It's a cyclical import that fails!
from node_calculator.core import noca_op
from node_calculator.core import Op


# ~~~~~~~~~~~~~~~~~~~~~ STEP 1: REQUIRED PLUGINS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
REQUIRED_EXTENSION_PLUGINS = []


# ~~~~~~~~~~~~~~~~~~~~~ STEP 2: OPERATORS DICTIONARY ~~~~~~~~~~~~~~~~~~~~~~~~~~
EXTENSION_OPERATORS = {}


# ~~~~~~~~~~~~~~~~~~~~~ STEP 3: OPERATOR FUNCTION ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@noca_op
def soft_approach(in_value, fade_in_range=0.5, target_value=1):
    """Follow in_value, but approach the target_value slowly.

    Note:
        Only works for 1D inputs!

    Args:
        in_value (NcNode or NcAttrs or str or int or float): Value or attr
        fade_in_range (NcNode or NcAttrs or str or int or float): Value or
            attr. This defines a range over which the target_value will be
            approached. Before the in_value is within this range the output
            of this and the in_value will be equal.
        target_value (NcNode or NcAttrs or str or int or float): Value or
            attr. This is the value that will be approached slowly.

    Returns:
        NcNode: Instance with node and output-attr.

    Example:
        ::

            in_attr = Node("pCube.tx")
            Op.soft_approach(in_attr, fade_in_range=2, target_value=5)
            # Starting at the value 3 (because 5-2=3), the output of this
            # will slowly approach the target_value 5.
    """
    start_val = target_value - fade_in_range

    exponent = ((start_val) - in_value) / fade_in_range
    soft_approach_value = target_value - fade_in_range * Op.exp(exponent)

    is_range_valid_condition = Op.condition(
        fade_in_range > 0,
        soft_approach_value,
        target_value
    )

    is_in_range_condition = Op.condition(
        in_value > start_val,
        is_range_valid_condition,
        in_value
    )

    return is_in_range_condition

Example: Simple cogs

Code used in video:
# EXAMPLE: cogs_simple

import node_calculator.core as noca

"""Task:
Drive all cogs by an attribute on the ctrl.
"""

# Solution 1:
# Direct drive rotation
ctrl = noca.Node("ctrl")
gear_5 = noca.Node("gear_5_geo")
gear_7 = noca.Node("gear_7_geo")
gear_8 = noca.Node("gear_8_geo")
gear_17 = noca.Node("gear_17_geo")
gear_22 = noca.Node("gear_22_geo")

driver = ctrl.add_float("cogRotation") * 10

gear_22.ry = driver / 22.0
gear_5.ry = driver / -5.0
gear_7.ry = driver / 7.0
gear_8.ry = driver / -8.0
gear_17.ry = driver / 17.0


# Solution 2:
# Chained rotation:
ctrl = noca.Node("ctrl")
gear_5 = noca.Node("gear_5_geo")
gear_7 = noca.Node("gear_7_geo")
gear_8 = noca.Node("gear_8_geo")
gear_17 = noca.Node("gear_17_geo")
gear_22 = noca.Node("gear_22_geo")

driver = ctrl.add_float("cogRotation", min=0)

gear_22.ry = driver
gear_8.ry = gear_22.ry * (-22/8.0)
gear_7.ry = gear_8.ry * (-8/7.0)
gear_5.ry = gear_7.ry * (-7/5.0)
gear_17.ry = gear_5.ry * (-5/17.0)

Example: Stepping cogs

_images/cogB_loop.gif
Code used in video:
# EXAMPLE: cogs_stepping

import node_calculator.core as noca

"""Task:
Drive all cogs by an attribute on the ctrl. All teeth but one were removed from
one of the cogs(!)

Uses maya_math_nodes extension!
"""

# Initialize nodes.
main_rot = noca.Node("ctrl.cogRot") * 100
cog_5 = noca.Node("gear_5_geo")
cog_7 = noca.Node("gear_7_geo")
cog_8 = noca.Node("gear_8_geo")
cog_22 = noca.Node("gear_22_geo")
cog_17 = noca.Node("gear_17_geo")

# Basic cog-rotation of small and big cog.
cog_5.ry = main_rot / 5.0
cog_7.ry = main_rot / -7.0
cog_22.ry = main_rot / -22.0

# Make rotation positive (cog_22 increases in negative direction).
single_tooth_rot = - cog_22.ry

# Dividing single_tooth_rot by 360deg and ceiling gives number of steps.
step_count = noca.Op.floor(noca.Op.divide(single_tooth_rot, 360))

single_tooth_degrees_per_step = 360 / 8.0
receiving_degrees_per_step = single_tooth_degrees_per_step / 17.0 * 8
stepped_receiving_rot = step_count * receiving_degrees_per_step
single_tooth_live_step_rotation = noca.Op.modulus_int(single_tooth_rot, single_tooth_degrees_per_step)
receiving_live_step_rotation = single_tooth_live_step_rotation / 17.0 * 8
rot_offset = 1

cog_17.ry = noca.Op.condition(
    # At every turn of the single tooth gear: Actively rotate the receiving gear during degrees_per_step degrees (45deg).
    noca.Op.modulus_int(single_tooth_rot, 360) < single_tooth_degrees_per_step,
    # When live rotation is happening the current step rotation is added to the accumulated stepped rotation.
    stepped_receiving_rot + receiving_live_step_rotation + rot_offset,
    # Static rotation if single tooth gear isn't driving. Needs an extra step since step_count is floored.
    stepped_receiving_rot + receiving_degrees_per_step + rot_offset
)

cog_8.ry = cog_17.ry / -8.0 * 17

Example: Dynamic colors

Note

No video (yet)!

Code used in video:
# EXAMPLE: Dynamic colors

import node_calculator.core as noca


"""Task:
Drive color based on translate x, y, z values:
The further into the x/y/z plane: The more r/g/b.
Values below zero/threshold should default to black.
"""

# Easy, but incomplete, due to: minus * minus = positive
b = noca.Node("geo")
b_mat = noca.Node("mat")
multiplier = b.add_float("multiplier", value=0.25, max=0.5)
r_value = b.ty * b.tz * multiplier
g_value = b.tx * b.tz * multiplier
b_value = b.tx * b.ty * multiplier
b_mat.color = [r_value, g_value, b_value]

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# Prototype red first.
b = noca.Node("geo")
b_mat = noca.Node("mat")
multiplier = b.add_float("multiplier", value=0.25, max=0.5)
r_value = b.ty * b.tz * multiplier
b_mat.colorR = noca.Op.condition(b.ty > 0, noca.Op.condition(b.tz > 0, r_value, 0), 0)

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# Doesn't display correctly in viewport!
b = noca.Node("geo")
b_mat = noca.Node("mat")

multiplier = b.add_float("multiplier", value=0.25, max=0.5)
threshold = 1

tx_zeroed = b.tx - threshold
ty_zeroed = b.ty - threshold
tz_zeroed = b.tz - threshold

r_value = ty_zeroed * tz_zeroed * multiplier
g_value = tx_zeroed * tz_zeroed * multiplier
b_value = tx_zeroed * ty_zeroed * multiplier

black = 0
with noca.Tracer(pprint_trace=True):
    b_mat.color = [
        noca.Op.condition(b.ty > threshold, noca.Op.condition(b.tz > threshold, r_value, black), black),
        noca.Op.condition(b.tz > threshold, noca.Op.condition(b.tx > threshold, g_value, black), black),
        noca.Op.condition(b.tx > threshold, noca.Op.condition(b.ty > threshold, b_value, black), black),
    ]