Tutorial: Examples¶
Example: Soft Approach Value¶
# 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()
# 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¶
# 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¶
# 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)!
# 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),
]