Skip to content

Commit d107c31

Browse files
authored
Merge pull request #567 from ordinc/upv_visualizer
UPV Visualization for Arm/Leg Guides in Component Guide Mode
2 parents 33283c7 + 0649ed7 commit d107c31

13 files changed

Lines changed: 517 additions & 12 deletions

File tree

Lines changed: 397 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,397 @@
1+
"""
2+
UPV Visualization Module.
3+
4+
This module is used to create and set up a pole vector (UPV) visualization system in Maya, specifically for the guide stage.
5+
6+
"""
7+
8+
import mgear.pymaya as pm
9+
10+
11+
def upv_vis_decompose_nodes(root, elbow, wrist, eff):
12+
"""
13+
Create decompose matrix nodes for guide nodes.
14+
15+
Creates a decomposeMatrix node for each guide node to extract world space transformation information.
16+
17+
Args:
18+
root (PyNode): Root guide node
19+
elbow (PyNode): Elbow guide node
20+
wrist (PyNode): Wrist guide node
21+
eff (PyNode): End effector guide node
22+
23+
Returns:
24+
list: A list containing four decomposeMatrix nodes, in the order [root, elbow, wrist, eff]
25+
"""
26+
guide_nodes = [root, elbow, wrist, eff]
27+
decompose_nodes = [
28+
pm.createNode("decomposeMatrix", name=f"{guide}_decomposeMatrix")
29+
for guide in guide_nodes
30+
]
31+
for guide, decompose_node in zip(guide_nodes, decompose_nodes):
32+
guide.worldMatrix[0] >> decompose_node.inputMatrix
33+
34+
return decompose_nodes
35+
36+
37+
def create_vector_nodes(name, node_type="subtract"):
38+
"""
39+
Create vector operation node groups.
40+
41+
Creates a set of three nodes of the same type for processing the X, Y, Z components of a vector.
42+
43+
Args:
44+
name (str): Node name prefix
45+
node_type (str, optional): Node type, supports 'subtract', 'sum', 'multiply'
46+
47+
Returns:
48+
list: A list containing three specified type nodes, corresponding to the X, Y, Z axes respectively
49+
50+
Raises:
51+
ValueError: When the node_type is not supported
52+
"""
53+
node_creators = {
54+
"subtract": ("subtract", "subtract"),
55+
"sum": ("sum", "sum"),
56+
"multiply": ("multiply", "multiply"),
57+
}
58+
if node_type not in node_creators:
59+
raise ValueError(f"Unsupported node type: {node_type}")
60+
node_class, suffix = node_creators[node_type]
61+
nodes = [
62+
pm.createNode(node_class, name=f"{name}_{axis}_{suffix}")
63+
for axis in ["x", "y", "z"]
64+
]
65+
66+
return nodes
67+
68+
69+
def connect_vector_components(
70+
source_node, target_nodes, target_attribute="input1", axes=["X", "Y", "Z"]
71+
):
72+
"""
73+
Connect vector components to target nodes.
74+
75+
Connects the output components of the source node to the specified attributes of the target nodes.
76+
77+
Args:
78+
source_node (PyNode): Source node, containing outputTranslateX/Y/Z attributes
79+
target_nodes (list): Target node list
80+
target_attribute (str, optional): Target attribute name
81+
axes (list, optional): List of axes to connect
82+
83+
"""
84+
for i, component in enumerate(axes):
85+
source_component = getattr(source_node, f"outputTranslate{component}")
86+
if hasattr(target_nodes[i], target_attribute):
87+
target_attr = getattr(target_nodes[i], target_attribute)
88+
source_component >> target_attr
89+
elif target_attribute.startswith("input["):
90+
index = int(target_attribute.split("[")[1].split("]")[0])
91+
target_nodes[i].input[index] << source_component
92+
else:
93+
pm.displayError(
94+
f"Target node {target_nodes[i]} has no attribute {target_attribute}"
95+
)
96+
97+
98+
def create_vector_subtraction_nodes(elbow, wrist, root, eff):
99+
"""
100+
Create vector subtraction node network.
101+
102+
Creates all necessary vector subtraction nodes for pole vector calculation.
103+
104+
Args:
105+
elbow (PyNode): Elbow guide node
106+
wrist (PyNode): Wrist guide node
107+
root (PyNode): Root guide node
108+
eff (PyNode): End effector guide node
109+
110+
Returns:
111+
dict: A dictionary containing various vector subtraction nodes, keys include:
112+
- 'crossProduct_elbow': Nodes related to elbow cross product
113+
- 'crossProduct_wrist': Nodes related to wrist cross product
114+
- 'crossProduct_root': Nodes related to root cross product
115+
- 'sub_elbow': Elbow subtraction nodes
116+
- 'sub_wrist': Wrist subtraction nodes
117+
- 'sub_eff': End effector subtraction nodes
118+
"""
119+
vector_nodes = {}
120+
node_type = "subtract"
121+
vector_nodes["crossProduct_elbow"] = create_vector_nodes(
122+
f"{elbow}_crossProduct", node_type
123+
)
124+
vector_nodes["crossProduct_wrist"] = create_vector_nodes(
125+
f"{wrist}_crossProduct", node_type
126+
)
127+
vector_nodes["crossProduct_root"] = create_vector_nodes(
128+
f"{root}_crossProduct", node_type
129+
)
130+
131+
vector_nodes["sub_elbow"] = create_vector_nodes(f"{elbow}", node_type)
132+
vector_nodes["sub_wrist"] = create_vector_nodes(f"{wrist}", node_type)
133+
vector_nodes["sub_eff"] = create_vector_nodes(f"{eff}", node_type)
134+
135+
return vector_nodes
136+
137+
138+
def connect_decompose_to_vector_nodes(decompose_nodes, vector_nodes):
139+
"""
140+
Connect decompose matrix nodes to vector nodes.
141+
142+
Connects the outputs of decomposeMatrix nodes to the inputs of vector subtraction nodes.
143+
144+
Args:
145+
decompose_nodes (list): decomposeMatrix node list
146+
vector_nodes (dict): Vector subtraction node dictionary
147+
148+
Note:
149+
Node order convention: decompose_nodes = [root, elbow, wrist, eff]
150+
"""
151+
decm = decompose_nodes
152+
# Connect crossProduct_root (wrist - root)
153+
connect_vector_components(decm[2], vector_nodes["crossProduct_root"], "input1")
154+
connect_vector_components(decm[0], vector_nodes["crossProduct_root"], "input2")
155+
# Connect crossProduct_elbow (elbow - root)
156+
connect_vector_components(decm[1], vector_nodes["crossProduct_elbow"], "input1")
157+
connect_vector_components(decm[0], vector_nodes["crossProduct_elbow"], "input2")
158+
# Connect crossProduct_wrist_sub (wrist - root)
159+
connect_vector_components(decm[2], vector_nodes["crossProduct_wrist"], "input1")
160+
connect_vector_components(decm[0], vector_nodes["crossProduct_wrist"], "input2")
161+
# elbow - root
162+
connect_vector_components(decm[1], vector_nodes["sub_elbow"], "input1")
163+
connect_vector_components(decm[0], vector_nodes["sub_elbow"], "input2")
164+
# wrist - root
165+
connect_vector_components(decm[2], vector_nodes["sub_wrist"], "input1")
166+
connect_vector_components(decm[0], vector_nodes["sub_wrist"], "input2")
167+
# eff - root
168+
connect_vector_components(decm[3], vector_nodes["sub_eff"], "input1")
169+
connect_vector_components(decm[0], vector_nodes["sub_eff"], "input2")
170+
171+
172+
def calculate_vector_lengths(vector_nodes):
173+
"""
174+
Calculate vector lengths.
175+
176+
Creates length nodes for eff, elbow, wrist vectors to calculate their lengths.
177+
178+
Args:
179+
vector_nodes (dict): Dictionary containing vector subtraction nodes
180+
181+
Returns:
182+
dict: Dictionary containing length nodes, keys are 'eff', 'elbow', 'wrist'
183+
"""
184+
length_nodes = {}
185+
for joint_name in ["eff", "elbow", "wrist"]:
186+
length_node = pm.createNode("length", name=f"{joint_name}_length")
187+
sub_nodes = vector_nodes[f"sub_{joint_name}"]
188+
sub_nodes[0].output >> length_node.inputX
189+
sub_nodes[1].output >> length_node.inputY
190+
sub_nodes[2].output >> length_node.inputZ
191+
length_nodes[joint_name] = length_node
192+
193+
return length_nodes
194+
195+
196+
def setup_math_operations(root, length_nodes, float_value=0.5):
197+
"""
198+
Set up mathematical operation nodes.
199+
200+
Creates a chain of mathematical operation nodes for pole vector calculation.
201+
202+
Args:
203+
root (PyNode): Root guide node
204+
length_nodes (dict): Dictionary containing length nodes
205+
float_value (float, optional): Multiplication coefficient, defaults to 0.5
206+
207+
Returns:
208+
tuple: A tuple containing two elements:
209+
- half_one_float_node: Final multiplication node
210+
- math_nodes: Mathematical node dictionary, containing 'max' and 'half_multiply' nodes
211+
"""
212+
max_float_node = pm.createNode("floatMath", name=f"{root}_max_floatMath")
213+
max_float_node.floatA.set(0.010)
214+
max_float_node.operation.set(2) # multiply
215+
216+
max_node = pm.createNode("max", name=f"{root.name()}_max")
217+
max_float_node.outFloat >> max_node.input[0]
218+
219+
length_nodes["eff"].output >> max_node.input[1]
220+
length_nodes["elbow"].output >> max_node.input[2]
221+
length_nodes["wrist"].output >> max_node.input[3]
222+
223+
half_one_float_node = pm.createNode("floatMath", name=f"{root}_half_one_floatMath")
224+
half_one_float_node.floatB.set(float_value)
225+
half_one_float_node.operation.set(2) # multiply
226+
max_node.output >> half_one_float_node.floatA
227+
228+
math_nodes = {}
229+
math_nodes["max"] = max_node
230+
math_nodes["half_multiply"] = half_one_float_node
231+
232+
return half_one_float_node, math_nodes
233+
234+
235+
def setup_cross_product_chain(root, elbow, wrist, vector_nodes, float_value):
236+
"""
237+
Set up cross product calculation chain.
238+
239+
Creates a complete cross product calculation node network to determine the pole vector direction.
240+
241+
Args:
242+
root (PyNode): Root guide node
243+
elbow (PyNode): Elbow guide node
244+
wrist (PyNode): Wrist guide node
245+
vector_nodes (dict): Vector node dictionary
246+
float_value (float): Coefficient used for length calculation
247+
248+
Returns:
249+
tuple: A tuple containing three elements:
250+
- crossProduct_root_normalize_node: Normalized final cross product result
251+
- half_multiply_node: Length multiplication node
252+
- math_nodes: Mathematical operation node dictionary
253+
"""
254+
length_nodes = calculate_vector_lengths(vector_nodes)
255+
half_multiply_node, math_nodes = setup_math_operations(
256+
root, length_nodes, float_value
257+
)
258+
259+
normalize_elbow = pm.createNode("normalize", name=f"{elbow}_normalize")
260+
normalize_wrist = pm.createNode("normalize", name=f"{wrist}_normalize")
261+
262+
for i, axis in enumerate(["X", "Y", "Z"]):
263+
getattr(vector_nodes["crossProduct_elbow"][i], "output") >> getattr(
264+
normalize_elbow, f"input{axis}"
265+
)
266+
getattr(vector_nodes["crossProduct_wrist"][i], "output") >> getattr(
267+
normalize_wrist, f"input{axis}"
268+
)
269+
270+
crossProduct_wrist_elbow = pm.createNode(
271+
"crossProduct", name=f"{root}_crossProduct_wrist_elbow"
272+
)
273+
crossProduct_default = pm.createNode(
274+
"crossProduct", name=f"{root}_crossProduct_default"
275+
)
276+
crossProduct_default.input2Z.set(-1.000)
277+
278+
normalize_wrist.output >> crossProduct_wrist_elbow.input1
279+
normalize_elbow.output >> crossProduct_wrist_elbow.input2
280+
normalize_elbow.output >> crossProduct_default.input1
281+
282+
crossProduct_wrist_elbow_sum_node = pm.createNode(
283+
"sum", name=f"{root}_crossProduct_wrist_elbow_sum"
284+
)
285+
286+
crossProduct_wrist_elbow.outputX >> crossProduct_wrist_elbow_sum_node.input[0]
287+
crossProduct_wrist_elbow.outputY >> crossProduct_wrist_elbow_sum_node.input[1]
288+
crossProduct_wrist_elbow.outputZ >> crossProduct_wrist_elbow_sum_node.input[2]
289+
290+
condition_node = pm.createNode("condition", name=f"{root}_condition")
291+
condition_node.secondTerm.set(0.000)
292+
293+
crossProduct_wrist_elbow_sum_node.output >> condition_node.firstTerm
294+
crossProduct_default.output >> condition_node.colorIfTrue
295+
crossProduct_wrist_elbow.output >> condition_node.colorIfFalse
296+
297+
normalize_condition_node = pm.createNode(
298+
"normalize", name=f"{root}_normalize_condition"
299+
)
300+
condition_node.outColor >> normalize_condition_node.input
301+
302+
crossProduct_root = pm.createNode("crossProduct", name=f"{root}_crossProduct_root")
303+
normalize_condition_node.output >> crossProduct_root.input1
304+
305+
for i, axis in enumerate(["X", "Y", "Z"]):
306+
getattr(vector_nodes["crossProduct_root"][i], "output") >> getattr(
307+
crossProduct_root, f"input2{axis}"
308+
)
309+
310+
crossProduct_root_normalize_node = pm.createNode(
311+
"normalize", name=f"{root}_crossProduct_root_normalize"
312+
)
313+
crossProduct_root.output >> crossProduct_root_normalize_node.input
314+
315+
return crossProduct_root_normalize_node, half_multiply_node, math_nodes
316+
317+
318+
def setup_upv_position_calculation(
319+
elbow, upv, normalize_node, half_multiply_node, decompose_nodes
320+
):
321+
"""
322+
Calculate the final position of the UPV node.
323+
324+
Determines the final position of the pole vector guide node based on cross product direction and length calculation.
325+
326+
Args:
327+
elbow (PyNode): Elbow guide node
328+
upv (PyNode): Pole vector guide node
329+
normalize_node (PyNode): Normalized cross product direction node
330+
half_multiply_node (PyNode): Length multiplication node
331+
decompose_nodes (list): decomposeMatrix node list
332+
"""
333+
upv_pos_mul = create_vector_nodes(f"{elbow}_upv_pos", "multiply")
334+
upv_pos_sum = create_vector_nodes(f"{elbow}_upv_pos", "sum")
335+
if upv_pos_mul and upv_pos_sum:
336+
for i, axis in enumerate(["X", "Y", "Z"]):
337+
getattr(normalize_node, f"output{axis}") >> upv_pos_mul[i].input[0]
338+
half_multiply_node.outFloat >> upv_pos_mul[i].input[1]
339+
340+
upv_pos_mul[i].output >> upv_pos_sum[i].input[0]
341+
(
342+
getattr(decompose_nodes[1], f"outputTranslate{axis}")
343+
>> upv_pos_sum[i].input[1]
344+
)
345+
346+
upv_pos_sum[i].output >> getattr(upv, f"translate{axis}")
347+
348+
349+
def setup_visibility_and_matrix(root, upv, upvcrv):
350+
"""
351+
Set up visibility and matrix connections.
352+
353+
Ensures the UPV node and curve correctly inherit the root node's transformation.
354+
355+
Args:
356+
root (PyNode): Root guide node
357+
upv (PyNode): Pole vector guide node
358+
upvcrv (PyNode): Pole vector display curve
359+
"""
360+
root.scale >> upv.scale
361+
root.worldInverseMatrix[0] >> upv.offsetParentMatrix
362+
root.worldInverseMatrix[0] >> upvcrv.offsetParentMatrix
363+
364+
365+
def create_upv_system(root, elbow, wrist, eff, upvcrv, upv, float_value=0.5):
366+
"""
367+
Create a complete UPV visualization system.
368+
369+
Main function that coordinates all sub-functions to create a complete pole vector visualization system.
370+
371+
Args:
372+
root (PyNode): Root guide node
373+
elbow (PyNode): Elbow guide node
374+
wrist (PyNode): Wrist guide node
375+
eff (PyNode): End effector guide node
376+
upvcrv (PyNode): Pole vector display curve node
377+
upv (PyNode): Pole vector guide node
378+
float_value (float, optional): Pole vector length coefficient, defaults to 0.5
379+
380+
Example:
381+
>>> create_upv_system('root_guide', 'elbow_guide', 'wrist_guide',
382+
... 'eff_guide', upv_curve, upv_node, 0.5)
383+
"""
384+
decompose_nodes = upv_vis_decompose_nodes(root, elbow, wrist, eff)
385+
if decompose_nodes:
386+
vector_nodes = create_vector_subtraction_nodes(elbow, wrist, root, eff)
387+
connect_decompose_to_vector_nodes(decompose_nodes, vector_nodes)
388+
389+
normalize_node, half_multiply_node, math_nodes = setup_cross_product_chain(
390+
root, elbow, wrist, vector_nodes, float_value
391+
)
392+
393+
setup_upv_position_calculation(
394+
elbow, upv, normalize_node, half_multiply_node, decompose_nodes
395+
)
396+
397+
setup_visibility_and_matrix(root, upv, upvcrv)

0 commit comments

Comments
 (0)