Add OpenPose_full renderer plugin for Maya
Maya API 2.0 plugin that maps a skeleton to OpenPose_full keypoints (BODY_25 or COCO body, 21+21 hand, 70 face) via an openposeCharacter node, with a live Viewport 2.0 draw override, an "OpenPose" viewport renderer plus openposeRenderSequence for PNG output, and a PySide2 mapping editor. Face can also be driven procedurally from ARKit's 52 blendshape weights instead of facial joints, for rigs with no facial skeleton. Includes VS Code debug configs and Maya test scenes/renders. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
"""Viewport 2.0 draw override for ``openposeCharacter``: a live, colored
|
||||
skeleton overlay drawn in world space on top of the mapped joints, so a rig
|
||||
can be visually QA'd against the OpenPose_full mapping without rendering.
|
||||
|
||||
This is independent of the render override (render_override.py) -- it always
|
||||
draws (when the node's enableDisplay attr is on) in every viewport, in 3D
|
||||
world space, and tracks animation live via prepareForDraw() being re-run
|
||||
whenever the node is dirtied.
|
||||
"""
|
||||
|
||||
import maya.api.OpenMaya as om
|
||||
import maya.api.OpenMayaRender as omr
|
||||
|
||||
from . import constants
|
||||
from . import face_blendshapes
|
||||
from . import mapping_node
|
||||
from . import projector
|
||||
|
||||
|
||||
def _mcolor(rgb255):
|
||||
return om.MColor((rgb255[0] / 255.0, rgb255[1] / 255.0, rgb255[2] / 255.0, 1.0))
|
||||
|
||||
|
||||
class OpenPoseUserData(om.MUserData):
|
||||
|
||||
def __init__(self):
|
||||
om.MUserData.__init__(self, False) # Maya should not delete after one draw
|
||||
self.lines = [] # [(p0, p1, MColor, thickness), ...]
|
||||
self.points = [] # [(p, MColor, pointSize), ...]
|
||||
|
||||
|
||||
class OpenPoseDrawOverride(omr.MPxDrawOverride):
|
||||
|
||||
@staticmethod
|
||||
def creator(obj):
|
||||
return OpenPoseDrawOverride(obj)
|
||||
|
||||
def __init__(self, obj):
|
||||
omr.MPxDrawOverride.__init__(self, obj, None, isAlwaysDirty=True)
|
||||
|
||||
def supportedDrawAPIs(self):
|
||||
return omr.MRenderer.kAllDevices
|
||||
|
||||
def isBounded(self, obj_path, camera_path):
|
||||
# Joints being displayed can be anywhere in the scene relative to
|
||||
# this node's own (irrelevant) transform -- don't cull on bbox.
|
||||
return False
|
||||
|
||||
def hasUIDrawables(self):
|
||||
return True
|
||||
|
||||
def prepareForDraw(self, obj_path, camera_path, frame_context, old_data):
|
||||
data = old_data if isinstance(old_data, OpenPoseUserData) else OpenPoseUserData()
|
||||
data.lines = []
|
||||
data.points = []
|
||||
|
||||
dep_fn = om.MFnDependencyNode(obj_path.node())
|
||||
try:
|
||||
enabled = dep_fn.findPlug(mapping_node.aEnableDisplay, False).asBool()
|
||||
except RuntimeError:
|
||||
enabled = True
|
||||
if not enabled:
|
||||
return data
|
||||
|
||||
radius_scale = dep_fn.findPlug(mapping_node.aJointRadiusScale, False).asFloat()
|
||||
thickness_scale = dep_fn.findPlug(mapping_node.aLimbThicknessScale, False).asFloat()
|
||||
body_model = mapping_node.get_body_model(dep_fn)
|
||||
|
||||
mapped = mapping_node.get_mapped_joint_paths(dep_fn)
|
||||
positions = {index: projector.world_position(path) for index, path in mapped.items()}
|
||||
|
||||
if mapping_node.get_face_source(dep_fn) == mapping_node.FACE_SOURCE_ARKIT_BLENDSHAPES:
|
||||
nose_index = constants.NAME_TO_INDEX["Body_Nose"]
|
||||
anchor = positions.get(nose_index)
|
||||
if anchor is not None:
|
||||
weights = mapping_node.get_blendshape_weights(dep_fn)
|
||||
face_scale = mapping_node.get_face_scale(dep_fn)
|
||||
local_points = face_blendshapes.compute_face_local_points(weights)
|
||||
world_points = projector.billboard_world_points(anchor, camera_path, local_points, face_scale)
|
||||
for offset, world_point in enumerate(world_points):
|
||||
positions[constants.FACE_START + offset] = world_point
|
||||
|
||||
active_indices = set(constants.active_point_indices(body_model))
|
||||
pairs, colors, thickness_list = constants.full_limb_data(body_model)
|
||||
|
||||
for pair_i, (a, b) in enumerate(pairs):
|
||||
if a in positions and b in positions:
|
||||
thickness = thickness_list[pair_i] * thickness_scale
|
||||
if thickness <= 0.0:
|
||||
continue
|
||||
data.lines.append((positions[a], positions[b], _mcolor(colors[pair_i]), thickness))
|
||||
|
||||
for index, pos in positions.items():
|
||||
if index not in active_indices:
|
||||
continue
|
||||
radius = constants.KEYPOINT_POINT_RADIUS[index] * radius_scale
|
||||
if radius <= 0.0:
|
||||
continue
|
||||
data.points.append((pos, _mcolor(constants.KEYPOINT_POINT_COLORS[index]), radius * 2.0))
|
||||
|
||||
return data
|
||||
|
||||
def addUIDrawables(self, obj_path, draw_manager, frame_context, data):
|
||||
if not isinstance(data, OpenPoseUserData):
|
||||
return
|
||||
|
||||
draw_manager.beginDrawable()
|
||||
draw_manager.setDepthPriority(omr.MRenderItem.sActiveWireDepthPriority)
|
||||
|
||||
for p0, p1, color, thickness in data.lines:
|
||||
draw_manager.setColor(color)
|
||||
draw_manager.setLineWidth(thickness)
|
||||
draw_manager.line(p0, p1)
|
||||
|
||||
for pos, color, point_size in data.points:
|
||||
draw_manager.setColor(color)
|
||||
draw_manager.setPointSize(point_size)
|
||||
draw_manager.point(pos)
|
||||
|
||||
draw_manager.endDrawable()
|
||||
|
||||
|
||||
def register(fn_plugin):
|
||||
omr.MDrawRegistry.registerDrawOverrideCreator(
|
||||
mapping_node.DRAW_CLASSIFICATION,
|
||||
mapping_node.DRAW_REGISTRANT_ID,
|
||||
OpenPoseDrawOverride.creator,
|
||||
)
|
||||
|
||||
|
||||
def deregister(fn_plugin):
|
||||
omr.MDrawRegistry.deregisterDrawOverrideCreator(
|
||||
mapping_node.DRAW_CLASSIFICATION,
|
||||
mapping_node.DRAW_REGISTRANT_ID,
|
||||
)
|
||||
Reference in New Issue
Block a user