"""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)) def _fill_default_eye_ear_positions(dep_fn, mapped, positions): """Fill in synthetic REye/LEye/REar/LEar world positions (see constants.DEFAULT_EYE_EAR_LOCAL_OFFSETS) for whichever of those slots aren't already explicitly mapped, anchored at and oriented by the joint mapped to Nose (the head) -- so they turn with the head's actual rotation rather than facing the camera. No-op if Nose isn't mapped. """ missing_offsets = { index: offset for index, offset in constants.DEFAULT_EYE_EAR_LOCAL_OFFSETS.items() if index not in positions } if not missing_offsets: return nose_index = constants.NAME_TO_INDEX["Body_Nose"] if nose_index not in mapped: return anchor = positions[nose_index] face_scale = mapping_node.get_face_scale(dep_fn) computed = projector.default_head_relative_points(anchor, mapped[nose_index], face_scale, missing_offsets) positions.update(computed) 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_use_default_eyes_ears(dep_fn): _fill_default_eye_ear_positions(dep_fn, mapped, positions) 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) neck_index = constants.NAME_TO_INDEX["Body_Neck"] if mapping_node.get_face_follow(dep_fn) == mapping_node.FACE_FOLLOW_NECK and neck_index in mapped: world_points = projector.oriented_world_points( anchor, mapped[neck_index], local_points, face_scale ) else: 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, )