From: Antonio Ospite Date: Sat, 5 Aug 2006 15:40:36 +0000 (+0200) Subject: Add edge coloring X-Git-Tag: vrm-0.3~24 X-Git-Url: https://git.ao2.it/vrm.git/commitdiff_plain/107a4b3320d72d749c33bfe1a2bb9250cef78914 Add edge coloring * Implement Edge coloring * Fix some values in the config class * Apply the color model to objects, FLAT or TOON? * Add edge color picker to the GUI Signed-off-by: Antonio Ospite --- diff --git a/vrm.py b/vrm.py index fc7cb5d..c7d7a87 100755 --- a/vrm.py +++ b/vrm.py @@ -50,7 +50,6 @@ __bpydoc__ = """\ # - 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). -# - Implement Edge coloring # - Use multiple lighting sources in color calculation # - Implement Shading Styles? (for now we use Flat Shading). # - Use a data structure other than Mesh to represent the 2D image? @@ -82,21 +81,22 @@ from math import * class config: polygons = dict() - polygons['FILL'] = True - polygons['STYLE'] = None + polygons['SHOW'] = True + polygons['SHADING'] = 'TOON' # Hidden to the user for now polygons['EXPANSION_TRICK'] = True edges = dict() edges['SHOW'] = True edges['SHOW_HIDDEN'] = False - edges['STYLE'] = 'silhouette' + edges['STYLE'] = 'SILHOUETTE' edges['WIDTH'] = 2 + edges['COLOR'] = [0, 0, 0] output = dict() output['FORMAT'] = 'SVG' output['ANIMATION'] = False - output['MERGED_OBJECTS'] = True + output['JOIN_OBJECTS'] = True @@ -120,10 +120,10 @@ class MeshUtils: return adjface_list - def isVisibleEdge(e, mesh): - """Normal edge selection rule. + def isMeshEdge(e, mesh): + """Mesh edge rule. - An edge is visible if _any_ of its adjacent faces is selected. + 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. """ @@ -178,7 +178,7 @@ class MeshUtils: getEdgeAdjacentFaces = staticmethod(getEdgeAdjacentFaces) - isVisibleEdge = staticmethod(isVisibleEdge) + isMeshEdge = staticmethod(isMeshEdge) isSilhouetteEdge = staticmethod(isSilhouetteEdge) toonShading = staticmethod(toonShading) @@ -532,7 +532,7 @@ class SVGVectorWriter(VectorWriter): self.file.write("\n\n") - def _printPolygons(self, mesh): + def _printPolygons(self, mesh): """Print the selected (visible) polygons. """ @@ -563,15 +563,14 @@ class SVGVectorWriter(VectorWriter): else: color = [255, 255, 255, 255] + # Convert the color to the #RRGGBB form + str_col = "#%02X%02X%02X" % (color[0], color[1], color[2]) + # use the stroke property to alleviate the "adjacent edges" problem, # we simulate polygon expansion using borders, # see http://www.antigrain.com/svg/index.html for more info - stroke_col = color stroke_width = 0.5 - # Convert the color to the #RRGGBB form - str_col = "#%02X%02X%02X" % (color[0], color[1], color[2]) - # Handle transparent polygons opacity_string = "" if color[3] != 255: @@ -592,8 +591,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") @@ -628,17 +627,19 @@ class SVGVectorWriter(VectorWriter): # # --------------------------------------------------------------------- -# A dictionary to collect all the different edge styles and their edge -# selection criteria -edgeSelectionStyles = { - 'normal': MeshUtils.isVisibleEdge, - 'silhouette': MeshUtils.isSilhouetteEdge - } +# 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 = { - 'SVG': SVGVectorWriter, - } +outputWriters = dict() +outputWriters['SVG'] = SVGVectorWriter class Renderer: @@ -705,11 +706,11 @@ class Renderer: """ 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: @@ -725,22 +726,23 @@ class Renderer: # Use some temporary workspace, a full copy of the scene inputScene = self._SCENE.copy(2) - print "Before" - try: renderedScene = self.doRenderScene(inputScene) - except: + 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 = config.polygons['FILL'], + doPrintPolygons = config.polygons['SHOW'], doPrintEdges = config.edges['SHOW'], showHiddenEdges = config.edges['SHOW_HIDDEN']) - print "After" - # clear the rendered scene self._SCENE.makeCurrent() Scene.unlink(renderedScene) @@ -748,7 +750,7 @@ class Renderer: outputWriter.close() print "Done!" - context.currentFrame(currentFrame) + context.currentFrame(origCurrentFrame) def doRenderScene(self, workScene): @@ -764,19 +766,9 @@ class Renderer: self._doSceneClipping(workScene) - - # XXX: Joining objects does not work in batch mode!! - # Do not touch the following if, please :) - - global OPTIMIZE_FOR_SPACE - if Blender.mode == 'background': - print "\nWARNING! Joining objects not supported in background mode!\n" - OPTIMIZE_FOR_SPACE = False - - if OPTIMIZE_FOR_SPACE: + if config.output['JOIN_OBJECTS']: self._joinMeshObjectsInScene(workScene) - self._doSceneDepthSorting(workScene) # Per object activities @@ -790,22 +782,17 @@ class Renderer: print "Rendering: ", obj.getName() - mesh = obj.getData() + mesh = obj.getData(mesh=1) self._doModelToWorldCoordinates(mesh, obj.matrix) self._doObjectDepthSorting(mesh) - # We use both Mesh and NMesh because for depth sorting we change - # face order and Mesh class don't let us to do that. - mesh.update() - mesh = obj.getData(mesh=1) - self._doBackFaceCulling(mesh) self._doColorAndLighting(mesh) - self._doEdgesStyle(mesh, edgeSelectionStyles[EDGE_STYLE]) + self._doEdgesStyle(mesh, edgeStyles[config.edges['STYLE']]) self._doProjection(mesh, self.proj) @@ -907,7 +894,6 @@ class Renderer: me.recalcNormals() me.update() - def _doSceneClipping(self, scene): """Clip objects against the View Frustum. @@ -967,18 +953,31 @@ class Renderer: """Merge all the Mesh Objects in a scene into a single Mesh Object. """ + 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'] # FIXME: Object.join() do not work if the list contains 1 object if len(oList) == 1: return - mesh = Mesh.New() + mesh = Mesh.New('BigOne') bigObj = Object.New('Mesh', 'BigOne') bigObj.link(mesh) - bigObj.join(oList) 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) @@ -1024,6 +1023,9 @@ class Renderer: The faces in the object are sorted following the distance of the vertices from the camera position. """ + if len(mesh.faces) == 0: + return + c = self._getObjPosition(self.cameraObj) # hackish sorting of faces @@ -1043,8 +1045,19 @@ class Renderer: 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))) - mesh.faces.sort(by_max_vert_dist) - mesh.faces.reverse() + + # 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() + + 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 + def _doBackFaceCulling(self, mesh): """Simple Backface Culling routine. @@ -1065,22 +1078,10 @@ 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 module 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. """ @@ -1118,16 +1119,16 @@ class Renderer: 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 @@ -1137,8 +1138,11 @@ class Renderer: # Diffuse component (add light.col for kd) kd = mat.getRef() * Vector(mat.getRGBCol()) Ip = light.getEnergy() - #Idiff = Ip * kd * int(N*L > 0.5) - Idiff = Ip * kd * MeshUtils.toonShading(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()) @@ -1148,19 +1152,18 @@ class Renderer: # 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()) - # Toon shading - #I = [MeshUtils.toonShading(c) for c in I] - # 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] for c in f.col: @@ -1229,11 +1232,17 @@ class GUI: GUI.evtAnimToggle = 1 # Join Objects toggle button - GUI.joinObjsToggle = Draw.Create(config.output['MERGED_OBJECTS']) + GUI.joinObjsToggle = Draw.Create(config.output['JOIN_OBJECTS']) GUI.evtJoinObjsToggle = 2 # Render filled polygons - GUI.polygonsToggle = Draw.Create(config.polygons['FILL']) + 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 @@ -1248,7 +1257,7 @@ class GUI: # Edge Style menu edge_style = config.edges['STYLE'] - default_value = edgeSelectionStyles.keys().index(edge_style)+1 + default_value = edgeStyles.keys().index(edge_style)+1 GUI.edgeStyleMenu = Draw.Create(default_value) GUI.evtEdgeStyleMenu = 6 @@ -1256,6 +1265,11 @@ class GUI: 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 @@ -1307,30 +1321,45 @@ class GUI: 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, 260, 160, 18, GUI.showEdgesToggle.val, + 200, 235, 160, 18, GUI.showEdgesToggle.val, "Render polygon edges") if GUI.showEdgesToggle.val == 1: # Edge Style edgeStyleMenuStruct = "Edge Style %t" - for t in edgeSelectionStyles.keys(): - edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t + for t in edgeStyles.keys(): + edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t.lower() GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu, - 200, 235, 160, 18, GUI.edgeStyleMenu.val, + 200, 210, 160, 18, GUI.edgeStyleMenu.val, "Choose the edge style") # Edge size GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider, - 200, 210, 160, 18, GUI.edgeWidthSlider.val, + 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, 185, 160, 18, GUI.showHiddenEdgesToggle.val, + 200, 160, 160, 18, GUI.showHiddenEdgesToggle.val, "Render hidden edges as dashed lines") glRasterPos2i(10, 160) @@ -1358,10 +1387,14 @@ class GUI: config.outpur['ANIMATION'] = bool(GUI.animToggle.val) elif evt == GUI.evtJoinObjsToggle: - config.output['MERGED_OBJECTS'] = bool(GUI.joinObjsToggle.val) + config.output['JOIN_OBJECTS'] = bool(GUI.joinObjsToggle.val) elif evt == GUI.evtPolygonsToggle: - config.polygons['FILL'] = bool(GUI.polygonsToggle.val) + 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) @@ -1371,11 +1404,14 @@ class GUI: elif evt == GUI.evtEdgeStyleMenu: i = GUI.edgeStyleMenu.val - 1 - config.edges['STYLE'] = edgeSelectionStyles.keys()[i] + 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 @@ -1391,7 +1427,10 @@ class GUI: def conf_debug(): from pprint import pprint - pprint(config) + print "\nConfig" + pprint(config.output) + pprint(config.polygons) + pprint(config.edges) _init = staticmethod(_init) draw = staticmethod(draw)