// =========================================================================== // Copyright 2018 Autodesk, Inc. All rights reserved. // // Use of this software is subject to the terms of the Autodesk license // agreement provided at the time of installation or download, or which // otherwise accompanies this software in either electronic or hard copy form. // =========================================================================== // // General Batch render interface. // // Loop over renderable layers, // and call the renderer associated with the layer. // This procedure is used for interactive batch and command line rendering, // for maya software renderer, hardware renderer, mental ray for maya // renderer, vector renderer. // proc append(string $sa[], string $s) { $sa[size($sa)] = $s; } proc int renderTargetHasOutput(string $rt) { if ($rt == "") { error((uiRes("m_mayaBatchRenderProcedure.kEmptyRenderTarget"))); } string $colorAttr = ($rt + ".outColor"); string $alphaAttr = ($rt + ".outAlpha"); string $colorDst[] = `connectionInfo -dfs $colorAttr`; string $alphaDst[] = `connectionInfo -dfs $alphaAttr`; int $hasOutput = (size($colorDst) > 0) || (size($alphaDst) > 0); return $hasOutput; } proc string renderLayerFromConnection(string $inRl) { string $outRl = "defaultRenderLayer"; if ($inRl != "" ) { string $buffer[]; tokenize($inRl, ".", $buffer); $outRl = $buffer[0]; } return $outRl; } proc string[] renderableRenderTargets(string $allRenderTargets[]) { // Return render targets renderable in current render layer. string $renderable[]; for ($rt in $allRenderTargets) { if( `getAttr ($rt + ".renderable")` ) append($renderable, $rt); } return $renderable; } proc clearRendered(string $renderLayers[]) { // Loop over the layers once and initialize an empty set of // rendered render targets per layer. python("rendered = {}"); string $rl; for ($rl in $renderLayers) { python("rendered['" + $rl + "'] = set([])"); } } proc int rendered(string $rt, string $rl) { int $found = python("'" + $rt + "' in rendered['" + $rl + "']"); return $found; } proc markRendered(string $rt, string $rl) { python("rendered['" + $rl + "'].add('" + $rt + "')"); } proc string currentRenderLayer() { return `editRenderLayerGlobals -q -currentRenderLayer`; } proc renderTargetUnsupportedWarning(string $rt, string $renderer) { string $msg = (uiRes("m_mayaBatchRenderProcedure.kRenderTargetUnsupported")); string $msgDisp = `format -s $renderer -s $rt $msg`; warning($msgDisp); } proc setCreate(string $set) { python($set + " = set([])"); } proc setInsert(string $set, string $value) { python($set + ".add('" + $value + "')"); } // Remove element from set if present. proc setDiscard(string $set, string $value) { python($set + ".discard('" + $value + "')"); } // Convert set to string array. Order defined by Python set. Only // works for set of strings. proc string[] setValues(string $set) { string $values[] = python("list(" + $set + ")"); return $values; } proc mapCreate(string $map) { python($map + " = {}"); } proc mapInsert(string $map, string $key, string $value) { python($map + "['" + $key + "'] = '" + $value + "'"); } proc string mapFind(string $map, string $key) { string $found = python($map + "['" + $key + "']"); return $found; } proc int mapFindKey(string $map, string $key) { int $found = python("'" + $key + "' in " + $map); return $found; } proc multimapCreate(string $map) { mapCreate($map); } proc string[] multimapFind(string $map, string $key) { string $stringArray[]; string $found = mapFind($map, $key); if ($found != "") { tokenize($found, $stringArray); } return $stringArray; } proc string[] multimapKeys(string $map) { string $keys[] = python($map + ".keys()"); return $keys; } proc multimapAppend(string $map, string $key, string $value) { if ($value != "") { string $append = $value; if (mapFindKey($map, $key)) { string $found = mapFind($map, $key); $append = ($found + " " + $value); } mapInsert($map, $key, $append); } } proc multimapPrepend(string $map, string $key, string $value) { if ($value != "") { string $prepend = $value; if (mapFindKey($map, $key)) { string $found = mapFind($map, $key); $prepend = ($value + " " + $found); } mapInsert($map, $key, $prepend); } } proc initRenderLayerRenderTargets(string $multimap, string $unassigned) { // Render target can be categorized along two dimensions: // 1) Are they the root of a subgraph or not? // 2) Are they explicitly assigned to a render layer or not? // // Finding render tasks for the root of a subgraph first, and // rendering all their upstream render targets, ensures that we don't ask // for render tasks of these upstream render targets later (since they will // already have been rendered). Since dependencies are not stored // between runs of the getRenderTasks command, we wish to run it // as seldom as possible, and save on duplicate dependency computation. // // If a render target does not have a render layer assigned to it, // we will render it in the default render layer if it is a subgraph // root. Other render targets will have render layers assigned to // them by downstream render targets. // Force existence of default render layer in multimap, so we know // we'll loop through it. This allows us to guarantee we'll look for a // renderable camera in that layer, to support unassigned render // target rendering at end of processing. mapInsert($multimap, "defaultRenderLayer", ""); string $allRt[] = `ls -type renderTarget`; string $rt; for ($rt in $allRt) { renderTargetWarnIfNoInput($rt); // Get the render layer associated with the render target. string $rlAttr = $rt + ".renderLayer"; string $rl = `connectionInfo -sfd $rlAttr`; // Is the render target the root of a subgraph? Relying on // presence of output connection does not detect the fringe case // where a render target has downstream nodes, none of which is a // render target. In such a case, the downstream nodes don't // influence batch rendering in any way (since they're not // connected to anything renderable). This somewhat degenerate // case is handled as an upstream (non-root) render target. int $isRoot = !renderTargetHasOutput($rt); if ($isRoot) { $rl = renderLayerFromConnection($rl); // Render target is a subgraph root, prepend it to be rendered first. multimapPrepend($multimap, $rl, $rt); } else { if ($rl == "" ) { setInsert($unassigned, $rt); } else { multimapAppend($multimap, $rl, $rt); } } } } // Copy-pasted from AWplugins/viceAssets/scripts/renderableCameras.mel proc string[] renderableCameras() { string $renderableCameras[]; string $cameras[]; $cameras = `ls -cameras`; string $camera; for ($camera in $cameras) { if (`getAttr ($camera + ".renderable")`) { $renderableCameras[size($renderableCameras)] = $camera; } } return $renderableCameras; } // Batch rendering is done for the 3D scene before any render target // rendering. This means that render target render tasks that refer // to image sources that have already been rendered in the 3D batch // render can simply be discarded. proc string[] batchRenderTask( string $renderTarget, string $renderLayer, string $camera ) { string $tasks[] = `getRenderTasks -c $camera -rl $renderLayer $renderTarget`; string $rtTasks[]; string $task; for ($task in $tasks) { python("task = " + $task); if (python("task['renderTarget']") != "") { append($rtTasks, $task); } } return $rtTasks; } // Get the render procedure in the current render layer. proc string getRenderProcedure( string $currentRenderer, int $isInteractiveBatch ) { string $cmd = ""; if( $isInteractiveBatch ) { $cmd += `renderer -q -batchRenderProcedure ($currentRenderer)`; if ($cmd == "") { string $msg = (uiRes("m_mayaBatchRenderProcedure.kNoBatchRender")); warning (`format -stringArg $currentRenderer $msg`); } } else { $cmd += `renderer -q -commandRenderProcedure ($currentRenderer)`; if ($cmd == "") { string $msg = (uiRes("m_mayaBatchRenderProcedure.kNoCmdLine")); warning (`format -stringArg $currentRenderer $msg`); } } if ($cmd != "" && rendererRenderTargetSupport($currentRenderer)) { string $renderLayer = currentRenderLayer(); mapInsert("rtCommand", $renderLayer, $cmd); } return $cmd; } // Get the render target render procedure in the current render layer, // either from cache, or by querying the renderer. proc string getRenderTargetRenderProcedure( string $currentRenderer, int $isInteractiveBatch ) { string $cmd = ""; string $renderLayer = currentRenderLayer(); // Look up the render procedure in our cache. if (mapFindKey("rtCommand", $renderLayer)) { $cmd = mapFind("rtCommand", $renderLayer); } else { $cmd = getRenderProcedure($currentRenderer, $isInteractiveBatch); } return $cmd; } proc renderRenderTarget( string $rt, string $rtLayer, string $camera, string $option, string $unassignedRt, int $isInteractiveBatch ) { // Get render target-only render tasks for this render target. string $tasks[] = batchRenderTask($rt, $rtLayer, $camera); string $task; for ($task in $tasks) { python("task = " + $task); string $taskRt = python("task['renderTarget']"); string $taskRenderLayer = python("task['renderLayer']"); // Have we rendered this render target in this layer before? if (!rendered($taskRt, $taskRenderLayer)) { // Unfortunately for performance, might require a change in // render layer. Ideally, we would minimize render layer // switches over all layers by having a single list of render // targets to render. if (currentRenderLayer() != $taskRenderLayer) { editRenderLayerGlobals -currentRenderLayer $taskRenderLayer; } // Pathological case: render target to render can be assigned to // a layer whose renderer does not support them. $currentRenderer = currentRenderer(); if (rendererRenderTargetSupport($currentRenderer)) { // Fetch the command for the renderer, evaluate it, and mark // the render target as rendered in that layer. Note how we // don't check the renderable flag for the render target: as // per the spec, a render target not marked as renderable will // be rendered if a downstream renderable render target // depends on it. string $cmd = getRenderTargetRenderProcedure($currentRenderer, $isInteractiveBatch); if ($cmd != "") { $cmd += "(\" -layer " + $taskRenderLayer + " -renderTarget " + $taskRt + " " + $option + " \")"; if (catch(eval($cmd))) { string $msg = (uiRes("m_mayaBatchRenderProcedure.kWarningMessage")); $msg = `format -stringArg $taskRt $msg`; warning ($msg); } } // Set the render target as "rendered" even if there was no // command for it, or it failed, as further attempts to render // it will hit the same issue. Remove the render target from // the unassigned set if it was there. markRendered($taskRt, $taskRenderLayer); setDiscard($unassignedRt, $taskRt); } else { renderTargetUnsupportedWarning($taskRt, $currentRenderer); } } // if not rendered } // for all tasks } proc renderRenderTargets( string $allRt[], string $rtLayer, string $camera, string $option, string $unassignedRt, int $isInteractiveBatch ) { string $renderTargets[] = renderableRenderTargets($allRt); string $rt; for ($rt in $renderTargets) { // Have we rendered this render target in this layer before? if (!rendered($rt, $rtLayer)) { renderRenderTarget($rt, $rtLayer, $camera, $option, $unassignedRt, $isInteractiveBatch); } } } global proc mayaBatchRenderProcedure( int $isInteractiveBatch, // interactive batch or command line rendering string $sceneName, // the original scene name before export string $layer, // render specific layer string $renderer, // use specific renderer is not empty string. string $option // optional arg to the render command ) { // gather any values we do not want overwritten by a layer // we will set these values per-layer below to // bypass any override system (RenderLayers, RenderSetup, etc.) // This must be done before switching to defaultRenderLayer because // switch may put some of these attribute back to the value of the // defaultRenderLayer when unapplying the overrides. global string $gMayaBatchRenderIgnoreOverridesForAttributes[]; string $names[] = $gMayaBatchRenderIgnoreOverridesForAttributes; string $values[]; string $types[]; for ($attrNum =0; $attrNum < size($names); ++$attrNum) { string $attrName = $names[$attrNum]; if (!catch($values[$attrNum] = eval("getAttr " + $attrName))) { if(!catch($types[$attrNum] = eval("getAttr -type " + $attrName))) { if($types[$attrNum] == "string") { $values[$attrNum] = "\""+$values[$attrNum]+"\""; } } } } // get list of render layers string $renderLayers[]; clear($renderLayers); $renderLayers = `listConnections renderLayerManager.renderLayerId`; // switch to defaultRenderLayer editRenderLayerGlobals -currentRenderLayer $renderLayers[0]; // Determine what are are being asked to render: 2D alone (1), 3D // alone (2), or both (0). int $renderedOutput = `getAttr defaultRenderGlobals.renderedOutput`; // The batch rendering is performed on a copy of the scene. // No need to setup render preparation with prepareRender -setup (including saving the assembly // configuration, if required by default render traversal set). // Run preRender traversal of default render traversal set. prepareRender -invokePreRender; // execute pre mel script string $melScript = `getAttr defaultRenderGlobals.preMel`; if( $melScript != "" ) eval $melScript; // render specific layer if( $layer != "" ) { for($renderLayer in $renderLayers) { if( $renderLayer == $layer ) { clear($renderLayers); $renderLayers[0] = $layer; break; } } if( size($renderLayers)!=1 ) { // could not find the layer string $msg = (uiRes("m_mayaBatchRenderProcedure.kNoLayer")); error(`format -stringArg $layer $msg`); return; } } // determine scene name string $realSceneName = $sceneName; if ($realSceneName == "") { $realSceneName = `file -q -ns`; } if ($isInteractiveBatch) { // interactive batch appends pid to scene name, remove to get original string $temp = $realSceneName; if (`match "__[0-9]+$" $temp` != "") { $temp = `match ".+[^0-9]+" $temp`; $temp = substring($temp, 1, size($temp)-2); } if (size($temp) > 0) { $realSceneName = $temp; } } int $numLayers = size($renderLayers); $renderLayers = sortLayers($renderLayers); // Save up command we'll need for render target (2D) rendering, // for each renderable layer with a renderer that supports render // target rendering and a non-null render command. string $pythonCleanup[]; mapCreate("rtCommand"); // Render command map, keyed on render layer name append($pythonCleanup, "rtCommand"); int $numRenderableLayers = 0; // render all the layers // At time of writing (5-Nov-2010), render targets as inputs to // textures in the 3D scene are not supported, so the 3D scene // does not depend on 2D render target rendering, and can safely // be done first. // // Note that even if we are asked to perform only the 2D render // (render targets only, defaultRenderGlobals.renderedOutput == // 1), we go through the motions of looping through the layers for // a 3D render, building image file prefixes and collecting render // commands as we go. We simply omit to call the render command // and the pre- and post- layer layer MEL scripts. This preserves // code simplicity, at a cost to performance of switching render layers. for($i=0; $i<$numLayers; $i++) { // render this layer if it is renderable. // check renderable flag before switching to the layer if( `getAttr ($renderLayers[$i] + ".renderable")` ) { $numRenderableLayers = $numRenderableLayers + 1; // switch render layer! if(catch(`editRenderLayerGlobals -currentRenderLayer $renderLayers[$i]`)) { string $msg = (uiRes("m_mayaBatchRenderProcedure.kCannotRenderLayer")); string $msgDisp = `format -s $renderLayers[$i] $msg`; warning($msgDisp); continue; } // override the overrides... // Sometimes we want to update overrides for non-active layers. // This can only be done after we have activated the layers. // So we gather the attributes we want to override and // we apply the values after the layer is active to update the stored override. for ($attrNum=0; $attrNum < size($names); ++$attrNum) { string $attrName = $names[$attrNum]; string $paramVal = $values[$attrNum]; $evalStr = "setAttr " + $attrName + " " + $paramVal; if (catchQuiet(eval($evalStr))) { // if an error was thrown then we probably need to try to set the value again // This time specifying the type for non-numeric types. $evalStr = "setAttr -type \"" + $types[$attrNum] + "\" " + $attrName + " " + $paramVal; catch(eval($evalStr)); } } // Interactive batch rendering case // If the image file name is not specified, // the scene name is used internally. // However, the scene name contains the process ID, // so use the original scene name which was passed down in $imageName // set the imageFilePrefix for interactive batch rendering string $ifp = "defaultRenderGlobals.imageFilePrefix"; if( $isInteractiveBatch && `getAttr -settable $ifp` ) { string $imageName = `getAttr $ifp`; if( $imageName == "" ) { $imageName = $realSceneName; } // there may be several occurences of %s, make sure to // substitutes them all string $oldImageName = ""; while ($oldImageName != $imageName) { $oldImageName = $imageName; $imageName = substitute("", $imageName, $realSceneName); $imageName = substitute("%s", $imageName, $realSceneName); $imageName = substitute("%/s", $imageName, ($realSceneName+"/")); $imageName = substitute("%-s", $imageName, ("-"+$realSceneName)); $imageName = substitute("%_s", $imageName, ("_"+$realSceneName)); $imageName = substitute("%\\.s", $imageName, ("."+$realSceneName)); } setAttr $ifp -type "string" $imageName; } string $currentRenderer = $renderer; // Run preRenderLayer traversal of default render traversal set. if($renderedOutput != 1) { prepareRender -invokePreRenderLayer; } // execute pre render layer mel script $melScript = `getAttr defaultRenderGlobals.preRenderLayerMel`; // Render targets only is 1. if( $melScript != "" && $renderedOutput != 1) eval $melScript; // if renderer is not overriden by the command line arg, use current renderer. if( size($renderer) == 0 ) $currentRenderer = currentRenderer(); // dynamics runup dynRunupForBatchRender(); string $cmd = getRenderProcedure($currentRenderer, $isInteractiveBatch); if( $cmd!="" ) { $cmd += "(\" -layer " + $renderLayers[$i] + " " + $option + " \")"; } if ($renderedOutput != 1) { // Some clients rely on the error not being caught so that // the command engine can terminate with a proper error code. // MAYA-22346 string $mayaBatchRenderingStopsOnError = getenv("MAYA_BATCH_RENDERING_STOPS_ON_ERROR"); if(size($mayaBatchRenderingStopsOnError) > 0) { eval($cmd); } else if(catch(`eval $cmd`)) { string $msg = (uiRes("m_mayaBatchRenderProcedure.kRenderCmdFail")); string $msgDisp = `format -s $renderLayers[$i] $msg`; warning($msgDisp); } } // execute post render layer mel script $melScript = `getAttr defaultRenderGlobals.postRenderLayerMel`; if( $melScript != "" && $renderedOutput != 1) eval $melScript; // Run postRenderLayer traversal of default render traversal set. if ($renderedOutput != 1) { prepareRender -invokePostRenderLayer; } } } if($numRenderableLayers == 0) { error ((uiRes("m_mayaBatchRenderProcedure.kNoRenderableLayers"))); } // Clear the list of overrides. They are only valid for a single render. clear($gMayaBatchRenderIgnoreOverridesForAttributes); // switch to the base layer, and check is we want to combine // images in a sequence of layered PSD files. editRenderLayerGlobals -currentRenderLayer $renderLayers[0]; int $format = `getAttr defaultRenderGlobals.imageFormat`; if ($format == 36) // Layered PSD { if( $isInteractiveBatch && $realSceneName!="" ) renderLayerPostProcess -sceneName $realSceneName; else renderLayerPostProcess ; } // Render target (2D) rendering. Render target render tasks // tell us exactly which 3D render layers and passes to render and // in what order, but simpler to unconditionally do 3D rendering // over all render layers first and render target rendering as a second // pass because: // 1) Render targets are not necessarily connected to all renderable // render layers (or equivalently their render passes), so // render target render tasks do not necessarily render all // renderable render layers. // 2) If a render target depends on 3D renders from more than one // render layer (an important use case), the 3D rendering for // these layers must be done first. // defaultRenderGlobals.renderedOutput == 2 means omit render targets. if ($renderedOutput != 2) { // Set the default render layer to renderable, so that render // targets assigned to it will unconditionally get rendered. int $defaultRenderable = `getAttr defaultRenderLayer.renderable`; setAttr defaultRenderLayer.renderable true; // Create a render target multimap, keyed on render layer name. // Add render targets to the appropriate multimap entry by looking // at the renderLayer connection. No connection means default // render layer. string $rlRenderTargets = "rlRenderTargets"; string $unassignedRt = "unassignedRt"; multimapCreate($rlRenderTargets); setCreate($unassignedRt); append($pythonCleanup, $rlRenderTargets); append($pythonCleanup, $unassignedRt); initRenderLayerRenderTargets($rlRenderTargets, $unassignedRt); // The list of render layers for which we have render targets to // render is the keys of the render layer multimap. string $rtLayer[] = multimapKeys($rlRenderTargets); // Keep a set of rendered render targets per layer, to avoid // rendering render targets more than once in a layer. append($pythonCleanup, "rendered"); clearRendered($rtLayer); // When we render the default render layer, cache a renderable // camera for it, in case we need it to render render targets // with an unassigned render layer. string $defaultLayerCamera = ""; // Not checking render layer switch status in loop below: switch will // succeed because we only kept layers to which we sucessfully switched // in the 3D render loop above. int $rtNumLayers = size($rtLayer); for ($i = 0; $i < $rtNumLayers; $i++) { editRenderLayerGlobals -currentRenderLayer $rtLayer[$i]; // Batch render code will internally loop over all renderable // cameras, but render task discovery code requires a single // one, so just use the first renderable camera we find in the // current layer. If there is none in the layer, no need to // loop over render targets. string $cameras[] = renderableCameras(); if (size($cameras) > 0) { if ($rtLayer[$i] == "defaultRenderLayer") { $defaultLayerCamera = $cameras[0]; } // Create array of renderable render targets for the layer. string $allRt[] = multimapFind($rlRenderTargets, $rtLayer[$i]); renderRenderTargets( $allRt, $rtLayer[$i], $cameras[0], $option, $unassignedRt, $isInteractiveBatch); } } // Mop up any render targets with their render layer left // unassigned by rendering them in the default render layer. // This will be non-root render targets without any downstream // render targets, a fringe case. string $anyRtLeft[] = setValues($unassignedRt); if (size($anyRtLeft) > 0 && $defaultLayerCamera != "") { string $rl = "defaultRenderLayer"; editRenderLayerGlobals -currentRenderLayer $rl; renderRenderTargets( $anyRtLeft, $rl, $defaultLayerCamera, $option, $unassignedRt, $isInteractiveBatch); } // Restore defaultRenderLayer renderable attribute value. Not // strictly necessary, as batch rendering is done on a scene copy. setAttr defaultRenderLayer.renderable $defaultRenderable; // Python clean up, and set the current render layer back to what // it was after the render layer post process. string $toDelete; for ($toDelete in $pythonCleanup) { python("del " + $toDelete); } editRenderLayerGlobals -currentRenderLayer $renderLayers[0]; } // execute post mel script $melScript = `getAttr defaultRenderGlobals.postMel`; if( $melScript != "" ) eval $melScript; // Run postRender traversal of default render traversal set. prepareRender -invokePostRender; // No need to clean up after rendering with prepareRender -restore, // as batch rendering is performed on a copy of the scene, with the copy // discarded after rendering. }