Skip to content

Instantly share code, notes, and snippets.

@davidlatwe
Last active October 21, 2021 04:57
Show Gist options
  • Save davidlatwe/cd7502440b80fb475c00d58d543991a4 to your computer and use it in GitHub Desktop.
Save davidlatwe/cd7502440b80fb475c00d58d543991a4 to your computer and use it in GitHub Desktop.
Maya Mesh Hasher

Purpose

This is for identifying any geometry mesh changes during dependency data collecting process in publish, in Maya. Especially when publishing lookDev/Rig.

Artist often needs to work on modeling and look developing all together, which means the working model often not being the published one. And in publish process, system may not know which version of model is the dependency of current look.

To solve this kind of problem, we need to generate hashcode that based on target's feature. Take model as example, the hash function would based on mesh vertex position, normal, and UV.

With that hashcode saved with model verison data in database, we can verify look's model with previous version's hashcode to see if it's unchanged and matched the latest version of subset.

import codecs
import hashlib
from maya.api import OpenMaya as om
class _C4Hasher(object):
CHUNK_SIZE = 4096 * 10 # magic number
PREFIX = "c4"
def __init__(self):
self.hash_obj = None
self.clear()
def clear(self):
"""Start a new hash session
"""
self.hash_obj = hashlib.sha512()
def _b58encode(self, bytes):
"""Base58 Encode bytes to string
"""
b58chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
b58base = 58
long_value = int(codecs.encode(bytes, "hex_codec"), 16)
result = ""
while long_value >= b58base:
div, mod = divmod(long_value, b58base)
result = b58chars[mod] + result
long_value = div
result = b58chars[long_value] + result
return result
def digest(self):
"""Return hash value of data added so far
"""
c4_id_length = 90
b58_hash = self._b58encode(self.hash_obj.digest())
padding = ""
if len(b58_hash) < (c4_id_length - 2):
padding = "1" * (c4_id_length - 2 - len(b58_hash))
c4id = self.PREFIX + padding + b58_hash
return c4id
class MeshHasher(object):
"""A mesh geometry hasher for Maya
For quickly identifying if any change on vertices, normals and UVs.
Hash value will be encoded as Avalanche-io C4 Asset ID.
Order matters, transform does not.
Example Usage:
>> hasher = MeshHasher()
>> hasher.set_mesh("path|to|mesh")
>> hasher.update_points()
>> hasher.update_normals()
>> hasher.update_uvmap()
>> hasher.digest()
{'normals': 'c41MSCnAGqWS9dBDDpydbpcMwzzFkGH66jNpuTqctfY...',
'points': 'c44fV5wa6bNiekUadZ4HsRPDL2HZ11RFKcXhf3pntsUJ...',
'uvmap': 'c45JRQTPxgMNYfcijAbm31vkJRt6CUUSn7ew2X1Mnyjwi...'}
Hash meshes in loop
>> for mesh in cmds.ls(type="mesh", ni=Ture, long=True)
... hasher.set_mesh(mesh)
... hasher.update_points()
... hasher.update_normals()
...
>> hasher.digest()
{'normals': 'c456rBNH5pzobqjHzFnHApanrTdJo64r2R8o4GJxqU9G...',
'points': 'c449wXhjNSSKfnUjPp2ub3fd1DeNowW2x5gBJDYrSvxrT...'}
You can still adding more meshes until you call `clear`
>> hasher.clear()
"""
def __init__(self):
self.clear()
def clear(self):
self._mesh = None
self._points = 0
self._normals = 0
self._uvmap = 0
def set_mesh(self, dag_path):
"""Set one mesh geometry node to hasher
Arguments:
dag_path (str): Mesh node's DAG path
"""
sel_list = om.MSelectionList()
sel_list.add(dag_path)
sel_obj = sel_list.getDagPath(0)
self._mesh = om.MFnMesh(sel_obj)
def update_points(self):
for i, vt in enumerate(self._mesh.getPoints()):
self._points += _hash_MPoint(*vt) + i
def update_normals(self):
for i, vt in enumerate(self._mesh.getNormals()):
self._normals += _hash_MFloatVectors(*vt) + i
def update_uvmap(self, uv_set=""):
for i, uv in enumerate(zip(*self._mesh.getUVs(uv_set))):
self._uvmap += _hash_UV(*uv) + i
def digest(self):
result = dict()
hasher = _C4Hasher()
if self._points:
hasher.hash_obj.update(str(self._points))
result["points"] = hasher.digest()
hasher.clear()
if self._normals:
hasher.hash_obj.update(str(self._normals))
result["normals"] = hasher.digest()
hasher.clear()
if self._uvmap:
hasher.hash_obj.update(str(self._uvmap))
result["uvmap"] = hasher.digest()
hasher.clear()
return result
# these are just hashing with magic numbers..
def _hash_MPoint(x, y, z, w):
x = (x + 1) * 233
y = (y + x) * 239
z = (z + y) * 241
return (x + y + z) * w
def _hash_MFloatVectors(x, y, z):
x = (x + 1) * 383
y = (y + x) * 389
z = (z + y) * 397
return x + y + z
def _hash_UV(u, v):
u = (u + 1) * 547
v = (v + u) * 557
return u * v
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment