コード例 #1
0
ファイル: viewer_widget.py プロジェクト: rheniser/bb_alembic
    def __init__(self, parent=None, fps=24, state=None):
        """
        :param parent: parent Qt object
        :param fps: frames per second (default 24)
        :param state: GLState object (for shared states)
        """
        self.camera = None
        format = QtOpenGL.QGLFormat()
        format.setDirectRendering(True)
        format.setSampleBuffers(True)
        self.state = state or GLState()
        self.state.signal_state_change.connect(self.handle_state_change)
        super(GLWidget, self).__init__(format, parent)
        self.setAutoBufferSwap(True)
        self.setMouseTracking(True)
        self.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.setCursor(QtCore.Qt.OpenHandCursor)

        # save the parent object
        self._main = parent

        # frames per second value
        self.__time = 0
        self.__frame = 0
        self.__fps = fps
        self.__playing = False

        # various matrices and vectors
        self.__bounds_default = imath.Box3d((-5, -5, -5), (5, 5, 5))
        self.__bounds = None
        self.__radius = 5.0
        self.__last_pok = False
        self.__last_p2d = QtCore.QPoint()
        self.__last_p3d = [1.0, 0.0, 0.0]
        self.__rotating = False

        # for animated scenes
        self.timer = QtCore.QTimer(self)

        # default camera
        self.camera = GLCamera(self)
        self.state.add_camera(self.camera)

        self.frame()
コード例 #2
0
ファイル: viewer_widget.py プロジェクト: rheniser/bb_alembic
 def handle_camera_action(self, action):
     """
     New camera menu handler.
     """
     action_name = str(action.text().toAscii())
     if action_name == "New":
         text, ok = QtGui.QInputDialog.getText(self, "New Camera",
                                               "Camera Name:",
                                               QtGui.QLineEdit.Normal)
         if ok and not text.isEmpty():
             name = str(text.toAscii())
             camera = GLCamera(self, name)
             self.add_camera(camera)
             self.set_camera(name)
コード例 #3
0
ファイル: viewer_widget.py プロジェクト: hallLong/bb_alembic
    def __init__(self, parent=None, fps=24, state=None):
        """
        :param parent: parent Qt object
        :param fps: frames per second (default 24)
        :param state: GLState object (for shared states)
        """
        self.camera = None
        format = QtOpenGL.QGLFormat()
        format.setDirectRendering(True)
        format.setSampleBuffers(True)
        self.state = state or GLState()
        self.state.signal_state_change.connect(self.handle_state_change)
        super(GLWidget, self).__init__(format, parent)
        self.setAutoBufferSwap(True)
        self.setMouseTracking(True)
        self.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.setCursor(QtCore.Qt.OpenHandCursor)
      
        # save the parent object
        self._main = parent

        # frames per second value
        self.__time = 0
        self.__frame = 0
        self.__fps = fps
        self.__playing = False

        # various matrices and vectors
        self.__bounds_default = imath.Box3d((-5,-5,-5), (5,5,5))
        self.__bounds = None
        self.__radius = 5.0
        self.__last_pok = False
        self.__last_p2d = QtCore.QPoint()
        self.__last_p3d = [1.0, 0.0, 0.0]
        self.__rotating = False

        # for animated scenes
        self.timer = QtCore.QTimer(self)

        # default camera
        self.camera = GLCamera(self)
        self.state.add_camera(self.camera)

        self.frame()
