X-Git-Url: https://git.ao2.it/vrm.git/blobdiff_plain/d2402688723bb3a348e7991869e45145db77a959..d9268e6c053da9eaddaa67883ade7a701057f264:/vrm.py diff --git a/vrm.py b/vrm.py index e6ac4d4..7ac4d83 100755 --- a/vrm.py +++ b/vrm.py @@ -1,7 +1,7 @@ #!BPY """ Name: 'VRM' -Blender: 242 +Blender: 245 Group: 'Render' Tooltip: 'Vector Rendering Method script' """ @@ -15,7 +15,7 @@ __bpydoc__ = """\ """ # --------------------------------------------------------------------- -# Copyright (c) 2006 Antonio Ospite +# Copyright (c) 2006, 2007, 2008 Antonio Ospite # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -40,13 +40,14 @@ __bpydoc__ = """\ # from scratch but Nikola gave me the idea, so I thank him publicly. # # --------------------------------------------------------------------- -# +# # Things TODO for a next release: +# - Shadeless shader # - FIX the issue with negative scales in object tranformations! # - Use a better depth sorting algorithm # - Review how selections are made (this script uses selection states of # primitives to represent visibility infos) -# - Use a data structure other than Mesh to represent the 2D image? +# - 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. # Or a way to use paths for silhouettes and contours. # - Consider SMIL for animation handling instead of ECMA Script? (Firefox do @@ -64,6 +65,7 @@ __bpydoc__ = """\ # Changelog: # # vrm-0.3.py - ... +# * Adapted to blender API 2.45 # * First release after code restucturing. # Now the script offers a useful set of functionalities # and it can render animations, too. @@ -85,6 +87,9 @@ __bpydoc__ = """\ # * Scene clipping done using bounding box instead of object center # * Fix camera type selection for blender>2.43 (Thanks to Thomas Lachmann) # * Compatibility with python 2.3 +# * Process only object that are on visible layers. +# * Saving config to registry (Thanks to Thomas Lachmann for a draft +# implementation) # # --------------------------------------------------------------------- @@ -108,7 +113,7 @@ EPS = 10e-5 progress = None -# Some global settings +# Config class for global settings class config: polygons = dict() @@ -132,6 +137,41 @@ class config: output['ANIMATION'] = False output['JOIN_OBJECTS'] = True + def saveToRegistry(): + registry = {} + + for k,v in config.__dict__.iteritems(): + + # config class store settings in dictionaries + if v.__class__ == dict().__class__: + + regkey_prefix = k.upper()+"_" + + for opt_k,opt_v in v.iteritems(): + regkey = regkey_prefix + opt_k + + registry[regkey] = opt_v + + Blender.Registry.SetKey('VRM', registry, True) + + saveToRegistry = staticmethod(saveToRegistry) + + def loadFromRegistry(): + registry = Blender.Registry.GetKey('VRM', True) + if not registry: + return + + for k,v in registry.iteritems(): + k_tmp = k.split('_') + conf_attr = k_tmp[0].lower() + conf_key = str.join("_",k_tmp[1:]) + conf_val = v + + if config.__dict__.has_key(conf_attr): + config.__dict__[conf_attr][conf_key] = conf_val + + loadFromRegistry = staticmethod(loadFromRegistry) + # Utility functions print_debug = False @@ -160,7 +200,7 @@ def debug(msg): sys.stderr.write(msg) def EQ(v1, v2): - return (abs(v1[0]-v2[0]) < EPS and + return (abs(v1[0]-v2[0]) < EPS and abs(v1[1]-v2[1]) < EPS ) by_furthest_z = (lambda f1, f2: cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS) @@ -199,8 +239,8 @@ class HSR: Geometric objects lying in a common plane are said to be coplanar. Three noncollinear points determine a plane and so are trivially coplanar. Four points are coplanar iff the volume of the tetrahedron defined by them is - 0, - + 0, + | x_1 y_1 z_1 1 | | x_2 y_2 z_2 1 | | x_3 y_3 z_3 1 | @@ -334,7 +374,7 @@ class HSR: def det(a, b, c): return ((b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]) ) - + det = staticmethod(det) def pointInPolygon(q, P): @@ -482,7 +522,7 @@ class HSR: # #newfaces = splitOn(plane, f) - + if newfaces == None: print "Big FAT problem, we weren't able to split POLYGONS!" raise AssertionError @@ -640,7 +680,7 @@ class HSR: #print "d0:", d0, "d1:", d1 - # if the vertex lies in the cutplane + # if the vertex lies in the cutplane if abs(d1) < EPS: #print "d1 On cutplane" posVertList.append(V1) @@ -691,7 +731,7 @@ class HSR: else: negVertList.append(V1) - + # uniq for python > 2.4 #posVertList = [ u for u in posVertList if u not in locals()['_[1]'] ] #negVertList = [ u for u in negVertList if u not in locals()['_[1]'] ] @@ -752,7 +792,7 @@ class HSR: class MeshUtils: def buildEdgeFaceUsersCache(me): - ''' + ''' Takes a mesh and returns a list aligned with the meshes edges. Each item is a list of the faces that use the edge would be the equiv for having ed.face_users as a property @@ -768,23 +808,23 @@ class MeshUtils: i1,i2= i2,i1 return i1, i2 - + face_edges_dict= dict([(sorted_edge_indicies(ed), (ed.index, [])) for ed in me.edges]) for f in me.faces: fvi= [v.index for v in f.v]# face vert idx's for i in xrange(len(f)): i1= fvi[i] i2= fvi[i-1] - + if i1>i2: i1,i2= i2,i1 - + face_edges_dict[i1,i2][1].append(f) - + face_edges= [None] * len(me.edges) for ed_index, ed_faces in face_edges_dict.itervalues(): face_edges[ed_index]= ed_faces - + return face_edges def isMeshEdge(adjacent_faces): @@ -875,10 +915,10 @@ class ShadingUtils: class Projector: """Calculate the projection of an object given the camera. - + A projector is useful to so some per-object transformation to obtain the projection of an object given the camera. - + The main method is #doProjection# see the method description for the parameter list. """ @@ -908,18 +948,18 @@ class Projector: else: camPersp = 'persp' camOrtho = 'ortho' - + # What projection do we want? if camera.type == camPersp: - mP = self._calcPerspectiveMatrix(fovy, aspect, near, far) + mP = self._calcPerspectiveMatrix(fovy, aspect, near, far) elif camera.type == camOrtho: mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale) - + # View transformation cam = Matrix(cameraObj.getInverseMatrix()) - cam.transpose() - + cam.transpose() + mP = mP * cam self.projectionMatrix = mP @@ -934,13 +974,13 @@ class Projector: Given a vertex calculate the projection using the current projection matrix. """ - + # Note that we have to work on the vertex using homogeneous coordinates # From blender 2.42+ we don't need to resize the vector to be 4d # when applying a 4x4 matrix, but we do that anyway since we need the # 4th coordinate later p = self.projectionMatrix * Vector(v).resize4D() - + # Perspective division if p[3] != 0: p[0] = p[0]/p[3] @@ -957,11 +997,11 @@ class Projector: ## # Private methods # - + def _calcPerspectiveMatrix(self, fovy, aspect, near, far): """Return a perspective projection matrix. """ - + top = near * tan(fovy * pi / 360.0) bottom = -top left = bottom*aspect @@ -972,7 +1012,7 @@ class Projector: b = (top+bottom) / (top - bottom) c = - ((far+near) / (far-near)) d = - ((2*far*near)/(far-near)) - + m = Matrix( [x, 0.0, a, 0.0], [0.0, y, b, 0.0], @@ -984,15 +1024,15 @@ class Projector: def _calcOrthoMatrix(self, fovy, aspect , near, far, scale): """Return an orthogonal projection matrix. """ - + # The 11 in the formula was found emiprically top = near * tan(fovy * pi / 360.0) * (scale * 11) - bottom = -top + bottom = -top left = bottom * aspect right= top * aspect rl = right-left tb = top-bottom - fn = near-far + fn = near-far tx = -((right+left)/rl) ty = -((top+bottom)/tb) tz = ((far+near)/fn) @@ -1002,7 +1042,7 @@ class Projector: [0.0, 2.0/tb, 0.0, ty], [0.0, 0.0, 2.0/fn, tz], [0.0, 0.0, 0.0, 1.0]) - + return m @@ -1014,7 +1054,7 @@ class Projector: class Progress: """A model for a progress indicator. - + Do the progress calculation calculation and the view independent stuff of a progress indicator. """ @@ -1126,7 +1166,7 @@ class ConsoleProgressIndicator(ProgressIndicator): def show(self, progress, name): ProgressIndicator.show(self, progress, name) - + bar_length = 70 bar_progress = int( (progress/100.0) * bar_length ) bar = ("=" * bar_progress).ljust(bar_length) @@ -1204,12 +1244,18 @@ class VectorWriter: - printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False): """ - + def __init__(self, fileName): """Set the output file name and other properties""" + try: + config.writer + except: + config.writer = dict() + config.writer['SETTING'] = True + self.outputFileName = fileName - + context = Scene.GetCurrent().getRenderingContext() self.canvasSize = ( context.imageSizeX(), context.imageSizeY() ) @@ -1223,7 +1269,7 @@ class VectorWriter: ## # Public Methods # - + def open(self, startFrame=1, endFrame=1): if startFrame != endFrame: self.startFrame = startFrame @@ -1242,7 +1288,7 @@ class VectorWriter: """This is the interface for the needed printing routine. """ return - + ## SVG Writer @@ -1282,13 +1328,13 @@ class SVGVectorWriter(VectorWriter): # remember to call the close method of the parent as last VectorWriter.close(self) - + def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False): """Convert the scene representation to SVG. """ - Objects = scene.getChildren() + Objects = scene.objects context = scene.getRenderingContext() framenumber = context.currentFrame() @@ -1297,7 +1343,7 @@ class SVGVectorWriter(VectorWriter): framestyle = "display:none" else: framestyle = "display:block" - + # Assign an id to this group so we can set properties on it using DOM self.file.write("\n" % (framenumber, framestyle) ) @@ -1317,22 +1363,22 @@ class SVGVectorWriter(VectorWriter): if doPrintEdges: self._printEdges(mesh, showHiddenEdges) - + self.file.write("\n") self.file.write("\n") - - ## + + ## # Private Methods # - + def _calcCanvasCoord(self, v): """Convert vertex in scene coordinates to canvas coordinates. """ pt = Vector([0, 0, 0]) - + mW = float(self.canvasSize[0])/2.0 mH = float(self.canvasSize[1])/2.0 @@ -1340,12 +1386,12 @@ class SVGVectorWriter(VectorWriter): pt[0] = v.co[0]*mW + mW pt[1] = v.co[1]*mH + mH pt[2] = v.co[2] - + # For now we want (0,0) in the top-left corner of the canvas. # Mirror and translate along y pt[1] *= -1 pt[1] += self.canvasSize[1] - + return pt def _printHeader(self): @@ -1397,13 +1443,13 @@ class SVGVectorWriter(VectorWriter): } \n]]>\n \n""") - + def _printFooter(self): """Print the SVG footer.""" self.file.write("\n\n") - def _printPolygons(self, mesh): + def _printPolygons(self, mesh): """Print the selected (visible) polygons. """ @@ -1425,11 +1471,11 @@ class SVGVectorWriter(VectorWriter): for v in face.v[1:]: p = self._calcCanvasCoord(v) self.file.write("%g,%g " % (p[0], p[1])) - + # get rid of the last blank space, just cosmetics here. - self.file.seek(-1, 1) + self.file.seek(-1, 1) self.file.write(" z\"\n") - + # take as face color the first vertex color if face.col: fcol = face.col[0] @@ -1472,13 +1518,13 @@ class SVGVectorWriter(VectorWriter): stroke_width = config.edges['WIDTH'] stroke_col = config.edges['COLOR'] - + self.file.write("\n") for e in mesh.edges: - + hidden_stroke_style = "" - + if e.sel == 0: if showHiddenEdges == False: continue @@ -1487,7 +1533,7 @@ class SVGVectorWriter(VectorWriter): p1 = self._calcCanvasCoord(e.v1) p2 = self._calcCanvasCoord(e.v2) - + self.file.write(" 0 the face is visible from the camera d = view_vect * normal - + if d > 0: return True else: @@ -2109,6 +2149,38 @@ class Renderer: # Scene methods + def _filterHiddenObjects(self, scene): + """Discard object that are on hidden layers in the scene. + """ + + Objects = scene.objects + + visible_obj_list = [ obj for obj in Objects if + set(obj.layers).intersection(set(scene.getLayers())) ] + + for o in Objects: + if o not in visible_obj_list: + scene.objects.unlink(o) + + scene.update() + + + + def _buildLightSetup(self, scene): + # Get the list of lighting sources + obj_lst = scene.objects + 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) + + def _doSceneClipping(self, scene): """Clip whole objects against the View Frustum. @@ -2125,7 +2197,8 @@ class Renderer: fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32)) fovy = fovy * 360.0/pi - Objects = scene.getChildren() + Objects = scene.objects + for o in Objects: if o.getType() != 'Mesh': continue; @@ -2134,17 +2207,17 @@ class Renderer: 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) + scene.objects.unlink(o) """ # Use the object bounding box # (whose points are already in WorldSpace Coordinate) bb = o.getBoundBox() - + points_outside = 0 for p in bb: p_vect = Vector(cam_pos) - Vector(p) @@ -2159,7 +2232,7 @@ class Renderer: # If the bb is all outside the view frustum we clip the whole # object away if points_outside == len(bb): - scene.unlink(o) + scene.objects.unlink(o) @@ -2169,13 +2242,14 @@ class Renderer: geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text'] #geometricObjTypes = ['Mesh', 'Surf', 'Curve'] - Objects = scene.getChildren() + Objects = scene.objects + 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) + scene.objects.link(obj) + scene.objects.unlink(old_obj) # XXX Workaround for Text and Curve which have some normals @@ -2214,21 +2288,22 @@ class Renderer: ) ) - - Objects = scene.getChildren() + + Objects = list(scene.objects) + #Objects.sort(by_obj_center_pos) Objects.sort(by_nearest_bbox_point) - + # update the scene for o in Objects: - scene.unlink(o) - scene.link(o) + scene.objects.unlink(o) + scene.objects.link(o) def _joinMeshObjectsInScene(self, scene): """Merge all the Mesh Objects in a scene into a single Mesh Object. """ - oList = [o for o in scene.getChildren() if o.getType()=='Mesh'] + oList = [o for o in scene.objects if o.getType()=='Mesh'] # FIXME: Object.join() do not work if the list contains 1 object if len(oList) == 1: @@ -2238,23 +2313,23 @@ class Renderer: bigObj = Object.New('Mesh', 'BigOne') bigObj.link(mesh) - scene.link(bigObj) + scene.objects.link(bigObj) try: bigObj.join(oList) except RuntimeError: print "\nWarning! - Can't Join Objects\n" - scene.unlink(bigObj) + scene.objects.unlink(bigObj) return except TypeError: print "Objects Type error?" - + for o in oList: - scene.unlink(o) + scene.objects.unlink(o) scene.update() - + # Per object/mesh methods def _convertToRawMeshObj(self, object): @@ -2290,16 +2365,16 @@ class Renderer: def _doBackFaceCulling(self, mesh): """Simple Backface Culling routine. - + At this level we simply do a visibility test face by face and then select the vertices belonging to visible faces. """ - + # Select all vertices, so edges can be displayed even if there are no # faces for v in mesh.verts: v.sel = 1 - + Mesh.Mode(Mesh.SelectModes['FACE']) # Loop on faces for f in mesh.faces: @@ -2364,7 +2439,7 @@ class Renderer: light_obj = l light_pos = self._getObjPosition(l) light = light_obj.getData() - + L = Vector(light_pos).normalize() V = (Vector(camPos) - Vector(f.cent)).normalize() @@ -2454,7 +2529,7 @@ class Renderer: # The Canonical View Volume, 8 vertices, and 6 faces, # We consider its face normals pointing outside - + v1 = NMesh.Vert(1, 1, -1) v2 = NMesh.Vert(1, -1, -1) v3 = NMesh.Vert(-1, -1, -1) @@ -2487,8 +2562,9 @@ class Renderer: clippedfaces = [] for f in facelist: - - newfaces = HSR.splitOn(clipface, f, return_positive_faces=False) + + #newfaces = HSR.splitOn(clipface, f, return_positive_faces=False) + newfaces = None if not newfaces: # Check if the face is all outside the view frustum @@ -2515,7 +2591,7 @@ class Renderer: nmesh.faces = facelist nmesh.update() - + # HSR routines def __simpleDepthSort(self, mesh): @@ -2587,7 +2663,7 @@ class Renderer: progress.setActivity("HSR: Newell", len(facelist)) #progress.setQuiet(True) - + while len(facelist): debug("\n----------------------\n") debug("len(facelits): %d\n" % len(facelist)) @@ -2611,7 +2687,7 @@ class Renderer: qSign = sign(Q.normal[2]) # TODO: check also if Q is parallel?? - + # Test 0: We need to test only those Qs whose furthest vertex # is closer to the observer than the closest vertex of P. @@ -2629,7 +2705,7 @@ class Renderer: debug("met a marked face\n") continue - + # Test 1: X extent overlapping xP = [v.co[0] for v in P.v] xQ = [v.co[0] for v in Q.v] @@ -2652,7 +2728,7 @@ class Renderer: debug("\nTest 2\n") debug("NOT Y OVERLAP!\n") continue - + # Test 3: P vertices are all behind the plane of Q n = 0 @@ -2702,7 +2778,7 @@ class Renderer: facelist = HSR.facesplit(P, Q, facelist, nmesh) split_done = 1 - break + break # The question now is: Does Q obscure P? @@ -2732,7 +2808,7 @@ class Renderer: debug("\nTest 4bis\n") debug("P IN FRONT OF Q!\n") - + # We don't even know if Q does obscure P, so they should # intersect each other, split one of them in two parts. if not qVerticesBehindPlaneP and not pVerticesInFrontPlaneQ: @@ -2742,16 +2818,16 @@ class Renderer: facelist = HSR.facesplit(P, Q, facelist, nmesh) split_done = 1 - break - + break + facelist.remove(Q) facelist.insert(0, Q) Q.smooth = 1 face_marked = 1 debug("Q marked!\n") break - - # Write P! + + # Write P! if split_done == 0 and face_marked == 0: facelist.remove(P) maplist.append(P) @@ -2770,7 +2846,7 @@ class Renderer: # break # end of while len(facelist) - + nmesh.faces = maplist #for f in nmesh.faces: @@ -2838,10 +2914,10 @@ from Blender import BGL, Draw from Blender.BGL import * class GUI: - + def _init(): - # Output Format menu + # Output Format menu output_format = config.output['FORMAT'] default_value = outputWriters.keys().index(output_format)+1 GUI.outFormatMenu = Draw.Create(default_value) @@ -2858,7 +2934,7 @@ class GUI: # Render filled polygons GUI.polygonsToggle = Draw.Create(config.polygons['SHOW']) - # Shading Style menu + # Shading Style menu shading_style = config.polygons['SHADING'] default_value = shadingStyles.keys().index(shading_style)+1 GUI.shadingStyleMenu = Draw.Create(default_value) @@ -2875,7 +2951,7 @@ class GUI: GUI.showHiddenEdgesToggle = Draw.Create(config.edges['SHOW_HIDDEN']) GUI.evtShowHiddenEdgesToggle = 5 - # Edge Style menu + # Edge Style menu edge_style = config.edges['STYLE'] default_value = edgeStyles.keys().index(edge_style)+1 GUI.edgeStyleMenu = Draw.Create(default_value) @@ -2896,6 +2972,9 @@ class GUI: # Exit Button GUI.evtExitButton = 9 + # Save default button + GUI.evtSaveDefaultButton = 99 + def draw(): # initialize static members @@ -2903,9 +2982,12 @@ class GUI: glClear(GL_COLOR_BUFFER_BIT) glColor3f(0.0, 0.0, 0.0) - glRasterPos2i(10, 350) + glRasterPos2i(10, 380) Draw.Text("VRM: Vector Rendering Method script. Version %s." % __version__) + glRasterPos2i(10, 365) + Draw.Text("%s (c) 2006, 2007" % __author__) + glRasterPos2i(10, 335) Draw.Text("Press Q or ESC to quit.") @@ -2933,6 +3015,9 @@ class GUI: "Start Rendering") Draw.Button("Exit", GUI.evtExitButton, 95, 210-25, 75, 25+18, "Exit!") + Draw.Button("Save settings as default", GUI.evtSaveDefaultButton, 10, 210-50, 160, 18, + "Save settings as default") + # Rendering Styles glRasterPos2i(200, 310) Draw.Text("Rendering Style:") @@ -2959,7 +3044,7 @@ class GUI: "Render polygon edges") if GUI.showEdgesToggle.val == 1: - + # Edge Style edgeStyleMenuStruct = "Edge Style %t" for t in edgeStyles.keys(): @@ -2983,8 +3068,6 @@ class GUI: 200, 160, 160, 18, GUI.showHiddenEdgesToggle.val, "Render hidden edges as dashed lines") - glRasterPos2i(10, 160) - Draw.Text("%s (c) 2006" % __author__) def event(evt, val): @@ -3042,6 +3125,9 @@ class GUI: global outputfile Blender.Window.FileSelector(vectorize, label, outputfile) + elif evt == GUI.evtSaveDefaultButton: + config.saveToRegistry() + else: print "Event: %d not handled!" % evt @@ -3065,7 +3151,7 @@ class GUI: # A wrapper function for the vectorizing process def vectorize(filename): """The vectorizing process is as follows: - + - Instanciate the writer and the renderer - Render! """ @@ -3080,11 +3166,11 @@ def vectorize(filename): actualWriter = outputWriters[config.output['FORMAT']] writer = actualWriter(filename) - + renderer = Renderer() renderer.doRendering(writer, config.output['ANIMATION']) - if editmode: Window.EditMode(1) + if editmode: Window.EditMode(1) @@ -3093,6 +3179,13 @@ if __name__ == "__main__": global progress + config.loadFromRegistry() + + # initialize writer setting also here to configure writer specific + # settings on startup + actualWriter = outputWriters[config.output['FORMAT']] + writer = actualWriter("") + outputfile = "" basename = Blender.sys.basename(Blender.Get('filename')) if basename != "":