Skip to content

Instantly share code, notes, and snippets.

@Liametc
Last active August 30, 2023 13:30
Show Gist options
  • Save Liametc/5231abe1df3e5e541353fe84a6055064 to your computer and use it in GitHub Desktop.
Save Liametc/5231abe1df3e5e541353fe84a6055064 to your computer and use it in GitHub Desktop.
Check polygon normals in maya using python api 2.0
import maya.api.OpenMaya as om
import maya.cmds
import time
def is_normal_reversed(poly, mesh):
"""
Check the normal faces in or out of the model by doing a simple ray cast and
count results. Evens face out, odds face in.
This is costly, so we're only doing it for the first poly. Everything else is
compared to this.
:param MItMeshPolygon poly: The current poly face.
:param MFnMesh mesh: The current mesh object.
:returns: :class:`bool` True = normal reversed
"""
space = om.MSpace.kWorld
center = om.MFloatPoint(poly.center(space=space))
normal = om.MFloatVector(poly.getNormal(space=space))
point = center + (normal*0.001) # has to be just off the surface to not get current poly
hit_count = 0
hit_nothing = False
while not hit_nothing:
point, _, face_id, _, _, _ = mesh.closestIntersection(point, normal, space, 10000, False)
if face_id == -1:
hit_nothing = True
break
point = point + (normal*0.001)
hit_count += 1
return bool(hit_count & 1)
def check_polygon_normals(objects, select_faces=False, select_objs=False):
"""
Check the normals of a polygon.
Uses technique defined in http://www.dillonbhuff.com/?p=30
where we check the winding of the vertices against it's neighbour.
If any group of two vertices match the order of another group of two,
the winding is opposite and he normals face in different directions.
We do a better check on the first normal to make sure it's correct.
:param list objects: List of object names to check.
:param bool select_faces: Select faces with incorret normals.
:param bool select_objs: Select objects with incorrect normals
"""
start = time.time()
mSel = om.MSelectionList()
map(mSel.add, objects)
selection = om.MItSelectionList(mSel)
incorrect_polys = om.MSelectionList()
while not selection.isDone():
dag = selection.getDagPath()
mesh = om.MFnMesh(dag)
poly = om.MItMeshPolygon(dag)
visited = set([0])
incorrect = set([])
if is_normal_reversed(poly, mesh):
incorrect.add(poly.index())
while not poly.isDone():
current = poly.index()
vertices = mesh.getPolygonVertices(current)
connected_faces = poly.getConnectedFaces()
main_count = len(vertices)
for face in connected_faces:
if face in visited:
continue
visited.add(face)
face_verts = mesh.getPolygonVertices(face)
vert_count = len(face_verts)
is_different = False
matched = False
for indexA in range(main_count):
indexB = (indexA + 1) % main_count
for vert_indexA in range(vert_count):
vert_indexB = (vert_indexA + 1) % vert_count
matching_verts = all((
vertices[indexA] == face_verts[vert_indexA],
vertices[indexB] == face_verts[vert_indexB]
))
if matching_verts:
is_different = True
break
# face is incorrect if winding is different compared to correct face
# or is the same and an incorrect face
if is_different:
if current not in incorrect:
incorrect.add(face)
elif current in incorrect:
incorrect.add(face)
poly.next(0) # docs says this doesn't take an input, running says otherwise. Input does nothing
if incorrect:
if select_objs:
incorrect_polys.add(dag)
if select_faces:
for face in incorrect:
incorrect_polys.add("{}.f[{}]".format(dag, face))
print("found incorrect polys on {}: {!r}".format(dag, sorted(incorrect)))
selection.next()
duration = time.time() - start
print('ran in: {:.0f}m {:0>2.0f}s'.format(duration // 60, duration % 60))
if select_faces or select_objs:
om.MGlobal.setActiveSelectionList(incorrect_polys)
# how to run
#check_polygon_normals(maya.cmds.ls(geometry=True), select_faces=True, select_objs=False)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment