Render depth map using python
-
I'm creating a simple Python plugin and i need to render depth map using c4d.RENDERFLAGS_EXTERNAL. I'm not sure how to do this.
import c4d from c4d import gui, plugins, storage import os PLUGIN_ID = 1234567 def GetFilterTypeFromFilename(filename): ext = os.path.splitext(filename)[1].lower() if ext == '.bmp': return c4d.FILTER_BMP elif ext == '.jpg' or ext == '.jpeg': return c4d.FILTER_JPG elif ext == '.png': return c4d.FILTER_PNG elif ext == '.tif' or ext == '.tiff': return c4d.FILTER_TIF elif ext == '.exr': return c4d.FILTER_EXR else: return c4d.FILTER_PNG # Default to PNG if extension is unrecognized class RenderDialog(gui.GeDialog): DESTINATION_FILE = 1001 BROWSE_BUTTON = 1002 RENDER_BUTTON = 2000 def CreateLayout(self): self.SetTitle("External Render Plugin") # Add Destination File Field self.GroupBegin(id=0, flags=c4d.BFH_SCALEFIT, cols=2) self.AddStaticText(id=0, flags=c4d.BFH_LEFT, name="Destination File:") self.AddEditText(id=self.DESTINATION_FILE, flags=c4d.BFH_SCALEFIT) self.GroupEnd() # Add Browse Button self.AddButton(id=self.BROWSE_BUTTON, flags=c4d.BFH_LEFT, name="Browse") # Add Render Button self.AddButton(id=self.RENDER_BUTTON, flags=c4d.BFH_CENTER, name="Render") return True def Command(self, id, msg): if id == self.BROWSE_BUTTON: path = storage.SaveDialog(title="Select Destination File") if path: self.SetString(self.DESTINATION_FILE, path) return True elif id == self.RENDER_BUTTON: path = self.GetString(self.DESTINATION_FILE) if not path: gui.MessageDialog("Please specify a destination file.") return True # Get the active document and render settings doc = c4d.documents.GetActiveDocument() rd = doc.GetActiveRenderData() rdata = rd.GetDataInstance().GetClone(c4d.COPYFLAGS_NONE) # Set render settings rdata[c4d.RDATA_PATH] = path rdata[c4d.RDATA_RENDERENGINE] = c4d.RDATA_RENDERENGINE_STANDARD # Create a bitmap to render into width = int(rdata[c4d.RDATA_XRES]) height = int(rdata[c4d.RDATA_YRES]) bmp = c4d.bitmaps.BaseBitmap() result = bmp.Init(width, height) if result != c4d.IMAGERESULT_OK: gui.MessageDialog("Failed to initialize bitmap.") return True # Trigger render render_result = c4d.documents.RenderDocument( doc, # Document to render rdata, # Render settings bmp, # Bitmap to render into c4d.RENDERFLAGS_EXTERNAL | c4d.RENDERFLAGS_DONTANIMATE # Flags ) if render_result != c4d.RENDERRESULT_OK: gui.MessageDialog("Render failed.") else: # Determine the appropriate file format filter filter_type = GetFilterTypeFromFilename(path) # Save the rendered image save_result = bmp.Save(path, filter_type, c4d.BaseContainer()) if save_result != c4d.IMAGERESULT_OK: gui.MessageDialog("Failed to save image.") else: gui.MessageDialog("Render completed and saved.") return True return False class ExternalRenderPlugin(plugins.CommandData): def __init__(self): self.dlg = None def Execute(self, doc): if not self.dlg: self.dlg = RenderDialog() self.dlg.Open(c4d.DLG_TYPE_ASYNC, defaultw=400, defaulth=100) return True if __name__ == "__main__": plugins.RegisterCommandPlugin( id=PLUGIN_ID, str="External Render Plugin", info=0, icon=None, help="Render current frame to a specified file", dat=ExternalRenderPlugin() )
-
Hello @Gregor-M,
Thank you for reaching out to us. You cannot really use
c4d.documents.RenderDocument
for this as it does not rally support multipass output. But you can use the render queue.Cheers,
FerdinandResult
Code
"""Renders a depth channel for the active document and shows it in the picture viewer. This script is a demonstration of how to render a depth pass for the active document and show it in the picture viewer. It does so by saving the document to a temporary file, setting up the depth pass, and then rendering the document using the batch renderer. After the rendering is done, the depth pass is loaded and shown in the picture viewer. We cannot use RenderDocument() to render a depth pass, as it does not support multipass channels. And we also cannot just use the render command, as a script manager script is blocking for commands, i.e., the render command will only be executed after the script has finished. # Requirements - The document must be saved before rendering. - The render engine must be set to Standard Renderer, as the depth pass is not supported by all other engines. If yours does, you can remove or adjust the check in the script. But Redshift has for example its own pass system. """ import c4d import os import time import mxutils doc: c4d.documents.BaseDocument # The active document. def EnableMultiPass(rData: c4d.documents.RenderData, passType: int) -> None: """Assures that the given #rData has a multipass of the given #passType. """ # Find an existing pass of the given type. hasPass: bool = False element: c4d.BaseList2D = rData.GetFirstMultipass() while element: if element.GetDataInstance()[c4d.MULTIPASSOBJECT_TYPE] == passType: element.DelBit(c4d.BIT_VPDISABLED) # Make sure it is enabled. hasPass = True break element = element.GetNext() # If the pass was not found, create a new one. if not hasPass: element: c4d.BaseList2D = mxutils.CheckType(c4d.BaseList2D(c4d.Zmultipass)) element[c4d.MULTIPASSOBJECT_TYPE] = passType rData.InsertMultipass(element) def main(): """Run the script. """ # Get the name and path of the current document and bail when it is an unsaved document. name: str = os.path.splitext(doc.GetDocumentName())[0] path: str = doc.GetDocumentPath() if not path: raise ValueError("The document must be saved before rendering.") # Get the batch render and bail when it is already running. batch: c4d.documents.BatchRender = c4d.documents.GetBatchRender() if batch.IsRendering(): raise ValueError("The batch render is already running.") # Define a path for a copy of this document and a path to save the multipass images to. documentPath: str = os.path.join(path, f"{name}_job.c4d") multipassPath: str = os.path.join(path, f"{name}_job_multipass") depthPngPath: str = f"{multipassPath}_depth.png" # Get the render data from the document and get and set the relevant settings. rData: c4d.documents.RenderData = doc.GetActiveRenderData() if rData[c4d.RDATA_RENDERENGINE] != c4d.RDATA_RENDERENGINE_STANDARD: raise ValueError("The render engine must be set to Standard Renderer.") # Setup the depth pass. EnableMultiPass(rData, c4d.VPBUFFER_DEPTH) rData[c4d.RDATA_MULTIPASS_ENABLE] = True rData[c4d.RDATA_MULTIPASS_SAVEIMAGE] = True rData[c4d.RDATA_MULTIPASS_SAVEONEFILE] = False rData[c4d.RDATA_MULTIPASS_SAVEFORMAT] = c4d.FILTER_PNG rData[c4d.RDATA_MULTIPASS_FILENAME] = multipassPath # We cannot use RenderDocument() to render a depth pass, as it does not support multipass # channels. So, we must use the render queue and for that also setup the render data (which # is also why we did set the multipass save settings above). # Save the new file state to #documentPath and then push it into the render queue. if not c4d.documents.SaveDocument(doc, documentPath, c4d.SAVEDOCUMENTFLAGS_0, c4d.FORMAT_C4DEXPORT): raise ValueError("Failed to save the document.") # Disable all possibly already existing jobs in the batch renderer, add a new job in front, # and carry out the rendering. for i in range(batch.GetElementCount()): batch.EnableElement(i, False) batch.AddFile(documentPath, 0) batch.SetRendering(c4d.BR_START) c4d.StatusSetText(f"Rendering {name}...") c4d.StatusSetSpin() while batch.IsRendering(): time.sleep(.25) # The batch render is done, we check if the depth pass exists and load it. c4d.StatusClear() if not os.path.exists(depthPngPath): raise ValueError(f"Failed to render the multipass image to {depthPngPath}.") bmp: c4d.bitmaps.BaseBitmap = c4d.bitmaps.BaseBitmap() if not bmp.InitWith(depthPngPath): raise ValueError(f"Failed to load bitmap from: {depthPngPath}") # Do something with the bitmap, we just show it in the picture viewer. c4d.bitmaps.ShowBitmap(bmp) if __name__=='__main__': main()
-
Thank you for the quick answer.
Unfortunately this approach was quite destructive for my workflow, so i found a workaround using materials and 3d gradients. For anyone who want to render z-depth in python from c4d scene, this may be the approach. There may be some tweaks considering color space.
It would be nice to get access to this kind of data inside cinema 4d's python without crazy workarounds.import c4d from c4d import gui, plugins, storage import os PLUGIN_ID = 1234567 # Replace this ID with your unique plugin ID from Maxon def GetFilterTypeFromFilename(filename): ext = os.path.splitext(filename)[1].lower() if ext == '.bmp': return c4d.FILTER_BMP elif ext == '.jpg' or ext == '.jpeg': return c4d.FILTER_JPG elif ext == '.png': return c4d.FILTER_PNG elif ext == '.tif' or ext == '.tiff': return c4d.FILTER_TIF elif ext == '.exr': return c4d.FILTER_EXR else: return c4d.FILTER_PNG # Default to PNG if extension is unrecognized class RenderDialog(gui.GeDialog): DESTINATION_FILE = 1001 BROWSE_BUTTON = 1002 RENDER_BUTTON = 2000 def CreateLayout(self): self.SetTitle("External Render Plugin") # Add Destination File Field self.GroupBegin(id=0, flags=c4d.BFH_SCALEFIT, cols=2) self.AddStaticText(id=0, flags=c4d.BFH_LEFT, name="Destination File:") self.AddEditText(id=self.DESTINATION_FILE, flags=c4d.BFH_SCALEFIT) self.GroupEnd() # Add Browse Button self.AddButton(id=self.BROWSE_BUTTON, flags=c4d.BFH_LEFT, name="Browse") # Add Render Button self.AddButton(id=self.RENDER_BUTTON, flags=c4d.BFH_CENTER, name="Render") return True def Command(self, id, msg): if id == self.BROWSE_BUTTON: path = storage.SaveDialog(title="Select Destination File") if path: self.SetString(self.DESTINATION_FILE, path) return True elif id == self.RENDER_BUTTON: path = self.GetString(self.DESTINATION_FILE) if not path: gui.MessageDialog("Please specify a destination file.") return True # Get the active document doc = c4d.documents.GetActiveDocument() # Create a large sphere object sphere = c4d.BaseObject(c4d.Osphere) sphere[c4d.PRIM_SPHERE_RAD] = 10000000 # Set radius to 10,000,000 m doc.InsertObject(sphere) c4d.EventAdd() # Check if a material named "LuminanceMaterial" already exists material = doc.SearchMaterial("LuminanceMaterial") if not material: # Create new material and set its parameters material = c4d.BaseMaterial(c4d.Mmaterial) material.SetName("LuminanceMaterial") material[c4d.MATERIAL_USE_COLOR] = False material[c4d.MATERIAL_USE_REFLECTION] = False material[c4d.MATERIAL_USE_LUMINANCE] = True # Create gradient shader and set it as luminance gradient_shader = c4d.BaseShader(c4d.Xgradient) if gradient_shader: gradient_shader[c4d.SLA_GRADIENT_TYPE] = c4d.SLA_GRADIENT_TYPE_3D_LINEAR # 3D Linear Gradient gradient_shader[c4d.SLA_GRADIENT_SPACE] = c4d.SLA_GRADIENT_SPACE_WORLD # World space gradient_shader[c4d.SLA_GRADIENT_CYCLE] = False material[c4d.MATERIAL_LUMINANCE_SHADER] = gradient_shader material.InsertShader(gradient_shader) # Insert material into the document doc.InsertMaterial(material) # Update gradient start and end points for the current camera gradient_shader = material[c4d.MATERIAL_LUMINANCE_SHADER] if gradient_shader: camera = doc.GetActiveObject() if not camera or camera.GetType() != c4d.Ocamera: camera = doc.GetRenderBaseDraw().GetSceneCamera(doc) if not camera: camera = doc.GetRenderBaseDraw().GetEditorCamera() if camera: start_position = camera.GetMg().off focus_distance = camera[c4d.CAMERAOBJECT_TARGETDISTANCE] end_position = start_position + (camera.GetMg().v3 * focus_distance) # Use v3 for z-axis direction gradient_shader[c4d.SLA_GRADIENT_START] = start_position gradient_shader[c4d.SLA_GRADIENT_END] = end_position # Enable material override in render settings rd = doc.GetActiveRenderData() rd[c4d.RDATA_MATERIAL_OVERRIDE] = True rd[c4d.RDATA_MATERIAL_OVERRIDE_LINK] = material # Set render settings rdata = rd.GetDataInstance().GetClone(c4d.COPYFLAGS_NONE) rdata[c4d.RDATA_PATH] = path rdata[c4d.RDATA_RENDERENGINE] = c4d.RDATA_RENDERENGINE_STANDARD # Create a bitmap to render into width = int(rdata[c4d.RDATA_XRES]) height = int(rdata[c4d.RDATA_YRES]) bmp = c4d.bitmaps.BaseBitmap() result = bmp.Init(width, height) if result != c4d.IMAGERESULT_OK: gui.MessageDialog("Failed to initialize bitmap.") return True # Trigger render render_result = c4d.documents.RenderDocument( doc, # Document to render rdata, # Render settings bmp, # Bitmap to render into c4d.RENDERFLAGS_EXTERNAL | c4d.RENDERFLAGS_DONTANIMATE # Flags ) if render_result != c4d.RENDERRESULT_OK: gui.MessageDialog("Render failed.") else: # Determine the appropriate file format filter filter_type = GetFilterTypeFromFilename(path) # Save the rendered image save_result = bmp.Save(path, filter_type, c4d.BaseContainer()) if save_result != c4d.IMAGERESULT_OK: gui.MessageDialog("Failed to save image.") else: gui.MessageDialog("Render completed and saved.") # Turn off material override after rendering rd[c4d.RDATA_MATERIAL_OVERRIDE] = False rd[c4d.RDATA_MATERIAL_OVERRIDE_LINK] = None # Delete the large sphere after rendering sphere.Remove() c4d.EventAdd() # Delete the material after rendering if material: material.Remove() c4d.EventAdd() # Refresh the document c4d.EventAdd() return True return False class ExternalRenderPlugin(plugins.CommandData): def __init__(self): self.dlg = None def Execute(self, doc): if not self.dlg: self.dlg = RenderDialog() self.dlg.Open(c4d.DLG_TYPE_ASYNC, defaultw=400, defaulth=100) return True if __name__ == "__main__": plugins.RegisterCommandPlugin( id=PLUGIN_ID, str="External Render Plugin", info=0, icon=None, help="Render current frame to a specified file", dat=ExternalRenderPlugin() )