Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge master to pymaya branch #421

Merged
merged 21 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
bc33194
update version number to 4.2.6
miquelcampos May 22, 2024
8f96656
update code format to black and adding Offset Parent Matrix functions
miquelcampos May 31, 2024
3a1d8ca
adding doc string
miquelcampos May 31, 2024
fb6adc1
Core: adding surface module
miquelcampos Jun 11, 2024
d0f7694
Core: Applyop: Adding uvPin support. New module surface.py
miquelcampos Jun 11, 2024
894a320
verion number updated to 4.2.7
miquelcampos Jun 11, 2024
442de07
Shifter: addBlade new arg to set a custom worldup
miquelcampos Jun 26, 2024
3cfe266
Crank: return True when evaluation mode is already PG
miquelcampos Jun 27, 2024
43c4e63
Core: transoform add rotate_180 function
miquelcampos Jul 2, 2024
7ded634
Shifter: AddJoint. Now neutral rotation on Joints is optional
miquelcampos Jul 3, 2024
a25427a
Shifter: update data collector to add the following data: relatives, …
miquelcampos Jul 3, 2024
32f71aa
Shifter: update data collector to add the following data converted to…
miquelcampos Jul 3, 2024
2e0c180
Shifter: adding option to deactivate neutral rotation on the generate…
miquelcampos Jul 4, 2024
249093e
Core: Anim_utils: IKFKmatch added support for gimbal controls if exist
miquelcampos Jul 5, 2024
b7516c4
Crank: add support to use existing blendshapes nodes or creating node…
miquelcampos Jul 9, 2024
0796608
Crank: added support to use the existing blendshape node in different…
miquelcampos Jul 9, 2024
f04b400
update number version to 4.2.8
miquelcampos Jul 9, 2024
c2f07bc
update number version to 4.2.9
miquelcampos Jul 11, 2024
547bc51
Shifter: Update data collector to include world quaternion rotation a…
miquelcampos Jul 11, 2024
d53f586
Shifter: Adding hostRelative feature in components. In some situation…
miquelcampos Jul 18, 2024
53a927f
Core: fix error with bake locak to offset parent matrix, when attr ar…
miquelcampos Jul 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -649,6 +649,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 @@ -1514,6 +1518,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 pymel.core 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
Loading