#!/usr/bin/env python import math import os import sys from iv import * from qt import * class Info(Exception): def __init__(self, info): Exception.__init__(self, info) # __init__() # class Info class Viewer(SoQtExaminerViewer): """Demonstrates how to: (1) reimplement processSoEvent() to customize event handling. (2) use the offscreen renderer to save a scene to a bitmap or .(e)ps file. (3) use the PostScript vectorize action to save a scene to an .(e)ps file. """ def __init__(self, *args): SoQtExaminerViewer.__init__(self, *args) # Disable the viewer decorations. self.setDecoration(False) # Set an orthographic camera. self.setCameraType(SoOrthographicCamera.getClassTypeId()) # Enable the axis cross in the lower right corner. self.setFeedbackVisibility(True) # Set a blue background. self.setBackgroundColor(SbColor(0.0, 0.0, 1.0)) # The next statement starts the scene manager of the widget self.setSceneGraph(None) # Change the table to change the mapping from buttons to actions. self.actions = { # left button SoMouseButtonEvent.BUTTON1: self.spin, # right button SoMouseButtonEvent.BUTTON2: self.zoom, # middle button SoMouseButtonEvent.BUTTON3: self.pan, } # The actions use the current and last mouse position. self.new2f = None self.old2f = None # The action to take on a SoLocation2Event. self.action = None # The spin action uses a spin projector. self.spinProjector = SbSphereSheetProjector( SbSphere(SbVec3f(0, 0, 0), 0.8), True) viewVolume = SbViewVolume() viewVolume.ortho(-1, 1, -1, 1, -1, 1) self.spinProjector.setViewVolume(viewVolume) # An alias for self.getCamera() self.camera = None # Coin documentation advises to reutilize SoOffscreenRender instances. self.offscreenRenderer = None # __init__() def saveAsVector(self, filename): """Save the scene in a vector format. """ # FIXME: this is not working well. Who is to blame, SIM or me? # Use saveAsBitmap() for good results. try: # Get the scene including camera and light from the scene manager; # see the documentation for SoOffscreenRenderer.render(). scene = self.getSceneManager().getSceneGraph() action = SoVectorizePSAction() action.setBackgroundColor(True, SbColor(0.0, 0.0, 1.0)) output = action.getOutput() if not output.openFile(filename): raise Info('Unable to open %s for writing' % filename) viewportSize = self.getViewportRegion().getViewportSizePixels() viewportRatio = float(viewportSize[0]) / float(viewportSize[1]) if viewportRatio > 1.0: action.setOrientation(SoVectorizeAction.LANDSCAPE) viewportRatio = 1.0 / viewportRatio else: action.setOrientation(SoVectorizeAction.PORTRAIT) pageSize = action.getPageSize() pageRatio = float(pageSize[0]) / float(pageSize[1]) if pageRatio < viewportRatio: xPageSize = float(pageSize[0]) yPageSize = xPageSize / viewportRatio else: yPageSize = float(pageSize[1]) xPageSize = yPageSize * viewportRatio action.beginPage(SbVec2f(), SbVec2f((xPageSize, yPageSize))) action.calibrate(self.getViewportRegion()) action.apply(scene) action.endPage() output.closeFile() except Info, info: QMessageBox.information( self.getParentWidget(), 'Saving to a vector format:', info[0], QMessageBox.Ok, ) # saveAsVector() def getBitmapExtensions(self): """Returns a sorted list of all offscreen renderer file types. """ if not self.offscreenRenderer: self.offscreenRenderer = SoOffscreenRenderer(SbViewportRegion()) extensions = [] for i in range(self.offscreenRenderer.getNumWriteFiletypes()): for extension in self.offscreenRenderer.getWriteFiletypeInfo(i)[0]: if extension not in extensions: extensions.append(extension) extensions.sort() return extensions # getBitmapExtensions() def saveAsBitmap(self, root, ext): """Save the scene in a bitmap format. """ try: # Get the scene including camera and light from the scene manager; # see the documentation for SoOffscreenRenderer.render(). scene = self.getSceneManager().getSceneGraph() viewportRegion = self.getViewportRegion() if not self.offscreenRenderer: self.offscreenRenderer = SoOffscreenRenderer(viewportRegion) else: self.offscreenRenderer.setViewportRegion(viewportRegion) self.offscreenRenderer.setBackgroundColor( self.getBackgroundColor()) if not self.offscreenRenderer.render(scene): raise Info('Failed to render the scenegraph') if not self.offscreenRenderer.writeToFile( '%s.%s' % (root, ext), ext ): raise Info('Failed to write "%s.%s"' % (root, ext)) except Info, info: QMessageBox.information( self.getParentWidget(), 'Saving to a bitmap format:', info[0], QMessageBox.Ok, ) # saveAsBitmap() def pan(self, camera): """Pan the camera. """ viewVolume = camera.getViewVolume(self.getGLAspectRatio()) focalDistance = camera.focalDistance.getValue() panPlane = viewVolume.getPlane(focalDistance) line = SbLine() viewVolume.projectPointToLine(self.new2f, line) new3f = SbVec3f() panPlane.intersect(line, new3f) viewVolume.projectPointToLine(self.old2f, line) old3f = SbVec3f() panPlane.intersect(line, old3f) camera.position.setValue(camera.position.getValue() + old3f - new3f) # pan() def spin(self, camera): """Spin the camera. """ self.spinProjector.project(self.old2f) rotation = SbRotation() self.spinProjector.projectAndGetRotation(self.new2f, rotation) rotation.invert() # Find global coordinates of focal point. direction = SbVec3f() camera.orientation.getValue().multVec(SbVec3f(0, 0, -1), direction) focalpoint = camera.position.getValue() \ + direction * camera.focalDistance.getValue() # Set new orientation value by accumulating the new rotation. camera.orientation.setValue(rotation * camera.orientation.getValue()) # Reposition camera to point to the same old focal point. camera.orientation.getValue().multVec(SbVec3f(0, 0, -1), direction) camera.position.setValue( focalpoint - direction * camera.focalDistance.getValue()) # spin() def zoom(self, camera): """Zoom the camera. """ assert(isinstance(camera, SoOrthographicCamera)) factor = math.exp(20.0*(self.new2f[1] - self.old2f[1])) camera.height.setValue(factor*camera.height.getValue()) oldFocalDistance = camera.focalDistance.getValue() newFocalDistance = factor * oldFocalDistance direction = SbVec3f() camera.orientation.getValue().multVec(SbVec3f(0, 0, -1), direction) oldPosition = camera.position.getValue() newPosition = oldPosition - direction * ( newFocalDistance - oldFocalDistance) if (newPosition.length() < 1e19): camera.position.setValue(newPosition) camera.focalDistance.setValue(newFocalDistance) # zoom() def processSoEvent(self, event): """Intercept events to pan, spin, and zoom for an orthographic camera. """ camera = self.getCamera() if not isinstance(camera, SoOrthographicCamera): return SoQtExaminerViewer.processSoEvent(self, event) if SoMouseButtonEvent.isButtonPressEvent( event, SoMouseButtonEvent.ANY ): self.new2f = event.getNormalizedPosition(self.getViewportRegion()) self.action = self.actions.get(event.getButton(), None) return True if SoMouseButtonEvent.isButtonReleaseEvent( event, SoMouseButtonEvent.ANY ): self.new2f = None self.old2f = None self.action = None return True if self.action and isinstance(event, SoLocation2Event): self.old2f = self.new2f self.new2f = event.getNormalizedPosition(self.getViewportRegion()) self.action(camera) return True return SoQtExaminerViewer.processSoEvent(self, event) # processSoEvent() # class Viewer class MainWindow(QMainWindow): def __init__(self): QMainWindow.__init__( self, None, 'IVuPy Main Window Example', Qt.WDestructiveClose) # Since SoQt widgets are not derived from QWidget, # it is useful to give them a shell QWidget as parent: shell = QWidget(self) self.viewer = Viewer(shell) # and now self.setCentralWidget(shell) # instead of self.setCentralWidget(self.viewer) which raises an error. fileMenu = QPopupMenu(self) self.menuBar().insertItem('&File', fileMenu) fileMenu.insertItem( '&Open...', self.openFile, Qt.CTRL + Qt.Key_O) fileMenu.insertItem( 'Save as &vector...', self.saveAsVector) fileMenu.insertItem( 'Save as &bitmap...', self.saveAsBitmap) fileMenu.insertItem( '&Quit', qApp, SLOT('closeAllWindows()'), Qt.CTRL + Qt.Key_Q) self.statusBar().message('Ready', 5000) self.resize(600, 400) # __init__() def setSceneGraph(self, scene): self.viewer.setSceneGraph(scene) # setSceneGraph() def openFile(self): """Open an Inventor file to display in the viewer. """ name = QFileDialog.getOpenFileName( os.path.join(os.pardir, 'data'), 'Inventor files (*.iv)', self) if not name: self.statusBar().message('Reading aborted', 5000); return source = SoInput() # str(name), since a QString is not accepted by SoInput.openFile() if not source.openFile(str(name)): self.statusBar().message('Failed to open %s' % name, 5000) scene = SoDB.readAll(source) self.viewer.setSceneGraph(scene) # openFile() def saveAsVector(self): """Saves the scene in the viewer as a ps file. """ pattern = 'Files (*.eps *.ps)' name = QFileDialog.getSaveFileName('.', pattern, self) self.viewer.saveAsVector(str(name)) # saveAsVector() def saveAsBitmap(self): """Saves the scene in the viewer as a bitmap or (e)ps file. """ extensions = self.viewer.getBitmapExtensions() pattern = 'Files (%s)' % ' '.join(['*.%s' % x for x in extensions]) name = QFileDialog.getSaveFileName('.', pattern, self) root, ext = os.path.splitext(str(name)) if ext.startswith('.'): ext = ext[1:] if ext not in extensions: self.statusBar().message( '"%s" bitmap format is not supported' % ext) return self.viewer.saveAsBitmap(root, ext) # saveAsBitmap() # class MainWindow def main(): # Initialize Qt and SoQt app = QApplication(sys.argv) SoQt.init(None) demo = MainWindow() demo.show() app.connect(app, SIGNAL('lastWindowClosed()'), app, SLOT('quit()')) SoQt.mainLoop() # main() if __name__ == '__main__': main() # Local Variables: *** # mode: python *** # End: ***