Files
2025-07-03 17:03:00 +08:00

168 lines
6.4 KiB
Python

"""Shares utils to work with Maya"""
import ctypes
import os
import numpy as np
from maya import OpenMaya
from maya import cmds
# ----- Working with files -----
def load_file(filepath, name='object'):
"""Load mesh to the scene"""
if not os.path.isfile(filepath):
raise RuntimeError('Loading Object from file to Maya::Missing file {}'.format(filepath))
obj = cmds.file(filepath, i=True, rnn=True)[0]
obj = cmds.rename(obj, name + '#')
return obj
def save_mesh(target, to_file):
"""Save given object to file as a mesh"""
# Make sure to only select requested mesh
cmds.select(clear=True)
cmds.select(target)
cmds.file(
to_file,
type='OBJExport',
exportSelectedStrict=True, # export selected -- only explicitely selected
options='groups=0;ptgroups=0;materials=0;smoothing=0;normals=1', # very simple obj
force=True, # force override if file exists
defaultExtensions=False
)
cmds.select(clear=True)
# ----- Mesh info -----
def get_dag(object_name):
"""Return DAG for requested object"""
selectionList = OpenMaya.MSelectionList()
selectionList.add(object_name)
dag = OpenMaya.MDagPath()
selectionList.getDagPath(0, dag)
return dag
def get_mesh_dag(object_name):
"""Return MFnMesh object by the object name"""
# get object as OpenMaya object -- though DAG
dag = get_dag(object_name)
# as mesh
mesh = OpenMaya.MFnMesh(dag) # reference https://help.autodesk.com/view/MAYAUL/2017/ENU/?guid=__py_ref_class_open_maya_1_1_m_fn_mesh_html
return mesh, dag
def get_vertices_np(mesh):
"""
Retreive vertex info as np array for given mesh object
"""
maya_vertices = OpenMaya.MPointArray()
mesh.getPoints(maya_vertices, OpenMaya.MSpace.kWorld)
vertices = np.empty((maya_vertices.length(), 3))
for i in range(maya_vertices.length()):
for j in range(3):
vertices[i, j] = maya_vertices[i][j]
return vertices
def match_vert_lists(short_list, long_list):
"""
Find the vertices from long list that correspond to verts in short_list
Both lists are numpy arrays
NOTE: Assuming order is matching => O(len(long_list)) complexity:
order of vertices in short list is the same as in long list (for those that are left)
"""
match_list = []
idx_short = 0
for idx_long in range(len(long_list)):
long_vertex = long_list[idx_long]
short_vertex = short_list[idx_short]
if all(np.isclose(short_vertex, long_vertex, atol=1e-5)):
match_list.append(idx_long)
idx_short += 1 # advance the short list indexing
if idx_short >= len(short_list): # short list finished before the long one
break
if len(match_list) != len(short_list):
raise ValueError('Vertex matching unsuccessfull: matched {} of {} vertices in short list'.format(
len(match_list), len(short_list)
))
return match_list
# ---- Mesh operations ----
def test_ray_intersect(mesh, raySource, rayVector, accelerator=None, hit_tol=None, return_info=False):
"""Check if given ray intersect given mesh
* hit_tol ignores intersections that are within hit_tol from the ray source (as % of ray length) -- usefull when checking self-intersect
* mesh is expected to be of MFnMesh type
* accelrator is a stucture for speeding-up calculations.
It can be initialized from MFnMesh object and should be supplied with every call to this function
"""
# It turns out that OpenMaya python reference has nothing to do with reality of passing argument:
# most of the functions I use below are to be treated as wrappers of c++ API
# https://help.autodesk.com/view/MAYAUL/2018//ENU/?guid=__cpp_ref_class_m_fn_mesh_html
# follow structure https://stackoverflow.com/questions/58390664/how-to-fix-typeerror-in-method-mfnmesh-anyintersection-argument-4-of-type
maxParam = 1 # only search for intersections within given vector
testBothDirections = False # only in the given direction
sortHits = False # no need to waste time on sorting
hitPoints = OpenMaya.MFloatPointArray()
hitRayParams = OpenMaya.MFloatArray()
hitFaces = OpenMaya.MIntArray()
hit = mesh.allIntersections(
raySource, rayVector, None, None, False, OpenMaya.MSpace.kWorld, maxParam, testBothDirections, accelerator, sortHits,
hitPoints, hitRayParams, hitFaces, None, None, None, 1e-6)
if hit and hit_tol is not None:
hit = any([dist > hit_tol for dist in hitRayParams])
if return_info:
return hit, hitFaces, hitPoints, hitRayParams
return hit
def edge_vert_ids(mesh, edge_id):
"""Return vertex ids for a given edge in given mesh"""
# Have to go through the C++ wrappers
# Vertices that comprise an edge
script_util = OpenMaya.MScriptUtil(0.0)
v_ids_cptr = script_util.asInt2Ptr() # https://forums.cgsociety.org/t/mfnmesh-getedgevertices-error-on-2011/1652362
mesh.getEdgeVertices(edge_id, v_ids_cptr)
# get values from SWIG pointer https://stackoverflow.com/questions/39344039/python-cast-swigpythonobject-to-python-object
ty = ctypes.c_uint * 2
v_ids_list = ty.from_address(int(v_ids_cptr))
return v_ids_list[0], v_ids_list[1]
def scale_to_cm(target, max_height_cm=220):
"""Heuristically check the target units and scale to cantimeters if other units are detected
* default value of max_height_cm is for meshes of humans
"""
# check for througth height (Y axis)
# NOTE prone to fails if non-meter units are used for body
bb = cmds.polyEvaluate(target, boundingBox=True) # ((xmin,xmax), (ymin,ymax), (zmin,zmax))
height = bb[1][1] - bb[1][0]
if height < max_height_cm * 0.01: # meters
cmds.scale(100, 100, 100, target, centerPivot=True, absolute=True)
print('WARNING: {} is found to use meters as units. Scaled up by 100 for cm'.format(target))
elif height < max_height_cm * 0.1: # decimeters
cmds.scale(10, 10, 10, target, centerPivot=True, absolute=True)
print('WARNING: {} is found to use decimeters as units. Scaled up by 10 for cm'.format(target))
elif height > max_height_cm: # millimiters or something strange
cmds.scale(0.1, 0.1, 0.1, target, centerPivot=True, absolute=True)
print('WARNING: {} is found to use millimiters as units. Scaled down by 0.1 for cm'.format(target))