コード例 #4
0
ファイル: viewer_widget.py プロジェクト: hallLong/bb_alembic
class GLWidget(QtOpenGL.QGLWidget):
    """
    AbcView OpenGL Widget.

    Basic usage ::

        >>> viewer = GLWidget()
        >>> viewer.add_file("file.abc")
    """
    signal_scene_opened = QtCore.pyqtSignal(GLScene)
    signal_scene_removed = QtCore.pyqtSignal(GLScene)
    signal_scene_error = QtCore.pyqtSignal(str)
    signal_scene_drawn = QtCore.pyqtSignal()
    signal_play_fwd = QtCore.pyqtSignal()
    signal_play_stop = QtCore.pyqtSignal()
    signal_current_time = QtCore.pyqtSignal(float)
    signal_current_frame = QtCore.pyqtSignal(int)
    signal_set_camera = QtCore.pyqtSignal(GLCamera)
    signal_new_camera = QtCore.pyqtSignal(GLCamera)
    signal_camera_updated = QtCore.pyqtSignal(GLCamera)

    def __init__(self, parent=None, fps=24, state=None):
        """
        :param parent: parent Qt object
        :param fps: frames per second (default 24)
        :param state: GLState object (for shared states)
        """
        self.camera = None
        format = QtOpenGL.QGLFormat()
        format.setDirectRendering(True)
        format.setSampleBuffers(True)
        self.state = state or GLState()
        self.state.signal_state_change.connect(self.handle_state_change)
        super(GLWidget, self).__init__(format, parent)
        self.setAutoBufferSwap(True)
        self.setMouseTracking(True)
        self.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.setCursor(QtCore.Qt.OpenHandCursor)
      
        # save the parent object
        self._main = parent

        # frames per second value
        self.__time = 0
        self.__frame = 0
        self.__fps = fps
        self.__playing = False

        # various matrices and vectors
        self.__bounds_default = imath.Box3d((-5,-5,-5), (5,5,5))
        self.__bounds = None
        self.__radius = 5.0
        self.__last_pok = False
        self.__last_p2d = QtCore.QPoint()
        self.__last_p3d = [1.0, 0.0, 0.0]
        self.__rotating = False

        # for animated scenes
        self.timer = QtCore.QTimer(self)

        # default camera
        self.camera = GLCamera(self)
        self.state.add_camera(self.camera)

        self.frame()

    def clear(self):
        self.frame()
        self.updateGL()

    def add_file(self, filepath):
        self.add_scene(GLScene(filepath))

    def add_scene(self, scene):
        """
        Adds a scene to the viewer session. 

        :param scene: GLScene object
        """
        self.state.add_scene(scene)
        self.signal_scene_opened.emit(scene)
        self.updateGL()

    def remove_scene(self, scene):
        """
        Removes a given GLScene object from the master scene.

        :param scene: GLScene to remove.
        """
        self.state.remove_scene(scene)
        self.signal_scene_removed.emit(scene)
        self.updateGL()

    def add_camera(self, camera):
        """
        :param camera: GLCamera object
        """
        log.debug("GLWidget.add_camera: %s" % camera)
        self.state.add_camera(camera)
        self.signal_new_camera.emit(camera)

    def remove_camera(self, camera):
        """
        :param camera: GLCamera object to remove
        """
        self.state.remove_camera(camera)

    @update_camera
    def set_camera(self, camera="interactive"):
        """
        Sets the scene camera from a given camera name string

        :param camera: Name of camera or GLCamera object
        """
        if type(camera) in [str, unicode]:
            if "/" in camera:
                camera = os.path.split("/")[-1]
            if camera not in [cam.name for cam in self.state.cameras]:
                log.warn("camera not found: %s" % camera)
                return
            self.camera = self.state.get_camera_by_name(camera)
        else:
            self.camera = camera
        self.resizeGL(self.width(), self.height())
        self.signal_set_camera.emit(self.camera)

    def _get_time(self):
        return self.__time

    def _set_time(self, new_time):
        self.__time = new_time
        self.__frame = new_time * self.fps
        for scene in self.state.scenes:
            if scene.visible:
                scene.set_time(new_time)
        if self.camera and self.camera.auto_frame:
            self.frame()
        self.signal_current_time.emit(new_time) 
        self.signal_current_frame.emit(int(round(new_time * self.fps)))
        self.updateGL()

    current_time = property(_get_time, _set_time, doc="set/get current time")

    def _get_frame(self):
        return self.__frame

    def _set_frame(self, frame):
        if frame > self.frame_range()[1]:
            frame = self.frame_range()[0]
        elif frame < self.frame_range()[0]:
            frame = self.frame_range()[1]
        frame = frame / float(self.fps)
        self.current_time = frame

    current_frame = property(_get_frame, _set_frame, doc="set/get current frame")

    def is_playing(self):
        return self.__playing

    def play(self):
        """
        plays loaded scenes by activating timer and setting callback
        """
        self.timer.setInterval(1.0 / (float(self.fps)))
        self.connect(self.timer, QtCore.SIGNAL("timeout ()"), self._play_fwd_cb)
        self.timer.start()
        self.signal_play_fwd.emit()

    def _play_fwd_cb(self):
        """
        play callback, sets current time for all scenes
        """
        self.__playing = True
        min_time, max_time = self.time_range()
        self.current_time += 1.0 / float(self.fps)
        if self.current_time > max_time:
            self.current_time = min_time

    def stop(self):
        """
        stops scene playback
        """
        self.__playing = False
        self.disconnect(self.timer,  QtCore.SIGNAL("timeout ()"), self._play_fwd_cb)
        self.connect(self.timer, QtCore.SIGNAL("timeout ()"), self._play_stop_cb)
        self.updateGL()

    def _play_stop_cb(self):
        self.signal_play_stop.emit()

    def _get_fps(self):
        return self.__fps

    def _set_fps(self, fps):
        self.__fps = fps

    fps = property(_get_fps, _set_fps, doc="frames per second value")

    def aspect_ratio(self):
        """
        Returns current aspect ration of the viewer.
        """
        return self.width() / float(self.height())

    def time_range(self):
        """
        Returns total time range in seconds.
        """
        min = None
        max = None
        if self.state.scenes:
            for scene in self.state.scenes:
                if min is None or scene.min_time() < min:
                    min = scene.min_time()
                if max is None or scene.max_time() > max:
                    max = scene.max_time()
        else:
            min, max = 0, 0
        return (min, max)

    def frame_range(self):
        """
        Returns total frame range.
        """
        (min, max) = self.time_range()
        return (min * self.fps, max * self.fps)
    
    def _get_bounds(self):
        #TODO: ugly code needs refactor
        if self.state.scenes:
            bounds = None
            for scene in self.state.scenes:
                if not scene.loaded:
                    continue
                if bounds is None or scene.bounds().max() > bounds.max():
                    bounds = scene.bounds()
                    min = bounds.min()
                    max = bounds.max()
                    if scene.properties.get("translate"):
                        max = max * imath.V3d(*scene.translate)
                        min = min * imath.V3d(*scene.translate)
                    if scene.properties.get("scale"):
                        max = max * imath.V3d(*scene.scale)
                        min = min * imath.V3d(*scene.scale)
                    bounds = imath.Box3d(min, max)
            
            if bounds is None:
                return self.__bounds_default
            self.__bounds = bounds
            return self.__bounds
        else:
            return self.__bounds_default

    def _set_bounds(self, bounds):
        self.__bounds = bounds

    bounds = property(_get_bounds, _set_bounds, doc="scene bounding box")

    @update_camera
    def frame(self, bounds=None):
        """
        Frames the viewer's active camera on the bounds of the currently
        loaded and visible scenes.

        :param bounds: imath.Box3d bounds object.
        """
        if bounds is None:
            bounds = self.bounds
        self.camera.frame(bounds)

    def split(self, orientation=QtCore.Qt.Vertical,
                    wipe=False):
        """
        Splist the viewer into two separate widgets according
        to the orientation param.
        """
        if not self.parent():
            return

        item = self.parent().layout().itemAt(0)

        splitter = GLSplitter(orientation, wipe)
        self.parent().layout().addWidget(splitter)

        group1 = QtGui.QGroupBox()
        group1.setLayout(QtGui.QVBoxLayout())
        group1.layout().setSpacing(0)
        group1.layout().setMargin(0)
        group1.layout().addWidget(item.widget())

        group2 = QtGui.QGroupBox()
        group2.setLayout(QtGui.QVBoxLayout())
        group2.layout().setSpacing(0)
        group2.layout().setMargin(0)
        new_viewer = GLWidget(self._main, state=self.state)
        group2.layout().addWidget(new_viewer)

        splitter.addWidget(group1)
        splitter.addWidget(group2)

    def split_vert(self):
        """
        Splits the viewer vertically.
        """
        self.split(QtCore.Qt.Horizontal)

    def split_horz(self):
        """
        Splits the viewer horizontally.
        """
        self.split(QtCore.Qt.Vertical)

    #def wipe_horz(self):
    #    self.split(QtCore.Qt.Horizontal, wipe=True)

    #TODO: support viewer pop-outs
    def unsplit(self):
        """
        Unsplits and deletes current viewer
        """
        if not self.parent():
            return

        splitter = self.parent().parent()

        # should be exactly two groups per splitter
        index = splitter.indexOf(self.parent())
        index_other = 1 * (1 - index)
        
        group = splitter.widget(index)
        group_other = splitter.widget(index_other)

        # disconnect signals
        #self.state.signal_state_change.disconnect()
  
        splitter.parent().layout().addWidget(group_other)
        splitter.parent().layout().removeWidget(splitter)

        # cleanup widgets
        del splitter
        del group
        del self

    def _paint_normals(self):
        glColor3f(1, 1, 1)
        def _draw(obj):
            md = obj.getMetaData()
            if alembic.AbcGeom.IPolyMesh.matches(md) or alembic.AbcGeom.ISubD.matches(md):
                meshObj = alembic.AbcGeom.IPolyMesh(obj.getParent(), obj.getName())
                mesh = meshObj.getSchema()
                
                xf = get_final_matrix(meshObj)
                iss = alembic.Abc.ISampleSelector(0)

                facesProp = mesh.getFaceIndicesProperty()
                if not facesProp.valid():
                    return
                pointsProp = mesh.getPositionsProperty()
                if not pointsProp.valid():
                    return
                normalsProp = mesh.getNormalsParam().getValueProperty()
                if not normalsProp.valid():
                    return
                boundsProp = mesh.getSelfBoundsProperty()
                if not boundsProp.valid():
                    return
                
                faces = facesProp.getValue(iss)
                points = pointsProp.getValue(iss)
                normals = normalsProp.getValue(iss)
                bounds = boundsProp.getValue(iss)

                for i, fi in enumerate(faces):
                    p = points[fi] * xf
                    n = normals[i]
                    v = p + n
                    glBegin(GL_LINES)
                    glColor3f(0, 1, 0)
                    glVertex3f(p[0], p[1], p[2])
                    glVertex3f(v[0], v[1], v[2])
                    glEnd()
        
            for child in obj.children:
                try:
                    _draw(child)
                except Exception, e:
                    log.warn("unhandled exception: %s" % e)

        for scene in self.state.scenes:
            _draw(scene.top())
