X-Git-Url: https://git.ao2.it/vrm.git/blobdiff_plain/cb636907756fdaa87a409240bbebfda832bca34a..bfe651e97954b264507caf17365aa37c8562ded8:/vrm.py diff --git a/vrm.py b/vrm.py index 69099c4..0d35415 100755 --- a/vrm.py +++ b/vrm.py @@ -1,13 +1,13 @@ #!BPY """ Name: 'VRM' -Blender: 241 -Group: 'Export' -Tooltip: 'Vector Rendering Method Export Script' +Blender: 242 +Group: 'Render' +Tooltip: 'Vector Rendering Method script' """ __author__ = "Antonio Ospite" -__url__ = ["blender"] +__url__ = ["http://vrm.projects.blender.org"] __version__ = "0.3" __bpydoc__ = """\ @@ -43,18 +43,22 @@ __bpydoc__ = """\ # # Things TODO for a next release: # - Switch to the Mesh structure, should be considerably faster -# (partially done, but cannot sort faces, yet) +# (partially done, but with Mesh we cannot sort faces, yet) # - Use a better depth sorting algorithm # - Review how selections are made (this script uses selection states of # primitives to represent visibility infos) -# - Implement Clipping and do handle object intersections -# - Implement Edge Styles (silhouettes, contours, etc.) -# - Implement Edge coloring +# - Implement clipping of primitives and do handle object intersections. +# (for now only clipping for whole objects is supported). +# - Implement Edge Styles (silhouettes, contours, etc.) (partially done). # - Use multiple lighting sources in color calculation -# - Implement Shading Styles? -# - Use another representation for the 2D projection? +# - Implement Shading Styles? (for now we use Flat Shading). +# - Use a data structure other than Mesh to represent the 2D image? # Think to a way to merge adjacent polygons that have the same color. -# - Add other Vector Writers. +# Or a way to use paths for silhouettes and contours. +# - Add Vector Writers other that SVG. +# - Consider SMIL for animation handling instead of ECMA Script? (Firefox do +# not support SMIL for animations) +# - FIX the issue with negative scales in object tranformations! # # --------------------------------------------------------------------- # @@ -74,18 +78,110 @@ from math import * # Some global settings -PRINT_POLYGONS = True -PRINT_EDGES = False -SHOW_HIDDEN_EDGES = False -EDGES_WIDTH = 0.5 +class config: + polygons = dict() + polygons['SHOW'] = True + polygons['SHADING'] = 'TOON' + # Hidden to the user for now + polygons['EXPANSION_TRICK'] = True -POLYGON_EXPANSION_TRICK = True + edges = dict() + edges['SHOW'] = True + edges['SHOW_HIDDEN'] = False + edges['STYLE'] = 'SILHOUETTE' + edges['WIDTH'] = 2 + edges['COLOR'] = [0, 0, 0] -RENDER_ANIMATION = False + output = dict() + output['FORMAT'] = 'SVG' + output['ANIMATION'] = False + output['JOIN_OBJECTS'] = True + + + +# --------------------------------------------------------------------- +# +## Utility Mesh class +# +# --------------------------------------------------------------------- +class MeshUtils: + + def getEdgeAdjacentFaces(edge, mesh): + """Get the faces adjacent to a given edge. + + There can be 0, 1 or more (usually 2) faces adjacent to an edge. + """ + adjface_list = [] + + for f in mesh.faces: + if (edge.v1 in f.v) and (edge.v2 in f.v): + adjface_list.append(f) + + return adjface_list + + def isMeshEdge(e, mesh): + """Mesh edge rule. + + A mesh edge is visible if _any_ of its adjacent faces is selected. + Note: if the edge has no adjacent faces we want to show it as well, + useful for "edge only" portion of objects. + """ + + adjacent_faces = MeshUtils.getEdgeAdjacentFaces(e, mesh) + + if len(adjacent_faces) == 0: + return True + + selected_faces = [f for f in adjacent_faces if f.sel] + + if len(selected_faces) != 0: + return True + else: + return False + + def isSilhouetteEdge(e, mesh): + """Silhuette selection rule. + + An edge is a silhuette edge if it is shared by two faces with + different selection status or if it is a boundary edge of a selected + face. + """ + + adjacent_faces = MeshUtils.getEdgeAdjacentFaces(e, mesh) + + if ((len(adjacent_faces) == 1 and adjacent_faces[0].sel == 1) or + (len(adjacent_faces) == 2 and + adjacent_faces[0].sel != adjacent_faces[1].sel) + ): + return True + else: + return False + + def toonShading(u): + + levels = 2 + texels = 2*levels - 1 + map = [0.0] + [(i)/float(texels-1) for i in range(1, texels-1) ] + [1.0] + + v = 1.0 + for i in range(0, len(map)-1): + pivot = (map[i]+map[i+1])/2.0 + j = int(u>pivot) + + v = map[i+j] + + if v\n" % (framenumber, framestyle) ) + for obj in Objects: if(obj.getType() != 'Mesh'): @@ -432,7 +533,7 @@ class SVGVectorWriter(VectorWriter): self.file.write("\n\n") - def _printPolygons(self, mesh): + def _printPolygons(self, mesh): """Print the selected (visible) polygons. """ @@ -443,7 +544,7 @@ class SVGVectorWriter(VectorWriter): for face in mesh.faces: if not face.sel: - continue + continue self.file.write("\n") @@ -482,8 +592,8 @@ class SVGVectorWriter(VectorWriter): """Print the wireframe using mesh edges. """ - stroke_width=EDGES_WIDTH - stroke_col = [0, 0, 0] + stroke_width = config.edges['WIDTH'] + stroke_col = config.edges['COLOR'] self.file.write("\n") @@ -491,8 +601,7 @@ class SVGVectorWriter(VectorWriter): hidden_stroke_style = "" - # Consider an edge selected if both vertices are selected - if e.v1.sel == 0 or e.v2.sel == 0: + if e.sel == 0: if showHiddenEdges == False: continue else: @@ -519,6 +628,21 @@ class SVGVectorWriter(VectorWriter): # # --------------------------------------------------------------------- +# A dictionary to collect different shading style methods +shadingStyles = dict() +shadingStyles['FLAT'] = None +shadingStyles['TOON'] = None + +# A dictionary to collect different edge style methods +edgeStyles = dict() +edgeStyles['MESH'] = MeshUtils.isMeshEdge +edgeStyles['SILHOUETTE'] = MeshUtils.isSilhouetteEdge + +# A dictionary to collect the supported output formats +outputWriters = dict() +outputWriters['SVG'] = SVGVectorWriter + + class Renderer: """Render a scene viewed from a given camera. @@ -549,13 +673,22 @@ class Renderer: # Render from the currently active camera self.cameraObj = self._SCENE.getCurrentCamera() + # Get a projector for this camera. + # NOTE: the projector wants object in world coordinates, + # so we should remember to apply modelview transformations + # _before_ we do projection transformations. + self.proj = Projector(self.cameraObj, self.canvasRatio) + # Get the list of lighting sources obj_lst = self._SCENE.getChildren() self.lights = [ o for o in obj_lst if o.getType() == 'Lamp'] + # When there are no lights we use a default lighting source + # that have the same position of the camera if len(self.lights) == 0: l = Lamp.New('Lamp') lobj = Object.New('Lamp') + lobj.loc = self.cameraObj.loc lobj.link(l) self.lights.append(lobj) @@ -568,17 +701,17 @@ class Renderer: """Render picture or animation and write it out. The parameters are: - - a Vector writer object than will be used to output the result. + - a Vector writer object that will be used to output the result. - a flag to tell if we want to render an animation or only the current frame. """ context = self._SCENE.getRenderingContext() - currentFrame = context.currentFrame() + origCurrentFrame = context.currentFrame() # Handle the animation case if not animation: - startFrame = currentFrame + startFrame = origCurrentFrame endFrame = startFrame outputWriter.open() else: @@ -591,11 +724,25 @@ class Renderer: for f in range(startFrame, endFrame+1): context.currentFrame(f) - renderedScene = self.doRenderScene(self._SCENE) + # Use some temporary workspace, a full copy of the scene + inputScene = self._SCENE.copy(2) + + try: + renderedScene = self.doRenderScene(inputScene) + except : + print "There was an error! Aborting." + import traceback + print traceback.print_exc() + + self._SCENE.makeCurrent() + Scene.unlink(inputScene) + del inputScene + return + outputWriter.printCanvas(renderedScene, - doPrintPolygons = PRINT_POLYGONS, - doPrintEdges = PRINT_EDGES, - showHiddenEdges = SHOW_HIDDEN_EDGES) + doPrintPolygons = config.polygons['SHOW'], + doPrintEdges = config.edges['SHOW'], + showHiddenEdges = config.edges['SHOW_HIDDEN']) # clear the rendered scene self._SCENE.makeCurrent() @@ -604,73 +751,52 @@ class Renderer: outputWriter.close() print "Done!" - context.currentFrame(currentFrame) + context.currentFrame(origCurrentFrame) - def doRenderScene(self, inputScene): + def doRenderScene(self, workScene): """Control the rendering process. Here we control the entire rendering process invoking the operation needed to transform and project the 3D scene in two dimensions. """ - # Use some temporary workspace, a full copy of the scene - workScene = inputScene.copy(2) - - # Get a projector for this scene. - # NOTE: the projector wants object in world coordinates, - # so we should apply modelview transformations _before_ - # projection transformations - proj = Projector(self.cameraObj, self.canvasRatio) + # global processing of the scene + self._doConvertGeometricObjToMesh(workScene) - # Convert geometric object types to mesh Objects - geometricObjTypes = ['Mesh', 'Surf', 'Curve'] # TODO: add the Text type - Objects = workScene.getChildren() - objList = [ o for o in Objects if o.getType() in geometricObjTypes ] - for obj in objList: - old_obj = obj - obj = self._convertToRawMeshObj(obj) - workScene.link(obj) - workScene.unlink(old_obj) + #self._doSceneClipping(workScene) - - # FIXME: does not work!!, Blender segfaults on joins - if OPTIMIZE_FOR_SPACE: + if config.output['JOIN_OBJECTS']: self._joinMeshObjectsInScene(workScene) - - # global processing of the scene - self._doClipping() - self._doSceneDepthSorting(workScene) # Per object activities - Objects = workScene.getChildren() + Objects = workScene.getChildren() for obj in Objects: + - if obj.getType() not in geometricObjTypes: - print "Only geometric Objects supported! - Skipping type:", obj.getType() + if obj.getType() != 'Mesh': + print "Only Mesh supported! - Skipping type:", obj.getType() continue print "Rendering: ", obj.getName() - mesh = obj.data + mesh = obj.getData(mesh=1) self._doModelToWorldCoordinates(mesh, obj.matrix) - self._doObjectDepthSorting(mesh) - self._doBackFaceCulling(mesh) + self._doObjectDepthSorting(mesh) + self._doColorAndLighting(mesh) - # TODO: 'style' can be a function that determine - # if an edge should be showed? - self._doEdgesStyle(mesh, style=None) + self._doEdgesStyle(mesh, edgeStyles[config.edges['STYLE']]) - self._doProjection(mesh, proj) + self._doProjection(mesh, self.proj) # Update the object data, important! :) mesh.update() @@ -684,19 +810,15 @@ class Renderer: # Utility methods - def _worldPosition(self, obj): + def _getObjPosition(self, obj): """Return the obj position in World coordinates. """ return obj.matrix.translationPart() - def _cameraWorldPosition(self): - """Return the camera position in World coordinates. - - This trick is needed when the camera follows a path and then - camera.loc does not correspond to the current real position of the - camera in the world. + def _cameraViewDirection(self): + """Get the View Direction form the camera matrix. """ - return self._worldPosition(self.cameraObj) + return Vector(self.cameraObj.matrix[2]).resize3D() # Faces methods @@ -722,25 +844,20 @@ class Renderer: """ normal = Vector(face.no) - c = self._cameraWorldPosition() - - # View vector in orthographics projections can be considered simply as the - # camera position - view_vect = Vector(c) - #if self.cameraObj.data.getType() == 1: - # view_vect = Vector(c) - - # View vector as in perspective projections - # it is the difference between the camera position and one point of - # the face, we choose the farthest point. - # TODO: make the code more pythonic :) + camPos = self._getObjPosition(self.cameraObj) + view_vect = None + + # View Vector in orthographics projections is the view Direction of + # the camera + if self.cameraObj.data.getType() == 1: + view_vect = self._cameraViewDirection() + + # View vector in perspective projections can be considered as + # the difference between the camera position and one point of + # the face, we choose the farthest point from the camera. if self.cameraObj.data.getType() == 0: - max_len = 0 - for vect in face: - vv = Vector(c) - Vector(vect.co) - if vv.length > max_len: - max_len = vv.length - view_vect = vv + vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] ) + view_vect = vv[1] # if d > 0 the face is visible from the camera d = view_vect * normal @@ -753,11 +870,60 @@ class Renderer: # Scene methods - def _doClipping(self): - """Clip object against the View Frustum. + def _doConvertGeometricObjToMesh(self, scene): + """Convert all "geometric" objects to mesh ones. """ - print "TODO: _doClipping()" - return + geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text'] + + Objects = scene.getChildren() + objList = [ o for o in Objects if o.getType() in geometricObjTypes ] + for obj in objList: + old_obj = obj + obj = self._convertToRawMeshObj(obj) + scene.link(obj) + scene.unlink(old_obj) + + + # XXX Workaround for Text and Curve which have some normals + # inverted when they are converted to Mesh, REMOVE that when + # blender will fix that!! + if old_obj.getType() in ['Curve', 'Text']: + me = obj.getData(mesh=1) + for f in me.faces: f.sel = 1; + for v in me.verts: v.sel = 1; + me.remDoubles(0) + me.triangleToQuad() + me.recalcNormals() + me.update() + + def _doSceneClipping(self, scene): + """Clip objects against the View Frustum. + + For now clip away only objects according to their center position. + """ + + cpos = self._getObjPosition(self.cameraObj) + view_vect = self._cameraViewDirection() + + near = self.cameraObj.data.clipStart + far = self.cameraObj.data.clipEnd + + aspect = float(self.canvasRatio[0])/float(self.canvasRatio[1]) + fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32)) + fovy = fovy * 360.0/pi + + Objects = scene.getChildren() + for o in Objects: + if o.getType() != 'Mesh': continue; + + obj_vect = Vector(cpos) - self._getObjPosition(o) + + d = obj_vect*view_vect + theta = AngleBetweenVecs(obj_vect, view_vect) + + # if the object is outside the view frustum, clip it away + if (d < near) or (d > far) or (theta > fovy): + scene.unlink(o) def _doSceneDepthSorting(self, scene): """Sort objects in the scene. @@ -765,40 +931,60 @@ class Renderer: The object sorting is done accordingly to the object centers. """ - c = self._cameraWorldPosition() + c = self._getObjPosition(self.cameraObj) - Objects = scene.getChildren() + by_center_pos = (lambda o1, o2: + (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and + cmp((self._getObjPosition(o1) - Vector(c)).length, + (self._getObjPosition(o2) - Vector(c)).length) + ) - #Objects.sort(lambda obj1, obj2: - # cmp((Vector(obj1.loc) - Vector(c)).length, - # (Vector(obj2.loc) - Vector(c)).length - # ) - # ) + # TODO: implement sorting by bounding box, if obj1.bb is inside obj2.bb, + # then ob1 goes farther than obj2, useful when obj2 has holes + by_bbox = None - Objects.sort(lambda obj1, obj2: - cmp((self._worldPosition(obj1) - Vector(c)).length, - (self._worldPosition(obj2) - Vector(c)).length - ) - ) + Objects = scene.getChildren() + Objects.sort(by_center_pos) # update the scene for o in Objects: scene.unlink(o) scene.link(o) - def _joinMeshObjectsInScene(self, scene): """Merge all the Mesh Objects in a scene into a single Mesh Object. """ - bigObj = Object.New('Mesh', 'BigOne') + + if Blender.mode == 'background': + print "\nWARNING! Joining objects not supported in background mode!\n" + return + oList = [o for o in scene.getChildren() if o.getType()=='Mesh'] - print "Before join", oList - bigObj.join(oList) - print "After join" + + # FIXME: Object.join() do not work if the list contains 1 object + if len(oList) == 1: + return + + mesh = Mesh.New('BigOne') + bigObj = Object.New('Mesh', 'BigOne') + bigObj.link(mesh) + scene.link(bigObj) + + try: + bigObj.join(oList) + except RuntimeError: + print "Can't Join Objects" + scene.unlink(bigObj) + return + except TypeError: + print "Objects Type error?" + for o in oList: scene.unlink(o) + scene.update() + # Per object methods @@ -811,6 +997,11 @@ class Renderer: newObject = Object.New('Mesh', 'RawMesh_'+object.name) newObject.link(me) + # If the object has no materials set a default material + if not me.materials: + me.materials = [Material.New()] + #for f in me.faces: f.mat = 0 + newObject.setMatrix(object.getMatrix()) return newObject @@ -821,6 +1012,11 @@ class Renderer: This step is done simply applying to the object its tranformation matrix and recalculating its normals. """ + # XXX FIXME: blender do not transform normals in the right way when + # there are negative scale values + if matrix[0][0] < 0 or matrix[1][1] < 0 or matrix[2][2] < 0: + print "WARNING: Negative scales, expect incorrect results!" + mesh.transform(matrix, True) def _doObjectDepthSorting(self, mesh): @@ -829,24 +1025,49 @@ class Renderer: The faces in the object are sorted following the distance of the vertices from the camera position. """ - c = self._cameraWorldPosition() + if len(mesh.faces) == 0: + return + + c = self._getObjPosition(self.cameraObj) # hackish sorting of faces - mesh.faces.sort( - lambda f1, f2: - # Sort faces according to the min distance from the camera - #cmp(min([(Vector(v.co)-Vector(c)).length for v in f1]), - # min([(Vector(v.co)-Vector(c)).length for v in f2]))) - # Sort faces according to the max distance from the camera + # Sort faces according to the max distance from the camera + by_max_vert_dist = (lambda f1, f2: cmp(max([(Vector(v.co)-Vector(c)).length for v in f1]), max([(Vector(v.co)-Vector(c)).length for v in f2]))) - - # Sort faces according to the avg distance from the camera - #cmp(sum([(Vector(v.co)-Vector(c)).length for v in f1])/len(f1), - # sum([(Vector(v.co)-Vector(c)).length for v in f2])/len(f2))) + + # Sort faces according to the min distance from the camera + by_min_vert_dist = (lambda f1, f2: + cmp(min([(Vector(v.co)-Vector(c)).length for v in f1]), + min([(Vector(v.co)-Vector(c)).length for v in f2]))) + + # Sort faces according to the avg distance from the camera + by_avg_vert_dist = (lambda f1, f2: + cmp(sum([(Vector(v.co)-Vector(c)).length for v in f1])/len(f1), + sum([(Vector(v.co)-Vector(c)).length for v in f2])/len(f2))) + + + # FIXME: using NMesh to sort faces. We should avoid that! + nmesh = NMesh.GetRaw(mesh.name) + nmesh.faces.sort(by_max_vert_dist) + nmesh.faces.reverse() + + # Get visible faces + #vf = nmesh.faces + #while len(vf)>1: + # p1 = vf[0] + # insideList = + + + mesh.faces.delete(1, range(0, len(mesh.faces))) + + for i,f in enumerate(nmesh.faces): + fv = [v.index for v in f.v] + mesh.faces.extend(fv) + mesh.faces[i].mat = f.mat + mesh.faces[i].sel = f.sel - mesh.faces.reverse() def _doBackFaceCulling(self, mesh): """Simple Backface Culling routine. @@ -855,7 +1076,8 @@ class Renderer: select the vertices belonging to visible faces. """ - # Select all vertices, so edges without faces can be displayed + # Select all vertices, so edges can be displayed even if there are no + # faces for v in mesh.verts: v.sel = 1 @@ -866,42 +1088,28 @@ class Renderer: if self._isFaceVisible(f): f.sel = 1 - # Is this the correct way to propagate the face selection info to the - # vertices belonging to a face ?? - # TODO: Using the Mesh class this should come for free. Right? - Mesh.Mode(Mesh.SelectModes['VERTEX']) - for f in mesh.faces: - if not f.sel: - for v in f: - v.sel = 0 - - for f in mesh.faces: - if f.sel: - for v in f: - v.sel = 1 - def _doColorAndLighting(self, mesh): - """Apply an Illumination model to the object. + """Apply an Illumination ans shading model to the object. - The Illumination model used is the Phong one, it may be inefficient, + The model used is the Phong one, it may be inefficient, but I'm just learning about rendering and starting from Phong seemed the most natural way. """ # If the mesh has vertex colors already, use them, # otherwise turn them on and do some calculations - if mesh.hasVertexColours(): + if mesh.vertexColors: return - mesh.hasVertexColours(True) + mesh.vertexColors = 1 materials = mesh.materials # TODO: use multiple lighting sources light_obj = self.lights[0] - light_pos = self._worldPosition(light_obj) + light_pos = self._getObjPosition(light_obj) light = light_obj.data - camPos = self._cameraWorldPosition() + camPos = self._getObjPosition(self.cameraObj) # We do per-face color calculation (FLAT Shading), we can easily turn # to a per-vertex calculation if we want to implement some shading @@ -916,21 +1124,21 @@ class Renderer: mat = materials[f.mat] # A new default material - if not mat: + if mat == None: mat = Material.New('defMat') L = Vector(light_pos).normalize() - V = (Vector(camPos) - Vector(f.v[0].co)).normalize() + V = (Vector(camPos) - Vector(f.cent)).normalize() N = Vector(f.no).normalize() R = 2 * (N*L) * N - L # TODO: Attenuation factor (not used for now) - a0 = 1; a1 = 0.0; a2 = 0.0 + a0 = 1.0; a1 = 0.0; a2 = 1.0 d = (Vector(f.v[0].co) - Vector(light_pos)).length - fd = min(1, 1.0/(a0 + a1*d + a2*d*d)) + fd = min(1, 1.0/(a0 + a1*d + a2*(d*d))) # Ambient component Ia = 1.0 @@ -940,38 +1148,62 @@ class Renderer: # Diffuse component (add light.col for kd) kd = mat.getRef() * Vector(mat.getRGBCol()) Ip = light.getEnergy() - Idiff = Ip * kd * (N*L) + if config.polygons['SHADING'] == 'FLAT': + Idiff = Ip * kd * (N*L) + elif config.polygons['SHADING'] == 'TOON': + Idiff = Ip * kd * MeshUtils.toonShading(N*L) + # Specular component ks = mat.getSpec() * Vector(mat.getSpecCol()) ns = mat.getHardness() - Ispec = Ip * ks * pow((V * R), ns) + Ispec = Ip * ks * pow((V*R), ns) # Emissive component ki = Vector([mat.getEmit()]*3) - I = ki + Iamb + Idiff + Ispec + I = ki + Iamb + (Idiff + Ispec) + + + # Set Alpha component + I = list(I) + I.append(mat.getAlpha()) # Clamp I values between 0 and 1 I = [ min(c, 1) for c in I] I = [ max(0, c) for c in I] + + # Convert to a value between 0 and 255 tmp_col = [ int(c * 255.0) for c in I] - vcol = NMesh.Col(tmp_col[0], tmp_col[1], tmp_col[2], 255) - f.col = [] - for v in f.v: - f.col.append(vcol) + for c in f.col: + c.r = tmp_col[0] + c.g = tmp_col[1] + c.b = tmp_col[2] + c.a = tmp_col[3] + + def _doEdgesStyle(self, mesh, edgestyleSelect): + """Process Mesh Edges accroding to a given selection style. - def _doEdgesStyle(self, mesh, style): - """Process Mesh Edges. (For now copy the edge data, in next version it - can be a place where recognize silouhettes and/or contours). + Examples of algorithms: - input: an edge list - return: a processed edge list + Contours: + given an edge if its adjacent faces have the same normal (that is + they are complanar), than deselect it. + + Silhouettes: + given an edge if one its adjacent faces is frontfacing and the + other is backfacing, than select it, else deselect. """ - #print "\tTODO: _doEdgeStyle()" - return + Mesh.Mode(Mesh.SelectModes['EDGE']) + + for e in mesh.edges: + + e.sel = 0 + if edgestyleSelect(e, mesh): + e.sel = 1 + def _doProjection(self, mesh, projector): """Calculate the Projection for the object. """ @@ -987,44 +1219,269 @@ class Renderer: # --------------------------------------------------------------------- # -## Main Program +## GUI Class and Main Program # # --------------------------------------------------------------------- + +from Blender import BGL, Draw +from Blender.BGL import * + +class GUI: + + def _init(): + + # Output Format menu + output_format = config.output['FORMAT'] + default_value = outputWriters.keys().index(output_format)+1 + GUI.outFormatMenu = Draw.Create(default_value) + GUI.evtOutFormatMenu = 0 + + # Animation toggle button + GUI.animToggle = Draw.Create(config.output['ANIMATION']) + GUI.evtAnimToggle = 1 + + # Join Objects toggle button + GUI.joinObjsToggle = Draw.Create(config.output['JOIN_OBJECTS']) + GUI.evtJoinObjsToggle = 2 + + # Render filled polygons + GUI.polygonsToggle = Draw.Create(config.polygons['SHOW']) + + # Shading Style menu + shading_style = config.polygons['SHADING'] + default_value = shadingStyles.keys().index(shading_style)+1 + GUI.shadingStyleMenu = Draw.Create(default_value) + GUI.evtShadingStyleMenu = 21 + + GUI.evtPolygonsToggle = 3 + # We hide the config.polygons['EXPANSION_TRICK'], for now + + # Render polygon edges + GUI.showEdgesToggle = Draw.Create(config.edges['SHOW']) + GUI.evtShowEdgesToggle = 4 + + # Render hidden edges + GUI.showHiddenEdgesToggle = Draw.Create(config.edges['SHOW_HIDDEN']) + GUI.evtShowHiddenEdgesToggle = 5 + + # Edge Style menu + edge_style = config.edges['STYLE'] + default_value = edgeStyles.keys().index(edge_style)+1 + GUI.edgeStyleMenu = Draw.Create(default_value) + GUI.evtEdgeStyleMenu = 6 + + # Edge Width slider + GUI.edgeWidthSlider = Draw.Create(config.edges['WIDTH']) + GUI.evtEdgeWidthSlider = 7 + + # Edge Color Picker + c = config.edges['COLOR'] + GUI.edgeColorPicker = Draw.Create(c[0]/255.0, c[1]/255.0, c[2]/255.0) + GUI.evtEdgeColorPicker = 71 + + # Render Button + GUI.evtRenderButton = 8 + + # Exit Button + GUI.evtExitButton = 9 + + def draw(): + + # initialize static members + GUI._init() + + glClear(GL_COLOR_BUFFER_BIT) + glColor3f(0.0, 0.0, 0.0) + glRasterPos2i(10, 350) + Draw.Text("VRM: Vector Rendering Method script.") + glRasterPos2i(10, 335) + Draw.Text("Press Q or ESC to quit.") + + # Build the output format menu + glRasterPos2i(10, 310) + Draw.Text("Select the output Format:") + outMenuStruct = "Output Format %t" + for t in outputWriters.keys(): + outMenuStruct = outMenuStruct + "|%s" % t + GUI.outFormatMenu = Draw.Menu(outMenuStruct, GUI.evtOutFormatMenu, + 10, 285, 160, 18, GUI.outFormatMenu.val, "Choose the Output Format") + + # Animation toggle + GUI.animToggle = Draw.Toggle("Animation", GUI.evtAnimToggle, + 10, 260, 160, 18, GUI.animToggle.val, + "Toggle rendering of animations") + + # Join Objects toggle + GUI.joinObjsToggle = Draw.Toggle("Join objects", GUI.evtJoinObjsToggle, + 10, 235, 160, 18, GUI.joinObjsToggle.val, + "Join objects in the rendered file") + + # Render Button + Draw.Button("Render", GUI.evtRenderButton, 10, 210-25, 75, 25+18, + "Start Rendering") + Draw.Button("Exit", GUI.evtExitButton, 95, 210-25, 75, 25+18, "Exit!") + + # Rendering Styles + glRasterPos2i(200, 310) + Draw.Text("Rendering Style:") + + # Render Polygons + GUI.polygonsToggle = Draw.Toggle("Filled Polygons", GUI.evtPolygonsToggle, + 200, 285, 160, 18, GUI.polygonsToggle.val, + "Render filled polygons") + + if GUI.polygonsToggle.val == 1: + + # Polygon Shading Style + shadingStyleMenuStruct = "Shading Style %t" + for t in shadingStyles.keys(): + shadingStyleMenuStruct = shadingStyleMenuStruct + "|%s" % t.lower() + GUI.shadingStyleMenu = Draw.Menu(shadingStyleMenuStruct, GUI.evtShadingStyleMenu, + 200, 260, 160, 18, GUI.shadingStyleMenu.val, + "Choose the shading style") + + + # Render Edges + GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle, + 200, 235, 160, 18, GUI.showEdgesToggle.val, + "Render polygon edges") + + if GUI.showEdgesToggle.val == 1: + + # Edge Style + edgeStyleMenuStruct = "Edge Style %t" + for t in edgeStyles.keys(): + edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t.lower() + GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu, + 200, 210, 160, 18, GUI.edgeStyleMenu.val, + "Choose the edge style") + + # Edge size + GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider, + 200, 185, 140, 18, GUI.edgeWidthSlider.val, + 0.0, 10.0, 0, "Change Edge Width") + + # Edge Color + GUI.edgeColorPicker = Draw.ColorPicker(GUI.evtEdgeColorPicker, + 342, 185, 18, 18, GUI.edgeColorPicker.val, "Choose Edge Color") + + # Show Hidden Edges + GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges", + GUI.evtShowHiddenEdgesToggle, + 200, 160, 160, 18, GUI.showHiddenEdgesToggle.val, + "Render hidden edges as dashed lines") + + glRasterPos2i(10, 160) + Draw.Text("Antonio Ospite (c) 2006") + + def event(evt, val): + + if evt == Draw.ESCKEY or evt == Draw.QKEY: + Draw.Exit() + else: + return + + Draw.Redraw(1) + + def button_event(evt): + + if evt == GUI.evtExitButton: + Draw.Exit() + + elif evt == GUI.evtOutFormatMenu: + i = GUI.outFormatMenu.val - 1 + config.output['FORMAT']= outputWriters.keys()[i] + + elif evt == GUI.evtAnimToggle: + config.outpur['ANIMATION'] = bool(GUI.animToggle.val) + + elif evt == GUI.evtJoinObjsToggle: + config.output['JOIN_OBJECTS'] = bool(GUI.joinObjsToggle.val) + + elif evt == GUI.evtPolygonsToggle: + config.polygons['SHOW'] = bool(GUI.polygonsToggle.val) + + elif evt == GUI.evtShadingStyleMenu: + i = GUI.shadingStyleMenu.val - 1 + config.polygons['SHADING'] = shadingStyles.keys()[i] + + elif evt == GUI.evtShowEdgesToggle: + config.edges['SHOW'] = bool(GUI.showEdgesToggle.val) + + elif evt == GUI.evtShowHiddenEdgesToggle: + config.edges['SHOW_HIDDEN'] = bool(GUI.showHiddenEdgesToggle.val) + + elif evt == GUI.evtEdgeStyleMenu: + i = GUI.edgeStyleMenu.val - 1 + config.edges['STYLE'] = edgeStyles.keys()[i] + + elif evt == GUI.evtEdgeWidthSlider: + config.edges['WIDTH'] = float(GUI.edgeWidthSlider.val) + + elif evt == GUI.evtEdgeColorPicker: + config.edges['COLOR'] = [int(c*255.0) for c in GUI.edgeColorPicker.val] + + elif evt == GUI.evtRenderButton: + label = "Save %s" % config.output['FORMAT'] + # Show the File Selector + global outputfile + Blender.Window.FileSelector(vectorize, label, outputfile) + + else: + print "Event: %d not handled!" % evt + + if evt: + Draw.Redraw(1) + GUI.conf_debug() + + def conf_debug(): + from pprint import pprint + print "\nConfig" + pprint(config.output) + pprint(config.polygons) + pprint(config.edges) + + _init = staticmethod(_init) + draw = staticmethod(draw) + event = staticmethod(event) + button_event = staticmethod(button_event) + conf_debug = staticmethod(conf_debug) + +# A wrapper function for the vectorizing process def vectorize(filename): """The vectorizing process is as follows: - Instanciate the writer and the renderer - Render! """ + + if filename == "": + print "\nERROR: invalid file name!" + return + from Blender import Window editmode = Window.EditMode() if editmode: Window.EditMode(0) - writer = SVGVectorWriter(filename) + actualWriter = outputWriters[config.output['FORMAT']] + writer = actualWriter(filename) renderer = Renderer() - renderer.doRendering(writer, RENDER_ANIMATION) + renderer.doRendering(writer, config.output['ANIMATION']) if editmode: Window.EditMode(1) -def vectorize_gui(filename): - """Draw the gui. - - I would like to keep that simple, really. - """ - Blender.Window.FileSelector (vectorize, 'Save SVG', filename) - Blender.Redraw() - # Here the main if __name__ == "__main__": - import os - outputfile = os.path.splitext(Blender.Get('filename'))[0]+".svg" + outputfile = "" + basename = Blender.sys.basename(Blender.Get('filename')) + if basename != "": + outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower() - # with this trick we can run the script in batch mode - try: - vectorize_gui(outputfile) - except: + if Blender.mode == 'background': vectorize(outputfile) + else: + Draw.Register(GUI.draw, GUI.event, GUI.button_event)