def is_installed(self): if self.on_git and not self.src_filename: return False return (os.path.exists( os.path.join(FreeCAD.getUserMacroDir(True), self.filename)) or os.path.exists( os.path.join(FreeCAD.getUserMacroDir(True), "Macro_" + self.filename)))
def __init__(self): commands_dialog = gui.PySideUic.loadUi( app.getUserMacroDir(True) + '/MultiCopy/resources/MultiCopy_Commands_Dialog.ui' ) commands_dialog.findChild(QPushButton, 'okay_button').clicked.connect( lambda: commands_dialog.done(1) ) commands_dialog.setWindowIcon( QIcon(app.getUserMacroDir(True) + 'MultiCopy.svg') ) commands_dialog.exec_()
def check_macro(self, macro_wrapper: Addon) -> None: """Check to see if the online copy of the macro's code differs from the local copy.""" # Make sure this macro has its code downloaded: try: if not macro_wrapper.macro.parsed and macro_wrapper.macro.on_git: macro_wrapper.macro.fill_details_from_file( macro_wrapper.macro.src_filename ) elif not macro_wrapper.macro.parsed and macro_wrapper.macro.on_wiki: mac = macro_wrapper.macro.name.replace(" ", "_") mac = mac.replace("&", "%26") mac = mac.replace("+", "%2B") url = "https://wiki.freecad.org/Macro_" + mac macro_wrapper.macro.fill_details_from_wiki(url) except Exception: FreeCAD.Console.PrintWarning( translate( "AddonsInstaller", "Failed to fetch code for macro '{name}'", ).format(name=macro_wrapper.macro.name) + "\n" ) macro_wrapper.set_status(Addon.Status.CANNOT_CHECK) return hasher1 = hashlib.sha1() hasher2 = hashlib.sha1() hasher1.update(macro_wrapper.macro.code.encode("utf-8")) new_sha1 = hasher1.hexdigest() test_file_one = os.path.join( FreeCAD.getUserMacroDir(True), macro_wrapper.macro.filename ) test_file_two = os.path.join( FreeCAD.getUserMacroDir(True), "Macro_" + macro_wrapper.macro.filename ) if os.path.exists(test_file_one): with open(test_file_one, "rb") as f: contents = f.read() hasher2.update(contents) old_sha1 = hasher2.hexdigest() elif os.path.exists(test_file_two): with open(test_file_two, "rb") as f: contents = f.read() hasher2.update(contents) old_sha1 = hasher2.hexdigest() else: return if new_sha1 == old_sha1: macro_wrapper.set_status(Addon.Status.NO_UPDATE_AVAILABLE) else: macro_wrapper.set_status(Addon.Status.UPDATE_AVAILABLE)
def remove(self) -> bool: """Remove a macro and all its related files Returns True if the macro was removed correctly. """ if not self.is_installed(): # Macro not installed, nothing to do. return True macro_dir = FreeCAD.getUserMacroDir(True) macro_path = os.path.join(macro_dir, self.filename) macro_path_with_macro_prefix = os.path.join(macro_dir, "Macro_" + self.filename) if os.path.exists(macro_path): os.remove(macro_path) elif os.path.exists(macro_path_with_macro_prefix): os.remove(macro_path_with_macro_prefix) # Remove related files, which are supposed to be given relative to # self.src_filename. for other_file in self.other_files: dst_file = os.path.join(macro_dir, other_file) try: os.remove(dst_file) remove_directory_if_empty(os.path.dirname(dst_file)) except Exception: FreeCAD.Console.PrintWarning( translate( "AddonsInstaller", "Failed to remove macro file '{}': it might not exist, or its permissions changed", ).format(dst_file) + "\n" ) return True
def remove_macro(macro): """Remove a macro and all its related files Returns True if the macro was removed correctly. Parameters ---------- - macro: a addonmanager_macro.Macro instance """ if not macro.is_installed(): # Macro not installed, nothing to do. return True macro_dir = FreeCAD.getUserMacroDir(True) macro_path = os.path.join(macro_dir, macro.filename) macro_path_with_macro_prefix = os.path.join(macro_dir, 'Macro_' + macro.filename) if os.path.exists(macro_path): os.remove(macro_path) elif os.path.exists(macro_path_with_macro_prefix): os.remove(macro_path_with_macro_prefix) # Remove related files, which are supposed to be given relative to # macro.src_filename. for other_file in macro.other_files: dst_file = os.path.join(macro_dir, other_file) remove_directory_if_empty(os.path.dirname(dst_file)) os.remove(dst_file) return True
def remove_directory_if_empty(dir_to_remove): """Remove the directory if it is empty, with one exception: the directory returned by FreeCAD.getUserMacroDir(True) will not be removed even if it is empty.""" if dir_to_remove == FreeCAD.getUserMacroDir(True): return if not os.listdir(dir_to_remove): os.rmdir(dir_to_remove)
def update_macro_installation_details(repo) -> None: if repo is None or not hasattr(repo, "macro") or repo.macro is None: FreeCAD.Console.PrintLog( f"Requested macro details for non-macro object\n") return test_file_one = os.path.join(FreeCAD.getUserMacroDir(True), repo.macro.filename) test_file_two = os.path.join(FreeCAD.getUserMacroDir(True), "Macro_" + repo.macro.filename) if os.path.exists(test_file_one): repo.updated_timestamp = os.path.getmtime(test_file_one) repo.installed_version = get_macro_version_from_file(test_file_one) elif os.path.exists(test_file_two): repo.updated_timestamp = os.path.getmtime(test_file_two) repo.installed_version = get_macro_version_from_file(test_file_two) else: return
def install_macro_to_toolbar(repo: Addon, toolbar: object) -> None: """Adds an icon for the given macro to the given toolbar.""" menuText = repo.display_name tooltipText = f"<b>{repo.display_name}</b>" if repo.macro.comment: tooltipText += f"<br/><p>{repo.macro.comment}</p>" whatsThisText = repo.macro.comment else: whatsThisText = translate( "AddonsInstaller", "A macro installed with the FreeCAD Addon Manager") statustipText = (translate("AddonsInstaller", "Run", "Indicates a macro that can be 'run'") + " " + repo.display_name) if repo.macro.icon: if os.path.isabs(repo.macro.icon): pixmapText = os.path.normpath(repo.macro.icon) else: macro_repo_dir = FreeCAD.getUserMacroDir(True) pixmapText = os.path.normpath( os.path.join(macro_repo_dir, repo.macro.icon)) elif repo.macro.xpm: macro_repo_dir = FreeCAD.getUserMacroDir(True) icon_file = os.path.normpath( os.path.join(macro_repo_dir, repo.macro.name + "_icon.xpm")) with open(icon_file, "w", encoding="utf-8") as f: f.write(repo.macro.xpm) pixmapText = icon_file else: pixmapText = None # Add this command to that toolbar command_name = FreeCADGui.Command.createCustomCommand( repo.macro.filename, menuText, tooltipText, whatsThisText, statustipText, pixmapText, ) toolbar.SetString(command_name, "FreeCAD") # Force the toolbars to be recreated wb = FreeCADGui.activeWorkbench() wb.reloadActive()
def remove_directory_if_empty(dir): """Remove the directory if it is empty Directory FreeCAD.getUserMacroDir(True) will not be removed even if empty. """ if dir == FreeCAD.getUserMacroDir(True): return if not os.listdir(dir): os.rmdir(dir)
def remove(self) -> bool: """Remove a macro and all its related files Returns True if the macro was removed correctly. """ if not self.is_installed(): # Macro not installed, nothing to do. return True macro_dir = FreeCAD.getUserMacroDir(True) macro_path = os.path.join(macro_dir, self.filename) macro_path_with_macro_prefix = os.path.join(macro_dir, "Macro_" + self.filename) if os.path.exists(macro_path): os.remove(macro_path) elif os.path.exists(macro_path_with_macro_prefix): os.remove(macro_path_with_macro_prefix) # Remove related files, which are supposed to be given relative to # self.src_filename. if self.xpm: xpm_file = os.path.join(macro_dir, self.name + "_icon.xpm") if os.path.exists(xpm_file): os.remove(xpm_file) for other_file in self.other_files: if not other_file: continue FreeCAD.Console.PrintMessage(f"{other_file}...") dst_file = os.path.join(macro_dir, other_file) if not dst_file or not os.path.exists(dst_file): FreeCAD.Console.PrintMessage(f"X\n") continue try: os.remove(dst_file) remove_directory_if_empty(os.path.dirname(dst_file)) FreeCAD.Console.PrintMessage("✓\n") except Exception: FreeCAD.Console.PrintMessage(f"?\n") if os.path.isabs(self.icon): dst_file = os.path.normpath( os.path.join(macro_dir, os.path.basename(self.icon))) if os.path.exists(dst_file): try: FreeCAD.Console.PrintMessage( f"{os.path.basename(self.icon)}...") os.remove(dst_file) FreeCAD.Console.PrintMessage("✓\n") except Exception: FreeCAD.Console.PrintMessage(f"?\n") return True
def executemacro(self): if self.tabWidget.currentIndex() == 1: # Tab "Macros". macro = self.macros[self.listMacros.currentRow()] if not macro.is_installed(): # Macro not installed, nothing to do. return macro_path = os.path.join(FreeCAD.getUserMacroDir(True), macro.filename) if os.path.exists(macro_path): macro_path = macro_path.replace("\\","/") FreeCADGui.open(str(macro_path)) self.hide() FreeCADGui.SendMsgToActiveView("Run") else: self.buttonExecute.setEnabled(False)
def install_macro(macro, macro_repo_dir): """Install a macro and all its related files Returns True if the macro was installed correctly. Parameters ---------- - macro: an addonmanager_macro.Macro instance """ if not macro.code: return False macro_dir = FreeCAD.getUserMacroDir(True) if not os.path.isdir(macro_dir): try: os.makedirs(macro_dir) except OSError: return False macro_path = os.path.join(macro_dir, macro.filename) if sys.version_info.major < 3: # In python2 the code is a bytes object. mode = 'wb' else: mode = 'w' try: with open(macro_path, mode) as macrofile: macrofile.write(macro.code) except IOError: return False # Copy related files, which are supposed to be given relative to # macro.src_filename. base_dir = os.path.dirname(macro.src_filename) for other_file in macro.other_files: dst_dir = os.path.join(macro_dir, os.path.dirname(other_file)) if not os.path.isdir(dst_dir): try: os.makedirs(dst_dir) except OSError: return False src_file = os.path.join(base_dir, other_file) dst_file = os.path.join(macro_dir, other_file) try: shutil.copy(src_file, dst_file) except IOError: return False return True
def update_macro(self, repo: Addon): """Updating a macro happens in this function, in the current thread""" cache_path = os.path.join( FreeCAD.getUserCachePath(), "AddonManager", "MacroCache" ) os.makedirs(cache_path, exist_ok=True) install_succeeded, _ = repo.macro.install(cache_path) if install_succeeded: install_succeeded, _ = repo.macro.install(FreeCAD.getUserMacroDir(True)) utils.update_macro_installation_details(repo) if install_succeeded: self.success.emit(repo) else: self.failure.emit(repo)
def install_macro(macro, macro_repo_dir): """Install a macro and all its related files Returns True if the macro was installed correctly. Parameters ---------- - macro: a addonmanager_macro.Macro instance """ if not macro.code: return False macro_dir = FreeCAD.getUserMacroDir(True) if not os.path.isdir(macro_dir): try: os.makedirs(macro_dir) except OSError: return False macro_path = os.path.join(macro_dir, macro.filename) if sys.version_info.major < 3: # In python2 the code is a bytes object. mode = 'wb' else: mode = 'w' try: with open(macro_path, mode) as macrofile: macrofile.write(macro.code) except IOError: return False # Copy related files, which are supposed to be given relative to # macro.src_filename. base_dir = os.path.dirname(macro.src_filename) for other_file in macro.other_files: dst_dir = os.path.join(base_dir, os.path.dirname(other_file)) if not os.path.isdir(dst_dir): try: os.makedirs(dst_dir) except OSError: return False src_file = os.path.join(base_dir, other_file) dst_file = os.path.join(macro_dir, other_file) try: shutil.copy(src_file, dst_file) except IOError: return False return True
def run_git_clone(self, clonedir: str) -> None: """Clones a repo using git""" self.status_message.emit("Cloning module...") current_thread = QtCore.QThread.currentThread() FreeCAD.Console.PrintMessage("Cloning repo...\n") if self.repo.git_lock.locked(): FreeCAD.Console.PrintMessage("Waiting for lock to be released to us...\n") if not self.repo.git_lock.acquire(timeout=2): FreeCAD.Console.PrintError( "Timeout waiting for a lock on the git process, failed to clone repo\n" ) return self.repo.git_lock.release() with self.repo.git_lock: FreeCAD.Console.PrintMessage("Lock acquired...\n") self.git_manager.clone(self.repo.url, clonedir) FreeCAD.Console.PrintMessage("Initial clone complete...\n") if current_thread.isInterruptionRequested(): return if current_thread.isInterruptionRequested(): return FreeCAD.Console.PrintMessage("Clone complete\n") if self.repo.contains_workbench(): answer = translate( "AddonsInstaller", "Workbench successfully installed. Please restart FreeCAD to apply the changes.", ) else: answer = translate( "AddonsInstaller", "Addon successfully installed.", ) if self.repo.repo_type == Addon.Kind.WORKBENCH: # symlink any macro contained in the module to the macros folder macro_dir = FreeCAD.getUserMacroDir(True) if not os.path.exists(macro_dir): os.makedirs(macro_dir) if os.path.exists(clonedir): for f in os.listdir(clonedir): if f.lower().endswith(".fcmacro"): try: utils.symlink( os.path.join(clonedir, f), os.path.join(macro_dir, f) ) except OSError: # If the symlink failed (e.g. for a non-admin user on Windows), copy # the macro instead shutil.copy( os.path.join(clonedir, f), os.path.join(macro_dir, f) ) FreeCAD.ParamGet( "User parameter:Plugins/" + self.repo.name ).SetString("destination", clonedir) # pylint: disable=line-too-long answer += "\n\n" + translate( "AddonsInstaller", "A macro has been installed and is available under Macro -> Macros menu", ) answer += ":\n<b>" + f + "</b>" self.update_metadata() self.success.emit(self.repo, answer)
def macroFilePath(cls): grp = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro") return grp.GetString("MacroPath", FreeCAD.getUserMacroDir())
def display_repo_status(self, status): repo = self.repo self.set_change_branch_button_state() if status != AddonManagerRepo.UpdateStatus.NOT_INSTALLED: version = repo.installed_version date = "" installed_version_string = "<h3>" if repo.updated_timestamp: date = (QDateTime.fromTime_t( repo.updated_timestamp).date().toString( Qt.SystemLocaleShortDate)) if version and date: installed_version_string += ( translate("AddonsInstaller", "Version {version} installed on {date}").format( version=version, date=date) + ". ") elif version: installed_version_string += (translate( "AddonsInstaller", "Version {version} installed") + ". ").format(version=version) elif date: installed_version_string += ( translate("AddonsInstaller", "Installed on {date}") + ". ").format(date=date) else: installed_version_string += ( translate("AddonsInstaller", "Installed") + ". ") if status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE: if repo.metadata: installed_version_string += ("<b>" + translate( "AddonsInstaller", "On branch {}, update available to version", ).format(repo.branch) + " ") installed_version_string += repo.metadata.Version installed_version_string += ".</b>" elif repo.macro and repo.macro.version: installed_version_string += ("<b>" + translate( "AddonsInstaller", "Update available to version") + " ") installed_version_string += repo.macro.version installed_version_string += ".</b>" else: installed_version_string += ("<b>" + translate( "AddonsInstaller", "An update is available", ) + ".</b>") elif status == AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE: detached_head = False branch = repo.branch if have_git and repo.repo_type != AddonManagerRepo.RepoType.MACRO: basedir = FreeCAD.getUserAppDataDir() moddir = os.path.join(basedir, "Mod", repo.name) if os.path.exists(os.path.join(moddir, ".git")): gitrepo = git.Repo(moddir) branch = gitrepo.head.ref.name detached_head = gitrepo.head.is_detached if detached_head: installed_version_string += (translate( "AddonsInstaller", "Git tag '{}' checked out, no updates possible", ).format(branch) + ".") else: installed_version_string += (translate( "AddonsInstaller", "This is the latest version available for branch {}", ).format(branch) + ".") elif status == AddonManagerRepo.UpdateStatus.PENDING_RESTART: installed_version_string += ( translate("AddonsInstaller", "Updated, please restart FreeCAD to use") + ".") elif status == AddonManagerRepo.UpdateStatus.UNCHECKED: pref = FreeCAD.ParamGet( "User parameter:BaseApp/Preferences/Addons") autocheck = pref.GetBool("AutoCheck", False) if autocheck: installed_version_string += (translate( "AddonsInstaller", "Update check in progress") + ".") else: installed_version_string += ( translate("AddonsInstaller", "Automatic update checks disabled") + ".") installed_version_string += "</h3>" self.ui.labelPackageDetails.setText(installed_version_string) if repo.status() == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE: self.ui.labelPackageDetails.setStyleSheet( "color:" + utils.attention_color_string()) else: self.ui.labelPackageDetails.setStyleSheet( "color:" + utils.bright_color_string()) self.ui.labelPackageDetails.show() if repo.macro is not None: moddir = FreeCAD.getUserMacroDir(True) else: basedir = FreeCAD.getUserAppDataDir() moddir = os.path.join(basedir, "Mod", repo.name) installationLocationString = ( translate("AddonsInstaller", "Installation location") + ": " + moddir) self.ui.labelInstallationLocation.setText( installationLocationString) self.ui.labelInstallationLocation.show() else: self.ui.labelPackageDetails.hide() self.ui.labelInstallationLocation.hide() if status == AddonManagerRepo.UpdateStatus.NOT_INSTALLED: self.ui.buttonInstall.show() self.ui.buttonUninstall.hide() self.ui.buttonUpdate.hide() self.ui.buttonCheckForUpdate.hide() elif status == AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE: self.ui.buttonInstall.hide() self.ui.buttonUninstall.show() self.ui.buttonUpdate.hide() self.ui.buttonCheckForUpdate.hide() elif status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE: self.ui.buttonInstall.hide() self.ui.buttonUninstall.show() self.ui.buttonUpdate.show() self.ui.buttonCheckForUpdate.hide() elif status == AddonManagerRepo.UpdateStatus.UNCHECKED: self.ui.buttonInstall.hide() self.ui.buttonUninstall.show() self.ui.buttonUpdate.hide() self.ui.buttonCheckForUpdate.show() elif status == AddonManagerRepo.UpdateStatus.PENDING_RESTART: self.ui.buttonInstall.hide() self.ui.buttonUninstall.show() self.ui.buttonUpdate.hide() self.ui.buttonCheckForUpdate.hide() required_version = self.requires_newer_freecad() if repo.obsolete: self.ui.labelWarningInfo.show() self.ui.labelWarningInfo.setText("<h1>" + translate( "AddonsInstaller", "WARNING: This addon is obsolete") + "</h1>") self.ui.labelWarningInfo.setStyleSheet( "color:" + utils.warning_color_string()) elif repo.python2: self.ui.labelWarningInfo.show() self.ui.labelWarningInfo.setText("<h1>" + translate( "AddonsInstaller", "WARNING: This addon is Python 2 Only") + "</h1>") self.ui.labelWarningInfo.setStyleSheet( "color:" + utils.warning_color_string()) elif required_version: self.ui.labelWarningInfo.show() self.ui.labelWarningInfo.setText("<h1>" + translate( "AddonsInstaller", "WARNING: This addon requires FreeCAD ") + required_version + "</h1>") self.ui.labelWarningInfo.setStyleSheet( "color:" + utils.warning_color_string()) else: self.ui.labelWarningInfo.hide()
def __launch(self): """This function generates and displays the MultiCopy GUI interface. It creates all the dialog boxes for user interaction and input. """ self.main_dialog = gui.PySideUic.loadUi( app.getUserMacroDir(True) + '/MultiCopy/resources/MultiCopy_Main_Dialog.ui' ) objects_list_textbox = self.main_dialog.findChild( QTextEdit, 'objects_list_textbox' ) commands_input_textbox = self.main_dialog.findChild( QPlainTextEdit, 'commands_input_textbox' ) # Adds a filter to detect Paste Code Commands changes in the 'commands_input_textbox' and # validate the same. _filter = Filter() _filter.sendObject(self) commands_input_textbox.installEventFilter(_filter) # Inserts the list of selected FreeCAD objects into the 'objects_list_textbox'. objects_list_textbox_text = '<table>' for i_, selected_obj in enumerate(self.selected_objs, 1): objects_list_textbox_text += ( '<tr><td>[' + str(i_) + ']</td><td> ' + selected_obj.Label + '</td><td> <' + str(selected_obj.TypeId).replace('\'', '') + '></td></tr>' ) objects_list_textbox_text += '</table>' objects_list_textbox.setHtml(objects_list_textbox_text) commands_input_textbox.setPlainText('from ') commands_input_textbox.moveCursor(QTextCursor.End, QTextCursor.MoveAnchor) self.__numbering_type_radios_clicked() for docElem in self.list_of_documents: self.main_dialog.findChild(QComboBox, 'documents_list_combobox').addItem( app.getDocument(docElem).Label ) self.main_dialog.findChild( QRadioButton, 'numbering_type_n_radio' ).clicked.connect(lambda: self.__numbering_type_radios_clicked()) self.main_dialog.findChild( QRadioButton, 'numbering_type_ru_radio' ).clicked.connect(lambda: self.__numbering_type_radios_clicked()) self.main_dialog.findChild( QRadioButton, 'numbering_type_rl_radio' ).clicked.connect(lambda: self.__numbering_type_radios_clicked()) self.main_dialog.findChild( QRadioButton, 'numbering_type_au_radio' ).clicked.connect(lambda: self.__numbering_type_radios_clicked()) self.main_dialog.findChild( QRadioButton, 'numbering_type_al_radio' ).clicked.connect(lambda: self.__numbering_type_radios_clicked()) self.main_dialog.findChild( QCheckBox, 'delete_selections_check' ).toggled.connect(lambda: self.__delete_selections_check_toggled()) self.main_dialog.findChild(QCheckBox, 'add_separator_check').toggled.connect( lambda: self.__add_separator_check_toggled() ) self.main_dialog.findChild(QCheckBox, 'add_padding_check').toggled.connect( lambda: self.__add_padding_check_toggled() ) self.main_dialog.findChild(QPushButton, 'paste_button').clicked.connect( lambda: self.__paste_button_clicked() ) self.main_dialog.findChild(QTabWidget, 'tabset').currentChanged.connect( lambda: self.__tabset_tab_toggled() ) self.main_dialog.findChild(QPushButton, 'command_list_button').clicked.connect( launch_commands_list_dialog ) self.main_dialog.findChild(QPushButton, 'close_button').clicked.connect( lambda: self.main_dialog.done(1) ) self.main_dialog.setWindowIcon( QIcon(app.getUserMacroDir(True) + '/MultiCopy/resources/MultiCopy.svg') ) self.main_dialog.exec_()
def run(self): "installs or updates the selected addon" git = None try: import git except Exception as e: self.info_label.emit("GitPython not found.") print(e) FreeCAD.Console.PrintWarning( translate( "AddonsInstaller", "GitPython not found. Using standard download instead.") + "\n") try: import zipfile except: self.info_label.emit("no zip support.") FreeCAD.Console.PrintError( translate( "AddonsInstaller", "Your version of python doesn't appear to support ZIP files. Unable to proceed." ) + "\n") return try: import StringIO as io except ImportError: # StringIO is not available with python3 import io if not isinstance(self.idx, list): self.idx = [self.idx] for idx in self.idx: if idx < 0: return if not self.repos: return if NOGIT: git = None basedir = FreeCAD.getUserAppDataDir() moddir = basedir + os.sep + "Mod" if not os.path.exists(moddir): os.makedirs(moddir) clonedir = moddir + os.sep + self.repos[idx][0] self.progressbar_show.emit(True) if os.path.exists(clonedir): self.info_label.emit("Updating module...") if git: if not os.path.exists(clonedir + os.sep + '.git'): # Repair addon installed with raw download bare_repo = git.Repo.clone_from(self.repos[idx][1], clonedir + os.sep + '.git', bare=True) try: with bare_repo.config_writer() as cw: cw.set('core', 'bare', False) except AttributeError: FreeCAD.Console.PrintWarning( translate( "AddonsInstaller", "Outdated GitPython detected, consider upgrading with pip." ) + "\n") cw = bare_repo.config_writer() cw.set('core', 'bare', False) del cw repo = git.Repo(clonedir) repo.head.reset('--hard') repo = git.Git(clonedir) try: answer = repo.pull() except: print("Error updating module", self.repos[idx][1], " - Please fix manually") answer = repo.status() print(answer) else: # Update the submodules for this repository repo_sms = git.Repo(clonedir) for submodule in repo_sms.submodules: submodule.update(init=True, recursive=True) else: answer = self.download(self.repos[idx][1], clonedir) else: self.info_label.emit("Checking module dependencies...") depsok, answer = self.checkDependencies(self.repos[idx][1]) if depsok: if git: self.info_label.emit("Cloning module...") repo = git.Repo.clone_from(self.repos[idx][1], clonedir, branch='master') # Make sure to clone all the submodules as well if repo.submodules: repo.submodule_update(recursive=True) else: self.info_label.emit("Downloading module...") self.download(self.repos[idx][1], clonedir) answer = translate( "AddonsInstaller", "Workbench successfully installed. Please restart FreeCAD to apply the changes." ) # symlink any macro contained in the module to the macros folder macro_dir = FreeCAD.getUserMacroDir(True) if not os.path.exists(macro_dir): os.makedirs(macro_dir) if os.path.exists(clonedir): for f in os.listdir(clonedir): if f.lower().endswith(".fcmacro"): print("copying macro:", f) utils.symlink(os.path.join(clonedir, f), os.path.join(macro_dir, f)) FreeCAD.ParamGet('User parameter:Plugins/' + self.repos[idx][0]).SetString( "destination", clonedir) answer += "\n\n" + translate( "AddonsInstaller", "A macro has been installed and is available under Macro -> Macros menu" ) + ":" answer += "\n<b>" + f + "</b>" self.progressbar_show.emit(False) self.info_label.emit(answer) self.mark_recompute.emit(self.repos[idx][0]) self.stop = True
def processFileNameSubstitutions( job, subpartname, sequencenumber, outputpath, filename, ext, ): """Process any substitutions in the outputpath or filename.""" # The following section allows substitution within the path part PathLog.track(f"path before substitution: {outputpath}") if "%D" in outputpath: # Directory of active document D = FreeCAD.ActiveDocument.FileName if D: D = os.path.dirname(D) # in case the document is in the current working directory if not D: D = "." else: FreeCAD.Console.PrintError( "Please save document in order to resolve output path!\n" ) return None outputpath = outputpath.replace("%D", D) if "%M" in outputpath: M = FreeCAD.getUserMacroDir() outputpath = outputpath.replace("%M", M) # Use the file label if "%d" in outputpath: d = FreeCAD.ActiveDocument.Label outputpath = outputpath.replace("%d", d) # Use the name of the active job object if "%j" in outputpath: j = job.Label outputpath = outputpath.replace("%j", j) PathLog.track(f"path after substitution: {outputpath}") # The following section allows substitution within the filename part PathLog.track(f"filename before substitution: {filename}") # Use the file label if "%d" in filename: d = FreeCAD.ActiveDocument.Label filename = filename.replace("%d", d) # Use the name of the active job object if "%j" in filename: j = job.Label filename = filename.replace("%j", j) # Use the sequence number if explicitly called if "%S" in filename: j = job.Label filename = filename.replace("%S", str(sequencenumber)) # This section handles unique names for splitting output if job.SplitOutput: PathLog.track() if "%T" in filename and job.OrderOutputBy == "Tool": filename = filename.replace("%T", subpartname) if "%t" in filename and job.OrderOutputBy == "Tool": filename = filename.replace("%t", subpartname) if "%W" in filename and job.OrderOutputBy == "Fixture": filename = filename.replace("%W", subpartname) if "%O" in filename and job.OrderOutputBy == "Operation": filename = filename.replace("%O", subpartname) if ( "%S" in filename ): # We always add a sequence number but the user can say where filename = filename.replace("%S", str(sequencenumber)) else: filename = f"{filename}-{sequencenumber}" PathLog.track(f"filename after substitution: {filename}") if not ext: ext = ".nc" PathLog.track(f"file extension: {ext}") fullPath = f"{outputpath}{os.path.sep}{filename}{ext}" PathLog.track(f"full filepath: {fullPath}") return fullPath
def get_macro_path(): """Returns platform independent macro base path""" return Path(App.getUserMacroDir(True))
def run(self): "installs or updates the selected addon" git = None try: import git except Exception as e: self.info_label.emit("GitPython not found.") print(e) FreeCAD.Console.PrintWarning(translate("AddonsInstaller","GitPython not found. Using standard download instead.")+"\n") try: import zipfile except: self.info_label.emit("no zip support.") FreeCAD.Console.PrintError(translate("AddonsInstaller","Your version of python doesn't appear to support ZIP files. Unable to proceed.")+"\n") return try: import StringIO as io except ImportError: # StringIO is not available with python3 import io if not isinstance(self.idx,list): self.idx = [self.idx] for idx in self.idx: if idx < 0: return if not self.repos: return if NOGIT: git = None basedir = FreeCAD.getUserAppDataDir() moddir = basedir + os.sep + "Mod" if not os.path.exists(moddir): os.makedirs(moddir) clonedir = moddir + os.sep + self.repos[idx][0] self.progressbar_show.emit(True) if os.path.exists(clonedir): self.info_label.emit("Updating module...") if git: if not os.path.exists(clonedir + os.sep + '.git'): # Repair addon installed with raw download bare_repo = git.Repo.clone_from(self.repos[idx][1], clonedir + os.sep + '.git', bare=True) try: with bare_repo.config_writer() as cw: cw.set('core', 'bare', False) except AttributeError: FreeCAD.Console.PrintWarning(translate("AddonsInstaller", "Outdated GitPython detected, consider upgrading with pip.")+"\n") cw = bare_repo.config_writer() cw.set('core', 'bare', False) del cw repo = git.Repo(clonedir) repo.head.reset('--hard') repo = git.Git(clonedir) answer = repo.pull() # Update the submodules for this repository repo_sms = git.Repo(clonedir) for submodule in repo_sms.submodules: submodule.update(init=True, recursive=True) else: answer = self.download(self.repos[idx][1],clonedir) else: self.info_label.emit("Checking module dependencies...") depsok,answer = self.checkDependencies(self.repos[idx][1]) if depsok: if git: self.info_label.emit("Cloning module...") repo = git.Repo.clone_from(self.repos[idx][1], clonedir, branch='master') # Make sure to clone all the submodules as well if repo.submodules: repo.submodule_update(recursive=True) else: self.info_label.emit("Downloading module...") self.download(self.repos[idx][1],clonedir) answer = translate("AddonsInstaller", "Workbench successfully installed. Please restart FreeCAD to apply the changes.") # symlink any macro contained in the module to the macros folder macro_dir = FreeCAD.getUserMacroDir(True) if not os.path.exists(macro_dir): os.makedirs(macro_dir) for f in os.listdir(clonedir): if f.lower().endswith(".fcmacro"): print("copying macro:",f) symlink(os.path.join(clonedir, f), os.path.join(macro_dir, f)) FreeCAD.ParamGet('User parameter:Plugins/'+self.repos[idx][0]).SetString("destination",clonedir) answer += translate("AddonsInstaller", "A macro has been installed and is available the Macros menu") + ": <b>" answer += f + "</b>" self.progressbar_show.emit(False) self.info_label.emit(answer) self.stop = True
def setUI(self): self.base = QtGui.QWidget() self.base.setWindowTitle("PACE tools") self.form = self.base self.layout = QtGui.QVBoxLayout() self.form.setLayout(self.layout) titles = {'init': 'Initial situation', 'mod': 'Modified situation'} iconPath = os.path.join(App.getUserMacroDir(True), 'pace_logo.png') print(iconPath) if os.path.exists(iconPath) and os.name != 'posix': print("In if") paceIcon = QtGui.QIcon(iconPath) self.base.setWindowIcon(paceIcon) #dommage, ca fait crasher... self.situationChoice = QtGui.QComboBox() self.situationChoice.addItems( [titles[situation] for situation in self.proj.VP.keys()]) self.situationChoice.currentIndexChanged.connect(self.change) skinElementsDescriptionButton = QtGui.QPushButton( "Skin elements description") #skinElementsDescriptionButton.clicked.connect(self.proj.setSkinElementsDescription) skinElementsDescriptionButton.clicked.connect( lambda: paceGeomTools.skinElementsConfigurator(self.proj)) self.compass = paceGeomTools.Compass() if (hasattr(self.proj, 'sectormap')): self.compass.sectormap = self.proj.sectormap setCompassButton = QtGui.QPushButton("Define building orientation") setCompassButton.clicked.connect( lambda: self.compass.setCompassFromFace(self.proj)) exportToPaceButton = QtGui.QPushButton("Export geometry to PACE") exportToPaceButton.clicked.connect(self.proj.exportToPace) self.collayouts = {} self.vpwidgets = {} for situation in self.proj.VP.keys(): self.vpwidgets[situation] = QtGui.QWidget() self.collayouts[situation] = QtGui.QVBoxLayout() identifySelB = QtGui.QPushButton("Assign SELECTION wall type") identifySelB.clicked.connect( self.proj.VP[situation].setSelectionType) #showlabelsB=QtGui.QPushButton("Show/refresh wall types") #showlabelsB.clicked.connect(self.proj.VP[situation].showVisibleFacesLabel) #hidelabelsB=QtGui.QPushButton("Hide wall types") #hidelabelsB.clicked.connect(self.proj.VP[situation].hideAllLabels) colorbyLabelB = QtGui.QPushButton("Color by Label") colorbyLabelB.clicked.connect(self.proj.VP[situation].colorByLabel) showHideLegend = QtGui.QPushButton("Show/hide color legend") showHideLegend.clicked.connect(self.showHideLegend) #hideL=QtGui.QPushButton("Hide colors legend") #hideL.clicked.connect(self.proj.VP[situation].hideLegend) showAreasAndVolume = QtGui.QPushButton("Show areas per wall type") showAreasAndVolume.clicked.connect( self.proj.VP[situation].showAreasAndVolume) showAreasWt = QtGui.QPushButton( "Show areas per wall type and Facade") showAreasWt.clicked.connect( lambda state=False, situation=situation: self.proj.VP[ situation].showAreasPerFacade(self.proj.sectormap)) # see https://stackoverflow.com/questions/35819538/using-lambda-expression-to-connect-slots-in-pyqt # I cant avoid lambda if I want to pass a parameter # However, the lambda does not record the situation value, but only the reference ! # Have to use default value (situation = situation) to force recording it exportStep = QtGui.QPushButton("Export step file") exportStep.clicked.connect(self.proj.VP[situation].exportStep) exportPNG = QtGui.QPushButton("Save PNG figure to file") exportPNG.clicked.connect(self.proj.VP[situation].exportPNG) exportPNGtoPACE = QtGui.QPushButton( "Export current view to PACE file (" + situation + " situation)") exportPNGtoPACE.clicked.connect( lambda state=False, situation=situation: self.proj. insertCurrentViewInPaceFile(situation)) updateAfterBodyModificationButton = QtGui.QPushButton( "Update after body modif") updateAfterBodyModificationButton.clicked.connect( lambda state=False, situation=situation: self.updateSituation( situation)) self.collayouts[situation].addWidget(identifySelB) #self.collayouts[situation].addWidget(showlabelsB) #self.collayouts[situation].addWidget(hidelabelsB) self.collayouts[situation].addWidget(colorbyLabelB) self.collayouts[situation].addWidget(showHideLegend) # self.collayouts[situation].addWidget(hideL) self.collayouts[situation].addWidget(showAreasAndVolume) self.collayouts[situation].addWidget(showAreasWt) self.collayouts[situation].addWidget(exportStep) self.collayouts[situation].addWidget(exportPNG) self.collayouts[situation].addWidget(exportPNGtoPACE) self.collayouts[situation].addWidget( updateAfterBodyModificationButton) self.vpwidgets[situation].setLayout(self.collayouts[situation]) if ("mod" in self.proj.VP.keys()): mapB = QtGui.QPushButton("Copy types from init to mod") mapB.clicked.connect( lambda: self.proj.copyLabelsToOtherSituation('init', 'mod')) self.collayouts['mod'].addWidget(mapB) self.layout.addWidget(self.situationChoice) self.layout.addWidget(skinElementsDescriptionButton) self.layout.addWidget(setCompassButton) self.layout.addWidget(exportToPaceButton) self.layout.addWidget(self.vpwidgets['init']) if ("mod" in self.proj.VP.keys()): self.layout.addWidget(self.vpwidgets['mod']) self.vpwidgets['mod'].hide()
def is_installed(self): if self.on_git and not self.src_filename: return False return (os.path.exists(os.path.join(FreeCAD.getUserMacroDir(True), self.filename)) or os.path.exists(os.path.join(FreeCAD.getUserMacroDir(True), 'Macro_' + self.filename)))
def show_repo(self, repo: AddonManagerRepo, reload: bool = False) -> None: self.repo = repo if self.worker is not None: if not self.worker.isFinished(): self.worker.requestInterruption() self.worker.wait() # Always load bare macros from scratch, we need to grab their code, which isn't cached force_reload = reload if repo.repo_type == AddonManagerRepo.RepoType.MACRO: force_reload = True self.check_and_clean_cache(force_reload) if repo.repo_type == AddonManagerRepo.RepoType.MACRO: self.show_macro(repo) self.ui.buttonExecute.show() elif repo.repo_type == AddonManagerRepo.RepoType.WORKBENCH: self.show_workbench(repo) self.ui.buttonExecute.hide() elif repo.repo_type == AddonManagerRepo.RepoType.PACKAGE: self.show_package(repo) self.ui.buttonExecute.hide() if repo.update_status != AddonManagerRepo.UpdateStatus.NOT_INSTALLED: version = repo.installed_version date = "" installed_version_string = "<h3>" if repo.updated_timestamp: date = (QDateTime.fromTime_t( repo.updated_timestamp).date().toString( Qt.SystemLocaleShortDate)) if version and date: installed_version_string += ( translate("AddonsInstaller", f"Version {version} installed on {date}") + ". ") elif version: installed_version_string += (translate( "AddonsInstaller", f"Version {version} installed") + ". ") elif date: installed_version_string += ( translate("AddonsInstaller", f"Installed on {date}") + ". ") else: installed_version_string += ( translate("AddonsInstaller", "Installed") + ". ") if repo.update_status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE: if repo.metadata: installed_version_string += ("<b>" + translate( "AddonsInstaller", "Update available to version") + " ") installed_version_string += repo.metadata.Version installed_version_string += ".</b>" elif repo.macro and repo.macro.version: installed_version_string += ("<b>" + translate( "AddonsInstaller", "Update available to version") + " ") installed_version_string += repo.macro.version installed_version_string += ".</b>" else: installed_version_string += ("<b>" + translate( "AddonsInstaller", "An update is available", ) + ".</b>") elif (repo.update_status == AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE): installed_version_string += ( translate("AddonsInstaller", "This is the latest version available") + ".") elif repo.update_status == AddonManagerRepo.UpdateStatus.PENDING_RESTART: installed_version_string += ( translate("AddonsInstaller", "Updated, please restart FreeCAD to use") + ".") elif repo.update_status == AddonManagerRepo.UpdateStatus.UNCHECKED: pref = FreeCAD.ParamGet( "User parameter:BaseApp/Preferences/Addons") autocheck = pref.GetBool("AutoCheck", False) if autocheck: installed_version_string += (translate( "AddonsInstaller", "Update check in progress") + ".") else: installed_version_string += ( translate("AddonsInstaller", "Automatic update checks disabled") + ".") installed_version_string += "</h3>" self.ui.labelPackageDetails.setText(installed_version_string) if repo.update_status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE: self.ui.labelPackageDetails.setStyleSheet( "color:" + utils.attention_color_string()) else: self.ui.labelPackageDetails.setStyleSheet( "color:" + utils.bright_color_string()) self.ui.labelPackageDetails.show() if repo.macro is not None: moddir = FreeCAD.getUserMacroDir(True) else: basedir = FreeCAD.getUserAppDataDir() moddir = os.path.join(basedir, "Mod", repo.name) installationLocationString = ( translate("AddonsInstaller", "Installation location") + ": " + moddir) self.ui.labelInstallationLocation.setText( installationLocationString) self.ui.labelInstallationLocation.show() else: self.ui.labelPackageDetails.hide() self.ui.labelInstallationLocation.hide() if repo.update_status == AddonManagerRepo.UpdateStatus.NOT_INSTALLED: self.ui.buttonInstall.show() self.ui.buttonUninstall.hide() self.ui.buttonUpdate.hide() self.ui.buttonCheckForUpdate.hide() elif repo.update_status == AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE: self.ui.buttonInstall.hide() self.ui.buttonUninstall.show() self.ui.buttonUpdate.hide() self.ui.buttonCheckForUpdate.hide() elif repo.update_status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE: self.ui.buttonInstall.hide() self.ui.buttonUninstall.show() self.ui.buttonUpdate.show() self.ui.buttonCheckForUpdate.hide() elif repo.update_status == AddonManagerRepo.UpdateStatus.UNCHECKED: self.ui.buttonInstall.hide() self.ui.buttonUninstall.show() self.ui.buttonUpdate.hide() self.ui.buttonCheckForUpdate.show() elif repo.update_status == AddonManagerRepo.UpdateStatus.PENDING_RESTART: self.ui.buttonInstall.hide() self.ui.buttonUninstall.show() self.ui.buttonUpdate.hide() self.ui.buttonCheckForUpdate.hide() if repo.obsolete: self.ui.labelWarningInfo.show() self.ui.labelWarningInfo.setText("<h1>" + translate( "AddonsInstaller", "WARNING: This addon is obsolete") + "</h1>") self.ui.labelWarningInfo.setStyleSheet( "color:" + utils.warning_color_string()) elif repo.python2: self.ui.labelWarningInfo.show() self.ui.labelWarningInfo.setText("<h1>" + translate( "AddonsInstaller", "WARNING: This addon is Python 2 Only") + "</h1>") self.ui.labelWarningInfo.setStyleSheet( "color:" + utils.warning_color_string()) else: self.ui.labelWarningInfo.hide()
from PySide.QtGui import QDialog, QMessageBox, QTableWidgetItem from femmesh.gmshtools import GmshTools from femtools import ccxtools import FreeCAD,FreeCADGui,Part import PySide import json import time MSGBOX_TITLE = "Iterative CCX Solver" FEMITERATE_VERSION = "0.0.1" TYPEID_FEM_MESH = "Fem::FemMeshObjectPython" TYPEID_FEM_ANALYSIS = "Fem::FemAnalysis" TYPEID_FEM_SOLVER = "Fem::FemSolverObjectPython" UI_BASE_PATH = FreeCAD.getUserMacroDir() + "FEMIterate" UI_MAIN_FILE_PATH = f"{UI_BASE_PATH}/main.ui" UI_CHANGE_FILE_PATH = f"{UI_BASE_PATH}/addchange.ui" UI_CHECK_FILE_PATH = f"{UI_BASE_PATH}/addcheck.ui" GUI_IN_SIDEBAR = True USERTYPE_NUMBER = "Number" USERTYPE_UNIT = "Unit" USERTYPE_PYTHON = "Python expr." BUILTIN_QUICK_EXPRESSIONS = [ "max(r.vonMises) < 100", "max(r.Temperature) < 300" ]
class TestOutputNameSubstitution(unittest.TestCase): """ String substitution allows the following: %D ... directory of the active document %d ... name of the active document (with extension) %M ... user macro directory %j ... name of the active Job object The Following can be used if output is being split. If Output is not split these will be ignored. %S ... Sequence Number (default) %T ... Tool Number %t ... Tool Controller label %W ... Work Coordinate System %O ... Operation Label self.job.Fixtures = ["G54"] self.job.SplitOutput = False self.job.OrderOutputBy = "Fixture" Assume: active document: self.assertTrue(filename, f"{home}/testdoc.fcstd user macro: ~/.local/share/FreeCAD/Macro Job: MainJob Operations: OutsideProfile DrillAllHoles TC: 7/16" two flute (5) TC: Drill (2) Fixtures: (G54, G55) Strings should be sanitized like this to ensure valid filenames # import re # filename="TC: 7/16" two flute" # >>> re.sub(r"[^\w\d-]","_",filename) # "TC__7_16__two_flute" """ testfile = FreeCAD.getHomePath( ) + "Mod/Path/PathTests/test_filenaming.fcstd" testfilepath, testfilename = os.path.split(testfile) testfilename, ext = os.path.splitext(testfilename) doc = FreeCAD.open(testfile) job = doc.getObjectsByLabel("MainJob")[0] macro = FreeCAD.getUserMacroDir() def test000(self): # Test basic name generation with empty string FreeCAD.setActiveDocument(self.doc.Label) teststring = "" self.job.PostProcessorOutputFile = teststring self.job.SplitOutput = False outlist = PathPost.buildPostList(self.job) self.assertTrue(len(outlist) == 1) subpart, objs = outlist[0] filename = PathPost.resolveFileName(self.job, subpart, 0) self.assertEqual(filename, f"{self.testfilename}.nc") def test015(self): # Test basic string substitution without splitting teststring = "~/Desktop/%j.nc" self.job.PostProcessorOutputFile = teststring self.job.SplitOutput = False outlist = PathPost.buildPostList(self.job) self.assertTrue(len(outlist) == 1) subpart, objs = outlist[0] filename = PathPost.resolveFileName(self.job, subpart, 0) self.assertEqual(filename, "~/Desktop/MainJob.nc") def test010(self): # Substitute current file path teststring = "%D/testfile.nc" self.job.PostProcessorOutputFile = teststring outlist = PathPost.buildPostList(self.job) subpart, objs = outlist[0] filename = PathPost.resolveFileName(self.job, subpart, 0) self.assertEqual(filename, f"{self.testfilepath}/testfile.nc") def test020(self): teststring = "%d.nc" self.job.PostProcessorOutputFile = teststring outlist = PathPost.buildPostList(self.job) subpart, objs = outlist[0] filename = PathPost.resolveFileName(self.job, subpart, 0) self.assertEqual(filename, f"{self.testfilename}.nc") def test030(self): teststring = "%M/outfile.nc" self.job.PostProcessorOutputFile = teststring outlist = PathPost.buildPostList(self.job) subpart, objs = outlist[0] filename = PathPost.resolveFileName(self.job, subpart, 0) self.assertEqual(filename, f"{self.macro}outfile.nc") def test040(self): # unused substitution strings should be ignored teststring = "%d%T%t%W%O/testdoc.nc" self.job.PostProcessorOutputFile = teststring outlist = PathPost.buildPostList(self.job) subpart, objs = outlist[0] filename = PathPost.resolveFileName(self.job, subpart, 0) self.assertEqual(filename, f"{self.testfilename}/testdoc.nc") def test050(self): # explicitly using the sequence number should include it where indicated. teststring = "%S-%d.nc" self.job.PostProcessorOutputFile = teststring outlist = PathPost.buildPostList(self.job) subpart, objs = outlist[0] filename = PathPost.resolveFileName(self.job, subpart, 0) self.assertEqual(filename, "0-test_filenaming.nc") def test060(self): # # Split by Tool self.job.SplitOutput = True self.job.OrderOutputBy = "Tool" outlist = PathPost.buildPostList(self.job) # substitute jobname and use default sequence numbers teststring = "%j.nc" self.job.PostProcessorOutputFile = teststring subpart, objs = outlist[0] filename = PathPost.resolveFileName(self.job, subpart, 0) self.assertEqual(filename, "MainJob-0.nc") subpart, objs = outlist[1] filename = PathPost.resolveFileName(self.job, subpart, 1) self.assertEqual(filename, "MainJob-1.nc") # Use Toolnumbers and default sequence numbers teststring = "%T.nc" self.job.PostProcessorOutputFile = teststring outlist = PathPost.buildPostList(self.job) subpart, objs = outlist[0] filename = PathPost.resolveFileName(self.job, subpart, 0) self.assertEqual(filename, "5-0.nc") subpart, objs = outlist[1] filename = PathPost.resolveFileName(self.job, subpart, 1) self.assertEqual(filename, "2-1.nc") # Use Tooldescriptions and default sequence numbers teststring = "%t.nc" self.job.PostProcessorOutputFile = teststring outlist = PathPost.buildPostList(self.job) subpart, objs = outlist[0] filename = PathPost.resolveFileName(self.job, subpart, 0) self.assertEqual(filename, "TC__7_16__two_flute-0.nc") subpart, objs = outlist[1] filename = PathPost.resolveFileName(self.job, subpart, 1) self.assertEqual(filename, "TC__Drill-1.nc") def test070(self): # Split by WCS self.job.SplitOutput = True self.job.OrderOutputBy = "Fixture" outlist = PathPost.buildPostList(self.job) teststring = "%j.nc" self.job.PostProcessorOutputFile = teststring subpart, objs = outlist[0] filename = PathPost.resolveFileName(self.job, subpart, 0) self.assertEqual(filename, "MainJob-0.nc") subpart, objs = outlist[1] filename = PathPost.resolveFileName(self.job, subpart, 1) self.assertEqual(filename, "MainJob-1.nc") teststring = "%W-%j.nc" self.job.PostProcessorOutputFile = teststring subpart, objs = outlist[0] filename = PathPost.resolveFileName(self.job, subpart, 0) self.assertEqual(filename, "G54-MainJob-0.nc") subpart, objs = outlist[1] filename = PathPost.resolveFileName(self.job, subpart, 1) self.assertEqual(filename, "G55-MainJob-1.nc") def test080(self): # Split by Operation self.job.SplitOutput = True self.job.OrderOutputBy = "Operation" outlist = PathPost.buildPostList(self.job) teststring = "%j.nc" self.job.PostProcessorOutputFile = teststring subpart, objs = outlist[0] filename = PathPost.resolveFileName(self.job, subpart, 0) self.assertEqual(filename, "MainJob-0.nc") subpart, objs = outlist[1] filename = PathPost.resolveFileName(self.job, subpart, 1) self.assertEqual(filename, "MainJob-1.nc") teststring = "%O-%j.nc" self.job.PostProcessorOutputFile = teststring subpart, objs = outlist[0] filename = PathPost.resolveFileName(self.job, subpart, 0) self.assertEqual(filename, "OutsideProfile-MainJob-0.nc") subpart, objs = outlist[1] filename = PathPost.resolveFileName(self.job, subpart, 1) self.assertEqual(filename, "DrillAllHoles-MainJob-1.nc")
def macroFilePath(): grp = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro") return grp.GetString("MacroPath", FreeCAD.getUserMacroDir())
def resolveFileName(job, subpartname, sequencenumber): PathLog.track(subpartname, sequencenumber) validPathSubstitutions = ["D", "d", "M", "j"] validFilenameSubstitutions = ["j", "d", "T", "t", "W", "O", "S"] # Look for preference default outputpath, filename = os.path.split(PathPreferences.defaultOutputFile()) filename, ext = os.path.splitext(filename) # Override with document default if it exists if job.PostProcessorOutputFile: matchstring = job.PostProcessorOutputFile candidateOutputPath, candidateFilename = os.path.split(matchstring) if candidateOutputPath: outputpath = candidateOutputPath if candidateFilename: filename, ext = os.path.splitext(candidateFilename) # Strip any invalid substitutions from the ouputpath for match in re.findall("%(.)", outputpath): if match not in validPathSubstitutions: outputpath = outputpath.replace(f"%{match}", "") # if nothing else, use current directory if not outputpath: outputpath = "." # Strip any invalid substitutions from the filename for match in re.findall("%(.)", filename): if match not in validFilenameSubstitutions: filename = filename.replace(f"%{match}", "") # if no filename, use the active document label if not filename: filename = FreeCAD.ActiveDocument.Label # if no extension, use something sensible if not ext: ext = ".nc" # By now we should have a sanitized path, filename and extension to work with PathLog.track(f"path: {outputpath} name: {filename} ext: {ext}") # The following section allows substitution within the path part PathLog.track(f"path before substitution: {outputpath}") if "%D" in outputpath: # Directory of active document D = FreeCAD.ActiveDocument.FileName if D: D = os.path.dirname(D) # in case the document is in the current working directory if not D: D = "." else: FreeCAD.Console.PrintError( "Please save document in order to resolve output path!\n" ) return None outputpath = outputpath.replace("%D", D) if "%M" in outputpath: M = FreeCAD.getUserMacroDir() outputpath = outputpath.replace("%M", M) # Use the file label if "%d" in outputpath: d = FreeCAD.ActiveDocument.Label outputpath = outputpath.replace("%d", d) # Use the name of the active job object if "%j" in outputpath: j = job.Label outputpath = outputpath.replace("%j", j) PathLog.track(f"path after substitution: {outputpath}") # The following section allows substitution within the filename part PathLog.track(f"filename before substitution: {filename}") # Use the file label if "%d" in filename: d = FreeCAD.ActiveDocument.Label filename = filename.replace("%d", d) # Use the name of the active job object if "%j" in filename: j = job.Label filename = filename.replace("%j", j) # Use the sequnce number if explicitly called if "%S" in filename: j = job.Label filename = filename.replace("%S", str(sequencenumber)) # This section handles unique names for splitting output if job.SplitOutput: PathLog.track() if "%T" in filename and job.OrderOutputBy == "Tool": filename = filename.replace("%T", subpartname) if "%t" in filename and job.OrderOutputBy == "Tool": filename = filename.replace("%t", subpartname) if "%W" in filename and job.OrderOutputBy == "Fixture": filename = filename.replace("%W", subpartname) if "%O" in filename and job.OrderOutputBy == "Operation": filename = filename.replace("%O", subpartname) if ( "%S" in filename ): # We always add a sequence number but the user can say where filename = filename.replace("%S", str(sequencenumber)) else: filename = f"{filename}-{sequencenumber}" PathLog.track(f"filename after substitution: {filename}") if not ext: ext = ".nc" PathLog.track(f"file extension: {ext}") fullPath = f"{outputpath}{os.path.sep}{filename}{ext}" PathLog.track(f"full filepath: {fullPath}") # This section determines whether user interaction is necessary policy = PathPreferences.defaultOutputPolicy() openDialog = policy == "Open File Dialog" # if os.path.isdir(filename) or not os.path.isdir(os.path.dirname(filename)): # # Either the entire filename resolves into a directory or the parent directory doesn't exist. # # Either way I don't know what to do - ask for help # openDialog = True if not FreeCAD.GuiUp: # if testing, or scripting, never open dialog. policy = "Append Unique ID on conflict" openDialog = False if os.path.isfile(fullPath) and not openDialog: if policy == "Open File Dialog on conflict": openDialog = True elif policy == "Append Unique ID on conflict": fn, ext = os.path.splitext(fullPath) nr = fn[-3:] n = 1 if nr.isdigit(): n = int(nr) while os.path.isfile("%s%03d%s" % (fn, n, ext)): n = n + 1 fullPath = "%s%03d%s" % (fn, n, ext) if openDialog: foo = QtGui.QFileDialog.getSaveFileName( QtGui.QApplication.activeWindow(), "Output File", filename ) if foo[0]: fullPath = foo[0] else: fullPath = None # remove any unused substitution strings: for s in validPathSubstitutions + validFilenameSubstitutions: fullPath = fullPath.replace(f"%{s}", "") fullPath = os.path.normpath(fullPath) PathLog.track(fullPath) return fullPath