Files
PoseRenderer/python/openpose_renderer/draw_override.py
T
indigo e2f2e6668a 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>
2026-07-14 08:41:57 +08:00

136 lines
5.0 KiB
Python

"""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,
)