From 6a47bc3f5d2161c7923ead62081f90d0f3b05a00 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Fri, 3 Sep 2021 16:33:39 -0500 Subject: [PATCH 01/65] Path: Fixes #4645 Fixes bug #4645. This PR adds a simple `integrityCheck()` method to Job object class. The method is called on the Gui side before openning the task panel to edit the job. The same method is also called upon document restoration. --- src/Mod/Path/PathScripts/PathJob.py | 118 +++++++++++++++++++------ src/Mod/Path/PathScripts/PathJobGui.py | 8 +- 2 files changed, 100 insertions(+), 26 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py index 983a4a7473..63a452c0d4 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/PathScripts/PathJob.py @@ -20,8 +20,6 @@ # * * # *************************************************************************** -from PathScripts.PathPostProcessor import PostProcessor -from PySide import QtCore import FreeCAD import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences @@ -31,6 +29,8 @@ import PathScripts.PathToolController as PathToolController import PathScripts.PathUtil as PathUtil import json import time +from PathScripts.PathPostProcessor import PostProcessor +from PySide import QtCore # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader @@ -75,7 +75,6 @@ def isResourceClone(obj, propLink, resourceName): def createResourceClone(obj, orig, name, icon): - clone = Draft.clone(orig) clone.Label = "%s-%s" % (name, orig.Label) clone.addProperty("App::PropertyString", "PathResource") @@ -102,9 +101,12 @@ Notification = NotificationClass() class ObjectJob: - def __init__(self, obj, models, templateFile=None): self.obj = obj + self.tooltip = None + self.tooltipArgs = None + obj.Proxy = self + obj.addProperty( "App::PropertyFile", "PostProcessorOutputFile", @@ -217,6 +219,15 @@ class ObjectJob: obj.PostProcessorArgs = PathPreferences.defaultPostProcessorArgs() obj.GeometryTolerance = PathPreferences.defaultGeometryTolerance() + self.setupOperations(obj) + self.setupSetupSheet(obj) + self.setupBaseModel(obj, models) + self.setupToolTable(obj) + self.setFromTemplateFile(obj, templateFile) + self.setupStock(obj) + + def setupOperations(self, obj): + """setupOperations(obj)... setup the Operations group for the Job object.""" ops = FreeCAD.ActiveDocument.addObject( "Path::FeatureCompoundPython", "Operations" ) @@ -228,25 +239,6 @@ class ObjectJob: obj.setEditorMode("Operations", 2) # hide obj.setEditorMode("Placement", 2) - self.setupSetupSheet(obj) - self.setupBaseModel(obj, models) - self.setupToolTable(obj) - - self.tooltip = None - self.tooltipArgs = None - - obj.Proxy = self - - self.setFromTemplateFile(obj, templateFile) - if not obj.Stock: - stockTemplate = PathPreferences.defaultStockTemplate() - if stockTemplate: - obj.Stock = PathStock.CreateFromTemplate(obj, json.loads(stockTemplate)) - if not obj.Stock: - obj.Stock = PathStock.CreateFromBase(obj) - if obj.Stock.ViewObject: - obj.Stock.ViewObject.Visibility = False - def setupSetupSheet(self, obj): if not getattr(obj, "SetupSheet", None): obj.addProperty( @@ -264,10 +256,13 @@ class ObjectJob: PathScripts.PathIconViewProvider.Attach( obj.SetupSheet.ViewObject, "SetupSheet" ) + obj.SetupSheet.Label = "SetupSheet" self.setupSheet = obj.SetupSheet.Proxy def setupBaseModel(self, obj, models=None): PathLog.track(obj.Label, models) + addModels = False + if not hasattr(obj, "Model"): obj.addProperty( "App::PropertyLink", @@ -277,6 +272,11 @@ class ObjectJob: "PathJob", "The base objects for all operations" ), ) + addModels = True + elif obj.Model is None: + addModels = True + + if addModels: model = FreeCAD.ActiveDocument.addObject( "App::DocumentObjectGroup", "Model" ) @@ -287,6 +287,7 @@ class ObjectJob: [createModelResourceClone(obj, base) for base in models] ) obj.Model = model + obj.Model.Label = "Model" if hasattr(obj, "Base"): PathLog.info( @@ -297,6 +298,7 @@ class ObjectJob: obj.removeProperty("Base") def setupToolTable(self, obj): + addTable = False if not hasattr(obj, "Tools"): obj.addProperty( "App::PropertyLink", @@ -306,6 +308,11 @@ class ObjectJob: "PathJob", "Collection of all tool controllers for the job" ), ) + addTable = True + elif obj.Tools is None: + addTable = True + + if addTable: toolTable = FreeCAD.ActiveDocument.addObject( "App::DocumentObjectGroup", "Tools" ) @@ -317,6 +324,17 @@ class ObjectJob: obj.removeProperty("ToolController") obj.Tools = toolTable + def setupStock(self, obj): + """setupStock(obj)... setup the Stock for the Job object.""" + if not obj.Stock: + stockTemplate = PathPreferences.defaultStockTemplate() + if stockTemplate: + obj.Stock = PathStock.CreateFromTemplate(obj, json.loads(stockTemplate)) + if not obj.Stock: + obj.Stock = PathStock.CreateFromBase(obj) + if obj.Stock.ViewObject: + obj.Stock.ViewObject.Visibility = False + def removeBase(self, obj, base, removeFromModel): if isResourceClone(obj, base, None): PathUtil.clearExpressionEngine(base) @@ -403,13 +421,17 @@ class ObjectJob: obj.Operations.Group = [] obj.Operations = ops FreeCAD.ActiveDocument.removeObject(name) - ops.Label = label + if label == "Unnamed": + ops.Label = "Operations" + else: + ops.Label = label def onDocumentRestored(self, obj): self.setupBaseModel(obj) self.fixupOperations(obj) self.setupSetupSheet(obj) self.setupToolTable(obj) + self.integrityCheck(obj) obj.setEditorMode("Operations", 2) # hide obj.setEditorMode("Placement", 2) @@ -624,7 +646,10 @@ class ObjectJob: def nextToolNumber(self): # returns the next available toolnumber in the job group = self.obj.Tools.Group - return sorted([t.ToolNumber for t in group])[-1] + 1 + if len(group) > 0: + return sorted([t.ToolNumber for t in group])[-1] + 1 + else: + return 1 def addToolController(self, tc): group = self.obj.Tools.Group @@ -680,6 +705,49 @@ class ObjectJob: for op in self.allOperations(): op.Path.Center = center + def integrityCheck(self, job): + """integrityCheck(job)... Return True if job has all expected children objects. Attempts to restore any missing children.""" + suffix = "" + if len(job.Name) > 3: + suffix = job.Name[3:] + + def errorMessage(grp, job): + PathLog.error( + translate("PathJobGui", "{} corrupt in {} job.".format(grp, job.Name)) + ) + + if not job.Operations: + self.setupOperations(job) + job.Operations.Label = "Operations" + suffix + if not job.Operations: + errorMessage("Operations", job) + return False + if not job.SetupSheet: + self.setupSetupSheet(job) + job.SetupSheet.Label = "SetupSheet" + suffix + if not job.SetupSheet: + errorMessage("SetupSheet", job) + return False + if not job.Model: + self.setupBaseModel(job) + job.Model.Label = "Model" + suffix + if not job.Model: + errorMessage("Model", job) + return False + if not job.Stock: + self.setupStock(job) + job.Stock.Label = "Stock" + suffix + if not job.Stock: + errorMessage("Stock", job) + return False + if not job.Tools: + self.setupToolTable(job) + job.Tools.Label = "Tools" + suffix + if not job.Tools: + errorMessage("Tools", job) + return False + return True + @classmethod def baseCandidates(cls): """Answer all objects in the current document which could serve as a Base for a job.""" diff --git a/src/Mod/Path/PathScripts/PathJobGui.py b/src/Mod/Path/PathScripts/PathJobGui.py index cc322b5069..ff040ce98a 100644 --- a/src/Mod/Path/PathScripts/PathJobGui.py +++ b/src/Mod/Path/PathScripts/PathJobGui.py @@ -167,6 +167,9 @@ class ViewProvider: def setEdit(self, vobj=None, mode=0): PathLog.track(mode) if 0 == mode: + job = self.vobj.Object + if not job.Proxy.integrityCheck(job): + return False self.openTaskPanel() return True @@ -1413,7 +1416,10 @@ class TaskPanel: def setupUi(self, activate): self.setupGlobal.setupUi() - self.setupOps.setupUi() + try: + self.setupOps.setupUi() + except Exception as ee: + PathLog.error(str(ee)) self.updateStockEditor(-1, False) self.setFields() From 1e1f1343611086277a6e4e78985bd6ec7698e6f7 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 29 Dec 2021 10:28:51 -0600 Subject: [PATCH 02/65] fix #4810 use subprocess.Popen() to avoid executing arbitrary code --- src/Mod/Path/PathScripts/PathSanity.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathSanity.py b/src/Mod/Path/PathScripts/PathSanity.py index 4b8ee5218b..6c5e0ac3f4 100644 --- a/src/Mod/Path/PathScripts/PathSanity.py +++ b/src/Mod/Path/PathScripts/PathSanity.py @@ -40,6 +40,7 @@ from collections import Counter from datetime import datetime import os import webbrowser +import subprocess # Qt translation handling @@ -464,7 +465,9 @@ class CommandPathSanity: ) try: - result = os.system("asciidoctor {} -o {}".format(reportraw, reporthtml)) + result = subprocess.Popen( + "asciidoctor {} -o {}".format(reportraw, reporthtml) + ) if str(result) == "32512": msg = "asciidoctor not found. html cannot be generated." QtGui.QMessageBox.information(None, "Path Sanity", msg) From 189d3c72a290ae2c7a5d31e1640350a3ebc5066a Mon Sep 17 00:00:00 2001 From: sliptonic Date: Thu, 30 Dec 2021 16:58:45 -0600 Subject: [PATCH 03/65] use subprocess.Popen() the right way --- src/Mod/Path/PathScripts/PathSanity.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSanity.py b/src/Mod/Path/PathScripts/PathSanity.py index 6c5e0ac3f4..0e48f247ef 100644 --- a/src/Mod/Path/PathScripts/PathSanity.py +++ b/src/Mod/Path/PathScripts/PathSanity.py @@ -465,9 +465,7 @@ class CommandPathSanity: ) try: - result = subprocess.Popen( - "asciidoctor {} -o {}".format(reportraw, reporthtml) - ) + result = subprocess.Popen(["asciidoctor", reportraw, "-o", reporthtml]) if str(result) == "32512": msg = "asciidoctor not found. html cannot be generated." QtGui.QMessageBox.information(None, "Path Sanity", msg) From 7558945c2ba59b0ffffb512f664480ddda07496b Mon Sep 17 00:00:00 2001 From: sliptonic Date: Fri, 31 Dec 2021 10:04:17 -0600 Subject: [PATCH 04/65] Use run() instead of Popen() to avoid need for communicate() call. --- src/Mod/Path/PathScripts/PathSanity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathSanity.py b/src/Mod/Path/PathScripts/PathSanity.py index 0e48f247ef..cfc46f4149 100644 --- a/src/Mod/Path/PathScripts/PathSanity.py +++ b/src/Mod/Path/PathScripts/PathSanity.py @@ -465,7 +465,7 @@ class CommandPathSanity: ) try: - result = subprocess.Popen(["asciidoctor", reportraw, "-o", reporthtml]) + result = subprocess.run(["asciidoctor", reportraw, "-o", reporthtml]) if str(result) == "32512": msg = "asciidoctor not found. html cannot be generated." QtGui.QMessageBox.information(None, "Path Sanity", msg) From e450e50bb276b1d52d39b35241e8680f16e4596f Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Fri, 31 Dec 2021 15:10:57 -0600 Subject: [PATCH 05/65] Addon Manager: Implement simple macro metadata cache --- src/Mod/AddonManager/AddonManager.py | 43 ++++-- src/Mod/AddonManager/addonmanager_macro.py | 98 +++++++------ .../AddonManager/addonmanager_utilities.py | 4 +- src/Mod/AddonManager/addonmanager_workers.py | 131 +++++++++++++++++- src/Mod/AddonManager/package_list.py | 9 ++ 5 files changed, 228 insertions(+), 57 deletions(-) diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index ae8b6e887b..0eb185a123 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -24,6 +24,7 @@ # * * # *************************************************************************** +from inspect import indentsize import os import shutil import stat @@ -78,6 +79,7 @@ class CommandAddonManager: "update_worker", "check_worker", "show_worker", + "cache_macros_worker", "showmacro_worker", "macro_worker", "install_worker", @@ -262,7 +264,7 @@ class CommandAddonManager: ) # set info for the progress bar: - self.dialog.progressBar.setMaximum(100) + self.dialog.progressBar.setMaximum(1000) # begin populating the table in a set of sub-threads self.startup() @@ -383,6 +385,7 @@ class CommandAddonManager: self.activate_table_widgets, self.populate_macros, self.update_metadata_cache, + self.cache_macros, self.check_updates, ] self.current_progress_region = 0 @@ -410,7 +413,6 @@ class CommandAddonManager: def populate_packages_table(self) -> None: self.item_model.clear() - self.current_progress_region += 1 use_cache = not self.update_cache if use_cache: @@ -456,7 +458,7 @@ class CommandAddonManager: if hasattr(self, "package_cache"): package_cache_path = self.get_cache_file_name("package_cache.json") with open(package_cache_path, "w") as f: - f.write(json.dumps(self.package_cache)) + f.write(json.dumps(self.package_cache, indent=" ")) def activate_table_widgets(self) -> None: self.packageList.setEnabled(True) @@ -464,7 +466,6 @@ class CommandAddonManager: self.do_next_startup_phase() def populate_macros(self) -> None: - self.current_progress_region += 1 if self.update_cache or not os.path.isfile( self.get_cache_file_name("macro_cache.json") ): @@ -495,11 +496,10 @@ class CommandAddonManager: def write_macro_cache(self): macro_cache_path = self.get_cache_file_name("macro_cache.json") with open(macro_cache_path, "w") as f: - f.write(json.dumps(self.macro_cache)) + f.write(json.dumps(self.macro_cache, indent=" ")) self.macro_cache = [] def update_metadata_cache(self) -> None: - self.current_progress_region += 1 if self.update_cache: self.update_metadata_cache_worker = UpdateMetadataCacheWorker( self.item_model.repos @@ -532,10 +532,20 @@ class CommandAddonManager: repo.icon = self.get_icon(repo, update=True) self.item_model.reload_item(repo) + def cache_macros(self) -> None: + if self.update_cache: + self.cache_macros_worker = CacheMacroCode(self.item_model.repos) + self.cache_macros_worker.status_message.connect(self.show_information) + self.cache_macros_worker.update_macro.connect(self.on_package_updated) + self.cache_macros_worker.progress_made.connect(self.update_progress_bar) + self.cache_macros_worker.finished.connect(self.do_next_startup_phase) + self.cache_macros_worker.start() + else: + self.do_next_startup_phase() + def check_updates(self) -> None: "checks every installed addon for available updates" - self.current_progress_region += 1 pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") autocheck = pref.GetBool("AutoCheck", False) if not autocheck: @@ -645,6 +655,7 @@ class CommandAddonManager: """shows generic text in the information pane (which might be collapsed)""" self.dialog.labelStatusInfo.setText(message) + self.dialog.labelStatusInfo.repaint() def show_workbench(self, repo: AddonManagerRepo) -> None: self.packageList.hide() @@ -867,12 +878,20 @@ class CommandAddonManager: def update_progress_bar(self, current_value: int, max_value: int) -> None: """Update the progress bar, showing it if it's hidden""" + if current_value < 0: + FreeCAD.Console.PrintWarning( + f"Addon Manager: Internal error, current progress value is negative in region {self.current_progress_region}" + ) + self.show_progress_widgets() - region_size = 100 / self.number_of_progress_regions - value = (self.current_progress_region - 1) * region_size + ( - current_value / max_value / self.number_of_progress_regions - ) * region_size - self.dialog.progressBar.setValue(value) + region_size = 100.0 / self.number_of_progress_regions + completed_region_portion = (self.current_progress_region - 1) * region_size + current_region_portion = (float(current_value) / float(max_value)) * region_size + value = completed_region_portion + current_region_portion + self.dialog.progressBar.setValue( + value * 10 + ) # Out of 1000 segments, so it moves sort of smoothly + self.dialog.progressBar.repaint() def toggle_details(self) -> None: if self.dialog.labelStatusInfo.isHidden(): diff --git a/src/Mod/AddonManager/addonmanager_macro.py b/src/Mod/AddonManager/addonmanager_macro.py index d602dabd15..4fbcf96067 100644 --- a/src/Mod/AddonManager/addonmanager_macro.py +++ b/src/Mod/AddonManager/addonmanager_macro.py @@ -23,10 +23,10 @@ import os import re -import sys +import io import codecs import shutil -from typing import Dict, Union, List +from typing import Dict, Tuple, List import FreeCAD @@ -56,10 +56,12 @@ class Macro(object): self.on_wiki = False self.on_git = False self.desc = "" + self.comment = "" self.code = "" self.url = "" self.version = "" self.src_filename = "" + self.author = "" self.other_files = [] self.parsed = False @@ -93,37 +95,56 @@ class Macro(object): os.path.join(FreeCAD.getUserMacroDir(True), "Macro_" + self.filename) ) - def fill_details_from_file(self, filename): - with open(filename) as f: - # Number of parsed fields of metadata. For now, __Comment__, - # __Web__, __Version__, __Files__. - number_of_required_fields = 4 - re_desc = re.compile(r"^__Comment__\s*=\s*(['\"])(.*)\1") - re_url = re.compile(r"^__Web__\s*=\s*(['\"])(.*)\1") - re_version = re.compile(r"^__Version__\s*=\s*(['\"])(.*)\1") - re_files = re.compile(r"^__Files__\s*=\s*(['\"])(.*)\1") - for line in f.readlines(): - match = re.match(re_desc, line) - if match: - self.desc = match.group(2) - number_of_required_fields -= 1 - match = re.match(re_url, line) - if match: - self.url = match.group(2) - number_of_required_fields -= 1 - match = re.match(re_version, line) - if match: - self.version = match.group(2) - number_of_required_fields -= 1 - match = re.match(re_files, line) - if match: - self.other_files = [of.strip() for of in match.group(2).split(",")] - number_of_required_fields -= 1 - if number_of_required_fields <= 0: - break - f.seek(0) + def fill_details_from_file(self, filename: str) -> None: + with open(filename, errors="replace") as f: self.code = f.read() - self.parsed = True + self.fill_details_from_code(self.code) + + def fill_details_from_code(self, code: str) -> None: + # Number of parsed fields of metadata. Overrides anything set previously (the code is considered authoritative). + # For now: + # __Comment__ + # __Web__ + # __Version__ + # __Files__ + # __Author__ + number_of_fields = 5 + re_comment = re.compile( + r"^__Comment__\s*=\s*(['\"])(.*)\1", flags=re.IGNORECASE + ) + re_url = re.compile(r"^__Web__\s*=\s*(['\"])(.*)\1", flags=re.IGNORECASE) + re_version = re.compile( + r"^__Version__\s*=\s*(['\"])(.*)\1", flags=re.IGNORECASE + ) + re_files = re.compile(r"^__Files__\s*=\s*(['\"])(.*)\1", flags=re.IGNORECASE) + re_author = re.compile(r"^__Author__\s*=\s*(['\"])(.*)\1", flags=re.IGNORECASE) + + f = io.StringIO(code) + while f: + line = f.readline() + match = re.match(re_comment, line) + if match: + self.comment = match.group(2) + number_of_fields -= 1 + match = re.match(re_author, line) + if match: + self.author = match.group(2) + number_of_fields -= 1 + match = re.match(re_url, line) + if match: + self.url = match.group(2) + number_of_fields -= 1 + match = re.match(re_version, line) + if match: + self.version = match.group(2) + number_of_fields -= 1 + match = re.match(re_files, line) + if match: + self.other_files = [of.strip() for of in match.group(2).split(",")] + number_of_fields -= 1 + if number_of_fields <= 0: + break + self.parsed = True def fill_details_from_wiki(self, url): code = "" @@ -157,16 +178,9 @@ class Macro(object): + "\n" ) return - # code = u2.read() - # github is slow to respond... We need to use this trick below response = "" block = 8192 - # expected = int(u2.headers["content-length"]) - while True: - # print("expected:", expected, "got:", len(response)) - data = u2.read(block) - if not data: - break + while data := u2.read(block): if isinstance(data, bytes): data = data.decode("utf-8") response += data @@ -213,9 +227,9 @@ class Macro(object): flat_code += chunk code = flat_code self.code = code - self.parsed = True + self.fill_details_from_code(self.code) - def install(self, macro_dir: str) -> (bool, List[str]): + def install(self, macro_dir: str) -> Tuple[bool, List[str]]: """Install a macro and all its related files Returns True if the macro was installed correctly. diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py index 532c46ec67..c7f516b9ef 100644 --- a/src/Mod/AddonManager/addonmanager_utilities.py +++ b/src/Mod/AddonManager/addonmanager_utilities.py @@ -29,11 +29,13 @@ import sys import ctypes import tempfile import ssl +from typing import Union import urllib from urllib.request import Request from urllib.error import URLError from urllib.parse import urlparse +from http.client import HTTPResponse from PySide2 import QtGui, QtCore, QtWidgets @@ -94,7 +96,7 @@ def symlink(source, link_name): raise ctypes.WinError() -def urlopen(url: str): +def urlopen(url: str) -> Union[None, HTTPResponse]: """Opens an url with urllib and streams it to a temp file""" timeout = 5 diff --git a/src/Mod/AddonManager/addonmanager_workers.py b/src/Mod/AddonManager/addonmanager_workers.py index 1460301b11..8a65498ae3 100644 --- a/src/Mod/AddonManager/addonmanager_workers.py +++ b/src/Mod/AddonManager/addonmanager_workers.py @@ -31,6 +31,7 @@ import hashlib import threading import queue import io +import time from datetime import datetime from typing import Union, List @@ -637,6 +638,128 @@ class FillMacroListWorker(QtCore.QThread): self.add_macro_signal.emit(repo) +class CacheMacroCode(QtCore.QThread): + """Download and cache the macro code, and parse its internal metadata""" + + status_message = QtCore.Signal(str) + update_macro = QtCore.Signal(AddonManagerRepo) + progress_made = QtCore.Signal(int, int) + + def __init__(self, repos: List[AddonManagerRepo]) -> None: + QtCore.QThread.__init__(self) + self.repos = repos + self.workers = [] + self.terminators = [] + self.lock = threading.Lock() + self.failed = [] + self.counter = 0 + + def run(self): + self.status_message.emit(translate("AddonsInstaller", "Caching macro code...")) + + self.repo_queue = queue.Queue() + current_thread = QtCore.QThread.currentThread() + num_macros = 0 + for repo in self.repos: + if repo.macro is not None: + self.repo_queue.put(repo) + num_macros += 1 + + # Emulate QNetworkAccessManager and spool up six connections: + for _ in range(6): + self.update_and_advance(None) + + while True: + if current_thread.isInterruptionRequested(): + for worker in self.workers: + worker.requestInterruption() + worker.wait(100) + if not worker.isFinished(): + # Kill it + worker.terminate() + return + # Ensure our signals propagate out by running an internal thread-local event loop + QtCore.QCoreApplication.processEvents() + with self.lock: + if self.counter >= num_macros: + break + time.sleep(0.1) + + # Make sure all of our child threads have fully exited: + for i, worker in enumerate(self.workers): + worker.wait(50) + if not worker.isFinished(): + FreeCAD.Console.PrintError( + f"Addon Manager: a worker process failed to complete while fetching {worker.macro.name}\n" + ) + worker.terminate() + + self.repo_queue.join() + for terminator in self.terminators: + if terminator and terminator.isActive(): + terminator.stop() + + FreeCAD.Console.PrintMessage( + f"Out of {num_macros} macros, {len(self.failed)} failed" + ) + + def update_and_advance(self, repo: AddonManagerRepo) -> None: + if repo is not None: + if repo.macro.name not in self.failed: + self.update_macro.emit(repo) + self.repo_queue.task_done() + with self.lock: + self.counter += 1 + + if QtCore.QThread.currentThread().isInterruptionRequested(): + return + + self.progress_made.emit( + len(self.repos) - self.repo_queue.qsize(), len(self.repos) + ) + + try: + next_repo = self.repo_queue.get_nowait() + worker = GetMacroDetailsWorker(next_repo) + worker.finished.connect(lambda: self.update_and_advance(next_repo)) + with self.lock: + self.workers.append(worker) + self.terminators.append( + QtCore.QTimer.singleShot(10000, lambda: self.terminate(worker)) + ) + self.status_message.emit( + translate( + "AddonsInstaller", + f"Getting metadata from macro {next_repo.macro.name}", + ) + ) + worker.start() + except queue.Empty: + pass + + def terminate(self, worker) -> None: + if not worker.isFinished(): + macro_name = worker.macro.name + FreeCAD.Console.PrintWarning( + translate( + "AddonsInstaller", + f"Timeout while fetching metadata for macro {macro_name}", + ) + + "\n" + ) + worker.requestInterruption() + worker.wait(100) + if worker.isRunning(): + worker.terminate() + worker.wait(50) + if worker.isRunning(): + FreeCAD.Console.PrintError( + f"Failed to kill process for macro {macro_name}!\n" + ) + with self.lock: + self.failed.append(macro_name) + + class ShowWorker(QtCore.QThread): """This worker retrieves info of a given workbench""" @@ -1545,11 +1668,15 @@ class UpdateAllWorker(QtCore.QThread): self.done.emit() def on_success(self, repo: AddonManagerRepo) -> None: - self.progress_made.emit(self.repo_queue.qsize(), len(self.repos)) + self.progress_made.emit( + len(self.repos) - self.repo_queue.qsize(), len(self.repos) + ) self.success.emit(repo) def on_failure(self, repo: AddonManagerRepo) -> None: - self.progress_made.emit(self.repo_queue.qsize(), len(self.repos)) + self.progress_made.emit( + len(self.repos) - self.repo_queue.qsize(), len(self.repos) + ) self.failure.emit(repo) diff --git a/src/Mod/AddonManager/package_list.py b/src/Mod/AddonManager/package_list.py index 17a2a78e08..2e7e677dee 100644 --- a/src/Mod/AddonManager/package_list.py +++ b/src/Mod/AddonManager/package_list.py @@ -358,6 +358,15 @@ class PackageListItemDelegate(QStyledItemDelegate): f"\n{maintainer['name']} <{maintainer['email']}>" ) self.widget.ui.labelMaintainer.setText(maintainers_string) + elif repo.macro and repo.macro.parsed: + if repo.macro.comment: + self.widget.ui.labelDescription.setText(repo.macro.comment) + elif repo.macro.desc: + comment, _, _ = repo.desc.partition(" Date: Fri, 31 Dec 2021 16:02:15 -0600 Subject: [PATCH 06/65] Addon Manager: Improve macro scanning performance --- src/Mod/AddonManager/addonmanager_macro.py | 36 ++++++++++++++++------ src/Mod/AddonManager/package_details.py | 2 +- src/Mod/AddonManager/package_list.py | 18 +++++++---- 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/Mod/AddonManager/addonmanager_macro.py b/src/Mod/AddonManager/addonmanager_macro.py index 4fbcf96067..ffd619cab7 100644 --- a/src/Mod/AddonManager/addonmanager_macro.py +++ b/src/Mod/AddonManager/addonmanager_macro.py @@ -26,6 +26,7 @@ import re import io import codecs import shutil +import time from typing import Dict, Tuple, List import FreeCAD @@ -64,6 +65,7 @@ class Macro(object): self.author = "" self.other_files = [] self.parsed = False + self.parse_time = 0.0 def __eq__(self, other): return self.filename == other.filename @@ -108,23 +110,29 @@ class Macro(object): # __Version__ # __Files__ # __Author__ + start = time.perf_counter() + max_lines_to_search = 50 + line_counter = 0 number_of_fields = 5 - re_comment = re.compile( - r"^__Comment__\s*=\s*(['\"])(.*)\1", flags=re.IGNORECASE - ) - re_url = re.compile(r"^__Web__\s*=\s*(['\"])(.*)\1", flags=re.IGNORECASE) - re_version = re.compile( - r"^__Version__\s*=\s*(['\"])(.*)\1", flags=re.IGNORECASE - ) - re_files = re.compile(r"^__Files__\s*=\s*(['\"])(.*)\1", flags=re.IGNORECASE) - re_author = re.compile(r"^__Author__\s*=\s*(['\"])(.*)\1", flags=re.IGNORECASE) + ic = re.IGNORECASE # Shorten the line for Black + re_comment = re.compile(r"^__Comment__\s*=\s*(['\"])(.*)\1", flags=ic) + re_url = re.compile(r"^__Web__\s*=\s*(['\"])(.*)\1", flags=ic) + re_version = re.compile(r"^__Version__\s*=\s*(['\"])(.*)\1", flags=ic) + re_files = re.compile(r"^__Files__\s*=\s*(['\"])(.*)\1", flags=ic) + re_author = re.compile(r"^__Author__\s*=\s*(['\"])(.*)\1", flags=ic) f = io.StringIO(code) - while f: + while f and line_counter < max_lines_to_search: line = f.readline() + line_counter += 1 + if not line.startswith( + "__" + ): # Speed things up a bit... this comparison is very cheap + continue match = re.match(re_comment, line) if match: self.comment = match.group(2) + self.comment = re.sub("<.*?>", "", self.comment) # Strip any HTML tags number_of_fields -= 1 match = re.match(re_author, line) if match: @@ -144,7 +152,13 @@ class Macro(object): number_of_fields -= 1 if number_of_fields <= 0: break + + # Truncate long comments to speed up searches, and clean up display + if len(self.comment) > 512: + self.comment = self.comment[:511] + "…" self.parsed = True + end = time.perf_counter() + self.parse_time = end - start def fill_details_from_wiki(self, url): code = "" @@ -220,6 +234,8 @@ class Macro(object): ) desc = "No description available" self.desc = desc + self.comment, _, _ = desc.partition("", "", self.comment) # Strip any tags self.url = url if isinstance(code, list): flat_code = "" diff --git a/src/Mod/AddonManager/package_details.py b/src/Mod/AddonManager/package_details.py index 2e06781a3a..2e4a115def 100644 --- a/src/Mod/AddonManager/package_details.py +++ b/src/Mod/AddonManager/package_details.py @@ -207,7 +207,7 @@ class PackageDetails(QWidget): self.ui.buttonCheckForUpdate.hide() warningColorString = "rgb(255,0,0)" - if hasattr(QApplication.instance(),"styleSheet"): + if hasattr(QApplication.instance(), "styleSheet"): # Qt 5.9 doesn't give a QApplication instance, so can't give the stylesheet info if "dark" in QApplication.instance().styleSheet().lower(): warningColorString = "rgb(255,50,50)" diff --git a/src/Mod/AddonManager/package_list.py b/src/Mod/AddonManager/package_list.py index 2e7e677dee..f0e30a83a4 100644 --- a/src/Mod/AddonManager/package_list.py +++ b/src/Mod/AddonManager/package_list.py @@ -359,14 +359,14 @@ class PackageListItemDelegate(QStyledItemDelegate): ) self.widget.ui.labelMaintainer.setText(maintainers_string) elif repo.macro and repo.macro.parsed: - if repo.macro.comment: - self.widget.ui.labelDescription.setText(repo.macro.comment) - elif repo.macro.desc: - comment, _, _ = repo.desc.partition(" Date: Fri, 31 Dec 2021 17:11:23 -0600 Subject: [PATCH 07/65] Addon Manager: Fix macro cache loading --- src/Mod/AddonManager/AddonManager.py | 53 ++++++++++++-------- src/Mod/AddonManager/addonmanager_workers.py | 2 - src/Mod/AddonManager/package_list.py | 2 + 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index 0eb185a123..e30c0d6924 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -79,11 +79,11 @@ class CommandAddonManager: "update_worker", "check_worker", "show_worker", - "cache_macros_worker", "showmacro_worker", "macro_worker", "install_worker", "update_metadata_cache_worker", + "load_macro_metadata_worker", "update_all_worker", "update_check_single_worker", ] @@ -385,7 +385,7 @@ class CommandAddonManager: self.activate_table_widgets, self.populate_macros, self.update_metadata_cache, - self.cache_macros, + self.load_macro_metadata, self.check_updates, ] self.current_progress_region = 0 @@ -466,32 +466,36 @@ class CommandAddonManager: self.do_next_startup_phase() def populate_macros(self) -> None: - if self.update_cache or not os.path.isfile( - self.get_cache_file_name("macro_cache.json") - ): + macro_cache_file = self.get_cache_file_name("macro_cache.json") + cache_is_bad = True + if os.path.isfile(macro_cache_file): + size = os.path.getsize(macro_cache_file) + if size > 1000: # Make sure there is actually data in there + cache_is_bad = False + if self.update_cache or cache_is_bad: self.macro_worker = FillMacroListWorker(self.get_cache_file_name("Macros")) self.macro_worker.status_message_signal.connect(self.show_information) self.macro_worker.progress_made.connect(self.update_progress_bar) self.macro_worker.add_macro_signal.connect(self.add_addon_repo) - self.macro_worker.finished.connect( - self.do_next_startup_phase - ) # Link to step 3 + self.macro_worker.finished.connect(self.do_next_startup_phase) self.macro_worker.start() else: self.macro_worker = LoadMacrosFromCacheWorker( self.get_cache_file_name("macro_cache.json") ) self.macro_worker.add_macro_signal.connect(self.add_addon_repo) - self.macro_worker.finished.connect( - self.do_next_startup_phase - ) # Link to step 3 + self.macro_worker.finished.connect(self.do_next_startup_phase) self.macro_worker.start() - def cache_macro(self, macro: AddonManagerRepo): + def cache_macro(self, repo: AddonManagerRepo): if not hasattr(self, "macro_cache"): self.macro_cache = [] - if macro.macro is not None: - self.macro_cache.append(macro.macro.to_cache()) + if repo.macro is not None: + self.macro_cache.append(repo.macro.to_cache()) + else: + FreeCAD.Console.PrintError( + f"Addon Manager: Internal error, cache_macro called on non-macro {repo.name}\n" + ) def write_macro_cache(self): macro_cache_path = self.get_cache_file_name("macro_cache.json") @@ -528,18 +532,23 @@ class CommandAddonManager: """Called when the named package has either new metadata or a new icon (or both)""" with self.lock: - self.cache_package(repo) repo.icon = self.get_icon(repo, update=True) self.item_model.reload_item(repo) - def cache_macros(self) -> None: + def load_macro_metadata(self) -> None: if self.update_cache: - self.cache_macros_worker = CacheMacroCode(self.item_model.repos) - self.cache_macros_worker.status_message.connect(self.show_information) - self.cache_macros_worker.update_macro.connect(self.on_package_updated) - self.cache_macros_worker.progress_made.connect(self.update_progress_bar) - self.cache_macros_worker.finished.connect(self.do_next_startup_phase) - self.cache_macros_worker.start() + self.load_macro_metadata_worker = CacheMacroCode(self.item_model.repos) + self.load_macro_metadata_worker.status_message.connect( + self.show_information + ) + self.load_macro_metadata_worker.update_macro.connect( + self.on_package_updated + ) + self.load_macro_metadata_worker.progress_made.connect( + self.update_progress_bar + ) + self.load_macro_metadata_worker.finished.connect(self.do_next_startup_phase) + self.load_macro_metadata_worker.start() else: self.do_next_startup_phase() diff --git a/src/Mod/AddonManager/addonmanager_workers.py b/src/Mod/AddonManager/addonmanager_workers.py index 8a65498ae3..e57e842641 100644 --- a/src/Mod/AddonManager/addonmanager_workers.py +++ b/src/Mod/AddonManager/addonmanager_workers.py @@ -286,7 +286,6 @@ class LoadPackagesFromCacheWorker(QtCore.QThread): class LoadMacrosFromCacheWorker(QtCore.QThread): add_macro_signal = QtCore.Signal(object) - done = QtCore.Signal() def __init__(self, cache_file: str): QtCore.QThread.__init__(self) @@ -301,7 +300,6 @@ class LoadMacrosFromCacheWorker(QtCore.QThread): return new_macro = Macro.from_cache(item) self.add_macro_signal.emit(AddonManagerRepo.from_macro(new_macro)) - self.done.emit() class CheckWorkbenchesForUpdatesWorker(QtCore.QThread): diff --git a/src/Mod/AddonManager/package_list.py b/src/Mod/AddonManager/package_list.py index f0e30a83a4..fd3a35cc02 100644 --- a/src/Mod/AddonManager/package_list.py +++ b/src/Mod/AddonManager/package_list.py @@ -367,6 +367,8 @@ class PackageListItemDelegate(QStyledItemDelegate): self.widget.ui.labelMaintainer.setText( caption + ": " + repo.macro.author ) + else: + self.widget.ui.labelMaintainer.setText("") else: self.widget.ui.labelDescription.setText("") self.widget.ui.labelVersion.setText("") From 382eabbb622c2926af6ccf0ca6201910d5a8bed8 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Fri, 31 Dec 2021 17:46:13 -0600 Subject: [PATCH 08/65] Addon Manager: Improve wiki page data extraction --- src/Mod/AddonManager/addonmanager_macro.py | 23 ++++++++++++++++---- src/Mod/AddonManager/addonmanager_workers.py | 11 +++++++--- src/Mod/AddonManager/package_list.py | 15 +++++++++++++ 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/Mod/AddonManager/addonmanager_macro.py b/src/Mod/AddonManager/addonmanager_macro.py index ffd619cab7..d2ee65a41a 100644 --- a/src/Mod/AddonManager/addonmanager_macro.py +++ b/src/Mod/AddonManager/addonmanager_macro.py @@ -27,7 +27,7 @@ import io import codecs import shutil import time -from typing import Dict, Tuple, List +from typing import Dict, Tuple, List, Union import FreeCAD @@ -61,6 +61,7 @@ class Macro(object): self.code = "" self.url = "" self.version = "" + self.date = "" self.src_filename = "" self.author = "" self.other_files = [] @@ -110,7 +111,7 @@ class Macro(object): # __Version__ # __Files__ # __Author__ - start = time.perf_counter() + # __Date__ max_lines_to_search = 50 line_counter = 0 number_of_fields = 5 @@ -120,6 +121,7 @@ class Macro(object): re_version = re.compile(r"^__Version__\s*=\s*(['\"])(.*)\1", flags=ic) re_files = re.compile(r"^__Files__\s*=\s*(['\"])(.*)\1", flags=ic) re_author = re.compile(r"^__Author__\s*=\s*(['\"])(.*)\1", flags=ic) + re_date = re.compile(r"^__Date__\s*=\s*(['\"])(.*)\1", flags=ic) f = io.StringIO(code) while f and line_counter < max_lines_to_search: @@ -146,6 +148,10 @@ class Macro(object): if match: self.version = match.group(2) number_of_fields -= 1 + match = re.match(re_date, line) + if match: + self.date = match.group(2) + number_of_fields -= 1 match = re.match(re_files, line) if match: self.other_files = [of.strip() for of in match.group(2).split(",")] @@ -157,8 +163,6 @@ class Macro(object): if len(self.comment) > 512: self.comment = self.comment[:511] + "…" self.parsed = True - end = time.perf_counter() - self.parse_time = end - start def fill_details_from_wiki(self, url): code = "" @@ -244,6 +248,17 @@ class Macro(object): code = flat_code self.code = code self.fill_details_from_code(self.code) + if not self.author: + self.author = self.parse_desc("Author: ") + if not self.date: + self.date = self.parse_desc("Last modified: ") + + def parse_desc(self, line_start: str) -> Union[str, None]: + components = self.desc.split(">") + for component in components: + if component.startswith(line_start): + end = component.find("<") + return component[len(line_start) : end] def install(self, macro_dir: str) -> Tuple[bool, List[str]]: """Install a macro and all its related files diff --git a/src/Mod/AddonManager/addonmanager_workers.py b/src/Mod/AddonManager/addonmanager_workers.py index e57e842641..00f4762adf 100644 --- a/src/Mod/AddonManager/addonmanager_workers.py +++ b/src/Mod/AddonManager/addonmanager_workers.py @@ -697,9 +697,14 @@ class CacheMacroCode(QtCore.QThread): if terminator and terminator.isActive(): terminator.stop() - FreeCAD.Console.PrintMessage( - f"Out of {num_macros} macros, {len(self.failed)} failed" - ) + if len(self.failed) > 0: + num_failed = len(self.failed) + FreeCAD.Console.PrintWarning( + translate( + "AddonsInstaller", + f"Out of {num_macros} macros, {num_failed} timed out while processing", + ) + ) def update_and_advance(self, repo: AddonManagerRepo) -> None: if repo is not None: diff --git a/src/Mod/AddonManager/package_list.py b/src/Mod/AddonManager/package_list.py index fd3a35cc02..247f5bac91 100644 --- a/src/Mod/AddonManager/package_list.py +++ b/src/Mod/AddonManager/package_list.py @@ -361,6 +361,21 @@ class PackageListItemDelegate(QStyledItemDelegate): elif repo.macro and repo.macro.parsed: self.widget.ui.labelDescription.setText(repo.macro.comment) self.widget.ui.labelVersion.setText(repo.macro.version) + if repo.macro.date: + if repo.macro.version: + new_label = ( + "v" + + repo.macro.version + + ", " + + translate("AddonsInstaller", "updated") + + " " + + repo.macro.date + ) + else: + new_label = ( + translate("AddonsInstaller", "Updated") + " " + repo.macro.date + ) + self.widget.ui.labelVersion.setText(new_label) if self.displayStyle == ListDisplayStyle.EXPANDED: if repo.macro.author: caption = translate("AddonsInstaller", "Author") From cd51ec53c033295ef7adb79b7ddc08ce46ef5ffe Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Fri, 31 Dec 2021 17:55:23 -0600 Subject: [PATCH 09/65] Addon Manager: Remove extraneous UI elements --- src/Mod/AddonManager/AddonManager.py | 14 +------------- src/Mod/AddonManager/AddonManager.ui | 20 -------------------- 2 files changed, 1 insertion(+), 33 deletions(-) diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index e30c0d6924..fac36dc128 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -243,7 +243,6 @@ class CommandAddonManager: ) self.dialog.buttonClose.clicked.connect(self.dialog.reject) self.dialog.buttonUpdateCache.clicked.connect(self.on_buttonUpdateCache_clicked) - self.dialog.buttonShowDetails.clicked.connect(self.toggle_details) self.dialog.buttonPauseUpdate.clicked.connect(self.stop_update) self.packageList.itemSelected.connect(self.table_row_activated) self.packageList.setEnabled(False) @@ -871,7 +870,6 @@ class CommandAddonManager: self.dialog.labelStatusInfo.hide() self.dialog.progressBar.hide() self.dialog.buttonPauseUpdate.hide() - self.dialog.buttonShowDetails.hide() self.dialog.labelUpdateInProgress.hide() self.packageList.ui.lineEditFilter.setFocus() @@ -879,9 +877,7 @@ class CommandAddonManager: if self.dialog.progressBar.isHidden(): self.dialog.progressBar.show() self.dialog.buttonPauseUpdate.show() - self.dialog.buttonShowDetails.show() - self.dialog.labelStatusInfo.hide() - self.dialog.buttonShowDetails.setArrowType(QtCore.Qt.RightArrow) + self.dialog.labelStatusInfo.show() self.dialog.labelUpdateInProgress.show() def update_progress_bar(self, current_value: int, max_value: int) -> None: @@ -902,14 +898,6 @@ class CommandAddonManager: ) # Out of 1000 segments, so it moves sort of smoothly self.dialog.progressBar.repaint() - def toggle_details(self) -> None: - if self.dialog.labelStatusInfo.isHidden(): - self.dialog.labelStatusInfo.show() - self.dialog.buttonShowDetails.setArrowType(QtCore.Qt.DownArrow) - else: - self.dialog.labelStatusInfo.hide() - self.dialog.buttonShowDetails.setArrowType(QtCore.Qt.RightArrow) - def stop_update(self) -> None: self.cleanup_workers() self.hide_progress_widgets() diff --git a/src/Mod/AddonManager/AddonManager.ui b/src/Mod/AddonManager/AddonManager.ui index ac9269af0a..0b1be55d46 100644 --- a/src/Mod/AddonManager/AddonManager.ui +++ b/src/Mod/AddonManager/AddonManager.ui @@ -28,26 +28,6 @@ - - - - Show details - - - ... - - - Qt::RightArrow - - - - - - - Loading... - - - From f5f78670dd6b3a079d4f0201f6a6896b83ba38f8 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Fri, 31 Dec 2021 20:02:03 -0600 Subject: [PATCH 10/65] Addon Manager: Add preference to control macro download --- src/Mod/AddonManager/AddonManager.py | 86 +++++++---- src/Mod/AddonManager/AddonManagerOptions.ui | 17 ++- src/Mod/AddonManager/CMakeLists.txt | 1 + .../AddonManager/addonmanager_utilities.py | 11 ++ src/Mod/AddonManager/first_run.ui | 144 ++++++++++++++++++ 5 files changed, 230 insertions(+), 29 deletions(-) create mode 100644 src/Mod/AddonManager/first_run.ui diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index fac36dc128..5073d1bd7f 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -111,32 +111,64 @@ class CommandAddonManager: def Activated(self) -> None: # display first use dialog if needed - readWarningParameter = FreeCAD.ParamGet( - "User parameter:BaseApp/Preferences/Addons" - ) - readWarning = readWarningParameter.GetBool("readWarning", False) - newReadWarningParameter = FreeCAD.ParamGet( - "User parameter:Plugins/addonsRepository" - ) - readWarning |= newReadWarningParameter.GetBool("readWarning", False) + pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") + readWarning = pref.GetBool("readWarning2022", False) + if not readWarning: - if ( - QtWidgets.QMessageBox.warning( - None, - "FreeCAD", - translate( - "AddonsInstaller", - "The addons that can be installed here are not " - "officially part of FreeCAD, and are not reviewed " - "by the FreeCAD team. Make sure you know what you " - "are installing!", - ), - QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Ok, - ) - != QtWidgets.QMessageBox.StandardButton.Cancel - ): - readWarningParameter.SetBool("readWarning", True) + warning_dialog = FreeCADGui.PySideUic.loadUi( + os.path.join(os.path.dirname(__file__), "first_run.ui") + ) + autocheck = pref.GetBool("AutoCheck", False) + download_macros = pref.GetBool("DownloadMacros", False) + proxy_string = pref.GetString("ProxyUrl", "") + if pref.GetBool("NoProxyCheck", True): + proxy_option = 0 + elif pref.GetBool("SystemProxyCheck", False): + proxy_option = 1 + elif pref.GetBool("UserProxyCheck", False): + proxy_option = 2 + + def toggle_proxy_list(option: int): + if option == 2: + warning_dialog.lineEditProxy.show() + else: + warning_dialog.lineEditProxy.hide() + + warning_dialog.checkBoxAutoCheck.setChecked(autocheck) + warning_dialog.checkBoxDownloadMacroMetadata.setChecked(download_macros) + warning_dialog.comboBoxProxy.setCurrentIndex(proxy_option) + toggle_proxy_list(proxy_option) + if proxy_option == 2: + warning_dialog.lineEditProxy.setText(proxy_string) + + warning_dialog.comboBoxProxy.currentIndexChanged.connect(toggle_proxy_list) + + warning_dialog.labelWarning.setStyleSheet( + f"color:{utils.warning_color_string()};font-weight:bold;" + ) + + if warning_dialog.exec() == QtWidgets.QDialog.Accepted: readWarning = True + pref.SetBool("readWarning2022", True) + pref.SetBool("AutoCheck", warning_dialog.checkBoxAutoCheck.isChecked()) + pref.SetBool( + "DownloadMacros", + warning_dialog.checkBoxDownloadMacroMetadata.isChecked(), + ) + selected_proxy_option = warning_dialog.comboBoxProxy.currentIndex() + if selected_proxy_option == 0: + pref.SetBool("NoProxyCheck", True) + pref.SetBool("SystemProxyCheck", False) + pref.SetBool("UserProxyCheck", False) + elif selected_proxy_option == 1: + pref.SetBool("NoProxyCheck", False) + pref.SetBool("SystemProxyCheck", True) + pref.SetBool("UserProxyCheck", False) + else: + pref.SetBool("NoProxyCheck", False) + pref.SetBool("SystemProxyCheck", False) + pref.SetBool("UserProxyCheck", True) + pref.SetString("ProxyUrl", warning_dialog.lineEditProxy.text()) if readWarning: self.launch() @@ -384,9 +416,11 @@ class CommandAddonManager: self.activate_table_widgets, self.populate_macros, self.update_metadata_cache, - self.load_macro_metadata, self.check_updates, ] + pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") + if pref.GetBool("DownloadMacros", False): + self.startup_sequence.append(self.load_macro_metadata) self.current_progress_region = 0 self.number_of_progress_regions = len(self.startup_sequence) self.do_next_startup_phase() @@ -870,7 +904,6 @@ class CommandAddonManager: self.dialog.labelStatusInfo.hide() self.dialog.progressBar.hide() self.dialog.buttonPauseUpdate.hide() - self.dialog.labelUpdateInProgress.hide() self.packageList.ui.lineEditFilter.setFocus() def show_progress_widgets(self) -> None: @@ -878,7 +911,6 @@ class CommandAddonManager: self.dialog.progressBar.show() self.dialog.buttonPauseUpdate.show() self.dialog.labelStatusInfo.show() - self.dialog.labelUpdateInProgress.show() def update_progress_bar(self, current_value: int, max_value: int) -> None: """Update the progress bar, showing it if it's hidden""" diff --git a/src/Mod/AddonManager/AddonManagerOptions.ui b/src/Mod/AddonManager/AddonManagerOptions.ui index e022702023..33970bb76c 100644 --- a/src/Mod/AddonManager/AddonManagerOptions.ui +++ b/src/Mod/AddonManager/AddonManagerOptions.ui @@ -6,8 +6,8 @@ 0 0 - 390 - 628 + 388 + 621 @@ -35,6 +35,19 @@ installed addons will be checked for available updates + + + + Download Macro metadata (approximately 10MB) + + + DownloadMacros + + + Addons + + + diff --git a/src/Mod/AddonManager/CMakeLists.txt b/src/Mod/AddonManager/CMakeLists.txt index de711853f1..9ef2819ea1 100644 --- a/src/Mod/AddonManager/CMakeLists.txt +++ b/src/Mod/AddonManager/CMakeLists.txt @@ -13,6 +13,7 @@ SET(AddonManager_SRCS addonmanager_workers.py AddonManager.ui AddonManagerOptions.ui + first_run.ui compact_view.py expanded_view.py package_list.py diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py index c7f516b9ef..a0cd0236fe 100644 --- a/src/Mod/AddonManager/addonmanager_utilities.py +++ b/src/Mod/AddonManager/addonmanager_utilities.py @@ -300,4 +300,15 @@ def fix_relative_links(text, base_url): return new_text +def warning_color_string() -> str: + warningColorString = "rgb(255,0,0)" + if hasattr(QtWidgets.QApplication.instance(), "styleSheet"): + # Qt 5.9 doesn't give a QApplication instance, so can't give the stylesheet info + if "dark" in QtWidgets.QApplication.instance().styleSheet().lower(): + warningColorString = "rgb(255,50,50)" + else: + warningColorString = "rgb(200,0,0)" + return warningColorString + + # @} diff --git a/src/Mod/AddonManager/first_run.ui b/src/Mod/AddonManager/first_run.ui new file mode 100644 index 0000000000..4daf95476c --- /dev/null +++ b/src/Mod/AddonManager/first_run.ui @@ -0,0 +1,144 @@ + + + Dialog + + + Qt::WindowModal + + + + 0 + 0 + 398 + 237 + + + + Welcome to the Addon Manager + + + + + + The addons that can be installed here are not officially part of FreeCAD, and are not reviewed by the FreeCAD team. Make sure you know what you are installing! + + + true + + + + + + + Qt::Horizontal + + + + + + + + 75 + true + + + + Download Settings + + + + + + + Automatically check installed Addons for updates + + + + + + + Download Macro metadata (approximately 10MB) + + + + + + + + + + No proxy + + + + + System proxy + + + + + User-defined proxy: + + + + + + + + + + + + + These and other settings are available in the FreeCAD Preferences window. + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + From e954f677fbc52526d1aa47a01fde6198578773e1 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Fri, 31 Dec 2021 20:10:44 -0600 Subject: [PATCH 11/65] Addon Manager: Downgrade failed low-level urlopen to log --- src/Mod/AddonManager/addonmanager_utilities.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py index a0cd0236fe..8ad9df5528 100644 --- a/src/Mod/AddonManager/addonmanager_utilities.py +++ b/src/Mod/AddonManager/addonmanager_utilities.py @@ -127,9 +127,7 @@ def urlopen(url: str) -> Union[None, HTTPResponse]: u = urllib.request.urlopen(req, timeout=timeout) except URLError as e: - FreeCAD.Console.PrintError( - translate("AddonsInstaller", f"Error loading {url}") + ":\n {e.reason}\n" - ) + FreeCAD.Console.PrintLog(f"Error loading {url}:\n {e.reason}\n") return None except Exception: return None From a5e89982f499edc5d96795d797e14d39d113b230 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Fri, 31 Dec 2021 20:19:52 -0600 Subject: [PATCH 12/65] Addon Manager: Ensure cache is rebuilt after firstrun dialog --- src/Mod/AddonManager/AddonManager.py | 5 ++++- src/Mod/AddonManager/addonmanager_macro.py | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index 5073d1bd7f..d336e516ad 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -24,7 +24,6 @@ # * * # *************************************************************************** -from inspect import indentsize import os import shutil import stat @@ -155,6 +154,8 @@ class CommandAddonManager: "DownloadMacros", warning_dialog.checkBoxDownloadMacroMetadata.isChecked(), ) + if warning_dialog.checkBoxDownloadMacroMetadata.isChecked(): + self.trigger_recache = True selected_proxy_option = warning_dialog.comboBoxProxy.currentIndex() if selected_proxy_option == 0: pref.SetBool("NoProxyCheck", True) @@ -202,6 +203,8 @@ class CommandAddonManager: # 0: Update every launch # >0: Update every n days self.update_cache = False + if hasattr(self, "trigger_recache") and self.trigger_recache: + self.update_cache = True update_frequency = pref.GetInt("UpdateFrequencyComboEntry", 0) if update_frequency == 0: days_between_updates = -1 diff --git a/src/Mod/AddonManager/addonmanager_macro.py b/src/Mod/AddonManager/addonmanager_macro.py index d2ee65a41a..6e65685de8 100644 --- a/src/Mod/AddonManager/addonmanager_macro.py +++ b/src/Mod/AddonManager/addonmanager_macro.py @@ -198,7 +198,10 @@ class Macro(object): return response = "" block = 8192 - while data := u2.read(block): + while True: + data = u2.read(block) + if not data: + break if isinstance(data, bytes): data = data.decode("utf-8") response += data @@ -232,7 +235,7 @@ class Macro(object): FreeCAD.Console.PrintWarning( translate( "AddonsInstaller", - "Unable to retrieve a description for this macro.", + f"Unable to retrieve a description from the wiki for macro {self.name}", ) + "\n" ) From 6f1c2627b235c3fa37613e76596c46881b8810ad Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sat, 1 Jan 2022 09:43:06 -0600 Subject: [PATCH 13/65] Addon Manager: Remove unused imports --- src/Mod/AddonManager/AddonManager.py | 3 +-- src/Mod/AddonManager/addonmanager_macro.py | 1 - src/Mod/AddonManager/addonmanager_utilities.py | 7 +------ src/Mod/AddonManager/addonmanager_workers.py | 2 +- 4 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index d336e516ad..7253877b29 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -29,8 +29,7 @@ import shutil import stat import tempfile from datetime import date, timedelta -from typing import Dict, Union -from enum import Enum +from typing import Dict from PySide2 import QtGui, QtCore, QtWidgets import FreeCADGui diff --git a/src/Mod/AddonManager/addonmanager_macro.py b/src/Mod/AddonManager/addonmanager_macro.py index 6e65685de8..65a8739126 100644 --- a/src/Mod/AddonManager/addonmanager_macro.py +++ b/src/Mod/AddonManager/addonmanager_macro.py @@ -66,7 +66,6 @@ class Macro(object): self.author = "" self.other_files = [] self.parsed = False - self.parse_time = 0.0 def __eq__(self, other): return self.filename == other.filename diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py index 8ad9df5528..5cc3ca5950 100644 --- a/src/Mod/AddonManager/addonmanager_utilities.py +++ b/src/Mod/AddonManager/addonmanager_utilities.py @@ -21,23 +21,18 @@ # * * # *************************************************************************** -import codecs import os import re -import shutil -import sys import ctypes -import tempfile import ssl from typing import Union import urllib -from urllib.request import Request from urllib.error import URLError from urllib.parse import urlparse from http.client import HTTPResponse -from PySide2 import QtGui, QtCore, QtWidgets +from PySide2 import QtCore, QtWidgets import FreeCAD import FreeCADGui diff --git a/src/Mod/AddonManager/addonmanager_workers.py b/src/Mod/AddonManager/addonmanager_workers.py index 00f4762adf..a75d43b355 100644 --- a/src/Mod/AddonManager/addonmanager_workers.py +++ b/src/Mod/AddonManager/addonmanager_workers.py @@ -35,7 +35,7 @@ import time from datetime import datetime from typing import Union, List -from PySide2 import QtCore, QtGui, QtNetwork +from PySide2 import QtCore, QtNetwork import FreeCAD From 172243138a2c759881f6604bc37b3e49ea8bdb3b Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sat, 1 Jan 2022 11:41:23 -0600 Subject: [PATCH 14/65] Addon Manager: Improve display of installation details --- src/Mod/AddonManager/AddonManager.py | 2 + .../AddonManager/addonmanager_utilities.py | 66 ++++++++++- src/Mod/AddonManager/addonmanager_workers.py | 18 ++- src/Mod/AddonManager/package_details.py | 104 +++++++++++------- 4 files changed, 139 insertions(+), 51 deletions(-) diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index 7253877b29..13c2440cf8 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -755,6 +755,8 @@ class CommandAddonManager: real_install_succeeded, errors = macro.install(self.macro_repo_dir) if not real_install_succeeded: failed = True + else: + utils.update_macro_installation_details(repo) if not failed: message = translate( diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py index 5cc3ca5950..1ab5fafc15 100644 --- a/src/Mod/AddonManager/addonmanager_utilities.py +++ b/src/Mod/AddonManager/addonmanager_utilities.py @@ -28,6 +28,7 @@ import ssl from typing import Union import urllib +from urllib.request import Request from urllib.error import URLError from urllib.parse import urlparse from http.client import HTTPResponse @@ -294,14 +295,75 @@ def fix_relative_links(text, base_url): def warning_color_string() -> str: + """A shade of red, adapted to darkmode if possible. Targets a minimum 7:1 contrast ratio.""" + warningColorString = "rgb(255,0,0)" if hasattr(QtWidgets.QApplication.instance(), "styleSheet"): # Qt 5.9 doesn't give a QApplication instance, so can't give the stylesheet info if "dark" in QtWidgets.QApplication.instance().styleSheet().lower(): - warningColorString = "rgb(255,50,50)" + warningColorString = "rgb(255,105,97)" else: - warningColorString = "rgb(200,0,0)" + warningColorString = "rgb(215,0,21)" return warningColorString +def bright_color_string() -> str: + """A shade of green, adapted to darkmode if possible. Targets a minimum 7:1 contrast ratio.""" + + brightColorString = "rgb(0,255,0)" + if hasattr(QtWidgets.QApplication.instance(), "styleSheet"): + # Qt 5.9 doesn't give a QApplication instance, so can't give the stylesheet info + if "dark" in QtWidgets.QApplication.instance().styleSheet().lower(): + brightColorString = "rgb(48,219,91)" + else: + brightColorString = "rgb(36,138,61)" + return brightColorString + + +def attention_color_string() -> str: + """A shade of orange, adapted to darkmode if possible. Targets a minimum 7:1 contrast ratio.""" + + attentionColorString = "rgb(255,149,0)" + if hasattr(QtWidgets.QApplication.instance(), "styleSheet"): + # Qt 5.9 doesn't give a QApplication instance, so can't give the stylesheet info + if "dark" in QtWidgets.QApplication.instance().styleSheet().lower(): + attentionColorString = "rgb(255,179,64)" + else: + attentionColorString = "rgb(255,149,0)" + return attentionColorString + + +def get_macro_version_from_file(filename: str) -> str: + re_version = re.compile(r"^__Version__\s*=\s*(['\"])(.*)\1", flags=re.IGNORECASE) + with open(filename, "r", errors="ignore") as f: + line_counter = 0 + max_lines_to_scan = 50 + while line_counter < max_lines_to_scan: + line_counter += 1 + line = f.readline() + if line.startswith("__"): + match = re.match(re_version, line) + if match: + return match.group(2) + return "" + + +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 + + # @} diff --git a/src/Mod/AddonManager/addonmanager_workers.py b/src/Mod/AddonManager/addonmanager_workers.py index a75d43b355..45dd516e12 100644 --- a/src/Mod/AddonManager/addonmanager_workers.py +++ b/src/Mod/AddonManager/addonmanager_workers.py @@ -299,7 +299,9 @@ class LoadMacrosFromCacheWorker(QtCore.QThread): if QtCore.QThread.currentThread().isInterruptionRequested(): return new_macro = Macro.from_cache(item) - self.add_macro_signal.emit(AddonManagerRepo.from_macro(new_macro)) + repo = AddonManagerRepo.from_macro(new_macro) + utils.update_macro_installation_details(repo) + self.add_macro_signal.emit(repo) class CheckWorkbenchesForUpdatesWorker(QtCore.QThread): @@ -588,6 +590,7 @@ class FillMacroListWorker(QtCore.QThread): macro.src_filename = os.path.join(dirpath, filename) repo = AddonManagerRepo.from_macro(macro) repo.url = "https://github.com/FreeCAD/FreeCAD-macros.git" + utils.update_macro_installation_details(repo) self.add_macro_signal.emit(repo) def retrieve_macros_from_wiki(self): @@ -633,6 +636,7 @@ class FillMacroListWorker(QtCore.QThread): macro.on_wiki = True repo = AddonManagerRepo.from_macro(macro) repo.url = "https://wiki.freecad.org/Macros_recipes" + utils.update_macro_installation_details(repo) self.add_macro_signal.emit(repo) @@ -1053,17 +1057,8 @@ class GetMacroDetailsWorker(QtCore.QThread): mac = mac.replace("+", "%2B") url = "https://wiki.freecad.org/Macro_" + mac self.macro.fill_details_from_wiki(url) - if self.macro.is_installed(): - already_installed_msg = ( - '' - + translate("AddonsInstaller", "This macro is already installed.") - + "
" - ) - else: - already_installed_msg = "" message = ( - already_installed_msg - + "

" + "

" + self.macro.name + "

" + self.macro.desc @@ -1719,6 +1714,7 @@ class UpdateSingleWorker(QtCore.QThread): install_succeeded, errors = repo.macro.install( FreeCAD.getUserMacroDir(True) ) + utils.update_macro_installation_details(repo) if install_succeeded: self.success.emit(repo) diff --git a/src/Mod/AddonManager/package_details.py b/src/Mod/AddonManager/package_details.py index 2e4a115def..508073d7d4 100644 --- a/src/Mod/AddonManager/package_details.py +++ b/src/Mod/AddonManager/package_details.py @@ -31,12 +31,14 @@ from datetime import date, timedelta import FreeCAD -from addonmanager_utilities import translate # this needs to be as is for pylupdate +import addonmanager_utilities as utils from addonmanager_workers import ShowWorker, GetMacroDetailsWorker from AddonManagerRepo import AddonManagerRepo import inspect +translate = FreeCAD.Qt.translate + class PackageDetails(QWidget): @@ -93,31 +95,34 @@ class PackageDetails(QWidget): self.ui.buttonExecute.hide() if repo.update_status != AddonManagerRepo.UpdateStatus.NOT_INSTALLED: - installed_version_string = "" - if repo.installed_version: - installed_version_string = translate("AddonsInstaller", "Version") + " " - installed_version_string += repo.installed_version - else: - installed_version_string = ( - translate( - "AddonsInstaller", "Unknown version (no package.xml file found)" - ) - + " " - ) + version = repo.installed_version + date = "" + installed_version_string = "

" if repo.updated_timestamp: - installed_version_string += ( - " " + translate("AddonsInstaller", "installed on") + " " - ) - installed_version_string += ( + date = ( QDateTime.fromTime_t(repo.updated_timestamp) .date() .toString(Qt.SystemLocaleShortDate) ) - installed_version_string += ". " + 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") + ". " + translate("AddonsInstaller", "Installed") + ". " ) if repo.update_status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE: @@ -129,12 +134,20 @@ class PackageDetails(QWidget): ) installed_version_string += repo.metadata.Version installed_version_string += "." + elif repo.macro and repo.macro.version: + installed_version_string += ( + "" + + translate("AddonsInstaller", "Update available to version") + + " " + ) + installed_version_string += repo.macro.version + installed_version_string += "." else: installed_version_string += ( "" + translate( "AddonsInstaller", - "Update available to unknown version (no package.xml file found)", + "An update is available", ) + "." ) @@ -166,19 +179,32 @@ class PackageDetails(QWidget): + "." ) - basedir = FreeCAD.getUserAppDataDir() - moddir = os.path.join(basedir, "Mod", repo.name) - installed_version_string += ( - "
" - + translate("AddonsInstaller", "Installation location") - + ": " - + moddir + installed_version_string += "

" + 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.labelPackageDetails.setText(installed_version_string) - self.ui.labelPackageDetails.show() + 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() @@ -206,14 +232,6 @@ class PackageDetails(QWidget): self.ui.buttonUpdate.hide() self.ui.buttonCheckForUpdate.hide() - warningColorString = "rgb(255,0,0)" - if hasattr(QApplication.instance(), "styleSheet"): - # Qt 5.9 doesn't give a QApplication instance, so can't give the stylesheet info - if "dark" in QApplication.instance().styleSheet().lower(): - warningColorString = "rgb(255,50,50)" - else: - warningColorString = "rgb(200,0,0)" - if repo.obsolete: self.ui.labelWarningInfo.show() self.ui.labelWarningInfo.setText( @@ -221,7 +239,9 @@ class PackageDetails(QWidget): + translate("AddonsInstaller", "WARNING: This addon is obsolete") + "" ) - self.ui.labelWarningInfo.setStyleSheet("color:" + warningColorString) + self.ui.labelWarningInfo.setStyleSheet( + "color:" + utils.warning_color_string() + ) elif repo.python2: self.ui.labelWarningInfo.show() self.ui.labelWarningInfo.setText( @@ -229,7 +249,9 @@ class PackageDetails(QWidget): + translate("AddonsInstaller", "WARNING: This addon is Python 2 Only") + "" ) - self.ui.labelWarningInfo.setStyleSheet("color:" + warningColorString) + self.ui.labelWarningInfo.setStyleSheet( + "color:" + utils.warning_color_string() + ) else: self.ui.labelWarningInfo.hide() @@ -416,6 +438,12 @@ class Ui_PackageDetails(object): self.verticalLayout_2.addWidget(self.labelPackageDetails) + self.labelInstallationLocation = QLabel(PackageDetails) + self.labelInstallationLocation.setTextInteractionFlags(Qt.TextSelectableByMouse) + self.labelInstallationLocation.hide() + + self.verticalLayout_2.addWidget(self.labelInstallationLocation) + self.labelWarningInfo = QLabel(PackageDetails) self.labelWarningInfo.hide() From 911fd3bf179b5cc5657f886eba38f7930b82da6e Mon Sep 17 00:00:00 2001 From: Roy-043 <70520633+Roy-043@users.noreply.github.com> Date: Sat, 1 Jan 2022 19:04:21 +0100 Subject: [PATCH 15/65] Draft: fix several issues with importAirfoilDAT.py The importAirfoilDAT.py code could not handle several *.dat files posted in a forum topic. --- src/Mod/Draft/importAirfoilDAT.py | 49 ++++++++++++++++++------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/Mod/Draft/importAirfoilDAT.py b/src/Mod/Draft/importAirfoilDAT.py index 3e3e13d147..e6886f94d1 100644 --- a/src/Mod/Draft/importAirfoilDAT.py +++ b/src/Mod/Draft/importAirfoilDAT.py @@ -9,7 +9,6 @@ \brief Airfoil (.dat) file importer This module provides support for importing airfoil .dat files. -Note (2019): this module has been unmaintained for a long time. ''' # Check code with # flake8 --ignore=E226,E266,E401,W503 @@ -103,8 +102,9 @@ def open(filename): """ docname = os.path.splitext(os.path.basename(filename))[0] doc = FreeCAD.newDocument(docname) - doc.Label = decodeName(docname[:-4]) - process(doc, filename) + doc.Label = decodeName(docname) + process(filename) + doc.recompute() def insert(filename, docname): @@ -131,13 +131,16 @@ def insert(filename, docname): doc = FreeCAD.getDocument(docname) except NameError: doc = FreeCAD.newDocument(docname) - importgroup = doc.addObject("App::DocumentObjectGroup", groupname) - importgroup.Label = decodeName(groupname) - process(doc, filename) + obj = process(filename) + if obj is not None: + importgroup = doc.addObject("App::DocumentObjectGroup", groupname) + importgroup.Label = decodeName(groupname) + importgroup.Group = [obj] + doc.recompute() -def process(doc, filename): - """Process the filename and provide the document with the information. +def process(filename): + """Process the filename and create a Draft Wire from the data. The common airfoil dat format has many flavors. This code should work with almost every dialect. @@ -146,18 +149,15 @@ def process(doc, filename): ---------- filename : str The path to the filename to be opened. - docname : str - The name of the active App::Document if one exists, or - of the new one created. Returns ------- - App::Document - The active FreeCAD document, or the document created if none exists, - with the parsed information. + Part::Part2DObject or None. + The created Draft Wire object or None if the file contains less + than 3 points. """ # Regex to identify data rows and throw away unused metadata - xval = r'(?P(\-|\d*)\.*\d*([Ee]\-?\d+)?)' + xval = r'(?P\-?\s*\d*\.*\d*([Ee]\-?\d+)?)' yval = r'(?P\-?\s*\d*\.*\d*([Ee]\-?\d+)?)' _regex = r'^\s*' + xval + r'\,?\s*' + yval + r'\s*$' @@ -170,21 +170,28 @@ def process(doc, filename): # upside = True # last_x = None - # Collect the data for the upper and the lower side separately if possible + # Collect the data for lin in afile: curdat = regex.match(lin) - if curdat is not None: + if (curdat is not None + and curdat.group("xval") + and curdat.group("yval")): x = float(curdat.group("xval")) y = float(curdat.group("yval")) - # the normal processing - coords.append(Vector(x, y, 0)) + # Some files specify the number of upper and lower points on the 2nd line: + # " 67. 72." + # See: http://airfoiltools.com/airfoil + # This line must be skipped: + if x < 2 and y < 2: + # the normal processing + coords.append(Vector(x, y, 0)) afile.close() if len(coords) < 3: FCC.PrintError(translate("ImportAirfoilDAT", "Did not find enough coordinates") + "\n") - return + return None # sometimes coords are divided in upper an lower side # so that x-coordinate begin new from leading or trailing edge @@ -227,4 +234,4 @@ def process(doc, filename): obj = FreeCAD.ActiveDocument.addObject('Part::Feature', airfoilname) obj.Shape = face - doc.recompute() + return obj From 3c1531b39ac8bc6b970068923fdef352c7b18412 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 23 Dec 2021 11:50:12 +0100 Subject: [PATCH 16/65] Draft: Commands acting on subelements did not load the Draft module --- src/Mod/Draft/draftguitools/gui_move.py | 1 + src/Mod/Draft/draftguitools/gui_rotate.py | 1 + src/Mod/Draft/draftguitools/gui_scale.py | 1 + 3 files changed, 3 insertions(+) diff --git a/src/Mod/Draft/draftguitools/gui_move.py b/src/Mod/Draft/draftguitools/gui_move.py index 6c2196cb6c..803d95b55e 100644 --- a/src/Mod/Draft/draftguitools/gui_move.py +++ b/src/Mod/Draft/draftguitools/gui_move.py @@ -197,6 +197,7 @@ class Move(gui_base_original.Modifier): def move_subelements(self, is_copy): """Move the subelements.""" + Gui.addModule("Draft") try: if is_copy: self.commit(translate("draft", "Copy"), diff --git a/src/Mod/Draft/draftguitools/gui_rotate.py b/src/Mod/Draft/draftguitools/gui_rotate.py index d382e8723e..be7a5888b0 100644 --- a/src/Mod/Draft/draftguitools/gui_rotate.py +++ b/src/Mod/Draft/draftguitools/gui_rotate.py @@ -271,6 +271,7 @@ class Rotate(gui_base_original.Modifier): def rotate_subelements(self, is_copy): """Rotate the subelements.""" + Gui.addModule("Draft") try: if is_copy: self.commit(translate("draft", "Copy"), diff --git a/src/Mod/Draft/draftguitools/gui_scale.py b/src/Mod/Draft/draftguitools/gui_scale.py index 0e8e77a42c..0f1e164639 100644 --- a/src/Mod/Draft/draftguitools/gui_scale.py +++ b/src/Mod/Draft/draftguitools/gui_scale.py @@ -192,6 +192,7 @@ class Scale(gui_base_original.Modifier): the selected object is not a rectangle or another object that can't be used with `scaleVertex` and `scaleEdge`. """ + Gui.addModule("Draft") try: if self.task.isCopy.isChecked(): self.commit(translate("draft", "Copy"), From 5ade10d049e68724f6b852434d1586be5f9b4527 Mon Sep 17 00:00:00 2001 From: Jean-Marie Verdun Date: Sat, 1 Jan 2022 12:11:29 -0500 Subject: [PATCH 17/65] Fix MacBundle relocation --- src/MacAppBundle/CMakeLists.txt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/MacAppBundle/CMakeLists.txt b/src/MacAppBundle/CMakeLists.txt index c0b6ccf09f..14d547e33e 100644 --- a/src/MacAppBundle/CMakeLists.txt +++ b/src/MacAppBundle/CMakeLists.txt @@ -32,10 +32,14 @@ if(HOMEBREW_PREFIX) file(READ ${PTH_FILE} ADDITIONAL_DIR) string(STRIP "${ADDITIONAL_DIR}" ADDITIONAL_DIR) - string(REGEX REPLACE "^${HOMEBREW_PREFIX}/Cellar/([A-Za-z0-9_]+).*$" "\\1" LIB_NAME ${ADDITIONAL_DIR}) - string(REGEX REPLACE ".*libexec(.*)/site-packages" "libexec/${LIB_NAME}\\1" NEW_SITE_DIR ${ADDITIONAL_DIR}) + string(FIND "${ADDITIONAL_DIR}" "${HOMEBREW_PREFIX}/Cellar" POSITION) + string(LENGTH "${ADDITIONAL_DIR}" DIR_LENGTH) + string(SUBSTRING "${ADDITIONAL_DIR}" ${POSITION} ${DIR_LENGTH}-${POSITION} DIR_TAIL) + string(REGEX MATCHALL "^([/A-Za-z0-9_.@-]+)" CLEAR_TAIL ${DIR_TAIL}) + string(REGEX REPLACE "^${HOMEBREW_PREFIX}/Cellar/([A-Za-z0-9_]+).*$" "\\1" LIB_NAME ${CLEAR_TAIL}) + string(REGEX REPLACE ".*libexec(.*)/site-packages" "libexec/${LIB_NAME}\\1" NEW_SITE_DIR ${CLEAR_TAIL}) - install(DIRECTORY ${ADDITIONAL_DIR} DESTINATION ${CMAKE_INSTALL_PREFIX}/${NEW_SITE_DIR}) + install(DIRECTORY ${CLEAR_TAIL} DESTINATION ${CMAKE_INSTALL_PREFIX}/${NEW_SITE_DIR}) #update the paths of the .pth files copied into the bundle get_filename_component(PTH_FILENAME ${PTH_FILE} NAME) @@ -120,7 +124,7 @@ find_package(PkgConfig) pkg_check_modules(ICU icu-uc) execute_process( - COMMAND find /usr/local/Cellar/nglib -name MacOS + COMMAND find -L /usr/local/Cellar/nglib -name MacOS OUTPUT_VARIABLE CONFIG_NGLIB) install(CODE From 74dc5344d4a4e0d638fd35ae112c5bb6f68b58ad Mon Sep 17 00:00:00 2001 From: Wanderer Fan Date: Thu, 23 Dec 2021 15:29:04 -0500 Subject: [PATCH 18/65] [TD]prevent touched but must be execute on DrawPage --- src/Mod/TechDraw/App/DrawPage.cpp | 2 +- src/Mod/TechDraw/Gui/Command.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Mod/TechDraw/App/DrawPage.cpp b/src/Mod/TechDraw/App/DrawPage.cpp index f35835c0f6..75bd59e160 100644 --- a/src/Mod/TechDraw/App/DrawPage.cpp +++ b/src/Mod/TechDraw/App/DrawPage.cpp @@ -162,7 +162,7 @@ void DrawPage::onChanged(const App::Property* prop) //Page is just a container. It doesn't "do" anything. App::DocumentObjectExecReturn *DrawPage::execute(void) { - return App::DocumentObject::StdReturn; + return App::DocumentObject::execute(); } // this is now irrelevant, b/c DP::execute doesn't do anything. diff --git a/src/Mod/TechDraw/Gui/Command.cpp b/src/Mod/TechDraw/Gui/Command.cpp index da4565bfdd..d540d9f0a4 100644 --- a/src/Mod/TechDraw/Gui/Command.cpp +++ b/src/Mod/TechDraw/Gui/Command.cpp @@ -141,6 +141,7 @@ void CmdTechDrawPageDefault::activated(int iMsg) doCommand(Doc,"App.activeDocument().%s.Template = '%s'",TemplateName.c_str(), templateFileName.toStdString().c_str()); doCommand(Doc,"App.activeDocument().%s.Template = App.activeDocument().%s",PageName.c_str(),TemplateName.c_str()); + updateActive(); commitCommand(); TechDraw::DrawPage* fp = dynamic_cast(getDocument()->getObject(PageName.c_str())); if (!fp) { @@ -220,6 +221,7 @@ void CmdTechDrawPageTemplate::activated(int iMsg) doCommand(Doc,"App.activeDocument().%s.Template = App.activeDocument().%s",PageName.c_str(),TemplateName.c_str()); // consider renaming DrawSVGTemplate.Template property? + updateActive(); commitCommand(); TechDraw::DrawPage* fp = dynamic_cast(getDocument()->getObject(PageName.c_str())); if (!fp) { From 5f109b5ee32f0bf20ef9e6ac0b60fa24b6871747 Mon Sep 17 00:00:00 2001 From: Wanderer Fan Date: Thu, 30 Dec 2021 12:25:24 -0500 Subject: [PATCH 19/65] [TD]Allow selection of Page when adding View --- src/Mod/TechDraw/Gui/CMakeLists.txt | 4 + src/Mod/TechDraw/Gui/DlgPageChooser.cpp | 103 ++++++++++++++++++++++++ src/Mod/TechDraw/Gui/DlgPageChooser.h | 57 +++++++++++++ src/Mod/TechDraw/Gui/DlgPageChooser.ui | 93 +++++++++++++++++++++ src/Mod/TechDraw/Gui/DrawGuiUtil.cpp | 31 ++++++- 5 files changed, 284 insertions(+), 4 deletions(-) create mode 100644 src/Mod/TechDraw/Gui/DlgPageChooser.cpp create mode 100644 src/Mod/TechDraw/Gui/DlgPageChooser.h create mode 100644 src/Mod/TechDraw/Gui/DlgPageChooser.ui diff --git a/src/Mod/TechDraw/Gui/CMakeLists.txt b/src/Mod/TechDraw/Gui/CMakeLists.txt index 1cffb31d8f..f0c7d73fba 100644 --- a/src/Mod/TechDraw/Gui/CMakeLists.txt +++ b/src/Mod/TechDraw/Gui/CMakeLists.txt @@ -54,6 +54,7 @@ else() endif() set(TechDrawGui_UIC_SRCS + DlgPageChooser.ui DlgPrefsTechDrawAdvanced.ui DlgPrefsTechDrawAnnotation.ui DlgPrefsTechDrawColors.ui @@ -117,6 +118,9 @@ SET(TechDrawGui_SRCS TaskProjGroup.ui TaskProjGroup.cpp TaskProjGroup.h + DlgPageChooser.ui + DlgPageChooser.cpp + DlgPageChooser.h DlgPrefsTechDrawGeneral.ui DlgPrefsTechDrawGeneralImp.cpp DlgPrefsTechDrawGeneralImp.h diff --git a/src/Mod/TechDraw/Gui/DlgPageChooser.cpp b/src/Mod/TechDraw/Gui/DlgPageChooser.cpp new file mode 100644 index 0000000000..b8373e1f86 --- /dev/null +++ b/src/Mod/TechDraw/Gui/DlgPageChooser.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** + * Copyright (c) 2021 Wanderer Fan * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +# include +# include +# include +#endif + +#include +#include +#include "DlgPageChooser.h" +#include "ui_DlgPageChooser.h" + +FC_LOG_LEVEL_INIT("Gui",true,true) + +using namespace TechDrawGui; + +/* TRANSLATOR Gui::DlgPageChooser */ + +DlgPageChooser::DlgPageChooser( + const std::vector labels, + const std::vector names, + QWidget* parent, Qt::WindowFlags fl) + : QDialog(parent, fl), ui(new Ui_DlgPageChooser) +{ + ui->setupUi(this); + ui->lwPages->setSortingEnabled(true); + + fillList(labels, names); + + connect(ui->bbButtons, SIGNAL(accepted()), this, SLOT(accept())); + connect(ui->bbButtons, SIGNAL(rejected()), this, SLOT(reject())); +} + +/** + * Destroys the object and frees any allocated resources + */ +DlgPageChooser::~DlgPageChooser() +{ + // no need to delete child widgets, Qt does it all for us + delete ui; +} + +void DlgPageChooser::fillList(std::vector labels, std::vector names) +{ + QListWidgetItem* item; + QString qLabel; + QString qName; + QString qText; + int labelCount = labels.size(); + int i = 0; + for(; i < labelCount; i++) { + qLabel = Base::Tools::fromStdString(labels[i]); + qName = Base::Tools::fromStdString(names[i]); + qText = QString::fromUtf8("%1 (%2)").arg(qLabel).arg(qName); + item = new QListWidgetItem(qText, ui->lwPages); + item->setData(Qt::UserRole, qName); + } +} + +std::string DlgPageChooser::getSelection() const +{ + std::string result; + QList sels = ui->lwPages->selectedItems(); + if (!sels.empty()) { + QListWidgetItem* item = sels.front(); + result = item->data(Qt::UserRole).toByteArray().constData(); + } + return result; +} + + +void DlgPageChooser::accept() { + QDialog::accept(); +} + +void DlgPageChooser::reject() { + QDialog::reject(); +} + +#include "moc_DlgPageChooser.cpp" diff --git a/src/Mod/TechDraw/Gui/DlgPageChooser.h b/src/Mod/TechDraw/Gui/DlgPageChooser.h new file mode 100644 index 0000000000..d786276bd8 --- /dev/null +++ b/src/Mod/TechDraw/Gui/DlgPageChooser.h @@ -0,0 +1,57 @@ +/**************************************************************************** + * Copyright (c) 2021 Wanderer Fan * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ +#ifndef GUI_DLGPAGECHOOSER_H +#define GUI_DLGPAGECHOOSER_H + +#include + +namespace TechDrawGui { + +class Ui_DlgPageChooser; +class TechDrawGuiExport DlgPageChooser : public QDialog +{ + Q_OBJECT + +public: + DlgPageChooser(const std::vector labels, + const std::vector names, + QWidget* parent = 0, Qt::WindowFlags fl = Qt::WindowFlags()); + ~DlgPageChooser(); + + std::string getSelection() const; + void accept(); + void reject(); + +private Q_SLOTS: + +private: + void fillList(std::vector labels, std::vector names); + +private: + Ui_DlgPageChooser* ui; +}; + +} // namespace Gui + + +#endif // GUI_DLGPAGECHOOSER_H + diff --git a/src/Mod/TechDraw/Gui/DlgPageChooser.ui b/src/Mod/TechDraw/Gui/DlgPageChooser.ui new file mode 100644 index 0000000000..c10bdf4304 --- /dev/null +++ b/src/Mod/TechDraw/Gui/DlgPageChooser.ui @@ -0,0 +1,93 @@ + + + TechDrawGui::DlgPageChooser + + + Qt::WindowModal + + + + 0 + 0 + 360 + 280 + + + + Page Chooser + + + + + + true + + + + + + FreeCAD could not determine which Page to use. Please select a Page. + + + true + + + + + + + Select a Page that should be used + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + + + + + + bbButtons + accepted() + TechDrawGui::DlgPageChooser + accept() + + + 179 + 228 + + + 179 + 139 + + + + + bbButtons + rejected() + TechDrawGui::DlgPageChooser + reject() + + + 179 + 228 + + + 179 + 139 + + + + + diff --git a/src/Mod/TechDraw/Gui/DrawGuiUtil.cpp b/src/Mod/TechDraw/Gui/DrawGuiUtil.cpp index 1fa0ce681e..3ff5097b44 100644 --- a/src/Mod/TechDraw/Gui/DrawGuiUtil.cpp +++ b/src/Mod/TechDraw/Gui/DrawGuiUtil.cpp @@ -80,6 +80,7 @@ #include "QGVPage.h" #include "MDIViewPage.h" #include "ViewProviderPage.h" +#include "DlgPageChooser.h" #include "DrawGuiUtil.h" using namespace TechDrawGui; @@ -104,6 +105,8 @@ void DrawGuiUtil::loadArrowBox(QComboBox* qcb) TechDraw::DrawPage* DrawGuiUtil::findPage(Gui::Command* cmd) { TechDraw::DrawPage* page = nullptr; + std::vector names; + std::vector labels; //check Selection for a page std::vector selPages = cmd->getSelection(). @@ -127,8 +130,18 @@ TechDraw::DrawPage* DrawGuiUtil::findPage(Gui::Command* cmd) page = qp->getDrawPage(); } else { // no active page - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Which page?"), - QObject::tr("Can not determine correct page.")); + for(auto obj: selPages) { + std::string name = obj->getNameInDocument(); + names.push_back(name); + std::string label = obj->Label.getValue(); + labels.push_back(label); + } + DlgPageChooser dlg(labels, names, Gui::getMainWindow()); + if(dlg.exec()==QDialog::Accepted) { + std::string selName = dlg.getSelection(); + App::Document* doc = cmd->getDocument(); + page = static_cast(doc->getObject(selName.c_str())); + } } } else { //only 1 page in document - use it @@ -136,8 +149,18 @@ TechDraw::DrawPage* DrawGuiUtil::findPage(Gui::Command* cmd) } } else if (selPages.size() > 1) { //multiple pages in selection - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Too many pages"), - QObject::tr("Select only 1 page.")); + for(auto obj: selPages) { + std::string name = obj->getNameInDocument(); + names.push_back(name); + std::string label = obj->Label.getValue(); + labels.push_back(label); + } + DlgPageChooser dlg(labels, names, Gui::getMainWindow()); + if(dlg.exec()==QDialog::Accepted) { + std::string selName = dlg.getSelection(); + App::Document* doc = cmd->getDocument(); + page = static_cast(doc->getObject(selName.c_str())); + } } else { //exactly 1 page in selection, use it page = static_cast(selPages.front()); From c5affe7ea9c146a0356dc67dc63e7dd9dedc3702 Mon Sep 17 00:00:00 2001 From: Florian Simmer Date: Sat, 25 Dec 2021 21:42:37 +0100 Subject: [PATCH 20/65] Github: Create codespell workflow and codespellignore --- .github/codespellignore | 90 +++++++++++++++++++++++++++++++++ .github/workflows/codespell.yml | 31 ++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 .github/codespellignore create mode 100644 .github/workflows/codespell.yml diff --git a/.github/codespellignore b/.github/codespellignore new file mode 100644 index 0000000000..54bd1fa58a --- /dev/null +++ b/.github/codespellignore @@ -0,0 +1,90 @@ +aci +ake +aline +alle +alledges +alocation +als +ang +anid +apoints +ba +beginn +behaviour +bloaded +bottome +byteorder +calculater +cancelled +cancelling +cas +cascade +centimetre +childrens +childs +colour +colours +commen +connexion +currenty +dof +doubleclick +dum +eiter +elemente +ende +feld +finde +findf +freez +hist +iff +indicies +initialisation +initialise +initialised +initialises +initialisiert +inout +ist +kilometre +lod +mantatory +methode +metres +millimetre +modell +nd +noe +normale +normaly +nto +numer +oder +ontop +orgin +orginx +orginy +ot +pard +parm +parms +pres +programm +que +recurrance +rougly +seperator +serie +sinc +strack +substraction +te +thist +thru +tread +uint +unter +vertexes +wallthickness +whitespaces \ No newline at end of file diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml new file mode 100644 index 0000000000..068a2a6221 --- /dev/null +++ b/.github/workflows/codespell.yml @@ -0,0 +1,31 @@ +# GitHub Action to automate the identification of common misspellings in text files. +# https://github.com/codespell-project/actions-codespell +# https://github.com/codespell-project/codespell + +name: Codespell +on: + pull_request: + +jobs: + codespell: + name: Check for spelling errors + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v12.2 + + - name: List all changed files + run: | + for file in ${{ steps.changed-files.outputs.all_changed_files }}; do + echo "$file was changed" + done + + - uses: codespell-project/actions-codespell@master + with: + check_filenames: true + ignore_words_file: .github/codespellignore + skip: ./.git,*.po,*.ts,./ChangeLog.txt,./src/3rdParty,./src/Mod/Assembly/App/opendcm,./src/CXX,./src/zipios++,./src/Base/swig*,./src/Mod/Robot/App/kdl_cp,./src/Mod/Import/App/SCL,./src/WindowsInstaller,./src/Doc/FreeCAD.uml,./build/ + path: ${{ steps.changed-files.outputs.all_changed_files }} \ No newline at end of file From 36fe42d86941db98fed79a7528026e204153f4fe Mon Sep 17 00:00:00 2001 From: wmayer Date: Sun, 2 Jan 2022 22:44:04 +0100 Subject: [PATCH 21/65] Raytracing: replace subprocess.call with subprocess.Popen and escape file names --- src/Mod/Raytracing/Gui/Command.cpp | 33 ++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/Mod/Raytracing/Gui/Command.cpp b/src/Mod/Raytracing/Gui/Command.cpp index a9f6fbbb8c..eb9ffeb355 100644 --- a/src/Mod/Raytracing/Gui/Command.cpp +++ b/src/Mod/Raytracing/Gui/Command.cpp @@ -31,16 +31,18 @@ # include # include # include -# include # include # include # include # include # include # include +# include +# include #endif #include +#include #include #include #include @@ -678,11 +680,8 @@ void CmdRaytracingRender::activated(int) } openCommand("Render project"); int width = hGrp->GetInt("OutputWidth", 800); - std::stringstream w; - w << width; int height = hGrp->GetInt("OutputHeight", 600); - std::stringstream h; - h << height; + std::string par = hGrp->GetASCII("OutputParameters", "+P +A"); doCommand(Doc,"PageFile = open(App.activeDocument().%s.PageResult,'rb')",Sel[0].getFeatName()); doCommand(Doc,"import os,subprocess,tempfile"); @@ -691,12 +690,30 @@ void CmdRaytracingRender::activated(int) doCommand(Doc,"f.write(PageFile.read())"); doCommand(Doc,"f.close()"); doCommand(Doc,"os.close(fd)"); + + renderer = Base::Tools::escapeEncodeFilename(renderer); + std::stringstream str; + str << "proc = subprocess.Popen([" + << "\"" << renderer << "\", "; + std::istringstream istr(par); + std::string s; + while (std::getline(istr, s, ' ')) { + s = Base::Tools::escapeEncodeString(s); + str << "\"" << s << "\", "; + } + + str << "\"+W" << width << "\", " + << "\"+H" << height << "\", " + << "\"+O" << imageFile.data() << "\""; #ifdef FC_OS_WIN32 // http://povray.org/documentation/view/3.6.1/603/ - doCommand(Doc,"subprocess.call('\"%s\" %s +W%s +H%s +O\"%s\" /EXIT /RENDER '+TempFile)",renderer.c_str(),par.c_str(),w.str().c_str(),h.str().c_str(),imageFile.data()); -#else - doCommand(Doc,"subprocess.call('\"%s\" %s +W%s +H%s +O\"%s\" '+TempFile,shell=True)",renderer.c_str(),par.c_str(),w.str().c_str(),h.str().c_str(),imageFile.data()); + str << ", \"/EXIT\", \"/RENDER\""; #endif + str << ", TempFile])\n" + << "proc.communicate()"; + + doCommand(Doc, str.str().c_str()); + if (utf8Name != imageFile) { imageFile = utf8Name; if (QFile::exists(fn)) From 5266ecf36f23d05efa153275c534febdb7a906b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20B=C3=A4hr?= Date: Sun, 5 Dec 2021 21:44:56 +0100 Subject: [PATCH 22/65] PD: Use the helix property docs as tooltip in the task panel By reusing the property docs as tooltip we avoid maintaining the same information in two places. The propery descriptions have been made translatable, too, to ensure a fully translatable UI. The ground work for this was layed by the work on Issue 0002524, long time ago: https://tracker.freecadweb.org/view.php?id=0002524 --- src/Mod/PartDesign/App/FeatureHelix.cpp | 38 ++++++------- .../PartDesign/Gui/TaskHelixParameters.cpp | 53 ++++++++++++++++++- src/Mod/PartDesign/Gui/TaskHelixParameters.h | 2 + src/Mod/PartDesign/Gui/TaskHelixParameters.ui | 4 +- 4 files changed, 75 insertions(+), 22 deletions(-) diff --git a/src/Mod/PartDesign/App/FeatureHelix.cpp b/src/Mod/PartDesign/App/FeatureHelix.cpp index cf0e726353..cc77422642 100644 --- a/src/Mod/PartDesign/App/FeatureHelix.cpp +++ b/src/Mod/PartDesign/App/FeatureHelix.cpp @@ -80,40 +80,40 @@ Helix::Helix() const char* group = "Helix"; ADD_PROPERTY_TYPE(Base, (Base::Vector3d(0.0, 0.0, 0.0)), group, App::Prop_ReadOnly, - "The center point of the helix' start; derived from the reference axis."); + QT_TRANSLATE_NOOP("App::Property", "The center point of the helix' start; derived from the reference axis.")); ADD_PROPERTY_TYPE(Axis, (Base::Vector3d(0.0, 1.0, 0.0)), group, App::Prop_ReadOnly, - "The helix' direction; derived from the reference axis."); + QT_TRANSLATE_NOOP("App::Property", "The helix' direction; derived from the reference axis.")); ADD_PROPERTY_TYPE(ReferenceAxis, (0), group, App::Prop_None, - "The reference axis of the helix."); + QT_TRANSLATE_NOOP("App::Property", "The reference axis of the helix.")); ADD_PROPERTY_TYPE(Mode, (long(initialMode)), group, App::Prop_None, - "The helix input mode specifies which properties are set by the user.\n" - "Dependent properties are then calculated."); + QT_TRANSLATE_NOOP("App::Property", "The helix input mode specifies which properties are set by the user.\n" + "Dependent properties are then calculated.")); Mode.setEnums(ModeEnums); ADD_PROPERTY_TYPE(Pitch, (10.0), group, App::Prop_None, - "The axial distance between two turns."); + QT_TRANSLATE_NOOP("App::Property", "The axial distance between two turns.")); ADD_PROPERTY_TYPE(Height, (30.0), group, App::Prop_None, - "The height of the helix' path, not accounting for the extent of the profile."); + QT_TRANSLATE_NOOP("App::Property", "The height of the helix' path, not accounting for the extent of the profile.")); ADD_PROPERTY_TYPE(Turns, (3.0), group, App::Prop_None, - "The number of turns in the helix."); + QT_TRANSLATE_NOOP("App::Property", "The number of turns in the helix.")); Turns.setConstraints(&floatTurns); ADD_PROPERTY_TYPE(Angle, (0.0), group, App::Prop_None, - "The angle of the cone that forms a hull around the helix.\n" - "Non-zero values turn the helix into a conical spiral.\n" - "Positive values make the radius grow, nevatige shrink."); + QT_TRANSLATE_NOOP("App::Property", "The angle of the cone that forms a hull around the helix.\n" + "Non-zero values turn the helix into a conical spiral.\n" + "Positive values make the radius grow, nevatige shrink.")); Angle.setConstraints(&floatAngle); ADD_PROPERTY_TYPE(Growth, (0.0), group, App::Prop_None, - "The growth of the helix' radius per turn.\n" - "Non-zero values turn the helix into a conical spiral."); + QT_TRANSLATE_NOOP("App::Property", "The growth of the helix' radius per turn.\n" + "Non-zero values turn the helix into a conical spiral.")); ADD_PROPERTY_TYPE(LeftHanded, (false), group, App::Prop_None, - "Sets the turning direction to left handed,\n" - "i.e. counter-clockwise when moving along its axis."); + QT_TRANSLATE_NOOP("App::Property", "Sets the turning direction to left handed,\n" + "i.e. counter-clockwise when moving along its axis.")); ADD_PROPERTY_TYPE(Reversed, (false), group, App::Prop_None, - "Determines whether the helix points in the opposite direction of the axis."); + QT_TRANSLATE_NOOP("App::Property", "Determines whether the helix points in the opposite direction of the axis.")); ADD_PROPERTY_TYPE(Outside, (false), group, App::Prop_None, - "If set, the result will be the intersection of the profile and the preexisting body."); + QT_TRANSLATE_NOOP("App::Property", "If set, the result will be the intersection of the profile and the preexisting body.")); ADD_PROPERTY_TYPE(HasBeenEdited, (false), group, App::Prop_Hidden, - "If false, the tool will propose an initial value for the pitch based on the profile bounding box,\n" - "so that self intersection is avoided."); + QT_TRANSLATE_NOOP("App::Property", "If false, the tool will propose an initial value for the pitch based on the profile bounding box,\n" + "so that self intersection is avoided.")); setReadWriteStatusForMode(initialMode); } diff --git a/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp b/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp index 6e9034262b..1360ee6761 100644 --- a/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp @@ -301,9 +301,13 @@ void TaskHelixParameters::updateStatus() void TaskHelixParameters::updateUI() { fillAxisCombo(); - + assignToolTipsFromPropertyDocs(); updateStatus(); + adaptVisibilityToMode(); +} +void TaskHelixParameters::adaptVisibilityToMode() +{ bool isPitchVisible = false; bool isHeightVisible = false; bool isTurnsVisible = false; @@ -358,6 +362,52 @@ void TaskHelixParameters::updateUI() ui->checkBoxOutside->setVisible(isOutsideVisible); } +void TaskHelixParameters::assignToolTipsFromPropertyDocs() +{ + auto pcHelix = static_cast(vp->getObject()); + const char* propCategory = "App::Property"; // cf. https://tracker.freecadweb.org/view.php?id=0002524 + QString toolTip; + + // Beware that "Axis" in the GUI actually represents the property "ReferenceAxis"! + // The property "Axis" holds only the directional part of the reference axis and has no corresponding GUI element. + toolTip = QApplication::translate(propCategory, pcHelix->ReferenceAxis.getDocumentation()); + ui->axis->setToolTip(toolTip); + ui->labelAxis->setToolTip(toolTip); + + toolTip = QApplication::translate(propCategory, pcHelix->Mode.getDocumentation()); + ui->inputMode->setToolTip(toolTip); + ui->labelInputMode->setToolTip(toolTip); + + toolTip = QApplication::translate(propCategory, pcHelix->Pitch.getDocumentation()); + ui->pitch->setToolTip(toolTip); + ui->labelPitch->setToolTip(toolTip); + + toolTip = QApplication::translate(propCategory, pcHelix->Height.getDocumentation()); + ui->height->setToolTip(toolTip); + ui->labelHeight->setToolTip(toolTip); + + toolTip = QApplication::translate(propCategory, pcHelix->Turns.getDocumentation()); + ui->turns->setToolTip(toolTip); + ui->labelTurns->setToolTip(toolTip); + + toolTip = QApplication::translate(propCategory, pcHelix->Angle.getDocumentation()); + ui->coneAngle->setToolTip(toolTip); + ui->labelConeAngle->setToolTip(toolTip); + + toolTip = QApplication::translate(propCategory, pcHelix->Growth.getDocumentation()); + ui->growth->setToolTip(toolTip); + ui->labelGrowth->setToolTip(toolTip); + + toolTip = QApplication::translate(propCategory, pcHelix->LeftHanded.getDocumentation()); + ui->checkBoxLeftHanded->setToolTip(toolTip); + + toolTip = QApplication::translate(propCategory, pcHelix->Reversed.getDocumentation()); + ui->checkBoxReversed->setToolTip(toolTip); + + toolTip = QApplication::translate(propCategory, pcHelix->Outside.getDocumentation()); + ui->checkBoxOutside->setToolTip(toolTip); +} + void TaskHelixParameters::onSelectionChanged(const Gui::SelectionChanges& msg) { if (msg.Type == Gui::SelectionChanges::AddSelection) { @@ -529,6 +579,7 @@ void TaskHelixParameters::changeEvent(QEvent* e) int axis = ui->axis->currentIndex(); int mode = ui->inputMode->currentIndex(); ui->retranslateUi(proxy); + assignToolTipsFromPropertyDocs(); // Axes added by the user cannot be restored fillAxisCombo(true); diff --git a/src/Mod/PartDesign/Gui/TaskHelixParameters.h b/src/Mod/PartDesign/Gui/TaskHelixParameters.h index 02b9cdddd0..b7c5c1dbd2 100644 --- a/src/Mod/PartDesign/Gui/TaskHelixParameters.h +++ b/src/Mod/PartDesign/Gui/TaskHelixParameters.h @@ -69,6 +69,8 @@ private: void addSketchAxes(); void addPartAxes(); int addCurrentLink(); + void assignToolTipsFromPropertyDocs(); + void adaptVisibilityToMode(); private Q_SLOTS: void onPitchChanged(double); diff --git a/src/Mod/PartDesign/Gui/TaskHelixParameters.ui b/src/Mod/PartDesign/Gui/TaskHelixParameters.ui index ff22ee13b6..434f79a473 100644 --- a/src/Mod/PartDesign/Gui/TaskHelixParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskHelixParameters.ui @@ -35,7 +35,7 @@ - + Axis: @@ -88,7 +88,7 @@ - + Mode: From ccb03cadd5e5016791948a709e106ce872a22bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20B=C3=A4hr?= Date: Sun, 2 Jan 2022 17:42:18 +0100 Subject: [PATCH 23/65] PD: helix cleanup: remove unused strings from task panel ui Since the tool tips are now set from the property descriptions we don't need them any more in the .ui file. Removing them prevents garbage in the translation source files. --- src/Mod/PartDesign/Gui/TaskHelixParameters.ui | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/src/Mod/PartDesign/Gui/TaskHelixParameters.ui b/src/Mod/PartDesign/Gui/TaskHelixParameters.ui index 434f79a473..11d7f7fd4c 100644 --- a/src/Mod/PartDesign/Gui/TaskHelixParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskHelixParameters.ui @@ -43,9 +43,6 @@ - - Axis the helix winds around - Base X axis @@ -96,9 +93,6 @@ - - Parameter set defining the helix - Pitch-Height-Angle @@ -134,9 +128,6 @@ - - Axial distance between two turns - false @@ -164,9 +155,6 @@ - - Height of helix - false @@ -194,9 +182,6 @@ - - Number of turns - false @@ -221,9 +206,6 @@ - - Angle of the cone that forms a hull around the helix. - false @@ -254,9 +236,6 @@ - - Radial growth of helix per turn - false @@ -272,9 +251,6 @@ true - - Sets turning direction to left handed - Left handed @@ -285,9 +261,6 @@ true - - Reverses the Axis direction - Reversed @@ -295,9 +268,6 @@ - - the result will be the intersection of helix and the preexisting body - Remove outside of profile From af9857faef9a7218820e335ed7404f78fcb3597f Mon Sep 17 00:00:00 2001 From: mwganson Date: Sun, 2 Jan 2022 12:08:52 -0600 Subject: [PATCH 24/65] [PartDesign::Scripts::Spring.py] update old example script --- src/Mod/PartDesign/Scripts/Spring.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/PartDesign/Scripts/Spring.py b/src/Mod/PartDesign/Scripts/Spring.py index 08337fc70f..5276f48c37 100644 --- a/src/Mod/PartDesign/Scripts/Spring.py +++ b/src/Mod/PartDesign/Scripts/Spring.py @@ -32,8 +32,8 @@ class MySpring: c.Radius = barradius p = c.toShape() section = Part.Wire([p]) - makeSolid = 1 # change to 1 to make a solid - isFrenet = 1 + makeSolid = True + isFrenet = True myspring = Part.Wire(myhelix).makePipeShell([section], makeSolid, isFrenet) fp.Shape = myspring From e483cc2b093e7738ceaae602f8eaf52d87c4afd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Br=C3=A6strup=20Sayoc?= Date: Sun, 26 Dec 2021 00:56:24 +0100 Subject: [PATCH 25/65] Use latest user input for Add Cosmetic Vertex --- src/Mod/TechDraw/Gui/TaskCosVertex.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Mod/TechDraw/Gui/TaskCosVertex.cpp b/src/Mod/TechDraw/Gui/TaskCosVertex.cpp index d5532159cd..5c94bbe9ab 100644 --- a/src/Mod/TechDraw/Gui/TaskCosVertex.cpp +++ b/src/Mod/TechDraw/Gui/TaskCosVertex.cpp @@ -86,8 +86,7 @@ TaskCosVertex::TaskCosVertex(TechDraw::DrawViewPart* baseFeat, m_btnOK(nullptr), m_btnCancel(nullptr), m_pbTrackerState(TRACKERPICK), - m_savePoint(QPointF(0.0,0.0)), - pointFromTracker(false) + m_savePoint(QPointF(0.0,0.0)) { if ( (m_basePage == nullptr) || (m_baseFeat == nullptr) ) { @@ -263,7 +262,6 @@ void TaskCosVertex::onTrackerFinished(std::vector pts, QGIView* qgParen QPointF scenePosCV = displace / scale; m_savePoint = Rez::appX(scenePosCV); - pointFromTracker = true; updateUi(); m_tracker->sleep(true); @@ -325,14 +323,11 @@ bool TaskCosVertex::accept() if (!doc) return false; removeTracker(); - if (pointFromTracker) { - addCosVertex(m_savePoint); - } else { - double x = ui->dsbX->value().getValue(); - double y = ui->dsbY->value().getValue(); - QPointF uiPoint(x,-y); - addCosVertex(uiPoint); - } + double x = ui->dsbX->value().getValue(); + double y = ui->dsbY->value().getValue(); + QPointF uiPoint(x,-y); + addCosVertex(uiPoint); + m_baseFeat->recomputeFeature(); m_baseFeat->requestPaint(); m_mdi->setContextMenuPolicy(m_saveContextPolicy); From 2d14c0be8da64e0270bb8edc2219ef458d4274b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Br=C3=A6strup=20Sayoc?= Date: Sun, 26 Dec 2021 00:58:50 +0100 Subject: [PATCH 26/65] Make it possible to do new Pick Points --- src/Mod/TechDraw/Gui/TaskCosVertex.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Mod/TechDraw/Gui/TaskCosVertex.cpp b/src/Mod/TechDraw/Gui/TaskCosVertex.cpp index 5c94bbe9ab..b4c6cd2c1b 100644 --- a/src/Mod/TechDraw/Gui/TaskCosVertex.cpp +++ b/src/Mod/TechDraw/Gui/TaskCosVertex.cpp @@ -178,9 +178,10 @@ void TaskCosVertex::onTrackerClicked(bool b) Q_UNUSED(b); // Base::Console().Message("TCV::onTrackerClicked() m_pbTrackerState: %d\n", // m_pbTrackerState); - if (m_pbTrackerState == TRACKERCANCEL) { - removeTracker(); + removeTracker(); + + if (m_pbTrackerState == TRACKERCANCEL) { m_pbTrackerState = TRACKERPICK; ui->pbTracker->setText(QString::fromUtf8("Pick Points")); enableTaskButtons(true); @@ -266,7 +267,9 @@ void TaskCosVertex::onTrackerFinished(std::vector pts, QGIView* qgParen m_tracker->sleep(true); m_inProgressLock = false; - ui->pbTracker->setEnabled(false); + m_pbTrackerState = TRACKERPICK; + ui->pbTracker->setText(QString::fromUtf8("Pick Points")); + ui->pbTracker->setEnabled(true); enableTaskButtons(true); setEditCursor(Qt::ArrowCursor); m_mdi->setContextMenuPolicy(m_saveContextPolicy); From c055ac46195c5dce2fc13f614876824b4bb255be Mon Sep 17 00:00:00 2001 From: sliptonic Date: Fri, 17 Dec 2021 16:49:16 -0600 Subject: [PATCH 27/65] Make combos in .UI non-translatable. --- .../Resources/panels/PageOpProfileFullEdit.ui | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpProfileFullEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpProfileFullEdit.ui index 707a72ed61..3f8dc294cb 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpProfileFullEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpProfileFullEdit.ui @@ -67,16 +67,11 @@ - <html><head/><body><p>Specify if the profile should be performed inside or outside the base geometry features. This only matters if Use Compensation is checked (the default).</p></body></html> + <html><head/><body><p>Specify if the profile should be performed inside or outside the base geometry features. This only matters if Use Compensation is checked (the default).</p></body></html> - Outside - - - - - Inside + PLACEHOLDER @@ -97,16 +92,11 @@ - <html><head/><body><p>The direction in which the profile is performed, clockwise or counter clockwise.</p></body></html> + <html><head/><body><p>The direction in which the profile is performed, clockwise or counter clockwise.</p></body></html> - CW - - - - - CCW + PLACEHOLDER From 0fae42230c5ab7bd3090d478a3b48525987061e4 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Fri, 17 Dec 2021 17:15:13 -0600 Subject: [PATCH 28/65] black reformat black reformat for PathCommands Black reform PathCopy Black reform PathComment Black reformat PathCustom/gui Black reformat PathUtil/PathUtils Black reformat PathHop Black reformat PathGeom Black reformat Black reformat Black reformat Black reformat Black reformat black reformat Black reformat black --- src/Mod/Path/InitGui.py | 188 ++++++-- src/Mod/Path/PathCommands.py | 117 +++-- src/Mod/Path/PathScripts/PathArray.py | 343 ++++++++++---- src/Mod/Path/PathScripts/PathCollision.py | 39 +- src/Mod/Path/PathScripts/PathComment.py | 71 +-- src/Mod/Path/PathScripts/PathCopy.py | 41 +- src/Mod/Path/PathScripts/PathCustom.py | 10 +- src/Mod/Path/PathScripts/PathCustomGui.py | 24 +- src/Mod/Path/PathScripts/PathFixture.py | 121 +++-- src/Mod/Path/PathScripts/PathGeom.py | 342 ++++++++++---- src/Mod/Path/PathScripts/PathHop.py | 54 ++- .../Path/PathScripts/PathIconViewProvider.py | 46 +- src/Mod/Path/PathScripts/PathPlane.py | 84 ++-- .../Path/PathScripts/PathPropertyEditor.py | 88 ++-- src/Mod/Path/PathScripts/PathStock.py | 430 +++++++++++++----- src/Mod/Path/PathScripts/PathStop.py | 84 ++-- src/Mod/Path/PathScripts/PathUtil.py | 91 ++-- src/Mod/Path/PathScripts/PathUtils.py | 377 +++++++++------ 18 files changed, 1728 insertions(+), 822 deletions(-) diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index d4011741ab..44e5d41b09 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -35,7 +35,7 @@ class PathCommandGroup: return tuple(self.cmdlist) def GetResources(self): - return {'MenuText': self.menu, 'ToolTip': self.tooltip} + return {"MenuText": self.menu, "ToolTip": self.tooltip} def IsActive(self): if FreeCAD.ActiveDocument is not None: @@ -45,11 +45,13 @@ class PathCommandGroup: return False -class PathWorkbench (Workbench): +class PathWorkbench(Workbench): "Path workbench" def __init__(self): - self.__class__.Icon = FreeCAD.getResourceDir() + "Mod/Path/Resources/icons/PathWorkbench.svg" + self.__class__.Icon = ( + FreeCAD.getResourceDir() + "Mod/Path/Resources/icons/PathWorkbench.svg" + ) self.__class__.MenuText = "Path" self.__class__.ToolTip = "Path workbench" @@ -58,8 +60,11 @@ class PathWorkbench (Workbench): # Add preferences pages - before loading PathGui to properly order pages of Path group from PathScripts import PathPreferencesPathJob, PathPreferencesPathDressup + FreeCADGui.addPreferencePage(PathPreferencesPathJob.JobPreferencesPage, "Path") - FreeCADGui.addPreferencePage(PathPreferencesPathDressup.DressupPreferencesPage, "Path") + FreeCADGui.addPreferencePage( + PathPreferencesPathDressup.DressupPreferencesPage, "Path" + ) # Check enablement of experimental features from PathScripts import PathPreferences @@ -69,6 +74,7 @@ class PathWorkbench (Workbench): import PathScripts import PathGui from PySide import QtCore, QtGui + FreeCADGui.addLanguagePath(":/translations") FreeCADGui.addIconPath(":/icons") from PathScripts import PathGuiInit @@ -78,29 +84,50 @@ class PathWorkbench (Workbench): from PathScripts import PathToolBitLibraryCmd import PathCommands + PathGuiInit.Startup() # build commands list projcmdlist = ["Path_Job", "Path_Post"] - toolcmdlist = ["Path_Inspect", "Path_Simulator", "Path_SelectLoop", - "Path_OpActiveToggle"] - prepcmdlist = ["Path_Fixture", "Path_Comment", "Path_Stop", - "Path_Custom", "Path_Probe"] - twodopcmdlist = ["Path_Profile", "Path_Pocket_Shape", "Path_Drilling", - "Path_MillFace", "Path_Helix", "Path_Adaptive"] + toolcmdlist = [ + "Path_Inspect", + "Path_Simulator", + "Path_SelectLoop", + "Path_OpActiveToggle", + ] + prepcmdlist = [ + "Path_Fixture", + "Path_Comment", + "Path_Stop", + "Path_Custom", + "Path_Probe", + ] + twodopcmdlist = [ + "Path_Profile", + "Path_Pocket_Shape", + "Path_Drilling", + "Path_MillFace", + "Path_Helix", + "Path_Adaptive", + ] threedopcmdlist = ["Path_Pocket_3D"] engravecmdlist = ["Path_Engrave", "Path_Deburr", "Path_Vcarve"] modcmdlist = ["Path_OperationCopy", "Path_Array", "Path_SimpleCopy"] - dressupcmdlist = ["Path_DressupAxisMap", "Path_DressupPathBoundary", - "Path_DressupDogbone", "Path_DressupDragKnife", - "Path_DressupLeadInOut", "Path_DressupRampEntry", - "Path_DressupTag", "Path_DressupZCorrect"] + dressupcmdlist = [ + "Path_DressupAxisMap", + "Path_DressupPathBoundary", + "Path_DressupDogbone", + "Path_DressupDragKnife", + "Path_DressupLeadInOut", + "Path_DressupRampEntry", + "Path_DressupTag", + "Path_DressupZCorrect", + ] extracmdlist = [] # modcmdmore = ["Path_Hop",] # remotecmdlist = ["Path_Remote"] specialcmdlist = [] - if PathPreferences.toolsUseLegacyTools(): toolcmdlist.append("Path_ToolLibraryEdit") toolbitcmdlist = [] @@ -108,17 +135,20 @@ class PathWorkbench (Workbench): toolcmdlist.extend(PathToolBitLibraryCmd.BarList) toolbitcmdlist = PathToolBitLibraryCmd.MenuList - - - engravecmdgroup = ['Path_EngraveTools'] - FreeCADGui.addCommand('Path_EngraveTools', PathCommandGroup(engravecmdlist, QtCore.QT_TRANSLATE_NOOP("Path", 'Engraving Operations'))) + engravecmdgroup = ["Path_EngraveTools"] + FreeCADGui.addCommand( + "Path_EngraveTools", + PathCommandGroup( + engravecmdlist, QtCore.QT_TRANSLATE_NOOP("Path", "Engraving Operations") + ), + ) threedcmdgroup = threedopcmdlist if PathPreferences.experimentalFeaturesEnabled(): projcmdlist.append("Path_Sanity") prepcmdlist.append("Path_Shape") extracmdlist.extend(["Path_Area", "Path_Area_Workplane"]) - specialcmdlist.append('Path_Thread_Milling') + specialcmdlist.append("Path_Thread_Milling") twodopcmdlist.append("Path_Slot") if PathPreferences.advancedOCLFeaturesEnabled(): @@ -126,38 +156,91 @@ class PathWorkbench (Workbench): import ocl # pylint: disable=unused-variable from PathScripts import PathSurfaceGui from PathScripts import PathWaterlineGui + threedopcmdlist.extend(["Path_Surface", "Path_Waterline"]) - threedcmdgroup = ['Path_3dTools'] - FreeCADGui.addCommand('Path_3dTools', PathCommandGroup(threedopcmdlist, QtCore.QT_TRANSLATE_NOOP("Path", '3D Operations'))) + threedcmdgroup = ["Path_3dTools"] + FreeCADGui.addCommand( + "Path_3dTools", + PathCommandGroup( + threedopcmdlist, + QtCore.QT_TRANSLATE_NOOP("Path", "3D Operations"), + ), + ) except ImportError: if not PathPreferences.suppressOpenCamLibWarning(): FreeCAD.Console.PrintError("OpenCamLib is not working!\n") - self.appendToolbar(QtCore.QT_TRANSLATE_NOOP("Path", "Project Setup"), projcmdlist) - self.appendToolbar(QtCore.QT_TRANSLATE_NOOP("Path", "Tool Commands"), toolcmdlist) - self.appendToolbar(QtCore.QT_TRANSLATE_NOOP("Path", "New Operations"), twodopcmdlist+engravecmdgroup+threedcmdgroup) - self.appendToolbar(QtCore.QT_TRANSLATE_NOOP("Path", "Path Modification"), modcmdlist) + self.appendToolbar( + QtCore.QT_TRANSLATE_NOOP("Path", "Project Setup"), projcmdlist + ) + self.appendToolbar( + QtCore.QT_TRANSLATE_NOOP("Path", "Tool Commands"), toolcmdlist + ) + self.appendToolbar( + QtCore.QT_TRANSLATE_NOOP("Path", "New Operations"), + twodopcmdlist + engravecmdgroup + threedcmdgroup, + ) + self.appendToolbar( + QtCore.QT_TRANSLATE_NOOP("Path", "Path Modification"), modcmdlist + ) if extracmdlist: - self.appendToolbar(QtCore.QT_TRANSLATE_NOOP("Path", "Helpful Tools"), extracmdlist) + self.appendToolbar( + QtCore.QT_TRANSLATE_NOOP("Path", "Helpful Tools"), extracmdlist + ) - self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], projcmdlist + ["Path_ExportTemplate", "Separator"] + - toolcmdlist + toolbitcmdlist + ["Separator"] + twodopcmdlist + engravecmdlist + ["Separator"] + - threedopcmdlist + ["Separator"]) - self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP( - "Path", "Path Dressup")], dressupcmdlist) - self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP( - "Path", "Supplemental Commands")], prepcmdlist) - self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP( - "Path", "Path Modification")], modcmdlist) + self.appendMenu( + [QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], + projcmdlist + + ["Path_ExportTemplate", "Separator"] + + toolcmdlist + + toolbitcmdlist + + ["Separator"] + + twodopcmdlist + + engravecmdlist + + ["Separator"] + + threedopcmdlist + + ["Separator"], + ) + self.appendMenu( + [ + QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), + QtCore.QT_TRANSLATE_NOOP("Path", "Path Dressup"), + ], + dressupcmdlist, + ) + self.appendMenu( + [ + QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), + QtCore.QT_TRANSLATE_NOOP("Path", "Supplemental Commands"), + ], + prepcmdlist, + ) + self.appendMenu( + [ + QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), + QtCore.QT_TRANSLATE_NOOP("Path", "Path Modification"), + ], + modcmdlist, + ) if specialcmdlist: - self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP( - "Path", "Specialty Operations")], specialcmdlist) + self.appendMenu( + [ + QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), + QtCore.QT_TRANSLATE_NOOP("Path", "Specialty Operations"), + ], + specialcmdlist, + ) if extracmdlist: self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], extracmdlist) self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], ["Separator"]) - self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP("Path", "Utils")], - ["Path_PropertyBag"]) + self.appendMenu( + [ + QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), + QtCore.QT_TRANSLATE_NOOP("Path", "Utils"), + ], + ["Path_PropertyBag"], + ) self.dressupcmds = dressupcmdlist @@ -167,8 +250,11 @@ class PathWorkbench (Workbench): # keep this one the last entry in the preferences import PathScripts.PathPreferencesAdvanced as PathPreferencesAdvanced - FreeCADGui.addPreferencePage(PathPreferencesAdvanced.AdvancedPreferencesPage, "Path") - Log('Loading Path workbench... done\n') + + FreeCADGui.addPreferencePage( + PathPreferencesAdvanced.AdvancedPreferencesPage, "Path" + ) + Log("Loading Path workbench... done\n") def GetClassName(self): return "Gui::PythonWorkbench" @@ -184,6 +270,7 @@ class PathWorkbench (Workbench): def ContextMenu(self, recipient): import PathScripts + menuAppended = False if len(FreeCADGui.Selection.getSelection()) == 1: obj = FreeCADGui.Selection.getSelection()[0] @@ -194,13 +281,21 @@ class PathWorkbench (Workbench): if "Remote" in selectedName: self.appendContextMenu("", ["Refresh_Path"]) if "Job" in selectedName: - self.appendContextMenu("", ["Path_ExportTemplate"] + self.toolbitctxmenu) + self.appendContextMenu( + "", ["Path_ExportTemplate"] + self.toolbitctxmenu + ) menuAppended = True if isinstance(obj.Proxy, PathScripts.PathOp.ObjectOp): - self.appendContextMenu("", ["Path_OperationCopy", "Path_OpActiveToggle"]) + self.appendContextMenu( + "", ["Path_OperationCopy", "Path_OpActiveToggle"] + ) menuAppended = True if obj.isDerivedFrom("Path::Feature"): - if "Profile" in selectedName or "Contour" in selectedName or "Dressup" in selectedName: + if ( + "Profile" in selectedName + or "Contour" in selectedName + or "Dressup" in selectedName + ): self.appendContextMenu("", "Separator") # self.appendContextMenu("", ["Set_StartPoint"]) # self.appendContextMenu("", ["Set_EndPoint"]) @@ -216,7 +311,6 @@ class PathWorkbench (Workbench): Gui.addWorkbench(PathWorkbench()) -FreeCAD.addImportType( - "GCode (*.nc *.gc *.ncc *.ngc *.cnc *.tap *.gcode)", "PathGui") +FreeCAD.addImportType("GCode (*.nc *.gc *.ncc *.ngc *.cnc *.tap *.gcode)", "PathGui") # FreeCAD.addExportType( # "GCode (*.nc *.gc *.ncc *.ngc *.cnc *.tap *.gcode)", "PathGui") diff --git a/src/Mod/Path/PathCommands.py b/src/Mod/Path/PathCommands.py index 29e26d83ea..d79288289d 100644 --- a/src/Mod/Path/PathCommands.py +++ b/src/Mod/Path/PathCommands.py @@ -36,9 +36,11 @@ if FreeCAD.GuiUp: from PySide import QtCore from PySide import QtGui else: + def translate(ctxt, txt): return txt + __title__ = "FreeCAD Path Commands" __author__ = "sliptonic" __url__ = "https://www.freecadweb.org" @@ -46,17 +48,24 @@ __url__ = "https://www.freecadweb.org" class _CommandSelectLoop: "the Path command to complete loop selection definition" + def __init__(self): self.obj = None self.sub = [] self.active = False def GetResources(self): - return {'Pixmap': 'Path_SelectLoop', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_SelectLoop", "Finish Selecting Loop"), - 'Accel': "P, L", - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_SelectLoop", "Complete loop selection from two edges"), - 'CmdType': "ForEdit"} + return { + "Pixmap": "Path_SelectLoop", + "MenuText": QtCore.QT_TRANSLATE_NOOP( + "Path_SelectLoop", "Finish Selecting Loop" + ), + "Accel": "P, L", + "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "Path_SelectLoop", "Complete loop selection from two edges" + ), + "CmdType": "ForEdit", + } def IsActive(self): if bool(FreeCADGui.Selection.getSelection()) is False: @@ -68,7 +77,7 @@ class _CommandSelectLoop: self.obj = sel.Object self.sub = sel.SubElementNames if sel.SubObjects: - #self.active = self.formsPartOfALoop(sel.Object, sel.SubObjects[0], sel.SubElementNames) + # self.active = self.formsPartOfALoop(sel.Object, sel.SubObjects[0], sel.SubElementNames) self.active = True else: self.active = False @@ -81,11 +90,14 @@ class _CommandSelectLoop: def Activated(self): from PathScripts.PathUtils import horizontalEdgeLoop from PathScripts.PathUtils import horizontalFaceLoop + sel = FreeCADGui.Selection.getSelectionEx()[0] obj = sel.Object edge1 = sel.SubObjects[0] - if 'Face' in sel.SubElementNames[0]: - loop = horizontalFaceLoop(sel.Object, sel.SubObjects[0], sel.SubElementNames) + if "Face" in sel.SubElementNames[0]: + loop = horizontalFaceLoop( + sel.Object, sel.SubObjects[0], sel.SubElementNames + ) if loop: FreeCADGui.Selection.clearSelection() FreeCADGui.Selection.addSelection(sel.Object, loop) @@ -102,21 +114,27 @@ class _CommandSelectLoop: for e in elist: for i in loopwire.Edges: if e.hashCode() == i.hashCode(): - FreeCADGui.Selection.addSelection(obj, "Edge" + str(elist.index(e) + 1)) + FreeCADGui.Selection.addSelection( + obj, "Edge" + str(elist.index(e) + 1) + ) elif FreeCAD.GuiUp: - QtGui.QMessageBox.information(None, - QtCore.QT_TRANSLATE_NOOP('Path_SelectLoop', 'Feature Completion'), - QtCore.QT_TRANSLATE_NOOP('Path_SelectLoop', 'Closed loop detection failed.')) + QtGui.QMessageBox.information( + None, + QtCore.QT_TRANSLATE_NOOP("Path_SelectLoop", "Feature Completion"), + QtCore.QT_TRANSLATE_NOOP( + "Path_SelectLoop", "Closed loop detection failed." + ), + ) def formsPartOfALoop(self, obj, sub, names): try: - if names[0][0:4] != 'Edge': - if names[0][0:4] == 'Face' and horizontalFaceLoop(obj, sub, names): + if names[0][0:4] != "Edge": + if names[0][0:4] == "Face" and horizontalFaceLoop(obj, sub, names): return True return False if len(names) == 1 and horizontalEdgeLoop(obj, sub): return True - if len(names) == 1 or names[1][0:4] != 'Edge': + if len(names) == 1 or names[1][0:4] != "Edge": return False return True except Exception: @@ -124,17 +142,24 @@ class _CommandSelectLoop: if FreeCAD.GuiUp: - FreeCADGui.addCommand('Path_SelectLoop', _CommandSelectLoop()) + FreeCADGui.addCommand("Path_SelectLoop", _CommandSelectLoop()) class _ToggleOperation: "command definition to toggle Operation Active state" + def GetResources(self): - return {'Pixmap': 'Path_OpActive', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_OpActiveToggle", "Toggle the Active State of the Operation"), - 'Accel': "P, X", - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_OpActiveToggle", "Toggle the Active State of the Operation"), - 'CmdType': "ForEdit"} + return { + "Pixmap": "Path_OpActive", + "MenuText": QtCore.QT_TRANSLATE_NOOP( + "Path_OpActiveToggle", "Toggle the Active State of the Operation" + ), + "Accel": "P, X", + "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "Path_OpActiveToggle", "Toggle the Active State of the Operation" + ), + "CmdType": "ForEdit", + } def IsActive(self): if bool(FreeCADGui.Selection.getSelection()) is False: @@ -142,11 +167,12 @@ class _ToggleOperation: try: for sel in FreeCADGui.Selection.getSelectionEx(): selProxy = PathScripts.PathDressup.baseOp(sel.Object).Proxy - if not isinstance(selProxy, PathScripts.PathOp.ObjectOp) and \ - not isinstance(selProxy, PathScripts.PathArray.ObjectArray): - return False + if not isinstance( + selProxy, PathScripts.PathOp.ObjectOp + ) and not isinstance(selProxy, PathScripts.PathArray.ObjectArray): + return False return True - except(IndexError, AttributeError): + except (IndexError, AttributeError): return False def Activated(self): @@ -159,16 +185,23 @@ class _ToggleOperation: if FreeCAD.GuiUp: - FreeCADGui.addCommand('Path_OpActiveToggle', _ToggleOperation()) + FreeCADGui.addCommand("Path_OpActiveToggle", _ToggleOperation()) class _CopyOperation: "the Path Copy Operation command definition" + def GetResources(self): - return {'Pixmap': 'Path_OpCopy', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_OperationCopy", "Copy the operation in the job"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_OperationCopy", "Copy the operation in the job"), - 'CmdType': "ForEdit"} + return { + "Pixmap": "Path_OpCopy", + "MenuText": QtCore.QT_TRANSLATE_NOOP( + "Path_OperationCopy", "Copy the operation in the job" + ), + "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "Path_OperationCopy", "Copy the operation in the job" + ), + "CmdType": "ForEdit", + } def IsActive(self): if bool(FreeCADGui.Selection.getSelection()) is False: @@ -178,7 +211,7 @@ class _CopyOperation: if not isinstance(sel.Object.Proxy, PathScripts.PathOp.ObjectOp): return False return True - except(IndexError, AttributeError): + except (IndexError, AttributeError): return False def Activated(self): @@ -188,27 +221,27 @@ class _CopyOperation: if FreeCAD.GuiUp: - FreeCADGui.addCommand('Path_OperationCopy', _CopyOperation()) + FreeCADGui.addCommand("Path_OperationCopy", _CopyOperation()) # \c findShape() is referenced from Gui/Command.cpp and used by Path.Area commands. # Do not remove! def findShape(shape, subname=None, subtype=None): - '''To find a higher order shape containing the subshape with subname. - E.g. to find the wire containing 'Edge1' in shape, - findShape(shape,'Edge1','Wires') - ''' + """To find a higher order shape containing the subshape with subname. + E.g. to find the wire containing 'Edge1' in shape, + findShape(shape,'Edge1','Wires') + """ if not subname: return shape ret = shape.getElement(subname) if not subtype or not ret or ret.isNull(): return ret - if subname.startswith('Face'): - tp = 'Faces' - elif subname.startswith('Edge'): - tp = 'Edges' - elif subname.startswith('Vertex'): - tp = 'Vertex' + if subname.startswith("Face"): + tp = "Faces" + elif subname.startswith("Edge"): + tp = "Edges" + elif subname.startswith("Vertex"): + tp = "Vertex" else: return ret for obj in getattr(shape, subtype): diff --git a/src/Mod/Path/PathScripts/PathArray.py b/src/Mod/Path/PathScripts/PathArray.py index d8bc7093c6..3d08e3c1a1 100644 --- a/src/Mod/Path/PathScripts/PathArray.py +++ b/src/Mod/Path/PathScripts/PathArray.py @@ -35,38 +35,113 @@ __doc__ = """Path Array object and FreeCAD command""" def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) -class ObjectArray: +class ObjectArray: def __init__(self, obj): - obj.addProperty("App::PropertyLinkList", "Base", - "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The path(s) to array")) - obj.addProperty("App::PropertyEnumeration", "Type", - "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Pattern method")) - obj.addProperty("App::PropertyVectorDistance", "Offset", - "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The spacing between the array copies in Linear pattern")) - obj.addProperty("App::PropertyInteger", "CopiesX", - "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The number of copies in X direction in Linear pattern")) - obj.addProperty("App::PropertyInteger", "CopiesY", - "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The number of copies in Y direction in Linear pattern")) - obj.addProperty("App::PropertyAngle", "Angle", - "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Total angle in Polar pattern")) - obj.addProperty("App::PropertyInteger", "Copies", - "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The number of copies in Linear 1D and Polar pattern")) - obj.addProperty("App::PropertyVector", "Centre", - "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The centre of rotation in Polar pattern")) - obj.addProperty("App::PropertyBool", "SwapDirection", - "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Make copies in X direction before Y in Linear 2D pattern")) - obj.addProperty("App::PropertyInteger", "JitterPercent", - "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Percent of copies to randomly offset")) - obj.addProperty("App::PropertyVectorDistance", "JitterMagnitude", - "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Maximum random offset of copies")) - obj.addProperty("App::PropertyLink", "ToolController", - "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The tool controller that will be used to calculate the path")) - obj.addProperty("App::PropertyBool", "Active", - "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make False, to prevent operation from generating code")) + obj.addProperty( + "App::PropertyLinkList", + "Base", + "Path", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The path(s) to array"), + ) + obj.addProperty( + "App::PropertyEnumeration", + "Type", + "Path", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Pattern method"), + ) + obj.addProperty( + "App::PropertyVectorDistance", + "Offset", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", + "The spacing between the array copies in Linear pattern", + ), + ) + obj.addProperty( + "App::PropertyInteger", + "CopiesX", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "The number of copies in X direction in Linear pattern" + ), + ) + obj.addProperty( + "App::PropertyInteger", + "CopiesY", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "The number of copies in Y direction in Linear pattern" + ), + ) + obj.addProperty( + "App::PropertyAngle", + "Angle", + "Path", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Total angle in Polar pattern"), + ) + obj.addProperty( + "App::PropertyInteger", + "Copies", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "The number of copies in Linear 1D and Polar pattern" + ), + ) + obj.addProperty( + "App::PropertyVector", + "Centre", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "The centre of rotation in Polar pattern" + ), + ) + obj.addProperty( + "App::PropertyBool", + "SwapDirection", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", + "Make copies in X direction before Y in Linear 2D pattern", + ), + ) + obj.addProperty( + "App::PropertyInteger", + "JitterPercent", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "Percent of copies to randomly offset" + ), + ) + obj.addProperty( + "App::PropertyVectorDistance", + "JitterMagnitude", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "Maximum random offset of copies" + ), + ) + obj.addProperty( + "App::PropertyLink", + "ToolController", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", + "The tool controller that will be used to calculate the path", + ), + ) + obj.addProperty( + "App::PropertyBool", + "Active", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "PathOp", "Make False, to prevent operation from generating code" + ), + ) obj.Active = True - obj.Type = ['Linear1D', 'Linear2D', 'Polar'] + obj.Type = ["Linear1D", "Linear2D", "Polar"] self.setEditorModes(obj) obj.Proxy = self @@ -78,26 +153,26 @@ class ObjectArray: return None def setEditorModes(self, obj): - if obj.Type == 'Linear1D': + if obj.Type == "Linear1D": angleMode = centreMode = copiesXMode = copiesYMode = swapDirectionMode = 2 copiesMode = offsetMode = 0 - elif obj.Type == 'Linear2D': + elif obj.Type == "Linear2D": angleMode = copiesMode = centreMode = 2 copiesXMode = copiesYMode = offsetMode = swapDirectionMode = 0 - elif obj.Type == 'Polar': + elif obj.Type == "Polar": angleMode = copiesMode = centreMode = 0 copiesXMode = copiesYMode = offsetMode = swapDirectionMode = 2 - obj.setEditorMode('Angle', angleMode) - obj.setEditorMode('Copies', copiesMode) - obj.setEditorMode('Centre', centreMode) - obj.setEditorMode('CopiesX', copiesXMode) - obj.setEditorMode('CopiesY', copiesYMode) - obj.setEditorMode('Offset', offsetMode) - obj.setEditorMode('SwapDirection', swapDirectionMode) - obj.setEditorMode('JitterPercent', 0) - obj.setEditorMode('JitterMagnitude', 0) - obj.setEditorMode('ToolController', 2) + obj.setEditorMode("Angle", angleMode) + obj.setEditorMode("Copies", copiesMode) + obj.setEditorMode("Centre", centreMode) + obj.setEditorMode("CopiesX", copiesXMode) + obj.setEditorMode("CopiesY", copiesYMode) + obj.setEditorMode("Offset", offsetMode) + obj.setEditorMode("SwapDirection", swapDirectionMode) + obj.setEditorMode("JitterPercent", 0) + obj.setEditorMode("JitterMagnitude", 0) + obj.setEditorMode("ToolController", 2) def onChanged(self, obj, prop): if prop == "Type": @@ -107,31 +182,41 @@ class ObjectArray: """onDocumentRestored(obj) ... Called automatically when document is restored.""" if not hasattr(obj, "Active"): - obj.addProperty("App::PropertyBool", "Active", - "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make False, to prevent operation from generating code")) + obj.addProperty( + "App::PropertyBool", + "Active", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "PathOp", "Make False, to prevent operation from generating code" + ), + ) obj.Active = True self.setEditorModes(obj) def rotatePath(self, path, angle, centre): - ''' - Rotates Path around given centre vector - Only X and Y is considered - ''' - CmdMoveRapid = ['G0', 'G00'] - CmdMoveStraight = ['G1', 'G01'] - CmdMoveCW = ['G2', 'G02'] - CmdMoveCCW = ['G3', 'G03'] - CmdDrill = ['G81', 'G82', 'G83'] - CmdMoveArc = CmdMoveCW + CmdMoveCCW - CmdMove = CmdMoveStraight + CmdMoveArc + """ + Rotates Path around given centre vector + Only X and Y is considered + """ + CmdMoveRapid = ["G0", "G00"] + CmdMoveStraight = ["G1", "G01"] + CmdMoveCW = ["G2", "G02"] + CmdMoveCCW = ["G3", "G03"] + CmdDrill = ["G81", "G82", "G83"] + CmdMoveArc = CmdMoveCW + CmdMoveCCW + CmdMove = CmdMoveStraight + CmdMoveArc commands = [] ang = angle / 180 * math.pi currX = 0 currY = 0 for cmd in path.Commands: - if (cmd.Name in CmdMoveRapid) or (cmd.Name in CmdMove) or (cmd.Name in CmdDrill): + if ( + (cmd.Name in CmdMoveRapid) + or (cmd.Name in CmdMove) + or (cmd.Name in CmdDrill) + ): params = cmd.Parameters x = params.get("X") if x is None: @@ -151,7 +236,7 @@ class ObjectArray: ny = y * math.cos(ang) + x * math.sin(ang) # "move" the centre back and update - params.update({'X': nx + centre.x, 'Y': ny + centre.y}) + params.update({"X": nx + centre.x, "Y": ny + centre.y}) # Arcs need to have the I and J params rotated as well if cmd.Name in CmdMoveArc: @@ -164,7 +249,7 @@ class ObjectArray: ni = i * math.cos(ang) - j * math.sin(ang) nj = j * math.cos(ang) + i * math.sin(ang) - params.update({'I': ni, 'J': nj}) + params.update({"I": ni, "J": nj}) cmd.Parameters = params commands.append(cmd) @@ -184,15 +269,26 @@ class ObjectArray: obj.ToolController = base[0].ToolController - # Do not generate paths and clear current Path data if operation not + # Do not generate paths and clear current Path data if operation not if not obj.Active: if obj.Path: obj.Path = Path.Path() return - pa = PathArray(obj.Base, obj.Type, obj.Copies, obj.Offset, - obj.CopiesX, obj.CopiesY, obj.Angle, obj.Centre, obj.SwapDirection, - obj.JitterMagnitude, obj.JitterPercent, obj.Name) + pa = PathArray( + obj.Base, + obj.Type, + obj.Copies, + obj.Offset, + obj.CopiesX, + obj.CopiesY, + obj.Angle, + obj.Centre, + obj.SwapDirection, + obj.JitterMagnitude, + obj.JitterPercent, + obj.Name, + ) obj.Path = pa.getPath() @@ -201,10 +297,21 @@ class PathArray: This class receives one or more base operations and repeats those operations at set intervals based upon array type requested and the related settings for that type.""" - def __init__(self, baseList, arrayType, copies, offsetVector, - copiesX, copiesY, angle, centre, swapDirection, - jitterMagnitude=FreeCAD.Vector(0, 0, 0), jitterPercent=0, - seed='FreeCAD'): + def __init__( + self, + baseList, + arrayType, + copies, + offsetVector, + copiesX, + copiesY, + angle, + centre, + swapDirection, + jitterMagnitude=FreeCAD.Vector(0, 0, 0), + jitterPercent=0, + seed="FreeCAD", + ): self.baseList = list() self.arrayType = arrayType # ['Linear1D', 'Linear2D', 'Polar'] self.copies = copies @@ -230,10 +337,16 @@ class PathArray: Returns the position argument with a random vector shift applied.""" if self.jitterPercent == 0: pass - elif random.randint(0,100) < self.jitterPercent: - pos.x = pos.x + random.uniform(-self.jitterMagnitude.x, self.jitterMagnitude.y) - pos.y = pos.y + random.uniform(-self.jitterMagnitude.y, self.jitterMagnitude.y) - pos.z = pos.z + random.uniform(-self.jitterMagnitude.z, self.jitterMagnitude.z) + elif random.randint(0, 100) < self.jitterPercent: + pos.x = pos.x + random.uniform( + -self.jitterMagnitude.x, self.jitterMagnitude.y + ) + pos.y = pos.y + random.uniform( + -self.jitterMagnitude.y, self.jitterMagnitude.y + ) + pos.z = pos.z + random.uniform( + -self.jitterMagnitude.z, self.jitterMagnitude.z + ) return pos # Public method @@ -255,32 +368,48 @@ class PathArray: return if b.ToolController != base[0].ToolController: # this may be important if Job output is split by tool controller - PathLog.warning(translate("PathArray", "Arrays of paths having different tool controllers are handled according to the tool controller of the first path.")) + PathLog.warning( + translate( + "PathArray", + "Arrays of paths having different tool controllers are handled according to the tool controller of the first path.", + ) + ) # build copies output = "" random.seed(self.seed) - if self.arrayType == 'Linear1D': + if self.arrayType == "Linear1D": for i in range(self.copies): - pos = FreeCAD.Vector(self.offsetVector.x * (i + 1), self.offsetVector.y * (i + 1), self.offsetVector.z * (i + 1)) + pos = FreeCAD.Vector( + self.offsetVector.x * (i + 1), + self.offsetVector.y * (i + 1), + self.offsetVector.z * (i + 1), + ) pos = self._calculateJitter(pos) for b in base: pl = FreeCAD.Placement() pl.move(pos) - np = Path.Path([cm.transform(pl) - for cm in b.Path.Commands]) + np = Path.Path([cm.transform(pl) for cm in b.Path.Commands]) output += np.toGCode() - elif self.arrayType == 'Linear2D': + elif self.arrayType == "Linear2D": if self.swapDirection: for i in range(self.copiesY + 1): for j in range(self.copiesX + 1): if (i % 2) == 0: - pos = FreeCAD.Vector(self.offsetVector.x * j, self.offsetVector.y * i, self.offsetVector.z * i) + pos = FreeCAD.Vector( + self.offsetVector.x * j, + self.offsetVector.y * i, + self.offsetVector.z * i, + ) else: - pos = FreeCAD.Vector(self.offsetVector.x * (self.copiesX - j), self.offsetVector.y * i, self.offsetVector.z * i) + pos = FreeCAD.Vector( + self.offsetVector.x * (self.copiesX - j), + self.offsetVector.y * i, + self.offsetVector.z * i, + ) pos = self._calculateJitter(pos) for b in base: @@ -288,15 +417,25 @@ class PathArray: # do not process the index 0,0. It will be processed by the base Paths themselves if not (i == 0 and j == 0): pl.move(pos) - np = Path.Path([cm.transform(pl) for cm in b.Path.Commands]) + np = Path.Path( + [cm.transform(pl) for cm in b.Path.Commands] + ) output += np.toGCode() else: for i in range(self.copiesX + 1): for j in range(self.copiesY + 1): if (i % 2) == 0: - pos = FreeCAD.Vector(self.offsetVector.x * i, self.offsetVector.y * j, self.offsetVector.z * i) + pos = FreeCAD.Vector( + self.offsetVector.x * i, + self.offsetVector.y * j, + self.offsetVector.z * i, + ) else: - pos = FreeCAD.Vector(self.offsetVector.x * i, self.offsetVector.y * (self.copiesY - j), self.offsetVector.z * i) + pos = FreeCAD.Vector( + self.offsetVector.x * i, + self.offsetVector.y * (self.copiesY - j), + self.offsetVector.z * i, + ) pos = self._calculateJitter(pos) for b in base: @@ -304,7 +443,9 @@ class PathArray: # do not process the index 0,0. It will be processed by the base Paths themselves if not (i == 0 and j == 0): pl.move(pos) - np = Path.Path([cm.transform(pl) for cm in b.Path.Commands]) + np = Path.Path( + [cm.transform(pl) for cm in b.Path.Commands] + ) output += np.toGCode() # Eif else: @@ -321,7 +462,6 @@ class PathArray: class ViewProviderArray: - def __init__(self, vobj): self.Object = vobj.Object vobj.Proxy = self @@ -345,11 +485,14 @@ class ViewProviderArray: class CommandPathArray: - def GetResources(self): - return {'Pixmap': 'Path_Array', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Array"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Creates an array from selected path(s)")} + return { + "Pixmap": "Path_Array", + "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_Array", "Array"), + "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "Path_Array", "Creates an array from selected path(s)" + ), + } def IsActive(self): if bool(FreeCADGui.Selection.getSelection()) is False: @@ -357,7 +500,7 @@ class CommandPathArray: try: obj = FreeCADGui.Selection.getSelectionEx()[0].Object return isinstance(obj.Proxy, PathScripts.PathOp.ObjectOp) - except(IndexError, AttributeError): + except (IndexError, AttributeError): return False def Activated(self): @@ -366,9 +509,13 @@ class CommandPathArray: selection = FreeCADGui.Selection.getSelection() for sel in selection: - if not(sel.isDerivedFrom("Path::Feature")): + if not (sel.isDerivedFrom("Path::Feature")): FreeCAD.Console.PrintError( - translate("Path_Array", "Arrays can be created only from Path operations.")+"\n") + translate( + "Path_Array", "Arrays can be created only from Path operations." + ) + + "\n" + ) return # if everything is ok, execute and register the transaction in the @@ -377,19 +524,23 @@ class CommandPathArray: FreeCADGui.addModule("PathScripts.PathArray") FreeCADGui.addModule("PathScripts.PathUtils") - FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Array")') + FreeCADGui.doCommand( + 'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Array")' + ) - FreeCADGui.doCommand('PathScripts.PathArray.ObjectArray(obj)') + FreeCADGui.doCommand("PathScripts.PathArray.ObjectArray(obj)") - baseString = "[%s]" % ','.join(["FreeCAD.ActiveDocument.%s" % sel.Name for sel in selection]) - FreeCADGui.doCommand('obj.Base = %s' % baseString) + baseString = "[%s]" % ",".join( + ["FreeCAD.ActiveDocument.%s" % sel.Name for sel in selection] + ) + FreeCADGui.doCommand("obj.Base = %s" % baseString) - FreeCADGui.doCommand('obj.ViewObject.Proxy = 0') - FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') + FreeCADGui.doCommand("obj.ViewObject.Proxy = 0") + FreeCADGui.doCommand("PathScripts.PathUtils.addToJob(obj)") FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_Array', CommandPathArray()) + FreeCADGui.addCommand("Path_Array", CommandPathArray()) diff --git a/src/Mod/Path/PathScripts/PathCollision.py b/src/Mod/Path/PathScripts/PathCollision.py index 260dbd2b48..9e0e8e9a55 100644 --- a/src/Mod/Path/PathScripts/PathCollision.py +++ b/src/Mod/Path/PathScripts/PathCollision.py @@ -25,16 +25,17 @@ import PathScripts.PathLog as PathLog from PySide import QtCore from PathScripts.PathUtils import waiting_effects -LOG_MODULE = 'PathCollision' +LOG_MODULE = "PathCollision" PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE) -PathLog.trackModule('PathCollision') -FreeCAD.setLogLevel('Path.Area', 0) +PathLog.trackModule("PathCollision") +FreeCAD.setLogLevel("Path.Area", 0) # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) + __title__ = "Path Collision Utility" __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" @@ -44,19 +45,26 @@ __url__ = "https://www.freecadweb.org" class _CollisionSim: def __init__(self, obj): - #obj.addProperty("App::PropertyLink", "Original", "reference", QtCore.QT_TRANSLATE_NOOP("App::Property", "The base object this collision refers to")) + # obj.addProperty("App::PropertyLink", "Original", "reference", QtCore.QT_TRANSLATE_NOOP("App::Property", "The base object this collision refers to")) obj.Proxy = self def execute(self, fp): - '''Do something when doing a recomputation, this method is mandatory''' - print('_CollisionSim', fp) + """Do something when doing a recomputation, this method is mandatory""" + print("_CollisionSim", fp) class _ViewProviderCollisionSim: def __init__(self, vobj): self.Object = vobj.Object vobj.Proxy = self - vobj.addProperty("App::PropertyLink", "Original", "reference", QtCore.QT_TRANSLATE_NOOP("App::Property", "The base object this collision refers to")) + vobj.addProperty( + "App::PropertyLink", + "Original", + "reference", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "The base object this collision refers to" + ), + ) def attach(self, vobj): self.Object = vobj.Object @@ -82,12 +90,14 @@ class _ViewProviderCollisionSim: def __compareBBSpace(bb1, bb2): - if (bb1.XMin == bb2.XMin and - bb1.XMax == bb2.XMax and - bb1.YMin == bb2.YMin and - bb1.YMax == bb2.YMax and - bb1.ZMin == bb2.ZMin and - bb1.ZMax == bb2.ZMax): + if ( + bb1.XMin == bb2.XMin + and bb1.XMax == bb2.XMax + and bb1.YMin == bb2.YMin + and bb1.YMax == bb2.YMax + and bb1.ZMin == bb2.ZMin + and bb1.ZMax == bb2.ZMax + ): return True return False @@ -124,6 +134,3 @@ def getCollisionObject(baseobject, simobject): obj.ViewObject.Original = baseobject return result - - - diff --git a/src/Mod/Path/PathScripts/PathComment.py b/src/Mod/Path/PathScripts/PathComment.py index d949abf97d..bcd9a468f2 100644 --- a/src/Mod/Path/PathScripts/PathComment.py +++ b/src/Mod/Path/PathScripts/PathComment.py @@ -20,7 +20,7 @@ # * * # *************************************************************************** -'''Used for CNC machine comments for Path module. Create a comment and place it in the Document tree.''' +"""Used for CNC machine comments for Path module. Create a comment and place it in the Document tree.""" import FreeCAD import FreeCADGui @@ -31,14 +31,15 @@ from PySide import QtCore def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) -class Comment: +class Comment: def __init__(self, obj): - obj.addProperty("App::PropertyString", "Comment", - "Path", "Comment or note for CNC program") + obj.addProperty( + "App::PropertyString", "Comment", "Path", "Comment or note for CNC program" + ) obj.Proxy = self mode = 2 - obj.setEditorMode('Placement', mode) + obj.setEditorMode("Placement", mode) def __getstate__(self): return None @@ -51,25 +52,24 @@ class Comment: def execute(self, obj): output = "" - output += '(' + str(obj.Comment) + ')\n' + output += "(" + str(obj.Comment) + ")\n" path = Path.Path(output) obj.Path = path class _ViewProviderComment: - def __init__(self, vobj): # mandatory vobj.Proxy = self mode = 2 - vobj.setEditorMode('LineWidth', mode) - vobj.setEditorMode('MarkerColor', mode) - vobj.setEditorMode('NormalColor', mode) - vobj.setEditorMode('DisplayMode', mode) - vobj.setEditorMode('BoundingBox', mode) - vobj.setEditorMode('Selectable', mode) - vobj.setEditorMode('ShapeColor', mode) - vobj.setEditorMode('Transparency', mode) - vobj.setEditorMode('Visibility', mode) + vobj.setEditorMode("LineWidth", mode) + vobj.setEditorMode("MarkerColor", mode) + vobj.setEditorMode("NormalColor", mode) + vobj.setEditorMode("DisplayMode", mode) + vobj.setEditorMode("BoundingBox", mode) + vobj.setEditorMode("Selectable", mode) + vobj.setEditorMode("ShapeColor", mode) + vobj.setEditorMode("Transparency", mode) + vobj.setEditorMode("Visibility", mode) def __getstate__(self): # mandatory return None @@ -83,23 +83,26 @@ class _ViewProviderComment: def onChanged(self, vobj, prop): # optional # pylint: disable=unused-argument mode = 2 - vobj.setEditorMode('LineWidth', mode) - vobj.setEditorMode('MarkerColor', mode) - vobj.setEditorMode('NormalColor', mode) - vobj.setEditorMode('DisplayMode', mode) - vobj.setEditorMode('BoundingBox', mode) - vobj.setEditorMode('Selectable', mode) - vobj.setEditorMode('ShapeColor', mode) - vobj.setEditorMode('Transparency', mode) - vobj.setEditorMode('Visibility', mode) + vobj.setEditorMode("LineWidth", mode) + vobj.setEditorMode("MarkerColor", mode) + vobj.setEditorMode("NormalColor", mode) + vobj.setEditorMode("DisplayMode", mode) + vobj.setEditorMode("BoundingBox", mode) + vobj.setEditorMode("Selectable", mode) + vobj.setEditorMode("ShapeColor", mode) + vobj.setEditorMode("Transparency", mode) + vobj.setEditorMode("Visibility", mode) class CommandPathComment: - def GetResources(self): - return {'Pixmap': 'Path_Comment', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Comment", "Comment"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Comment", "Add a Comment to your CNC program")} + return { + "Pixmap": "Path_Comment", + "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_Comment", "Comment"), + "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "Path_Comment", "Add a Comment to your CNC program" + ), + } def IsActive(self): if FreeCAD.ActiveDocument is not None: @@ -110,9 +113,10 @@ class CommandPathComment: def Activated(self): FreeCAD.ActiveDocument.openTransaction( - translate("Path_Comment", "Create a Comment in your CNC program")) + translate("Path_Comment", "Create a Comment in your CNC program") + ) FreeCADGui.addModule("PathScripts.PathComment") - snippet = ''' + snippet = """ import Path import PathScripts from PathScripts import PathUtils @@ -121,14 +125,15 @@ PathScripts.PathComment.Comment(obj) PathScripts.PathComment._ViewProviderComment(obj.ViewObject) PathUtils.addToJob(obj) -''' +""" FreeCADGui.doCommand(snippet) FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() + if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_Comment', CommandPathComment()) + FreeCADGui.addCommand("Path_Comment", CommandPathComment()) FreeCAD.Console.PrintLog("Loading PathComment... done\n") diff --git a/src/Mod/Path/PathScripts/PathCopy.py b/src/Mod/Path/PathScripts/PathCopy.py index fb20a73ebf..bf626b2dc6 100644 --- a/src/Mod/Path/PathScripts/PathCopy.py +++ b/src/Mod/Path/PathScripts/PathCopy.py @@ -32,10 +32,22 @@ def translate(context, text, disambig=None): class ObjectPathCopy: - def __init__(self, obj): - obj.addProperty("App::PropertyLink", "Base", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The path to be copied")) - obj.addProperty("App::PropertyLink", "ToolController", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The tool controller that will be used to calculate the path")) + obj.addProperty( + "App::PropertyLink", + "Base", + "Path", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The path to be copied"), + ) + obj.addProperty( + "App::PropertyLink", + "ToolController", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", + "The tool controller that will be used to calculate the path", + ), + ) obj.Proxy = self def __getstate__(self): @@ -46,14 +58,13 @@ class ObjectPathCopy: def execute(self, obj): if obj.Base: - if hasattr(obj.Base, 'ToolController'): + if hasattr(obj.Base, "ToolController"): obj.ToolController = obj.Base.ToolController if obj.Base.Path: obj.Path = obj.Base.Path.copy() class ViewProviderPathCopy: - def __init__(self, vobj): self.Object = vobj.Object vobj.Proxy = self @@ -73,11 +84,14 @@ class ViewProviderPathCopy: class CommandPathCopy: - def GetResources(self): - return {'Pixmap': 'Path_Copy', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Copy", "Copy"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Copy", "Creates a linked copy of another path")} + return { + "Pixmap": "Path_Copy", + "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_Copy", "Copy"), + "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "Path_Copy", "Creates a linked copy of another path" + ), + } def IsActive(self): if FreeCAD.ActiveDocument is not None: @@ -88,11 +102,10 @@ class CommandPathCopy: def Activated(self): - FreeCAD.ActiveDocument.openTransaction( - translate("Path_Copy", "Create Copy")) + FreeCAD.ActiveDocument.openTransaction(translate("Path_Copy", "Create Copy")) FreeCADGui.addModule("PathScripts.PathCopy") - consolecode = ''' + consolecode = """ import Path import PathScripts from PathScripts import PathCopy @@ -123,7 +136,7 @@ proj.Group = g FreeCAD.ActiveDocument.recompute() -''' +""" FreeCADGui.doCommand(consolecode) FreeCAD.ActiveDocument.commitTransaction() @@ -132,6 +145,6 @@ FreeCAD.ActiveDocument.recompute() if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_Copy', CommandPathCopy()) + FreeCADGui.addCommand("Path_Copy", CommandPathCopy()) FreeCAD.Console.PrintLog("Loading PathCopy... done\n") diff --git a/src/Mod/Path/PathScripts/PathCustom.py b/src/Mod/Path/PathScripts/PathCustom.py index b31534d07e..c8e517b4b3 100644 --- a/src/Mod/Path/PathScripts/PathCustom.py +++ b/src/Mod/Path/PathScripts/PathCustom.py @@ -48,8 +48,12 @@ class ObjectCustom(PathOp.ObjectOp): return PathOp.FeatureTool | PathOp.FeatureCoolant def initOperation(self, obj): - obj.addProperty("App::PropertyStringList", "Gcode", "Path", - QtCore.QT_TRANSLATE_NOOP("PathCustom", "The gcode to be inserted")) + obj.addProperty( + "App::PropertyStringList", + "Gcode", + "Path", + QtCore.QT_TRANSLATE_NOOP("PathCustom", "The gcode to be inserted"), + ) obj.Proxy = self @@ -69,7 +73,7 @@ def SetupProperties(): def Create(name, obj=None, parentJob=None): - '''Create(name) ... Creates and returns a Custom operation.''' + """Create(name) ... Creates and returns a Custom operation.""" if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) obj.Proxy = ObjectCustom(obj, name, parentJob) diff --git a/src/Mod/Path/PathScripts/PathCustomGui.py b/src/Mod/Path/PathScripts/PathCustomGui.py index 987bcfb50e..39c237cdcd 100644 --- a/src/Mod/Path/PathScripts/PathCustomGui.py +++ b/src/Mod/Path/PathScripts/PathCustomGui.py @@ -46,25 +46,25 @@ def translate(context, text, disambig=None): class TaskPanelOpPage(PathOpGui.TaskPanelPage): - '''Page controller class for the Custom operation.''' + """Page controller class for the Custom operation.""" def getForm(self): - '''getForm() ... returns UI''' + """getForm() ... returns UI""" return FreeCADGui.PySideUic.loadUi(":/panels/PageOpCustomEdit.ui") def getFields(self, obj): - '''getFields(obj) ... transfers values from UI to obj's properties''' + """getFields(obj) ... transfers values from UI to obj's properties""" self.updateToolController(obj, self.form.toolController) self.updateCoolant(obj, self.form.coolantController) def setFields(self, obj): - '''setFields(obj) ... transfers obj's property values to UI''' + """setFields(obj) ... transfers obj's property values to UI""" self.setupToolController(obj, self.form.toolController) self.form.txtGCode.setText("\n".join(obj.Gcode)) self.setupCoolant(obj, self.form.coolantController) def getSignalsForUpdate(self, obj): - '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' + """getSignalsForUpdate(obj) ... return list of signals for updating obj""" signals = [] signals.append(self.form.toolController.currentIndexChanged) signals.append(self.form.coolantController.currentIndexChanged) @@ -75,10 +75,14 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.obj.Gcode = self.form.txtGCode.toPlainText().splitlines() -Command = PathOpGui.SetupOperation('Custom', PathCustom.Create, TaskPanelOpPage, - 'Path_Custom', - QtCore.QT_TRANSLATE_NOOP("Path_Custom", "Custom"), - QtCore.QT_TRANSLATE_NOOP("Path_Custom", "Create custom gcode snippet"), - PathCustom.SetupProperties) +Command = PathOpGui.SetupOperation( + "Custom", + PathCustom.Create, + TaskPanelOpPage, + "Path_Custom", + QtCore.QT_TRANSLATE_NOOP("Path_Custom", "Custom"), + QtCore.QT_TRANSLATE_NOOP("Path_Custom", "Create custom gcode snippet"), + PathCustom.SetupProperties, +) FreeCAD.Console.PrintLog("Loading PathCustomGui... done\n") diff --git a/src/Mod/Path/PathScripts/PathFixture.py b/src/Mod/Path/PathScripts/PathFixture.py index b841e94445..defbd0fe0a 100644 --- a/src/Mod/Path/PathScripts/PathFixture.py +++ b/src/Mod/Path/PathScripts/PathFixture.py @@ -20,29 +20,75 @@ # * * # *************************************************************************** -''' Used to create CNC machine fixture offsets such as G54,G55, etc...''' +""" Used to create CNC machine fixture offsets such as G54,G55, etc...""" import FreeCAD import FreeCADGui import Path import PathScripts.PathUtils as PathUtils -from PySide import QtCore#, QtGui +from PySide import QtCore # , QtGui # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) + class Fixture: - def __init__(self,obj): - obj.addProperty("App::PropertyEnumeration", "Fixture", "Path",QtCore.QT_TRANSLATE_NOOP("App::Property","Fixture Offset Number")) - obj.Fixture=['G53','G54','G55','G56','G57','G58','G59','G59.1', 'G59.2', 'G59.3', 'G59.4', 'G59.5','G59.6','G59.7', 'G59.8', 'G59.9'] - obj.addProperty("App::PropertyBool","Active","Path",QtCore.QT_TRANSLATE_NOOP("App::Property","Make False, to prevent operation from generating code")) + def __init__(self, obj): + obj.addProperty( + "App::PropertyEnumeration", + "Fixture", + "Path", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Fixture Offset Number"), + ) + obj.Fixture = [ + "G53", + "G54", + "G55", + "G56", + "G57", + "G58", + "G59", + "G59.1", + "G59.2", + "G59.3", + "G59.4", + "G59.5", + "G59.6", + "G59.7", + "G59.8", + "G59.9", + ] + obj.addProperty( + "App::PropertyBool", + "Active", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "Make False, to prevent operation from generating code" + ), + ) obj.Proxy = self def execute(self, obj): - fixlist = ['G53', 'G54', 'G55', 'G56', 'G57', 'G58', 'G59', 'G59.1', - 'G59.2', 'G59.3', 'G59.4', 'G59.5', 'G59.6', 'G59.7', 'G59.8', 'G59.9'] + fixlist = [ + "G53", + "G54", + "G55", + "G56", + "G57", + "G58", + "G59", + "G59.1", + "G59.2", + "G59.3", + "G59.4", + "G59.5", + "G59.6", + "G59.7", + "G59.8", + "G59.9", + ] fixture = fixlist.index(obj.Fixture) obj.Path = Path.Path(str(obj.Fixture)) obj.Label = "Fixture" + str(fixture) @@ -58,20 +104,19 @@ class Fixture: class _ViewProviderFixture: - def __init__(self, vobj): # mandatory # obj.addProperty("App::PropertyFloat","SomePropertyName","PropertyGroup","Description of this property") vobj.Proxy = self mode = 2 - vobj.setEditorMode('LineWidth', mode) - vobj.setEditorMode('MarkerColor', mode) - vobj.setEditorMode('NormalColor', mode) - vobj.setEditorMode('DisplayMode', mode) - vobj.setEditorMode('BoundingBox', mode) - vobj.setEditorMode('Selectable', mode) - vobj.setEditorMode('ShapeColor', mode) - vobj.setEditorMode('Transparency', mode) - vobj.setEditorMode('Visibility', mode) + vobj.setEditorMode("LineWidth", mode) + vobj.setEditorMode("MarkerColor", mode) + vobj.setEditorMode("NormalColor", mode) + vobj.setEditorMode("DisplayMode", mode) + vobj.setEditorMode("BoundingBox", mode) + vobj.setEditorMode("Selectable", mode) + vobj.setEditorMode("ShapeColor", mode) + vobj.setEditorMode("Transparency", mode) + vobj.setEditorMode("Visibility", mode) def __getstate__(self): # mandatory return None @@ -85,15 +130,15 @@ class _ViewProviderFixture: def onChanged(self, vobj, prop): # optional # pylint: disable=unused-argument mode = 2 - vobj.setEditorMode('LineWidth', mode) - vobj.setEditorMode('MarkerColor', mode) - vobj.setEditorMode('NormalColor', mode) - vobj.setEditorMode('DisplayMode', mode) - vobj.setEditorMode('BoundingBox', mode) - vobj.setEditorMode('Selectable', mode) - vobj.setEditorMode('ShapeColor', mode) - vobj.setEditorMode('Transparency', mode) - vobj.setEditorMode('Visibility', mode) + vobj.setEditorMode("LineWidth", mode) + vobj.setEditorMode("MarkerColor", mode) + vobj.setEditorMode("NormalColor", mode) + vobj.setEditorMode("DisplayMode", mode) + vobj.setEditorMode("BoundingBox", mode) + vobj.setEditorMode("Selectable", mode) + vobj.setEditorMode("ShapeColor", mode) + vobj.setEditorMode("Transparency", mode) + vobj.setEditorMode("Visibility", mode) def updateData(self, vobj, prop): # optional # this is executed when a property of the APP OBJECT changes @@ -109,11 +154,14 @@ class _ViewProviderFixture: class CommandPathFixture: - def GetResources(self): - return {'Pixmap': 'Path_Datums', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Fixture", "Fixture"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Fixture", "Creates a Fixture Offset object")} + return { + "Pixmap": "Path_Datums", + "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_Fixture", "Fixture"), + "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "Path_Fixture", "Creates a Fixture Offset object" + ), + } def IsActive(self): if FreeCAD.ActiveDocument is not None: @@ -123,9 +171,11 @@ class CommandPathFixture: return False def Activated(self): - FreeCAD.ActiveDocument.openTransaction(translate("Path_Fixture", "Create a Fixture Offset")) + FreeCAD.ActiveDocument.openTransaction( + translate("Path_Fixture", "Create a Fixture Offset") + ) FreeCADGui.addModule("PathScripts.PathFixture") - snippet = ''' + snippet = """ import Path import PathScripts from PathScripts import PathUtils @@ -137,14 +187,15 @@ PathScripts.PathFixture._ViewProviderFixture(obj.ViewObject) PathUtils.addToJob(obj) -''' +""" FreeCADGui.doCommand(snippet) FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() + if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_Fixture', CommandPathFixture()) + FreeCADGui.addCommand("Path_Fixture", CommandPathFixture()) FreeCAD.Console.PrintLog("Loading PathFixture... done\n") diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index c90ccb7034..ff276c9385 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -31,7 +31,8 @@ from PySide import QtCore # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -Part = LazyLoader('Part', globals(), 'Part') + +Part = LazyLoader("Part", globals(), "Part") __title__ = "PathGeom - geometry utilities for Path" __author__ = "sliptonic (Brad Collette)" @@ -41,15 +42,17 @@ __doc__ = "Functions to extract and convert between Path.Command and Part.Edge a Tolerance = 0.000001 PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) + class Side: """Class to determine and define the side a Path is on, or Vectors are in relation to each other.""" - Left = +1 + + Left = +1 Right = -1 Straight = 0 On = 0 @@ -59,10 +62,10 @@ class Side: """toString(side) Returns a string representation of the enum value.""" if side == cls.Left: - return 'Left' + return "Left" if side == cls.Right: - return 'Right' - return 'On' + return "Right" + return "On" @classmethod def of(cls, ptRef, pt): @@ -71,72 +74,89 @@ class Side: If both Points are viewed as vectors with their origin in (0,0,0) then the two vectors either form a straight line (On) or pt lies in the left or right hemisphere in regards to ptRef.""" - d = -ptRef.x*pt.y + ptRef.y*pt.x + d = -ptRef.x * pt.y + ptRef.y * pt.x if d < 0: return cls.Left if d > 0: return cls.Right return cls.Straight -CmdMoveRapid = ['G0', 'G00'] -CmdMoveStraight = ['G1', 'G01'] -CmdMoveCW = ['G2', 'G02'] -CmdMoveCCW = ['G3', 'G03'] -CmdMoveArc = CmdMoveCW + CmdMoveCCW -CmdMove = CmdMoveStraight + CmdMoveArc -CmdMoveAll = CmdMove + CmdMoveRapid + +CmdMoveRapid = ["G0", "G00"] +CmdMoveStraight = ["G1", "G01"] +CmdMoveCW = ["G2", "G02"] +CmdMoveCCW = ["G3", "G03"] +CmdMoveArc = CmdMoveCW + CmdMoveCCW +CmdMove = CmdMoveStraight + CmdMoveArc +CmdMoveAll = CmdMove + CmdMoveRapid + def isRoughly(float1, float2, error=Tolerance): """isRoughly(float1, float2, [error=Tolerance]) Returns true if the two values are the same within a given error.""" return math.fabs(float1 - float2) <= error + def pointsCoincide(p1, p2, error=Tolerance): """pointsCoincide(p1, p2, [error=Tolerance]) Return True if two points are roughly identical (see also isRoughly).""" - return isRoughly(p1.x, p2.x, error) and isRoughly(p1.y, p2.y, error) and isRoughly(p1.z, p2.z, error) + return ( + isRoughly(p1.x, p2.x, error) + and isRoughly(p1.y, p2.y, error) + and isRoughly(p1.z, p2.z, error) + ) + def edgesMatch(e0, e1, error=Tolerance): """edgesMatch(e0, e1, [error=Tolerance] Return true if the edges start and end at the same point and have the same type of curve.""" if type(e0.Curve) != type(e1.Curve) or len(e0.Vertexes) != len(e1.Vertexes): return False - return all(pointsCoincide(e0.Vertexes[i].Point, e1.Vertexes[i].Point, error) for i in range(len(e0.Vertexes))) + return all( + pointsCoincide(e0.Vertexes[i].Point, e1.Vertexes[i].Point, error) + for i in range(len(e0.Vertexes)) + ) + def edgeConnectsTo(edge, vector, error=Tolerance): """edgeConnectsTop(edge, vector, error=Tolerance) Returns True if edge connects to given vector.""" - return pointsCoincide(edge.valueAt(edge.FirstParameter), vector, error) or pointsCoincide(edge.valueAt(edge.LastParameter), vector, error) + return pointsCoincide( + edge.valueAt(edge.FirstParameter), vector, error + ) or pointsCoincide(edge.valueAt(edge.LastParameter), vector, error) + def getAngle(vector): """getAngle(vector) Returns the angle [-pi,pi] of a vector using the X-axis as the reference. Positive angles for vertexes in the upper hemisphere (positive y values) and negative angles for the lower hemisphere.""" - a = vector.getAngle(Vector(1,0,0)) + a = vector.getAngle(Vector(1, 0, 0)) if vector.y < 0: return -a return a -def diffAngle(a1, a2, direction = 'CW'): + +def diffAngle(a1, a2, direction="CW"): """diffAngle(a1, a2, [direction='CW']) Returns the difference between two angles (a1 -> a2) into a given direction.""" - if direction == 'CW': + if direction == "CW": while a1 < a2: - a1 += 2*math.pi + a1 += 2 * math.pi a = a1 - a2 else: while a2 < a1: - a2 += 2*math.pi + a2 += 2 * math.pi a = a2 - a1 return a + def isVertical(obj): - '''isVertical(obj) ... answer True if obj points into Z''' + """isVertical(obj) ... answer True if obj points into Z""" if type(obj) == FreeCAD.Vector: return isRoughly(obj.x, 0) and isRoughly(obj.y, 0) - if obj.ShapeType == 'Face': + if obj.ShapeType == "Face": if type(obj.Surface) == Part.Plane: return isHorizontal(obj.Surface.Axis) if type(obj.Surface) == Part.Cylinder or type(obj.Surface) == Part.Cone: @@ -148,30 +168,39 @@ def isVertical(obj): if type(obj.Surface) == Part.SurfaceOfRevolution: return isHorizontal(obj.Surface.Direction) if type(obj.Surface) != Part.BSplineSurface: - PathLog.info(translate('PathGeom', "face %s not handled, assuming not vertical") % type(obj.Surface)) + PathLog.info( + translate("PathGeom", "face %s not handled, assuming not vertical") + % type(obj.Surface) + ) return None - if obj.ShapeType == 'Edge': + if obj.ShapeType == "Edge": if type(obj.Curve) == Part.Line or type(obj.Curve) == Part.LineSegment: return isVertical(obj.Vertexes[1].Point - obj.Vertexes[0].Point) - if type(obj.Curve) == Part.Circle or type(obj.Curve) == Part.Ellipse: # or type(obj.Curve) == Part.BSplineCurve: + if ( + type(obj.Curve) == Part.Circle or type(obj.Curve) == Part.Ellipse + ): # or type(obj.Curve) == Part.BSplineCurve: return isHorizontal(obj.Curve.Axis) if type(obj.Curve) == Part.BezierCurve: # the current assumption is that a bezier curve is vertical if its end points are vertical return isVertical(obj.Curve.EndPoint - obj.Curve.StartPoint) if type(obj.Curve) != Part.BSplineCurve: - PathLog.info(translate('PathGeom', "edge %s not handled, assuming not vertical") % type(obj.Curve)) + PathLog.info( + translate("PathGeom", "edge %s not handled, assuming not vertical") + % type(obj.Curve) + ) return None - PathLog.error(translate('PathGeom', "isVertical(%s) not supported") % obj) + PathLog.error(translate("PathGeom", "isVertical(%s) not supported") % obj) return None + def isHorizontal(obj): - '''isHorizontal(obj) ... answer True if obj points into X or Y''' + """isHorizontal(obj) ... answer True if obj points into X or Y""" if type(obj) == FreeCAD.Vector: return isRoughly(obj.z, 0) - if obj.ShapeType == 'Face': + if obj.ShapeType == "Face": if type(obj.Surface) == Part.Plane: return isVertical(obj.Surface.Axis) if type(obj.Surface) == Part.Cylinder or type(obj.Surface) == Part.Cone: @@ -184,18 +213,20 @@ def isHorizontal(obj): return isVertical(obj.Surface.Direction) return isRoughly(obj.BoundBox.ZLength, 0.0) - if obj.ShapeType == 'Edge': + if obj.ShapeType == "Edge": if type(obj.Curve) == Part.Line or type(obj.Curve) == Part.LineSegment: return isHorizontal(obj.Vertexes[1].Point - obj.Vertexes[0].Point) - if type(obj.Curve) == Part.Circle or type(obj.Curve) == Part.Ellipse: # or type(obj.Curve) == Part.BSplineCurve: + if ( + type(obj.Curve) == Part.Circle or type(obj.Curve) == Part.Ellipse + ): # or type(obj.Curve) == Part.BSplineCurve: return isVertical(obj.Curve.Axis) return isRoughly(obj.BoundBox.ZLength, 0.0) - PathLog.error(translate('PathGeom', "isHorizontal(%s) not supported") % obj) + PathLog.error(translate("PathGeom", "isHorizontal(%s) not supported") % obj) return None -def commandEndPoint(cmd, defaultPoint = Vector(), X='X', Y='Y', Z='Z'): +def commandEndPoint(cmd, defaultPoint=Vector(), X="X", Y="Y", Z="Z"): """commandEndPoint(cmd, [defaultPoint=Vector()], [X='X'], [Y='Y'], [Z='Z']) Extracts the end point from a Path Command.""" x = cmd.Parameters.get(X, defaultPoint.x) @@ -203,11 +234,13 @@ def commandEndPoint(cmd, defaultPoint = Vector(), X='X', Y='Y', Z='Z'): z = cmd.Parameters.get(Z, defaultPoint.z) return Vector(x, y, z) + def xy(point): """xy(point) Convenience function to return the projection of the Vector in the XY-plane.""" return Vector(point.x, point.y, 0) + def speedBetweenPoints(p0, p1, hSpeed, vSpeed): if isRoughly(hSpeed, vSpeed): return hSpeed @@ -223,7 +256,10 @@ def speedBetweenPoints(p0, p1, hSpeed, vSpeed): pitch = pitch + 1 while pitch > 1: pitch = pitch - 1 - PathLog.debug(" pitch = %g %g (%.2f, %.2f, %.2f) -> %.2f" % (pitch, math.atan2(xy(d).Length, d.z), d.x, d.y, d.z, xy(d).Length)) + PathLog.debug( + " pitch = %g %g (%.2f, %.2f, %.2f) -> %.2f" + % (pitch, math.atan2(xy(d).Length, d.z), d.x, d.y, d.z, xy(d).Length) + ) speed = vSpeed + pitch * (hSpeed - vSpeed) if speed > hSpeed and speed > vSpeed: return max(hSpeed, vSpeed) @@ -231,7 +267,8 @@ def speedBetweenPoints(p0, p1, hSpeed, vSpeed): return min(hSpeed, vSpeed) return speed -def cmdsForEdge(edge, flip = False, useHelixForBSpline = True, segm = 50, hSpeed = 0, vSpeed = 0): + +def cmdsForEdge(edge, flip=False, useHelixForBSpline=True, segm=50, hSpeed=0, vSpeed=0): """cmdsForEdge(edge, flip=False, useHelixForBSpline=True, segm=50) -> List(Path.Command) Returns a list of Path.Command representing the given edge. If flip is True the edge is considered to be backwards. @@ -240,31 +277,67 @@ def cmdsForEdge(edge, flip = False, useHelixForBSpline = True, segm = 50, hSpeed no direct Path.Command mapping and will be approximated by straight segments. segm is a factor for the segmentation of arbitrary curves not mapped to G1/2/3 commands. The higher the value the more segments will be used.""" - pt = edge.valueAt(edge.LastParameter) if not flip else edge.valueAt(edge.FirstParameter) - params = {'X': pt.x, 'Y': pt.y, 'Z': pt.z} + pt = ( + edge.valueAt(edge.LastParameter) + if not flip + else edge.valueAt(edge.FirstParameter) + ) + params = {"X": pt.x, "Y": pt.y, "Z": pt.z} if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: if hSpeed > 0 and vSpeed > 0: - pt2 = edge.valueAt(edge.FirstParameter) if not flip else edge.valueAt(edge.LastParameter) - params.update({'F': speedBetweenPoints(pt, pt2, hSpeed, vSpeed)}) - commands = [Path.Command('G1', params)] + pt2 = ( + edge.valueAt(edge.FirstParameter) + if not flip + else edge.valueAt(edge.LastParameter) + ) + params.update({"F": speedBetweenPoints(pt, pt2, hSpeed, vSpeed)}) + commands = [Path.Command("G1", params)] else: - p1 = edge.valueAt(edge.FirstParameter) if not flip else edge.valueAt(edge.LastParameter) - p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) + p1 = ( + edge.valueAt(edge.FirstParameter) + if not flip + else edge.valueAt(edge.LastParameter) + ) + p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter) / 2) p3 = pt - if hasattr(edge.Curve, 'Axis') and ((type(edge.Curve) == Part.Circle and isRoughly(edge.Curve.Axis.x, 0) and isRoughly(edge.Curve.Axis.y, 0)) or (useHelixForBSpline and type(edge.Curve) == Part.BSplineCurve)): + if hasattr(edge.Curve, "Axis") and ( + ( + type(edge.Curve) == Part.Circle + and isRoughly(edge.Curve.Axis.x, 0) + and isRoughly(edge.Curve.Axis.y, 0) + ) + or (useHelixForBSpline and type(edge.Curve) == Part.BSplineCurve) + ): # This is an arc or a helix and it should be represented by a simple G2/G3 command if edge.Curve.Axis.z < 0: - cmd = 'G2' if not flip else 'G3' + cmd = "G2" if not flip else "G3" else: - cmd = 'G3' if not flip else 'G2' + cmd = "G3" if not flip else "G2" if pointsCoincide(p1, p3): # A full circle offset = edge.Curve.Center - pt else: pd = Part.Circle(xy(p1), xy(p2), xy(p3)).Center - PathLog.debug("**** %s.%d: (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) -> center=(%.2f, %.2f)" % (cmd, flip, p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z, pd.x, pd.y)) + PathLog.debug( + "**** %s.%d: (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) -> center=(%.2f, %.2f)" + % ( + cmd, + flip, + p1.x, + p1.y, + p1.z, + p2.x, + p2.y, + p2.z, + p3.x, + p3.y, + p3.z, + pd.x, + pd.y, + ) + ) # Have to calculate the center in the XY plane, using pd leads to an error if this is a helix pa = xy(p1) @@ -272,15 +345,21 @@ def cmdsForEdge(edge, flip = False, useHelixForBSpline = True, segm = 50, hSpeed pc = xy(p3) offset = Part.Circle(pa, pb, pc).Center - pa - PathLog.debug("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z)) - PathLog.debug("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z)) + PathLog.debug( + "**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" + % (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z) + ) + PathLog.debug( + "**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" + % (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z) + ) PathLog.debug("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z)) - params.update({'I': offset.x, 'J': offset.y, 'K': (p3.z - p1.z)/2}) + params.update({"I": offset.x, "J": offset.y, "K": (p3.z - p1.z) / 2}) # G2/G3 commands are always performed at hSpeed if hSpeed > 0: - params.update({'F': hSpeed}) - commands = [ Path.Command(cmd, params) ] + params.update({"F": hSpeed}) + commands = [Path.Command(cmd, params)] else: # We're dealing with a helix or a more complex shape and it has to get approximated @@ -291,18 +370,19 @@ def cmdsForEdge(edge, flip = False, useHelixForBSpline = True, segm = 50, hSpeed commands = [] if points: - p0 = points[0] + p0 = points[0] for p in points[1:]: - params = {'X': p.x, 'Y': p.y, 'Z': p.z} + params = {"X": p.x, "Y": p.y, "Z": p.z} if hSpeed > 0 and vSpeed > 0: - params['F'] = speedBetweenPoints(p0, p, hSpeed, vSpeed) - cmd = Path.Command('G1', params) + params["F"] = speedBetweenPoints(p0, p, hSpeed, vSpeed) + cmd = Path.Command("G1", params) # print("***** {}".format(cmd)) commands.append(cmd) p0 = p - #print commands + # print commands return commands + def edgeForCmd(cmd, startPoint): """edgeForCmd(cmd, startPoint). Returns an Edge representing the given command, assuming a given startPoint.""" @@ -317,29 +397,50 @@ def edgeForCmd(cmd, startPoint): return Part.Edge(Part.LineSegment(startPoint, endPoint)) if cmd.Name in CmdMoveArc: - center = startPoint + commandEndPoint(cmd, Vector(0,0,0), 'I', 'J', 'K') + center = startPoint + commandEndPoint(cmd, Vector(0, 0, 0), "I", "J", "K") A = xy(startPoint - center) B = xy(endPoint - center) d = -B.x * A.y + B.y * A.x if isRoughly(d, 0, 0.005): - PathLog.debug("Half circle arc at: (%.2f, %.2f, %.2f)" % (center.x, center.y, center.z)) + PathLog.debug( + "Half circle arc at: (%.2f, %.2f, %.2f)" + % (center.x, center.y, center.z) + ) # we're dealing with half a circle here - angle = getAngle(A) + math.pi/2 + angle = getAngle(A) + math.pi / 2 if cmd.Name in CmdMoveCW: angle -= math.pi else: C = A + B angle = getAngle(C) - PathLog.debug("Arc (%8f) at: (%.2f, %.2f, %.2f) -> angle=%f" % (d, center.x, center.y, center.z, angle / math.pi)) + PathLog.debug( + "Arc (%8f) at: (%.2f, %.2f, %.2f) -> angle=%f" + % (d, center.x, center.y, center.z, angle / math.pi) + ) R = A.Length - PathLog.debug("arc: p1=(%.2f, %.2f) p2=(%.2f, %.2f) -> center=(%.2f, %.2f)" % (startPoint.x, startPoint.y, endPoint.x, endPoint.y, center.x, center.y)) - PathLog.debug("arc: A=(%.2f, %.2f) B=(%.2f, %.2f) -> d=%.2f" % (A.x, A.y, B.x, B.y, d)) - PathLog.debug("arc: R=%.2f angle=%.2f" % (R, angle/math.pi)) + PathLog.debug( + "arc: p1=(%.2f, %.2f) p2=(%.2f, %.2f) -> center=(%.2f, %.2f)" + % (startPoint.x, startPoint.y, endPoint.x, endPoint.y, center.x, center.y) + ) + PathLog.debug( + "arc: A=(%.2f, %.2f) B=(%.2f, %.2f) -> d=%.2f" % (A.x, A.y, B.x, B.y, d) + ) + PathLog.debug("arc: R=%.2f angle=%.2f" % (R, angle / math.pi)) if isRoughly(startPoint.z, endPoint.z): midPoint = center + Vector(math.cos(angle), math.sin(angle), 0) * R - PathLog.debug("arc: (%.2f, %.2f) -> (%.2f, %.2f) -> (%.2f, %.2f)" % (startPoint.x, startPoint.y, midPoint.x, midPoint.y, endPoint.x, endPoint.y)) + PathLog.debug( + "arc: (%.2f, %.2f) -> (%.2f, %.2f) -> (%.2f, %.2f)" + % ( + startPoint.x, + startPoint.y, + midPoint.x, + midPoint.y, + endPoint.x, + endPoint.y, + ) + ) PathLog.debug("StartPoint:{}".format(startPoint)) PathLog.debug("MidPoint:{}".format(midPoint)) PathLog.debug("EndPoint:{}".format(endPoint)) @@ -350,25 +451,26 @@ def edgeForCmd(cmd, startPoint): return Part.Edge(Part.Arc(startPoint, midPoint, endPoint)) # It's a Helix - #print('angle: A=%.2f B=%.2f' % (getAngle(A)/math.pi, getAngle(B)/math.pi)) + # print('angle: A=%.2f B=%.2f' % (getAngle(A)/math.pi, getAngle(B)/math.pi)) if cmd.Name in CmdMoveCW: cw = True else: cw = False - angle = diffAngle(getAngle(A), getAngle(B), 'CW' if cw else 'CCW') + angle = diffAngle(getAngle(A), getAngle(B), "CW" if cw else "CCW") height = endPoint.z - startPoint.z pitch = height * math.fabs(2 * math.pi / angle) if angle > 0: cw = not cw - #print("Helix: R=%.2f h=%.2f angle=%.2f pitch=%.2f" % (R, height, angle/math.pi, pitch)) + # print("Helix: R=%.2f h=%.2f angle=%.2f pitch=%.2f" % (R, height, angle/math.pi, pitch)) helix = Part.makeHelix(pitch, height, R, 0, not cw) - helix.rotate(Vector(), Vector(0,0,1), 180 * getAngle(A) / math.pi) + helix.rotate(Vector(), Vector(0, 0, 1), 180 * getAngle(A) / math.pi) e = helix.Edges[0] helix.translate(startPoint - e.valueAt(e.FirstParameter)) return helix.Edges[0] return None -def wireForPath(path, startPoint = Vector(0, 0, 0)): + +def wireForPath(path, startPoint=Vector(0, 0, 0)): """wireForPath(path, [startPoint=Vector(0,0,0)]) Returns a wire representing all move commands found in the given path.""" edges = [] @@ -385,7 +487,8 @@ def wireForPath(path, startPoint = Vector(0, 0, 0)): return (None, rapid) return (Part.Wire(edges), rapid) -def wiresForPath(path, startPoint = Vector(0, 0, 0)): + +def wiresForPath(path, startPoint=Vector(0, 0, 0)): """wiresForPath(path, [startPoint=Vector(0,0,0)]) Returns a collection of wires, each representing a continuous cutting Path in path.""" wires = [] @@ -404,36 +507,37 @@ def wiresForPath(path, startPoint = Vector(0, 0, 0)): wires.append(Part.Wire(edges)) return wires + def arcToHelix(edge, z0, z1): """arcToHelix(edge, z0, z1) Assuming edge is an arc it'll return a helix matching the arc starting at z0 and rising/falling to z1.""" - p1 = edge.valueAt(edge.FirstParameter) # p2 = edge.valueAt(edge.LastParameter) cmd = cmdsForEdge(edge)[0] params = cmd.Parameters - params.update({'Z': z1, 'K': (z1 - z0)/2}) + params.update({"Z": z1, "K": (z1 - z0) / 2}) command = Path.Command(cmd.Name, params) - #print("- (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): %.2f:%.2f" % (edge.Vertexes[0].X, edge.Vertexes[0].Y, edge.Vertexes[0].Z, edge.Vertexes[1].X, edge.Vertexes[1].Y, edge.Vertexes[1].Z, z0, z1)) - #print("- %s -> %s" % (cmd, command)) + # print("- (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): %.2f:%.2f" % (edge.Vertexes[0].X, edge.Vertexes[0].Y, edge.Vertexes[0].Z, edge.Vertexes[1].X, edge.Vertexes[1].Y, edge.Vertexes[1].Z, z0, z1)) + # print("- %s -> %s" % (cmd, command)) return edgeForCmd(command, Vector(p1.x, p1.y, z0)) -def helixToArc(edge, z = 0): +def helixToArc(edge, z=0): """helixToArc(edge, z=0) Returns the projection of the helix onto the XY-plane with a given offset.""" p1 = edge.valueAt(edge.FirstParameter) - p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) + p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter) / 2) p3 = edge.valueAt(edge.LastParameter) p01 = Vector(p1.x, p1.y, z) p02 = Vector(p2.x, p2.y, z) p03 = Vector(p3.x, p3.y, z) return Part.Edge(Part.Arc(p01, p02, p03)) + def splitArcAt(edge, pt): """splitArcAt(edge, pt) Returns a list of 2 edges which together form the original arc split at the given point. @@ -443,6 +547,7 @@ def splitArcAt(edge, pt): e1 = Part.Arc(edge.Curve.copy(), p, edge.LastParameter).toShape() return [e0, e1] + def splitEdgeAt(edge, pt): """splitEdgeAt(edge, pt) Returns a list of 2 edges, forming the original edge split at the given point. @@ -456,7 +561,10 @@ def splitEdgeAt(edge, pt): if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: # it's a line - return [Part.Edge(Part.LineSegment(p1, p2)), Part.Edge(Part.LineSegment(p2, p3))] + return [ + Part.Edge(Part.LineSegment(p1, p2)), + Part.Edge(Part.LineSegment(p2, p3)), + ] elif type(edge.Curve) == Part.Circle: # it's an arc return splitArcAt(edge, pt) @@ -466,6 +574,7 @@ def splitEdgeAt(edge, pt): aes = splitArcAt(arc, Vector(pt.x, pt.y, 0)) return [arcToHelix(aes[0], p1.z, p2.z), arcToHelix(aes[1], p2.z, p3.z)] + def combineConnectedShapes(shapes): done = False while not done: @@ -474,7 +583,13 @@ def combineConnectedShapes(shapes): PathLog.debug("shapes: {}".format(shapes)) for shape in shapes: connected = [f for f in combined if isRoughly(shape.distToShape(f)[0], 0.0)] - PathLog.debug(" {}: connected: {} dist: {}".format(len(combined), connected, [shape.distToShape(f)[0] for f in combined])) + PathLog.debug( + " {}: connected: {} dist: {}".format( + len(combined), + connected, + [shape.distToShape(f)[0] for f in combined], + ) + ) if connected: combined = [f for f in combined if f not in connected] connected.append(shape) @@ -485,6 +600,7 @@ def combineConnectedShapes(shapes): shapes = combined return shapes + def removeDuplicateEdges(wire): unique = [] for e in wire.Edges: @@ -492,22 +608,36 @@ def removeDuplicateEdges(wire): unique.append(e) return Part.Wire(unique) + OddsAndEnds = [] + def flipEdge(edge): - '''flipEdge(edge) + """flipEdge(edge) Flips given edge around so the new Vertexes[0] was the old Vertexes[-1] and vice versa, without changing the shape. - Currently only lines, line segments, circles and arcs are supported.''' + Currently only lines, line segments, circles and arcs are supported.""" if Part.Line == type(edge.Curve) and not edge.Vertexes: - return Part.Edge(Part.Line(edge.valueAt(edge.LastParameter), edge.valueAt(edge.FirstParameter))) + return Part.Edge( + Part.Line( + edge.valueAt(edge.LastParameter), edge.valueAt(edge.FirstParameter) + ) + ) elif Part.Line == type(edge.Curve) or Part.LineSegment == type(edge.Curve): - return Part.Edge(Part.LineSegment(edge.Vertexes[-1].Point, edge.Vertexes[0].Point)) + return Part.Edge( + Part.LineSegment(edge.Vertexes[-1].Point, edge.Vertexes[0].Point) + ) elif Part.Circle == type(edge.Curve): # Create an inverted circle circle = Part.Circle(edge.Curve.Center, -edge.Curve.Axis, edge.Curve.Radius) # Rotate the circle appropriately so it starts at edge.valueAt(edge.LastParameter) - circle.rotate(FreeCAD.Placement(circle.Center, circle.Axis, 180 - math.degrees(edge.LastParameter + edge.Curve.AngleXU))) + circle.rotate( + FreeCAD.Placement( + circle.Center, + circle.Axis, + 180 - math.degrees(edge.LastParameter + edge.Curve.AngleXU), + ) + ) # Now the edge always starts at 0 and LastParameter is the value range arc = Part.Edge(circle, 0, edge.LastParameter - edge.FirstParameter) return arc @@ -527,7 +657,7 @@ def flipEdge(edge): ma = max(knots) mi = min(knots) - knots = [ma+mi-k for k in knots] + knots = [ma + mi - k for k in knots] mults.reverse() weights.reverse() @@ -535,29 +665,36 @@ def flipEdge(edge): knots.reverse() flipped = Part.BSplineCurve() - flipped.buildFromPolesMultsKnots(poles, mults , knots, perio, degree, weights, ratio) + flipped.buildFromPolesMultsKnots( + poles, mults, knots, perio, degree, weights, ratio + ) return Part.Edge(flipped) elif type(edge.Curve) == Part.OffsetCurve: return edge.reversed() - global OddsAndEnds # pylint: disable=global-statement + global OddsAndEnds # pylint: disable=global-statement OddsAndEnds.append(edge) - PathLog.warning(translate('PathGeom', "%s not supported for flipping") % type(edge.Curve)) + PathLog.warning( + translate("PathGeom", "%s not supported for flipping") % type(edge.Curve) + ) + Wire = [] + def flipWire(wire): - '''Flip the entire wire and all its edges so it is being processed the other way around.''' + """Flip the entire wire and all its edges so it is being processed the other way around.""" Wire.append(wire) edges = [flipEdge(e) for e in wire.Edges] edges.reverse() PathLog.debug(edges) return Part.Wire(edges) + def makeBoundBoxFace(bBox, offset=0.0, zHeight=0.0): - '''makeBoundBoxFace(bBox, offset=0.0, zHeight=0.0)... - Function to create boundbox face, with possible extra offset and custom Z-height.''' + """makeBoundBoxFace(bBox, offset=0.0, zHeight=0.0)... + Function to create boundbox face, with possible extra offset and custom Z-height.""" p1 = FreeCAD.Vector(bBox.XMin - offset, bBox.YMin - offset, zHeight) p2 = FreeCAD.Vector(bBox.XMax + offset, bBox.YMin - offset, zHeight) p3 = FreeCAD.Vector(bBox.XMax + offset, bBox.YMax + offset, zHeight) @@ -570,9 +707,10 @@ def makeBoundBoxFace(bBox, offset=0.0, zHeight=0.0): return Part.Face(Part.Wire([L1, L2, L3, L4])) + # Method to combine faces if connected def combineHorizontalFaces(faces): - '''combineHorizontalFaces(faces)... + """combineHorizontalFaces(faces)... This function successfully identifies and combines multiple connected faces and works on multiple independent faces with multiple connected faces within the list. The return value is a list of simplified faces. @@ -580,7 +718,7 @@ def combineHorizontalFaces(faces): Attempts to do the same shape connecting failed with TechDraw.findShapeOutline() and PathGeom.combineConnectedShapes(), so this algorithm was created. - ''' + """ horizontal = list() offset = 10.0 topFace = None @@ -594,8 +732,10 @@ def combineHorizontalFaces(faces): # Make offset compound boundbox solid and cut incoming face extrusions from it allFaces = Part.makeCompound(faces) if hasattr(allFaces, "Area") and isRoughly(allFaces.Area, 0.0): - msg = translate('PathGeom', - 'Zero working area to process. Check your selection and settings.') + msg = translate( + "PathGeom", + "Zero working area to process. Check your selection and settings.", + ) PathLog.info(msg) return horizontal @@ -621,10 +761,12 @@ def combineHorizontalFaces(faces): for f in cut.Faces: fbb = f.BoundBox if isRoughly(fbb.ZMin, 5.0) and isRoughly(fbb.ZMax, 5.0): - if (isRoughly(afbb.XMin - offset, fbb.XMin) and - isRoughly(afbb.XMax + offset, fbb.XMax) and - isRoughly(afbb.YMin - offset, fbb.YMin) and - isRoughly(afbb.YMax + offset, fbb.YMax)): + if ( + isRoughly(afbb.XMin - offset, fbb.XMin) + and isRoughly(afbb.XMax + offset, fbb.XMax) + and isRoughly(afbb.YMin - offset, fbb.YMin) + and isRoughly(afbb.YMax + offset, fbb.YMax) + ): topFace = f else: innerFaces.append(f) diff --git a/src/Mod/Path/PathScripts/PathHop.py b/src/Mod/Path/PathScripts/PathHop.py index d2d2a7750a..67631ead08 100644 --- a/src/Mod/Path/PathScripts/PathHop.py +++ b/src/Mod/Path/PathScripts/PathHop.py @@ -31,11 +31,23 @@ __doc__ = """Path Hop object and FreeCAD command""" def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) -class ObjectHop: +class ObjectHop: def __init__(self, obj): - obj.addProperty("App::PropertyLink", "NextObject", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The object to be reached by this hop")) - obj.addProperty("App::PropertyDistance", "HopHeight", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The Z height of the hop")) + obj.addProperty( + "App::PropertyLink", + "NextObject", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "The object to be reached by this hop" + ), + ) + obj.addProperty( + "App::PropertyDistance", + "HopHeight", + "Path", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The Z height of the hop"), + ) obj.Proxy = self def __getstate__(self): @@ -69,7 +81,6 @@ class ObjectHop: class ViewProviderPathHop: - def __init__(self, vobj): self.Object = vobj.Object vobj.Proxy = self @@ -88,11 +99,14 @@ class ViewProviderPathHop: class CommandPathHop: - def GetResources(self): - return {'Pixmap': 'Path_Hop', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Hop", "Hop"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Hop", "Creates a Path Hop object")} + return { + "Pixmap": "Path_Hop", + "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_Hop", "Hop"), + "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "Path_Hop", "Creates a Path Hop object" + ), + } def IsActive(self): if FreeCAD.ActiveDocument is not None: @@ -107,31 +121,33 @@ class CommandPathHop: selection = FreeCADGui.Selection.getSelection() if len(selection) != 1: FreeCAD.Console.PrintError( - translate("Path_Hop", "Please select one path object")+"\n") + translate("Path_Hop", "Please select one path object") + "\n" + ) return if not selection[0].isDerivedFrom("Path::Feature"): FreeCAD.Console.PrintError( - translate("Path_Hop", "The selected object is not a path")+"\n") + translate("Path_Hop", "The selected object is not a path") + "\n" + ) return - FreeCAD.ActiveDocument.openTransaction( - translate("Path_Hop", "Create Hop")) + FreeCAD.ActiveDocument.openTransaction(translate("Path_Hop", "Create Hop")) FreeCADGui.addModule("PathScripts.PathHop") FreeCADGui.addModule("PathScripts.PathUtils") FreeCADGui.doCommand( - 'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Hop")') - FreeCADGui.doCommand('PathScripts.PathHop.ObjectHop(obj)') + 'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Hop")' + ) + FreeCADGui.doCommand("PathScripts.PathHop.ObjectHop(obj)") + FreeCADGui.doCommand("PathScripts.PathHop.ViewProviderPathHop(obj.ViewObject)") FreeCADGui.doCommand( - 'PathScripts.PathHop.ViewProviderPathHop(obj.ViewObject)') - FreeCADGui.doCommand( - 'obj.NextObject = FreeCAD.ActiveDocument.' + selection[0].Name) - FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') + "obj.NextObject = FreeCAD.ActiveDocument." + selection[0].Name + ) + FreeCADGui.doCommand("PathScripts.PathUtils.addToJob(obj)") FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_Hop', CommandPathHop()) + FreeCADGui.addCommand("Path_Hop", CommandPathHop()) FreeCAD.Console.PrintLog("Loading PathHop... done\n") diff --git a/src/Mod/Path/PathScripts/PathIconViewProvider.py b/src/Mod/Path/PathScripts/PathIconViewProvider.py index 59c00ff760..0bf4c8321d 100644 --- a/src/Mod/Path/PathScripts/PathIconViewProvider.py +++ b/src/Mod/Path/PathScripts/PathIconViewProvider.py @@ -31,10 +31,11 @@ __url__ = "https://www.freecadweb.org" __doc__ = "ViewProvider who's main and only task is to assign an icon." PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) + class ViewProvider(object): - '''Generic view provider to assign an icon.''' + """Generic view provider to assign an icon.""" def __init__(self, vobj, icon): self.icon = icon @@ -50,17 +51,17 @@ class ViewProvider(object): self.obj = vobj.Object def __getstate__(self): - attrs = {'icon': self.icon } - if hasattr(self, 'editModule'): - attrs['editModule'] = self.editModule - attrs['editCallback'] = self.editCallback + attrs = {"icon": self.icon} + if hasattr(self, "editModule"): + attrs["editModule"] = self.editModule + attrs["editCallback"] = self.editCallback return attrs def __setstate__(self, state): - self.icon = state['icon'] - if state.get('editModule', None): - self.editModule = state['editModule'] - self.editCallback = state['editCallback'] + self.icon = state["icon"] + if state.get("editModule", None): + self.editModule = state["editModule"] + self.editCallback = state["editCallback"] def getIcon(self): return ":/icons/Path_{}.svg".format(self.icon) @@ -70,7 +71,7 @@ class ViewProvider(object): self.editCallback = callback.__name__ def _onEditCallback(self, edit): - if hasattr(self, 'editModule'): + if hasattr(self, "editModule"): mod = importlib.import_module(self.editModule) callback = getattr(mod, self.editCallback) callback(self.obj, self.vobj, edit) @@ -89,30 +90,33 @@ class ViewProvider(object): # pylint: disable=unused-argument PathLog.track() from PySide import QtCore, QtGui - edit = QtCore.QCoreApplication.translate('Path', 'Edit', None) + + edit = QtCore.QCoreApplication.translate("Path", "Edit", None) action = QtGui.QAction(edit, menu) action.triggered.connect(self.setEdit) menu.addAction(action) + _factory = {} + def Attach(vobj, name): - '''Attach(vobj, name) ... attach the appropriate view provider to the view object. - If no view provider was registered for the given name a default IconViewProvider is created.''' + """Attach(vobj, name) ... attach the appropriate view provider to the view object. + If no view provider was registered for the given name a default IconViewProvider is created.""" PathLog.track(vobj.Object.Label, name) - global _factory # pylint: disable=global-statement - for key,value in PathUtil.keyValueIter(_factory): + global _factory # pylint: disable=global-statement + for key, value in PathUtil.keyValueIter(_factory): if key == name: return value(vobj, name) - PathLog.track(vobj.Object.Label, name, 'PathIconViewProvider') + PathLog.track(vobj.Object.Label, name, "PathIconViewProvider") return ViewProvider(vobj, name) + def RegisterViewProvider(name, provider): - '''RegisterViewProvider(name, provider) ... if an IconViewProvider is created for an object with the given name - an instance of provider is used instead.''' + """RegisterViewProvider(name, provider) ... if an IconViewProvider is created for an object with the given name + an instance of provider is used instead.""" PathLog.track(name) - global _factory # pylint: disable=global-statement + global _factory # pylint: disable=global-statement _factory[name] = provider - diff --git a/src/Mod/Path/PathScripts/PathPlane.py b/src/Mod/Path/PathScripts/PathPlane.py index 1f2bddd210..365554c9a3 100644 --- a/src/Mod/Path/PathScripts/PathPlane.py +++ b/src/Mod/Path/PathScripts/PathPlane.py @@ -20,7 +20,7 @@ # * * # *************************************************************************** -''' Used for CNC machine plane selection G17,G18,G19 ''' +""" Used for CNC machine plane selection G17,G18,G19 """ import FreeCAD import FreeCADGui @@ -33,16 +33,28 @@ def translate(context, text, disambig=None): class Plane: - def __init__(self,obj): - obj.addProperty("App::PropertyEnumeration", "SelectionPlane","Path",QtCore.QT_TRANSLATE_NOOP("App::Property","Orientation plane of CNC path")) - obj.SelectionPlane=['XY', 'XZ', 'YZ'] - obj.addProperty("App::PropertyBool","Active","Path",QtCore.QT_TRANSLATE_NOOP("App::Property","Make False, to prevent operation from generating code")) + def __init__(self, obj): + obj.addProperty( + "App::PropertyEnumeration", + "SelectionPlane", + "Path", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Orientation plane of CNC path"), + ) + obj.SelectionPlane = ["XY", "XZ", "YZ"] + obj.addProperty( + "App::PropertyBool", + "Active", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "Make False, to prevent operation from generating code" + ), + ) obj.Proxy = self def execute(self, obj): - clonelist = ['XY', 'XZ', 'YZ'] + clonelist = ["XY", "XZ", "YZ"] cindx = clonelist.index(str(obj.SelectionPlane)) - pathlist = ['G17', 'G18', 'G19'] + pathlist = ["G17", "G18", "G19"] labelindx = clonelist.index(obj.SelectionPlane) + 1 obj.Label = "Plane" + str(labelindx) if obj.Active: @@ -54,19 +66,18 @@ class Plane: class _ViewProviderPlane: - def __init__(self, vobj): # mandatory vobj.Proxy = self mode = 2 - vobj.setEditorMode('LineWidth', mode) - vobj.setEditorMode('MarkerColor', mode) - vobj.setEditorMode('NormalColor', mode) - vobj.setEditorMode('DisplayMode', mode) - vobj.setEditorMode('BoundingBox', mode) - vobj.setEditorMode('Selectable', mode) - vobj.setEditorMode('ShapeColor', mode) - vobj.setEditorMode('Transparency', mode) - vobj.setEditorMode('Visibility', mode) + vobj.setEditorMode("LineWidth", mode) + vobj.setEditorMode("MarkerColor", mode) + vobj.setEditorMode("NormalColor", mode) + vobj.setEditorMode("DisplayMode", mode) + vobj.setEditorMode("BoundingBox", mode) + vobj.setEditorMode("Selectable", mode) + vobj.setEditorMode("ShapeColor", mode) + vobj.setEditorMode("Transparency", mode) + vobj.setEditorMode("Visibility", mode) def __getstate__(self): # mandatory return None @@ -80,15 +91,15 @@ class _ViewProviderPlane: def onChanged(self, vobj, prop): # optional # pylint: disable=unused-argument mode = 2 - vobj.setEditorMode('LineWidth', mode) - vobj.setEditorMode('MarkerColor', mode) - vobj.setEditorMode('NormalColor', mode) - vobj.setEditorMode('DisplayMode', mode) - vobj.setEditorMode('BoundingBox', mode) - vobj.setEditorMode('Selectable', mode) - vobj.setEditorMode('ShapeColor', mode) - vobj.setEditorMode('Transparency', mode) - vobj.setEditorMode('Visibility', mode) + vobj.setEditorMode("LineWidth", mode) + vobj.setEditorMode("MarkerColor", mode) + vobj.setEditorMode("NormalColor", mode) + vobj.setEditorMode("DisplayMode", mode) + vobj.setEditorMode("BoundingBox", mode) + vobj.setEditorMode("Selectable", mode) + vobj.setEditorMode("ShapeColor", mode) + vobj.setEditorMode("Transparency", mode) + vobj.setEditorMode("Visibility", mode) def updateData(self, vobj, prop): # optional # this is executed when a property of the APP OBJECT changes @@ -104,11 +115,14 @@ class _ViewProviderPlane: class CommandPathPlane: - def GetResources(self): - return {'Pixmap': 'Path_Plane', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Plane", "Selection Plane"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Plane", "Create a Selection Plane object")} + return { + "Pixmap": "Path_Plane", + "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_Plane", "Selection Plane"), + "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "Path_Plane", "Create a Selection Plane object" + ), + } def IsActive(self): if FreeCAD.ActiveDocument is not None: @@ -119,9 +133,10 @@ class CommandPathPlane: def Activated(self): FreeCAD.ActiveDocument.openTransaction( - translate("Path_Plane", "Create a Selection Plane object")) + translate("Path_Plane", "Create a Selection Plane object") + ) FreeCADGui.addModule("PathScripts.PathPlane") - snippet = ''' + snippet = """ import Path import PathScripts from PathScripts import PathUtils @@ -132,15 +147,16 @@ obj.Active = True PathScripts.PathPlane._ViewProviderPlane(obj.ViewObject) PathUtils.addToJob(obj) -''' +""" FreeCADGui.doCommand(snippet) FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() + if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_Plane', CommandPathPlane()) + FreeCADGui.addCommand("Path_Plane", CommandPathPlane()) FreeCAD.Console.PrintLog("Loading PathPlane... done\n") diff --git a/src/Mod/Path/PathScripts/PathPropertyEditor.py b/src/Mod/Path/PathScripts/PathPropertyEditor.py index f376e351fd..4798b4a40f 100644 --- a/src/Mod/Path/PathScripts/PathPropertyEditor.py +++ b/src/Mod/Path/PathScripts/PathPropertyEditor.py @@ -35,31 +35,33 @@ __doc__ = "Task panel editor for Properties" def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) class _PropertyEditor(object): - '''Base class of all property editors - just outlines the TableView delegate interface.''' + """Base class of all property editors - just outlines the TableView delegate interface.""" + def __init__(self, obj, prop): - self.obj = obj + self.obj = obj self.prop = prop def widget(self, parent): - '''widget(parent) ... called by the delegate to get a new editor widget. - Must be implemented by subclasses and return the widget.''' - pass # pylint: disable=unnecessary-pass + """widget(parent) ... called by the delegate to get a new editor widget. + Must be implemented by subclasses and return the widget.""" + pass # pylint: disable=unnecessary-pass def setEditorData(self, widget): - '''setEditorData(widget) ... called by the delegate to initialize the editor. + """setEditorData(widget) ... called by the delegate to initialize the editor. The widget is the object returned by widget(). - Must be implemented by subclasses.''' - pass # pylint: disable=unnecessary-pass + Must be implemented by subclasses.""" + pass # pylint: disable=unnecessary-pass def setModelData(self, widget): - '''setModelData(widget) ... called by the delegate to store new values. - Must be implemented by subclasses.''' - pass # pylint: disable=unnecessary-pass + """setModelData(widget) ... called by the delegate to store new values. + Must be implemented by subclasses.""" + pass # pylint: disable=unnecessary-pass def propertyValue(self): return self.obj.getPropertyByName(self.prop) @@ -70,8 +72,9 @@ class _PropertyEditor(object): def displayString(self): return self.propertyValue() + class _PropertyEditorBool(_PropertyEditor): - '''Editor for boolean values - uses a combo box.''' + """Editor for boolean values - uses a combo box.""" def widget(self, parent): return QtGui.QComboBox(parent) @@ -85,21 +88,22 @@ class _PropertyEditorBool(_PropertyEditor): def setModelData(self, widget): self.setProperty(widget.currentText() == str(True)) + class _PropertyEditorString(_PropertyEditor): - '''Editor for string values - uses a line edit.''' + """Editor for string values - uses a line edit.""" def widget(self, parent): return QtGui.QLineEdit(parent) def setEditorData(self, widget): - text = '' if self.propertyValue() is None else self.propertyValue() + text = "" if self.propertyValue() is None else self.propertyValue() widget.setText(text) def setModelData(self, widget): self.setProperty(widget.text()) -class _PropertyEditorQuantity(_PropertyEditor): +class _PropertyEditorQuantity(_PropertyEditor): def widget(self, parent): return QtGui.QLineEdit(parent) @@ -117,23 +121,26 @@ class _PropertyEditorQuantity(_PropertyEditor): def displayString(self): if self.propertyValue() is None: - return '' + return "" return self.propertyValue().getUserPreferred()[0] + class _PropertyEditorAngle(_PropertyEditorQuantity): - '''Editor for angle values - uses a line edit''' + """Editor for angle values - uses a line edit""" def defaultQuantity(self): return FreeCAD.Units.Quantity(0, FreeCAD.Units.Angle) + class _PropertyEditorLength(_PropertyEditorQuantity): - '''Editor for length values - uses a line edit.''' + """Editor for length values - uses a line edit.""" def defaultQuantity(self): return FreeCAD.Units.Quantity(0, FreeCAD.Units.Length) + class _PropertyEditorPercent(_PropertyEditor): - '''Editor for percent values - uses a spin box.''' + """Editor for percent values - uses a spin box.""" def widget(self, parent): return QtGui.QSpinBox(parent) @@ -148,8 +155,9 @@ class _PropertyEditorPercent(_PropertyEditor): def setModelData(self, widget): self.setProperty(widget.value()) + class _PropertyEditorInteger(_PropertyEditor): - '''Editor for integer values - uses a spin box.''' + """Editor for integer values - uses a spin box.""" def widget(self, parent): return QtGui.QSpinBox(parent) @@ -163,8 +171,9 @@ class _PropertyEditorInteger(_PropertyEditor): def setModelData(self, widget): self.setProperty(widget.value()) + class _PropertyEditorFloat(_PropertyEditor): - '''Editor for float values - uses a double spin box.''' + """Editor for float values - uses a double spin box.""" def widget(self, parent): return QtGui.QDoubleSpinBox(parent) @@ -178,20 +187,20 @@ class _PropertyEditorFloat(_PropertyEditor): def setModelData(self, widget): self.setProperty(widget.value()) -class _PropertyEditorFile(_PropertyEditor): +class _PropertyEditorFile(_PropertyEditor): def widget(self, parent): return QtGui.QLineEdit(parent) def setEditorData(self, widget): - text = '' if self.propertyValue() is None else self.propertyValue() + text = "" if self.propertyValue() is None else self.propertyValue() widget.setText(text) def setModelData(self, widget): self.setProperty(widget.text()) -class _PropertyEditorEnumeration(_PropertyEditor): +class _PropertyEditorEnumeration(_PropertyEditor): def widget(self, parent): return QtGui.QComboBox(parent) @@ -203,25 +212,28 @@ class _PropertyEditorEnumeration(_PropertyEditor): def setModelData(self, widget): self.setProperty(widget.currentText()) + _EditorFactory = { - 'App::PropertyAngle' : _PropertyEditorAngle, - 'App::PropertyBool' : _PropertyEditorBool, - 'App::PropertyDistance' : _PropertyEditorLength, - 'App::PropertyEnumeration' : _PropertyEditorEnumeration, - #'App::PropertyFile' : _PropertyEditorFile, - 'App::PropertyFloat' : _PropertyEditorFloat, - 'App::PropertyInteger' : _PropertyEditorInteger, - 'App::PropertyLength' : _PropertyEditorLength, - 'App::PropertyPercent' : _PropertyEditorPercent, - 'App::PropertyString' : _PropertyEditorString, - } + "App::PropertyAngle": _PropertyEditorAngle, + "App::PropertyBool": _PropertyEditorBool, + "App::PropertyDistance": _PropertyEditorLength, + "App::PropertyEnumeration": _PropertyEditorEnumeration, + #'App::PropertyFile' : _PropertyEditorFile, + "App::PropertyFloat": _PropertyEditorFloat, + "App::PropertyInteger": _PropertyEditorInteger, + "App::PropertyLength": _PropertyEditorLength, + "App::PropertyPercent": _PropertyEditorPercent, + "App::PropertyString": _PropertyEditorString, +} + def Types(): - '''Return the types of properties supported.''' + """Return the types of properties supported.""" return [t for t in _EditorFactory] + def Editor(obj, prop): - '''Returns an editor class to be used for the given property.''' + """Returns an editor class to be used for the given property.""" factory = _EditorFactory[obj.getTypeIdOfProperty(prop)] if factory: return factory(obj, prop) diff --git a/src/Mod/Path/PathScripts/PathStock.py b/src/Mod/Path/PathScripts/PathStock.py index 17d66d42d7..aa7a9ef910 100644 --- a/src/Mod/Path/PathScripts/PathStock.py +++ b/src/Mod/Path/PathScripts/PathStock.py @@ -20,7 +20,7 @@ # * * # *************************************************************************** -'''Used to create material stock around a machined part - for visualization''' +"""Used to create material stock around a machined part - for visualization""" import FreeCAD import PathScripts.PathLog as PathLog @@ -30,41 +30,44 @@ from PySide import QtCore # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -Part = LazyLoader('Part', globals(), 'Part') + +Part = LazyLoader("Part", globals(), "Part") PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) + class StockType: # pylint: disable=no-init - NoStock = 'None' - FromBase = 'FromBase' - CreateBox = 'CreateBox' - CreateCylinder = 'CreateCylinder' - Unknown = 'Unknown' + NoStock = "None" + FromBase = "FromBase" + CreateBox = "CreateBox" + CreateCylinder = "CreateCylinder" + Unknown = "Unknown" @classmethod def FromStock(cls, stock): - '''FromStock(stock) ... Answer a string representing the type of stock.''' + """FromStock(stock) ... Answer a string representing the type of stock.""" if not stock: return cls.NoStock - if hasattr(stock, 'StockType'): + if hasattr(stock, "StockType"): return stock.StockType # fallback in case somebody messed with internals - if hasattr(stock, 'ExtXneg') and hasattr(stock, 'ExtZpos'): + if hasattr(stock, "ExtXneg") and hasattr(stock, "ExtZpos"): return cls.FromBase - if hasattr(stock, 'Length') and hasattr(stock, 'Width'): + if hasattr(stock, "Length") and hasattr(stock, "Width"): return cls.CreateBox - if hasattr(stock, 'Radius') and hasattr(stock, 'Height'): + if hasattr(stock, "Radius") and hasattr(stock, "Height"): return cls.CreateCylinder return cls.Unknown + def shapeBoundBox(obj): PathLog.track(type(obj)) if list == type(obj) and obj: @@ -73,9 +76,9 @@ def shapeBoundBox(obj): bb.add(shapeBoundBox(o)) return bb - if hasattr(obj, 'Shape'): + if hasattr(obj, "Shape"): return obj.Shape.BoundBox - if obj and 'App::Part' == obj.TypeId: + if obj and "App::Part" == obj.TypeId: bounds = [shapeBoundBox(o) for o in obj.Group] if bounds: bb = bounds[0] @@ -83,39 +86,104 @@ def shapeBoundBox(obj): bb = bb.united(b) return bb if obj: - PathLog.error(translate('PathStock', "Invalid base object %s - no shape found") % obj.Name) + PathLog.error( + translate("PathStock", "Invalid base object %s - no shape found") % obj.Name + ) return None + class Stock(object): def onDocumentRestored(self, obj): - if hasattr(obj, 'StockType'): - obj.setEditorMode('StockType', 2) # hide + if hasattr(obj, "StockType"): + obj.setEditorMode("StockType", 2) # hide + class StockFromBase(Stock): - def __init__(self, obj, base): "Make stock" - obj.addProperty("App::PropertyLink", "Base", "Base", QtCore.QT_TRANSLATE_NOOP("PathStock", "The base object this stock is derived from")) - obj.addProperty("App::PropertyDistance", "ExtXneg", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in negative X direction")) - obj.addProperty("App::PropertyDistance", "ExtXpos", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in positive X direction")) - obj.addProperty("App::PropertyDistance", "ExtYneg", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in negative Y direction")) - obj.addProperty("App::PropertyDistance", "ExtYpos", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in positive Y direction")) - obj.addProperty("App::PropertyDistance", "ExtZneg", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in negative Z direction")) - obj.addProperty("App::PropertyDistance", "ExtZpos", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in positive Z direction")) - obj.addProperty("App::PropertyLink","Material","Component", QtCore.QT_TRANSLATE_NOOP("App::Property","A material for this object")) + obj.addProperty( + "App::PropertyLink", + "Base", + "Base", + QtCore.QT_TRANSLATE_NOOP( + "PathStock", "The base object this stock is derived from" + ), + ) + obj.addProperty( + "App::PropertyDistance", + "ExtXneg", + "Stock", + QtCore.QT_TRANSLATE_NOOP( + "PathStock", + "Extra allowance from part bound box in negative X direction", + ), + ) + obj.addProperty( + "App::PropertyDistance", + "ExtXpos", + "Stock", + QtCore.QT_TRANSLATE_NOOP( + "PathStock", + "Extra allowance from part bound box in positive X direction", + ), + ) + obj.addProperty( + "App::PropertyDistance", + "ExtYneg", + "Stock", + QtCore.QT_TRANSLATE_NOOP( + "PathStock", + "Extra allowance from part bound box in negative Y direction", + ), + ) + obj.addProperty( + "App::PropertyDistance", + "ExtYpos", + "Stock", + QtCore.QT_TRANSLATE_NOOP( + "PathStock", + "Extra allowance from part bound box in positive Y direction", + ), + ) + obj.addProperty( + "App::PropertyDistance", + "ExtZneg", + "Stock", + QtCore.QT_TRANSLATE_NOOP( + "PathStock", + "Extra allowance from part bound box in negative Z direction", + ), + ) + obj.addProperty( + "App::PropertyDistance", + "ExtZpos", + "Stock", + QtCore.QT_TRANSLATE_NOOP( + "PathStock", + "Extra allowance from part bound box in positive Z direction", + ), + ) + obj.addProperty( + "App::PropertyLink", + "Material", + "Component", + QtCore.QT_TRANSLATE_NOOP("App::Property", "A material for this object"), + ) obj.Base = base - obj.ExtXneg= 1.0 - obj.ExtXpos= 1.0 - obj.ExtYneg= 1.0 - obj.ExtYpos= 1.0 - obj.ExtZneg= 1.0 - obj.ExtZpos= 1.0 + obj.ExtXneg = 1.0 + obj.ExtXpos = 1.0 + obj.ExtYneg = 1.0 + obj.ExtYpos = 1.0 + obj.ExtZneg = 1.0 + obj.ExtZpos = 1.0 # placement is only tracked on creation bb = shapeBoundBox(base.Group) if base else None if bb: - obj.Placement = FreeCAD.Placement(FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), FreeCAD.Rotation()) + obj.Placement = FreeCAD.Placement( + FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), FreeCAD.Rotation() + ) else: PathLog.track(obj.Label, base.Label) obj.Proxy = self @@ -123,25 +191,32 @@ class StockFromBase(Stock): # debugging aids self.origin = None self.length = None - self.width = None + self.width = None self.height = None def __getstate__(self): return None + def __setstate__(self, state): return None def execute(self, obj): - bb = shapeBoundBox(obj.Base.Group) if obj.Base and hasattr(obj.Base, 'Group') else None + bb = ( + shapeBoundBox(obj.Base.Group) + if obj.Base and hasattr(obj.Base, "Group") + else None + ) PathLog.track(obj.Label, bb) # Sometimes, when the Base changes it's temporarily not assigned when # Stock.execute is triggered - it'll be set correctly the next time around. if bb: - self.origin = FreeCAD.Vector(-obj.ExtXneg.Value, -obj.ExtYneg.Value, -obj.ExtZneg.Value) + self.origin = FreeCAD.Vector( + -obj.ExtXneg.Value, -obj.ExtYneg.Value, -obj.ExtZneg.Value + ) self.length = bb.XLength + obj.ExtXneg.Value + obj.ExtXpos.Value - self.width = bb.YLength + obj.ExtYneg.Value + obj.ExtYpos.Value + self.width = bb.YLength + obj.ExtYneg.Value + obj.ExtYpos.Value self.height = bb.ZLength + obj.ExtZneg.Value + obj.ExtZpos.Value shape = Part.makeBox(self.length, self.width, self.height, self.origin) @@ -149,7 +224,10 @@ class StockFromBase(Stock): obj.Shape = shape def onChanged(self, obj, prop): - if prop in ['ExtXneg', 'ExtXpos', 'ExtYneg', 'ExtYpos', 'ExtZneg', 'ExtZpos'] and not 'Restore' in obj.State: + if ( + prop in ["ExtXneg", "ExtXpos", "ExtYneg", "ExtYpos", "ExtZneg", "ExtZpos"] + and not "Restore" in obj.State + ): self.execute(obj) @@ -157,18 +235,34 @@ class StockCreateBox(Stock): MinExtent = 0.001 def __init__(self, obj): - obj.addProperty('App::PropertyLength', 'Length', 'Stock', QtCore.QT_TRANSLATE_NOOP("PathStock", "Length of this stock box")) - obj.addProperty('App::PropertyLength', 'Width', 'Stock', QtCore.QT_TRANSLATE_NOOP("PathStock", "Width of this stock box")) - obj.addProperty('App::PropertyLength', 'Height', 'Stock', QtCore.QT_TRANSLATE_NOOP("PathStock", "Height of this stock box")) + obj.addProperty( + "App::PropertyLength", + "Length", + "Stock", + QtCore.QT_TRANSLATE_NOOP("PathStock", "Length of this stock box"), + ) + obj.addProperty( + "App::PropertyLength", + "Width", + "Stock", + QtCore.QT_TRANSLATE_NOOP("PathStock", "Width of this stock box"), + ) + obj.addProperty( + "App::PropertyLength", + "Height", + "Stock", + QtCore.QT_TRANSLATE_NOOP("PathStock", "Height of this stock box"), + ) obj.Length = 10 - obj.Width = 10 + obj.Width = 10 obj.Height = 10 obj.Proxy = self def __getstate__(self): return None + def __setstate__(self, state): return None @@ -185,15 +279,26 @@ class StockCreateBox(Stock): obj.Shape = shape def onChanged(self, obj, prop): - if prop in ['Length', 'Width', 'Height'] and not 'Restore' in obj.State: + if prop in ["Length", "Width", "Height"] and not "Restore" in obj.State: self.execute(obj) + class StockCreateCylinder(Stock): MinExtent = 0.001 def __init__(self, obj): - obj.addProperty('App::PropertyLength', 'Radius', 'Stock', QtCore.QT_TRANSLATE_NOOP("PathStock", "Radius of this stock cylinder")) - obj.addProperty('App::PropertyLength', 'Height', 'Stock', QtCore.QT_TRANSLATE_NOOP("PathStock", "Height of this stock cylinder")) + obj.addProperty( + "App::PropertyLength", + "Radius", + "Stock", + QtCore.QT_TRANSLATE_NOOP("PathStock", "Radius of this stock cylinder"), + ) + obj.addProperty( + "App::PropertyLength", + "Height", + "Stock", + QtCore.QT_TRANSLATE_NOOP("PathStock", "Height of this stock cylinder"), + ) obj.Radius = 2 obj.Height = 10 @@ -202,6 +307,7 @@ class StockCreateCylinder(Stock): def __getstate__(self): return None + def __setstate__(self, state): return None @@ -216,38 +322,51 @@ class StockCreateCylinder(Stock): obj.Shape = shape def onChanged(self, obj, prop): - if prop in ['Radius', 'Height'] and not 'Restore' in obj.State: + if prop in ["Radius", "Height"] and not "Restore" in obj.State: self.execute(obj) + def SetupStockObject(obj, stockType): PathLog.track(obj.Label, stockType) if FreeCAD.GuiUp and obj.ViewObject: - obj.addProperty('App::PropertyString', 'StockType', 'Stock', QtCore.QT_TRANSLATE_NOOP("PathStock", "Internal representation of stock type")) + obj.addProperty( + "App::PropertyString", + "StockType", + "Stock", + QtCore.QT_TRANSLATE_NOOP( + "PathStock", "Internal representation of stock type" + ), + ) obj.StockType = stockType - obj.setEditorMode('StockType', 2) # hide + obj.setEditorMode("StockType", 2) # hide import PathScripts.PathIconViewProvider - PathScripts.PathIconViewProvider.ViewProvider(obj.ViewObject, 'Stock') + + PathScripts.PathIconViewProvider.ViewProvider(obj.ViewObject, "Stock") obj.ViewObject.Transparency = 90 - obj.ViewObject.DisplayMode = 'Wireframe' + obj.ViewObject.DisplayMode = "Wireframe" + class FakeJob(object): def __init__(self, base): self.Group = [base] + def _getBase(job): - if job and hasattr(job, 'Model'): + if job and hasattr(job, "Model"): return job.Model if job: import PathScripts.PathUtils as PathUtils + job = PathUtils.findParentJob(job) return job.Model if job else None return None + def CreateFromBase(job, neg=None, pos=None, placement=None): PathLog.track(job.Label, neg, pos, placement) base = _getBase(job) - obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', 'Stock') + obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Stock") obj.Proxy = StockFromBase(obj, base) if neg: @@ -268,19 +387,20 @@ def CreateFromBase(job, neg=None, pos=None, placement=None): obj.purgeTouched() return obj + def CreateBox(job, extent=None, placement=None): base = _getBase(job) - obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', 'Stock') + obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Stock") obj.Proxy = StockCreateBox(obj) if extent: obj.Length = extent.x - obj.Width = extent.y + obj.Width = extent.y obj.Height = extent.z elif base: bb = shapeBoundBox(base.Group) obj.Length = max(bb.XLength, 1) - obj.Width = max(bb.YLength, 1) + obj.Width = max(bb.YLength, 1) obj.Height = max(bb.ZLength, 1) if placement: @@ -293,9 +413,10 @@ def CreateBox(job, extent=None, placement=None): SetupStockObject(obj, StockType.CreateBox) return obj + def CreateCylinder(job, radius=None, height=None, placement=None): base = _getBase(job) - obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', 'Stock') + obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Stock") obj.Proxy = StockCreateCylinder(obj) if radius: @@ -312,112 +433,191 @@ def CreateCylinder(job, radius=None, height=None, placement=None): obj.Placement = placement elif base: bb = shapeBoundBox(base.Group) - origin = FreeCAD.Vector((bb.XMin + bb.XMax)/2, (bb.YMin + bb.YMax)/2, bb.ZMin) + origin = FreeCAD.Vector( + (bb.XMin + bb.XMax) / 2, (bb.YMin + bb.YMax) / 2, bb.ZMin + ) obj.Placement = FreeCAD.Placement(origin, FreeCAD.Vector(), 0) SetupStockObject(obj, StockType.CreateCylinder) return obj + def TemplateAttributes(stock, includeExtent=True, includePlacement=True): attrs = {} if stock: - attrs['version'] = 1 + attrs["version"] = 1 stockType = StockType.FromStock(stock) - attrs['create'] = stockType + attrs["create"] = stockType if includeExtent: if stockType == StockType.FromBase: - attrs['xneg'] = ("%s" % stock.ExtXneg) - attrs['xpos'] = ("%s" % stock.ExtXpos) - attrs['yneg'] = ("%s" % stock.ExtYneg) - attrs['ypos'] = ("%s" % stock.ExtYpos) - attrs['zneg'] = ("%s" % stock.ExtZneg) - attrs['zpos'] = ("%s" % stock.ExtZpos) + attrs["xneg"] = "%s" % stock.ExtXneg + attrs["xpos"] = "%s" % stock.ExtXpos + attrs["yneg"] = "%s" % stock.ExtYneg + attrs["ypos"] = "%s" % stock.ExtYpos + attrs["zneg"] = "%s" % stock.ExtZneg + attrs["zpos"] = "%s" % stock.ExtZpos if stockType == StockType.CreateBox: - attrs['length'] = ("%s" % stock.Length) - attrs['width'] = ("%s" % stock.Width) - attrs['height'] = ("%s" % stock.Height) + attrs["length"] = "%s" % stock.Length + attrs["width"] = "%s" % stock.Width + attrs["height"] = "%s" % stock.Height if stockType == StockType.CreateCylinder: - attrs['radius'] = ("%s" % stock.Radius) - attrs['height'] = ("%s" % stock.Height) + attrs["radius"] = "%s" % stock.Radius + attrs["height"] = "%s" % stock.Height if includePlacement: pos = stock.Placement.Base - attrs['posX'] = pos.x - attrs['posY'] = pos.y - attrs['posZ'] = pos.z + attrs["posX"] = pos.x + attrs["posY"] = pos.y + attrs["posZ"] = pos.z rot = stock.Placement.Rotation - attrs['rotX'] = rot.Q[0] - attrs['rotY'] = rot.Q[1] - attrs['rotZ'] = rot.Q[2] - attrs['rotW'] = rot.Q[3] + attrs["rotX"] = rot.Q[0] + attrs["rotY"] = rot.Q[1] + attrs["rotZ"] = rot.Q[2] + attrs["rotW"] = rot.Q[3] return attrs + def CreateFromTemplate(job, template): - if template.get('version') and 1 == int(template['version']): - stockType = template.get('create') + if template.get("version") and 1 == int(template["version"]): + stockType = template.get("create") if stockType: placement = None - posX = template.get('posX') - posY = template.get('posY') - posZ = template.get('posZ') - rotX = template.get('rotX') - rotY = template.get('rotY') - rotZ = template.get('rotZ') - rotW = template.get('rotW') - if posX is not None and posY is not None and posZ is not None and rotX is not None and rotY is not None and rotZ is not None and rotW is not None: + posX = template.get("posX") + posY = template.get("posY") + posZ = template.get("posZ") + rotX = template.get("rotX") + rotY = template.get("rotY") + rotZ = template.get("rotZ") + rotW = template.get("rotW") + if ( + posX is not None + and posY is not None + and posZ is not None + and rotX is not None + and rotY is not None + and rotZ is not None + and rotW is not None + ): pos = FreeCAD.Vector(float(posX), float(posY), float(posZ)) - rot = FreeCAD.Rotation(float(rotX), float(rotY), float(rotZ), float(rotW)) + rot = FreeCAD.Rotation( + float(rotX), float(rotY), float(rotZ), float(rotW) + ) placement = FreeCAD.Placement(pos, rot) - elif posX is not None or posY is not None or posZ is not None or rotX is not None or rotY is not None or rotZ is not None or rotW is not None: - PathLog.warning(translate('PathStock', 'Corrupted or incomplete placement information in template - ignoring')) + elif ( + posX is not None + or posY is not None + or posZ is not None + or rotX is not None + or rotY is not None + or rotZ is not None + or rotW is not None + ): + PathLog.warning( + translate( + "PathStock", + "Corrupted or incomplete placement information in template - ignoring", + ) + ) if stockType == StockType.FromBase: - xneg = template.get('xneg') - xpos = template.get('xpos') - yneg = template.get('yneg') - ypos = template.get('ypos') - zneg = template.get('zneg') - zpos = template.get('zpos') + xneg = template.get("xneg") + xpos = template.get("xpos") + yneg = template.get("yneg") + ypos = template.get("ypos") + zneg = template.get("zneg") + zpos = template.get("zpos") neg = None pos = None - if xneg is not None and xpos is not None and yneg is not None and ypos is not None and zneg is not None and zpos is not None: - neg = FreeCAD.Vector(FreeCAD.Units.Quantity(xneg).Value, FreeCAD.Units.Quantity(yneg).Value, FreeCAD.Units.Quantity(zneg).Value) - pos = FreeCAD.Vector(FreeCAD.Units.Quantity(xpos).Value, FreeCAD.Units.Quantity(ypos).Value, FreeCAD.Units.Quantity(zpos).Value) - elif xneg is not None or xpos is not None or yneg is not None or ypos is not None or zneg is not None or zpos is not None: - PathLog.error(translate('PathStock', 'Corrupted or incomplete specification for creating stock from base - ignoring extent')) + if ( + xneg is not None + and xpos is not None + and yneg is not None + and ypos is not None + and zneg is not None + and zpos is not None + ): + neg = FreeCAD.Vector( + FreeCAD.Units.Quantity(xneg).Value, + FreeCAD.Units.Quantity(yneg).Value, + FreeCAD.Units.Quantity(zneg).Value, + ) + pos = FreeCAD.Vector( + FreeCAD.Units.Quantity(xpos).Value, + FreeCAD.Units.Quantity(ypos).Value, + FreeCAD.Units.Quantity(zpos).Value, + ) + elif ( + xneg is not None + or xpos is not None + or yneg is not None + or ypos is not None + or zneg is not None + or zpos is not None + ): + PathLog.error( + translate( + "PathStock", + "Corrupted or incomplete specification for creating stock from base - ignoring extent", + ) + ) return CreateFromBase(job, neg, pos, placement) if stockType == StockType.CreateBox: - PathLog.track(' create box') - length = template.get('length') - width = template.get('width') - height = template.get('height') + PathLog.track(" create box") + length = template.get("length") + width = template.get("width") + height = template.get("height") extent = None if length is not None and width is not None and height is not None: - PathLog.track(' have extent') - extent = FreeCAD.Vector(FreeCAD.Units.Quantity(length).Value, FreeCAD.Units.Quantity(width).Value, FreeCAD.Units.Quantity(height).Value) + PathLog.track(" have extent") + extent = FreeCAD.Vector( + FreeCAD.Units.Quantity(length).Value, + FreeCAD.Units.Quantity(width).Value, + FreeCAD.Units.Quantity(height).Value, + ) elif length is not None or width is not None or height is not None: - PathLog.error(translate('PathStock', 'Corrupted or incomplete size for creating a stock box - ignoring size')) + PathLog.error( + translate( + "PathStock", + "Corrupted or incomplete size for creating a stock box - ignoring size", + ) + ) else: - PathLog.track(" take placement (%s) and extent (%s) from model" % (placement, extent)) + PathLog.track( + " take placement (%s) and extent (%s) from model" + % (placement, extent) + ) return CreateBox(job, extent, placement) if stockType == StockType.CreateCylinder: - radius = template.get('radius') - height = template.get('height') + radius = template.get("radius") + height = template.get("height") if radius is not None and height is not None: pass elif radius is not None or height is not None: radius = None height = None - PathLog.error(translate('PathStock', 'Corrupted or incomplete size for creating a stock cylinder - ignoring size')) + PathLog.error( + translate( + "PathStock", + "Corrupted or incomplete size for creating a stock cylinder - ignoring size", + ) + ) return CreateCylinder(job, radius, height, placement) - PathLog.error(translate('PathStock', 'Unsupported stock type named {}').format(stockType)) + PathLog.error( + translate("PathStock", "Unsupported stock type named {}").format( + stockType + ) + ) else: - PathLog.error(translate('PathStock', 'Unsupported PathStock template version {}').format(template.get('version'))) + PathLog.error( + translate( + "PathStock", "Unsupported PathStock template version {}" + ).format(template.get("version")) + ) return None diff --git a/src/Mod/Path/PathScripts/PathStop.py b/src/Mod/Path/PathScripts/PathStop.py index fdfe80572e..7cdc08cfc6 100644 --- a/src/Mod/Path/PathScripts/PathStop.py +++ b/src/Mod/Path/PathScripts/PathStop.py @@ -20,7 +20,7 @@ # * * # *************************************************************************** -'''Used for CNC machine Stops for Path module. Create an Optional or Mandatory Stop.''' +"""Used for CNC machine Stops for Path module. Create an Optional or Mandatory Stop.""" import FreeCAD import FreeCADGui @@ -31,13 +31,21 @@ from PySide import QtCore def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) + class Stop: - def __init__(self,obj): - obj.addProperty("App::PropertyEnumeration", "Stop", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Add Optional or Mandatory Stop to the program")) - obj.Stop=['Optional', 'Mandatory'] + def __init__(self, obj): + obj.addProperty( + "App::PropertyEnumeration", + "Stop", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "Add Optional or Mandatory Stop to the program" + ), + ) + obj.Stop = ["Optional", "Mandatory"] obj.Proxy = self mode = 2 - obj.setEditorMode('Placement', mode) + obj.setEditorMode("Placement", mode) def __getstate__(self): return None @@ -49,31 +57,30 @@ class Stop: pass def execute(self, obj): - if obj.Stop == 'Optional': - word = 'M1' + if obj.Stop == "Optional": + word = "M1" else: - word = 'M0' + word = "M0" output = "" - output = word + '\n' + output = word + "\n" path = Path.Path(output) obj.Path = path class _ViewProviderStop: - def __init__(self, vobj): # mandatory vobj.Proxy = self mode = 2 - vobj.setEditorMode('LineWidth', mode) - vobj.setEditorMode('MarkerColor', mode) - vobj.setEditorMode('NormalColor', mode) - vobj.setEditorMode('DisplayMode', mode) - vobj.setEditorMode('BoundingBox', mode) - vobj.setEditorMode('Selectable', mode) - vobj.setEditorMode('ShapeColor', mode) - vobj.setEditorMode('Transparency', mode) - vobj.setEditorMode('Visibility', mode) + vobj.setEditorMode("LineWidth", mode) + vobj.setEditorMode("MarkerColor", mode) + vobj.setEditorMode("NormalColor", mode) + vobj.setEditorMode("DisplayMode", mode) + vobj.setEditorMode("BoundingBox", mode) + vobj.setEditorMode("Selectable", mode) + vobj.setEditorMode("ShapeColor", mode) + vobj.setEditorMode("Transparency", mode) + vobj.setEditorMode("Visibility", mode) def __getstate__(self): # mandatory return None @@ -87,23 +94,26 @@ class _ViewProviderStop: def onChanged(self, vobj, prop): # optional # pylint: disable=unused-argument mode = 2 - vobj.setEditorMode('LineWidth', mode) - vobj.setEditorMode('MarkerColor', mode) - vobj.setEditorMode('NormalColor', mode) - vobj.setEditorMode('DisplayMode', mode) - vobj.setEditorMode('BoundingBox', mode) - vobj.setEditorMode('Selectable', mode) - vobj.setEditorMode('ShapeColor', mode) - vobj.setEditorMode('Transparency', mode) - vobj.setEditorMode('Visibility', mode) + vobj.setEditorMode("LineWidth", mode) + vobj.setEditorMode("MarkerColor", mode) + vobj.setEditorMode("NormalColor", mode) + vobj.setEditorMode("DisplayMode", mode) + vobj.setEditorMode("BoundingBox", mode) + vobj.setEditorMode("Selectable", mode) + vobj.setEditorMode("ShapeColor", mode) + vobj.setEditorMode("Transparency", mode) + vobj.setEditorMode("Visibility", mode) class CommandPathStop: - def GetResources(self): - return {'Pixmap': 'Path_Stop', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Stop", "Stop"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Stop", "Add Optional or Mandatory Stop to the program")} + return { + "Pixmap": "Path_Stop", + "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_Stop", "Stop"), + "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "Path_Stop", "Add Optional or Mandatory Stop to the program" + ), + } def IsActive(self): if FreeCAD.ActiveDocument is not None: @@ -114,9 +124,10 @@ class CommandPathStop: def Activated(self): FreeCAD.ActiveDocument.openTransaction( - translate("Path_Stop", "Add Optional or Mandatory Stop to the program")) + translate("Path_Stop", "Add Optional or Mandatory Stop to the program") + ) FreeCADGui.addModule("PathScripts.PathStop") - snippet = ''' + snippet = """ import Path import PathScripts from PathScripts import PathUtils @@ -126,14 +137,15 @@ PathScripts.PathStop.Stop(obj) PathScripts.PathStop._ViewProviderStop(obj.ViewObject) PathUtils.addToJob(obj) -''' +""" FreeCADGui.doCommand(snippet) FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() + if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_Stop', CommandPathStop()) + FreeCADGui.addCommand("Path_Stop", CommandPathStop()) FreeCAD.Console.PrintLog("Loading PathStop... done\n") diff --git a/src/Mod/Path/PathScripts/PathUtil.py b/src/Mod/Path/PathScripts/PathUtil.py index db43b466e5..02776b3ab1 100644 --- a/src/Mod/Path/PathScripts/PathUtil.py +++ b/src/Mod/Path/PathScripts/PathUtil.py @@ -20,7 +20,7 @@ # * * # *************************************************************************** -''' +""" The purpose of this file is to collect some handy functions. The reason they are not in PathUtils (and there is this confusing naming going on) is that PathUtils depends on PathJob. Which makes it impossible to use the functions @@ -28,7 +28,7 @@ and classes defined there in PathJob. So if you add to this file and think about importing anything from PathScripts other than PathLog, then it probably doesn't belong here. -''' +""" import six import PathScripts.PathLog as PathLog @@ -36,117 +36,138 @@ import PySide PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + def translate(context, text, disambig=None): return PySide.QtCore.QCoreApplication.translate(context, text, disambig) + def _getProperty(obj, prop): o = obj attr = obj name = None - for name in prop.split('.'): + for name in prop.split("."): o = attr if not hasattr(o, name): break attr = getattr(o, name) if o == attr: - PathLog.warning(translate('PathGui', "%s has no property %s (%s))") % (obj.Label, prop, name)) + PathLog.warning( + translate("PathGui", "%s has no property %s (%s))") + % (obj.Label, prop, name) + ) return (None, None, None) - #PathLog.debug("found property %s of %s (%s: %s)" % (prop, obj.Label, name, attr)) - return(o, attr, name) + # PathLog.debug("found property %s of %s (%s: %s)" % (prop, obj.Label, name, attr)) + return (o, attr, name) + def getProperty(obj, prop): - '''getProperty(obj, prop) ... answer obj's property defined by its canonical name.''' - o, attr, name = _getProperty(obj, prop) # pylint: disable=unused-variable + """getProperty(obj, prop) ... answer obj's property defined by its canonical name.""" + o, attr, name = _getProperty(obj, prop) # pylint: disable=unused-variable return attr + def getPropertyValueString(obj, prop): - '''getPropertyValueString(obj, prop) ... answer a string representation of an object's property's value.''' + """getPropertyValueString(obj, prop) ... answer a string representation of an object's property's value.""" attr = getProperty(obj, prop) - if hasattr(attr, 'UserString'): + if hasattr(attr, "UserString"): return attr.UserString return str(attr) + def setProperty(obj, prop, value): - '''setProperty(obj, prop, value) ... set the property value of obj's property defined by its canonical name.''' - o, attr, name = _getProperty(obj, prop) # pylint: disable=unused-variable + """setProperty(obj, prop, value) ... set the property value of obj's property defined by its canonical name.""" + o, attr, name = _getProperty(obj, prop) # pylint: disable=unused-variable if not attr is None and type(value) == str: if type(attr) == int: value = int(value, 0) elif type(attr) == bool: - value = value.lower() in ['true', '1', 'yes', 'ok'] + value = value.lower() in ["true", "1", "yes", "ok"] if o and name: setattr(o, name, value) + # NotValidBaseTypeIds = ['Sketcher::SketchObject'] NotValidBaseTypeIds = [] def isValidBaseObject(obj): - '''isValidBaseObject(obj) ... returns true if the object can be used as a base for a job.''' - if hasattr(obj, 'getParentGeoFeatureGroup') and obj.getParentGeoFeatureGroup(): + """isValidBaseObject(obj) ... returns true if the object can be used as a base for a job.""" + if hasattr(obj, "getParentGeoFeatureGroup") and obj.getParentGeoFeatureGroup(): # Can't link to anything inside a geo feature group anymore PathLog.debug("%s is inside a geo feature group" % obj.Label) return False - if hasattr(obj, 'BitBody') and hasattr(obj, 'BitShape'): + if hasattr(obj, "BitBody") and hasattr(obj, "BitShape"): # ToolBit's are not valid base objects return False if obj.TypeId in NotValidBaseTypeIds: PathLog.debug("%s is blacklisted (%s)" % (obj.Label, obj.TypeId)) return False - if hasattr(obj, 'Sheets') or hasattr(obj, 'TagText'): # Arch.Panels and Arch.PanelCut + if hasattr(obj, "Sheets") or hasattr( + obj, "TagText" + ): # Arch.Panels and Arch.PanelCut PathLog.debug("%s is not an Arch.Panel" % (obj.Label)) return False import Part + return not Part.getShape(obj).isNull() + def isSolid(obj): - '''isSolid(obj) ... return True if the object is a valid solid.''' + """isSolid(obj) ... return True if the object is a valid solid.""" import Part + shape = Part.getShape(obj) return not shape.isNull() and shape.Volume and shape.isClosed() + def opProperty(op, prop): - '''opProperty(op, prop) ... return the value of property prop of the underlying operation (or None if prop does not exist)''' + """opProperty(op, prop) ... return the value of property prop of the underlying operation (or None if prop does not exist)""" if hasattr(op, prop): return getattr(op, prop) - if hasattr(op, 'Base'): + if hasattr(op, "Base"): return opProperty(op.Base, prop) return None + def toolControllerForOp(op): - '''toolControllerForOp(op) ... return the tool controller used by the op. + """toolControllerForOp(op) ... return the tool controller used by the op. If the op doesn't have its own tool controller but has a Base object, return its tool controller. - Otherwise return None.''' - return opProperty(op, 'ToolController') + Otherwise return None.""" + return opProperty(op, "ToolController") + def getPublicObject(obj): - '''getPublicObject(obj) ... returns the object which should be used to reference a feature of the given object.''' - if hasattr(obj, 'getParentGeoFeatureGroup'): + """getPublicObject(obj) ... returns the object which should be used to reference a feature of the given object.""" + if hasattr(obj, "getParentGeoFeatureGroup"): body = obj.getParentGeoFeatureGroup() if body: return getPublicObject(body) return obj -def clearExpressionEngine(obj): - '''clearExpressionEngine(obj) ... removes all expressions from obj. -There is currently a bug that invalidates the DAG if an object -is deleted that still has one or more expressions attached to it. -Use this function to remove all expressions before deletion.''' - if hasattr(obj, 'ExpressionEngine'): - for attr, expr in obj.ExpressionEngine: # pylint: disable=unused-variable +def clearExpressionEngine(obj): + """clearExpressionEngine(obj) ... removes all expressions from obj. + + There is currently a bug that invalidates the DAG if an object + is deleted that still has one or more expressions attached to it. + Use this function to remove all expressions before deletion.""" + if hasattr(obj, "ExpressionEngine"): + for attr, expr in obj.ExpressionEngine: # pylint: disable=unused-variable obj.setExpression(attr, None) + def toUnicode(string): - '''toUnicode(string) ... returns a unicode version of string regardless of the python version.''' + """toUnicode(string) ... returns a unicode version of string regardless of the python version.""" return six.text_type(string) + def isString(string): - '''isString(string) ... return True if string is a string, regardless of string type and python version.''' + """isString(string) ... return True if string is a string, regardless of string type and python version.""" return isinstance(string, six.string_types) + def keyValueIter(dictionary): - '''keyValueIter(dict) ... return iterable object over dictionary's (key,value) tuples.''' + """keyValueIter(dict) ... return iterable object over dictionary's (key,value) tuples.""" return six.iteritems(dictionary) diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index 8e5c1e0b42..a97679bae7 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -19,9 +19,10 @@ # * USA * # * * # *************************************************************************** -'''PathUtils -common functions used in PathScripts for filtering, sorting, and generating gcode toolpath data ''' +"""PathUtils -common functions used in PathScripts for filtering, sorting, and generating gcode toolpath data """ import FreeCAD import Path + # import PathScripts import PathScripts.PathJob as PathJob import PathScripts.PathGeom as PathGeom @@ -35,9 +36,10 @@ from PySide import QtGui # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -DraftGeomUtils = LazyLoader('DraftGeomUtils', globals(), 'DraftGeomUtils') -Part = LazyLoader('Part', globals(), 'Part') -TechDraw = LazyLoader('TechDraw', globals(), 'TechDraw') + +DraftGeomUtils = LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils") +Part = LazyLoader("Part", globals(), "Part") +TechDraw = LazyLoader("TechDraw", globals(), "TechDraw") PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # PathLog.trackModule(PathLog.thisModule()) @@ -65,6 +67,7 @@ def waiting_effects(function): finally: QtGui.QApplication.restoreOverrideCursor() return res + return new_function @@ -78,7 +81,9 @@ def isDrillable(obj, candidate, tooldiameter=None, includePartials=False): candidate = Face or Edge tooldiameter=float """ - PathLog.track('obj: {} candidate: {} tooldiameter {}'.format(obj, candidate, tooldiameter)) + PathLog.track( + "obj: {} candidate: {} tooldiameter {}".format(obj, candidate, tooldiameter) + ) if list == type(obj): for shape in obj: if isDrillable(shape, candidate, tooldiameter, includePartials): @@ -87,28 +92,47 @@ def isDrillable(obj, candidate, tooldiameter=None, includePartials=False): drillable = False try: - if candidate.ShapeType == 'Face': + if candidate.ShapeType == "Face": face = candidate # eliminate flat faces - if (round(face.ParameterRange[0], 8) == 0.0) and (round(face.ParameterRange[1], 8) == round(math.pi * 2, 8)): - for edge in face.Edges: # Find seam edge and check if aligned to Z axis. - if (isinstance(edge.Curve, Part.Line)): + if (round(face.ParameterRange[0], 8) == 0.0) and ( + round(face.ParameterRange[1], 8) == round(math.pi * 2, 8) + ): + for ( + edge + ) in face.Edges: # Find seam edge and check if aligned to Z axis. + if isinstance(edge.Curve, Part.Line): PathLog.debug("candidate is a circle") v0 = edge.Vertexes[0].Point v1 = edge.Vertexes[1].Point # check if the cylinder seam is vertically aligned. Eliminate tilted holes - if (numpy.isclose(v1.sub(v0).x, 0, rtol=1e-05, atol=1e-06)) and \ - (numpy.isclose(v1.sub(v0).y, 0, rtol=1e-05, atol=1e-06)): + if ( + numpy.isclose(v1.sub(v0).x, 0, rtol=1e-05, atol=1e-06) + ) and (numpy.isclose(v1.sub(v0).y, 0, rtol=1e-05, atol=1e-06)): drillable = True # vector of top center - lsp = Vector(face.BoundBox.Center.x, face.BoundBox.Center.y, face.BoundBox.ZMax) + lsp = Vector( + face.BoundBox.Center.x, + face.BoundBox.Center.y, + face.BoundBox.ZMax, + ) # vector of bottom center - lep = Vector(face.BoundBox.Center.x, face.BoundBox.Center.y, face.BoundBox.ZMin) + lep = Vector( + face.BoundBox.Center.x, + face.BoundBox.Center.y, + face.BoundBox.ZMin, + ) # check if the cylindrical 'lids' are inside the base # object. This eliminates extruded circles but allows # actual holes. - if obj.isInside(lsp, 1e-6, False) or obj.isInside(lep, 1e-6, False): - PathLog.track("inside check failed. lsp: {} lep: {}".format(lsp, lep)) + if obj.isInside(lsp, 1e-6, False) or obj.isInside( + lep, 1e-6, False + ): + PathLog.track( + "inside check failed. lsp: {} lep: {}".format( + lsp, lep + ) + ) drillable = False # eliminate elliptical holes elif not hasattr(face.Surface, "Radius"): @@ -119,7 +143,9 @@ def isDrillable(obj, candidate, tooldiameter=None, includePartials=False): drillable = face.Surface.Radius >= tooldiameter / 2 else: drillable = True - elif type(face.Surface) == Part.Plane and PathGeom.pointsCoincide(face.Surface.Axis, FreeCAD.Vector(0, 0, 1)): + elif type(face.Surface) == Part.Plane and PathGeom.pointsCoincide( + face.Surface.Axis, FreeCAD.Vector(0, 0, 1) + ): if len(face.Edges) == 1 and type(face.Edges[0].Curve) == Part.Circle: center = face.Edges[0].Curve.Center if obj.isInside(center, 1e-6, False): @@ -129,7 +155,9 @@ def isDrillable(obj, candidate, tooldiameter=None, includePartials=False): drillable = True else: for edge in candidate.Edges: - if isinstance(edge.Curve, Part.Circle) and (includePartials or edge.isClosed()): + if isinstance(edge.Curve, Part.Circle) and ( + includePartials or edge.isClosed() + ): PathLog.debug("candidate is a circle or ellipse") if not hasattr(edge.Curve, "Radius"): PathLog.debug("No radius. Ellipse.") @@ -142,33 +170,38 @@ def isDrillable(obj, candidate, tooldiameter=None, includePartials=False): FreeCAD.Console.PrintMessage( "Found a drillable hole with diameter: {}: " "too small for the current tool with " - "diameter: {}".format(edge.Curve.Radius * 2, tooldiameter)) + "diameter: {}".format( + edge.Curve.Radius * 2, tooldiameter + ) + ) else: drillable = True PathLog.debug("candidate is drillable: {}".format(drillable)) except Exception as ex: # pylint: disable=broad-except - PathLog.warning(translate("PathUtils", "Issue determine drillability: {}").format(ex)) + PathLog.warning( + translate("PathUtils", "Issue determine drillability: {}").format(ex) + ) return drillable # set at 4 decimal places for testing def fmt(val): - return format(val, '.4f') + return format(val, ".4f") def segments(poly): - ''' A sequence of (x,y) numeric coordinates pairs ''' + """A sequence of (x,y) numeric coordinates pairs""" return zip(poly, poly[1:] + [poly[0]]) def loopdetect(obj, edge1, edge2): - ''' + """ Returns a loop wire that includes the two edges. Useful for detecting boundaries of negative space features ie 'holes' If a unique loop is not found, returns None edge1 = edge edge2 = edge - ''' + """ PathLog.track() candidates = [] @@ -178,7 +211,9 @@ def loopdetect(obj, edge1, edge2): candidates.append((wire.hashCode(), wire)) if e.hashCode() == edge2.hashCode(): candidates.append((wire.hashCode(), wire)) - loop = set([x for x in candidates if candidates.count(x) > 1]) # return the duplicate item + loop = set( + [x for x in candidates if candidates.count(x) > 1] + ) # return the duplicate item if len(loop) != 1: return None loopwire = next(x for x in loop)[1] @@ -186,18 +221,23 @@ def loopdetect(obj, edge1, edge2): def horizontalEdgeLoop(obj, edge): - '''horizontalEdgeLoop(obj, edge) ... returns a wire in the horizontal plane, if that is the only horizontal wire the given edge is a part of.''' + """horizontalEdgeLoop(obj, edge) ... returns a wire in the horizontal plane, if that is the only horizontal wire the given edge is a part of.""" h = edge.hashCode() wires = [w for w in obj.Shape.Wires if any(e.hashCode() == h for e in w.Edges)] - loops = [w for w in wires if all(PathGeom.isHorizontal(e) for e in w.Edges) and PathGeom.isHorizontal(Part.Face(w))] + loops = [ + w + for w in wires + if all(PathGeom.isHorizontal(e) for e in w.Edges) + and PathGeom.isHorizontal(Part.Face(w)) + ] if len(loops) == 1: return loops[0] return None def horizontalFaceLoop(obj, face, faceList=None): - '''horizontalFaceLoop(obj, face, faceList=None) ... returns a list of face names which form the walls of a vertical hole face is a part of. - All face names listed in faceList must be part of the hole for the solution to be returned.''' + """horizontalFaceLoop(obj, face, faceList=None) ... returns a list of face names which form the walls of a vertical hole face is a part of. + All face names listed in faceList must be part of the hole for the solution to be returned.""" wires = [horizontalEdgeLoop(obj, e) for e in face.Edges] # Not sure if sorting by Area is a premature optimization - but it seems @@ -208,7 +248,11 @@ def horizontalFaceLoop(obj, face, faceList=None): hashes = [e.hashCode() for e in wire.Edges] # find all faces that share a an edge with the wire and are vertical - faces = ["Face%d" % (i + 1) for i, f in enumerate(obj.Shape.Faces) if any(e.hashCode() in hashes for e in f.Edges) and PathGeom.isVertical(f)] + faces = [ + "Face%d" % (i + 1) + for i, f in enumerate(obj.Shape.Faces) + if any(e.hashCode() in hashes for e in f.Edges) and PathGeom.isVertical(f) + ] if faceList and not all(f in faces for f in faceList): continue @@ -231,13 +275,19 @@ def horizontalFaceLoop(obj, face, faceList=None): # wire is still closed and it still has the same footprint bb1 = comp.BoundBox bb2 = w.BoundBox - if w.isClosed() and PathGeom.isRoughly(bb1.XMin, bb2.XMin) and PathGeom.isRoughly(bb1.XMax, bb2.XMax) and PathGeom.isRoughly(bb1.YMin, bb2.YMin) and PathGeom.isRoughly(bb1.YMax, bb2.YMax): + if ( + w.isClosed() + and PathGeom.isRoughly(bb1.XMin, bb2.XMin) + and PathGeom.isRoughly(bb1.XMax, bb2.XMax) + and PathGeom.isRoughly(bb1.YMin, bb2.YMin) + and PathGeom.isRoughly(bb1.YMax, bb2.YMax) + ): return faces return None def filterArcs(arcEdge): - '''filterArcs(Edge) -used to split arcs that over 180 degrees. Returns list ''' + """filterArcs(Edge) -used to split arcs that over 180 degrees. Returns list""" PathLog.track() s = arcEdge if isinstance(s.Curve, Part.Circle): @@ -245,7 +295,7 @@ def filterArcs(arcEdge): angle = abs(s.LastParameter - s.FirstParameter) # overhalfcircle = False goodarc = False - if (angle > math.pi): + if angle > math.pi: pass # overhalfcircle = True else: @@ -253,9 +303,14 @@ def filterArcs(arcEdge): if not goodarc: arcstpt = s.valueAt(s.FirstParameter) arcmid = s.valueAt( - (s.LastParameter - s.FirstParameter) * 0.5 + s.FirstParameter) - arcquad1 = s.valueAt((s.LastParameter - s.FirstParameter) * 0.25 + s.FirstParameter) # future midpt for arc1 - arcquad2 = s.valueAt((s.LastParameter - s.FirstParameter) * 0.75 + s.FirstParameter) # future midpt for arc2 + (s.LastParameter - s.FirstParameter) * 0.5 + s.FirstParameter + ) + arcquad1 = s.valueAt( + (s.LastParameter - s.FirstParameter) * 0.25 + s.FirstParameter + ) # future midpt for arc1 + arcquad2 = s.valueAt( + (s.LastParameter - s.FirstParameter) * 0.75 + s.FirstParameter + ) # future midpt for arc2 arcendpt = s.valueAt(s.LastParameter) # reconstruct with 2 arcs arcseg1 = Part.ArcOfCircle(arcstpt, arcquad1, arcmid) @@ -277,28 +332,28 @@ def makeWorkplane(shape): Creates a workplane circle at the ZMin level. """ PathLog.track() - loc = FreeCAD.Vector(shape.BoundBox.Center.x, - shape.BoundBox.Center.y, - shape.BoundBox.ZMin) + loc = FreeCAD.Vector( + shape.BoundBox.Center.x, shape.BoundBox.Center.y, shape.BoundBox.ZMin + ) c = Part.makeCircle(10, loc) return c def getEnvelope(partshape, subshape=None, depthparams=None): - ''' + """ getEnvelope(partshape, stockheight=None) returns a shape corresponding to the partshape silhouette extruded to height. if stockheight is given, the returned shape is extruded to that height otherwise the returned shape is the height of the original shape boundbox partshape = solid object stockheight = float - Absolute Z height of the top of material before cutting. - ''' + """ PathLog.track(partshape, subshape, depthparams) zShift = 0 if subshape is not None: if isinstance(subshape, Part.Face): - PathLog.debug('processing a face') + PathLog.debug("processing a face") sec = Part.makeCompound([subshape]) else: area = Path.Area(Fill=2, Coplanar=0).add(subshape) @@ -306,7 +361,11 @@ def getEnvelope(partshape, subshape=None, depthparams=None): PathLog.debug("About to section with params: {}".format(area.getParams())) sec = area.makeSections(heights=[0.0], project=True)[0].getShape() - PathLog.debug('partshapeZmin: {}, subshapeZMin: {}, zShift: {}'.format(partshape.BoundBox.ZMin, subshape.BoundBox.ZMin, zShift)) + PathLog.debug( + "partshapeZmin: {}, subshapeZMin: {}, zShift: {}".format( + partshape.BoundBox.ZMin, subshape.BoundBox.ZMin, zShift + ) + ) else: area = Path.Area(Fill=2, Coplanar=0).add(partshape) @@ -318,7 +377,11 @@ def getEnvelope(partshape, subshape=None, depthparams=None): if depthparams is not None: eLength = depthparams.safe_height - depthparams.final_depth zShift = depthparams.final_depth - sec.BoundBox.ZMin - PathLog.debug('boundbox zMIN: {} elength: {} zShift {}'.format(partshape.BoundBox.ZMin, eLength, zShift)) + PathLog.debug( + "boundbox zMIN: {} elength: {} zShift {}".format( + partshape.BoundBox.ZMin, eLength, zShift + ) + ) else: eLength = partshape.BoundBox.ZLength - sec.BoundBox.ZMin @@ -335,35 +398,37 @@ def getEnvelope(partshape, subshape=None, depthparams=None): # Function to extract offset face from shape -def getOffsetArea(fcShape, - offset, - removeHoles=False, - # Default: XY plane - plane=Part.makeCircle(10), - tolerance=1e-4): - '''Make an offset area of a shape, projected onto a plane. +def getOffsetArea( + fcShape, + offset, + removeHoles=False, + # Default: XY plane + plane=Part.makeCircle(10), + tolerance=1e-4, +): + """Make an offset area of a shape, projected onto a plane. Positive offsets expand the area, negative offsets shrink it. Inspired by _buildPathArea() from PathAreaOp.py module. Adjustments made based on notes by @sliptonic at this webpage: - https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.''' - PathLog.debug('getOffsetArea()') + https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.""" + PathLog.debug("getOffsetArea()") areaParams = {} - areaParams['Offset'] = offset - areaParams['Fill'] = 1 # 1 - areaParams['Outline'] = removeHoles - areaParams['Coplanar'] = 0 - areaParams['SectionCount'] = 1 # -1 = full(all per depthparams??) sections - areaParams['Reorient'] = True - areaParams['OpenMode'] = 0 - areaParams['MaxArcPoints'] = 400 # 400 - areaParams['Project'] = True - areaParams['FitArcs'] = False # Can be buggy & expensive - areaParams['Deflection'] = tolerance - areaParams['Accuracy'] = tolerance - areaParams['Tolerance'] = 1e-5 # Equal point tolerance - areaParams['Simplify'] = True - areaParams['CleanDistance'] = tolerance / 5 + areaParams["Offset"] = offset + areaParams["Fill"] = 1 # 1 + areaParams["Outline"] = removeHoles + areaParams["Coplanar"] = 0 + areaParams["SectionCount"] = 1 # -1 = full(all per depthparams??) sections + areaParams["Reorient"] = True + areaParams["OpenMode"] = 0 + areaParams["MaxArcPoints"] = 400 # 400 + areaParams["Project"] = True + areaParams["FitArcs"] = False # Can be buggy & expensive + areaParams["Deflection"] = tolerance + areaParams["Accuracy"] = tolerance + areaParams["Tolerance"] = 1e-5 # Equal point tolerance + areaParams["Simplify"] = True + areaParams["CleanDistance"] = tolerance / 5 area = Path.Area() # Create instance of Area() class object # Set working plane normal to Z=1 @@ -380,11 +445,16 @@ def getOffsetArea(fcShape, def reverseEdge(e): if DraftGeomUtils.geomType(e) == "Circle": arcstpt = e.valueAt(e.FirstParameter) - arcmid = e.valueAt((e.LastParameter - e.FirstParameter) * 0.5 + e.FirstParameter) + arcmid = e.valueAt( + (e.LastParameter - e.FirstParameter) * 0.5 + e.FirstParameter + ) arcendpt = e.valueAt(e.LastParameter) arcofCirc = Part.ArcOfCircle(arcendpt, arcmid, arcstpt) newedge = arcofCirc.toShape() - elif DraftGeomUtils.geomType(e) == "LineSegment" or DraftGeomUtils.geomType(e) == "Line": + elif ( + DraftGeomUtils.geomType(e) == "LineSegment" + or DraftGeomUtils.geomType(e) == "Line" + ): stpt = e.valueAt(e.FirstParameter) endpt = e.valueAt(e.LastParameter) newedge = Part.makeLine(endpt, stpt) @@ -393,7 +463,7 @@ def reverseEdge(e): def getToolControllers(obj, proxy=None): - '''returns all the tool controllers''' + """returns all the tool controllers""" if proxy is None: proxy = obj.Proxy try: @@ -408,11 +478,11 @@ def getToolControllers(obj, proxy=None): def findToolController(obj, proxy, name=None): - '''returns a tool controller with a given name. + """returns a tool controller with a given name. If no name is specified, returns the first controller. - if no controller is found, returns None''' + if no controller is found, returns None""" - PathLog.track('name: {}'.format(name)) + PathLog.track("name: {}".format(name)) c = None if UserInput: c = UserInput.selectedToolController() @@ -438,12 +508,16 @@ def findToolController(obj, proxy, name=None): def findParentJob(obj): - '''retrieves a parent job object for an operation or other Path object''' + """retrieves a parent job object for an operation or other Path object""" PathLog.track() for i in obj.InList: - if hasattr(i, 'Proxy') and isinstance(i.Proxy, PathJob.ObjectJob): + if hasattr(i, "Proxy") and isinstance(i.Proxy, PathJob.ObjectJob): return i - if i.TypeId == "Path::FeaturePython" or i.TypeId == "Path::FeatureCompoundPython" or i.TypeId == "App::DocumentObjectGroup": + if ( + i.TypeId == "Path::FeaturePython" + or i.TypeId == "Path::FeatureCompoundPython" + or i.TypeId == "App::DocumentObjectGroup" + ): grandParent = findParentJob(i) if grandParent is not None: return grandParent @@ -451,16 +525,16 @@ def findParentJob(obj): def GetJobs(jobname=None): - '''returns all jobs in the current document. If name is given, returns that job''' + """returns all jobs in the current document. If name is given, returns that job""" if jobname: return [job for job in PathJob.Instances() if job.Name == jobname] return PathJob.Instances() def addToJob(obj, jobname=None): - '''adds a path object to a job + """adds a path object to a job obj = obj - jobname = None''' + jobname = None""" PathLog.track(jobname) job = None @@ -486,14 +560,14 @@ def addToJob(obj, jobname=None): def rapid(x=None, y=None, z=None): - """ Returns gcode string to perform a rapid move.""" + """Returns gcode string to perform a rapid move.""" retstr = "G00" if (x is not None) or (y is not None) or (z is not None): - if (x is not None): + if x is not None: retstr += " X" + str("%.4f" % x) - if (y is not None): + if y is not None: retstr += " Y" + str("%.4f" % y) - if (z is not None): + if z is not None: retstr += " Z" + str("%.4f" % z) else: return "" @@ -501,19 +575,19 @@ def rapid(x=None, y=None, z=None): def feed(x=None, y=None, z=None, horizFeed=0, vertFeed=0): - """ Return gcode string to perform a linear feed.""" + """Return gcode string to perform a linear feed.""" retstr = "G01 F" - if(x is None) and (y is None): + if (x is None) and (y is None): retstr += str("%.4f" % horizFeed) else: retstr += str("%.4f" % vertFeed) if (x is not None) or (y is not None) or (z is not None): - if (x is not None): + if x is not None: retstr += " X" + str("%.4f" % x) - if (y is not None): + if y is not None: retstr += " Y" + str("%.4f" % y) - if (z is not None): + if z is not None: retstr += " Z" + str("%.4f" % z) else: return "" @@ -537,7 +611,10 @@ def arc(cx, cy, sx, sy, ex, ey, horizFeed=0, ez=None, ccw=False): """ eps = 0.01 - if (math.sqrt((cx - sx)**2 + (cy - sy)**2) - math.sqrt((cx - ex)**2 + (cy - ey)**2)) >= eps: + if ( + math.sqrt((cx - sx) ** 2 + (cy - sy) ** 2) + - math.sqrt((cx - ex) ** 2 + (cy - ey) ** 2) + ) >= eps: PathLog.error(translate("Path", "Illegal arc: Start and end radii not equal")) return "" @@ -578,7 +655,7 @@ def helicalPlunge(plungePos, rampangle, destZ, startZ, toold, plungeR, horizFeed helixY = plungePos.y helixCirc = math.pi * toold * plungeR - dzPerRev = math.sin(rampangle / 180. * math.pi) * helixCirc + dzPerRev = math.sin(rampangle / 180.0 * math.pi) * helixCirc # Go to the start of the helix position helixCmds += rapid(helixX, helixY) @@ -589,14 +666,34 @@ def helicalPlunge(plungePos, rampangle, destZ, startZ, toold, plungeR, horizFeed curZ = max(startZ - dzPerRev, destZ) done = False while not done: - done = (curZ == destZ) + done = curZ == destZ # NOTE: FreeCAD doesn't render this, but at least LinuxCNC considers it valid # helixCmds += arc(plungePos.x, plungePos.y, helixX, helixY, helixX, helixY, ez = curZ, ccw=True) # Use two half-helixes; FreeCAD renders that correctly, # and it fits with the other code breaking up 360-degree arcs - helixCmds += arc(plungePos.x, plungePos.y, helixX, helixY, helixX - toold * plungeR, helixY, horizFeed, ez=(curZ + lastZ) / 2., ccw=True) - helixCmds += arc(plungePos.x, plungePos.y, helixX - toold * plungeR, helixY, helixX, helixY, horizFeed, ez=curZ, ccw=True) + helixCmds += arc( + plungePos.x, + plungePos.y, + helixX, + helixY, + helixX - toold * plungeR, + helixY, + horizFeed, + ez=(curZ + lastZ) / 2.0, + ccw=True, + ) + helixCmds += arc( + plungePos.x, + plungePos.y, + helixX - toold * plungeR, + helixY, + helixX, + helixY, + horizFeed, + ez=curZ, + ccw=True, + ) lastZ = curZ curZ = max(curZ - dzPerRev, destZ) @@ -619,7 +716,7 @@ def rampPlunge(edge, rampangle, destZ, startZ): """ rampCmds = "(START RAMP PLUNGE)\n" - if(edge is None): + if edge is None: raise Exception("Ramp plunging requires an edge!") sPoint = edge.Vertexes[0].Point @@ -630,7 +727,7 @@ def rampPlunge(edge, rampangle, destZ, startZ): ePoint = edge.Vertexes[-1].Point rampDist = edge.Length - rampDZ = math.sin(rampangle / 180. * math.pi) * rampDist + rampDZ = math.sin(rampangle / 180.0 * math.pi) * rampDist rampCmds += rapid(sPoint.x, sPoint.y) rampCmds += rapid(z=startZ) @@ -640,7 +737,7 @@ def rampPlunge(edge, rampangle, destZ, startZ): curZ = max(startZ - rampDZ, destZ) done = False while not done: - done = (curZ == destZ) + done = curZ == destZ # If it's an arc, handle it! if isinstance(edge.Curve, Part.Circle): @@ -656,9 +753,9 @@ def rampPlunge(edge, rampangle, destZ, startZ): def sort_jobs(locations, keys, attractors=None): - """ sort holes by the nearest neighbor method - keys: two-element list of keys for X and Y coordinates. for example ['x','y'] - originally written by m0n5t3r for PathHelix + """sort holes by the nearest neighbor method + keys: two-element list of keys for X and Y coordinates. for example ['x','y'] + originally written by m0n5t3r for PathHelix """ if attractors is None: attractors = [] @@ -671,7 +768,7 @@ def sort_jobs(locations, keys, attractors=None): attractors = attractors or [keys[0]] def sqdist(a, b): - """ square Euclidean distance """ + """square Euclidean distance""" d = 0 for k in keys: d += (a[k] - b[k]) ** 2 @@ -740,7 +837,9 @@ def guessDepths(objshape, subs=None): elif fbb.ZMax == fbb.ZMin and fbb.ZMax > bb.ZMin: # face/shelf final = fbb.ZMin - return depth_params(clearance, safe, start, 1.0, 0.0, final, user_depths=None, equalstep=False) + return depth_params( + clearance, safe, start, 1.0, 0.0, final, user_depths=None, equalstep=False + ) def drillTipLength(tool): @@ -750,28 +849,36 @@ def drillTipLength(tool): PathLog.error(translate("Path", "Legacy Tools not supported")) return 0.0 - if not hasattr(tool, 'TipAngle'): + if not hasattr(tool, "TipAngle"): PathLog.error(translate("Path", "Selected tool is not a drill")) return 0.0 angle = tool.TipAngle if angle <= 0 or angle >= 180: - PathLog.error(translate("Path", "Invalid Cutting Edge Angle %.2f, must be >0° and <=180°") % angle) + PathLog.error( + translate("Path", "Invalid Cutting Edge Angle %.2f, must be >0° and <=180°") + % angle + ) return 0.0 theta = math.radians(angle) length = (float(tool.Diameter) / 2) / math.tan(theta / 2) if length < 0: - PathLog.error(translate("Path", "Cutting Edge Angle (%.2f) results in negative tool tip length") % angle) + PathLog.error( + translate( + "Path", "Cutting Edge Angle (%.2f) results in negative tool tip length" + ) + % angle + ) return 0.0 return length class depth_params(object): - '''calculates the intermediate depth values for various operations given the starting, ending, and stepdown parameters + """calculates the intermediate depth values for various operations given the starting, ending, and stepdown parameters (self, clearance_height, safe_height, start_depth, step_down, z_finish_depth, final_depth, [user_depths=None], equalstep=False) Note: if user_depths are supplied, only user_depths will be used. @@ -784,10 +891,20 @@ class depth_params(object): final_depth: Lowest point of the cutting operation user_depths: List of specified depths equalstep: Boolean. If True, steps down except Z_finish_depth will be balanced. - ''' + """ - def __init__(self, clearance_height, safe_height, start_depth, step_down, z_finish_step, final_depth, user_depths=None, equalstep=False): - '''self, clearance_height, safe_height, start_depth, step_down, z_finish_depth, final_depth, [user_depths=None], equalstep=False''' + def __init__( + self, + clearance_height, + safe_height, + start_depth, + step_down, + z_finish_step, + final_depth, + user_depths=None, + equalstep=False, + ): + """self, clearance_height, safe_height, start_depth, step_down, z_finish_depth, final_depth, [user_depths=None], equalstep=False""" self.__clearance_height = clearance_height self.__safe_height = safe_height @@ -800,7 +917,7 @@ class depth_params(object): self.index = 0 if self.__z_finish_step > self.__step_down: - raise ValueError('z_finish_step must be less than step_down') + raise ValueError("z_finish_step must be less than step_down") def __iter__(self): self.index = 0 @@ -874,8 +991,8 @@ class depth_params(object): return self.__user_depths def __get_depths(self, equalstep=False): - '''returns a list of depths to be used in order from first to last. - equalstep=True: all steps down before the finish pass will be equalized.''' + """returns a list of depths to be used in order from first to last. + equalstep=True: all steps down before the finish pass will be equalized.""" if self.user_depths is not None: return self.__user_depths @@ -895,18 +1012,22 @@ class depth_params(object): return depths if equalstep: - depths += self.__equal_steps(self.__start_depth, depths[-1], self.__step_down)[1:] + depths += self.__equal_steps( + self.__start_depth, depths[-1], self.__step_down + )[1:] else: - depths += self.__fixed_steps(self.__start_depth, depths[-1], self.__step_down)[1:] + depths += self.__fixed_steps( + self.__start_depth, depths[-1], self.__step_down + )[1:] depths.reverse() return depths def __equal_steps(self, start, stop, max_size): - '''returns a list of depths beginning with the bottom (included), ending + """returns a list of depths beginning with the bottom (included), ending with the top (not included). all steps are of equal size, which is as big as possible but not bigger - than max_size.''' + than max_size.""" steps_needed = math.ceil((start - stop) / max_size) depths = list(numpy.linspace(stop, start, steps_needed, endpoint=False)) @@ -914,10 +1035,10 @@ class depth_params(object): return depths def __fixed_steps(self, start, stop, size): - '''returns a list of depths beginning with the bottom (included), ending + """returns a list of depths beginning with the bottom (included), ending with the top (not included). all steps are of size 'size' except the one at the bottom which can be - smaller.''' + smaller.""" fullsteps = int((start - stop) / size) last_step = start - (fullsteps * size) @@ -968,17 +1089,17 @@ def simplify3dLine(line, tolerance=1e-4): def RtoIJ(startpoint, command): - ''' + """ This function takes a startpoint and an arc command in radius mode and returns an arc command in IJ mode. Useful for preprocessor scripts - ''' - if 'R' not in command.Parameters: - raise ValueError('No R parameter in command') - if command.Name not in ['G2', 'G02', 'G03', 'G3']: - raise ValueError('Not an arc command') + """ + if "R" not in command.Parameters: + raise ValueError("No R parameter in command") + if command.Name not in ["G2", "G02", "G03", "G3"]: + raise ValueError("Not an arc command") endpoint = command.Placement.Base - radius = command.Parameters['R'] + radius = command.Parameters["R"] # calculate the IJ # we take a vector between the start and endpoints @@ -988,7 +1109,7 @@ def RtoIJ(startpoint, command): perp = chord.cross(FreeCAD.Vector(0, 0, 1)) # use pythagoras to get the perp length - plength = math.sqrt(radius**2 - (chord.Length / 2)**2) + plength = math.sqrt(radius ** 2 - (chord.Length / 2) ** 2) perp.normalize() perp.scale(plength, plength, plength) @@ -996,9 +1117,9 @@ def RtoIJ(startpoint, command): relativecenter = chord.scale(0.5, 0.5, 0.5).add(perp) # build new command - params = { c: command.Parameters[c] for c in 'XYZF' if c in command.Parameters} - params['I'] = relativecenter.x - params['J'] = relativecenter.y + params = {c: command.Parameters[c] for c in "XYZF" if c in command.Parameters} + params["I"] = relativecenter.x + params["J"] = relativecenter.y newcommand = Path.Command(command.Name) newcommand.Parameters = params From 7766cda1b165c899ba7c3caa81d5e7a60cdfbf56 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Fri, 17 Dec 2021 17:27:26 -0600 Subject: [PATCH 29/65] cleanup for translation handling --- src/Mod/Path/InitGui.py | 44 ++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 44e5d41b09..820133a723 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -83,6 +83,8 @@ class PathWorkbench(Workbench): from PathScripts import PathToolBitCmd from PathScripts import PathToolBitLibraryCmd + from PySide.QtCore import QT_TRANSLATE_NOOP + import PathCommands PathGuiInit.Startup() @@ -139,7 +141,7 @@ class PathWorkbench(Workbench): FreeCADGui.addCommand( "Path_EngraveTools", PathCommandGroup( - engravecmdlist, QtCore.QT_TRANSLATE_NOOP("Path", "Engraving Operations") + engravecmdlist, QT_TRANSLATE_NOOP("Path", "Engraving Operations") ), ) @@ -163,7 +165,7 @@ class PathWorkbench(Workbench): "Path_3dTools", PathCommandGroup( threedopcmdlist, - QtCore.QT_TRANSLATE_NOOP("Path", "3D Operations"), + QT_TRANSLATE_NOOP("Path", "3D Operations"), ), ) except ImportError: @@ -171,25 +173,25 @@ class PathWorkbench(Workbench): FreeCAD.Console.PrintError("OpenCamLib is not working!\n") self.appendToolbar( - QtCore.QT_TRANSLATE_NOOP("Path", "Project Setup"), projcmdlist + QT_TRANSLATE_NOOP("Path", "Project Setup"), projcmdlist ) self.appendToolbar( - QtCore.QT_TRANSLATE_NOOP("Path", "Tool Commands"), toolcmdlist + QT_TRANSLATE_NOOP("Path", "Tool Commands"), toolcmdlist ) self.appendToolbar( - QtCore.QT_TRANSLATE_NOOP("Path", "New Operations"), + QT_TRANSLATE_NOOP("Path", "New Operations"), twodopcmdlist + engravecmdgroup + threedcmdgroup, ) self.appendToolbar( - QtCore.QT_TRANSLATE_NOOP("Path", "Path Modification"), modcmdlist + QT_TRANSLATE_NOOP("Path", "Path Modification"), modcmdlist ) if extracmdlist: self.appendToolbar( - QtCore.QT_TRANSLATE_NOOP("Path", "Helpful Tools"), extracmdlist + QT_TRANSLATE_NOOP("Path", "Helpful Tools"), extracmdlist ) self.appendMenu( - [QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], + [QT_TRANSLATE_NOOP("Path", "&Path")], projcmdlist + ["Path_ExportTemplate", "Separator"] + toolcmdlist @@ -203,41 +205,41 @@ class PathWorkbench(Workbench): ) self.appendMenu( [ - QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), - QtCore.QT_TRANSLATE_NOOP("Path", "Path Dressup"), + QT_TRANSLATE_NOOP("Path", "&Path"), + QT_TRANSLATE_NOOP("Path", "Path Dressup"), ], dressupcmdlist, ) self.appendMenu( [ - QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), - QtCore.QT_TRANSLATE_NOOP("Path", "Supplemental Commands"), + QT_TRANSLATE_NOOP("Path", "&Path"), + QT_TRANSLATE_NOOP("Path", "Supplemental Commands"), ], prepcmdlist, ) self.appendMenu( [ - QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), - QtCore.QT_TRANSLATE_NOOP("Path", "Path Modification"), + QT_TRANSLATE_NOOP("Path", "&Path"), + QT_TRANSLATE_NOOP("Path", "Path Modification"), ], modcmdlist, ) if specialcmdlist: self.appendMenu( [ - QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), - QtCore.QT_TRANSLATE_NOOP("Path", "Specialty Operations"), + QT_TRANSLATE_NOOP("Path", "&Path"), + QT_TRANSLATE_NOOP("Path", "Specialty Operations"), ], specialcmdlist, ) if extracmdlist: - self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], extracmdlist) + self.appendMenu([QT_TRANSLATE_NOOP("Path", "&Path")], extracmdlist) - self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], ["Separator"]) + self.appendMenu([QT_TRANSLATE_NOOP("Path", "&Path")], ["Separator"]) self.appendMenu( [ - QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), - QtCore.QT_TRANSLATE_NOOP("Path", "Utils"), + QT_TRANSLATE_NOOP("Path", "&Path"), + QT_TRANSLATE_NOOP("Path", "Utils"), ], ["Path_PropertyBag"], ) @@ -312,5 +314,3 @@ class PathWorkbench(Workbench): Gui.addWorkbench(PathWorkbench()) FreeCAD.addImportType("GCode (*.nc *.gc *.ncc *.ngc *.cnc *.tap *.gcode)", "PathGui") -# FreeCAD.addExportType( -# "GCode (*.nc *.gc *.ncc *.ngc *.cnc *.tap *.gcode)", "PathGui") From af0411a897bea73e256231fe98d0bf259dc42891 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sat, 18 Dec 2021 09:52:34 -0600 Subject: [PATCH 30/65] Translation cleanup: PathCommands --- src/Mod/Path/PathCommands.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/Mod/Path/PathCommands.py b/src/Mod/Path/PathCommands.py index d79288289d..726166ae78 100644 --- a/src/Mod/Path/PathCommands.py +++ b/src/Mod/Path/PathCommands.py @@ -31,15 +31,14 @@ from PathScripts.PathUtils import horizontalFaceLoop from PathScripts.PathUtils import addToJob from PathScripts.PathUtils import findParentJob +from PySide.QtCore import QT_TRANSLATE_NOOP + if FreeCAD.GuiUp: import FreeCADGui from PySide import QtCore from PySide import QtGui -else: - - def translate(ctxt, txt): - return txt +# translate = FreeCAD.Qt.translate __title__ = "FreeCAD Path Commands" __author__ = "sliptonic" @@ -57,11 +56,9 @@ class _CommandSelectLoop: def GetResources(self): return { "Pixmap": "Path_SelectLoop", - "MenuText": QtCore.QT_TRANSLATE_NOOP( - "Path_SelectLoop", "Finish Selecting Loop" - ), + "MenuText": QT_TRANSLATE_NOOP("Path_SelectLoop", "Finish Selecting Loop"), "Accel": "P, L", - "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "ToolTip": QT_TRANSLATE_NOOP( "Path_SelectLoop", "Complete loop selection from two edges" ), "CmdType": "ForEdit", @@ -120,10 +117,8 @@ class _CommandSelectLoop: elif FreeCAD.GuiUp: QtGui.QMessageBox.information( None, - QtCore.QT_TRANSLATE_NOOP("Path_SelectLoop", "Feature Completion"), - QtCore.QT_TRANSLATE_NOOP( - "Path_SelectLoop", "Closed loop detection failed." - ), + QT_TRANSLATE_NOOP("Path_SelectLoop", "Feature Completion"), + QT_TRANSLATE_NOOP("Path_SelectLoop", "Closed loop detection failed."), ) def formsPartOfALoop(self, obj, sub, names): @@ -151,11 +146,11 @@ class _ToggleOperation: def GetResources(self): return { "Pixmap": "Path_OpActive", - "MenuText": QtCore.QT_TRANSLATE_NOOP( + "MenuText": QT_TRANSLATE_NOOP( "Path_OpActiveToggle", "Toggle the Active State of the Operation" ), "Accel": "P, X", - "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "ToolTip": QT_TRANSLATE_NOOP( "Path_OpActiveToggle", "Toggle the Active State of the Operation" ), "CmdType": "ForEdit", @@ -194,10 +189,10 @@ class _CopyOperation: def GetResources(self): return { "Pixmap": "Path_OpCopy", - "MenuText": QtCore.QT_TRANSLATE_NOOP( + "MenuText": QT_TRANSLATE_NOOP( "Path_OperationCopy", "Copy the operation in the job" ), - "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "ToolTip": QT_TRANSLATE_NOOP( "Path_OperationCopy", "Copy the operation in the job" ), "CmdType": "ForEdit", From 41ea04f87523700530da9c33fe86c171185fe47e Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sat, 18 Dec 2021 10:04:29 -0600 Subject: [PATCH 31/65] tranlation cleanup PathCopy transaction PathCopy --- src/Mod/Path/PathScripts/PathCopy.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathCopy.py b/src/Mod/Path/PathScripts/PathCopy.py index bf626b2dc6..0a622a3331 100644 --- a/src/Mod/Path/PathScripts/PathCopy.py +++ b/src/Mod/Path/PathScripts/PathCopy.py @@ -24,11 +24,12 @@ import FreeCAD import FreeCADGui from PySide import QtCore +from PySide.QtCore import QT_TRANSLATE_NOOP + __doc__ = """Path Copy object and FreeCAD command""" -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) + +translate = FreeCAD.Qt.translate class ObjectPathCopy: @@ -37,13 +38,13 @@ class ObjectPathCopy: "App::PropertyLink", "Base", "Path", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The path to be copied"), + QT_TRANSLATE_NOOP("App::Property", "The path to be copied"), ) obj.addProperty( "App::PropertyLink", "ToolController", "Path", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "The tool controller that will be used to calculate the path", ), @@ -87,8 +88,8 @@ class CommandPathCopy: def GetResources(self): return { "Pixmap": "Path_Copy", - "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_Copy", "Copy"), - "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "MenuText": QT_TRANSLATE_NOOP("Path_Copy", "Copy"), + "ToolTip": QT_TRANSLATE_NOOP( "Path_Copy", "Creates a linked copy of another path" ), } @@ -102,7 +103,7 @@ class CommandPathCopy: def Activated(self): - FreeCAD.ActiveDocument.openTransaction(translate("Path_Copy", "Create Copy")) + FreeCAD.ActiveDocument.openTransaction("Create Copy") FreeCADGui.addModule("PathScripts.PathCopy") consolecode = """ From 958c9121048a8414bbc0bcd8410cf410a393ad28 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sat, 18 Dec 2021 10:10:40 -0600 Subject: [PATCH 32/65] translation cleanup PathComment transaction PathComment --- src/Mod/Path/PathScripts/PathComment.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathComment.py b/src/Mod/Path/PathScripts/PathComment.py index bcd9a468f2..58e5e605b6 100644 --- a/src/Mod/Path/PathScripts/PathComment.py +++ b/src/Mod/Path/PathScripts/PathComment.py @@ -27,15 +27,18 @@ import FreeCADGui import Path from PySide import QtCore -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +from PySide.QtCore import QT_TRANSLATE_NOOP + +translate = FreeCAD.Qt.translate class Comment: def __init__(self, obj): obj.addProperty( - "App::PropertyString", "Comment", "Path", "Comment or note for CNC program" + "App::PropertyString", + "Comment", + "Path", + QT_TRANSLATE_NOOP("App::Property", "Comment or note for CNC program"), ) obj.Proxy = self mode = 2 @@ -98,8 +101,8 @@ class CommandPathComment: def GetResources(self): return { "Pixmap": "Path_Comment", - "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_Comment", "Comment"), - "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "MenuText": QT_TRANSLATE_NOOP("Path_Comment", "Comment"), + "ToolTip": QT_TRANSLATE_NOOP( "Path_Comment", "Add a Comment to your CNC program" ), } @@ -112,9 +115,7 @@ class CommandPathComment: return False def Activated(self): - FreeCAD.ActiveDocument.openTransaction( - translate("Path_Comment", "Create a Comment in your CNC program") - ) + FreeCAD.ActiveDocument.openTransaction("Create a Comment in your CNC program") FreeCADGui.addModule("PathScripts.PathComment") snippet = """ import Path From 240d44238b00213187da9316b179ba6cbd22777e Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sat, 18 Dec 2021 10:20:36 -0600 Subject: [PATCH 33/65] translation cleanup PathCustom/gui --- src/Mod/Path/PathScripts/PathCustom.py | 15 ++++++++------- src/Mod/Path/PathScripts/PathCustomGui.py | 19 ++++--------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathCustom.py b/src/Mod/Path/PathScripts/PathCustom.py index c8e517b4b3..17b03f830d 100644 --- a/src/Mod/Path/PathScripts/PathCustom.py +++ b/src/Mod/Path/PathScripts/PathCustom.py @@ -26,7 +26,7 @@ import Path import PathScripts.PathOp as PathOp import PathScripts.PathLog as PathLog -from PySide import QtCore +from PySide.QtCore import QT_TRANSLATE_NOOP __title__ = "Path Custom Operation" __author__ = "sliptonic (Brad Collette)" @@ -34,13 +34,14 @@ __url__ = "http://www.freecadweb.org" __doc__ = "Path Custom object and FreeCAD command" -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +translate = FreeCAD.Qt.translate class ObjectCustom(PathOp.ObjectOp): @@ -52,7 +53,7 @@ class ObjectCustom(PathOp.ObjectOp): "App::PropertyStringList", "Gcode", "Path", - QtCore.QT_TRANSLATE_NOOP("PathCustom", "The gcode to be inserted"), + QT_TRANSLATE_NOOP("App::Property", "The gcode to be inserted"), ) obj.Proxy = self diff --git a/src/Mod/Path/PathScripts/PathCustomGui.py b/src/Mod/Path/PathScripts/PathCustomGui.py index 39c237cdcd..184229247d 100644 --- a/src/Mod/Path/PathScripts/PathCustomGui.py +++ b/src/Mod/Path/PathScripts/PathCustomGui.py @@ -25,7 +25,8 @@ import FreeCADGui import PathScripts.PathCustom as PathCustom import PathScripts.PathOpGui as PathOpGui -from PySide import QtCore +from PySide.QtCore import QT_TRANSLATE_NOOP + __title__ = "Path Custom Operation UI" __author__ = "sliptonic (Brad Collette)" @@ -33,18 +34,6 @@ __url__ = "http://www.freecadweb.org" __doc__ = "Custom operation page controller and command implementation." -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) - - -# class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): -# '''Page controller for the base geometry.''' - -# def getForm(self): -# return None - - class TaskPanelOpPage(PathOpGui.TaskPanelPage): """Page controller class for the Custom operation.""" @@ -80,8 +69,8 @@ Command = PathOpGui.SetupOperation( PathCustom.Create, TaskPanelOpPage, "Path_Custom", - QtCore.QT_TRANSLATE_NOOP("Path_Custom", "Custom"), - QtCore.QT_TRANSLATE_NOOP("Path_Custom", "Create custom gcode snippet"), + QT_TRANSLATE_NOOP("Path_Custom", "Custom"), + QT_TRANSLATE_NOOP("Path_Custom", "Create custom gcode snippet"), PathCustom.SetupProperties, ) From d0d15f956f6c46a655e15b7447d6fd40ba8ac33b Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sat, 18 Dec 2021 10:30:58 -0600 Subject: [PATCH 34/65] Translation cleanup PathUtils/PathUtil --- src/Mod/Path/PathScripts/PathUtil.py | 12 +++++++----- src/Mod/Path/PathScripts/PathUtils.py | 12 +++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathUtil.py b/src/Mod/Path/PathScripts/PathUtil.py index 02776b3ab1..5f2c8b9bdc 100644 --- a/src/Mod/Path/PathScripts/PathUtil.py +++ b/src/Mod/Path/PathScripts/PathUtil.py @@ -30,15 +30,17 @@ So if you add to this file and think about importing anything from PathScripts other than PathLog, then it probably doesn't belong here. """ +import FreeCAD import six import PathScripts.PathLog as PathLog -import PySide -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +translate = FreeCAD.Qt.translate - -def translate(context, text, disambig=None): - return PySide.QtCore.QCoreApplication.translate(context, text, disambig) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) def _getProperty(obj, prop): diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index a97679bae7..4d4f2426fb 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -41,12 +41,14 @@ DraftGeomUtils = LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils") Part = LazyLoader("Part", globals(), "Part") TechDraw = LazyLoader("TechDraw", globals(), "TechDraw") -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +translate = FreeCAD.Qt.translate -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) UserInput = None @@ -179,7 +181,7 @@ def isDrillable(obj, candidate, tooldiameter=None, includePartials=False): PathLog.debug("candidate is drillable: {}".format(drillable)) except Exception as ex: # pylint: disable=broad-except PathLog.warning( - translate("PathUtils", "Issue determine drillability: {}").format(ex) + translate("Path", "Issue determine drillability: {}").format(ex) ) return drillable From d567266c512c134af4418903319156b4492f1a88 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sat, 18 Dec 2021 10:39:02 -0600 Subject: [PATCH 35/65] Translation cleanup PathHop PathHop transaction --- src/Mod/Path/PathScripts/PathHop.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathHop.py b/src/Mod/Path/PathScripts/PathHop.py index 67631ead08..1447f98fa3 100644 --- a/src/Mod/Path/PathScripts/PathHop.py +++ b/src/Mod/Path/PathScripts/PathHop.py @@ -23,13 +23,11 @@ import FreeCAD import FreeCADGui import Path -from PySide import QtCore +from PySide.QtCore import QT_TRANSLATE_NOOP __doc__ = """Path Hop object and FreeCAD command""" -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +translate = FreeCAD.Qt.translate class ObjectHop: @@ -38,15 +36,13 @@ class ObjectHop: "App::PropertyLink", "NextObject", "Path", - QtCore.QT_TRANSLATE_NOOP( - "App::Property", "The object to be reached by this hop" - ), + QT_TRANSLATE_NOOP("App::Property", "The object to be reached by this hop"), ) obj.addProperty( "App::PropertyDistance", "HopHeight", "Path", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The Z height of the hop"), + QT_TRANSLATE_NOOP("App::Property", "The Z height of the hop"), ) obj.Proxy = self @@ -102,10 +98,8 @@ class CommandPathHop: def GetResources(self): return { "Pixmap": "Path_Hop", - "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_Hop", "Hop"), - "ToolTip": QtCore.QT_TRANSLATE_NOOP( - "Path_Hop", "Creates a Path Hop object" - ), + "MenuText": QT_TRANSLATE_NOOP("Path_Hop", "Hop"), + "ToolTip": QT_TRANSLATE_NOOP("Path_Hop", "Creates a Path Hop object"), } def IsActive(self): @@ -130,7 +124,7 @@ class CommandPathHop: ) return - FreeCAD.ActiveDocument.openTransaction(translate("Path_Hop", "Create Hop")) + FreeCAD.ActiveDocument.openTransaction("Create Hop") FreeCADGui.addModule("PathScripts.PathHop") FreeCADGui.addModule("PathScripts.PathUtils") FreeCADGui.doCommand( From de6977db73b8ada92df424238945ce9214d11aae Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sat, 18 Dec 2021 10:43:48 -0600 Subject: [PATCH 36/65] translation cleanup PathGeom --- src/Mod/Path/PathScripts/PathGeom.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index ff276c9385..bf45ca4141 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -41,12 +41,13 @@ __doc__ = "Functions to extract and convert between Path.Command and Part.Edge a Tolerance = 0.000001 -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +translate = FreeCAD.Qt.translate -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) class Side: From aa30b7ba603de8b129920266128a503520feeea0 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 22 Dec 2021 11:32:42 -0600 Subject: [PATCH 37/65] PathArray translation cleanup --- src/Mod/Path/PathScripts/PathArray.py | 41 ++++++++++++--------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathArray.py b/src/Mod/Path/PathScripts/PathArray.py index 3d08e3c1a1..69ec4e2a1b 100644 --- a/src/Mod/Path/PathScripts/PathArray.py +++ b/src/Mod/Path/PathScripts/PathArray.py @@ -28,12 +28,11 @@ from PathScripts import PathLog from PySide import QtCore import math import random +from PySide.QtCore import QT_TRANSLATE_NOOP __doc__ = """Path Array object and FreeCAD command""" -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +translate = FreeCAD.Qt.translate class ObjectArray: @@ -42,19 +41,19 @@ class ObjectArray: "App::PropertyLinkList", "Base", "Path", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The path(s) to array"), + QT_TRANSLATE_NOOP("App::Property", "The path(s) to array"), ) obj.addProperty( "App::PropertyEnumeration", "Type", "Path", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Pattern method"), + QT_TRANSLATE_NOOP("App::Property", "Pattern method"), ) obj.addProperty( "App::PropertyVectorDistance", "Offset", "Path", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "The spacing between the array copies in Linear pattern", ), @@ -63,7 +62,7 @@ class ObjectArray: "App::PropertyInteger", "CopiesX", "Path", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "The number of copies in X direction in Linear pattern" ), ) @@ -71,7 +70,7 @@ class ObjectArray: "App::PropertyInteger", "CopiesY", "Path", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "The number of copies in Y direction in Linear pattern" ), ) @@ -79,13 +78,13 @@ class ObjectArray: "App::PropertyAngle", "Angle", "Path", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Total angle in Polar pattern"), + QT_TRANSLATE_NOOP("App::Property", "Total angle in Polar pattern"), ) obj.addProperty( "App::PropertyInteger", "Copies", "Path", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "The number of copies in Linear 1D and Polar pattern" ), ) @@ -93,7 +92,7 @@ class ObjectArray: "App::PropertyVector", "Centre", "Path", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "The centre of rotation in Polar pattern" ), ) @@ -101,7 +100,7 @@ class ObjectArray: "App::PropertyBool", "SwapDirection", "Path", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Make copies in X direction before Y in Linear 2D pattern", ), @@ -110,23 +109,19 @@ class ObjectArray: "App::PropertyInteger", "JitterPercent", "Path", - QtCore.QT_TRANSLATE_NOOP( - "App::Property", "Percent of copies to randomly offset" - ), + QT_TRANSLATE_NOOP("App::Property", "Percent of copies to randomly offset"), ) obj.addProperty( "App::PropertyVectorDistance", "JitterMagnitude", "Path", - QtCore.QT_TRANSLATE_NOOP( - "App::Property", "Maximum random offset of copies" - ), + QT_TRANSLATE_NOOP("App::Property", "Maximum random offset of copies"), ) obj.addProperty( "App::PropertyLink", "ToolController", "Path", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "The tool controller that will be used to calculate the path", ), @@ -135,7 +130,7 @@ class ObjectArray: "App::PropertyBool", "Active", "Path", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "PathOp", "Make False, to prevent operation from generating code" ), ) @@ -186,7 +181,7 @@ class ObjectArray: "App::PropertyBool", "Active", "Path", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "PathOp", "Make False, to prevent operation from generating code" ), ) @@ -488,8 +483,8 @@ class CommandPathArray: def GetResources(self): return { "Pixmap": "Path_Array", - "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_Array", "Array"), - "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "MenuText": QT_TRANSLATE_NOOP("Path_Array", "Array"), + "ToolTip": QT_TRANSLATE_NOOP( "Path_Array", "Creates an array from selected path(s)" ), } From 61435ffe544d63d6ab0e48ddddd10d7359f74e8c Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 22 Dec 2021 11:37:49 -0600 Subject: [PATCH 38/65] translation cleanup PathCollision --- src/Mod/Path/PathScripts/PathCollision.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathCollision.py b/src/Mod/Path/PathScripts/PathCollision.py index 9e0e8e9a55..5927294bea 100644 --- a/src/Mod/Path/PathScripts/PathCollision.py +++ b/src/Mod/Path/PathScripts/PathCollision.py @@ -24,18 +24,13 @@ import FreeCAD import PathScripts.PathLog as PathLog from PySide import QtCore from PathScripts.PathUtils import waiting_effects +from PySide.QtCore import QT_TRANSLATE_NOOP LOG_MODULE = "PathCollision" PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE) PathLog.trackModule("PathCollision") FreeCAD.setLogLevel("Path.Area", 0) - -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) - - __title__ = "Path Collision Utility" __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" @@ -45,7 +40,6 @@ __url__ = "https://www.freecadweb.org" class _CollisionSim: def __init__(self, obj): - # obj.addProperty("App::PropertyLink", "Original", "reference", QtCore.QT_TRANSLATE_NOOP("App::Property", "The base object this collision refers to")) obj.Proxy = self def execute(self, fp): @@ -61,7 +55,7 @@ class _ViewProviderCollisionSim: "App::PropertyLink", "Original", "reference", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "The base object this collision refers to" ), ) From 37284c5a735aa1dffe0a4ed004dbf04899916917 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 22 Dec 2021 11:42:03 -0600 Subject: [PATCH 39/65] translation cleanup --- src/Mod/Path/PathScripts/PathFixture.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathFixture.py b/src/Mod/Path/PathScripts/PathFixture.py index defbd0fe0a..0571e13467 100644 --- a/src/Mod/Path/PathScripts/PathFixture.py +++ b/src/Mod/Path/PathScripts/PathFixture.py @@ -26,11 +26,7 @@ import FreeCAD import FreeCADGui import Path import PathScripts.PathUtils as PathUtils -from PySide import QtCore # , QtGui - -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +from PySide.QtCore import QT_TRANSLATE_NOOP class Fixture: @@ -39,7 +35,7 @@ class Fixture: "App::PropertyEnumeration", "Fixture", "Path", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Fixture Offset Number"), + QT_TRANSLATE_NOOP("App::Property", "Fixture Offset Number"), ) obj.Fixture = [ "G53", @@ -63,7 +59,7 @@ class Fixture: "App::PropertyBool", "Active", "Path", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Make False, to prevent operation from generating code" ), ) @@ -157,9 +153,9 @@ class CommandPathFixture: def GetResources(self): return { "Pixmap": "Path_Datums", - "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_Fixture", "Fixture"), - "ToolTip": QtCore.QT_TRANSLATE_NOOP( - "Path_Fixture", "Creates a Fixture Offset object" + "MenuText": QT_TRANSLATE_NOOP("PathFixture", "Fixture"), + "ToolTip": QT_TRANSLATE_NOOP( + "PathFixture", "Creates a Fixture Offset object" ), } @@ -171,9 +167,7 @@ class CommandPathFixture: return False def Activated(self): - FreeCAD.ActiveDocument.openTransaction( - translate("Path_Fixture", "Create a Fixture Offset") - ) + FreeCAD.ActiveDocument.openTransaction("Create a Fixture Offset") FreeCADGui.addModule("PathScripts.PathFixture") snippet = """ import Path @@ -195,7 +189,7 @@ PathUtils.addToJob(obj) if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand("Path_Fixture", CommandPathFixture()) + FreeCADGui.addCommand("PathFixture", CommandPathFixture()) FreeCAD.Console.PrintLog("Loading PathFixture... done\n") From 8c3ec0b78eb79ded37b2068ca3e6021e9762fc56 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 22 Dec 2021 11:46:00 -0600 Subject: [PATCH 40/65] Translation cleanup --- src/Mod/Path/PathScripts/PathPlane.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPlane.py b/src/Mod/Path/PathScripts/PathPlane.py index 365554c9a3..d25fa76e2f 100644 --- a/src/Mod/Path/PathScripts/PathPlane.py +++ b/src/Mod/Path/PathScripts/PathPlane.py @@ -26,10 +26,7 @@ import FreeCAD import FreeCADGui import Path from PySide import QtCore - -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +from PySide.QtCore import QT_TRANSLATE_NOOP class Plane: @@ -38,14 +35,14 @@ class Plane: "App::PropertyEnumeration", "SelectionPlane", "Path", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Orientation plane of CNC path"), + QT_TRANSLATE_NOOP("App::Property", "Orientation plane of CNC path"), ) obj.SelectionPlane = ["XY", "XZ", "YZ"] obj.addProperty( "App::PropertyBool", "Active", "Path", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Make False, to prevent operation from generating code" ), ) @@ -117,10 +114,10 @@ class _ViewProviderPlane: class CommandPathPlane: def GetResources(self): return { - "Pixmap": "Path_Plane", - "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_Plane", "Selection Plane"), - "ToolTip": QtCore.QT_TRANSLATE_NOOP( - "Path_Plane", "Create a Selection Plane object" + "Pixmap": "PathPlane", + "MenuText": QT_TRANSLATE_NOOP("PathPlane", "Selection Plane"), + "ToolTip": QT_TRANSLATE_NOOP( + "PathPlane", "Create a Selection Plane object" ), } @@ -132,9 +129,7 @@ class CommandPathPlane: return False def Activated(self): - FreeCAD.ActiveDocument.openTransaction( - translate("Path_Plane", "Create a Selection Plane object") - ) + FreeCAD.ActiveDocument.openTransaction("Create a Selection Plane object") FreeCADGui.addModule("PathScripts.PathPlane") snippet = """ import Path From b67bd47aa35d824b71ba6bebb3add87f934109c1 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 22 Dec 2021 11:57:06 -0600 Subject: [PATCH 41/65] translation cleanup --- src/Mod/Path/PathScripts/PathStock.py | 77 +++++++++++---------------- 1 file changed, 32 insertions(+), 45 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathStock.py b/src/Mod/Path/PathScripts/PathStock.py index aa7a9ef910..685fe1de94 100644 --- a/src/Mod/Path/PathScripts/PathStock.py +++ b/src/Mod/Path/PathScripts/PathStock.py @@ -25,7 +25,7 @@ import FreeCAD import PathScripts.PathLog as PathLog import math - +from PySide.QtCore import QT_TRANSLATE_NOOP from PySide import QtCore # lazily loaded modules @@ -33,12 +33,13 @@ from lazy_loader.lazy_loader import LazyLoader Part = LazyLoader("Part", globals(), "Part") -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +translate = FreeCAD.Qt.translate -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) class StockType: @@ -105,16 +106,16 @@ class StockFromBase(Stock): "App::PropertyLink", "Base", "Base", - QtCore.QT_TRANSLATE_NOOP( - "PathStock", "The base object this stock is derived from" + QT_TRANSLATE_NOOP( + "App::Property", "The base object this stock is derived from" ), ) obj.addProperty( "App::PropertyDistance", "ExtXneg", "Stock", - QtCore.QT_TRANSLATE_NOOP( - "PathStock", + QT_TRANSLATE_NOOP( + "App::Property", "Extra allowance from part bound box in negative X direction", ), ) @@ -122,8 +123,8 @@ class StockFromBase(Stock): "App::PropertyDistance", "ExtXpos", "Stock", - QtCore.QT_TRANSLATE_NOOP( - "PathStock", + QT_TRANSLATE_NOOP( + "App::Property", "Extra allowance from part bound box in positive X direction", ), ) @@ -131,8 +132,8 @@ class StockFromBase(Stock): "App::PropertyDistance", "ExtYneg", "Stock", - QtCore.QT_TRANSLATE_NOOP( - "PathStock", + QT_TRANSLATE_NOOP( + "App::Property", "Extra allowance from part bound box in negative Y direction", ), ) @@ -140,8 +141,8 @@ class StockFromBase(Stock): "App::PropertyDistance", "ExtYpos", "Stock", - QtCore.QT_TRANSLATE_NOOP( - "PathStock", + QT_TRANSLATE_NOOP( + "App::Property", "Extra allowance from part bound box in positive Y direction", ), ) @@ -149,8 +150,8 @@ class StockFromBase(Stock): "App::PropertyDistance", "ExtZneg", "Stock", - QtCore.QT_TRANSLATE_NOOP( - "PathStock", + QT_TRANSLATE_NOOP( + "App::Property", "Extra allowance from part bound box in negative Z direction", ), ) @@ -158,8 +159,8 @@ class StockFromBase(Stock): "App::PropertyDistance", "ExtZpos", "Stock", - QtCore.QT_TRANSLATE_NOOP( - "PathStock", + QT_TRANSLATE_NOOP( + "App::Property", "Extra allowance from part bound box in positive Z direction", ), ) @@ -167,7 +168,7 @@ class StockFromBase(Stock): "App::PropertyLink", "Material", "Component", - QtCore.QT_TRANSLATE_NOOP("App::Property", "A material for this object"), + QT_TRANSLATE_NOOP("App::Property", "A material for this object"), ) obj.Base = base @@ -239,19 +240,19 @@ class StockCreateBox(Stock): "App::PropertyLength", "Length", "Stock", - QtCore.QT_TRANSLATE_NOOP("PathStock", "Length of this stock box"), + QT_TRANSLATE_NOOP("App::Property", "Length of this stock box"), ) obj.addProperty( "App::PropertyLength", "Width", "Stock", - QtCore.QT_TRANSLATE_NOOP("PathStock", "Width of this stock box"), + QT_TRANSLATE_NOOP("App::Property", "Width of this stock box"), ) obj.addProperty( "App::PropertyLength", "Height", "Stock", - QtCore.QT_TRANSLATE_NOOP("PathStock", "Height of this stock box"), + QT_TRANSLATE_NOOP("App::Property", "Height of this stock box"), ) obj.Length = 10 @@ -291,13 +292,13 @@ class StockCreateCylinder(Stock): "App::PropertyLength", "Radius", "Stock", - QtCore.QT_TRANSLATE_NOOP("PathStock", "Radius of this stock cylinder"), + QT_TRANSLATE_NOOP("App::Property", "Radius of this stock cylinder"), ) obj.addProperty( "App::PropertyLength", "Height", "Stock", - QtCore.QT_TRANSLATE_NOOP("PathStock", "Height of this stock cylinder"), + QT_TRANSLATE_NOOP("App::Property", "Height of this stock cylinder"), ) obj.Radius = 2 @@ -333,9 +334,7 @@ def SetupStockObject(obj, stockType): "App::PropertyString", "StockType", "Stock", - QtCore.QT_TRANSLATE_NOOP( - "PathStock", "Internal representation of stock type" - ), + QT_TRANSLATE_NOOP("App::Property", "Internal representation of stock type"), ) obj.StockType = stockType obj.setEditorMode("StockType", 2) # hide @@ -515,10 +514,7 @@ def CreateFromTemplate(job, template): or rotW is not None ): PathLog.warning( - translate( - "PathStock", - "Corrupted or incomplete placement information in template - ignoring", - ) + "Corrupted or incomplete placement information in template - ignoring" ) if stockType == StockType.FromBase: @@ -557,10 +553,7 @@ def CreateFromTemplate(job, template): or zpos is not None ): PathLog.error( - translate( - "PathStock", - "Corrupted or incomplete specification for creating stock from base - ignoring extent", - ) + "Corrupted or incomplete specification for creating stock from base - ignoring extent" ) return CreateFromBase(job, neg, pos, placement) @@ -579,10 +572,7 @@ def CreateFromTemplate(job, template): ) elif length is not None or width is not None or height is not None: PathLog.error( - translate( - "PathStock", - "Corrupted or incomplete size for creating a stock box - ignoring size", - ) + "Corrupted or incomplete size for creating a stock box - ignoring size" ) else: PathLog.track( @@ -600,10 +590,7 @@ def CreateFromTemplate(job, template): radius = None height = None PathLog.error( - translate( - "PathStock", - "Corrupted or incomplete size for creating a stock cylinder - ignoring size", - ) + "Corrupted or incomplete size for creating a stock cylinder - ignoring size" ) return CreateCylinder(job, radius, height, placement) From bd140e3b3c4a12e90e83a7a92989d9d2465c837f Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 22 Dec 2021 12:00:51 -0600 Subject: [PATCH 42/65] translation cleanup --- src/Mod/Path/PathScripts/PathStop.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathStop.py b/src/Mod/Path/PathScripts/PathStop.py index 7cdc08cfc6..ba79ee605d 100644 --- a/src/Mod/Path/PathScripts/PathStop.py +++ b/src/Mod/Path/PathScripts/PathStop.py @@ -26,10 +26,9 @@ import FreeCAD import FreeCADGui import Path from PySide import QtCore +from PySide.QtCore import QT_TRANSLATE_NOOP -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +translate = FreeCAD.Qt.translate class Stop: @@ -38,7 +37,7 @@ class Stop: "App::PropertyEnumeration", "Stop", "Path", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Add Optional or Mandatory Stop to the program" ), ) @@ -109,8 +108,8 @@ class CommandPathStop: def GetResources(self): return { "Pixmap": "Path_Stop", - "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_Stop", "Stop"), - "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "MenuText": QT_TRANSLATE_NOOP("Path_Stop", "Stop"), + "ToolTip": QT_TRANSLATE_NOOP( "Path_Stop", "Add Optional or Mandatory Stop to the program" ), } @@ -124,7 +123,7 @@ class CommandPathStop: def Activated(self): FreeCAD.ActiveDocument.openTransaction( - translate("Path_Stop", "Add Optional or Mandatory Stop to the program") + "Add Optional or Mandatory Stop to the program" ) FreeCADGui.addModule("PathScripts.PathStop") snippet = """ From 8cdcfcd6962876fc4e7a31c0b246ca524e255fb2 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 22 Dec 2021 12:05:34 -0600 Subject: [PATCH 43/65] translation cleanup PathSanity --- src/Mod/Path/PathScripts/PathSanity.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSanity.py b/src/Mod/Path/PathScripts/PathSanity.py index cfc46f4149..b7478ea422 100644 --- a/src/Mod/Path/PathScripts/PathSanity.py +++ b/src/Mod/Path/PathScripts/PathSanity.py @@ -41,12 +41,9 @@ from datetime import datetime import os import webbrowser import subprocess +from PySide.QtCore import QT_TRANSLATE_NOOP -# Qt translation handling - - -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +translate = FreeCAD.Qt.translate LOG_MODULE = "PathSanity" @@ -103,11 +100,11 @@ class CommandPathSanity: def GetResources(self): return { "Pixmap": "Path_Sanity", - "MenuText": QtCore.QT_TRANSLATE_NOOP( + "MenuText": QT_TRANSLATE_NOOP( "Path_Sanity", "Check the path job for common errors" ), "Accel": "P, S", - "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "ToolTip": QT_TRANSLATE_NOOP( "Path_Sanity", "Check the path job for common errors" ), } From 47dd77cd748bbef3962033579db2f15811b9cd05 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 22 Dec 2021 12:21:22 -0600 Subject: [PATCH 44/65] translation cleanup --- src/Mod/Path/PathScripts/PathPropertyEditor.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPropertyEditor.py b/src/Mod/Path/PathScripts/PathPropertyEditor.py index 4798b4a40f..afc7f1e54d 100644 --- a/src/Mod/Path/PathScripts/PathPropertyEditor.py +++ b/src/Mod/Path/PathScripts/PathPropertyEditor.py @@ -31,13 +31,12 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Task panel editor for Properties" -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) - -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) class _PropertyEditor(object): From 1a9374ec5948eb34cad42f07ba0cc559fa51ee42 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 22 Dec 2021 12:25:30 -0600 Subject: [PATCH 45/65] translation cleanup pathiconviewprovider --- src/Mod/Path/PathScripts/PathIconViewProvider.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathIconViewProvider.py b/src/Mod/Path/PathScripts/PathIconViewProvider.py index 0bf4c8321d..74e458f03b 100644 --- a/src/Mod/Path/PathScripts/PathIconViewProvider.py +++ b/src/Mod/Path/PathScripts/PathIconViewProvider.py @@ -20,6 +20,7 @@ # * * # *************************************************************************** +import FreeCAD import PathGui import PathScripts.PathLog as PathLog import PathScripts.PathUtil as PathUtil @@ -30,8 +31,13 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "ViewProvider who's main and only task is to assign an icon." -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +translate = FreeCAD.Qt.translate + +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) class ViewProvider(object): @@ -89,9 +95,9 @@ class ViewProvider(object): def setupContextMenu(self, vobj, menu): # pylint: disable=unused-argument PathLog.track() - from PySide import QtCore, QtGui + from PySide import QtGui - edit = QtCore.QCoreApplication.translate("Path", "Edit", None) + edit = translate("Path", "Edit") action = QtGui.QAction(edit, menu) action.triggered.connect(self.setEdit) menu.addAction(action) From a3674181ae40f6137ebd1e9ae8d365e1ccd3ba4a Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 22 Dec 2021 16:08:25 -0600 Subject: [PATCH 46/65] Black pathdressupdragknife --- .../Path/PathScripts/PathDressupDragknife.py | 231 ++++++++++++------ 1 file changed, 161 insertions(+), 70 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupDragknife.py b/src/Mod/Path/PathScripts/PathDressupDragknife.py index 8b0fea27c7..1f5e25e344 100644 --- a/src/Mod/Path/PathScripts/PathDressupDragknife.py +++ b/src/Mod/Path/PathScripts/PathDressupDragknife.py @@ -30,7 +30,8 @@ import PathScripts.PathGui as PathGui # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -D = LazyLoader('DraftVecUtils', globals(), 'DraftVecUtils') + +D = LazyLoader("DraftVecUtils", globals(), "DraftVecUtils") __doc__ = """Dragknife Dressup object and FreeCAD command""" @@ -43,20 +44,46 @@ def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) -movecommands = ['G1', 'G01', 'G2', 'G02', 'G3', 'G03'] -rapidcommands = ['G0', 'G00'] -arccommands = ['G2', 'G3', 'G02', 'G03'] +movecommands = ["G1", "G01", "G2", "G02", "G3", "G03"] +rapidcommands = ["G0", "G00"] +arccommands = ["G2", "G3", "G02", "G03"] currLocation = {} class ObjectDressup: - def __init__(self, obj): - obj.addProperty("App::PropertyLink", "Base", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The base path to modify")) - obj.addProperty("App::PropertyAngle", "filterAngle", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Angles less than filter angle will not receive corner actions")) - obj.addProperty("App::PropertyFloat", "offset", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Distance the point trails behind the spindle")) - obj.addProperty("App::PropertyFloat", "pivotheight", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Height to raise during corner action")) + obj.addProperty( + "App::PropertyLink", + "Base", + "Path", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The base path to modify"), + ) + obj.addProperty( + "App::PropertyAngle", + "filterAngle", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", + "Angles less than filter angle will not receive corner actions", + ), + ) + obj.addProperty( + "App::PropertyFloat", + "offset", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "Distance the point trails behind the spindle" + ), + ) + obj.addProperty( + "App::PropertyFloat", + "pivotheight", + "Path", + QtCore.QT_TRANSLATE_NOOP( + "App::Property", "Height to raise during corner action" + ), + ) obj.Proxy = self @@ -67,13 +94,17 @@ class ObjectDressup: return None def shortcut(self, queue): - '''Determines whether its shorter to twist CW or CCW to align with - the next move''' + """Determines whether its shorter to twist CW or CCW to align with + the next move""" # get the vector of the last move if queue[1].Name in arccommands: - arcLoc = FreeCAD.Vector(queue[2].x + queue[1].I, queue[2].y + queue[1].J, currLocation['Z']) - radvector = arcLoc.sub(queue[1].Placement.Base) # .sub(arcLoc) # vector of chord from center to point + arcLoc = FreeCAD.Vector( + queue[2].x + queue[1].I, queue[2].y + queue[1].J, currLocation["Z"] + ) + radvector = arcLoc.sub( + queue[1].Placement.Base + ) # .sub(arcLoc) # vector of chord from center to point # vector of line perp to chord. v1 = radvector.cross(FreeCAD.Vector(0, 0, 1)) else: @@ -81,7 +112,9 @@ class ObjectDressup: # get the vector of the current move if queue[0].Name in arccommands: - arcLoc = FreeCAD.Vector((queue[1].x + queue[0].I), (queue[1].y + queue[0].J), currLocation['Z']) + arcLoc = FreeCAD.Vector( + (queue[1].x + queue[0].I), (queue[1].y + queue[0].J), currLocation["Z"] + ) radvector = queue[1].Placement.Base.sub(arcLoc) # calculate arcangle v2 = radvector.cross(FreeCAD.Vector(0, 0, 1)) else: @@ -93,24 +126,36 @@ class ObjectDressup: return "CCW" def segmentAngleXY(self, prevCommand, currCommand, endpos=False, currentZ=0): - '''returns in the starting angle in radians for a Path command. + """returns in the starting angle in radians for a Path command. requires the previous command in order to calculate arcs correctly - if endpos = True, return the angle at the end of the segment.''' + if endpos = True, return the angle at the end of the segment.""" if currCommand.Name in arccommands: - arcLoc = FreeCAD.Vector((prevCommand.x + currCommand.I), (prevCommand.y + currCommand.J), currentZ) + arcLoc = FreeCAD.Vector( + (prevCommand.x + currCommand.I), + (prevCommand.y + currCommand.J), + currentZ, + ) if endpos is True: - radvector = arcLoc.sub(currCommand.Placement.Base) # Calculate vector at start of arc + radvector = arcLoc.sub( + currCommand.Placement.Base + ) # Calculate vector at start of arc else: - radvector = arcLoc.sub(prevCommand.Placement.Base) # Calculate vector at end of arc + radvector = arcLoc.sub( + prevCommand.Placement.Base + ) # Calculate vector at end of arc v1 = radvector.cross(FreeCAD.Vector(0, 0, 1)) if currCommand.Name in ["G2", "G02"]: v1 = D.rotate2D(v1, math.radians(180)) else: - v1 = currCommand.Placement.Base.sub(prevCommand.Placement.Base) # Straight segments are easy + v1 = currCommand.Placement.Base.sub( + prevCommand.Placement.Base + ) # Straight segments are easy - myAngle = D.angle(v1, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1)) + myAngle = D.angle( + v1, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1) + ) return myAngle def getIncidentAngle(self, queue): @@ -123,18 +168,20 @@ class ObjectDressup: if angleatstart < 0: angleatstart = 360 + angleatstart - incident_angle = angleatend-angleatstart + incident_angle = angleatend - angleatstart return incident_angle def arcExtension(self, obj, queue): - '''returns gcode for arc extension''' + """returns gcode for arc extension""" global currLocation # pylint: disable=global-statement results = [] offset = obj.offset # Find the center of the old arc - C = FreeCAD.Base.Vector(queue[2].x + queue[1].I, queue[2].y + queue[1].J, currLocation['Z']) + C = FreeCAD.Base.Vector( + queue[2].x + queue[1].I, queue[2].y + queue[1].J, currLocation["Z"] + ) # Find radius of old arc R = math.hypot(queue[1].I, queue[1].J) @@ -165,8 +212,8 @@ class ObjectDressup: return (results, replace) def arcTwist(self, obj, queue, lastXY, twistCW=False): - '''returns gcode to do an arc move toward an arc to perform - a corner action twist. Includes lifting and plungeing the knife''' + """returns gcode to do an arc move toward an arc to perform + a corner action twist. Includes lifting and plungeing the knife""" global currLocation # pylint: disable=global-statement pivotheight = obj.pivotheight @@ -186,7 +233,9 @@ class ObjectDressup: currLocation.update(retract.Parameters) # get the center of the destination arc - arccenter = FreeCAD.Base.Vector(queue[1].x + queue[0].I, queue[1].y + queue[0].J, currLocation["Z"]) + arccenter = FreeCAD.Base.Vector( + queue[1].x + queue[0].I, queue[1].y + queue[0].J, currLocation["Z"] + ) # The center of the twist arc is the old line end point. C = queue[1].Placement.Base @@ -196,7 +245,9 @@ class ObjectDressup: # find angle of original center to startpoint v1 = queue[1].Placement.Base.sub(arccenter) - segAngle = D.angle(v1, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1)) + segAngle = D.angle( + v1, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1) + ) # Find angle subtended by the offset theta = offset / R @@ -210,14 +261,21 @@ class ObjectDressup: # calculate endpoints Bx = arccenter.x + R * math.cos(newangle) By = arccenter.y + R * math.sin(newangle) - endpointvector = FreeCAD.Base.Vector(Bx, By, currLocation['Z']) + endpointvector = FreeCAD.Base.Vector(Bx, By, currLocation["Z"]) # calculate IJ offsets of twist arc from current position. offsetvector = C.sub(lastXY) # add G2/G3 move arcmove = Path.Command( - arcdir, {"X": endpointvector.x, "Y": endpointvector.y, "I": offsetvector.x, "J": offsetvector.y}) + arcdir, + { + "X": endpointvector.x, + "Y": endpointvector.y, + "I": offsetvector.x, + "J": offsetvector.y, + }, + ) results.append(arcmove) currLocation.update(arcmove.Parameters) @@ -230,11 +288,13 @@ class ObjectDressup: offsetv = arccenter.sub(endpointvector) replace = Path.Command( - queue[0].Name, {"X": queue[0].X, "Y": queue[0].Y, "I": offsetv.x, "J": offsetv.y}) + queue[0].Name, + {"X": queue[0].X, "Y": queue[0].Y, "I": offsetv.x, "J": offsetv.y}, + ) return (results, replace) def lineExtension(self, obj, queue): - '''returns gcode for line extension''' + """returns gcode for line extension""" global currLocation # pylint: disable=global-statement offset = float(obj.offset) @@ -243,14 +303,16 @@ class ObjectDressup: v1 = queue[1].Placement.Base.sub(queue[2].Placement.Base) # extend the current segment to comp for offset - segAngle = D.angle(v1, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1)) + segAngle = D.angle( + v1, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1) + ) xoffset = math.cos(segAngle) * offset yoffset = math.sin(segAngle) * offset newX = currLocation["X"] + xoffset newY = currLocation["Y"] + yoffset - extendcommand = Path.Command('G1', {"X": newX, "Y": newY}) + extendcommand = Path.Command("G1", {"X": newX, "Y": newY}) results.append(extendcommand) currLocation.update(extendcommand.Parameters) @@ -259,8 +321,8 @@ class ObjectDressup: return (results, replace) def lineTwist(self, obj, queue, lastXY, twistCW=False): - '''returns gcode to do an arc move toward a line to perform - a corner action twist. Includes lifting and plungeing the knife''' + """returns gcode to do an arc move toward a line to perform + a corner action twist. Includes lifting and plungeing the knife""" global currLocation # pylint: disable=global-statement pivotheight = obj.pivotheight offset = obj.offset @@ -285,7 +347,9 @@ class ObjectDressup: v2 = queue[0].Placement.Base.sub(queue[1].Placement.Base) # calc arc endpoints to twist to - segAngle = D.angle(v2, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1)) + segAngle = D.angle( + v2, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1) + ) xoffset = math.cos(segAngle) * offset yoffset = math.sin(segAngle) * offset newX = queue[1].x + xoffset @@ -297,7 +361,8 @@ class ObjectDressup: # add the arc move arcmove = Path.Command( - arcdir, {"X": newX, "Y": newY, "I": I, "J": J}) # add G2/G3 move + arcdir, {"X": newX, "Y": newY, "I": I, "J": J} + ) # add G2/G3 move results.append(arcmove) currLocation.update(arcmove.Parameters) @@ -335,11 +400,11 @@ class ObjectDressup: continue if curCommand.x is None: - curCommand.x = currLocation['X'] + curCommand.x = currLocation["X"] if curCommand.y is None: - curCommand.y = currLocation['Y'] + curCommand.y = currLocation["Y"] if curCommand.z is None: - curCommand.z = currLocation['Z'] + curCommand.z = currLocation["Z"] # rapid retract triggers exit move, else just add to output if curCommand.Name in rapidcommands: @@ -348,7 +413,7 @@ class ObjectDressup: tempqueue = queue tempqueue.insert(0, curCommand) - if queue[1].Name in ['G01', 'G1']: + if queue[1].Name in ["G01", "G1"]: temp = self.lineExtension(obj, tempqueue) newpath.extend(temp[0]) lastxy = temp[0][-1].Placement.Base @@ -395,7 +460,7 @@ class ObjectDressup: # # DO THE EXTENSION # - if queue[1].Name in ['G01', 'G1']: + if queue[1].Name in ["G01", "G1"]: temp = self.lineExtension(obj, queue) newpath.extend(temp[0]) replace = temp[1] @@ -410,7 +475,7 @@ class ObjectDressup: # # DO THE TWIST # - if queue[0].Name in ['G01', 'G1']: + if queue[0].Name in ["G01", "G1"]: temp = self.lineTwist(obj, queue, lastxy, twistCW) replace = temp[1] newpath.extend(temp[0]) @@ -433,15 +498,22 @@ class ObjectDressup: class TaskPanel: - def __init__(self, obj): self.obj = obj self.form = FreeCADGui.PySideUic.loadUi(":/panels/DragKnifeEdit.ui") - self.filterAngle = PathGui.QuantitySpinBox(self.form.filterAngle, obj, 'filterAngle') - self.offsetDistance = PathGui.QuantitySpinBox(self.form.offsetDistance, obj, 'offset') - self.pivotHeight = PathGui.QuantitySpinBox(self.form.pivotHeight, obj, 'pivotheight') + self.filterAngle = PathGui.QuantitySpinBox( + self.form.filterAngle, obj, "filterAngle" + ) + self.offsetDistance = PathGui.QuantitySpinBox( + self.form.offsetDistance, obj, "offset" + ) + self.pivotHeight = PathGui.QuantitySpinBox( + self.form.pivotHeight, obj, "pivotheight" + ) - FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupDragKnife", "Edit Dragknife Dress-up")) + FreeCAD.ActiveDocument.openTransaction( + translate("Path_DressupDragKnife", "Edit Dragknife Dress-up") + ) def reject(self): FreeCAD.ActiveDocument.abortTransaction() @@ -483,7 +555,6 @@ class TaskPanel: class ViewProviderDressup: - def __init__(self, vobj): self.Object = vobj.Object @@ -537,9 +608,16 @@ class CommandDressupDragknife: # pylint: disable=no-init def GetResources(self): - return {'Pixmap': 'Path_Dressup', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_DressupDragKnife", "DragKnife Dress-up"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_DressupDragKnife", "Modifies a path to add dragknife corner actions")} + return { + "Pixmap": "Path_Dressup", + "MenuText": QtCore.QT_TRANSLATE_NOOP( + "Path_DressupDragKnife", "DragKnife Dress-up" + ), + "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "Path_DressupDragKnife", + "Modifies a path to add dragknife corner actions", + ), + } def IsActive(self): if FreeCAD.ActiveDocument is not None: @@ -554,33 +632,46 @@ class CommandDressupDragknife: selection = FreeCADGui.Selection.getSelection() if len(selection) != 1: FreeCAD.Console.PrintError( - translate("Path_DressupDragKnife", "Please select one path object")+"\n") + translate("Path_DressupDragKnife", "Please select one path object") + + "\n" + ) return if not selection[0].isDerivedFrom("Path::Feature"): FreeCAD.Console.PrintError( - translate("Path_DressupDragKnife", "The selected object is not a path")+"\n") + translate("Path_DressupDragKnife", "The selected object is not a path") + + "\n" + ) return if selection[0].isDerivedFrom("Path::FeatureCompoundPython"): FreeCAD.Console.PrintError( - translate("Path_DressupDragKnife", "Please select a Path object")) + translate("Path_DressupDragKnife", "Please select a Path object") + ) return # everything ok! - FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupDragKnife", "Create Dress-up")) + FreeCAD.ActiveDocument.openTransaction( + translate("Path_DressupDragKnife", "Create Dress-up") + ) FreeCADGui.addModule("PathScripts.PathDressupDragknife") FreeCADGui.addModule("PathScripts.PathUtils") - FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","DragknifeDressup")') - FreeCADGui.doCommand('PathScripts.PathDressupDragknife.ObjectDressup(obj)') - FreeCADGui.doCommand('base = FreeCAD.ActiveDocument.' + selection[0].Name) - FreeCADGui.doCommand('job = PathScripts.PathUtils.findParentJob(base)') - FreeCADGui.doCommand('obj.Base = base') - FreeCADGui.doCommand('job.Proxy.addOperation(obj, base)') - FreeCADGui.doCommand('obj.ViewObject.Proxy = PathScripts.PathDressupDragknife.ViewProviderDressup(obj.ViewObject)') - FreeCADGui.doCommand('Gui.ActiveDocument.getObject(base.Name).Visibility = False') - FreeCADGui.doCommand('obj.filterAngle = 20') - FreeCADGui.doCommand('obj.offset = 2') - FreeCADGui.doCommand('obj.pivotheight = 4') - FreeCADGui.doCommand('obj.ViewObject.Document.setEdit(obj.ViewObject, 0)') + FreeCADGui.doCommand( + 'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","DragknifeDressup")' + ) + FreeCADGui.doCommand("PathScripts.PathDressupDragknife.ObjectDressup(obj)") + FreeCADGui.doCommand("base = FreeCAD.ActiveDocument." + selection[0].Name) + FreeCADGui.doCommand("job = PathScripts.PathUtils.findParentJob(base)") + FreeCADGui.doCommand("obj.Base = base") + FreeCADGui.doCommand("job.Proxy.addOperation(obj, base)") + FreeCADGui.doCommand( + "obj.ViewObject.Proxy = PathScripts.PathDressupDragknife.ViewProviderDressup(obj.ViewObject)" + ) + FreeCADGui.doCommand( + "Gui.ActiveDocument.getObject(base.Name).Visibility = False" + ) + FreeCADGui.doCommand("obj.filterAngle = 20") + FreeCADGui.doCommand("obj.offset = 2") + FreeCADGui.doCommand("obj.pivotheight = 4") + FreeCADGui.doCommand("obj.ViewObject.Document.setEdit(obj.ViewObject, 0)") FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() @@ -588,6 +679,6 @@ class CommandDressupDragknife: if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_DressupDragKnife', CommandDressupDragknife()) + FreeCADGui.addCommand("Path_DressupDragKnife", CommandDressupDragknife()) FreeCAD.Console.PrintLog("Loading Path_DressupDragKnife... done\n") From 1a4ad44c7bcd3d4cd4fb90b75f6f9e2e26c5f857 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 22 Dec 2021 16:22:47 -0600 Subject: [PATCH 47/65] translation cleanup pathdressupdragknife --- .../Path/PathScripts/PathDressupDragknife.py | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupDragknife.py b/src/Mod/Path/PathScripts/PathDressupDragknife.py index 1f5e25e344..7542d4054b 100644 --- a/src/Mod/Path/PathScripts/PathDressupDragknife.py +++ b/src/Mod/Path/PathScripts/PathDressupDragknife.py @@ -27,6 +27,7 @@ from PySide import QtCore import math import PathScripts.PathUtils as PathUtils import PathScripts.PathGui as PathGui +from PySide.QtCore import QT_TRANSLATE_NOOP # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader @@ -39,9 +40,7 @@ if FreeCAD.GuiUp: import FreeCADGui -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +translate = FreeCAD.Qt.translate movecommands = ["G1", "G01", "G2", "G02", "G3", "G03"] @@ -57,13 +56,13 @@ class ObjectDressup: "App::PropertyLink", "Base", "Path", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The base path to modify"), + QT_TRANSLATE_NOOP("App::Property", "The base path to modify"), ) obj.addProperty( "App::PropertyAngle", "filterAngle", "Path", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Angles less than filter angle will not receive corner actions", ), @@ -72,7 +71,7 @@ class ObjectDressup: "App::PropertyFloat", "offset", "Path", - QtCore.QT_TRANSLATE_NOOP( + QT_TRANSLATE_NOOP( "App::Property", "Distance the point trails behind the spindle" ), ) @@ -80,9 +79,7 @@ class ObjectDressup: "App::PropertyFloat", "pivotheight", "Path", - QtCore.QT_TRANSLATE_NOOP( - "App::Property", "Height to raise during corner action" - ), + QT_TRANSLATE_NOOP("App::Property", "Height to raise during corner action"), ) obj.Proxy = self @@ -511,9 +508,7 @@ class TaskPanel: self.form.pivotHeight, obj, "pivotheight" ) - FreeCAD.ActiveDocument.openTransaction( - translate("Path_DressupDragKnife", "Edit Dragknife Dress-up") - ) + FreeCAD.ActiveDocument.openTransaction("Edit Dragknife Dress-up") def reject(self): FreeCAD.ActiveDocument.abortTransaction() @@ -610,10 +605,10 @@ class CommandDressupDragknife: def GetResources(self): return { "Pixmap": "Path_Dressup", - "MenuText": QtCore.QT_TRANSLATE_NOOP( + "MenuText": QT_TRANSLATE_NOOP( "Path_DressupDragKnife", "DragKnife Dress-up" ), - "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "ToolTip": QT_TRANSLATE_NOOP( "Path_DressupDragKnife", "Modifies a path to add dragknife corner actions", ), @@ -649,9 +644,7 @@ class CommandDressupDragknife: return # everything ok! - FreeCAD.ActiveDocument.openTransaction( - translate("Path_DressupDragKnife", "Create Dress-up") - ) + FreeCAD.ActiveDocument.openTransaction("Create Dress-up") FreeCADGui.addModule("PathScripts.PathDressupDragknife") FreeCADGui.addModule("PathScripts.PathUtils") FreeCADGui.doCommand( From ceb06c06f9e7ba34fe7ab5422a22d41f1f583a70 Mon Sep 17 00:00:00 2001 From: Uwe Date: Mon, 3 Jan 2022 22:51:50 +0100 Subject: [PATCH 48/65] [CMake] fix mis-matching arguments issue fixes this error: A logical block opening on the line cMake/FindCoin3D.cmake:12 (IF) closes on the line cMake/FindCoin3D.cmake:64 (ENDIF) with mis-matching arguments. --- cMake/FindCoin3D.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cMake/FindCoin3D.cmake b/cMake/FindCoin3D.cmake index 22b901f38b..8e14ef41f8 100644 --- a/cMake/FindCoin3D.cmake +++ b/cMake/FindCoin3D.cmake @@ -61,7 +61,7 @@ IF (WIN32) # SET (COIN3D_LIBRARIES coin2d CACHE STRING "Coin3D Library (Debug) - Open Inventor API") ENDIF (COIN3D_LIBRARIES) - ENDIF (CYGWIN) + ENDIF (CYGWIN OR MINGW) ELSE (WIN32) IF(APPLE) From a3023ed0b96c0ea6a45bfdf9c97d7f6c5afc3238 Mon Sep 17 00:00:00 2001 From: Uwe Date: Mon, 3 Jan 2022 23:30:43 +0100 Subject: [PATCH 49/65] [TD] some style unification automatically done by MSVC --- src/Mod/TechDraw/Gui/Command.cpp | 326 ++++++++++++------------ src/Mod/TechDraw/Gui/DlgPageChooser.cpp | 8 +- src/Mod/TechDraw/Gui/DrawGuiUtil.cpp | 113 ++++---- src/Mod/TechDraw/Gui/TaskCosVertex.cpp | 45 ++-- 4 files changed, 252 insertions(+), 240 deletions(-) diff --git a/src/Mod/TechDraw/Gui/Command.cpp b/src/Mod/TechDraw/Gui/Command.cpp index d540d9f0a4..b7c4b11368 100644 --- a/src/Mod/TechDraw/Gui/Command.cpp +++ b/src/Mod/TechDraw/Gui/Command.cpp @@ -135,11 +135,11 @@ void CmdTechDrawPageDefault::activated(int iMsg) if (tfi.isReadable()) { Gui::WaitCursor wc; openCommand(QT_TRANSLATE_NOOP("Command", "Drawing create page")); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawPage','%s')",PageName.c_str()); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawSVGTemplate','%s')",TemplateName.c_str()); + doCommand(Doc, "App.activeDocument().addObject('TechDraw::DrawPage','%s')", PageName.c_str()); + doCommand(Doc, "App.activeDocument().addObject('TechDraw::DrawSVGTemplate','%s')", TemplateName.c_str()); - doCommand(Doc,"App.activeDocument().%s.Template = '%s'",TemplateName.c_str(), templateFileName.toStdString().c_str()); - doCommand(Doc,"App.activeDocument().%s.Template = App.activeDocument().%s",PageName.c_str(),TemplateName.c_str()); + doCommand(Doc, "App.activeDocument().%s.Template = '%s'", TemplateName.c_str(), templateFileName.toStdString().c_str()); + doCommand(Doc, "App.activeDocument().%s.Template = App.activeDocument().%s", PageName.c_str(), TemplateName.c_str()); updateActive(); commitCommand(); @@ -154,9 +154,10 @@ void CmdTechDrawPageDefault::activated(int iMsg) dvp->show(); } else { - Base::Console().Log("INFO - Template: %s for Page: %s NOT Found\n", PageName.c_str(),TemplateName.c_str()); + Base::Console().Log("INFO - Template: %s for Page: %s NOT Found\n", PageName.c_str(), TemplateName.c_str()); } - } else { + } + else { QMessageBox::critical(Gui::getMainWindow(), QLatin1String("No template"), QLatin1String("No default template found")); @@ -208,17 +209,17 @@ void CmdTechDrawPageTemplate::activated(int iMsg) if (tfi.isReadable()) { Gui::WaitCursor wc; openCommand(QT_TRANSLATE_NOOP("Command", "Drawing create page")); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawPage','%s')",PageName.c_str()); + doCommand(Doc, "App.activeDocument().addObject('TechDraw::DrawPage','%s')", PageName.c_str()); // Create the Template Object to attach to the page - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawSVGTemplate','%s')",TemplateName.c_str()); + doCommand(Doc, "App.activeDocument().addObject('TechDraw::DrawSVGTemplate','%s')", TemplateName.c_str()); //why is "Template" property set twice? -wf // once to set DrawSVGTemplate.Template to OS template file name templateFileName = Base::Tools::escapeEncodeFilename(templateFileName); - doCommand(Doc,"App.activeDocument().%s.Template = \"%s\"",TemplateName.c_str(), templateFileName.toUtf8().constData()); + doCommand(Doc, "App.activeDocument().%s.Template = \"%s\"", TemplateName.c_str(), templateFileName.toUtf8().constData()); // once to set Page.Template to DrawSVGTemplate.Name - doCommand(Doc,"App.activeDocument().%s.Template = App.activeDocument().%s",PageName.c_str(),TemplateName.c_str()); + doCommand(Doc, "App.activeDocument().%s.Template = App.activeDocument().%s", PageName.c_str(), TemplateName.c_str()); // consider renaming DrawSVGTemplate.Template property? updateActive(); @@ -233,7 +234,7 @@ void CmdTechDrawPageTemplate::activated(int iMsg) dvp->show(); } else { - Base::Console().Log("INFO - Template: %s for Page: %s NOT Found\n", PageName.c_str(),TemplateName.c_str()); + Base::Console().Log("INFO - Template: %s for Page: %s NOT Found\n", PageName.c_str(), TemplateName.c_str()); } } else { @@ -281,7 +282,7 @@ void CmdTechDrawRedrawPage::activated(int iMsg) bool CmdTechDrawRedrawPage::isActive(void) { bool havePage = DrawGuiUtil::needPage(this); - bool haveView = DrawGuiUtil::needView(this,false); + bool haveView = DrawGuiUtil::needView(this, false); return (havePage && haveView); } @@ -327,19 +328,19 @@ void CmdTechDrawView::activated(int iMsg) for (auto& sel: selection) { bool is_linked = false; auto obj = sel.getObject(); - if (obj->isDerivedFrom(TechDraw::DrawPage::getClassTypeId()) ) { + if (obj->isDerivedFrom(TechDraw::DrawPage::getClassTypeId())) { continue; } - if ( obj->isDerivedFrom(App::LinkElement::getClassTypeId()) || - obj->isDerivedFrom(App::LinkGroup::getClassTypeId()) || - obj->isDerivedFrom(App::Link::getClassTypeId()) ) { + if (obj->isDerivedFrom(App::LinkElement::getClassTypeId()) || + obj->isDerivedFrom(App::LinkGroup::getClassTypeId()) || + obj->isDerivedFrom(App::Link::getClassTypeId()) ) { is_linked = true; } // If parent of the obj is a link to another document, we possibly need to treat non-link obj as linked, too // 1st, is obj in another document? if (obj->getDocument() != this->getDocument()) { - std::set parents = obj->getInListEx(true); - for (auto &parent: parents) { + std::set parents = obj->getInListEx(true); + for (auto& parent : parents) { // Only consider parents in the current document, i.e. possible links in this View's document if (parent->getDocument() != this->getDocument()) { continue; @@ -360,11 +361,11 @@ void CmdTechDrawView::activated(int iMsg) //not a Link and not null. assume to be drawable. Undrawables will be // skipped later. shapes.push_back(obj); - if(partObj != nullptr) { + if (partObj != nullptr) { continue; } //don't know if this works for an XLink - for(auto& sub : sel.getSubNames()) { + for (auto& sub : sel.getSubNames()) { if (TechDraw::DrawUtil::getGeomTypeFromName(sub) == "Face") { faceName = sub; // @@ -374,8 +375,8 @@ void CmdTechDrawView::activated(int iMsg) } } - if ( shapes.empty() && - xShapes.empty() ) { + if (shapes.empty() && + xShapes.empty()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("No Shapes, Groups or Links in this selection")); return; @@ -386,39 +387,40 @@ void CmdTechDrawView::activated(int iMsg) Gui::WaitCursor wc; openCommand(QT_TRANSLATE_NOOP("Command", "Create view")); std::string FeatName = getUniqueObjectName("View"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewPart','%s')",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); + doCommand(Doc, "App.activeDocument().addObject('TechDraw::DrawViewPart','%s')", FeatName.c_str()); + doCommand(Doc, "App.activeDocument().%s.addView(App.activeDocument().%s)", PageName.c_str(), FeatName.c_str()); - App::DocumentObject *docObj = getDocument()->getObject(FeatName.c_str()); - TechDraw::DrawViewPart* dvp = dynamic_cast(docObj); + App::DocumentObject* docObj = getDocument()->getObject(FeatName.c_str()); + TechDraw::DrawViewPart* dvp = dynamic_cast(docObj); if (!dvp) { throw Base::TypeError("CmdTechDrawView DVP not found\n"); } dvp->Source.setValues(shapes); dvp->XSource.setValues(xShapes); if (faceName.size()) { - std::pair dirs = DrawGuiUtil::getProjDirFromFace(partObj,faceName); + std::pair dirs = DrawGuiUtil::getProjDirFromFace(partObj, faceName); projDir = dirs.first; getDocument()->setStatus(App::Document::Status::SkipRecompute, true); - doCommand(Doc,"App.activeDocument().%s.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", - FeatName.c_str(), projDir.x,projDir.y,projDir.z); + doCommand(Doc, "App.activeDocument().%s.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", + FeatName.c_str(), projDir.x, projDir.y, projDir.z); //do something clever with dirs.second; // dvp->setXDir(dirs.second); - doCommand(Doc,"App.activeDocument().%s.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", - FeatName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); - doCommand(Doc,"App.activeDocument().%s.recompute()", FeatName.c_str()); + doCommand(Doc, "App.activeDocument().%s.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", + FeatName.c_str(), dirs.second.x, dirs.second.y, dirs.second.z); + doCommand(Doc, "App.activeDocument().%s.recompute()", FeatName.c_str()); getDocument()->setStatus(App::Document::Status::SkipRecompute, false); - } else { - std::pair dirs = DrawGuiUtil::get3DDirAndRot(); + } + else { + std::pair dirs = DrawGuiUtil::get3DDirAndRot(); projDir = dirs.first; getDocument()->setStatus(App::Document::Status::SkipRecompute, true); - doCommand(Doc,"App.activeDocument().%s.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", - FeatName.c_str(), projDir.x,projDir.y,projDir.z); - doCommand(Doc,"App.activeDocument().%s.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", - FeatName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); -// dvp->setXDir(dirs.second); + doCommand(Doc, "App.activeDocument().%s.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", + FeatName.c_str(), projDir.x, projDir.y, projDir.z); + doCommand(Doc, "App.activeDocument().%s.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", + FeatName.c_str(), dirs.second.x, dirs.second.y, dirs.second.z); + // dvp->setXDir(dirs.second); getDocument()->setStatus(App::Document::Status::SkipRecompute, false); - doCommand(Doc,"App.activeDocument().%s.recompute()", FeatName.c_str()); + doCommand(Doc, "App.activeDocument().%s.recompute()", FeatName.c_str()); } commitCommand(); } @@ -538,7 +540,7 @@ void CmdTechDrawDetailView::activated(int iMsg) return; } - std::vector baseObj = getSelection(). + std::vector baseObj = getSelection(). getObjectsOfType(TechDraw::DrawViewPart::getClassTypeId()); if (baseObj.empty()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), @@ -605,19 +607,19 @@ void CmdTechDrawProjectionGroup::activated(int iMsg) for (auto& sel: selection) { bool is_linked = false; auto obj = sel.getObject(); - if (obj->isDerivedFrom(TechDraw::DrawPage::getClassTypeId()) ) { + if (obj->isDerivedFrom(TechDraw::DrawPage::getClassTypeId())) { continue; } - if ( obj->isDerivedFrom(App::LinkElement::getClassTypeId()) || - obj->isDerivedFrom(App::LinkGroup::getClassTypeId()) || - obj->isDerivedFrom(App::Link::getClassTypeId()) ) { + if (obj->isDerivedFrom(App::LinkElement::getClassTypeId()) || + obj->isDerivedFrom(App::LinkGroup::getClassTypeId()) || + obj->isDerivedFrom(App::Link::getClassTypeId()) ) { is_linked = true; } // If parent of the obj is a link to another document, we possibly need to treat non-link obj as linked, too // 1st, is obj in another document? if (obj->getDocument() != this->getDocument()) { - std::set parents = obj->getInListEx(true); - for (auto &parent: parents) { + std::set parents = obj->getInListEx(true); + for (auto& parent : parents) { // Only consider parents in the current document, i.e. possible links in this View's document if (parent->getDocument() != this->getDocument()) { continue; @@ -638,10 +640,10 @@ void CmdTechDrawProjectionGroup::activated(int iMsg) //not a Link and not null. assume to be drawable. Undrawables will be // skipped later. shapes.push_back(obj); - if(partObj != nullptr) { + if (partObj != nullptr) { continue; } - for(auto& sub : sel.getSubNames()) { + for (auto& sub : sel.getSubNames()) { if (TechDraw::DrawUtil::getGeomTypeFromName(sub) == "Face") { faceName = sub; partObj = obj; @@ -649,8 +651,8 @@ void CmdTechDrawProjectionGroup::activated(int iMsg) } } } - if ( shapes.empty() && - xShapes.empty() ) { + if (shapes.empty() && + xShapes.empty()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("No Shapes, Groups or Links in this selection")); return; @@ -662,45 +664,46 @@ void CmdTechDrawProjectionGroup::activated(int iMsg) openCommand(QT_TRANSLATE_NOOP("Command", "Create Projection Group")); std::string multiViewName = getUniqueObjectName("ProjGroup"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawProjGroup','%s')", + doCommand(Doc, "App.activeDocument().addObject('TechDraw::DrawProjGroup','%s')", multiViewName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)", - PageName.c_str(),multiViewName.c_str()); + doCommand(Doc, "App.activeDocument().%s.addView(App.activeDocument().%s)", + PageName.c_str(), multiViewName.c_str()); - App::DocumentObject *docObj = getDocument()->getObject(multiViewName.c_str()); - auto multiView( static_cast(docObj) ); + App::DocumentObject* docObj = getDocument()->getObject(multiViewName.c_str()); + auto multiView(static_cast(docObj)); multiView->Source.setValues(shapes); multiView->XSource.setValues(xShapes); - doCommand(Doc,"App.activeDocument().%s.addProjection('Front')",multiViewName.c_str()); + doCommand(Doc, "App.activeDocument().%s.addProjection('Front')", multiViewName.c_str()); if (faceName.size()) { - std::pair dirs = DrawGuiUtil::getProjDirFromFace(partObj,faceName); + std::pair dirs = DrawGuiUtil::getProjDirFromFace(partObj, faceName); getDocument()->setStatus(App::Document::Status::SkipRecompute, true); - doCommand(Doc,"App.activeDocument().%s.Anchor.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", - multiViewName.c_str(), dirs.first.x,dirs.first.y,dirs.first.z); - doCommand(Doc,"App.activeDocument().%s.Anchor.RotationVector = FreeCAD.Vector(%.3f,%.3f,%.3f)", - multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); - doCommand(Doc,"App.activeDocument().%s.Anchor.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", - multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); - getDocument()->setStatus(App::Document::Status::SkipRecompute, false); - } else { - std::pair dirs = DrawGuiUtil::get3DDirAndRot(); - getDocument()->setStatus(App::Document::Status::SkipRecompute, true); - doCommand(Doc,"App.activeDocument().%s.Anchor.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", - multiViewName.c_str(), dirs.first.x,dirs.first.y,dirs.first.z); - doCommand(Doc,"App.activeDocument().%s.Anchor.RotationVector = FreeCAD.Vector(%.3f,%.3f,%.3f)", - multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); - doCommand(Doc,"App.activeDocument().%s.Anchor.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", - multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); + doCommand(Doc, "App.activeDocument().%s.Anchor.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", + multiViewName.c_str(), dirs.first.x, dirs.first.y, dirs.first.z); + doCommand(Doc, "App.activeDocument().%s.Anchor.RotationVector = FreeCAD.Vector(%.3f,%.3f,%.3f)", + multiViewName.c_str(), dirs.second.x, dirs.second.y, dirs.second.z); + doCommand(Doc, "App.activeDocument().%s.Anchor.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", + multiViewName.c_str(), dirs.second.x, dirs.second.y, dirs.second.z); getDocument()->setStatus(App::Document::Status::SkipRecompute, false); } - - doCommand(Doc,"App.activeDocument().%s.Anchor.recompute()", multiViewName.c_str()); + else { + std::pair dirs = DrawGuiUtil::get3DDirAndRot(); + getDocument()->setStatus(App::Document::Status::SkipRecompute, true); + doCommand(Doc, "App.activeDocument().%s.Anchor.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", + multiViewName.c_str(), dirs.first.x, dirs.first.y, dirs.first.z); + doCommand(Doc, "App.activeDocument().%s.Anchor.RotationVector = FreeCAD.Vector(%.3f,%.3f,%.3f)", + multiViewName.c_str(), dirs.second.x, dirs.second.y, dirs.second.z); + doCommand(Doc, "App.activeDocument().%s.Anchor.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", + multiViewName.c_str(), dirs.second.x, dirs.second.y, dirs.second.z); + getDocument()->setStatus(App::Document::Status::SkipRecompute, false); + } + + doCommand(Doc, "App.activeDocument().%s.Anchor.recompute()", multiViewName.c_str()); commitCommand(); updateActive(); // create the rest of the desired views - Gui::Control().showDialog(new TaskDlgProjGroup(multiView,true)); + Gui::Control().showDialog(new TaskDlgProjGroup(multiView, true)); } bool CmdTechDrawProjectionGroup::isActive(void) @@ -710,7 +713,7 @@ bool CmdTechDrawProjectionGroup::isActive(void) if (havePage) { taskInProgress = Gui::Control().activeDialog(); } - return (havePage && !taskInProgress); + return (havePage && !taskInProgress); } //=========================================================================== @@ -781,14 +784,14 @@ bool _checkSelectionBalloon(Gui::Command* cmd, unsigned maxObjs) { } const std::vector SubNames = selection[0].getSubNames(); - if (SubNames.size() > maxObjs){ + if (SubNames.size() > maxObjs) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Incorrect selection"), QObject::tr("Too many objects selected")); return false; } std::vector pages = cmd->getDocument()->getObjectsOfType(TechDraw::DrawPage::getClassTypeId()); - if (pages.empty()){ + if (pages.empty()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Incorrect selection"), QObject::tr("Create a page first.")); return false; @@ -798,17 +801,17 @@ bool _checkSelectionBalloon(Gui::Command* cmd, unsigned maxObjs) { bool _checkDrawViewPartBalloon(Gui::Command* cmd) { std::vector selection = cmd->getSelection().getSelectionEx(); - auto objFeat( dynamic_cast(selection[0].getObject()) ); - if( !objFeat ) { - QMessageBox::warning( Gui::getMainWindow(), - QObject::tr("Incorrect selection"), - QObject::tr("No View of a Part in selection.") ); + auto objFeat(dynamic_cast(selection[0].getObject())); + if (!objFeat) { + QMessageBox::warning(Gui::getMainWindow(), + QObject::tr("Incorrect selection"), + QObject::tr("No View of a Part in selection.") ); return false; } return true; } -bool _checkDirectPlacement(const QGIViewPart *viewPart, const std::vector &subNames, QPointF &placement) +bool _checkDirectPlacement(const QGIViewPart* viewPart, const std::vector& subNames, QPointF& placement) { // Let's see, if we can help speed up the placement of the balloon: // As of now we support: @@ -825,7 +828,7 @@ bool _checkDirectPlacement(const QGIViewPart *viewPart, const std::vector(viewPart->getViewObject())->getProjVertexByIndex(index); + TechDraw::VertexPtr vertex = static_cast(viewPart->getViewObject())->getProjVertexByIndex(index); if (vertex) { placement = viewPart->mapToScene(Rez::guiX(vertex->x()), Rez::guiX(vertex->y())); return true; @@ -833,7 +836,7 @@ bool _checkDirectPlacement(const QGIViewPart *viewPart, const std::vector(viewPart->getViewObject())->getGeomByIndex(index); + TechDraw::BaseGeom* geo = static_cast(viewPart->getViewObject())->getGeomByIndex(index); if (geo) { Base::Vector3d midPoint(Rez::guiX(geo->getMidPoint())); placement = viewPart->mapToScene(midPoint.x, midPoint.y); @@ -861,7 +864,7 @@ CmdTechDrawBalloon::CmdTechDrawBalloon() void CmdTechDrawBalloon::activated(int iMsg) { Q_UNUSED(iMsg); - bool result = _checkSelectionBalloon(this,1); + bool result = _checkSelectionBalloon(this, 1); if (!result) return; result = _checkDrawViewPartBalloon(this); @@ -870,8 +873,8 @@ void CmdTechDrawBalloon::activated(int iMsg) std::vector selection = getSelection().getSelectionEx(); - auto objFeat( dynamic_cast(selection[0].getObject()) ); - if( objFeat == nullptr ) { + auto objFeat(dynamic_cast(selection[0].getObject())); + if (objFeat == nullptr) { return; } @@ -880,16 +883,16 @@ void CmdTechDrawBalloon::activated(int iMsg) page->balloonParent = objFeat; - Gui::Document *guiDoc = Gui::Application::Instance->getDocument(page->getDocument()); - ViewProviderPage *pageVP = dynamic_cast(guiDoc->getViewProvider(page)); - ViewProviderViewPart *partVP = dynamic_cast(guiDoc->getViewProvider(objFeat)); + Gui::Document* guiDoc = Gui::Application::Instance->getDocument(page->getDocument()); + ViewProviderPage* pageVP = dynamic_cast(guiDoc->getViewProvider(page)); + ViewProviderViewPart* partVP = dynamic_cast(guiDoc->getViewProvider(objFeat)); if (pageVP && partVP) { - QGVPage *viewPage = pageVP->getGraphicsView(); + QGVPage* viewPage = pageVP->getGraphicsView(); if (viewPage) { viewPage->startBalloonPlacing(); - QGIViewPart *viewPart = dynamic_cast(partVP->getQView()); + QGIViewPart* viewPart = dynamic_cast(partVP->getQView()); QPointF placement; if (viewPart && _checkDirectPlacement(viewPart, selection[0].getSubNames(), placement)) { viewPage->createBalloon(placement, objFeat); @@ -934,8 +937,8 @@ void CmdTechDrawClipGroup::activated(int iMsg) std::string FeatName = getUniqueObjectName("Clip"); openCommand(QT_TRANSLATE_NOOP("Command", "Create Clip")); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewClip','%s')",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); + doCommand(Doc, "App.activeDocument().addObject('TechDraw::DrawViewClip','%s')", FeatName.c_str()); + doCommand(Doc, "App.activeDocument().%s.addView(App.activeDocument().%s)", PageName.c_str(), FeatName.c_str()); updateActive(); commitCommand(); } @@ -965,20 +968,21 @@ CmdTechDrawClipGroupAdd::CmdTechDrawClipGroupAdd() void CmdTechDrawClipGroupAdd::activated(int iMsg) { Q_UNUSED(iMsg); - std::vector selection = getSelection().getSelectionEx(); - if (selection.size() != 2) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select one Clip group and one View.")); - return; - } + std::vector selection = getSelection().getSelectionEx(); + if (selection.size() != 2) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Select one Clip group and one View.")); + return; + } TechDraw::DrawViewClip* clip = 0; TechDraw::DrawView* view = 0; std::vector::iterator itSel = selection.begin(); - for (; itSel != selection.end(); itSel++) { + for (; itSel != selection.end(); itSel++) { if ((*itSel).getObject()->isDerivedFrom(TechDraw::DrawViewClip::getClassTypeId())) { clip = static_cast((*itSel).getObject()); - } else if ((*itSel).getObject()->isDerivedFrom(TechDraw::DrawView::getClassTypeId())) { + } + else if ((*itSel).getObject()->isDerivedFrom(TechDraw::DrawView::getClassTypeId())) { view = static_cast((*itSel).getObject()); } } @@ -1007,9 +1011,9 @@ void CmdTechDrawClipGroupAdd::activated(int iMsg) std::string ViewName = view->getNameInDocument(); openCommand(QT_TRANSLATE_NOOP("Command", "ClipGroupAdd")); - doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = False",ViewName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",ClipName.c_str(),ViewName.c_str()); - doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = True",ViewName.c_str()); + doCommand(Doc, "App.activeDocument().%s.ViewObject.Visibility = False", ViewName.c_str()); + doCommand(Doc, "App.activeDocument().%s.addView(App.activeDocument().%s)", ClipName.c_str(), ViewName.c_str()); + doCommand(Doc, "App.activeDocument().%s.ViewObject.Visibility = True", ViewName.c_str()); updateActive(); commitCommand(); } @@ -1019,7 +1023,7 @@ bool CmdTechDrawClipGroupAdd::isActive(void) bool havePage = DrawGuiUtil::needPage(this); bool haveClip = false; if (havePage) { - auto drawClipType( TechDraw::DrawViewClip::getClassTypeId() ); + auto drawClipType(TechDraw::DrawViewClip::getClassTypeId()); auto selClips = getDocument()->getObjectsOfType(drawClipType); if (!selClips.empty()) { haveClip = true; @@ -1048,20 +1052,20 @@ CmdTechDrawClipGroupRemove::CmdTechDrawClipGroupRemove() void CmdTechDrawClipGroupRemove::activated(int iMsg) { Q_UNUSED(iMsg); - auto dObj( getSelection().getObjectsOfType(TechDraw::DrawView::getClassTypeId()) ); + auto dObj(getSelection().getObjectsOfType(TechDraw::DrawView::getClassTypeId())); if (dObj.empty()) { - QMessageBox::warning( Gui::getMainWindow(), - QObject::tr("Wrong selection"), - QObject::tr("Select exactly one View to remove from Group.") ); + QMessageBox::warning(Gui::getMainWindow(), + QObject::tr("Wrong selection"), + QObject::tr("Select exactly one View to remove from Group.") ); return; } - auto view( static_cast(dObj.front()) ); + auto view(static_cast(dObj.front())); TechDraw::DrawPage* page = view->findParentPage(); const std::vector pViews = page->Views.getValues(); - TechDraw::DrawViewClip *clip(nullptr); - for (auto &v : pViews) { + TechDraw::DrawViewClip* clip(nullptr); + for (auto& v : pViews) { clip = dynamic_cast(v); if (clip && clip->isViewInClip(view)) { break; @@ -1070,9 +1074,9 @@ void CmdTechDrawClipGroupRemove::activated(int iMsg) } if (!clip) { - QMessageBox::warning( Gui::getMainWindow(), - QObject::tr("Wrong selection"), - QObject::tr("View does not belong to a Clip") ); + QMessageBox::warning(Gui::getMainWindow(), + QObject::tr("Wrong selection"), + QObject::tr("View does not belong to a Clip") ); return; } @@ -1080,9 +1084,9 @@ void CmdTechDrawClipGroupRemove::activated(int iMsg) std::string ViewName = view->getNameInDocument(); openCommand(QT_TRANSLATE_NOOP("Command", "ClipGroupRemove")); - doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = False",ViewName.c_str()); - doCommand(Doc,"App.activeDocument().%s.removeView(App.activeDocument().%s)",ClipName.c_str(),ViewName.c_str()); - doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = True",ViewName.c_str()); + doCommand(Doc, "App.activeDocument().%s.ViewObject.Visibility = False", ViewName.c_str()); + doCommand(Doc, "App.activeDocument().%s.removeView(App.activeDocument().%s)", ClipName.c_str(), ViewName.c_str()); + doCommand(Doc, "App.activeDocument().%s.ViewObject.Visibility = True", ViewName.c_str()); updateActive(); commitCommand(); } @@ -1092,7 +1096,7 @@ bool CmdTechDrawClipGroupRemove::isActive(void) bool havePage = DrawGuiUtil::needPage(this); bool haveClip = false; if (havePage) { - auto drawClipType( TechDraw::DrawViewClip::getClassTypeId() ); + auto drawClipType(TechDraw::DrawViewClip::getClassTypeId()); auto selClips = getDocument()->getObjectsOfType(drawClipType); if (!selClips.empty()) { haveClip = true; @@ -1130,7 +1134,7 @@ void CmdTechDrawSymbol::activated(int iMsg) std::string PageName = page->getNameInDocument(); // Reading an image - QString filename = Gui::FileDialog::getOpenFileName(Gui::getMainWindow(), + QString filename = Gui::FileDialog::getOpenFileName(Gui::getMainWindow(), QObject::tr("Choose an SVG file to open"), QString(), QString::fromLatin1("%1 (*.svg *.svgz);;%2 (*.*)"). arg(QObject::tr("Scalable Vector Graphic")). @@ -1141,12 +1145,12 @@ void CmdTechDrawSymbol::activated(int iMsg) std::string FeatName = getUniqueObjectName("Symbol"); filename = Base::Tools::escapeEncodeFilename(filename); openCommand(QT_TRANSLATE_NOOP("Command", "Create Symbol")); - doCommand(Doc,"f = open(\"%s\",'r')",(const char*)filename.toUtf8()); - doCommand(Doc,"svg = f.read()"); - doCommand(Doc,"f.close()"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewSymbol','%s')",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.Symbol = svg",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); + doCommand(Doc, "f = open(\"%s\",'r')", (const char*)filename.toUtf8()); + doCommand(Doc, "svg = f.read()"); + doCommand(Doc, "f.close()"); + doCommand(Doc, "App.activeDocument().addObject('TechDraw::DrawViewSymbol','%s')", FeatName.c_str()); + doCommand(Doc, "App.activeDocument().%s.Symbol = svg", FeatName.c_str()); + doCommand(Doc, "App.activeDocument().%s.addView(App.activeDocument().%s)", PageName.c_str(), FeatName.c_str()); updateActive(); commitCommand(); } @@ -1193,26 +1197,26 @@ void CmdTechDrawDraftView::activated(int iMsg) return; } - std::pair dirs = DrawGuiUtil::get3DDirAndRot(); + std::pair dirs = DrawGuiUtil::get3DDirAndRot(); int draftItemsFound = 0; for (std::vector::iterator it = objects.begin(); it != objects.end(); ++it) { - if (DrawGuiUtil::isDraftObject((*it))) { + if (DrawGuiUtil::isDraftObject((*it))) { draftItemsFound++; std::string FeatName = getUniqueObjectName("DraftView"); std::string SourceName = (*it)->getNameInDocument(); openCommand(QT_TRANSLATE_NOOP("Command", "Create DraftView")); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewDraft','%s')",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.Source = App.activeDocument().%s", - FeatName.c_str(),SourceName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)", - PageName.c_str(),FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", + doCommand(Doc, "App.activeDocument().addObject('TechDraw::DrawViewDraft','%s')", FeatName.c_str()); + doCommand(Doc, "App.activeDocument().%s.Source = App.activeDocument().%s", + FeatName.c_str(), SourceName.c_str()); + doCommand(Doc, "App.activeDocument().%s.addView(App.activeDocument().%s)", + PageName.c_str(), FeatName.c_str()); + doCommand(Doc, "App.activeDocument().%s.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", FeatName.c_str(), dirs.first.x, dirs.first.y, dirs.first.z); updateActive(); commitCommand(); } } - if (draftItemsFound == 0) { + if (draftItemsFound == 0) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("There were no DraftWB objects in the selection.")); } @@ -1247,21 +1251,21 @@ void CmdTechDrawArchView::activated(int iMsg) TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); if (!page) { return; - } + } std::string PageName = page->getNameInDocument(); - const std::vector objects = getSelection(). - getObjectsOfType(App::DocumentObject::getClassTypeId()); + const std::vector objects = getSelection(). + getObjectsOfType(App::DocumentObject::getClassTypeId()); App::DocumentObject* archObject = nullptr; int archCount = 0; for (auto& obj : objects) { - if (DrawGuiUtil::isArchSection(obj) ) { + if (DrawGuiUtil::isArchSection(obj)) { archCount++; archObject = obj; } } - if ( archCount > 1 ) { + if (archCount > 1) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Please select only 1 Arch Section.")); return; @@ -1276,9 +1280,9 @@ void CmdTechDrawArchView::activated(int iMsg) std::string FeatName = getUniqueObjectName("ArchView"); std::string SourceName = archObject->getNameInDocument(); openCommand(QT_TRANSLATE_NOOP("Command", "Create ArchView")); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewArch','%s')",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.Source = App.activeDocument().%s",FeatName.c_str(),SourceName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); + doCommand(Doc, "App.activeDocument().addObject('TechDraw::DrawViewArch','%s')", FeatName.c_str()); + doCommand(Doc, "App.activeDocument().%s.Source = App.activeDocument().%s", FeatName.c_str(), SourceName.c_str()); + doCommand(Doc, "App.activeDocument().%s.addView(App.activeDocument().%s)", PageName.c_str(), FeatName.c_str()); updateActive(); commitCommand(); } @@ -1325,9 +1329,9 @@ void CmdTechDrawSpreadsheetView::activated(int iMsg) openCommand(QT_TRANSLATE_NOOP("Command", "Create spreadsheet view")); std::string FeatName = getUniqueObjectName("Sheet"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewSpreadsheet','%s')",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.Source = App.activeDocument().%s",FeatName.c_str(),SpreadName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); + doCommand(Doc, "App.activeDocument().addObject('TechDraw::DrawViewSpreadsheet','%s')", FeatName.c_str()); + doCommand(Doc, "App.activeDocument().%s.Source = App.activeDocument().%s", FeatName.c_str(), SpreadName.c_str()); + doCommand(Doc, "App.activeDocument().%s.addView(App.activeDocument().%s)", PageName.c_str(), FeatName.c_str()); updateActive(); commitCommand(); } @@ -1338,7 +1342,7 @@ bool CmdTechDrawSpreadsheetView::isActive(void) bool havePage = DrawGuiUtil::needPage(this); bool haveSheet = false; if (havePage) { - auto spreadSheetType( Spreadsheet::Sheet::getClassTypeId() ); + auto spreadSheetType(Spreadsheet::Sheet::getClassTypeId()); auto selSheets = getDocument()->getObjectsOfType(spreadSheetType); if (!selSheets.empty()) { haveSheet = true; @@ -1378,9 +1382,10 @@ void CmdTechDrawExportPageSVG::activated(int iMsg) Gui::ViewProvider* vp = activeGui->getViewProvider(page); ViewProviderPage* dvp = dynamic_cast(vp); - if (dvp && dvp->getMDIViewPage()) { + if (dvp && dvp->getMDIViewPage()) { dvp->getMDIViewPage()->saveSVG(); - } else { + } + else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("No Drawing View"), QObject::tr("Open Drawing View before attempting export to SVG.")); return; @@ -1418,15 +1423,16 @@ void CmdTechDrawExportPageDXF::activated(int iMsg) } std::vector views = page->Views.getValues(); - for (auto& v: views) { + for (auto& v : views) { if (v->isDerivedFrom(TechDraw::DrawViewArch::getClassTypeId())) { QMessageBox::StandardButton rc = QMessageBox::question(Gui::getMainWindow(), QObject::tr("Can not export selection"), QObject::tr("Page contains DrawViewArch which will not be exported. Continue?"), - QMessageBox::StandardButtons(QMessageBox::Yes| QMessageBox::No)); + QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No)); if (rc == QMessageBox::No) { return; - } else { + } + else { break; } } @@ -1445,9 +1451,9 @@ void CmdTechDrawExportPageDXF::activated(int iMsg) std::string PageName = page->getNameInDocument(); openCommand(QT_TRANSLATE_NOOP("Command", "Save page to dxf")); - doCommand(Doc,"import TechDraw"); + doCommand(Doc, "import TechDraw"); fileName = Base::Tools::escapeEncodeFilename(fileName); - doCommand(Doc,"TechDraw.writeDXFPage(App.activeDocument().%s,u\"%s\")",PageName.c_str(),(const char*)fileName.toUtf8()); + doCommand(Doc, "TechDraw.writeDXFPage(App.activeDocument().%s,u\"%s\")", PageName.c_str(), (const char*)fileName.toUtf8()); commitCommand(); } @@ -1459,7 +1465,7 @@ bool CmdTechDrawExportPageDXF::isActive(void) void CreateTechDrawCommands(void) { - Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); + Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager(); rcCmdMgr.addCommand(new CmdTechDrawPageDefault()); rcCmdMgr.addCommand(new CmdTechDrawPageTemplate()); diff --git a/src/Mod/TechDraw/Gui/DlgPageChooser.cpp b/src/Mod/TechDraw/Gui/DlgPageChooser.cpp index b8373e1f86..f54780d1ae 100644 --- a/src/Mod/TechDraw/Gui/DlgPageChooser.cpp +++ b/src/Mod/TechDraw/Gui/DlgPageChooser.cpp @@ -33,7 +33,7 @@ #include "DlgPageChooser.h" #include "ui_DlgPageChooser.h" -FC_LOG_LEVEL_INIT("Gui",true,true) +FC_LOG_LEVEL_INIT("Gui", true, true) using namespace TechDrawGui; @@ -47,7 +47,7 @@ DlgPageChooser::DlgPageChooser( { ui->setupUi(this); ui->lwPages->setSortingEnabled(true); - + fillList(labels, names); connect(ui->bbButtons, SIGNAL(accepted()), this, SLOT(accept())); @@ -71,7 +71,7 @@ void DlgPageChooser::fillList(std::vector labels, std::vector labels, std::vector sels = ui->lwPages->selectedItems(); diff --git a/src/Mod/TechDraw/Gui/DrawGuiUtil.cpp b/src/Mod/TechDraw/Gui/DrawGuiUtil.cpp index 3ff5097b44..bac8e73f49 100644 --- a/src/Mod/TechDraw/Gui/DrawGuiUtil.cpp +++ b/src/Mod/TechDraw/Gui/DrawGuiUtil.cpp @@ -118,7 +118,8 @@ TechDraw::DrawPage* DrawGuiUtil::findPage(Gui::Command* cmd) //no page in document QMessageBox::warning(Gui::getMainWindow(), QObject::tr("No page found"), QObject::tr("No Drawing Pages in document.")); - } else if (selPages.size() > 1) { + } + else if (selPages.size() > 1) { //multiple pages in document, but none selected //use active page if there is one Gui::MainWindow* w = Gui::getMainWindow(); @@ -128,40 +129,44 @@ TechDraw::DrawPage* DrawGuiUtil::findPage(Gui::Command* cmd) QString windowTitle = mvp->windowTitle(); QGVPage* qp = mvp->getQGVPage(); page = qp->getDrawPage(); - } else { + } + else { // no active page - for(auto obj: selPages) { + for (auto obj : selPages) { std::string name = obj->getNameInDocument(); names.push_back(name); std::string label = obj->Label.getValue(); labels.push_back(label); } DlgPageChooser dlg(labels, names, Gui::getMainWindow()); - if(dlg.exec()==QDialog::Accepted) { + if (dlg.exec() == QDialog::Accepted) { std::string selName = dlg.getSelection(); - App::Document* doc = cmd->getDocument(); + App::Document* doc = cmd->getDocument(); page = static_cast(doc->getObject(selName.c_str())); } } - } else { + } + else { //only 1 page in document - use it page = static_cast(selPages.front()); } - } else if (selPages.size() > 1) { + } + else if (selPages.size() > 1) { //multiple pages in selection - for(auto obj: selPages) { + for (auto obj : selPages) { std::string name = obj->getNameInDocument(); names.push_back(name); std::string label = obj->Label.getValue(); labels.push_back(label); } DlgPageChooser dlg(labels, names, Gui::getMainWindow()); - if(dlg.exec()==QDialog::Accepted) { + if (dlg.exec() == QDialog::Accepted) { std::string selName = dlg.getSelection(); - App::Document* doc = cmd->getDocument(); + App::Document* doc = cmd->getDocument(); page = static_cast(doc->getObject(selName.c_str())); } - } else { + } + else { //exactly 1 page in selection, use it page = static_cast(selPages.front()); } @@ -171,7 +176,7 @@ TechDraw::DrawPage* DrawGuiUtil::findPage(Gui::Command* cmd) bool DrawGuiUtil::isDraftObject(App::DocumentObject* obj) { - bool result = false; + bool result = false; App::PropertyPythonObject* proxy = dynamic_cast(obj->getPropertyByName("Proxy")); if (proxy != nullptr) { @@ -183,10 +188,11 @@ bool DrawGuiUtil::isDraftObject(App::DocumentObject* obj) try { if (proxyObj.hasAttr("__module__")) { Py::String mod(proxyObj.getAttr("__module__")); - ss << (std::string)mod; + ss << (std::string)mod; if (ss.str().find("Draft") != std::string::npos) { result = true; - } else if (ss.str().find("draft") != std::string::npos) { + } + else if (ss.str().find("draft") != std::string::npos) { result = true; } } @@ -202,7 +208,7 @@ bool DrawGuiUtil::isDraftObject(App::DocumentObject* obj) bool DrawGuiUtil::isArchObject(App::DocumentObject* obj) { - bool result = false; + bool result = false; App::PropertyPythonObject* proxy = dynamic_cast(obj->getPropertyByName("Proxy")); if (proxy != nullptr) { @@ -214,7 +220,7 @@ bool DrawGuiUtil::isArchObject(App::DocumentObject* obj) try { if (proxyObj.hasAttr("__module__")) { Py::String mod(proxyObj.getAttr("__module__")); - ss << (std::string)mod; + ss << (std::string)mod; //does this have to be an ArchSection, or can it be any Arch object? if (ss.str().find("Arch") != std::string::npos) { result = true; @@ -244,7 +250,7 @@ bool DrawGuiUtil::isArchSection(App::DocumentObject* obj) try { if (proxyObj.hasAttr("__module__")) { Py::String mod(proxyObj.getAttr("__module__")); - ss << (std::string)mod; + ss << (std::string)mod; //does this have to be an ArchSection, or can it be other Arch objects? if (ss.str().find("ArchSectionPlane") != std::string::npos) { result = true; @@ -265,7 +271,7 @@ bool DrawGuiUtil::needPage(Gui::Command* cmd) //need a Document and a Page bool active = false; if (cmd->hasActiveDocument()) { - auto drawPageType( TechDraw::DrawPage::getClassTypeId() ); + auto drawPageType(TechDraw::DrawPage::getClassTypeId()); auto selPages = cmd->getDocument()->getObjectsOfType(drawPageType); if (!selPages.empty()) { active = true; @@ -279,13 +285,14 @@ bool DrawGuiUtil::needView(Gui::Command* cmd, bool partOnly) bool haveView = false; if (cmd->hasActiveDocument()) { if (partOnly) { - auto drawPartType (TechDraw::DrawViewPart::getClassTypeId()); + auto drawPartType(TechDraw::DrawViewPart::getClassTypeId()); auto selParts = cmd->getDocument()->getObjectsOfType(drawPartType); if (!selParts.empty()) { haveView = true; } - } else { - auto drawViewType (TechDraw::DrawView::getClassTypeId()); + } + else { + auto drawViewType(TechDraw::DrawView::getClassTypeId()); auto selParts = cmd->getDocument()->getObjectsOfType(drawViewType); if (!selParts.empty()) { haveView = true; @@ -297,32 +304,32 @@ bool DrawGuiUtil::needView(Gui::Command* cmd, bool partOnly) void DrawGuiUtil::dumpRectF(const char* text, const QRectF& r) { - Base::Console().Message("DUMP - dumpRectF - %s\n",text); + Base::Console().Message("DUMP - dumpRectF - %s\n", text); double left = r.left(); double right = r.right(); double top = r.top(); double bottom = r.bottom(); - Base::Console().Message("Extents: L: %.3f, R: %.3f, T: %.3f, B: %.3f\n",left,right,top,bottom); - Base::Console().Message("Size: W: %.3f H: %.3f\n",r.width(),r.height()); - Base::Console().Message("Centre: (%.3f, %.3f)\n",r.center().x(),r.center().y()); + Base::Console().Message("Extents: L: %.3f, R: %.3f, T: %.3f, B: %.3f\n", left, right, top, bottom); + Base::Console().Message("Size: W: %.3f H: %.3f\n", r.width(), r.height()); + Base::Console().Message("Centre: (%.3f, %.3f)\n", r.center().x(), r.center().y()); } void DrawGuiUtil::dumpPointF(const char* text, const QPointF& p) { - Base::Console().Message("DUMP - dumpPointF - %s\n",text); - Base::Console().Message("Point: (%.3f, %.3f)\n",p.x(),p.y()); + Base::Console().Message("DUMP - dumpPointF - %s\n", text); + Base::Console().Message("Point: (%.3f, %.3f)\n", p.x(), p.y()); } -std::pair DrawGuiUtil::get3DDirAndRot() +std::pair DrawGuiUtil::get3DDirAndRot() { - std::pair result; - Base::Vector3d viewDir(0.0,-1.0,0.0); //default to front - Base::Vector3d viewUp(0.0,0.0,1.0); //default to top - Base::Vector3d viewRight(1.0,0.0,0.0); //default to right + std::pair result; + Base::Vector3d viewDir(0.0, -1.0, 0.0); //default to front + Base::Vector3d viewUp(0.0, 0.0, 1.0); //default to top + Base::Vector3d viewRight(1.0, 0.0, 0.0); //default to right std::list mdis = Gui::Application::Instance->activeDocument()->getMDIViews(); - Gui::View3DInventor *view; - Gui::View3DInventorViewer *viewer = nullptr; - for (auto& m: mdis) { //find the 3D viewer + Gui::View3DInventor* view; + Gui::View3DInventorViewer* viewer = nullptr; + for (auto& m : mdis) { //find the 3D viewer view = dynamic_cast(m); if (view) { viewer = view->getViewer(); @@ -331,7 +338,7 @@ std::pair DrawGuiUtil::get3DDirAndRot() } if (!viewer) { Base::Console().Log("LOG - DrawGuiUtil could not find a 3D viewer\n"); - return std::make_pair( viewDir, viewRight); + return std::make_pair(viewDir, viewRight); } SbVec3f dvec = viewer->getViewDirection(); @@ -341,45 +348,45 @@ std::pair DrawGuiUtil::get3DDirAndRot() viewDir = viewDir * (-1.0); // Inventor dir is opposite TD projection dir viewUp = Base::Vector3d(upvec[0],upvec[1],upvec[2]); -// Base::Vector3d dirXup = viewDir.Cross(viewUp); + // Base::Vector3d dirXup = viewDir.Cross(viewUp); Base::Vector3d right = viewUp.Cross(viewDir); - result = std::make_pair(viewDir,right); + result = std::make_pair(viewDir, right); return result; } -std::pair DrawGuiUtil::getProjDirFromFace(App::DocumentObject* obj, std::string faceName) +std::pair DrawGuiUtil::getProjDirFromFace(App::DocumentObject* obj, std::string faceName) { - std::pair d3Dirs = get3DDirAndRot(); + std::pair d3Dirs = get3DDirAndRot(); Base::Vector3d d3Up = (d3Dirs.first).Cross(d3Dirs.second); - std::pair dirs; - dirs.first = Base::Vector3d(0.0,0.0,1.0); //set a default - dirs.second = Base::Vector3d(1.0,0.0,0.0); + std::pair dirs; + dirs.first = Base::Vector3d(0.0, 0.0, 1.0); //set a default + dirs.second = Base::Vector3d(1.0, 0.0, 0.0); Base::Vector3d projDir, rotVec; projDir = d3Dirs.first; rotVec = d3Dirs.second; - auto ts = Part::Feature::getShape(obj,faceName.c_str(),true); - if(ts.IsNull() || ts.ShapeType()!=TopAbs_FACE) { - Base::Console().Warning("getProjDirFromFace(%s) is not a Face\n",faceName.c_str()); + auto ts = Part::Feature::getShape(obj, faceName.c_str(), true); + if (ts.IsNull() || ts.ShapeType() != TopAbs_FACE) { + Base::Console().Warning("getProjDirFromFace(%s) is not a Face\n", faceName.c_str()); return dirs; } const TopoDS_Face& face = TopoDS::Face(ts); TopAbs_Orientation orient = face.Orientation(); BRepAdaptor_Surface adapt(face); - + double u1 = adapt.FirstUParameter(); double u2 = adapt.LastUParameter(); double v1 = adapt.FirstVParameter(); double v2 = adapt.LastVParameter(); - double uMid = (u1+u2)/2.0; - double vMid = (v1+v2)/2.0; + double uMid = (u1 + u2) / 2.0; + double vMid = (v1 + v2) / 2.0; - BRepLProp_SLProps props(adapt,uMid,vMid,2,Precision::Confusion()); + BRepLProp_SLProps props(adapt, uMid, vMid, 2, Precision::Confusion()); if (props.IsNormalDefined()) { gp_Dir vec = props.Normal(); - projDir = Base::Vector3d(vec.X(),vec.Y(),vec.Z()); + projDir = Base::Vector3d(vec.X(), vec.Y(), vec.Z()); rotVec = projDir.Cross(d3Up); if (orient != TopAbs_FORWARD) { projDir = projDir * (-1.0); @@ -389,8 +396,6 @@ std::pair DrawGuiUtil::getProjDirFromFace(App::Do Base::Console().Log("Selected Face has no normal at midpoint\n"); } - dirs = std::make_pair(projDir,rotVec); + dirs = std::make_pair(projDir, rotVec); return dirs; } - - diff --git a/src/Mod/TechDraw/Gui/TaskCosVertex.cpp b/src/Mod/TechDraw/Gui/TaskCosVertex.cpp index b4c6cd2c1b..ed25488574 100644 --- a/src/Mod/TechDraw/Gui/TaskCosVertex.cpp +++ b/src/Mod/TechDraw/Gui/TaskCosVertex.cpp @@ -86,10 +86,10 @@ TaskCosVertex::TaskCosVertex(TechDraw::DrawViewPart* baseFeat, m_btnOK(nullptr), m_btnCancel(nullptr), m_pbTrackerState(TRACKERPICK), - m_savePoint(QPointF(0.0,0.0)) + m_savePoint(QPointF(0.0, 0.0)) { - if ( (m_basePage == nullptr) || - (m_baseFeat == nullptr) ) { + if ((m_basePage == nullptr) || + (m_baseFeat == nullptr)) { //should be caught in CMD caller Base::Console().Error("TaskCosVertex - bad parameters. Can not proceed.\n"); return; @@ -118,12 +118,12 @@ TaskCosVertex::~TaskCosVertex() void TaskCosVertex::updateTask() { -// blockUpdate = true; + // blockUpdate = true; -// blockUpdate = false; + // blockUpdate = false; } -void TaskCosVertex::changeEvent(QEvent *e) +void TaskCosVertex::changeEvent(QEvent* e) { if (e->type() == QEvent::LanguageChange) { ui->retranslateUi(this); @@ -199,7 +199,7 @@ void TaskCosVertex::onTrackerClicked(bool b) QString msg = tr("Pick a point for cosmetic vertex"); getMainWindow()->statusBar()->show(); - Gui::getMainWindow()->showMessage(msg,3000); + Gui::getMainWindow()->showMessage(msg, 3000); ui->pbTracker->setText(QString::fromUtf8("Escape picking")); ui->pbTracker->setEnabled(true); m_pbTrackerState = TRACKERCANCEL; @@ -217,22 +217,23 @@ void TaskCosVertex::startTracker(void) m_tracker = new QGTracker(m_scene, m_trackerMode); QObject::connect( m_tracker, SIGNAL(drawingFinished(std::vector, QGIView*)), - this , SLOT (onTrackerFinished(std::vector, QGIView*)) - ); - } else { + this, SLOT(onTrackerFinished(std::vector, QGIView*)) + ); + } + else { //this is too harsh. but need to avoid restarting process throw Base::RuntimeError("TechDrawNewLeader - tracker already active\n"); } setEditCursor(Qt::CrossCursor); QString msg = tr("Left click to set a point"); Gui::getMainWindow()->statusBar()->show(); - Gui::getMainWindow()->showMessage(msg,3000); + Gui::getMainWindow()->showMessage(msg, 3000); } void TaskCosVertex::onTrackerFinished(std::vector pts, QGIView* qgParent) { -// Base::Console().Message("TCV::onTrackerFinished()\n"); - (void) qgParent; + // Base::Console().Message("TCV::onTrackerFinished()\n"); + (void)qgParent; if (pts.empty()) { Base::Console().Error("TaskCosVertex - no points available\n"); return; @@ -279,8 +280,8 @@ void TaskCosVertex::onTrackerFinished(std::vector pts, QGIView* qgParen void TaskCosVertex::removeTracker(void) { // Base::Console().Message("TCV::removeTracker()\n"); - if ( (m_tracker != nullptr) && - (m_tracker->scene() != nullptr) ) { + if ((m_tracker != nullptr) && + (m_tracker->scene() != nullptr)) { m_scene->removeItem(m_tracker); delete m_tracker; m_tracker = nullptr; @@ -299,7 +300,7 @@ void TaskCosVertex::abandonEditSession(void) { QString msg = tr("In progress edit abandoned. Start over."); getMainWindow()->statusBar()->show(); - Gui::getMainWindow()->showMessage(msg,4000); + Gui::getMainWindow()->showMessage(msg, 4000); ui->pbTracker->setEnabled(true); @@ -328,14 +329,14 @@ bool TaskCosVertex::accept() removeTracker(); double x = ui->dsbX->value().getValue(); double y = ui->dsbY->value().getValue(); - QPointF uiPoint(x,-y); + QPointF uiPoint(x, -y); addCosVertex(uiPoint); - + m_baseFeat->recomputeFeature(); m_baseFeat->requestPaint(); m_mdi->setContextMenuPolicy(m_saveContextPolicy); m_trackerMode = QGTracker::TrackerMode::None; - Gui::Command::doCommand(Gui::Command::Gui,"Gui.ActiveDocument.resetEdit()"); + Gui::Command::doCommand(Gui::Command::Gui, "Gui.ActiveDocument.resetEdit()"); return true; } @@ -352,8 +353,8 @@ bool TaskCosVertex::reject() } //make sure any dangling objects are cleaned up - Gui::Command::doCommand(Gui::Command::Gui,"App.activeDocument().recompute()"); - Gui::Command::doCommand(Gui::Command::Gui,"Gui.ActiveDocument.resetEdit()"); + Gui::Command::doCommand(Gui::Command::Gui, "App.activeDocument().recompute()"); + Gui::Command::doCommand(Gui::Command::Gui, "Gui.ActiveDocument.resetEdit()"); return false; } @@ -363,7 +364,7 @@ TaskDlgCosVertex::TaskDlgCosVertex(TechDraw::DrawViewPart* baseFeat, TechDraw::DrawPage* page) : TaskDialog() { - widget = new TaskCosVertex(baseFeat,page); + widget = new TaskCosVertex(baseFeat, page); taskbox = new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("actions/techdraw-LeaderLine"), widget->windowTitle(), true, 0); taskbox->groupLayout()->addWidget(widget); From 6ae65ea36bc02ef0950191252c97b305e84c68b5 Mon Sep 17 00:00:00 2001 From: Florian Simmer Date: Sat, 25 Dec 2021 21:42:37 +0100 Subject: [PATCH 50/65] Github: Create Labeler workflow/action and labels.yml file --- .github/labels.yml | 42 +++++++++++++++++++++++++++++++++++ .github/workflows/labeler.yml | 24 ++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 .github/labels.yml create mode 100644 .github/workflows/labeler.yml diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 0000000000..22e2bc7208 --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,42 @@ +# Add 'source' label to any change to src files within the source dir EXCEPT for the docs sub-folder +# source: +# - any: ['src/**/*', '!src/docs/*'] + + +🛤️ Path: +- 'src/Mod/Path/**/*' + +🏛 Arch: +- 'src/Mod/Arch/**/*' + +AddonManager: +- 'src/Mod/AddonManager/**/*' + +🚜 PartDesign: +- 'src/Mod/PartDesign/**/*' + +':pencil2: Sketcher': +- 'src/Mod/Sketcher/**/*' + +📐 Draft: +- 'src/Mod/Draft/**/*' + +🧪 FEM: +- 'src/Mod/Fem/**/*' + +⚙ TechDraw: +- 'src/Mod/TechDraw/**/*' + +🧱 Part: +- 'src/Mod/Part/**/*' + +🥅Mesh: +- 'src/Mod/Mesh/**/*' + +Spreadsheet: +- 'src/Mod/Spreadsheet/**/*' + +Core: +- 'src/App/**/*' +- 'src/Base/**/*' +- 'src/Gui/**/*' \ No newline at end of file diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000000..5d0a3663a8 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,24 @@ +# This workflow will triage pull requests and apply a label based on the +# paths that are modified in the pull request. +# +# For more information, see: +# https://github.com/actions/labeler + +name: Labeler +on: + pull_request: + types: [opened, reopened] + +jobs: + label: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + + steps: + - uses: actions/labeler@v3 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + configuration-path: ".github/labels.yml" + sync-labels: false \ No newline at end of file From 642d3088005ad346c35b05952e8f62b71d775da2 Mon Sep 17 00:00:00 2001 From: Florian Simmer Date: Mon, 3 Jan 2022 09:11:54 +0100 Subject: [PATCH 51/65] remove the AddonManager Label --- .github/labels.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/labels.yml b/.github/labels.yml index 22e2bc7208..39b9676ef3 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -9,9 +9,6 @@ 🏛 Arch: - 'src/Mod/Arch/**/*' -AddonManager: -- 'src/Mod/AddonManager/**/*' - 🚜 PartDesign: - 'src/Mod/PartDesign/**/*' From 4f40254bf2d203b2fc23bab31340033723b8446e Mon Sep 17 00:00:00 2001 From: Florian Simmer Date: Mon, 3 Jan 2022 09:12:54 +0100 Subject: [PATCH 52/65] change the workflow trigger from pull_request to pull_request_target --- .github/workflows/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 5d0a3663a8..7de0b79bb4 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -6,7 +6,7 @@ name: Labeler on: - pull_request: + pull_request_target: types: [opened, reopened] jobs: From bffb4f5364b028adf0e5c3241d4955426aa1e04e Mon Sep 17 00:00:00 2001 From: Uwe Date: Tue, 4 Jan 2022 04:10:56 +0100 Subject: [PATCH 53/65] [Win installer] upgrade year --- src/WindowsInstaller/Settings.nsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WindowsInstaller/Settings.nsh b/src/WindowsInstaller/Settings.nsh index f22304f0c7..f995a0e0e6 100644 --- a/src/WindowsInstaller/Settings.nsh +++ b/src/WindowsInstaller/Settings.nsh @@ -25,7 +25,7 @@ SetCompressor /SOLID lzma !define APP_VERSION "${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}.${APP_VERSION_REVISION}${APP_EMERGENCY_DOT}${APP_VERSION_EMERGENCY}" # Version to display -!define COPYRIGHT_YEAR 2021 +!define COPYRIGHT_YEAR 2022 #-------------------------------- # Installer file name From 6ef0b68236be0580da72ec4512308e94e6c1c939 Mon Sep 17 00:00:00 2001 From: Florian Simmer Date: Mon, 3 Jan 2022 19:39:25 +0100 Subject: [PATCH 54/65] Codespell: add rady to the ignorelist --- .github/codespellignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/codespellignore b/.github/codespellignore index 54bd1fa58a..5691d0a7d9 100644 --- a/.github/codespellignore +++ b/.github/codespellignore @@ -72,6 +72,7 @@ parms pres programm que +rady recurrance rougly seperator From 09d1e31b733ec345b051451ec02082fff9a683d5 Mon Sep 17 00:00:00 2001 From: mwganson Date: Sun, 2 Jan 2022 15:44:11 -0600 Subject: [PATCH 55/65] [TechDraw] Use global DontUseNativeColorDialog parameter from BaseApp/Preferences/Dialog when opening color dialog --- src/Mod/TechDraw/Gui/mrichtextedit.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Mod/TechDraw/Gui/mrichtextedit.cpp b/src/Mod/TechDraw/Gui/mrichtextedit.cpp index ad136a9358..43759f3b43 100644 --- a/src/Mod/TechDraw/Gui/mrichtextedit.cpp +++ b/src/Mod/TechDraw/Gui/mrichtextedit.cpp @@ -51,9 +51,9 @@ #include #include #include +#include #include - #include "PreferencesGui.h" #include "mrichtextedit.h" @@ -433,7 +433,12 @@ void MRichTextEdit::textStyle(int index) { } void MRichTextEdit::textFgColor() { - QColor col = QColorDialog::getColor(f_textedit->textColor(), this); + QColor col; + if (Gui::DialogOptions::dontUseNativeColorDialog()){ + col = QColorDialog::getColor(f_textedit->textColor(),this, QLatin1String(""), QColorDialog::DontUseNativeDialog); + } else { + col = QColorDialog::getColor(f_textedit->textColor(), this); + } QTextCursor cursor = f_textedit->textCursor(); if (!cursor.hasSelection()) { cursor.select(QTextCursor::WordUnderCursor); @@ -450,7 +455,12 @@ void MRichTextEdit::textFgColor() { } void MRichTextEdit::textBgColor() { - QColor col = QColorDialog::getColor(f_textedit->textBackgroundColor(), this); + QColor col; + if (Gui::DialogOptions::dontUseNativeColorDialog()){ + col = QColorDialog::getColor(f_textedit->textBackgroundColor(),this, QLatin1String(""), QColorDialog::DontUseNativeDialog); + } else { + col = QColorDialog::getColor(f_textedit->textBackgroundColor(), this); + } QTextCursor cursor = f_textedit->textCursor(); if (!cursor.hasSelection()) { cursor.select(QTextCursor::WordUnderCursor); From 0cbafaf2701ed9a492c648f91a9a12dec9e7fccb Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 4 Jan 2022 13:34:22 -0600 Subject: [PATCH 56/65] Black reformat (postprocessing files) --- src/Mod/Path/PathScripts/PathPost.py | 118 +- src/Mod/Path/PathScripts/PostUtils.py | 133 ++- src/Mod/Path/PathScripts/post/gcode_pre.py | 47 +- src/Mod/Path/PathScripts/post/grbl_post.py | 1052 ++++++++++-------- src/Mod/Path/PathScripts/post/marlin_post.py | 539 ++++----- 5 files changed, 1067 insertions(+), 822 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPost.py b/src/Mod/Path/PathScripts/PathPost.py index 39abd1cec5..3a5017d259 100644 --- a/src/Mod/Path/PathScripts/PathPost.py +++ b/src/Mod/Path/PathScripts/PathPost.py @@ -20,7 +20,7 @@ # * * # *************************************************************************** -''' Post Process command that will make use of the Output File and Post Processor entries in PathJob ''' +""" Post Process command that will make use of the Output File and Post Processor entries in PathJob """ from __future__ import print_function @@ -57,14 +57,15 @@ class _TempObject: class DlgSelectPostProcessor: - def __init__(self, parent=None): # pylint: disable=unused-argument self.dialog = FreeCADGui.PySideUic.loadUi(":/panels/DlgSelectPostProcessor.ui") firstItem = None for post in PathPreferences.allEnabledPostProcessors(): item = QtGui.QListWidgetItem(post) - item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled) + item.setFlags( + QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled + ) self.dialog.lwPostProcessor.addItem(item) if not firstItem: firstItem = item @@ -103,50 +104,52 @@ class CommandPathPost: path = job.PostProcessorOutputFile filename = path - if '%D' in filename: + if "%D" in filename: 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 = '.' + D = "." else: - FreeCAD.Console.PrintError("Please save document in order to resolve output path!\n") + FreeCAD.Console.PrintError( + "Please save document in order to resolve output path!\n" + ) return None - filename = filename.replace('%D', D) + filename = filename.replace("%D", D) - if '%d' in filename: + if "%d" in filename: d = FreeCAD.ActiveDocument.Label - filename = filename.replace('%d', d) + filename = filename.replace("%d", d) - if '%j' in filename: + if "%j" in filename: j = job.Label - filename = filename.replace('%j', j) + filename = filename.replace("%j", j) - if '%M' in filename: + if "%M" in filename: pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro") M = pref.GetString("MacroPath", FreeCAD.getUserAppDataDir()) - filename = filename.replace('%M', M) + filename = filename.replace("%M", M) - if '%s' in filename: + if "%s" in filename: if job.SplitOutput: - filename = filename.replace('%s', '_'+str(self.subpart)) + filename = filename.replace("%s", "_" + str(self.subpart)) self.subpart += 1 else: - filename = filename.replace('%s', '') + filename = filename.replace("%s", "") policy = PathPreferences.defaultOutputPolicy() - openDialog = policy == 'Open File Dialog' + 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 os.path.isfile(filename) and not openDialog: - if policy == 'Open File Dialog on conflict': + if policy == "Open File Dialog on conflict": openDialog = True - elif policy == 'Append Unique ID on conflict': + elif policy == "Append Unique ID on conflict": fn, ext = os.path.splitext(filename) nr = fn[-3:] n = 1 @@ -157,7 +160,9 @@ class CommandPathPost: filename = "%s%03d%s" % (fn, n, ext) if openDialog: - foo = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), "Output File", filename) + foo = QtGui.QFileDialog.getSaveFileName( + QtGui.QApplication.activeWindow(), "Output File", filename + ) if foo[0]: filename = foo[0] else: @@ -176,10 +181,14 @@ class CommandPathPost: return dlg.exec_() def GetResources(self): - return {'Pixmap': 'Path_Post', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Post", "Post Process"), - 'Accel': "P, P", - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Post", "Post Process the selected Job")} + return { + "Pixmap": "Path_Post", + "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_Post", "Post Process"), + "Accel": "P, P", + "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "Path_Post", "Post Process the selected Job" + ), + } def IsActive(self): if FreeCAD.ActiveDocument is not None: @@ -198,10 +207,10 @@ class CommandPathPost: if hasattr(job, "PostProcessorArgs") and job.PostProcessorArgs: postArgs = job.PostProcessorArgs elif hasattr(job, "PostProcessor") and job.PostProcessor: - postArgs = '' + postArgs = "" postname = self.resolvePostProcessor(job) - filename = '-' + filename = "-" if postname and needFilename: filename = self.resolveFileName(job) @@ -211,12 +220,13 @@ class CommandPathPost: gcode = processor.export(objs, filename, postArgs) return (False, gcode, filename) else: - return (True, '', filename) + return (True, "", filename) def Activated(self): PathLog.track() FreeCAD.ActiveDocument.openTransaction( - translate("Path_Post", "Post Process the Selected path(s)")) + translate("Path_Post", "Post Process the Selected path(s)") + ) FreeCADGui.addModule("PathScripts.PathPost") # Attempt to figure out what the user wants to post-process @@ -227,7 +237,9 @@ class CommandPathPost: selected = FreeCADGui.Selection.getSelectionEx() if len(selected) > 1: - FreeCAD.Console.PrintError("Please select a single job or other path object\n") + FreeCAD.Console.PrintError( + "Please select a single job or other path object\n" + ) return elif len(selected) == 1: sel = selected[0].Object @@ -267,7 +279,7 @@ class CommandPathPost: postlist = [] - if orderby == 'Fixture': + if orderby == "Fixture": PathLog.debug("Ordering by Fixture") # Order by fixture means all operations and tool changes will be completed in one # fixture before moving to the next. @@ -279,7 +291,13 @@ class CommandPathPost: c1 = Path.Command(f) fobj.Path = Path.Path([c1]) if index != 0: - c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value)) + c2 = Path.Command( + "G0 Z" + + str( + job.Stock.Shape.BoundBox.ZMax + + job.SetupSheet.ClearanceHeightOffset.Value + ) + ) fobj.Path.addCommands(c2) fobj.InList.append(job) sublist = [fobj] @@ -287,7 +305,7 @@ class CommandPathPost: # Now generate the gcode for obj in job.Operations.Group: tc = PathUtil.toolControllerForOp(obj) - if tc is not None and PathUtil.opProperty(obj, 'Active'): + if tc is not None and PathUtil.opProperty(obj, "Active"): if tc.ToolNumber != currTool or split is True: sublist.append(tc) PathLog.debug("Appending TC: {}".format(tc.Name)) @@ -295,7 +313,7 @@ class CommandPathPost: sublist.append(obj) postlist.append(sublist) - elif orderby == 'Tool': + elif orderby == "Tool": PathLog.debug("Ordering by Tool") # Order by tool means tool changes are minimized. # all operations with the current tool are processed in the current @@ -307,7 +325,13 @@ class CommandPathPost: # create an object to serve as the fixture path fobj = _TempObject() c1 = Path.Command(f) - c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value)) + c2 = Path.Command( + "G0 Z" + + str( + job.Stock.Shape.BoundBox.ZMax + + job.SetupSheet.ClearanceHeightOffset.Value + ) + ) fobj.Path = Path.Path([c1, c2]) fobj.InList.append(job) fixturelist.append(fobj) @@ -319,16 +343,20 @@ class CommandPathPost: for idx, obj in enumerate(job.Operations.Group): # check if the operation is active - active = PathUtil.opProperty(obj, 'Active') + active = PathUtil.opProperty(obj, "Active") tc = PathUtil.toolControllerForOp(obj) if tc is None or tc.ToolNumber == currTool and active: curlist.append(obj) - elif tc.ToolNumber != currTool and currTool is None and active: # first TC + elif ( + tc.ToolNumber != currTool and currTool is None and active + ): # first TC sublist.append(tc) curlist.append(obj) currTool = tc.ToolNumber - elif tc.ToolNumber != currTool and currTool is not None and active: # TC + elif ( + tc.ToolNumber != currTool and currTool is not None and active + ): # TC for fixture in fixturelist: sublist.append(fixture) sublist.extend(curlist) @@ -343,7 +371,7 @@ class CommandPathPost: sublist.extend(curlist) postlist.append(sublist) - elif orderby == 'Operation': + elif orderby == "Operation": PathLog.debug("Ordering by Operation") # Order by operation means ops are done in each fixture in # sequence. @@ -352,7 +380,7 @@ class CommandPathPost: # Now generate the gcode for obj in job.Operations.Group: - if PathUtil.opProperty(obj, 'Active'): + if PathUtil.opProperty(obj, "Active"): sublist = [] PathLog.debug("obj: {}".format(obj.Name)) for f in wcslist: @@ -360,7 +388,13 @@ class CommandPathPost: c1 = Path.Command(f) fobj.Path = Path.Path([c1]) if not firstFixture: - c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value)) + c2 = Path.Command( + "G0 Z" + + str( + job.Stock.Shape.BoundBox.ZMax + + job.SetupSheet.ClearanceHeightOffset.Value + ) + ) fobj.Path.addCommands(c2) fobj.InList.append(job) sublist.append(fobj) @@ -374,7 +408,7 @@ class CommandPathPost: postlist.append(sublist) fail = True - rc = '' + rc = "" if split: for slist in postlist: (fail, rc, filename) = self.exportObjectsWith(slist, job) @@ -400,6 +434,6 @@ class CommandPathPost: if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_Post', CommandPathPost()) + FreeCADGui.addCommand("Path_Post", CommandPathPost()) FreeCAD.Console.PrintLog("Loading PathPost... done\n") diff --git a/src/Mod/Path/PathScripts/PostUtils.py b/src/Mod/Path/PathScripts/PostUtils.py index 06bd1914e0..7f05a1fd19 100644 --- a/src/Mod/Path/PathScripts/PostUtils.py +++ b/src/Mod/Path/PathScripts/PostUtils.py @@ -1,30 +1,30 @@ # -*- coding: utf-8 -*- -#*************************************************************************** -#* Copyright (c) 2014 Yorik van Havre * -#* * -#* This file is part of the FreeCAD CAx development system. * -#* * -#* This program is free software; you can redistribute it and/or modify * -#* it under the terms of the GNU Lesser General Public License (LGPL) * -#* as published by the Free Software Foundation; either version 2 of * -#* the License, or (at your option) any later version. * -#* for detail see the LICENCE text file. * -#* * -#* FreeCAD is distributed in the hope that it will be useful, * -#* but WITHOUT ANY WARRANTY; without even the implied warranty of * -#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -#* GNU Lesser General Public License for more details. * -#* * -#* You should have received a copy of the GNU Library General Public * -#* License along with FreeCAD; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** +# *************************************************************************** +# * Copyright (c) 2014 Yorik van Havre * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * FreeCAD is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Lesser General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with FreeCAD; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** -''' +""" These are a common functions and classes for creating custom post processors. -''' +""" from PySide import QtCore, QtGui import FreeCAD @@ -41,15 +41,16 @@ class GCodeHighlighter(QtGui.QSyntaxHighlighter): keywordFormat = QtGui.QTextCharFormat() keywordFormat.setForeground(QtCore.Qt.cyan) keywordFormat.setFontWeight(QtGui.QFont.Bold) - keywordPatterns = ["\\bG[0-9]+\\b", - "\\bM[0-9]+\\b"] + keywordPatterns = ["\\bG[0-9]+\\b", "\\bM[0-9]+\\b"] - self.highlightingRules = [(QtCore.QRegExp(pattern), keywordFormat) for pattern in keywordPatterns] + self.highlightingRules = [ + (QtCore.QRegExp(pattern), keywordFormat) for pattern in keywordPatterns + ] speedFormat = QtGui.QTextCharFormat() speedFormat.setFontWeight(QtGui.QFont.Bold) speedFormat.setForeground(QtCore.Qt.green) - self.highlightingRules.append((QtCore.QRegExp("\\bF[0-9\\.]+\\b"),speedFormat)) + self.highlightingRules.append((QtCore.QRegExp("\\bF[0-9\\.]+\\b"), speedFormat)) def highlightBlock(self, text): for pattern, hlFormat in self.highlightingRules: @@ -61,12 +62,11 @@ class GCodeHighlighter(QtGui.QSyntaxHighlighter): index = expression.indexIn(text, index + length) - class GCodeEditorDialog(QtGui.QDialog): - def __init__(self, parent = None): + def __init__(self, parent=None): if parent is None: parent = FreeCADGui.getMainWindow() - QtGui.QDialog.__init__(self,parent) + QtGui.QDialog.__init__(self, parent) layout = QtGui.QVBoxLayout(self) @@ -83,7 +83,9 @@ class GCodeEditorDialog(QtGui.QDialog): # OK and Cancel buttons self.buttons = QtGui.QDialogButtonBox( QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel, - QtCore.Qt.Horizontal, self) + QtCore.Qt.Horizontal, + self, + ) layout.addWidget(self.buttons) # restore placement and size @@ -111,45 +113,68 @@ class GCodeEditorDialog(QtGui.QDialog): def stringsplit(commandline): - returndict = {'command':None, 'X':None, 'Y':None, 'Z':None, 'A':None, 'B':None, 'F':None, 'T':None, 'S':None, 'I':None, 'J':None,'K':None, 'txt': None} + returndict = { + "command": None, + "X": None, + "Y": None, + "Z": None, + "A": None, + "B": None, + "F": None, + "T": None, + "S": None, + "I": None, + "J": None, + "K": None, + "txt": None, + } wordlist = [a.strip() for a in commandline.split(" ")] - if wordlist[0][0] == '(': - returndict['command'] = 'message' - returndict['txt'] = wordlist[0] + if wordlist[0][0] == "(": + returndict["command"] = "message" + returndict["txt"] = wordlist[0] else: - returndict['command'] = wordlist[0] + returndict["command"] = wordlist[0] for word in wordlist[1:]: returndict[word[0]] = word[1:] return returndict -def fmt(num,dec,units): - ''' used to format axis moves, feedrate, etc for decimal places and units''' - if units == 'G21': #metric - fnum = '%.*f' % (dec, num) - else: #inch - fnum = '%.*f' % (dec, num/25.4) #since FreeCAD uses metric units internally + +def fmt(num, dec, units): + """used to format axis moves, feedrate, etc for decimal places and units""" + if units == "G21": # metric + fnum = "%.*f" % (dec, num) + else: # inch + fnum = "%.*f" % (dec, num / 25.4) # since FreeCAD uses metric units internally return fnum + def editor(gcode): - '''pops up a handy little editor to look at the code output ''' + """pops up a handy little editor to look at the code output""" prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") # default Max Highlighter Size = 512 Ko defaultMHS = 512 * 1024 - mhs = prefs.GetUnsigned('inspecteditorMaxHighlighterSize', defaultMHS) + mhs = prefs.GetUnsigned("inspecteditorMaxHighlighterSize", defaultMHS) dia = GCodeEditorDialog() dia.editor.setText(gcode) gcodeSize = len(dia.editor.toPlainText()) - if (gcodeSize <= mhs): + if gcodeSize <= mhs: # because of poor performance, syntax highlighting is # limited to mhs octets (default 512 KB). # It seems than the response time curve has an inflexion near 500 KB # beyond 500 KB, the response time increases exponentially. dia.highlighter = GCodeHighlighter(dia.editor.document()) else: - FreeCAD.Console.PrintMessage(translate("Path", "GCode size too big ({} o), disabling syntax highlighter.".format(gcodeSize))) + FreeCAD.Console.PrintMessage( + translate( + "Path", + "GCode size too big ({} o), disabling syntax highlighter.".format( + gcodeSize + ), + ) + ) result = dia.exec_() if result: # If user selected 'OK' get modified G Code final = dia.editor.toPlainText() @@ -157,14 +182,12 @@ def editor(gcode): final = gcode return final -def fcoms(string,commentsym): - ''' filter and rebuild comments with user preferred comment symbol''' - if len(commentsym)==1: - s1 = string.replace('(', commentsym) - comment = s1.replace(')', '') + +def fcoms(string, commentsym): + """filter and rebuild comments with user preferred comment symbol""" + if len(commentsym) == 1: + s1 = string.replace("(", commentsym) + comment = s1.replace(")", "") else: return string return comment - - - diff --git a/src/Mod/Path/PathScripts/post/gcode_pre.py b/src/Mod/Path/PathScripts/post/gcode_pre.py index e3e2303b3f..b24187ebc6 100644 --- a/src/Mod/Path/PathScripts/post/gcode_pre.py +++ b/src/Mod/Path/PathScripts/post/gcode_pre.py @@ -22,7 +22,7 @@ # ***************************************************************************/ -''' +""" This is an example preprocessor file for the Path workbench. Its aim is to open a gcode file, parse its contents, and create the appropriate objects in FreeCAD. @@ -40,7 +40,7 @@ assumed. The user should carefully examine the resulting gcode! Read the Path Workbench documentation to know how to create Path objects from GCode. -''' +""" import os import FreeCAD @@ -61,7 +61,7 @@ if LEVEL == PathLog.Level.DEBUG: # to distinguish python built-in open function from the one declared below -if open.__module__ in ['__builtin__', 'io']: +if open.__module__ in ["__builtin__", "io"]: pythonopen = open @@ -90,10 +90,10 @@ def insert(filename, docname): gfile.close() # Regular expression to match tool changes in the format 'M6 Tn' - p = re.compile('[mM]+?\s?0?6\s?T\d*\s') + p = re.compile("[mM]+?\s?0?6\s?T\d*\s") # split the gcode on tool changes - paths = re.split('([mM]+?\s?0?6\s?T\d*\s)', gcode) + paths = re.split("([mM]+?\s?0?6\s?T\d*\s)", gcode) # iterate the gcode sections and add customs for each toolnumber = 0 @@ -103,7 +103,7 @@ def insert(filename, docname): # if the section is a tool change, extract the tool number m = p.match(path) if m: - toolnumber = int(m.group().split('T')[-1]) + toolnumber = int(m.group().split("T")[-1]) continue # Parse the gcode and throw away any empty lists @@ -113,10 +113,15 @@ def insert(filename, docname): # Create a custom and viewobject obj = PathCustom.Create("Custom") - res = PathOpGui.CommandResources('Custom', PathCustom.Create, - PathCustomGui.TaskPanelOpPage, - 'Path_Custom', - QtCore.QT_TRANSLATE_NOOP('Path_Custom', 'Custom'), '', '') + res = PathOpGui.CommandResources( + "Custom", + PathCustom.Create, + PathCustomGui.TaskPanelOpPage, + "Path_Custom", + QtCore.QT_TRANSLATE_NOOP("Path_Custom", "Custom"), + "", + "", + ) obj.ViewObject.Proxy = PathOpGui.ViewProvider(obj.ViewObject, res) obj.ViewObject.Proxy.setDeleteObjectsOnReject(False) @@ -127,16 +132,24 @@ def insert(filename, docname): FreeCAD.ActiveDocument.recompute() - def parse(inputstring): "parse(inputstring): returns a parsed output string" - supported = ['G0', 'G00', - 'G1', 'G01', - 'G2', 'G02', - 'G3', 'G03', - 'G81', 'G82', 'G83', - 'G90', 'G91'] + supported = [ + "G0", + "G00", + "G1", + "G01", + "G2", + "G02", + "G3", + "G03", + "G81", + "G82", + "G83", + "G90", + "G91", + ] axis = ["X", "Y", "Z", "A", "B", "C", "U", "V", "W"] diff --git a/src/Mod/Path/PathScripts/post/grbl_post.py b/src/Mod/Path/PathScripts/post/grbl_post.py index 08cabb85b1..66579c6b8e 100755 --- a/src/Mod/Path/PathScripts/post/grbl_post.py +++ b/src/Mod/Path/PathScripts/post/grbl_post.py @@ -33,11 +33,11 @@ import shlex import PathScripts.PathUtil as PathUtil -TOOLTIP = ''' +TOOLTIP = """ Generate g-code from a Path that is compatible with the grbl controller. import grbl_post grbl_post.export(object, "/path/to/file.ncc") -''' +""" # *************************************************************************** @@ -45,71 +45,133 @@ grbl_post.export(object, "/path/to/file.ncc") # *************************************************************************** # Default values for command line arguments: -OUTPUT_COMMENTS = True # default output of comments in output gCode file -OUTPUT_HEADER = True # default output header in output gCode file -OUTPUT_LINE_NUMBERS = False # default doesn't output line numbers in output gCode file -OUTPUT_BCNC = False # default doesn't add bCNC operation block headers in output gCode file -SHOW_EDITOR = True # default show the resulting file dialog output in GUI -PRECISION = 3 # Default precision for metric (see http://linuxcnc.org/docs/2.7/html/gcode/overview.html#_g_code_best_practices) -TRANSLATE_DRILL_CYCLES = False # If true, G81, G82 & G83 are translated in G0/G1 moves -PREAMBLE = '''G17 G90 -''' # default preamble text will appear at the beginning of the gCode output file. -POSTAMBLE = '''M5 +OUTPUT_COMMENTS = True # default output of comments in output gCode file +OUTPUT_HEADER = True # default output header in output gCode file +OUTPUT_LINE_NUMBERS = False # default doesn't output line numbers in output gCode file +OUTPUT_BCNC = ( + False # default doesn't add bCNC operation block headers in output gCode file +) +SHOW_EDITOR = True # default show the resulting file dialog output in GUI +PRECISION = 3 # Default precision for metric (see http://linuxcnc.org/docs/2.7/html/gcode/overview.html#_g_code_best_practices) +TRANSLATE_DRILL_CYCLES = False # If true, G81, G82 & G83 are translated in G0/G1 moves +PREAMBLE = """G17 G90 +""" # default preamble text will appear at the beginning of the gCode output file. +POSTAMBLE = """M5 G17 G90 M2 -''' # default postamble text will appear following the last operation. +""" # default postamble text will appear following the last operation. -SPINDLE_WAIT = 0 # no waiting after M3 / M4 by default -RETURN_TO = None # no movements after end of program +SPINDLE_WAIT = 0 # no waiting after M3 / M4 by default +RETURN_TO = None # no movements after end of program # Customisation with no command line argument -MODAL = False # if true commands are suppressed if the same as previous line. -LINENR = 100 # line number starting value -LINEINCR = 10 # line number increment -OUTPUT_TOOL_CHANGE = False # default don't output M6 tool changes (comment it) as grbl currently does not handle it -DRILL_RETRACT_MODE = 'G98' # Default value of drill retractations (CURRENT_Z) other possible value is G99 -MOTION_MODE = 'G90' # G90 for absolute moves, G91 for relative -UNITS = 'G21' # G21 for metric, G20 for us standard -UNIT_FORMAT = 'mm' -UNIT_SPEED_FORMAT = 'mm/min' -PRE_OPERATION = '''''' # Pre operation text will be inserted before every operation -POST_OPERATION = '''''' # Post operation text will be inserted after every operation -TOOL_CHANGE = '''''' # Tool Change commands will be inserted before a tool change +MODAL = False # if true commands are suppressed if the same as previous line. +LINENR = 100 # line number starting value +LINEINCR = 10 # line number increment +OUTPUT_TOOL_CHANGE = False # default don't output M6 tool changes (comment it) as grbl currently does not handle it +DRILL_RETRACT_MODE = "G98" # Default value of drill retractations (CURRENT_Z) other possible value is G99 +MOTION_MODE = "G90" # G90 for absolute moves, G91 for relative +UNITS = "G21" # G21 for metric, G20 for us standard +UNIT_FORMAT = "mm" +UNIT_SPEED_FORMAT = "mm/min" +PRE_OPERATION = """""" # Pre operation text will be inserted before every operation +POST_OPERATION = """""" # Post operation text will be inserted after every operation +TOOL_CHANGE = """""" # Tool Change commands will be inserted before a tool change # *************************************************************************** # * End of customization # *************************************************************************** # Parser arguments list & definition -parser = argparse.ArgumentParser(prog='grbl', add_help=False) -parser.add_argument('--comments', action='store_true', help='output comment (default)') -parser.add_argument('--no-comments', action='store_true', help='suppress comment output') -parser.add_argument('--header', action='store_true', help='output headers (default)') -parser.add_argument('--no-header', action='store_true', help='suppress header output') -parser.add_argument('--line-numbers', action='store_true', help='prefix with line numbers') -parser.add_argument('--no-line-numbers', action='store_true', help='don\'t prefix with line numbers (default)') -parser.add_argument('--show-editor', action='store_true', help='pop up editor before writing output (default)') -parser.add_argument('--no-show-editor', action='store_true', help='don\'t pop up editor before writing output') -parser.add_argument('--precision', default='3', help='number of digits of precision, default=3') -parser.add_argument('--translate_drill', action='store_true', help='translate drill cycles G81, G82 & G83 in G0/G1 movements') -parser.add_argument('--no-translate_drill', action='store_true', help='don\'t translate drill cycles G81, G82 & G83 in G0/G1 movements (default)') -parser.add_argument('--preamble', help='set commands to be issued before the first command, default="G17 G90"') -parser.add_argument('--postamble', help='set commands to be issued after the last command, default="M5\nG17 G90\n;M2"') -parser.add_argument('--inches', action='store_true', help='Convert output for US imperial mode (G20)') -parser.add_argument('--tool-change', action='store_true', help='Insert M6 for all tool changes') -parser.add_argument('--wait-for-spindle', type=int, default=0, help='Wait for spindle to reach desired speed after M3 / M4, default=0') -parser.add_argument('--return-to', default='', help='Move to the specified coordinates at the end, e.g. --return-to=0,0') -parser.add_argument('--bcnc', action='store_true', help='Add Job operations as bCNC block headers. Consider suppressing existing comments: Add argument --no-comments') -parser.add_argument('--no-bcnc', action='store_true', help='suppress bCNC block header output (default)') +parser = argparse.ArgumentParser(prog="grbl", add_help=False) +parser.add_argument("--comments", action="store_true", help="output comment (default)") +parser.add_argument( + "--no-comments", action="store_true", help="suppress comment output" +) +parser.add_argument("--header", action="store_true", help="output headers (default)") +parser.add_argument("--no-header", action="store_true", help="suppress header output") +parser.add_argument( + "--line-numbers", action="store_true", help="prefix with line numbers" +) +parser.add_argument( + "--no-line-numbers", + action="store_true", + help="don't prefix with line numbers (default)", +) +parser.add_argument( + "--show-editor", + action="store_true", + help="pop up editor before writing output (default)", +) +parser.add_argument( + "--no-show-editor", + action="store_true", + help="don't pop up editor before writing output", +) +parser.add_argument( + "--precision", default="3", help="number of digits of precision, default=3" +) +parser.add_argument( + "--translate_drill", + action="store_true", + help="translate drill cycles G81, G82 & G83 in G0/G1 movements", +) +parser.add_argument( + "--no-translate_drill", + action="store_true", + help="don't translate drill cycles G81, G82 & G83 in G0/G1 movements (default)", +) +parser.add_argument( + "--preamble", + help='set commands to be issued before the first command, default="G17 G90"', +) +parser.add_argument( + "--postamble", + help='set commands to be issued after the last command, default="M5\nG17 G90\n;M2"', +) +parser.add_argument( + "--inches", action="store_true", help="Convert output for US imperial mode (G20)" +) +parser.add_argument( + "--tool-change", action="store_true", help="Insert M6 for all tool changes" +) +parser.add_argument( + "--wait-for-spindle", + type=int, + default=0, + help="Wait for spindle to reach desired speed after M3 / M4, default=0", +) +parser.add_argument( + "--return-to", + default="", + help="Move to the specified coordinates at the end, e.g. --return-to=0,0", +) +parser.add_argument( + "--bcnc", + action="store_true", + help="Add Job operations as bCNC block headers. Consider suppressing existing comments: Add argument --no-comments", +) +parser.add_argument( + "--no-bcnc", action="store_true", help="suppress bCNC block header output (default)" +) TOOLTIP_ARGS = parser.format_help() # *************************************************************************** # * Internal global variables # *************************************************************************** -MOTION_COMMANDS = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03'] # Motion gCode commands definition -RAPID_MOVES = ['G0', 'G00'] # Rapid moves gCode commands definition -SUPPRESS_COMMANDS = [] # These commands are ignored by commenting them out +MOTION_COMMANDS = [ + "G0", + "G00", + "G1", + "G01", + "G2", + "G02", + "G3", + "G03", +] # Motion gCode commands definition +RAPID_MOVES = ["G0", "G00"] # Rapid moves gCode commands definition +SUPPRESS_COMMANDS = [] # These commands are ignored by commenting them out COMMAND_SPACE = " " # Global variables storing current position CURRENT_X = 0 @@ -119,455 +181,549 @@ CURRENT_Z = 0 # *************************************************************************** # * to distinguish python built-in open function from the one declared below -if open.__module__ in ['__builtin__', 'io']: - pythonopen = open +if open.__module__ in ["__builtin__", "io"]: + pythonopen = open def processArguments(argstring): - global OUTPUT_HEADER - global OUTPUT_COMMENTS - global OUTPUT_LINE_NUMBERS - global SHOW_EDITOR - global PRECISION - global PREAMBLE - global POSTAMBLE - global UNITS - global UNIT_SPEED_FORMAT - global UNIT_FORMAT - global TRANSLATE_DRILL_CYCLES - global OUTPUT_TOOL_CHANGE - global SPINDLE_WAIT - global RETURN_TO - global OUTPUT_BCNC + global OUTPUT_HEADER + global OUTPUT_COMMENTS + global OUTPUT_LINE_NUMBERS + global SHOW_EDITOR + global PRECISION + global PREAMBLE + global POSTAMBLE + global UNITS + global UNIT_SPEED_FORMAT + global UNIT_FORMAT + global TRANSLATE_DRILL_CYCLES + global OUTPUT_TOOL_CHANGE + global SPINDLE_WAIT + global RETURN_TO + global OUTPUT_BCNC - try: - args = parser.parse_args(shlex.split(argstring)) - if args.no_header: - OUTPUT_HEADER = False - if args.header: - OUTPUT_HEADER = True - if args.no_comments: - OUTPUT_COMMENTS = False - if args.comments: - OUTPUT_COMMENTS = True - if args.no_line_numbers: - OUTPUT_LINE_NUMBERS = False - if args.line_numbers: - OUTPUT_LINE_NUMBERS = True - if args.no_show_editor: - SHOW_EDITOR = False - if args.show_editor: - SHOW_EDITOR = True - PRECISION = args.precision - if args.preamble is not None: - PREAMBLE = args.preamble - if args.postamble is not None: - POSTAMBLE = args.postamble - if args.no_translate_drill: - TRANSLATE_DRILL_CYCLES = False - if args.translate_drill: - TRANSLATE_DRILL_CYCLES = True - if args.inches: - UNITS = 'G20' - UNIT_SPEED_FORMAT = 'in/min' - UNIT_FORMAT = 'in' - PRECISION = 4 - if args.tool_change: - OUTPUT_TOOL_CHANGE = True - if args.wait_for_spindle > 0: - SPINDLE_WAIT = args.wait_for_spindle - if args.return_to != '': - RETURN_TO = [int(v) for v in args.return_to.split(',')] - if len(RETURN_TO) != 2: - RETURN_TO = None - print("--return-to coordinates must be specified as ,, ignoring") - if args.bcnc: - OUTPUT_BCNC = True - if args.no_bcnc: - OUTPUT_BCNC = False + try: + args = parser.parse_args(shlex.split(argstring)) + if args.no_header: + OUTPUT_HEADER = False + if args.header: + OUTPUT_HEADER = True + if args.no_comments: + OUTPUT_COMMENTS = False + if args.comments: + OUTPUT_COMMENTS = True + if args.no_line_numbers: + OUTPUT_LINE_NUMBERS = False + if args.line_numbers: + OUTPUT_LINE_NUMBERS = True + if args.no_show_editor: + SHOW_EDITOR = False + if args.show_editor: + SHOW_EDITOR = True + PRECISION = args.precision + if args.preamble is not None: + PREAMBLE = args.preamble + if args.postamble is not None: + POSTAMBLE = args.postamble + if args.no_translate_drill: + TRANSLATE_DRILL_CYCLES = False + if args.translate_drill: + TRANSLATE_DRILL_CYCLES = True + if args.inches: + UNITS = "G20" + UNIT_SPEED_FORMAT = "in/min" + UNIT_FORMAT = "in" + PRECISION = 4 + if args.tool_change: + OUTPUT_TOOL_CHANGE = True + if args.wait_for_spindle > 0: + SPINDLE_WAIT = args.wait_for_spindle + if args.return_to != "": + RETURN_TO = [int(v) for v in args.return_to.split(",")] + if len(RETURN_TO) != 2: + RETURN_TO = None + print("--return-to coordinates must be specified as ,, ignoring") + if args.bcnc: + OUTPUT_BCNC = True + if args.no_bcnc: + OUTPUT_BCNC = False + except Exception as e: + return False - except Exception as e: - return False - - return True + return True # For debug... def dump(obj): - for attr in dir(obj): - print("obj.%s = %s" % (attr, getattr(obj, attr))) + for attr in dir(obj): + print("obj.%s = %s" % (attr, getattr(obj, attr))) def export(objectslist, filename, argstring): - if not processArguments(argstring): - return None + if not processArguments(argstring): + return None - global UNITS - global UNIT_FORMAT - global UNIT_SPEED_FORMAT - global MOTION_MODE - global SUPPRESS_COMMANDS + global UNITS + global UNIT_FORMAT + global UNIT_SPEED_FORMAT + global MOTION_MODE + global SUPPRESS_COMMANDS - print("Post Processor: " + __name__ + " postprocessing...") - gcode = "" + print("Post Processor: " + __name__ + " postprocessing...") + gcode = "" - # write header - if OUTPUT_HEADER: - gcode += linenumber() + "(Exported by FreeCAD)\n" - gcode += linenumber() + "(Post Processor: " + __name__ + ")\n" - gcode += linenumber() + "(Output Time:" + str(datetime.datetime.now()) + ")\n" + # write header + if OUTPUT_HEADER: + gcode += linenumber() + "(Exported by FreeCAD)\n" + gcode += linenumber() + "(Post Processor: " + __name__ + ")\n" + gcode += linenumber() + "(Output Time:" + str(datetime.datetime.now()) + ")\n" - # Check canned cycles for drilling - if TRANSLATE_DRILL_CYCLES: - if len(SUPPRESS_COMMANDS) == 0: - SUPPRESS_COMMANDS = ['G99', 'G98', 'G80'] - else: - SUPPRESS_COMMANDS += ['G99', 'G98', 'G80'] - - # Write the preamble - if OUTPUT_COMMENTS: - gcode += linenumber() + "(Begin preamble)\n" - for line in PREAMBLE.splitlines(True): - gcode += linenumber() + line - # verify if PREAMBLE have changed MOTION_MODE or UNITS - if 'G90' in PREAMBLE: - MOTION_MODE = 'G90' - elif 'G91' in PREAMBLE: - MOTION_MODE = 'G91' - else: - gcode += linenumber() + MOTION_MODE + "\n" - if 'G21' in PREAMBLE: - UNITS = 'G21' - UNIT_FORMAT = 'mm' - UNIT_SPEED_FORMAT = 'mm/min' - elif 'G20' in PREAMBLE: - UNITS = 'G20' - UNIT_FORMAT = 'in' - UNIT_SPEED_FORMAT = 'in/min' - else: - gcode += linenumber() + UNITS + "\n" - - for obj in objectslist: - # Debug... - # print("\n" + "*"*70) - # dump(obj) - # print("*"*70 + "\n") - if not hasattr(obj, "Path"): - print("The object " + obj.Name + " is not a path. Please select only path and Compounds.") - return - - # Skip inactive operations - if PathUtil.opProperty(obj, 'Active') is False: - continue - - # do the pre_op - if OUTPUT_BCNC: - gcode += linenumber() + "(Block-name: " + obj.Label + ")\n" - gcode += linenumber() + "(Block-expand: 0)\n" - gcode += linenumber() + "(Block-enable: 1)\n" - if OUTPUT_COMMENTS: - gcode += linenumber() + "(Begin operation: " + obj.Label + ")\n" - for line in PRE_OPERATION.splitlines(True): - gcode += linenumber() + line - - # get coolant mode - coolantMode = 'None' - if hasattr(obj, "CoolantMode") or hasattr(obj, 'Base') and hasattr(obj.Base, "CoolantMode"): - if hasattr(obj, "CoolantMode"): - coolantMode = obj.CoolantMode + # Check canned cycles for drilling + if TRANSLATE_DRILL_CYCLES: + if len(SUPPRESS_COMMANDS) == 0: + SUPPRESS_COMMANDS = ["G99", "G98", "G80"] else: - coolantMode = obj.Base.CoolantMode + SUPPRESS_COMMANDS += ["G99", "G98", "G80"] - # turn coolant on if required + # Write the preamble if OUTPUT_COMMENTS: - if not coolantMode == 'None': - gcode += linenumber() + '(Coolant On:' + coolantMode + ')\n' - if coolantMode == 'Flood': - gcode += linenumber() + 'M8' + '\n' - if coolantMode == 'Mist': - gcode += linenumber() + 'M7' + '\n' - - # Parse the op - gcode += parse(obj) - - # do the post_op - if OUTPUT_COMMENTS: - gcode += linenumber() + "(Finish operation: " + obj.Label + ")\n" - for line in POST_OPERATION.splitlines(True): - gcode += linenumber() + line - - # turn coolant off if required - if not coolantMode == 'None': - if OUTPUT_COMMENTS: - gcode += linenumber() + '(Coolant Off:' + coolantMode + ')\n' - gcode += linenumber() +'M9' + '\n' - - if RETURN_TO: - gcode += linenumber() + "G0 X%s Y%s\n" % tuple(RETURN_TO) - - # do the post_amble - if OUTPUT_BCNC: - gcode += linenumber() + "(Block-name: post_amble)\n" - gcode += linenumber() + "(Block-expand: 0)\n" - gcode += linenumber() + "(Block-enable: 1)\n" - if OUTPUT_COMMENTS: - gcode += linenumber() + "(Begin postamble)\n" - for line in POSTAMBLE.splitlines(True): - gcode += linenumber() + line - - # show the gCode result dialog - if FreeCAD.GuiUp and SHOW_EDITOR: - dia = PostUtils.GCodeEditorDialog() - dia.editor.setText(gcode) - result = dia.exec_() - if result: - final = dia.editor.toPlainText() + gcode += linenumber() + "(Begin preamble)\n" + for line in PREAMBLE.splitlines(True): + gcode += linenumber() + line + # verify if PREAMBLE have changed MOTION_MODE or UNITS + if "G90" in PREAMBLE: + MOTION_MODE = "G90" + elif "G91" in PREAMBLE: + MOTION_MODE = "G91" else: - final = gcode - else: - final = gcode + gcode += linenumber() + MOTION_MODE + "\n" + if "G21" in PREAMBLE: + UNITS = "G21" + UNIT_FORMAT = "mm" + UNIT_SPEED_FORMAT = "mm/min" + elif "G20" in PREAMBLE: + UNITS = "G20" + UNIT_FORMAT = "in" + UNIT_SPEED_FORMAT = "in/min" + else: + gcode += linenumber() + UNITS + "\n" - print("Done postprocessing.") + for obj in objectslist: + # Debug... + # print("\n" + "*"*70) + # dump(obj) + # print("*"*70 + "\n") + if not hasattr(obj, "Path"): + print( + "The object " + + obj.Name + + " is not a path. Please select only path and Compounds." + ) + return - # write the file - gfile = pythonopen(filename, "w") - gfile.write(final) - gfile.close() + # Skip inactive operations + if PathUtil.opProperty(obj, "Active") is False: + continue + + # do the pre_op + if OUTPUT_BCNC: + gcode += linenumber() + "(Block-name: " + obj.Label + ")\n" + gcode += linenumber() + "(Block-expand: 0)\n" + gcode += linenumber() + "(Block-enable: 1)\n" + if OUTPUT_COMMENTS: + gcode += linenumber() + "(Begin operation: " + obj.Label + ")\n" + for line in PRE_OPERATION.splitlines(True): + gcode += linenumber() + line + + # get coolant mode + coolantMode = "None" + if ( + hasattr(obj, "CoolantMode") + or hasattr(obj, "Base") + and hasattr(obj.Base, "CoolantMode") + ): + if hasattr(obj, "CoolantMode"): + coolantMode = obj.CoolantMode + else: + coolantMode = obj.Base.CoolantMode + + # turn coolant on if required + if OUTPUT_COMMENTS: + if not coolantMode == "None": + gcode += linenumber() + "(Coolant On:" + coolantMode + ")\n" + if coolantMode == "Flood": + gcode += linenumber() + "M8" + "\n" + if coolantMode == "Mist": + gcode += linenumber() + "M7" + "\n" + + # Parse the op + gcode += parse(obj) + + # do the post_op + if OUTPUT_COMMENTS: + gcode += linenumber() + "(Finish operation: " + obj.Label + ")\n" + for line in POST_OPERATION.splitlines(True): + gcode += linenumber() + line + + # turn coolant off if required + if not coolantMode == "None": + if OUTPUT_COMMENTS: + gcode += linenumber() + "(Coolant Off:" + coolantMode + ")\n" + gcode += linenumber() + "M9" + "\n" + + if RETURN_TO: + gcode += linenumber() + "G0 X%s Y%s\n" % tuple(RETURN_TO) + + # do the post_amble + if OUTPUT_BCNC: + gcode += linenumber() + "(Block-name: post_amble)\n" + gcode += linenumber() + "(Block-expand: 0)\n" + gcode += linenumber() + "(Block-enable: 1)\n" + if OUTPUT_COMMENTS: + gcode += linenumber() + "(Begin postamble)\n" + for line in POSTAMBLE.splitlines(True): + gcode += linenumber() + line + + # show the gCode result dialog + if FreeCAD.GuiUp and SHOW_EDITOR: + dia = PostUtils.GCodeEditorDialog() + dia.editor.setText(gcode) + result = dia.exec_() + if result: + final = dia.editor.toPlainText() + else: + final = gcode + else: + final = gcode + + print("Done postprocessing.") + + # write the file + gfile = pythonopen(filename, "w") + gfile.write(final) + gfile.close() def linenumber(): - if not OUTPUT_LINE_NUMBERS: - return "" - global LINENR - global LINEINCR - s = "N" + str(LINENR) + " " - LINENR += LINEINCR - return s + if not OUTPUT_LINE_NUMBERS: + return "" + global LINENR + global LINEINCR + s = "N" + str(LINENR) + " " + LINENR += LINEINCR + return s def format_outstring(strTable): - global COMMAND_SPACE - # construct the line for the final output - s = "" - for w in strTable: - s += w + COMMAND_SPACE - s = s.strip() - return s + global COMMAND_SPACE + # construct the line for the final output + s = "" + for w in strTable: + s += w + COMMAND_SPACE + s = s.strip() + return s def parse(pathobj): - global DRILL_RETRACT_MODE - global MOTION_MODE - global CURRENT_X - global CURRENT_Y - global CURRENT_Z + global DRILL_RETRACT_MODE + global MOTION_MODE + global CURRENT_X + global CURRENT_Y + global CURRENT_Z - out = "" - lastcommand = None - precision_string = '.' + str(PRECISION) + 'f' + out = "" + lastcommand = None + precision_string = "." + str(PRECISION) + "f" - params = ['X', 'Y', 'Z', 'A', 'B', 'C', 'U', 'V', 'W', 'I', 'J', 'K', 'F', 'S', 'T', 'Q', 'R', 'L', 'P'] + params = [ + "X", + "Y", + "Z", + "A", + "B", + "C", + "U", + "V", + "W", + "I", + "J", + "K", + "F", + "S", + "T", + "Q", + "R", + "L", + "P", + ] - if hasattr(pathobj, "Group"): # We have a compound or project. - if OUTPUT_COMMENTS: - out += linenumber() + "(Compound: " + pathobj.Label + ")\n" - for p in pathobj.Group: - out += parse(p) - return out - - else: # parsing simple path - if not hasattr(pathobj, "Path"): # groups might contain non-path things like stock. - return out - - if OUTPUT_COMMENTS: - out += linenumber() + "(Path: " + pathobj.Label + ")\n" - - for c in pathobj.Path.Commands: - outstring = [] - command = c.Name - - outstring.append(command) - - # if modal: only print the command if it is not the same as the last one - if MODAL: - if command == lastcommand: - outstring.pop(0) - - # Now add the remaining parameters in order - for param in params: - if param in c.Parameters: - if param == 'F': - if command not in RAPID_MOVES: - speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity) - if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0: - outstring.append(param + format(float(speed.getValueAs(UNIT_SPEED_FORMAT)), precision_string)) - elif param in ['T', 'H', 'D', 'S', 'P', 'L']: - outstring.append(param + str(c.Parameters[param])) - elif param in ['A', 'B', 'C']: - outstring.append(param + format(c.Parameters[param], precision_string)) - else: # [X, Y, Z, U, V, W, I, J, K, R, Q] (Conversion eventuelle mm/inches) - pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length) - outstring.append(param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string)) - - # store the latest command - lastcommand = command - - # Memorizes the current position for calculating the related movements and the withdrawal plan - if command in MOTION_COMMANDS: - if 'X' in c.Parameters: - CURRENT_X = Units.Quantity(c.Parameters['X'], FreeCAD.Units.Length) - if 'Y' in c.Parameters: - CURRENT_Y = Units.Quantity(c.Parameters['Y'], FreeCAD.Units.Length) - if 'Z' in c.Parameters: - CURRENT_Z = Units.Quantity(c.Parameters['Z'], FreeCAD.Units.Length) - - if command in ('G98', 'G99'): - DRILL_RETRACT_MODE = command - - if command in ('G90', 'G91'): - MOTION_MODE = command - - - if TRANSLATE_DRILL_CYCLES: - if command in ('G81', 'G82', 'G83'): - out += drill_translate(outstring, command, c.Parameters) - # Erase the line we just translated - outstring = [] - - if SPINDLE_WAIT > 0: - if command in ('M3', 'M03', 'M4', 'M04'): - out += linenumber() + format_outstring(outstring) + "\n" - out += linenumber() + format_outstring(['G4', 'P%s' % SPINDLE_WAIT]) + "\n" - outstring = [] - - # Check for Tool Change: - if command in ('M6', 'M06'): + if hasattr(pathobj, "Group"): # We have a compound or project. if OUTPUT_COMMENTS: - out += linenumber() + "(Begin toolchange)\n" - if not OUTPUT_TOOL_CHANGE: - outstring.insert(0, "(" ) - outstring.append( ")" ) - else: - for line in TOOL_CHANGE.splitlines(True): - out += linenumber() + line + out += linenumber() + "(Compound: " + pathobj.Label + ")\n" + for p in pathobj.Group: + out += parse(p) + return out - if command == "message": - if OUTPUT_COMMENTS is False: - out = [] - else: - outstring.pop(0) # remove the command + else: # parsing simple path + if not hasattr( + pathobj, "Path" + ): # groups might contain non-path things like stock. + return out - if command in SUPPRESS_COMMANDS: - outstring.insert(0, "(" ) - outstring.append( ")" ) + if OUTPUT_COMMENTS: + out += linenumber() + "(Path: " + pathobj.Label + ")\n" - # prepend a line number and append a newline - if len(outstring) >= 1: - out += linenumber() + format_outstring(outstring) + "\n" + for c in pathobj.Path.Commands: + outstring = [] + command = c.Name - return out + outstring.append(command) + + # if modal: only print the command if it is not the same as the last one + if MODAL: + if command == lastcommand: + outstring.pop(0) + + # Now add the remaining parameters in order + for param in params: + if param in c.Parameters: + if param == "F": + if command not in RAPID_MOVES: + speed = Units.Quantity( + c.Parameters["F"], FreeCAD.Units.Velocity + ) + if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0: + outstring.append( + param + + format( + float(speed.getValueAs(UNIT_SPEED_FORMAT)), + precision_string, + ) + ) + elif param in ["T", "H", "D", "S", "P", "L"]: + outstring.append(param + str(c.Parameters[param])) + elif param in ["A", "B", "C"]: + outstring.append( + param + format(c.Parameters[param], precision_string) + ) + else: # [X, Y, Z, U, V, W, I, J, K, R, Q] (Conversion eventuelle mm/inches) + pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length) + outstring.append( + param + + format( + float(pos.getValueAs(UNIT_FORMAT)), precision_string + ) + ) + + # store the latest command + lastcommand = command + + # Memorizes the current position for calculating the related movements and the withdrawal plan + if command in MOTION_COMMANDS: + if "X" in c.Parameters: + CURRENT_X = Units.Quantity(c.Parameters["X"], FreeCAD.Units.Length) + if "Y" in c.Parameters: + CURRENT_Y = Units.Quantity(c.Parameters["Y"], FreeCAD.Units.Length) + if "Z" in c.Parameters: + CURRENT_Z = Units.Quantity(c.Parameters["Z"], FreeCAD.Units.Length) + + if command in ("G98", "G99"): + DRILL_RETRACT_MODE = command + + if command in ("G90", "G91"): + MOTION_MODE = command + + if TRANSLATE_DRILL_CYCLES: + if command in ("G81", "G82", "G83"): + out += drill_translate(outstring, command, c.Parameters) + # Erase the line we just translated + outstring = [] + + if SPINDLE_WAIT > 0: + if command in ("M3", "M03", "M4", "M04"): + out += linenumber() + format_outstring(outstring) + "\n" + out += ( + linenumber() + + format_outstring(["G4", "P%s" % SPINDLE_WAIT]) + + "\n" + ) + outstring = [] + + # Check for Tool Change: + if command in ("M6", "M06"): + if OUTPUT_COMMENTS: + out += linenumber() + "(Begin toolchange)\n" + if not OUTPUT_TOOL_CHANGE: + outstring.insert(0, "(") + outstring.append(")") + else: + for line in TOOL_CHANGE.splitlines(True): + out += linenumber() + line + + if command == "message": + if OUTPUT_COMMENTS is False: + out = [] + else: + outstring.pop(0) # remove the command + + if command in SUPPRESS_COMMANDS: + outstring.insert(0, "(") + outstring.append(")") + + # prepend a line number and append a newline + if len(outstring) >= 1: + out += linenumber() + format_outstring(outstring) + "\n" + + return out def drill_translate(outstring, cmd, params): - global DRILL_RETRACT_MODE - global MOTION_MODE - global CURRENT_X - global CURRENT_Y - global CURRENT_Z - global UNITS - global UNIT_FORMAT - global UNIT_SPEED_FORMAT + global DRILL_RETRACT_MODE + global MOTION_MODE + global CURRENT_X + global CURRENT_Y + global CURRENT_Z + global UNITS + global UNIT_FORMAT + global UNIT_SPEED_FORMAT - strFormat = '.' + str(PRECISION) + 'f' + strFormat = "." + str(PRECISION) + "f" - trBuff = "" + trBuff = "" - if OUTPUT_COMMENTS: # Comment the original command - outstring[0] = "(" + outstring[0] - outstring[-1] = outstring[-1] + ")" - trBuff += linenumber() + format_outstring(outstring) + "\n" + if OUTPUT_COMMENTS: # Comment the original command + outstring[0] = "(" + outstring[0] + outstring[-1] = outstring[-1] + ")" + trBuff += linenumber() + format_outstring(outstring) + "\n" + + # cycle conversion + # currently only cycles in XY are provided (G17) + # other plains ZX (G18) and YZ (G19) are not dealt with : Z drilling only. + drill_X = Units.Quantity(params["X"], FreeCAD.Units.Length) + drill_Y = Units.Quantity(params["Y"], FreeCAD.Units.Length) + drill_Z = Units.Quantity(params["Z"], FreeCAD.Units.Length) + RETRACT_Z = Units.Quantity(params["R"], FreeCAD.Units.Length) + # R less than Z is error + if RETRACT_Z < drill_Z: + trBuff += linenumber() + "(drill cycle error: R less than Z )\n" + return trBuff + + if MOTION_MODE == "G91": # G91 relative movements + drill_X += CURRENT_X + drill_Y += CURRENT_Y + drill_Z += CURRENT_Z + RETRACT_Z += CURRENT_Z + + if DRILL_RETRACT_MODE == "G98" and CURRENT_Z >= RETRACT_Z: + RETRACT_Z = CURRENT_Z + + # get the other parameters + drill_feedrate = Units.Quantity(params["F"], FreeCAD.Units.Velocity) + if cmd == "G83": + drill_Step = Units.Quantity(params["Q"], FreeCAD.Units.Length) + a_bit = ( + drill_Step * 0.05 + ) # NIST 3.5.16.4 G83 Cycle: "current hole bottom, backed off a bit." + elif cmd == "G82": + drill_DwellTime = params["P"] + + # wrap this block to ensure machine MOTION_MODE is restored in case of error + try: + if MOTION_MODE == "G91": + trBuff += linenumber() + "G90\n" # force absolute coordinates during cycles + + strG0_RETRACT_Z = ( + "G0 Z" + format(float(RETRACT_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n" + ) + strF_Feedrate = ( + " F" + + format(float(drill_feedrate.getValueAs(UNIT_SPEED_FORMAT)), ".2f") + + "\n" + ) + print(strF_Feedrate) + + # preliminary mouvement(s) + if CURRENT_Z < RETRACT_Z: + trBuff += linenumber() + strG0_RETRACT_Z + trBuff += ( + linenumber() + + "G0 X" + + format(float(drill_X.getValueAs(UNIT_FORMAT)), strFormat) + + " Y" + + format(float(drill_Y.getValueAs(UNIT_FORMAT)), strFormat) + + "\n" + ) + if CURRENT_Z > RETRACT_Z: + # NIST GCODE 3.5.16.1 Preliminary and In-Between Motion says G0 to RETRACT_Z. Here use G1 since retract height may be below surface ! + trBuff += ( + linenumber() + + "G1 Z" + + format(float(RETRACT_Z.getValueAs(UNIT_FORMAT)), strFormat) + + strF_Feedrate + ) + last_Stop_Z = RETRACT_Z + + # drill moves + if cmd in ("G81", "G82"): + trBuff += ( + linenumber() + + "G1 Z" + + format(float(drill_Z.getValueAs(UNIT_FORMAT)), strFormat) + + strF_Feedrate + ) + # pause where applicable + if cmd == "G82": + trBuff += linenumber() + "G4 P" + str(drill_DwellTime) + "\n" + trBuff += linenumber() + strG0_RETRACT_Z + else: # 'G83' + if params["Q"] != 0: + while 1: + if last_Stop_Z != RETRACT_Z: + clearance_depth = ( + last_Stop_Z + a_bit + ) # rapid move to just short of last drilling depth + trBuff += ( + linenumber() + + "G0 Z" + + format( + float(clearance_depth.getValueAs(UNIT_FORMAT)), + strFormat, + ) + + "\n" + ) + next_Stop_Z = last_Stop_Z - drill_Step + if next_Stop_Z > drill_Z: + trBuff += ( + linenumber() + + "G1 Z" + + format( + float(next_Stop_Z.getValueAs(UNIT_FORMAT)), strFormat + ) + + strF_Feedrate + ) + trBuff += linenumber() + strG0_RETRACT_Z + last_Stop_Z = next_Stop_Z + else: + trBuff += ( + linenumber() + + "G1 Z" + + format(float(drill_Z.getValueAs(UNIT_FORMAT)), strFormat) + + strF_Feedrate + ) + trBuff += linenumber() + strG0_RETRACT_Z + break + + except Exception as e: + pass + + if MOTION_MODE == "G91": + trBuff += linenumber() + "G91" # Restore if changed - # cycle conversion - # currently only cycles in XY are provided (G17) - # other plains ZX (G18) and YZ (G19) are not dealt with : Z drilling only. - drill_X = Units.Quantity(params['X'], FreeCAD.Units.Length) - drill_Y = Units.Quantity(params['Y'], FreeCAD.Units.Length) - drill_Z = Units.Quantity(params['Z'], FreeCAD.Units.Length) - RETRACT_Z = Units.Quantity(params['R'], FreeCAD.Units.Length) - # R less than Z is error - if RETRACT_Z < drill_Z : - trBuff += linenumber() + "(drill cycle error: R less than Z )\n" return trBuff - if MOTION_MODE == 'G91': # G91 relative movements - drill_X += CURRENT_X - drill_Y += CURRENT_Y - drill_Z += CURRENT_Z - RETRACT_Z += CURRENT_Z - - if DRILL_RETRACT_MODE == 'G98' and CURRENT_Z >= RETRACT_Z: - RETRACT_Z = CURRENT_Z - - # get the other parameters - drill_feedrate = Units.Quantity(params['F'], FreeCAD.Units.Velocity) - if cmd == 'G83': - drill_Step = Units.Quantity(params['Q'], FreeCAD.Units.Length) - a_bit = drill_Step * 0.05 # NIST 3.5.16.4 G83 Cycle: "current hole bottom, backed off a bit." - elif cmd == 'G82': - drill_DwellTime = params['P'] - - # wrap this block to ensure machine MOTION_MODE is restored in case of error - try: - if MOTION_MODE == 'G91': - trBuff += linenumber() + "G90\n" # force absolute coordinates during cycles - - strG0_RETRACT_Z = 'G0 Z' + format(float(RETRACT_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n" - strF_Feedrate = ' F' + format(float(drill_feedrate.getValueAs(UNIT_SPEED_FORMAT)), '.2f') + "\n" - print (strF_Feedrate) - - # preliminary mouvement(s) - if CURRENT_Z < RETRACT_Z: - trBuff += linenumber() + strG0_RETRACT_Z - trBuff += linenumber() + 'G0 X' + format(float(drill_X.getValueAs(UNIT_FORMAT)), strFormat) + ' Y' + format(float(drill_Y.getValueAs(UNIT_FORMAT)), strFormat) + "\n" - if CURRENT_Z > RETRACT_Z: - # NIST GCODE 3.5.16.1 Preliminary and In-Between Motion says G0 to RETRACT_Z. Here use G1 since retract height may be below surface ! - trBuff += linenumber() + 'G1 Z' + format(float(RETRACT_Z.getValueAs(UNIT_FORMAT)), strFormat) + strF_Feedrate - last_Stop_Z = RETRACT_Z - - # drill moves - if cmd in ('G81', 'G82'): - trBuff += linenumber() + 'G1 Z' + format(float(drill_Z.getValueAs(UNIT_FORMAT)), strFormat) + strF_Feedrate - # pause where applicable - if cmd == 'G82': - trBuff += linenumber() + 'G4 P' + str(drill_DwellTime) + "\n" - trBuff += linenumber() + strG0_RETRACT_Z - else: # 'G83' - if params['Q'] != 0 : - while 1: - if last_Stop_Z != RETRACT_Z : - clearance_depth = last_Stop_Z + a_bit # rapid move to just short of last drilling depth - trBuff += linenumber() + 'G0 Z' + format(float(clearance_depth.getValueAs(UNIT_FORMAT)) , strFormat) + "\n" - next_Stop_Z = last_Stop_Z - drill_Step - if next_Stop_Z > drill_Z: - trBuff += linenumber() + 'G1 Z' + format(float(next_Stop_Z.getValueAs(UNIT_FORMAT)), strFormat) + strF_Feedrate - trBuff += linenumber() + strG0_RETRACT_Z - last_Stop_Z = next_Stop_Z - else: - trBuff += linenumber() + 'G1 Z' + format(float(drill_Z.getValueAs(UNIT_FORMAT)), strFormat) + strF_Feedrate - trBuff += linenumber() + strG0_RETRACT_Z - break - - except Exception as e: - pass - - if MOTION_MODE == 'G91': - trBuff += linenumber() + 'G91' # Restore if changed - - return trBuff - # print(__name__ + ": GCode postprocessor loaded.") diff --git a/src/Mod/Path/PathScripts/post/marlin_post.py b/src/Mod/Path/PathScripts/post/marlin_post.py index f7cee65634..e80bb44da1 100644 --- a/src/Mod/Path/PathScripts/post/marlin_post.py +++ b/src/Mod/Path/PathScripts/post/marlin_post.py @@ -35,7 +35,7 @@ from FreeCAD import Units import PathScripts.PathUtil as PathUtil import PathScripts.PostUtils as PostUtils -Revised = '2020-11-03' # Revision date for this file. +Revised = "2020-11-03" # Revision date for this file. # ***************************************************************************** # * Due to the fundamentals of the FreeCAD pre-processor, * @@ -47,155 +47,148 @@ Revised = '2020-11-03' # Revision date for this file. # ***************************************************************************** -TOOLTIP = ''' +TOOLTIP = """ Generate g-code from a Path that is compatible with the Marlin controller. import marlin_post marlin_post.export(object, "/path/to/file.nc") -''' +""" # ***************************************************************************** # * Initial configuration, not changeable * # ***************************************************************************** -MOTION_MODE = 'G90' # G90 only, for absolute moves -WORK_PLANE = 'G17' # G17 only, XY plane, for vertical milling -UNITS = 'G21' # G21 only, for metric -UNIT_FORMAT = 'mm' -UNIT_FEED_FORMAT = 'mm/s' +MOTION_MODE = "G90" # G90 only, for absolute moves +WORK_PLANE = "G17" # G17 only, XY plane, for vertical milling +UNITS = "G21" # G21 only, for metric +UNIT_FORMAT = "mm" +UNIT_FEED_FORMAT = "mm/s" # ***************************************************************************** # * Initial configuration, changeable via command line arguments * # ***************************************************************************** -PRECISION = 3 # Decimal places displayed for metric -DRILL_RETRACT_MODE = 'G98' # End of drill-cycle retractation type. G99 +PRECISION = 3 # Decimal places displayed for metric +DRILL_RETRACT_MODE = "G98" # End of drill-cycle retractation type. G99 # is the alternative. -TRANSLATE_DRILL_CYCLES = True # If true, G81, G82, and G83 are translated +TRANSLATE_DRILL_CYCLES = True # If true, G81, G82, and G83 are translated # into G0/G1 moves -OUTPUT_TOOL_CHANGE = False # Do not output M6 tool change (comment it) -RETURN_TO = None # None = No movement at end of program -SPINDLE_WAIT = 3 # 0 == No waiting after M3 / M4 -MODAL = False # True: Commands are suppressed if they are +OUTPUT_TOOL_CHANGE = False # Do not output M6 tool change (comment it) +RETURN_TO = None # None = No movement at end of program +SPINDLE_WAIT = 3 # 0 == No waiting after M3 / M4 +MODAL = False # True: Commands are suppressed if they are # the same as the previous line -LINENR = 100 # Line number starting value -LINEINCR = 10 # Line number increment -PRE_OPERATION = '''''' # Pre operation text will be inserted before +LINENR = 100 # Line number starting value +LINEINCR = 10 # Line number increment +PRE_OPERATION = """""" # Pre operation text will be inserted before # every operation -POST_OPERATION = '''''' # Post operation text will be inserted after +POST_OPERATION = """""" # Post operation text will be inserted after # every operation -TOOL_CHANGE = '''''' # Tool Change commands will be inserted +TOOL_CHANGE = """""" # Tool Change commands will be inserted # before a tool change # ***************************************************************************** # * Initial gcode output options, changeable via command line arguments * # ***************************************************************************** -OUTPUT_HEADER = True # Output header in output gcode file -OUTPUT_COMMENTS = True # Comments in output gcode file -OUTPUT_FINISH = False # Include an operation finished comment -OUTPUT_PATH = False # Include a Path: comment -OUTPUT_MARLIN_CONFIG = False # Display expected #defines for Marlin config -OUTPUT_LINE_NUMBERS = False # Output line numbers in output gcode file -OUTPUT_BCNC = False # Add bCNC operation block headers in output +OUTPUT_HEADER = True # Output header in output gcode file +OUTPUT_COMMENTS = True # Comments in output gcode file +OUTPUT_FINISH = False # Include an operation finished comment +OUTPUT_PATH = False # Include a Path: comment +OUTPUT_MARLIN_CONFIG = False # Display expected #defines for Marlin config +OUTPUT_LINE_NUMBERS = False # Output line numbers in output gcode file +OUTPUT_BCNC = False # Add bCNC operation block headers in output # gcode file -SHOW_EDITOR = True # Display the resulting gcode file +SHOW_EDITOR = True # Display the resulting gcode file # ***************************************************************************** # * Command line arguments * # ***************************************************************************** -parser = argparse.ArgumentParser(prog='marlin', add_help=False) +parser = argparse.ArgumentParser(prog="marlin", add_help=False) +parser.add_argument("--header", action="store_true", help="output headers (default)") +parser.add_argument("--no-header", action="store_true", help="suppress header output") +parser.add_argument("--comments", action="store_true", help="output comment (default)") parser.add_argument( - '--header', - action='store_true', - help='output headers (default)') + "--no-comments", action="store_true", help="suppress comment output" +) parser.add_argument( - '--no-header', - action='store_true', - help='suppress header output') + "--finish-comments", action="store_true", help="output finish-comment" +) parser.add_argument( - '--comments', - action='store_true', - help='output comment (default)') + "--no-finish-comments", + action="store_true", + help="suppress finish-comment output (default)", +) +parser.add_argument("--path-comments", action="store_true", help="output path-comment") parser.add_argument( - '--no-comments', - action='store_true', - help='suppress comment output') + "--no-path-comments", + action="store_true", + help="suppress path-comment output (default)", +) parser.add_argument( - '--finish-comments', - action='store_true', - help='output finish-comment') + "--marlin-config", action="store_true", help="output #defines for Marlin" +) parser.add_argument( - '--no-finish-comments', - action='store_true', - help='suppress finish-comment output (default)') + "--no-marlin-config", + action="store_true", + help="suppress output #defines for Marlin (default)", +) parser.add_argument( - '--path-comments', - action='store_true', - help='output path-comment') + "--line-numbers", action="store_true", help="prefix with line numbers" +) parser.add_argument( - '--no-path-comments', - action='store_true', - help='suppress path-comment output (default)') + "--no-line-numbers", + action="store_true", + help="do not prefix with line numbers (default)", +) parser.add_argument( - '--marlin-config', - action='store_true', - help='output #defines for Marlin') + "--show-editor", + action="store_true", + help="pop up editor before writing output (default)", +) parser.add_argument( - '--no-marlin-config', - action='store_true', - help='suppress output #defines for Marlin (default)') + "--no-show-editor", + action="store_true", + help="do not pop up editor before writing output", +) parser.add_argument( - '--line-numbers', - action='store_true', - help='prefix with line numbers') + "--precision", default="3", help="number of digits of precision, default=3" +) parser.add_argument( - '--no-line-numbers', - action='store_true', - help='do not prefix with line numbers (default)') + "--translate_drill", + action="store_true", + help="translate drill cycles G81, G82, G83 into G0/G1 movements (default)", +) parser.add_argument( - '--show-editor', - action='store_true', - help='pop up editor before writing output (default)') + "--no-translate_drill", + action="store_true", + help="do not translate drill cycles G81, G82, G83 into G0/G1 movements", +) parser.add_argument( - '--no-show-editor', - action='store_true', - help='do not pop up editor before writing output') + "--preamble", help='set commands to be issued before the first command, default=""' +) parser.add_argument( - '--precision', - default='3', - help='number of digits of precision, default=3') + "--postamble", help='set commands to be issued after the last command, default="M5"' +) parser.add_argument( - '--translate_drill', - action='store_true', - help='translate drill cycles G81, G82, G83 into G0/G1 movements (default)') + "--tool-change", action="store_true", help="Insert M6 for all tool changes" +) parser.add_argument( - '--no-translate_drill', - action='store_true', - help='do not translate drill cycles G81, G82, G83 into G0/G1 movements') -parser.add_argument( - '--preamble', - help='set commands to be issued before the first command, default=""') -parser.add_argument( - '--postamble', - help='set commands to be issued after the last command, default="M5"') -parser.add_argument( - '--tool-change', action='store_true', - help='Insert M6 for all tool changes') -parser.add_argument( - '--wait-for-spindle', + "--wait-for-spindle", type=int, default=3, - help='Wait for spindle to reach desired speed after M3 or M4, default=0') + help="Wait for spindle to reach desired speed after M3 or M4, default=0", +) parser.add_argument( - '--return-to', - default='', - help='When done, move to, e.g. --return-to="3.175, 4.702, 50.915"') + "--return-to", + default="", + help='When done, move to, e.g. --return-to="3.175, 4.702, 50.915"', +) parser.add_argument( - '--bcnc', - action='store_true', - help='Add Job operations as bCNC block headers. \ - Consider suppressing existing comments: Add argument --no-comments') + "--bcnc", + action="store_true", + help="Add Job operations as bCNC block headers. \ + Consider suppressing existing comments: Add argument --no-comments", +) parser.add_argument( - '--no-bcnc', - action='store_true', - help='suppress bCNC block header output (default)') + "--no-bcnc", action="store_true", help="suppress bCNC block header output (default)" +) TOOLTIP_ARGS = parser.format_help() # ***************************************************************************** @@ -209,19 +202,19 @@ TOOLTIP_ARGS = parser.format_help() # ***************************************************************************** # Default preamble text will appear at the beginning of the gcode output file. -PREAMBLE = '''''' +PREAMBLE = """""" # Default postamble text will appear following the last operation. -POSTAMBLE = '''M5 -''' +POSTAMBLE = """M5 +""" # ***************************************************************************** # * Internal global variables * # ***************************************************************************** -MOTION_COMMANDS = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03'] -RAPID_MOVES = ['G0', 'G00'] # Rapid moves gcode commands definition -SUPPRESS_COMMANDS = [''] # These commands are ignored by commenting them out -COMMAND_SPACE = ' ' +MOTION_COMMANDS = ["G0", "G00", "G1", "G01", "G2", "G02", "G3", "G03"] +RAPID_MOVES = ["G0", "G00"] # Rapid moves gcode commands definition +SUPPRESS_COMMANDS = [""] # These commands are ignored by commenting them out +COMMAND_SPACE = " " # Global variables storing current position (Use None for safety.) CURRENT_X = None CURRENT_Y = None @@ -290,9 +283,9 @@ def processArguments(argstring): OUTPUT_TOOL_CHANGE = True if args.return_to: RETURN_TO = args.return_to - if RETURN_TO.find(',') == -1: + if RETURN_TO.find(",") == -1: RETURN_TO = None - print('--return-to coordinates must be specified as:') + print("--return-to coordinates must be specified as:") print('--return-to "x.n,y.n,z.n"') if args.bcnc: OUTPUT_BCNC = True @@ -311,14 +304,14 @@ def processArguments(argstring): def dump(obj): for attr in dir(obj): try: - if attr.startswith('__'): + if attr.startswith("__"): continue - print('>' + attr + '<') + print(">" + attr + "<") attr_text = "%s = %s" % (attr, getattr(obj, attr)) - if attr in ['HorizFeed', 'VertFeed']: - print('==============\n', attr_text) - if 'mm/s' in attr_text: - print('===> metric values <===') + if attr in ["HorizFeed", "VertFeed"]: + print("==============\n", attr_text) + if "mm/s" in attr_text: + print("===> metric values <===") except Exception: # Insignificant errors # print('==>', obj, attr) pass @@ -334,128 +327,134 @@ def export(objectslist, filename, argstring): global MOTION_MODE global SUPPRESS_COMMANDS - print('Post Processor: ' + __name__ + ' postprocessing...') - gcode = '' + print("Post Processor: " + __name__ + " postprocessing...") + gcode = "" # Write header: if OUTPUT_HEADER: - gcode += linenumber() + '(Exported by FreeCAD)\n' - gcode += linenumber() + '(Post Processor: ' + __name__ - gcode += '.py, version: ' + Revised + ')\n' - gcode += linenumber() + '(Output Time:' + str(datetime.now()) + ')\n' + gcode += linenumber() + "(Exported by FreeCAD)\n" + gcode += linenumber() + "(Post Processor: " + __name__ + gcode += ".py, version: " + Revised + ")\n" + gcode += linenumber() + "(Output Time:" + str(datetime.now()) + ")\n" # Suppress drill-cycle commands: if TRANSLATE_DRILL_CYCLES: - SUPPRESS_COMMANDS += ['G80', 'G98', 'G99'] + SUPPRESS_COMMANDS += ["G80", "G98", "G99"] # Write the preamble: if OUTPUT_COMMENTS: - gcode += linenumber() + '(Begin preamble)\n' + gcode += linenumber() + "(Begin preamble)\n" for line in PREAMBLE.splitlines(True): gcode += linenumber() + line # Write these settings AFTER the preamble, # to prevent the preamble from changing these: if OUTPUT_COMMENTS: - gcode += linenumber() + '(Default Configuration)\n' - gcode += linenumber() + MOTION_MODE + '\n' - gcode += linenumber() + UNITS + '\n' - gcode += linenumber() + WORK_PLANE + '\n' + gcode += linenumber() + "(Default Configuration)\n" + gcode += linenumber() + MOTION_MODE + "\n" + gcode += linenumber() + UNITS + "\n" + gcode += linenumber() + WORK_PLANE + "\n" for obj in objectslist: # Debug... # print('\n' + '*'*70 + '\n') # dump(obj) # print('\n' + '*'*70 + '\n') - if not hasattr(obj, 'Path'): - print('The object ' + obj.Name + - ' is not a path. Please select only path and Compounds.') + if not hasattr(obj, "Path"): + print( + "The object " + + obj.Name + + " is not a path. Please select only path and Compounds." + ) return # Skip inactive operations: - if PathUtil.opProperty(obj, 'Active') is False: + if PathUtil.opProperty(obj, "Active") is False: continue # Do the pre_op: if OUTPUT_BCNC: - gcode += linenumber() + '(Block-name: ' + obj.Label + ')\n' - gcode += linenumber() + '(Block-expand: 0)\n' - gcode += linenumber() + '(Block-enable: 1)\n' + gcode += linenumber() + "(Block-name: " + obj.Label + ")\n" + gcode += linenumber() + "(Block-expand: 0)\n" + gcode += linenumber() + "(Block-enable: 1)\n" if OUTPUT_COMMENTS: - gcode += linenumber() + '(Begin operation: ' + obj.Label + ')\n' + gcode += linenumber() + "(Begin operation: " + obj.Label + ")\n" for line in PRE_OPERATION.splitlines(True): gcode += linenumber() + line # Get coolant mode: - coolantMode = 'None' # None is the word returned from the operation - if hasattr(obj, 'CoolantMode') or hasattr(obj, 'Base') and \ - hasattr(obj.Base, 'CoolantMode'): - if hasattr(obj, 'CoolantMode'): + coolantMode = "None" # None is the word returned from the operation + if ( + hasattr(obj, "CoolantMode") + or hasattr(obj, "Base") + and hasattr(obj.Base, "CoolantMode") + ): + if hasattr(obj, "CoolantMode"): coolantMode = obj.CoolantMode else: coolantMode = obj.Base.CoolantMode # Turn coolant on if required: if OUTPUT_COMMENTS: - if not coolantMode == 'None': - gcode += linenumber() + '(Coolant On:' + coolantMode + ')\n' - if coolantMode == 'Flood': - gcode += linenumber() + 'M8\n' - if coolantMode == 'Mist': - gcode += linenumber() + 'M7\n' + if not coolantMode == "None": + gcode += linenumber() + "(Coolant On:" + coolantMode + ")\n" + if coolantMode == "Flood": + gcode += linenumber() + "M8\n" + if coolantMode == "Mist": + gcode += linenumber() + "M7\n" # Parse the op: gcode += parse(obj) # Do the post_op: if OUTPUT_COMMENTS and OUTPUT_FINISH: - gcode += linenumber() + '(Finish operation: ' + obj.Label + ')\n' + gcode += linenumber() + "(Finish operation: " + obj.Label + ")\n" for line in POST_OPERATION.splitlines(True): gcode += linenumber() + line # Turn coolant off if previously enabled: - if not coolantMode == 'None': + if not coolantMode == "None": if OUTPUT_COMMENTS: - gcode += linenumber() + '(Coolant Off:' + coolantMode + ')\n' - gcode += linenumber() + 'M9\n' + gcode += linenumber() + "(Coolant Off:" + coolantMode + ")\n" + gcode += linenumber() + "M9\n" # Do the post_amble: if OUTPUT_BCNC: - gcode += linenumber() + '(Block-name: post_amble)\n' - gcode += linenumber() + '(Block-expand: 0)\n' - gcode += linenumber() + '(Block-enable: 1)\n' + gcode += linenumber() + "(Block-name: post_amble)\n" + gcode += linenumber() + "(Block-expand: 0)\n" + gcode += linenumber() + "(Block-enable: 1)\n" if OUTPUT_COMMENTS: - gcode += linenumber() + '(Begin postamble)\n' + gcode += linenumber() + "(Begin postamble)\n" for line in POSTAMBLE.splitlines(True): gcode += linenumber() + line # Optionally add a final XYZ position to the end of the gcode: if RETURN_TO: - first_comma = RETURN_TO.find(',') - last_comma = RETURN_TO.rfind(',') # == first_comma if only one comma - ref_X = ' X' + RETURN_TO[0: first_comma].strip() + first_comma = RETURN_TO.find(",") + last_comma = RETURN_TO.rfind(",") # == first_comma if only one comma + ref_X = " X" + RETURN_TO[0:first_comma].strip() # Z is optional: if last_comma != first_comma: - ref_Z = ' Z' + RETURN_TO[last_comma + 1:].strip() - ref_Y = ' Y' + RETURN_TO[first_comma + 1:last_comma].strip() + ref_Z = " Z" + RETURN_TO[last_comma + 1 :].strip() + ref_Y = " Y" + RETURN_TO[first_comma + 1 : last_comma].strip() else: - ref_Z = '' - ref_Y = ' Y' + RETURN_TO[first_comma + 1:].strip() + ref_Z = "" + ref_Y = " Y" + RETURN_TO[first_comma + 1 :].strip() - gcode += linenumber() + 'G0' + ref_X + ref_Y + ref_Z + '\n' + gcode += linenumber() + "G0" + ref_X + ref_Y + ref_Z + "\n" # Optionally add recommended Marlin 2.x configuration to gcode file: if OUTPUT_MARLIN_CONFIG: - gcode += linenumber() + '(Marlin 2.x Configuration)\n' - gcode += linenumber() + '(The following should be enabled in)\n' - gcode += linenumber() + '(the configuration files of Marlin 2.x)\n' - gcode += linenumber() + '(#define ARC_SUPPORT)\n' - gcode += linenumber() + '(#define CNC_COORDINATE_SYSTEMS)\n' - gcode += linenumber() + '(#define PAREN_COMMENTS)\n' - gcode += linenumber() + '(#define GCODE_MOTION_MODES)\n' - gcode += linenumber() + '(#define G0_FEEDRATE)\n' - gcode += linenumber() + '(define VARIABLE_G0_FEEDRATE)\n' + gcode += linenumber() + "(Marlin 2.x Configuration)\n" + gcode += linenumber() + "(The following should be enabled in)\n" + gcode += linenumber() + "(the configuration files of Marlin 2.x)\n" + gcode += linenumber() + "(#define ARC_SUPPORT)\n" + gcode += linenumber() + "(#define CNC_COORDINATE_SYSTEMS)\n" + gcode += linenumber() + "(#define PAREN_COMMENTS)\n" + gcode += linenumber() + "(#define GCODE_MOTION_MODES)\n" + gcode += linenumber() + "(#define G0_FEEDRATE)\n" + gcode += linenumber() + "(define VARIABLE_G0_FEEDRATE)\n" # Show the gcode result dialog: if FreeCAD.GuiUp and SHOW_EDITOR: @@ -469,26 +468,26 @@ def export(objectslist, filename, argstring): else: final = gcode - print('Done postprocessing.') + print("Done postprocessing.") # Write the file: - with open(filename, 'w') as fp: + with open(filename, "w") as fp: fp.write(final) def linenumber(): if not OUTPUT_LINE_NUMBERS: - return '' + return "" global LINENR global LINEINCR LINENR += LINEINCR - return 'N' + str(LINENR) + ' ' + return "N" + str(LINENR) + " " def format_outlist(strTable): # construct the line for the final output global COMMAND_SPACE - s = '' + s = "" for w in strTable: s += w + COMMAND_SPACE return s.strip() @@ -501,27 +500,46 @@ def parse(pathobj): global CURRENT_Y global CURRENT_Z - out = '' + out = "" lastcommand = None - precision_string = '.' + str(PRECISION) + 'f' + precision_string = "." + str(PRECISION) + "f" - params = ['X', 'Y', 'Z', 'A', 'B', 'C', 'U', 'V', 'W', 'I', 'J', 'K', 'F', - 'S', 'T', 'Q', 'R', 'L', 'P'] + params = [ + "X", + "Y", + "Z", + "A", + "B", + "C", + "U", + "V", + "W", + "I", + "J", + "K", + "F", + "S", + "T", + "Q", + "R", + "L", + "P", + ] - if hasattr(pathobj, 'Group'): # We have a compound or project. + if hasattr(pathobj, "Group"): # We have a compound or project. if OUTPUT_COMMENTS: - out += linenumber() + '(Compound: ' + pathobj.Label + ')\n' + out += linenumber() + "(Compound: " + pathobj.Label + ")\n" for p in pathobj.Group: out += parse(p) return out else: # Parsing simple path # groups might contain non-path things like stock. - if not hasattr(pathobj, 'Path'): + if not hasattr(pathobj, "Path"): return out if OUTPUT_COMMENTS and OUTPUT_PATH: - out += linenumber() + '(Path: ' + pathobj.Label + ')\n' + out += linenumber() + "(Path: " + pathobj.Label + ")\n" for c in pathobj.Path.Commands: outlist = [] @@ -538,96 +556,102 @@ def parse(pathobj): # Add the remaining parameters in order: for param in params: if param in c.Parameters: - if param == 'F': + if param == "F": if command not in RAPID_MOVES: feedRate = Units.Quantity( - c.Parameters['F'], FreeCAD.Units.Velocity) + c.Parameters["F"], FreeCAD.Units.Velocity + ) if feedRate.getValueAs(UNIT_FEED_FORMAT) > 0.0: - outlist.append(param + format(float( - feedRate.getValueAs(UNIT_FEED_FORMAT)), - precision_string)) - elif param in ['T', 'H', 'D', 'S', 'P', 'L']: + outlist.append( + param + + format( + float(feedRate.getValueAs(UNIT_FEED_FORMAT)), + precision_string, + ) + ) + elif param in ["T", "H", "D", "S", "P", "L"]: outlist.append(param + str(c.Parameters[param])) - elif param in ['A', 'B', 'C']: - outlist.append(param + format( - c.Parameters[param], precision_string)) + elif param in ["A", "B", "C"]: + outlist.append( + param + format(c.Parameters[param], precision_string) + ) # [X, Y, Z, U, V, W, I, J, K, R, Q] else: - pos = Units.Quantity( - c.Parameters[param], FreeCAD.Units.Length) - outlist.append(param + format(float( - pos.getValueAs(UNIT_FORMAT)), precision_string)) + pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length) + outlist.append( + param + + format( + float(pos.getValueAs(UNIT_FORMAT)), precision_string + ) + ) # Store the latest command: lastcommand = command # Capture the current position for subsequent calculations: if command in MOTION_COMMANDS: - if 'X' in c.Parameters: - CURRENT_X = Units.Quantity( - c.Parameters['X'], FreeCAD.Units.Length) - if 'Y' in c.Parameters: - CURRENT_Y = Units.Quantity( - c.Parameters['Y'], FreeCAD.Units.Length) - if 'Z' in c.Parameters: - CURRENT_Z = Units.Quantity( - c.Parameters['Z'], FreeCAD.Units.Length) + if "X" in c.Parameters: + CURRENT_X = Units.Quantity(c.Parameters["X"], FreeCAD.Units.Length) + if "Y" in c.Parameters: + CURRENT_Y = Units.Quantity(c.Parameters["Y"], FreeCAD.Units.Length) + if "Z" in c.Parameters: + CURRENT_Z = Units.Quantity(c.Parameters["Z"], FreeCAD.Units.Length) - if command in ('G98', 'G99'): + if command in ("G98", "G99"): DRILL_RETRACT_MODE = command if TRANSLATE_DRILL_CYCLES: - if command in ('G81', 'G82', 'G83'): + if command in ("G81", "G82", "G83"): out += drill_translate(outlist, command, c.Parameters) # Erase the line just translated: outlist = [] if SPINDLE_WAIT > 0: - if command in ('M3', 'M03', 'M4', 'M04'): - out += linenumber() + format_outlist(outlist) + '\n' + if command in ("M3", "M03", "M4", "M04"): + out += linenumber() + format_outlist(outlist) + "\n" # Marlin: P for milliseconds, S for seconds, change P to S out += linenumber() - out += format_outlist(['G4', 'S%s' % SPINDLE_WAIT]) - out += '\n' + out += format_outlist(["G4", "S%s" % SPINDLE_WAIT]) + out += "\n" outlist = [] # Check for Tool Change: - if command in ('M6', 'M06'): + if command in ("M6", "M06"): if OUTPUT_COMMENTS: - out += linenumber() + '(Begin toolchange)\n' + out += linenumber() + "(Begin toolchange)\n" if OUTPUT_TOOL_CHANGE: for line in TOOL_CHANGE.splitlines(True): out += linenumber() + line if not OUTPUT_TOOL_CHANGE and OUTPUT_COMMENTS: - outlist[0] = '(' + outlist[0] - outlist[-1] = outlist[-1] + ')' + outlist[0] = "(" + outlist[0] + outlist[-1] = outlist[-1] + ")" if not OUTPUT_TOOL_CHANGE and not OUTPUT_COMMENTS: outlist = [] - if command == 'message': + if command == "message": if OUTPUT_COMMENTS: outlist.pop(0) # remove the command else: out = [] if command in SUPPRESS_COMMANDS: - outlist[0] = '(' + outlist[0] - outlist[-1] = outlist[-1] + ')' + outlist[0] = "(" + outlist[0] + outlist[-1] = outlist[-1] + ")" # Remove embedded comments: if not OUTPUT_COMMENTS: tmplist = [] list_index = 0 while list_index < len(outlist): - left_index = outlist[list_index].find('(') + left_index = outlist[list_index].find("(") if left_index == -1: # Not a comment tmplist.append(outlist[list_index]) else: # This line contains a comment, and possibly more - right_index = outlist[list_index].find(')') - comment_area = outlist[list_index][ - left_index: right_index + 1] - line_minus_comment = outlist[list_index].replace( - comment_area, '').strip() + right_index = outlist[list_index].find(")") + comment_area = outlist[list_index][left_index : right_index + 1] + line_minus_comment = ( + outlist[list_index].replace(comment_area, "").strip() + ) if line_minus_comment: # Line contained more than just a comment tmplist.append(line_minus_comment) @@ -637,7 +661,7 @@ def parse(pathobj): # Prepend a line number and append a newline if len(outlist) >= 1: - out += linenumber() + format_outlist(outlist) + '\n' + out += linenumber() + format_outlist(outlist) + "\n" return out @@ -658,61 +682,56 @@ def drill_translate(outlist, cmd, params): global UNIT_FEED_FORMAT class Drill: # Using a class is necessary for the nested functions. - gcode = '' + gcode = "" - strFormat = '.' + str(PRECISION) + 'f' + strFormat = "." + str(PRECISION) + "f" if OUTPUT_COMMENTS: # Comment the original command - outlist[0] = '(' + outlist[0] - outlist[-1] = outlist[-1] + ')' - Drill.gcode += linenumber() + format_outlist(outlist) + '\n' + outlist[0] = "(" + outlist[0] + outlist[-1] = outlist[-1] + ")" + Drill.gcode += linenumber() + format_outlist(outlist) + "\n" # Cycle conversion only converts the cycles in the XY plane (G17). # --> ZX (G18) and YZ (G19) planes produce false gcode. - drill_X = Units.Quantity(params['X'], FreeCAD.Units.Length) - drill_Y = Units.Quantity(params['Y'], FreeCAD.Units.Length) - drill_Z = Units.Quantity(params['Z'], FreeCAD.Units.Length) - drill_R = Units.Quantity(params['R'], FreeCAD.Units.Length) - drill_F = Units.Quantity(params['F'], FreeCAD.Units.Velocity) - if cmd == 'G82': - drill_DwellTime = params['P'] - elif cmd == 'G83': - drill_Step = Units.Quantity(params['Q'], FreeCAD.Units.Length) + drill_X = Units.Quantity(params["X"], FreeCAD.Units.Length) + drill_Y = Units.Quantity(params["Y"], FreeCAD.Units.Length) + drill_Z = Units.Quantity(params["Z"], FreeCAD.Units.Length) + drill_R = Units.Quantity(params["R"], FreeCAD.Units.Length) + drill_F = Units.Quantity(params["F"], FreeCAD.Units.Velocity) + if cmd == "G82": + drill_DwellTime = params["P"] + elif cmd == "G83": + drill_Step = Units.Quantity(params["Q"], FreeCAD.Units.Length) # R less than Z is error if drill_R < drill_Z: - Drill.gcode += linenumber() + '(drill cycle error: R less than Z )\n' + Drill.gcode += linenumber() + "(drill cycle error: R less than Z )\n" return Drill.gcode # Z height to retract to when drill cycle is done: - if DRILL_RETRACT_MODE == 'G98' and CURRENT_Z > drill_R: + if DRILL_RETRACT_MODE == "G98" and CURRENT_Z > drill_R: RETRACT_Z = CURRENT_Z else: RETRACT_Z = drill_R # Z motion nested functions: def rapid_Z_to(new_Z): - Drill.gcode += linenumber() + 'G0 Z' - Drill.gcode += format( - float(new_Z.getValueAs(UNIT_FORMAT)), strFormat) + '\n' + Drill.gcode += linenumber() + "G0 Z" + Drill.gcode += format(float(new_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n" def feed_Z_to(new_Z): - Drill.gcode += linenumber() + 'G1 Z' - Drill.gcode += format( - float(new_Z.getValueAs(UNIT_FORMAT)), strFormat) + ' F' - Drill.gcode += format( - float(drill_F.getValueAs(UNIT_FEED_FORMAT)), '.2f') + '\n' + Drill.gcode += linenumber() + "G1 Z" + Drill.gcode += format(float(new_Z.getValueAs(UNIT_FORMAT)), strFormat) + " F" + Drill.gcode += format(float(drill_F.getValueAs(UNIT_FEED_FORMAT)), ".2f") + "\n" # Make sure that Z is not below RETRACT_Z: if CURRENT_Z < RETRACT_Z: rapid_Z_to(RETRACT_Z) # Rapid to hole position XY: - Drill.gcode += linenumber() + 'G0 X' - Drill.gcode += format( - float(drill_X.getValueAs(UNIT_FORMAT)), strFormat) + ' Y' - Drill.gcode += format( - float(drill_Y.getValueAs(UNIT_FORMAT)), strFormat) + '\n' + Drill.gcode += linenumber() + "G0 X" + Drill.gcode += format(float(drill_X.getValueAs(UNIT_FORMAT)), strFormat) + " Y" + Drill.gcode += format(float(drill_Y.getValueAs(UNIT_FORMAT)), strFormat) + "\n" # Rapid to R: rapid_Z_to(drill_R) @@ -728,13 +747,13 @@ def drill_translate(outlist, cmd, params): # * G99 After the hole has been drilled, retract to R height * # * Select G99 only if safe to move from hole to hole at the R height * # ************************************************************************* - if cmd in ('G81', 'G82'): + if cmd in ("G81", "G82"): feed_Z_to(drill_Z) # Drill hole in one step - if cmd == 'G82': # Dwell time delay at the bottom of the hole - Drill.gcode += linenumber() + 'G4 S' + str(drill_DwellTime) + '\n' + if cmd == "G82": # Dwell time delay at the bottom of the hole + Drill.gcode += linenumber() + "G4 S" + str(drill_DwellTime) + "\n" # Marlin uses P for milliseconds, S for seconds, change P to S - elif cmd == 'G83': # Peck drill cycle: + elif cmd == "G83": # Peck drill cycle: chip_Space = drill_Step * 0.5 next_Stop_Z = drill_R - drill_Step while next_Stop_Z >= drill_Z: From bfaca44221a85137829cde9bcad73f1850546f73 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 4 Jan 2022 13:49:27 -0600 Subject: [PATCH 57/65] translation cleanup (postprocessing files) --- src/Mod/Path/PathScripts/PathPost.py | 16 ++++------------ src/Mod/Path/PathScripts/PostUtils.py | 2 ++ src/Mod/Path/PathScripts/post/gcode_pre.py | 20 ++++++++++---------- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPost.py b/src/Mod/Path/PathScripts/PathPost.py index 3a5017d259..841872b996 100644 --- a/src/Mod/Path/PathScripts/PathPost.py +++ b/src/Mod/Path/PathScripts/PathPost.py @@ -37,17 +37,13 @@ import os from PathScripts.PathPostProcessor import PostProcessor from PySide import QtCore, QtGui from datetime import datetime +from PySide.QtCore import QT_TRANSLATE_NOOP LOG_MODULE = PathLog.thisModule() PathLog.setLevel(PathLog.Level.INFO, LOG_MODULE) -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) - - class _TempObject: # pylint: disable=no-init Path = None @@ -183,11 +179,9 @@ class CommandPathPost: def GetResources(self): return { "Pixmap": "Path_Post", - "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_Post", "Post Process"), + "MenuText": QT_TRANSLATE_NOOP("Path_Post", "Post Process"), "Accel": "P, P", - "ToolTip": QtCore.QT_TRANSLATE_NOOP( - "Path_Post", "Post Process the selected Job" - ), + "ToolTip": QT_TRANSLATE_NOOP("Path_Post", "Post Process the selected Job"), } def IsActive(self): @@ -224,9 +218,7 @@ class CommandPathPost: def Activated(self): PathLog.track() - FreeCAD.ActiveDocument.openTransaction( - translate("Path_Post", "Post Process the Selected path(s)") - ) + FreeCAD.ActiveDocument.openTransaction("Post Process the Selected path(s)") FreeCADGui.addModule("PathScripts.PathPost") # Attempt to figure out what the user wants to post-process diff --git a/src/Mod/Path/PathScripts/PostUtils.py b/src/Mod/Path/PathScripts/PostUtils.py index 7f05a1fd19..4d554188f7 100644 --- a/src/Mod/Path/PathScripts/PostUtils.py +++ b/src/Mod/Path/PathScripts/PostUtils.py @@ -29,6 +29,8 @@ These are a common functions and classes for creating custom post processors. from PySide import QtCore, QtGui import FreeCAD +translate = FreeCAD.Qt.translate + FreeCADGui = None if FreeCAD.GuiUp: import FreeCADGui diff --git a/src/Mod/Path/PathScripts/post/gcode_pre.py b/src/Mod/Path/PathScripts/post/gcode_pre.py index b24187ebc6..c89a3c269a 100644 --- a/src/Mod/Path/PathScripts/post/gcode_pre.py +++ b/src/Mod/Path/PathScripts/post/gcode_pre.py @@ -50,14 +50,14 @@ import re import PathScripts.PathCustom as PathCustom import PathScripts.PathCustomGui as PathCustomGui import PathScripts.PathOpGui as PathOpGui -from PySide import QtCore +from PySide.QtCore import QT_TRANSLATE_NOOP -# LEVEL = PathLog.Level.DEBUG -LEVEL = PathLog.Level.INFO -PathLog.setLevel(LEVEL, PathLog.thisModule()) -if LEVEL == PathLog.Level.DEBUG: +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # to distinguish python built-in open function from the one declared below @@ -66,7 +66,7 @@ if open.__module__ in ["__builtin__", "io"]: def open(filename): - "called when freecad opens a file." + """called when freecad opens a file.""" PathLog.track(filename) docname = os.path.splitext(os.path.basename(filename))[0] doc = FreeCAD.newDocument(docname) @@ -83,7 +83,7 @@ def matchToolController(op, toolnumber): def insert(filename, docname): - "called when freecad imports a file" + """called when freecad imports a file""" PathLog.track(filename) gfile = pythonopen(filename) gcode = gfile.read() @@ -118,7 +118,7 @@ def insert(filename, docname): PathCustom.Create, PathCustomGui.TaskPanelOpPage, "Path_Custom", - QtCore.QT_TRANSLATE_NOOP("Path_Custom", "Custom"), + QT_TRANSLATE_NOOP("Path_Custom", "Custom"), "", "", ) @@ -153,7 +153,7 @@ def parse(inputstring): axis = ["X", "Y", "Z", "A", "B", "C", "U", "V", "W"] - print("preprocessing...") + FreeCAD.Console.PrintMessage("preprocessing...") PathLog.track(inputstring) # split the input by line lines = inputstring.splitlines() @@ -191,7 +191,7 @@ def parse(inputstring): elif currcommand[0] in axis and lastcommand: output.append(lastcommand + " " + lin) - print("done preprocessing.") + FreeCAD.Console.PrintMessage("done preprocessing.") return output From ddd680e93273301d970c6165551337a2ab447931 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 4 Jan 2022 14:26:52 -0600 Subject: [PATCH 58/65] black reformat (setupsheet) --- src/Mod/Path/PathScripts/PathSetupSheet.py | 289 +++++++++++++----- src/Mod/Path/PathScripts/PathSetupSheetGui.py | 82 +++-- .../PathSetupSheetOpPrototypeGui.py | 84 ++--- src/Mod/Path/PathScripts/PathSimpleCopy.py | 44 ++- src/Mod/Path/PathScripts/PathSimulatorGui.py | 147 ++++++--- 5 files changed, 439 insertions(+), 207 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSetupSheet.py b/src/Mod/Path/PathScripts/PathSetupSheet.py index 79fbc339c8..d2681bd742 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheet.py +++ b/src/Mod/Path/PathScripts/PathSetupSheet.py @@ -45,21 +45,32 @@ def translate(context, text, disambig=None): class Template: # pylint: disable=no-init - HorizRapid = 'HorizRapid' - VertRapid = 'VertRapid' - CoolantMode = 'CoolantMode' - SafeHeightOffset = 'SafeHeightOffset' - SafeHeightExpression = 'SafeHeightExpression' - ClearanceHeightOffset = 'ClearanceHeightOffset' - ClearanceHeightExpression = 'ClearanceHeightExpression' - StartDepthExpression = 'StartDepthExpression' - FinalDepthExpression = 'FinalDepthExpression' - StepDownExpression = 'StepDownExpression' - Fixtures = 'Fixtures' - OrderOutputBy = 'OrderOutputBy' - SplitOutput = 'SplitOutput' + HorizRapid = "HorizRapid" + VertRapid = "VertRapid" + CoolantMode = "CoolantMode" + SafeHeightOffset = "SafeHeightOffset" + SafeHeightExpression = "SafeHeightExpression" + ClearanceHeightOffset = "ClearanceHeightOffset" + ClearanceHeightExpression = "ClearanceHeightExpression" + StartDepthExpression = "StartDepthExpression" + FinalDepthExpression = "FinalDepthExpression" + StepDownExpression = "StepDownExpression" + Fixtures = "Fixtures" + OrderOutputBy = "OrderOutputBy" + SplitOutput = "SplitOutput" - All = [HorizRapid, VertRapid, CoolantMode, SafeHeightOffset, SafeHeightExpression, ClearanceHeightOffset, ClearanceHeightExpression, StartDepthExpression, FinalDepthExpression, StepDownExpression] + All = [ + HorizRapid, + VertRapid, + CoolantMode, + SafeHeightOffset, + SafeHeightExpression, + ClearanceHeightOffset, + ClearanceHeightExpression, + StartDepthExpression, + FinalDepthExpression, + StepDownExpression, + ] def _traverseTemplateAttributes(attrs, codec): @@ -82,46 +93,130 @@ def _traverseTemplateAttributes(attrs, codec): class SetupSheet: - '''Property container object used by a Job to hold global reference values. ''' + """Property container object used by a Job to hold global reference values.""" - TemplateReference = '${SetupSheet}' + TemplateReference = "${SetupSheet}" - DefaultSafeHeightOffset = '3 mm' - DefaultClearanceHeightOffset = '5 mm' - DefaultSafeHeightExpression = "OpStockZMax+${SetupSheet}.SafeHeightOffset" + DefaultSafeHeightOffset = "3 mm" + DefaultClearanceHeightOffset = "5 mm" + DefaultSafeHeightExpression = "OpStockZMax+${SetupSheet}.SafeHeightOffset" DefaultClearanceHeightExpression = "OpStockZMax+${SetupSheet}.ClearanceHeightOffset" - DefaultStartDepthExpression = 'OpStartDepth' - DefaultFinalDepthExpression = 'OpFinalDepth' - DefaultStepDownExpression = 'OpToolDiameter' + DefaultStartDepthExpression = "OpStartDepth" + DefaultFinalDepthExpression = "OpFinalDepth" + DefaultStepDownExpression = "OpToolDiameter" - DefaultCoolantModes = ['None', 'Flood', 'Mist'] + DefaultCoolantModes = ["None", "Flood", "Mist"] def __init__(self, obj): self.obj = obj - obj.addProperty('App::PropertySpeed', 'VertRapid', 'ToolController', translate('PathSetupSheet', 'Default speed for horizontal rapid moves.')) - obj.addProperty('App::PropertySpeed', 'HorizRapid', 'ToolController', translate('PathSetupSheet', 'Default speed for vertical rapid moves.')) + obj.addProperty( + "App::PropertySpeed", + "VertRapid", + "ToolController", + translate("PathSetupSheet", "Default speed for horizontal rapid moves."), + ) + obj.addProperty( + "App::PropertySpeed", + "HorizRapid", + "ToolController", + translate("PathSetupSheet", "Default speed for vertical rapid moves."), + ) - obj.addProperty('App::PropertyStringList', 'CoolantModes', 'CoolantMode', translate('PathSetupSheet', 'Coolant Modes')) - obj.addProperty('App::PropertyEnumeration', 'CoolantMode', 'CoolantMode', translate('PathSetupSheet', 'Default coolant mode.')) + obj.addProperty( + "App::PropertyStringList", + "CoolantModes", + "CoolantMode", + translate("PathSetupSheet", "Coolant Modes"), + ) + obj.addProperty( + "App::PropertyEnumeration", + "CoolantMode", + "CoolantMode", + translate("PathSetupSheet", "Default coolant mode."), + ) - obj.addProperty('App::PropertyLength', 'SafeHeightOffset', 'OperationHeights', translate('PathSetupSheet', 'The usage of this field depends on SafeHeightExpression - by default its value is added to StartDepth and used for SafeHeight of an operation.')) - obj.addProperty('App::PropertyString', 'SafeHeightExpression', 'OperationHeights', translate('PathSetupSheet', 'Expression set for the SafeHeight of new operations.')) - obj.addProperty('App::PropertyLength', 'ClearanceHeightOffset', 'OperationHeights', translate('PathSetupSheet', 'The usage of this field depends on ClearanceHeightExpression - by default is value is added to StartDepth and used for ClearanceHeight of an operation.')) - obj.addProperty('App::PropertyString', 'ClearanceHeightExpression', 'OperationHeights', translate('PathSetupSheet', 'Expression set for the ClearanceHeight of new operations.')) + obj.addProperty( + "App::PropertyLength", + "SafeHeightOffset", + "OperationHeights", + translate( + "PathSetupSheet", + "The usage of this field depends on SafeHeightExpression - by default its value is added to StartDepth and used for SafeHeight of an operation.", + ), + ) + obj.addProperty( + "App::PropertyString", + "SafeHeightExpression", + "OperationHeights", + translate( + "PathSetupSheet", "Expression set for the SafeHeight of new operations." + ), + ) + obj.addProperty( + "App::PropertyLength", + "ClearanceHeightOffset", + "OperationHeights", + translate( + "PathSetupSheet", + "The usage of this field depends on ClearanceHeightExpression - by default is value is added to StartDepth and used for ClearanceHeight of an operation.", + ), + ) + obj.addProperty( + "App::PropertyString", + "ClearanceHeightExpression", + "OperationHeights", + translate( + "PathSetupSheet", + "Expression set for the ClearanceHeight of new operations.", + ), + ) - obj.addProperty('App::PropertyString', 'StartDepthExpression', 'OperationDepths', translate('PathSetupSheet', 'Expression used for StartDepth of new operations.')) - obj.addProperty('App::PropertyString', 'FinalDepthExpression', 'OperationDepths', translate('PathSetupSheet', 'Expression used for FinalDepth of new operations.')) - obj.addProperty('App::PropertyString', 'StepDownExpression', 'OperationDepths', translate('PathSetupSheet', 'Expression used for StepDown of new operations.')) + obj.addProperty( + "App::PropertyString", + "StartDepthExpression", + "OperationDepths", + translate( + "PathSetupSheet", "Expression used for StartDepth of new operations." + ), + ) + obj.addProperty( + "App::PropertyString", + "FinalDepthExpression", + "OperationDepths", + translate( + "PathSetupSheet", "Expression used for FinalDepth of new operations." + ), + ) + obj.addProperty( + "App::PropertyString", + "StepDownExpression", + "OperationDepths", + translate( + "PathSetupSheet", "Expression used for StepDown of new operations." + ), + ) - obj.SafeHeightOffset = self.decodeAttributeString(self.DefaultSafeHeightOffset) - obj.ClearanceHeightOffset = self.decodeAttributeString(self.DefaultClearanceHeightOffset) - obj.SafeHeightExpression = self.decodeAttributeString(self.DefaultSafeHeightExpression) - obj.ClearanceHeightExpression = self.decodeAttributeString(self.DefaultClearanceHeightExpression) + obj.SafeHeightOffset = self.decodeAttributeString(self.DefaultSafeHeightOffset) + obj.ClearanceHeightOffset = self.decodeAttributeString( + self.DefaultClearanceHeightOffset + ) + obj.SafeHeightExpression = self.decodeAttributeString( + self.DefaultSafeHeightExpression + ) + obj.ClearanceHeightExpression = self.decodeAttributeString( + self.DefaultClearanceHeightExpression + ) - obj.StartDepthExpression = self.decodeAttributeString(self.DefaultStartDepthExpression) - obj.FinalDepthExpression = self.decodeAttributeString(self.DefaultFinalDepthExpression) - obj.StepDownExpression = self.decodeAttributeString(self.DefaultStepDownExpression) + obj.StartDepthExpression = self.decodeAttributeString( + self.DefaultStartDepthExpression + ) + obj.FinalDepthExpression = self.decodeAttributeString( + self.DefaultFinalDepthExpression + ) + obj.StepDownExpression = self.decodeAttributeString( + self.DefaultStepDownExpression + ) obj.CoolantModes = self.DefaultCoolantModes obj.CoolantMode = self.DefaultCoolantModes @@ -133,22 +228,34 @@ class SetupSheet: def __setstate__(self, state): for obj in FreeCAD.ActiveDocument.Objects: - if hasattr(obj, 'Proxy') and obj.Proxy == self: + if hasattr(obj, "Proxy") and obj.Proxy == self: self.obj = obj break return None def hasDefaultToolRapids(self): - return PathGeom.isRoughly(self.obj.VertRapid.Value, 0) and PathGeom.isRoughly(self.obj.HorizRapid.Value, 0) + return PathGeom.isRoughly(self.obj.VertRapid.Value, 0) and PathGeom.isRoughly( + self.obj.HorizRapid.Value, 0 + ) def hasDefaultOperationHeights(self): - if self.obj.SafeHeightOffset.UserString != FreeCAD.Units.Quantity(self.DefaultSafeHeightOffset).UserString: + if ( + self.obj.SafeHeightOffset.UserString + != FreeCAD.Units.Quantity(self.DefaultSafeHeightOffset).UserString + ): return False - if self.obj.ClearanceHeightOffset.UserString != FreeCAD.Units.Quantity(self.DefaultClearanceHeightOffset).UserString: + if ( + self.obj.ClearanceHeightOffset.UserString + != FreeCAD.Units.Quantity(self.DefaultClearanceHeightOffset).UserString + ): return False - if self.obj.SafeHeightExpression != self.decodeAttributeString(self.DefaultSafeHeightExpression): + if self.obj.SafeHeightExpression != self.decodeAttributeString( + self.DefaultSafeHeightExpression + ): return False - if self.obj.ClearanceHeightExpression != self.decodeAttributeString(self.DefaultClearanceHeightExpression): + if self.obj.ClearanceHeightExpression != self.decodeAttributeString( + self.DefaultClearanceHeightExpression + ): return False return True @@ -165,7 +272,7 @@ class SetupSheet: return self.obj.CoolantMode == "None" def setFromTemplate(self, attrs): - '''setFromTemplate(attrs) ... sets the default values from the given dictionary.''' + """setFromTemplate(attrs) ... sets the default values from the given dictionary.""" for name in Template.All: if attrs.get(name) is not None: setattr(self.obj, name, attrs[name]) @@ -180,15 +287,22 @@ class SetupSheet: prop = prototype.getProperty(propName) propertyName = OpPropertyName(opName, propName) propertyGroup = OpPropertyGroup(opName) - prop.setupProperty(self.obj, propertyName, propertyGroup, prop.valueFromString(value)) + prop.setupProperty( + self.obj, + propertyName, + propertyGroup, + prop.valueFromString(value), + ) - def templateAttributes(self, - includeRapids=True, - includeCoolantMode=True, - includeHeights=True, - includeDepths=True, - includeOps=None): - '''templateAttributes(includeRapids, includeHeights, includeDepths) ... answers a dictionary with the default values.''' + def templateAttributes( + self, + includeRapids=True, + includeCoolantMode=True, + includeHeights=True, + includeDepths=True, + includeOps=None, + ): + """templateAttributes(includeRapids, includeHeights, includeDepths) ... answers a dictionary with the default values.""" attrs = {} if includeRapids: @@ -199,15 +313,19 @@ class SetupSheet: attrs[Template.CoolantMode] = self.obj.CoolantMode if includeHeights: - attrs[Template.SafeHeightOffset] = self.obj.SafeHeightOffset.UserString - attrs[Template.SafeHeightExpression] = self.obj.SafeHeightExpression - attrs[Template.ClearanceHeightOffset] = self.obj.ClearanceHeightOffset.UserString - attrs[Template.ClearanceHeightExpression] = self.obj.ClearanceHeightExpression + attrs[Template.SafeHeightOffset] = self.obj.SafeHeightOffset.UserString + attrs[Template.SafeHeightExpression] = self.obj.SafeHeightExpression + attrs[ + Template.ClearanceHeightOffset + ] = self.obj.ClearanceHeightOffset.UserString + attrs[ + Template.ClearanceHeightExpression + ] = self.obj.ClearanceHeightExpression if includeDepths: attrs[Template.StartDepthExpression] = self.obj.StartDepthExpression attrs[Template.FinalDepthExpression] = self.obj.FinalDepthExpression - attrs[Template.StepDownExpression] = self.obj.StepDownExpression + attrs[Template.StepDownExpression] = self.obj.StepDownExpression if includeOps: for opName in includeOps: @@ -216,13 +334,15 @@ class SetupSheet: for propName in op.properties(): prop = OpPropertyName(opName, propName) if hasattr(self.obj, prop): - settings[propName] = PathUtil.getPropertyValueString(self.obj, prop) + settings[propName] = PathUtil.getPropertyValueString( + self.obj, prop + ) attrs[opName] = settings return attrs def expressionReference(self): - '''expressionReference() ... returns the string to be used in expressions''' + """expressionReference() ... returns the string to be used in expressions""" # Using the Name here and not the Label (both would be valid) because the Name 'fails early'. # # If there is a Name/Label conflict and an expression is bound to the Name we'll get an error @@ -244,23 +364,27 @@ class SetupSheet: return self.obj.Name def encodeAttributeString(self, attr): - '''encodeAttributeString(attr) ... return the encoded string of a template attribute.''' - return PathUtil.toUnicode(attr.replace(self.expressionReference(), self.TemplateReference)) + """encodeAttributeString(attr) ... return the encoded string of a template attribute.""" + return PathUtil.toUnicode( + attr.replace(self.expressionReference(), self.TemplateReference) + ) def decodeAttributeString(self, attr): - '''decodeAttributeString(attr) ... return the decoded string of a template attribute.''' - return PathUtil.toUnicode(attr.replace(self.TemplateReference, self.expressionReference())) + """decodeAttributeString(attr) ... return the decoded string of a template attribute.""" + return PathUtil.toUnicode( + attr.replace(self.TemplateReference, self.expressionReference()) + ) def encodeTemplateAttributes(self, attrs): - '''encodeTemplateAttributes(attrs) ... return a dictionary with all values encoded.''' + """encodeTemplateAttributes(attrs) ... return a dictionary with all values encoded.""" return _traverseTemplateAttributes(attrs, self.encodeAttributeString) def decodeTemplateAttributes(self, attrs): - '''decodeTemplateAttributes(attrs) ... expand template attributes to reference the receiver where applicable.''' + """decodeTemplateAttributes(attrs) ... expand template attributes to reference the receiver where applicable.""" return _traverseTemplateAttributes(attrs, self.decodeAttributeString) def operationsWithSettings(self): - '''operationsWithSettings() ... returns a list of operations which currently have some settings defined.''' + """operationsWithSettings() ... returns a list of operations which currently have some settings defined.""" ops = [] for name, value in PathUtil.keyValueIter(_RegisteredOps): for prop in value.registeredPropertyNames(name): @@ -283,23 +407,32 @@ class SetupSheet: def onDocumentRestored(self, obj): - if not hasattr(obj, 'CoolantModes'): - obj.addProperty('App::PropertyStringList', 'CoolantModes', 'CoolantMode', translate('PathSetupSheet', 'Coolant Modes')) + if not hasattr(obj, "CoolantModes"): + obj.addProperty( + "App::PropertyStringList", + "CoolantModes", + "CoolantMode", + translate("PathSetupSheet", "Coolant Modes"), + ) obj.CoolantModes = self.DefaultCoolantModes - if not hasattr(obj, 'CoolantMode'): - obj.addProperty('App::PropertyEnumeration', 'CoolantMode', 'CoolantMode', translate('PathSetupSheet', 'Default coolant mode.')) + if not hasattr(obj, "CoolantMode"): + obj.addProperty( + "App::PropertyEnumeration", + "CoolantMode", + "CoolantMode", + translate("PathSetupSheet", "Default coolant mode."), + ) obj.CoolantMode = self.DefaultCoolantModes -def Create(name='SetupSheet'): - obj = FreeCAD.ActiveDocument.addObject('App::FeaturePython', name) +def Create(name="SetupSheet"): + obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython", name) obj.Proxy = SetupSheet(obj) return obj class _RegisteredOp(object): - def __init__(self, factory, properties): self.factory = factory self.properties = properties @@ -319,7 +452,7 @@ def RegisterOperation(name, objFactory, setupProperties): def OpNamePrefix(name): - return name.replace('Path', '').replace(' ', '').replace('_', '') + return name.replace("Path", "").replace(" ", "").replace("_", "") def OpPropertyName(opName, propName): diff --git a/src/Mod/Path/PathScripts/PathSetupSheetGui.py b/src/Mod/Path/PathScripts/PathSetupSheetGui.py index 1584b964a0..540fd3612d 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheetGui.py +++ b/src/Mod/Path/PathScripts/PathSetupSheetGui.py @@ -22,7 +22,7 @@ import FreeCAD import FreeCADGui -import PathGui as PGui # ensure Path/Gui/Resources are loaded +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGui as PathGui import PathScripts.PathIconViewProvider as PathIconViewProvider import PathScripts.PathLog as PathLog @@ -54,8 +54,8 @@ else: class ViewProvider: - '''ViewProvider for a SetupSheet. - It's sole job is to provide an icon and invoke the TaskPanel on edit.''' + """ViewProvider for a SetupSheet. + It's sole job is to provide an icon and invoke the TaskPanel on edit.""" def __init__(self, vobj, name): PathLog.track(name) @@ -82,7 +82,7 @@ class ViewProvider: def getDisplayMode(self, mode): # pylint: disable=unused-argument - return 'Default' + return "Default" def setEdit(self, vobj, mode=0): # pylint: disable=unused-argument @@ -107,7 +107,7 @@ class ViewProvider: class Delegate(QtGui.QStyledItemDelegate): PropertyRole = QtCore.Qt.UserRole + 1 - EditorRole = QtCore.Qt.UserRole + 2 + EditorRole = QtCore.Qt.UserRole + 2 def createEditor(self, parent, option, index): # pylint: disable=unused-argument @@ -133,7 +133,7 @@ class Delegate(QtGui.QStyledItemDelegate): class OpTaskPanel: - '''Editor for an operation's property default values. + """Editor for an operation's property default values. The implementation is a simplified generic property editor with basically 3 fields - checkbox - if set a default value for the given property is set - name - a non-editable string with the property name @@ -141,7 +141,7 @@ class OpTaskPanel: The specific editor classes for a given property type are implemented in PathSetupSheetOpPrototypeGui which also provides a factory function. The properties are displayed in a table, each field occypying a column and each row representing - a single property.''' + a single property.""" def __init__(self, obj, name, op): self.name = name @@ -168,7 +168,7 @@ class OpTaskPanel: self.delegate = Delegate(self.form) self.model = QtGui.QStandardItemModel(len(self.props), 3, self.form) - self.model.setHorizontalHeaderLabels(['Set', 'Property', 'Value']) + self.model.setHorizontalHeaderLabels(["Set", "Property", "Value"]) for i, name in enumerate(self.props): prop = self.prototype.getProperty(name) @@ -179,10 +179,12 @@ class OpTaskPanel: self.model.setData(self.model.index(i, 0), isset, QtCore.Qt.EditRole) self.model.setData(self.model.index(i, 1), name, QtCore.Qt.EditRole) self.model.setData(self.model.index(i, 2), prop, Delegate.PropertyRole) - self.model.setData(self.model.index(i, 2), prop.displayString(), QtCore.Qt.DisplayRole) + self.model.setData( + self.model.index(i, 2), prop.displayString(), QtCore.Qt.DisplayRole + ) self.model.item(i, 0).setCheckable(True) - self.model.item(i, 0).setText('') + self.model.item(i, 0).setText("") self.model.item(i, 1).setEditable(False) self.model.item(i, 1).setToolTip(prop.info) self.model.item(i, 2).setToolTip(prop.info) @@ -213,7 +215,9 @@ class OpTaskPanel: propName = self.propertyName(name) enabled = self.model.item(i, 0).checkState() == QtCore.Qt.Checked if enabled and not prop.getValue() is None: - if prop.setupProperty(self.obj, propName, self.propertyGroup(), prop.getValue()): + if prop.setupProperty( + self.obj, propName, self.propertyGroup(), prop.getValue() + ): propertiesCreatedRemoved = True else: if hasattr(self.obj, propName): @@ -223,15 +227,21 @@ class OpTaskPanel: class OpsDefaultEditor: - '''Class to collect and display default property editors for all registered operations. + """Class to collect and display default property editors for all registered operations. If a form is given at creation time it will integrate with that form and provide an interface to switch between the editors of different operations. If no form is provided the class assumes that the UI is - taken care of somehow else and just serves as an interface to all operation editors.''' + taken care of somehow else and just serves as an interface to all operation editors.""" def __init__(self, obj, form): self.form = form self.obj = obj - self.ops = sorted([OpTaskPanel(self.obj, name, op) for name, op in PathUtil.keyValueIter(PathSetupSheet._RegisteredOps)], key=lambda op: op.name) + self.ops = sorted( + [ + OpTaskPanel(self.obj, name, op) + for name, op in PathUtil.keyValueIter(PathSetupSheet._RegisteredOps) + ], + key=lambda op: op.name, + ) if form: parent = form.tabOpDefaults for op in self.ops: @@ -281,7 +291,7 @@ class OpsDefaultEditor: class GlobalEditor(object): - '''Editor for the global properties which affect almost every operation.''' + """Editor for the global properties which affect almost every operation.""" def __init__(self, obj, form): self.form = form @@ -306,11 +316,13 @@ class GlobalEditor(object): if val != value: PathUtil.setProperty(self.obj, name, value) - updateExpression('StartDepthExpression', self.form.setupStartDepthExpr) - updateExpression('FinalDepthExpression', self.form.setupFinalDepthExpr) - updateExpression('StepDownExpression', self.form.setupStepDownExpr) - updateExpression('ClearanceHeightExpression', self.form.setupClearanceHeightExpr) - updateExpression('SafeHeightExpression', self.form.setupSafeHeightExpr) + updateExpression("StartDepthExpression", self.form.setupStartDepthExpr) + updateExpression("FinalDepthExpression", self.form.setupFinalDepthExpr) + updateExpression("StepDownExpression", self.form.setupStepDownExpr) + updateExpression( + "ClearanceHeightExpression", self.form.setupClearanceHeightExpr + ) + updateExpression("SafeHeightExpression", self.form.setupSafeHeightExpr) self.clearanceHeightOffs.updateProperty() self.safeHeightOffs.updateProperty() self.rapidVertical.updateProperty() @@ -318,7 +330,7 @@ class GlobalEditor(object): self.obj.CoolantMode = self.form.setupCoolantMode.currentText() def selectInComboBox(self, name, combo): - '''selectInComboBox(name, combo) ... helper function to select a specific value in a combo box.''' + """selectInComboBox(name, combo) ... helper function to select a specific value in a combo box.""" index = combo.findText(name, QtCore.Qt.MatchFixedString) if index >= 0: combo.blockSignals(True) @@ -349,16 +361,24 @@ class GlobalEditor(object): self.updateUI() def setupUi(self): - self.clearanceHeightOffs = PathGui.QuantitySpinBox(self.form.setupClearanceHeightOffs, self.obj, 'ClearanceHeightOffset') - self.safeHeightOffs = PathGui.QuantitySpinBox(self.form.setupSafeHeightOffs, self.obj, 'SafeHeightOffset') - self.rapidHorizontal = PathGui.QuantitySpinBox(self.form.setupRapidHorizontal, self.obj, 'HorizRapid') - self.rapidVertical = PathGui.QuantitySpinBox(self.form.setupRapidVertical, self.obj, 'VertRapid') + self.clearanceHeightOffs = PathGui.QuantitySpinBox( + self.form.setupClearanceHeightOffs, self.obj, "ClearanceHeightOffset" + ) + self.safeHeightOffs = PathGui.QuantitySpinBox( + self.form.setupSafeHeightOffs, self.obj, "SafeHeightOffset" + ) + self.rapidHorizontal = PathGui.QuantitySpinBox( + self.form.setupRapidHorizontal, self.obj, "HorizRapid" + ) + self.rapidVertical = PathGui.QuantitySpinBox( + self.form.setupRapidVertical, self.obj, "VertRapid" + ) self.form.setupCoolantMode.addItems(self.obj.CoolantModes) self.setFields() class TaskPanel: - '''TaskPanel for the SetupSheet - if it is being edited directly.''' + """TaskPanel for the SetupSheet - if it is being edited directly.""" def __init__(self, vobj): self.vobj = vobj @@ -368,7 +388,9 @@ class TaskPanel: self.globalEditor = GlobalEditor(self.obj, self.globalForm) self.opsEditor = OpsDefaultEditor(self.obj, None) self.form = [op.form for op in self.opsEditor.ops] + [self.globalForm] - FreeCAD.ActiveDocument.openTransaction(translate("Path_SetupSheet", "Edit SetupSheet")) + FreeCAD.ActiveDocument.openTransaction( + translate("Path_SetupSheet", "Edit SetupSheet") + ) def reject(self): self.globalEditor.reject() @@ -408,12 +430,12 @@ class TaskPanel: self.opsEditor.setupUi() -def Create(name='SetupSheet'): - '''Create(name='SetupSheet') ... creates a new setup sheet''' +def Create(name="SetupSheet"): + """Create(name='SetupSheet') ... creates a new setup sheet""" FreeCAD.ActiveDocument.openTransaction(translate("Path_Job", "Create Job")) ssheet = PathSetupSheet.Create(name) PathIconViewProvider.Attach(ssheet, name) return ssheet -PathIconViewProvider.RegisterViewProvider('SetupSheet', ViewProvider) +PathIconViewProvider.RegisterViewProvider("SetupSheet", ViewProvider) diff --git a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py b/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py index fa37d2944d..305f290507 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py +++ b/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py @@ -35,6 +35,7 @@ __doc__ = "Task panel editor for a SetupSheet" def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) + LOGLEVEL = False if LOGLEVEL: @@ -43,27 +44,32 @@ if LOGLEVEL: else: PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + class _PropertyEditor(object): - '''Base class of all property editors - just outlines the TableView delegate interface.''' + """Base class of all property editors - just outlines the TableView delegate interface.""" + def __init__(self, prop): self.prop = prop def widget(self, parent): - '''widget(parent) ... called by the delegate to get a new editor widget. - Must be implemented by subclasses and return the widget.''' - pass # pylint: disable=unnecessary-pass + """widget(parent) ... called by the delegate to get a new editor widget. + Must be implemented by subclasses and return the widget.""" + pass # pylint: disable=unnecessary-pass + def setEditorData(self, widget): - '''setEditorData(widget) ... called by the delegate to initialize the editor. + """setEditorData(widget) ... called by the delegate to initialize the editor. The widget is the object returned by widget(). - Must be implemented by subclasses.''' - pass # pylint: disable=unnecessary-pass + Must be implemented by subclasses.""" + pass # pylint: disable=unnecessary-pass + def setModelData(self, widget): - '''setModelData(widget) ... called by the delegate to store new values. - Must be implemented by subclasses.''' - pass # pylint: disable=unnecessary-pass + """setModelData(widget) ... called by the delegate to store new values. + Must be implemented by subclasses.""" + pass # pylint: disable=unnecessary-pass + class _PropertyEnumEditor(_PropertyEditor): - '''Editor for enumeration values - uses a combo box.''' + """Editor for enumeration values - uses a combo box.""" def widget(self, parent): PathLog.track(self.prop.name, self.prop.getEnumValues()) @@ -82,36 +88,38 @@ class _PropertyEnumEditor(_PropertyEditor): class _PropertyBoolEditor(_PropertyEditor): - '''Editor for boolean values - uses a combo box.''' + """Editor for boolean values - uses a combo box.""" def widget(self, parent): return QtGui.QComboBox(parent) def setEditorData(self, widget): widget.clear() - widget.addItems(['false', 'true']) + widget.addItems(["false", "true"]) if not self.prop.getValue() is None: index = 1 if self.prop.getValue() else 0 widget.setCurrentIndex(index) def setModelData(self, widget): - self.prop.setValue(widget.currentText() == 'true') + self.prop.setValue(widget.currentText() == "true") + class _PropertyStringEditor(_PropertyEditor): - '''Editor for string values - uses a line edit.''' + """Editor for string values - uses a line edit.""" def widget(self, parent): return QtGui.QLineEdit(parent) def setEditorData(self, widget): - text = '' if self.prop.getValue() is None else self.prop.getValue() + text = "" if self.prop.getValue() is None else self.prop.getValue() widget.setText(text) def setModelData(self, widget): self.prop.setValue(widget.text()) + class _PropertyAngleEditor(_PropertyEditor): - '''Editor for angle values - uses a line edit''' + """Editor for angle values - uses a line edit""" def widget(self, parent): return QtGui.QLineEdit(parent) @@ -125,8 +133,9 @@ class _PropertyAngleEditor(_PropertyEditor): def setModelData(self, widget): self.prop.setValue(FreeCAD.Units.Quantity(widget.text())) + class _PropertyLengthEditor(_PropertyEditor): - '''Editor for length values - uses a line edit.''' + """Editor for length values - uses a line edit.""" def widget(self, parent): return QtGui.QLineEdit(parent) @@ -140,8 +149,9 @@ class _PropertyLengthEditor(_PropertyEditor): def setModelData(self, widget): self.prop.setValue(FreeCAD.Units.Quantity(widget.text())) + class _PropertyPercentEditor(_PropertyEditor): - '''Editor for percent values - uses a spin box.''' + """Editor for percent values - uses a spin box.""" def widget(self, parent): return QtGui.QSpinBox(parent) @@ -156,8 +166,9 @@ class _PropertyPercentEditor(_PropertyEditor): def setModelData(self, widget): self.prop.setValue(widget.value()) + class _PropertyIntegerEditor(_PropertyEditor): - '''Editor for integer values - uses a spin box.''' + """Editor for integer values - uses a spin box.""" def widget(self, parent): return QtGui.QSpinBox(parent) @@ -171,8 +182,9 @@ class _PropertyIntegerEditor(_PropertyEditor): def setModelData(self, widget): self.prop.setValue(widget.value()) + class _PropertyFloatEditor(_PropertyEditor): - '''Editor for float values - uses a double spin box.''' + """Editor for float values - uses a double spin box.""" def widget(self, parent): return QtGui.QDoubleSpinBox(parent) @@ -186,33 +198,35 @@ class _PropertyFloatEditor(_PropertyEditor): def setModelData(self, widget): self.prop.setValue(widget.value()) -class _PropertyFileEditor(_PropertyEditor): +class _PropertyFileEditor(_PropertyEditor): def widget(self, parent): return QtGui.QLineEdit(parent) def setEditorData(self, widget): - text = '' if self.prop.getValue() is None else self.prop.getValue() + text = "" if self.prop.getValue() is None else self.prop.getValue() widget.setText(text) def setModelData(self, widget): self.prop.setValue(widget.text()) + _EditorFactory = { - PathSetupSheetOpPrototype.Property: None, - PathSetupSheetOpPrototype.PropertyAngle: _PropertyAngleEditor, - PathSetupSheetOpPrototype.PropertyBool: _PropertyBoolEditor, - PathSetupSheetOpPrototype.PropertyDistance: _PropertyLengthEditor, - PathSetupSheetOpPrototype.PropertyEnumeration: _PropertyEnumEditor, - PathSetupSheetOpPrototype.PropertyFloat: _PropertyFloatEditor, - PathSetupSheetOpPrototype.PropertyInteger: _PropertyIntegerEditor, - PathSetupSheetOpPrototype.PropertyLength: _PropertyLengthEditor, - PathSetupSheetOpPrototype.PropertyPercent: _PropertyPercentEditor, - PathSetupSheetOpPrototype.PropertyString: _PropertyStringEditor, - } + PathSetupSheetOpPrototype.Property: None, + PathSetupSheetOpPrototype.PropertyAngle: _PropertyAngleEditor, + PathSetupSheetOpPrototype.PropertyBool: _PropertyBoolEditor, + PathSetupSheetOpPrototype.PropertyDistance: _PropertyLengthEditor, + PathSetupSheetOpPrototype.PropertyEnumeration: _PropertyEnumEditor, + PathSetupSheetOpPrototype.PropertyFloat: _PropertyFloatEditor, + PathSetupSheetOpPrototype.PropertyInteger: _PropertyIntegerEditor, + PathSetupSheetOpPrototype.PropertyLength: _PropertyLengthEditor, + PathSetupSheetOpPrototype.PropertyPercent: _PropertyPercentEditor, + PathSetupSheetOpPrototype.PropertyString: _PropertyStringEditor, +} + def Editor(prop): - '''Returns an editor class to be used for the given property.''' + """Returns an editor class to be used for the given property.""" factory = _EditorFactory[prop.__class__] if factory: return factory(prop) diff --git a/src/Mod/Path/PathScripts/PathSimpleCopy.py b/src/Mod/Path/PathScripts/PathSimpleCopy.py index 6c6cb5d5a6..33daaa6904 100644 --- a/src/Mod/Path/PathScripts/PathSimpleCopy.py +++ b/src/Mod/Path/PathScripts/PathSimpleCopy.py @@ -34,11 +34,14 @@ def translate(context, text, disambig=None): class CommandPathSimpleCopy: - def GetResources(self): - return {'Pixmap': 'Path_SimpleCopy', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_SimpleCopy", "Simple Copy"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_SimpleCopy", "Creates a non-parametric copy of another path")} + return { + "Pixmap": "Path_SimpleCopy", + "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_SimpleCopy", "Simple Copy"), + "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "Path_SimpleCopy", "Creates a non-parametric copy of another path" + ), + } def IsActive(self): if bool(FreeCADGui.Selection.getSelection()) is False: @@ -46,7 +49,7 @@ class CommandPathSimpleCopy: try: obj = FreeCADGui.Selection.getSelectionEx()[0].Object return isinstance(obj.Proxy, PathScripts.PathOp.ObjectOp) - except Exception: # pylint: disable=broad-except + except Exception: # pylint: disable=broad-except return False def Activated(self): @@ -54,27 +57,38 @@ class CommandPathSimpleCopy: selection = FreeCADGui.Selection.getSelection() if len(selection) != 1: FreeCAD.Console.PrintError( - translate("Path_SimpleCopy", "Please select exactly one path object")+"\n") + translate("Path_SimpleCopy", "Please select exactly one path object") + + "\n" + ) return - if not(selection[0].isDerivedFrom("Path::Feature")): + if not (selection[0].isDerivedFrom("Path::Feature")): FreeCAD.Console.PrintError( - translate("Path_SimpleCopy", "Please select exactly one path object")+"\n") + translate("Path_SimpleCopy", "Please select exactly one path object") + + "\n" + ) return FreeCAD.ActiveDocument.openTransaction( - translate("Path_SimpleCopy", "Simple Copy")) - FreeCADGui.doCommand("srcpath = FreeCADGui.Selection.getSelectionEx()[0].Object.Path\n") + translate("Path_SimpleCopy", "Simple Copy") + ) + FreeCADGui.doCommand( + "srcpath = FreeCADGui.Selection.getSelectionEx()[0].Object.Path\n" + ) FreeCADGui.addModule("PathScripts.PathUtils") FreeCADGui.addModule("PathScripts.PathCustom") - FreeCADGui.doCommand('obj = PathScripts.PathCustom.Create("' + selection[0].Name + '_SimpleCopy")') - FreeCADGui.doCommand('obj.ViewObject.Proxy = 0') - FreeCADGui.doCommand('obj.Gcode = [c.toGCode() for c in srcpath.Commands]') - FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') + FreeCADGui.doCommand( + 'obj = PathScripts.PathCustom.Create("' + + selection[0].Name + + '_SimpleCopy")' + ) + FreeCADGui.doCommand("obj.ViewObject.Proxy = 0") + FreeCADGui.doCommand("obj.Gcode = [c.toGCode() for c in srcpath.Commands]") + FreeCADGui.doCommand("PathScripts.PathUtils.addToJob(obj)") FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_SimpleCopy', CommandPathSimpleCopy()) + FreeCADGui.addCommand("Path_SimpleCopy", CommandPathSimpleCopy()) diff --git a/src/Mod/Path/PathScripts/PathSimulatorGui.py b/src/Mod/Path/PathScripts/PathSimulatorGui.py index 9fc2344cbf..118e2a9e87 100644 --- a/src/Mod/Path/PathScripts/PathSimulatorGui.py +++ b/src/Mod/Path/PathScripts/PathSimulatorGui.py @@ -22,7 +22,7 @@ import FreeCAD import Path -import PathGui as PGui # ensure Path/Gui/Resources are loaded +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathDressup as PathDressup import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog @@ -36,8 +36,9 @@ from FreeCAD import Vector, Base # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -Mesh = LazyLoader('Mesh', globals(), 'Mesh') -Part = LazyLoader('Part', globals(), 'Part') + +Mesh = LazyLoader("Mesh", globals(), "Mesh") +Part = LazyLoader("Part", globals(), "Part") if FreeCAD.GuiUp: import FreeCADGui @@ -83,7 +84,9 @@ class PathSimulation: def UpdateProgress(self): if self.numCommands > 0: - self.taskForm.form.progressBar.setValue(self.iprogress * 100 / self.numCommands) + self.taskForm.form.progressBar.setValue( + self.iprogress * 100 / self.numCommands + ) def Activate(self): self.initdone = False @@ -112,7 +115,7 @@ class PathSimulation: def _populateJobSelection(self, form): # Make Job selection combobox setJobIdx = 0 - jobName = '' + jobName = "" jIdx = 0 # Get list of Job objects in active document jobList = FreeCAD.ActiveDocument.findObjects("Path::FeaturePython", "Job.*") @@ -122,10 +125,12 @@ class PathSimulation: guiSelection = FreeCADGui.Selection.getSelectionEx() if guiSelection: # Identify job selected by user sel = guiSelection[0] - if hasattr(sel.Object, "Proxy") and isinstance(sel.Object.Proxy, PathJob.ObjectJob): + if hasattr(sel.Object, "Proxy") and isinstance( + sel.Object.Proxy, PathJob.ObjectJob + ): jobName = sel.Object.Name FreeCADGui.Selection.clearSelection() - + # populate the job selection combobox form.comboJobs.blockSignals(True) form.comboJobs.clear() @@ -136,7 +141,7 @@ class PathSimulation: if j.Name == jobName or jCnt == 1: setJobIdx = jIdx jIdx += 1 - + # Pre-select GUI-selected job in the combobox if jobName or jCnt == 1: form.comboJobs.setCurrentIndex(setJobIdx) @@ -155,19 +160,22 @@ class PathSimulation: self.numCommands += len(self.operations[i].Path.Commands) self.stock = self.job.Stock.Shape - if (self.isVoxel): + if self.isVoxel: maxlen = self.stock.BoundBox.XLength - if (maxlen < self.stock.BoundBox.YLength): + if maxlen < self.stock.BoundBox.YLength: maxlen = self.stock.BoundBox.YLength self.voxSim.BeginSimulation(self.stock, 0.01 * self.accuracy * maxlen) - (self.cutMaterial.Mesh, self.cutMaterialIn.Mesh) = self.voxSim.GetResultMesh() + ( + self.cutMaterial.Mesh, + self.cutMaterialIn.Mesh, + ) = self.voxSim.GetResultMesh() else: self.cutMaterial.Shape = self.stock self.busy = False self.tool = None for i in range(len(self.activeOps)): self.SetupOperation(0) - if (self.tool is not None): + if self.tool is not None: break self.iprogress = 0 self.UpdateProgress() @@ -179,18 +187,29 @@ class PathSimulation: except Exception: self.tool = None - if (self.tool is not None): + if self.tool is not None: if isinstance(self.tool, Path.Tool): # handle legacy tools - toolProf = self.CreateToolProfile(self.tool, Vector(0, 1, 0), Vector(0, 0, 0), float(self.tool.Diameter) / 2.0) - self.cutTool.Shape = Part.makeSolid(toolProf.revolve(Vector(0, 0, 0), Vector(0, 0, 1))) + toolProf = self.CreateToolProfile( + self.tool, + Vector(0, 1, 0), + Vector(0, 0, 0), + float(self.tool.Diameter) / 2.0, + ) + self.cutTool.Shape = Part.makeSolid( + toolProf.revolve(Vector(0, 0, 0), Vector(0, 0, 1)) + ) else: # handle tool bits self.cutTool.Shape = self.tool.Shape if not self.cutTool.Shape.isValid() or self.cutTool.Shape.isNull(): self.EndSimulation() - raise RuntimeError("Path Simulation: Error in tool geometry - {}".format(self.tool.Name)) + raise RuntimeError( + "Path Simulation: Error in tool geometry - {}".format( + self.tool.Name + ) + ) self.cutTool.ViewObject.show() self.voxSim.SetToolShape(self.cutTool.Shape, 0.05 * self.accuracy) @@ -206,19 +225,27 @@ class PathSimulation: self.skipStep = False self.initialPos = Vector(0, 0, self.job.Stock.Shape.BoundBox.ZMax) # Add cut tool - self.cutTool = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "CutTool") + self.cutTool = FreeCAD.ActiveDocument.addObject( + "Part::FeaturePython", "CutTool" + ) self.cutTool.ViewObject.Proxy = 0 self.cutTool.ViewObject.hide() # Add cut material if self.isVoxel: - self.cutMaterial = FreeCAD.ActiveDocument.addObject("Mesh::FeaturePython", "CutMaterial") - self.cutMaterialIn = FreeCAD.ActiveDocument.addObject("Mesh::FeaturePython", "CutMaterialIn") + self.cutMaterial = FreeCAD.ActiveDocument.addObject( + "Mesh::FeaturePython", "CutMaterial" + ) + self.cutMaterialIn = FreeCAD.ActiveDocument.addObject( + "Mesh::FeaturePython", "CutMaterialIn" + ) self.cutMaterialIn.ViewObject.Proxy = 0 self.cutMaterialIn.ViewObject.show() self.cutMaterialIn.ViewObject.ShapeColor = (1.0, 0.85, 0.45, 0.0) else: - self.cutMaterial = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "CutMaterial") + self.cutMaterial = FreeCAD.ActiveDocument.addObject( + "Part::FeaturePython", "CutMaterial" + ) self.cutMaterial.Shape = self.job.Stock.Shape self.cutMaterial.ViewObject.Proxy = 0 self.cutMaterial.ViewObject.show() @@ -226,7 +253,9 @@ class PathSimulation: # Add cut path solid for debug if self.debug: - self.cutSolid = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "CutDebug") + self.cutSolid = FreeCAD.ActiveDocument.addObject( + "Part::FeaturePython", "CutDebug" + ) self.cutSolid.ViewObject.Proxy = 0 self.cutSolid.ViewObject.hide() @@ -246,28 +275,30 @@ class PathSimulation: cmd = self.operation.Path.Commands[self.icmd] pathSolid = None - if cmd.Name in ['G0']: + if cmd.Name in ["G0"]: self.firstDrill = True self.curpos = self.RapidMove(cmd, self.curpos) - if cmd.Name in ['G1', 'G2', 'G3']: + if cmd.Name in ["G1", "G2", "G3"]: self.firstDrill = True if self.skipStep: self.curpos = self.RapidMove(cmd, self.curpos) else: - (pathSolid, self.curpos) = self.GetPathSolid(self.tool, cmd, self.curpos) + (pathSolid, self.curpos) = self.GetPathSolid( + self.tool, cmd, self.curpos + ) - if cmd.Name in ['G80']: + if cmd.Name in ["G80"]: self.firstDrill = True - if cmd.Name in ['G81', 'G82', 'G83']: + if cmd.Name in ["G81", "G82", "G83"]: if self.firstDrill: - extendcommand = Path.Command('G0', {"Z": cmd.r}) + extendcommand = Path.Command("G0", {"Z": cmd.r}) self.curpos = self.RapidMove(extendcommand, self.curpos) self.firstDrill = False - extendcommand = Path.Command('G0', {"X": cmd.x, "Y": cmd.y, "Z": cmd.r}) + extendcommand = Path.Command("G0", {"X": cmd.x, "Y": cmd.y, "Z": cmd.r}) self.curpos = self.RapidMove(extendcommand, self.curpos) - extendcommand = Path.Command('G1', {"X": cmd.x, "Y": cmd.y, "Z": cmd.z}) + extendcommand = Path.Command("G1", {"X": cmd.x, "Y": cmd.y, "Z": cmd.z}) self.curpos = self.RapidMove(extendcommand, self.curpos) - extendcommand = Path.Command('G1', {"X": cmd.x, "Y": cmd.y, "Z": cmd.r}) + extendcommand = Path.Command("G1", {"X": cmd.x, "Y": cmd.y, "Z": cmd.r}) self.curpos = self.RapidMove(extendcommand, self.curpos) self.skipStep = False if pathSolid is not None: @@ -307,27 +338,39 @@ class PathSimulation: cmd = self.opCommands[self.icmd] # for cmd in job.Path.Commands: - if cmd.Name in ['G0', 'G1', 'G2', 'G3']: + if cmd.Name in ["G0", "G1", "G2", "G3"]: self.firstDrill = True self.curpos = self.voxSim.ApplyCommand(self.curpos, cmd) if not self.disableAnim: self.cutTool.Placement = self.curpos - (self.cutMaterial.Mesh, self.cutMaterialIn.Mesh) = self.voxSim.GetResultMesh() - if cmd.Name in ['G80']: + ( + self.cutMaterial.Mesh, + self.cutMaterialIn.Mesh, + ) = self.voxSim.GetResultMesh() + if cmd.Name in ["G80"]: self.firstDrill = True - if cmd.Name in ['G81', 'G82', 'G83']: + if cmd.Name in ["G81", "G82", "G83"]: extendcommands = [] if self.firstDrill: - extendcommands.append(Path.Command('G0', {"Z": cmd.r})) + extendcommands.append(Path.Command("G0", {"Z": cmd.r})) self.firstDrill = False - extendcommands.append(Path.Command('G0', {"X": cmd.x, "Y": cmd.y, "Z": cmd.r})) - extendcommands.append(Path.Command('G1', {"X": cmd.x, "Y": cmd.y, "Z": cmd.z})) - extendcommands.append(Path.Command('G1', {"X": cmd.x, "Y": cmd.y, "Z": cmd.r})) + extendcommands.append( + Path.Command("G0", {"X": cmd.x, "Y": cmd.y, "Z": cmd.r}) + ) + extendcommands.append( + Path.Command("G1", {"X": cmd.x, "Y": cmd.y, "Z": cmd.z}) + ) + extendcommands.append( + Path.Command("G1", {"X": cmd.x, "Y": cmd.y, "Z": cmd.r}) + ) for ecmd in extendcommands: self.curpos = self.voxSim.ApplyCommand(self.curpos, ecmd) if not self.disableAnim: self.cutTool.Placement = self.curpos - (self.cutMaterial.Mesh, self.cutMaterialIn.Mesh) = self.voxSim.GetResultMesh() + ( + self.cutMaterial.Mesh, + self.cutMaterialIn.Mesh, + ) = self.voxSim.GetResultMesh() self.icmd += 1 self.iprogress += 1 self.UpdateProgress() @@ -341,7 +384,7 @@ class PathSimulation: self.busy = False def PerformCut(self): - if (self.isVoxel): + if self.isVoxel: self.PerformCutVoxel() else: self.PerformCutBoolean() @@ -457,7 +500,7 @@ class PathSimulation: form.listOperations.clear() self.operations = [] for op in j.Operations.OutList: - if PathUtil.opProperty(op, 'Active'): + if PathUtil.opProperty(op, "Active"): listItem = QtGui.QListWidgetItem(op.ViewObject.Icon, op.Label) listItem.setFlags(listItem.flags() | QtCore.Qt.ItemIsUserCheckable) listItem.setCheckState(QtCore.Qt.CheckState.Checked) @@ -499,7 +542,7 @@ class PathSimulation: def InvalidOperation(self): if len(self.activeOps) == 0: return True - if (self.tool is None): + if self.tool is None: TSError("No tool assigned for the operation") return True return False @@ -526,7 +569,10 @@ class PathSimulation: def ViewShape(self): if self.isVoxel: - (self.cutMaterial.Mesh, self.cutMaterialIn.Mesh) = self.voxSim.GetResultMesh() + ( + self.cutMaterial.Mesh, + self.cutMaterialIn.Mesh, + ) = self.voxSim.GetResultMesh() else: self.cutMaterial.Shape = self.stock @@ -570,12 +616,15 @@ class PathSimulation: class CommandPathSimulate: - def GetResources(self): - return {'Pixmap': 'Path_Simulator', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Simulator", "CAM Simulator"), - 'Accel': "P, M", - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Simulator", "Simulate Path G-Code on stock")} + return { + "Pixmap": "Path_Simulator", + "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_Simulator", "CAM Simulator"), + "Accel": "P, M", + "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "Path_Simulator", "Simulate Path G-Code on stock" + ), + } def IsActive(self): if FreeCAD.ActiveDocument is not None: @@ -592,5 +641,5 @@ pathSimulation = PathSimulation() if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_Simulator', CommandPathSimulate()) + FreeCADGui.addCommand("Path_Simulator", CommandPathSimulate()) FreeCAD.Console.PrintLog("Loading PathSimulator Gui... done\n") From 05d8253ee22bf7b2626f15be488d27665b16ad68 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 4 Jan 2022 14:45:36 -0600 Subject: [PATCH 59/65] Translation cleanup. Setupsheet related files. Simple copy, simulator --- src/Mod/Path/PathScripts/PathSetupSheet.py | 62 +++++++++---------- src/Mod/Path/PathScripts/PathSetupSheetGui.py | 12 +--- .../PathSetupSheetOpPrototypeGui.py | 4 -- src/Mod/Path/PathScripts/PathSimpleCopy.py | 15 ++--- src/Mod/Path/PathScripts/PathSimulatorGui.py | 9 ++- 5 files changed, 44 insertions(+), 58 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSetupSheet.py b/src/Mod/Path/PathScripts/PathSetupSheet.py index d2681bd742..e5d84ddf7d 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheet.py +++ b/src/Mod/Path/PathScripts/PathSetupSheet.py @@ -25,21 +25,21 @@ import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype import PathScripts.PathUtil as PathUtil -import PySide +from PySide.QtCore import QT_TRANSLATE_NOOP __title__ = "Setup Sheet for a Job." __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "A container for all default values and job specific configuration values." -_RegisteredOps = {} - -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +_RegisteredOps: dict = {} -def translate(context, text, disambig=None): - return PySide.QtCore.QCoreApplication.translate(context, text, disambig) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) class Template: @@ -114,34 +114,36 @@ class SetupSheet: "App::PropertySpeed", "VertRapid", "ToolController", - translate("PathSetupSheet", "Default speed for horizontal rapid moves."), + QT_TRANSLATE_NOOP( + "App::Property", "Default speed for horizontal rapid moves." + ), ) obj.addProperty( "App::PropertySpeed", "HorizRapid", "ToolController", - translate("PathSetupSheet", "Default speed for vertical rapid moves."), + QT_TRANSLATE_NOOP( + "App::Property", "Default speed for vertical rapid moves." + ), ) - obj.addProperty( "App::PropertyStringList", "CoolantModes", "CoolantMode", - translate("PathSetupSheet", "Coolant Modes"), + QT_TRANSLATE_NOOP("App::Property", "Coolant Modes"), ) obj.addProperty( "App::PropertyEnumeration", "CoolantMode", "CoolantMode", - translate("PathSetupSheet", "Default coolant mode."), + QT_TRANSLATE_NOOP("App::Property", "Default coolant mode."), ) - obj.addProperty( "App::PropertyLength", "SafeHeightOffset", "OperationHeights", - translate( - "PathSetupSheet", + QT_TRANSLATE_NOOP( + "App::Property", "The usage of this field depends on SafeHeightExpression - by default its value is added to StartDepth and used for SafeHeight of an operation.", ), ) @@ -149,16 +151,16 @@ class SetupSheet: "App::PropertyString", "SafeHeightExpression", "OperationHeights", - translate( - "PathSetupSheet", "Expression set for the SafeHeight of new operations." + QT_TRANSLATE_NOOP( + "App::Property", "Expression set for the SafeHeight of new operations." ), ) obj.addProperty( "App::PropertyLength", "ClearanceHeightOffset", "OperationHeights", - translate( - "PathSetupSheet", + QT_TRANSLATE_NOOP( + "App::Property", "The usage of this field depends on ClearanceHeightExpression - by default is value is added to StartDepth and used for ClearanceHeight of an operation.", ), ) @@ -166,34 +168,33 @@ class SetupSheet: "App::PropertyString", "ClearanceHeightExpression", "OperationHeights", - translate( - "PathSetupSheet", + QT_TRANSLATE_NOOP( + "App::Property", "Expression set for the ClearanceHeight of new operations.", ), ) - obj.addProperty( "App::PropertyString", "StartDepthExpression", "OperationDepths", - translate( - "PathSetupSheet", "Expression used for StartDepth of new operations." + QT_TRANSLATE_NOOP( + "App::Property", "Expression used for StartDepth of new operations." ), ) obj.addProperty( "App::PropertyString", "FinalDepthExpression", "OperationDepths", - translate( - "PathSetupSheet", "Expression used for FinalDepth of new operations." + QT_TRANSLATE_NOOP( + "App::Property", "Expression used for FinalDepth of new operations." ), ) obj.addProperty( "App::PropertyString", "StepDownExpression", "OperationDepths", - translate( - "PathSetupSheet", "Expression used for StepDown of new operations." + QT_TRANSLATE_NOOP( + "App::Property", "Expression used for StepDown of new operations." ), ) @@ -403,7 +404,6 @@ class SetupSheet: setattr(obj, prop, getattr(self.obj, propName)) except Exception: PathLog.info("SetupSheet has no support for {}".format(opName)) - # traceback.print_exc() def onDocumentRestored(self, obj): @@ -412,7 +412,7 @@ class SetupSheet: "App::PropertyStringList", "CoolantModes", "CoolantMode", - translate("PathSetupSheet", "Coolant Modes"), + QT_TRANSLATE_NOOP("App::Property", "Coolant Modes"), ) obj.CoolantModes = self.DefaultCoolantModes @@ -421,7 +421,7 @@ class SetupSheet: "App::PropertyEnumeration", "CoolantMode", "CoolantMode", - translate("PathSetupSheet", "Default coolant mode."), + QT_TRANSLATE_NOOP("App::Property", "Default coolant mode."), ) obj.CoolantMode = self.DefaultCoolantModes diff --git a/src/Mod/Path/PathScripts/PathSetupSheetGui.py b/src/Mod/Path/PathScripts/PathSetupSheetGui.py index 540fd3612d..4747219bd2 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheetGui.py +++ b/src/Mod/Path/PathScripts/PathSetupSheetGui.py @@ -38,14 +38,8 @@ __url__ = "https://www.freecadweb.org" __doc__ = "Task panel editor for a SetupSheet" -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) - - LOGLEVEL = False - if LOGLEVEL: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) @@ -388,9 +382,7 @@ class TaskPanel: self.globalEditor = GlobalEditor(self.obj, self.globalForm) self.opsEditor = OpsDefaultEditor(self.obj, None) self.form = [op.form for op in self.opsEditor.ops] + [self.globalForm] - FreeCAD.ActiveDocument.openTransaction( - translate("Path_SetupSheet", "Edit SetupSheet") - ) + FreeCAD.ActiveDocument.openTransaction("Edit SetupSheet") def reject(self): self.globalEditor.reject() @@ -432,7 +424,7 @@ class TaskPanel: def Create(name="SetupSheet"): """Create(name='SetupSheet') ... creates a new setup sheet""" - FreeCAD.ActiveDocument.openTransaction(translate("Path_Job", "Create Job")) + FreeCAD.ActiveDocument.openTransaction("Create Job") ssheet = PathSetupSheet.Create(name) PathIconViewProvider.Attach(ssheet, name) return ssheet diff --git a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py b/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py index 305f290507..aa5205961d 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py +++ b/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py @@ -31,10 +31,6 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Task panel editor for a SetupSheet" -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) - LOGLEVEL = False diff --git a/src/Mod/Path/PathScripts/PathSimpleCopy.py b/src/Mod/Path/PathScripts/PathSimpleCopy.py index 33daaa6904..135edc4fe8 100644 --- a/src/Mod/Path/PathScripts/PathSimpleCopy.py +++ b/src/Mod/Path/PathScripts/PathSimpleCopy.py @@ -23,22 +23,19 @@ import FreeCAD import FreeCADGui import PathScripts -from PySide import QtCore +from PySide.QtCore import QT_TRANSLATE_NOOP __doc__ = """Path SimpleCopy command""" - -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +translate = FreeCAD.Qt.translate class CommandPathSimpleCopy: def GetResources(self): return { "Pixmap": "Path_SimpleCopy", - "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_SimpleCopy", "Simple Copy"), - "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "MenuText": QT_TRANSLATE_NOOP("Path_SimpleCopy", "Simple Copy"), + "ToolTip": QT_TRANSLATE_NOOP( "Path_SimpleCopy", "Creates a non-parametric copy of another path" ), } @@ -68,9 +65,7 @@ class CommandPathSimpleCopy: ) return - FreeCAD.ActiveDocument.openTransaction( - translate("Path_SimpleCopy", "Simple Copy") - ) + FreeCAD.ActiveDocument.openTransaction("Simple Copy") FreeCADGui.doCommand( "srcpath = FreeCADGui.Selection.getSelectionEx()[0].Object.Path\n" ) diff --git a/src/Mod/Path/PathScripts/PathSimulatorGui.py b/src/Mod/Path/PathScripts/PathSimulatorGui.py index 118e2a9e87..28b478c627 100644 --- a/src/Mod/Path/PathScripts/PathSimulatorGui.py +++ b/src/Mod/Path/PathScripts/PathSimulatorGui.py @@ -31,6 +31,7 @@ import PathScripts.PathJob as PathJob import PathSimulator import math import os +from PySide.QtCore import QT_TRANSLATE_NOOP from FreeCAD import Vector, Base @@ -310,7 +311,9 @@ class PathSimulation: self.stock = newStock.removeSplitter() except Exception: if self.debug: - print("invalid cut at cmd #{}".format(self.icmd)) + FreeCAD.Console.PrintError( + "invalid cut at cmd #{}".format(self.icmd) + ) if not self.disableAnim: self.cutTool.Placement = FreeCAD.Placement(self.curpos, self.stdrot) self.icmd += 1 @@ -619,9 +622,9 @@ class CommandPathSimulate: def GetResources(self): return { "Pixmap": "Path_Simulator", - "MenuText": QtCore.QT_TRANSLATE_NOOP("Path_Simulator", "CAM Simulator"), + "MenuText": QT_TRANSLATE_NOOP("Path_Simulator", "CAM Simulator"), "Accel": "P, M", - "ToolTip": QtCore.QT_TRANSLATE_NOOP( + "ToolTip": QT_TRANSLATE_NOOP( "Path_Simulator", "Simulate Path G-Code on stock" ), } From f670fd3c86958de9d256e21a4fd1639f70faa7cb Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 4 Jan 2022 17:34:09 -0600 Subject: [PATCH 60/65] newline characters --- src/Mod/Path/PathScripts/post/gcode_pre.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/post/gcode_pre.py b/src/Mod/Path/PathScripts/post/gcode_pre.py index c89a3c269a..0c9c3f35ed 100644 --- a/src/Mod/Path/PathScripts/post/gcode_pre.py +++ b/src/Mod/Path/PathScripts/post/gcode_pre.py @@ -153,7 +153,7 @@ def parse(inputstring): axis = ["X", "Y", "Z", "A", "B", "C", "U", "V", "W"] - FreeCAD.Console.PrintMessage("preprocessing...") + FreeCAD.Console.PrintMessage("preprocessing...\n") PathLog.track(inputstring) # split the input by line lines = inputstring.splitlines() @@ -191,7 +191,7 @@ def parse(inputstring): elif currcommand[0] in axis and lastcommand: output.append(lastcommand + " " + lin) - FreeCAD.Console.PrintMessage("done preprocessing.") + FreeCAD.Console.PrintMessage("done preprocessing.\n") return output From 6be358fdfae8b71ebb2542193212fc06ee4db509 Mon Sep 17 00:00:00 2001 From: mwganson Date: Mon, 3 Jan 2022 23:02:00 -0600 Subject: [PATCH 61/65] [PartDesign] Chamfer and Fillet, select all edges for the user when no subobjects have been selected prior to executing command --- src/Mod/PartDesign/Gui/Command.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Mod/PartDesign/Gui/Command.cpp b/src/Mod/PartDesign/Gui/Command.cpp index 7303531e09..0d9612d8a3 100644 --- a/src/Mod/PartDesign/Gui/Command.cpp +++ b/src/Mod/PartDesign/Gui/Command.cpp @@ -1911,6 +1911,21 @@ bool dressupGetSelected(Gui::Command* cmd, const std::string& which, return false; } + // if 1 Part::Feature object selected, but no subobjects, select all edges for the user + if (selection[0].getSubNames().size() == 0){ + int count = TopShape.countSubElements("Edge"); + std::string docName = App::GetApplication().getDocumentName(base->getDocument()); + std::string objName = base->getNameInDocument(); + for (int ii = 0; ii < count; ii++){ + std::ostringstream edgeName; + edgeName << "Edge" << ii+1; + Gui::Selection().addSelection(docName.c_str(), objName.c_str(), edgeName.str().c_str()); + } + selection = cmd->getSelection().getSelectionEx(); + if (selection.size() == 1){ + selected = selection[0]; + } + } return true; } From ec6d157b82f48ac854e304b30b7d042142ee7227 Mon Sep 17 00:00:00 2001 From: luz paz Date: Tue, 4 Jan 2022 16:25:04 -0500 Subject: [PATCH 62/65] Fix various typos --- src/Gui/DocumentPy.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gui/DocumentPy.xml b/src/Gui/DocumentPy.xml index 29da86eb2d..6a551f2807 100644 --- a/src/Gui/DocumentPy.xml +++ b/src/Gui/DocumentPy.xml @@ -110,7 +110,7 @@ mergeProject(filename) -> None Change TreeItem of a document object. toggleTreeItem(DocObject,[flag=0]) -> None -- -flag can be 0:Toggle, 1:Collaps, 2:Expand +flag can be 0:Toggle, 1:Collapse, 2:Expand From daa508a55c5a26c502df9dcdc80ce15fb0d5f8b1 Mon Sep 17 00:00:00 2001 From: luz paz Date: Tue, 4 Jan 2022 19:54:13 -0500 Subject: [PATCH 63/65] Fix more typos + whitespace fix and added 'TODO' Found via `codespell -q 3 -L aci,ake,aline,alle,alledges,alocation,als,ang,anid,apoints,ba,beginn,behaviour,bloaded,bottome,byteorder,calculater,cancelled,cancelling,cas,cascade,centimetre,childrens,childs,colour,colours,commen,connexion,currenty,dof,doubleclick,dum,eiter,elemente,ende,feld,finde,findf,freez,hist,iff,indicies,initialisation,initialise,initialised,initialises,initialisiert,inout,ist,kilometre,lod,mantatory,methode,metres,millimetre,modell,nd,noe,normale,normaly,nto,numer,oce,oder,ontop,orgin,orginx,orginy,ot,pard,parm,parms,pres,programm,que,rady,recurrance,rougly,seperator,serie,sinc,strack,substraction,te,thist,thru,tread,uint,unter,vertexes,wallthickness,whitespaces -S ./.git,*.po,*.ts,./ChangeLog.txt,./src/3rdParty,./src/Mod/Assembly/App/opendcm,./src/CXX,./src/zipios++,./src/Base/swig*,./src/Mod/Robot/App/kdl_cp,./src/Mod/Import/App/SCL,./src/WindowsInstaller,./src/Doc/FreeCAD.uml,./build/doc/SourceDocu` --- src/App/GeoFeatureGroupExtension.h | 4 ++-- src/Base/FileInfo.h | 2 +- src/Mod/Arch/exportIFCStructuralTools.py | 4 ++-- src/Mod/Draft/coding_conventions.md | 2 +- src/Mod/Part/App/Attacher.h | 2 +- src/Mod/Path/PathScripts/post/grbl_post.py | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/App/GeoFeatureGroupExtension.h b/src/App/GeoFeatureGroupExtension.h index 8b090d1217..376b2c1401 100644 --- a/src/App/GeoFeatureGroupExtension.h +++ b/src/App/GeoFeatureGroupExtension.h @@ -74,8 +74,8 @@ public: /** Returns the geo feature group which contains this object. * In case this object is not part of any geoFeatureGroup 0 is returned. - * Unlike DocumentObjectGroup::getGroupOfObject serches only for GeoFeatureGroups - * @param obj the object to search for + * Unlike DocumentObjectGroup::getGroupOfObject searches only for GeoFeatureGroups + * @param obj the object to search for */ static DocumentObject* getGroupOfObject(const DocumentObject* obj); diff --git a/src/Base/FileInfo.h b/src/Base/FileInfo.h index 763218bad9..26c053339d 100644 --- a/src/Base/FileInfo.h +++ b/src/Base/FileInfo.h @@ -86,7 +86,7 @@ public: *@endcode */ std::string completeExtension () const; - /// Checks for a special extension, NOT case sensetive + /// Checks for a special extension, NOT case sensitive bool hasExtension (const char* Ext) const; //@} diff --git a/src/Mod/Arch/exportIFCStructuralTools.py b/src/Mod/Arch/exportIFCStructuralTools.py index aa879cc198..e767cd338f 100644 --- a/src/Mod/Arch/exportIFCStructuralTools.py +++ b/src/Mod/Arch/exportIFCStructuralTools.py @@ -251,8 +251,8 @@ def createStructuralMember(ifcfile, ifcbin, obj): topologyRep = ifcfile.createIfcTopologyRepresentation(structContext, "Analysis", "Face", (face,)) prodDefShape = ifcfile.createIfcProductDefinitionShape(None, None, (topologyRep,)) # sets surface thickness - # ATM limitations - # - for verical slabs (walls) or inclined slabs (ramps) the thickness is taken from the Height property + # TODO: ATM limitations + # - for vertical slabs (walls) or inclined slabs (ramps) the thickness is taken from the Height property thickness = float(obj.Height)*scaling # creates structural member structuralMember = ifcfile.createIfcStructuralSurfaceMember( diff --git a/src/Mod/Draft/coding_conventions.md b/src/Mod/Draft/coding_conventions.md index c807f41bc6..d88526a48a 100644 --- a/src/Mod/Draft/coding_conventions.md +++ b/src/Mod/Draft/coding_conventions.md @@ -103,7 +103,7 @@ find src/Mod/Draft -name '*.py' ! -name InitGui.py -exec flake8 --ignore=E226,E2 - The online [LGTM service](https://lgtm.com/projects/g/FreeCAD/FreeCAD/latest/files/src/Mod/Draft/) is also able to analyze the code and detect problems. -- Avoid automatic code formaters. +- Avoid automatic code formatters. ## C++ diff --git a/src/Mod/Part/App/Attacher.h b/src/Mod/Part/App/Attacher.h index 8b3cc28a99..be49ef5258 100644 --- a/src/Mod/Part/App/Attacher.h +++ b/src/Mod/Part/App/Attacher.h @@ -45,7 +45,7 @@ namespace Attacher class AttachEngine; -//Attention! The numbers assiciated to the modes are permanent, because they are what get written into files. +//Attention! The numbers associated to the modes are permanent, because they are what get written into files. enum eMapMode { mmDeactivated, mmTranslate, diff --git a/src/Mod/Path/PathScripts/post/grbl_post.py b/src/Mod/Path/PathScripts/post/grbl_post.py index 66579c6b8e..9064e8f821 100755 --- a/src/Mod/Path/PathScripts/post/grbl_post.py +++ b/src/Mod/Path/PathScripts/post/grbl_post.py @@ -646,7 +646,7 @@ def drill_translate(outstring, cmd, params): ) print(strF_Feedrate) - # preliminary mouvement(s) + # preliminary movement(s) if CURRENT_Z < RETRACT_Z: trBuff += linenumber() + strG0_RETRACT_Z trBuff += ( From 1f84ed1e6ecaf6bb0784aafc4fd24f0d395a6eb5 Mon Sep 17 00:00:00 2001 From: David Osterberg Date: Mon, 3 Jan 2022 23:51:25 +0100 Subject: [PATCH 64/65] PD: Fix bug in FeatureTransformed It is important to not include the original in the pattern because the original might not be at the tip of the feature tree. Including the original will interfere with changes that occur further down in the feature tree This fixes the bug identified in https://forum.freecadweb.org/viewtopic.php?f=3&t=64900 --- src/Mod/PartDesign/App/FeatureTransformed.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Mod/PartDesign/App/FeatureTransformed.cpp b/src/Mod/PartDesign/App/FeatureTransformed.cpp index bcde049a73..7de5640a13 100644 --- a/src/Mod/PartDesign/App/FeatureTransformed.cpp +++ b/src/Mod/PartDesign/App/FeatureTransformed.cpp @@ -20,7 +20,6 @@ * * ******************************************************************************/ - #include "PreCompiled.h" #ifndef _PreComp_ # include @@ -267,7 +266,10 @@ App::DocumentObjectExecReturn *Transformed::execute(void) bool overlapping = false; std::vector::const_iterator t = transformations.begin(); - bool first = true; + + // First transformation is skipped since it should not be part of the toolShape. + t++; + for (; t != transformations.end(); ++t) { // Make an explicit copy of the shape because the "true" parameter to BRepBuilderAPI_Transform // seems to be pretty broken @@ -283,15 +285,14 @@ App::DocumentObjectExecReturn *Transformed::execute(void) shapes.emplace_back(shape); builder.Add(compShape, shape); - if (overlapDetectionMode && !first) + if (overlapDetectionMode) overlapping = overlapping || (countSolids(TopoShape(origShape).fuse(shape))==1); - if (first) - first = false; } TopoDS_Shape toolShape; + #ifndef FC_DEBUG if (overlapping || overlapMode == "Overlap mode") Base::Console().Message("Transformed: Overlapping feature mode (fusing tool shapes)\n"); @@ -300,7 +301,7 @@ App::DocumentObjectExecReturn *Transformed::execute(void) #endif if (overlapping || overlapMode == "Overlap mode") - toolShape = TopoShape(origShape).fuse(shapes, Precision::Confusion()); + toolShape = TopoShape(shape).fuse(shapes, Precision::Confusion()); else toolShape = compShape; From f3f6f08e31fc786d4e5f28611a9171e98d18025b Mon Sep 17 00:00:00 2001 From: Uwe Date: Wed, 5 Jan 2022 03:16:00 +0100 Subject: [PATCH 65/65] [PD] avoid a single-letter variable (single letter variables make it hard to read and understand code quickly and are error-prone for typos) --- src/Mod/PartDesign/App/FeatureTransformed.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Mod/PartDesign/App/FeatureTransformed.cpp b/src/Mod/PartDesign/App/FeatureTransformed.cpp index 7de5640a13..65b6270b6e 100644 --- a/src/Mod/PartDesign/App/FeatureTransformed.cpp +++ b/src/Mod/PartDesign/App/FeatureTransformed.cpp @@ -265,19 +265,19 @@ App::DocumentObjectExecReturn *Transformed::execute(void) std::vector shapes; bool overlapping = false; - std::vector::const_iterator t = transformations.begin(); + std::vector::const_iterator transformIter = transformations.begin(); // First transformation is skipped since it should not be part of the toolShape. - t++; + transformIter++; - for (; t != transformations.end(); ++t) { + for (; transformIter != transformations.end(); ++transformIter) { // Make an explicit copy of the shape because the "true" parameter to BRepBuilderAPI_Transform // seems to be pretty broken BRepBuilderAPI_Copy copy(origShape); shape = copy.Shape(); - BRepBuilderAPI_Transform mkTrf(shape, *t, false); // No need to copy, now + BRepBuilderAPI_Transform mkTrf(shape, *transformIter, false); // No need to copy, now if (!mkTrf.IsDone()) return new App::DocumentObjectExecReturn("Transformation failed", (*o)); shape = mkTrf.Shape();