예제 #1
0
파일: main.py 프로젝트: koniiiik/obj_viewer
 def set_view(self, filename=None):
     """Paint either a blank scene (if no filename has been
     specified) or the model stored in the file.
     """
     self.scene = QtGui.QGraphicsScene()
     if filename is None:
         filename = self.current_file
     if filename is not None:
         try:
             self.model = Model(self.scene, filename)
         except (IOError, WrongFileFormatError) as e:
             self.model = None
             err = QtGui.QMessageBox(self)
             err.setWindowTitle('Oops!')
             err.setText(str(e))
             err.exec()
             sys.stderr.write(str(e) + EOL)
         if self.model is not None:
             self.model.render()
             self.update_matrix()
     self.update_transform_controls()
     self.view.setScene(self.scene)
     self.view.show()
예제 #2
0
 def set_view(self, filename = None):
     """Paint either a blank scene (if no filename has been
     specified) or the model stored in the file.
     """
     self.scene = QtGui.QGraphicsScene()
     if filename is not None:
         old_model = self.model
         old_mod = self.environment.mod
         try:
             self.model = Model(self.scene, filename,
                                self.environment)
         except (IOError, WrongFileFormatError) as e:
             self.show_error(e)
             # TODO
             self.model = old_model
             self.environment.mod = old_mod
         self.model.render()
         self.update_matrix()
     self.update_transform_controls()
     self.view.setScene(self.scene)
     self.view.show()
예제 #3
0
파일: main.py 프로젝트: koniiiik/obj_viewer
 def set_view(self, filename = None):
     """Paint either a blank scene (if no filename has been
     specified) or the model stored in the file.
     """
     self.scene = QtGui.QGraphicsScene()
     if filename is None:
         filename = self.current_file
     if filename is not None:
         try:
             self.model = Model(self.scene, filename)
         except (IOError, WrongFileFormatError) as e:
             self.model = None
             err = QtGui.QMessageBox(self)
             err.setWindowTitle('Oops!')
             err.setText(str(e))
             err.exec()
             sys.stderr.write(str(e) + EOL)
         if self.model is not None:
             self.model.render()
             self.update_matrix()
     self.update_transform_controls()
     self.view.setScene(self.scene)
     self.view.show()
