X-Git-Url: https://git.ao2.it/vrm.git/blobdiff_plain/40fe52111a7e783234bd644b51b28d206f890f08..c0cad5c078eca627696971112e37153cedc13072:/vrm.py?ds=inline diff --git a/vrm.py b/vrm.py index da71fab..d9c2d40 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,15 +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 -# - Implement clipping of primitives and do handle object intersections. -# (for now only clipping away whole objects is supported). # - 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 @@ -66,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. @@ -75,9 +75,21 @@ __bpydoc__ = """\ # * The SVG output is now SVG 1.0 valid. # Checked with: http://jiggles.w3.org/svgvalidator/ValidatorURI.html # * Progress indicator during HSR. -# * Initial SWF output support +# * Initial SWF output support (using ming) # * Fixed a bug in the animation code, now the projection matrix is # recalculated at each frame! +# * PDF output (using reportlab) +# * Fixed another problem in the animation code the current frame was off +# by one in the case of camera movement. +# * Use fps as specified in blender when VectorWriter handles animation +# * Remove the real file opening in the abstract VectorWriter +# * View frustum clipping +# * 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) # # --------------------------------------------------------------------- @@ -87,6 +99,19 @@ from Blender.Mathutils import * from math import * import sys, time +try: + set() +except NameError: + from sets import Set as set + + +def uniq(alist): + tmpdict = dict() + return [tmpdict.setdefault(e,e) for e in alist if e not in tmpdict] + # in python > 2.4 we ca use the following + #return [ u for u in alist if u not in locals()['_[1]'] ] + + # Constants EPS = 10e-5 @@ -94,7 +119,7 @@ EPS = 10e-5 progress = None -# Some global settings +# Config class for global settings class config: polygons = dict() @@ -118,6 +143,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 @@ -146,7 +206,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) @@ -185,8 +245,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 | @@ -320,7 +380,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): @@ -468,7 +528,7 @@ class HSR: # #newfaces = splitOn(plane, f) - + if newfaces == None: print "Big FAT problem, we weren't able to split POLYGONS!" raise AssertionError @@ -587,7 +647,7 @@ class HSR: makeFaces = staticmethod(makeFaces) - def splitOn(Q, P): + def splitOn(Q, P, return_positive_faces=True, return_negative_faces=True): """Split P using the plane of Q. Logic taken from the knife.py python script """ @@ -626,7 +686,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) @@ -677,24 +737,32 @@ class HSR: else: negVertList.append(V1) - - # uniq - posVertList = [ u for u in posVertList if u not in locals()['_[1]'] ] - negVertList = [ u for u in negVertList if u not in locals()['_[1]'] ] + + # 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]'] ] + + # a more portable way + posVertList = uniq(posVertList) + negVertList = uniq(negVertList) # If vertex are all on the same half-space, return #if len(posVertList) < 3: - # print "Problem, we created a face with less that 3 verteices??" + # print "Problem, we created a face with less that 3 vertices??" # posVertList = [] #if len(negVertList) < 3: - # print "Problem, we created a face with less that 3 verteices??" + # print "Problem, we created a face with less that 3 vertices??" # negVertList = [] if len(posVertList) < 3 or len(negVertList) < 3: - print "RETURN NONE, SURE???" + #print "RETURN NONE, SURE???" return None + if not return_positive_faces: + posVertList = [] + if not return_negative_faces: + negVertList = [] newfaces = HSR.addNewFaces(posVertList, negVertList) @@ -730,7 +798,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 @@ -746,23 +814,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): @@ -853,10 +921,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. """ @@ -878,17 +946,26 @@ class Projector: fovy = atan(0.5/aspect/(camera.lens/32)) fovy = fovy * 360.0/pi - + + + if Blender.Get('version') < 243: + camPersp = 0 + camOrtho = 1 + else: + camPersp = 'persp' + camOrtho = 'ortho' + # What projection do we want? - if camera.type == 0: - mP = self._calcPerspectiveMatrix(fovy, aspect, near, far) - elif camera.type == 1: - mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale) - + if camera.type == camPersp: + 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 @@ -903,13 +980,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] @@ -926,11 +1003,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 @@ -941,7 +1018,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], @@ -953,15 +1030,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) @@ -971,7 +1048,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 @@ -983,7 +1060,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. """ @@ -1095,7 +1172,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) @@ -1173,16 +1250,23 @@ 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 - self.file = None - + context = Scene.GetCurrent().getRenderingContext() self.canvasSize = ( context.imageSizeX(), context.imageSizeY() ) + self.fps = context.fps + self.startFrame = 1 self.endFrame = 1 self.animation = False @@ -1191,21 +1275,18 @@ class VectorWriter: ## # Public Methods # - + def open(self, startFrame=1, endFrame=1): if startFrame != endFrame: self.startFrame = startFrame self.endFrame = endFrame self.animation = True - self.file = open(self.outputFileName, "w") print "Outputting to: ", self.outputFileName return def close(self): - if self.file: - self.file.close() return def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False, @@ -1213,7 +1294,7 @@ class VectorWriter: """This is the interface for the needed printing routine. """ return - + ## SVG Writer @@ -1226,6 +1307,8 @@ class SVGVectorWriter(VectorWriter): """ VectorWriter.__init__(self, fileName) + self.file = None + ## # Public Methods @@ -1235,6 +1318,9 @@ class SVGVectorWriter(VectorWriter): """Do some initialization operations. """ VectorWriter.open(self, startFrame, endFrame) + + self.file = open(self.outputFileName, "w") + self._printHeader() def close(self): @@ -1242,16 +1328,19 @@ class SVGVectorWriter(VectorWriter): """ self._printFooter() - # remember to call the close method of the parent + if self.file: + self.file.close() + + # 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() @@ -1260,7 +1349,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) ) @@ -1280,22 +1369,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 @@ -1303,12 +1392,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): @@ -1323,15 +1412,17 @@ class SVGVectorWriter(VectorWriter): self.canvasSize) if self.animation: + delay = 1000/self.fps self.file.write("""\n\n - \n""" % (self.startFrame, self.endFrame, self.startFrame) ) - + \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. """ @@ -1386,11 +1477,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] @@ -1433,13 +1524,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 @@ -1448,7 +1539,7 @@ class SVGVectorWriter(VectorWriter): p1 = self._calcCanvasCoord(e.v1) p2 = self._calcCanvasCoord(e.v2) - + self.file.write("