def open_and_display_file(self, fpath): progress_dialog = ProgressDialog('Loading', self.window) self.window.set_cursor(gtk.gdk.WATCH) success = True try: self.model_file = ModelFile(fpath) if self.scene is None: self.scene = Scene() self.glarea = SceneArea(self.scene) progress_dialog.set_text('Reading file...') progress_dialog.show() model, model_data = self.model_file.read(progress_dialog.step) progress_dialog.set_text('Loading model...') model.load_data(model_data, progress_dialog.step) self.scene.clear() model.offset_x = self.config.read('machine.platform_offset_x', int) model.offset_y = self.config.read('machine.platform_offset_y', int) self.scene.add_model(model) # platform needs to be added last to be translucent platform_w = self.config.read('machine.platform_w', int) platform_d = self.config.read('machine.platform_d', int) platform = Platform(platform_w, platform_d) self.scene.add_supporting_actor(platform) if self.panel is None or not self.panel_matches_file(): self.panel = self.create_panel() # update panel to reflect new model properties self.panel.set_initial_values() self.panel.connect_handlers() # always start with the same view on the scene self.scene.reset_view(True) if self.model_file.filetype == 'gcode': self.scene.mode_2d = self.config.read('ui.gcode_2d', bool) else: self.scene.mode_2d = False if hasattr(self.panel, 'set_3d_view'): self.panel.set_3d_view(not self.scene.mode_2d) self.window.set_file_widgets(self.glarea, self.panel) self.window.filename = self.model_file.basename self.menu_enable_file_items(self.model_file.filetype != 'gcode') except IOError, e: self.window.window.set_cursor(None) progress_dialog.hide() error_dialog = OpenErrorAlert(self.window, fpath, e.strerror) error_dialog.run() error_dialog.destroy() success = False
def on_file_save_as(self, event=None): """ Save changes to a new file. """ dialog = SaveDialog(self.window, self.current_dir) fpath = dialog.get_path() if fpath: stl_file = ModelFile(fpath) self.scene.export_to_file(stl_file) self.model_file = stl_file self.window.filename = stl_file.basename self.window.file_modified = False
class App(BaseApp): TATLIN_VERSION = '0.2.5' TATLIN_LICENSE = """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 the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ RECENT_FILE_LIMIT = 10 def __init__(self): super(App, self).__init__() # --------------------------------------------------------------------- # WINDOW SETUP # --------------------------------------------------------------------- self.window = MainWindow() self.icon = load_icon(resolve_path('tatlin-logo.png')) self.window.set_icon(self.icon) # --------------------------------------------------------------------- # APP SETUP # --------------------------------------------------------------------- self.init_config() recent_files = self.config.read('ui.recent_files') if recent_files: self.recent_files = [] for f in recent_files.split(os.path.pathsep): if f[-1] in ['0', '1', '2']: fpath, ftype = f[:-1], OpenDialog.ftypes[int(f[-1])] else: fpath, ftype = f, None if os.path.exists(fpath): self.recent_files.append( (os.path.basename(fpath), fpath, ftype)) self.recent_files = self.recent_files[:self.RECENT_FILE_LIMIT] else: self.recent_files = [] self.window.update_recent_files_menu(self.recent_files) window_w = self.config.read('ui.window_w', int) window_h = self.config.read('ui.window_h', int) self.window.set_size((window_w, window_h)) self.init_scene() def init_config(self): fname = os.path.expanduser(os.path.join('~', '.tatlin')) self.config = Config(fname) def init_scene(self): self.panel = None self.scene = None self.model_file = None # dict of properties that other components can read from the app self._app_properties = { 'layers_range_max': lambda: self.scene.get_property('max_layers'), 'layers_value': lambda: self.scene.get_property('max_layers'), 'scaling-factor': self.model_scaling_factor, 'width': self.model_width, 'depth': self.model_depth, 'height': self.model_height, 'rotation-x': self.model_rotation_x, 'rotation-y': self.model_rotation_y, 'rotation-z': self.model_rotation_z, } def show_window(self): self.window.show_all() # ------------------------------------------------------------------------- # PROPERTIES # ------------------------------------------------------------------------- def get_property(self, name): """ Return a property of the application. """ return self._app_properties[name]() def model_scaling_factor(self): factor = self.scene.get_property('scaling-factor') return format_float(factor) def model_width(self): width = self.scene.get_property('width') return format_float(width) def model_depth(self): depth = self.scene.get_property('depth') return format_float(depth) def model_height(self): height = self.scene.get_property('height') return format_float(height) def model_rotation_x(self): angle = self.scene.get_property('rotation-x') return format_float(angle) def model_rotation_y(self): angle = self.scene.get_property('rotation-y') return format_float(angle) def model_rotation_z(self): angle = self.scene.get_property('rotation-z') return format_float(angle) @property def current_dir(self): """ Return path where a file should be saved to. """ if self.model_file is not None: dur = self.model_file.dirname elif len(self.recent_files) > 0: dur = os.path.dirname(self.recent_files[0][1]) else: dur = os.getcwd() return dur def command_line(self): if len(sys.argv) > 1: self.open_and_display_file(os.path.abspath(sys.argv[1])) # ------------------------------------------------------------------------- # EVENT HANDLERS # ------------------------------------------------------------------------- def on_file_open(self, event=None): if self.save_changes_dialog(): show_again = True while show_again: dialog = OpenDialog(self.window, self.current_dir) fpath = dialog.get_path() if fpath: show_again = not self.open_and_display_file( fpath, dialog.get_type()) else: show_again = False def on_file_save(self, event=None): """ Save changes to the same file. """ self.scene.export_to_file(self.model_file) self.window.file_modified = False def on_file_save_as(self, event=None): """ Save changes to a new file. """ dialog = SaveDialog(self.window, self.current_dir) fpath = dialog.get_path() if fpath: stl_file = ModelFile(fpath) self.scene.export_to_file(stl_file) self.model_file = stl_file self.window.filename = stl_file.basename self.window.file_modified = False def on_quit(self, event=None): """ On quit, write config settings and show a dialog proposing to save the changes if the scene has been modified. """ try: self.config.write( 'ui.recent_files', os.path.pathsep.join([ f[1] + str(OpenDialog.ftypes.index(f[2])) for f in self.recent_files ])) w, h = self.window.get_size() self.config.write('ui.window_w', w) self.config.write('ui.window_h', h) if self.scene: self.config.write('ui.gcode_2d', int(self.scene.mode_2d)) self.config.commit() except IOError: logging.warning('Could not write settings to config file %s' % self.config.fname) if self.save_changes_dialog(): self.window.quit() def save_changes_dialog(self): proceed = True if self.scene and self.scene.model_modified: ask_again = True while ask_again: dialog = QuitDialog(self.window) response = dialog.show() if response == QuitDialog.RESPONSE_SAVE: self.on_file_save() ask_again = False elif response == QuitDialog.RESPONSE_SAVE_AS: self.on_file_save_as() ask_again = self.scene.model_modified elif response == QuitDialog.RESPONSE_CANCEL: ask_again = False proceed = False elif response == QuitDialog.RESPONSE_DISCARD: ask_again = False else: logging.warning('Unknown dialog response: %s' % response) ask_again = False proceed = False return proceed def on_about(self, event=None): AboutDialog() def scaling_factor_changed(self, factor): try: self.scene.scale_model(float(factor)) self.scene.invalidate() # tell all the widgets that care about model size that it has changed self.panel.model_size_changed() self.window.file_modified = self.scene.model_modified except ValueError: pass # ignore invalid values def dimension_changed(self, dimension, value): try: self.scene.change_model_dimension(dimension, float(value)) self.scene.invalidate() self.panel.model_size_changed() self.window.file_modified = self.scene.model_modified except ValueError: pass # ignore invalid values def on_layers_changed(self, layers): self.scene.change_num_layers(layers) self.scene.invalidate() def rotation_changed(self, axis, angle): try: self.scene.rotate_model(float(angle), axis) self.scene.invalidate() self.window.file_modified = self.scene.model_modified except ValueError: pass # ignore invalid values def on_center_model(self): """ Center model on platform. """ self.scene.center_model() self.scene.invalidate() self.window.file_modified = self.scene.model_modified def on_arrows_toggled(self, value): """ Show/hide arrows on the Gcode model. """ self.scene.show_arrows(value) self.scene.invalidate() def on_reset_view(self): """ Restore the view of the model shown on startup. """ self.scene.reset_view() self.scene.invalidate() def on_set_mode(self, value): self.scene.mode_2d = not value if self.scene.initialized: self.scene.invalidate() def on_set_ortho(self, value): self.scene.mode_ortho = value self.scene.invalidate() def on_view_front(self): self.scene.rotate_view(0, 0) def on_view_back(self): self.scene.rotate_view(180, 0) def on_view_left(self): self.scene.rotate_view(90, 0) def on_view_right(self): self.scene.rotate_view(-90, 0) def on_view_top(self): self.scene.rotate_view(0, -90) def on_view_bottom(self): self.scene.rotate_view(0, 90) # ------------------------------------------------------------------------- # FILE OPERATIONS # ------------------------------------------------------------------------- def update_recent_files(self, fpath, ftype=None): self.recent_files = [f for f in self.recent_files if f[1] != fpath] self.recent_files.insert(0, (os.path.basename(fpath), fpath, ftype)) self.recent_files = self.recent_files[:self.RECENT_FILE_LIMIT] self.window.update_recent_files_menu(self.recent_files) def open_and_display_file(self, fpath, ftype=None): self.set_wait_cursor() progress_dialog_read = None progress_dialog_load = None success = True try: self.update_recent_files(fpath, ftype) self.model_file = ModelFile(fpath, ftype) self.scene = Scene(self.window) progress_dialog_read = ProgressDialog('Reading file...') model, model_data = self.model_file.read(progress_dialog_read.step) progress_dialog_load = ProgressDialog('Loading model...') model.load_data(model_data, progress_dialog_load.step) self.scene.clear() self.scene.add_model(model) if self.model_file.filetype == 'gcode': offset_x = self.config.read('machine.platform_offset_x', float) offset_y = self.config.read('machine.platform_offset_y', float) offset_z = self.config.read('machine.platform_offset_z', float) if offset_x is None and offset_y is None and offset_z is None: self.scene.view_model_center() logging.info( 'Platform offsets not set, showing model in the center' ) else: model.offset_x = offset_x if offset_x is not None else 0 model.offset_y = offset_y if offset_y is not None else 0 model.offset_z = offset_z if offset_z is not None else 0 logging.info( 'Using platform offsets: (%s, %s, %s)' % (model.offset_x, model.offset_y, model.offset_z)) # platform needs to be added last to be translucent platform_w = self.config.read('machine.platform_w', float) platform_d = self.config.read('machine.platform_d', float) platform = Platform(platform_w, platform_d) self.scene.add_supporting_actor(platform) self.panel = self.create_panel() # update panel to reflect new model properties self.panel.set_initial_values() self.panel.connect_handlers() # always start with the same view on the scene self.scene.reset_view(True) if self.model_file.filetype == 'gcode': self.scene.mode_2d = bool(self.config.read('ui.gcode_2d', int)) else: self.scene.mode_2d = False if hasattr(self.panel, 'set_3d_view'): self.panel.set_3d_view(not self.scene.mode_2d) self.window.set_file_widgets(self.scene, self.panel) self.window.filename = self.model_file.basename self.window.file_modified = False self.window.menu_enable_file_items( self.model_file.filetype != 'gcode') if self.model_file.size > 2**30: size = self.model_file.size / 2**30 units = 'GB' elif self.model_file.size > 2**20: size = self.model_file.size / 2**20 units = 'MB' elif self.model_file.size > 2**10: size = self.model_file.size / 2**10 units = 'KB' else: size = self.model_file.size units = 'B' vertex_plural = 'vertex' if int(str( model.vertex_count)[-1]) == 1 else 'vertices' self.window.update_status(' %s (%.1f%s, %d %s)' % (self.model_file.basename, size, units, model.vertex_count, vertex_plural)) except IOError, e: self.set_normal_cursor() error_dialog = OpenErrorAlert(fpath, e.strerror) error_dialog.show() success = False except ModelFileError, e: self.set_normal_cursor() error_dialog = OpenErrorAlert(fpath, e.message) error_dialog.show() success = False
def open_and_display_file(self, fpath, ftype=None): self.set_wait_cursor() progress_dialog_read = None progress_dialog_load = None success = True try: self.update_recent_files(fpath, ftype) self.model_file = ModelFile(fpath, ftype) self.scene = Scene(self.window) progress_dialog_read = ProgressDialog('Reading file...') model, model_data = self.model_file.read(progress_dialog_read.step) progress_dialog_load = ProgressDialog('Loading model...') model.load_data(model_data, progress_dialog_load.step) self.scene.clear() self.scene.add_model(model) if self.model_file.filetype == 'gcode': offset_x = self.config.read('machine.platform_offset_x', float) offset_y = self.config.read('machine.platform_offset_y', float) offset_z = self.config.read('machine.platform_offset_z', float) if offset_x is None and offset_y is None and offset_z is None: self.scene.view_model_center() logging.info( 'Platform offsets not set, showing model in the center' ) else: model.offset_x = offset_x if offset_x is not None else 0 model.offset_y = offset_y if offset_y is not None else 0 model.offset_z = offset_z if offset_z is not None else 0 logging.info( 'Using platform offsets: (%s, %s, %s)' % (model.offset_x, model.offset_y, model.offset_z)) # platform needs to be added last to be translucent platform_w = self.config.read('machine.platform_w', float) platform_d = self.config.read('machine.platform_d', float) platform = Platform(platform_w, platform_d) self.scene.add_supporting_actor(platform) self.panel = self.create_panel() # update panel to reflect new model properties self.panel.set_initial_values() self.panel.connect_handlers() # always start with the same view on the scene self.scene.reset_view(True) if self.model_file.filetype == 'gcode': self.scene.mode_2d = bool(self.config.read('ui.gcode_2d', int)) else: self.scene.mode_2d = False if hasattr(self.panel, 'set_3d_view'): self.panel.set_3d_view(not self.scene.mode_2d) self.window.set_file_widgets(self.scene, self.panel) self.window.filename = self.model_file.basename self.window.file_modified = False self.window.menu_enable_file_items( self.model_file.filetype != 'gcode') if self.model_file.size > 2**30: size = self.model_file.size / 2**30 units = 'GB' elif self.model_file.size > 2**20: size = self.model_file.size / 2**20 units = 'MB' elif self.model_file.size > 2**10: size = self.model_file.size / 2**10 units = 'KB' else: size = self.model_file.size units = 'B' vertex_plural = 'vertex' if int(str( model.vertex_count)[-1]) == 1 else 'vertices' self.window.update_status(' %s (%.1f%s, %d %s)' % (self.model_file.basename, size, units, model.vertex_count, vertex_plural)) except IOError, e: self.set_normal_cursor() error_dialog = OpenErrorAlert(fpath, e.strerror) error_dialog.show() success = False
class App(object): def __init__(self): # --------------------------------------------------------------------- # WINDOW SETUP # --------------------------------------------------------------------- self.window = MainWindow() self.actiongroup = self.set_up_actions() self.create_menu_items(self.actiongroup) for menu_item in self.menu_items: self.window.append_menu_item(menu_item) self.menu_enable_file_items(False) self.window.connect('destroy', self.quit) self.window.connect('open-clicked', self.open_file_dialog) # --------------------------------------------------------------------- # APP SETUP # --------------------------------------------------------------------- self.init_config() self.init_scene() def init_config(self): fname = os.path.expanduser(os.path.join('~', '.tatlin')) self.config = Config(fname) def init_scene(self): self.panel = None self.scene = None self.model_file = None # dict of properties that other components can read from the app self._app_properties = { 'layers_range_max': lambda: self.scene.get_property('max_layers'), 'layers_value': lambda: self.scene.get_property('max_layers'), 'scaling-factor': self.model_scaling_factor, 'width': self.model_width, 'depth': self.model_depth, 'height': self.model_height, 'rotation-x': self.model_rotation_x, 'rotation-y': self.model_rotation_y, 'rotation-z': self.model_rotation_z, } def show_window(self): self.window.show_all() # ------------------------------------------------------------------------- # ACTIONS # ------------------------------------------------------------------------- def set_up_actions(self): actiongroup = ActionGroup('main') actiongroup.add_action(gtk.Action('file', '_File', 'File', None)) action_open = gtk.Action('open', 'Open', 'Open', gtk.STOCK_OPEN) action_open.connect('activate', self.open_file_dialog) actiongroup.add_action_with_accel(action_open, '<Control>o') action_save = gtk.Action('save', 'Save', 'Save file', gtk.STOCK_SAVE) action_save.connect('activate', self.save_file) actiongroup.add_action_with_accel(action_save, '<Control>s') save_as = gtk.Action('save-as', 'Save As...', 'Save As...', gtk.STOCK_SAVE_AS) save_as.connect('activate', self.save_file_as) actiongroup.add_action_with_accel(save_as, '<Control><Shift>s') action_quit = gtk.Action('quit', 'Quit', 'Quit', gtk.STOCK_QUIT) action_quit.connect('activate', self.quit) actiongroup.add_action_with_accel(action_quit, '<Control>q') accelgroup = gtk.AccelGroup() for action in actiongroup.list_actions(): action.set_accel_group(accelgroup) self.window.add_accel_group(accelgroup) return actiongroup def create_menu_items(self, actiongroup): file_menu = gtk.Menu() file_menu.append(actiongroup.menu_item('open')) save_item = actiongroup.menu_item('save') file_menu.append(save_item) save_as_item = actiongroup.menu_item('save-as') file_menu.append(save_as_item) file_menu.append(actiongroup.menu_item('quit')) item_file = actiongroup.menu_item('file') item_file.set_submenu(file_menu) self.menu_items_file = [save_item, save_as_item] self.menu_items = [item_file] def menu_enable_file_items(self, enable=True): for menu_item in self.menu_items_file: menu_item.set_sensitive(enable) # ------------------------------------------------------------------------- # PROPERTIES # ------------------------------------------------------------------------- def get_property(self, name): """ Return a property of the application. """ return self._app_properties[name]() def model_scaling_factor(self): factor = self.scene.get_property('scaling-factor') return format_float(factor) def model_width(self): width = self.scene.get_property('width') return format_float(width) def model_depth(self): depth = self.scene.get_property('depth') return format_float(depth) def model_height(self): height = self.scene.get_property('height') return format_float(height) def model_rotation_x(self): angle = self.scene.get_property('rotation-x') return format_float(angle) def model_rotation_y(self): angle = self.scene.get_property('rotation-y') return format_float(angle) def model_rotation_z(self): angle = self.scene.get_property('rotation-z') return format_float(angle) @property def current_dir(self): """ Return path where a file should be saved to. """ if self.model_file is not None: dur = self.model_file.dirname else: dur = os.getcwd() return dur # ------------------------------------------------------------------------- # EVENT HANDLERS # ------------------------------------------------------------------------- def command_line(self): if len(sys.argv) > 1: self.open_and_display_file(sys.argv[1]) def save_file(self, action=None): """ Save changes to the same file. """ self.scene.export_to_file(self.model_file) self.window.file_modified = False def save_file_as(self, action=None): """ Save changes to a new file. """ dialog = SaveDialog(self.current_dir) if dialog.run() == gtk.RESPONSE_ACCEPT: stl_file = ModelFile(dialog.get_filename()) self.scene.export_to_file(stl_file) self.model_file = stl_file self.window.filename = stl_file.basename self.window.file_modified = False dialog.destroy() def open_file_dialog(self, action=None): if self.save_changes_dialog(): dialog = OpenDialog(self.current_dir) show_again = True while show_again: if dialog.run() == gtk.RESPONSE_ACCEPT: dialog.hide() fname = dialog.get_filename() show_again = not self.open_and_display_file(fname) else: show_again = False dialog.destroy() def quit(self, action=None): """ On quit, show a dialog proposing to save the changes if the scene has been modified. """ if self.save_changes_dialog(): gtk.main_quit() def save_changes_dialog(self): proceed = True if self.scene and self.scene.model_modified: dialog = QuitDialog(self.window) ask_again = True while ask_again: response = dialog.run() if response == QuitDialog.RESPONSE_SAVE: self.save_file() ask_again = False elif response == QuitDialog.RESPONSE_SAVE_AS: self.save_file_as() ask_again = self.scene.model_modified elif response in [QuitDialog.RESPONSE_CANCEL, gtk.RESPONSE_DELETE_EVENT]: ask_again = False proceed = False elif response == QuitDialog.RESPONSE_DISCARD: ask_again = False dialog.destroy() return proceed def scaling_factor_changed(self, factor): try: factor = float(factor) self.scene.scale_model(factor) self.scene.invalidate() # tell all the widgets that care about model size that it has changed self.panel.model_size_changed() self.window.file_modified = self.scene.model_modified except ValueError: pass # ignore invalid values def dimension_changed(self, dimension, value): try: value = float(value) self.scene.change_model_dimension(dimension, value) self.scene.invalidate() self.panel.model_size_changed() self.window.file_modified = self.scene.model_modified except ValueError: pass # ignore invalid values def on_scale_value_changed(self, widget): value = int(widget.get_value()) self.scene.change_num_layers(value) self.scene.invalidate() def rotation_changed(self, axis, angle): try: self.scene.rotate_model(float(angle), axis) self.scene.invalidate() self.window.file_modified = self.scene.model_modified except ValueError: pass # ignore invalid values def on_button_center_clicked(self, widget): """ Center model on platform. """ self.scene.center_model() self.scene.invalidate() self.window.file_modified = self.scene.model_modified def on_arrows_toggled(self, widget): """ Show/hide arrows on the Gcode model. """ self.scene.show_arrows(widget.get_active()) self.scene.invalidate() def on_reset_view(self, widget): """ Restore the view of the model shown on startup. """ self.scene.reset_view() self.scene.invalidate() def on_set_mode(self, widget): self.scene.mode_2d = not widget.get_active() self.scene.invalidate() def on_set_ortho(self, widget): self.scene.mode_ortho = widget.get_active() self.scene.invalidate() def on_view_front(self, widget): self.scene.rotate_view(0, 0) def on_view_back(self, widget): self.scene.rotate_view(180, 0) def on_view_left(self, widget): self.scene.rotate_view(90, 0) def on_view_right(self, widget): self.scene.rotate_view(-90, 0) def on_view_top(self, widget): self.scene.rotate_view(0, -90) def on_view_bottom(self, widget): self.scene.rotate_view(0, 90) # ------------------------------------------------------------------------- # FILE OPERATIONS # ------------------------------------------------------------------------- def open_and_display_file(self, fpath): progress_dialog = ProgressDialog('Loading', self.window) self.window.set_cursor(gtk.gdk.WATCH) success = True try: self.model_file = ModelFile(fpath) if self.scene is None: self.scene = Scene() self.glarea = SceneArea(self.scene) progress_dialog.set_text('Reading file...') progress_dialog.show() model, model_data = self.model_file.read(progress_dialog.step) progress_dialog.set_text('Loading model...') model.load_data(model_data, progress_dialog.step) self.scene.clear() self.scene.add_model(model) # platform needs to be added last to be translucent platform_w = self.config.read('machine.platform_w', int) platform_d = self.config.read('machine.platform_d', int) platform = Platform(platform_w, platform_d) self.scene.add_supporting_actor(platform) if self.panel is None or not self.panel_matches_file(): self.panel = self.create_panel() # update panel to reflect new model properties self.panel.set_initial_values() self.panel.connect_handlers() # always start with the same view on the scene self.scene.reset_view(True) self.scene.mode_2d = False self.window.set_file_widgets(self.glarea, self.panel) self.window.filename = self.model_file.basename self.menu_enable_file_items(self.model_file.filetype != 'gcode') except IOError, e: self.window.window.set_cursor(None) progress_dialog.hide() error_dialog = OpenErrorAlert(self.window, fpath, e.strerror) error_dialog.run() error_dialog.destroy() success = False except ModelFileError, e: self.window.window.set_cursor(None) progress_dialog.hide() error_dialog = OpenErrorAlert(self.window, fpath, e.message) error_dialog.run() error_dialog.destroy() success = False
class App(BaseApp): TATLIN_VERSION = '0.2.5' TATLIN_LICENSE = """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 the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ RECENT_FILE_LIMIT = 10 def __init__(self): super(App, self).__init__() # --------------------------------------------------------------------- # WINDOW SETUP # --------------------------------------------------------------------- self.window = MainWindow() self.icon = load_icon(resolve_path('tatlin-logo.png')) self.window.set_icon(self.icon) # --------------------------------------------------------------------- # APP SETUP # --------------------------------------------------------------------- self.init_config() recent_files = self.config.read('ui.recent_files') if recent_files: self.recent_files = [] for f in recent_files.split(os.path.pathsep): if f[-1] in ['0', '1', '2']: fpath, ftype = f[:-1], OpenDialog.ftypes[int(f[-1])] else: fpath, ftype = f, None if os.path.exists(fpath): self.recent_files.append((os.path.basename(fpath), fpath, ftype)) self.recent_files = self.recent_files[:self.RECENT_FILE_LIMIT] else: self.recent_files = [] self.window.update_recent_files_menu(self.recent_files) window_w = self.config.read('ui.window_w', int) window_h = self.config.read('ui.window_h', int) self.window.set_size((window_w, window_h)) self.init_scene() def init_config(self): fname = os.path.expanduser(os.path.join('~', '.tatlin')) self.config = Config(fname) def init_scene(self): self.panel = None self.scene = None self.model_file = None # dict of properties that other components can read from the app self._app_properties = { 'layers_range_max': lambda: self.scene.get_property('max_layers'), 'layers_value': lambda: self.scene.get_property('max_layers'), 'scaling-factor': self.model_scaling_factor, 'width': self.model_width, 'depth': self.model_depth, 'height': self.model_height, 'rotation-x': self.model_rotation_x, 'rotation-y': self.model_rotation_y, 'rotation-z': self.model_rotation_z, } def show_window(self): self.window.show_all() # ------------------------------------------------------------------------- # PROPERTIES # ------------------------------------------------------------------------- def get_property(self, name): """ Return a property of the application. """ return self._app_properties[name]() def model_scaling_factor(self): factor = self.scene.get_property('scaling-factor') return format_float(factor) def model_width(self): width = self.scene.get_property('width') return format_float(width) def model_depth(self): depth = self.scene.get_property('depth') return format_float(depth) def model_height(self): height = self.scene.get_property('height') return format_float(height) def model_rotation_x(self): angle = self.scene.get_property('rotation-x') return format_float(angle) def model_rotation_y(self): angle = self.scene.get_property('rotation-y') return format_float(angle) def model_rotation_z(self): angle = self.scene.get_property('rotation-z') return format_float(angle) @property def current_dir(self): """ Return path where a file should be saved to. """ if self.model_file is not None: dur = self.model_file.dirname elif len(self.recent_files) > 0: dur = os.path.dirname(self.recent_files[0][1]) else: dur = os.getcwd() return dur def command_line(self): if len(sys.argv) > 1: self.open_and_display_file(os.path.abspath(sys.argv[1])) # ------------------------------------------------------------------------- # EVENT HANDLERS # ------------------------------------------------------------------------- def on_file_open(self, event=None): if self.save_changes_dialog(): show_again = True while show_again: dialog = OpenDialog(self.window, self.current_dir) fpath = dialog.get_path() if fpath: show_again = not self.open_and_display_file(fpath, dialog.get_type()) else: show_again = False def on_file_save(self, event=None): """ Save changes to the same file. """ self.scene.export_to_file(self.model_file) self.window.file_modified = False def on_file_save_as(self, event=None): """ Save changes to a new file. """ dialog = SaveDialog(self.window, self.current_dir) fpath = dialog.get_path() if fpath: stl_file = ModelFile(fpath) self.scene.export_to_file(stl_file) self.model_file = stl_file self.window.filename = stl_file.basename self.window.file_modified = False def on_quit(self, event=None): """ On quit, write config settings and show a dialog proposing to save the changes if the scene has been modified. """ try: self.config.write('ui.recent_files', os.path.pathsep.join([f[1] + str(OpenDialog.ftypes.index(f[2])) for f in self.recent_files])) w, h = self.window.get_size() self.config.write('ui.window_w', w) self.config.write('ui.window_h', h) if self.scene: self.config.write('ui.gcode_2d', int(self.scene.mode_2d)) self.config.commit() except IOError: logging.warning('Could not write settings to config file %s' % self.config.fname) if self.save_changes_dialog(): self.window.quit() def save_changes_dialog(self): proceed = True if self.scene and self.scene.model_modified: ask_again = True while ask_again: dialog = QuitDialog(self.window) response = dialog.show() if response == QuitDialog.RESPONSE_SAVE: self.on_file_save() ask_again = False elif response == QuitDialog.RESPONSE_SAVE_AS: self.on_file_save_as() ask_again = self.scene.model_modified elif response == QuitDialog.RESPONSE_CANCEL: ask_again = False proceed = False elif response == QuitDialog.RESPONSE_DISCARD: ask_again = False else: logging.warning('Unknown dialog response: %s' % response) ask_again = False proceed = False return proceed def on_about(self, event=None): AboutDialog() def scaling_factor_changed(self, factor): try: self.scene.scale_model(float(factor)) self.scene.invalidate() # tell all the widgets that care about model size that it has changed self.panel.model_size_changed() self.window.file_modified = self.scene.model_modified except ValueError: pass # ignore invalid values def dimension_changed(self, dimension, value): try: self.scene.change_model_dimension(dimension, float(value)) self.scene.invalidate() self.panel.model_size_changed() self.window.file_modified = self.scene.model_modified except ValueError: pass # ignore invalid values def on_layers_changed(self, layers): self.scene.change_num_layers(layers) self.scene.invalidate() def rotation_changed(self, axis, angle): try: self.scene.rotate_model(float(angle), axis) self.scene.invalidate() self.window.file_modified = self.scene.model_modified except ValueError: pass # ignore invalid values def on_center_model(self): """ Center model on platform. """ self.scene.center_model() self.scene.invalidate() self.window.file_modified = self.scene.model_modified def on_arrows_toggled(self, value): """ Show/hide arrows on the Gcode model. """ self.scene.show_arrows(value) self.scene.invalidate() def on_reset_view(self): """ Restore the view of the model shown on startup. """ self.scene.reset_view() self.scene.invalidate() def on_set_mode(self, value): self.scene.mode_2d = not value if self.scene.initialized: self.scene.invalidate() def on_set_ortho(self, value): self.scene.mode_ortho = value self.scene.invalidate() def on_view_front(self): self.scene.rotate_view(0, 0) def on_view_back(self): self.scene.rotate_view(180, 0) def on_view_left(self): self.scene.rotate_view(90, 0) def on_view_right(self): self.scene.rotate_view(-90, 0) def on_view_top(self): self.scene.rotate_view(0, -90) def on_view_bottom(self): self.scene.rotate_view(0, 90) # ------------------------------------------------------------------------- # FILE OPERATIONS # ------------------------------------------------------------------------- def update_recent_files(self, fpath, ftype=None): self.recent_files = [f for f in self.recent_files if f[1] != fpath] self.recent_files.insert(0, (os.path.basename(fpath), fpath, ftype)) self.recent_files = self.recent_files[:self.RECENT_FILE_LIMIT] self.window.update_recent_files_menu(self.recent_files) def open_and_display_file(self, fpath, ftype=None): self.set_wait_cursor() progress_dialog_read = None progress_dialog_load = None success = True try: self.update_recent_files(fpath, ftype) self.model_file = ModelFile(fpath, ftype) self.scene = Scene(self.window) progress_dialog_read = ProgressDialog('Reading file...') model, model_data = self.model_file.read(progress_dialog_read.step) progress_dialog_load = ProgressDialog('Loading model...') model.load_data(model_data, progress_dialog_load.step) self.scene.clear() self.scene.add_model(model) if self.model_file.filetype == 'gcode': offset_x = self.config.read('machine.platform_offset_x', float) offset_y = self.config.read('machine.platform_offset_y', float) offset_z = self.config.read('machine.platform_offset_z', float) if offset_x is None and offset_y is None and offset_z is None: self.scene.view_model_center() logging.info('Platform offsets not set, showing model in the center') else: model.offset_x = offset_x if offset_x is not None else 0 model.offset_y = offset_y if offset_y is not None else 0 model.offset_z = offset_z if offset_z is not None else 0 logging.info('Using platform offsets: (%s, %s, %s)' % ( model.offset_x, model.offset_y, model.offset_z)) # platform needs to be added last to be translucent platform_w = self.config.read('machine.platform_w', float) platform_d = self.config.read('machine.platform_d', float) platform = Platform(platform_w, platform_d) self.scene.add_supporting_actor(platform) self.panel = self.create_panel() # update panel to reflect new model properties self.panel.set_initial_values() self.panel.connect_handlers() # always start with the same view on the scene self.scene.reset_view(True) if self.model_file.filetype == 'gcode': self.scene.mode_2d = bool(self.config.read('ui.gcode_2d', int)) else: self.scene.mode_2d = False if hasattr(self.panel, 'set_3d_view'): self.panel.set_3d_view(not self.scene.mode_2d) self.window.set_file_widgets(self.scene, self.panel) self.window.filename = self.model_file.basename self.window.file_modified = False self.window.menu_enable_file_items(self.model_file.filetype != 'gcode') if self.model_file.size > 2**30: size = self.model_file.size / 2**30 units = 'GB' elif self.model_file.size > 2**20: size = self.model_file.size / 2**20 units = 'MB' elif self.model_file.size > 2**10: size = self.model_file.size / 2**10 units = 'KB' else: size = self.model_file.size units = 'B' vertex_plural = 'vertex' if int(str(model.vertex_count)[-1]) == 1 else 'vertices' self.window.update_status(' %s (%.1f%s, %d %s)' % ( self.model_file.basename, size, units, model.vertex_count, vertex_plural)) except IOError, e: self.set_normal_cursor() error_dialog = OpenErrorAlert(fpath, e.strerror) error_dialog.show() success = False except ModelFileError, e: self.set_normal_cursor() error_dialog = OpenErrorAlert(fpath, e.message) error_dialog.show() success = False
def open_and_display_file(self, fpath, ftype=None): self.set_wait_cursor() progress_dialog_read = None progress_dialog_load = None success = True try: self.update_recent_files(fpath, ftype) self.model_file = ModelFile(fpath, ftype) self.scene = Scene(self.window) progress_dialog_read = ProgressDialog('Reading file...') model, model_data = self.model_file.read(progress_dialog_read.step) progress_dialog_load = ProgressDialog('Loading model...') model.load_data(model_data, progress_dialog_load.step) self.scene.clear() self.scene.add_model(model) if self.model_file.filetype == 'gcode': offset_x = self.config.read('machine.platform_offset_x', float) offset_y = self.config.read('machine.platform_offset_y', float) offset_z = self.config.read('machine.platform_offset_z', float) if offset_x is None and offset_y is None and offset_z is None: self.scene.view_model_center() logging.info('Platform offsets not set, showing model in the center') else: model.offset_x = offset_x if offset_x is not None else 0 model.offset_y = offset_y if offset_y is not None else 0 model.offset_z = offset_z if offset_z is not None else 0 logging.info('Using platform offsets: (%s, %s, %s)' % ( model.offset_x, model.offset_y, model.offset_z)) # platform needs to be added last to be translucent platform_w = self.config.read('machine.platform_w', float) platform_d = self.config.read('machine.platform_d', float) platform = Platform(platform_w, platform_d) self.scene.add_supporting_actor(platform) self.panel = self.create_panel() # update panel to reflect new model properties self.panel.set_initial_values() self.panel.connect_handlers() # always start with the same view on the scene self.scene.reset_view(True) if self.model_file.filetype == 'gcode': self.scene.mode_2d = bool(self.config.read('ui.gcode_2d', int)) else: self.scene.mode_2d = False if hasattr(self.panel, 'set_3d_view'): self.panel.set_3d_view(not self.scene.mode_2d) self.window.set_file_widgets(self.scene, self.panel) self.window.filename = self.model_file.basename self.window.file_modified = False self.window.menu_enable_file_items(self.model_file.filetype != 'gcode') if self.model_file.size > 2**30: size = self.model_file.size / 2**30 units = 'GB' elif self.model_file.size > 2**20: size = self.model_file.size / 2**20 units = 'MB' elif self.model_file.size > 2**10: size = self.model_file.size / 2**10 units = 'KB' else: size = self.model_file.size units = 'B' vertex_plural = 'vertex' if int(str(model.vertex_count)[-1]) == 1 else 'vertices' self.window.update_status(' %s (%.1f%s, %d %s)' % ( self.model_file.basename, size, units, model.vertex_count, vertex_plural)) except IOError, e: self.set_normal_cursor() error_dialog = OpenErrorAlert(fpath, e.strerror) error_dialog.show() success = False