Skip to content

Commit

Permalink
Merge pull request #421 from mgear-dev/master
Browse files Browse the repository at this point in the history
merge master to pymaya branch
  • Loading branch information
miquelcampos authored Sep 5, 2024
2 parents bba9f9a + 53a927f commit 38bfb2a
Show file tree
Hide file tree
Showing 12 changed files with 717 additions and 108 deletions.
2 changes: 1 addition & 1 deletion release/scripts/mgear/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
sev_comment = 32

# gear version
VERSION = [4, 2, 5]
VERSION = [4, 2, 9]

self = sys.modules[__name__]
self.menu_id = None
Expand Down
38 changes: 38 additions & 0 deletions release/scripts/mgear/core/anim_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,41 @@ def ikFkMatch_with_namespace(
ikRot (None, str): optional. Name of the Ik Rotation control
key (None, bool): optional. Whether we do an snap with animation
"""
# -----------------------------------------------
# NOTE: the following section is a workaround to match and reset the gimbal
# controls for legs and arms
# this workaround doesn't support custom naming.
gimbal_exist = False
try:
if "arm" in ikfk_attr or "leg" in ikfk_attr:

fks_gimbal = [pm.PyNode(x.replace("fk", "gimbal")) for x in fks]
ik_gimbal = pm.PyNode(ik.replace("ik", "gimbalIK"))

# store world transforms
fks_wtrans = [x.getMatrix(worldSpace=True) for x in fks_gimbal]
ik_wtrans = ik_gimbal.getMatrix(worldSpace=True)

# reset local transform
for x in fks_gimbal:
transform.resetTransform(x)
transform.resetTransform(ik_gimbal)

# apply transform to main control
for i, x in enumerate(fks):
pm.PyNode(x).setMatrix(fks_wtrans[i], worldSpace=True)
pm.PyNode(ik).setMatrix(ik_wtrans, worldSpace=True)

# keyframes
if key:
for x in fks_gimbal + [ik_gimbal]:
pm.setKeyframe(x, time=(cmds.currentTime(query=True) - 1.0))
gimbal_exist = True
except:
pass

# end of workaround gimbal match
# -----------------------------------------------

# returns a pymel node on the given name
def _get_node(name):
Expand Down Expand Up @@ -1087,6 +1122,9 @@ def _get_mth(name):
)
for elem in _all_controls
]
if gimbal_exist:
for x in fks_gimbal + [ik_gimbal]:
pm.setKeyframe(x, time=(cmds.currentTime(query=True)))


def ikFkMatch(model, ikfk_attr, ui_host, fks, ik, upv, ik_rot=None, key=None):
Expand Down
95 changes: 86 additions & 9 deletions release/scripts/mgear/core/applyop.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
import maya.api.OpenMaya as om
from .six import string_types

from mgear.core import attribute
from mgear.core import surface

#############################################
# BUILT IN NODES
#############################################
Expand Down Expand Up @@ -829,7 +832,12 @@ def gear_inverseRotorder_op(out_obj, in_obj):


def create_proximity_constraint(
shape, in_trans, existing_pin=None, mtx_connect=True, out_trans=None
shape,
in_trans,
existing_pin=None,
mtx_connect=True,
out_trans=None,
**kwargs
):
"""Create a proximity constraint between a shape and a transform.
Expand All @@ -842,13 +850,6 @@ def create_proximity_constraint(
Tuple[PyNode, PyNode]: out_trans, pin
"""

def find_next_available_index(node, attribute):
"""Find the next available index for a multi-attribute on a node."""
idx = 0
while node.attr(attribute)[idx].isConnected():
idx += 1
return idx

# Convert shape to PyNode if necessary
if isinstance(shape, str):
shape = pm.PyNode(shape)
Expand Down Expand Up @@ -881,7 +882,7 @@ def find_next_available_index(node, attribute):

if existing_pin:
pin = existing_pin
idx = find_next_available_index(pin, "inputMatrix")
idx = attribute.find_next_available_index(pin, "inputMatrix")
else:
# Create a new proximity pin node
pin = pm.createNode("proximityPin", n="{}_proximityPin".format(shape))
Expand Down Expand Up @@ -966,3 +967,79 @@ def create_proximity_constraints(shape, in_trans_list):
out_trans_list.append(out_trans)

return out_trans_list


def create_uv_pin_constraint(
shape,
in_trans,
existing_pin=None,
out_trans=None,
**kwargs
):
"""Create a UV pin constraint between a shape and a transform.
Args:
shape (PyNode or str): Driver shape
in_trans (PyNode or str): in transform
existing_pin (PyNode, optional): Existing uvPin node to connect to. Defaults to None.
mtx_connect (bool, optional): Whether to connect the input matrix. Defaults to True.
out_trans (PyNode, optional): Existing out transform node to connect to. Defaults to None.
Returns:
Tuple[PyNode, PyNode]: out_trans, pin
"""
# Convert shape to PyNode if necessary
if isinstance(shape, str):
shape = pm.PyNode(shape)
if isinstance(in_trans, str):
in_trans = pm.PyNode(in_trans)
# Try to get the original shape node
shape_orig_connections = shape.worldSpace.listConnections(d=True)
if not shape_orig_connections:
# If there's no original shape node, create one
dup = pm.duplicate(shape, n="{}OrigTrans".format(shape), rc=True)[0]
shape_orig = pm.listRelatives(dup, s=True)[0]
shape_orig.rename("{}Orig".format(shape))
pm.parent(shape_orig, shape, shape=True, add=True)
pm.delete(dup)
shape_orig.intermediateObject.set(1)
else:
shape_orig = shape_orig_connections[0]
# In some situations we get the transform instead of the shape.
# In that case we try to get the shape
try:
shape_orig = shape_orig.getShape()
except AttributeError:
pass
if not isinstance(shape_orig, pm.nt.NurbsSurface):
shape_orig = shape_orig.originalGeometry.listConnections(
d=True, sh=True
)[0]

if existing_pin:
pin = existing_pin
idx = attribute.find_next_available_index(pin, "outputMatrix")
else:
# Create a new UV pin node
pin = pm.createNode("uvPin", n="{}_uvPin".format(shape))
idx = 0

# Set the input connections for the UV pin
shape.worldSpace[0] >> pin.deformedGeometry
shape_orig.worldSpace[0] >> pin.originalGeometry

# Set UV coordinates to the closest point
position = in_trans.getTranslation(space="world")
closest_uv = surface.get_closest_uv_coord(shape.name(), position)
pin.coordinate[idx].coordinateU.set(closest_uv[0])
pin.coordinate[idx].coordinateV.set(closest_uv[1])

if not out_trans:
# Create the output transform
out_trans = pm.createNode(
"transform", n="{}_pinTrans{}".format(shape, idx)
)
# Set the input connections for the output transform
pin.outputMatrix[idx] >> out_trans.offsetParentMatrix

return out_trans, pin
21 changes: 21 additions & 0 deletions release/scripts/mgear/core/attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,10 @@ def setInvertMirror(node, invList=None):
Arguments:
node (dagNode): The object to set invert mirror Values
invList (list, optional): list of axis to invert ["tx", "tz"]
i.e: attribute.setInvertMirror(ctl_pyNode, invList=["tx", "tz"] )
"""

Expand Down Expand Up @@ -1530,6 +1534,23 @@ def get_next_available_index(attr):
return e


def find_next_available_index(node, attribute):
"""Find the next available index for a multi-attribute on a given node.
This function ins similar to get_next_available_index but with 2 args
Args:
node (PyNode): Node with multi-attribute.
attribute (str): Multi-attribute name.
Returns:
int: Next available index.
"""
idx = 0
while node.attr(attribute)[idx].isConnected():
idx += 1
return idx


def connect_message(source, attr):
"""
Connects the 'message' attribute of one or more source nodes to a
Expand Down
45 changes: 39 additions & 6 deletions release/scripts/mgear/core/node_utils.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import mgear.pymaya as pm


def find_connected_proximity_pin_node(source_object):
def find_connected_proximity_pin_node(source_mesh):
"""
Check if the worldMesh[0] output of an object is connected to the
Check if the Mesh Source object worldMesh[0] output of an object is connected to the
deformedGeometry of a proximityPin node. Return the proximityPin node as
a PyNode if found.
Args:
source_object (pyNode or str): Name of the source object.
source_mesh (pyNode or str): Name of the source Mesh object.
Returns:
pymel.core.general.PyNode: The connected proximityPin node as a PyNode
if a connection exists, otherwise None.
"""
# Get the source worldMesh[0] plug as PyNode
if isinstance(source_object, str):
source_object = pm.PyNode(source_object)
source_plug = source_object.worldMesh[0]
if isinstance(source_mesh, str):
source_mesh = pm.PyNode(source_mesh)
source_plug = source_mesh.worldMesh[0]

# Find all destination connections from this plug
connections = source_plug.connections(d=True, s=False, p=True)
Expand All @@ -33,3 +33,36 @@ def find_connected_proximity_pin_node(source_object):
return node

return None


def find_connected_uv_pin_node(source_nurbs):
"""
Check if the NURBS source object worldSpace[0] output of an object is connected to the
deformedGeometry of a uvPin node. Return the proximityPin node as
a PyNode if found.
Args:
source_nurbs (pyNode or str): Name of the source NURBS object.
Returns:
pymel.core.general.PyNode: The connected proximityPin node as a PyNode
if a connection exists, otherwise None.
"""
# Get the source worldSpace[0] plug as PyNode
if isinstance(source_nurbs, str):
source_nurbs = pm.PyNode(source_nurbs)
source_plug = source_nurbs.worldSpace[0]
# Find all destination connections from this plug
connections = source_plug.connections(d=True, s=False, p=True)

# Check each connection to see if it is to a proximityPin node
for connection in connections:
# The node connected to
node = connection.node()
# Check if this node is of the type 'proximityPin'
if node.type() == "uvPin":
# Check if the connected attribute is deformedGeometry
if "deformedGeometry" in connection.name():
return node

return None
87 changes: 87 additions & 0 deletions release/scripts/mgear/core/surface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""Functions to help navigate the NURBS surface"""

#############################################
# GLOBAL
#############################################
import maya.api.OpenMaya as om
import pymel.core as pm


def is_selected_object_nurbs(obj=None):
"""
Check if the currently selected object's transform has a NURBS shape.
Returns:
bool: True if the transform's shape is a NURBS, False otherwise.
Args:
obj (str or PyNode, optional): Object to check if is NURBS
"""
if not obj:
# Get the current selection
selection = pm.selected()

if not selection:
raise ValueError("No object is selected.")

# Get the first item in the selection
obj = selection[0]
else:
if isinstance(obj, str):
obj = pm.PyNode(obj)

# Check if the selected object is a transform and has a shape
if obj.nodeType() == "transform" and obj.getShape():
# Check if the shape of the selected object is a NURBS
is_nurbs = isinstance(obj.getShape(), pm.nodetypes.NurbsSurface)
return is_nurbs
else:
return False


def get_closest_uv_coord(surface_name, position):
"""
Get the closest UV coordinates on a NURBS surface from a given position.
Args:
surface_name (str): The name of the NURBS surface.
position (list): The position in world space [x, y, z].
Returns:
tuple: The closest UV coordinates (u, v).
"""
# Get the MObject for the NURBS surface
selection_list = om.MSelectionList()
selection_list.add(surface_name)
surface_dag_path = selection_list.getDagPath(0)

# Create MFnNurbsSurface function set
surface_fn = om.MFnNurbsSurface(surface_dag_path)

# Create MPoint from position
point = om.MPoint(position[0], position[1], position[2])

# Initialize closest distance and UV coordinates
closest_distance = float("inf")
closest_uv = (0.0, 0.0)
# Sample the surface at regular intervals to find the closest UV
num_samples = 100
u_range = surface_fn.knotDomainInU
v_range = surface_fn.knotDomainInV

for i in range(num_samples + 1):
for j in range(num_samples + 1):
u = u_range[0] + (u_range[1] - u_range[0]) * (i / num_samples)
v = v_range[0] + (v_range[1] - v_range[0]) * (j / num_samples)
sample_point = surface_fn.getPointAtParam(u, v, om.MSpace.kWorld)
distance = (sample_point - point).length()
if distance < closest_distance:
closest_distance = distance
closest_uv = (u, v)

# Normalize the UV coordinates
u_normalized = (closest_uv[0] - u_range[0]) / (u_range[1] - u_range[0])
v_normalized = (closest_uv[1] - v_range[0]) / (v_range[1] - v_range[0])

return u_normalized, v_normalized
Loading

0 comments on commit 38bfb2a

Please sign in to comment.