예제 #4
0
파일: main.py 프로젝트: koniiiik/obj_viewer
class Layout(QtGui.QMainWindow):
    def __init__(self, *args, **kwargs):
        # super().__init__(*args, **kwargs)
        super(Layout, self).__init__(*args, **kwargs)
        pyside_dynamic.loadUi('obj_viewer/gui/layout.ui', self)
        # TODO: do we really need to remember the current file?
        self.current_file = None
        self.model = None
        self.assign_icons()
        self.update_transform_controls()
        self.connect_controls()
        self.set_view()

    def connect_controls(self):
        """Connect relevant signals to their slots."""
        # Generate functions to connect to:
        rotate_x = self.transformation_clicked(rotate={'axis': 'x'})
        rotate_y = self.transformation_clicked(rotate={'axis': 'y'})
        rotate_z = self.transformation_clicked(rotate={'axis': 'z'})
        transl_x = self.transformation_clicked(translate={'axis': 'x'})
        transl_y = self.transformation_clicked(translate={'axis': 'y'})
        transl_z = self.transformation_clicked(translate={'axis': 'z'})
        scale_up = self.transformation_clicked(scale={'factor': FACTOR_PLUS})
        scale_down = self.transformation_clicked(
            scale={'factor': FACTOR_MINUS})
        # Main menu:
        self.openAction.triggered.connect(self.choose_file)
        self.quitAction.triggered.connect(
            QtCore.QCoreApplication.instance().quit)
        self.resetAction.triggered.connect(self.reset_clicked)
        self.rotateXAction.triggered.connect(rotate_x)
        self.rotateYAction.triggered.connect(rotate_y)
        self.rotateZAction.triggered.connect(rotate_z)
        self.translateXAction.triggered.connect(transl_x)
        self.translateYAction.triggered.connect(transl_y)
        self.translateZAction.triggered.connect(transl_z)
        self.scaleUpAction.triggered.connect(scale_up)
        self.scaleDownAction.triggered.connect(scale_down)
        # Left panel (application controls):
        self.loadButton.clicked.connect(self.choose_file)
        self.quitButton.clicked.connect(
            QtCore.QCoreApplication.instance().quit)
        # Right panel (model transformation):
        self.resetButton.clicked.connect(self.reset_clicked)
        self.rotateXButton.clicked.connect(rotate_x)
        self.rotateYButton.clicked.connect(rotate_y)
        self.rotateZButton.clicked.connect(rotate_z)
        self.translateXButton.clicked.connect(transl_x)
        self.translateYButton.clicked.connect(transl_y)
        self.translateZButton.clicked.connect(transl_z)
        self.scaleUpButton.clicked.connect(scale_up)
        self.scaleDownButton.clicked.connect(scale_down)

    def assign_icons(self):
        path = 'obj_viewer/gui/images/'
        # Main menu:
        self.openAction.setIcon(QtGui.QIcon(path + 'open.png'))
        self.quitAction.setIcon(QtGui.QIcon(path + 'quit.png'))
        self.resetAction.setIcon(QtGui.QIcon(path + 'reset.png'))
        self.rotateMenu.setIcon(QtGui.QIcon(path + 'rotate_x.png'))
        self.rotateXAction.setIcon(QtGui.QIcon(path + 'rotate_x.png'))
        self.rotateYAction.setIcon(QtGui.QIcon(path + 'rotate_y.png'))
        self.rotateZAction.setIcon(QtGui.QIcon(path + 'rotate_z.png'))
        self.translateMenu.setIcon(QtGui.QIcon(path + 'translate_x.png'))
        self.translateXAction.setIcon(QtGui.QIcon(path + 'translate_x.png'))
        self.translateYAction.setIcon(QtGui.QIcon(path + 'translate_y.png'))
        self.translateZAction.setIcon(QtGui.QIcon(path + 'translate_z.png'))
        self.scaleUpAction.setIcon(QtGui.QIcon(path + 'scale_up.png'))
        self.scaleDownAction.setIcon(QtGui.QIcon(path + 'scale_down.png'))

        self.loadButton.setIcon(QtGui.QIcon(path + 'open.png'))
        self.quitButton.setIcon(QtGui.QIcon(path + 'quit.png'))
        self.resetButton.setIcon(QtGui.QIcon(path + 'reset.png'))
        self.rotateXButton.setIcon(QtGui.QIcon(path + 'rotate_x.png'))
        self.rotateYButton.setIcon(QtGui.QIcon(path + 'rotate_y.png'))
        self.rotateZButton.setIcon(QtGui.QIcon(path + 'rotate_z.png'))
        self.translateXButton.setIcon(QtGui.QIcon(path + 'translate_x.png'))
        self.translateYButton.setIcon(QtGui.QIcon(path + 'translate_y.png'))
        self.translateZButton.setIcon(QtGui.QIcon(path + 'translate_z.png'))
        self.scaleUpButton.setIcon(QtGui.QIcon(path + 'scale_up.png'))
        self.scaleDownButton.setIcon(QtGui.QIcon(path + 'scale_down.png'))

    def set_view(self, filename=None):
        """Paint either a blank scene (if no filename has been
        specified) or the model stored in the file.
        """
        self.scene = QtGui.QGraphicsScene()
        if filename is None:
            filename = self.current_file
        if filename is not None:
            try:
                self.model = Model(self.scene, filename)
            except (IOError, WrongFileFormatError) as e:
                self.model = None
                err = QtGui.QMessageBox(self)
                err.setWindowTitle('Oops!')
                err.setText(str(e))
                err.exec()
                sys.stderr.write(str(e) + EOL)
            if self.model is not None:
                self.model.render()
                self.update_matrix()
        self.update_transform_controls()
        self.view.setScene(self.scene)
        self.view.show()

    def update_transform_controls(self):
        """Either enable or disable the model transformation controls
        (e.g. the rotation buttons) based on whether there's a model
        to apply them to.
        """
        if self.model is not None:
            self.infoBox.setEnabled(True)
            self.resetButton.setEnabled(True)
            self.rotationBox.setEnabled(True)
            self.scalingBox.setEnabled(True)
            self.translationBox.setEnabled(True)
            self.modelMenu.setEnabled(True)
            self.loadButton.setDefault(False)
        else:
            self.infoBox.setEnabled(False)
            self.resetButton.setEnabled(False)
            self.rotationBox.setEnabled(False)
            self.scalingBox.setEnabled(False)
            self.translationBox.setEnabled(False)
            self.modelMenu.setEnabled(False)
            self.loadButton.setDefault(True)

    def choose_file(self):
        """Show a dialog that enables the user to choose a file
        to load, then set the view accordingly.
        """
        dialog = QtGui.QFileDialog(self)
        dialog.setFileMode(QtGui.QFileDialog.ExistingFile)
        dialog.setNameFilter("Wavefront OBJ (*.obj)")
        if dialog.exec_():
            self.current_file = dialog.selectedFiles()[0]
            self.set_view(self.current_file)

    def reset_clicked(self):
        self.model.reset()
        self.update_matrix()

    def transformation_clicked(self, rotate=None, translate=None, scale=None):
        if rotate is not None:
            matrix = Rotation(**rotate)
        elif translate is not None:
            matrix = Translation(**translate)
        elif scale is not None:
            matrix = Scaling(**scale)

        def transform():
            self.model.transform(matrix)
            self.update_matrix()

        return transform

    # TODO: consider subclassing QTableWidget later
    def update_matrix(self):
        for r, row in enumerate(self.model.current_mod):
            for c, num in enumerate(row):
                if int(num) == num:
                    self.matrixView.setItem(
                        r, c, QtGui.QTableWidgetItem(str(int(num))))
                else:
                    self.matrixView.setItem(
                        r, c, QtGui.QTableWidgetItem('%.3f' % num))