コード例 #5
0
ファイル: viewer_widget.py プロジェクト: rheniser/bb_alembic
class GLWidget(QtOpenGL.QGLWidget):
    """
    AbcView OpenGL Widget.

    Basic usage ::

        >>> viewer = GLWidget()
        >>> viewer.add_file("file.abc")
    """
    signal_scene_opened = QtCore.pyqtSignal(GLScene)
    signal_scene_removed = QtCore.pyqtSignal(GLScene)
    signal_scene_error = QtCore.pyqtSignal(str)
    signal_scene_drawn = QtCore.pyqtSignal()
    signal_play_fwd = QtCore.pyqtSignal()
    signal_play_stop = QtCore.pyqtSignal()
    signal_current_time = QtCore.pyqtSignal(float)
    signal_current_frame = QtCore.pyqtSignal(int)
    signal_set_camera = QtCore.pyqtSignal(GLCamera)
    signal_new_camera = QtCore.pyqtSignal(GLCamera)
    signal_camera_updated = QtCore.pyqtSignal(GLCamera)

    def __init__(self, parent=None, fps=24, state=None):
        """
        :param parent: parent Qt object
        :param fps: frames per second (default 24)
        :param state: GLState object (for shared states)
        """
        self.camera = None
        format = QtOpenGL.QGLFormat()
        format.setDirectRendering(True)
        format.setSampleBuffers(True)
        self.state = state or GLState()
        self.state.signal_state_change.connect(self.handle_state_change)
        super(GLWidget, self).__init__(format, parent)
        self.setAutoBufferSwap(True)
        self.setMouseTracking(True)
        self.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.setCursor(QtCore.Qt.OpenHandCursor)

        # save the parent object
        self._main = parent

        # frames per second value
        self.__time = 0
        self.__frame = 0
        self.__fps = fps
        self.__playing = False

        # various matrices and vectors
        self.__bounds_default = imath.Box3d((-5, -5, -5), (5, 5, 5))
        self.__bounds = None
        self.__radius = 5.0
        self.__last_pok = False
        self.__last_p2d = QtCore.QPoint()
        self.__last_p3d = [1.0, 0.0, 0.0]
        self.__rotating = False

        # for animated scenes
        self.timer = QtCore.QTimer(self)

        # default camera
        self.camera = GLCamera(self)
        self.state.add_camera(self.camera)

        self.frame()

    def clear(self):
        self.frame()
        self.updateGL()

    def add_file(self, filepath):
        self.add_scene(GLScene(filepath))

    def add_scene(self, scene):
        """
        Adds a scene to the viewer session. 

        :param scene: GLScene object
        """
        self.state.add_scene(scene)
        self.signal_scene_opened.emit(scene)
        self.updateGL()

    def remove_scene(self, scene):
        """
        Removes a given GLScene object from the master scene.

        :param scene: GLScene to remove.
        """
        self.state.remove_scene(scene)
        self.signal_scene_removed.emit(scene)
        self.updateGL()

    def add_camera(self, camera):
        """
        :param camera: GLCamera object
        """
        log.debug("GLWidget.add_camera: %s" % camera)
        self.state.add_camera(camera)
        self.signal_new_camera.emit(camera)

    def remove_camera(self, camera):
        """
        :param camera: GLCamera object to remove
        """
        self.state.remove_camera(camera)

    @update_camera
    def set_camera(self, camera="interactive"):
        """
        Sets the scene camera from a given camera name string

        :param camera: Name of camera or GLCamera object
        """
        if type(camera) in [str, unicode]:
            if "/" in camera:
                camera = os.path.split("/")[-1]
            if camera not in [cam.name for cam in self.state.cameras]:
                log.warn("camera not found: %s" % camera)
                return
            self.camera = self.state.get_camera_by_name(camera)
        else:
            self.camera = camera
        self.resizeGL(self.width(), self.height())
        self.signal_set_camera.emit(self.camera)

    def _get_time(self):
        return self.__time

    def _set_time(self, new_time):
        self.__time = new_time
        self.__frame = new_time * self.fps
        for scene in self.state.scenes:
            if scene.visible:
                scene.set_time(new_time)
        if self.camera and self.camera.auto_frame:
            self.frame()
        self.signal_current_time.emit(new_time)
        self.signal_current_frame.emit(int(round(new_time * self.fps)))
        self.updateGL()

    current_time = property(_get_time, _set_time, doc="set/get current time")

    def _get_frame(self):
        return self.__frame

    def _set_frame(self, frame):
        if frame > self.frame_range()[1]:
            frame = self.frame_range()[0]
        elif frame < self.frame_range()[0]:
            frame = self.frame_range()[1]
        frame = frame / float(self.fps)
        self.current_time = frame

    current_frame = property(_get_frame,
                             _set_frame,
                             doc="set/get current frame")

    def is_playing(self):
        return self.__playing

    def play(self):
        """
        plays loaded scenes by activating timer and setting callback
        """
        self.timer.setInterval(1.0 / (float(self.fps)))
        self.connect(self.timer, QtCore.SIGNAL("timeout ()"),
                     self._play_fwd_cb)
        self.timer.start()
        self.signal_play_fwd.emit()

    def _play_fwd_cb(self):
        """
        play callback, sets current time for all scenes
        """
        self.__playing = True
        min_time, max_time = self.time_range()
        self.current_time += 1.0 / float(self.fps)
        if self.current_time > max_time:
            self.current_time = min_time

    def stop(self):
        """
        stops scene playback
        """
        self.__playing = False
        self.disconnect(self.timer, QtCore.SIGNAL("timeout ()"),
                        self._play_fwd_cb)
        self.connect(self.timer, QtCore.SIGNAL("timeout ()"),
                     self._play_stop_cb)
        self.updateGL()

    def _play_stop_cb(self):
        self.signal_play_stop.emit()

    def _get_fps(self):
        return self.__fps

    def _set_fps(self, fps):
        self.__fps = fps

    fps = property(_get_fps, _set_fps, doc="frames per second value")

    def aspect_ratio(self):
        """
        Returns current aspect ration of the viewer.
        """
        return self.width() / float(self.height())

    def time_range(self):
        """
        Returns total time range in seconds.
        """
        min = None
        max = None
        if self.state.scenes:
            for scene in self.state.scenes:
                if min is None or scene.min_time() < min:
                    min = scene.min_time()
                if max is None or scene.max_time() > max:
                    max = scene.max_time()
        else:
            min, max = 0, 0
        return (min, max)

    def frame_range(self):
        """
        Returns total frame range.
        """
        (min, max) = self.time_range()
        return (min * self.fps, max * self.fps)

    def _get_bounds(self):
        #TODO: ugly code needs refactor
        if self.state.scenes:
            bounds = None
            for scene in self.state.scenes:
                if not scene.loaded:
                    continue
                if bounds is None or scene.bounds().max() > bounds.max():
                    bounds = scene.bounds()
                    min = bounds.min()
                    max = bounds.max()
                    if scene.properties.get("translate"):
                        max = max * imath.V3d(*scene.translate)
                        min = min * imath.V3d(*scene.translate)
                    if scene.properties.get("scale"):
                        max = max * imath.V3d(*scene.scale)
                        min = min * imath.V3d(*scene.scale)
                    bounds = imath.Box3d(min, max)

            if bounds is None:
                return self.__bounds_default
            self.__bounds = bounds
            return self.__bounds
        else:
            return self.__bounds_default

    def _set_bounds(self, bounds):
        self.__bounds = bounds

    bounds = property(_get_bounds, _set_bounds, doc="scene bounding box")

    @update_camera
    def frame(self, bounds=None):
        """
        Frames the viewer's active camera on the bounds of the currently
        loaded and visible scenes.

        :param bounds: imath.Box3d bounds object.
        """
        if bounds is None:
            bounds = self.bounds
        self.camera.frame(bounds)

    def split(self, orientation=QtCore.Qt.Vertical, wipe=False):
        """
        Splist the viewer into two separate widgets according
        to the orientation param.
        """
        if not self.parent():
            return

        item = self.parent().layout().itemAt(0)

        splitter = GLSplitter(orientation, wipe)
        self.parent().layout().addWidget(splitter)

        group1 = QtGui.QGroupBox()
        group1.setLayout(QtGui.QVBoxLayout())
        group1.layout().setSpacing(0)
        group1.layout().setMargin(0)
        group1.layout().addWidget(item.widget())

        group2 = QtGui.QGroupBox()
        group2.setLayout(QtGui.QVBoxLayout())
        group2.layout().setSpacing(0)
        group2.layout().setMargin(0)
        new_viewer = GLWidget(self._main, state=self.state)
        group2.layout().addWidget(new_viewer)

        splitter.addWidget(group1)
        splitter.addWidget(group2)

    def split_vert(self):
        """
        Splits the viewer vertically.
        """
        self.split(QtCore.Qt.Horizontal)

    def split_horz(self):
        """
        Splits the viewer horizontally.
        """
        self.split(QtCore.Qt.Vertical)

    #def wipe_horz(self):
    #    self.split(QtCore.Qt.Horizontal, wipe=True)

    #TODO: support viewer pop-outs
    def unsplit(self):
        """
        Unsplits and deletes current viewer
        """
        if not self.parent():
            return

        splitter = self.parent().parent()

        # should be exactly two groups per splitter
        index = splitter.indexOf(self.parent())
        index_other = 1 * (1 - index)

        group = splitter.widget(index)
        group_other = splitter.widget(index_other)

        # disconnect signals
        #self.state.signal_state_change.disconnect()

        splitter.parent().layout().addWidget(group_other)
        splitter.parent().layout().removeWidget(splitter)

        # cleanup widgets
        del splitter
        del group
        del self

    def _paint_normals(self):
        glColor3f(1, 1, 1)

        def _draw(obj):
            md = obj.getMetaData()
            if alembic.AbcGeom.IPolyMesh.matches(
                    md) or alembic.AbcGeom.ISubD.matches(md):
                meshObj = alembic.AbcGeom.IPolyMesh(obj.getParent(),
                                                    obj.getName())
                mesh = meshObj.getSchema()

                xf = get_final_matrix(meshObj)
                iss = alembic.Abc.ISampleSelector(0)

                facesProp = mesh.getFaceIndicesProperty()
                if not facesProp.valid():
                    return
                pointsProp = mesh.getPositionsProperty()
                if not pointsProp.valid():
                    return
                normalsProp = mesh.getNormalsParam().getValueProperty()
                if not normalsProp.valid():
                    return
                boundsProp = mesh.getSelfBoundsProperty()
                if not boundsProp.valid():
                    return

                faces = facesProp.getValue(iss)
                points = pointsProp.getValue(iss)
                normals = normalsProp.getValue(iss)
                bounds = boundsProp.getValue(iss)

                for i, fi in enumerate(faces):
                    p = points[fi] * xf
                    n = normals[i]
                    v = p + n
                    glBegin(GL_LINES)
                    glColor3f(0, 1, 0)
                    glVertex3f(p[0], p[1], p[2])
                    glVertex3f(v[0], v[1], v[2])
                    glEnd()

            for child in obj.children:
                try:
                    _draw(child)
                except Exception, e:
                    log.warn("unhandled exception: %s" % e)

        for scene in self.state.scenes:
            _draw(scene.top())