class MetadataPanel(wx.Panel, IUIBehavior): """This class contains the wx widgets for control over metadata information in the program. These widgets may include, but not limited to author, license, stl file input, and ldraw file output. """ max_path_length = 256 def __init__(self, parent): """Default constructor for MainPanel class. """ wx.Panel.__init__(self, parent, size=UIStyle.metadata_panel_size, style=UIStyle.metadata_border) self.parent = parent self.browse_stl_button = None self.help_button = None self.about_button = None self.popup = None self.browse_stl_button = None self.author_input = None self.license_input = None self.stl_path_input = None # The input element self.stl_path_text = None # The text entered self.stl_path_isvalid = False self.ldraw_name_input = None self.ldraw_name_isvalid = False self.out_file = None # entire output file path # Settings self.stl_dir = None # Essentially stl_path_text minus file part self.part_dir = None # ldraw_name_text minus file part self.part_name = None # "untitled.dat" or whatever user entered self.author_default = None # The one loaded from file at start self.license_default = None self.load_settings() self.license_text = self.license_default self.author_text = self.author_default # The text entered by user self._build_gui() self.parent.Layout() def _build_gui(self): """Initializing input, output, process control, and log panel elements :return: """ self.SetBackgroundColour(UIStyle.metadata_background_color) # Input path_name_static_text = wx.StaticText( self, label="Step 1: Choose Input STL File", size=UIStyle.metadata_label_size, style=wx.ALIGN_RIGHT) path_name_static_text.SetForegroundColour(UIStyle.metadata_label_color) # Stl input. self.stl_path_input = wx.TextCtrl(self, size=UIStyle.metadata_text_ctrl_size) self.stl_path_input.SetMaxLength(self.max_path_length) self.stl_path_input.SetBackgroundColour(UIStyle.metadata_input_valid_background) self.stl_path_input.SetForegroundColour(UIStyle.metadata_input_text_color) self.browse_stl_button = Button(self, label="Browse Input", size=UIStyle.metadata_big_button) self.browse_stl_button.SetForegroundColour(UIStyle.button_text) self.browse_stl_button.SetBackgroundColour(UIStyle.button_background) # Help / About. self.help_button = Button(self, label="?", size=UIStyle.metadata_small_button_size) self.help_button.SetForegroundColour(UIStyle.button_text) self.help_button.SetBackgroundColour(UIStyle.button_background) self.about_button = Button(self, label="i", size=UIStyle.metadata_small_button_size) self.about_button.SetForegroundColour(UIStyle.button_text) self.about_button.SetBackgroundColour(UIStyle.button_background) # Output path selection. path_part_static_text = wx.StaticText(self, label="Step 2: Choose Output Name", size=UIStyle.metadata_label_size, style=wx.ALIGN_RIGHT) path_part_static_text.SetForegroundColour(UIStyle.metadata_label_color) self.ldraw_name_input = wx.TextCtrl(self, size=UIStyle.metadata_text_ctrl_size, style= wx.TE_READONLY) self.ldraw_name_input.SetMaxLength(self.max_path_length) self.ldraw_name_input.SetValue("Browse output -->") self.ldraw_name_input.SetForegroundColour(UIStyle.metadata_input_text_color) self.ldraw_name_input.SetBackgroundColour(UIStyle.metadata_input_valid_background) self.browse_output_button = Button(self, label="Browse Output", size=UIStyle.metadata_big_button) self.browse_output_button.SetForegroundColour(UIStyle.button_text) self.browse_output_button.SetBackgroundColour(UIStyle.button_background) # Author author_static_text = wx.StaticText(self, label="Optional: Set Author", size=UIStyle.metadata_label_size, style=wx.ALIGN_RIGHT) author_static_text.SetForegroundColour(UIStyle.metadata_label_color) self.author_input = wx.TextCtrl(self, size=UIStyle.metadata_text_ctrl_size) self.author_input.SetForegroundColour(UIStyle.metadata_input_text_color) self.author_input.SetBackgroundColour(UIStyle.metadata_input_valid_background) self.author_input.SetMaxLength(self.max_path_length) # License information. license_static_text = wx.StaticText(self, label="Optional: Set License", size=UIStyle.metadata_label_size, style=wx.ALIGN_RIGHT) license_static_text.SetForegroundColour(UIStyle.metadata_label_color) self.license_input = wx.TextCtrl(self, size=UIStyle.metadata_text_ctrl_size) self.license_input.SetForegroundColour(UIStyle.metadata_input_text_color) self.license_input.SetBackgroundColour(UIStyle.metadata_input_valid_background) self.license_input.SetMaxLength(self.max_path_length) # Create the layout. horizontal_input = wx.BoxSizer(wx.HORIZONTAL) horizontal_output = wx.BoxSizer(wx.HORIZONTAL) horizontal_author = wx.BoxSizer(wx.HORIZONTAL) horizontal_license = wx.BoxSizer(wx.HORIZONTAL) horizontal_input.Add(path_name_static_text, 0, wx.ALIGN_CENTER) horizontal_input.AddSpacer(5) horizontal_input.Add(self.stl_path_input, 0, wx.ALIGN_CENTER) horizontal_input.AddSpacer(5) horizontal_input.Add(self.browse_stl_button, 0, wx.ALIGN_CENTER) horizontal_input.AddSpacer(5) horizontal_input.Add(self.help_button, 0, wx.ALIGN_CENTER) horizontal_input.AddSpacer(5) horizontal_input.Add(self.about_button, 0, wx.ALIGN_CENTER) horizontal_output.Add(path_part_static_text, 0, wx.ALIGN_LEFT) horizontal_output.AddSpacer(5) horizontal_output.Add(self.ldraw_name_input, 0, wx.ALIGN_LEFT) horizontal_output.AddSpacer(5) horizontal_output.Add(self.browse_output_button, 0, wx.ALIGN_LEFT) horizontal_author.Add(author_static_text, 0, wx.ALIGN_LEFT) horizontal_author.AddSpacer(5) horizontal_author.Add(self.author_input, 0, wx.ALIGN_LEFT) horizontal_license.Add(license_static_text, 0, wx.ALIGN_LEFT) horizontal_license.AddSpacer(5) horizontal_license.Add(self.license_input, 0, wx.ALIGN_LEFT) vertical_layout = wx.BoxSizer(wx.VERTICAL) vertical_layout.Add(horizontal_input, 0, wx.ALIGN_LEFT) vertical_layout.Add(horizontal_output, 0, wx.ALIGN_LEFT) vertical_layout.Add(horizontal_author, 0, wx.ALIGN_LEFT) vertical_layout.Add(horizontal_license, 0, wx.ALIGN_LEFT) horizontal_split = wx.BoxSizer(wx.HORIZONTAL) horizontal_split.AddSpacer(100) horizontal_split.Add(vertical_layout, 0, wx.ALIGN_LEFT) self.SetSizer(horizontal_split) # Fill in default fields self.reset_author() self.reset_license() # Register events. self.Bind(wx.EVT_BUTTON, self.about, self.about_button) self.Bind(wx.EVT_BUTTON, self.browse_output, self.browse_output_button) self.Bind(wx.EVT_BUTTON, self.help, self.help_button) self.Bind(wx.EVT_BUTTON, self.browse_input, self.browse_stl_button) # Bind input field change events self.stl_path_input.Bind(wx.EVT_KILL_FOCUS, self.text_ctrl_input_on_kill_focus) self.stl_path_input.Bind(wx.EVT_SET_FOCUS, self.text_ctrl_input_on_gain_focus) self.ldraw_name_input.Bind(wx.EVT_KILL_FOCUS, self.text_ctrl_output_on_kill_focus) self.ldraw_name_input.Bind(wx.EVT_SET_FOCUS, self.text_ctrl_placeholder_on_gain_focus) self.author_input.Bind(wx.EVT_KILL_FOCUS, self.text_ctrl_author_on_kill_focus) self.license_input.Bind(wx.EVT_KILL_FOCUS, self.text_ctrl_license_on_kill_focus) def check_input(self): """Checks if all input fields have valid flag, and changes program state if needed. Should be called after an input field updates. :return: None """ if self.ldraw_name_isvalid and self.stl_path_isvalid: if UIDriver.application_state != ApplicationState.WAITING_GO: UIDriver.fire_event(UserEvent( UserEventType.INPUT_VALID, LogMessage(LogType.IGNORE, ""))) else: if UIDriver.application_state != ApplicationState.WAITING_INPUT: UIDriver.fire_event(UserEvent( UserEventType.INPUT_INVALID, LogMessage(LogType.IGNORE, ""))) # Set colors if self.ldraw_name_isvalid: self.ldraw_name_input.SetBackgroundColour(UIStyle.metadata_input_valid_background) else: self.ldraw_name_input.SetBackgroundColour(UIStyle.metadata_input_invalid_background) if self.stl_path_isvalid: self.stl_path_input.SetBackgroundColour(UIStyle.metadata_input_valid_background) else: self.stl_path_input.SetBackgroundColour(wx.Colour(UIStyle.metadata_input_invalid_background)) def help(self, event): """Presents program limitations, common troubleshooting steps, and steps to update LDraw parts library. :param event: :return: """ help_text = UIDriver.get_assets_file_text("HELP.txt") if help_text is not None: self.popup = Popup(self.GetTopLevelParent(), "Help", help_text) else: self.popup = Popup(self.GetTopLevelParent(), "Error", "Could not read help text file, sorry.") self.help_button.Disable() self.about_button.Disable() self.popup.Show(True) self.popup.Bind(wx.EVT_CLOSE, self.popup_on_close) def about(self, event): """Presents program name, program version, copyright information, licensing information, and authors to user. :param event: :return: """ about_text = UIDriver.get_assets_file_text("ABOUT.txt") if about_text is not None: self.popup = Popup(self.GetTopLevelParent(), "About", about_text) else: self.popup = Popup(self.GetTopLevelParent(), "Error", "Could not read about text file, sorry.") self.help_button.Disable() self.about_button.Disable() self.popup.Show(True) self.popup.Bind(wx.EVT_CLOSE, self.popup_on_close) def popup_on_close(self, event): print("window closed") self.popup.Destroy() self.popup = None self.help_button.Enable() self.about_button.Enable() def browse_input(self, event): """Browse for a valid STL input file. :param event: :return: """ UIDriver.fire_event(UserEvent( UserEventType.RENDERING_CANVAS_DISABLE, LogMessage(LogType.IGNORE, ""))) stl_wildcard = "*.stl" dialog = wx.FileDialog(self, "Choose a STL file", defaultDir=self.stl_dir, wildcard=stl_wildcard, style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) if dialog.ShowModal() == wx.ID_OK: filename = dialog.GetPath() # Check for file existing # If valid, pass to worker thread who will check data if self.stl_path_text != filename: self.stl_path_input.SetValue(filename) self.stl_path_input.SetValue(MetadataPanel.reduce_text_path(self.stl_path_input.GetValue())) # Only update stuff if selection changed # Check if this .stl is valid mesh = ModelShipper.load_stl_model(filename) if mesh: # Load in LDraw object to input model ModelShipper.input_model = LDrawModel(mesh) self.stl_dir = Util.get_parent(filename) # Only the dir self.stl_path_text = filename # The whole path to file self.stl_path_isvalid = True SettingsManager.save_settings("stl_dir", self.stl_dir) UIDriver.fire_event( UserEvent(UserEventType.INPUT_MODEL_READY, LogMessage(LogType.INFORMATION, "Input file loaded from: '" + self.stl_path_text + "'."))) else: self.stl_path_isvalid = False UIDriver.fire_event( UserEvent(UserEventType.LOG_INFO, LogMessage(LogType.ERROR, "The input file '" + filename + "' is not a valid STL file."))) self.check_input() UIDriver.fire_event(UserEvent( UserEventType.RENDERING_CANVAS_ENABLE, LogMessage(LogType.IGNORE, ""))) dialog.Destroy() def text_ctrl_input_on_gain_focus(self, event): """ Return the path to the original. :param event: :return: """ if self.stl_path_text: self.stl_path_input.SetValue(self.stl_path_text) event.Skip() def text_ctrl_input_on_kill_focus(self, event): """Get the path for STL input file from user typing into TextCtrl element. :param event: :return: """ prev_text = self.stl_path_text self.stl_path_text = self.stl_path_input.GetValue() self.stl_path_input.SetValue(MetadataPanel.reduce_text_path(self.stl_path_input.GetValue())) if prev_text != self.stl_path_text: # Check file path validity if Util.is_file(self.stl_path_text): if self.stl_path_text.endswith('.stl'): # Check if this .stl is valid mesh = ModelShipper.load_stl_model(self.stl_path_text) if mesh: # Load in LDraw object to input model ModelShipper.input_model = LDrawModel(mesh) self.stl_dir = Util.get_parent(self.stl_path_text) # Only the dir SettingsManager.save_settings("stl_dir", self.stl_dir) self.stl_path_isvalid = True UIDriver.fire_event( UserEvent(UserEventType.INPUT_MODEL_READY, LogMessage(LogType.INFORMATION, "Input file loaded from: '" + self.stl_path_text + "'."))) else: self.stl_path_isvalid = False UIDriver.fire_event( UserEvent(UserEventType.LOG_INFO, LogMessage(LogType.ERROR, "The input file '" + self.stl_path_text + "' is not a valid STL file."))) else: self.stl_path_isvalid = False UIDriver.fire_event( UserEvent(UserEventType.LOG_INFO, LogMessage(LogType.ERROR, "Input file must have .stl extension."))) else: self.stl_path_isvalid = False if len(self.stl_path_text) <=0: log_msg = "Input filepath cannot be blank." else: log_msg = "The path '" + self.stl_path_text + "' could not be found." UIDriver.fire_event( UserEvent(UserEventType.LOG_INFO, LogMessage(LogType.ERROR, log_msg))) self.check_input() event.Skip() def browse_output(self, event): """Browse for a valid output file path :param event: :return: """ UIDriver.fire_event(UserEvent( UserEventType.RENDERING_CANVAS_DISABLE, LogMessage(LogType.IGNORE, ""))) dat_wildcard = "*.dat" dialog = wx.FileDialog(self, "Choose a location for the LDraw file", defaultDir=self.part_dir, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, wildcard=dat_wildcard) dialog.SetFilename(self.part_name) if dialog.ShowModal() == wx.ID_OK: pathname = dialog.GetPath() if self.out_file != pathname: # Check if part name ends with .dat, if not append that if not pathname.endswith('.dat'): pathname = pathname + '.dat' self.out_file = pathname # Full path self.part_dir = Util.get_parent(pathname) # Only the dir self.part_name = Util.get_filename(pathname) # Only filename self.ldraw_name_isvalid = True SettingsManager.save_settings("part_dir", self.part_dir) SettingsManager.save_settings("part_name", self.part_name) self.ldraw_name_input.SetValue(self.out_file) self.ldraw_name_input.SetValue(MetadataPanel.reduce_text_path(self.ldraw_name_input.GetValue())) self.check_input() UIDriver.fire_event( UserEvent(UserEventType.LOG_INFO, LogMessage(LogType.INFORMATION, "Output file will be saved as: '" + self.out_file + "'."))) UIDriver.fire_event(UserEvent( UserEventType.RENDERING_CANVAS_ENABLE, LogMessage(LogType.IGNORE, ""))) dialog.Destroy() def text_ctrl_placeholder_on_gain_focus(self, event): """Remove placeholder text and reset style if output has not been set :param event: :return: """ if not self.ldraw_name_isvalid: self.ldraw_name_input.SetValue("") event.Skip() def text_ctrl_output_on_kill_focus(self, event): """Called when the output text control loses focus. :param event: The event that occurred. :return: None. """ output_text = self.ldraw_name_input.GetValue() if len(output_text) <= 0: self.ldraw_name_input.SetValue("Browse output -->") if len(self.ldraw_name_input.GetValue()) > 0: self.ldraw_name_input.SetBackgroundColour(UIStyle.metadata_input_valid_background) UIDriver.fire_event( UserEvent(UserEventType.LOG_INFO, LogMessage(LogType.ERROR, "Output file path cannot be blank."))) event.Skip() def text_ctrl_author_on_kill_focus(self, event): """Get the author value from the user and update the settings file as needed. :param event: The event that occurred. :return: None """ author = self.author_input.GetValue() # Update settings file author info if author != self.author_text and author != "": self.author_text = author UIDriver.fire_event( UserEvent(UserEventType.LOG_INFO, LogMessage(LogType.INFORMATION, "Author changed to: " + self.author_text))) SettingsManager.save_settings("author", self.author_text) elif len(author) == 0: self.reset_author() event.Skip() def text_ctrl_license_on_kill_focus(self, event): """Get the license value from the user and update the settings file as needed.""" license_input_text = self.license_input.GetValue() # Update settings file license info if license_input_text != self.license_text and license_input_text != "": self.license_text = license_input_text UIDriver.fire_event( UserEvent(UserEventType.LOG_INFO, LogMessage(LogType.INFORMATION, "License changed to: " + self.license_text))) SettingsManager.save_settings("license", self.license_text) elif len(license_input_text) == 0: self.reset_license() event.Skip() def reset_author(self): """Fill in author field with default""" self.author_input.SetValue(self.author_default) def reset_license(self): """Fill in license field with default""" self.license_input.SetValue(self.license_default) def on_state_changed(self, new_state: ApplicationState): """A state change was passed to the MetadataPanel. :param new_state: The recorded ApplicationState. :return: None """ if new_state == ApplicationState.WAITING_GO: self.stl_path_input.Enable() self.ldraw_name_input.Enable() self.author_input.Enable() self.license_input.Enable() self.browse_output_button.Enable() self.browse_stl_button.Enable() elif new_state == ApplicationState.WORKING: self.stl_path_input.Disable() self.ldraw_name_input.Disable() self.author_input.Disable() self.license_input.Disable() self.browse_output_button.Disable() self.browse_stl_button.Disable() def on_event(self, event: UserEvent): """A user event was passed to the MetadataPanel. :param event: The recorded UserEvent. :return: None """ pass def load_settings(self): """Load settings values into memory on startup. """ # If settings file doesnt exist if not Util.is_file(SettingsManager.file_path): # If directory doesnt exist if not Util.is_dir(SettingsManager.settings_path): Util.mkdir(SettingsManager.settings_path) # Create user settings with default SettingsManager.create_settings(SettingsManager.filename) with open(SettingsManager.file_path, "r") as file: file_settings = json.load(file) self.stl_dir = file_settings["stl_dir"] self.part_name = file_settings["part_name"] self.part_dir = file_settings["part_dir"] self.author_default = file_settings["author"] self.license_default = file_settings["license"] def get_stl_path_text(self): """Return the string of the path to the input stl file. """ return self.stl_path_text def get_stl_dir(self): """Return the string of the stl directory. """ return self.stl_dir def get_out_file(self): """Return the string of the path to the output dat file. """ return self.out_file def get_part_dir(self): """Return the string of the parts directory. """ return self.part_dir def get_part_name(self): """Return string of the part name.""" return self.part_name def get_author(self): """Return the string of the author. """ return self.author_text def get_license(self): """Return the string of the license. """ return self.license_text def update(self, dt: float): """Called every loop by the GUIEventLoop :param dt: The delta time between the last call. :return: None """ pass @staticmethod def reduce_text_path(path_text): """ Reduce text length to fit the wx.textctrl box :param path_text: :return: the reduce text that is long equal or less than 64 characters.(Unless the file's name is super long) """ windows = False # Both Linux and Mac start with "/", so we could decide what kind of path is it. if path_text: if path_text[0] != "/": windows = True if windows: # Windows format. # The file path format is Root:\something\something\file.stl list_str = path_text.split("\\") length_text = MetadataPanel.list_string_length(list_str) pop = False while length_text > 60 and len(list_str) > 1: list_str.pop(0) pop = True length_text = MetadataPanel.list_string_length(list_str) text = "\\" text = text.join(list_str) if pop: text = "...\\" + text return text else: # Mac or Linux format. # The file format is /something/something/.../file.stl list_str = path_text.split("/") list_str.pop(0) length_text = MetadataPanel.list_string_length(list_str) pop = False while length_text > 59 and len(list_str) != 1: list_str.pop(0) pop = True length_text = MetadataPanel.list_string_length(list_str) text = "/" text = text.join(list_str) if pop: text = "/.../" + text else: text = "/" + text return text return path_text @staticmethod def list_string_length(list_str): """ Return the length of the path :param list_str: list of string after the list. :return: """ sum_str = 0 for a_str in list_str: sum_str += len(a_str) return len(list_str) + sum_str - 1
class OpenGLPanel(wx.Panel, IUIBehavior): """Holds wx controls relevant to controlling the program behavior for starting, stopping, pausing, and canceling the conversion process. """ def __init__(self, parent): """Default constructor for ConversionPanel class. :param parent: The parent wx object for this panel. """ wx.Panel.__init__(self, parent, size=UIStyle.opengl_panel_size, style=UIStyle.conversion_border) self.parent = parent self.stl_preview_context = True self.cb_wire_frame = None self.zoom_static_text_ctrl = None self.scale_static_text = None self.scale_up_button = None self.scale_down_button = None self.scale_input = None self.cycle_preview_button = None self.camera_rotation_static_text_ctrl = None self.camera_position_static_text_ctrl = None self.help_rotate_static_text_ctrl = None self.help_zoom_static_text_ctrl = None self.opengl_canvas = None self.timer = 0 self._build_gui() def _build_gui(self): """Initializing wx objects that make up this OpenGL panel and their layout within. :return: None """ # Create the controls. self.cb_wire_frame = wx.CheckBox(self, label=" Wireframe") self.cb_wire_frame.SetForegroundColour(UIStyle.opengl_label_color) self.zoom_static_text_ctrl = wx.StaticText(self, size=(150, 30)) self.zoom_static_text_ctrl.SetLabelText("Camera Distance to Origin: ") self.zoom_static_text_ctrl.SetForegroundColour( UIStyle.opengl_label_color) self.scale_static_text = wx.StaticText(self, label="Scale:", size=(50, 20)) self.scale_static_text.SetForegroundColour( UIStyle.metadata_label_color) self.scale_up_button = Button(self, label="+", size=(23, 23)) self.scale_down_button = Button(self, label="-", size=(23, 23)) self.scale_input = wx.lib.masked.NumCtrl(self, value=1.0, size=(100, 20), integerWidth=10, fractionWidth=10, min=0.0) self.scale_input.SetBackgroundColour(UIStyle.opengl_input_background) self.scale_input.SetForegroundColour(UIStyle.opengl_input_foreground) self.cycle_preview_button = Button(self, label="Preview LDraw Model", size=(150, 30)) self.cycle_preview_button.Disable() self.camera_rotation_static_text_ctrl = wx.StaticText(self, size=(270, 20)) self.camera_rotation_static_text_ctrl.SetLabelText("Model Rotation: ") self.camera_rotation_static_text_ctrl.SetForegroundColour( UIStyle.opengl_label_color) self.camera_position_static_text_ctrl = wx.StaticText(self, size=(270, 20)) self.camera_position_static_text_ctrl.SetLabelText("Camera Position: ") self.camera_position_static_text_ctrl.SetForegroundColour( UIStyle.opengl_label_color) self.help_rotate_static_text_ctrl = wx.StaticText(self, size=(270, 50)) self.help_rotate_static_text_ctrl.SetLabelText( "Hold left click while moving the mouse to rotate the camera.") self.help_rotate_static_text_ctrl.SetForegroundColour( UIStyle.opengl_label_color) self.help_rotate_static_text_ctrl.SetFont( wx.Font(12, wx.DECORATIVE, wx.ITALIC, wx.NORMAL)) self.help_zoom_static_text_ctrl = wx.StaticText(self, size=(270, 50)) self.help_zoom_static_text_ctrl.SetLabelText( "Use the mouse wheel to zoom the camera from the origin.") self.help_zoom_static_text_ctrl.SetForegroundColour( UIStyle.opengl_label_color) self.help_zoom_static_text_ctrl.SetFont( wx.Font(12, wx.DECORATIVE, wx.ITALIC, wx.NORMAL)) self.preview_render_context = wx.StaticText(self, size=(270, 20)) self.preview_render_context.SetLabelText("Current Preview: ") self.preview_render_context.SetForegroundColour( UIStyle.opengl_label_color) self.opengl_canvas = OpenGLCanvas(self) show = glInitGl42VERSION() # Build the layout and show the controls if correct OpenGL version self._build_layout(show) # Bind events to functions. self.Bind(wx.EVT_CHECKBOX, self.on_wire_frame_pressed, self.cb_wire_frame) self.Bind(wx.EVT_BUTTON, self.on_cycle_preview_pressed, self.cycle_preview_button) self.Bind(wx.EVT_BUTTON, self.on_scale_up, self.scale_up_button) self.Bind(wx.EVT_BUTTON, self.on_scale_down, self.scale_down_button) self.scale_input.Bind(wx.lib.masked.EVT_NUM, self.on_scale_value_changed) # Disable widgets until they are necessary from application state context. self.set_widget_rendering_contexts(False) def _build_layout(self, show: bool): """Set up how the wx controls are laid out on the log panel. :param show: Whether or not to enable all the controls of this panel. :return: """ self.cb_wire_frame.Show(show) self.zoom_static_text_ctrl.Show(show) self.scale_static_text.Show(show) self.scale_up_button.Show(show) self.scale_down_button.Show(show) self.scale_input.Show(show) self.cycle_preview_button.Show(show) self.camera_rotation_static_text_ctrl.Show(show) self.camera_position_static_text_ctrl.Show(show) self.help_rotate_static_text_ctrl.Show(show) self.help_zoom_static_text_ctrl.Show(show) self.opengl_canvas.Show(show) # Layout the UI # Left Side left_vertical_layout = wx.BoxSizer(wx.VERTICAL) left_vertical_layout.AddSpacer(10) left_vertical_layout.Add(self.cb_wire_frame, 0, wx.ALIGN_LEFT) left_vertical_layout.AddSpacer(10) left_vertical_layout.Add(self.scale_static_text, 0, wx.ALIGN_LEFT) scale_horizontal_layout = wx.BoxSizer(wx.HORIZONTAL) scale_horizontal_layout.Add(self.scale_down_button, 0, wx.ALIGN_LEFT) scale_horizontal_layout.Add(self.scale_input, 0, wx.ALIGN_LEFT) scale_horizontal_layout.Add(self.scale_up_button, 0, wx.ALIGN_LEFT) scale_horizontal_layout.AddSpacer(115) left_vertical_layout.Add(scale_horizontal_layout) left_vertical_layout.AddSpacer(10) left_vertical_layout.Add(self.cycle_preview_button) left_vertical_layout.AddSpacer(10) left_vertical_layout.Add(self.preview_render_context) horizontal_layout = wx.BoxSizer(wx.HORIZONTAL) horizontal_layout.Add(left_vertical_layout) # Middle horizontal_layout.Add(self.opengl_canvas, 0, wx.ALIGN_LEFT) # Right Side right_vertical_layout = wx.BoxSizer(wx.VERTICAL) right_vertical_layout.AddSpacer(10) right_vertical_layout.Add(self.camera_rotation_static_text_ctrl, wx.ALIGN_LEFT) right_vertical_layout.Add(self.camera_position_static_text_ctrl, wx.ALIGN_LEFT) right_vertical_layout.Add(self.zoom_static_text_ctrl, wx.ALIGN_LEFT) right_vertical_layout.AddSpacer(40) right_vertical_layout.Add(self.help_rotate_static_text_ctrl, wx.ALIGN_RIGHT) right_vertical_layout.AddSpacer(10) right_vertical_layout.Add(self.help_zoom_static_text_ctrl, wx.ALIGN_RIGHT) horizontal_layout.AddSpacer(5) horizontal_layout.Add(right_vertical_layout, 0, wx.ALIGN_RIGHT) self.SetSizer(horizontal_layout) def on_state_changed(self, new_state: ApplicationState): """A state change was passed to the ConversionPanel. :param new_state: The recorded ApplicationState. :return: None """ pass def on_event(self, event: UserEvent): """A user event was passed to the ConversionPanel. :param event: The recorded UserEvent. :return: None """ if not glInitGl42VERSION(): return if event is not None: if event.get_event_type() == UserEventType.CONVERSION_COMPLETE: self.opengl_canvas.update_meshes() self.cycle_preview_button.Enable() self.set_preview_from_context() if event.get_event_type( ) == UserEventType.RENDERING_MOUSE_WHEEL_EVENT: if self.can_use_opengl(): # Log Message here is of derived class FloatMessage. if isinstance(event.get_log_message(), FloatMessage): self.zoom_static_text_ctrl.SetLabelText( "Camera Distance to Origin: {0:0.3f}".format( event.get_log_message().get_float())) elif event.get_event_type() == UserEventType.INPUT_MODEL_READY: if self.can_use_opengl(): self.preview_render_context.SetLabelText( "Current Preview: STL Model") self.set_widget_rendering_contexts(True) self.cycle_preview_button.Disable() self.zoom_static_text_ctrl.SetLabelText( "Camera Distance to Origin: " + str(self.opengl_canvas.scene. get_camera_distance_to_origin())) def on_wire_frame_pressed(self, event): """Send an event that the wire frame button was pressed. The OpenGLCanvas will detect and react accordingly. :param event: The wxpython event that occured. :return: None """ UIDriver.fire_event( UserEvent( UserEventType.RENDERING_WIRE_FRAME_PRESSED, BoolMessage(LogType.DEBUG, "Wire frame checkbox pressed.", self.cb_wire_frame.GetValue()))) event.Skip() def on_cycle_preview_pressed(self, event): """The user pressed the cycle preview button to switch between previewing stl and ldraw model. :param event: The wxpython Event. :return: None """ self.stl_preview_context = not self.stl_preview_context self.set_preview_from_context() event.Skip() def set_preview_from_context(self): """Update the preview label and models based on our current contextual state. :return: None """ if self.stl_preview_context is True: self.cycle_preview_button.SetLabelText("Preview LDraw Model") self.opengl_canvas.set_output_preview_inactive() self.opengl_canvas.set_input_preview_active() self.preview_render_context.SetLabelText( "Current Preview: STL Model") else: self.cycle_preview_button.SetLabelText("Preview STL Model") self.opengl_canvas.set_input_preview_inactive() self.opengl_canvas.set_output_preview_active() self.preview_render_context.SetLabelText( "Current Preview: LDraw Model") def on_scale_value_changed(self, event): """The scale input value has been modified by the user. Notify the OpenGL scene of the new scale value. :param event: The wxpython Event. :return: None """ self.update_model_scale() event.Skip() def update_model_scale(self): """Update the model scale to reflect the value within the scale input control. :return: None """ self.opengl_canvas.scene.set_model_scale(self.scale_input.GetValue()) def set_widget_rendering_contexts(self, enabled): """Disable or enable the controls the user may press on the OpenGL Panel. :param enabled: Whether to enable or disable the controls. :return: None """ self.scale_down_button.Enabled = enabled self.cb_wire_frame.Enabled = enabled self.scale_input.Enabled = enabled self.cycle_preview_button.Enabled = enabled self.scale_up_button.Enabled = enabled def on_scale_up(self, event): """User pressed the scale up button. :param event: The wxpython Event. :return: None """ value = self.scale_input.GetValue() self.scale_input.SetValue(value + 0.125) event.Skip() def on_scale_down(self, event): """User pressed the scale down button. :param event: The wxpython Event. :return: None """ value = self.scale_input.GetValue() self.scale_input.SetValue(value - 0.125) event.Skip() def update(self, dt: float): """Called every loop by the GUIEventLoop :param dt: The delta time between that last call. :return: None """ self.timer += dt delay = 0.20 # Activate timer every 200 ms if self.timer > delay: self.timer = 0 if self.opengl_canvas is not None: scene = self.opengl_canvas.scene if scene is not None: camera = scene.get_main_camera() active_model = scene.get_active_model() if camera is not None and active_model is not None: rotation = active_model.transform.euler_angles position = camera.transform.position # Update the camera rotation and position metrics on screen. self.camera_rotation_static_text_ctrl.SetLabelText( "Model Rotation: [{0:0.3f}, {1:0.3f}, {2:0.3f}]". format(rotation[0], rotation[1], rotation[2])) self.camera_position_static_text_ctrl.SetLabelText( "Camera Position: [{0:0.3f}, {1:0.3f}, {2:0.3f}]". format(position[0], position[1], position[2])) def can_use_opengl(self): return self.opengl_canvas is not None and glInitGl42VERSION()
class ConversionPanel(wx.Panel, IUIBehavior): """Holds wx controls relevant to controlling the program behavior for starting, stopping, pausing, and canceling the conversion process. """ def __init__(self, parent): """Default constructor for ConversionPanel class. :param parent: The parent wx object for this panel. """ wx.Panel.__init__(self, parent, size=(1024, 30), style=UIStyle.conversion_border) self.parent = parent self.convert_button = None self.pause_button = None self.cancel_button = None self.save_button = None self.is_paused = False self._build_gui() def _build_gui(self): """Initializing wx objects that make up this conversion panel and their layout within. :return: None """ self.SetBackgroundColour(UIStyle.conversion_background_color) # Create the wx controls for this conversion panel. self.convert_button = Button(self, label="Convert to LDraw", size=UIStyle.conversion_big_button_size) self.convert_button.SetBackgroundColour(UIStyle.button_background) self.convert_button.SetForegroundColour(UIStyle.button_text) self.pause_button = Button(self, label="Pause", size=UIStyle.conversion_big_button_size) self.pause_button.SetBackgroundColour(UIStyle.button_background) self.pause_button.SetForegroundColour(UIStyle.button_text) self.cancel_button = Button(self, label="Cancel", size=UIStyle.conversion_big_button_size) self.cancel_button.SetBackgroundColour(UIStyle.button_background) self.cancel_button.SetForegroundColour(UIStyle.button_text) self.save_button = Button(self, label="Save Conversion", size=UIStyle.conversion_big_button_size) self.save_button.SetBackgroundColour(UIStyle.button_background) self.save_button.SetForegroundColour(UIStyle.button_text) # Create the layout. horizontal_layout = wx.BoxSizer(wx.HORIZONTAL) horizontal_layout.Add(self.save_button, 0, wx.ALIGN_CENTER_HORIZONTAL) horizontal_layout.AddSpacer(5) horizontal_layout.Add(self.cancel_button, 0, wx.ALIGN_CENTER_HORIZONTAL) horizontal_layout.AddSpacer(5) horizontal_layout.Add(self.pause_button, 0, wx.ALIGN_CENTER_HORIZONTAL) horizontal_layout.AddSpacer(5) horizontal_layout.Add(self.convert_button, 0, wx.ALIGN_CENTER_HORIZONTAL) vertical_layout = wx.BoxSizer(wx.VERTICAL) vertical_layout.Add(horizontal_layout, 0, wx.ALIGN_CENTER) self.SetSizer(vertical_layout) # Bind the events for each wx control. self.Bind(wx.EVT_BUTTON, self.convert, self.convert_button) self.Bind(wx.EVT_BUTTON, self.pause_resume, self.pause_button) self.Bind(wx.EVT_BUTTON, self.cancel, self.cancel_button) self.Bind(wx.EVT_BUTTON, self.save, self.save_button) def convert(self, event): """Convert the selected STL file into an LDraw file. :param event: The wx event that was recorded. :return: None """ UIDriver.fire_event( UserEvent( UserEventType.CONVERSION_STARTED, LogMessage(LogType.INFORMATION, "Conversion process started.."))) def pause_resume(self, event): """Pause/resume the conversion process. :param event: The wx event that was recorded. :return: None """ self.is_paused = not self.is_paused if self.is_paused: self.pause_button.SetLabelText('Resume') UIDriver.fire_event( UserEvent( UserEventType.CONVERSION_PAUSED, LogMessage(LogType.INFORMATION, "Conversion process paused."))) else: self.pause_button.SetLabelText('Pause') UIDriver.fire_event( UserEvent( UserEventType.CONVERSION_RESUMED, LogMessage(LogType.INFORMATION, "Conversion process resumed."))) def cancel(self, event): """Cancel the conversion operation. :param event: The wx event that was recorded. :return: None """ UIDriver.fire_event( UserEvent( UserEventType.CONVERSION_CANCELED, LogMessage(LogType.INFORMATION, "Conversion process canceled."))) def save(self, event): """Save the finalized conversion of the input file. Hide main window options and replace them with metadata options. Once the user finalizes their metadata options (back or save), they return to the original options. :param event: The wx event that was recorded. :return: None """ self.save_button.Disable() with open(SettingsManager.file_path, "r") as file: file_settings = json.load(file) part_dir = file_settings["part_dir"] part_name = file_settings["part_name"] file_path = Util.path_conversion(part_dir + "/" + part_name) with open(file_path, "w") as text_file: text_file.write(ModelShipper.get_metadata() + ModelShipper.output_data_text) self.save_button.Enable() UIDriver.fire_event( UserEvent( UserEventType.LOG_INFO, LogMessage(LogType.INFORMATION, "File was saved to '" + file_path + "'."))) def on_state_changed(self, new_state: ApplicationState): """A state change was passed to the ConversionPanel. :param new_state: The recorded ApplicationState. :return: None """ if new_state == ApplicationState.STARTUP: self.save_button.Disable() self.cancel_button.Disable() self.pause_button.Disable() self.convert_button.Disable() elif new_state == ApplicationState.WAITING_INPUT: self.convert_button.Disable() elif new_state == ApplicationState.WAITING_GO: self.convert_button.Enable() # This is a work-around for the button now showing up immediately after enabling. self.convert_button.SetLabelText( self.convert_button.GetLabelText()) self.cancel_button.Disable() self.pause_button.Disable() if self.is_paused: self.is_paused = False self.pause_button.SetLabelText('Pause') elif new_state == ApplicationState.WORKING: self.save_button.Disable() # I assume this will be enabled after self.cancel_button.Enable() self.pause_button.Enable() self.convert_button.Disable() def on_event(self, event: UserEvent): """A user event was passed to the ConversionPanel. :param event: The recorded UserEvent. :return: None """ if event.get_event_type() == UserEventType.CONVERSION_COMPLETE: self.save_button.Enable() if event.get_event_type() == UserEventType.INPUT_MODEL_READY: self.save_button.Disable() def update(self, dt: float): """Called every loop by the GUIEventLoop :param dt: The delta time between the last call. :return: None """ pass