From c58df79014145fe9448ef3e863c4542176929d0d Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Tue, 16 Jul 2019 18:50:51 -0300 Subject: [PATCH] Arch: Improvements to OfflineRenderingUtils (ability to generate shadows) --- src/Mod/Arch/OfflineRenderingUtils.py | 101 +++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 11 deletions(-) diff --git a/src/Mod/Arch/OfflineRenderingUtils.py b/src/Mod/Arch/OfflineRenderingUtils.py index b39094fed9..0bc2e47308 100755 --- a/src/Mod/Arch/OfflineRenderingUtils.py +++ b/src/Mod/Arch/OfflineRenderingUtils.py @@ -155,7 +155,7 @@ class FreeCADGuiHandler(xml.sax.ContentHandler): elif tag == "Integer": self.currentval = int(attributes["value"]) elif tag == "String": - self.currentval = int(attributes["value"]) + self.currentval = attributes["value"] elif tag == "Float": self.currentval = float(attributes["value"]) elif tag == "ColorList": @@ -174,7 +174,8 @@ class FreeCADGuiHandler(xml.sax.ContentHandler): elif isinstance(self.currentval,list): self.currentval.append(attributes["value"]) elif tag == "Python": - self.currentval = (attributes["value"],attributes["encoded"],attributes["module"],attributes["class"]) + if "module" in attributes: + self.currentval = (attributes["value"],attributes["encoded"],attributes["module"],attributes["class"]) elif tag == "Camera": self.guidata["GuiCameraSettings"] = attributes["settings"] @@ -289,12 +290,14 @@ def getStepData(objects,colors): return data -def render(outputfile,scene=None,camera=None,zoom=False,width=400,height=300,background=(1.0,1.0,1.0)): +def render(outputfile,scene=None,camera=None,zoom=False,width=400,height=300,background=(1.0,1.0,1.0),lightdir=None): - """render(outputfile,scene=None,camera=None,zoom=False,width=400,height=300,background=(1.0,1.0,1.0)): + """render(outputfile,scene=None,camera=None,zoom=False,width=400,height=300,background=(1.0,1.0,1.0),lightdir=None): Renders a PNG image of given width and height and background color from the given coin scene, using the given coin camera (ortho or perspective). If zoom is True the camera will be resized to fit all - objects. The outputfile must be a file path to save a png image.""" + objects. The outputfile must be a file path to save a png image. Optionally a light direction as a (x,y,z) + tuple can be given. In this case, a directional light will be added and shadows will + be turned on. This might not work with soem 3D drivers.""" # On Linux, the X server must have indirect rendering enabled in order to be able to do offline # PNG rendering. Unfortunately, this is turned off by default on most recent distros. The easiest @@ -313,9 +316,10 @@ def render(outputfile,scene=None,camera=None,zoom=False,width=400,height=300,bac print("Starting offline renderer") # build an offline scene root separator root = coin.SoSeparator() - # add one light (mandatory) - light = coin.SoDirectionalLight() - root.addChild(light) + if not lightdir: + # add one light (mandatory) + light = coin.SoDirectionalLight() + root.addChild(light) if not camera: # create a default camera if none was given camera = coin.SoPerspectiveCamera() @@ -332,6 +336,8 @@ def render(outputfile,scene=None,camera=None,zoom=False,width=400,height=300,bac # no scene was given, add a simple cube cube = coin.SoCube() root.addChild(cube) + if lightdir: + root = embedLight(root,lightdir) vpRegion = coin.SbViewportRegion(width,height) if zoom: camera.viewAll(root,vpRegion) @@ -416,9 +422,13 @@ def getCoinCamera(camerastring): -def viewer(scene=None,background=(1.0,1.0,1.0)): +def viewer(scene=None,background=(1.0,1.0,1.0),lightdir=None): - """viewer(scene=None,background=(1.0,1.0,1.0)): starts a standalone coin viewer with the contents of the given scene""" + """viewer(scene=None,background=(1.0,1.0,1.0),lightdir=None): starts + a standalone coin viewer with the contents of the given scene. You can + give a background color, and optionally a light direction as a (x,y,z) + tuple. In this case, a directional light will be added and shadows will + be turned on. This might not work with soem 3D drivers.""" # Initialize Coin. This returns a main window to use from pivy import sogui @@ -438,6 +448,9 @@ def viewer(scene=None,background=(1.0,1.0,1.0)): scene.addChild(mat) scene.addChild(coin.SoCone()) + if lightdir: + scene = embedLight(scene,lightdir) + # ref the scene so it doesn't get garbage-collected scene.ref() @@ -454,6 +467,44 @@ def viewer(scene=None,background=(1.0,1.0,1.0)): +def embedLight(scene,lightdir): + + """embedLight(scene,lightdir): embeds a given coin node + inside a shadow group with directional light with the + given direction (x,y,z) tuple. Returns the final coin node""" + + from pivy import coin + + # buggy - no SoShadowGroup in pivy? + #sgroup = coin.SoShadowGroup() + #sgroup.quality = 1 + #sgroup.precision = 1 + #slight = SoShadowDirectionalLight() + #slight.direction = lightdir + #slight.intensity = 20.0 + + buf = """ +#Inventor V2.1 ascii +ShadowGroup { + quality 1 + precision 1 + ShadowDirectionalLight { + direction """+str(lightdir[0])+" "+str(lightdir[1])+" "+str(lightdir[2])+""" + intensity 200.0 + # enable this to reduce the shadow view distance + # maxShadowDistance 200 + } +}""" + + inp = coin.SoInput() + inp.setBuffer(buf) + sgroup = coin.SoDB.readAll(inp) + + sgroup.addChild(scene) + return sgroup + + + def save(document,filename=None,guidata=None,colors=None,camera=None): """save(document,filename=None,guidata=None,colors=None,camera=None): Saves the current document. If no filename @@ -737,7 +788,7 @@ def getViewProviderClass(obj): def extract(filename,inputpath,outputpath=None): """extract(filename,inputpath,outputpath=None): extracts 'inputpath' which is a filename - stored in infile (a FreeCAD or zip file). If outputpath is given, the file is saved as outputpath and + stored in filename (a FreeCAD or zip file). If outputpath is given, the file is saved as outputpath and nothing is returned. If not, the contents of the inputfile are returned and nothing is saved.""" zdoc = zipfile.ZipFile(filename) @@ -757,4 +808,32 @@ def extract(filename,inputpath,outputpath=None): else: return data + + +def openiv(filename): + + """openiv(filename): opens an .iv file and returns a coin node from it""" + + f = open(filename,"r") + buf = f.read() + f.close() + inp = coin.SoInput() + inp.setBuffer(buf) + node = coin.SoDB.readAll(inp) + return node + + + +def saveiv(scene,filename): + + """saveiv(scene,filename): saves an .iv file with the contents of the given coin node""" + + wa=coin.SoWriteAction() + wa.getOutput().openFile(filename) + wa.getOutput().setBinary(False) + wa.apply(sc) + wa.getOutput().closeFile() + + + ## @}