From 5309d85a64ca297dac94f5523c2a68c29dedb7d6 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Fri, 28 Jun 2019 17:15:29 -0300 Subject: [PATCH] Added ground plane option and image render sizes settable in prefs --- InitGui.py | 1 + Render.py | 107 +++++++++++++++++++++++++++----- icons/preferences-render.svg | 116 +++++++++++++++++++++++++++++++++++ renderers/Appleseed.py | 31 ++++++++-- renderers/Luxrender.py | 31 +++++++++- renderers/Povray.py | 17 ++++- ui/RenderSettings.ui | 88 ++++++++++++++++++++++---- 7 files changed, 355 insertions(+), 36 deletions(-) create mode 100644 icons/preferences-render.svg diff --git a/InitGui.py b/InitGui.py index c50b2e73..9f95c0bc 100644 --- a/InitGui.py +++ b/InitGui.py @@ -95,6 +95,7 @@ def QT_TRANSLATE_NOOP(scope, text): commands = Render.RenderCommands self.appendToolbar(QT_TRANSLATE_NOOP("Workbench","Render"),commands) self.appendMenu(QT_TRANSLATE_NOOP("Workbench","&Render"),commands) + FreeCADGui.addIconPath(Render.iconpath) FreeCADGui.addPreferencePage(Render.prefpage,"Render") Log ('Loading Render module...done\n') diff --git a/Render.py b/Render.py index e95d1ac6..176ee635 100644 --- a/Render.py +++ b/Render.py @@ -30,15 +30,24 @@ from PySide import QtCore, QtGui def translate(context, text): if sys.version_info.major >= 3: - return QtGui.QApplication.translate(context, text, None, QtGui.QApplication.UnicodeUTF8) + if hasattr(QtGui.QApplication,"UnicodeUTF8"): + return QtGui.QApplication.translate(context, text, None, QtGui.QApplication.UnicodeUTF8) + else: + return QtGui.QApplication.translate(context, text, None) else: - return QtGui.QApplication.translate(context, text, None, QtGui.QApplication.UnicodeUTF8).encode("utf8") + if hasattr(QtGui.QApplication,"UnicodeUTF8"): + return QtGui.QApplication.translate(context, text, None, QtGui.QApplication.UnicodeUTF8).encode("utf8") + else: + return QtGui.QApplication.translate(context, text, None).encode("utf8") else: def translate(context,txt): return txt def QT_TRANSLATE_NOOP(scope, text): return text + + + class RenderProjectCommand: @@ -59,7 +68,7 @@ def Activated(self): project.Label = self.renderer + " Project" project.Renderer = self.renderer ViewProviderProject(project.ViewObject) - filename = QtGui.QFileDialog.getOpenFileName(FreeCADGui.getMainWindow(),'Select template','*.*') + filename = QtGui.QFileDialog.getOpenFileName(FreeCADGui.getMainWindow(),'Select template',os.path.join(os.path.dirname(__file__),"templates"),'*.*') if filename: project.Template = filename[0] project.ViewObject.Proxy.setCamera() @@ -174,14 +183,39 @@ class Project: def __init__(self,obj): - obj.addProperty("App::PropertyString", "Renderer", "Render", QT_TRANSLATE_NOOP("App::Property","The name of the raytracing engine to use")) - obj.addProperty("App::PropertyBool", "DelayedBuild", "Render", QT_TRANSLATE_NOOP("App::Property","If true, the views will be updated on render only")) - obj.addProperty("App::PropertyFile", "Template", "Render", QT_TRANSLATE_NOOP("App::Property","The template to be use by this rendering")) - obj.addProperty("App::PropertyString", "Camera", "Render", QT_TRANSLATE_NOOP("App::Property","The camera data to be used")) - obj.addProperty("App::PropertyFileIncluded", "PageResult", "Render", QT_TRANSLATE_NOOP("App::Property","The result file to be sent to the renderer")) - obj.addExtension("App::GroupExtensionPython", self) - obj.DelayedBuild = True obj.Proxy = self + self.setProperties(obj) + + def setProperties(self,obj): + + if not "Renderer" in obj.PropertiesList: + obj.addProperty("App::PropertyString","Renderer","Render", QT_TRANSLATE_NOOP("App::Property","The name of the raytracing engine to use")) + if not "DelayedBuild" in obj.PropertiesList: + obj.addProperty("App::PropertyBool","DelayedBuild","Render", QT_TRANSLATE_NOOP("App::Property","If true, the views will be updated on render only")) + obj.DelayedBuild = True + if not "Template" in obj.PropertiesList: + obj.addProperty("App::PropertyFile","Template","Render", QT_TRANSLATE_NOOP("App::Property","The template to be use by this rendering")) + if not "Camera" in obj.PropertiesList: + obj.addProperty("App::PropertyString","Camera","Render", QT_TRANSLATE_NOOP("App::Property","The camera data to be used")) + if not "PageResult" in obj.PropertiesList: + obj.addProperty("App::PropertyFileIncluded", "PageResult","Render", QT_TRANSLATE_NOOP("App::Property","The result file to be sent to the renderer")) + if not "Group" in obj.PropertiesList: + obj.addExtension("App::GroupExtensionPython", self) + if not "RenderWidth" in obj.PropertiesList: + obj.addProperty("App::PropertyInteger","RenderWidth","Render", QT_TRANSLATE_NOOP("App::Property","The width of the rendered image in pixels")) + obj.RenderWidth = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Render").GetInt("RenderWidth",800) + if not "RenderHeight" in obj.PropertiesList: + obj.addProperty("App::PropertyInteger","RenderHeight","Render", QT_TRANSLATE_NOOP("App::Property","The height of the rendered image in pixels")) + obj.RenderHeight = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Render").GetInt("RenderHeight",600) + if not "GroundPlane" in obj.PropertiesList: + obj.addProperty("App::PropertyBool","GroundPlane","Render", QT_TRANSLATE_NOOP("App::Property","If true, a default ground plane will be added to the scene")) + obj.GroundPlane = False + obj.setEditorMode("PageResult",2) + obj.setEditorMode("Camera",2) + + def onDocumentRestored(self,obj): + + self.setProperties(obj) def execute(self,obj): @@ -229,6 +263,48 @@ def writeObject(self,obj,view): else: return renderer.writeObject(view) + def writeGroundPlane(self,obj): + + result = "" + bbox = FreeCAD.BoundBox() + for view in obj.Group: + if view.Source and hasattr(view.Source,"Shape") and hasattr(view.Source.Shape,"BoundBox"): + bbox.add(view.Source.Shape.BoundBox) + if bbox.isValid(): + import Part + margin = bbox.DiagonalLength/2 + p1 = FreeCAD.Vector(bbox.XMin-margin,bbox.YMin-margin,0) + p2 = FreeCAD.Vector(bbox.XMax+margin,bbox.YMin-margin,0) + p3 = FreeCAD.Vector(bbox.XMax+margin,bbox.YMax+margin,0) + p4 = FreeCAD.Vector(bbox.XMin-margin,bbox.YMax+margin,0) + + # create temporary object. We do this to keep the renderers code as simple as possible: + # they only ned to deal with one type of object: RenderView objects + dummy1 = FreeCAD.ActiveDocument.addObject("Part::Feature","renderdummy1") + dummy1.Shape = Part.Face(Part.makePolygon([p1,p2,p3,p4,p1])) + dummy2 = FreeCAD.ActiveDocument.addObject("App::FeaturePython","renderdummy2") + View(dummy2) + dummy2.Source = dummy1 + ViewProviderView(dummy2.ViewObject) + FreeCAD.ActiveDocument.recompute() + + if obj.Renderer: + try: + renderer = importlib.import_module("renderers."+obj.Renderer) + except ImportError: + FreeCAD.Console.PrintError(translate("Render","Error importing renderer")+" "+str(obj.Renderer)) + else: + r = renderer.writeObject(dummy2) + if r: + result = r + + # remove temp objects + FreeCAD.ActiveDocument.removeObject(dummy2.Name) + FreeCAD.ActiveDocument.removeObject(dummy1.Name) + FreeCAD.ActiveDocument.recompute() + + return result + def render(self,obj,external=True): if obj.Renderer: @@ -253,6 +329,8 @@ def render(self,obj,external=True): renderobjs += self.writeObject(obj,view) else: renderobjs += view.ViewResult + if hasattr(obj,"GroundPlane") and obj.GroundPlane: + renderobjs += self.writeGroundPlane(obj) if "RaytracingCamera" in template: template = re.sub("(.*RaytracingCamera.*)",cam,template) @@ -276,10 +354,8 @@ def render(self,obj,external=True): FreeCAD.Console.PrintError(translate("Render","Error importing renderer")+" "+str(obj.Renderer)) return "" else: - try: - return renderer.render(obj,external) - except: - FreeCAD.Console.PrintError(translate("Render","Error while executing renderer")+" "+str(obj.Renderer)) + return renderer.render(obj,external) + # FreeCAD.Console.PrintError(translate("Render","Error while executing renderer")+" "+str(obj.Renderer)) class ViewProviderProject: @@ -402,7 +478,7 @@ def getIcon(self): RenderCommands = [] Renderers = os.listdir(os.path.dirname(__file__)+os.sep+"renderers") Renderers = [r for r in Renderers if not ".pyc" in r] - Renderers = [r for r in Renderers if not "__init__" in r] + Renderers = [r for r in Renderers if not "__" in r] Renderers = [os.path.splitext(r)[0] for r in Renderers] for renderer in Renderers: FreeCADGui.addCommand('Render_'+renderer, RenderProjectCommand(renderer)) @@ -413,4 +489,5 @@ def getIcon(self): RenderCommands.append('Render_Render') # This is for InitGui.py because it cannot import os + iconpath = os.path.join(os.path.dirname(__file__),"icons") prefpage = os.path.join(os.path.dirname(__file__),"ui","RenderSettings.ui") diff --git a/icons/preferences-render.svg b/icons/preferences-render.svg new file mode 100644 index 00000000..8ce9fbfd --- /dev/null +++ b/icons/preferences-render.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/renderers/Appleseed.py b/renderers/Appleseed.py index 4f51f23c..a521ecb3 100644 --- a/renderers/Appleseed.py +++ b/renderers/Appleseed.py @@ -31,7 +31,7 @@ # A render engine module must contain the following functions: # # writeCamera(camdata): returns a string containing an openInventor camera string in renderer format -# writeObject(viewobj): returns a string containing a RaytracingView object in renderer format +# writeObject(view): returns a string containing a RaytracingView object in renderer format # render(project,external=True): renders the given project, external means if the user wishes to open # the render file in an external application/editor or not. If this # is not supported by your renderer, you can simply ignore it @@ -42,7 +42,11 @@ # An icon under the name Renderer.svg (where Renderer is the name of your Renderer -import tempfile,FreeCAD,os,math +import tempfile +import FreeCAD +import os +import math +import re def writeCamera(camdata): @@ -96,7 +100,7 @@ def writeCamera(camdata): up = rot.multVec(FreeCAD.Vector(0,1,0)) up = str(up.x)+" "+str(up.y)+" "+str(up.z) pos = str(pos.x)+" "+str(pos.y)+" "+str(pos.z) - print("cam:",pos," : ",tpos," : ",up) + #print("cam:",pos," : ",tpos," : ",up) cam = """ @@ -232,6 +236,23 @@ def render(project,external=False): if not project.PageResult: return + + if hasattr(project,"RenderWidth") and hasattr(project,"RenderHeight"): + # change image size in template + f = open(project.PageResult,"r") + t = f.read() + f.close() + res = re.findall("",t) + if res: + t = t.replace(res[0],"") + fp = tempfile.mkstemp(prefix=project.Name,suffix=os.path.splitext(project.Template)[-1])[1] + f = open(fp,"w") + f.write(t) + f.close() + project.PageResult = fp + os.remove(fp) + FreeCAD.ActiveDocument.recompute() + p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Render") if external: rpath = p.GetString("AppleseedStudioPath","") @@ -240,10 +261,10 @@ def render(project,external=False): rpath = p.GetString("AppleseedCliPath","") args = p.GetString("AppleseedParameters","") if not rpath: - raise + FreeCAD.Console.PrintError("Unable to locate Appleseed executable. Please set the correct path in Edit -> Preferences -> Render") + return if args: args += " " - import os os.system(rpath+" "+args+project.PageResult) return diff --git a/renderers/Luxrender.py b/renderers/Luxrender.py index 035ab3a5..90b8893c 100644 --- a/renderers/Luxrender.py +++ b/renderers/Luxrender.py @@ -41,7 +41,11 @@ # An icon under the name Renderer.svg (where Renderer is the name of your Renderer -import FreeCAD,math +import FreeCAD +import math +import os +import re +import tempfile def writeCamera(camdata): @@ -192,6 +196,27 @@ def render(project,external=True): if not project.PageResult: return + + if hasattr(project,"RenderWidth") and hasattr(project,"RenderHeight"): + # change image size in template + f = open(project.PageResult,"r") + t = f.read() + f.close() + res = re.findall("integer xresolution",t) + if res: + t = re.sub("\"integer xresolution\".*?\[.*?\]","\"integer xresolution\" ["+str(project.RenderWidth)+"]",t) + res = re.findall("integer yresolution",t) + if res: + t = re.sub("\"integer yresolution\".*?\[.*?\]","\"integer yresolution\" ["+str(project.RenderHeight)+"]",t) + if res: + fp = tempfile.mkstemp(prefix=project.Name,suffix=os.path.splitext(project.Template)[-1])[1] + f = open(fp,"w") + f.write(t) + f.close() + project.PageResult = fp + os.remove(fp) + FreeCAD.ActiveDocument.recompute() + p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Render") if external: rpath = p.GetString("LuxRenderPath","") @@ -200,10 +225,10 @@ def render(project,external=True): rpath = p.GetString("LuxConsolePath","") args = p.GetString("LuxParameters","") if not rpath: - raise + FreeCAD.Console.PrintError("Unable to locate Luxrender executable. Please set the correct path in Edit -> Preferences -> Render") + return if args: args += " " - import os os.system(rpath+" "+args+project.PageResult) return diff --git a/renderers/Povray.py b/renderers/Povray.py index a5f92afd..5a65fcf2 100644 --- a/renderers/Povray.py +++ b/renderers/Povray.py @@ -41,7 +41,9 @@ # An icon under the name Renderer.svg (where Renderer is the name of your Renderer -import FreeCAD,math +import FreeCAD +import math +import re def writeCamera(camdata): @@ -201,6 +203,7 @@ def writeObject(viewobj): def render(project,external=True): + # This is the actual rendering operation if not project.PageResult: return @@ -211,13 +214,23 @@ def render(project,external=True): raise if args: args += " " + if "RenderWidth" in project.PropertiesList: + if "+W" in args: + args = re.sub("\+W[0-9]+","+W"+str(project.RenderWidth),args) + else: + args = args + "+W"+str(project.RenderWidth)+" " + if "RenderHeight" in project.PropertiesList: + if "+H" in args: + args = re.sub("\+H[0-9]+","+H"+str(project.RenderHeight),args) + else: + args = args + "+H"+str(project.RenderHeight)+" " import os exe = rpath+" "+args+project.PageResult print("Executing "+exe) os.system(exe) import ImageGui - print("Saving image as "+imgname) imgname = os.path.splitext(project.PageResult)[0]+".png" + print("Saving image as "+imgname) ImageGui.open(imgname) return diff --git a/ui/RenderSettings.ui b/ui/RenderSettings.ui index 4409cfe5..5d1c76ee 100644 --- a/ui/RenderSettings.ui +++ b/ui/RenderSettings.ui @@ -20,27 +20,27 @@ 6 - + Appleseed - + Appleseed command (cli) path - + Appleseed studio path - + The path to the Appleseed cli executable @@ -53,7 +53,7 @@ - + The path to the Appleseed studio executable (optional) @@ -66,14 +66,14 @@ - + Render parameters - + Optional rendering parameters to be passed to the Appleseed executable @@ -92,7 +92,7 @@ - + Qt::Vertical @@ -105,7 +105,7 @@ - + Luxrender @@ -154,7 +154,7 @@ - + PovRay @@ -193,7 +193,7 @@ Optional parameters to be passed to Pov-Ray when rendering - +P +A +W800 +H600 + +P +A PovRayParameters @@ -206,6 +206,67 @@ + + + + General + + + + + + Defautl render height + + + + + + + Default render width + + + + + + + px + + + 9999 + + + 800 + + + RenderWidth + + + Mod/Render + + + + + + + px + + + 9999 + + + 600 + + + RenderHeight + + + Mod/Render + + + + + + @@ -220,6 +281,11 @@ Gui::FileChooser
Gui/PrefWidgets.h
+ + Gui::PrefSpinBox + QSpinBox +
Gui/PrefWidgets.h
+
Gui::PrefLineEdit QLineEdit