"""Maya renderer integration for OpenPose_full output. Two pieces: * ``OpenposeRenderOverride`` -- a Viewport 2.0 ``MRenderOverride`` registered as "OpenPose" in the viewport panel's Renderer dropdown, using the same minimal scene/HUD/present operation chain as Maya's own reference render override sample. This makes "OpenPose" a normal, selectable renderer rather than gambling on undocumented GPU render-target pixel readback from Python for the actual image content (see the plan's flagged risk). * ``render_frame`` / the ``openposeRenderSequence`` command -- the actual OpenPose image generation: projects every ``openposeCharacter`` in the scene through a given camera and rasterizes them (projector.py + rasterizer.py, pure software, no GPU dependency) to a PNG per frame, and pushes each frame into Maya's Render View via ``renderWindowEditor(loadImage=...)`` -- the same mechanism third-party renderers use to display progress there, so the images are visible in the normal render workflow, not just written silently to disk. """ import os import maya.api.OpenMaya as om import maya.api.OpenMayaRender as omr import maya.cmds as cmds from . import constants from . import face_blendshapes from . import mapping_node from . import projector from . import rasterizer UI_NAME = "OpenPose" RENDER_OVERRIDE_NAME = "openposeRenderOverride" _override_instance = None class OpenposeRenderOverride(omr.MRenderOverride): def __init__(self, name): omr.MRenderOverride.__init__(self, name) self._operations = [ omr.MSceneRender("openposeSceneRender"), omr.MHUDRender(), omr.MPresentTarget("openposePresentTarget"), ] self._current_operation = -1 def uiName(self): return UI_NAME def supportedDrawAPIs(self): return omr.MRenderer.kAllDevices def setup(self, destination): return def cleanup(self): self._current_operation = -1 def startOperationIterator(self): self._current_operation = 0 return True def renderOperation(self): if 0 <= self._current_operation < len(self._operations): return self._operations[self._current_operation] return None def nextRenderOperation(self): self._current_operation += 1 return self._current_operation < len(self._operations) def render_frame(camera_path, width, height, output_path): """Project + rasterize every mapped openposeCharacter to a single PNG.""" characters = [] for dep_fn in mapping_node.iter_character_nodes(): try: enabled = dep_fn.findPlug(mapping_node.aEnableDisplay, False).asBool() except RuntimeError: enabled = True if not enabled: continue 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) keypoints = projector.project_character(mapped, camera_path, width, height) if mapping_node.get_face_source(dep_fn) == mapping_node.FACE_SOURCE_ARKIT_BLENDSHAPES: nose_index = constants.NAME_TO_INDEX["Body_Nose"] nose_x, nose_y, nose_confidence = keypoints[nose_index] if nose_confidence > 0.0 and nose_index in mapped: anchor = projector.world_position(mapped[nose_index]) 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): px, py, in_front = projector.project_point(world_point, camera_path, width, height) keypoints[constants.FACE_START + offset] = (px, py, 1.0 if in_front else 0.0) characters.append((keypoints, radius_scale, thickness_scale, body_model)) rasterizer.render_characters_to_file(characters, width, height, output_path) return output_path def _default_output_dir(): images_rule = cmds.workspace(fileRuleEntry="images", query=True) or "images" workspace_root = cmds.workspace(query=True, rootDirectory=True) out_dir = os.path.join(workspace_root, images_rule, "openpose") if not os.path.isdir(out_dir): os.makedirs(out_dir) return out_dir def _resolve_camera_path(camera_name): if camera_name: selection = om.MSelectionList() selection.add(camera_name) dag_path = selection.getDagPath(0) if dag_path.apiType() == om.MFn.kTransform: dag_path.extendToShape() return dag_path import maya.api.OpenMayaUI as omui return omui.M3dView.active3dView().getCamera() class RenderSequenceCommand(om.MPxCommand): kCmdName = "openposeRenderSequence" @staticmethod def creator(): return RenderSequenceCommand() @staticmethod def createSyntax(): syntax = om.MSyntax() syntax.addFlag("-s", "-startFrame", om.MSyntax.kDouble) syntax.addFlag("-e", "-endFrame", om.MSyntax.kDouble) syntax.addFlag("-cam", "-camera", om.MSyntax.kString) syntax.addFlag("-dir", "-outputDirectory", om.MSyntax.kString) syntax.addFlag("-sw", "-showInRenderView", om.MSyntax.kBoolean) return syntax def isUndoable(self): return False def doIt(self, args): parser = om.MArgParser(self.syntax(), args) start = ( parser.flagArgumentDouble("-s", 0) if parser.isFlagSet("-s") else cmds.playbackOptions(query=True, minTime=True) ) end = ( parser.flagArgumentDouble("-e", 0) if parser.isFlagSet("-e") else cmds.playbackOptions(query=True, maxTime=True) ) camera_name = parser.flagArgumentString("-cam", 0) if parser.isFlagSet("-cam") else None out_dir = parser.flagArgumentString("-dir", 0) if parser.isFlagSet("-dir") else _default_output_dir() show_in_render_view = parser.flagArgumentBool("-sw", 0) if parser.isFlagSet("-sw") else True if not os.path.isdir(out_dir): os.makedirs(out_dir) camera_path = _resolve_camera_path(camera_name) width, height = projector.get_render_resolution() scene_path = cmds.file(query=True, sceneName=True) scene_name = os.path.splitext(os.path.basename(scene_path))[0] if scene_path else "openpose" scene_name = scene_name or "openpose" if show_in_render_view: cmds.RenderViewWindow() original_time = cmds.currentTime(query=True) written = [] frame = int(start) while frame <= int(end): cmds.currentTime(frame) file_path = os.path.join(out_dir, "{0}.{1:04d}.png".format(scene_name, frame)) render_frame(camera_path, width, height, file_path) written.append(file_path) if show_in_render_view: cmds.renderWindowEditor("renderView", edit=True, loadImage=file_path) frame += 1 cmds.currentTime(original_time) self.setResult(written) def register(fn_plugin): global _override_instance fn_plugin.registerCommand( RenderSequenceCommand.kCmdName, RenderSequenceCommand.creator, RenderSequenceCommand.createSyntax ) _override_instance = OpenposeRenderOverride(RENDER_OVERRIDE_NAME) omr.MRenderer.registerOverride(_override_instance) def deregister(fn_plugin): global _override_instance if _override_instance is not None: omr.MRenderer.deregisterOverride(_override_instance) _override_instance = None fn_plugin.deregisterCommand(RenderSequenceCommand.kCmdName)