# Things TODO for a next release:
# - 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?
# recalculated at each frame!
# * PDF output (using reportlab)
# * Fixed another problem in the animation code the current frame was off
-# by one
+# 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.
#
# ---------------------------------------------------------------------
from math import *
import sys, time
+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
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
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:
+ if camera.type == camPersp:
mP = self._calcPerspectiveMatrix(fovy, aspect, near, far)
- elif camera.type == 1:
- mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale)
+ elif camera.type == camOrtho:
+ mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale)
+
# View transformation
cam = Matrix(cameraObj.getInverseMatrix())
cam.transpose()
p1 = self._calcCanvasCoord(e.v1)
p2 = self._calcCanvasCoord(e.v2)
- # FIXME: this is just a qorkaround, remove that after the
- # implementation of propoer Viewport clipping
- if abs(p1[0]) < 3000 and abs(p2[0]) < 3000 and abs(p1[1]) < 3000 and abs(p1[2]) < 3000:
- s.movePenTo(p1[0], p1[1])
- s.drawLineTo(p2[0], p2[1])
-
+ s.movePenTo(p1[0], p1[1])
+ s.drawLineTo(p2[0], p2[1])
s.end()
sprite.add(s)
p1 = self._calcCanvasCoord(e.v1)
p2 = self._calcCanvasCoord(e.v2)
- # FIXME: this is just a workaround, remove that after the
- # implementation of propoer Viewport clipping
- if abs(p1[0]) < 3000 and abs(p2[0]) < 3000 and abs(p1[1]) < 3000 and abs(p1[2]) < 3000:
- self.canvas.line(p1[0], p1[1], p2[0], p2[1])
+ self.canvas.line(p1[0], p1[1], p2[0], p2[1])
# Render from the currently active camera
#self.cameraObj = self._SCENE.getCurrentCamera()
- # 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)
+ self.lights = []
##
# global processing of the scene
+ self._filterHiddenObjects(workScene)
+
+ self._buildLightSetup(workScene)
+
self._doSceneClipping(workScene)
self._doConvertGeometricObjsToMesh(workScene)
# Per object activities
Objects = workScene.getChildren()
+
print "Total Objects: %d" % len(Objects)
for i,obj in enumerate(Objects):
print "\n\n-------"
# Scene methods
+ def _filterHiddenObjects(self, scene):
+ """Discard object that are on hidden layers in the scene.
+ """
+
+ Objects = scene.getChildren()
+
+ 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.unlink(o)
+
+ scene.update()
+
+
+
+ def _buildLightSetup(self, scene):
+ # Get the list of lighting sources
+ obj_lst = 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)
+
+
def _doSceneClipping(self, scene):
"""Clip whole objects against the View Frustum.
fovy = fovy * 360.0/pi
Objects = scene.getChildren()
+
for o in Objects:
if o.getType() != 'Mesh': continue;
- # TODO: use the object bounding box (that is already in WorldSpace)
- # bb = o.getBoundBox() and then: for point in bb: ...
-
+ """
obj_vect = Vector(cam_pos) - self._getObjPosition(o)
d = 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)
+ """
+
+ # 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)
+
+ d = p_vect * view_vect
+ theta = AngleBetweenVecs(p_vect, view_vect)
+
+ # Is this point outside the view frustum?
+ if (d < near) or (d > far) or (theta > fovy):
+ points_outside += 1
+
+ # If the bb is all outside the view frustum we clip the whole
+ # object away
+ if points_outside == len(bb):
+ scene.unlink(o)
+
+
def _doConvertGeometricObjsToMesh(self, scene):
"""Convert all "geometric" objects to mesh ones.
#geometricObjTypes = ['Mesh', 'Surf', 'Curve']
Objects = scene.getChildren()
+
objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
for obj in objList:
old_obj = obj
c = self._getObjPosition(self.cameraObj)
- by_center_pos = (lambda o1, o2:
+ by_obj_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)
)
- # 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
+ # Implement sorting by bounding box, the object with the bb
+ # nearest to the camera should be drawn as last.
+ by_nearest_bbox_point = (lambda o1, o2:
+ (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
+ cmp( min( [(Vector(p) - Vector(c)).length for p in o1.getBoundBox()] ),
+ min( [(Vector(p) - Vector(c)).length for p in o2.getBoundBox()] )
+ )
+ )
+
Objects = scene.getChildren()
- Objects.sort(by_center_pos)
+
+ #Objects.sort(by_obj_center_pos)
+ Objects.sort(by_nearest_bbox_point)
# update the scene
for o in Objects:
for clipface in cvv:
clippedfaces = []
+
for f in facelist:
newfaces = HSR.splitOn(clipface, f, return_positive_faces=False)
if not newfaces:
- # Check if the face is inside the view rectangle
+ # Check if the face is all outside the view frustum
# TODO: Do this test before, it is more efficient
points_outside = 0
for v in f:
- if abs(v[0]) > 1-EPS or abs(v[1]) > 1-EPS:
+ if abs(v[0]) > 1-EPS or abs(v[1]) > 1-EPS or abs(v[2]) > 1-EPS:
points_outside += 1
if points_outside != len(f):
nf.col = [f.col[0]] * len(nf.v)
clippedfaces.append(nf)
-
facelist = clippedfaces[:]
+
nmesh.faces = facelist
nmesh.update()