#!/usr/bin/env python3 #################################################################################################### # Name: Autodesk Fusion 360 - Setup Wizard (Linux) # # Description: With this file you can install Autodesk Fusion 360 on Linux. # # Author: Steve Zabka # # Author URI: https://cryinkfly.com # # License: MIT # # Copyright (c) 2020-2024 # # Time/Date: 23:30/31.07.2024 # # Version: 2.0.0 # #################################################################################################### import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk, GdkPixbuf, GLib import gettext import threading import subprocess import subprocess #################################################################################################### create_structure = ''' mkdir -p {data,graphics,locale,logs,styles} curl -o data/autodesk_fusion_installer.sh -L https://raw.githubusercontent.com/cryinkfly/Autodesk-Fusion-360-for-Linux/main/files/setup/data/autodesk_fusion_installer.sh curl -o graphics/welcome.svg -L https://raw.githubusercontent.com/cryinkfly/Autodesk-Fusion-360-for-Linux/main/files/setup/resource/graphics/welcome.svg curl -o styles/fusion360-dark.css -L https://raw.githubusercontent.com/cryinkfly/Autodesk-Fusion-360-for-Linux/main/files/setup/resource/styles/fusion360-dark.css curl -o styles/fusion360-light.css -L https://raw.githubusercontent.com/cryinkfly/Autodesk-Fusion-360-for-Linux/main/files/setup/resource/styles/fusion360-light.css for lang in cs_CZ de_DE en_US es_ES fr_FR it_IT ja_JP ko_KR pl_PL pt_BR tr_TR zh_CN zh_TW; do mkdir -p locale/$lang/LC_MESSAGES # Download the .po files curl -o locale/$lang/LC_MESSAGES/autodesk_fusion.po -L https://raw.githubusercontent.com/cryinkfly/Autodesk-Fusion-360-for-Linux/main/files/setup/locale/$lang/LC_MESSAGES/autodesk_fusion.po done curl -o locale/update-locale.sh -L https://raw.githubusercontent.com/cryinkfly/Autodesk-Fusion-360-for-Linux/main/files/setup/locale/update-locale.sh chmod +x locale/update-locale.sh chmod +x data/autodesk_fusion_installer.sh echo "The Autodesk Fusion 360 installer client has been downloaded successfully!" # Run update-locale.sh in an extra terminal to update the locale files and close the terminal after the update source ./locale/update-locale.sh ''' subprocess.call(create_structure, shell=True) #################################################################################################### # Set the locale directory for gettext (/locale/en_US/LC_MESSAGES/messages.mo, /locale/de_DE/LC_MESSAGES/messages.mo, ...) language_domain = 'autodesk_fusion' # Your gettext domain translation_directory = 'locale' # Directory containing your .mo files default_language = 'en_US' # Default language languages = { # Mapping of language names to locale names "English - English": 'en_US', "German - Deutsch": 'de_DE', "Czech - Čeština": 'cs_CZ', "French - Français": 'fr_FR', "Italian - Italiano": 'it_IT', "Japanese - 日本語": 'ja_JP', "Korean - 한국인": 'ko_KR', "Polish - Polski": 'pl_PL', "Portuguese - Português": 'pt_BR', "Simplified Chinese - 简体中文": 'zh_CN', "Spanish - Español": 'es_ES', "Traditional Chinese - 中國人": 'zh_TW', "Turkish - Türkçe": 'tr_TR' } # Set the locale based on the default language subprocess.run(['export', f'LANGUAGE={default_language}'], shell=True) gettext.translation(language_domain, translation_directory, languages=[default_language]).install() #################################################################################################### # Function to determine if a color is dark def is_dark(color): # Calculate the luminance of the color r, g, b = color.red, color.green, color.blue luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b # Return True if luminance is less than a threshold, else False return luminance < 0.5 # Get the current Gtk settings settings = Gtk.Settings.get_default() # Get the current window window = Gtk.Window() # Get the background color of the window context = window.get_style_context() background_color = context.get_background_color(Gtk.StateFlags.NORMAL) # Check if the background color is dark is_dark_theme = is_dark(background_color) # Choose the appropriate CSS file based on the theme css_filename = "fusion360-dark.css" if is_dark_theme else "fusion360-light.css" # Get the directory of the current script script_dir = subprocess.run(['dirname', subprocess.run(['realpath', '--', __file__], capture_output=True, text=True).stdout.strip()], capture_output=True, text=True).stdout.strip() # Construct the path to the CSS file css_path = subprocess.run(['printf', '%s/styles/%s', script_dir, css_filename], capture_output=True, text=True).stdout.strip() # Check if the CSS file exists css_file_exists = subprocess.run(['test', '-f', css_path], capture_output=True).returncode == 0 if css_file_exists: # Load the CSS file style_provider = Gtk.CssProvider() style_provider.load_from_path(css_path) # Add the style provider to the default screen Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) else: print(f"CSS file '{css_filename}' not found in '{css_path}'") #################################################################################################### class LanguageSelectionWindow(Gtk.Window): def __init__(self): Gtk.Window.__init__(self, title=_("Fusion 360 - Linux Installer")) self.set_border_width(35) self.set_position(Gtk.WindowPosition.CENTER) self.set_resizable(False) self.selected_language = None # Close button in the top-left corner self.close_button_label = _("Close") self.close_button = Gtk.Button(label=self.close_button_label) self.close_button.connect("clicked", self.on_close_clicked) # Next button in the top-right corner self.next_button_label = _("Next") self.next_button = Gtk.Button(label=self.next_button_label) self.next_button.connect("clicked", self.on_next_clicked) # Header-Bar Configuration header_bar = Gtk.HeaderBar() header_bar.props.title = _("Fusion 360 - Linux Installer") header_bar.pack_start(self.close_button) header_bar.pack_end(self.next_button) self.set_titlebar(header_bar) svg_file_path = "graphics/welcome.svg" pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(svg_file_path, 100, 150) # Create an image widget and set the Pixbuf image = Gtk.Image.new_from_pixbuf(pixbuf) # Create a label widget for title self.title_label = Gtk.Label() self.title_label.set_markup('' + _('Welcome') + '') # Read languages from file self.languages = list(languages.keys()) language_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) language_box.set_name("box") # Set the name for CSS styling # Create a ListBox to display the languages self.listbox = Gtk.ListBox() self.listbox.set_selection_mode(Gtk.SelectionMode.SINGLE) for language in self.languages: row = Gtk.ListBoxRow() hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50) label = Gtk.Label(label=language) hbox.pack_start(label, True, True, 0) row.add(hbox) self.listbox.add(row) # Add the ListBox to a ScrolledWindow scrolled_window = Gtk.ScrolledWindow() scrolled_window.add(self.listbox) scrolled_window.set_size_request(300, 200) language_box.pack_start(scrolled_window, True, True, 0) # Create a VBox to organize widgets vertically vbox = Gtk.VBox(spacing=10) vbox.pack_start(image, False, False, 5) vbox.pack_start(self.title_label, False, False, 0) vbox.pack_start(language_box, True, True, 0) # Add the VBox to the window self.add(vbox) # Connect the "row-activated" signal to the callback function self.listbox.connect("row-activated", self.on_language_selected) def on_language_selected(self, listbox, row): selected_language = self.languages[row.get_index()] locale_name = languages[selected_language] print(f"Selected language: {selected_language} ({locale_name})") # Set the locale based on the selected language subprocess.run(['export', f'LANGUAGE={locale_name}'], shell=True) gettext.translation(language_domain, translation_directory, languages=[locale_name]).install() # Refresh buttons to update the language self.close_button.set_label(_("Close")) self.next_button.set_label(_("Next")) # Refresh the window to update the language self.title_label.set_markup('' + _('Welcome') + '') # Update the window title self.set_title(_("Fusion 360 - Linux Installer")) def on_next_clicked(self, button): row = self.listbox.get_selected_row() if row: selected_language = self.languages[row.get_index()] self.selected_language = selected_language print("Next button clicked") installation_license_window = LicenseAgreementWindow(selected_language) installation_license_window.connect("destroy", Gtk.main_quit) installation_license_window.show_all() self.hide() def on_close_clicked(self, button): print("Close button clicked") Gtk.main_quit() #################################################################################################### # create class like as InstallationOptionsWindow for LicenseAgreementWindow class LicenseAgreementWindow(Gtk.Window): def __init__(self, selected_language): Gtk.Window.__init__(self, title=_("Fusion 360 - Linux Installer")) self.set_border_width(35) self.set_position(Gtk.WindowPosition.CENTER) self.set_resizable(False) self.connect('destroy', Gtk.main_quit) self.selected_language = selected_language # Previous button in the top-left corner self.previous_button_label = _("Previous") self.previous_button = Gtk.Button(label=self.previous_button_label) self.previous_button.connect("clicked", self.on_previous_clicked) # Next button in the top-right corner self.next_button_label = _("Next") self.next_button = Gtk.Button(label=self.next_button_label) self.next_button.connect("clicked", self.on_next_clicked) self.next_button.set_sensitive(False) # Disable the next button by default # Header-Bar Configuration header_bar = Gtk.HeaderBar() header_bar.props.title = _("Fusion 360 - Linux Installer") header_bar.pack_start(self.previous_button) header_bar.pack_end(self.next_button) self.set_titlebar(header_bar) # Create a label for the installation options. self.title_label = Gtk.Label() self.title_label.set_markup('' + _('License Agreement') + '') # Create a box to hold the radio buttons license_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) license_box.set_halign(Gtk.Align.CENTER) # Center align the radio_box license_box_1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) license_box_1.set_name("box") # Set the name for CSS styling # Add the ListBox to a ScrolledWindow license_text_scrolled_window = Gtk.ScrolledWindow() #set the size of the ScrolledWindow license_text_scrolled_window.set_size_request(600, 300) # Create a box to show the license agreement text license_text = Gtk.Label() license_text.set_markup('' + _("LICENSE AGREEMENT\n\n" "Name: Autodesk Fusion 360 - Setup Wizard (Linux)\n" "Description: With this Setup Wizard you can install Autodesk Fusion 360 on various Linux distributions.\n" "Author: Steve Zabka\n" "Author URI: https://cryinkfly.com\n" "License: MIT\n" "Copyright (c) 2020-2024\n\n" "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the „Software“), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\n" "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\n" "THE SOFTWARE IS PROVIDED „AS IS“, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.") + '') license_text.set_line_wrap(True) license_text.set_max_width_chars(50) license_text.set_name("box-text") license_box_2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) # Create a checkbox for the license agreement license_agreement = Gtk.CheckButton.new_with_label(_('I have read and agree to the License Agreement.')) license_agreement.set_name("box-button") # Set the name for CSS styling license_agreement.set_active(False) license_agreement.connect("toggled", self.on_license_agreement_toggled) license_text_scrolled_window.add(license_text) license_box_1.pack_start(license_text_scrolled_window, False, False, 0) license_box_2.pack_start(license_agreement, False, False, 0) license_box.pack_start(license_box_1, False, False, 3) license_box.pack_start(license_box_2, False, False, 3) # Create a VBox to organize widgets vertically vbox = Gtk.VBox(spacing=10) vbox.pack_start(self.title_label, False, False, 0) vbox.pack_start(license_text, False, False, 0) vbox.pack_start(license_box, False, False, 0) # Add the VBox to the window self.add(vbox) def on_previous_clicked(self, button): print("Previous button clicked") subprocess.run(['export', f'LANGUAGE={default_language}'], shell=True) gettext.translation(language_domain, translation_directory, languages=[default_language]).install() language_selection_window = LanguageSelectionWindow() language_selection_window.connect("destroy", Gtk.main_quit) language_selection_window.show_all() self.hide() def on_license_agreement_toggled(self, button): if button.get_active(): self.license_agreement_accepted = True self.next_button.set_sensitive(True) # Enable the next button else: self.license_agreement_accepted = False self.next_button.set_sensitive(False) # Disable the next button def on_next_clicked(self, button): print("Next button clicked") installation_options_selection_window = InstallationOptionsWindow() installation_options_selection_window.connect("destroy", Gtk.main_quit) installation_options_selection_window.show_all() self.hide() #################################################################################################### class InstallationOptionsWindow(Gtk.Window): def __init__(self): Gtk.Window.__init__(self, title=_("Fusion 360 - Linux Installer")) self.set_border_width(35) self.set_position(Gtk.WindowPosition.CENTER) self.set_resizable(False) self.connect('destroy', Gtk.main_quit) # Previous button in the top-left corner self.previous_button_label = _("Previous") self.previous_button = Gtk.Button(label=self.previous_button_label) self.previous_button.connect("clicked", self.on_previous_clicked) # Next button in the top-right corner self.next_button_label = _("Next") self.next_button = Gtk.Button(label=self.next_button_label) self.next_button.connect("clicked", self.on_next_clicked) # Header-Bar Configuration header_bar = Gtk.HeaderBar() header_bar.props.title = _("Fusion 360 - Linux Installer") header_bar.pack_start(self.previous_button) header_bar.pack_end(self.next_button) self.set_titlebar(header_bar) # Create a label for the installation options. self.title_label = Gtk.Label() self.title_label.set_markup('' + _('Select the installation type:') + '') # Create a box to hold the radio buttons radio_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) radio_box.set_halign(Gtk.Align.CENTER) # Center align the radio_box radio_box_1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) radio_box_1.set_name("box") # Set the name for CSS styling # Create a radio button for the express installation. express_installation = Gtk.RadioButton.new_with_label_from_widget(None, _('Basic')) express_installation.set_name("box-button") # Set the name for CSS styling express_installation.set_active(True) express_installation.connect("toggled", self.on_installation_type_toggled) express_installation_label = Gtk.Label() express_installation_label.set_markup('' + _('Select the Basic installation type for a seamless setup process. This option offers a default configuration, simplifying installation and minimizing setup time. Get Fusion 360 up and running quickly, allowing you to focus on your projects without unnecessary delays.') + '') express_installation_label.set_line_wrap(True) express_installation_label.set_max_width_chars(50) express_installation_label.set_name("box-button-text") express_installation_label.set_xalign(0) radio_box_2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) radio_box_2.set_name("box") # Set the name for CSS styling # Create a radio button for the full installation. full_installation = Gtk.RadioButton.new_with_label_from_widget(express_installation, _('Complete')) full_installation.set_name("box-button") # Set the name for CSS styling full_installation.set_active(False) # Ensure it is not selected full_installation.connect("toggled", self.on_installation_type_toggled) full_installation_label = Gtk.Label() full_installation_label.set_markup('' + _('Select the Complete installation type to seamlessly install Fusion 360 along with various extensions like OctoPrint for Autodesk® Fusion 360™. These extensions are Linux-tested for optimal compatibility and performance, ensuring a smooth and efficient experience.') + '') full_installation_label.set_line_wrap(True) full_installation_label.set_max_width_chars(50) full_installation_label.set_name("box-button-text") # Set the name for CSS styling full_installation_label.set_xalign(0) radio_box_3 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) radio_box_3.set_name("box") # Set the name for CSS styling # Create a radio button for the custom installation. custom_installation = Gtk.RadioButton.new_with_label_from_widget(express_installation, _('Custom')) custom_installation.set_name("box-button") # Set the name for CSS styling custom_installation.set_active(False) # Ensure it is not selected custom_installation.connect("toggled", self.on_installation_type_toggled) custom_installation_label = Gtk.Label() custom_installation_label.set_markup('' + _('Select the Custom installation type to personalize your Fusion 360 setup on Linux. Adjust settings such as the installation directory and select verified extensions for Fusion 360. Although it is more detailed, it ensures a custom configuration that meets your needs.') + '') custom_installation_label.set_line_wrap(True) custom_installation_label.set_max_width_chars(50) custom_installation_label.set_name("box-button-text") # Set the name for CSS styling custom_installation_label.set_xalign(0) # Add the radio buttons to the radio boxes radio_box_1.pack_start(express_installation, False, False, 0) radio_box_1.pack_start(express_installation_label, False, False, 0) radio_box_2.pack_start(full_installation, False, False, 0) radio_box_2.pack_start(full_installation_label, True, False, 0) radio_box_3.pack_start(custom_installation, False, False, 0) radio_box_3.pack_start(custom_installation_label, False, False, 0) radio_box.pack_start(radio_box_1, False, False, 3) radio_box.pack_start(radio_box_2, False, False, 3) radio_box.pack_start(radio_box_3, False, False, 3) # Create a VBox to organize widgets vertically vbox = Gtk.VBox(spacing=10) # Assume self.title_label is already defined somewhere in your class vbox.pack_start(self.title_label, False, False, 0) vbox.pack_start(radio_box, False, False, 0) # Add the VBox to the window self.add(vbox) def on_installation_type_toggled(self, button): if button.get_active(): self.selected_installation_type = button.get_label() def on_previous_clicked(self, button): print("Previous button clicked") selected_language = LanguageSelectionWindow().selected_language installation_license_window = LicenseAgreementWindow(selected_language) installation_license_window.connect("destroy", Gtk.main_quit) installation_license_window.show_all() self.hide() def on_next_clicked(self, button): print("Next button clicked") if hasattr(self, 'selected_installation_type') and self.selected_installation_type == _('Complete'): print(_("Complete installation type selected")) home_directory = subprocess.check_output("echo $HOME", shell=True).decode().strip() selected_directory = home_directory + "/.fusion360" print("Directory selected: " + selected_directory) selected_extensions = ["CzechlocalizationforF360,HP3DPrintersforAutodesk®Fusion®,MarkforgedforAutodesk®Fusion®,OctoPrintforAutodesk®Fusion360™,UltimakerDigitalFactoryforAutodeskFusion360™"] print("Extensions selected: " + str(selected_extensions)) installation_progress_window = InstallationProgressWindow(selected_directory, selected_extensions) installation_progress_window.connect("destroy", Gtk.main_quit) installation_progress_window.show_all() self.hide() elif hasattr(self, 'selected_installation_type') and self.selected_installation_type == _('Custom'): print(_("Custom installation type selected")) directory_selection_window = InstallationDirectoryWindow() directory_selection_window.connect("destroy", Gtk.main_quit) directory_selection_window.show_all() self.hide() else: print(_("Basic installation type selected")) home_directory = subprocess.check_output("echo $HOME", shell=True).decode().strip() selected_directory = home_directory + "/.fusion360" print("Directory selected: " + selected_directory) selected_extensions = [] print("Extensions selected: " + str(selected_extensions)) installation_progress_window = InstallationProgressWindow(selected_directory, selected_extensions) installation_progress_window.connect("destroy", Gtk.main_quit) installation_progress_window.show_all() self.hide() #################################################################################################### class InstallationDirectoryWindow(Gtk.Window): def __init__(self): Gtk.Window.__init__(self, title=_("Fusion 360 - Linux Installer")) self.set_border_width(35) self.set_position(Gtk.WindowPosition.CENTER) self.set_resizable(False) self.connect('destroy', Gtk.main_quit) # Previous button in the top-left corner self.previous_button_label = _("Previous") self.previous_button = Gtk.Button(label=self.previous_button_label) self.previous_button.connect("clicked", self.on_previous_clicked) # Next button in the top-right corner self.next_button_label = _("Next") self.next_button = Gtk.Button(label=self.next_button_label) self.next_button.connect("clicked", self.on_next_clicked) # Header-Bar Configuration header_bar = Gtk.HeaderBar() header_bar.props.title = _("Fusion 360 - Linux Installer") header_bar.pack_start(self.previous_button) header_bar.pack_end(self.next_button) self.set_titlebar(header_bar) # Create a label for the installation options. self.title_label = Gtk.Label() self.title_label.set_markup('' + _('Select the installation directory:') + '') self.title_label.set_line_wrap(True) self.title_label.set_max_width_chars(50) directory_box_1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) directory_box_1.set_halign(Gtk.Align.CENTER) directory_box_1.set_name("box") # Set the name for CSS styling # Create Label for description self.description_label = Gtk.Label() self.description_label.set_markup('' + _("By default, Fusion 360 installs in your home directory, but if you prefer a " "different location, you can specify the desired installation directory here. \n\n" "This allows for better organization and management of your software installations, especially " "if you have multiple users or limited space in your home directory.") + '') self.description_label.set_line_wrap(True) self.description_label.set_max_width_chars(50) self.description_label.set_name("box-text") # Get the current user's home directory home_directory = subprocess.check_output("echo $HOME", shell=True).decode().strip() directory_box_2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) directory_box_2.set_halign(Gtk.Align.CENTER) # Create a button to browse for the installation directory self.browse_button = Gtk.Button(label=_("Browse")) self.browse_button.set_name("box-button") # Set the name for CSS styling self.browse_button.connect("clicked", self.on_browse_clicked) self.browse_button.set_hexpand(False) # Set the button to not expand horizontally self.browse_button.set_halign(Gtk.Align.CENTER) # Center align the button horizontally # Create an entry field for the installation directory self.installation_directory_entry = Gtk.Entry() self.installation_directory_entry.set_name("box-button") # Set the name for CSS styling self.installation_directory_entry.set_text(home_directory + "/.fusion360") self.installation_directory_entry.set_hexpand(False) self.installation_directory_entry.set_halign(Gtk.Align.CENTER) # Create a refesh button to reset the installation directory to the default with a symbolic icon self.refresh_button = Gtk.Button() self.refresh_button.set_image(Gtk.Image.new_from_icon_name("view-refresh-symbolic", Gtk.IconSize.BUTTON)) self.refresh_button.set_tooltip_text(_("Reset to default")) self.refresh_button.connect("clicked", self.on_refresh_clicked) self.refresh_button.set_hexpand(False) # Set the button to not expand horizontally # Minimum width of the entry field self.installation_directory_entry.set_size_request(300, 0) # Align the entry field content to the center self.installation_directory_entry.set_alignment(0.5) # Entry not selected by default self.installation_directory_entry.set_can_focus(False) directory_box_1.pack_start(self.description_label, False, False, 10) directory_box_2.pack_start(self.browse_button, False, False, 5) directory_box_2.pack_start(self.installation_directory_entry, False, False, 5) directory_box_2.pack_start(self.refresh_button, False, False, 5) # Create a VBox to organize widgets vertically vbox = Gtk.VBox(spacing=10) vbox.pack_start(self.title_label, False, False, 0) vbox.pack_start(directory_box_1, False, False, 10) vbox.pack_start(directory_box_2, False, False, 0) # Add the VBox to the window self.add(vbox) def on_browse_clicked(self, button): print("Browse button clicked") # Open a file chooser dialog to select the installation directory dialog = Gtk.FileChooserDialog(title=_("Select the installation directory"), parent=self, action=Gtk.FileChooserAction.SELECT_FOLDER) dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK) response = dialog.run() if response == Gtk.ResponseType.OK: print("Select clicked") directory = dialog.get_filename() # Refresh the selected directory in the entry field self.installation_directory_entry.set_text(directory) # Save the selected directory for later use and if the user goes back from the InstallationExtensionsWindow to this window self.selected_directory = directory elif response == Gtk.ResponseType.CANCEL: print("Cancel clicked") dialog.destroy() def on_refresh_clicked(self, button): print("Refresh button clicked") # Get the current user's home directory home_directory = subprocess.check_output("echo $HOME", shell=True).decode().strip() self.installation_directory_entry.set_text(home_directory + "/.fusion360") def on_previous_clicked(self, button): print("Previous button clicked") installation_options_selection_window = InstallationOptionsWindow() installation_options_selection_window.connect("destroy", Gtk.main_quit) installation_options_selection_window.show_all() self.hide() def on_next_clicked(self, button): print("Next button clicked") selected_directory = self.installation_directory_entry.get_text() print("Directory selected: " + selected_directory) extensions_selection_window = InstallationExtensionsWindow(selected_directory) extensions_selection_window.connect("destroy", Gtk.main_quit) extensions_selection_window.show_all() self.hide() #################################################################################################### class InstallationExtensionsWindow(Gtk.Window): def __init__(self, selected_directory): Gtk.Window.__init__(self, title=_("Fusion 360 - Linux Installer")) self.set_default_size(650, 450) self.set_border_width(35) self.set_position(Gtk.WindowPosition.CENTER) self.set_resizable(False) self.connect('destroy', Gtk.main_quit) self.selected_directory = selected_directory # Previous button in the top-left corner self.previous_button_label = _("Previous") self.previous_button = Gtk.Button(label=self.previous_button_label) self.previous_button.connect("clicked", self.on_previous_clicked) # Next button in the top-right corner self.next_button_label = _("Next") self.next_button = Gtk.Button(label=self.next_button_label) self.next_button.connect("clicked", self.on_next_clicked) # Header-Bar Configuration header_bar = Gtk.HeaderBar() header_bar.props.title = _("Fusion 360 - Linux Installer") header_bar.pack_start(self.previous_button) header_bar.pack_end(self.next_button) self.set_titlebar(header_bar) # Create a label for the installation options. self.title_label = Gtk.Label() self.title_label.set_markup('' + _('Please select the extensions.') + '') # Create a scrolled window where the user can select the extensions to install with Fusion 360 on Linux extensions_scrolled_window = Gtk.ScrolledWindow() extensions_scrolled_window.set_size_request(600, 300) # Create a box to hold the extensions extensions_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) extensions_box.set_halign(Gtk.Align.CENTER) # List of extensions with their descriptions extensions = [ (_('Czech localization for F360'), _('With the help of this free extension, the user can also use the Czech language in Fusion 360.')), (_('HP 3D Printers for Autodesk® Fusion®'), _('This extension allows you to connect Autodesk Fusion 360 with HP SmartStream software and send jobs directly to the HP software.')), (_('Markforged for Autodesk® Fusion®'), _('This extension allows you to connect Autodesk Fusion 360 with Markforged software (https://www.eiger.io).')), (_('OctoPrint for Autodesk® Fusion 360™'), _('With this extension you can send the G-code of your created 3D models directly to the OctoPrint server via Autodesk Fusion 360.')), (_('Ultimaker Digital Factory for Autodesk Fusion 360™'), _('This is a connector between Autodesk® Fusion 360™ and the Ultimaker Digital Factory site and its services.')) ] self.extension_checkbuttons = [] # Create check buttons with labels for each extension for ext_name, ext_description in extensions: box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) box.set_name("box") check_button = Gtk.CheckButton.new_with_label(ext_name) check_button.set_name("box-button") check_button.set_active(False) self.extension_checkbuttons.append(check_button) label = Gtk.Label() label.set_markup('' + ext_description + '') label.set_line_wrap(True) label.set_max_width_chars(50) label.set_name("box-button-text") label.set_xalign(0) box.pack_start(check_button, False, False, 0) box.pack_start(label, False, False, 0) extensions_box.pack_start(box, False, False, 3) extensions_scrolled_window.add(extensions_box) # Create a VBox to organize widgets vertically vbox = Gtk.VBox(spacing=10) vbox.pack_start(self.title_label, False, False, 0) vbox.pack_start(extensions_scrolled_window, False, False, 0) # Add the VBox to the window self.add(vbox) def on_previous_clicked(self, button): print("Previous button clicked") directory_selection_window = InstallationDirectoryWindow() directory_selection_window.connect("destroy", Gtk.main_quit) directory_selection_window.show_all() self.hide() def on_next_clicked(self, button): print("Next button clicked") self.selected_extensions = [cb.get_label() for cb in self.extension_checkbuttons if cb.get_active()] installation_progress_window = InstallationProgressWindow(self.selected_directory, self.selected_extensions) installation_progress_window.connect("destroy", Gtk.main_quit) installation_progress_window.show_all() self.hide() #################################################################################################### class InstallationProgressWindow(Gtk.Window): def __init__(self, selected_directory, selected_extensions): Gtk.Window.__init__(self, title=_("Fusion 360 - Linux Installer")) self.set_default_size(650, 450) self.set_border_width(35) self.set_position(Gtk.WindowPosition.CENTER) self.set_resizable(False) self.connect('destroy', Gtk.main_quit) print("Selected directory: " + selected_directory) print("Selected extensions: " + str(selected_extensions)) self.selected_extensions = selected_extensions self.selected_directory = selected_directory # Abort button in the top-left corner self.abort_button_label = _("Abort") self.abort_button = Gtk.Button(label=self.abort_button_label) self.abort_button.connect("clicked", self.on_abort_clicked) # Next button in the top-right corner self.next_button_label = _("Next") self.next_button = Gtk.Button(label=self.next_button_label) self.next_button.connect("clicked", self.on_next_clicked) self.next_button.set_sensitive(False) # Deactivate the Next button # Header-Bar Configuration header_bar = Gtk.HeaderBar() header_bar.props.title = _("Fusion 360 - Linux Installer") header_bar.pack_start(self.abort_button) header_bar.pack_end(self.next_button) self.set_titlebar(header_bar) # Create a label for the installation options self.title_label = Gtk.Label() self.title_label.set_markup('Please wait ...') # Create a vertical box to contain progress boxes self.main_progresses_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) self.main_progresses_vbox.set_halign(Gtk.Align.CENTER) # Create progress boxes with updated descriptions self.create_progress_box(_("Step 1: Preparing files for installation ..."), "install_part1") self.create_progress_box(_("Step 2: Setting up Wine for installation ..."), "install_part2") self.create_progress_box(_("Step 3: Installing Autodesk Fusion ..."), "install_part3") self.create_progress_box(_("Step 4: Installing Autodesk Fusion extensions ..."), "install_part4") self.create_progress_box(_("Step 5: Completing the installation ..."), "install_part5") # Create a VBox to organize widgets vertically vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) vbox.pack_start(self.title_label, False, False, 30) vbox.pack_start(self.main_progresses_vbox, False, False, 0) # Add the VBox to the window self.add(vbox) # Start the installation process self.run_installation_part1() def create_progress_box(self, label_text, name): # Create a horizontal box for the installation progress progress_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) progress_box.set_halign(Gtk.Align.CENTER) progress_box.set_name(name) # Set the name for the progress box # Create a label for the installation progress progress_label = Gtk.Label() progress_label.set_markup('' + label_text + '') progress_label.set_line_wrap(True) progress_label.set_max_width_chars(50) progress_label.set_name("box-button-text") progress_label.set_xalign(0) # Create a stack to hold the spinner and the completed icon stack = Gtk.Stack() stack.set_name(name + "_stack") # Create a spinner for the installation progress spinner = Gtk.Spinner() spinner.start() stack.add_named(spinner, "spinner") # Create a completed icon for the installation progress icon = Gtk.Image.new_from_icon_name("emblem-default", Gtk.IconSize.BUTTON) stack.add_named(icon, "icon") # Add the label and stack to the progress box progress_box.pack_start(progress_label, False, False, 0) progress_box.pack_start(stack, False, False, 0) # Add the progress box to the main progress VBox self.main_progresses_vbox.pack_start(progress_box, False, False, 3) def run_installation_part1(self): threading.Thread(target=self.run_command, args=("install_part1",)).start() def run_installation_part2(self): threading.Thread(target=self.run_command, args=("install_part2",)).start() def run_installation_part3(self): threading.Thread(target=self.run_command, args=("install_part3",)).start() def run_installation_part4(self): threading.Thread(target=self.run_command, args=("install_part4",)).start() def run_installation_part5(self): threading.Thread(target=self.run_command, args=("install_part5",)).start() # Activate the Next button self.next_button.set_sensitive(True) # Deactivate the Abort button self.abort_button.set_sensitive(False) # GLib use 5 seconds to run the function on_next_clicked GLib.timeout_add_seconds(5, self.on_next_clicked, self.next_button) def run_command(self, part_name): argument1 = part_name argument2 = self.selected_directory argument3 = str(self.selected_extensions).replace(" ", "").replace("[", "").replace("]", "").replace("'", "") command = f"./data/autodesk_fusion_installer.sh {argument1} {argument2} {argument3}" subprocess.call(command, shell=True) # Update GUI from the main thread GLib.idle_add(self.complete_part, part_name, self.get_next_function(part_name)) def get_next_function(self, part_name): functions = { "install_part1": self.run_installation_part2, "install_part2": self.run_installation_part3, "install_part3": self.run_installation_part4, "install_part4": self.run_installation_part5 } return functions.get(part_name, None) def complete_part(self, part_name, next_function=None): print(f"Completing {part_name}") # Debugging line # Get the progress box by its name progress_box = next((child for child in self.main_progresses_vbox.get_children() if child.get_name() == part_name), None) if progress_box is None: print(f"Error: No progress box found with name {part_name}") return stack = progress_box.get_children()[1] # Stop the spinner and switch to the completed icon spinner = stack.get_child_by_name("spinner") spinner.stop() stack.set_visible_child_name("icon") # Call the next installation part function, if provided if next_function: next_function() def on_abort_clicked(self, button): print("Abort button clicked") threading.Thread(target=self.run_command, args=("abort",)).start() Gtk.main_quit() def on_next_clicked(self, button): print("Next button clicked") installation_progress_solved_window = InstallationSolvedWindow() installation_progress_solved_window.connect("destroy", Gtk.main_quit) installation_progress_solved_window.show_all() self.hide() #################################################################################################### class InstallationSolvedWindow(Gtk.Window): def __init__(self): Gtk.Window.__init__(self, title=_("Fusion 360 - Linux Installer")) self.set_default_size(400, 150) self.set_border_width(35) self.set_position(Gtk.WindowPosition.CENTER) self.set_resizable(False) self.connect('destroy', Gtk.main_quit) # Header-Bar Configuration header_bar = Gtk.HeaderBar() self.set_titlebar(header_bar) # Create a label for the installation options. self.title_label = Gtk.Label() self.title_label.set_markup('' + _('You are all set!') + '') # Create a label for thank you message self.thank_you_label = Gtk.Label() self.thank_you_label.set_markup(_('' + _('Thank you for installing Fusion 360 on Linux.') + '')) self.thank_you_label.set_line_wrap(True) self.thank_you_label.set_max_width_chars(10) # Create a Button to close the application self.close_button_label = _("Start using Fusion 360") self.close_button = Gtk.Button(label=self.close_button_label) self.close_button.set_hexpand(False) # Set the button to not expand horizontally self.close_button.set_halign(Gtk.Align.CENTER) # Center align the button horizontally self.close_button.connect("clicked", self.on_close_clicked) # Create a VBox to organize widgets vertically vbox = Gtk.VBox(spacing=10) vbox.pack_start(self.title_label, False, False, 0) vbox.pack_start(self.thank_you_label, False, False, 0) vbox.pack_start(self.close_button, False, False, 0) # Add the VBox to the window self.add(vbox) def on_close_clicked(self, button): print("Close button clicked") # Run a linux command to start Fusion 360 and close this python application in the same time subprocess.run(["xdg-open", "https://cryinkfly.com/sponsoring/"]) Gtk.main_quit() # Go to the next window ... (Select the extensions) #################################################################################################### # Run the application #################################################################################################### # Open the language selection window. language_selection_window = LanguageSelectionWindow() language_selection_window.connect("destroy", Gtk.main_quit) language_selection_window.show_all() Gtk.main()