From fbb59ae712e3ebf2219f480f799f8c2e1f1ca006 Mon Sep 17 00:00:00 2001 From: Eric Trombly Date: Mon, 16 Mar 2020 10:17:32 -0500 Subject: [PATCH 001/117] initial work for modifying path of imported gcode --- src/Mod/Path/PathScripts/PathCustom.py | 18 +++ src/Mod/Path/PathScripts/post/gcode_pre.py | 124 +++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 src/Mod/Path/PathScripts/post/gcode_pre.py diff --git a/src/Mod/Path/PathScripts/PathCustom.py b/src/Mod/Path/PathScripts/PathCustom.py index 74cb93ba1a..a07343731c 100644 --- a/src/Mod/Path/PathScripts/PathCustom.py +++ b/src/Mod/Path/PathScripts/PathCustom.py @@ -25,9 +25,11 @@ import FreeCAD import FreeCADGui import Path from PySide import QtCore +from copy import copy __doc__ = """Path Custom object and FreeCAD command""" +movecommands = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03'] # Qt translation handling def translate(context, text, disambig=None): @@ -39,6 +41,8 @@ class ObjectCustom: def __init__(self,obj): obj.addProperty("App::PropertyStringList", "Gcode", "Path", QtCore.QT_TRANSLATE_NOOP("PathCustom", "The gcode to be inserted")) obj.addProperty("App::PropertyLink", "ToolController", "Path", QtCore.QT_TRANSLATE_NOOP("PathCustom", "The tool controller that will be used to calculate the path")) + obj.addProperty("App::PropertyBool", "OperationPlacement", "Path", "Use operation placement") + obj.OperationPlacement = False obj.Proxy = self def __getstate__(self): @@ -54,6 +58,20 @@ class ObjectCustom: s += str(l) if s: path = Path.Path(s) + if obj.OperationPlacement: + for x in range(len(path.Commands)): + if path.Commands[x].Name in movecommands: + base = copy(obj.Placement.Base) + new = path.Commands[x] + if 'X' not in path.Commands[x].Parameters: + base[0] = 0.0 + if 'Y' not in path.Commands[x].Parameters: + base[1] = 0.0 + if 'Z' not in path.Commands[x].Parameters: + base[2] = 0.0 + new.Placement.translate(base) + path.deleteCommand(x) + path.insertCommand(new, x) obj.Path = path diff --git a/src/Mod/Path/PathScripts/post/gcode_pre.py b/src/Mod/Path/PathScripts/post/gcode_pre.py new file mode 100644 index 0000000000..1ec9aaa702 --- /dev/null +++ b/src/Mod/Path/PathScripts/post/gcode_pre.py @@ -0,0 +1,124 @@ +# *************************************************************************** +# * (c) Yorik van Havre (yorik@uncreated.net) 2014 * +# * * +# * 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 * +# * * +# ***************************************************************************/ + + +''' +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. + +Read the Path Workbench documentation to know how to create Path objects +from GCode. +''' + +import os +import Path +import FreeCAD +import PathScripts.PathUtils +import PathScripts.PathLog as PathLog + +# LEVEL = PathLog.Level.DEBUG +LEVEL = PathLog.Level.INFO +PathLog.setLevel(LEVEL, PathLog.thisModule()) + +if LEVEL == PathLog.Level.DEBUG: + PathLog.trackModule(PathLog.thisModule()) + + +# to distinguish python built-in open function from the one declared below +if open.__module__ in ['__builtin__', 'io']: + pythonopen = open + + +def open(filename): + "called when freecad opens a file." + PathLog.track(filename) + docname = os.path.splitext(os.path.basename(filename))[0] + doc = FreeCAD.newDocument(docname) + insert(filename, doc.Name) + + +def insert(filename, docname): + "called when freecad imports a file" + PathLog.track(filename) + gfile = pythonopen(filename) + gcode = gfile.read() + gfile.close() + gcode = parse(gcode) + doc = FreeCAD.getDocument(docname) + obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Custom") + PathScripts.PathCustom.ObjectCustom(obj) + obj.ViewObject.Proxy = 0 + obj.Gcode = gcode + PathScripts.PathUtils.addToJob(obj) + obj.ToolController = PathScripts.PathUtils.findToolController(obj) + FreeCAD.ActiveDocument.recompute() + + +def parse(inputstring): + "parse(inputstring): returns a parsed output string" + print("preprocessing...") + print(inputstring) + PathLog.track(inputstring) + # split the input by line + lines = inputstring.split("\n") + output = "" + lastcommand = None + print(lines) + + for lin in lines: + # remove any leftover trailing and preceding spaces + lin = lin.strip() + if not lin: + # discard empty lines + continue + if lin[0].upper() in ["N"]: + # remove line numbers + lin = lin.split(" ", 1) + if len(lin) >= 1: + lin = lin[1] + else: + continue + + if lin[0] in ["(", "%", "#", ";"]: + # discard comment and other non strictly gcode lines + continue + if lin[0].upper() in ["G", "M"]: + # found a G or M command: we store it + output += lin + "\n" + last = lin[0].upper() + for c in lin[1:]: + if not c.isdigit(): + break + else: + last += c + lastcommand = last + elif lastcommand: + # no G or M command: we repeat the last one + output += lastcommand + " " + lin + "\n" + + print("done preprocessing.") + return output + + +print(__name__ + " gcode preprocessor loaded.") From 31b17055f1327b2760e0b9e2e39560d1633375fe Mon Sep 17 00:00:00 2001 From: Eric Trombly Date: Mon, 16 Mar 2020 10:17:32 -0500 Subject: [PATCH 002/117] initial work for modifying path of imported gcode --- src/Mod/Path/PathScripts/PathCustom.py | 18 +++ src/Mod/Path/PathScripts/post/gcode_pre.py | 124 +++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 src/Mod/Path/PathScripts/post/gcode_pre.py diff --git a/src/Mod/Path/PathScripts/PathCustom.py b/src/Mod/Path/PathScripts/PathCustom.py index 74cb93ba1a..a07343731c 100644 --- a/src/Mod/Path/PathScripts/PathCustom.py +++ b/src/Mod/Path/PathScripts/PathCustom.py @@ -25,9 +25,11 @@ import FreeCAD import FreeCADGui import Path from PySide import QtCore +from copy import copy __doc__ = """Path Custom object and FreeCAD command""" +movecommands = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03'] # Qt translation handling def translate(context, text, disambig=None): @@ -39,6 +41,8 @@ class ObjectCustom: def __init__(self,obj): obj.addProperty("App::PropertyStringList", "Gcode", "Path", QtCore.QT_TRANSLATE_NOOP("PathCustom", "The gcode to be inserted")) obj.addProperty("App::PropertyLink", "ToolController", "Path", QtCore.QT_TRANSLATE_NOOP("PathCustom", "The tool controller that will be used to calculate the path")) + obj.addProperty("App::PropertyBool", "OperationPlacement", "Path", "Use operation placement") + obj.OperationPlacement = False obj.Proxy = self def __getstate__(self): @@ -54,6 +58,20 @@ class ObjectCustom: s += str(l) if s: path = Path.Path(s) + if obj.OperationPlacement: + for x in range(len(path.Commands)): + if path.Commands[x].Name in movecommands: + base = copy(obj.Placement.Base) + new = path.Commands[x] + if 'X' not in path.Commands[x].Parameters: + base[0] = 0.0 + if 'Y' not in path.Commands[x].Parameters: + base[1] = 0.0 + if 'Z' not in path.Commands[x].Parameters: + base[2] = 0.0 + new.Placement.translate(base) + path.deleteCommand(x) + path.insertCommand(new, x) obj.Path = path diff --git a/src/Mod/Path/PathScripts/post/gcode_pre.py b/src/Mod/Path/PathScripts/post/gcode_pre.py new file mode 100644 index 0000000000..1ec9aaa702 --- /dev/null +++ b/src/Mod/Path/PathScripts/post/gcode_pre.py @@ -0,0 +1,124 @@ +# *************************************************************************** +# * (c) Yorik van Havre (yorik@uncreated.net) 2014 * +# * * +# * 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 * +# * * +# ***************************************************************************/ + + +''' +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. + +Read the Path Workbench documentation to know how to create Path objects +from GCode. +''' + +import os +import Path +import FreeCAD +import PathScripts.PathUtils +import PathScripts.PathLog as PathLog + +# LEVEL = PathLog.Level.DEBUG +LEVEL = PathLog.Level.INFO +PathLog.setLevel(LEVEL, PathLog.thisModule()) + +if LEVEL == PathLog.Level.DEBUG: + PathLog.trackModule(PathLog.thisModule()) + + +# to distinguish python built-in open function from the one declared below +if open.__module__ in ['__builtin__', 'io']: + pythonopen = open + + +def open(filename): + "called when freecad opens a file." + PathLog.track(filename) + docname = os.path.splitext(os.path.basename(filename))[0] + doc = FreeCAD.newDocument(docname) + insert(filename, doc.Name) + + +def insert(filename, docname): + "called when freecad imports a file" + PathLog.track(filename) + gfile = pythonopen(filename) + gcode = gfile.read() + gfile.close() + gcode = parse(gcode) + doc = FreeCAD.getDocument(docname) + obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Custom") + PathScripts.PathCustom.ObjectCustom(obj) + obj.ViewObject.Proxy = 0 + obj.Gcode = gcode + PathScripts.PathUtils.addToJob(obj) + obj.ToolController = PathScripts.PathUtils.findToolController(obj) + FreeCAD.ActiveDocument.recompute() + + +def parse(inputstring): + "parse(inputstring): returns a parsed output string" + print("preprocessing...") + print(inputstring) + PathLog.track(inputstring) + # split the input by line + lines = inputstring.split("\n") + output = "" + lastcommand = None + print(lines) + + for lin in lines: + # remove any leftover trailing and preceding spaces + lin = lin.strip() + if not lin: + # discard empty lines + continue + if lin[0].upper() in ["N"]: + # remove line numbers + lin = lin.split(" ", 1) + if len(lin) >= 1: + lin = lin[1] + else: + continue + + if lin[0] in ["(", "%", "#", ";"]: + # discard comment and other non strictly gcode lines + continue + if lin[0].upper() in ["G", "M"]: + # found a G or M command: we store it + output += lin + "\n" + last = lin[0].upper() + for c in lin[1:]: + if not c.isdigit(): + break + else: + last += c + lastcommand = last + elif lastcommand: + # no G or M command: we repeat the last one + output += lastcommand + " " + lin + "\n" + + print("done preprocessing.") + return output + + +print(__name__ + " gcode preprocessor loaded.") From f1ebaa4cc635f77287e5bbe390b7b8bf30a55780 Mon Sep 17 00:00:00 2001 From: Eric Trombly Date: Fri, 20 Mar 2020 11:21:26 -0500 Subject: [PATCH 003/117] change preprocessor to split lines --- src/Mod/Path/CMakeLists.txt | 1 + src/Mod/Path/PathScripts/PathCustom.py | 34 ++++++++++------------ src/Mod/Path/PathScripts/post/gcode_pre.py | 13 ++++----- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index b30ca0e472..098bb114dd 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -128,6 +128,7 @@ SET(PathScripts_post_SRCS PathScripts/post/comparams_post.py PathScripts/post/dynapath_post.py PathScripts/post/example_pre.py + PathScripts/post/gcode_pre.py PathScripts/post/grbl_post.py PathScripts/post/jtech_post.py PathScripts/post/linuxcnc_post.py diff --git a/src/Mod/Path/PathScripts/PathCustom.py b/src/Mod/Path/PathScripts/PathCustom.py index a07343731c..4be7e94314 100644 --- a/src/Mod/Path/PathScripts/PathCustom.py +++ b/src/Mod/Path/PathScripts/PathCustom.py @@ -52,27 +52,23 @@ class ObjectCustom: return None def execute(self, obj): + commands = [] + newPath = Path.Path if obj.Gcode: - s = "" for l in obj.Gcode: - s += str(l) - if s: - path = Path.Path(s) - if obj.OperationPlacement: - for x in range(len(path.Commands)): - if path.Commands[x].Name in movecommands: - base = copy(obj.Placement.Base) - new = path.Commands[x] - if 'X' not in path.Commands[x].Parameters: - base[0] = 0.0 - if 'Y' not in path.Commands[x].Parameters: - base[1] = 0.0 - if 'Z' not in path.Commands[x].Parameters: - base[2] = 0.0 - new.Placement.translate(base) - path.deleteCommand(x) - path.insertCommand(new, x) - obj.Path = path + newcommand=Path.Command(str(l)) + if newcommand.Name in movecommands and obj.OperationPlacement: + if 'X' in newcommand.Parameters: + newcommand.x += obj.Placement.Base.x + if 'Y' in newcommand.Parameters: + newcommand.y += obj.Placement.Base.y + if 'Z' in newcommand.Parameters: + newcommand.z += obj.Placement.Base.z + + commands.append(newcommand) + newPath.addCommands(commands) + + obj.Path = newPath class CommandPathCustom: diff --git a/src/Mod/Path/PathScripts/post/gcode_pre.py b/src/Mod/Path/PathScripts/post/gcode_pre.py index 1ec9aaa702..0ec421b521 100644 --- a/src/Mod/Path/PathScripts/post/gcode_pre.py +++ b/src/Mod/Path/PathScripts/post/gcode_pre.py @@ -78,13 +78,11 @@ def insert(filename, docname): def parse(inputstring): "parse(inputstring): returns a parsed output string" print("preprocessing...") - print(inputstring) PathLog.track(inputstring) # split the input by line lines = inputstring.split("\n") - output = "" + output = [] #"" lastcommand = None - print(lines) for lin in lines: # remove any leftover trailing and preceding spaces @@ -96,7 +94,7 @@ def parse(inputstring): # remove line numbers lin = lin.split(" ", 1) if len(lin) >= 1: - lin = lin[1] + lin = lin[1].strip() else: continue @@ -105,7 +103,8 @@ def parse(inputstring): continue if lin[0].upper() in ["G", "M"]: # found a G or M command: we store it - output += lin + "\n" + #output += lin + "\n" + output.append(lin) # + "\n" last = lin[0].upper() for c in lin[1:]: if not c.isdigit(): @@ -115,10 +114,10 @@ def parse(inputstring): lastcommand = last elif lastcommand: # no G or M command: we repeat the last one - output += lastcommand + " " + lin + "\n" + output.append(lastcommand + " " + lin) # + "\n" print("done preprocessing.") return output -print(__name__ + " gcode preprocessor loaded.") +print(__name__ + " gcode preprocessor loaded.") \ No newline at end of file From 7f76c5fbc3ad3075b67752bec2a81a7ddc30c8c2 Mon Sep 17 00:00:00 2001 From: Eric Trombly Date: Sun, 22 Mar 2020 12:05:55 -0500 Subject: [PATCH 004/117] split paths on M6 and change placement to use offset --- src/Mod/Path/PathScripts/PathCustom.py | 37 ++++++++++---------- src/Mod/Path/PathScripts/post/example_pre.py | 13 ++++--- src/Mod/Path/PathScripts/post/gcode_pre.py | 25 ++++++++----- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathCustom.py b/src/Mod/Path/PathScripts/PathCustom.py index 4be7e94314..d78bffe360 100644 --- a/src/Mod/Path/PathScripts/PathCustom.py +++ b/src/Mod/Path/PathScripts/PathCustom.py @@ -25,12 +25,13 @@ import FreeCAD import FreeCADGui import Path from PySide import QtCore -from copy import copy + __doc__ = """Path Custom object and FreeCAD command""" movecommands = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03'] + # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) @@ -38,11 +39,13 @@ def translate(context, text, disambig=None): class ObjectCustom: - def __init__(self,obj): - obj.addProperty("App::PropertyStringList", "Gcode", "Path", QtCore.QT_TRANSLATE_NOOP("PathCustom", "The gcode to be inserted")) - obj.addProperty("App::PropertyLink", "ToolController", "Path", QtCore.QT_TRANSLATE_NOOP("PathCustom", "The tool controller that will be used to calculate the path")) - obj.addProperty("App::PropertyBool", "OperationPlacement", "Path", "Use operation placement") - obj.OperationPlacement = False + def __init__(self, obj): + obj.addProperty("App::PropertyStringList", "Gcode", "Path", + QtCore.QT_TRANSLATE_NOOP("PathCustom", "The gcode to be inserted")) + obj.addProperty("App::PropertyLink", "ToolController", "Path", + QtCore.QT_TRANSLATE_NOOP("PathCustom", "The tool controller that will be used to calculate the path")) + obj.addProperty("App::PropertyPlacement", "Offset", "Path", + "Placement Offset") obj.Proxy = self def __getstate__(self): @@ -52,23 +55,21 @@ class ObjectCustom: return None def execute(self, obj): - commands = [] - newPath = Path.Path + newpath = Path.Path() if obj.Gcode: for l in obj.Gcode: - newcommand=Path.Command(str(l)) - if newcommand.Name in movecommands and obj.OperationPlacement: + newcommand = Path.Command(str(l)) + if newcommand.Name in movecommands: if 'X' in newcommand.Parameters: - newcommand.x += obj.Placement.Base.x + newcommand.x += obj.Offset.Base.x if 'Y' in newcommand.Parameters: - newcommand.y += obj.Placement.Base.y + newcommand.y += obj.Offset.Base.y if 'Z' in newcommand.Parameters: - newcommand.z += obj.Placement.Base.z + newcommand.z += obj.Offset.Base.z - commands.append(newcommand) - newPath.addCommands(commands) + newpath.insertCommand(newcommand) - obj.Path = newPath + obj.Path=newpath class CommandPathCustom: @@ -89,7 +90,7 @@ class CommandPathCustom: FreeCAD.ActiveDocument.openTransaction("Create Custom Path") FreeCADGui.addModule("PathScripts.PathCustom") FreeCADGui.addModule("PathScripts.PathUtils") - FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Custom")') + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Custom")') FreeCADGui.doCommand('PathScripts.PathCustom.ObjectCustom(obj)') FreeCADGui.doCommand('obj.ViewObject.Proxy = 0') FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') @@ -100,4 +101,4 @@ class CommandPathCustom: if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_Custom', CommandPathCustom()) + FreeCADGui.addCommand('Path_Custom', CommandPathCustom()) \ No newline at end of file diff --git a/src/Mod/Path/PathScripts/post/example_pre.py b/src/Mod/Path/PathScripts/post/example_pre.py index 5928c30301..6e90d556b9 100644 --- a/src/Mod/Path/PathScripts/post/example_pre.py +++ b/src/Mod/Path/PathScripts/post/example_pre.py @@ -73,13 +73,11 @@ def insert(filename, docname): def parse(inputstring): "parse(inputstring): returns a parsed output string" print("preprocessing...") - print(inputstring) PathLog.track(inputstring) # split the input by line lines = inputstring.split("\n") - output = "" - lastcommand = None - print(lines) + output = [] #"" + lastcommand = None for lin in lines: # remove any leftover trailing and preceding spaces @@ -91,7 +89,7 @@ def parse(inputstring): # remove line numbers lin = lin.split(" ", 1) if len(lin) >= 1: - lin = lin[1] + lin = lin[1].strip() else: continue @@ -100,7 +98,8 @@ def parse(inputstring): continue if lin[0].upper() in ["G", "M"]: # found a G or M command: we store it - output += lin + "\n" + #output += lin + "\n" + output.append(lin) # + "\n" last = lin[0].upper() for c in lin[1:]: if not c.isdigit(): @@ -110,7 +109,7 @@ def parse(inputstring): lastcommand = last elif lastcommand: # no G or M command: we repeat the last one - output += lastcommand + " " + lin + "\n" + output.append(lastcommand + " " + lin) # + "\n" print("done preprocessing.") return output diff --git a/src/Mod/Path/PathScripts/post/gcode_pre.py b/src/Mod/Path/PathScripts/post/gcode_pre.py index 0ec421b521..1b3eee4b49 100644 --- a/src/Mod/Path/PathScripts/post/gcode_pre.py +++ b/src/Mod/Path/PathScripts/post/gcode_pre.py @@ -36,6 +36,7 @@ import Path import FreeCAD import PathScripts.PathUtils import PathScripts.PathLog as PathLog +import re # LEVEL = PathLog.Level.DEBUG LEVEL = PathLog.Level.INFO @@ -64,14 +65,20 @@ def insert(filename, docname): gfile = pythonopen(filename) gcode = gfile.read() gfile.close() - gcode = parse(gcode) - doc = FreeCAD.getDocument(docname) - obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Custom") - PathScripts.PathCustom.ObjectCustom(obj) - obj.ViewObject.Proxy = 0 - obj.Gcode = gcode - PathScripts.PathUtils.addToJob(obj) - obj.ToolController = PathScripts.PathUtils.findToolController(obj) + # split on tool changes + paths = re.split('(?=[mM]+\s?0?6)', gcode) + # if there are any tool changes combine the preamble with the default tool + if len(paths) > 1: + paths = ["\n".join(paths[0:2])] + paths[2:] + for path in paths: + gcode = parse(path) + doc = FreeCAD.getDocument(docname) + obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Custom") + PathScripts.PathCustom.ObjectCustom(obj) + obj.ViewObject.Proxy = 0 + obj.Gcode = gcode + PathScripts.PathUtils.addToJob(obj) + obj.ToolController = PathScripts.PathUtils.findToolController(obj) FreeCAD.ActiveDocument.recompute() @@ -82,7 +89,7 @@ def parse(inputstring): # split the input by line lines = inputstring.split("\n") output = [] #"" - lastcommand = None + lastcommand = None for lin in lines: # remove any leftover trailing and preceding spaces From f66747ec15eb9fc6200e1ea9c23207d4546f0db7 Mon Sep 17 00:00:00 2001 From: Eric Trombly Date: Sun, 22 Mar 2020 12:15:57 -0500 Subject: [PATCH 005/117] split paths on M6 and change placement to use offset --- src/Mod/Path/PathScripts/post/gcode_pre.py | 41 ---------------------- 1 file changed, 41 deletions(-) diff --git a/src/Mod/Path/PathScripts/post/gcode_pre.py b/src/Mod/Path/PathScripts/post/gcode_pre.py index 80a0753a78..053c39c229 100644 --- a/src/Mod/Path/PathScripts/post/gcode_pre.py +++ b/src/Mod/Path/PathScripts/post/gcode_pre.py @@ -36,10 +36,7 @@ import Path import FreeCAD import PathScripts.PathUtils import PathScripts.PathLog as PathLog -<<<<<<< HEAD import re -======= ->>>>>>> fbb59ae712e3ebf2219f480f799f8c2e1f1ca006 # LEVEL = PathLog.Level.DEBUG LEVEL = PathLog.Level.INFO @@ -68,7 +65,6 @@ def insert(filename, docname): gfile = pythonopen(filename) gcode = gfile.read() gfile.close() -<<<<<<< HEAD # split on tool changes paths = re.split('(?=[mM]+\s?0?6)', gcode) # if there are any tool changes combine the preamble with the default tool @@ -83,37 +79,17 @@ def insert(filename, docname): obj.Gcode = gcode PathScripts.PathUtils.addToJob(obj) obj.ToolController = PathScripts.PathUtils.findToolController(obj) -======= - gcode = parse(gcode) - doc = FreeCAD.getDocument(docname) - obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Custom") - PathScripts.PathCustom.ObjectCustom(obj) - obj.ViewObject.Proxy = 0 - obj.Gcode = gcode - PathScripts.PathUtils.addToJob(obj) - obj.ToolController = PathScripts.PathUtils.findToolController(obj) ->>>>>>> fbb59ae712e3ebf2219f480f799f8c2e1f1ca006 FreeCAD.ActiveDocument.recompute() def parse(inputstring): "parse(inputstring): returns a parsed output string" print("preprocessing...") -<<<<<<< HEAD PathLog.track(inputstring) # split the input by line lines = inputstring.split("\n") output = [] #"" lastcommand = None -======= - print(inputstring) - PathLog.track(inputstring) - # split the input by line - lines = inputstring.split("\n") - output = "" - lastcommand = None - print(lines) ->>>>>>> fbb59ae712e3ebf2219f480f799f8c2e1f1ca006 for lin in lines: # remove any leftover trailing and preceding spaces @@ -125,11 +101,7 @@ def parse(inputstring): # remove line numbers lin = lin.split(" ", 1) if len(lin) >= 1: -<<<<<<< HEAD lin = lin[1].strip() -======= - lin = lin[1] ->>>>>>> fbb59ae712e3ebf2219f480f799f8c2e1f1ca006 else: continue @@ -138,12 +110,8 @@ def parse(inputstring): continue if lin[0].upper() in ["G", "M"]: # found a G or M command: we store it -<<<<<<< HEAD #output += lin + "\n" output.append(lin) # + "\n" -======= - output += lin + "\n" ->>>>>>> fbb59ae712e3ebf2219f480f799f8c2e1f1ca006 last = lin[0].upper() for c in lin[1:]: if not c.isdigit(): @@ -153,18 +121,9 @@ def parse(inputstring): lastcommand = last elif lastcommand: # no G or M command: we repeat the last one -<<<<<<< HEAD output.append(lastcommand + " " + lin) # + "\n" -======= - output += lastcommand + " " + lin + "\n" ->>>>>>> fbb59ae712e3ebf2219f480f799f8c2e1f1ca006 print("done preprocessing.") return output - -<<<<<<< HEAD print(__name__ + " gcode preprocessor loaded.") -======= -print(__name__ + " gcode preprocessor loaded.") ->>>>>>> fbb59ae712e3ebf2219f480f799f8c2e1f1ca006 From 8756ae8590411f71a343c92d7064567176629a48 Mon Sep 17 00:00:00 2001 From: Eric Trombly Date: Sun, 22 Mar 2020 17:59:16 -0500 Subject: [PATCH 006/117] Change example_pre to use a list of commands instead of one string --- src/Mod/Path/PathScripts/post/example_pre.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/post/example_pre.py b/src/Mod/Path/PathScripts/post/example_pre.py index 6e90d556b9..48ceb433bf 100644 --- a/src/Mod/Path/PathScripts/post/example_pre.py +++ b/src/Mod/Path/PathScripts/post/example_pre.py @@ -99,7 +99,7 @@ def parse(inputstring): if lin[0].upper() in ["G", "M"]: # found a G or M command: we store it #output += lin + "\n" - output.append(lin) # + "\n" + output.append(Path.Command(str(lin))) # + "\n" last = lin[0].upper() for c in lin[1:]: if not c.isdigit(): @@ -109,7 +109,7 @@ def parse(inputstring): lastcommand = last elif lastcommand: # no G or M command: we repeat the last one - output.append(lastcommand + " " + lin) # + "\n" + output.append(Path.Command(str(lastcommand + " " + lin))) # + "\n" print("done preprocessing.") return output From 1955c62a285acdd79c70e33fb65216e67e5339ac Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Mon, 23 Mar 2020 00:48:09 +0100 Subject: [PATCH 007/117] Added 4th axis support to drilling GUI --- .../Resources/panels/PageOpDrillingEdit.ui | 35 +++++++++++++++++-- src/Mod/Path/PathScripts/PathDrillingGui.py | 4 +++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpDrillingEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpDrillingEdit.ui index e815e4cc86..d7cd354325 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpDrillingEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpDrillingEdit.ui @@ -6,8 +6,8 @@ 0 0 - 572 - 419 + 454 + 386 @@ -154,6 +154,37 @@ + + + + + Off + + + + + A(x) + + + + + B(y) + + + + + A & B + + + + + + + + Enable Rotation + + + diff --git a/src/Mod/Path/PathScripts/PathDrillingGui.py b/src/Mod/Path/PathScripts/PathDrillingGui.py index 10e4965c19..ed7a8e714d 100644 --- a/src/Mod/Path/PathScripts/PathDrillingGui.py +++ b/src/Mod/Path/PathScripts/PathDrillingGui.py @@ -99,6 +99,8 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): obj.PeckEnabled = self.form.peckEnabled.isChecked() if obj.ExtraOffset != str(self.form.ExtraOffset.currentText()): obj.ExtraOffset = str(self.form.ExtraOffset.currentText()) + if obj.EnableRotation != str(self.form.enableRotation.currentText()): + obj.EnableRotation = str(self.form.enableRotation.currentText()) self.updateToolController(obj, self.form.toolController) self.updateCoolant(obj, self.form.coolantController) @@ -122,6 +124,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): self.setupToolController(obj, self.form.toolController) self.setupCoolant(obj, self.form.coolantController) + self.selectInComboBox(obj.EnableRotation, self.form.enableRotation) def getSignalsForUpdate(self, obj): @@ -137,6 +140,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): signals.append(self.form.coolantController.currentIndexChanged) signals.append(self.form.coolantController.currentIndexChanged) signals.append(self.form.ExtraOffset.currentIndexChanged) + signals.append(self.form.enableRotation.currentIndexChanged) return signals From b38dcd9ff6c92e6a975c36344769661fc4c4ea84 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 11 Feb 2020 02:07:18 -0600 Subject: [PATCH 008/117] Draft: move DraftTrackers to another module. Properly import the tracker classes. Avoid star imports, and use the classes prefixed with the name of the module. The BIM Workbench also has to be updated to use the new module. ``` import draftguitools.gui_trackers as DraftTrackers ``` --- src/Mod/Draft/CMakeLists.txt | 2 +- src/Mod/Draft/DraftTools.py | 78 +++++++++---------- .../gui_trackers.py} | 0 3 files changed, 39 insertions(+), 41 deletions(-) rename src/Mod/Draft/{DraftTrackers.py => draftguitools/gui_trackers.py} (100%) diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index 0bd5a6f64c..a8bc314f46 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -10,7 +10,6 @@ SET(Draft_SRCS_base Draft.py DraftTools.py DraftGui.py - DraftTrackers.py DraftVecUtils.py DraftGeomUtils.py DraftLayer.py @@ -85,6 +84,7 @@ SET(Draft_GUI_tools draftguitools/gui_arrays.py draftguitools/gui_snaps.py draftguitools/gui_snapper.py + draftguitools/gui_trackers.py draftguitools/README.md ) diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 809514eded..855ef83ebf 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -42,8 +42,7 @@ from PySide import QtCore,QtGui from DraftGui import todo, translate, utf8_decode import draftguitools.gui_snapper as gui_snapper import DraftGui -import DraftTrackers -from DraftTrackers import * +import draftguitools.gui_trackers as trackers from pivy import coin if not hasattr(FreeCADGui, "Snapper"): @@ -271,7 +270,7 @@ class DraftTool: self.ui.setTitle(name) self.planetrack = None if Draft.getParam("showPlaneTracker",False): - self.planetrack = PlaneTracker() + self.planetrack = trackers.PlaneTracker() if hasattr(FreeCADGui,"Snapper"): FreeCADGui.Snapper.setTrackers() @@ -596,7 +595,7 @@ class BSpline(Line): def Activated(self): Line.Activated(self,name=translate("draft","BSpline")) if self.doc: - self.bsplinetrack = bsplineTracker() + self.bsplinetrack = trackers.bsplineTracker() def action(self,arg): """scene event handler""" @@ -697,7 +696,7 @@ class BezCurve(Line): def Activated(self): Line.Activated(self,name=translate("draft","BezCurve")) if self.doc: - self.bezcurvetrack = bezcurveTracker() + self.bezcurvetrack = trackers.bezcurveTracker() def action(self,arg): """scene event handler""" @@ -816,7 +815,7 @@ class CubicBezCurve(Line): def Activated(self): Line.Activated(self,name=translate("draft","CubicBezCurve")) if self.doc: - self.bezcurvetrack = bezcurveTracker() + self.bezcurvetrack = trackers.bezcurveTracker() def action(self,arg): """scene event handler""" @@ -1045,7 +1044,7 @@ class Rectangle(Creator): self.fillstate = self.ui.hasFill.isChecked() self.ui.hasFill.setChecked(True) self.call = self.view.addEventCallback("SoEvent",self.action) - self.rect = rectangleTracker() + self.rect = trackers.rectangleTracker() FreeCAD.Console.PrintMessage(translate("draft", "Pick first point")+"\n") def finish(self,closed=False,cont=False): @@ -1172,8 +1171,8 @@ class Arc(Creator): else: self.ui.circleUi() self.altdown = False self.ui.sourceCmd = self - self.linetrack = lineTracker(dotted=True) - self.arctrack = arcTracker() + self.linetrack = trackers.lineTracker(dotted=True) + self.arctrack = trackers.arcTracker() self.call = self.view.addEventCallback("SoEvent",self.action) FreeCAD.Console.PrintMessage(translate("draft", "Pick center point")+"\n") @@ -1510,7 +1509,7 @@ class Polygon(Creator): self.ui.numFacesLabel.show() self.altdown = False self.ui.sourceCmd = self - self.arctrack = arcTracker() + self.arctrack = trackers.arcTracker() self.call = self.view.addEventCallback("SoEvent",self.action) FreeCAD.Console.PrintMessage(translate("draft", "Pick center point")+"\n") @@ -1688,7 +1687,7 @@ class Ellipse(Creator): self.ui.pointUi(name) self.ui.extUi() self.call = self.view.addEventCallback("SoEvent",self.action) - self.rect = rectangleTracker() + self.rect = trackers.rectangleTracker() FreeCAD.Console.PrintMessage(translate("draft", "Pick first point")+"\n") def finish(self,closed=False,cont=False): @@ -1890,8 +1889,8 @@ class Dimension(Creator): self.finish() elif self.hasMeasures(): Creator.Activated(self,name) - self.dimtrack = dimTracker() - self.arctrack = arcTracker() + self.dimtrack = trackers.dimTracker() + self.arctrack = trackers.arcTracker() self.createOnMeasures() self.finish() else: @@ -1902,8 +1901,8 @@ class Dimension(Creator): self.ui.selectButton.show() self.altdown = False self.call = self.view.addEventCallback("SoEvent",self.action) - self.dimtrack = dimTracker() - self.arctrack = arcTracker() + self.dimtrack = trackers.dimTracker() + self.arctrack = trackers.arcTracker() self.link = None self.edges = [] self.pts = [] @@ -2460,7 +2459,7 @@ class Move(Modifier): def set_ghosts(self): if self.ui.isSubelementMode.isChecked(): return self.set_subelement_ghosts() - self.ghosts = [ghostTracker(self.selected_objects)] + self.ghosts = [trackers.ghostTracker(self.selected_objects)] def set_subelement_ghosts(self): import Part @@ -2468,7 +2467,7 @@ class Move(Modifier): for subelement in object.SubObjects: if isinstance(subelement, Part.Vertex) \ or isinstance(subelement, Part.Edge): - self.ghosts.append(ghostTracker(subelement)) + self.ghosts.append(trackers.ghostTracker(subelement)) def move(self): if self.ui.isSubelementMode.isChecked(): @@ -2616,7 +2615,7 @@ class Rotate(Modifier): self.ui.rotateSetCenterUi() self.ui.modUi() self.ui.setTitle(translate("draft","Rotate")) - self.arctrack = arcTracker() + self.arctrack = trackers.arcTracker() self.call = self.view.addEventCallback("SoEvent",self.action) FreeCAD.Console.PrintMessage(translate("draft", "Pick rotation center")+"\n") @@ -2730,7 +2729,7 @@ class Rotate(Modifier): def set_ghosts(self): if self.ui.isSubelementMode.isChecked(): return self.set_subelement_ghosts() - self.ghosts = [ghostTracker(self.selected_objects)] + self.ghosts = [trackers.ghostTracker(self.selected_objects)] def set_subelement_ghosts(self): import Part @@ -2738,7 +2737,7 @@ class Rotate(Modifier): for subelement in object.SubObjects: if isinstance(subelement, Part.Vertex) \ or isinstance(subelement, Part.Edge): - self.ghosts.append(ghostTracker(subelement)) + self.ghosts.append(trackers.ghostTracker(subelement)) def finish(self, closed=False, cont=False): """finishes the arc""" @@ -2892,19 +2891,19 @@ class Offset(Modifier): self.npts = None self.constrainSeg = None self.ui.offsetUi() - self.linetrack = lineTracker() + self.linetrack = trackers.lineTracker() self.faces = False self.shape = self.sel.Shape self.mode = None if Draft.getType(self.sel) in ["Circle","Arc"]: - self.ghost = arcTracker() + self.ghost = trackers.arcTracker() self.mode = "Circle" self.center = self.shape.Edges[0].Curve.Center self.ghost.setCenter(self.center) self.ghost.setStartAngle(math.radians(self.sel.FirstAngle)) self.ghost.setEndAngle(math.radians(self.sel.LastAngle)) elif Draft.getType(self.sel) == "BSpline": - self.ghost = bsplineTracker(points=self.sel.Points) + self.ghost = trackers.bsplineTracker(points=self.sel.Points) self.mode = "BSpline" elif Draft.getType(self.sel) == "BezCurve": FreeCAD.Console.PrintWarning(translate("draft", "Sorry, offset of Bezier curves is currently still not supported")+"\n") @@ -2914,7 +2913,7 @@ class Offset(Modifier): if len(self.sel.Shape.Edges) == 1: import Part if isinstance(self.sel.Shape.Edges[0].Curve,Part.Circle): - self.ghost = arcTracker() + self.ghost = trackers.arcTracker() self.mode = "Circle" self.center = self.shape.Edges[0].Curve.Center self.ghost.setCenter(self.center) @@ -2922,7 +2921,7 @@ class Offset(Modifier): self.ghost.setStartAngle(self.sel.Shape.Edges[0].FirstParameter) self.ghost.setEndAngle(self.sel.Shape.Edges[0].LastParameter) if not self.ghost: - self.ghost = wireTracker(self.shape) + self.ghost = trackers.wireTracker(self.shape) self.mode = "Wire" self.call = self.view.addEventCallback("SoEvent",self.action) FreeCAD.Console.PrintMessage(translate("draft", "Pick distance")+"\n") @@ -3102,7 +3101,7 @@ class Stretch(Modifier): self.ui.pointUi("Stretch") self.ui.extUi() self.call = self.view.addEventCallback("SoEvent",self.action) - self.rectracker = rectangleTracker(dotted=True,scolor=(0.0,0.0,1.0),swidth=2) + self.rectracker = trackers.rectangleTracker(dotted=True,scolor=(0.0,0.0,1.0),swidth=2) self.nodetracker = [] self.displacement = None FreeCAD.Console.PrintMessage(translate("draft", "Pick first point of selection rectangle")+"\n") @@ -3195,7 +3194,7 @@ class Stretch(Modifier): self.ops.append([o]) nodes.append(p) for n in nodes: - nt = editTracker(n,inactive=True) + nt = trackers.editTracker(n,inactive=True) nt.on() self.nodetracker.append(nt) self.step = 3 @@ -3538,7 +3537,7 @@ class Trimex(Modifier): return self.obj = sel[0] self.ui.trimUi() - self.linetrack = lineTracker() + self.linetrack = trackers.lineTracker() import DraftGeomUtils @@ -3548,10 +3547,10 @@ class Trimex(Modifier): if len(self.obj.Shape.Faces) == 1: # simple extrude mode, the object itself is extruded self.extrudeMode = True - self.ghost = [ghostTracker([self.obj])] + self.ghost = [trackers.ghostTracker([self.obj])] self.normal = self.obj.Shape.Faces[0].normalAt(.5,.5) for v in self.obj.Shape.Vertexes: - self.ghost.append(lineTracker()) + self.ghost.append(trackers.lineTracker()) elif len(self.obj.Shape.Faces) > 1: # face extrude mode, a new object is created ss = FreeCADGui.Selection.getSelectionEx()[0] @@ -3560,10 +3559,10 @@ class Trimex(Modifier): self.obj = self.doc.addObject("Part::Feature","Face") self.obj.Shape = ss.SubObjects[0] self.extrudeMode = True - self.ghost = [ghostTracker([self.obj])] + self.ghost = [trackers.ghostTracker([self.obj])] self.normal = self.obj.Shape.Faces[0].normalAt(.5,.5) for v in self.obj.Shape.Vertexes: - self.ghost.append(lineTracker()) + self.ghost.append(trackers.lineTracker()) else: # normal wire trimex mode self.color = self.obj.ViewObject.LineColor @@ -3583,9 +3582,9 @@ class Trimex(Modifier): sw = self.width for e in self.edges: if DraftGeomUtils.geomType(e) == "Line": - self.ghost.append(lineTracker(scolor=sc,swidth=sw)) + self.ghost.append(trackers.lineTracker(scolor=sc,swidth=sw)) else: - self.ghost.append(arcTracker(scolor=sc,swidth=sw)) + self.ghost.append(trackers.arcTracker(scolor=sc,swidth=sw)) if not self.ghost: self.finish() for g in self.ghost: g.on() self.activePoint = 0 @@ -3967,7 +3966,7 @@ class Scale(Modifier): def set_ghosts(self): if self.ui.isSubelementMode.isChecked(): return self.set_subelement_ghosts() - self.ghosts = [ghostTracker(self.selected_objects)] + self.ghosts = [trackers.ghostTracker(self.selected_objects)] def set_subelement_ghosts(self): import Part @@ -3975,7 +3974,7 @@ class Scale(Modifier): for subelement in object.SubObjects: if isinstance(subelement, Part.Vertex) \ or isinstance(subelement, Part.Edge): - self.ghosts.append(ghostTracker(subelement)) + self.ghosts.append(trackers.ghostTracker(subelement)) def pickRef(self): self.pickmode = True @@ -4972,7 +4971,7 @@ class Mirror(Modifier): self.ui.modUi() self.ui.xValue.setFocus() self.ui.xValue.selectAll() - #self.ghost = ghostTracker(self.sel) TODO: solve this (see below) + # self.ghost = trackers.ghostTracker(self.sel) TODO: solve this (see below) self.call = self.view.addEventCallback("SoEvent",self.action) FreeCAD.Console.PrintMessage(translate("draft", "Pick start point of mirror line")+"\n") self.ui.isCopy.hide() @@ -5199,7 +5198,7 @@ class Draft_Label(Creator): self.ui.labelUi(self.name,callback=self.setmode) self.ui.xValue.setFocus() self.ui.xValue.selectAll() - self.ghost = DraftTrackers.lineTracker() + self.ghost = trackers.lineTracker() self.call = self.view.addEventCallback("SoEvent",self.action) FreeCAD.Console.PrintMessage(translate("draft", "Pick target point")+"\n") self.ui.isCopy.hide() @@ -5363,10 +5362,9 @@ class Draft_Arc_3Points: def Activated(self): - import DraftTrackers self.points = [] self.normal = None - self.tracker = DraftTrackers.arcTracker() + self.tracker = trackers.arcTracker() self.tracker.autoinvert = False if hasattr(FreeCAD,"DraftWorkingPlane"): FreeCAD.DraftWorkingPlane.setup() diff --git a/src/Mod/Draft/DraftTrackers.py b/src/Mod/Draft/draftguitools/gui_trackers.py similarity index 100% rename from src/Mod/Draft/DraftTrackers.py rename to src/Mod/Draft/draftguitools/gui_trackers.py From 439d021dd791c35eb7ba42c47b5a233cab8afd4f Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 11 Feb 2020 20:07:53 -0600 Subject: [PATCH 009/117] Draft: Snapper class with new gui_trackers module Also change the imports in `DraftFillet.py` and `DraftEdit.py` so the trackers are found. --- src/Mod/Draft/DraftEdit.py | 2 +- src/Mod/Draft/DraftFillet.py | 6 +- src/Mod/Draft/draftguitools/gui_snapper.py | 82 ++++++++++++---------- 3 files changed, 47 insertions(+), 43 deletions(-) diff --git a/src/Mod/Draft/DraftEdit.py b/src/Mod/Draft/DraftEdit.py index aac590921c..095115269c 100644 --- a/src/Mod/Draft/DraftEdit.py +++ b/src/Mod/Draft/DraftEdit.py @@ -35,7 +35,7 @@ if App.GuiUp: # Do not import GUI-related modules if GUI is not there import FreeCADGui as Gui import DraftTools - from DraftTrackers import editTracker, wireTracker, arcTracker, bsplineTracker, bezcurveTracker + from draftguitools.gui_trackers import editTracker, wireTracker, arcTracker, bsplineTracker, bezcurveTracker from pivy import coin from PySide import QtCore, QtGui from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Draft/DraftFillet.py b/src/Mod/Draft/DraftFillet.py index d075597216..020f283b2b 100644 --- a/src/Mod/Draft/DraftFillet.py +++ b/src/Mod/Draft/DraftFillet.py @@ -15,7 +15,7 @@ if FreeCAD.GuiUp: from PySide.QtCore import QT_TRANSLATE_NOOP from PySide import QtCore import DraftTools - import DraftTrackers + import draftguitools.gui_trackers as trackers from DraftGui import translate else: def QT_TRANSLATE_NOOP(context, text): @@ -221,8 +221,8 @@ class CommandFillet(DraftTools.Creator): QtCore.QObject.connect(self.ui.check_chamfer, QtCore.SIGNAL("stateChanged(int)"), self.set_chamfer) - self.linetrack = DraftTrackers.lineTracker(dotted=True) - self.arctrack = DraftTrackers.arcTracker() + self.linetrack = trackers.lineTracker(dotted=True) + self.arctrack = trackers.arcTracker() # self.call = self.view.addEventCallback("SoEvent", self.action) FCC.PrintMessage(translate("draft", "Enter radius") + "\n") diff --git a/src/Mod/Draft/draftguitools/gui_snapper.py b/src/Mod/Draft/draftguitools/gui_snapper.py index 447693ff7c..189207f30a 100644 --- a/src/Mod/Draft/draftguitools/gui_snapper.py +++ b/src/Mod/Draft/draftguitools/gui_snapper.py @@ -1,25 +1,25 @@ -#*************************************************************************** -#* Copyright (c) 2011 Yorik van Havre * -#* * -#* 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. * -#* * -#* This program is distributed in the hope that it will be useful, * -#* but WITHOUT ANY WARRANTY; without even the implied warranty of * -#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -#* GNU Library General Public License for more details. * -#* * -#* You should have received a copy of the GNU Library General Public * -#* License along with this program; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** +# *************************************************************************** +# * Copyright (c) 2011 Yorik van Havre * +# * * +# * 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. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** -__title__="FreeCAD Draft Snap tools" +__title__ = "FreeCAD Draft Snap tools" __author__ = "Yorik van Havre" __url__ = "https://www.freecadweb.org" @@ -31,14 +31,18 @@ __url__ = "https://www.freecadweb.org" # everything that goes with it (toolbar buttons, cursor icons, etc) -import FreeCAD, FreeCADGui, math, Draft, DraftGui, DraftTrackers, DraftVecUtils, itertools +import FreeCAD, FreeCADGui, math, Draft, DraftVecUtils, itertools +import draftguitools.gui_trackers as trackers from collections import OrderedDict from FreeCAD import Vector from pivy import coin -from PySide import QtCore,QtGui +from PySide import QtCore, QtGui + class Snapper: - """The Snapper objects contains all the functionality used by draft + """Classes to manage snapping in Draft and Arch. + + The Snapper objects contains all the functionality used by draft and arch module to manage object snapping. It is responsible for finding snap points and displaying snap markers. Usually You only need to invoke it's snap() function, all the rest is taken @@ -972,9 +976,9 @@ class Snapper: "show arch dimensions between 2 points" if self.isEnabled("Dimensions"): if not self.dim1: - self.dim1 = DraftTrackers.archDimTracker(mode=2) + self.dim1 = trackers.archDimTracker(mode=2) if not self.dim2: - self.dim2 = DraftTrackers.archDimTracker(mode=3) + self.dim2 = trackers.archDimTracker(mode=3) self.dim1.p1(p1) self.dim2.p1(p1) self.dim1.p2(p2) @@ -1090,9 +1094,9 @@ class Snapper: # setup trackers if needed if not self.constrainLine: if self.snapStyle: - self.constrainLine = DraftTrackers.lineTracker(scolor=FreeCADGui.draftToolBar.getDefaultColor("snap")) + self.constrainLine = trackers.lineTracker(scolor=FreeCADGui.draftToolBar.getDefaultColor("snap")) else: - self.constrainLine = DraftTrackers.lineTracker(dotted=True) + self.constrainLine = trackers.lineTracker(dotted=True) # setting basepoint if not basepoint: @@ -1441,23 +1445,23 @@ class Snapper: self.holdTracker = self.trackers[9][i] else: if Draft.getParam("grid",True): - self.grid = DraftTrackers.gridTracker() + self.grid = trackers.gridTracker() self.grid.on() else: self.grid = None - self.tracker = DraftTrackers.snapTracker() - self.trackLine = DraftTrackers.lineTracker() + self.tracker = trackers.snapTracker() + self.trackLine = trackers.lineTracker() if self.snapStyle: c = FreeCADGui.draftToolBar.getDefaultColor("snap") - self.extLine = DraftTrackers.lineTracker(scolor=c) - self.extLine2 = DraftTrackers.lineTracker(scolor = c) + self.extLine = trackers.lineTracker(scolor=c) + self.extLine2 = trackers.lineTracker(scolor = c) else: - self.extLine = DraftTrackers.lineTracker(dotted=True) - self.extLine2 = DraftTrackers.lineTracker(dotted=True) - self.radiusTracker = DraftTrackers.radiusTracker() - self.dim1 = DraftTrackers.archDimTracker(mode=2) - self.dim2 = DraftTrackers.archDimTracker(mode=3) - self.holdTracker = DraftTrackers.snapTracker() + self.extLine = trackers.lineTracker(dotted=True) + self.extLine2 = trackers.lineTracker(dotted=True) + self.radiusTracker = trackers.radiusTracker() + self.dim1 = trackers.archDimTracker(mode=2) + self.dim2 = trackers.archDimTracker(mode=3) + self.holdTracker = trackers.snapTracker() self.holdTracker.setMarker("cross") self.holdTracker.clear() self.trackers[0].append(v) From 791f799626f14870d14629c0307f8bb891dd0ed3 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 11 Feb 2020 20:49:23 -0600 Subject: [PATCH 010/117] Draft: update unit test for new gui_trackers module --- src/Mod/Draft/drafttests/test_import_gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Draft/drafttests/test_import_gui.py b/src/Mod/Draft/drafttests/test_import_gui.py index 0a23b0c8b6..decec80ac7 100644 --- a/src/Mod/Draft/drafttests/test_import_gui.py +++ b/src/Mod/Draft/drafttests/test_import_gui.py @@ -68,7 +68,7 @@ class DraftGuiImport(unittest.TestCase): def test_import_gui_draft_trackers(self): """Import Draft tracker utilities.""" - module = "DraftTrackers" + module = "draftguitools.gui_trackers" if not App.GuiUp: aux._no_gui(module) self.assertTrue(True) From a86e2a69b1650cc926bbb612913518946fbc3080 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 10 Mar 2020 22:32:20 -0600 Subject: [PATCH 011/117] Draft: ImageTools, import new gui_trackers module --- .../Image/ImageTools/_CommandImageScaling.py | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/Mod/Image/ImageTools/_CommandImageScaling.py b/src/Mod/Image/ImageTools/_CommandImageScaling.py index 50f7e5a39e..d61c8d52a8 100644 --- a/src/Mod/Image/ImageTools/_CommandImageScaling.py +++ b/src/Mod/Image/ImageTools/_CommandImageScaling.py @@ -19,27 +19,28 @@ # * USA * # * * # *************************************************************************** +"""Provides the Image_Scaling GuiCommand.""" __title__ = "ImageTools._CommandImageScaling" -__author__ = "JAndersM" -__url__ = "http://www.freecadweb.org/index-fr.html" +__author__ = "JAndersM" +__url__ = "http://www.freecadweb.org/index-fr.html" __version__ = "00.02" -__date__ = "03/05/2019" - - -import FreeCAD -if FreeCAD.GuiUp: - import FreeCADGui - from PySide import QtGui - from PySide import QtCore - import FreeCADGui, FreeCAD, Part - import math - import pivy.coin as pvy - import DraftTrackers, Draft +__date__ = "03/05/2019" -# translation-related code -#(see forum thread "A new Part tool is being born... JoinFeatures!" -#http://forum.freecadweb.org/viewtopic.php?f=22&t=11112&start=30#p90239 ) +import math +import FreeCAD +from PySide import QtCore + +if FreeCAD.GuiUp: + from PySide import QtGui + import pivy.coin as pvy + + import FreeCADGui + import draftguitools.gui_trackers as trackers + +# Translation-related code +# See forum thread "A new Part tool is being born... JoinFeatures!" +# http://forum.freecadweb.org/viewtopic.php?f=22&t=11112&start=30#p90239 try: _fromUtf8 = QtCore.QString.fromUtf8 except (Exception): @@ -134,7 +135,7 @@ def cmdCreateImageScaling(name): QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), self.accept) QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), self.reject) QtCore.QMetaObject.connectSlotsByName(Dialog) - self.tracker = DraftTrackers.lineTracker(scolor=(1,0,0)) + self.tracker = trackers.lineTracker(scolor=(1,0,0)) self.tracker.raiseTracker() self.tracker.on() self.dialog.show() From a150b3048dbc7a5867315b4c69649881eccc5ae6 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 11 Feb 2020 02:15:17 -0600 Subject: [PATCH 012/117] Draft: use functions from the utils module, not DraftGui These functions were moved from DraftGui to individual modules precisely to make them easy to import. Otherwise we have to import the entire DraftGui module which creates dependency problems when we want to use some functions without the graphical interface. --- src/Mod/Draft/DraftTools.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 855ef83ebf..7629b91c65 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -39,7 +39,8 @@ __url__ = "https://www.freecadweb.org" import sys, os, FreeCAD, FreeCADGui, WorkingPlane, math, re, Draft, Draft_rc, DraftVecUtils from FreeCAD import Vector from PySide import QtCore,QtGui -from DraftGui import todo, translate, utf8_decode +from draftutils.todo import todo +from draftutils.translate import translate import draftguitools.gui_snapper as gui_snapper import DraftGui import draftguitools.gui_trackers as trackers @@ -2245,7 +2246,7 @@ class ShapeString(Creator): pass self.task = DraftGui.ShapeStringTaskPanel() self.task.sourceCmd = self - DraftGui.todo.delay(FreeCADGui.Control.showDialog,self.task) + todo.delay(FreeCADGui.Control.showDialog,self.task) else: self.dialog = None self.text = '' @@ -4130,9 +4131,9 @@ class Scale(Modifier): self.view.removeEventCallback("SoEvent",self.call) self.task = DraftGui.ScaleTaskPanel() self.task.sourceCmd = self - DraftGui.todo.delay(FreeCADGui.Control.showDialog,self.task) - DraftGui.todo.delay(self.task.xValue.selectAll,None) - DraftGui.todo.delay(self.task.xValue.setFocus,None) + todo.delay(FreeCADGui.Control.showDialog,self.task) + todo.delay(self.task.xValue.selectAll,None) + todo.delay(self.task.xValue.setFocus,None) for ghost in self.ghosts: ghost.on() elif len(self.node) == 2: From ffb802d72cd369d4bd9fc204cff1c85ceb6e6ceb Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Mon, 10 Feb 2020 22:50:36 -0600 Subject: [PATCH 013/117] Arch: import from draftutils instead of DraftTools These functions were moved from DraftTools and DraftGui to individual modules precisely to make them easy to import. Otherwise we have to import the entire DraftTools and DraftGui modules which creates dependency problems when we want to use some functions without the graphical interface. --- src/Mod/Arch/ArchCommands.py | 3 ++- src/Mod/Arch/ArchComponent.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Mod/Arch/ArchCommands.py b/src/Mod/Arch/ArchCommands.py index 6ae41c050a..67a778db1e 100644 --- a/src/Mod/Arch/ArchCommands.py +++ b/src/Mod/Arch/ArchCommands.py @@ -27,7 +27,8 @@ from FreeCAD import Vector if FreeCAD.GuiUp: import FreeCADGui from PySide import QtGui,QtCore - from DraftTools import translate, utf8_decode + from draftutils.translate import translate + from draftutils.utils import utf8_decode else: # \cond def translate(ctxt,txt): diff --git a/src/Mod/Arch/ArchComponent.py b/src/Mod/Arch/ArchComponent.py index f798171dbb..625a74fa81 100644 --- a/src/Mod/Arch/ArchComponent.py +++ b/src/Mod/Arch/ArchComponent.py @@ -28,7 +28,7 @@ from FreeCAD import Vector if FreeCAD.GuiUp: import FreeCADGui from PySide import QtGui,QtCore - from DraftTools import translate + from draftutils.translate import translate from PySide.QtCore import QT_TRANSLATE_NOOP else: # \cond From c32c9d5658a400b347b061e9bf7f49f4875a8ee4 Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 23 Mar 2020 13:06:38 +0100 Subject: [PATCH 014/117] Part: [skip ci] commit and open transaction when clicking Apply in Attachment dialog (Python version) --- src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py b/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py index e11864b86e..ddb8a77f37 100644 --- a/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py +++ b/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py @@ -301,6 +301,9 @@ class AttachmentEditorTaskPanel(FrozenClass): if button == QtGui.QDialogButtonBox.Apply: if self.obj_is_attachable: self.writeParameters() + if self.create_transaction: + self.obj.Document.commitTransaction() + self.obj.Document.openTransaction(_translate('AttachmentEditor',"Edit attachment of {feat}",None).format(feat= self.obj.Name)) self.updatePreview() if self.callback_Apply: self.callback_Apply() From 3e00de25f4a0f7b4ad64a25a35446e08da6c9b78 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Sun, 22 Mar 2020 18:39:48 -0400 Subject: [PATCH 015/117] [TD]fix Leader attach point secondary views --- src/Mod/TechDraw/Gui/TaskLeaderLine.cpp | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/Mod/TechDraw/Gui/TaskLeaderLine.cpp b/src/Mod/TechDraw/Gui/TaskLeaderLine.cpp index 9aea6e3102..db770e1806 100644 --- a/src/Mod/TechDraw/Gui/TaskLeaderLine.cpp +++ b/src/Mod/TechDraw/Gui/TaskLeaderLine.cpp @@ -609,26 +609,14 @@ void TaskLeaderLine::startTracker(void) void TaskLeaderLine::onTrackerFinished(std::vector pts, QGIView* qgParent) { + //in this case, we already know who the parent is. We don't need QGTracker to tell us. + (void) qgParent; // Base::Console().Message("TTL::onTrackerFinished() - parent: %X\n",qgParent); if (pts.empty()) { Base::Console().Error("TaskLeaderLine - no points available\n"); return; } - if (qgParent == nullptr) { - //do something; - m_qgParent = findParentQGIV(); - } else { - QGIView* qgiv = dynamic_cast(qgParent); - if (qgiv != nullptr) { - m_qgParent = qgiv; - } else { - Base::Console().Message("TTL::onTrackerFinished - can't find parent graphic!\n"); - //blow up!? - throw Base::RuntimeError("TaskLeaderLine - can not find parent graphic"); - } - } - if (m_qgParent != nullptr) { double scale = m_qgParent->getScale(); QPointF mapped = m_qgParent->mapFromScene(pts.front()) / scale; From 779ebb7715fafb85d373af974c7523ab029e6c83 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Sun, 22 Mar 2020 19:37:48 -0400 Subject: [PATCH 016/117] [TD]fix Leader end type selection --- src/Mod/TechDraw/Gui/QGILeaderLine.cpp | 8 ++++---- src/Mod/TechDraw/Gui/TaskLeaderLine.cpp | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Mod/TechDraw/Gui/QGILeaderLine.cpp b/src/Mod/TechDraw/Gui/QGILeaderLine.cpp index f2925274fa..b01d8c375f 100644 --- a/src/Mod/TechDraw/Gui/QGILeaderLine.cpp +++ b/src/Mod/TechDraw/Gui/QGILeaderLine.cpp @@ -424,11 +424,11 @@ QPainterPath QGILeaderLine::makeLeaderPath(std::vector qPoints) double endAdjLength(0.0); if (qPoints.size() > 1) { //make path adjustment to hide leaderline ends behind arrowheads - if (featLeader->StartSymbol.getValue() > ArrowType::NONE) { + if (featLeader->StartSymbol.getValue() != ArrowType::NONE) { startAdjLength = QGIArrow::getOverlapAdjust(featLeader->StartSymbol.getValue(), QGIArrow::getPrefArrowSize()); } - if (featLeader->EndSymbol.getValue() > ArrowType::NONE) { + if (featLeader->EndSymbol.getValue() != ArrowType::NONE) { endAdjLength = QGIArrow::getOverlapAdjust(featLeader->EndSymbol.getValue(), QGIArrow::getPrefArrowSize()); } @@ -499,7 +499,7 @@ void QGILeaderLine::setArrows(std::vector pathPoints) QPointF lastOffset = (pathPoints.back() - pathPoints.front()); - if (featLeader->StartSymbol.getValue() > ArrowType::NONE) { + if (featLeader->StartSymbol.getValue() != ArrowType::NONE) { m_arrow1->setStyle(featLeader->StartSymbol.getValue()); m_arrow1->setWidth(getLineWidth()); // TODO: variable size arrow heads @@ -521,7 +521,7 @@ void QGILeaderLine::setArrows(std::vector pathPoints) m_arrow1->hide(); } - if (featLeader->EndSymbol.getValue() > ArrowType::NONE) { + if (featLeader->EndSymbol.getValue() != ArrowType::NONE) { m_arrow2->setStyle(featLeader->EndSymbol.getValue()); m_arrow2->setWidth(getLineWidth()); m_arrow2->setDirMode(true); diff --git a/src/Mod/TechDraw/Gui/TaskLeaderLine.cpp b/src/Mod/TechDraw/Gui/TaskLeaderLine.cpp index db770e1806..43808bc1e0 100644 --- a/src/Mod/TechDraw/Gui/TaskLeaderLine.cpp +++ b/src/Mod/TechDraw/Gui/TaskLeaderLine.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include @@ -267,7 +268,7 @@ void TaskLeaderLine::setUiPrimary() ui->cboxStartSym->setCurrentIndex(aStyle); DrawGuiUtil::loadArrowBox(ui->cboxEndSym); - ui->cboxEndSym->setCurrentIndex(0); + ui->cboxEndSym->setCurrentIndex(TechDraw::ArrowType::NONE); ui->dsbWeight->setUnit(Base::Unit::Length); ui->dsbWeight->setMinimum(0); From bf4e6b6316f09d317b2cfd1644d603c9a8390424 Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 22 Mar 2020 15:22:40 +0100 Subject: [PATCH 017/117] [TD] refurbish LineDecorDlg - also set a better step size - also apply the changes in the DLG immediately see: https://forum.freecadweb.org/viewtopic.php?f=35&t=44362&p=379377#p379377 --- src/Mod/TechDraw/Gui/TaskLineDecor.cpp | 21 +- src/Mod/TechDraw/Gui/TaskLineDecor.ui | 376 +++++++++++-------------- 2 files changed, 182 insertions(+), 215 deletions(-) diff --git a/src/Mod/TechDraw/Gui/TaskLineDecor.cpp b/src/Mod/TechDraw/Gui/TaskLineDecor.cpp index 63a748ea3b..4244c251a2 100644 --- a/src/Mod/TechDraw/Gui/TaskLineDecor.cpp +++ b/src/Mod/TechDraw/Gui/TaskLineDecor.cpp @@ -69,10 +69,10 @@ TaskLineDecor::TaskLineDecor(TechDraw::DrawViewPart* partFeat, getDefaults(); ui->setupUi(this); - connect(ui->cb_Style, SIGNAL(currentIndexChanged( int )), this, SLOT(onStyleChanged(void))); - connect(ui->cc_Color, SIGNAL(changed( )), this, SLOT(onColorChanged(void))); - connect(ui->dsb_Weight, SIGNAL(valueChanged( double )), this, SLOT(onWeightChanged( void ))); - connect(ui->cb_Visible, SIGNAL(currentIndexChanged( int )), this, SLOT(onVisibleChanged( void ))); + connect(ui->cb_Style, SIGNAL(currentIndexChanged(int)), this, SLOT(onStyleChanged(void))); + connect(ui->cc_Color, SIGNAL(changed()), this, SLOT(onColorChanged(void))); + connect(ui->dsb_Weight, SIGNAL(valueChanged(double)), this, SLOT(onWeightChanged(void))); + connect(ui->cb_Visible, SIGNAL(currentIndexChanged(int)), this, SLOT(onVisibleChanged(void))); initUi(); } @@ -101,6 +101,7 @@ void TaskLineDecor::initUi() ui->cb_Style->setCurrentIndex(m_style - 1); //combobox does not have 0:NoLine choice ui->cc_Color->setColor(m_color.asValue()); ui->dsb_Weight->setValue(m_weight); + ui->dsb_Weight->setSingleStep(0.1); ui->cb_Visible->setCurrentIndex(m_visible); } @@ -157,25 +158,29 @@ void TaskLineDecor::getDefaults(void) void TaskLineDecor::onStyleChanged(void) { m_style = ui->cb_Style->currentIndex() + 1; - //livePreview(); + applyDecorations(); + m_partFeat->requestPaint(); } void TaskLineDecor::onColorChanged(void) { m_color.setValue(ui->cc_Color->color()); - //livePreview(); + applyDecorations(); + m_partFeat->requestPaint(); } void TaskLineDecor::onWeightChanged(void) { m_weight = ui->dsb_Weight->value(); - //livePreview(); + applyDecorations(); + m_partFeat->requestPaint(); } void TaskLineDecor::onVisibleChanged(void) { m_visible = ui->cb_Visible->currentIndex(); - //livePreview(); + applyDecorations(); + m_partFeat->requestPaint(); } void TaskLineDecor::applyDecorations(void) diff --git a/src/Mod/TechDraw/Gui/TaskLineDecor.ui b/src/Mod/TechDraw/Gui/TaskLineDecor.ui index 46f111cf42..9092dc2aee 100644 --- a/src/Mod/TechDraw/Gui/TaskLineDecor.ui +++ b/src/Mod/TechDraw/Gui/TaskLineDecor.ui @@ -6,12 +6,12 @@ 0 0 - 395 - 294 + 350 + 200 - + 0 0 @@ -25,215 +25,177 @@ Line Decoration - + - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - - false - - - - - - - Lines - - - - - - - View - - - - - - - false - - - false - - - Qt::NoFocus - - - false - - - - - - - - - Qt::Vertical + + + + + View + + + + + + + false + + + false + + + Qt::NoFocus + + + false + + + + + + + Lines + + + + + + + false + + + + + + + Qt::Horizontal + + + + + + + Style + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 40 + 20 + + + + + + + + 0 + + + + Continuous - - - 20 - 40 - + + + + Dash - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Color - - - - - - - Style - - - - - - - Weight - - - - - - - - 0 - 0 - 0 - - - - - - - - Thickness of pattern lines. - - - 0.500000000000000 - - - - - - - Visible - - - - - - - 1 - - - 2 - - - 2 - - - 2 - - - - False - - - - - True - - - - - - - - 0 - - - - Continuous - - - - - Dash - - - - - Dot - - - - - DashDot - - - - - DashDotDot - - - - - - - - - - Qt::Vertical + + + + Dot - - - 20 - 40 - + + + + DashDot - - - - + + + + DashDotDot + + + + + + + + Color + + + + + + + + 0 + 0 + 0 + + + + + + + + Weight + + + + + + + Thickness of pattern lines. + + + 0.500000000000000 + + + + + + + Visible + + + + + + + 1 + + + 2 + + + 2 + + + 2 + + + + False + + + + + True + + + + + From 28e40f8dc55d9f928950994bfb52f3e2e6d025f3 Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 22 Mar 2020 23:11:10 +0100 Subject: [PATCH 018/117] [TD] Centerline dialog refurbish - group the radio buttons - make edit dialog apply immediately --- src/Mod/TechDraw/Gui/TaskCenterLine.cpp | 76 +++++++++++++ src/Mod/TechDraw/Gui/TaskCenterLine.h | 12 ++- src/Mod/TechDraw/Gui/TaskCenterLine.ui | 135 +++++++++++++++--------- 3 files changed, 171 insertions(+), 52 deletions(-) diff --git a/src/Mod/TechDraw/Gui/TaskCenterLine.cpp b/src/Mod/TechDraw/Gui/TaskCenterLine.cpp index 903c6952ff..5c0803899a 100644 --- a/src/Mod/TechDraw/Gui/TaskCenterLine.cpp +++ b/src/Mod/TechDraw/Gui/TaskCenterLine.cpp @@ -29,6 +29,7 @@ #endif // #ifndef _PreComp_ +#include #include #include @@ -133,6 +134,7 @@ TaskCenterLine::TaskCenterLine(TechDraw::DrawViewPart* partFeat, Base::Console().Error("TaskCenterLine - unknown geometry type: %s. Can not proceed.\n", geomType.c_str()); return; } + setUiPrimary(); } @@ -181,6 +183,7 @@ void TaskCenterLine::setUiPrimary() ui->qsbRotate->setValue(qAngle); int precision = Base::UnitsApi::getDecimals(); ui->qsbRotate->setDecimals(precision); + if (m_type == 0) // if face, then aligned is not possible ui->rbAligned->setEnabled(false); else @@ -202,8 +205,11 @@ void TaskCenterLine::setUiEdit() ui->lstSubList->addItem(listItem); } ui->cpLineColor->setColor(m_cl->m_format.m_color.asValue()); + connect(ui->cpLineColor, SIGNAL(changed()), this, SLOT(onColorChanged())); ui->dsbWeight->setValue(m_cl->m_format.m_weight); + connect(ui->dsbWeight, SIGNAL(valueChanged(double)), this, SLOT(onWeightChanged())); ui->cboxStyle->setCurrentIndex(m_cl->m_format.m_style - 1); + connect(ui->cboxStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(onStyleChanged())); ui->rbVertical->setChecked(false); ui->rbHorizontal->setChecked(false); @@ -223,10 +229,13 @@ void TaskCenterLine::setUiEdit() qVal.setUnit(Base::Unit::Length); qVal.setValue(m_cl->m_vShift); ui->qsbVertShift->setValue(qVal); + connect(ui->qsbVertShift, SIGNAL(valueChanged(double)), this, SLOT(onShiftVertChanged())); qVal.setValue(m_cl->m_hShift); ui->qsbHorizShift->setValue(qVal); + connect(ui->qsbHorizShift, SIGNAL(valueChanged(double)), this, SLOT(onShiftHorizChanged())); qVal.setValue(m_cl->m_extendBy); ui->qsbExtend->setValue(qVal); + connect(ui->qsbExtend, SIGNAL(valueChanged(double)), this, SLOT(onExtendChanged())); Base::Quantity qAngle; qAngle.setUnit(Base::Unit::Angle); @@ -234,6 +243,7 @@ void TaskCenterLine::setUiEdit() int precision = Base::UnitsApi::getDecimals(); ui->qsbRotate->setDecimals(precision); ui->qsbRotate->setValue(m_cl->m_rotate); + connect(ui->qsbRotate, SIGNAL(valueChanged(double)), this, SLOT(onRotationChanged())); if (m_cl->m_flip2Line) ui->cbFlip->setChecked(true); @@ -244,8 +254,74 @@ void TaskCenterLine::setUiEdit() ui->cbFlip->setEnabled(true); else ui->cbFlip->setEnabled(false); + connect(ui->cbFlip, SIGNAL(toggled(bool)), this, SLOT(onFlipChanged())); + + // connect the Orientation radio group box + connect(ui->bgOrientation, SIGNAL(buttonClicked(int)), this, SLOT(onOrientationChanged())); } +void TaskCenterLine::onOrientationChanged() +{ + if (ui->rbVertical->isChecked()) + m_cl->m_mode = CenterLine::CLMODE::VERTICAL; + else if (ui->rbHorizontal->isChecked()) + m_cl->m_mode = CenterLine::CLMODE::HORIZONTAL; + else if (ui->rbAligned->isChecked()) + m_cl->m_mode = CenterLine::CLMODE::ALIGNED; + m_partFeat->recomputeFeature(); +} + +void TaskCenterLine::onShiftHorizChanged() +{ + m_cl->m_hShift = ui->qsbHorizShift->rawValue(); + m_partFeat->recomputeFeature(); +} + +void TaskCenterLine::onShiftVertChanged() +{ + m_cl->m_vShift = ui->qsbVertShift->rawValue(); + m_partFeat->recomputeFeature(); +} + +void TaskCenterLine::onRotationChanged() +{ + m_cl->m_rotate = ui->qsbRotate->rawValue(); + m_partFeat->recomputeFeature(); +} + +void TaskCenterLine::onExtendChanged() +{ + m_cl->m_extendBy = ui->qsbExtend->rawValue(); + m_partFeat->recomputeFeature(); +} + +void TaskCenterLine::onColorChanged() +{ + App::Color ac; + ac.setValue(ui->cpLineColor->color()); + m_cl->m_format.m_color.setValue(ui->cpLineColor->color()); + m_partFeat->recomputeFeature(); +} + +void TaskCenterLine::onWeightChanged() +{ + m_cl->m_format.m_weight = ui->dsbWeight->value(); + m_partFeat->recomputeFeature(); +} + +void TaskCenterLine::onStyleChanged() +{ + m_cl->m_format.m_style = ui->cboxStyle->currentIndex() + 1; + m_partFeat->recomputeFeature(); +} + +void TaskCenterLine::onFlipChanged() +{ + m_cl->m_flip2Line = ui->cbFlip->isChecked(); + m_partFeat->recomputeFeature(); +} + + //****************************************************************************** void TaskCenterLine::createCenterLine(void) { diff --git a/src/Mod/TechDraw/Gui/TaskCenterLine.h b/src/Mod/TechDraw/Gui/TaskCenterLine.h index 6d1c3faee2..e63973e944 100644 --- a/src/Mod/TechDraw/Gui/TaskCenterLine.h +++ b/src/Mod/TechDraw/Gui/TaskCenterLine.h @@ -94,7 +94,6 @@ public: void enableTaskButtons(bool b); void setFlipped(bool b); - protected Q_SLOTS: protected: @@ -117,6 +116,17 @@ protected: Qt::PenStyle getCenterStyle(); double getExtendBy(); +private Q_SLOTS: + void onOrientationChanged(); + void onShiftHorizChanged(); + void onShiftVertChanged(); + void onRotationChanged(); + void onExtendChanged(); + void onColorChanged(); + void onWeightChanged(); + void onStyleChanged(); + void onFlipChanged(); + private: Ui_TaskCenterLine * ui; diff --git a/src/Mod/TechDraw/Gui/TaskCenterLine.ui b/src/Mod/TechDraw/Gui/TaskCenterLine.ui index 3440ebdbeb..4222c054b2 100644 --- a/src/Mod/TechDraw/Gui/TaskCenterLine.ui +++ b/src/Mod/TechDraw/Gui/TaskCenterLine.ui @@ -2,16 +2,19 @@ TechDrawGui::TaskCenterLine + + true + 0 0 380 - 393 + 374 - + 0 0 @@ -32,6 +35,13 @@ + + + + Base View + + + @@ -48,13 +58,6 @@ - - - - Base View - - - @@ -67,10 +70,16 @@ false + + + 0 + 0 + + 16777215 - 100 + 50 @@ -78,50 +87,67 @@ - - - - - Top to Bottom line - - - Vertical - - - true - - - - - - - true - - - Left to Right line - - - Horizontal - - - - - - - true - - - centerline between + + + + + + Orientation + + + + + + Top to Bottom line + + + Vertical + + + true + + + bgOrientation + + + + + + + true + + + Left to Right line + + + Horizontal + + + bgOrientation + + + + + + + true + + + centerline between - lines: in equal distance to the lines and with half of the angle the lines have to each other - points: in equal distance to the points - - - Aligned - - - - + + + Aligned + + + bgOrientation + + + + + @@ -359,4 +385,11 @@ see the FreeCAD Wiki '2LineCenterLine' for a description + + + + true + + + From bd0f5ca5c5ca06c588c6ec39a349a08229c9c754 Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 23 Mar 2020 17:14:42 +0100 Subject: [PATCH 019/117] Part: [skip ci] explicitly open transaction when opening the Attachment dialog --- src/Mod/Part/Gui/TaskAttacher.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Mod/Part/Gui/TaskAttacher.cpp b/src/Mod/Part/Gui/TaskAttacher.cpp index bafc9fc99e..3e06235eca 100644 --- a/src/Mod/Part/Gui/TaskAttacher.cpp +++ b/src/Mod/Part/Gui/TaskAttacher.cpp @@ -1041,7 +1041,8 @@ TaskDlgAttacher::~TaskDlgAttacher() void TaskDlgAttacher::open() { - + Gui::Document* document = Gui::Application::Instance->getDocument(ViewProvider->getObject()->getDocument()); + document->openCommand("Edit attachment"); } void TaskDlgAttacher::clicked(int) @@ -1061,20 +1062,21 @@ bool TaskDlgAttacher::accept() auto obj = ViewProvider->getObject(); //DeepSOIC: changed this to heavily rely on dialog constantly updating feature properties - if (pcAttach->AttachmentOffset.isTouched()){ + //if (pcAttach->AttachmentOffset.isTouched()){ Base::Placement plm = pcAttach->AttachmentOffset.getValue(); double yaw, pitch, roll; plm.getRotation().getYawPitchRoll(yaw,pitch,roll); Gui::cmdAppObjectArgs(obj, "AttachmentOffset = App.Placement(App.Vector(%.10f, %.10f, %.10f), App.Rotation(%.10f, %.10f, %.10f))", plm.getPosition().x, plm.getPosition().y, plm.getPosition().z, yaw, pitch, roll); - } + //} Gui::cmdAppObjectArgs(obj, "MapReversed = %s", pcAttach->MapReversed.getValue() ? "True" : "False"); Gui::cmdAppObjectArgs(obj, "Support = %s", pcAttach->Support.getPyReprString().c_str()); - Gui::cmdAppObjectArgs(obj, "MapMode = '%s'", AttachEngine::getModeName(eMapMode(pcAttach->MapMode.getValue())).c_str()); + Gui::cmdAppObjectArgs(obj, "MapPathParameter = %f", pcAttach->MapPathParameter.getValue()); + Gui::cmdAppObjectArgs(obj, "MapMode = '%s'", AttachEngine::getModeName(eMapMode(pcAttach->MapMode.getValue())).c_str()); Gui::cmdAppObject(obj, "recompute()"); Gui::cmdGuiDocument(obj, "resetEdit()"); From bdc23aabcf4bd39cadf08e7685829029a9da830a Mon Sep 17 00:00:00 2001 From: DeepSOIC Date: Tue, 24 Mar 2020 04:03:20 +0300 Subject: [PATCH 020/117] Sketcher: Fix #3658 Levenberg-Marquardt solver precision issues --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index d915d6f186..76ecb4fef5 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -1740,7 +1740,7 @@ int System::solve_LM(SubSystem* subsys, bool isRedundantsolving) // check error double err=e.squaredNorm(); - if (err <= eps) { // error is small, Success + if (err <= eps*eps) { // error is small, Success stop = 1; break; } From 0a1312e9f59f4c2157d94398ba31caa297b1fa31 Mon Sep 17 00:00:00 2001 From: donovaly Date: Tue, 24 Mar 2020 03:12:10 +0100 Subject: [PATCH 021/117] [PD] fix logic of TaskPrimitiveParameters dialog - only allow geometrically possible values for the different parameters - this way also fix a crash - fix a typo see https://forum.freecadweb.org/viewtopic.php?f=3&t=44467 --- .../Gui/TaskPrimitiveParameters.cpp | 166 ++++++++++++------ .../PartDesign/Gui/TaskPrimitiveParameters.h | 10 +- 2 files changed, 113 insertions(+), 63 deletions(-) diff --git a/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.cpp b/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.cpp index da898b89f2..65a08a489a 100644 --- a/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.cpp @@ -61,54 +61,6 @@ TaskBoxPrimitives::TaskBoxPrimitives(ViewProviderPrimitive* vp, QWidget* parent) proxy = new QWidget(this); ui->setupUi(proxy); - // box - ui->boxLength->setMaximum(INT_MAX); - ui->boxWidth->setMaximum(INT_MAX); - ui->boxHeight->setMaximum(INT_MAX); - - // cylinder - ui->cylinderRadius->setMaximum(INT_MAX); - ui->cylinderHeight->setMaximum(INT_MAX); - - // cone - ui->coneRadius1->setMaximum(INT_MAX); - ui->coneRadius2->setMaximum(INT_MAX); - ui->coneHeight->setMaximum(INT_MAX); - - // sphere - ui->sphereRadius->setMaximum(INT_MAX); - - // ellipsoid - ui->ellipsoidRadius1->setMaximum(INT_MAX); - ui->ellipsoidRadius2->setMaximum(INT_MAX); - ui->ellipsoidRadius3->setMaximum(INT_MAX); - - // torus - ui->torusRadius1->setMaximum(INT_MAX); - ui->torusRadius2->setMaximum(INT_MAX); - - // wedge - ui->wedgeXmin->setMinimum(INT_MIN); - ui->wedgeXmin->setMaximum(INT_MAX); - ui->wedgeYmin->setMinimum(INT_MIN); - ui->wedgeYmin->setMaximum(INT_MAX); - ui->wedgeZmin->setMinimum(INT_MIN); - ui->wedgeZmin->setMaximum(INT_MAX); - ui->wedgeX2min->setMinimum(INT_MIN); - ui->wedgeX2min->setMaximum(INT_MAX); - ui->wedgeZ2min->setMinimum(INT_MIN); - ui->wedgeZ2min->setMaximum(INT_MAX); - ui->wedgeXmax->setMinimum(INT_MIN); - ui->wedgeXmax->setMaximum(INT_MAX); - ui->wedgeYmax->setMinimum(INT_MIN); - ui->wedgeYmax->setMaximum(INT_MAX); - ui->wedgeZmax->setMinimum(INT_MIN); - ui->wedgeZmax->setMaximum(INT_MAX); - ui->wedgeX2max->setMinimum(INT_MIN); - ui->wedgeX2max->setMaximum(INT_MAX); - ui->wedgeZ2max->setMinimum(INT_MIN); - ui->wedgeZ2max->setMaximum(INT_MAX); - this->groupLayout()->addWidget(proxy); int index = 0; @@ -122,6 +74,12 @@ TaskBoxPrimitives::TaskBoxPrimitives(ViewProviderPrimitive* vp, QWidget* parent) ui->boxHeight->bind(static_cast(vp->getObject())->Height); ui->boxWidth->setValue(static_cast(vp->getObject())->Width.getValue()); ui->boxWidth->bind(static_cast(vp->getObject())->Width); + ui->boxLength->setMinimum(0.0); + ui->boxLength->setMaximum(INT_MAX); + ui->boxWidth->setMinimum(0.0); + ui->boxWidth->setMaximum(INT_MAX); + ui->boxHeight->setMinimum(0.0); + ui->boxHeight->setMaximum(INT_MAX); break; case PartDesign::FeaturePrimitive::Cylinder: index = 2; @@ -131,6 +89,12 @@ TaskBoxPrimitives::TaskBoxPrimitives(ViewProviderPrimitive* vp, QWidget* parent) ui->cylinderHeight->bind(static_cast(vp->getObject())->Height); ui->cylinderRadius->setValue(static_cast(vp->getObject())->Radius.getValue()); ui->cylinderRadius->bind(static_cast(vp->getObject())->Radius); + ui->cylinderAngle->setMaximum(360.0); + ui->cylinderAngle->setMinimum(0.0); + ui->cylinderHeight->setMaximum(INT_MAX); + ui->cylinderHeight->setMinimum(0.0); + ui->cylinderRadius->setMaximum(INT_MAX); + ui->cylinderRadius->setMinimum(0.0); break; case PartDesign::FeaturePrimitive::Sphere: index = 4; @@ -142,6 +106,14 @@ TaskBoxPrimitives::TaskBoxPrimitives(ViewProviderPrimitive* vp, QWidget* parent) ui->sphereAngle3->bind(static_cast(vp->getObject())->Angle3); ui->sphereRadius->setValue(static_cast(vp->getObject())->Radius.getValue()); ui->sphereRadius->bind(static_cast(vp->getObject())->Radius); + ui->sphereAngle1->setMaximum(ui->sphereAngle2->rawValue()); // must geometrically be <= than sphereAngle2 + ui->sphereAngle1->setMinimum(-90.0); + ui->sphereAngle2->setMaximum(90); + ui->sphereAngle2->setMinimum(ui->sphereAngle1->rawValue()); + ui->sphereAngle3->setMaximum(360.0); + ui->sphereAngle3->setMinimum(0.0); + ui->sphereRadius->setMaximum(INT_MAX); + ui->sphereRadius->setMinimum(0.0); break; case PartDesign::FeaturePrimitive::Cone: index = 3; @@ -153,6 +125,14 @@ TaskBoxPrimitives::TaskBoxPrimitives(ViewProviderPrimitive* vp, QWidget* parent) ui->coneRadius1->bind(static_cast(vp->getObject())->Radius1); ui->coneRadius2->setValue(static_cast(vp->getObject())->Radius2.getValue()); ui->coneRadius2->bind(static_cast(vp->getObject())->Radius2); + ui->coneAngle->setMaximum(360.0); + ui->coneAngle->setMinimum(0.0); + ui->coneHeight->setMaximum(INT_MAX); + ui->coneHeight->setMinimum(0.0); + ui->coneRadius1->setMaximum(INT_MAX); + ui->coneRadius1->setMinimum(0.0); + ui->coneRadius2->setMaximum(INT_MAX); + ui->coneRadius2->setMinimum(0.0); break; case PartDesign::FeaturePrimitive::Ellipsoid: index = 5; @@ -168,6 +148,18 @@ TaskBoxPrimitives::TaskBoxPrimitives(ViewProviderPrimitive* vp, QWidget* parent) ui->ellipsoidRadius2->bind(static_cast(vp->getObject())->Radius2); ui->ellipsoidRadius3->setValue(static_cast(vp->getObject())->Radius3.getValue()); ui->ellipsoidRadius3->bind(static_cast(vp->getObject())->Radius3); + ui->ellipsoidAngle1->setMaximum(ui->ellipsoidAngle2->rawValue()); // must geometrically be <= than sphereAngle2 + ui->ellipsoidAngle1->setMinimum(-90.0); + ui->ellipsoidAngle2->setMaximum(90); + ui->ellipsoidAngle2->setMinimum(ui->ellipsoidAngle1->rawValue()); + ui->ellipsoidAngle3->setMaximum(360.0); + ui->ellipsoidAngle3->setMinimum(0.0); + ui->ellipsoidRadius1->setMinimum(0.0); + ui->ellipsoidRadius1->setMaximum(INT_MAX); + ui->ellipsoidRadius2->setMinimum(0.0); + ui->ellipsoidRadius2->setMaximum(INT_MAX); + ui->ellipsoidRadius3->setMinimum(0.0); + ui->ellipsoidRadius3->setMaximum(INT_MAX); break; case PartDesign::FeaturePrimitive::Torus: index = 6; @@ -181,6 +173,19 @@ TaskBoxPrimitives::TaskBoxPrimitives(ViewProviderPrimitive* vp, QWidget* parent) ui->torusRadius1->bind(static_cast(vp->getObject())->Radius1); ui->torusRadius2->setValue(static_cast(vp->getObject())->Radius2.getValue()); ui->torusRadius2->bind(static_cast(vp->getObject())->Radius2); + ui->torusAngle1->setMaximum(ui->torusAngle2->rawValue()); // must geometrically be <= than sphereAngle2 + ui->torusAngle1->setMinimum(-180.0); + ui->torusAngle2->setMaximum(180); + ui->torusAngle2->setMinimum(ui->torusAngle1->rawValue()); + ui->torusAngle3->setMaximum(360.0); + ui->torusAngle3->setMinimum(0.0); + // this is the outer radius that must not be smaller than the inner one + // otherwise the geometry is impossible and we can even get a crash: + // https://forum.freecadweb.org/viewtopic.php?f=3&t=44467 + ui->torusRadius1->setMaximum(INT_MAX); + ui->torusRadius1->setMinimum(ui->torusRadius2->rawValue()); + ui->torusRadius2->setMaximum(ui->torusRadius1->rawValue()); + ui->torusRadius2->setMinimum(0.0); break; case PartDesign::FeaturePrimitive::Prism: index = 7; @@ -189,6 +194,10 @@ TaskBoxPrimitives::TaskBoxPrimitives(ViewProviderPrimitive* vp, QWidget* parent) ui->prismCircumradius->bind(static_cast(vp->getObject())->Circumradius); ui->prismHeight->setValue(static_cast(vp->getObject())->Height.getValue()); ui->prismHeight->bind(static_cast(vp->getObject())->Height); + ui->prismCircumradius->setMaximum(INT_MAX); + ui->prismCircumradius->setMinimum(0.0); + ui->prismHeight->setMaximum(INT_MAX); + ui->prismHeight->setMinimum(0.0); break; case PartDesign::FeaturePrimitive::Wedge: index = 8; @@ -212,6 +221,26 @@ TaskBoxPrimitives::TaskBoxPrimitives(ViewProviderPrimitive* vp, QWidget* parent) ui->wedgeZ2max->bind(static_cast(vp->getObject())->Z2max); ui->wedgeZ2min->setValue(static_cast(vp->getObject())->Z2min.getValue()); ui->wedgeZ2min->bind(static_cast(vp->getObject())->Z2min); + ui->wedgeXmin->setMinimum(INT_MIN); + ui->wedgeXmin->setMaximum(ui->wedgeXmax->rawValue()); // must be <= than wedgeXmax + ui->wedgeYmin->setMinimum(INT_MIN); + ui->wedgeYmin->setMaximum(ui->wedgeYmax->rawValue()); // must be <= than wedgeYmax + ui->wedgeZmin->setMinimum(INT_MIN); + ui->wedgeZmin->setMaximum(ui->wedgeZmax->rawValue()); // must be <= than wedgeZmax + ui->wedgeX2min->setMinimum(INT_MIN); + ui->wedgeX2min->setMaximum(ui->wedgeX2max->rawValue()); // must be <= than wedgeXmax + ui->wedgeZ2min->setMinimum(INT_MIN); + ui->wedgeZ2min->setMaximum(ui->wedgeZ2max->rawValue()); // must be <= than wedgeXmax + ui->wedgeXmax->setMinimum(ui->wedgeXmin->rawValue()); + ui->wedgeXmax->setMaximum(INT_MAX); + ui->wedgeYmax->setMinimum(ui->wedgeYmin->rawValue()); + ui->wedgeYmax->setMaximum(INT_MAX); + ui->wedgeZmax->setMinimum(ui->wedgeZmin->rawValue()); + ui->wedgeZmax->setMaximum(INT_MAX); + ui->wedgeX2max->setMinimum(ui->wedgeX2min->rawValue()); + ui->wedgeX2max->setMaximum(INT_MAX); + ui->wedgeZ2max->setMinimum(ui->wedgeZ2min->rawValue()); + ui->wedgeZ2max->setMaximum(INT_MAX); break; } @@ -283,15 +312,15 @@ TaskBoxPrimitives::TaskBoxPrimitives(ViewProviderPrimitive* vp, QWidget* parent) // wedge connect(ui->wedgeXmax, SIGNAL(valueChanged(double)), this, SLOT(onWedgeXmaxChanged(double))); - connect(ui->wedgeXmin, SIGNAL(valueChanged(double)), this, SLOT(onWedgeXinChanged(double))); + connect(ui->wedgeXmin, SIGNAL(valueChanged(double)), this, SLOT(onWedgeXminChanged(double))); connect(ui->wedgeYmax, SIGNAL(valueChanged(double)), this, SLOT(onWedgeYmaxChanged(double))); - connect(ui->wedgeYmin, SIGNAL(valueChanged(double)), this, SLOT(onWedgeYinChanged(double))); + connect(ui->wedgeYmin, SIGNAL(valueChanged(double)), this, SLOT(onWedgeYminChanged(double))); connect(ui->wedgeZmax, SIGNAL(valueChanged(double)), this, SLOT(onWedgeZmaxChanged(double))); - connect(ui->wedgeZmin, SIGNAL(valueChanged(double)), this, SLOT(onWedgeZinChanged(double))); + connect(ui->wedgeZmin, SIGNAL(valueChanged(double)), this, SLOT(onWedgeZminChanged(double))); connect(ui->wedgeX2max, SIGNAL(valueChanged(double)), this, SLOT(onWedgeX2maxChanged(double))); - connect(ui->wedgeX2min, SIGNAL(valueChanged(double)), this, SLOT(onWedgeX2inChanged(double))); + connect(ui->wedgeX2min, SIGNAL(valueChanged(double)), this, SLOT(onWedgeX2minChanged(double))); connect(ui->wedgeZ2max, SIGNAL(valueChanged(double)), this, SLOT(onWedgeZ2maxChanged(double))); - connect(ui->wedgeZ2min, SIGNAL(valueChanged(double)), this, SLOT(onWedgeZ2inChanged(double))); + connect(ui->wedgeZ2min, SIGNAL(valueChanged(double)), this, SLOT(onWedgeZ2minChanged(double))); } /* @@ -357,12 +386,14 @@ void TaskBoxPrimitives::onCylinderRadiusChanged(double v) { void TaskBoxPrimitives::onSphereAngle1Changed(double v) { PartDesign::Sphere* sph = static_cast(vp->getObject()); + ui->sphereAngle2->setMinimum(v); // Angle1 must geometrically be <= than Angle2 sph->Angle1.setValue(v); vp->getObject()->getDocument()->recomputeFeature(vp->getObject()); } void TaskBoxPrimitives::onSphereAngle2Changed(double v) { PartDesign::Sphere* sph = static_cast(vp->getObject()); + ui->sphereAngle1->setMaximum(v); // Angle1 must geometrically be <= than Angle2 sph->Angle2.setValue(v); vp->getObject()->getDocument()->recomputeFeature(vp->getObject()); } @@ -406,12 +437,14 @@ void TaskBoxPrimitives::onConeRadius2Changed(double v) { void TaskBoxPrimitives::onEllipsoidAngle1Changed(double v) { PartDesign::Ellipsoid* sph = static_cast(vp->getObject()); + ui->ellipsoidAngle2->setMinimum(v); // Angle1 must geometrically be <= than Angle2 sph->Angle1.setValue(v); vp->getObject()->getDocument()->recomputeFeature(vp->getObject()); } void TaskBoxPrimitives::onEllipsoidAngle2Changed(double v) { PartDesign::Ellipsoid* sph = static_cast(vp->getObject()); + ui->ellipsoidAngle1->setMaximum(v); // Angle1 must geometrically be <= than Angle22 sph->Angle2.setValue(v); vp->getObject()->getDocument()->recomputeFeature(vp->getObject()); } @@ -442,12 +475,14 @@ void TaskBoxPrimitives::onEllipsoidRadius3Changed(double v) { void TaskBoxPrimitives::onTorusAngle1Changed(double v) { PartDesign::Torus* sph = static_cast(vp->getObject()); + ui->torusAngle2->setMinimum(v); // Angle1 must geometrically be <= than Angle2 sph->Angle1.setValue(v); vp->getObject()->getDocument()->recomputeFeature(vp->getObject()); } void TaskBoxPrimitives::onTorusAngle2Changed(double v) { PartDesign::Torus* sph = static_cast(vp->getObject()); + ui->torusAngle1->setMaximum(v); // Angle1 must geometrically be <= than Angle2 sph->Angle2.setValue(v); vp->getObject()->getDocument()->recomputeFeature(vp->getObject()); } @@ -460,12 +495,17 @@ void TaskBoxPrimitives::onTorusAngle3Changed(double v) { void TaskBoxPrimitives::onTorusRadius1Changed(double v) { PartDesign::Torus* sph = static_cast(vp->getObject()); + // this is the outer radius that must not be smaller than the inner one + // otherwise the geometry is impossible and we can even get a crash: + // https://forum.freecadweb.org/viewtopic.php?f=3&t=44467 + ui->torusRadius2->setMaximum(v); sph->Radius1.setValue(v); vp->getObject()->getDocument()->recomputeFeature(vp->getObject()); } void TaskBoxPrimitives::onTorusRadius2Changed(double v) { PartDesign::Torus* sph = static_cast(vp->getObject()); + ui->torusRadius1->setMinimum(v); sph->Radius2.setValue(v); vp->getObject()->getDocument()->recomputeFeature(vp->getObject()); } @@ -489,62 +529,72 @@ void TaskBoxPrimitives::onPrismPolygonChanged(int v) { } -void TaskBoxPrimitives::onWedgeX2inChanged(double v) { +void TaskBoxPrimitives::onWedgeX2minChanged(double v) { PartDesign::Wedge* sph = static_cast(vp->getObject()); + ui->wedgeX2max->setMinimum(v); // wedgeX2min must be <= than wedgeX2max sph->X2min.setValue(v); vp->getObject()->getDocument()->recomputeFeature(vp->getObject()); } void TaskBoxPrimitives::onWedgeX2maxChanged(double v) { PartDesign::Wedge* sph = static_cast(vp->getObject()); + ui->wedgeX2min->setMaximum(v); // wedgeX2min must be <= than wedgeX2max sph->X2max.setValue(v); vp->getObject()->getDocument()->recomputeFeature(vp->getObject()); } -void TaskBoxPrimitives::onWedgeXinChanged(double v) { +void TaskBoxPrimitives::onWedgeXminChanged(double v) { PartDesign::Wedge* sph = static_cast(vp->getObject()); + ui->wedgeXmax->setMinimum(v); sph->Xmin.setValue(v); vp->getObject()->getDocument()->recomputeFeature(vp->getObject()); } void TaskBoxPrimitives::onWedgeXmaxChanged(double v) { PartDesign::Wedge* sph = static_cast(vp->getObject()); + ui->wedgeXmin->setMaximum(v); // must be <= than wedgeXmax sph->Xmax.setValue(v); vp->getObject()->getDocument()->recomputeFeature(vp->getObject()); } -void TaskBoxPrimitives::onWedgeYinChanged(double v) { +void TaskBoxPrimitives::onWedgeYminChanged(double v) { PartDesign::Wedge* sph = static_cast(vp->getObject()); + ui->wedgeYmax->setMinimum(v); sph->Ymin.setValue(v); vp->getObject()->getDocument()->recomputeFeature(vp->getObject()); } void TaskBoxPrimitives::onWedgeYmaxChanged(double v) { PartDesign::Wedge* sph = static_cast(vp->getObject()); + ui->wedgeYmin->setMaximum(v); sph->Ymax.setValue(v); vp->getObject()->getDocument()->recomputeFeature(vp->getObject()); } -void TaskBoxPrimitives::onWedgeZ2inChanged(double v) { +void TaskBoxPrimitives::onWedgeZ2minChanged(double v) { PartDesign::Wedge* sph = static_cast(vp->getObject()); + ui->wedgeZ2max->setMinimum(v); sph->Z2min.setValue(v); vp->getObject()->getDocument()->recomputeFeature(vp->getObject()); } void TaskBoxPrimitives::onWedgeZ2maxChanged(double v) { PartDesign::Wedge* sph = static_cast(vp->getObject()); + ui->wedgeZ2min->setMaximum(v); // must be <= than wedgeXmax sph->Z2max.setValue(v); vp->getObject()->getDocument()->recomputeFeature(vp->getObject()); } -void TaskBoxPrimitives::onWedgeZinChanged(double v) { +void TaskBoxPrimitives::onWedgeZminChanged(double v) { PartDesign::Wedge* sph = static_cast(vp->getObject()); + ui->wedgeZmax->setMinimum(v); sph->Zmin.setValue(v); vp->getObject()->getDocument()->recomputeFeature(vp->getObject()); } void TaskBoxPrimitives::onWedgeZmaxChanged(double v) { PartDesign::Wedge* sph = static_cast(vp->getObject()); + ui->wedgeZmin->setMaximum(v); sph->Zmax.setValue(v); vp->getObject()->getDocument()->recomputeFeature(vp->getObject()); } diff --git a/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.h b/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.h index 712efffd67..db43aa2a78 100644 --- a/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.h +++ b/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.h @@ -87,15 +87,15 @@ public Q_SLOTS: void onPrismHeightChanged(double); void onPrismPolygonChanged(int); void onWedgeXmaxChanged(double); - void onWedgeXinChanged(double); + void onWedgeXminChanged(double); void onWedgeYmaxChanged(double); - void onWedgeYinChanged(double); + void onWedgeYminChanged(double); void onWedgeZmaxChanged(double); - void onWedgeZinChanged(double); + void onWedgeZminChanged(double); void onWedgeX2maxChanged(double); - void onWedgeX2inChanged(double); + void onWedgeX2minChanged(double); void onWedgeZ2maxChanged(double); - void onWedgeZ2inChanged(double); + void onWedgeZ2minChanged(double); private: /** Notifies when the object is about to be removed. */ From 2241d0d1123314cd08d6191c8628956ad719357e Mon Sep 17 00:00:00 2001 From: triplus Date: Sun, 22 Mar 2020 23:16:13 +0100 Subject: [PATCH 022/117] NaviCube - tweaks and new parameters By default navigation cube text now has more weight and CS axes are slightly shifted. A few additional parameters have been added, for being able to customize navigation cube offset, colors, font and visibility of CS. Parameters are consolidated under NaviCube group. Customization front-end, making use of this new parameters, will be added to CubeMenu module in the following days: https://forum.freecadweb.org/viewtopic.php?f=34&t=43338 --- src/Gui/NaviCube.cpp | 153 +++++++++++++++++++++++++++---------------- 1 file changed, 95 insertions(+), 58 deletions(-) diff --git a/src/Gui/NaviCube.cpp b/src/Gui/NaviCube.cpp index 8cee393596..244230e533 100644 --- a/src/Gui/NaviCube.cpp +++ b/src/Gui/NaviCube.cpp @@ -245,6 +245,7 @@ public: int m_CubeWidgetPosY = 0; int m_PrevWidth = 0; int m_PrevHeight = 0; + QColor m_TextColor; QColor m_HiliteColor; QColor m_ButtonColor; QColor m_FrontFaceColor; @@ -275,7 +276,6 @@ public: NaviCube::NaviCube(Gui::View3DInventorViewer* viewer) { m_NaviCubeImplementation = new NaviCubeImplementation(viewer); - } NaviCube::~NaviCube() { @@ -305,17 +305,37 @@ void NaviCube::setCorner(Corner c) { } NaviCubeImplementation::NaviCubeImplementation( - Gui::View3DInventorViewer* viewer) { + Gui::View3DInventorViewer* viewer) { + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/NaviCube"); m_View3DInventorViewer = viewer; + + m_TextColor = QColor(0,0,0,255); + if (hGrp->GetUnsigned("TextColor")) { + m_TextColor.setRgba(hGrp->GetUnsigned("TextColor")); + } + m_FrontFaceColor = QColor(255,255,255,128); + if (hGrp->GetUnsigned("FrontColor")) { + m_FrontFaceColor.setRgba(hGrp->GetUnsigned("FrontColor")); + } + m_BackFaceColor = QColor(226,233,239,128); - m_HiliteColor = QColor(170,226,247); + if (hGrp->GetUnsigned("BackColor")) { + m_BackFaceColor.setRgba(hGrp->GetUnsigned("BackColor")); + } + + m_HiliteColor = QColor(170,226,255); + if (hGrp->GetUnsigned("HiliteColor")) { + m_HiliteColor.setRgba(hGrp->GetUnsigned("HiliteColor")); + } + m_ButtonColor = QColor(226,233,239,128); + if (hGrp->GetUnsigned("ButtonColor")) { + m_ButtonColor.setRgba(hGrp->GetUnsigned("ButtonColor")); + } + m_PickingFramebuffer = NULL; - - m_CubeWidgetSize = (App::GetApplication().GetUserParameter(). - GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("View")->GetInt("NaviWidgetSize", 132)); - + m_CubeWidgetSize = (hGrp->GetInt("CubeSize", 132)); m_Menu = createNaviCubeMenu(); } @@ -372,10 +392,25 @@ GLuint NaviCubeImplementation::createCubeFaceTex(QtGLWidget* gl, float gap, floa paint.begin(&image); if (text) { + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/NaviCube"); paint.setPen(Qt::white); QFont sansFont(str("Helvetica"), 0.18 * texSize); - sansFont.setStretch(App::GetApplication().GetUserParameter().GetGroup("BaseApp") - ->GetGroup("Preferences")->GetGroup("View")->GetInt("NaviFontStretch", 62)); + QString fontString = QString::fromUtf8((hGrp->GetASCII("FontString")).c_str()); + if (fontString.isEmpty()) { + // Improving readability + sansFont.setWeight(hGrp->GetInt("FontWeight", 87)); + sansFont.setStretch(hGrp->GetInt("FontStretch", 62)); + } + else { + sansFont.fromString(fontString); + } + // Override fromString + if (hGrp->GetInt("FontWeight") > 0) { + sansFont.setWeight(hGrp->GetInt("FontWeight")); + } + if (hGrp->GetInt("FontStretch") > 0) { + sansFont.setStretch(hGrp->GetInt("FontStretch")); + } paint.setFont(sansFont); paint.drawText(QRect(0, 0, texSize, texSize), Qt::AlignCenter,qApp->translate("Gui::NaviCube",text)); } @@ -616,7 +651,7 @@ void NaviCubeImplementation::addFace(const Vector3f& x, const Vector3f& z, int f m_Textures[frontTex], pickId, m_Textures[pickTex], - Qt::black, + m_TextColor, 2); m_Faces.push_back(ft); @@ -680,18 +715,13 @@ void NaviCubeImplementation::initNaviCube(QtGLWidget* gl) { if (labels.size() != 6) { labels.clear(); - labels.push_back(App::GetApplication().GetUserParameter().GetGroup("BaseApp") - ->GetGroup("Preferences")->GetGroup("View")->GetASCII("NaviTextFront", "FRONT")); - labels.push_back(App::GetApplication().GetUserParameter().GetGroup("BaseApp") - ->GetGroup("Preferences")->GetGroup("View")->GetASCII("NaviTextRear", "REAR")); - labels.push_back(App::GetApplication().GetUserParameter().GetGroup("BaseApp") - ->GetGroup("Preferences")->GetGroup("View")->GetASCII("NaviTextTop", "TOP")); - labels.push_back(App::GetApplication().GetUserParameter().GetGroup("BaseApp") - ->GetGroup("Preferences")->GetGroup("View")->GetASCII("NaviTextBottom", "BOTTOM")); - labels.push_back(App::GetApplication().GetUserParameter().GetGroup("BaseApp") - ->GetGroup("Preferences")->GetGroup("View")->GetASCII("NaviTextRight", "RIGHT")); - labels.push_back(App::GetApplication().GetUserParameter().GetGroup("BaseApp") - ->GetGroup("Preferences")->GetGroup("View")->GetASCII("NaviTextLeft", "LEFT")); + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/NaviCube"); + labels.push_back(hGrp->GetASCII("TextFront", "FRONT")); + labels.push_back(hGrp->GetASCII("TextRear", "REAR")); + labels.push_back(hGrp->GetASCII("TextTop", "TOP")); + labels.push_back(hGrp->GetASCII("TextBottom", "BOTTOM")); + labels.push_back(hGrp->GetASCII("TextRight", "RIGHT")); + labels.push_back(hGrp->GetASCII("TextLeft", "LEFT")); } float gap = 0.12f; @@ -801,27 +831,30 @@ void NaviCubeImplementation::handleResize() { if ((m_PrevWidth > 0) && (m_PrevHeight > 0)) { // maintain position relative to closest edge if (m_CubeWidgetPosX > m_PrevWidth / 2) - m_CubeWidgetPosX = view[0] - (m_PrevWidth -m_CubeWidgetPosX); + m_CubeWidgetPosX = view[0] - (m_PrevWidth - m_CubeWidgetPosX); if (m_CubeWidgetPosY > m_PrevHeight / 2) m_CubeWidgetPosY = view[1] - (m_PrevHeight - m_CubeWidgetPosY); } else { // initial position + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/NaviCube"); + int m_CubeWidgetOffsetX = hGrp->GetInt("OffsetX", 0); + int m_CubeWidgetOffsetY = hGrp->GetInt("OffsetY", 0); switch (m_Corner) { case NaviCube::TopLeftCorner: - m_CubeWidgetPosX = m_CubeWidgetSize*1.1 / 2; - m_CubeWidgetPosY = view[1] - m_CubeWidgetSize*1.1 / 2; + m_CubeWidgetPosX = m_CubeWidgetSize*1.1 / 2 + m_CubeWidgetOffsetX; + m_CubeWidgetPosY = view[1] - m_CubeWidgetSize*1.1 / 2 - m_CubeWidgetOffsetY; break; case NaviCube::TopRightCorner: - m_CubeWidgetPosX = view[0] - m_CubeWidgetSize*1.1 / 2; - m_CubeWidgetPosY = view[1] - m_CubeWidgetSize*1.1 / 2; + m_CubeWidgetPosX = view[0] - m_CubeWidgetSize*1.1 / 2 - m_CubeWidgetOffsetX; + m_CubeWidgetPosY = view[1] - m_CubeWidgetSize*1.1 / 2 - m_CubeWidgetOffsetY; break; case NaviCube::BottomLeftCorner: - m_CubeWidgetPosX = m_CubeWidgetSize*1.1 / 2; - m_CubeWidgetPosY = m_CubeWidgetSize*1.1 / 2; + m_CubeWidgetPosX = m_CubeWidgetSize*1.1 / 2 + m_CubeWidgetOffsetX; + m_CubeWidgetPosY = m_CubeWidgetSize*1.1 / 2 + m_CubeWidgetOffsetY; break; case NaviCube::BottomRightCorner: - m_CubeWidgetPosX = view[0] - m_CubeWidgetSize*1.1 / 2; - m_CubeWidgetPosY = m_CubeWidgetSize*1.1 / 2; + m_CubeWidgetPosX = view[0] - m_CubeWidgetSize*1.1 / 2 - m_CubeWidgetOffsetX; + m_CubeWidgetPosY = m_CubeWidgetSize*1.1 / 2 + m_CubeWidgetOffsetY; break; } } @@ -932,37 +965,41 @@ void NaviCubeImplementation::drawNaviCube(bool pickMode) { if (!pickMode) { // Draw the axes - glDisable(GL_TEXTURE_2D); - float a=1.1f; + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/NaviCube"); + bool ShowCS = hGrp->GetBool("ShowCS", 1); + if (ShowCS) { + glDisable(GL_TEXTURE_2D); + float a=1.1f; - static GLubyte xbmp[] = { 0x11,0x11,0x0a,0x04,0x0a,0x11,0x11 }; - glColor3f(1, 0, 0); - glBegin(GL_LINES); - glVertex3f(-1 , -1, -1); - glVertex3f(+1 , -1, -1); - glEnd(); - glRasterPos3d(a, -a, -a); - glBitmap(8, 7, 0, 0, 0, 0, xbmp); + static GLubyte xbmp[] = { 0x11,0x11,0x0a,0x04,0x0a,0x11,0x11 }; + glColor3f(1, 0, 0); + glBegin(GL_LINES); + glVertex3f(-1.1 , -1.1, -1.1); + glVertex3f(+0.5 , -1.1, -1.1); + glEnd(); + glRasterPos3d(a, -a, -a); + glBitmap(8, 7, 0, 0, 0, 0, xbmp); - static GLubyte ybmp[] = { 0x04,0x04,0x04,0x04,0x0a,0x11,0x11 }; - glColor3f(0, 1, 0); - glBegin(GL_LINES); - glVertex3f(-1 , -1, -1); - glVertex3f(-1 , +1, -1); - glEnd(); - glRasterPos3d( -a, a, -a); - glBitmap(8, 7, 0, 0, 0, 0, ybmp); + static GLubyte ybmp[] = { 0x04,0x04,0x04,0x04,0x0a,0x11,0x11 }; + glColor3f(0, 1, 0); + glBegin(GL_LINES); + glVertex3f(-1.1 , -1.1, -1.1); + glVertex3f(-1.1 , +0.5, -1.1); + glEnd(); + glRasterPos3d( -a, a, -a); + glBitmap(8, 7, 0, 0, 0, 0, ybmp); - static GLubyte zbmp[] = { 0x1f,0x10,0x08,0x04,0x02,0x01,0x1f }; - glColor3f(0, 0, 1); - glBegin(GL_LINES); - glVertex3f(-1 , -1, -1); - glVertex3f(-1 , -1, +1); - glEnd(); - glRasterPos3d( -a, -a, a); - glBitmap(8, 7, 0, 0, 0, 0, zbmp); + static GLubyte zbmp[] = { 0x1f,0x10,0x08,0x04,0x02,0x01,0x1f }; + glColor3f(0, 0, 1); + glBegin(GL_LINES); + glVertex3f(-1.1 , -1.1, -1.1); + glVertex3f(-1.1 , -1.1, +0.5); + glEnd(); + glRasterPos3d( -a, -a, a); + glBitmap(8, 7, 0, 0, 0, 0, zbmp); - glEnable(GL_TEXTURE_2D); + glEnable(GL_TEXTURE_2D); + } } // Draw the cube faces From 76a77e4f889f24ba74cac55865294bae7eac974f Mon Sep 17 00:00:00 2001 From: "luz.paz" Date: Mon, 23 Mar 2020 08:36:51 -0400 Subject: [PATCH 023/117] [skip ci] Fix typos Found via ``` codespell -q 3 -L aci,ake,aline,alle,alledges,alocation,als,ang,anid,ba,beginn,behaviour,bloaded,byteorder,calculater,cancelled,cancelling,cas,cascade,centimetre,childs,colour,colours,commen,connexion,currenty,dof,doubleclick,dum,eiter,elemente,ende,feld,finde,findf,freez,hist,iff,indicies,initialisation,initialise,initialised,initialises,initialisiert,ist,kilometre,lod,mantatory,methode,metres,millimetre,modell,nd,noe,normale,normaly,nto,numer,oder,orgin,orginx,orginy,ot,pard,pres,programm,que,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 ``` --- src/Gui/Control.cpp | 2 +- src/Gui/DocumentRecovery.cpp | 2 +- src/Mod/AddonManager/addonmanager_workers.py | 2 +- src/Mod/Draft/coding_conventions.md | 4 ++-- src/Mod/Import/InitGui.py | 1 + src/Mod/Material/Material.py | 6 +++--- src/Mod/Path/PathScripts/PathSurface.py | 8 ++++---- src/Mod/Raytracing/Init.py | 1 + src/Mod/ReverseEngineering/Init.py | 1 + src/Mod/Sketcher/Gui/Command.cpp | 2 +- 10 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/Gui/Control.cpp b/src/Gui/Control.cpp index 11ca885cd4..791bd836ab 100644 --- a/src/Gui/Control.cpp +++ b/src/Gui/Control.cpp @@ -110,7 +110,7 @@ void ControlSingleton::showDialog(Gui::TaskView::TaskDialog *dlg) return; } - // Since the caller sets up a modeless task panel, it indicates intension + // Since the caller sets up a modeless task panel, it indicates intention // for prolonged editing. So disable auto transaction in the current call // stack. // Do this before showing the dialog because its open() function is called diff --git a/src/Gui/DocumentRecovery.cpp b/src/Gui/DocumentRecovery.cpp index bbc9c1d0de..faa00555f8 100644 --- a/src/Gui/DocumentRecovery.cpp +++ b/src/Gui/DocumentRecovery.cpp @@ -230,7 +230,7 @@ QString DocumentRecovery::createProjectFile(const QString& documentXml) void DocumentRecovery::closeEvent(QCloseEvent* e) { // Do not disable the X button in the title bar - // #0004281: Close Documant Recovery + // #0004281: Close Document Recovery e->accept(); } diff --git a/src/Mod/AddonManager/addonmanager_workers.py b/src/Mod/AddonManager/addonmanager_workers.py index 31ac5eb245..a87d40146a 100644 --- a/src/Mod/AddonManager/addonmanager_workers.py +++ b/src/Mod/AddonManager/addonmanager_workers.py @@ -231,7 +231,7 @@ class CheckWBWorker(QtCore.QThread): class FillMacroListWorker(QtCore.QThread): - """This worker opulates the list of macros""" + """This worker populates the list of macros""" add_macro_signal = QtCore.Signal(Macro) info_label_signal = QtCore.Signal(str) diff --git a/src/Mod/Draft/coding_conventions.md b/src/Mod/Draft/coding_conventions.md index ba662d93ef..c807f41bc6 100644 --- a/src/Mod/Draft/coding_conventions.md +++ b/src/Mod/Draft/coding_conventions.md @@ -30,7 +30,7 @@ ## Python -### Code formating +### Code formatting - In general, code should follow [PEP 8](https://www.python.org/dev/peps/pep-0008/) and [PEP 257](https://www.python.org/dev/peps/pep-0257/) (docstrings). @@ -72,7 +72,7 @@ and not meant to be part of the public interface should start with an underscore like `_MyInternalClass` or `_my_small_variable`. -### Python code formating tools +### Python code formatting tools - Using a code editor that automatically checks compliance with PEP 8 is recommended. diff --git a/src/Mod/Import/InitGui.py b/src/Mod/Import/InitGui.py index d55f9c0d2c..d61259dec2 100644 --- a/src/Mod/Import/InitGui.py +++ b/src/Mod/Import/InitGui.py @@ -1,3 +1,4 @@ +# -*- coding: utf8 -*- # Import gui init module # (c) 2003 Jürgen Riegel # diff --git a/src/Mod/Material/Material.py b/src/Mod/Material/Material.py index ba592f297b..18ad48eacc 100644 --- a/src/Mod/Material/Material.py +++ b/src/Mod/Material/Material.py @@ -24,18 +24,18 @@ import sys import FreeCAD -# here the usage description if you use this tool from the command line ("__main__") +# The usage description if you use this tool from the command line ("__main__") CommandlineUsage = """Material - Tool to work with FreeCAD Material definition cards Usage: Material [Options] card-file-name Options: - -c, --output-csv=file-name write a comma separated grid with the material data + -c, --output-csv=filename write a comma separated grid with the material data Exit: 0 No Error or Warning found - 1 Argument error, wrong or less Arguments given + 1 Argument error, wrong or too few Arguments given Tool to work with FreeCAD Material definition cards diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 414d60ca5f..0c83884bc7 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -111,7 +111,7 @@ class ObjectSurface(PathOp.ObjectOp): obj.addProperty("App::PropertyPercent", "StepOver", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path")) obj.addProperty("App::PropertyVectorDistance", "CircularCenterCustom", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path")) - obj.addProperty("App::PropertyEnumeration", "CircularCenterAt", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "Choose what point to start the ciruclar pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom.")) + obj.addProperty("App::PropertyEnumeration", "CircularCenterAt", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "Choose what point to start the circular pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom.")) obj.addProperty("App::PropertyEnumeration", "CutMode", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part: Climb(ClockWise) or Conventional(CounterClockWise)")) obj.addProperty("App::PropertyEnumeration", "CutPattern", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use")) obj.addProperty("App::PropertyFloat", "CutPatternAngle", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")) @@ -507,7 +507,7 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.error('No data for model base: {}'.format(JOB.Model.Group[m].Label)) else: if m > 0: - # Raise to clearance between moddels + # Raise to clearance between models CMDS.append(Path.Command('N (Transition to base: {}.)'.format(Mdl.Label))) CMDS.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) PathLog.info('Working on Model.Group[{}]: {}'.format(m, Mdl.Label)) @@ -1171,7 +1171,7 @@ class ObjectSurface(PathOp.ObjectOp): def _isPocket(self, b, f, w): '''_isPocket(b, f, w)... - Attempts to determing if the wire(w) in face(f) of base(b) is a pocket or raised protrusion. + Attempts to determine if the wire(w) in face(f) of base(b) is a pocket or raised protrusion. Returns True if pocket, False if raised protrusion.''' e = w.Edges[0] for fi in range(0, len(b.Shape.Faces)): @@ -1907,7 +1907,7 @@ class ObjectSurface(PathOp.ObjectOp): def _planarPerformOclScan(self, obj, pdc, pathGeom, offsetPoints=False): '''_planarPerformOclScan(obj, pdc, pathGeom, offsetPoints=False)... - Switching fuction for calling the appropriate path-geometry to OCL points conversion fucntion + Switching function for calling the appropriate path-geometry to OCL points conversion function for the various cut patterns.''' PathLog.debug('_planarPerformOclScan()') SCANS = list() diff --git a/src/Mod/Raytracing/Init.py b/src/Mod/Raytracing/Init.py index 19eb5447a3..5cf86075c7 100644 --- a/src/Mod/Raytracing/Init.py +++ b/src/Mod/Raytracing/Init.py @@ -1,3 +1,4 @@ +# -*- coding: utf8 -*- # FreeCAD init script of the Raytracing module # (c) 2001 Jürgen Riegel diff --git a/src/Mod/ReverseEngineering/Init.py b/src/Mod/ReverseEngineering/Init.py index ecf1bb3f55..3a13579462 100644 --- a/src/Mod/ReverseEngineering/Init.py +++ b/src/Mod/ReverseEngineering/Init.py @@ -1,3 +1,4 @@ +# -*- coding: utf8 -*- # FreeCAD init script of the ReverseEngineering module # (c) 2001 Jürgen Riegel diff --git a/src/Mod/Sketcher/Gui/Command.cpp b/src/Mod/Sketcher/Gui/Command.cpp index 825594bd4b..c80bed10fc 100644 --- a/src/Mod/Sketcher/Gui/Command.cpp +++ b/src/Mod/Sketcher/Gui/Command.cpp @@ -762,7 +762,7 @@ void CmdSketcherMirrorSketch::activated(int iMsg) std::vector tempgeo = tempsketch->getInternalGeometry(); std::vector tempconstr = tempsketch->Constraints.getValues(); - // If value of addedGeometries or addedConstraints is -1, it gets added to vector begin iterator and that is invlid + // If value of addedGeometries or addedConstraints is -1, it gets added to vector begin iterator and that is invalid std::vector mirrorgeo(tempgeo.begin() + (addedGeometries + 1), tempgeo.end()); std::vector mirrorconstr(tempconstr.begin() + (addedConstraints + 1), tempconstr.end()); From 115d4b0d0815e95015972f67d396fbf950bba35e Mon Sep 17 00:00:00 2001 From: carlopav Date: Thu, 19 Mar 2020 08:21:19 +0100 Subject: [PATCH 024/117] [Draft] Reordering Modifiers toolbar --- src/Mod/Draft/draftutils/init_tools.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/Mod/Draft/draftutils/init_tools.py b/src/Mod/Draft/draftutils/init_tools.py index 0f90623a63..327756dba5 100644 --- a/src/Mod/Draft/draftutils/init_tools.py +++ b/src/Mod/Draft/draftutils/init_tools.py @@ -59,15 +59,22 @@ def get_draft_array_commands(): def get_draft_modification_commands(): """Return the modification commands list.""" - lst = ["Draft_Move", "Draft_Rotate", "Draft_Offset", - "Draft_Trimex", "Draft_Join", "Draft_Split", - "Draft_Upgrade", "Draft_Downgrade", "Draft_Scale", - "Draft_Edit", "Draft_SubelementHighlight", - "Draft_WireToBSpline", "Draft_Draft2Sketch", - "Draft_Shape2DView"] + lst = ["Draft_Move", "Draft_Rotate", + "Draft_Scale", "Draft_Mirror", + "Draft_Offset", "Draft_Trimex", + "Draft_Stretch", + "Separator", + "Draft_Clone"] lst += get_draft_array_commands() - lst += ["Draft_Clone", - "Draft_Drawing", "Draft_Mirror", "Draft_Stretch"] + lst += ["Separator", + "Draft_Edit", "Draft_SubelementHighlight", + "Separator", + "Draft_Join", "Draft_Split", + "Draft_Upgrade", "Draft_Downgrade", + "Separator", + "Draft_WireToBSpline", "Draft_Draft2Sketch", + "Separator", + "Draft_Shape2DView", "Draft_Drawing"] return lst From 107831e0a7769b4f09e2f8fdb0f8b59d100114dd Mon Sep 17 00:00:00 2001 From: wmayer Date: Tue, 24 Mar 2020 15:15:25 +0100 Subject: [PATCH 025/117] PartDesign: [skip ci] fixes #0004254: Crash when canceling duplicate sketch in PartDesign --- src/Mod/PartDesign/Gui/CommandBody.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Mod/PartDesign/Gui/CommandBody.cpp b/src/Mod/PartDesign/Gui/CommandBody.cpp index 00983f96b4..febbb73f90 100644 --- a/src/Mod/PartDesign/Gui/CommandBody.cpp +++ b/src/Mod/PartDesign/Gui/CommandBody.cpp @@ -662,7 +662,8 @@ void CmdPartDesignDuplicateSelection::activated(int iMsg) } // Adjust visibility of features - FCMD_OBJ_SHOW(newFeatures.back()); + if (!newFeatures.empty()) + FCMD_OBJ_SHOW(newFeatures.back()); } updateActive(); From 4256e797286e02292646ab7abe4bb1d6415435a5 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Mon, 23 Mar 2020 11:46:31 -0400 Subject: [PATCH 026/117] [TD]fix preference path for SectionLine colour --- src/Mod/TechDraw/Gui/QGISectionLine.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Mod/TechDraw/Gui/QGISectionLine.cpp b/src/Mod/TechDraw/Gui/QGISectionLine.cpp index 04db4f6916..23bcd922aa 100644 --- a/src/Mod/TechDraw/Gui/QGISectionLine.cpp +++ b/src/Mod/TechDraw/Gui/QGISectionLine.cpp @@ -279,7 +279,7 @@ void QGISectionLine::setFont(QFont f, double fsize) QColor QGISectionLine::getSectionColor() { Base::Reference hGrp = App::GetApplication().GetUserParameter() - .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Colors"); + .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Decorations"); App::Color fcColor = App::Color((uint32_t) hGrp->GetUnsigned("SectionColor", 0x00000000)); return fcColor.asValue(); } @@ -354,7 +354,11 @@ void QGISectionLine::setTools() // m_arrow2->setPen(m_pen); // m_arrow1->setBrush(m_brush); // m_arrow2->setBrush(m_brush); + m_arrow1->setNormalColor(getSectionColor()); + m_arrow1->setFillColor(getSectionColor()); m_arrow1->setPrettyNormal(); + m_arrow2->setNormalColor(getSectionColor()); + m_arrow2->setFillColor(getSectionColor()); m_arrow2->setPrettyNormal(); m_symbol1->setDefaultTextColor(m_colCurrent); From afff0df472efab03a330ebb8c189ddb4bcb0d5f6 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Mon, 23 Mar 2020 11:47:10 -0400 Subject: [PATCH 027/117] [TD]add default pref for section cut surface --- src/Mod/TechDraw/App/DrawViewSection.cpp | 11 +- src/Mod/TechDraw/App/DrawViewSection.h | 1 + src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui | 116 +++++++++++++----- src/Mod/TechDraw/Gui/DlgPrefsTechDraw3Imp.cpp | 2 + 4 files changed, 95 insertions(+), 35 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewSection.cpp b/src/Mod/TechDraw/App/DrawViewSection.cpp index 897a15fbf1..f27941a468 100644 --- a/src/Mod/TechDraw/App/DrawViewSection.cpp +++ b/src/Mod/TechDraw/App/DrawViewSection.cpp @@ -128,7 +128,7 @@ DrawViewSection::DrawViewSection() ADD_PROPERTY_TYPE(FuseBeforeCut ,(false),sgroup,App::Prop_None,"Merge Source(s) into a single shape before cutting"); CutSurfaceDisplay.setEnums(CutSurfaceEnums); - ADD_PROPERTY_TYPE(CutSurfaceDisplay,((long)2),fgroup, App::Prop_None, "Appearance of Cut Surface"); + ADD_PROPERTY_TYPE(CutSurfaceDisplay,(prefCutSurface()),fgroup, App::Prop_None, "Appearance of Cut Surface"); //initialize these to defaults ADD_PROPERTY_TYPE(FileHatchPattern ,(DrawHatch::prefSvgHatch()),fgroup,App::Prop_None,"The hatch pattern file for the cut surface"); @@ -893,6 +893,15 @@ bool DrawViewSection::debugSection(void) const return result; } +int DrawViewSection::prefCutSurface(void) const +{ +// Base::Console().Message("DVS::prefCutSurface()\n"); + Base::ReferencehGrp = App::GetApplication().GetUserParameter() + .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Decorations"); + + int result = hGrp->GetInt("CutSurfaceDisplay", 2); //default to SvgHatch + return result; +} void DrawViewSection::onDocumentRestored() { diff --git a/src/Mod/TechDraw/App/DrawViewSection.h b/src/Mod/TechDraw/App/DrawViewSection.h index b999c0bf44..b6e257998d 100644 --- a/src/Mod/TechDraw/App/DrawViewSection.h +++ b/src/Mod/TechDraw/App/DrawViewSection.h @@ -131,6 +131,7 @@ protected: void getParameters(void); bool debugSection(void) const; + int prefCutSurface(void) const; TopoDS_Shape m_cutShape; diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui index 9b113542c0..7143e158df 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui @@ -7,7 +7,7 @@ 0 0 448 - 856 + 952 @@ -231,7 +231,7 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + 4.000000000000000 @@ -256,7 +256,7 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + 5.000000000000000 @@ -379,7 +379,7 @@ - + @@ -391,7 +391,7 @@ - + @@ -403,7 +403,22 @@ - + + + + + true + + + + Length of horizontal portion of Balloon leader + + + Ballon Leader Kink Length + + + + @@ -485,22 +500,7 @@ - - - - - true - - - - Length of horizontal portion of Balloon leader - - - Ballon Leader Kink Length - - - - + @@ -525,7 +525,7 @@ - + @@ -601,7 +601,7 @@ - + @@ -613,7 +613,7 @@ - + @@ -632,7 +632,7 @@ - + @@ -745,7 +745,7 @@ - + @@ -770,7 +770,7 @@ - + @@ -827,7 +827,7 @@ - + Qt::Horizontal @@ -840,7 +840,7 @@ - + @@ -877,7 +877,7 @@ - + @@ -889,7 +889,7 @@ - + @@ -913,7 +913,7 @@ - + @@ -940,7 +940,7 @@ - + @@ -962,6 +962,54 @@ + + + + + true + + + + Section Cut Surface + + + + + + + Default appearance of cut surface in section view + + + 2 + + + CutSurfaceDisplay + + + /Mod/TechDraw/Decorations + + + + Hide + + + + + Solid Color + + + + + Svg Hatch + + + + + PAT Hatch + + + + diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3Imp.cpp b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3Imp.cpp index 8587e11605..b9e470256d 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3Imp.cpp +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3Imp.cpp @@ -80,6 +80,7 @@ void DlgPrefsTechDraw3Imp::saveSettings() plsb_ArrowSize->onSave(); plsb_FontSize->onSave(); sbAltDecimals->onSave(); + cbCutSurface->onSave(); } void DlgPrefsTechDraw3Imp::loadSettings() @@ -117,6 +118,7 @@ void DlgPrefsTechDraw3Imp::loadSettings() plsb_ArrowSize->onRestore(); plsb_FontSize->onRestore(); sbAltDecimals->onRestore(); + cbCutSurface->onRestore(); DrawGuiUtil::loadArrowBox(pcbBalloonArrow); pcbBalloonArrow->setCurrentIndex(prefBalloonArrow()); From 83cb709893a645bc5ccbdbaf6a29b9fc5d5c72be Mon Sep 17 00:00:00 2001 From: wandererfan Date: Mon, 23 Mar 2020 15:52:52 -0400 Subject: [PATCH 028/117] [TD]correct button text --- src/Mod/TechDraw/Gui/TaskLeaderLine.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Mod/TechDraw/Gui/TaskLeaderLine.cpp b/src/Mod/TechDraw/Gui/TaskLeaderLine.cpp index 43808bc1e0..2f317c6e92 100644 --- a/src/Mod/TechDraw/Gui/TaskLeaderLine.cpp +++ b/src/Mod/TechDraw/Gui/TaskLeaderLine.cpp @@ -494,7 +494,6 @@ void TaskLeaderLine::onTrackerClicked(bool b) if (m_tracker != nullptr) { m_tracker->terminateDrawing(); } - m_pbTrackerState = TRACKERPICK; ui->pbTracker->setText(QString::fromUtf8("Pick Points")); ui->pbCancelEdit->setEnabled(false); @@ -503,13 +502,12 @@ void TaskLeaderLine::onTrackerClicked(bool b) setEditCursor(Qt::ArrowCursor); return; } else if ( (m_pbTrackerState == TRACKERSAVE) && - (!getCreateMode()) ) { + (!getCreateMode()) ) { //edit mode if (m_qgLine != nullptr) { m_qgLine->closeEdit(); } - m_pbTrackerState = TRACKERPICK; - ui->pbTracker->setText(QString::fromUtf8("Pick Points")); + ui->pbTracker->setText(QString::fromUtf8("Edit Points")); ui->pbCancelEdit->setEnabled(false); enableTaskButtons(true); From 1d04cee15d32afe5df08c880417971e2b2713479 Mon Sep 17 00:00:00 2001 From: donovaly Date: Tue, 24 Mar 2020 00:20:32 +0100 Subject: [PATCH 029/117] [TD] remove UI for unimplemented feature as discussed: https://forum.freecadweb.org/viewtopic.php?p=379709#p379709 --- src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui | 818 +++++++++--------- src/Mod/TechDraw/Gui/DlgPrefsTechDraw2Imp.cpp | 8 - 2 files changed, 386 insertions(+), 440 deletions(-) diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui index ab958d60a1..751ed1b649 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui @@ -7,11 +7,11 @@ 0 0 440 - 500 + 450 - + 0 0 @@ -19,7 +19,7 @@ 0 - 500 + 0 @@ -29,6 +29,388 @@ + + + + + 0 + 0 + + + + + 0 + 141 + + + + Size Adjustments + + + + + + + + Vertex Scale + + + + + + + + 0 + 0 + + + + + 174 + 0 + + + + Scale of vertex dots. Multiplier of line width. + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 5.000000000000000 + + + VertexScale + + + Mod/TechDraw/General + + + + + + + + true + + + + Center Mark Scale + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 174 + 0 + + + + Size of center marks. Multiplier of vertex size. + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0.500000000000000 + + + CenterMarkScale + + + Mod/TechDraw/Decorations + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + true + + + + Tolerance Text Scale + + + + + + + + 0 + 0 + + + + + 174 + 0 + + + + + 0 + 0 + + + + Tolerance font size adjustment. Multiplier of dimension font size. + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0.500000000000000 + + + TolSizeAdjust + + + Mod/TechDraw/Dimensions + + + + + + + Template Edit Mark + + + + + + + + 0 + 0 + + + + + 174 + 0 + + + + Size of template field click handles + + + 3.000000000000000 + + + TemplateDotSize + + + Mod/TechDraw/General + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 200 + + + + Selection + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 174 + 0 + + + + + 0 + 0 + + + + Selection area around center marks +Each unit means is approx. 0.1 mm + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 5.000000000000000 + + + MarkFuzz + + + Mod/TechDraw/General + + + + + + + + 0 + 0 + + + + + 174 + 0 + + + + + 0 + 0 + + + + Size of selection area around edges +Each unit means is approx. 0.1 mm + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 10.000000000000000 + + + EdgeFuzz + + + Mod/TechDraw/General + + + + + + + + 0 + 0 + + + + Mark Fuzz + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Edge Fuzz + + + + + + + + @@ -226,207 +608,6 @@ - - - - - 0 - 0 - - - - - 0 - 113 - - - - - 0 - 200 - - - - Selection - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - Edge Fuzz - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 174 - 0 - - - - - 0 - 0 - - - - Size of selection area around edges - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 10.000000000000000 - - - EdgeFuzz - - - Mod/TechDraw/General - - - - - - - Mark Fuzz - - - - - - - - 0 - 0 - - - - - 174 - 0 - - - - - 0 - 0 - - - - Selection area around center marks - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 5.000000000000000 - - - MarkFuzz - - - Mod/TechDraw/General - - - - - - - Overlap Radius - - - - - - - false - - - - 0 - 0 - - - - - 174 - 0 - - - - - 0 - 33 - - - - Area to be inspected for overlap object selection. -(not implemented yet) - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 20.000000000000000 - - - OverlapRadius - - - Mod/TechDraw/General - - - - - - - - @@ -446,233 +627,6 @@ - - - - - 0 - 0 - - - - - 0 - 141 - - - - Size Adjustments - - - - - - - - Vertex Scale - - - - - - - - 0 - 0 - - - - - 174 - 0 - - - - Scale of vertex dots. Multiplier of line width. - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 5.000000000000000 - - - VertexScale - - - Mod/TechDraw/General - - - - - - - - true - - - - Center Mark Scale - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 174 - 0 - - - - Size of center marks. Multiplier of vertex size. - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 0.500000000000000 - - - CenterMarkScale - - - Mod/TechDraw/Decorations - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - true - - - - Tolerance Text Scale - - - - - - - - 0 - 0 - - - - - 174 - 0 - - - - - 0 - 0 - - - - Tolerance font size adjustment. Multiplier of dimension font size. - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 0.500000000000000 - - - TolSizeAdjust - - - Mod/TechDraw/Dimensions - - - - - - - Template Edit Mark - - - - - - - - 0 - 0 - - - - - 174 - 0 - - - - Size of template field click handles - - - Qt::AlignRight - - - 3.000000000000000 - - - TemplateDotSize - - - Mod/TechDraw/General - - - - - - - - @@ -701,7 +655,7 @@ Gui::PrefUnitSpinBox - Gui::QuantitySpinBox + QWidget
Gui/PrefWidgets.h
diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2Imp.cpp b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2Imp.cpp index dd02aa5360..2a24864609 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2Imp.cpp +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2Imp.cpp @@ -61,16 +61,13 @@ void DlgPrefsTechDraw2Imp::saveSettings() { pdsbToleranceScale->onSave(); pdsbTemplateMark->onSave(); - pdsbVertexScale->onSave(); pdsbCenterScale->onSave(); - pdsbPageScale->onSave(); cbViewScaleType->onSave(); pdsbViewScale->onSave(); pdsbEdgeFuzz->onSave(); pdsbMarkFuzz->onSave(); - pdsbOverlapRadius->onSave(); pdsbTemplateMark->onSave(); } @@ -78,20 +75,15 @@ void DlgPrefsTechDraw2Imp::loadSettings() { double markDefault = 3.0; pdsbTemplateMark->setValue(markDefault); - pdsbToleranceScale->onRestore(); - pdsbTemplateMark->onRestore(); - pdsbVertexScale->onRestore(); pdsbCenterScale->onRestore(); - pdsbPageScale->onRestore(); cbViewScaleType->onRestore(); pdsbViewScale->onRestore(); pdsbEdgeFuzz->onRestore(); pdsbMarkFuzz->onRestore(); - pdsbOverlapRadius->onRestore(); pdsbTemplateMark->onRestore(); } From 7effad82e27b55bbd27dec4b4514aed330a7de3b Mon Sep 17 00:00:00 2001 From: donovaly Date: Tue, 24 Mar 2020 00:27:24 +0100 Subject: [PATCH 030/117] DlgPrefsTechDraw2.ui: restore alignment info removed by Qt Creator --- src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui index 751ed1b649..8a853ade52 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui @@ -221,7 +221,7 @@
- + 0 @@ -246,6 +246,9 @@ Mod/TechDraw/General + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + From ffeeb322d199a1c44122b0ac313f644a3fa8f34a Mon Sep 17 00:00:00 2001 From: donovaly Date: Tue, 24 Mar 2020 00:41:11 +0100 Subject: [PATCH 031/117] [TD] fix centerline dialog - I realized that on smaller screens the vertical space was too small and Shift Vert was hardly visible. Thus Shift Vert is now below Shift Horiz: --- src/Mod/TechDraw/Gui/TaskCenterLine.ui | 139 +++++++++++++------------ 1 file changed, 71 insertions(+), 68 deletions(-) diff --git a/src/Mod/TechDraw/Gui/TaskCenterLine.ui b/src/Mod/TechDraw/Gui/TaskCenterLine.ui index 4222c054b2..8ef2ae3ddc 100644 --- a/src/Mod/TechDraw/Gui/TaskCenterLine.ui +++ b/src/Mod/TechDraw/Gui/TaskCenterLine.ui @@ -9,8 +9,8 @@ 0 0 - 380 - 374 + 360 + 385
@@ -150,18 +150,50 @@ - + - Shift Horiz + Shift Horizontal - + + + + + 0 + 20 + + + + Move line -Left or +Right + + + + + + + - Shift Vert + Shift Vertical + + + + + + + + 0 + 20 + + + + Move line +Up or -Down + + + @@ -191,32 +223,6 @@ - - - - Move line +Up or -Down - - - - - - - - - - - 0 - 20 - - - - Move line -Left or +Right - - - - - - @@ -227,18 +233,41 @@ - - - QFormLayout::AllNonFixedFieldsGrow - - + + + + + Extend By + + + + + + + + 0 + 20 + + + + Make the line a little longer. + + + mm + + + 3.000000000000000 + + + + Color - + @@ -249,14 +278,14 @@ - + Weight - + 0.100000000000000 @@ -266,14 +295,14 @@ - + Style - + 1 @@ -325,32 +354,6 @@ - - - - Extend By - - - - - - - - 0 - 20 - - - - Make the line a little longer. - - - mm - - - 3.000000000000000 - - - From e21342d453e934f314dea117e049215fc82ed0dc Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Wed, 25 Mar 2020 12:26:43 +0100 Subject: [PATCH 032/117] Arch: Fixed export of furniture elements --- src/Mod/Arch/exportIFC.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Mod/Arch/exportIFC.py b/src/Mod/Arch/exportIFC.py index 89cc676afa..f0b3c71cbe 100644 --- a/src/Mod/Arch/exportIFC.py +++ b/src/Mod/Arch/exportIFC.py @@ -1501,6 +1501,8 @@ def getIfcTypeFromObj(obj): ifctype = "Group" if ifctype == "Undefined": ifctype = "BuildingElementProxy" + if ifctype == "Furniture": + ifctype = "FurnishingElement" return "Ifc" + ifctype From ce3eadf289b18bd70ed28c35a3afe6982edf9d41 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 25 Mar 2020 13:22:18 +0100 Subject: [PATCH 033/117] Gui: [skip ci] implement Python wrapper for ExpressionBinding --- src/Gui/Application.cpp | 5 + src/Gui/CMakeLists.txt | 4 +- src/Gui/ExpressionBindingPy.cpp | 168 ++++++++++++++++++++++++++++++++ src/Gui/ExpressionBindingPy.h | 57 +++++++++++ 4 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 src/Gui/ExpressionBindingPy.cpp create mode 100644 src/Gui/ExpressionBindingPy.h diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 3082a548f1..2da5437b61 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -93,6 +93,7 @@ #include "DocumentRecovery.h" #include "TransactionObject.h" #include "FileDialog.h" +#include "ExpressionBindingPy.h" #include "TextDocumentEditorView.h" #include "SplitView3DInventor.h" @@ -384,6 +385,10 @@ Application::Application(bool GUIenabled) Py_INCREF(pySide->module().ptr()); PyModule_AddObject(module, "PySideUic", pySide->module().ptr()); + ExpressionBindingPy::init_type(); + Base::Interpreter().addType(ExpressionBindingPy::type_object(), + module,"ExpressionBinding"); + //insert Selection module #if PY_MAJOR_VERSION >= 3 static struct PyModuleDef SelectionModuleDef = { diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index dc0ca11da2..6f3b925a70 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -1223,6 +1223,7 @@ SET(FreeCADGui_CPP_SRCS DocumentObserver.cpp DocumentObserverPython.cpp ExpressionBinding.cpp + ExpressionBindingPy.cpp GraphicsViewZoom.cpp ExpressionCompleter.cpp GuiApplication.cpp @@ -1249,7 +1250,8 @@ SET(FreeCADGui_SRCS DocumentModel.h DocumentObserver.h DocumentObserverPython.h - ExpressionBinding.cpp + ExpressionBinding.h + ExpressionBindingPy.h ExpressionCompleter.h FreeCADGuiInit.py GraphicsViewZoom.h diff --git a/src/Gui/ExpressionBindingPy.cpp b/src/Gui/ExpressionBindingPy.cpp new file mode 100644 index 0000000000..6899a59177 --- /dev/null +++ b/src/Gui/ExpressionBindingPy.cpp @@ -0,0 +1,168 @@ +/*************************************************************************** + * Copyright (c) 2020 Werner Mayer * + * * + * 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., 51 Franklin Street, * + * Fifth Floor, Boston, MA 02110-1301, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +#endif +#include "ExpressionBindingPy.h" +#include "ExpressionBinding.h" +#include "WidgetFactory.h" +#include "QuantitySpinBox.h" +#include "InputField.h" +#include + +using namespace Gui; + +void ExpressionBindingPy::init_type() +{ + behaviors().name("ExpressionBinding"); + behaviors().doc("Python interface class for ExpressionBinding"); + // you must have overwritten the virtual functions + behaviors().supportRepr(); + behaviors().supportGetattr(); + behaviors().supportSetattr(); + behaviors().set_tp_new(PyMake); + behaviors().readyType(); + + add_varargs_method("bind",&ExpressionBindingPy::bind,"Bind with an expression"); + add_varargs_method("isBound",&ExpressionBindingPy::isBound,"Check if already bound with an expression"); + add_varargs_method("apply",&ExpressionBindingPy::apply,"apply"); + add_varargs_method("hasExpression",&ExpressionBindingPy::hasExpression,"hasExpression"); + add_varargs_method("autoApply",&ExpressionBindingPy::autoApply,"autoApply"); + add_varargs_method("setAutoApply",&ExpressionBindingPy::setAutoApply,"setAutoApply"); +} + +PyObject *ExpressionBindingPy::PyMake(struct _typeobject *, PyObject * args, PyObject *) +{ + Py::Tuple tuple(args); + + ExpressionBinding* expr = nullptr; + PythonWrapper wrap; + wrap.loadWidgetsModule(); + + QWidget* obj = dynamic_cast(wrap.toQObject(tuple.getItem(0))); + if (obj) { + do { + QuantitySpinBox* sb = qobject_cast(obj); + if (sb) { + expr = sb; + break; + } + InputField* le = qobject_cast(obj); + if (le) { + expr = le; + break; + } + } + while(false); + } + + if (!expr) { + PyErr_SetString(PyExc_TypeError, "Wrong type"); + return nullptr; + } + + return new ExpressionBindingPy(expr); +} + +ExpressionBindingPy::ExpressionBindingPy(ExpressionBinding* expr) + : expr(expr) +{ +} + +ExpressionBindingPy::~ExpressionBindingPy() +{ +} + +Py::Object ExpressionBindingPy::repr() +{ + std::stringstream s; + s << ""; + return Py::String(s.str()); +} + +Py::Object ExpressionBindingPy::bind(const Py::Tuple& args) +{ + PyObject* py; + const char* str; + if (!PyArg_ParseTuple(args.ptr(), "O!s", &App::DocumentObjectPy::Type, &py, &str)) + throw Py::Exception(); + + try { + App::DocumentObject* obj = static_cast(py)->getDocumentObjectPtr(); + App::ObjectIdentifier id(App::ObjectIdentifier::parse(obj, str)); + if (!id.getProperty()) { + throw Base::AttributeError("Wrong property"); + } + + expr->bind(id); + return Py::None(); + } + catch (const Base::Exception& e) { + e.setPyException(); + throw Py::Exception(); + } + catch (...) { + throw Py::RuntimeError("Cannot bind to object"); + } +} + +Py::Object ExpressionBindingPy::isBound(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + return Py::Boolean(expr->isBound()); +} + +Py::Object ExpressionBindingPy::apply(const Py::Tuple& args) +{ + const char* str; + if (!PyArg_ParseTuple(args.ptr(), "s", &str)) + throw Py::Exception(); + + return Py::Boolean(expr->apply(str)); +} + +Py::Object ExpressionBindingPy::hasExpression(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + return Py::Boolean(expr->hasExpression()); +} + +Py::Object ExpressionBindingPy::autoApply(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + return Py::Boolean(expr->autoApply()); +} + +Py::Object ExpressionBindingPy::setAutoApply(const Py::Tuple& args) +{ + PyObject* b; + if (!PyArg_ParseTuple(args.ptr(), "O!", &PyBool_Type, &b)) + throw Py::Exception(); + + bool value = PyObject_IsTrue(b) ? true : false; + expr->setAutoApply(value); + return Py::None(); +} diff --git a/src/Gui/ExpressionBindingPy.h b/src/Gui/ExpressionBindingPy.h new file mode 100644 index 0000000000..b02f52a873 --- /dev/null +++ b/src/Gui/ExpressionBindingPy.h @@ -0,0 +1,57 @@ +/*************************************************************************** + * Copyright (c) 2020 Werner Mayer * + * * + * 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., 51 Franklin Street, * + * Fifth Floor, Boston, MA 02110-1301, USA * + * * + ***************************************************************************/ + +#ifndef EXPRESSIONBINDINGPY_H +#define EXPRESSIONBINDINGPY_H + +#include + +namespace Gui { +class ExpressionBinding; + +class ExpressionBindingPy : public Py::PythonExtension +{ +public: + static void init_type(void); // announce properties and methods + + ExpressionBindingPy(ExpressionBinding*); + ~ExpressionBindingPy(); + + Py::Object repr(); + + Py::Object bind(const Py::Tuple&); + Py::Object isBound(const Py::Tuple&); + Py::Object apply(const Py::Tuple&); + Py::Object hasExpression(const Py::Tuple&); + Py::Object autoApply(const Py::Tuple&); + Py::Object setAutoApply(const Py::Tuple&); + +private: + static PyObject *PyMake(struct _typeobject *, PyObject *, PyObject *); + +private: + ExpressionBinding* expr; +}; + +} + +#endif // EXPRESSIONBINDING_H From d09c4217d0bb875821a6b8f5115dded7b9d4cd03 Mon Sep 17 00:00:00 2001 From: donovaly Date: Wed, 25 Mar 2020 00:32:51 +0100 Subject: [PATCH 034/117] [TD] make dimension DLG shorter - I saw that the dialog was now too large vertically for my laptop screen. Now it is at least no longer than the IFC export settings dialog - also fix a typo in a tooltip --- src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui | 4 +- src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui | 100 +++++++++++----------- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui index 8a853ade52..f354a9cf8e 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui @@ -317,7 +317,7 @@
Selection area around center marks -Each unit means is approx. 0.1 mm +Each unit is approx. 0.1 mm wide @@ -358,7 +358,7 @@ Each unit means is approx. 0.1 mm Size of selection area around edges -Each unit means is approx. 0.1 mm +Each unit is approx. 0.1 mm wide diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui index 7143e158df..948124695f 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui @@ -7,7 +7,7 @@ 0 0 448 - 952 + 856 @@ -338,7 +338,7 @@ - + @@ -500,31 +500,6 @@ - - - - - 0 - 0 - - - - Forces last leader line segment to be horizontal - - - Leader Line Auto Horizontal - - - true - - - AutoHorizontal - - - Mod/TechDraw/LeaderLines - - - @@ -940,28 +915,6 @@ - - - - - 0 - 0 - - - - Show arc centers in printed output - - - Print Center Marks - - - PrintCenterMarks - - - Mod/TechDraw/Decorations - - - @@ -1000,7 +953,7 @@ - Svg Hatch + SVG Hatch @@ -1010,6 +963,53 @@ + + + + + 0 + 0 + + + + Forces last leader line segment to be horizontal + + + Leader Line Auto Horizontal + + + true + + + AutoHorizontal + + + Mod/TechDraw/LeaderLines + + + + + + + + 0 + 0 + + + + Show arc centers in printed output + + + Print Center Marks + + + PrintCenterMarks + + + Mod/TechDraw/Decorations + + + From 3a0d9e8c514c163be0b750dd75bb4f2afe4f94f1 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 25 Mar 2020 19:56:41 +0100 Subject: [PATCH 035/117] Part: [skip ci] make quantity fields in Python attachment dialog expressen aware --- .../AttachmentEditor/TaskAttachmentEditor.py | 23 ++++++++++++++----- .../AttachmentEditor/TaskAttachmentEditor.ui | 18 +++++++-------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py b/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py index ddb8a77f37..33e6ece843 100644 --- a/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py +++ b/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py @@ -227,6 +227,17 @@ class AttachmentEditorTaskPanel(FrozenClass): self.form.setWindowIcon(QtGui.QIcon(':/icons/Part_Attachment.svg')) self.form.setWindowTitle(_translate('AttachmentEditor',"Attachment",None)) + self.form.attachmentOffsetX.setProperty("unit", "mm") + self.form.attachmentOffsetY.setProperty("unit", "mm") + self.form.attachmentOffsetZ.setProperty("unit", "mm") + Gui.ExpressionBinding(self.form.attachmentOffsetX).bind(self.obj,"AttachmentOffset.Base.x") + Gui.ExpressionBinding(self.form.attachmentOffsetY).bind(self.obj,"AttachmentOffset.Base.y") + Gui.ExpressionBinding(self.form.attachmentOffsetZ).bind(self.obj,"AttachmentOffset.Base.z") + + Gui.ExpressionBinding(self.form.attachmentOffsetYaw).bind(self.obj,"AttachmentOffset.Rotation.Yaw") + Gui.ExpressionBinding(self.form.attachmentOffsetPitch).bind(self.obj,"AttachmentOffset.Rotation.Pitch") + Gui.ExpressionBinding(self.form.attachmentOffsetRoll).bind(self.obj,"AttachmentOffset.Rotation.Roll") + self.refLines = [self.form.lineRef1, self.form.lineRef2, self.form.lineRef3, @@ -437,12 +448,12 @@ class AttachmentEditorTaskPanel(FrozenClass): try: old_selfblock = self.block self.block = True - self.form.attachmentOffsetX.setText ((plm.Base.x * mm).UserString) - self.form.attachmentOffsetY.setText ((plm.Base.y * mm).UserString) - self.form.attachmentOffsetZ.setText ((plm.Base.z * mm).UserString) - self.form.attachmentOffsetYaw.setText ((plm.Rotation.toEuler()[0] * deg).UserString) - self.form.attachmentOffsetPitch.setText((plm.Rotation.toEuler()[1] * deg).UserString) - self.form.attachmentOffsetRoll.setText ((plm.Rotation.toEuler()[2] * deg).UserString) + self.form.attachmentOffsetX.lineEdit().setText ((plm.Base.x * mm).UserString) + self.form.attachmentOffsetY.lineEdit().setText ((plm.Base.y * mm).UserString) + self.form.attachmentOffsetZ.lineEdit().setText ((plm.Base.z * mm).UserString) + self.form.attachmentOffsetYaw.lineEdit().setText ((plm.Rotation.toEuler()[0] * deg).UserString) + self.form.attachmentOffsetPitch.lineEdit().setText((plm.Rotation.toEuler()[1] * deg).UserString) + self.form.attachmentOffsetRoll.lineEdit().setText ((plm.Rotation.toEuler()[2] * deg).UserString) self.form.checkBoxFlip.setChecked(self.attacher.Reverse) diff --git a/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.ui b/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.ui index 5dd8174005..f685fe6e7c 100644 --- a/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.ui +++ b/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.ui @@ -154,7 +154,7 @@ - + 0 @@ -186,7 +186,7 @@ - + 0 @@ -244,7 +244,7 @@ - + 0 @@ -263,7 +263,7 @@ - + 0 @@ -295,7 +295,7 @@ Note: The placement is expressed in local space of object being attached. - + 0 @@ -327,7 +327,7 @@ Note: The placement is expressed in local space of object being attached. - + 0 @@ -372,9 +372,9 @@ Note: The placement is expressed in local space of object being attached. - Gui::InputField - QLineEdit -
Gui/InputField.h
+ Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
From 3318ddeac9cf0336d9c530317aff9c833f56ee9b Mon Sep 17 00:00:00 2001 From: donovaly Date: Wed, 25 Mar 2020 22:47:08 +0100 Subject: [PATCH 036/117] [TD] add missing repaint on property change --- src/Mod/TechDraw/App/DrawViewClip.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Mod/TechDraw/App/DrawViewClip.cpp b/src/Mod/TechDraw/App/DrawViewClip.cpp index a7af72171c..1e44c9418e 100644 --- a/src/Mod/TechDraw/App/DrawViewClip.cpp +++ b/src/Mod/TechDraw/App/DrawViewClip.cpp @@ -69,6 +69,12 @@ DrawViewClip::~DrawViewClip() void DrawViewClip::onChanged(const App::Property* prop) { + if ((prop == &Height) || + (prop == &Width) || + (prop == &ShowFrame) || + (prop == &Views)) { + requestPaint(); + } DrawView::onChanged(prop); } From be694170a550e09eac2792562fa66b1508f37c1a Mon Sep 17 00:00:00 2001 From: donovaly Date: Wed, 25 Mar 2020 23:39:32 +0100 Subject: [PATCH 037/117] [TD] Detail view - add missing update - now changes of the BaseView and to the anchor coordinates are directly shown --- src/Mod/TechDraw/App/DrawViewDetail.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewDetail.cpp b/src/Mod/TechDraw/App/DrawViewDetail.cpp index 10951d896a..832da7e7bf 100644 --- a/src/Mod/TechDraw/App/DrawViewDetail.cpp +++ b/src/Mod/TechDraw/App/DrawViewDetail.cpp @@ -147,13 +147,15 @@ void DrawViewDetail::onChanged(const App::Property* prop) std::string(Reference.getValue()); Label.setValue(lblText); } - if ((prop == &Reference) || - (prop == &Radius) || - (prop == &AnchorPoint)) { -// BaseView.getValue()->touch(); //hack. sb "update graphics" - enforceRecompute(); + if ((prop == &Reference) || + (prop == &Radius) || + (prop == &BaseView)) { + requestPaint(); + } + if (prop == &AnchorPoint) { + // to see AnchorPoint changes repainting is not enough, we must recompute + recomputeFeature(true); } - } DrawView::onChanged(prop); } From 8c45bef57c19a2e224444db6ce352eb1b8662488 Mon Sep 17 00:00:00 2001 From: donovaly Date: Thu, 26 Mar 2020 01:40:46 +0100 Subject: [PATCH 038/117] [TD] add missing repaints for DrawView - this enables to see e.g. changes of the rotation of all kinds of views directly - also fix a bug, see https://forum.freecadweb.org/viewtopic.php?f=35&t=44571 --- src/Mod/TechDraw/App/DrawProjGroup.cpp | 10 +++++----- src/Mod/TechDraw/App/DrawView.cpp | 9 +++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawProjGroup.cpp b/src/Mod/TechDraw/App/DrawProjGroup.cpp index 33abf5829a..c63dd05d3c 100644 --- a/src/Mod/TechDraw/App/DrawProjGroup.cpp +++ b/src/Mod/TechDraw/App/DrawProjGroup.cpp @@ -424,12 +424,12 @@ App::DocumentObject * DrawProjGroup::addProjection(const char *viewProjType) throw Base::TypeError("Error: new projection is not a DPGI!"); } if (view != nullptr) { //coverity CID 151722 + // the label must be set before the view is added + view->Label.setValue(viewProjType); addView(view); //from DrawViewCollection - view->Source.setValues( Source.getValues() ); - view->Scale.setValue( getScale() ); - view->Type.setValue( viewProjType ); - view->Label.setValue( viewProjType ); - view->Source.setValues( Source.getValues() ); + view->Source.setValues(Source.getValues()); + // the Scale is already set by DrawView + view->Type.setValue(viewProjType); if (strcmp(viewProjType, "Front") != 0 ) { //not Front! vecs = getDirsFromFront(view); view->Direction.setValue(vecs.first); diff --git a/src/Mod/TechDraw/App/DrawView.cpp b/src/Mod/TechDraw/App/DrawView.cpp index 85f7c47125..8302613477 100644 --- a/src/Mod/TechDraw/App/DrawView.cpp +++ b/src/Mod/TechDraw/App/DrawView.cpp @@ -144,6 +144,15 @@ void DrawView::onChanged(const App::Property* prop) handleXYLock(); LockPosition.purgeTouched(); } + if ((prop == &Caption) || + (prop == &Label)) { + requestPaint(); + } // rotation and scaling requires recompute + else if ((prop == &Rotation) || + (prop == &Scale) || + (prop == &ScaleType)) { + recompute(); + } } App::DocumentObject::onChanged(prop); } From caee1eb48d793d131b1ec9348a5b6f31fdfb9bad Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 26 Mar 2020 15:25:40 +0100 Subject: [PATCH 039/117] [skip ci] make Init scripts working if system locale is set to C --- src/Mod/Import/Init.py | 4 ++-- src/Mod/Import/InitGui.py | 4 ++-- src/Mod/Raytracing/Init.py | 4 ++-- src/Mod/Raytracing/InitGui.py | 4 ++-- src/Mod/ReverseEngineering/Init.py | 4 ++-- src/Mod/ReverseEngineering/InitGui.py | 4 ++-- src/Mod/Robot/Init.py | 4 ++-- src/Mod/Robot/InitGui.py | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Mod/Import/Init.py b/src/Mod/Import/Init.py index 773fd57802..ea54ff6d1c 100644 --- a/src/Mod/Import/Init.py +++ b/src/Mod/Import/Init.py @@ -1,8 +1,8 @@ # FreeCAD init script of the Import module -# (c) 2001 Jürgen Riegel +# (c) 2001 Juergen Riegel #*************************************************************************** -#* Copyright (c) 2002 Jürgen Riegel * +#* Copyright (c) 2002 Juergen Riegel * #* * #* This file is part of the FreeCAD CAx development system. * #* * diff --git a/src/Mod/Import/InitGui.py b/src/Mod/Import/InitGui.py index d61259dec2..046d29709f 100644 --- a/src/Mod/Import/InitGui.py +++ b/src/Mod/Import/InitGui.py @@ -1,13 +1,13 @@ # -*- coding: utf8 -*- # Import gui init module -# (c) 2003 Jürgen Riegel +# (c) 2003 Juergen Riegel # # Gathering all the information to start FreeCAD # This is the second one of three init scripts, the third one # runs when the gui is up #*************************************************************************** -#* Copyright (c) 2002 Jürgen Riegel * +#* Copyright (c) 2002 Juergen Riegel * #* * #* This file is part of the FreeCAD CAx development system. * #* * diff --git a/src/Mod/Raytracing/Init.py b/src/Mod/Raytracing/Init.py index 5cf86075c7..46cc464fd2 100644 --- a/src/Mod/Raytracing/Init.py +++ b/src/Mod/Raytracing/Init.py @@ -1,9 +1,9 @@ # -*- coding: utf8 -*- # FreeCAD init script of the Raytracing module -# (c) 2001 Jürgen Riegel +# (c) 2001 Juergen Riegel #*************************************************************************** -#* Copyright (c) 2002 Jürgen Riegel * +#* Copyright (c) 2002 Juergen Riegel * #* * #* This file is part of the FreeCAD CAx development system. * #* * diff --git a/src/Mod/Raytracing/InitGui.py b/src/Mod/Raytracing/InitGui.py index c3da1efe71..4c527be3cc 100644 --- a/src/Mod/Raytracing/InitGui.py +++ b/src/Mod/Raytracing/InitGui.py @@ -1,12 +1,12 @@ # Raytracing gui init module -# (c) 2003 Jürgen Riegel +# (c) 2003 Juergen Riegel # # Gathering all the information to start FreeCAD # This is the second one of three init scripts, the third one # runs when the gui is up #*************************************************************************** -#* Copyright (c) 2002 Jürgen Riegel * +#* Copyright (c) 2002 Juergen Riegel * #* * #* This file is part of the FreeCAD CAx development system. * #* * diff --git a/src/Mod/ReverseEngineering/Init.py b/src/Mod/ReverseEngineering/Init.py index 3a13579462..042fb264cc 100644 --- a/src/Mod/ReverseEngineering/Init.py +++ b/src/Mod/ReverseEngineering/Init.py @@ -1,9 +1,9 @@ # -*- coding: utf8 -*- # FreeCAD init script of the ReverseEngineering module -# (c) 2001 Jürgen Riegel +# (c) 2001 Juergen Riegel # *************************************************************************** -# * Copyright (c) 2002 Jürgen Riegel * +# * Copyright (c) 2002 Juergen Riegel * # * * # * This file is part of the FreeCAD CAx development system. * # * * diff --git a/src/Mod/ReverseEngineering/InitGui.py b/src/Mod/ReverseEngineering/InitGui.py index d44a981236..e9ace199fd 100644 --- a/src/Mod/ReverseEngineering/InitGui.py +++ b/src/Mod/ReverseEngineering/InitGui.py @@ -1,12 +1,12 @@ # ReverseEngineering gui init module -# (c) 2003 Jürgen Riegel +# (c) 2003 Juergen Riegel # # Gathering all the information to start FreeCAD # This is the second one of three init scripts, the third one # runs when the gui is up #*************************************************************************** -#* Copyright (c) 2002 Jürgen Riegel * +#* Copyright (c) 2002 Juergen Riegel * #* * #* This file is part of the FreeCAD CAx development system. * #* * diff --git a/src/Mod/Robot/Init.py b/src/Mod/Robot/Init.py index 6b33c22f6e..8db77748e5 100644 --- a/src/Mod/Robot/Init.py +++ b/src/Mod/Robot/Init.py @@ -1,8 +1,8 @@ # FreeCAD init script of the Robot module -# (c) 2001 Jürgen Riegel +# (c) 2001 Juergen Riegel #*************************************************************************** -#* Copyright (c) 2002 Jürgen Riegel * +#* Copyright (c) 2002 Juergen Riegel * #* * #* This file is part of the FreeCAD CAx development system. * #* * diff --git a/src/Mod/Robot/InitGui.py b/src/Mod/Robot/InitGui.py index 882430e8f6..c9a0117eca 100644 --- a/src/Mod/Robot/InitGui.py +++ b/src/Mod/Robot/InitGui.py @@ -1,12 +1,12 @@ # Robot gui init module -# (c) 2009 Jürgen Riegel +# (c) 2009 Juergen Riegel # # Gathering all the information to start FreeCAD # This is the second one of three init scripts, the third one # runs when the gui is up #*************************************************************************** -#* Copyright (c) 2009 Jürgen Riegel * +#* Copyright (c) 2009 Juergen Riegel * #* * #* This file is part of the FreeCAD CAx development system. * #* * From cbc38e988ecfc361d4bbba38b16d6ccb709c6f67 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Thu, 26 Mar 2020 10:26:53 -0500 Subject: [PATCH 040/117] Path: Fix failed open edge path for zero GeometryTolerance case Add error message to inform user to set Job.GeometryTolerance to an acceptable value. Remove creation of docObject in lieu of geometry shape usage. --- src/Mod/Path/PathScripts/PathProfileEdges.py | 56 +++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathProfileEdges.py b/src/Mod/Path/PathScripts/PathProfileEdges.py index 5fce9b0774..e3290ccc8c 100644 --- a/src/Mod/Path/PathScripts/PathProfileEdges.py +++ b/src/Mod/Path/PathScripts/PathProfileEdges.py @@ -112,18 +112,23 @@ class ObjectProfile(PathProfileBase.ObjectProfile): else: PathLog.error(translate('PathProfileEdges', 'The selected edge(s) are inaccessible.')) else: - cutWireObjs = False - (origWire, flatWire) = self._flattenWire(obj, wire, obj.FinalDepth.Value) - cutShp = self._getCutAreaCrossSection(obj, base, origWire, flatWire) - if cutShp is not False: - cutWireObjs = self._extractPathWire(obj, base, flatWire, cutShp) - - if cutWireObjs is not False: - for cW in cutWireObjs: - shapes.append((cW, False)) - self.profileEdgesIsOpen = True + if self.JOB.GeometryTolerance.Value == 0.0: + msg = self.JOB.Label + '.GeometryTolerance = 0.0.' + msg += translate('PathProfileEdges', 'Please set to an acceptable value greater than zero.') + PathLog.error(msg) else: - PathLog.error(translate('PathProfileEdges', 'The selected edge(s) are inaccessible.')) + cutWireObjs = False + (origWire, flatWire) = self._flattenWire(obj, wire, obj.FinalDepth.Value) + cutShp = self._getCutAreaCrossSection(obj, base, origWire, flatWire) + if cutShp is not False: + cutWireObjs = self._extractPathWire(obj, base, flatWire, cutShp) + + if cutWireObjs is not False: + for cW in cutWireObjs: + shapes.append((cW, False)) + self.profileEdgesIsOpen = True + else: + PathLog.error(translate('PathProfileEdges', 'The selected edge(s) are inaccessible.')) # Delete the temporary objects if PathLog.getLevel(PathLog.thisModule()) != 4: @@ -179,13 +184,13 @@ class ObjectProfile(PathProfileBase.ObjectProfile): return (OW, FW) + # Open-edges methods def _getCutAreaCrossSection(self, obj, base, origWire, flatWireObj): PathLog.debug('_getCutAreaCrossSection()') tmpGrp = self.tmpGrp FCAD = FreeCAD.ActiveDocument tolerance = self.JOB.GeometryTolerance.Value - # toolDiam = float(obj.ToolController.Tool.Diameter) - toolDiam = 2 * self.radius # self.radius defined in PathAreaOp or PathprofileBase modules + toolDiam = 2 * self.radius # self.radius defined in PathAreaOp or PathProfileBase modules minBfr = toolDiam * 1.25 bbBfr = (self.ofstRadius * 2) * 1.25 if bbBfr < minBfr: @@ -243,34 +248,33 @@ class ObjectProfile(PathProfileBase.ObjectProfile): # Cut model(selected edges) from extended edges boundbox cutArea = extBndboxEXT.Shape.cut(base.Shape) - CA = FCAD.addObject('Part::Feature', 'tmpBndboxCutByBase') - CA.Shape = cutArea - CA.purgeTouched() - tmpGrp.addObject(CA) # Get top and bottom faces of cut area (CA), and combine faces when necessary topFc = list() botFc = list() - bbZMax = CA.Shape.BoundBox.ZMax - bbZMin = CA.Shape.BoundBox.ZMin - for f in range(0, len(CA.Shape.Faces)): - Fc = CA.Shape.Faces[f] - if abs(Fc.BoundBox.ZMax - bbZMax) < tolerance and abs(Fc.BoundBox.ZMin - bbZMax) < tolerance: + bbZMax = cutArea.BoundBox.ZMax + bbZMin = cutArea.BoundBox.ZMin + for f in range(0, len(cutArea.Faces)): + FcBB = cutArea.Faces[f].BoundBox + if abs(FcBB.ZMax - bbZMax) < tolerance and abs(FcBB.ZMin - bbZMax) < tolerance: topFc.append(f) - if abs(Fc.BoundBox.ZMax - bbZMin) < tolerance and abs(Fc.BoundBox.ZMin - bbZMin) < tolerance: + if abs(FcBB.ZMax - bbZMin) < tolerance and abs(FcBB.ZMin - bbZMin) < tolerance: botFc.append(f) - topComp = Part.makeCompound([CA.Shape.Faces[f] for f in topFc]) + if len(topFc) == 0: + PathLog.error('Failed to identify top faces of cut area.') + return False + topComp = Part.makeCompound([cutArea.Faces[f] for f in topFc]) topComp.translate(FreeCAD.Vector(0, 0, fdv - topComp.BoundBox.ZMin)) # Translate face to final depth if len(botFc) > 1: PathLog.debug('len(botFc) > 1') bndboxFace = Part.Face(extBndbox.Shape.Wires[0]) tmpFace = Part.Face(extBndbox.Shape.Wires[0]) for f in botFc: - Q = tmpFace.cut(CA.Shape.Faces[f]) + Q = tmpFace.cut(cutArea.Faces[f]) tmpFace = Q botComp = bndboxFace.cut(tmpFace) else: - botComp = Part.makeCompound([CA.Shape.Faces[f] for f in botFc]) + botComp = Part.makeCompound([cutArea.Faces[f] for f in botFc]) # Part.makeCompound([CA.Shape.Faces[f] for f in botFc]) botComp.translate(FreeCAD.Vector(0, 0, fdv - botComp.BoundBox.ZMin)) # Translate face to final depth # Convert compound shapes to FC objects for use in multicommon operation From ec8b1bc0720f271db1f8795763701f5db6f246cc Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 26 Mar 2020 16:37:45 +0100 Subject: [PATCH 041/117] [skip ci] use QLocale class consistently to make it possible to change it application-wide --- src/Base/UnitsSchema.cpp | 2 +- src/Base/UnitsSchemaCentimeters.cpp | 1 - src/Base/UnitsSchemaImperial1.cpp | 1 - src/Base/UnitsSchemaInternal.cpp | 1 - src/Base/UnitsSchemaMKS.cpp | 1 - src/Base/UnitsSchemaMmMin.cpp | 1 - src/Gui/Application.cpp | 6 +- src/Gui/DlgGeneralImp.cpp | 4 +- src/Gui/DlgSettingsColorGradientImp.cpp | 12 +- src/Gui/DlgUnitsCalculatorImp.cpp | 4 +- src/Gui/propertyeditor/PropertyItem.cpp | 116 ++++++++++-------- src/Mod/Part/Gui/DlgFilletEdges.cpp | 10 +- .../Sketcher/Gui/TaskSketcherValidation.cpp | 3 +- src/Mod/Spreadsheet/App/Cell.cpp | 12 +- src/Mod/Spreadsheet/Gui/SheetModel.cpp | 8 +- src/Mod/TechDraw/Gui/TaskProjGroup.cpp | 6 +- 16 files changed, 97 insertions(+), 91 deletions(-) diff --git a/src/Base/UnitsSchema.cpp b/src/Base/UnitsSchema.cpp index 4cffebcaf4..512fda4ee6 100644 --- a/src/Base/UnitsSchema.cpp +++ b/src/Base/UnitsSchema.cpp @@ -38,7 +38,7 @@ using namespace Base; QString UnitsSchema::toLocale(const Base::Quantity& quant, double factor, const QString& unitString) const { //return QString::fromUtf8("%L1 %2").arg(quant.getValue() / factor).arg(unitString); - QLocale Lc = QLocale::system(); + QLocale Lc; const QuantityFormat& format = quant.getFormat(); if (format.option != QuantityFormat::None) { uint opt = static_cast(format.option); diff --git a/src/Base/UnitsSchemaCentimeters.cpp b/src/Base/UnitsSchemaCentimeters.cpp index 205108da8d..cd38607137 100644 --- a/src/Base/UnitsSchemaCentimeters.cpp +++ b/src/Base/UnitsSchemaCentimeters.cpp @@ -27,7 +27,6 @@ #endif #include -#include #include "Exception.h" #include "UnitsApi.h" #include "UnitsSchemaCentimeters.h" diff --git a/src/Base/UnitsSchemaImperial1.cpp b/src/Base/UnitsSchemaImperial1.cpp index eca7846b0e..bffe6067b3 100644 --- a/src/Base/UnitsSchemaImperial1.cpp +++ b/src/Base/UnitsSchemaImperial1.cpp @@ -30,7 +30,6 @@ #endif #include -#include #include "Console.h" #include "Exception.h" #include "UnitsApi.h" diff --git a/src/Base/UnitsSchemaInternal.cpp b/src/Base/UnitsSchemaInternal.cpp index 1f0149f9a7..6340abfb68 100644 --- a/src/Base/UnitsSchemaInternal.cpp +++ b/src/Base/UnitsSchemaInternal.cpp @@ -27,7 +27,6 @@ #endif #include -#include #include "Exception.h" #include "UnitsApi.h" #include "UnitsSchemaInternal.h" diff --git a/src/Base/UnitsSchemaMKS.cpp b/src/Base/UnitsSchemaMKS.cpp index 8b505381cf..b01c86c3a7 100644 --- a/src/Base/UnitsSchemaMKS.cpp +++ b/src/Base/UnitsSchemaMKS.cpp @@ -27,7 +27,6 @@ #endif #include -#include #include "Exception.h" #include "UnitsApi.h" #include "UnitsSchemaMKS.h" diff --git a/src/Base/UnitsSchemaMmMin.cpp b/src/Base/UnitsSchemaMmMin.cpp index e62c89430b..2b5860b427 100644 --- a/src/Base/UnitsSchemaMmMin.cpp +++ b/src/Base/UnitsSchemaMmMin.cpp @@ -27,7 +27,6 @@ #endif #include -#include #include "Exception.h" #include "UnitsApi.h" #include "UnitsSchemaMmMin.h" diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 2da5437b61..eddfa41b0c 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -305,7 +305,7 @@ Application::Application(bool GUIenabled) // install the last active language ParameterGrp::handle hPGrp = App::GetApplication().GetUserParameter().GetGroup("BaseApp"); hPGrp = hPGrp->GetGroup("Preferences")->GetGroup("General"); - QString lang = QLocale::languageToString(QLocale::system().language()); + QString lang = QLocale::languageToString(QLocale().language()); Translator::instance()->activateLanguage(hPGrp->GetASCII("Language", (const char*)lang.toLatin1()).c_str()); GetWidgetFactorySupplier(); @@ -319,7 +319,7 @@ Application::Application(bool GUIenabled) // Check for the symbols for group separator and decimal point. They must be different otherwise // Qt doesn't work properly. #if defined(Q_OS_WIN32) - if (QLocale::system().groupSeparator() == QLocale::system().decimalPoint()) { + if (QLocale().groupSeparator() == QLocale().decimalPoint()) { QMessageBox::critical(0, QLatin1String("Invalid system settings"), QLatin1String("Your system uses the same symbol for decimal point and group separator.\n\n" "This causes serious problems and makes the application fail to work properly.\n" @@ -331,7 +331,7 @@ Application::Application(bool GUIenabled) // http://forum.freecadweb.org/viewtopic.php?f=10&t=6910 // A workaround is to disable the group separator for double-to-string conversion, i.e. // setting the flag 'OmitGroupSeparator'. - QLocale loc = QLocale::system(); + QLocale loc; loc.setNumberOptions(QLocale::OmitGroupSeparator); QLocale::setDefault(loc); #endif diff --git a/src/Gui/DlgGeneralImp.cpp b/src/Gui/DlgGeneralImp.cpp index d30ccd9af6..d821de9774 100644 --- a/src/Gui/DlgGeneralImp.cpp +++ b/src/Gui/DlgGeneralImp.cpp @@ -131,7 +131,7 @@ void DlgGeneralImp::saveSettings() setRecentFileSize(); ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("General"); - QString lang = QLocale::languageToString(QLocale::system().language()); + QString lang = QLocale::languageToString(QLocale().language()); QByteArray language = hGrp->GetASCII("Language", (const char*)lang.toLatin1()).c_str(); QByteArray current = ui->Languages->itemData(ui->Languages->currentIndex()).toByteArray(); if (current != language) { @@ -182,7 +182,7 @@ void DlgGeneralImp::loadSettings() // search for the language files ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("General"); - QString langToStr = QLocale::languageToString(QLocale::system().language()); + QString langToStr = QLocale::languageToString(QLocale().language()); QByteArray language = hGrp->GetASCII("Language", langToStr.toLatin1()).c_str(); int index = 1; diff --git a/src/Gui/DlgSettingsColorGradientImp.cpp b/src/Gui/DlgSettingsColorGradientImp.cpp index dc5197364e..f0bdcaad35 100644 --- a/src/Gui/DlgSettingsColorGradientImp.cpp +++ b/src/Gui/DlgSettingsColorGradientImp.cpp @@ -136,17 +136,17 @@ bool DlgSettingsColorGradientImp::isOutInvisible() const void DlgSettingsColorGradientImp::setRange( float fMin, float fMax ) { ui->floatLineEditMax->blockSignals(true); - ui->floatLineEditMax->setText(QLocale::system().toString(fMax, 'f', numberOfDecimals())); + ui->floatLineEditMax->setText(QLocale().toString(fMax, 'f', numberOfDecimals())); ui->floatLineEditMax->blockSignals(false); ui->floatLineEditMin->blockSignals(true); - ui->floatLineEditMin->setText(QLocale::system().toString(fMin, 'f', numberOfDecimals())); + ui->floatLineEditMin->setText(QLocale().toString(fMin, 'f', numberOfDecimals())); ui->floatLineEditMin->blockSignals(false); } void DlgSettingsColorGradientImp::getRange(float& fMin, float& fMax) const { - fMax = QLocale::system().toFloat(ui->floatLineEditMax->text()); - fMin = QLocale::system().toFloat(ui->floatLineEditMin->text()); + fMax = QLocale().toFloat(ui->floatLineEditMax->text()); + fMin = QLocale().toFloat(ui->floatLineEditMin->text()); } void DlgSettingsColorGradientImp::setNumberOfLabels(int val) @@ -171,8 +171,8 @@ int DlgSettingsColorGradientImp::numberOfDecimals() const void DlgSettingsColorGradientImp::accept() { - double fMax = QLocale::system().toDouble(ui->floatLineEditMax->text()); - double fMin = QLocale::system().toDouble(ui->floatLineEditMin->text()); + double fMax = QLocale().toDouble(ui->floatLineEditMax->text()); + double fMin = QLocale().toDouble(ui->floatLineEditMin->text()); if (fMax <= fMin) { QMessageBox::warning(this, tr("Wrong parameter"), diff --git a/src/Gui/DlgUnitsCalculatorImp.cpp b/src/Gui/DlgUnitsCalculatorImp.cpp index 0e3fa14999..78e034d216 100644 --- a/src/Gui/DlgUnitsCalculatorImp.cpp +++ b/src/Gui/DlgUnitsCalculatorImp.cpp @@ -159,9 +159,9 @@ void DlgUnitsCalculator::valueChanged(const Base::Quantity& quant) // at first use scientific notation, if there is no "e", we can round it to the user-defined decimals, // but the user-defined decimals might be too low for cases like "10 um" in "in", // thus only if value > 0.005 because FC's default are 2 decimals - QString val = QLocale::system().toString(value, 'g'); + QString val = QLocale().toString(value, 'g'); if (!val.contains(QChar::fromLatin1('e')) && (value > 0.005)) - val = QLocale::system().toString(value, 'f', Base::UnitsApi::getDecimals()); + val = QLocale().toString(value, 'f', Base::UnitsApi::getDecimals()); // create the output string QString out = QString::fromLatin1("%1 %2").arg(val, ui->UnitInput->text()); ui->ValueOutput->setText(out); diff --git a/src/Gui/propertyeditor/PropertyItem.cpp b/src/Gui/propertyeditor/PropertyItem.cpp index 334aeccd32..0351c919c9 100644 --- a/src/Gui/propertyeditor/PropertyItem.cpp +++ b/src/Gui/propertyeditor/PropertyItem.cpp @@ -937,7 +937,7 @@ PropertyFloatItem::PropertyFloatItem() QVariant PropertyFloatItem::toString(const QVariant& prop) const { double value = prop.toDouble(); - QString data = QLocale::system().toString(value, 'f', decimals()); + QString data = QLocale().toString(value, 'f', decimals()); if (hasExpression()) data += QString::fromLatin1(" ( %1 )").arg(QString::fromStdString(getExpressionString())); @@ -1116,7 +1116,7 @@ PropertyFloatConstraintItem::PropertyFloatConstraintItem() QVariant PropertyFloatConstraintItem::toString(const QVariant& prop) const { double value = prop.toDouble(); - QString data = QLocale::system().toString(value, 'f', decimals()); + QString data = QLocale().toString(value, 'f', decimals()); return QVariant(data); } @@ -1317,11 +1317,12 @@ PropertyVectorItem::PropertyVectorItem() QVariant PropertyVectorItem::toString(const QVariant& prop) const { + QLocale loc; const Base::Vector3d& value = prop.value(); QString data = QString::fromLatin1("[%1 %2 %3]") - .arg(QLocale::system().toString(value.x, 'f', 2), - QLocale::system().toString(value.y, 'f', 2), - QLocale::system().toString(value.z, 'f', 2)); + .arg(loc.toString(value.x, 'f', 2), + loc.toString(value.y, 'f', 2), + loc.toString(value.z, 'f', 2)); if (hasExpression()) data += QString::fromLatin1(" ( %1 )").arg(QString::fromStdString(getExpressionString())); return QVariant(data); @@ -1363,12 +1364,13 @@ QWidget* PropertyVectorItem::createEditor(QWidget* parent, const QObject* /*rece void PropertyVectorItem::setEditorData(QWidget *editor, const QVariant& data) const { + QLocale loc; QLineEdit* le = qobject_cast(editor); const Base::Vector3d& value = data.value(); QString text = QString::fromLatin1("[%1 %2 %3]") - .arg(QLocale::system().toString(value.x, 'f', 2), - QLocale::system().toString(value.y, 'f', 2), - QLocale::system().toString(value.z, 'f', 2)); + .arg(loc.toString(value.x, 'f', 2), + loc.toString(value.y, 'f', 2), + loc.toString(value.z, 'f', 2)); le->setProperty("coords", data); le->setText(text); } @@ -1634,24 +1636,25 @@ PropertyMatrixItem::PropertyMatrixItem() QVariant PropertyMatrixItem::toString(const QVariant& prop) const { + QLocale loc; const Base::Matrix4D& value = prop.value(); QString text = QString::fromLatin1("[%1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15 %16]") - .arg(QLocale::system().toString(value[0][0], 'f', 2), //(unsigned short usNdx) - QLocale::system().toString(value[0][1], 'f', 2), - QLocale::system().toString(value[0][2], 'f', 2), - QLocale::system().toString(value[0][3], 'f', 2), - QLocale::system().toString(value[1][0], 'f', 2), - QLocale::system().toString(value[1][1], 'f', 2), - QLocale::system().toString(value[1][2], 'f', 2), - QLocale::system().toString(value[1][3], 'f', 2), - QLocale::system().toString(value[2][0], 'f', 2)) - .arg(QLocale::system().toString(value[2][1], 'f', 2), - QLocale::system().toString(value[2][2], 'f', 2), - QLocale::system().toString(value[2][3], 'f', 2), - QLocale::system().toString(value[3][0], 'f', 2), - QLocale::system().toString(value[3][1], 'f', 2), - QLocale::system().toString(value[3][2], 'f', 2), - QLocale::system().toString(value[3][3], 'f', 2)); + .arg(loc.toString(value[0][0], 'f', 2), //(unsigned short usNdx) + loc.toString(value[0][1], 'f', 2), + loc.toString(value[0][2], 'f', 2), + loc.toString(value[0][3], 'f', 2), + loc.toString(value[1][0], 'f', 2), + loc.toString(value[1][1], 'f', 2), + loc.toString(value[1][2], 'f', 2), + loc.toString(value[1][3], 'f', 2), + loc.toString(value[2][0], 'f', 2)) + .arg(loc.toString(value[2][1], 'f', 2), + loc.toString(value[2][2], 'f', 2), + loc.toString(value[2][3], 'f', 2), + loc.toString(value[3][0], 'f', 2), + loc.toString(value[3][1], 'f', 2), + loc.toString(value[3][2], 'f', 2), + loc.toString(value[3][3], 'f', 2)); return QVariant(text); } @@ -1707,25 +1710,26 @@ QWidget* PropertyMatrixItem::createEditor(QWidget* parent, const QObject* /*rece void PropertyMatrixItem::setEditorData(QWidget *editor, const QVariant& data) const { + QLocale loc; QLineEdit* le = qobject_cast(editor); const Base::Matrix4D& value = data.value(); QString text = QString::fromLatin1("[%1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15 %16]") - .arg(QLocale::system().toString(value[0][0], 'f', 2), //(unsigned short usNdx) - QLocale::system().toString(value[0][1], 'f', 2), - QLocale::system().toString(value[0][2], 'f', 2), - QLocale::system().toString(value[0][3], 'f', 2), - QLocale::system().toString(value[1][0], 'f', 2), - QLocale::system().toString(value[1][1], 'f', 2), - QLocale::system().toString(value[1][2], 'f', 2), - QLocale::system().toString(value[1][3], 'f', 2), - QLocale::system().toString(value[2][0], 'f', 2)) - .arg(QLocale::system().toString(value[2][1], 'f', 2), - QLocale::system().toString(value[2][2], 'f', 2), - QLocale::system().toString(value[2][3], 'f', 2), - QLocale::system().toString(value[3][0], 'f', 2), - QLocale::system().toString(value[3][1], 'f', 2), - QLocale::system().toString(value[3][2], 'f', 2), - QLocale::system().toString(value[3][3], 'f', 2)); + .arg(loc.toString(value[0][0], 'f', 2), //(unsigned short usNdx) + loc.toString(value[0][1], 'f', 2), + loc.toString(value[0][2], 'f', 2), + loc.toString(value[0][3], 'f', 2), + loc.toString(value[1][0], 'f', 2), + loc.toString(value[1][1], 'f', 2), + loc.toString(value[1][2], 'f', 2), + loc.toString(value[1][3], 'f', 2), + loc.toString(value[2][0], 'f', 2)) + .arg(loc.toString(value[2][1], 'f', 2), + loc.toString(value[2][2], 'f', 2), + loc.toString(value[2][3], 'f', 2), + loc.toString(value[3][0], 'f', 2), + loc.toString(value[3][1], 'f', 2), + loc.toString(value[3][2], 'f', 2), + loc.toString(value[3][3], 'f', 2)); le->setText(text); } @@ -1939,14 +1943,16 @@ void PlacementEditor::showValue(const QVariant& d) p.getRotation().getRawValue(dir, angle); angle = Base::toDegrees(angle); pos = p.getPosition(); + + QLocale loc; QString data = QString::fromUtf8("[(%1 %2 %3);%4 \xc2\xb0;(%5 %6 %7)]") - .arg(QLocale::system().toString(dir.x,'f',2), - QLocale::system().toString(dir.y,'f',2), - QLocale::system().toString(dir.z,'f',2), - QLocale::system().toString(angle,'f',2), - QLocale::system().toString(pos.x,'f',2), - QLocale::system().toString(pos.y,'f',2), - QLocale::system().toString(pos.z,'f',2)); + .arg(loc.toString(dir.x,'f',2), + loc.toString(dir.y,'f',2), + loc.toString(dir.z,'f',2), + loc.toString(angle,'f',2), + loc.toString(pos.x,'f',2), + loc.toString(pos.y,'f',2), + loc.toString(pos.z,'f',2)); getLabel()->setText(data); } @@ -2138,12 +2144,14 @@ QVariant PropertyPlacementItem::toolTip(const App::Property* prop) const p.getRotation().getRawValue(dir, angle); angle = Base::toDegrees(angle); pos = p.getPosition(); + + QLocale loc; QString data = QString::fromUtf8("Axis: (%1 %2 %3)\n" "Angle: %4\n" "Position: (%5 %6 %7)") - .arg(QLocale::system().toString(dir.x,'f',decimals()), - QLocale::system().toString(dir.y,'f',decimals()), - QLocale::system().toString(dir.z,'f',decimals()), + .arg(loc.toString(dir.x,'f',decimals()), + loc.toString(dir.y,'f',decimals()), + loc.toString(dir.z,'f',decimals()), Base::Quantity(angle, Base::Unit::Angle).getUserString(), Base::Quantity(pos.x, Base::Unit::Length).getUserString(), Base::Quantity(pos.y, Base::Unit::Length).getUserString(), @@ -2159,10 +2167,12 @@ QVariant PropertyPlacementItem::toString(const QVariant& prop) const p.getRotation().getRawValue(dir, angle); angle = Base::toDegrees(angle); pos = p.getPosition(); + + QLocale loc; QString data = QString::fromUtf8("[(%1 %2 %3); %4; (%5 %6 %7)]") - .arg(QLocale::system().toString(dir.x,'f',2), - QLocale::system().toString(dir.y,'f',2), - QLocale::system().toString(dir.z,'f',2), + .arg(loc.toString(dir.x,'f',2), + loc.toString(dir.y,'f',2), + loc.toString(dir.z,'f',2), Base::Quantity(angle, Base::Unit::Angle).getUserString(), Base::Quantity(pos.x, Base::Unit::Length).getUserString(), Base::Quantity(pos.y, Base::Unit::Length).getUserString(), diff --git a/src/Mod/Part/Gui/DlgFilletEdges.cpp b/src/Mod/Part/Gui/DlgFilletEdges.cpp index be2ba625a7..766d798d18 100644 --- a/src/Mod/Part/Gui/DlgFilletEdges.cpp +++ b/src/Mod/Part/Gui/DlgFilletEdges.cpp @@ -110,7 +110,7 @@ void FilletRadiusDelegate::setModelData(QWidget *editor, QAbstractItemModel *mod spinBox->interpretText(); //double value = spinBox->value(); //QString value = QString::fromLatin1("%1").arg(spinBox->value(),0,'f',2); - //QString value = QLocale::system().toString(spinBox->value().getValue(),'f',Base::UnitsApi::getDecimals()); + //QString value = QLocale().toString(spinBox->value().getValue(),'f',Base::UnitsApi::getDecimals()); Base::Quantity value = spinBox->value(); model->setData(index, QVariant::fromValue(value), Qt::EditRole); @@ -594,8 +594,8 @@ void DlgFilletEdges::setupFillet(const std::vector& objs) if (it != d->edge_ids.end()) { int index = it - d->edge_ids.begin(); model->setData(model->index(index, 0), Qt::Checked, Qt::CheckStateRole); - //model->setData(model->index(index, 1), QVariant(QLocale::system().toString(et->radius1,'f',Base::UnitsApi::getDecimals()))); - //model->setData(model->index(index, 2), QVariant(QLocale::system().toString(et->radius2,'f',Base::UnitsApi::getDecimals()))); + //model->setData(model->index(index, 1), QVariant(QLocale().toString(et->radius1,'f',Base::UnitsApi::getDecimals()))); + //model->setData(model->index(index, 2), QVariant(QLocale().toString(et->radius2,'f',Base::UnitsApi::getDecimals()))); model->setData(model->index(index, 1), QVariant::fromValue(Base::Quantity(et->radius1, Base::Unit::Length))); model->setData(model->index(index, 2), QVariant::fromValue(Base::Quantity(et->radius2, Base::Unit::Length))); @@ -751,8 +751,8 @@ void DlgFilletEdges::on_shapeObject_activated(int index) for (std::vector::iterator it = d->edge_ids.begin(); it != d->edge_ids.end(); ++it) { model->setData(model->index(index, 0), QVariant(tr("Edge%1").arg(*it))); model->setData(model->index(index, 0), QVariant(*it), Qt::UserRole); - //model->setData(model->index(index, 1), QVariant(QLocale::system().toString(1.0,'f',Base::UnitsApi::getDecimals()))); - //model->setData(model->index(index, 2), QVariant(QLocale::system().toString(1.0,'f',Base::UnitsApi::getDecimals()))); + //model->setData(model->index(index, 1), QVariant(QLocale().toString(1.0,'f',Base::UnitsApi::getDecimals()))); + //model->setData(model->index(index, 2), QVariant(QLocale().toString(1.0,'f',Base::UnitsApi::getDecimals()))); model->setData(model->index(index, 1), QVariant::fromValue(Base::Quantity(1.0,Base::Unit::Length))); model->setData(model->index(index, 2), QVariant::fromValue(Base::Quantity(1.0,Base::Unit::Length))); std::stringstream element; diff --git a/src/Mod/Sketcher/Gui/TaskSketcherValidation.cpp b/src/Mod/Sketcher/Gui/TaskSketcherValidation.cpp index 7fc540ddac..b2d4f853ee 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherValidation.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherValidation.cpp @@ -82,8 +82,9 @@ SketcherValidation::SketcherValidation(Sketcher::SketchObject* Obj, QWidget* par Precision::Confusion() * 100000 }; + QLocale loc; for (int i=0; i<8; i++) { - ui->comboBoxTolerance->addItem(QLocale::system().toString(tolerances[i]), QVariant(tolerances[i])); + ui->comboBoxTolerance->addItem(loc.toString(tolerances[i]), QVariant(tolerances[i])); } ui->comboBoxTolerance->setCurrentIndex(5); ui->comboBoxTolerance->setEditable(true); diff --git a/src/Mod/Spreadsheet/App/Cell.cpp b/src/Mod/Spreadsheet/App/Cell.cpp index 0c7063780a..008bb02bdc 100644 --- a/src/Mod/Spreadsheet/App/Cell.cpp +++ b/src/Mod/Spreadsheet/App/Cell.cpp @@ -983,11 +983,11 @@ std::string Cell::getFormattedQuantity(void) bool hasDisplayUnit = getDisplayUnit(du); double duScale = du.scaler; const Base::Unit& computedUnit = floatProp->getUnit(); - qFormatted = QLocale::system().toString(rawVal,'f',Base::UnitsApi::getDecimals()); + qFormatted = QLocale().toString(rawVal,'f',Base::UnitsApi::getDecimals()); if (hasDisplayUnit) { if (computedUnit.isEmpty() || computedUnit == du.unit) { QString number = - QLocale::system().toString(rawVal / duScale,'f',Base::UnitsApi::getDecimals()); + QLocale().toString(rawVal / duScale,'f',Base::UnitsApi::getDecimals()); qFormatted = number + Base::Tools::fromStdString(" " + displayUnit.stringRep); } } @@ -997,9 +997,9 @@ std::string Cell::getFormattedQuantity(void) DisplayUnit du; bool hasDisplayUnit = getDisplayUnit(du); double duScale = du.scaler; - qFormatted = QLocale::system().toString(rawVal,'f',Base::UnitsApi::getDecimals()); + qFormatted = QLocale().toString(rawVal,'f',Base::UnitsApi::getDecimals()); if (hasDisplayUnit) { - QString number = QLocale::system().toString(rawVal / duScale, 'f',Base::UnitsApi::getDecimals()); + QString number = QLocale().toString(rawVal / duScale, 'f',Base::UnitsApi::getDecimals()); qFormatted = number + Base::Tools::fromStdString(" " + displayUnit.stringRep); } } else if (prop->isDerivedFrom(App::PropertyInteger::getClassTypeId())) { @@ -1008,9 +1008,9 @@ std::string Cell::getFormattedQuantity(void) bool hasDisplayUnit = getDisplayUnit(du); double duScale = du.scaler; int iRawVal = std::round(rawVal); - qFormatted = QLocale::system().toString(iRawVal); + qFormatted = QLocale().toString(iRawVal); if (hasDisplayUnit) { - QString number = QLocale::system().toString(rawVal / duScale, 'f',Base::UnitsApi::getDecimals()); + QString number = QLocale().toString(rawVal / duScale, 'f',Base::UnitsApi::getDecimals()); qFormatted = number + Base::Tools::fromStdString(" " + displayUnit.stringRep); } } diff --git a/src/Mod/Spreadsheet/Gui/SheetModel.cpp b/src/Mod/Spreadsheet/Gui/SheetModel.cpp index 2023556f52..a95b430cdd 100644 --- a/src/Mod/Spreadsheet/Gui/SheetModel.cpp +++ b/src/Mod/Spreadsheet/Gui/SheetModel.cpp @@ -361,7 +361,7 @@ QVariant SheetModel::data(const QModelIndex &index, int role) const // Display locale specific decimal separator (#0003875,#0003876) if (cell->getDisplayUnit(displayUnit)) { if (computedUnit.isEmpty() || computedUnit == displayUnit.unit) { - QString number = QLocale::system().toString(floatProp->getValue() / displayUnit.scaler,'f',Base::UnitsApi::getDecimals()); + QString number = QLocale().toString(floatProp->getValue() / displayUnit.scaler,'f',Base::UnitsApi::getDecimals()); //QString number = QString::number(floatProp->getValue() / displayUnit.scaler); v = number + Base::Tools::fromStdString(" " + displayUnit.stringRep); } @@ -370,7 +370,7 @@ QVariant SheetModel::data(const QModelIndex &index, int role) const } } else { - QString number = QLocale::system().toString(floatProp->getValue(),'f',Base::UnitsApi::getDecimals()); + QString number = QLocale().toString(floatProp->getValue(),'f',Base::UnitsApi::getDecimals()); //QString number = QString::number(floatProp->getValue()); if (!computedUnit.isEmpty()) v = number + Base::Tools::fromStdString(" " + getUnitString(computedUnit)); @@ -424,12 +424,12 @@ QVariant SheetModel::data(const QModelIndex &index, int role) const // Display locale specific decimal separator (#0003875,#0003876) if (cell->getDisplayUnit(displayUnit)) { - QString number = QLocale::system().toString(d / displayUnit.scaler,'f',Base::UnitsApi::getDecimals()); + QString number = QLocale().toString(d / displayUnit.scaler,'f',Base::UnitsApi::getDecimals()); //QString number = QString::number(d / displayUnit.scaler); v = number + Base::Tools::fromStdString(" " + displayUnit.stringRep); } else { - v = QLocale::system().toString(d,'f',Base::UnitsApi::getDecimals()); + v = QLocale().toString(d,'f',Base::UnitsApi::getDecimals()); //v = QString::number(d); } return QVariant(v); diff --git a/src/Mod/TechDraw/Gui/TaskProjGroup.cpp b/src/Mod/TechDraw/Gui/TaskProjGroup.cpp index d453d90727..53240341d1 100644 --- a/src/Mod/TechDraw/Gui/TaskProjGroup.cpp +++ b/src/Mod/TechDraw/Gui/TaskProjGroup.cpp @@ -468,9 +468,9 @@ void TaskProjGroup::setUiPrimary() QString TaskProjGroup::formatVector(Base::Vector3d v) { QString data = QString::fromLatin1("[%1 %2 %3]") - .arg(QLocale::system().toString(v.x, 'f', 2)) - .arg(QLocale::system().toString(v.y, 'f', 2)) - .arg(QLocale::system().toString(v.z, 'f', 2)); + .arg(QLocale().toString(v.x, 'f', 2)) + .arg(QLocale().toString(v.y, 'f', 2)) + .arg(QLocale().toString(v.z, 'f', 2)); return data; } From 60329f838931013951ae55c6fd8c3d4e97089c6b Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 21 Mar 2020 15:37:54 -0500 Subject: [PATCH 042/117] Path: Deleting blank indents --- src/Mod/Path/PathScripts/PathMillFace.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathMillFace.py b/src/Mod/Path/PathScripts/PathMillFace.py index 9fa300ef71..ed1fcfcbb0 100644 --- a/src/Mod/Path/PathScripts/PathMillFace.py +++ b/src/Mod/Path/PathScripts/PathMillFace.py @@ -99,7 +99,7 @@ class ObjectFace(PathPocketBase.ObjectPocket): '''areaOpShapes(obj) ... return top face''' # Facing is done either against base objects holeShape = None - + if obj.Base: PathLog.debug("obj.Base: {}".format(obj.Base)) faces = [] @@ -147,17 +147,17 @@ class ObjectFace(PathPocketBase.ObjectPocket): # Find the correct shape depending on Boundary shape. PathLog.debug("Boundary Shape: {}".format(obj.BoundaryShape)) bb = planeshape.BoundBox - + # Apply offset for clearing edges offset = 0; if obj.ClearEdges == True: offset = self.radius + 0.1 - + bb.XMin = bb.XMin - offset bb.YMin = bb.YMin - offset bb.XMax = bb.XMax + offset bb.YMax = bb.YMax + offset - + if obj.BoundaryShape == 'Boundbox': bbperim = Part.makeBox(bb.XLength, bb.YLength, 1, FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), FreeCAD.Vector(0, 0, 1)) env = PathUtils.getEnvelope(partshape=bbperim, depthparams=self.depthparams) @@ -170,7 +170,7 @@ class ObjectFace(PathPocketBase.ObjectPocket): elif obj.BoundaryShape == 'Stock': stock = PathUtils.findParentJob(obj).Stock.Shape env = stock - + if obj.ExcludeRaisedAreas is True and oneBase[1] is True: includedFaces = self.getAllIncludedFaces(oneBase[0], stock, faceZ=minHeight) if len(includedFaces) > 0: @@ -269,7 +269,6 @@ def SetupProperties(): setup.append("BoundaryShape") setup.append("ExcludeRaisedAreas") setup.append("ClearEdges") - return setup @@ -278,5 +277,4 @@ def Create(name, obj=None): if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) obj.Proxy = ObjectFace(obj, name) - return obj From 0564739c5f1be0e7ec48cdc2183439b3242b1b69 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 21 Mar 2020 15:46:42 -0500 Subject: [PATCH 043/117] Path: Improve 4th-axis rotation analysis and application Fix incorrect depth calculations for envelopes. Added solid-based model orientation check developed in PathProfileEdges open edges upgrade. Clean-up some blank indentations. Down-grade some messaging levels. Add a few debug comments. --- src/Mod/Path/PathScripts/PathPocketShape.py | 39 +++++++---- src/Mod/Path/PathScripts/PathProfileFaces.py | 71 +++++++++++++------- 2 files changed, 74 insertions(+), 36 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPocketShape.py b/src/Mod/Path/PathScripts/PathPocketShape.py index 0271809bb5..56e238b007 100644 --- a/src/Mod/Path/PathScripts/PathPocketShape.py +++ b/src/Mod/Path/PathScripts/PathPocketShape.py @@ -42,7 +42,7 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" __doc__ = "Class and implementation of shape based Pocket operation." -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # PathLog.trackModule(PathLog.thisModule()) @@ -435,7 +435,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): if obj.Base: PathLog.debug('Processing... obj.Base') self.removalshapes = [] # pylint: disable=attribute-defined-outside-init - # ---------------------------------------------------------------------- + if obj.EnableRotation == 'Off': stock = PathUtils.findParentJob(obj).Stock for (base, subList) in obj.Base: @@ -450,11 +450,11 @@ class ObjectPocket(PathPocketBase.ObjectPocket): (isLoop, norm, surf) = self.checkForFacesLoop(base, subsList) if isLoop is True: - PathLog.info("Common Surface.Axis or normalAt() value found for loop faces.") + PathLog.debug("Common Surface.Axis or normalAt() value found for loop faces.") rtn = False subCount += 1 (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable - PathLog.info("angle: {}; axis: {}".format(angle, axis)) + PathLog.debug("angle: {}; axis: {}".format(angle, axis)) if rtn is True: faceNums = "" @@ -471,13 +471,15 @@ class ObjectPocket(PathPocketBase.ObjectPocket): rtn = False PathLog.warning(translate("PathPocketShape", "Face appears to NOT be horizontal AFTER rotation applied.")) break + if rtn is False: + PathLog.debug(translate("Path", "Face appears misaligned after initial rotation.")) if obj.InverseAngle is False: if obj.AttemptInverseAngle is True: - PathLog.debug("Applying the inverse angle.") (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle) else: - PathLog.warning(translate("Path", "Consider toggling the InverseAngle property and recomputing the operation.")) + msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") + PathLog.warning(msg) if angle < -180.0: angle += 360.0 @@ -518,6 +520,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): (norm, surf) = self.getFaceNormAndSurf(face) (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable + PathLog.debug("initial {}".format(praInfo)) if rtn is True: faceNum = sub.replace('Face', '') @@ -525,20 +528,31 @@ class ObjectPocket(PathPocketBase.ObjectPocket): # Verify faces are correctly oriented - InverseAngle might be necessary faceIA = clnBase.Shape.getElement(sub) (norm, surf) = self.getFaceNormAndSurf(faceIA) - (rtn, praAngle, praAxis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable + (rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable + PathLog.debug("follow-up {}".format(praInfo2)) + + if praAxis == axis and abs(praAngle) == 180.0: + rtn = False + if self.isFaceUp(clnBase, faceIA) is False: + PathLog.debug('isFaceUp is False') + angle -= 180.0 if rtn is True: - PathLog.debug("Face not aligned after initial rotation.") + PathLog.debug(translate("Path", "Face appears misaligned after initial rotation.")) if obj.InverseAngle is False: if obj.AttemptInverseAngle is True: - PathLog.debug("Applying the inverse angle.") (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle) else: - PathLog.warning(translate("Path", "Consider toggling the InverseAngle property and recomputing the operation.")) + msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") + PathLog.warning(msg) + + if self.isFaceUp(clnBase, faceIA) is False: + PathLog.debug('isFaceUp is False') + angle += 180.0 else: PathLog.debug("Face appears to be oriented correctly.") - if angle < -180.0: + if angle < 0.0: angle += 360.0 tup = clnBase, [sub], angle, axis, clnStock @@ -650,8 +664,9 @@ class ObjectPocket(PathPocketBase.ObjectPocket): if shpZMin > obj.FinalDepth.Value: afD = shpZMin if sD <= afD: - PathLog.error('Start Depth is lower than face depth.') sD = afD + 1.0 + msg = translate('PathPocketShape', 'Start Depth is lower than face depth. Setting to ') + PathLog.warning(msg + ' {} mm.'.format(sD)) else: face.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - shpZMin)) diff --git a/src/Mod/Path/PathScripts/PathProfileFaces.py b/src/Mod/Path/PathScripts/PathProfileFaces.py index 506d5a1d00..68fa413cd5 100644 --- a/src/Mod/Path/PathScripts/PathProfileFaces.py +++ b/src/Mod/Path/PathScripts/PathProfileFaces.py @@ -43,6 +43,7 @@ __doc__ = "Path Profile operation based on faces." PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) @@ -132,22 +133,43 @@ class ObjectProfile(PathProfileBase.ObjectProfile): rtn = False (norm, surf) = self.getFaceNormAndSurf(shape) (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable + PathLog.debug("initial faceRotationAnalysis: {}".format(praInfo)) if rtn is True: (clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, subCount) # Verify faces are correctly oriented - InverseAngle might be necessary faceIA = getattr(clnBase.Shape, sub) (norm, surf) = self.getFaceNormAndSurf(faceIA) - (rtn, praAngle, praAxis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable + (rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable + PathLog.debug("follow-up faceRotationAnalysis: {}".format(praInfo2)) + + if praAxis == axis and abs(praAngle) == 180.0: + rtn = False + if self.isFaceUp(clnBase, faceIA) is False: + PathLog.debug('isFaceUp is False') + angle -= 180.0 + if rtn is True: - PathLog.error(translate("Path", "Face appears misaligned after initial rotation.")) - if obj.AttemptInverseAngle is True and obj.InverseAngle is False: - (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle) + PathLog.debug(translate("Path", "Face appears misaligned after initial rotation.")) + if obj.InverseAngle is False: + if obj.AttemptInverseAngle is True: + (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle) + clnBase.recompute() + else: + msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") + PathLog.warning(msg) + + if self.isFaceUp(clnBase, faceIA) is False: + PathLog.debug('isFaceUp is False') + angle += 180.0 else: - msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") - PathLog.error(msg) + PathLog.debug(' isFaceUp') + else: PathLog.debug("Face appears to be oriented correctly.") + if angle < 0.0: + angle += 360.0 + tup = clnBase, sub, tag, angle, axis, clnStock else: if self.warnDisabledAxis(obj, axis) is False: @@ -157,21 +179,21 @@ class ObjectProfile(PathProfileBase.ObjectProfile): tag = base.Name + '_' + axis + str(angle).replace('.', '_') stock = PathUtils.findParentJob(obj).Stock tup = base, sub, tag, angle, axis, stock - + allTuples.append(tup) - + if subCount > 1: msg = translate('Path', "Multiple faces in Base Geometry.") + " " msg += translate('Path', "Depth settings will be applied to all faces.") PathLog.warning(msg) - + (Tags, Grps) = self.sortTuplesByIndex(allTuples, 2) # return (TagList, GroupList) subList = [] for o in range(0, len(Tags)): subList = [] for (base, sub, tag, angle, axis, stock) in Grps[o]: subList.append(sub) - + pair = base, subList, angle, axis, stock baseSubsTuples.append(pair) # Efor @@ -196,7 +218,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile): if numpy.isclose(abs(shape.normalAt(0, 0).z), 1): # horizontal face for wire in shape.Wires[1:]: holes.append((base.Shape, wire)) - + # Add face depth to list faceDepths.append(shape.BoundBox.ZMin) else: @@ -205,13 +227,12 @@ class ObjectProfile(PathProfileBase.ObjectProfile): PathLog.error(msg) FreeCAD.Console.PrintWarning(msg) - # Set initial Start and Final Depths and recalculate depthparams finDep = obj.FinalDepth.Value strDep = obj.StartDepth.Value if strDep > stock.Shape.BoundBox.ZMax: strDep = stock.Shape.BoundBox.ZMax - + startDepths.append(strDep) self.depthparams = self._customDepthParams(obj, strDep, finDep) @@ -230,31 +251,34 @@ class ObjectProfile(PathProfileBase.ObjectProfile): if obj.processPerimeter: if obj.HandleMultipleFeatures == 'Collectively': custDepthparams = self.depthparams + if obj.LimitDepthToFace is True and obj.EnableRotation != 'Off': if profileshape.BoundBox.ZMin > obj.FinalDepth.Value: finDep = profileshape.BoundBox.ZMin - custDepthparams = self._customDepthParams(obj, strDep, finDep - 0.5) # only an envelope + envDepthparams = self._customDepthParams(obj, strDep + 0.5, finDep) # only an envelope try: - env = PathUtils.getEnvelope(base.Shape, subshape=profileshape, depthparams=custDepthparams) + # env = PathUtils.getEnvelope(base.Shape, subshape=profileshape, depthparams=envDepthparams) + env = PathUtils.getEnvelope(profileshape, depthparams=envDepthparams) except Exception: # pylint: disable=broad-except # PathUtils.getEnvelope() failed to return an object. PathLog.error(translate('Path', 'Unable to create path for face(s).')) else: tup = env, False, 'pathProfileFaces', angle, axis, strDep, finDep shapes.append(tup) - + elif obj.HandleMultipleFeatures == 'Individually': for shape in faces: - profShape = Part.makeCompound([shape]) + # profShape = Part.makeCompound([shape]) finalDep = obj.FinalDepth.Value custDepthparams = self.depthparams if obj.Side == 'Inside': if finalDep < shape.BoundBox.ZMin: # Recalculate depthparams finalDep = shape.BoundBox.ZMin - custDepthparams = self._customDepthParams(obj, strDep, finalDep - 0.5) - - env = PathUtils.getEnvelope(base.Shape, subshape=profShape, depthparams=custDepthparams) + custDepthparams = self._customDepthParams(obj, strDep + 0.5, finalDep) + + # env = PathUtils.getEnvelope(base.Shape, subshape=profShape, depthparams=custDepthparams) + env = PathUtils.getEnvelope(shape, depthparams=custDepthparams) tup = env, False, 'pathProfileFaces', angle, axis, strDep, finalDep shapes.append(tup) @@ -262,11 +286,11 @@ class ObjectProfile(PathProfileBase.ObjectProfile): startDepth = max(startDepths) if obj.StartDepth.Value > startDepth: obj.StartDepth.Value = startDepth - + else: # Try to build targets from the job base if 1 == len(self.model): if hasattr(self.model[0], "Proxy"): - PathLog.info("hasattr() Proxy") + PathLog.debug("hasattr() Proxy") if isinstance(self.model[0].Proxy, ArchPanel.PanelSheet): # process the sheet if obj.processCircles or obj.processHoles: for shape in self.model[0].Proxy.getHoles(self.model[0], transform=True): @@ -302,7 +326,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile): obj.InverseAngle = False obj.AttemptInverseAngle = True obj.LimitDepthToFace = True - obj.HandleMultipleFeatures = 'Collectively' + obj.HandleMultipleFeatures = 'Individually' def SetupProperties(): @@ -321,6 +345,5 @@ def Create(name, obj=None): '''Create(name) ... Creates and returns a Profile based on faces operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectProfile(obj, name) return obj From 7cbd3bf793453a11ac3ce9f46f97ec6c88b6b8fe Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 21 Mar 2020 17:44:40 -0500 Subject: [PATCH 044/117] Path: Improve rotational transitions between operations Check previous and next operations for rotation, and eliminate rotational reset if can be done. --- src/Mod/Path/PathScripts/PathAreaOp.py | 51 +++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathAreaOp.py b/src/Mod/Path/PathScripts/PathAreaOp.py index d057742bfa..ab46b538f1 100644 --- a/src/Mod/Path/PathScripts/PathAreaOp.py +++ b/src/Mod/Path/PathScripts/PathAreaOp.py @@ -49,6 +49,7 @@ PathLog.setLevel(LOGLEVEL, PathLog.thisModule()) if LOGLEVEL is PathLog.Level.DEBUG: PathLog.trackModule() + # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) @@ -467,10 +468,14 @@ class ObjectOp(PathOp.ObjectOp): # Rotate Model to correct angle ppCmds.insert(0, Path.Command('G0', {axisOfRot: angle, 'F': self.axialRapid})) - # Raise cutter to safe depth and return index to starting position - ppCmds.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - if axis != nextAxis: - ppCmds.append(Path.Command('G0', {axisOfRot: 0.0, 'F': self.axialRapid})) + # Raise cutter to safe height + ppCmds.insert(0, Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) + + # Return index to starting position if axis of rotation changes. + if numShapes > 1: + if ns != numShapes - 1: + if axis != nextAxis: + ppCmds.append(Path.Command('G0', {axisOfRot: 0.0, 'F': self.axialRapid})) # Eif # Save gcode commands to object command list @@ -483,9 +488,43 @@ class ObjectOp(PathOp.ObjectOp): # Raise cutter to safe height and rotate back to original orientation if self.rotateFlag is True: + resetAxis = False + lastJobOp = None + nextJobOp = None + opIdx = 0 + JOB = PathUtils.findParentJob(obj) + jobOps = JOB.Operations.Group + numJobOps = len(jobOps) + + for joi in range(0, numJobOps): + jo = jobOps[joi] + if jo.Name == obj.Name: + opIdx = joi + lastOpIdx = opIdx - 1 + nextOpIdx = opIdx + 1 + if lastOpIdx > -1: + lastJobOp = jobOps[lastOpIdx] + if nextOpIdx < numJobOps: + nextJobOp = jobOps[nextOpIdx] + + if lastJobOp is not None: + if hasattr(lastJobOp, 'EnableRotation'): + PathLog.debug('Last Op, {}, has `EnableRotation` set to {}'.format(lastJobOp.Label, lastJobOp.EnableRotation)) + if lastJobOp.EnableRotation != obj.EnableRotation: + resetAxis = True + if ns == numShapes - 1: # If last shape, check next op EnableRotation setting + if nextJobOp is not None: + if hasattr(nextJobOp, 'EnableRotation'): + PathLog.debug('Next Op, {}, has `EnableRotation` set to {}'.format(nextJobOp.Label, nextJobOp.EnableRotation)) + if nextJobOp.EnableRotation != obj.EnableRotation: + resetAxis = True + + # Raise to safe height if rotation activated self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - self.commandlist.append(Path.Command('G0', {'A': 0.0, 'F': self.axialRapid})) - self.commandlist.append(Path.Command('G0', {'B': 0.0, 'F': self.axialRapid})) + # reset rotational axises if necessary + if resetAxis is True: + self.commandlist.append(Path.Command('G0', {'A': 0.0, 'F': self.axialRapid})) + self.commandlist.append(Path.Command('G0', {'B': 0.0, 'F': self.axialRapid})) self.useTempJobClones('Delete') # Delete temp job clone group and contents self.guiMessage('title', None, show=True) # Process GUI messages to user From 9239c67ec7b860921ebd4ef3bfd6c5f7d1997317 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 21 Mar 2020 17:45:50 -0500 Subject: [PATCH 045/117] Path: Remove unnecessary comments PathAreaOp PathProfileFaces --- src/Mod/Path/PathScripts/PathAreaOp.py | 12 ------------ src/Mod/Path/PathScripts/PathProfileFaces.py | 2 -- 2 files changed, 14 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathAreaOp.py b/src/Mod/Path/PathScripts/PathAreaOp.py index ab46b538f1..d95f39ea7a 100644 --- a/src/Mod/Path/PathScripts/PathAreaOp.py +++ b/src/Mod/Path/PathScripts/PathAreaOp.py @@ -3,7 +3,6 @@ # *************************************************************************** # * * # * Copyright (c) 2017 sliptonic * -# * Copyright (c) 2020 russ4262 (Russell Johnson) * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -67,7 +66,6 @@ class ObjectOp(PathOp.ObjectOp): '''opFeatures(obj) ... returns the base features supported by all Path.Area based operations. The standard feature list is OR'ed with the return value of areaOpFeatures(). Do not overwrite, implement areaOpFeatures(obj) instead.''' - # return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureStepDown | PathOp.FeatureHeights | PathOp.FeatureStartPoint | self.areaOpFeatures(obj) | PathOp.FeatureRotation return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureStepDown | PathOp.FeatureHeights | PathOp.FeatureStartPoint | self.areaOpFeatures(obj) | PathOp.FeatureCoolant def areaOpFeatures(self, obj): @@ -305,8 +303,6 @@ class ObjectOp(PathOp.ObjectOp): pathParams['return_end'] = True # Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers pathParams['preamble'] = False - #if not self.areaOpRetractTool(obj): - # pathParams['threshold'] = 2.001 * self.radius if self.endVector is None: V = hWire.Wires[0].Vertexes @@ -375,12 +371,6 @@ class ObjectOp(PathOp.ObjectOp): obj.ClearanceHeight.Value = strDep + self.clrOfset obj.SafeHeight.Value = strDep + self.safOfst - #if self.initWithRotation is False: - # if obj.FinalDepth.Value == obj.OpFinalDepth.Value: - # obj.FinalDepth.Value = finDep - # if obj.StartDepth.Value == obj.OpStartDepth.Value: - # obj.StartDepth.Value = strDep - # Create visual axes when debugging. if PathLog.getLevel(PathLog.thisModule()) == 4: self.visualAxis() @@ -570,10 +560,8 @@ class ObjectOp(PathOp.ObjectOp): Determine rotational radii for 4th-axis rotations, for clearance/safe heights ''' parentJob = PathUtils.findParentJob(obj) - # bb = parentJob.Stock.Shape.BoundBox xlim = 0.0 ylim = 0.0 - # zlim = 0.0 # Determine boundbox radius based upon xzy limits data if math.fabs(self.stockBB.ZMin) > math.fabs(self.stockBB.ZMax): diff --git a/src/Mod/Path/PathScripts/PathProfileFaces.py b/src/Mod/Path/PathScripts/PathProfileFaces.py index 68fa413cd5..c465b6d160 100644 --- a/src/Mod/Path/PathScripts/PathProfileFaces.py +++ b/src/Mod/Path/PathScripts/PathProfileFaces.py @@ -3,7 +3,6 @@ # *************************************************************************** # * * # * Copyright (c) 2014 Yorik van Havre * -# * Copyright (c) 2020 russ4262 (Russell Johnson) * # * Copyright (c) 2020 Schildkroet * # * * # * This program is free software; you can redistribute it and/or modify * @@ -153,7 +152,6 @@ class ObjectProfile(PathProfileBase.ObjectProfile): if obj.InverseAngle is False: if obj.AttemptInverseAngle is True: (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle) - clnBase.recompute() else: msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") PathLog.warning(msg) From 9b4b8fb920a2520f32f20fd63687f9786a02fd6c Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 21 Mar 2020 20:43:02 -0500 Subject: [PATCH 046/117] Path: Apply rotation improvement to B(y) rotations --- src/Mod/Path/PathScripts/PathPocketShape.py | 5 ++--- src/Mod/Path/PathScripts/PathProfileFaces.py | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPocketShape.py b/src/Mod/Path/PathScripts/PathPocketShape.py index 56e238b007..7c98f71d9c 100644 --- a/src/Mod/Path/PathScripts/PathPocketShape.py +++ b/src/Mod/Path/PathScripts/PathPocketShape.py @@ -3,7 +3,6 @@ # *************************************************************************** # * * # * Copyright (c) 2017 sliptonic * -# * Copyright (c) 2020 russ4262 (Russell Johnson) * # * Copyright (c) 2020 Schildkroet * # * * # * This program is free software; you can redistribute it and/or modify * @@ -481,7 +480,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") PathLog.warning(msg) - if angle < -180.0: + if angle < 0.0: angle += 360.0 tup = clnBase, subsList, angle, axis, clnStock @@ -531,7 +530,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): (rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable PathLog.debug("follow-up {}".format(praInfo2)) - if praAxis == axis and abs(praAngle) == 180.0: + if abs(praAngle) == 180.0: rtn = False if self.isFaceUp(clnBase, faceIA) is False: PathLog.debug('isFaceUp is False') diff --git a/src/Mod/Path/PathScripts/PathProfileFaces.py b/src/Mod/Path/PathScripts/PathProfileFaces.py index c465b6d160..6f2233b215 100644 --- a/src/Mod/Path/PathScripts/PathProfileFaces.py +++ b/src/Mod/Path/PathScripts/PathProfileFaces.py @@ -141,10 +141,10 @@ class ObjectProfile(PathProfileBase.ObjectProfile): (rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable PathLog.debug("follow-up faceRotationAnalysis: {}".format(praInfo2)) - if praAxis == axis and abs(praAngle) == 180.0: + if abs(praAngle) == 180.0: rtn = False if self.isFaceUp(clnBase, faceIA) is False: - PathLog.debug('isFaceUp is False') + PathLog.debug('isFaceUp 1 is False') angle -= 180.0 if rtn is True: @@ -157,7 +157,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile): PathLog.warning(msg) if self.isFaceUp(clnBase, faceIA) is False: - PathLog.debug('isFaceUp is False') + PathLog.debug('isFaceUp 2 is False') angle += 180.0 else: PathLog.debug(' isFaceUp') From 01e9b4151a5944a8e46d0149bda08de1c0804ecd Mon Sep 17 00:00:00 2001 From: wandererfan Date: Tue, 24 Mar 2020 19:35:20 -0400 Subject: [PATCH 047/117] [TD]spurious warning on Section --- src/Mod/TechDraw/App/DrawViewSection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/TechDraw/App/DrawViewSection.cpp b/src/Mod/TechDraw/App/DrawViewSection.cpp index f27941a468..d3cb579bb6 100644 --- a/src/Mod/TechDraw/App/DrawViewSection.cpp +++ b/src/Mod/TechDraw/App/DrawViewSection.cpp @@ -805,7 +805,7 @@ gp_Ax2 DrawViewSection::getSectionCS(void) const gXDir); } catch (...) { - Base::Console().Warning("DVS::getSectionCS - %s - failed to create section CS\n", getNameInDocument()); + Base::Console().Log("DVS::getSectionCS - %s - failed to create section CS\n", getNameInDocument()); } return sectionCS; } From 1f3966e1c3517f1138dee69b53dd098488c70cf8 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Wed, 25 Mar 2020 09:20:52 -0400 Subject: [PATCH 048/117] [TD]SectionLine colour property --- src/Mod/TechDraw/Gui/QGISectionLine.cpp | 13 ++++-- src/Mod/TechDraw/Gui/QGISectionLine.h | 2 + src/Mod/TechDraw/Gui/QGIViewPart.cpp | 1 + src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp | 42 ++++++++++++------- src/Mod/TechDraw/Gui/ViewProviderViewPart.h | 2 + 5 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/Mod/TechDraw/Gui/QGISectionLine.cpp b/src/Mod/TechDraw/Gui/QGISectionLine.cpp index 23bcd922aa..b6e99ef7d0 100644 --- a/src/Mod/TechDraw/Gui/QGISectionLine.cpp +++ b/src/Mod/TechDraw/Gui/QGISectionLine.cpp @@ -275,6 +275,10 @@ void QGISectionLine::setFont(QFont f, double fsize) m_symSize = fsize; } +void QGISectionLine::setSectionColor(QColor c) +{ + setColor(c); +} QColor QGISectionLine::getSectionColor() { @@ -354,11 +358,12 @@ void QGISectionLine::setTools() // m_arrow2->setPen(m_pen); // m_arrow1->setBrush(m_brush); // m_arrow2->setBrush(m_brush); - m_arrow1->setNormalColor(getSectionColor()); - m_arrow1->setFillColor(getSectionColor()); + + m_arrow1->setNormalColor(m_colCurrent); + m_arrow1->setFillColor(m_colCurrent); m_arrow1->setPrettyNormal(); - m_arrow2->setNormalColor(getSectionColor()); - m_arrow2->setFillColor(getSectionColor()); + m_arrow2->setNormalColor(m_colCurrent); + m_arrow2->setFillColor(m_colCurrent); m_arrow2->setPrettyNormal(); m_symbol1->setDefaultTextColor(m_colCurrent); diff --git a/src/Mod/TechDraw/Gui/QGISectionLine.h b/src/Mod/TechDraw/Gui/QGISectionLine.h index ff14e257d0..608e7e2030 100644 --- a/src/Mod/TechDraw/Gui/QGISectionLine.h +++ b/src/Mod/TechDraw/Gui/QGISectionLine.h @@ -55,6 +55,8 @@ public: void setDirection(Base::Vector3d dir); void setFont(QFont f, double fsize); void setSectionStyle(int style); + void setSectionColor(QColor c); + virtual void draw(); protected: diff --git a/src/Mod/TechDraw/Gui/QGIViewPart.cpp b/src/Mod/TechDraw/Gui/QGIViewPart.cpp index f8196c194b..997e786853 100644 --- a/src/Mod/TechDraw/Gui/QGIViewPart.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewPart.cpp @@ -848,6 +848,7 @@ void QGIViewPart::drawSectionLine(TechDraw::DrawViewSection* viewSection, bool b addToGroup(sectionLine); sectionLine->setSymbol(const_cast(viewSection->SectionSymbol.getValue())); sectionLine->setSectionStyle(vp->SectionLineStyle.getValue()); + sectionLine->setSectionColor(vp->SectionLineColor.getValue().asValue()); //TODO: handle oblique section lines? //find smallest internal angle(normalDir,get?Dir()) and use -1*get?Dir() +/- angle diff --git a/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp b/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp index 3a4b858710..a8302cea16 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp +++ b/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp @@ -56,12 +56,12 @@ using namespace TechDrawGui; PROPERTY_SOURCE(TechDrawGui::ViewProviderViewPart, TechDrawGui::ViewProviderDrawingView) -const char* ViewProviderViewPart::LineStyleEnums[] = { "NoLine", - "Continuous", - "Dash", - "Dot", - "DashDot", - "DashDotDot", +const char* ViewProviderViewPart::LineStyleEnums[] = { "NoLine", + "Continuous", + "Dash", + "Dot", + "DashDot", + "DashDotDot", NULL }; //************************************************************************** @@ -110,7 +110,10 @@ ViewProviderViewPart::ViewProviderViewPart() ADD_PROPERTY_TYPE(ShowSectionLine ,(true) ,dgroup,App::Prop_None,"Show/hide section line if applicable"); int defLineStyle = hGrp->GetInt("SectionLine", 2); SectionLineStyle.setEnums(LineStyleEnums); - ADD_PROPERTY_TYPE(SectionLineStyle, (defLineStyle), dgroup, App::Prop_None, "Set section line style if applicable"); + ADD_PROPERTY_TYPE(SectionLineStyle, (defLineStyle), dgroup, App::Prop_None, + "Set section line style if applicable"); + ADD_PROPERTY_TYPE(SectionLineColor, (prefSectionColor()), dgroup, App::Prop_None, + "Set section line color if applicable"); //properties that affect Detail Highlights ADD_PROPERTY_TYPE(HighlightAdjust,(0.0),hgroup,App::Prop_None,"Adjusts the rotation of the Detail highlight"); @@ -139,6 +142,7 @@ void ViewProviderViewPart::onChanged(const App::Property* prop) prop == &(CenterScale) || prop == &(ShowSectionLine) || prop == &(SectionLineStyle) || + prop == &(SectionLineColor) || prop == &(HorizCenterLine) || prop == &(VertCenterLine) ) { // redraw QGIVP @@ -290,13 +294,13 @@ bool ViewProviderViewPart::onDelete(const std::vector &) QMessageBox::Ok); return false; } - else if (!viewLeader.empty()) { - bodyMessageStream << qApp->translate("Std_Delete", - "You cannot delete this view because it has a leader line that would become broken."); - QMessageBox::warning(Gui::getMainWindow(), - qApp->translate("Std_Delete", "Object dependencies"), bodyMessage, - QMessageBox::Ok); - return false; + else if (!viewLeader.empty()) { + bodyMessageStream << qApp->translate("Std_Delete", + "You cannot delete this view because it has a leader line that would become broken."); + QMessageBox::warning(Gui::getMainWindow(), + qApp->translate("Std_Delete", "Object dependencies"), bodyMessage, + QMessageBox::Ok); + return false; } else { return true; @@ -310,3 +314,13 @@ bool ViewProviderViewPart::canDelete(App::DocumentObject *obj) const Q_UNUSED(obj) return true; } + +App::Color ViewProviderViewPart::prefSectionColor(void) +{ + Base::Reference hGrp = App::GetApplication().GetUserParameter() + .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Decorations"); + App::Color fcColor; + fcColor.setPackedValue(hGrp->GetUnsigned("SectionColor", 0x00FF0000)); + return fcColor; +} + diff --git a/src/Mod/TechDraw/Gui/ViewProviderViewPart.h b/src/Mod/TechDraw/Gui/ViewProviderViewPart.h index 202268341d..01600f456a 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderViewPart.h +++ b/src/Mod/TechDraw/Gui/ViewProviderViewPart.h @@ -53,6 +53,7 @@ public: App::PropertyBool VertCenterLine; App::PropertyBool ShowSectionLine; App::PropertyEnumeration SectionLineStyle; + App::PropertyColor SectionLineColor; App::PropertyFloat HighlightAdjust; App::PropertyBool ShowAllEdges; @@ -70,6 +71,7 @@ public: virtual void onChanged(const App::Property *prop); virtual void updateData(const App::Property*); virtual void handleChangedPropertyType(Base::XMLReader &reader, const char *TypeName, App::Property * prop); + App::Color prefSectionColor(void); virtual std::vector claimChildren(void) const; From ebc6536ce834c4c61c3273a45d9a17856396d5e0 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Wed, 25 Mar 2020 18:22:10 -0400 Subject: [PATCH 049/117] [TD]remove highlight on deletion of detail --- src/Mod/TechDraw/App/DrawViewDetail.cpp | 12 +++++++++++- src/Mod/TechDraw/App/DrawViewDetail.h | 12 +++++++----- src/Mod/TechDraw/App/DrawViewPart.cpp | 4 +++- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewDetail.cpp b/src/Mod/TechDraw/App/DrawViewDetail.cpp index 832da7e7bf..4e2e3f21ed 100644 --- a/src/Mod/TechDraw/App/DrawViewDetail.cpp +++ b/src/Mod/TechDraw/App/DrawViewDetail.cpp @@ -117,7 +117,6 @@ DrawViewDetail::DrawViewDetail() //hide Properties not relevant to DVDetail Direction.setStatus(App::Property::ReadOnly,true); //Should be same as BaseView Rotation.setStatus(App::Property::ReadOnly,true); //same as BaseView - } DrawViewDetail::~DrawViewDetail() @@ -420,6 +419,17 @@ bool DrawViewDetail::debugDetail(void) const return result; } +void DrawViewDetail::unsetupObject() +{ +// Base::Console().Message("DVD::unsetupObject()\n"); + App::DocumentObject* baseObj = BaseView.getValue(); + DrawView* base = dynamic_cast(baseObj); + if (base != nullptr) { + base->requestPaint(); + } + +} + void DrawViewDetail::getParameters() { diff --git a/src/Mod/TechDraw/App/DrawViewDetail.h b/src/Mod/TechDraw/App/DrawViewDetail.h index 496bfa7c65..8bfb536eb7 100644 --- a/src/Mod/TechDraw/App/DrawViewDetail.h +++ b/src/Mod/TechDraw/App/DrawViewDetail.h @@ -50,7 +50,7 @@ namespace TechDraw class TechDrawExport DrawViewDetail : public DrawViewPart { - PROPERTY_HEADER(Part::DrawViewDetail); + PROPERTY_HEADER_WITH_OVERRIDE(Part::DrawViewDetail); public: /// Constructor @@ -62,12 +62,14 @@ public: App::PropertyFloat Radius; App::PropertyString Reference; - virtual short mustExecute() const; - virtual App::DocumentObjectExecReturn *execute(void); - virtual void onChanged(const App::Property* prop); - virtual const char* getViewProviderName(void) const { + virtual short mustExecute() const override; + virtual App::DocumentObjectExecReturn *execute(void) override; + virtual void onChanged(const App::Property* prop) override; + virtual const char* getViewProviderName(void) const override { return "TechDrawGui::ViewProviderViewPart"; } + virtual void unsetupObject() override; + void detailExec(TopoDS_Shape s, DrawViewPart* baseView, diff --git a/src/Mod/TechDraw/App/DrawViewPart.cpp b/src/Mod/TechDraw/App/DrawViewPart.cpp index dc9c78a5b4..e1f95e7d4b 100644 --- a/src/Mod/TechDraw/App/DrawViewPart.cpp +++ b/src/Mod/TechDraw/App/DrawViewPart.cpp @@ -934,7 +934,9 @@ std::vector DrawViewPart::getDetailRefs(void) const std::vector inObjs = getInList(); for (auto& o:inObjs) { if (o->getTypeId().isDerivedFrom(DrawViewDetail::getClassTypeId())) { - result.push_back(static_cast(o)); + if (!o->isRemoving()) { + result.push_back(static_cast(o)); + } } } return result; From fcf5ec027013cb24c78bb523c78cf709bfadcb6f Mon Sep 17 00:00:00 2001 From: wandererfan Date: Wed, 25 Mar 2020 20:30:18 -0400 Subject: [PATCH 050/117] [TD]detail highlight color/style --- src/Mod/TechDraw/Gui/DlgPrefsTechDraw1.ui | 37 +- src/Mod/TechDraw/Gui/DlgPrefsTechDraw1Imp.cpp | 2 + src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui | 754 ++++++++++-------- src/Mod/TechDraw/Gui/DlgPrefsTechDraw3Imp.cpp | 2 + src/Mod/TechDraw/Gui/QGIViewPart.cpp | 2 + src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp | 24 + src/Mod/TechDraw/Gui/ViewProviderViewPart.h | 4 + 7 files changed, 485 insertions(+), 340 deletions(-) diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw1.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw1.ui index 837e53c4a9..819c9f728c 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw1.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw1.ui @@ -493,7 +493,7 @@ for ProjectionGroups Geometric hatch pattern color - + 0 0 @@ -548,7 +548,7 @@ for ProjectionGroups
- + @@ -569,10 +569,10 @@ for ProjectionGroups - + - Face color + Face color (if not transparent) @@ -589,6 +589,18 @@ for ProjectionGroups + + + + + true + + + + Detail Highlight + + + @@ -621,6 +633,23 @@ for ProjectionGroups + + + + + 0 + 0 + 0 + + + + HighlightColor + + + /Mod/TechDraw/Decorations + + + diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw1Imp.cpp b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw1Imp.cpp index 3a3bf22c3e..4c3c299027 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw1Imp.cpp +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw1Imp.cpp @@ -77,6 +77,7 @@ void DlgPrefsTechDraw1Imp::saveSettings() pcbVertexColor->onSave(); pcbMarkup->onSave(); + pcbHighlight->onSave(); } void DlgPrefsTechDraw1Imp::loadSettings() @@ -116,6 +117,7 @@ void DlgPrefsTechDraw1Imp::loadSettings() pcbVertexColor->onRestore(); pcbMarkup->onRestore(); + pcbHighlight->onRestore(); } /** diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui index 948124695f..0b71c8547a 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui @@ -7,7 +7,7 @@ 0 0 448 - 856 + 891
@@ -379,256 +379,15 @@ - - + + true - Line Group Name - - - - - - - - true - - - - Detail View Outline Shape - - - - - - - - true - - - - Length of horizontal portion of Balloon leader - - - Ballon Leader Kink Length - - - - - - - - 0 - 0 - - - - Shape of balloon annotations - - - BalloonShape - - - Mod/TechDraw/Decorations - - - - Circular - - - - :/icons/circular.svg:/icons/circular.svg - - - - - None - - - - :/icons/none.svg:/icons/none.svg - - - - - Triangle - - - - :/icons/triangle.svg:/icons/triangle.svg - - - - - Inspection - - - - :/icons/inspection.svg:/icons/inspection.svg - - - - - Hexagon - - - - :/icons/hexagon.svg:/icons/hexagon.svg - - - - - Square - - - - :/icons/square.svg:/icons/square.svg - - - - - Rectangle - - - - :/icons/rectangle.svg:/icons/rectangle.svg - - - - - - - - - 0 - 0 - - - - Type for centerlines - - - 2 - - - CenterLine - - - /Mod/TechDraw/Decorations - - - - NeverShow - - - - :/icons/arrownone.svg:/icons/arrownone.svg - - - - - Continuous - - - - :/icons/continuous-line.svg:/icons/continuous-line.svg - - - - - Dash - - - - :/icons/dash-line.svg:/icons/dash-line.svg - - - - - Dot - - - - :/icons/dot-line.svg:/icons/dot-line.svg - - - - - DashDot - - - - :/icons/dashDot-line.svg:/icons/dashDot-line.svg - - - - - DashDotDot - - - - :/icons/dashDotDot-line.svg:/icons/dashDotDot-line.svg - - - - - - - - - true - - - - Balloon Leader End - - - - - - - - 0 - 0 - - - - Style for balloon leader line ends - - - BalloonArrow - - - Mod/TechDraw/Decorations - - - - - - - - 0 - 0 - - - - Default name in LineGroup CSV file - - - FC 0.70mm - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - LineGroup - - - /Mod/TechDraw/Decorations + Center Line Style @@ -720,7 +479,80 @@ - + + + + + true + + + + Section Cut Surface + + + + + + + Default appearance of cut surface in section view + + + 2 + + + CutSurfaceDisplay + + + /Mod/TechDraw/Decorations + + + + Hide + + + + + Solid Color + + + + + SVG Hatch + + + + + PAT Hatch + + + + + + + + + 0 + 0 + + + + Forces last leader line segment to be horizontal + + + Leader Line Auto Horizontal + + + true + + + AutoHorizontal + + + Mod/TechDraw/LeaderLines + + + + @@ -745,8 +577,8 @@ - - + + 0 @@ -754,20 +586,165 @@ - Restrict Filled Triangle line end to vertical or horizontal directions + Type for centerlines - - Balloon Orthogonal Triangle - - - true + + 2 - PyramidOrtho + CenterLine + + + /Mod/TechDraw/Decorations + + + + NeverShow + + + + :/icons/arrownone.svg:/icons/arrownone.svg + + + + + Continuous + + + + :/icons/continuous-line.svg:/icons/continuous-line.svg + + + + + Dash + + + + :/icons/dash-line.svg:/icons/dash-line.svg + + + + + Dot + + + + :/icons/dot-line.svg:/icons/dot-line.svg + + + + + DashDot + + + + :/icons/dashDot-line.svg:/icons/dashDot-line.svg + + + + + DashDotDot + + + + :/icons/dashDotDot-line.svg:/icons/dashDotDot-line.svg + + + + + + + + + 0 + 0 + + + + Shape of balloon annotations + + + BalloonShape Mod/TechDraw/Decorations + + + Circular + + + + :/icons/circular.svg:/icons/circular.svg + + + + + None + + + + :/icons/none.svg:/icons/none.svg + + + + + Triangle + + + + :/icons/triangle.svg:/icons/triangle.svg + + + + + Inspection + + + + :/icons/inspection.svg:/icons/inspection.svg + + + + + Hexagon + + + + :/icons/hexagon.svg:/icons/hexagon.svg + + + + + Square + + + + :/icons/square.svg:/icons/square.svg + + + + + Rectangle + + + + :/icons/rectangle.svg:/icons/rectangle.svg + + + + + + + + + true + + + + Balloon Leader End + @@ -802,7 +779,7 @@ - + Qt::Horizontal @@ -852,30 +829,6 @@ - - - - - true - - - - Balloon Shape - - - - - - - - true - - - - Center Line Style - - - @@ -888,7 +841,7 @@ - + @@ -915,56 +868,8 @@ - - - - - true - - - - Section Cut Surface - - - - - - - Default appearance of cut surface in section view - - - 2 - - - CutSurfaceDisplay - - - /Mod/TechDraw/Decorations - - - - Hide - - - - - Solid Color - - - - - SVG Hatch - - - - - PAT Hatch - - - - - - + + 0 @@ -972,23 +877,118 @@ - Forces last leader line segment to be horizontal + Default name in LineGroup CSV file - Leader Line Auto Horizontal + FC 0.70mm + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + LineGroup + + + /Mod/TechDraw/Decorations + + + + + + + + true + + + + Detail View Outline Shape + + + + + + + + 0 + 0 + + + + Style for balloon leader line ends + + + BalloonArrow + + + Mod/TechDraw/Decorations + + + + + + + + true + + + + Length of horizontal portion of Balloon leader + + + Ballon Leader Kink Length + + + + + + + + 0 + 0 + + + + Restrict Filled Triangle line end to vertical or horizontal directions + + + Balloon Orthogonal Triangle true - AutoHorizontal + PyramidOrtho - Mod/TechDraw/LeaderLines + Mod/TechDraw/Decorations - + + + + + true + + + + Line Group Name + + + + + + + + true + + + + Balloon Shape + + + + @@ -1010,6 +1010,88 @@ + + + + + true + + + + Line style of detail highlight on base view + + + Detail Highlight Style + + + + + + + 2 + + + HighlightStyle + + + /Mod/TechDraw/Decorations + + + + NeverShow + + + + :/icons/arrownone.svg:/icons/arrownone.svg + + + + + Continuous + + + + :/icons/continuous-line.svg:/icons/continuous-line.svg + + + + + Dash + + + + :/icons/dash-line.svg:/icons/dash-line.svg + + + + + Dot + + + + :/icons/dot-line.svg:/icons/dot-line.svg + + + + + DashDot + + + + :/icons/dashDot-line.svg:/icons/dashDot-line.svg + + + + + DashDotDot + + + + :/icons/dashDotDot-line.svg:/icons/dashDotDot-line.svg + + + + diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3Imp.cpp b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3Imp.cpp index b9e470256d..746941693b 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3Imp.cpp +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3Imp.cpp @@ -81,6 +81,7 @@ void DlgPrefsTechDraw3Imp::saveSettings() plsb_FontSize->onSave(); sbAltDecimals->onSave(); cbCutSurface->onSave(); + pcbHighlightStyle->onSave(); } void DlgPrefsTechDraw3Imp::loadSettings() @@ -119,6 +120,7 @@ void DlgPrefsTechDraw3Imp::loadSettings() plsb_FontSize->onRestore(); sbAltDecimals->onRestore(); cbCutSurface->onRestore(); + pcbHighlightStyle->onRestore(); DrawGuiUtil::loadArrowBox(pcbBalloonArrow); pcbBalloonArrow->setCurrentIndex(prefBalloonArrow()); diff --git a/src/Mod/TechDraw/Gui/QGIViewPart.cpp b/src/Mod/TechDraw/Gui/QGIViewPart.cpp index 997e786853..45eaa27157 100644 --- a/src/Mod/TechDraw/Gui/QGIViewPart.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewPart.cpp @@ -1016,6 +1016,8 @@ void QGIViewPart::drawHighlight(TechDraw::DrawViewDetail* viewDetail, bool b) addToGroup(highlight); highlight->setPos(0.0,0.0); //sb setPos(center.x,center.y)? highlight->setReference(const_cast(viewDetail->Reference.getValue())); + highlight->setStyle((Qt::PenStyle)vp->HighlightLineStyle.getValue()); + highlight->setColor(vp->HighlightLineColor.getValue().asValue()); Base::Vector3d center = viewDetail->AnchorPoint.getValue() * viewPart->getScale(); diff --git a/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp b/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp index a8302cea16..a1c35d66cf 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp +++ b/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp @@ -116,6 +116,11 @@ ViewProviderViewPart::ViewProviderViewPart() "Set section line color if applicable"); //properties that affect Detail Highlights + HighlightLineStyle.setEnums(LineStyleEnums); + ADD_PROPERTY_TYPE(HighlightLineStyle, (prefHighlightStyle()), hgroup, App::Prop_None, + "Set highlight line style if applicable"); + ADD_PROPERTY_TYPE(HighlightLineColor, (prefHighlightColor()), hgroup, App::Prop_None, + "Set highlight line color if applicable"); ADD_PROPERTY_TYPE(HighlightAdjust,(0.0),hgroup,App::Prop_None,"Adjusts the rotation of the Detail highlight"); ADD_PROPERTY_TYPE(ShowAllEdges ,(false) ,dgroup,App::Prop_None,"Temporarily show invisible lines"); @@ -143,6 +148,8 @@ void ViewProviderViewPart::onChanged(const App::Property* prop) prop == &(ShowSectionLine) || prop == &(SectionLineStyle) || prop == &(SectionLineColor) || + prop == &(HighlightLineStyle) || + prop == &(HighlightLineColor) || prop == &(HorizCenterLine) || prop == &(VertCenterLine) ) { // redraw QGIVP @@ -324,3 +331,20 @@ App::Color ViewProviderViewPart::prefSectionColor(void) return fcColor; } +App::Color ViewProviderViewPart::prefHighlightColor(void) +{ + Base::Reference hGrp = App::GetApplication().GetUserParameter() + .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Decorations"); + App::Color fcColor; + fcColor.setPackedValue(hGrp->GetUnsigned("HighlightColor", 0x00000000)); + return fcColor; +} + +int ViewProviderViewPart::prefHighlightStyle(void) +{ + Base::Reference hGrp = App::GetApplication().GetUserParameter() + .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Decorations"); + return hGrp->GetInt("HighlightStyle", 2); +} + + diff --git a/src/Mod/TechDraw/Gui/ViewProviderViewPart.h b/src/Mod/TechDraw/Gui/ViewProviderViewPart.h index 01600f456a..9ab6a4501e 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderViewPart.h +++ b/src/Mod/TechDraw/Gui/ViewProviderViewPart.h @@ -54,6 +54,8 @@ public: App::PropertyBool ShowSectionLine; App::PropertyEnumeration SectionLineStyle; App::PropertyColor SectionLineColor; + App::PropertyEnumeration HighlightLineStyle; + App::PropertyColor HighlightLineColor; App::PropertyFloat HighlightAdjust; App::PropertyBool ShowAllEdges; @@ -72,6 +74,8 @@ public: virtual void updateData(const App::Property*); virtual void handleChangedPropertyType(Base::XMLReader &reader, const char *TypeName, App::Property * prop); App::Color prefSectionColor(void); + App::Color prefHighlightColor(void); + int prefHighlightStyle(void); virtual std::vector claimChildren(void) const; From 5f323a4169cfbfb89ba068968c18a0c176746df9 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Thu, 26 Mar 2020 08:35:09 -0400 Subject: [PATCH 051/117] [TD]Uncomplicate default decimal setting --- src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui | 82 ++--- src/Mod/TechDraw/Gui/DlgPrefsTechDraw3Imp.cpp | 2 - src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui | 307 ++++++++++-------- src/Mod/TechDraw/Gui/DlgPrefsTechDraw4Imp.cpp | 2 + 4 files changed, 205 insertions(+), 188 deletions(-) diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui index 0b71c8547a..c3e3c2d63b 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui @@ -22,10 +22,22 @@ + + + 0 + 0 + + 0 - 284 + 250 + + + + + 0 + 0 @@ -33,8 +45,8 @@ - - + + @@ -64,7 +76,7 @@ - + false @@ -117,7 +129,7 @@ - + @@ -129,7 +141,7 @@ - + @@ -151,41 +163,13 @@ - + Alternate Decimals - - - - true - - - - 0 - 0 - - - - Custom format for dimension text - - - %.2f - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - formatSpec - - - /Mod/TechDraw/Dimensions - - - @@ -193,7 +177,7 @@ - + @@ -205,7 +189,7 @@ - + @@ -217,7 +201,7 @@ - + @@ -242,7 +226,7 @@ - + @@ -267,18 +251,6 @@ - - - - - true - - - - Default Format - - - @@ -318,14 +290,14 @@ - + Diameter Symbol - + Qt::Horizontal @@ -1308,8 +1280,8 @@ 71 - 338 - 127 + 425 + 124 diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3Imp.cpp b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3Imp.cpp index 746941693b..624527ce47 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3Imp.cpp +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3Imp.cpp @@ -67,7 +67,6 @@ void DlgPrefsTechDraw3Imp::saveSettings() cbShowCenterMarks->onSave(); cbShowUnits->onSave(); leDiameter->onSave(); - leformatSpec->onSave(); leLineGroup->onSave(); pcbArrow->onSave(); pcbBalloonArrow->onSave(); @@ -106,7 +105,6 @@ void DlgPrefsTechDraw3Imp::loadSettings() cbShowCenterMarks->onRestore(); cbShowUnits->onRestore(); leDiameter->onRestore(); - leformatSpec->onRestore(); leLineGroup->onRestore(); pcbArrow->onRestore(); pcbBalloonArrow->onRestore(); diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui index 060e4093a3..399558154c 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui @@ -7,19 +7,63 @@ 0 0 440 - 268 + 381 Advanced - + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 12 + true + + + + QFrame::Box + + + Items in italics are default values for new objects. They have no effect on existing objects. + + + true + + + + + + + 0 + 0 + + 0 - 141 + 300 + + + + + 0 + 0 @@ -28,7 +72,7 @@ - + @@ -68,18 +112,35 @@ Only change unless you know what you are doing! - - - - Qt::Horizontal + + + + Limit of 64x64 pixel SVG tiles used to hatch a single face. +For large scalings you might get an error about to many SVG tiles. +Then you need to increase the tile limit. - - - 40 - 20 - + + Qt::AlignRight - + + 1 + + + 1000000 + + + 100 + + + 10000 + + + MaxSVGTile + + + Mod/TechDraw/Decorations + + @@ -103,62 +164,44 @@ Only change unless you know what you are doing!
- - + + - Limit of 64x64 pixel SVG tiles used to hatch a single face. -For large scalings you might get an error about to many SVG tiles. -Then you need to increase the tile limit. + Include 2D Objects in projection - - 1 + + Show Loose 2D Geom - - 1000000 - - - 100 - - - Qt::AlignRight - - - 10000 + + false - MaxSVGTile + ShowLoose2d - Mod/TechDraw/Decorations + Mod/TechDraw/General - - + + + + + 0 + 0 + + - Maximum hatch line segments to use -when hatching a face with a PAT pattern + Dump intermediate results during Section view processing - - 1 - - - 1000000 - - - 100 - - - Qt::AlignRight - - - 10000 + + Debug Section - MaxSeg + debugSection - Mod/TechDraw/PAT + Mod/TechDraw/debug @@ -211,25 +254,44 @@ when hatching a face with a PAT pattern
- - - - - 0 - 0 - - + + - Dump intermediate results during Section view processing + Maximum hatch line segments to use +when hatching a face with a PAT pattern - - Debug Section + + Qt::AlignRight + + + 1 + + + 1000000 + + + 100 + + + 10000 - debugSection + MaxSeg - Mod/TechDraw/debug + Mod/TechDraw/PAT + + + + + + + + true + + + + Line End Cap Shape @@ -261,31 +323,18 @@ can be a performance penalty in complex models. - - - - - true - + + + + Qt::Horizontal - - Line End Cap Shape + + + 40 + 20 + - - - - - - Max SVG Hatch Tiles - - - - - - - Max PAT Hatch Segments - - + @@ -309,22 +358,45 @@ can be a performance penalty in complex models. - - - - Include 2D Objects in projection + + + + Max SVG Hatch Tiles + + + + + + + Max PAT Hatch Segments + + + + + + + + true + - Show Loose 2D Geom + Dimension Format - - false + + + + + + Override automatic dimension format + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - ShowLoose2d + formatSpec - Mod/TechDraw/General + /Mod/TechDraw/Dimensions @@ -333,38 +405,6 @@ can be a performance penalty in complex models. - - - - - 12 - true - - - - QFrame::Box - - - Items in italics are default values for new objects. They have no effect on existing objects. - - - true - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -383,6 +423,11 @@ can be a performance penalty in complex models. QComboBox
Gui/PrefWidgets.h
+ + Gui::PrefLineEdit + QLineEdit +
Gui/PrefWidgets.h
+
diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4Imp.cpp b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4Imp.cpp index 8ea42d7b5e..9725728ee4 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4Imp.cpp +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4Imp.cpp @@ -53,6 +53,7 @@ void DlgPrefsTechDraw4Imp::saveSettings() sbMaxTiles->onSave(); sbMaxPat->onSave(); cbShowLoose->onSave(); + leFormatSpec->onSave(); } void DlgPrefsTechDraw4Imp::loadSettings() @@ -67,6 +68,7 @@ void DlgPrefsTechDraw4Imp::loadSettings() sbMaxTiles->onRestore(); sbMaxPat->onRestore(); cbShowLoose->onRestore(); + leFormatSpec->onRestore(); } /** From 59a9ab9bc06a83c40e70e01bc6bdd3075679e7ed Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sun, 16 Sep 2018 16:04:26 -0500 Subject: [PATCH 052/117] Path: Z depth correction from probe data --- src/Mod/Path/CMakeLists.txt | 3 + src/Mod/Path/Gui/Resources/Path.qrc | 2 + .../Path/Gui/Resources/icons/Path-Probe.svg | 666 ++++++++++++++++++ .../Gui/Resources/panels/PageOpProbeEdit.ui | 93 +++ src/Mod/Path/InitGui.py | 2 +- .../Path/PathScripts/PathDressupZCorrect.py | 261 +++++++ src/Mod/Path/PathScripts/PathProbe.py | 107 +++ src/Mod/Path/PathScripts/PathProbeGui.py | 70 ++ src/Mod/Path/PathScripts/PathSelection.py | 11 +- 9 files changed, 1213 insertions(+), 2 deletions(-) create mode 100644 src/Mod/Path/Gui/Resources/icons/Path-Probe.svg create mode 100644 src/Mod/Path/Gui/Resources/panels/PageOpProbeEdit.ui create mode 100644 src/Mod/Path/PathScripts/PathDressupZCorrect.py create mode 100644 src/Mod/Path/PathScripts/PathProbe.py create mode 100644 src/Mod/Path/PathScripts/PathProbeGui.py diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 098bb114dd..1a51ebc872 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -46,6 +46,7 @@ SET(PathScripts_SRCS PathScripts/PathDressupTag.py PathScripts/PathDressupTagGui.py PathScripts/PathDressupTagPreferences.py + PathScripts/PathDressupZCorrect.py PathScripts/PathDrilling.py PathScripts/PathDrillingGui.py PathScripts/PathEngrave.py @@ -82,6 +83,8 @@ SET(PathScripts_SRCS PathScripts/PathPreferences.py PathScripts/PathPreferencesPathDressup.py PathScripts/PathPreferencesPathJob.py + PathScripts/PathProbe.py + PathScripts/PathProbeGui.py PathScripts/PathProfileBase.py PathScripts/PathProfileBaseGui.py PathScripts/PathProfileContour.py diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index b461c8cdf6..184bbb6266 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -37,6 +37,7 @@ icons/Path-Plane.svg icons/Path-Pocket.svg icons/Path-Post.svg + icons/Path-Probe.svg icons/Path-Profile-Edges.svg icons/Path-Profile-Face.svg icons/Path-Profile.svg @@ -102,6 +103,7 @@ panels/PageOpHelixEdit.ui panels/PageOpPocketExtEdit.ui panels/PageOpPocketFullEdit.ui + panels/PageOpProbeEdit.ui panels/PageOpProfileFullEdit.ui panels/PageOpSurfaceEdit.ui panels/PathEdit.ui diff --git a/src/Mod/Path/Gui/Resources/icons/Path-Probe.svg b/src/Mod/Path/Gui/Resources/icons/Path-Probe.svg new file mode 100644 index 0000000000..63eae06c30 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-Probe.svg @@ -0,0 +1,666 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + Path-Drilling + 2015-07-04 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Path/Gui/Resources/icons/Path-Drilling.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpProbeEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpProbeEdit.ui new file mode 100644 index 0000000000..c2c9a27309 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/PageOpProbeEdit.ui @@ -0,0 +1,93 @@ + + + Form + + + + 0 + 0 + 400 + 140 + + + + Form + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + + + ToolController + + + + + + + <html><head/><body><p>The tool and its settings to be used for this operation.</p></body></html> + + + + + + + + + + + + + Algorithm + + + + + + + + OCL Dropcutter + + + + + OCL Waterline + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 0657fac577..64a48bc54a 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -92,7 +92,7 @@ class PathWorkbench (Workbench): threedopcmdlist = ["Path_Pocket_3D"] engravecmdlist = ["Path_Engrave", "Path_Deburr"] modcmdlist = ["Path_OperationCopy", "Path_Array", "Path_SimpleCopy" ] - dressupcmdlist = ["Path_DressupAxisMap", "Path_DressupPathBoundary", "Path_DressupDogbone", "Path_DressupDragKnife", "Path_DressupLeadInOut", "Path_DressupRampEntry", "Path_DressupTag"] + dressupcmdlist = ["Path_DressupAxisMap", "Path_DressupPathBoundary", "Path_DressupDogbone", "Path_DressupDragKnife", "Path_DressupLeadInOut", "Path_DressupRampEntry", "Path_DressupTag", "Path_DressupZCorrect"] extracmdlist = [] #modcmdmore = ["Path_Hop",] #remotecmdlist = ["Path_Remote"] diff --git a/src/Mod/Path/PathScripts/PathDressupZCorrect.py b/src/Mod/Path/PathScripts/PathDressupZCorrect.py new file mode 100644 index 0000000000..ecb9dc43e3 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathDressupZCorrect.py @@ -0,0 +1,261 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2018 sliptonic * +# * * +# * 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. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +import FreeCAD +import FreeCADGui +import Path +import PathScripts.PathUtils as PathUtils +from bisect import bisect_left + +from PySide import QtCore, QtGui + +"""Z Depth Correction Dressup. This dressup takes a probe file as input and does bilinear interpolation of the Zdepths to correct for a surface which is not parallel to the milling table/bed. The probe file should conform to the format specified by the linuxcnc G38 probe logging: 9-number coordinate consisting of XYZABCUVW http://linuxcnc.org/docs/html/gcode/g-code.html#gcode:g38 +""" + +# Qt tanslation handling +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'] + +class ObjectDressup: + x_index = 0 + y_index = 0 + values = None + x_length = 0 + y_length = 0 + + def __init__(self, obj): + obj.addProperty("App::PropertyLink", "Base", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupAxisMap", "The base path to modify")) + obj.addProperty("App::PropertyFile", "probefile", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "The point file from the surface probing.")) + obj.Proxy = self + + def __getstate__(self): + return None + + def __setstate__(self, state): + return None + + def onChanged(self, fp, prop): + if str(prop) == "probefile": + self._loadFile(fp.probefile) + + def _bilinearInterpolate(self, x, y): + # local lookups + x_index, y_index, values = self.x_index, self.y_index, self.values + + i = bisect_left(x_index, x) - 1 + j = bisect_left(y_index, y) - 1 + + if True: #self.extrapolate: + # fix x index + if i == -1: + x_slice = slice(None, 2) + elif i == self.x_length - 1: + x_slice = slice(-2, None) + else: + x_slice = slice(i, i + 2) + # fix y index + if j == -1: + j = 0 + y_slice = slice(None, 2) + elif j == self.y_length - 1: + j = -2 + y_slice = slice(-2, None) + else: + y_slice = slice(j, j + 2) + else: + if i == -1 or i == self.x_length - 1: + raise ValueError("Extrapolation not allowed!") + if j == -1 or j == self.y_length - 1: + raise ValueError("Extrapolation not allowed!") + + x1, x2 = x_index[x_slice] + y1, y2 = y_index[y_slice] + z11, z12 = values[j][x_slice] + z21, z22 = values[j + 1][x_slice] + + return (z11 * (x2 - x) * (y2 - y) + + z21 * (x - x1) * (y2 - y) + + z12 * (x2 - x) * (y - y1) + + z22 * (x - x1) * (y - y1)) / ((x2 - x1) * (y2 - y1)) + + + def _loadFile(self, filename): + f1 = open(filename, 'r') + + pointlist = [] + for line in f1.readlines(): + w = line.split() + xval = round(float(w[0]), 3) + yval = round(float(w[1]), 3) + zval = round(float(w[2]), 3) + pointlist.append((xval, yval,zval)) + cols = list(zip(*pointlist)) + + xcolpos = list(sorted(set(cols[0]))) + #ycolpos = list(sorted(set(cols[1]))) + zdict = {x:[] for x in xcolpos} + + for (x, y, z) in pointlist: + zdict[x].append(z) + + self.values = tuple(tuple(x) for x in [zdict[x] for x in sorted(xcolpos)]) + self.x_index = tuple(sorted(set(cols[0]))) + self.y_index = tuple(sorted(set(cols[1]))) + + # sanity check + x_length = len(self.x_index) + y_length = len(self.y_index) + + if x_length < 2 or y_length < 2: + raise ValueError("Probe grid must be at least 2x2.") + if y_length != len(self.values): + raise ValueError("Probe grid data must have equal number of rows to y_index.") + if any(x2 - x1 <= 0 for x1, x2 in zip(self.x_index, self.x_index[1:])): + raise ValueError("x_index must be in strictly ascending order!") + if any(y2 - y1 <= 0 for y1, y2 in zip(self.y_index, self.y_index[1:])): + raise ValueError("y_index must be in strictly ascending order!") + + self.x_length = x_length + self.y_length = y_length + + + def execute(self, obj): + if self.values is None: #No valid probe data. return unchanged path + obj.Path = obj.Base.Path + return + + if obj.Base: + if obj.Base.isDerivedFrom("Path::Feature"): + if obj.Base.Path: + if obj.Base.Path.Commands: + pp = obj.Base.Path.Commands + # process the path + + pathlist = pp + newcommandlist = [] + currLocation = {'X':0,'Y':0,'Z':0, 'F': 0} + + for c in pathlist: #obj.Base.Path.Commands: + newparams = dict(c.Parameters) + currLocation.update(newparams) + remapvar = newparams.pop("Z", None) + if remapvar is not None: + offset = self._bilinearInterpolate(currLocation['X'], currLocation['Y']) + newparams["Z"] = remapvar + offset + newcommand = Path.Command(c.Name, newparams) + newcommandlist.append(newcommand) + currLocation.update(newparams) + else: + newcommandlist.append(c) + + path = Path.Path(newcommandlist) + obj.Path = path + + +class ViewProviderDressup: + + def __init__(self, vobj): + vobj.Proxy = self + + def attach(self, vobj): + self.obj = vobj.Object + if self.obj and self.obj.Base: + for i in self.obj.Base.InList: + if hasattr(i, "Group"): + group = i.Group + for g in group: + if g.Name == self.obj.Base.Name: + group.remove(g) + i.Group = group + # FreeCADGui.ActiveDocument.getObject(obj.Base.Name).Visibility = False + return + + def claimChildren(self): + return [self.obj.Base] + + def __getstate__(self): + return None + + def __setstate__(self, state): + return None + + def onDelete(self, arg1=None, arg2=None): + '''this makes sure that the base operation is added back to the project and visible''' + FreeCADGui.ActiveDocument.getObject(arg1.Object.Base.Name).Visibility = True + job = PathUtils.findParentJob(arg1.Object) + job.Proxy.addOperation(arg1.Object.Base) + arg1.Object.Base = None + return True + +class CommandPathDressup: + + def GetResources(self): + return {'Pixmap': 'Path-Dressup', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "Z Depth Correction Dress-up"), + 'Accel': "", + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "Use Probe Map to correct Z depth")} + + def IsActive(self): + if FreeCAD.ActiveDocument is not None: + for o in FreeCAD.ActiveDocument.Objects: + if o.Name[:3] == "Job": + return True + return False + + def Activated(self): + + # check that the selection contains exactly what we want + selection = FreeCADGui.Selection.getSelection() + if len(selection) != 1: + FreeCAD.Console.PrintError(translate("Path_Dressup", "Please select one path object\n")) + return + if not selection[0].isDerivedFrom("Path::Feature"): + FreeCAD.Console.PrintError(translate("Path_Dressup", "The selected object is not a path\n")) + return + if selection[0].isDerivedFrom("Path::FeatureCompoundPython"): + FreeCAD.Console.PrintError(translate("Path_Dressup", "Please select a Path object")) + return + + # everything ok! + FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupZCorrect", "Create Dress-up")) + FreeCADGui.addModule("PathScripts.PathDressupZCorrect") + FreeCADGui.addModule("PathScripts.PathUtils") + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "ZCorrectDressup")') + FreeCADGui.doCommand('PathScripts.PathDressupZCorrect.ObjectDressup(obj)') + FreeCADGui.doCommand('obj.Base = FreeCAD.ActiveDocument.' + selection[0].Name) + FreeCADGui.doCommand('PathScripts.PathDressupZCorrect.ViewProviderDressup(obj.ViewObject)') + FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') + FreeCADGui.doCommand('Gui.ActiveDocument.getObject(obj.Base.Name).Visibility = False') + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() + + +if FreeCAD.GuiUp: + # register the FreeCAD command + FreeCADGui.addCommand('Path_DressupZCorrect', CommandPathDressup()) + +FreeCAD.Console.PrintLog("Loading PathDressup... done\n") diff --git a/src/Mod/Path/PathScripts/PathProbe.py b/src/Mod/Path/PathScripts/PathProbe.py new file mode 100644 index 0000000000..4bb66b1261 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathProbe.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2018 sliptonic * +# * * +# * 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. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +from __future__ import print_function + +import FreeCAD +import Path +import PathScripts.PathLog as PathLog +import PathScripts.PathOp as PathOp +import PathScripts.PathUtils as PathUtils + +#from PathScripts.PathUtils import waiting_effects +from PySide import QtCore + +__title__ = "Path Probing Operation" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Path Probing operation." + +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + + +# Qt tanslation handling +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + + +class ObjectProbing(PathOp.ObjectOp): + '''Proxy object for Probing operation.''' + + def opFeatures(self, obj): + '''opFeatures(obj) ... Probing works on the stock object.''' + return PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureTool + + def initOperation(self, obj): + obj.addProperty("App::PropertyLength", "Xoffset", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "X offset between tool and probe")) + obj.addProperty("App::PropertyLength", "Yoffset", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Y offset between tool and probe")) + obj.addProperty("App::PropertyInteger", "PointCountX", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Number of points to probe in X direction")).PointCountX=3 + obj.addProperty("App::PropertyInteger", "PointCountY", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Number of points to probe in Y direction")).PointCountY=3 + + def drange(self, start=1.0, stop=5.0, step=1.0): + r = start + while r <= stop: + yield r + r += step + + def opExecute(self, obj): + '''opExecute(obj) ... generate probe locations.''' + PathLog.track() + self.commandlist.append(Path.Command("(Begin Probing)")) + + stock = PathUtils.findParentJob(obj).Stock + bb = stock.Shape.BoundBox + + xdist = (bb.XMax - bb.XMin)/ (obj.PointCountX - 1) + ydist = (bb.YMax - bb.YMin)/ (obj.PointCountY - 1) + + self.commandlist.append(Path.Command("(PROBEOPEN probe_points.txt)")) + + self.commandlist.append(Path.Command("G0", {"X":bb.XMin, "Y":bb.YMin, "Z":obj.SafeHeight.Value})) + for x in self.drange(bb.XMin, bb.XMax, xdist): + for y in self.drange(bb.YMin, bb.YMax, ydist): + self.commandlist.append(Path.Command("G0", {"X":x + obj.Xoffset.Value, "Y":y + obj.Yoffset.Value, "Z":obj.SafeHeight.Value})) + self.commandlist.append(Path.Command("G38.2",{"Z":obj.FinalDepth.Value, "F":obj.ToolController.VertFeed.Value})) + self.commandlist.append(Path.Command("G0", {"Z":obj.SafeHeight.Value})) + + self.commandlist.append(Path.Command("(PROBECLOSE)")) + + + def opSetDefaultValues(self, obj, job): + '''opSetDefaultValues(obj, job) ... set default value for RetractHeight''' + +def SetupProperties(): + setup = [] + return setup + +def Create(name, obj = None): + '''Create(name) ... Creates and returns a Probing operation.''' + if obj is None: + obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) + proxy = ObjectProbing(obj, name) + return obj diff --git a/src/Mod/Path/PathScripts/PathProbeGui.py b/src/Mod/Path/PathScripts/PathProbeGui.py new file mode 100644 index 0000000000..eba3996517 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathProbeGui.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2017 sliptonic * +# * * +# * 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. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import FreeCAD +import FreeCADGui +import PathScripts.PathProbe as PathProbe +import PathScripts.PathOpGui as PathOpGui + +from PySide import QtCore + +__title__ = "Path Probing Operation UI" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Probing operation page controller and command implementation." + +class TaskPanelOpPage(PathOpGui.TaskPanelPage): + '''Page controller class for the Probing operation.''' + + def getForm(self): + '''getForm() ... returns UI''' + return FreeCADGui.PySideUic.loadUi(":/panels/PageOpProbeEdit.ui") + + def getFields(self, obj): + '''getFields(obj) ... transfers values from UI to obj's proprties''' + # if obj.StartVertex != self.form.startVertex.value(): + # obj.StartVertex = self.form.startVertex.value() + self.updateToolController(obj, self.form.toolController) + + def setFields(self, obj): + '''setFields(obj) ... transfers obj's property values to UI''' + #self.form.startVertex.setValue(obj.StartVertex) + self.setupToolController(obj, self.form.toolController) + + def getSignalsForUpdate(self, obj): + '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' + signals = [] + #signals.append(self.form.startVertex.editingFinished) + signals.append(self.form.toolController.currentIndexChanged) + return signals + +Command = PathOpGui.SetupOperation('Probe', + PathProbe.Create, + TaskPanelOpPage, + 'Path-Probe', + QtCore.QT_TRANSLATE_NOOP("Probe", "Probe"), + QtCore.QT_TRANSLATE_NOOP("Probe", "Create a Probing Grid from a job stock"))# PathProbe.SetupProperties) + +FreeCAD.Console.PrintLog("Loading PathProbeGui... done\n") + diff --git a/src/Mod/Path/PathScripts/PathSelection.py b/src/Mod/Path/PathScripts/PathSelection.py index acb9c473e2..386ff1c29c 100644 --- a/src/Mod/Path/PathScripts/PathSelection.py +++ b/src/Mod/Path/PathScripts/PathSelection.py @@ -167,13 +167,17 @@ class ADAPTIVEGate(PathBaseGate): obj = obj.Shape except Exception: # pylint: disable=broad-except return False - + return adaptive class CONTOURGate(PathBaseGate): def allow(self, doc, obj, sub): # pylint: disable=unused-argument pass +class PROBEGate: + def allow(self, doc, obj, sub): + pass + def contourselect(): FreeCADGui.Selection.addSelectionGate(CONTOURGate()) FreeCAD.Console.PrintWarning("Contour Select Mode\n") @@ -215,6 +219,10 @@ def surfaceselect(): # FreeCADGui.Selection.addSelectionGate(PROFILEGate()) # Added for face selection FreeCAD.Console.PrintWarning("Surfacing Select Mode\n") +def probeselect(): + FreeCADGui.Selection.addSelectionGate(PROBEGate()) + FreeCAD.Console.PrintWarning("Probe Select Mode\n") + def select(op): opsel = {} opsel['Contour'] = contourselect @@ -230,6 +238,7 @@ def select(op): opsel['Profile Faces'] = profileselect opsel['Surface'] = surfaceselect opsel['Adaptive'] = adaptiveselect + opsel['Probe'] = probeselect return opsel[op] def clear(): From 70a52a8eda7681ca4b1597576a686c5607d665b2 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Mon, 17 Sep 2018 10:53:23 -0500 Subject: [PATCH 053/117] Path: Layout op panel for probe --- .../Gui/Resources/panels/PageOpProbeEdit.ui | 133 +++++++++++++++--- src/Mod/Path/PathScripts/PathProbe.py | 11 +- src/Mod/Path/PathScripts/PathProbeGui.py | 29 +++- 3 files changed, 145 insertions(+), 28 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpProbeEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpProbeEdit.ui index c2c9a27309..f222080379 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpProbeEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpProbeEdit.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 140 + 424 + 376 @@ -47,27 +47,113 @@ - - - - - - Algorithm + + + Probe Grid Points + + + + + + 3 + + + 1000 - - - - - OCL Dropcutter - - - - - OCL Waterline - - + + + + 3 + + + 1000 + + + + + + + X: + + + + + + + Y: + + + + + + + + + + Probe + + + + + + Y Offset + + + + + + + + + + + + + + X Offset + + + + + + + + + + + + + + + + + Output + + + + + + File Name + + + + + + + <html><head/><body><p>Enter the filename where the probe points should be written.</p></body></html> + + + ProbePoints.txt + + + + + + + ... + @@ -88,6 +174,13 @@ + + + Gui::InputField + QLineEdit +
Gui/InputField.h
+
+
diff --git a/src/Mod/Path/PathScripts/PathProbe.py b/src/Mod/Path/PathScripts/PathProbe.py index 4bb66b1261..9e27b53411 100644 --- a/src/Mod/Path/PathScripts/PathProbe.py +++ b/src/Mod/Path/PathScripts/PathProbe.py @@ -60,9 +60,9 @@ class ObjectProbing(PathOp.ObjectOp): def initOperation(self, obj): obj.addProperty("App::PropertyLength", "Xoffset", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "X offset between tool and probe")) obj.addProperty("App::PropertyLength", "Yoffset", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Y offset between tool and probe")) - obj.addProperty("App::PropertyInteger", "PointCountX", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Number of points to probe in X direction")).PointCountX=3 - obj.addProperty("App::PropertyInteger", "PointCountY", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Number of points to probe in Y direction")).PointCountY=3 - + obj.addProperty("App::PropertyInteger", "PointCountX", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Number of points to probe in X direction")) + obj.addProperty("App::PropertyInteger", "PointCountY", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Number of points to probe in Y direction")) + obj.addProperty("App::PropertyFile", "OutputFileName", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The output location for the probe data to be written")) def drange(self, start=1.0, stop=5.0, step=1.0): r = start while r <= stop: @@ -80,7 +80,8 @@ class ObjectProbing(PathOp.ObjectOp): xdist = (bb.XMax - bb.XMin)/ (obj.PointCountX - 1) ydist = (bb.YMax - bb.YMin)/ (obj.PointCountY - 1) - self.commandlist.append(Path.Command("(PROBEOPEN probe_points.txt)")) + openstring = '(PROBEOPEN {})'.format(obj.OutputFileName) + self.commandlist.append(Path.Command(openstring)) self.commandlist.append(Path.Command("G0", {"X":bb.XMin, "Y":bb.YMin, "Z":obj.SafeHeight.Value})) for x in self.drange(bb.XMin, bb.XMax, xdist): @@ -96,7 +97,7 @@ class ObjectProbing(PathOp.ObjectOp): '''opSetDefaultValues(obj, job) ... set default value for RetractHeight''' def SetupProperties(): - setup = [] + setup = ['Xoffset', 'Yoffset', 'PointCountX', 'PointCountY'] return setup def Create(name, obj = None): diff --git a/src/Mod/Path/PathScripts/PathProbeGui.py b/src/Mod/Path/PathScripts/PathProbeGui.py index eba3996517..2ae4730924 100644 --- a/src/Mod/Path/PathScripts/PathProbeGui.py +++ b/src/Mod/Path/PathScripts/PathProbeGui.py @@ -26,8 +26,9 @@ import FreeCAD import FreeCADGui import PathScripts.PathProbe as PathProbe import PathScripts.PathOpGui as PathOpGui +import PathScripts.PathGui as PathGui -from PySide import QtCore +from PySide import QtCore, QtGui __title__ = "Path Probing Operation UI" __author__ = "sliptonic (Brad Collette)" @@ -46,25 +47,47 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): # if obj.StartVertex != self.form.startVertex.value(): # obj.StartVertex = self.form.startVertex.value() self.updateToolController(obj, self.form.toolController) + PathGui.updateInputField(obj, 'Xoffset', self.form.Xoffset) + PathGui.updateInputField(obj, 'Yoffset', self.form.Yoffset) + obj.PointCountX = self.form.PointCountX.value() + obj.PointCountY = self.form.PointCountY.value() + obj.OutputFileName = str(self.form.OutputFileName.text()) def setFields(self, obj): '''setFields(obj) ... transfers obj's property values to UI''' #self.form.startVertex.setValue(obj.StartVertex) self.setupToolController(obj, self.form.toolController) + self.form.Xoffset.setText(FreeCAD.Units.Quantity(obj.Xoffset.Value, FreeCAD.Units.Length).UserString) + self.form.Yoffset.setText(FreeCAD.Units.Quantity(obj.Yoffset.Value, FreeCAD.Units.Length).UserString) + self.form.OutputFileName.setText(obj.OutputFileName) + self.form.PointCountX.setValue(obj.PointCountX) + self.form.PointCountY.setValue(obj.PointCountY) def getSignalsForUpdate(self, obj): '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' signals = [] - #signals.append(self.form.startVertex.editingFinished) signals.append(self.form.toolController.currentIndexChanged) + signals.append(self.form.PointCountX.valueChanged) + signals.append(self.form.PointCountY.valueChanged) + signals.append(self.form.OutputFileName.editingFinished) + signals.append(self.form.Xoffset.valueChanged) + signals.append(self.form.Yoffset.valueChanged) + signals.append(self.form.SetOutputFileName.clicked) return signals + # def SetOutputFileName(self): + # filename = QtGui.QFileDialog.getSaveFileName(self.form, translate("Path_Probe", "Select Output File"), None, translate("Path_Probe", "All Files (*.*)")) + # if filename and filename[0]: + # self.obj.OutputFileName = str(filename[0]) + # self.setFields() + Command = PathOpGui.SetupOperation('Probe', PathProbe.Create, TaskPanelOpPage, 'Path-Probe', QtCore.QT_TRANSLATE_NOOP("Probe", "Probe"), - QtCore.QT_TRANSLATE_NOOP("Probe", "Create a Probing Grid from a job stock"))# PathProbe.SetupProperties) + QtCore.QT_TRANSLATE_NOOP("Probe", "Create a Probing Grid from a job stock"), + PathProbe.SetupProperties) FreeCAD.Console.PrintLog("Loading PathProbeGui... done\n") From 4b0f4e1ac006037575c99af8a4c4fe4624fe9cfe Mon Sep 17 00:00:00 2001 From: sliptonic Date: Mon, 17 Sep 2018 23:37:40 -0500 Subject: [PATCH 054/117] Path: make setupsheet handle PropertyFile --- .../Path/PathScripts/PathSetupSheetOpPrototypeGui.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py b/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py index f89b7b40ed..d648ca6a62 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py +++ b/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py @@ -188,6 +188,18 @@ class _PropertyFloatEditor(_PropertyEditor): def setModelData(self, widget): self.prop.setValue(widget.value()) +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() + widget.setText(text) + + def setModelData(self, widget): + self.prop.setValue(widget.text()) + _EditorFactory = { PathSetupSheetOpPrototype.Property: None, PathSetupSheetOpPrototype.PropertyAngle: _PropertyAngleEditor, From a8483c764e9422fb74a3b17123d869eedf7202c3 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 18 Sep 2018 09:46:04 -0500 Subject: [PATCH 055/117] Path: Finish GUI & Cleanup --- .../Path/PathScripts/PathDressupZCorrect.py | 42 +++++++++---------- src/Mod/Path/PathScripts/PathProbe.py | 23 +++++----- src/Mod/Path/PathScripts/PathProbeGui.py | 20 +++++---- 3 files changed, 42 insertions(+), 43 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupZCorrect.py b/src/Mod/Path/PathScripts/PathDressupZCorrect.py index ecb9dc43e3..5a49d4bc8a 100644 --- a/src/Mod/Path/PathScripts/PathDressupZCorrect.py +++ b/src/Mod/Path/PathScripts/PathDressupZCorrect.py @@ -20,6 +20,10 @@ # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * # * USA * # * * +# * Bilinear interpolation code modified heavily from the interpolation * +# * library https://github.com/pmav99/interpolation * +# * Copyright (c) 2013 by Panagiotis Mavrogiorgos * +# * * # *************************************************************************** import FreeCAD import FreeCADGui @@ -27,7 +31,7 @@ import Path import PathScripts.PathUtils as PathUtils from bisect import bisect_left -from PySide import QtCore, QtGui +from PySide import QtCore """Z Depth Correction Dressup. This dressup takes a probe file as input and does bilinear interpolation of the Zdepths to correct for a surface which is not parallel to the milling table/bed. The probe file should conform to the format specified by the linuxcnc G38 probe logging: 9-number coordinate consisting of XYZABCUVW http://linuxcnc.org/docs/html/gcode/g-code.html#gcode:g38 """ @@ -69,28 +73,22 @@ class ObjectDressup: i = bisect_left(x_index, x) - 1 j = bisect_left(y_index, y) - 1 - if True: #self.extrapolate: - # fix x index - if i == -1: - x_slice = slice(None, 2) - elif i == self.x_length - 1: - x_slice = slice(-2, None) - else: - x_slice = slice(i, i + 2) - # fix y index - if j == -1: - j = 0 - y_slice = slice(None, 2) - elif j == self.y_length - 1: - j = -2 - y_slice = slice(-2, None) - else: - y_slice = slice(j, j + 2) + # fix x index + if i == -1: + x_slice = slice(None, 2) + elif i == self.x_length - 1: + x_slice = slice(-2, None) else: - if i == -1 or i == self.x_length - 1: - raise ValueError("Extrapolation not allowed!") - if j == -1 or j == self.y_length - 1: - raise ValueError("Extrapolation not allowed!") + x_slice = slice(i, i + 2) + # fix y index + if j == -1: + j = 0 + y_slice = slice(None, 2) + elif j == self.y_length - 1: + j = -2 + y_slice = slice(-2, None) + else: + y_slice = slice(j, j + 2) x1, x2 = x_index[x_slice] y1, y2 = y_index[y_slice] diff --git a/src/Mod/Path/PathScripts/PathProbe.py b/src/Mod/Path/PathScripts/PathProbe.py index 9e27b53411..eb409d833d 100644 --- a/src/Mod/Path/PathScripts/PathProbe.py +++ b/src/Mod/Path/PathScripts/PathProbe.py @@ -63,11 +63,13 @@ class ObjectProbing(PathOp.ObjectOp): obj.addProperty("App::PropertyInteger", "PointCountX", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Number of points to probe in X direction")) obj.addProperty("App::PropertyInteger", "PointCountY", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Number of points to probe in Y direction")) obj.addProperty("App::PropertyFile", "OutputFileName", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The output location for the probe data to be written")) - def drange(self, start=1.0, stop=5.0, step=1.0): - r = start - while r <= stop: - yield r - r += step + + def nextpoint(self, startpoint=0.0, endpoint=0.0, count=3): + curstep = 0 + dist = (endpoint - startpoint) / (count - 1) + while curstep <= count-1: + yield startpoint + (curstep * dist) + curstep += 1 def opExecute(self, obj): '''opExecute(obj) ... generate probe locations.''' @@ -77,15 +79,12 @@ class ObjectProbing(PathOp.ObjectOp): stock = PathUtils.findParentJob(obj).Stock bb = stock.Shape.BoundBox - xdist = (bb.XMax - bb.XMin)/ (obj.PointCountX - 1) - ydist = (bb.YMax - bb.YMin)/ (obj.PointCountY - 1) - openstring = '(PROBEOPEN {})'.format(obj.OutputFileName) self.commandlist.append(Path.Command(openstring)) + self.commandlist.append(Path.Command("G0", {"Z":obj.ClearanceHeight.Value})) - self.commandlist.append(Path.Command("G0", {"X":bb.XMin, "Y":bb.YMin, "Z":obj.SafeHeight.Value})) - for x in self.drange(bb.XMin, bb.XMax, xdist): - for y in self.drange(bb.YMin, bb.YMax, ydist): + for x in self.nextpoint(bb.XMin, bb.XMax, obj.PointCountX): + for y in self.nextpoint(bb.YMin, bb.YMax, obj.PointCountY): self.commandlist.append(Path.Command("G0", {"X":x + obj.Xoffset.Value, "Y":y + obj.Yoffset.Value, "Z":obj.SafeHeight.Value})) self.commandlist.append(Path.Command("G38.2",{"Z":obj.FinalDepth.Value, "F":obj.ToolController.VertFeed.Value})) self.commandlist.append(Path.Command("G0", {"Z":obj.SafeHeight.Value})) @@ -97,7 +96,7 @@ class ObjectProbing(PathOp.ObjectOp): '''opSetDefaultValues(obj, job) ... set default value for RetractHeight''' def SetupProperties(): - setup = ['Xoffset', 'Yoffset', 'PointCountX', 'PointCountY'] + setup = ['Xoffset', 'Yoffset', 'PointCountX', 'PointCountY', 'OutputFileName'] return setup def Create(name, obj = None): diff --git a/src/Mod/Path/PathScripts/PathProbeGui.py b/src/Mod/Path/PathScripts/PathProbeGui.py index 2ae4730924..767bd1c573 100644 --- a/src/Mod/Path/PathScripts/PathProbeGui.py +++ b/src/Mod/Path/PathScripts/PathProbeGui.py @@ -35,6 +35,10 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" __doc__ = "Probing operation page controller and command implementation." +# Qt tanslation handling +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + class TaskPanelOpPage(PathOpGui.TaskPanelPage): '''Page controller class for the Probing operation.''' @@ -44,8 +48,6 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): def getFields(self, obj): '''getFields(obj) ... transfers values from UI to obj's proprties''' - # if obj.StartVertex != self.form.startVertex.value(): - # obj.StartVertex = self.form.startVertex.value() self.updateToolController(obj, self.form.toolController) PathGui.updateInputField(obj, 'Xoffset', self.form.Xoffset) PathGui.updateInputField(obj, 'Yoffset', self.form.Yoffset) @@ -55,7 +57,6 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): def setFields(self, obj): '''setFields(obj) ... transfers obj's property values to UI''' - #self.form.startVertex.setValue(obj.StartVertex) self.setupToolController(obj, self.form.toolController) self.form.Xoffset.setText(FreeCAD.Units.Quantity(obj.Xoffset.Value, FreeCAD.Units.Length).UserString) self.form.Yoffset.setText(FreeCAD.Units.Quantity(obj.Yoffset.Value, FreeCAD.Units.Length).UserString) @@ -72,14 +73,15 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): signals.append(self.form.OutputFileName.editingFinished) signals.append(self.form.Xoffset.valueChanged) signals.append(self.form.Yoffset.valueChanged) - signals.append(self.form.SetOutputFileName.clicked) + #signals.append(self.form.SetOutputFileName.clicked) + self.form.SetOutputFileName.clicked.connect(self.SetOutputFileName) return signals - # def SetOutputFileName(self): - # filename = QtGui.QFileDialog.getSaveFileName(self.form, translate("Path_Probe", "Select Output File"), None, translate("Path_Probe", "All Files (*.*)")) - # if filename and filename[0]: - # self.obj.OutputFileName = str(filename[0]) - # self.setFields() + def SetOutputFileName(self): + filename = QtGui.QFileDialog.getSaveFileName(self.form, translate("Path_Probe", "Select Output File"), None, translate("Path_Probe", "All Files (*.*)")) + if filename and filename[0]: + self.obj.OutputFileName = str(filename[0]) + self.setFields(self.obj) Command = PathOpGui.SetupOperation('Probe', PathProbe.Create, From 71da4906889fdacc32a192643aa6073c62aaa415 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Mon, 24 Sep 2018 12:37:50 -0500 Subject: [PATCH 056/117] Path: adding dressup task panel --- src/Mod/Path/Gui/Resources/Path.qrc | 1 + .../Path/Gui/Resources/panels/ZCorrectEdit.ui | 92 ++++++++ .../Path/PathScripts/PathDressupZCorrect.py | 209 +++++++++++------- src/Mod/Path/PathScripts/PathProbe.py | 4 +- 4 files changed, 229 insertions(+), 77 deletions(-) create mode 100644 src/Mod/Path/Gui/Resources/panels/ZCorrectEdit.ui diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index 184bbb6266..ca09e140b8 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -116,6 +116,7 @@ panels/ToolEditor.ui panels/ToolLibraryEditor.ui panels/TaskPathSimulator.ui + panels/ZCorrectEdit.ui preferences/PathDressupHoldingTags.ui preferences/PathJob.ui translations/Path_af.qm diff --git a/src/Mod/Path/Gui/Resources/panels/ZCorrectEdit.ui b/src/Mod/Path/Gui/Resources/panels/ZCorrectEdit.ui new file mode 100644 index 0000000000..36d58f0c6f --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/ZCorrectEdit.ui @@ -0,0 +1,92 @@ + + + TaskPanel + + + + 0 + 0 + 376 + 387 + + + + Z Depth Correction + + + + + + QFrame::NoFrame + + + 0 + + + + + 0 + 0 + 358 + 340 + + + + Dressup + + + + + + Probe Points File + + + + + + ... + + + + + + + File Name + + + + + + + <html><head/><body><p>Enter the filename containing the probe data</p></body></html> + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + diff --git a/src/Mod/Path/PathScripts/PathDressupZCorrect.py b/src/Mod/Path/PathScripts/PathDressupZCorrect.py index 5a49d4bc8a..2be8337f4b 100644 --- a/src/Mod/Path/PathScripts/PathDressupZCorrect.py +++ b/src/Mod/Path/PathScripts/PathDressupZCorrect.py @@ -27,15 +27,26 @@ # *************************************************************************** import FreeCAD import FreeCADGui +import Part import Path +import PathScripts.PathLog as PathLog import PathScripts.PathUtils as PathUtils -from bisect import bisect_left +#from bisect import bisect_left -from PySide import QtCore +from PySide import QtCore, QtGui """Z Depth Correction Dressup. This dressup takes a probe file as input and does bilinear interpolation of the Zdepths to correct for a surface which is not parallel to the milling table/bed. The probe file should conform to the format specified by the linuxcnc G38 probe logging: 9-number coordinate consisting of XYZABCUVW http://linuxcnc.org/docs/html/gcode/g-code.html#gcode:g38 """ +LOG_MODULE = PathLog.thisModule() + +if False: + PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE) + PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE) +else: + PathLog.setLevel(PathLog.Level.NOTICE, LOG_MODULE) + + # Qt tanslation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) @@ -45,16 +56,13 @@ rapidcommands = ['G0', 'G00'] arccommands = ['G2', 'G3', 'G02', 'G03'] class ObjectDressup: - x_index = 0 - y_index = 0 - values = None - x_length = 0 - y_length = 0 def __init__(self, obj): obj.addProperty("App::PropertyLink", "Base", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupAxisMap", "The base path to modify")) obj.addProperty("App::PropertyFile", "probefile", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "The point file from the surface probing.")) obj.Proxy = self + obj.addProperty("Part::PropertyPartShape", "interpSurface", "Path") + obj.setEditorMode('interpSurface', 2) # hide def __getstate__(self): return None @@ -64,88 +72,61 @@ class ObjectDressup: def onChanged(self, fp, prop): if str(prop) == "probefile": - self._loadFile(fp.probefile) + self._loadFile(fp, fp.probefile) - def _bilinearInterpolate(self, x, y): - # local lookups - x_index, y_index, values = self.x_index, self.y_index, self.values + def _bilinearInterpolate(self, surface, x, y): - i = bisect_left(x_index, x) - 1 - j = bisect_left(y_index, y) - 1 + print ('xval:{}, yval:{}'.format(x, y)) + p1 = FreeCAD.Vector(x, y, 100.0) + p2 = FreeCAD.Vector(x, y, -100.0) - # fix x index - if i == -1: - x_slice = slice(None, 2) - elif i == self.x_length - 1: - x_slice = slice(-2, None) - else: - x_slice = slice(i, i + 2) - # fix y index - if j == -1: - j = 0 - y_slice = slice(None, 2) - elif j == self.y_length - 1: - j = -2 - y_slice = slice(-2, None) - else: - y_slice = slice(j, j + 2) - - x1, x2 = x_index[x_slice] - y1, y2 = y_index[y_slice] - z11, z12 = values[j][x_slice] - z21, z22 = values[j + 1][x_slice] - - return (z11 * (x2 - x) * (y2 - y) + - z21 * (x - x1) * (y2 - y) + - z12 * (x2 - x) * (y - y1) + - z22 * (x - x1) * (y - y1)) / ((x2 - x1) * (y2 - y1)) + vertical_line = Part.Line(p1, p2) + points, curves = vertical_line.intersectCS(surface) + return points[0].Z - def _loadFile(self, filename): + def _loadFile(self, obj, filename): + if filename == "": + return + f1 = open(filename, 'r') - pointlist = [] - for line in f1.readlines(): - w = line.split() - xval = round(float(w[0]), 3) - yval = round(float(w[1]), 3) - zval = round(float(w[2]), 3) - pointlist.append((xval, yval,zval)) - cols = list(zip(*pointlist)) + try: + pointlist = [] + for line in f1.readlines(): + w = line.split() + xval = round(float(w[0]), 2) + yval = round(float(w[1]), 2) + zval = round(float(w[2]), 2) - xcolpos = list(sorted(set(cols[0]))) - #ycolpos = list(sorted(set(cols[1]))) - zdict = {x:[] for x in xcolpos} + pointlist.append([xval, yval,zval]) - for (x, y, z) in pointlist: - zdict[x].append(z) + cols = list(zip(*pointlist)) + yindex = list(sorted(set(cols[1]))) - self.values = tuple(tuple(x) for x in [zdict[x] for x in sorted(xcolpos)]) - self.x_index = tuple(sorted(set(cols[0]))) - self.y_index = tuple(sorted(set(cols[1]))) + array = [] + for y in yindex: + points = sorted([p for p in pointlist if p[1] == y]) + inner = [] + for p in points: + inner.append(FreeCAD.Vector(p[0],p[1],p[2])) + array.append(inner) - # sanity check - x_length = len(self.x_index) - y_length = len(self.y_index) + intSurf = Part.BSplineSurface() + intSurf.interpolate(array) - if x_length < 2 or y_length < 2: - raise ValueError("Probe grid must be at least 2x2.") - if y_length != len(self.values): - raise ValueError("Probe grid data must have equal number of rows to y_index.") - if any(x2 - x1 <= 0 for x1, x2 in zip(self.x_index, self.x_index[1:])): - raise ValueError("x_index must be in strictly ascending order!") - if any(y2 - y1 <= 0 for y1, y2 in zip(self.y_index, self.y_index[1:])): - raise ValueError("y_index must be in strictly ascending order!") - - self.x_length = x_length - self.y_length = y_length + obj.interpSurface = intSurf.toShape() + except: + raise ValueError("File does not contain appropriate point data") def execute(self, obj): - if self.values is None: #No valid probe data. return unchanged path + if obj.interpSurface.isNull(): #No valid probe data. return unchanged path obj.Path = obj.Base.Path return + surface = obj.interpSurface.toNurbs().Faces[0].Surface + if obj.Base: if obj.Base.isDerivedFrom("Path::Feature"): if obj.Base.Path: @@ -159,20 +140,92 @@ class ObjectDressup: for c in pathlist: #obj.Base.Path.Commands: newparams = dict(c.Parameters) + zval = newparams.pop("Z", currLocation['Z']) currLocation.update(newparams) - remapvar = newparams.pop("Z", None) - if remapvar is not None: - offset = self._bilinearInterpolate(currLocation['X'], currLocation['Y']) + if c.Name in movecommands: + remapvar = currLocation['Z'] + offset = self._bilinearInterpolate(surface, currLocation['X'], currLocation['Y']) newparams["Z"] = remapvar + offset newcommand = Path.Command(c.Name, newparams) newcommandlist.append(newcommand) currLocation.update(newparams) + currLocation['Z'] = zval else: newcommandlist.append(c) path = Path.Path(newcommandlist) obj.Path = path +class TaskPanel: + + def __init__(self, obj): + self.obj = obj + self.form = FreeCADGui.PySideUic.loadUi(":/panels/ZCorrectEdit.ui") + FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupZCorrect", "Edit Z Correction Dress-up")) + self.interpshape = FreeCAD.ActiveDocument.addObject("Part::Feature", "InterpolationSurface") + self.interpshape.Shape = obj.interpSurface + self.interpshape.ViewObject.Transparency = 60 + self.interpshape.ViewObject.ShapeColor = (1.00000,1.00000,0.01961) + self.interpshape.ViewObject.Selectable = False + stock = PathUtils.findParentJob(obj).Stock + self.interpshape.Placement.Base.z = stock.Shape.BoundBox.ZMax + def reject(self): + FreeCAD.ActiveDocument.abortTransaction() + FreeCADGui.Control.closeDialog() + FreeCAD.ActiveDocument.recompute() + + def accept(self): + self.getFields() + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.removeObject(self.interpshape.Name) + FreeCADGui.ActiveDocument.resetEdit() + FreeCADGui.Control.closeDialog() + FreeCAD.ActiveDocument.recompute() + FreeCAD.ActiveDocument.recompute() + + def getFields(self): + self.obj.Proxy.execute(self.obj) + + + def updateUI(self): + + if PathLog.getLevel(LOG_MODULE) == PathLog.Level.DEBUG: + for obj in FreeCAD.ActiveDocument.Objects: + if obj.Name.startswith('Shape'): + FreeCAD.ActiveDocument.removeObject(obj.Name) + print('object name %s' % self.obj.Name) + if hasattr(self.obj.Proxy, "shapes"): + PathLog.info("showing shapes attribute") + for shapes in self.obj.Proxy.shapes.itervalues(): + for shape in shapes: + Part.show(shape) + else: + PathLog.info("no shapes attribute found") + + def updateModel(self): + self.getFields() + self.updateUI() + FreeCAD.ActiveDocument.recompute() + + def setFields(self): + self.form.ProbePointFileName.setText(self.obj.probefile) + + self.updateUI() + + def open(self): + pass + def setupUi(self): + self.setFields() + # now that the form is filled, setup the signal handlers + self.form.ProbePointFileName.editingFinished.connect(self.updateModel) + self.form.SetProbePointFileName.clicked.connect(self.SetProbePointFileName) + + def SetProbePointFileName(self): + filename = QtGui.QFileDialog.getSaveFileName(self.form, translate("Path_Probe", "Select Probe Point File"), None, translate("Path_Probe", "All Files (*.*)")) + if filename and filename[0]: + self.obj.probefile = str(filename[0]) + self.setFields() + class ViewProviderDressup: @@ -189,12 +242,18 @@ class ViewProviderDressup: if g.Name == self.obj.Base.Name: group.remove(g) i.Group = group - # FreeCADGui.ActiveDocument.getObject(obj.Base.Name).Visibility = False return def claimChildren(self): return [self.obj.Base] + def setEdit(self, vobj, mode=0): + FreeCADGui.Control.closeDialog() + panel = TaskPanel(vobj.Object) + FreeCADGui.Control.showDialog(panel) + panel.setupUi() + return True + def __getstate__(self): return None diff --git a/src/Mod/Path/PathScripts/PathProbe.py b/src/Mod/Path/PathScripts/PathProbe.py index eb409d833d..c8ff986950 100644 --- a/src/Mod/Path/PathScripts/PathProbe.py +++ b/src/Mod/Path/PathScripts/PathProbe.py @@ -83,8 +83,8 @@ class ObjectProbing(PathOp.ObjectOp): self.commandlist.append(Path.Command(openstring)) self.commandlist.append(Path.Command("G0", {"Z":obj.ClearanceHeight.Value})) - for x in self.nextpoint(bb.XMin, bb.XMax, obj.PointCountX): - for y in self.nextpoint(bb.YMin, bb.YMax, obj.PointCountY): + for y in self.nextpoint(bb.YMin, bb.YMax, obj.PointCountY): + for x in self.nextpoint(bb.XMin, bb.XMax, obj.PointCountX): self.commandlist.append(Path.Command("G0", {"X":x + obj.Xoffset.Value, "Y":y + obj.Yoffset.Value, "Z":obj.SafeHeight.Value})) self.commandlist.append(Path.Command("G38.2",{"Z":obj.FinalDepth.Value, "F":obj.ToolController.VertFeed.Value})) self.commandlist.append(Path.Command("G0", {"Z":obj.SafeHeight.Value})) From 040324c6ae4c0da5b6cbe3b313978436808c3d1a Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sun, 7 Oct 2018 17:19:05 -0500 Subject: [PATCH 057/117] discretizing --- .../Path/PathScripts/PathDressupZCorrect.py | 57 ++++++++++++------- src/Mod/Path/PathScripts/PathGeom.py | 7 +++ src/Mod/Path/PathScripts/PathGuiInit.py | 2 + 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupZCorrect.py b/src/Mod/Path/PathScripts/PathDressupZCorrect.py index 2be8337f4b..65388caa21 100644 --- a/src/Mod/Path/PathScripts/PathDressupZCorrect.py +++ b/src/Mod/Path/PathScripts/PathDressupZCorrect.py @@ -29,6 +29,7 @@ import FreeCAD import FreeCADGui import Part import Path +import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import PathScripts.PathUtils as PathUtils #from bisect import bisect_left @@ -40,7 +41,7 @@ from PySide import QtCore, QtGui LOG_MODULE = PathLog.thisModule() -if False: +if True: PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE) PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE) else: @@ -59,11 +60,14 @@ class ObjectDressup: def __init__(self, obj): obj.addProperty("App::PropertyLink", "Base", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupAxisMap", "The base path to modify")) - obj.addProperty("App::PropertyFile", "probefile", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "The point file from the surface probing.")) + obj.addProperty("App::PropertyFile", "probefile", "ProbeData", QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "The point file from the surface probing.")) obj.Proxy = self obj.addProperty("Part::PropertyPartShape", "interpSurface", "Path") obj.setEditorMode('interpSurface', 2) # hide - + obj.addProperty("App::PropertyDistance", "ArcInterpolate", "Interpolate", QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "Deflection distance for arc interpolation")) + obj.addProperty("App::PropertyDistance", "SegInterpolate", "Interpolate", QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrectp", "break segments into smaller segments of this length.")) + obj.ArcInterpolate = 0.1 + obj.SegInterpolate = 1.0 def __getstate__(self): return None @@ -76,7 +80,6 @@ class ObjectDressup: def _bilinearInterpolate(self, surface, x, y): - print ('xval:{}, yval:{}'.format(x, y)) p1 = FreeCAD.Vector(x, y, 100.0) p2 = FreeCAD.Vector(x, y, -100.0) @@ -84,7 +87,6 @@ class ObjectDressup: points, curves = vertical_line.intersectCS(surface) return points[0].Z - def _loadFile(self, obj, filename): if filename == "": return @@ -100,9 +102,12 @@ class ObjectDressup: zval = round(float(w[2]), 2) pointlist.append([xval, yval,zval]) + PathLog.debug(pointlist) cols = list(zip(*pointlist)) + PathLog.debug("cols: {}".format(cols)) yindex = list(sorted(set(cols[1]))) + PathLog.debug("yindex: {}".format(yindex)) array = [] for y in yindex: @@ -121,6 +126,10 @@ class ObjectDressup: def execute(self, obj): + + sampleD = obj.SegInterpolate.Value + curveD = obj.ArcInterpolate.Value + if obj.interpSurface.isNull(): #No valid probe data. return unchanged path obj.Path = obj.Base.Path return @@ -131,28 +140,36 @@ class ObjectDressup: if obj.Base.isDerivedFrom("Path::Feature"): if obj.Base.Path: if obj.Base.Path.Commands: - pp = obj.Base.Path.Commands - # process the path + pathlist = obj.Base.Path.Commands - pathlist = pp newcommandlist = [] currLocation = {'X':0,'Y':0,'Z':0, 'F': 0} for c in pathlist: #obj.Base.Path.Commands: + PathLog.debug(c) + PathLog.debug(" curLoc:{}".format(currLocation)) newparams = dict(c.Parameters) - zval = newparams.pop("Z", currLocation['Z']) - currLocation.update(newparams) + zval = newparams.get("Z", currLocation['Z']) if c.Name in movecommands: - remapvar = currLocation['Z'] - offset = self._bilinearInterpolate(surface, currLocation['X'], currLocation['Y']) - newparams["Z"] = remapvar + offset - newcommand = Path.Command(c.Name, newparams) - newcommandlist.append(newcommand) - currLocation.update(newparams) - currLocation['Z'] = zval - else: - newcommandlist.append(c) + curVec = FreeCAD.Vector(currLocation['X'], currLocation['Y'], currLocation['Z']) + arcwire = PathGeom.edgeForCmd(c, curVec) + if arcwire is None: + continue + if c.Name in arccommands: + pointlist = arcwire.discretize(Deflection=curveD) + else: + pointlist = arcwire.discretize(Number=int(arcwire.Length / sampleD)) + for point in pointlist: + offset = self._bilinearInterpolate(surface, point.x, point.y) + newcommand = Path.Command("G1", {'X':point.x, 'Y':point.y, 'Z':point.z + offset}) + newcommandlist.append(newcommand) + currLocation.update(newcommand.Parameters) + currLocation['Z'] = zval + else: + # Non Feed Command + newcommandlist.append(c) + currLocation.update(c.Parameters) path = Path.Path(newcommandlist) obj.Path = path @@ -162,7 +179,7 @@ class TaskPanel: self.obj = obj self.form = FreeCADGui.PySideUic.loadUi(":/panels/ZCorrectEdit.ui") FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupZCorrect", "Edit Z Correction Dress-up")) - self.interpshape = FreeCAD.ActiveDocument.addObject("Part::Feature", "InterpolationSurface") + self.interpshape = FreeCAD.ActiveDocument.addObject("Part::Feature", "InterpolationSurface") self.interpshape.Shape = obj.interpSurface self.interpshape.ViewObject.Transparency = 60 self.interpshape.ViewObject.ShapeColor = (1.00000,1.00000,0.01961) diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 2fbaae4a40..fba8d1ddb1 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -313,6 +313,9 @@ def edgeForCmd(cmd, startPoint): """edgeForCmd(cmd, startPoint). Returns an Edge representing the given command, assuming a given startPoint.""" + PathLog.debug("cmd: {}".format(cmd)) + PathLog.debug("startpoint {}".format(startPoint)) + endPoint = commandEndPoint(cmd, startPoint) if (cmd.Name in CmdMoveStraight) or (cmd.Name in CmdMoveRapid): if pointsCoincide(startPoint, endPoint): @@ -343,6 +346,10 @@ def edgeForCmd(cmd, startPoint): 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("StartPoint:{}".format(startPoint)) + PathLog.debug("MidPoint:{}".format(midPoint)) + PathLog.debug("EndPoint:{}".format(endPoint)) + return Part.Edge(Part.Arc(startPoint, midPoint, endPoint)) # It's a Helix diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index 57f302fc3a..29272c0382 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -51,6 +51,7 @@ def Startup(): from PathScripts import PathDressupPathBoundaryGui from PathScripts import PathDressupTagGui from PathScripts import PathDressupLeadInOut + from PathScripts import PathDressupZCorrect from PathScripts import PathDrillingGui from PathScripts import PathEngraveGui from PathScripts import PathFixture @@ -61,6 +62,7 @@ def Startup(): from PathScripts import PathPocketGui from PathScripts import PathPocketShapeGui from PathScripts import PathPost + from PathScripts import PathProbeGui from PathScripts import PathProfileContourGui from PathScripts import PathProfileEdgesGui from PathScripts import PathProfileFacesGui From 4e91020c1f25cc88bb3435d5b90693ff75974da1 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Fri, 6 Mar 2020 13:20:50 -0600 Subject: [PATCH 058/117] fix error when discretize N=1 cleanup InitGui to add Path_Probe command Probe setupsheet --- src/Mod/Path/InitGui.py | 2 +- .../Path/PathScripts/PathDressupZCorrect.py | 38 +++++++++++-------- src/Mod/Path/PathScripts/PathProbe.py | 14 +++---- src/Mod/Path/PathScripts/PathProbeGui.py | 17 ++++----- .../PathScripts/PathSetupSheetOpPrototype.py | 1 + 5 files changed, 40 insertions(+), 32 deletions(-) diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 64a48bc54a..dc638a9299 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -87,7 +87,7 @@ class PathWorkbench (Workbench): # build commands list projcmdlist = ["Path_Job", "Path_Post"] toolcmdlist = ["Path_Inspect", "Path_Simulator", "Path_ToolLibraryEdit", "Path_SelectLoop", "Path_OpActiveToggle"] - prepcmdlist = ["Path_Fixture", "Path_Comment", "Path_Stop", "Path_Custom"] + prepcmdlist = ["Path_Fixture", "Path_Comment", "Path_Stop", "Path_Custom", "Path_Probe"] twodopcmdlist = ["Path_Contour", "Path_Profile_Faces", "Path_Profile_Edges", "Path_Pocket_Shape", "Path_Drilling", "Path_MillFace", "Path_Helix", "Path_Adaptive" ] threedopcmdlist = ["Path_Pocket_3D"] engravecmdlist = ["Path_Engrave", "Path_Deburr"] diff --git a/src/Mod/Path/PathScripts/PathDressupZCorrect.py b/src/Mod/Path/PathScripts/PathDressupZCorrect.py index 65388caa21..c035451829 100644 --- a/src/Mod/Path/PathScripts/PathDressupZCorrect.py +++ b/src/Mod/Path/PathScripts/PathDressupZCorrect.py @@ -32,7 +32,6 @@ import Path import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import PathScripts.PathUtils as PathUtils -#from bisect import bisect_left from PySide import QtCore, QtGui @@ -41,7 +40,7 @@ from PySide import QtCore, QtGui LOG_MODULE = PathLog.thisModule() -if True: +if False: PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE) PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE) else: @@ -52,10 +51,12 @@ else: 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'] + class ObjectDressup: def __init__(self, obj): @@ -68,6 +69,7 @@ class ObjectDressup: obj.addProperty("App::PropertyDistance", "SegInterpolate", "Interpolate", QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrectp", "break segments into smaller segments of this length.")) obj.ArcInterpolate = 0.1 obj.SegInterpolate = 1.0 + def __getstate__(self): return None @@ -91,7 +93,7 @@ class ObjectDressup: if filename == "": return - f1 = open(filename, 'r') + f1 = open(filename, 'r') try: pointlist = [] @@ -101,7 +103,7 @@ class ObjectDressup: yval = round(float(w[1]), 2) zval = round(float(w[2]), 2) - pointlist.append([xval, yval,zval]) + pointlist.append([xval, yval, zval]) PathLog.debug(pointlist) cols = list(zip(*pointlist)) @@ -114,23 +116,22 @@ class ObjectDressup: points = sorted([p for p in pointlist if p[1] == y]) inner = [] for p in points: - inner.append(FreeCAD.Vector(p[0],p[1],p[2])) + inner.append(FreeCAD.Vector(p[0], p[1], p[2])) array.append(inner) intSurf = Part.BSplineSurface() intSurf.interpolate(array) obj.interpSurface = intSurf.toShape() - except: + except Exception: raise ValueError("File does not contain appropriate point data") - def execute(self, obj): sampleD = obj.SegInterpolate.Value curveD = obj.ArcInterpolate.Value - if obj.interpSurface.isNull(): #No valid probe data. return unchanged path + if obj.interpSurface.isNull(): # No valid probe data. return unchanged path obj.Path = obj.Base.Path return @@ -143,9 +144,9 @@ class ObjectDressup: pathlist = obj.Base.Path.Commands newcommandlist = [] - currLocation = {'X':0,'Y':0,'Z':0, 'F': 0} + currLocation = {'X': 0, 'Y': 0, 'Z': 0, 'F': 0} - for c in pathlist: #obj.Base.Path.Commands: + for c in pathlist: PathLog.debug(c) PathLog.debug(" curLoc:{}".format(currLocation)) newparams = dict(c.Parameters) @@ -156,12 +157,16 @@ class ObjectDressup: if arcwire is None: continue if c.Name in arccommands: - pointlist = arcwire.discretize(Deflection=curveD) + pointlist = arcwire.discretize(Deflection=curveD) else: - pointlist = arcwire.discretize(Number=int(arcwire.Length / sampleD)) + disc_number = int(arcwire.Length / sampleD) + if disc_number > 1: + pointlist = arcwire.discretize(Number=int(arcwire.Length / sampleD)) + else: + pointlist = [v.Point for v in arcwire.Vertexes] for point in pointlist: offset = self._bilinearInterpolate(surface, point.x, point.y) - newcommand = Path.Command("G1", {'X':point.x, 'Y':point.y, 'Z':point.z + offset}) + newcommand = Path.Command("G1", {'X': point.x, 'Y': point.y, 'Z': point.z + offset}) newcommandlist.append(newcommand) currLocation.update(newcommand.Parameters) currLocation['Z'] = zval @@ -173,6 +178,7 @@ class ObjectDressup: path = Path.Path(newcommandlist) obj.Path = path + class TaskPanel: def __init__(self, obj): @@ -182,10 +188,11 @@ class TaskPanel: self.interpshape = FreeCAD.ActiveDocument.addObject("Part::Feature", "InterpolationSurface") self.interpshape.Shape = obj.interpSurface self.interpshape.ViewObject.Transparency = 60 - self.interpshape.ViewObject.ShapeColor = (1.00000,1.00000,0.01961) + self.interpshape.ViewObject.ShapeColor = (1.00000, 1.00000, 0.01961) self.interpshape.ViewObject.Selectable = False stock = PathUtils.findParentJob(obj).Stock self.interpshape.Placement.Base.z = stock.Shape.BoundBox.ZMax + def reject(self): FreeCAD.ActiveDocument.abortTransaction() FreeCADGui.Control.closeDialog() @@ -203,7 +210,6 @@ class TaskPanel: def getFields(self): self.obj.Proxy.execute(self.obj) - def updateUI(self): if PathLog.getLevel(LOG_MODULE) == PathLog.Level.DEBUG: @@ -231,6 +237,7 @@ class TaskPanel: def open(self): pass + def setupUi(self): self.setFields() # now that the form is filled, setup the signal handlers @@ -285,6 +292,7 @@ class ViewProviderDressup: arg1.Object.Base = None return True + class CommandPathDressup: def GetResources(self): diff --git a/src/Mod/Path/PathScripts/PathProbe.py b/src/Mod/Path/PathScripts/PathProbe.py index c8ff986950..c5e8b34f81 100644 --- a/src/Mod/Path/PathScripts/PathProbe.py +++ b/src/Mod/Path/PathScripts/PathProbe.py @@ -30,7 +30,6 @@ import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathUtils as PathUtils -#from PathScripts.PathUtils import waiting_effects from PySide import QtCore __title__ = "Path Probing Operation" @@ -81,25 +80,26 @@ class ObjectProbing(PathOp.ObjectOp): openstring = '(PROBEOPEN {})'.format(obj.OutputFileName) self.commandlist.append(Path.Command(openstring)) - self.commandlist.append(Path.Command("G0", {"Z":obj.ClearanceHeight.Value})) + self.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) for y in self.nextpoint(bb.YMin, bb.YMax, obj.PointCountY): for x in self.nextpoint(bb.XMin, bb.XMax, obj.PointCountX): - self.commandlist.append(Path.Command("G0", {"X":x + obj.Xoffset.Value, "Y":y + obj.Yoffset.Value, "Z":obj.SafeHeight.Value})) - self.commandlist.append(Path.Command("G38.2",{"Z":obj.FinalDepth.Value, "F":obj.ToolController.VertFeed.Value})) - self.commandlist.append(Path.Command("G0", {"Z":obj.SafeHeight.Value})) + self.commandlist.append(Path.Command("G0", {"X": x + obj.Xoffset.Value, "Y": y + obj.Yoffset.Value, "Z": obj.SafeHeight.Value})) + self.commandlist.append(Path.Command("G38.2", {"Z": obj.FinalDepth.Value, "F": obj.ToolController.VertFeed.Value})) + self.commandlist.append(Path.Command("G0", {"Z": obj.SafeHeight.Value})) self.commandlist.append(Path.Command("(PROBECLOSE)")) - def opSetDefaultValues(self, obj, job): '''opSetDefaultValues(obj, job) ... set default value for RetractHeight''' + def SetupProperties(): setup = ['Xoffset', 'Yoffset', 'PointCountX', 'PointCountY', 'OutputFileName'] return setup -def Create(name, obj = None): + +def Create(name, obj=None): '''Create(name) ... Creates and returns a Probing operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) diff --git a/src/Mod/Path/PathScripts/PathProbeGui.py b/src/Mod/Path/PathScripts/PathProbeGui.py index 767bd1c573..9da563fa48 100644 --- a/src/Mod/Path/PathScripts/PathProbeGui.py +++ b/src/Mod/Path/PathScripts/PathProbeGui.py @@ -35,10 +35,12 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" __doc__ = "Probing operation page controller and command implementation." + # Qt tanslation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) + class TaskPanelOpPage(PathOpGui.TaskPanelPage): '''Page controller class for the Probing operation.''' @@ -73,7 +75,6 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): signals.append(self.form.OutputFileName.editingFinished) signals.append(self.form.Xoffset.valueChanged) signals.append(self.form.Yoffset.valueChanged) - #signals.append(self.form.SetOutputFileName.clicked) self.form.SetOutputFileName.clicked.connect(self.SetOutputFileName) return signals @@ -83,13 +84,11 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.obj.OutputFileName = str(filename[0]) self.setFields(self.obj) -Command = PathOpGui.SetupOperation('Probe', - PathProbe.Create, - TaskPanelOpPage, - 'Path-Probe', - QtCore.QT_TRANSLATE_NOOP("Probe", "Probe"), - QtCore.QT_TRANSLATE_NOOP("Probe", "Create a Probing Grid from a job stock"), - PathProbe.SetupProperties) + +Command = PathOpGui.SetupOperation('Probe', PathProbe.Create, TaskPanelOpPage, + 'Path-Probe', + QtCore.QT_TRANSLATE_NOOP("Probe", "Probe"), + QtCore.QT_TRANSLATE_NOOP("Probe", "Create a Probing Grid from a job stock"), + PathProbe.SetupProperties) FreeCAD.Console.PrintLog("Loading PathProbeGui... done\n") - diff --git a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototype.py b/src/Mod/Path/PathScripts/PathSetupSheetOpPrototype.py index fa99cbc72b..d089dd7f7a 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototype.py +++ b/src/Mod/Path/PathScripts/PathSetupSheetOpPrototype.py @@ -149,6 +149,7 @@ class OpPrototype(object): 'App::PropertyBool': PropertyBool, 'App::PropertyDistance': PropertyDistance, 'App::PropertyEnumeration': PropertyEnumeration, + 'App::PropertyFile': PropertyString, 'App::PropertyFloat': PropertyFloat, 'App::PropertyFloatConstraint': Property, 'App::PropertyFloatList': Property, From 7fa8cc83da0642822a75ed9eda747ffe0fb9c34d Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 24 Mar 2020 12:24:27 -0600 Subject: [PATCH 059/117] Part: remove excessive spacing in strings --- src/Mod/Part/Gui/AttacherTexts.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Mod/Part/Gui/AttacherTexts.cpp b/src/Mod/Part/Gui/AttacherTexts.cpp index 0c72be68f7..5480ef61a7 100644 --- a/src/Mod/Part/Gui/AttacherTexts.cpp +++ b/src/Mod/Part/Gui/AttacherTexts.cpp @@ -52,13 +52,13 @@ TextSet getUIStrings(Base::Type attacherType, eMapMode mmode) return TwoStrings(qApp->translate("Attacher3D", "Translate origin","Attachment3D mode caption"), qApp->translate("Attacher3D", "Origin is aligned to match Vertex. Orientation is controlled by Placement property.","Attachment3D mode tooltip")); case mmObjectXY: - return TwoStrings(qApp->translate("Attacher3D", "Object's X Y Z","Attachment3D mode caption"), + return TwoStrings(qApp->translate("Attacher3D", "Object's X Y Z","Attachment3D mode caption"), qApp->translate("Attacher3D", "Placement is made equal to Placement of linked object.","Attachment3D mode tooltip")); case mmObjectXZ: - return TwoStrings(qApp->translate("Attacher3D", "Object's X Z-Y","Attachment3D mode caption"), + return TwoStrings(qApp->translate("Attacher3D", "Object's X Z Y","Attachment3D mode caption"), qApp->translate("Attacher3D", "X', Y', Z' axes are matched with object's local X, Z, -Y, respectively.","Attachment3D mode tooltip")); case mmObjectYZ: - return TwoStrings(qApp->translate("Attacher3D", "Object's Y Z X","Attachment3D mode caption"), + return TwoStrings(qApp->translate("Attacher3D", "Object's Y Z X","Attachment3D mode caption"), qApp->translate("Attacher3D", "X', Y', Z' axes are matched with object's local Y, Z, X, respectively.","Attachment3D mode tooltip")); case mmFlatFace: return TwoStrings(qApp->translate("Attacher3D", "XY on plane","Attachment3D mode caption"), @@ -133,7 +133,7 @@ TextSet getUIStrings(Base::Type attacherType, eMapMode mmode) return TwoStrings(qApp->translate("Attacher2D", "Object's XZ","AttachmentPlane mode caption"), qApp->translate("Attacher2D", "Plane is aligned to XZ local plane of linked object.","AttachmentPlane mode tooltip")); case mmObjectYZ: - return TwoStrings(qApp->translate("Attacher2D", "Object's YZ","AttachmentPlane mode caption"), + return TwoStrings(qApp->translate("Attacher2D", "Object's YZ","AttachmentPlane mode caption"), qApp->translate("Attacher2D", "Plane is aligned to YZ local plane of linked object.","AttachmentPlane mode tooltip")); case mmFlatFace: return TwoStrings(qApp->translate("Attacher2D", "Plane face","AttachmentPlane mode caption"), From 8ce0fdee0c365efd6a7572ef6f33404e6956a0c0 Mon Sep 17 00:00:00 2001 From: donovaly Date: Fri, 27 Mar 2020 00:04:00 +0100 Subject: [PATCH 060/117] [TD] fix preferences dialogs see: https://forum.freecadweb.org/viewtopic.php?p=380990#p381051 --- src/Mod/TechDraw/Gui/DlgPrefsTechDraw1.ui | 103 ++-- src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui | 587 +++++++++++++--------- src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui | 126 +++-- 3 files changed, 504 insertions(+), 312 deletions(-) diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw1.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw1.ui index 819c9f728c..9ff2e1e169 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw1.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw1.ui @@ -7,7 +7,7 @@ 0 0 460 - 829 + 760
@@ -19,7 +19,7 @@ 0 - 760 + 0 @@ -28,8 +28,8 @@ - - + + @@ -40,7 +40,7 @@ 0 - 141 + 0 @@ -52,9 +52,9 @@ Drawing Update - - - + + + @@ -80,7 +80,7 @@ - + @@ -88,6 +88,12 @@ 0 + + + 0 + 20 + + Whether or not a page's 'Keep Update' property can override the global 'Update With 3D' parameter @@ -106,7 +112,7 @@ can override the global 'Update With 3D' parameter - + @@ -137,7 +143,7 @@ This can slow down the response time. - + @@ -145,6 +151,12 @@ This can slow down the response time. 0 + + + 0 + 20 + + true @@ -173,7 +185,7 @@ for ProjectionGroups - + @@ -216,7 +228,7 @@ for ProjectionGroups Normal line color - + 0 0 @@ -286,7 +298,7 @@ for ProjectionGroups Preselection color - + 255 255 @@ -318,7 +330,7 @@ for ProjectionGroups Section face color - + 225 225 @@ -350,7 +362,7 @@ for ProjectionGroups Selected item color - + 28 173 @@ -402,7 +414,7 @@ for ProjectionGroups Background color around pages - + 80 80 @@ -434,7 +446,7 @@ for ProjectionGroups Hatch image color - + 0 0 @@ -461,7 +473,7 @@ for ProjectionGroups Color of dimension lines and text. - + 0 0 @@ -493,7 +505,7 @@ for ProjectionGroups Geometric hatch pattern color - + 0 0 @@ -550,6 +562,12 @@ for ProjectionGroups + + + 0 + 20 + + true @@ -574,7 +592,7 @@ for ProjectionGroups Face color (if not transparent) - + 255 255 @@ -618,7 +636,7 @@ for ProjectionGroups Default color for leader lines - + 0 0 @@ -635,7 +653,7 @@ for ProjectionGroups - + 0 0 @@ -655,7 +673,7 @@ for ProjectionGroups - + @@ -771,7 +789,7 @@ for ProjectionGroups - + 0 @@ -781,10 +799,7 @@ for ProjectionGroups Label size - - Qt::AlignRight - - + 8.000000000000000 @@ -800,7 +815,7 @@ for ProjectionGroups - + @@ -834,7 +849,7 @@ for ProjectionGroups - + 0 @@ -885,7 +900,7 @@ for ProjectionGroups - + 191 @@ -895,9 +910,6 @@ for ProjectionGroups Starting directory for menu 'Insert Page using Template' - - Gui::FileChooser::Directory - TemplateDir @@ -925,7 +937,7 @@ for ProjectionGroups - + 191 @@ -962,7 +974,7 @@ for ProjectionGroups - + 191 @@ -999,7 +1011,7 @@ for ProjectionGroups - + 191 @@ -1009,9 +1021,6 @@ for ProjectionGroups Default directory for welding symbols - - Gui::FileChooser::Directory - WeldingDir @@ -1039,7 +1048,7 @@ for ProjectionGroups - + 191 @@ -1105,7 +1114,7 @@ for ProjectionGroups - + @@ -1124,15 +1133,15 @@ for ProjectionGroups - - + + Qt::Vertical - 17 - 17 + 20 + 19 diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui index c3e3c2d63b..49317bca81 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui @@ -6,8 +6,8 @@ 0 0 - 448 - 891 + 460 + 790 @@ -19,8 +19,8 @@ Dimensions - - + + @@ -31,7 +31,7 @@ 0 - 250 + 0 @@ -43,214 +43,22 @@ Dimensions - - - - - - - - 0 - 0 - - - - - 12 - - - - Character used to indicate diameter dimensions - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - DiameterSymbol - - - /Mod/TechDraw/Dimensions - - - - - - - false - - - - 0 - 0 - - - - Number of decimals if 'Use Global Decimals' is not used - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 2 - - - AltDecimals - - - /Mod/TechDraw/Dimensions - - - - - - - - 0 - 0 - - - - Use system setting for number of decimals - - - Use Global Decimals - - - true - - - UseGlobalDecimals - - - /Mod/TechDraw/Dimensions - - - - - - - - true - - - - Font Size - - - - - - - - 0 - 0 - - - - Arrowhead style - - - -1 - - - ArrowStyle - - - Mod/TechDraw/Dimensions - - - - - - - Alternate Decimals - - - + + + + + + 0 + 0 + + Standard and Style - - - - - true - - - - Arrow Size - - - - - - - - true - - - - Arrow Style - - - - - - - - 0 - 0 - - - - Dimension text font size - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 4.000000000000000 - - - FontSize - - - /Mod/TechDraw/Dimensions - - - - - - - - 0 - 0 - - - - Arrowhead size - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 5.000000000000000 - - - ArrowSize - - - Mod/TechDraw/Dimensions - - - @@ -259,6 +67,12 @@ 0 + + + 184 + 22 + + Standard to be used for dimensional values @@ -290,10 +104,109 @@ - - + + + + + 0 + 0 + + + + Use system setting for number of decimals + - Diameter Symbol + Use Global Decimals + + + true + + + UseGlobalDecimals + + + /Mod/TechDraw/Dimensions + + + + + + + + 0 + 0 + + + + + 0 + 22 + + + + Append unit to dimension values + + + Show Units + + + ShowUnits + + + /Mod/TechDraw/Dimensions + + + + + + + Alternate Decimals + + + + + + + false + + + + 0 + 0 + + + + + 0 + 22 + + + + Number of decimals if 'Use Global Decimals' is not used + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 2 + + + AltDecimals + + + /Mod/TechDraw/Dimensions + + + + + + + + true + + + + Font Size @@ -310,34 +223,163 @@ - - + + 0 0 - - Append unit to dimension values + + + 0 + 22 + - - Show Units + + Dimension text font size + + + 4.000000000000000 - ShowUnits + FontSize /Mod/TechDraw/Dimensions + + + + Diameter Symbol + + + + + + + + 0 + 0 + + + + + 0 + 22 + + + + + 12 + + + + Character used to indicate diameter dimensions + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + DiameterSymbol + + + /Mod/TechDraw/Dimensions + + + + + + + + true + + + + Arrow Style + + + + + + + + 0 + 0 + + + + + 0 + 22 + + + + Arrowhead style + + + -1 + + + ArrowStyle + + + Mod/TechDraw/Dimensions + + + + + + + + true + + + + Arrow Size + + + + + + + + 0 + 0 + + + + + 0 + 22 + + + + Arrowhead size + + + 5.000000000000000 + + + ArrowSize + + + Mod/TechDraw/Dimensions + + + - + @@ -371,6 +413,12 @@ 0 + + + 0 + 0 + + Style for section lines @@ -465,6 +513,12 @@ + + + 0 + 22 + + Default appearance of cut surface in section view @@ -507,6 +561,12 @@ 0 + + + 0 + 0 + + Forces last leader line segment to be horizontal @@ -525,20 +585,23 @@ - + 0 0 + + + 0 + 0 + + Length of balloon leader line kink - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + 5.000000000000000 @@ -557,6 +620,12 @@ 0 + + + 0 + 0 + + Type for centerlines @@ -633,6 +702,12 @@ 0 + + + 0 + 0 + + Shape of balloon annotations @@ -727,6 +802,12 @@ 0 + + + 184 + 0 + + Standard to be used to draw section lines @@ -772,6 +853,12 @@ 0 + + + 0 + 0 + + Outline shape for detail views @@ -821,6 +908,12 @@ 0 + + + 0 + 0 + + true @@ -848,6 +941,12 @@ 0 + + + 0 + 0 + + Default name in LineGroup CSV file @@ -885,6 +984,12 @@ 0 + + + 0 + 0 + + Style for balloon leader line ends @@ -919,6 +1024,12 @@ 0 + + + 0 + 0 + + Restrict Filled Triangle line end to vertical or horizontal directions @@ -968,6 +1079,12 @@ 0 + + + 0 + 0 + + Show arc centers in printed output @@ -999,6 +1116,12 @@ + + + 0 + 0 + + 2 @@ -1069,7 +1192,7 @@ - + @@ -1129,6 +1252,12 @@ 0 + + + 184 + 0 + + Use first- or third-angle mutliview projection convention @@ -1165,6 +1294,12 @@ 0 + + + 0 + 0 + + Style for hidden lines @@ -1199,7 +1334,7 @@ - + @@ -1218,7 +1353,7 @@ - + Qt::Vertical @@ -1226,7 +1361,7 @@ 20 - 40 + 20 diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui index 399558154c..8c9b8c1cea 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui @@ -6,47 +6,15 @@ 0 0 - 440 - 381 + 460 + 330 Advanced - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 12 - true - - - - QFrame::Box - - - Items in italics are default values for new objects. They have no effect on existing objects. - - - true - - - - + + @@ -57,7 +25,7 @@ 0 - 300 + 0 @@ -69,8 +37,8 @@ Advanced - - + + @@ -80,6 +48,12 @@ 0 + + + 0 + 20 + + false @@ -114,6 +88,12 @@ Only change unless you know what you are doing! + + + 0 + 20 + + Limit of 64x64 pixel SVG tiles used to hatch a single face. For large scalings you might get an error about to many SVG tiles. @@ -150,6 +130,12 @@ Then you need to increase the tile limit. 0 + + + 0 + 20 + + Dump intermediate results during Detail view processing @@ -166,6 +152,12 @@ Then you need to increase the tile limit. + + + 0 + 20 + + Include 2D Objects in projection @@ -213,6 +205,12 @@ Then you need to increase the tile limit. 0 + + + 0 + 20 + + true @@ -240,6 +238,12 @@ Then you need to increase the tile limit. 0 + + + 0 + 20 + + Highlights border of section cut in section views @@ -256,6 +260,12 @@ Then you need to increase the tile limit. + + + 0 + 20 + + Maximum hatch line segments to use when hatching a face with a PAT pattern @@ -386,6 +396,12 @@ can be a performance penalty in complex models. + + + 0 + 20 + + Override automatic dimension format @@ -405,6 +421,38 @@ can be a performance penalty in complex models. + + + + + 12 + true + + + + QFrame::Box + + + Items in italics are default values for new objects. They have no effect on existing objects. + + + true + + + + + + + Qt::Vertical + + + + 20 + 20 + + + + From a2140c7e559d3e4fb13db423dd27bc3912ff6102 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Thu, 26 Mar 2020 08:22:00 +0100 Subject: [PATCH 061/117] FEM: solid selection, code improvements --- .../Fem/femguiobjects/FemSelectionWidgets.py | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/Mod/Fem/femguiobjects/FemSelectionWidgets.py b/src/Mod/Fem/femguiobjects/FemSelectionWidgets.py index 8b808cf902..8e9eb5d647 100644 --- a/src/Mod/Fem/femguiobjects/FemSelectionWidgets.py +++ b/src/Mod/Fem/femguiobjects/FemSelectionWidgets.py @@ -463,50 +463,55 @@ class GeometryElementsSelection(QtGui.QWidget): self.sel_server = FemSelectionObserver(self.selectionParser, print_message) def selectionParser(self, selection): - print("selection: {} {} {}".format( + FreeCAD.Console.PrintMessage("Selection: {} {} {}\n".format( selection[0].Shape.ShapeType, selection[0].Name, selection[1] )) if hasattr(selection[0], "Shape") and selection[1]: - elt = selection[0].Shape.getElement(selection[1]) + sobj = selection[0] + elt = sobj.Shape.getElement(selection[1]) ele_ShapeType = elt.ShapeType if self.selection_mode_solid and "Solid" in self.sel_elem_types: # in solid selection mode use edges and faces for selection of a solid # adapt selection variable to hold the Solid solid_to_add = None if ele_ShapeType == "Edge": - found_edge = False - for i, s in enumerate(selection[0].Shape.Solids): + found_eltedge_in_other_solid = False + for i, s in enumerate(sobj.Shape.Solids): for e in s.Edges: if elt.isSame(e): - if not found_edge: + if found_eltedge_in_other_solid is False: solid_to_add = str(i + 1) else: + # could be more than two solids, think of polar pattern FreeCAD.Console.PrintMessage( - "Edge belongs to more than one solid\n" + " Edge belongs to at least two solids: Solid{}, Solid{}\n" + .format(solid_to_add, str(i + 1)) ) solid_to_add = None - found_edge = True + found_eltedge_in_other_solid = True elif ele_ShapeType == "Face": - found_face = False - for i, s in enumerate(selection[0].Shape.Solids): + found_eltface_in_other_solid = False + for i, s in enumerate(sobj.Shape.Solids): for e in s.Faces: if elt.isSame(e): - if not found_face: + if not found_eltface_in_other_solid: solid_to_add = str(i + 1) else: + # AFAIK (bernd) a face can only belong to two solids FreeCAD.Console.PrintMessage( - "Face belongs to more than one solid\n" + " Face belongs to two solids: Solid{}, Solid{}\n" + .format(solid_to_add, str(i + 1)) ) solid_to_add = None - found_face = True + found_eltface_in_other_solid = True if solid_to_add: - selection = (selection[0], "Solid" + solid_to_add) + selection = (sobj, "Solid" + solid_to_add) ele_ShapeType = "Solid" FreeCAD.Console.PrintMessage( - "selection variable adapted to hold the Solid: {} {} {}\n" - .format(selection[0].Shape.ShapeType, selection[0].Name, selection[1]) + " Selection variable adapted to hold the Solid: {} {} {}\n" + .format(sobj.Shape.ShapeType, sobj.Name, selection[1]) ) else: return @@ -533,11 +538,11 @@ class GeometryElementsSelection(QtGui.QWidget): # selected shape will not added to the list FreeCADGui.Selection.clearSelection() message = ( - "{} is in reference list already!\n" + " Selection {} is in reference list already!\n" .format(self.get_item_text(selection)) ) FreeCAD.Console.PrintMessage(message) - QtGui.QMessageBox.critical(None, "Geometry already in list", message) + QtGui.QMessageBox.critical(None, "Geometry already in list", message.lstrip(" ")) else: # selected shape will not added to the list FreeCADGui.Selection.clearSelection() From 8b48fdb0abd90aa4f25207c1d9c1d7a823cc761e Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Fri, 27 Mar 2020 05:21:36 +0100 Subject: [PATCH 062/117] FEM: cmake sort --- src/Mod/Fem/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index bfb9553629..ac7d898d29 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -221,12 +221,12 @@ SET(FemTestsMesh_SRCS SET(FemTools_SRCS femtools/__init__.py - femtools/membertools.py femtools/ccxtools.py femtools/checksanalysis.py femtools/constants.py femtools/errors.py femtools/femutils.py + femtools/membertools.py femtools/tokrules.py ) From e61699fc952f908ab83ca22a6bd24c18bd7b6a0c Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Fri, 27 Mar 2020 05:33:58 +0100 Subject: [PATCH 063/117] FEM: geom tools, add new module and move some geom tools from mesh tools in --- src/Mod/Fem/CMakeLists.txt | 1 + src/Mod/Fem/femmesh/gmshtools.py | 3 +- src/Mod/Fem/femmesh/meshtools.py | 110 +----------------- src/Mod/Fem/femsolver/calculix/writer.py | 5 +- src/Mod/Fem/femtools/geomtools.py | 140 +++++++++++++++++++++++ 5 files changed, 148 insertions(+), 111 deletions(-) create mode 100644 src/Mod/Fem/femtools/geomtools.py diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index ac7d898d29..18f21804ca 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -226,6 +226,7 @@ SET(FemTools_SRCS femtools/constants.py femtools/errors.py femtools/femutils.py + femtools/geomtools.py femtools/membertools.py femtools/tokrules.py ) diff --git a/src/Mod/Fem/femmesh/gmshtools.py b/src/Mod/Fem/femmesh/gmshtools.py index bf577033f7..7a07276155 100644 --- a/src/Mod/Fem/femmesh/gmshtools.py +++ b/src/Mod/Fem/femmesh/gmshtools.py @@ -37,6 +37,7 @@ from FreeCAD import Units import Fem from . import meshtools from femtools import femutils +from femtools import geomtools class GmshTools(): @@ -451,7 +452,7 @@ class GmshTools(): for eleml in self.ele_length_map: # the method getElement(element) does not return Solid elements ele_shape = meshtools.get_element(self.part_obj, eleml) - ele_vertexes = meshtools.get_vertexes_by_element(self.part_obj.Shape, ele_shape) + ele_vertexes = geomtools.get_vertexes_by_element(self.part_obj.Shape, ele_shape) self.ele_node_map[eleml] = ele_vertexes Console.PrintMessage(" {}\n".format(self.ele_length_map)) Console.PrintMessage(" {}\n".format(self.ele_node_map)) diff --git a/src/Mod/Fem/femmesh/meshtools.py b/src/Mod/Fem/femmesh/meshtools.py index 23bc864b11..fe6293bd87 100644 --- a/src/Mod/Fem/femmesh/meshtools.py +++ b/src/Mod/Fem/femmesh/meshtools.py @@ -29,6 +29,8 @@ __url__ = "http://www.freecadweb.org" import FreeCAD +from femtools import geomtools + # ************************************************************************************************ def get_femnodes_by_femobj_with_references( @@ -2111,55 +2113,6 @@ def find_element_in_shape( FreeCAD.Console.PrintError("Compound is not supported.\n") -# ************************************************************************************************ -def get_vertexes_by_element( - aShape, - anElement -): - # we're going to extend the method find_element_in_shape and return the vertexes - # import Part - ele_vertexes = [] - ele_st = anElement.ShapeType - if ele_st == "Solid" or ele_st == "CompSolid": - for index, solid in enumerate(aShape.Solids): - if is_same_geometry(solid, anElement): - for vele in aShape.Solids[index].Vertexes: - for i, v in enumerate(aShape.Vertexes): - if vele.isSame(v): # use isSame, because orientation could be different - ele_vertexes.append(i) - # FreeCAD.Console.PrintMessage(" " + str(sorted(ele_vertexes)), "\n") - return ele_vertexes - FreeCAD.Console.PrintError( - "Error, Solid " + str(anElement) + " not found in: " + str(aShape) + "\n" - ) - elif ele_st == "Face" or ele_st == "Shell": - for index, face in enumerate(aShape.Faces): - if is_same_geometry(face, anElement): - for vele in aShape.Faces[index].Vertexes: - for i, v in enumerate(aShape.Vertexes): - if vele.isSame(v): # use isSame, because orientation could be different - ele_vertexes.append(i) - # FreeCAD.Console.PrintMessage(" " + str(sorted(ele_vertexes)) + "\n") - return ele_vertexes - elif ele_st == "Edge" or ele_st == "Wire": - for index, edge in enumerate(aShape.Edges): - if is_same_geometry(edge, anElement): - for vele in aShape.Edges[index].Vertexes: - for i, v in enumerate(aShape.Vertexes): - if vele.isSame(v): # use isSame, because orientation could be different - ele_vertexes.append(i) - # FreeCAD.Console.PrintMessage(" " + str(sorted(ele_vertexes)) + "\n") - return ele_vertexes - elif ele_st == "Vertex": - for index, vertex in enumerate(aShape.Vertexes): - if is_same_geometry(vertex, anElement): - ele_vertexes.append(index) - # FreeCAD.Console.PrintMessage(" " + str(sorted(ele_vertexes)) + "\n") - return ele_vertexes - elif ele_st == "Compound": - FreeCAD.Console.PrintError("Compound is not supported.\n") - - # ************************************************************************************************ def is_same_geometry( shape1, @@ -2374,65 +2327,6 @@ def get_three_non_colinear_nodes( return [node_1, node_2, node_3] -# ************************************************************************************************ -def get_rectangular_coords( - obj -): - from math import cos, sin, radians - A = [1, 0, 0] - B = [0, 1, 0] - a_x = A[0] - a_y = A[1] - a_z = A[2] - b_x = B[0] - b_y = B[1] - b_z = B[2] - x_rot = radians(obj.X_rot) - y_rot = radians(obj.Y_rot) - z_rot = radians(obj.Z_rot) - if obj.X_rot != 0: - a_y = A[1] * cos(x_rot) + A[2] * sin(x_rot) - a_z = A[2] * cos(x_rot) - A[1] * sin(x_rot) - b_y = B[1] * cos(x_rot) + B[2] * sin(x_rot) - b_z = B[2] * cos(x_rot) - B[1] * sin(x_rot) - if obj.Y_rot != 0: - a_x = A[0] * cos(y_rot) - A[2] * sin(y_rot) - a_z = A[2] * cos(y_rot) + A[0] * sin(y_rot) - b_x = B[0] * cos(y_rot) - B[2] * sin(y_rot) - b_z = B[2] * cos(y_rot) + B[0] * sin(z_rot) - if obj.Z_rot != 0: - a_x = A[0] * cos(z_rot) + A[1] * sin(z_rot) - a_y = A[1] * cos(z_rot) - A[0] * sin(z_rot) - b_x = B[0] * cos(z_rot) + B[1] * sin(z_rot) - b_y = B[1] * cos(z_rot) - B[0] * sin(z_rot) - A = [a_x, a_y, a_z] - B = [b_x, b_y, b_z] - A_coords = str(round(A[0], 4)) + "," + str(round(A[1], 4)) + "," + str(round(A[2], 4)) - B_coords = str(round(B[0], 4)) + "," + str(round(B[1], 4)) + "," + str(round(B[2], 4)) - coords = A_coords + "," + B_coords - return coords - - -# ************************************************************************************************ -def get_cylindrical_coords( - obj -): - vec = obj.Axis - base = obj.BasePoint - Ax = base[0] + 10 * vec[0] - Ay = base[1] + 10 * vec[1] - Az = base[2] + 10 * vec[2] - Bx = base[0] - 10 * vec[0] - By = base[1] - 10 * vec[1] - Bz = base[2] - 10 * vec[2] - A = [Ax, Ay, Az] - B = [Bx, By, Bz] - A_coords = str(A[0]) + "," + str(A[1]) + "," + str(A[2]) - B_coords = str(B[0]) + "," + str(B[1]) + "," + str(B[2]) - coords = A_coords + "," + B_coords - return coords - - # ************************************************************************************************ def write_D_network_element_to_inputfile( fileName diff --git a/src/Mod/Fem/femsolver/calculix/writer.py b/src/Mod/Fem/femsolver/calculix/writer.py index f73228f8f7..cda2e38200 100644 --- a/src/Mod/Fem/femsolver/calculix/writer.py +++ b/src/Mod/Fem/femsolver/calculix/writer.py @@ -39,6 +39,7 @@ import FreeCAD from .. import writerbase from femmesh import meshtools +from femtools import geomtools class FemInputWriterCcx(writerbase.FemInputWriter): @@ -1084,11 +1085,11 @@ class FemInputWriterCcx(writerbase.FemInputWriter): f.write("** " + trans_obj.Label + "\n") if trans_obj.TransformType == "Rectangular": f.write("*TRANSFORM, NSET=Rect" + trans_obj.Name + ", TYPE=R\n") - coords = meshtools.get_rectangular_coords(trans_obj) + coords = geomtools.get_rectangular_coords(trans_obj) f.write(coords + "\n") elif trans_obj.TransformType == "Cylindrical": f.write("*TRANSFORM, NSET=Cylin" + trans_obj.Name + ", TYPE=C\n") - coords = meshtools.get_cylindrical_coords(trans_obj) + coords = geomtools.get_cylindrical_coords(trans_obj) f.write(coords + "\n") def write_constraints_selfweight(self, f): diff --git a/src/Mod/Fem/femtools/geomtools.py b/src/Mod/Fem/femtools/geomtools.py new file mode 100644 index 0000000000..73df2688c9 --- /dev/null +++ b/src/Mod/Fem/femtools/geomtools.py @@ -0,0 +1,140 @@ +# *************************************************************************** +# * Copyright (c) 2020 Bernd Hahnebach * +# * * +# * 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. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +__title__ = "FEM geometry tools" +__author__ = "Markus Hovorka, Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +import FreeCAD + +from . import femutils + + +# ************************************************************************************************ +def get_vertexes_by_element( + aShape, + anElement +): + # we're going to extend the method find_element_in_shape and return the vertexes + # import Part + ele_vertexes = [] + ele_st = anElement.ShapeType + if ele_st == "Solid" or ele_st == "CompSolid": + for index, solid in enumerate(aShape.Solids): + if is_same_geometry(solid, anElement): + for vele in aShape.Solids[index].Vertexes: + for i, v in enumerate(aShape.Vertexes): + if vele.isSame(v): # use isSame, because orientation could be different + ele_vertexes.append(i) + # FreeCAD.Console.PrintMessage(" " + str(sorted(ele_vertexes)), "\n") + return ele_vertexes + FreeCAD.Console.PrintError( + "Error, Solid " + str(anElement) + " not found in: " + str(aShape) + "\n" + ) + elif ele_st == "Face" or ele_st == "Shell": + for index, face in enumerate(aShape.Faces): + if is_same_geometry(face, anElement): + for vele in aShape.Faces[index].Vertexes: + for i, v in enumerate(aShape.Vertexes): + if vele.isSame(v): # use isSame, because orientation could be different + ele_vertexes.append(i) + # FreeCAD.Console.PrintMessage(" " + str(sorted(ele_vertexes)) + "\n") + return ele_vertexes + elif ele_st == "Edge" or ele_st == "Wire": + for index, edge in enumerate(aShape.Edges): + if is_same_geometry(edge, anElement): + for vele in aShape.Edges[index].Vertexes: + for i, v in enumerate(aShape.Vertexes): + if vele.isSame(v): # use isSame, because orientation could be different + ele_vertexes.append(i) + # FreeCAD.Console.PrintMessage(" " + str(sorted(ele_vertexes)) + "\n") + return ele_vertexes + elif ele_st == "Vertex": + for index, vertex in enumerate(aShape.Vertexes): + if is_same_geometry(vertex, anElement): + ele_vertexes.append(index) + # FreeCAD.Console.PrintMessage(" " + str(sorted(ele_vertexes)) + "\n") + return ele_vertexes + elif ele_st == "Compound": + FreeCAD.Console.PrintError("Compound is not supported.\n") + + +# ************************************************************************************************ +def get_rectangular_coords( + obj +): + from math import cos, sin, radians + A = [1, 0, 0] + B = [0, 1, 0] + a_x = A[0] + a_y = A[1] + a_z = A[2] + b_x = B[0] + b_y = B[1] + b_z = B[2] + x_rot = radians(obj.X_rot) + y_rot = radians(obj.Y_rot) + z_rot = radians(obj.Z_rot) + if obj.X_rot != 0: + a_y = A[1] * cos(x_rot) + A[2] * sin(x_rot) + a_z = A[2] * cos(x_rot) - A[1] * sin(x_rot) + b_y = B[1] * cos(x_rot) + B[2] * sin(x_rot) + b_z = B[2] * cos(x_rot) - B[1] * sin(x_rot) + if obj.Y_rot != 0: + a_x = A[0] * cos(y_rot) - A[2] * sin(y_rot) + a_z = A[2] * cos(y_rot) + A[0] * sin(y_rot) + b_x = B[0] * cos(y_rot) - B[2] * sin(y_rot) + b_z = B[2] * cos(y_rot) + B[0] * sin(z_rot) + if obj.Z_rot != 0: + a_x = A[0] * cos(z_rot) + A[1] * sin(z_rot) + a_y = A[1] * cos(z_rot) - A[0] * sin(z_rot) + b_x = B[0] * cos(z_rot) + B[1] * sin(z_rot) + b_y = B[1] * cos(z_rot) - B[0] * sin(z_rot) + A = [a_x, a_y, a_z] + B = [b_x, b_y, b_z] + A_coords = str(round(A[0], 4)) + "," + str(round(A[1], 4)) + "," + str(round(A[2], 4)) + B_coords = str(round(B[0], 4)) + "," + str(round(B[1], 4)) + "," + str(round(B[2], 4)) + coords = A_coords + "," + B_coords + return coords + + +# ************************************************************************************************ +def get_cylindrical_coords( + obj +): + vec = obj.Axis + base = obj.BasePoint + Ax = base[0] + 10 * vec[0] + Ay = base[1] + 10 * vec[1] + Az = base[2] + 10 * vec[2] + Bx = base[0] - 10 * vec[0] + By = base[1] - 10 * vec[1] + Bz = base[2] - 10 * vec[2] + A = [Ax, Ay, Az] + B = [Bx, By, Bz] + A_coords = str(A[0]) + "," + str(A[1]) + "," + str(A[2]) + B_coords = str(B[0]) + "," + str(B[1]) + "," + str(B[2]) + coords = A_coords + "," + B_coords + return coords + + From 5d9bb6a9f0e92d3feaf3bb68d25e4788620a4db7 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Fri, 27 Mar 2020 05:39:56 +0100 Subject: [PATCH 064/117] FEM: geom tools, move find element in shape and is same geometry from meshtools --- src/Mod/Fem/femmesh/gmshtools.py | 4 +- src/Mod/Fem/femmesh/meshtools.py | 98 +------------------------------ src/Mod/Fem/femtools/geomtools.py | 94 +++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 98 deletions(-) diff --git a/src/Mod/Fem/femmesh/gmshtools.py b/src/Mod/Fem/femmesh/gmshtools.py index 7a07276155..e0ca1e3827 100644 --- a/src/Mod/Fem/femmesh/gmshtools.py +++ b/src/Mod/Fem/femmesh/gmshtools.py @@ -415,7 +415,7 @@ class GmshTools(): # the method getElement(element) # does not return Solid elements ele_shape = meshtools.get_element(sub[0], elems) - found_element = meshtools.find_element_in_shape( + found_element = geomtools.find_element_in_shape( self.part_obj.Shape, ele_shape ) if found_element: @@ -503,7 +503,7 @@ class GmshTools(): # and use the found element as elems # the method getElement(element) does not return Solid elements ele_shape = meshtools.get_element(sub[0], elems) - found_element = meshtools.find_element_in_shape( + found_element = geomtools.find_element_in_shape( self.part_obj.Shape, ele_shape ) diff --git a/src/Mod/Fem/femmesh/meshtools.py b/src/Mod/Fem/femmesh/meshtools.py index fe6293bd87..26759c425e 100644 --- a/src/Mod/Fem/femmesh/meshtools.py +++ b/src/Mod/Fem/femmesh/meshtools.py @@ -1944,7 +1944,7 @@ def get_reference_group_elements( "Error, two refshapes in References with different ShapeTypes.\n" ) FreeCAD.Console.PrintLog("\n".format(ref_shape)) - found_element = find_element_in_shape(aShape, ref_shape) + found_element = geomtools.find_element_in_shape(aShape, ref_shape) if found_element is not None: elements.append(found_element) else: @@ -1978,7 +1978,7 @@ def get_reference_group_elements( else: FreeCAD.Console.PrintError("This should not happen, please debug!\n") # in this case we would not have needed to use the - # is_same_geometry() inside find_element_in_shape() + # is_same_geometry() inside geomtools.find_element_in_shape() # AFAIK we could have used the Part methods isPartner() or even isSame() # We're going to find out when we need to debug this :-)! return (key, sorted(elements)) @@ -2058,100 +2058,6 @@ def get_anlysis_empty_references_group_elements( return group_elements -# ************************************************************************************************ -def find_element_in_shape( - aShape, - anElement -): - # import Part - ele_st = anElement.ShapeType - if ele_st == "Solid" or ele_st == "CompSolid": - for index, solid in enumerate(aShape.Solids): - # FreeCAD.Console.PrintMessage("{}\n".format(is_same_geometry(solid, anElement))) - if is_same_geometry(solid, anElement): - # FreeCAD.Console.PrintMessage("{}\n".format(index)) - # Part.show(aShape.Solids[index]) - ele = ele_st + str(index + 1) - return ele - FreeCAD.Console.PrintError( - "Solid " + str(anElement) + " not found in: " + str(aShape) + "\n" - ) - if ele_st == "Solid" and aShape.ShapeType == "Solid": - message_part = ( - "We have been searching for a Solid in a Solid and we have not found it. " - "In most cases this should be searching for a Solid inside a CompSolid. " - "Check the ShapeType of your Part to mesh." - ) - FreeCAD.Console.PrintMessage(message_part + "\n") - # Part.show(anElement) - # Part.show(aShape) - elif ele_st == "Face" or ele_st == "Shell": - for index, face in enumerate(aShape.Faces): - # FreeCAD.Console.PrintMessage("{}\n".format(is_same_geometry(face, anElement))) - if is_same_geometry(face, anElement): - # FreeCAD.Console.PrintMessage("{}\n".format(index)) - # Part.show(aShape.Faces[index]) - ele = ele_st + str(index + 1) - return ele - elif ele_st == "Edge" or ele_st == "Wire": - for index, edge in enumerate(aShape.Edges): - # FreeCAD.Console.PrintMessage("{}\n".format(is_same_geometry(edge, anElement))) - if is_same_geometry(edge, anElement): - # FreeCAD.Console.PrintMessage(index, "\n") - # Part.show(aShape.Edges[index]) - ele = ele_st + str(index + 1) - return ele - elif ele_st == "Vertex": - for index, vertex in enumerate(aShape.Vertexes): - # FreeCAD.Console.PrintMessage("{}\n".format(is_same_geometry(vertex, anElement))) - if is_same_geometry(vertex, anElement): - # FreeCAD.Console.PrintMessage("{}\n".format(index)) - # Part.show(aShape.Vertexes[index]) - ele = ele_st + str(index + 1) - return ele - elif ele_st == "Compound": - FreeCAD.Console.PrintError("Compound is not supported.\n") - - -# ************************************************************************************************ -def is_same_geometry( - shape1, - shape2 -): - # the vertexes and the CenterOfMass are compared - # it is a hack, but I do not know any better ! - # check of Volume and Area before starting with the vertices could be added - # BoundBox is possible too, but is BB calculations robust?! - # FreeCAD.Console.PrintMessage("{}\n".format(shape1)) - # FreeCAD.Console.PrintMessage("{}\n".format(shape2)) - same_Vertexes = 0 - if len(shape1.Vertexes) == len(shape2.Vertexes) and len(shape1.Vertexes) > 1: - # compare CenterOfMass - if shape1.CenterOfMass != shape2.CenterOfMass: - return False - else: - # compare the Vertexes - for vs1 in shape1.Vertexes: - for vs2 in shape2.Vertexes: - if vs1.X == vs2.X and vs1.Y == vs2.Y and vs1.Z == vs2.Z: - same_Vertexes += 1 - continue - # FreeCAD.Console.PrintMessage("{}\n".(same_Vertexes)) - if same_Vertexes == len(shape1.Vertexes): - return True - else: - return False - if len(shape1.Vertexes) == len(shape2.Vertexes) and len(shape1.Vertexes) == 1: - vs1 = shape1.Vertexes[0] - vs2 = shape2.Vertexes[0] - if vs1.X == vs2.X and vs1.Y == vs2.Y and vs1.Z == vs2.Z: - return True - else: - return False - else: - return False - - # ************************************************************************************************ def get_element( part, diff --git a/src/Mod/Fem/femtools/geomtools.py b/src/Mod/Fem/femtools/geomtools.py index 73df2688c9..c34078367c 100644 --- a/src/Mod/Fem/femtools/geomtools.py +++ b/src/Mod/Fem/femtools/geomtools.py @@ -30,6 +30,61 @@ import FreeCAD from . import femutils +# ************************************************************************************************ +def find_element_in_shape( + aShape, + anElement +): + # import Part + ele_st = anElement.ShapeType + if ele_st == "Solid" or ele_st == "CompSolid": + for index, solid in enumerate(aShape.Solids): + # FreeCAD.Console.PrintMessage("{}\n".format(is_same_geometry(solid, anElement))) + if is_same_geometry(solid, anElement): + # FreeCAD.Console.PrintMessage("{}\n".format(index)) + # Part.show(aShape.Solids[index]) + ele = ele_st + str(index + 1) + return ele + FreeCAD.Console.PrintError( + "Solid " + str(anElement) + " not found in: " + str(aShape) + "\n" + ) + if ele_st == "Solid" and aShape.ShapeType == "Solid": + message_part = ( + "We have been searching for a Solid in a Solid and we have not found it. " + "In most cases this should be searching for a Solid inside a CompSolid. " + "Check the ShapeType of your Part to mesh." + ) + FreeCAD.Console.PrintMessage(message_part + "\n") + # Part.show(anElement) + # Part.show(aShape) + elif ele_st == "Face" or ele_st == "Shell": + for index, face in enumerate(aShape.Faces): + # FreeCAD.Console.PrintMessage("{}\n".format(is_same_geometry(face, anElement))) + if is_same_geometry(face, anElement): + # FreeCAD.Console.PrintMessage("{}\n".format(index)) + # Part.show(aShape.Faces[index]) + ele = ele_st + str(index + 1) + return ele + elif ele_st == "Edge" or ele_st == "Wire": + for index, edge in enumerate(aShape.Edges): + # FreeCAD.Console.PrintMessage("{}\n".format(is_same_geometry(edge, anElement))) + if is_same_geometry(edge, anElement): + # FreeCAD.Console.PrintMessage(index, "\n") + # Part.show(aShape.Edges[index]) + ele = ele_st + str(index + 1) + return ele + elif ele_st == "Vertex": + for index, vertex in enumerate(aShape.Vertexes): + # FreeCAD.Console.PrintMessage("{}\n".format(is_same_geometry(vertex, anElement))) + if is_same_geometry(vertex, anElement): + # FreeCAD.Console.PrintMessage("{}\n".format(index)) + # Part.show(aShape.Vertexes[index]) + ele = ele_st + str(index + 1) + return ele + elif ele_st == "Compound": + FreeCAD.Console.PrintError("Compound is not supported.\n") + + # ************************************************************************************************ def get_vertexes_by_element( aShape, @@ -79,6 +134,45 @@ def get_vertexes_by_element( FreeCAD.Console.PrintError("Compound is not supported.\n") +# ************************************************************************************************ +def is_same_geometry( + shape1, + shape2 +): + # the vertexes and the CenterOfMass are compared + # it is a hack, but I do not know any better ! + # check of Volume and Area before starting with the vertices could be added + # BoundBox is possible too, but is BB calculations robust?! + # FreeCAD.Console.PrintMessage("{}\n".format(shape1)) + # FreeCAD.Console.PrintMessage("{}\n".format(shape2)) + same_Vertexes = 0 + if len(shape1.Vertexes) == len(shape2.Vertexes) and len(shape1.Vertexes) > 1: + # compare CenterOfMass + if shape1.CenterOfMass != shape2.CenterOfMass: + return False + else: + # compare the Vertexes + for vs1 in shape1.Vertexes: + for vs2 in shape2.Vertexes: + if vs1.X == vs2.X and vs1.Y == vs2.Y and vs1.Z == vs2.Z: + same_Vertexes += 1 + continue + # FreeCAD.Console.PrintMessage("{}\n".(same_Vertexes)) + if same_Vertexes == len(shape1.Vertexes): + return True + else: + return False + if len(shape1.Vertexes) == len(shape2.Vertexes) and len(shape1.Vertexes) == 1: + vs1 = shape1.Vertexes[0] + vs2 = shape2.Vertexes[0] + if vs1.X == vs2.X and vs1.Y == vs2.Y and vs1.Z == vs2.Z: + return True + else: + return False + else: + return False + + # ************************************************************************************************ def get_rectangular_coords( obj From 6c399ae851ae7d202b511599070c25578ab6ac5e Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Fri, 27 Mar 2020 05:52:34 +0100 Subject: [PATCH 065/117] FEM: geom tools, move get element --- .../Fem/femguiobjects/FemSelectionWidgets.py | 6 ++--- src/Mod/Fem/femmesh/gmshtools.py | 6 ++--- src/Mod/Fem/femmesh/meshtools.py | 22 ++----------------- src/Mod/Fem/femtools/femutils.py | 4 ++-- src/Mod/Fem/femtools/geomtools.py | 18 +++++++++++++++ 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/Mod/Fem/femguiobjects/FemSelectionWidgets.py b/src/Mod/Fem/femguiobjects/FemSelectionWidgets.py index 8e9eb5d647..2f3ba9405c 100644 --- a/src/Mod/Fem/femguiobjects/FemSelectionWidgets.py +++ b/src/Mod/Fem/femguiobjects/FemSelectionWidgets.py @@ -39,7 +39,7 @@ import FreeCAD import FreeCADGui import FreeCADGui as Gui -from femmesh import meshtools +from femtools import geomtools class _Selector(QtGui.QWidget): @@ -375,7 +375,7 @@ class GeometryElementsSelection(QtGui.QWidget): # since only Subelements can be selected # we're going to select all Faces of said Solids # the method getElement(element)doesn't return Solid elements - solid = meshtools.get_element(ref[0], ref[1]) + solid = geomtools.get_element(ref[0], ref[1]) if not solid: return faces = [] @@ -553,7 +553,7 @@ class GeometryElementsSelection(QtGui.QWidget): def has_equal_references_shape_types(self, ref_shty=""): for ref in self.references: # the method getElement(element) does not return Solid elements - r = meshtools.get_element(ref[0], ref[1]) + r = geomtools.get_element(ref[0], ref[1]) if not r: FreeCAD.Console.PrintError( "Problem in retrieving element: {} \n".format(ref[1]) diff --git a/src/Mod/Fem/femmesh/gmshtools.py b/src/Mod/Fem/femmesh/gmshtools.py index e0ca1e3827..69c44f16f5 100644 --- a/src/Mod/Fem/femmesh/gmshtools.py +++ b/src/Mod/Fem/femmesh/gmshtools.py @@ -414,7 +414,7 @@ class GmshTools(): # Shape to mesh and use the found element as elems # the method getElement(element) # does not return Solid elements - ele_shape = meshtools.get_element(sub[0], elems) + ele_shape = geomtools.get_element(sub[0], elems) found_element = geomtools.find_element_in_shape( self.part_obj.Shape, ele_shape ) @@ -451,7 +451,7 @@ class GmshTools(): ) for eleml in self.ele_length_map: # the method getElement(element) does not return Solid elements - ele_shape = meshtools.get_element(self.part_obj, eleml) + ele_shape = geomtools.get_element(self.part_obj, eleml) ele_vertexes = geomtools.get_vertexes_by_element(self.part_obj.Shape, ele_shape) self.ele_node_map[eleml] = ele_vertexes Console.PrintMessage(" {}\n".format(self.ele_length_map)) @@ -502,7 +502,7 @@ class GmshTools(): # we try to find the element it in the Shape to mesh # and use the found element as elems # the method getElement(element) does not return Solid elements - ele_shape = meshtools.get_element(sub[0], elems) + ele_shape = geomtools.get_element(sub[0], elems) found_element = geomtools.find_element_in_shape( self.part_obj.Shape, ele_shape diff --git a/src/Mod/Fem/femmesh/meshtools.py b/src/Mod/Fem/femmesh/meshtools.py index 26759c425e..a35a4d82d8 100644 --- a/src/Mod/Fem/femmesh/meshtools.py +++ b/src/Mod/Fem/femmesh/meshtools.py @@ -115,7 +115,7 @@ def get_femnodes_by_refshape( nodes = [] for refelement in ref[1]: # the following method getElement(element) does not return Solid elements - r = get_element(ref[0], refelement) + r = geomtools.get_element(ref[0], refelement) FreeCAD.Console.PrintMessage( " " "ReferenceShape ... Type: {0}, " @@ -1936,7 +1936,7 @@ def get_reference_group_elements( # FreeCAD.Console.PrintMessage("{}\n".format(childs)) for child in childs: # the method getElement(element) does not return Solid elements - ref_shape = get_element(parent, child) + ref_shape = geomtools.get_element(parent, child) if not stype: stype = ref_shape.ShapeType elif stype != ref_shape.ShapeType: @@ -2058,24 +2058,6 @@ def get_anlysis_empty_references_group_elements( return group_elements -# ************************************************************************************************ -def get_element( - part, - element -): - if element.startswith("Solid"): - index = int(element.lstrip("Solid")) - 1 - if index >= len(part.Shape.Solids): - FreeCAD.Console.PrintError( - "Index out of range. This Solid does not exist in the Shape!\n" - ) - return None - else: - return part.Shape.Solids[index] # Solid - else: - return part.Shape.getElement(element) # Face, Edge, Vertex - - # ************************************************************************************************ def femelements_count_ok( len_femelement_table, diff --git a/src/Mod/Fem/femtools/femutils.py b/src/Mod/Fem/femtools/femutils.py index 69c481854d..939dad2a1f 100644 --- a/src/Mod/Fem/femtools/femutils.py +++ b/src/Mod/Fem/femtools/femutils.py @@ -309,10 +309,10 @@ def get_refshape_type(fem_doc_object): :note: Undefined behaviour if constraint contains no references (empty list). """ - import femmesh.meshtools as FemMeshTools + from femtools.geomtools import get_element if hasattr(fem_doc_object, "References") and fem_doc_object.References: first_ref_obj = fem_doc_object.References[0] - first_ref_shape = FemMeshTools.get_element(first_ref_obj[0], first_ref_obj[1][0]) + first_ref_shape = get_element(first_ref_obj[0], first_ref_obj[1][0]) st = first_ref_shape.ShapeType FreeCAD.Console.PrintMessage( "References: {} in {}, {}\n". format(st, fem_doc_object.Name, fem_doc_object.Label) diff --git a/src/Mod/Fem/femtools/geomtools.py b/src/Mod/Fem/femtools/geomtools.py index c34078367c..568b6eb267 100644 --- a/src/Mod/Fem/femtools/geomtools.py +++ b/src/Mod/Fem/femtools/geomtools.py @@ -173,6 +173,24 @@ def is_same_geometry( return False +# ************************************************************************************************ +def get_element( + part, + element +): + if element.startswith("Solid"): + index = int(element.lstrip("Solid")) - 1 + if index >= len(part.Shape.Solids): + FreeCAD.Console.PrintError( + "Index out of range. This Solid does not exist in the Shape!\n" + ) + return None + else: + return part.Shape.Solids[index] # Solid + else: + return part.Shape.getElement(element) # Face, Edge, Vertex + + # ************************************************************************************************ def get_rectangular_coords( obj From 97d90d53edcb9a5da8a58ec0241405b5e7025b4f Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Fri, 27 Mar 2020 05:56:36 +0100 Subject: [PATCH 066/117] FEM: z88 some small code improvements --- src/Mod/Fem/feminout/importZ88Mesh.py | 18 +++++++++--------- src/Mod/Fem/femsolver/z88/writer.py | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Mod/Fem/feminout/importZ88Mesh.py b/src/Mod/Fem/feminout/importZ88Mesh.py index 0a189e1833..7388ff9bc0 100644 --- a/src/Mod/Fem/feminout/importZ88Mesh.py +++ b/src/Mod/Fem/feminout/importZ88Mesh.py @@ -30,9 +30,12 @@ __url__ = "http://www.freecadweb.org" # \brief FreeCAD Z88 Mesh reader and writer for FEM workbench import os + import FreeCAD from FreeCAD import Console +from femmesh import meshtools + # ************************************************************************************************ # ********* generic FreeCAD import and export methods ******************************************** # names are fix given from FreeCAD, these methods are called from FreeCAD @@ -86,8 +89,7 @@ def export( Console.PrintError("No FEM mesh object selected.\n") return femnodes_mesh = obj.FemMesh.Nodes - import femmesh.meshtools as FemMeshTools - femelement_table = FemMeshTools.get_femelement_table(obj.FemMesh) + femelement_table = meshtools.get_femelement_table(obj.FemMesh) z88_element_type = get_z88_element_type(obj.FemMesh, femelement_table) f = pyopen(filename, "wb") write_z88_mesh_to_file(femnodes_mesh, femelement_table, z88_element_type, f) @@ -429,8 +431,7 @@ def write( Console.PrintError("Not a FemMesh was given as parameter.\n") return femnodes_mesh = fem_mesh.Nodes - import femmesh.meshtools as FemMeshTools - femelement_table = FemMeshTools.get_femelement_table(fem_mesh) + femelement_table = meshtools.get_femelement_table(fem_mesh) z88_element_type = get_z88_element_type(fem_mesh, femelement_table) f = pyopen(filename, "w") write_z88_mesh_to_file(femnodes_mesh, femelement_table, z88_element_type, f) @@ -554,18 +555,17 @@ def get_z88_element_type( femmesh, femelement_table=None ): - import femmesh.meshtools as FemMeshTools if not femmesh: Console.PrintError("Error: No femmesh.\n") if not femelement_table: Console.PrintError("The femelement_table need to be calculated.\n") - femelement_table = FemMeshTools.get_femelement_table(femmesh) + femelement_table = meshtools.get_femelement_table(femmesh) # in some cases lowest key in femelement_table is not [1] for elem in sorted(femelement_table): elem_length = len(femelement_table[elem]) Console.PrintLog("Node count of first element: {}\n".format(elem_length)) break # break after the first elem - if FemMeshTools.is_solid_femmesh(femmesh): + if meshtools.is_solid_femmesh(femmesh): if femmesh.TetraCount == femmesh.VolumeCount: if elem_length == 4: return 17 @@ -583,7 +583,7 @@ def get_z88_element_type( return 0 else: Console.PrintError("no tetra, no hexa or Mixed Volume Elements.\n") - elif FemMeshTools.is_face_femmesh(femmesh): + elif meshtools.is_face_femmesh(femmesh): if femmesh.TriangleCount == femmesh.FaceCount: if elem_length == 3: Console.PrintError("tria3mesh, not supported by Z88.\n") @@ -605,7 +605,7 @@ def get_z88_element_type( else: Console.PrintError("no tria, no quad\n") return 0 - elif FemMeshTools.is_edge_femmesh(femmesh): + elif meshtools.is_edge_femmesh(femmesh): Console.PrintMessage("Edge femmesh will be exported as 3D truss element nr 4.\n") return 4 else: diff --git a/src/Mod/Fem/femsolver/z88/writer.py b/src/Mod/Fem/femsolver/z88/writer.py index ba4f4a73ed..f433c05f64 100644 --- a/src/Mod/Fem/femsolver/z88/writer.py +++ b/src/Mod/Fem/femsolver/z88/writer.py @@ -32,12 +32,12 @@ import time import FreeCAD -from .. import writerbase as FemInputWriter +from .. import writerbase from feminout import importZ88Mesh -from femmesh import meshtools as FemMeshTools +from femmesh import meshtools -class FemInputWriterZ88(FemInputWriter.FemInputWriter): +class FemInputWriterZ88(writerbase.FemInputWriter): def __init__( self, analysis_obj, @@ -46,7 +46,7 @@ class FemInputWriterZ88(FemInputWriter.FemInputWriter): member, dir_name=None ): - FemInputWriter.FemInputWriter.__init__( + writerbase.FemInputWriter.__init__( self, analysis_obj, solver_obj, @@ -68,7 +68,7 @@ class FemInputWriterZ88(FemInputWriter.FemInputWriter): if not self.femnodes_mesh: self.femnodes_mesh = self.femmesh.Nodes if not self.femelement_table: - self.femelement_table = FemMeshTools.get_femelement_table(self.femmesh) + self.femelement_table = meshtools.get_femelement_table(self.femmesh) self.element_count = len(self.femelement_table) self.set_z88_elparam() self.write_z88_mesh() @@ -193,7 +193,7 @@ class FemInputWriterZ88(FemInputWriter.FemInputWriter): def write_z88_elements_properties(self): element_properties_file_path = self.file_name + "elp.txt" elements_data = [] - if FemMeshTools.is_edge_femmesh(self.femmesh): + if meshtools.is_edge_femmesh(self.femmesh): if len(self.beamsection_objects) == 1: beam_obj = self.beamsection_objects[0]["Object"] width = beam_obj.RectWidth.getValueAs("mm") @@ -207,7 +207,7 @@ class FemInputWriterZ88(FemInputWriter.FemInputWriter): ) else: FreeCAD.Console.PrintError("Multiple beamsections for Z88 not yet supported!\n") - elif FemMeshTools.is_face_femmesh(self.femmesh): + elif meshtools.is_face_femmesh(self.femmesh): if len(self.shellthickness_objects) == 1: thick_obj = self.shellthickness_objects[0]["Object"] thickness = str(thick_obj.Thickness.getValueAs("mm")) @@ -218,7 +218,7 @@ class FemInputWriterZ88(FemInputWriter.FemInputWriter): FreeCAD.Console.PrintError( "Multiple thicknesses for Z88 not yet supported!\n" ) - elif FemMeshTools.is_solid_femmesh(self.femmesh): + elif meshtools.is_solid_femmesh(self.femmesh): elements_data.append("1 " + str(self.element_count) + " 0 0 0 0 0 0 0") else: FreeCAD.Console.PrintError("Error!\n") From c0e02f331c956394070f018bce8f3b510b578b17 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Fri, 27 Mar 2020 06:39:24 +0100 Subject: [PATCH 067/117] FEM: pep8 --- src/Mod/Fem/femguiobjects/FemSelectionWidgets.py | 9 +++++++-- src/Mod/Fem/femtools/geomtools.py | 4 ---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Mod/Fem/femguiobjects/FemSelectionWidgets.py b/src/Mod/Fem/femguiobjects/FemSelectionWidgets.py index 2f3ba9405c..36ba742108 100644 --- a/src/Mod/Fem/femguiobjects/FemSelectionWidgets.py +++ b/src/Mod/Fem/femguiobjects/FemSelectionWidgets.py @@ -486,7 +486,8 @@ class GeometryElementsSelection(QtGui.QWidget): else: # could be more than two solids, think of polar pattern FreeCAD.Console.PrintMessage( - " Edge belongs to at least two solids: Solid{}, Solid{}\n" + " Edge belongs to at least two solids: " + " Solid{}, Solid{}\n" .format(solid_to_add, str(i + 1)) ) solid_to_add = None @@ -542,7 +543,11 @@ class GeometryElementsSelection(QtGui.QWidget): .format(self.get_item_text(selection)) ) FreeCAD.Console.PrintMessage(message) - QtGui.QMessageBox.critical(None, "Geometry already in list", message.lstrip(" ")) + QtGui.QMessageBox.critical( + None, + "Geometry already in list", + message.lstrip(" ") + ) else: # selected shape will not added to the list FreeCADGui.Selection.clearSelection() diff --git a/src/Mod/Fem/femtools/geomtools.py b/src/Mod/Fem/femtools/geomtools.py index 568b6eb267..786d2e8329 100644 --- a/src/Mod/Fem/femtools/geomtools.py +++ b/src/Mod/Fem/femtools/geomtools.py @@ -27,8 +27,6 @@ __url__ = "http://www.freecadweb.org" import FreeCAD -from . import femutils - # ************************************************************************************************ def find_element_in_shape( @@ -248,5 +246,3 @@ def get_cylindrical_coords( B_coords = str(B[0]) + "," + str(B[1]) + "," + str(B[2]) coords = A_coords + "," + B_coords return coords - - From 39b691a994e8815fd9181baa7d10bc06e0111673 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Fri, 27 Mar 2020 06:46:50 +0100 Subject: [PATCH 068/117] FEM: delete wrong author, copy paste error --- src/Mod/Fem/femtools/geomtools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Fem/femtools/geomtools.py b/src/Mod/Fem/femtools/geomtools.py index 786d2e8329..80ad805eb0 100644 --- a/src/Mod/Fem/femtools/geomtools.py +++ b/src/Mod/Fem/femtools/geomtools.py @@ -22,7 +22,7 @@ # *************************************************************************** __title__ = "FEM geometry tools" -__author__ = "Markus Hovorka, Bernd Hahnebach" +__author__ = "Bernd Hahnebach" __url__ = "http://www.freecadweb.org" import FreeCAD From f501198832656a23160e663b9d6cfd55c6a20365 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 27 Mar 2020 12:41:38 +0800 Subject: [PATCH 069/117] Part: fix Feature::getTopoShape() for link array with LinkTransform --- src/Mod/Part/App/PartFeature.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Mod/Part/App/PartFeature.cpp b/src/Mod/Part/App/PartFeature.cpp index 47dc264ba7..85b5469e58 100644 --- a/src/Mod/Part/App/PartFeature.cpp +++ b/src/Mod/Part/App/PartFeature.cpp @@ -407,9 +407,10 @@ static TopoShape _getTopoShape(const App::DocumentObject *obj, const char *subna // not return the linked object when calling getLinkedObject(). // Therefore, it should be handled here. TopoShape baseShape; + Base::Matrix4D baseMat; std::string op; if(link && link->getElementCountValue()) { - linked = link->getTrueLinkedObject(false); + linked = link->getTrueLinkedObject(false,&baseMat); if(linked && linked!=owner) { baseShape = Feature::getTopoShape(linked,0,false,0,0,false,false); // if(!link->getShowElementValue()) @@ -421,7 +422,7 @@ static TopoShape _getTopoShape(const App::DocumentObject *obj, const char *subna int visible; std::string childName; App::DocumentObject *parent=0; - Base::Matrix4D mat; + Base::Matrix4D mat = baseMat; App::DocumentObject *subObj=0; if(sub.find('.')==std::string::npos) visible = 1; From 5c4ddf42ee6efda214068ca3fbe96bea205269a1 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 27 Mar 2020 17:08:08 +0100 Subject: [PATCH 070/117] Gui: [skip ci] bind spin boxes to object in Placement dialog --- src/Gui/Placement.cpp | 20 ++++++++++++++++++++ src/Gui/Placement.h | 2 ++ src/Gui/propertyeditor/PropertyItem.cpp | 1 + 3 files changed, 23 insertions(+) diff --git a/src/Gui/Placement.cpp b/src/Gui/Placement.cpp index 56f84ae3d9..6880ff9249 100644 --- a/src/Gui/Placement.cpp +++ b/src/Gui/Placement.cpp @@ -635,6 +635,21 @@ void Placement::on_resetButton_clicked() onPlacementChanged(0); } +void Placement::bindObject() +{ + if (!selectionObjects.empty()) { + App::DocumentObject* obj = selectionObjects.front().getObject(); + + ui->xPos->bind(App::ObjectIdentifier::parse(obj, propertyName + std::string(".Base.x"))); + ui->yPos->bind(App::ObjectIdentifier::parse(obj, propertyName + std::string(".Base.y"))); + ui->zPos->bind(App::ObjectIdentifier::parse(obj, propertyName + std::string(".Base.z"))); + + ui->yawAngle ->bind(App::ObjectIdentifier::parse(obj, propertyName + std::string(".Rotation.Yaw"))); + ui->pitchAngle->bind(App::ObjectIdentifier::parse(obj, propertyName + std::string(".Rotation.Pitch"))); + ui->rollAngle ->bind(App::ObjectIdentifier::parse(obj, propertyName + std::string(".Rotation.Roll"))); + } +} + void Placement::directionActivated(int index) { if (ui->directionActivated(this, index)) { @@ -840,6 +855,11 @@ TaskPlacement::~TaskPlacement() // automatically deleted in the sub-class } +void TaskPlacement::bindObject() +{ + widget->bindObject(); +} + void TaskPlacement::open() { widget->open(); diff --git a/src/Gui/Placement.h b/src/Gui/Placement.h index d7122d8aff..87be33bb88 100644 --- a/src/Gui/Placement.h +++ b/src/Gui/Placement.h @@ -51,6 +51,7 @@ public: void accept(); void reject(); + void bindObject(); Base::Vector3d getDirection() const; void setPlacement(const Base::Placement&); Base::Placement getPlacement() const; @@ -134,6 +135,7 @@ public: public: void setPropertyName(const QString&); void setPlacement(const Base::Placement&); + void bindObject(); bool accept(); bool reject(); void clicked(int id); diff --git a/src/Gui/propertyeditor/PropertyItem.cpp b/src/Gui/propertyeditor/PropertyItem.cpp index 0351c919c9..621e766637 100644 --- a/src/Gui/propertyeditor/PropertyItem.cpp +++ b/src/Gui/propertyeditor/PropertyItem.cpp @@ -1932,6 +1932,7 @@ void PlacementEditor::browse() } task->setPlacement(value().value()); task->setPropertyName(propertyname); + task->bindObject(); Gui::Control().showDialog(task); } From 6182a14691ace7ac2cb1cae99e928dee7779e26b Mon Sep 17 00:00:00 2001 From: donovaly Date: Sat, 28 Mar 2020 01:32:42 +0100 Subject: [PATCH 071/117] [TD] restore alignment I lost the alignment property because of issues with Qt Creator as discussed in https://forum.freecadweb.org/viewtopic.php?f=10&t=44609 This PR restores this. (More to come but with further changes) --- src/Mod/TechDraw/Gui/DlgPrefsTechDraw1.ui | 41 +- src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui | 728 +++++++++++----------- src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui | 23 +- src/Mod/TechDraw/Gui/TaskCenterLine.ui | 28 +- 4 files changed, 422 insertions(+), 398 deletions(-) diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw1.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw1.ui index 9ff2e1e169..fec43f04a1 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw1.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw1.ui @@ -228,7 +228,7 @@ for ProjectionGroups Normal line color - + 0 0 @@ -298,7 +298,7 @@ for ProjectionGroups Preselection color - + 255 255 @@ -330,7 +330,7 @@ for ProjectionGroups Section face color - + 225 225 @@ -362,7 +362,7 @@ for ProjectionGroups Selected item color - + 28 173 @@ -414,7 +414,7 @@ for ProjectionGroups Background color around pages - + 80 80 @@ -446,7 +446,7 @@ for ProjectionGroups Hatch image color - + 0 0 @@ -473,7 +473,7 @@ for ProjectionGroups Color of dimension lines and text. - + 0 0 @@ -505,7 +505,7 @@ for ProjectionGroups Geometric hatch pattern color - + 0 0 @@ -592,7 +592,7 @@ for ProjectionGroups Face color (if not transparent) - + 255 255 @@ -636,7 +636,7 @@ for ProjectionGroups Default color for leader lines - + 0 0 @@ -653,7 +653,7 @@ for ProjectionGroups - + 0 0 @@ -789,7 +789,7 @@ for ProjectionGroups - + 0 @@ -799,7 +799,10 @@ for ProjectionGroups Label size - + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 8.000000000000000 @@ -849,7 +852,7 @@ for ProjectionGroups - + 0 @@ -900,7 +903,7 @@ for ProjectionGroups - + 191 @@ -937,7 +940,7 @@ for ProjectionGroups - + 191 @@ -974,7 +977,7 @@ for ProjectionGroups - + 191 @@ -1011,7 +1014,7 @@ for ProjectionGroups - + 191 @@ -1048,7 +1051,7 @@ for ProjectionGroups - + 191 diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui index f354a9cf8e..5829ebe238 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui @@ -28,8 +28,363 @@ - - + + + + + + 0 + 0 + + + + + 0 + 113 + + + + + 16777215 + 16777215 + + + + Scale + + + + + + + + + true + + + + Page Scale + + + + + + + + 0 + 0 + + + + + 174 + 0 + + + + Default scale for new pages + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 2 + + + 1.000000000000000 + + + DefaultScale + + + Mod/TechDraw/General + + + + + + + + true + + + + View Scale Type + + + + + + + true + + + + 0 + 0 + + + + + 174 + 0 + + + + Default scale for new views + + + DefaultScaleType + + + Mod/TechDraw/General + + + + Page + + + + + Auto + + + + + Custom + + + + + + + + + true + + + + View Custom Scale + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + + 0 + 0 + + + + + 174 + 0 + + + + Default scale for views if 'View Scale Type' is 'Custom' + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 2 + + + 1.000000000000000 + + + DefaultViewScale + + + Mod/TechDraw/General + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 200 + + + + Selection + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 174 + 0 + + + + + 0 + 0 + + + + Selection area around center marks +Each unit is approx. 0.1 mm wide + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 5.000000000000000 + + + MarkFuzz + + + Mod/TechDraw/General + + + + + + + + 0 + 0 + + + + + 174 + 0 + + + + + 0 + 0 + + + + Size of selection area around edges +Each unit is approx. 0.1 mm wide + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 10.000000000000000 + + + EdgeFuzz + + + Mod/TechDraw/General + + + + + + + + 0 + 0 + + + + Mark Fuzz + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Edge Fuzz + + + + + + + + + @@ -237,7 +592,10 @@ Size of template field click handles - + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 3.000000000000000 @@ -246,9 +604,6 @@ Mod/TechDraw/General - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - @@ -256,362 +611,7 @@ - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 0 - 200 - - - - Selection - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 174 - 0 - - - - - 0 - 0 - - - - Selection area around center marks -Each unit is approx. 0.1 mm wide - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 5.000000000000000 - - - MarkFuzz - - - Mod/TechDraw/General - - - - - - - - 0 - 0 - - - - - 174 - 0 - - - - - 0 - 0 - - - - Size of selection area around edges -Each unit is approx. 0.1 mm wide - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 10.000000000000000 - - - EdgeFuzz - - - Mod/TechDraw/General - - - - - - - - 0 - 0 - - - - Mark Fuzz - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - Edge Fuzz - - - - - - - - - - - - - 0 - 0 - - - - - 0 - 113 - - - - - 16777215 - 16777215 - - - - Scale - - - - - - - - - true - - - - Page Scale - - - - - - - - 0 - 0 - - - - - 174 - 0 - - - - Default scale for new pages - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 2 - - - 1.000000000000000 - - - DefaultScale - - - Mod/TechDraw/General - - - - - - - - true - - - - View Scale Type - - - - - - - true - - - - 0 - 0 - - - - - 174 - 0 - - - - Default scale for new views - - - DefaultScaleType - - - Mod/TechDraw/General - - - - Page - - - - - Auto - - - - - Custom - - - - - - - - - true - - - - View Custom Scale - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - false - - - - 0 - 0 - - - - - 174 - 0 - - - - Default scale for views if 'View Scale Type' is 'Custom' - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 2 - - - 1.000000000000000 - - - DefaultViewScale - - - Mod/TechDraw/General - - - - - - - - - + @@ -630,7 +630,7 @@ Each unit is approx. 0.1 mm wide - + Qt::Vertical @@ -658,7 +658,7 @@ Each unit is approx. 0.1 mm wide Gui::PrefUnitSpinBox - QWidget + Gui::QuantitySpinBox
Gui/PrefWidgets.h
diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui index 49317bca81..c2d313e355 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw3.ui @@ -224,7 +224,7 @@
- + 0 @@ -240,7 +240,10 @@ Dimension text font size - + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 4.000000000000000 @@ -347,7 +350,7 @@ - + 0 @@ -363,7 +366,10 @@ Arrowhead size - + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 5.000000000000000 @@ -585,7 +591,7 @@ - + 0 @@ -601,7 +607,10 @@ Length of balloon leader line kink - + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 5.000000000000000 @@ -805,7 +814,7 @@ 184 - 0 + 22 diff --git a/src/Mod/TechDraw/Gui/TaskCenterLine.ui b/src/Mod/TechDraw/Gui/TaskCenterLine.ui index 8ef2ae3ddc..99a3e17cc1 100644 --- a/src/Mod/TechDraw/Gui/TaskCenterLine.ui +++ b/src/Mod/TechDraw/Gui/TaskCenterLine.ui @@ -159,7 +159,7 @@ - + 0 @@ -169,6 +169,9 @@ Move line -Left or +Right + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + @@ -182,7 +185,7 @@ - + 0 @@ -192,6 +195,9 @@ Move line +Up or -Down + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + @@ -205,7 +211,7 @@ - + 0 @@ -215,10 +221,10 @@ Rotate line +CCW or -CW - + -360.000000000000000 - + 360.000000000000000 @@ -242,7 +248,7 @@ - + 0 @@ -252,10 +258,13 @@ Make the line a little longer. + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + mm - + 3.000000000000000 @@ -269,7 +278,7 @@ - + 0 0 @@ -287,6 +296,9 @@ + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + 0.100000000000000 From d9e65c77804a4de972a691c47c498c17bbd40496 Mon Sep 17 00:00:00 2001 From: donovaly Date: Sat, 28 Mar 2020 03:04:50 +0100 Subject: [PATCH 072/117] [TD] use Gui::QuantitySpinBox in dialogs as recently discussed - also add lost alignment properties --- src/Mod/TechDraw/Gui/TaskBalloon.cpp | 4 +- src/Mod/TechDraw/Gui/TaskBalloon.ui | 44 ++++++++++---- src/Mod/TechDraw/Gui/TaskLeaderLine.ui | 82 ++++++++++++++++++++------ src/Mod/TechDraw/Gui/TaskLineDecor.cpp | 2 +- src/Mod/TechDraw/Gui/TaskLineDecor.ui | 12 +++- src/Mod/TechDraw/Gui/TaskRichAnno.cpp | 2 +- src/Mod/TechDraw/Gui/TaskRichAnno.ui | 26 ++++---- 7 files changed, 124 insertions(+), 48 deletions(-) diff --git a/src/Mod/TechDraw/Gui/TaskBalloon.cpp b/src/Mod/TechDraw/Gui/TaskBalloon.cpp index 73a788ad69..0c5a8b318f 100644 --- a/src/Mod/TechDraw/Gui/TaskBalloon.cpp +++ b/src/Mod/TechDraw/Gui/TaskBalloon.cpp @@ -120,7 +120,7 @@ bool TaskBalloon::accept() ac.setValue(ui->textColor->color()); m_balloonVP->Color.setValue(ac); m_balloonVP->Fontsize.setValue(ui->qsbFontSize->value().getValue()); - m_parent->dvBalloon->ShapeScale.setValue(ui->inputScale->value()); + m_parent->dvBalloon->ShapeScale.setValue(ui->inputScale->value().getValue()); m_parent->dvBalloon->EndType.setValue(ui->comboEndType->currentIndex()); m_parent->dvBalloon->Shape.setValue(ui->comboSymbol->currentIndex()); m_balloonVP->LineWidth.setValue(ui->qsbLineWidth->value().getValue()); @@ -170,7 +170,7 @@ void TaskBalloon::onShapeChanged() void TaskBalloon::onShapeScaleChanged() { - m_parent->dvBalloon->ShapeScale.setValue(ui->inputScale->value()); + m_parent->dvBalloon->ShapeScale.setValue(ui->inputScale->value().getValue()); recomputeFeature(); } diff --git a/src/Mod/TechDraw/Gui/TaskBalloon.ui b/src/Mod/TechDraw/Gui/TaskBalloon.ui index 3ffef4004e..9433fc0882 100644 --- a/src/Mod/TechDraw/Gui/TaskBalloon.ui +++ b/src/Mod/TechDraw/Gui/TaskBalloon.ui @@ -42,7 +42,7 @@ Color for 'Text' - + 0 0 @@ -59,7 +59,7 @@ - + 0 @@ -75,7 +75,10 @@ Fontsize for 'Text' - + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 4.000000000000000 @@ -171,10 +174,19 @@ - + + + + 0 + 20 + + Scale factor for the 'Shape' + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + 0.000000000000000 @@ -208,7 +220,7 @@ - + 0 @@ -224,7 +236,10 @@ Leader line width - + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 0.350000000000000 @@ -243,7 +258,7 @@ - + 0 @@ -259,7 +274,10 @@ Length of balloon leader line kink - + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 5.000000000000000 @@ -275,16 +293,16 @@ - - Gui::ColorButton - QPushButton -
Gui/Widgets.h
-
Gui::QuantitySpinBox QWidget
Gui/QuantitySpinBox.h
+ + Gui::ColorButton + QPushButton +
Gui/Widgets.h
+
diff --git a/src/Mod/TechDraw/Gui/TaskLeaderLine.ui b/src/Mod/TechDraw/Gui/TaskLeaderLine.ui index 81b0ddc077..6fc06a3dfa 100644 --- a/src/Mod/TechDraw/Gui/TaskLeaderLine.ui +++ b/src/Mod/TechDraw/Gui/TaskLeaderLine.ui @@ -142,47 +142,76 @@ You can pick further points to get line segments.
- - - QFormLayout::AllNonFixedFieldsGrow - - + + Start Symbol - + + + + 0 + 22 + + -1 - + End Symbol - - + + + + Qt::Horizontal + + + + 40 + 20 + + + - + + + + + 0 + 22 + + + + + Color - + + + + 0 + 22 + + Line color - + 0 0 @@ -191,38 +220,53 @@ You can pick further points to get line segments. - + Width - - + + true + + + 0 + 22 + + Line width - + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 0.100000000000000 - + 0.500000000000000 - + Style - + + + + 0 + 22 + + Line style diff --git a/src/Mod/TechDraw/Gui/TaskLineDecor.cpp b/src/Mod/TechDraw/Gui/TaskLineDecor.cpp index 4244c251a2..f7094c1d3a 100644 --- a/src/Mod/TechDraw/Gui/TaskLineDecor.cpp +++ b/src/Mod/TechDraw/Gui/TaskLineDecor.cpp @@ -171,7 +171,7 @@ void TaskLineDecor::onColorChanged(void) void TaskLineDecor::onWeightChanged(void) { - m_weight = ui->dsb_Weight->value(); + m_weight = ui->dsb_Weight->value().getValue(); applyDecorations(); m_partFeat->requestPaint(); } diff --git a/src/Mod/TechDraw/Gui/TaskLineDecor.ui b/src/Mod/TechDraw/Gui/TaskLineDecor.ui index 9092dc2aee..15ffa309cd 100644 --- a/src/Mod/TechDraw/Gui/TaskLineDecor.ui +++ b/src/Mod/TechDraw/Gui/TaskLineDecor.ui @@ -136,7 +136,7 @@ - + 0 0 @@ -153,10 +153,13 @@ - + Thickness of pattern lines. + + Qt::AlignCenter + 0.500000000000000 @@ -200,6 +203,11 @@ + + Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
+
Gui::ColorButton QPushButton diff --git a/src/Mod/TechDraw/Gui/TaskRichAnno.cpp b/src/Mod/TechDraw/Gui/TaskRichAnno.cpp index f42c0e0d95..d19c93df09 100644 --- a/src/Mod/TechDraw/Gui/TaskRichAnno.cpp +++ b/src/Mod/TechDraw/Gui/TaskRichAnno.cpp @@ -417,7 +417,7 @@ void TaskRichAnno::commonFeatureUpdate(void) // Base::Console().Message("TRA::commonFeatureUpdate()\n"); m_annoFeat->setPosition(Rez::appX(m_attachPoint.x),Rez::appX(- m_attachPoint.y), true); m_annoFeat->AnnoText.setValue(ui->teAnnoText->toHtml().toUtf8()); - m_annoFeat->MaxWidth.setValue(ui->dsbMaxWidth->value()); + m_annoFeat->MaxWidth.setValue(ui->dsbMaxWidth->value().getValue()); m_annoFeat->ShowFrame.setValue(ui->cbShowFrame->isChecked()); } diff --git a/src/Mod/TechDraw/Gui/TaskRichAnno.ui b/src/Mod/TechDraw/Gui/TaskRichAnno.ui index 26ea6e7b47..06ec52b906 100644 --- a/src/Mod/TechDraw/Gui/TaskRichAnno.ui +++ b/src/Mod/TechDraw/Gui/TaskRichAnno.ui @@ -69,10 +69,13 @@
- + Maximal width, if -1 then automatic width + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + -1.000000000000000 @@ -144,7 +147,7 @@ Line color - + 0 0 @@ -161,17 +164,20 @@ - + false Line width - + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 0.100000000000000 - + 0.500000000000000 @@ -234,16 +240,16 @@
- - Gui::ColorButton - QPushButton -
Gui/Widgets.h
-
Gui::QuantitySpinBox QWidget
Gui/QuantitySpinBox.h
+ + Gui::ColorButton + QPushButton +
Gui/Widgets.h
+
From 9064768a5c19180220daeecc52e5eb53c1cca335 Mon Sep 17 00:00:00 2001 From: donovaly Date: Sat, 28 Mar 2020 21:23:08 +0100 Subject: [PATCH 073/117] [TD] TaskCenterLine add missing unit initialization next try because PR #3275 cannot be merged. --- src/Mod/TechDraw/Gui/TaskCenterLine.cpp | 8 +++++--- src/Mod/TechDraw/Gui/TaskCenterLine.ui | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Mod/TechDraw/Gui/TaskCenterLine.cpp b/src/Mod/TechDraw/Gui/TaskCenterLine.cpp index 5c0803899a..79d110eca2 100644 --- a/src/Mod/TechDraw/Gui/TaskCenterLine.cpp +++ b/src/Mod/TechDraw/Gui/TaskCenterLine.cpp @@ -173,6 +173,8 @@ void TaskCenterLine::setUiPrimary() ui->dsbWeight->setValue(getCenterWidth()); ui->cboxStyle->setCurrentIndex(getCenterStyle() - 1); + ui->qsbVertShift->setUnit(Base::Unit::Length); + ui->qsbHorizShift->setUnit(Base::Unit::Length); Base::Quantity qVal; qVal.setUnit(Base::Unit::Length); qVal.setValue(getExtendBy()); @@ -305,7 +307,7 @@ void TaskCenterLine::onColorChanged() void TaskCenterLine::onWeightChanged() { - m_cl->m_format.m_weight = ui->dsbWeight->value(); + m_cl->m_format.m_weight = ui->dsbWeight->value().getValue(); m_partFeat->recomputeFeature(); } @@ -355,7 +357,7 @@ void TaskCenterLine::createCenterLine(void) App::Color ac; ac.setValue(ui->cpLineColor->color()); cl->m_format.m_color = ac; - cl->m_format.m_weight = ui->dsbWeight->value(); + cl->m_format.m_weight = ui->dsbWeight->value().getValue(); cl->m_format.m_style = ui->cboxStyle->currentIndex() + 1; //Qt Styles start at 0:NoLine cl->m_format.m_visible = true; m_partFeat->addCenterLine(cl); @@ -373,7 +375,7 @@ void TaskCenterLine::updateCenterLine(void) // Base::Console().Message("TCL::updateCenterLine()\n"); Gui::Command::openCommand("Edit CenterLine"); m_cl->m_format.m_color.setValue(ui->cpLineColor->color() ); - m_cl->m_format.m_weight = ui->dsbWeight->value(); + m_cl->m_format.m_weight = ui->dsbWeight->value().getValue(); m_cl->m_format.m_style = ui->cboxStyle->currentIndex() + 1; m_cl->m_format.m_visible = true; diff --git a/src/Mod/TechDraw/Gui/TaskCenterLine.ui b/src/Mod/TechDraw/Gui/TaskCenterLine.ui index 99a3e17cc1..278d2cc5a9 100644 --- a/src/Mod/TechDraw/Gui/TaskCenterLine.ui +++ b/src/Mod/TechDraw/Gui/TaskCenterLine.ui @@ -221,6 +221,9 @@ Rotate line +CCW or -CW + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + -360.000000000000000 @@ -295,7 +298,7 @@
- + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter From 2cac4898d7d62276fbbaaef5b4cecb9bfdd29a33 Mon Sep 17 00:00:00 2001 From: donovaly Date: Sat, 28 Mar 2020 03:48:53 +0100 Subject: [PATCH 074/117] [TD] sanitize TaskCosVertex - add missing unit (therefore switch to Gui::QuantitySpinBox) - remove unused and confusing z-parameter --- src/Mod/TechDraw/Gui/TaskCosVertex.cpp | 13 +- src/Mod/TechDraw/Gui/TaskCosVertex.ui | 337 +++++++++++-------------- 2 files changed, 158 insertions(+), 192 deletions(-) diff --git a/src/Mod/TechDraw/Gui/TaskCosVertex.cpp b/src/Mod/TechDraw/Gui/TaskCosVertex.cpp index bd802d99f6..09a7f548ff 100644 --- a/src/Mod/TechDraw/Gui/TaskCosVertex.cpp +++ b/src/Mod/TechDraw/Gui/TaskCosVertex.cpp @@ -134,21 +134,19 @@ void TaskCosVertex::setUiPrimary() ui->pbTracker->setEnabled(true); ui->dsbX->setEnabled(true); ui->dsbY->setEnabled(true); - ui->dsbZ->setEnabled(false); int decimals = Base::UnitsApi::getDecimals(); ui->dsbX->setDecimals(decimals); ui->dsbY->setDecimals(decimals); - ui->dsbZ->setDecimals(decimals); + ui->dsbX->setUnit(Base::Unit::Length); + ui->dsbY->setUnit(Base::Unit::Length); } void TaskCosVertex::updateUi(void) { double x = m_savePoint.x(); double y = - m_savePoint.y(); - double z = 0.0; ui->dsbX->setValue(x); ui->dsbY->setValue(y); - ui->dsbZ->setValue(z); } void TaskCosVertex::addCosVertex(QPointF qPos) @@ -297,8 +295,6 @@ void TaskCosVertex::enableTaskButtons(bool b) //****************************************************************************** bool TaskCosVertex::accept() { -// Base::Console().Message("TCV::accept()\n"); - Gui::Document* doc = Gui::Application::Instance->getDocument(m_basePage->getDocument()); if (!doc) return false; @@ -306,9 +302,8 @@ bool TaskCosVertex::accept() if (pointFromTracker) { addCosVertex(m_savePoint); } else { - double x = ui->dsbX->value(); - double y = ui->dsbY->value(); -// double z = ui->dsbZ->value(); + double x = ui->dsbX->value().getValue(); + double y = ui->dsbY->value().getValue(); QPointF uiPoint(x,-y); addCosVertex(uiPoint); } diff --git a/src/Mod/TechDraw/Gui/TaskCosVertex.ui b/src/Mod/TechDraw/Gui/TaskCosVertex.ui index 595fd26d27..c1518ebbec 100644 --- a/src/Mod/TechDraw/Gui/TaskCosVertex.ui +++ b/src/Mod/TechDraw/Gui/TaskCosVertex.ui @@ -6,8 +6,8 @@ 0 0 - 409 - 405 + 400 + 200 @@ -31,202 +31,173 @@ - - - - 0 - 0 - + + + + + false + + + false + + + Qt::NoFocus + + + false + + + + + + + Base View + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Point Picker + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal - - - 300 - 300 - + + + + + + Position from the view center - - - 300 - 300 - + + Position - - QFrame::Box - - - QFrame::Raised - - + - - - - - - - false - - - false - - - Qt::NoFocus - - - false - - - - - - - Base View - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Point Picker - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Horizontal + + + + + X - - - - - - X - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Z - - - - - - - Y - - - - - - - 4 - - - -2147483647.000000000000000 - - - 2147483647.000000000000000 - - - - - - - 4 - - - -2147483647.000000000000000 - - - 2147483647.000000000000000 - - - - - - - 4 - - - -2147483647.000000000000000 - - - 2147483647.000000000000000 - - - - + + + + Qt::Horizontal + + + + 28 + 20 + + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + -2147483647.000000000000000 + + + 2147483647.000000000000000 + + + 4 + + + + + + + Y + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + -2147483647.000000000000000 + + + 2147483647.000000000000000 + + + 4 + + - - - - Qt::Vertical - - - - 20 - 40 - - - - + + + + Qt::Vertical + + + + 17 + 24 + + + + + + + Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
+
+
From 915e551bbb7e3614bc804f1ae1f65027b5432b9f Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 29 Mar 2020 03:23:50 +0200 Subject: [PATCH 075/117] [TD] vertex dialog: fix vertical size there was too much vertical whitespace below the actual dialog -> remove superfluous spacer and correct size policy --- src/Mod/TechDraw/Gui/TaskCosVertex.ui | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/Mod/TechDraw/Gui/TaskCosVertex.ui b/src/Mod/TechDraw/Gui/TaskCosVertex.ui index c1518ebbec..12e6989744 100644 --- a/src/Mod/TechDraw/Gui/TaskCosVertex.ui +++ b/src/Mod/TechDraw/Gui/TaskCosVertex.ui @@ -7,11 +7,11 @@ 0 0 400 - 200 + 167 - + 0 0 @@ -176,19 +176,6 @@
- - - - Qt::Vertical - - - - 17 - 24 - - - - From 2fbd3729b76e200eeb9e4ab5ca171d175991083a Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 29 Mar 2020 17:57:45 +0200 Subject: [PATCH 076/117] [TD] Section dialog overhaul - replace use uniformly Gui::QuantitySpinBox - fix and uniform size of all objects in the dialog - apply changes immediately once the section is created because this speeds up the workflow a lot, especially for when changing the plane position - some code cleanup: remove unused void, avoid unused variables --- src/Mod/TechDraw/Gui/TaskSectionView.cpp | 113 ++- src/Mod/TechDraw/Gui/TaskSectionView.h | 17 +- src/Mod/TechDraw/Gui/TaskSectionView.ui | 901 +++++++++++------------ 3 files changed, 516 insertions(+), 515 deletions(-) diff --git a/src/Mod/TechDraw/Gui/TaskSectionView.cpp b/src/Mod/TechDraw/Gui/TaskSectionView.cpp index fcb879ccfd..8e86e9a150 100644 --- a/src/Mod/TechDraw/Gui/TaskSectionView.cpp +++ b/src/Mod/TechDraw/Gui/TaskSectionView.cpp @@ -94,19 +94,12 @@ TaskSectionView::TaskSectionView(TechDraw::DrawViewPart* base) : m_saveBaseName = m_base->getNameInDocument(); m_savePageName = m_base->findParentPage()->getNameInDocument(); - - ui->setupUi(this); + ui->setupUi(this); - connect(ui->pbUp, SIGNAL(clicked(bool)), - this, SLOT(onUpClicked(bool))); - connect(ui->pbDown, SIGNAL(clicked(bool)), - this, SLOT(onDownClicked(bool))); - connect(ui->pbRight, SIGNAL(clicked(bool)), - this, SLOT(onRightClicked(bool))); - connect(ui->pbLeft, SIGNAL(clicked(bool)), - this, SLOT(onLeftClicked(bool))); - connect(ui->pbApply, SIGNAL(clicked(bool)), - this, SLOT(onApplyClicked(bool))); + connect(ui->pbUp, SIGNAL(clicked(bool)), this, SLOT(onUpClicked())); + connect(ui->pbDown, SIGNAL(clicked(bool)), this, SLOT(onDownClicked())); + connect(ui->pbRight, SIGNAL(clicked(bool)), this, SLOT(onRightClicked())); + connect(ui->pbLeft, SIGNAL(clicked(bool)), this, SLOT(onLeftClicked())); setUiPrimary(); } @@ -143,16 +136,10 @@ TaskSectionView::TaskSectionView(TechDraw::DrawViewSection* section) : ui->setupUi(this); - connect(ui->pbUp, SIGNAL(clicked(bool)), - this, SLOT(onUpClicked(bool))); - connect(ui->pbDown, SIGNAL(clicked(bool)), - this, SLOT(onDownClicked(bool))); - connect(ui->pbRight, SIGNAL(clicked(bool)), - this, SLOT(onRightClicked(bool))); - connect(ui->pbLeft, SIGNAL(clicked(bool)), - this, SLOT(onLeftClicked(bool))); - connect(ui->pbApply, SIGNAL(clicked(bool)), - this, SLOT(onApplyClicked(bool))); + connect(ui->pbUp, SIGNAL(clicked(bool)), this, SLOT(onUpClicked())); + connect(ui->pbDown, SIGNAL(clicked(bool)), this, SLOT(onDownClicked())); + connect(ui->pbRight, SIGNAL(clicked(bool)), this, SLOT(onRightClicked())); + connect(ui->pbLeft, SIGNAL(clicked(bool)), this, SLOT(onLeftClicked())); m_dirName = m_section->SectionDirection.getValueAsString(); saveSectionState(); @@ -180,6 +167,18 @@ void TaskSectionView::setUiPrimary() ui->sbOrgX->setValue(origin.x); ui->sbOrgY->setValue(origin.y); ui->sbOrgZ->setValue(origin.z); + + // before the user did not select an orientation, + // the section properties cannot be changed + this->setToolTip(QObject::tr("Select at first an orientation")); + enableAll(false); + + // now connect and not earlier to avoid premature apply() calls + connect(ui->leSymbol, SIGNAL(textChanged(QString)), this, SLOT(onIdentifierChanged())); + connect(ui->sbScale, SIGNAL(valueChanged(double)), this, SLOT(onScaleChanged())); + connect(ui->sbOrgX, SIGNAL(valueChanged(double)), this, SLOT(onXChanged())); + connect(ui->sbOrgY, SIGNAL(valueChanged(double)), this, SLOT(onYChanged())); + connect(ui->sbOrgZ, SIGNAL(valueChanged(double)), this, SLOT(onZChanged())); } void TaskSectionView::setUiEdit() @@ -200,6 +199,13 @@ void TaskSectionView::setUiEdit() ui->sbOrgX->setValue(origin.x); ui->sbOrgY->setValue(origin.y); ui->sbOrgZ->setValue(origin.z); + + // connect affter initializing the object values + connect(ui->leSymbol, SIGNAL(textChanged(QString)), this, SLOT(onIdentifierChanged())); + connect(ui->sbScale, SIGNAL(valueChanged(double)), this, SLOT(onScaleChanged())); + connect(ui->sbOrgX, SIGNAL(valueChanged(double)), this, SLOT(onXChanged())); + connect(ui->sbOrgY, SIGNAL(valueChanged(double)), this, SLOT(onYChanged())); + connect(ui->sbOrgZ, SIGNAL(valueChanged(double)), this, SLOT(onZChanged())); } //save the start conditions @@ -231,51 +237,64 @@ void TaskSectionView::restoreSectionState() } } -void TaskSectionView::blockButtons(bool b) -{ - Q_UNUSED(b); -} - -void TaskSectionView::onUpClicked(bool b) +void TaskSectionView::onUpClicked() { // Base::Console().Message("TSV::onUpClicked()\n"); - Q_UNUSED(b); checkAll(false); ui->pbUp->setChecked(true); applyQuick("Up"); } -void TaskSectionView::onDownClicked(bool b) +void TaskSectionView::onDownClicked() { // Base::Console().Message("TSV::onDownClicked()\n"); - Q_UNUSED(b); checkAll(false); ui->pbDown->setChecked(true); applyQuick("Down"); } -void TaskSectionView::onLeftClicked(bool b) +void TaskSectionView::onLeftClicked() { // Base::Console().Message("TSV::onLeftClicked()\n"); checkAll(false); ui->pbLeft->setChecked(true); - Q_UNUSED(b); applyQuick("Left"); } -void TaskSectionView::onRightClicked(bool b) +void TaskSectionView::onRightClicked() { // Base::Console().Message("TSV::onRightClicked()\n"); - Q_UNUSED(b); checkAll(false); ui->pbRight->setChecked(true); applyQuick("Right"); } -void TaskSectionView::onApplyClicked(bool b) +void TaskSectionView::onIdentifierChanged() +{ + checkAll(false); + apply(); +} + +void TaskSectionView::onScaleChanged() +{ + checkAll(false); + apply(); +} + +void TaskSectionView::onXChanged() +{ + checkAll(false); + apply(); +} + +void TaskSectionView::onYChanged() +{ + checkAll(false); + apply(); +} + +void TaskSectionView::onZChanged() { -// Base::Console().Message("TSV::onApplyClicked()\n"); - Q_UNUSED(b); checkAll(false); apply(); } @@ -288,13 +307,22 @@ void TaskSectionView::checkAll(bool b) ui->pbLeft->setChecked(b); } +void TaskSectionView::enableAll(bool b) +{ + ui->leSymbol->setEnabled(b); + ui->sbScale->setEnabled(b); + ui->sbOrgX->setEnabled(b); + ui->sbOrgY->setEnabled(b); + ui->sbOrgZ->setEnabled(b); +} + //****************************************************************************** bool TaskSectionView::apply(void) { // Base::Console().Message("TSV::apply() - m_dirName: %s\n", m_dirName.c_str()); if (m_dirName.empty()) { std::string msg = - Base::Tools::toStdString(tr("TSV::apply - Nothing to apply. No section direction picked yet")); + Base::Tools::toStdString(tr("Nothing to apply. No section direction picked yet")); Base::Console().Error((msg + "\n").c_str()); return false; } @@ -318,6 +346,11 @@ void TaskSectionView::applyQuick(std::string dir) if (isSectionValid()) { updateSectionView(); m_section->recomputeFeature(); + this->setToolTip(QObject::tr("Select at first an orientation")); + // we can in any case enable all objects in the dialog + // and remove the dialog-wide tooltip if there was one + enableAll(true); + this->setToolTip(QString()); } else { failNoObject(m_sectionName); } @@ -420,7 +453,7 @@ void TaskSectionView::updateSectionView(void) lblText.c_str()); Command::doCommand(Command::Doc,"App.activeDocument().%s.Scale = %0.6f", m_sectionName.c_str(), - ui->sbScale->value()); + ui->sbScale->value().getValue()); m_section->setCSFromBase(m_dirName.c_str()); } } diff --git a/src/Mod/TechDraw/Gui/TaskSectionView.h b/src/Mod/TechDraw/Gui/TaskSectionView.h index acd2f74d30..955df54a1d 100644 --- a/src/Mod/TechDraw/Gui/TaskSectionView.h +++ b/src/Mod/TechDraw/Gui/TaskSectionView.h @@ -51,15 +51,17 @@ public: virtual bool reject(); protected Q_SLOTS: - void onUpClicked(bool b); - void onDownClicked(bool b); - void onLeftClicked(bool b); - void onRightClicked(bool b); - void onApplyClicked(bool b); + void onUpClicked(); + void onDownClicked(); + void onLeftClicked(); + void onRightClicked(); + void onIdentifierChanged(); + void onScaleChanged(); + void onXChanged(); + void onYChanged(); + void onZChanged(); protected: - void blockButtons(bool b); - void changeEvent(QEvent *e); void saveSectionState(); void restoreSectionState(); @@ -75,6 +77,7 @@ protected: void setUiEdit(); void checkAll(bool b); + void enableAll(bool b); void failNoObject(std::string objName); bool isBaseValid(void); diff --git a/src/Mod/TechDraw/Gui/TaskSectionView.ui b/src/Mod/TechDraw/Gui/TaskSectionView.ui index 95cf59f30e..158dd94da4 100644 --- a/src/Mod/TechDraw/Gui/TaskSectionView.ui +++ b/src/Mod/TechDraw/Gui/TaskSectionView.ui @@ -1,468 +1,433 @@ - - - TechDrawGui::TaskSectionView - - - - 0 - 0 - 368 - 450 - - - - - 0 - 0 - - - - - 250 - 450 - - - - Section Parameters - - - - - - - 0 - 0 - - - - - 350 - 400 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - BaseView - - - - - - - false - - - - - - - Identifier - - - - - - - - 0 - 32 - - - - Identifier for this section - - - - - - - - 0 - 32 - - - - - 0 - 0 - - - - 4 - - - 0.000000000000000 - - - 999.000000000000000 - - - 1.000000000000000 - - - - - - - Scale - - - - - - - - - Qt::Horizontal - - - - - - - Section Orientation - - - - - - - - - Looking right - - - - - - - :/icons/actions/section-right.svg - - - - - 48 - 48 - - - - true - - - - - - - Looking up - - - - - - - - - - - - - :/icons/actions/section-up.svg - - - - - 48 - 48 - - - - true - - - - - - - Looking left - - - - - - - :/icons/actions/section-left.svg - - - - - 48 - 48 - - - - true - - - - - - - Looking down - - - - - - - :/icons/actions/section-down.svg - - - - - 48 - 48 - - - - true - - - - - - - - - Qt::Horizontal - - - - - - - Section Plane Location - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - - 0 - 0 - - - - - 150 - 0 - - - - X - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 150 - 32 - - - - <html><head/><body><p>Location of section plane in 3D coordinates</p></body></html> - - - - - - - - - - - 0 - 0 - - - - - 150 - 0 - - - - Y - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 150 - 32 - - - - <html><head/><body><p>Location of section plane in 3D coordinates</p></body></html> - - - - - - - - - - - 0 - 0 - - - - - 150 - 0 - - - - Z - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 150 - 32 - - - - <html><head/><body><p>Location of section plane in 3D coordinates</p></body></html> - - - - - - - - - - - - - - Apply - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - Gui::QuantitySpinBox - QWidget -
Gui/QuantitySpinBox.h
-
-
- - - - -
+ + + TechDrawGui::TaskSectionView + + + + 0 + 0 + 370 + 326 + + + + + 0 + 0 + + + + Section Parameters + + + + + + + + BaseView + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + + 0 + 22 + + + + + + + + Identifier + + + + + + + + 0 + 22 + + + + Identifier for this section + + + + + + + Scale + + + + + + + + 0 + 0 + + + + + 0 + 22 + + + + + 0 + 0 + + + + Scale factor for the section view + + + 0.000000000000000 + + + 999.000000000000000 + + + 1.000000000000000 + + + 4 + + + + + + + + + Qt::Horizontal + + + + + + + Section Orientation + + + + + + + + + 50 + 50 + + + + Looking up + + + + + + + + + + + + + :/icons/actions/section-up.svg + + + + + 48 + 48 + + + + true + + + + + + + + 50 + 50 + + + + Looking down + + + + + + + :/icons/actions/section-down.svg + + + + + 48 + 48 + + + + true + + + + + + + + 50 + 50 + + + + Looking left + + + + + + + :/icons/actions/section-left.svg + + + + + 48 + 48 + + + + true + + + + + + + + 50 + 50 + + + + Looking right + + + + + + + :/icons/actions/section-right.svg + + + + + 48 + 48 + + + + true + + + + + + + + + + + + Qt::Horizontal + + + + + + + Position from the 3D origin of the object in the view + + + Section Plane Location + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + X + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 150 + 22 + + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Y + + + + + + + + 0 + 0 + + + + + 150 + 22 + + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Z + + + + + + + + 0 + 0 + + + + + 150 + 22 + + + + + + + + + + + + + + + + + Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
+
+
+ + + + +
From 286c50e99d1e44e9d422a30f9b911be43e70bead Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 29 Mar 2020 18:03:17 +0200 Subject: [PATCH 077/117] TaskSectionView.cpp: fix a typo --- src/Mod/TechDraw/Gui/TaskSectionView.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/TechDraw/Gui/TaskSectionView.cpp b/src/Mod/TechDraw/Gui/TaskSectionView.cpp index 8e86e9a150..a2b32248b0 100644 --- a/src/Mod/TechDraw/Gui/TaskSectionView.cpp +++ b/src/Mod/TechDraw/Gui/TaskSectionView.cpp @@ -154,7 +154,7 @@ TaskSectionView::~TaskSectionView() void TaskSectionView::setUiPrimary() { // Base::Console().Message("TSV::setUiPrimary()\n"); - setWindowTitle(QObject::tr("Create SectionView")); + setWindowTitle(QObject::tr("Create Section View")); std::string temp = m_base->getNameInDocument(); QString qTemp = Base::Tools::fromStdString(temp); ui->leBaseView->setText(qTemp); @@ -184,7 +184,7 @@ void TaskSectionView::setUiPrimary() void TaskSectionView::setUiEdit() { // Base::Console().Message("TSV::setUiEdit()\n"); - setWindowTitle(QObject::tr("Edit SectionView")); + setWindowTitle(QObject::tr("Edit Section View")); std::string temp = m_base->getNameInDocument(); QString qTemp = Base::Tools::fromStdString(temp); From ccbdf1c11f4559530550d23e2d3a51b2d07872a5 Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 29 Mar 2020 18:19:22 +0200 Subject: [PATCH 078/117] TaskSectionView.cpp: add missing unit initialization --- src/Mod/TechDraw/Gui/TaskSectionView.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Mod/TechDraw/Gui/TaskSectionView.cpp b/src/Mod/TechDraw/Gui/TaskSectionView.cpp index a2b32248b0..36e5dc1e93 100644 --- a/src/Mod/TechDraw/Gui/TaskSectionView.cpp +++ b/src/Mod/TechDraw/Gui/TaskSectionView.cpp @@ -164,8 +164,11 @@ void TaskSectionView::setUiPrimary() ui->sbScale->setValue(m_base->getScale()); Base::Vector3d origin = m_base->getOriginalCentroid(); + ui->sbOrgX->setUnit(Base::Unit::Length); ui->sbOrgX->setValue(origin.x); + ui->sbOrgY->setUnit(Base::Unit::Length); ui->sbOrgY->setValue(origin.y); + ui->sbOrgZ->setUnit(Base::Unit::Length); ui->sbOrgZ->setValue(origin.z); // before the user did not select an orientation, @@ -191,13 +194,16 @@ void TaskSectionView::setUiEdit() ui->leBaseView->setText(qTemp); temp = m_section->SectionSymbol.getValue(); - qTemp = Base::Tools::fromStdString(temp); + qTemp = Base::Tools::fromStdString(temp); ui->leSymbol->setText(qTemp); ui->sbScale->setValue(m_section->getScale()); Base::Vector3d origin = m_section->SectionOrigin.getValue(); + ui->sbOrgX->setUnit(Base::Unit::Length); ui->sbOrgX->setValue(origin.x); + ui->sbOrgY->setUnit(Base::Unit::Length); ui->sbOrgY->setValue(origin.y); + ui->sbOrgZ->setUnit(Base::Unit::Length); ui->sbOrgZ->setValue(origin.z); // connect affter initializing the object values From 0f477df6ed29485c92c5c8eeb2bb4f4421068598 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Sun, 29 Mar 2020 13:04:47 -0400 Subject: [PATCH 079/117] [TD]prevent loop with AutoScale --- src/Mod/TechDraw/App/DrawView.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawView.cpp b/src/Mod/TechDraw/App/DrawView.cpp index 8302613477..873e934a98 100644 --- a/src/Mod/TechDraw/App/DrawView.cpp +++ b/src/Mod/TechDraw/App/DrawView.cpp @@ -116,6 +116,9 @@ void DrawView::checkScale(void) void DrawView::onChanged(const App::Property* prop) { +//Coding note: calling execute, recompute or recomputeFeature inside an onChanged +//method can create infinite loops. In general don't do this! There may be +//situations where it is OK, but careful analysis is a must. if (!isRestoring()) { if (prop == &ScaleType) { auto page = findParentPage(); @@ -144,14 +147,9 @@ void DrawView::onChanged(const App::Property* prop) handleXYLock(); LockPosition.purgeTouched(); } - if ((prop == &Caption) || - (prop == &Label)) { - requestPaint(); - } // rotation and scaling requires recompute - else if ((prop == &Rotation) || - (prop == &Scale) || - (prop == &ScaleType)) { - recompute(); + if ((prop == &Caption) || + (prop == &Label)) { + requestPaint(); } } App::DocumentObject::onChanged(prop); From f520737a289834e519fb76bc75ee15ad6b7be64e Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 29 Mar 2020 20:27:40 +0200 Subject: [PATCH 080/117] [TD] sanitize active view dialog - add missing enable slot - fix size policies - add missing unit initialization - add and fix tooltips --- src/Mod/TechDraw/Gui/TaskActiveView.cpp | 6 +- src/Mod/TechDraw/Gui/TaskActiveView.ui | 527 +++++++++++++----------- 2 files changed, 289 insertions(+), 244 deletions(-) diff --git a/src/Mod/TechDraw/Gui/TaskActiveView.cpp b/src/Mod/TechDraw/Gui/TaskActiveView.cpp index 1bb61b5623..a7f123e4cf 100644 --- a/src/Mod/TechDraw/Gui/TaskActiveView.cpp +++ b/src/Mod/TechDraw/Gui/TaskActiveView.cpp @@ -73,11 +73,15 @@ TaskActiveView::TaskActiveView(TechDraw::DrawPage* pageFeat) : // Base::Console().Message("TAV::TAV() - create mode\n"); if (m_pageFeat == nullptr) { //should be caught in CMD caller - Base::Console().Error("TaskActiveView - bad parameters. Can not proceed.\n"); + Base::Console().Error("TaskActiveView - bad parameters. Can not proceed.\n"); return; } ui->setupUi(this); + ui->qsbWidth->setUnit(Base::Unit::Length); + ui->qsbHeight->setUnit(Base::Unit::Length); + ui->qsbBorder->setUnit(Base::Unit::Length); + setUiPrimary(); } diff --git a/src/Mod/TechDraw/Gui/TaskActiveView.ui b/src/Mod/TechDraw/Gui/TaskActiveView.ui index 70a5860dbb..4f7dda1a0c 100644 --- a/src/Mod/TechDraw/Gui/TaskActiveView.ui +++ b/src/Mod/TechDraw/Gui/TaskActiveView.ui @@ -1,243 +1,284 @@ - - - TaskActiveView - - - - 0 - 0 - 423 - 317 - - - - - 0 - 0 - - - - - 250 - 0 - - - - ActiveView to TD View - - - - :/icons/actions/techdraw-ActiveView.svg:/icons/actions/techdraw-activeview.svg - - - - - - - 0 - 0 - - - - QFrame::Box - - - QFrame::Raised - - - - - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - Width - - - - - - - Width of generated view - - - - - - 0.000000000000000 - - - 297.000000000000000 - - - - - - - Height - - - - - - - Border - - - - - - - Unused area around view - - - - - - 0.000000000000000 - - - - - - - Paint background yes/no - - - Background - - - - - - - Background color - - - - - - - Line Width - - - - - - - Width of lines in generated view. - - - - - - 0.000000000000000 - - - 0.500000000000000 - - - - - - - Render Mode - - - - - - - Drawing style - see SoRenderManager - - - - AS_IS - - - - - WIREFRAME - - - - - POINTS - - - - - WIREFRAME_OVERLAY - - - - - HIDDEN_LINE - - - - - BOUNDING_BOX - - - - - - - - Height of generated view - - - - - - 0.000000000000000 - - - 210.000000000000000 - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - - Gui::QuantitySpinBox - QWidget -
Gui/QuantitySpinBox.h
-
- - Gui::ColorButton - QPushButton -
Gui/Widgets.h
-
-
- - - - -
+ + + TaskActiveView + + + + 0 + 0 + 375 + 176 + + + + + 0 + 0 + + + + + 0 + 0 + + + + ActiveView to TD View + + + + :/icons/actions/techdraw-ActiveView.svg:/icons/actions/techdraw-ActiveView.svg + + + + + + + + Width + + + + + + + + 0 + 0 + + + + Width of generated view + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 0.000000000000000 + + + 297.000000000000000 + + + + + + + Height + + + + + + + + 0 + 0 + + + + Height of generated view + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 0.000000000000000 + + + 210.000000000000000 + + + + + + + Border + + + + + + + + 0 + 0 + + + + Minimal distance of the object from +the top and left view border + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 0.000000000000000 + + + + + + + Paint background yes/no + + + Background + + + + + + + Qt::Horizontal + + + + 28 + 20 + + + + + + + + false + + + + 0 + 0 + + + + Background color + + + + + + + Line Width + + + + + + + + 0 + 0 + + + + Width of lines in generated view + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 0.000000000000000 + + + 0.100000000000000 + + + 0.500000000000000 + + + + + + + Render Mode + + + + + + + Drawing style - see SoRenderManager + + + + AS_IS + + + + + WIREFRAME + + + + + POINTS + + + + + WIREFRAME_OVERLAY + + + + + HIDDEN_LINE + + + + + BOUNDING_BOX + + + + + + + + + + + Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
+
+ + Gui::ColorButton + QPushButton +
Gui/Widgets.h
+
+
+ + + + + + cbbg + toggled(bool) + ccBgColor + setEnabled(bool) + + + 49 + 99 + + + 295 + 99 + + + + +
From f516c4af0535052eeaf7d436d35eeaab2102e3d7 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 30 Mar 2020 11:31:15 +0200 Subject: [PATCH 081/117] FEM: z88 mesh exporter, fix file handle type --- src/Mod/Fem/feminout/importZ88Mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Fem/feminout/importZ88Mesh.py b/src/Mod/Fem/feminout/importZ88Mesh.py index 7388ff9bc0..7bc92951d0 100644 --- a/src/Mod/Fem/feminout/importZ88Mesh.py +++ b/src/Mod/Fem/feminout/importZ88Mesh.py @@ -91,7 +91,7 @@ def export( femnodes_mesh = obj.FemMesh.Nodes femelement_table = meshtools.get_femelement_table(obj.FemMesh) z88_element_type = get_z88_element_type(obj.FemMesh, femelement_table) - f = pyopen(filename, "wb") + f = pyopen(filename, "w") write_z88_mesh_to_file(femnodes_mesh, femelement_table, z88_element_type, f) f.close() From adde500f295b8e0d115de3257165bf95f0ec056e Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 29 Mar 2020 23:25:28 +0200 Subject: [PATCH 082/117] [TD] overhaul welding UI - make some strings translatable - remove some unused code - fix UI size policies - show changes while editing directly (only repaint necessary) - avoid unused variables - add tooltips --- src/Mod/TechDraw/Gui/SymbolChooser.ui | 117 ++--- src/Mod/TechDraw/Gui/TaskRestoreLines.ui | 2 +- src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp | 211 ++++----- src/Mod/TechDraw/Gui/TaskWeldingSymbol.h | 24 +- src/Mod/TechDraw/Gui/TaskWeldingSymbol.ui | 502 ++++++++++----------- 5 files changed, 409 insertions(+), 447 deletions(-) diff --git a/src/Mod/TechDraw/Gui/SymbolChooser.ui b/src/Mod/TechDraw/Gui/SymbolChooser.ui index 634b025821..355a9ef2e2 100644 --- a/src/Mod/TechDraw/Gui/SymbolChooser.ui +++ b/src/Mod/TechDraw/Gui/SymbolChooser.ui @@ -9,8 +9,8 @@ 0 0 - 400 - 394 + 360 + 280 @@ -19,98 +19,79 @@ true - - - - 19 - 19 - 361 - 341 - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - 2 - - - - - 9 - 19 - 341 - 191 - - - - - - - - - - - - 10 - 220 - 341 - 41 - - - - + + + + + Select a symbol that should be used + + + + + + + + + 0 + 0 + + Cancel - + + + + 0 + 0 + + OK + + + + Qt::Horizontal + + + + 40 + 20 + + + + - - - - - 10 - 280 - 341 - 35 - - - - + + + + Symbol Dir - + + + Directory to welding symbols. + Gui::FileChooser::Directory - - +
+ diff --git a/src/Mod/TechDraw/Gui/TaskRestoreLines.ui b/src/Mod/TechDraw/Gui/TaskRestoreLines.ui index db6338b64c..f8199ce9d3 100644 --- a/src/Mod/TechDraw/Gui/TaskRestoreLines.ui +++ b/src/Mod/TechDraw/Gui/TaskRestoreLines.ui @@ -7,7 +7,7 @@ 0 0 227 - 180 + 130 diff --git a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp index 2f53579f5b..5b2ed4f49f 100644 --- a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp +++ b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp @@ -83,44 +83,27 @@ TaskWeldingSymbol::TaskWeldingSymbol(TechDraw::DrawLeaderLine* leader) : m_leadFeat(leader), m_weldFeat(nullptr), m_createMode(true), - m_arrowDirty(false), m_otherDirty(false) { //TODO: why does DWS need DLL as parent? // Base::Console().Message("TWS::TWS() - create mode\n"); if (m_leadFeat == nullptr) { //should be caught in CMD caller - Base::Console().Error("TaskWeldingSymbol - bad parameters. Can not proceed.\n"); + Base::Console().Error("TaskWeldingSymbol - bad parameters. Can not proceed.\n"); return; } ui->setupUi(this); - connect(ui->pbArrowSymbol, SIGNAL(clicked(bool)), - this, SLOT(onArrowSymbolClicked(bool))); - connect(ui->pbOtherSymbol, SIGNAL(clicked(bool)), - this, SLOT(onOtherSymbolClicked(bool))); - connect(ui->pbOtherErase, SIGNAL(clicked(bool)), - this, SLOT(onOtherEraseClicked(bool))); - - connect(ui->fcSymbolDir, SIGNAL(fileNameSelected(const QString&)), - this, SLOT(onDirectorySelected(const QString&))); - - connect(ui->leArrowTextL, SIGNAL(textEdited(const QString&)), - this, SLOT(onArrowTextChanged(const QString&))); - connect(ui->leArrowTextR, SIGNAL(textEdited(const QString&)), - this, SLOT(onArrowTextChanged(const QString&))); - connect(ui->leArrowTextC, SIGNAL(textEdited(const QString&)), - this, SLOT(onArrowTextChanged(const QString&))); - - connect(ui->leOtherTextL, SIGNAL(textEdited(const QString&)), - this, SLOT(onOtherTextChanged(const QString&))); - connect(ui->leOtherTextR, SIGNAL(textEdited(const QString&)), - this, SLOT(onOtherTextChanged(const QString&))); - connect(ui->leOtherTextC, SIGNAL(textEdited(const QString&)), - this, SLOT(onOtherTextChanged(const QString&))); - - setUiPrimary(); + + connect(ui->pbArrowSymbol, SIGNAL(clicked(bool)), + this, SLOT(onArrowSymbolCreateClicked())); + connect(ui->pbOtherSymbol, SIGNAL(clicked(bool)), + this, SLOT(onOtherSymbolCreateClicked())); + connect(ui->pbOtherErase, SIGNAL(clicked(bool)), + this, SLOT(onOtherEraseCreateClicked())); + connect(ui->fcSymbolDir, SIGNAL(fileNameSelected(QString)), + this, SLOT(onDirectorySelected(const QString))); } //ctor for edit @@ -129,7 +112,6 @@ TaskWeldingSymbol::TaskWeldingSymbol(TechDraw::DrawWeldSymbol* weld) : m_leadFeat(nullptr), m_weldFeat(weld), m_createMode(false), - m_arrowDirty(false), m_otherDirty(false) { // Base::Console().Message("TWS::TWS() - edit mode\n"); @@ -150,33 +132,41 @@ TaskWeldingSymbol::TaskWeldingSymbol(TechDraw::DrawWeldSymbol* weld) : ui->setupUi(this); + setUiEdit(); + connect(ui->pbArrowSymbol, SIGNAL(clicked(bool)), - this, SLOT(onArrowSymbolClicked(bool))); + this, SLOT(onArrowSymbolClicked())); connect(ui->pbOtherSymbol, SIGNAL(clicked(bool)), - this, SLOT(onOtherSymbolClicked(bool))); + this, SLOT(onOtherSymbolClicked())); connect(ui->pbOtherErase, SIGNAL(clicked(bool)), - this, SLOT(onOtherEraseClicked(bool))); + this, SLOT(onOtherEraseClicked())); - connect(ui->fcSymbolDir, SIGNAL(fileNameSelected(const QString&)), - this, SLOT(onDirectorySelected(const QString&))); + connect(ui->fcSymbolDir, SIGNAL(fileNameSelected(QString)), + this, SLOT(onDirectorySelected(const QString))); - connect(ui->leArrowTextL, SIGNAL(textEdited(const QString&)), - this, SLOT(onArrowTextChanged(const QString&))); - connect(ui->leArrowTextR, SIGNAL(textEdited(const QString&)), - this, SLOT(onArrowTextChanged(const QString&))); - connect(ui->leArrowTextC, SIGNAL(textEdited(const QString&)), - this, SLOT(onArrowTextChanged(const QString&))); + connect(ui->leArrowTextL, SIGNAL(textEdited(QString)), + this, SLOT(onArrowTextChanged())); + connect(ui->leArrowTextR, SIGNAL(textEdited(QString)), + this, SLOT(onArrowTextChanged())); + connect(ui->leArrowTextC, SIGNAL(textEdited(QString)), + this, SLOT(onArrowTextChanged())); - connect(ui->leOtherTextL, SIGNAL(textEdited(const QString&)), - this, SLOT(onOtherTextChanged(const QString&))); - connect(ui->leOtherTextR, SIGNAL(textEdited(const QString&)), - this, SLOT(onOtherTextChanged(const QString&))); - connect(ui->leOtherTextC, SIGNAL(textEdited(const QString&)), - this, SLOT(onOtherTextChanged(const QString&))); + connect(ui->leOtherTextL, SIGNAL(textEdited(QString)), + this, SLOT(onOtherTextChanged())); + connect(ui->leOtherTextR, SIGNAL(textEdited(QString)), + this, SLOT(onOtherTextChanged())); + connect(ui->leOtherTextC, SIGNAL(textEdited(QString)), + this, SLOT(onOtherTextChanged())); - saveState(); - setUiEdit(); + connect(ui->leTailText, SIGNAL(textEdited(QString)), + this, SLOT(onWeldingChanged())); + connect(ui->cbFieldWeld, SIGNAL(toggled(bool)), + this, SLOT(onWeldingChanged())); + connect(ui->cbAllAround, SIGNAL(toggled(bool)), + this, SLOT(onWeldingChanged())); + connect(ui->cbAltWeld, SIGNAL(toggled(bool)), + this, SLOT(onWeldingChanged())); } TaskWeldingSymbol::~TaskWeldingSymbol() @@ -246,7 +236,7 @@ void TaskWeldingSymbol::setUiEdit() ui->pbArrowSymbol->setIconSize(iconSize); ui->pbArrowSymbol->setText(QString()); } else { - ui->pbArrowSymbol->setText(QString::fromUtf8("Symbol")); + ui->pbArrowSymbol->setText(tr("Symbol")); } } @@ -263,78 +253,106 @@ void TaskWeldingSymbol::setUiEdit() if (fi.isReadable()) { qTemp = QString::fromUtf8(m_otherFeat->SymbolFile.getValue()); QIcon targetIcon(qTemp); - QSize iconSize(32,32); + QSize iconSize(32, 32); ui->pbOtherSymbol->setIcon(targetIcon); ui->pbOtherSymbol->setIconSize(iconSize); ui->pbOtherSymbol->setText(QString()); } else { - ui->pbOtherSymbol->setText(QString::fromUtf8("Symbol")); + ui->pbOtherSymbol->setText(tr("Symbol")); } } ui->pbArrowSymbol->setFocus(); } -void TaskWeldingSymbol::onArrowSymbolClicked(bool b) +void TaskWeldingSymbol::onArrowSymbolCreateClicked() { -// Base::Console().Message("TWS::OnArrowSymbolClicked()\n"); - Q_UNUSED(b); + QString source = tr("arrow"); + SymbolChooser* dlg = new SymbolChooser(this, m_currDir, source); + connect(dlg, SIGNAL(symbolSelected(QString, QString)), + this, SLOT(onSymbolSelected(QString, QString))); + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->exec(); +} - QString source = QString::fromUtf8("arrow"); +void TaskWeldingSymbol::onArrowSymbolClicked() +{ + QString source = tr("arrow"); SymbolChooser* dlg = new SymbolChooser(this, m_currDir, source); connect(dlg, SIGNAL(symbolSelected(QString, QString)), this, SLOT(onSymbolSelected(QString, QString))); dlg->setAttribute(Qt::WA_DeleteOnClose); - //int rc = + dlg->exec(); + updateTiles(); + m_weldFeat->requestPaint(); +} + +void TaskWeldingSymbol::onOtherSymbolCreateClicked() +{ + QString source = tr("other"); + SymbolChooser* dlg = new SymbolChooser(this, m_currDir, source); + connect(dlg, SIGNAL(symbolSelected(QString, QString)), + this, SLOT(onSymbolSelected(QString, QString))); + dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->exec(); } -void TaskWeldingSymbol::onOtherSymbolClicked(bool b) +void TaskWeldingSymbol::onOtherSymbolClicked() { -// Base::Console().Message("TWS::OnOtherSymbolClicked()\n"); - Q_UNUSED(b); - - QString source = QString::fromUtf8("other"); + QString source = tr("other"); SymbolChooser* dlg = new SymbolChooser(this, m_currDir, source); connect(dlg, SIGNAL(symbolSelected(QString, QString)), this, SLOT(onSymbolSelected(QString, QString))); dlg->setAttribute(Qt::WA_DeleteOnClose); - -// int rc = dlg->exec(); + updateTiles(); + m_weldFeat->requestPaint(); } -void TaskWeldingSymbol::onOtherEraseClicked(bool b) +void TaskWeldingSymbol::onOtherEraseCreateClicked() { -// Base::Console().Message("TWS::onOtherEraseClicked()\n"); - Q_UNUSED(b); - m_otherDirty = true; - m_otherOut.init(); - ui->leOtherTextL->setText(QString()); ui->leOtherTextC->setText(QString()); ui->leOtherTextR->setText(QString()); ui->pbOtherSymbol->setIcon(QIcon()); - ui->pbOtherSymbol->setText(QString::fromUtf8("Symbol")); + ui->pbOtherSymbol->setText(tr("Symbol")); m_otherOut.init(); m_otherPath = QString(); } -void TaskWeldingSymbol::onArrowTextChanged(const QString& qs) +void TaskWeldingSymbol::onOtherEraseClicked() { -// Base::Console().Message("TWS::onArrowTextChanged(%s)\n", qPrintable(qs)); - Q_UNUSED(qs); - m_arrowDirty = true; -} - -void TaskWeldingSymbol::onOtherTextChanged(const QString& qs) -{ -// Base::Console().Message("TWS::onOtherTextChanged(%s)\n", qPrintable(qs)); - Q_UNUSED(qs); m_otherDirty = true; + ui->leOtherTextL->setText(QString()); + ui->leOtherTextC->setText(QString()); + ui->leOtherTextR->setText(QString()); + ui->pbOtherSymbol->setIcon(QIcon()); + ui->pbOtherSymbol->setText(tr("Symbol")); + m_otherOut.init(); + m_otherPath = QString(); + updateTiles(); + m_weldFeat->requestPaint(); } +void TaskWeldingSymbol::onArrowTextChanged() +{ + updateTiles(); + m_weldFeat->requestPaint(); +} + +void TaskWeldingSymbol::onOtherTextChanged() +{ + m_otherDirty = true; + updateTiles(); + m_weldFeat->requestPaint(); +} + +void TaskWeldingSymbol::onWeldingChanged() +{ + updateWeldingSymbol(); + m_weldFeat->requestPaint(); +} void TaskWeldingSymbol::onDirectorySelected(const QString& newDir) { @@ -349,10 +367,9 @@ void TaskWeldingSymbol::onSymbolSelected(QString symbolPath, // qPrintable(symbolPath), qPrintable(source)); QIcon targetIcon(symbolPath); QSize iconSize(32,32); - QString arrow = QString::fromUtf8("arrow"); - QString other = QString::fromUtf8("other"); + QString arrow = tr("arrow"); + QString other = tr("other"); if (source == arrow) { - m_arrowDirty = true; ui->pbArrowSymbol->setIcon(targetIcon); ui->pbArrowSymbol->setIconSize(iconSize); ui->pbArrowSymbol->setText(QString()); @@ -366,16 +383,6 @@ void TaskWeldingSymbol::onSymbolSelected(QString symbolPath, } } -void TaskWeldingSymbol::blockButtons(bool b) -{ - Q_UNUSED(b); -} - -//obsolete. tiles are only updated on accept. -void TaskWeldingSymbol::saveState(void) -{ -} - void TaskWeldingSymbol::collectArrowData(void) { // Base::Console().Message("TWS::collectArrowData()\n"); @@ -383,10 +390,10 @@ void TaskWeldingSymbol::collectArrowData(void) m_arrowOut.arrowSide = false; m_arrowOut.row = 0; m_arrowOut.col = 0; - m_arrowOut.leftText = Base::Tools::toStdString(ui->leArrowTextL->text()); - m_arrowOut.centerText = Base::Tools::toStdString(ui->leArrowTextC->text()); - m_arrowOut.rightText = Base::Tools::toStdString(ui->leArrowTextR->text()); - m_arrowOut.symbolPath= Base::Tools::toStdString(m_arrowPath); + m_arrowOut.leftText = ui->leArrowTextL->text().toStdString(); + m_arrowOut.centerText = ui->leArrowTextC->text().toStdString(); + m_arrowOut.rightText = ui->leArrowTextR->text().toStdString(); + m_arrowOut.symbolPath= m_arrowPath.toStdString(); m_arrowOut.tileName = ""; } @@ -397,10 +404,10 @@ void TaskWeldingSymbol::collectOtherData(void) m_otherOut.arrowSide = false; m_otherOut.row = -1; m_otherOut.col = 0; - m_otherOut.leftText = Base::Tools::toStdString(ui->leOtherTextL->text()); - m_otherOut.centerText = Base::Tools::toStdString(ui->leOtherTextC->text()); - m_otherOut.rightText = Base::Tools::toStdString(ui->leOtherTextR->text()); - m_otherOut.symbolPath = Base::Tools::toStdString(m_otherPath); + m_otherOut.leftText = ui->leOtherTextL->text().toStdString(); + m_otherOut.centerText = ui->leOtherTextC->text().toStdString(); + m_otherOut.rightText = ui->leOtherTextR->text().toStdString(); + m_otherOut.symbolPath = m_otherPath.toStdString(); m_otherOut.tileName = ""; } @@ -462,7 +469,7 @@ TechDraw::DrawWeldSymbol* TaskWeldingSymbol::createWeldingSymbol(void) Command::doCommand(Command::Doc,"App.activeDocument().%s.AlternatingWeld = %s", symbolName.c_str(), altWeldText.c_str()); - std::string tailText = Base::Tools::toStdString(ui->leTailText->text()); + std::string tailText = ui->leTailText->text().toStdString(); tailText = Base::Tools::escapeEncodeString(tailText); Command::doCommand(Command::Doc,"App.activeDocument().%s.TailText = '%s'", symbolName.c_str(), tailText.c_str()); @@ -497,7 +504,7 @@ void TaskWeldingSymbol::updateWeldingSymbol(void) Command::doCommand(Command::Doc,"App.activeDocument().%s.AlternatingWeld = %s", symbolName.c_str(), altWeldText.c_str()); - std::string tailText = Base::Tools::toStdString(ui->leTailText->text()); + std::string tailText = ui->leTailText->text().toStdString(); tailText = Base::Tools::escapeEncodeString(tailText); Command::doCommand(Command::Doc,"App.activeDocument().%s.TailText = '%s'", symbolName.c_str(), tailText.c_str()); diff --git a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.h b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.h index f3acf1007e..84f4b44530 100644 --- a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.h +++ b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.h @@ -102,14 +102,15 @@ public: ~TaskWeldingSymbol(); public Q_SLOTS: - void onArrowSymbolClicked(bool b); - - void onOtherSymbolClicked(bool b); - void onOtherEraseClicked(bool b); - - void onArrowTextChanged(const QString& qs); - void onOtherTextChanged(const QString& qs); - + void onArrowSymbolCreateClicked(); + void onArrowSymbolClicked(); + void onOtherSymbolCreateClicked(); + void onOtherSymbolClicked(); + void onOtherEraseCreateClicked(); + void onOtherEraseClicked(); + void onArrowTextChanged(); + void onOtherTextChanged(); + void onWeldingChanged(); void onDirectorySelected(const QString& newDir); void onSymbolSelected(QString symbolPath, QString source); @@ -125,8 +126,6 @@ protected Q_SLOTS: protected: void changeEvent(QEvent *e); - - void blockButtons(bool b); void setUiPrimary(void); void setUiEdit(); @@ -141,7 +140,6 @@ protected: void collectOtherData(void); std::string prefSymbolDir(); - void saveState(void); QString m_currDir; @@ -163,14 +161,10 @@ private: QString m_arrowSymbol; QString m_otherSymbol; -/* std::vector m_toRemove;*/ - QPushButton* m_btnOK; QPushButton* m_btnCancel; bool m_createMode; - - bool m_arrowDirty; bool m_otherDirty; }; diff --git a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.ui b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.ui index 22e2949735..c1955ee51a 100644 --- a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.ui +++ b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.ui @@ -6,12 +6,12 @@ 0 0 - 423 - 374 + 400 + 244 - + 0 0 @@ -29,268 +29,248 @@ :/icons/actions/techdraw-weldsymbol.svg:/icons/actions/techdraw-weldsymbol.svg - + - - - - 0 - 0 - + + + + + + + + + Text before arrow side symbol + + + + + + + Text after arrow side symbol + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 32 + + + + Pick arrow side symbol + + + + + + Symbol + + + false + + + + + + + Text above arrow side symbol + + + + + + + + + + + QFrame::Plain + + + 5 + + + Qt::Horizontal + + + + + + + + + + + Text after other side symbol + + + + + + + Pick other side symbol + + + Symbol + + + + + + + Text before other side symbol + + + + + + + Text below other side symbol + + + + + + + + 0 + 0 + + + + + 60 + 30 + + + + + 60 + 30 + + + + + 60 + 30 + + + + Remove other side symbol + + + Delete + + + + + + + + + + + + + Qt::Horizontal - - QFrame::Box - - - QFrame::Raised - - - - - - - - - - - - Text before arrow side symbol - - - - - - - Text after arrow side symbol - - - - - - - - 0 - 0 - - - - - 0 - 32 - - - - - 0 - 32 - - - - Pick arrow side symbol - - - - - - Symbol - - - false - - - - - - - Text above arrow side symbol - - - - - - - - - - - QFrame::Plain - - - 5 - - - Qt::Horizontal - - - - - - - - - - - Text after other side symbol - - - - - - - Pick other side symbol - - - Symbol - - - - - - - Text before other side symbol - - - - - - - Text below other side symbol - - - - - - - - 0 - 0 - - - - - 60 - 30 - - - - - 60 - 30 - - - - - 60 - 30 - - - - Remove other side symbol - - - Delete - - - - - - - - - - - - - Qt::Horizontal - - - - - - - - - - - Field Weld - - - - - - - All Around - - - - - - - Alternating - - - - - - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - Tail Text - - - - - - - Text at end of symbol - - - - - - - Symbol Directory - - - - - - - Pick a directory of welding symbols - - - Gui::FileChooser::Directory - - - *.svg - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - + + + + + + Adds the 'Field Weld' symbol (flag) +at the kink in the leader line + + + Field Weld + + + + + + + Adds the 'All Around' symbol (circle) +at the kink in the leader line + + + All Around + + + + + + + Offsets the lower symbol to indicate alternating welds + + + Alternating + + + + + + + + + + + Directory to welding symbols. +This directory will be used for the symbol selection. + + + Gui::FileChooser::Directory + + + *.svg + + + + + + + + 0 + 0 + + + + Text at end of symbol + + + + + + + Symbol Directory + + + + + + + Tail Text + + + + + From 5da2b423511f6476e8bdda85db1c8c59207ef457 Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 29 Mar 2020 23:31:24 +0200 Subject: [PATCH 083/117] TaskWeldingSymbol.cpp: revert an unintended change --- src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp index 5b2ed4f49f..1e49c58d7b 100644 --- a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp +++ b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp @@ -102,8 +102,8 @@ TaskWeldingSymbol::TaskWeldingSymbol(TechDraw::DrawLeaderLine* leader) : this, SLOT(onOtherSymbolCreateClicked())); connect(ui->pbOtherErase, SIGNAL(clicked(bool)), this, SLOT(onOtherEraseCreateClicked())); - connect(ui->fcSymbolDir, SIGNAL(fileNameSelected(QString)), - this, SLOT(onDirectorySelected(const QString))); + connect(ui->fcSymbolDir, SIGNAL(fileNameSelected(const QString&)), + this, SLOT(onDirectorySelected(const QString&))); } //ctor for edit @@ -142,8 +142,8 @@ TaskWeldingSymbol::TaskWeldingSymbol(TechDraw::DrawWeldSymbol* weld) : connect(ui->pbOtherErase, SIGNAL(clicked(bool)), this, SLOT(onOtherEraseClicked())); - connect(ui->fcSymbolDir, SIGNAL(fileNameSelected(QString)), - this, SLOT(onDirectorySelected(const QString))); + connect(ui->fcSymbolDir, SIGNAL(fileNameSelected(const QString&)), + this, SLOT(onDirectorySelected(const QString&))); connect(ui->leArrowTextL, SIGNAL(textEdited(QString)), this, SLOT(onArrowTextChanged())); From 954d4e64f5b3d7ef492bfe93417ff96ac7f082a8 Mon Sep 17 00:00:00 2001 From: donovaly Date: Mon, 30 Mar 2020 00:28:13 +0200 Subject: [PATCH 084/117] SymbolChooser: use Qt's OK|Cancel buttons - use the standard Qt buttons for the OK/Cancel action (QDialogButtonBox) advantage: better layout and cleaner than 2 own and separate buttons --- src/Mod/TechDraw/Gui/SymbolChooser.cpp | 17 ++--- src/Mod/TechDraw/Gui/SymbolChooser.h | 4 +- src/Mod/TechDraw/Gui/SymbolChooser.ui | 93 +++++++++++++------------- 3 files changed, 54 insertions(+), 60 deletions(-) diff --git a/src/Mod/TechDraw/Gui/SymbolChooser.cpp b/src/Mod/TechDraw/Gui/SymbolChooser.cpp index fac665daf4..6cc636663b 100644 --- a/src/Mod/TechDraw/Gui/SymbolChooser.cpp +++ b/src/Mod/TechDraw/Gui/SymbolChooser.cpp @@ -48,10 +48,6 @@ SymbolChooser::SymbolChooser(QWidget *parent, m_source(source) { ui->setupUi(this); - connect(ui->pbOK, SIGNAL(clicked(bool)), - this, SLOT(onOKClicked(bool))); - connect(ui->pbCancel, SIGNAL(clicked(bool)), - this, SLOT(onCancelClicked(bool))); connect(ui->fcSymbolDir, SIGNAL(fileNameSelected(const QString&)), this, SLOT(onDirectorySelected(const QString&))); connect(ui->lwSymbols, SIGNAL(itemClicked(QListWidgetItem*)), //double click? @@ -85,10 +81,9 @@ void SymbolChooser::setUiPrimary() ui->lwSymbols->setAcceptDrops(false); } -void SymbolChooser::onOKClicked(bool b) +void SymbolChooser::onOKClicked() { - Q_UNUSED(b); -// Base::Console().Message("SC::OnOKClicked()\n"); + QDialog::accept(); QListWidgetItem* sourceItem = ui->lwSymbols->currentItem(); if (!sourceItem) return; @@ -98,15 +93,11 @@ void SymbolChooser::onOKClicked(bool b) QString::fromUtf8(".svg"); Q_EMIT symbolSelected(m_symbolPath, m_source); -// Base::Console().Message("SC::onOKClicked - symbol; %s\n", qPrintable(m_symbolPath)); - accept(); } -void SymbolChooser::onCancelClicked(bool b) +void SymbolChooser::onCancelClicked() { - Q_UNUSED(b); -// Base::Console().Message("SC::OnCancelCicked()\n"); - reject(); + QDialog::reject(); } void SymbolChooser::onItemClicked(QListWidgetItem* item) diff --git a/src/Mod/TechDraw/Gui/SymbolChooser.h b/src/Mod/TechDraw/Gui/SymbolChooser.h index d45fc58eee..dbd9fe7556 100644 --- a/src/Mod/TechDraw/Gui/SymbolChooser.h +++ b/src/Mod/TechDraw/Gui/SymbolChooser.h @@ -40,8 +40,8 @@ public: QString source = QString()); public Q_SLOTS: - void onOKClicked(bool b); - void onCancelClicked(bool b); + void onOKClicked(); + void onCancelClicked(); void onItemClicked(QListWidgetItem* item); void onDirectorySelected(const QString& newDir); diff --git a/src/Mod/TechDraw/Gui/SymbolChooser.ui b/src/Mod/TechDraw/Gui/SymbolChooser.ui index 355a9ef2e2..c7bc556075 100644 --- a/src/Mod/TechDraw/Gui/SymbolChooser.ui +++ b/src/Mod/TechDraw/Gui/SymbolChooser.ui @@ -1,7 +1,7 @@ - SymbolChooser - + TechDrawGui::SymbolChooser + Qt::WindowModal @@ -14,7 +14,7 @@ - SymbolChooser + Symbol Chooser true @@ -28,47 +28,17 @@
- - - - - - 0 - 0 - - - - Cancel - - - - - - - - 0 - 0 - - - - OK - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + @@ -101,5 +71,38 @@ - + + + bbButtons + accepted() + TechDrawGui::SymbolChooser + accept() + + + 179 + 228 + + + 179 + 139 + + + + + bbButtons + rejected() + TechDrawGui::SymbolChooser + reject() + + + 179 + 228 + + + 179 + 139 + + + + From fe333b219fd2238a20269b1b3e48031159974a3c Mon Sep 17 00:00:00 2001 From: donovaly Date: Mon, 30 Mar 2020 03:34:44 +0200 Subject: [PATCH 085/117] fix tile properties - make TileColumn read-only - restrict TileRow to what is possible - improve tooltips - add button to flip the sides (the symbol is not mirrored, just flipped) --- src/Mod/TechDraw/App/DrawTile.cpp | 25 ++++++++- src/Mod/TechDraw/App/DrawTile.h | 5 +- src/Mod/TechDraw/App/DrawTileWeld.cpp | 11 ++-- src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp | 57 +++++++++++++++++++- src/Mod/TechDraw/Gui/TaskWeldingSymbol.h | 2 + src/Mod/TechDraw/Gui/TaskWeldingSymbol.ui | 62 +++++++++++++++++----- 6 files changed, 136 insertions(+), 26 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawTile.cpp b/src/Mod/TechDraw/App/DrawTile.cpp index 51af3ca07c..fbeb6b4c27 100644 --- a/src/Mod/TechDraw/App/DrawTile.cpp +++ b/src/Mod/TechDraw/App/DrawTile.cpp @@ -49,8 +49,17 @@ DrawTile::DrawTile(void) ADD_PROPERTY_TYPE(TileParent,(0),group,(App::PropertyType)(App::Prop_None), "Object to which this tile is attached"); - ADD_PROPERTY_TYPE(TileRow, (0), group, App::Prop_None, "Row in parent"); - ADD_PROPERTY_TYPE(TileColumn, (0), group, App::Prop_None, "Column in parent"); + ADD_PROPERTY_TYPE(TileRow, (0), group, App::Prop_None, "Row in parent object\n 0 for arrow side, -1 for other side"); + ADD_PROPERTY_TYPE(TileColumn, (0), group, App::Prop_None, "Column in parent object"); + + // there is currently only one column, this don't allow to edit + TileColumn.setStatus(App::Property::ReadOnly, true); + // the row can only have the value 0 or -1 + // allow its editing because this way the tiles can be flipped + TileRowConstraints.LowerBound = -1; + TileRowConstraints.UpperBound = 0; + TileRowConstraints.StepSize = 1; + TileRow.setConstraints(&TileRowConstraints); } DrawTile::~DrawTile() @@ -77,6 +86,18 @@ App::DocumentObjectExecReturn *DrawTile::execute(void) return DocumentObject::execute(); } +void DrawTile::handleChangedPropertyType(Base::XMLReader &reader, const char *TypeName, App::Property *prop) +// transforms properties that had been changed +{ + // property TileRow had App::PropertyInteger and was changed to App::PropertyIntegerConstraint + if (prop == &TileRow && strcmp(TypeName, "App::PropertyInteger") == 0) { + App::PropertyInteger TileRowProperty; + // restore the PropertyInteger to be able to set its value + TileRowProperty.Restore(reader); + TileRow.setValue(TileRowProperty.getValue()); + } +} + DrawView* DrawTile::getParent(void) const { // Base::Console().Message("DT::getParent() - %s\n", getNameInDocument()); diff --git a/src/Mod/TechDraw/App/DrawTile.h b/src/Mod/TechDraw/App/DrawTile.h index 1f3de65d43..221a35782d 100644 --- a/src/Mod/TechDraw/App/DrawTile.h +++ b/src/Mod/TechDraw/App/DrawTile.h @@ -40,9 +40,9 @@ public: virtual ~DrawTile(); App::PropertyLink TileParent; //eg DrawWeldSymbol - App::PropertyInteger TileRow; + App::PropertyIntegerConstraint TileRow; + App::PropertyIntegerConstraint::Constraints TileRowConstraints; App::PropertyInteger TileColumn; -/* App::PropertyVector TileOrigin; //sb call to TileParent - WeldingSymbol*/ virtual short mustExecute() const; virtual App::DocumentObjectExecReturn *execute(void); @@ -55,6 +55,7 @@ public: protected: virtual void onChanged(const App::Property* prop); + virtual void handleChangedPropertyType(Base::XMLReader &reader, const char *TypeName, App::Property * prop); private: }; diff --git a/src/Mod/TechDraw/App/DrawTileWeld.cpp b/src/Mod/TechDraw/App/DrawTileWeld.cpp index 8be2991608..8d737d002e 100644 --- a/src/Mod/TechDraw/App/DrawTileWeld.cpp +++ b/src/Mod/TechDraw/App/DrawTileWeld.cpp @@ -53,18 +53,17 @@ DrawTileWeld::DrawTileWeld(void) static const char *group = "TileWeld"; ADD_PROPERTY_TYPE(LeftText,(""),group,(App::PropertyType)(App::Prop_None), - "Text LHS"); - ADD_PROPERTY_TYPE(RightText, (0), group, App::Prop_None, "Text RHS"); - ADD_PROPERTY_TYPE(CenterText, (0), group, App::Prop_None, "Text above Symbol"); - ADD_PROPERTY_TYPE(SymbolFile, (prefSymbol()), group, App::Prop_None, "Symbol Symbol File"); - ADD_PROPERTY_TYPE(SymbolIncluded, (""), group,App::Prop_None, + "Text before symbol"); + ADD_PROPERTY_TYPE(RightText, (0), group, App::Prop_None, "Text after symbol"); + ADD_PROPERTY_TYPE(CenterText, (0), group, App::Prop_None, "Text above/below symbol"); + ADD_PROPERTY_TYPE(SymbolFile, (prefSymbol()), group, App::Prop_None, "Symbol File"); + ADD_PROPERTY_TYPE(SymbolIncluded, (""), group, App::Prop_None, "Embedded Symbol. System use only."); // n/a to end users // SymbolFile.setStatus(App::Property::ReadOnly,true); std::string svgFilter("Symbol files (*.svg *.SVG);;All files (*)"); SymbolFile.setFilter(svgFilter); - } DrawTileWeld::~DrawTileWeld() diff --git a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp index 1e49c58d7b..a2a236b2b5 100644 --- a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp +++ b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp @@ -102,6 +102,8 @@ TaskWeldingSymbol::TaskWeldingSymbol(TechDraw::DrawLeaderLine* leader) : this, SLOT(onOtherSymbolCreateClicked())); connect(ui->pbOtherErase, SIGNAL(clicked(bool)), this, SLOT(onOtherEraseCreateClicked())); + connect(ui->pbFlipSides, SIGNAL(clicked(bool)), + this, SLOT(onFlipSidesCreateClicked())); connect(ui->fcSymbolDir, SIGNAL(fileNameSelected(const QString&)), this, SLOT(onDirectorySelected(const QString&))); } @@ -136,11 +138,12 @@ TaskWeldingSymbol::TaskWeldingSymbol(TechDraw::DrawWeldSymbol* weld) : connect(ui->pbArrowSymbol, SIGNAL(clicked(bool)), this, SLOT(onArrowSymbolClicked())); - connect(ui->pbOtherSymbol, SIGNAL(clicked(bool)), this, SLOT(onOtherSymbolClicked())); connect(ui->pbOtherErase, SIGNAL(clicked(bool)), this, SLOT(onOtherEraseClicked())); + connect(ui->pbFlipSides, SIGNAL(clicked(bool)), + this, SLOT(onFlipSidesClicked())); connect(ui->fcSymbolDir, SIGNAL(fileNameSelected(const QString&)), this, SLOT(onDirectorySelected(const QString&))); @@ -231,7 +234,7 @@ void TaskWeldingSymbol::setUiEdit() if (fi.isReadable()) { qTemp = QString::fromUtf8(m_arrowFeat->SymbolFile.getValue()); QIcon targetIcon(qTemp); - QSize iconSize(32,32); + QSize iconSize(32, 32); ui->pbArrowSymbol->setIcon(targetIcon); ui->pbArrowSymbol->setIconSize(iconSize); ui->pbArrowSymbol->setText(QString()); @@ -335,6 +338,56 @@ void TaskWeldingSymbol::onOtherEraseClicked() m_weldFeat->requestPaint(); } +void TaskWeldingSymbol::onFlipSidesCreateClicked() +{ + QString tempText = ui->leOtherTextL->text(); + ui->leOtherTextL->setText(ui->leArrowTextL->text()); + ui->leArrowTextL->setText(tempText); + tempText = ui->leOtherTextC->text(); + ui->leOtherTextC->setText(ui->leArrowTextC->text()); + ui->leArrowTextC->setText(tempText); + tempText = ui->leOtherTextR->text(); + ui->leOtherTextR->setText(ui->leArrowTextR->text()); + ui->leArrowTextR->setText(tempText); + + QString tempPathArrow = m_otherPath; + m_otherPath = m_arrowPath; + m_arrowPath = tempPathArrow; + tempText = ui->pbOtherSymbol->text(); + ui->pbOtherSymbol->setText(ui->pbArrowSymbol->text()); + ui->pbArrowSymbol->setText(tempText); + QIcon tempIcon = ui->pbOtherSymbol->icon(); + ui->pbOtherSymbol->setIcon(ui->pbArrowSymbol->icon()); + ui->pbArrowSymbol->setIcon(tempIcon); +} + +void TaskWeldingSymbol::onFlipSidesClicked() +{ + QString tempText = ui->leOtherTextL->text(); + ui->leOtherTextL->setText(ui->leArrowTextL->text()); + ui->leArrowTextL->setText(tempText); + tempText = ui->leOtherTextC->text(); + ui->leOtherTextC->setText(ui->leArrowTextC->text()); + ui->leArrowTextC->setText(tempText); + tempText = ui->leOtherTextR->text(); + ui->leOtherTextR->setText(ui->leArrowTextR->text()); + ui->leArrowTextR->setText(tempText); + + // one cannot get the path from the icon therfore read out + // the path property + auto tempPathArrow = m_arrowFeat->SymbolFile.getValue(); + auto tempPathOther = m_otherFeat->SymbolFile.getValue(); + m_otherPath = QString::fromLatin1(tempPathArrow); + m_arrowPath = QString::fromLatin1(tempPathOther); + QIcon tempIcon = ui->pbOtherSymbol->icon(); + ui->pbOtherSymbol->setIcon(ui->pbArrowSymbol->icon()); + ui->pbArrowSymbol->setIcon(tempIcon); + + m_otherDirty = true; + updateTiles(); + m_weldFeat->requestPaint(); +} + void TaskWeldingSymbol::onArrowTextChanged() { updateTiles(); diff --git a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.h b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.h index 84f4b44530..6702f82bd4 100644 --- a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.h +++ b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.h @@ -108,6 +108,8 @@ public Q_SLOTS: void onOtherSymbolClicked(); void onOtherEraseCreateClicked(); void onOtherEraseClicked(); + void onFlipSidesCreateClicked(); + void onFlipSidesClicked(); void onArrowTextChanged(); void onOtherTextChanged(); void onWeldingChanged(); diff --git a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.ui b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.ui index c1955ee51a..504f12f6b9 100644 --- a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.ui +++ b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.ui @@ -112,13 +112,6 @@ - - - - Text after other side symbol - - - @@ -129,13 +122,6 @@ - - - - Text before other side symbol - - - @@ -143,6 +129,54 @@ + + + + Text after other side symbol + + + + + + + + 0 + 0 + + + + + 60 + 30 + + + + + 60 + 30 + + + + + 60 + 30 + + + + Flips the sides + + + Flip Sides + + + + + + + Text before other side symbol + + + From c92ab7efb4febde712923eaae57842e0c6f71a4e Mon Sep 17 00:00:00 2001 From: donovaly Date: Mon, 30 Mar 2020 03:52:38 +0200 Subject: [PATCH 086/117] TaskDlgWeldingSymbol: fix creation issue - assure that the other side is always created, also if it has no symbol yet Now everything should be correct and I won't change the PR further --- src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp index a2a236b2b5..215e2706bb 100644 --- a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp +++ b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp @@ -103,7 +103,7 @@ TaskWeldingSymbol::TaskWeldingSymbol(TechDraw::DrawLeaderLine* leader) : connect(ui->pbOtherErase, SIGNAL(clicked(bool)), this, SLOT(onOtherEraseCreateClicked())); connect(ui->pbFlipSides, SIGNAL(clicked(bool)), - this, SLOT(onFlipSidesCreateClicked())); + this, SLOT(onFlipSidesCreateClicked())); connect(ui->fcSymbolDir, SIGNAL(fileNameSelected(const QString&)), this, SLOT(onDirectorySelected(const QString&))); } @@ -205,6 +205,9 @@ void TaskWeldingSymbol::setUiPrimary() m_otherOut.init(); m_otherPath = QString(); m_otherSymbol = QString(); + + // we must mark the other side dirty to assure it gets created + m_otherDirty = true; } void TaskWeldingSymbol::setUiEdit() From 316be97da8dc93df0d26b41c7157c556cc2e8234 Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 29 Mar 2020 23:55:20 +0200 Subject: [PATCH 087/117] [TD] DlgTemplateField.ui: size policy - compact the dialog size --- src/Mod/TechDraw/Gui/DlgTemplateField.ui | 112 ++++++++++------------- 1 file changed, 47 insertions(+), 65 deletions(-) diff --git a/src/Mod/TechDraw/Gui/DlgTemplateField.ui b/src/Mod/TechDraw/Gui/DlgTemplateField.ui index ecd80bbc7e..f595b1717e 100644 --- a/src/Mod/TechDraw/Gui/DlgTemplateField.ui +++ b/src/Mod/TechDraw/Gui/DlgTemplateField.ui @@ -9,8 +9,8 @@ 0 0 - 420 - 160 + 340 + 90 @@ -19,69 +19,51 @@ true - - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - - - - Text Name: - - - - - - - TextLabel - - - - - - - Value: - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - false - - - - - - - + + + + + + + + + Text Name: + + + + + + + TextLabel + + + + + + + Value: + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + From 5316d5d7093b78399f57b488181ba36bd709988a Mon Sep 17 00:00:00 2001 From: donovaly Date: Mon, 30 Mar 2020 00:33:02 +0200 Subject: [PATCH 088/117] DlgTemplateField.ui: remove unnecessary layout --- src/Mod/TechDraw/Gui/DlgTemplateField.ui | 76 +++++++++++------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/src/Mod/TechDraw/Gui/DlgTemplateField.ui b/src/Mod/TechDraw/Gui/DlgTemplateField.ui index f595b1717e..6565a9d86b 100644 --- a/src/Mod/TechDraw/Gui/DlgTemplateField.ui +++ b/src/Mod/TechDraw/Gui/DlgTemplateField.ui @@ -19,52 +19,48 @@ true - + - - - - - - - Text Name: - - - - - - - TextLabel - - - - - - - Value: - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - false + + + + + Text Name: + + + + TextLabel + + + + + + + Value: + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + From 7c9d5fe57c94eeec0289317e6be8a99bdceea11f Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 29 Mar 2020 23:44:43 +0200 Subject: [PATCH 089/117] [TD] geometric Hatch dialog - use Gui::QuantitySpinBox - some UI file size policy adjustments --- src/Mod/TechDraw/Gui/TaskGeomHatch.cpp | 8 +-- src/Mod/TechDraw/Gui/TaskGeomHatch.ui | 72 ++++++++++++++++++-------- 2 files changed, 54 insertions(+), 26 deletions(-) diff --git a/src/Mod/TechDraw/Gui/TaskGeomHatch.cpp b/src/Mod/TechDraw/Gui/TaskGeomHatch.cpp index 6447649c24..def79cfdd2 100644 --- a/src/Mod/TechDraw/Gui/TaskGeomHatch.cpp +++ b/src/Mod/TechDraw/Gui/TaskGeomHatch.cpp @@ -105,11 +105,11 @@ void TaskGeomHatch::updateValues() QString cText = ui->cbName->currentText(); m_name = cText.toUtf8().constData(); m_hatch->NamePattern.setValue(m_name); - m_scale = ui->sbScale->value(); + m_scale = ui->sbScale->value().getValue(); m_hatch->ScalePattern.setValue(m_scale); m_color.setValue(ui->ccColor->color()); m_Vp->ColorPattern.setValue(m_color); - m_weight = ui->sbWeight->value(); + m_weight = ui->sbWeight->value().getValue(); m_Vp->WeightPattern.setValue(m_weight); } @@ -152,13 +152,13 @@ void TaskGeomHatch::onNameChanged() void TaskGeomHatch::onScaleChanged() { - m_hatch->ScalePattern.setValue(ui->sbScale->value()); + m_hatch->ScalePattern.setValue(ui->sbScale->value().getValue()); m_source->getDocument()->recompute(); } void TaskGeomHatch::onLineWeightChanged() { - m_Vp->WeightPattern.setValue(ui->sbWeight->value()); + m_Vp->WeightPattern.setValue(ui->sbWeight->value().getValue()); m_source->getDocument()->recompute(); } diff --git a/src/Mod/TechDraw/Gui/TaskGeomHatch.ui b/src/Mod/TechDraw/Gui/TaskGeomHatch.ui index 52785cec31..44e72ce644 100644 --- a/src/Mod/TechDraw/Gui/TaskGeomHatch.ui +++ b/src/Mod/TechDraw/Gui/TaskGeomHatch.ui @@ -7,11 +7,11 @@ 0 0 385 - 265 + 191 - + 0 0 @@ -25,8 +25,8 @@ Apply Geometric Hatch to Face - - + + @@ -37,8 +37,8 @@ Define your pattern - - + + @@ -62,7 +62,7 @@ - + @@ -107,6 +107,12 @@ + + + 0 + 22 + + Name of pattern within file @@ -114,26 +120,56 @@ + + + 0 + 22 + + Color of pattern lines - + + + + 0 + 22 + + Enlarges/shrinks the pattern + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0.100000000000000 + 1.000000000000000 - + + + + 0 + 22 + + Thickness of lines within the pattern + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0.100000000000000 + 1.000000000000000 @@ -144,19 +180,6 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -165,6 +188,11 @@ QWidget
Gui/FileDialog.h
+ + Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
+
Gui::ColorButton QPushButton From 1908e86e2639d3feca9ca4369006319a220feefa Mon Sep 17 00:00:00 2001 From: Gauthier Date: Mon, 30 Mar 2020 20:01:47 +0200 Subject: [PATCH 090/117] [Path] fix tool change and Feature missing output --- src/Mod/Path/PathScripts/post/grbl_post.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/post/grbl_post.py b/src/Mod/Path/PathScripts/post/grbl_post.py index 4c3f2e9df2..a1d712e379 100755 --- a/src/Mod/Path/PathScripts/post/grbl_post.py +++ b/src/Mod/Path/PathScripts/post/grbl_post.py @@ -261,7 +261,7 @@ def export(objectslist, filename, argstring): return # Skip inactive operations - if not PathUtil.opProperty(obj, 'Active'): + if PathUtil.opProperty(obj, 'Active') is False: continue # do the pre_op From c7ba4f4f3f8dee49e89eefa8adf3b0bd58f52cfb Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 22 Mar 2020 18:02:50 -0500 Subject: [PATCH 091/117] Path: Add `Waterline` icon file --- .../Gui/Resources/icons/Path-Waterline.svg | 281 ++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 src/Mod/Path/Gui/Resources/icons/Path-Waterline.svg diff --git a/src/Mod/Path/Gui/Resources/icons/Path-Waterline.svg b/src/Mod/Path/Gui/Resources/icons/Path-Waterline.svg new file mode 100644 index 0000000000..86c07f4c62 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-Waterline.svg @@ -0,0 +1,281 @@ + + + Path_Waterline + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Path_Waterline + Path-Waterline + 2019-05-19 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Path/Gui/Resources/icons/Path-Waterline.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [russ4262] Russell Johnson + + + + + [russ4262] Russell Johnson + + + + + + + + + + + + + + + + + + + + + + + From 312f09e81ebc5c70e079f9efa6162a190406e7c9 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 22 Mar 2020 18:29:02 -0500 Subject: [PATCH 092/117] Path: Add `Waterline` modules to PathScripts --- src/Mod/Path/PathScripts/PathWaterline.py | 2237 ++++++++++++++++++ src/Mod/Path/PathScripts/PathWaterlineGui.py | 154 ++ 2 files changed, 2391 insertions(+) create mode 100644 src/Mod/Path/PathScripts/PathWaterline.py create mode 100644 src/Mod/Path/PathScripts/PathWaterlineGui.py diff --git a/src/Mod/Path/PathScripts/PathWaterline.py b/src/Mod/Path/PathScripts/PathWaterline.py new file mode 100644 index 0000000000..db2c2229bd --- /dev/null +++ b/src/Mod/Path/PathScripts/PathWaterline.py @@ -0,0 +1,2237 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2016 sliptonic * +# * * +# * 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. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +# * * +# * Additional modifications and contributions beginning 2019 * +# * by Russell Johnson 2020-03-15 10:55 CST * +# * * +# *************************************************************************** + +from __future__ import print_function + +import FreeCAD +import MeshPart +import Path +import PathScripts.PathLog as PathLog +import PathScripts.PathUtils as PathUtils +import PathScripts.PathOp as PathOp + +from PySide import QtCore +import time +import math +import Part +import Draft + +if FreeCAD.GuiUp: + import FreeCADGui + +__title__ = "Path Surface Operation" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Class and implementation of Mill Facing operation." + +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) + + +# Qt translation handling +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + + +# OCL must be installed +try: + import ocl +except ImportError: + FreeCAD.Console.PrintError( + translate("Path_Surface", "This operation requires OpenCamLib to be installed.") + "\n") + import sys + sys.exit(translate("Path_Surface", "This operation requires OpenCamLib to be installed.")) + + +class ObjectSurface(PathOp.ObjectOp): + '''Proxy object for Surfacing operation.''' + + def baseObject(self): + '''baseObject() ... returns super of receiver + Used to call base implementation in overwritten functions.''' + return super(self.__class__, self) + + def opFeatures(self, obj): + '''opFeatures(obj) ... return all standard features and edges based geomtries''' + return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureCoolant | PathOp.FeatureBaseFaces + + def initOperation(self, obj): + '''initPocketOp(obj) ... create facing specific properties''' + obj.addProperty("App::PropertyEnumeration", "BoundBox", "Waterline", QtCore.QT_TRANSLATE_NOOP("App::Property", "Should the operation be limited by the stock object or by the bounding box of the base object")) + obj.addProperty("App::PropertyEnumeration", "LayerMode", "Waterline", QtCore.QT_TRANSLATE_NOOP("App::Property", "The completion mode for the operation: single or multi-pass")) + obj.addProperty("App::PropertyEnumeration", "ScanType", "Waterline", QtCore.QT_TRANSLATE_NOOP("App::Property", "Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan.")) + + obj.addProperty("App::PropertyFloat", "CutterTilt", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")) + obj.addProperty("App::PropertyEnumeration", "RotationAxis", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "The model will be rotated around this axis.")) + obj.addProperty("App::PropertyFloat", "StartIndex", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Start index(angle) for rotational scan")) + obj.addProperty("App::PropertyFloat", "StopIndex", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")) + + obj.addProperty("App::PropertyInteger", "AvoidLastX_Faces", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.")) + obj.addProperty("App::PropertyBool", "AvoidLastX_InternalFeatures", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces.")) + obj.addProperty("App::PropertyDistance", "BoundaryAdjustment", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.")) + obj.addProperty("App::PropertyBool", "BoundaryEnforcement", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).")) + obj.addProperty("App::PropertyDistance", "DepthOffset", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Z-axis offset from the surface of the object")) + obj.addProperty("App::PropertyEnumeration", "HandleMultipleFeatures", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features.")) + obj.addProperty("App::PropertyDistance", "InternalFeaturesAdjustment", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.")) + obj.addProperty("App::PropertyBool", "InternalFeaturesCut", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face.")) + obj.addProperty("App::PropertyEnumeration", "ProfileEdges", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")) + obj.addProperty("App::PropertyDistance", "SampleInterval", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "The Sample Interval. Small values cause long wait times")) + obj.addProperty("App::PropertyPercent", "StepOver", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path")) + + obj.addProperty("App::PropertyVectorDistance", "CircularCenterCustom", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path")) + obj.addProperty("App::PropertyEnumeration", "CircularCenterAt", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "Choose what point to start the ciruclar pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom.")) + obj.addProperty("App::PropertyEnumeration", "CutMode", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part: Climb(ClockWise) or Conventional(CounterClockWise)")) + obj.addProperty("App::PropertyEnumeration", "CutPattern", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use")) + obj.addProperty("App::PropertyFloat", "CutPatternAngle", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")) + obj.addProperty("App::PropertyBool", "CutPatternReversed", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the order of the step-overs will be reversed; the operation will begin cutting the outer most line/arc, and work toward the inner most line/arc.")) + + obj.addProperty("App::PropertyBool", "OptimizeLinearPaths", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.")) + obj.addProperty("App::PropertyBool", "OptimizeStepOverTransitions", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.")) + obj.addProperty("App::PropertyBool", "CircularUseG2G3", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.")) + obj.addProperty("App::PropertyDistance", "GapThreshold", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.")) + obj.addProperty("App::PropertyString", "GapSizes", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")) + + obj.addProperty("App::PropertyBool", "IgnoreWaste", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore areas that proceed below specified depth.")) + obj.addProperty("App::PropertyFloat", "IgnoreWasteDepth", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Depth used to identify waste areas to ignore.")) + obj.addProperty("App::PropertyBool", "ReleaseFromWaste", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Cut through waste to depth at model edge, releasing the model.")) + + obj.addProperty("App::PropertyVectorDistance", "StartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path")) + obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make True, if specifying a Start Point")) + + # For debugging + obj.addProperty('App::PropertyString', 'AreaParams', 'Debugging') + obj.setEditorMode('AreaParams', 2) # hide + obj.addProperty("App::PropertyBool", "ShowTempObjects", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")) + if PathLog.getLevel(PathLog.thisModule()) != 4: + obj.setEditorMode('ShowTempObjects', 2) # hide + + obj.BoundBox = ['BaseBoundBox', 'Stock'] + obj.CircularCenterAt = ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'] + obj.CutMode = ['Conventional', 'Climb'] + obj.CutPattern = ['Line', 'ZigZag', 'Circular', 'CircularZigZag'] # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] + obj.HandleMultipleFeatures = ['Collectively', 'Individually'] + obj.LayerMode = ['Single-pass', 'Multi-pass'] + obj.ProfileEdges = ['None', 'Only', 'First', 'Last'] + obj.RotationAxis = ['X', 'Y'] + obj.ScanType = ['Planar', 'Rotational'] + + if not hasattr(obj, 'DoNotSetDefaultValues'): + self.setEditorProperties(obj) + + self.addedAllProperties = True + + def setEditorProperties(self, obj): + # Used to hide inputs in properties list + + ''' + obj.setEditorMode('CutPattern', 0) + obj.setEditorMode('HandleMultipleFeatures', 0) + obj.setEditorMode('CircularCenterAt', 0) + obj.setEditorMode('CircularCenterCustom', 0) + obj.setEditorMode('CutPatternAngle', 0) + # obj.setEditorMode('BoundaryEnforcement', 0) + + if obj.ScanType == 'Planar': + obj.setEditorMode('RotationAxis', 2) # 2=hidden + obj.setEditorMode('StartIndex', 2) + obj.setEditorMode('StopIndex', 2) + obj.setEditorMode('CutterTilt', 2) + if obj.CutPattern == 'Circular' or obj.CutPattern == 'CircularZigZag': + obj.setEditorMode('CutPatternAngle', 2) + else: # if obj.CutPattern == 'Line' or obj.CutPattern == 'ZigZag': + obj.setEditorMode('CircularCenterAt', 2) + obj.setEditorMode('CircularCenterCustom', 2) + elif obj.ScanType == 'Rotational': + obj.setEditorMode('RotationAxis', 0) # 0=show & editable + obj.setEditorMode('StartIndex', 0) + obj.setEditorMode('StopIndex', 0) + obj.setEditorMode('CutterTilt', 0) + ''' + + obj.setEditorMode('HandleMultipleFeatures', 2) + obj.setEditorMode('CutPattern', 2) + obj.setEditorMode('CutPatternAngle', 2) + # obj.setEditorMode('BoundaryEnforcement', 2) + + # Disable IgnoreWaste feature + obj.setEditorMode('IgnoreWaste', 2) + obj.setEditorMode('IgnoreWasteDepth', 2) + obj.setEditorMode('ReleaseFromWaste', 2) + + def onChanged(self, obj, prop): + if hasattr(self, 'addedAllProperties'): + if self.addedAllProperties is True: + if prop == 'ScanType': + self.setEditorProperties(obj) + if prop == 'CutPattern': + self.setEditorProperties(obj) + + def opOnDocumentRestored(self, obj): + if PathLog.getLevel(PathLog.thisModule()) != 4: + obj.setEditorMode('ShowTempObjects', 2) # hide + else: + obj.setEditorMode('ShowTempObjects', 0) # show + self.addedAllProperties = True + self.setEditorProperties(obj) + + def opSetDefaultValues(self, obj, job): + '''opSetDefaultValues(obj, job) ... initialize defaults''' + job = PathUtils.findParentJob(obj) + + obj.OptimizeLinearPaths = True + obj.IgnoreWaste = False + obj.ReleaseFromWaste = False + obj.InternalFeaturesCut = True + obj.OptimizeStepOverTransitions = False + obj.CircularUseG2G3 = False + obj.BoundaryEnforcement = True + obj.UseStartPoint = False + obj.AvoidLastX_InternalFeatures = True + obj.CutPatternReversed = False + obj.StartPoint.x = 0.0 + obj.StartPoint.y = 0.0 + obj.StartPoint.z = obj.ClearanceHeight.Value + obj.ProfileEdges = 'None' + obj.LayerMode = 'Single-pass' + obj.ScanType = 'Planar' + obj.RotationAxis = 'X' + obj.CutMode = 'Conventional' + obj.CutPattern = 'Line' + obj.HandleMultipleFeatures = 'Collectively' # 'Individually' + obj.CircularCenterAt = 'CenterOfMass' # 'CenterOfBoundBox', 'XminYmin', 'Custom' + obj.AreaParams = '' + obj.GapSizes = 'No gaps identified.' + obj.StepOver = 100 + obj.CutPatternAngle = 0.0 + obj.CutterTilt = 0.0 + obj.StartIndex = 0.0 + obj.StopIndex = 360.0 + obj.SampleInterval.Value = 1.0 + obj.BoundaryAdjustment.Value = 0.0 + obj.InternalFeaturesAdjustment.Value = 0.0 + obj.AvoidLastX_Faces = 0 + obj.CircularCenterCustom.x = 0.0 + obj.CircularCenterCustom.y = 0.0 + obj.CircularCenterCustom.z = 0.0 + obj.GapThreshold.Value = 0.005 + # For debugging + obj.ShowTempObjects = False + + # need to overwrite the default depth calculations for facing + d = None + if job: + if job.Stock: + d = PathUtils.guessDepths(job.Stock.Shape, None) + PathLog.debug("job.Stock exists") + else: + PathLog.debug("job.Stock NOT exist") + else: + PathLog.debug("job NOT exist") + + if d is not None: + obj.OpFinalDepth.Value = d.final_depth + obj.OpStartDepth.Value = d.start_depth + else: + obj.OpFinalDepth.Value = -10 + obj.OpStartDepth.Value = 10 + + PathLog.debug('Default OpFinalDepth: {}'.format(obj.OpFinalDepth.Value)) + PathLog.debug('Defualt OpStartDepth: {}'.format(obj.OpStartDepth.Value)) + + def opApplyPropertyLimits(self, obj): + '''opApplyPropertyLimits(obj) ... Apply necessary limits to user input property values before performing main operation.''' + # Limit start index + if obj.StartIndex < 0.0: + obj.StartIndex = 0.0 + if obj.StartIndex > 360.0: + obj.StartIndex = 360.0 + + # Limit stop index + if obj.StopIndex > 360.0: + obj.StopIndex = 360.0 + if obj.StopIndex < 0.0: + obj.StopIndex = 0.0 + + # Limit cutter tilt + if obj.CutterTilt < -90.0: + obj.CutterTilt = -90.0 + if obj.CutterTilt > 90.0: + obj.CutterTilt = 90.0 + + # Limit sample interval + if obj.SampleInterval.Value < 0.001: + obj.SampleInterval.Value = 0.001 + PathLog.error(translate('PathSurface', 'Sample interval limits are 0.001 to 25.4 millimeters.')) + if obj.SampleInterval.Value > 25.4: + obj.SampleInterval.Value = 25.4 + PathLog.error(translate('PathSurface', 'Sample interval limits are 0.001 to 25.4 millimeters.')) + + # Limit cut pattern angle + if obj.CutPatternAngle < -360.0: + obj.CutPatternAngle = 0.0 + PathLog.error(translate('PathSurface', 'Cut pattern angle limits are +-360 degrees.')) + if obj.CutPatternAngle >= 360.0: + obj.CutPatternAngle = 0.0 + PathLog.error(translate('PathSurface', 'Cut pattern angle limits are +- 360 degrees.')) + + # Limit StepOver to natural number percentage + if obj.StepOver > 100: + obj.StepOver = 100 + if obj.StepOver < 1: + obj.StepOver = 1 + + # Limit AvoidLastX_Faces to zero and positive values + if obj.AvoidLastX_Faces < 0: + obj.AvoidLastX_Faces = 0 + PathLog.error(translate('PathSurface', 'AvoidLastX_Faces: Only zero or positive values permitted.')) + if obj.AvoidLastX_Faces > 100: + obj.AvoidLastX_Faces = 100 + PathLog.error(translate('PathSurface', 'AvoidLastX_Faces: Avoid last X faces count limited to 100.')) + + def opExecute(self, obj): + '''opExecute(obj) ... process surface operation''' + PathLog.track() + + self.modelSTLs = list() + self.safeSTLs = list() + self.modelTypes = list() + self.boundBoxes = list() + self.profileShapes = list() + self.collectiveShapes = list() + self.individualShapes = list() + self.avoidShapes = list() + self.deflection = None + self.tempGroup = None + self.CutClimb = False + self.closedGap = False + self.gaps = [0.1, 0.2, 0.3] + CMDS = list() + modelVisibility = list() + FCAD = FreeCAD.ActiveDocument + + # Set debugging behavior + self.showDebugObjects = False # Set to true if you want a visual DocObjects created for some path construction objects + self.showDebugObjects = obj.ShowTempObjects + deleteTempsFlag = True # Set to False for debugging + if PathLog.getLevel(PathLog.thisModule()) == 4: + deleteTempsFlag = False + else: + self.showDebugObjects = False + + # mark beginning of operation and identify parent Job + PathLog.info('\nBegin 3D Surface operation...') + startTime = time.time() + + # Disable(ignore) ReleaseFromWaste option(input) + obj.ReleaseFromWaste = False + + # Identify parent Job + JOB = PathUtils.findParentJob(obj) + if JOB is None: + PathLog.error(translate('PathSurface', "No JOB")) + return + self.stockZMin = JOB.Stock.Shape.BoundBox.ZMin + + # set cut mode; reverse as needed + if obj.CutMode == 'Climb': + self.CutClimb = True + if obj.CutPatternReversed is True: + if self.CutClimb is True: + self.CutClimb = False + else: + self.CutClimb = True + + # Begin GCode for operation with basic information + # ... and move cutter to clearance height and startpoint + output = '' + if obj.Comment != '': + output += '(' + str(obj.Comment) + ')\n' + output += '(' + obj.Label + ')\n' + output += '(Tool type: ' + str(obj.ToolController.Tool.ToolType) + ')\n' + output += '(Compensated Tool Path. Diameter: ' + str(obj.ToolController.Tool.Diameter) + ')\n' + output += '(Sample interval: ' + str(obj.SampleInterval.Value) + ')\n' + output += '(Step over %: ' + str(obj.StepOver) + ')\n' + self.commandlist.append(Path.Command('N ({})'.format(output), {})) + self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) + if obj.UseStartPoint is True: + self.commandlist.append(Path.Command('G0', {'X': obj.StartPoint.x, 'Y': obj.StartPoint.y, 'F': self.horizRapid})) + + # Instantiate additional class operation variables + self.resetOpVariables() + + # Impose property limits + self.opApplyPropertyLimits(obj) + + # Create temporary group for temporary objects, removing existing + # if self.showDebugObjects is True: + tempGroupName = 'tempPathSurfaceGroup' + if FCAD.getObject(tempGroupName): + for to in FCAD.getObject(tempGroupName).Group: + FCAD.removeObject(to.Name) + FCAD.removeObject(tempGroupName) # remove temp directory if already exists + if FCAD.getObject(tempGroupName + '001'): + for to in FCAD.getObject(tempGroupName + '001').Group: + FCAD.removeObject(to.Name) + FCAD.removeObject(tempGroupName + '001') # remove temp directory if already exists + tempGroup = FCAD.addObject('App::DocumentObjectGroup', tempGroupName) + tempGroupName = tempGroup.Name + self.tempGroup = tempGroup + tempGroup.purgeTouched() + # Add temp object to temp group folder with following code: + # ... self.tempGroup.addObject(OBJ) + + # Setup cutter for OCL and cutout value for operation - based on tool controller properties + self.cutter = self.setOclCutter(obj) + self.safeCutter = self.setOclCutter(obj, safe=True) + if self.cutter is False or self.safeCutter is False: + PathLog.error(translate('PathSurface', "Canceling 3D Surface operation. Error creating OCL cutter.")) + return + toolDiam = self.cutter.getDiameter() + self.cutOut = (toolDiam * (float(obj.StepOver) / 100.0)) + self.radius = toolDiam / 2.0 + self.gaps = [toolDiam, toolDiam, toolDiam] + + # Get height offset values for later use + self.SafeHeightOffset = JOB.SetupSheet.SafeHeightOffset.Value + self.ClearHeightOffset = JOB.SetupSheet.ClearanceHeightOffset.Value + + # Calculate default depthparams for operation + self.depthParams = PathUtils.depth_params(obj.ClearanceHeight.Value, obj.SafeHeight.Value, obj.StartDepth.Value, obj.StepDown.Value, 0.0, obj.FinalDepth.Value) + self.midDep = (obj.StartDepth.Value + obj.FinalDepth.Value) / 2.0 + + # make circle for workplane + self.wpc = Part.makeCircle(2.0) + + # Set deflection values for mesh generation + self.angularDeflection = 0.05 + try: # try/except is for Path Jobs created before GeometryTolerance + self.deflection = JOB.GeometryTolerance.Value + except AttributeError as ee: + PathLog.warning('Error setting Mesh deflection: {}. Using PathPreferences.defaultGeometryTolerance().'.format(ee)) + import PathScripts.PathPreferences as PathPreferences + self.deflection = PathPreferences.defaultGeometryTolerance() + + # Save model visibilities for restoration + if FreeCAD.GuiUp: + for m in range(0, len(JOB.Model.Group)): + mNm = JOB.Model.Group[m].Name + modelVisibility.append(FreeCADGui.ActiveDocument.getObject(mNm).Visibility) + + # Setup STL, model type, and bound box containers for each model in Job + for m in range(0, len(JOB.Model.Group)): + M = JOB.Model.Group[m] + self.modelSTLs.append(False) + self.safeSTLs.append(False) + self.profileShapes.append(False) + # Set bound box + if obj.BoundBox == 'BaseBoundBox': + if M.TypeId.startswith('Mesh'): + self.modelTypes.append('M') # Mesh + self.boundBoxes.append(M.Mesh.BoundBox) + else: + self.modelTypes.append('S') # Solid + self.boundBoxes.append(M.Shape.BoundBox) + elif obj.BoundBox == 'Stock': + self.modelTypes.append('S') # Solid + self.boundBoxes.append(JOB.Stock.Shape.BoundBox) + + # ###### MAIN COMMANDS FOR OPERATION ###### + + # If algorithm is `Waterline`, force certain property values + ''' + # Save initial value for restoration later. + if obj.Algorithm == 'OCL Waterline': + preCP = obj.CutPattern + preCPA = obj.CutPatternAngle + preRB = obj.BoundaryEnforcement + obj.CutPattern = 'Line' + obj.CutPatternAngle = 0.0 + obj.BoundaryEnforcement = False + ''' + + # Begin processing obj.Base data and creating GCode + # Process selected faces, if available + pPM = self._preProcessModel(JOB, obj) + if pPM is False: + PathLog.error('Unable to pre-process obj.Base.') + else: + (FACES, VOIDS) = pPM + + # Create OCL.stl model objects + self._prepareModelSTLs(JOB, obj) + + for m in range(0, len(JOB.Model.Group)): + Mdl = JOB.Model.Group[m] + if FACES[m] is False: + PathLog.error('No data for model base: {}'.format(JOB.Model.Group[m].Label)) + else: + if m > 0: + # Raise to clearance between moddels + CMDS.append(Path.Command('N (Transition to base: {}.)'.format(Mdl.Label))) + CMDS.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) + PathLog.info('Working on Model.Group[{}]: {}'.format(m, Mdl.Label)) + # make stock-model-voidShapes STL model for avoidance detection on transitions + self._makeSafeSTL(JOB, obj, m, FACES[m], VOIDS[m]) + time.sleep(0.2) + # Process model/faces - OCL objects must be ready + CMDS.extend(self._processCutAreas(JOB, obj, m, FACES[m], VOIDS[m])) + + # Save gcode produced + self.commandlist.extend(CMDS) + + # If algorithm is `Waterline`, restore initial property values + ''' + if obj.Algorithm == 'OCL Waterline': + obj.CutPattern = preCP + obj.CutPatternAngle = preCPA + obj.BoundaryEnforcement = preRB + ''' + + # ###### CLOSING COMMANDS FOR OPERATION ###### + + # Delete temporary objects + # Restore model visibilities for restoration + if FreeCAD.GuiUp: + FreeCADGui.ActiveDocument.getObject(tempGroupName).Visibility = False + for m in range(0, len(JOB.Model.Group)): + M = JOB.Model.Group[m] + M.Visibility = modelVisibility[m] + + if deleteTempsFlag is True: + for to in tempGroup.Group: + if hasattr(to, 'Group'): + for go in to.Group: + FCAD.removeObject(go.Name) + FCAD.removeObject(to.Name) + FCAD.removeObject(tempGroupName) + else: + if len(tempGroup.Group) == 0: + FCAD.removeObject(tempGroupName) + else: + tempGroup.purgeTouched() + + # Provide user feedback for gap sizes + gaps = list() + for g in self.gaps: + if g != toolDiam: + gaps.append(g) + if len(gaps) > 0: + obj.GapSizes = '{} mm'.format(gaps) + else: + if self.closedGap is True: + obj.GapSizes = 'Closed gaps < Gap Threshold.' + else: + obj.GapSizes = 'No gaps identified.' + + # clean up class variables + self.resetOpVariables() + self.deleteOpVariables() + + self.modelSTLs = None + self.safeSTLs = None + self.modelTypes = None + self.boundBoxes = None + self.gaps = None + self.closedGap = None + self.SafeHeightOffset = None + self.ClearHeightOffset = None + self.depthParams = None + self.midDep = None + self.wpc = None + self.angularDeflection = None + self.deflection = None + del self.modelSTLs + del self.safeSTLs + del self.modelTypes + del self.boundBoxes + del self.gaps + del self.closedGap + del self.SafeHeightOffset + del self.ClearHeightOffset + del self.depthParams + del self.midDep + del self.wpc + del self.angularDeflection + del self.deflection + + execTime = time.time() - startTime + PathLog.info('Operation time: {} sec.'.format(execTime)) + + return True + + # Methods for constructing the cut area + def _preProcessModel(self, JOB, obj): + PathLog.debug('_preProcessModel()') + + FACES = list() + VOIDS = list() + fShapes = list() + vShapes = list() + preProcEr = translate('PathSurface', 'Error pre-processing Face') + warnFinDep = translate('PathSurface', 'Final Depth might need to be lower. Internal features detected in Face') + GRP = JOB.Model.Group + lenGRP = len(GRP) + + # Crete place holders for each base model in Job + for m in range(0, lenGRP): + FACES.append(False) + VOIDS.append(False) + fShapes.append(False) + vShapes.append(False) + + # The user has selected subobjects from the base. Pre-Process each. + if obj.Base and len(obj.Base) > 0: + PathLog.debug(' -obj.Base exists. Pre-processing for selected faces.') + + (FACES, VOIDS) = self._identifyFacesAndVoids(JOB, obj, FACES, VOIDS) + + # Cycle through each base model, processing faces for each + for m in range(0, lenGRP): + base = GRP[m] + (mFS, mVS, mPS) = self._preProcessFacesAndVoids(obj, base, m, FACES, VOIDS) + fShapes[m] = mFS + vShapes[m] = mVS + self.profileShapes[m] = mPS + else: + PathLog.debug(' -No obj.Base data.') + for m in range(0, lenGRP): + self.modelSTLs[m] = True + + # Process each model base, as a whole, as needed + # PathLog.debug(' -Pre-processing all models in Job.') + for m in range(0, lenGRP): + if fShapes[m] is False: + PathLog.debug(' -Pre-processing {} as a whole.'.format(GRP[m].Label)) + if obj.BoundBox == 'BaseBoundBox': + base = GRP[m] + elif obj.BoundBox == 'Stock': + base = JOB.Stock + + pPEB = self._preProcessEntireBase(obj, base, m) + if pPEB is False: + PathLog.error(' -Failed to pre-process base as a whole.') + else: + (fcShp, prflShp) = pPEB + if fcShp is not False: + if fcShp is True: + PathLog.debug(' -fcShp is True.') + fShapes[m] = True + else: + fShapes[m] = [fcShp] + if prflShp is not False: + if fcShp is not False: + PathLog.debug('vShapes[{}]: {}'.format(m, vShapes[m])) + if vShapes[m] is not False: + PathLog.debug(' -Cutting void from base profile shape.') + adjPS = prflShp.cut(vShapes[m][0]) + self.profileShapes[m] = [adjPS] + else: + PathLog.debug(' -vShapes[m] is False.') + self.profileShapes[m] = [prflShp] + else: + PathLog.debug(' -Saving base profile shape.') + self.profileShapes[m] = [prflShp] + PathLog.debug('self.profileShapes[{}]: {}'.format(m, self.profileShapes[m])) + # Efor + + return (fShapes, vShapes) + + def _identifyFacesAndVoids(self, JOB, obj, F, V): + TUPS = list() + GRP = JOB.Model.Group + lenGRP = len(GRP) + + # Separate selected faces into (base, face) tuples and flag model(s) for STL creation + for (bs, SBS) in obj.Base: + for sb in SBS: + # Flag model for STL creation + mdlIdx = None + for m in range(0, lenGRP): + if bs is GRP[m]: + self.modelSTLs[m] = True + mdlIdx = m + break + TUPS.append((mdlIdx, bs, sb)) # (model idx, base, sub) + + # Apply `AvoidXFaces` value + faceCnt = len(TUPS) + add = faceCnt - obj.AvoidLastX_Faces + for bst in range(0, faceCnt): + (m, base, sub) = TUPS[bst] + shape = getattr(base.Shape, sub) + if isinstance(shape, Part.Face): + faceIdx = int(sub[4:]) - 1 + if bst < add: + if F[m] is False: + F[m] = list() + F[m].append((shape, faceIdx)) + else: + if V[m] is False: + V[m] = list() + V[m].append((shape, faceIdx)) + return (F, V) + + def _preProcessFacesAndVoids(self, obj, base, m, FACES, VOIDS): + mFS = False + mVS = False + mPS = False + mIFS = list() + BB = base.Shape.BoundBox + + if FACES[m] is not False: + isHole = False + if obj.HandleMultipleFeatures == 'Collectively': + cont = True + fsL = list() # face shape list + ifL = list() # avoid shape list + outFCS = list() + + # Get collective envelope slice of selected faces + for (fcshp, fcIdx) in FACES[m]: + fNum = fcIdx + 1 + fsL.append(fcshp) + gFW = self._getFaceWires(base, fcshp, fcIdx) + if gFW is False: + PathLog.debug('Failed to get wires from Face{}'.format(fNum)) + elif gFW[0] is False: + PathLog.debug('Cannot process Face{}. Check that it has horizontal surface exposure.'.format(fNum)) + else: + ((otrFace, raised), intWires) = gFW + outFCS.append(otrFace) + if obj.InternalFeaturesCut is False: + if intWires is not False: + for (iFace, rsd) in intWires: + ifL.append(iFace) + + PathLog.debug('Attempting to get cross-section of collective faces.') + if len(outFCS) == 0: + PathLog.error('Cannot process selected faces. Check horizontal surface exposure.'.format(fNum)) + cont = False + else: + cfsL = Part.makeCompound(outFCS) + + # Handle profile edges request + if cont is True and obj.ProfileEdges != 'None': + ofstVal = self._calculateOffsetValue(obj, isHole) + psOfst = self._extractFaceOffset(obj, cfsL, ofstVal) + if psOfst is not False: + mPS = [psOfst] + if obj.ProfileEdges == 'Only': + mFS = True + cont = False + else: + PathLog.error(' -Failed to create profile geometry for selected faces.') + cont = False + + if cont is True: + if self.showDebugObjects is True: + T = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCollectiveShape') + T.Shape = cfsL + T.purgeTouched() + self.tempGroup.addObject(T) + + ofstVal = self._calculateOffsetValue(obj, isHole) + faceOfstShp = self._extractFaceOffset(obj, cfsL, ofstVal) + if faceOfstShp is False: + PathLog.error(' -Failed to create offset face.') + cont = False + + if cont is True: + lenIfL = len(ifL) + if obj.InternalFeaturesCut is False: + if lenIfL == 0: + PathLog.debug(' -No internal features saved.') + else: + if lenIfL == 1: + casL = ifL[0] + else: + casL = Part.makeCompound(ifL) + if self.showDebugObjects is True: + C = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCompoundIntFeat') + C.Shape = casL + C.purgeTouched() + self.tempGroup.addObject(C) + ofstVal = self._calculateOffsetValue(obj, isHole=True) + intOfstShp = self._extractFaceOffset(obj, casL, ofstVal) + mIFS.append(intOfstShp) + # faceOfstShp = faceOfstShp.cut(intOfstShp) + + mFS = [faceOfstShp] + # Eif + + elif obj.HandleMultipleFeatures == 'Individually': + for (fcshp, fcIdx) in FACES[m]: + cont = True + fsL = list() # face shape list + ifL = list() # avoid shape list + fNum = fcIdx + 1 + outerFace = False + + gFW = self._getFaceWires(base, fcshp, fcIdx) + if gFW is False: + PathLog.debug('Failed to get wires from Face{}'.format(fNum)) + cont = False + elif gFW[0] is False: + PathLog.debug('Cannot process Face{}. Check that it has horizontal surface exposure.'.format(fNum)) + cont = False + outerFace = False + else: + ((otrFace, raised), intWires) = gFW + outerFace = otrFace + if obj.InternalFeaturesCut is False: + if intWires is not False: + for (iFace, rsd) in intWires: + ifL.append(iFace) + + if outerFace is not False: + PathLog.debug('Attempting to create offset face of Face{}'.format(fNum)) + + if obj.ProfileEdges != 'None': + ofstVal = self._calculateOffsetValue(obj, isHole) + psOfst = self._extractFaceOffset(obj, outerFace, ofstVal) + if psOfst is not False: + if mPS is False: + mPS = list() + mPS.append(psOfst) + if obj.ProfileEdges == 'Only': + if mFS is False: + mFS = list() + mFS.append(True) + cont = False + else: + PathLog.error(' -Failed to create profile geometry for Face{}.'.format(fNum)) + cont = False + + if cont is True: + ofstVal = self._calculateOffsetValue(obj, isHole) + faceOfstShp = self._extractFaceOffset(obj, slc, ofstVal) + + lenIfl = len(ifL) + if obj.InternalFeaturesCut is False and lenIfl > 0: + if lenIfl == 1: + casL = ifL[0] + else: + casL = Part.makeCompound(ifL) + + ofstVal = self._calculateOffsetValue(obj, isHole=True) + intOfstShp = self._extractFaceOffset(obj, casL, ofstVal) + mIFS.append(intOfstShp) + # faceOfstShp = faceOfstShp.cut(intOfstShp) + + if mFS is False: + mFS = list() + mFS.append(faceOfstShp) + # Eif + # Efor + # Eif + # Eif + + if len(mIFS) > 0: + if mVS is False: + mVS = list() + for ifs in mIFS: + mVS.append(ifs) + + if VOIDS[m] is not False: + PathLog.debug('Processing avoid faces.') + cont = True + isHole = False + outFCS = list() + intFEAT = list() + + for (fcshp, fcIdx) in VOIDS[m]: + fNum = fcIdx + 1 + gFW = self._getFaceWires(base, fcshp, fcIdx) + if gFW is False: + PathLog.debug('Failed to get wires from avoid Face{}'.format(fNum)) + cont = False + else: + ((otrFace, raised), intWires) = gFW + outFCS.append(otrFace) + if obj.AvoidLastX_InternalFeatures is False: + if intWires is not False: + for (iFace, rsd) in intWires: + intFEAT.append(iFace) + + lenOtFcs = len(outFCS) + if lenOtFcs == 0: + cont = False + else: + if lenOtFcs == 1: + avoid = outFCS[0] + else: + avoid = Part.makeCompound(outFCS) + + if self.showDebugObjects is True: + PathLog.debug('*** tmpAvoidArea') + P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpVoidEnvelope') + P.Shape = avoid + # P.recompute() + P.purgeTouched() + self.tempGroup.addObject(P) + + if cont is True: + if self.showDebugObjects is True: + PathLog.debug('*** tmpVoidCompound') + P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpVoidCompound') + P.Shape = avoid + # P.recompute() + P.purgeTouched() + self.tempGroup.addObject(P) + ofstVal = self._calculateOffsetValue(obj, isHole, isVoid=True) + avdOfstShp = self._extractFaceOffset(obj, avoid, ofstVal) + if avdOfstShp is False: + PathLog.error('Failed to create collective offset avoid face.') + cont = False + + if cont is True: + avdShp = avdOfstShp + + if obj.AvoidLastX_InternalFeatures is False and len(intFEAT) > 0: + if len(intFEAT) > 1: + ifc = Part.makeCompound(intFEAT) + else: + ifc = intFEAT[0] + ofstVal = self._calculateOffsetValue(obj, isHole=True) + ifOfstShp = self._extractFaceOffset(obj, ifc, ofstVal) + if ifOfstShp is False: + PathLog.error('Failed to create collective offset avoid internal features.') + else: + avdShp = avdOfstShp.cut(ifOfstShp) + + if mVS is False: + mVS = list() + mVS.append(avdShp) + + + return (mFS, mVS, mPS) + + def _getFaceWires(self, base, fcshp, fcIdx): + outFace = False + INTFCS = list() + fNum = fcIdx + 1 + # preProcEr = translate('PathSurface', 'Error pre-processing Face') + warnFinDep = translate('PathSurface', 'Final Depth might need to be lower. Internal features detected in Face') + + PathLog.debug('_getFaceWires() from Face{}'.format(fNum)) + WIRES = self._extractWiresFromFace(base, fcshp) + if WIRES is False: + PathLog.error('Failed to extract wires from Face{}'.format(fNum)) + return False + + # Process remaining internal features, adding to FCS list + lenW = len(WIRES) + for w in range(0, lenW): + (wire, rsd) = WIRES[w] + PathLog.debug('Processing Wire{} in Face{}. isRaised: {}'.format(w + 1, fNum, rsd)) + if wire.isClosed() is False: + PathLog.debug(' -wire is not closed.') + else: + slc = self._flattenWireToFace(wire) + if slc is False: + PathLog.error('FAILED to identify horizontal exposure on Face{}.'.format(fNum)) + else: + if w == 0: + outFace = (slc, rsd) + else: + # add to VOIDS so cutter avoids area. + PathLog.warning(warnFinDep + str(fNum) + '.') + INTFCS.append((slc, rsd)) + if len(INTFCS) == 0: + return (outFace, False) + else: + return (outFace, INTFCS) + + def _preProcessEntireBase(self, obj, base, m): + cont = True + isHole = False + prflShp = False + # Create envelope, extract cross-section and make offset co-planar shape + # baseEnv = PathUtils.getEnvelope(base.Shape, subshape=None, depthparams=self.depthParams) + + try: + baseEnv = PathUtils.getEnvelope(partshape=base.Shape, subshape=None, depthparams=self.depthParams) # Produces .Shape + except Exception as ee: + PathLog.error(str(ee)) + shell = base.Shape.Shells[0] + solid = Part.makeSolid(shell) + try: + baseEnv = PathUtils.getEnvelope(partshape=solid, subshape=None, depthparams=self.depthParams) # Produces .Shape + except Exception as eee: + PathLog.error(str(eee)) + cont = False + time.sleep(0.2) + + if cont is True: + csFaceShape = self._getShapeSlice(baseEnv) + if csFaceShape is False: + PathLog.debug('_getShapeSlice(baseEnv) failed') + csFaceShape = self._getCrossSection(baseEnv) + if csFaceShape is False: + PathLog.debug('_getCrossSection(baseEnv) failed') + csFaceShape = self._getSliceFromEnvelope(baseEnv) + if csFaceShape is False: + PathLog.error('Failed to slice baseEnv shape.') + cont = False + + if cont is True and obj.ProfileEdges != 'None': + PathLog.debug(' -Attempting profile geometry for model base.') + ofstVal = self._calculateOffsetValue(obj, isHole) + psOfst = self._extractFaceOffset(obj, csFaceShape, ofstVal) + if psOfst is not False: + if obj.ProfileEdges == 'Only': + return (True, psOfst) + prflShp = psOfst + else: + PathLog.error(' -Failed to create profile geometry.') + cont = False + + if cont is True: + ofstVal = self._calculateOffsetValue(obj, isHole) + faceOffsetShape = self._extractFaceOffset(obj, csFaceShape, ofstVal) + if faceOffsetShape is False: + PathLog.error('_extractFaceOffset() failed.') + else: + faceOffsetShape.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - faceOffsetShape.BoundBox.ZMin)) + return (faceOffsetShape, prflShp) + return False + + def _extractWiresFromFace(self, base, fc): + '''_extractWiresFromFace(base, fc) ... + Attempts to return all closed wires within a parent face, including the outer most wire of the parent. + The wires are ordered by area. Each wire is also categorized as a pocket(False) or raised protrusion(True). + ''' + PathLog.debug('_extractWiresFromFace()') + + WIRES = list() + lenWrs = len(fc.Wires) + PathLog.debug(' -Wire count: {}'.format(lenWrs)) + + def index0(tup): + return tup[0] + + # Cycle through wires in face + for w in range(0, lenWrs): + PathLog.debug(' -Analyzing wire_{}'.format(w + 1)) + wire = fc.Wires[w] + checkEdges = False + cont = True + + # Check for closed edges (circles, ellipses, etc...) + for E in wire.Edges: + if E.isClosed() is True: + checkEdges = True + break + + if checkEdges is True: + PathLog.debug(' -checkEdges is True') + for e in range(0, len(wire.Edges)): + edge = wire.Edges[e] + if edge.isClosed() is True and edge.Mass > 0.01: + PathLog.debug(' -Found closed edge') + raised = False + ip = self._isPocket(base, fc, edge) + if ip is False: + raised = True + ebb = edge.BoundBox + eArea = ebb.XLength * ebb.YLength + F = Part.Face(Part.Wire([edge])) + WIRES.append((eArea, F.Wires[0], raised)) + cont = False + + if cont is True: + PathLog.debug(' -cont is True') + # If only one wire and not checkEdges, return first wire + if lenWrs == 1: + return [(wire, False)] + + raised = False + wbb = wire.BoundBox + wArea = wbb.XLength * wbb.YLength + if w > 0: + ip = self._isPocket(base, fc, wire) + if ip is False: + raised = True + WIRES.append((wArea, Part.Wire(wire.Edges), raised)) + + nf = len(WIRES) + if nf > 0: + PathLog.debug(' -number of wires found is {}'.format(nf)) + if nf == 1: + (area, W, raised) = WIRES[0] + return [(W, raised)] + else: + sortedWIRES = sorted(WIRES, key=index0, reverse=True) + return [(W, raised) for (area, W, raised) in sortedWIRES] # outer, then inner by area size + + return False + + def _calculateOffsetValue(self, obj, isHole, isVoid=False): + '''_calculateOffsetValue(obj, isHole, isVoid) ... internal function. + Calculate the offset for the Path.Area() function.''' + JOB = PathUtils.findParentJob(obj) + tolrnc = JOB.GeometryTolerance.Value + + if isVoid is False: + if isHole is True: + offset = -1 * obj.InternalFeaturesAdjustment.Value + offset += self.radius # (self.radius + (tolrnc / 10.0)) + else: + offset = -1 * obj.BoundaryAdjustment.Value + if obj.BoundaryEnforcement is True: + offset += self.radius # (self.radius + (tolrnc / 10.0)) + else: + offset -= self.radius # (self.radius + (tolrnc / 10.0)) + offset = 0.0 - offset + else: + offset = -1 * obj.BoundaryAdjustment.Value + offset += self.radius # (self.radius + (tolrnc / 10.0)) + + return offset + + def _extractFaceOffset(self, obj, fcShape, offset): + '''_extractFaceOffset(fcShape, offset) ... internal function. + Original _buildPathArea() version copied from PathAreaOp.py module. This version is modified. + Adjustments made based on notes by @sliptonic at this webpage: https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.''' + PathLog.debug('_extractFaceOffset()') + + if fcShape.BoundBox.ZMin != 0.0: + fcShape.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - fcShape.BoundBox.ZMin)) + + areaParams = {} + areaParams['Offset'] = offset + areaParams['Fill'] = 1 + 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 + + area = Path.Area() # Create instance of Area() class object + # area.setPlane(PathUtils.makeWorkplane(fcShape)) # Set working plane + area.setPlane(PathUtils.makeWorkplane(self.wpc)) # Set working plane to normal at Z=1 + area.add(fcShape) + area.setParams(**areaParams) # set parameters + + # Save parameters for debugging + # obj.AreaParams = str(area.getParams()) + # PathLog.debug("Area with params: {}".format(area.getParams())) + + offsetShape = area.getShape() + wCnt = len(offsetShape.Wires) + if wCnt == 0: + return False + elif wCnt == 1: + ofstFace = Part.Face(offsetShape.Wires[0]) + else: + W = list() + for wr in offsetShape.Wires: + W.append(Part.Face(wr)) + ofstFace = Part.makeCompound(W) + + return ofstFace # offsetShape + + def _isPocket(self, b, f, w): + '''_isPocket(b, f, w)... + Attempts to determing if the wire(w) in face(f) of base(b) is a pocket or raised protrusion. + Returns True if pocket, False if raised protrusion.''' + e = w.Edges[0] + for fi in range(0, len(b.Shape.Faces)): + face = b.Shape.Faces[fi] + for ei in range(0, len(face.Edges)): + edge = face.Edges[ei] + if e.isSame(edge) is True: + if f is face: + # Alternative: run loop to see if all edges are same + pass # same source face, look for another + else: + if face.CenterOfMass.z < f.CenterOfMass.z: + return True + return False + + def _flattenWireToFace(self, wire): + PathLog.debug('_flattenWireToFace()') + if wire.isClosed() is False: + PathLog.debug(' -wire.isClosed() is False') + return False + + # If wire is planar horizontal, convert to a face and return + if wire.BoundBox.ZLength == 0.0: + slc = Part.Face(wire) + return slc + + # Attempt to create a new wire for manipulation, if not, use original + newWire = Part.Wire(wire.Edges) + if newWire.isClosed() is True: + nWire = newWire + else: + PathLog.debug(' -newWire.isClosed() is False') + nWire = wire + + # Attempt extrusion, and then try a manual slice and then cross-section + ext = self._getExtrudedShape(nWire) + if ext is False: + PathLog.debug('_getExtrudedShape() failed') + else: + slc = self._getShapeSlice(ext) + if slc is not False: + return slc + cs = self._getCrossSection(ext, True) + if cs is not False: + return cs + + # Attempt creating an envelope, and then try a manual slice and then cross-section + env = self._getShapeEnvelope(nWire) + if env is False: + PathLog.debug('_getShapeEnvelope() failed') + else: + slc = self._getShapeSlice(env) + if slc is not False: + return slc + cs = self._getCrossSection(env, True) + if cs is not False: + return cs + + # Attempt creating a projection + slc = self._getProjectedFace(nWire) + if slc is False: + PathLog.debug('_getProjectedFace() failed') + else: + return slc + + return False + + def _getExtrudedShape(self, wire): + PathLog.debug('_getExtrudedShape()') + wBB = wire.BoundBox + extFwd = math.floor(2.0 * wBB.ZLength) + 10.0 + + try: + # slower, but renders collective faces correctly. Method 5 in TESTING + shell = wire.extrude(FreeCAD.Vector(0.0, 0.0, extFwd)) + except Exception as ee: + PathLog.error(' -extrude wire failed: \n{}'.format(ee)) + return False + + SHP = Part.makeSolid(shell) + return SHP + + def _getShapeSlice(self, shape): + PathLog.debug('_getShapeSlice()') + + bb = shape.BoundBox + mid = (bb.ZMin + bb.ZMax) / 2.0 + xmin = bb.XMin - 1.0 + xmax = bb.XMax + 1.0 + ymin = bb.YMin - 1.0 + ymax = bb.YMax + 1.0 + p1 = FreeCAD.Vector(xmin, ymin, mid) + p2 = FreeCAD.Vector(xmax, ymin, mid) + p3 = FreeCAD.Vector(xmax, ymax, mid) + p4 = FreeCAD.Vector(xmin, ymax, mid) + + e1 = Part.makeLine(p1, p2) + e2 = Part.makeLine(p2, p3) + e3 = Part.makeLine(p3, p4) + e4 = Part.makeLine(p4, p1) + face = Part.Face(Part.Wire([e1, e2, e3, e4])) + fArea = face.BoundBox.XLength * face.BoundBox.YLength # face.Wires[0].Area + sArea = shape.BoundBox.XLength * shape.BoundBox.YLength + midArea = (fArea + sArea) / 2.0 + + slcShp = shape.common(face) + slcArea = slcShp.BoundBox.XLength * slcShp.BoundBox.YLength + + if slcArea < midArea: + for W in slcShp.Wires: + if W.isClosed() is False: + PathLog.debug(' -wire.isClosed() is False') + return False + if len(slcShp.Wires) == 1: + wire = slcShp.Wires[0] + slc = Part.Face(wire) + slc.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - slc.BoundBox.ZMin)) + return slc + else: + fL = list() + for W in slcShp.Wires: + slc = Part.Face(W) + slc.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - slc.BoundBox.ZMin)) + fL.append(slc) + comp = Part.makeCompound(fL) + if self.showDebugObjects is True: + PathLog.debug('*** tmpSliceCompound') + P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpSliceCompound') + P.Shape = comp + # P.recompute() + P.purgeTouched() + self.tempGroup.addObject(P) + return comp + + PathLog.debug(' -slcArea !< midArea') + PathLog.debug(' -slcShp.Edges count: {}. Might be a vertically oriented face.'.format(len(slcShp.Edges))) + return False + + def _getProjectedFace(self, wire): + PathLog.debug('_getProjectedFace()') + F = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpProjectionWire') + F.Shape = wire + F.purgeTouched() + self.tempGroup.addObject(F) + try: + prj = Draft.makeShape2DView(F, FreeCAD.Vector(0, 0, 1)) + prj.recompute() + prj.purgeTouched() + self.tempGroup.addObject(prj) + except Exception as ee: + PathLog.error(str(ee)) + return False + else: + pWire = Part.Wire(prj.Shape.Edges) + if pWire.isClosed() is False: + # PathLog.debug(' -pWire.isClosed() is False') + return False + slc = Part.Face(pWire) + slc.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - slc.BoundBox.ZMin)) + return slc + return False + + def _getCrossSection(self, shape, withExtrude=False): + PathLog.debug('_getCrossSection()') + wires = list() + bb = shape.BoundBox + mid = (bb.ZMin + bb.ZMax) / 2.0 + + for i in shape.slice(FreeCAD.Vector(0, 0, 1), mid): + wires.append(i) + + if len(wires) > 0: + comp = Part.Compound(wires) # produces correct cross-section wire ! + comp.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - comp.BoundBox.ZMin)) + csWire = comp.Wires[0] + if csWire.isClosed() is False: + PathLog.debug(' -comp.Wires[0] is not closed') + return False + if withExtrude is True: + ext = self._getExtrudedShape(csWire) + CS = self._getShapeSlice(ext) + else: + CS = Part.Face(csWire) + CS.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - CS.BoundBox.ZMin)) + return CS + else: + PathLog.debug(' -No wires from .slice() method') + + return False + + def _getShapeEnvelope(self, shape): + PathLog.debug('_getShapeEnvelope()') + + wBB = shape.BoundBox + extFwd = wBB.ZLength + 10.0 + minz = wBB.ZMin + maxz = wBB.ZMin + extFwd + stpDwn = (maxz - minz) / 4.0 + dep_par = PathUtils.depth_params(maxz + 5.0, maxz + 3.0, maxz, stpDwn, 0.0, minz) + + try: + env = PathUtils.getEnvelope(partshape=shape, depthparams=dep_par) # Produces .Shape + except Exception as ee: + PathLog.error('try: PathUtils.getEnvelope() failed.\n' + str(ee)) + return False + else: + return env + + return False + + def _getSliceFromEnvelope(self, env): + PathLog.debug('_getSliceFromEnvelope()') + eBB = env.BoundBox + extFwd = eBB.ZLength + 10.0 + maxz = eBB.ZMin + extFwd + + maxMax = env.Edges[0].BoundBox.ZMin + emax = math.floor(maxz - 1.0) + E = list() + for e in range(0, len(env.Edges)): + emin = env.Edges[e].BoundBox.ZMin + if emin > emax: + E.append(env.Edges[e]) + tf = Part.Face(Part.Wire(Part.__sortEdges__(E))) + tf.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - tf.BoundBox.ZMin)) + + return tf + + def _prepareModelSTLs(self, JOB, obj): + PathLog.debug('_prepareModelSTLs()') + for m in range(0, len(JOB.Model.Group)): + M = JOB.Model.Group[m] + + # PathLog.debug(f" -self.modelTypes[{m}] == 'M'") + if self.modelTypes[m] == 'M': + mesh = M.Mesh + else: + # base.Shape.tessellate(0.05) # 0.5 original value + # mesh = MeshPart.meshFromShape(base.Shape, Deflection=self.deflection) + mesh = MeshPart.meshFromShape(Shape=M.Shape, LinearDeflection=self.deflection, AngularDeflection=self.angularDeflection, Relative=False) + + if self.modelSTLs[m] is True: + stl = ocl.STLSurf() + + for f in mesh.Facets: + p = f.Points[0] + q = f.Points[1] + r = f.Points[2] + t = ocl.Triangle(ocl.Point(p[0], p[1], p[2] + obj.DepthOffset.Value), + ocl.Point(q[0], q[1], q[2] + obj.DepthOffset.Value), + ocl.Point(r[0], r[1], r[2] + obj.DepthOffset.Value)) + stl.addTriangle(t) + self.modelSTLs[m] = stl + return + + def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes): + '''_makeSafeSTL(JOB, obj, mdlIdx, faceShapes, voidShapes)... + Creates and OCL.stl object with combined data with waste stock, + model, and avoided faces. Travel lines can be checked against this + STL object to determine minimum travel height to clear stock and model.''' + PathLog.debug('_makeSafeSTL()') + + fuseShapes = list() + Mdl = JOB.Model.Group[mdlIdx] + FCAD = FreeCAD.ActiveDocument + mBB = Mdl.Shape.BoundBox + sBB = JOB.Stock.Shape.BoundBox + + # add Model shape to safeSTL shape + fuseShapes.append(Mdl.Shape) + + if obj.BoundBox == 'BaseBoundBox': + cont = False + extFwd = (sBB.ZLength) + zmin = mBB.ZMin + zmax = mBB.ZMin + extFwd + stpDwn = (zmax - zmin) / 4.0 + dep_par = PathUtils.depth_params(zmax + 5.0, zmax + 3.0, zmax, stpDwn, 0.0, zmin) + + try: + envBB = PathUtils.getEnvelope(partshape=Mdl.Shape, depthparams=dep_par) # Produces .Shape + cont = True + except Exception as ee: + PathLog.error(str(ee)) + shell = Mdl.Shape.Shells[0] + solid = Part.makeSolid(shell) + try: + envBB = PathUtils.getEnvelope(partshape=solid, depthparams=dep_par) # Produces .Shape + cont = True + except Exception as eee: + PathLog.error(str(eee)) + + if cont is True: + stckWst = JOB.Stock.Shape.cut(envBB) + if obj.BoundaryAdjustment > 0.0: + cmpndFS = Part.makeCompound(faceShapes) + baBB = PathUtils.getEnvelope(partshape=cmpndFS, depthparams=self.depthParams) # Produces .Shape + adjStckWst = stckWst.cut(baBB) + else: + adjStckWst = stckWst + fuseShapes.append(adjStckWst) + else: + PathLog.warning('Path transitions might not avoid the model. Verify paths.') + time.sleep(0.3) + + else: + # If boundbox is Job.Stock, add hidden pad under stock as base plate + toolDiam = self.cutter.getDiameter() + zMin = JOB.Stock.Shape.BoundBox.ZMin + xMin = JOB.Stock.Shape.BoundBox.XMin - toolDiam + yMin = JOB.Stock.Shape.BoundBox.YMin - toolDiam + bL = JOB.Stock.Shape.BoundBox.XLength + (2 * toolDiam) + bW = JOB.Stock.Shape.BoundBox.YLength + (2 * toolDiam) + bH = 1.0 + crnr = FreeCAD.Vector(xMin, yMin, zMin - 1.0) + B = Part.makeBox(bL, bW, bH, crnr, FreeCAD.Vector(0, 0, 1)) + fuseShapes.append(B) + + if voidShapes is not False: + voidComp = Part.makeCompound(voidShapes) + voidEnv = PathUtils.getEnvelope(partshape=voidComp, depthparams=self.depthParams) # Produces .Shape + fuseShapes.append(voidEnv) + + f0 = fuseShapes.pop(0) + if len(fuseShapes) > 0: + fused = f0.fuse(fuseShapes) + else: + fused = f0 + + if self.showDebugObjects is True: + T = FreeCAD.ActiveDocument.addObject('Part::Feature', 'safeSTLShape') + T.Shape = fused + T.purgeTouched() + self.tempGroup.addObject(T) + + # Extract mesh from fusion + meshFuse = MeshPart.meshFromShape(Shape=fused, LinearDeflection=(self.deflection / 2.0), AngularDeflection=self.angularDeflection, Relative=False) + time.sleep(0.2) + stl = ocl.STLSurf() + for f in meshFuse.Facets: + p = f.Points[0] + q = f.Points[1] + r = f.Points[2] + t = ocl.Triangle(ocl.Point(p[0], p[1], p[2]), + ocl.Point(q[0], q[1], q[2]), + ocl.Point(r[0], r[1], r[2])) + stl.addTriangle(t) + + self.safeSTLs[mdlIdx] = stl + + def _processCutAreas(self, JOB, obj, mdlIdx, FCS, VDS): + '''_processCutAreas(JOB, obj, mdlIdx, FCS, VDS)... + This method applies any avoided faces or regions to the selected faces. + It then calls the correct scan method depending on the ScanType property.''' + PathLog.debug('_processCutAreas()') + + final = list() + base = JOB.Model.Group[mdlIdx] + + # Process faces Collectively or Individually + if obj.HandleMultipleFeatures == 'Collectively': + if FCS is True: + COMP = False + else: + ADD = Part.makeCompound(FCS) + if VDS is not False: + DEL = Part.makeCompound(VDS) + COMP = ADD.cut(DEL) + else: + COMP = ADD + + final.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) + final.extend(self._waterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline + + elif obj.HandleMultipleFeatures == 'Individually': + for fsi in range(0, len(FCS)): + fShp = FCS[fsi] + # self.deleteOpVariables(all=False) + self.resetOpVariables(all=False) + + if fShp is True: + COMP = False + else: + ADD = Part.makeCompound([fShp]) + if VDS is not False: + DEL = Part.makeCompound(VDS) + COMP = ADD.cut(DEL) + else: + COMP = ADD + + final.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) + final.extend(self._waterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline + COMP = None + # Eif + + return final + + def _planarGetPDC(self, stl, finalDep, SampleInterval, useSafeCutter=False): + pdc = ocl.PathDropCutter() # create a pdc [PathDropCutter] object + pdc.setSTL(stl) # add stl model + if useSafeCutter is True: + pdc.setCutter(self.safeCutter) # add safeCutter + else: + pdc.setCutter(self.cutter) # add cutter + pdc.setZ(finalDep) # set minimumZ (final / target depth value) + pdc.setSampling(SampleInterval) # set sampling size + return pdc + + # Main waterline functions + def _waterlineOp(self, JOB, obj, mdlIdx, subShp=None): + '''_waterlineOp(obj, base) ... Main waterline function to perform waterline extraction from model.''' + commands = [] + + t_begin = time.time() + # JOB = PathUtils.findParentJob(obj) + base = JOB.Model.Group[mdlIdx] + bb = self.boundBoxes[mdlIdx] + stl = self.modelSTLs[mdlIdx] + + # Prepare global holdpoint and layerEndPnt containers + if self.holdPoint is None: + self.holdPoint = ocl.Point(float("inf"), float("inf"), float("inf")) + if self.layerEndPnt is None: + self.layerEndPnt = ocl.Point(float("inf"), float("inf"), float("inf")) + + # Set extra offset to diameter of cutter to allow cutter to move around perimeter of model + toolDiam = self.cutter.getDiameter() + cdeoX = 0.6 * toolDiam + cdeoY = 0.6 * toolDiam + + if subShp is None: + # Get correct boundbox + if obj.BoundBox == 'Stock': + BS = JOB.Stock + bb = BS.Shape.BoundBox + elif obj.BoundBox == 'BaseBoundBox': + BS = base + bb = base.Shape.BoundBox + + env = PathUtils.getEnvelope(partshape=BS.Shape, depthparams=self.depthParams) # Produces .Shape + + xmin = bb.XMin + xmax = bb.XMax + ymin = bb.YMin + ymax = bb.YMax + zmin = bb.ZMin + zmax = bb.ZMax + else: + xmin = subShp.BoundBox.XMin + xmax = subShp.BoundBox.XMax + ymin = subShp.BoundBox.YMin + ymax = subShp.BoundBox.YMax + zmin = subShp.BoundBox.ZMin + zmax = subShp.BoundBox.ZMax + + smplInt = obj.SampleInterval.Value + minSampInt = 0.001 # value is mm + if smplInt < minSampInt: + smplInt = minSampInt + + # Determine bounding box length for the OCL scan + bbLength = math.fabs(ymax - ymin) + numScanLines = int(math.ceil(bbLength / smplInt) + 1) # Number of lines + + # Compute number and size of stepdowns, and final depth + if obj.LayerMode == 'Single-pass': + depthparams = [obj.FinalDepth.Value] + else: + depthparams = [dp for dp in self.depthParams] + lenDP = len(depthparams) + + # Prepare PathDropCutter objects with STL data + safePDC = self._planarGetPDC(self.safeSTLs[mdlIdx], + depthparams[lenDP - 1], obj.SampleInterval.Value, useSafeCutter=False) + + # Scan the piece to depth at smplInt + oclScan = [] + oclScan = self._waterlineDropCutScan(stl, smplInt, xmin, xmax, ymin, depthparams[lenDP - 1], numScanLines) + # oclScan = SCANS + lenOS = len(oclScan) + ptPrLn = int(lenOS / numScanLines) + + # Convert oclScan list of points to multi-dimensional list + scanLines = [] + for L in range(0, numScanLines): + scanLines.append([]) + for P in range(0, ptPrLn): + pi = L * ptPrLn + P + scanLines[L].append(oclScan[pi]) + lenSL = len(scanLines) + pntsPerLine = len(scanLines[0]) + PathLog.debug("--OCL scan: " + str(lenSL * pntsPerLine) + " points, with " + str(numScanLines) + " lines and " + str(pntsPerLine) + " pts/line") + + # Extract Wl layers per depthparams + lyr = 0 + cmds = [] + layTime = time.time() + self.topoMap = [] + for layDep in depthparams: + cmds = self._getWaterline(obj, scanLines, layDep, lyr, lenSL, pntsPerLine) + commands.extend(cmds) + lyr += 1 + PathLog.debug("--All layer scans combined took " + str(time.time() - layTime) + " s") + return commands + + def _waterlineDropCutScan(self, stl, smplInt, xmin, xmax, ymin, fd, numScanLines): + '''_waterlineDropCutScan(stl, smplInt, xmin, xmax, ymin, fd, numScanLines) ... + Perform OCL scan for waterline purpose.''' + pdc = ocl.PathDropCutter() # create a pdc + pdc.setSTL(stl) + pdc.setCutter(self.cutter) + pdc.setZ(fd) # set minimumZ (final / target depth value) + pdc.setSampling(smplInt) + + # Create line object as path + path = ocl.Path() # create an empty path object + for nSL in range(0, numScanLines): + yVal = ymin + (nSL * smplInt) + p1 = ocl.Point(xmin, yVal, fd) # start-point of line + p2 = ocl.Point(xmax, yVal, fd) # end-point of line + path.append(ocl.Line(p1, p2)) + # path.append(l) # add the line to the path + pdc.setPath(path) + pdc.run() # run drop-cutter on the path + + # return the list the points + return pdc.getCLPoints() + + def _getWaterline(self, obj, scanLines, layDep, lyr, lenSL, pntsPerLine): + '''_getWaterline(obj, scanLines, layDep, lyr, lenSL, pntsPerLine) ... Get waterline.''' + commands = [] + cmds = [] + loopList = [] + self.topoMap = [] + # Create topo map from scanLines (highs and lows) + self.topoMap = self._createTopoMap(scanLines, layDep, lenSL, pntsPerLine) + # Add buffer lines and columns to topo map + self._bufferTopoMap(lenSL, pntsPerLine) + # Identify layer waterline from OCL scan + self._highlightWaterline(4, 9) + # Extract waterline and convert to gcode + loopList = self._extractWaterlines(obj, scanLines, lyr, layDep) + # save commands + for loop in loopList: + cmds = self._loopToGcode(obj, layDep, loop) + commands.extend(cmds) + return commands + + def _createTopoMap(self, scanLines, layDep, lenSL, pntsPerLine): + '''_createTopoMap(scanLines, layDep, lenSL, pntsPerLine) ... Create topo map version of OCL scan data.''' + topoMap = [] + for L in range(0, lenSL): + topoMap.append([]) + for P in range(0, pntsPerLine): + if scanLines[L][P].z > layDep: + topoMap[L].append(2) + else: + topoMap[L].append(0) + return topoMap + + def _bufferTopoMap(self, lenSL, pntsPerLine): + '''_bufferTopoMap(lenSL, pntsPerLine) ... Add buffer boarder of zeros to all sides to topoMap data.''' + pre = [0, 0] + post = [0, 0] + for p in range(0, pntsPerLine): + pre.append(0) + post.append(0) + for l in range(0, lenSL): + self.topoMap[l].insert(0, 0) + self.topoMap[l].append(0) + self.topoMap.insert(0, pre) + self.topoMap.append(post) + return True + + def _highlightWaterline(self, extraMaterial, insCorn): + '''_highlightWaterline(extraMaterial, insCorn) ... Highlight the waterline data, separating from extra material.''' + TM = self.topoMap + lastPnt = len(TM[1]) - 1 + lastLn = len(TM) - 1 + highFlag = 0 + + # ("--Convert parallel data to ridges") + for lin in range(1, lastLn): + for pt in range(1, lastPnt): # Ignore first and last points + if TM[lin][pt] == 0: + if TM[lin][pt + 1] == 2: # step up + TM[lin][pt] = 1 + if TM[lin][pt - 1] == 2: # step down + TM[lin][pt] = 1 + + # ("--Convert perpendicular data to ridges and highlight ridges") + for pt in range(1, lastPnt): # Ignore first and last points + for lin in range(1, lastLn): + if TM[lin][pt] == 0: + highFlag = 0 + if TM[lin + 1][pt] == 2: # step up + TM[lin][pt] = 1 + if TM[lin - 1][pt] == 2: # step down + TM[lin][pt] = 1 + elif TM[lin][pt] == 2: + highFlag += 1 + if highFlag == 3: + if TM[lin - 1][pt - 1] < 2 or TM[lin - 1][pt + 1] < 2: + highFlag = 2 + else: + TM[lin - 1][pt] = extraMaterial + highFlag = 2 + + # ("--Square corners") + for pt in range(1, lastPnt): + for lin in range(1, lastLn): + if TM[lin][pt] == 1: # point == 1 + cont = True + if TM[lin + 1][pt] == 0: # forward == 0 + if TM[lin + 1][pt - 1] == 1: # forward left == 1 + if TM[lin][pt - 1] == 2: # left == 2 + TM[lin + 1][pt] = 1 # square the corner + cont = False + + if cont is True and TM[lin + 1][pt + 1] == 1: # forward right == 1 + if TM[lin][pt + 1] == 2: # right == 2 + TM[lin + 1][pt] = 1 # square the corner + cont = True + + if TM[lin - 1][pt] == 0: # back == 0 + if TM[lin - 1][pt - 1] == 1: # back left == 1 + if TM[lin][pt - 1] == 2: # left == 2 + TM[lin - 1][pt] = 1 # square the corner + cont = False + + if cont is True and TM[lin - 1][pt + 1] == 1: # back right == 1 + if TM[lin][pt + 1] == 2: # right == 2 + TM[lin - 1][pt] = 1 # square the corner + + # remove inside corners + for pt in range(1, lastPnt): + for lin in range(1, lastLn): + if TM[lin][pt] == 1: # point == 1 + if TM[lin][pt + 1] == 1: + if TM[lin - 1][pt + 1] == 1 or TM[lin + 1][pt + 1] == 1: + TM[lin][pt + 1] = insCorn + elif TM[lin][pt - 1] == 1: + if TM[lin - 1][pt - 1] == 1 or TM[lin + 1][pt - 1] == 1: + TM[lin][pt - 1] = insCorn + + return True + + def _extractWaterlines(self, obj, oclScan, lyr, layDep): + '''_extractWaterlines(obj, oclScan, lyr, layDep) ... Extract water lines from OCL scan data.''' + srch = True + lastPnt = len(self.topoMap[0]) - 1 + lastLn = len(self.topoMap) - 1 + maxSrchs = 5 + srchCnt = 1 + loopList = [] + loop = [] + loopNum = 0 + + if self.CutClimb is True: + lC = [-1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0] + pC = [-1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1] + else: + lC = [1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0] + pC = [-1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1] + + while srch is True: + srch = False + if srchCnt > maxSrchs: + PathLog.debug("Max search scans, " + str(maxSrchs) + " reached\nPossible incomplete waterline result!") + break + for L in range(1, lastLn): + for P in range(1, lastPnt): + if self.topoMap[L][P] == 1: + # start loop follow + srch = True + loopNum += 1 + loop = self._trackLoop(oclScan, lC, pC, L, P, loopNum) + self.topoMap[L][P] = 0 # Mute the starting point + loopList.append(loop) + srchCnt += 1 + PathLog.debug("Search count for layer " + str(lyr) + " is " + str(srchCnt) + ", with " + str(loopNum) + " loops.") + return loopList + + def _trackLoop(self, oclScan, lC, pC, L, P, loopNum): + '''_trackLoop(oclScan, lC, pC, L, P, loopNum) ... Track the loop direction.''' + loop = [oclScan[L - 1][P - 1]] # Start loop point list + cur = [L, P, 1] + prv = [L, P - 1, 1] + nxt = [L, P + 1, 1] + follow = True + ptc = 0 + ptLmt = 200000 + while follow is True: + ptc += 1 + if ptc > ptLmt: + PathLog.debug("Loop number " + str(loopNum) + " at [" + str(nxt[0]) + ", " + str(nxt[1]) + "] pnt count exceeds, " + str(ptLmt) + ". Stopped following loop.") + break + nxt = self._findNextWlPoint(lC, pC, cur[0], cur[1], prv[0], prv[1]) # get next point + loop.append(oclScan[nxt[0] - 1][nxt[1] - 1]) # add it to loop point list + self.topoMap[nxt[0]][nxt[1]] = nxt[2] # Mute the point, if not Y stem + if nxt[0] == L and nxt[1] == P: # check if loop complete + follow = False + elif nxt[0] == cur[0] and nxt[1] == cur[1]: # check if line cannot be detected + follow = False + prv = cur + cur = nxt + return loop + + def _findNextWlPoint(self, lC, pC, cl, cp, pl, pp): + '''_findNextWlPoint(lC, pC, cl, cp, pl, pp) ... + Find the next waterline point in the point cloud layer provided.''' + dl = cl - pl + dp = cp - pp + num = 0 + i = 3 + s = 0 + mtch = 0 + found = False + while mtch < 8: # check all 8 points around current point + if lC[i] == dl: + if pC[i] == dp: + s = i - 3 + found = True + # Check for y branch where current point is connection between branches + for y in range(1, mtch): + if lC[i + y] == dl: + if pC[i + y] == dp: + num = 1 + break + break + i += 1 + mtch += 1 + if found is False: + # ("_findNext: No start point found.") + return [cl, cp, num] + + for r in range(0, 8): + l = cl + lC[s + r] + p = cp + pC[s + r] + if self.topoMap[l][p] == 1: + return [l, p, num] + + # ("_findNext: No next pnt found") + return [cl, cp, num] + + def _loopToGcode(self, obj, layDep, loop): + '''_loopToGcode(obj, layDep, loop) ... Convert set of loop points to Gcode.''' + # generate the path commands + output = [] + optimize = obj.OptimizeLinearPaths + + prev = ocl.Point(float("inf"), float("inf"), float("inf")) + nxt = ocl.Point(float("inf"), float("inf"), float("inf")) + pnt = ocl.Point(float("inf"), float("inf"), float("inf")) + + # Create first point + pnt.x = loop[0].x + pnt.y = loop[0].y + pnt.z = layDep + + # Position cutter to begin loop + output.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) + output.append(Path.Command('G0', {'X': pnt.x, 'Y': pnt.y, 'F': self.horizRapid})) + output.append(Path.Command('G1', {'Z': pnt.z, 'F': self.vertFeed})) + + lenCLP = len(loop) + lastIdx = lenCLP - 1 + # Cycle through each point on loop + for i in range(0, lenCLP): + if i < lastIdx: + nxt.x = loop[i + 1].x + nxt.y = loop[i + 1].y + nxt.z = layDep + else: + optimize = False + + if not optimize or not self.isPointOnLine(FreeCAD.Vector(prev.x, prev.y, prev.z), FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): + output.append(Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'F': self.horizFeed})) + + # Rotate point data + prev.x = pnt.x + prev.y = pnt.y + prev.z = pnt.z + pnt.x = nxt.x + pnt.y = nxt.y + pnt.z = nxt.z + + # Save layer end point for use in transitioning to next layer + self.layerEndPnt.x = pnt.x + self.layerEndPnt.y = pnt.y + self.layerEndPnt.z = pnt.z + + return output + + # Support functions for both dropcutter and waterline operations + def isPointOnLine(self, strtPnt, endPnt, pointP): + '''isPointOnLine(strtPnt, endPnt, pointP) ... Determine if a given point is on the line defined by start and end points.''' + tolerance = 1e-6 + vectorAB = endPnt - strtPnt + vectorAC = pointP - strtPnt + crossproduct = vectorAB.cross(vectorAC) + dotproduct = vectorAB.dot(vectorAC) + + if crossproduct.Length > tolerance: + return False + + if dotproduct < 0: + return False + + if dotproduct > vectorAB.Length * vectorAB.Length: + return False + + return True + + def holdStopCmds(self, obj, zMax, pd, p2, txt): + '''holdStopCmds(obj, zMax, pd, p2, txt) ... Gcode commands to be executed at beginning of hold.''' + cmds = [] + msg = 'N (' + txt + ')' + cmds.append(Path.Command(msg, {})) # Raise cutter rapid to zMax in line of travel + cmds.append(Path.Command('G0', {'Z': zMax, 'F': self.vertRapid})) # Raise cutter rapid to zMax in line of travel + cmds.append(Path.Command('G0', {'X': p2.x, 'Y': p2.y, 'F': self.horizRapid})) # horizontal rapid to current XY coordinate + if zMax != pd: + cmds.append(Path.Command('G0', {'Z': pd, 'F': self.vertRapid})) # drop cutter down rapidly to prevDepth depth + cmds.append(Path.Command('G0', {'Z': p2.z, 'F': self.vertFeed})) # drop cutter down to current Z depth, returning to normal cut path and speed + return cmds + + def holdStopEndCmds(self, obj, p2, txt): + '''holdStopEndCmds(obj, p2, txt) ... Gcode commands to be executed at end of hold stop.''' + cmds = [] + msg = 'N (' + txt + ')' + cmds.append(Path.Command(msg, {})) # Raise cutter rapid to zMax in line of travel + cmds.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) # Raise cutter rapid to zMax in line of travel + # cmds.append(Path.Command('G0', {'X': p2.x, 'Y': p2.y, 'F': self.horizRapid})) # horizontal rapid to current XY coordinate + return cmds + + def subsectionCLP(self, CLP, xmin, ymin, xmax, ymax): + '''subsectionCLP(CLP, xmin, ymin, xmax, ymax) ... + This function returns a subsection of the CLP scan, limited to the min/max values supplied.''' + section = list() + lenCLP = len(CLP) + for i in range(0, lenCLP): + if CLP[i].x < xmax: + if CLP[i].y < ymax: + if CLP[i].x > xmin: + if CLP[i].y > ymin: + section.append(CLP[i]) + return section + + def getMaxHeightBetweenPoints(self, finalDepth, p1, p2, cutter, CLP): + ''' getMaxHeightBetweenPoints(finalDepth, p1, p2, cutter, CLP) ... + This function connects two HOLD points with line. + Each point within the subsection point list is tested to determinie if it is under cutter. + Points determined to be under the cutter on line are tested for z height. + The highest z point is the requirement for clearance between p1 and p2, and returned as zMax with 2 mm extra. + ''' + dx = (p2.x - p1.x) + if dx == 0.0: + dx = 0.00001 # Need to employ a global tolerance here + m = (p2.y - p1.y) / dx + b = p1.y - (m * p1.x) + + avoidTool = round(cutter * 0.75, 1) # 1/2 diam. of cutter is theoretically safe, but 3/4 diam is used for extra clearance + zMax = finalDepth + lenCLP = len(CLP) + for i in range(0, lenCLP): + mSqrd = m**2 + if mSqrd < 0.0000001: # Need to employ a global tolerance here + mSqrd = 0.0000001 + perpDist = math.sqrt((CLP[i].y - (m * CLP[i].x) - b)**2 / (1 + 1 / (mSqrd))) + if perpDist < avoidTool: # if point within cutter reach on line of travel, test z height and update as needed + if CLP[i].z > zMax: + zMax = CLP[i].z + return zMax + 2.0 + + def resetOpVariables(self, all=True): + '''resetOpVariables() ... Reset class variables used for instance of operation.''' + self.holdPoint = None + self.layerEndPnt = None + self.onHold = False + self.SafeHeightOffset = 2.0 + self.ClearHeightOffset = 4.0 + self.layerEndzMax = 0.0 + self.resetTolerance = 0.0 + self.holdPntCnt = 0 + self.bbRadius = 0.0 + self.axialFeed = 0.0 + self.axialRapid = 0.0 + self.FinalDepth = 0.0 + self.clearHeight = 0.0 + self.safeHeight = 0.0 + self.faceZMax = -999999999999.0 + if all is True: + self.cutter = None + self.stl = None + self.fullSTL = None + self.cutOut = 0.0 + self.radius = 0.0 + self.useTiltCutter = False + return True + + def deleteOpVariables(self, all=True): + '''deleteOpVariables() ... Reset class variables used for instance of operation.''' + del self.holdPoint + del self.layerEndPnt + del self.onHold + del self.SafeHeightOffset + del self.ClearHeightOffset + del self.layerEndzMax + del self.resetTolerance + del self.holdPntCnt + del self.bbRadius + del self.axialFeed + del self.axialRapid + del self.FinalDepth + del self.clearHeight + del self.safeHeight + del self.faceZMax + if all is True: + del self.cutter + del self.stl + del self.fullSTL + del self.cutOut + del self.radius + del self.useTiltCutter + return True + + def setOclCutter(self, obj, safe=False): + ''' setOclCutter(obj) ... Translation function to convert FreeCAD tool definition to OCL formatted tool. ''' + # Set cutter details + # https://www.freecadweb.org/api/dd/dfe/classPath_1_1Tool.html#details + diam_1 = float(obj.ToolController.Tool.Diameter) + lenOfst = obj.ToolController.Tool.LengthOffset if hasattr(obj.ToolController.Tool, 'LengthOffset') else 0 + FR = obj.ToolController.Tool.FlatRadius if hasattr(obj.ToolController.Tool, 'FlatRadius') else 0 + CEH = obj.ToolController.Tool.CuttingEdgeHeight if hasattr(obj.ToolController.Tool, 'CuttingEdgeHeight') else 0 + CEA = obj.ToolController.Tool.CuttingEdgeAngle if hasattr(obj.ToolController.Tool, 'CuttingEdgeAngle') else 0 + + # Make safeCutter with 2 mm buffer around physical cutter + if safe is True: + diam_1 += 4.0 + if FR != 0.0: + FR += 2.0 + + PathLog.debug('ToolType: {}'.format(obj.ToolController.Tool.ToolType)) + if obj.ToolController.Tool.ToolType == 'EndMill': + # Standard End Mill + return ocl.CylCutter(diam_1, (CEH + lenOfst)) + + elif obj.ToolController.Tool.ToolType == 'BallEndMill' and FR == 0.0: + # Standard Ball End Mill + # OCL -> BallCutter::BallCutter(diameter, length) + self.useTiltCutter = True + return ocl.BallCutter(diam_1, (diam_1 / 2 + lenOfst)) + + elif obj.ToolController.Tool.ToolType == 'BallEndMill' and FR > 0.0: + # Bull Nose or Corner Radius cutter + # Reference: https://www.fine-tools.com/halbstabfraeser.html + # OCL -> BallCutter::BallCutter(diameter, length) + return ocl.BullCutter(diam_1, FR, (CEH + lenOfst)) + + elif obj.ToolController.Tool.ToolType == 'Engraver' and FR > 0.0: + # Bull Nose or Corner Radius cutter + # Reference: https://www.fine-tools.com/halbstabfraeser.html + # OCL -> ConeCutter::ConeCutter(diameter, angle, lengthOffset) + return ocl.ConeCutter(diam_1, (CEA / 2), lenOfst) + + elif obj.ToolController.Tool.ToolType == 'ChamferMill': + # Bull Nose or Corner Radius cutter + # Reference: https://www.fine-tools.com/halbstabfraeser.html + # OCL -> ConeCutter::ConeCutter(diameter, angle, lengthOffset) + return ocl.ConeCutter(diam_1, (CEA / 2), lenOfst) + else: + # Default to standard end mill + PathLog.warning("Defaulting cutter to standard end mill.") + return ocl.CylCutter(diam_1, (CEH + lenOfst)) + + # http://www.carbidecutter.net/products/carbide-burr-cone-shape-sm.html + ''' + # Available FreeCAD cutter types - some still need translation to available OCL cutter classes. + Drill, CenterDrill, CounterSink, CounterBore, FlyCutter, Reamer, Tap, + EndMill, SlotCutter, BallEndMill, ChamferMill, CornerRound, Engraver + ''' + # Adittional problem is with new ToolBit user-defined cutter shapes. + # Some sort of translation/conversion will have to be defined to make compatible with OCL. + PathLog.error('Unable to set OCL cutter.') + return False + + def determineVectDirect(self, pnt, nxt, travVect): + if nxt.x == pnt.x: + travVect.x = 0 + elif nxt.x < pnt.x: + travVect.x = -1 + else: + travVect.x = 1 + + if nxt.y == pnt.y: + travVect.y = 0 + elif nxt.y < pnt.y: + travVect.y = -1 + else: + travVect.y = 1 + return travVect + + def determineLineOfTravel(self, travVect): + if travVect.x == 0 and travVect.y != 0: + lineOfTravel = "Y" + elif travVect.y == 0 and travVect.x != 0: + lineOfTravel = "X" + else: + lineOfTravel = "O" # used for turns + return lineOfTravel + + def _getMinSafeTravelHeight(self, pdc, p1, p2, minDep=None): + A = (p1.x, p1.y) + B = (p2.x, p2.y) + LINE = self._planarDropCutScan(pdc, A, B) + zMax = LINE[0].z + for p in LINE: + if p.z > zMax: + zMax = p.z + if minDep is not None: + if zMax < minDep: + zMax = minDep + return zMax + + +def SetupProperties(): + ''' SetupProperties() ... Return list of properties required for operation.''' + setup = [] + setup.append('AvoidLastX_Faces') + setup.append('AvoidLastX_InternalFeatures') + setup.append('BoundBox') + setup.append('BoundaryAdjustment') + setup.append('CircularCenterAt') + setup.append('CircularCenterCustom') + setup.append('CircularUseG2G3') + setup.append('InternalFeaturesCut') + setup.append('InternalFeaturesAdjustment') + setup.append('CutMode') + setup.append('CutPattern') + setup.append('CutPatternAngle') + setup.append('CutPatternReversed') + setup.append('CutterTilt') + setup.append('DepthOffset') + setup.append('GapSizes') + setup.append('GapThreshold') + setup.append('HandleMultipleFeatures') + setup.append('LayerMode') + setup.append('OptimizeStepOverTransitions') + setup.append('ProfileEdges') + setup.append('BoundaryEnforcement') + setup.append('RotationAxis') + setup.append('SampleInterval') + setup.append('ScanType') + setup.append('StartIndex') + setup.append('StartPoint') + setup.append('StepOver') + setup.append('StopIndex') + setup.append('UseStartPoint') + # For debugging + setup.append('AreaParams') + setup.append('ShowTempObjects') + # Targeted for possible removal + setup.append('IgnoreWaste') + setup.append('IgnoreWasteDepth') + setup.append('ReleaseFromWaste') + return setup + + +def Create(name, obj=None): + '''Create(name) ... Creates and returns a Surface operation.''' + if obj is None: + obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) + obj.Proxy = ObjectSurface(obj, name) + return obj diff --git a/src/Mod/Path/PathScripts/PathWaterlineGui.py b/src/Mod/Path/PathScripts/PathWaterlineGui.py new file mode 100644 index 0000000000..2c9e5d31d1 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathWaterlineGui.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2017 sliptonic * +# * * +# * 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. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import FreeCAD +import FreeCADGui +import PathScripts.PathWaterline as PathWaterline +import PathScripts.PathGui as PathGui +import PathScripts.PathOpGui as PathOpGui + +from PySide import QtCore + +__title__ = "Path Waterline Operation UI" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Waterline operation page controller and command implementation." +__contributors__ = "russ4262 (Russell Johnson)" +__created__ = "2019" +__scriptVersion__ = "3t Usable" +__lastModified__ = "2019-05-18 21:18 CST" + + +class TaskPanelOpPage(PathOpGui.TaskPanelPage): + '''Page controller class for the Waterline operation.''' + + def getForm(self): + '''getForm() ... returns UI''' + return FreeCADGui.PySideUic.loadUi(":/panels/PageOpWaterlineEdit.ui") + + def getFields(self, obj): + '''getFields(obj) ... transfers values from UI to obj's proprties''' + # if obj.StartVertex != self.form.startVertex.value(): + # obj.StartVertex = self.form.startVertex.value() + PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset) + PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) + + if obj.StepOver != self.form.stepOver.value(): + obj.StepOver = self.form.stepOver.value() + + # if obj.Algorithm != str(self.form.algorithmSelect.currentText()): + # obj.Algorithm = str(self.form.algorithmSelect.currentText()) + + if obj.BoundBox != str(self.form.boundBoxSelect.currentText()): + obj.BoundBox = str(self.form.boundBoxSelect.currentText()) + + # if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentText()): + # obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentText()) + + # obj.DropCutterExtraOffset.x = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetX.text()).Value + # obj.DropCutterExtraOffset.y = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetY.text()).Value + + if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked(): + obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked() + + self.updateToolController(obj, self.form.toolController) + + def setFields(self, obj): + '''setFields(obj) ... transfers obj's property values to UI''' + # self.form.startVertex.setValue(obj.StartVertex) + # self.selectInComboBox(obj.Algorithm, self.form.algorithmSelect) + self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect) + # self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect) + + # self.form.boundBoxExtraOffsetX.setText(str(obj.DropCutterExtraOffset.x)) + # self.form.boundBoxExtraOffsetY.setText(str(obj.DropCutterExtraOffset.y)) + # self.form.depthOffset.setText(FreeCAD.Units.Quantity(obj.DepthOffset.Value, FreeCAD.Units.Length).UserString) + self.form.sampleInterval.setText(str(obj.SampleInterval)) + self.form.stepOver.setValue(obj.StepOver) + + if obj.OptimizeLinearPaths: + self.form.optimizeEnabled.setCheckState(QtCore.Qt.Checked) + else: + self.form.optimizeEnabled.setCheckState(QtCore.Qt.Unchecked) + + self.setupToolController(obj, self.form.toolController) + + def getSignalsForUpdate(self, obj): + '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' + signals = [] + # signals.append(self.form.startVertex.editingFinished) + signals.append(self.form.toolController.currentIndexChanged) + # signals.append(self.form.algorithmSelect.currentIndexChanged) + signals.append(self.form.boundBoxSelect.currentIndexChanged) + # signals.append(self.form.dropCutterDirSelect.currentIndexChanged) + # signals.append(self.form.boundBoxExtraOffsetX.editingFinished) + # signals.append(self.form.boundBoxExtraOffsetY.editingFinished) + signals.append(self.form.sampleInterval.editingFinished) + signals.append(self.form.stepOver.editingFinished) + # signals.append(self.form.depthOffset.editingFinished) + signals.append(self.form.optimizeEnabled.stateChanged) + + return signals + + def updateVisibility(self): + # self.form.boundBoxExtraOffsetX.setEnabled(True) + # self.form.boundBoxExtraOffsetY.setEnabled(True) + self.form.boundBoxSelect.setEnabled(True) + self.form.sampleInterval.setEnabled(True) + self.form.stepOver.setEnabled(True) + ''' + if self.form.algorithmSelect.currentText() == "OCL Dropcutter": + self.form.boundBoxExtraOffsetX.setEnabled(True) + self.form.boundBoxExtraOffsetY.setEnabled(True) + self.form.boundBoxSelect.setEnabled(True) + self.form.dropCutterDirSelect.setEnabled(True) + self.form.stepOver.setEnabled(True) + else: + self.form.boundBoxExtraOffsetX.setEnabled(False) + self.form.boundBoxExtraOffsetY.setEnabled(False) + self.form.boundBoxSelect.setEnabled(False) + self.form.dropCutterDirSelect.setEnabled(False) + self.form.stepOver.setEnabled(False) + ''' + self.form.boundBoxExtraOffsetX.setEnabled(False) + self.form.boundBoxExtraOffsetY.setEnabled(False) + self.form.dropCutterDirSelect.setEnabled(False) + self.form.depthOffset.setEnabled(False) + # self.form.stepOver.setEnabled(False) + pass + + def registerSignalHandlers(self, obj): + # self.form.algorithmSelect.currentIndexChanged.connect(self.updateVisibility) + pass + + +Command = PathOpGui.SetupOperation('Waterline', + PathWaterline.Create, + TaskPanelOpPage, + 'Path-Waterline', + QtCore.QT_TRANSLATE_NOOP("Waterline", "Waterline"), + QtCore.QT_TRANSLATE_NOOP("Waterline", "Create a Waterline Operation from a model"), + PathWaterline.SetupProperties) + +FreeCAD.Console.PrintLog("Loading PathWaterlineGui... done\n") From dacb6e62f291ab13f67a708d8fa3be124d4e3771 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 22 Mar 2020 18:30:41 -0500 Subject: [PATCH 093/117] Path: Add `Waterline` selection gate Using the same gate as PathSurface --- src/Mod/Path/PathScripts/PathSelection.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSelection.py b/src/Mod/Path/PathScripts/PathSelection.py index 386ff1c29c..0cccde13b3 100644 --- a/src/Mod/Path/PathScripts/PathSelection.py +++ b/src/Mod/Path/PathScripts/PathSelection.py @@ -30,12 +30,14 @@ import PathScripts.PathUtils as PathUtils import math PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) + class PathBaseGate(object): # pylint: disable=no-init pass + class EGate(PathBaseGate): def allow(self, doc, obj, sub): # pylint: disable=unused-argument return sub and sub[0:4] == 'Edge' @@ -66,6 +68,7 @@ class ENGRAVEGate(PathBaseGate): return False + class CHAMFERGate(PathBaseGate): def allow(self, doc, obj, sub): # pylint: disable=unused-argument try: @@ -94,7 +97,7 @@ class DRILLGate(PathBaseGate): if hasattr(obj, "Shape") and sub: shape = obj.Shape subobj = shape.getElement(sub) - return PathUtils.isDrillable(shape, subobj, includePartials = True) + return PathUtils.isDrillable(shape, subobj, includePartials=True) else: return False @@ -159,6 +162,7 @@ class POCKETGate(PathBaseGate): return pocketable + class ADAPTIVEGate(PathBaseGate): def allow(self, doc, obj, sub): # pylint: disable=unused-argument @@ -170,6 +174,7 @@ class ADAPTIVEGate(PathBaseGate): return adaptive + class CONTOURGate(PathBaseGate): def allow(self, doc, obj, sub): # pylint: disable=unused-argument pass @@ -182,34 +187,42 @@ def contourselect(): FreeCADGui.Selection.addSelectionGate(CONTOURGate()) FreeCAD.Console.PrintWarning("Contour Select Mode\n") + def eselect(): FreeCADGui.Selection.addSelectionGate(EGate()) FreeCAD.Console.PrintWarning("Edge Select Mode\n") + def drillselect(): FreeCADGui.Selection.addSelectionGate(DRILLGate()) FreeCAD.Console.PrintWarning("Drilling Select Mode\n") + def engraveselect(): FreeCADGui.Selection.addSelectionGate(ENGRAVEGate()) FreeCAD.Console.PrintWarning("Engraving Select Mode\n") + def chamferselect(): FreeCADGui.Selection.addSelectionGate(CHAMFERGate()) FreeCAD.Console.PrintWarning("Deburr Select Mode\n") + def profileselect(): FreeCADGui.Selection.addSelectionGate(PROFILEGate()) FreeCAD.Console.PrintWarning("Profiling Select Mode\n") + def pocketselect(): FreeCADGui.Selection.addSelectionGate(POCKETGate()) FreeCAD.Console.PrintWarning("Pocketing Select Mode\n") + def adaptiveselect(): FreeCADGui.Selection.addSelectionGate(ADAPTIVEGate()) FreeCAD.Console.PrintWarning("Adaptive Select Mode\n") + def surfaceselect(): if(MESHGate() is True or PROFILEGate() is True): FreeCADGui.Selection.addSelectionGate(True) @@ -237,10 +250,12 @@ def select(op): opsel['Profile Edges'] = eselect opsel['Profile Faces'] = profileselect opsel['Surface'] = surfaceselect + opsel['Waterline'] = surfaceselect opsel['Adaptive'] = adaptiveselect opsel['Probe'] = probeselect return opsel[op] + def clear(): FreeCADGui.Selection.removeSelectionGate() FreeCAD.Console.PrintWarning("Free Select\n") From c8fc8e61ae882184fa587a26cf6475c572002c48 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Mon, 23 Mar 2020 16:28:25 -0500 Subject: [PATCH 094/117] Path: Alphabetize and add `Waterline` files to list Icon file Task panel file Path: Remove references to nonexistent files Path: Add missing ToolTable icon file reference --- src/Mod/Path/Gui/Resources/Path.qrc | 62 +++++++++++++++-------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index ca09e140b8..4471e2eb6e 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -1,9 +1,19 @@ + icons/Path-Adaptive.svg + icons/Path-ToolDuplicate.svg icons/Path-3DPocket.svg icons/Path-3DSurface.svg + icons/Path-Area-View.svg + icons/Path-Area-Workplane.svg + icons/Path-Area.svg icons/Path-Array.svg icons/Path-Axis.svg + icons/Path-BFastForward.svg + icons/Path-BPause.svg + icons/Path-BPlay.svg + icons/Path-BStep.svg + icons/Path-BStop.svg icons/Path-BaseGeometry.svg icons/Path-Comment.svg icons/Path-Compound.svg @@ -17,9 +27,9 @@ icons/Path-Drilling.svg icons/Path-Engrave.svg icons/Path-ExportTemplate.svg + icons/Path-Face.svg icons/Path-FacePocket.svg icons/Path-FaceProfile.svg - icons/Path-Face.svg icons/Path-Heights.svg icons/Path-Helix.svg icons/Path-Hop.svg @@ -27,13 +37,13 @@ icons/Path-Job.svg icons/Path-Kurve.svg icons/Path-LengthOffset.svg + icons/Path-Machine.svg icons/Path-MachineLathe.svg icons/Path-MachineMill.svg - icons/Path-Machine.svg icons/Path-OpActive.svg + icons/Path-OpCopy.svg icons/Path-OperationA.svg icons/Path-OperationB.svg - icons/Path-OpCopy.svg icons/Path-Plane.svg icons/Path-Pocket.svg icons/Path-Post.svg @@ -46,49 +56,40 @@ icons/Path-SetupSheet.svg icons/Path-Shape.svg icons/Path-SimpleCopy.svg + icons/Path-Simulator.svg icons/Path-Speed.svg icons/Path-Stock.svg icons/Path-Stop.svg icons/Path-ToolBit.svg icons/Path-ToolChange.svg icons/Path-ToolController.svg - icons/Path-ToolDuplicate.svg icons/Path-Toolpath.svg icons/Path-ToolTable.svg - icons/Path-Area.svg - icons/Path-Area-View.svg - icons/Path-Area-Workplane.svg - icons/Path-Simulator.svg - icons/Path-BFastForward.svg - icons/Path-BPause.svg - icons/Path-BPlay.svg - icons/Path-BStep.svg - icons/Path-BStop.svg + icons/Path-Waterline.svg icons/arrow-ccw.svg icons/arrow-cw.svg icons/arrow-down.svg - icons/arrow-left.svg icons/arrow-left-down.svg icons/arrow-left-up.svg - icons/arrow-right.svg + icons/arrow-left.svg icons/arrow-right-down.svg icons/arrow-right-up.svg + icons/arrow-right.svg icons/arrow-up.svg - icons/edge-join-miter.svg icons/edge-join-miter-not.svg - icons/edge-join-round.svg + icons/edge-join-miter.svg icons/edge-join-round-not.svg + icons/edge-join-round.svg icons/preferences-path.svg - icons/Path-Adaptive.svg panels/DlgJobChooser.ui panels/DlgJobCreate.ui panels/DlgJobModelSelect.ui panels/DlgJobTemplateExport.ui panels/DlgSelectPostProcessor.ui + panels/DlgTCChooser.ui panels/DlgToolControllerEdit.ui panels/DlgToolCopy.ui panels/DlgToolEdit.ui - panels/DlgTCChooser.ui panels/DogboneEdit.ui panels/DressupPathBoundary.ui panels/HoldingTagsEdit.ui @@ -106,6 +107,7 @@ panels/PageOpProbeEdit.ui panels/PageOpProfileFullEdit.ui panels/PageOpSurfaceEdit.ui + panels/PageOpWaterlineEdit.ui panels/PathEdit.ui panels/PointEdit.ui panels/SetupGlobal.ui @@ -120,16 +122,25 @@ preferences/PathDressupHoldingTags.ui preferences/PathJob.ui translations/Path_af.qm + translations/Path_ar.qm + translations/Path_ca.qm translations/Path_cs.qm translations/Path_de.qm translations/Path_el.qm translations/Path_es-ES.qm + translations/Path_eu.qm translations/Path_fi.qm + translations/Path_fil.qm translations/Path_fr.qm + translations/Path_gl.qm translations/Path_hr.qm translations/Path_hu.qm + translations/Path_id.qm translations/Path_it.qm translations/Path_ja.qm + translations/Path_kab.qm + translations/Path_ko.qm + translations/Path_lt.qm translations/Path_nl.qm translations/Path_no.qm translations/Path_pl.qm @@ -143,18 +154,9 @@ translations/Path_sv-SE.qm translations/Path_tr.qm translations/Path_uk.qm + translations/Path_val-ES.qm + translations/Path_vi.qm translations/Path_zh-CN.qm translations/Path_zh-TW.qm - translations/Path_eu.qm - translations/Path_ca.qm - translations/Path_gl.qm - translations/Path_kab.qm - translations/Path_ko.qm - translations/Path_fil.qm - translations/Path_id.qm - translations/Path_lt.qm - translations/Path_val-ES.qm - translations/Path_ar.qm - translations/Path_vi.qm From ba48b9e48f5b13db1a5668cb1f654585e7f60df3 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 22 Mar 2020 18:23:04 -0500 Subject: [PATCH 095/117] Path: Alphabetize and add `Waterline` module files --- src/Mod/Path/CMakeLists.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 1a51ebc872..ded7c91a93 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -25,6 +25,8 @@ INSTALL( SET(PathScripts_SRCS PathCommands.py + PathScripts/PathAdaptive.py + PathScripts/PathAdaptiveGui.py PathScripts/PathAreaOp.py PathScripts/PathArray.py PathScripts/PathCircularHoleBase.py @@ -100,6 +102,7 @@ SET(PathScripts_SRCS PathScripts/PathSetupSheetOpPrototype.py PathScripts/PathSetupSheetOpPrototypeGui.py PathScripts/PathSimpleCopy.py + PathScripts/PathSimulatorGui.py PathScripts/PathStock.py PathScripts/PathStop.py PathScripts/PathSurface.py @@ -113,15 +116,14 @@ SET(PathScripts_SRCS PathScripts/PathToolController.py PathScripts/PathToolControllerGui.py PathScripts/PathToolEdit.py - PathScripts/PathToolLibraryManager.py PathScripts/PathToolLibraryEditor.py + PathScripts/PathToolLibraryManager.py PathScripts/PathUtil.py PathScripts/PathUtils.py PathScripts/PathUtilsGui.py - PathScripts/PathSimulatorGui.py + PathScripts/PathWaterline.py + PathScripts/PathWaterlineGui.py PathScripts/PostUtils.py - PathScripts/PathAdaptiveGui.py - PathScripts/PathAdaptive.py PathScripts/__init__.py ) @@ -192,8 +194,8 @@ SET(PathTests_SRCS PathTests/boxtest.fcstd PathTests/test_centroid_00.ngc PathTests/test_geomop.fcstd - PathTests/test_linuxcnc_00.ngc PathTests/test_holes00.fcstd + PathTests/test_linuxcnc_00.ngc ) SET(PathImages_Ops From 04f8c474a425ac211617082b290ade7fcadee228 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 22 Mar 2020 18:24:58 -0500 Subject: [PATCH 096/117] Path: Add `Waterline` command and PEP8 clean-up --- src/Mod/Path/InitGui.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index dc638a9299..6fabab05f0 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -21,8 +21,9 @@ # * * # ***************************************************************************/ + class PathCommandGroup: - def __init__(self, cmdlist, menu, tooltip = None): + def __init__(self, cmdlist, menu, tooltip=None): self.cmdlist = cmdlist self.menu = menu if tooltip is None: @@ -34,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: @@ -43,6 +44,7 @@ class PathCommandGroup: return True return False + class PathWorkbench (Workbench): "Path workbench" @@ -88,14 +90,14 @@ class PathWorkbench (Workbench): projcmdlist = ["Path_Job", "Path_Post"] toolcmdlist = ["Path_Inspect", "Path_Simulator", "Path_ToolLibraryEdit", "Path_SelectLoop", "Path_OpActiveToggle"] prepcmdlist = ["Path_Fixture", "Path_Comment", "Path_Stop", "Path_Custom", "Path_Probe"] - twodopcmdlist = ["Path_Contour", "Path_Profile_Faces", "Path_Profile_Edges", "Path_Pocket_Shape", "Path_Drilling", "Path_MillFace", "Path_Helix", "Path_Adaptive" ] + twodopcmdlist = ["Path_Contour", "Path_Profile_Faces", "Path_Profile_Edges", "Path_Pocket_Shape", "Path_Drilling", "Path_MillFace", "Path_Helix", "Path_Adaptive"] threedopcmdlist = ["Path_Pocket_3D"] engravecmdlist = ["Path_Engrave", "Path_Deburr"] - modcmdlist = ["Path_OperationCopy", "Path_Array", "Path_SimpleCopy" ] + modcmdlist = ["Path_OperationCopy", "Path_Array", "Path_SimpleCopy"] dressupcmdlist = ["Path_DressupAxisMap", "Path_DressupPathBoundary", "Path_DressupDogbone", "Path_DressupDragKnife", "Path_DressupLeadInOut", "Path_DressupRampEntry", "Path_DressupTag", "Path_DressupZCorrect"] extracmdlist = [] - #modcmdmore = ["Path_Hop",] - #remotecmdlist = ["Path_Remote"] + # modcmdmore = ["Path_Hop",] + # remotecmdlist = ["Path_Remote"] engravecmdgroup = ['Path_EngraveTools'] FreeCADGui.addCommand('Path_EngraveTools', PathCommandGroup(engravecmdlist, QtCore.QT_TRANSLATE_NOOP("Path", 'Engraving Operations'))) @@ -107,11 +109,12 @@ class PathWorkbench (Workbench): extracmdlist.extend(["Path_Area", "Path_Area_Workplane"]) try: - import ocl # pylint: disable=unused-variable + import ocl # pylint: disable=unused-variable from PathScripts import PathSurfaceGui - threedopcmdlist.append("Path_Surface") + 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'))) + FreeCADGui.addCommand('Path_3dTools', PathCommandGroup(threedopcmdlist, QtCore.QT_TRANSLATE_NOOP("Path", '3D Operations'))) except ImportError: FreeCAD.Console.PrintError("OpenCamLib is not working!\n") @@ -122,7 +125,9 @@ class PathWorkbench (Workbench): if extracmdlist: self.appendToolbar(QtCore.QT_TRANSLATE_NOOP("Path", "Helpful Tools"), extracmdlist) - self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], projcmdlist +["Path_ExportTemplate", "Separator"] + toolbitcmdlist + toolcmdlist +["Separator"] + twodopcmdlist + engravecmdlist +["Separator"] +threedopcmdlist +["Separator"]) + self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], projcmdlist + ["Path_ExportTemplate", "Separator"] + + toolbitcmdlist + toolcmdlist + ["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( @@ -136,7 +141,7 @@ class PathWorkbench (Workbench): curveAccuracy = PathPreferences.defaultLibAreaCurveAccuracy() if curveAccuracy: - Path.Area.setDefaultParams(Accuracy = curveAccuracy) + Path.Area.setDefaultParams(Accuracy=curveAccuracy) Log('Loading Path workbench... done\n') @@ -171,8 +176,8 @@ class PathWorkbench (Workbench): if obj.isDerivedFrom("Path::Feature"): if "Profile" in selectedName or "Contour" in selectedName or "Dressup" in selectedName: self.appendContextMenu("", "Separator") - #self.appendContextMenu("", ["Set_StartPoint"]) - #self.appendContextMenu("", ["Set_EndPoint"]) + # self.appendContextMenu("", ["Set_StartPoint"]) + # self.appendContextMenu("", ["Set_EndPoint"]) for cmd in self.dressupcmds: self.appendContextMenu("", [cmd]) menuAppended = True @@ -182,10 +187,10 @@ class PathWorkbench (Workbench): if menuAppended: self.appendContextMenu("", "Separator") + 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 a2c72feba86fb2d98a601a961a6feeb005e98716 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 22 Mar 2020 18:37:27 -0500 Subject: [PATCH 097/117] Path: Add commented references for missing imported modules PathSurfaceGui and PathWaterlineGui are imported in the initGui.py module due to OCL dependency. --- src/Mod/Path/PathScripts/PathGuiInit.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index 29272c0382..52116e608b 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -35,6 +35,7 @@ else: Processed = False + def Startup(): global Processed # pylint: disable=global-statement if not Processed: @@ -71,12 +72,13 @@ def Startup(): from PathScripts import PathSimpleCopy from PathScripts import PathSimulatorGui from PathScripts import PathStop + # from PathScripts import PathSurfaceGui # Added in initGui.py due to OCL dependency from PathScripts import PathToolController from PathScripts import PathToolControllerGui from PathScripts import PathToolLibraryManager from PathScripts import PathToolLibraryEditor from PathScripts import PathUtilsGui + # from PathScripts import PathWaterlineGui # Added in initGui.py due to OCL dependency Processed = True else: PathLog.debug('Skipping PathGui initialisation') - From df253cdcdcd05ac023bc8cf0ba53f20205aa9257 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Mon, 23 Mar 2020 11:31:49 -0500 Subject: [PATCH 098/117] Path: Add `Waterline` task panel Path: Update `Waterline` task panel and GUI code --- .../Resources/panels/PageOpWaterlineEdit.ui | 206 ++++++++++++++++++ src/Mod/Path/PathScripts/PathWaterlineGui.py | 72 ++---- 2 files changed, 219 insertions(+), 59 deletions(-) create mode 100644 src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui new file mode 100644 index 0000000000..e9c94ac535 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui @@ -0,0 +1,206 @@ + + + Form + + + + 0 + 0 + 400 + 400 + + + + Form + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + ToolController + + + + + + + <html><head/><body><p>The tool and its settings to be used for this operation.</p></body></html> + + + + + + + + + + + + + mm + + + + + + + Cut Pattern + + + + + + + Sample interval + + + + + + + + + + + 9 + + + + + Line + + + + + ZigZag + + + + + Circular + + + + + CircularZigZag + + + + + + + + Depth offset + + + + + + + + 9 + + + + + Stock + + + + + BaseBoundBox + + + + + + + + mm + + + + + + + Optimize Linear Paths + + + + + + + BoundBox + + + + + + + Boundary Adjustment + + + + + + + <html><head/><body><p>The amount by which the tool is laterally displaced on each cycle of the pattern, specified in percent of the tool diameter.</p><p>A step over of 100% results in no overlap between two different cycles.</p></body></html> + + + 1 + + + 100 + + + 10 + + + 100 + + + + + + + Step over + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Gui::InputField + QWidget +
gui::inputfield.h
+
+
+ + +
diff --git a/src/Mod/Path/PathScripts/PathWaterlineGui.py b/src/Mod/Path/PathScripts/PathWaterlineGui.py index 2c9e5d31d1..ece048ecee 100644 --- a/src/Mod/Path/PathScripts/PathWaterlineGui.py +++ b/src/Mod/Path/PathScripts/PathWaterlineGui.py @@ -2,7 +2,8 @@ # *************************************************************************** # * * -# * Copyright (c) 2017 sliptonic * +# * Copyright (c) 2020 sliptonic * +# * Copyright (c) 2020 russ4262 * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -31,13 +32,9 @@ import PathScripts.PathOpGui as PathOpGui from PySide import QtCore __title__ = "Path Waterline Operation UI" -__author__ = "sliptonic (Brad Collette)" +__author__ = "sliptonic (Brad Collette), russ4262 (Russell Johnson)" __url__ = "http://www.freecadweb.org" __doc__ = "Waterline operation page controller and command implementation." -__contributors__ = "russ4262 (Russell Johnson)" -__created__ = "2019" -__scriptVersion__ = "3t Usable" -__lastModified__ = "2019-05-18 21:18 CST" class TaskPanelOpPage(PathOpGui.TaskPanelPage): @@ -49,26 +46,15 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): def getFields(self, obj): '''getFields(obj) ... transfers values from UI to obj's proprties''' - # if obj.StartVertex != self.form.startVertex.value(): - # obj.StartVertex = self.form.startVertex.value() - PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset) + PathGui.updateInputField(obj, 'BoundaryAdjustment', self.form.boundaryAdjustment) PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) if obj.StepOver != self.form.stepOver.value(): obj.StepOver = self.form.stepOver.value() - # if obj.Algorithm != str(self.form.algorithmSelect.currentText()): - # obj.Algorithm = str(self.form.algorithmSelect.currentText()) - if obj.BoundBox != str(self.form.boundBoxSelect.currentText()): obj.BoundBox = str(self.form.boundBoxSelect.currentText()) - # if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentText()): - # obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentText()) - - # obj.DropCutterExtraOffset.x = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetX.text()).Value - # obj.DropCutterExtraOffset.y = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetY.text()).Value - if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked(): obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked() @@ -76,70 +62,38 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): def setFields(self, obj): '''setFields(obj) ... transfers obj's property values to UI''' - # self.form.startVertex.setValue(obj.StartVertex) - # self.selectInComboBox(obj.Algorithm, self.form.algorithmSelect) + self.setupToolController(obj, self.form.toolController) self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect) - # self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect) - - # self.form.boundBoxExtraOffsetX.setText(str(obj.DropCutterExtraOffset.x)) - # self.form.boundBoxExtraOffsetY.setText(str(obj.DropCutterExtraOffset.y)) - # self.form.depthOffset.setText(FreeCAD.Units.Quantity(obj.DepthOffset.Value, FreeCAD.Units.Length).UserString) - self.form.sampleInterval.setText(str(obj.SampleInterval)) + self.form.boundaryAdjustment.setText(FreeCAD.Units.Quantity(obj.BoundaryAdjustment.Value, FreeCAD.Units.Length).UserString) self.form.stepOver.setValue(obj.StepOver) + self.form.sampleInterval.setText(str(obj.SampleInterval)) if obj.OptimizeLinearPaths: self.form.optimizeEnabled.setCheckState(QtCore.Qt.Checked) else: self.form.optimizeEnabled.setCheckState(QtCore.Qt.Unchecked) - self.setupToolController(obj, self.form.toolController) - def getSignalsForUpdate(self, obj): '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' signals = [] - # signals.append(self.form.startVertex.editingFinished) signals.append(self.form.toolController.currentIndexChanged) - # signals.append(self.form.algorithmSelect.currentIndexChanged) signals.append(self.form.boundBoxSelect.currentIndexChanged) - # signals.append(self.form.dropCutterDirSelect.currentIndexChanged) - # signals.append(self.form.boundBoxExtraOffsetX.editingFinished) - # signals.append(self.form.boundBoxExtraOffsetY.editingFinished) - signals.append(self.form.sampleInterval.editingFinished) + signals.append(self.form.boundaryAdjustment.editingFinished) signals.append(self.form.stepOver.editingFinished) - # signals.append(self.form.depthOffset.editingFinished) + signals.append(self.form.sampleInterval.editingFinished) signals.append(self.form.optimizeEnabled.stateChanged) return signals def updateVisibility(self): - # self.form.boundBoxExtraOffsetX.setEnabled(True) - # self.form.boundBoxExtraOffsetY.setEnabled(True) self.form.boundBoxSelect.setEnabled(True) - self.form.sampleInterval.setEnabled(True) + self.form.boundaryAdjustment.setEnabled(True) self.form.stepOver.setEnabled(True) - ''' - if self.form.algorithmSelect.currentText() == "OCL Dropcutter": - self.form.boundBoxExtraOffsetX.setEnabled(True) - self.form.boundBoxExtraOffsetY.setEnabled(True) - self.form.boundBoxSelect.setEnabled(True) - self.form.dropCutterDirSelect.setEnabled(True) - self.form.stepOver.setEnabled(True) - else: - self.form.boundBoxExtraOffsetX.setEnabled(False) - self.form.boundBoxExtraOffsetY.setEnabled(False) - self.form.boundBoxSelect.setEnabled(False) - self.form.dropCutterDirSelect.setEnabled(False) - self.form.stepOver.setEnabled(False) - ''' - self.form.boundBoxExtraOffsetX.setEnabled(False) - self.form.boundBoxExtraOffsetY.setEnabled(False) - self.form.dropCutterDirSelect.setEnabled(False) - self.form.depthOffset.setEnabled(False) - # self.form.stepOver.setEnabled(False) - pass + self.form.sampleInterval.setEnabled(True) + self.form.optimizeEnabled.setEnabled(True) def registerSignalHandlers(self, obj): - # self.form.algorithmSelect.currentIndexChanged.connect(self.updateVisibility) + # self.form.clearLayers.currentIndexChanged.connect(self.updateVisibility) pass From 4109f0c7df90eb8e332cb3e713453d76f5e7b7a2 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Thu, 26 Mar 2020 18:04:04 -0500 Subject: [PATCH 099/117] Path: Restructure initOperation() for backward compatibility Added initOpProperties() to handle independent creation of properties for backward compatibility and to allow for future storage of enumeration property lists within the operation. Path: Change from PathSurface code to `Waterline` Path: Updates and fixes Path: Simplify code Remove `Algorithm` and `AreaParams` property usage. Remove other unused properties. Simplify setupEditorProperties(). Path: Remove unused methods Path: Update property initialization and handling Make properties backward compatible with previous versions of the operation. Remove references to unused properties due to 3D Surface and Waterline separation. Path: Insert placeholder for `Experimental` algorithm Path: Fix missing property and fix syntax Missing `Algrorithm --- src/Mod/Path/PathScripts/PathSurface.py | 769 +++++----------------- src/Mod/Path/PathScripts/PathWaterline.py | 410 +++++------- 2 files changed, 326 insertions(+), 853 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 0c83884bc7..89932a104a 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -82,138 +82,171 @@ class ObjectSurface(PathOp.ObjectOp): return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureCoolant | PathOp.FeatureBaseFaces def initOperation(self, obj): - '''initPocketOp(obj) ... create facing specific properties''' - obj.addProperty("App::PropertyEnumeration", "Algorithm", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "The library to use to generate the path")) - obj.addProperty("App::PropertyEnumeration", "BoundBox", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "Should the operation be limited by the stock object or by the bounding box of the base object")) - obj.addProperty("App::PropertyEnumeration", "DropCutterDir", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction along which dropcutter lines are created")) - obj.addProperty("App::PropertyVectorDistance", "DropCutterExtraOffset", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "Additional offset to the selected bounding box")) - obj.addProperty("App::PropertyEnumeration", "LayerMode", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "The completion mode for the operation: single or multi-pass")) - obj.addProperty("App::PropertyEnumeration", "ScanType", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan.")) - - obj.addProperty("App::PropertyDistance", "AngularDeflection", "Mesh Conversion", QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values increase processing time a lot.")) - obj.addProperty("App::PropertyDistance", "LinearDeflection", "Mesh Conversion", QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values do not increase processing time much.")) - - obj.addProperty("App::PropertyFloat", "CutterTilt", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")) - obj.addProperty("App::PropertyEnumeration", "RotationAxis", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "The model will be rotated around this axis.")) - obj.addProperty("App::PropertyFloat", "StartIndex", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Start index(angle) for rotational scan")) - obj.addProperty("App::PropertyFloat", "StopIndex", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")) - - obj.addProperty("App::PropertyInteger", "AvoidLastX_Faces", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.")) - obj.addProperty("App::PropertyBool", "AvoidLastX_InternalFeatures", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces.")) - obj.addProperty("App::PropertyDistance", "BoundaryAdjustment", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.")) - obj.addProperty("App::PropertyBool", "BoundaryEnforcement", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).")) - obj.addProperty("App::PropertyDistance", "DepthOffset", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Z-axis offset from the surface of the object")) - obj.addProperty("App::PropertyEnumeration", "HandleMultipleFeatures", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features.")) - obj.addProperty("App::PropertyDistance", "InternalFeaturesAdjustment", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.")) - obj.addProperty("App::PropertyBool", "InternalFeaturesCut", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face.")) - obj.addProperty("App::PropertyEnumeration", "ProfileEdges", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")) - obj.addProperty("App::PropertyDistance", "SampleInterval", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "The Sample Interval. Small values cause long wait times")) - obj.addProperty("App::PropertyPercent", "StepOver", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path")) - - obj.addProperty("App::PropertyVectorDistance", "CircularCenterCustom", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path")) - obj.addProperty("App::PropertyEnumeration", "CircularCenterAt", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "Choose what point to start the circular pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom.")) - obj.addProperty("App::PropertyEnumeration", "CutMode", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part: Climb(ClockWise) or Conventional(CounterClockWise)")) - obj.addProperty("App::PropertyEnumeration", "CutPattern", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use")) - obj.addProperty("App::PropertyFloat", "CutPatternAngle", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")) - obj.addProperty("App::PropertyBool", "CutPatternReversed", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the order of the step-overs will be reversed; the operation will begin cutting the outer most line/arc, and work toward the inner most line/arc.")) - - obj.addProperty("App::PropertyBool", "OptimizeLinearPaths", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.")) - obj.addProperty("App::PropertyBool", "OptimizeStepOverTransitions", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.")) - obj.addProperty("App::PropertyBool", "CircularUseG2G3", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.")) - obj.addProperty("App::PropertyDistance", "GapThreshold", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.")) - obj.addProperty("App::PropertyString", "GapSizes", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")) - - obj.addProperty("App::PropertyBool", "IgnoreWaste", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore areas that proceed below specified depth.")) - obj.addProperty("App::PropertyFloat", "IgnoreWasteDepth", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Depth used to identify waste areas to ignore.")) - obj.addProperty("App::PropertyBool", "ReleaseFromWaste", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Cut through waste to depth at model edge, releasing the model.")) - - obj.addProperty("App::PropertyVectorDistance", "StartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path")) - obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make True, if specifying a Start Point")) + '''initPocketOp(obj) ... create operation specific properties''' + self.initOpProperties(obj) # For debugging - obj.addProperty('App::PropertyString', 'AreaParams', 'Debugging') - obj.setEditorMode('AreaParams', 2) # hide - obj.addProperty("App::PropertyBool", "ShowTempObjects", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")) if PathLog.getLevel(PathLog.thisModule()) != 4: obj.setEditorMode('ShowTempObjects', 2) # hide - obj.Algorithm = ['OCL Dropcutter', 'OCL Waterline'] - obj.BoundBox = ['BaseBoundBox', 'Stock'] - obj.CircularCenterAt = ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'] - obj.CutMode = ['Conventional', 'Climb'] - obj.CutPattern = ['Line', 'ZigZag', 'Circular', 'CircularZigZag'] # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] - obj.DropCutterDir = ['X', 'Y'] - obj.HandleMultipleFeatures = ['Collectively', 'Individually'] - obj.LayerMode = ['Single-pass', 'Multi-pass'] - obj.ProfileEdges = ['None', 'Only', 'First', 'Last'] - obj.RotationAxis = ['X', 'Y'] - obj.ScanType = ['Planar', 'Rotational'] - if not hasattr(obj, 'DoNotSetDefaultValues'): self.setEditorProperties(obj) + def initOpProperties(self, obj): + '''initOpProperties(obj) ... create operation specific properties''' + + PROPS = [ + ("App::PropertyBool", "ShowTempObjects", "Debug", + QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")), + + ("App::PropertyDistance", "AngularDeflection", "Mesh Conversion", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values increase processing time a lot.")), + ("App::PropertyDistance", "LinearDeflection", "Mesh Conversion", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values do not increase processing time much.")), + + ("App::PropertyFloat", "CutterTilt", "Rotational", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")), + ("App::PropertyEnumeration", "DropCutterDir", "Rotational", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction along which dropcutter lines are created")), + ("App::PropertyVectorDistance", "DropCutterExtraOffset", "Rotational", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Additional offset to the selected bounding box")), + ("App::PropertyEnumeration", "RotationAxis", "Rotational", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The model will be rotated around this axis.")), + ("App::PropertyFloat", "StartIndex", "Rotational", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Start index(angle) for rotational scan")), + ("App::PropertyFloat", "StopIndex", "Rotational", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")), + + ("App::PropertyEnumeration", "BoundBox", "Surface", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Should the operation be limited by the stock object or by the bounding box of the base object")), + ("App::PropertyEnumeration", "ScanType", "Surface", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan.")), + ("App::PropertyDistance", "SampleInterval", "Surface", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The Sample Interval. Small values cause long wait times")), + + ("App::PropertyInteger", "AvoidLastX_Faces", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.")), + ("App::PropertyBool", "AvoidLastX_InternalFeatures", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces.")), + ("App::PropertyDistance", "BoundaryAdjustment", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.")), + ("App::PropertyBool", "BoundaryEnforcement", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).")), + ("App::PropertyEnumeration", "HandleMultipleFeatures", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features.")), + ("App::PropertyDistance", "InternalFeaturesAdjustment", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.")), + ("App::PropertyBool", "InternalFeaturesCut", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face.")), + + ("App::PropertyVectorDistance", "CircularCenterCustom", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")), + ("App::PropertyEnumeration", "CircularCenterAt", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose what point to start the ciruclar pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom.")), + ("App::PropertyEnumeration", "CutMode", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part: Climb(ClockWise) or Conventional(CounterClockWise)")), + ("App::PropertyEnumeration", "CutPattern", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use")), + ("App::PropertyFloat", "CutPatternAngle", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")), + ("App::PropertyBool", "CutPatternReversed", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the order of the step-overs will be reversed; the operation will begin cutting the outer most line/arc, and work toward the inner most line/arc.")), + ("App::PropertyDistance", "DepthOffset", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Z-axis offset from the surface of the object")), + ("App::PropertyEnumeration", "LayerMode", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The completion mode for the operation: single or multi-pass")), + ("App::PropertyEnumeration", "ProfileEdges", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")), + ("App::PropertyPercent", "StepOver", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path")), + + ("App::PropertyBool", "OptimizeLinearPaths", "Surface Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.")), + ("App::PropertyBool", "OptimizeStepOverTransitions", "Surface Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.")), + ("App::PropertyBool", "CircularUseG2G3", "Surface Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.")), + ("App::PropertyDistance", "GapThreshold", "Surface Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.")), + ("App::PropertyString", "GapSizes", "Surface Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")), + + ("App::PropertyVectorDistance", "StartPoint", "Start Point", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")), + ("App::PropertyBool", "UseStartPoint", "Start Point", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Make True, if specifying a Start Point")) + ] + + missing = list() + for (prtyp, nm, grp, tt) in PROPS: + if not hasattr(obj, nm): + obj.addProperty(prtyp, nm, grp, tt) + missing.append(nm) + + # Set enumeration lists for enumeration properties + if len(missing) > 0: + ENUMS = self._propertyEnumerations() + for n in ENUMS: + if n in missing: + cmdStr = 'obj.{}={}'.format(n, ENUMS[n]) + exec(cmdStr) + self.addedAllProperties = True + def _propertyEnumerations(self): + # Enumeration lists for App::PropertyEnumeration properties + return { + 'BoundBox': ['BaseBoundBox', 'Stock'], + 'CircularCenterAt': ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'], + 'CutMode': ['Conventional', 'Climb'], + 'CutPattern': ['Line', 'ZigZag', 'Circular', 'CircularZigZag'], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] + 'DropCutterDir': ['X', 'Y'], + 'HandleMultipleFeatures': ['Collectively', 'Individually'], + 'LayerMode': ['Single-pass', 'Multi-pass'], + 'ProfileEdges': ['None', 'Only', 'First', 'Last'], + 'RotationAxis': ['X', 'Y'], + 'ScanType': ['Planar', 'Rotational'] + } + def setEditorProperties(self, obj): # Used to hide inputs in properties list - if obj.Algorithm == 'OCL Dropcutter': - obj.setEditorMode('CutPattern', 0) - obj.setEditorMode('HandleMultipleFeatures', 0) - obj.setEditorMode('CircularCenterAt', 0) - obj.setEditorMode('CircularCenterCustom', 0) - obj.setEditorMode('CutPatternAngle', 0) - # obj.setEditorMode('BoundaryEnforcement', 0) - - if obj.ScanType == 'Planar': - obj.setEditorMode('DropCutterDir', 2) - obj.setEditorMode('DropCutterExtraOffset', 2) - obj.setEditorMode('RotationAxis', 2) # 2=hidden - obj.setEditorMode('StartIndex', 2) - obj.setEditorMode('StopIndex', 2) - obj.setEditorMode('CutterTilt', 2) - if obj.CutPattern == 'Circular' or obj.CutPattern == 'CircularZigZag': - obj.setEditorMode('CutPatternAngle', 2) - else: # if obj.CutPattern == 'Line' or obj.CutPattern == 'ZigZag': - obj.setEditorMode('CircularCenterAt', 2) - obj.setEditorMode('CircularCenterCustom', 2) - elif obj.ScanType == 'Rotational': - obj.setEditorMode('DropCutterDir', 0) - obj.setEditorMode('DropCutterExtraOffset', 0) - obj.setEditorMode('RotationAxis', 0) # 0=show & editable - obj.setEditorMode('StartIndex', 0) - obj.setEditorMode('StopIndex', 0) - obj.setEditorMode('CutterTilt', 0) - - elif obj.Algorithm == 'OCL Waterline': - obj.setEditorMode('DropCutterExtraOffset', 2) - obj.setEditorMode('DropCutterDir', 2) - obj.setEditorMode('HandleMultipleFeatures', 2) - obj.setEditorMode('CutPattern', 2) - obj.setEditorMode('CutPatternAngle', 2) - # obj.setEditorMode('BoundaryEnforcement', 2) - - # Disable IgnoreWaste feature - obj.setEditorMode('IgnoreWaste', 2) - obj.setEditorMode('IgnoreWasteDepth', 2) - obj.setEditorMode('ReleaseFromWaste', 2) + mode = 2 # 2=hidden + if obj.ScanType == 'Planar': + show = 0 + hide = 2 + # if obj.CutPattern in ['Line', 'ZigZag']: + if obj.CutPattern in ['Circular', 'CircularZigZag']: + show = 2 # hide + hide = 0 # show + obj.setEditorMode('CutPatternAngle', show) + obj.setEditorMode('CircularCenterAt', hide) + obj.setEditorMode('CircularCenterCustom', hide) + elif obj.ScanType == 'Rotational': + mode = 0 # show and editable + obj.setEditorMode('DropCutterDir', mode) + obj.setEditorMode('DropCutterExtraOffset', mode) + obj.setEditorMode('RotationAxis', mode) + obj.setEditorMode('StartIndex', mode) + obj.setEditorMode('StopIndex', mode) + obj.setEditorMode('CutterTilt', mode) def onChanged(self, obj, prop): if hasattr(self, 'addedAllProperties'): if self.addedAllProperties is True: - if prop == 'Algorithm': - self.setEditorProperties(obj) if prop == 'ScanType': self.setEditorProperties(obj) if prop == 'CutPattern': self.setEditorProperties(obj) def opOnDocumentRestored(self, obj): + self.initOpProperties(obj) + if PathLog.getLevel(PathLog.thisModule()) != 4: obj.setEditorMode('ShowTempObjects', 2) # hide else: obj.setEditorMode('ShowTempObjects', 0) # show - self.addedAllProperties = True + self.setEditorProperties(obj) def opSetDefaultValues(self, obj, job): @@ -221,8 +254,6 @@ class ObjectSurface(PathOp.ObjectOp): job = PathUtils.findParentJob(obj) obj.OptimizeLinearPaths = True - obj.IgnoreWaste = False - obj.ReleaseFromWaste = False obj.InternalFeaturesCut = True obj.OptimizeStepOverTransitions = False obj.CircularUseG2G3 = False @@ -241,7 +272,6 @@ class ObjectSurface(PathOp.ObjectOp): obj.CutPattern = 'Line' obj.HandleMultipleFeatures = 'Collectively' # 'Individually' obj.CircularCenterAt = 'CenterOfMass' # 'CenterOfBoundBox', 'XminYmin', 'Custom' - obj.AreaParams = '' obj.GapSizes = 'No gaps identified.' obj.StepOver = 100 obj.CutPatternAngle = 0.0 @@ -366,9 +396,6 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.info('\nBegin 3D Surface operation...') startTime = time.time() - # Disable(ignore) ReleaseFromWaste option(input) - obj.ReleaseFromWaste = False - # Identify parent Job JOB = PathUtils.findParentJob(obj) if JOB is None: @@ -480,16 +507,6 @@ class ObjectSurface(PathOp.ObjectOp): # ###### MAIN COMMANDS FOR OPERATION ###### - # If algorithm is `Waterline`, force certain property values - # Save initial value for restoration later. - if obj.Algorithm == 'OCL Waterline': - preCP = obj.CutPattern - preCPA = obj.CutPatternAngle - preRB = obj.BoundaryEnforcement - obj.CutPattern = 'Line' - obj.CutPatternAngle = 0.0 - obj.BoundaryEnforcement = False - # Begin processing obj.Base data and creating GCode # Process selected faces, if available pPM = self._preProcessModel(JOB, obj) @@ -520,12 +537,6 @@ class ObjectSurface(PathOp.ObjectOp): # Save gcode produced self.commandlist.extend(CMDS) - # If algorithm is `Waterline`, restore initial property values - if obj.Algorithm == 'OCL Waterline': - obj.CutPattern = preCP - obj.CutPatternAngle = preCPA - obj.BoundaryEnforcement = preRB - # ###### CLOSING COMMANDS FOR OPERATION ###### # Delete temporary objects @@ -750,7 +761,7 @@ class ObjectSurface(PathOp.ObjectOp): # Handle profile edges request if cont is True and obj.ProfileEdges != 'None': ofstVal = self._calculateOffsetValue(obj, isHole) - psOfst = self._extractFaceOffset(obj, cfsL, ofstVal) + psOfst = self._extractFaceOffset(cfsL, ofstVal) if psOfst is not False: mPS = [psOfst] if obj.ProfileEdges == 'Only': @@ -760,7 +771,7 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.error(' -Failed to create profile geometry for selected faces.') cont = False - if cont is True: + if cont: if self.showDebugObjects is True: T = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCollectiveShape') T.Shape = cfsL @@ -768,12 +779,12 @@ class ObjectSurface(PathOp.ObjectOp): self.tempGroup.addObject(T) ofstVal = self._calculateOffsetValue(obj, isHole) - faceOfstShp = self._extractFaceOffset(obj, cfsL, ofstVal) + faceOfstShp = self._extractFaceOffset(cfsL, ofstVal) if faceOfstShp is False: PathLog.error(' -Failed to create offset face.') cont = False - if cont is True: + if cont: lenIfL = len(ifL) if obj.InternalFeaturesCut is False: if lenIfL == 0: @@ -789,7 +800,7 @@ class ObjectSurface(PathOp.ObjectOp): C.purgeTouched() self.tempGroup.addObject(C) ofstVal = self._calculateOffsetValue(obj, isHole=True) - intOfstShp = self._extractFaceOffset(obj, casL, ofstVal) + intOfstShp = self._extractFaceOffset(casL, ofstVal) mIFS.append(intOfstShp) # faceOfstShp = faceOfstShp.cut(intOfstShp) @@ -825,7 +836,7 @@ class ObjectSurface(PathOp.ObjectOp): if obj.ProfileEdges != 'None': ofstVal = self._calculateOffsetValue(obj, isHole) - psOfst = self._extractFaceOffset(obj, outerFace, ofstVal) + psOfst = self._extractFaceOffset(outerFace, ofstVal) if psOfst is not False: if mPS is False: mPS = list() @@ -839,9 +850,9 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.error(' -Failed to create profile geometry for Face{}.'.format(fNum)) cont = False - if cont is True: + if cont: ofstVal = self._calculateOffsetValue(obj, isHole) - faceOfstShp = self._extractFaceOffset(obj, outerFace, ofstVal) + faceOfstShp = self._extractFaceOffset(outerFace, ofstVal) lenIfl = len(ifL) if obj.InternalFeaturesCut is False and lenIfl > 0: @@ -851,7 +862,7 @@ class ObjectSurface(PathOp.ObjectOp): casL = Part.makeCompound(ifL) ofstVal = self._calculateOffsetValue(obj, isHole=True) - intOfstShp = self._extractFaceOffset(obj, casL, ofstVal) + intOfstShp = self._extractFaceOffset(casL, ofstVal) mIFS.append(intOfstShp) # faceOfstShp = faceOfstShp.cut(intOfstShp) @@ -907,7 +918,7 @@ class ObjectSurface(PathOp.ObjectOp): P.purgeTouched() self.tempGroup.addObject(P) - if cont is True: + if cont: if self.showDebugObjects is True: PathLog.debug('*** tmpVoidCompound') P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpVoidCompound') @@ -916,12 +927,12 @@ class ObjectSurface(PathOp.ObjectOp): P.purgeTouched() self.tempGroup.addObject(P) ofstVal = self._calculateOffsetValue(obj, isHole, isVoid=True) - avdOfstShp = self._extractFaceOffset(obj, avoid, ofstVal) + avdOfstShp = self._extractFaceOffset(avoid, ofstVal) if avdOfstShp is False: PathLog.error('Failed to create collective offset avoid face.') cont = False - if cont is True: + if cont: avdShp = avdOfstShp if obj.AvoidLastX_InternalFeatures is False and len(intFEAT) > 0: @@ -930,7 +941,7 @@ class ObjectSurface(PathOp.ObjectOp): else: ifc = intFEAT[0] ofstVal = self._calculateOffsetValue(obj, isHole=True) - ifOfstShp = self._extractFaceOffset(obj, ifc, ofstVal) + ifOfstShp = self._extractFaceOffset(ifc, ofstVal) if ifOfstShp is False: PathLog.error('Failed to create collective offset avoid internal features.') else: @@ -999,7 +1010,7 @@ class ObjectSurface(PathOp.ObjectOp): cont = False time.sleep(0.2) - if cont is True: + if cont: csFaceShape = self._getShapeSlice(baseEnv) if csFaceShape is False: PathLog.debug('_getShapeSlice(baseEnv) failed') @@ -1014,7 +1025,7 @@ class ObjectSurface(PathOp.ObjectOp): if cont is True and obj.ProfileEdges != 'None': PathLog.debug(' -Attempting profile geometry for model base.') ofstVal = self._calculateOffsetValue(obj, isHole) - psOfst = self._extractFaceOffset(obj, csFaceShape, ofstVal) + psOfst = self._extractFaceOffset(csFaceShape, ofstVal) if psOfst is not False: if obj.ProfileEdges == 'Only': return (True, psOfst) @@ -1023,9 +1034,9 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.error(' -Failed to create profile geometry.') cont = False - if cont is True: + if cont: ofstVal = self._calculateOffsetValue(obj, isHole) - faceOffsetShape = self._extractFaceOffset(obj, csFaceShape, ofstVal) + faceOffsetShape = self._extractFaceOffset(csFaceShape, ofstVal) if faceOffsetShape is False: PathLog.error('_extractFaceOffset() failed.') else: @@ -1076,7 +1087,7 @@ class ObjectSurface(PathOp.ObjectOp): WIRES.append((eArea, F.Wires[0], raised)) cont = False - if cont is True: + if cont: PathLog.debug(' -cont is True') # If only one wire and not checkEdges, return first wire if lenWrs == 1: @@ -1126,7 +1137,7 @@ class ObjectSurface(PathOp.ObjectOp): return offset - def _extractFaceOffset(self, obj, fcShape, offset): + def _extractFaceOffset(self, fcShape, offset): '''_extractFaceOffset(fcShape, offset) ... internal function. Original _buildPathArea() version copied from PathAreaOp.py module. This version is modified. Adjustments made based on notes by @sliptonic at this webpage: https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.''' @@ -1151,10 +1162,6 @@ class ObjectSurface(PathOp.ObjectOp): area.add(fcShape) area.setParams(**areaParams) # set parameters - # Save parameters for debugging - # obj.AreaParams = str(area.getParams()) - # PathLog.debug("Area with params: {}".format(area.getParams())) - offsetShape = area.getShape() wCnt = len(offsetShape.Wires) if wCnt == 0: @@ -1420,26 +1427,15 @@ class ObjectSurface(PathOp.ObjectOp): if self.modelSTLs[m] is True: stl = ocl.STLSurf() - if obj.Algorithm == 'OCL Dropcutter': - for f in mesh.Facets: - p = f.Points[0] - q = f.Points[1] - r = f.Points[2] - t = ocl.Triangle(ocl.Point(p[0], p[1], p[2]), - ocl.Point(q[0], q[1], q[2]), - ocl.Point(r[0], r[1], r[2])) - stl.addTriangle(t) - self.modelSTLs[m] = stl - elif obj.Algorithm == 'OCL Waterline': - for f in mesh.Facets: - p = f.Points[0] - q = f.Points[1] - r = f.Points[2] - t = ocl.Triangle(ocl.Point(p[0], p[1], p[2] + obj.DepthOffset.Value), - ocl.Point(q[0], q[1], q[2] + obj.DepthOffset.Value), - ocl.Point(r[0], r[1], r[2] + obj.DepthOffset.Value)) - stl.addTriangle(t) - self.modelSTLs[m] = stl + for f in mesh.Facets: + p = f.Points[0] + q = f.Points[1] + r = f.Points[2] + t = ocl.Triangle(ocl.Point(p[0], p[1], p[2]), + ocl.Point(q[0], q[1], q[2]), + ocl.Point(r[0], r[1], r[2])) + stl.addTriangle(t) + self.modelSTLs[m] = stl return def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes): @@ -1479,7 +1475,7 @@ class ObjectSurface(PathOp.ObjectOp): except Exception as eee: PathLog.error(str(eee)) - if cont is True: + if cont: stckWst = JOB.Stock.Shape.cut(envBB) if obj.BoundaryAdjustment > 0.0: cmpndFS = Part.makeCompound(faceShapes) @@ -1543,7 +1539,7 @@ class ObjectSurface(PathOp.ObjectOp): def _processCutAreas(self, JOB, obj, mdlIdx, FCS, VDS): '''_processCutAreas(JOB, obj, mdlIdx, FCS, VDS)... This method applies any avoided faces or regions to the selected faces. - It then calls the correct scan method depending on the Algorithm and ScanType properties.''' + It then calls the correct scan method depending on the ScanType property.''' PathLog.debug('_processCutAreas()') final = list() @@ -1561,10 +1557,7 @@ class ObjectSurface(PathOp.ObjectOp): else: COMP = ADD - if obj.Algorithm == 'OCL Waterline': - final.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - final.extend(self._waterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline - elif obj.ScanType == 'Planar': + if obj.ScanType == 'Planar': final.extend(self._processPlanarOp(JOB, obj, mdlIdx, COMP, 0)) elif obj.ScanType == 'Rotational': final.extend(self._processRotationalOp(obj, base, COMP)) @@ -1585,10 +1578,7 @@ class ObjectSurface(PathOp.ObjectOp): else: COMP = ADD - if obj.Algorithm == 'OCL Waterline': - final.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - final.extend(self._waterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline - elif obj.ScanType == 'Planar': + if obj.ScanType == 'Planar': final.extend(self._processPlanarOp(JOB, obj, mdlIdx, COMP, fsi)) elif obj.ScanType == 'Rotational': final.extend(self._processRotationalOp(JOB, obj, mdlIdx, COMP)) @@ -3550,347 +3540,7 @@ class ObjectSurface(PathOp.ObjectOp): return output - # Main waterline functions - def _waterlineOp(self, JOB, obj, mdlIdx, subShp=None): - '''_waterlineOp(obj, base) ... Main waterline function to perform waterline extraction from model.''' - commands = [] - t_begin = time.time() - # JOB = PathUtils.findParentJob(obj) - base = JOB.Model.Group[mdlIdx] - bb = self.boundBoxes[mdlIdx] - stl = self.modelSTLs[mdlIdx] - - # Prepare global holdpoint and layerEndPnt containers - if self.holdPoint is None: - self.holdPoint = ocl.Point(float("inf"), float("inf"), float("inf")) - if self.layerEndPnt is None: - self.layerEndPnt = ocl.Point(float("inf"), float("inf"), float("inf")) - - # Set extra offset to diameter of cutter to allow cutter to move around perimeter of model - # Need to make DropCutterExtraOffset available for waterline algorithm - # cdeoX = obj.DropCutterExtraOffset.x - # cdeoY = obj.DropCutterExtraOffset.y - toolDiam = self.cutter.getDiameter() - cdeoX = 0.6 * toolDiam - cdeoY = 0.6 * toolDiam - - if subShp is None: - # Get correct boundbox - if obj.BoundBox == 'Stock': - BS = JOB.Stock - bb = BS.Shape.BoundBox - elif obj.BoundBox == 'BaseBoundBox': - BS = base - bb = base.Shape.BoundBox - - env = PathUtils.getEnvelope(partshape=BS.Shape, depthparams=self.depthParams) # Produces .Shape - - xmin = bb.XMin - xmax = bb.XMax - ymin = bb.YMin - ymax = bb.YMax - zmin = bb.ZMin - zmax = bb.ZMax - else: - xmin = subShp.BoundBox.XMin - xmax = subShp.BoundBox.XMax - ymin = subShp.BoundBox.YMin - ymax = subShp.BoundBox.YMax - zmin = subShp.BoundBox.ZMin - zmax = subShp.BoundBox.ZMax - - smplInt = obj.SampleInterval.Value - minSampInt = 0.001 # value is mm - if smplInt < minSampInt: - smplInt = minSampInt - - # Determine bounding box length for the OCL scan - bbLength = math.fabs(ymax - ymin) - numScanLines = int(math.ceil(bbLength / smplInt) + 1) # Number of lines - - # Compute number and size of stepdowns, and final depth - if obj.LayerMode == 'Single-pass': - depthparams = [obj.FinalDepth.Value] - else: - depthparams = [dp for dp in self.depthParams] - lenDP = len(depthparams) - - # Prepare PathDropCutter objects with STL data - safePDC = self._planarGetPDC(self.safeSTLs[mdlIdx], - depthparams[lenDP - 1], obj.SampleInterval.Value, useSafeCutter=False) - - # Scan the piece to depth at smplInt - oclScan = [] - oclScan = self._waterlineDropCutScan(stl, smplInt, xmin, xmax, ymin, depthparams[lenDP - 1], numScanLines) - # oclScan = SCANS - lenOS = len(oclScan) - ptPrLn = int(lenOS / numScanLines) - - # Convert oclScan list of points to multi-dimensional list - scanLines = [] - for L in range(0, numScanLines): - scanLines.append([]) - for P in range(0, ptPrLn): - pi = L * ptPrLn + P - scanLines[L].append(oclScan[pi]) - lenSL = len(scanLines) - pntsPerLine = len(scanLines[0]) - PathLog.debug("--OCL scan: " + str(lenSL * pntsPerLine) + " points, with " + str(numScanLines) + " lines and " + str(pntsPerLine) + " pts/line") - - # Extract Wl layers per depthparams - lyr = 0 - cmds = [] - layTime = time.time() - self.topoMap = [] - for layDep in depthparams: - cmds = self._getWaterline(obj, scanLines, layDep, lyr, lenSL, pntsPerLine) - commands.extend(cmds) - lyr += 1 - PathLog.debug("--All layer scans combined took " + str(time.time() - layTime) + " s") - return commands - - def _waterlineDropCutScan(self, stl, smplInt, xmin, xmax, ymin, fd, numScanLines): - '''_waterlineDropCutScan(stl, smplInt, xmin, xmax, ymin, fd, numScanLines) ... - Perform OCL scan for waterline purpose.''' - pdc = ocl.PathDropCutter() # create a pdc - pdc.setSTL(stl) - pdc.setCutter(self.cutter) - pdc.setZ(fd) # set minimumZ (final / target depth value) - pdc.setSampling(smplInt) - - # Create line object as path - path = ocl.Path() # create an empty path object - for nSL in range(0, numScanLines): - yVal = ymin + (nSL * smplInt) - p1 = ocl.Point(xmin, yVal, fd) # start-point of line - p2 = ocl.Point(xmax, yVal, fd) # end-point of line - path.append(ocl.Line(p1, p2)) - # path.append(l) # add the line to the path - pdc.setPath(path) - pdc.run() # run drop-cutter on the path - - # return the list the points - return pdc.getCLPoints() - - def _getWaterline(self, obj, scanLines, layDep, lyr, lenSL, pntsPerLine): - '''_getWaterline(obj, scanLines, layDep, lyr, lenSL, pntsPerLine) ... Get waterline.''' - commands = [] - cmds = [] - loopList = [] - self.topoMap = [] - # Create topo map from scanLines (highs and lows) - self.topoMap = self._createTopoMap(scanLines, layDep, lenSL, pntsPerLine) - # Add buffer lines and columns to topo map - self._bufferTopoMap(lenSL, pntsPerLine) - # Identify layer waterline from OCL scan - self._highlightWaterline(4, 9) - # Extract waterline and convert to gcode - loopList = self._extractWaterlines(obj, scanLines, lyr, layDep) - # save commands - for loop in loopList: - cmds = self._loopToGcode(obj, layDep, loop) - commands.extend(cmds) - return commands - - def _createTopoMap(self, scanLines, layDep, lenSL, pntsPerLine): - '''_createTopoMap(scanLines, layDep, lenSL, pntsPerLine) ... Create topo map version of OCL scan data.''' - topoMap = [] - for L in range(0, lenSL): - topoMap.append([]) - for P in range(0, pntsPerLine): - if scanLines[L][P].z > layDep: - topoMap[L].append(2) - else: - topoMap[L].append(0) - return topoMap - - def _bufferTopoMap(self, lenSL, pntsPerLine): - '''_bufferTopoMap(lenSL, pntsPerLine) ... Add buffer boarder of zeros to all sides to topoMap data.''' - pre = [0, 0] - post = [0, 0] - for p in range(0, pntsPerLine): - pre.append(0) - post.append(0) - for l in range(0, lenSL): - self.topoMap[l].insert(0, 0) - self.topoMap[l].append(0) - self.topoMap.insert(0, pre) - self.topoMap.append(post) - return True - - def _highlightWaterline(self, extraMaterial, insCorn): - '''_highlightWaterline(extraMaterial, insCorn) ... Highlight the waterline data, separating from extra material.''' - TM = self.topoMap - lastPnt = len(TM[1]) - 1 - lastLn = len(TM) - 1 - highFlag = 0 - - # ("--Convert parallel data to ridges") - for lin in range(1, lastLn): - for pt in range(1, lastPnt): # Ignore first and last points - if TM[lin][pt] == 0: - if TM[lin][pt + 1] == 2: # step up - TM[lin][pt] = 1 - if TM[lin][pt - 1] == 2: # step down - TM[lin][pt] = 1 - - # ("--Convert perpendicular data to ridges and highlight ridges") - for pt in range(1, lastPnt): # Ignore first and last points - for lin in range(1, lastLn): - if TM[lin][pt] == 0: - highFlag = 0 - if TM[lin + 1][pt] == 2: # step up - TM[lin][pt] = 1 - if TM[lin - 1][pt] == 2: # step down - TM[lin][pt] = 1 - elif TM[lin][pt] == 2: - highFlag += 1 - if highFlag == 3: - if TM[lin - 1][pt - 1] < 2 or TM[lin - 1][pt + 1] < 2: - highFlag = 2 - else: - TM[lin - 1][pt] = extraMaterial - highFlag = 2 - - # ("--Square corners") - for pt in range(1, lastPnt): - for lin in range(1, lastLn): - if TM[lin][pt] == 1: # point == 1 - cont = True - if TM[lin + 1][pt] == 0: # forward == 0 - if TM[lin + 1][pt - 1] == 1: # forward left == 1 - if TM[lin][pt - 1] == 2: # left == 2 - TM[lin + 1][pt] = 1 # square the corner - cont = False - - if cont is True and TM[lin + 1][pt + 1] == 1: # forward right == 1 - if TM[lin][pt + 1] == 2: # right == 2 - TM[lin + 1][pt] = 1 # square the corner - cont = True - - if TM[lin - 1][pt] == 0: # back == 0 - if TM[lin - 1][pt - 1] == 1: # back left == 1 - if TM[lin][pt - 1] == 2: # left == 2 - TM[lin - 1][pt] = 1 # square the corner - cont = False - - if cont is True and TM[lin - 1][pt + 1] == 1: # back right == 1 - if TM[lin][pt + 1] == 2: # right == 2 - TM[lin - 1][pt] = 1 # square the corner - - # remove inside corners - for pt in range(1, lastPnt): - for lin in range(1, lastLn): - if TM[lin][pt] == 1: # point == 1 - if TM[lin][pt + 1] == 1: - if TM[lin - 1][pt + 1] == 1 or TM[lin + 1][pt + 1] == 1: - TM[lin][pt + 1] = insCorn - elif TM[lin][pt - 1] == 1: - if TM[lin - 1][pt - 1] == 1 or TM[lin + 1][pt - 1] == 1: - TM[lin][pt - 1] = insCorn - - return True - - def _extractWaterlines(self, obj, oclScan, lyr, layDep): - '''_extractWaterlines(obj, oclScan, lyr, layDep) ... Extract water lines from OCL scan data.''' - srch = True - lastPnt = len(self.topoMap[0]) - 1 - lastLn = len(self.topoMap) - 1 - maxSrchs = 5 - srchCnt = 1 - loopList = [] - loop = [] - loopNum = 0 - - if self.CutClimb is True: - lC = [-1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0] - pC = [-1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1] - else: - lC = [1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0] - pC = [-1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1] - - while srch is True: - srch = False - if srchCnt > maxSrchs: - PathLog.debug("Max search scans, " + str(maxSrchs) + " reached\nPossible incomplete waterline result!") - break - for L in range(1, lastLn): - for P in range(1, lastPnt): - if self.topoMap[L][P] == 1: - # start loop follow - srch = True - loopNum += 1 - loop = self._trackLoop(oclScan, lC, pC, L, P, loopNum) - self.topoMap[L][P] = 0 # Mute the starting point - loopList.append(loop) - srchCnt += 1 - PathLog.debug("Search count for layer " + str(lyr) + " is " + str(srchCnt) + ", with " + str(loopNum) + " loops.") - return loopList - - def _trackLoop(self, oclScan, lC, pC, L, P, loopNum): - '''_trackLoop(oclScan, lC, pC, L, P, loopNum) ... Track the loop direction.''' - loop = [oclScan[L - 1][P - 1]] # Start loop point list - cur = [L, P, 1] - prv = [L, P - 1, 1] - nxt = [L, P + 1, 1] - follow = True - ptc = 0 - ptLmt = 200000 - while follow is True: - ptc += 1 - if ptc > ptLmt: - PathLog.debug("Loop number " + str(loopNum) + " at [" + str(nxt[0]) + ", " + str(nxt[1]) + "] pnt count exceeds, " + str(ptLmt) + ". Stopped following loop.") - break - nxt = self._findNextWlPoint(lC, pC, cur[0], cur[1], prv[0], prv[1]) # get next point - loop.append(oclScan[nxt[0] - 1][nxt[1] - 1]) # add it to loop point list - self.topoMap[nxt[0]][nxt[1]] = nxt[2] # Mute the point, if not Y stem - if nxt[0] == L and nxt[1] == P: # check if loop complete - follow = False - elif nxt[0] == cur[0] and nxt[1] == cur[1]: # check if line cannot be detected - follow = False - prv = cur - cur = nxt - return loop - - def _findNextWlPoint(self, lC, pC, cl, cp, pl, pp): - '''_findNextWlPoint(lC, pC, cl, cp, pl, pp) ... - Find the next waterline point in the point cloud layer provided.''' - dl = cl - pl - dp = cp - pp - num = 0 - i = 3 - s = 0 - mtch = 0 - found = False - while mtch < 8: # check all 8 points around current point - if lC[i] == dl: - if pC[i] == dp: - s = i - 3 - found = True - # Check for y branch where current point is connection between branches - for y in range(1, mtch): - if lC[i + y] == dl: - if pC[i + y] == dp: - num = 1 - break - break - i += 1 - mtch += 1 - if found is False: - # ("_findNext: No start point found.") - return [cl, cp, num] - - for r in range(0, 8): - l = cl + lC[s + r] - p = cp + pC[s + r] - if self.topoMap[l][p] == 1: - return [l, p, num] - - # ("_findNext: No next pnt found") - return [cl, cp, num] - - def _loopToGcode(self, obj, layDep, loop): '''_loopToGcode(obj, layDep, loop) ... Convert set of loop points to Gcode.''' # generate the path commands output = [] @@ -3971,54 +3621,6 @@ class ObjectSurface(PathOp.ObjectOp): cmds.append(Path.Command('G0', {'Z': p2.z, 'F': self.vertFeed})) # drop cutter down to current Z depth, returning to normal cut path and speed return cmds - def holdStopEndCmds(self, obj, p2, txt): - '''holdStopEndCmds(obj, p2, txt) ... Gcode commands to be executed at end of hold stop.''' - cmds = [] - msg = 'N (' + txt + ')' - cmds.append(Path.Command(msg, {})) # Raise cutter rapid to zMax in line of travel - cmds.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) # Raise cutter rapid to zMax in line of travel - # cmds.append(Path.Command('G0', {'X': p2.x, 'Y': p2.y, 'F': self.horizRapid})) # horizontal rapid to current XY coordinate - return cmds - - def subsectionCLP(self, CLP, xmin, ymin, xmax, ymax): - '''subsectionCLP(CLP, xmin, ymin, xmax, ymax) ... - This function returns a subsection of the CLP scan, limited to the min/max values supplied.''' - section = list() - lenCLP = len(CLP) - for i in range(0, lenCLP): - if CLP[i].x < xmax: - if CLP[i].y < ymax: - if CLP[i].x > xmin: - if CLP[i].y > ymin: - section.append(CLP[i]) - return section - - def getMaxHeightBetweenPoints(self, finalDepth, p1, p2, cutter, CLP): - ''' getMaxHeightBetweenPoints(finalDepth, p1, p2, cutter, CLP) ... - This function connects two HOLD points with line. - Each point within the subsection point list is tested to determinie if it is under cutter. - Points determined to be under the cutter on line are tested for z height. - The highest z point is the requirement for clearance between p1 and p2, and returned as zMax with 2 mm extra. - ''' - dx = (p2.x - p1.x) - if dx == 0.0: - dx = 0.00001 # Need to employ a global tolerance here - m = (p2.y - p1.y) / dx - b = p1.y - (m * p1.x) - - avoidTool = round(cutter * 0.75, 1) # 1/2 diam. of cutter is theoretically safe, but 3/4 diam is used for extra clearance - zMax = finalDepth - lenCLP = len(CLP) - for i in range(0, lenCLP): - mSqrd = m**2 - if mSqrd < 0.0000001: # Need to employ a global tolerance here - mSqrd = 0.0000001 - perpDist = math.sqrt((CLP[i].y - (m * CLP[i].x) - b)**2 / (1 + 1 / (mSqrd))) - if perpDist < avoidTool: # if point within cutter reach on line of travel, test z height and update as needed - if CLP[i].z > zMax: - zMax = CLP[i].z - return zMax + 2.0 - def resetOpVariables(self, all=True): '''resetOpVariables() ... Reset class variables used for instance of operation.''' self.holdPoint = None @@ -4131,31 +3733,6 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.error('Unable to set OCL cutter.') return False - def determineVectDirect(self, pnt, nxt, travVect): - if nxt.x == pnt.x: - travVect.x = 0 - elif nxt.x < pnt.x: - travVect.x = -1 - else: - travVect.x = 1 - - if nxt.y == pnt.y: - travVect.y = 0 - elif nxt.y < pnt.y: - travVect.y = -1 - else: - travVect.y = 1 - return travVect - - def determineLineOfTravel(self, travVect): - if travVect.x == 0 and travVect.y != 0: - lineOfTravel = "Y" - elif travVect.y == 0 and travVect.x != 0: - lineOfTravel = "X" - else: - lineOfTravel = "O" # used for turns - return lineOfTravel - def _getMinSafeTravelHeight(self, pdc, p1, p2, minDep=None): A = (p1.x, p1.y) B = (p2.x, p2.y) @@ -4173,7 +3750,6 @@ class ObjectSurface(PathOp.ObjectOp): def SetupProperties(): ''' SetupProperties() ... Return list of properties required for operation.''' setup = [] - setup.append('Algorithm') setup.append('AvoidLastX_Faces') setup.append('AvoidLastX_InternalFeatures') setup.append('BoundBox') @@ -4208,12 +3784,7 @@ def SetupProperties(): setup.append('AngularDeflection') setup.append('LinearDeflection') # For debugging - setup.append('AreaParams') setup.append('ShowTempObjects') - # Targeted for possible removal - setup.append('IgnoreWaste') - setup.append('IgnoreWasteDepth') - setup.append('ReleaseFromWaste') return setup diff --git a/src/Mod/Path/PathScripts/PathWaterline.py b/src/Mod/Path/PathScripts/PathWaterline.py index db2c2229bd..06c960c8c3 100644 --- a/src/Mod/Path/PathScripts/PathWaterline.py +++ b/src/Mod/Path/PathScripts/PathWaterline.py @@ -23,7 +23,7 @@ # *************************************************************************** # * * # * Additional modifications and contributions beginning 2019 * -# * by Russell Johnson 2020-03-15 10:55 CST * +# * by Russell Johnson 2020-03-23 16:15 CST * # * * # *************************************************************************** @@ -45,7 +45,7 @@ import Draft if FreeCAD.GuiUp: import FreeCADGui -__title__ = "Path Surface Operation" +__title__ = "Path Waterline Operation" __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" __doc__ = "Class and implementation of Mill Facing operation." @@ -64,12 +64,12 @@ try: import ocl except ImportError: FreeCAD.Console.PrintError( - translate("Path_Surface", "This operation requires OpenCamLib to be installed.") + "\n") + translate("Path_Waterline", "This operation requires OpenCamLib to be installed.") + "\n") import sys - sys.exit(translate("Path_Surface", "This operation requires OpenCamLib to be installed.")) + sys.exit(translate("Path_Waterline", "This operation requires OpenCamLib to be installed.")) -class ObjectSurface(PathOp.ObjectOp): +class ObjectWaterline(PathOp.ObjectOp): '''Proxy object for Surfacing operation.''' def baseObject(self): @@ -82,122 +82,148 @@ class ObjectSurface(PathOp.ObjectOp): return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureCoolant | PathOp.FeatureBaseFaces def initOperation(self, obj): - '''initPocketOp(obj) ... create facing specific properties''' - obj.addProperty("App::PropertyEnumeration", "BoundBox", "Waterline", QtCore.QT_TRANSLATE_NOOP("App::Property", "Should the operation be limited by the stock object or by the bounding box of the base object")) - obj.addProperty("App::PropertyEnumeration", "LayerMode", "Waterline", QtCore.QT_TRANSLATE_NOOP("App::Property", "The completion mode for the operation: single or multi-pass")) - obj.addProperty("App::PropertyEnumeration", "ScanType", "Waterline", QtCore.QT_TRANSLATE_NOOP("App::Property", "Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan.")) - - obj.addProperty("App::PropertyFloat", "CutterTilt", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")) - obj.addProperty("App::PropertyEnumeration", "RotationAxis", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "The model will be rotated around this axis.")) - obj.addProperty("App::PropertyFloat", "StartIndex", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Start index(angle) for rotational scan")) - obj.addProperty("App::PropertyFloat", "StopIndex", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")) - - obj.addProperty("App::PropertyInteger", "AvoidLastX_Faces", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.")) - obj.addProperty("App::PropertyBool", "AvoidLastX_InternalFeatures", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces.")) - obj.addProperty("App::PropertyDistance", "BoundaryAdjustment", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.")) - obj.addProperty("App::PropertyBool", "BoundaryEnforcement", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).")) - obj.addProperty("App::PropertyDistance", "DepthOffset", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Z-axis offset from the surface of the object")) - obj.addProperty("App::PropertyEnumeration", "HandleMultipleFeatures", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features.")) - obj.addProperty("App::PropertyDistance", "InternalFeaturesAdjustment", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.")) - obj.addProperty("App::PropertyBool", "InternalFeaturesCut", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face.")) - obj.addProperty("App::PropertyEnumeration", "ProfileEdges", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")) - obj.addProperty("App::PropertyDistance", "SampleInterval", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "The Sample Interval. Small values cause long wait times")) - obj.addProperty("App::PropertyPercent", "StepOver", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path")) - - obj.addProperty("App::PropertyVectorDistance", "CircularCenterCustom", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path")) - obj.addProperty("App::PropertyEnumeration", "CircularCenterAt", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "Choose what point to start the ciruclar pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom.")) - obj.addProperty("App::PropertyEnumeration", "CutMode", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part: Climb(ClockWise) or Conventional(CounterClockWise)")) - obj.addProperty("App::PropertyEnumeration", "CutPattern", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use")) - obj.addProperty("App::PropertyFloat", "CutPatternAngle", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")) - obj.addProperty("App::PropertyBool", "CutPatternReversed", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the order of the step-overs will be reversed; the operation will begin cutting the outer most line/arc, and work toward the inner most line/arc.")) - - obj.addProperty("App::PropertyBool", "OptimizeLinearPaths", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.")) - obj.addProperty("App::PropertyBool", "OptimizeStepOverTransitions", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.")) - obj.addProperty("App::PropertyBool", "CircularUseG2G3", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.")) - obj.addProperty("App::PropertyDistance", "GapThreshold", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.")) - obj.addProperty("App::PropertyString", "GapSizes", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")) - - obj.addProperty("App::PropertyBool", "IgnoreWaste", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore areas that proceed below specified depth.")) - obj.addProperty("App::PropertyFloat", "IgnoreWasteDepth", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Depth used to identify waste areas to ignore.")) - obj.addProperty("App::PropertyBool", "ReleaseFromWaste", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Cut through waste to depth at model edge, releasing the model.")) - - obj.addProperty("App::PropertyVectorDistance", "StartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path")) - obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make True, if specifying a Start Point")) + '''initPocketOp(obj) ... + Initialize the operation - property creation and property editor status.''' + self.initOpProperties(obj) # For debugging - obj.addProperty('App::PropertyString', 'AreaParams', 'Debugging') - obj.setEditorMode('AreaParams', 2) # hide - obj.addProperty("App::PropertyBool", "ShowTempObjects", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")) if PathLog.getLevel(PathLog.thisModule()) != 4: obj.setEditorMode('ShowTempObjects', 2) # hide - obj.BoundBox = ['BaseBoundBox', 'Stock'] - obj.CircularCenterAt = ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'] - obj.CutMode = ['Conventional', 'Climb'] - obj.CutPattern = ['Line', 'ZigZag', 'Circular', 'CircularZigZag'] # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] - obj.HandleMultipleFeatures = ['Collectively', 'Individually'] - obj.LayerMode = ['Single-pass', 'Multi-pass'] - obj.ProfileEdges = ['None', 'Only', 'First', 'Last'] - obj.RotationAxis = ['X', 'Y'] - obj.ScanType = ['Planar', 'Rotational'] - if not hasattr(obj, 'DoNotSetDefaultValues'): self.setEditorProperties(obj) + def initOpProperties(self, obj): + '''initOpProperties(obj) ... create operation specific properties''' + + PROPS = [ + ("App::PropertyBool", "ShowTempObjects", "Debug", + QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")), + + ("App::PropertyDistance", "AngularDeflection", "Mesh Conversion", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values increase processing time a lot.")), + ("App::PropertyDistance", "LinearDeflection", "Mesh Conversion", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values do not increase processing time much.")), + + ("App::PropertyInteger", "AvoidLastX_Faces", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.")), + ("App::PropertyBool", "AvoidLastX_InternalFeatures", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces.")), + ("App::PropertyDistance", "BoundaryAdjustment", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.")), + ("App::PropertyBool", "BoundaryEnforcement", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).")), + ("App::PropertyEnumeration", "HandleMultipleFeatures", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features.")), + ("App::PropertyDistance", "InternalFeaturesAdjustment", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.")), + ("App::PropertyBool", "InternalFeaturesCut", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face.")), + + ("App::PropertyEnumeration", "Algorithm", "Waterline", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Select the algorithm to use: OCL Dropcutter*, or Experimental.")), + ("App::PropertyEnumeration", "BoundBox", "Waterline", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Should the operation be limited by the stock object or by the bounding box of the base object")), + ("App::PropertyDistance", "SampleInterval", "Waterline", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The Sample Interval. Small values cause long wait times")), + + ("App::PropertyVectorDistance", "CircularCenterCustom", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")), + ("App::PropertyEnumeration", "CircularCenterAt", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose what point to start the ciruclar pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom.")), + ("App::PropertyEnumeration", "CutMode", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part: Climb(ClockWise) or Conventional(CounterClockWise)")), + ("App::PropertyEnumeration", "CutPattern", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use")), + ("App::PropertyFloat", "CutPatternAngle", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")), + ("App::PropertyBool", "CutPatternReversed", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the order of the step-overs will be reversed; the operation will begin cutting the outer most line/arc, and work toward the inner most line/arc.")), + ("App::PropertyDistance", "DepthOffset", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Z-axis offset from the surface of the object")), + ("App::PropertyEnumeration", "LayerMode", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The completion mode for the operation: single or multi-pass")), + ("App::PropertyEnumeration", "ProfileEdges", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")), + ("App::PropertyPercent", "StepOver", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path")), + + ("App::PropertyBool", "OptimizeLinearPaths", "Waterline Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.")), + ("App::PropertyBool", "OptimizeStepOverTransitions", "Waterline Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.")), + ("App::PropertyBool", "CircularUseG2G3", "Waterline Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.")), + ("App::PropertyDistance", "GapThreshold", "Waterline Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.")), + ("App::PropertyString", "GapSizes", "Waterline Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")), + + ("App::PropertyVectorDistance", "StartPoint", "Start Point", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")), + ("App::PropertyBool", "UseStartPoint", "Start Point", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Make True, if specifying a Start Point")) + ] + + missing = list() + for (prtyp, nm, grp, tt) in PROPS: + if not hasattr(obj, nm): + obj.addProperty(prtyp, nm, grp, tt) + missing.append(nm) + + # Set enumeration lists for enumeration properties + if len(missing) > 0: + ENUMS = self._propertyEnumerations() + for n in ENUMS: + if n in missing: + cmdStr = 'obj.{}={}'.format(n, ENUMS[n]) + exec(cmdStr) + self.addedAllProperties = True + def _propertyEnumerations(self): + # Enumeration lists for App::PropertyEnumeration properties + return { + 'Algorithm': ['OCL Dropcutter', 'Experimental'], + 'BoundBox': ['BaseBoundBox', 'Stock'], + 'CircularCenterAt': ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'], + 'CutMode': ['Conventional', 'Climb'], + 'CutPattern': ['Line', 'ZigZag', 'Circular', 'CircularZigZag'], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] + 'HandleMultipleFeatures': ['Collectively', 'Individually'], + 'LayerMode': ['Single-pass', 'Multi-pass'], + 'ProfileEdges': ['None', 'Only', 'First', 'Last'], + } + def setEditorProperties(self, obj): # Used to hide inputs in properties list - - ''' - obj.setEditorMode('CutPattern', 0) - obj.setEditorMode('HandleMultipleFeatures', 0) - obj.setEditorMode('CircularCenterAt', 0) - obj.setEditorMode('CircularCenterCustom', 0) - obj.setEditorMode('CutPatternAngle', 0) - # obj.setEditorMode('BoundaryEnforcement', 0) - - if obj.ScanType == 'Planar': - obj.setEditorMode('RotationAxis', 2) # 2=hidden - obj.setEditorMode('StartIndex', 2) - obj.setEditorMode('StopIndex', 2) - obj.setEditorMode('CutterTilt', 2) - if obj.CutPattern == 'Circular' or obj.CutPattern == 'CircularZigZag': - obj.setEditorMode('CutPatternAngle', 2) - else: # if obj.CutPattern == 'Line' or obj.CutPattern == 'ZigZag': - obj.setEditorMode('CircularCenterAt', 2) - obj.setEditorMode('CircularCenterCustom', 2) - elif obj.ScanType == 'Rotational': - obj.setEditorMode('RotationAxis', 0) # 0=show & editable - obj.setEditorMode('StartIndex', 0) - obj.setEditorMode('StopIndex', 0) - obj.setEditorMode('CutterTilt', 0) - ''' - - obj.setEditorMode('HandleMultipleFeatures', 2) - obj.setEditorMode('CutPattern', 2) - obj.setEditorMode('CutPatternAngle', 2) - # obj.setEditorMode('BoundaryEnforcement', 2) - - # Disable IgnoreWaste feature - obj.setEditorMode('IgnoreWaste', 2) - obj.setEditorMode('IgnoreWasteDepth', 2) - obj.setEditorMode('ReleaseFromWaste', 2) + show = 0 + hide = 2 + obj.setEditorMode('BoundaryEnforcement', hide) + obj.setEditorMode('ProfileEdges', hide) + obj.setEditorMode('InternalFeaturesAdjustment', hide) + obj.setEditorMode('InternalFeaturesCut', hide) + # if obj.CutPattern in ['Line', 'ZigZag']: + if obj.CutPattern in ['Circular', 'CircularZigZag']: + show = 2 # hide + hide = 0 # show + obj.setEditorMode('CutPatternAngle', show) + obj.setEditorMode('CircularCenterAt', hide) + obj.setEditorMode('CircularCenterCustom', hide) def onChanged(self, obj, prop): if hasattr(self, 'addedAllProperties'): if self.addedAllProperties is True: - if prop == 'ScanType': - self.setEditorProperties(obj) if prop == 'CutPattern': self.setEditorProperties(obj) def opOnDocumentRestored(self, obj): + self.initOpProperties(obj) + if PathLog.getLevel(PathLog.thisModule()) != 4: obj.setEditorMode('ShowTempObjects', 2) # hide else: obj.setEditorMode('ShowTempObjects', 0) # show - self.addedAllProperties = True + self.setEditorProperties(obj) def opSetDefaultValues(self, obj, job): @@ -205,8 +231,6 @@ class ObjectSurface(PathOp.ObjectOp): job = PathUtils.findParentJob(obj) obj.OptimizeLinearPaths = True - obj.IgnoreWaste = False - obj.ReleaseFromWaste = False obj.InternalFeaturesCut = True obj.OptimizeStepOverTransitions = False obj.CircularUseG2G3 = False @@ -217,21 +241,16 @@ class ObjectSurface(PathOp.ObjectOp): obj.StartPoint.x = 0.0 obj.StartPoint.y = 0.0 obj.StartPoint.z = obj.ClearanceHeight.Value + obj.Algorithm = 'OCL Dropcutter' obj.ProfileEdges = 'None' obj.LayerMode = 'Single-pass' - obj.ScanType = 'Planar' - obj.RotationAxis = 'X' obj.CutMode = 'Conventional' obj.CutPattern = 'Line' obj.HandleMultipleFeatures = 'Collectively' # 'Individually' obj.CircularCenterAt = 'CenterOfMass' # 'CenterOfBoundBox', 'XminYmin', 'Custom' - obj.AreaParams = '' obj.GapSizes = 'No gaps identified.' obj.StepOver = 100 obj.CutPatternAngle = 0.0 - obj.CutterTilt = 0.0 - obj.StartIndex = 0.0 - obj.StopIndex = 360.0 obj.SampleInterval.Value = 1.0 obj.BoundaryAdjustment.Value = 0.0 obj.InternalFeaturesAdjustment.Value = 0.0 @@ -266,39 +285,21 @@ class ObjectSurface(PathOp.ObjectOp): def opApplyPropertyLimits(self, obj): '''opApplyPropertyLimits(obj) ... Apply necessary limits to user input property values before performing main operation.''' - # Limit start index - if obj.StartIndex < 0.0: - obj.StartIndex = 0.0 - if obj.StartIndex > 360.0: - obj.StartIndex = 360.0 - - # Limit stop index - if obj.StopIndex > 360.0: - obj.StopIndex = 360.0 - if obj.StopIndex < 0.0: - obj.StopIndex = 0.0 - - # Limit cutter tilt - if obj.CutterTilt < -90.0: - obj.CutterTilt = -90.0 - if obj.CutterTilt > 90.0: - obj.CutterTilt = 90.0 - # Limit sample interval if obj.SampleInterval.Value < 0.001: obj.SampleInterval.Value = 0.001 - PathLog.error(translate('PathSurface', 'Sample interval limits are 0.001 to 25.4 millimeters.')) + PathLog.error(translate('PathWaterline', 'Sample interval limits are 0.001 to 25.4 millimeters.')) if obj.SampleInterval.Value > 25.4: obj.SampleInterval.Value = 25.4 - PathLog.error(translate('PathSurface', 'Sample interval limits are 0.001 to 25.4 millimeters.')) + PathLog.error(translate('PathWaterline', 'Sample interval limits are 0.001 to 25.4 millimeters.')) # Limit cut pattern angle if obj.CutPatternAngle < -360.0: obj.CutPatternAngle = 0.0 - PathLog.error(translate('PathSurface', 'Cut pattern angle limits are +-360 degrees.')) + PathLog.error(translate('PathWaterline', 'Cut pattern angle limits are +-360 degrees.')) if obj.CutPatternAngle >= 360.0: obj.CutPatternAngle = 0.0 - PathLog.error(translate('PathSurface', 'Cut pattern angle limits are +- 360 degrees.')) + PathLog.error(translate('PathWaterline', 'Cut pattern angle limits are +- 360 degrees.')) # Limit StepOver to natural number percentage if obj.StepOver > 100: @@ -309,10 +310,10 @@ class ObjectSurface(PathOp.ObjectOp): # Limit AvoidLastX_Faces to zero and positive values if obj.AvoidLastX_Faces < 0: obj.AvoidLastX_Faces = 0 - PathLog.error(translate('PathSurface', 'AvoidLastX_Faces: Only zero or positive values permitted.')) + PathLog.error(translate('PathWaterline', 'AvoidLastX_Faces: Only zero or positive values permitted.')) if obj.AvoidLastX_Faces > 100: obj.AvoidLastX_Faces = 100 - PathLog.error(translate('PathSurface', 'AvoidLastX_Faces: Avoid last X faces count limited to 100.')) + PathLog.error(translate('PathWaterline', 'AvoidLastX_Faces: Avoid last X faces count limited to 100.')) def opExecute(self, obj): '''opExecute(obj) ... process surface operation''' @@ -345,16 +346,13 @@ class ObjectSurface(PathOp.ObjectOp): self.showDebugObjects = False # mark beginning of operation and identify parent Job - PathLog.info('\nBegin 3D Surface operation...') + PathLog.info('\nBegin Waterline operation...') startTime = time.time() - # Disable(ignore) ReleaseFromWaste option(input) - obj.ReleaseFromWaste = False - # Identify parent Job JOB = PathUtils.findParentJob(obj) if JOB is None: - PathLog.error(translate('PathSurface', "No JOB")) + PathLog.error(translate('PathWaterline', "No JOB")) return self.stockZMin = JOB.Stock.Shape.BoundBox.ZMin @@ -390,7 +388,7 @@ class ObjectSurface(PathOp.ObjectOp): # Create temporary group for temporary objects, removing existing # if self.showDebugObjects is True: - tempGroupName = 'tempPathSurfaceGroup' + tempGroupName = 'tempPathWaterlineGroup' if FCAD.getObject(tempGroupName): for to in FCAD.getObject(tempGroupName).Group: FCAD.removeObject(to.Name) @@ -410,7 +408,7 @@ class ObjectSurface(PathOp.ObjectOp): self.cutter = self.setOclCutter(obj) self.safeCutter = self.setOclCutter(obj, safe=True) if self.cutter is False or self.safeCutter is False: - PathLog.error(translate('PathSurface', "Canceling 3D Surface operation. Error creating OCL cutter.")) + PathLog.error(translate('PathWaterline', "Canceling Waterline operation. Error creating OCL cutter.")) return toolDiam = self.cutter.getDiameter() self.cutOut = (toolDiam * (float(obj.StepOver) / 100.0)) @@ -463,18 +461,6 @@ class ObjectSurface(PathOp.ObjectOp): # ###### MAIN COMMANDS FOR OPERATION ###### - # If algorithm is `Waterline`, force certain property values - ''' - # Save initial value for restoration later. - if obj.Algorithm == 'OCL Waterline': - preCP = obj.CutPattern - preCPA = obj.CutPatternAngle - preRB = obj.BoundaryEnforcement - obj.CutPattern = 'Line' - obj.CutPatternAngle = 0.0 - obj.BoundaryEnforcement = False - ''' - # Begin processing obj.Base data and creating GCode # Process selected faces, if available pPM = self._preProcessModel(JOB, obj) @@ -505,14 +491,6 @@ class ObjectSurface(PathOp.ObjectOp): # Save gcode produced self.commandlist.extend(CMDS) - # If algorithm is `Waterline`, restore initial property values - ''' - if obj.Algorithm == 'OCL Waterline': - obj.CutPattern = preCP - obj.CutPatternAngle = preCPA - obj.BoundaryEnforcement = preRB - ''' - # ###### CLOSING COMMANDS FOR OPERATION ###### # Delete temporary objects @@ -593,8 +571,8 @@ class ObjectSurface(PathOp.ObjectOp): VOIDS = list() fShapes = list() vShapes = list() - preProcEr = translate('PathSurface', 'Error pre-processing Face') - warnFinDep = translate('PathSurface', 'Final Depth might need to be lower. Internal features detected in Face') + preProcEr = translate('PathWaterline', 'Error pre-processing Face') + warnFinDep = translate('PathWaterline', 'Final Depth might need to be lower. Internal features detected in Face') GRP = JOB.Model.Group lenGRP = len(GRP) @@ -936,8 +914,8 @@ class ObjectSurface(PathOp.ObjectOp): outFace = False INTFCS = list() fNum = fcIdx + 1 - # preProcEr = translate('PathSurface', 'Error pre-processing Face') - warnFinDep = translate('PathSurface', 'Final Depth might need to be lower. Internal features detected in Face') + # preProcEr = translate('PathWaterline', 'Error pre-processing Face') + warnFinDep = translate('PathWaterline', 'Final Depth might need to be lower. Internal features detected in Face') PathLog.debug('_getFaceWires() from Face{}'.format(fNum)) WIRES = self._extractWiresFromFace(base, fcshp) @@ -1140,10 +1118,6 @@ class ObjectSurface(PathOp.ObjectOp): area.add(fcShape) area.setParams(**areaParams) # set parameters - # Save parameters for debugging - # obj.AreaParams = str(area.getParams()) - # PathLog.debug("Area with params: {}".format(area.getParams())) - offsetShape = area.getShape() wCnt = len(offsetShape.Wires) if wCnt == 0: @@ -1514,7 +1488,7 @@ class ObjectSurface(PathOp.ObjectOp): def _processCutAreas(self, JOB, obj, mdlIdx, FCS, VDS): '''_processCutAreas(JOB, obj, mdlIdx, FCS, VDS)... This method applies any avoided faces or regions to the selected faces. - It then calls the correct scan method depending on the ScanType property.''' + It then calls the correct method.''' PathLog.debug('_processCutAreas()') final = list() @@ -1533,7 +1507,10 @@ class ObjectSurface(PathOp.ObjectOp): COMP = ADD final.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - final.extend(self._waterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline + if obj.Algorithm == 'OCL Dropcutter': + final.extend(self._oclWaterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline + else: + final.extend(self._experimentalWaterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline elif obj.HandleMultipleFeatures == 'Individually': for fsi in range(0, len(FCS)): @@ -1552,7 +1529,10 @@ class ObjectSurface(PathOp.ObjectOp): COMP = ADD final.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - final.extend(self._waterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline + if obj.Algorithm == 'OCL Dropcutter': + final.extend(self._oclWaterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline + else: + final.extend(self._experimentalWaterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline COMP = None # Eif @@ -1570,8 +1550,8 @@ class ObjectSurface(PathOp.ObjectOp): return pdc # Main waterline functions - def _waterlineOp(self, JOB, obj, mdlIdx, subShp=None): - '''_waterlineOp(obj, base) ... Main waterline function to perform waterline extraction from model.''' + def _oclWaterlineOp(self, JOB, obj, mdlIdx, subShp=None): + '''_oclWaterlineOp(obj, base) ... Main waterline function to perform waterline extraction from model.''' commands = [] t_begin = time.time() @@ -1955,6 +1935,10 @@ class ObjectSurface(PathOp.ObjectOp): return output + def _experimentalWaterlineOp(self, JOB, obj, mdlIdx, subShp=None): + PathLog.error('The `Experimental` algorithm is not available at this time.') + return [] + # Support functions for both dropcutter and waterline operations def isPointOnLine(self, strtPnt, endPnt, pointP): '''isPointOnLine(strtPnt, endPnt, pointP) ... Determine if a given point is on the line defined by start and end points.''' @@ -1987,54 +1971,6 @@ class ObjectSurface(PathOp.ObjectOp): cmds.append(Path.Command('G0', {'Z': p2.z, 'F': self.vertFeed})) # drop cutter down to current Z depth, returning to normal cut path and speed return cmds - def holdStopEndCmds(self, obj, p2, txt): - '''holdStopEndCmds(obj, p2, txt) ... Gcode commands to be executed at end of hold stop.''' - cmds = [] - msg = 'N (' + txt + ')' - cmds.append(Path.Command(msg, {})) # Raise cutter rapid to zMax in line of travel - cmds.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) # Raise cutter rapid to zMax in line of travel - # cmds.append(Path.Command('G0', {'X': p2.x, 'Y': p2.y, 'F': self.horizRapid})) # horizontal rapid to current XY coordinate - return cmds - - def subsectionCLP(self, CLP, xmin, ymin, xmax, ymax): - '''subsectionCLP(CLP, xmin, ymin, xmax, ymax) ... - This function returns a subsection of the CLP scan, limited to the min/max values supplied.''' - section = list() - lenCLP = len(CLP) - for i in range(0, lenCLP): - if CLP[i].x < xmax: - if CLP[i].y < ymax: - if CLP[i].x > xmin: - if CLP[i].y > ymin: - section.append(CLP[i]) - return section - - def getMaxHeightBetweenPoints(self, finalDepth, p1, p2, cutter, CLP): - ''' getMaxHeightBetweenPoints(finalDepth, p1, p2, cutter, CLP) ... - This function connects two HOLD points with line. - Each point within the subsection point list is tested to determinie if it is under cutter. - Points determined to be under the cutter on line are tested for z height. - The highest z point is the requirement for clearance between p1 and p2, and returned as zMax with 2 mm extra. - ''' - dx = (p2.x - p1.x) - if dx == 0.0: - dx = 0.00001 # Need to employ a global tolerance here - m = (p2.y - p1.y) / dx - b = p1.y - (m * p1.x) - - avoidTool = round(cutter * 0.75, 1) # 1/2 diam. of cutter is theoretically safe, but 3/4 diam is used for extra clearance - zMax = finalDepth - lenCLP = len(CLP) - for i in range(0, lenCLP): - mSqrd = m**2 - if mSqrd < 0.0000001: # Need to employ a global tolerance here - mSqrd = 0.0000001 - perpDist = math.sqrt((CLP[i].y - (m * CLP[i].x) - b)**2 / (1 + 1 / (mSqrd))) - if perpDist < avoidTool: # if point within cutter reach on line of travel, test z height and update as needed - if CLP[i].z > zMax: - zMax = CLP[i].z - return zMax + 2.0 - def resetOpVariables(self, all=True): '''resetOpVariables() ... Reset class variables used for instance of operation.''' self.holdPoint = None @@ -2147,31 +2083,6 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.error('Unable to set OCL cutter.') return False - def determineVectDirect(self, pnt, nxt, travVect): - if nxt.x == pnt.x: - travVect.x = 0 - elif nxt.x < pnt.x: - travVect.x = -1 - else: - travVect.x = 1 - - if nxt.y == pnt.y: - travVect.y = 0 - elif nxt.y < pnt.y: - travVect.y = -1 - else: - travVect.y = 1 - return travVect - - def determineLineOfTravel(self, travVect): - if travVect.x == 0 and travVect.y != 0: - lineOfTravel = "Y" - elif travVect.y == 0 and travVect.x != 0: - lineOfTravel = "X" - else: - lineOfTravel = "O" # used for turns - return lineOfTravel - def _getMinSafeTravelHeight(self, pdc, p1, p2, minDep=None): A = (p1.x, p1.y) B = (p2.x, p2.y) @@ -2189,6 +2100,7 @@ class ObjectSurface(PathOp.ObjectOp): def SetupProperties(): ''' SetupProperties() ... Return list of properties required for operation.''' setup = [] + setup.append('Algorithm') setup.append('AvoidLastX_Faces') setup.append('AvoidLastX_InternalFeatures') setup.append('BoundBox') @@ -2202,7 +2114,6 @@ def SetupProperties(): setup.append('CutPattern') setup.append('CutPatternAngle') setup.append('CutPatternReversed') - setup.append('CutterTilt') setup.append('DepthOffset') setup.append('GapSizes') setup.append('GapThreshold') @@ -2211,27 +2122,18 @@ def SetupProperties(): setup.append('OptimizeStepOverTransitions') setup.append('ProfileEdges') setup.append('BoundaryEnforcement') - setup.append('RotationAxis') setup.append('SampleInterval') - setup.append('ScanType') - setup.append('StartIndex') setup.append('StartPoint') setup.append('StepOver') - setup.append('StopIndex') setup.append('UseStartPoint') # For debugging - setup.append('AreaParams') setup.append('ShowTempObjects') - # Targeted for possible removal - setup.append('IgnoreWaste') - setup.append('IgnoreWasteDepth') - setup.append('ReleaseFromWaste') return setup def Create(name, obj=None): - '''Create(name) ... Creates and returns a Surface operation.''' + '''Create(name) ... Creates and returns a Waterline operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectSurface(obj, name) + obj.Proxy = ObjectWaterline(obj, name) return obj From d495ee202dbf1744fa538eb02e09989f92d257d3 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Thu, 26 Mar 2020 16:05:13 -0500 Subject: [PATCH 100/117] Path: Add `ScanType` and `LayerMode` inputs Added combo boxes with labels Path: Remove 'Algorithm' property usage. Path: Connect `ScanType` and `LayerMode' selections from task panel Path: Update GUI inputs for independent 3D Surface operation --- .../Gui/Resources/panels/PageOpSurfaceEdit.ui | 292 ++++++++++-------- src/Mod/Path/PathScripts/PathSurfaceGui.py | 83 +++-- 2 files changed, 217 insertions(+), 158 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui index d019e3fe67..7013dd6e28 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui @@ -6,8 +6,8 @@ 0 0 - 357 - 427 + 350 + 400
@@ -57,126 +57,7 @@ - - - - Algorithm - - - - - - - - OCL Dropcutter - - - - - OCL Waterline - - - - - - - - BoundBox - - - - - - - - Stock - - - - - BaseBoundBox - - - - - - - - BoundBox extra offset X, Y - - - - - - - mm - - - - - - - mm - - - - - - - Drop Cutter Direction - - - - - - - - X - - - - - Y - - - - - - - - Depth offset - - - - - - - mm - - - - - - - Sample interval - - - - - - - mm - - - - - - - Step over - - - - + <html><head/><body><p>The amount by which the tool is laterally displaced on each cycle of the pattern, specified in percent of the tool diameter.</p><p>A step over of 100% results in no overlap between two different cycles.</p></body></html> @@ -195,17 +76,174 @@ - - + + - Optimize output + BoundBox - + + + + Layer Mode + + + + + + + Depth offset + + + + + + + mm + + + + + + + Sample interval + + + + + + + + X + + + + + Y + + + + + + + + Step over + + + + + + + + Planar + + + + + Rotational + + + + + + + + + Single-pass + + + + + Multi-pass + + + + + - Enabled + Optimize Linear Paths + + + + + + + + Stock + + + + + BaseBoundBox + + + + + + + + Optimize StepOver Transitions + + + + + + + mm + + + + + + + Scan Type + + + + + + + + + + 0 + 0 + + + + mm + + + + + + + mm + + + + + + + + + Drop Cutter Direction + + + + + + + BoundBox extra offset X, Y + + + + + + + Use Start Point diff --git a/src/Mod/Path/PathScripts/PathSurfaceGui.py b/src/Mod/Path/PathScripts/PathSurfaceGui.py index 40feff2c70..f11bdd4864 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceGui.py +++ b/src/Mod/Path/PathScripts/PathSurfaceGui.py @@ -45,83 +45,104 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): def getFields(self, obj): '''getFields(obj) ... transfers values from UI to obj's proprties''' + self.updateToolController(obj, self.form.toolController) + self.updateCoolant(obj, self.form.coolantController) + PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset) PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) - if obj.StepOver != self.form.stepOver.value(): - obj.StepOver = self.form.stepOver.value() - - if obj.Algorithm != str(self.form.algorithmSelect.currentText()): - obj.Algorithm = str(self.form.algorithmSelect.currentText()) - if obj.BoundBox != str(self.form.boundBoxSelect.currentText()): obj.BoundBox = str(self.form.boundBoxSelect.currentText()) - if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentText()): - obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentText()) + if obj.ScanType != str(self.form.scanType.currentText()): + obj.ScanType = str(self.form.scanType.currentText()) + + if obj.StepOver != self.form.stepOver.value(): + obj.StepOver = self.form.stepOver.value() + + if obj.LayerMode != str(self.form.layerMode.currentText()): + obj.LayerMode = str(self.form.layerMode.currentText()) obj.DropCutterExtraOffset.x = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetX.text()).Value obj.DropCutterExtraOffset.y = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetY.text()).Value + if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentText()): + obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentText()) + + PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset) + PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) + + if obj.UseStartPoint != self.form.useStartPoint.isChecked(): + obj.UseStartPoint = self.form.useStartPoint.isChecked() + if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked(): obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked() - self.updateToolController(obj, self.form.toolController) - self.updateCoolant(obj, self.form.coolantController) + if obj.OptimizeStepOverTransitions != self.form.optimizeStepOverTransitions.isChecked(): + obj.OptimizeStepOverTransitions = self.form.optimizeStepOverTransitions.isChecked() def setFields(self, obj): '''setFields(obj) ... transfers obj's property values to UI''' - self.selectInComboBox(obj.Algorithm, self.form.algorithmSelect) + self.setupToolController(obj, self.form.toolController) + self.setupCoolant(obj, self.form.coolantController) self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect) - self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect) - + self.selectInComboBox(obj.ScanType, self.form.scanType) + self.selectInComboBox(obj.LayerMode, self.form.layerMode) self.form.boundBoxExtraOffsetX.setText(str(obj.DropCutterExtraOffset.x)) self.form.boundBoxExtraOffsetY.setText(str(obj.DropCutterExtraOffset.y)) + self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect) self.form.depthOffset.setText(FreeCAD.Units.Quantity(obj.DepthOffset.Value, FreeCAD.Units.Length).UserString) - self.form.sampleInterval.setText(str(obj.SampleInterval)) self.form.stepOver.setValue(obj.StepOver) + self.form.sampleInterval.setText(str(obj.SampleInterval)) + + if obj.UseStartPoint: + self.form.useStartPoint.setCheckState(QtCore.Qt.Checked) + else: + self.form.useStartPoint.setCheckState(QtCore.Qt.Unchecked) if obj.OptimizeLinearPaths: self.form.optimizeEnabled.setCheckState(QtCore.Qt.Checked) else: self.form.optimizeEnabled.setCheckState(QtCore.Qt.Unchecked) - self.setupToolController(obj, self.form.toolController) - self.setupCoolant(obj, self.form.coolantController) + if obj.OptimizeStepOverTransitions: + self.form.optimizeStepOverTransitions.setCheckState(QtCore.Qt.Checked) + else: + self.form.optimizeStepOverTransitions.setCheckState(QtCore.Qt.Unchecked) + def getSignalsForUpdate(self, obj): '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' signals = [] signals.append(self.form.toolController.currentIndexChanged) - signals.append(self.form.algorithmSelect.currentIndexChanged) + signals.append(self.form.coolantController.currentIndexChanged) signals.append(self.form.boundBoxSelect.currentIndexChanged) - signals.append(self.form.dropCutterDirSelect.currentIndexChanged) + signals.append(self.form.scanType.currentIndexChanged) + signals.append(self.form.layerMode.currentIndexChanged) signals.append(self.form.boundBoxExtraOffsetX.editingFinished) signals.append(self.form.boundBoxExtraOffsetY.editingFinished) - signals.append(self.form.sampleInterval.editingFinished) - signals.append(self.form.stepOver.editingFinished) + signals.append(self.form.dropCutterDirSelect.currentIndexChanged) signals.append(self.form.depthOffset.editingFinished) + signals.append(self.form.stepOver.editingFinished) + signals.append(self.form.sampleInterval.editingFinished) + signals.append(self.form.useStartPoint.stateChanged) signals.append(self.form.optimizeEnabled.stateChanged) - signals.append(self.form.coolantController.currentIndexChanged) + signals.append(self.form.optimizeStepOverTransitions.stateChanged) return signals def updateVisibility(self): - if self.form.algorithmSelect.currentText() == "OCL Dropcutter": - self.form.boundBoxExtraOffsetX.setEnabled(True) - self.form.boundBoxExtraOffsetY.setEnabled(True) - self.form.boundBoxSelect.setEnabled(True) - self.form.dropCutterDirSelect.setEnabled(True) - self.form.stepOver.setEnabled(True) - else: + if self.form.scanType.currentText() == "Planar": self.form.boundBoxExtraOffsetX.setEnabled(False) self.form.boundBoxExtraOffsetY.setEnabled(False) - self.form.boundBoxSelect.setEnabled(False) self.form.dropCutterDirSelect.setEnabled(False) - self.form.stepOver.setEnabled(False) + else: + self.form.boundBoxExtraOffsetX.setEnabled(True) + self.form.boundBoxExtraOffsetY.setEnabled(True) + self.form.dropCutterDirSelect.setEnabled(True) def registerSignalHandlers(self, obj): - self.form.algorithmSelect.currentIndexChanged.connect(self.updateVisibility) + self.form.scanType.currentIndexChanged.connect(self.updateVisibility) Command = PathOpGui.SetupOperation('Surface', From 33636396819bc6023524cfcee45bf7073873e398 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Thu, 26 Mar 2020 16:05:36 -0500 Subject: [PATCH 101/117] Path: Update GUI inputs for independent Waterline operation --- .../Resources/panels/PageOpWaterlineEdit.ui | 229 +++++++++++------- src/Mod/Path/PathScripts/PathWaterlineGui.py | 52 +++- 2 files changed, 185 insertions(+), 96 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui index e9c94ac535..5e0edef1c9 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui @@ -6,7 +6,7 @@ 0 0 - 400 + 350 400 @@ -43,37 +43,100 @@ - - - - mm - - - - - - - Cut Pattern - - - - - - - Sample interval - - - - - - - + - 9 + 8 + + + Stock + + + + + BaseBoundBox + + + + + + + + + 0 + 0 + + + + + + + + + 8 + + + + + Single-pass + + + + + Multi-pass + + + + + + + + + OCL Dropcutter + + + + + Experimental + + + + + + + + Optimize Linear Paths + + + + + + + + 0 + 0 + + + + Boundary Adjustment + + + + + + + + 8 + + + + + None + + Line @@ -96,62 +159,14 @@ - - - - Depth offset - - - - - - - - 9 - - - - - Stock - - - - - BaseBoundBox - - - - - - - - mm - - - - - - Optimize Linear Paths - - - - - - - BoundBox - - - - - - - Boundary Adjustment - - - - + + + 0 + 0 + + <html><head/><body><p>The amount by which the tool is laterally displaced on each cycle of the pattern, specified in percent of the tool diameter.</p><p>A step over of 100% results in no overlap between two different cycles.</p></body></html> @@ -169,13 +184,61 @@ - - + + + + Layer Mode + + + + + + + + 0 + 0 + + + + BoundBox + + + + + Step over + + + + mm + + + + + + + Cut Pattern + + + + + + + Sample interval + + + + + + + Algorithm + + + diff --git a/src/Mod/Path/PathScripts/PathWaterlineGui.py b/src/Mod/Path/PathScripts/PathWaterlineGui.py index ece048ecee..7a631efd11 100644 --- a/src/Mod/Path/PathScripts/PathWaterlineGui.py +++ b/src/Mod/Path/PathScripts/PathWaterlineGui.py @@ -46,24 +46,37 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): def getFields(self, obj): '''getFields(obj) ... transfers values from UI to obj's proprties''' - PathGui.updateInputField(obj, 'BoundaryAdjustment', self.form.boundaryAdjustment) - PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) + self.updateToolController(obj, self.form.toolController) - if obj.StepOver != self.form.stepOver.value(): - obj.StepOver = self.form.stepOver.value() + if obj.Algorithm != str(self.form.algorithmSelect.currentText()): + obj.Algorithm = str(self.form.algorithmSelect.currentText()) if obj.BoundBox != str(self.form.boundBoxSelect.currentText()): obj.BoundBox = str(self.form.boundBoxSelect.currentText()) + if obj.LayerMode != str(self.form.layerMode.currentText()): + obj.LayerMode = str(self.form.layerMode.currentText()) + + if obj.CutPattern != str(self.form.cutPattern.currentText()): + obj.CutPattern = str(self.form.cutPattern.currentText()) + + PathGui.updateInputField(obj, 'BoundaryAdjustment', self.form.boundaryAdjustment) + + if obj.StepOver != self.form.stepOver.value(): + obj.StepOver = self.form.stepOver.value() + + PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) + if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked(): obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked() - self.updateToolController(obj, self.form.toolController) - def setFields(self, obj): '''setFields(obj) ... transfers obj's property values to UI''' self.setupToolController(obj, self.form.toolController) + self.selectInComboBox(obj.Algorithm, self.form.algorithmSelect) self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect) + self.selectInComboBox(obj.LayerMode, self.form.layerMode) + self.selectInComboBox(obj.CutPattern, self.form.cutPattern) self.form.boundaryAdjustment.setText(FreeCAD.Units.Quantity(obj.BoundaryAdjustment.Value, FreeCAD.Units.Length).UserString) self.form.stepOver.setValue(obj.StepOver) self.form.sampleInterval.setText(str(obj.SampleInterval)) @@ -77,7 +90,10 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' signals = [] signals.append(self.form.toolController.currentIndexChanged) + signals.append(self.form.algorithmSelect.currentIndexChanged) signals.append(self.form.boundBoxSelect.currentIndexChanged) + signals.append(self.form.layerMode.currentIndexChanged) + signals.append(self.form.cutPattern.currentIndexChanged) signals.append(self.form.boundaryAdjustment.editingFinished) signals.append(self.form.stepOver.editingFinished) signals.append(self.form.sampleInterval.editingFinished) @@ -86,15 +102,25 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): return signals def updateVisibility(self): - self.form.boundBoxSelect.setEnabled(True) - self.form.boundaryAdjustment.setEnabled(True) - self.form.stepOver.setEnabled(True) - self.form.sampleInterval.setEnabled(True) - self.form.optimizeEnabled.setEnabled(True) + if self.form.algorithmSelect.currentText() == 'OCL Dropcutter': + self.form.cutPattern.setEnabled(False) + self.form.boundaryAdjustment.setEnabled(False) + self.form.stepOver.setEnabled(False) + self.form.sampleInterval.setEnabled(True) + self.form.optimizeEnabled.setEnabled(True) + else: + self.form.cutPattern.setEnabled(True) + self.form.boundaryAdjustment.setEnabled(True) + if self.form.cutPattern.currentText() == 'None': + self.form.stepOver.setEnabled(False) + else: + self.form.stepOver.setEnabled(True) + self.form.sampleInterval.setEnabled(False) + self.form.optimizeEnabled.setEnabled(False) def registerSignalHandlers(self, obj): - # self.form.clearLayers.currentIndexChanged.connect(self.updateVisibility) - pass + self.form.algorithmSelect.currentIndexChanged.connect(self.updateVisibility) + self.form.cutPattern.currentIndexChanged.connect(self.updateVisibility) Command = PathOpGui.SetupOperation('Waterline', From 3720b57d63586eb2a9be9f816962298a08de760f Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Thu, 26 Mar 2020 18:07:02 -0500 Subject: [PATCH 102/117] Path: Add `initPage()` call to update GUI upon loading Added initPage() function call updates task panel values and visibilities when user edits an existing operation. --- src/Mod/Path/PathScripts/PathSurfaceGui.py | 5 ++++- src/Mod/Path/PathScripts/PathWaterlineGui.py | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathSurfaceGui.py b/src/Mod/Path/PathScripts/PathSurfaceGui.py index f11bdd4864..5709c0341e 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceGui.py +++ b/src/Mod/Path/PathScripts/PathSurfaceGui.py @@ -39,6 +39,10 @@ __doc__ = "Surface operation page controller and command implementation." class TaskPanelOpPage(PathOpGui.TaskPanelPage): '''Page controller class for the Surface operation.''' + def initPage(self, obj): + self.setTitle("3D Surface") + self.updateVisibility() + def getForm(self): '''getForm() ... returns UI''' return FreeCADGui.PySideUic.loadUi(":/panels/PageOpSurfaceEdit.ui") @@ -110,7 +114,6 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): else: self.form.optimizeStepOverTransitions.setCheckState(QtCore.Qt.Unchecked) - def getSignalsForUpdate(self, obj): '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' signals = [] diff --git a/src/Mod/Path/PathScripts/PathWaterlineGui.py b/src/Mod/Path/PathScripts/PathWaterlineGui.py index 7a631efd11..cf72300d46 100644 --- a/src/Mod/Path/PathScripts/PathWaterlineGui.py +++ b/src/Mod/Path/PathScripts/PathWaterlineGui.py @@ -40,6 +40,10 @@ __doc__ = "Waterline operation page controller and command implementation." class TaskPanelOpPage(PathOpGui.TaskPanelPage): '''Page controller class for the Waterline operation.''' + def initPage(self, obj): + # self.setTitle("Waterline") + self.updateVisibility() + def getForm(self): '''getForm() ... returns UI''' return FreeCADGui.PySideUic.loadUi(":/panels/PageOpWaterlineEdit.ui") From 11db9553d4ce5c805e59998557604a6a4c73507b Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Fri, 27 Mar 2020 14:14:36 -0500 Subject: [PATCH 103/117] Path: Add `Cut Pattern` selection to task panel --- .../Gui/Resources/panels/PageOpSurfaceEdit.ui | 251 ++++++++++-------- src/Mod/Path/PathScripts/PathSurfaceGui.py | 7 + 2 files changed, 148 insertions(+), 110 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui index 7013dd6e28..e4aaf19e5e 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui @@ -24,7 +24,7 @@ - + ToolController @@ -38,7 +38,7 @@ - + Coolant Mode @@ -57,81 +57,6 @@ - - - - <html><head/><body><p>The amount by which the tool is laterally displaced on each cycle of the pattern, specified in percent of the tool diameter.</p><p>A step over of 100% results in no overlap between two different cycles.</p></body></html> - - - 1 - - - 100 - - - 10 - - - 100 - - - - - - - BoundBox - - - - - - - Layer Mode - - - - - - - Depth offset - - - - - - - mm - - - - - - - Sample interval - - - - - - - - X - - - - - Y - - - - - - - - Step over - - - @@ -160,38 +85,71 @@ - + + + + <html><head/><body><p>The amount by which the tool is laterally displaced on each cycle of the pattern, specified in percent of the tool diameter.</p><p>A step over of 100% results in no overlap between two different cycles.</p></body></html> + + + 1 + + + 100 + + + 10 + + + 100 + + + + + + + Step over + + + + + + + Sample interval + + + + + + + Layer Mode + + + + Optimize Linear Paths - - - - - Stock - - - - - BaseBoundBox - - - - - - + + - Optimize StepOver Transitions + Drop Cutter Direction - - - - mm + + + + BoundBox extra offset X, Y + + + + + + + Use Start Point @@ -202,7 +160,21 @@ - + + + + BoundBox + + + + + + + mm + + + + @@ -226,25 +198,84 @@ - - + + + + mm + + + + + - Drop Cutter Direction + Depth offset + + + + + + + + X + + + + + Y + + + + + + + + + Stock + + + + + BaseBoundBox + + + + + + + + Optimize StepOver Transitions - + - BoundBox extra offset X, Y + Cut Pattern - - - - Use Start Point - + + + + + Line + + + + + ZigZag + + + + + Circular + + + + + CircularZigZag + + diff --git a/src/Mod/Path/PathScripts/PathSurfaceGui.py b/src/Mod/Path/PathScripts/PathSurfaceGui.py index 5709c0341e..1eaca56e4f 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceGui.py +++ b/src/Mod/Path/PathScripts/PathSurfaceGui.py @@ -67,6 +67,9 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): if obj.LayerMode != str(self.form.layerMode.currentText()): obj.LayerMode = str(self.form.layerMode.currentText()) + if obj.CutPattern != str(self.form.cutPattern.currentText()): + obj.CutPattern = str(self.form.cutPattern.currentText()) + obj.DropCutterExtraOffset.x = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetX.text()).Value obj.DropCutterExtraOffset.y = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetY.text()).Value @@ -92,6 +95,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect) self.selectInComboBox(obj.ScanType, self.form.scanType) self.selectInComboBox(obj.LayerMode, self.form.layerMode) + self.selectInComboBox(obj.CutPattern, self.form.cutPattern) self.form.boundBoxExtraOffsetX.setText(str(obj.DropCutterExtraOffset.x)) self.form.boundBoxExtraOffsetY.setText(str(obj.DropCutterExtraOffset.y)) self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect) @@ -122,6 +126,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): signals.append(self.form.boundBoxSelect.currentIndexChanged) signals.append(self.form.scanType.currentIndexChanged) signals.append(self.form.layerMode.currentIndexChanged) + signals.append(self.form.cutPattern.currentIndexChanged) signals.append(self.form.boundBoxExtraOffsetX.editingFinished) signals.append(self.form.boundBoxExtraOffsetY.editingFinished) signals.append(self.form.dropCutterDirSelect.currentIndexChanged) @@ -136,10 +141,12 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): def updateVisibility(self): if self.form.scanType.currentText() == "Planar": + self.form.cutPattern.setEnabled(True) self.form.boundBoxExtraOffsetX.setEnabled(False) self.form.boundBoxExtraOffsetY.setEnabled(False) self.form.dropCutterDirSelect.setEnabled(False) else: + self.form.cutPattern.setEnabled(False) self.form.boundBoxExtraOffsetX.setEnabled(True) self.form.boundBoxExtraOffsetY.setEnabled(True) self.form.dropCutterDirSelect.setEnabled(True) From a907585f0d9f621ee0509cc45012dfe158116bd0 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 29 Mar 2020 23:32:22 -0500 Subject: [PATCH 104/117] Path: Four bug fixes per forum discussion Fix grammar mistake. Fix Gcode comment formatting. Fix application of `Optimize Step Over Transitions` by adjusting the offset tolerance for creating the offset cut area for the operation. Fix math error in circular based cut patterns for small diameter cutters. Identification of bugs begins in the forum at https://forum.freecadweb.org/viewtopic.php?style=3&f=15&t=41997&start=210. Path: Lower SampleInterval minimum to 0.0001mm --- src/Mod/Path/PathScripts/PathSurface.py | 42 +++++++++++++++---------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 89932a104a..1d8c695925 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -100,9 +100,9 @@ class ObjectSurface(PathOp.ObjectOp): QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")), ("App::PropertyDistance", "AngularDeflection", "Mesh Conversion", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values increase processing time a lot.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate mesh. Smaller values increase processing time a lot.")), ("App::PropertyDistance", "LinearDeflection", "Mesh Conversion", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values do not increase processing time much.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate mesh. Smaller values do not increase processing time much.")), ("App::PropertyFloat", "CutterTilt", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")), @@ -333,8 +333,8 @@ class ObjectSurface(PathOp.ObjectOp): obj.CutterTilt = 90.0 # Limit sample interval - if obj.SampleInterval.Value < 0.001: - obj.SampleInterval.Value = 0.001 + if obj.SampleInterval.Value < 0.0001: + obj.SampleInterval.Value = 0.0001 PathLog.error(translate('PathSurface', 'Sample interval limits are 0.001 to 25.4 millimeters.')) if obj.SampleInterval.Value > 25.4: obj.SampleInterval.Value = 25.4 @@ -416,12 +416,12 @@ class ObjectSurface(PathOp.ObjectOp): # ... and move cutter to clearance height and startpoint output = '' if obj.Comment != '': - output += '(' + str(obj.Comment) + ')\n' - output += '(' + obj.Label + ')\n' - output += '(Tool type: ' + str(obj.ToolController.Tool.ToolType) + ')\n' - output += '(Compensated Tool Path. Diameter: ' + str(obj.ToolController.Tool.Diameter) + ')\n' - output += '(Sample interval: ' + str(obj.SampleInterval.Value) + ')\n' - output += '(Step over %: ' + str(obj.StepOver) + ')\n' + self.commandlist.append(Path.Command('N ({})'.format(str(obj.Comment)), {})) + self.commandlist.append(Path.Command('N ({})'.format(obj.Label), {})) + self.commandlist.append(Path.Command('N (Tool type: {})'.format(str(obj.ToolController.Tool.ToolType)), {})) + self.commandlist.append(Path.Command('N (Compensated Tool Path. Diameter: {})'.format(str(obj.ToolController.Tool.Diameter)), {})) + self.commandlist.append(Path.Command('N (Sample interval: {})'.format(str(obj.SampleInterval.Value)), {})) + self.commandlist.append(Path.Command('N (Step over %: {})'.format(str(obj.StepOver)), {})) self.commandlist.append(Path.Command('N ({})'.format(output), {})) self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) if obj.UseStartPoint is True: @@ -1123,17 +1123,17 @@ class ObjectSurface(PathOp.ObjectOp): if isVoid is False: if isHole is True: offset = -1 * obj.InternalFeaturesAdjustment.Value - offset += self.radius # (self.radius + (tolrnc / 10.0)) + offset += self.radius + (tolrnc / 10.0) else: offset = -1 * obj.BoundaryAdjustment.Value if obj.BoundaryEnforcement is True: - offset += self.radius # (self.radius + (tolrnc / 10.0)) + offset += self.radius + (tolrnc / 10.0) else: - offset -= self.radius # (self.radius + (tolrnc / 10.0)) + offset -= self.radius + (tolrnc / 10.0) offset = 0.0 - offset else: offset = -1 * obj.BoundaryAdjustment.Value - offset += self.radius # (self.radius + (tolrnc / 10.0)) + offset += self.radius + (tolrnc / 10.0) return offset @@ -2356,7 +2356,11 @@ class ObjectSurface(PathOp.ObjectOp): p1 = FreeCAD.Vector(v1.X, v1.Y, v1.Z) sp = (v1.X, v1.Y, 0.0) rad = p1.sub(COM).Length - tolrncAng = math.asin(space/rad) + spcRadRatio = space/rad + if spcRadRatio < 1.0: + tolrncAng = math.asin(spcRadRatio) + else: + tolrncAng = 0.999998 * math.pi X = COM.x + (rad * math.cos(tolrncAng)) Y = v1.Y - space # rad * math.sin(tolrncAng) @@ -2392,8 +2396,12 @@ class ObjectSurface(PathOp.ObjectOp): # Pop connected edge index values from arc segments index list iEi = EI.index(iE) iSi = EI.index(iS) - EI.pop(iEi) - EI.pop(iSi) + if iEi > iSi: + EI.pop(iEi) + EI.pop(iSi) + else: + EI.pop(iSi) + EI.pop(iEi) if len(EI) > 0: PRTS.append('BRK') chkGap = True From 5d0329498099d15775e526cbe3c0ae1f6d0fef1e Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Fri, 27 Mar 2020 18:49:34 -0500 Subject: [PATCH 105/117] Path: Fix "increase by factor of 10" issue Forum discussion at https://forum.freecadweb.org/viewtopic.php?style=3&f=15&t=44486. --- src/Mod/Path/PathScripts/PathSurfaceGui.py | 6 +++--- src/Mod/Path/PathScripts/PathWaterlineGui.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSurfaceGui.py b/src/Mod/Path/PathScripts/PathSurfaceGui.py index 1eaca56e4f..41f11f6007 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceGui.py +++ b/src/Mod/Path/PathScripts/PathSurfaceGui.py @@ -96,12 +96,12 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.selectInComboBox(obj.ScanType, self.form.scanType) self.selectInComboBox(obj.LayerMode, self.form.layerMode) self.selectInComboBox(obj.CutPattern, self.form.cutPattern) - self.form.boundBoxExtraOffsetX.setText(str(obj.DropCutterExtraOffset.x)) - self.form.boundBoxExtraOffsetY.setText(str(obj.DropCutterExtraOffset.y)) + self.form.boundBoxExtraOffsetX.setText(FreeCAD.Units.Quantity(obj.DropCutterExtraOffset.x, FreeCAD.Units.Length).UserString) + self.form.boundBoxExtraOffsetY.setText(FreeCAD.Units.Quantity(obj.DropCutterExtraOffset.y, FreeCAD.Units.Length).UserString) self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect) self.form.depthOffset.setText(FreeCAD.Units.Quantity(obj.DepthOffset.Value, FreeCAD.Units.Length).UserString) self.form.stepOver.setValue(obj.StepOver) - self.form.sampleInterval.setText(str(obj.SampleInterval)) + self.form.sampleInterval.setText(FreeCAD.Units.Quantity(obj.SampleInterval.Value, FreeCAD.Units.Length).UserString) if obj.UseStartPoint: self.form.useStartPoint.setCheckState(QtCore.Qt.Checked) diff --git a/src/Mod/Path/PathScripts/PathWaterlineGui.py b/src/Mod/Path/PathScripts/PathWaterlineGui.py index cf72300d46..eed15fc3d3 100644 --- a/src/Mod/Path/PathScripts/PathWaterlineGui.py +++ b/src/Mod/Path/PathScripts/PathWaterlineGui.py @@ -83,7 +83,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.selectInComboBox(obj.CutPattern, self.form.cutPattern) self.form.boundaryAdjustment.setText(FreeCAD.Units.Quantity(obj.BoundaryAdjustment.Value, FreeCAD.Units.Length).UserString) self.form.stepOver.setValue(obj.StepOver) - self.form.sampleInterval.setText(str(obj.SampleInterval)) + self.form.sampleInterval.setText(FreeCAD.Units.Quantity(obj.SampleInterval.Value, FreeCAD.Units.Length).UserString) if obj.OptimizeLinearPaths: self.form.optimizeEnabled.setCheckState(QtCore.Qt.Checked) From 7d4b2f2a501e8c6ff223835274a3c0e1dadcee92 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 28 Mar 2020 21:51:19 -0500 Subject: [PATCH 106/117] Path: Fix bug that fails `Multi-pass` usage --- src/Mod/Path/PathScripts/PathSurface.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 1d8c695925..a485225402 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -2643,6 +2643,7 @@ class ObjectSurface(PathOp.ObjectOp): # Process each layer in depthparams prvLyrFirst = None prvLyrLast = None + lastPrvStpLast = None actvLyrs = 0 for lyr in range(0, lenDP): odd = True # ZigZag directional switch @@ -2651,8 +2652,12 @@ class ObjectSurface(PathOp.ObjectOp): actvSteps = 0 LYR = list() prvStpFirst = None + if lyr > 0: + if prvStpLast is not None: + lastPrvStpLast = prvStpLast prvStpLast = None lyrDep = depthparams[lyr] + PathLog.debug('Multi-pass lyrDep: {}'.format(round(lyrDep, 4))) # Cycle through step-over sections (line segments or arcs) for so in range(0, len(SCANDATA)): @@ -2693,6 +2698,7 @@ class ObjectSurface(PathOp.ObjectOp): # Manage step over transition and CircularZigZag direction if so > 0: + # PathLog.debug(' stepover index: {}'.format(so)) # Control ZigZag direction if obj.CutPattern == 'CircularZigZag': if odd is True: @@ -2700,6 +2706,8 @@ class ObjectSurface(PathOp.ObjectOp): else: odd = True # Control step over transition + if prvStpLast is None: + prvStpLast = lastPrvStpLast minTrnsHght = self._getMinSafeTravelHeight(safePDC, prvStpLast, first, minDep=None) # Check safe travel height against fullSTL transCmds.append(Path.Command('N (--Step {} transition)'.format(so), {})) transCmds.extend(self._stepTransitionCmds(obj, prvStpLast, first, minTrnsHght, tolrnc)) @@ -2712,6 +2720,7 @@ class ObjectSurface(PathOp.ObjectOp): for i in range(0, lenAdjPrts): prt = ADJPRTS[i] lenPrt = len(prt) + # PathLog.debug(' adj parts index - lenPrt: {} - {}'.format(i, lenPrt)) if prt == 'BRK' and prtsHasCmds is True: nxtStart = ADJPRTS[i + 1][0] minSTH = self._getMinSafeTravelHeight(safePDC, last, nxtStart, minDep=None) # Check safe travel height against fullSTL From 6644d08191c7a5be205bb945319d09dc649baea3 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Mon, 30 Mar 2020 22:34:49 -0500 Subject: [PATCH 107/117] Path: Implement experimental Waterline algorithm Insert experimental Waterline algorithm that is non-OCL based. It slices by step-down value, not stepover. --- src/Mod/Path/PathScripts/PathWaterline.py | 1572 +++++++++++++++++++-- 1 file changed, 1473 insertions(+), 99 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathWaterline.py b/src/Mod/Path/PathScripts/PathWaterline.py index 06c960c8c3..b2d3fdc197 100644 --- a/src/Mod/Path/PathScripts/PathWaterline.py +++ b/src/Mod/Path/PathScripts/PathWaterline.py @@ -2,7 +2,8 @@ # *************************************************************************** # * * -# * Copyright (c) 2016 sliptonic * +# * Copyright (c) 2019 Russell Johnson (russ4262) * +# * Copyright (c) 2019 sliptonic * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -23,7 +24,7 @@ # *************************************************************************** # * * # * Additional modifications and contributions beginning 2019 * -# * by Russell Johnson 2020-03-23 16:15 CST * +# * by Russell Johnson 2020-03-30 22:27 CST * # * * # *************************************************************************** @@ -46,7 +47,7 @@ if FreeCAD.GuiUp: import FreeCADGui __title__ = "Path Waterline Operation" -__author__ = "sliptonic (Brad Collette)" +__author__ = "russ4262 (Russell Johnson), sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" __doc__ = "Class and implementation of Mill Facing operation." @@ -95,7 +96,6 @@ class ObjectWaterline(PathOp.ObjectOp): def initOpProperties(self, obj): '''initOpProperties(obj) ... create operation specific properties''' - PROPS = [ ("App::PropertyBool", "ShowTempObjects", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")), @@ -105,58 +105,57 @@ class ObjectWaterline(PathOp.ObjectOp): ("App::PropertyDistance", "LinearDeflection", "Mesh Conversion", QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values do not increase processing time much.")), - ("App::PropertyInteger", "AvoidLastX_Faces", "Selected Face(s) Settings", + ("App::PropertyInteger", "AvoidLastX_Faces", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.")), - ("App::PropertyBool", "AvoidLastX_InternalFeatures", "Selected Face(s) Settings", + ("App::PropertyBool", "AvoidLastX_InternalFeatures", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces.")), - ("App::PropertyDistance", "BoundaryAdjustment", "Selected Face(s) Settings", + ("App::PropertyDistance", "BoundaryAdjustment", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.")), - ("App::PropertyBool", "BoundaryEnforcement", "Selected Face(s) Settings", + ("App::PropertyBool", "BoundaryEnforcement", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).")), - ("App::PropertyEnumeration", "HandleMultipleFeatures", "Selected Face(s) Settings", + ("App::PropertyEnumeration", "HandleMultipleFeatures", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features.")), - ("App::PropertyDistance", "InternalFeaturesAdjustment", "Selected Face(s) Settings", + ("App::PropertyDistance", "InternalFeaturesAdjustment", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.")), - ("App::PropertyBool", "InternalFeaturesCut", "Selected Face(s) Settings", + ("App::PropertyBool", "InternalFeaturesCut", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face.")), - ("App::PropertyEnumeration", "Algorithm", "Waterline", + ("App::PropertyEnumeration", "Algorithm", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Select the algorithm to use: OCL Dropcutter*, or Experimental.")), - ("App::PropertyEnumeration", "BoundBox", "Waterline", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Should the operation be limited by the stock object or by the bounding box of the base object")), - ("App::PropertyDistance", "SampleInterval", "Waterline", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The Sample Interval. Small values cause long wait times")), - + ("App::PropertyEnumeration", "BoundBox", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Select the overall boundary for the operation. ")), ("App::PropertyVectorDistance", "CircularCenterCustom", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path.")), ("App::PropertyEnumeration", "CircularCenterAt", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose what point to start the ciruclar pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose center point to start the ciruclar pattern.")), + ("App::PropertyEnumeration", "ClearLastLayer", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set to clear last layer in a `Multi-pass` operation.")), ("App::PropertyEnumeration", "CutMode", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part: Climb(ClockWise) or Conventional(CounterClockWise)")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the direction for the cutting tool to engage the material: Climb (ClockWise) or Conventional (CounterClockWise)")), ("App::PropertyEnumeration", "CutPattern", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the geometric clearing pattern to use.")), ("App::PropertyFloat", "CutPatternAngle", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")), ("App::PropertyBool", "CutPatternReversed", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the order of the step-overs will be reversed; the operation will begin cutting the outer most line/arc, and work toward the inner most line/arc.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Reverse the cut order of the stepover paths. For circular cut patterns, begin at the outside and work toward the center.")), ("App::PropertyDistance", "DepthOffset", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Z-axis offset from the surface of the object")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the Z-axis depth offset from the target surface.")), ("App::PropertyEnumeration", "LayerMode", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The completion mode for the operation: single or multi-pass")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "The step down mode for the operation.")), ("App::PropertyEnumeration", "ProfileEdges", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")), + ("App::PropertyDistance", "SampleInterval", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the sampling resolution. Smaller values increase processing time.")), ("App::PropertyPercent", "StepOver", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the stepover percentage, based on the tool's diameter.")), - ("App::PropertyBool", "OptimizeLinearPaths", "Waterline Optimization", + ("App::PropertyBool", "OptimizeLinearPaths", "Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.")), - ("App::PropertyBool", "OptimizeStepOverTransitions", "Waterline Optimization", + ("App::PropertyBool", "OptimizeStepOverTransitions", "Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.")), - ("App::PropertyBool", "CircularUseG2G3", "Waterline Optimization", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.")), - ("App::PropertyDistance", "GapThreshold", "Waterline Optimization", + ("App::PropertyDistance", "GapThreshold", "Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.")), - ("App::PropertyString", "GapSizes", "Waterline Optimization", + ("App::PropertyString", "GapSizes", "Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")), ("App::PropertyVectorDistance", "StartPoint", "Start Point", @@ -187,8 +186,9 @@ class ObjectWaterline(PathOp.ObjectOp): 'Algorithm': ['OCL Dropcutter', 'Experimental'], 'BoundBox': ['BaseBoundBox', 'Stock'], 'CircularCenterAt': ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'], + 'ClearLastLayer': ['Off', 'Line', 'Circular', 'CircularZigZag', 'Offset', 'ZigZag'], 'CutMode': ['Conventional', 'Climb'], - 'CutPattern': ['Line', 'ZigZag', 'Circular', 'CircularZigZag'], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] + 'CutPattern': ['None', 'Line', 'Circular', 'CircularZigZag', 'Offset', 'ZigZag'], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] 'HandleMultipleFeatures': ['Collectively', 'Individually'], 'LayerMode': ['Single-pass', 'Multi-pass'], 'ProfileEdges': ['None', 'Only', 'First', 'Last'], @@ -198,22 +198,36 @@ class ObjectWaterline(PathOp.ObjectOp): # Used to hide inputs in properties list show = 0 hide = 2 + cpShow = 0 + expMode = 0 obj.setEditorMode('BoundaryEnforcement', hide) obj.setEditorMode('ProfileEdges', hide) obj.setEditorMode('InternalFeaturesAdjustment', hide) obj.setEditorMode('InternalFeaturesCut', hide) - # if obj.CutPattern in ['Line', 'ZigZag']: - if obj.CutPattern in ['Circular', 'CircularZigZag']: + if obj.CutPattern == 'None': + show = 2 + hide = 2 + cpShow = 2 + # elif obj.CutPattern in ['Line', 'ZigZag']: + # show = 0 + # hide = 2 + elif obj.CutPattern in ['Circular', 'CircularZigZag']: show = 2 # hide hide = 0 # show + # obj.setEditorMode('StepOver', cpShow) obj.setEditorMode('CutPatternAngle', show) obj.setEditorMode('CircularCenterAt', hide) obj.setEditorMode('CircularCenterCustom', hide) + if obj.Algorithm == 'Experimental': + expMode = 2 + obj.setEditorMode('SampleInterval', expMode) + obj.setEditorMode('LinearDeflection', expMode) + obj.setEditorMode('AngularDeflection', expMode) def onChanged(self, obj, prop): if hasattr(self, 'addedAllProperties'): if self.addedAllProperties is True: - if prop == 'CutPattern': + if prop in ['Algorithm', 'CutPattern']: self.setEditorProperties(obj) def opOnDocumentRestored(self, obj): @@ -233,7 +247,6 @@ class ObjectWaterline(PathOp.ObjectOp): obj.OptimizeLinearPaths = True obj.InternalFeaturesCut = True obj.OptimizeStepOverTransitions = False - obj.CircularUseG2G3 = False obj.BoundaryEnforcement = True obj.UseStartPoint = False obj.AvoidLastX_InternalFeatures = True @@ -245,10 +258,11 @@ class ObjectWaterline(PathOp.ObjectOp): obj.ProfileEdges = 'None' obj.LayerMode = 'Single-pass' obj.CutMode = 'Conventional' - obj.CutPattern = 'Line' + obj.CutPattern = 'None' obj.HandleMultipleFeatures = 'Collectively' # 'Individually' obj.CircularCenterAt = 'CenterOfMass' # 'CenterOfBoundBox', 'XminYmin', 'Custom' obj.GapSizes = 'No gaps identified.' + obj.ClearLastLayer = 'Off' obj.StepOver = 100 obj.CutPatternAngle = 0.0 obj.SampleInterval.Value = 1.0 @@ -259,6 +273,8 @@ class ObjectWaterline(PathOp.ObjectOp): obj.CircularCenterCustom.y = 0.0 obj.CircularCenterCustom.z = 0.0 obj.GapThreshold.Value = 0.005 + obj.LinearDeflection.Value = 0.0001 + obj.AngularDeflection.Value = 0.25 # For debugging obj.ShowTempObjects = False @@ -327,7 +343,7 @@ class ObjectWaterline(PathOp.ObjectOp): self.collectiveShapes = list() self.individualShapes = list() self.avoidShapes = list() - self.deflection = None + self.geoTlrnc = None self.tempGroup = None self.CutClimb = False self.closedGap = False @@ -369,12 +385,12 @@ class ObjectWaterline(PathOp.ObjectOp): # ... and move cutter to clearance height and startpoint output = '' if obj.Comment != '': - output += '(' + str(obj.Comment) + ')\n' - output += '(' + obj.Label + ')\n' - output += '(Tool type: ' + str(obj.ToolController.Tool.ToolType) + ')\n' - output += '(Compensated Tool Path. Diameter: ' + str(obj.ToolController.Tool.Diameter) + ')\n' - output += '(Sample interval: ' + str(obj.SampleInterval.Value) + ')\n' - output += '(Step over %: ' + str(obj.StepOver) + ')\n' + self.commandlist.append(Path.Command('N ({})'.format(str(obj.Comment)), {})) + self.commandlist.append(Path.Command('N ({})'.format(obj.Label), {})) + self.commandlist.append(Path.Command('N (Tool type: {})'.format(str(obj.ToolController.Tool.ToolType)), {})) + self.commandlist.append(Path.Command('N (Compensated Tool Path. Diameter: {})'.format(str(obj.ToolController.Tool.Diameter)), {})) + self.commandlist.append(Path.Command('N (Sample interval: {})'.format(str(obj.SampleInterval.Value)), {})) + self.commandlist.append(Path.Command('N (Step over %: {})'.format(str(obj.StepOver)), {})) self.commandlist.append(Path.Command('N ({})'.format(output), {})) self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) if obj.UseStartPoint is True: @@ -419,6 +435,19 @@ class ObjectWaterline(PathOp.ObjectOp): self.SafeHeightOffset = JOB.SetupSheet.SafeHeightOffset.Value self.ClearHeightOffset = JOB.SetupSheet.ClearanceHeightOffset.Value + # Set deflection values for mesh generation + useDGT = False + try: # try/except is for Path Jobs created before GeometryTolerance + self.geoTlrnc = JOB.GeometryTolerance.Value + if self.geoTlrnc == 0.0: + useDGT = True + except AttributeError as ee: + PathLog.warning('{}\nPlease set Job.GeometryTolerance to an acceptable value. Using PathPreferences.defaultGeometryTolerance().'.format(ee)) + useDGT = True + if useDGT: + import PathScripts.PathPreferences as PathPreferences + self.geoTlrnc = PathPreferences.defaultGeometryTolerance() + # Calculate default depthparams for operation self.depthParams = PathUtils.depth_params(obj.ClearanceHeight.Value, obj.SafeHeight.Value, obj.StartDepth.Value, obj.StepDown.Value, 0.0, obj.FinalDepth.Value) self.midDep = (obj.StartDepth.Value + obj.FinalDepth.Value) / 2.0 @@ -426,15 +455,6 @@ class ObjectWaterline(PathOp.ObjectOp): # make circle for workplane self.wpc = Part.makeCircle(2.0) - # Set deflection values for mesh generation - self.angularDeflection = 0.05 - try: # try/except is for Path Jobs created before GeometryTolerance - self.deflection = JOB.GeometryTolerance.Value - except AttributeError as ee: - PathLog.warning('Error setting Mesh deflection: {}. Using PathPreferences.defaultGeometryTolerance().'.format(ee)) - import PathScripts.PathPreferences as PathPreferences - self.deflection = PathPreferences.defaultGeometryTolerance() - # Save model visibilities for restoration if FreeCAD.GuiUp: for m in range(0, len(JOB.Model.Group)): @@ -470,7 +490,10 @@ class ObjectWaterline(PathOp.ObjectOp): (FACES, VOIDS) = pPM # Create OCL.stl model objects - self._prepareModelSTLs(JOB, obj) + if obj.Algorithm == 'OCL Dropcutter': + self._prepareModelSTLs(JOB, obj) + PathLog.debug('obj.LinearDeflection.Value: {}'.format(obj.LinearDeflection.Value)) + PathLog.debug('obj.AngularDeflection.Value: {}'.format(obj.AngularDeflection.Value)) for m in range(0, len(JOB.Model.Group)): Mdl = JOB.Model.Group[m] @@ -483,8 +506,9 @@ class ObjectWaterline(PathOp.ObjectOp): CMDS.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) PathLog.info('Working on Model.Group[{}]: {}'.format(m, Mdl.Label)) # make stock-model-voidShapes STL model for avoidance detection on transitions - self._makeSafeSTL(JOB, obj, m, FACES[m], VOIDS[m]) - time.sleep(0.2) + if obj.Algorithm == 'OCL Dropcutter': + self._makeSafeSTL(JOB, obj, m, FACES[m], VOIDS[m]) + # time.sleep(0.2) # Process model/faces - OCL objects must be ready CMDS.extend(self._processCutAreas(JOB, obj, m, FACES[m], VOIDS[m])) @@ -542,8 +566,6 @@ class ObjectWaterline(PathOp.ObjectOp): self.depthParams = None self.midDep = None self.wpc = None - self.angularDeflection = None - self.deflection = None del self.modelSTLs del self.safeSTLs del self.modelTypes @@ -555,8 +577,6 @@ class ObjectWaterline(PathOp.ObjectOp): del self.depthParams del self.midDep del self.wpc - del self.angularDeflection - del self.deflection execTime = time.time() - startTime PathLog.info('Operation time: {} sec.'.format(execTime)) @@ -964,7 +984,7 @@ class ObjectWaterline(PathOp.ObjectOp): except Exception as eee: PathLog.error(str(eee)) cont = False - time.sleep(0.2) + # time.sleep(0.2) if cont is True: csFaceShape = self._getShapeSlice(baseEnv) @@ -1093,7 +1113,7 @@ class ObjectWaterline(PathOp.ObjectOp): return offset - def _extractFaceOffset(self, obj, fcShape, offset): + def _extractFaceOffset(self, obj, fcShape, offset, makeComp=True): '''_extractFaceOffset(fcShape, offset) ... internal function. Original _buildPathArea() version copied from PathAreaOp.py module. This version is modified. Adjustments made based on notes by @sliptonic at this webpage: https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.''' @@ -1104,13 +1124,14 @@ class ObjectWaterline(PathOp.ObjectOp): areaParams = {} areaParams['Offset'] = offset - areaParams['Fill'] = 1 + areaParams['Fill'] = 1 # 1 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['Tolerance'] = 0.001 area = Path.Area() # Create instance of Area() class object # area.setPlane(PathUtils.makeWorkplane(fcShape)) # Set working plane @@ -1118,17 +1139,26 @@ class ObjectWaterline(PathOp.ObjectOp): area.add(fcShape) area.setParams(**areaParams) # set parameters + # Save parameters for debugging + # obj.AreaParams = str(area.getParams()) + # PathLog.debug("Area with params: {}".format(area.getParams())) + offsetShape = area.getShape() wCnt = len(offsetShape.Wires) if wCnt == 0: return False elif wCnt == 1: ofstFace = Part.Face(offsetShape.Wires[0]) + if not makeComp: + ofstFace = [ofstFace] else: W = list() for wr in offsetShape.Wires: W.append(Part.Face(wr)) - ofstFace = Part.makeCompound(W) + if makeComp: + ofstFace = Part.makeCompound(W) + else: + ofstFace = W return ofstFace # offsetShape @@ -1373,8 +1403,10 @@ class ObjectWaterline(PathOp.ObjectOp): mesh = M.Mesh else: # base.Shape.tessellate(0.05) # 0.5 original value - # mesh = MeshPart.meshFromShape(base.Shape, Deflection=self.deflection) - mesh = MeshPart.meshFromShape(Shape=M.Shape, LinearDeflection=self.deflection, AngularDeflection=self.angularDeflection, Relative=False) + mesh = MeshPart.meshFromShape(Shape=M.Shape, + LinearDeflection=obj.LinearDeflection.Value, + AngularDeflection=obj.AngularDeflection.Value, + Relative=False) if self.modelSTLs[m] is True: stl = ocl.STLSurf() @@ -1438,7 +1470,7 @@ class ObjectWaterline(PathOp.ObjectOp): fuseShapes.append(adjStckWst) else: PathLog.warning('Path transitions might not avoid the model. Verify paths.') - time.sleep(0.3) + # time.sleep(0.3) else: # If boundbox is Job.Stock, add hidden pad under stock as base plate @@ -1471,8 +1503,11 @@ class ObjectWaterline(PathOp.ObjectOp): self.tempGroup.addObject(T) # Extract mesh from fusion - meshFuse = MeshPart.meshFromShape(Shape=fused, LinearDeflection=(self.deflection / 2.0), AngularDeflection=self.angularDeflection, Relative=False) - time.sleep(0.2) + meshFuse = MeshPart.meshFromShape(Shape=fused, + LinearDeflection=obj.LinearDeflection.Value, + AngularDeflection=obj.AngularDeflection.Value, + Relative=False) + # time.sleep(0.2) stl = ocl.STLSurf() for f in meshFuse.Facets: p = f.Points[0] @@ -1538,6 +1573,763 @@ class ObjectWaterline(PathOp.ObjectOp): return final + # Methods for creating path geometry + def _planarMakePathGeom(self, obj, faceShp): + '''_planarMakePathGeom(obj, faceShp)... + Creates the line/arc cut pattern geometry and returns the intersection with the received faceShp. + The resulting intersecting line/arc geometries are then converted to lines or arcs for OCL.''' + PathLog.debug('_planarMakePathGeom()') + GeoSet = list() + + # Apply drop cutter extra offset and set the max and min XY area of the operation + xmin = faceShp.BoundBox.XMin + xmax = faceShp.BoundBox.XMax + ymin = faceShp.BoundBox.YMin + ymax = faceShp.BoundBox.YMax + zmin = faceShp.BoundBox.ZMin + zmax = faceShp.BoundBox.ZMax + + # Compute weighted center of mass of all faces combined + fCnt = 0 + totArea = 0.0 + zeroCOM = FreeCAD.Vector(0.0, 0.0, 0.0) + for F in faceShp.Faces: + comF = F.CenterOfMass + areaF = F.Area + totArea += areaF + fCnt += 1 + zeroCOM = zeroCOM.add(FreeCAD.Vector(comF.x, comF.y, 0.0).multiply(areaF)) + if fCnt == 0: + PathLog.error(translate('PathSurface', 'Cannot calculate the Center Of Mass. Using Center of Boundbox.')) + zeroCOM = FreeCAD.Vector((xmin + xmax) / 2.0, (ymin + ymax) / 2.0, 0.0) + else: + avgArea = totArea / fCnt + zeroCOM.multiply(1 / fCnt) + zeroCOM.multiply(1 / avgArea) + COM = FreeCAD.Vector(zeroCOM.x, zeroCOM.y, 0.0) + + # get X, Y, Z spans; Compute center of rotation + deltaX = abs(xmax-xmin) + deltaY = abs(ymax-ymin) + deltaZ = abs(zmax-zmin) + deltaC = math.sqrt(deltaX**2 + deltaY**2) + lineLen = deltaC + (2.0 * self.cutter.getDiameter()) # Line length to span boundbox diag with 2x cutter diameter extra on each end + halfLL = math.ceil(lineLen / 2.0) + cutPasses = math.ceil(lineLen / self.cutOut) + 1 # Number of lines(passes) required to cover lineLen + halfPasses = math.ceil(cutPasses / 2.0) + bbC = faceShp.BoundBox.Center + + # Generate the Draft line/circle sets to be intersected with the cut-face-area + if obj.CutPattern in ['ZigZag', 'Line']: + MaxLC = -1 + centRot = FreeCAD.Vector(0.0, 0.0, 0.0) # Bottom left corner of face/selection/model + cAng = math.atan(deltaX / deltaY) # BoundaryBox angle + + # Determine end points and create top lines + x1 = centRot.x - halfLL + x2 = centRot.x + halfLL + diag = None + if obj.CutPatternAngle == 0 or obj.CutPatternAngle == 180: + MaxLC = math.floor(deltaY / self.cutOut) + diag = deltaY + elif obj.CutPatternAngle == 90 or obj.CutPatternAngle == 270: + MaxLC = math.floor(deltaX / self.cutOut) + diag = deltaX + else: + perpDist = math.cos(cAng - math.radians(obj.CutPatternAngle)) * deltaC + MaxLC = math.floor(perpDist / self.cutOut) + diag = perpDist + y1 = centRot.y + diag + # y2 = y1 + + p1 = FreeCAD.Vector(x1, y1, 0.0) + p2 = FreeCAD.Vector(x2, y1, 0.0) + topLineTuple = (p1, p2) + ny1 = centRot.y - diag + n1 = FreeCAD.Vector(x1, ny1, 0.0) + n2 = FreeCAD.Vector(x2, ny1, 0.0) + negTopLineTuple = (n1, n2) + + # Create end points for set of lines to intersect with cross-section face + pntTuples = list() + for lc in range((-1 * (halfPasses - 1)), halfPasses + 1): + # if lc == (cutPasses - MaxLC - 1): + # pntTuples.append(negTopLineTuple) + # if lc == (MaxLC + 1): + # pntTuples.append(topLineTuple) + x1 = centRot.x - halfLL + x2 = centRot.x + halfLL + y1 = centRot.y + (lc * self.cutOut) + # y2 = y1 + p1 = FreeCAD.Vector(x1, y1, 0.0) + p2 = FreeCAD.Vector(x2, y1, 0.0) + pntTuples.append( (p1, p2) ) + + # Convert end points to lines + for (p1, p2) in pntTuples: + line = Part.makeLine(p1, p2) + GeoSet.append(line) + elif obj.CutPattern in ['Circular', 'CircularZigZag']: + zTgt = faceShp.BoundBox.ZMin + axisRot = FreeCAD.Vector(0.0, 0.0, 1.0) + cntr = FreeCAD.Placement() + cntr.Rotation = FreeCAD.Rotation(axisRot, 0.0) + + if obj.CircularCenterAt == 'CenterOfMass': + cntr.Base = FreeCAD.Vector(COM.x, COM.y, zTgt) # COM # Use center of Mass + elif obj.CircularCenterAt == 'CenterOfBoundBox': + cent = faceShp.BoundBox.Center + cntr.Base = FreeCAD.Vector(cent.x, cent.y, zTgt) + elif obj.CircularCenterAt == 'XminYmin': + cntr.Base = FreeCAD.Vector(faceShp.BoundBox.XMin, faceShp.BoundBox.YMin, zTgt) + elif obj.CircularCenterAt == 'Custom': + newCent = FreeCAD.Vector(obj.CircularCenterCustom.x, obj.CircularCenterCustom.y, zTgt) + cntr.Base = newCent + + # recalculate cutPasses value, if need be + radialPasses = halfPasses + if obj.CircularCenterAt != 'CenterOfBoundBox': + # make 4 corners of boundbox in XY plane, find which is greatest distance to new circular center + EBB = faceShp.BoundBox + CORNERS = [ + FreeCAD.Vector(EBB.XMin, EBB.YMin, 0.0), + FreeCAD.Vector(EBB.XMin, EBB.YMax, 0.0), + FreeCAD.Vector(EBB.XMax, EBB.YMax, 0.0), + FreeCAD.Vector(EBB.XMax, EBB.YMin, 0.0), + ] + dMax = 0.0 + for c in range(0, 4): + dist = CORNERS[c].sub(cntr.Base).Length + if dist > dMax: + dMax = dist + lineLen = dMax + (2.0 * self.cutter.getDiameter()) # Line length to span boundbox diag with 2x cutter diameter extra on each end + radialPasses = math.ceil(lineLen / self.cutOut) + 1 # Number of lines(passes) required to cover lineLen + + # Update COM point and current CircularCenter + if obj.CircularCenterAt != 'Custom': + obj.CircularCenterCustom = cntr.Base + + minRad = self.cutter.getDiameter() * 0.45 + siX3 = 3 * obj.SampleInterval.Value + minRadSI = (siX3 / 2.0) / math.pi + if minRad < minRadSI: + minRad = minRadSI + + # Make small center circle to start pattern + if obj.StepOver > 50: + circle = Part.makeCircle(minRad, cntr.Base) + GeoSet.append(circle) + + for lc in range(1, radialPasses + 1): + rad = (lc * self.cutOut) + if rad >= minRad: + circle = Part.makeCircle(rad, cntr.Base) + GeoSet.append(circle) + # Efor + COM = cntr.Base + # Eif + + if obj.CutPatternReversed is True: + GeoSet.reverse() + + if faceShp.BoundBox.ZMin != 0.0: + faceShp.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - faceShp.BoundBox.ZMin)) + + # Create compound object to bind all lines in Lineset + geomShape = Part.makeCompound(GeoSet) + + # Position and rotate the Line and ZigZag geometry + if obj.CutPattern in ['Line', 'ZigZag']: + if obj.CutPatternAngle != 0.0: + geomShape.Placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), obj.CutPatternAngle) + geomShape.Placement.Base = FreeCAD.Vector(bbC.x, bbC.y, 0.0 - geomShape.BoundBox.ZMin) + + if self.showDebugObjects is True: + F = FreeCAD.ActiveDocument.addObject('Part::Feature','tmpGeometrySet') + F.Shape = geomShape + F.purgeTouched() + self.tempGroup.addObject(F) + + # Identify intersection of cross-section face and lineset + cmnShape = faceShp.common(geomShape) + + if self.showDebugObjects is True: + F = FreeCAD.ActiveDocument.addObject('Part::Feature','tmpPathGeometry') + F.Shape = cmnShape + F.purgeTouched() + self.tempGroup.addObject(F) + + self.tmpCOM = FreeCAD.Vector(COM.x, COM.y, faceShp.BoundBox.ZMin) + return cmnShape + + def _pathGeomToLinesPointSet(self, obj, compGeoShp): + '''_pathGeomToLinesPointSet(obj, compGeoShp)... + Convert a compound set of sequential line segments to directionally-oriented collinear groupings.''' + PathLog.debug('_pathGeomToLinesPointSet()') + # Extract intersection line segments for return value as list() + LINES = list() + inLine = list() + chkGap = False + lnCnt = 0 + ec = len(compGeoShp.Edges) + cutClimb = self.CutClimb + toolDiam = 2.0 * self.radius + cpa = obj.CutPatternAngle + + edg0 = compGeoShp.Edges[0] + p1 = (edg0.Vertexes[0].X, edg0.Vertexes[0].Y) + p2 = (edg0.Vertexes[1].X, edg0.Vertexes[1].Y) + if cutClimb is True: + tup = (p2, p1) + lst = FreeCAD.Vector(p1[0], p1[1], 0.0) + else: + tup = (p1, p2) + lst = FreeCAD.Vector(p2[0], p2[1], 0.0) + inLine.append(tup) + sp = FreeCAD.Vector(p1[0], p1[1], 0.0) # start point + + for ei in range(1, ec): + chkGap = False + edg = compGeoShp.Edges[ei] # Get edge for vertexes + v1 = (edg.Vertexes[0].X, edg.Vertexes[0].Y) # vertex 0 + v2 = (edg.Vertexes[1].X, edg.Vertexes[1].Y) # vertex 1 + + ep = FreeCAD.Vector(v2[0], v2[1], 0.0) # end point + cp = FreeCAD.Vector(v1[0], v1[1], 0.0) # check point (first / middle point) + iC = self.isPointOnLine(sp, ep, cp) + if iC is True: + inLine.append('BRK') + chkGap = True + else: + if cutClimb is True: + inLine.reverse() + LINES.append(inLine) # Save inLine segments + lnCnt += 1 + inLine = list() # reset collinear container + if cutClimb is True: + sp = cp # FreeCAD.Vector(v1[0], v1[1], 0.0) + else: + sp = ep + + if cutClimb is True: + tup = (v2, v1) + if chkGap is True: + gap = abs(toolDiam - lst.sub(ep).Length) + lst = cp + else: + tup = (v1, v2) + if chkGap is True: + gap = abs(toolDiam - lst.sub(cp).Length) + lst = ep + + if chkGap is True: + if gap < obj.GapThreshold.Value: + b = inLine.pop() # pop off 'BRK' marker + (vA, vB) = inLine.pop() # pop off previous line segment for combining with current + tup = (vA, tup[1]) + self.closedGap = True + else: + # PathLog.debug('---- Gap: {} mm'.format(gap)) + gap = round(gap, 6) + if gap < self.gaps[0]: + self.gaps.insert(0, gap) + self.gaps.pop() + inLine.append(tup) + # Efor + lnCnt += 1 + if cutClimb is True: + inLine.reverse() + LINES.append(inLine) # Save inLine segments + + # Handle last inLine set, reversing it. + if obj.CutPatternReversed is True: + if cpa != 0.0 and cpa % 90.0 == 0.0: + F = LINES.pop(0) + rev = list() + for iL in F: + if iL == 'BRK': + rev.append(iL) + else: + (p1, p2) = iL + rev.append((p2, p1)) + rev.reverse() + LINES.insert(0, rev) + + isEven = lnCnt % 2 + if isEven == 0: + PathLog.debug('Line count is ODD.') + else: + PathLog.debug('Line count is even.') + + return LINES + + def _pathGeomToZigzagPointSet(self, obj, compGeoShp): + '''_pathGeomToZigzagPointSet(obj, compGeoShp)... + Convert a compound set of sequential line segments to directionally-oriented collinear groupings + with a ZigZag directional indicator included for each collinear group.''' + PathLog.debug('_pathGeomToZigzagPointSet()') + # Extract intersection line segments for return value as list() + LINES = list() + inLine = list() + lnCnt = 0 + chkGap = False + ec = len(compGeoShp.Edges) + toolDiam = 2.0 * self.radius + + if self.CutClimb is True: + dirFlg = -1 + else: + dirFlg = 1 + + edg0 = compGeoShp.Edges[0] + p1 = (edg0.Vertexes[0].X, edg0.Vertexes[0].Y) + p2 = (edg0.Vertexes[1].X, edg0.Vertexes[1].Y) + if dirFlg == 1: + tup = (p1, p2) + lst = FreeCAD.Vector(p2[0], p2[1], 0.0) + sp = FreeCAD.Vector(p1[0], p1[1], 0.0) # start point + else: + tup = (p2, p1) + lst = FreeCAD.Vector(p1[0], p1[1], 0.0) + sp = FreeCAD.Vector(p2[0], p2[1], 0.0) # start point + inLine.append(tup) + otr = lst + + for ei in range(1, ec): + edg = compGeoShp.Edges[ei] + v1 = (edg.Vertexes[0].X, edg.Vertexes[0].Y) + v2 = (edg.Vertexes[1].X, edg.Vertexes[1].Y) + + cp = FreeCAD.Vector(v1[0], v1[1], 0.0) # check point (start point of segment) + ep = FreeCAD.Vector(v2[0], v2[1], 0.0) # end point + iC = self.isPointOnLine(sp, ep, cp) + if iC is True: + inLine.append('BRK') + chkGap = True + gap = abs(toolDiam - lst.sub(cp).Length) + else: + chkGap = False + if dirFlg == -1: + inLine.reverse() + LINES.append((dirFlg, inLine)) + lnCnt += 1 + dirFlg = -1 * dirFlg # Change zig to zag + inLine = list() # reset collinear container + sp = cp # FreeCAD.Vector(v1[0], v1[1], 0.0) + otr = ep + + lst = ep + if dirFlg == 1: + tup = (v1, v2) + else: + tup = (v2, v1) + + if chkGap is True: + if gap < obj.GapThreshold.Value: + b = inLine.pop() # pop off 'BRK' marker + (vA, vB) = inLine.pop() # pop off previous line segment for combining with current + if dirFlg == 1: + tup = (vA, tup[1]) + else: + #tup = (vA, tup[1]) + #tup = (tup[1], vA) + tup = (tup[0], vB) + self.closedGap = True + else: + gap = round(gap, 6) + if gap < self.gaps[0]: + self.gaps.insert(0, gap) + self.gaps.pop() + inLine.append(tup) + # Efor + lnCnt += 1 + + # Fix directional issue with LAST line when line count is even + isEven = lnCnt % 2 + if isEven == 0: # Changed to != with 90 degree CutPatternAngle + PathLog.debug('Line count is even.') + else: + PathLog.debug('Line count is ODD.') + dirFlg = -1 * dirFlg + if obj.CutPatternReversed is False: + if self.CutClimb is True: + dirFlg = -1 * dirFlg + + if obj.CutPatternReversed is True: + dirFlg = -1 * dirFlg + + # Handle last inLine list + if dirFlg == 1: + rev = list() + for iL in inLine: + if iL == 'BRK': + rev.append(iL) + else: + (p1, p2) = iL + rev.append((p2, p1)) + + if obj.CutPatternReversed is False: + rev.reverse() + else: + rev2 = list() + for iL in rev: + if iL == 'BRK': + rev2.append(iL) + else: + (p1, p2) = iL + rev2.append((p2, p1)) + rev2.reverse() + rev = rev2 + + LINES.append((dirFlg, rev)) + else: + LINES.append((dirFlg, inLine)) + + return LINES + + def _pathGeomToArcPointSet(self, obj, compGeoShp): + '''_pathGeomToArcPointSet(obj, compGeoShp)... + Convert a compound set of arcs/circles to a set of directionally-oriented arc end points + and the corresponding center point.''' + # Extract intersection line segments for return value as list() + PathLog.debug('_pathGeomToArcPointSet()') + ARCS = list() + stpOvrEI = list() + segEI = list() + isSame = False + sameRad = None + COM = self.tmpCOM + toolDiam = 2.0 * self.radius + ec = len(compGeoShp.Edges) + + def gapDist(sp, ep): + X = (ep[0] - sp[0])**2 + Y = (ep[1] - sp[1])**2 + Z = (ep[2] - sp[2])**2 + # return math.sqrt(X + Y + Z) + return math.sqrt(X + Y) # the 'z' value is zero in both points + + # Separate arc data into Loops and Arcs + for ei in range(0, ec): + edg = compGeoShp.Edges[ei] + if edg.Closed is True: + stpOvrEI.append(('L', ei, False)) + else: + if isSame is False: + segEI.append(ei) + isSame = True + pnt = FreeCAD.Vector(edg.Vertexes[0].X, edg.Vertexes[0].Y, 0.0) + sameRad = pnt.sub(COM).Length + else: + # Check if arc is co-radial to current SEGS + pnt = FreeCAD.Vector(edg.Vertexes[0].X, edg.Vertexes[0].Y, 0.0) + if abs(sameRad - pnt.sub(COM).Length) > 0.00001: + isSame = False + + if isSame is True: + segEI.append(ei) + else: + # Move co-radial arc segments + stpOvrEI.append(['A', segEI, False]) + # Start new list of arc segments + segEI = [ei] + isSame = True + pnt = FreeCAD.Vector(edg.Vertexes[0].X, edg.Vertexes[0].Y, 0.0) + sameRad = pnt.sub(COM).Length + # Process trailing `segEI` data, if available + if isSame is True: + stpOvrEI.append(['A', segEI, False]) + + # Identify adjacent arcs with y=0 start/end points that connect + for so in range(0, len(stpOvrEI)): + SO = stpOvrEI[so] + if SO[0] == 'A': + startOnAxis = list() + endOnAxis = list() + EI = SO[1] # list of corresponding compGeoShp.Edges indexes + + # Identify startOnAxis and endOnAxis arcs + for i in range(0, len(EI)): + ei = EI[i] # edge index + E = compGeoShp.Edges[ei] # edge object + if abs(COM.y - E.Vertexes[0].Y) < 0.00001: + startOnAxis.append((i, ei, E.Vertexes[0])) + elif abs(COM.y - E.Vertexes[1].Y) < 0.00001: + endOnAxis.append((i, ei, E.Vertexes[1])) + + # Look for connections between startOnAxis and endOnAxis arcs. Consolidate data when connected + lenSOA = len(startOnAxis) + lenEOA = len(endOnAxis) + if lenSOA > 0 and lenEOA > 0: + delIdxs = list() + lstFindIdx = 0 + for soa in range(0, lenSOA): + (iS, eiS, vS) = startOnAxis[soa] + for eoa in range(0, len(endOnAxis)): + (iE, eiE, vE) = endOnAxis[eoa] + dist = vE.X - vS.X + if abs(dist) < 0.00001: # They connect on axis at same radius + SO[2] = (eiE, eiS) + break + elif dist > 0: + break # stop searching + # Eif + # Eif + # Efor + + # Construct arc data tuples for OCL + dirFlg = 1 + # cutPat = obj.CutPattern + if self.CutClimb is False: # True yields Climb when set to Conventional + dirFlg = -1 + + # Cycle through stepOver data + for so in range(0, len(stpOvrEI)): + SO = stpOvrEI[so] + if SO[0] == 'L': # L = Loop/Ring/Circle + # PathLog.debug("SO[0] == 'Loop'") + lei = SO[1] # loop Edges index + v1 = compGeoShp.Edges[lei].Vertexes[0] + + # space = obj.SampleInterval.Value / 2.0 + space = 0.0000001 + + # p1 = FreeCAD.Vector(v1.X, v1.Y, v1.Z) + p1 = FreeCAD.Vector(v1.X, v1.Y, 0.0) + rad = p1.sub(COM).Length + spcRadRatio = space/rad + if spcRadRatio < 1.0: + tolrncAng = math.asin(spcRadRatio) + else: + tolrncAng = 0.9999998 * math.pi + EX = COM.x + (rad * math.cos(tolrncAng)) + EY = v1.Y - space # rad * math.sin(tolrncAng) + + sp = (v1.X, v1.Y, 0.0) + ep = (EX, EY, 0.0) + cp = (COM.x, COM.y, 0.0) + if dirFlg == 1: + arc = (sp, ep, cp) + else: + arc = (ep, sp, cp) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) + ARCS.append(('L', dirFlg, [arc])) + else: # SO[0] == 'A' A = Arc + # PathLog.debug("SO[0] == 'Arc'") + PRTS = list() + EI = SO[1] # list of corresponding Edges indexes + CONN = SO[2] # list of corresponding connected edges tuples (iE, iS) + chkGap = False + lst = None + + if CONN is not False: + (iE, iS) = CONN + v1 = compGeoShp.Edges[iE].Vertexes[0] + v2 = compGeoShp.Edges[iS].Vertexes[1] + sp = (v1.X, v1.Y, 0.0) + ep = (v2.X, v2.Y, 0.0) + cp = (COM.x, COM.y, 0.0) + if dirFlg == 1: + arc = (sp, ep, cp) + lst = ep + else: + arc = (ep, sp, cp) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) + lst = sp + PRTS.append(arc) + # Pop connected edge index values from arc segments index list + iEi = EI.index(iE) + iSi = EI.index(iS) + if iEi > iSi: + EI.pop(iEi) + EI.pop(iSi) + else: + EI.pop(iSi) + EI.pop(iEi) + if len(EI) > 0: + PRTS.append('BRK') + chkGap = True + cnt = 0 + for ei in EI: + if cnt > 0: + PRTS.append('BRK') + chkGap = True + v1 = compGeoShp.Edges[ei].Vertexes[0] + v2 = compGeoShp.Edges[ei].Vertexes[1] + sp = (v1.X, v1.Y, 0.0) + ep = (v2.X, v2.Y, 0.0) + cp = (COM.x, COM.y, 0.0) + if dirFlg == 1: + arc = (sp, ep, cp) + if chkGap is True: + gap = abs(toolDiam - gapDist(lst, sp)) # abs(toolDiam - lst.sub(sp).Length) + lst = ep + else: + arc = (ep, sp, cp) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) + if chkGap is True: + gap = abs(toolDiam - gapDist(lst, ep)) # abs(toolDiam - lst.sub(ep).Length) + lst = sp + if chkGap is True: + if gap < obj.GapThreshold.Value: + b = PRTS.pop() # pop off 'BRK' marker + (vA, vB, vC) = PRTS.pop() # pop off previous arc segment for combining with current + arc = (vA, arc[1], vC) + self.closedGap = True + else: + # PathLog.debug('---- Gap: {} mm'.format(gap)) + gap = round(gap, 6) + if gap < self.gaps[0]: + self.gaps.insert(0, gap) + self.gaps.pop() + PRTS.append(arc) + cnt += 1 + + if dirFlg == -1: + PRTS.reverse() + + ARCS.append(('A', dirFlg, PRTS)) + # Eif + if obj.CutPattern == 'CircularZigZag': + dirFlg = -1 * dirFlg + # Efor + + return ARCS + + def _getExperimentalWaterlinePaths(self, obj, PNTSET, csHght): + '''_getExperimentalWaterlinePaths(obj, PNTSET, csHght)... + Switching fuction for calling the appropriate path-geometry to OCL points conversion fucntion + for the various cut patterns.''' + PathLog.debug('_getExperimentalWaterlinePaths()') + SCANS = list() + + if obj.CutPattern == 'Line': + stpOvr = list() + for D in PNTSET: + for SEG in D: + if SEG == 'BRK': + stpOvr.append(SEG) + else: + # D format is ((p1, p2), (p3, p4)) + (A, B) = SEG + P1 = FreeCAD.Vector(A[0], A[1], csHght) + P2 = FreeCAD.Vector(B[0], B[1], csHght) + stpOvr.append((P1, P2)) + SCANS.append(stpOvr) + stpOvr = list() + elif obj.CutPattern == 'ZigZag': + stpOvr = list() + for (dirFlg, LNS) in PNTSET: + for SEG in LNS: + if SEG == 'BRK': + stpOvr.append(SEG) + else: + # D format is ((p1, p2), (p3, p4)) + (A, B) = SEG + P1 = FreeCAD.Vector(A[0], A[1], csHght) + P2 = FreeCAD.Vector(B[0], B[1], csHght) + stpOvr.append((P1, P2)) + SCANS.append(stpOvr) + stpOvr = list() + elif obj.CutPattern in ['Circular', 'CircularZigZag']: + # PNTSET is list, by stepover. + # Each stepover is a list containing arc/loop descriptions, (sp, ep, cp) + for so in range(0, len(PNTSET)): + stpOvr = list() + erFlg = False + (aTyp, dirFlg, ARCS) = PNTSET[so] + + if dirFlg == 1: # 1 + cMode = True # Climb mode + else: + cMode = False + + for a in range(0, len(ARCS)): + Arc = ARCS[a] + if Arc == 'BRK': + stpOvr.append('BRK') + else: + (sp, ep, cp) = Arc + S = FreeCAD.Vector(sp[0], sp[1], csHght) + E = FreeCAD.Vector(ep[0], ep[1], csHght) + C = FreeCAD.Vector(cp[0], cp[1], csHght) + scan = (S, E, C, cMode) + if scan is False: + erFlg = True + else: + ##if aTyp == 'L': + ## stpOvr.append(FreeCAD.Vector(scan[0][0].x, scan[0][0].y, scan[0][0].z)) + stpOvr.append(scan) + if erFlg is False: + SCANS.append(stpOvr) + + return SCANS + + # Main planar scan functions + def _stepTransitionCmds(self, obj, lstPnt, first, minSTH, tolrnc): + cmds = list() + rtpd = False + horizGC = 'G0' + hSpeed = self.horizRapid + height = obj.SafeHeight.Value + + if obj.CutPattern in ['Line', 'Circular']: + if obj.OptimizeStepOverTransitions is True: + height = minSTH + 2.0 + # if obj.LayerMode == 'Multi-pass': + # rtpd = minSTH + elif obj.CutPattern in ['ZigZag', 'CircularZigZag']: + if obj.OptimizeStepOverTransitions is True: + zChng = first.z - lstPnt.z + # PathLog.debug('first.z: {}'.format(first.z)) + # PathLog.debug('lstPnt.z: {}'.format(lstPnt.z)) + # PathLog.debug('zChng: {}'.format(zChng)) + # PathLog.debug('minSTH: {}'.format(minSTH)) + if abs(zChng) < tolrnc: # transitions to same Z height + # PathLog.debug('abs(zChng) < tolrnc') + if (minSTH - first.z) > tolrnc: + # PathLog.debug('(minSTH - first.z) > tolrnc') + height = minSTH + 2.0 + else: + # PathLog.debug('ELSE (minSTH - first.z) > tolrnc') + horizGC = 'G1' + height = first.z + elif (minSTH + (2.0 * tolrnc)) >= max(first.z, lstPnt.z): + height = False # allow end of Zig to cut to beginning of Zag + + + # Create raise, shift, and optional lower commands + if height is not False: + cmds.append(Path.Command('G0', {'Z': height, 'F': self.vertRapid})) + cmds.append(Path.Command(horizGC, {'X': first.x, 'Y': first.y, 'F': hSpeed})) + if rtpd is not False: # ReturnToPreviousDepth + cmds.append(Path.Command('G0', {'Z': rtpd, 'F': self.vertRapid})) + + return cmds + + def _breakCmds(self, obj, lstPnt, first, minSTH, tolrnc): + cmds = list() + rtpd = False + horizGC = 'G0' + hSpeed = self.horizRapid + height = obj.SafeHeight.Value + + if obj.CutPattern in ['Line', 'Circular']: + if obj.OptimizeStepOverTransitions is True: + height = minSTH + 2.0 + elif obj.CutPattern in ['ZigZag', 'CircularZigZag']: + if obj.OptimizeStepOverTransitions is True: + zChng = first.z - lstPnt.z + if abs(zChng) < tolrnc: # transitions to same Z height + if (minSTH - first.z) > tolrnc: + height = minSTH + 2.0 + else: + height = first.z + 2.0 # first.z + + cmds.append(Path.Command('G0', {'Z': height, 'F': self.vertRapid})) + cmds.append(Path.Command(horizGC, {'X': first.x, 'Y': first.y, 'F': hSpeed})) + if rtpd is not False: # ReturnToPreviousDepth + cmds.append(Path.Command('G0', {'Z': rtpd, 'F': self.vertRapid})) + + return cmds + def _planarGetPDC(self, stl, finalDep, SampleInterval, useSafeCutter=False): pdc = ocl.PathDropCutter() # create a pdc [PathDropCutter] object pdc.setSTL(stl) # add stl model @@ -1935,9 +2727,614 @@ class ObjectWaterline(PathOp.ObjectOp): return output + # Main waterline functions def _experimentalWaterlineOp(self, JOB, obj, mdlIdx, subShp=None): - PathLog.error('The `Experimental` algorithm is not available at this time.') - return [] + '''_waterlineOp(JOB, obj, mdlIdx, subShp=None) ... + Main waterline function to perform waterline extraction from model.''' + PathLog.debug('_experimentalWaterlineOp()') + + msg = translate('PathWaterline', 'Experimental Waterline does not currently support selected faces.') + PathLog.info('\n..... ' + msg) + + commands = [] + t_begin = time.time() + base = JOB.Model.Group[mdlIdx] + bb = self.boundBoxes[mdlIdx] + stl = self.modelSTLs[mdlIdx] + safeSTL = self.safeSTLs[mdlIdx] + self.endVector = None + + finDep = obj.FinalDepth.Value + (self.geoTlrnc / 10.0) + depthParams = PathUtils.depth_params(obj.ClearanceHeight.Value, obj.SafeHeight.Value, obj.StartDepth.Value, obj.StepDown.Value, 0.0, finDep) + + # Compute number and size of stepdowns, and final depth + if obj.LayerMode == 'Single-pass': + depthparams = [finDep] + else: + depthparams = [dp for dp in depthParams] + lenDP = len(depthparams) + PathLog.debug('Experimental Waterline depthparams:\n{}'.format(depthparams)) + + # Prepare PathDropCutter objects with STL data + # safePDC = self._planarGetPDC(safeSTL, depthparams[lenDP - 1], obj.SampleInterval.Value, useSafeCutter=False) + + buffer = self.cutter.getDiameter() * 2.0 + borderFace = Part.Face(self._makeExtendedBoundBox(JOB.Stock.Shape.BoundBox, buffer, 0.0)) + + # Get correct boundbox + if obj.BoundBox == 'Stock': + stockEnv = self._getShapeEnvelope(JOB.Stock.Shape) + bbFace = self._getCrossSection(stockEnv) # returned at Z=0.0 + elif obj.BoundBox == 'BaseBoundBox': + baseEnv = self._getShapeEnvelope(base.Shape) + bbFace = self._getCrossSection(baseEnv) # returned at Z=0.0 + + trimFace = borderFace.cut(bbFace) + if self.showDebugObjects is True: + TF = FreeCAD.ActiveDocument.addObject('Part::Feature', 'trimFace') + TF.Shape = trimFace + TF.purgeTouched() + self.tempGroup.addObject(TF) + + # Cycle through layer depths + CUTAREAS = self._getCutAreas(base.Shape, depthparams, bbFace, trimFace, borderFace) + if not CUTAREAS: + PathLog.error('No cross-section cut areas identified.') + return commands + + caCnt = 0 + ofst = obj.BoundaryAdjustment.Value + ofst -= self.radius # (self.radius + (tolrnc / 10.0)) + caLen = len(CUTAREAS) + lastCA = caLen - 1 + lastClearArea = None + lastCsHght = None + clearLastLayer = True + for ca in range(0, caLen): + area = CUTAREAS[ca] + csHght = area.BoundBox.ZMin + cont = False + caCnt += 1 + if area.Area > 0.0: + cont = True + caWireCnt = len(area.Wires) - 1 # first wire is boundFace wire + PathLog.debug('cutAreaWireCnt: {}'.format(caWireCnt)) + if self.showDebugObjects is True: + CA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'cutArea_{}'.format(caCnt)) + CA.Shape = area + CA.purgeTouched() + self.tempGroup.addObject(CA) + else: + PathLog.error('Cut area at {} is zero.'.format(round(csHght, 4))) + + # get offset wire(s) based upon cross-section cut area + if cont: + area.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - area.BoundBox.ZMin)) + activeArea = area.cut(trimFace) + activeAreaWireCnt = len(activeArea.Wires) # first wire is boundFace wire + PathLog.debug('activeAreaWireCnt: {}'.format(activeAreaWireCnt)) + if self.showDebugObjects is True: + CA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'activeArea_{}'.format(caCnt)) + CA.Shape = activeArea + CA.purgeTouched() + self.tempGroup.addObject(CA) + ofstArea = self._extractFaceOffset(obj, activeArea, ofst, makeComp=False) + if not ofstArea: + PathLog.error('No offset area returned for cut area depth: {}'.format(csHght)) + cont = False + + if cont: + # Identify solid areas in the offset data + ofstSolidFacesList = self._getSolidAreasFromPlanarFaces(ofstArea) + if ofstSolidFacesList: + clearArea = Part.makeCompound(ofstSolidFacesList) + if self.showDebugObjects is True: + CA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'clearArea_{}'.format(caCnt)) + CA.Shape = clearArea + CA.purgeTouched() + self.tempGroup.addObject(CA) + else: + cont = False + PathLog.error('ofstSolids is False.') + + if cont: + # Make waterline path for current CUTAREA depth (csHght) + commands.extend(self._wiresToWaterlinePath(obj, clearArea, csHght)) + clearArea.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - clearArea.BoundBox.ZMin)) + lastClearArea = clearArea + lastCsHght = csHght + + # Clear layer as needed + (useOfst, usePat, clearLastLayer) = self._clearLayer(obj, ca, lastCA, clearLastLayer) + ##if self.showDebugObjects is True and (usePat or useOfst): + ## OA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'clearPatternArea_{}'.format(round(csHght, 2))) + ## OA.Shape = clearArea + ## OA.purgeTouched() + ## self.tempGroup.addObject(OA) + if usePat: + commands.extend(self._makeCutPatternLayerPaths(JOB, obj, clearArea, csHght)) + if useOfst: + commands.extend(self._makeOffsetLayerPaths(JOB, obj, clearArea, csHght)) + # Efor + + if clearLastLayer: + (useOfst, usePat, cLL) = self._clearLayer(obj, 1, 1, False) + clearArea.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - lastClearArea.BoundBox.ZMin)) + if usePat: + commands.extend(self._makeCutPatternLayerPaths(JOB, obj, lastClearArea, lastCsHght)) + + if useOfst: + commands.extend(self._makeOffsetLayerPaths(JOB, obj, lastClearArea, lastCsHght)) + + PathLog.info("Waterline: All layer scans combined took " + str(time.time() - t_begin) + " s") + return commands + + def _getCutAreas(self, shape, depthparams, bbFace, trimFace, borderFace): + '''_getCutAreas(JOB, shape, depthparams, bbFace, borderFace) ... + Takes shape, depthparams and base-envelope-cross-section, and + returns a list of cut areas - one for each depth.''' + PathLog.debug('_getCutAreas()') + + CUTAREAS = list() + lastLayComp = None + isFirst = True + lenDP = len(depthparams) + + # Cycle through layer depths + for dp in range(0, lenDP): + csHght = depthparams[dp] + PathLog.debug('Depth {} is {}'.format(dp + 1, csHght)) + + # Get slice at depth of shape + csFaces = self._getModelCrossSection(shape, csHght) # returned at Z=0.0 + if not csFaces: + PathLog.error('No cross-section wires at {}'.format(csHght)) + else: + PathLog.debug('cross-section face count {}'.format(len(csFaces))) + if len(csFaces) > 0: + useFaces = self._getSolidAreasFromPlanarFaces(csFaces) + else: + useFaces = False + + if useFaces: + PathLog.debug('useFacesCnt: {}'.format(len(useFaces))) + compAdjFaces = Part.makeCompound(useFaces) + + if self.showDebugObjects is True: + CA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpSolids_{}'.format(dp + 1)) + CA.Shape = compAdjFaces + CA.purgeTouched() + self.tempGroup.addObject(CA) + + if isFirst: + allPrevComp = compAdjFaces + cutArea = borderFace.cut(compAdjFaces) + else: + preCutArea = borderFace.cut(compAdjFaces) + cutArea = preCutArea.cut(allPrevComp) # cut out higher layers to avoid cutting recessed areas + allPrevComp = allPrevComp.fuse(compAdjFaces) + cutArea.translate(FreeCAD.Vector(0.0, 0.0, csHght - cutArea.BoundBox.ZMin)) + CUTAREAS.append(cutArea) + isFirst = False + else: + PathLog.error('No waterline at depth: {} mm.'.format(csHght)) + # Efor + + if len(CUTAREAS) > 0: + return CUTAREAS + + return False + + def _wiresToWaterlinePath(self, obj, ofstPlnrShp, csHght): + PathLog.debug('_wiresToWaterlinePath()') + commands = list() + + # Translate path geometry to layer height + ofstPlnrShp.translate(FreeCAD.Vector(0.0, 0.0, csHght - ofstPlnrShp.BoundBox.ZMin)) + if self.showDebugObjects is True: + OA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'waterlinePathArea_{}'.format(round(csHght, 2))) + OA.Shape = ofstPlnrShp + OA.purgeTouched() + self.tempGroup.addObject(OA) + + commands.append(Path.Command('N (Cut Area {}.)'.format(round(csHght, 2)))) + for w in range(0, len(ofstPlnrShp.Wires)): + wire = ofstPlnrShp.Wires[w] + V = wire.Vertexes + if obj.CutMode == 'Climb': + lv = len(V) - 1 + startVect = FreeCAD.Vector(V[lv].X, V[lv].Y, V[lv].Z) + else: + startVect = FreeCAD.Vector(V[0].X, V[0].Y, V[0].Z) + + commands.append(Path.Command('N (Wire {}.)'.format(w))) + (cmds, endVect) = self._wireToPath(obj, wire, startVect) + commands.extend(cmds) + commands.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) + + return commands + + def _makeCutPatternLayerPaths(self, JOB, obj, clrAreaShp, csHght): + PathLog.debug('_makeCutPatternLayerPaths()') + commands = [] + + clrAreaShp.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - clrAreaShp.BoundBox.ZMin)) + pathGeom = self._planarMakePathGeom(obj, clrAreaShp) + pathGeom.translate(FreeCAD.Vector(0.0, 0.0, csHght - pathGeom.BoundBox.ZMin)) + # clrAreaShp.translate(FreeCAD.Vector(0.0, 0.0, csHght - clrAreaShp.BoundBox.ZMin)) + + if self.showDebugObjects is True: + OA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'pathGeom_{}'.format(round(csHght, 2))) + OA.Shape = pathGeom + OA.purgeTouched() + self.tempGroup.addObject(OA) + + # Convert pathGeom to gcode more efficiently + if True: + if obj.CutPattern == 'Offset': + commands.extend(self._makeOffsetLayerPaths(JOB, obj, clrAreaShp, csHght)) + else: + clrAreaShp.translate(FreeCAD.Vector(0.0, 0.0, csHght - clrAreaShp.BoundBox.ZMin)) + if obj.CutPattern == 'Line': + pntSet = self._pathGeomToLinesPointSet(obj, pathGeom) + elif obj.CutPattern == 'ZigZag': + pntSet = self._pathGeomToZigzagPointSet(obj, pathGeom) + elif obj.CutPattern in ['Circular', 'CircularZigZag']: + pntSet = self._pathGeomToArcPointSet(obj, pathGeom) + stpOVRS = self._getExperimentalWaterlinePaths(obj, pntSet, csHght) + # PathLog.debug('stpOVRS:\n{}'.format(stpOVRS)) + safePDC = False + cmds = self._clearGeomToPaths(JOB, obj, safePDC, stpOVRS, csHght) + commands.extend(cmds) + else: + # Use Path.fromShape() to convert edges to paths + for w in range(0, len(pathGeom.Edges)): + wire = pathGeom.Edges[w] + V = wire.Vertexes + if obj.CutMode == 'Climb': + lv = len(V) - 1 + startVect = FreeCAD.Vector(V[lv].X, V[lv].Y, V[lv].Z) + else: + startVect = FreeCAD.Vector(V[0].X, V[0].Y, V[0].Z) + + commands.append(Path.Command('N (Wire {}.)'.format(w))) + (cmds, endVect) = self._wireToPath(obj, wire, startVect) + commands.extend(cmds) + commands.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) + + return commands + + def _makeOffsetLayerPaths(self, JOB, obj, clrAreaShp, csHght): + PathLog.debug('_makeOffsetLayerPaths()') + PathLog.warning('Using `Offset` for clearing bottom layer.') + cmds = list() + # ofst = obj.BoundaryAdjustment.Value + ofst = 0.0 - self.cutOut # - self.cutter.getDiameter() # (self.radius + (tolrnc / 10.0)) + shape = clrAreaShp + cont = True + cnt = 0 + while cont: + ofstArea = self._extractFaceOffset(obj, shape, ofst, makeComp=True) + if not ofstArea: + PathLog.warning('No offset clearing area returned.') + break + for F in ofstArea.Faces: + cmds.extend(self._wiresToWaterlinePath(obj, F, csHght)) + shape = ofstArea + if cnt == 0: + ofst = 0.0 - self.cutOut # self.cutter.Diameter() + cnt += 1 + return cmds + + def _clearGeomToPaths(self, JOB, obj, safePDC, SCANDATA, csHght): + PathLog.debug('_clearGeomToPaths()') + + GCODE = [Path.Command('N (Beginning of Single-pass layer.)', {})] + tolrnc = JOB.GeometryTolerance.Value + prevDepth = obj.SafeHeight.Value + lenSCANDATA = len(SCANDATA) + gDIR = ['G3', 'G2'] + + if self.CutClimb is True: + gDIR = ['G2', 'G3'] + + # Send cutter to x,y position of first point on first line + first = SCANDATA[0][0][0] # [step][item][point] + GCODE.append(Path.Command('G0', {'X': first.x, 'Y': first.y, 'F': self.horizRapid})) + + # Cycle through step-over sections (line segments or arcs) + odd = True + lstStpEnd = None + prevDepth = obj.SafeHeight.Value # Not used for Single-pass + for so in range(0, lenSCANDATA): + cmds = list() + PRTS = SCANDATA[so] + lenPRTS = len(PRTS) + first = PRTS[0][0] # first point of arc/line stepover group + start = PRTS[0][0] # will change with each line/arc segment + last = None + cmds.append(Path.Command('N (Begin step {}.)'.format(so), {})) + + if so > 0: + if obj.CutPattern == 'CircularZigZag': + if odd is True: + odd = False + else: + odd = True + # minTrnsHght = self._getMinSafeTravelHeight(safePDC, lstStpEnd, first) # Check safe travel height against fullSTL + minTrnsHght = obj.SafeHeight.Value + # cmds.append(Path.Command('N (Transition: last, first: {}, {}: minSTH: {})'.format(lstStpEnd, first, minTrnsHght), {})) + cmds.extend(self._stepTransitionCmds(obj, lstStpEnd, first, minTrnsHght, tolrnc)) + + # Cycle through current step-over parts + for i in range(0, lenPRTS): + prt = PRTS[i] + lenPrt = len(prt) + # PathLog.debug('prt: {}'.format(prt)) + if prt == 'BRK': + nxtStart = PRTS[i + 1][0] + # minSTH = self._getMinSafeTravelHeight(safePDC, last, nxtStart) # Check safe travel height against fullSTL + minSTH = obj.SafeHeight.Value + cmds.append(Path.Command('N (Break)', {})) + cmds.extend(self._breakCmds(obj, last, nxtStart, minSTH, tolrnc)) + else: + cmds.append(Path.Command('N (part {}.)'.format(i + 1), {})) + if obj.CutPattern in ['Line', 'ZigZag']: + start, last = prt + cmds.append(Path.Command('G1', {'X': start.x, 'Y': start.y, 'Z': start.z, 'F': self.horizFeed})) + cmds.append(Path.Command('G1', {'X': last.x, 'Y': last.y, 'F': self.horizFeed})) + elif obj.CutPattern in ['Circular', 'CircularZigZag']: + start, last, centPnt, cMode = prt + gcode = self._makeGcodeArc(start, last, odd, gDIR, tolrnc) + cmds.extend(gcode) + cmds.append(Path.Command('N (End of step {}.)'.format(so), {})) + GCODE.extend(cmds) # save line commands + lstStpEnd = last + # Efor + + return GCODE + + def _getSolidAreasFromPlanarFaces(self, csFaces): + PathLog.debug('_getSolidAreasFromPlanarFaces()') + holds = list() + cutFaces = list() + useFaces = list() + lenCsF = len(csFaces) + PathLog.debug('lenCsF: {}'.format(lenCsF)) + + if lenCsF == 1: + useFaces = csFaces + else: + fIds = list() + aIds = list() + pIds = list() + cIds = list() + + for af in range(0, lenCsF): + fIds.append(af) # face ids + aIds.append(af) # face ids + pIds.append(-1) # parent ids + cIds.append(False) # cut ids + holds.append(False) + + while len(fIds) > 0: + li = fIds.pop() + low = csFaces[li] # senior face + pIds = self._idInternalFeature(csFaces, fIds, pIds, li, low) + # Ewhile + ##PathLog.info('fIds: {}'.format(fIds)) + ##PathLog.info('pIds: {}'.format(pIds)) + + for af in range(lenCsF - 1, -1, -1): # cycle from last item toward first + ##PathLog.info('af: {}'.format(af)) + prnt = pIds[af] + ##PathLog.info('prnt: {}'.format(prnt)) + if prnt == -1: + stack = -1 + else: + stack = [af] + # get_face_ids_to_parent + stack.insert(0, prnt) + nxtPrnt = pIds[prnt] + # find af value for nxtPrnt + while nxtPrnt != -1: + stack.insert(0, nxtPrnt) + nxtPrnt = pIds[nxtPrnt] + cIds[af] = stack + # PathLog.debug('cIds: {}\n'.format(cIds)) + + for af in range(0, lenCsF): + # PathLog.debug('af is {}'.format(af)) + pFc = cIds[af] + if pFc == -1: + # Simple, independent region + holds[af] = csFaces[af] # place face in hold + # PathLog.debug('pFc == -1') + else: + # Compound region + # PathLog.debug('pFc is not -1') + cnt = len(pFc) + if cnt % 2.0 == 0.0: + # even is donut cut + # PathLog.debug('cnt is even') + inr = pFc[cnt - 1] + otr = pFc[cnt - 2] + # PathLog.debug('inr / otr: {} / {}'.format(inr, otr)) + holds[otr] = holds[otr].cut(csFaces[inr]) + else: + # odd is floating solid + # PathLog.debug('cnt is ODD') + holds[af] = csFaces[af] + # Efor + + for af in range(0, lenCsF): + if holds[af]: + useFaces.append(holds[af]) # save independent solid + + # Eif + + if len(useFaces) > 0: + return useFaces + + return False + + def _getModelCrossSection(self, shape, csHght): + PathLog.debug('_getCrossSection()') + wires = list() + + def byArea(fc): + return fc.Area + + for i in shape.slice(FreeCAD.Vector(0, 0, 1), csHght): + wires.append(i) + + if len(wires) > 0: + for w in wires: + if w.isClosed() is False: + return False + FCS = list() + for w in wires: + w.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - w.BoundBox.ZMin)) + FCS.append(Part.Face(w)) + FCS.sort(key=byArea, reverse=True) + return FCS + else: + PathLog.debug(' -No wires from .slice() method') + + return False + + def _isInBoundBox(self, outShp, inShp): + obb = outShp.BoundBox + ibb = inShp.BoundBox + + if obb.XMin < ibb.XMin: + if obb.XMax > ibb.XMax: + if obb.YMin < ibb.YMin: + if obb.YMax > ibb.YMax: + return True + return False + + def _idInternalFeature(self, csFaces, fIds, pIds, li, low): + Ids = list() + for i in fIds: + Ids.append(i) + while len(Ids) > 0: + hi = Ids.pop() + high = csFaces[hi] + if self._isInBoundBox(high, low): + cmn = high.common(low) + if cmn.Area > 0.0: + pIds[li] = hi + break + # Ewhile + return pIds + + def _wireToPath(self, obj, wire, startVect): + '''_wireToPath(obj, wire, startVect) ... wire to path.''' + PathLog.track() + + paths = [] + pathParams = {} # pylint: disable=assignment-from-no-return + V = wire.Vertexes + + pathParams['shapes'] = [wire] + pathParams['feedrate'] = self.horizFeed + pathParams['feedrate_v'] = self.vertFeed + pathParams['verbose'] = True + pathParams['resume_height'] = obj.SafeHeight.Value + pathParams['retraction'] = obj.ClearanceHeight.Value + pathParams['return_end'] = True + # Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers + pathParams['preamble'] = False + pathParams['start'] = startVect + + (pp, end_vector) = Path.fromShapes(**pathParams) + paths.extend(pp.Commands) + # PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector)) + + self.endVector = end_vector # pylint: disable=attribute-defined-outside-init + + return (paths, end_vector) + + def _makeExtendedBoundBox(self, wBB, bbBfr, zDep): + pl = FreeCAD.Placement() + pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0) + pl.Base = FreeCAD.Vector(0, 0, 0) + + p1 = FreeCAD.Vector(wBB.XMin - bbBfr, wBB.YMin - bbBfr, zDep) + p2 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMin - bbBfr, zDep) + p3 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMax + bbBfr, zDep) + p4 = FreeCAD.Vector(wBB.XMin - bbBfr, wBB.YMax + bbBfr, zDep) + bb = Part.makePolygon([p1, p2, p3, p4, p1]) + + return bb + + def _makeGcodeArc(self, strtPnt, endPnt, odd, gDIR, tolrnc): + cmds = list() + isCircle = False + inrPnt = None + gdi = 0 + if odd is True: + gdi = 1 + + # Test if pnt set is circle + if abs(strtPnt.x - endPnt.x) < tolrnc: + if abs(strtPnt.y - endPnt.y) < tolrnc: + isCircle = True + isCircle = False + + if isCircle is True: + # convert LN to G2/G3 arc, consolidating GCode + # https://wiki.shapeoko.com/index.php/G-Code#G2_-_clockwise_arc + # https://www.cnccookbook.com/cnc-g-code-arc-circle-g02-g03/ + # Dividing circle into two arcs allows for G2/G3 on inclined surfaces + + # ijk = self.tmpCOM - strtPnt # vector from start to center + ijk = self.tmpCOM - strtPnt # vector from start to center + xyz = self.tmpCOM.add(ijk) # end point + cmds.append(Path.Command('G1', {'X': strtPnt.x, 'Y': strtPnt.y, 'Z': strtPnt.z, 'F': self.horizFeed})) + cmds.append(Path.Command(gDIR[gdi], {'X': xyz.x, 'Y': xyz.y, 'Z': xyz.z, + 'I': ijk.x, 'J': ijk.y, 'K': ijk.z, # leave same xyz.z height + 'F': self.horizFeed})) + cmds.append(Path.Command('G1', {'X': xyz.x, 'Y': xyz.y, 'Z': xyz.z, 'F': self.horizFeed})) + ijk = self.tmpCOM - xyz # vector from start to center + rst = strtPnt # end point + cmds.append(Path.Command(gDIR[gdi], {'X': rst.x, 'Y': rst.y, 'Z': rst.z, + 'I': ijk.x, 'J': ijk.y, 'K': ijk.z, # leave same xyz.z height + 'F': self.horizFeed})) + cmds.append(Path.Command('G1', {'X': strtPnt.x, 'Y': strtPnt.y, 'Z': strtPnt.z, 'F': self.horizFeed})) + else: + # ijk = self.tmpCOM - strtPnt + ijk = self.tmpCOM.sub(strtPnt) # vector from start to center + xyz = endPnt + cmds.append(Path.Command('G1', {'X': strtPnt.x, 'Y': strtPnt.y, 'Z': strtPnt.z, 'F': self.horizFeed})) + cmds.append(Path.Command(gDIR[gdi], {'X': xyz.x, 'Y': xyz.y, 'Z': xyz.z, + 'I': ijk.x, 'J': ijk.y, 'K': ijk.z, # leave same xyz.z height + 'F': self.horizFeed})) + cmds.append(Path.Command('G1', {'X': endPnt.x, 'Y': endPnt.y, 'Z': endPnt.z, 'F': self.horizFeed})) + + return cmds + + def _clearLayer(self, obj, ca, lastCA, clearLastLayer): + PathLog.debug('_clearLayer()') + usePat = False + useOfst = False + + if obj.ClearLastLayer == 'Off': + if obj.CutPattern != 'None': + usePat = True + else: + if ca == lastCA: + PathLog.debug('... Clearing bottom layer.') + if obj.ClearLastLayer == 'Offset': + obj.CutPattern = 'None' + useOfst = True + else: + obj.CutPattern = obj.ClearLastLayer + usePat = True + clearLastLayer = False + + return (useOfst, usePat, clearLastLayer) # Support functions for both dropcutter and waterline operations def isPointOnLine(self, strtPnt, endPnt, pointP): @@ -1959,18 +3356,6 @@ class ObjectWaterline(PathOp.ObjectOp): return True - def holdStopCmds(self, obj, zMax, pd, p2, txt): - '''holdStopCmds(obj, zMax, pd, p2, txt) ... Gcode commands to be executed at beginning of hold.''' - cmds = [] - msg = 'N (' + txt + ')' - cmds.append(Path.Command(msg, {})) # Raise cutter rapid to zMax in line of travel - cmds.append(Path.Command('G0', {'Z': zMax, 'F': self.vertRapid})) # Raise cutter rapid to zMax in line of travel - cmds.append(Path.Command('G0', {'X': p2.x, 'Y': p2.y, 'F': self.horizRapid})) # horizontal rapid to current XY coordinate - if zMax != pd: - cmds.append(Path.Command('G0', {'Z': pd, 'F': self.vertRapid})) # drop cutter down rapidly to prevDepth depth - cmds.append(Path.Command('G0', {'Z': p2.z, 'F': self.vertFeed})) # drop cutter down to current Z depth, returning to normal cut path and speed - return cmds - def resetOpVariables(self, all=True): '''resetOpVariables() ... Reset class variables used for instance of operation.''' self.holdPoint = None @@ -2083,33 +3468,19 @@ class ObjectWaterline(PathOp.ObjectOp): PathLog.error('Unable to set OCL cutter.') return False - def _getMinSafeTravelHeight(self, pdc, p1, p2, minDep=None): - A = (p1.x, p1.y) - B = (p2.x, p2.y) - LINE = self._planarDropCutScan(pdc, A, B) - zMax = LINE[0].z - for p in LINE: - if p.z > zMax: - zMax = p.z - if minDep is not None: - if zMax < minDep: - zMax = minDep - return zMax - def SetupProperties(): ''' SetupProperties() ... Return list of properties required for operation.''' setup = [] setup.append('Algorithm') + setup.append('AngularDeflection') setup.append('AvoidLastX_Faces') setup.append('AvoidLastX_InternalFeatures') setup.append('BoundBox') setup.append('BoundaryAdjustment') setup.append('CircularCenterAt') setup.append('CircularCenterCustom') - setup.append('CircularUseG2G3') - setup.append('InternalFeaturesCut') - setup.append('InternalFeaturesAdjustment') + setup.append('ClearLastLayer') setup.append('CutMode') setup.append('CutPattern') setup.append('CutPatternAngle') @@ -2118,7 +3489,10 @@ def SetupProperties(): setup.append('GapSizes') setup.append('GapThreshold') setup.append('HandleMultipleFeatures') + setup.append('InternalFeaturesCut') + setup.append('InternalFeaturesAdjustment') setup.append('LayerMode') + setup.append('LinearDeflection') setup.append('OptimizeStepOverTransitions') setup.append('ProfileEdges') setup.append('BoundaryEnforcement') From ebb383cc6c54fcb94c923e52dc7d127d96ea12d7 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Mon, 30 Mar 2020 23:22:17 -0500 Subject: [PATCH 108/117] Path: Synchronize tooltips. Apply `DepthOffset`. Hide properties. Sync tooltip text between 3D Surface and Waterline. Also, apply `DepthOffset` to Experimental Waterline algorithm. Hide some properties in Data tab. --- src/Mod/Path/PathScripts/PathSurface.py | 56 +++++++++++------------ src/Mod/Path/PathScripts/PathWaterline.py | 33 +++++++------ 2 files changed, 47 insertions(+), 42 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index a485225402..19af63586d 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -97,7 +97,7 @@ class ObjectSurface(PathOp.ObjectOp): PROPS = [ ("App::PropertyBool", "ShowTempObjects", "Debug", - QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Show the temporary path construction objects when module is in DEBUG mode.")), ("App::PropertyDistance", "AngularDeflection", "Mesh Conversion", QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate mesh. Smaller values increase processing time a lot.")), @@ -117,62 +117,62 @@ class ObjectSurface(PathOp.ObjectOp): ("App::PropertyFloat", "StopIndex", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")), - ("App::PropertyEnumeration", "BoundBox", "Surface", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Should the operation be limited by the stock object or by the bounding box of the base object")), ("App::PropertyEnumeration", "ScanType", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan.")), - ("App::PropertyDistance", "SampleInterval", "Surface", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The Sample Interval. Small values cause long wait times")), - ("App::PropertyInteger", "AvoidLastX_Faces", "Selected Face(s) Settings", + ("App::PropertyInteger", "AvoidLastX_Faces", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.")), - ("App::PropertyBool", "AvoidLastX_InternalFeatures", "Selected Face(s) Settings", + ("App::PropertyBool", "AvoidLastX_InternalFeatures", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces.")), - ("App::PropertyDistance", "BoundaryAdjustment", "Selected Face(s) Settings", + ("App::PropertyDistance", "BoundaryAdjustment", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.")), - ("App::PropertyBool", "BoundaryEnforcement", "Selected Face(s) Settings", + ("App::PropertyBool", "BoundaryEnforcement", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).")), - ("App::PropertyEnumeration", "HandleMultipleFeatures", "Selected Face(s) Settings", + ("App::PropertyEnumeration", "HandleMultipleFeatures", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features.")), - ("App::PropertyDistance", "InternalFeaturesAdjustment", "Selected Face(s) Settings", + ("App::PropertyDistance", "InternalFeaturesAdjustment", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.")), - ("App::PropertyBool", "InternalFeaturesCut", "Selected Face(s) Settings", + ("App::PropertyBool", "InternalFeaturesCut", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face.")), + ("App::PropertyEnumeration", "BoundBox", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Select the overall boundary for the operation. ")), ("App::PropertyVectorDistance", "CircularCenterCustom", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the start point for circular cut patterns.")), ("App::PropertyEnumeration", "CircularCenterAt", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose what point to start the ciruclar pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose location of the center point for starting the ciruclar pattern.")), ("App::PropertyEnumeration", "CutMode", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part: Climb(ClockWise) or Conventional(CounterClockWise)")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the direction for the cutting tool to engage the material: Climb (ClockWise) or Conventional (CounterClockWise)")), ("App::PropertyEnumeration", "CutPattern", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the geometric clearing pattern to use for the operation.")), ("App::PropertyFloat", "CutPatternAngle", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "The yaw angle used for certain clearing patterns")), ("App::PropertyBool", "CutPatternReversed", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the order of the step-overs will be reversed; the operation will begin cutting the outer most line/arc, and work toward the inner most line/arc.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Reverse the cut order of the stepover paths. For circular cut patterns, begin at the outside and work toward the center.")), ("App::PropertyDistance", "DepthOffset", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Z-axis offset from the surface of the object")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the Z-axis depth offset from the target surface.")), ("App::PropertyEnumeration", "LayerMode", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The completion mode for the operation: single or multi-pass")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Complete the operation in a single pass at depth, or mulitiple passes to final depth.")), ("App::PropertyEnumeration", "ProfileEdges", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")), + ("App::PropertyDistance", "SampleInterval", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the sampling resolution. Smaller values quickly increase processing time.")), ("App::PropertyPercent", "StepOver", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the stepover percentage, based on the tool's diameter.")), - ("App::PropertyBool", "OptimizeLinearPaths", "Surface Optimization", + ("App::PropertyBool", "OptimizeLinearPaths", "Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.")), - ("App::PropertyBool", "OptimizeStepOverTransitions", "Surface Optimization", + ("App::PropertyBool", "OptimizeStepOverTransitions", "Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.")), - ("App::PropertyBool", "CircularUseG2G3", "Surface Optimization", + ("App::PropertyBool", "CircularUseG2G3", "Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.")), - ("App::PropertyDistance", "GapThreshold", "Surface Optimization", + ("App::PropertyDistance", "GapThreshold", "Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.")), - ("App::PropertyString", "GapSizes", "Surface Optimization", + ("App::PropertyString", "GapSizes", "Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")), ("App::PropertyVectorDistance", "StartPoint", "Start Point", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "The custom start point for the path of this operation")), ("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "Make True, if specifying a Start Point")) ] @@ -199,7 +199,7 @@ class ObjectSurface(PathOp.ObjectOp): 'BoundBox': ['BaseBoundBox', 'Stock'], 'CircularCenterAt': ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'], 'CutMode': ['Conventional', 'Climb'], - 'CutPattern': ['Line', 'ZigZag', 'Circular', 'CircularZigZag'], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] + 'CutPattern': ['Line', 'Circular', 'CircularZigZag', 'ZigZag'], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] 'DropCutterDir': ['X', 'Y'], 'HandleMultipleFeatures': ['Collectively', 'Individually'], 'LayerMode': ['Single-pass', 'Multi-pass'], diff --git a/src/Mod/Path/PathScripts/PathWaterline.py b/src/Mod/Path/PathScripts/PathWaterline.py index b2d3fdc197..bdc4fda076 100644 --- a/src/Mod/Path/PathScripts/PathWaterline.py +++ b/src/Mod/Path/PathScripts/PathWaterline.py @@ -22,11 +22,6 @@ # * USA * # * * # *************************************************************************** -# * * -# * Additional modifications and contributions beginning 2019 * -# * by Russell Johnson 2020-03-30 22:27 CST * -# * * -# *************************************************************************** from __future__ import print_function @@ -83,7 +78,7 @@ class ObjectWaterline(PathOp.ObjectOp): return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureCoolant | PathOp.FeatureBaseFaces def initOperation(self, obj): - '''initPocketOp(obj) ... + '''initPocketOp(obj) ... Initialize the operation - property creation and property editor status.''' self.initOpProperties(obj) @@ -98,7 +93,7 @@ class ObjectWaterline(PathOp.ObjectOp): '''initOpProperties(obj) ... create operation specific properties''' PROPS = [ ("App::PropertyBool", "ShowTempObjects", "Debug", - QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Show the temporary path construction objects when module is in DEBUG mode.")), ("App::PropertyDistance", "AngularDeflection", "Mesh Conversion", QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values increase processing time a lot.")), @@ -125,27 +120,27 @@ class ObjectWaterline(PathOp.ObjectOp): ("App::PropertyEnumeration", "BoundBox", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Select the overall boundary for the operation. ")), ("App::PropertyVectorDistance", "CircularCenterCustom", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the start point for circular cut patterns.")), ("App::PropertyEnumeration", "CircularCenterAt", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose center point to start the ciruclar pattern.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose location of the center point for starting the ciruclar pattern.")), ("App::PropertyEnumeration", "ClearLastLayer", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Set to clear last layer in a `Multi-pass` operation.")), ("App::PropertyEnumeration", "CutMode", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the direction for the cutting tool to engage the material: Climb (ClockWise) or Conventional (CounterClockWise)")), ("App::PropertyEnumeration", "CutPattern", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the geometric clearing pattern to use.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the geometric clearing pattern to use for the operation.")), ("App::PropertyFloat", "CutPatternAngle", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "The yaw angle used for certain clearing patterns")), ("App::PropertyBool", "CutPatternReversed", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Reverse the cut order of the stepover paths. For circular cut patterns, begin at the outside and work toward the center.")), ("App::PropertyDistance", "DepthOffset", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the Z-axis depth offset from the target surface.")), ("App::PropertyEnumeration", "LayerMode", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The step down mode for the operation.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Complete the operation in a single pass at depth, or mulitiple passes to final depth.")), ("App::PropertyEnumeration", "ProfileEdges", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")), ("App::PropertyDistance", "SampleInterval", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the sampling resolution. Smaller values increase processing time.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the sampling resolution. Smaller values quickly increase processing time.")), ("App::PropertyPercent", "StepOver", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the stepover percentage, based on the tool's diameter.")), @@ -159,7 +154,7 @@ class ObjectWaterline(PathOp.ObjectOp): QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")), ("App::PropertyVectorDistance", "StartPoint", "Start Point", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "The custom start point for the path of this operation")), ("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "Make True, if specifying a Start Point")) ] @@ -204,6 +199,14 @@ class ObjectWaterline(PathOp.ObjectOp): obj.setEditorMode('ProfileEdges', hide) obj.setEditorMode('InternalFeaturesAdjustment', hide) obj.setEditorMode('InternalFeaturesCut', hide) + obj.setEditorMode('GapSizes', hide) + obj.setEditorMode('GapThreshold', hide) + obj.setEditorMode('AvoidLastX_Faces', hide) + obj.setEditorMode('AvoidLastX_InternalFeatures', hide) + obj.setEditorMode('BoundaryAdjustment', hide) + obj.setEditorMode('HandleMultipleFeatures', hide) + if hasattr(obj, 'EnableRotation'): + obj.setEditorMode('EnableRotation', hide) if obj.CutPattern == 'None': show = 2 hide = 2 @@ -265,6 +268,7 @@ class ObjectWaterline(PathOp.ObjectOp): obj.ClearLastLayer = 'Off' obj.StepOver = 100 obj.CutPatternAngle = 0.0 + obj.DepthOffset.Value = 0.0 obj.SampleInterval.Value = 1.0 obj.BoundaryAdjustment.Value = 0.0 obj.InternalFeaturesAdjustment.Value = 0.0 @@ -2793,6 +2797,7 @@ class ObjectWaterline(PathOp.ObjectOp): for ca in range(0, caLen): area = CUTAREAS[ca] csHght = area.BoundBox.ZMin + csHght += obj.DepthOffset.Value cont = False caCnt += 1 if area.Area > 0.0: From f54ca61463fd129b1aee8d5bfba29c5a0e60630b Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Mon, 10 Feb 2020 20:06:15 -0600 Subject: [PATCH 109/117] Draft: move and rename DraftSelectPlane --- src/Mod/Draft/CMakeLists.txt | 2 +- src/Mod/Draft/DraftTools.py | 2 +- .../{DraftSelectPlane.py => draftguitools/gui_selectplane.py} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/Mod/Draft/{DraftSelectPlane.py => draftguitools/gui_selectplane.py} (100%) diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index a8bc314f46..6aa9fbbf5b 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -15,7 +15,6 @@ SET(Draft_SRCS_base DraftLayer.py DraftEdit.py DraftFillet.py - DraftSelectPlane.py WorkingPlane.py getSVG.py TestDraft.py @@ -81,6 +80,7 @@ SET(Draft_GUI_tools draftguitools/gui_circulararray.py draftguitools/gui_orthoarray.py draftguitools/gui_polararray.py + draftguitools/gui_selectplane.py draftguitools/gui_arrays.py draftguitools/gui_snaps.py draftguitools/gui_snapper.py diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 7629b91c65..bc9d16c973 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -58,7 +58,7 @@ if not hasattr(FreeCAD, "DraftWorkingPlane"): import DraftEdit # import DraftFillet -import DraftSelectPlane +import draftguitools.gui_selectplane #--------------------------------------------------------------------------- # Preflight stuff diff --git a/src/Mod/Draft/DraftSelectPlane.py b/src/Mod/Draft/draftguitools/gui_selectplane.py similarity index 100% rename from src/Mod/Draft/DraftSelectPlane.py rename to src/Mod/Draft/draftguitools/gui_selectplane.py From 48f2ba9c7432d9aa09fcdb936ee1abdceef20c36 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 11 Feb 2020 13:23:54 -0600 Subject: [PATCH 110/117] Draft: improve the Python style gui_selectplane Also import `todo` and `translate` from the utils module instead of from `DraftGui`. Otherwise we have to import the entire `DraftGui` module which creates dependency problems when we want to use some functions without the graphical interface. --- .../Draft/draftguitools/gui_selectplane.py | 111 ++++++++---------- 1 file changed, 49 insertions(+), 62 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_selectplane.py b/src/Mod/Draft/draftguitools/gui_selectplane.py index 72fa0988f0..27ddfd94f7 100644 --- a/src/Mod/Draft/draftguitools/gui_selectplane.py +++ b/src/Mod/Draft/draftguitools/gui_selectplane.py @@ -1,26 +1,26 @@ # -*- coding: utf8 -*- -#*************************************************************************** -#* Copyright (c) 2019 Yorik van Havre * -#* * -#* 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. * -#* * -#* This program is distributed in the hope that it will be useful, * -#* but WITHOUT ANY WARRANTY; without even the implied warranty of * -#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -#* GNU Library General Public License for more details. * -#* * -#* You should have received a copy of the GNU Library General Public * -#* License along with this program; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** +# *************************************************************************** +# * Copyright (c) 2019 Yorik van Havre * +# * * +# * 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. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** -__title__="FreeCAD Draft Workbench GUI Tools - Working plane-related tools" +__title__ = "FreeCAD Draft Workbench GUI Tools - Working plane-related tools" __author__ = "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, Dmitry Chigrin" __url__ = "https://www.freecadweb.org" @@ -30,43 +30,43 @@ import FreeCADGui import math import Draft import DraftVecUtils -from DraftGui import todo, translate +from draftutils.todo import todo +from draftutils.translate import translate + def QT_TRANSLATE_NOOP(ctx,txt): return txt class Draft_SelectPlane: - - """The Draft_SelectPlane FreeCAD command definition""" + """The Draft_SelectPlane FreeCAD command definition.""" def __init__(self): - self.ac = "FreeCAD.DraftWorkingPlane.alignToPointAndAxis" self.param = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") self.states = [] def GetResources(self): - + """Set icon, menu and tooltip.""" return {'Pixmap' : 'Draft_SelectPlane', 'Accel' : "W, P", 'MenuText': QT_TRANSLATE_NOOP("Draft_SelectPlane", "SelectPlane"), 'ToolTip' : QT_TRANSLATE_NOOP("Draft_SelectPlane", "Select a working plane for geometry creation")} def IsActive(self): - + """Return True when this command should be available.""" if FreeCADGui.ActiveDocument: return True else: return False def Activated(self): - + """Execute this when the command is called.""" # reset variables self.view = Draft.get3DView() self.wpButton = FreeCADGui.draftToolBar.wplabel FreeCAD.DraftWorkingPlane.setup() - + # write current WP if states are empty if not self.states: p = FreeCAD.DraftWorkingPlane @@ -167,9 +167,7 @@ class Draft_SelectPlane: self.finish() def handle(self): - - """tries to build a WP. Returns True if successful""" - + """Build a working plane. Return True if successful.""" sel = FreeCADGui.Selection.getSelectionEx() if len(sel) == 1: sel = sel[0] @@ -276,7 +274,7 @@ class Draft_SelectPlane: return True return False - def getCenterPoint(self,x,y,z): + def getCenterPoint(self, x, y, z): if not self.taskd.form.checkCenter.isChecked(): return FreeCAD.Vector() @@ -293,19 +291,15 @@ class Draft_SelectPlane: cp = cam1.add(vcam2) return cp - def tostr(self,v): - - """makes a string from a vector or tuple""" - + def tostr(self, v): + """Make a string from a vector or tuple.""" return "FreeCAD.Vector("+str(v[0])+","+str(v[1])+","+str(v[2])+")" def getOffset(self): - - """returns the offset value as a float in mm""" - + """Return the offset value as a float in mm.""" try: o = float(self.taskd.form.fieldOffset.text()) - except: + except Exception: o = FreeCAD.Units.Quantity(self.taskd.form.fieldOffset.text()) o = o.Value return o @@ -377,16 +371,16 @@ class Draft_SelectPlane: # calculate delta p = FreeCAD.Vector(c.position.getValue().getValue()) pp = FreeCAD.DraftWorkingPlane.projectPoint(p) - delta = pp.negative() # to bring it above the (0,0) point + delta = pp.negative() # to bring it above the (0,0) point np = p.add(delta) c.position.setValue(tuple(np)) self.finish() def onClickPrevious(self): - + p = FreeCAD.DraftWorkingPlane if len(self.states) > 1: - self.states.pop() # discard the last one + self.states.pop() # discard the last one s = self.states[-1] p.u = s[0] p.v = s[1] @@ -416,13 +410,11 @@ class Draft_SelectPlane: def onSetSnapRadius(self,i): self.param.SetInt("snapRange",i) - if hasattr(FreeCADGui,"Snapper"): + if hasattr(FreeCADGui, "Snapper"): FreeCADGui.Snapper.showradius() def display(self,arg): - - """sets the text of the WP button""" - + """Set the text of the working plane button.""" o = self.getOffset() if o: if o > 0: @@ -450,10 +442,8 @@ class Draft_SelectPlane: FreeCADGui.doCommandGui("FreeCADGui.Snapper.setGrid()") - class SelectPlane_TaskPanel: - - '''The editmode TaskPanel for Arch Material objects''' + """The task panel definition of the Draft_SelectPlane command.""" def __init__(self): @@ -462,30 +452,28 @@ class SelectPlane_TaskPanel: def getStandardButtons(self): - return 2097152 #int(QtGui.QDialogButtonBox.Close) - + return 2097152 # int(QtGui.QDialogButtonBox.Close) class Draft_SetWorkingPlaneProxy(): - """The Draft_SetWorkingPlaneProxy FreeCAD command definition""" def GetResources(self): - - return {'Pixmap' : 'Draft_SelectPlane', + """Set icon, menu and tooltip.""" + return {'Pixmap': 'Draft_SelectPlane', 'MenuText': QT_TRANSLATE_NOOP("Draft_SetWorkingPlaneProxy", "Create Working Plane Proxy"), 'ToolTip': QT_TRANSLATE_NOOP("Draft_SetWorkingPlaneProxy", "Creates a proxy object from the current working plane")} def IsActive(self): - + """Return True when this command should be available.""" if FreeCADGui.ActiveDocument: return True else: return False def Activated(self): - - if hasattr(FreeCAD,"DraftWorkingPlane"): + """Execute this when the command is called.""" + if hasattr(FreeCAD, "DraftWorkingPlane"): FreeCAD.ActiveDocument.openTransaction("Create WP proxy") FreeCADGui.addModule("Draft") FreeCADGui.doCommand("Draft.makeWorkingPlaneProxy(FreeCAD.DraftWorkingPlane.getPlacement())") @@ -493,6 +481,5 @@ class Draft_SetWorkingPlaneProxy(): FreeCAD.ActiveDocument.commitTransaction() - -FreeCADGui.addCommand('Draft_SelectPlane',Draft_SelectPlane()) -FreeCADGui.addCommand('Draft_SetWorkingPlaneProxy',Draft_SetWorkingPlaneProxy()) +FreeCADGui.addCommand('Draft_SelectPlane', Draft_SelectPlane()) +FreeCADGui.addCommand('Draft_SetWorkingPlaneProxy', Draft_SetWorkingPlaneProxy()) From 6f028e0395f80e96ef1ae1d28845c58ce7d0faf5 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Wed, 12 Feb 2020 21:47:29 -0600 Subject: [PATCH 111/117] Draft: update unit test for gui_selectplane --- src/Mod/Draft/drafttests/test_import_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Draft/drafttests/test_import_tools.py b/src/Mod/Draft/drafttests/test_import_tools.py index ada1eeabea..77bd030f4a 100644 --- a/src/Mod/Draft/drafttests/test_import_tools.py +++ b/src/Mod/Draft/drafttests/test_import_tools.py @@ -68,7 +68,7 @@ class DraftImportTools(unittest.TestCase): def test_import_gui_draftplane(self): """Import Draft SelectPlane.""" - module = "DraftSelectPlane" + module = "draftguitools.gui_selectplane" if not App.GuiUp: aux._no_gui(module) self.assertTrue(True) From d1d85c62078fab9f32a0d09f7abccffcbcc661f8 Mon Sep 17 00:00:00 2001 From: donovaly Date: Tue, 31 Mar 2020 01:04:10 +0200 Subject: [PATCH 112/117] [Tools] extend ThumbNail Provider for .FCBak - as requested in the forum, the thumbnail provider should also work for *.FCBak file, see https://forum.freecadweb.org/viewtopic.php?f=4&t=10775&p=381936#p378680 --- src/Tools/thumbs/ThumbnailProvider/Main.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tools/thumbs/ThumbnailProvider/Main.cpp b/src/Tools/thumbs/ThumbnailProvider/Main.cpp index 8b61c2721a..7aacc17622 100644 --- a/src/Tools/thumbs/ThumbnailProvider/Main.cpp +++ b/src/Tools/thumbs/ThumbnailProvider/Main.cpp @@ -107,7 +107,8 @@ STDAPI DllRegisterServer() {HKEY_CLASSES_ROOT, L"CLSID\\" szCLSID_SampleThumbnailProvider L"\\InprocServer32", NULL, REG_SZ, (DWORD_PTR)szModule}, {HKEY_CLASSES_ROOT, L"CLSID\\" szCLSID_SampleThumbnailProvider L"\\InprocServer32", L"ThreadingModel", REG_SZ, (DWORD_PTR)L"Apartment"}, //{HKEY_CLASSES_ROOT, L".FCStd\\shellex", L"Trick only here to create shellex when not existing",REG_DWORD, 1}, - {HKEY_CLASSES_ROOT, L".FCStd\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}", NULL, REG_SZ, (DWORD_PTR)szCLSID_SampleThumbnailProvider} + {HKEY_CLASSES_ROOT, L".FCStd\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}", NULL, REG_SZ, (DWORD_PTR)szCLSID_SampleThumbnailProvider}, + {HKEY_CLASSES_ROOT, L".FCBak\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}", NULL, REG_SZ, (DWORD_PTR)szCLSID_SampleThumbnailProvider} }; return CreateRegistryKeys(keys, ARRAYSIZE(keys)); From f018dbf1593624ad747ae5d6d147e99ec52964c2 Mon Sep 17 00:00:00 2001 From: carlopav Date: Sat, 28 Mar 2020 08:25:28 +0100 Subject: [PATCH 113/117] [Draft] Fix to mirror tool the normal is not computed with the view, but with the working plane if active. ref. https://forum.freecadweb.org/viewtopic.php?f=23&t=44301&p=380632#p380370 --- src/Mod/Draft/Draft.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index b93d067594..c125005009 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -2222,38 +2222,47 @@ def getCloneBase(obj,strict=False): return obj -def mirror(objlist,p1,p2): - """mirror(objlist,p1,p2,[clone]): creates a mirrored version of the given object(s) - along an axis that passes through the two vectors p1 and p2.""" +def mirror(objlist, p1, p2): + """mirror(objlist, p1, p2) + creates a Part::Mirror of the given object(s), along a plane defined + by the 2 given points and the draft working plane normal. + """ if not objlist: - FreeCAD.Console.PrintError(translate("draft","No object given")+"\n") + _err = "No object given" + FreeCAD.Console.PrintError(translate("draft", _err) + "\n") return if p1 == p2: - FreeCAD.Console.PrintError(translate("draft","The two points are coincident")+"\n") + _err = "The two points are coincident" + FreeCAD.Console.PrintError(translate("draft", _err) + "\n") return if not isinstance(objlist,list): objlist = [objlist] + if hasattr(FreeCAD, "DraftWorkingPlane"): + norm = FreeCAD.DraftWorkingPlane.getNormal() + elif gui: + norm = FreeCADGui.ActiveDocument.ActiveView.getViewDirection().negative() + else: + norm = FreeCAD.Vector(0,0,1) + + pnorm = p2.sub(p1).cross(norm).normalize() + result = [] for obj in objlist: mir = FreeCAD.ActiveDocument.addObject("Part::Mirroring","mirror") - mir.Label = "Mirror of "+obj.Label + mir.Label = "Mirror of " + obj.Label mir.Source = obj - if gui: - norm = FreeCADGui.ActiveDocument.ActiveView.getViewDirection().negative() - else: - norm = FreeCAD.Vector(0,0,1) - pnorm = p2.sub(p1).cross(norm).normalize() mir.Base = p1 mir.Normal = pnorm - formatObject(mir,obj) + formatObject(mir, obj) result.append(mir) if len(result) == 1: result = result[0] select(result) + return result From acda0d4faf02fb5a9821c60bc1e156a2cbf6917e Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 24 Mar 2020 00:17:12 -0600 Subject: [PATCH 114/117] Draft: add example file to test Draft objects --- data/examples/CMakeLists.txt | 2 +- data/examples/draft_test_objects.FCStd | Bin 0 -> 92535 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 data/examples/draft_test_objects.FCStd diff --git a/data/examples/CMakeLists.txt b/data/examples/CMakeLists.txt index 44d9e6908e..2da83cf4d4 100644 --- a/data/examples/CMakeLists.txt +++ b/data/examples/CMakeLists.txt @@ -1,7 +1,7 @@ SET(Examples_Files Schenkel.stp - DrawingExample.FCStd + draft_test_objects.FCStd EngineBlock.FCStd PartDesignExample.FCStd RobotExample.FCStd diff --git a/data/examples/draft_test_objects.FCStd b/data/examples/draft_test_objects.FCStd new file mode 100644 index 0000000000000000000000000000000000000000..2f40a67f2bc446201e1244bf0a56998a07343fa8 GIT binary patch literal 92535 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 literal 0 HcmV?d00001 From f05b38c502ae5e5c206ded2bb20d2a34ca5badec Mon Sep 17 00:00:00 2001 From: donovaly Date: Sat, 28 Mar 2020 02:33:29 +0100 Subject: [PATCH 115/117] [Tools] add Visual Studio project files see: https://forum.freecadweb.org/viewtopic.php?f=10&t=44609&start=10#p381463 One needs all 4 files. It works on difference PCs since all paths are relative. --- src/Tools/plugins/widget/FreeCAD_widgets.sln | 25 +++ .../plugins/widget/FreeCAD_widgets.vcxproj | 195 ++++++++++++++++++ .../widget/FreeCAD_widgets.vcxproj.filters | 65 ++++++ .../widget/FreeCAD_widgets.vcxproj.user | 4 + 4 files changed, 289 insertions(+) create mode 100644 src/Tools/plugins/widget/FreeCAD_widgets.sln create mode 100644 src/Tools/plugins/widget/FreeCAD_widgets.vcxproj create mode 100644 src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.filters create mode 100644 src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.user diff --git a/src/Tools/plugins/widget/FreeCAD_widgets.sln b/src/Tools/plugins/widget/FreeCAD_widgets.sln new file mode 100644 index 0000000000..59eb9f9632 --- /dev/null +++ b/src/Tools/plugins/widget/FreeCAD_widgets.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.1062 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FreeCAD_widgets", "FreeCAD_widgets.vcxproj", "{0FFD6565-C54C-3BE4-96B6-AAFB7DA921A1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0FFD6565-C54C-3BE4-96B6-AAFB7DA921A1}.Debug|x64.ActiveCfg = Debug|x64 + {0FFD6565-C54C-3BE4-96B6-AAFB7DA921A1}.Debug|x64.Build.0 = Debug|x64 + {0FFD6565-C54C-3BE4-96B6-AAFB7DA921A1}.Release|x64.ActiveCfg = Release|x64 + {0FFD6565-C54C-3BE4-96B6-AAFB7DA921A1}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EC696D06-B20E-41F8-94A6-AFEB0D9F3D25} + EndGlobalSection +EndGlobal diff --git a/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj b/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj new file mode 100644 index 0000000000..ba4f3f9ba7 --- /dev/null +++ b/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj @@ -0,0 +1,195 @@ + + + + + Release + x64 + + + Debug + x64 + + + + {0FFD6565-C54C-3BE4-96B6-AAFB7DA921A1} + FreeCAD_widgets + Qt4VSv1.0 + 10.0.17763.0 + 10.0.17763.0 + + + + v141 + release\ + false + NotSet + DynamicLibrary + release\ + FreeCAD_widgets + + + v141 + debug\ + false + NotSet + DynamicLibrary + debug\ + FreeCAD_widgetsd + + + + + + + + + + + + release\ + release\ + FreeCAD_widgets + true + false + debug\ + debug\ + FreeCAD_widgetsd + true + + + + .;.;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtDesigner;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtUiPlugin;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtWidgets;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtGui;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtANGLE;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtXml;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtCore;release;/include;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\mkspecs\win32-msvc;%(AdditionalIncludeDirectories) + -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) + release\ + false + None + 4577;4467;%(DisableSpecificWarnings) + Sync + release\ + MaxSpeed + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_NO_DEBUG;QT_PLUGIN;QT_DESIGNER_LIB;QT_UIPLUGIN_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_CORE_LIB;QDESIGNER_EXPORT_WIDGETS;NDEBUG;%(PreprocessorDefinitions) + false + + MultiThreadedDLL + true + true + Level3 + + + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Designer.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Widgets.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Gui.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Xml.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Core.lib;%(AdditionalDependencies) + true + false + true + true + false + $(OutDir)\FreeCAD_widgets.dll + true + Windows + true + + + Unsigned + None + 0 + + + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_NO_DEBUG;QT_PLUGIN;QT_DESIGNER_LIB;QT_UIPLUGIN_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_CORE_LIB;QDESIGNER_EXPORT_WIDGETS;%(PreprocessorDefinitions) + + + + + .;.;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtDesigner;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtUiPlugin;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtWidgets;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtGui;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtANGLE;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtXml;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtCore;debug;/include;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\mkspecs\win32-msvc;%(AdditionalIncludeDirectories) + -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) + debug\ + false + ProgramDatabase + 4577;4467;%(DisableSpecificWarnings) + Sync + debug\ + Disabled + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_PLUGIN;QT_DESIGNER_LIB;QT_UIPLUGIN_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_CORE_LIB;QDESIGNER_EXPORT_WIDGETS;%(PreprocessorDefinitions) + false + MultiThreadedDebugDLL + true + true + Level3 + + + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Designerd.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Widgetsd.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Guid.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Xmld.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Cored.lib;%(AdditionalDependencies) + true + true + true + true + $(OutDir)\FreeCAD_widgetsd.dll + true + Windows + true + + + Unsigned + None + 0 + + + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_PLUGIN;QT_DESIGNER_LIB;QT_UIPLUGIN_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_CORE_LIB;QDESIGNER_EXPORT_WIDGETS;_DEBUG;%(PreprocessorDefinitions) + + + + + + + + + customwidgets.h;release\moc_predefs.h;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe;%(AdditionalInputs) + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe -DUNICODE -D_UNICODE -DWIN32 -D_ENABLE_EXTENDED_ALIGNED_STORAGE -DWIN64 -DQT_NO_DEBUG -DQT_PLUGIN -DQT_DESIGNER_LIB -DQT_UIPLUGIN_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_XML_LIB -DQT_CORE_LIB -DQDESIGNER_EXPORT_WIDGETS --compiler-flavor=msvc --include D:/FreeCADGit/src/Tools/plugins/widget/release/moc_predefs.h -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/mkspecs/win32-msvc -ID:/FreeCADGit/src/Tools/plugins/widget -ID:/FreeCADGit/src/Tools/plugins/widget -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtDesigner -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtUiPlugin -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtWidgets -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtGui -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtANGLE -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtXml -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtCore -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\ATLMFC\include" -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\include" -I"C:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\ucrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\shared" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\winrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\cppwinrt" customwidgets.h -o release\moc_customwidgets.cpp + MOC customwidgets.h + release\moc_customwidgets.cpp;%(Outputs) + customwidgets.h;debug\moc_predefs.h;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe;%(AdditionalInputs) + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe -DUNICODE -D_UNICODE -DWIN32 -D_ENABLE_EXTENDED_ALIGNED_STORAGE -DWIN64 -DQT_PLUGIN -DQT_DESIGNER_LIB -DQT_UIPLUGIN_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_XML_LIB -DQT_CORE_LIB -DQDESIGNER_EXPORT_WIDGETS --compiler-flavor=msvc --include D:/FreeCADGit/src/Tools/plugins/widget/debug/moc_predefs.h -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/mkspecs/win32-msvc -ID:/FreeCADGit/src/Tools/plugins/widget -ID:/FreeCADGit/src/Tools/plugins/widget -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtDesigner -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtUiPlugin -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtWidgets -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtGui -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtANGLE -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtXml -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtCore -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\ATLMFC\include" -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\include" -I"C:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\ucrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\shared" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\winrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\cppwinrt" customwidgets.h -o debug\moc_customwidgets.cpp + MOC customwidgets.h + debug\moc_customwidgets.cpp;%(Outputs) + + + plugin.h;release\moc_predefs.h;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe;%(AdditionalInputs) + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe -DUNICODE -D_UNICODE -DWIN32 -D_ENABLE_EXTENDED_ALIGNED_STORAGE -DWIN64 -DQT_NO_DEBUG -DQT_PLUGIN -DQT_DESIGNER_LIB -DQT_UIPLUGIN_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_XML_LIB -DQT_CORE_LIB -DQDESIGNER_EXPORT_WIDGETS --compiler-flavor=msvc --include D:/FreeCADGit/src/Tools/plugins/widget/release/moc_predefs.h -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/mkspecs/win32-msvc -ID:/FreeCADGit/src/Tools/plugins/widget -ID:/FreeCADGit/src/Tools/plugins/widget -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtDesigner -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtUiPlugin -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtWidgets -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtGui -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtANGLE -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtXml -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtCore -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\ATLMFC\include" -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\include" -I"C:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\ucrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\shared" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\winrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\cppwinrt" plugin.h -o release\moc_plugin.cpp + MOC plugin.h + release\moc_plugin.cpp;%(Outputs) + plugin.h;debug\moc_predefs.h;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe;%(AdditionalInputs) + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe -DUNICODE -D_UNICODE -DWIN32 -D_ENABLE_EXTENDED_ALIGNED_STORAGE -DWIN64 -DQT_PLUGIN -DQT_DESIGNER_LIB -DQT_UIPLUGIN_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_XML_LIB -DQT_CORE_LIB -DQDESIGNER_EXPORT_WIDGETS --compiler-flavor=msvc --include D:/FreeCADGit/src/Tools/plugins/widget/debug/moc_predefs.h -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/mkspecs/win32-msvc -ID:/FreeCADGit/src/Tools/plugins/widget -ID:/FreeCADGit/src/Tools/plugins/widget -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtDesigner -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtUiPlugin -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtWidgets -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtGui -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtANGLE -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtXml -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtCore -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\ATLMFC\include" -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\include" -I"C:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\ucrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\shared" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\winrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\cppwinrt" plugin.h -o debug\moc_plugin.cpp + MOC plugin.h + debug\moc_plugin.cpp;%(Outputs) + + + + + true + + + true + + + true + + + true + + + Document + true + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\mkspecs\features\data\dummy.cpp;%(AdditionalInputs) + cl -BxC:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\qmake.exe -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -Zi -MDd -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\mkspecs\features\data\dummy.cpp 2>NUL >debug\moc_predefs.h + Generate moc_predefs.h + debug\moc_predefs.h;%(Outputs) + + + Document + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\mkspecs\features\data\dummy.cpp;%(AdditionalInputs) + cl -BxC:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\qmake.exe -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -O2 -MD -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\mkspecs\features\data\dummy.cpp 2>NUL >release\moc_predefs.h + Generate moc_predefs.h + release\moc_predefs.h;%(Outputs) + true + + + + + \ No newline at end of file diff --git a/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.filters b/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.filters new file mode 100644 index 0000000000..5316410ce8 --- /dev/null +++ b/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.filters @@ -0,0 +1,65 @@ + + + + + {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} + cpp;c;cxx;moc;h;def;odl;idl;res; + + + {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} + cpp;c;cxx;moc;h;def;odl;idl;res; + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + + + Generated Files + + + Generated Files + + + Generated Files + + + Generated Files + + + Generated Files + + + Generated Files + + + \ No newline at end of file diff --git a/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.user b/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.user new file mode 100644 index 0000000000..be25078707 --- /dev/null +++ b/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From 58d4596888b9bf01276dae842c1c3bac839f2efd Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Wed, 25 Mar 2020 11:47:06 -0600 Subject: [PATCH 116/117] Arch: use the new Draft trackers module Previously it was `DraftTrackers.py`. Now we import the trackers from `draftguitools/gui_trackers.py`. --- src/Mod/Arch/ArchPanel.py | 3 ++- src/Mod/Arch/ArchStructure.py | 4 +++- src/Mod/Arch/ArchWall.py | 3 ++- src/Mod/Arch/ArchWindow.py | 3 ++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Mod/Arch/ArchPanel.py b/src/Mod/Arch/ArchPanel.py index 95a3a1e5ed..1b4c806de9 100644 --- a/src/Mod/Arch/ArchPanel.py +++ b/src/Mod/Arch/ArchPanel.py @@ -26,6 +26,7 @@ if FreeCAD.GuiUp: from PySide import QtCore, QtGui from DraftTools import translate from PySide.QtCore import QT_TRANSLATE_NOOP + import draftguitools.gui_trackers as DraftTrackers else: # \cond def translate(ctxt,txt): @@ -180,7 +181,7 @@ class CommandPanel: # interactive mode if hasattr(FreeCAD,"DraftWorkingPlane"): FreeCAD.DraftWorkingPlane.setup() - import DraftTrackers + self.points = [] self.tracker = DraftTrackers.boxTracker() self.tracker.width(self.Width) diff --git a/src/Mod/Arch/ArchStructure.py b/src/Mod/Arch/ArchStructure.py index dc78284e7a..83a3e3dcec 100644 --- a/src/Mod/Arch/ArchStructure.py +++ b/src/Mod/Arch/ArchStructure.py @@ -29,6 +29,8 @@ if FreeCAD.GuiUp: from PySide import QtCore, QtGui from DraftTools import translate from PySide.QtCore import QT_TRANSLATE_NOOP + import ArchPrecast + import draftguitools.gui_trackers as DraftTrackers else: # \cond def translate(ctxt,txt): @@ -247,7 +249,7 @@ class _CommandStructure: # interactive mode if hasattr(FreeCAD,"DraftWorkingPlane"): FreeCAD.DraftWorkingPlane.setup() - import DraftTrackers,ArchPrecast + self.points = [] self.tracker = DraftTrackers.boxTracker() self.tracker.width(self.Width) diff --git a/src/Mod/Arch/ArchWall.py b/src/Mod/Arch/ArchWall.py index 7cb2e5e25e..65645bdb14 100644 --- a/src/Mod/Arch/ArchWall.py +++ b/src/Mod/Arch/ArchWall.py @@ -26,6 +26,7 @@ if FreeCAD.GuiUp: from PySide import QtCore, QtGui from DraftTools import translate from PySide.QtCore import QT_TRANSLATE_NOOP + import draftguitools.gui_trackers as DraftTrackers else: # \cond def translate(ctxt,txt, utf8_decode=False): @@ -232,7 +233,7 @@ class _CommandWall: if not done: # interactive mode - import DraftTrackers + self.points = [] self.tracker = DraftTrackers.boxTracker() if hasattr(FreeCAD,"DraftWorkingPlane"): diff --git a/src/Mod/Arch/ArchWindow.py b/src/Mod/Arch/ArchWindow.py index 7fc7ab2d14..9448d2173a 100644 --- a/src/Mod/Arch/ArchWindow.py +++ b/src/Mod/Arch/ArchWindow.py @@ -27,6 +27,7 @@ if FreeCAD.GuiUp: from PySide import QtCore, QtGui, QtSvg from DraftTools import translate from PySide.QtCore import QT_TRANSLATE_NOOP + import draftguitools.gui_trackers as DraftTrackers else: # \cond def translate(ctxt,txt): @@ -653,7 +654,7 @@ class _CommandWindow: # interactive mode if hasattr(FreeCAD,"DraftWorkingPlane"): FreeCAD.DraftWorkingPlane.setup() - import DraftTrackers + self.tracker = DraftTrackers.boxTracker() self.tracker.length(self.Width) self.tracker.width(self.Thickness) From bb7bc5e65b74aa97ed4c084d026696e74894203e Mon Sep 17 00:00:00 2001 From: donovaly Date: Tue, 31 Mar 2020 01:56:14 +0200 Subject: [PATCH 117/117] [TD] weld symbols cannot be rotated - therefore hide the Rotation property --- src/Mod/TechDraw/App/DrawWeldSymbol.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mod/TechDraw/App/DrawWeldSymbol.cpp b/src/Mod/TechDraw/App/DrawWeldSymbol.cpp index 155c7dea99..0279868ea6 100644 --- a/src/Mod/TechDraw/App/DrawWeldSymbol.cpp +++ b/src/Mod/TechDraw/App/DrawWeldSymbol.cpp @@ -62,6 +62,7 @@ DrawWeldSymbol::DrawWeldSymbol(void) Caption.setStatus(App::Property::Hidden,true); Scale.setStatus(App::Property::Hidden,true); ScaleType.setStatus(App::Property::Hidden,true); + Rotation.setStatus(App::Property::Hidden, true); } DrawWeldSymbol::~DrawWeldSymbol()