예제 #5
0
class MainWindow(QtGui.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        pyside_dynamic.loadUi('obj_viewer/gui/layout.ui', self)
        self.environment = Environment()
        self.model = None
        self.assign_icons()
        self.update_transform_controls()
        self.connect_controls()
        self.set_view()

    def connect_controls(self):
        """Connect relevant signals to their slots."""
        # Generate functions to connect to:
        rotate_x = self.transformation_clicked(rotation = {'axis': 'x'})
        rotate_y = self.transformation_clicked(rotation = {'axis': 'y'})
        rotate_z = self.transformation_clicked(rotation = {'axis': 'z'})
        transl_x = self.transformation_clicked(translation = {'axis': 'x'})
        transl_y = self.transformation_clicked(translation = {'axis': 'y'})
        transl_z = self.transformation_clicked(translation = {'axis': 'z'})
        scale_up = self.transformation_clicked(scaling =
                                               {'factor': FACTOR_PLUS})
        scale_down = self.transformation_clicked(scaling =
                                                 {'factor': FACTOR_MINUS})
        # Main menu:
        self.openAction.triggered.connect(self.choose_file)
        self.quitAction.triggered.connect(QtCore.QCoreApplication.instance().quit)
        self.resetAction.triggered.connect(self.reset_clicked)
        self.rotateXAction.triggered.connect(rotate_x)
        self.rotateYAction.triggered.connect(rotate_y)
        self.rotateZAction.triggered.connect(rotate_z)
        self.translateXAction.triggered.connect(transl_x)
        self.translateYAction.triggered.connect(transl_y)
        self.translateZAction.triggered.connect(transl_z)
        self.scaleUpAction.triggered.connect(scale_up)
        self.scaleDownAction.triggered.connect(scale_down)
        self.wireAction.triggered.connect(self.show_wireframe)
        self.colorAction.triggered.connect(self.choose_color)
        # Left panel (application/environment controls):
        self.loadButton.clicked.connect(self.choose_file)
        self.quitButton.clicked.connect(QtCore.QCoreApplication.instance().quit)
        self.colorButton.clicked.connect(self.choose_color)
        self.wireCheckbox.stateChanged.connect(self.show_wireframe)
        self.lightXEdit.editingFinished.connect(self.apply_light)
        self.lightYEdit.editingFinished.connect(self.apply_light)
        self.lightZEdit.editingFinished.connect(self.apply_light)
        # Right panel (model transformation):
        self.resetButton.clicked.connect(self.reset_clicked)
        self.clockwiseCheckbox.stateChanged.connect(self.direction_inverted)
        self.directionCheckbox.stateChanged.connect(self.direction_inverted)
        self.rotateXButton.clicked.connect(rotate_x)
        self.rotateYButton.clicked.connect(rotate_y)
        self.rotateZButton.clicked.connect(rotate_z)
        self.translateXButton.clicked.connect(transl_x)
        self.translateYButton.clicked.connect(transl_y)
        self.translateZButton.clicked.connect(transl_z)
        self.scaleUpButton.clicked.connect(scale_up)
        self.scaleDownButton.clicked.connect(scale_down)

    def assign_icons(self):
        path = 'obj_viewer/gui/images/'
        # Main menu:
        self.openAction.setIcon(QtGui.QIcon(path + 'open.png'))
        self.quitAction.setIcon(QtGui.QIcon(path + 'quit.png'))
        self.resetAction.setIcon(QtGui.QIcon(path + 'reset.png'))
        self.rotateMenu.setIcon(QtGui.QIcon(path + 'rotate_x.png'))
        self.rotateXAction.setIcon(QtGui.QIcon(path + 'rotate_x.png'))
        self.rotateYAction.setIcon(QtGui.QIcon(path + 'rotate_y.png'))
        self.rotateZAction.setIcon(QtGui.QIcon(path + 'rotate_z.png'))
        self.translateMenu.setIcon(QtGui.QIcon(path + 'translate_x.png'))
        self.translateXAction.setIcon(QtGui.QIcon(path + 'translate_x.png'))
        self.translateYAction.setIcon(QtGui.QIcon(path + 'translate_y.png'))
        self.translateZAction.setIcon(QtGui.QIcon(path + 'translate_z.png'))
        self.scaleUpAction.setIcon(QtGui.QIcon(path + 'scale_up.png'))
        self.scaleDownAction.setIcon(QtGui.QIcon(path + 'scale_down.png'))
        self.wireAction.setIcon(QtGui.QIcon(path + 'wire.png'))
        self.colorAction.setIcon(QtGui.QIcon(path + 'color.png'))
        # Left panel:
        self.colorButton.setIcon(QtGui.QIcon(path + 'color.png'))
        # Right panel:
        self.loadButton.setIcon(QtGui.QIcon(path + 'open.png'))
        self.quitButton.setIcon(QtGui.QIcon(path + 'quit.png'))
        self.resetButton.setIcon(QtGui.QIcon(path + 'reset.png'))
        self.rotateXButton.setIcon(QtGui.QIcon(path + 'rotate_x.png'))
        self.rotateYButton.setIcon(QtGui.QIcon(path + 'rotate_y.png'))
        self.rotateZButton.setIcon(QtGui.QIcon(path + 'rotate_z.png'))
        self.translateXButton.setIcon(QtGui.QIcon(path + 'translate_x.png'))
        self.translateYButton.setIcon(QtGui.QIcon(path + 'translate_y.png'))
        self.translateZButton.setIcon(QtGui.QIcon(path + 'translate_z.png'))
        self.scaleUpButton.setIcon(QtGui.QIcon(path + 'scale_up.png'))
        self.scaleDownButton.setIcon(QtGui.QIcon(path + 'scale_down.png'))

    def set_view(self, filename = None):
        """Paint either a blank scene (if no filename has been
        specified) or the model stored in the file.
        """
        self.scene = QtGui.QGraphicsScene()
        if filename is not None:
            old_model = self.model
            old_mod = self.environment.mod
            try:
                self.model = Model(self.scene, filename,
                                   self.environment)
            except (IOError, WrongFileFormatError) as e:
                self.show_error(e)
                # TODO
                self.model = old_model
                self.environment.mod = old_mod
            self.model.render()
            self.update_matrix()
        self.update_transform_controls()
        self.view.setScene(self.scene)
        self.view.show()

    def show_error(self, e):
        err = QtGui.QMessageBox(self)
        err.setWindowTitle('Oops!')
        err.setText(str(e))
        err.exec_()
        sys.stderr.write(str(e) + EOL)

    def update_transform_controls(self):
        """Either enable or disable the model transformation controls
        based on whether there's a model to apply them to.
        """
        if self.model is not None:
            self.environmentBox.setEnabled(True)
            self.transformationBox.setEnabled(True)
            self.resetButton.setEnabled(True)
            self.rotationBox.setEnabled(True)
            self.scalingBox.setEnabled(True)
            self.translationBox.setEnabled(True)
            self.modelMenu.setEnabled(True)
            self.sceneMenu.setEnabled(True)
            self.loadButton.setDefault(False)
        else:
            self.environmentBox.setEnabled(False)
            self.transformationBox.setEnabled(False)
            self.resetButton.setEnabled(False)
            self.rotationBox.setEnabled(False)
            self.scalingBox.setEnabled(False)
            self.translationBox.setEnabled(False)
            self.modelMenu.setEnabled(False)
            self.sceneMenu.setEnabled(False)
            self.loadButton.setDefault(True)

    def choose_file(self):
        """Show a dialog that enables the user to choose a file
        to load, then set the view accordingly.
        """
        dialog = QtGui.QFileDialog(self)
        dialog.setFileMode(QtGui.QFileDialog.ExistingFile)
        dialog.setNameFilter("Wavefront OBJ (*.obj)")
        if dialog.exec_():
            self.current_file = dialog.selectedFiles()[0]
            self.set_view(self.current_file)

    def choose_color(self):
        color = QtGui.QColorDialog.getColor()
        if color.isValid():
            self.environment.color = color
            self.model.render()

    def apply_light(self):
        """Check if the coordinates are valid; if not, change them to
        zero. If yes, update the environment's light and render
        the updated scene.
        """
        x, y, z = (self.lightXEdit.text(), self.lightYEdit.text(),
                   self.lightZEdit.text())
        e = ValueError("Invalid light coordinate: "
                       "a decimal point number is "
                       "expected. Also, please use "
                       "\".\" rather than \",\" as "
                       "delimiter.")
        if not is_float(x):
            self.lightXEdit.setText('0.0')
            x = 0
            self.show_error(e)
        if not is_float(y):
            self.lightYEdit.setText('0.0')
            y = 0
            self.show_error(e)
        if not is_float(z):
            self.lightZEdit.setText('0.0')
            z = 0
            self.show_error(e)
        self.environment.light = Point(float(x), float(y), float(z))
        self.model.render()

    def show_wireframe(self):
        if self.sender() == self.wireAction:
            self.environment.wire = not self.environment.wire
            self.wireCheckbox.setChecked(self.environment.wire)
        else:
            self.environment.wire = self.wireCheckbox.isChecked()
        self.model.render()

    def reset_clicked(self):
        self.model.reset()
        self.update_matrix()

    def direction_inverted(self):
        self.environment.rotation_inverted = self.clockwiseCheckbox.isChecked()
        self.environment.translation_inverted = self.directionCheckbox.isChecked()

    def transformation_clicked(self, rotation = None,
                               translation = None, scaling = None):
        def transform():
            if rotation is not None:
                self.model.rotate(**rotation)
            elif translation is not None:
                self.model.translate(**translation)
            elif scaling is not None:
                self.model.scale(**scaling)
            self.update_matrix()
        return transform

    def update_matrix(self):
        for r, row in enumerate(self.environment.mod):
            for c, num in enumerate(row):
                if int(num) == num:
                    self.matrixView.setItem(r, c,
                                            QtGui.QTableWidgetItem(str(int(num))))
                else:
                    self.matrixView.setItem(r, c,
                                            QtGui.QTableWidgetItem('%.3f' % num))
예제 #6
0
파일: main.py 프로젝트: koniiiik/obj_viewer
class Layout(QtGui.QMainWindow):

    def __init__(self, *args, **kwargs):
        # super().__init__(*args, **kwargs)
        super(Layout, self).__init__(*args, **kwargs)
        pyside_dynamic.loadUi('obj_viewer/gui/layout.ui', self)
        # TODO: do we really need to remember the current file?
        self.current_file = None
        self.model = None
        self.assign_icons()
        self.update_transform_controls()
        self.connect_controls()
        self.set_view()

    def connect_controls(self):
        """Connect relevant signals to their slots."""
        # Generate functions to connect to:
        rotate_x = self.transformation_clicked(rotate = {'axis': 'x'})
        rotate_y = self.transformation_clicked(rotate = {'axis': 'y'})
        rotate_z = self.transformation_clicked(rotate = {'axis': 'z'})
        transl_x = self.transformation_clicked(translate = {'axis': 'x'})
        transl_y = self.transformation_clicked(translate = {'axis': 'y'})
        transl_z = self.transformation_clicked(translate = {'axis': 'z'})
        scale_up = self.transformation_clicked(scale =
                                               {'factor': FACTOR_PLUS})
        scale_down = self.transformation_clicked(scale =
                                                 {'factor': FACTOR_MINUS})
        # Main menu:
        self.openAction.triggered.connect(self.choose_file)
        self.quitAction.triggered.connect(QtCore.QCoreApplication.instance().quit)
        self.resetAction.triggered.connect(self.reset_clicked)
        self.rotateXAction.triggered.connect(rotate_x)
        self.rotateYAction.triggered.connect(rotate_y)
        self.rotateZAction.triggered.connect(rotate_z)
        self.translateXAction.triggered.connect(transl_x)
        self.translateYAction.triggered.connect(transl_y)
        self.translateZAction.triggered.connect(transl_z)
        self.scaleUpAction.triggered.connect(scale_up)
        self.scaleDownAction.triggered.connect(scale_down)
        # Left panel (application controls):
        self.loadButton.clicked.connect(self.choose_file)
        self.quitButton.clicked.connect(QtCore.QCoreApplication.instance().quit)
        # Right panel (model transformation):
        self.resetButton.clicked.connect(self.reset_clicked)
        self.rotateXButton.clicked.connect(rotate_x)
        self.rotateYButton.clicked.connect(rotate_y)
        self.rotateZButton.clicked.connect(rotate_z)
        self.translateXButton.clicked.connect(transl_x)
        self.translateYButton.clicked.connect(transl_y)
        self.translateZButton.clicked.connect(transl_z)
        self.scaleUpButton.clicked.connect(scale_up)
        self.scaleDownButton.clicked.connect(scale_down)

    def assign_icons(self):
        path = 'obj_viewer/gui/images/'
        # Main menu:
        self.openAction.setIcon(QtGui.QIcon(path + 'open.png'))
        self.quitAction.setIcon(QtGui.QIcon(path + 'quit.png'))
        self.resetAction.setIcon(QtGui.QIcon(path + 'reset.png'))
        self.rotateMenu.setIcon(QtGui.QIcon(path + 'rotate_x.png'))
        self.rotateXAction.setIcon(QtGui.QIcon(path + 'rotate_x.png'))
        self.rotateYAction.setIcon(QtGui.QIcon(path + 'rotate_y.png'))
        self.rotateZAction.setIcon(QtGui.QIcon(path + 'rotate_z.png'))
        self.translateMenu.setIcon(QtGui.QIcon(path + 'translate_x.png'))
        self.translateXAction.setIcon(QtGui.QIcon(path + 'translate_x.png'))
        self.translateYAction.setIcon(QtGui.QIcon(path + 'translate_y.png'))
        self.translateZAction.setIcon(QtGui.QIcon(path + 'translate_z.png'))
        self.scaleUpAction.setIcon(QtGui.QIcon(path + 'scale_up.png'))
        self.scaleDownAction.setIcon(QtGui.QIcon(path + 'scale_down.png'))
        
        self.loadButton.setIcon(QtGui.QIcon(path + 'open.png'))
        self.quitButton.setIcon(QtGui.QIcon(path + 'quit.png'))
        self.resetButton.setIcon(QtGui.QIcon(path + 'reset.png'))
        self.rotateXButton.setIcon(QtGui.QIcon(path + 'rotate_x.png'))
        self.rotateYButton.setIcon(QtGui.QIcon(path + 'rotate_y.png'))
        self.rotateZButton.setIcon(QtGui.QIcon(path + 'rotate_z.png'))
        self.translateXButton.setIcon(QtGui.QIcon(path + 'translate_x.png'))
        self.translateYButton.setIcon(QtGui.QIcon(path + 'translate_y.png'))
        self.translateZButton.setIcon(QtGui.QIcon(path + 'translate_z.png'))
        self.scaleUpButton.setIcon(QtGui.QIcon(path + 'scale_up.png'))
        self.scaleDownButton.setIcon(QtGui.QIcon(path + 'scale_down.png'))

    def set_view(self, filename = None):
        """Paint either a blank scene (if no filename has been
        specified) or the model stored in the file.
        """
        self.scene = QtGui.QGraphicsScene()
        if filename is None:
            filename = self.current_file
        if filename is not None:
            try:
                self.model = Model(self.scene, filename)
            except (IOError, WrongFileFormatError) as e:
                self.model = None
                err = QtGui.QMessageBox(self)
                err.setWindowTitle('Oops!')
                err.setText(str(e))
                err.exec()
                sys.stderr.write(str(e) + EOL)
            if self.model is not None:
                self.model.render()
                self.update_matrix()
        self.update_transform_controls()
        self.view.setScene(self.scene)
        self.view.show()

    def update_transform_controls(self):
        """Either enable or disable the model transformation controls
        (e.g. the rotation buttons) based on whether there's a model
        to apply them to.
        """
        if self.model is not None:
            self.infoBox.setEnabled(True)
            self.resetButton.setEnabled(True)
            self.rotationBox.setEnabled(True)
            self.scalingBox.setEnabled(True)
            self.translationBox.setEnabled(True)
            self.modelMenu.setEnabled(True)
            self.loadButton.setDefault(False)
        else:
            self.infoBox.setEnabled(False)
            self.resetButton.setEnabled(False)
            self.rotationBox.setEnabled(False)
            self.scalingBox.setEnabled(False)
            self.translationBox.setEnabled(False)
            self.modelMenu.setEnabled(False)
            self.loadButton.setDefault(True)

    def choose_file(self):
        """Show a dialog that enables the user to choose a file
        to load, then set the view accordingly.
        """
        dialog = QtGui.QFileDialog(self)
        dialog.setFileMode(QtGui.QFileDialog.ExistingFile)
        dialog.setNameFilter("Wavefront OBJ (*.obj)")
        if dialog.exec_():
            self.current_file = dialog.selectedFiles()[0]
            self.set_view(self.current_file)

    def reset_clicked(self):
        self.model.reset()
        self.update_matrix()

    def transformation_clicked(self, rotate = None,
                               translate = None, scale = None):
        if rotate is not None:
            matrix = Rotation(**rotate)
        elif translate is not None:
            matrix = Translation(**translate)
        elif scale is not None:
            matrix = Scaling(**scale)
        def transform():
            self.model.transform(matrix)
            self.update_matrix()
        return transform

    # TODO: consider subclassing QTableWidget later
    def update_matrix(self):
        for r, row in enumerate(self.model.current_mod):
            for c, num in enumerate(row):
                if int(num) == num:
                    self.matrixView.setItem(r, c,
                                            QtGui.QTableWidgetItem(str(int(num))))
                else:
                    self.matrixView.setItem(r, c,
                                            QtGui.QTableWidgetItem('%.3f' % num))