From 8c3bad64e0560806c1dd5ef2b588e75664427eb2 Mon Sep 17 00:00:00 2001 From: Stefan Endres Date: Sun, 13 Dec 2020 20:52:32 +0100 Subject: [PATCH 001/168] Path: Fix #3914: Adding stock label prefix to name comparison in order to select correct existing solid in combo box --- src/Mod/Path/PathScripts/PathJobGui.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathJobGui.py b/src/Mod/Path/PathScripts/PathJobGui.py index a85c437fdf..04547ab85e 100644 --- a/src/Mod/Path/PathScripts/PathJobGui.py +++ b/src/Mod/Path/PathScripts/PathJobGui.py @@ -519,6 +519,7 @@ class StockCreateCylinderEdit(StockEdit): class StockFromExistingEdit(StockEdit): Index = 3 StockType = PathStock.StockType.Unknown + StockLabelPrefix = 'Stock' def editorFrame(self): return self.form.stockFromExisting @@ -527,7 +528,7 @@ class StockFromExistingEdit(StockEdit): stock = self.form.stockExisting.itemData(self.form.stockExisting.currentIndex()) if not (hasattr(obj.Stock, 'Objects') and len(obj.Stock.Objects) == 1 and obj.Stock.Objects[0] == stock): if stock: - stock = PathJob.createResourceClone(obj, stock, 'Stock', 'Stock') + stock = PathJob.createResourceClone(obj, stock, self.StockLabelPrefix , 'Stock') stock.ViewObject.Visibility = True PathStock.SetupStockObject(stock, PathStock.StockType.Unknown) stock.Proxy.execute(stock) @@ -553,7 +554,9 @@ class StockFromExistingEdit(StockEdit): index = -1 for i, solid in enumerate(self.candidates(obj)): self.form.stockExisting.addItem(solid.Label, solid) - if solid.Label == stockName: + label="%s-%s" % (self.StockLabelPrefix,solid.Label) + + if label == stockName: index = i self.form.stockExisting.setCurrentIndex(index if index != -1 else 0) From 3df9d07e6eab2cda599ce72934a1a388478b3167 Mon Sep 17 00:00:00 2001 From: gauna85 <61654541+gauna85@users.noreply.github.com> Date: Tue, 15 Dec 2020 19:35:29 +0100 Subject: [PATCH 002/168] Update src/Mod/Path/PathScripts/PathJobGui.py Co-authored-by: sliptonic --- src/Mod/Path/PathScripts/PathJobGui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathJobGui.py b/src/Mod/Path/PathScripts/PathJobGui.py index 04547ab85e..ade073016d 100644 --- a/src/Mod/Path/PathScripts/PathJobGui.py +++ b/src/Mod/Path/PathScripts/PathJobGui.py @@ -554,7 +554,7 @@ class StockFromExistingEdit(StockEdit): index = -1 for i, solid in enumerate(self.candidates(obj)): self.form.stockExisting.addItem(solid.Label, solid) - label="%s-%s" % (self.StockLabelPrefix,solid.Label) + label="{}-{}".format(self.StockLabelPrefix, solid.Label) if label == stockName: index = i From 74e146610a6aa6df887c091d195d501c4692c20c Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Fri, 18 Dec 2020 13:43:23 -0600 Subject: [PATCH 003/168] Add prefs for import/export in Recent Files Add two (currently hidden) preferences controlling whether imported and exported files are included in the recent files list. RecentIncludesImported defaults to true, and RecentIncludesExported defaults to false. --- src/Gui/Application.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index ea4c6a76d9..34a9e3a0f7 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -716,8 +716,12 @@ void Application::importFrom(const char* FileName, const char* DocName, const ch } // the original file name is required - QString filename = QString::fromUtf8(File.filePath().c_str()); - getMainWindow()->appendRecentFile(filename); + QString filename = QString::fromUtf8(File.filePath().c_str()); + bool addToRecent = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")-> + GetBool("RecentIncludesImported", true); + if (addToRecent) { + getMainWindow()->appendRecentFile(filename); + } FileDialog::setWorkingDirectory(filename); } catch (const Base::PyException& e){ @@ -771,9 +775,15 @@ void Application::exportTo(const char* FileName, const char* DocName, const char // search for a module that is able to open the exported file because otherwise // it doesn't need to be added to the recent files list (#0002047) std::map importMap = App::GetApplication().getImportFilters(te.c_str()); - if (!importMap.empty()) - getMainWindow()->appendRecentFile(QString::fromUtf8(File.filePath().c_str())); - + bool addToRecent = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")-> + GetBool("RecentIncludesExported", false); + if (addToRecent) { + // search for a module that is able to open the exported file because otherwise + // it doesn't need to be added to the recent files list (#0002047) + std::map importMap = App::GetApplication().getImportFilters(te.c_str()); + if (!importMap.empty()) + getMainWindow()->appendRecentFile(QString::fromUtf8(File.filePath().c_str())); + } // allow exporters to pass _objs__ to submodules before deleting it Gui::Command::runCommand(Gui::Command::App, "del __objs__"); } From 95cb91dc5241d6b225a5f42f9491c6809946c236 Mon Sep 17 00:00:00 2001 From: Patrick F Date: Fri, 8 Jan 2021 20:32:20 +0100 Subject: [PATCH 004/168] [PATH] Added cone helix to adaptive --- src/Mod/Path/PathScripts/PathAdaptive.py | 101 +++++++++++++++++------ 1 file changed, 75 insertions(+), 26 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathAdaptive.py b/src/Mod/Path/PathScripts/PathAdaptive.py index 162406cfb3..38ec365754 100644 --- a/src/Mod/Path/PathScripts/PathAdaptive.py +++ b/src/Mod/Path/PathScripts/PathAdaptive.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # *************************************************************************** # * Copyright (c) 2018 Kresimir Tusek * +# * Copyright (c) 2019-2021 Schildkroet * # * * # * This file is part of the FreeCAD CAx development system. * # * * @@ -20,12 +21,6 @@ # * Suite 330, Boston, MA 02111-1307, USA * # * * # *************************************************************************** -# * * -# * Additional modifications and contributions beginning 2019 * -# * by Schildkroet. (https://github.com/Schildkroet) * -# * * -# *************************************************************************** - import PathScripts.PathOp as PathOp import PathScripts.PathUtils as PathUtils @@ -88,6 +83,22 @@ def discretize(edge, flipDirection = False): return pts +def CalculateCone(radius, alpha): + #beta = -alpha + 90 + b = radius * (math.cos(math.radians(alpha)) / math.sin(math.radians(alpha))) + #c = radius / math.sin(ath.radians(alpha)) + #p = (radius*radius) / c + #q = c - p + + print("B:{}".format(b)) + +def CalcHelixConePoint(height, cur_z, radius, angle): + x = ((height - cur_z) / height) * radius * math.cos(math.radians(angle)*cur_z) + y = ((height - cur_z) / height) * radius * math.sin(math.radians(angle)*cur_z) + z = cur_z + + return {'X': x, 'Y': y, 'Z': z} + def GenerateGCode(op,obj,adaptiveResults, helixDiameter): # pylint: disable=unused-argument if len(adaptiveResults) == 0 or len(adaptiveResults[0]["AdaptivePaths"]) == 0: @@ -112,6 +123,9 @@ def GenerateGCode(op,obj,adaptiveResults, helixDiameter): if float(obj.HelixAngle) < 1: obj.HelixAngle = 1 + + if float(obj.HelixConeAngle) < 0: + obj.HelixConeAngle = 0 helixAngleRad = math.pi * float(obj.HelixAngle) / 180.0 depthPerOneCircle = length * math.tan(helixAngleRad) @@ -121,7 +135,6 @@ def GenerateGCode(op,obj,adaptiveResults, helixDiameter): if stepUp < 0: stepUp = 0 - finish_step = obj.FinishDepth.Value if hasattr(obj, "FinishDepth") else 0.0 if finish_step > stepDown: finish_step = stepDown @@ -136,7 +149,6 @@ def GenerateGCode(op,obj,adaptiveResults, helixDiameter): user_depths=None) - # ml: this is dangerous because it'll hide all unused variables hence forward # however, I don't know what lx and ly signify so I'll leave them for now # pylint: disable=unused-variable @@ -183,25 +195,60 @@ def GenerateGCode(op,obj,adaptiveResults, helixDiameter): # move to start depth op.commandlist.append(Path.Command("G1", {"X": helixStart[0], "Y": helixStart[1], "Z": passStartDepth, "F": op.vertFeed})) - while fi < maxfi: - x = region["HelixCenterPoint"][0] + r * math.cos(fi+offsetFi) - y = region["HelixCenterPoint"][1] + r * math.sin(fi+offsetFi) - z = passStartDepth - fi / maxfi * (passStartDepth - passEndDepth) - op.commandlist.append(Path.Command("G1", { "X": x, "Y":y, "Z":z, "F": op.vertFeed})) - lx = x - ly = y - fi=fi+math.pi/16 + if obj.HelixConeAngle == 0: + while fi < maxfi: + x = region["HelixCenterPoint"][0] + r * math.cos(fi+offsetFi) + y = region["HelixCenterPoint"][1] + r * math.sin(fi+offsetFi) + z = passStartDepth - fi / maxfi * (passStartDepth - passEndDepth) + op.commandlist.append(Path.Command("G1", { "X": x, "Y":y, "Z":z, "F": op.vertFeed})) + lx = x + ly = y + fi=fi+math.pi/16 + + # one more circle at target depth to make sure center is cleared + maxfi = maxfi + 2*math.pi + while fi < maxfi: + x = region["HelixCenterPoint"][0] + r * math.cos(fi+offsetFi) + y = region["HelixCenterPoint"][1] + r * math.sin(fi+offsetFi) + z = passEndDepth + op.commandlist.append(Path.Command("G1", { "X": x, "Y":y, "Z":z, "F": op.horizFeed})) + lx = x + ly = y + fi = fi + math.pi/16 + + else: + # Cone + # Calculate everything + helix_height = passStartDepth - passEndDepth + r_extra = helix_height * math.tan(math.radians(obj.HelixConeAngle)) + HelixTopRadius = helixRadius + r_extra + helix_full_height = HelixTopRadius * (math.cos(math.radians(obj.HelixConeAngle)) / math.sin(math.radians(obj.HelixConeAngle))) + + # rapid move to start point + op.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) + op.commandlist.append(Path.Command("G0", {"X": helixStart[0], "Y": helixStart[1], "Z": obj.ClearanceHeight.Value})) + + # rapid move to safe height + op.commandlist.append(Path.Command("G0", {"X": helixStart[0], "Y": helixStart[1], "Z": obj.SafeHeight.Value})) + + # move to start depth + op.commandlist.append(Path.Command("G1", {"X": helixStart[0], "Y": helixStart[1], "Z": passStartDepth, "F": op.vertFeed})) + + z = passStartDepth + i = 0 + z_step = 0.05 + if obj.HelixAngle > 180: + z_step = 0.025 + + while(z >= passEndDepth): + if z < passEndDepth: + z = passEndDepth + + p = CalcHelixConePoint(helix_full_height, i, HelixTopRadius, obj.HelixAngle) + op.commandlist.append(Path.Command("G1", { "X": p['X'] + region["HelixCenterPoint"][0], "Y": p['Y'] + region["HelixCenterPoint"][1], "Z": z, "F": op.vertFeed})) + z = z - 0.1 + i = i + z_step - # one more circle at target depth to make sure center is cleared - maxfi = maxfi + 2*math.pi - while fi < maxfi: - x = region["HelixCenterPoint"][0] + r * math.cos(fi+offsetFi) - y = region["HelixCenterPoint"][1] + r * math.sin(fi+offsetFi) - z = passEndDepth - op.commandlist.append(Path.Command("G1", { "X": x, "Y":y, "Z":z, "F": op.horizFeed})) - lx = x - ly = y - fi = fi + math.pi/16 else: helixStart = [region["HelixCenterPoint"][0] + r, region["HelixCenterPoint"][1]] @@ -512,6 +559,7 @@ class PathAdaptive(PathOp.ObjectOp): obj.setEditorMode('AdaptiveInputState', 2) #hide this property obj.setEditorMode('AdaptiveOutputState', 2) #hide this property obj.addProperty("App::PropertyAngle", "HelixAngle", "Adaptive", "Helix ramp entry angle (degrees)") + obj.addProperty("App::PropertyAngle", "HelixConeAngle", "Adaptive", "Helix cone angle (degrees)") obj.addProperty("App::PropertyLength", "HelixDiameterLimit", "Adaptive", "Limit helix entry diameter, if limit larger than tool diameter or 0, tool diameter is used") @@ -527,6 +575,7 @@ class PathAdaptive(PathOp.ObjectOp): obj.Stopped = False obj.StopProcessing = False obj.HelixAngle = 5 + obj.HelixConeAngle = 0 obj.HelixDiameterLimit = 0.0 obj.AdaptiveInputState ="" obj.AdaptiveOutputState = "" From e3722ae73ddcbcc564298cffb76cb947e36d2ca1 Mon Sep 17 00:00:00 2001 From: Patrick F Date: Fri, 8 Jan 2021 22:54:17 +0100 Subject: [PATCH 005/168] [PATH] Added gui support and some improvements --- src/Mod/Path/PathScripts/PathAdaptive.py | 47 +++++++++++++-------- src/Mod/Path/PathScripts/PathAdaptiveGui.py | 20 +++++++-- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathAdaptive.py b/src/Mod/Path/PathScripts/PathAdaptive.py index 38ec365754..1653522fd8 100644 --- a/src/Mod/Path/PathScripts/PathAdaptive.py +++ b/src/Mod/Path/PathScripts/PathAdaptive.py @@ -123,11 +123,13 @@ def GenerateGCode(op,obj,adaptiveResults, helixDiameter): if float(obj.HelixAngle) < 1: obj.HelixAngle = 1 + if float(obj.HelixAngle) > 359: + obj.HelixAngle = 359 if float(obj.HelixConeAngle) < 0: obj.HelixConeAngle = 0 - helixAngleRad = math.pi * float(obj.HelixAngle) / 180.0 + helixAngleRad = math.pi * ((360 - float(obj.HelixAngle)) / 4) / 180.0 depthPerOneCircle = length * math.tan(helixAngleRad) #print("Helix circle depth: {}".format(depthPerOneCircle)) @@ -224,32 +226,41 @@ def GenerateGCode(op,obj,adaptiveResults, helixDiameter): HelixTopRadius = helixRadius + r_extra helix_full_height = HelixTopRadius * (math.cos(math.radians(obj.HelixConeAngle)) / math.sin(math.radians(obj.HelixConeAngle))) - # rapid move to start point - op.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) - op.commandlist.append(Path.Command("G0", {"X": helixStart[0], "Y": helixStart[1], "Z": obj.ClearanceHeight.Value})) - - # rapid move to safe height - op.commandlist.append(Path.Command("G0", {"X": helixStart[0], "Y": helixStart[1], "Z": obj.SafeHeight.Value})) - - # move to start depth - op.commandlist.append(Path.Command("G1", {"X": helixStart[0], "Y": helixStart[1], "Z": passStartDepth, "F": op.vertFeed})) - + # Start height z = passStartDepth i = 0 + # Default step down z_step = 0.05 - if obj.HelixAngle > 180: + # Bigger angle, smaller step down + if obj.HelixAngle > 120: z_step = 0.025 + if obj.HelixAngle > 240: + z_step = 0.015 + p = None + # Calculate conical helix while(z >= passEndDepth): if z < passEndDepth: z = passEndDepth p = CalcHelixConePoint(helix_full_height, i, HelixTopRadius, obj.HelixAngle) op.commandlist.append(Path.Command("G1", { "X": p['X'] + region["HelixCenterPoint"][0], "Y": p['Y'] + region["HelixCenterPoint"][1], "Z": z, "F": op.vertFeed})) - z = z - 0.1 + z = z - z_step i = i + z_step + + p['X'] = p['X'] + region["HelixCenterPoint"][0] + p['Y'] = p['Y'] + region["HelixCenterPoint"][1] + x_m = region["HelixCenterPoint"][0] - p['X'] + region["HelixCenterPoint"][0] + y_m = region["HelixCenterPoint"][1] - p['Y'] + region["HelixCenterPoint"][1] + i_off = (x_m - p['X']) / 2 + j_off = (y_m - p['Y']) / 2 + + # one more circle at target depth to make sure center is cleared + op.commandlist.append(Path.Command("G3", { "X": x_m, "Y": y_m, "Z": passEndDepth, "I": i_off, "J": j_off, "F": op.horizFeed})) + op.commandlist.append(Path.Command("G3", { "X": p['X'], "Y": p['Y'], "Z": passEndDepth, "I": -i_off, "J": -j_off, "F": op.horizFeed})) else: + # Use arcs for helix - no conical shape support helixStart = [region["HelixCenterPoint"][0] + r, region["HelixCenterPoint"][1]] # rapid move to start point @@ -267,16 +278,16 @@ def GenerateGCode(op,obj,adaptiveResults, helixDiameter): curDep = passStartDepth while curDep > (passEndDepth + depthPerOneCircle): - op.commandlist.append(Path.Command("G2", { "X": x - (2*r), "Y": y, "Z": curDep - (depthPerOneCircle/2), "I": -r, "F": op.horizFeed})) - op.commandlist.append(Path.Command("G2", { "X": x, "Y": y, "Z": curDep - depthPerOneCircle, "I": r, "F": op.horizFeed})) + op.commandlist.append(Path.Command("G2", { "X": x - (2*r), "Y": y, "Z": curDep - (depthPerOneCircle/2), "I": -r, "F": op.vertFeed})) + op.commandlist.append(Path.Command("G2", { "X": x, "Y": y, "Z": curDep - depthPerOneCircle, "I": r, "F": op.vertFeed})) curDep = curDep - depthPerOneCircle lastStep = curDep - passEndDepth if lastStep > (depthPerOneCircle/2): - op.commandlist.append(Path.Command("G2", { "X": x - (2*r), "Y": y, "Z": curDep - (lastStep/2), "I": -r, "F": op.horizFeed})) - op.commandlist.append(Path.Command("G2", { "X": x, "Y": y, "Z": passEndDepth, "I": r, "F": op.horizFeed})) + op.commandlist.append(Path.Command("G2", { "X": x - (2*r), "Y": y, "Z": curDep - (lastStep/2), "I": -r, "F": op.vertFeed})) + op.commandlist.append(Path.Command("G2", { "X": x, "Y": y, "Z": passEndDepth, "I": r, "F": op.vertFeed})) else: - op.commandlist.append(Path.Command("G2", { "X": x - (2*r), "Y": y, "Z": passEndDepth, "I": -r, "F": op.horizFeed})) + op.commandlist.append(Path.Command("G2", { "X": x - (2*r), "Y": y, "Z": passEndDepth, "I": -r, "F": op.vertFeed})) op.commandlist.append(Path.Command("G1", {"X": x, "Y": y, "Z": passEndDepth, "F": op.vertFeed})) # one more circle at target depth to make sure center is cleared diff --git a/src/Mod/Path/PathScripts/PathAdaptiveGui.py b/src/Mod/Path/PathScripts/PathAdaptiveGui.py index e8c6d8da3f..0660b82887 100644 --- a/src/Mod/Path/PathScripts/PathAdaptiveGui.py +++ b/src/Mod/Path/PathScripts/PathAdaptiveGui.py @@ -78,13 +78,22 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): # helix angle form.HelixAngle = QtGui.QDoubleSpinBox() - form.HelixAngle.setMinimum(0.1) - form.HelixAngle.setMaximum(90) - form.HelixAngle.setSingleStep(0.1) - form.HelixAngle.setValue(5) + form.HelixAngle.setMinimum(0.5) + form.HelixAngle.setMaximum(359) + form.HelixAngle.setSingleStep(1) + form.HelixAngle.setValue(180) form.HelixAngle.setToolTip("Angle of the helix ramp entry") formLayout.addRow(QtGui.QLabel("Helix Ramp Angle"), form.HelixAngle) + # helix cone angle + form.HelixConeAngle = QtGui.QDoubleSpinBox() + form.HelixConeAngle.setMinimum(0) + form.HelixConeAngle.setMaximum(45) + form.HelixConeAngle.setSingleStep(1) + form.HelixConeAngle.setValue(0) + form.HelixConeAngle.setToolTip("Angle of the helix cone") + formLayout.addRow(QtGui.QLabel("Helix Cone Angle"), form.HelixConeAngle) + # helix diam. limit form.HelixDiameterLimit = QtGui.QDoubleSpinBox() form.HelixDiameterLimit.setMinimum(0.0) @@ -151,6 +160,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): signals.append(self.form.StepOver.valueChanged) signals.append(self.form.Tolerance.valueChanged) signals.append(self.form.HelixAngle.valueChanged) + signals.append(self.form.HelixConeAngle.valueChanged) signals.append(self.form.HelixDiameterLimit.valueChanged) signals.append(self.form.LiftDistance.valueChanged) signals.append(self.form.KeepToolDownRatio.valueChanged) @@ -169,6 +179,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.form.StepOver.setValue(obj.StepOver) self.form.Tolerance.setValue(int(obj.Tolerance * 100)) self.form.HelixAngle.setValue(obj.HelixAngle) + self.form.HelixConeAngle.setValue(obj.HelixConeAngle) self.form.HelixDiameterLimit.setValue(obj.HelixDiameterLimit) self.form.LiftDistance.setValue(obj.LiftDistance) if hasattr(obj, 'KeepToolDownRatio'): @@ -198,6 +209,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): obj.StepOver = self.form.StepOver.value() obj.Tolerance = 1.0 * self.form.Tolerance.value() / 100.0 obj.HelixAngle = self.form.HelixAngle.value() + obj.HelixConeAngle = self.form.HelixConeAngle.value() obj.HelixDiameterLimit = self.form.HelixDiameterLimit.value() obj.LiftDistance = self.form.LiftDistance.value() From bd8cce58577a3f424fca6e2ded4bab89b2479bd9 Mon Sep 17 00:00:00 2001 From: Patrick F Date: Fri, 8 Jan 2021 23:06:24 +0100 Subject: [PATCH 006/168] [PATH] code refactoring + clean up --- src/Mod/Path/PathScripts/PathAdaptive.py | 18 ++++++------------ src/Mod/Path/PathScripts/PathAdaptiveGui.py | 4 ++-- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathAdaptive.py b/src/Mod/Path/PathScripts/PathAdaptive.py index 1653522fd8..26af7478f3 100644 --- a/src/Mod/Path/PathScripts/PathAdaptive.py +++ b/src/Mod/Path/PathScripts/PathAdaptive.py @@ -83,15 +83,6 @@ def discretize(edge, flipDirection = False): return pts -def CalculateCone(radius, alpha): - #beta = -alpha + 90 - b = radius * (math.cos(math.radians(alpha)) / math.sin(math.radians(alpha))) - #c = radius / math.sin(ath.radians(alpha)) - #p = (radius*radius) / c - #q = c - p - - print("B:{}".format(b)) - def CalcHelixConePoint(height, cur_z, radius, angle): x = ((height - cur_z) / height) * radius * math.cos(math.radians(angle)*cur_z) y = ((height - cur_z) / height) * radius * math.sin(math.radians(angle)*cur_z) @@ -174,8 +165,8 @@ def GenerateGCode(op,obj,adaptiveResults, helixDiameter): p2 = region["StartPoint"] helixRadius = math.sqrt((p1[0]-p2[0]) * (p1[0]-p2[0]) + (p1[1]-p2[1]) * (p1[1]-p2[1])) - # helix ramp - if helixRadius > 0.001: + # Helix ramp + if helixRadius > 0.01: r = helixRadius - 0.01 maxfi = passDepth / depthPerOneCircle * 2 * math.pi @@ -229,8 +220,10 @@ def GenerateGCode(op,obj,adaptiveResults, helixDiameter): # Start height z = passStartDepth i = 0 + # Default step down z_step = 0.05 + # Bigger angle, smaller step down if obj.HelixAngle > 120: z_step = 0.025 @@ -248,6 +241,7 @@ def GenerateGCode(op,obj,adaptiveResults, helixDiameter): z = z - z_step i = i + z_step + # Calculate some stuff for arcs at bottom p['X'] = p['X'] + region["HelixCenterPoint"][0] p['Y'] = p['Y'] + region["HelixCenterPoint"][1] x_m = region["HelixCenterPoint"][0] - p['X'] + region["HelixCenterPoint"][0] @@ -585,7 +579,7 @@ class PathAdaptive(PathOp.ObjectOp): obj.FinishingProfile = True obj.Stopped = False obj.StopProcessing = False - obj.HelixAngle = 5 + obj.HelixAngle = 250 obj.HelixConeAngle = 0 obj.HelixDiameterLimit = 0.0 obj.AdaptiveInputState ="" diff --git a/src/Mod/Path/PathScripts/PathAdaptiveGui.py b/src/Mod/Path/PathScripts/PathAdaptiveGui.py index 0660b82887..de3a49d918 100644 --- a/src/Mod/Path/PathScripts/PathAdaptiveGui.py +++ b/src/Mod/Path/PathScripts/PathAdaptiveGui.py @@ -78,10 +78,10 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): # helix angle form.HelixAngle = QtGui.QDoubleSpinBox() - form.HelixAngle.setMinimum(0.5) + form.HelixAngle.setMinimum(1) form.HelixAngle.setMaximum(359) form.HelixAngle.setSingleStep(1) - form.HelixAngle.setValue(180) + form.HelixAngle.setValue(250) form.HelixAngle.setToolTip("Angle of the helix ramp entry") formLayout.addRow(QtGui.QLabel("Helix Ramp Angle"), form.HelixAngle) From 636add044bcda19b805adea5e43341f58faa2711 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 10 Jan 2021 15:40:10 -0600 Subject: [PATCH 007/168] Add parameters to user.cfg file when used The two parameters are now added to the user.cfg file once they are accessed in the code. So the first time you export a file, the parameeter RecentIncludesExported is created and defaulted to false, and the first time you import a file the parameter RecentIncludesImported is created and defaults to true. Once that is done the parameters can be edited from the Parameter Editor, even though they do not have entries in the Preferences dialog. --- src/Gui/Application.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 90c122228a..299c61906c 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -716,9 +716,10 @@ void Application::importFrom(const char* FileName, const char* DocName, const ch } // the original file name is required - QString filename = QString::fromUtf8(File.filePath().c_str()); - bool addToRecent = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")-> - GetBool("RecentIncludesImported", true); + QString filename = QString::fromUtf8(File.filePath().c_str()); + auto parameterGroup = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General"); + bool addToRecent = parameterGroup->GetBool("RecentIncludesImported", true); + parameterGroup->SetBool("RecentIncludesImported", addToRecent); // Make sure it gets added to the parameter list if (addToRecent) { getMainWindow()->appendRecentFile(filename); } @@ -775,8 +776,10 @@ void Application::exportTo(const char* FileName, const char* DocName, const char // search for a module that is able to open the exported file because otherwise // it doesn't need to be added to the recent files list (#0002047) std::map importMap = App::GetApplication().getImportFilters(te.c_str()); - bool addToRecent = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")-> - GetBool("RecentIncludesExported", false); + + auto parameterGroup = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General"); + bool addToRecent = parameterGroup->GetBool("RecentIncludesExported", false); + parameterGroup->SetBool("RecentIncludesExported", addToRecent); // Make sure it gets added to the parameter list if (addToRecent) { // search for a module that is able to open the exported file because otherwise // it doesn't need to be added to the recent files list (#0002047) From f18ad9453361cc2ef023d68c40cdfb2d8494753c Mon Sep 17 00:00:00 2001 From: Patrick F Date: Wed, 13 Jan 2021 21:05:23 +0100 Subject: [PATCH 008/168] [PATH] Changed angle input --- src/Mod/Path/PathScripts/PathAdaptive.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathAdaptive.py b/src/Mod/Path/PathScripts/PathAdaptive.py index 26af7478f3..f55e9ef1ea 100644 --- a/src/Mod/Path/PathScripts/PathAdaptive.py +++ b/src/Mod/Path/PathScripts/PathAdaptive.py @@ -114,13 +114,13 @@ def GenerateGCode(op,obj,adaptiveResults, helixDiameter): if float(obj.HelixAngle) < 1: obj.HelixAngle = 1 - if float(obj.HelixAngle) > 359: - obj.HelixAngle = 359 + if float(obj.HelixAngle) > 89: + obj.HelixAngle = 89 if float(obj.HelixConeAngle) < 0: obj.HelixConeAngle = 0 - helixAngleRad = math.pi * ((360 - float(obj.HelixAngle)) / 4) / 180.0 + helixAngleRad = math.pi * float(obj.HelixAngle) / 180.0 depthPerOneCircle = length * math.tan(helixAngleRad) #print("Helix circle depth: {}".format(depthPerOneCircle)) @@ -211,6 +211,7 @@ def GenerateGCode(op,obj,adaptiveResults, helixDiameter): else: # Cone + _HelixAngle = 360 - (float(obj.HelixAngle) * 4) # Calculate everything helix_height = passStartDepth - passEndDepth r_extra = helix_height * math.tan(math.radians(obj.HelixConeAngle)) @@ -225,9 +226,9 @@ def GenerateGCode(op,obj,adaptiveResults, helixDiameter): z_step = 0.05 # Bigger angle, smaller step down - if obj.HelixAngle > 120: + if _HelixAngle > 120: z_step = 0.025 - if obj.HelixAngle > 240: + if _HelixAngle > 240: z_step = 0.015 p = None @@ -236,7 +237,7 @@ def GenerateGCode(op,obj,adaptiveResults, helixDiameter): if z < passEndDepth: z = passEndDepth - p = CalcHelixConePoint(helix_full_height, i, HelixTopRadius, obj.HelixAngle) + p = CalcHelixConePoint(helix_full_height, i, HelixTopRadius, _HelixAngle) op.commandlist.append(Path.Command("G1", { "X": p['X'] + region["HelixCenterPoint"][0], "Y": p['Y'] + region["HelixCenterPoint"][1], "Z": z, "F": op.vertFeed})) z = z - z_step i = i + z_step @@ -579,7 +580,7 @@ class PathAdaptive(PathOp.ObjectOp): obj.FinishingProfile = True obj.Stopped = False obj.StopProcessing = False - obj.HelixAngle = 250 + obj.HelixAngle = 5 obj.HelixConeAngle = 0 obj.HelixDiameterLimit = 0.0 obj.AdaptiveInputState ="" From ab8a4a62a92b9cc3d112fb338abd6981caa7cf13 Mon Sep 17 00:00:00 2001 From: Keilin Bickar Date: Sun, 17 Jan 2021 22:04:26 -0500 Subject: [PATCH 009/168] Fix AttributeError on deleting corrupt dressup --- src/Mod/Path/PathScripts/PathDressupTagGui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathDressupTagGui.py b/src/Mod/Path/PathScripts/PathDressupTagGui.py index 0a6c01dc6b..0a95156c2d 100644 --- a/src/Mod/Path/PathScripts/PathDressupTagGui.py +++ b/src/Mod/Path/PathScripts/PathDressupTagGui.py @@ -418,7 +418,7 @@ class PathDressupTagViewProvider: '''this makes sure that the base operation is added back to the job and visible''' # pylint: disable=unused-argument PathLog.track() - if self.obj.Base.ViewObject: + if self.obj.Base and self.obj.Base.ViewObject: self.obj.Base.ViewObject.Visibility = True job = PathUtils.findParentJob(self.obj) if arg1.Object and arg1.Object.Base and job: From dbedb21676e35d6352c70dc000c1a760631cfad2 Mon Sep 17 00:00:00 2001 From: Benjamin Nauck Date: Sun, 27 Dec 2020 15:17:23 +0100 Subject: [PATCH 010/168] [Spreadsheet] Only evaluate cell values when prefixed with '=' This commit only changes the user interaction with spreadsheet and does not affect backwards compatibility (as valid cell expressions are prefixed with '=' when serialized). This fixes [#4156](https://tracker.freecadweb.org/view.php?id=4156), which is discussed in the forum thread: https://forum.freecadweb.org/viewtopic.php?f=3&t=39665 There has been additional logic added to handle numbers and simple fractions without using '='. The behaviour is what is expected by the spreadsheet test cases and in line with how other spreadsheet software works. The '-prefix can still be used to force the input to be handled as as string instead. Example of numbers and fractions handled are: 3 2mm 1/8 1mm/2 1/2mm 2/m 1mm/2s More complex expressions are not handled without '=' and will be stored as strings instead, for example: 2 / 3 / 2 1 + 1/3 --- src/Mod/Spreadsheet/App/Cell.cpp | 98 ++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 29 deletions(-) diff --git a/src/Mod/Spreadsheet/App/Cell.cpp b/src/Mod/Spreadsheet/App/Cell.cpp index 3af0131c24..459ba7db93 100644 --- a/src/Mod/Spreadsheet/App/Cell.cpp +++ b/src/Mod/Spreadsheet/App/Cell.cpp @@ -41,6 +41,7 @@ #include #include "Sheet.h" #include +#include FC_LOG_LEVEL_INIT("Spreadsheet",true,true) @@ -279,12 +280,12 @@ void Cell::afterRestore() { void Cell::setContent(const char * value) { PropertySheet::AtomicPropertyChange signaller(*owner); - App::Expression * expr = 0; + App::Expression * expr = nullptr; clearException(); - if (value != 0) { - if(owner->sheet()->isRestoring()) { - expression.reset(new App::StringExpression(owner->sheet(),value)); + if (value != nullptr) { + if (owner->sheet()->isRestoring()) { + expression.reset(new App::StringExpression(owner->sheet(), value)); setUsed(EXPRESSION_SET, true); return; } @@ -301,40 +302,80 @@ void Cell::setContent(const char * value) expr = new App::StringExpression(owner->sheet(), value + 1); } else if (*value != '\0') { + // check if value is just a number char * end; errno = 0; - double float_value = strtod(value, &end); - if (!*end && errno == 0) { - expr = new App::NumberExpression(owner->sheet(), Quantity(float_value)); + const double float_value = strtod(value, &end); + if (errno == 0) { + const bool isEndEmpty = *end == '\0' || strspn(end, " \t\n\r") == strlen(end); + if (isEndEmpty) { + expr = new App::NumberExpression(owner->sheet(), Quantity(float_value)); + } } - else { + + // if not a float, check if it is a quantity or compatible fraction + const bool isStartingWithNumber = value != end; + if (expr == nullptr && isStartingWithNumber) { try { - expr = ExpressionParser::parse(owner->sheet(), value); - if (expr) - delete expr->eval(); - } - catch (Base::Exception &) { - expr = new App::StringExpression(owner->sheet(), value); + auto parsedExpr = App::ExpressionParser::parse(owner->sheet(), value); + + if (const auto fraction = freecad_dynamic_cast(parsedExpr)) { + if (fraction->getOperator() == OperatorExpression::UNIT) { + const auto left = freecad_dynamic_cast(fraction->getLeft()); + const auto right = freecad_dynamic_cast(fraction->getRight()); + if (left && right) { + expr = parsedExpr; + } + } + else if (fraction->getOperator() == OperatorExpression::DIV) { + // only the following types of fractions are ok: + // 1/2, 1m/2, 1/2s, 1m/2s, 1/m + + // check for numbers in (de)nominator + const bool isNumberNom = freecad_dynamic_cast(fraction->getLeft()); + const bool isNumberDenom = freecad_dynamic_cast(fraction->getRight()); + + // check for numbers with units in (de)nominator + const auto opNom = freecad_dynamic_cast(fraction->getLeft()); + const auto opDenom = freecad_dynamic_cast(fraction->getRight()); + const bool isQuantityNom = opNom && opNom->getOperator() == OperatorExpression::UNIT; + const bool isQuantityDenom = opDenom && opDenom->getOperator() == OperatorExpression::UNIT; + + // check for units in denomainator + const auto uDenom = freecad_dynamic_cast(fraction->getRight()); + const bool isUnitDenom = uDenom && uDenom->getTypeId() == UnitExpression::getClassTypeId(); + + const bool isNomValid = isNumberNom || isQuantityNom; + const bool isDenomValid = isNumberDenom || isQuantityDenom || isUnitDenom; + if (isNomValid && isDenomValid) { + expr = parsedExpr; + } + } + } + else if (const auto number = freecad_dynamic_cast(parsedExpr)) { + // NumbersExpressions can accept more than can be parsed with strtod. + // Example: 12.34 and 12,34 are both valid NumberExpressions + expr = parsedExpr; + } + + if (expr == nullptr) { + delete parsedExpr; + } } + catch (...) {} } } - } - try { - setExpression(App::ExpressionPtr(expr)); - signaller.tryInvoke(); - } - catch (Base::Exception &e) { - if (value) { - std::string _value = value; - if (_value[0] != '=') { - _value.insert (0, 1, '='); - } - - setExpression(App::ExpressionPtr(new App::StringExpression(owner->sheet(), _value))); - setParseException(e.what()); + if (expr == nullptr && *value != '\0') { + expr = new App::StringExpression(owner->sheet(), value); } + + // trying to add an empty string will make expr = nullptr } + + // set expression, or delete the current expression by setting nullptr if empty string was entered + setExpression(App::ExpressionPtr(expr)); + signaller.tryInvoke(); } /** @@ -1020,4 +1061,3 @@ std::string Cell::getFormattedQuantity(void) return result; } - From 98414334ad7456d209275a62ccb8a16038d7af87 Mon Sep 17 00:00:00 2001 From: Benjamin Nauck Date: Tue, 29 Dec 2020 17:55:04 +0100 Subject: [PATCH 011/168] [Spreadsheet] Add unit tests for new input behaviour --- src/Mod/Spreadsheet/TestSpreadsheet.py | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Mod/Spreadsheet/TestSpreadsheet.py b/src/Mod/Spreadsheet/TestSpreadsheet.py index adda58466d..ba6f2a1ec1 100644 --- a/src/Mod/Spreadsheet/TestSpreadsheet.py +++ b/src/Mod/Spreadsheet/TestSpreadsheet.py @@ -668,6 +668,23 @@ class SpreadsheetCases(unittest.TestCase): self.assertEqual(sheet.A17, 0.5) self.assertEqual(sheet.A18, 0.5) + def testQuantitiesAndFractionsAsNumbers(self): + """ Test quantities and simple fractions as numbers """ + sheet = self.doc.addObject('Spreadsheet::Sheet','Spreadsheet') + sheet.set('A1', '1mm') + sheet.set('A2', '1/2') + sheet.set('A3', '4mm/2') + sheet.set('A4', '2/mm') + sheet.set('A5', '4/2mm') + sheet.set('A6', '6mm/3s') + self.doc.recompute() + self.assertEqual(sheet.A1, Units.Quantity('1 mm')) + self.assertEqual(sheet.A2, 0.5) + self.assertEqual(sheet.A3, Units.Quantity('2 mm')) + self.assertEqual(sheet.A4, Units.Quantity('2 1/mm')) + self.assertEqual(sheet.A5, Units.Quantity('2 1/mm')) + self.assertEqual(sheet.A6, Units.Quantity('2 mm/s')) + def testRemoveRows(self): """ Removing rows -- check renaming of internal cells """ sheet = self.doc.addObject('Spreadsheet::Sheet','Spreadsheet') @@ -1034,6 +1051,16 @@ class SpreadsheetCases(unittest.TestCase): self.doc.recompute() self.assertEqual(sheet.get('C1'), Units.Quantity('3 mm')) + def testIssue4156(self): + """ Regression test for issue 4156; necessarily use of leading '=' to enter an expression, creates inconsistent behavior depending on the spreadsheet state""" + sheet = self.doc.addObject('Spreadsheet::Sheet','Spreadsheet') + sheet.set('A3', 'A1') + sheet.set('A1', '1000') + self.doc.recompute() + sheet.set('A3', '') + sheet.set('A3', 'A1') + self.assertEqual(sheet.getContents('A3'), 'A1') + def testInsertRowsAlias(self): """ Regression test for issue 4429; insert rows to sheet with aliases""" sheet = self.doc.addObject('Spreadsheet::Sheet','Spreadsheet') From 24c61836619e46fa0ceabea677c10e58b03947b2 Mon Sep 17 00:00:00 2001 From: Benjamin Nauck Date: Tue, 19 Jan 2021 02:55:49 +0100 Subject: [PATCH 012/168] Spreadsheet: make setContent use unique_ptr and cleanup Make `Cell::setContent` use `unique_ptr` and `make_unique` for expressions to avoid potential memory leaks. Also renames `expo` to `newExpr` to avoid mixup with the member variable `expression`. Both changes was made at the request of @chennes. --- src/Mod/Spreadsheet/App/Cell.cpp | 39 +++++++++++++++----------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/src/Mod/Spreadsheet/App/Cell.cpp b/src/Mod/Spreadsheet/App/Cell.cpp index 459ba7db93..27768b4a8f 100644 --- a/src/Mod/Spreadsheet/App/Cell.cpp +++ b/src/Mod/Spreadsheet/App/Cell.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include "Sheet.h" #include @@ -280,10 +281,10 @@ void Cell::afterRestore() { void Cell::setContent(const char * value) { PropertySheet::AtomicPropertyChange signaller(*owner); - App::Expression * expr = nullptr; + ExpressionPtr newExpr; clearException(); - if (value != nullptr) { + if (value) { if (owner->sheet()->isRestoring()) { expression.reset(new App::StringExpression(owner->sheet(), value)); setUsed(EXPRESSION_SET, true); @@ -291,15 +292,15 @@ void Cell::setContent(const char * value) } if (*value == '=') { try { - expr = App::ExpressionParser::parse(owner->sheet(), value + 1); + newExpr = ExpressionPtr(App::ExpressionParser::parse(owner->sheet(), value + 1)); } catch (Base::Exception & e) { - expr = new App::StringExpression(owner->sheet(), value); + newExpr = std::make_unique(owner->sheet(), value); setParseException(e.what()); } } else if (*value == '\'') { - expr = new App::StringExpression(owner->sheet(), value + 1); + newExpr = std::make_unique(owner->sheet(), value + 1); } else if (*value != '\0') { // check if value is just a number @@ -309,22 +310,22 @@ void Cell::setContent(const char * value) if (errno == 0) { const bool isEndEmpty = *end == '\0' || strspn(end, " \t\n\r") == strlen(end); if (isEndEmpty) { - expr = new App::NumberExpression(owner->sheet(), Quantity(float_value)); + newExpr = std::make_unique(owner->sheet(), Quantity(float_value)); } } // if not a float, check if it is a quantity or compatible fraction const bool isStartingWithNumber = value != end; - if (expr == nullptr && isStartingWithNumber) { + if (!newExpr && isStartingWithNumber) { try { - auto parsedExpr = App::ExpressionParser::parse(owner->sheet(), value); + ExpressionPtr parsedExpr(App::ExpressionParser::parse(owner->sheet(), value)); - if (const auto fraction = freecad_dynamic_cast(parsedExpr)) { + if (const auto fraction = freecad_dynamic_cast(parsedExpr.get())) { if (fraction->getOperator() == OperatorExpression::UNIT) { const auto left = freecad_dynamic_cast(fraction->getLeft()); const auto right = freecad_dynamic_cast(fraction->getRight()); if (left && right) { - expr = parsedExpr; + newExpr = std::move(parsedExpr); } } else if (fraction->getOperator() == OperatorExpression::DIV) { @@ -348,33 +349,29 @@ void Cell::setContent(const char * value) const bool isNomValid = isNumberNom || isQuantityNom; const bool isDenomValid = isNumberDenom || isQuantityDenom || isUnitDenom; if (isNomValid && isDenomValid) { - expr = parsedExpr; + newExpr = std::move(parsedExpr); } } } - else if (const auto number = freecad_dynamic_cast(parsedExpr)) { + else if (const auto number = freecad_dynamic_cast(parsedExpr.get())) { // NumbersExpressions can accept more than can be parsed with strtod. // Example: 12.34 and 12,34 are both valid NumberExpressions - expr = parsedExpr; - } - - if (expr == nullptr) { - delete parsedExpr; + newExpr = std::move(parsedExpr); } } catch (...) {} } } - if (expr == nullptr && *value != '\0') { - expr = new App::StringExpression(owner->sheet(), value); + if (!newExpr && *value != '\0') { + newExpr = std::make_unique(owner->sheet(), value); } - // trying to add an empty string will make expr = nullptr + // trying to add an empty string will make newExpr = nullptr } // set expression, or delete the current expression by setting nullptr if empty string was entered - setExpression(App::ExpressionPtr(expr)); + setExpression(std::move(newExpr)); signaller.tryInvoke(); } From 6a07fb6237c57e96ab01927471ea98feb7e930b6 Mon Sep 17 00:00:00 2001 From: Tyler Colbert Date: Tue, 19 Jan 2021 13:25:00 -0700 Subject: [PATCH 013/168] [PATH] First pass at a post processor for Fanuc controllers --- src/Mod/Path/PathScripts/post/fanuc_post.py | 513 ++++++++++++++++++++ 1 file changed, 513 insertions(+) create mode 100644 src/Mod/Path/PathScripts/post/fanuc_post.py diff --git a/src/Mod/Path/PathScripts/post/fanuc_post.py b/src/Mod/Path/PathScripts/post/fanuc_post.py new file mode 100644 index 0000000000..d2bb6acb0e --- /dev/null +++ b/src/Mod/Path/PathScripts/post/fanuc_post.py @@ -0,0 +1,513 @@ +# *************************************************************************** +# * Copyright (c) 2014 sliptonic * +# * Copyright (c) 2021 shadowbane1000 * +# * * +# * 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 * +# * * +# ***************************************************************************/ +from __future__ import print_function +import FreeCAD +from FreeCAD import Units +import Path +import argparse +import datetime +import shlex +import os.path +from PathScripts import PostUtils +from PathScripts import PathUtils + +TOOLTIP = ''' +This is a postprocessor file for the Path workbench. It is used to +take a pseudo-gcode fragment outputted by a Path object, and output +real GCode suitable should be suitable for most Fanuc controllers. +It has only been tested on a 21i-MB controller on a 3 axis mill. +This postprocessor, once placed in the appropriate PathScripts folder, +can be used directly from inside FreeCAD, via the GUI importer or via +python scripts with: + +import fanuc_post +fanuc_post.export(object,"/path/to/file.ncc","") +''' + +now = datetime.datetime.now() + +parser = argparse.ArgumentParser(prog='fanuc', add_help=False) +parser.add_argument('--no-header', action='store_true', help='suppress header output') +parser.add_argument('--no-comments', action='store_true', help='suppress comment output') +parser.add_argument('--line-numbers', action='store_true', help='prefix with line numbers') +parser.add_argument('--no-show-editor', action='store_true', help='don\'t pop up editor before writing output') +parser.add_argument('--precision', default='3', help='number of digits of precision, default=3') +parser.add_argument('--preamble', help='set commands to be issued before the first command, default="G17\nG90"') +parser.add_argument('--postamble', help='set commands to be issued after the last command, default="M05\nG17 G90\nM2"') +parser.add_argument('--inches', action='store_true', help='Convert output for US imperial mode (G20)') +parser.add_argument('--no-modal', action='store_true', help='Don\'t output the Same G-command Name USE NonModal Mode') +parser.add_argument('--no-axis-modal', action='store_true', help='Don\'t output the Same Axis Value Mode') +parser.add_argument('--no-tlo', action='store_true', help='suppress tool length offset (G43) following tool changes') + +TOOLTIP_ARGS = parser.format_help() + +# These globals set common customization preferences +OUTPUT_COMMENTS = True +OUTPUT_HEADER = True +OUTPUT_LINE_NUMBERS = False +SHOW_EDITOR = True +MODAL = True # if true commands are suppressed if the same as previous line. +USE_TLO = True # if true G43 will be output following tool changes +OUTPUT_DOUBLES = False # if false duplicate axis values are suppressed if the same as previous line. +COMMAND_SPACE = " " +LINENR = 100 # line number starting value + +# These globals will be reflected in the Machine configuration of the project +UNITS = "G21" # G21 for metric, G20 for us standard +UNIT_SPEED_FORMAT = 'mm/min' +UNIT_FORMAT = 'mm' + +MACHINE_NAME = "fanuc" +CORNER_MIN = {'x': 0, 'y': 0, 'z': 0} +CORNER_MAX = {'x': 500, 'y': 300, 'z': 300} +PRECISION = 3 + +# this global is used to pass spindle speed from the tool command into the machining command for +# rigid tapping. +tapSpeed = 0 + +# Preamble text will appear at the beginning of the GCODE output file. +PREAMBLE = '''G17 G54 G40 G49 G80 G90 +''' + +# Postamble text will appear following the last operation. +POSTAMBLE = '''M05 +G17 G54 G90 G80 G40 +M6 T0 +M2 +''' + +# Pre operation text will be inserted before every operation +PRE_OPERATION = '''''' + +# Post operation text will be inserted after every operation +POST_OPERATION = '''''' + +# Tool Change commands will be inserted before a tool change +TOOL_CHANGE = '''''' + +# to distinguish python built-in open function from the one declared below +if open.__module__ in ['__builtin__','io']: + pythonopen = open + + +def processArguments(argstring): + # pylint: disable=global-statement + global OUTPUT_HEADER + global OUTPUT_COMMENTS + global OUTPUT_LINE_NUMBERS + global SHOW_EDITOR + global PRECISION + global PREAMBLE + global POSTAMBLE + global UNITS + global UNIT_SPEED_FORMAT + global UNIT_FORMAT + global MODAL + global USE_TLO + global OUTPUT_DOUBLES + + try: + args = parser.parse_args(shlex.split(argstring)) + if args.no_header: + OUTPUT_HEADER = False + if args.no_comments: + OUTPUT_COMMENTS = False + if args.line_numbers: + OUTPUT_LINE_NUMBERS = True + if args.no_show_editor: + SHOW_EDITOR = False + print("Show editor = %d" % SHOW_EDITOR) + PRECISION = args.precision + if args.preamble is not None: + PREAMBLE = args.preamble + if args.postamble is not None: + POSTAMBLE = args.postamble + if args.inches: + UNITS = 'G20' + UNIT_SPEED_FORMAT = 'in/min' + UNIT_FORMAT = 'in' + PRECISION = 4 + if args.no_modal: + MODAL = False + if args.no_tlo: + USE_TLO = False + if args.no_axis_modal: + OUTPUT_DOUBLES = true + + except Exception: # pylint: disable=broad-except + return False + + return True + + +def export(objectslist, filename, argstring): + # pylint: disable=global-statement + if not processArguments(argstring): + return None + global UNITS + global UNIT_FORMAT + global UNIT_SPEED_FORMAT + global HORIZRAPID + global VERTRAPID + + for obj in objectslist: + if not hasattr(obj, "Path"): + print("the object " + obj.Name + " is not a path. Please select only path and Compounds.") + return None + + print("postprocessing...") + gcode = "" + + # write header + if OUTPUT_HEADER: + gcode += "%\n" + gcode += ";\n" + gcode += os.path.split(filename)[-1]+" ("+"FREECAD-FILENAME-GOES-HERE" + ", " + "JOB-NAME-GOES-HERE"+")\n" + gcode += linenumber() + "("+filename.upper()+",EXPORTED BY FREECAD!)\n" + gcode += linenumber() + "(POST PROCESSOR: " + __name__.upper() + ")\n" + gcode += linenumber() + "(OUTPUT TIME:" + str(now).upper() + ")\n" + + # Write the preamble + if OUTPUT_COMMENTS: + gcode += linenumber() + "(BEGIN PREAMBLE)\n" + for line in PREAMBLE.splitlines(False): + gcode += linenumber() + line + "\n" + gcode += linenumber() + UNITS + "\n" + + for obj in objectslist: + + # Skip inactive operations + if hasattr(obj, 'Active'): + if not obj.Active: + continue + if hasattr(obj, 'Base') and hasattr(obj.Base, 'Active'): + if not obj.Base.Active: + continue + + # fetch machine details + job = PathUtils.findParentJob(obj) + + myMachine = 'not set' + + if hasattr(job, "MachineName"): + myMachine = job.MachineName + + if hasattr(job, "MachineUnits"): + if job.MachineUnits == "Metric": + UNITS = "G21" + UNIT_FORMAT = 'mm' + UNIT_SPEED_FORMAT = 'mm/min' + else: + UNITS = "G20" + UNIT_FORMAT = 'in' + UNIT_SPEED_FORMAT = 'in/min' + + if hasattr(job, "SetupSheet"): + if hasattr(job.SetupSheet, "HorizRapid"): + HORIZRAPID = Units.Quantity(job.SetupSheet.HorizRapid, FreeCAD.Units.Velocity) + if hasattr(job.SetupSheet, "VertRapid"): + VERTRAPID = Units.Quantity(job.SetupSheet.HorizRapid, FreeCAD.Units.Velocity) + + # do the pre_op + if OUTPUT_COMMENTS: + gcode += linenumber() + "(BEGIN OPERATION: %s)\n" % obj.Label.upper() + gcode += linenumber() + "(MACHINE: %s, %s)\n" % (myMachine.upper(), UNIT_SPEED_FORMAT.upper()) + for line in PRE_OPERATION.splitlines(True): + gcode += linenumber() + line + + # get coolant mode + coolantMode = 'None' + if hasattr(obj, "CoolantMode") or hasattr(obj, 'Base') and hasattr(obj.Base, "CoolantMode"): + if hasattr(obj, "CoolantMode"): + coolantMode = obj.CoolantMode + else: + coolantMode = obj.Base.CoolantMode + + # turn coolant on if required + if OUTPUT_COMMENTS: + if not coolantMode == 'None': + gcode += linenumber() + '(COOLANT ON:' + coolantMode.upper() + ')\n' + if coolantMode == 'Flood': + gcode += linenumber() + 'M8' + '\n' + if coolantMode == 'Mist': + gcode += linenumber() + 'M7' + '\n' + + # process the operation gcode + gcode += parse(obj) + + # do the post_op + if OUTPUT_COMMENTS: + gcode += linenumber() + "(FINISH OPERATION: %s)\n" % obj.Label.upper() + for line in POST_OPERATION.splitlines(True): + gcode += linenumber() + line + + # turn coolant off if required + if not coolantMode == 'None': + if OUTPUT_COMMENTS: + gcode += linenumber() + '(COOLANT OFF:' + coolantMode.upper() + ')\n' + gcode += linenumber() +'M9' + '\n' + + # do the post_amble + if OUTPUT_COMMENTS: + gcode += "(BEGIN POSTAMBLE)\n" + for line in POSTAMBLE.splitlines(True): + gcode += linenumber() + line + gcode += "%\n" + + if FreeCAD.GuiUp and SHOW_EDITOR: + dia = PostUtils.GCodeEditorDialog() + dia.editor.setText(gcode) + result = dia.exec_() + if result: + final = dia.editor.toPlainText() + else: + final = gcode + else: + final = gcode + + print("done postprocessing.") + + if not filename == '-': + gfile = pythonopen(filename, "w") + gfile.write(final) + gfile.close() + + return final + + +def linenumber(): + # pylint: disable=global-statement + global LINENR + if OUTPUT_LINE_NUMBERS is True: + LINENR += 10 + return "N" + str(LINENR) + " " + return "" + + +def parse(pathobj): + # pylint: disable=global-statement + global PRECISION + global MODAL + global OUTPUT_DOUBLES + global UNIT_FORMAT + global UNIT_SPEED_FORMAT + global tapSpeed + + out = "" + lastcommand = None + precision_string = '.' + str(PRECISION) + 'f' + currLocation = {} # keep track for no doubles + print("Startup!") + + # the order of parameters + # arcs need work. original code from mach3_4 doesn't want K properties on XY plane. Not sure + # what fanuc does here. + params = ['X', 'Y', 'Z', 'A', 'B', 'C', 'I', 'J', 'F', 'S', 'T', 'Q', 'R', 'L', 'H', 'D', 'P'] + firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0}) + currLocation.update(firstmove.Parameters) # set First location Parameters + + if hasattr(pathobj, "Group"): # We have a compound or project. + # if OUTPUT_COMMENTS: + # out += linenumber() + "(compound: " + pathobj.Label + ")\n" + for p in pathobj.Group: + out += parse(p) + return out + else: # parsing simple path + + # groups might contain non-path things like stock. + if not hasattr(pathobj, "Path"): + return out + + # if OUTPUT_COMMENTS: + # out += linenumber() + "(" + pathobj.Label + ")\n" + + adaptiveOp = False + opHorizRapid = 0 + opVertRapid = 0 + + if 'Adaptive' in pathobj.Name: + adaptiveOp = True + if hasattr(pathobj, 'ToolController'): + if hasattr(pathobj.ToolController, 'HorizRapid') and pathobj.ToolController.HorizRapid > 0: + opHorizRapid = Units.Quantity(pathobj.ToolController.HorizRapid, FreeCAD.Units.Velocity) + else: + FreeCAD.Console.PrintWarning('Tool Controller Horizontal Rapid Values are unset'+ '\n') + + if hasattr(pathobj.ToolController, 'VertRapid') and pathobj.ToolController.VertRapid > 0: + opVertRapid = Units.Quantity(pathobj.ToolController.VertRapid, FreeCAD.Units.Velocity) + else: + FreeCAD.Console.PrintWarning('Tool Controller Vertical Rapid Values are unset'+ '\n') + + for index,c in enumerate(pathobj.Path.Commands): + + outstring = [] + command = c.Name + if index+1 == len(pathobj.Path.Commands): + nextcommand = "" + else: + nextcommand = pathobj.Path.Commands[index+1].Name + + if adaptiveOp and c.Name in ["G0", "G00"]: + if opHorizRapid and opVertRapid: + command = 'G1' + else: + outstring.append('(TOOL CONTROLLER RAPID VALUES ARE UNSET)' + '\n') + + # suppress moves in fixture selection + if pathobj.Label == "Fixture": + if command == "G0": + continue + + # if it's a tap, we rigid tap, so don't start the spindle yet... + if command == "M03" or command == "M3": + if pathobj.Tool.ToolType == "Tap": + tapSpeed = int(pathobj.SpindleSpeed) + continue + + # convert drill cycles to tap cycles if tool is a tap + if command == "G81" or command == "G83": + if hasattr(pathobj, 'ToolController') and pathobj.ToolController.Tool.ToolType == "Tap": + command = "G84" + out += linenumber() + "G95\n" + paramstring = "" + for param in [ "X", "Y" ]: + if param in c.Parameters: + if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]): + continue + else: + pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length) + paramstring += " " + param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string) + if paramstring != "": + out += linenumber() + "G00"+paramstring+"\n" + + if "S" in c.Parameters: + tapSpeed = int(c.Parameters['S']) + out += "M29 S"+str(tapSpeed)+"\n" + + for param in [ "Z", "R" ]: + if param in c.Parameters: + if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]): + continue + else: + pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length) + paramstring += " " + param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string) + # in this mode, F is the distance per revolution of the thread (pitch) + # P is the dwell time in seconds at the bottom of the thread + # Q is the peck depth of the threading operation + for param in [ "F", "P", "Q" ]: + if param in c.Parameters: + value = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length) + paramstring += " " + param + format(float(value.getValueAs(UNIT_FORMAT)), precision_string) + + out += linenumber() + "G84" + paramstring + "\n" + out += linenumber() + "G80\n" + out += linenumber() + "G94\n" + continue + + + outstring.append(command) + + # if modal: suppress the command if it is the same as the last one + if MODAL is True: + if command == lastcommand: + outstring.pop(0) + + # suppress a G80 between two identical command + if command == "G80" and lastcommand == nextcommand: + continue + + if c.Name[0] == '(' and not OUTPUT_COMMENTS: # command is a comment + continue + + # Now add the remaining parameters in order + for param in params: + if param in c.Parameters: + if param == 'F' and (currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES): + if c.Name not in ["G0", "G00"]: # fanuc doesn't use rapid speeds + speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity) + if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0: + outstring.append(param + format(float(speed.getValueAs(UNIT_SPEED_FORMAT)), precision_string)) + else: + continue + elif param == 'T': + outstring.append(param + str(int(c.Parameters['T']))) + elif param == 'H': + outstring.append(param + str(int(c.Parameters['H']))) + elif param == 'D': + outstring.append(param + str(int(c.Parameters['D']))) + elif param == 'S': + outstring.append(param + str(int(c.Parameters['S']))) + currentSpeed = int(c.Parameters['S']) + else: + if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]): + continue + else: + pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length) + outstring.append( + param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string)) + + if adaptiveOp and c.Name in ["G0", "G00"]: + if opHorizRapid and opVertRapid: + if 'Z' not in c.Parameters: + outstring.append('F' + format(float(opHorizRapid.getValueAs(UNIT_SPEED_FORMAT)), precision_string)) + else: + outstring.append('F' + format(float(opVertRapid.getValueAs(UNIT_SPEED_FORMAT)), precision_string)) + + # store the latest command + lastcommand = command + currLocation.update(c.Parameters) + + # Check for Tool Change: + if command == 'M6': + # stop the spindle + currentSpeed = 0 + out += linenumber() + "M5\n" + for line in TOOL_CHANGE.splitlines(True): + out += linenumber() + line + + # add height offset + if USE_TLO: + tool_height = '\nG43 H' + str(int(c.Parameters['T'])) + outstring.append(tool_height) + + if command == "message": + if OUTPUT_COMMENTS is False: + out = [] + else: + outstring.pop(0) # remove the command + + # prepend a line number and append a newline + if len(outstring) >= 1: + if OUTPUT_LINE_NUMBERS: + outstring.insert(0, (linenumber())) + + # append the line to the final output + for w in outstring: + out += w.upper() + COMMAND_SPACE + out = out.strip() + "\n" + + return out + +# print(__name__ + " gcode postprocessor loaded.") From a78ef319c62304df27d4e22a2e4612fa142ea543 Mon Sep 17 00:00:00 2001 From: Roy-043 <70520633+Roy-043@users.noreply.github.com> Date: Fri, 22 Jan 2021 20:17:47 +0100 Subject: [PATCH 014/168] Fixed typo in Arch_Schedule Typo --- src/Mod/Arch/ArchSchedule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Arch/ArchSchedule.py b/src/Mod/Arch/ArchSchedule.py index 9a11a60e5f..57003b1441 100644 --- a/src/Mod/Arch/ArchSchedule.py +++ b/src/Mod/Arch/ArchSchedule.py @@ -220,7 +220,7 @@ class _ArchSchedule: prop = args[0].upper() fval = args[1].upper() if prop == "TYPE": - prop == "IFCTYPE" + prop = "IFCTYPE" if inv: if prop in props: csprop = o.PropertiesList[props.index(prop)] From 7306ed28b0cac8e7519cfa1cae9d8c1d376dd0e3 Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 13 Dec 2020 06:12:35 +0100 Subject: [PATCH 015/168] [PD] fix blind hole depth handling as reported in https://tracker.freecadweb.org/view.php?id=3818 we treat the blind hole depth not according to the conventions. The size of the drill point due to the angle is normally not taken into account but FC does this in any case. This PR adds therefore an option, that is by default off, to take the drill point size into account. Without the option, (the new default) the depth is calculated according to the conventions. The PR also removes unused widgets and restored the tab order in the .ui file. The thread parameters were never used. In case we made in future the decision to carve into holes real (modeled) threads, we need a special UI for that solution anyway so having the dead code in is not helpful at all. --- src/Mod/PartDesign/App/FeatureHole.cpp | 119 ++++---- src/Mod/PartDesign/App/FeatureHole.h | 1 + src/Mod/PartDesign/Gui/TaskHoleParameters.cpp | 121 ++++---- src/Mod/PartDesign/Gui/TaskHoleParameters.h | 2 + src/Mod/PartDesign/Gui/TaskHoleParameters.ui | 258 +++++++----------- .../PartDesign/PartDesignTests/TestHole.py | 1 + 6 files changed, 216 insertions(+), 286 deletions(-) diff --git a/src/Mod/PartDesign/App/FeatureHole.cpp b/src/Mod/PartDesign/App/FeatureHole.cpp index 1808d0b1e7..444005fdee 100644 --- a/src/Mod/PartDesign/App/FeatureHole.cpp +++ b/src/Mod/PartDesign/App/FeatureHole.cpp @@ -591,6 +591,8 @@ Hole::Hole() DrillPoint.setEnums(DrillPointEnums); ADD_PROPERTY_TYPE(DrillPointAngle, (118.0), "Hole", App::Prop_None, "Drill point angle"); + ADD_PROPERTY_TYPE(DrillForDepth, ((long)0), "Hole", App::Prop_None, + "The size of the drill point will be taken into\n account for the depth of blind holes"); ADD_PROPERTY_TYPE(Tapered, (false),"Hole", App::Prop_None, "Tapered"); @@ -1043,19 +1045,19 @@ void Hole::onChanged(const App::Property *prop) } else if (prop == &DepthType) { Depth.setReadOnly((std::string(DepthType.getValueAsString()) != "Dimension")); + DrillForDepth.setReadOnly((std::string(DepthType.getValueAsString()) != "Dimension")); } ProfileBased::onChanged(prop); } /** - * Compute 2D intersection between the lines (pa1, pa2) and (pb1, pb2). + * Computes 2D intersection between the lines (pa1, pa2) and (pb1, pb2). * The lines are assumed to be crossing, and it is an error * to specify parallel lines. + * Only the x and y coordinates of the points are used to compute the 2D intersection. * - * Only the x and y coordinates are used to compute the 2D intersection. - * + * The result are the x and y coordinate of the intersection point. */ - static void computeIntersection(gp_Pnt pa1, gp_Pnt pa2, gp_Pnt pb1, gp_Pnt pb2, double & x, double & y) { double vx1 = pa1.X() - pa2.X(); @@ -1200,7 +1202,7 @@ App::DocumentObjectExecReturn *Hole::execute(void) base.Move(invObjLoc); if (profileshape.IsNull()) - return new App::DocumentObjectExecReturn("Pocket: Creating a face from sketch failed"); + return new App::DocumentObjectExecReturn("Hole error: Creating a face from sketch failed"); profileshape.Move(invObjLoc); /* Build the prototype hole */ @@ -1238,10 +1240,10 @@ App::DocumentObjectExecReturn *Hole::execute(void) length = 1e4; } else - return new App::DocumentObjectExecReturn("Hole: Unsupported length specification."); + return new App::DocumentObjectExecReturn("Hole error: Unsupported length specification"); - if (length <= 0) - return new App::DocumentObjectExecReturn("Hole: Invalid hole depth"); + if (length <= 0.0) + return new App::DocumentObjectExecReturn("Hole error: Invalid hole depth"); BRepBuilderAPI_MakeWire mkWire; const std::string holeCutType = HoleCutType.getValueAsString(); @@ -1253,26 +1255,27 @@ App::DocumentObjectExecReturn *Hole::execute(void) holeCutType == "Cheesehead (deprecated)" || holeCutType == "Cap screw (deprecated)" || isDynamicCounterbore(threadType, holeCutType)); - double hasTaperedAngle = Tapered.getValue() ? Base::toRadians( TaperedAngle.getValue() ) : Base::toRadians(90.0); - double radiusBottom = Diameter.getValue() / 2.0 - length * 1.0 / tan( hasTaperedAngle ); + double TaperedAngleVal = Tapered.getValue() ? Base::toRadians( TaperedAngle.getValue() ) : Base::toRadians(90.0); + double radiusBottom = Diameter.getValue() / 2.0 - length / tan(TaperedAngleVal); double radius = Diameter.getValue() / 2.0; double holeCutRadius = HoleCutDiameter.getValue() / 2.0; gp_Pnt firstPoint(0, 0, 0); gp_Pnt lastPoint(0, 0, 0); - double length1 = 0; + double lengthCounter = 0.0; + double xPosCounter = 0.0; + double zPosCounter = 0.0; - if ( hasTaperedAngle <= 0 || hasTaperedAngle > Base::toRadians( 180.0 ) ) - return new App::DocumentObjectExecReturn("Hole: Invalid taper angle."); + if (TaperedAngleVal <= 0.0 || TaperedAngleVal > Base::toRadians( 180.0 ) ) + return new App::DocumentObjectExecReturn("Hole error: Invalid taper angle"); if ( isCountersink ) { - double x, z; double countersinkAngle = Base::toRadians( HoleCutCountersinkAngle.getValue() / 2.0 ); if ( countersinkAngle <= 0 || countersinkAngle > Base::toRadians( 180.0 ) ) - return new App::DocumentObjectExecReturn("Hole: Invalid countersink angle."); + return new App::DocumentObjectExecReturn("Hole error: Invalid countersink angle"); if (holeCutRadius < radius) - return new App::DocumentObjectExecReturn("Hole: Hole cut diameter too small."); + return new App::DocumentObjectExecReturn("Hole error: Hole cut diameter too small"); // Top point gp_Pnt newPoint = toPnt(holeCutRadius * xDir); @@ -1282,28 +1285,27 @@ App::DocumentObjectExecReturn *Hole::execute(void) computeIntersection(gp_Pnt( holeCutRadius, 0, 0 ), gp_Pnt( holeCutRadius - sin( countersinkAngle ), -cos( countersinkAngle ), 0 ), gp_Pnt( radius, 0, 0 ), - gp_Pnt( radiusBottom, -length, 0), x, z ); - if (-length > z) - return new App::DocumentObjectExecReturn("Hole: Invalid countersink."); + gp_Pnt( radiusBottom, -length, 0), xPosCounter, zPosCounter); + if (-length > zPosCounter) + return new App::DocumentObjectExecReturn("Hole error: Invalid countersink"); - length1 = z; + lengthCounter = zPosCounter; - newPoint = toPnt(x * xDir + z * zDir); + newPoint = toPnt(xPosCounter * xDir + zPosCounter * zDir); mkWire.Add( BRepBuilderAPI_MakeEdge( lastPoint, newPoint ) ); lastPoint = newPoint; } else if ( isCounterbore ) { double holeCutDepth = HoleCutDepth.getValue(); - double x, z; - if (holeCutDepth <= 0) - return new App::DocumentObjectExecReturn("Hole: Hole cut depth must be greater than zero."); + if (holeCutDepth <= 0.0) + return new App::DocumentObjectExecReturn("Hole error: Hole cut depth must be greater than zero"); if (holeCutDepth > length) - return new App::DocumentObjectExecReturn("Hole: Hole cut depth must be less than hole depth."); + return new App::DocumentObjectExecReturn("Hole error: Hole cut depth must be less than hole depth"); if (holeCutRadius < radius) - return new App::DocumentObjectExecReturn("Hole: Hole cut diameter too small."); + return new App::DocumentObjectExecReturn("Hole error: Hole cut diameter too small"); // Top point gp_Pnt newPoint = toPnt(holeCutRadius * xDir); @@ -1311,7 +1313,7 @@ App::DocumentObjectExecReturn *Hole::execute(void) lastPoint = newPoint; // Bottom of counterbore - newPoint = toPnt(holeCutRadius * xDir -holeCutDepth * zDir); + newPoint = toPnt(holeCutRadius * xDir - holeCutDepth * zDir); mkWire.Add(BRepBuilderAPI_MakeEdge(lastPoint, newPoint)); lastPoint = newPoint; @@ -1319,10 +1321,10 @@ App::DocumentObjectExecReturn *Hole::execute(void) computeIntersection(gp_Pnt( 0, -holeCutDepth, 0 ), gp_Pnt( holeCutRadius, -holeCutDepth, 0 ), gp_Pnt( radius, 0, 0 ), - gp_Pnt( radiusBottom, length, 0 ), x, z ); + gp_Pnt( radiusBottom, length, 0 ), xPosCounter, zPosCounter); - length1 = z; - newPoint = toPnt(x * xDir + z * zDir); + lengthCounter = zPosCounter; + newPoint = toPnt(xPosCounter * xDir + zPosCounter * zDir); mkWire.Add(BRepBuilderAPI_MakeEdge(lastPoint, newPoint)); lastPoint = newPoint; } @@ -1330,10 +1332,12 @@ App::DocumentObjectExecReturn *Hole::execute(void) gp_Pnt newPoint = toPnt(radius * xDir); mkWire.Add(BRepBuilderAPI_MakeEdge(lastPoint, newPoint)); lastPoint = newPoint; - length1 = 0; + lengthCounter = 0.0; } std::string drillPoint = DrillPoint.getValueAsString(); + double xPosDrill = 0.0; + double zPosDrill = 0.0; if (drillPoint == "Flat") { gp_Pnt newPoint = toPnt(radiusBottom * xDir + -length * zDir); mkWire.Add(BRepBuilderAPI_MakeEdge(lastPoint, newPoint)); @@ -1345,30 +1349,47 @@ App::DocumentObjectExecReturn *Hole::execute(void) } else if (drillPoint == "Angled") { double drillPointAngle = Base::toRadians( ( 180.0 - DrillPointAngle.getValue() ) / 2.0 ); - double x, z; gp_Pnt newPoint; + bool isDrillForDepth = DrillForDepth.getValue(); - if ( drillPointAngle <= 0 || drillPointAngle > Base::toRadians( 180.0 ) ) - return new App::DocumentObjectExecReturn("Hole: Invalid drill point angle."); + // the angle is in any case > 0 and < 90 but nevertheless this safeguard: + if ( drillPointAngle <= 0.0 || drillPointAngle >= Base::toRadians( 180.0 ) ) + return new App::DocumentObjectExecReturn("Hole error: Invalid drill point angle"); - computeIntersection(gp_Pnt( 0, -length, 0 ), - gp_Pnt( cos( drillPointAngle ), -length + sin( drillPointAngle ), 0), - gp_Pnt(radius, 0, 0), - gp_Pnt(radiusBottom, -length, 0), x, z); + // if option to take drill point size into account + // the next wire point is the intersection of the drill edge and the hole edge + if (isDrillForDepth) { + computeIntersection(gp_Pnt(0, -length, 0), + gp_Pnt(radius, radius * tan(drillPointAngle) - length, 0), + gp_Pnt(radius, 0, 0), + gp_Pnt(radiusBottom, -length, 0), xPosDrill, zPosDrill); + if (zPosDrill > 0 || zPosDrill >= lengthCounter) + return new App::DocumentObjectExecReturn("Hole error: Invalid drill point"); - if (z > 0 || z >= length1) - return new App::DocumentObjectExecReturn("Hole: Invalid drill point."); + newPoint = toPnt(xPosDrill * xDir + zPosDrill * zDir); + mkWire.Add(BRepBuilderAPI_MakeEdge(lastPoint, newPoint)); + lastPoint = newPoint; - newPoint = toPnt(x * xDir + z * zDir); - mkWire.Add(BRepBuilderAPI_MakeEdge(lastPoint, newPoint)); - lastPoint = newPoint; + newPoint = toPnt(-length * zDir); + mkWire.Add(BRepBuilderAPI_MakeEdge(lastPoint, newPoint)); + lastPoint = newPoint; + } + else { + xPosDrill = radiusBottom; + zPosDrill = -length; - newPoint = toPnt(-length * zDir); - mkWire.Add(BRepBuilderAPI_MakeEdge(lastPoint, newPoint)); - lastPoint = newPoint; + newPoint = toPnt(xPosDrill * xDir + zPosDrill * zDir); + mkWire.Add(BRepBuilderAPI_MakeEdge(lastPoint, newPoint)); + lastPoint = newPoint; + + // the end point is the size of the drill tip + newPoint = toPnt((-length - radius * tan(drillPointAngle)) * zDir); + mkWire.Add(BRepBuilderAPI_MakeEdge(lastPoint, newPoint)); + lastPoint = newPoint; + } } - mkWire.Add( BRepBuilderAPI_MakeEdge(lastPoint, firstPoint) ); + mkWire.Add( BRepBuilderAPI_MakeEdge(lastPoint, firstPoint) ); TopoDS_Wire wire = mkWire.Wire(); @@ -1382,10 +1403,10 @@ App::DocumentObjectExecReturn *Hole::execute(void) protoHole = RevolMaker.Shape(); if (protoHole.IsNull()) - return new App::DocumentObjectExecReturn("Hole: Resulting shape is empty"); + return new App::DocumentObjectExecReturn("Hole error: Resulting shape is empty"); } else - return new App::DocumentObjectExecReturn("Hole: Could not revolve sketch!"); + return new App::DocumentObjectExecReturn("Hole error: Could not revolve sketch"); #if 0 // Make thread diff --git a/src/Mod/PartDesign/App/FeatureHole.h b/src/Mod/PartDesign/App/FeatureHole.h index 66dde28a3a..72cf07fc13 100644 --- a/src/Mod/PartDesign/App/FeatureHole.h +++ b/src/Mod/PartDesign/App/FeatureHole.h @@ -64,6 +64,7 @@ public: App::PropertyLength Depth; App::PropertyEnumeration DrillPoint; App::PropertyAngle DrillPointAngle; + App::PropertyBool DrillForDepth; App::PropertyBool Tapered; App::PropertyAngle TaperedAngle; diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp b/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp index 407f5465b5..0e9878f8ba 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp @@ -67,17 +67,6 @@ TaskHoleParameters::TaskHoleParameters(ViewProviderHole *HoleView, QWidget *pare ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); - /* Remove actual threading parameters for now */ - ui->ModelActualThread->setVisible(false); - ui->ThreadPitch->setVisible(false); - ui->ThreadCutOffInner->setVisible(false); - ui->ThreadCutOffOuter->setVisible(false); - ui->ThreadAngle->setVisible(false); - ui->label_Pitch->setVisible(false); - ui->label_CutoffInner->setVisible(false); - ui->label_CutoffOuter->setVisible(false); - ui->label_Angle->setVisible(false); - ui->ThreadType->addItem(tr("None"), QByteArray("None")); ui->ThreadType->addItem(tr("ISO metric regular profile"), QByteArray("ISO")); ui->ThreadType->addItem(tr("ISO metric fine profile"), QByteArray("ISO")); @@ -88,11 +77,6 @@ TaskHoleParameters::TaskHoleParameters(ViewProviderHole *HoleView, QWidget *pare // read values from the hole properties PartDesign::Hole* pcHole = static_cast(vp->getObject()); ui->Threaded->setChecked(pcHole->Threaded.getValue()); - ui->ModelActualThread->setChecked(pcHole->ModelActualThread.getValue()); - ui->ThreadPitch->setValue(pcHole->ThreadPitch.getValue()); - ui->ThreadAngle->setValue(pcHole->ThreadAngle.getValue()); - ui->ThreadCutOffInner->setValue(pcHole->ThreadCutOffInner.getValue()); - ui->ThreadCutOffOuter->setValue(pcHole->ThreadCutOffOuter.getValue()); ui->ThreadType->setCurrentIndex(pcHole->ThreadType.getValue()); ui->ThreadSize->clear(); const char** cursor = pcHole->ThreadSize.getEnums(); @@ -167,6 +151,12 @@ TaskHoleParameters::TaskHoleParameters(ViewProviderHole *HoleView, QWidget *pare else ui->drillPointAngled->setChecked(true); ui->DrillPointAngle->setValue(pcHole->DrillPointAngle.getValue()); + ui->DrillForDepth->setChecked(pcHole->DrillForDepth.getValue()); + // DrillForDepth is only enabled (sensible) if type is 'Dimension' + if (std::string(pcHole->DepthType.getValueAsString()) == "Dimension") + ui->DrillForDepth->setEnabled(true); + else + ui->DrillForDepth->setEnabled(false); ui->Tapered->setChecked(pcHole->Tapered.getValue()); // Angle is only enabled (sensible) if tapered ui->TaperedAngle->setEnabled(pcHole->Tapered.getValue()); @@ -175,11 +165,6 @@ TaskHoleParameters::TaskHoleParameters(ViewProviderHole *HoleView, QWidget *pare connect(ui->Threaded, SIGNAL(clicked(bool)), this, SLOT(threadedChanged())); connect(ui->ThreadType, SIGNAL(currentIndexChanged(int)), this, SLOT(threadTypeChanged(int))); - connect(ui->ModelActualThread, SIGNAL(clicked(bool)), this, SLOT(modelActualThreadChanged())); - connect(ui->ThreadPitch, SIGNAL(valueChanged(double)), this, SLOT(threadPitchChanged(double))); - connect(ui->ThreadAngle, SIGNAL(valueChanged(double)), this, SLOT(threadAngleChanged(double))); - connect(ui->ThreadCutOffInner, SIGNAL(valueChanged(double)), this, SLOT(threadCutOffInnerChanged(double))); - connect(ui->ThreadCutOffOuter, SIGNAL(valueChanged(double)), this, SLOT(threadCutOffOuterChanged(double))); connect(ui->ThreadSize, SIGNAL(currentIndexChanged(int)), this, SLOT(threadSizeChanged(int))); connect(ui->ThreadClass, SIGNAL(currentIndexChanged(int)), this, SLOT(threadClassChanged(int))); connect(ui->ThreadFit, SIGNAL(currentIndexChanged(int)), this, SLOT(threadFitChanged(int))); @@ -195,16 +180,13 @@ TaskHoleParameters::TaskHoleParameters(ViewProviderHole *HoleView, QWidget *pare connect(ui->drillPointFlat, SIGNAL(clicked(bool)), this, SLOT(drillPointChanged())); connect(ui->drillPointAngled, SIGNAL(clicked(bool)), this, SLOT(drillPointChanged())); connect(ui->DrillPointAngle, SIGNAL(valueChanged(double)), this, SLOT(drillPointAngledValueChanged(double))); + connect(ui->DrillForDepth, SIGNAL(clicked(bool)), this, SLOT(drillForDepthChanged())); connect(ui->Tapered, SIGNAL(clicked(bool)), this, SLOT(taperedChanged())); connect(ui->Reversed, SIGNAL(clicked(bool)), this, SLOT(reversedChanged())); connect(ui->TaperedAngle, SIGNAL(valueChanged(double)), this, SLOT(taperedAngleChanged(double))); vp->show(); - ui->ThreadPitch->bind(pcHole->ThreadPitch); - ui->ThreadAngle->bind(pcHole->ThreadAngle); - ui->ThreadCutOffInner->bind(pcHole->ThreadCutOffInner); - ui->ThreadCutOffOuter->bind(pcHole->ThreadCutOffOuter); ui->Diameter->bind(pcHole->Diameter); ui->HoleCutDiameter->bind(pcHole->HoleCutDiameter); ui->HoleCutDepth->bind(pcHole->HoleCutDepth); @@ -236,7 +218,7 @@ void TaskHoleParameters::modelActualThreadChanged() { PartDesign::Hole* pcHole = static_cast(vp->getObject()); - pcHole->ModelActualThread.setValue(ui->ModelActualThread->isChecked()); + pcHole->ModelActualThread.setValue(false); recomputeFeature(); } @@ -333,6 +315,12 @@ void TaskHoleParameters::depthChanged(int index) PartDesign::Hole* pcHole = static_cast(vp->getObject()); pcHole->DepthType.setValue(index); + + // disable DrillforDepth if not 'Dimension' + if (std::string(pcHole->DepthType.getValueAsString()) == "Dimension") + ui->DrillForDepth->setEnabled(true); + else + ui->DrillForDepth->setEnabled(false); recomputeFeature(); } @@ -348,12 +336,17 @@ void TaskHoleParameters::drillPointChanged() { PartDesign::Hole* pcHole = static_cast(vp->getObject()); - if (sender() == ui->drillPointFlat) + if (sender() == ui->drillPointFlat) { pcHole->DrillPoint.setValue((long)0); - else if (sender() == ui->drillPointAngled) + ui->DrillForDepth->setEnabled(false); + } + else if (sender() == ui->drillPointAngled) { pcHole->DrillPoint.setValue((long)1); - else - assert( 0 ); + ui->DrillForDepth->setEnabled(true); + } + else { + assert(0); + } recomputeFeature(); } @@ -365,6 +358,14 @@ void TaskHoleParameters::drillPointAngledValueChanged(double value) recomputeFeature(); } +void TaskHoleParameters::drillForDepthChanged() +{ + PartDesign::Hole* pcHole = static_cast(vp->getObject()); + + pcHole->DrillForDepth.setValue(ui->DrillForDepth->isChecked()); + recomputeFeature(); +} + void TaskHoleParameters::taperedChanged() { PartDesign::Hole* pcHole = static_cast(vp->getObject()); @@ -528,46 +529,6 @@ void TaskHoleParameters::changedObject(const App::Document&, const App::Property } ui->Threaded->setDisabled(ro); } - else if (&Prop == &pcHole->ModelActualThread) { - if (ui->ModelActualThread->isChecked() ^ pcHole->ModelActualThread.getValue()) { - ui->ModelActualThread->blockSignals(true); - ui->ModelActualThread->setChecked(pcHole->ModelActualThread.getValue()); - ui->ModelActualThread->blockSignals(false); - } - ui->ModelActualThread->setDisabled(ro); - } - else if (&Prop == &pcHole->ThreadPitch) { - if (ui->ThreadPitch->value().getValue() != pcHole->ThreadPitch.getValue()) { - ui->ThreadPitch->blockSignals(true); - ui->ThreadPitch->setValue(pcHole->ThreadPitch.getValue()); - ui->ThreadPitch->blockSignals(false); - } - ui->ThreadPitch->setDisabled(ro); - } - else if (&Prop == &pcHole->ThreadAngle) { - if (ui->ThreadAngle->value().getValue() != pcHole->ThreadAngle.getValue()) { - ui->ThreadAngle->blockSignals(true); - ui->ThreadAngle->setValue(pcHole->ThreadAngle.getValue()); - ui->ThreadAngle->blockSignals(false); - } - ui->ThreadAngle->setDisabled(ro); - } - else if (&Prop == &pcHole->ThreadCutOffInner) { - if (ui->ThreadCutOffInner->value().getValue() != pcHole->ThreadCutOffInner.getValue()) { - ui->ThreadCutOffInner->blockSignals(true); - ui->ThreadCutOffInner->setValue(pcHole->ThreadCutOffInner.getValue()); - ui->ThreadCutOffInner->blockSignals(false); - } - ui->ThreadCutOffInner->setDisabled(ro); - } - else if (&Prop == &pcHole->ThreadCutOffOuter) { - if (ui->ThreadCutOffOuter->value().getValue() != pcHole->ThreadCutOffOuter.getValue()) { - ui->ThreadCutOffOuter->blockSignals(true); - ui->ThreadCutOffOuter->setValue(pcHole->ThreadCutOffOuter.getValue()); - ui->ThreadCutOffOuter->blockSignals(false); - } - ui->ThreadCutOffOuter->setDisabled(ro); - } else if (&Prop == &pcHole->ThreadType) { ui->ThreadType->setEnabled(true); @@ -742,6 +703,15 @@ void TaskHoleParameters::changedObject(const App::Document&, const App::Property } ui->DrillPointAngle->setDisabled(ro); } + else if (&Prop == &pcHole->DrillForDepth) { + ui->DrillForDepth->setEnabled(true); + if (ui->DrillForDepth->isChecked() ^ pcHole->DrillForDepth.getValue()) { + ui->DrillForDepth->blockSignals(true); + ui->DrillForDepth->setChecked(pcHole->DrillForDepth.getValue()); + ui->DrillForDepth->blockSignals(false); + } + ui->DrillForDepth->setDisabled(ro); + } else if (&Prop == &pcHole->Tapered) { ui->Tapered->setEnabled(true); if (ui->Tapered->isChecked() ^ pcHole->Tapered.getValue()) { @@ -866,6 +836,11 @@ bool TaskHoleParameters::getTapered() const return ui->Tapered->isChecked(); } +bool TaskHoleParameters::getDrillForDepth() const +{ + return ui->DrillForDepth->isChecked(); +} + Base::Quantity TaskHoleParameters::getTaperedAngle() const { return ui->TaperedAngle->value(); @@ -878,10 +853,6 @@ void TaskHoleParameters::apply() isApplying = true; - ui->ThreadPitch->apply(); - ui->ThreadAngle->apply(); - ui->ThreadCutOffInner->apply(); - ui->ThreadCutOffOuter->apply(); ui->Diameter->apply(); ui->HoleCutDiameter->apply(); ui->HoleCutDepth->apply(); @@ -910,6 +881,8 @@ void TaskHoleParameters::apply() FCMD_OBJ_CMD(obj,"DepthType = " << getDepthType()); if (!pcHole->DrillPoint.isReadOnly()) FCMD_OBJ_CMD(obj,"DrillPoint = " << getDrillPoint()); + if (!pcHole->DrillForDepth.isReadOnly()) + FCMD_OBJ_CMD(obj, "DrillForDepth = " << (getDrillForDepth() ? 1 : 0)); if (!pcHole->Tapered.isReadOnly()) FCMD_OBJ_CMD(obj,"Tapered = " << getTapered()); diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.h b/src/Mod/PartDesign/Gui/TaskHoleParameters.h index 9c74bb30af..bfa22a693c 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.h +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.h @@ -76,6 +76,7 @@ public: Base::Quantity getDepth() const; long getDrillPoint() const; Base::Quantity getDrillPointAngle() const; + bool getDrillForDepth() const; bool getTapered() const; Base::Quantity getTaperedAngle() const; @@ -100,6 +101,7 @@ private Q_SLOTS: void depthValueChanged(double value); void drillPointChanged(); void drillPointAngledValueChanged(double value); + void drillForDepthChanged(); void taperedChanged(); void reversedChanged(); void taperedAngleChanged(double value); diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui index c723acb05c..37becccf82 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui @@ -6,8 +6,8 @@ 0 0 - 441 - 710 + 354 + 463 @@ -66,116 +66,7 @@ - - - - false - - - Model actual thread - - - - - - - false - - - Pitch - - - - - - - false - - - mm - - - 0.000000000000000 - - - - - - - <b>Hole cut</b> - - - - - - - false - - - Angle - - - - - - - false - - - deg - - - 0.000000000000000 - - - - - - - false - - - Cutoff inner - - - - - - - false - - - mm - - - 0.000000000000000 - - - - - - - false - - - Cutoff outer - - - - - - - false - - - mm - - - 0.000000000000000 - - - - + @@ -191,7 +82,7 @@ - + @@ -204,7 +95,7 @@ - + Left hand @@ -214,7 +105,7 @@ - + @@ -227,7 +118,7 @@ - + @@ -243,14 +134,14 @@ - + Clearance - + @@ -285,7 +176,7 @@ Only available for holes without thread - + @@ -298,7 +189,7 @@ Only available for holes without thread - + @@ -317,7 +208,7 @@ Only available for holes without thread - + Qt::Horizontal @@ -333,7 +224,20 @@ Only available for holes without thread - + + + + + 0 + 0 + + + + Diameter + + + + @@ -358,20 +262,7 @@ Only available for holes without thread - - - - - 0 - 0 - - - - Diameter - - - - + @@ -384,7 +275,7 @@ Only available for holes without thread - + @@ -410,7 +301,7 @@ Only available for holes without thread - + @@ -423,7 +314,14 @@ Only available for holes without thread - + + + + <b>Hole cut</b> + + + + @@ -436,7 +334,7 @@ Only available for holes without thread - + @@ -455,14 +353,14 @@ Only available for holes without thread - + Diameter - + @@ -490,14 +388,14 @@ Only available for holes without thread - + Depth - + @@ -522,14 +420,14 @@ Only available for holes without thread - + Countersink angle - + @@ -551,7 +449,7 @@ Only available for holes without thread - + @@ -567,7 +465,7 @@ Only available for holes without thread - + Type @@ -577,7 +475,7 @@ Only available for holes without thread - + @@ -593,7 +491,7 @@ Only available for holes without thread - + @@ -609,7 +507,7 @@ Only available for holes without thread - + @@ -625,21 +523,32 @@ Only available for holes without thread - + + + + The size of the drill point will be taken into +account for the depth of blind holes + + + Take into account for depth + + + + <b>Misc</b> - + Tapered - + Taper angle for the hole @@ -655,7 +564,7 @@ over 90: larger hole radius at the bottom - + Reverses the hole direction @@ -679,6 +588,29 @@ over 90: larger hole radius at the bottom
Gui/PrefWidgets.h
+ + ThreadType + Threaded + directionRightHand + directionLeftHand + ThreadSize + ThreadFit + ThreadClass + Diameter + DepthType + Depth + HoleCutType + HoleCutDiameter + HoleCutDepth + HoleCutCountersinkAngle + drillPointFlat + drillPointAngled + DrillPointAngle + DrillForDepth + Tapered + TaperedAngle + Reversed + @@ -688,12 +620,12 @@ over 90: larger hole radius at the bottom setEnabled(bool) - 40 - 540 + 49 + 451 - 136 - 540 + 163 + 453 @@ -708,8 +640,8 @@ over 90: larger hole radius at the bottom 63 - 322 - 254 + 344 + 142 @@ -724,8 +656,8 @@ over 90: larger hole radius at the bottom 63 - 136 - 280 + 163 + 168 diff --git a/src/Mod/PartDesign/PartDesignTests/TestHole.py b/src/Mod/PartDesign/PartDesignTests/TestHole.py index cc838f4a3b..a32dd11bd4 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestHole.py +++ b/src/Mod/PartDesign/PartDesignTests/TestHole.py @@ -86,6 +86,7 @@ class TestHole(unittest.TestCase): self.Hole.DepthType = 0 self.Hole.DrillPoint = 1 self.Hole.Tapered = 0 + self.Hole.DrillForDepth = 1 self.Doc.recompute() self.assertEqual(len(self.Hole.Shape.Faces), 8) From 2d2041e98c5c30fe59e344de930ad743aa19db38 Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 24 Jan 2021 03:06:14 +0100 Subject: [PATCH 016/168] [TD] add Arch paper size Templates thanks to the recently merged PR #4292 one can use custom page sizes such as the templates in https://wiki.freecadweb.org/Arch_templates This PR add them to be directly provided by FC. --- .../TechDraw/Templates/Arch_A_Landscape.svg | 358 +++++++++++++++++ .../TechDraw/Templates/Arch_A_Portrait.svg | 358 +++++++++++++++++ .../TechDraw/Templates/Arch_B_Landscape.svg | 358 +++++++++++++++++ .../TechDraw/Templates/Arch_B_Portrait.svg | 358 +++++++++++++++++ .../TechDraw/Templates/Arch_C_Landscape.svg | 358 +++++++++++++++++ .../TechDraw/Templates/Arch_C_Portrait.svg | 358 +++++++++++++++++ .../TechDraw/Templates/Arch_D_Landscape.svg | 358 +++++++++++++++++ .../TechDraw/Templates/Arch_D_Portrait.svg | 358 +++++++++++++++++ .../TechDraw/Templates/Arch_E1_Landscape.svg | 358 +++++++++++++++++ .../TechDraw/Templates/Arch_E1_Portrait.svg | 358 +++++++++++++++++ .../TechDraw/Templates/Arch_E2_Landscape.svg | 366 ++++++++++++++++++ .../TechDraw/Templates/Arch_E2_Portrait.svg | 358 +++++++++++++++++ .../TechDraw/Templates/Arch_E3_Landscape.svg | 358 +++++++++++++++++ .../TechDraw/Templates/Arch_E3_Portrait.svg | 366 ++++++++++++++++++ .../TechDraw/Templates/Arch_E_Landscape.svg | 358 +++++++++++++++++ .../TechDraw/Templates/Arch_E_Portrait.svg | 358 +++++++++++++++++ 16 files changed, 5744 insertions(+) create mode 100644 src/Mod/TechDraw/Templates/Arch_A_Landscape.svg create mode 100644 src/Mod/TechDraw/Templates/Arch_A_Portrait.svg create mode 100644 src/Mod/TechDraw/Templates/Arch_B_Landscape.svg create mode 100644 src/Mod/TechDraw/Templates/Arch_B_Portrait.svg create mode 100644 src/Mod/TechDraw/Templates/Arch_C_Landscape.svg create mode 100644 src/Mod/TechDraw/Templates/Arch_C_Portrait.svg create mode 100644 src/Mod/TechDraw/Templates/Arch_D_Landscape.svg create mode 100644 src/Mod/TechDraw/Templates/Arch_D_Portrait.svg create mode 100644 src/Mod/TechDraw/Templates/Arch_E1_Landscape.svg create mode 100644 src/Mod/TechDraw/Templates/Arch_E1_Portrait.svg create mode 100644 src/Mod/TechDraw/Templates/Arch_E2_Landscape.svg create mode 100644 src/Mod/TechDraw/Templates/Arch_E2_Portrait.svg create mode 100644 src/Mod/TechDraw/Templates/Arch_E3_Landscape.svg create mode 100644 src/Mod/TechDraw/Templates/Arch_E3_Portrait.svg create mode 100644 src/Mod/TechDraw/Templates/Arch_E_Landscape.svg create mode 100644 src/Mod/TechDraw/Templates/Arch_E_Portrait.svg diff --git a/src/Mod/TechDraw/Templates/Arch_A_Landscape.svg b/src/Mod/TechDraw/Templates/Arch_A_Landscape.svg new file mode 100644 index 0000000000..6659dfce8b --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_A_Landscape.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch A + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_A_Portrait.svg b/src/Mod/TechDraw/Templates/Arch_A_Portrait.svg new file mode 100644 index 0000000000..253821f556 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_A_Portrait.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch A + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_B_Landscape.svg b/src/Mod/TechDraw/Templates/Arch_B_Landscape.svg new file mode 100644 index 0000000000..caa5a6faca --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_B_Landscape.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch B + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_B_Portrait.svg b/src/Mod/TechDraw/Templates/Arch_B_Portrait.svg new file mode 100644 index 0000000000..912e8be8b3 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_B_Portrait.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch B + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_C_Landscape.svg b/src/Mod/TechDraw/Templates/Arch_C_Landscape.svg new file mode 100644 index 0000000000..ae6791964e --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_C_Landscape.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch C + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_C_Portrait.svg b/src/Mod/TechDraw/Templates/Arch_C_Portrait.svg new file mode 100644 index 0000000000..e0dd6cae46 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_C_Portrait.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch C + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_D_Landscape.svg b/src/Mod/TechDraw/Templates/Arch_D_Landscape.svg new file mode 100644 index 0000000000..d3660f8a4d --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_D_Landscape.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch D + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_D_Portrait.svg b/src/Mod/TechDraw/Templates/Arch_D_Portrait.svg new file mode 100644 index 0000000000..d4087adbb3 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_D_Portrait.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch D + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_E1_Landscape.svg b/src/Mod/TechDraw/Templates/Arch_E1_Landscape.svg new file mode 100644 index 0000000000..b9358b6ab0 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_E1_Landscape.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch E1 + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_E1_Portrait.svg b/src/Mod/TechDraw/Templates/Arch_E1_Portrait.svg new file mode 100644 index 0000000000..6e3967ae15 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_E1_Portrait.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch E1 + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_E2_Landscape.svg b/src/Mod/TechDraw/Templates/Arch_E2_Landscape.svg new file mode 100644 index 0000000000..4711cf77c9 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_E2_Landscape.svg @@ -0,0 +1,366 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch E2 + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_E2_Portrait.svg b/src/Mod/TechDraw/Templates/Arch_E2_Portrait.svg new file mode 100644 index 0000000000..dab40ec5f6 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_E2_Portrait.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch E2 + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_E3_Landscape.svg b/src/Mod/TechDraw/Templates/Arch_E3_Landscape.svg new file mode 100644 index 0000000000..71184d1dd9 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_E3_Landscape.svg @@ -0,0 +1,358 @@ + + + _________ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch E3 + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_E3_Portrait.svg b/src/Mod/TechDraw/Templates/Arch_E3_Portrait.svg new file mode 100644 index 0000000000..55af04f93e --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_E3_Portrait.svg @@ -0,0 +1,366 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch E3 + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Templates/Arch_E_Landscape.svg b/src/Mod/TechDraw/Templates/Arch_E_Landscape.svg new file mode 100644 index 0000000000..3e535aa440 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_E_Landscape.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch E + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_E_Portrait.svg b/src/Mod/TechDraw/Templates/Arch_E_Portrait.svg new file mode 100644 index 0000000000..93e7ffae38 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_E_Portrait.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch E + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + From d97a393709706a675e97d6650a2ae578cfc40b7f Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 24 Jan 2021 18:35:36 -0800 Subject: [PATCH 017/168] Make PathOp rebust(er) against manual Base assignment --- src/Mod/Path/PathScripts/PathOp.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathOp.py b/src/Mod/Path/PathScripts/PathOp.py index 8b2679a295..c0e24b03b4 100644 --- a/src/Mod/Path/PathScripts/PathOp.py +++ b/src/Mod/Path/PathScripts/PathOp.py @@ -422,7 +422,10 @@ class ObjectOp(object): zmax = max(zmax, bb.ZMax) for sub in sublist: try: - fbb = base.Shape.getElement(sub).BoundBox + if sub: + fbb = base.Shape.getElement(sub).BoundBox + else: + fbb = base.Shape.BoundBox zmin = max(zmin, faceZmin(bb, fbb)) zmax = max(zmax, fbb.ZMax) except Part.OCCError as e: From 31c9dbafb2f8c6eb7f208f8dd2309488be7b086b Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 24 Jan 2021 18:41:09 -0800 Subject: [PATCH 018/168] Add support for multiple models to vcarve op. --- src/Mod/Path/PathScripts/PathVcarve.py | 31 +++++++++++++------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index dd713923d6..f9c08cc729 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -242,7 +242,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): edges.append(_getPartEdge(e, geom)) return edges - def buildPathMedial(self, obj, Faces): + def buildPathMedial(self, obj, faces): '''constructs a medial axis path using openvoronoi''' def insert_many_wires(vd, wires): @@ -271,7 +271,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): VD.clear() voronoiWires = [] - for f in Faces: + for f in faces: vd = Path.Voronoi() insert_many_wires(vd, f.Wires) @@ -321,31 +321,30 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): "Engraver Cutting Edge Angle must be < 180 degrees.") + "\n") return try: + faces = [] if obj.Base: PathLog.track() for base in obj.Base: - faces = [] for sub in base[1]: shape = getattr(base[0].Shape, sub) if isinstance(shape, Part.Face): faces.append(shape) - modelshape = Part.makeCompound(faces) + else: + for model in self.model: + if model.isDerivedFrom('Sketcher::SketchObject') or model.isDerivedFrom('Part::Part2DObject'): + faces.extend(model.Shape.Faces) - elif len(self.model) == 1 and self.model[0].isDerivedFrom('Sketcher::SketchObject') or \ - self.model[0].isDerivedFrom('Part::Part2DObject'): - PathLog.track() - - modelshape = self.model[0].Shape - self.buildPathMedial(obj, modelshape.Faces) + if faces: + self.buildPathMedial(obj, faces) + else: + PathLog.error(translate('PathVcarve', 'The Job Base Object has no engraveable element. Engraving operation will produce no output.')) except Exception as e: - PathLog.error(e) - traceback.print_exc() - PathLog.error(translate('PathVcarve', 'The Job Base Object has \ -no engraveable element. Engraving \ -operation will produce no output.')) - raise e + #PathLog.error(e) + #traceback.print_exc() + PathLog.error(translate('PathVcarve', 'Error processing Base object. Engraving operation will produce no output.')) + #raise e def opUpdateDepths(self, obj, ignoreErrors=False): '''updateDepths(obj) ... engraving is always done at the top most z-value''' From d8797ba0cb971245ef20961f2b2f7b46f0e984f0 Mon Sep 17 00:00:00 2001 From: donovaly Date: Mon, 25 Jan 2021 04:29:45 +0100 Subject: [PATCH 019/168] [TD] show by default center marks as requested here: https://forum.freecadweb.org/viewtopic.php?f=35&t=54718#p470334 --- src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotation.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotation.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotation.ui index 105a38b0a7..8838d9990d 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotation.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotation.ui @@ -569,6 +569,9 @@ Show Center Marks + + true + ShowCenterMarks From f440fa9dbd8a7e08a279bef230dee9eb168a93dc Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 24 Jan 2021 22:35:27 -0600 Subject: [PATCH 020/168] Add the standard-defined "xml" namespace if it is not specified The XML standard stipulates: The prefix xml is by definition bound to the namespace name http://www.w3.org/XML/1998/namespace. It MAY, but need not, be declared, and MUST NOT be bound to any other namespace name. Other prefixes MUST NOT be bound to this namespace name, and it MUST NOT be declared as the default namespace. If the document does not explicitly include this namespace, it is added. This prevents errors due to the use of the namespace in some imported SVG files. In debug builds those errors emit warning messages, and in Windows debug builds those errors cause an abort() to be called. --- src/Mod/TechDraw/App/QDomNodeModel.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Mod/TechDraw/App/QDomNodeModel.cpp b/src/Mod/TechDraw/App/QDomNodeModel.cpp index 1ecfa48d04..25a704a174 100644 --- a/src/Mod/TechDraw/App/QDomNodeModel.cpp +++ b/src/Mod/TechDraw/App/QDomNodeModel.cpp @@ -195,9 +195,10 @@ QXmlName QDomNodeModel::name ( const QXmlNodeModelIndex & ni ) const return QXmlName(m_Pool, n.nodeName(), QString(), QString()); } -QVector QDomNodeModel::namespaceBindings(const QXmlNodeModelIndex & ni ) const +QVector QDomNodeModel::namespaceBindings(const QXmlNodeModelIndex & ni) const { QDomNode n = toDomNode(ni); + bool xmlNamespaceWasDefined = false; QVector res; while (!n.isNull()) @@ -219,14 +220,27 @@ QVector QDomNodeModel::namespaceBindings(const QXmlNodeModelIndex & ni for (x = 0; x < res.size(); ++x) if (res.at(x).prefix(m_Pool) == p) break; - if (x >= res.size()) + if (x >= res.size()) { res.append(QXmlName(m_Pool, QString::fromUtf8("xmlns"), attrs.item(i).nodeValue(), p)); - } + if (p == QString::fromLatin1("xml")) + xmlNamespaceWasDefined = true; + } + } } n = n.parentNode(); } + // Per the XML standard: + // "The prefix xml is by definition bound to the namespace name http://www.w3.org/XML/1998/namespace. It MAY, but + // need not, be declared, and MUST NOT be bound to any other namespace name. Other prefixes MUST NOT be bound to + // this namespace name, and it MUST NOT be declared as the default namespace." + // + // If the document does not specifically include this namespace, add it now: + if (!xmlNamespaceWasDefined) { + res.append(QXmlName(m_Pool, QString::fromUtf8("xmlns"), QString::fromLatin1("http://www.w3.org/XML/1998/namespace"), QString::fromLatin1("xml"))); + } + return res; } From c2fa48ba901dfb8e2616e50ccc808ff892f3ef37 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 24 Jan 2021 20:52:43 -0800 Subject: [PATCH 021/168] Disabled base geometry-list resizing since it makes the list unusable on HDP monitors. --- .../Resources/panels/PageBaseGeometryEdit.ui | 180 +++++++++--------- src/Mod/Path/PathScripts/PathOpGui.py | 23 ++- 2 files changed, 98 insertions(+), 105 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PageBaseGeometryEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageBaseGeometryEdit.ui index 3ea92feef0..42441cbc2c 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageBaseGeometryEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageBaseGeometryEdit.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 250 + 476 + 342
@@ -23,94 +23,28 @@ :/icons/Path_BaseGeometry.svg:/icons/Path_BaseGeometry.svg - - - - - - - Import - - - - - - - <html><head/><body><p>Remove the selected list items from the list of base geometries. The operation will not be applied to them.</p></body></html> - - - Remove - - - - - - - - 0 - 0 - - - - <html><head/><body><p>List of operations with Base Geometry in current Job.</p></body></html> - - - - - - - true - - - - 0 - 0 - - - - <html><head/><body><p>Select one or more features in the 3d view and press 'Add' to add them as the base items for this operation.</p><p><br/></p><p>Selected features can be deleted entirely.</p></body></html> - - - QAbstractItemView::ExtendedSelection - - - - - - - <html><head/><body><p>Clears list of base geometries.</p></body></html> - - - Clear - - - - - - - <html><head/><body><p>Add selected features to the list of base geometries for this operation.</p></body></html> - - - Add - - - - - - - All objects will be processed using the same operation properties - - - Qt::AlignCenter - - - true - - - - + + + + + <html><head/><body><p>Add selected features to the list of base geometries for this operation.</p></body></html> + + + Add + + - + + + + <html><head/><body><p>Remove the selected list items from the list of base geometries. The operation will not be applied to them.</p></body></html> + + + Remove + + + + Qt::Vertical @@ -123,14 +57,70 @@ + + + + <html><head/><body><p>Clears list of base geometries.</p></body></html> + + + Clear + + + + + + + All objects will be processed using the same operation properties + + + Qt::AlignCenter + + + true + + + + + + + true + + + + 0 + 0 + + + + <html><head/><body><p>Select one or more features in the 3d view and press 'Add' to add them as the base items for this operation.</p><p><br/></p><p>Selected features can be deleted entirely.</p></body></html> + + + QAbstractItemView::ExtendedSelection + + + + + + + Import + + + + + + + + 0 + 0 + + + + <html><head/><body><p>List of operations with Base Geometry in current Job.</p></body></html> + + +
- - baseList - addBase - deleteBase - clearBase - diff --git a/src/Mod/Path/PathScripts/PathOpGui.py b/src/Mod/Path/PathScripts/PathOpGui.py index 64d93f2966..87e5f9b1e6 100644 --- a/src/Mod/Path/PathScripts/PathOpGui.py +++ b/src/Mod/Path/PathScripts/PathOpGui.py @@ -42,13 +42,8 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Base classes and framework for Path operation's UI" -LOGLEVEL = False - -if LOGLEVEL: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) -else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) def translate(context, text, disambig=None): @@ -566,6 +561,7 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): return False def addBase(self): + PathLog.track() if self.addBaseGeometry(FreeCADGui.Selection.getSelectionEx()): # self.obj.Proxy.execute(self.obj) self.setFields(self.obj) @@ -636,11 +632,18 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): # Set base geometry list window to resize based on contents # Code reference: # https://stackoverflow.com/questions/6337589/qlistwidget-adjust-size-to-content + # ml: disabling this logic because I can't get it to work on HPD monitor. + # On my systems the values returned by the list object are also incorrect on + # creation, leading to a list object of size 15. count() always returns 0 until + # the list is actually displayed. The same is true for sizeHintForRow(0), which + # returns -1 until the widget is rendered. The widget claims to have a size of + # (100, 30), once it becomes visible the size is (535, 192). + # Leaving the framework here in case somebody figures out how to set this up + # properly. qList = self.form.baseList - # qList.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - col = qList.width() # 300 row = (qList.count() + qList.frameWidth()) * 15 - qList.setFixedSize(col, row) + #qList.setMinimumHeight(row) + PathLog.debug("baseList({}, {}) {} * {}".format(qList.size(), row, qList.count(), qList.sizeHintForRow(0))) class TaskPanelBaseLocationPage(TaskPanelPage): From 38b142be7acbe5b4625ecb3bcdc96a385495d32f Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 24 Jan 2021 21:09:35 -0800 Subject: [PATCH 022/168] Properly calling base logic for adding a base object if special handling did not apply. --- src/Mod/Path/PathScripts/PathVcarveGui.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathVcarveGui.py b/src/Mod/Path/PathScripts/PathVcarveGui.py index 5254a78a22..e77dab4b7f 100644 --- a/src/Mod/Path/PathScripts/PathVcarveGui.py +++ b/src/Mod/Path/PathScripts/PathVcarveGui.py @@ -53,6 +53,7 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): return super(TaskPanelBaseGeometryPage, self) def addBaseGeometry(self, selection): + PathLog.track(selection) added = False shapes = self.obj.BaseShapes for sel in selection: @@ -78,10 +79,12 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): shapes.append(base) self.obj.BaseShapes = shapes added = True - else: - # user wants us to engrave an edge of face of a base model - base = self.super().addBaseGeometry(selection) - added = added or base + + if not added: + # user wants us to engrave an edge of face of a base model + PathLog.info(" call default") + base = self.super().addBaseGeometry(selection) + added = added or base return added From 13a4f54ecd9bccbec5827e997aeb7a1ffe93fbd5 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 24 Jan 2021 21:10:39 -0800 Subject: [PATCH 023/168] Process BaseShapes in vcarve and only fall back to the model if nothing was found to engrave. --- src/Mod/Path/PathScripts/PathVcarve.py | 27 +++++++++++++------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index f9c08cc729..438661677a 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -311,26 +311,25 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): PathLog.track() if not hasattr(obj.ToolController.Tool, "CuttingEdgeAngle"): - FreeCAD.Console.PrintError( - translate("Path_Vcarve", "VCarve requires an engraving \ - cutter with CuttingEdgeAngle") + "\n") + PathLog.error(translate("Path_Vcarve", "VCarve requires an engraving cutter with CuttingEdgeAngle")) if obj.ToolController.Tool.CuttingEdgeAngle >= 180.0: - FreeCAD.Console.PrintError( - translate("Path_Vcarve", - "Engraver Cutting Edge Angle must be < 180 degrees.") + "\n") + PathLog.error(translate("Path_Vcarve", "Engraver Cutting Edge Angle must be < 180 degrees.")) return + try: faces = [] - if obj.Base: - PathLog.track() - for base in obj.Base: - for sub in base[1]: - shape = getattr(base[0].Shape, sub) - if isinstance(shape, Part.Face): - faces.append(shape) - else: + for base in obj.BaseShapes: + faces.extend(base.Shape.Faces) + + for base in obj.Base: + for sub in base[1]: + shape = getattr(base[0].Shape, sub) + if isinstance(shape, Part.Face): + faces.append(shape) + + if not faces: for model in self.model: if model.isDerivedFrom('Sketcher::SketchObject') or model.isDerivedFrom('Part::Part2DObject'): faces.extend(model.Shape.Faces) From ce625703b43f7101a5fb2a7d0e0c26c79f1f8393 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 24 Jan 2021 21:15:31 -0800 Subject: [PATCH 024/168] Removed unused BaseObjects property from vcarve --- src/Mod/Path/PathScripts/PathVcarve.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index 438661677a..22c974d108 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -210,11 +210,6 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): QtCore.QT_TRANSLATE_NOOP("PathVcarve", "Additional base objects to be engraved")) obj.setEditorMode('BaseShapes', 2) # hide - if not hasattr(obj, 'BaseObject'): - obj.addProperty("App::PropertyLink", "BaseObject", "Path", - QtCore.QT_TRANSLATE_NOOP("PathVcarve", - "Additional base objects to be engraved")) - obj.setEditorMode('BaseObject', 2) # hide def initOperation(self, obj): '''initOperation(obj) ... create vcarve specific properties.''' From 08297e5239e1405ed81a1a414d3fb61fcf42ed8f Mon Sep 17 00:00:00 2001 From: J-Dunn Date: Sun, 27 Dec 2020 08:59:07 +0000 Subject: [PATCH 025/168] Path: fix unnecessary copy on read for Command.parameters dict Contents of underlying c++ std::map data is copied to a new PyDict on every read. This is contrary to expected python behaviour which would normally just return the pointer to a python object and increment the reference counter for it. This was leading to massive redundant deletion and copying in all post processors which reference this variable in a nested loop. This PR adds a permanent dict member to the class and keeps track of changes to avoid unnecessary copying. --- src/Mod/Path/App/CommandPy.xml | 5 ++++- src/Mod/Path/App/CommandPyImp.cpp | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/App/CommandPy.xml b/src/Mod/Path/App/CommandPy.xml index 14e6df4109..1588b9dbe1 100644 --- a/src/Mod/Path/App/CommandPy.xml +++ b/src/Mod/Path/App/CommandPy.xml @@ -43,7 +43,7 @@ pairs, or a placement, or a vector - toGCode(): returns a GCode representation of the command + setFromGCode(): sets the path from the contents of the given GCode string @@ -51,5 +51,8 @@ pairs, or a placement, or a vector transform(Placement): returns a copy of this command transformed by the given placement + + mutable Py::Dict parameters_copy_dict; + diff --git a/src/Mod/Path/App/CommandPyImp.cpp b/src/Mod/Path/App/CommandPyImp.cpp index 98dc904264..b6344ba4c4 100644 --- a/src/Mod/Path/App/CommandPyImp.cpp +++ b/src/Mod/Path/App/CommandPyImp.cpp @@ -117,6 +117,7 @@ int CommandPy::PyInit(PyObject* args, PyObject* kwd) } getCommandPtr()->Parameters[ckey]=cvalue; } + parameters_copy_dict.clear(); return 0; } PyErr_Clear(); // set by PyArg_ParseTuple() @@ -157,11 +158,13 @@ void CommandPy::setName(Py::String arg) Py::Dict CommandPy::getParameters(void) const { - Py::Dict dict; - for(std::map::iterator i = getCommandPtr()->Parameters.begin(); i != getCommandPtr()->Parameters.end(); ++i) { - dict.setItem(i->first, Py::Float(i->second)); + // dict now a class member , https://forum.freecadweb.org/viewtopic.php?f=15&t=50583& + if (parameters_copy_dict.length()==0) { + for(std::map::iterator i = getCommandPtr()->Parameters.begin(); i != getCommandPtr()->Parameters.end(); ++i) { + parameters_copy_dict.setItem(i->first, Py::Float(i->second)); + } } - return dict; + return parameters_copy_dict; } void CommandPy::setParameters(Py::Dict arg) @@ -200,6 +203,7 @@ void CommandPy::setParameters(Py::Dict arg) throw Py::TypeError("The dictionary can only contain number values"); } getCommandPtr()->Parameters[ckey]=cvalue; + parameters_copy_dict.clear(); } } @@ -224,6 +228,7 @@ PyObject* CommandPy::setFromGCode(PyObject *args) std::string gcode(pstr); try { getCommandPtr()->setFromGCode(gcode); + parameters_copy_dict.clear(); } catch (const Base::Exception& e) { PyErr_SetString(PyExc_ValueError, e.what()); @@ -249,6 +254,7 @@ void CommandPy::setPlacement(Py::Object arg) Py::Type PlacementType(pyType.o); if(arg.isType(PlacementType)) { getCommandPtr()->setFromPlacement( *static_cast((*arg))->getPlacementPtr() ); + parameters_copy_dict.clear(); } else throw Py::TypeError("Argument must be a placement"); } @@ -259,6 +265,7 @@ PyObject* CommandPy::transform(PyObject *args) if ( PyArg_ParseTuple(args, "O!", &(Base::PlacementPy::Type), &placement) ) { Base::PlacementPy *p = static_cast(placement); Path::Command trCmd = getCommandPtr()->transform( *p->getPlacementPtr() ); + parameters_copy_dict.clear(); return new CommandPy(new Path::Command(trCmd)); } else throw Py::TypeError("Argument must be a placement"); @@ -302,6 +309,7 @@ int CommandPy::setCustomAttributes(const char* attr, PyObject* obj) return 0; } getCommandPtr()->Parameters[satt]=cvalue; + parameters_copy_dict.clear(); return 1; } } From 8cb513ba459f2f8d9fda69ccc0093e7ae0791424 Mon Sep 17 00:00:00 2001 From: J-Dunn Date: Mon, 4 Jan 2021 08:58:55 +0000 Subject: [PATCH 026/168] add comment about clearing Py::Dict when changing C++ data --- src/Mod/Path/App/CommandPyImp.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/App/CommandPyImp.cpp b/src/Mod/Path/App/CommandPyImp.cpp index b6344ba4c4..88bd30fce5 100644 --- a/src/Mod/Path/App/CommandPyImp.cpp +++ b/src/Mod/Path/App/CommandPyImp.cpp @@ -56,6 +56,11 @@ std::string CommandPy::representation(void) const return str.str(); } +// +// Py::Dict parameters_copy_dict is now a class member to avoid delete/create/copy on every read access from python code +// Now the pre-filled Py::Dict is returned which is more consistent with normal python behaviour. +// It should be cleared whenever the c++ Parameters object is changed eg setParameters() or other objects invalidate its content, eg setPlacement() +// https://forum.freecadweb.org/viewtopic.php?f=15&t=50583 PyObject *CommandPy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Python wrapper { @@ -158,7 +163,7 @@ void CommandPy::setName(Py::String arg) Py::Dict CommandPy::getParameters(void) const { - // dict now a class member , https://forum.freecadweb.org/viewtopic.php?f=15&t=50583& + // dict now a class member , https://forum.freecadweb.org/viewtopic.php?f=15&t=50583 if (parameters_copy_dict.length()==0) { for(std::map::iterator i = getCommandPtr()->Parameters.begin(); i != getCommandPtr()->Parameters.end(); ++i) { parameters_copy_dict.setItem(i->first, Py::Float(i->second)); From 8113794e75b65eac54900b440c19b1d21fecc993 Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 25 Jan 2021 15:00:16 +0100 Subject: [PATCH 027/168] Win32: [skip ci] disable logging again as regression of support of Plus XT model has been fixed --- src/Gui/3Dconnexion/GuiNativeEventWin32.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Gui/3Dconnexion/GuiNativeEventWin32.cpp b/src/Gui/3Dconnexion/GuiNativeEventWin32.cpp index ec519955fb..24726546be 100644 --- a/src/Gui/3Dconnexion/GuiNativeEventWin32.cpp +++ b/src/Gui/3Dconnexion/GuiNativeEventWin32.cpp @@ -71,8 +71,8 @@ Gui::GuiNativeEvent* Gui::GuiNativeEvent::gMouseInput = 0; #define _TRACE_RI_TYPE 0 #define _TRACE_RIDI_DEVICENAME 0 #define _TRACE_RIDI_DEVICEINFO 0 -#define _TRACE_RI_RAWDATA 1 -#define _TRACE_3DINPUT_PERIOD 1 +#define _TRACE_RI_RAWDATA 0 +#define _TRACE_3DINPUT_PERIOD 0 #ifdef _WIN64 typedef unsigned __int64 QWORD; From 74d5c0a88a74c733d0a10d6ee1f389c7b90fa549 Mon Sep 17 00:00:00 2001 From: Roy-043 <70520633+Roy-043@users.noreply.github.com> Date: Mon, 25 Jan 2021 15:33:36 +0100 Subject: [PATCH 028/168] [Draft] Fix snap to center of faces of solids The code would only find a center snap on the face with index=0 of solids. In V0.19 Part.getShape was introduced (line 399). But not all consequences were not fully implemented. In the '# we are snapping to an edge' section (line 411) the code could be cleaned up. There is no need to check if the index of the edge is correct for the parent object since we are no longer dealing with a parent object. That portion was effectively dead code. The '# we are snapping to a face' section (line 429 in the revised code) has been modified accordingly, which fixes the bug. Forum discussion: https://forum.freecadweb.org/viewtopic.php?f=23&t=54747 --- src/Mod/Draft/draftguitools/gui_snapper.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_snapper.py b/src/Mod/Draft/draftguitools/gui_snapper.py index 71692219ac..bd880e6030 100644 --- a/src/Mod/Draft/draftguitools/gui_snapper.py +++ b/src/Mod/Draft/draftguitools/gui_snapper.py @@ -410,14 +410,8 @@ class Snapper: if (not self.maxEdges) or (len(shape.Edges) <= self.maxEdges): if "Edge" in comp: # we are snapping to an edge - edge = None if shape.ShapeType == "Edge": edge = shape - else: - en = int(comp[4:])-1 - if len(shape.Edges) > en: - edge = shape.Edges[en] - if edge: snaps.extend(self.snapToEndpoints(edge)) snaps.extend(self.snapToMidpoint(edge)) snaps.extend(self.snapToPerpendicular(edge, lastpoint)) @@ -433,9 +427,9 @@ class Snapper: # extra ellipse options snaps.extend(self.snapToCenter(edge)) elif "Face" in comp: - en = int(comp[4:])-1 - if len(shape.Faces) > en: - face = shape.Faces[en] + # we are snapping to a face + if shape.ShapeType == "Face": + face = shape snaps.extend(self.snapToFace(face)) elif "Vertex" in comp: # directly snapped to a vertex From 69547e280016a41805cbeccc310f3a8028b9d592 Mon Sep 17 00:00:00 2001 From: bitacovir Date: Mon, 25 Jan 2021 16:23:54 -0300 Subject: [PATCH 029/168] [UI] Replace SVG icons for Surface WB commands --- src/Mod/Surface/Gui/Command.cpp | 4 +- src/Mod/Surface/Gui/Resources/Surface.qrc | 3 +- .../icons/Surface_BSplineSurface.svg | 2257 +------ .../Resources/icons/Surface_BezierSurface.svg | 2453 +------ .../Resources/icons/Surface_CurveOnMesh.svg | 2242 +------ .../Gui/Resources/icons/Surface_Cut.svg | 3795 +---------- .../Gui/Resources/icons/Surface_Extend.svg | 5767 ----------------- .../Resources/icons/Surface_ExtendFace.svg | 211 + .../Gui/Resources/icons/Surface_Filling.svg | 2013 +----- .../icons/Surface_GeomFillSurface.svg | 171 + .../Gui/Resources/icons/Surface_Sections.svg | 2988 +-------- .../Gui/Resources/icons/Surface_Sewing.svg | 1953 +----- .../Gui/Resources/icons/Surface_Surface.svg | 1810 +----- .../Gui/Resources/icons/Surface_Workbench.svg | 1826 +----- 14 files changed, 1408 insertions(+), 26085 deletions(-) delete mode 100644 src/Mod/Surface/Gui/Resources/icons/Surface_Extend.svg create mode 100644 src/Mod/Surface/Gui/Resources/icons/Surface_ExtendFace.svg create mode 100644 src/Mod/Surface/Gui/Resources/icons/Surface_GeomFillSurface.svg diff --git a/src/Mod/Surface/Gui/Command.cpp b/src/Mod/Surface/Gui/Command.cpp index abc31b9b23..705a6a822f 100644 --- a/src/Mod/Surface/Gui/Command.cpp +++ b/src/Mod/Surface/Gui/Command.cpp @@ -171,7 +171,7 @@ CmdSurfaceGeomFillSurface::CmdSurfaceGeomFillSurface() sToolTipText = QT_TR_NOOP("Creates a surface from two, three or four boundary edges."); sWhatsThis = "Surface_GeomFillSurface"; sStatusTip = sToolTipText; - sPixmap = "Surface_BSplineSurface"; + sPixmap = "Surface_GeomFillSurface"; } bool CmdSurfaceGeomFillSurface::isActive(void) @@ -237,7 +237,7 @@ CmdSurfaceExtendFace::CmdSurfaceExtendFace() "with its local U and V parameters."); sWhatsThis = "Surface_ExtendFace"; sStatusTip = sToolTipText; - sPixmap = "Surface_Extend"; + sPixmap = "Surface_ExtendFace"; } void CmdSurfaceExtendFace::activated(int) diff --git a/src/Mod/Surface/Gui/Resources/Surface.qrc b/src/Mod/Surface/Gui/Resources/Surface.qrc index c09283aa91..7af306fa62 100644 --- a/src/Mod/Surface/Gui/Resources/Surface.qrc +++ b/src/Mod/Surface/Gui/Resources/Surface.qrc @@ -4,8 +4,9 @@ icons/Surface_BSplineSurface.svg icons/Surface_CurveOnMesh.svg icons/Surface_Cut.svg - icons/Surface_Extend.svg + icons/Surface_ExtendFace.svg icons/Surface_Filling.svg + icons/Surface_GeomFillSurface.svg icons/Surface_Sections.svg icons/Surface_Sewing.svg icons/Surface_Surface.svg diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_BSplineSurface.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_BSplineSurface.svg index 3e9b34bee4..a2644e009c 100644 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_BSplineSurface.svg +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_BSplineSurface.svg @@ -7,2106 +7,111 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" - id="svg3364" + id="svg2985" height="64px" width="64px"> Surface_BSplineSurface + id="title889">Surface_BSplineSurface + id="defs2987"> + id="linearGradient4387"> - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + id="radialGradient3692" + xlink:href="#linearGradient3377" /> + id="linearGradient3377"> + id="stop3379" /> + id="stop3381" /> + id="linearGradient3377-3"> + id="stop3379-8" /> + id="stop3381-3" /> + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + gradientTransform="matrix(0.67067175,0,0,0.64145918,-63.380792,0.83845403)" + gradientUnits="userSpaceOnUse" + id="radialGradient6412" + xlink:href="#linearGradient3377-3" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient3036"> + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + id="metadata2990"> @@ -2114,127 +119,75 @@ Surface_BSplineSurface - 2017-04-17 - Nate Miller + [bitacovir] - - - CC-BY-SA 4.0 - - + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork FreeCAD - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_BSplineSurface.svg - http://www.freecadweb.org/wiki/index.php?title=Artwork - A purple curved surface that has a thick, red, highlighted edge. This edge has three vertices indicated as circles as if it was a multipoint spline. It is based on the 'Surface' icon. + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html - [vocx] + - surface - curve - spline - points + - - - - - - - - - + d="M 13.816245,52.162367 C 28.338105,17.330258 44.796216,62.805512 56.413699,28.940961" + style="fill:none;stroke:#fce94f;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - - - - - - - - - - - - + id="path3820-1-9-6" + d="M 5.4725393,24.503343 13.365603,45.716252 C 30.88904,17.984227 44.167745,56.981787 53.473496,28.274182 L 39.51487,7.9294358 C 31.707324,28.541126 16.157527,5.2639846 5.4725393,24.503343 Z" + style="display:inline;fill:none;fill-opacity:1;stroke:#729fcf;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + + + diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_BezierSurface.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_BezierSurface.svg index d6abc7b517..d5179839bc 100644 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_BezierSurface.svg +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_BezierSurface.svg @@ -6,2307 +6,112 @@ xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" - width="64px" + version="1.1" + id="svg2985" height="64px" - id="svg3364" - version="1.1"> + width="64px"> Surface_BezierSurface + id="title889">Surface_BezierSurface + id="defs2987"> + id="linearGradient4387"> - - - - - - + + + + - + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + id="radialGradient3692" + xlink:href="#linearGradient3377" /> + id="linearGradient3377"> + id="stop3379" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="stop3381" /> + id="linearGradient3377-3"> + id="stop3379-8" /> + id="stop3381-3" /> + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + gradientTransform="matrix(0.67067175,0,0,0.64145918,-63.380792,0.83845403)" + gradientUnits="userSpaceOnUse" + id="radialGradient6412" + xlink:href="#linearGradient3377-3" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient3036"> + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + id="metadata2990"> @@ -2314,109 +119,91 @@ Surface_BezierSurface - - 2017-04-17 - Nate Miller + [bitacovir] - - - CC-BY-SA 4.0 - - + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork FreeCAD - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_BezierSurface.svg - http://www.freecadweb.org/wiki/index.php?title=Artwork - A purple curved surface, with one thick, red highlighted edge. The endpoints of this edge are marked with squares, which are tied to handles indicating control points for a bezier curve. It is based on the 'Surface' icon. + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html - [vocx] + surface - curve - bezier - handles + - - - - - - - - - + style="display:inline;fill:url(#linearGradient2095);fill-opacity:1;stroke:#0b1521;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 3.1519208,24.413282 13.801284,52.472481 C 28.323144,17.640372 44.781255,63.115626 56.398738,29.251075 L 38.972508,3.1269928 C 30.809307,30.407148 17.749067,-0.16084869 3.1519208,24.413282 Z" + id="path3820-1-9" /> + style="fill:none;stroke:#172a04;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 13.801284,52.472481 C 28.323144,17.640372 44.781255,63.115626 56.398738,29.251075" + id="path3764" /> + id="path1219" + d="M 13.816245,52.162367 C 28.338105,17.330258 44.796216,62.805512 56.413699,28.940961" + style="fill:none;stroke:#fce94f;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + id="path3867" + d="M 48.620445,57.157022 55.854309,32.153751" + style="fill:#ce5c00;stroke:#ce5c00;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + id="path3820-1-9-6" + d="M 5.4725393,24.503343 13.365603,45.716252 C 31.320659,18.292526 44.167745,56.981787 53.473496,28.274182 L 39.51487,7.9294358 C 31.707324,28.541126 16.157527,5.2639846 5.4725393,24.503343 Z" + style="display:inline;fill:none;fill-opacity:1;stroke:#729fcf;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + + style="fill:#fcaf3e;fill-opacity:1;stroke:#ce5c00;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + style="fill:#8ae234;fill-opacity:1;fill-rule:nonzero;stroke:#172a04;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + style="fill:#8ae234;fill-opacity:1;fill-rule:nonzero;stroke:#172a04;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + style="fill:#fcaf3e;fill-opacity:1;stroke:#ce5c00;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_CurveOnMesh.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_CurveOnMesh.svg index c6bf9bf8ba..d2611f592f 100644 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_CurveOnMesh.svg +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_CurveOnMesh.svg @@ -7,2106 +7,111 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" - id="svg3364" + id="svg2985" height="64px" width="64px"> Surface_CurveOnMesh + id="title866">Surface_CurveOnMesh + id="defs2987"> + id="linearGradient4387"> - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + id="radialGradient3692" + xlink:href="#linearGradient3377" /> + id="linearGradient3377"> + id="stop3379" /> + id="stop3381" /> + id="linearGradient3377-3"> + id="stop3379-8" /> + id="stop3381-3" /> + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + gradientTransform="matrix(0.67067175,0,0,0.64145918,-63.380792,0.83845403)" + gradientUnits="userSpaceOnUse" + id="radialGradient6412" + xlink:href="#linearGradient3377-3" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient3036"> + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + id="metadata2990"> @@ -2114,95 +119,86 @@ Surface_CurveOnMesh - 2020-09-30 - [vocx] + [bitacovir] - - - CC-BY-SA 4.0 - - + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork FreeCAD - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_CurveOnMesh.svg - http://www.freecadweb.org/wiki/index.php?title=Artwork - A purple curved surface that has a thick, red, highlighted curve on top of, it in the middle of the shape. The surface has mesh lines. It is based on the 'Surface' icon. + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html - - mesh - curve - spline - middle + surface + - - - - - - - - - + d="M 3.1519208,24.413282 17.899691,60.320494 c 14.52186,-34.832109 31.154371,7.93994 42.771854,-25.924611 L 38.972508,3.1269928 C 30.809307,30.407148 17.749067,-0.16084869 3.1519208,24.413282 Z" + style="display:inline;fill:url(#linearGradient2095);fill-opacity:1;stroke:#172a04;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + id="path864" + d="M 13.603223,48.657681 Z" + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="display:inline;fill:none;fill-opacity:1;stroke:#8ae234;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 5.3492197,24.626663 18.113409,55.643482 C 32.677175,26.678261 49.470489,63.641047 58.406281,34.501823 L 39.63819,7.4361573 C 30.967406,29.589343 16.774125,4.5857267 5.3492197,24.626663 Z" + id="path3820-1-9-6" /> + style="fill:none;stroke:#4e9a06;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 14.551524,40.360731 c 4.206197,-6.239691 10.886783,-4.698556 12.49142,-4.817808" + id="path891" /> + id="path859" + d="M 9.0688148,17.178428 24.67764,49.965681" + style="fill:#0b1521;stroke:#172a04;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - - - - + id="path861" + d="M 22.497637,14.213624 40.286466,47.088077" + style="fill:none;stroke:#172a04;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + + + + + + diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_Cut.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_Cut.svg index cee2cf4fce..8a16b39b90 100644 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_Cut.svg +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_Cut.svg @@ -7,3683 +7,111 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" - id="svg3364" + id="svg2985" height="64px" width="64px"> Surface_Cut + id="title889">Surface_Cut + id="defs2987"> + id="linearGradient4387"> - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + id="radialGradient3692" + xlink:href="#linearGradient3377" /> + id="linearGradient3377"> + id="stop3379" /> + id="stop3381" /> + id="linearGradient3377-3"> + id="stop3379-8" /> + id="stop3381-3" /> + id="radialGradient6412" + xlink:href="#linearGradient3377-3" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient3036"> + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + x1="47" + y1="9" + x2="7" + y2="28" + gradientUnits="userSpaceOnUse" /> + + + + + id="metadata2990"> @@ -3691,86 +119,69 @@ Surface_Cut - - 2017-04-17 - Nate Miller + [bitacovir] - - - CC-BY-SA 4.0 - - + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork FreeCAD - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_Cut.svg - http://www.freecadweb.org/wiki/index.php?title=Artwork - A purple curved surface. A semi-transparent plane cuts the curve in two. The position of the cut is marked with a red thick edge on the curve. It is based on the 'Surface' icon. + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html - [vocx] + surface - plane - cut + - - - - - - - - + style="display:inline;fill:url(#linearGradient2095);fill-opacity:1;stroke:#0b1521;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 3.1519208,24.413282 17.899691,60.320494 c 14.52186,-34.832109 31.154371,7.93994 42.771854,-25.924611 L 38.972508,3.1269928 C 30.809307,30.407148 17.749067,-0.16084869 3.1519208,24.413282 Z" + id="path3820-1-9" /> + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 13.603223,48.657681 Z" + id="path864" /> + id="path3820-1-9-6" + d="M 5.3492197,24.626663 18.113409,55.643482 C 32.677175,26.678261 49.470489,63.641047 58.406281,34.501823 L 39.63819,7.4361573 C 30.967406,29.589343 16.774125,4.5857267 5.3492197,24.626663 Z" + style="display:inline;fill:none;fill-opacity:1;stroke:#729fcf;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + style="opacity:0.7175;fill:#ef2929;stroke:#280000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 57.281971,22.709091 57.096986,3.6995887 3.097977,11.384814 2.9746572,44.456724 9.7698774,41.423451 C 17.231743,18.328355 39.666029,41.309915 50.005085,20.047261 l 3.975198,3.989905 z" + id="path866" /> + id="path866-8" + d="M 57.281971,22.709091 57.096992,3.6995881 3.097977,11.384814 2.9746572,44.456724 9.7698772,41.423451 C 17.223185,18.328171 39.853014,41.194251 50.390456,19.754377 l 3.296943,4.452353 z" + style="opacity:1;fill:none;stroke:#280000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + id="path857-1" + d="M 50.227282,19.009631 C 40.095257,43.882552 19.635873,17.890993 9.5920156,41.158467" + style="fill:none;stroke:#0b1521;stroke-width:4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + style="fill:none;stroke:#fce94f;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 50.140081,19.053231 C 40.008056,43.926152 19.548672,17.934593 9.5048155,41.202067" + id="path857" /> diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_Extend.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_Extend.svg deleted file mode 100644 index fb03b99117..0000000000 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_Extend.svg +++ /dev/null @@ -1,5767 +0,0 @@ - - - Surface_Extend - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - Surface_Extend - 2020-09-30 - - - [vocx] - - - - - CC-BY-SA 4.0 - - - - - FreeCAD - - - http://www.freecadweb.org/wiki/index.php?title=Artwork - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_Extend.svg - A purple curved surface that contains a smaller surface. The smaller surface has Its four edges in thick, red highlighted. Gray lines extend from the smaller, inner surface to the exterior one. It is based on the 'Surface' icon. - - - - - - - - - surface - curve - extrapolation - highlights - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_ExtendFace.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_ExtendFace.svg new file mode 100644 index 0000000000..d7af2c7f2d --- /dev/null +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_ExtendFace.svg @@ -0,0 +1,211 @@ + + + Surface_ExtendFace + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Surface_ExtendFace + + + [bitacovir] + + + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + + + + + + surface + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_Filling.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_Filling.svg index 791cf3cff3..5daf7464f8 100644 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_Filling.svg +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_Filling.svg @@ -6,1795 +6,89 @@ xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" - version="1.1" - id="svg3364" + width="64px" height="64px" - width="64px"> + id="svg2985" + version="1.1"> Surface_Filling + id="title889">Surface_Filling + id="defs2987"> - - - - - - + id="linearGradient4387"> + id="stop4389" /> + id="stop4391" /> - - - - - - - - - - - - - - - - - + id="linearGradient6321"> + id="stop6323" /> - + id="stop6325" /> - - - - - - - - - + gradientTransform="translate(-0.23443224,0.23443198)" /> + id="linearGradient3377"> + style="stop-color:#faff2b;stop-opacity:1;" /> + style="stop-color:#ffaa00;stop-opacity:1;" /> + id="linearGradient3377-3"> + style="stop-color:#faff2b;stop-opacity:1;" /> + style="stop-color:#ffaa00;stop-opacity:1;" /> + gradientTransform="matrix(0.67067175,0,0,0.64145918,-63.380792,0.83845403)" + cx="45.883327" + cy="28.869568" + fx="45.883327" + fy="28.869568" + r="19.467436" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient3036"> + + + - - - - - - - - - - + xlink:href="#linearGradient1189" + gradientTransform="matrix(0.96812402,0,0,0.96755864,-0.72057496,-2.6783592)" /> + + + + + id="metadata2990"> @@ -1913,100 +119,61 @@ Surface_Filling - 2017-04-17 - Nate Miller + [bitacovir] - - - CC-BY-SA 4.0 - - + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork FreeCAD - http://www.freecadweb.org/wiki/index.php?title=Artwork - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_Filling.svg - A purple curved surface. Its four edges are thick and highlighted in red. It is based on the 'Surface' icon. + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html - [vocx] + - surface - curve - edges - highlights + - - - - - - - - + id="path3820-1-9" + d="M 3.1519208,24.413282 17.899691,60.320494 c 14.52186,-34.832109 31.154371,7.93994 42.771854,-25.924611 L 38.972508,3.1269928 C 30.809307,30.407148 17.749067,-0.16084869 3.1519208,24.413282 Z" + style="display:inline;fill:url(#linearGradient2095);fill-opacity:1;stroke:#302b00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + style="display:inline;fill:none;fill-opacity:1;stroke:#fce94f;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 6.0274776,24.811642 18.175069,54.286966 C 33.540412,25.445065 49.03887,62.222871 57.666363,34.995102 L 39.76151,8.7926732 C 30.227488,30.267601 17.452383,4.7707061 6.0274776,24.811642 Z" + id="path3820-1-9-6" /> + style="fill:none;stroke:#302b00;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 8.3549048,24.972224 18.621264,49.266191 C 31.716699,27.213603 50.17526,55.741537 55.247193,35.269413 L 40.202199,12.856071 C 30.336022,29.759966 16.753608,10.080699 8.3549048,24.972224 Z" + id="path857" /> + style="fill:none;stroke:#0b1521;stroke-width:6;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 50.052882,19.009632 C 39.920857,43.882553 19.461473,17.890994 9.4176161,41.158468" + id="path857-1" /> - - - - - + id="path857-3" + d="M 49.965681,19.053232 C 39.833656,43.926153 19.374272,17.934594 9.3304161,41.202068" + style="fill:none;stroke:#fce94f;stroke-width:4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_GeomFillSurface.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_GeomFillSurface.svg new file mode 100644 index 0000000000..2c6e085a7b --- /dev/null +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_GeomFillSurface.svg @@ -0,0 +1,171 @@ + + + Surface_GeomFillSurface + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Surface_GeomFillSurface + + + [bitacovir] + + + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + + + + + + surface + + + + + + + + + + + + diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_Sections.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_Sections.svg index 95c5529f4a..f84f5b73fe 100644 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_Sections.svg +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_Sections.svg @@ -6,2754 +6,150 @@ xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" - width="64px" + version="1.1" + id="svg2985" height="64px" - id="svg3364" - version="1.1"> + width="64px"> Surface_Sections + id="title889">Surface_Sections + id="defs2987"> + id="linearGradient873"> + id="stop869" /> - - + id="stop871" /> + id="linearGradient863"> + + + + + + + + - + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + id="radialGradient3692" + xlink:href="#linearGradient3377" /> + id="linearGradient3377"> + id="stop3379" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="stop3381" /> + id="linearGradient3377-3"> + id="stop3379-8" /> + id="stop3381-3" /> + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + gradientTransform="matrix(0.67067175,0,0,0.64145918,-63.380792,0.83845403)" + gradientUnits="userSpaceOnUse" + id="radialGradient6412" + xlink:href="#linearGradient3377-3" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient3036"> + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + x1="47" + y1="9" + x2="7" + y2="28" + gradientUnits="userSpaceOnUse" /> + + + + + + + id="metadata2990"> @@ -2761,139 +157,73 @@ Surface_Sections - - 2020-09-29 - [vocx] + [bitacovir] - - - CC-BY-SA 4.0 - - + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork FreeCAD - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_Sections.svg - http://www.freecadweb.org/wiki/index.php?title=Artwork + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + + + surface - curve - sections - edges - A purple surface made with several highlighted red edges which represent transversal sections of the surface. + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_Sewing.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_Sewing.svg index cf8e3b2e59..7411c1dcc6 100644 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_Sewing.svg +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_Sewing.svg @@ -7,1806 +7,111 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" - id="svg3364" + id="svg2985" height="64px" width="64px"> Surface_Sewing + id="title889">Surface_Sewing + id="defs2987"> + id="linearGradient4387"> - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + id="radialGradient3692" + xlink:href="#linearGradient3377" /> + id="linearGradient3377"> + id="stop3379" /> + id="stop3381" /> + id="linearGradient3377-3"> + id="stop3379-8" /> + id="stop3381-3" /> + id="radialGradient6412" + xlink:href="#linearGradient3377-3" /> + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient1189"> + + + + id="metadata2990"> @@ -1814,99 +119,73 @@ Surface_Sewing - - 2017-04-17 - Nate Miller + [bitacovir] - - - CC-BY-SA 4.0 - - + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork FreeCAD - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_Sewing.svg - http://www.freecadweb.org/wiki/index.php?title=Artwork - A purple curved surface, with black edges, and one edge passing through the middle, dividing the curve in two parts. Six straight lines cross the middle edge, indicating stitches. It is based on the 'Surface' icon. + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html - [vocx] + surface - curve - edges + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_Surface.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_Surface.svg index a0f51cfc3f..b3217de204 100644 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_Surface.svg +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_Surface.svg @@ -7,1697 +7,111 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" - id="svg3364" + id="svg2985" height="64px" width="64px"> Surface_Surface + id="title889">Surface_Surface + id="defs2987"> + id="linearGradient4387"> - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + id="linearGradient3377"> + id="stop3379" /> + id="stop3381" /> + id="linearGradient3377-3"> + id="stop3379-8" /> + id="stop3381-3" /> + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient1189"> + + + + id="metadata2990"> @@ -1705,75 +119,53 @@ Surface_Surface - 2017-04-17 - Nate Miller + [bitacovir] - - - CC-BY-SA 4.0 - - + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork FreeCAD - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_Surface.svg - http://www.freecadweb.org/wiki/index.php?title=Artwork - A purple curved surface, with a linear gradient, and some highlights in light and dark purple. - + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html - [vocx] + surface - curve - wave + - - - - - - - - - - - - - + + + + + diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_Workbench.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_Workbench.svg index 35053ff75c..44e6791ec6 100644 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_Workbench.svg +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_Workbench.svg @@ -6,1698 +6,112 @@ xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" - version="1.1" - id="svg3364" + width="64px" height="64px" - width="64px"> + id="svg2985" + version="1.1"> Surface_Workbench + id="title889">Surface_Workbench + id="defs2987"> - - - - - - + id="linearGradient4387"> + id="stop4389" /> + id="stop4391" /> - - - - - - - - - - - - - - - - - + id="linearGradient6321"> + id="stop6323" /> - - - - - - - - - + id="stop6325" /> + xlink:href="#linearGradient3377" + id="radialGradient3692" + cx="45.883327" + cy="28.869568" + fx="45.883327" + fy="28.869568" + r="19.467436" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-0.23443224,0.23443198)" /> + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + x2="7" + y1="9" + x1="47" + id="linearGradient2095" + xlink:href="#linearGradient1189" + gradientTransform="matrix(0.96812402,0,0,0.96755864,-0.72057496,-2.6783592)" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient1189"> + + + + id="metadata2990"> @@ -1705,75 +119,53 @@ Surface_Workbench - 2017-04-17 - Nate Miller + [bitacovir] - - - CC-BY-SA 4.0 - - + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork FreeCAD - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_Workbench.svg - http://www.freecadweb.org/wiki/index.php?title=Artwork - A purple curved surface, with a linear gradient, and some highlights in light and dark purple. It is based on the 'Surface' icon. - + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html - [vocx] + surface - curve - wave + - - - - - - - - - - - - - + + + + + From 2f731b7da7b58cb320f977498feb03a91fa4bb24 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Tue, 26 Jan 2021 09:35:30 +0100 Subject: [PATCH 030/168] Sketcher: Show partial redundant messages when sketch is fully constraint --- src/Mod/Sketcher/Gui/ViewProviderSketch.cpp | 32 ++++++++++----------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index 95b5b458de..4e90b0007b 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -6493,6 +6493,16 @@ void ViewProviderSketch::UpdateSolverInformation() .arg(tr("(click to select)")) .arg(appendRedundantMsg(getSketchObject()->getLastRedundant()))); } + + QString partiallyRedundantString; + + if(hasPartiallyRedundant) { + partiallyRedundantString = QString::fromLatin1("
%1%2
%3

") + .arg(tr("Sketch contains partially redundant constraints ")) + .arg(tr("(click to select)")) + .arg(appendPartiallyRedundantMsg(getSketchObject()->getLastPartiallyRedundant())); + } + if (getSketchObject()->getLastSolverStatus() == 0) { if (dofs == 0) { // color the sketch as fully constrained if it has geometry (other than the axes) @@ -6500,31 +6510,19 @@ void ViewProviderSketch::UpdateSolverInformation() edit->FullyConstrained = true; if (!hasRedundancies) { - signalSetUp(QString::fromLatin1("%1").arg(tr("Fully constrained sketch"))); + signalSetUp(QString::fromLatin1("%1 %2").arg(tr("Fully constrained sketch")).arg(partiallyRedundantString)); } } else if (!hasRedundancies) { QString infoString; if (dofs == 1) - infoString = tr("Under-constrained sketch with 1 degree of freedom. %1") - .arg(hasPartiallyRedundant? - QString::fromLatin1("
%1%2
%3

") - .arg(tr("Sketch contains partially redundant constraints ")) - .arg(tr("(click to select)")) - .arg(appendPartiallyRedundantMsg(getSketchObject()->getLastPartiallyRedundant())) - : QString()); + signalSetUp(tr("Under-constrained sketch with 1 degree of freedom. %1") + .arg(partiallyRedundantString)); else - infoString = tr("Under-constrained sketch with %1 degrees of freedom. %2") + signalSetUp(tr("Under-constrained sketch with %1 degrees of freedom. %2") .arg(dofs) - .arg(hasPartiallyRedundant? - QString::fromLatin1("
%1%2
%3

") - .arg(tr("Sketch contains partially redundant constraints ")) - .arg(tr("(click to select)")) - .arg(appendPartiallyRedundantMsg(getSketchObject()->getLastPartiallyRedundant())) - : QString()); - - signalSetUp(infoString); + .arg(partiallyRedundantString)); } signalSolved(QString::fromLatin1("%1").arg(tr("Solved in %1 sec").arg(getSketchObject()->getLastSolveTime()))); From dd3a4be81d0fc7b04bd7f199b5d1f1f07f300d30 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 7 Dec 2020 21:00:43 -0800 Subject: [PATCH 031/168] Basic property container with editor, no adding of properties yet --- src/Mod/Path/CMakeLists.txt | 4 + src/Mod/Path/Gui/Resources/Path.qrc | 1 + .../Gui/Resources/panels/PropertyContainer.ui | 57 +++++ src/Mod/Path/PathScripts/PathGuiInit.py | 1 + src/Mod/Path/PathScripts/PathProperty.py | 201 +++++++++++++++ .../Path/PathScripts/PathPropertyContainer.py | 89 +++++++ .../PathScripts/PathPropertyContainerGui.py | 235 ++++++++++++++++++ .../Path/PathScripts/PathPropertyEditor.py | 219 ++++++++++++++++ 8 files changed, 807 insertions(+) create mode 100644 src/Mod/Path/Gui/Resources/panels/PropertyContainer.ui create mode 100644 src/Mod/Path/PathScripts/PathProperty.py create mode 100644 src/Mod/Path/PathScripts/PathPropertyContainer.py create mode 100644 src/Mod/Path/PathScripts/PathPropertyContainerGui.py create mode 100644 src/Mod/Path/PathScripts/PathPropertyEditor.py diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 444be1ecd4..28bc67fa96 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -96,6 +96,10 @@ SET(PathScripts_SRCS PathScripts/PathProfileFaces.py PathScripts/PathProfileFacesGui.py PathScripts/PathProfileGui.py + PathScripts/PathProperty.py + PathScripts/PathPropertyContainer.py + PathScripts/PathPropertyContainerGui.py + PathScripts/PathPropertyEditor.py PathScripts/PathSanity.py PathScripts/PathSelection.py PathScripts/PathSetupSheet.py diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index c99246c667..a8d0ab5d21 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -121,6 +121,7 @@ panels/PageOpVcarveEdit.ui panels/PathEdit.ui panels/PointEdit.ui + panels/PropertyContainer.ui panels/SetupGlobal.ui panels/SetupOp.ui panels/ToolBitEditor.ui diff --git a/src/Mod/Path/Gui/Resources/panels/PropertyContainer.ui b/src/Mod/Path/Gui/Resources/panels/PropertyContainer.ui new file mode 100644 index 0000000000..21df9f9cb5 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/PropertyContainer.ui @@ -0,0 +1,57 @@ + + + Form + + + + 0 + 0 + 552 + 651 + + + + Form + + + + + + QAbstractItemView::AllEditTriggers + + + true + + + true + + + false + + + + + + + + + + Remove + + + + + + + Add + + + + + + + + + + + diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index cd48d41309..29f285d59a 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -66,6 +66,7 @@ def Startup(): # from PathScripts import PathProfileEdgesGui # from PathScripts import PathProfileFacesGui from PathScripts import PathProfileGui + from PathScripts import PathPropertyContainerGui from PathScripts import PathSanity from PathScripts import PathSetupSheetGui from PathScripts import PathSimpleCopy diff --git a/src/Mod/Path/PathScripts/PathProperty.py b/src/Mod/Path/PathScripts/PathProperty.py new file mode 100644 index 0000000000..7e3eeb1e6f --- /dev/null +++ b/src/Mod/Path/PathScripts/PathProperty.py @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2020 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 PathScripts.PathLog as PathLog + +__title__ = "Property type abstraction for editing purposes" +__author__ = "sliptonic (Brad Collette)" +__url__ = "https://www.freecadweb.org" +__doc__ = "Prototype objects to allow extraction of setup sheet values and editing." + + +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +#PathLog.trackModule(PathLog.thisModule()) + +class Property(object): + '''Base class for all prototype properties''' + def __init__(self, name, propType, category, info): + self.name = name + self.propType = propType + self.category = category + self.info = info + self.editorMode = 0 + self.value = None + + def setValue(self, value): + self.value = value + def getValue(self): + return self.value + + def setEditorMode(self, mode): + self.editorMode = mode + + def displayString(self): + if self.value is None: + t = self.typeString() + p = 'an' if t[0] in ['A', 'E', 'I', 'O', 'U'] else 'a' + return "%s %s" % (p, t) + return self.value + + def typeString(self): + return "Property" + + def setupProperty(self, obj, name, category, value): + created = False + if not hasattr(obj, name): + obj.addProperty(self.propType, name, category, self.info) + self.initProperty(obj, name) + created = True + setattr(obj, name, value) + return created + + def initProperty(self, obj, name): + pass + + def setValueFromString(self, string): + self.setValue(self.valueFromString(string)) + + def valueFromString(self, string): + return string + +class PropertyEnumeration(Property): + def typeString(self): + return "Enumeration" + + def setValue(self, value): + if list == type(value): + self.enums = value # pylint: disable=attribute-defined-outside-init + else: + super(PropertyEnumeration, self).setValue(value) + + def getEnumValues(self): + return self.enums + + def initProperty(self, obj, name): + setattr(obj, name, self.enums) + +class PropertyQuantity(Property): + def displayString(self): + if self.value is None: + return Property.displayString(self) + return self.value.getUserPreferred()[0] + +class PropertyAngle(PropertyQuantity): + def typeString(self): + return "Angle" + +class PropertyDistance(PropertyQuantity): + def typeString(self): + return "Distance" + +class PropertyLength(PropertyQuantity): + def typeString(self): + return "Length" + +class PropertyPercent(Property): + def typeString(self): + return "Percent" + +class PropertyFloat(Property): + def typeString(self): + return "Float" + + def valueFromString(self, string): + return float(string) + +class PropertyInteger(Property): + def typeString(self): + return "Integer" + + def valueFromString(self, string): + return int(string) + +class PropertyBool(Property): + def typeString(self): + return "Bool" + + def valueFromString(self, string): + return bool(string) + +class PropertyString(Property): + def typeString(self): + return "String" + +class PropertyMap(Property): + def typeString(self): + return "Map" + + def displayString(self, value): + return str(value) + +class OpPrototype(object): + + PropertyType = { + 'App::PropertyAngle': PropertyAngle, + 'App::PropertyBool': PropertyBool, + 'App::PropertyDistance': PropertyDistance, + 'App::PropertyEnumeration': PropertyEnumeration, + 'App::PropertyFile': PropertyString, + 'App::PropertyFloat': PropertyFloat, + 'App::PropertyFloatConstraint': Property, + 'App::PropertyFloatList': Property, + 'App::PropertyInteger': PropertyInteger, + 'App::PropertyIntegerList': PropertyInteger, + 'App::PropertyLength': PropertyLength, + 'App::PropertyLink': Property, + 'App::PropertyLinkList': Property, + 'App::PropertyLinkSubListGlobal': Property, + 'App::PropertyMap': PropertyMap, + 'App::PropertyPercent': PropertyPercent, + 'App::PropertyString': PropertyString, + 'App::PropertyStringList': Property, + 'App::PropertyVectorDistance': Property, + 'App::PropertyVectorList': Property, + 'Part::PropertyPartShape': Property, + } + + def __init__(self, name): + self.Label = name + self.properties = {} + self.DoNotSetDefaultValues = True + self.Proxy = None + + def __setattr__(self, name, val): + if name in ['Label', 'DoNotSetDefaultValues', 'properties', 'Proxy']: + if name == 'Proxy': + val = None # make sure the proxy is never set + return super(OpPrototype, self).__setattr__(name, val) + self.properties[name].setValue(val) + + def addProperty(self, typeString, name, category, info = None): + prop = self.PropertyType[typeString](name, typeString, category, info) + self.properties[name] = prop + return self + + def setEditorMode(self, name, mode): + self.properties[name].setEditorMode(mode) + + def getProperty(self, name): + return self.properties[name] + + def setupProperties(self, setup): + return [p for p in self.properties if p.name in setup] diff --git a/src/Mod/Path/PathScripts/PathPropertyContainer.py b/src/Mod/Path/PathScripts/PathPropertyContainer.py new file mode 100644 index 0000000000..a1b52b8e08 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathPropertyContainer.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2020 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 PySide + +__title__ = 'Generic property container to store some values.' +__author__ = 'sliptonic (Brad Collette)' +__url__ = 'https://www.freecadweb.org' +__doc__ = 'A generic container for typed properties in arbitrary categories.' + +def translate(context, text, disambig=None): + return PySide.QtCore.QCoreApplication.translate(context, text, disambig) + + +class PropertyContainer(object): + '''Property container object.''' + + CustomPropertyGroups = 'CustomPropertyGroups' + CustomPropertyGroupDefault = 'User' + + def __init__(self, obj): + self.obj = obj + obj.addProperty('App::PropertyStringList', self.CustomPropertyGroups, 'Base', PySide.QtCore.QT_TRANSLATE_NOOP('PathPropertyContainer', 'List of custom property groups')) + obj.setEditorMode(self.CustomPropertyGroups, 2) # hide + + def __getstate__(self): + return None + + def __setstate__(self, state): + for obj in FreeCAD.ActiveDocument.Objects: + if hasattr(obj, 'Proxy') and obj.Proxy == self: + self.obj = obj + obj.setEditorMode(self.CustomPropertyGroups, 2) # hide + break + return None + + def onDocumentRestored(self, obj): + obj.setEditorMode(self.CustomPropertyGroups, 2) # hide + + def getCustomProperties(self): + '''Return a list of all custom properties created in this container.''' + return [p for p in self.obj.PropertiesList if self.obj.getGroupOfProperty(p) in self.obj.CustomPropertyGroups] + + def addCustomProperty(self, propertyType, name, group=None, desc=None): + '''addCustomProperty(propertyType, name, group=None, desc=None) ... adds a custom property and tracks its group.''' + if desc is None: + desc = '' + if group is None: + group = self.CustomPropertyGroupDefault + groups = self.obj.CustomPropertyGroups + if not group in groups: + groups.append(group) + self.obj.CustomPropertyGroups = groups + self.obj.addProperty(propertyType, name, group, desc) + +def Create(name = 'PropertyContainer'): + obj = FreeCAD.ActiveDocument.addObject('App::FeaturePython', name) + obj.Proxy = PropertyContainer(obj) + return obj + +def IsPropertyContainer(obj): + '''Returns True if the supplied object is a property container (or its Proxy).''' + + if type(obj) == PropertyContainer: + return True + if hasattr(obj, 'Proxy'): + return IsPropertyContainer(obj.Proxy) + return False + diff --git a/src/Mod/Path/PathScripts/PathPropertyContainerGui.py b/src/Mod/Path/PathScripts/PathPropertyContainerGui.py new file mode 100644 index 0000000000..53b79fa517 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathPropertyContainerGui.py @@ -0,0 +1,235 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2020 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.PathGui as PathGui +import PathScripts.PathIconViewProvider as PathIconViewProvider +import PathScripts.PathLog as PathLog +import PathScripts.PathPropertyContainer as PathPropertyContainer +import PathScripts.PathPropertyEditor as PathPropertyEditor +import PathScripts.PathUtil as PathUtil + +from PySide import QtCore, QtGui + +__title__ = "Property Container Editor" +__author__ = "sliptonic (Brad Collette)" +__url__ = "https://www.freecadweb.org" +__doc__ = "Task panel editor for a PropertyContainer" + +PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +PathLog.trackModule(PathLog.thisModule()) + +# Qt translation handling +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + + +class ViewProvider: + '''ViewProvider for a PropertyContainer. + It's sole job is to provide an icon and invoke the TaskPanel on edit.''' + + def __init__(self, vobj, name): + PathLog.track(name) + vobj.Proxy = self + self.icon = name + # mode = 2 + self.obj = None + self.vobj = None + + def attach(self, vobj): + PathLog.track() + self.vobj = vobj + self.obj = vobj.Object + + def getIcon(self): + return ":/icons/Path-SetupSheet.svg" + + def __getstate__(self): + return None + + def __setstate__(self, state): + # pylint: disable=unused-argument + return None + + def getDisplayMode(self, mode): + # pylint: disable=unused-argument + return 'Default' + + def setEdit(self, vobj, mode=0): + # pylint: disable=unused-argument + PathLog.track() + taskPanel = TaskPanel(vobj) + FreeCADGui.Control.closeDialog() + FreeCADGui.Control.showDialog(taskPanel) + taskPanel.setupUi() + return True + + def unsetEdit(self, vobj, mode): + # pylint: disable=unused-argument + FreeCADGui.Control.closeDialog() + return + + def claimChildren(self): + return [] + + def doubleClicked(self, vobj): + self.setEdit(vobj) + +class Delegate(QtGui.QStyledItemDelegate): + RoleObject = QtCore.Qt.UserRole + 1 + RoleProperty = QtCore.Qt.UserRole + 2 + RoleEditor = QtCore.Qt.UserRole + 3 + + + #def paint(self, painter, option, index): + # #PathLog.track(index.column(), type(option)) + + def createEditor(self, parent, option, index): + # pylint: disable=unused-argument + editor = PathPropertyEditor.Editor(index.data(self.RoleObject), index.data(self.RoleProperty)) + index.model().setData(index, editor, self.RoleEditor) + return editor.widget(parent) + + def setEditorData(self, widget, index): + PathLog.track(index.row(), index.column()) + index.data(self.RoleEditor).setEditorData(widget) + + def setModelData(self, widget, model, index): + # pylint: disable=unused-argument + PathLog.track(index.row(), index.column()) + editor = index.data(self.RoleEditor) + editor.setModelData(widget) + index.model().setData(index, editor.displayString(), QtCore.Qt.DisplayRole) + + def updateEditorGeometry(self, widget, option, index): + # pylint: disable=unused-argument + widget.setGeometry(option.rect) + +class TaskPanel: + ColumnName = 0 + ColumnVal = 1 + ColumnDesc = 2 + + def __init__(self, vobj): + self.obj = vobj.Object + self.props = sorted(self.obj.Proxy.getCustomProperties()) + self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyContainer.ui") + + # initialized later + self.delegate = None + self.model = None + FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyContainer", "Edit PropertyContainer")) + + def updateData(self, topLeft, bottomRight): + # pylint: disable=unused-argument + #if 0 == topLeft.column(): + # isset = self.model.item(topLeft.row(), 0).checkState() == QtCore.Qt.Checked + # self.model.item(topLeft.row(), 1).setEnabled(isset) + # self.model.item(topLeft.row(), 2).setEnabled(isset) + print("index = ({}, {}) - ({}, {})".format(topLeft.row(), topLeft.column(), bottomRight.row(), bottomRight.column())) + if topLeft.column() == self.ColumnDesc: + obj = topLeft.data(Delegate.RoleObject) + prop = topLeft.data(Delegate.RoleProperty) + + def setupUi(self): + PathLog.track() + + self.delegate = Delegate(self.form) + self.model = QtGui.QStandardItemModel(len(self.props), 3, self.form) + self.model.setHorizontalHeaderLabels(['Property', 'Value', 'Description']) + + for i,name in enumerate(self.props): + info = self.obj.getDocumentationOfProperty(name) + value = self.obj.getPropertyByName(name) + self.model.setData(self.model.index(i, self.ColumnName), name, QtCore.Qt.EditRole) + self.model.setData(self.model.index(i, self.ColumnVal), self.obj, Delegate.RoleObject) + self.model.setData(self.model.index(i, self.ColumnVal), name, Delegate.RoleProperty) + self.model.setData(self.model.index(i, self.ColumnVal), str(value), QtCore.Qt.DisplayRole) + self.model.setData(self.model.index(i, self.ColumnDesc), info, QtCore.Qt.EditRole) + + self.model.item(i, self.ColumnName).setEditable(False) + + self.form.table.setModel(self.model) + self.form.table.setItemDelegateForColumn(self.ColumnVal, self.delegate) + self.form.table.resizeColumnsToContents() + + self.model.dataChanged.connect(self.updateData) + self.form.table.selectionModel().selectionChanged.connect(self.propertySelected) + self.form.add.clicked.connect(self.propertyAdd) + self.form.remove.clicked.connect(self.propertyRemove) + self.propertySelected([]) + + def accept(self): + #propertiesCreatedRemoved = False + #for i,name in enumerate(self.props): + # prop = self.prototype.getProperty(name) + # propName = self.propertyName(name) + # enabled = self.model.item(i, 0).checkState() == QtCore.Qt.Checked + # if enabled and not prop.getValue() is None: + # if prop.setupProperty(self.obj, propName, self.propertyGroup(), prop.getValue()): + # propertiesCreatedRemoved = True + # else: + # if hasattr(self.obj, propName): + # self.obj.removeProperty(propName) + # propertiesCreatedRemoved = True + + FreeCAD.ActiveDocument.commitTransaction() + FreeCADGui.ActiveDocument.resetEdit() + FreeCADGui.Control.closeDialog() + FreeCAD.ActiveDocument.recompute() + + def reject(self): + FreeCAD.ActiveDocument.abortTransaction() + FreeCADGui.Control.closeDialog() + FreeCAD.ActiveDocument.recompute() + + def propertySelected(self, selection): + PathLog.track() + if selection: + self.form.remove.setEnabled(True) + else: + self.form.remove.setEnabled(False) + + def propertyAdd(self): + PathLog.track() + + def propertyRemove(self): + PathLog.track() + rows = [] + for index in self.form.table.selectionModel().selectedIndexes(): + if not index.row() in rows: + rows.append(index.row()) + for row in reversed(sorted(rows)): + self.obj.removeProperty(self.model.item(row).data(QtCore.Qt.EditRole)) + self.model.removeRow(row) + + +def Create(name = 'PropertyContainer'): + '''Create(name = 'PropertyContainer') ... creates a new setup sheet''' + FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyContainer", "Create PropertyContainer")) + pcont = PathPropertyContainer.Create(name) + PathIconViewProvider.Attach(pcont.ViewObject, name) + return pcont + +PathIconViewProvider.RegisterViewProvider('PropertyContainer', ViewProvider) + diff --git a/src/Mod/Path/PathScripts/PathPropertyEditor.py b/src/Mod/Path/PathScripts/PathPropertyEditor.py new file mode 100644 index 0000000000..9b0cd81afd --- /dev/null +++ b/src/Mod/Path/PathScripts/PathPropertyEditor.py @@ -0,0 +1,219 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2020 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 PathScripts.PathLog as PathLog +import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype + +from PySide import QtCore, QtGui + +__title__ = "Path Property Editor" +__author__ = "sliptonic (Brad Collette)" +__url__ = "https://www.freecadweb.org" +__doc__ = "Task panel editor for Properties" + +# Qt translation handling +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + +LOGLEVEL = False + +if LOGLEVEL: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + +class _PropertyEditor(object): + '''Base class of all property editors - just outlines the TableView delegate interface.''' + def __init__(self, obj, prop): + self.obj = obj + self.prop = prop + + def widget(self, parent): + '''widget(parent) ... called by the delegate to get a new editor widget. + Must be implemented by subclasses and return the widget.''' + pass # pylint: disable=unnecessary-pass + + def setEditorData(self, widget): + '''setEditorData(widget) ... called by the delegate to initialize the editor. + The widget is the object returned by widget(). + Must be implemented by subclasses.''' + pass # pylint: disable=unnecessary-pass + + def setModelData(self, widget): + '''setModelData(widget) ... called by the delegate to store new values. + Must be implemented by subclasses.''' + pass # pylint: disable=unnecessary-pass + + def propertyValue(self): + return self.obj.getPropertyByName(self.prop) + + def setProperty(self, value): + setattr(self.obj, self.prop, value) + + def displayString(self): + return self.propertyValue() + +class _PropertyEditorBool(_PropertyEditor): + '''Editor for boolean values - uses a combo box.''' + + def widget(self, parent): + return QtGui.QComboBox(parent) + + def setEditorData(self, widget): + widget.clear() + widget.addItems(['false', 'true']) + index = 1 if self.propertyValue() else 0 + widget.setCurrentIndex(index) + + def setModelData(self, widget): + self.setProperty(widget.currentText() == 'true') + +class _PropertyEditorString(_PropertyEditor): + '''Editor for string values - uses a line edit.''' + + def widget(self, parent): + return QtGui.QLineEdit(parent) + + def setEditorData(self, widget): + text = '' if self.propertyValue() is None else self.propertyValue() + widget.setText(text) + + def setModelData(self, widget): + self.setProperty(widget.text()) + +class _PropertyEditorQuantity(_PropertyEditor): + + def widget(self, parent): + return QtGui.QLineEdit(parent) + + def setEditorData(self, widget): + quantity = self.propertyValue() + if quantity is None: + quantity = self.defaultQuantity() + widget.setText(quantity.getUserPreferred()[0]) + + def defaultQuantity(self): + pass + + def setModelData(self, widget): + self.setProperty(FreeCAD.Units.Quantity(widget.text())) + + def displayString(self): + if self.propertyValue() is None: + return '' + return self.propertyValue().getUserPreferred()[0] + +class _PropertyEditorAngle(_PropertyEditorQuantity): + '''Editor for angle values - uses a line edit''' + + def defaultQuantity(self): + return FreeCAD.Units.Quantity(0, FreeCAD.Units.Angle) + +class _PropertyEditorLength(_PropertyEditorQuantity): + '''Editor for length values - uses a line edit.''' + + def defaultQuantity(self): + return FreeCAD.Units.Quantity(0, FreeCAD.Units.Length) + +class _PropertyEditorPercent(_PropertyEditor): + '''Editor for percent values - uses a spin box.''' + + def widget(self, parent): + return QtGui.QSpinBox(parent) + + def setEditorData(self, widget): + widget.setRange(0, 100) + value = self.propertyValue() + if value is None: + value = 0 + widget.setValue(value) + + def setModelData(self, widget): + self.setProperty(widget.value()) + +class _PropertyEditorInteger(_PropertyEditor): + '''Editor for integer values - uses a spin box.''' + + def widget(self, parent): + return QtGui.QSpinBox(parent) + + def setEditorData(self, widget): + value = self.propertyValue() + if value is None: + value = 0 + widget.setValue(value) + + def setModelData(self, widget): + self.setProperty(widget.value()) + +class _PropertyEditorFloat(_PropertyEditor): + '''Editor for float values - uses a double spin box.''' + + def widget(self, parent): + return QtGui.QDoubleSpinBox(parent) + + def setEditorData(self, widget): + value = self.propertyValue() + if value is None: + value = 0.0 + widget.setValue(value) + + def setModelData(self, widget): + self.setProperty(widget.value()) + +class _PropertyEditorFile(_PropertyEditor): + + def widget(self, parent): + return QtGui.QLineEdit(parent) + + def setEditorData(self, widget): + text = '' if self.propertyValue() is None else self.propertyValue() + widget.setText(text) + + def setModelData(self, widget): + self.setProperty(widget.text()) + +_EditorFactory = { + 'App::PropertyAngle' : _PropertyEditorAngle, + 'App::PropertyBool' : _PropertyEditorBool, + 'App::PropertyDistance' : _PropertyEditorLength, + #'App::PropertyEnumeration' : _PropertyEditorEnum, + #'App::PropertyFile' : _PropertyEditorFile, + 'App::PropertyFloat' : _PropertyEditorFloat, + 'App::PropertyInteger' : _PropertyEditorInteger, + 'App::PropertyLength' : _PropertyEditorLength, + 'App::PropertyPercent' : _PropertyEditorPercent, + 'App::PropertyString' : _PropertyEditorString, + } + +def Types(): + '''Return the types of properties supported.''' + return [t for t in _EditorFactory] + +def Editor(obj, prop): + '''Returns an editor class to be used for the given property.''' + factory = _EditorFactory[obj.getTypeIdOfProperty(prop)] + if factory: + return factory(obj, prop) + return None From da308d13f6fe2b7b509bfe148b2fa6c5a2231a62 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 7 Dec 2020 22:33:26 -0800 Subject: [PATCH 032/168] Cleaned up logging --- src/Mod/Path/PathScripts/PathPropertyEditor.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPropertyEditor.py b/src/Mod/Path/PathScripts/PathPropertyEditor.py index 9b0cd81afd..d45add0658 100644 --- a/src/Mod/Path/PathScripts/PathPropertyEditor.py +++ b/src/Mod/Path/PathScripts/PathPropertyEditor.py @@ -35,13 +35,9 @@ __doc__ = "Task panel editor for Properties" def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) -LOGLEVEL = False +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +#PathLog.trackModule(PathLog.thisModule()) -if LOGLEVEL: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) -else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) class _PropertyEditor(object): '''Base class of all property editors - just outlines the TableView delegate interface.''' From c65ed378761f57f8404f1ebd406677a6b530c0a1 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 10 Dec 2020 20:18:54 -0800 Subject: [PATCH 033/168] Adding properties to property container. --- src/Mod/Path/Gui/Resources/Path.qrc | 1 + .../Gui/Resources/panels/PropertyCreate.ui | 132 ++++++++++++++++++ .../PathScripts/PathPropertyContainerGui.py | 94 +++++++++++-- 3 files changed, 216 insertions(+), 11 deletions(-) create mode 100644 src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index a8d0ab5d21..8300eb42ee 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -122,6 +122,7 @@ panels/PathEdit.ui panels/PointEdit.ui panels/PropertyContainer.ui + panels/PropertyCreate.ui panels/SetupGlobal.ui panels/SetupOp.ui panels/ToolBitEditor.ui diff --git a/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui b/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui new file mode 100644 index 0000000000..32f0bf7ca6 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui @@ -0,0 +1,132 @@ + + + Dialog + + + + 0 + 0 + 432 + 300 + + + + Dialog + + + + + + + + + + + + Type + + + + + + + Name + + + + + + + Info + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Group + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + + + + + + + propertyName + propertyGroup + propertyType + propertyInfo + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/Mod/Path/PathScripts/PathPropertyContainerGui.py b/src/Mod/Path/PathScripts/PathPropertyContainerGui.py index 53b79fa517..629bab3b39 100644 --- a/src/Mod/Path/PathScripts/PathPropertyContainerGui.py +++ b/src/Mod/Path/PathScripts/PathPropertyContainerGui.py @@ -43,8 +43,20 @@ PathLog.trackModule(PathLog.thisModule()) def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) +SupportedPropertyType = { + 'Angle' : 'App::PropertyAngle', + 'Bool' : 'App::PropertyBool', + 'Distance' : 'App::PropertyDistance', + # 'Enumeration' : 'App::PropertyEnumeration', + 'File' : 'App::PropertyFile', + 'Float' : 'App::PropertyFloat', + 'Integer' : 'App::PropertyInteger', + 'Length' : 'App::PropertyLength', + 'Percent' : 'App::PropertyPercent', + 'String' : 'App::PropertyString', + } -class ViewProvider: +class ViewProvider(object): '''ViewProvider for a PropertyContainer. It's sole job is to provide an icon and invoke the TaskPanel on edit.''' @@ -125,7 +137,44 @@ class Delegate(QtGui.QStyledItemDelegate): # pylint: disable=unused-argument widget.setGeometry(option.rect) -class TaskPanel: +class PropertyCreate(object): + + def __init__(self, obj, parent=None): + self.obj = obj + self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyCreate.ui") + + for group in sorted(obj.CustomPropertyGroups): + self.form.propertyGroup.addItem(group) + for typ in sorted(SupportedPropertyType): + self.form.propertyType.addItem(typ) + self.form.propertyType.setCurrentText('String') + + self.form.propertyGroup.currentTextChanged.connect(self.updateUI) + self.form.propertyGroup.currentIndexChanged.connect(self.updateUI) + self.form.propertyName.textChanged.connect(self.updateUI) + + self.updateUI() + + def updateUI(self): + ok = self.form.buttonBox.button(QtGui.QDialogButtonBox.Ok) + if self.form.propertyName.text() and self.form.propertyGroup.currentText(): + ok.setEnabled(True) + else: + ok.setEnabled(False) + + def propertyName(self): + return self.form.propertyName.text() + def propertyGroup(self): + return self.form.propertyGroup.currentText() + def propertyType(self): + return SupportedPropertyType[self.form.propertyType.currentText()] + def propertyInfo(self): + return self.form.propertyInfo.toPlainText() + + def exec_(self): + return self.form.exec_() + +class TaskPanel(object): ColumnName = 0 ColumnVal = 1 ColumnDesc = 2 @@ -151,6 +200,19 @@ class TaskPanel: obj = topLeft.data(Delegate.RoleObject) prop = topLeft.data(Delegate.RoleProperty) + + def _setupProperty(self, i, name): + info = self.obj.getDocumentationOfProperty(name) + value = self.obj.getPropertyByName(name) + + self.model.setData(self.model.index(i, self.ColumnName), name, QtCore.Qt.EditRole) + self.model.setData(self.model.index(i, self.ColumnVal), self.obj, Delegate.RoleObject) + self.model.setData(self.model.index(i, self.ColumnVal), name, Delegate.RoleProperty) + self.model.setData(self.model.index(i, self.ColumnVal), str(value), QtCore.Qt.DisplayRole) + self.model.setData(self.model.index(i, self.ColumnDesc), info, QtCore.Qt.EditRole) + + self.model.item(i, self.ColumnName).setEditable(False) + def setupUi(self): PathLog.track() @@ -159,15 +221,7 @@ class TaskPanel: self.model.setHorizontalHeaderLabels(['Property', 'Value', 'Description']) for i,name in enumerate(self.props): - info = self.obj.getDocumentationOfProperty(name) - value = self.obj.getPropertyByName(name) - self.model.setData(self.model.index(i, self.ColumnName), name, QtCore.Qt.EditRole) - self.model.setData(self.model.index(i, self.ColumnVal), self.obj, Delegate.RoleObject) - self.model.setData(self.model.index(i, self.ColumnVal), name, Delegate.RoleProperty) - self.model.setData(self.model.index(i, self.ColumnVal), str(value), QtCore.Qt.DisplayRole) - self.model.setData(self.model.index(i, self.ColumnDesc), info, QtCore.Qt.EditRole) - - self.model.item(i, self.ColumnName).setEditable(False) + self._setupProperty(i, name) self.form.table.setModel(self.model) self.form.table.setItemDelegateForColumn(self.ColumnVal, self.delegate) @@ -212,6 +266,21 @@ class TaskPanel: def propertyAdd(self): PathLog.track() + dialog = PropertyCreate(self.obj) + if dialog.exec_(): + #self.model.blockSignals(True) + name = dialog.propertyName() + typ = dialog.propertyType() + grpe = dialog.propertyGroup() + info = dialog.propertyInfo() + self.obj.Proxy.addCustomProperty(typ, name, grpe, info) + for i in range(self.model.rowCount()): + if self.model.item(i, self.ColumnName).data(QtCore.Qt.EditRole) > dialog.propertyName(): + self.model.insertRows(i, 1) + self._setupProperty(i, name) + self.form.table.selectionModel().setCurrentIndex(self.model.index(i, 0), QtCore.QItemSelectionModel.Rows) + break + #self.model.blockSignals(False) def propertyRemove(self): PathLog.track() @@ -219,9 +288,12 @@ class TaskPanel: for index in self.form.table.selectionModel().selectedIndexes(): if not index.row() in rows: rows.append(index.row()) + + self.model.blockSignals(True) for row in reversed(sorted(rows)): self.obj.removeProperty(self.model.item(row).data(QtCore.Qt.EditRole)) self.model.removeRow(row) + self.model.blockSignals(False) def Create(name = 'PropertyContainer'): From e9093e1edc7a5492c3bd5a272f778f84273f81a7 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 10 Dec 2020 20:54:54 -0800 Subject: [PATCH 034/168] Some code and comment cleanup. --- .../PathScripts/PathPropertyContainerGui.py | 29 ++++--------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPropertyContainerGui.py b/src/Mod/Path/PathScripts/PathPropertyContainerGui.py index 629bab3b39..c2ac81ac28 100644 --- a/src/Mod/Path/PathScripts/PathPropertyContainerGui.py +++ b/src/Mod/Path/PathScripts/PathPropertyContainerGui.py @@ -36,8 +36,8 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Task panel editor for a PropertyContainer" -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -PathLog.trackModule(PathLog.thisModule()) +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +#PathLog.trackModule(PathLog.thisModule()) # Qt translation handling def translate(context, text, disambig=None): @@ -190,12 +190,6 @@ class TaskPanel(object): FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyContainer", "Edit PropertyContainer")) def updateData(self, topLeft, bottomRight): - # pylint: disable=unused-argument - #if 0 == topLeft.column(): - # isset = self.model.item(topLeft.row(), 0).checkState() == QtCore.Qt.Checked - # self.model.item(topLeft.row(), 1).setEnabled(isset) - # self.model.item(topLeft.row(), 2).setEnabled(isset) - print("index = ({}, {}) - ({}, {})".format(topLeft.row(), topLeft.column(), bottomRight.row(), bottomRight.column())) if topLeft.column() == self.ColumnDesc: obj = topLeft.data(Delegate.RoleObject) prop = topLeft.data(Delegate.RoleProperty) @@ -234,19 +228,6 @@ class TaskPanel(object): self.propertySelected([]) def accept(self): - #propertiesCreatedRemoved = False - #for i,name in enumerate(self.props): - # prop = self.prototype.getProperty(name) - # propName = self.propertyName(name) - # enabled = self.model.item(i, 0).checkState() == QtCore.Qt.Checked - # if enabled and not prop.getValue() is None: - # if prop.setupProperty(self.obj, propName, self.propertyGroup(), prop.getValue()): - # propertiesCreatedRemoved = True - # else: - # if hasattr(self.obj, propName): - # self.obj.removeProperty(propName) - # propertiesCreatedRemoved = True - FreeCAD.ActiveDocument.commitTransaction() FreeCADGui.ActiveDocument.resetEdit() FreeCADGui.Control.closeDialog() @@ -268,6 +249,7 @@ class TaskPanel(object): PathLog.track() dialog = PropertyCreate(self.obj) if dialog.exec_(): + # if we block signals the view doesn't get updated, surprise, surprise #self.model.blockSignals(True) name = dialog.propertyName() typ = dialog.propertyType() @@ -284,16 +266,17 @@ class TaskPanel(object): def propertyRemove(self): PathLog.track() + # first find all rows which need to be removed rows = [] for index in self.form.table.selectionModel().selectedIndexes(): if not index.row() in rows: rows.append(index.row()) - self.model.blockSignals(True) + # then remove them in reverse order so the indexes of the remaining rows + # to delete are still valid for row in reversed(sorted(rows)): self.obj.removeProperty(self.model.item(row).data(QtCore.Qt.EditRole)) self.model.removeRow(row) - self.model.blockSignals(False) def Create(name = 'PropertyContainer'): From cc12ce4283c1cc4a07c462be2c61ba97e2ff0727 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 10 Dec 2020 21:40:11 -0800 Subject: [PATCH 035/168] Added command with menu item to create a property container. --- src/Mod/Path/InitGui.py | 4 +++ .../PathScripts/PathPropertyContainerGui.py | 28 +++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index e26c2fe0da..8e9fb87c94 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -154,6 +154,10 @@ class PathWorkbench (Workbench): if extracmdlist: self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], extracmdlist) + self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], ["Separator"]) + self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP("Path", "Tools")], + ["Path_PropertyContainer"]) + self.dressupcmds = dressupcmdlist curveAccuracy = PathPreferences.defaultLibAreaCurveAccuracy() diff --git a/src/Mod/Path/PathScripts/PathPropertyContainerGui.py b/src/Mod/Path/PathScripts/PathPropertyContainerGui.py index c2ac81ac28..5f59071e96 100644 --- a/src/Mod/Path/PathScripts/PathPropertyContainerGui.py +++ b/src/Mod/Path/PathScripts/PathPropertyContainerGui.py @@ -256,12 +256,14 @@ class TaskPanel(object): grpe = dialog.propertyGroup() info = dialog.propertyInfo() self.obj.Proxy.addCustomProperty(typ, name, grpe, info) + index = 0 for i in range(self.model.rowCount()): + index = i if self.model.item(i, self.ColumnName).data(QtCore.Qt.EditRole) > dialog.propertyName(): - self.model.insertRows(i, 1) - self._setupProperty(i, name) - self.form.table.selectionModel().setCurrentIndex(self.model.index(i, 0), QtCore.QItemSelectionModel.Rows) break + self.model.insertRows(index, 1) + self._setupProperty(index, name) + self.form.table.selectionModel().setCurrentIndex(self.model.index(index, 0), QtCore.QItemSelectionModel.Rows) #self.model.blockSignals(False) def propertyRemove(self): @@ -288,3 +290,23 @@ def Create(name = 'PropertyContainer'): PathIconViewProvider.RegisterViewProvider('PropertyContainer', ViewProvider) +class PropertyContainerCreateCommand(object): + '''Command to create a property container object''' + + def __init__(self): + pass + + def GetResources(self): + return {'MenuText': translate('PathPropertyContainer', 'Property Container'), + 'ToolTip': translate('PathPropertyContainer', 'Creates an object which can be used to store reference properties.')} + + def IsActive(self): + return not FreeCAD.ActiveDocument is None + + def Activated(self): + Create() + +if FreeCAD.GuiUp: + FreeCADGui.addCommand('Path_PropertyContainer', PropertyContainerCreateCommand()) + +FreeCAD.Console.PrintLog("Loading PathPropertyContainerGui ... done\n") From cbaa1bab85088dfe44e38a1329bd211875fbf2f1 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 10 Dec 2020 22:23:22 -0800 Subject: [PATCH 036/168] Added ability to create multiple attributes in a row --- .../Gui/Resources/panels/PropertyCreate.ui | 95 ++++++++++++------- .../PathScripts/PathPropertyContainerGui.py | 79 +++++++++------ 2 files changed, 113 insertions(+), 61 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui b/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui index 32f0bf7ca6..13fe6cd1e9 100644 --- a/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui +++ b/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui @@ -6,7 +6,7 @@ 0 0 - 432 + 474 300 @@ -14,19 +14,6 @@ Dialog - - - - - - - - - - Type - - - @@ -34,25 +21,8 @@ - - - - Info - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - + + @@ -86,6 +56,65 @@
+ + + + Type + + + + + + + + + + Info + + + + + + + true + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Create another + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + diff --git a/src/Mod/Path/PathScripts/PathPropertyContainerGui.py b/src/Mod/Path/PathScripts/PathPropertyContainerGui.py index 5f59071e96..3e376e6da2 100644 --- a/src/Mod/Path/PathScripts/PathPropertyContainerGui.py +++ b/src/Mod/Path/PathScripts/PathPropertyContainerGui.py @@ -139,15 +139,24 @@ class Delegate(QtGui.QStyledItemDelegate): class PropertyCreate(object): - def __init__(self, obj, parent=None): + def __init__(self, obj, grp, typ, another): self.obj = obj self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyCreate.ui") - for group in sorted(obj.CustomPropertyGroups): - self.form.propertyGroup.addItem(group) - for typ in sorted(SupportedPropertyType): - self.form.propertyType.addItem(typ) - self.form.propertyType.setCurrentText('String') + for g in sorted(obj.CustomPropertyGroups): + self.form.propertyGroup.addItem(g) + if grp: + self.form.propertyGroup.setCurrentText(grp) + + for t in sorted(SupportedPropertyType): + self.form.propertyType.addItem(t) + if SupportedPropertyType[t] == typ: + typ = t + if typ: + self.form.propertyType.setCurrentText(typ) + else: + self.form.propertyType.setCurrentText('String') + self.form.createAnother.setChecked(another) self.form.propertyGroup.currentTextChanged.connect(self.updateUI) self.form.propertyGroup.currentIndexChanged.connect(self.updateUI) @@ -163,15 +172,20 @@ class PropertyCreate(object): ok.setEnabled(False) def propertyName(self): - return self.form.propertyName.text() + return self.form.propertyName.text().strip() def propertyGroup(self): - return self.form.propertyGroup.currentText() + return self.form.propertyGroup.currentText().strip() def propertyType(self): - return SupportedPropertyType[self.form.propertyType.currentText()] + return SupportedPropertyType[self.form.propertyType.currentText()].strip() def propertyInfo(self): - return self.form.propertyInfo.toPlainText() + return self.form.propertyInfo.toPlainText().strip() + def createAnother(self): + return self.form.createAnother.isChecked() def exec_(self): + self.form.propertyName.setText('') + self.form.propertyInfo.setText('') + #self.form.propertyName.setFocus() return self.form.exec_() class TaskPanel(object): @@ -247,24 +261,33 @@ class TaskPanel(object): def propertyAdd(self): PathLog.track() - dialog = PropertyCreate(self.obj) - if dialog.exec_(): - # if we block signals the view doesn't get updated, surprise, surprise - #self.model.blockSignals(True) - name = dialog.propertyName() - typ = dialog.propertyType() - grpe = dialog.propertyGroup() - info = dialog.propertyInfo() - self.obj.Proxy.addCustomProperty(typ, name, grpe, info) - index = 0 - for i in range(self.model.rowCount()): - index = i - if self.model.item(i, self.ColumnName).data(QtCore.Qt.EditRole) > dialog.propertyName(): - break - self.model.insertRows(index, 1) - self._setupProperty(index, name) - self.form.table.selectionModel().setCurrentIndex(self.model.index(index, 0), QtCore.QItemSelectionModel.Rows) - #self.model.blockSignals(False) + more = False + grp = None + typ = None + while True: + dialog = PropertyCreate(self.obj, grp, typ, more) + if dialog.exec_(): + # if we block signals the view doesn't get updated, surprise, surprise + #self.model.blockSignals(True) + name = dialog.propertyName() + typ = dialog.propertyType() + grp = dialog.propertyGroup() + info = dialog.propertyInfo() + self.obj.Proxy.addCustomProperty(typ, name, grp, info) + index = 0 + for i in range(self.model.rowCount()): + index = i + if self.model.item(i, self.ColumnName).data(QtCore.Qt.EditRole) > dialog.propertyName(): + break + self.model.insertRows(index, 1) + self._setupProperty(index, name) + self.form.table.selectionModel().setCurrentIndex(self.model.index(index, 0), QtCore.QItemSelectionModel.Rows) + #self.model.blockSignals(False) + more = dialog.createAnother() + else: + more = False + if not more: + break def propertyRemove(self): PathLog.track() From 8b2bfaa3221f16eb6642f6aa48f1220648a8a3c5 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 11 Dec 2020 19:17:02 -0800 Subject: [PATCH 037/168] Allow property container to become a child of a body when one is selected on creation. --- .../Path/PathScripts/PathPropertyContainerGui.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathPropertyContainerGui.py b/src/Mod/Path/PathScripts/PathPropertyContainerGui.py index 3e376e6da2..c5c799d19c 100644 --- a/src/Mod/Path/PathScripts/PathPropertyContainerGui.py +++ b/src/Mod/Path/PathScripts/PathPropertyContainerGui.py @@ -327,7 +327,19 @@ class PropertyContainerCreateCommand(object): return not FreeCAD.ActiveDocument is None def Activated(self): - Create() + sel = FreeCADGui.Selection.getSelectionEx() + obj = Create() + body = None + if sel: + if 'PartDesign::Body' == sel[0].Object.TypeId: + body = sel[0].Object + elif hasattr(sel[0].Object, 'getParentGeoFeatureGroup'): + body = sel[0].Object.getParentGeoFeatureGroup() + if body: + obj.Label = 'Attributes' + group = body.Group + group.append(obj) + body.Group = group if FreeCAD.GuiUp: FreeCADGui.addCommand('Path_PropertyContainer', PropertyContainerCreateCommand()) From ad6d463e368c77ca09de66e64853223b5bbb6113 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 20 Dec 2020 18:14:29 -0800 Subject: [PATCH 038/168] Using property container for endmill attributes. --- .../Path/PathScripts/PathPropertyContainer.py | 34 +++++-- .../PathScripts/PathPropertyContainerGui.py | 19 +--- src/Mod/Path/PathScripts/PathToolBit.py | 88 +++++++++++------- src/Mod/Path/PathScripts/PathToolBitEdit.py | 51 +++++----- src/Mod/Path/PathScripts/PathToolBitGui.py | 4 +- src/Mod/Path/Tools/Shape/endmill.fcstd | Bin 10858 -> 11351 bytes 6 files changed, 116 insertions(+), 80 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPropertyContainer.py b/src/Mod/Path/PathScripts/PathPropertyContainer.py index a1b52b8e08..baf033dfba 100644 --- a/src/Mod/Path/PathScripts/PathPropertyContainer.py +++ b/src/Mod/Path/PathScripts/PathPropertyContainer.py @@ -32,6 +32,31 @@ def translate(context, text, disambig=None): return PySide.QtCore.QCoreApplication.translate(context, text, disambig) +SupportedPropertyType = { + 'Angle' : 'App::PropertyAngle', + 'Bool' : 'App::PropertyBool', + 'Distance' : 'App::PropertyDistance', + # 'Enumeration' : 'App::PropertyEnumeration', + 'File' : 'App::PropertyFile', + 'Float' : 'App::PropertyFloat', + 'Integer' : 'App::PropertyInteger', + 'Length' : 'App::PropertyLength', + 'Percent' : 'App::PropertyPercent', + 'String' : 'App::PropertyString', + } + +def getPropertyType(o): + if type(o) == str: + return SupportedPropertyType['String'] + if type(o) == bool: + return SupportedPropertyType['Bool'] + if type(o) == int: + return SupportedPropertyType['Integer'] + if type(o) == float: + return SupportedPropertyType['Float'] + if type(o) == FreeCAD.Units.Quantity: + return SupportedPropertyType[o.Unit.Type] + class PropertyContainer(object): '''Property container object.''' @@ -39,22 +64,17 @@ class PropertyContainer(object): CustomPropertyGroupDefault = 'User' def __init__(self, obj): - self.obj = obj obj.addProperty('App::PropertyStringList', self.CustomPropertyGroups, 'Base', PySide.QtCore.QT_TRANSLATE_NOOP('PathPropertyContainer', 'List of custom property groups')) - obj.setEditorMode(self.CustomPropertyGroups, 2) # hide + self.onDocumentRestored(obj) def __getstate__(self): return None def __setstate__(self, state): - for obj in FreeCAD.ActiveDocument.Objects: - if hasattr(obj, 'Proxy') and obj.Proxy == self: - self.obj = obj - obj.setEditorMode(self.CustomPropertyGroups, 2) # hide - break return None def onDocumentRestored(self, obj): + self.obj = obj obj.setEditorMode(self.CustomPropertyGroups, 2) # hide def getCustomProperties(self): diff --git a/src/Mod/Path/PathScripts/PathPropertyContainerGui.py b/src/Mod/Path/PathScripts/PathPropertyContainerGui.py index c5c799d19c..d12ee29466 100644 --- a/src/Mod/Path/PathScripts/PathPropertyContainerGui.py +++ b/src/Mod/Path/PathScripts/PathPropertyContainerGui.py @@ -43,19 +43,6 @@ PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) -SupportedPropertyType = { - 'Angle' : 'App::PropertyAngle', - 'Bool' : 'App::PropertyBool', - 'Distance' : 'App::PropertyDistance', - # 'Enumeration' : 'App::PropertyEnumeration', - 'File' : 'App::PropertyFile', - 'Float' : 'App::PropertyFloat', - 'Integer' : 'App::PropertyInteger', - 'Length' : 'App::PropertyLength', - 'Percent' : 'App::PropertyPercent', - 'String' : 'App::PropertyString', - } - class ViewProvider(object): '''ViewProvider for a PropertyContainer. It's sole job is to provide an icon and invoke the TaskPanel on edit.''' @@ -148,9 +135,9 @@ class PropertyCreate(object): if grp: self.form.propertyGroup.setCurrentText(grp) - for t in sorted(SupportedPropertyType): + for t in sorted(PathPropertyContainer.SupportedPropertyType): self.form.propertyType.addItem(t) - if SupportedPropertyType[t] == typ: + if PathPropertyContainer.SupportedPropertyType[t] == typ: typ = t if typ: self.form.propertyType.setCurrentText(typ) @@ -176,7 +163,7 @@ class PropertyCreate(object): def propertyGroup(self): return self.form.propertyGroup.currentText().strip() def propertyType(self): - return SupportedPropertyType[self.form.propertyType.currentText()].strip() + return PathPropertyContainer.SupportedPropertyType[self.form.propertyType.currentText()].strip() def propertyInfo(self): return self.form.propertyInfo.toPlainText().strip() def createAnother(self): diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 5ffe7503dd..5e63902c34 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -24,6 +24,7 @@ import FreeCAD import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences +import PathScripts.PathPropertyContainer as PathPropertyContainer import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype import PathScripts.PathUtil as PathUtil import PySide @@ -42,8 +43,10 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Class to deal with and represent a tool bit." -# PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -# PathLog.trackModule() +_DebugFindTool = True + +PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +PathLog.trackModule() def translate(context, text, disambig=None): @@ -58,7 +61,8 @@ ParameterTypeConstraint = { 'Radius': 'App::PropertyLength'} -def _findTool(path, typ, dbg=False): +def _findTool(path, typ, dbg=_DebugFindTool): + PathLog.track(path) if os.path.exists(path): # absolute reference if dbg: PathLog.debug("Found {} at {}".format(typ, path)) @@ -67,7 +71,7 @@ def _findTool(path, typ, dbg=False): def searchFor(pname, fname): # PathLog.debug("pname: {} fname: {}".format(pname, fname)) if dbg: - PathLog.debug("Looking for {}".format(pname)) + PathLog.debug("Looking for {} in {}".format(pname, fname)) if fname: for p in PathPreferences.searchPathsTool(typ): PathLog.track(p) @@ -174,6 +178,9 @@ class ToolBit(object): translate('PathToolBit', 'The file of the tool')) obj.addProperty('App::PropertyString', 'ShapeName', 'Base', translate('PathToolBit', 'The name of the shape file')) + obj.addProperty('App::PropertyStringList', 'BitPropertyNames', 'Base', + translate('PathToolBit', 'List of all properties inherited from the bit')) + if shapeFile is None: obj.BitShape = 'endmill.fcstd' self._setupBitShape(obj) @@ -193,9 +200,6 @@ class ToolBit(object): break return None - def propertyNamesBit(self, obj): - return [prop for prop in obj.PropertiesList if obj.getGroupOfProperty(prop) == PropertyGroupBit] - def propertyNamesAttribute(self, obj): return [prop for prop in obj.PropertiesList if obj.getGroupOfProperty(prop) == PropertyGroupAttribute] @@ -206,8 +210,9 @@ class ToolBit(object): obj.setEditorMode('BitBody', 2) obj.setEditorMode('File', 1) obj.setEditorMode('Shape', 2) + obj.setEditorMode('BitPropertyNames', 2) - for prop in self.propertyNamesBit(obj): + for prop in obj.BitPropertyNames: obj.setEditorMode(prop, 1) # I currently don't see why these need to be read-only # for prop in self.propertyNamesAttribute(obj): @@ -227,12 +232,9 @@ class ToolBit(object): def _updateBitShape(self, obj, properties=None): if obj.BitBody is not None: - if not properties: - properties = self.propertyNamesBit(obj) - for prop in properties: - for sketch in [o for o in obj.BitBody.Group if o.TypeId == 'Sketcher::SketchObject']: - PathLog.track(obj.Label, sketch.Label, prop) - updateConstraint(sketch, prop, obj.getPropertyByName(prop)) + for attributes in [o for o in obj.BitBody.Group if hasattr(o, 'Proxy') and hasattr(o.Proxy, 'getCustomProperties')]: + for prop in attributes.Proxy.getCustomProperties(): + setattr(attributes, prop, obj.getPropertyByName(prop)) self._copyBitShape(obj) def _copyBitShape(self, obj): @@ -243,6 +245,7 @@ class ToolBit(object): obj.Shape = Part.Shape() def _loadBitBody(self, obj, path=None): + PathLog.track(obj.Label, path) p = path if path else obj.BitShape docOpened = False doc = None @@ -254,9 +257,12 @@ class ToolBit(object): p = findShape(p) if not path and p != obj.BitShape: obj.BitShape = p + PathLog.debug("ToolBit {} using shape file: {}".format(obj.Label, p)) doc = FreeCAD.openDocument(p, True) obj.ShapeName = doc.Name docOpened = True + else: + PathLog.debug("ToolBit {} already open: {}".format(obj.Label, doc)) return (doc, docOpened) def _removeBitBody(self, obj): @@ -269,7 +275,7 @@ class ToolBit(object): PathLog.track(obj.Label) self._removeBitBody(obj) self._copyBitShape(obj) - for prop in self.propertyNamesBit(obj): + for prop in obj.BitPropertyNames: obj.removeProperty(prop) def loadBitBody(self, obj, force=False): @@ -296,6 +302,10 @@ class ToolBit(object): obj.Label = doc.RootObjects[0].Label self._deleteBitSetup(obj) bitBody = obj.Document.copyObject(doc.RootObjects[0], True) + + for o in doc.RootObjects[0].Group: + PathLog.debug("..... {}: {}".format(o.Label, o.Name)) + if docOpened: FreeCAD.setActiveDocument(activeDoc.Name) FreeCAD.closeDocument(doc.Name) @@ -303,23 +313,35 @@ class ToolBit(object): if bitBody.ViewObject: bitBody.ViewObject.Visibility = False - for sketch in [o for o in bitBody.Group if o.TypeId == 'Sketcher::SketchObject']: - for constraint in [c for c in sketch.Constraints if c.Name != '']: - typ = ParameterTypeConstraint.get(constraint.Type) - PathLog.track(constraint, typ) - if typ is not None: - parts = [p.strip() for p in constraint.Name.split(';')] - prop = parts[0] - desc = '' - if len(parts) > 1: - desc = parts[1] - obj.addProperty(typ, prop, PropertyGroupBit, desc) - obj.setEditorMode(prop, 1) - value = constraint.Value - if constraint.Type == 'Angle': - value = value * 180 / math.pi - PathUtil.setProperty(obj, prop, value) + PathLog.debug("bitBody.{} ({}): {}".format(bitBody.Label, bitBody.Name, type(bitBody))) + + def isAttributes(o): + if not hasattr(o, 'Proxy'): + PathLog.debug(" {} has not Proxy ({})".format(o.Label, type(o))) + return False + if not hasattr(o.Proxy, 'getCustomProperties'): + PathLog.debug(" {}.Proxy has no getCustomProperties ({})".format(o.Label, type(o.Proxy))) + return False + PathLog.debug(" {} <-".format(o.Label)) + return True + + propNames = [] + for attributes in [o for o in bitBody.Group if isAttributes(o)]: + PathLog.debug("Process properties from {}".format(attributes.Label)) + for prop in attributes.Proxy.getCustomProperties(): + # extract property parameters and values so it can be copied + src = attributes.getPropertyByName(prop) + typ = PathPropertyContainer.getPropertyType(src) + grp = attributes.getGroupOfProperty(prop) + dsc = attributes.getDocumentationOfProperty(prop) + + obj.addProperty(typ, prop, grp, dsc) + obj.setEditorMode(prop, 1) + PathUtil.setProperty(obj, prop, src) + propNames.append(prop) + # has to happen last because it could trigger op.execute evaluations + obj.BitPropertyNames = propNames obj.BitBody = bitBody self._copyBitShape(obj) @@ -360,7 +382,7 @@ class ToolBit(object): else: attrs['shape'] = findRelativePathShape(obj.BitShape) params = {} - for name in self.propertyNamesBit(obj): + for name in obj.BitPropertyNames: params[name] = PathUtil.getPropertyValueString(obj, name) attrs['parameter'] = params params = {} @@ -435,6 +457,7 @@ class ToolBitFactory(object): return obj def CreateFrom(self, path, name='ToolBit'): + PathLog.track(name, path) try: data = Declaration(path) bit = Factory.CreateFromAttrs(data, name) @@ -445,6 +468,7 @@ class ToolBitFactory(object): raise def Create(self, name='ToolBit', shapeFile=None): + PathLog.track(name, shapeFile) obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', name) obj.Proxy = ToolBit(obj, shapeFile) return obj diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index 3488bcc95d..b2cc823d76 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -84,32 +84,32 @@ class ToolBitEditor(object): layout = self.form.bitParams.layout() ui = FreeCADGui.UiLoader() - nr = 0 # for all properties either assign them to existing labels and editors # or create additional ones for them if not enough have already been # created. - for name in tool.PropertiesList: - if tool.getGroupOfProperty(name) == PathToolBit.PropertyGroupBit: - if nr < len(self.widgets): - PathLog.debug("re-use row: {} [{}]".format(nr, name)) - label, qsb, editor = self.widgets[nr] - label.setText(labelText(name)) - editor.attachTo(tool, name) - label.show() - qsb.show() - else: - qsb = ui.createWidget('Gui::QuantitySpinBox') - editor = PathGui.QuantitySpinBox(qsb, tool, name) - label = QtGui.QLabel(labelText(name)) - self.widgets.append((label, qsb, editor)) - PathLog.debug("create row: {} [{}]".format(nr, name)) - if nr >= layout.rowCount(): - layout.addRow(label, qsb) - nr = nr + 1 + for nr, name in enumerate(tool.BitPropertyNames): + if nr < len(self.widgets): + PathLog.debug("re-use row: {} [{}]".format(nr, name)) + label, qsb, editor = self.widgets[nr] + label.setText(labelText(name)) + editor.attachTo(tool, name) + label.show() + qsb.show() + else: + qsb = ui.createWidget('Gui::QuantitySpinBox') + editor = PathGui.QuantitySpinBox(qsb, tool, name) + label = QtGui.QLabel(labelText(name)) + self.widgets.append((label, qsb, editor)) + PathLog.debug("create row: {} [{}] {}".format(nr, name, type(qsb))) + if hasattr(qsb, 'editingFinished'): + qsb.editingFinished.connect(self.updateTool) + + if nr >= layout.rowCount(): + layout.addRow(label, qsb) # hide all rows which aren't being used - for i in range(nr, len(self.widgets)): + for i in range(len(tool.BitPropertyNames), len(self.widgets)): label, qsb, editor = self.widgets[i] label.hide() qsb.hide() @@ -242,13 +242,18 @@ class ToolBitEditor(object): def updateTool(self): PathLog.track() - self.tool.Label = str(self.form.toolName.text()) - self.tool.BitShape = str(self.form.shapePath.text()) + + label = str(self.form.toolName.text()) + shape = str(self.form.shapePath.text()) + if self.tool.Label != label: + self.tool.Label = label + if self.tool.BitShape != shape: + self.tool.BitShape = shape for lbl, qsb, editor in self.widgets: editor.updateProperty() - # self.tool.Proxy._updateBitShape(self.tool) + self.tool.Proxy._updateBitShape(self.tool) def refresh(self): PathLog.track() diff --git a/src/Mod/Path/PathScripts/PathToolBitGui.py b/src/Mod/Path/PathScripts/PathToolBitGui.py index 1fe1df5370..c36bb83ec0 100644 --- a/src/Mod/Path/PathScripts/PathToolBitGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitGui.py @@ -174,8 +174,8 @@ class ToolBitGuiFactory(PathToolBit.ToolBitFactory): '''Create(name = 'ToolBit') ... creates a new tool bit. It is assumed the tool will be edited immediately so the internal bit body is still attached.''' - FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', - 'Create ToolBit')) + PathLog.track(name, shapeFile) + FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', 'Create ToolBit')) tool = PathToolBit.ToolBitFactory.Create(self, name, shapeFile) PathIconViewProvider.Attach(tool.ViewObject, name) FreeCAD.ActiveDocument.commitTransaction() diff --git a/src/Mod/Path/Tools/Shape/endmill.fcstd b/src/Mod/Path/Tools/Shape/endmill.fcstd index 98888db1e3f04f93e66ad561a105cd998a066be5..c5be12d7453497eae4253a031fdb551660285db3 100644 GIT binary patch delta 10103 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 delta 9614 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 G`2PSA(J)>B From 6f4df10d4a3f80e8f34f9218972e388ad7294960 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 22 Dec 2020 11:31:43 -0800 Subject: [PATCH 039/168] Renamed PropertyContainer to PropertyBag, PropertyContainer already is a thing. --- src/Mod/Path/CMakeLists.txt | 4 +- src/Mod/Path/Gui/Resources/Path.qrc | 2 +- .../{PropertyContainer.ui => PropertyBag.ui} | 7 +++- .../Gui/Resources/panels/PropertyCreate.ui | 26 +++++++++++-- src/Mod/Path/InitGui.py | 4 +- src/Mod/Path/PathScripts/PathGuiInit.py | 2 +- ...ropertyContainer.py => PathPropertyBag.py} | 14 +++---- ...yContainerGui.py => PathPropertyBagGui.py} | 38 +++++++++---------- src/Mod/Path/PathScripts/PathToolBit.py | 19 ++-------- 9 files changed, 63 insertions(+), 53 deletions(-) rename src/Mod/Path/Gui/Resources/panels/{PropertyContainer.ui => PropertyBag.ui} (91%) rename src/Mod/Path/PathScripts/{PathPropertyContainer.py => PathPropertyBag.py} (94%) rename src/Mod/Path/PathScripts/{PathPropertyContainerGui.py => PathPropertyBagGui.py} (90%) diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 28bc67fa96..8e25c23ef3 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -97,8 +97,8 @@ SET(PathScripts_SRCS PathScripts/PathProfileFacesGui.py PathScripts/PathProfileGui.py PathScripts/PathProperty.py - PathScripts/PathPropertyContainer.py - PathScripts/PathPropertyContainerGui.py + PathScripts/PathPropertyBag.py + PathScripts/PathPropertyBagGui.py PathScripts/PathPropertyEditor.py PathScripts/PathSanity.py PathScripts/PathSelection.py diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index 8300eb42ee..832f74f868 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -121,7 +121,7 @@ panels/PageOpVcarveEdit.ui panels/PathEdit.ui panels/PointEdit.ui - panels/PropertyContainer.ui + panels/PropertyBag.ui panels/PropertyCreate.ui panels/SetupGlobal.ui panels/SetupOp.ui diff --git a/src/Mod/Path/Gui/Resources/panels/PropertyContainer.ui b/src/Mod/Path/Gui/Resources/panels/PropertyBag.ui similarity index 91% rename from src/Mod/Path/Gui/Resources/panels/PropertyContainer.ui rename to src/Mod/Path/Gui/Resources/panels/PropertyBag.ui index 21df9f9cb5..9240477417 100644 --- a/src/Mod/Path/Gui/Resources/panels/PropertyContainer.ui +++ b/src/Mod/Path/Gui/Resources/panels/PropertyBag.ui @@ -11,7 +11,7 @@ - Form + Property Bag @@ -52,6 +52,11 @@ + + table + add + remove + diff --git a/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui b/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui index 13fe6cd1e9..28f62a039d 100644 --- a/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui +++ b/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui @@ -11,7 +11,7 @@ - Dialog + Create Property @@ -22,7 +22,11 @@ - + + + <html><head/><body><p>Name of property.</p></body></html> + + @@ -48,6 +52,9 @@ + + <html><head/><body><p>The category group the property belongs to.</p></body></html> + true @@ -64,17 +71,24 @@ - + + + <html><head/><body><p>The type of the property value.</p></body></html> + + - Info + ToolTip + + <html><head/><body><p>ToolTip to be displayed when user hovers mouse over property.</p></body></html> + true @@ -97,6 +111,9 @@ + + <html><head/><body><p>Check if you want to create several properties in a batch.</p></body></html> + Create another @@ -122,6 +139,7 @@ propertyGroup propertyType propertyInfo + createAnother diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 8e9fb87c94..20e35d8590 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -155,8 +155,8 @@ class PathWorkbench (Workbench): self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], extracmdlist) self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], ["Separator"]) - self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP("Path", "Tools")], - ["Path_PropertyContainer"]) + self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP("Path", "Utils")], + ["Path_PropertyBag"]) self.dressupcmds = dressupcmdlist diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index 29f285d59a..405dcb4fcc 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -66,7 +66,7 @@ def Startup(): # from PathScripts import PathProfileEdgesGui # from PathScripts import PathProfileFacesGui from PathScripts import PathProfileGui - from PathScripts import PathPropertyContainerGui + from PathScripts import PathPropertyBagGui from PathScripts import PathSanity from PathScripts import PathSetupSheetGui from PathScripts import PathSimpleCopy diff --git a/src/Mod/Path/PathScripts/PathPropertyContainer.py b/src/Mod/Path/PathScripts/PathPropertyBag.py similarity index 94% rename from src/Mod/Path/PathScripts/PathPropertyContainer.py rename to src/Mod/Path/PathScripts/PathPropertyBag.py index baf033dfba..01455fa5be 100644 --- a/src/Mod/Path/PathScripts/PathPropertyContainer.py +++ b/src/Mod/Path/PathScripts/PathPropertyBag.py @@ -57,14 +57,14 @@ def getPropertyType(o): if type(o) == FreeCAD.Units.Quantity: return SupportedPropertyType[o.Unit.Type] -class PropertyContainer(object): +class PropertyBag(object): '''Property container object.''' CustomPropertyGroups = 'CustomPropertyGroups' CustomPropertyGroupDefault = 'User' def __init__(self, obj): - obj.addProperty('App::PropertyStringList', self.CustomPropertyGroups, 'Base', PySide.QtCore.QT_TRANSLATE_NOOP('PathPropertyContainer', 'List of custom property groups')) + obj.addProperty('App::PropertyStringList', self.CustomPropertyGroups, 'Base', PySide.QtCore.QT_TRANSLATE_NOOP('PathPropertyBag', 'List of custom property groups')) self.onDocumentRestored(obj) def __getstate__(self): @@ -93,17 +93,17 @@ class PropertyContainer(object): self.obj.CustomPropertyGroups = groups self.obj.addProperty(propertyType, name, group, desc) -def Create(name = 'PropertyContainer'): +def Create(name = 'PropertyBag'): obj = FreeCAD.ActiveDocument.addObject('App::FeaturePython', name) - obj.Proxy = PropertyContainer(obj) + obj.Proxy = PropertyBag(obj) return obj -def IsPropertyContainer(obj): +def IsPropertyBag(obj): '''Returns True if the supplied object is a property container (or its Proxy).''' - if type(obj) == PropertyContainer: + if type(obj) == PropertyBag: return True if hasattr(obj, 'Proxy'): - return IsPropertyContainer(obj.Proxy) + return IsPropertyBag(obj.Proxy) return False diff --git a/src/Mod/Path/PathScripts/PathPropertyContainerGui.py b/src/Mod/Path/PathScripts/PathPropertyBagGui.py similarity index 90% rename from src/Mod/Path/PathScripts/PathPropertyContainerGui.py rename to src/Mod/Path/PathScripts/PathPropertyBagGui.py index d12ee29466..bebdfcfca6 100644 --- a/src/Mod/Path/PathScripts/PathPropertyContainerGui.py +++ b/src/Mod/Path/PathScripts/PathPropertyBagGui.py @@ -25,16 +25,16 @@ import FreeCADGui import PathScripts.PathGui as PathGui import PathScripts.PathIconViewProvider as PathIconViewProvider import PathScripts.PathLog as PathLog -import PathScripts.PathPropertyContainer as PathPropertyContainer +import PathScripts.PathPropertyBag as PathPropertyBag import PathScripts.PathPropertyEditor as PathPropertyEditor import PathScripts.PathUtil as PathUtil from PySide import QtCore, QtGui -__title__ = "Property Container Editor" +__title__ = "Property Bag Editor" __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" -__doc__ = "Task panel editor for a PropertyContainer" +__doc__ = "Task panel editor for a PropertyBag" PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) #PathLog.trackModule(PathLog.thisModule()) @@ -44,7 +44,7 @@ def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) class ViewProvider(object): - '''ViewProvider for a PropertyContainer. + '''ViewProvider for a PropertyBag. It's sole job is to provide an icon and invoke the TaskPanel on edit.''' def __init__(self, vobj, name): @@ -135,9 +135,9 @@ class PropertyCreate(object): if grp: self.form.propertyGroup.setCurrentText(grp) - for t in sorted(PathPropertyContainer.SupportedPropertyType): + for t in sorted(PathPropertyBag.SupportedPropertyType): self.form.propertyType.addItem(t) - if PathPropertyContainer.SupportedPropertyType[t] == typ: + if PathPropertyBag.SupportedPropertyType[t] == typ: typ = t if typ: self.form.propertyType.setCurrentText(typ) @@ -163,7 +163,7 @@ class PropertyCreate(object): def propertyGroup(self): return self.form.propertyGroup.currentText().strip() def propertyType(self): - return PathPropertyContainer.SupportedPropertyType[self.form.propertyType.currentText()].strip() + return PathPropertyBag.SupportedPropertyType[self.form.propertyType.currentText()].strip() def propertyInfo(self): return self.form.propertyInfo.toPlainText().strip() def createAnother(self): @@ -183,12 +183,12 @@ class TaskPanel(object): def __init__(self, vobj): self.obj = vobj.Object self.props = sorted(self.obj.Proxy.getCustomProperties()) - self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyContainer.ui") + self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyBag.ui") # initialized later self.delegate = None self.model = None - FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyContainer", "Edit PropertyContainer")) + FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyBag", "Edit PropertyBag")) def updateData(self, topLeft, bottomRight): if topLeft.column() == self.ColumnDesc: @@ -291,24 +291,24 @@ class TaskPanel(object): self.model.removeRow(row) -def Create(name = 'PropertyContainer'): - '''Create(name = 'PropertyContainer') ... creates a new setup sheet''' - FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyContainer", "Create PropertyContainer")) - pcont = PathPropertyContainer.Create(name) +def Create(name = 'PropertyBag'): + '''Create(name = 'PropertyBag') ... creates a new setup sheet''' + FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyBag", "Create PropertyBag")) + pcont = PathPropertyBag.Create(name) PathIconViewProvider.Attach(pcont.ViewObject, name) return pcont -PathIconViewProvider.RegisterViewProvider('PropertyContainer', ViewProvider) +PathIconViewProvider.RegisterViewProvider('PropertyBag', ViewProvider) -class PropertyContainerCreateCommand(object): +class PropertyBagCreateCommand(object): '''Command to create a property container object''' def __init__(self): pass def GetResources(self): - return {'MenuText': translate('PathPropertyContainer', 'Property Container'), - 'ToolTip': translate('PathPropertyContainer', 'Creates an object which can be used to store reference properties.')} + return {'MenuText': translate('PathPropertyBag', 'Property Bag'), + 'ToolTip': translate('PathPropertyBag', 'Creates an object which can be used to store reference properties.')} def IsActive(self): return not FreeCAD.ActiveDocument is None @@ -329,6 +329,6 @@ class PropertyContainerCreateCommand(object): body.Group = group if FreeCAD.GuiUp: - FreeCADGui.addCommand('Path_PropertyContainer', PropertyContainerCreateCommand()) + FreeCADGui.addCommand('Path_PropertyBag', PropertyBagCreateCommand()) -FreeCAD.Console.PrintLog("Loading PathPropertyContainerGui ... done\n") +FreeCAD.Console.PrintLog("Loading PathPropertyBagGui ... done\n") diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 5e63902c34..ea6b84c499 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -24,7 +24,7 @@ import FreeCAD import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences -import PathScripts.PathPropertyContainer as PathPropertyContainer +import PathScripts.PathPropertyBag as PathPropertyBag import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype import PathScripts.PathUtil as PathUtil import PySide @@ -303,9 +303,6 @@ class ToolBit(object): self._deleteBitSetup(obj) bitBody = obj.Document.copyObject(doc.RootObjects[0], True) - for o in doc.RootObjects[0].Group: - PathLog.debug("..... {}: {}".format(o.Label, o.Name)) - if docOpened: FreeCAD.setActiveDocument(activeDoc.Name) FreeCAD.closeDocument(doc.Name) @@ -315,23 +312,13 @@ class ToolBit(object): PathLog.debug("bitBody.{} ({}): {}".format(bitBody.Label, bitBody.Name, type(bitBody))) - def isAttributes(o): - if not hasattr(o, 'Proxy'): - PathLog.debug(" {} has not Proxy ({})".format(o.Label, type(o))) - return False - if not hasattr(o.Proxy, 'getCustomProperties'): - PathLog.debug(" {}.Proxy has no getCustomProperties ({})".format(o.Label, type(o.Proxy))) - return False - PathLog.debug(" {} <-".format(o.Label)) - return True - propNames = [] - for attributes in [o for o in bitBody.Group if isAttributes(o)]: + for attributes in [o for o in bitBody.Group if PathPropertyBag.IsPropertyBag(o)]: PathLog.debug("Process properties from {}".format(attributes.Label)) for prop in attributes.Proxy.getCustomProperties(): # extract property parameters and values so it can be copied src = attributes.getPropertyByName(prop) - typ = PathPropertyContainer.getPropertyType(src) + typ = PathPropertyBag.getPropertyType(src) grp = attributes.getGroupOfProperty(prop) dsc = attributes.getDocumentationOfProperty(prop) From 3f66b24f7d181163d51e3e5f6e468d2f6134ad1c Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 29 Dec 2020 12:30:18 -0800 Subject: [PATCH 040/168] Renamed 'Bit Parameters' to geometry --- src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui b/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui index 1342da5649..7642c14185 100644 --- a/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui +++ b/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui @@ -6,7 +6,7 @@ 0 0 - 401 + 489 715 @@ -104,7 +104,7 @@ - Bit Parameter + Geometry From b0bce7c2e0d1c23d191e8b271b7abc0abc57cb6a Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 29 Dec 2020 17:41:25 -0800 Subject: [PATCH 041/168] Add last used toolbit path to the end of the list to make using multiple installation possible. --- src/Mod/Path/PathScripts/PathPreferences.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPreferences.py b/src/Mod/Path/PathScripts/PathPreferences.py index d914568631..d8dad1d07c 100644 --- a/src/Mod/Path/PathScripts/PathPreferences.py +++ b/src/Mod/Path/PathScripts/PathPreferences.py @@ -153,6 +153,14 @@ def searchPathsPost(): def searchPathsTool(sub='Bit'): paths = [] + def appendPath(p, sub): + if p: + paths.append(os.path.join(p, 'Tools', sub)) + paths.append(os.path.join(p, sub)) + paths.append(p) + appendPath(defaultFilePath(), sub) + appendPath(macroFilePath(), sub) + appendPath(os.path.join(FreeCAD.getHomePath(), "Mod/Path/"), sub) if 'Bit' == sub: paths.append("{}/Bit".format(os.path.dirname(lastPathToolLibrary()))) @@ -163,14 +171,6 @@ def searchPathsTool(sub='Bit'): if 'Shape' == sub: paths.append(lastPathToolShape()) - def appendPath(p, sub): - if p: - paths.append(os.path.join(p, 'Tools', sub)) - paths.append(os.path.join(p, sub)) - paths.append(p) - appendPath(defaultFilePath(), sub) - appendPath(macroFilePath(), sub) - appendPath(os.path.join(FreeCAD.getHomePath(), "Mod/Path/"), sub) return paths From 79b63bd521dbb5d629fc0433c5c5c6409b4ec68e Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 29 Dec 2020 17:42:33 -0800 Subject: [PATCH 042/168] Add error message if toolbit shape does not contain any extractable properties. --- src/Mod/Path/PathScripts/PathToolBit.py | 3 +++ src/Mod/Path/Tools/Shape/endmill.fcstd | Bin 11351 -> 11428 bytes 2 files changed, 3 insertions(+) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index ea6b84c499..fdee7aeb50 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -303,6 +303,7 @@ class ToolBit(object): self._deleteBitSetup(obj) bitBody = obj.Document.copyObject(doc.RootObjects[0], True) + docName = doc.Name if docOpened: FreeCAD.setActiveDocument(activeDoc.Name) FreeCAD.closeDocument(doc.Name) @@ -326,6 +327,8 @@ class ToolBit(object): obj.setEditorMode(prop, 1) PathUtil.setProperty(obj, prop, src) propNames.append(prop) + if not propNames: + PathLog.error(translate('PathToolBit', 'Did not find a PropertyBag in {} - not a ToolBit shape?').format(docName)) # has to happen last because it could trigger op.execute evaluations obj.BitPropertyNames = propNames diff --git a/src/Mod/Path/Tools/Shape/endmill.fcstd b/src/Mod/Path/Tools/Shape/endmill.fcstd index c5be12d7453497eae4253a031fdb551660285db3..4a292c5f0e241d84d362a906ef1ec7a46ef6781f 100644 GIT binary patch delta 10161 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 Y#6XT!_XmLjkaRLN6%!FB`8T=#FA1$d5C8xG delta 10102 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 VQ>ic!L9(UTGf_fXr6e%D`+tX>_5lC@ From de1a64f090138e2757fdd626e58d6f9f8acfbc6b Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Wed, 30 Dec 2020 21:58:37 -0800 Subject: [PATCH 043/168] Simplified tool bit editor using property-bags --- .../Gui/Resources/panels/ToolBitEditor.ui | 341 ++++++++---------- src/Mod/Path/PathScripts/PathToolBit.py | 138 ++----- src/Mod/Path/PathScripts/PathToolBitEdit.py | 93 +---- src/Mod/Path/PathScripts/PathUtil.py | 5 + src/Mod/Path/Tools/Shape/endmill.fcstd | Bin 11428 -> 11485 bytes 5 files changed, 194 insertions(+), 383 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui b/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui index 7642c14185..5ed0208067 100644 --- a/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui +++ b/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui @@ -13,202 +13,168 @@ Tool Bit Attributes - - - + + + - + 0 0 - - 0 + + Tool Bit - - - Shape - - - - - - Tool Bit + + + + + Name + + + + + + + <html><head/><body><p>Display name of the Tool Bit (initial value taken from the shape file).</p></body></html> + + + 50 + + + Display Name + + + + + + + Shape File + + + + + + + + 0 + 0 + + + + + 0 - - - - - Name - - - - - - - <html><head/><body><p>Display name of the Tool Bit (initial value taken from the shape file).</p></body></html> - - - 50 - - - Display Name - - - - - - - Shape File - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - <html><head/><body><p>The file which defines the type and shape of the Tool Bit.</p></body></html> - - - - - - - <html><head/><body><p>Change file defining type and shape of Tool Bit.</p></body></html> - - - ... - - - - - - - - - - - - - Geometry + + 0 - - - QFormLayout::AllNonFixedFieldsGrow - - - - - Point/Tip Angle - - - - - - - 0 ° - - - ° - - - - - - - Cutting Edge Height - - - - - - - 0 mm - - - mm - - - - - - - - - - - 210 - 297 - + + 0 - - Image + + 0 - - Qt::AlignCenter - - - - - - - Qt::Vertical - - - - 20 - 277 - - - - - - - - - Attributes - - - - - - - 0 - 2 - - - - - 0 - 300 - - - - QAbstractItemView::AllEditTriggers - - - true - - - - - + + + + <html><head/><body><p>The file which defines the type and shape of the Tool Bit.</p></body></html> + + + path + + + + + + + <html><head/><body><p>Change file defining type and shape of Tool Bit.</p></body></html> + + + ... + + + + + + + + + + + Parameter + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Point/Tip Angle + + + + + + + 0 ° + + + ° + + + + + + + Cutting Edge Height + + + + + + + 0 mm + + + mm + + + + + + + + + + + 210 + 297 + + + + Image + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 277 + + + + @@ -218,6 +184,13 @@
Gui/InputField.h
+ + toolName + toolCuttingEdgeAngle + toolCuttingEdgeHeight + shapePath + shapeSet + diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index fdee7aeb50..0445feabab 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -43,7 +43,9 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Class to deal with and represent a tool bit." -_DebugFindTool = True +PropertyGroupShape = 'Shape' + +_DebugFindTool = False PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule() @@ -52,15 +54,6 @@ PathLog.trackModule() def translate(context, text, disambig=None): return PySide.QtCore.QCoreApplication.translate(context, text, disambig) - -ParameterTypeConstraint = { - 'Angle': 'App::PropertyAngle', - 'Distance': 'App::PropertyLength', - 'DistanceX': 'App::PropertyLength', - 'DistanceY': 'App::PropertyLength', - 'Radius': 'App::PropertyLength'} - - def _findTool(path, typ, dbg=_DebugFindTool): PathLog.track(path) if os.path.exists(path): # absolute reference @@ -136,50 +129,16 @@ def findRelativePathTool(path): def findRelativePathLibrary(path): return _findRelativePath(path, 'Library') - -def updateConstraint(sketch, name, value): - for i, constraint in enumerate(sketch.Constraints): - if constraint.Name.split(';')[0] == name: - constr = None - if constraint.Type in ['DistanceX', 'DistanceY', 'Distance', 'Radius', 'Angle']: - constr = Sketcher.Constraint(constraint.Type, constraint.First, constraint.FirstPos, constraint.Second, constraint.SecondPos, value) - else: - print(constraint.Name, constraint.Type) - - if constr is not None: - if not PathGeom.isRoughly(constraint.Value, value.Value): - PathLog.track(name, constraint.Type, - 'update', i, "(%.2f -> %.2f)" - % (constraint.Value, value.Value)) - sketch.delConstraint(i) - sketch.recompute() - n = sketch.addConstraint(constr) - sketch.renameConstraint(n, constraint.Name) - else: - PathLog.track(name, constraint.Type, 'unchanged') - break - - -PropertyGroupBit = 'Bit' -PropertyGroupAttribute = 'Attribute' - - class ToolBit(object): def __init__(self, obj, shapeFile): PathLog.track(obj.Label, shapeFile) self.obj = obj - obj.addProperty('App::PropertyFile', 'BitShape', 'Base', - translate('PathToolBit', 'Shape for bit shape')) - obj.addProperty('App::PropertyLink', 'BitBody', 'Base', - translate('PathToolBit', - 'The parametrized body representing the tool bit')) - obj.addProperty('App::PropertyFile', 'File', 'Base', - translate('PathToolBit', 'The file of the tool')) - obj.addProperty('App::PropertyString', 'ShapeName', 'Base', - translate('PathToolBit', 'The name of the shape file')) - obj.addProperty('App::PropertyStringList', 'BitPropertyNames', 'Base', - translate('PathToolBit', 'List of all properties inherited from the bit')) + obj.addProperty('App::PropertyFile', 'BitShape', 'Base', translate('PathToolBit', 'Shape for bit shape')) + obj.addProperty('App::PropertyLink', 'BitBody', 'Base', translate('PathToolBit', 'The parametrized body representing the tool bit')) + obj.addProperty('App::PropertyFile', 'File', 'Base', translate('PathToolBit', 'The file of the tool')) + obj.addProperty('App::PropertyString', 'ShapeName', 'Base', translate('PathToolBit', 'The name of the shape file')) + obj.addProperty('App::PropertyStringList', 'BitPropertyNames', 'Base', translate('PathToolBit', 'List of all properties inherited from the bit')) if shapeFile is None: obj.BitShape = 'endmill.fcstd' @@ -200,9 +159,6 @@ class ToolBit(object): break return None - def propertyNamesAttribute(self, obj): - return [prop for prop in obj.PropertiesList if obj.getGroupOfProperty(prop) == PropertyGroupAttribute] - def onDocumentRestored(self, obj): # when files are shared it is essential to be able to change/set the shape file, # otherwise the file is hard to use @@ -213,17 +169,19 @@ class ToolBit(object): obj.setEditorMode('BitPropertyNames', 2) for prop in obj.BitPropertyNames: - obj.setEditorMode(prop, 1) - # I currently don't see why these need to be read-only - # for prop in self.propertyNamesAttribute(obj): - # obj.setEditorMode(prop, 1) + if obj.getGroupOfProperty(prop) == PropertyGroupShape: + # properties in the Shape group can only be modified while the actual + # shape is loaded, so we have to disable direct property editing + obj.setEditorMode(prop, 1) + else: + # all other custom properties can and should be edited directly in the + # property editor widget, not much value in re-implementing that + obj.setEditorMode(prop, 0) def onChanged(self, obj, prop): PathLog.track(obj.Label, prop) if prop == 'BitShape' and 'Restore' not in obj.State: self._setupBitShape(obj) - # elif obj.getGroupOfProperty(prop) == PropertyGroupBit: - # self._updateBitShape(obj, [prop]) def onDelete(self, obj, arg2=None): PathLog.track(obj.Label) @@ -335,6 +293,20 @@ class ToolBit(object): obj.BitBody = bitBody self._copyBitShape(obj) + def toolShapeProperties(self, obj): + '''toolShapeProperties(obj) ... return all properties defining the geometry''' + return sorted([prop for prop in obj.BitPropertyNames if obj.getGroupOfProperty(prop) == PropertyGroupShape]) + + def toolGroupsAndProperties(self, obj, includeShape=True): + '''toolGroupsAndProperties(obj) ... returns a dictionary of group names with a list of property names.''' + category = {} + for prop in obj.BitPropertyNames: + group = obj.getGroupOfProperty(prop) + properties = category.get(group, []) + properties.append(prop) + category[group] = properties + return category + def getBitThumbnail(self, obj): if obj.BitShape: path = findShape(obj.BitShape) @@ -359,8 +331,7 @@ class ToolBit(object): obj.File = path return True except (OSError, IOError) as e: - PathLog.error("Could not save tool {} to {} ({})".format( - obj.Label, path, e)) + PathLog.error("Could not save tool {} to {} ({})".format(obj.Label, path, e)) raise def templateAttrs(self, obj): @@ -376,12 +347,6 @@ class ToolBit(object): params[name] = PathUtil.getPropertyValueString(obj, name) attrs['parameter'] = params params = {} - for name in self.propertyNamesAttribute(obj): - if name == "UserAttributes": - for key, value in obj.UserAttributes.items(): - params[key] = value - else: - params[name] = PathUtil.getPropertyValueString(obj, name) attrs['attribute'] = params return attrs @@ -392,32 +357,6 @@ def Declaration(path): return json.load(fp) -class AttributePrototype(PathSetupSheetOpPrototype.OpPrototype): - - def __init__(self): - PathSetupSheetOpPrototype.OpPrototype.__init__(self, 'ToolBitAttribute') - self.addProperty('App::PropertyEnumeration', 'Material', - PropertyGroupAttribute, - translate('PathToolBit', 'Tool bit material')) - self.Material = ['Carbide', 'CastAlloy', 'Ceramics', 'Diamond', - 'HighCarbonToolSteel', 'HighSpeedSteel', 'Sialon'] - self.addProperty('App::PropertyDistance', 'LengthOffset', - PropertyGroupAttribute, translate('PathToolBit', - 'Length offset in Z direction')) - self.addProperty('App::PropertyInteger', 'Flutes', - PropertyGroupAttribute, translate('PathToolBit', - 'The number of flutes')) - self.addProperty('App::PropertyDistance', 'ChipLoad', - PropertyGroupAttribute, translate('PathToolBit', - 'Chipload as per manufacturer')) - self.addProperty('App::PropertyMap', 'UserAttributes', - PropertyGroupAttribute, translate('PathToolBit', - 'User Defined Values')) - self.addProperty('App::PropertyBool', 'SpindlePower', - PropertyGroupAttribute, translate('PathToolBit', - 'Whether Spindle Power should be allowed')) - - class ToolBitFactory(object): def CreateFromAttrs(self, attrs, name='ToolBit'): @@ -429,21 +368,6 @@ class ToolBitFactory(object): PathUtil.setProperty(obj, prop, params[prop]) obj.Proxy._updateBitShape(obj) obj.Proxy.unloadBitBody(obj) - params = attrs['attribute'] - proto = AttributePrototype() - uservals = {} - for pname in params: - try: - prop = proto.getProperty(pname) - prop.setupProperty(obj, pname, PropertyGroupAttribute, prop.valueFromString(params[pname])) - except Exception: - prop = proto.getProperty("UserAttributes") - uservals.update({pname: params[pname]}) - - if len(uservals.items()) > 0: - prop.setupProperty(obj, "UserAttributes", - PropertyGroupAttribute, uservals) - return obj def CreateFrom(self, path, name='ToolBit'): diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index b2cc823d76..5ced9249aa 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -24,7 +24,6 @@ import FreeCADGui import PathScripts.PathGui as PathGui import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences -import PathScripts.PathSetupSheetGui as PathSetupSheetGui import PathScripts.PathToolBit as PathToolBit import os import re @@ -70,7 +69,6 @@ class ToolBitEditor(object): self.widgets = [] self.setupTool(self.tool) - self.setupAttributes(self.tool) def setupTool(self, tool): PathLog.track() @@ -88,7 +86,7 @@ class ToolBitEditor(object): # for all properties either assign them to existing labels and editors # or create additional ones for them if not enough have already been # created. - for nr, name in enumerate(tool.BitPropertyNames): + for nr, name in enumerate(tool.Proxy.toolShapeProperties(tool)): if nr < len(self.widgets): PathLog.debug("re-use row: {} [{}]".format(nr, name)) label, qsb, editor = self.widgets[nr] @@ -122,100 +120,11 @@ class ToolBitEditor(object): else: self.form.image.setPixmap(QtGui.QPixmap()) - def setupAttributes(self, tool): - PathLog.track() - self.proto = PathToolBit.AttributePrototype() - self.props = sorted(self.proto.properties) - self.delegate = PathSetupSheetGui.Delegate(self.form) - self.model = QtGui.QStandardItemModel(len(self.props)-1, 3, self.form) - self.model.setHorizontalHeaderLabels(['Set', 'Property', 'Value']) - - for i, name in enumerate(self.props): - PathLog.debug("propname: %s " % name) - - prop = self.proto.getProperty(name) - isset = hasattr(tool, name) - - if isset: - prop.setValue(getattr(tool, name)) - - if name == "UserAttributes": - continue - - else: - - self.model.setData(self.model.index(i, 0), isset, - QtCore.Qt.EditRole) - self.model.setData(self.model.index(i, 1), name, - QtCore.Qt.EditRole) - self.model.setData(self.model.index(i, 2), prop, - PathSetupSheetGui.Delegate.PropertyRole) - self.model.setData(self.model.index(i, 2), - prop.displayString(), - QtCore.Qt.DisplayRole) - - self.model.item(i, 0).setCheckable(True) - self.model.item(i, 0).setText('') - self.model.item(i, 1).setEditable(False) - self.model.item(i, 1).setToolTip(prop.info) - self.model.item(i, 2).setToolTip(prop.info) - - if isset: - self.model.item(i, 0).setCheckState(QtCore.Qt.Checked) - else: - self.model.item(i, 0).setCheckState(QtCore.Qt.Unchecked) - self.model.item(i, 1).setEnabled(False) - self.model.item(i, 2).setEnabled(False) - - if hasattr(tool, "UserAttributes"): - for key, value in tool.UserAttributes.items(): - PathLog.debug(key, value) - c1 = QtGui.QStandardItem() - c1.setCheckable(False) - c1.setEditable(False) - c1.setCheckState(QtCore.Qt.CheckState.Checked) - - c1.setText('') - c2 = QtGui.QStandardItem(key) - c2.setEditable(False) - c3 = QtGui.QStandardItem(value) - c3.setEditable(False) - - self.model.appendRow([c1, c2, c3]) - - self.form.attrTable.setModel(self.model) - self.form.attrTable.setItemDelegateForColumn(2, self.delegate) - self.form.attrTable.resizeColumnsToContents() - self.form.attrTable.verticalHeader().hide() - - self.model.dataChanged.connect(self.updateData) - - def updateData(self, topLeft, bottomRight): - PathLog.track() - if 0 == topLeft.column(): - isset = self.model.item(topLeft.row(), - 0).checkState() == QtCore.Qt.Checked - self.model.item(topLeft.row(), 1).setEnabled(isset) - self.model.item(topLeft.row(), 2).setEnabled(isset) - def accept(self): PathLog.track() self.refresh() self.tool.Proxy.unloadBitBody(self.tool) - # get the attributes - for i, name in enumerate(self.props): - PathLog.debug('in accept: {}'.format(name)) - prop = self.proto.getProperty(name) - if self.model.item(i, 0) is not None: - enabled = self.model.item(i, 0).checkState() == QtCore.Qt.Checked - if enabled and not prop.getValue() is None: - prop.setupProperty(self.tool, name, - PathToolBit.PropertyGroupAttribute, - prop.getValue()) - elif hasattr(self.tool, name): - self.tool.removeProperty(name) - def reject(self): PathLog.track() self.tool.Proxy.unloadBitBody(self.tool) diff --git a/src/Mod/Path/PathScripts/PathUtil.py b/src/Mod/Path/PathScripts/PathUtil.py index 40e59a78af..7c423af22f 100644 --- a/src/Mod/Path/PathScripts/PathUtil.py +++ b/src/Mod/Path/PathScripts/PathUtil.py @@ -71,6 +71,11 @@ def getPropertyValueString(obj, prop): def setProperty(obj, prop, value): '''setProperty(obj, prop, value) ... set the property value of obj's property defined by its canonical name.''' o, attr, name = _getProperty(obj, prop) # pylint: disable=unused-variable + if attr and type(value) == str: + if type(attr) == int: + value = int(value, 0) + elif type(attr) == bool: + value = value.lower() in ['true', '1', 'yes', 'ok'] if o and name: setattr(o, name, value) diff --git a/src/Mod/Path/Tools/Shape/endmill.fcstd b/src/Mod/Path/Tools/Shape/endmill.fcstd index 4a292c5f0e241d84d362a906ef1ec7a46ef6781f..a40e5fbe5168b3cbda5f37d5ff67a630bc9b9f33 100644 GIT binary patch delta 10275 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 From b41282ebd2ce4dcd74a92787b5517b83bead225f Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 31 Dec 2020 01:02:07 -0800 Subject: [PATCH 044/168] Added tool bit attribute editor back in. --- .../Gui/Resources/panels/ToolBitEditor.ui | 351 ++++++++++-------- src/Mod/Path/PathScripts/PathToolBit.py | 17 +- src/Mod/Path/PathScripts/PathToolBitEdit.py | 120 ++++++ 3 files changed, 326 insertions(+), 162 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui b/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui index 5ed0208067..d77a523a12 100644 --- a/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui +++ b/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui @@ -13,168 +13,214 @@ Tool Bit Attributes - - - + + + 0 0 - - Tool Bit + + 0 - - - - - Name - - - - - - - <html><head/><body><p>Display name of the Tool Bit (initial value taken from the shape file).</p></body></html> - - - 50 - - - Display Name - - - - - - - Shape File - - - - - - - - 0 - 0 - - - - - 0 + + + Shape + + + + + + + 0 + 0 + - - 0 + + Tool Bit - - 0 + + + + + Name + + + + + + + <html><head/><body><p>Display name of the Tool Bit (initial value taken from the shape file).</p></body></html> + + + 50 + + + Display Name + + + + + + + Shape File + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <html><head/><body><p>The file which defines the type and shape of the Tool Bit.</p></body></html> + + + path + + + + + + + <html><head/><body><p>Change file defining type and shape of Tool Bit.</p></body></html> + + + ... + + + + + + + + + + + + + Parameter - - 0 + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Point/Tip Angle + + + + + + + 0 ° + + + ° + + + + + + + Cutting Edge Height + + + + + + + 0 mm + + + mm + + + + + + + + + + + 210 + 297 + - - - - <html><head/><body><p>The file which defines the type and shape of the Tool Bit.</p></body></html> - - - path - - - - - - - <html><head/><body><p>Change file defining type and shape of Tool Bit.</p></body></html> - - - ... - - - - - - - + + Image + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 277 + + + + + + + + + Attributes + + + + + + + 0 + 2 + + + + + 0 + 300 + + + + QAbstractItemView::AllEditTriggers + + + + + - - - - Parameter - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - Point/Tip Angle - - - - - - - 0 ° - - - ° - - - - - - - Cutting Edge Height - - - - - - - 0 mm - - - mm - - - - - - - - - - - 210 - 297 - - - - Image - - - Qt::AlignCenter - - - - - - - Qt::Vertical - - - - 20 - 277 - - - - @@ -184,13 +230,6 @@
Gui/InputField.h
- - toolName - toolCuttingEdgeAngle - toolCuttingEdgeHeight - shapePath - shapeSet - diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 0445feabab..97176b86cd 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -47,8 +47,8 @@ PropertyGroupShape = 'Shape' _DebugFindTool = False -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -PathLog.trackModule() +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +#PathLog.trackModule() def translate(context, text, disambig=None): @@ -294,17 +294,22 @@ class ToolBit(object): self._copyBitShape(obj) def toolShapeProperties(self, obj): - '''toolShapeProperties(obj) ... return all properties defining the geometry''' + '''toolShapeProperties(obj) ... return all properties defining it's shape''' return sorted([prop for prop in obj.BitPropertyNames if obj.getGroupOfProperty(prop) == PropertyGroupShape]) + def toolAdditionalProperties(self, obj): + '''toolShapeProperties(obj) ... return all properties unrelated to it's shape''' + return sorted([prop for prop in obj.BitPropertyNames if obj.getGroupOfProperty(prop) != PropertyGroupShape]) + def toolGroupsAndProperties(self, obj, includeShape=True): '''toolGroupsAndProperties(obj) ... returns a dictionary of group names with a list of property names.''' category = {} for prop in obj.BitPropertyNames: group = obj.getGroupOfProperty(prop) - properties = category.get(group, []) - properties.append(prop) - category[group] = properties + if includeShape or group != PropertyGroupShape: + properties = category.get(group, []) + properties.append(prop) + category[group] = properties return category def getBitThumbnail(self, obj): diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index 5ced9249aa..26ccd068d5 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -20,11 +20,13 @@ # * * # *************************************************************************** +import FreeCAD import FreeCADGui import PathScripts.PathGui as PathGui import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathToolBit as PathToolBit +import PathScripts.PathUtil as PathUtil import os import re @@ -38,6 +40,90 @@ PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) +class _PropertyEditorBase(object): + '''Base class of all typed property editors''' + + def __init__(self, obj, prop): + self.obj = obj + self.prop = prop + def getValue(self): + return getattr(self.obj, self.prop) + def setValue(self, val): + setattr(self.obj, self.prop, val) + +class _PropertyEditorInteger(_PropertyEditorBase): + def widget(self, parent): + return QtGui.QSpinBox(parent) + def setEditorData(self, widget): + widget.setValue(self.getValue()) + def setModelData(self, widget): + self.setValue(widget.value()) + +class _PropertyEditorFloat(_PropertyEditorInteger): + def widget(self, parent): + return QtGui.QDoubleSpinBox(parent) + +class _PropertyEditorBool(_PropertyEditorBase): + def widget(self, parent): + return QtGui.QComboBox(parent) + def setEditorData(self, widget): + widget.clear() + widget.addItems([str(False), str(True)]) + widget.setCurrentIndex(1 if self.getValue() else 0) + def setModelData(self, widget): + self.setValue(widget.currentIndex() == 1) + +class _PropertyEditorString(_PropertyEditorBase): + def widget(self, parent): + return QtGui.QLineEdit(parent) + def setEditorData(self, widget): + widget.setText(self.getValue()) + def setModelData(self, widget): + self.setValue(widget.text()) + +class _PropertyEditorQuantity(_PropertyEditorBase): + def widget(self, parent): + qsb = FreeCADGui.UiLoader().createWidget('Gui::QuantitySpinBox', parent) + self.editor = PathGui.QuantitySpinBox(qsb, self.obj, self.prop) + return qsb + def setEditorData(self, widget): + self.editor.updateSpinBox() + def setModelData(self, widget): + self.editor.updateProperty() + +_PropertyEditorFactory = { + bool : _PropertyEditorBool, + int : _PropertyEditorInteger, + float : _PropertyEditorFloat, + str : _PropertyEditorString, + FreeCAD.Units.Quantity : _PropertyEditorQuantity, + } + +class _Delegate(QtGui.QStyledItemDelegate): + '''Handles the creation of an appropriate editing widget for a given property.''' + ObjectRole = QtCore.Qt.UserRole + 1 + PropertyRole = QtCore.Qt.UserRole + 2 + EditorRole = QtCore.Qt.UserRole + 3 + + def createEditor(self, parent, option, index): + editor = index.data(self.EditorRole) + if editor is None: + obj = index.data(self.ObjectRole) + prp = index.data(self.PropertyRole) + editor = _PropertyEditorFactory[type(getattr(obj, prp))](obj, prp) + index.model().setData(index, editor, self.EditorRole) + return editor.widget(parent) + + def setEditorData(self, widget, index): + # called to update the widget with the current data + index.data(self.EditorRole).setEditorData(widget) + + def setModelData(self, widget, model, index): + # called to update the model with the data from the widget + editor = index.data(self.EditorRole) + editor.setModelData(widget) + index.model().setData(index, PathUtil.getPropertyValueString(editor.obj, editor.prop), QtCore.Qt.DisplayRole) + class ToolBitEditor(object): '''UI and controller for editing a ToolBit. @@ -69,6 +155,7 @@ class ToolBitEditor(object): self.widgets = [] self.setupTool(self.tool) + self.setupAttributes(self.tool) def setupTool(self, tool): PathLog.track() @@ -120,6 +207,39 @@ class ToolBitEditor(object): else: self.form.image.setPixmap(QtGui.QPixmap()) + def setupAttributes(self, tool): + PathLog.track() + + self.delegate = _Delegate(self.form.attrTree) + self.model = QtGui.QStandardItemModel(self.form.attrTree) + self.model.setHorizontalHeaderLabels(['Property', 'Value']) + + attributes = tool.Proxy.toolGroupsAndProperties(tool, False) + for name in attributes: + group = QtGui.QStandardItem() + group.setData(name, QtCore.Qt.EditRole) + group.setEditable(False) + for prop in attributes[name]: + label = QtGui.QStandardItem() + label.setData(prop, QtCore.Qt.EditRole) + label.setEditable(False) + + value = QtGui.QStandardItem() + value.setData(PathUtil.getPropertyValueString(tool, prop), QtCore.Qt.DisplayRole) + value.setData(tool, _Delegate.ObjectRole) + value.setData(prop, _Delegate.PropertyRole) + + group.appendRow([label, value]) + self.model.appendRow(group) + + + self.form.attrTree.setModel(self.model) + self.form.attrTree.setItemDelegateForColumn(1, self.delegate) + self.form.attrTree.expandAll() + self.form.attrTree.resizeColumnToContents(0) + self.form.attrTree.resizeColumnToContents(1) + #self.form.attrTree.collapseAll() + def accept(self): PathLog.track() self.refresh() From ee27fc876f6b9fda37d0c49d41ac72c5c47b2cfe Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 2 Jan 2021 00:05:36 -0800 Subject: [PATCH 045/168] Added support for enumerations to property-bag, relies on base api to get the enum values. --- .../Gui/Resources/panels/PropertyCreate.ui | 31 ++++++++++++++++--- src/Mod/Path/PathScripts/PathPropertyBag.py | 2 +- .../Path/PathScripts/PathPropertyBagGui.py | 23 +++++++++++++- .../Path/PathScripts/PathPropertyEditor.py | 19 ++++++++++-- 4 files changed, 65 insertions(+), 10 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui b/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui index 28f62a039d..e50a671cca 100644 --- a/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui +++ b/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui @@ -6,8 +6,8 @@ 0 0 - 474 - 300 + 484 + 362 @@ -77,14 +77,14 @@
- + ToolTip - + <html><head/><body><p>ToolTip to be displayed when user hovers mouse over property.</p></body></html> @@ -94,7 +94,7 @@ - + @@ -132,12 +132,33 @@ + + + + false + + + Values + + + + + + + false + + + <html><head/><body><p>Comma separated list of enumeration values.</p></body></html> + + + propertyName propertyGroup propertyType + enumValues propertyInfo createAnother diff --git a/src/Mod/Path/PathScripts/PathPropertyBag.py b/src/Mod/Path/PathScripts/PathPropertyBag.py index 01455fa5be..a9cc8d4c00 100644 --- a/src/Mod/Path/PathScripts/PathPropertyBag.py +++ b/src/Mod/Path/PathScripts/PathPropertyBag.py @@ -36,7 +36,7 @@ SupportedPropertyType = { 'Angle' : 'App::PropertyAngle', 'Bool' : 'App::PropertyBool', 'Distance' : 'App::PropertyDistance', - # 'Enumeration' : 'App::PropertyEnumeration', + 'Enumeration' : 'App::PropertyEnumeration', 'File' : 'App::PropertyFile', 'Float' : 'App::PropertyFloat', 'Integer' : 'App::PropertyInteger', diff --git a/src/Mod/Path/PathScripts/PathPropertyBagGui.py b/src/Mod/Path/PathScripts/PathPropertyBagGui.py index bebdfcfca6..948f41d14f 100644 --- a/src/Mod/Path/PathScripts/PathPropertyBagGui.py +++ b/src/Mod/Path/PathScripts/PathPropertyBagGui.py @@ -148,12 +148,26 @@ class PropertyCreate(object): self.form.propertyGroup.currentTextChanged.connect(self.updateUI) self.form.propertyGroup.currentIndexChanged.connect(self.updateUI) self.form.propertyName.textChanged.connect(self.updateUI) + self.form.propertyType.currentIndexChanged.connect(self.updateUI) + self.form.enumValues.textChanged.connect(self.updateUI) self.updateUI() def updateUI(self): + typeSet = True + if self.propertyIsEnumeration(): + self.form.enumLabel.setEnabled(True) + self.form.enumValues.setEnabled(True) + typeSet = self.form.enumValues.text().strip() != '' + else: + self.form.enumLabel.setEnabled(False) + self.form.enumValues.setEnabled(False) + if self.form.enumValues.text().strip(): + self.form.enumValues.setText('') + ok = self.form.buttonBox.button(QtGui.QDialogButtonBox.Ok) - if self.form.propertyName.text() and self.form.propertyGroup.currentText(): + + if typeSet and self.propertyName() and self.propertyGroup(): ok.setEnabled(True) else: ok.setEnabled(False) @@ -168,10 +182,15 @@ class PropertyCreate(object): return self.form.propertyInfo.toPlainText().strip() def createAnother(self): return self.form.createAnother.isChecked() + def propertyEnumerations(self): + return [s.strip() for s in self.form.enumValues.text().strip().split(',')] + def propertyIsEnumeration(self): + return self.propertyType() == 'App::PropertyEnumeration' def exec_(self): self.form.propertyName.setText('') self.form.propertyInfo.setText('') + self.form.enumValues.setText('') #self.form.propertyName.setFocus() return self.form.exec_() @@ -261,6 +280,8 @@ class TaskPanel(object): grp = dialog.propertyGroup() info = dialog.propertyInfo() self.obj.Proxy.addCustomProperty(typ, name, grp, info) + if dialog.propertyIsEnumeration(): + setattr(self.obj, name, dialog.propertyEnumerations()) index = 0 for i in range(self.model.rowCount()): index = i diff --git a/src/Mod/Path/PathScripts/PathPropertyEditor.py b/src/Mod/Path/PathScripts/PathPropertyEditor.py index d45add0658..f376e351fd 100644 --- a/src/Mod/Path/PathScripts/PathPropertyEditor.py +++ b/src/Mod/Path/PathScripts/PathPropertyEditor.py @@ -78,12 +78,12 @@ class _PropertyEditorBool(_PropertyEditor): def setEditorData(self, widget): widget.clear() - widget.addItems(['false', 'true']) + widget.addItems([str(False), str(True)]) index = 1 if self.propertyValue() else 0 widget.setCurrentIndex(index) def setModelData(self, widget): - self.setProperty(widget.currentText() == 'true') + self.setProperty(widget.currentText() == str(True)) class _PropertyEditorString(_PropertyEditor): '''Editor for string values - uses a line edit.''' @@ -190,11 +190,24 @@ class _PropertyEditorFile(_PropertyEditor): def setModelData(self, widget): self.setProperty(widget.text()) +class _PropertyEditorEnumeration(_PropertyEditor): + + def widget(self, parent): + return QtGui.QComboBox(parent) + + def setEditorData(self, widget): + widget.clear() + widget.addItems(self.obj.getEnumerationsOfProperty(self.prop)) + widget.setCurrentText(self.propertyValue()) + + def setModelData(self, widget): + self.setProperty(widget.currentText()) + _EditorFactory = { 'App::PropertyAngle' : _PropertyEditorAngle, 'App::PropertyBool' : _PropertyEditorBool, 'App::PropertyDistance' : _PropertyEditorLength, - #'App::PropertyEnumeration' : _PropertyEditorEnum, + 'App::PropertyEnumeration' : _PropertyEditorEnumeration, #'App::PropertyFile' : _PropertyEditorFile, 'App::PropertyFloat' : _PropertyEditorFloat, 'App::PropertyInteger' : _PropertyEditorInteger, From ac1e863c3e3a32e77450dbcd9053776bb8ae1285 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 2 Jan 2021 00:38:53 -0800 Subject: [PATCH 046/168] Added support for enumerations to the tool bit setup and using the property bag editors instead of re-implementing. --- src/Mod/Path/PathScripts/PathToolBit.py | 36 ++++++++---- src/Mod/Path/PathScripts/PathToolBitEdit.py | 62 +------------------- src/Mod/Path/Tools/Shape/endmill.fcstd | Bin 11485 -> 11592 bytes 3 files changed, 28 insertions(+), 70 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 97176b86cd..4a72dedab2 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -192,7 +192,17 @@ class ToolBit(object): if obj.BitBody is not None: for attributes in [o for o in obj.BitBody.Group if hasattr(o, 'Proxy') and hasattr(o.Proxy, 'getCustomProperties')]: for prop in attributes.Proxy.getCustomProperties(): - setattr(attributes, prop, obj.getPropertyByName(prop)) + # the property might not exist in our local object (new attribute in shape) + # for such attributes we just keep the default + if hasattr(obj, prop): + setattr(attributes, prop, obj.getPropertyByName(prop)) + else: + # if the template shape has a new attribute defined we should add that + # to the local object + self._setupProperty(obj, prop, attributes) + propNames = obj.BitPropertyNames + propNames.append(prop) + obj.BitPropertyNames = propNames self._copyBitShape(obj) def _copyBitShape(self, obj): @@ -251,6 +261,20 @@ class ToolBit(object): def unloadBitBody(self, obj): self._removeBitBody(obj) + def _setupProperty(self, obj, prop, orig): + # extract property parameters and values so it can be copied + val = orig.getPropertyByName(prop) + typ = orig.getTypeIdOfProperty(prop) + grp = orig.getGroupOfProperty(prop) + dsc = orig.getDocumentationOfProperty(prop) + + obj.addProperty(typ, prop, grp, dsc) + if 'App::PropertyEnumeration' == typ: + setattr(obj, prop, orig.getEnumerationsOfProperty(prop)) + + obj.setEditorMode(prop, 1) + PathUtil.setProperty(obj, prop, val) + def _setupBitShape(self, obj, path=None): PathLog.track(obj.Label) @@ -275,15 +299,7 @@ class ToolBit(object): for attributes in [o for o in bitBody.Group if PathPropertyBag.IsPropertyBag(o)]: PathLog.debug("Process properties from {}".format(attributes.Label)) for prop in attributes.Proxy.getCustomProperties(): - # extract property parameters and values so it can be copied - src = attributes.getPropertyByName(prop) - typ = PathPropertyBag.getPropertyType(src) - grp = attributes.getGroupOfProperty(prop) - dsc = attributes.getDocumentationOfProperty(prop) - - obj.addProperty(typ, prop, grp, dsc) - obj.setEditorMode(prop, 1) - PathUtil.setProperty(obj, prop, src) + self._setupProperty(obj, prop, attributes) propNames.append(prop) if not propNames: PathLog.error(translate('PathToolBit', 'Did not find a PropertyBag in {} - not a ToolBit shape?').format(docName)) diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index 26ccd068d5..724f5b65cc 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -25,6 +25,7 @@ import FreeCADGui import PathScripts.PathGui as PathGui import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences +import PathScripts.PathPropertyEditor as PathPropertyEditor import PathScripts.PathToolBit as PathToolBit import PathScripts.PathUtil as PathUtil import os @@ -40,65 +41,6 @@ PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) -class _PropertyEditorBase(object): - '''Base class of all typed property editors''' - - def __init__(self, obj, prop): - self.obj = obj - self.prop = prop - def getValue(self): - return getattr(self.obj, self.prop) - def setValue(self, val): - setattr(self.obj, self.prop, val) - -class _PropertyEditorInteger(_PropertyEditorBase): - def widget(self, parent): - return QtGui.QSpinBox(parent) - def setEditorData(self, widget): - widget.setValue(self.getValue()) - def setModelData(self, widget): - self.setValue(widget.value()) - -class _PropertyEditorFloat(_PropertyEditorInteger): - def widget(self, parent): - return QtGui.QDoubleSpinBox(parent) - -class _PropertyEditorBool(_PropertyEditorBase): - def widget(self, parent): - return QtGui.QComboBox(parent) - def setEditorData(self, widget): - widget.clear() - widget.addItems([str(False), str(True)]) - widget.setCurrentIndex(1 if self.getValue() else 0) - def setModelData(self, widget): - self.setValue(widget.currentIndex() == 1) - -class _PropertyEditorString(_PropertyEditorBase): - def widget(self, parent): - return QtGui.QLineEdit(parent) - def setEditorData(self, widget): - widget.setText(self.getValue()) - def setModelData(self, widget): - self.setValue(widget.text()) - -class _PropertyEditorQuantity(_PropertyEditorBase): - def widget(self, parent): - qsb = FreeCADGui.UiLoader().createWidget('Gui::QuantitySpinBox', parent) - self.editor = PathGui.QuantitySpinBox(qsb, self.obj, self.prop) - return qsb - def setEditorData(self, widget): - self.editor.updateSpinBox() - def setModelData(self, widget): - self.editor.updateProperty() - -_PropertyEditorFactory = { - bool : _PropertyEditorBool, - int : _PropertyEditorInteger, - float : _PropertyEditorFloat, - str : _PropertyEditorString, - FreeCAD.Units.Quantity : _PropertyEditorQuantity, - } - class _Delegate(QtGui.QStyledItemDelegate): '''Handles the creation of an appropriate editing widget for a given property.''' ObjectRole = QtCore.Qt.UserRole + 1 @@ -110,7 +52,7 @@ class _Delegate(QtGui.QStyledItemDelegate): if editor is None: obj = index.data(self.ObjectRole) prp = index.data(self.PropertyRole) - editor = _PropertyEditorFactory[type(getattr(obj, prp))](obj, prp) + editor = PathPropertyEditor.Editor(obj, prp) index.model().setData(index, editor, self.EditorRole) return editor.widget(parent) diff --git a/src/Mod/Path/Tools/Shape/endmill.fcstd b/src/Mod/Path/Tools/Shape/endmill.fcstd index a40e5fbe5168b3cbda5f37d5ff67a630bc9b9f33..60526286e134970ae10f7c04ce4aaaa0a10c4544 100644 GIT binary patch delta 5449 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 P$uh{o%$#i@c255T>Ywf1 delta 5354 zcmaJ_bySq=)}NsT22i@289F2dL|Vj=5kNG#Y|?om42ksaPmM`eA!spSQHc#$ak|ldsq=l*-IQG;9+yBd&kOAJOh!Idvgr8 zB^bypzMYHlFss+IL5G?Gw5`8xC~gRl^W_A*zMVvjJhT`iZ%_JBTr|9};EPl8AO0w# z3ZpJKo*}#TJU)AmCA>0{YeVvn6P&w_xc@Np>scbP*vMlhm-vRjoM&vj3r(8cO?Hcj zcDN#f|9NKn0{HNBiPOoGH8LmzP1EVx7!@B7Q3692%QxZU0em``;TYEK;lmaORhXEn z`9#fG&g6YeqnkHC`{r%XE{d>}Su}$c&8Zyvtv^c`ph_4;Ik_ko_OM09=dyd}nW&m; zY*+b{mNXImyaF$|iA?7M_w0+CfSe&De<8x&_^r*FV{)jcp;2bD^ReNfMhYe9Qddgp zy?JB|Zaigim^Ih9dShaM`E(@!F6|^grY(b9z_V;POc_#slZ^i9j3vna@=Sfl=}TM* z`{sDgQVm~_HazU2h|_qyy!W#+U--Nolcs(Y7tM3km2oGv?9=@pRb(n!1k^petCR?~ z#`1ZVI7OMnd&HkDi2*zGcsr?vInT(><-VES)@$(&Ub)GrXARjmV!FP;lUnHy5cSNg z2Fg5(u}O`oaKUp|1hM_t@3LL>RQ97K2!e2`Bu(5;oBen*yQfjsB?v#EB2K-obgGVj zhPJP~dtJ#l(BW*Uz0=rr9dG`J^->(dfPkS;&WolRP7!&}l3mVd!HD%^`DbqPmJul| zrew}UliV_!K(F-c+`gz}NY>|vTm|5)9Jva8-S6475G}v(sG10(#AJ@6561!wpLtk=!qWc;7Ez zP^ep_m9eaaxbQfbi+R#|SyR%6wFljPe&!+NdHJafVturNEr8y3PiJA3R3*;hQH?aa z10(eF>H?XLY{)ogE$6}lq_-k=41}bdb&6lsVqOc~)8@(U`A+tc4~DJN!7obP?J59? z9YgjH9VAm-K5zdFEw{)=m5!|5})YG)Vh8(zw1zX+M$|oTTE!XIagsQ5JHxdf?(r63% z-_9&U8HCn10;I*OwC?R8-#`@hm8Yw{I=)y8PQ3xcCRp%sAi zC8sC6tcEF2gAv0fp((_n1D}J0|C&aWNd4rXf%-Kz0dYJWIY0jm*-bd3GI{*O0@f{4 z%W<)m(&>}Zq#?6-(`*e5qnB-#DqXT9vOR`Eobl@#9M_ZkMDzxfQZ$FEE!Bc1hC?SI zv+*%iYNzD6jlKHGFm%$aB%QQjw1|+Ajqze@sXY8T5yTQGn2Zosal}=8tACPg93Chh z$dSuDX)a!TD}I{%_AZ-iWqBG~E3m||-MlXJAR`Mw#Z^*uiU!~mRaCZIc7kVRCc1TttRpo%*PdI@}3x9*6@kV!3&5jiVW};xzd`%y3|PnhIA+ z@23i1?+he+rYDh6pCGulbJsl=ulA4Hw^L!x>bZ}Q4@J8erHccbl!1mKC!}fnmxak< z@tSenQG)1Nq67Kr>*=$dg7RBN23rp!mHa>J9QBPA#}y;+=kC_AP7r-fiQPOJN=9cm z68V8@o!x0RUj&}#Rmd$c@jGMQLy^JP{}AY8C3vbWexZGfh1Dxh5U#>wXvW+t89ioq zqabRSzrjC+NM7Rs^2WT8Y#G3 zT0j_n+)RmZ09{^o81kFF&LzxdUY;iynZw8hA;%O4poBh7h2uTDBS~0fZPxhWPOkZ8 zG0rT{M(vE686i7RiHkgBiB)=urNRKZ*u6RAsaUNcRd40eml+nzxVzZft0Yy8pqiDe z22!nCDzQqL*vY4ce)FGVE7Z|5Aa^BmwDtgB00P}$s_-b#ZuW5YqB7;8*6Y)dSL0?)i%AOS4CvRbLw-{({c;clo`f~Dn zi}Pg;>=wRK_NvV;wZVDG!I@GUv5gI#etwTkQcfTG5!EYVccKn=L5)WncwgssBOcV2 z^_*{bSUiohRbBGtp*rM=pBt+2D9^d@MHmcuH4@N5m&Hf|A&tR8v~hutL7^X~KJeS@ z8{+K}cGL*~7;i-zJ@@gU=X~B3-B-+ujO3NO{XG$=UfASn*UCT3f>fP2T#+mkZm{j- z;;z>4o)AWsP8N{8^w9&;PQcjxUonKB+|FqCm*jZV#q^gMvLs5r?fgO zKm6HbgBeA-uk}6E5oFxTo@{-XfLP+y6x=8uh%sWq(WadbXb8rAr{dUq+^!w)JVU}* zsKeuePWM9L#DzPvwf{KQ$U~O@OExx?It{MPnQU7(_(~h{xfTn4TXcsMh@=d)g%yyR z1>X%jaV#kMHU?Zh9RiIcfu@EeKb%aW?tRI~C2ze~GL!Ihpaujr5}qucK@83VXd;rC zYU?g%A={kC~4Bo+=T)uyDeBe zA5Mp)C(S4Ym1BJ`^q8XA7Q4+9+0mYf`c#TR9oVNDbW3?f)wQ-&^3F0n9hH1^-GrH| z(hm|fOg5aK1pwNnQk(@+5HQBYOv7N?)XvJ=L92oFHY&~XW8)8v+t*R7;)Q@lGxfS2 zs0pWiD(LD1XVRcfX>y8M27CkH&*w?i8e?{IHNe%ZoRknP zi8pV)y)NDn=BCf4E3ixFVKc7QR*c^(W^YjIrTbmD8!>Dp?>e9s;H6vR zU7nhaB{namZ0>!`rSi&y4Ft4}6Y*?s+0;2)y&zV#BI zTU**TxZ>M*e)uaWBeHppMFy-B>+v_0oTPsW@Ys zvPaWSZ?KX-N2{WF5ylfpt%mUe-PYVa3oOA%arm= zVzg6DVOuyt9y)6!xGZFF%I3_(B`P?7za%tS+r-Z|O#wQrWRZ6bv)XYSP-u#uj`cVl zQXBSemU7i0RhEn63Khdr5Wldp?vJ*lSe*&G0pu~##NPBlB%REsT#UNhS%!-AxnAS* zxQi;Ybz4Vt$QZuG1)i+E$u|rAy7sskOev8O-4*soj0=9px1BwndNDOzP`ob&+X#Nm ze*gA6y%%3TcGrX1tT+eakN1tzE74`J$N{csWsue%l&LJoAzK@yWj~BQM>+u}2~Mq4iKG*G*Yl71jF`1Y1u(8Zg?39-fHfVG zolPvb=}4|Z&aytpmxgD+r{&j3jopcC=Kui!EUC~}gq2r*m!n1Q`JwURdg%+UMK^gO zlVcw(pwZnukh<{}(w=1Z@u-of>6-sM5{3!u1WISE`~Wv0X1(JB3SxMUCt^+&NKR$q zj1CP(=WW^u*+P~qb${}ghSLZ~bTWM;)S<#Wx^K?86&(#VJDkz~vTVj5T;NkeyDwR8 zgcp^Jz@w(YS&a!AXOWNT-fuVZ-knnHpH>$X%Zlx;2s4KRYGXivf(N0I?_-R&v)bpl z-Muj`;)m1-uAV#E^CXLq9{K)li{&MOc!lK;;RlpJnwbahQbn|4d-KUQG}R)LH*(9$p4)Px#FMRc(ts-@q;@;;fpdiTC8aI{&2-t6NVNs)ek zY+ROM{X6OE>M6E){}s97uAW4_4cwM_o5!|!K5_0A0#K8$vMXIhV1 zK?Kg&oi*CYn>&3rp*J}ohJXY{U(8&$EqA{&6{U}6%Tc3ph=QW5jBY|~V!>xmU zX87CQAWB6u*mvub=ZqEmh7@<@l)k#$L3my7Y*SPf7OCx08c6uW%F~mGL9>wTkGg~2 zECc3l^*AeGS8a_7%hM)V9%?{KV{;GF8_tR6$f;ac;@iIrh#$Eh{jnK--EEVZ z!+w92JXi26H>bIYB_FOt7JlW)D#+eino%tYJb5+qSd;E*DKuD?NkDL|O8C$uz2((a6SGgY<%U!!{LPa6L z7gzv&bDEr&ca()bv9FKY<9mlw?|k&Zz7|vwvvv<2=;!4r9q#YFdvLU(C#Q>&uS_Wl zM9_QlNwvw`KNqSaCwGOA9Uby*uc+L!nKjTGv|^isUR=LkOT|9fO*o_58{*+8@y3~V zXzz};uf(#jy)bNajpmZ4kRs3lPyvT?`hAwe&jh@MYUF3( z_+RY2BIv(YO>iAG>V%)g^q0{;GyAVmChh+rw8Sq$>uLk>Xz>3#LxW6n)tO&a<^0d8 z{MVNO1s7ob9aDk;6J8Drs^qVI|J6Q~7sbZMB=}D}(!%VHYgZsww+TT07ueOiCin(Q zM3DJ69#oJCsE%?JWCj0KpI_#`9?HMD_<^s&hr?XGP;f!I->!i|Oq^v`YBiXHgSV%> zD%=_FA^y)GP(UFjyk#(oRp`$IZVNF1=~2c)?Ee8lKp}qzk@zRbueSZ~u!65#@55Yw ze_Ywa!`kovb-tQV{%2kF?aKKoClswPE8X^$Oa;*Ju(wxLR^w2E+j_g$yL$Z#nqb|+ From eee8d12a17713840ea745938accd62114dfa2c9f Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 2 Jan 2021 19:09:21 -0800 Subject: [PATCH 047/168] Cleaned up property-bag task panel and added support for editing the definition. --- .../Path/Gui/Resources/panels/PropertyBag.ui | 15 ++- .../Gui/Resources/panels/PropertyCreate.ui | 46 +++---- src/Mod/Path/PathScripts/PathPropertyBag.py | 16 +-- .../Path/PathScripts/PathPropertyBagGui.py | 122 +++++++++++++----- 4 files changed, 131 insertions(+), 68 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PropertyBag.ui b/src/Mod/Path/Gui/Resources/panels/PropertyBag.ui index 9240477417..45ae39f764 100644 --- a/src/Mod/Path/Gui/Resources/panels/PropertyBag.ui +++ b/src/Mod/Path/Gui/Resources/panels/PropertyBag.ui @@ -32,6 +32,12 @@ + + + 0 + 0 + + @@ -40,10 +46,17 @@ + + + + Modify... + + + - Add + Add... diff --git a/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui b/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui index e50a671cca..f06eea2054 100644 --- a/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui +++ b/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui @@ -6,8 +6,8 @@ 0 0 - 484 - 362 + 480 + 468 @@ -77,14 +77,24 @@ - + + + + Enums + + + + + + + ToolTip - + <html><head/><body><p>ToolTip to be displayed when user hovers mouse over property.</p></body></html> @@ -94,8 +104,14 @@ - + + + + 0 + 0 + + 0 @@ -132,26 +148,6 @@ - - - - false - - - Values - - - - - - - false - - - <html><head/><body><p>Comma separated list of enumeration values.</p></body></html> - - - diff --git a/src/Mod/Path/PathScripts/PathPropertyBag.py b/src/Mod/Path/PathScripts/PathPropertyBag.py index a9cc8d4c00..4bbb35ba19 100644 --- a/src/Mod/Path/PathScripts/PathPropertyBag.py +++ b/src/Mod/Path/PathScripts/PathPropertyBag.py @@ -45,17 +45,11 @@ SupportedPropertyType = { 'String' : 'App::PropertyString', } -def getPropertyType(o): - if type(o) == str: - return SupportedPropertyType['String'] - if type(o) == bool: - return SupportedPropertyType['Bool'] - if type(o) == int: - return SupportedPropertyType['Integer'] - if type(o) == float: - return SupportedPropertyType['Float'] - if type(o) == FreeCAD.Units.Quantity: - return SupportedPropertyType[o.Unit.Type] +def getPropertyTypeName(o): + for typ in SupportedPropertyType: + if SupportedPropertyType[typ] == o: + return typ + raise IndexError() class PropertyBag(object): '''Property container object.''' diff --git a/src/Mod/Path/PathScripts/PathPropertyBagGui.py b/src/Mod/Path/PathScripts/PathPropertyBagGui.py index 948f41d14f..ca98d94caa 100644 --- a/src/Mod/Path/PathScripts/PathPropertyBagGui.py +++ b/src/Mod/Path/PathScripts/PathPropertyBagGui.py @@ -151,8 +151,6 @@ class PropertyCreate(object): self.form.propertyType.currentIndexChanged.connect(self.updateUI) self.form.enumValues.textChanged.connect(self.updateUI) - self.updateUI() - def updateUI(self): typeSet = True if self.propertyIsEnumeration(): @@ -187,52 +185,76 @@ class PropertyCreate(object): def propertyIsEnumeration(self): return self.propertyType() == 'App::PropertyEnumeration' - def exec_(self): - self.form.propertyName.setText('') - self.form.propertyInfo.setText('') - self.form.enumValues.setText('') - #self.form.propertyName.setFocus() + def exec_(self, name): + if name: + # property exists - this is an edit operation + self.form.propertyName.setText(name) + if self.propertyIsEnumeration(): + self.form.enumValues.setText(','.join(self.obj.getEnumerationsOfProperty(name))) + self.form.propertyInfo.setText(self.obj.getDocumentationOfProperty(name)) + + self.form.propertyName.setEnabled(False) + self.form.propertyType.setEnabled(False) + self.form.createAnother.setEnabled(False) + + else: + self.form.propertyName.setText('') + self.form.propertyInfo.setText('') + self.form.enumValues.setText('') + #self.form.propertyName.setFocus() + + self.updateUI() + return self.form.exec_() +Panel = [] + class TaskPanel(object): ColumnName = 0 + #ColumnType = 1 ColumnVal = 1 - ColumnDesc = 2 + #TableHeaders = ['Property', 'Type', 'Value'] + TableHeaders = ['Property', 'Value'] def __init__(self, vobj): - self.obj = vobj.Object + self.obj = vobj.Object self.props = sorted(self.obj.Proxy.getCustomProperties()) - self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyBag.ui") + self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyBag.ui") # initialized later + self.model = None self.delegate = None - self.model = None FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyBag", "Edit PropertyBag")) + Panel.append(self) def updateData(self, topLeft, bottomRight): - if topLeft.column() == self.ColumnDesc: - obj = topLeft.data(Delegate.RoleObject) - prop = topLeft.data(Delegate.RoleProperty) + pass def _setupProperty(self, i, name): - info = self.obj.getDocumentationOfProperty(name) - value = self.obj.getPropertyByName(name) + typ = PathPropertyBag.getPropertyTypeName(self.obj.getTypeIdOfProperty(name)) + val = PathUtil.getPropertyValueString(self.obj, name) + info = self.obj.getDocumentationOfProperty(name) - self.model.setData(self.model.index(i, self.ColumnName), name, QtCore.Qt.EditRole) - self.model.setData(self.model.index(i, self.ColumnVal), self.obj, Delegate.RoleObject) - self.model.setData(self.model.index(i, self.ColumnVal), name, Delegate.RoleProperty) - self.model.setData(self.model.index(i, self.ColumnVal), str(value), QtCore.Qt.DisplayRole) - self.model.setData(self.model.index(i, self.ColumnDesc), info, QtCore.Qt.EditRole) + self.model.setData(self.model.index(i, self.ColumnName), name, QtCore.Qt.EditRole) + #self.model.setData(self.model.index(i, self.ColumnType), typ, QtCore.Qt.EditRole) + self.model.setData(self.model.index(i, self.ColumnVal), self.obj, Delegate.RoleObject) + self.model.setData(self.model.index(i, self.ColumnVal), name, Delegate.RoleProperty) + self.model.setData(self.model.index(i, self.ColumnVal), val, QtCore.Qt.DisplayRole) + + self.model.setData(self.model.index(i, self.ColumnName), typ, QtCore.Qt.ToolTipRole) + #self.model.setData(self.model.index(i, self.ColumnType), info, QtCore.Qt.ToolTipRole) + self.model.setData(self.model.index(i, self.ColumnVal), info, QtCore.Qt.ToolTipRole) self.model.item(i, self.ColumnName).setEditable(False) + #self.model.item(i, self.ColumnType).setEditable(False) def setupUi(self): PathLog.track() self.delegate = Delegate(self.form) - self.model = QtGui.QStandardItemModel(len(self.props), 3, self.form) - self.model.setHorizontalHeaderLabels(['Property', 'Value', 'Description']) + self.model = QtGui.QStandardItemModel(len(self.props), len(self.TableHeaders), self.form) + self.model.setHorizontalHeaderLabels(self.TableHeaders) for i,name in enumerate(self.props): self._setupProperty(i, name) @@ -245,6 +267,7 @@ class TaskPanel(object): self.form.table.selectionModel().selectionChanged.connect(self.propertySelected) self.form.add.clicked.connect(self.propertyAdd) self.form.remove.clicked.connect(self.propertyRemove) + self.form.modify.clicked.connect(self.propertyModify) self.propertySelected([]) def accept(self): @@ -261,10 +284,22 @@ class TaskPanel(object): def propertySelected(self, selection): PathLog.track() if selection: + self.form.modify.setEnabled(True) self.form.remove.setEnabled(True) else: + self.form.modify.setEnabled(False) self.form.remove.setEnabled(False) + def addCustomProperty(self, obj, dialog): + name = dialog.propertyName() + typ = dialog.propertyType() + grp = dialog.propertyGroup() + info = dialog.propertyInfo() + self.obj.Proxy.addCustomProperty(typ, name, grp, info) + if dialog.propertyIsEnumeration(): + setattr(self.obj, name, dialog.propertyEnumerations()) + return (name, info) + def propertyAdd(self): PathLog.track() more = False @@ -272,16 +307,10 @@ class TaskPanel(object): typ = None while True: dialog = PropertyCreate(self.obj, grp, typ, more) - if dialog.exec_(): + if dialog.exec_(None): # if we block signals the view doesn't get updated, surprise, surprise #self.model.blockSignals(True) - name = dialog.propertyName() - typ = dialog.propertyType() - grp = dialog.propertyGroup() - info = dialog.propertyInfo() - self.obj.Proxy.addCustomProperty(typ, name, grp, info) - if dialog.propertyIsEnumeration(): - setattr(self.obj, name, dialog.propertyEnumerations()) + name, info = self.addCustomProperty(self.obj, dialog) index = 0 for i in range(self.model.rowCount()): index = i @@ -297,6 +326,37 @@ class TaskPanel(object): if not more: break + def propertyModify(self): + PathLog.track() + rows = [] + for index in self.form.table.selectionModel().selectedIndexes(): + row = index.row() + if row in rows: + continue + rows.append(row) + + obj = self.model.item(row, self.ColumnVal).data(Delegate.RoleObject) + nam = self.model.item(row, self.ColumnVal).data(Delegate.RoleProperty) + grp = obj.getGroupOfProperty(nam) + typ = obj.getTypeIdOfProperty(nam) + + dialog = PropertyCreate(self.obj, grp, typ, False) + if dialog.exec_(nam): + val = getattr(obj, nam) + obj.removeProperty(nam) + name, info = self.addCustomProperty(self.obj, dialog) + try: + setattr(obj, nam, val) + except: + # this can happen if the old enumeration value doesn't exist anymore + pass + newVal = PathUtil.getPropertyValueString(obj, nam) + self.model.setData(self.model.index(row, self.ColumnVal), newVal, QtCore.Qt.DisplayRole) + + #self.model.setData(self.model.index(row, self.ColumnType), info, QtCore.Qt.ToolTipRole) + self.model.setData(self.model.index(row, self.ColumnVal), info, QtCore.Qt.ToolTipRole) + + def propertyRemove(self): PathLog.track() # first find all rows which need to be removed From a40569186e109d0ace540f0557006d0e63ec51e9 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 2 Jan 2021 19:22:26 -0800 Subject: [PATCH 048/168] Double click on property name brings up property modify editor. --- .../Path/PathScripts/PathPropertyBagGui.py | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPropertyBagGui.py b/src/Mod/Path/PathScripts/PathPropertyBagGui.py index ca98d94caa..1aed66a0dd 100644 --- a/src/Mod/Path/PathScripts/PathPropertyBagGui.py +++ b/src/Mod/Path/PathScripts/PathPropertyBagGui.py @@ -268,6 +268,7 @@ class TaskPanel(object): self.form.add.clicked.connect(self.propertyAdd) self.form.remove.clicked.connect(self.propertyRemove) self.form.modify.clicked.connect(self.propertyModify) + self.form.table.doubleClicked.connect(self.propertyModifyIndex) self.propertySelected([]) def accept(self): @@ -326,6 +327,31 @@ class TaskPanel(object): if not more: break + def propertyModifyIndex(self, index): + PathLog.track(index.row(), index.column()) + row = index.row() + + obj = self.model.item(row, self.ColumnVal).data(Delegate.RoleObject) + nam = self.model.item(row, self.ColumnVal).data(Delegate.RoleProperty) + grp = obj.getGroupOfProperty(nam) + typ = obj.getTypeIdOfProperty(nam) + + dialog = PropertyCreate(self.obj, grp, typ, False) + if dialog.exec_(nam): + val = getattr(obj, nam) + obj.removeProperty(nam) + name, info = self.addCustomProperty(self.obj, dialog) + try: + setattr(obj, nam, val) + except: + # this can happen if the old enumeration value doesn't exist anymore + pass + newVal = PathUtil.getPropertyValueString(obj, nam) + self.model.setData(self.model.index(row, self.ColumnVal), newVal, QtCore.Qt.DisplayRole) + + #self.model.setData(self.model.index(row, self.ColumnType), info, QtCore.Qt.ToolTipRole) + self.model.setData(self.model.index(row, self.ColumnVal), info, QtCore.Qt.ToolTipRole) + def propertyModify(self): PathLog.track() rows = [] @@ -335,26 +361,7 @@ class TaskPanel(object): continue rows.append(row) - obj = self.model.item(row, self.ColumnVal).data(Delegate.RoleObject) - nam = self.model.item(row, self.ColumnVal).data(Delegate.RoleProperty) - grp = obj.getGroupOfProperty(nam) - typ = obj.getTypeIdOfProperty(nam) - - dialog = PropertyCreate(self.obj, grp, typ, False) - if dialog.exec_(nam): - val = getattr(obj, nam) - obj.removeProperty(nam) - name, info = self.addCustomProperty(self.obj, dialog) - try: - setattr(obj, nam, val) - except: - # this can happen if the old enumeration value doesn't exist anymore - pass - newVal = PathUtil.getPropertyValueString(obj, nam) - self.model.setData(self.model.index(row, self.ColumnVal), newVal, QtCore.Qt.DisplayRole) - - #self.model.setData(self.model.index(row, self.ColumnType), info, QtCore.Qt.ToolTipRole) - self.model.setData(self.model.index(row, self.ColumnVal), info, QtCore.Qt.ToolTipRole) + self.propertyModifyIndex(index) def propertyRemove(self): From 6c5b9ccc4dcb5bccb0ace462219a09320dbc26d2 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 2 Jan 2021 19:53:30 -0800 Subject: [PATCH 049/168] Updated the README.md with the new workflow for creating additional tool bit shapes. --- src/Mod/Path/Tools/README.md | 43 ++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/Mod/Path/Tools/README.md b/src/Mod/Path/Tools/README.md index cdaed5a5b4..af51833aca 100644 --- a/src/Mod/Path/Tools/README.md +++ b/src/Mod/Path/Tools/README.md @@ -1,6 +1,6 @@ # Tools -Each tool is stored as a JSON file which has the template's path and values for all named constraints of the template. +Each tool is stored as a JSON file which has the shape's path and values for all attributes of the shape. It also includes all additional parameters and their values. Storing a tool as a JSON file sounds great but eliminates the option of an accurate thumbnail. On the other hand, @@ -8,10 +8,10 @@ storing each tool as a `*.fcstd` file requires more space and does not allow for extensive tool aresenal they might want to script the generation of tools which is easily done for a `*.json` file but practically impossible for `*.fcstd` files. -When a tool is instantiated in a job the PDN body is created from the template and the constraints are set according -to the values from the JSON file. All additional parameters are created as properties on the object. This provides the -the correct shape and dimensions which can be used to generate a point cloud or mesh for advanced algorithms (and -potentially simulation). +When a tool is instantiated in a job the PDN body is created from the shape and the attributes and constraints are set +according to the values from the JSON file. All additional parameters are created as properties on the object. This +provides the the correct shape and dimensions which can be used to generate a point cloud or mesh for advanced +algorithms (and potentially simulation). # Tool Libraries @@ -55,33 +55,34 @@ TechDraw's templates. ## How to create a new tool 1. Set the tool's Label, this will show up in the object tree -1. Select a tool shape from the existing templates. If your tool doesn't exist, you'll have to create a new template, +1. Select a tool shape from the existing shape files. If your tool doesn't exist, you'll have to create a new shape, see below for details. -1. Each template has its own set of parameters, fill them with the tool's values. +1. Each tool bit shape has its own set of parameters, fill them with the tool's values. 1. Select additional parameters 1. Save the tool under path/file that makes sense to you ## How to create a new tool bit Shape -A tool bit template represents the physical shape of a tool. It does not completely describe the bit - for that some -additional parameters are needed which will be added when an actual bit is parametrized from the template. +The shape file for a tool bit is expected to contain a PD body which represents the tool as a 3d solid. The PD body +should be parametric based on a a PropertyBag object so that, when the properties of the PropertyBag are changed the +solid is updated to the correct representation. 1. Create a new FreeCAD document 1. Open the `PartDesign` workbench, create a body and give the body a label you want to show up in the bit selection. -1. Create a sketch in the XZ plane and draw half the profile of the bit. - * Put the top center of the bit on the origin (0,0) -1. For any constraint serving as a parameter for the tool (like overall Length) create a named constraint - * The name is the label of the input field - * Names are split at CamelCase boundaries into words in the edit dialog - * Use a `;` in the name to add help text which will show up as the entry fields tool tip - * If the tool is used by legacy ops it should at least have one constraint called `Diameter` - * Use construction lines for constraints that are not directly accessible, like `Diameter` and `Angle` -1. Any unnamed constraint will not be editable for a specific tool -1. Once the sketch is fully constrained, close the sketch -1. Rotate the sketch around the z-axis +1. Open the Path workbench and (with the PD body selected) create a PropertyBag, + menu 'Path' -> 'Utils' -> 'Property Bag' + * this creates a PropertyBag object inside the Body (assuming it was selected) + * add properties to which define the tool bit's shape and put those into the group 'Shape' + * add any other properties to the bag which might be useful for the tool bit +1. Construct the body of the tool bit and assign experssions referencing properties from the PropertyBag (in the Shape + Group) for all constraints. + * Position the tip of the tool bit on the origin (0,0) 1. Save the document as a new file in the Shape directory * Before saving the document make sure you have _Save Thumbnail_ selected, and _Add program logo_ deselected in FreeCAD's preferences. * Also make sure to switch to _Front View_ and _Fit content to screen_ - * Whatever you see when saving the document will end up being the visual representation of the template + * Whatever you see when saving the document will end up being the visual representation of tool bits with this shape + +Not that 'Shape' is the only property group which has special meaning for tool bits. All other property groups are +copied verbatim to the ToolBit object when one is created. From eae6c32ce8b19249c1209d062abcb37dec7ba934 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 3 Jan 2021 11:24:16 -0800 Subject: [PATCH 050/168] Load PathGui in all modules which require gui resources to be loaded --- .../PathScripts/PathCircularHoleBaseGui.py | 1 + src/Mod/Path/PathScripts/PathCustomGui.py | 1 + src/Mod/Path/PathScripts/PathDeburrGui.py | 1 + .../PathScripts/PathDressupPathBoundaryGui.py | 1 + src/Mod/Path/PathScripts/PathDressupTagGui.py | 1 + src/Mod/Path/PathScripts/PathDrillingGui.py | 1 + src/Mod/Path/PathScripts/PathEngraveGui.py | 1 + src/Mod/Path/PathScripts/PathHelixGui.py | 1 + src/Mod/Path/PathScripts/PathJobGui.py | 1 + src/Mod/Path/PathScripts/PathOpGui.py | 1 + src/Mod/Path/PathScripts/PathPocketBaseGui.py | 1 + .../Path/PathScripts/PathPocketShapeGui.py | 1 + src/Mod/Path/PathScripts/PathProbeGui.py | 1 + src/Mod/Path/PathScripts/PathProfileGui.py | 1 + .../Path/PathScripts/PathPropertyBagGui.py | 2 +- src/Mod/Path/PathScripts/PathSetupSheetGui.py | 1 + src/Mod/Path/PathScripts/PathSimulatorGui.py | 1 + src/Mod/Path/PathScripts/PathSlotGui.py | 1 + src/Mod/Path/PathScripts/PathSurfaceGui.py | 1 + .../Path/PathScripts/PathThreadMillingGui.py | 1 + .../Path/PathScripts/PathToolBitLibraryGui.py | 28 ++++++++++--------- .../Path/PathScripts/PathToolControllerGui.py | 1 + src/Mod/Path/PathScripts/PathUtilsGui.py | 1 + src/Mod/Path/PathScripts/PathVcarveGui.py | 1 + src/Mod/Path/PathScripts/PathWaterlineGui.py | 1 + 25 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py b/src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py index 85dbb81e0a..c768fd53ab 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py +++ b/src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui diff --git a/src/Mod/Path/PathScripts/PathCustomGui.py b/src/Mod/Path/PathScripts/PathCustomGui.py index 987bcfb50e..658468a4e0 100644 --- a/src/Mod/Path/PathScripts/PathCustomGui.py +++ b/src/Mod/Path/PathScripts/PathCustomGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathCustom as PathCustom import PathScripts.PathOpGui as PathOpGui diff --git a/src/Mod/Path/PathScripts/PathDeburrGui.py b/src/Mod/Path/PathScripts/PathDeburrGui.py index 1b0a0da690..d98642f4dd 100644 --- a/src/Mod/Path/PathScripts/PathDeburrGui.py +++ b/src/Mod/Path/PathScripts/PathDeburrGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathDeburr as PathDeburr import PathScripts.PathGui as PathGui import PathScripts.PathLog as PathLog diff --git a/src/Mod/Path/PathScripts/PathDressupPathBoundaryGui.py b/src/Mod/Path/PathScripts/PathDressupPathBoundaryGui.py index 34cffb1d68..6152c51433 100644 --- a/src/Mod/Path/PathScripts/PathDressupPathBoundaryGui.py +++ b/src/Mod/Path/PathScripts/PathDressupPathBoundaryGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathDressupPathBoundary as PathDressupPathBoundary import PathScripts.PathLog as PathLog diff --git a/src/Mod/Path/PathScripts/PathDressupTagGui.py b/src/Mod/Path/PathScripts/PathDressupTagGui.py index 0a6c01dc6b..ee7c0119a6 100644 --- a/src/Mod/Path/PathScripts/PathDressupTagGui.py +++ b/src/Mod/Path/PathScripts/PathDressupTagGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGeom as PathGeom import PathScripts.PathGetPoint as PathGetPoint import PathScripts.PathDressupHoldingTags as PathDressupTag diff --git a/src/Mod/Path/PathScripts/PathDrillingGui.py b/src/Mod/Path/PathScripts/PathDrillingGui.py index 534d44e1a8..4ebee90d5f 100644 --- a/src/Mod/Path/PathScripts/PathDrillingGui.py +++ b/src/Mod/Path/PathScripts/PathDrillingGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui import PathScripts.PathDrilling as PathDrilling import PathScripts.PathGui as PathGui diff --git a/src/Mod/Path/PathScripts/PathEngraveGui.py b/src/Mod/Path/PathScripts/PathEngraveGui.py index 95d5ce9dc5..6301c4882d 100644 --- a/src/Mod/Path/PathScripts/PathEngraveGui.py +++ b/src/Mod/Path/PathScripts/PathEngraveGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathEngrave as PathEngrave import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui diff --git a/src/Mod/Path/PathScripts/PathHelixGui.py b/src/Mod/Path/PathScripts/PathHelixGui.py index 141cce33d9..b65048ee9c 100644 --- a/src/Mod/Path/PathScripts/PathHelixGui.py +++ b/src/Mod/Path/PathScripts/PathHelixGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui import PathScripts.PathHelix as PathHelix import PathScripts.PathLog as PathLog diff --git a/src/Mod/Path/PathScripts/PathJobGui.py b/src/Mod/Path/PathScripts/PathJobGui.py index f5d2fa9db2..5e78e944df 100644 --- a/src/Mod/Path/PathScripts/PathJobGui.py +++ b/src/Mod/Path/PathScripts/PathJobGui.py @@ -31,6 +31,7 @@ from PySide import QtCore, QtGui import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathJob as PathJob import PathScripts.PathJobCmd as PathJobCmd import PathScripts.PathJobDlg as PathJobDlg diff --git a/src/Mod/Path/PathScripts/PathOpGui.py b/src/Mod/Path/PathScripts/PathOpGui.py index 87e5f9b1e6..0aa304a5fc 100644 --- a/src/Mod/Path/PathScripts/PathOpGui.py +++ b/src/Mod/Path/PathScripts/PathOpGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGeom as PathGeom import PathScripts.PathGetPoint as PathGetPoint import PathScripts.PathGui as PathGui diff --git a/src/Mod/Path/PathScripts/PathPocketBaseGui.py b/src/Mod/Path/PathScripts/PathPocketBaseGui.py index 915b6ea71a..68920b0a5e 100644 --- a/src/Mod/Path/PathScripts/PathPocketBaseGui.py +++ b/src/Mod/Path/PathScripts/PathPocketBaseGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGui as PathGui import PathScripts.PathOpGui as PathOpGui diff --git a/src/Mod/Path/PathScripts/PathPocketShapeGui.py b/src/Mod/Path/PathScripts/PathPocketShapeGui.py index 53bc26c336..4e1b651148 100644 --- a/src/Mod/Path/PathScripts/PathPocketShapeGui.py +++ b/src/Mod/Path/PathScripts/PathPocketShapeGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGeom as PathGeom import PathScripts.PathGui as PathGui import PathScripts.PathLog as PathLog diff --git a/src/Mod/Path/PathScripts/PathProbeGui.py b/src/Mod/Path/PathScripts/PathProbeGui.py index a00ec913e8..29c97d7adc 100644 --- a/src/Mod/Path/PathScripts/PathProbeGui.py +++ b/src/Mod/Path/PathScripts/PathProbeGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathProbe as PathProbe import PathScripts.PathOpGui as PathOpGui import PathScripts.PathGui as PathGui diff --git a/src/Mod/Path/PathScripts/PathProfileGui.py b/src/Mod/Path/PathScripts/PathProfileGui.py index a7f77d7062..8a85a6abfa 100644 --- a/src/Mod/Path/PathScripts/PathProfileGui.py +++ b/src/Mod/Path/PathScripts/PathProfileGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGui as PathGui import PathScripts.PathOpGui as PathOpGui import PathScripts.PathProfile as PathProfile diff --git a/src/Mod/Path/PathScripts/PathPropertyBagGui.py b/src/Mod/Path/PathScripts/PathPropertyBagGui.py index 1aed66a0dd..1167a54a02 100644 --- a/src/Mod/Path/PathScripts/PathPropertyBagGui.py +++ b/src/Mod/Path/PathScripts/PathPropertyBagGui.py @@ -22,7 +22,7 @@ import FreeCAD import FreeCADGui -import PathScripts.PathGui as PathGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathIconViewProvider as PathIconViewProvider import PathScripts.PathLog as PathLog import PathScripts.PathPropertyBag as PathPropertyBag diff --git a/src/Mod/Path/PathScripts/PathSetupSheetGui.py b/src/Mod/Path/PathScripts/PathSetupSheetGui.py index d2ceb3956b..1584b964a0 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheetGui.py +++ b/src/Mod/Path/PathScripts/PathSetupSheetGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGui as PathGui import PathScripts.PathIconViewProvider as PathIconViewProvider import PathScripts.PathLog as PathLog diff --git a/src/Mod/Path/PathScripts/PathSimulatorGui.py b/src/Mod/Path/PathScripts/PathSimulatorGui.py index 086db6b145..3a01944abc 100644 --- a/src/Mod/Path/PathScripts/PathSimulatorGui.py +++ b/src/Mod/Path/PathScripts/PathSimulatorGui.py @@ -22,6 +22,7 @@ import FreeCAD import Path +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathDressup as PathDressup import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog diff --git a/src/Mod/Path/PathScripts/PathSlotGui.py b/src/Mod/Path/PathScripts/PathSlotGui.py index 397ec14fdf..518209e562 100644 --- a/src/Mod/Path/PathScripts/PathSlotGui.py +++ b/src/Mod/Path/PathScripts/PathSlotGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathSlot as PathSlot import PathScripts.PathGui as PathGui import PathScripts.PathOpGui as PathOpGui diff --git a/src/Mod/Path/PathScripts/PathSurfaceGui.py b/src/Mod/Path/PathScripts/PathSurfaceGui.py index 038a670c52..25a5cea8c7 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceGui.py +++ b/src/Mod/Path/PathScripts/PathSurfaceGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathSurface as PathSurface import PathScripts.PathGui as PathGui import PathScripts.PathOpGui as PathOpGui diff --git a/src/Mod/Path/PathScripts/PathThreadMillingGui.py b/src/Mod/Path/PathScripts/PathThreadMillingGui.py index fab21692a7..885f129a6b 100644 --- a/src/Mod/Path/PathScripts/PathThreadMillingGui.py +++ b/src/Mod/Path/PathScripts/PathThreadMillingGui.py @@ -23,6 +23,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui import PathScripts.PathThreadMilling as PathThreadMilling import PathScripts.PathGui as PathGui diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index 33e8fb8e39..7d853e136b 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -24,23 +24,25 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathToolBit as PathToolBit -import PathScripts.PathToolBitGui as PathToolBitGui import PathScripts.PathToolBitEdit as PathToolBitEdit +import PathScripts.PathToolBitGui as PathToolBitGui import PathScripts.PathToolControllerGui as PathToolControllerGui import PathScripts.PathUtilsGui as PathUtilsGui -from PySide import QtCore, QtGui import PySide +import glob import json import os -import glob -import uuid as UUID -from functools import partial import shutil +import uuid as UUID -# PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +from functools import partial + + +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # PathLog.trackModule(PathLog.thisModule()) _UuidRole = PySide.QtCore.Qt.UserRole + 1 @@ -208,10 +210,10 @@ class ModelFactory(object): for libFile in libFiles: loc, fnlong = os.path.split(libFile) fn, ext = os.path.splitext(fnlong) - libItem = QtGui.QStandardItem(fn) + libItem = PySide.QtGui.QStandardItem(fn) libItem.setToolTip(loc) libItem.setData(libFile, _PathRole) - libItem.setIcon(QtGui.QPixmap(':/icons/Path_ToolTable.svg')) + libItem.setIcon(PySide.QtGui.QPixmap(':/icons/Path_ToolTable.svg')) model.appendRow(libItem) PathLog.debug('model rows: {}'.format(model.rowCount())) @@ -340,7 +342,7 @@ class ToolBitSelector(object): def open(self, path=None): ''' load library stored in path and bring up ui''' - docs = FreeCADGui.getMainWindow().findChildren(QtGui.QDockWidget) + docs = FreeCADGui.getMainWindow().findChildren(PySide.QtGui.QDockWidget) for doc in docs: if doc.objectName() == "ToolSelector": if doc.isVisible(): @@ -351,7 +353,7 @@ class ToolBitSelector(object): return mw = FreeCADGui.getMainWindow() - mw.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.form, + mw.addDockWidget(PySide.QtCore.Qt.RightDockWidgetArea, self.form, PySide.QtCore.Qt.Orientation.Vertical) @@ -557,8 +559,8 @@ class ToolBitLibrary(object): self.temptool = PathToolBit.ToolBitFactory().CreateFrom(tbpath, 'temptool') self.editor = PathToolBitEdit.ToolBitEditor(self.temptool, self.form.toolTableGroup, loadBitBody=False) - QBtn = QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel - buttonBox = QtGui.QDialogButtonBox(QBtn) + QBtn = PySide.QtGui.QDialogButtonBox.Ok | PySide.QtGui.QDialogButtonBox.Cancel + buttonBox = PySide.QtGui.QDialogButtonBox(QBtn) buttonBox.accepted.connect(self.accept) buttonBox.rejected.connect(self.reject) @@ -652,7 +654,7 @@ class ToolBitLibrary(object): if curIndex: sm = self.form.TableList.selectionModel() - sm.select(curIndex, QtCore.QItemSelectionModel.Select) + sm.select(curIndex, PySide.QtCore.QItemSelectionModel.Select) self.toolTableView.setUpdatesEnabled(True) self.form.TableList.setUpdatesEnabled(True) diff --git a/src/Mod/Path/PathScripts/PathToolControllerGui.py b/src/Mod/Path/PathScripts/PathToolControllerGui.py index 33e735a08d..905888026e 100644 --- a/src/Mod/Path/PathScripts/PathToolControllerGui.py +++ b/src/Mod/Path/PathScripts/PathToolControllerGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts import PathScripts.PathGui as PathGui import PathScripts.PathLog as PathLog diff --git a/src/Mod/Path/PathScripts/PathUtilsGui.py b/src/Mod/Path/PathScripts/PathUtilsGui.py index da33987017..a5df387eb3 100644 --- a/src/Mod/Path/PathScripts/PathUtilsGui.py +++ b/src/Mod/Path/PathScripts/PathUtilsGui.py @@ -21,6 +21,7 @@ # *************************************************************************** import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts import PathScripts.PathJobCmd as PathJobCmd import PathScripts.PathUtils as PathUtils diff --git a/src/Mod/Path/PathScripts/PathVcarveGui.py b/src/Mod/Path/PathScripts/PathVcarveGui.py index e77dab4b7f..286cce9b28 100644 --- a/src/Mod/Path/PathScripts/PathVcarveGui.py +++ b/src/Mod/Path/PathScripts/PathVcarveGui.py @@ -23,6 +23,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathVcarve as PathVcarve import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui diff --git a/src/Mod/Path/PathScripts/PathWaterlineGui.py b/src/Mod/Path/PathScripts/PathWaterlineGui.py index 07958536fd..089e15256a 100644 --- a/src/Mod/Path/PathScripts/PathWaterlineGui.py +++ b/src/Mod/Path/PathScripts/PathWaterlineGui.py @@ -24,6 +24,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathWaterline as PathWaterline import PathScripts.PathGui as PathGui import PathScripts.PathOpGui as PathOpGui From e9ca3633cef8eed89f8b0788d6ac8670a446e124 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 3 Jan 2021 12:05:58 -0800 Subject: [PATCH 051/168] Added enum placeholder text and disable labels when entry field is disabled. --- .../Gui/Resources/panels/PropertyCreate.ui | 80 ++++++++----------- .../Path/PathScripts/PathPropertyBagGui.py | 24 +++--- 2 files changed, 46 insertions(+), 58 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui b/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui index f06eea2054..c65c02ea82 100644 --- a/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui +++ b/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui @@ -7,7 +7,7 @@ 0 0 480 - 468 + 452 @@ -15,7 +15,7 @@ - + Name @@ -28,45 +28,20 @@ - - - - Group + + + + <html><head/><body><p>The category group the property belongs to.</p></body></html> + + + true - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - <html><head/><body><p>The category group the property belongs to.</p></body></html> - - - true - - - - - - - - + + - Type + Group @@ -77,20 +52,17 @@ - - + + - Enums + Type - - - - - - ToolTip + + + val1,val2,val3,... @@ -104,6 +76,20 @@ + + + + Enums + + + + + + + ToolTip + + + @@ -154,7 +140,7 @@ propertyName propertyGroup propertyType - enumValues + propertyEnum propertyInfo createAnother diff --git a/src/Mod/Path/PathScripts/PathPropertyBagGui.py b/src/Mod/Path/PathScripts/PathPropertyBagGui.py index 1167a54a02..854e030017 100644 --- a/src/Mod/Path/PathScripts/PathPropertyBagGui.py +++ b/src/Mod/Path/PathScripts/PathPropertyBagGui.py @@ -149,19 +149,19 @@ class PropertyCreate(object): self.form.propertyGroup.currentIndexChanged.connect(self.updateUI) self.form.propertyName.textChanged.connect(self.updateUI) self.form.propertyType.currentIndexChanged.connect(self.updateUI) - self.form.enumValues.textChanged.connect(self.updateUI) + self.form.propertyEnum.textChanged.connect(self.updateUI) def updateUI(self): typeSet = True if self.propertyIsEnumeration(): - self.form.enumLabel.setEnabled(True) - self.form.enumValues.setEnabled(True) - typeSet = self.form.enumValues.text().strip() != '' + self.form.labelEnum.setEnabled(True) + self.form.propertyEnum.setEnabled(True) + typeSet = self.form.propertyEnum.text().strip() != '' else: - self.form.enumLabel.setEnabled(False) - self.form.enumValues.setEnabled(False) - if self.form.enumValues.text().strip(): - self.form.enumValues.setText('') + self.form.labelEnum.setEnabled(False) + self.form.propertyEnum.setEnabled(False) + if self.form.propertyEnum.text().strip(): + self.form.propertyEnum.setText('') ok = self.form.buttonBox.button(QtGui.QDialogButtonBox.Ok) @@ -181,7 +181,7 @@ class PropertyCreate(object): def createAnother(self): return self.form.createAnother.isChecked() def propertyEnumerations(self): - return [s.strip() for s in self.form.enumValues.text().strip().split(',')] + return [s.strip() for s in self.form.propertyEnum.text().strip().split(',')] def propertyIsEnumeration(self): return self.propertyType() == 'App::PropertyEnumeration' @@ -190,17 +190,19 @@ class PropertyCreate(object): # property exists - this is an edit operation self.form.propertyName.setText(name) if self.propertyIsEnumeration(): - self.form.enumValues.setText(','.join(self.obj.getEnumerationsOfProperty(name))) + self.form.propertyEnum.setText(','.join(self.obj.getEnumerationsOfProperty(name))) self.form.propertyInfo.setText(self.obj.getDocumentationOfProperty(name)) + self.form.labelName.setEnabled(False) self.form.propertyName.setEnabled(False) + self.form.labelType.setEnabled(False) self.form.propertyType.setEnabled(False) self.form.createAnother.setEnabled(False) else: self.form.propertyName.setText('') self.form.propertyInfo.setText('') - self.form.enumValues.setText('') + self.form.propertyEnum.setText('') #self.form.propertyName.setFocus() self.updateUI() From 7b6cb0f44c54345ecf448f68efeb5e7d8903e8a5 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 3 Jan 2021 15:38:17 -0800 Subject: [PATCH 052/168] Expand task panel according to the data in it. --- src/Mod/Path/Gui/Resources/panels/PropertyBag.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Mod/Path/Gui/Resources/panels/PropertyBag.ui b/src/Mod/Path/Gui/Resources/panels/PropertyBag.ui index 45ae39f764..ac9af5c8d5 100644 --- a/src/Mod/Path/Gui/Resources/panels/PropertyBag.ui +++ b/src/Mod/Path/Gui/Resources/panels/PropertyBag.ui @@ -16,6 +16,9 @@ + + QAbstractScrollArea::AdjustToContents + QAbstractItemView::AllEditTriggers From b0aa6f32fe188e2a9365571c9cace99747dc58c4 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 5 Jan 2021 11:42:58 -0600 Subject: [PATCH 053/168] rework shapes with propertybag --- src/Mod/Path/Tools/Shape/ballend.fcstd | Bin 11631 -> 12746 bytes src/Mod/Path/Tools/Shape/bullnose.fcstd | Bin 12483 -> 13244 bytes src/Mod/Path/Tools/Shape/chamfer.fcstd | Bin 12186 -> 13418 bytes src/Mod/Path/Tools/Shape/drill.fcstd | Bin 10381 -> 11000 bytes src/Mod/Path/Tools/Shape/endmill.fcstd | Bin 11592 -> 11829 bytes src/Mod/Path/Tools/Shape/probe.fcstd | Bin 10824 -> 11600 bytes src/Mod/Path/Tools/Shape/slittingsaw.fcstd | Bin 11644 -> 12544 bytes src/Mod/Path/Tools/Shape/thread-mill.fcstd | Bin 12785 -> 13868 bytes src/Mod/Path/Tools/Shape/v-bit.fcstd | Bin 12725 -> 14034 bytes 9 files changed, 0 insertions(+), 0 deletions(-) diff --git a/src/Mod/Path/Tools/Shape/ballend.fcstd b/src/Mod/Path/Tools/Shape/ballend.fcstd index c3c5ee8b2541914700a5025331b6c5cebf248f86..016a87cb832afa3e22b5b09d0bdeaf92c462eb81 100644 GIT binary patch literal 12746 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 literal 11631 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 Td5{3_oJ>tcg~f>e8M^-u$QDH? diff --git a/src/Mod/Path/Tools/Shape/chamfer.fcstd b/src/Mod/Path/Tools/Shape/chamfer.fcstd index 3470b4a3c6a38af5c18345d398027222a9fb9563..f2d39cff848769c346e8ab5c397fcb4dcbae3ea9 100644 GIT binary patch delta 10229 zcmZvC1ymi&miECR$O-Q5PH=a3cXxLSc7g#zBbZHC35w~W@+4FeMQV9s)%NH`!a8{64@NSs& zlCo}L=gUUATk7Om+k*j!MA-splYCEm&ozUAO)q_SYnL8oUqwI%Og={wj`!9t0rhxtE-O>Plt4L1z%?q4dqnt zcCAoe=WRfAlhKQxL-@mpEvFwhk-04UJ0HFrgVz0LPc)d1eMS!(u`V0d@ehfnUD(u8 zi*2ANjQe|&(+dzO%-MJE>gq_ghe!N~Jjxp*-5^^7D4!kXbyvan$xuFfSwlfO9V zok)Bhv+vpu46hdmfQ7-JZ!Rz_T_#)SyF)t{36OS_z&`6fxB`5Z&`Z&6YB^Oa3)ck2 zPy9P6-T7#DE8e~P5^_m}BaKvCKq+l^`{!eo`oiN3$YQGmzL@y5;cH+|`}{Wn;xIZn0dcf9h! z?IXy;35yYhenZz0Q2}(7c^zkiRm-7H{SF@He5a|KU1z`6y1H9lY83*V;I|1cnjQSH zNp7Nk4NZaW;sAwgKQHs#scj<)Jx3IB(6^d}n%DBF^|{Z-8+KiL0y%&%ldaI>Z`Kq1 z=W6U(J}xHr!E}kHA%=}>GdZt6jCDCyV~S_RZWf`epZPLQ54>!i;cre>X^1N})3+WZ zrRVY9ZMe@#@?UC|lWbW9d$eqox>QdWL!Xe-y77VT&w`q4IL1qq_dXv$sVaVwtczr8 z=O#9_RLgGp1V!cOkXWP8QG4ZwdX?4i=);|p(c^!A1gUq2cLB9RZ14eCS~++j{F=0w zW^^j3PUX??BFPUGOeW(=ZZ+IV3+TbP$@!X${CNQlrTL157)UT@^ZL3hEe=uIs!37r z)Mh~hsL(9XN4^&WSz?~0IVlXog=Q;fHMbKUsfqQBXC_QtSJDUh0-Y88dpeBSlZ4W? zxA=wbU2HC6Bgu8OqSnpSWldDk+RM?uFCHH{i8&STcPruZw7DKGF0VgZbWm58i{1{F zr6>ujXA%)ZU}QREq0xF^X9+H5v0EK;Sb9NL$0#^Xt6;5G$2_qLt7j|&x#L1BwPgx; zY46PuRJBe~VP@;@Bmy1oFYPjsd98!(JM3B9lw5Nxill;!^)73JgJEXW`-WGYLWDre z3|s_5H=0q&C~uO2=VI1J##82+Bh*l|G0oYu1jQ9xZ>Q_D=s+Z>l0dWmmUD#>`cqI< z?NOF14!hle9bzBEd7Z;tp|{rOD0j|yW!?5LcPnR)l!T_2+FuSwe1siec8d_6fkHWv zJIRSjSPsDeX_iAbKw-0^4S4YWGrg_CVRl?{Xaw6%Sc-71O-r&Sy>I5lFo4*6 z!M1V9D2Q0Mt?$U#7syMlwCQ4=XUB@^BHC8HH^XQx)QUCR(%!c$OT z1>;gnV4%Rsct>8#%y7zWOZ|B7DS?fh-9s^!}CTA^OnOOKaO>wrW*V}&4(pYm?|jK{?qxkys%EU=9(y;cyz(}=@2F^$V8 z1%;%ipLW9Yx&I9GZQQd7uUcqzow;TF9oC6oewz>5i3)#}d1dt0Mrvq5f76rQ6K~RCNT&bfD(1ltUrB_DOcunB$`OJ|K8%!BlEI|%uH=J;tO9m4O#|C5 z=rx!w>++;>`*4&9@t?|{vVw7vX0a^9A5WY+@O2lFI~Z_4@oCrA4Ws^(od(%y-ZfS~ zG8iVrS`WH}+%~#HTluU+{dzn13iM45Uw!#l+s7J8E;^{*U-P0&{Oa;+FkBLQ<=1!Urm14*-v+^bctxrIv#Z)I%oc%RbX1FN4qE_NN`Z6|B@5ZD@97b zu2R)*tm`V)8t2-N#2t*xPun|;{LMrKhDLYWKHaq0-*8k<=3OivvU?krEA{g&>la2- zVSmGdt#{6x2d<;(axoHcJ<#7j5(G%~`3bP6LPHgBVAIWbEyyqgxN-@8ju5}hEfH@I zqf+z*d144lSWs0$?|5h$OW`nyVY(`HQ6#{*Q5|(rT!!1yUA#rYTR*?Yz>n+0qEip8 zgcenmL#VcPN@R+$l+&>^*7oG3{(NX`iOm4P7lKvQub`(+$6|vcSu7h-)~|qE=$}gg zrEZcX-NkMI#tw5+ELD#+l0aS}EuTRT232`bA%bXty*ChEHRl`6fy@b0!B(G{O_8^VC=7ZQBm+8+BrfHpu-tr z#=jS1Dta&5BskEa`l@3LzdovmugIoZYH4%o;ZhmlaFH2f>*bFeHegWFRTe}yei=Oh zp)@9EE5b2bJxF%b*J!q4sJ=5S`EHko0@oaZG!iqWHI|!P1_Rb+kVm3ZyjMp{rZpoT z#7Uu2gVwxr4tl4wHMT$m;N%B3ah6D8&)?D z>wc2;+YHSi{aSO@>`_%6;mzW<2x3kP`jaiXvKTs*@Ep_j3PLz?Ep|b{%tFMF0tRWU z(UhlDU1Kay<1Alkc1;`0w3#A*%fbSQu+tGemD<*RM0AF4L9kRzM&2+L99S}lu9%*O z_EC+-eE=l$<{Edl3bwL^6ItIv`W>4fHumagPw7If4xWn(X=*DU zRyjKDeOPgK8PKoYN2}6U1sz-Bhw~~2@^v-@H5IO%9@nX!0Okj5;@1fcv&$rHTSBWA zf@*By@O!vy?iy2d`GHQyJgp#etf!sM(ojitQPRBJFh=<*_x9}SF-SVMUJgkk+lIBJ zQH~d1C&5R;DS`3fBIJ|#;2{!Ad|3(wpmZdqsVuNIA@1RV&5wZkt{dXK^Ad0ZHA428 zsm3l6pzk-Q9|jQCR9e$lN>H%YMRXU>ma&@y+alY7c<6EREyLjqyFif?4B9W8Gg26o zn%NfAa}G{IaQSX&HMcF`Wbfh`0fIW#dgLThR=A(ihknXWD}$3yVoaH-lPJjTu* zCtr)O^=A=R4ZAo=^*|@982Xy@ZQpWFO@TN4@a>!NV{;-0!F(pY`3<-RDr$~g4J2Ri zE3(HatrTL9u8bW^N;kl*%`Idtwi4F-DyaDP>c^MzHDGxk66&)^nW5NjhuNh*UnGa7nemM{fWEI_R0KL-01yzn$pI zsZhYY8~Y`F=6b}nns#=zaJ9?7S^bWsZp)#!Awc~|Vfm(#rqm;NPCVPQ_#hCHBxe(6 zgA*^P3yxk*thmiff%T9oz#9i<%GZ5xe&Sw$H>RB#%tZ3YuqtgaV*bUNIa+#dFDFjA zm&B45{rO<-yc$&J!@J_ykv0#1Lt~<<#9391&qTWmHGZ9}*&{n;!h?yqDvNh1I>h=; zszTfP7=0_=s4m}9T{l$?EpA?%7JE@rN)`L-Z?(X1z2A$F+=ENUaORUNz*k|!ttGdv zP96nX$dqezj?wXJLHb9GB(c@cKT$16s6R%Sjy}N+qh5nthq(If=KSKt&2vqv5PKV8 zrDs{uXJSH+ABf`;63SZLXQM9a9;mbZU(=eSqw;>2m&__}j>tc6&TCs4Uq2+3nioGu z)yZy%qpPMIIT$o)JyjXBHGLf1SZb0|U(KQ@+HsEp4izuXwMl7r{aBbQd6VLDo;KDr zORks`rL_Z@+xaTB5y&m4NY{VhNwTfVva57qo+oKZsE(lb)F|=oUgohs{4CY`Mg+=p zqsg}0C#*}+T6`9h>%jHsanPgDUm5OcwyXd$KN7#2;JDp&9^thmmg>YecN$zf?&v*E z;b+28HE}vqJ6dRwgZQww;$9?9lz&g8LY7!_)GPo63Gz0J&v}~yzIXldb+y*xKwCcI zotFU~tZ~ghI0*71;><)>{8CIqypJ=C$d1ha%?nt(-+7xj)x${0T2AZ=_HR%$nNNN7 zr5z``aYpLfRqyd+q@_nm`)W|eCiguIkIXGr<2*BKc+f4HGtFcc1EwW3WDJY(aS`cK zR;d8Ay2s#cd7f$RH!jq;b$w^_USuoFQ7w_xl%ueA-N=-1%rfgr_Q|)(oRNQ}%GKGd zcZU!!`!ge+lTQV8t#dzY-6n29O^b&#WR92{nn_2+05N_bNFS*!LUPvcoWiDH|nbwLqQjmVT zUmVj+yW70m=@?Z@K;rcQ?AoZd%4g~b`!d)>())6ON?Mh8}x zV0PSJtlsJCJ z6RBvLcZasW-H%qI+3<$G-BiRq4|D~Cqz=-doaeFgoaoTUctDHfe(p|f<-c6TJPUk1 z0$68+sY4v*hFBea_H}VLh@FKva&p4XFn# zBS3np@72LzY9JZtS?8f`)tMrGldo_f_l_F7pAQ;`p#Z-5)7Re327?YcA*eF!l>zCI z;ssy!-#s6f9o;!|h=}tp=Jspbooe$Q@B_Bp;*Ou6+*-1^Jnm}R%g-;Z zMulMDOAdt%PdEo>tvV}cuue;#oOpDKrLp-4CkG-`bA#eQa+B1opQ3@ib39&igQn^p zTh>D~SN0ca8!u5zt)(=;=x>Z4+kcYbvn<8wkdL%~74ZM*r`e%SUjbR%%DGS{quEM`G`C_AE!Z!#00Dsh>rR#uvBaO(d2qAE}AQ759Ld5Qqg0epSBe z=0xmllbb2j<7B56s!NB$62Aikt8BnqKpmQEd>qoi$QKujf0QC%PQKP>Jf|id2|23Y za>`AN_!h#D7;cmRlxX-&87w(p#`THI7~h2nBsSP#%2R}${lk1 zI%zXiill9tgv)#1EDe^!6tE8|pd=_O9{LFf;+|fI7j#mZ7cF+@A+2kJM?|o^;`kP`<{%xFVlxIKAlrX{1R$mgCT;vNk&$TNzqUQ{m$CNBd z1Zc;O=X4-a>7a+Du}_@wGe^8l-5OrMGndH{`P#=!0pG)s06Y_`jf5w#Ky_c&0XBOb zNOpe?&H(}EV@+QEr^Z}Bsl!TJ1Rfe%s^k?z7-hwWI1@Ar&oi8iDmrJ}Pj0G<=g5bh zHGAah(vE)DtwAx=1HmNN0bQRijKdb)$k>3?gv0gF;RuXM*#nv(TqnSPU#U%-7+BD- zf1$Qb_gF(P|MdFsMqogH(GZPpy-& zR)m6H+2&@5&A7Q@!LRo1jqzVTaLUZ5XM;|raxTUE-)^hAcTLdH?xqQ)NAy}F{Ptai zDZ{S?8j-{_zBR<50ng9R$1M)oz83kj(`7k^4AV$(fYBvUA3VZ|eWlwCHw1V;p@)j8 zf_SMp0(QZwm8sJLj(H-wORjbyK;u*L2R?5RLUK;dgYc#&!8PkGd_MY|%pxiJTF~mB zkCc&|iYY#`5{dH<&Uq5&dcQ*#-+UxSU#U6H55Lrtc(d_e|3X6al0m!|C(*m0j%KrQ zi=w5LTti5hk*nD0-Mrck5|KYKJ=8`r;+O=QcDz9jTh}7sRW3-Eidsr`hh#h?7QhZ#+Ip0> z>PxM9GH;|XE6&A7`Tj|Wh~>Qd3HzG>#C>Cup2>-Ll}&4#i-wZrQyTl$|Ew z`y!r=8UXKyYVh;>yb=y0FbFg*eN2>&*b2(uUWsj4a9m&*Xz37vqYyEVV2cu)7+xCG zvanYVts@g8^c5f^do7-KZV|}wE1G2~KqrnrX!%BLtJ{Ox$jqL7oc_p+t!m6+zk0)R zh@7{iVXqqMDxpUY16<3~FEgt6;lSmK&{NRJ27}zSCG>(e4hkD8NC4ecs=P(E=DB!c zM)0sRE6v$!G0VMmtcO}@r%eehLLQnhd*3J=5glkgkEV6I1R~MA-Hk6Q5EIs8CC})f zsV_}|2==ww8?@G8U1d3jqU@PK(cm4_mvKHHs?iYWx(cxCIIgXsm8X zXL@#mdJv*(QG|Uj4nROOH#6-eDiaT0OX3!o5iVPk!t4(M16n+r9DszQL%JAFLpJ_` zVFT;Qn!N;SQiQc7R(N?>$pu;_@r%m-ILOPF$_0W|F+3xSfxvhY{O1I{6L@StHx=I^ z9!Lwgwt0Nw3nc}E<}x6SExbGY_ZWiV2=Q&f0iMt7-WYzh0?2jFw$kZ00V(6`@)B}; z;b`0wf_<5roZ_Hp*zc?WYgoUC0hhcdAn-x3xjZAoaKveKeWj$U2eW}k*CtS zGke9cRd$A0$#~2r(Fj&#OlM10np-60;!VggQytsJVZ~W{K5<`hIRJjBKphm%EnVdZ zL-Eqq=psj4)+EGmHiQ!{Zt7PTaJFw?Ov*7YC0>4}_9qmyp>!~5v2}?&5KOV{WfG+2 zaoVMzt^oD##!@ttI_2U_Ez3G+Uj~l)Dl?F(3>#&i_$oUWUSODqKW|2Igf%rQq7U9G z$K}hIUyRj?#hBos+Gn*J~pwXZUn9>4Z6PLsAM$=pZJBm1%XAteddeTD!bl z&0l+Tf@}$|?2w}RP(?`KuX}MQa5ktMS;C}?hqog!z342`R72Saf%I5X{A_(q6eB2*L&oGeab7!$rHil4^_-~YoT~6UF%b)uXAlUQuz?w18D9Gl zYZ58Uodqo&UD&3F?sjQ#X`*>25Co-X^)5F0Sv%5o0W(=v1-f(mwS6q8ta0;^J`MD$ zNy&g7S1-W~GDz|wrFFg%*{nSAab7^Tx=ERt>XE};ARjxdN^Tu*M4LhJouYcqdHE8b z_bz&5^Kg?v#3Uo&vXc6UsMfVzC^@h02wGt;u)h=K;jtJ?XKdV|E3o^*u4WSR?K%~w@y2|=sWZ#Q;wPxeq-@DId6y~jjf1My-&?$oR ze5}zJD0)-vR&#DPC7XC%dZoS1d(Lv1jt7P&P1oK1{*MA@l<_1FA07a3h6jJ3CILBV zA+fFM5&f2_(j3D7iXYuDqh3+q^;Kl1@Q{S z?Dv`31|u&oUws2#VjqZqqLS>w_&bV@cGj37ua8<$c`7DMe1izjAF0(ey{v~mBct{r@iQ?{L50rkdd_M#RJd>f1W+0`tWCmKyddISaqizC*Hj}hbJ zxq`nm8FaFcbXX*y@t2~<2QfiW+*nIKdP-bpj z0cTIl-mc~$#V~y;4@L3-P0@H#_?<=nd_qnz#TDx7L{uqTeg!iN8G!jkp&kv9&D4?2 z5Y`Uu3g`#$R#hGNz4PV~66UP_R(`hIr)$gjyDq3@+6U(ONrYf&F%RUM;;d)iPaI%- zweVY$C4;r+l06TC^LnwI?`w*hs^KjWXbw-?#ZJeML0`f3_z zkM^#%hTnTVZCyY@ozRof zefQfV`GU=GP~8ei-NjeQiQ9cT%8-IgHpZB&RSvk1=DOp5esuALj_z05*i<17qd;5b z{;p3yk)jsv2%`m=TqgDInn>^rx)2Qt#(BHL6;DKV;aVuf@(TGukXreeq9yh9)u?v) zIufUyihM^_Te)sIaSGqUdiZYGw(mamiyNPz>2vChyUnYzGUwl=HLRXp8v!?oQN{bs zu)EP25Xfggmzu;(3+L?EhI1;hK~y3KtE##Vn$k9o6jwpwj?1U z*Yx>F)hz}Oo!+Kl=klby|H2FrUO4UE@!y`a1(C9nAQ1~Cr}X)L4ov}UlRt2ZAmH_2TWv?ZD)8oNwo@YVt>?! zvI*M#Ny0P`j7h~_U|x>Cc-Fg{a`=qv;f$&nZhu9IK}TMwrj;6&W3gVZ{9ySK{tXj@ zq{?=@!t83Jo*s)uOTBsg{t{GlsmAaSOCjD%V!w@v*fxgvs50|-Mo`v-8?98-838xN zT!J0-$U>GsPCGKOdE?YPmm<=tkWxYJ%F4=p^TG^BPs@$Yw{xPuE}d4YYXcG5Lsn~$ z(>+lkDP8742v;5{^VD!mL<>{pjK@7gnFQZ$$W6-4g=xrusk6gGAmKp|TvSwqskI?M zbOFzXR>u3?npKIM>daVc5Pbgh&5dV}QxTI}s?y^uMow~UkdCkdp84NUK z5v$0jjOm$4QbnFNJIr-=r^}?Ja=7MdGYxt$nbTt&>=qdfSRY9|-3Md~LfLiih76n7 zscCB*Ja25ysqs!Ks2$6d3!N#n;C6kjp$OyP%B6aODLJRKyK4Lq&$=DwDC#nzjj}OB z^dbt7F|bi=ZZ^5v{P)5eyiU*hceM?E&%pLiN0Q;gKb?;ZY>0oA-_z1qQs4##Vk`i( zoSYosKNJKrlHz`_u>3#YePkd4b24K6qppU5M33FV($w?;2>TyOu--U){?zw>4sHPe z@Bdt?E_TkI5-yG|?qcrlW*`5T6cjWT%>Q-73p~ikMD~}ne`9`s_9<@y008xms(&>| z@CG^<$VB*$FDy(1(An5vc_yNNp6!_kKoHzNhRxhPHEhjXt=L)2-Cb1`{@q-9iXMOd zAKmbT007#*6#uzHf&>3h{#%yg|C0Sr4C7zokYG8|zYz^E3^U%}5&X}l4glc&i|ubK zs&rrhW}3g5-j(KmMfyYhD`S5hZu?h*1n04UgP95b2`i780D6`M+{yf3VXiV0Ai=Z# zSAN0$%sBtBO|lS>3;ZEV+F4tBd073O_z(XXJot|=vj delta 8981 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 a2nsWjmOhH(gI>B@S;@;O(Eo3z%l`mVmKRU} diff --git a/src/Mod/Path/Tools/Shape/drill.fcstd b/src/Mod/Path/Tools/Shape/drill.fcstd index aa2a626d028b33ac6d97de7fbb248a978bc6a7b7..5358197072d5722d6019f5c801a563afb5b85e66 100644 GIT binary patch literal 11000 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 literal 10381 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 R0>m7QjfDk82>wak{{znPTVenJ diff --git a/src/Mod/Path/Tools/Shape/endmill.fcstd b/src/Mod/Path/Tools/Shape/endmill.fcstd index 60526286e134970ae10f7c04ce4aaaa0a10c4544..5b5a76dc41bc35e827c9cdbb9bbd0f5dbe86fb57 100644 GIT binary patch literal 11829 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 literal 11592 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%0DlDID_C*84Qu|Gp{QsutUk<#u4)gd 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 diff --git a/src/Mod/Path/Tools/Shape/probe.fcstd b/src/Mod/Path/Tools/Shape/probe.fcstd index 5f853f415b075e98cc37b7f1b6236c98857d4122..41c74b9af08d8b8165bc7dbc4769e680634a2cde 100644 GIT binary patch literal 11600 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 bh`&B#zh))~w7<_q?Tw6t1cV9xW!?V+%kqX* literal 11644 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 V+uJ?JAfooh#-9X*3IDe4{{c#oFkJuu diff --git a/src/Mod/Path/Tools/Shape/thread-mill.fcstd b/src/Mod/Path/Tools/Shape/thread-mill.fcstd index d97e56241f2ea49a4326542f93195f2d0a8c6476..c5252940bdcbcd3458500d7aaf8930188cb17176 100644 GIT binary patch literal 13868 zcmbWe1CV9QwuW1_-DTUh?JlFswyVq4W!tuG+qP}ne!b6)6KC(faZkMWa>b0fVnu#4 z{~3`vMvgI7%1Z%*pa1{>Kmbr`k|>P-Fo)4c0syG00sw&d{j0Ezfs>VywIi*ImF0z& zrbC)In%7j7?gM?DW?O6vbo-+83zCTfsvLJsoMdf7f*?|(IDIhy$<62M2c{`VF_up> zih7KNf`QP_InUSisgpUfE^lw!fGTGM&Q7*x<|Js5@LwfqR4MOZb_3ldCvZ4-CgkILFX~capP>Yx!*i@vT6z6 zKLU_H@`!$LR%WooaCY$^Upx4ADsly5bD>Orq@crc#x5dS1CK%DjB|Jv=1=U=X3g?O z`I_)7`@wyCmU~}B%pIcmX$=q*Z6Tt)Wq3_U)sS`oST11fZT;(pZ(bwbqL&bSTx0`V21( z=R-Ch2VyDvyf3p39^RBKqrmHHJU2mGE=Uy5O^> z9A`}5fY{|A+>)+5Bz3{v;j~q7dtEM99^!l&u5)#7&^>S^L- zSNfvH0MX7-_>;5Ol8>bBqyE&gihs_>pa$`66sWAI*GIBIntr>2Ua~({E$|dk`%ujo zmetKj3=LU#ExmuD|LZY`33mG#gi+GT19=t4r9}mRb5#RvBy*lA8TJ!-rTaNDU7AiUx~PC3a?h$?ho6 zt~}{ubam!T3UBADP~CNcN1I%{vKT7*LnK{Xdjew0A}vJwCb81QHrwA@>xvrAa&~M)E!Nrd7N-wvv$KSJ4YCK04#mXFFeOJ0VSS;tJCPfy%ea-9;@=mLfzq zj_Q+~*{E8{Tui`+hDK~gv}Xh4Yo)k$uFBiF$%;2=Tdjxi+`{vygN|j?$PuHOV_h7 zC&5uhanky3)Ihy*_epX}#aSoFwc*$qWP(V1Bj5klcbjdu{ly!=A!NJV z62zm2kcisjr4Lo1E1m)qSBPA(j8Z|yL1@`GLZqmv8S2a-KLUm7_SE8f z>Qm3XHWr~ziTc7QKA{FEy2+-%6}5h)rpU2;O1V@uAMayIB63UU!g z=FqbFUHV7xd#>m2dfY*GCOaIAnA`6$?ztf#kTOU{14_h-LjAOMpQaz3xgoXIl7S^- zn7axgcR@GYk6JX`Hrt}hgi~7Z_Iz5IX{)5*nQfZfNj8kQF4mjnGG1HFnVP#f+X>Q{ zQkiuww61WOYpCv}>d%1apmWOZv3mD1-_aI2bEpa-wzjpIHXd}J=8Rg^p%#5EF45Kw z4j@@!$#Jn^g2YYKrCAWy)9mS$ToTiyuOJI5myA< zwxWrJmgaePu|r1NlKWzd+ry7)(sG#Llr&A-k=B9rx8_mRuhJ;7q53lb*np`>3MhyC zZ&o%OnXYBgnU$$CX^$op;^5PNHnG#4DUH=8Q;rGz{MXaL!EKyMe%%NEoXB2noqjv9 z*LI6F5IS*^AUrffQO_Icoe8|%lQEjB300gQ=@UOR0dFdn|4!ws!jzR3nFdJ8E7Q&G zx2yyz9!R<}T6J|Pn5Jc=k2}A6&`0{f1D66QQntV`>S{mY-v`aI@p z=U!D6Ec`g@{@|h?^6+Z6>$pq}vAJ6vaSUrG4CyS+4$IMYSl#5YTssim=Pm(dx@X6H z$LM+1{Rm`c+}@^ecrnW4nUNbuWK#7uruU{EbO{=s_IMG2Hdt4&IosjZqTTkr0gt{1 zDya{o>|@2Y0PV;0v5B9Q1Li0M_zM^)X0OhhfRL?8K6Ab@{W=ih0frPJc?5=(Dyuu@ zER#~c&N0+&K?E;1Er~TWT^OyI=Z5@_cg!@{+y+=v1xbMI_*+pPez_~h*Z}V&U|b&> zw@E00{UjUm7=JDqaiU#mS}c5DRdOj6sLQ^ZBqDBvTIg!r_n=4@E&;4-c}f&Z2N{vm zCt+m<=@EYiB?d#GUi3x*?uKP*a?3~}j%0uyd-hofQune>rza)vvH)`1BPK*pi2A5t z1*uyUUHfWp1(0Lkda>P69`mf#1mYOpiHZUs?K;su|X%4J{brtb5{O zfhE5SnSr3REG4~$n(n#?ZABZY5BdJ|Pn%beclq>1!(TDF@VXQh*trc@Hm7uS8P6|H zXBLdB^vOq^m}i!mMhB8*@MXm$SWe~2Z`W8y-)7|~jD8SVakDOVQ9iU^Dbd!%J z4Ruaz^3CsCMQQpuqqO-Aa`{J*L&L&NJ?eI>>SfsXYEfyUq?iIUz!<1#=`Vwsrg}F1 z6e+xx-T<9zm7`_JvE-6jMMtebV4cA58EK*3pU%RHB|+4n$^mD=>sW>aZ*}W_Q$lFJ z4STvfEEMf~k@+*Z2Rg4hdt6aG{w##tJZ@#IfHLB-QE8w%?=a#a(uE^Qx`PT!bOLEf zEUhF&ij|o6*jc^4M9Bm;1vfXvDP=^uls`s(rt$JIuj;aq{?VCBzYY zX-41JdnmHap#xH(3iCHd`w;%GnEEeg5Uw(6uH*Mz(J#`k=uuSmK6S&OuJt|0&j-;u za>2>5lAlpXMBzUkshOHy!wwg_Oy|FL1N?fYo8Jr`9#{a5sn*(}&}fCo;|EJY3aA%! zmO0G9#mcHA5zo9&<=pQJc(4z=>gXClje&^;SmbO@pd=*D^+gUE%Rx;~)M-lvfP{dZ zNn{kTE{s`!sL*g&)wcILShqXZ(M`3g5hip-9AzZTGZA}zY@&@Fi;59kVeTE+Kj9WtPK{o3tTU~GD<1aX6cH2xUbL&U zz+!8Dnzw#!UC63OIc%-cOjxwrI-RiOd;oTyDO*5dDrJO@Z5#O5IT9c%+otfZ+sW>` z!zETdDrmvG=A8KTia}-dM7(!G? zIC&bOjYiPfbN8Q6M!-hd7g-pYn_|Yiv6sMnM52kpE1=XtL-{ z_n3lO`%@QM%E%~UI-$~AaYS;C%b-Pd@X&tEWjLuzptAz)*{?Q6eSjwJ*NPhxA)9eo z`PJ@S1`W5zL3mN+!398^w#=SrLjax<&pp{fGlBUfb|kaR!YE}TSVDkCbp4@CD$m#v z+DY{V^8(vr2F`Nv;|OYNg|SXa&Jo(jrQ*A%><_xRam^~!l5sxmD~&LYt$S_NM3ydV z8g=!c#CK=6Glz#gYvOG$V)l8JK`$o?|9EQU!;@l95BD^2WMc&01Ri&r7)M!8}#_J?R$fbtZ07e zdSMa=3al$8FHe3DBfWF&^t{5*ho5{eccSyi(G?qWSD!S|+vk&)b{B9?vDze2H75)D zp77hcVHWiF#hiNvGKUl}2c_AP}k_Wf^t$ zQ{Yfo_r_@71Ms3*-I>)2^+yNgHt9;0ss5ghuBO3GkDb;zeIq@g3512Ux=g4@PtC1e zU}A9F(c?~mRAdz+hXSU}&hv z7G|-b1y#NK`2B7G7eW8SGHE|h3Xfnug83L5_)w~*Ll-C~B9esEZiye9oVPS>+A!vz z0t*Yv0;Q{%XaVUd9vj<&Sb>ApRD~{tNV)SHhB;e|Rog=wdvjH93eUBOfnCQ%zroJ) zBc;E<0TpUw2r~*dHndF?ipbIXstBRF2Xtn%Pv_y&SBBK*&9fa7 z;cv~_P0g?cSvgO%*qLx(%;kavcTi?J!w!b|jxNeDU{ zLN^i-yEr35E{Y+3;jXdfN|db@fH zl!TGM<{&Bqv&`cqZzTM_?|v5;giudqvxOV8}@FwJ4+jX2}?X}odm4&x7 zu<+{o@o{-#eV6UEwqBlfb08sRl%SRRDVEkTS^VDkk!p;&T6NL~S2@h3EDlE98)C-o zoo=terKM&y>6H%-JGZA>JFD`RD06*!Q?~jkN&Qn+tCQR7YnPYLOqF7te!h(1yoH)m ztP;y?mbRkG+`!}8SgYM;V6h47$b*MhoXi(fNs``)zF8ifjMdrT(DtbMvuw5))Sy49 ziGB)$b^t|q7Gk0ihtW25oSnMrCW{QJrEOtl>g!kcIVQY{ab8LAWqJ0Un!AB7v^5l; z2}-i~4wJ}bsu6PAHzt+jP=7_JXAW3;xtt_374O+bf52^p;prkZi-^bB6~4B~m@-|L zM0+|<`nTwHha4_zNQNgqB6;yFn%2wbU)B2f_SiJv&~%mx*l3wqS$kdv$E3$^2zmni z4}e6%V!8tuK=wS0MlnY!!+mbX78Sz$TtIjQtuPHB@94J$Oi8E0gMje~3VUM4fR;7_ zpqqK}`gIw)scjhTx1)&$B7$L{_C0U=@=K7;>Je&evDgeZq((UVkeD(??_Kck3O`rZznV9=<5ic% z+&(^!`X2T+UUwc3KAqNyL6mi(;Qaa+8mNSDu z+IW)}aI8W5XxUM&h6>e2c6o2bW&-#ExT{Bvr@ex)mF-1N-LbjPlx9#;cvmySmjpDipsusz2x zmdfcglFU}HD5?+6igey3$OunT>>CtZ8=fbqJLT|tsc3f9S>ZG^DhVT^#Nqy;JE z_)Q>dzpnzym!cWX7tqe$2!41zEwa4AC+f35ondMjw=G+M1>&IB$s2_xpxl$~663g) z%B#L@E0LrhFAtUQQy8PmKHET>L$f(Kf{>Bs2V$A{)UYDQymU`T0BmQS8+zw7tl>>T6X>QsMJ8I49ywy9^y-mIHH z`gxCWivcyDFUu34Jp%@CdL_(*GgJaTuXQRWB3Bgy(>PiT#<+|-E@DHHTO_gXgqgF9 z9EzJ}s=#SBYX?$ae1S_S@hD6DHrzNhidz7t>GM$?bUpK}I?82yxryeA0?ZQ0i!-tg z{+;uD4X4*+dFm&L!z&i#2YG+zj2v+EY*su`Cno^y$^wsg?Bn-n1XDZ`kxM}5>sMd7 zQ}9JrLI87KfLnL4j$8=cdmGJ5MQr>*av;ysnc))1GWdw0W_oD*nPyleOa|A#;Vo(^Hlbd)2fL=x^K$<%WCnvlvqCCP55^>uE1ohnYS*MFNc9Ghw`A0-?V0(H$}5?6>oo z#C;D1D>|K*;P=_kNIGDFu09n+I??`k-}eXQhlsb@5F3K@-$2nKPtgmy0p z@CRvLoQ0ryS#3*p_pvwG@SdktZ^O}*o5PjiC<28^njSwG?FH&_*Y@`WzgEh(5+@4N zbI#MRye)AX%iQX?%coW?7T&8rwCLnx%k|w)^*)QddU%FUZ(az4lg23$H3hm|j8P<0 z71RK_t6mB5CXC|8<{t8{vgQwRpe-Z9g+a1U}2DV+ktsO6D|O%3sPu5^!q1J&6m zRHL~NIpn@$ar&_SQo8b++MNG zXn?iKRJ2KstNjr}J1ghaIwm5^tn{TNA-|#c^`$`}k_S8zW-o1)34rJUL`)O7*;UU` z%6#!{m2oHoIbjAY;EV2`)AHe_rV9PH-+!h0V_Gu)+wrfl|I6_=N2mshhv0FdNw}Xj zLbD%dM%2pygV@DH0ew8Z1=@GSvP+AO9O{zEW|P;7+ij+Ov1ByAfN8YR$bhMs<+5 zWiKpi|2F6P$!^rZ+RE7K1)0cN?POt9yLMdIg!5%J6?s+Z?bV=TUH{OW3aGMl-T?E_ z>&ZdZ9AjabWWDQzf9#Dn2?I@45`t~`LzAd+nmWQlNUM#MHK%U!d02mt=Y@lx>UKJU zuTJzNiItVKwGfWfk=AXEJLK|PpwzdtnSyzBC@tggCNH=>aDOAec$N$+sFnhMqaT5b zE0q=0!D|roZT1I=I=@pk?VrkVr+fbLoiidK{{azER&gbL-tLrP z;BX?~8ZZ;i9JHcOp8M3`WVHdZ-$vS)0mh45W8)H{9)IT53rvTj0|L)Tz8s7SO~Z>l zj#LkqL;;HSlnCZEF!*DDn%9TWzgJ%r36`W|Y)%*6t2Xuye{h#$*9X@+P;ia?OW`Pp zLoJ%P@FlxPVPfc)zSt;6-&lS;v}9UZM*r)Dnf3M4;qB7PVJU}zfR+2_$?5&8zNeGd z&lZ~EMRTX0A6A}VE#IImsBcW4SYtXz&%b7S!*geuhAr)s*3l}oQe687u) zf8-@j%!fYemWb?G`8du(>0qaxBc;l~RVTdFCMZcrIrY z^Tt-vuZq+pUH*TES8Qgo?Cc&fQN`BEuGT&saeK#QpzZ?0A1SyPSx?Ff!q?6 zG5{Rwy_D$TGHf-ZQR~PIIkxff7{0D1Jpuj=cI_LRW`t90@zI5;UXlhg;zX$YEPaf6+Alf<26R zs5R~RTLMTz&c}QhWUJOHSi@6KY zFi2H!oG3p1dubWuhlrcVdgVC47vZ|B!N?0;9XV$sB2Jxr1CCVsO~9K*_zp4B65;n| zZq_6h3ZCs+WCsbz3NV$sl%~jJxgqeBqqnN0JSfmy=y9SnEa*#|G0#my;TS)X{cw(_ zeRt(6z~wcv%M-OjTwVAUIxwD&9Oe={6J({;inRHCVgWg(31=_Dt+6e<-J>E=X|k9t z!bL;RC;$mP8xcLcmCu!GHd!qATSeN@&AV2aHINE{q4*PEZFr4v~|M?)~~a788aUzViFZ zIRK^`y-lKyjR?k)v`=h^F=JB4jE)J9D=H93kS5t^$`)Gb>lx|(?b*dEI^k6@iP_L# zIkb(;J}WIb*-p}(mmCvh-EZ1g$pZ?|Je3xP!ZfC3{`BmwbavX9zfI`s@6hQVo}KA` zOsJIHK089^o9c5jbXwv?e-ySDz58ksNGeZLvN{;#3=uBTMAueTdZ5fMqmnL%qmZAV z_)uh7r<;dzBQ*_$~dI*DMivTC(ODZckHhaH~(W!PQfRC#&b$mS{)ELEYY>S~J z^;BnVrr|b8J=sg|$wiNo%h4GkD zMx#JCwA2C~QI0E(QXnQV(q+r8OHW$WJt@l%HyiXrH3?32rFyf&a9){&K#sP65{8q` zlr?yYmoa)Ow~M|Q=da3HgNRH{!&7gC<66JC9Wt@3)}ZjN6)^i}u@X6gLlE4Fns9F- z6k174ymt@IeJR5rloP8g9f zc)O>3dv9oYx9e;%kD8HR%lvY6a`UX|QV+Jm@6ySS=r?i zT`%GJdRy)E&ab5`Py!~%`kFmUr5@I+lB3{KJy69dkH=RAKKn?|(TIPySysq5KIl5b zn_n@?E$UX+$6IuMcwtVE#Az94?GzUUetH$=&T)(tZ{PfQhIw0aD_sbj7qA-&)kH*Kyb1R-*`@~%zRi*a%6JVvQjUW98@TT#1rid2Dd-DF}h6FlF=o!!ao-})z~T00(A!G7Uo z?mVl(bO-UEZLa}7S^}|hdGRjx1hqtVH$|~`0=&y6{ZRAe4L`*7)aXL_ARmmImP-{Y zsxJ=_tvv&#_BkVJwGC^kLnnIQe&>k?eJ6_Y<}hxgWCR#|)7uB}WCc#^;KMru1Lz+D zyRMDr!{{cSCorPPE0Q8FLN$=A+Tn@H0aC67i22gY1c%#*oms;+1PC6j^aIS71W1NY z>5c~mD6fS80KQ&oBQkU>4}^gABZlL}m7AKi)=J7Ue6yG*sdJ>k!kW0lOA&y6w% z^Zp)$()|hg&y8qBz&3s>1OPy$8vp?O@Ayc}$?Tv1hq7|mWkvYR)XAAm*-Q%5z0()( za}pP?T57RiB$(*%XhFp1=P__MjPZAV-Dj$XW$fRHPm^FL-Zv`PSm4#T$ozO^ zXPM#Aqiv{oq^kJg8W7A<9W;;Gq%-k+-28I7v?ON2hmD7qvxPx`obDZF_HMF| zCI+sFD|0<~Mg+oVvgt155B4~|JNA@kg_arXe6o%^DJ8MddEq8>Y2`o) zL6z|wb@Q73JOzc@{@TSQiCdZCDHr7L#4vPBm`z4Jk43Nw{bJY?gx&l6jvXZx>;jM9 z=Ujs1(KXQL=M~?)GaZ4{YUGR8eZOsXBj*Yan}xt#Eu}TofA{nl9gmR3#V<5uCo9)y zorES$bXQp5|0D8cT+7HpJeKWQGc1ll?-)0Y38MtbXgStyXIaX4uxp7s`*O6kAp;&+ z{~=fcEFA_~oX8_hOidE9y)E-D#7gVo3v5=Ae(DPMw$f&!MiT1a$>GugE%YIEifNiwPP9=J0KBWa2z@5tAZHBw+b`uj{X_ ztbW})m#Et`YAk3Js0#{lVov_PST8%T;y4>GWGx;7Im?%?!g82pYgMvm?(RIGyp1r8%}KjL6eW3Dm=AH7=j) z(tff{QNBCMG|})~k=XP>Wc|ew*L!@_;lKlu_jMw56w{linBsAOFS5Zs^(J%GPZ8Ip zt4-PnX;CWO$@s!Bb2n4eo}pxPThP=Mq!EWJuNa;N1t?UJh8E%SU}vp;c|%X>mYx#Ph<5i1yMAU>nD2u9sx`_;}ktU}X>HHD4{tYiRo6v4K-> ztcRrN1vXD82Y%@iQw3G8Z<=z#IIoi}U1ho{vGHd=ro*-kYgq09ypD^K#`1`i-VuP_ z3qb(-`59_Mb8PMfY>^=sa{JlMx!JR+yJ~4-o8=Zll2H2!sJ|vd`qCs-6g?tn z96B6n)ne&G;&&@0rxjRPg2+K49kR2+otDYL0Jhkq=)+xHRHlAuc~JNAssKR}(C?~= z9Nrd?mIhY-Cro;UzO9?%c9tsS-S>&oI9ReskLdERO;m)%B93TUeKI$DS#@Sv)nzfZYq6^O2()p%=ux1HroB(&wX^#Msvq)SMhMXT`P#hpyIMHE zs;e{+ciS*S;g%qJA4T@PfSg;-(Oeo3Ts++{BQiVm9i=PS#xps>2&4oo-v+%PcW!LQ z@bRfduWNfE8a&(;`KvtPhn!CLM}%B!gEekHqK#}1WaTpReI_$-c@DaV26(V^@K7%+ zWn$omI#oTi$}n6(jIU6&lhF3{x6cU9>fA=5v~_;Oc+51tIzv?*5ihxmE)|fp;a|T> zAzftTp1owHQy^&iEN>oEMf9-jeE5_dDm>)DrBrr0zx308E93Z7eC+irtHmVJ$+q@6I*({PcahbY&G(F#zKOLck!&~BnRB>(YwUASfF3!$p4G1f&8&;5QvVqt zwTMTb87FISbagM`FI9+YVfO9`nUF7$U1RrAK|DPyQ`yx28*;YvFdNwfn_3xO?L)hu zxk*1ly6Ee5cr3JPx9ytmS-}#~7g^)Bk>3^7s0+O!*yAYiXfMAOo~7_~<~Fg(wS{Qf z#eo0IwpyAcj)qg+v8QVRYEbXt#6 z1GJzaeFtZL6CBH^SO2*RlR@Be>D@g=^;)APS$eX==H*s;gstf zu@jjKm)nbDlmm3&k8(+xNLgP1Wd-`6<%dnr#bLSbW2P)CPAfaSBGFE7E6=k$B??&( z+cHcDD-lwOG4G4RbS{02Z~FdJgx4@i2eBb!r5Dq1&f|<7v)3{Au~)TukrX!hHkKwk zJyfGLObS0>*dVHI!9p1U#<_TA)UjyFqA~2Q%yge!HzBOWQ-ujMD-ZffQ1OF}Qnc(l zbgLT3s3ZRXfF&%%>_hGMIg5qsyFAmh@2sa0sfb<(VVEB8qA-#5y45i}-G}z|zo>~! zGZ11#VZJ@sE8Cc-elzP?P&?)`^ny!^a2q^P`atC_A0)k;@pcV?=x42iT!g;n#0p{FS{?n#B;Z|Ico5I z?O`2qMFVjIzU@BSz$Sv*Xdn8%!$je12*utXk+*>;h5n3zOw{0;^yBh}E`u}qr{3E; zL9<6TjGg1;FR$h5>^oceCwL0s#+fo)@3ws(PX}#RnO+)(EKOI>d*u~c*e~I{Fx+p= zd?SD|&)uDNYj0qafob9VRN>!XcRR357==A{HxQq^tSOkL#*k~(tB2} z6@6ffhcWlzs(~+m4}Sm{>LS%G{7ZJ%GI@l4&vT6+d+mi^S{*Fg`I}fUs41n-V=9w$ znO;gNCdb9e>V(D9SDJCQ=E(i|-5nfcrso))NoAKc&)54^*Bf)I_Qi6y>NS-|-^H(o z2QhWH`Q)(d3*H;V9tCRGL*9T9|xdWM{^~#nMG>`O) zfMs~Asi0fBrqMxcN_~#tYpUs}UYM*09zD3{^I_m{f-%xyrgIw^mW(Z423H>X?+h-j zuPx;clBPfa|J+kU6=wHC{+?vNx7Gi1z`)Gd*vY|2$i~vf-oOyRm&s)Eccu3qbzqb) zrk{Ux|LgiInIUav{XhK$?Ctej4GeX4pW#9N3-j03UEapb+VNk5{1@o&+Wsmy3;#_U z!~Y>}S`LqJShMB>qtMcL?EstNTBw|0a*||ChYKsQ>Ts{zd@;p#cBi zhev+D*}q!7&tJE{hfeZR|0Mq`_5TkC08sA}{`)Y>KMMbUqJI{#{)1ki{|)_DN$XGc z&q~98SbxmFvHz+&{K@{AV*3v}g!ec0UkSHA**^oz|FEzm|0~S=6aLd&{|COO`M-_# zpHKPwWcur%&%ax}j~@L$Xa1k$pUd$-Wc+XPpY#8}v45?Wf46#{;os~Zi}X+U?~?yo zp8sz3J|BNbX805R`-1!DxC{(`YW}Yj`hU^?x9arY^xsVkA<#iUK;D5&6-Fv)7+YbvAc26WW`KaezkMraZ|q`gYUj-8 zZfkR*Ywa}Cj`}iQeLjRWp2jWZ-IbW^23s_lcFVxT0DoSzO=)K=c@nwZ;NT1N@Qx!} zi8tQ=5Ff(C-b5}_K6hGQKKJvyS|3Sta|&jl>WrWxk!MS$=fcx>^l(|d zz=ZfZXV--+4?gJO%cB>;F^=2&ErNrRsWedw&$uwoJ4K&o>CH68?{I1!bxg&_ie6{sPqlQ5E~6Br67rR(k5 z0Brhc^_KEF*DWX?-N1pqA}ZPMK;#`o1~uiya=>>p*NA_i6Mrg^#` zCwv{EwUoa3ZC58FzYuQTU066f2o385gC0zy4&)KY4@fY`eMFa1uT^&rn8}Q&AN!wF{5j=|k8%GcfG42Zma(mM>>SGZGq#Ky6MD$_wS`!~hCEg%j_d zMcJI)J$7PoxgK0^R6Il&+@N_k&$COl8DiI&C`JwuH_%I~X|u1?`7(q){%N^k-3bi+?^kz_NfoY_7(O=43Z`BgmJj__J@Fn z(V3mr5@jWLz@PceoKp7odnYH5^Ugn(Z3a4PNPdPn5^K?@z`N63qc)ObmM-_pNSn+f z7huU-u)#Eq%j+OK7bDqqow<)zwO{xkhs(qsyq_XSXu_y1E^{k0T02aV zIf|WxJD>^{O=%X)Bvhm95iyeC`t%_>^zdgM`Xox!=lE%AW=zt2!p8M`pM%kcI;|ZI zblDLfM?&Bxd9UmBb3d$&)&c6#cqOgF%?ylJP&npd4)?MAlN>|tw88XQ!sfyArgsPT z%WgCs&!w$ez*#-4H_-L!lZ6+zFVp1F(OsH&CrkJ&g#?eJ2qH`KrF5T+gigIP&vSYa zo7OCY!Ux@Y?+-=kyD9>sm~xN>^g@2&X+tR%^@ZK=YzqUUU>92BQVZdx3F!hvCm>q} z$Ln6u&|&nFvV}YL&3?1Ws!Db+&h=8|4JTrlddex!($;kKR^KGSZUk2Xkn=t0^2_4%(OjLv<*#DW>LyyA&*+9@o(Q=ADI>WRM}G~*T$GbqjU@m3fieQ zVH^_r-X@6~pDWXXUWgNFc5Q-tNM&iP5|8?jq;A+f$x+lV?IZBkc{L@88Gj1J>g zbXHR4bAvdcLZUO@DE~7uLic?``Nb?6EK(s=PI453+IJpx)fm>7iok}Weq8z{YA<>B zZxO|@M=CsSgN7J!1$Tvmti=YF9~NOloNGR2BEb~1hf(tV95HIJ_+biyCF4+^%D1({ z5OB131k!$+H<7N{f*51S7X6fi5Q!K zt(44DeaAMxuv515V&{84eCPKy5^2s+voEvigg|LMCkWqThsVqt9jzAfF1cWd z10SJK#!&}>iOshv2;QPw^BV98g?t5WkYW6G=u<>Jzw`6B!?VjXEdCim*DD(>zH172 zih5qWbk&olq>(^YqN7-k1!Km510_1ccK%F`Dd4oo2zQqFeSN|`TqDfKWsOp<91_xt z9efz2hYn6L$7FRh$q}uNoM*!^;IRo^G_RlsRkvfR7lw!%zpGH0+8ryW%4$@w7DJbU zXxfMbZxo<8%(v7wp;c=r>{{XY*^nY(sFh4>5x79Eq((1Z$9|kf zF-|nbj4qexBE!m{J72?=$mNGUVZ5Pw=mE~zi)(uNR+E>homIxM>8aU}PUlcdN^65= z#&d^brgm&zY?5pm9>4j}>H)Wjo%V5w+9^>X_2;VPIN6yRG0l$%D(dtoojqBYZ6lkf zCm3y-=+k<~cFBvo!Nc#@6jyn%Z+WxC-_MDCsvho9+|;z2?qEo6@4MEO0dvlov2M5;dszA=^-Hs!9kY}2Fp>TvUKe0wNwg2#=^+1b76M)-QRrN5&J44%y*Ma%FS~HX;TKZd+ik z)r`OPSK|21@F|M1NjVI8To}{IHJ2|tT4k`swK;37-%`y-Oh>m9nbDhnaI2_`Lwm8U zAy6)ZtwkB@txup~6$9e%_LKY*c`x)$;-U%`Ou{zrc{PpTp-scR9N7y@RI<&YOjK5m ziN6eY!?HSy2BBe4rOUv{o)Ak#(jI;XkYkYqr7Z9daE(*4DlNvByG{N$Od`SX#h9|%Uk~t|u5n%=no{S;(Hw$7xwjA( z2SnvP`N$-@RGsr!coVitRHljmx|~qC2ZLQD*>HF3wHgVij#c4J=HwH zQd#D};CNzuOnq=9r2x*BZKwG#eJIg(<7-5`7^_c?N6Oqh+OB=^-IZuXnjXy1Yu`=U z55dhL1n3uXbjR$;(z19u4m?L+7wLBlEszz?=(;JOz9uT@??)2Pvus&A3-KQ}Jw=B%lp=>Bzq19R$eK_xpkuim17qH777o%J6;(R zvp_PBG}=R46KrMES*%hL#nM?M#Qb6<u{yxhOpMSWT~#qCfy7$>^e%=VzAPVL*P ziuc&tCdka8ZOyoC2Y;w_3Y0J#y&i@@Qi(sIm^iCc)u!hbZjty&3^wV{k7!||QAa5b zFk@@Pj35fz5w7Fq5l8QS%64;2a7z^^TpInRt}SjBY4^CqHerAa3id}bQ<%R0CmgpoJ-LWn1T2_(z&CoHNcRfo-p+tX|Xh94{o$1oL1fL1o{sE{wv)>;K11x znX^C(uyb1uT(_pOaJwaI`1;Aj@@WQdF6PNccC!x4zdaJtfe-&t0V~1qAe;07S!KTu ztD;IKmB{NZlddG5Gt+nHIxC+^f1BXdaVdq{w+C1)kodk;*h39fFkd&`Gc0@$Vyy2B znIq^316CMiQ7l}a)Y0sr8AW1Yf(^$ILDW<{DmISMPq`rOE2oPi)6YVqm9nZJDh=x? zK5Je=l-4?J(-gP0>w&Lc9S%3p&pjwfIJ85#;099YU77>8G8@ikn`JdFTN( zYODM8(epSmmzUGa=e2$>pT_5=cJ1}&$(&=R$Ks=$o9c+-P;r-9cP;NOpLX5#@|cmM zezZ=$pCuJ_PW2B{X2o=6?Y7N%X#uWyo?Qv$(gn0bpT)UYf>L5scr*gQB}CdnvI+0PQY{?Ub*b$2}=3_nVrCD>HCUc4YEleu`Cg>cQufV7B*# z%8eD!o;^GFuQ4K)#%F-R+-Pd#WF*H^aS-5OMR9VMbYL{(mY6akpJWL!$h4PTBVC%S zHDzyTWH*{1`lN)NF`$7o`i&Avikea(GL)Z~nORpn@tUHquxj6caWC?VTR$QxK9np zwV&`XP1dT&vq-{l3c}r26Wmq&=+Ast>CcYfGRZo;Ux6<_H(fvZU3uR*9$U}uUVYP# zu>K;K38FBYzT4)7PeLD=YM34EvgQ5#I5V^7mYsYN*hB8rLI4lgRBR{2Bcpi2!`wT# zIZtL9hx`)qwXyVOeHq=`D#)3PhuXE6d%)0Etf-2WxIP*KR_*ZklqTrse&RVw+b{>H zqqJo|^vqX(hUWKEn-J}4!GuccDf(C&xe`B11bj48Bo}B^F>qwmcZbw#k-3XwW0HCk zKB<|_rFp(;R^E(zI?paUqNvUgeSOx&CsYnbWK~#v=m$|OGjo}3ta9}F`qJ+6KI$c? z61&PrquXS@j}QK9TxfuSggIszsSyPvFpf*AIGb*k%NsSIEkGsVQ@$%&F>Fz)E!Sq3 zZszw%&`j){KDPm8wU-fz0ZJ1;eZH#&)xfl`lyMX`Xm+|qCNby`0nZ*n7bnLL&1u5x5yO~8) zK`H^vB0=$Ye3<5?Ex?sLbFYE-q=WI)64OgYx#TyT+@@ESreffU_Q#Cs0Chq1qJn+; z9z7AlFOARy`eYc|9m--L_#|Xc(m=OfkFB0)&<>t>cU?h>WU#eS z)iU7=LPUnNNciesa|F7L>2v8HH5$@pk<(F2D1-sUQYs}&J492Ta0*em1C|f(PyVe< zNN<^!NjDan>$?Fo(id==T)&YJ@EvrJW_soB@P}haZcv*Tzt3W!K-K%MqAL$gwNALW zeT{8?-cgqQb&(C`yK4wRx%i7q`ulP|)dMR20yD0mfP`^!m~=Lmhem)OaR*EJ&;_l8 zj5MU(yMbqZUX!G*s_Uy=<y;QP_-t7n{qAXPNSsxk^xKDh4}mkIKhl$I}SJ$Hb^@ zKONrtUgnygSOcuE&K^-Z51FobfdxypxNMfLV=ImTn}wfVy!Ni{^>exBDvQt4w_R%J zoNT?yL~gxGtFl~~0F`av-pLZbZ6p7>or`j=?j}=U(=wa2wFj-k7&AQqk~DO_lVxkO z(@+a%F5MNsq3>03S^>Fc|Lv^J z<8=ELA^FX{H)fyH9{NZnu>l_&EQK5mJ;)DrpQM;Q*5Td5$0;O*pU;yxO;FDo^oT&1 zpb+sQd51ViD+rhj+q7?-A`(7m@}}8N zJD-<8nl!N-<@;qcv3Q<8?CfNxpu&FtIJ^73RGE`(!o1gEM^DTyLKY%!4QGl)Lrnf& zJby-erT<{YCLi8TiT1gYTW~t_hnq7s^!VVE!48)U#ZGMr-I;P37=*cX4i&KbN}(Zm zCUcCHesLFY6~#yOsyuFKq{ekmn%_jS&wUfZO*?{dNDAC327D}(6usHNfSlvMeLX8u zX6K1*ryfk)LqYD55Sljf|G0+#G5r?aHtpMZ+^|JZ?<>~t-H}~B5D5P^*FVzz(H&X; zIoZqnn(RA&P4-dhU_@I-zvlWC#vi%-aF+j=>tn!xcf6vldQ$k51%=#;rD)9I^h%j$ z892V8()e9^L|pVQv}qFPMmS zg;-h#YI1_g{68b9?XAD4WDcU}CN-mErNp3pkY>PRn3JiQ4Xo6*u3yty?s2`h_j2}j z18zq=c#}ni)#Y%MK zSdNXH$)aK@$bQDpopYaL8<>t)dYl(4FQ}(Ih}F3s5C}Vk3~|EIo}^gNq}3IOy8!Q< z78y>T;7D3k{>Ivr=R{dFZf*bZK>3TzBOfs{U8}3`I(Ex}vNVfO;O#($phomGVOkw+ zw54(eDRJvlUllJO^BswI-Ck?lvf8!(_As&XdhBoj!{_gCr580!JHrf}kH$#mM7g=n znh3C1fiA=Mg~pcy{n3%HL6@)$FqVjB^?6^`>9%=H!FPD^0OX)KBF8gf4Ff?rm$4Ry z_oVJWgoQuQYg`Z2$Yrwg#kSVCLd33yq~30RFxYV+q`?vN|=Gr4`G*bWpgGLKz4R+U>`Xi-5Z zq8GFEiK!&thwFo#uM2C;=cjITSk;}R;VO2yWQ5c&7grroarer=FG=!SR&AlRw$CFD zMqE)@+}h7Q&(k#xd*Lc+*lLFz5*?~kWv_W>Cr%>SclfCNN&pcSwY8nHHvdz;Eu54T zTA1Tes7SpG6I44`q&w*Sb2!<>Uj1TNlkUC;8fx?HIZ|z{4m}YfUK1Q62kNk@+QiCK zpstZA+3Nhxt)zF`y(zRWcoBZ4*GiC2A%B9M(8afY@(OWhr`tKKz~T4&XRjg$H4cFh zy!V^Be8L4IxkkK@jRCgVPeQ@=&`?lzlVz|aNKaPl{OMNkUC48gLfjmR{$(krEG1=; zv`t7*IAhD+fGK~D1mS`0J7!O8<%>26k8hueAd`v5dnM6T)W!Wy1UB!+yf}Ro#`FMhu2K0-@e_;iZq~$7IzH9Gr>{%n3I|@M z2QPmMqjL9ErqKP>kfNMrhh8~L`=uPNF zI?zJ?>>`3~!f@c*7HoHKB3=ATkH_-Sw_)C?s!4ZIPvb7Kz zuxF^8gIsY1!&c-&*KFy}@YJ|xfD?e!)g5+&8c~3b>Vcr3r8|OwRMVq&3wgT+;=kNU z2#WQ6c_z6hq9w9WQtLE{v;ExCLvAjK7v^PF1d1!g<-v%MZbSRC6NAGA30nTc2U2zk zBcQF0W$vnmrgQtikLNXiW0J@RE?$!aB?5}n7(3ceGwX);;*2;`({h52t4MS zZ!W*ZNWV?vZ2$C4ZG}}HWZ%2$b7Lg-IHrq}+Q97M?1+YveeQgIbeLL<6lvgH)~94! z)EL9M#8BNfVzj!j?T5~^3r^R*A;8=aVCL@92I%6`*m=(=__O(P?1AXA`sMViz5Em4 zWo7qd_fgARBj?V|!GSJlQl#1HG0v)(Ex)~zME(<@gtO}U2WLHj(;p{n%D77wrFua{ zS+hvT3nBHajyy_4h&{78j)(Au ztyKWiOgi!%7R@i(F8>h#4T)2G+!~4qy#`jf%_=?<+`B$4q;Auvnc8oz_*s$0%-4rK zH7KXzp0B3XJolDA^#u6MG*Vt|&dGMUkxr>r$x>M5M)p3` zYgKipens^O4Sk7i^lPgCp!K+#>9m>yZa})?)c)mcLfCFV`eK6cWSw$)gZ0TAr^P_& z5YmLhOEuy4{pm+h8aO5G4P&~Y2e8_}Mw@)^^&n-q!8ORNKqoD#Gv)I}t&fd-!OhJ> z0a_2my20gjx0tZX2Q~b`48Dl4M9Zql2Qfj?bmQf*e2J@C;@}ZK)X)RPGAlc;^@U>> zsRex3?Fj6P%Be{mkxdGaK6(~eq132SL-1*QtL8#I6AY`gSaC(vj`cWG-6AsDZr2d# zs~q-5x-$ZQ%50C|EPw>`)kaMZQ#MO%b&Wvtp5syP{#ec7UzAv8JA^_P)RFDg664(O z2nfz~l!I1ecs_R+&gduZ)`XXxJYX$Zloz%&$k=C5+*}C=2*7J71N%ACneImQ755vH zmoRyXKm)*`vDdxCByr z`qtU-#CgM`>usP3IWFRiBFC|PZ(7ncJoD{3A$YB|%kH2Cg7t@xld{P;%H&>#=d_G!A!xVjz-y!Gf8he=ti|_)^lDs!D!JACGmoSiU0!%dPKf-dnWe%)ER$83Qy++~`Ng#B_TFPI}ZsYPq`ciC_W=THXba z6pbGa4b9A`7-pNe|ZAeVkBd4&o=6w z_dVb?KE$_+X=}sT<0w# zq7b(V=oESIfQ)$q8|yGS-93js->}zA2)b-8 zw;4h40<5XaLjlB@&%?tvfY|j|c!Rl(^SP3bnV`h5huMl5Jc~!8;;;a_E;+3_oLo&Y zPMoqwhvWDF1qHG;dx*abu*7UViedFEM%URcgCi_)k*>AR4qwyUMVKh#xRL2S<9(<0 zqKhn7N1d0=^~28=c19+8j?lQ={pRR#1x#^pv0lz<*shya=NIQhK^Pcss~MVwwbdcl zFlf_N(S2- z=_FBlV#g*>L_g`Zs78FVLtp}Rn7xQ1JlYFUrdxn7&``F9Jz$V^LNHyw>UL2~(eC(x zy2qP~;;PGcvC+0K*CJ}eQmSir3QK1eiBVs=>PR4&n)BZDMt}Z$?x4UnA;D>H{y|!V zm+jip_&!P9@|P!+l9UcLIP1}eLRiCs+htYws`Ugq*vzX+K)7ii{B5^g1$bk$C`@B;iFyOC^|e;<_z(t%;#( z8^ME8R%vr!SB32{cC#NlN{#@{qbGU5>!CJdYAH1m3ryI}{+9qjRGI|@Qr$g@er5;F zP^r7cegYe#w|wKNAyk^&1Uz$E5d2Moc!SyI-Mf~dLGNVH;>uK63Ig(kv)sG)?w-}% zJUgEdHIEWk*|bOE@|ij4Bz}A)bj9@)eu4BIeVSAZhxCDj`S@HXQ7v1I{=U*|_Bvlp zjSl|%^>8P9qVy8dM|jcl!*o41T5+X_6rf2Wwh($w6k#VvPHlz*$22x|4it%eNGtA9 z$cNih>#EwG1vWY9h3p9(7_b)Iw%bPKO097)!t2j`4d#M=xQC0yx|hq5==3dDNmh+z z)^jdbXVdY@pM^2uznebS8e*a}-^WaET_yGNRYOTf z^kZ|-^D&z5^$Yt`Fmr6hkgrE}FV{xt;#gk3R%Tc>&wa-h44ONR0)Q$H-1CA=n8tR^ z5Wj9-97Vw}G*z{?RQnvU7C_u`j#+C~n~gB*hdg8?5R={khksrAPDjIE1w9jQE>b=< zJ_AD^kn{`3?&3qp8;Fu{vHi4b;jc||Clah(lLg7ZeQ*vYPy=`Ej0Ax^Yj#@_me(GL zUF?sjYx^`;+Hz|1*wL#!Se%w>xIUKGMN|4>KovaTt(zEtK8qKnNuPb%cG*w2fpLtb z5y0k)p1g?MgIp8JUR#8o97|+ww7t&olVu&}d;R_;nPy#kWMd-#(!QxABYw&Uhs}5O z@yD~em??~dAK9?@N7Kv%R(8BXQC!8GtQu%87A=}Q3w--yC;M~eQPMaWTa5R?d-w%N zs@9-d_|VkK(zVJ}=fCclR&D5XHrDBqT6c5HG`Vgm^8_aI`-M{#L1tB$9rtPaDh>3Z z(H1+)65&AV!*PF^I@}!@2UhSYeHbeF=u$u*j&httExj1I6GUS*eDkyvly#YC9} zLf&XO$KbT}yg1`}+n_0UOBJ4m%A;}44|?d}JuMeyq0EYdZEn0{AA&ZYGCgusrWH^C z6PzhC8&}nVHIoFzGULjI^)+RJsv?qykZeXfdvg|&p)O|88ca=VVR4fA;3-@_t*v8v_T`xnGsUI^W*{ZCgRNWwYC=AkI zSvO=Z?kMfGmX_2L0Q%>2;NTA(ROTEMjHcc?g&JsRUIl);5KEii*dd!|CypvI&in}e+-ZL|e#^+f;K$9i0}R+S z0`X`M1i6n<=NPDRCM028KRmp*giz|IGL9T}aPVz=1+2l?aBllE#!+5$;_37A2BmVN zVA*BS5g431)NyDVQ@^;%$rD_x^-4+-wzF`NdHCQU$~e1Xfey}xE}fOLg}Yd<`DRD6 zuoj5<=LcuUgt4v^+DHA>dnWgs44P&K9}m@_$Hd~`r&o!?FSea&#_yZY?zd@#_g(ka zeSwWu=A*6qIYgMN-&bBt!~6#IKI}n+;6%dMAjHF41|^Z)D%+j`7NwFWaxh&kz2}!L zUUF^j#CMs#>QL0{6O2D~vRR$fPY9|+^1YYyiKyIjWw_M1XLPXVa*kpmdb(6rwi1X=cZr>{bPX$`!%-@=IxJoTO1Kh*slLjF&6|AqQr@>u_iyuY{If6Dts z{eK$o|B&}L3KR?-{J)<7e|xfjl!ky`AAgSn6lH!V|JKhZz8 zUjB_fzbX3{`j3s6KiNO?QvWBWwOA`)97`-|RR1zp?+w9R11u872Ljg(Urt znCVaWPfPnZoJ9NI4erlh`Fm&jbz1+g(hxxTmi+pA&;OJBdzbn(9RDf}0h({*@BROO zvA??GU!@@+>W%$lkp2n(UGlHt`B!NO=>0=7%b)1q2i))V-u{Q?{|WyZ4u6$~06K>M z5d9zYe^+hzM*qE`S^rf1_m=)$bf8oK`vQh8kR diff --git a/src/Mod/Path/Tools/Shape/v-bit.fcstd b/src/Mod/Path/Tools/Shape/v-bit.fcstd index 3168e5fe8d246925ccb5ffcf205ff5c7cba05796..df99cb26ee9a624f2a471ef3a20585aeb721d376 100644 GIT binary patch literal 14034 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 literal 12725 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 LAiog)->& Date: Thu, 14 Jan 2021 23:16:15 -0800 Subject: [PATCH 054/168] Changed tool-bit search path to either absolute, local install or relative to context path --- src/Mod/Path/CMakeLists.txt | 3 + src/Mod/Path/PathScripts/PathPreferences.py | 21 +--- src/Mod/Path/PathScripts/PathToolBit.py | 86 ++++++------- src/Mod/Path/PathTests/TestPathToolBit.py | 119 +++++++++++++++--- .../Tools/Bit/test-path-tool-bit-bit-00.fctb | 12 ++ .../test-path-tool-bit-library-00.fctl | 41 ++++++ .../Shape/test-path-tool-bit-shape-00.fcstd | Bin 0 -> 11829 bytes 7 files changed, 203 insertions(+), 79 deletions(-) create mode 100644 src/Mod/Path/PathTests/Tools/Bit/test-path-tool-bit-bit-00.fctb create mode 100644 src/Mod/Path/PathTests/Tools/Library/test-path-tool-bit-library-00.fctl create mode 100644 src/Mod/Path/PathTests/Tools/Shape/test-path-tool-bit-shape-00.fcstd diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 8e25c23ef3..918bb073b1 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -212,6 +212,9 @@ SET(PathTests_SRCS PathTests/TestPathUtil.py PathTests/TestPathVcarve.py PathTests/TestPathVoronoi.py + PathTests/Tools/Bit/test-path-tool-bit-bit-00.fctb + PathTests/Tools/Library/test-path-tool-bit-library-00.fctl + PathTests/Tools/Shape/test-path-tool-bit-shape-00.fcstd PathTests/boxtest.fcstd PathTests/test_centroid_00.ngc PathTests/test_geomop.fcstd diff --git a/src/Mod/Path/PathScripts/PathPreferences.py b/src/Mod/Path/PathScripts/PathPreferences.py index d8dad1d07c..5b3760a767 100644 --- a/src/Mod/Path/PathScripts/PathPreferences.py +++ b/src/Mod/Path/PathScripts/PathPreferences.py @@ -151,26 +151,9 @@ def searchPathsPost(): return paths -def searchPathsTool(sub='Bit'): +def searchPathsTool(sub): paths = [] - def appendPath(p, sub): - if p: - paths.append(os.path.join(p, 'Tools', sub)) - paths.append(os.path.join(p, sub)) - paths.append(p) - appendPath(defaultFilePath(), sub) - appendPath(macroFilePath(), sub) - appendPath(os.path.join(FreeCAD.getHomePath(), "Mod/Path/"), sub) - - if 'Bit' == sub: - paths.append("{}/Bit".format(os.path.dirname(lastPathToolLibrary()))) - paths.append(lastPathToolBit()) - - if 'Library' == sub: - paths.append(lastPathToolLibrary()) - if 'Shape' == sub: - paths.append(lastPathToolShape()) - + paths.append(os.path.join(FreeCAD.getHomePath(), 'Mod', 'Path', 'Tools', sub)) return paths diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 4a72dedab2..a92ad85f64 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -54,56 +54,56 @@ PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) def translate(context, text, disambig=None): return PySide.QtCore.QCoreApplication.translate(context, text, disambig) -def _findTool(path, typ, dbg=_DebugFindTool): - PathLog.track(path) - if os.path.exists(path): # absolute reference +def _findToolFile(name, containerFile, typ, dbg=_DebugFindTool): + PathLog.track(name) + if os.path.exists(name): # absolute reference if dbg: - PathLog.debug("Found {} at {}".format(typ, path)) - return path + PathLog.debug("Found {} at {}".format(typ, name)) + return name - def searchFor(pname, fname): - # PathLog.debug("pname: {} fname: {}".format(pname, fname)) - if dbg: - PathLog.debug("Looking for {} in {}".format(pname, fname)) - if fname: - for p in PathPreferences.searchPathsTool(typ): - PathLog.track(p) - f = os.path.join(p, fname) - if dbg: - PathLog.debug(" Checking {}".format(f)) - if os.path.exists(f): - if dbg: - PathLog.debug(" Found {} at {}".format(typ, f)) - return f - if pname and os.path.sep != pname: - PathLog.track(pname) - ppname, pfname = os.path.split(pname) - ffname = os.path.join(pfname, fname) if fname else pfname - return searchFor(ppname, ffname) - return None + if containerFile: + rootPath = os.path.dirname(os.path.dirname(containerFile)) + paths = [os.path.join(rootPath, typ)] + else: + paths = [] + paths.extend(PathPreferences.searchPathsTool(typ)) - return searchFor(path, '') + def _findFile(path, name): + PathLog.track(path, name) + fullPath = os.path.join(path, name) + if os.path.exists(fullPath): + return (True, fullPath) + for root, ds, fs in os.walk(path): + for d in ds: + found, fullPath = _findFile(d, name) + if found: + return (True, fullPath) + return (False, None) -def findShape(path): - ''' - findShape(path) ... search for path, full and partially - in all known shape directories. - ''' - return _findTool(path, 'Shape') + for p in paths: + found, path = _findFile(p, name) + if found: + return path + return None -def findBit(path): - PathLog.track(path) - if path.endswith('.fctb'): - return _findTool(path, 'Bit') - return _findTool("{}.fctb".format(path), 'Bit') +def _findShape(name, path=None): + '''_findShape(name, path) ... search for name, full and partially starting with path in known shape directories''' + return _findToolFile(name, path, 'Shape') -def findLibrary(path, dbg=False): - if path.endswith('.fctl'): - return _findTool(path, 'Library', dbg) - return _findTool("{}.fctl".format(path), 'Library', dbg) +def findBit(name, path=None): + PathLog.track(name) + if name.endswith('.fctb'): + return _findToolFile(name, path, 'Bit') + return _findToolFile("{}.fctb".format(name), path, 'Bit') + + +def findLibrary(name, path=None, dbg=False): + if name.endswith('.fctl'): + return _findToolFile(name, path, 'Library', dbg) + return _findToolFile("{}.fctl".format(name), path, 'Library', dbg) def _findRelativePath(path, typ): @@ -222,7 +222,7 @@ class ToolBit(object): doc = FreeCAD.getDocument(d) break if doc is None: - p = findShape(p) + p = _findShape(p) if not path and p != obj.BitShape: obj.BitShape = p PathLog.debug("ToolBit {} using shape file: {}".format(obj.Label, p)) @@ -330,7 +330,7 @@ class ToolBit(object): def getBitThumbnail(self, obj): if obj.BitShape: - path = findShape(obj.BitShape) + path = _findShape(obj.BitShape) if path: with open(path, 'rb') as fd: try: diff --git a/src/Mod/Path/PathTests/TestPathToolBit.py b/src/Mod/Path/PathTests/TestPathToolBit.py index dd82c7fc63..6d3b1022b4 100644 --- a/src/Mod/Path/PathTests/TestPathToolBit.py +++ b/src/Mod/Path/PathTests/TestPathToolBit.py @@ -22,37 +22,122 @@ import PathScripts.PathToolBit as PathToolBit import PathTests.PathTestUtils as PathTestUtils +import os +TestToolDir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'Tools') +TestInvalidDir = os.path.join(TestToolDir, 'some', 'silly', 'path', 'that', 'should', 'not', 'exist') + +TestToolBitName = 'test-path-tool-bit-bit-00.fctb' +TestToolShapeName = 'test-path-tool-bit-shape-00.fcstd' +TestToolLibraryName = 'test-path-tool-bit-library-00.fctl' + +def testToolShape(path = TestToolDir, name = TestToolShapeName): + return os.path.join(path, 'Shape', name) + +def testToolBit(path = TestToolDir, name = TestToolBitName): + return os.path.join(path, 'Bit', name) + +def testToolLibrary(path = TestToolDir, name = TestToolLibraryName): + return os.path.join(path, 'Library', name) class TestPathToolBit(PathTestUtils.PathTestBase): def test00(self): - '''Find a tool shapee from file name''' - - path = PathToolBit.findShape('endmill.fcstd') + '''Find a tool shape from file name''' + path = PathToolBit._findShape('endmill.fcstd') self.assertIsNot(path, None) self.assertNotEqual(path, 'endmill.fcstd') - def test01(self): - '''Find a tool shapee from an invalid absolute path.''' - path = PathToolBit.findShape('/this/is/unlikely/a/valid/path/v-bit.fcstd') + def test01(self): + '''Not find a relative path shape if not stored in default location''' + path = PathToolBit._findShape(TestToolShapeName) + self.assertIsNone(path) + + + def test02(self): + '''Find a relative path shape if it's local to a bit path''' + path = PathToolBit._findShape(TestToolShapeName, testToolBit()) self.assertIsNot(path, None) - self.assertNotEqual(path, '/this/is/unlikely/a/valid/path/v-bit.fcstd') + self.assertEqual(path, testToolShape()) + + + def test03(self): + '''Not find a tool shape from an invalid absolute path.''' + path = PathToolBit._findShape(testToolShape(TestInvalidDir)) + self.assertIsNone(path) + + + def test04(self): + '''Find a tool shape from a valid absolute path.''' + path = PathToolBit._findShape(testToolShape()) + self.assertIsNot(path, None) + self.assertEqual(path, testToolShape()) def test10(self): - '''find the relative path of a tool bit''' - shape = 'endmill.fcstd' - path = PathToolBit.findShape(shape) + '''Find a tool bit from file name''' + path = PathToolBit.findBit('5mm_Endmill.fctb') self.assertIsNot(path, None) - self.assertGreater(len(path), len(shape)) - rel = PathToolBit.findRelativePathShape(path) - self.assertEqual(rel, shape) + self.assertNotEqual(path, '5mm_Endmill.fctb') + def test11(self): - '''store full path if relative path isn't found''' - path = '/this/is/unlikely/a/valid/path/v-bit.fcstd' - rel = PathToolBit.findRelativePathShape(path) - self.assertEqual(rel, path) + '''Not find a relative path bit if not stored in default location''' + path = PathToolBit.findBit(TestToolBitName) + self.assertIsNone(path) + + + def test12(self): + '''Find a relative path bit if it's local to a library path''' + path = PathToolBit.findBit(TestToolBitName, testToolLibrary()) + self.assertIsNot(path, None) + self.assertEqual(path, testToolBit()) + + + def test13(self): + '''Not find a tool bit from an invalid absolute path.''' + path = PathToolBit.findBit(testToolBit(TestInvalidDir)) + self.assertIsNone(path) + + + def test14(self): + '''Find a tool bit from a valid absolute path.''' + path = PathToolBit.findBit(testToolBit()) + self.assertIsNot(path, None) + self.assertEqual(path, testToolBit()) + + + + def test20(self): + '''Find a tool library from file name''' + path = PathToolBit.findBit('5mm_Endmill.fctb') + self.assertIsNot(path, None) + self.assertNotEqual(path, '5mm_Endmill.fctb') + + + def test21(self): + '''Not find a relative path library if not stored in default location''' + path = PathToolBit.findBit(TestToolBitName) + self.assertIsNone(path) + + + def test22(self): + '''[skipped] Find a relative path library if it's local to ''' + # this is not a valid test for libraries because t + self.assertTrue(True) + + + def test23(self): + '''Not find a tool library from an invalid absolute path.''' + path = PathToolBit.findBit(testToolBit(TestInvalidDir)) + self.assertIsNone(path) + + + def test24(self): + '''Find a tool library from a valid absolute path.''' + path = PathToolBit.findBit(testToolBit()) + self.assertIsNot(path, None) + self.assertEqual(path, testToolBit()) + diff --git a/src/Mod/Path/PathTests/Tools/Bit/test-path-tool-bit-bit-00.fctb b/src/Mod/Path/PathTests/Tools/Bit/test-path-tool-bit-bit-00.fctb new file mode 100644 index 0000000000..7cc72ba33c --- /dev/null +++ b/src/Mod/Path/PathTests/Tools/Bit/test-path-tool-bit-bit-00.fctb @@ -0,0 +1,12 @@ +{ + "version": 2, + "name": "5mm Endmill", + "shape": "endmill.fcstd", + "parameter": { + "CuttingEdgeHeight": "30.0000 mm", + "Diameter": "5.0000 mm", + "Length": "50.0000 mm", + "ShankDiameter": "3.0000 mm" + }, + "attribute": {} +} diff --git a/src/Mod/Path/PathTests/Tools/Library/test-path-tool-bit-library-00.fctl b/src/Mod/Path/PathTests/Tools/Library/test-path-tool-bit-library-00.fctl new file mode 100644 index 0000000000..60de98e08e --- /dev/null +++ b/src/Mod/Path/PathTests/Tools/Library/test-path-tool-bit-library-00.fctl @@ -0,0 +1,41 @@ +{ + "tools": [ + { + "nr": 1, + "path": "5mm_Endmill.fctb" + }, + { + "nr": 2, + "path": "5mm_Drill.fctb" + }, + { + "nr": 3, + "path": "6mm_Ball_End.fctb" + }, + { + "nr": 4, + "path": "6mm_Bullnose.fctb" + }, + { + "nr": 5, + "path": "60degree_Vbit.fctb" + }, + { + "nr": 6, + "path": "45degree_chamfer.fctb" + }, + { + "nr": 7, + "path": "slittingsaw.fctb" + }, + { + "nr": 8, + "path": "probe.fctb" + }, + { + "nr": 9, + "path": "5mm-thread-cutter.fctb" + } + ], + "version": 1 +} diff --git a/src/Mod/Path/PathTests/Tools/Shape/test-path-tool-bit-shape-00.fcstd b/src/Mod/Path/PathTests/Tools/Shape/test-path-tool-bit-shape-00.fcstd new file mode 100644 index 0000000000000000000000000000000000000000..5b5a76dc41bc35e827c9cdbb9bbd0f5dbe86fb57 GIT binary patch literal 11829 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 literal 0 HcmV?d00001 From 3ec542b071546ec334a411170f9d9d9f48d5148c Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 15 Jan 2021 19:07:22 -0800 Subject: [PATCH 055/168] Adapted client code to use new findTool... api and fixed test cases. --- src/Mod/Path/PathScripts/PathToolBit.py | 22 +++++++------- .../Path/PathScripts/PathToolBitLibraryGui.py | 2 +- src/Mod/Path/PathTests/TestPathToolBit.py | 30 +++++++++---------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index a92ad85f64..573376fb7d 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -54,11 +54,9 @@ PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) def translate(context, text, disambig=None): return PySide.QtCore.QCoreApplication.translate(context, text, disambig) -def _findToolFile(name, containerFile, typ, dbg=_DebugFindTool): +def _findToolFile(name, containerFile, typ): PathLog.track(name) if os.path.exists(name): # absolute reference - if dbg: - PathLog.debug("Found {} at {}".format(typ, name)) return name if containerFile: @@ -88,22 +86,24 @@ def _findToolFile(name, containerFile, typ, dbg=_DebugFindTool): return None -def _findShape(name, path=None): - '''_findShape(name, path) ... search for name, full and partially starting with path in known shape directories''' +def findToolShape(name, path=None): + '''findToolShape(name, path) ... search for name, if relative path look in path''' return _findToolFile(name, path, 'Shape') -def findBit(name, path=None): +def findToolBit(name, path=None): + '''findToolBit(name, path) ... search for name, if relative path look in path''' PathLog.track(name) if name.endswith('.fctb'): return _findToolFile(name, path, 'Bit') return _findToolFile("{}.fctb".format(name), path, 'Bit') -def findLibrary(name, path=None, dbg=False): +def findToolLibrary(name, path=None): + '''findToolLibrary(name, path) ... search for name, if relative path look in path''' if name.endswith('.fctl'): - return _findToolFile(name, path, 'Library', dbg) - return _findToolFile("{}.fctl".format(name), path, 'Library', dbg) + return _findToolFile(name, path, 'Library') + return _findToolFile("{}.fctl".format(name), path, 'Library') def _findRelativePath(path, typ): @@ -222,7 +222,7 @@ class ToolBit(object): doc = FreeCAD.getDocument(d) break if doc is None: - p = _findShape(p) + p = findToolShape(p, path if path else obj.File) if not path and p != obj.BitShape: obj.BitShape = p PathLog.debug("ToolBit {} using shape file: {}".format(obj.Label, p)) @@ -330,7 +330,7 @@ class ToolBit(object): def getBitThumbnail(self, obj): if obj.BitShape: - path = _findShape(obj.BitShape) + path = findToolShape(obj.BitShape) if path: with open(path, 'rb') as fd: try: diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index 7d853e136b..c4fd89a8a4 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -143,7 +143,7 @@ class ModelFactory(object): for toolBit in library['tools']: try: nr = toolBit['nr'] - bit = PathToolBit.findBit(toolBit['path']) + bit = PathToolBit.findToolBit(toolBit['path'], path) if bit: PathLog.track(bit) tool = PathToolBit.Declaration(bit) diff --git a/src/Mod/Path/PathTests/TestPathToolBit.py b/src/Mod/Path/PathTests/TestPathToolBit.py index 6d3b1022b4..9d1e05e5c1 100644 --- a/src/Mod/Path/PathTests/TestPathToolBit.py +++ b/src/Mod/Path/PathTests/TestPathToolBit.py @@ -44,66 +44,66 @@ class TestPathToolBit(PathTestUtils.PathTestBase): def test00(self): '''Find a tool shape from file name''' - path = PathToolBit._findShape('endmill.fcstd') + path = PathToolBit.findToolShape('endmill.fcstd') self.assertIsNot(path, None) self.assertNotEqual(path, 'endmill.fcstd') def test01(self): '''Not find a relative path shape if not stored in default location''' - path = PathToolBit._findShape(TestToolShapeName) + path = PathToolBit.findToolShape(TestToolShapeName) self.assertIsNone(path) def test02(self): '''Find a relative path shape if it's local to a bit path''' - path = PathToolBit._findShape(TestToolShapeName, testToolBit()) + path = PathToolBit.findToolShape(TestToolShapeName, testToolBit()) self.assertIsNot(path, None) self.assertEqual(path, testToolShape()) def test03(self): '''Not find a tool shape from an invalid absolute path.''' - path = PathToolBit._findShape(testToolShape(TestInvalidDir)) + path = PathToolBit.findToolShape(testToolShape(TestInvalidDir)) self.assertIsNone(path) def test04(self): '''Find a tool shape from a valid absolute path.''' - path = PathToolBit._findShape(testToolShape()) + path = PathToolBit.findToolShape(testToolShape()) self.assertIsNot(path, None) self.assertEqual(path, testToolShape()) def test10(self): '''Find a tool bit from file name''' - path = PathToolBit.findBit('5mm_Endmill.fctb') + path = PathToolBit.findToolBit('5mm_Endmill.fctb') self.assertIsNot(path, None) self.assertNotEqual(path, '5mm_Endmill.fctb') def test11(self): '''Not find a relative path bit if not stored in default location''' - path = PathToolBit.findBit(TestToolBitName) + path = PathToolBit.findToolBit(TestToolBitName) self.assertIsNone(path) def test12(self): '''Find a relative path bit if it's local to a library path''' - path = PathToolBit.findBit(TestToolBitName, testToolLibrary()) + path = PathToolBit.findToolBit(TestToolBitName, testToolLibrary()) self.assertIsNot(path, None) self.assertEqual(path, testToolBit()) def test13(self): '''Not find a tool bit from an invalid absolute path.''' - path = PathToolBit.findBit(testToolBit(TestInvalidDir)) + path = PathToolBit.findToolBit(testToolBit(TestInvalidDir)) self.assertIsNone(path) def test14(self): '''Find a tool bit from a valid absolute path.''' - path = PathToolBit.findBit(testToolBit()) + path = PathToolBit.findToolBit(testToolBit()) self.assertIsNot(path, None) self.assertEqual(path, testToolBit()) @@ -111,14 +111,14 @@ class TestPathToolBit(PathTestUtils.PathTestBase): def test20(self): '''Find a tool library from file name''' - path = PathToolBit.findBit('5mm_Endmill.fctb') + path = PathToolBit.findToolLibrary('Default.fctl') self.assertIsNot(path, None) - self.assertNotEqual(path, '5mm_Endmill.fctb') + self.assertNotEqual(path, 'Default.fctl') def test21(self): '''Not find a relative path library if not stored in default location''' - path = PathToolBit.findBit(TestToolBitName) + path = PathToolBit.findToolLibrary(TestToolLibraryName) self.assertIsNone(path) @@ -130,13 +130,13 @@ class TestPathToolBit(PathTestUtils.PathTestBase): def test23(self): '''Not find a tool library from an invalid absolute path.''' - path = PathToolBit.findBit(testToolBit(TestInvalidDir)) + path = PathToolBit.findToolLibrary(testToolLibrary(TestInvalidDir)) self.assertIsNone(path) def test24(self): '''Find a tool library from a valid absolute path.''' - path = PathToolBit.findBit(testToolBit()) + path = PathToolBit.findToolBit(testToolBit()) self.assertIsNot(path, None) self.assertEqual(path, testToolBit()) From 533be4bcd12e2d34a2eda765764fca108ddf64de Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 16 Jan 2021 16:57:21 -0800 Subject: [PATCH 056/168] Migrate existing toolbits to new property layout. --- src/Mod/Path/PathScripts/PathToolBit.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 573376fb7d..0444f03118 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -166,6 +166,23 @@ class ToolBit(object): obj.setEditorMode('BitBody', 2) obj.setEditorMode('File', 1) obj.setEditorMode('Shape', 2) + if not hasattr(obj, 'BitPropertyNames'): + obj.addProperty('App::PropertyStringList', 'BitPropertyNames', 'Base', translate('PathToolBit', 'List of all properties inherited from the bit')) + propNames = [] + for prop in obj.PropertiesList: + if obj.getGroupOfProperty(prop) == 'Bit': + val = obj.getPropertyByName(prop) + typ = obj.getTypeIdOfProperty(prop) + dsc = obj.getDocumentationOfProperty(prop) + + obj.removeProperty(prop) + obj.addProperty(typ, prop, PropertyGroupShape, dsc) + + PathUtil.setProperty(obj, prop, val) + propNames.append(prop) + elif obj.getGroupOfProperty(prop) == 'Attribute': + propNames.append(prop) + obj.BitPropertyNames = propNames obj.setEditorMode('BitPropertyNames', 2) for prop in obj.BitPropertyNames: From 9b7aa779beddeab1be70ee360a4249a95f6c2190 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 16 Jan 2021 19:54:52 -0800 Subject: [PATCH 057/168] Fixed toolbit editor to properly deal with changing shape files. --- src/Mod/Path/PathScripts/PathGui.py | 7 +++ src/Mod/Path/PathScripts/PathToolBitEdit.py | 53 ++++++++++++++------- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathGui.py b/src/Mod/Path/PathScripts/PathGui.py index c9fee42062..fb37725bf3 100644 --- a/src/Mod/Path/PathScripts/PathGui.py +++ b/src/Mod/Path/PathScripts/PathGui.py @@ -48,6 +48,7 @@ def updateInputField(obj, prop, widget, onBeforeChange=None): If onBeforeChange is specified it is called before a new value is assigned to the property. Returns True if a new value was assigned, False otherwise (new value is the same as the current). ''' + PathLog.track() value = widget.property('rawValue') attr = PathUtil.getProperty(obj, prop) attrValue = attr.Value if hasattr(attr, 'Value') else attr @@ -98,10 +99,12 @@ class QuantitySpinBox: PathLog.track(widget) self.widget = widget self.onBeforeChange = onBeforeChange + self.prop = None self.attachTo(obj, prop) def attachTo(self, obj, prop = None): '''attachTo(obj, prop=None) ... use an existing editor for the given object and property''' + PathLog.track(self.prop, prop) self.obj = obj self.prop = prop if obj and prop: @@ -119,12 +122,14 @@ class QuantitySpinBox: def expression(self): '''expression() ... returns the expression if one is bound to the property''' + PathLog.track(self.prop, self.valid) if self.valid: return self.widget.property('expression') return '' def setMinimum(self, quantity): '''setMinimum(quantity) ... set the minimum''' + PathLog.track(self.prop, self.valid) if self.valid: value = quantity.Value if hasattr(quantity, 'Value') else quantity self.widget.setProperty('setMinimum', value) @@ -133,6 +138,7 @@ class QuantitySpinBox: '''updateSpinBox(quantity=None) ... update the display value of the spin box. If no value is provided the value of the bound property is used. quantity can be of type Quantity or Float.''' + PathLog.track(self.prop, self.valid) if self.valid: if quantity is None: quantity = PathUtil.getProperty(self.obj, self.prop) @@ -141,6 +147,7 @@ class QuantitySpinBox: def updateProperty(self): '''updateProperty() ... update the bound property with the value from the spin box''' + PathLog.track(self.prop, self.valid) if self.valid: return updateInputField(self.obj, self.prop, self.widget, self.onBeforeChange) return None diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index 724f5b65cc..3028bd7bb1 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -115,6 +115,7 @@ class ToolBitEditor(object): # for all properties either assign them to existing labels and editors # or create additional ones for them if not enough have already been # created. + usedRows = 0 for nr, name in enumerate(tool.Proxy.toolShapeProperties(tool)): if nr < len(self.widgets): PathLog.debug("re-use row: {} [{}]".format(nr, name)) @@ -134,9 +135,11 @@ class ToolBitEditor(object): if nr >= layout.rowCount(): layout.addRow(label, qsb) + usedRows = usedRows + 1 # hide all rows which aren't being used - for i in range(len(tool.BitPropertyNames), len(self.widgets)): + PathLog.track(usedRows, len(self.widgets)) + for i in range(usedRows, len(self.widgets)): label, qsb, editor = self.widgets[i] label.hide() qsb.hide() @@ -152,9 +155,14 @@ class ToolBitEditor(object): def setupAttributes(self, tool): PathLog.track() - self.delegate = _Delegate(self.form.attrTree) - self.model = QtGui.QStandardItemModel(self.form.attrTree) - self.model.setHorizontalHeaderLabels(['Property', 'Value']) + setup = True + if not hasattr(self, 'delegate'): + self.delegate = _Delegate(self.form.attrTree) + self.model = QtGui.QStandardItemModel(self.form.attrTree) + self.model.setHorizontalHeaderLabels(['Property', 'Value']) + else: + self.model.removeRows(0, self.model.rowCount()) + setup = False attributes = tool.Proxy.toolGroupsAndProperties(tool, False) for name in attributes: @@ -175,8 +183,9 @@ class ToolBitEditor(object): self.model.appendRow(group) - self.form.attrTree.setModel(self.model) - self.form.attrTree.setItemDelegateForColumn(1, self.delegate) + if setup: + self.form.attrTree.setModel(self.model) + self.form.attrTree.setItemDelegateForColumn(1, self.delegate) self.form.attrTree.expandAll() self.form.attrTree.resizeColumnToContents(0) self.form.attrTree.resizeColumnToContents(1) @@ -199,15 +208,29 @@ class ToolBitEditor(object): for lbl, qsb, editor in self.widgets: editor.updateSpinBox() + def _updateBitShape(self, shapePath): + # Only need to go through this exercise if the shape actually changed. + if self.tool.BitShape != shapePath: + # Before setting a new bitshape we need to make sure that none of + # editors fires an event and tries to access its old property, which + # might not exist anymore. + for lbl, qsb, editor in self.widgets: + editor.attachTo(self.tool, 'File') + self.tool.BitShape = shapePath + self.setupTool(self.tool) + self.form.toolName.setText(self.tool.Label) + if self.tool.BitBody and self.tool.BitBody.ViewObject: + if not self.tool.BitBody.ViewObject.Visibility: + self.tool.BitBody.ViewObject.Visibility = True + self.setupAttributes(self.tool) + return True + return False + def updateShape(self): PathLog.track() shapePath = str(self.form.shapePath.text()) # Only need to go through this exercise if the shape actually changed. - if self.tool.BitShape != shapePath: - self.tool.BitShape = shapePath - self.setupTool(self.tool) - self.form.toolName.setText(self.tool.Label) - + if self._updateBitShape(shapePath): for lbl, qsb, editor in self.widgets: editor.updateSpinBox() @@ -218,8 +241,7 @@ class ToolBitEditor(object): shape = str(self.form.shapePath.text()) if self.tool.Label != label: self.tool.Label = label - if self.tool.BitShape != shape: - self.tool.BitShape = shape + self._updateBitShape(shape) for lbl, qsb, editor in self.widgets: editor.updateProperty() @@ -238,10 +260,7 @@ class ToolBitEditor(object): path = self.tool.BitShape if not path: path = PathPreferences.lastPathToolShape() - foo = QtGui.QFileDialog.getOpenFileName(self.form, - "Path - Tool Shape", - path, - "*.fcstd") + foo = QtGui.QFileDialog.getOpenFileName(self.form, "Path - Tool Shape", path, "*.fcstd") if foo and foo[0]: PathPreferences.setLastPathToolShape(os.path.dirname(foo[0])) self.form.shapePath.setText(foo[0]) From ee6220193fa47fd8980b6f6011d0ce7c4588c2bb Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 17 Jan 2021 00:07:27 -0800 Subject: [PATCH 058/168] Fixed property update if property value happens to be 0 --- src/Mod/Path/PathScripts/PathUtil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathUtil.py b/src/Mod/Path/PathScripts/PathUtil.py index 7c423af22f..db43b466e5 100644 --- a/src/Mod/Path/PathScripts/PathUtil.py +++ b/src/Mod/Path/PathScripts/PathUtil.py @@ -71,7 +71,7 @@ def getPropertyValueString(obj, prop): def setProperty(obj, prop, value): '''setProperty(obj, prop, value) ... set the property value of obj's property defined by its canonical name.''' o, attr, name = _getProperty(obj, prop) # pylint: disable=unused-variable - if attr and type(value) == str: + if not attr is None and type(value) == str: if type(attr) == int: value = int(value, 0) elif type(attr) == bool: From 609afaf7e9eb9b5bd57010e567f007c66e3bbcc3 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 17 Jan 2021 00:21:24 -0800 Subject: [PATCH 059/168] Made all toobit shapes consistent, and added a script for further maintenance --- src/Mod/Path/Tools/README.md | 4 +- src/Mod/Path/Tools/Shape/ballend.fcstd | Bin 12746 -> 12385 bytes src/Mod/Path/Tools/Shape/bullnose.fcstd | Bin 13244 -> 12612 bytes src/Mod/Path/Tools/Shape/chamfer.fcstd | Bin 13418 -> 13052 bytes src/Mod/Path/Tools/Shape/drill.fcstd | Bin 11000 -> 10903 bytes src/Mod/Path/Tools/Shape/endmill.fcstd | Bin 11829 -> 11664 bytes src/Mod/Path/Tools/Shape/probe.fcstd | Bin 11600 -> 11282 bytes src/Mod/Path/Tools/Shape/slittingsaw.fcstd | Bin 12544 -> 12392 bytes src/Mod/Path/Tools/Shape/thread-mill.fcstd | Bin 13868 -> 13782 bytes src/Mod/Path/Tools/Shape/v-bit.fcstd | Bin 14034 -> 13538 bytes src/Mod/Path/Tools/toolbit-attributes.py | 147 +++++++++++++++++++++ 11 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 src/Mod/Path/Tools/toolbit-attributes.py diff --git a/src/Mod/Path/Tools/README.md b/src/Mod/Path/Tools/README.md index af51833aca..8f46b421bc 100644 --- a/src/Mod/Path/Tools/README.md +++ b/src/Mod/Path/Tools/README.md @@ -75,8 +75,8 @@ solid is updated to the correct representation. * this creates a PropertyBag object inside the Body (assuming it was selected) * add properties to which define the tool bit's shape and put those into the group 'Shape' * add any other properties to the bag which might be useful for the tool bit -1. Construct the body of the tool bit and assign experssions referencing properties from the PropertyBag (in the Shape - Group) for all constraints. +1. Construct the body of the tool bit and assign experssions referencing properties from the PropertyBag (in the + `Shape` Group) for all constraints. * Position the tip of the tool bit on the origin (0,0) 1. Save the document as a new file in the Shape directory * Before saving the document make sure you have _Save Thumbnail_ selected, and _Add program logo_ deselected in diff --git a/src/Mod/Path/Tools/Shape/ballend.fcstd b/src/Mod/Path/Tools/Shape/ballend.fcstd index 016a87cb832afa3e22b5b09d0bdeaf92c462eb81..42c1b2d6bbe005e42439ef00678c4a8755b45624 100644 GIT binary patch literal 12385 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 literal 12746 zcma)?19T=`*6(AdW4qI_opfy5wr$()*tTtVoOEp4wv#XKe0SEo-E(K|J)_1ftv~r+!v9vhD zF|}W5zH|2iErUyQQX6iETUd_LA8bnzonIdkjdS>pA?EkZ%0!M|&URIM1Kej5QYQaP zRr*j}N<*lB_f8w$<@!=r=i1Y>GIGBbI!qP?@KZ~GpD62{-_zw2dCJ|{!N>E{(vtS` zoAI<_z-0eZfdht=k~68uJ3&8$X!tkPfg%!;hldA$jN9bq8Fxn9fGLgCz!(QKA(3mK z$GdUKO(OrEP1PxR+0zT54JOudcyU1h1a3)=0w z)|^KpH?s4BM{lnZepw9YZ342}IV!CX%DnWaWJ1!dk*oOkI%7u+RA*8_9#VrGBEIgy zL7W^{)|cwiVz`Ny4^MWkZX?eTu@E{laoc^8N>|m!L#c(ytuyR7(3|5fCsZwQ9ru1T zPYS6<=&hBeWAZf_8g5hj(B-F(U)y`}>PP26 zaW6a(?2s5eryd7RJ%kwd7ODn%NnHxSUDKz&8nu?qArQzsT*5p@~uok_t^J&%R+z&9%F~xS@skfg%kN zA;X+^(5xd$a-xYBAiXX?mftgIo*hUkzknUGU2Aa3b=msvFkr~)lTMOL7CXe2$#D7( z03BHLNN!~J?E#(Aq0y-YJaoCukU=$zyCMVukP`6NFdz{#EPzK)hRvYT0zj^)8LXsQ zrqyctzdBjGXABw_UaWVtQl4sbuWVGbn}PxKG7d)R-H%cH2Vtmn1eIXZth!>PT(6-Fl{fA= zBv{N8y3F8lWqIFh2km)jENsoRe!=7N>bzRZ7C=EM_Ij*u92J&Is*7{l6k!?eCP?B@ z1J-KYN$i+8YdX#7$+h%uJV|_GAZ<9!(xW)htfwT;67h*yQnfRGCfB>vEsbm0mDxAVE>)LzAn&L0_AxIjwT2 zZ?s}WcgU8Qs);a|uF+Xw?rLF8M3DK8O1XHikHJ=;Kjx@UW~>`-lpX~5>zJ8?u<`cH zsc4Jste`g|Xbbs5#=%X@J8vV_G9ZR1LnH=^nEb0$4D+EeC6_3PI`lL}^`jC(tE&hq zK9u}q;v`!JSunMJpJ6C0vA;U}GLx?&-&N8EqD&U=qaR0Cq*5_??e)FIh#1<>uv#m} z+D9TN<~6>S1oFBh@DnkyfWCR}N`QUySJx|uzc3PO$4mWb-&WhC+suv^>$^sE*&iPTdcYAOk%%ppoPz=;QA= zHEx$C8(EioB6K<+IiZym{v?1kzqwd2yTO_biY-Zm;JTo29{uid}RhCKVM}=Hn{sgBm)M!tbDTvjyy)+?ey=^?bo^Pa8VX8+P4;lz!JM2*UCU)FfOe#$&YDM2)mndvYFElci)4o?@4LfZv^N7XT)^Vk7*<+1m7w0H( zM)AGHTzDfp9k>$)u03^%Ug?2kid_NtZpgL2GXalFIptAY@PC`*!#ZIM9W#&%LqMTu zvl92m%QF*G_W#PE|1g9yQ->$ULMSRxtf^4ozZaH<@ii=Dq9%?5PMAO6z|~RK)OGfS z`Br`4#k5%N(Q^dXXAS>>EBSn95wJvs^Lkan#@WcM;*|@>wNho}sJ)&4R-BV5(|k5v zqzmZ)?tsnq-6qrjrsy6!ioxeb7-#59dK4|P>KwMDEeynh>_N1yYAQilVcud?2_}4e z?l|7K{dQX^=f$Rwi$G=@)~;782TOxw6vsogOXZ>g$MtHis<`(?Tc-L}PB%ph2O9Um zmBuT!i&BTWQHkum`Jhls8+x4Iy0%)@wcI!B{TH--7yoH7o zVV0P!ptfI1ujePKWfGEZ(wi~JZ+y(4F{^;i0hjC~B$=p$!GnRENPNGSs&HtIr)7w9*{%0(x3^PJo#>TvfUm{eoQK@t;yjy;u7_Zke#i1@|M zq}fFJpqE6gur8P(p<%2)J=$l(8k(=|OS7eeEwAOcHrHx|k|g(x2S=2rOlt71nO4dDfaR4y<} zS%LCZ*=7eC5yb)?)mA;1>Q>pKN34tXmAIJmj9Fz;#U_ybhl!0Z>zkX0uM{_?#%851 z6q>*OWuXH|Ut%w)Pv0R#=8QIE4uDcaQ4Uu?h{94eY)I!xsVOODc|ITiH~=E{VpKs& zl#zdrmCx`yIwMFSyT2%~JsBYA{kTtTPZ7HevSGG)W6o%+u0IxU4@*iZ&sN1DoevPZ zhBOb+fE*8U^S%ZTvWZDFsdVO&pZB4#K6!;rL7xTmE%4=XdjsppKKzuF&$pVzvkj)y{I8L22s-%Hb70e_ZIsA0`CCcn+vB%bdfhoNr37 zlJQT0C+6}|a||Z-9Mbo^inR31uC0wu3F0I1Mt~@70z2--dW%vM{m-y=Y zD3+%*7MP?OvP(3K;z-!sBDSlP8kHngChL%q|3E#`*|imIq2VcrAkW|c3#N=#KO!Zv zHUK1kyNEB@y#?OQQ)r)7U?@2x<%{Me5jau@95PJ~tV}DA`FG!;d>fnptRpvPxm!R&0q?T8nVWoV}DY4rA< z#y!%{RZd((t#x=EMylnl4PRH{(s1qEW~9MxpwbcnnFphp>l-#;vCyjl)#LM<36oY* zB*j@RSD=b+=b4hWrXw3PoH^0ScrEi{7;C?REyzj`X`h%$DC`d?3<_!KEBW*n+kpLV zkx1N!DY9S=G~cep>qJ;U=bH&g19P!U#{krp=22{9>~ycB zW)YL;=j=|O&-3I0Fq+2meER@yDU{O!o_7}A5!NX4-7zDG%zD&qPwG)%Bw_4{Xot)R z^Hdm*ln>(k*(kKT6`9-I%fu8lbaQFo3io(mR&?kz=4L?q zM*3`%vV`Dy@6UxVh}ryLC7_ISu_R*?nld44lVB&z?b=Zk`T)->-`)ycKvOK`5U-&+ zR{7k2dh6ySOZcYMu+J$%y&ezRQ4|&>+;isuiuOg~T3+bx{IejhmWF2^EVYVcuoDujju7gnhx+2|I)3vu-^Uo-PCOA{OKDZ5RB?B&Cz zzIDXyV5Ic8iRBt;bM}p;b-Dhqn-gy|7ULWctovx&D&uyT>$;z61U;PMI%%sQ0FA6S4fbq9WfLU67bDck)oxe{r9pc#u}nTe zt;4?dCTyvQNg2(;f>Qlm3DRG@3mZ_eWVwb#-O%~Y>I?HmAC`4%{vAFWeOUaKDKLBF4%l*ULP@}P*u!sJc8OVjhi^=;r;dG3T(@CJ=h^qyr$) zJ6!D?IH}-ANiuDTP7G}dX7&n$o6#q}N^AQzJW^E2KOETGxGSKA9Q#4lPR5y&v=mdT zPZl(Z)?m!N-cK&|lO4=P4k*fcZ_@AU?W-Xak{L0)&yQu5Es4}0NBM@Sz(Mx`kSn}B z$j9JME9!G0RLb1C8p zBHe2o4x|u>QDroV;mJT6_SXGR=%|Ps!N128hxzW5^k#~9e{E)dv>X2l35SnlWz)FA8kW5X*1+;pUUE<=Tc^un3F1AwvF`gO5?jK3BFSdBzg zr8o6f*9RN?V(03~2BK-u53+BN0BZ%Xx(BDJ<0L3L zStxNYw;zX&XuD^a2u+>{bXC|2Rw@wCsqB&nxpZdOsj+LSSgv;zSYsBh_~tJtDcdvp zng@2hZdV0dPv;H4jrFFmcg!)9wogtd>n|FsI(#X|f)72cG?Qn^amcaq=!11YJ?H$! zFtauwX=rz@!aqjqR8wC=E@^pE7}k@}x``|HfIMu{xQSw%gq$o@2DiIQqC)s0yCBcDYAwk^&L`j5kPc*!5M#JhC#MF0sPCZv#eF zOjWd%u1!>HE&EGx`vel7GP*qI8G0%LS&L_;j)X6XU6Ept`%v>wcdmTaYVhhK{@x_} zm|ofK{xpNiOD#-fI-#=ys52ff8(H0@YU8hhnRt38^8Jncb=Qv(a5;jf1a+zxN(AidJ_$YfEEb6F)ImALmx^qMeA0jrL|@Xu@F;IYMG;4mX4$;D6#Dqlf^ML0 zVhxkHq@z#`?J0#oT<`$-nZ2NzK?+0ZtfW`Iq1%W6=O&x+aOO4~W6G$AI`wOgAp*6R z96)1n3^XQTw8NTw7j>3m-iHEwQxR8_Op2ndAPlB1rIBU=DBMiWuFwdM;X$2jU-OV6 zh1rZbC^@F;1bLPrQsTkn!_(AAgEXypjDJqSq73EzV~lJ62@SL80JK@ZAaP4bC5@1| zo41C#^1z~iybvB{-i-;B={^SW20)EVN+>9Sj1#D2r?3mMAn{Q84H ztSlp0S4ksQ4sV7Q@I9WLe`A$uEDqAETaWXyO69c<;T8&uHwEzVKs9Spy5!^GSMW_@ zbr#7xbOAcJ@W9Xa_yr>9t2@}KG<~XH+D9k`wLkJ$_}WFB{5bDIsC%a(ZM14_v@(m( zM(;8f-3-VMg&;^o3Re6w^$d%Uzau{v>t$l#i`=4n1Nmf7Cde$-+D@^c2UfnI23mcpo`07d` zR~Nas#_1xgi*{CqGg9WP?@F~il1bX>lyA8AJC`j>4dl*BANHJ7tG28;tDNeWFhQOR z8e1aX5T6m^aJ|jB-I|=Xx5v#r=D6L5YjcmjJJy%2S_`OmS>-M_PBU!KI#nL@ zHPCI{crDw{mxtcQ`c?Q*#_}BWB~wdU9;mB9qOQEaTu?a{13v>;rC!9ld7dA7Zo5iv zqoW(L8?q@UQ~-YRQXHe}tQJ1XrNjn3hp-5Q!R>x86# zP%}hs`anHj zE5*up6CUHq&@|9C5PgaOMdLvRO0lOznDS!rU^t(?drV?y;BR>W{caj*I3Yo6pJRv-&L5^h|Cec; zsft^#(86?{C?7U6+2cPEh!1`}b6SEnnaYT4GzUhujj0EHzIc!wuVu|eGdh9-Nkbrw z;uHPG-6>Hve6_UuxT}!W^l+x>ep60!%Ko;0UGYB4@hj~#-_<0uFx-?~9&crIACLw5 zEdFy4c}`;XX3U#5#9$y|i&`Sr9I(j5EwZbq7%yl3@XH4^&=kYkYivC<7_8b`+qHdn z$k3*$ub@jK{e|vCJ@pQt83qZ_((j~Use%LGX6ppj_{+x4@^~|BbuQ<&nM{?M?GENF zWm2u{B6Iah?pt>u)iHvJfFIcna5gUK*M<8eTccvenb4w+aFa(d7^9R?l zO=Ezw1#;&lW1bq1m=HY{D0ZQ6JN@YO`;&JU=s< zsn1u)lG+4^*MVX&7rd8rHabtRoRzco#0_7+kDhvPSsJlKJCBmc=Z)hYY@~_rHkxIZ zBiOrF8{Kf1e2{Z1JYA2n&p5v^HxoEq%@w_$h4iDB_ya8@|J~K}ix8*i`om zA~G+yo)ZkCFvp6AX-&=dxCs(G$<=h@cs=I6_%1GsHuaEk0(Mk1s9I9@b(6U`c=sKfbY+tTqu}ST1DYIb5;Zu^gL1fpX;Y#leu&`h{`kjJ=_d=+Ax0|@SQ zCIz1^y)Ee^i)uz6uY+5J*TbhSXAkss%-f6&>*3|h1e-dzbYV!Gb!y(95OR8(N3oF( z5{PzFksg%cc5*6sQ?wSV)%#44ZMz;$ZJBFiD$(8Kk#siGmO^#e772WG{7EC<2YEdD z9^ift1jJa9YFnRyKT8fpbxmPw6`5^(%487U=lFPanii%WbG?DU74>jW1&Fs;VJUG#y^5ExjRD!EvIbU){ zJ%7;m-vr(Q_q6Y)B4M;3kiN*Oi8@7)pvhfVquDV)W-P)W$SrQ4`jbQW(1vTYHN-AW zm+Bd7Tlwr1l2?Y>O%PVYx(N4s-mbfW8zL4XKuT}AfhT#=hfhbPnDD4+X=+V1Y_3o9 zB(?RG8=UYF00+aD9ZoCuxEXt%9G}b-TZ%Rb7XqbEi7>-r$DAjxXeS#T38%~rtD8yN|AH{5 zV(Zml_Vw79gukqAAz@fU$~+}l)3-1N-lck=iWQzDNl$R>*oHAr4ozx+TLAiV><^PH zs}F8l%--o&Z;wZ9?$4<~w2WcI4Y7_h#=eTf9K;EEfW3@y`97HKHJ~R~ky3y#C;26| zN)bmn*DiMJ@OhSwoPQIy|y z)nv)^W5`tA4X>Tt)6jneOE@=vT8!mAf`Q7MUl^ZI6LH883@f8xlJtqz&4*iJry7!f zeSSTT#DvA-CkzFI<=JveBO@@is?K~}O8+(h)#b13yy)-rRXfcwuFtRS;TR zCa*36b9Y4DE;tKBzHsX#pUEE(H&h)C*a&h{RL?3gHuDI&*g=t4^X);^lH)}zCrg}Q zC;TCpgUGBqmI%f*cEliIDK4S8jxc?~$b{T~Oc(=^DhS&k000u*007`VCyc_5roUD6 zHBaQXMc_Zqwds<|THoxx1_*b@t?}97DZ8m{x14w_Ve0%?f)zER&t9E-yI6<=2Vss0 z0109BlKIw~e|gdTbP-^WT?f_!{PSvQ3bu*2lt#i)~d$<&P0cvksgD0)+dSZ z*A{Ld-`3_OywNQQZ^rtMh|3INKRLNcVT&wo)kPZ&)xE7Zu9p&rwp+nnb5p>Lkkq`B8_E4b7uEJyM=qIf>lG42LT++oCDDk)aQjjlnT4kW2m| zj0Qf&<3_oaVO`t#PE4~*X-@C>?f~93luZ}e-z8{h@Kw)3?kyUh&a7jK zR5{EM^zKN9wo&uEJ8-Fqw^7hdABW*W3?Y6YWSKeRQGW}^T?Ez)9&1*l27trHYgVYE zI8hO$(_cml)ksIVP%nT*=bofu$SG)lhhzuULK=p%iPJXwBVgu6i-iu(q%pP7)J!yd zx@+9VJ7tdJoJp*DvEHsgqqJx*Q(vvU2x+9t{HI&BkyCc~MX_X?LJ@ZPlNfMg47j@; zW78n-1HQ=`@I^5Cn~y&N?aStFI=Ah%3oAZT6W<%Pn$)G!>j{G}zCq}(U{~zcfe3H( znVlVcDynf?Yt}T`D~^DJpnyeN8-I@2M3D^b>ZHoo)3IVhLn`Y+LkC`Btz8v2|}W0bElUzEpn1pNjIc|)TezMws-{7R2J^HB_I zTtb`uVp1fch67DJkPA51lcJf-vH5B}Ruq}fXr9$LcB#)-WMN*fIT=h4tSRQf!cJ{k zP|xQ9$R%mvOcz@!<6M)Q7^a4Lz8x`us?WMAF+T4J)6q;xC_YmZk4UG^x8Ovi9@pAv zKThIkNx)odLBxE%3>D>B=*b0x5*`1nHnxf+UE>->F;~K(vOt{N*dBhDJ~TFTcl}BR zx{@L#c5l%{tBC_@uQKoI6y4lOlGtHzp*&=2K0>TIWLi+84#C!Q-nBeA(omZ^Kcz~t zUmrW4AH;U)7^}9w3Z9_{x=FO7!wljj1?)Z~(_*V%> z55j@0yRifd;vpP6kM}xz5(z7v6Pvs4ZC68PF%>TsVm)0ic!gnG{;+nqC1O1RFBZ+p zZc<%v`?rk)YgnEpIN}Y^ALFvAY5wM~Aal)nx4xKo$!v|8{pJBtE9fS9wStpEyJBN# zCWMJnBVe>`xF&?ScsGE{A^CS~lS0S9?;e9SK(j5+q|PcwOg2R74E)2kM7E;h9jAy} zVzHn#P7xHLR0HA7u@bDQmyfPQ+HslM9Z))=9BHv{SN5ML8a3Z~VY5|s-e ziG8T21yAzg_uq$N6Ux`Dr?ik%YbmDIX$*$v>&J+re>#Oz4r_QkV5lORqqSmvUKP;A z`zn=z7w`bEmqyCayy^n0n=+rA)}bl~h23{Xj6!)D(9A?<`Vn+XFohZJk@6~&NC`-m zh#FH<{$mg;Rs&&0`3o_A!GMhwV`9Q`-(6n*f+aX_=olB`;FawFGso_Wi z`{cxeB{XWX>QXi#*BX|>6(+B0JPZXHT%YPR6K+TSeE`dr{kC@E5!;g4tx zy*>72Om!$IA~QnG970A+j{>4)PV*%xfds{`4G*-;lbd8sXBiu0C7}g0X}oD0L)Fu{ z8e-H5eQ9sj3ZypnC-Ies@bz}VWtQmF4Bx4B zi(-o63yMAhbwUhz=rNP1lG^jO^8>x3n2|Vt<&{27FG+^B*JRdjexZV_s$)FBJL3BV6o_az_%D`*lU`EyD`F>P zXa2*^Ctrs(MdV!}+kT);GSEkNy6&ou;&jUkxQlc564PG|ZaBB#+4z!cr;07QhPz+A zw}fk_jevbeGOCI~%wycn3QotSdH}so1#dG^K8*h*n22#|S%rRDg@@i4RDYoP`p~2; z-p>u6%44nz7~a~KNK`wdcE+}-%gik%HN6U6CQRbQ-L}wRv~MS@3O~8 zQtFx~LHz9Ty<%MkG6)rc9HmrE)_j(H9p0aZozo;ZC=!>qRBe*ntf1Tt4ZO!(jkj!` zJoST@(#`K0i;j)_X=e;~{-Dm)!E}9&00DqVfeMv8(k@TKaox%5?cfzP=wx^30^;p{ z^N$RAO6k%h;^(5r?DIhXoaZ>0I9lpi>6%*D(v$FjI7ST-U;Bbk_}z1w>a06xB$Ix0Vb z-;Jt%IVEHN;ZfvFH|mQh0GO+Mt*v&>< z9!LeXT#V#v;efN(SAO@l*L-d^RaNPm1MaJ;U)vtC7W9S;Sp&B-aU{}QsRpbc9^TS$ zaNHlKX~q+op3jVmeU6rk$KeZuTv8HZ@ns*;2{Xq@#wF#M#8zM|_q% zt;nY?Mn6alFsSQr0iMD2P+ol5N}(&=z3H5s8*iOl`ZRSo_HeB!ejLp%C^B2CKa=m2Vda|Y2vne5<6<%UH3K?~jkj9k8n0z5<@0ohep zmL6&_oqEh<$~CE^8nkT+TTxL#qunX7jGjZF$2cRL8<~~W)!yDNGr(D$UrcK9b~mT2 zOr^y-*vPM>)Y#PZ;W{M+oO#^w@cv%$HetZ@usW3|Y`q?_ifIUStx>;oH1quG;q>O| zIqIjh-s$PS^)nC(`((b#+ajjv?S8p>#<)_m&3UU?qSt6DzJ4sN!}}w%>%=N^&Evfn zZMQ=Fj6~7vVa2-I^=$d_#fjx&7CXt)t0U32>#nb8nt9aMHQ2XjW^TeTL!J-mkM#mnVRqkV zZuxUF@c-HVYieZVXm7}GZDDPvZvf!OU_Aa=p8BnLH7SHF_}A@!9e?E;BuuUT_kLbG zJ6+dL2uM^^^uI8FElXsqO|2aM(aC>-{;ut>Dw)9Fw9);4i2JLf-^Kk!{Xf*f|GT>X zK>as)^#4H~@&|+SKfm3-%lo8sbROYA{`h!*hyE|}{zd@;Ap`&Ct=*q*_K#NY``6#! zmxVIozmtE~C;yiN0I2tM{M;)3Teb2}^v{aLztBvSzoGxAU;N4bnbQ6j+k*Bt_8$rE zKiNOCVEV1tq**~20Px$YW|8mcNwtC;RPxv=y{S*DW z=HL6B_&3dT{|)}j9sb$seL<-HP4vIf|5^3-Px|kw>HqBb?|%5ZYO7E5_ek?!;onu~ l{k!T<8WilW_k#cYjebsXmOk%+2N1F|H00+M!2hS~{tpMkAr$}s diff --git a/src/Mod/Path/Tools/Shape/bullnose.fcstd b/src/Mod/Path/Tools/Shape/bullnose.fcstd index 740e99748c56e277591adb9408aa76ece124e2da..1213e0173c67a968a8c8fd0365c5735df6be06ef 100644 GIT binary patch delta 8949 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 delta 9600 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 diff --git a/src/Mod/Path/Tools/Shape/chamfer.fcstd b/src/Mod/Path/Tools/Shape/chamfer.fcstd index f2d39cff848769c346e8ab5c397fcb4dcbae3ea9..b7c4b059e35bb36b051539fc8ae78b550284baea 100644 GIT binary patch delta 11749 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 O%1S~^lJsw7=>GvtzsH0C diff --git a/src/Mod/Path/Tools/Shape/drill.fcstd b/src/Mod/Path/Tools/Shape/drill.fcstd index 5358197072d5722d6019f5c801a563afb5b85e66..6a9283407204dcfd6f854ba0077c53c4707d7bd1 100644 GIT binary patch delta 8987 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 diff --git a/src/Mod/Path/Tools/Shape/endmill.fcstd b/src/Mod/Path/Tools/Shape/endmill.fcstd index 5b5a76dc41bc35e827c9cdbb9bbd0f5dbe86fb57..965d29a72a10cfad7f8129feeb0110979b5cff6f 100644 GIT binary patch delta 9411 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 delta 10882 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 MsE8QJ-$m^I0e=}FK>z>% diff --git a/src/Mod/Path/Tools/Shape/thread-mill.fcstd b/src/Mod/Path/Tools/Shape/thread-mill.fcstd index c5252940bdcbcd3458500d7aaf8930188cb17176..d158c553ba30553e84c9669bffa0da6406230211 100644 GIT binary patch literal 13782 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 X5YYa|HAq0B4kji-g2IIV8oK`vtW-ra literal 13868 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 Z3{Fz4o4*E8KqOr)EW|{_iT{(g_&@g}3Y!1` delta 12726 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�Qcz=2@8Rw`+YScxG;ChHcb|;p>Vng6z|qJRNzz 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 Jr2lrV{vUjh*Uta| diff --git a/src/Mod/Path/Tools/toolbit-attributes.py b/src/Mod/Path/Tools/toolbit-attributes.py new file mode 100644 index 0000000000..610edb89b2 --- /dev/null +++ b/src/Mod/Path/Tools/toolbit-attributes.py @@ -0,0 +1,147 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2021 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 * +# * * +# *************************************************************************** +# +# Script for manipulating toolbit shapes in a batch. This is handy for making +# attributes consistent over multiple or all shape file. +# +# Most commands are straight forward, except for --set which can be used to +# set the actual value of a property, or, in the case of enumerations it can +# also be used to set the enum values. This might be particularly useful when +# adding more materials. +# +# The following example moves all properties from the "Extra" group into the +# "Attributes" group. Note that the Attributes group might or might not +# already exist. If it does exist the specified group gets merged in. +# +# ./toolbit-attributes.py --move 'Extra:Attributes' src/Mod/Path/Tools/Shape/*.fcstd +# +# This example sets the Flutes value of all Shapes to 0: +# +# ./toolbit-attributes.py --set Flutes=0 src/Mod/Path/Tools/Shape/*.fcstd +# +# Finally, this example sets the enumerations of the Material attribute: +# +# ./toolbit-attributes.py --set 'Material=[HSS,Carbide,Tool Steel,Titanium]' src/Mod/Path/Tools/Shape/*.fcstd +# +# After running this tool it might be necessary to open the shape files +# manually and make sure they are visible and the thumbprint image is +# saved. +# +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# !!! Final note: this is a dangerous tool and should not be shipped with FC. !!!! +# !!! It's sole purpose is to make life of the developers easier and ensure !!!! +# !!! consistent attributes across all toobit shapes. !!!! +# !!! A single typo can ruin a lot of toolbit shapes - make sure to only use !!!! +# !!! it if those shape files are under a version control system and you can !!!! +# !!! back out the changes easily. !!!! +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +import argparse +import os +import sys + +parser = argparse.ArgumentParser() +parser.add_argument('path', nargs='+', help='Shape file to process') +parser.add_argument('--move', metavar=':', help='Move attributes from group 1 into group 2') +parser.add_argument('--delete', metavar='prop', help='Delete the given attribute') +parser.add_argument('--set', metavar='prop=value', help='Set property value') +parser.add_argument('--print', action='store_true', help='If set attributes are printed as discovered') +parser.add_argument('--print-all', action='store_true', help='If set Shape attributes are also printed') +parser.add_argument('--save-changes', action='store_true', help='Unless specified the file is not saved') +parser.add_argument('--freecad', help='Directory FreeCAD binaries (libFreeCAD.so) if not installed') +args = parser.parse_args() + +if args.freecad: + sys.path.append(args.freecad) + +import FreeCAD +import Path +import PathScripts.PathPropertyBag as PathPropertyBag +import PathScripts.PathUtil as PathUtil + +set_var=None +set_val=None + +GroupMap = {} +if args.move: + g = args.move.split(':') + if len(g) != 2: + print("ERROR: {} not a valid group mapping".format(args.move)) + sys.exit(1) + GroupMap[g[0]] = g[1] + +if args.set: + s = args.set.split('=') + if len(s) != 2: + print("ERROR: {} not a valid group mapping".format(args.move)) + sys.exit(1) + set_var = s[0] + set_val = s[1] + +for i, fname in enumerate(args.path): + #print(fname) + doc = FreeCAD.openDocument(fname, True) + print("{}:".format(doc.Name)) + for o in doc.Objects: + if PathPropertyBag.IsPropertyBag(o): + print(" {}:".format(o.Label)) + for p in o.Proxy.getCustomProperties(): + grp = o.getGroupOfProperty(p) + typ = o.getTypeIdOfProperty(p) + ttp = PathPropertyBag.getPropertyTypeName(typ) + val = PathUtil.getProperty(o, p) + dsc = o.getDocumentationOfProperty(p) + enm = '' + enum = [] + if ttp == 'Enumeration': + enum = o.getEnumerationsOfProperty(p) + enm = "{}".format(','.join(enum)) + if GroupMap.get(grp): + group = GroupMap.get(grp) + print("move: {}.{} -> {}".format(grp, p, group)) + o.removeProperty(p) + o.Proxy.addCustomProperty(typ, p, group, dsc) + if enum: + print("enum {}.{}: {}".format(group, p, enum)) + setattr(o, p, enum) + PathUtil.setProperty(o, p, val) + if p == set_var: + print("set {}.{} = {}".format(grp, p, set_val)) + if ttp == 'Enumeration' and set_val[0] == '[': + enum = set_val[1:-1].split(',') + setattr(o, p, enum) + else: + PathUtil.setProperty(o, p, set_val) + if p == args.delete: + print("delete {}.{}".format(grp, p)) + o.removeProperty(p) + if not args.print_all and grp == 'Shape': + continue + if args.print or args.print_all: + print(" {:10} {:20} {:20} {:10} {}".format(grp, p, ttp, str(val), enm)) + if args.save_changes: + doc.recompute() + doc.save() + FreeCAD.closeDocument(doc.Name) + +print('-done-') From c7fd08f858519e31b4e382177b2ea02fc7874def Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 17 Jan 2021 10:48:17 -0800 Subject: [PATCH 060/168] Added full toolbit library path as tool-tip --- src/Mod/Path/PathScripts/PathToolBitLibraryGui.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index c4fd89a8a4..19a654bbd5 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -252,20 +252,20 @@ class ToolBitSelector(object): def columnNames(self): return ['#', 'Tool'] - def curLib(self): + def currentLibrary(self, shortNameOnly): libfile = PathPreferences.lastFileToolLibrary() if libfile is None or libfile == "": return "" - else: - libfile = os.path.split(PathPreferences.lastFileToolLibrary())[1] - libfile = os.path.splitext(libfile)[0] + elif shortNameOnly: + return os.path.splitext(os.path.basename(libfile))[0] return libfile def loadData(self): PathLog.track() self.toolModel.clear() self.toolModel.setHorizontalHeaderLabels(self.columnNames()) - self.form.lblLibrary.setText(self.curLib()) + self.form.lblLibrary.setText(self.currentLibrary(True)) + self.form.lblLibrary.setToolTip(self.currentLibrary(False)) self.factory.libraryOpen(self.toolModel) self.toolModel.takeColumn(3) self.toolModel.takeColumn(2) From be4d90a8b3a7e373490bbe3d488ed9e4fd87a44f Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 17 Jan 2021 16:51:58 -0800 Subject: [PATCH 061/168] Fixed view providers and materials --- src/Mod/Path/Tools/Shape/ballend.fcstd | Bin 12385 -> 15686 bytes src/Mod/Path/Tools/Shape/bullnose.fcstd | Bin 12612 -> 16021 bytes src/Mod/Path/Tools/Shape/chamfer.fcstd | Bin 13052 -> 15624 bytes src/Mod/Path/Tools/Shape/drill.fcstd | Bin 10903 -> 14400 bytes src/Mod/Path/Tools/Shape/endmill.fcstd | Bin 11664 -> 15056 bytes src/Mod/Path/Tools/Shape/probe.fcstd | Bin 11282 -> 14383 bytes src/Mod/Path/Tools/Shape/slittingsaw.fcstd | Bin 12392 -> 15661 bytes src/Mod/Path/Tools/Shape/thread-mill.fcstd | Bin 13782 -> 17166 bytes src/Mod/Path/Tools/Shape/v-bit.fcstd | Bin 13538 -> 13530 bytes src/Mod/Path/Tools/toolbit-attributes.py | 2 +- 10 files changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 src/Mod/Path/Tools/toolbit-attributes.py diff --git a/src/Mod/Path/Tools/Shape/ballend.fcstd b/src/Mod/Path/Tools/Shape/ballend.fcstd index 42c1b2d6bbe005e42439ef00678c4a8755b45624..512018de3f69ee26db8a03dd81ba120842826b7e 100644 GIT binary patch literal 15686 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 Q!ghv+0(^o5|LVH`2UIk3g#Z8m literal 12385 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 diff --git a/src/Mod/Path/Tools/Shape/bullnose.fcstd b/src/Mod/Path/Tools/Shape/bullnose.fcstd index 1213e0173c67a968a8c8fd0365c5735df6be06ef..d72bfd802d9f6089f03385d8307f1db063a6dbec 100644 GIT binary patch delta 11156 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 diff --git a/src/Mod/Path/Tools/Shape/chamfer.fcstd b/src/Mod/Path/Tools/Shape/chamfer.fcstd index b7c4b059e35bb36b051539fc8ae78b550284baea..c03bb04c139b9f99854add90c76e1f4cf4da8f78 100644 GIT binary patch delta 11314 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 MM8t^yCk^<&0G;w@CjbBd delta 8714 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 delta 7769 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

2gqLl@u0lVc1DN19u=GOal>iLPralfTvHj~vLm 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 G@&5z#E@}(_ diff --git a/src/Mod/Path/Tools/Shape/endmill.fcstd b/src/Mod/Path/Tools/Shape/endmill.fcstd index 965d29a72a10cfad7f8129feeb0110979b5cff6f..1d94bdf970b06430db0a6a0d6db10e3da960fd3e 100644 GIT binary patch delta 13315 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 delta 9877 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 I<_>QE2U5+vv;Y7A diff --git a/src/Mod/Path/Tools/Shape/probe.fcstd b/src/Mod/Path/Tools/Shape/probe.fcstd index 602cf02baddae6d8bcb9c4a968f26f301f392db4..eb3b77d683c75abf1d17283d600d8f7badf363f5 100644 GIT binary patch delta 11954 zcmZv?1yEf(-kl=2?-Q6{~yKFQ_@Qu5>yE`F3aCZ+7oZ$M%Iq#lx|5tCS zX7zN}^mLEZs_FSUQM=X&q$~#o4FmuH@PMk&&#L1gZ-2_r005jw008+zD(-0JZg1h> z#_VNpcV?icwkC}eFjYJKWYVD5$Lt-Pg_>zgaoeLo(zR(_ownt=Q8T-%)UqzfW{W5x4+^LoD(=s89; ze82u%+1=~bS(~&MA3qpEc|eCZ=@?1+{v{G#g~nOnyN9R8`T4mIciCq*DbM{5+)%Rv z8u;WjGODPFPmRCsL5 zu0j4O2y^eQ^ph>&O)qk1Gd3SJUelIhqSS`7b*FkaC*wDa5@p*Fj?S%%6J6o(Lddj} zir;RW)s<_h9WAzLofVJ8$3j`rB=$&(8@6oKtuKGvrv|n&I1G-;35PHG7aY1Yw5J(z z4#VyJpWUlLl{Ng0kYja(?klR;q;pdafatGN*>=cKM1Cx%bh|O zkHwGDG$St%1AjkQq8L8q4Ar0;$MHsfmvOphapa%?^*Av&1_J^UAwzLoRWQT<{+LI( z($zCWi|+5!gp&*0iXJiYHdXX-cXvtBYYVM6n&`$b>OIc*p-jBt=04GOoDnN{%2%;+ zff{Mu)?rIHC`xT{=A0A+n^YlgwbTnHhX^rE7EZXU%(Np{OXH!TVxV-&XsWcvC@%_AUD#ho=b$lfY*SIt zsp$LuwlWf#0iB;MEYFC@d+95{p^jvYYV|f(g(P@aaYc41h*xk1du{V2@Tg5N3 zy7eF$T+a;Oe-X+N0hZ)3<+-lTLDKV3GRxX&BReR5WkINdjOu;G_~QHRGEAVih);bq zUMI=QAf7&QyS)`l8eL&JWJ)xGl5%3mRxa~7XHNvaLE_Eal;DjBVLD5~oMa}jiH zfCb>nyX*CH2NGY1`@*Fs<@e=(JG6j@bDMp z349K&#aTmYds?tOLN1G{yzdaC5+Eg}*S;?xmd7{YtNShWQBQ?ygH%6m<7&j;u4FX< zH0f{Y=1*2>J$cVrT}E)tEHvkz%zmhEX=l+)_iERR3r!=Ap`cRT@#}1Cy7R07D;brj zY<^bQXf8oV3i~8@o+)%|pJuU)S@=v-q#Gn-SufNu@hjlbvsJ5>csWmA#)B%B_%Vwr z!n9u#Q-qTcX?WzZKJONPX(pKU-63lVJ2DU|)l({;BX+(hrmVoNkrzy3n~pX2-CY%t zO;l;-%Ghxwo*=8uO?iz`JySXLPtwGFrYK$*&XIXErwWkuhKgKL6?(@;x${_^Ng|A* zD$mHmmEk@I(%PeNMBbXKMu9M>34K=*znPWUNvFn%iat_WD3&Rx7t@Yo zFR_br;OFj%3f${`DKhrvG$Ez<^NKESOLEs3B76J>JbU+uf5vCuP_J=Fcc5t}bZyp+ z&HW~?Rdddfv#I7LFUVbD%jBI9AlD@vd=YssbxFLLTYSM27j!RmQ4Wf==1D2}^UdsQ zY}g#%@7MY#|3OvvwHeshYCRsX=_YCa{g8L_O?S}bU4(oq@qWM$dG2P#7WsSY&j#%? zZ5AEggDJ0X+Zhg#k-<8M>CCUprX4r@X%1Rv>54mWc0m25Q|l1XEfsh1G8DT(l^tTg z8U0q`(|{lP+(><^*BhYHVNVN{V1+4QtI)SI6Du7>&V+Te1G6$GX72P&SIh48=WdH` zrRZBtA~}x!38oYK$lE&&arl+jpb`|j@zcGk5c=+M=TGv-*nUa^D8&JJ>tcrs+vLj6 zvGzwETc3B-@#?0MLpW{$}O*=bsTXrx-3B5O5bmz9Ga^T?=X0e zTdG>A7dzdrHA|7CFFck6bZn8jb3dOPXz`NJ{frGWBN?ja&-%1sPX`JpOXDMIs=G)uu)S3zHe^5jcQLa&Th+i`W?I9D3j^Xhxp OQ`}FqMd?> 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 delta 8889 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 dDb5VJ7NW-YVg>-P05Y!D){^2e%V{s*y1>i7Tv diff --git a/src/Mod/Path/Tools/Shape/thread-mill.fcstd b/src/Mod/Path/Tools/Shape/thread-mill.fcstd index d158c553ba30553e84c9669bffa0da6406230211..64982f6478a239262973efcda02392e1027c3eda 100644 GIT binary patch delta 13633 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 L3yBc_TQd4T_olvU delta 10208 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 diff --git a/src/Mod/Path/Tools/Shape/v-bit.fcstd b/src/Mod/Path/Tools/Shape/v-bit.fcstd index e25840b9b8e73932f564abf24a05cdddaf7af4cc..f29c42f1c2824c038e155a547e47e524ea1cecde 100644 GIT binary patch delta 8276 zcmZvB1yEf}vo!~I3GSBQ?(TMQcL^SXyR-2C!4Doh5Zv_;g1bYo;4T4z1-C!>ZrytC zd-dEho3Hb#C9INE_50Qu$nonMq^z>auAWh1Lp7bPO=^|4eUiq=NnGhw% z)2zhX9CWuO>`!S3_r#s51RhcQzP^5a`K|J|tk&%Z8dLyq{LtwYuCxqDf^o|(m(f7a z*%Ir57t*8%J*5#`^v7;%0Aeo3W?`H2JQtU?iqe&huN7o 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 Date: Sun, 17 Jan 2021 18:18:46 -0800 Subject: [PATCH 062/168] Fixed loading of TC from template --- src/Mod/Path/PathScripts/PathToolController.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolController.py b/src/Mod/Path/PathScripts/PathToolController.py index 199d5cdf22..3f8405ccd3 100644 --- a/src/Mod/Path/PathScripts/PathToolController.py +++ b/src/Mod/Path/PathScripts/PathToolController.py @@ -250,7 +250,8 @@ def Create(name='TC: Default Tool', tool=None, toolNumber=1, assignViewProvider= if tool.ViewObject: tool.ViewObject.Visibility = False - obj.Tool = tool + if tool: + obj.Tool = tool obj.ToolNumber = toolNumber return obj @@ -260,7 +261,7 @@ def FromTemplate(template, assignViewProvider=True): PathLog.track() name = template.get(ToolControllerTemplate.Name, ToolControllerTemplate.Label) - obj = Create(name, assignViewProvider=True) + obj = Create(name, tool=False, assignViewProvider=True) obj.Proxy.setFromTemplate(obj, template) return obj From 8c4dfacb275b433fab4bfcf65270e9068bed7dd7 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 17 Jan 2021 19:04:51 -0800 Subject: [PATCH 063/168] Don't enforce working dir setup and don't copy toolbit shapes --- .../Path/PathScripts/PathToolBitLibraryGui.py | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index 19a654bbd5..dd91d78c4a 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -363,9 +363,7 @@ class ToolBitLibrary(object): def __init__(self): PathLog.track() - if not self.checkWorkingDir(): - return - + self.checkWorkingDir() self.factory = ModelFactory() self.temptool = None self.toolModel = PySide.QtGui.QStandardItemModel(0, len(self.columnNames())) @@ -413,15 +411,16 @@ class ToolBitLibrary(object): if ret == qm.Yes: os.mkdir(subdir, mode) - qm = PySide.QtGui.QMessageBox - ret = qm.question(None,'', "Copy example files to new {} directory?".format(dir), qm.Yes | qm.No) - if ret == qm.Yes: - src="{}/{}".format(defaultdir, dir) - src_files = os.listdir(src) - for file_name in src_files: - full_file_name = os.path.join(src, file_name) - if os.path.isfile(full_file_name): - shutil.copy(full_file_name, subdir) + if dir != 'Shape': + qm = PySide.QtGui.QMessageBox + ret = qm.question(None,'', "Copy example files to new {} directory?".format(dir), qm.Yes | qm.No) + if ret == qm.Yes: + src="{}/{}".format(defaultdir, dir) + src_files = os.listdir(src) + for file_name in src_files: + full_file_name = os.path.join(src, file_name) + if os.path.isfile(full_file_name): + shutil.copy(full_file_name, subdir) return True From c815115cae6e6b86f0de64acece0c0310690b3b5 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 19 Jan 2021 10:18:48 -0600 Subject: [PATCH 064/168] library file path bugs --- src/Mod/Path/PathScripts/PathPreferences.py | 3 ++- src/Mod/Path/PathScripts/PathToolBitLibraryGui.py | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPreferences.py b/src/Mod/Path/PathScripts/PathPreferences.py index 5b3760a767..dfab692aee 100644 --- a/src/Mod/Path/PathScripts/PathPreferences.py +++ b/src/Mod/Path/PathScripts/PathPreferences.py @@ -299,7 +299,8 @@ def lastPathToolLibrary(): def setLastPathToolLibrary(path): PathLog.track(path) curLib = lastFileToolLibrary() - if os.path.split(curLib)[0] != path: + PathLog.debug('curLib: {}'.format(curLib)) + if curLib and os.path.split(curLib)[0] != path: setLastFileToolLibrary('') # a path is known but not specific file return preferences().SetString(LastPathToolLibrary, path) diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index dd91d78c4a..7993baaf6b 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -383,6 +383,8 @@ class ToolBitLibrary(object): workingdir = os.path.dirname(PathPreferences.lastPathToolLibrary()) defaultdir = os.path.dirname(PathPreferences.pathDefaultToolsPath()) + PathLog.debug('workingdir: {} defaultdir: {}'.format(workingdir, defaultdir)) + dirOK = lambda : workingdir != defaultdir and (os.access(workingdir, os.W_OK)) if dirOK(): @@ -400,6 +402,7 @@ class ToolBitLibrary(object): PathPreferences.filePath()) PathPreferences.setLastPathToolLibrary("{}/Library".format(workingdir)) + PathLog.debug('setting workingdir to: {}'.format(workingdir)) subdirlist = ['Bit', 'Library', 'Shape'] mode = 0o777 @@ -605,8 +608,9 @@ class ToolBitLibrary(object): else: tools.append({'nr': toolNr, 'path': PathToolBit.findRelativePathTool(toolPath)}) - with open(self.path, 'w') as fp: - json.dump(library, fp, sort_keys=True, indent=2) + if self.path is not None: + with open(self.path, 'w') as fp: + json.dump(library, fp, sort_keys=True, indent=2) def libraryOk(self): self.librarySave() From c1202c3afaaa71a7ea1383af13efd6b53646c108 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 19 Jan 2021 11:11:05 -0600 Subject: [PATCH 065/168] check working dir on dock open as well as manager open. use os.path.sep liberally choose the first library if no other is selected --- .../Path/PathScripts/PathToolBitLibraryGui.py | 123 +++++++++--------- 1 file changed, 65 insertions(+), 58 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index 7993baaf6b..931ca46dea 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -52,6 +52,66 @@ _PathRole = PySide.QtCore.Qt.UserRole + 2 def translate(context, text, disambig=None): return PySide.QtCore.QCoreApplication.translate(context, text, disambig) +def checkWorkingDir(): + # users shouldn't use the example toolbits and libraries. + # working directory should be writable + PathLog.track() + + workingdir = os.path.dirname(PathPreferences.lastPathToolLibrary()) + defaultdir = os.path.dirname(PathPreferences.pathDefaultToolsPath()) + + PathLog.debug('workingdir: {} defaultdir: {}'.format(workingdir, defaultdir)) + + dirOK = lambda : workingdir != defaultdir and (os.access(workingdir, os.W_OK)) + + if dirOK(): + return True + + qm = PySide.QtGui.QMessageBox + ret = qm.question(None,'', "Toolbit working directory not set up. Do that now?", qm.Yes | qm.No) + + if ret == qm.No: + return False + + msg = translate("Path", "Choose a writable location for your toolbits", None) + while not dirOK(): + workingdir = PySide.QtGui.QFileDialog.getExistingDirectory(None, msg, + PathPreferences.filePath()) + + if workingdir[-8:] == os.path.sep + 'Library': + workingdir = workingdir[:-8] # trim off trailing /Library if user chose it + + PathPreferences.setLastPathToolLibrary("{}{}Library".format(workingdir, os.path.sep)) + PathLog.debug('setting workingdir to: {}'.format(workingdir)) + + subdirlist = ['Bit', 'Library', 'Shape'] + mode = 0o777 + for dir in subdirlist: + subdir = "{}{}{}".format(workingdir, os.path.sep, dir) + if not os.path.exists(subdir): + qm = PySide.QtGui.QMessageBox + ret = qm.question(None,'', "Toolbit Working directory {} should contain a '{}' subdirectory. Create it?".format(workingdir, dir), qm.Yes | qm.No) + + if ret == qm.Yes: + os.mkdir(subdir, mode) + if dir != 'Shape': + qm = PySide.QtGui.QMessageBox + ret = qm.question(None,'', "Copy example files to new {} directory?".format(dir), qm.Yes | qm.No) + if ret == qm.Yes: + src="{}{}{}".format(defaultdir, os.path.sep, dir) + src_files = os.listdir(src) + for file_name in src_files: + full_file_name = os.path.join(src, file_name) + if os.path.isfile(full_file_name): + shutil.copy(full_file_name, subdir) + + + # if no library is set, choose the first one in the Library directory + if PathPreferences.lastFileToolLibrary() is None: + libFiles = [f for f in glob.glob(PathPreferences.lastPathToolLibrary() + os.path.sep + '*.fctl')] + PathPreferences.setLastFileToolLibrary(libFiles[0]) + + return True class _TableView(PySide.QtGui.QTableView): '''Subclass of QTableView to support rearrange and copying of ToolBits''' @@ -205,7 +265,7 @@ class ModelFactory(object): path = PathPreferences.lastPathToolLibrary() if os.path.isdir(path): # opening all tables in a directory - libFiles = [f for f in glob.glob(path + '/*.fctl')] + libFiles = [f for f in glob.glob(path + os.path.sep + '*.fctl')] libFiles.sort() for libFile in libFiles: loc, fnlong = os.path.split(libFile) @@ -243,6 +303,7 @@ class ToolBitSelector(object): '''Controller for displaying a library and creating ToolControllers''' def __init__(self): + checkWorkingDir() self.form = FreeCADGui.PySideUic.loadUi(':/panels/ToolBitSelector.ui') self.factory = ModelFactory() self.toolModel = PySide.QtGui.QStandardItemModel(0, len(self.columnNames())) @@ -273,7 +334,6 @@ class ToolBitSelector(object): def setupUI(self): PathLog.track() self.loadData() - self.form.tools.setModel(self.toolModel) self.form.tools.selectionModel().selectionChanged.connect(self.enableButtons) self.form.tools.doubleClicked.connect(partial(self.selectedOrAllToolControllers)) @@ -363,7 +423,7 @@ class ToolBitLibrary(object): def __init__(self): PathLog.track() - self.checkWorkingDir() + checkWorkingDir() self.factory = ModelFactory() self.temptool = None self.toolModel = PySide.QtGui.QStandardItemModel(0, len(self.columnNames())) @@ -375,59 +435,6 @@ class ToolBitLibrary(object): self.setupUI() self.title = self.form.windowTitle() - def checkWorkingDir(self): - # users shouldn't use the example toolbits and libraries. - # working directory should be writable - PathLog.track() - - workingdir = os.path.dirname(PathPreferences.lastPathToolLibrary()) - defaultdir = os.path.dirname(PathPreferences.pathDefaultToolsPath()) - - PathLog.debug('workingdir: {} defaultdir: {}'.format(workingdir, defaultdir)) - - dirOK = lambda : workingdir != defaultdir and (os.access(workingdir, os.W_OK)) - - if dirOK(): - return True - - qm = PySide.QtGui.QMessageBox - ret = qm.question(None,'', "Toolbit working directory not set up. Do that now?", qm.Yes | qm.No) - - if ret == qm.No: - return False - - msg = translate("Path", "Choose a writable location for your toolbits", None) - while not dirOK(): - workingdir = PySide.QtGui.QFileDialog.getExistingDirectory(None, msg, - PathPreferences.filePath()) - - PathPreferences.setLastPathToolLibrary("{}/Library".format(workingdir)) - PathLog.debug('setting workingdir to: {}'.format(workingdir)) - - subdirlist = ['Bit', 'Library', 'Shape'] - mode = 0o777 - for dir in subdirlist: - subdir = "{}/{}".format(workingdir, dir) - if not os.path.exists(subdir): - qm = PySide.QtGui.QMessageBox - ret = qm.question(None,'', "Toolbit Working directory {} should contain a '{}' subdirectory. Create it?".format(workingdir, dir), qm.Yes | qm.No) - - if ret == qm.Yes: - os.mkdir(subdir, mode) - if dir != 'Shape': - qm = PySide.QtGui.QMessageBox - ret = qm.question(None,'', "Copy example files to new {} directory?".format(dir), qm.Yes | qm.No) - if ret == qm.Yes: - src="{}/{}".format(defaultdir, dir) - src_files = os.listdir(src) - for file_name in src_files: - full_file_name = os.path.join(src, file_name) - if os.path.isfile(full_file_name): - shutil.copy(full_file_name, subdir) - - return True - - def toolBitNew(self): PathLog.track() @@ -443,7 +450,7 @@ class ToolBitLibrary(object): # Parse out the name of the file and write the structure loc, fil = os.path.split(filename) fname = os.path.splitext(fil)[0] - fullpath = "{}/{}.fctb".format(loc, fname) + fullpath = "{}{}{}.fctb".format(loc, os.path.sep, fname) PathLog.debug(fullpath) self.temptool = PathToolBit.ToolBitFactory().Create(name=fname) @@ -468,7 +475,7 @@ class ToolBitLibrary(object): loc, fil = os.path.split(f) fname = os.path.splitext(fil)[0] - fullpath = "{}/{}.fctb".format(loc, fname) + fullpath = "{}{}{}.fctb".format(loc, os.path.sep, fname) self.factory.newTool(self.toolModel, fullpath) From c8d3210ba45edb22a348f1cb90c557ad037adcf8 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 19 Jan 2021 18:16:15 -0800 Subject: [PATCH 066/168] Fixed relative path search for shapes --- src/Mod/Path/PathScripts/PathToolBit.py | 26 +++++++++++++--------- src/Mod/Path/PathScripts/PathToolBitGui.py | 6 ++--- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 0444f03118..38e8a4ce4e 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -88,12 +88,13 @@ def _findToolFile(name, containerFile, typ): def findToolShape(name, path=None): '''findToolShape(name, path) ... search for name, if relative path look in path''' + PathLog.track(name, path) return _findToolFile(name, path, 'Shape') def findToolBit(name, path=None): '''findToolBit(name, path) ... search for name, if relative path look in path''' - PathLog.track(name) + PathLog.track(name, path) if name.endswith('.fctb'): return _findToolFile(name, path, 'Bit') return _findToolFile("{}.fctb".format(name), path, 'Bit') @@ -101,12 +102,14 @@ def findToolBit(name, path=None): def findToolLibrary(name, path=None): '''findToolLibrary(name, path) ... search for name, if relative path look in path''' + PathLog.track(name, path) if name.endswith('.fctl'): return _findToolFile(name, path, 'Library') return _findToolFile("{}.fctl".format(name), path, 'Library') def _findRelativePath(path, typ): + PathLog.track(path, typ) relative = path for p in PathPreferences.searchPathsTool(typ): if path.startswith(p): @@ -131,8 +134,8 @@ def findRelativePathLibrary(path): class ToolBit(object): - def __init__(self, obj, shapeFile): - PathLog.track(obj.Label, shapeFile) + def __init__(self, obj, shapeFile, path=None): + PathLog.track(obj.Label, shapeFile, path) self.obj = obj obj.addProperty('App::PropertyFile', 'BitShape', 'Base', translate('PathToolBit', 'Shape for bit shape')) obj.addProperty('App::PropertyLink', 'BitBody', 'Base', translate('PathToolBit', 'The parametrized body representing the tool bit')) @@ -140,6 +143,8 @@ class ToolBit(object): obj.addProperty('App::PropertyString', 'ShapeName', 'Base', translate('PathToolBit', 'The name of the shape file')) obj.addProperty('App::PropertyStringList', 'BitPropertyNames', 'Base', translate('PathToolBit', 'List of all properties inherited from the bit')) + if path: + obj.File = path if shapeFile is None: obj.BitShape = 'endmill.fcstd' self._setupBitShape(obj) @@ -397,9 +402,9 @@ def Declaration(path): class ToolBitFactory(object): - def CreateFromAttrs(self, attrs, name='ToolBit'): - PathLog.debug(attrs) - obj = Factory.Create(name, attrs['shape']) + def CreateFromAttrs(self, attrs, name='ToolBit', path=None): + PathLog.track(attrs, path) + obj = Factory.Create(name, attrs['shape'], path) obj.Label = attrs['name'] params = attrs['parameter'] for prop in params: @@ -412,17 +417,16 @@ class ToolBitFactory(object): PathLog.track(name, path) try: data = Declaration(path) - bit = Factory.CreateFromAttrs(data, name) - bit.File = path + bit = Factory.CreateFromAttrs(data, name, path) return bit except (OSError, IOError) as e: PathLog.error("%s not a valid tool file (%s)" % (path, e)) raise - def Create(self, name='ToolBit', shapeFile=None): - PathLog.track(name, shapeFile) + def Create(self, name='ToolBit', shapeFile=None, path=None): + PathLog.track(name, shapeFile, path) obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', name) - obj.Proxy = ToolBit(obj, shapeFile) + obj.Proxy = ToolBit(obj, shapeFile, path) return obj diff --git a/src/Mod/Path/PathScripts/PathToolBitGui.py b/src/Mod/Path/PathScripts/PathToolBitGui.py index c36bb83ec0..bc8b2ba7c4 100644 --- a/src/Mod/Path/PathScripts/PathToolBitGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitGui.py @@ -170,13 +170,13 @@ class TaskPanel: class ToolBitGuiFactory(PathToolBit.ToolBitFactory): - def Create(self, name='ToolBit', shapeFile=None): + def Create(self, name='ToolBit', shapeFile=None, path=None): '''Create(name = 'ToolBit') ... creates a new tool bit. It is assumed the tool will be edited immediately so the internal bit body is still attached.''' - PathLog.track(name, shapeFile) + PathLog.track(name, shapeFile, path) FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', 'Create ToolBit')) - tool = PathToolBit.ToolBitFactory.Create(self, name, shapeFile) + tool = PathToolBit.ToolBitFactory.Create(self, name, shapeFile, path) PathIconViewProvider.Attach(tool.ViewObject, name) FreeCAD.ActiveDocument.commitTransaction() return tool From f26e631ab6ef69dae057fbe4d8f209bd923c43ae Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 20 Jan 2021 16:24:31 -0600 Subject: [PATCH 067/168] make initial path defaults slightly more intuitive --- src/Mod/Path/PathScripts/PathPreferences.py | 4 ++++ src/Mod/Path/PathScripts/PathToolBitGui.py | 3 ++- src/Mod/Path/PathScripts/PathToolBitLibraryGui.py | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathPreferences.py b/src/Mod/Path/PathScripts/PathPreferences.py index dfab692aee..a1c1e58fc5 100644 --- a/src/Mod/Path/PathScripts/PathPreferences.py +++ b/src/Mod/Path/PathScripts/PathPreferences.py @@ -246,15 +246,19 @@ def setDefaultTaskPanelLayout(style): def experimentalFeaturesEnabled(): return preferences().GetBool(EnableExperimentalFeatures, False) + def suppressAllSpeedsWarning(): return preferences().GetBool(WarningSuppressAllSpeeds, True) + def suppressRapidSpeedsWarning(): return suppressAllSpeedsWarning() or preferences().GetBool(WarningSuppressRapidSpeeds, True) + def suppressSelectionModeWarning(): return preferences().GetBool(WarningSuppressSelectionMode, True) + def suppressOpenCamLibWarning(): return preferences().GetBool(WarningSuppressOpenCamLib, True) diff --git a/src/Mod/Path/PathScripts/PathToolBitGui.py b/src/Mod/Path/PathScripts/PathToolBitGui.py index bc8b2ba7c4..1bf56aea68 100644 --- a/src/Mod/Path/PathScripts/PathToolBitGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitGui.py @@ -247,7 +247,8 @@ def GetToolShapeFile(parent=None): location, '*.fcstd') if fname and fname[0]: if fname != location: - PathPreferences.setLastPathToolShape(location) + newloc = os.path.dirname(fname[0]) + PathPreferences.setLastPathToolShape(newloc) return fname[0] else: return None diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index 931ca46dea..ea12f438b4 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -82,6 +82,7 @@ def checkWorkingDir(): workingdir = workingdir[:-8] # trim off trailing /Library if user chose it PathPreferences.setLastPathToolLibrary("{}{}Library".format(workingdir, os.path.sep)) + PathPreferences.setLastPathToolBit("{}{}Bit".format(workingdir, os.path.sep)) PathLog.debug('setting workingdir to: {}'.format(workingdir)) subdirlist = ['Bit', 'Library', 'Shape'] From 4f0f9e78f1bd877f3d6819a1389bc69a6db4c4c2 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 23 Jan 2021 11:41:00 -0800 Subject: [PATCH 068/168] Added test case to determine directory layout. --- src/Mod/Path/PathTests/TestPathToolBit.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Mod/Path/PathTests/TestPathToolBit.py b/src/Mod/Path/PathTests/TestPathToolBit.py index 9d1e05e5c1..e3a639e2c5 100644 --- a/src/Mod/Path/PathTests/TestPathToolBit.py +++ b/src/Mod/Path/PathTests/TestPathToolBit.py @@ -22,6 +22,7 @@ import PathScripts.PathToolBit as PathToolBit import PathTests.PathTestUtils as PathTestUtils +import glob import os TestToolDir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'Tools') @@ -40,8 +41,25 @@ def testToolBit(path = TestToolDir, name = TestToolBitName): def testToolLibrary(path = TestToolDir, name = TestToolLibraryName): return os.path.join(path, 'Library', name) +def printTree(path, indent): + print("{} {}".format(indent, os.path.basename(path))) + if os.path.isdir(path): + if os.path.basename(path).startswith('__'): + print("{} ...".format(indent)) + else: + for foo in sorted(glob.glob(os.path.join(path, '*'))): + printTree(foo, "{} ".format(indent)) + class TestPathToolBit(PathTestUtils.PathTestBase): + def test(self): + '''Log test setup''' + print() + print("realpath : {}".format(os.path.realpath(__file__))) + print(" Tools : {}".format(TestToolDir)) + print(" dir : {}".format(os.path.dirname(os.path.realpath(__file__)))) + printTree(os.path.dirname(os.path.realpath(__file__)), " :") + def test00(self): '''Find a tool shape from file name''' path = PathToolBit.findToolShape('endmill.fcstd') From 26ba120aafe845b707b9056de13fe285f6cf3746 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 23 Jan 2021 15:58:54 -0800 Subject: [PATCH 069/168] Added Tools directory for recursive test file copy --- src/Mod/Path/CMakeLists.txt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 918bb073b1..0c680e4262 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -212,9 +212,6 @@ SET(PathTests_SRCS PathTests/TestPathUtil.py PathTests/TestPathVcarve.py PathTests/TestPathVoronoi.py - PathTests/Tools/Bit/test-path-tool-bit-bit-00.fctb - PathTests/Tools/Library/test-path-tool-bit-library-00.fctl - PathTests/Tools/Shape/test-path-tool-bit-shape-00.fcstd PathTests/boxtest.fcstd PathTests/test_centroid_00.ngc PathTests/test_geomop.fcstd @@ -286,6 +283,14 @@ INSTALL( Mod/Path/PathTests ) +INSTALL( + DIRECTORY + PathTests/Tools + DESTINATION + Mod/Path/PathTests +) + + INSTALL( FILES ${PathScripts_post_SRCS} From 571be1faf2dd5ec17e5dbc92d6c9c01363f2dab6 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 24 Jan 2021 13:15:07 -0800 Subject: [PATCH 070/168] Added commit to explain the purpose of dumping the directory tree --- src/Mod/Path/PathTests/TestPathToolBit.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathTests/TestPathToolBit.py b/src/Mod/Path/PathTests/TestPathToolBit.py index e3a639e2c5..7a684b3697 100644 --- a/src/Mod/Path/PathTests/TestPathToolBit.py +++ b/src/Mod/Path/PathTests/TestPathToolBit.py @@ -53,7 +53,10 @@ def printTree(path, indent): class TestPathToolBit(PathTestUtils.PathTestBase): def test(self): - '''Log test setup''' + '''Log test setup directory structure''' + # Enable this test if there are errors showing up in the build system with the + # paths that work OK locally. It'll print out the directory tree, and if it + # doesn't look right you know where to look for it print() print("realpath : {}".format(os.path.realpath(__file__))) print(" Tools : {}".format(TestToolDir)) From 231ad8f96968d605c668191d7f8708421652f888 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Mon, 25 Jan 2021 11:18:43 -0600 Subject: [PATCH 071/168] fix bug when declining the creation of workdir subdirectories. --- .../Path/PathScripts/PathToolBitLibraryGui.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index ea12f438b4..621ad0e996 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -89,11 +89,19 @@ def checkWorkingDir(): mode = 0o777 for dir in subdirlist: subdir = "{}{}{}".format(workingdir, os.path.sep, dir) - if not os.path.exists(subdir): - qm = PySide.QtGui.QMessageBox - ret = qm.question(None,'', "Toolbit Working directory {} should contain a '{}' subdirectory. Create it?".format(workingdir, dir), qm.Yes | qm.No) + if os.path.exists(subdir): + subdirlist.remove(dir) - if ret == qm.Yes: + if len(subdirlist) >= 1: + needed = ', '.join([str(d) for d in subdirlist]) + qm = PySide.QtGui.QMessageBox + ret = qm.question(None,'', "Toolbit Working directory {} needs these sudirectories:\n {} \n Create them?".format(workingdir, needed), qm.Yes | qm.No) + + if ret == qm.No: + return False + else: + for dir in subdirlist: + subdir = "{}{}{}".format(workingdir, os.path.sep, dir) os.mkdir(subdir, mode) if dir != 'Shape': qm = PySide.QtGui.QMessageBox From 86b57384335eccb472d8e88064cdff4920b011ba Mon Sep 17 00:00:00 2001 From: sliptonic Date: Mon, 25 Jan 2021 11:56:05 -0600 Subject: [PATCH 072/168] tool bugs --- src/Mod/Path/Tools/Bit/45degree_chamfer.fctb | 2 +- src/Mod/Path/Tools/Bit/5mm-thread-cutter.fctb | 6 +++--- src/Mod/Path/Tools/Bit/probe.fctb | 2 +- src/Mod/Path/Tools/Bit/slittingsaw.fctb | 4 ++-- src/Mod/Path/Tools/Shape/chamfer.fcstd | Bin 15624 -> 11667 bytes src/Mod/Path/Tools/Shape/probe.fcstd | Bin 14383 -> 10479 bytes src/Mod/Path/Tools/Shape/slittingsaw.fcstd | Bin 15661 -> 11765 bytes 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Mod/Path/Tools/Bit/45degree_chamfer.fctb b/src/Mod/Path/Tools/Bit/45degree_chamfer.fctb index 6c1231ed0f..77e53e523c 100644 --- a/src/Mod/Path/Tools/Bit/45degree_chamfer.fctb +++ b/src/Mod/Path/Tools/Bit/45degree_chamfer.fctb @@ -11,4 +11,4 @@ "ShankDiameter": "6.3500 mm" }, "attribute": {} -} \ No newline at end of file +} diff --git a/src/Mod/Path/Tools/Bit/5mm-thread-cutter.fctb b/src/Mod/Path/Tools/Bit/5mm-thread-cutter.fctb index 265978053b..2a5f0253cd 100644 --- a/src/Mod/Path/Tools/Bit/5mm-thread-cutter.fctb +++ b/src/Mod/Path/Tools/Bit/5mm-thread-cutter.fctb @@ -1,14 +1,14 @@ { "version": 2, - "name": "3mm-thread-cutter", + "name": "5mm-thread-cutter", "shape": "thread-mill.fcstd", "parameter": { "Crest": "0.10 mm", "Diameter": "5.00 mm", "Length": "50.00 mm", "NeckDiameter": "3.00 mm", - "NeckHeight": "20.00 mm", + "NeckLength": "20.00 mm", "ShankDiameter": "5.00 mm" }, "attribute": {} -} \ No newline at end of file +} diff --git a/src/Mod/Path/Tools/Bit/probe.fctb b/src/Mod/Path/Tools/Bit/probe.fctb index b92828e7ac..31fa354ff6 100644 --- a/src/Mod/Path/Tools/Bit/probe.fctb +++ b/src/Mod/Path/Tools/Bit/probe.fctb @@ -1,6 +1,6 @@ { "version": 2, - "name": "Probe004", + "name": "Probe", "shape": "probe.fcstd", "parameter": { "Diameter": "6.0000 mm", diff --git a/src/Mod/Path/Tools/Bit/slittingsaw.fctb b/src/Mod/Path/Tools/Bit/slittingsaw.fctb index e9d33fe571..6424fc2528 100644 --- a/src/Mod/Path/Tools/Bit/slittingsaw.fctb +++ b/src/Mod/Path/Tools/Bit/slittingsaw.fctb @@ -3,9 +3,9 @@ "name": "Slitting Saw", "shape": "slittingsaw.fcstd", "parameter": { - "BladeThickness": "3.0000 mm", + "Blade_Thickness": "3.0000 mm", "BoltHeight": "3.0000 mm", - "BoltWidth": "8.0000 mm", + "BoltDiameter": "8.0000 mm", "Diameter": "76.2000 mm", "Length": "50.0000 mm", "ShankDiameter": "19.0500 mm" diff --git a/src/Mod/Path/Tools/Shape/chamfer.fcstd b/src/Mod/Path/Tools/Shape/chamfer.fcstd index c03bb04c139b9f99854add90c76e1f4cf4da8f78..fd46e0c88cc3b1a92eb08c9bf570084fab739d7d 100644 GIT binary patch delta 10511 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 n4YdGv};*_Wk!g_gkwzRMq=* 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 diff --git a/src/Mod/Path/Tools/Shape/probe.fcstd b/src/Mod/Path/Tools/Shape/probe.fcstd index eb3b77d683c75abf1d17283d600d8f7badf363f5..56e7945c95013313260921d4a274580dfb3d3698 100644 GIT binary patch literal 10479 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 literal 14383 zcmajG1$5j>7OvZ7W@hG?nIUFoW{jDc8DeH;W@d(%VrJ&pj@dEeb>^;f=bX%&dDW|0 zTHX4jU(#Qas`i$=6bL9P004jlR0c6Bjt9Q|EJXkS(8B=$*pFX@ZH=6*O>CU#+^nt6 zbhMP##L<1GYNnqI>a{x9+Sd#XiXnqOsTk`68z)#<7X@uBr(=$ijS`*KY}G7SXvBAz zjH1Ib6jP(gZ^`9&=J0jhkH@T)U1tTwVLUw7G-W?&61_2Cy1%)--|}=H!|T3Z|ElQf zaq6f^+>4DJ2qZb6#GJGZCwTuH4yizH&-2yA)#d#B+=IQ8(Mim8za1mU=zttDX^oIH z;&5+Ci~Sp6m+pm1%U3#g`mrH9A)AeuV_Ys%F;0X)`=d?MocZh@Uei(CKPi3Ll@Dqz zIv;HD4wf_GDEcWJS3WF=K%Kxx?P=eJNOhoRQq&(2j9 z7CP2;W#t{BzG}AJuO*wY4+=7cm77U{$I4J$SESL2vBIP;vZ7J@Zgzl(-9Qy0qJlvJ0mM%hMU#}{J zl;2k5u%5f2tcSC+L!wq|P@UdH7m{Ajar!rTybUMkiPq!vXx>w<@|6qtaP!u7OPm2g zGLtj=M1Szaa$&Qjo&+Mugp<#jVw}XxyGAV`rC@MWHE?ySrHE9hrO#RHeUJ{!)>cVK z=xB2u1S9wQTqW^za*yHrn#x=tEAA?2blq`o9A!s&iwxRZZhO&Rt-jQ?<0jG4a%Pd~ zHPY(cl+-}*ca|Eq=cuVX)R*^{OzBOPRO{tNAS?5GYiJxa6)1 zXRYKsQmmwTmM%bF9Y$l9QL(RO^4%G$EVVw?jehvkq3SO3*?g4s|?jDW?W zJGZN`O=%``2M@~Cb;Z_-{!xfSqdwZIDZhYcci%kF+UQMc6|2zf)&+N9JtK7gg)dtG zwK$hH*Ku_Yn2LjhPSQ#p)<*UVJya!dM9(YIXU~w!V4j{rF6Gf!jYKn@A| zru%Bnw>ujhR+Q1$uFaanRwfUFZnyty3)TmYhoRRCj;_=nHdn+5%97H=zGf+&v5#<3`KP-#yMoP*C{ zQIYce@3=KKHk~=vP%CKUNG(RnEmW64!}&epT+d{> z)K4?%$4oq?iPLnFP|O#qX}M)EshFx1i{0!eFJsG>xRDFXgVkT-llc>&$vLD^7)} zo++GqC#qsR6BjKEWlKC7lln-ygM=?B^1Y+M-MOsJB;rJnmZfK6NU)!yQdq;Wgx?w~ zMj(;lc&@~U7?oOyr^E;g7oR876dMp|GNVYymdYp>QH-N4F$=R`mKJL)yW@l5q>XriMN?kbionhe=l}X7H!UvT>LY{=u33)9M_N6x+m`eMd!5{@aQTn zjs(L^g1-Af_okaJ|H->B=@z_wpKsFaO|s3>_vVZ`Z8NPV?d}7~uW#GwHsRp`8qjHU zuXKj(H{7W?daIkHXTj7)}Omfy5u5nRdJktix~bRD>Z{ zUj2*Vtj16GDg&vy#_c~zAESB6@xT@NiOmk7GF<(8RVMQYfhr+0p=`s;0kO;aWG9TMkpb7c$JVu$m!Y6*<^h079;h9yi_ z4&%vz8Ye#GNOZ6f{$L$<=BEv7N?)JSR4&}cYP6`nA7cGdg=MAAeyYs58TT1L-a9Uh_GzX4u;?eq>u^Wov|>dWMb1I%>k2tu+5y~A;EF)ldXgv78LADxn34k&nLlN zX@>9~2koha(|!*uq^(P+o?&LiX*2J2ian5VhLvt1bE$7pa_X^4X5U3<|D}>vXv9ci z^5a{=tL&unErHCltr(F8Q{wBS+}uOX4MAp1o-1#R#LXP0-eryJKG75c6K90BSsF^Q z2TR4cB=?#K+%P)Jq?~b+z|WIG2X=2 z$J_ECTJxj@4O53#YqsaTM<2m!FIN3Wux51c3@3e>$XWj6Ri3nC*)2Re`}Xl z%%9yXYLeYx5Y7j;Mwz=-inJ_4K>-EpL!ILWF3(gt79(#|UJ*o8>$UPAgC|<7%BPBH7T8&4E1m zSw58O$LaIr{oXV>3kL8z*nV)*8-`P_7Oni82N9v#@nb7-J$y=! zHolnzfCrf);AZ0aQ56??bCAaQ%-e0-u{Y9YS6w$LI{L!iQ3&Zzz79TgXnI6E z8TU6>CfP@dHu3~LB`vdrJIK+FPgI^ljPsDoEj|&`8EkVRSQJ6;y=*?Y#nMRYV#ODU z@c`xECd3|Mqf@Zi#CoF6afTkky@k&ElaH-?@W8Eik_fT272@Y;K4XwDsO?t_f*7gr zWnr6>;T&Ef2dCGhHGlru=YCXJ{&!r-!5|pzLVDO?ORt9UJxh;YKw^o`~PyU>5n~Xx{2evZ2=*)7B&O0pX;l}u2 z?q#7GWa!hCxG1Tj-v`>K(fZ$&Vc!@`Y&xsqe0n(d9a>U=;{~1z4Wi;)Dh9 zmFF=-g-dg-&I)|%VxudKUT2+2Cnp@~NIH7Mhb8vjc#UENT(Lh{rX|YNu_HLu_FTcq zVyx&UHH?1U?K5v`qj2l3l-;}?a7Tq`@haV?g{$+K zVq$K1$YHDsp+H0db4P`}=N=^i>5F2LZdbZYba9AEUJwqY1Ky8sO=S>&%Wxw>r$|_d zqwdlpOp5$E!?`-5iy{ST&KTu{zN(C7qG;uSzIW$%&?jEUSFrf>N)HTf5_RW-Zn+r= zT(8a>*F8vIWSWtFon@>>Pa)jqas^=DY&GqRWMxN2FkEDm2kU#lS+(+&7cWD#)q)T7 zVra<2CeRyX6vHN7ONCVD9$W8SxWv`zASI0OT8Rbk{q7rv=XQE9eIufz0UlHcujW?s z8Z&d-_r}+s{YnHMZrAgg?U%C!Ucl+e^(k8$_OmUzSl9kMxyj3oyOjxtGf8H4wOe>C zlmnVk42~_?VUQDZ-}Xv~5NO<5va5=nnC*Z}SE~El7s<&$?y+E#5c-twoDQbfk6S20 zWSaFq!<>tR6ZHA;sQduBNw=foea$gHl*ecssh3YitvD;Tj^eOqfR-E;4(gYtuD+j7 z)!ey62N20Bc;ni>6UhIgNAFQiLplZw0E8g}0B|2YdU*o}C#5e2b|!R&4t5*r8n(L( zh(0-bJHtB5xmVWws?*-9QoznRB&$W@Vam8kPZzG4ijqcR90E;ZD#5W_dubP#Yr#7U zZf>mYrU^guG?g1DTn6~-5awb|E!#AbhDz5HV}9LsYWAmCXxbc^PT<;YgLpj;TozIw z_PA&s3FpoU_1M~rWGcrg*`dpZZ(lhPVxm-T!@fUDZmAUeLL@}k7w4>y>auz(J=?O5 zL&SuvE$n5rR>9$}p)o#wLN*(3O^%g>tBliyMinA~W(iTWX^yaAwBJS*+lv&ju1q=d zN{KZk%-oliYFzg?i;OkPn*sVh5~b`09hn?EPt_=L5{0VV;@~s~^1^QHfo6q2n-9wn;PK9bOIoY?``tR8 zRAfiTufOACag#L}HX@}Aa*U&}NICrUTD%rUX`Yop%29h5B1?cy-564img!ifP#g4x zk4W?;F6lM)(&LL&YW2_Pcs0%G$*jw5`$WoS$Ji;54=fO!>5~Vs)KW{(h#;f&?del* zzt}D+M%95qfrK@O<0TysHR#higT#q->)rO@3mI!rfF<-&;1pR=rgRcPi$1N9@Idl^Y2g-OV?d=7 zgMywdCPBfN<-CyRGT~w3-WGzQ?P^lhn6Ta+W)gc5fBNpe6N~S`JQ3|qCI(^CUzCL{ zwCk5{rWAdlr?YVgRM8D1IDq%s1Y9bEYlRy*15(fIoUrvz(>Jl{3`7Hc3~E6D0Pue| zJ>zfJ7bVKr_A|hST)d%i+BgYLib;41_gc@9`!BZvf1}PrZ}J=L$thqY|4GQSsEXJY z{<`d=g9%qsu;WH-dfU7?e^QWIL2*^;j22o?djg$(B2rf%LW4TJF+47{ zt%JTjY}|{ALbn}30K;*(qu*!?Pt#=u=0mS#APD8-;>TkRyM{TI*AV6qaW)?nih3Kyz9yrNEaPTwlR^7ltoy`^af>uw(6|Ie>6(8N=Y~>TI_6b zADlBv-7gcOUEqG+!48jidcsffeZnTpk2=3`w4Ud4!ir#Y|7}t;7i*j2WV>=3jMMX1 zp8_Tyig&(W{(I-%W}cV2z-~J6+Jt$c;iR{9yiU6nn;_zye%*k7o;e?o5a#xe%T+Ma zA7_r~_nA9Wld{ETMC_ibxvVp>++TXKk0j*1CG(H}6;TKZRfiNK{&oQ7<*iB`*7cg zcHE8^cr4fY1w!cx#TxMl_LvZx#A;U1k$i{fb<~Y+K8^QGM^E?$o3$WFlc!7-guIBo zI9H76K@xns7(Uyc8;qbMy_$Ee`J@O#p+niF(vE?R*ZaMwa)>lU^f(AcmF8y6?bHc4 z;X3oMUodXbWm1<7XTd7(EN7M`i;ttM`V{1~N?%-5 zAd%JpIr}tnUNmT2y2N|d=A7twWD1VoBD?!imI{pj=fO#*?lGgTd+6K!f>T$i>aUG! z`D@73(?$LYtTByy=>*<`Oz(;xfhk#RZxr>PC%y)U=2_y;iSf6!Km4NN(TIT?SnN^v%pF%|AGWIp31X~Y(jWk+E z8`G!KVHdYW)_seE8HgyhKE&VQ-s5Xj${ArtLOmZFA58H&H<=Ibh#m3vyz^wststIMH#P?P;!x>R@pePGm8& z&0T#aPuFpKEaN6LLDV<=H%$;0rf8w)F-!n7_WL{VKV2RuRAW5);c}yooAN)dPt5<} z@{W<1Tca4H!jf)Mc?aFNL_#9AT_mda?!5zr-iEQ>;?Qy(A|rIq)5q?|Z$_Bwk>KnD zT8z~>C+@u7Xs$mK9vB`eyH!t2k`OAKZ<(|veu8!d?m}yRG zMAZ(aC0D!W7%%Wq4@cG` zcaxClmXff<633DTMmhZH_!@+4mvd;VrJDCi(NRkpU1W<}37fX5S<&Jc7kF5^!#N{Q zeirPo$hn(^DKRD0$r~>cdQrT_%RF$d7zG={Z}eZD54oL9nf!9o0lC};DLwWp69z&= zbB&LC#Y(vWc?c{$&rU`OU6g~0&VlrRjx)B4EyYW+#_4H9!nuz9rX|4Non6M<3M%2U zmRS)8DLZYpy-??z{CMOrgn)H2)4ilAgy8nYS(E-dVoayb`?FzZrpni2({7q0(nz-P zoCVMWE3V?vXb01ra<9u}^F3eUHI*B6H&mwyC8rq#HH?d#*%cxID9Rb=NHMtuW(yI{ zR!<&n?)f)9&_nZ>(p^P2pmAwHSz$lI&GsD-d?0*rorm?93sO%Qj+#U6Fm+4X0A6LC z@DI4nGxV+yX-1b<&b2CqV(*3dgHhiz@Y5v(96lP2pjt4Gzv}CwZ!4vwRq!XTg zi)C?EcDPTv8n~I;c{u7u&!z&?cTfV6h^no9?CaemF2aJX2aZ#Xw$1xfsgu~nj%Y@- zVsw=w>CIW5gHI!NE3)3iTf*|K7B&eeaZ_CpTvuxLsAdOs?g@FA>uwW@t3mSM=0ZD? zR9g$AZ=Or_189R;z7&U$}NnX~t*fY<0}2 zziFZ8&nyr4S^}hXuKYiZl|rOqFxU35KAP_X!`7mRsJG zCjv4+3gGDtbBf>NgSmDTEx+ZEwb-qgaF1mFbl&S=qxGDi@1}p+85aF4Os9Wqbh!Np~PwlC8VWDO>qs~ zZ|IM47#)6j*58N*-bQ^Zq%sLT3p}vW^uSUljf35}mMU(nrgl(LJD64CE~t~C%62%6 zOFEZwfI2xNfro5j5bsGW;^=9O6N@*Hu@Q7z|EdfP>nO$KG{Dl-ZYBPumQYx8)@wiR zB93p4>WVfuVRZT0`-Cq2R$l6vWE`m6Tb^9My3zs)G+_3MSGq4EP3W0G%MP7=OBMa8$;m=uI$DII+wOobl%&J3So6s7yXZkkjfpbtZ0s_+( z!~voc&G*8S{rF@|-^pl9?EA%K#PPe+e7QzrLHgAOpkMBi)g58f3}&ti?Z?rzEwng7 z6_)KQ^VsQ{9++53aueM!H3@3W z%<$Go%A9l%S0QMq0t5EWg0!`|7aFW+#9I_;VhNeAVC@FigZcTig9Ooise_NHFkF+LiJXfG3C5Ztd+WT`Nh(cwoKrHaGxqQRH<;rODN`ev_2C3K*-c zFB>6aqG=$~4bbK)S*&UTW`K!HoCB$fMgT3C%*W;ADQ=paWtCr6|c&io=&{v+>rMLTs_X2s@(-ew}K{QCDx=tcL_}iTm)bDc{U!~12i|a*swGL zx~j3BDYJ)R43CY{mdyKY)u&bf;|EhLVXP`8U1LLBGBJ|>nDDe??Yb`dzWmDkk3ihT zj1Gl;mv+=?7TAW`R7Q&QGK<<>isEBnk$@Q4(^CdpLEKWY@b3mrH@w)0{)qEh?* zI`75EyY7KZqF3g#ila79gQ52|P2OMOBUbK~e{2|l=0BZ3?4B~kq4tHjY_t0{Fb8KD zK#d#wno)zn=Au9TwfFAV`CZL-9MtG;yPJVIIUFEI|FaUSUt5n8LBMM+08^R`dL@PjJowY^;~=~qgcX84eRUMGymv|r!S&o(M3@TeVobwN9Q zNgDa2wultQ{d=Q7q_EDTb_2xT)c4# z?70TewNa`r^9-Cpc;+-hhgm7>kvGYjAmoTk*%s=_K~crTIHE>p?w`+(pE12H&7TD_ zwUQ|<_6X_9P=kGE%VQ-WJUdC*FlI23ljtFIO?a;1>myvk#0c4sY>wMrMjz{=HO$je zXgIe-3*E^$b2Qj-WESSf4GD(of0?M-k8e0)&6e;bsHvG5<=o3}GLKEAOl? z)E~#vv6~;sVyyH;mS?Jn8!SyXuI{jZGVG=@aZP;ae!>ilS6#I`zH}N*N}o;E>3$q`6h

Ubx-c=BDl5ygqOL@nn1*$q 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 literal 15661 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�GtFkiwbi0V;F5QF7zpoY7^}Or&SE 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 I{HJyQ58RkuApigX From 8d44fcdb066c453bbf848f954a4ac774852e263f Mon Sep 17 00:00:00 2001 From: sliptonic Date: Mon, 25 Jan 2021 17:11:22 -0600 Subject: [PATCH 073/168] fixed slitting saw shape and bit --- src/Mod/Path/Tools/Bit/slittingsaw.fctb | 6 +++--- src/Mod/Path/Tools/Shape/slittingsaw.fcstd | Bin 11765 -> 11757 bytes 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/Tools/Bit/slittingsaw.fctb b/src/Mod/Path/Tools/Bit/slittingsaw.fctb index 6424fc2528..5b63c060ed 100644 --- a/src/Mod/Path/Tools/Bit/slittingsaw.fctb +++ b/src/Mod/Path/Tools/Bit/slittingsaw.fctb @@ -3,9 +3,9 @@ "name": "Slitting Saw", "shape": "slittingsaw.fcstd", "parameter": { - "Blade_Thickness": "3.0000 mm", - "BoltHeight": "3.0000 mm", - "BoltDiameter": "8.0000 mm", + "BladeThickness": "3.0000 mm", + "CapHeight": "3.0000 mm", + "CapDiameter": "8.0000 mm", "Diameter": "76.2000 mm", "Length": "50.0000 mm", "ShankDiameter": "19.0500 mm" diff --git a/src/Mod/Path/Tools/Shape/slittingsaw.fcstd b/src/Mod/Path/Tools/Shape/slittingsaw.fcstd index f180e1e672d6a59a39fd4df53dd07abe557dcecf..15d5d4ad651e911bdfb46ddcee6649b389b8d14b 100644 GIT binary patch delta 7468 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 E1GDW1fdBvi delta 7459 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 Date: Mon, 25 Jan 2021 20:11:36 -0800 Subject: [PATCH 074/168] Add toolbit test files to list to maybe get windows build to pass --- src/Mod/Path/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 0c680e4262..8e74cf70a8 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -212,6 +212,9 @@ SET(PathTests_SRCS PathTests/TestPathUtil.py PathTests/TestPathVcarve.py PathTests/TestPathVoronoi.py + PathTests/Tools/Bit/test-path-tool-bit-bit-00.fctb + PathTests/Tools/Library/test-path-tool-bit-library-00.fctl + PathTests/Tools/Shape/test-path-tool-bit-shape-00.fcstd PathTests/boxtest.fcstd PathTests/test_centroid_00.ngc PathTests/test_geomop.fcstd From a4890e51f5e76d715a1796a71c2bdd4741f7467e Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 26 Jan 2021 18:17:09 -0800 Subject: [PATCH 075/168] Disable library name edit --- src/Mod/Path/Gui/Resources/panels/ToolBitLibraryEdit.ui | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Mod/Path/Gui/Resources/panels/ToolBitLibraryEdit.ui b/src/Mod/Path/Gui/Resources/panels/ToolBitLibraryEdit.ui index 70650bf817..805f7a3e3d 100644 --- a/src/Mod/Path/Gui/Resources/panels/ToolBitLibraryEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/ToolBitLibraryEdit.ui @@ -173,6 +173,12 @@ QFrame::Box + + QAbstractItemView::NoEditTriggers + + + false + From ba8e0aa8630e936e4c882cca50bbac9919cdd750 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Tue, 26 Jan 2021 20:38:51 -0600 Subject: [PATCH 076/168] Minor tweaks to Start recommended by LGTM Using the results of the vulnerability scan at lgtm.com, some minor changes were made. First, all Python exception handling now explicitly catches Exception, rather than BaseException (which would include SystemExit and KeyboardInterrupt). Second, unused imports were removed. Third, a couple of unnecessary or unused assignments were addressed. Finally, the JavaScript was modified to explicitly declare the local ddiv variable when needed. --- src/Mod/Start/StartPage/EnableDownload.py | 2 +- src/Mod/Start/StartPage/LoadMRU.py | 2 +- src/Mod/Start/StartPage/StartPage.js | 8 ++++---- src/Mod/Start/StartPage/StartPage.py | 15 +++++++-------- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/Mod/Start/StartPage/EnableDownload.py b/src/Mod/Start/StartPage/EnableDownload.py index 4baca89549..f7f117969e 100644 --- a/src/Mod/Start/StartPage/EnableDownload.py +++ b/src/Mod/Start/StartPage/EnableDownload.py @@ -20,6 +20,6 @@ #* * #*************************************************************************** -import FreeCAD,FreeCADGui +import FreeCAD rf=FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start") rf.SetBool("AllowDownload",True) diff --git a/src/Mod/Start/StartPage/LoadMRU.py b/src/Mod/Start/StartPage/LoadMRU.py index 4c7889daa3..7b7e14c7d6 100644 --- a/src/Mod/Start/StartPage/LoadMRU.py +++ b/src/Mod/Start/StartPage/LoadMRU.py @@ -20,7 +20,7 @@ #* * #*************************************************************************** -import FreeCADGui,sys +import FreeCADGui # MRU will be given before this script is run rf=FreeCAD.ParamGet("User parameter:BaseApp/Preferences/RecentFiles") FreeCADGui.loadFile(rf.GetString("MRU"+str(MRU))) diff --git a/src/Mod/Start/StartPage/StartPage.js b/src/Mod/Start/StartPage/StartPage.js index ca70689613..ad906f89e9 100644 --- a/src/Mod/Start/StartPage/StartPage.js +++ b/src/Mod/Start/StartPage/StartPage.js @@ -31,7 +31,7 @@ function load() { if (allowDownloads == 1) { // load latest commits - ddiv = document.getElementById("commits"); + var ddiv = document.getElementById("commits"); ddiv.innerHTML = "Connecting..."; var tobj=new JSONscriptRequest('https://api.github.com/repos/FreeCAD/FreeCAD/commits?callback=printCommits'); tobj.buildScriptTag(); // Build the script tag @@ -61,7 +61,7 @@ function printCommits(data) { // json callback for git commits - ddiv = document.getElementById('commits'); + var ddiv = document.getElementById('commits'); ddiv.innerHTML = "Received"; var html = ['

    ']; for (var i = 0; i < 25; i++) { @@ -76,7 +76,7 @@ function printAddons(data) { // json callback for addons list - ddiv = document.getElementById('addons'); + var ddiv = document.getElementById('addons'); ddiv.innerHTML = "Received"; var html = ['
      ']; var blacklist = ['addons_installer.FCMacro','FreeCAD-Addon-Details.md','README.md']; @@ -98,7 +98,7 @@ function printForum(data) { // json callback for forum posts - ddiv = document.getElementById('forum'); + var ddiv = document.getElementById('forum'); ddiv.innerHTML = "Received"; var html = ['' @@ -503,19 +502,19 @@ def handle(): try: import dxfLibrary - except: + except Exception: pass else: wblist.append("dxf-library") try: import RebarTools - except: + except Exception: pass else: wblist.append("reinforcement") try: import CADExchangerIO - except: + except Exception: pass else: wblist.append("cadexchanger") From 75e65dd09d59234f2b0cc36db328bc4b0cc626e5 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 26 Jan 2021 19:46:06 -0800 Subject: [PATCH 077/168] Added support for deleting empty groups from the property bag's group tracking. --- src/Mod/Path/CMakeLists.txt | 1 + src/Mod/Path/PathScripts/PathPropertyBag.py | 12 ++- .../Path/PathScripts/PathPropertyBagGui.py | 1 + src/Mod/Path/PathTests/TestPathPropertyBag.py | 76 +++++++++++++++++++ src/Mod/Path/TestPathApp.py | 2 + 5 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/Mod/Path/PathTests/TestPathPropertyBag.py diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 8e74cf70a8..1e1c4e305c 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -202,6 +202,7 @@ SET(PathTests_SRCS PathTests/TestPathOpTools.py PathTests/TestPathPost.py PathTests/TestPathPreferences.py + PathTests/TestPathPropertyBag.py PathTests/TestPathSetupSheet.py PathTests/TestPathStock.py PathTests/TestPathThreadMilling.py diff --git a/src/Mod/Path/PathScripts/PathPropertyBag.py b/src/Mod/Path/PathScripts/PathPropertyBag.py index 4bbb35ba19..2b21be2c6b 100644 --- a/src/Mod/Path/PathScripts/PathPropertyBag.py +++ b/src/Mod/Path/PathScripts/PathPropertyBag.py @@ -72,7 +72,7 @@ class PropertyBag(object): obj.setEditorMode(self.CustomPropertyGroups, 2) # hide def getCustomProperties(self): - '''Return a list of all custom properties created in this container.''' + '''getCustomProperties() ... Return a list of all custom properties created in this container.''' return [p for p in self.obj.PropertiesList if self.obj.getGroupOfProperty(p) in self.obj.CustomPropertyGroups] def addCustomProperty(self, propertyType, name, group=None, desc=None): @@ -87,6 +87,16 @@ class PropertyBag(object): self.obj.CustomPropertyGroups = groups self.obj.addProperty(propertyType, name, group, desc) + def refreshCustomPropertyGroups(self): + '''refreshCustomPropertyGroups() ... removes empty property groups, should be called after deleting properties.''' + customGroups = [] + for p in self.obj.PropertiesList: + group = self.obj.getGroupOfProperty(p) + if group in self.obj.CustomPropertyGroups and not group in customGroups: + customGroups.append(group) + self.obj.CustomPropertyGroups = customGroups + + def Create(name = 'PropertyBag'): obj = FreeCAD.ActiveDocument.addObject('App::FeaturePython', name) obj.Proxy = PropertyBag(obj) diff --git a/src/Mod/Path/PathScripts/PathPropertyBagGui.py b/src/Mod/Path/PathScripts/PathPropertyBagGui.py index 854e030017..54f55d4a56 100644 --- a/src/Mod/Path/PathScripts/PathPropertyBagGui.py +++ b/src/Mod/Path/PathScripts/PathPropertyBagGui.py @@ -130,6 +130,7 @@ class PropertyCreate(object): self.obj = obj self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyCreate.ui") + obj.Proxy.refreshCustomPropertyGroups() for g in sorted(obj.CustomPropertyGroups): self.form.propertyGroup.addItem(g) if grp: diff --git a/src/Mod/Path/PathTests/TestPathPropertyBag.py b/src/Mod/Path/PathTests/TestPathPropertyBag.py new file mode 100644 index 0000000000..974ca3bd6c --- /dev/null +++ b/src/Mod/Path/PathTests/TestPathPropertyBag.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2021 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 PathScripts.PathPropertyBag as PathPropertyBag +import PathTests.PathTestUtils as PathTestUtils + +class TestPathPropertyBag(PathTestUtils.PathTestBase): + + def setUp(self): + self.doc = FreeCAD.newDocument('test-property-bag') + + def tearDown(self): + FreeCAD.closeDocument(self.doc.Name) + + def test00(self): + '''basic PropertyBag creation and access test''' + bag = PathPropertyBag.Create() + self.assertTrue(hasattr(bag, 'Proxy')) + self.assertEqual(bag.Proxy.getCustomProperties(), []) + self.assertEqual(bag.CustomPropertyGroups, []) + + def test01(self): + '''adding properties to a PropertyBag is tracked properly''' + bag = PathPropertyBag.Create() + proxy = bag.Proxy + proxy.addCustomProperty('App::PropertyString', 'Title', 'Address', 'Some description') + self.assertTrue(hasattr(bag, 'Title')) + bag.Title = 'Madame' + self.assertEqual(bag.Title, 'Madame') + self.assertEqual(bag.Proxy.getCustomProperties(), ['Title']) + self.assertEqual(bag.CustomPropertyGroups, ['Address']) + + def test02(self): + '''refreshCustomPropertyGroups deletes empty groups''' + bag = PathPropertyBag.Create() + proxy = bag.Proxy + proxy.addCustomProperty('App::PropertyString', 'Title', 'Address', 'Some description') + bag.Title = 'Madame' + bag.removeProperty('Title') + proxy.refreshCustomPropertyGroups() + self.assertEqual(bag.Proxy.getCustomProperties(), []) + self.assertEqual(bag.CustomPropertyGroups, []) + + def test03(self): + '''refreshCustomPropertyGroups does not delete non-empty groups''' + bag = PathPropertyBag.Create() + proxy = bag.Proxy + proxy.addCustomProperty('App::PropertyString', 'Title', 'Address', 'Some description') + proxy.addCustomProperty('App::PropertyString', 'Gender', 'Attributes') + bag.Title = 'Madame' + bag.Gender = 'Female' + bag.removeProperty('Gender') + proxy.refreshCustomPropertyGroups() + self.assertEqual(bag.Proxy.getCustomProperties(), ['Title']) + self.assertEqual(bag.CustomPropertyGroups, ['Address']) + diff --git a/src/Mod/Path/TestPathApp.py b/src/Mod/Path/TestPathApp.py index 0adebedfc6..0be621b040 100644 --- a/src/Mod/Path/TestPathApp.py +++ b/src/Mod/Path/TestPathApp.py @@ -24,6 +24,7 @@ import TestApp from PathTests.TestPathLog import TestPathLog from PathTests.TestPathPreferences import TestPathPreferences +from PathTests.TestPathPropertyBag import TestPathPropertyBag from PathTests.TestPathCore import TestPathCore #from PathTests.TestPathPost import PathPostTestCases from PathTests.TestPathGeom import TestPathGeom @@ -66,4 +67,5 @@ False if TestPathToolBit.__name__ else True False if TestPathVoronoi.__name__ else True False if TestPathThreadMilling.__name__ else True False if TestPathVcarve.__name__ else True +False if TestPathPropertyBag.__name__ else True From 0d9863849b7fd6b241e56bda3570110c4abd5dfd Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 26 Jan 2021 20:04:46 -0800 Subject: [PATCH 078/168] Updated toolbit-attributes.py script and cleaned up shapes. --- src/Mod/Path/Tools/Shape/ballend.fcstd | Bin 15686 -> 12389 bytes src/Mod/Path/Tools/Shape/bullnose.fcstd | Bin 16021 -> 12547 bytes src/Mod/Path/Tools/Shape/chamfer.fcstd | Bin 11667 -> 12999 bytes src/Mod/Path/Tools/Shape/drill.fcstd | Bin 14400 -> 10766 bytes src/Mod/Path/Tools/Shape/endmill.fcstd | Bin 15056 -> 11610 bytes src/Mod/Path/Tools/Shape/probe.fcstd | Bin 10479 -> 11289 bytes src/Mod/Path/Tools/Shape/slittingsaw.fcstd | Bin 11757 -> 12325 bytes src/Mod/Path/Tools/Shape/thread-mill.fcstd | Bin 17166 -> 13734 bytes src/Mod/Path/Tools/Shape/v-bit.fcstd | Bin 13530 -> 13457 bytes src/Mod/Path/Tools/toolbit-attributes.py | 7 ++++++- 10 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/Tools/Shape/ballend.fcstd b/src/Mod/Path/Tools/Shape/ballend.fcstd index 512018de3f69ee26db8a03dd81ba120842826b7e..14b2a65efaef33388c6b67724d7999ba74f3c541 100644 GIT binary patch delta 10648 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 YAWV>~hqbkogfykJtEIQIwTsvP0qraghX4Qo delta 13991 zcma*O1yCkUuQ2-H?(WXw?(Xgmi@PoE_5h1J`yh+6xH~K??(XjH?hbd~_ndpr`TnZE z>P}Tpx_gpLI^8`rNhkZp13tJa@(_@i0000M@F#>-jW*~Uo)HZIa7azW6#+|0)J=s( zW!%n*qDg#UMvM>llN}I%&-Ra(5(Ke=$-wNQ=y4l0wSoe{=z%+_5C=wG==lDuvfPN` zDKUg`Y{pE{Kjv}9J1RD#b|QCiQi+}nJ^!)?ACZ0m<=&#Npa6l zz$285h7|^0*%Ko3r516XjN^dE3c}$9S&|FGSh*!4BlPnsgFTBpz9mV!10`?=91;`` z?gC^{k#5)iz?86J)1fzGa8r0`m)`XA3=K8-cC)dD|P-B4$6iY1xD(su2jllcS3*IrZvsRqLI@!VpW!S?U?Y@nVVW54PU5_ zW^9iKG3!1YB;DH;p&ZD!b$taw@$@dt$?=uZoczff(bKIvTvwR0xEn!My_tTF%chgr zDXqbX*q4zF>QVuY0`gA1-u>YY5C?)5iTyB|OtjA>?p@XsB&K3DfzW!CC90X(R(E%zFNAP4|rqRgIvi$OsV=cITQO0ay&%LLUI%409u+yxtOZI2{`x{E)t^STc zD(@s#jW*sXubF4(4#Q_u{TscGkl9NncoUrMCc~MiLiVA2+ z_-(s_=~6nX27Yd*Tyvc|1T)fbSiPObYN_z%p8f`7Ke#f`dDXKpgq7TuK}tWef-_+= zEndYcpv|SmfxS_wX{kPSM`KbyYbA|uD%O0}^&2VKaE&2};&n@;Wx}G$D6w?a$IyeY zOKW|5;@XA%CMA%SP?-t{7<3#q`UIwf756&0?Wd4OmeVYTaV^-OXABoWQpjm}%%?yL zOD!ic!AR;1TqM#{TL~95f(-zf@19 znr{l%M`k?G{G%>qv3ncI@xKOI)-1il$9zg3fv-b8k1_WPAyg|{2Z{F{eLhHNO0dUa zw6aB2rOURnlRSzD{{lU7<#V71Gx^=+Hf3)<}a1AXS*rx*saw zRu>zr@F4IkVVHNFEkk9+n6`B&KjX-MQkJu#y z(6arGZ7>dbIdS)=m8!WicJeCH{$MMgI!w?)tdd#6{jV)-7flZMTuy-*#Y06f3?mw2 zLvj0Z$b{1CwEgmB28pva3|;eRPr4NI7oKbs8kh5LlQ5aR@4skjh?wP+n=H61kDiAr z)Yl86O__b}#VidLW%u3rs9V$!CD6W@0E1LEh-A5X)aIG9NMo31#kFvs+|PTlXr6YV z75z|{0`DyX(cVE^8%&v`cF~`CinY{EV2hIWj^K_-I@K|+T>uy(Ps{2PMK3g-O(!~{ zW+TQnxJ@rlCDcm$M@u-Xhh3}=xJl~7TcpkcC-KT1|7Cb4mdAK#9N2~GL7byIV1750 z10l83G%mHHn6!vJo<2Y(DQ=7R84m;ASko2%MEs`=HmCY`)Hb@)^nxxqISz9q((xc0 zyeJ1+>Yr!$5q-tEWGoxB3bsGx!Dwnc;gc|h&V$HhAv9SzH8nUf2;tB>CN-ep?Ie}K zFqgJ{!0A|02=lru4hi!_#9bwLfnJsf#CasKr5ruU;PvCvC|!2yMRk+SB4{4F>(W!$Dqa6)nvWY4xFDvTfJGbsusa!nj zTx|LAz=^BEgi(r135;TPAI)^X$u$p^NCT(a)uW4#kzl7irqPcVrI$D+fRL<5gt!qqazfX|Mxx7gnWK>3 z0;(Y)xarA4HvX2s;U&TrTwkR+evYnMy!j#2-<@Tz*fSG{oo~n_2fQK~esWY6u{kJJ zz5K%c!u^4=BM`)HC`t2XgRH#O+5Jox)kXHy5X-g(~>q~VZI z>3N%4I&SoU?R{JFqoctor=2*{^wv{!1^4pw&-!+sU4NUwDmhKyOuc~&Ke!6g67AP3 zs<(`JF9W5jg3%WVQ>m8crmye&Bkm;Xci)oWl~NSok|=7Yi9VZbtaQGpjwCQ1AmmlD z+RwhB7xgKJC6kDXj;|`T5%Y-*?Jo%Mb}Kulj1X|dgJuv|fP^qGi*Mz-n3QV6{yW`0>k&CnH~!Pp!pO}H?A8i%(rWX_I{SD5XWLElhww4Hq-}wO+`VSrihY!5lU`J`Zn&hCr~$g_}+#T z{#AOn$dovCBbgiOQiJ(heEQv?pjJdG;AqD{3zH1}Q41CkG%{_1RNUPQ5l+9GiD62d zI6iL*zfFiuns-fmOSDjcaWn1}+0o;}Tz4qFZh5TaIUaG`tdGWSaM$Y(Eq~ICD05r* zwK@(4o_3nW7p>agln+=g6F0@@OT~>@Ku0UtZ_mkq?EZH{8tm|QvoU(gYC)POAby`F zgLm5RO+fafstAH^O}S-*j2+5oOPCCVmk`FHU^z`!;pwV34vnL;v+$pWi_5hhJu)ml z%$mwuYVIK&!&1NB!;Aw+>^;s1v}b>ayHI?TTTgueYB*3}4qBsyBsG_2->|1j+fOlY zdank7VwDvQ4jRq)IHzzp;8xBKz(Y#QhOD1FTdN5zbqIFVdGe@9n-%)#v>5}mUSjnA zGwI}Y)W_+xj z8>Q*iR4?a#d2E+8(NaVbcu;Y&;UXnk(M)Iz1$lZt>fpCr6m4*;<`Sbd;Lt+&Z-tT| zdEa-Qy_U4~ss49goJKNV zI(O$5o6cm|tg)5P^?(W>DlIuqDB021#EmVaf(Ks{@!+gc7N>SjIC4RAwH($(`msg% zTS-on6-^Rf*Ok=$<1J|dfqd%`^lqR<`=%E?B*EXu{FaF)7mp+5dd~%v>yf;h{jxPx zjpl_2jL#5Bs{1E{=LxloPkWml5_2nw#ALetd64rDgY}nfikNKf25Q+iN@nPHO+zR%dz&e-6N0pjw1t&im z=BwRL#!On?g4FaMc@{GGi$slP+8fVwF>k)`x6Ot`t5an2%0p;lkPLll5n$*zH#b&2 zl-Wu;)TOpE%0FxHFx{%6656w7;R9B9><4zAU=5;ZSSK4BR$RAO^cbT8__k^xL5S5%u7P0gn(t>{e6;cMyQLAjY?k@OC4o zN=EX++L;|L(*hHDC&E4Nw7u@oc&*t3){60FvPoiMC$@S4Qa7bv2LnyaFWTN$f_Fybi@;Oi3ZA9R?MJFYyZ(JZ4x~=Ln+4bfnf2zBUAD(uZ1EVB78P8$s>qQ5@ z(R}yEb@#`1qEOm~l4DV-^2=Pn)aAs+Df9Vz1taq6>%;ZSg%6)fmhSqYe z?uYel*%vzCxqBX-Xf;N=?rs!Nzf{b$W=J{a%8!4)!Q)TON}IPlH-6D-P{E>>wkt=F zU1;aqPIZXO;dFQJ*>z9WrVRn6;n86^>sdVmAIt($goK-RV`+xU9~Ew)9cVs;N8%r8 z#}2!H*mylFaxHONbyjqei<;ck`?ur_oKOm15z{)r(kuk4eC!<-+z;1)?j1JiI^teV z0aS@<^ow@~e7skeY9)BwiZ~>z9OYg01RZNoj2J;`pSwCmXWEc zE#oJW}QR;Kf@lL){*|&zzk5eS^bJ!bg!D1Yo<4sr~BLBbx9#$Dl@?dXPQCr3O zG|7P|sINa4FP!%)Fx@*W6Qn$o@s@tDRe>vViLoq5Xr~C3#c0kCUu*gzXt|4K88Qrb zJ$U{OleMibU62g098#O+4gFA}J&REf)AT(#(r2vEl^GPEL3-hMH~ zrZLsuE2BC+ZUo1Qt*}a-tb-eMJ$ik!-e39hvv8<;O3j`I+xKm#ZRh?=2IVeP;~qIk zUt$p_pR--Ks*>K!K94pZC3f1oA%)`>5yptD9=m<2mw`GIXo_8%#2ET*hVvv>`k0Tf zcj`ArZxeBx3H0q7Y?5b~b3ac)9+dSq4_Si_;kKhLp_|}0`d}dgqmb;>oJkt2nnA)b zfFBCM4Kv?QZJ%w}yI<;IAM6)<$vr7>{(q43l@mY0yBYSk0B|hkIa5Y_-zwq*P)#Mw zm5tDbPz&dkfMYE3PsMYC914NL`x8^#o>|^(-6%R0FdtaaK(1gzQwkb(z^A||#tZHJ zXZEv`Aj&s(ygC^Z7F4@FwHu?s#zX{#wGqy8J;*0z1g6>!j^$t4Fl24BCLy~8#o5(ml-2xW^cynU4 z>2=PUA%yzgPe%k^R9>9&4p%b6OZZUE-%ZvZi#Pl#PAW5g;7`78u0A^$eA6BTj(bz; z{v`5bo)1p474`f6eAeib?|8Ij z)eLCf;^F|og5w9!LNps-_uw&OAyc^RB^gUiek}PaJaaK}=eh>qX->>jdpyV`2(;E9 zsLG{nbW9UWrF97~vbvJ>85;}%{Tl{~b)9hGt>hWZ(EKoX&6Iky1A$R(wa^sBqLm33 zBTdNoQ+RGWt%>qj!`B?R-qX{^W6Das3`vw0Ih=v6jaFOS4oT%8`$3KYyN`@%WxgH4 z$16{yuGv6JIdC)0IO~FR)nSydY(HWln>eKer%$l|y%eBX)tpXc0U!{L98hYA!P0(m zEW>*@t7Lk6_~`0LC#UK6xuWlV$>bT#`^8)P$IO~y##u>=9q~ZDGrwxi>g@qC3j2AI zriZnb@XFhCj9jY4SkfMkWw{$(y|+*1NMjRU{mL6c3U-aV!?%Xjle~H&;CPPI<81io zn`zOv*yTQxfg0DyN8dd)1t94VV^gm!;|X9Yx_q|YB5c-99Y%Yv3b}AleAsrlrPy&& zV&TQ$aW5{F9E@|FP-?25 z{X;HOql>iT#g8A=sbVI$Jucv@s7*mu!hS6%dQw6;Hk;xH$sCzF2S8}ms``d$(>N9vjxw!2yBzxfjwh?uHy7@XzU4T1iSj>Od~vd$r%py%7)!^duG3RM@52bV=sS!>EbuEP=s&B=bL zit~-I@z1)CSMa=%)M|^F2rQ$JJsf!2*pcXEJJjYmljI&nbXQLTO7u+bnI<+-N)ey@R{xK{c(L|cv6(jm!Z-%Lg( z=zCV-k`k;oYATSoa4x6B#MU}UFA`-fcAE^JTgpRUxZ<0k7fBnf-*K?)S7&3Ub2q`4 zD2QLbjJu=6c(FlZOmI4KDPubm;nTf8Lw|$~ez{a#Ici_}*=+cv zpRV1-p`PfmKir1Xg^i+nksN3Ot7xyo9#3Gvb6wWhdQuPV;$x)$1g@UK( z;z%+_IRASmv#Ke=hV|}(G6)EOt4gaPsQ#PPOM}=a)}G%k_tBtMQa313{hI>CD3fv_ z6P{kP4Z=Al;)X*2YSu`4G4-jut_KH^xWt4gD9G`9!+Vx4f-Qat#V?>Yrw7#lJBMOL z=cP5S$NXh$Hf*CWR^yBgmNRgScOvf0rP=oqPAOQKvk_T5qc^ukI0lJ8k|&p|h;cYS zDLD!R#`(^`h956z-+C%7=1m8Ix2@ajcON~OlLa1L>9h*8H@cSkg10V8EQxv*F!xc) zLAf|M#a8l5#^*^0O&klMBfHS)U)_(1Bm8|W%jN=B1!+CFP-l^2m>a9*_Ztuze-|Y| z-6!}ek=$u7)w{I>*_TfMwcIsfyZLo;II+HEB=G^VqFv;_Iqd#$JQOoQ?Wuz}MdkWs z#qHLDpjPB11nT(mb5Y-woWe7^0u6JSH9J#gxHo%foYKvdT|&krx*nm76wt%qa=svE zsa_tH<=+Apmr-UtyPGXGPBC@=Bz=GJ{+$h8BQk{9>D`RLGCL0p80qcoEvfzel1{;< zV5|Z$WtfupkIGOtSs#*|Hq>d>ePunfu#~$zAD0}|iIYbMDa4n>-Tukp``&$Xed3*U z^MFq?KQ#QE(o~c=46_gghrg) zu%!c0v(b_Ss=_{aK3Vj5w70yK7Q~)VFH&M##<51VU*abEgVXyDj-(5vGGl-d#I_F| zSg%?(NS<|~v(en%OpH#^{$^Bs6gF!Zol@~UJ6|}PRxxr2fLj#TZcUkYd3y*s3c>RFwiYhVrY^C^0)-kk(MtSgK}V?N zKf-pJ7wTqTf@X{EguFCgMxc`)<0NPclO3LjeDxNB<3yzXP$rBba`DOkQoS{~19avL z1Ym@yMWr(XPWaB5{2J{GAbeepPwD6jxa-cwtr21_;eE~0PDNS{ z=W70CNWhiP8l7|on4Oln@vG_I68@t!VX@$*bhmpN2E@NxqxK*ollaN3D?5lrhP9p-j|FE%gf{SNv)N=^9(Hxl z&#y6a-K=IKyu?`{hWc=)R^q_*9QtOri< zYNc*YUa%Bl-*c55B?g|DNJXbH?}%Fu zVRw_7xATv%pw`(~*RPbm%WCWrGdH{8qpsD3VPMpA;r%T5vQddH;4$hP7vDQEHA)HZ z!h3-6bNx9L!#nABQ7rGD!2=*9wUT2}neAOZaf$BU4&B59!D&XVe8NDTOhh<@&9?OC zN@3MQN9q3jgza$T9QX}J!f!ZkJUd;Z&!8d;|5}_my+J~_pFC@=0?=?c29k?&KAPI> z%pG{Ki~~+@E2BOA2)$N&W~?hz2O+A6udZWiezy9f{o4&;siXoIFJdS~Eu|b~NWC=L zb78?RuB-a$-s#?Zd)xV)E#+-m%m64yX!cP@ZIHjrDdvWkLA?+hYhS#@%2IDbK?M;bO+oI)uG@$RU0!V zVEI>P$VQS~=1WrTECg>k;OQR6J6z*IT5RQ1Qw44D4jdZo6{oAIV_AvsSKWW0k|>!m zI8ef_>_0_FA`4H`Yly97;UM)lEN6(^ZBo;d2JK4umqaaB*#Xqbq`kw_0($I^zKCmk zWh8(p*_Q$H<}@I}Tt z2>f4Gdqkp^z{n+b zUK2mE=X( z!JzrxY5!Aw-1R8UC}mcB z*HyWa=TmJ%iSI~yAx|)p^M|{NZhmfLqj$uV1`_Ye z@m#MPq4t#5aN>NK-lZN3w#xy7EMH@pD`9={1$IpeJYydE!d-?;oMJucL$pCJ$r&{r7Ob z1u6=9Lv1GH8F(dyn+T!u@Of1Xt}or_TLUv^lfGh#Q0`fgyGs=+3CE0h{9Kv9@R-RD zu6>HtKPLg-`xM{+F?Q9y5#a8X{J&Rixw`Gh%HSj1Ue&=LpAVwuK6dP+G>)_if|>9; zM^sWZe=#}3PT>lFaN>Ewwc^9da|cjCJG??C6%5lZ_)3DVhEoe$ovsrbo4?Q*{&9zO^HqO3gdq{ot zdE%qBNxS~3!AY2+rrC#&9GON5A7^C=LYg{lyrqlg=}Ur=Yce~`IT3{L%1WwKr*(4X zv*f$-Ie%GI`j&!Jd)gP%&Jtibu>sj?$lu7E(a0TA_1M3{j6k-r>;Sp|#yshzM4WNG zN=7Wsq^I~BxnPA$a2XQEW#0XqozNlslsRp{NFkbpYkXEml-^dO(n%~N^ux42pZ?ue zY2;-Q|JRQ2i{Z19@wI>ak{7Yhd62IZuNvM;b?xu4-xcWtb+5qSqBWrnFAJeKVAE7* z-7lrIcG$oyNk0+cGboA&H2;(V(=a{Qp+5}myLZ4W{0mPLLU?fZ_xwEZcD)&T&U{PKr-S6#5~y~2>Jw_8 zdG$nS1I>^IwZ^K2SUsvEQ?wE!M0exbc?y|D`THnC+EdY8VFN{DKBWu9mFI?ZXO3<^IkUb+>}dWWORctN6PC&ChmkOuOV za4rP@(c3a>8ZgmbG_YbUUn^VAs$5B1F4G+Zy1OrCIY$u@S!m8V+e#(jP0OH?Wd(+m zKtjrb2T_J3XbaE2zplUUF0Mn%c=C~v@}K@Lqwl=)&vLzJIn2I2cX)#S>?AWqu^{bK z5;f&vQz$A2+qY%%VC?a_CU_kg?L_9)Sy_8)tay!up|owLBps3$m!K1+*suz@20-rv zPuIWqLCX|5fm<*gfgoL$2*WU6jASV}yL6KA8aFv3LZGLZ%u=OqvaiQnIazEGo9!*~ zcB`Q*q!GMZwXd(OSF}BKaEV z-f2J(1U`$f{<#5gmFT%qX(^n6;dDB43W#l6QdTtE2}UojBZ^)12PrRotTBsD6HDxF zM;ft#h4}_1p~s@duuUiIPzQ$2mx~8HlJ_o z1tvt?mMeevrfk$~{1X*DgcL~e1xDKt+23k%9iTa{@>~8=#hrruiDSqcfMuu6ZgYV6 z(Yz1Eq3MsVsu`j6$Rn%dsD?6?S0MAoi6$*FWQCgOkwm2|su{^7K7TG#3r9ATsR0Nq zE0fSbDm|{Ne!As1+ZC43n5S6thi$>wYs=Fu6nNU}tg)P3Pin2Ux&^8W&%eq&d)Dl2 znC}#5AjA|r=q&H*oU7C}r4OX*efcqndc*28sQt6G4gPyK09`|KHa?4*B?C(iN3-VU zqV=k+Ju5-D2DdAFd#@>+TU~?iHn?#n6IlTpVOs@u%a5(4%7?tca#paS%?={`aY4QH z9I59QD%7mJj_alrd=#)4JXa2j(H1~UC6x~*soMPU)vmP78%dDJAVggBCb~FJLUvxo z2{y9NwsC58o=E+JXRb3tA)ECUjPVeI;4T<#`g*p$p4ni|-cs})Y0Rhv(HV`tq7+|T ze7hPE@Z<*;Lrr0skM@09M9D<74ah^-&3{CJ9+{Ap0Tp<-E)485_X<;WxST`(1-ZdO zOadoOP6Hbv8hJ@9BB2R~5;{#z7d0I*Z7y>3C|p+bOG@L#UlbF}vK%jeT4sub5*3lG z@ULWfu%c6!hgaF{fcJ!l9qXHzNR+^7-7Qr(bw!E_xxEAlRm>3b2*gm;M4d6q3a#j> zCmFNzAA!OllfYAH&3O}TjwK>4fY5q9vyX2LRLdxT6i=#h^x#Voe15lxvCL+pj0Rl( zbYQP#G(GVh%+c}Dj^7~OOX~z%r778RD3s1Ou9-6}doBdQ!&g{I&rN$)6E-|m3cBpg z4<#cRjL)o<`;RVy^exe?=O$6 z_ypNO_wm4-OxfqXJ@XmYDwOKQ@MU|jE@KWBO#G<0GX6A$v1S3d`>h$br=13`z9=-N zAtI^ZeseK(PQ)lM-*l{kg5!Muwx`EW7X4>@)XrET5&-Vm`FawTIi?wyaUvS*F$?)uw-5fEDJgIM(*9-m*d z110+*(OkA2Kjz{>f^-YPxLy}k?AN0{3jgmmT%bYHWuEuPjoLn#(1}!eC(f`Zn2_JG z#e}v-!aFoqdKmW(9pgx))mX+ zAD$avSuPONf!Oi&Jw5NlP8flx@VDjKDnRF5{JJmugLX4cJY&lfu1mA-hWW+$^jxtp z$eHrX0FH;CR%o4B|39FeW%e4KvAaj4oBnAGSTdRV;>2u+FBH8&6zg&9>=2KY+=Gay z`76{7*g8lxFCVdEU-d$VP2;#B!A0A?3%v!As)1<4Py{y&-`B`_1S}uX2_@H{v%((?gcaQfNS5Q+NSiU~^;6bJlUv%P@dYj9;(O3ZH<3=5b`La1p)sCo}x$#bv zE}{!X!#X^%2$(Y8A`!L-g~H2htkdDu5)zLoJ4q3=EuqO*E*_z*E8c0i9-_QNgcGn- zDV!m67^pesqwZkfpuk_ij$Pi`QWRz^0i zEUk_3^ho!k@Sc}H1e=4GM?dj^J`%O4ut;w!aPJR+_O4TrS-o!Ua)r6YDP;KM;i0`1 zN{|R@U}leOZ7sct4fdJ5UMnzkgwTzg%n%Rx&UbipG>*RW^Sd5$TPuW-Bk)a%Na6}U z{b6&eM)={NZ+a(~LWT~%=`z>k75R(_$2Eo7Vx{MKPm#9Ao^{u)D4z!Y8)LC51-{J# zN0EUi5hDyraGp@Kl-dI|a{m(sWs(L%-$e}uKK$$^_fX7?DPY6*nN#T1NxR~axL^&u zn>$glBf2=f*-mBj1^KQc1lXe}<8X6-KW^t`1%q^X1|a+C{Z8%xJo4os^cb_mSU>ck zp&8l_<6oRzS&B%q52o_X{lFP+^mLS}!-E{X)IsSw1`+)nrzVW!AV*84@D+-KYXcYX z{V|@x#zc?2I2RdmMyS<0JNC(F3R}j6tX7k zts(5xDHMt-EbS~z;(i@nLrKv8jvjmnrnUY%@@*Ij@;jMQQxgc3QA)5YL6GQ#+r^%K zBa4(!awJOx!`F4omVOvO`5Wvkz@6alx1%K!HwHQmI2~0aOQ)r!`kn;?0Q)fxYsTGU z5U$n;WiebIK}AB*2jr!Egd@}O-fY*<+6*kRCFJw^`71(Bs)$&nuxJ4CXL3~1u+25A z6Zr7(Q9iA)QTBV^V_6H_m(|7jlij^?q3vKQ z{21t8W%$q(hz}@vWpq}P4_hdZw9GlO0VYBO#Qv~3i^`8_$ZdzOB4|9Bczf7b#-2xb z1)C%V*PzLx%i96LTaB+U>P1S_?4!dYzTDRv%-Jx%1Y#=;AzR7KxVm`IfAy)v0KZFU ztkogc_bYUf>;d&XN%T0Fk4}lY)IAgO^1$JXF^xS7OmaAPf7pNrk^Wt5Hs5Rp)3$Sc zzF`f@YHl$DwPGPtoAm7O_9o|!$@sjw_a0{)%Vm)EwtN0coW-co&moHc+~ha(fE{dsCv_sj?d5ur+aAl`9WtEX-{nd6Pd?(?;2mgfGig~S{b2K7g5bS=|#V2 z;Jb}cf#DRfP+0q8S=w?|#fIvNOthvCbryu{t=usEFX9};1Q}p5mG}yAt%wm#(xNq9 z>wEwopBLI3_U~(c@3+B$!b7DL($gHnF;K3x{4+FJoUuP_)Jv9IQdO*BX{lTfCZgy! z8ytmw4gHgI7sFK`UA3x<@_ewKLAPo8ck@=zz!vpro{ALT~yK(((I$4%l?f@@G>3Z2$bRJx$c$z z3FQ5a7`Tvcq;@-_^}@#&<|&jXMQ$k#@!SY#g)bJmDr{~+$ba&dKhoCX>q9e*5#GG6?hK9aI6I1)9`iXfN-;_YKId%OM9 zJB$1Cz0>QoSa~2xI7Fi?kvQlsiK0;x032_%`}>>a+`{I>H#^Kc)n4@e1m~~UtyQuL zCd1=|UU>mm@D2WlbfB5@eyA6|fHD=xNki|At&@^!THrHc53L|6$6uv)xQJTBgQDMZ zZ%%GW`&IKXY`tr6+c+ZSzzl$yim9fOfY24GEIi+8<8oLie|{5u$Xig1NZT2k1VoYO zBuKz49Sj_eg3CTkxL|@57QNz1f1xP34F4nFDpw0dmdZW9z-G5iC}*WSyFbmL_W(pc zHRnt%jQmOfrz3EHgh6Wp5~J7?(!9OPdM6(B3ABvB=1 z9Q+?%d{8kR3&h_fAkYdO>%S10p6y>KPXAwR&;M>$(6j&BzeCRo`%lu)loX~U$d4W$ zWXFI25@CS-pUzr(g1?u{rA-nqzzhTApH6d z&cE>@U;w~(xW88P|BLV!HUtG@0RSo{E^g}9CQcTtOr|bQD)M09m=OPy&Gb)v!72m* zU_X1uE!28#VJ;b6nBbSaoXZgiWPS~xbNatiX7aXVh4A3_u}sE?yui>?|pB+ zJM;EGS;^#=WM!|}la-xVFFyl7@e@1(9t;c&GK^dXzjCoOat!vZ&f_h{+re&*Z=)M#1Ky~LSRz=jj@+vd;+a(HM&7g90XmlK$? z?g~74Qy_;Dey60Fiy|}5Qwc*5U0q#W1doiDuGpANsPp`2YI`H7?hc$g^j}H0&t8-w zjS`;aSPH#d#hXe42hQoTrkMw6r0E~?+s(im_h%xxeNMUNq@&rLetruJ3(E-1QX{w1 zWY@l@nn?(mj=28N&AZ!+o{+PsZ>2ZI3*C`cczgC-PPQpGd!+pC(hbOd2V?d9pPVwn&4oukOpLpH!O~Hs{;?rE+zmG;>Q(;`X7;Q($4y$(n z4a{8gz|4cBFCuta{RMOy^?Q)4PDw=zSCR#u zmpZ*z9aCq?fYx761~s|p@fAmlcIBHJZI3FSGsl0@o3yT|$9iK*^RcNLcOMUYQGZ`* zbT>AJrFNmR;N|+>yNucO2z^PTa%-m4qb_0ik-~lfdR2|vZ7VwaZka)1mZ5M*q|pgo zF&0|vIQ1M`{AOUEpXhX+q+a;@od53-{a4;W{U!5m`At9WWKvTN`$%WOY zN+y*W1|V|f$4KkN?eEK_M)myqShV1W#sHoX7`=q;u`feVK2io9FQc2rIEz6YIr6=G ziJ8L?4UejyL?fc8AG@Sij&`9@OJXMol7Fp`M8_f%Z>=n%edyfc#gRoXho z32JdEBhk$Bc&zuGI~%s&R5_O-iikZmr&c?AM+rH5S=pL#R7E^B=QgSG`^P_U+U0~9 ztwy3zl)$j+fIFx6&Pi%U^v0`%w46GxU&j#FD3r%mj}#H)GUMnOJ%lg5s&?qjZ!cC) z>j7dhaQ35SB8bHeHs}dUiwSgZsBkbfeyq9FSJYC}s0D$%JUgH$O6yYpP;MWo^-D(IK2# zvW7}>(u%%2nfozlmE=`kLFIY;&I({5kPFbU=<=l@oqOc*(?w%MXVdk0J%9sS^^2)_ z^{er8`-+k8>No~dDGGGHX5PwoQG9V-kNv}U&hjjLmnrOlbmx|L66VGi;;_#`_5s%m zi(1YDDo^SNyw_XB`Hp88&Y@}A_ekPTsjGJ{x1%pHpi%sG2engezm9>#QFfs9u3qnc z$9FH?@IGbD0cV!~mlvPY_G_Vcl)V!LqTb^(a0DNSWpHn45L1h-ZHY#By7x7ECE%Ua z6prR8&OBJp1DA)w?zrzME#Ey8v>%=a<|OoqGCP-8@MKCyp~g|pg((F-#I$%j_^CFm ze$wl>xuFRFds+yJX`hw6*INZXQDycoHB~g)2-VA1-(|-OswrrvHSoCU38Yx}=K5U9 zNL|612ad{7`!xk?a`T~rWLjXAXH#r{;&Jbg&~`(x6BxS55CT7)J*qqM^3VP(Ta2bb zq1n=0>0yvH5K46Nh!*UG}MIhH+%ld;v6@{gRW#p~w4XvWe-!gntMGpX)w2`VsQBa zl`L#F_iRI9dDWe{aR(-tt7)?6UZ;u=-?!1h(<*NyEZp^$^_9{{zm#Xkl#C7pL7Q&4 zmXH-xMJ_t%>g6Ju*lrIf+sNo1>l~$hB1rUf@YxJR<_3!j2w7HWT4#$SaCp1DuzTOE zg`u86WJw%P`@Bq_i@!dTFQ7=@ck0=!ZekZ5)>VX9n|KZF`%QK`Xhvny z!N_iiihj7edz(hvz6vxtlgq3FN|WkT!~m_C#u@L`1-*;iEy=o8)mGwk1nNr%Yfw1R zO&(q$5US{l7OY8tei<2Y>l+@3Ny4gfp`lGg(#HDQPl&LW4O-djm1oWZ>cqK{AC5@)ipjvq$V;_ghs)dU^)NHIS@s=j=hq;&} z)xn%3R`#YuSVTkTi^w6ej0uMoAA`5@st|lt`xUxx<+6^zAt>C(^vbg)qV&OZe4pvz zw)*u#CuF4PzpDA;2pe)IR&R}KsuG{AUt1`oz2-!{=e7DAUM7)q1{fhDQSZlftk1r4ygl=l7SY%&Lk_DnCbsPM@*|sg)WG6{#w` zpd2%2U0QD1g-CF3sA4K}|BW<~q{z4lAGBq1>(c(qqS(FfR!UAit^!hOO2m;!=+ny2 zO_PuX#ME??v(`S~G&*qE4!dz&VaC<>#od&X*8AW`vn0)m6>xF#dRW9||1F5JLi<8H zRO7qc(Yk8=-Hbc|7+rU6@T~fw@sqtn>5E3?ow{!0vy*l|_abRZ==`IW%=spu0aA^o zEl)79orL!xyG(r@jiApd?(Itx&9Pz6t{p4JYF)v9qHEJMYyt!h*}Kev&1347)R-iv znd+6Ke%8ol0j^*}<355)kMdd+t4b@jI7)e)_>*=~<^k8(p&+M7`vN;GtbUbf)$567 z`H9Z6PPz(*AKUflnda{9-z!a=az*%vO54fL3VgLAgj)LH)DGHHSiGQhPRG z!hwte&|!LBjMklS{UZ#oNINr!-MUmed#ztq=%%gCReZZEjf4OA^~{E`HuYiVdh~+g zF?RMSv`?saZP(L&J%DP{WpTm%D{~5dWFM>?zfbHChJNDO(R(ixnh=-nRakX|$oYy=)>9DZeo>N!wX1t>- zFS2Ka6k(76c@D4TS`624}TUG?TZj<$9_Ob%p)kmgZ+O(BU;Q|H@nqFb-E32ZW zKpoV*hMUqm?D=kVuC|bdd&-XrSqHcWTC`cVFDN!w8gxdnzvkDD!I`$7vc^aZ4Zur< z>6hDhwyz}_oho>RCzppvd+rFSYnvK(V1oizt_s(I@%;qfiZ8_(oJFJoXd1yMUz~Df z<(f9mh_vdC;4^GCU^4Dq>>fa(; z&d5PgzGceO?#2wjd_;wrk7!DhpP+WE1@?VR)8=3&NuC@fK56TWvfH_U*4{NwwNlA| zaf%qABiZdW_c14ZmF<@@eu2lNT8%njZL0F7`K-r5Gb5ohJG#OSOEPB$Qm6LqLn@N& zz4owHZS12{q52VTfGbjVUxxS_XJJn?W^SfpN4U|%zDe}Hmdkn| z%S1SGq74IHDbIr@icszDO*-TR%`>d3h9C*BZTg}FUPls|&{Eo|#r{xdO|P*K81slZ zRS|Fw7BD*G`bo5eUanc_A)qS7r3-SeZU}1pa)4(ekm&9(+jqM)I;X~kpcl=t;5WkY z+(Z@bH=g0J-C&49Uz<}z5u87P5dDY{ftkhU#%^zt4O*WJxl*)$)tH9h5cuJ|wSWi6 zYfnvN2suCueaj`iR7rnggx%ea;pe6t8&kSFMy$@|$OA%~!o&Gay+!O7!u2r_vTZKL zF^>b!?dMCs$uwAMT3=})C?MVKt6hIogoe%RoaHYg^AwLef0*(`{K;)y2Sla9k=T~@ z4Vz*tA|Hw;j`O1os~-cdzZ>5-Z_E}5_G=oe!%!PJq!c}XG_w=3sW%*5updafLG!7Pq3hw+_x;D5 z4%Uw6uSY{9K-BB6$Q>n6G390w-w(||3HqI62{{-h%YoLW;?}KrOOJJb@!%+fy&T^7 zVQRd-z?IvdQDKd)#J4-0t$Kq1!QOfmhSug7@CG9S310i+QrfPCYHye9tu0Tx>jwk2 zV5CQTJogLUeVXM{)t{bg+T!3OKJ0bg^HFYzk#**-X`~7!&sXE0Li>h$hOx_c$A1M@ zTh*QSW|5PVDPHnB8SMP&a3JNkBS*a!7BhLq`ez3Q9>I+G6c;j!%?@;>QR!vkSSX^& zJU*m?XKipI^e0dYa*-wxJp|KEl7nasyeksYQ_}h`wp;2gZPK;^o+(9iyyI35f}B!Q zr?IXk?JKRmtZO-Fh_uCrJBpzRsXRrf3J77JRV+#1`a0u2wFC_sX^F|$p8e(gB6mvcFb$)B&~)hVl#~UdUi+ACEM>an;pd@G z!AvHx-1QF21&jF8Zd(09lJkk+dS_!OgU;QA2IPb>nxhAqnLhlw`~=yxE{RrRJ1wI< z7?NMh+H?bDh^@#|G(6?|W@ulYu}yP&#(B~h^2Ouj%*qWt7zkRek0S420%Cdnp8bq z8%DNlB987 z$J9xBlT=SvU_`dtQcm)WD2SQHP-PW&2;NQ2^?>1EqQ6X_+U}c}PbUh-Nepjy>Gttm zp2k+bor?S2vx8I1x9Otf-p2TzK%B7NtMq%EbI<+2uF(s2@q)0)HSC~0HuIF9SF2UM zhonjUkL-bPM#~$8;=XO8Iax+(^_f{~%LH?8lNlqe0kn@4wSBoidmW>cu9mUC27Nr@ zi=gd~InVU%F0Lv(pVrKc<#hYTi)~BY+9e_}DBKWYsE$3rv%N_OT4ge@NEtFF=n1)P ze#|F+2J+#+xM$Ufv9kCc2383}T=*L_T`~gT2=az~(4>4oie*_?2Pn&Bo{|Wow`H7T z7u;gPb!%(6HhuleslzOa5H}Sui~Ef^_it`QzZ~@uZj#x$?}6M3_GRpFaA^13U)p0IrM%R&lg2dR$J*UUkb0B2{NW{*LX?t;l_!hzZ_UQaD{6b)|~DZyMA3 zC}8hZH;>a$n`^YQgxG;qwhMP!OZ|KnP5x2#R;v_i7ik7qy6}OL9cuLg*$8bI29ANY zq@Q`;Hc54zn)KoJd_oL>!AU{2{bfQhf--Qi4bd;qOH_sWbw{ zUwzwU9i_u5WEhw$$QlI+z>W8(NvpW``C{VLf_7@)(%ZKld(?8o6Ne_c&IrhMgjNw& zGG_TN&C)I3^^f%C5YdYSjVfj0u%bu!CR}WGY;}96xM+S)pf`h=2y+c3E}`R-mmfA< z4Q>?ndu>-pQ(qkI?fsXP#8{G?yd%LT+h>Ou|cu7*r{a=FzzVRDVGbb{8Pi(?f) z)g?M^{_$M|G|nWEO0bdLoelS`qt0F#=da2Mv~ypFMy^{xdg&d!m=|hp)$gT!tiU^S znM>~1QBXN|zMOFy3>T?2+`6W+C{9A{ugUVtrA43ci!~S0Bo%8Ga;>q-Wm&?bK8Vl! zwmf@HX}|nTo+E6oHJQma+S&uTD(p zrbsTp(>0HQOVHq{aD#h!h+!a%%SHWIQEWSJt3nE=fA=W%eyKF=Df9+X24Tl$2RFUyx zlnpqIm{Y^m-gI+;TY7Z;Ag@_#o_lzK^e- z^$^y4w2Y(~)K^fDealx$P*jv=;s?5cd}wuQbB8OWF*2;NM)fi{l&;OT{b(19pBV=1#qD2N7+X4I9w<<|fxp$S3YKk4r_{AL)+9lHePePW8mYOh<5%r09(Y=j6*pxer^ILux=87J zm&?0|z&oSKPUNE2S%RLfd|N*5Y+VgteHJR6BTuRM;>~SaW2$m3d%#|h`~?2gzc{G( zhScvEy)Ixgt(;KRCa_YySD}A?#Ll<2-t#eqFHF!Xx9MXn=gyIb!Q85mI|>n1w?=AH zMI8g$t%%DhP@rA-t6I4_Cr4;?vtx?&)X5bKqw)O8ur^AY-CXzy%ZGv2z-th|r|UoD zsl7M5Rx%xv%0at@3MTk8Tz7F{Br!H!`uvcCPkGz9#A$jxHfAdT_?BmfTN&P}XWp$ALCM4>OWDgM5H}uwP4nkgNm}X{{Tk0JaqFYt_No)LTBee=v1FWu zh6I?V6KcrWe}0-cL`U0J?flJ6e1t${)B|IQ>X1LJYXsk!EWV^3(@q~)CiI-Wnt!Ur zOHF*OOY8p!bv-0W?kN&JDz8;WjOaut12Uw<%;7)SzbGhtrQsMke_tX7r8?B2uT^N_l@ zfY^gILyI^)EpEyK{z`igQJ7*tER#t~@rvdzF^lvco(ESjPEf%Co%Ir*Et|5xqdY7w0ETMOYE!3 zpTo0^8~NLZ)Ct#`QT=G4NAdLxZ}XZNr-hk53mm_ttOkW+O@151;p?cHnHU&s3Ho8) z+YoRrR;5p%<^9>ive*te7=pHg%-g6m8*Bl3JbT{sRh(+n8co05-s&`^z))+(p$xdGTDbgi@)f zkJaZO8(SnM5$xCZY(FLTrGA#t>@cs{Pi%<$lRBC)strAY9y%R&kmii#<4t_J@g9$V zp5kM|mUKgVUVpSKk5=o;l=rRsTvL66t-IIfPT#7jNMji>+K;)vVu~!(Bv0cZ237l4 z9CSqz`2pCtLU)UBvq9n z`7l%%H-BSgNB!^=&O**Cb+KFMRwCZPfu9+w%2Y&CtqK-)_I}!VG|BJ0P-$JgOwNA! zXI-RBe(HUs01E>n2nPd0^yjBF;ArM#ZD6BsZsowL`ky6>osH=M`l-XtR`j}op5E%! zr5y^~1O~-WI|8+rLCY|#ateq%sv8~MQ(80uT*#fy1rNU=pg`Cg8OmpCOMgvf9|?X) z=v#b?k4HWW{hDe2=pzqakgwIAbCyI&{DF@~AlnqhF{rwzat_o(4~H&=faCjO&F(~| z$J?nXz0!?4_t={|Yh3>2IJbwFgiQ0~GQEM^tWgHYx{j;A=Jo6j+vLRpzFZOf5s z=zg#lu;hq_SwwtN2lrG5mh#U7PW7#BSe@ax&gQm*&Ok|MY8?NG+h$ees0bRb4(o6; zS?%-vPWpQS4xrST`P0kw_gJ$FS^FVT#^nt2M!jdK6D!|VdO5irqoE_l5PNd7>`vwI zot&st(Rb~avsR~vlWjSX)P|R5jIIZvheV)L)mDb zG11eY-Yf?xI7u-7s85wq^D??n{^TFB%1irKc?v=0WB;29^Ktx59sbSd|C?XrKPM9)E&NPx{u06ebyG%wfkAl-1B3C$?|+^u zpAra)0IAkrI;2{Rw@81^MFT1f4Ca3n{?IC)1AG`5MSXim6*GN1V|Eq;dppHXuyARea{thuifCSbQ(j-9kcSQeNhP%VSVEw~eIpkJ={BNEA zKCS;ztMaLYychge;&cT`;CLA!o`S@GJ4qBI0W29|U_P7M7>nCl+1h`wx7T<3?{M*6 zI9>i+0sc&K{I~P;KUhUua~sG1AN-$p_ZRq|x%~YHEMX4e6e9dP7C9jjIAkV>h0wqD zFDgueBE^AI~BLgBx%{{LHZ_@gUfZerr(@UNY4{MRlZf7#Dxf=~$muYI^hCWwwO NB^(Yj#77wKe*iNA;iUio delta 12352 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á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 delta 10262 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 MOhlaQpHBGy1J4Ij#Q*>R diff --git a/src/Mod/Path/Tools/Shape/drill.fcstd b/src/Mod/Path/Tools/Shape/drill.fcstd index e7a958ca2e8c29ebb7c3aa5d5c4d7c5ed98554bc..da1e277778c09c1a8938580d335030a191d560dd 100644 GIT binary patch delta 7735 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 U0sy!GIVTGXkdzGdzrEo90|fYD#sB~S delta 11457 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 v*pPwfpXwV363b@Z diff --git a/src/Mod/Path/Tools/Shape/endmill.fcstd b/src/Mod/Path/Tools/Shape/endmill.fcstd index 1d94bdf970b06430db0a6a0d6db10e3da960fd3e..b3b1ae18a4e7584d2317fe7094d4d16b10200093 100644 GIT binary patch delta 8059 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˜aLhsCW!X)8An`Urx;=mYYfQSmFW)lz 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 diff --git a/src/Mod/Path/Tools/Shape/probe.fcstd b/src/Mod/Path/Tools/Shape/probe.fcstd index 56e7945c95013313260921d4a274580dfb3d3698..b4f20fa949643a192bdcc7e6bfa259ca00e7fbb1 100644 GIT binary patch literal 11289 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 G=>883fMw$V literal 10479 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 diff --git a/src/Mod/Path/Tools/Shape/slittingsaw.fcstd b/src/Mod/Path/Tools/Shape/slittingsaw.fcstd index 15d5d4ad651e911bdfb46ddcee6649b389b8d14b..6694d911f717a87c93eb177fd946814fba3a9ecb 100644 GIT binary patch delta 10636 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 diff --git a/src/Mod/Path/Tools/Shape/thread-mill.fcstd b/src/Mod/Path/Tools/Shape/thread-mill.fcstd index 64982f6478a239262973efcda02392e1027c3eda..c69e91539610d657f388b7beb2e01d8adf1b3e38 100644 GIT binary patch delta 8132 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 NtX@k>)BnR|{}1J5InDq8 delta 6697 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 Date: Wed, 27 Jan 2021 07:50:39 +0100 Subject: [PATCH 079/168] ViewProvider: make partial redundant color blue --- src/Mod/Sketcher/Gui/ViewProviderSketch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index 4e90b0007b..098a62005c 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -6497,7 +6497,7 @@ void ViewProviderSketch::UpdateSolverInformation() QString partiallyRedundantString; if(hasPartiallyRedundant) { - partiallyRedundantString = QString::fromLatin1("
      %1
      %2
      %3

      ") + partiallyRedundantString = QString::fromLatin1("
      %1%2
      %3

      ") .arg(tr("Sketch contains partially redundant constraints ")) .arg(tr("(click to select)")) .arg(appendPartiallyRedundantMsg(getSketchObject()->getLastPartiallyRedundant())); From cbb0a8a11c7e91b027032fe9c33321ad401295d2 Mon Sep 17 00:00:00 2001 From: ci4ic4 Date: Wed, 27 Jan 2021 11:51:42 +0000 Subject: [PATCH 080/168] Update philips_post.py You can't compare dict with an integer. --- src/Mod/Path/PathScripts/post/philips_post.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/post/philips_post.py b/src/Mod/Path/PathScripts/post/philips_post.py index 41203ef075..f06d2d666d 100644 --- a/src/Mod/Path/PathScripts/post/philips_post.py +++ b/src/Mod/Path/PathScripts/post/philips_post.py @@ -384,7 +384,7 @@ def export(objectslist, filename, argstring): # #\better: append iff MODAL == False # if command == lastcommand: # outstring.pop(0) - if c.Parameters >= 1: + if len(c.Parameters) >= 1: for param in params: # test print("param: " + param + ", command: " + command) if param in c.Parameters: From 4dd4783702d8efa122cfa1e31b3b45052a0ca91c Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 26 Jan 2021 21:30:18 -0600 Subject: [PATCH 081/168] fix index bug in toolbit directory creation --- src/Mod/Path/PathScripts/PathToolBitLibraryGui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index 621ad0e996..0abc2a6ae4 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -87,7 +87,7 @@ def checkWorkingDir(): subdirlist = ['Bit', 'Library', 'Shape'] mode = 0o777 - for dir in subdirlist: + for dir in subdirlist.copy(): subdir = "{}{}{}".format(workingdir, os.path.sep, dir) if os.path.exists(subdir): subdirlist.remove(dir) From fd2f2d83d05ece79b7883895cc03c31252f9c8ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=A4chler?= Date: Mon, 25 Jan 2021 12:09:17 +0100 Subject: [PATCH 082/168] Path: Fix creating simple copy This commit fixes the issue discussed on the FreeCAD forum: https://forum.freecadweb.org/viewtopic.php?f=15&t=54769 --- src/Mod/Path/PathScripts/PathSimpleCopy.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSimpleCopy.py b/src/Mod/Path/PathScripts/PathSimpleCopy.py index 2901166801..6c6cb5d5a6 100644 --- a/src/Mod/Path/PathScripts/PathSimpleCopy.py +++ b/src/Mod/Path/PathScripts/PathSimpleCopy.py @@ -67,8 +67,7 @@ class CommandPathSimpleCopy: FreeCADGui.addModule("PathScripts.PathUtils") FreeCADGui.addModule("PathScripts.PathCustom") - FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","' + selection[0].Name + '_SimpleCopy")') - FreeCADGui.doCommand('PathScripts.PathCustom.ObjectCustom(obj)') + FreeCADGui.doCommand('obj = PathScripts.PathCustom.Create("' + selection[0].Name + '_SimpleCopy")') FreeCADGui.doCommand('obj.ViewObject.Proxy = 0') FreeCADGui.doCommand('obj.Gcode = [c.toGCode() for c in srcpath.Commands]') FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') From 6870b022105c2bd601da4eb417d4956a57a35359 Mon Sep 17 00:00:00 2001 From: Benjamin Nauck Date: Wed, 27 Jan 2021 23:15:37 +0100 Subject: [PATCH 083/168] [Gui] Make inventor markers symmetric These changes makes the round markers symmetric horizontally, vertically and on the diagonal. --- src/Gui/Inventor/MarkerBitmaps.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Gui/Inventor/MarkerBitmaps.cpp b/src/Gui/Inventor/MarkerBitmaps.cpp index 732cbe5f36..90ed4a6af2 100644 --- a/src/Gui/Inventor/MarkerBitmaps.cpp +++ b/src/Gui/Inventor/MarkerBitmaps.cpp @@ -345,12 +345,12 @@ const char circleLine11_marker[CIRCLE_LINE11_WIDTH * CIRCLE_LINE11_HEIGHT + 1] = " " " xxxxxx " " xxxxxxxx " -" xx xx " +" xxx xxx" " xx xx" " xx xx" " xx xx" " xx xx" -" xx xx " +" xxx xxx" " xxxxxxxx " " xxxxxx "}; @@ -381,7 +381,7 @@ const char circleLine15_marker[CIRCLE_LINE15_WIDTH * CIRCLE_LINE15_HEIGHT + 1] = " " " xxxxxx " " xxxxxxxxxx " -" xx xx " +" xxx xxx " " xx xx " " xx xx" " xx xx" @@ -390,7 +390,7 @@ const char circleLine15_marker[CIRCLE_LINE15_WIDTH * CIRCLE_LINE15_HEIGHT + 1] = " xx xx" " xx xx" " xx xx " -" xx xxxx" +" xxx xxx " " xxxxxxxxxx " " xxxxxx "}; @@ -401,12 +401,12 @@ const char circleFilled11_marker[CIRCLE_FILLED11_WIDTH * CIRCLE_FILLED11_HEIGHT " " " xxxxxx " " xxxxxxxx " -" xxxxxxxx " " xxxxxxxxxx" " xxxxxxxxxx" " xxxxxxxxxx" " xxxxxxxxxx" -" xxxxxxxx " +" xxxxxxxxxx" +" xxxxxxxxxx" " xxxxxxxx " " xxxxxx "}; @@ -446,7 +446,7 @@ const char circleFilled15_marker[CIRCLE_FILLED15_WIDTH * CIRCLE_FILLED15_HEIGHT " xxxxxxxxxxxxxx" " xxxxxxxxxxxxxx" " xxxxxxxxxxxx " -" xxxxxxxxxxxx" +" xxxxxxxxxxxx " " xxxxxxxxxx " " xxxxxx "}; From 92846ad0f134227d44bcf5bf1b22e4597f9d9861 Mon Sep 17 00:00:00 2001 From: Aapo Date: Thu, 28 Jan 2021 15:42:15 +0200 Subject: [PATCH 084/168] [TD] Avoid changing default open/save dir to templateDir when opening a Page template. --- src/Mod/TechDraw/Gui/Command.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Mod/TechDraw/Gui/Command.cpp b/src/Mod/TechDraw/Gui/Command.cpp index 687fd905ee..8fc52fbdd7 100644 --- a/src/Mod/TechDraw/Gui/Command.cpp +++ b/src/Mod/TechDraw/Gui/Command.cpp @@ -183,11 +183,13 @@ CmdTechDrawPageTemplate::CmdTechDrawPageTemplate() void CmdTechDrawPageTemplate::activated(int iMsg) { Q_UNUSED(iMsg); + QString work_dir = Gui::FileDialog::getWorkingDirectory(); QString templateDir = Preferences::defaultTemplateDir(); QString templateFileName = Gui::FileDialog::getOpenFileName(Gui::getMainWindow(), QString::fromUtf8(QT_TR_NOOP("Select a Template File")), templateDir, QString::fromUtf8(QT_TR_NOOP("Template (*.svg *.dxf)"))); + Gui::FileDialog::setWorkingDirectory(work_dir); // Don't overwrite WD with templateDir if (templateFileName.isEmpty()) { return; From 4bd5c2669d006254cdfc93e20c2e265226dfec5d Mon Sep 17 00:00:00 2001 From: vosk Date: Tue, 26 Jan 2021 17:00:08 +0200 Subject: [PATCH 085/168] [Part] - Fix memory leaks --- src/Gui/ViewProviderLine.cpp | 2 -- src/Gui/ViewProviderPlane.cpp | 2 -- src/Mod/Part/App/PropertyGeometryList.cpp | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Gui/ViewProviderLine.cpp b/src/Gui/ViewProviderLine.cpp index 6294d73bf9..bdae7a5f8b 100644 --- a/src/Gui/ViewProviderLine.cpp +++ b/src/Gui/ViewProviderLine.cpp @@ -68,13 +68,11 @@ void ViewProviderLine::attach ( App::DocumentObject *obj ) { sep->addChild ( pCoords ); SoIndexedLineSet *pLines = new SoIndexedLineSet (); - pLines->ref(); pLines->coordIndex.setNum(3); pLines->coordIndex.setValues(0, 3, lines); sep->addChild ( pLines ); SoTranslation *textTranslation = new SoTranslation (); - textTranslation->ref (); textTranslation->translation.setValue ( SbVec3f ( -size * 49. / 50., size / 30., 0 ) ); sep->addChild ( textTranslation ); diff --git a/src/Gui/ViewProviderPlane.cpp b/src/Gui/ViewProviderPlane.cpp index c93e1b8fd9..02fa1b550e 100644 --- a/src/Gui/ViewProviderPlane.cpp +++ b/src/Gui/ViewProviderPlane.cpp @@ -70,13 +70,11 @@ void ViewProviderPlane::attach ( App::DocumentObject *obj ) { sep->addChild ( pCoords ); SoIndexedLineSet *pLines = new SoIndexedLineSet (); - pLines->ref(); pLines->coordIndex.setNum(6); pLines->coordIndex.setValues(0, 6, lines); sep->addChild ( pLines ); SoTranslation *textTranslation = new SoTranslation (); - textTranslation->ref (); textTranslation->translation.setValue ( SbVec3f ( -size * 49. / 50., size * 9./10., 0 ) ); sep->addChild ( textTranslation ); diff --git a/src/Mod/Part/App/PropertyGeometryList.cpp b/src/Mod/Part/App/PropertyGeometryList.cpp index 432d3610df..3061c3d60d 100644 --- a/src/Mod/Part/App/PropertyGeometryList.cpp +++ b/src/Mod/Part/App/PropertyGeometryList.cpp @@ -226,7 +226,7 @@ void PropertyGeometryList::Restore(Base::XMLReader &reader) reader.readEndElement("GeometryList"); // assignment - setValues(values); + setValues(std::move(values)); } App::Property *PropertyGeometryList::Copy(void) const From b49c523a541ce7ace520463e9e0b695ef6b20e5b Mon Sep 17 00:00:00 2001 From: vosk Date: Tue, 26 Jan 2021 22:18:30 +0200 Subject: [PATCH 086/168] [Part] - Fix View3DInventor.setCamera leaking SoCamera* --- src/Gui/View3DInventor.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Gui/View3DInventor.cpp b/src/Gui/View3DInventor.cpp index 396da77057..ee55f39111 100644 --- a/src/Gui/View3DInventor.cpp +++ b/src/Gui/View3DInventor.cpp @@ -774,10 +774,13 @@ bool View3DInventor::setCamera(const char* pCamera) SoNode * Cam; SoDB::read(&in,Cam); - if (!Cam){ + if (!Cam || !Cam->isOfType(SoCamera::getClassTypeId())) { throw Base::RuntimeError("Camera settings failed to read"); } + // this is to make sure to reliably delete the node + CoinPtr camPtr(Cam, true); + // toggle between perspective and orthographic camera if (Cam->getTypeId() != CamViewer->getTypeId()) { From aa45875a2be8a6a43f150f18fd993aecde0785b0 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 28 Jan 2021 16:22:40 +0100 Subject: [PATCH 087/168] Gui: modernize C++ --- src/Gui/View3DInventor.cpp | 49 ++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/Gui/View3DInventor.cpp b/src/Gui/View3DInventor.cpp index ee55f39111..43a923a66b 100644 --- a/src/Gui/View3DInventor.cpp +++ b/src/Gui/View3DInventor.cpp @@ -782,42 +782,45 @@ bool View3DInventor::setCamera(const char* pCamera) CoinPtr camPtr(Cam, true); // toggle between perspective and orthographic camera - if (Cam->getTypeId() != CamViewer->getTypeId()) - { + if (Cam->getTypeId() != CamViewer->getTypeId()) { _viewer->setCameraType(Cam->getTypeId()); CamViewer = _viewer->getSoRenderManager()->getCamera(); } - SoPerspectiveCamera * CamViewerP = 0; - SoOrthographicCamera * CamViewerO = 0; + SoPerspectiveCamera * CamViewerP = nullptr; + SoOrthographicCamera * CamViewerO = nullptr; if (CamViewer->getTypeId() == SoPerspectiveCamera::getClassTypeId()) { - CamViewerP = (SoPerspectiveCamera *)CamViewer; // safe downward cast, knows the type - } else if (CamViewer->getTypeId() == SoOrthographicCamera::getClassTypeId()) { - CamViewerO = (SoOrthographicCamera *)CamViewer; // safe downward cast, knows the type + CamViewerP = static_cast(CamViewer); // safe downward cast, knows the type + } + else if (CamViewer->getTypeId() == SoOrthographicCamera::getClassTypeId()) { + CamViewerO = static_cast(CamViewer); // safe downward cast, knows the type } if (Cam->getTypeId() == SoPerspectiveCamera::getClassTypeId()) { if (CamViewerP){ - CamViewerP->position = ((SoPerspectiveCamera *)Cam)->position; - CamViewerP->orientation = ((SoPerspectiveCamera *)Cam)->orientation; - CamViewerP->nearDistance = ((SoPerspectiveCamera *)Cam)->nearDistance; - CamViewerP->farDistance = ((SoPerspectiveCamera *)Cam)->farDistance; - CamViewerP->focalDistance = ((SoPerspectiveCamera *)Cam)->focalDistance; - } else { + CamViewerP->position = static_cast(Cam)->position; + CamViewerP->orientation = static_cast(Cam)->orientation; + CamViewerP->nearDistance = static_cast(Cam)->nearDistance; + CamViewerP->farDistance = static_cast(Cam)->farDistance; + CamViewerP->focalDistance = static_cast(Cam)->focalDistance; + } + else { throw Base::TypeError("Camera type mismatch"); } - } else if (Cam->getTypeId() == SoOrthographicCamera::getClassTypeId()) { + } + else if (Cam->getTypeId() == SoOrthographicCamera::getClassTypeId()) { if (CamViewerO){ - CamViewerO->viewportMapping = ((SoOrthographicCamera *)Cam)->viewportMapping; - CamViewerO->position = ((SoOrthographicCamera *)Cam)->position; - CamViewerO->orientation = ((SoOrthographicCamera *)Cam)->orientation; - CamViewerO->nearDistance = ((SoOrthographicCamera *)Cam)->nearDistance; - CamViewerO->farDistance = ((SoOrthographicCamera *)Cam)->farDistance; - CamViewerO->focalDistance = ((SoOrthographicCamera *)Cam)->focalDistance; - CamViewerO->aspectRatio = ((SoOrthographicCamera *)Cam)->aspectRatio ; - CamViewerO->height = ((SoOrthographicCamera *)Cam)->height; - } else { + CamViewerO->viewportMapping = static_cast(Cam)->viewportMapping; + CamViewerO->position = static_cast(Cam)->position; + CamViewerO->orientation = static_cast(Cam)->orientation; + CamViewerO->nearDistance = static_cast(Cam)->nearDistance; + CamViewerO->farDistance = static_cast(Cam)->farDistance; + CamViewerO->focalDistance = static_cast(Cam)->focalDistance; + CamViewerO->aspectRatio = static_cast(Cam)->aspectRatio ; + CamViewerO->height = static_cast(Cam)->height; + } + else { throw Base::TypeError("Camera type mismatch"); } } From 1dddcd0b181be028065c59594057c8f7ba2979c2 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sat, 23 Jan 2021 21:19:12 +0800 Subject: [PATCH 088/168] Part: fix ViewProviderExt::updateVisual() on incomplete mesh --- src/Mod/Part/Gui/ViewProviderExt.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Mod/Part/Gui/ViewProviderExt.cpp b/src/Mod/Part/Gui/ViewProviderExt.cpp index 909049fdbf..5dcfd97135 100644 --- a/src/Mod/Part/Gui/ViewProviderExt.cpp +++ b/src/Mod/Part/Gui/ViewProviderExt.cpp @@ -1133,7 +1133,10 @@ void ViewProviderPartExt::updateVisual() const TopoDS_Face &actFace = TopoDS::Face(faceMap(i)); // get the mesh of the shape Handle (Poly_Triangulation) mesh = BRep_Tool::Triangulation(actFace,aLoc); - if (mesh.IsNull()) continue; + if (mesh.IsNull()) { + parts[ii] = 0; + continue; + } // getting the transformation of the shape/face gp_Trsf myTransf; From 569817d87a622e0625b25e64438f117fca9f47d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Br=C3=BCns?= Date: Thu, 28 Jan 2021 17:16:05 +0100 Subject: [PATCH 089/168] Fix ODR violation, correct Ui_TaskSketcherGeneral namespace The generated ui_TaskSketcherGeneral.h defines the class in the SketcherGui namespace. Fixes #4529 --- src/Mod/Sketcher/Gui/TaskSketcherGeneral.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherGeneral.h b/src/Mod/Sketcher/Gui/TaskSketcherGeneral.h index 1cdd92923a..335af9c434 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherGeneral.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherGeneral.h @@ -28,8 +28,6 @@ #include #include -class Ui_TaskSketcherGeneral; - namespace App { class Property; } @@ -40,6 +38,7 @@ class ViewProvider; namespace SketcherGui { +class Ui_TaskSketcherGeneral; class ViewProviderSketch; class SketcherGeneralWidget : public QWidget From 77d666a48b61285d230b277186e1e8969639a7a9 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Fri, 29 Jan 2021 17:01:22 -0600 Subject: [PATCH 090/168] Fix DXF import with no layers As discussed in https://forum.freecadweb.org/viewtopic.php?f=3&t=54842, if OpenSCAD creates a DXF with no layers in it, the code that is supposed to handle that in FreeCAD has a minor type error in it that prevents the import from working. --- src/Mod/Draft/importDXF.py | 2 +- src/Mod/OpenSCAD/OpenSCAD2Dgeom.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Mod/Draft/importDXF.py b/src/Mod/Draft/importDXF.py index 49430955ba..fac0493aa3 100644 --- a/src/Mod/Draft/importDXF.py +++ b/src/Mod/Draft/importDXF.py @@ -2220,7 +2220,7 @@ def processdxf(document, filename, getShapes=False, reComputeFlag=True): drawstyle = "Dashdot" locateLayer(name, color, drawstyle) else: - locateLayer("0", [0.0, 0.0, 0.0], "Solid") + locateLayer("0", (0.0, 0.0, 0.0), "Solid") # Draw lines lines = drawing.entities.get_type("line") diff --git a/src/Mod/OpenSCAD/OpenSCAD2Dgeom.py b/src/Mod/OpenSCAD/OpenSCAD2Dgeom.py index 378eea52fb..f65e59034b 100644 --- a/src/Mod/OpenSCAD/OpenSCAD2Dgeom.py +++ b/src/Mod/OpenSCAD/OpenSCAD2Dgeom.py @@ -500,7 +500,9 @@ def importDXFface(filename,layer=None,doc=None): #shapeobj.Document.removeObject(shapeobj.Name) #groupobj[0].Document.removeObject(groupobj[0].Name) for layer in layers: #remove everything that has been imported - layer.removeObjectsFromDocument() + removeOp = getattr(layer, "removeObjectsFromDocument", None) + if callable(removeOp): + layer.removeObjectsFromDocument() #for obj in layer.Group: # obj.Document.removeObject(obj.Name) layer.Document.removeObject(layer.Name) From df7638435f54ffa85ef742cc05e963ab8b330caf Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Fri, 29 Jan 2021 19:36:14 -0600 Subject: [PATCH 091/168] Path: Remove `Expand Profile` feature from Profile op --- src/Mod/Path/PathScripts/PathAreaOp.py | 2 - src/Mod/Path/PathScripts/PathProfile.py | 145 ++---------------------- 2 files changed, 8 insertions(+), 139 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathAreaOp.py b/src/Mod/Path/PathScripts/PathAreaOp.py index d68e4676dd..c0854934a3 100644 --- a/src/Mod/Path/PathScripts/PathAreaOp.py +++ b/src/Mod/Path/PathScripts/PathAreaOp.py @@ -231,8 +231,6 @@ class ObjectOp(PathOp.ObjectOp): area.add(baseobject) areaParams = self.areaOpAreaParams(obj, isHole) # pylint: disable=assignment-from-no-return - if hasattr(obj, 'ExpandProfile') and obj.ExpandProfile != 0: - areaParams = self.areaOpAreaParamsExpandProfile(obj, isHole) # pylint: disable=assignment-from-no-return heights = [i for i in self.depthparams] PathLog.debug('depths: {}'.format(heights)) diff --git a/src/Mod/Path/PathScripts/PathProfile.py b/src/Mod/Path/PathScripts/PathProfile.py index 8112b81840..b62cb53e45 100644 --- a/src/Mod/Path/PathScripts/PathProfile.py +++ b/src/Mod/Path/PathScripts/PathProfile.py @@ -99,10 +99,6 @@ class ObjectProfile(PathAreaOp.ObjectOp): return [ ("App::PropertyEnumeration", "Direction", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part ClockWise (CW) or CounterClockWise (CCW)")), - ("App::PropertyLength", "ExpandProfile", "Profile", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Extend the profile clearing beyond the Extra Offset.")), - ("App::PropertyPercent", "ExpandProfileStepOver", "Profile", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the stepover percentage, based on the tool's diameter.")), ("App::PropertyEnumeration", "HandleMultipleFeatures", "Profile", QtCore.QT_TRANSLATE_NOOP("PathPocket", "Choose how to process multiple Base Geometry features.")), ("App::PropertyEnumeration", "JoinType", "Profile", @@ -149,8 +145,6 @@ class ObjectProfile(PathAreaOp.ObjectOp): return { 'AttemptInverseAngle': True, 'Direction': 'CW', - 'ExpandProfile': 0.0, - 'ExpandProfileStepOver': 100, 'HandleMultipleFeatures': 'Individually', 'InverseAngle': False, 'JoinType': 'Round', @@ -261,29 +255,6 @@ class ObjectProfile(PathAreaOp.ObjectOp): return params - def areaOpAreaParamsExpandProfile(self, obj, isHole): - '''areaOpPathParamsExpandProfile(obj, isHole) ... return dictionary with area parameters for expanded profile''' - params = {} - - params['Fill'] = 1 - params['Coplanar'] = 0 - params['PocketMode'] = 1 - params['SectionCount'] = -1 - # params['Angle'] = obj.ZigZagAngle - # params['FromCenter'] = (obj.StartAt == "Center") - params['PocketStepover'] = self.tool.Diameter * (float(obj.ExpandProfileStepOver) / 100.0) - extraOffset = obj.OffsetExtra.Value - if False: # self.pocketInvertExtraOffset(): # Method simply returns False - extraOffset = 0.0 - extraOffset - params['PocketExtraOffset'] = extraOffset - params['ToolRadius'] = self.radius - - # Pattern = ['ZigZag', 'Offset', 'Spiral', 'ZigZagOffset', 'Line', 'Grid', 'Triangle'] - params['PocketMode'] = 2 # Pattern.index(obj.OffsetPattern) + 1 - params['JoinType'] = 0 # jointype = ['Round', 'Square', 'Miter'] - - return params - def areaOpPathParams(self, obj, isHole): '''areaOpPathParams(obj, isHole) ... returns dictionary with path parameters. Do not overwrite.''' @@ -310,9 +281,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): def areaOpUseProjection(self, obj): '''areaOpUseProjection(obj) ... returns True''' - if obj.ExpandProfile.Value == 0.0: - return True - return False + return True def opUpdateDepths(self, obj): if hasattr(obj, 'Base') and obj.Base.__len__() == 0: @@ -331,7 +300,6 @@ class ObjectProfile(PathAreaOp.ObjectOp): self.isDebug = True if PathLog.getLevel(PathLog.thisModule()) == 4 else False self.inaccessibleMsg = translate('PathProfile', 'The selected edge(s) are inaccessible. If multiple, re-ordering selection might work.') self.offsetExtra = obj.OffsetExtra.Value - self.expandProfile = None if self.isDebug: for grpNm in ['tmpDebugGrp', 'tmpDebugGrp001']: @@ -343,11 +311,6 @@ class ObjectProfile(PathAreaOp.ObjectOp): tmpGrpNm = self.tmpGrp.Name self.JOB = PathUtils.findParentJob(obj) - if obj.ExpandProfile.Value != 0.0: - import PathScripts.PathSurfaceSupport as PathSurfaceSupport - self.PathSurfaceSupport = PathSurfaceSupport - self.expandProfile = True - if obj.UseComp: self.useComp = True self.ofstRadius = self.radius + self.offsetExtra @@ -435,10 +398,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): if ot < 1: cont = True if cont: - if self.expandProfile: - shapeEnv = self._getExpandedProfileEnvelope(obj, f, True, obj.StartDepth.Value, finDep) - else: - shapeEnv = PathUtils.getEnvelope(baseShape, subshape=f, depthparams=self.depthparams) + shapeEnv = PathUtils.getEnvelope(baseShape, subshape=f, depthparams=self.depthparams) if shapeEnv: self._addDebugObject('HoleShapeEnvelope', shapeEnv) @@ -460,11 +420,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): custDepthparams = self._customDepthParams(obj, strDep + 0.5, finDep) # only an envelope try: - # env = PathUtils.getEnvelope(profileshape, depthparams=custDepthparams) - if self.expandProfile: - shapeEnv = self._getExpandedProfileEnvelope(obj, shape, False, obj.StartDepth.Value, finDep) - else: - shapeEnv = PathUtils.getEnvelope(profileshape, depthparams=custDepthparams) + shapeEnv = PathUtils.getEnvelope(profileshape, depthparams=custDepthparams) except Exception as ee: # pylint: disable=broad-except # PathUtils.getEnvelope() failed to return an object. msg = translate('Path', 'Unable to create path for face(s).') @@ -481,12 +437,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): finalDep = obj.FinalDepth.Value custDepthparams = self.depthparams self._addDebugObject('Rotation_Indiv_Shp', shape) - - if self.expandProfile: - shapeEnv = self._getExpandedProfileEnvelope(obj, shape, False, obj.StartDepth.Value, finalDep) - else: - shapeEnv = PathUtils.getEnvelope(shape, depthparams=custDepthparams) - + shapeEnv = PathUtils.getEnvelope(shape, depthparams=custDepthparams) if shapeEnv: self._addDebugObject('IndivCutShapeEnv', shapeEnv) tup = shapeEnv, False, 'pathProfile', angle, axis, strDep, finalDep @@ -499,11 +450,6 @@ class ObjectProfile(PathAreaOp.ObjectOp): if 1 == len(self.model) and hasattr(self.model[0], "Proxy"): if isinstance(self.model[0].Proxy, ArchPanel.PanelSheet): # process the sheet - # Cancel ExpandProfile feature. Unavailable for ArchPanels. - if obj.ExpandProfile.Value != 0.0: - obj.ExpandProfile.Value == 0.0 - msg = translate('PathProfile', 'No ExpandProfile support for ArchPanel models.') - FreeCAD.Console.PrintWarning(msg + '\n') modelProxy = self.model[0].Proxy # Process circles and holes if requested by user if obj.processCircles or obj.processHoles: @@ -631,77 +577,14 @@ class ObjectProfile(PathAreaOp.ObjectOp): PathLog.debug('_openingType() ' + msg) return -2 - # Method for expanded profile - def _getExpandedProfileEnvelope(self, obj, faceShape, isHole, strDep, finalDep): - shapeZ = faceShape.BoundBox.ZMin - - def calculateOffsetValue(obj, isHole): - offset = obj.ExpandProfile.Value + obj.OffsetExtra.Value # 0.0 - if obj.UseComp: - offset = obj.OffsetExtra.Value + self.tool.Diameter - offset += obj.ExpandProfile.Value - if isHole: - if obj.Side == 'Outside': - offset = 0 - offset - else: - if obj.Side == 'Inside': - offset = 0 - offset - return offset - - faceEnv = self.PathSurfaceSupport.getShapeEnvelope(faceShape) - # newFace = self.PathSurfaceSupport.getSliceFromEnvelope(faceEnv) - newFace = self.PathSurfaceSupport.getShapeSlice(faceEnv) - # Compute necessary offset - offsetVal = calculateOffsetValue(obj, isHole) - expandedFace = self.PathSurfaceSupport.extractFaceOffset(newFace, offsetVal, newFace) - if expandedFace: - if shapeZ != 0.0: - expandedFace.translate(FreeCAD.Vector(0.0, 0.0, shapeZ)) - newFace.translate(FreeCAD.Vector(0.0, 0.0, shapeZ)) - - if isHole: - if obj.Side == 'Outside': - newFace = newFace.cut(expandedFace) - else: - newFace = expandedFace.cut(newFace) - else: - if obj.Side == 'Inside': - newFace = newFace.cut(expandedFace) - else: - newFace = expandedFace.cut(newFace) - - if finalDep - shapeZ != 0: - newFace.translate(FreeCAD.Vector(0.0, 0.0, finalDep - shapeZ)) - - if strDep - finalDep != 0: - if newFace.Area > 0: - return newFace.extrude(FreeCAD.Vector(0.0, 0.0, strDep - finalDep)) - else: - PathLog.debug('No expanded profile face shape.\n') - return False - else: - PathLog.debug(translate('PathProfile', 'Failed to extract offset(s) for expanded profile.') + '\n') - - PathLog.debug(translate('PathProfile', 'Failed to expand profile.') + '\n') - return False - # Method to handle each model as a whole, when no faces are selected - # It includes ExpandProfile implementation def _processEachModel(self, obj): shapeTups = list() for base in self.model: if hasattr(base, 'Shape'): env = PathUtils.getEnvelope(partshape=base.Shape, subshape=None, depthparams=self.depthparams) - if self.expandProfile: - eSlice = self.PathSurfaceSupport.getCrossSection(env) # getSliceFromEnvelope(env) - eSlice.translate(FreeCAD.Vector(0.0, 0.0, base.Shape.BoundBox.ZMin - env.BoundBox.ZMin)) - self._addDebugObject('ModelSlice', eSlice) - shapeEnv = self._getExpandedProfileEnvelope(obj, eSlice, False, obj.StartDepth.Value, obj.FinalDepth.Value) - else: - shapeEnv = env - - if shapeEnv: - shapeTups.append((shapeEnv, False)) + if env: + shapeTups.append((env, False)) return shapeTups # Edges pre-processing @@ -711,7 +594,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): basewires = list() delPairs = list() ezMin = None - self.cutOut = self.tool.Diameter * (float(obj.ExpandProfileStepOver) / 100.0) + self.cutOut = self.tool.Diameter for p in range(0, len(obj.Base)): (base, subsList) = obj.Base[p] @@ -745,11 +628,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): (origWire, flatWire) = self._flattenWire(obj, wire, obj.FinalDepth.Value) f = flatWire.Wires[0] if f: - if self.expandProfile: - shapeEnv = self._getExpandedProfileEnvelope(obj, Part.Face(f), False, obj.StartDepth.Value, ezMin) - else: - shapeEnv = PathUtils.getEnvelope(Part.Face(f), depthparams=self.depthparams) - + shapeEnv = PathUtils.getEnvelope(Part.Face(f), depthparams=self.depthparams) if shapeEnv: tup = shapeEnv, False, 'Profile', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value shapes.append(tup) @@ -771,14 +650,6 @@ class ObjectProfile(PathAreaOp.ObjectOp): self._addDebugObject('FlatWire', flatWire) - if self.expandProfile: - # Identify list of pass offset values for expanded profile paths - regularOfst = self.ofstRadius - targetOfst = regularOfst + obj.ExpandProfile.Value - while regularOfst < targetOfst: - regularOfst += self.cutOut - passOffsets.insert(0, regularOfst) - for po in passOffsets: self.ofstRadius = po cutShp = self._getCutAreaCrossSection(obj, base, origWire, flatWire) From 1f77dc2af8f2f56649a5b634db593355a3c5a5b0 Mon Sep 17 00:00:00 2001 From: Benjamin Nauck Date: Sat, 30 Jan 2021 01:05:47 +0100 Subject: [PATCH 092/168] [MeshPart] Fix copy-paste error It doesn't make sense to check minLen and then call SetMinSize to maxLen so this must be a copy-paste error. Introduced in https://github.com/FreeCAD/FreeCAD/commit/f681b86abdddd55e2dcb80bc4612251570cc9b8b Found with Coverity. --- src/Mod/MeshPart/App/Mesher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/MeshPart/App/Mesher.cpp b/src/Mod/MeshPart/App/Mesher.cpp index ae31e09135..538b30486e 100644 --- a/src/Mod/MeshPart/App/Mesher.cpp +++ b/src/Mod/MeshPart/App/Mesher.cpp @@ -368,7 +368,7 @@ Mesh::MeshObject* Mesher::createMesh() const if (maxLen > 0) hyp2d->SetMaxSize(maxLen); if (minLen > 0) - hyp2d->SetMinSize(maxLen); + hyp2d->SetMinSize(minLen); hyp2d->SetQuadAllowed(allowquad); hyp2d->SetOptimize(optimize); From f4c25397657682037ba4401357e99803907e4a45 Mon Sep 17 00:00:00 2001 From: Benjamin Nauck Date: Fri, 22 Jan 2021 02:58:47 +0100 Subject: [PATCH 093/168] Spreadsheet: Allow alias removed by undo to be reused Fixes issue descripted in the forum post: https://forum.freecadweb.org/viewtopic.php?f=3&t=54009 --- src/Mod/Spreadsheet/App/Cell.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Mod/Spreadsheet/App/Cell.cpp b/src/Mod/Spreadsheet/App/Cell.cpp index 3af0131c24..1f01fdb477 100644 --- a/src/Mod/Spreadsheet/App/Cell.cpp +++ b/src/Mod/Spreadsheet/App/Cell.cpp @@ -503,15 +503,22 @@ void Cell::setAlias(const std::string &n) owner->revAliasProp.erase(alias); - alias = n; - // Update owner - if (alias != "") { + if (!n.empty()) { owner->aliasProp[address] = n; owner->revAliasProp[n] = address; } - else + else { owner->aliasProp.erase(address); + } + + if (!alias.empty()) { + // The property may have been added in Sheet::updateAlias + auto * docObj = static_cast(owner->getContainer()); + docObj->removeDynamicProperty(alias.c_str()); + } + + alias = n; setUsed(ALIAS_SET, !alias.empty()); setDirty(); From 37952a5949bad69a50c5453fa315f0c315c3fe4f Mon Sep 17 00:00:00 2001 From: Benjamin Nauck Date: Fri, 22 Jan 2021 03:01:01 +0100 Subject: [PATCH 094/168] Spreadsheet: Add test for undo alias issue --- src/Mod/Spreadsheet/TestSpreadsheet.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Mod/Spreadsheet/TestSpreadsheet.py b/src/Mod/Spreadsheet/TestSpreadsheet.py index adda58466d..acc803993c 100644 --- a/src/Mod/Spreadsheet/TestSpreadsheet.py +++ b/src/Mod/Spreadsheet/TestSpreadsheet.py @@ -1102,6 +1102,20 @@ class SpreadsheetCases(unittest.TestCase): sheet.removeColumns('B', 1) sheet.setAlias('C3','test') + def testUndoAliasCreationReuseName(self): + """ Test deleted aliases by undo remains in database""" + sheet = self.doc.addObject('Spreadsheet::Sheet','Spreadsheet') + + self.doc.UndoMode = 1 + self.doc.openTransaction("create alias") + sheet.setAlias('B2', 'test') + self.doc.commitTransaction() + self.doc.recompute() + + self.doc.undo() + self.doc.recompute() + sheet.setAlias('C3','test') + def tearDown(self): #closing doc FreeCAD.closeDocument(self.doc.Name) From 446ce2151796349a5f1831041d0dc9d6366ae55d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Sun, 17 Jan 2021 10:45:50 +0100 Subject: [PATCH 095/168] Remove ExtensionProxy and rely on default Python proxy for extensions. fixes #0004534 --- src/App/Extension.h | 6 +--- src/App/ExtensionContainerPy.xml | 3 +- src/App/ExtensionContainerPyImp.cpp | 14 ++------- src/Gui/ViewProviderExtension.h | 4 --- src/Mod/Arch/ArchBuildingPart.py | 8 ++--- src/Mod/Arch/ArchProject.py | 4 +-- src/Mod/Arch/ArchSite.py | 4 +-- src/Mod/Draft/draftmake/make_clone.py | 2 +- src/Mod/Draft/draftobjects/draftlink.py | 2 +- src/Mod/Fem/femsolver/solverbase.py | 4 +-- src/Mod/Part/BasicShapes/Shapes.py | 2 +- .../Part/BasicShapes/ViewProviderShapes.py | 2 +- src/Mod/Test/Document.py | 31 +++++++++---------- 13 files changed, 32 insertions(+), 54 deletions(-) diff --git a/src/App/Extension.h b/src/App/Extension.h index cf60e0094b..4f2da76a06 100644 --- a/src/App/Extension.h +++ b/src/App/Extension.h @@ -336,13 +336,9 @@ public: ExtensionPythonT() { ExtensionT::m_isPythonExtension = true; ExtensionT::initExtensionType(ExtensionPythonT::getExtensionClassTypeId()); - - EXTENSION_ADD_PROPERTY(ExtensionProxy,(Py::Object())); } virtual ~ExtensionPythonT() { } - - PropertyPythonObject ExtensionProxy; }; typedef ExtensionPythonT ExtensionPython; @@ -352,7 +348,7 @@ typedef ExtensionPythonT ExtensionPython; Base::PyGILStateLocker lock;\ Py::Object result;\ try {\ - Property* proxy = this->extensionGetPropertyByName("ExtensionProxy");\ + Property* proxy = this->getExtendedContainer()->getPropertyByName("Proxy");\ if (proxy && proxy->getTypeId() == PropertyPythonObject::getClassTypeId()) {\ Py::Object feature = static_cast(proxy)->getValue();\ if (feature.hasAttr(std::string("function"))) {\ diff --git a/src/App/ExtensionContainerPy.xml b/src/App/ExtensionContainerPy.xml index 79a9e105d8..2497c3dda1 100644 --- a/src/App/ExtensionContainerPy.xml +++ b/src/App/ExtensionContainerPy.xml @@ -17,8 +17,7 @@ - Adds an extension to the object. Requires the string identifier as well as the python object - used to check for overridden functions (most likely self) + Adds an extension to the object. Requires the string identifier for the python extension as argument diff --git a/src/App/ExtensionContainerPyImp.cpp b/src/App/ExtensionContainerPyImp.cpp index 75a6f4d148..2dc70cd7cb 100644 --- a/src/App/ExtensionContainerPyImp.cpp +++ b/src/App/ExtensionContainerPyImp.cpp @@ -198,8 +198,7 @@ PyObject* ExtensionContainerPy::hasExtension(PyObject *args) { PyObject* ExtensionContainerPy::addExtension(PyObject *args) { char *typeId; - PyObject* proxy; - if (!PyArg_ParseTuple(args, "sO", &typeId, &proxy)) + if (!PyArg_ParseTuple(args, "s", &typeId)) return NULL; //get the extension type asked for @@ -223,16 +222,7 @@ PyObject* ExtensionContainerPy::addExtension(PyObject *args) { GetApplication().signalBeforeAddingDynamicExtension(*getExtensionContainerPtr(), typeId); ext->initExtension(getExtensionContainerPtr()); - //set the proxy to allow python overrides - App::Property* pp = ext->extensionGetPropertyByName("ExtensionProxy"); - if (!pp) { - std::stringstream str; - str << "Accessing the proxy property failed!" << std::ends; - throw Py::Exception(Base::BaseExceptionFreeCADError,str.str()); - } - static_cast(pp)->setPyObject(proxy); - - // The PyTypeObject is shared by all instances of this type and therefore + // The PyTypeObject is shared by all instances of this type and therefore // we have to add new methods only once. PyObject* obj = ext->getExtensionPyObject(); PyMethodDef* meth = reinterpret_cast(obj->ob_type->tp_methods); diff --git a/src/Gui/ViewProviderExtension.h b/src/Gui/ViewProviderExtension.h index dd6ce09bfd..27af83db83 100644 --- a/src/Gui/ViewProviderExtension.h +++ b/src/Gui/ViewProviderExtension.h @@ -130,13 +130,9 @@ public: ViewProviderExtensionPythonT() { ExtensionT::m_isPythonExtension = true; ExtensionT::initExtensionType(ViewProviderExtensionPythonT::getExtensionClassTypeId()); - - EXTENSION_ADD_PROPERTY(ExtensionProxy,(Py::Object())); } virtual ~ViewProviderExtensionPythonT() { } - - App::PropertyPythonObject ExtensionProxy; }; typedef ViewProviderExtensionPythonT ViewProviderExtensionPython; diff --git a/src/Mod/Arch/ArchBuildingPart.py b/src/Mod/Arch/ArchBuildingPart.py index ec9a8274ce..8b929ecbc7 100644 --- a/src/Mod/Arch/ArchBuildingPart.py +++ b/src/Mod/Arch/ArchBuildingPart.py @@ -323,8 +323,8 @@ class BuildingPart(ArchIFC.IfcProduct): def __init__(self,obj): obj.Proxy = self - obj.addExtension('App::GroupExtensionPython', self) - #obj.addExtension('App::OriginGroupExtensionPython', self) + obj.addExtension('App::GroupExtensionPython') + #obj.addExtension('App::OriginGroupExtensionPython') self.setProperties(obj) def setProperties(self,obj): @@ -488,8 +488,8 @@ class ViewProviderBuildingPart: def __init__(self,vobj): - vobj.addExtension("Gui::ViewProviderGroupExtensionPython", self) - #vobj.addExtension("Gui::ViewProviderGeoFeatureGroupExtensionPython", self) + vobj.addExtension("Gui::ViewProviderGroupExtensionPython") + #vobj.addExtension("Gui::ViewProviderGeoFeatureGroupExtensionPython") vobj.Proxy = self self.setProperties(vobj) vobj.ShapeColor = ArchCommands.getDefaultColor("Helpers") diff --git a/src/Mod/Arch/ArchProject.py b/src/Mod/Arch/ArchProject.py index 863e15f88c..ee2bb2920a 100644 --- a/src/Mod/Arch/ArchProject.py +++ b/src/Mod/Arch/ArchProject.py @@ -158,7 +158,7 @@ class _Project(ArchIFC.IfcContext): ArchIFC.IfcContext.setProperties(self, obj) pl = obj.PropertiesList if not hasattr(obj,"Group"): - obj.addExtension("App::GroupExtensionPython", self) + obj.addExtension("App::GroupExtensionPython") self.Type = "Project" def onDocumentRestored(self, obj): @@ -185,7 +185,7 @@ class _ViewProviderProject(ArchIFCView.IfcContextView): def __init__(self,vobj): vobj.Proxy = self - vobj.addExtension("Gui::ViewProviderGroupExtensionPython", self) + vobj.addExtension("Gui::ViewProviderGroupExtensionPython") def getIcon(self): """Return the path to the appropriate icon. diff --git a/src/Mod/Arch/ArchSite.py b/src/Mod/Arch/ArchSite.py index b75ba6c665..486cea4d05 100644 --- a/src/Mod/Arch/ArchSite.py +++ b/src/Mod/Arch/ArchSite.py @@ -631,7 +631,7 @@ class _Site(ArchIFC.IfcProduct): if not "OriginOffset" in pl: obj.addProperty("App::PropertyVector","OriginOffset","Site",QT_TRANSLATE_NOOP("App::Property","An optional offset between the model (0,0,0) origin and the point indicated by the geocoordinates")) if not hasattr(obj,"Group"): - obj.addExtension("App::GroupExtensionPython", self) + obj.addExtension("App::GroupExtensionPython") if not "IfcType" in pl: obj.addProperty("App::PropertyEnumeration","IfcType","IFC",QT_TRANSLATE_NOOP("App::Property","The type of this object")) obj.IfcType = ArchIFC.IfcTypes @@ -819,7 +819,7 @@ class _ViewProviderSite: def __init__(self,vobj): vobj.Proxy = self - vobj.addExtension("Gui::ViewProviderGroupExtensionPython", self) + vobj.addExtension("Gui::ViewProviderGroupExtensionPython") self.setProperties(vobj) def setProperties(self,vobj): diff --git a/src/Mod/Draft/draftmake/make_clone.py b/src/Mod/Draft/draftmake/make_clone.py index 4f9779181f..76e1f3de35 100644 --- a/src/Mod/Draft/draftmake/make_clone.py +++ b/src/Mod/Draft/draftmake/make_clone.py @@ -108,7 +108,7 @@ def make_clone(obj, delta=None, forcedraft=False): # fall back to Draft clone mode if not cl: cl = App.ActiveDocument.addObject("Part::FeaturePython","Clone") - cl.addExtension("Part::AttachExtensionPython", None) + cl.addExtension("Part::AttachExtensionPython") cl.Label = prefix + obj[0].Label Clone(cl) if App.GuiUp: diff --git a/src/Mod/Draft/draftobjects/draftlink.py b/src/Mod/Draft/draftobjects/draftlink.py index 0ad659b241..dfaa0d4dc4 100644 --- a/src/Mod/Draft/draftobjects/draftlink.py +++ b/src/Mod/Draft/draftobjects/draftlink.py @@ -79,7 +79,7 @@ class DraftLink(DraftObject): def attach(self, obj): """Set up the properties when the object is attached.""" if self.use_link: - obj.addExtension('App::LinkExtensionPython', None) + obj.addExtension('App::LinkExtensionPython') self.linkSetup(obj) def canLinkProperties(self, _obj): diff --git a/src/Mod/Fem/femsolver/solverbase.py b/src/Mod/Fem/femsolver/solverbase.py index 384bfc8914..2edd5a096d 100644 --- a/src/Mod/Fem/femsolver/solverbase.py +++ b/src/Mod/Fem/femsolver/solverbase.py @@ -48,7 +48,7 @@ class Proxy(object): def __init__(self, obj): obj.Proxy = self - obj.addExtension("App::GroupExtensionPython", self) + obj.addExtension("App::GroupExtensionPython") def createMachine(self, obj, directory, testmode): raise NotImplementedError() @@ -78,7 +78,7 @@ class ViewProxy(object): def __init__(self, vobj): vobj.Proxy = self - vobj.addExtension("Gui::ViewProviderGroupExtensionPython", self) + vobj.addExtension("Gui::ViewProviderGroupExtensionPython") def setEdit(self, vobj, mode=0): try: diff --git a/src/Mod/Part/BasicShapes/Shapes.py b/src/Mod/Part/BasicShapes/Shapes.py index a8140b6074..b4bbb5167a 100644 --- a/src/Mod/Part/BasicShapes/Shapes.py +++ b/src/Mod/Part/BasicShapes/Shapes.py @@ -43,7 +43,7 @@ class TubeFeature: obj.addProperty("App::PropertyLength","OuterRadius","Tube","Outer radius").OuterRadius = 5.0 obj.addProperty("App::PropertyLength","InnerRadius","Tube","Inner radius").InnerRadius = 2.0 obj.addProperty("App::PropertyLength","Height","Tube", "Height of the tube").Height = 10.0 - obj.addExtension("Part::AttachExtensionPython", self) + obj.addExtension("Part::AttachExtensionPython") def execute(self, fp): if fp.InnerRadius >= fp.OuterRadius: diff --git a/src/Mod/Part/BasicShapes/ViewProviderShapes.py b/src/Mod/Part/BasicShapes/ViewProviderShapes.py index 2933eca80a..881df6c988 100644 --- a/src/Mod/Part/BasicShapes/ViewProviderShapes.py +++ b/src/Mod/Part/BasicShapes/ViewProviderShapes.py @@ -36,7 +36,7 @@ class ViewProviderTube: def __init__(self, obj): ''' Set this object to the proxy object of the actual view provider ''' obj.Proxy = self - obj.addExtension("PartGui::ViewProviderAttachExtensionPython", self) + obj.addExtension("PartGui::ViewProviderAttachExtensionPython") obj.setIgnoreOverlayIcon(True, "PartGui::ViewProviderAttachExtensionPython") def attach(self, obj): diff --git a/src/Mod/Test/Document.py b/src/Mod/Test/Document.py index 5abee2e9e3..538f8212f1 100644 --- a/src/Mod/Test/Document.py +++ b/src/Mod/Test/Document.py @@ -171,7 +171,7 @@ class DocumentBasicCases(unittest.TestCase): FreeCAD.Console.PrintLog(" exception thrown, OK\n") else: self.fail("no exception thrown") - self.failUnless(sorted(L1.getEnumerationsOfProperty('Enum')) == sorted(['Zero', 'One', 'Two', 'Three', 'Four'])) + self.failUnless(sorted(L1.getEnumerationsOfProperty('Enum')) == sorted(['Zero', 'One', 'Two', 'Three', 'Four'])) #self.failUnless(L1.IntegerList == [4711] ) #f = L1.FloatList @@ -245,7 +245,7 @@ class DocumentBasicCases(unittest.TestCase): #we should have all methods we need to handle extensions try: self.failUnless(not grp.hasExtension("App::GroupExtensionPython")) - grp.addExtension("App::GroupExtensionPython", self) + grp.addExtension("App::GroupExtensionPython") self.failUnless(grp.hasExtension("App::GroupExtension")) self.failUnless(grp.hasExtension("App::GroupExtensionPython")) grp.addObject(obj) @@ -260,8 +260,9 @@ class DocumentBasicCases(unittest.TestCase): return False; callback = SpecialGroup() - grp2 = self.Doc.addObject("App::DocumentObject", "Extension_3") - grp2.addExtension("App::GroupExtensionPython", callback) + grp2 = self.Doc.addObject("App::FeaturePython", "Extension_3") + grp2.addExtension("App::GroupExtensionPython") + grp2.Proxy = callback try: self.failUnless(grp2.hasExtension("App::GroupExtension")) @@ -281,7 +282,7 @@ class DocumentBasicCases(unittest.TestCase): class MyExtension(): def __init__(self, obj): - obj.addExtension("App::GroupExtensionPython", self) + obj.addExtension("App::GroupExtensionPython") obj = self.Doc.addObject("App::DocumentObject", "myObj") MyExtension(obj) @@ -293,7 +294,7 @@ class DocumentBasicCases(unittest.TestCase): def testExtensionGroup(self): obj = self.Doc.addObject("App::DocumentObject", "Obj") grp = self.Doc.addObject("App::FeaturePython", "Extension_2") - grp.addExtension("App::GroupExtensionPython", None) + grp.addExtension("App::GroupExtensionPython") grp.Group = [obj] self.assertTrue(obj in grp.Group) @@ -301,11 +302,11 @@ class DocumentBasicCases(unittest.TestCase): class Layer(): def __init__(self, obj): - obj.addExtension("App::GroupExtensionPython", self) + obj.addExtension("App::GroupExtensionPython") class LayerViewProvider(): def __init__(self, obj): - obj.addExtension("Gui::ViewProviderGroupExtensionPython", self) + obj.addExtension("Gui::ViewProviderGroupExtensionPython") obj.Proxy = self obj = self.Doc.addObject("App::FeaturePython","Layer") @@ -385,7 +386,7 @@ class DocumentBasicCases(unittest.TestCase): # class must be defined in global scope to allow it to be reloaded on document open class SaveRestoreSpecialGroup(): def __init__(self, obj): - obj.addExtension("App::GroupExtensionPython", self) + obj.addExtension("App::GroupExtensionPython") obj.Proxy = self def allowObject(self, obj): @@ -394,7 +395,7 @@ class SaveRestoreSpecialGroup(): # class must be defined in global scope to allow it to be reloaded on document open class SaveRestoreSpecialGroupViewProvider(): def __init__(self, obj): - obj.addExtension("Gui::ViewProviderGroupExtensionPython", self) + obj.addExtension("Gui::ViewProviderGroupExtensionPython") obj.Proxy = self def testFunction(self): @@ -468,7 +469,7 @@ class DocumentSaveRestoreCases(unittest.TestCase): grp1 = Doc.addObject("App::DocumentObject", "Extension_1") grp2 = Doc.addObject("App::FeaturePython", "Extension_2") - grp1.addExtension("App::GroupExtensionPython", None) + grp1.addExtension("App::GroupExtensionPython") SaveRestoreSpecialGroup(grp2) if FreeCAD.GuiUp: SaveRestoreSpecialGroupViewProvider(grp2.ViewObject) @@ -480,16 +481,12 @@ class DocumentSaveRestoreCases(unittest.TestCase): self.failUnless(Doc.Extension_1.hasExtension("App::GroupExtension")) self.failUnless(Doc.Extension_2.hasExtension("App::GroupExtension")) - self.failUnless(Doc.Extension_1.ExtensionProxy is None) - self.failUnless(Doc.Extension_2.ExtensionProxy is not None) self.failUnless(Doc.Extension_2.Group[0] is Doc.Obj) self.failUnless(hasattr(Doc.Extension_2.Proxy, 'allowObject')) - self.failUnless(hasattr(Doc.Extension_2.ExtensionProxy, 'allowObject')) if FreeCAD.GuiUp: self.failUnless(Doc.Extension_2.ViewObject.hasExtension("Gui::ViewProviderGroupExtensionPython")) self.failUnless(hasattr(Doc.Extension_2.ViewObject.Proxy, 'testFunction')) - self.failUnless(hasattr(Doc.Extension_2.ViewObject.ExtensionProxy, 'testFunction')) FreeCAD.closeDocument("SaveRestoreExtensions") @@ -1803,7 +1800,7 @@ class DocumentObserverCases(unittest.TestCase): self.failUnless(self.Obs.parameter2.pop() == 'Prop') self.failUnless(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2) - pyobj.addExtension("App::GroupExtensionPython", None) + pyobj.addExtension("App::GroupExtensionPython") self.failUnless(self.Obs.signal.pop() == 'ObjDynExt') self.failUnless(self.Obs.parameter.pop() is pyobj) self.failUnless(self.Obs.parameter2.pop() == 'App::GroupExtensionPython') @@ -1945,7 +1942,7 @@ class DocumentObserverCases(unittest.TestCase): self.failUnless(self.GuiObs.parameter.pop(0) is obj.ViewObject) self.failUnless(not self.GuiObs.signal and not self.GuiObs.parameter and not self.GuiObs.parameter2) - obj.ViewObject.addExtension("Gui::ViewProviderGroupExtensionPython", None) + obj.ViewObject.addExtension("Gui::ViewProviderGroupExtensionPython") self.failUnless(self.Obs.signal.pop() == 'ObjDynExt') self.failUnless(self.Obs.parameter.pop() is obj.ViewObject) self.failUnless(self.Obs.parameter2.pop() == 'Gui::ViewProviderGroupExtensionPython') From 756654c46ef25f03e11c3b56f0b3b833a5e83693 Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 30 Jan 2021 17:20:43 +0100 Subject: [PATCH 096/168] App: [skip ci] print deprecation warning if addExtension() has two arguments --- src/App/ExtensionContainerPyImp.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/App/ExtensionContainerPyImp.cpp b/src/App/ExtensionContainerPyImp.cpp index 2dc70cd7cb..e09f4b3a56 100644 --- a/src/App/ExtensionContainerPyImp.cpp +++ b/src/App/ExtensionContainerPyImp.cpp @@ -198,9 +198,15 @@ PyObject* ExtensionContainerPy::hasExtension(PyObject *args) { PyObject* ExtensionContainerPy::addExtension(PyObject *args) { char *typeId; - if (!PyArg_ParseTuple(args, "s", &typeId)) + PyObject* proxy = nullptr; + if (!PyArg_ParseTuple(args, "s|O", &typeId, &proxy)) return NULL; + if (proxy) { + PyErr_SetString(PyExc_DeprecationWarning, "A proxy object as seconbd argument is not needed any more. Please adjust your code"); + PyErr_Print(); + } + //get the extension type asked for Base::Type extension = Base::Type::fromName(typeId); if (extension.isBad() || !extension.isDerivedFrom(App::Extension::getExtensionClassTypeId())) { From b896bb4f2a6cdb7e60b404965c9459f7580bd0b6 Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 30 Jan 2021 17:22:54 +0100 Subject: [PATCH 097/168] Part: [skip ci] fix path of attachment icon --- src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py b/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py index 7950ddba8d..cd33e428a5 100644 --- a/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py +++ b/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py @@ -224,7 +224,7 @@ class AttachmentEditorTaskPanel(FrozenClass): import os self.form=uic.loadUi(os.path.dirname(__file__) + os.path.sep + 'TaskAttachmentEditor.ui') - self.form.setWindowIcon(QtGui.QIcon(':/icons/Part_Attachment.svg')) + self.form.setWindowIcon(QtGui.QIcon(':/icons/tools/Part_Attachment.svg')) self.form.setWindowTitle(_translate('AttachmentEditor',"Attachment",None)) self.form.attachmentOffsetX.setProperty("unit", "mm") From 096e538c9efa3c07344232fa89fc07c4ae70a5da Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 30 Jan 2021 17:27:08 +0100 Subject: [PATCH 098/168] App: [skip ci] use better deprecation warning as used in PR 4335 --- src/App/ExtensionContainerPyImp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/App/ExtensionContainerPyImp.cpp b/src/App/ExtensionContainerPyImp.cpp index e09f4b3a56..a7f6705887 100644 --- a/src/App/ExtensionContainerPyImp.cpp +++ b/src/App/ExtensionContainerPyImp.cpp @@ -203,7 +203,8 @@ PyObject* ExtensionContainerPy::addExtension(PyObject *args) { return NULL; if (proxy) { - PyErr_SetString(PyExc_DeprecationWarning, "A proxy object as seconbd argument is not needed any more. Please adjust your code"); + PyErr_SetString(PyExc_DeprecationWarning, "Second argument is deprecated. It is ignored and will be removed in future versions. " + "The default Python feature proxy is used for extension method overrides."); PyErr_Print(); } From eb6167ff89bc2b287c83d726dfcd52b775d1757e Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sat, 30 Jan 2021 09:38:42 -0600 Subject: [PATCH 099/168] Enable LGTM to analyze some C++ source code FreeCAD as a whole is too large for LGTM to analyze the entire codebase, the operation times out after three hours. By focusing only on the core code, this patch allows the automated testing framework there to analyze our C++ code, in addition to the Python and JavaScript analsis it was already doing. --- lgtm.yml | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/lgtm.yml b/lgtm.yml index 3ba78f4bb2..3622f4d692 100644 --- a/lgtm.yml +++ b/lgtm.yml @@ -10,3 +10,52 @@ extraction: index: filters: exclude: "**/translations/*.ts" + cpp: + prepare: + packages: + - "cmake" + - "cmake-gui" + - "libboost-date-time-dev" + - "libboost-dev" + - "libboost-filesystem-dev" + - "libboost-graph-dev" + - "libboost-iostreams-dev" + - "libboost-program-options-dev" + - "libboost-python-dev" + - "libboost-regex-dev" + - "libboost-serialization-dev" + - "libboost-thread-dev" + - "libcoin-dev" + - "libeigen3-dev" + - "libgts-bin" + - "libgts-dev" + - "libkdtree++-dev" + - "libmedc-dev" + - "libocct-data-exchange-dev" + - "libocct-ocaf-dev" + - "libocct-visualization-dev" + - "libopencv-dev" + - "libproj-dev" + - "libpyside2-dev" + - "libshiboken2-dev" + - "libspnav-dev" + - "libvtk7-dev" + - "libx11-dev" + - "libxerces-c-dev" + - "libzipios++-dev" + - "occt-draw" + - "pyside2-tools" + - "python3-dev" + - "python3-matplotlib" + - "python3-pivy" + - "python3-ply" + - "python3-pyside2.qtcore" + - "python3-pyside2uic" + - "qtbase5-dev" + - "qttools5-dev" + - "swig" + configure: + command: "cmake ./ -DPYTHON_EXECUTABLE=/usr/bin/python3 -DBUILD_GUI=OFF -DBUILD_ARCH=OFF\ + \ -DBUILD_DRAWING=OFF -DBUILD_IMAGE=OFF -DBUILD_INSPECTION=OFF -DBUILD_OPENSCAD=OFF\ + \ -DBUILD_RAYTRACING=OFF -DBUILD_REVERSEENGINEERING=OFF -DBUILD_SURFACE=OFF -DBUILD_START=OFF\ + \ -DBUILD_ROBOT=OFF -DBUILD_PATH=OFF -DBUILD_FEM=OFF" \ No newline at end of file From 4ab3c95552b5b23227a90e62a3f613728068b0c3 Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 31 Jan 2021 00:32:23 +0100 Subject: [PATCH 100/168] [PD] correct hole cut definitions I was able to get the norms and could update the definitions accordingly. For example in the ISO 10462, sizes smaller than M3 are not defined and your definition files should only contain what is really defined. --- src/Mod/PartDesign/Resources/Hole/iso10642.json | 11 ++++------- src/Mod/PartDesign/Resources/Hole/iso7046.json | 16 +++++++++------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/Mod/PartDesign/Resources/Hole/iso10642.json b/src/Mod/PartDesign/Resources/Hole/iso10642.json index 4ab3affb6d..35aafa8a11 100644 --- a/src/Mod/PartDesign/Resources/Hole/iso10642.json +++ b/src/Mod/PartDesign/Resources/Hole/iso10642.json @@ -4,17 +4,14 @@ "thread_type": "metric", "angle": 90, "data": [ - { "thread": "M1.6", "diameter": 3.6 }, - { "thread": "M2", "diameter": 4.5 }, - { "thread": "M2.5", "diameter": 5.6 }, { "thread": "M3", "diameter": 6.7 }, { "thread": "M4", "diameter": 9.0 }, { "thread": "M5", "diameter": 12.2 }, - { "thread": "M6", "diameter": 13.5 }, - { "thread": "M8", "diameter": 18.0 }, + { "thread": "M6", "diameter": 13.4 }, + { "thread": "M8", "diameter": 17.9 }, { "thread": "M10", "diameter": 22.4 }, - { "thread": "M12", "diameter": 26.8 }, - { "thread": "M14", "diameter": 30.9 }, + { "thread": "M12", "diameter": 26.9 }, + { "thread": "M14", "diameter": 30.8 }, { "thread": "M16", "diameter": 33.6 }, { "thread": "M20", "diameter": 40.3 } ] diff --git a/src/Mod/PartDesign/Resources/Hole/iso7046.json b/src/Mod/PartDesign/Resources/Hole/iso7046.json index 6a4bbec5be..2985b7ba9d 100644 --- a/src/Mod/PartDesign/Resources/Hole/iso7046.json +++ b/src/Mod/PartDesign/Resources/Hole/iso7046.json @@ -4,13 +4,15 @@ "thread_type": "metric", "angle": 90, "data": [ - { "thread": "M2", "diameter": 4.3 }, - { "thread": "M2.5", "diameter": 5.3 }, + { "thread": "M1.6", "diameter": 3.6 } + { "thread": "M2", "diameter": 4.4 }, + { "thread": "M2.5", "diameter": 5.5 }, { "thread": "M3", "diameter": 6.3 }, - { "thread": "M4", "diameter": 9.5 }, - { "thread": "M5", "diameter": 10.5 }, - { "thread": "M6", "diameter": 12.7 }, - { "thread": "M8", "diameter": 17.7 }, - { "thread": "M10", "diameter": 20.5 } + { "thread": "M3.5", "diameter": 8.2 }, + { "thread": "M4", "diameter": 9.4 }, + { "thread": "M5", "diameter": 10.4 }, + { "thread": "M6", "diameter": 12.6 }, + { "thread": "M8", "diameter": 17.3 }, + { "thread": "M10", "diameter": 20.0 } ] } From d26b6542ca74f13e10836d2a549de1c5afa4cd66 Mon Sep 17 00:00:00 2001 From: Aapo Date: Sun, 24 Jan 2021 17:49:12 +0200 Subject: [PATCH 101/168] [TD] Fix regression in Dimensions: Setting arbitrary under tolerance was somehow lost. --- src/Mod/TechDraw/App/DrawViewDimension.cpp | 17 ++++++++++------- src/Mod/TechDraw/App/DrawViewDimension.h | 3 ++- src/Mod/TechDraw/Gui/TaskDimension.cpp | 6 +++--- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewDimension.cpp b/src/Mod/TechDraw/App/DrawViewDimension.cpp index f848373005..bb28a7ca53 100644 --- a/src/Mod/TechDraw/App/DrawViewDimension.cpp +++ b/src/Mod/TechDraw/App/DrawViewDimension.cpp @@ -111,7 +111,8 @@ DrawViewDimension::DrawViewDimension(void) References3D.setScope(App::LinkScope::Global); ADD_PROPERTY_TYPE(FormatSpec, (getDefaultFormatSpec()), "Format", App::Prop_Output,"Dimension Format"); - ADD_PROPERTY_TYPE(FormatSpecTolerance, (getDefaultFormatSpec(true)), "Format", App::Prop_Output, "Dimension tolerance format"); + ADD_PROPERTY_TYPE(FormatSpecUnderTolerance, (getDefaultFormatSpec(true)), "Format", App::Prop_Output, "Dimension tolerance format"); + ADD_PROPERTY_TYPE(FormatSpecOverTolerance, (getDefaultFormatSpec(true)), "Format", App::Prop_Output, "Dimension tolerance format"); ADD_PROPERTY_TYPE(Arbitrary,(false), "Format", App::Prop_Output, "Value overridden by user"); ADD_PROPERTY_TYPE(ArbitraryTolerances, (false), "Format", App::Prop_Output, "Tolerance values overridden by user"); @@ -846,11 +847,12 @@ std::string DrawViewDimension::formatValue(qreal value, QString qFormatSpec, int } return result; + } std::string DrawViewDimension::getFormattedToleranceValue(int partial) { - QString FormatSpec = QString::fromUtf8(FormatSpecTolerance.getStrValue().data()); + QString FormatSpec = QString::fromUtf8(FormatSpecOverTolerance.getStrValue().data()); QString ToleranceString; if (ArbitraryTolerances.getValue()) @@ -863,25 +865,26 @@ std::string DrawViewDimension::getFormattedToleranceValue(int partial) std::pair DrawViewDimension::getFormattedToleranceValues(int partial) { - QString FormatSpec = QString::fromUtf8(FormatSpecTolerance.getStrValue().data()); + QString underFormatSpec = QString::fromUtf8(FormatSpecUnderTolerance.getStrValue().data()); + QString overFormatSpec = QString::fromUtf8(FormatSpecOverTolerance.getStrValue().data()); std::pair tolerances; QString underTolerance, overTolerance; if (ArbitraryTolerances.getValue()) { - underTolerance = FormatSpec; - overTolerance = FormatSpec; + underTolerance = underFormatSpec; + overTolerance = overFormatSpec; } else { if (DrawUtil::fpCompare(UnderTolerance.getValue(), 0.0)) { underTolerance = QString::fromUtf8(formatValue(UnderTolerance.getValue(), QString::fromUtf8("%.0f"), partial).c_str()); } else { - underTolerance = QString::fromUtf8(formatValue(UnderTolerance.getValue(), FormatSpec, partial).c_str()); + underTolerance = QString::fromUtf8(formatValue(UnderTolerance.getValue(), underFormatSpec, partial).c_str()); } if (DrawUtil::fpCompare(OverTolerance.getValue(), 0.0)) { overTolerance = QString::fromUtf8(formatValue(OverTolerance.getValue(), QString::fromUtf8("%.0f"), partial).c_str()); } else { - overTolerance = QString::fromUtf8(formatValue(OverTolerance.getValue(), FormatSpec, partial).c_str()); + overTolerance = QString::fromUtf8(formatValue(OverTolerance.getValue(), overFormatSpec, partial).c_str()); } } diff --git a/src/Mod/TechDraw/App/DrawViewDimension.h b/src/Mod/TechDraw/App/DrawViewDimension.h index 8876bb5356..040bd22285 100644 --- a/src/Mod/TechDraw/App/DrawViewDimension.h +++ b/src/Mod/TechDraw/App/DrawViewDimension.h @@ -102,7 +102,8 @@ public: App::PropertyBool TheoreticalExact; App::PropertyBool Inverted; App::PropertyString FormatSpec; - App::PropertyString FormatSpecTolerance; + App::PropertyString FormatSpecUnderTolerance; + App::PropertyString FormatSpecOverTolerance; App::PropertyBool Arbitrary; App::PropertyBool ArbitraryTolerances; App::PropertyBool EqualTolerance; diff --git a/src/Mod/TechDraw/Gui/TaskDimension.cpp b/src/Mod/TechDraw/Gui/TaskDimension.cpp index ad8967bb31..f51c03cc13 100644 --- a/src/Mod/TechDraw/Gui/TaskDimension.cpp +++ b/src/Mod/TechDraw/Gui/TaskDimension.cpp @@ -98,7 +98,7 @@ TaskDimension::TaskDimension(QGIViewDimension *parent, ViewProviderDimension *di connect(ui->leFormatSpecifier, SIGNAL(textChanged(QString)), this, SLOT(onFormatSpecifierChanged())); ui->cbArbitrary->setChecked(parent->dvDimension->Arbitrary.getValue()); connect(ui->cbArbitrary, SIGNAL(stateChanged(int)), this, SLOT(onArbitraryChanged())); - StringValue = parent->dvDimension->FormatSpecTolerance.getValue(); + StringValue = parent->dvDimension->FormatSpecOverTolerance.getValue(); qs = QString::fromUtf8(StringValue.data(), StringValue.size()); ui->leToleranceFormatSpecifier->setText(qs); connect(ui->leToleranceFormatSpecifier, SIGNAL(textChanged(QString)), this, SLOT(onToleranceFormatSpecifierChanged())); @@ -133,7 +133,7 @@ bool TaskDimension::accept() m_parent->dvDimension->FormatSpec.setValue(ui->leFormatSpecifier->text().toUtf8().constData()); m_parent->dvDimension->Arbitrary.setValue(ui->cbArbitrary->isChecked()); - m_parent->dvDimension->FormatSpecTolerance.setValue(ui->leToleranceFormatSpecifier->text().toUtf8().constData()); + m_parent->dvDimension->FormatSpecOverTolerance.setValue(ui->leToleranceFormatSpecifier->text().toUtf8().constData()); m_parent->dvDimension->ArbitraryTolerances.setValue(ui->cbArbitraryTolerances->isChecked()); m_dimensionVP->FlipArrowheads.setValue(ui->cbArrowheads->isChecked()); @@ -234,7 +234,7 @@ void TaskDimension::onArbitraryChanged() void TaskDimension::onToleranceFormatSpecifierChanged() { - m_parent->dvDimension->FormatSpecTolerance.setValue(ui->leToleranceFormatSpecifier->text().toUtf8().constData()); + m_parent->dvDimension->FormatSpecOverTolerance.setValue(ui->leToleranceFormatSpecifier->text().toUtf8().constData()); recomputeFeature(); } From a2f060e36e0e11bb052c5569afff687e76030975 Mon Sep 17 00:00:00 2001 From: Aapo Date: Sun, 24 Jan 2021 18:39:31 +0200 Subject: [PATCH 102/168] [TD] Fix degree sign processing for equilateral angular plusminus dimensions. --- src/Mod/TechDraw/App/DrawViewDimension.cpp | 23 ++++++++++++++++++++++ src/Mod/TechDraw/Gui/QGIViewDimension.cpp | 11 ----------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewDimension.cpp b/src/Mod/TechDraw/App/DrawViewDimension.cpp index bb28a7ca53..cccbef2aac 100644 --- a/src/Mod/TechDraw/App/DrawViewDimension.cpp +++ b/src/Mod/TechDraw/App/DrawViewDimension.cpp @@ -902,6 +902,29 @@ std::string DrawViewDimension::getFormattedDimensionValue(int partial) return FormatSpec.getStrValue(); } + // if there is an equal over-/undertolerance and not theoretically exact, add the tolerance to dimension + if (EqualTolerance.getValue() && !DrawUtil::fpCompare(OverTolerance.getValue(), 0.0) + && !TheoreticalExact.getValue()) { + QString labelText = QString::fromUtf8(formatValue(getDimValue(), qFormatSpec, 1).c_str()); //just the number pref/spec/suf + QString unitText = QString::fromUtf8(formatValue(getDimValue(), qFormatSpec, 2).c_str()); //just the unit + QString tolerance = QString::fromStdString(getFormattedToleranceValue(1).c_str()); + QString result; + // tolerance might start with a plus sign that we don't want, so cut it off + if (tolerance.at(0) == QChar::fromLatin1('+')) + tolerance.remove(0, 1); + if ((Type.isValue("Angle")) || (Type.isValue("Angle3Pt"))) { + result = labelText + unitText + QString::fromUtf8(" \xC2\xB1 ") + tolerance; + } else { + // add the tolerance to the dimension using the ± sign + result = labelText + QString::fromUtf8(" \xC2\xB1 ") + tolerance; + } + if (partial == 2) { + result = unitText; + } + + return result.toStdString(); + } + return formatValue(getDimValue(), qFormatSpec, partial); } diff --git a/src/Mod/TechDraw/Gui/QGIViewDimension.cpp b/src/Mod/TechDraw/Gui/QGIViewDimension.cpp index b16d4c7763..56068b52c4 100644 --- a/src/Mod/TechDraw/Gui/QGIViewDimension.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewDimension.cpp @@ -646,17 +646,6 @@ void QGIViewDimension::updateDim() labelText = QString::fromUtf8(dim->getFormattedDimensionValue(1).c_str()); //just the number pref/spec/suf unitText = QString::fromUtf8(dim->getFormattedDimensionValue(2).c_str()); //just the unit } - // if there is an equal over-/undertolerance and not theoretically exact, add the tolerance to dimension - if (dim->EqualTolerance.getValue() && !DrawUtil::fpCompare(dim->OverTolerance.getValue(), 0.0) - && !dim->TheoreticalExact.getValue()) { - std::pair ToleranceText, ToleranceUnit; - QString tolerance = QString::fromStdString(dim->getFormattedToleranceValue(1).c_str()); - // tolerance might start with a plus sign that we don't want, so cut it off - if (tolerance.at(0) == QChar::fromLatin1('+')) - tolerance.remove(0, 1); - // add the tolerance to the dimension using the ± sign - labelText = labelText + QString::fromUtf8(" \xC2\xB1 ") + tolerance; - } } QFont font = datumLabel->getFont(); font.setFamily(QString::fromUtf8(vp->Font.getValue())); From 660710c26eee508ee2f70e6c945f8f877f571035 Mon Sep 17 00:00:00 2001 From: Aapo Date: Wed, 27 Jan 2021 21:46:22 +0200 Subject: [PATCH 103/168] [TD] Add under- and overtolerances for Dimension Task layout, fixes for Dimension tolerance refresh and behavior. --- src/Mod/TechDraw/App/DrawViewDimension.cpp | 42 +++++++++++++++---- src/Mod/TechDraw/App/DrawViewDimension.h | 2 +- src/Mod/TechDraw/Gui/TaskDimension.cpp | 49 +++++++++++++++++----- src/Mod/TechDraw/Gui/TaskDimension.h | 3 +- src/Mod/TechDraw/Gui/TaskDimension.ui | 20 +++++++-- 5 files changed, 93 insertions(+), 23 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewDimension.cpp b/src/Mod/TechDraw/App/DrawViewDimension.cpp index cccbef2aac..5078c52d43 100644 --- a/src/Mod/TechDraw/App/DrawViewDimension.cpp +++ b/src/Mod/TechDraw/App/DrawViewDimension.cpp @@ -110,9 +110,9 @@ DrawViewDimension::DrawViewDimension(void) ADD_PROPERTY_TYPE(References3D, (0,0), "", (App::Prop_None), "3D Geometry References"); References3D.setScope(App::LinkScope::Global); - ADD_PROPERTY_TYPE(FormatSpec, (getDefaultFormatSpec()), "Format", App::Prop_Output,"Dimension Format"); - ADD_PROPERTY_TYPE(FormatSpecUnderTolerance, (getDefaultFormatSpec(true)), "Format", App::Prop_Output, "Dimension tolerance format"); - ADD_PROPERTY_TYPE(FormatSpecOverTolerance, (getDefaultFormatSpec(true)), "Format", App::Prop_Output, "Dimension tolerance format"); + ADD_PROPERTY_TYPE(FormatSpec, (getDefaultFormatSpec()), "Format", App::Prop_Output,"Dimension format"); + ADD_PROPERTY_TYPE(FormatSpecOverTolerance, (getDefaultFormatSpec(true)), "Format", App::Prop_Output, "Dimension overtolerance format"); + ADD_PROPERTY_TYPE(FormatSpecUnderTolerance, (getDefaultFormatSpec(true)), "Format", App::Prop_Output, "Dimension undertolerance format"); ADD_PROPERTY_TYPE(Arbitrary,(false), "Format", App::Prop_Output, "Value overridden by user"); ADD_PROPERTY_TYPE(ArbitraryTolerances, (false), "Format", App::Prop_Output, "Tolerance values overridden by user"); @@ -143,6 +143,7 @@ DrawViewDimension::DrawViewDimension(void) // by default EqualTolerance is true, thus make UnderTolerance read-only UnderTolerance.setStatus(App::Property::ReadOnly, true); + FormatSpecUnderTolerance.setStatus(App::Property::ReadOnly, true); measurement = new Measure::Measurement(); //TODO: should have better initial datumLabel position than (0,0) in the DVP?? something closer to the object being measured? @@ -213,11 +214,16 @@ void DrawViewDimension::onChanged(const App::Property* prop) UnderTolerance.setValue(0.0); OverTolerance.setReadOnly(true); UnderTolerance.setReadOnly(true); + FormatSpecOverTolerance.setReadOnly(true); + FormatSpecUnderTolerance.setReadOnly(true); } else { OverTolerance.setReadOnly(false); - if (!EqualTolerance.getValue()) + FormatSpecOverTolerance.setReadOnly(false); + if (!EqualTolerance.getValue()) { UnderTolerance.setReadOnly(false); + FormatSpecUnderTolerance.setReadOnly(false); + } } requestPaint(); } @@ -233,11 +239,15 @@ void DrawViewDimension::onChanged(const App::Property* prop) UnderTolerance.setValue(-1.0 * OverTolerance.getValue()); UnderTolerance.setUnit(OverTolerance.getUnit()); UnderTolerance.setReadOnly(true); + FormatSpecUnderTolerance.setValue(FormatSpecOverTolerance.getValue()); + FormatSpecUnderTolerance.setReadOnly(true); } else { OverTolerance.setConstraints(&ToleranceConstraint); - if (!TheoreticalExact.getValue()) + if (!TheoreticalExact.getValue()) { UnderTolerance.setReadOnly(false); + FormatSpecUnderTolerance.setReadOnly(false); + } } requestPaint(); } @@ -249,8 +259,21 @@ void DrawViewDimension::onChanged(const App::Property* prop) } requestPaint(); } + else if (prop == &FormatSpecOverTolerance) { + if (!ArbitraryTolerances.getValue()) { + FormatSpecUnderTolerance.setValue(FormatSpecOverTolerance.getValue()); + } + requestPaint(); + } + else if (prop == &FormatSpecUnderTolerance) { + if (!ArbitraryTolerances.getValue()) { + FormatSpecOverTolerance.setValue(FormatSpecUnderTolerance.getValue()); + } + requestPaint(); + } else if ( (prop == &FormatSpec) || (prop == &Arbitrary) || + (prop == &ArbitraryTolerances) || (prop == &MeasureType) || (prop == &UnderTolerance) || (prop == &Inverted) ) { @@ -323,6 +346,9 @@ short DrawViewDimension::mustExecute() const Type.isTouched() || FormatSpec.isTouched() || Arbitrary.isTouched() || + FormatSpecOverTolerance.isTouched() || + FormatSpecUnderTolerance.isTouched() || + ArbitraryTolerances.isTouched() || MeasureType.isTouched() || TheoreticalExact.isTouched() || EqualTolerance.isTouched() || @@ -847,7 +873,6 @@ std::string DrawViewDimension::formatValue(qreal value, QString qFormatSpec, int } return result; - } std::string DrawViewDimension::getFormattedToleranceValue(int partial) @@ -1373,8 +1398,9 @@ bool DrawViewDimension::has3DReferences(void) const bool DrawViewDimension::hasOverUnderTolerance(void) const { bool result = false; - if (!DrawUtil::fpCompare(OverTolerance.getValue(), 0.0) || - !DrawUtil::fpCompare(UnderTolerance.getValue(), 0.0) ) { + if (ArbitraryTolerances.getValue() || + !DrawUtil::fpCompare(OverTolerance.getValue(), 0.0) || + !DrawUtil::fpCompare(UnderTolerance.getValue(), 0.0)) { result = true; } return result; diff --git a/src/Mod/TechDraw/App/DrawViewDimension.h b/src/Mod/TechDraw/App/DrawViewDimension.h index 040bd22285..c4efe316ec 100644 --- a/src/Mod/TechDraw/App/DrawViewDimension.h +++ b/src/Mod/TechDraw/App/DrawViewDimension.h @@ -102,8 +102,8 @@ public: App::PropertyBool TheoreticalExact; App::PropertyBool Inverted; App::PropertyString FormatSpec; - App::PropertyString FormatSpecUnderTolerance; App::PropertyString FormatSpecOverTolerance; + App::PropertyString FormatSpecUnderTolerance; App::PropertyBool Arbitrary; App::PropertyBool ArbitraryTolerances; App::PropertyBool EqualTolerance; diff --git a/src/Mod/TechDraw/Gui/TaskDimension.cpp b/src/Mod/TechDraw/Gui/TaskDimension.cpp index f51c03cc13..f41aa41fe8 100644 --- a/src/Mod/TechDraw/Gui/TaskDimension.cpp +++ b/src/Mod/TechDraw/Gui/TaskDimension.cpp @@ -54,7 +54,6 @@ using namespace TechDrawGui; TaskDimension::TaskDimension(QGIViewDimension *parent, ViewProviderDimension *dimensionVP) : ui(new Ui_TaskDimension) { - int i = 0; m_parent = parent; m_dimensionVP = dimensionVP; @@ -68,6 +67,8 @@ TaskDimension::TaskDimension(QGIViewDimension *parent, ViewProviderDimension *di ui->cbEqualTolerance->setDisabled(true); ui->qsbOvertolerance->setDisabled(true); ui->qsbUndertolerance->setDisabled(true); + ui->leFormatSpecifierOverTolerance->setDisabled(true); + ui->leFormatSpecifierUnderTolerance->setDisabled(true); } ui->cbEqualTolerance->setChecked(parent->dvDimension->EqualTolerance.getValue()); connect(ui->cbEqualTolerance, SIGNAL(stateChanged(int)), this, SLOT(onEqualToleranceChanged())); @@ -88,8 +89,10 @@ TaskDimension::TaskDimension(QGIViewDimension *parent, ViewProviderDimension *di connect(ui->qsbOvertolerance, SIGNAL(valueChanged(double)), this, SLOT(onOvertoleranceChanged())); connect(ui->qsbUndertolerance, SIGNAL(valueChanged(double)), this, SLOT(onUndertoleranceChanged())); // undertolerance is disabled when EqualTolerance is true - if (ui->cbEqualTolerance->isChecked()) + if (ui->cbEqualTolerance->isChecked()) { ui->qsbUndertolerance->setDisabled(true); + ui->leFormatSpecifierUnderTolerance->setDisabled(true); + } // Formatting std::string StringValue = parent->dvDimension->FormatSpec.getValue(); @@ -100,8 +103,12 @@ TaskDimension::TaskDimension(QGIViewDimension *parent, ViewProviderDimension *di connect(ui->cbArbitrary, SIGNAL(stateChanged(int)), this, SLOT(onArbitraryChanged())); StringValue = parent->dvDimension->FormatSpecOverTolerance.getValue(); qs = QString::fromUtf8(StringValue.data(), StringValue.size()); - ui->leToleranceFormatSpecifier->setText(qs); - connect(ui->leToleranceFormatSpecifier, SIGNAL(textChanged(QString)), this, SLOT(onToleranceFormatSpecifierChanged())); + ui->leFormatSpecifierOverTolerance->setText(qs); + StringValue = parent->dvDimension->FormatSpecUnderTolerance.getValue(); + qs = QString::fromUtf8(StringValue.data(), StringValue.size()); + ui->leFormatSpecifierUnderTolerance->setText(qs); + connect(ui->leFormatSpecifierOverTolerance, SIGNAL(textChanged(QString)), this, SLOT(onFormatSpecifierOverToleranceChanged())); + connect(ui->leFormatSpecifierUnderTolerance, SIGNAL(textChanged(QString)), this, SLOT(onFormatSpecifierUnderToleranceChanged())); ui->cbArbitraryTolerances->setChecked(parent->dvDimension->ArbitraryTolerances.getValue()); connect(ui->cbArbitraryTolerances, SIGNAL(stateChanged(int)), this, SLOT(onArbitraryTolerancesChanged())); @@ -133,7 +140,8 @@ bool TaskDimension::accept() m_parent->dvDimension->FormatSpec.setValue(ui->leFormatSpecifier->text().toUtf8().constData()); m_parent->dvDimension->Arbitrary.setValue(ui->cbArbitrary->isChecked()); - m_parent->dvDimension->FormatSpecOverTolerance.setValue(ui->leToleranceFormatSpecifier->text().toUtf8().constData()); + m_parent->dvDimension->FormatSpecOverTolerance.setValue(ui->leFormatSpecifierOverTolerance->text().toUtf8().constData()); + m_parent->dvDimension->FormatSpecUnderTolerance.setValue(ui->leFormatSpecifierUnderTolerance->text().toUtf8().constData()); m_parent->dvDimension->ArbitraryTolerances.setValue(ui->cbArbitraryTolerances->isChecked()); m_dimensionVP->FlipArrowheads.setValue(ui->cbArrowheads->isChecked()); @@ -170,12 +178,17 @@ void TaskDimension::onTheoreticallyExactChanged() ui->cbEqualTolerance->setDisabled(true); ui->qsbOvertolerance->setDisabled(true); ui->qsbUndertolerance->setDisabled(true); + ui->leFormatSpecifierOverTolerance->setDisabled(true); + ui->leFormatSpecifierUnderTolerance->setDisabled(true); } else { ui->cbEqualTolerance->setDisabled(false); ui->qsbOvertolerance->setDisabled(false); - if (!ui->cbEqualTolerance->isChecked()) + ui->leFormatSpecifierOverTolerance->setDisabled(false); + if (!ui->cbEqualTolerance->isChecked()) { ui->qsbUndertolerance->setDisabled(false); + ui->leFormatSpecifierUnderTolerance->setDisabled(false); + } } recomputeFeature(); } @@ -193,13 +206,15 @@ void TaskDimension::onEqualToleranceChanged() ui->qsbUndertolerance->setValue(-1.0 * ui->qsbOvertolerance->value().getValue()); ui->qsbUndertolerance->setUnit(ui->qsbOvertolerance->value().getUnit()); ui->qsbUndertolerance->setDisabled(true); + ui->leFormatSpecifierUnderTolerance->setDisabled(true); } else { ui->qsbOvertolerance->setMinimum(-DBL_MAX); - if (!ui->cbTheoreticallyExact->isChecked()) + if (!ui->cbTheoreticallyExact->isChecked()) { ui->qsbUndertolerance->setDisabled(false); + ui->leFormatSpecifierUnderTolerance->setDisabled(false); + } } - ui->qsbUndertolerance->setDisabled(ui->cbEqualTolerance->isChecked()); recomputeFeature(); } @@ -232,9 +247,23 @@ void TaskDimension::onArbitraryChanged() recomputeFeature(); } -void TaskDimension::onToleranceFormatSpecifierChanged() +void TaskDimension::onFormatSpecifierOverToleranceChanged() { - m_parent->dvDimension->FormatSpecOverTolerance.setValue(ui->leToleranceFormatSpecifier->text().toUtf8().constData()); + m_parent->dvDimension->FormatSpecOverTolerance.setValue(ui->leFormatSpecifierOverTolerance->text().toUtf8().constData()); + if (!ui->cbArbitraryTolerances->isChecked()) { + ui->leFormatSpecifierUnderTolerance->setText(ui->leFormatSpecifierOverTolerance->text()); + m_parent->dvDimension->FormatSpecUnderTolerance.setValue(ui->leFormatSpecifierUnderTolerance->text().toUtf8().constData()); + } + recomputeFeature(); +} + +void TaskDimension::onFormatSpecifierUnderToleranceChanged() +{ + m_parent->dvDimension->FormatSpecUnderTolerance.setValue(ui->leFormatSpecifierUnderTolerance->text().toUtf8().constData()); + if (!ui->cbArbitraryTolerances->isChecked()) { + ui->leFormatSpecifierOverTolerance->setText(ui->leFormatSpecifierUnderTolerance->text()); + m_parent->dvDimension->FormatSpecOverTolerance.setValue(ui->leFormatSpecifierOverTolerance->text().toUtf8().constData()); + } recomputeFeature(); } diff --git a/src/Mod/TechDraw/Gui/TaskDimension.h b/src/Mod/TechDraw/Gui/TaskDimension.h index 6a4095460e..97d2a48633 100644 --- a/src/Mod/TechDraw/Gui/TaskDimension.h +++ b/src/Mod/TechDraw/Gui/TaskDimension.h @@ -56,7 +56,8 @@ private Q_SLOTS: void onUndertoleranceChanged(); void onFormatSpecifierChanged(); void onArbitraryChanged(); - void onToleranceFormatSpecifierChanged(); + void onFormatSpecifierOverToleranceChanged(); + void onFormatSpecifierUnderToleranceChanged(); void onArbitraryTolerancesChanged(); void onFlipArrowheadsChanged(); void onColorChanged(); diff --git a/src/Mod/TechDraw/Gui/TaskDimension.ui b/src/Mod/TechDraw/Gui/TaskDimension.ui index 4af2f251c7..f6a54c1dd7 100644 --- a/src/Mod/TechDraw/Gui/TaskDimension.ui +++ b/src/Mod/TechDraw/Gui/TaskDimension.ui @@ -145,18 +145,32 @@ be used instead if the dimension value - Tolerance Format Specifier: + OverTolerance Format Specifier: - + - Text to be displayed + Specifies the overtolerance format in printf() style, or arbitrary text + + + UnderTolerance Format Specifier: + + + + + + + Specifies the undertolerance format in printf() style, or arbitrary text + + + + If checked the content of 'Format Spec' will From e802179cf4272c9a89d1711fc796981d2a077c71 Mon Sep 17 00:00:00 2001 From: Aapo Date: Fri, 29 Jan 2021 01:35:12 +0200 Subject: [PATCH 104/168] [TD] Improve arbitrary tolerance and unit handling for Equal Tolerances. --- src/Mod/TechDraw/App/DrawViewDimension.cpp | 10 ++++++--- src/Mod/TechDraw/Gui/QGIViewDimension.cpp | 25 +++++++++++----------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewDimension.cpp b/src/Mod/TechDraw/App/DrawViewDimension.cpp index 5078c52d43..394c8d3659 100644 --- a/src/Mod/TechDraw/App/DrawViewDimension.cpp +++ b/src/Mod/TechDraw/App/DrawViewDimension.cpp @@ -923,17 +923,21 @@ std::string DrawViewDimension::getFormattedDimensionValue(int partial) { QString qFormatSpec = QString::fromUtf8(FormatSpec.getStrValue().data()); - if (Arbitrary.getValue()) { + if (Arbitrary.getValue() && !EqualTolerance.getValue()) { return FormatSpec.getStrValue(); } // if there is an equal over-/undertolerance and not theoretically exact, add the tolerance to dimension - if (EqualTolerance.getValue() && !DrawUtil::fpCompare(OverTolerance.getValue(), 0.0) - && !TheoreticalExact.getValue()) { + if (EqualTolerance.getValue() && !TheoreticalExact.getValue() && + (!DrawUtil::fpCompare(OverTolerance.getValue(), 0.0) || ArbitraryTolerances.getValue())) { QString labelText = QString::fromUtf8(formatValue(getDimValue(), qFormatSpec, 1).c_str()); //just the number pref/spec/suf QString unitText = QString::fromUtf8(formatValue(getDimValue(), qFormatSpec, 2).c_str()); //just the unit QString tolerance = QString::fromStdString(getFormattedToleranceValue(1).c_str()); QString result; + if (Arbitrary.getValue()) { + labelText = QString::fromStdString(FormatSpec.getStrValue()); + unitText = QString(); + } // tolerance might start with a plus sign that we don't want, so cut it off if (tolerance.at(0) == QChar::fromLatin1('+')) tolerance.remove(0, 1); diff --git a/src/Mod/TechDraw/Gui/QGIViewDimension.cpp b/src/Mod/TechDraw/Gui/QGIViewDimension.cpp index 56068b52c4..e8cbf9204b 100644 --- a/src/Mod/TechDraw/Gui/QGIViewDimension.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewDimension.cpp @@ -337,14 +337,10 @@ void QGIDatumLabel::setToleranceString() if( dim == nullptr ) { return; // don't show if both are zero or if EqualTolerance is true - } else if (!dim->hasOverUnderTolerance() || dim->EqualTolerance.getValue()) { + } else if (!dim->hasOverUnderTolerance() || dim->EqualTolerance.getValue() || dim->TheoreticalExact.getValue()) { m_tolTextOver->hide(); m_tolTextUnder->hide(); - return; - } else if (dim->TheoreticalExact.getValue()) { - m_tolTextOver->hide(); - m_tolTextUnder->hide(); - // we must explicitly empy the text other wise the frame drawn for + // we must explicitly empty the text otherwise the frame drawn for // TheoreticalExact would be as wide as necessary for the text m_tolTextOver->setPlainText(QString()); m_tolTextUnder->setPlainText(QString()); @@ -353,11 +349,6 @@ void QGIDatumLabel::setToleranceString() m_tolTextOver->show(); m_tolTextUnder->show(); - QString tolSuffix; - if ((dim->Type.isValue("Angle")) || (dim->Type.isValue("Angle3Pt"))) { - tolSuffix = QString::fromUtf8(dim->getFormattedDimensionValue(2).c_str()); //just the unit - } - std::pair labelTexts, unitTexts; if (dim->ArbitraryTolerances.getValue()) { @@ -637,14 +628,22 @@ void QGIViewDimension::updateDim() QString labelText; QString unitText; - if (dim->Arbitrary.getValue()) { + if (dim->Arbitrary.getValue() && !dim->EqualTolerance.getValue()) { labelText = QString::fromUtf8(dim->getFormattedDimensionValue(1).c_str()); //just the number pref/spec/suf } else { if (dim->isMultiValueSchema()) { labelText = QString::fromUtf8(dim->getFormattedDimensionValue(0).c_str()); //don't format multis } else { labelText = QString::fromUtf8(dim->getFormattedDimensionValue(1).c_str()); //just the number pref/spec/suf - unitText = QString::fromUtf8(dim->getFormattedDimensionValue(2).c_str()); //just the unit + if (dim->EqualTolerance.getValue()) { + if (dim->ArbitraryTolerances.getValue()) { + unitText = QString(); + } else { + unitText = QString::fromUtf8(dim->getFormattedToleranceValue(2).c_str()); //just the unit + } + } else { + unitText = QString::fromUtf8(dim->getFormattedDimensionValue(2).c_str()); //just the unit + } } } QFont font = datumLabel->getFont(); From 4ea0784341dc8d64d8b4c84b574492abbc487c93 Mon Sep 17 00:00:00 2001 From: Aapo Date: Fri, 29 Jan 2021 10:27:11 +0200 Subject: [PATCH 105/168] [TD] Dimensions: donovaly's minor fixes for TheoreticalExact dimensions. --- src/Mod/TechDraw/App/DrawViewDimension.cpp | 6 +++++- src/Mod/TechDraw/Gui/QGIViewDimension.cpp | 3 ++- src/Mod/TechDraw/Gui/TaskDimension.cpp | 3 +++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewDimension.cpp b/src/Mod/TechDraw/App/DrawViewDimension.cpp index 394c8d3659..d0f25e58ee 100644 --- a/src/Mod/TechDraw/App/DrawViewDimension.cpp +++ b/src/Mod/TechDraw/App/DrawViewDimension.cpp @@ -216,10 +216,13 @@ void DrawViewDimension::onChanged(const App::Property* prop) UnderTolerance.setReadOnly(true); FormatSpecOverTolerance.setReadOnly(true); FormatSpecUnderTolerance.setReadOnly(true); + ArbitraryTolerances.setValue(false); + ArbitraryTolerances.setReadOnly(true); } else { OverTolerance.setReadOnly(false); FormatSpecOverTolerance.setReadOnly(false); + ArbitraryTolerances.setReadOnly(false); if (!EqualTolerance.getValue()) { UnderTolerance.setReadOnly(false); FormatSpecUnderTolerance.setReadOnly(false); @@ -923,7 +926,8 @@ std::string DrawViewDimension::getFormattedDimensionValue(int partial) { QString qFormatSpec = QString::fromUtf8(FormatSpec.getStrValue().data()); - if (Arbitrary.getValue() && !EqualTolerance.getValue()) { + if ( (Arbitrary.getValue() && !EqualTolerance.getValue()) + || (Arbitrary.getValue() && TheoreticalExact.getValue()) ) { return FormatSpec.getStrValue(); } diff --git a/src/Mod/TechDraw/Gui/QGIViewDimension.cpp b/src/Mod/TechDraw/Gui/QGIViewDimension.cpp index e8cbf9204b..2bef981208 100644 --- a/src/Mod/TechDraw/Gui/QGIViewDimension.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewDimension.cpp @@ -628,7 +628,8 @@ void QGIViewDimension::updateDim() QString labelText; QString unitText; - if (dim->Arbitrary.getValue() && !dim->EqualTolerance.getValue()) { + if ( (dim->Arbitrary.getValue() && !dim->EqualTolerance.getValue()) + || (dim->Arbitrary.getValue() && dim->TheoreticalExact.getValue()) ) { labelText = QString::fromUtf8(dim->getFormattedDimensionValue(1).c_str()); //just the number pref/spec/suf } else { if (dim->isMultiValueSchema()) { diff --git a/src/Mod/TechDraw/Gui/TaskDimension.cpp b/src/Mod/TechDraw/Gui/TaskDimension.cpp index f41aa41fe8..83d8690255 100644 --- a/src/Mod/TechDraw/Gui/TaskDimension.cpp +++ b/src/Mod/TechDraw/Gui/TaskDimension.cpp @@ -180,11 +180,14 @@ void TaskDimension::onTheoreticallyExactChanged() ui->qsbUndertolerance->setDisabled(true); ui->leFormatSpecifierOverTolerance->setDisabled(true); ui->leFormatSpecifierUnderTolerance->setDisabled(true); + ui->cbArbitraryTolerances->setDisabled(true); + ui->cbArbitraryTolerances->setChecked(false); } else { ui->cbEqualTolerance->setDisabled(false); ui->qsbOvertolerance->setDisabled(false); ui->leFormatSpecifierOverTolerance->setDisabled(false); + ui->cbArbitraryTolerances->setDisabled(false); if (!ui->cbEqualTolerance->isChecked()) { ui->qsbUndertolerance->setDisabled(false); ui->leFormatSpecifierUnderTolerance->setDisabled(false); From b50838c7a5d5c1303774e49a0bf53af1a92da7a0 Mon Sep 17 00:00:00 2001 From: donovaly Date: Sat, 16 Jan 2021 18:28:47 +0100 Subject: [PATCH 106/168] [PD] fix alignment in pocket dialog just a minor UI issue: when you decrease the width of the pocket dialog (e.g. because you have a small screen), the alignment of the widgets is lost. This PR fixes this by grouping the widgets in a grid. --- .../PartDesign/Gui/TaskPocketParameters.ui | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/Mod/PartDesign/Gui/TaskPocketParameters.ui b/src/Mod/PartDesign/Gui/TaskPocketParameters.ui index 29ea32605f..cf487e40eb 100644 --- a/src/Mod/PartDesign/Gui/TaskPocketParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskPocketParameters.ui @@ -6,8 +6,8 @@ 0 0 - 241 - 233 + 193 + 272 @@ -15,15 +15,15 @@ - - + + Type - + @@ -32,18 +32,14 @@ - - - - - + Length - + false @@ -53,18 +49,14 @@ - - - - - + Offset - + false @@ -153,6 +145,17 @@
      Gui/PrefWidgets.h
      + + changeMode + lengthEdit + offsetEdit + checkBoxMidplane + checkBoxReversed + lengthEdit2 + buttonFace + lineFaceName + checkBoxUpdateView + From 93a956e70d45964f00959a519f4fe3b9714b1563 Mon Sep 17 00:00:00 2001 From: wmayer Date: Sun, 31 Jan 2021 16:26:22 +0100 Subject: [PATCH 107/168] [PD] [skip ci] fix alignment in pad dialog just a minor UI issue: when you decrease the width of the pad dialog (e.g. because you have a small screen), the alignment of the widgets is lost. This fixes it by grouping the widgets in a grid. --- src/Mod/PartDesign/Gui/TaskPadParameters.ui | 40 +++++++-------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/src/Mod/PartDesign/Gui/TaskPadParameters.ui b/src/Mod/PartDesign/Gui/TaskPadParameters.ui index 375986c44e..5a040a832d 100644 --- a/src/Mod/PartDesign/Gui/TaskPadParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskPadParameters.ui @@ -15,15 +15,15 @@
      - - + + Type - + @@ -32,18 +32,14 @@ - - - - - + Length - + false @@ -70,17 +66,15 @@ the sketch plane's normal vector will be used true - - - - + + x - + x-component of direction vector @@ -102,18 +96,14 @@ the sketch plane's normal vector will be used - - - - - + y - + y-component of direction vector @@ -135,18 +125,14 @@ the sketch plane's normal vector will be used - - - - - + z - + z-component of direction vector @@ -171,8 +157,6 @@ the sketch plane's normal vector will be used - - From bead9bb9381d039d6dda438b07e30dace7cf33ae Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Tue, 5 Jan 2021 16:03:54 +0800 Subject: [PATCH 108/168] Gui: sync recent action file list from external modification --- src/Gui/Action.cpp | 48 ++++++++++++++++++++++++++++++++++++---------- src/Gui/Action.h | 5 +++++ 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/Gui/Action.cpp b/src/Gui/Action.cpp index f759c4cd04..b167bf0a6e 100644 --- a/src/Gui/Action.cpp +++ b/src/Gui/Action.cpp @@ -694,11 +694,42 @@ void WorkbenchGroup::slotRemoveWorkbench(const char* name) // -------------------------------------------------------------------- +class RecentFilesAction::Private: public ParameterGrp::ObserverType +{ +public: + Private(RecentFilesAction *master, const char *path):master(master) + { + handle = App::GetApplication().GetParameterGroupByPath(path); + handle->Attach(this); + } + + virtual ~Private() + { + handle->Detach(this); + } + + void OnChange(Base::Subject &, const char *reason) + { + if (!updating && reason && strcmp(reason, "RecentFiles")==0) { + Base::StateLocker guard(updating); + master->restore(); + } + } + +public: + RecentFilesAction *master; + ParameterGrp::handle handle; + bool updating = false; +}; + +// -------------------------------------------------------------------- + /* TRANSLATOR Gui::RecentFilesAction */ RecentFilesAction::RecentFilesAction ( Command* pcCmd, QObject * parent ) : ActionGroup( pcCmd, parent ), visibleItems(4), maximumItems(20) { + _pimpl.reset(new Private(this, "User parameter:BaseApp/Preferences/RecentFiles")); restore(); } @@ -808,13 +839,10 @@ void RecentFilesAction::resizeList(int size) /** Loads all recent files from the preferences. */ void RecentFilesAction::restore() { - ParameterGrp::handle hGrp = App::GetApplication().GetUserParameter().GetGroup("BaseApp")->GetGroup("Preferences"); - if (hGrp->HasGroup("RecentFiles")) { - hGrp = hGrp->GetGroup("RecentFiles"); - // we want at least 20 items but we do only show the number of files - // that is defined in user parameters - this->visibleItems = hGrp->GetInt("RecentFiles", this->visibleItems); - } + ParameterGrp::handle hGrp = _pimpl->handle; + // we want at least 20 items but we do only show the number of files + // that is defined in user parameters + this->visibleItems = hGrp->GetInt("RecentFiles", this->visibleItems); int count = std::max(this->maximumItems, this->visibleItems); for (int i=0; iGetGroup("Preferences")->GetGroup("RecentFiles"); + ParameterGrp::handle hGrp = _pimpl->handle; int count = hGrp->GetInt("RecentFiles", this->visibleItems); // save number of files hGrp->Clear(); @@ -845,6 +872,7 @@ void RecentFilesAction::save() hGrp->SetASCII(key.toLatin1(), value.toUtf8()); } + Base::StateLocker guard(_pimpl->updating); hGrp->SetInt("RecentFiles", count); // restore } @@ -1004,7 +1032,7 @@ void RecentMacrosAction::restore() } int count = std::max(this->maximumItems, this->visibleItems); - for (int i=0; iactions().size(); iaddAction(QLatin1String(""))->setVisible(false); std::vector MRU = hGrp->GetASCIIs("MRU"); QStringList files; diff --git a/src/Gui/Action.h b/src/Gui/Action.h index cd7d0ad3a8..5aa6f2c313 100644 --- a/src/Gui/Action.h +++ b/src/Gui/Action.h @@ -24,6 +24,7 @@ #ifndef GUI_ACTION_H #define GUI_ACTION_H +#include #include #include #include @@ -210,6 +211,10 @@ private: private: int visibleItems; /**< Number of visible items */ int maximumItems; /**< Number of maximum items */ + + class Private; + friend class Private; + std::unique_ptr _pimpl; }; // -------------------------------------------------------------------- From 9f07b72b40eb4157dcd1a95e4f4f11501fd99b67 Mon Sep 17 00:00:00 2001 From: Patrick F Date: Sun, 31 Jan 2021 21:23:57 +0100 Subject: [PATCH 109/168] [PATH] Limited cone angle for adaptiv helix --- src/Mod/Path/PathScripts/PathAdaptive.py | 8 +++++++- src/Mod/Path/PathScripts/PathAdaptiveGui.py | 8 ++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathAdaptive.py b/src/Mod/Path/PathScripts/PathAdaptive.py index f55e9ef1ea..e171813f80 100644 --- a/src/Mod/Path/PathScripts/PathAdaptive.py +++ b/src/Mod/Path/PathScripts/PathAdaptive.py @@ -212,6 +212,12 @@ def GenerateGCode(op,obj,adaptiveResults, helixDiameter): else: # Cone _HelixAngle = 360 - (float(obj.HelixAngle) * 4) + + if obj.HelixConeAngle > 6: + obj.HelixConeAngle = 6 + + helixRadius *= 0.9 + # Calculate everything helix_height = passStartDepth - passEndDepth r_extra = helix_height * math.tan(math.radians(obj.HelixConeAngle)) @@ -250,7 +256,7 @@ def GenerateGCode(op,obj,adaptiveResults, helixDiameter): i_off = (x_m - p['X']) / 2 j_off = (y_m - p['Y']) / 2 - # one more circle at target depth to make sure center is cleared + # One more circle at target depth to make sure center is cleared op.commandlist.append(Path.Command("G3", { "X": x_m, "Y": y_m, "Z": passEndDepth, "I": i_off, "J": j_off, "F": op.horizFeed})) op.commandlist.append(Path.Command("G3", { "X": p['X'], "Y": p['Y'], "Z": passEndDepth, "I": -i_off, "J": -j_off, "F": op.horizFeed})) diff --git a/src/Mod/Path/PathScripts/PathAdaptiveGui.py b/src/Mod/Path/PathScripts/PathAdaptiveGui.py index de3a49d918..0e25e92128 100644 --- a/src/Mod/Path/PathScripts/PathAdaptiveGui.py +++ b/src/Mod/Path/PathScripts/PathAdaptiveGui.py @@ -79,19 +79,19 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): # helix angle form.HelixAngle = QtGui.QDoubleSpinBox() form.HelixAngle.setMinimum(1) - form.HelixAngle.setMaximum(359) + form.HelixAngle.setMaximum(89) form.HelixAngle.setSingleStep(1) - form.HelixAngle.setValue(250) + form.HelixAngle.setValue(5) form.HelixAngle.setToolTip("Angle of the helix ramp entry") formLayout.addRow(QtGui.QLabel("Helix Ramp Angle"), form.HelixAngle) # helix cone angle form.HelixConeAngle = QtGui.QDoubleSpinBox() form.HelixConeAngle.setMinimum(0) - form.HelixConeAngle.setMaximum(45) + form.HelixConeAngle.setMaximum(6) form.HelixConeAngle.setSingleStep(1) form.HelixConeAngle.setValue(0) - form.HelixConeAngle.setToolTip("Angle of the helix cone") + form.HelixConeAngle.setToolTip("Angle of the helix entry cone") formLayout.addRow(QtGui.QLabel("Helix Cone Angle"), form.HelixConeAngle) # helix diam. limit From 7b9c9d4f5ec8f83c0bcccc95872d032806a70d1b Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 31 Jan 2021 17:24:49 -0600 Subject: [PATCH 110/168] Add tests for Start Page HTML validity This test is in two parts: first the generated HTML is sanitized to remove any potentially sensitive information (e.g. filenames, authors, document info, etc.) and is then sent to the W3C Validator service at https://validator.w3.org/nu. The results are interrogated and if any errors or warnings are returned, the test fails. If the site cannot be reached this is NOT treated as a test failure. Second, the actual (unsanitized) filenames are checked for validity: the HTML standard prohibits backslashes in URLs, even if the URL refers to a local file on a system that uses backslashes as a path separator (e.g. Windows). This would have been caught by the W3C Validator if we had not sanitized the filenames. --- src/Mod/Start/TestStart/TestStartPage.py | 100 ++++++++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/src/Mod/Start/TestStart/TestStartPage.py b/src/Mod/Start/TestStart/TestStartPage.py index 9e0740a658..7cc1da5761 100644 --- a/src/Mod/Start/TestStart/TestStartPage.py +++ b/src/Mod/Start/TestStart/TestStartPage.py @@ -24,7 +24,7 @@ import unittest import FreeCAD import Start from StartPage import StartPage -from html.parser import HTMLParser +import re class TestStartPage(unittest.TestCase): """Basic validation of the generated Start page.""" @@ -35,6 +35,7 @@ class TestStartPage(unittest.TestCase): def setUp(self): pass + def test_all_css_placeholders_removed(self): """Check to see if all of the CSS placeholders have been replaced.""" placeholders = ["BACKGROUND","BGTCOLOR","FONTFAMILY","FONTSIZE","LINKCOLOR", @@ -44,6 +45,7 @@ class TestStartPage(unittest.TestCase): for placeholder in placeholders: self.assertNotIn (placeholder, page, "{} was not removed from the CSS".format(placeholder)) + def test_all_js_placeholders_removed(self): """Check to see if all of the JavaScript placeholders have been replaced.""" placeholders = ["IMAGE_SRC_INSTALLED"] @@ -51,6 +53,7 @@ class TestStartPage(unittest.TestCase): for placeholder in placeholders: self.assertNotIn (placeholder, page, "{} was not removed from the JS".format(placeholder)) + def test_all_html_placeholders_removed(self): """Check to see if all of the HTML placeholders have been replaced.""" placeholders = ["T_TITLE","VERSIONSTRING","T_DOCUMENTS","T_HELP","T_ACTIVITY", @@ -69,4 +72,97 @@ class TestStartPage(unittest.TestCase): page = StartPage.handle() for placeholder in placeholders: self.assertNotIn (placeholder, page, "{} was not removed from the HTML".format(placeholder)) - \ No newline at end of file + + + def test_files_do_not_contain_backslashes(self): + # This would be caught by the W3C validator if we didn't sanitize the filenames before sending them. + page = StartPage.handle() + fileRE = re.compile(r'"file:///(.*?)"') + results = fileRE.findall(string=page) + + badFilenames = [] + for result in results: + if result.find("\\") != -1: + badFilenames.append(result) + + if len(badFilenames) > 0: + self.fail("The following filenames contain backslashes, which is prohibited in HTML: {}".format(badFilenames)) + + + def test_html_validates(self): + # Send the generated html to the W3C validator for analysis (removing potentially-sensitive data first) + import urllib.request + import os + import json + page = self.sanitize(StartPage.handle()) # Remove potentially sensitive data + + # For debugging, if you want to ensure that the sanitization worked correctly: + # from pathlib import Path + # home = str(Path.home()) + # f=open(home+"/test.html", "w") + # f.write(page) + # f.close() + + validation_url = "https://validator.w3.org/nu/?out=json" + data = page.encode('utf-8') # data should be bytes + req = urllib.request.Request(validation_url, data) + req.add_header("Content-type","text/html; charset=utf-8") + errorCount = 0 + warningCount = 0 + infoCount = 0 + validationResultString = "" + try: + with urllib.request.urlopen (req) as response: + text = response.read() + + responseJSON = json.loads(text) + + for message in responseJSON["messages"]: + if "type" in message: + if message["type"] == "info": + if "subtype" in message: + if message["subtype"] == "warning": + warningCount += 1 + validationResultString += "WARNING: {}\n".format(ascii(message["message"])) + else: + infoCount += 1 + validationResultString += "INFO: {}\n".format(ascii(message["message"])) + elif message["type"] == "error": + errorCount += 1 + validationResultString += "ERROR: {}\n".format(ascii(message["message"])) + elif message["type"] == "non-document-error": + FreeCAD.Console.PrintWarning("W3C validator returned a non-document error:\n {}".format(message)) + return + + except urllib.error.HTTPError as e: + FreeCAD.Console.PrintWarning("W3C validator returned response code {}".format(e.code)) + + except urllib.error.URLError: + FreeCAD.Console.PrintWarning("Could not communicate with W3C validator") + + if errorCount > 0 or warningCount > 0: + StartPage.exportTestFile() + FreeCAD.Console.PrintWarning("HTML validation failed: Start page source written to your home directory for analysis.") + self.fail("W3C Validator analysis shows the Start page has {} errors and {} warnings:\n\n{}".format(errorCount, warningCount, validationResultString)) + elif infoCount > 0: + FreeCAD.Console.PrintWarning("The Start page is valid HTML, but the W3C sent back {} informative messages:\n{}.".format(infoCount,validationResultString)) + + def sanitize (self, html): + + # Anonymize all local filenames + fileRE = re.compile(r'"file:///.*?"') + html = fileRE.sub(repl=r'"file:///A/B/C"', string=html) + + # Anonymize titles, which are used for mouseover text and might contain document information + titleRE = re.compile(r'title="[\s\S]*?"') # Some titles have newlines in them + html = titleRE.sub(repl=r'title="Y"', string=html) + + # Anonymize the document names, which we display in

      tags + h4RE = re.compile(r'

      .*?

      ') + html = h4RE.sub(repl=r'

      Z

      ', string=html) + + # Remove any simple single-line paragraphs, which might contain document author information, file size information, etc. + pRE = re.compile(r'

      [^<]*?

      ') + html = pRE.sub(repl=r'

      X

      ', string=html) + + return html From 1958e30f84fdd0890e6dd5f978ffadac9530d1b2 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 31 Jan 2021 22:13:47 -0600 Subject: [PATCH 111/168] Modify Start HTML to be valid HTML Using the W3C validator, a number of minor issues were found with the Start page HTML. Those items were: * Missing language setting on the body of the document * Deprecated attributes set for the style and script tags * Image tags require alt text * List tags can only contain list items * All file:/// URLs must not contain backslashes, even on Windows --- src/Mod/Start/StartPage/StartPage.html | 17 +++++----- src/Mod/Start/StartPage/StartPage.py | 44 ++++++++++++++------------ 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/Mod/Start/StartPage/StartPage.html b/src/Mod/Start/StartPage/StartPage.html index 36a3bf2aa0..9227550771 100644 --- a/src/Mod/Start/StartPage/StartPage.html +++ b/src/Mod/Start/StartPage/StartPage.html @@ -1,9 +1,10 @@ - + + T_TITLE - - + + @@ -12,7 +13,7 @@
      VERSIONSTRING - + T_VTOOLTIP
      @@ -57,25 +58,25 @@

      T_GENERALDOCUMENTATION

      - + T_USERHUB T_USERHUB

      T_DESCR_USERHUB

      - + T_POWERHUB T_POWERHUB

      T_DESCR_POWERHUB

      - + T_DEVHUB T_DEVHUB

      T_DESCR_DEVHUB

      - + T_MANUAL T_MANUAL

      T_DESCR_MANUAL

      diff --git a/src/Mod/Start/StartPage/StartPage.py b/src/Mod/Start/StartPage/StartPage.py index a19c46949c..20589363bf 100644 --- a/src/Mod/Start/StartPage/StartPage.py +++ b/src/Mod/Start/StartPage/StartPage.py @@ -148,19 +148,19 @@ def getInfo(filename): if files[0] == "Document.xml": doc = str(zfile.read(files[0])) doc = doc.replace("\n"," ") - r = re.findall("Property name=\"CreatedBy.*?String value=\"(.*?)\"\/>",doc) + r = re.findall("Property name=\"CreatedBy.*?String value=\"(.*?)\"/>",doc) if r: author = r[0] # remove email if present in author field if "<" in author: author = author.split("<")[0].strip() - r = re.findall("Property name=\"Company.*?String value=\"(.*?)\"\/>",doc) + r = re.findall("Property name=\"Company.*?String value=\"(.*?)\"/>",doc) if r: company = r[0] - r = re.findall("Property name=\"License.*?String value=\"(.*?)\"\/>",doc) + r = re.findall("Property name=\"License.*?String value=\"(.*?)\"/>",doc) if r: lic = r[0] - r = re.findall("Property name=\"Comment.*?String value=\"(.*?)\"\/>",doc) + r = re.findall("Property name=\"Comment.*?String value=\"(.*?)\"/>",doc) if r: descr = r[0] if "thumbnails/Thumbnail.png" in files: @@ -247,16 +247,16 @@ def buildCard(filename,method,arg=None): if finfo[5]: infostring += "\n\n" + encode(finfo[5]) if size: - result += '' result += '
    • ' - result += '' + result += '' + result += ''+encode(basename)+'' result += '
      ' result += '

      '+encode(basename)+'

      ' result += '

      '+encode(author)+'

      ' result += '

      '+size+'

      ' result += '
      ' - result += '
    • ' result += '' + result += '' return result @@ -298,6 +298,10 @@ def handle(): HTML = HTML.replace("CSS",CSS) HTML = encode(HTML) + # set the language + + HTML = HTML.replace("BCP47_LANGUAGE",QtCore.QLocale().bcp47Name()) + # get the stylesheet if we are using one if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetBool("UseStyleSheet",False): @@ -370,17 +374,17 @@ def handle(): rfcount = rf.GetInt("RecentFiles",0) SECTION_RECENTFILES = encode("

      "+TranslationTexts.T_RECENTFILES+"

      ") SECTION_RECENTFILES += "
        " - SECTION_RECENTFILES += '' SECTION_RECENTFILES += '
      • ' + SECTION_RECENTFILES += '' if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetBool("NewFileGradient",False): - SECTION_RECENTFILES += '' + SECTION_RECENTFILES += ''+encode(TranslationTexts.T_CREATENEW)+'' else: - SECTION_RECENTFILES += '' + SECTION_RECENTFILES += ''+encode(TranslationTexts.T_CREATENEW)+'' SECTION_RECENTFILES += '
        ' SECTION_RECENTFILES += '

        '+encode(TranslationTexts.T_CREATENEW)+'

        ' SECTION_RECENTFILES += '
        ' - SECTION_RECENTFILES += '
      • ' SECTION_RECENTFILES += '' + SECTION_RECENTFILES += '' for i in range(rfcount): filename = rf.GetString("MRU%d" % (i)) SECTION_RECENTFILES += encode(buildCard(filename,method="LoadMRU.py?MRU=",arg=str(i))) @@ -424,14 +428,12 @@ def handle(): # build IMAGE_SRC paths - HTML = HTML.replace("IMAGE_SRC_USERHUB",'file:///'+os.path.join(resources_dir, 'images/userhub.png')) - HTML = HTML.replace("IMAGE_SRC_POWERHUB",'file:///'+os.path.join(resources_dir, 'images/poweruserhub.png')) - HTML = HTML.replace("IMAGE_SRC_DEVHUB",'file:///'+os.path.join(resources_dir, 'images/developerhub.png')) - HTML = HTML.replace("IMAGE_SRC_MANUAL",'file:///'+os.path.join(resources_dir, 'images/manual.png')) - HTML = HTML.replace("IMAGE_SRC_SETTINGS",'file:///'+os.path.join(resources_dir, 'images/settings.png')) - imagepath= 'file:///'+os.path.join(resources_dir, 'images/installed.png') - imagepath = imagepath.replace('\\','/') # replace Windows backslash with slash to make the path javascript compatible - HTML = HTML.replace("IMAGE_SRC_INSTALLED",imagepath) + HTML = HTML.replace("IMAGE_SRC_USERHUB",'file:///'+os.path.join(resources_dir, 'images/userhub.png').replace('\\','/')) + HTML = HTML.replace("IMAGE_SRC_POWERHUB",'file:///'+os.path.join(resources_dir, 'images/poweruserhub.png').replace('\\','/')) + HTML = HTML.replace("IMAGE_SRC_DEVHUB",'file:///'+os.path.join(resources_dir, 'images/developerhub.png').replace('\\','/')) + HTML = HTML.replace("IMAGE_SRC_MANUAL",'file:///'+os.path.join(resources_dir, 'images/manual.png').replace('\\','/')) + HTML = HTML.replace("IMAGE_SRC_SETTINGS",'file:///'+os.path.join(resources_dir, 'images/settings.png').replace('\\','/')) + HTML = HTML.replace("IMAGE_SRC_INSTALLED",'file:///'+os.path.join(resources_dir, 'images/installed.png').replace('\\','/')) # build UL_WORKBENCHES @@ -481,7 +483,7 @@ def handle(): xpm = w.Icon if "XPM" in xpm: xpm = xpm.replace("\n ","\n") # some XPMs have some indent that QT doesn't like - r = [s[:-1].strip('"') for s in re.findall("(?s)\{(.*?)\};",xpm)[0].split("\n")[1:]] + r = [s[:-1].strip('"') for s in re.findall("(?s){(.*?)};",xpm)[0].split("\n")[1:]] p = QtGui.QPixmap(r) p = p.scaled(24,24) img = tempfile.mkstemp(dir=tempfolder,suffix='.png')[1] @@ -492,7 +494,7 @@ def handle(): img = os.path.join(resources_dir,"images/freecad.png") iconbank[wb] = img UL_WORKBENCHES += '
      • ' - UL_WORKBENCHES += ' ' + UL_WORKBENCHES += ''+wn+' ' UL_WORKBENCHES += ''+wn.replace("ReverseEngineering","ReverseEng")+'' UL_WORKBENCHES += '
      • ' UL_WORKBENCHES += '
      ' From 5e07d708db9c17b86ab26b5955e7c819803e77b3 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Mon, 1 Feb 2021 00:26:18 -0600 Subject: [PATCH 112/168] Remove extraneous lines from export Corrects a merge issue from an earlier PR: the removed lines are redundant. --- src/Gui/Application.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 299c61906c..6fd86ecd84 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -773,9 +773,6 @@ void Application::exportTo(const char* FileName, const char* DocName, const char std::string code = str.str(); // the original file name is required Gui::Command::runCommand(Gui::Command::App, code.c_str()); - // search for a module that is able to open the exported file because otherwise - // it doesn't need to be added to the recent files list (#0002047) - std::map importMap = App::GetApplication().getImportFilters(te.c_str()); auto parameterGroup = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General"); bool addToRecent = parameterGroup->GetBool("RecentIncludesExported", false); From 612eba1b1a650d62069e627ee6c7daefecc953b2 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Mon, 1 Feb 2021 19:40:33 -0600 Subject: [PATCH 113/168] Fix Surface task panel shortcut use to use Widget context The Delete key shortcut of the three different panels in the Surface Workbench TaskFilling sidebar conflicted between the panels if multiple were showing. This is resolved by making the QAction's context the widget, rather than the window. --- src/Mod/Surface/Gui/TaskFilling.cpp | 1 + src/Mod/Surface/Gui/TaskFillingEdge.cpp | 1 + src/Mod/Surface/Gui/TaskFillingVertex.cpp | 1 + 3 files changed, 3 insertions(+) diff --git a/src/Mod/Surface/Gui/TaskFilling.cpp b/src/Mod/Surface/Gui/TaskFilling.cpp index e9ec391cef..30b7f00a8b 100644 --- a/src/Mod/Surface/Gui/TaskFilling.cpp +++ b/src/Mod/Surface/Gui/TaskFilling.cpp @@ -272,6 +272,7 @@ FillingPanel::FillingPanel(ViewProviderFilling* vp, Surface::Filling* obj) // Create context menu QAction* action = new QAction(tr("Remove"), this); action->setShortcut(QString::fromLatin1("Del")); + action->setShortcutContext(Qt::WidgetShortcut); ui->listBoundary->addAction(action); connect(action, SIGNAL(triggered()), this, SLOT(onDeleteEdge())); ui->listBoundary->setContextMenuPolicy(Qt::ActionsContextMenu); diff --git a/src/Mod/Surface/Gui/TaskFillingEdge.cpp b/src/Mod/Surface/Gui/TaskFillingEdge.cpp index 7ea642fbf4..f0bb1f71d2 100644 --- a/src/Mod/Surface/Gui/TaskFillingEdge.cpp +++ b/src/Mod/Surface/Gui/TaskFillingEdge.cpp @@ -128,6 +128,7 @@ FillingEdgePanel::FillingEdgePanel(ViewProviderFilling* vp, Surface::Filling* ob // Create context menu QAction* action = new QAction(tr("Remove"), this); action->setShortcut(QString::fromLatin1("Del")); + action->setShortcutContext(Qt::WidgetShortcut); ui->listUnbound->addAction(action); connect(action, SIGNAL(triggered()), this, SLOT(onDeleteUnboundEdge())); ui->listUnbound->setContextMenuPolicy(Qt::ActionsContextMenu); diff --git a/src/Mod/Surface/Gui/TaskFillingVertex.cpp b/src/Mod/Surface/Gui/TaskFillingVertex.cpp index b39d0fd334..4bfa3a6be6 100644 --- a/src/Mod/Surface/Gui/TaskFillingVertex.cpp +++ b/src/Mod/Surface/Gui/TaskFillingVertex.cpp @@ -127,6 +127,7 @@ FillingVertexPanel::FillingVertexPanel(ViewProviderFilling* vp, Surface::Filling // Create context menu QAction* action = new QAction(tr("Remove"), this); action->setShortcut(QString::fromLatin1("Del")); + action->setShortcutContext(Qt::WidgetShortcut); ui->listFreeVertex->addAction(action); connect(action, SIGNAL(triggered()), this, SLOT(onDeleteVertex())); ui->listFreeVertex->setContextMenuPolicy(Qt::ActionsContextMenu); From 078160ea70cf66f7e1c9063f16ad70d9f6047b79 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Tue, 2 Feb 2021 17:49:44 +0100 Subject: [PATCH 114/168] TechDraw: Exposed GeomHatch functionality to Python in TechDraw.makeGeomHatch() --- src/Mod/TechDraw/App/AppTechDrawPy.cpp | 102 +++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 5 deletions(-) diff --git a/src/Mod/TechDraw/App/AppTechDrawPy.cpp b/src/Mod/TechDraw/App/AppTechDrawPy.cpp index 7afe7011f9..ed07a2a5fe 100644 --- a/src/Mod/TechDraw/App/AppTechDrawPy.cpp +++ b/src/Mod/TechDraw/App/AppTechDrawPy.cpp @@ -29,8 +29,10 @@ #include #include #include +#include #include #include +#include #include #endif @@ -51,7 +53,9 @@ #include #include #include +#include #include +#include #include #include @@ -71,6 +75,8 @@ #include "DrawProjGroup.h" #include "DrawProjGroupItem.h" #include "DrawDimHelper.h" +#include "HatchLine.h" +#include "DrawGeomHatch.h" namespace TechDraw { //module level static C++ functions go here @@ -79,7 +85,9 @@ namespace TechDraw { using Part::TopoShape; using Part::TopoShapePy; using Part::TopoShapeEdgePy; +using Part::TopoShapeFacePy; using Part::TopoShapeWirePy; +using Part::TopoShapeCompoundPy; using Import::ImpExpDxfWrite; namespace TechDraw { @@ -122,7 +130,9 @@ public: add_varargs_method("makeDistanceDim3d",&Module::makeDistanceDim3d, "makeDistanceDim(DrawViewPart, dimType, 3dFromPoint, 3dToPoint) -- draw a Length dimension between fromPoint to toPoint. FromPoint and toPoint are unscaled 3d model points. dimType is one of ['Distance', 'DistanceX', 'DistanceY'." ); - + add_varargs_method("makeGeomHatch",&Module::makeGeomHatch, + "makeGeomHatch(face, [patScale], [patName], [patFile]) -- draw a geom hatch on a given face, using optionally the given scale (default 1) and a given pattern name (ex. Diamond) and .pat file (the default pattern name and/or .pat files set in preferences are used if none are given). Returns a Part compound shape." + ); initialize("This is a module for making drawings"); // register with Python } virtual ~Module() {} @@ -797,8 +807,8 @@ private: Py::Object makeDistanceDim(const Py::Tuple& args) { - //points come in unscaled,but makeDistDim unscales them so we need to prescale here. - //makeDistDim was built for extent dims which work from scaled geometry + //points come in unscaled,but makeDistDim unscales them so we need to prescale here. + //makeDistDim was built for extent dims which work from scaled geometry PyObject* pDvp; PyObject* pDimType; PyObject* pFrom; @@ -835,7 +845,7 @@ private: if (PyObject_TypeCheck(pTo, &(Base::VectorPy::Type))) { to = static_cast(pTo)->value(); } - DrawViewDimension* dvd = + DrawViewDimension* dvd = DrawDimHelper::makeDistDim(dvp, dimType, DrawUtil::invertY(from), @@ -885,7 +895,7 @@ private: //3d points are not scaled from = DrawUtil::invertY(dvp->projectPoint(from)); to = DrawUtil::invertY(dvp->projectPoint(to)); - //DrawViewDimension* = + //DrawViewDimension* = DrawDimHelper::makeDistDim(dvp, dimType, from, @@ -893,6 +903,88 @@ private: return Py::None(); } + + + Py::Object makeGeomHatch(const Py::Tuple& args) + { + PyObject* pFace; + double scale = 1.0; + char* pPatName = ""; + char* pPatFile = ""; + TechDraw::DrawViewPart* source = nullptr; + TopoDS_Face face; + + if (!PyArg_ParseTuple(args.ptr(), "O|detet", &pFace, &scale, "utf-8", &pPatName, "utf-8", &pPatFile)) { + throw Py::TypeError("expected (face, [scale], [patName], [patFile])"); + } + std::string patName = std::string(pPatName); + std::string patFile = std::string(pPatFile); + if (PyObject_TypeCheck(pFace, &(TopoShapeFacePy::Type))) { + const TopoDS_Shape& sh = static_cast(pFace)->getTopoShapePtr()->getShape(); + face = TopoDS::Face(sh); + } + else { + throw Py::TypeError("first argument must be a Part.Face instance"); + } + if (patName.empty()) { + patName = TechDraw::DrawGeomHatch::prefGeomHatchName(); + } + if (patFile.empty()) { + patFile = TechDraw::DrawGeomHatch::prefGeomHatchFile(); + } + Base::FileInfo fi(patFile); + if (!fi.isReadable()) { + Base::Console().Error(".pat File: %s is not readable\n",patFile.c_str()); + return Py::None(); + } + std::vector specs = TechDraw::DrawGeomHatch::getDecodedSpecsFromFile(patFile, patName); + std::vector lineSets; + for (auto& hl: specs) { + TechDraw::LineSet ls; + ls.setPATLineSpec(hl); + lineSets.push_back(ls); + } + std::vector lsresult = TechDraw::DrawGeomHatch::getTrimmedLines(source, lineSets, face, scale); + if (!lsresult.empty()) { + /* below code returns a list of edges, but probably slower to handle + Py::List result; + try { + for (auto& lsr:lsresult) { + std::vector edgeList = lsr.getEdges(); + for (auto& edge:edgeList) { + PyObject* pyedge = new TopoShapeEdgePy(new TopoShape(edge)); + result.append(Py::asObject(pyedge)); + } + } + } + catch (Base::Exception &e) { + throw Py::Exception(Base::BaseExceptionFreeCADError, e.what()); + } + return result; + */ + BRep_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + try { + for (auto& lsr:lsresult) { + std::vector edgeList = lsr.getEdges(); + for (auto& edge:edgeList) { + if (!edge.IsNull()) { + builder.Add(comp, edge); + } + } + } + } + catch (Base::Exception &e) { + throw Py::Exception(Base::BaseExceptionFreeCADError, e.what()); + } + PyObject* pycomp = new TopoShapeCompoundPy(new TopoShape(comp)); + return Py::asObject(pycomp); + } + return Py::None(); + } + + }; PyObject* initModule() From 7a8385353590709bb14c0e16c1f103e159113c97 Mon Sep 17 00:00:00 2001 From: wmayer Date: Tue, 2 Feb 2021 21:51:11 +0100 Subject: [PATCH 115/168] Part: offer all supported STEP schemes by OCC in a combo box --- src/Mod/Import/Gui/AppImportGuiPy.cpp | 9 ++--- src/Mod/Part/App/ImportStep.h | 10 ++++- src/Mod/Part/Gui/DlgImportExportStep.ui | 50 +++++++++++++++---------- src/Mod/Part/Gui/DlgSettingsGeneral.cpp | 34 ++++++++--------- 4 files changed, 60 insertions(+), 43 deletions(-) diff --git a/src/Mod/Import/Gui/AppImportGuiPy.cpp b/src/Mod/Import/Gui/AppImportGuiPy.cpp index d6054176fe..e029d2db61 100644 --- a/src/Mod/Import/Gui/AppImportGuiPy.cpp +++ b/src/Mod/Import/Gui/AppImportGuiPy.cpp @@ -642,11 +642,10 @@ private: Base::FileInfo file(Utf8Name.c_str()); if (file.hasExtension("stp") || file.hasExtension("step")) { ParameterGrp::handle hGrp_stp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Part/STEP"); - std::string scheme = hGrp_stp->GetASCII("Scheme", "AP214IS"); - if (scheme == "AP203") - Interface_Static::SetCVal("write.step.schema", "AP203"); - else if (scheme == "AP214IS") - Interface_Static::SetCVal("write.step.schema", "AP214IS"); + std::string scheme = hGrp_stp->GetASCII("Scheme", Interface_Static::CVal("write.step.schema")); + std::list supported = Part::supportedSTEPSchemes(); + if (std::find(supported.begin(), supported.end(), scheme) != supported.end()) + Interface_Static::SetCVal("write.step.schema", scheme.c_str()); STEPCAFControl_Writer writer; Interface_Static::SetIVal("write.step.assembly",1); diff --git a/src/Mod/Part/App/ImportStep.h b/src/Mod/Part/App/ImportStep.h index f3dd1bdf6c..4d736cf0c1 100644 --- a/src/Mod/Part/App/ImportStep.h +++ b/src/Mod/Part/App/ImportStep.h @@ -41,7 +41,15 @@ namespace Part */ PartExport int ImportStepParts(App::Document *pcDoc, const char* Name); - +inline std::list supportedSTEPSchemes() { + std::list schemes; + schemes.emplace_back("AP203"); + schemes.emplace_back("AP214CD"); + schemes.emplace_back("AP214DIS"); + schemes.emplace_back("AP214IS"); + schemes.emplace_back("AP242DIS"); + return schemes; +} } //namespace Part diff --git a/src/Mod/Part/Gui/DlgImportExportStep.ui b/src/Mod/Part/Gui/DlgImportExportStep.ui index 915713a7d2..da3d331cc7 100644 --- a/src/Mod/Part/Gui/DlgImportExportStep.ui +++ b/src/Mod/Part/Gui/DlgImportExportStep.ui @@ -7,7 +7,7 @@ 0 0 445 - 637 + 699 @@ -27,20 +27,32 @@ - - - AP 203 - - - true - - - - - - - AP 214 - + + + + AP203 + + + + + AP214 Committee Draft + + + + + AP214 Draft International Standard + + + + + AP214 International Standard + + + + + AP242 Draft International Standard + + @@ -113,12 +125,12 @@ Use legacy exporter - - Mod/Import - ExportLegacy + + Mod/Import +
      @@ -393,8 +405,6 @@ during file reading (slower but higher details). comboBoxUnits checkBoxPcurves checkBoxExportHiddenObj - radioButtonAP203 - radioButtonAP214 checkBoxMergeCompound checkBoxUseLinkGroup checkBoxImportHiddenObj diff --git a/src/Mod/Part/Gui/DlgSettingsGeneral.cpp b/src/Mod/Part/Gui/DlgSettingsGeneral.cpp index 783f954c2f..2dee4b9150 100644 --- a/src/Mod/Part/Gui/DlgSettingsGeneral.cpp +++ b/src/Mod/Part/Gui/DlgSettingsGeneral.cpp @@ -197,9 +197,16 @@ DlgImportExportStep::DlgImportExportStep(QWidget* parent) { ui = new Ui_DlgImportExportStep(); ui->setupUi(this); + + ui->comboBoxSchema->setItemData(0, QByteArray("AP203")); + ui->comboBoxSchema->setItemData(1, QByteArray("AP214CD")); + ui->comboBoxSchema->setItemData(2, QByteArray("AP214DIS")); + ui->comboBoxSchema->setItemData(3, QByteArray("AP214IS")); + ui->comboBoxSchema->setItemData(4, QByteArray("AP242DIS")); + ui->lineEditProduct->setReadOnly(true); - ui->radioButtonAP203->setToolTip(tr("Configuration controlled 3D designs of mechanical parts and assemblies")); - ui->radioButtonAP214->setToolTip(tr("Core data for automotive mechanical design processes")); + //ui->radioButtonAP203->setToolTip(tr("Configuration controlled 3D designs of mechanical parts and assemblies")); + //ui->radioButtonAP214->setToolTip(tr("Core data for automotive mechanical design processes")); // https://tracker.dev.opencascade.org/view.php?id=25654 ui->checkBoxPcurves->setToolTip(tr("This parameter indicates whether parametric curves (curves in parametric space of surface)\n" @@ -253,15 +260,10 @@ void DlgImportExportStep::saveSettings() } // scheme - if (ui->radioButtonAP203->isChecked()) { - Interface_Static::SetCVal("write.step.schema","AP203"); - hStepGrp->SetASCII("Scheme", "AP203"); - } - else { - // possible values: AP214CD (1996), AP214DIS (1998), AP214IS (2002) - Interface_Static::SetCVal("write.step.schema","AP214IS"); - hStepGrp->SetASCII("Scheme", "AP214IS"); - } + // possible values: AP214CD (1996), AP214DIS (1998), AP214IS (2002), AP242DIS + QByteArray schema = ui->comboBoxSchema->itemData(ui->comboBoxSchema->currentIndex()).toByteArray(); + Interface_Static::SetCVal("write.step.schema",schema); + hStepGrp->SetASCII("Scheme", schema); // header info hStepGrp->SetASCII("Company", ui->lineEditCompany->text().toLatin1()); @@ -299,12 +301,10 @@ void DlgImportExportStep::loadSettings() ui->comboBoxUnits->setCurrentIndex(unit); // scheme - QString ap = QString::fromStdString(hStepGrp->GetASCII("Scheme", - Interface_Static::CVal("write.step.schema"))); - if (ap.startsWith(QLatin1String("AP203"))) - ui->radioButtonAP203->setChecked(true); - else - ui->radioButtonAP214->setChecked(true); + QByteArray ap(hStepGrp->GetASCII("Scheme", Interface_Static::CVal("write.step.schema")).c_str()); + int index = ui->comboBoxSchema->findData(QVariant(ap)); + if (index >= 0) + ui->comboBoxSchema->setCurrentIndex(index); // header info ui->lineEditCompany->setText(QString::fromStdString(hStepGrp->GetASCII("Company"))); From 45eea3d9e7b01ebb7de9332cf3037f256785aeb6 Mon Sep 17 00:00:00 2001 From: luz paz Date: Wed, 3 Feb 2021 04:45:22 -0500 Subject: [PATCH 116/168] Fix various typos [skip ci] Found via codespell v2.1.dev0 ``` codespell -q 3 -L aci,ake,aline,alle,alledges,alocation,als,ang,anid,apoints,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,./build/doc/SourceDocu ``` --- src/Base/core-base.dox | 2 +- src/Mod/Path/Tools/README.md | 2 +- src/Mod/Sketcher/App/planegcs/Constraints.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Base/core-base.dox b/src/Base/core-base.dox index 05a78de886..eb38a35ceb 100644 --- a/src/Base/core-base.dox +++ b/src/Base/core-base.dox @@ -23,5 +23,5 @@ /** \defgroup GeomPrimers Geometry primers * \ingroup BASE - \brief Basic structures used by geomoetric objects + \brief Basic structures used by geometric objects */ diff --git a/src/Mod/Path/Tools/README.md b/src/Mod/Path/Tools/README.md index 8f46b421bc..e71218303b 100644 --- a/src/Mod/Path/Tools/README.md +++ b/src/Mod/Path/Tools/README.md @@ -75,7 +75,7 @@ solid is updated to the correct representation. * this creates a PropertyBag object inside the Body (assuming it was selected) * add properties to which define the tool bit's shape and put those into the group 'Shape' * add any other properties to the bag which might be useful for the tool bit -1. Construct the body of the tool bit and assign experssions referencing properties from the PropertyBag (in the +1. Construct the body of the tool bit and assign expressions referencing properties from the PropertyBag (in the `Shape` Group) for all constraints. * Position the tip of the tool bit on the origin (0,0) 1. Save the document as a new file in the Shape directory diff --git a/src/Mod/Sketcher/App/planegcs/Constraints.cpp b/src/Mod/Sketcher/App/planegcs/Constraints.cpp index 0829e09cde..9260cd2c83 100644 --- a/src/Mod/Sketcher/App/planegcs/Constraints.cpp +++ b/src/Mod/Sketcher/App/planegcs/Constraints.cpp @@ -2085,7 +2085,7 @@ void ConstraintEqualLineLength::errorgrad(double *err, double *grad, double *par // // So here we maintain the very small derivative of 1e-10 when the gradient is under such value, such // that the diagnose function with pivot threshold of 1e-13 treats the value as non-zero and correctly - // detects and can tell appart when a parameter is fully constrained or just locked into a maximum/minimum + // detects and can tell apart when a parameter is fully constrained or just locked into a maximum/minimum if(fabs(*grad) < 1e-10) { double surrogate = 1e-10; if( param == l1.p1.x ) From 400b25c0d020237c1cd990f0e2e79e4d30671fbb Mon Sep 17 00:00:00 2001 From: donovaly Date: Wed, 3 Feb 2021 03:49:56 +0100 Subject: [PATCH 117/168] [TD] fix color change for dimensions This PR fixes issue C reported here: https://forum.freecadweb.org/viewtopic.php?f=35&t=55008#p472939 - the changed color must also be applied to the dimension line and arrows --- src/Mod/TechDraw/Gui/QGIPrimPath.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Mod/TechDraw/Gui/QGIPrimPath.cpp b/src/Mod/TechDraw/Gui/QGIPrimPath.cpp index 3355a13a0f..59933a6a93 100644 --- a/src/Mod/TechDraw/Gui/QGIPrimPath.cpp +++ b/src/Mod/TechDraw/Gui/QGIPrimPath.cpp @@ -234,6 +234,7 @@ void QGIPrimPath::setNormalColor(QColor c) { m_colNormal = c; m_colOverride = true; + m_colCurrent = m_colNormal; } void QGIPrimPath::setCapStyle(Qt::PenCapStyle c) @@ -316,6 +317,7 @@ void QGIPrimPath::resetFill() { void QGIPrimPath::setFillColor(QColor c) { m_colNormalFill = c; + m_fillColorCurrent = m_colNormalFill; // m_colDefFill = c; } From cc3a30a221e0bb46c9e4325a659cd5aa26ced977 Mon Sep 17 00:00:00 2001 From: donovaly Date: Wed, 3 Feb 2021 01:06:44 +0100 Subject: [PATCH 118/168] [PD] fix typo in iso7046.json This mistake was introduced by me in commit 4ab3c955 --- src/Mod/PartDesign/Resources/Hole/iso7046.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/PartDesign/Resources/Hole/iso7046.json b/src/Mod/PartDesign/Resources/Hole/iso7046.json index 2985b7ba9d..ec227e1893 100644 --- a/src/Mod/PartDesign/Resources/Hole/iso7046.json +++ b/src/Mod/PartDesign/Resources/Hole/iso7046.json @@ -4,7 +4,7 @@ "thread_type": "metric", "angle": 90, "data": [ - { "thread": "M1.6", "diameter": 3.6 } + { "thread": "M1.6", "diameter": 3.6 }, { "thread": "M2", "diameter": 4.4 }, { "thread": "M2.5", "diameter": 5.5 }, { "thread": "M3", "diameter": 6.3 }, From 3ebd7d119b6192a2d2774bc8402e459b791df772 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 3 Feb 2021 16:10:38 +0100 Subject: [PATCH 119/168] Part: [skip ci] rename methods in TopoShape that override non-virtual functions of base class --- src/Mod/Part/App/TopoShape.cpp | 10 ++++++++-- src/Mod/Part/App/TopoShape.h | 6 +++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Mod/Part/App/TopoShape.cpp b/src/Mod/Part/App/TopoShape.cpp index 847e0207e0..7ce4568f34 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -704,7 +704,10 @@ Base::Matrix4D TopoShape::getTransform(void) const return mtrx; } -void TopoShape::setPlacement(const Base::Placement& rclTrf) +/*! + * \obsolete + */ +void TopoShape::setShapePlacement(const Base::Placement& rclTrf) { const Base::Vector3d& pos = rclTrf.getPosition(); Base::Vector3d axis; @@ -718,7 +721,10 @@ void TopoShape::setPlacement(const Base::Placement& rclTrf) _Shape.Location(loc); } -Base::Placement TopoShape::getPlacemet(void) const +/*! + * \obsolete + */ +Base::Placement TopoShape::getShapePlacement(void) const { TopLoc_Location loc = _Shape.Location(); gp_Trsf trsf = loc.Transformation(); diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 2ef6027191..4fd7a5b623 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -110,11 +110,11 @@ public: /// set the transformation of the CasCade Shape void setTransform(const Base::Matrix4D& rclTrf); /// set the transformation of the CasCade Shape - void setPlacement(const Base::Placement& rclTrf); + void setShapePlacement(const Base::Placement& rclTrf); + /// get the transformation of the CasCade Shape + Base::Placement getShapePlacement(void) const; /// get the transformation of the CasCade Shape Base::Matrix4D getTransform(void) const; - /// get the transformation of the CasCade Shape - Base::Placement getPlacemet(void) const; /// Bound box from the CasCade shape Base::BoundBox3d getBoundBox(void)const; virtual bool getCenterOfGravity(Base::Vector3d& center) const; From 7eb9b5ed422ea4e3c6d117d544434655872aaa92 Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 31 Jan 2021 21:58:22 +0100 Subject: [PATCH 120/168] [PD] hole dialog UI fixes This PR is the first in a series of probably 3 PRs to fix known hole dialog bugs. This one fixes: - the .ui file issues - readonly status issues of some widgets/properties (e.g. when the hole is through all, disable drill point settings) - the bug that updateHoleCutParams() overwrote the previously correctly determined hole diameter - just a trifle: change a function name to fit into the naming scheme --- src/Mod/PartDesign/App/FeatureHole.cpp | 79 ++++--- src/Mod/PartDesign/Gui/TaskHoleParameters.cpp | 43 +++- src/Mod/PartDesign/Gui/TaskHoleParameters.h | 2 +- src/Mod/PartDesign/Gui/TaskHoleParameters.ui | 200 +++++++++++------- 4 files changed, 218 insertions(+), 106 deletions(-) diff --git a/src/Mod/PartDesign/App/FeatureHole.cpp b/src/Mod/PartDesign/App/FeatureHole.cpp index 444005fdee..9c014d291f 100644 --- a/src/Mod/PartDesign/App/FeatureHole.cpp +++ b/src/Mod/PartDesign/App/FeatureHole.cpp @@ -424,7 +424,9 @@ const double Hole::metricHoleDiameters[36][4] = { 6.0, 6.4, 6.6, 7.0}, { 7.0, 7.4, 7.6, 8.0}, { 8.0, 8.4, 9.0, 10.0}, + // 9.0 undefined { 10.0, 10.5, 11.0, 12.0}, + // 11.0 undefined { 12.0, 13.0, 13.5, 14.5}, { 14.0, 15.0, 15.5, 16.5}, { 16.0, 17.0, 17.5, 18.5}, @@ -612,6 +614,10 @@ void Hole::updateHoleCutParams() return; } + // get diameter and size + double diameterVal = Diameter.getValue(); + + // handle thread types std::string threadType = ThreadType.getValueAsString(); if (threadType == "ISOMetricProfile" || threadType == "ISOMetricFineProfile") { if (ThreadSize.getValue() < 0) { @@ -619,35 +625,50 @@ void Hole::updateHoleCutParams() return; } - // get diameter and size - double diameter = threadDescription[ThreadType.getValue()][ThreadSize.getValue()].diameter; std::string threadSize{ ThreadSize.getValueAsString() }; // we don't update for these settings but we need to set a value for new holes + // furthermore we must assure the hole cut diameter is not <= the hole diameter // if we have a cut but the values are zero, we assume it is a new hole // we take in this case the values from the norm ISO 4762 or ISO 10642 if (holeCutType == "Counterbore") { // read ISO 4762 values const CutDimensionSet& counter = find_cutDimensionSet(threadType, "ISO 4762"); const CounterBoreDimension& dimen = counter.get_bore(threadSize); - if (HoleCutDiameter.getValue() == 0.0) { - HoleCutDiameter.setValue(dimen.diameter); - HoleCutDepth.setValue(dimen.depth); + if (HoleCutDiameter.getValue() == 0.0 || HoleCutDiameter.getValue() <= diameterVal) { + // there is no norm defining counterbores for all sizes, thus we need to use the + // same fallback as for the case HoleCutTypeMap.count(key) + if (dimen.diameter != 0.0) { + HoleCutDiameter.setValue(dimen.diameter); + HoleCutDepth.setValue(dimen.depth); + } + else { + // valid values for visual feedback + HoleCutDiameter.setValue(Diameter.getValue() + 0.1); + HoleCutDepth.setValue(0.1); + } } if (HoleCutDepth.getValue() == 0.0) HoleCutDepth.setValue(dimen.depth); + HoleCutCountersinkAngle.setReadOnly(true); } else if (holeCutType == "Countersink") { // read ISO 10642 values const CutDimensionSet& counter = find_cutDimensionSet(threadType, "ISO 10642"); - if (HoleCutDiameter.getValue() == 0.0) { + if (HoleCutDiameter.getValue() == 0.0 || HoleCutDiameter.getValue() <= diameterVal) { const CounterSinkDimension& dimen = counter.get_sink(threadSize); - HoleCutDiameter.setValue(dimen.diameter); + if (dimen.diameter != 0.0) { + HoleCutDiameter.setValue(dimen.diameter); + } + else { + HoleCutDiameter.setValue(Diameter.getValue() + 0.1); + } HoleCutCountersinkAngle.setValue(counter.angle); } if (HoleCutCountersinkAngle.getValue() == 0.0) { HoleCutCountersinkAngle.setValue(counter.angle); } + HoleCutCountersinkAngle.setReadOnly(false); } // cut definition @@ -688,39 +709,38 @@ void Hole::updateHoleCutParams() // handle legacy types but don’t change user settings for // user defined None, Counterbore and Countersink else if (holeCutType == "Cheesehead (deprecated)") { - HoleCutDiameter.setValue(diameter * 1.6); - HoleCutDepth.setValue(diameter * 0.6); + HoleCutDiameter.setValue(diameterVal * 1.6); + HoleCutDepth.setValue(diameterVal * 0.6); } else if (holeCutType == "Countersink socket screw (deprecated)") { - HoleCutDiameter.setValue(diameter * 2.0); - HoleCutDepth.setValue(diameter * 0.0); + HoleCutDiameter.setValue(diameterVal * 2.0); + HoleCutDepth.setValue(diameterVal * 0.0); if (HoleCutCountersinkAngle.getValue() == 0.0) { HoleCutCountersinkAngle.setValue(90.0); } } else if (holeCutType == "Cap screw (deprecated)") { - HoleCutDiameter.setValue(diameter * 1.5); - HoleCutDepth.setValue(diameter * 1.25); + HoleCutDiameter.setValue(diameterVal * 1.5); + HoleCutDepth.setValue(diameterVal * 1.25); } } else { // we have an UTS profile or none - // get diameter - double diameter = threadDescription[ThreadType.getValue()][ThreadSize.getValue()].diameter; // we don't update for these settings but we need to set a value for new holes + // furthermore we must assure the hole cut diameter is not <= the hole diameter // if we have a cut but the values are zero, we assume it is a new hole // we use rules of thumbs as proposal if (holeCutType == "Counterbore") { - if (HoleCutDiameter.getValue() == 0.0) { - HoleCutDiameter.setValue(diameter * 1.6); - HoleCutDepth.setValue(diameter * 0.9); + if (HoleCutDiameter.getValue() == 0.0 || HoleCutDiameter.getValue() <= diameterVal) { + HoleCutDiameter.setValue(diameterVal * 1.6); + HoleCutDepth.setValue(diameterVal * 0.9); } if (HoleCutDepth.getValue() == 0.0) - HoleCutDepth.setValue(diameter * 0.9); + HoleCutDepth.setValue(diameterVal * 0.9); } else if (holeCutType == "Countersink") { - if (HoleCutDiameter.getValue() == 0.0) { - HoleCutDiameter.setValue(diameter * 1.7); + if (HoleCutDiameter.getValue() == 0.0 || HoleCutDiameter.getValue() <= diameterVal) { + HoleCutDiameter.setValue(diameterVal * 1.7); // 82 degrees for UTS, 90 otherwise if (threadType != "None") HoleCutCountersinkAngle.setValue(82.0); @@ -996,10 +1016,14 @@ void Hole::onChanged(const App::Property *prop) ThreadCutOffOuter.setReadOnly(v); } else if (prop == &DrillPoint) { - if (DrillPoint.getValue() == 1) + if (DrillPoint.getValue() == 1) { DrillPointAngle.setReadOnly(false); - else + DrillForDepth.setReadOnly(false); + } + else { DrillPointAngle.setReadOnly(true); + DrillForDepth.setReadOnly(true); + } } else if (prop == &Tapered) { if (Tapered.getValue()) @@ -1009,11 +1033,16 @@ void Hole::onChanged(const App::Property *prop) } else if (prop == &ThreadSize) { updateDiameterParam(); - updateHoleCutParams(); + // updateHoleCutParams() will later automatically be called because updateDiameterParam() changes &Diameter } else if (prop == &ThreadFit) { updateDiameterParam(); } + else if (prop == &Diameter) { + // a changed diameter means we also need to check the hole cut + // because the hole cut diameter must not be <= than the diameter + updateHoleCutParams(); + } else if (prop == &HoleCutType) { std::string holeCutType; if (HoleCutType.isValid()) @@ -1045,6 +1074,8 @@ void Hole::onChanged(const App::Property *prop) } else if (prop == &DepthType) { Depth.setReadOnly((std::string(DepthType.getValueAsString()) != "Dimension")); + DrillPoint.setReadOnly((std::string(DepthType.getValueAsString()) != "Dimension")); + DrillPointAngle.setReadOnly((std::string(DepthType.getValueAsString()) != "Dimension")); DrillForDepth.setReadOnly((std::string(DepthType.getValueAsString()) != "Dimension")); } ProfileBased::onChanged(prop); diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp b/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp index 0e9878f8ba..8e281f7dcf 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp @@ -152,11 +152,28 @@ TaskHoleParameters::TaskHoleParameters(ViewProviderHole *HoleView, QWidget *pare ui->drillPointAngled->setChecked(true); ui->DrillPointAngle->setValue(pcHole->DrillPointAngle.getValue()); ui->DrillForDepth->setChecked(pcHole->DrillForDepth.getValue()); - // DrillForDepth is only enabled (sensible) if type is 'Dimension' - if (std::string(pcHole->DepthType.getValueAsString()) == "Dimension") + // drill point settings are only enabled (sensible) if type is 'Dimension' + if (std::string(pcHole->DepthType.getValueAsString()) == "Dimension") { + ui->drillPointFlat->setEnabled(true); + ui->drillPointAngled->setEnabled(true); + ui->DrillPointAngle->setEnabled(true); ui->DrillForDepth->setEnabled(true); - else + } + else { + ui->drillPointFlat->setEnabled(false); + ui->drillPointAngled->setEnabled(false); + ui->DrillPointAngle->setEnabled(false); ui->DrillForDepth->setEnabled(false); + } + // drill point is sensible but flat, disable angle and option + if (!ui->drillPointFlat->isChecked()) { + ui->DrillPointAngle->setEnabled(true); + ui->DrillForDepth->setEnabled(true); + } + else { + ui->DrillPointAngle->setEnabled(false); + ui->DrillForDepth->setEnabled(false); + } ui->Tapered->setChecked(pcHole->Tapered.getValue()); // Angle is only enabled (sensible) if tapered ui->TaperedAngle->setEnabled(pcHole->Tapered.getValue()); @@ -171,7 +188,7 @@ TaskHoleParameters::TaskHoleParameters(ViewProviderHole *HoleView, QWidget *pare connect(ui->Diameter, SIGNAL(valueChanged(double)), this, SLOT(threadDiameterChanged(double))); connect(ui->directionRightHand, SIGNAL(clicked(bool)), this, SLOT(threadDirectionChanged())); connect(ui->directionLeftHand, SIGNAL(clicked(bool)), this, SLOT(threadDirectionChanged())); - connect(ui->HoleCutType, SIGNAL(currentIndexChanged(int)), this, SLOT(holeCutChanged(int))); + connect(ui->HoleCutType, SIGNAL(currentIndexChanged(int)), this, SLOT(holeCutTypeChanged(int))); connect(ui->HoleCutDiameter, SIGNAL(valueChanged(double)), this, SLOT(holeCutDiameterChanged(double))); connect(ui->HoleCutDepth, SIGNAL(valueChanged(double)), this, SLOT(holeCutDepthChanged(double))); connect(ui->HoleCutCountersinkAngle, SIGNAL(valueChanged(double)), this, SLOT(holeCutCountersinkAngleChanged(double))); @@ -254,7 +271,7 @@ void TaskHoleParameters::threadCutOffOuterChanged(double value) recomputeFeature(); } -void TaskHoleParameters::holeCutChanged(int index) +void TaskHoleParameters::holeCutTypeChanged(int index) { if (index < 0) return; @@ -306,7 +323,7 @@ void TaskHoleParameters::holeCutCountersinkAngleChanged(double value) { PartDesign::Hole* pcHole = static_cast(vp->getObject()); - pcHole->HoleCutCountersinkAngle.setValue((double)value); + pcHole->HoleCutCountersinkAngle.setValue(value); recomputeFeature(); } @@ -316,11 +333,19 @@ void TaskHoleParameters::depthChanged(int index) pcHole->DepthType.setValue(index); - // disable DrillforDepth if not 'Dimension' - if (std::string(pcHole->DepthType.getValueAsString()) == "Dimension") + // disable drill point widgets if not 'Dimension' + if (std::string(pcHole->DepthType.getValueAsString()) == "Dimension") { + ui->drillPointFlat->setEnabled(true); + ui->drillPointAngled->setEnabled(true); + ui->DrillPointAngle->setEnabled(true); ui->DrillForDepth->setEnabled(true); - else + } + else { + ui->drillPointFlat->setEnabled(false); + ui->drillPointAngled->setEnabled(false); + ui->DrillPointAngle->setEnabled(false); ui->DrillForDepth->setEnabled(false); + } recomputeFeature(); } diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.h b/src/Mod/PartDesign/Gui/TaskHoleParameters.h index bfa22a693c..894c4314a6 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.h +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.h @@ -93,7 +93,7 @@ private Q_SLOTS: void threadAngleChanged(double value); void threadDiameterChanged(double value); void threadDirectionChanged(); - void holeCutChanged(int index); + void holeCutTypeChanged(int index); void holeCutDiameterChanged(double value); void holeCutDepthChanged(double value); void holeCutCountersinkAngleChanged(double value); diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui index 37becccf82..27a6b7ac60 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui @@ -6,7 +6,7 @@ 0 0 - 354 + 335 463 @@ -40,10 +40,10 @@
      - + - + 0 0 @@ -58,6 +58,12 @@ + + + 0 + 0 + + Whether the hole gets a thread @@ -84,6 +90,12 @@ + + + 0 + 0 + + @@ -97,6 +109,12 @@ + + + 0 + 0 + + Left hand @@ -121,37 +139,43 @@ - + 0 0 - 140 + 16777215 16777215 - + + + + 0 + 0 + + Clearance - + - + 0 0 - 110 + 16777215 16777215 @@ -192,14 +216,14 @@ Only available for holes without thread - + 0 0 - 140 + 16777215 16777215 @@ -208,23 +232,7 @@ Only available for holes without thread - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 13 - 20 - - - - - + @@ -237,17 +245,17 @@ Only available for holes without thread - + - + 0 0 - 110 + 16777215 16777215 @@ -278,14 +286,14 @@ Only available for holes without thread - + 0 0 - 140 + 16777215 16777215 @@ -301,10 +309,10 @@ Only available for holes without thread - + - + 0 0 @@ -334,10 +342,10 @@ Only available for holes without thread - + - + 0 0 @@ -355,15 +363,21 @@ Only available for holes without thread + + + 0 + 0 + + Diameter - + - + 0 0 @@ -390,15 +404,21 @@ Only available for holes without thread + + + 0 + 0 + + Depth - + - + 0 0 @@ -422,15 +442,21 @@ Only available for holes without thread + + + 0 + 0 + + Countersink angle - + - + 0 0 @@ -467,6 +493,12 @@ Only available for holes without thread + + + 0 + 0 + + Type @@ -478,7 +510,7 @@ Only available for holes without thread - + 0 0 @@ -494,7 +526,7 @@ Only available for holes without thread - + 0 0 @@ -507,10 +539,10 @@ Only available for holes without thread - + - + 0 0 @@ -523,8 +555,14 @@ Only available for holes without thread - + + + + 0 + 0 + + The size of the drill point will be taken into account for the depth of blind holes @@ -543,6 +581,12 @@ account for the depth of blind holes + + + 0 + 0 + + Tapered @@ -550,6 +594,12 @@ account for the depth of blind holes + + + 0 + 0 + + Taper angle for the hole 90 degree: straight hole @@ -564,8 +614,14 @@ over 90: larger hole radius at the bottom - + + + + 0 + 0 + + Reverses the hole direction @@ -574,7 +630,7 @@ over 90: larger hole radius at the bottom - + @@ -620,28 +676,12 @@ over 90: larger hole radius at the bottom setEnabled(bool) - 49 - 451 + 40 + 540 - 163 - 453 - - - - - Threaded - clicked(bool) - ThreadFit - setEnabled(bool) - - 136 - 63 - - - 344 - 142 + 540 @@ -656,14 +696,30 @@ over 90: larger hole radius at the bottom 63 - 163 - 168 + 136 + 280 + + + + + Threaded + clicked(bool) + ThreadFit + setEnabled(bool) + + + 136 + 63 + + + 322 + 254 - + From c676c073169a17df38057e7f1b3df27c5e84b2c3 Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 31 Jan 2021 22:53:29 +0100 Subject: [PATCH 121/168] [PD] fix UTS clearance holes This PR is based on PR #4343 and a spin-off of PR #4337. It fixes: - the bug that we did not use the normed clearance hole diameters for UTS holes - missing recompute when changing existing hole from Metric to UTS type --- src/Mod/PartDesign/App/FeatureHole.cpp | 205 ++++++++++++++---- src/Mod/PartDesign/App/FeatureHole.h | 11 +- src/Mod/PartDesign/Gui/TaskHoleParameters.cpp | 26 ++- 3 files changed, 188 insertions(+), 54 deletions(-) diff --git a/src/Mod/PartDesign/App/FeatureHole.cpp b/src/Mod/PartDesign/App/FeatureHole.cpp index 9c014d291f..884ce8a5ca 100644 --- a/src/Mod/PartDesign/App/FeatureHole.cpp +++ b/src/Mod/PartDesign/App/FeatureHole.cpp @@ -71,7 +71,8 @@ namespace PartDesign { const char* Hole::DepthTypeEnums[] = { "Dimension", "ThroughAll", /*, "UpToFirst", */ NULL }; const char* Hole::ThreadTypeEnums[] = { "None", "ISOMetricProfile", "ISOMetricFineProfile", "UNC", "UNF", "UNEF", NULL}; -const char* Hole::ThreadFitEnums[] = { "Standard", "Close", "Wide", NULL}; +const char* Hole::ClearanceMetricEnums[] = { "Standard", "Close", "Wide", NULL}; +const char* Hole::ClearanceUTSEnums[] = { "Normal", "Close", "Loose", NULL }; const char* Hole::DrillPointEnums[] = { "Flat", "Angled", NULL}; /* "None" profile */ @@ -449,6 +450,39 @@ const double Hole::metricHoleDiameters[36][4] = { 68.0, 70.0, 77.0, 78.0} }; +const Hole::UTSClearanceDefinition Hole::UTSHoleDiameters[22] = +{ + /* UTS clearance hole diameters according to ASME B18.2.8 */ + // for information: the norm defines a drill bit number (that is in turn standardized in another ASME norm). + // as result the norm defines a minimal clearance which is the diameter of that drill bit. + // we use here this minimal clearance as the theoretical exact hole diameter as this is also done in the ISO norm. + // {screw class, close, normal, loose} + { "#0", 1.7, 1.9, 2.4 }, + { "#1", 2.1, 2.3, 2.6 }, + { "#2", 2.4, 2.6, 2.9 }, + { "#3", 2.7, 2.9, 3.3 }, + { "#4", 3.0, 3.3, 3.7 }, + { "#5", 3.6, 4.0, 4.4 }, + { "#6", 3.9, 4.3, 4.7 }, + { "#8", 4.6, 5.0, 5.4 }, + { "#10", 5.2, 5.6, 6.0 }, + // "#12" not defined + { "1/4", 6.8, 7.1, 7.5 }, + { "5/16", 8.3, 8.7, 9.1 }, + { "3/8", 9.9, 10.3, 10.7 }, + { "7/16", 11.5, 11.9, 12.3 }, + { "1/2", 13.5, 14.3, 15.5 }, + // "9/16" not defined + { "5/8", 16.7, 17.5, 18.6 }, + { "3/4", 19.8, 20.6, 23.0 }, + { "7/8", 23.0, 23.8, 26.2 }, + { "1", 26.2, 27.8, 29.4 }, + { "1 1/8", 29.4, 31.0, 33.3 }, + { "1 1/4", 32.5, 34.1, 36.5 }, + { "1 3/8", 36.5, 38.1, 40.9 }, + { "1 1/2", 39.7, 41.3, 44.0 } +}; + /* ISO coarse metric enums */ std::vector Hole::HoleCutType_ISOmetric_Enums = { "None", "Counterbore", "Countersink", "Cheesehead (deprecated)", "Countersink socket screw (deprecated)", "Cap screw (deprecated)" }; const char* Hole::ThreadSize_ISOmetric_Enums[] = { "M1", "M1.1", "M1.2", "M1.4", "M1.6", @@ -566,8 +600,8 @@ Hole::Hole() ADD_PROPERTY_TYPE(ThreadClass, (0L), "Hole", App::Prop_None, "Thread class"); ThreadClass.setEnums(ThreadClass_None_Enums); - ADD_PROPERTY_TYPE(ThreadFit, (0L), "Hole", App::Prop_None, "Thread fit"); - ThreadFit.setEnums(ThreadFitEnums); + ADD_PROPERTY_TYPE(ThreadFit, (0L), "Hole", App::Prop_None, "Clearance hole fit"); + ThreadFit.setEnums(ClearanceMetricEnums); ADD_PROPERTY_TYPE(Diameter, (6.0), "Hole", App::Prop_None, "Diameter"); @@ -812,52 +846,130 @@ void Hole::updateDiameterParam() } else { // we have a clearance hole bool found = false; - int MatrixRowSize = sizeof(metricHoleDiameters) / sizeof(metricHoleDiameters[0]); - switch ( ThreadFit.getValue() ) { - case 0: /* standard fit */ - // read diameter out of matrix - for (int i = 0; i < MatrixRowSize; i++) { - if (metricHoleDiameters[i][0] == diameter) { - diameter = metricHoleDiameters[i][2]; - found = true; - break; + std::string threadType = ThreadType.getValueAsString(); + // UTS and metric have a different clearance hole set + if (threadType == "ISOMetricProfile" || threadType == "ISOMetricFineProfile") { + int MatrixRowSizeMetric = sizeof(metricHoleDiameters) / sizeof(metricHoleDiameters[0]); + switch (ThreadFit.getValue()) { + case 0: /* standard fit */ + // read diameter out of matrix + for (int i = 0; i < MatrixRowSizeMetric; i++) { + if (metricHoleDiameters[i][0] == diameter) { + diameter = metricHoleDiameters[i][2]; + found = true; + break; + } } - } - // if nothing was found (e.g. if not metric), we must calculate - if (!found) { - diameter = (5 * ((int)((diameter * 110) / 5))) / 100.0; - } - break; - case 1: /* close fit */ - // read diameter out of matrix - for (int i = 0; i < MatrixRowSize; i++) { - if (metricHoleDiameters[i][0] == diameter) { - diameter = metricHoleDiameters[i][1]; - found = true; - break; + // if nothing is defined (e.g. for M2.2, M9 and M11), we must calculate + // we use the factors defined for M5 in the metricHoleDiameters list + if (!found) { + diameter = diameter * 1.10; } - } - // if nothing was found, we must calculate - if (!found) { - diameter = (5 * ((int)((diameter * 105) / 5))) / 100.0; - } - break; - case 2: /* wide fit */ - // read diameter out of matrix - for (int i = 0; i < MatrixRowSize; i++) { - if (metricHoleDiameters[i][0] == diameter) { - diameter = metricHoleDiameters[i][3]; - found = true; - break; + break; + case 1: /* close fit */ + // read diameter out of matrix + for (int i = 0; i < MatrixRowSizeMetric; i++) { + if (metricHoleDiameters[i][0] == diameter) { + diameter = metricHoleDiameters[i][1]; + found = true; + break; + } } + // if nothing was found, we must calculate + if (!found) { + diameter = diameter * 1.06; + } + break; + case 2: /* wide fit */ + // read diameter out of matrix + for (int i = 0; i < MatrixRowSizeMetric; i++) { + if (metricHoleDiameters[i][0] == diameter) { + diameter = metricHoleDiameters[i][3]; + found = true; + break; + } + } + // if nothing was found, we must calculate + if (!found) { + diameter = diameter * 1.16; + } + break; + default: + throw Base::IndexError("Thread fit out of range"); } - // if nothing was found, we must calculate - if (!found) { - diameter = (5 * ((int)((diameter * 115) / 5))) / 100.0; + } + else if (threadType == "UNC" || threadType == "UNF" || threadType == "UNEF") { + std::string ThreadSizeString = ThreadSize.getValueAsString(); + int MatrixRowSizeUTS = sizeof(UTSHoleDiameters) / sizeof(UTSHoleDiameters[0]); + switch (ThreadFit.getValue()) { + case 0: /* normal fit */ + // read diameter out of matrix + for (int i = 0; i < MatrixRowSizeUTS; i++) { + if (UTSHoleDiameters[i].designation == ThreadSizeString) { + diameter = UTSHoleDiameters[i].normal; + found = true; + break; + } + } + // if nothing was found (if "#12" or "9/16"), we must calculate + // // we use the factors defined for "3/8" in the UTSHoleDiameters list + if (!found) { + diameter = diameter * 1.08; + } + break; + case 1: /* close fit */ + // read diameter out of matrix + for (int i = 0; i < MatrixRowSizeUTS; i++) { + if (UTSHoleDiameters[i].designation == ThreadSizeString) { + diameter = UTSHoleDiameters[i].close; + found = true; + break; + } + } + // if nothing was found, we must calculate + if (!found) { + diameter = diameter * 1.04; + } + break; + case 2: /* loose fit */ + // read diameter out of matrix + for (int i = 0; i < MatrixRowSizeUTS; i++) { + if (UTSHoleDiameters[i].designation == ThreadSizeString) { + diameter = UTSHoleDiameters[i].loose; + found = true; + break; + } + } + // if nothing was found, we must calculate + if (!found) { + diameter = diameter * 1.12; + } + break; + default: + throw Base::IndexError("Thread fit out of range"); + } + } + else { + switch (ThreadFit.getValue()) { + case 0: /* normal fit */ + // we must calculate + if (!found) { + diameter = diameter * 1.1; + } + break; + case 1: /* close fit */ + if (!found) { + diameter = diameter * 1.05; + } + break; + case 2: /* loose fit */ + if (!found) { + diameter = diameter * 1.15; + } + break; + default: + throw Base::IndexError("Thread fit out of range"); } - break; - default: - assert( 0 ); } } Diameter.setValue(diameter); @@ -887,6 +999,7 @@ void Hole::onChanged(const App::Property *prop) ThreadSize.setEnums(ThreadSize_ISOmetric_Enums); ThreadClass.setEnums(ThreadClass_ISOmetric_Enums); HoleCutType.setEnums(HoleCutType_ISOmetric_Enums); + ThreadFit.setEnums(ClearanceMetricEnums); Threaded.setReadOnly(false); ThreadSize.setReadOnly(false); // thread class and direction are only sensible if threaded @@ -899,6 +1012,7 @@ void Hole::onChanged(const App::Property *prop) ThreadSize.setEnums(ThreadSize_ISOmetricfine_Enums); ThreadClass.setEnums(ThreadClass_ISOmetricfine_Enums); HoleCutType.setEnums(HoleCutType_ISOmetricfine_Enums); + ThreadFit.setEnums(ClearanceMetricEnums); Threaded.setReadOnly(false); ThreadSize.setReadOnly(false); // thread class and direction are only sensible if threaded @@ -911,6 +1025,7 @@ void Hole::onChanged(const App::Property *prop) ThreadSize.setEnums(ThreadSize_UNC_Enums); ThreadClass.setEnums(ThreadClass_UNC_Enums); HoleCutType.setEnums(HoleCutType_UNC_Enums); + ThreadFit.setEnums(ClearanceUTSEnums); Threaded.setReadOnly(false); ThreadSize.setReadOnly(false); // thread class and direction are only sensible if threaded @@ -923,6 +1038,7 @@ void Hole::onChanged(const App::Property *prop) ThreadSize.setEnums(ThreadSize_UNF_Enums); ThreadClass.setEnums(ThreadClass_UNF_Enums); HoleCutType.setEnums(HoleCutType_UNF_Enums); + ThreadFit.setEnums(ClearanceUTSEnums); Threaded.setReadOnly(false); ThreadSize.setReadOnly(false); // thread class and direction are only sensible if threaded @@ -935,6 +1051,7 @@ void Hole::onChanged(const App::Property *prop) ThreadSize.setEnums(ThreadSize_UNEF_Enums); ThreadClass.setEnums(ThreadClass_UNEF_Enums); HoleCutType.setEnums(HoleCutType_UNEF_Enums); + ThreadFit.setEnums(ClearanceUTSEnums); Threaded.setReadOnly(false); ThreadSize.setReadOnly(false); // thread class and direction are only sensible if threaded diff --git a/src/Mod/PartDesign/App/FeatureHole.h b/src/Mod/PartDesign/App/FeatureHole.h index 72cf07fc13..9ec22ccd3f 100644 --- a/src/Mod/PartDesign/App/FeatureHole.h +++ b/src/Mod/PartDesign/App/FeatureHole.h @@ -90,6 +90,14 @@ public: static const double metricHoleDiameters[36][4]; + typedef struct { + std::string designation; + double close; + double normal; + double loose; + } UTSClearanceDefinition; + static const UTSClearanceDefinition UTSHoleDiameters[22]; + virtual void Restore(Base::XMLReader & reader); virtual void updateProps(); @@ -99,7 +107,8 @@ protected: private: static const char* DepthTypeEnums[]; static const char* ThreadTypeEnums[]; - static const char* ThreadFitEnums[]; + static const char* ClearanceMetricEnums[]; + static const char* ClearanceUTSEnums[]; static const char* DrillPointEnums[]; static const char* ThreadDirectionEnums[]; diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp b/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp index 8e281f7dcf..fd35108360 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp @@ -440,31 +440,37 @@ void TaskHoleParameters::threadTypeChanged(int index) // now set the new type, this will reset the comboboxes to item 0 pcHole->ThreadType.setValue(index); - // Size - // the size for ISO type has either the form "M3x0.35" or just "M3" - // so we need to check if the size contains a 'x'. If yes, check if the string - // up to the 'x' is exists in the new list + // size and clearance if (TypeClass == QByteArray("ISO")) { + // the size for ISO type has either the form "M3x0.35" or just "M3" + // so we need to check if the size contains a 'x'. If yes, check if the string + // up to the 'x' is exists in the new list if (ThreadSizeString.indexOf(QString::fromLatin1("x")) > -1) { // we have an ISO fine size // cut of the part behind the 'x' ThreadSizeString = ThreadSizeString.left(ThreadSizeString.indexOf(QString::fromLatin1("x"))); } - // search if the string exists in the combobox int threadSizeIndex = ui->ThreadSize->findText(ThreadSizeString, Qt::MatchContains); if (threadSizeIndex > -1) { // we can set it ui->ThreadSize->setCurrentIndex(threadSizeIndex); } - } - - // for the UTS types the entries are the same - if (TypeClass == QByteArray("UTS")) { + // the names of the clearance types are different in ISO and UTS + ui->ThreadFit->setItemText(0, QCoreApplication::translate("TaskHoleParameters", "Standard", nullptr)); + ui->ThreadFit->setItemText(1, QCoreApplication::translate("TaskHoleParameters", "Close", nullptr)); + ui->ThreadFit->setItemText(2, QCoreApplication::translate("TaskHoleParameters", "Wide", nullptr)); + } + else if (TypeClass == QByteArray("UTS")) { + // for all UTS types the size entries are the same int threadSizeIndex = ui->ThreadSize->findText(ThreadSizeString, Qt::MatchContains); if (threadSizeIndex > -1) { ui->ThreadSize->setCurrentIndex(threadSizeIndex); } + // the names of the clearance types are different in ISO and UTS + ui->ThreadFit->setItemText(0, QCoreApplication::translate("TaskHoleParameters", "Normal", nullptr)); + ui->ThreadFit->setItemText(1, QCoreApplication::translate("TaskHoleParameters", "Close", nullptr)); + ui->ThreadFit->setItemText(2, QCoreApplication::translate("TaskHoleParameters", "Loose", nullptr)); } // Class and cut type @@ -475,6 +481,8 @@ void TaskHoleParameters::threadTypeChanged(int index) int holeCutIndex = ui->HoleCutType->findText(CutTypeString, Qt::MatchContains); if (holeCutIndex > -1) ui->HoleCutType->setCurrentIndex(holeCutIndex); + + recomputeFeature(); } void TaskHoleParameters::threadSizeChanged(int index) From 1c58c3b677ce82d193583e57f0c94a7eee42fbb1 Mon Sep 17 00:00:00 2001 From: donovaly Date: Mon, 1 Feb 2021 06:11:40 +0100 Subject: [PATCH 122/168] [PD] hole fix custom cut handling This PR is based on PR #4344 and a spin-off of PR #4337. It fixes: We have normed screw head cuts. These values can be overridden but we must store the info about the overriding. Why? - because when you have e.g. made a custom change to a normed countersink and then change to another countersink norm, you would either not get the values defined in the norm or you get these values but loose e.g. your depth settings --- src/Mod/PartDesign/App/FeatureHole.cpp | 125 +++++++++++++----- src/Mod/PartDesign/App/FeatureHole.h | 1 + src/Mod/PartDesign/Gui/TaskHoleParameters.cpp | 104 +++++++++++---- src/Mod/PartDesign/Gui/TaskHoleParameters.h | 2 + src/Mod/PartDesign/Gui/TaskHoleParameters.ui | 53 +++++--- 5 files changed, 211 insertions(+), 74 deletions(-) diff --git a/src/Mod/PartDesign/App/FeatureHole.cpp b/src/Mod/PartDesign/App/FeatureHole.cpp index 884ce8a5ca..eb4914bec3 100644 --- a/src/Mod/PartDesign/App/FeatureHole.cpp +++ b/src/Mod/PartDesign/App/FeatureHole.cpp @@ -612,6 +612,8 @@ Hole::Hole() ADD_PROPERTY_TYPE(HoleCutType, (0L), "Hole", App::Prop_None, "Head cut type"); HoleCutType.setEnums(HoleCutType_None_Enums); + ADD_PROPERTY_TYPE(HoleCutCustomValues, (false), "Hole", App::Prop_None, "Custom cut values"); + ADD_PROPERTY_TYPE(HoleCutDiameter, (0.0), "Hole", App::Prop_None, "Head cut diameter"); ADD_PROPERTY_TYPE(HoleCutDepth, (0.0), "Hole", App::Prop_None, "Head cut deth"); @@ -684,6 +686,8 @@ void Hole::updateHoleCutParams() } if (HoleCutDepth.getValue() == 0.0) HoleCutDepth.setValue(dimen.depth); + HoleCutDiameter.setReadOnly(false); + HoleCutDepth.setReadOnly(false); HoleCutCountersinkAngle.setReadOnly(true); } else if (holeCutType == "Countersink") { @@ -702,6 +706,8 @@ void Hole::updateHoleCutParams() if (HoleCutCountersinkAngle.getValue() == 0.0) { HoleCutCountersinkAngle.setValue(counter.angle); } + HoleCutDiameter.setReadOnly(false); + HoleCutDepth.setReadOnly(false); HoleCutCountersinkAngle.setReadOnly(false); } @@ -718,9 +724,31 @@ void Hole::updateHoleCutParams() // valid values for visual feedback HoleCutDiameter.setValue(Diameter.getValue() + 0.1); HoleCutDepth.setValue(0.1); + // we force custom values since there are no normed ones + HoleCutCustomValues.setReadOnly(true); + // important to set only if not already true, to avoid loop call of updateHoleCutParams() + if (!HoleCutCustomValues.getValue()) { + HoleCutCustomValues.setValue(true); + HoleCutDiameter.setReadOnly(false); + HoleCutDepth.setReadOnly(false); + } } else { - HoleCutDiameter.setValue(dimen.diameter); - HoleCutDepth.setValue(dimen.depth); + // set normed values if not overwritten or if previously there + // were no normed values available and thus HoleCutCustomValues is checked and read-only + if (!HoleCutCustomValues.getValue() + || (HoleCutCustomValues.getValue() && HoleCutCustomValues.isReadOnly())) { + HoleCutDiameter.setValue(dimen.diameter); + HoleCutDepth.setValue(dimen.depth); + HoleCutDiameter.setReadOnly(true); + HoleCutDepth.setReadOnly(true); + if (HoleCutCustomValues.getValue() && HoleCutCustomValues.isReadOnly()) + HoleCutCustomValues.setValue(false); + } + else { + HoleCutDiameter.setReadOnly(false); + HoleCutDepth.setReadOnly(false); + } + HoleCutCustomValues.setReadOnly(false); } } else if (counter.cut_type == CutDimensionSet::Countersink) { const CounterSinkDimension &dimen = counter.get_sink(threadSize); @@ -728,12 +756,37 @@ void Hole::updateHoleCutParams() // valid values for visual feedback HoleCutDiameter.setValue(Diameter.getValue() + 0.1); // there might be an angle of zero (if no norm exists for the size) - if (HoleCutCountersinkAngle.getValue() == 0.0) { + if (HoleCutCountersinkAngle.getValue() == 0.0) { HoleCutCountersinkAngle.setValue(counter.angle); } + // we force custom values since there are no normed ones + HoleCutCustomValues.setReadOnly(true); + // important to set only if not already true, to avoid loop call of updateHoleCutParams() + if (!HoleCutCustomValues.getValue()) { + HoleCutCustomValues.setValue(true); + HoleCutDiameter.setReadOnly(false); + HoleCutDepth.setReadOnly(false); + HoleCutCountersinkAngle.setReadOnly(false); + } } else { - HoleCutDiameter.setValue(dimen.diameter); - HoleCutCountersinkAngle.setValue(counter.angle); + // set normed values if not overwritten or if previously there + // were no normed values available and thus HoleCutCustomValues is checked and read-only + if (!HoleCutCustomValues.getValue() + || (HoleCutCustomValues.getValue() && HoleCutCustomValues.isReadOnly())) { + HoleCutDiameter.setValue(dimen.diameter); + HoleCutDiameter.setReadOnly(true); + HoleCutDepth.setReadOnly(true); + HoleCutCountersinkAngle.setValue(counter.angle); + HoleCutCountersinkAngle.setReadOnly(true); + if (HoleCutCustomValues.getValue() && HoleCutCustomValues.isReadOnly()) + HoleCutCustomValues.setValue(false); + } + else { + HoleCutDiameter.setReadOnly(false); + HoleCutDepth.setReadOnly(false); + HoleCutCountersinkAngle.setReadOnly(false); + } + HoleCutCustomValues.setReadOnly(false); } } } @@ -745,6 +798,8 @@ void Hole::updateHoleCutParams() else if (holeCutType == "Cheesehead (deprecated)") { HoleCutDiameter.setValue(diameterVal * 1.6); HoleCutDepth.setValue(diameterVal * 0.6); + HoleCutDiameter.setReadOnly(false); + HoleCutDepth.setReadOnly(false); } else if (holeCutType == "Countersink socket screw (deprecated)") { HoleCutDiameter.setValue(diameterVal * 2.0); @@ -752,10 +807,15 @@ void Hole::updateHoleCutParams() if (HoleCutCountersinkAngle.getValue() == 0.0) { HoleCutCountersinkAngle.setValue(90.0); } + HoleCutDiameter.setReadOnly(false); + HoleCutDepth.setReadOnly(false); + HoleCutCountersinkAngle.setReadOnly(false); } else if (holeCutType == "Cap screw (deprecated)") { HoleCutDiameter.setValue(diameterVal * 1.5); HoleCutDepth.setValue(diameterVal * 1.25); + HoleCutDiameter.setReadOnly(false); + HoleCutDepth.setReadOnly(false); } } else { // we have an UTS profile or none @@ -771,6 +831,8 @@ void Hole::updateHoleCutParams() } if (HoleCutDepth.getValue() == 0.0) HoleCutDepth.setValue(diameterVal * 0.9); + HoleCutDiameter.setReadOnly(false); + HoleCutDepth.setReadOnly(false); } else if (holeCutType == "Countersink") { if (HoleCutDiameter.getValue() == 0.0 || HoleCutDiameter.getValue() <= diameterVal) { @@ -787,6 +849,9 @@ void Hole::updateHoleCutParams() else HoleCutCountersinkAngle.setValue(90.0); } + HoleCutDiameter.setReadOnly(false); + HoleCutDepth.setReadOnly(false); + HoleCutCountersinkAngle.setReadOnly(false); } } } @@ -1062,24 +1127,37 @@ void Hole::onChanged(const App::Property *prop) } if (holeCutType == "None") { + HoleCutCustomValues.setReadOnly(true); HoleCutDiameter.setReadOnly(true); HoleCutDepth.setReadOnly(true); HoleCutCountersinkAngle.setReadOnly(true); } else if (holeCutType == "Counterbore") { + HoleCutCustomValues.setReadOnly(true); HoleCutDiameter.setReadOnly(false); HoleCutDepth.setReadOnly(false); HoleCutCountersinkAngle.setReadOnly(true); } else if (holeCutType == "Countersink") { + HoleCutCustomValues.setReadOnly(true); HoleCutDiameter.setReadOnly(false); HoleCutDepth.setReadOnly(false); HoleCutCountersinkAngle.setReadOnly(false); } else { // screw definition - HoleCutDiameter.setReadOnly(false); - HoleCutDepth.setReadOnly(false); - HoleCutCountersinkAngle.setReadOnly(false); + HoleCutCustomValues.setReadOnly(false); + if (HoleCutCustomValues.getValue()) { + HoleCutDiameter.setReadOnly(false); + HoleCutDepth.setReadOnly(false); + // we must not set HoleCutCountersinkAngle here because the info if this can + // be enabled is first available in updateHoleCutParams and thus handled there + updateHoleCutParams(); + } + else { + HoleCutDiameter.setReadOnly(true); + HoleCutDepth.setReadOnly(true); + HoleCutCountersinkAngle.setReadOnly(true); + } } // Signal changes to these @@ -1161,32 +1239,17 @@ void Hole::onChanged(const App::Property *prop) updateHoleCutParams(); } else if (prop == &HoleCutType) { - std::string holeCutType; - if (HoleCutType.isValid()) - holeCutType = HoleCutType.getValueAsString(); - if (holeCutType == "None") { - HoleCutDiameter.setReadOnly(true); - HoleCutDepth.setReadOnly(true); - HoleCutCountersinkAngle.setReadOnly(true); - } - else if (holeCutType == "Counterbore") { - HoleCutDiameter.setReadOnly(false); - HoleCutDepth.setReadOnly(false); - HoleCutCountersinkAngle.setReadOnly(true); - } - else if (holeCutType == "Countersink") { - HoleCutDiameter.setReadOnly(false); - HoleCutDepth.setReadOnly(false); - HoleCutCountersinkAngle.setReadOnly(false); - } - else { // screw definition - HoleCutDiameter.setReadOnly(false); - HoleCutDepth.setReadOnly(false); - HoleCutCountersinkAngle.setReadOnly(false); - } ProfileBased::onChanged(&HoleCutDiameter); ProfileBased::onChanged(&HoleCutDepth); ProfileBased::onChanged(&HoleCutCountersinkAngle); + + // the read-only states are set in updateHoleCutParams() + updateHoleCutParams(); + } + else if (prop == &HoleCutCustomValues) { + // when going back to standardized values, we must recalculate + // also to find out if HoleCutCountersinkAngle can be ReadOnly + // both an also the read-only states is done in updateHoleCutParams() updateHoleCutParams(); } else if (prop == &DepthType) { diff --git a/src/Mod/PartDesign/App/FeatureHole.h b/src/Mod/PartDesign/App/FeatureHole.h index 9ec22ccd3f..c28ef80465 100644 --- a/src/Mod/PartDesign/App/FeatureHole.h +++ b/src/Mod/PartDesign/App/FeatureHole.h @@ -57,6 +57,7 @@ public: App::PropertyLength Diameter; App::PropertyEnumeration ThreadDirection; App::PropertyEnumeration HoleCutType; + App::PropertyBool HoleCutCustomValues; App::PropertyLength HoleCutDiameter; App::PropertyLength HoleCutDepth; App::PropertyAngle HoleCutCountersinkAngle; diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp b/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp index fd35108360..c1648446f7 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp @@ -115,34 +115,16 @@ TaskHoleParameters::TaskHoleParameters(ViewProviderHole *HoleView, QWidget *pare ++cursor; } ui->HoleCutType->setCurrentIndex(pcHole->HoleCutType.getValue()); + ui->HoleCutCustomValues->setChecked(pcHole->HoleCutCustomValues.getValue()); + ui->HoleCutCustomValues->setDisabled(pcHole->HoleCutCustomValues.isReadOnly()); + // HoleCutDiameter must not be smaller or equal than the Diameter + ui->HoleCutDiameter->setMinimum(pcHole->Diameter.getValue() + 0.1); ui->HoleCutDiameter->setValue(pcHole->HoleCutDiameter.getValue()); + ui->HoleCutDiameter->setDisabled(pcHole->HoleCutDiameter.isReadOnly()); ui->HoleCutDepth->setValue(pcHole->HoleCutDepth.getValue()); + ui->HoleCutDepth->setDisabled(pcHole->HoleCutDepth.isReadOnly()); ui->HoleCutCountersinkAngle->setValue(pcHole->HoleCutCountersinkAngle.getValue()); - - std::string holeCutType; - if (pcHole->HoleCutType.isValid()) - holeCutType = pcHole->HoleCutType.getValueAsString(); - - if (holeCutType == "None") { - ui->HoleCutDiameter->setEnabled(false); - ui->HoleCutDepth->setEnabled(false); - ui->HoleCutCountersinkAngle->setEnabled(false); - } - else if (holeCutType == "Counterbore") { - ui->HoleCutDiameter->setEnabled(true); - ui->HoleCutDepth->setEnabled(true); - ui->HoleCutCountersinkAngle->setEnabled(false); - } - else if (holeCutType == "Countersink") { - ui->HoleCutDiameter->setEnabled(true); - ui->HoleCutDepth->setEnabled(true); - ui->HoleCutCountersinkAngle->setEnabled(true); - } - else { // screw definition - ui->HoleCutDiameter->setEnabled(true); - ui->HoleCutDepth->setEnabled(true); - ui->HoleCutCountersinkAngle->setEnabled(true); - } + ui->HoleCutCountersinkAngle->setDisabled(pcHole->HoleCutCountersinkAngle.isReadOnly()); ui->DepthType->setCurrentIndex(pcHole->DepthType.getValue()); ui->Depth->setValue(pcHole->Depth.getValue()); @@ -189,6 +171,7 @@ TaskHoleParameters::TaskHoleParameters(ViewProviderHole *HoleView, QWidget *pare connect(ui->directionRightHand, SIGNAL(clicked(bool)), this, SLOT(threadDirectionChanged())); connect(ui->directionLeftHand, SIGNAL(clicked(bool)), this, SLOT(threadDirectionChanged())); connect(ui->HoleCutType, SIGNAL(currentIndexChanged(int)), this, SLOT(holeCutTypeChanged(int))); + connect(ui->HoleCutCustomValues, SIGNAL(clicked(bool)), this, SLOT(holeCutCustomValuesChanged())); connect(ui->HoleCutDiameter, SIGNAL(valueChanged(double)), this, SLOT(holeCutDiameterChanged(double))); connect(ui->HoleCutDepth, SIGNAL(valueChanged(double)), this, SLOT(holeCutDepthChanged(double))); connect(ui->HoleCutCountersinkAngle, SIGNAL(valueChanged(double)), this, SLOT(holeCutCountersinkAngleChanged(double))); @@ -282,8 +265,65 @@ void TaskHoleParameters::holeCutTypeChanged(int index) // therefore reset it, it will be reset to sensible values by setting the new HoleCutType pcHole->HoleCutDepth.setValue(0.0); + // when holeCutType was changed, reset HoleCutCustomValues to false because it should + // be a purpose decision to overwrite the normed values + // we will handle the case that there is no normed value later in this routine + ui->HoleCutCustomValues->setChecked(false); + pcHole->HoleCutCustomValues.setValue(false); + pcHole->HoleCutType.setValue(index); + + // recompute to get the info about the HoleCutType properties recomputeFeature(); + + // apply the result to the widgets + ui->HoleCutCustomValues->setDisabled(pcHole->HoleCutCustomValues.isReadOnly()); + ui->HoleCutCustomValues->setChecked(pcHole->HoleCutCustomValues.getValue()); + + // HoleCutCustomValues is only enabled for screw definitions + // we must do this after recomputeFeature() because this gives us the info if + // the type is a countersink and thus if HoleCutCountersinkAngle can be enabled + std::string HoleCutTypeString = pcHole->HoleCutType.getValueAsString(); + if (HoleCutTypeString == "None" || HoleCutTypeString == "Counterbore" + || HoleCutTypeString == "Countersink") { + ui->HoleCutCustomValues->setEnabled(false); + } + else { // screw definition + // we can have the case that we have no normed values + // in this case HoleCutCustomValues is read-only AND true + if (ui->HoleCutCustomValues->isChecked()) { + ui->HoleCutDiameter->setEnabled(true); + ui->HoleCutDepth->setEnabled(true); + if (!pcHole->HoleCutCountersinkAngle.isReadOnly()) + ui->HoleCutCountersinkAngle->setEnabled(true); + } + else { + ui->HoleCutDiameter->setEnabled(false); + ui->HoleCutDepth->setEnabled(false); + ui->HoleCutCountersinkAngle->setEnabled(false); + } + } +} + +void TaskHoleParameters::holeCutCustomValuesChanged() +{ + PartDesign::Hole* pcHole = static_cast(vp->getObject()); + + pcHole->HoleCutCustomValues.setValue(ui->HoleCutCustomValues->isChecked()); + + if (ui->HoleCutCustomValues->isChecked()) { + ui->HoleCutDiameter->setEnabled(true); + ui->HoleCutDepth->setEnabled(true); + if (!pcHole->HoleCutCountersinkAngle.isReadOnly()) + ui->HoleCutCountersinkAngle->setEnabled(true); + } + else { + ui->HoleCutDiameter->setEnabled(false); + ui->HoleCutDepth->setEnabled(false); + ui->HoleCutCountersinkAngle->setEnabled(false); + } + + recomputeFeature(); } void TaskHoleParameters::holeCutDiameterChanged(double value) @@ -482,6 +522,9 @@ void TaskHoleParameters::threadTypeChanged(int index) if (holeCutIndex > -1) ui->HoleCutType->setCurrentIndex(holeCutIndex); + // we must set the read-only state according to the new HoleCutType + holeCutTypeChanged(ui->HoleCutType->currentIndex()); + recomputeFeature(); } @@ -512,6 +555,10 @@ void TaskHoleParameters::threadDiameterChanged(double value) PartDesign::Hole* pcHole = static_cast(vp->getObject()); pcHole->Diameter.setValue(value); + + // HoleCutDiameter must not be smaller or equal than the Diameter + ui->HoleCutDiameter->setMinimum(value + 0.1); + recomputeFeature(); } @@ -824,6 +871,11 @@ long TaskHoleParameters::getHoleCutType() const return ui->HoleCutType->currentIndex(); } +bool TaskHoleParameters::getHoleCutCustomValues() const +{ + return ui->HoleCutCustomValues->isChecked(); +} + Base::Quantity TaskHoleParameters::getHoleCutDiameter() const { return ui->HoleCutDiameter->value(); @@ -910,6 +962,8 @@ void TaskHoleParameters::apply() FCMD_OBJ_CMD(obj,"ThreadDirection = " << getThreadDirection()); if (!pcHole->HoleCutType.isReadOnly()) FCMD_OBJ_CMD(obj,"HoleCutType = " << getHoleCutType()); + if (!pcHole->HoleCutCustomValues.isReadOnly()) + FCMD_OBJ_CMD(obj, "HoleCutCustomValues = " << (getHoleCutCustomValues() ? 1 : 0)); if (!pcHole->DepthType.isReadOnly()) FCMD_OBJ_CMD(obj,"DepthType = " << getDepthType()); if (!pcHole->DrillPoint.isReadOnly()) diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.h b/src/Mod/PartDesign/Gui/TaskHoleParameters.h index 894c4314a6..2ec266cac8 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.h +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.h @@ -69,6 +69,7 @@ public: Base::Quantity getDiameter() const; long getThreadDirection() const; long getHoleCutType() const; + bool getHoleCutCustomValues() const; Base::Quantity getHoleCutDiameter() const; Base::Quantity getHoleCutDepth() const; Base::Quantity getHoleCutCountersinkAngle() const; @@ -94,6 +95,7 @@ private Q_SLOTS: void threadDiameterChanged(double value); void threadDirectionChanged(); void holeCutTypeChanged(int index); + void holeCutCustomValuesChanged(); void holeCutDiameterChanged(double value); void holeCutDepthChanged(double value); void holeCutCountersinkAngleChanged(double value); diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui index 27a6b7ac60..acb1d821e9 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui @@ -6,8 +6,8 @@ 0 0 - 335 - 463 + 342 + 486 @@ -362,6 +362,22 @@ Only available for holes without thread + + + + 0 + 0 + + + + Check to override the values predefined by the 'Type' + + + Custom values + + + + @@ -374,7 +390,7 @@ Only available for holes without thread - + @@ -402,7 +418,7 @@ Only available for holes without thread - + @@ -415,7 +431,7 @@ Only available for holes without thread - + @@ -440,7 +456,7 @@ Only available for holes without thread - + @@ -453,7 +469,7 @@ Only available for holes without thread - + @@ -475,7 +491,7 @@ Only available for holes without thread - + @@ -491,7 +507,7 @@ Only available for holes without thread - + @@ -507,7 +523,7 @@ Only available for holes without thread - + @@ -523,7 +539,7 @@ Only available for holes without thread - + @@ -539,7 +555,7 @@ Only available for holes without thread - + @@ -555,7 +571,7 @@ Only available for holes without thread - + @@ -572,14 +588,14 @@ account for the depth of blind holes - + <b>Misc</b> - + @@ -592,7 +608,7 @@ account for the depth of blind holes - + @@ -614,7 +630,7 @@ over 90: larger hole radius at the bottom - + @@ -630,7 +646,7 @@ over 90: larger hole radius at the bottom - + @@ -656,6 +672,7 @@ over 90: larger hole radius at the bottom DepthType Depth HoleCutType + HoleCutCustomValues HoleCutDiameter HoleCutDepth HoleCutCountersinkAngle From 45dd1a7f608f822aaab4dc52d2308edb977a3f5a Mon Sep 17 00:00:00 2001 From: donovaly Date: Tue, 2 Feb 2021 04:11:10 +0100 Subject: [PATCH 123/168] fix issue reported by @chennes also fix annoying variable naming - different variables representing different types should not have the same name also update a comment according to depending PRs --- src/Mod/PartDesign/App/FeatureHole.cpp | 52 +++++++++---------- src/Mod/PartDesign/Gui/TaskHoleParameters.cpp | 4 ++ 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/Mod/PartDesign/App/FeatureHole.cpp b/src/Mod/PartDesign/App/FeatureHole.cpp index eb4914bec3..af43e512bf 100644 --- a/src/Mod/PartDesign/App/FeatureHole.cpp +++ b/src/Mod/PartDesign/App/FeatureHole.cpp @@ -639,10 +639,10 @@ Hole::Hole() void Hole::updateHoleCutParams() { - std::string holeCutType = HoleCutType.getValueAsString(); + std::string holeCutTypeStr = HoleCutType.getValueAsString(); // there is no cut, thus return - if (holeCutType == "None") + if (holeCutTypeStr == "None") return; if (ThreadType.getValue() < 0) { @@ -654,23 +654,23 @@ void Hole::updateHoleCutParams() double diameterVal = Diameter.getValue(); // handle thread types - std::string threadType = ThreadType.getValueAsString(); - if (threadType == "ISOMetricProfile" || threadType == "ISOMetricFineProfile") { + std::string threadTypeStr = ThreadType.getValueAsString(); + if (threadTypeStr == "ISOMetricProfile" || threadTypeStr == "ISOMetricFineProfile") { if (ThreadSize.getValue() < 0) { throw Base::IndexError("Thread size out of range"); return; } - std::string threadSize{ ThreadSize.getValueAsString() }; + std::string threadSizeStr = ThreadSize.getValueAsString(); // we don't update for these settings but we need to set a value for new holes // furthermore we must assure the hole cut diameter is not <= the hole diameter // if we have a cut but the values are zero, we assume it is a new hole // we take in this case the values from the norm ISO 4762 or ISO 10642 - if (holeCutType == "Counterbore") { + if (holeCutTypeStr == "Counterbore") { // read ISO 4762 values - const CutDimensionSet& counter = find_cutDimensionSet(threadType, "ISO 4762"); - const CounterBoreDimension& dimen = counter.get_bore(threadSize); + const CutDimensionSet& counter = find_cutDimensionSet(threadTypeStr, "ISO 4762"); + const CounterBoreDimension& dimen = counter.get_bore(threadSizeStr); if (HoleCutDiameter.getValue() == 0.0 || HoleCutDiameter.getValue() <= diameterVal) { // there is no norm defining counterbores for all sizes, thus we need to use the // same fallback as for the case HoleCutTypeMap.count(key) @@ -690,11 +690,11 @@ void Hole::updateHoleCutParams() HoleCutDepth.setReadOnly(false); HoleCutCountersinkAngle.setReadOnly(true); } - else if (holeCutType == "Countersink") { + else if (holeCutTypeStr == "Countersink") { // read ISO 10642 values - const CutDimensionSet& counter = find_cutDimensionSet(threadType, "ISO 10642"); + const CutDimensionSet& counter = find_cutDimensionSet(threadTypeStr, "ISO 10642"); if (HoleCutDiameter.getValue() == 0.0 || HoleCutDiameter.getValue() <= diameterVal) { - const CounterSinkDimension& dimen = counter.get_sink(threadSize); + const CounterSinkDimension& dimen = counter.get_sink(threadSizeStr); if (dimen.diameter != 0.0) { HoleCutDiameter.setValue(dimen.diameter); } @@ -712,14 +712,14 @@ void Hole::updateHoleCutParams() } // cut definition - CutDimensionKey key { threadType, holeCutType }; + CutDimensionKey key { threadTypeStr, holeCutTypeStr }; if (HoleCutTypeMap.count(key)) { const CutDimensionSet &counter = find_cutDimensionSet(key); if (counter.cut_type == CutDimensionSet::Counterbore) { // disable HoleCutCountersinkAngle and reset it to ISO's default HoleCutCountersinkAngle.setValue(90.0); HoleCutCountersinkAngle.setReadOnly(true); - const CounterBoreDimension &dimen = counter.get_bore(threadSize); + const CounterBoreDimension &dimen = counter.get_bore(threadSizeStr); if (dimen.thread == "None") { // valid values for visual feedback HoleCutDiameter.setValue(Diameter.getValue() + 0.1); @@ -751,7 +751,7 @@ void Hole::updateHoleCutParams() HoleCutCustomValues.setReadOnly(false); } } else if (counter.cut_type == CutDimensionSet::Countersink) { - const CounterSinkDimension &dimen = counter.get_sink(threadSize); + const CounterSinkDimension &dimen = counter.get_sink(threadSizeStr); if (dimen.thread == "None") { // valid values for visual feedback HoleCutDiameter.setValue(Diameter.getValue() + 0.1); @@ -795,13 +795,13 @@ void Hole::updateHoleCutParams() // user defined None, Counterbore and Countersink // handle legacy types but don’t change user settings for // user defined None, Counterbore and Countersink - else if (holeCutType == "Cheesehead (deprecated)") { + else if (holeCutTypeStr == "Cheesehead (deprecated)") { HoleCutDiameter.setValue(diameterVal * 1.6); HoleCutDepth.setValue(diameterVal * 0.6); HoleCutDiameter.setReadOnly(false); HoleCutDepth.setReadOnly(false); } - else if (holeCutType == "Countersink socket screw (deprecated)") { + else if (holeCutTypeStr == "Countersink socket screw (deprecated)") { HoleCutDiameter.setValue(diameterVal * 2.0); HoleCutDepth.setValue(diameterVal * 0.0); if (HoleCutCountersinkAngle.getValue() == 0.0) { @@ -811,7 +811,7 @@ void Hole::updateHoleCutParams() HoleCutDepth.setReadOnly(false); HoleCutCountersinkAngle.setReadOnly(false); } - else if (holeCutType == "Cap screw (deprecated)") { + else if (holeCutTypeStr == "Cap screw (deprecated)") { HoleCutDiameter.setValue(diameterVal * 1.5); HoleCutDepth.setValue(diameterVal * 1.25); HoleCutDiameter.setReadOnly(false); @@ -824,7 +824,7 @@ void Hole::updateHoleCutParams() // furthermore we must assure the hole cut diameter is not <= the hole diameter // if we have a cut but the values are zero, we assume it is a new hole // we use rules of thumbs as proposal - if (holeCutType == "Counterbore") { + if (holeCutTypeStr == "Counterbore") { if (HoleCutDiameter.getValue() == 0.0 || HoleCutDiameter.getValue() <= diameterVal) { HoleCutDiameter.setValue(diameterVal * 1.6); HoleCutDepth.setValue(diameterVal * 0.9); @@ -834,17 +834,17 @@ void Hole::updateHoleCutParams() HoleCutDiameter.setReadOnly(false); HoleCutDepth.setReadOnly(false); } - else if (holeCutType == "Countersink") { + else if (holeCutTypeStr == "Countersink") { if (HoleCutDiameter.getValue() == 0.0 || HoleCutDiameter.getValue() <= diameterVal) { HoleCutDiameter.setValue(diameterVal * 1.7); // 82 degrees for UTS, 90 otherwise - if (threadType != "None") + if (threadTypeStr != "None") HoleCutCountersinkAngle.setValue(82.0); else HoleCutCountersinkAngle.setValue(90.0); } if (HoleCutCountersinkAngle.getValue() == 0.0) { - if (threadType != "None") + if (threadTypeStr != "None") HoleCutCountersinkAngle.setValue(82.0); else HoleCutCountersinkAngle.setValue(90.0); @@ -1043,11 +1043,11 @@ void Hole::updateDiameterParam() void Hole::onChanged(const App::Property *prop) { if (prop == &ThreadType) { - std::string type, holeCutType; + std::string type, holeCutTypeStr; if (ThreadType.isValid()) type = ThreadType.getValueAsString(); if (HoleCutType.isValid()) - holeCutType = HoleCutType.getValueAsString(); + holeCutTypeStr = HoleCutType.getValueAsString(); if (type == "None" ) { ThreadSize.setEnums(ThreadSize_None_Enums); @@ -1126,19 +1126,19 @@ void Hole::onChanged(const App::Property *prop) Diameter.setReadOnly(true); } - if (holeCutType == "None") { + if (holeCutTypeStr == "None") { HoleCutCustomValues.setReadOnly(true); HoleCutDiameter.setReadOnly(true); HoleCutDepth.setReadOnly(true); HoleCutCountersinkAngle.setReadOnly(true); } - else if (holeCutType == "Counterbore") { + else if (holeCutTypeStr == "Counterbore") { HoleCutCustomValues.setReadOnly(true); HoleCutDiameter.setReadOnly(false); HoleCutDepth.setReadOnly(false); HoleCutCountersinkAngle.setReadOnly(true); } - else if (holeCutType == "Countersink") { + else if (holeCutTypeStr == "Countersink") { HoleCutCustomValues.setReadOnly(true); HoleCutDiameter.setReadOnly(false); HoleCutDepth.setReadOnly(false); diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp b/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp index c1648446f7..6dc77ad8ef 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp @@ -537,6 +537,10 @@ void TaskHoleParameters::threadSizeChanged(int index) pcHole->ThreadSize.setValue(index); recomputeFeature(); + + // apply the recompute result to the widgets + ui->HoleCutCustomValues->setDisabled(pcHole->HoleCutCustomValues.isReadOnly()); + ui->HoleCutCustomValues->setChecked(pcHole->HoleCutCustomValues.getValue()); } void TaskHoleParameters::threadClassChanged(int index) From d064cdc2157f0cbfca106409114bccfbb74e7c23 Mon Sep 17 00:00:00 2001 From: luz paz Date: Wed, 3 Feb 2021 10:27:17 -0500 Subject: [PATCH 124/168] Arch: Fix LGTM warning of missing param for translate() in ArchPanel.py Even though this is ignored per the docs[1], LGTM complains[2] about it. This commit adds the superfluous param. Note: there is precedent in ArchReference.py[3] and ArchWall.py[4]. [1]https://github.com/FreeCAD/FreeCAD/blob/master/src/Mod/Draft/draftutils/translate.py#L73-L78 [2] https://lgtm.com/projects/g/FreeCAD/FreeCAD/snapshot/17db14c9700ae65d042d3548ef07f9518a115ba4/files/src/Mod/Arch/ArchPanel.py#L32 [3] https://github.com/FreeCAD/FreeCAD/blob/45dd1a7f608f822aaab4dc52d2308edb977a3f5a/src/Mod/Arch/ArchReference.py#L39 [4] https://github.com/FreeCAD/FreeCAD/blob/45dd1a7f608f822aaab4dc52d2308edb977a3f5a/src/Mod/Arch/ArchWall.py#L42 --- src/Mod/Arch/ArchPanel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Arch/ArchPanel.py b/src/Mod/Arch/ArchPanel.py index 7da9e755a0..14677a76e3 100644 --- a/src/Mod/Arch/ArchPanel.py +++ b/src/Mod/Arch/ArchPanel.py @@ -29,7 +29,7 @@ if FreeCAD.GuiUp: import draftguitools.gui_trackers as DraftTrackers else: # \cond - def translate(ctxt,txt): + def translate(ctxt,txt,utf8_decode=False): return txt def QT_TRANSLATE_NOOP(ctxt,txt): return txt From 99b2cba5f1477ccb0728ece323a6703f50924ba3 Mon Sep 17 00:00:00 2001 From: David Osterberg Date: Sat, 30 Jan 2021 18:24:05 +0100 Subject: [PATCH 125/168] PartDesign: Allow selection of sketch plane by double click in picker dialog Thanks to @0penBrain for solving a tricky segmentation fault --- src/Mod/PartDesign/Gui/TaskFeaturePick.cpp | 16 ++++++++++++++++ src/Mod/PartDesign/Gui/TaskFeaturePick.h | 17 ++++++++++------- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/Mod/PartDesign/Gui/TaskFeaturePick.cpp b/src/Mod/PartDesign/Gui/TaskFeaturePick.cpp index fd49cc49d3..5d11ba6355 100644 --- a/src/Mod/PartDesign/Gui/TaskFeaturePick.cpp +++ b/src/Mod/PartDesign/Gui/TaskFeaturePick.cpp @@ -26,6 +26,7 @@ #ifndef _PreComp_ # include # include +# include #endif #include @@ -96,6 +97,8 @@ TaskFeaturePick::TaskFeaturePick(std::vector& objects, connect(ui->radioDependent, SIGNAL(toggled(bool)), this, SLOT(onUpdate(bool))); connect(ui->radioXRef, SIGNAL(toggled(bool)), this, SLOT(onUpdate(bool))); connect(ui->listWidget, SIGNAL(itemSelectionChanged()), this, SLOT(onItemSelectionChanged())); + connect(ui->listWidget, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(onDoubleClick(QListWidgetItem *))); + enum { axisBit=0, planeBit = 1}; @@ -471,6 +474,18 @@ void TaskFeaturePick::onItemSelectionChanged() doSelection = false; } +void TaskFeaturePick::onDoubleClick(QListWidgetItem *item) +{ + if (doSelection) + return; + doSelection = true; + QString t = item->data(Qt::UserRole).toString(); + Gui::Selection().addSelection(documentName.c_str(), t.toLatin1()); + doSelection = false; + + QMetaObject::invokeMethod(qobject_cast(&Gui::Control()), "accept", Qt::QueuedConnection); +} + void TaskFeaturePick::slotDeletedObject(const Gui::ViewProviderDocumentObject& Obj) { std::vector::iterator it; @@ -572,4 +587,5 @@ void TaskDlgFeaturePick::showExternal(bool val) } + #include "moc_TaskFeaturePick.cpp" diff --git a/src/Mod/PartDesign/Gui/TaskFeaturePick.h b/src/Mod/PartDesign/Gui/TaskFeaturePick.h index eef80dc5c2..bdce4fd7d6 100644 --- a/src/Mod/PartDesign/Gui/TaskFeaturePick.h +++ b/src/Mod/PartDesign/Gui/TaskFeaturePick.h @@ -31,6 +31,7 @@ #include #include +#include namespace PartDesignGui { @@ -55,22 +56,23 @@ public: afterTip }; - TaskFeaturePick(std::vector &objects, + TaskFeaturePick(std::vector &objects, const std::vector &status, QWidget *parent = 0); - + ~TaskFeaturePick(); std::vector getFeatures(); std::vector buildFeatures(); void showExternal(bool val); - + static App::DocumentObject* makeCopy(App::DocumentObject* obj, std::string sub, bool independent); - + protected Q_SLOTS: void onUpdate(bool); void onSelectionChanged(const Gui::SelectionChanges& msg); void onItemSelectionChanged(); + void onDoubleClick(QListWidgetItem *item); protected: /** Notifies when the object is about to be removed. */ @@ -117,16 +119,17 @@ public: virtual bool accept(); /// is called by the framework if the dialog is rejected (Cancel) virtual bool reject(); - /// is called by the framework if the user presses the help button + /// is called by the framework if the user presses the help button virtual bool isAllowedAlterDocument(void) const { return false; } - + void showExternal(bool val); - /// returns for Close and Help button + /// returns for Close and Help button virtual QDialogButtonBox::StandardButtons getStandardButtons(void) const { return QDialogButtonBox::Ok|QDialogButtonBox::Cancel; } + protected: TaskFeaturePick *pick; bool accepted; From d73ccbdba0c30cac819edb044657397738f9d886 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 3 Feb 2021 18:06:55 +0100 Subject: [PATCH 126/168] Gui: add preferences tab for selection --- src/Gui/CMakeLists.txt | 5 +++ src/Gui/DlgSettingsSelection.cpp | 77 ++++++++++++++++++++++++++++++++ src/Gui/DlgSettingsSelection.h | 62 +++++++++++++++++++++++++ src/Gui/DlgSettingsSelection.ui | 62 +++++++++++++++++++++++++ src/Gui/resource.cpp | 2 + 5 files changed, 208 insertions(+) create mode 100644 src/Gui/DlgSettingsSelection.cpp create mode 100644 src/Gui/DlgSettingsSelection.h create mode 100644 src/Gui/DlgSettingsSelection.ui diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 344a249882..e2412c0c6d 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -317,6 +317,7 @@ set(Gui_MOC_HDRS DlgReportViewImp.h DlgSettings3DViewImp.h DlgSettingsNavigation.h + DlgSettingsSelection.h DlgSettingsViewColor.h DlgSettingsColorGradientImp.h DlgSettingsDocumentImp.h @@ -443,6 +444,7 @@ SET(Gui_UIC_SRCS DlgReportView.ui DlgSettings3DView.ui DlgSettingsNavigation.ui + DlgSettingsSelection.ui DlgSettingsUnits.ui DlgSettingsViewColor.ui DlgSettingsColorGradient.ui @@ -673,6 +675,7 @@ SET(Dialog_Settings_CPP_SRCS DlgReportViewImp.cpp DlgSettings3DViewImp.cpp DlgSettingsNavigation.cpp + DlgSettingsSelection.cpp DlgSettingsUnitsImp.cpp DlgSettingsViewColor.cpp DlgSettingsColorGradientImp.cpp @@ -688,6 +691,7 @@ SET(Dialog_Settings_HPP_SRCS DlgReportViewImp.h DlgSettings3DViewImp.h DlgSettingsNavigation.h + DlgSettingsSelection.h DlgSettingsUnitsImp.h DlgSettingsViewColor.h DlgSettingsColorGradientImp.h @@ -705,6 +709,7 @@ SET(Dialog_Settings_SRCS DlgReportView.ui DlgSettings3DView.ui DlgSettingsNavigation.ui + DlgSettingsSelection.ui DlgSettingsUnits.ui DlgSettingsViewColor.ui DlgSettingsColorGradient.ui diff --git a/src/Gui/DlgSettingsSelection.cpp b/src/Gui/DlgSettingsSelection.cpp new file mode 100644 index 0000000000..ec798f80b2 --- /dev/null +++ b/src/Gui/DlgSettingsSelection.cpp @@ -0,0 +1,77 @@ +/*************************************************************************** + * Copyright (c) 2021 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., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#endif + +#include "DlgSettingsSelection.h" +#include "ui_DlgSettingsSelection.h" +#include + +using namespace Gui::Dialog; + +/* TRANSLATOR Gui::Dialog::DlgSettingsSelection */ + +DlgSettingsSelection::DlgSettingsSelection(QWidget* parent) + : PreferencePage(parent) + , ui(new Ui_DlgSettingsSelection) +{ + ui->setupUi(this); +} + +DlgSettingsSelection::~DlgSettingsSelection() +{ +} + +void DlgSettingsSelection::saveSettings() +{ + auto handle = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView"); + handle->SetBool("SyncView", ui->checkBoxAutoSwitch->isChecked()); + handle->SetBool("SyncSelection", ui->checkBoxAutoExpand->isChecked()); + handle->SetBool("PreSelection", ui->checkBoxPreselect->isChecked()); + handle->SetBool("RecordSelection", ui->checkBoxRecord->isChecked()); +} + +void DlgSettingsSelection::loadSettings() +{ + auto handle = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView"); + ui->checkBoxAutoSwitch->setChecked(handle->GetBool("SyncView")); + ui->checkBoxAutoExpand->setChecked(handle->GetBool("SyncSelection")); + ui->checkBoxPreselect->setChecked(handle->GetBool("PreSelection")); + ui->checkBoxRecord->setChecked(handle->GetBool("RecordSelection")); +} + +void DlgSettingsSelection::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::LanguageChange) { + ui->retranslateUi(this); + } + else { + QWidget::changeEvent(e); + } +} + +#include "moc_DlgSettingsSelection.cpp" + diff --git a/src/Gui/DlgSettingsSelection.h b/src/Gui/DlgSettingsSelection.h new file mode 100644 index 0000000000..b58846a6a5 --- /dev/null +++ b/src/Gui/DlgSettingsSelection.h @@ -0,0 +1,62 @@ +/*************************************************************************** + * Copyright (c) 2021 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., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#ifndef GUI_DIALOG_DLGSETTINGSSELECTION_H +#define GUI_DIALOG_DLGSETTINGSSELECTION_H + +#include "PropertyPage.h" +#include + +class QDoubleSpinBox; + +namespace Gui { +namespace Dialog { +class Ui_DlgSettingsSelection; + +/** + * The Ui_DlgSettingsSelection class implements a preference page to change settings + * for the selection. + * \author Werner Mayer + */ +class DlgSettingsSelection : public PreferencePage +{ + Q_OBJECT + +public: + DlgSettingsSelection(QWidget* parent = nullptr); + ~DlgSettingsSelection(); + + void saveSettings(); + void loadSettings(); + +protected: + void changeEvent(QEvent *e); + +private: + std::unique_ptr ui; +}; + +} // namespace Dialog +} // namespace Gui + +#endif // GUI_DIALOG_DLGSETTINGSSELECTION_H diff --git a/src/Gui/DlgSettingsSelection.ui b/src/Gui/DlgSettingsSelection.ui new file mode 100644 index 0000000000..02799e5312 --- /dev/null +++ b/src/Gui/DlgSettingsSelection.ui @@ -0,0 +1,62 @@ + + + Gui::Dialog::DlgSettingsSelection + + + + 0 + 0 + 670 + 411 + + + + Selection + + + + + + Auto switch to the 3D view containing the selected item + + + + + + + Auto expand tree item when the corresponding object is selected in 3D view + + + + + + + Preselect the object in 3D view when mouse over the tree item + + + + + + + Record selection in tree view in order to go back/forward using navigation button + + + + + + + Qt::Vertical + + + + 20 + 274 + + + + + + + + + diff --git a/src/Gui/resource.cpp b/src/Gui/resource.cpp index 4449b09b3b..d3d3647690 100644 --- a/src/Gui/resource.cpp +++ b/src/Gui/resource.cpp @@ -32,6 +32,7 @@ #include "DlgPreferencesImp.h" #include "DlgSettings3DViewImp.h" #include "DlgSettingsNavigation.h" +#include "DlgSettingsSelection.h" #include "DlgSettingsViewColor.h" #include "DlgGeneralImp.h" #include "DlgEditorImp.h" @@ -66,6 +67,7 @@ WidgetFactorySupplier::WidgetFactorySupplier() new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); //new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); new PrefPageProducer( QT_TRANSLATE_NOOP("QObject","General") ); + new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); From 05c61e349af66ce9489d4d7fc6aa819109d7a6a5 Mon Sep 17 00:00:00 2001 From: Benjamin Nauck Date: Sat, 2 Jan 2021 20:31:41 +0100 Subject: [PATCH 127/168] Gui: Add support for selecting multiple objects without keyboard Adds checkboxes to the document tree which makes it possible to select multiple document objects on devices without physical keyboard. These checkboxes are opt-in and can be enabled/disabled from: Edit -> Preferences -> Display -> Navigation -> Selection Forum thread for feature the request: https://forum.freecadweb.org/viewtopic.php?f=34&t=53065 We need to iterate the tree to add/remove the selection boxes when the enable-setting has been changed. The size for the first column in the tree is adjusted to fit both name and optional checkbox. --- src/Gui/DlgPropertyLink.cpp | 2 +- src/Gui/DlgSettingsSelection.cpp | 2 + src/Gui/DlgSettingsSelection.ui | 25 +++++++----- src/Gui/Tree.cpp | 68 ++++++++++++++++++++++++++++++-- src/Gui/Tree.h | 6 +++ 5 files changed, 89 insertions(+), 14 deletions(-) diff --git a/src/Gui/DlgPropertyLink.cpp b/src/Gui/DlgPropertyLink.cpp index 6fb8f2c1f0..ff99b918ed 100644 --- a/src/Gui/DlgPropertyLink.cpp +++ b/src/Gui/DlgPropertyLink.cpp @@ -897,7 +897,7 @@ QTreeWidgetItem *DlgPropertyLink::createItem( if(allowSubObject) { item->setChildIndicatorPolicy(obj->getLinkedObject(true)->getOutList().size()? QTreeWidgetItem::ShowIndicator:QTreeWidgetItem::DontShowIndicator); - item->setFlags(item->flags() | Qt::ItemIsEditable); + item->setFlags(item->flags() | Qt::ItemIsEditable | Qt::ItemIsUserCheckable); } const char *typeName = obj->getTypeId().getName(); diff --git a/src/Gui/DlgSettingsSelection.cpp b/src/Gui/DlgSettingsSelection.cpp index ec798f80b2..aff8d341d2 100644 --- a/src/Gui/DlgSettingsSelection.cpp +++ b/src/Gui/DlgSettingsSelection.cpp @@ -52,6 +52,7 @@ void DlgSettingsSelection::saveSettings() handle->SetBool("SyncSelection", ui->checkBoxAutoExpand->isChecked()); handle->SetBool("PreSelection", ui->checkBoxPreselect->isChecked()); handle->SetBool("RecordSelection", ui->checkBoxRecord->isChecked()); + handle->SetBool("CheckBoxesSelection", ui->checkBoxSelectionCheckBoxes->isChecked()); } void DlgSettingsSelection::loadSettings() @@ -61,6 +62,7 @@ void DlgSettingsSelection::loadSettings() ui->checkBoxAutoExpand->setChecked(handle->GetBool("SyncSelection")); ui->checkBoxPreselect->setChecked(handle->GetBool("PreSelection")); ui->checkBoxRecord->setChecked(handle->GetBool("RecordSelection")); + ui->checkBoxSelectionCheckBoxes->setChecked(handle->GetBool("CheckBoxesSelection")); } void DlgSettingsSelection::changeEvent(QEvent *e) diff --git a/src/Gui/DlgSettingsSelection.ui b/src/Gui/DlgSettingsSelection.ui index 02799e5312..b4ec70372f 100644 --- a/src/Gui/DlgSettingsSelection.ui +++ b/src/Gui/DlgSettingsSelection.ui @@ -14,13 +14,6 @@ Selection - - - - Auto switch to the 3D view containing the selected item - - - @@ -42,7 +35,7 @@ - + Qt::Vertical @@ -50,11 +43,25 @@ 20 - 274 + 245 + + + + Auto switch to the 3D view containing the selected item + + + + + + + Add checkboxes for selection in document tree + + + diff --git a/src/Gui/Tree.cpp b/src/Gui/Tree.cpp index 222625d279..d2557d3dc1 100644 --- a/src/Gui/Tree.cpp +++ b/src/Gui/Tree.cpp @@ -139,6 +139,11 @@ void TreeParams::onSyncSelectionChanged() { TreeWidget::scrollItemToTop(); } +void TreeParams::onCheckBoxesSelectionChanged() +{ + TreeWidget::instance()->synchronizeSelectionCheckBoxes(); +} + void TreeParams::onDocumentModeChanged() { App::GetApplication().setActiveDocument(App::GetApplication().getActiveDocument()); } @@ -549,6 +554,8 @@ TreeWidget::TreeWidget(const char *name, QWidget* parent) this, SLOT(onItemExpanded(QTreeWidgetItem*))); connect(this, SIGNAL(itemSelectionChanged()), this, SLOT(onItemSelectionChanged())); + connect(this, SIGNAL(itemChanged(QTreeWidgetItem*, int)), + this, SLOT(onItemChanged(QTreeWidgetItem*, int))); connect(this->preselectTimer, SIGNAL(timeout()), this, SLOT(onPreSelectTimer())); connect(this->selectTimer, SIGNAL(timeout()), @@ -2800,6 +2807,33 @@ void TreeWidget::onItemSelectionChanged () this->blockConnection(lock); } +static bool isSelectionCheckBoxesEnabled() { + return TreeParams::Instance()->CheckBoxesSelection(); +} + +void TreeWidget::synchronizeSelectionCheckBoxes() { + const bool useCheckBoxes = isSelectionCheckBoxesEnabled(); + for (QTreeWidgetItemIterator it(this); *it; ++it) { + if (const auto item = dynamic_cast(*it)) { + if (useCheckBoxes) + item->QTreeWidgetItem::setCheckState(0, item->isSelected() ? Qt::Checked : Qt::Unchecked); + else + item->setData(0, Qt::CheckStateRole, QVariant()); + } + } + resizeColumnToContents(0); +} + +void TreeWidget::onItemChanged(QTreeWidgetItem *item, int column) { + if (column == 0 && isSelectionCheckBoxesEnabled()) { + bool selected = item->isSelected(); + bool checked = item->checkState(0) == Qt::Checked; + if (checked != selected) { + item->setSelected(checked); + } + } +} + void TreeWidget::onSelectTimer() { _updateStatus(false); @@ -3923,6 +3957,7 @@ void DocumentItem::clearSelection(DocumentObjectItem *exclude) item->selected = 0; item->mySubs.clear(); item->setSelected(false); + item->setCheckState(false); } END_FOREACH_ITEM; treeWidget()->blockSignals(ok); @@ -3933,8 +3968,10 @@ void DocumentItem::updateSelection(QTreeWidgetItem *ti, bool unselect) { auto child = ti->child(i); if(child && child->type()==TreeWidget::ObjectType) { auto childItem = static_cast(child); - if(unselect) + if (unselect) { childItem->setSelected(false); + childItem->setCheckState(false); + } updateItemSelection(childItem); if(unselect && childItem->isGroup()) { // If the child item being force unselected by its group parent @@ -3952,8 +3989,17 @@ void DocumentItem::updateSelection(QTreeWidgetItem *ti, bool unselect) { void DocumentItem::updateItemSelection(DocumentObjectItem *item) { bool selected = item->isSelected(); - if((selected && item->selected>0) || (!selected && !item->selected)) + bool checked = item->checkState(0) == Qt::Checked; + + if(selected && !checked) + item->setCheckState(true); + + if(!selected && checked) + item->setCheckState(false); + + if((selected && item->selected>0) || (!selected && !item->selected)) { return; + } if(item->selected != -1) item->mySubs.clear(); item->selected = selected; @@ -4026,6 +4072,7 @@ void DocumentItem::updateItemSelection(DocumentObjectItem *item) { if(!Gui::Selection().addSelection(docname,objname,subname.c_str())) { item->selected = 0; item->setSelected(false); + item->setCheckState(false); return; } } @@ -4221,6 +4268,7 @@ void DocumentItem::selectItems(SelectionReason reason) { item->selected = 0; item->mySubs.clear(); item->setSelected(false); + item->setCheckState(false); }else if(item->selected) { if(sync) { if(item->selected==2 && showItem(item,false,reason==SR_FORCE_EXPAND)) { @@ -4242,6 +4290,7 @@ void DocumentItem::selectItems(SelectionReason reason) { } item->selected = 1; item->setSelected(true); + item->setCheckState(true); } END_FOREACH_ITEM; @@ -4338,8 +4387,10 @@ bool DocumentItem::showItem(DocumentObjectItem *item, bool select, bool force) { }else parent->setExpanded(true); - if(select) + if(select) { item->setSelected(true); + item->setCheckState(true); + } return true; } @@ -4366,7 +4417,9 @@ DocumentObjectItem::DocumentObjectItem(DocumentItem *ownerDocItem, DocumentObjec : QTreeWidgetItem(TreeWidget::ObjectType) , myOwner(ownerDocItem), myData(data), previousStatus(-1),selected(0),populated(false) { - setFlags(flags()|Qt::ItemIsEditable); + setFlags(flags() | Qt::ItemIsEditable | Qt::ItemIsUserCheckable); + setCheckState(false); + myData->items.insert(this); ++countItems; TREE_LOG("Create item: " << countItems << ", " << object()->getObject()->getFullName()); @@ -4948,6 +5001,13 @@ App::DocumentObject *DocumentObjectItem::getRelativeParent( return 0; } +void DocumentObjectItem::setCheckState(bool checked) { + if (isSelectionCheckBoxesEnabled()) + QTreeWidgetItem::setCheckState(0, checked ? Qt::Checked : Qt::Unchecked); + else + setData(0, Qt::CheckStateRole, QVariant()); +} + DocumentItem *DocumentObjectItem::getParentDocument() const { return getTree()->getDocumentItem(object()->getDocument()); } diff --git a/src/Gui/Tree.h b/src/Gui/Tree.h index 8f9b2a5a76..aa9e4cc9e2 100644 --- a/src/Gui/Tree.h +++ b/src/Gui/Tree.h @@ -129,6 +129,8 @@ public: void startItemSearch(QLineEdit*); void itemSearch(const QString &text, bool select); + void synchronizeSelectionCheckBoxes(); + protected: /// Observer message from the Selection void onSelectionChanged(const SelectionChanges& msg) override; @@ -176,6 +178,7 @@ protected Q_SLOTS: private Q_SLOTS: void onItemSelectionChanged(void); + void onItemChanged(QTreeWidgetItem*, int); void onItemEntered(QTreeWidgetItem * item); void onItemCollapsed(QTreeWidgetItem * item); void onItemExpanded(QTreeWidgetItem * item); @@ -436,6 +439,8 @@ public: TreeWidget *getTree() const; private: + void setCheckState(bool checked); + QBrush bgBrush; DocumentItem *myOwner; DocumentObjectDataPtr myData; @@ -521,6 +526,7 @@ public: #define FC_TREEPARAM_DEFS \ FC_TREEPARAM_DEF2(SyncSelection,bool,Bool,true) \ + FC_TREEPARAM_DEF2(CheckBoxesSelection,bool,Bool,false) \ FC_TREEPARAM_DEF(SyncView,bool,Bool,true) \ FC_TREEPARAM_DEF(PreSelection,bool,Bool,true) \ FC_TREEPARAM_DEF(SyncPlacement,bool,Bool,false) \ From 222145993d855edfbeb2243c48b785196a3ac573 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 3 Feb 2021 21:56:43 +0100 Subject: [PATCH 128/168] Gui: [skip ci] restore tab order in selection settings page --- src/Gui/DlgSettingsSelection.ui | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Gui/DlgSettingsSelection.ui b/src/Gui/DlgSettingsSelection.ui index b4ec70372f..12ac3da475 100644 --- a/src/Gui/DlgSettingsSelection.ui +++ b/src/Gui/DlgSettingsSelection.ui @@ -14,6 +14,13 @@ Selection + + + + Auto switch to the 3D view containing the selected item + + + @@ -35,6 +42,13 @@ + + + + Add checkboxes for selection in document tree + + + @@ -48,20 +62,6 @@ - - - - Auto switch to the 3D view containing the selected item - - - - - - - Add checkboxes for selection in document tree - - - From 27a6dedf19a765112771567048b941d9eb89bdf1 Mon Sep 17 00:00:00 2001 From: Benjamin Alterauge <5332429+ageeye@users.noreply.github.com> Date: Wed, 3 Feb 2021 18:28:14 +0100 Subject: [PATCH 129/168] Temporarily qt5 fixes for Apple Silicon need to disable the qtwebengine. That has to be considered in MacAppBundle. --- src/MacAppBundle/CMakeLists.txt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/MacAppBundle/CMakeLists.txt b/src/MacAppBundle/CMakeLists.txt index c8bd761b73..c0b6ccf09f 100644 --- a/src/MacAppBundle/CMakeLists.txt +++ b/src/MacAppBundle/CMakeLists.txt @@ -63,16 +63,17 @@ if(BUILD_QT5) endif(BUILD_QT5) # add QtWebEngineProcess to bundle + if(BUILD_WEB) install(PROGRAMS "${Qt5Core_DIR}/../../../lib/QtWebEngineCore.framework/Versions/5/Helpers/QtWebEngineProcess.app/Contents/MacOS/QtWebEngineProcess" DESTINATION ${CMAKE_INSTALL_PREFIX}/MacOS) # add locales to bundle - file(GLOB _locales_files RELATIVE "${Qt5Core_DIR}/../../../lib/QtWebEngineCore.framework/Versions/5/Resources/qtwebengine_locales" "${Qt5Core_DIR}/../../../lib/QtWebEngineCore.framework/Versions/5/Resources/qtwebengine_locales/*") - foreach(_locales_file ${_locales_files}) + file(GLOB _locales_files RELATIVE "${Qt5Core_DIR}/../../../lib/QtWebEngineCore.framework/Versions/5/Resources/qtwebengine_locales" "${Qt5Core_DIR}/../../../lib/QtWebEngineCore.framework/Versions/5/Resources/qtwebengine_locales/*") + foreach(_locales_file ${_locales_files}) get_filename_component(_resolved_file "${Qt5Core_DIR}/../../../lib/QtWebEngineCore.framework/Versions/5/Resources/qtwebengine_locales/${_locales_file}" REALPATH) list(APPEND _locales_resolved_files ${_resolved_file}) - endforeach() - install(FILES ${_locales_resolved_files} DESTINATION "${CMAKE_INSTALL_PREFIX}/MacOS/qtwebengine_locales") - + endforeach() + install(FILES ${_locales_resolved_files} DESTINATION "${CMAKE_INSTALL_PREFIX}/MacOS/qtwebengine_locales") + # add pak file(GLOB _pak_files RELATIVE "${Qt5Core_DIR}/../../../lib/QtWebEngineCore.framework/Versions/5/Resources/" "${Qt5Core_DIR}/../../../lib/QtWebEngineCore.framework/Versions/5/Resources/*.pak") foreach(_pak_file ${_pak_files}) @@ -82,9 +83,10 @@ endif(BUILD_QT5) install(FILES ${_pak_resolved_files} DESTINATION "${CMAKE_INSTALL_PREFIX}/") # add icudtl.dat + install(PROGRAMS "${Qt5Core_DIR}/../../../lib/QtWebEngineCore.framework/Versions/5/Resources/icudtl.dat" DESTINATION ${CMAKE_INSTALL_PREFIX}/) - install(PROGRAMS "${Qt5Core_DIR}/../../../lib/QtWebEngineCore.framework/Versions/5/Resources/QtWebEngineCore.prl" DESTINATION ${CMAKE_INSTALL_PREFIX}/) - + install(PROGRAMS "${Qt5Core_DIR}/../../../lib/QtWebEngineCore.framework/Versions/5/Resources/QtWebEngineCore.prl" DESTINATION ${CMAKE_INSTALL_PREFIX}/) + endif(BUILD_WEB) # Ensure the actual plugin files are installed instead of symlinks. From ccbc13ee8209c5c5bf3de7b501daa82f6e8f1a46 Mon Sep 17 00:00:00 2001 From: luz paz Date: Wed, 3 Feb 2021 11:25:48 -0500 Subject: [PATCH 130/168] Draft: fix LGTM 'Testing for None should use the 'is' operator' alerrts https://lgtm.com/projects/g/FreeCAD/FreeCAD/alerts/?mode=tree&ruleFocus=7900090 --- src/Mod/Draft/draftfunctions/draftify.py | 2 +- src/Mod/Draft/draftgeoutils/geometry.py | 2 +- src/Mod/Draft/draftgeoutils/offsets.py | 2 +- src/Mod/Draft/draftgeoutils/wires.py | 2 +- src/Mod/Draft/draftmake/make_line.py | 2 +- src/Mod/Draft/draftobjects/patharray.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Mod/Draft/draftfunctions/draftify.py b/src/Mod/Draft/draftfunctions/draftify.py index cb8355b55d..4d1bda8f6d 100644 --- a/src/Mod/Draft/draftfunctions/draftify.py +++ b/src/Mod/Draft/draftfunctions/draftify.py @@ -70,7 +70,7 @@ def draftify(objectslist, makeblock=False, delete=True): for cluster in Part.sortEdges(obj.Shape.Edges): w = Part.Wire(cluster) nobj = draftify_shape(w) - if nobj == None: + if nobj is None: nobj = App.ActiveDocument.addObject("Part::Feature", obj.Name) nobj.Shape = w newobjlist.append(nobj) diff --git a/src/Mod/Draft/draftgeoutils/geometry.py b/src/Mod/Draft/draftgeoutils/geometry.py index 1695e2bec4..00b5eccd44 100644 --- a/src/Mod/Draft/draftgeoutils/geometry.py +++ b/src/Mod/Draft/draftgeoutils/geometry.py @@ -465,7 +465,7 @@ def calculatePlacement(shape): pos = shape.BoundBox.Center norm = get_normal(shape) # for backward compatibility with previous getNormal implementation - if norm == None: + if norm is None: norm = App.Vector(0, 0, 1) pla = App.Placement() pla.Base = pos diff --git a/src/Mod/Draft/draftgeoutils/offsets.py b/src/Mod/Draft/draftgeoutils/offsets.py index a7ba52ff1c..23fe36c998 100644 --- a/src/Mod/Draft/draftgeoutils/offsets.py +++ b/src/Mod/Draft/draftgeoutils/offsets.py @@ -233,7 +233,7 @@ def offsetWire(wire, dvec, bind=False, occ=False, else: norm = get_normal(wire) # norm = Vector(0, 0, 1) # for backward compatibility with previous getNormal implementation - if norm == None: + if norm is None: norm = App.Vector(0, 0, 1) closed = isReallyClosed(wire) diff --git a/src/Mod/Draft/draftgeoutils/wires.py b/src/Mod/Draft/draftgeoutils/wires.py index 8c6f9dfed9..9ee4a5a1ee 100644 --- a/src/Mod/Draft/draftgeoutils/wires.py +++ b/src/Mod/Draft/draftgeoutils/wires.py @@ -160,7 +160,7 @@ def flattenWire(wire): """Force a wire to get completely flat along its normal.""" n = get_normal(wire) # for backward compatibility with previous getNormal implementation - if n == None: + if n is None: n = App.Vector(0, 0, 1) o = wire.Vertexes[0].Point diff --git a/src/Mod/Draft/draftmake/make_line.py b/src/Mod/Draft/draftmake/make_line.py index e8c1068642..eec782f38b 100644 --- a/src/Mod/Draft/draftmake/make_line.py +++ b/src/Mod/Draft/draftmake/make_line.py @@ -39,7 +39,7 @@ def make_line(first_param, last_param=None): Parameters ---------- first_param : - Base.Vector -> First point of the line (if p2 == None) + Base.Vector -> First point of the line (if p2 is None) Part.LineSegment -> Line is created from the given Linesegment Shape -> Line is created from the give Shape diff --git a/src/Mod/Draft/draftobjects/patharray.py b/src/Mod/Draft/draftobjects/patharray.py index 59b1e0a438..fe7c0355a5 100644 --- a/src/Mod/Draft/draftobjects/patharray.py +++ b/src/Mod/Draft/draftobjects/patharray.py @@ -446,7 +446,7 @@ def placements_on_path(shapeRotation, pathwire, count, xlate, align, closedpath = DraftGeomUtils.isReallyClosed(pathwire) normal = DraftGeomUtils.get_normal(pathwire) # for backward compatibility with previous getNormal implementation - if normal == None: + if normal is None: normal = App.Vector(0, 0, 1) if forceNormal and normalOverride: From 86992f8086cd337254ba0da30cf3af047c81e0b1 Mon Sep 17 00:00:00 2001 From: j <77419450+emmanuelobrien@users.noreply.github.com> Date: Wed, 3 Feb 2021 15:11:02 +0100 Subject: [PATCH 131/168] Sketcher: Preserve corner and constraints for sketch fillets Currently the sketch fillet tool deletes any constraints associated with the two lines to be filleted. By leaving a vertex at the intersection, we can instead preserve most constraints in reasonable ways. Sketch fillet horizontal and vertical point-to-point constraint support Also better future compatibility for point constraints, and some minor tweaks. --- src/Mod/Sketcher/App/SketchObject.cpp | 250 +++++++++++++++++++++----- src/Mod/Sketcher/App/SketchObject.h | 58 +++++- 2 files changed, 262 insertions(+), 46 deletions(-) diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index e87e51d7a7..6edd7e74e3 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -1542,6 +1542,172 @@ int SketchObject::delConstraintOnPoint(int GeoId, PointPos PosId, bool onlyCoinc return -1; // no such constraint } +void SketchObject::transferFilletConstraints(int geoId1, PointPos posId1, int geoId2, PointPos posId2) { + // If the lines don't intersect, there's no original corner to work with so + // don't try to transfer the constraints. But we should delete line length and equal + // constraints and constraints on the affected endpoints because they're about + // to move unpredictably. + if (!arePointsCoincident(geoId1, posId1, geoId2, posId2)) { + // Delete constraints on the endpoints + delConstraintOnPoint(geoId1, posId1, false); + delConstraintOnPoint(geoId2, posId2, false); + + // Delete line length and equal constraints + const std::vector &constraints = this->Constraints.getValues(); + std::vector deleteme; + for (int i=0; i < int(constraints.size()); i++) { + const Constraint *c = constraints[i]; + if (c->Type == Sketcher::Distance || c->Type == Sketcher::Equal) { + bool line1 = c->First == geoId1 && c->FirstPos == none; + bool line2 = c->First == geoId2 && c->FirstPos == none; + if (line1 || line2) { + deleteme.push_back(i); + } + } + } + delConstraints(deleteme, false); + return; + } + + // If the lines aren't straight, don't try to transfer the constraints. + // TODO: Add support for curved lines. + const Part::Geometry *geo1 = getGeometry(geoId1); + const Part::Geometry *geo2 = getGeometry(geoId2); + if (geo1->getTypeId() != Part::GeomLineSegment::getClassTypeId() || + geo2->getTypeId() != Part::GeomLineSegment::getClassTypeId() ) { + delConstraintOnPoint(geoId1, posId1, false); + delConstraintOnPoint(geoId2, posId2, false); + return; + } + + // Add a vertex to preserve the original intersection of the filleted lines + Part::GeomPoint *originalCorner = new Part::GeomPoint(getPoint(geoId1, posId1)); + int originalCornerId = addGeometry(originalCorner); + delete originalCorner; + + // Constrain the vertex to the two lines + Sketcher::Constraint *cornerToLine1 = new Sketcher::Constraint(); + cornerToLine1->Type = Sketcher::PointOnObject; + cornerToLine1->First = originalCornerId; + cornerToLine1->FirstPos = start; + cornerToLine1->Second = geoId1; + cornerToLine1->SecondPos = none; + addConstraint(cornerToLine1); + delete cornerToLine1; + Sketcher::Constraint *cornerToLine2 = new Sketcher::Constraint(); + cornerToLine2->Type = Sketcher::PointOnObject; + cornerToLine2->First = originalCornerId; + cornerToLine2->FirstPos = start; + cornerToLine2->Second = geoId2; + cornerToLine2->SecondPos = none; + addConstraint(cornerToLine2); + delete cornerToLine2; + + Base::StateLocker lock(managedoperation, true); + + // Loop through all the constraints and try to do reasonable things with the affected ones + std::vector newConstraints; + for (auto c : this->Constraints.getValues()) { + // Keep track of whether the affected lines and endpoints appear in this constraint + bool point1First = c->First == geoId1 && c->FirstPos == posId1; + bool point2First = c->First == geoId2 && c->FirstPos == posId2; + bool point1Second = c->Second == geoId1 && c->SecondPos == posId1; + bool point2Second = c->Second == geoId2 && c->SecondPos == posId2; + bool point1Third = c->Third == geoId1 && c->ThirdPos == posId1; + bool point2Third = c->Third == geoId2 && c->ThirdPos == posId2; + bool line1First = c->First == geoId1 && c->FirstPos == none; + bool line2First = c->First == geoId2 && c->FirstPos == none; + bool line1Second = c->Second == geoId1 && c->SecondPos == none; + bool line2Second = c->Second == geoId2 && c->SecondPos == none; + + if (c->Type == Sketcher::Coincident) { + if ((point1First && point2Second) || (point2First && point1Second)) { + // This is the constraint holding the two edges together that are about to be filleted. This constraint + // goes away because the edges will touch the fillet instead. + continue; + } + if (point1First || point2First) { + // Move the coincident constraint to the new corner point + c->First = originalCornerId; + c->FirstPos = start; + } + if (point1Second || point2Second) { + // Move the coincident constraint to the new corner point + c->Second = originalCornerId; + c->SecondPos = start; + } + } else if (c->Type == Sketcher::Horizontal || c->Type == Sketcher::Vertical) { + // Point-to-point horizontal or vertical constraint, move to new corner point + if (point1First || point2First) { + c->First = originalCornerId; + c->FirstPos = start; + } + if (point1Second || point2Second) { + c->Second = originalCornerId; + c->SecondPos = start; + } + } else if (c->Type == Sketcher::Distance || c->Type == Sketcher::DistanceX || c->Type == Sketcher::DistanceY) { + // Point-to-point distance constraint. Move it to the new corner point + if (point1First || point2First) { + c->First = originalCornerId; + c->FirstPos = start; + } + if (point1Second || point2Second) { + c->Second = originalCornerId; + c->SecondPos = start; + } + + // Distance constraint on the line itself. Change it to point-point between the far end of the line + // and the new corner + if (line1First) { + c->FirstPos = (posId1 == start) ? end : start; + c->Second = originalCornerId; + c->SecondPos = start; + } + if (line2First) { + c->FirstPos = (posId2 == start) ? end : start; + c->Second = originalCornerId; + c->SecondPos = start; + } + } else if (c->Type == Sketcher::PointOnObject) { + // The corner to be filleted was touching some other object. + if (point1First || point2First) { + c->First = originalCornerId; + c->FirstPos = start; + } + } else if (c->Type == Sketcher::Equal) { + // Equal length constraints are dicey because the lines are getting shorter. Safer to + // delete them and let the user notice the underconstraint. + if (line1First || line2First || line1Second || line2Second) { + continue; + } + } else if (c->Type == Sketcher::Symmetric) { + // Symmetries should probably be preserved relative to the original corner + if (point1First || point2First) { + c->First = originalCornerId; + c->FirstPos = start; + } else if (point1Second || point2Second) { + c->Second = originalCornerId; + c->SecondPos = start; + } else if (point1Third || point2Third) { + c->Third = originalCornerId; + c->ThirdPos = start; + } + } else if (c->Type == Sketcher::SnellsLaw) { + // Can't imagine any cases where you'd fillet a vertex going through a lens, so let's + // delete to be safe. + continue; + } else if (point1First || point2First || point1Second || point2Second || point1Third || point2Third) { + // Delete any other point-based constraints on the relevant points + continue; + } + + // Default: keep all other constraints + newConstraints.push_back(c->clone()); + } + this->Constraints.setValues(std::move(newConstraints)); +} + int SketchObject::transferConstraints(int fromGeoId, PointPos fromPosId, int toGeoId, PointPos toPosId) { Base::StateLocker lock(managedoperation, true); // no need to check input data validity as this is an sketchobject managed operation. @@ -1553,6 +1719,7 @@ int SketchObject::transferConstraints(int fromGeoId, PointPos fromPosId, int toG if (vals[i]->First == fromGeoId && vals[i]->FirstPos == fromPosId && !(vals[i]->Second == toGeoId && vals[i]->SecondPos == toPosId) && !(toGeoId < 0 && vals[i]->Second <0) ) { + // Nothing guarantees that a tangent can be freely transferred to another coincident point, as // the transfer destination edge most likely won't be intended to be tangent. However, if it is // an end to end point tangency, the user expects it to be substituted by a coincidence constraint. @@ -1607,7 +1774,7 @@ int SketchObject::transferConstraints(int fromGeoId, PointPos fromPosId, int toG return 0; } -int SketchObject::fillet(int GeoId, PointPos PosId, double radius, bool trim) +int SketchObject::fillet(int GeoId, PointPos PosId, double radius, bool trim, bool createCorner) { if (GeoId < 0 || GeoId > getHighestCurveIndex()) return -1; @@ -1628,7 +1795,7 @@ int SketchObject::fillet(int GeoId, PointPos PosId, double radius, bool trim) Base::Vector3d midPnt1 = (lineSeg1->getStartPoint() + lineSeg1->getEndPoint()) / 2 ; Base::Vector3d midPnt2 = (lineSeg2->getStartPoint() + lineSeg2->getEndPoint()) / 2 ; - return fillet(GeoIdList[0], GeoIdList[1], midPnt1, midPnt2, radius, trim); + return fillet(GeoIdList[0], GeoIdList[1], midPnt1, midPnt2, radius, trim, createCorner); } } @@ -1637,14 +1804,19 @@ int SketchObject::fillet(int GeoId, PointPos PosId, double radius, bool trim) int SketchObject::fillet(int GeoId1, int GeoId2, const Base::Vector3d& refPnt1, const Base::Vector3d& refPnt2, - double radius, bool trim) + double radius, bool trim, bool createCorner) { if (GeoId1 < 0 || GeoId1 > getHighestCurveIndex() || GeoId2 < 0 || GeoId2 > getHighestCurveIndex()) return -1; + // If either of the two input lines are locked, don't try to trim since it won't work anyway const Part::Geometry *geo1 = getGeometry(GeoId1); const Part::Geometry *geo2 = getGeometry(GeoId2); + if (trim && (GeometryFacade::getBlocked(geo1) || GeometryFacade::getBlocked(geo2))) { + trim = false; + } + if (geo1->getTypeId() == Part::GeomLineSegment::getClassTypeId() && geo2->getTypeId() == Part::GeomLineSegment::getClassTypeId() ) { const Part::GeomLineSegment *lineSeg1 = static_cast(geo1); @@ -1661,63 +1833,59 @@ int SketchObject::fillet(int GeoId1, int GeoId2, // create arc from known parameters and lines int filletId; - Part::GeomArcOfCircle *arc = Part::createFilletGeometry(lineSeg1, lineSeg2, filletCenter, radius); - if (arc) { - // calculate intersection and distances before we invalidate lineSeg1 and lineSeg2 - if (!find2DLinesIntersection(lineSeg1, lineSeg2, intersection)) { - delete arc; - return -1; - } - dist1.ProjectToLine(arc->getStartPoint(/*emulateCCW=*/true)-intersection, dir1); - dist2.ProjectToLine(arc->getStartPoint(/*emulateCCW=*/true)-intersection, dir2); - Part::Geometry *newgeo = arc; - filletId = addGeometry(newgeo); - if (filletId < 0) { - delete arc; - return -1; - } - } - else + std::unique_ptr arc( + Part::createFilletGeometry(lineSeg1, lineSeg2, filletCenter, radius)); + if (!arc) return -1; + + // calculate intersection and distances before we invalidate lineSeg1 and lineSeg2 + if (!find2DLinesIntersection(lineSeg1, lineSeg2, intersection)) { return -1; + } + + dist1.ProjectToLine(arc->getStartPoint(/*emulateCCW=*/true)-intersection, dir1); + dist2.ProjectToLine(arc->getStartPoint(/*emulateCCW=*/true)-intersection, dir2); + Part::Geometry *newgeo = arc.get(); + filletId = addGeometry(newgeo); if (trim) { PointPos PosId1 = (filletCenter-intersection)*dir1 > 0 ? start : end; PointPos PosId2 = (filletCenter-intersection)*dir2 > 0 ? start : end; - delConstraintOnPoint(GeoId1, PosId1, false); - delConstraintOnPoint(GeoId2, PosId2, false); - Sketcher::Constraint *tangent1 = new Sketcher::Constraint(); - Sketcher::Constraint *tangent2 = new Sketcher::Constraint(); + if (createCorner) { + transferFilletConstraints(GeoId1, PosId1, GeoId2, PosId2); + } else { + delConstraintOnPoint(GeoId1, PosId1, false); + delConstraintOnPoint(GeoId2, PosId2, false); + } - tangent1->Type = Sketcher::Tangent; - tangent1->First = GeoId1; - tangent1->FirstPos = PosId1; - tangent1->Second = filletId; + Sketcher::Constraint tangent1, tangent2; - tangent2->Type = Sketcher::Tangent; - tangent2->First = GeoId2; - tangent2->FirstPos = PosId2; - tangent2->Second = filletId; + tangent1.Type = Sketcher::Tangent; + tangent1.First = GeoId1; + tangent1.FirstPos = PosId1; + tangent1.Second = filletId; + + tangent2.Type = Sketcher::Tangent; + tangent2.First = GeoId2; + tangent2.FirstPos = PosId2; + tangent2.Second = filletId; if (dist1.Length() < dist2.Length()) { - tangent1->SecondPos = start; - tangent2->SecondPos = end; + tangent1.SecondPos = start; + tangent2.SecondPos = end; movePoint(GeoId1, PosId1, arc->getStartPoint(/*emulateCCW=*/true),false,true); movePoint(GeoId2, PosId2, arc->getEndPoint(/*emulateCCW=*/true),false,true); } else { - tangent1->SecondPos = end; - tangent2->SecondPos = start; + tangent1.SecondPos = end; + tangent2.SecondPos = start; movePoint(GeoId1, PosId1, arc->getEndPoint(/*emulateCCW=*/true),false,true); movePoint(GeoId2, PosId2, arc->getStartPoint(/*emulateCCW=*/true),false,true); } - addConstraint(tangent1); - addConstraint(tangent2); - delete tangent1; - delete tangent2; + addConstraint(&tangent1); + addConstraint(&tangent2); } - delete arc; if (noRecomputes) // if we do not have a recompute after the geometry creation, the sketch must be solved to update the DoF of the solver solve(); diff --git a/src/Mod/Sketcher/App/SketchObject.h b/src/Mod/Sketcher/App/SketchObject.h index aeae1bd9a8..0a5957f495 100644 --- a/src/Mod/Sketcher/App/SketchObject.h +++ b/src/Mod/Sketcher/App/SketchObject.h @@ -64,6 +64,14 @@ public: ~SketchObject(); /// Property + /** + The Geometry list contains the non-external Part::Geometry objects in the sketch. The list + may be accessed directly, or indirectly via getInternalGeometry(). + + Many of the methods in this class take geoId and posId parameters. A GeoId is a unique identifier for + geometry in the Sketch. geoId >= 0 means an index in the Geometry list. geoId < 0 refers to sketch + axes and external geometry. posId is a PointPos enum, documented in Constraint.h. + */ Part ::PropertyGeometryList Geometry; Sketcher::PropertyConstraintList Constraints; App ::PropertyLinkSubList ExternalGeometry; @@ -97,9 +105,19 @@ public: \retval bool - true if the geometry is supported */ bool isSupportedGeometry(const Part::Geometry *geo) const; - /// add unspecified geometry + /*! + \brief Add geometry to a sketch + \param geo - geometry to add + \param construction - true for construction lines + \retval int - GeoId of added element + */ int addGeometry(const Part::Geometry *geo, bool construction=false); - /// add unspecified geometry + /*! + \brief Add multiple geometry elements to a sketch + \param geoList - geometry to add + \param construction - true for construction lines + \retval int - GeoId of last added element + */ int addGeometry(const std::vector &geoList, bool construction=false); /*! \brief Deletes indicated geometry (by geoid). @@ -218,11 +236,28 @@ public: int toggleConstruction(int GeoId); int setConstruction(int GeoId, bool on); - /// create a fillet - int fillet(int geoId, PointPos pos, double radius, bool trim=true); + /*! + \brief Create a sketch fillet from the point at the intersection of two lines + \param geoId, pos - one of the (exactly) two coincident endpoints + \param radius - fillet radius + \param trim - if false, leaves the original lines untouched + \param createCorner - keep geoId/pos as a Point and keep as many constraints as possible + \retval - 0 on success, -1 on failure + */ + int fillet(int geoId, PointPos pos, double radius, bool trim=true, bool preserveCorner=false); + /*! + \brief More general form of fillet + \param geoId1, geoId2 - geoId for two lines (which don't necessarily have to coincide) + \param refPnt1, refPnt2 - reference points on the input geometry, used to influence the free fillet variables + \param radius - fillet radius + \param trim - if false, leaves the original lines untouched + \param preserveCorner - if the lines are coincident, place a Point where they meet and keep as many + of the existing constraints as possible + \retval - 0 on success, -1 on failure + */ int fillet(int geoId1, int geoId2, const Base::Vector3d& refPnt1, const Base::Vector3d& refPnt2, - double radius, bool trim=true); + double radius, bool trim=true, bool createCorner=false); /// trim a curve int trim(int geoId, const Base::Vector3d& point); @@ -479,6 +514,19 @@ protected: std::vector supportedGeometry(const std::vector &geoList) const; + /*! + \brief Transfer constraints on lines being filleted. + + Since filleting moves the endpoints of the input geometry, existing constraints may no longer be + sensible. If fillet() was called with preserveCorner=false, the constraints are simply deleted. + But if the lines are coincident and preserveCorner=true, we can preserve most constraints on the + old end points by moving them to the preserved corner, or transforming distance constraints on + straight lines into point-to-point distance constraints. + + \param geoId1, podId1, geoId2, posId2 - The two lines that have just been filleted + */ + void transferFilletConstraints(int geoId1, PointPos posId1, int geoId2, PointPos posId2); + // refactoring functions // check whether constraint may be changed driving status int testDrivingChange(int ConstrId, bool isdriving); From f6f43b6fe4e8a1621d7b4cc767b89be2b9675369 Mon Sep 17 00:00:00 2001 From: j <77419450+emmanuelobrien@users.noreply.github.com> Date: Wed, 3 Feb 2021 15:12:40 +0100 Subject: [PATCH 132/168] Sketcher: Constraint documentation --- src/Mod/Sketcher/App/Constraint.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Mod/Sketcher/App/Constraint.h b/src/Mod/Sketcher/App/Constraint.h index 7a3316b08a..6e526030cc 100644 --- a/src/Mod/Sketcher/App/Constraint.h +++ b/src/Mod/Sketcher/App/Constraint.h @@ -75,7 +75,13 @@ enum InternalAlignmentType { BSplineKnotPoint = 10, }; -/// define if you want to use the end or start point +/*! PointPos lets us refer to different aspects of a piece of geometry. sketcher::none refers + * to an edge itself (eg., for a Perpendicular constraint on two lines). sketcher::start and + * sketcher::end denote the endpoints of lines or bounded curves. sketcher::mid denotes + * geometries with geometrical centers (eg., circle, ellipse). Bare points use 'start'. More + * complex geometries like parabola focus or b-spline knots use InternalAlignment constraints + * in addition to PointPos. + */ enum PointPos { none, start, end, mid }; class SketcherExport Constraint : public Base::Persistence From 46feeb11f671ea9ec2db63a209fd4d0434ea10a8 Mon Sep 17 00:00:00 2001 From: j <77419450+emmanuelobrien@users.noreply.github.com> Date: Wed, 3 Feb 2021 15:14:14 +0100 Subject: [PATCH 133/168] Sketch: new fillet Python support --- src/Mod/Sketcher/App/SketchObjectPyImp.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Mod/Sketcher/App/SketchObjectPyImp.cpp b/src/Mod/Sketcher/App/SketchObjectPyImp.cpp index 2d6cb7f481..315af5a79c 100644 --- a/src/Mod/Sketcher/App/SketchObjectPyImp.cpp +++ b/src/Mod/Sketcher/App/SketchObjectPyImp.cpp @@ -1043,16 +1043,21 @@ PyObject* SketchObjectPy::getAxis(PyObject *args) PyObject* SketchObjectPy::fillet(PyObject *args) { PyObject *pcObj1, *pcObj2; - int geoId1, geoId2, posId1, trim=1; + int geoId1, geoId2, posId1; + int trim=true; + PyObject* createCorner = Py_False; double radius; // Two Lines, radius - if (PyArg_ParseTuple(args, "iiO!O!d|i", &geoId1, &geoId2, &(Base::VectorPy::Type), &pcObj1, &(Base::VectorPy::Type), &pcObj2, &radius, &trim)) { + if (PyArg_ParseTuple(args, "iiO!O!d|iO!", &geoId1, &geoId2, &(Base::VectorPy::Type), &pcObj1, &(Base::VectorPy::Type), &pcObj2, + &radius, &trim, &PyBool_Type, &createCorner)) { + // The i for &trim should probably have been a bool like &createCorner, but we'll leave it an int for backward + // compatibility (and because python will accept a bool there anyway) Base::Vector3d v1 = static_cast(pcObj1)->value(); Base::Vector3d v2 = static_cast(pcObj2)->value(); - if (this->getSketchObjectPtr()->fillet(geoId1, geoId2, v1, v2, radius, trim?true:false)) { + if (this->getSketchObjectPtr()->fillet(geoId1, geoId2, v1, v2, radius, trim, PyObject_IsTrue(createCorner))) { std::stringstream str; str << "Not able to fillet curves with ids : (" << geoId1 << ", " << geoId2 << ") and points (" << v1.x << ", " << v1.y << ", " << v1.z << ") & " << "(" << v2.x << ", " << v2.y << ", " << v2.z << ")"; @@ -1064,8 +1069,9 @@ PyObject* SketchObjectPy::fillet(PyObject *args) PyErr_Clear(); // Point, radius - if (PyArg_ParseTuple(args, "iid|i", &geoId1, &posId1, &radius, &trim)) { - if (this->getSketchObjectPtr()->fillet(geoId1, (Sketcher::PointPos) posId1, radius, trim?true:false)) { + if (PyArg_ParseTuple(args, "iid|iO!", &geoId1, &posId1, &radius, &trim, &PyBool_Type, &createCorner)) { + if (this->getSketchObjectPtr()->fillet(geoId1, (Sketcher::PointPos) posId1, radius, trim, + PyObject_IsTrue(createCorner))) { std::stringstream str; str << "Not able to fillet point with ( geoId: " << geoId1 << ", PointPos: " << posId1 << " )"; PyErr_SetString(PyExc_ValueError, str.str().c_str()); @@ -1075,8 +1081,8 @@ PyObject* SketchObjectPy::fillet(PyObject *args) } PyErr_SetString(PyExc_TypeError, "fillet() method accepts:\n" - "-- int,int,Vector,Vector,float,[int]\n" - "-- int,int,float,[int]\n"); + "-- int,int,Vector,Vector,float,[bool],[bool]\n" + "-- int,int,float,[bool],[bool]\n"); return 0; } From 9fc31dfa22cacaaedb7ac10ef7ac478ca625e534 Mon Sep 17 00:00:00 2001 From: j <77419450+emmanuelobrien@users.noreply.github.com> Date: Wed, 3 Feb 2021 15:15:46 +0100 Subject: [PATCH 134/168] Sketcher: new Fillet - icon --- src/Mod/Sketcher/Gui/Resources/Sketcher.qrc | 3 +- .../geometry/Sketcher_CreatePointFillet.svg | 342 ++++++++++++++++++ 2 files changed, 344 insertions(+), 1 deletion(-) create mode 100644 src/Mod/Sketcher/Gui/Resources/icons/geometry/Sketcher_CreatePointFillet.svg diff --git a/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc b/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc index 6ef59d52a5..4dcefcaaf7 100644 --- a/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc +++ b/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc @@ -128,6 +128,7 @@ icons/geometry/Sketcher_CreateElliptical_Arc.svg icons/geometry/Sketcher_CreateElliptical_Arc_Constr.svg icons/geometry/Sketcher_CreateFillet.svg + icons/geometry/Sketcher_CreatePointFillet.svg icons/geometry/Sketcher_CreateHeptagon.svg icons/geometry/Sketcher_CreateHeptagon_Constr.svg icons/geometry/Sketcher_CreateHexagon.svg @@ -266,4 +267,4 @@ translations/Sketcher_zh-CN.qm translations/Sketcher_zh-TW.qm - \ No newline at end of file + diff --git a/src/Mod/Sketcher/Gui/Resources/icons/geometry/Sketcher_CreatePointFillet.svg b/src/Mod/Sketcher/Gui/Resources/icons/geometry/Sketcher_CreatePointFillet.svg new file mode 100644 index 0000000000..84e0baa261 --- /dev/null +++ b/src/Mod/Sketcher/Gui/Resources/icons/geometry/Sketcher_CreatePointFillet.svg @@ -0,0 +1,342 @@ + + + Sketcher_PointFillet + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Sketcher_PointFillet + + + [bitacovir] + + + Sketcher_CreateFillet + 31-01-2021 + + + + FreeCAD + + + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + Based on a wmayer's design and [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 7b9d9fdcdb983e76cd8b0dfb471573cda0a96137 Mon Sep 17 00:00:00 2001 From: j <77419450+emmanuelobrien@users.noreply.github.com> Date: Wed, 3 Feb 2021 15:25:01 +0100 Subject: [PATCH 135/168] Sketcher: new fillet UI command --- src/Mod/Sketcher/Gui/CommandCreateGeo.cpp | 153 +++++++++++++++++++- src/Mod/Sketcher/Gui/ViewProviderSketch.cpp | 1 + src/Mod/Sketcher/Gui/Workbench.cpp | 23 ++- 3 files changed, 168 insertions(+), 9 deletions(-) diff --git a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp index d53caf646a..a491efc692 100644 --- a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp +++ b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp @@ -5016,7 +5016,6 @@ bool CmdSketcherCreateDraftLine::isActive(void) return false; } - // ====================================================================================== namespace SketcherGui { @@ -5064,11 +5063,17 @@ namespace SketcherGui { class DrawSketchHandlerFillet: public DrawSketchHandler { public: - DrawSketchHandlerFillet() : Mode(STATUS_SEEK_First), firstCurve(0) {} + enum FilletType { + SimpleFillet, + ConstraintPreservingFillet + }; + + DrawSketchHandlerFillet(FilletType filletType) : filletType(filletType), Mode(STATUS_SEEK_First), firstCurve(0) {} virtual ~DrawSketchHandlerFillet() { Gui::Selection().rmvSelectionGate(); } + enum SelectMode{ STATUS_SEEK_First, STATUS_SEEK_Second @@ -5135,8 +5140,10 @@ public: int currentgeoid= getHighestCurveIndex(); // create fillet at point try { + bool pointFillet = (filletType == 1); Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Create fillet")); - Gui::cmdAppObjectArgs(sketchgui->getObject(), "fillet(%d,%d,%f)", GeoId, PosId, radius); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "fillet(%d,%d,%f,%s,%s)", GeoId, PosId, radius, "True", + pointFillet ? "True":"False"); if (construction) { Gui::cmdAppObjectArgs(sketchgui->getObject(), "toggleConstruction(%d) ", currentgeoid+1); @@ -5211,11 +5218,13 @@ public: // create fillet between lines try { + bool pointFillet = (filletType == 1); Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Create fillet")); - Gui::cmdAppObjectArgs(sketchgui->getObject(), "fillet(%d,%d,App.Vector(%f,%f,0),App.Vector(%f,%f,0),%f)", + Gui::cmdAppObjectArgs(sketchgui->getObject(), "fillet(%d,%d,App.Vector(%f,%f,0),App.Vector(%f,%f,0),%f,%s,%s)", firstCurve, secondCurve, firstPos.x, firstPos.y, - secondPos.x, secondPos.y, radius); + secondPos.x, secondPos.y, radius, + "True", pointFillet ? "True":"False"); Gui::Command::commitCommand(); } catch (const Base::CADKernelError& e) { @@ -5256,6 +5265,7 @@ public: } protected: + int filletType; SelectMode Mode; int firstCurve; Base::Vector2d firstPos; @@ -5280,7 +5290,7 @@ CmdSketcherCreateFillet::CmdSketcherCreateFillet() void CmdSketcherCreateFillet::activated(int iMsg) { Q_UNUSED(iMsg); - ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerFillet()); + ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerFillet(DrawSketchHandlerFillet::SimpleFillet)); } bool CmdSketcherCreateFillet::isActive(void) @@ -5288,6 +5298,135 @@ bool CmdSketcherCreateFillet::isActive(void) return isCreateGeoActive(getActiveGuiDocument()); } +// ====================================================================================== + +DEF_STD_CMD_A(CmdSketcherCreatePointFillet) + +CmdSketcherCreatePointFillet::CmdSketcherCreatePointFillet() + : Command("Sketcher_CreatePointFillet") +{ + sAppModule = "Sketcher"; + sGroup = QT_TR_NOOP("Sketcher"); + sMenuText = QT_TR_NOOP("Create corner-preserving fillet"); + sToolTipText = QT_TR_NOOP("Fillet that preserves intersection point and most constraints"); + sWhatsThis = "Sketcher_CreatePointFillet"; + sStatusTip = sToolTipText; + sPixmap = "Sketcher_CreateFillet"; + sAccel = ""; + eType = ForEdit; +} + +void CmdSketcherCreatePointFillet::activated(int iMsg) +{ + Q_UNUSED(iMsg); + ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerFillet(DrawSketchHandlerFillet::ConstraintPreservingFillet)); +} + +bool CmdSketcherCreatePointFillet::isActive(void) +{ + return isCreateGeoActive(getActiveGuiDocument()); +} + +/// @brief Macro that declares a new sketcher command class 'CmdSketcherCompCreateFillets' +DEF_STD_CMD_ACLU(CmdSketcherCompCreateFillets) + +/** + * @brief ctor + */ +CmdSketcherCompCreateFillets::CmdSketcherCompCreateFillets() + : Command("Sketcher_CompCreateFillets") +{ + sAppModule = "Sketcher"; + sGroup = QT_TR_NOOP("Sketcher"); + sMenuText = QT_TR_NOOP("Fillets"); + sToolTipText = QT_TR_NOOP("Create a fillet between two lines"); + sWhatsThis = "Sketcher_CompCreateFillets"; + sStatusTip = sToolTipText; + eType = ForEdit; +} + +/** + * @brief Instantiates the fillet handler when the fillet command activated + * @param int iMsg + */ +void CmdSketcherCompCreateFillets::activated(int iMsg) +{ + if (iMsg == 0) { + ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerFillet(DrawSketchHandlerFillet::SimpleFillet)); + } else if (iMsg == 1) { + ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerFillet(DrawSketchHandlerFillet::ConstraintPreservingFillet)); + } else { + return; + } + + // Since the default icon is reset when enabling/disabling the command we have + // to explicitly set the icon of the used command. + Gui::ActionGroup* pcAction = qobject_cast(_pcAction); + QList a = pcAction->actions(); + + assert(iMsg < a.size()); + pcAction->setIcon(a[iMsg]->icon()); +} + +Gui::Action * CmdSketcherCompCreateFillets::createAction(void) +{ + Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow()); + pcAction->setDropDownMenu(true); + applyCommandData(this->className(), pcAction); + + QAction* oldFillet = pcAction->addAction(QString()); + oldFillet->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateFillet")); + + QAction* pointFillet = pcAction->addAction(QString()); + pointFillet->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreatePointFillet")); + + _pcAction = pcAction; + languageChange(); + + pcAction->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateFillet")); + int defaultId = 0; + pcAction->setProperty("defaultAction", QVariant(defaultId)); + + return pcAction; +} + +void CmdSketcherCompCreateFillets::updateAction(int mode) +{ + Q_UNUSED(mode); + Gui::ActionGroup* pcAction = qobject_cast(getAction()); + if (!pcAction) + return; + + QList a = pcAction->actions(); + int index = pcAction->property("defaultAction").toInt(); + a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateFillet")); + a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreatePointFillet")); + getAction()->setIcon(a[index]->icon()); +} + +void CmdSketcherCompCreateFillets::languageChange() +{ + Command::languageChange(); + + if (!_pcAction) + return; + Gui::ActionGroup* pcAction = qobject_cast(_pcAction); + QList a = pcAction->actions(); + + QAction* oldFillet = a[0]; + oldFillet->setText(QApplication::translate("CmdSketcherCompCreateFillets","Sketch fillet")); + oldFillet->setToolTip(QApplication::translate("Sketcher_CreateFillet","Creates a radius between two lines")); + oldFillet->setStatusTip(QApplication::translate("Sketcher_CreateFillet","Creates a radius between two lines")); + QAction* pointFillet = a[1]; + pointFillet->setText(QApplication::translate("CmdSketcherCompCreateFillets","Constraint-preserving sketch fillet")); + pointFillet->setToolTip(QApplication::translate("Sketcher_CreatePointFillet","Fillet that preserves constraints and intersection point")); + pointFillet->setStatusTip(QApplication::translate("Sketcher_CreatePointFillet","Fillet that preserves constraints and intersection point")); +} + +bool CmdSketcherCompCreateFillets::isActive(void) +{ + return isCreateGeoActive(getActiveGuiDocument()); +} // ====================================================================================== @@ -6874,7 +7013,9 @@ void CreateSketcherCommandsCreateGeo(void) rcCmdMgr.addCommand(new CmdSketcherCreateOctagon()); rcCmdMgr.addCommand(new CmdSketcherCreateRegularPolygon()); rcCmdMgr.addCommand(new CmdSketcherCreateSlot()); + rcCmdMgr.addCommand(new CmdSketcherCompCreateFillets()); rcCmdMgr.addCommand(new CmdSketcherCreateFillet()); + rcCmdMgr.addCommand(new CmdSketcherCreatePointFillet()); //rcCmdMgr.addCommand(new CmdSketcherCreateText()); //rcCmdMgr.addCommand(new CmdSketcherCreateDraftLine()); rcCmdMgr.addCommand(new CmdSketcherTrimming()); diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index 098a62005c..72a70c40d9 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -986,6 +986,7 @@ bool ViewProviderSketch::mouseButtonPressed(int Button, bool pressed, const SbVe << "Sketcher_CreateRectangle" << "Sketcher_CreateHexagon" << "Sketcher_CreateFillet" + << "Sketcher_CreatePointFillet" << "Sketcher_Trimming" << "Sketcher_Extend" << "Sketcher_External" diff --git a/src/Mod/Sketcher/Gui/Workbench.cpp b/src/Mod/Sketcher/Gui/Workbench.cpp index 104784ee24..748c423323 100644 --- a/src/Mod/Sketcher/Gui/Workbench.cpp +++ b/src/Mod/Sketcher/Gui/Workbench.cpp @@ -232,6 +232,23 @@ inline void SketcherAddWorkspaceRegularPolygon(Gui::ToolBarIte geom << "Sketcher_CompCreateRegularPolygon"; } + +template +void SketcherAddWorkspaceFillets(T& geom); + +template <> +inline void SketcherAddWorkspaceFillets(Gui::MenuItem& geom) +{ + geom << "Sketcher_CreateFillet" + << "Sketcher_CreatePointFillet"; +} + +template <> +inline void SketcherAddWorkspaceFillets(Gui::ToolBarItem& geom) +{ + geom << "Sketcher_CompCreateFillets"; +} + template inline void SketcherAddWorkbenchGeometries(T& geom) { @@ -243,9 +260,9 @@ inline void SketcherAddWorkbenchGeometries(T& geom) << "Sketcher_CreateRectangle"; SketcherAddWorkspaceRegularPolygon(geom); geom << "Sketcher_CreateSlot" - << "Separator" - << "Sketcher_CreateFillet" - << "Sketcher_Trimming" + << "Separator"; + SketcherAddWorkspaceFillets(geom); + geom << "Sketcher_Trimming" << "Sketcher_Extend" << "Sketcher_External" << "Sketcher_CarbonCopy" From 6db7737547036ea129c6172ff18ab8426e2493de Mon Sep 17 00:00:00 2001 From: j <77419450+emmanuelobrien@users.noreply.github.com> Date: Wed, 3 Feb 2021 15:30:39 +0100 Subject: [PATCH 136/168] Sketcher: Group tests in different directories --- src/Mod/Sketcher/CMakeLists.txt | 16 +- .../SketcherTests/TestSketchFillet.py | 292 ++++++++++++++++++ .../SketcherTests/TestSketcherSolver.py | 216 +++++++++++++ src/Mod/Sketcher/SketcherTests/__init__.py | 0 src/Mod/Sketcher/TestSketcherApp.py | 203 +----------- 5 files changed, 532 insertions(+), 195 deletions(-) create mode 100644 src/Mod/Sketcher/SketcherTests/TestSketchFillet.py create mode 100644 src/Mod/Sketcher/SketcherTests/TestSketcherSolver.py create mode 100644 src/Mod/Sketcher/SketcherTests/__init__.py diff --git a/src/Mod/Sketcher/CMakeLists.txt b/src/Mod/Sketcher/CMakeLists.txt index 4be9472e06..740b3ff9ec 100644 --- a/src/Mod/Sketcher/CMakeLists.txt +++ b/src/Mod/Sketcher/CMakeLists.txt @@ -11,6 +11,12 @@ set(Sketcher_Scripts Profiles.py ) +set(Sketcher_TestScripts + SketcherTests/__init__.py + SketcherTests/TestSketchFillet.py + SketcherTests/TestSketcherSolver.py +) + if(BUILD_GUI) list (APPEND Sketcher_Scripts InitGui.py @@ -25,7 +31,7 @@ set(Sketcher_Profiles ) add_custom_target(SketcherScripts ALL - SOURCES ${Sketcher_Scripts} ${Sketcher_Profiles} + SOURCES ${Sketcher_Scripts} ${Sketcher_Profiles} ${Sketcher_TestScripts} ) fc_target_copy_resource(SketcherScripts @@ -33,6 +39,7 @@ fc_target_copy_resource(SketcherScripts ${CMAKE_BINARY_DIR}/Mod/Sketcher ${Sketcher_Scripts} ${Sketcher_Profiles} + ${Sketcher_TestScripts} ) INSTALL( @@ -48,3 +55,10 @@ INSTALL( DESTINATION Mod/Sketcher/ProfileLib ) + +INSTALL( + FILES + ${Sketcher_TestScripts} + DESTINATION + Mod/Sketcher/SketcherTests + ) diff --git a/src/Mod/Sketcher/SketcherTests/TestSketchFillet.py b/src/Mod/Sketcher/SketcherTests/TestSketchFillet.py new file mode 100644 index 0000000000..2c076613dc --- /dev/null +++ b/src/Mod/Sketcher/SketcherTests/TestSketchFillet.py @@ -0,0 +1,292 @@ +# (c) Emmanuel O'Brien 2021 LGPL * +# * +# 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 Library 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 * +#************************************************************************** + + +import FreeCAD, math, os, sys, unittest, Part, Sketcher +from .TestSketcherSolver import CreateRectangleSketch +App = FreeCAD + +def VShape(SketchFeature): + # Simple inverted V shape + SketchFeature.addGeometry(Part.LineSegment(App.Vector(0,0,0),App.Vector(1,1,0))) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',0,1,-1,1)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',0,1,0,2,1)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',0,1,0,2,1)) + SketchFeature.addGeometry(Part.LineSegment(App.Vector(1,1,0),App.Vector(2,0,0))) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',1,1,0,2)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',1,1,1,2,1)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',1,1,1,2,-1)) + SketchFeature.fillet(0, 2, 0.25, True, True) + +def BoxCircle(SketchFeature): + # Square with a circle centered at the upper right corner + top_edge = int(SketchFeature.GeometryCount) + right_edge = top_edge + 1 + CreateRectangleSketch(SketchFeature, [0, 0], [2, 2]) + top_midpoint = App.Vector(1, 2, 0) + right_midpoint = App.Vector(2, 1, 0) + circle = int(SketchFeature.GeometryCount) + SketchFeature.addGeometry(Part.Circle(App.Vector(0,0,0), App.Vector(0,0,1), 1),False) + SketchFeature.addConstraint(Sketcher.Constraint('Radius',circle,1)) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',top_edge,2,circle,3)) + # Since the circle center is coincident with the corner, there are three coincident points + # and thus the simpler fillet() call would get confused. Instead we'll need to point at the two + # lines and their midpoints + SketchFeature.fillet(top_edge, right_edge, top_midpoint, right_midpoint, 0.25, True, True) + +def PointOnObject(SketchFeature): + # Square with the upper right corner touching the edge of a circle + top_edge = int(SketchFeature.GeometryCount) + right_edge = top_edge + 1 + CreateRectangleSketch(SketchFeature, [0, 0], [2, 2]) + circle = int(SketchFeature.GeometryCount) + SketchFeature.addGeometry(Part.Circle(App.Vector(12,3,0), App.Vector(0,0,1), 1),False) + SketchFeature.addConstraint(Sketcher.Constraint('Radius',circle,1)) + SketchFeature.addConstraint(Sketcher.Constraint('PointOnObject',top_edge,2,circle)) + SketchFeature.fillet(top_edge, 2, 0.25, True, True) + +class TestSketchFillet(unittest.TestCase): + def setUp(self): + self.Doc = FreeCAD.newDocument("TestSketchFillet") + + def testBasicFillet(self): + SketchFeature = self.Doc.addObject('Sketcher::SketchObject', 'BasicFillet') + CreateRectangleSketch(SketchFeature, [0, 0], [2, 2]) + SketchFeature.fillet(0,2, 0.25, True, True) + self.assertAlmostEqual(SketchFeature.Geometry[4].Radius, 0.25) + + # Fillets can be made even between unconnected lines + def testUnconnected(self): + SketchFeature = self.Doc.addObject('Sketcher::SketchObject', 'Unconnected') + # Inverted open V + SketchFeature.addGeometry(Part.LineSegment(App.Vector(0,0,0),App.Vector(1,1,0))) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',0,1,-1,1)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',0,1,0,2,1)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',0,1,0,2,1)) + SketchFeature.addGeometry(Part.LineSegment(App.Vector(2.1,1,0),App.Vector(3,0,0))) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',1,1, 1,2, 0.9)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',1,1, 1,2, -1)) + + SketchFeature.addGeometry(Part.LineSegment(App.Vector(3,3,0),App.Vector(5,3,0))) + SketchFeature.addConstraint(Sketcher.Constraint('Equal',0,2)) + self.Doc.recompute() + self.assertAlmostEqual(SketchFeature.Geometry[2].length(), math.sqrt(2)) + + SketchFeature.fillet(0,1, App.Vector(0.5,0.5,0), App.Vector(2.55,0.5,0), 0.25, True, True) + # Make sure a fillet was created + self.assertAlmostEqual(SketchFeature.Geometry[3].Radius, 0.25) + + self.Doc.recompute() + # Third line's length shouldn't have changed + self.assertAlmostEqual(SketchFeature.Geometry[2].length(), math.sqrt(2)) + # First line should be shorter + self.assertNotAlmostEqual(SketchFeature.Geometry[0].length(), math.sqrt(2)) + + # Curved lines can also be filleted + def testCurve(self): + SketchFeature = self.Doc.addObject('Sketcher::SketchObject', 'Curve') + SketchFeature.addGeometry(Part.LineSegment(App.Vector(0,0,0),App.Vector(1,1,0))) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',0,1,-1,1)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',0,1,0,2,1)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',0,1,0,2,1)) + arc = Part.ArcOfCircle(Part.Circle(App.Vector(3,0,0),App.Vector(0,0,1),3),0,-1) + SketchFeature.addGeometry(arc) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',1,3, -1,1, -3)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',1,3, -1,1, 0)) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',1,1, 0,2)) + self.Doc.recompute() + + SketchFeature.fillet(0,1, App.Vector(0.5,0.5,0), App.Vector(0.8,0.3,0), 0.25, True, True) + self.assertAlmostEqual(SketchFeature.Geometry[2].Radius, 0.25) + + def testUnconnectedCurve(self): + SketchFeature = self.Doc.addObject('Sketcher::SketchObject', 'UnconnectedCurve') + SketchFeature.addGeometry(Part.LineSegment(App.Vector(0,0,0),App.Vector(1,1,0))) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',0,1,-1,1)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',0,1,0,2,1)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',0,1,0,2,1)) + arc = Part.ArcOfCircle(Part.Circle(App.Vector(3,1,0),App.Vector(0,0,1), 1.75), -3.14, -2.17) + SketchFeature.addGeometry(arc) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',-1,1, 1,3, 3.0)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',-1,1, 1,3, 1.0)) + SketchFeature.addConstraint(Sketcher.Constraint('Distance',0,2, 1,1, 0.25)) + self.Doc.recompute() + + #SketchFeature.fillet(0,1, App.Vector(0.75,0.75,0), App.Vector(1.22,0.66,0), 0.25, True, True) + # Make sure the fillet happened + #self.Doc.recompute() + #self.assertAlmostEqual(SketchFeature.Geometry[2].Radius, 0.25) + + # The following tests are mostly about verifying that transferFilletConstraints + # does the right thing with pre-existing constraints when a fillet is created. + + # Make sure the original corner is preserved when filleting + def testOriginalCorner(self): + SketchFeature = self.Doc.addObject('Sketcher::SketchObject', 'OriginalCorner') + VShape(SketchFeature) + self.Doc.recompute() + # If fillet() ever gets refactored such that the corner gets added after + # the arc, then getPoint(3,1) might break. A more general approach would + # be to iterate over the geometry list and find the bare vertex. + self.assertAlmostEqual(App.Vector(1,1,0), SketchFeature.getPoint(3,1)) + + # Make sure coincident constraints get moved to the old corner location + def testCoincident(self): + SketchFeature = self.Doc.addObject('Sketcher::SketchObject', 'Coincident') + BoxCircle(SketchFeature) + self.Doc.recompute() + # Make sure the circle center is still at the old corner + self.assertAlmostEqual(App.Vector(2,2,0).distanceToPoint(SketchFeature.getPoint(4,3)), 0.0) + + # Point-to-point horizontal and vertical constraints should also move to the old corner + def testHorizontalVertical(self): + SketchFeature = self.Doc.addObject('Sketcher::SketchObject', 'HorizontalVertical') + # Inverted V + SketchFeature.addGeometry(Part.LineSegment(App.Vector(0,0,0),App.Vector(1,1,0))) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',0,1,-1,1)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',0,1,0,2,1)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',0,1,0,2,1)) + SketchFeature.addGeometry(Part.LineSegment(App.Vector(1,1,0),App.Vector(2,0,0))) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',1,1,0,2)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',1,1,1,2,1)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',1,1,1,2,-1)) + + SketchFeature.addGeometry(Part.Point(App.Vector(2,1))) + SketchFeature.addConstraint(Sketcher.Constraint('Horizontal',0,2, 2,1)) + SketchFeature.addGeometry(Part.Point(App.Vector(1,2))) + SketchFeature.addConstraint(Sketcher.Constraint('Vertical',1,1, 3,1)) + + SketchFeature.fillet(0, 2, 0.25, True, True) + + # Verify the constraint moved to the original corner + found_horizontal = False + found_vertical = False + for c in SketchFeature.Constraints: + if c.Type == 'Horizontal' and c.First == 5 and c.FirstPos == 1 and \ + c.Second == 2 and c.SecondPos == 1: + found_horizontal = True + elif c.Type == 'Vertical' and c.First == 5 and c.FirstPos == 1 and \ + c.Second == 3 and c.SecondPos == 1: + found_vertical = True + + self.assertTrue(found_horizontal) + self.assertTrue(found_vertical) + + # Distance constraints to the old corner point should be preserved + def testDistance(self): + SketchFeature = self.Doc.addObject('Sketcher::SketchObject', 'Distance') + # We'll end up implicitly testing line length constraints as well since that's + # what CreateRectangleSketch uses to enforce side length. If the side length doesn't + # switch to a point-to-point distance constraint with the original corner as expected, + # the point won't end up at its expected destination. + CreateRectangleSketch(SketchFeature, [0, 0], [2, 2]) + + point = int(SketchFeature.GeometryCount) + SketchFeature.addGeometry(Part.Point(App.Vector(3,2,0))) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',0,2, point,1, 1)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',0,2, point,1, 0)) + + self.Doc.recompute() + self.assertTrue(SketchFeature.FullyConstrained) + SketchFeature.fillet(0, 2, 0.25, True, True) + SketchFeature.addConstraint(Sketcher.Constraint('Radius',5,0.25)) + self.Doc.recompute() + # If any constraints disappeared then we won't be fully constrained + self.assertTrue(SketchFeature.FullyConstrained) + # Make sure the point is to the right of the original corner as expected + self.assertAlmostEqual(SketchFeature.getPoint(point,1), App.Vector(3, 2, 0)) + + # Make sure point on object constraints get moved to the old corner location + def testPointOnObject(self): + SketchFeature = self.Doc.addObject('Sketcher::SketchObject', 'PointOnObject') + PointOnObject(SketchFeature) + self.Doc.recompute() + # Make sure the circle center is one radius away from the old corner + self.assertAlmostEqual(App.Vector(2,2,0).distanceToPoint(SketchFeature.getPoint(4,3)), 1.0) + + # Make sure colinearity doesn't get dropped + def testTangent(self): + SketchFeature = self.Doc.addObject('Sketcher::SketchObject', 'Tangent') + + # Setup the geometry + first_line = int(SketchFeature.GeometryCount) + SketchFeature.addGeometry(Part.LineSegment(App.Vector(0,0,0),App.Vector(1,1,0))) + # Anchor at the origin + SketchFeature.addConstraint(Sketcher.Constraint('Coincident', 0, 1, -1, 1)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',first_line,2,-1,1,-1)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',first_line,2,-1,1,-1)) + + second_line = int(SketchFeature.GeometryCount) + SketchFeature.addGeometry(Part.LineSegment(App.Vector(1,1,0),App.Vector(2,0,0))) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',second_line,2,-1,1,-2)) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',first_line,2,second_line,1)) + tangent_line = int(SketchFeature.GeometryCount) + SketchFeature.addGeometry(Part.LineSegment(App.Vector(2,2,0), App.Vector(3,3,0))) + SketchFeature.addConstraint(Sketcher.Constraint('Tangent', tangent_line, first_line)) + SketchFeature.addConstraint(Sketcher.Constraint('Distance', tangent_line, 1.41421356237)) + SketchFeature.fillet(first_line, 2, 0.25, True, True) + + self.Doc.recompute() + # Move the tangent line and see if it's aimed right + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',tangent_line,1,-1,1,-4)) + self.Doc.recompute() + + # The first endpoint should now be at 4,4 + self.assertAlmostEqual(App.Vector(4,4,0).distanceToPoint(SketchFeature.getPoint(tangent_line, 1)), 0.0) + + # We expect the other end of the tangent line to be at 5,5, but I think 3,3 also satisfies + # the colinearity constraint + try: + self.assertAlmostEqual(App.Vector(3,3,0).distanceToPoint(SketchFeature.getPoint(tangent_line, 2)), 0.0) + except AssertionError: + self.assertAlmostEqual(App.Vector(5,5,0).distanceToPoint(SketchFeature.getPoint(tangent_line, 2)), 0.0) + + def testSymmetric(self): + SketchFeature = self.Doc.addObject('Sketcher::SketchObject', 'Symmetric') + # Inverted V + SketchFeature.addGeometry(Part.LineSegment(App.Vector(0,0,0),App.Vector(1,1,0))) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',0,1,-1,1)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',0,1,0,2,1)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',0,1,0,2,1)) + SketchFeature.addGeometry(Part.LineSegment(App.Vector(1,1,0),App.Vector(2,0,0))) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',1,1,0,2)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',1,1,1,2,1)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',1,1,1,2,-1)) + + # Mirror point + SketchFeature.addGeometry(Part.Point(App.Vector(3,2,0))) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',-1,1, 2,1, 3)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',-1,1, 2,1, 2)) + + # Point that will mirror the apex of the V + SketchFeature.addGeometry(Part.Point(App.Vector(4,2,0))) + + SketchFeature.addConstraint(Sketcher.Constraint('Symmetric',0,2, 3,1, 2,1)) + self.Doc.recompute() + + SketchFeature.fillet(0, 2, 0.25, True, True) + self.Doc.recompute() + self.assertAlmostEqual(App.Vector(5,3,0).distanceToPoint(SketchFeature.getPoint(3, 1)), 0.0) + + def tearDown(self): + # comment out to omit closing document for debugging + FreeCAD.closeDocument("TestSketchFillet") + pass diff --git a/src/Mod/Sketcher/SketcherTests/TestSketcherSolver.py b/src/Mod/Sketcher/SketcherTests/TestSketcherSolver.py new file mode 100644 index 0000000000..90a0e42cf0 --- /dev/null +++ b/src/Mod/Sketcher/SketcherTests/TestSketcherSolver.py @@ -0,0 +1,216 @@ +# (c) Juergen Riegel (FreeCAD@juergen-riegel.net) 2011 LGPL * +# * +# 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 Library 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 * +#************************************************************************** + + +import FreeCAD, os, sys, unittest, Part, Sketcher +App = FreeCAD + +def CreateRectangleSketch(SketchFeature, corner, lengths): + hmin, hmax = corner[0], corner[0] + lengths[0] + vmin, vmax = corner[1], corner[1] + lengths[1] + + # add the geometry and grab the count offset + i = int(SketchFeature.GeometryCount) + SketchFeature.addGeometry(Part.LineSegment(FreeCAD.Vector(hmin,vmax),FreeCAD.Vector(hmax,vmax,0))) + SketchFeature.addGeometry(Part.LineSegment(FreeCAD.Vector(hmax,vmax,0),FreeCAD.Vector(hmax,vmin,0))) + SketchFeature.addGeometry(Part.LineSegment(FreeCAD.Vector(hmax,vmin,0),FreeCAD.Vector(hmin,vmin,0))) + SketchFeature.addGeometry(Part.LineSegment(FreeCAD.Vector(hmin,vmin,0),FreeCAD.Vector(hmin,vmax,0))) + + # add the rectangular constraints + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',i+0,2,i+1,1)) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',i+1,2,i+2,1)) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',i+2,2,i+3,1)) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',i+3,2,i+0,1)) + SketchFeature.addConstraint(Sketcher.Constraint('Horizontal',i+0)) + SketchFeature.addConstraint(Sketcher.Constraint('Horizontal',i+2)) + SketchFeature.addConstraint(Sketcher.Constraint('Vertical',i+1)) + SketchFeature.addConstraint(Sketcher.Constraint('Vertical',i+3)) + + # Fix the bottom left corner of the rectangle + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',i+2,2,corner[0])) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',i+2,2,corner[1])) + + # add dimensions + if lengths[0] == lengths[1]: + SketchFeature.addConstraint(Sketcher.Constraint('Equal',i+2,i+3)) + SketchFeature.addConstraint(Sketcher.Constraint('Distance',i+0,hmax-hmin)) + else: + SketchFeature.addConstraint(Sketcher.Constraint('Distance',i+1,vmax-vmin)) + SketchFeature.addConstraint(Sketcher.Constraint('Distance',i+0,hmax-hmin)) + +def CreateCircleSketch(SketchFeature, center, radius): + i = int(SketchFeature.GeometryCount) + SketchFeature.addGeometry(Part.Circle(App.Vector(*center), App.Vector(0,0,1), radius),False) + SketchFeature.addConstraint(Sketcher.Constraint('Radius',i,radius)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',i,3,center[0])) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',i,3,center[1])) + +def CreateBoxSketchSet(SketchFeature): + SketchFeature.addGeometry(Part.LineSegment(FreeCAD.Vector(-99.230339,36.960674,0),FreeCAD.Vector(69.432587,36.960674,0))) + SketchFeature.addGeometry(Part.LineSegment(FreeCAD.Vector(69.432587,36.960674,0),FreeCAD.Vector(69.432587,-53.196629,0))) + SketchFeature.addGeometry(Part.LineSegment(FreeCAD.Vector(69.432587,-53.196629,0),FreeCAD.Vector(-99.230339,-53.196629,0))) + SketchFeature.addGeometry(Part.LineSegment(FreeCAD.Vector(-99.230339,-53.196629,0),FreeCAD.Vector(-99.230339,36.960674,0))) + # add the constraints + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',0,2,1,1)) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',1,2,2,1)) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',2,2,3,1)) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',3,2,0,1)) + SketchFeature.addConstraint(Sketcher.Constraint('Horizontal',0)) + SketchFeature.addConstraint(Sketcher.Constraint('Horizontal',2)) + SketchFeature.addConstraint(Sketcher.Constraint('Vertical',1)) + SketchFeature.addConstraint(Sketcher.Constraint('Vertical',3)) + # add dimensions + SketchFeature.addConstraint(Sketcher.Constraint('Distance',1,81.370787)) + SketchFeature.addConstraint(Sketcher.Constraint('Distance',0,187.573036)) + +def CreateSlotPlateSet(SketchFeature): + SketchFeature.addGeometry(Part.LineSegment(App.Vector(60.029362,-30.279360,0),App.Vector(-120.376335,-30.279360,0))) + SketchFeature.addConstraint(Sketcher.Constraint('Horizontal',0)) + SketchFeature.addGeometry(Part.LineSegment(App.Vector(-120.376335,-30.279360,0),App.Vector(-70.193062,38.113884,0))) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',0,2,1,1)) + SketchFeature.addGeometry(Part.LineSegment(App.Vector(-70.193062,38.113884,0),App.Vector(60.241116,37.478645,0))) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',1,2,2,1)) + SketchFeature.addConstraint(Sketcher.Constraint('Horizontal',2)) + SketchFeature.addGeometry(Part.ArcOfCircle(Part.Circle(App.Vector(60.039921,3.811391,0),App.Vector(0,0,1),35.127132),-1.403763,1.419522)) + SketchFeature.addConstraint(Sketcher.Constraint('Tangent',2,2,3,2)) + SketchFeature.addConstraint(Sketcher.Constraint('Tangent',0,1,3,1)) + SketchFeature.addConstraint(Sketcher.Constraint('Angle',0,2,1,1,0.947837)) + SketchFeature.addConstraint(Sketcher.Constraint('Distance',0,184.127425)) + SketchFeature.setDatum(7,200.000000) + SketchFeature.addConstraint(Sketcher.Constraint('Radius',3,38.424808)) + SketchFeature.setDatum(8,40.000000) + SketchFeature.setDatum(6,0.872665) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',0,2,0.0)) + SketchFeature.setDatum(9,0.000000) + SketchFeature.movePoint(0,2,App.Vector(-0.007829,-33.376450,0)) + SketchFeature.movePoint(0,2,App.Vector(-0.738149,-10.493386,0)) + SketchFeature.movePoint(0,2,App.Vector(-0.007829,2.165328,0)) + SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',0,2,2.165328)) + SketchFeature.setDatum(10,0.000000) + +def CreateSlotPlateInnerSet(SketchFeature): + SketchFeature.addGeometry(Part.Circle(App.Vector(195.055893,39.562252,0),App.Vector(0,0,1),29.846098)) + SketchFeature.addGeometry(Part.LineSegment(App.Vector(150.319031,13.449363,0),App.Vector(36.700474,13.139774,0))) + SketchFeature.addConstraint(Sketcher.Constraint('Horizontal',5)) + SketchFeature.addGeometry(Part.LineSegment(App.Vector(36.700474,13.139774,0),App.Vector(77.566010,63.292927,0))) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',5,2,6,1)) + SketchFeature.addGeometry(Part.LineSegment(App.Vector(77.566010,63.292927,0),App.Vector(148.151917,63.602505,0))) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',6,2,7,1)) + SketchFeature.addConstraint(Sketcher.Constraint('Horizontal',7)) + SketchFeature.addConstraint(Sketcher.Constraint('Parallel',1,6)) + SketchFeature.addGeometry(Part.ArcOfCircle(Part.Circle(App.Vector(192.422913,38.216347,0),App.Vector(0,0,1),45.315174),2.635158,3.602228)) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',7,2,8,1)) + SketchFeature.addConstraint(Sketcher.Constraint('Coincident',8,2,5,1)) + + + +#--------------------------------------------------------------------------- +# define the test cases to test the FreeCAD Sketcher module +#--------------------------------------------------------------------------- + + +class TestSketcherSolver(unittest.TestCase): + def setUp(self): + self.Doc = FreeCAD.newDocument("SketchSolverTest") + + def testBoxCase(self): + self.Box = self.Doc.addObject('Sketcher::SketchObject','SketchBox') + CreateBoxSketchSet(self.Box) + self.Doc.recompute() + # moving a point of the sketch + self.Box.movePoint(0,2,App.Vector(88.342697,28.174158,0)) + # fully constrain + self.Box.addConstraint(Sketcher.Constraint('DistanceX',1,2,90.0)) + self.Box.addConstraint(Sketcher.Constraint('DistanceY',1,2,-50.0)) + self.Doc.recompute() + + def testSlotCase(self): + self.Slot = self.Doc.addObject('Sketcher::SketchObject','SketchSlot') + CreateSlotPlateSet(self.Slot) + self.Doc.recompute() + # test if all edges created + self.failUnless(len(self.Slot.Shape.Edges) == 4) + CreateSlotPlateInnerSet(self.Slot) + self.Doc.recompute() + self.failUnless(len(self.Slot.Shape.Edges) == 9) + + def testIssue3245(self): + self.Doc2 = FreeCAD.newDocument("Issue3245") + self.Doc2.addObject('Sketcher::SketchObject','Sketch') + self.Doc2.Sketch.Placement = App.Placement(App.Vector(0.000000,0.000000,0.000000),App.Rotation(0.000000,0.000000,0.000000,1.000000)) + self.Doc2.Sketch.MapMode = "Deactivated" + self.Doc2.Sketch.addGeometry(Part.LineSegment(App.Vector(-1.195999,56.041161,0),App.Vector(60.654316,56.382877,0)),False) + self.Doc2.Sketch.addConstraint(Sketcher.Constraint('PointOnObject',0,1,-2)) + self.Doc2.Sketch.addConstraint(Sketcher.Constraint('Horizontal',0)) + self.Doc2.Sketch.addGeometry(Part.LineSegment(App.Vector(0.512583,32.121155,0),App.Vector(60.654316,31.779440,0)),False) + self.Doc2.Sketch.addConstraint(Sketcher.Constraint('Horizontal',1)) + self.Doc2.Sketch.addGeometry(Part.LineSegment(App.Vector(0.170867,13.326859,0),App.Vector(61.679455,13.326859,0)),False) + self.Doc2.Sketch.addConstraint(Sketcher.Constraint('PointOnObject',2,1,-2)) + self.Doc2.Sketch.addConstraint(Sketcher.Constraint('Horizontal',2)) + self.Doc2.Sketch.addConstraint(Sketcher.Constraint('PointOnObject',1,1,-2)) + self.Doc2.Sketch.addConstraint(Sketcher.Constraint('DistanceX',0,1,0,2,60.654316)) + self.Doc2.Sketch.setExpression('Constraints[6]', u'60') + self.Doc2.Sketch.addConstraint(Sketcher.Constraint('DistanceX',1,1,1,2,60.654316)) + self.Doc2.Sketch.setExpression('Constraints[7]', u'65') + self.Doc2.Sketch.addConstraint(Sketcher.Constraint('DistanceX',2,1,2,2,61.679455)) + self.Doc2.Sketch.setExpression('Constraints[8]', u'70') + self.Doc2.recompute() + self.Doc2.Sketch.delGeometry(2) + values = d = {key: value for (key, value) in self.Doc2.Sketch.ExpressionEngine} + self.failUnless(values['Constraints[4]'] == u'60') + self.failUnless(values['Constraints[5]'] == u'65') + FreeCAD.closeDocument("Issue3245") + + def testIssue3245_2(self): + self.Doc2 = FreeCAD.newDocument("Issue3245") + ActiveSketch = self.Doc2.addObject('Sketcher::SketchObject','Sketch') + ActiveSketch.Placement = App.Placement(App.Vector(0.000000,0.000000,0.000000),App.Rotation(0.000000,0.000000,0.000000,1.000000)) + ActiveSketch.MapMode = "Deactivated" + geoList = [] + geoList.append(Part.LineSegment(App.Vector(-23.574591,42.399727,0),App.Vector(81.949776,42.399727,0))) + geoList.append(Part.LineSegment(App.Vector(81.949776,42.399727,0),App.Vector(81.949776,-19.256901,0))) + geoList.append(Part.LineSegment(App.Vector(81.949776,-19.256901,0),App.Vector(-23.574591,-19.256901,0))) + geoList.append(Part.LineSegment(App.Vector(-23.574591,-19.256901,0),App.Vector(-23.574591,42.399727,0))) + ActiveSketch.addGeometry(geoList,False) + conList = [] + conList.append(Sketcher.Constraint('Coincident',0,2,1,1)) + conList.append(Sketcher.Constraint('Coincident',1,2,2,1)) + conList.append(Sketcher.Constraint('Coincident',2,2,3,1)) + conList.append(Sketcher.Constraint('Coincident',3,2,0,1)) + conList.append(Sketcher.Constraint('Horizontal',0)) + conList.append(Sketcher.Constraint('Horizontal',2)) + conList.append(Sketcher.Constraint('Vertical',1)) + conList.append(Sketcher.Constraint('Vertical',3)) + ActiveSketch.addConstraint(conList) + ActiveSketch.addConstraint(Sketcher.Constraint('DistanceX',0,1,0,2,105.524367)) + ActiveSketch.setExpression('Constraints[8]', u'10 + 10') + ActiveSketch.addConstraint(Sketcher.Constraint('DistanceY',3,1,3,2,61.656628)) + ActiveSketch.setDatum(9,App.Units.Quantity('5.000000 mm')) + ActiveSketch.delConstraint(8) + values = d = {key: value for (key, value) in self.Doc2.Sketch.ExpressionEngine} + self.Doc2.recompute() + self.failUnless(len(values) == 0) + FreeCAD.closeDocument("Issue3245") + + def tearDown(self): + #closing doc + FreeCAD.closeDocument("SketchSolverTest") + #print ("omit closing document for debugging") diff --git a/src/Mod/Sketcher/SketcherTests/__init__.py b/src/Mod/Sketcher/SketcherTests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mod/Sketcher/TestSketcherApp.py b/src/Mod/Sketcher/TestSketcherApp.py index d822aa4514..6a06b1520d 100644 --- a/src/Mod/Sketcher/TestSketcherApp.py +++ b/src/Mod/Sketcher/TestSketcherApp.py @@ -1,4 +1,5 @@ # (c) Juergen Riegel (FreeCAD@juergen-riegel.net) 2011 LGPL * +# (c) Emmanuel O'Brien 2021 LGPL * # * # This file is part of the FreeCAD CAx development system. * # * @@ -19,198 +20,12 @@ # USA * #************************************************************************** +# Broken-out test modules +from SketcherTests.TestSketcherSolver import TestSketcherSolver +from SketcherTests.TestSketchFillet import TestSketchFillet -import FreeCAD, os, sys, unittest, Part, Sketcher -App = FreeCAD - -def CreateRectangleSketch(SketchFeature, corner, lengths): - hmin, hmax = corner[0], corner[0] + lengths[0] - vmin, vmax = corner[1], corner[1] + lengths[1] - - # add the geometry and grab the count offset - i = int(SketchFeature.GeometryCount) - SketchFeature.addGeometry(Part.LineSegment(FreeCAD.Vector(hmin,vmax),FreeCAD.Vector(hmax,vmax,0))) - SketchFeature.addGeometry(Part.LineSegment(FreeCAD.Vector(hmax,vmax,0),FreeCAD.Vector(hmax,vmin,0))) - SketchFeature.addGeometry(Part.LineSegment(FreeCAD.Vector(hmax,vmin,0),FreeCAD.Vector(hmin,vmin,0))) - SketchFeature.addGeometry(Part.LineSegment(FreeCAD.Vector(hmin,vmin,0),FreeCAD.Vector(hmin,vmax,0))) - - # add the rectangular constraints - SketchFeature.addConstraint(Sketcher.Constraint('Coincident',i+0,2,i+1,1)) - SketchFeature.addConstraint(Sketcher.Constraint('Coincident',i+1,2,i+2,1)) - SketchFeature.addConstraint(Sketcher.Constraint('Coincident',i+2,2,i+3,1)) - SketchFeature.addConstraint(Sketcher.Constraint('Coincident',i+3,2,i+0,1)) - SketchFeature.addConstraint(Sketcher.Constraint('Horizontal',i+0)) - SketchFeature.addConstraint(Sketcher.Constraint('Horizontal',i+2)) - SketchFeature.addConstraint(Sketcher.Constraint('Vertical',i+1)) - SketchFeature.addConstraint(Sketcher.Constraint('Vertical',i+3)) - - # Fix the bottom left corner of the rectangle - SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',i+2,2,corner[0])) - SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',i+2,2,corner[1])) - - # add dimensions - if lengths[0] == lengths[1]: - SketchFeature.addConstraint(Sketcher.Constraint('Equal',i+2,i+3)) - SketchFeature.addConstraint(Sketcher.Constraint('Distance',i+0,hmax-hmin)) - else: - SketchFeature.addConstraint(Sketcher.Constraint('Distance',i+1,vmax-vmin)) - SketchFeature.addConstraint(Sketcher.Constraint('Distance',i+0,hmax-hmin)) - -def CreateCircleSketch(SketchFeature, center, radius): - i = int(SketchFeature.GeometryCount) - SketchFeature.addGeometry(Part.Circle(App.Vector(*center), App.Vector(0,0,1), radius),False) - SketchFeature.addConstraint(Sketcher.Constraint('Radius',i,radius)) - SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',i,3,center[0])) - SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',i,3,center[1])) - -def CreateBoxSketchSet(SketchFeature): - SketchFeature.addGeometry(Part.LineSegment(FreeCAD.Vector(-99.230339,36.960674,0),FreeCAD.Vector(69.432587,36.960674,0))) - SketchFeature.addGeometry(Part.LineSegment(FreeCAD.Vector(69.432587,36.960674,0),FreeCAD.Vector(69.432587,-53.196629,0))) - SketchFeature.addGeometry(Part.LineSegment(FreeCAD.Vector(69.432587,-53.196629,0),FreeCAD.Vector(-99.230339,-53.196629,0))) - SketchFeature.addGeometry(Part.LineSegment(FreeCAD.Vector(-99.230339,-53.196629,0),FreeCAD.Vector(-99.230339,36.960674,0))) - # add the constraints - SketchFeature.addConstraint(Sketcher.Constraint('Coincident',0,2,1,1)) - SketchFeature.addConstraint(Sketcher.Constraint('Coincident',1,2,2,1)) - SketchFeature.addConstraint(Sketcher.Constraint('Coincident',2,2,3,1)) - SketchFeature.addConstraint(Sketcher.Constraint('Coincident',3,2,0,1)) - SketchFeature.addConstraint(Sketcher.Constraint('Horizontal',0)) - SketchFeature.addConstraint(Sketcher.Constraint('Horizontal',2)) - SketchFeature.addConstraint(Sketcher.Constraint('Vertical',1)) - SketchFeature.addConstraint(Sketcher.Constraint('Vertical',3)) - # add dimensions - SketchFeature.addConstraint(Sketcher.Constraint('Distance',1,81.370787)) - SketchFeature.addConstraint(Sketcher.Constraint('Distance',0,187.573036)) - -def CreateSlotPlateSet(SketchFeature): - SketchFeature.addGeometry(Part.LineSegment(App.Vector(60.029362,-30.279360,0),App.Vector(-120.376335,-30.279360,0))) - SketchFeature.addConstraint(Sketcher.Constraint('Horizontal',0)) - SketchFeature.addGeometry(Part.LineSegment(App.Vector(-120.376335,-30.279360,0),App.Vector(-70.193062,38.113884,0))) - SketchFeature.addConstraint(Sketcher.Constraint('Coincident',0,2,1,1)) - SketchFeature.addGeometry(Part.LineSegment(App.Vector(-70.193062,38.113884,0),App.Vector(60.241116,37.478645,0))) - SketchFeature.addConstraint(Sketcher.Constraint('Coincident',1,2,2,1)) - SketchFeature.addConstraint(Sketcher.Constraint('Horizontal',2)) - SketchFeature.addGeometry(Part.ArcOfCircle(Part.Circle(App.Vector(60.039921,3.811391,0),App.Vector(0,0,1),35.127132),-1.403763,1.419522)) - SketchFeature.addConstraint(Sketcher.Constraint('Tangent',2,2,3,2)) - SketchFeature.addConstraint(Sketcher.Constraint('Tangent',0,1,3,1)) - SketchFeature.addConstraint(Sketcher.Constraint('Angle',0,2,1,1,0.947837)) - SketchFeature.addConstraint(Sketcher.Constraint('Distance',0,184.127425)) - SketchFeature.setDatum(7,200.000000) - SketchFeature.addConstraint(Sketcher.Constraint('Radius',3,38.424808)) - SketchFeature.setDatum(8,40.000000) - SketchFeature.setDatum(6,0.872665) - SketchFeature.addConstraint(Sketcher.Constraint('DistanceX',0,2,0.0)) - SketchFeature.setDatum(9,0.000000) - SketchFeature.movePoint(0,2,App.Vector(-0.007829,-33.376450,0)) - SketchFeature.movePoint(0,2,App.Vector(-0.738149,-10.493386,0)) - SketchFeature.movePoint(0,2,App.Vector(-0.007829,2.165328,0)) - SketchFeature.addConstraint(Sketcher.Constraint('DistanceY',0,2,2.165328)) - SketchFeature.setDatum(10,0.000000) - -def CreateSlotPlateInnerSet(SketchFeature): - SketchFeature.addGeometry(Part.Circle(App.Vector(195.055893,39.562252,0),App.Vector(0,0,1),29.846098)) - SketchFeature.addGeometry(Part.LineSegment(App.Vector(150.319031,13.449363,0),App.Vector(36.700474,13.139774,0))) - SketchFeature.addConstraint(Sketcher.Constraint('Horizontal',5)) - SketchFeature.addGeometry(Part.LineSegment(App.Vector(36.700474,13.139774,0),App.Vector(77.566010,63.292927,0))) - SketchFeature.addConstraint(Sketcher.Constraint('Coincident',5,2,6,1)) - SketchFeature.addGeometry(Part.LineSegment(App.Vector(77.566010,63.292927,0),App.Vector(148.151917,63.602505,0))) - SketchFeature.addConstraint(Sketcher.Constraint('Coincident',6,2,7,1)) - SketchFeature.addConstraint(Sketcher.Constraint('Horizontal',7)) - SketchFeature.addConstraint(Sketcher.Constraint('Parallel',1,6)) - SketchFeature.addGeometry(Part.ArcOfCircle(Part.Circle(App.Vector(192.422913,38.216347,0),App.Vector(0,0,1),45.315174),2.635158,3.602228)) - SketchFeature.addConstraint(Sketcher.Constraint('Coincident',7,2,8,1)) - SketchFeature.addConstraint(Sketcher.Constraint('Coincident',8,2,5,1)) - - - -#--------------------------------------------------------------------------- -# define the test cases to test the FreeCAD Sketcher module -#--------------------------------------------------------------------------- - - -class SketcherSolverTestCases(unittest.TestCase): - def setUp(self): - self.Doc = FreeCAD.newDocument("SketchSolverTest") - - def testBoxCase(self): - self.Box = self.Doc.addObject('Sketcher::SketchObject','SketchBox') - CreateBoxSketchSet(self.Box) - self.Doc.recompute() - # moving a point of the sketch - self.Box.movePoint(0,2,App.Vector(88.342697,28.174158,0)) - # fully constrain - self.Box.addConstraint(Sketcher.Constraint('DistanceX',1,2,90.0)) - self.Box.addConstraint(Sketcher.Constraint('DistanceY',1,2,-50.0)) - self.Doc.recompute() - - def testSlotCase(self): - self.Slot = self.Doc.addObject('Sketcher::SketchObject','SketchSlot') - CreateSlotPlateSet(self.Slot) - self.Doc.recompute() - # test if all edges created - self.failUnless(len(self.Slot.Shape.Edges) == 4) - CreateSlotPlateInnerSet(self.Slot) - self.Doc.recompute() - self.failUnless(len(self.Slot.Shape.Edges) == 9) - - def testIssue3245(self): - self.Doc2 = FreeCAD.newDocument("Issue3245") - self.Doc2.addObject('Sketcher::SketchObject','Sketch') - self.Doc2.Sketch.Placement = App.Placement(App.Vector(0.000000,0.000000,0.000000),App.Rotation(0.000000,0.000000,0.000000,1.000000)) - self.Doc2.Sketch.MapMode = "Deactivated" - self.Doc2.Sketch.addGeometry(Part.LineSegment(App.Vector(-1.195999,56.041161,0),App.Vector(60.654316,56.382877,0)),False) - self.Doc2.Sketch.addConstraint(Sketcher.Constraint('PointOnObject',0,1,-2)) - self.Doc2.Sketch.addConstraint(Sketcher.Constraint('Horizontal',0)) - self.Doc2.Sketch.addGeometry(Part.LineSegment(App.Vector(0.512583,32.121155,0),App.Vector(60.654316,31.779440,0)),False) - self.Doc2.Sketch.addConstraint(Sketcher.Constraint('Horizontal',1)) - self.Doc2.Sketch.addGeometry(Part.LineSegment(App.Vector(0.170867,13.326859,0),App.Vector(61.679455,13.326859,0)),False) - self.Doc2.Sketch.addConstraint(Sketcher.Constraint('PointOnObject',2,1,-2)) - self.Doc2.Sketch.addConstraint(Sketcher.Constraint('Horizontal',2)) - self.Doc2.Sketch.addConstraint(Sketcher.Constraint('PointOnObject',1,1,-2)) - self.Doc2.Sketch.addConstraint(Sketcher.Constraint('DistanceX',0,1,0,2,60.654316)) - self.Doc2.Sketch.setExpression('Constraints[6]', u'60') - self.Doc2.Sketch.addConstraint(Sketcher.Constraint('DistanceX',1,1,1,2,60.654316)) - self.Doc2.Sketch.setExpression('Constraints[7]', u'65') - self.Doc2.Sketch.addConstraint(Sketcher.Constraint('DistanceX',2,1,2,2,61.679455)) - self.Doc2.Sketch.setExpression('Constraints[8]', u'70') - self.Doc2.recompute() - self.Doc2.Sketch.delGeometry(2) - values = d = {key: value for (key, value) in self.Doc2.Sketch.ExpressionEngine} - self.failUnless(values['Constraints[4]'] == u'60') - self.failUnless(values['Constraints[5]'] == u'65') - FreeCAD.closeDocument("Issue3245") - - def testIssue3245_2(self): - self.Doc2 = FreeCAD.newDocument("Issue3245") - ActiveSketch = self.Doc2.addObject('Sketcher::SketchObject','Sketch') - ActiveSketch.Placement = App.Placement(App.Vector(0.000000,0.000000,0.000000),App.Rotation(0.000000,0.000000,0.000000,1.000000)) - ActiveSketch.MapMode = "Deactivated" - geoList = [] - geoList.append(Part.LineSegment(App.Vector(-23.574591,42.399727,0),App.Vector(81.949776,42.399727,0))) - geoList.append(Part.LineSegment(App.Vector(81.949776,42.399727,0),App.Vector(81.949776,-19.256901,0))) - geoList.append(Part.LineSegment(App.Vector(81.949776,-19.256901,0),App.Vector(-23.574591,-19.256901,0))) - geoList.append(Part.LineSegment(App.Vector(-23.574591,-19.256901,0),App.Vector(-23.574591,42.399727,0))) - ActiveSketch.addGeometry(geoList,False) - conList = [] - conList.append(Sketcher.Constraint('Coincident',0,2,1,1)) - conList.append(Sketcher.Constraint('Coincident',1,2,2,1)) - conList.append(Sketcher.Constraint('Coincident',2,2,3,1)) - conList.append(Sketcher.Constraint('Coincident',3,2,0,1)) - conList.append(Sketcher.Constraint('Horizontal',0)) - conList.append(Sketcher.Constraint('Horizontal',2)) - conList.append(Sketcher.Constraint('Vertical',1)) - conList.append(Sketcher.Constraint('Vertical',3)) - ActiveSketch.addConstraint(conList) - ActiveSketch.addConstraint(Sketcher.Constraint('DistanceX',0,1,0,2,105.524367)) - ActiveSketch.setExpression('Constraints[8]', u'10 + 10') - ActiveSketch.addConstraint(Sketcher.Constraint('DistanceY',3,1,3,2,61.656628)) - ActiveSketch.setDatum(9,App.Units.Quantity('5.000000 mm')) - ActiveSketch.delConstraint(8) - values = d = {key: value for (key, value) in self.Doc2.Sketch.ExpressionEngine} - self.Doc2.recompute() - self.failUnless(len(values) == 0) - FreeCAD.closeDocument("Issue3245") - - def tearDown(self): - #closing doc - FreeCAD.closeDocument("SketchSolverTest") - #print ("omit closing document for debugging") +# Path and PartDesign tests use these functions that used to live here +# but moved to SketcherTests/TestSketcherSolver.py +from SketcherTests.TestSketcherSolver import CreateCircleSketch +from SketcherTests.TestSketcherSolver import CreateRectangleSketch +from SketcherTests.TestSketcherSolver import CreateSlotPlateSet From 28c66fc501a1dd06195c99653b88400a486d57d2 Mon Sep 17 00:00:00 2001 From: donovaly Date: Thu, 4 Feb 2021 04:41:06 +0100 Subject: [PATCH 137/168] [Sketcher] make pointers to the UI std::unique_ptr Same as PR #4293, just for Sketcher as noted in https://github.com/FreeCAD/FreeCAD/pull/4271#discussion_r554673632 the pointer to the UI should be a unique pointer. This PR does this for all Sketcher dialogs that don't already use a unique_ptr. --- src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp | 9 ++++----- src/Mod/Sketcher/Gui/TaskSketcherConstrains.h | 2 +- src/Mod/Sketcher/Gui/TaskSketcherElements.cpp | 1 - src/Mod/Sketcher/Gui/TaskSketcherElements.h | 2 +- src/Mod/Sketcher/Gui/TaskSketcherGeneral.cpp | 1 - src/Mod/Sketcher/Gui/TaskSketcherGeneral.h | 2 +- src/Mod/Sketcher/Gui/TaskSketcherMessages.cpp | 9 ++++----- src/Mod/Sketcher/Gui/TaskSketcherMessages.h | 2 +- src/Mod/Sketcher/Gui/TaskSketcherSolverAdvanced.cpp | 9 ++++----- src/Mod/Sketcher/Gui/TaskSketcherSolverAdvanced.h | 2 +- 10 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp index 72378330df..1134dcce36 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp @@ -634,13 +634,13 @@ void ConstraintView::swapNamedOfSelectedItems() // ---------------------------------------------------------------------------- -TaskSketcherConstrains::TaskSketcherConstrains(ViewProviderSketch *sketchView) - : TaskBox(Gui::BitmapFactory().pixmap("document-new"),tr("Constraints"),true, 0) - , sketchView(sketchView), inEditMode(false) +TaskSketcherConstrains::TaskSketcherConstrains(ViewProviderSketch *sketchView) : + TaskBox(Gui::BitmapFactory().pixmap("document-new"), tr("Constraints"), true, 0), + sketchView(sketchView), inEditMode(false), + ui(new Ui_TaskSketcherConstrains) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskSketcherConstrains(); ui->setupUi(proxy); ui->listWidgetConstraints->setSelectionMode(QAbstractItemView::ExtendedSelection); ui->listWidgetConstraints->setEditTriggers(QListWidget::EditKeyPressed); @@ -700,7 +700,6 @@ TaskSketcherConstrains::~TaskSketcherConstrains() this->ui->filterInternalAlignment->onSave(); this->ui->extendedInformation->onSave(); connectionConstraintsChanged.disconnect(); - delete ui; } void TaskSketcherConstrains::onSelectionChanged(const Gui::SelectionChanges& msg) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h index fc52458bf1..27864995d9 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h @@ -101,7 +101,7 @@ protected: private: QWidget* proxy; bool inEditMode; - Ui_TaskSketcherConstrains* ui; + std::unique_ptr ui; }; } //namespace SketcherGui diff --git a/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp b/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp index d833fba3e6..4b6082bff3 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp @@ -342,7 +342,6 @@ TaskSketcherElements::~TaskSketcherElements() } connectionElementsChanged.disconnect(); - delete ui; } void TaskSketcherElements::onSelectionChanged(const Gui::SelectionChanges& msg) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherElements.h b/src/Mod/Sketcher/Gui/TaskSketcherElements.h index 86e69c47e3..0eff5aa1e3 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherElements.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherElements.h @@ -139,7 +139,7 @@ protected: private: QWidget* proxy; - Ui_TaskSketcherElements* ui; + std::unique_ptr ui; int focusItemIndex; int previouslySelectedItemIndex; diff --git a/src/Mod/Sketcher/Gui/TaskSketcherGeneral.cpp b/src/Mod/Sketcher/Gui/TaskSketcherGeneral.cpp index cb42687c45..1799bf1f2e 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherGeneral.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherGeneral.cpp @@ -69,7 +69,6 @@ SketcherGeneralWidget::SketcherGeneralWidget(QWidget *parent) SketcherGeneralWidget::~SketcherGeneralWidget() { - delete ui; } bool SketcherGeneralWidget::eventFilter(QObject *object, QEvent *event) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherGeneral.h b/src/Mod/Sketcher/Gui/TaskSketcherGeneral.h index 335af9c434..2c267714f4 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherGeneral.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherGeneral.h @@ -75,7 +75,7 @@ protected: void changeEvent(QEvent *e); private: - Ui_TaskSketcherGeneral* ui; + std::unique_ptr ui; }; class TaskSketcherGeneral : public Gui::TaskView::TaskBox, diff --git a/src/Mod/Sketcher/Gui/TaskSketcherMessages.cpp b/src/Mod/Sketcher/Gui/TaskSketcherMessages.cpp index 730256a978..14a3b4cb21 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherMessages.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherMessages.cpp @@ -47,13 +47,13 @@ using namespace SketcherGui; using namespace Gui::TaskView; namespace bp = boost::placeholders; -TaskSketcherMessages::TaskSketcherMessages(ViewProviderSketch *sketchView) - : TaskBox(Gui::BitmapFactory().pixmap("document-new"),tr("Solver messages"),true, 0) - , sketchView(sketchView) +TaskSketcherMessages::TaskSketcherMessages(ViewProviderSketch *sketchView) : + TaskBox(Gui::BitmapFactory().pixmap("document-new"), tr("Solver messages"), true, 0), + sketchView(sketchView), + ui(new Ui_TaskSketcherMessages) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskSketcherMessages(); ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); @@ -90,7 +90,6 @@ TaskSketcherMessages::~TaskSketcherMessages() { connectionSetUp.disconnect(); connectionSolved.disconnect(); - delete ui; } void TaskSketcherMessages::slotSetUp(QString msg) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherMessages.h b/src/Mod/Sketcher/Gui/TaskSketcherMessages.h index 09193959d6..30aae3fbb0 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherMessages.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherMessages.h @@ -63,7 +63,7 @@ protected: private: QWidget* proxy; - Ui_TaskSketcherMessages* ui; + std::unique_ptr ui; }; } //namespace SketcherGui diff --git a/src/Mod/Sketcher/Gui/TaskSketcherSolverAdvanced.cpp b/src/Mod/Sketcher/Gui/TaskSketcherSolverAdvanced.cpp index c5f6b4edb3..1b59b7e1b9 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherSolverAdvanced.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherSolverAdvanced.cpp @@ -60,13 +60,13 @@ using namespace SketcherGui; using namespace Gui::TaskView; -TaskSketcherSolverAdvanced::TaskSketcherSolverAdvanced(ViewProviderSketch *sketchView) - : TaskBox(Gui::BitmapFactory().pixmap("document-new"),tr("Advanced solver control"),true, 0) - , sketchView(sketchView) +TaskSketcherSolverAdvanced::TaskSketcherSolverAdvanced(ViewProviderSketch *sketchView) : + TaskBox(Gui::BitmapFactory().pixmap("document-new"), tr("Advanced solver control"), true, 0), + sketchView(sketchView), + ui(new Ui_TaskSketcherSolverAdvanced) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskSketcherSolverAdvanced(); ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); @@ -90,7 +90,6 @@ TaskSketcherSolverAdvanced::TaskSketcherSolverAdvanced(ViewProviderSketch *sketc TaskSketcherSolverAdvanced::~TaskSketcherSolverAdvanced() { - delete ui; } void TaskSketcherSolverAdvanced::updateDefaultMethodParameters(void) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherSolverAdvanced.h b/src/Mod/Sketcher/Gui/TaskSketcherSolverAdvanced.h index d55473d8b4..2e2dcfde1b 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherSolverAdvanced.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherSolverAdvanced.h @@ -76,7 +76,7 @@ protected: private: QWidget* proxy; - Ui_TaskSketcherSolverAdvanced* ui; + std::unique_ptr ui; }; } //namespace SketcherGui From ce7b57a08f3de5968e1bbbc9f36e6f2b7f20ebcc Mon Sep 17 00:00:00 2001 From: Greg V Date: Thu, 4 Feb 2021 00:08:10 +0300 Subject: [PATCH 138/168] Sketcher: [skip ci] fix computing of hotspot of sketcher icons on Wayland Restrict the hotspot multiplication on unix platforms to X11 (xcb platform) --- src/Mod/Sketcher/Gui/DrawSketchHandler.cpp | 19 +++++++++++-------- src/Mod/Sketcher/Gui/PreCompiled.h | 1 + 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp b/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp index 7db982dcb4..eac2fdb258 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp +++ b/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp @@ -29,6 +29,7 @@ # include # include # include +# include # include #endif // #ifndef _PreComp_ @@ -113,12 +114,13 @@ void DrawSketchHandler::setSvgCursor(const QString & cursorName, int x, int y, c qreal pRatio = devicePixelRatio(); bool isRatioOne = (pRatio == 1.0); qreal defaultCursorSize = isRatioOne ? 64 : 32; -#if defined(Q_OS_WIN32) || defined(Q_OS_MAC) qreal hotX = x; qreal hotY = y; -#else - qreal hotX = x * pRatio; - qreal hotY = y * pRatio; +#if !defined(Q_OS_WIN32) && !defined(Q_OS_MAC) + if (qGuiApp->platformName() == QLatin1String("xcb")) { + hotX *= pRatio; + hotY *= pRatio; + } #endif qreal cursorSize = defaultCursorSize * pRatio; @@ -155,12 +157,13 @@ void DrawSketchHandler::setCursor(const QPixmap &p,int x,int y, bool autoScale) #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) p1.setDevicePixelRatio(pRatio); #endif -#if defined(Q_OS_WIN32) || defined(Q_OS_MAC) qreal hotX = x; qreal hotY = y; -#else - qreal hotX = x * pRatio; - qreal hotY = y * pRatio; +#if !defined(Q_OS_WIN32) && !defined(Q_OS_MAC) + if (qGuiApp->platformName() == QLatin1String("xcb")) { + hotX *= pRatio; + hotY *= pRatio; + } #endif cursor = QCursor(p1, hotX, hotY); } else { diff --git a/src/Mod/Sketcher/Gui/PreCompiled.h b/src/Mod/Sketcher/Gui/PreCompiled.h index 9fe66a48d5..4521f5ce15 100644 --- a/src/Mod/Sketcher/Gui/PreCompiled.h +++ b/src/Mod/Sketcher/Gui/PreCompiled.h @@ -95,6 +95,7 @@ # include #endif +# include # include #include #include From 79995368581832a7b4e1fcdc4a4d3bd1c64b07ec Mon Sep 17 00:00:00 2001 From: Pierre LeMoine Date: Thu, 31 Dec 2020 05:19:30 +0100 Subject: [PATCH 139/168] Transform patterns can be created from multiple base features The infrastructure/piping seems to have been in place for a long while. Not tested for all variations of pattern transforms. The major enabler was removing the `break`. Some extra piping added to let the code at call-site decide if to select multiple features or not. --- src/Mod/PartDesign/Gui/Command.cpp | 6 +++--- src/Mod/PartDesign/Gui/CommandBody.cpp | 2 +- src/Mod/PartDesign/Gui/TaskFeaturePick.cpp | 9 +++++++-- src/Mod/PartDesign/Gui/TaskFeaturePick.h | 4 +++- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Mod/PartDesign/Gui/Command.cpp b/src/Mod/PartDesign/Gui/Command.cpp index 295da09853..22be4e650b 100644 --- a/src/Mod/PartDesign/Gui/Command.cpp +++ b/src/Mod/PartDesign/Gui/Command.cpp @@ -821,7 +821,7 @@ void CmdPartDesignNewSketch::activated(int iMsg) Gui::Control().closeDialog(); Gui::Selection().clearSelection(); - Gui::Control().showDialog(new PartDesignGui::TaskDlgFeaturePick(planes, status, accepter, worker, quitter)); + Gui::Control().showDialog(new PartDesignGui::TaskDlgFeaturePick(planes, status, accepter, worker, true, quitter)); } } } @@ -1151,7 +1151,7 @@ void prepareProfileBased(PartDesign::Body *pcActiveBody, Gui::Command* cmd, cons Gui::Control().closeDialog(); Gui::Selection().clearSelection(); - pickDlg = new PartDesignGui::TaskDlgFeaturePick(sketches, status, accepter, sketch_worker); + pickDlg = new PartDesignGui::TaskDlgFeaturePick(sketches, status, accepter, sketch_worker, true); // Logically dead code because 'bNoSketchWasSelected' must be true //if (!bNoSketchWasSelected && extReference) // pickDlg->showExternal(true); @@ -1979,7 +1979,7 @@ void prepareTransformed(PartDesign::Body *pcActiveBody, Gui::Command* cmd, const Gui::Control().closeDialog(); Gui::Selection().clearSelection(); - Gui::Control().showDialog(new PartDesignGui::TaskDlgFeaturePick(features, status, accepter, worker)); + Gui::Control().showDialog(new PartDesignGui::TaskDlgFeaturePick(features, status, accepter, worker, false)); return; } else if (features.empty()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("No valid features in this document"), diff --git a/src/Mod/PartDesign/Gui/CommandBody.cpp b/src/Mod/PartDesign/Gui/CommandBody.cpp index f3efffb205..61824c58c5 100644 --- a/src/Mod/PartDesign/Gui/CommandBody.cpp +++ b/src/Mod/PartDesign/Gui/CommandBody.cpp @@ -286,7 +286,7 @@ void CmdPartDesignBody::activated(int iMsg) Gui::TaskView::TaskDialog *dlg = Gui::Control().activeDialog(); if (!dlg) { Gui::Selection().clearSelection(); - Gui::Control().showDialog(new PartDesignGui::TaskDlgFeaturePick(planes, status, accepter, worker, quitter)); + Gui::Control().showDialog(new PartDesignGui::TaskDlgFeaturePick(planes, status, accepter, worker, true, quitter)); } } } diff --git a/src/Mod/PartDesign/Gui/TaskFeaturePick.cpp b/src/Mod/PartDesign/Gui/TaskFeaturePick.cpp index 5d11ba6355..905323e59b 100644 --- a/src/Mod/PartDesign/Gui/TaskFeaturePick.cpp +++ b/src/Mod/PartDesign/Gui/TaskFeaturePick.cpp @@ -80,6 +80,7 @@ const QString TaskFeaturePick::getFeatureStatusString(const featureStatus st) TaskFeaturePick::TaskFeaturePick(std::vector& objects, const std::vector& status, + bool singleFeatureSelect, QWidget* parent) : TaskBox(Gui::BitmapFactory().pixmap("edit-select-box"), tr("Select feature"), true, parent) @@ -100,6 +101,10 @@ TaskFeaturePick::TaskFeaturePick(std::vector& objects, connect(ui->listWidget, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(onDoubleClick(QListWidgetItem *))); + if (!singleFeatureSelect) { + ui->listWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); + } + enum { axisBit=0, planeBit = 1}; // NOTE: generally there shouldn't be more then one origin @@ -276,7 +281,6 @@ std::vector TaskFeaturePick::buildFeatures() result.push_back(obj); } - break; } index++; @@ -525,10 +529,11 @@ TaskDlgFeaturePick::TaskDlgFeaturePick( std::vector &objec const std::vector &status, boost::function)> afunc, boost::function)> wfunc, + bool singleFeatureSelect, boost::function abortfunc /* = NULL */ ) : TaskDialog(), accepted(false) { - pick = new TaskFeaturePick(objects, status); + pick = new TaskFeaturePick(objects, status, singleFeatureSelect); Content.push_back(pick); acceptFunction = afunc; diff --git a/src/Mod/PartDesign/Gui/TaskFeaturePick.h b/src/Mod/PartDesign/Gui/TaskFeaturePick.h index bdce4fd7d6..fa8ff1a1a9 100644 --- a/src/Mod/PartDesign/Gui/TaskFeaturePick.h +++ b/src/Mod/PartDesign/Gui/TaskFeaturePick.h @@ -58,6 +58,7 @@ public: TaskFeaturePick(std::vector &objects, const std::vector &status, + bool singleFeatureSelect, QWidget *parent = 0); ~TaskFeaturePick(); @@ -107,7 +108,8 @@ public: const std::vector &status, boost::function)> acceptfunc, boost::function)> workfunc, - boost::function abortfunc = 0 ); + bool singleFeatureSelect, + boost::function abortfunc = 0); ~TaskDlgFeaturePick(); public: From 59ec3cb141e301c305d2ea9df0d02b6dfed86eef Mon Sep 17 00:00:00 2001 From: David Osterberg Date: Fri, 25 Dec 2020 12:42:03 +0100 Subject: [PATCH 140/168] PartDesign: New features AdditiveHelix and SubtractiveHelix These features, based on the code for the Pipe class, allow the user to simply create a helical sweep within PartDesign workbench. Sample application is threads, springs, coils, augers, etc. Also, remove needless requirement for positive cone angle on helixes. Thanks to @bitacovir for helping with the icons Thanks to @chennes for review Thanks to @vosk for review Thanks to @wwmayer for review Enforce that links stay within scope for ProfileBased features This also ensures that the Body itself is not used for creating features within the body, causing a "Graph not a DAG" error. --- src/Mod/Part/App/TopoShape.cpp | 4 +- src/Mod/PartDesign/App/AppPartDesign.cpp | 4 + src/Mod/PartDesign/App/Body.cpp | 45 +- src/Mod/PartDesign/App/Body.h | 5 +- src/Mod/PartDesign/App/CMakeLists.txt | 2 + src/Mod/PartDesign/App/FeatureHelix.cpp | 553 +++++++ src/Mod/PartDesign/App/FeatureHelix.h | 103 ++ src/Mod/PartDesign/App/FeatureSketchBased.cpp | 8 +- src/Mod/PartDesign/App/FeatureSketchBased.h | 20 +- src/Mod/PartDesign/Gui/AppPartDesignGui.cpp | 2 + src/Mod/PartDesign/Gui/CMakeLists.txt | 9 +- src/Mod/PartDesign/Gui/Command.cpp | 166 +- .../PartDesign/Gui/Resources/PartDesign.qrc | 2 + .../icons/PartDesign_Additive_Helix.svg | 1456 +++++++++++++++++ .../icons/PartDesign_Subtractive_Helix.svg | 1456 +++++++++++++++++ .../PartDesign/Gui/TaskHelixParameters.cpp | 511 ++++++ src/Mod/PartDesign/Gui/TaskHelixParameters.h | 131 ++ src/Mod/PartDesign/Gui/TaskHelixParameters.ui | 303 ++++ src/Mod/PartDesign/Gui/ViewProviderHelix.cpp | 120 ++ src/Mod/PartDesign/Gui/ViewProviderHelix.h | 62 + .../Gui/ViewProviderSketchBased.cpp | 1 + .../PartDesign/Gui/ViewProviderSketchBased.h | 3 +- src/Mod/PartDesign/Gui/Workbench.cpp | 8 +- 23 files changed, 4927 insertions(+), 47 deletions(-) create mode 100644 src/Mod/PartDesign/App/FeatureHelix.cpp create mode 100644 src/Mod/PartDesign/App/FeatureHelix.h create mode 100644 src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Additive_Helix.svg create mode 100644 src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Subtractive_Helix.svg create mode 100644 src/Mod/PartDesign/Gui/TaskHelixParameters.cpp create mode 100644 src/Mod/PartDesign/Gui/TaskHelixParameters.h create mode 100644 src/Mod/PartDesign/Gui/TaskHelixParameters.ui create mode 100644 src/Mod/PartDesign/Gui/ViewProviderHelix.cpp create mode 100644 src/Mod/PartDesign/Gui/ViewProviderHelix.h diff --git a/src/Mod/Part/App/TopoShape.cpp b/src/Mod/Part/App/TopoShape.cpp index 7ce4568f34..356f116e00 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -2437,7 +2437,7 @@ TopoDS_Shape TopoShape::makeLongHelix(Standard_Real pitch, Standard_Real height, Handle(Geom_Surface) surf; Standard_Boolean isCylinder; - if (angle < Precision::Confusion()) { // Cylindrical helix + if (std::fabs(angle) < Precision::Confusion()) { // Cylindrical helix if (radius < Precision::Confusion()) Standard_Failure::Raise("Radius of helix too small"); surf= new Geom_CylindricalSurface(cylAx2, radius); @@ -2445,8 +2445,6 @@ TopoDS_Shape TopoShape::makeLongHelix(Standard_Real pitch, Standard_Real height, } else { // Conical helix angle = Base::toRadians(angle); - if (angle < Precision::Confusion()) - Standard_Failure::Raise("Angle of helix too small"); surf = new Geom_ConicalSurface(gp_Ax3(cylAx2), angle, radius); isCylinder = false; } diff --git a/src/Mod/PartDesign/App/AppPartDesign.cpp b/src/Mod/PartDesign/App/AppPartDesign.cpp index 3cae7878dd..ce39347034 100644 --- a/src/Mod/PartDesign/App/AppPartDesign.cpp +++ b/src/Mod/PartDesign/App/AppPartDesign.cpp @@ -59,6 +59,7 @@ #include "FeatureLoft.h" #include "ShapeBinder.h" #include "FeatureBase.h" +#include "FeatureHelix.h" namespace PartDesign { extern PyObject* initModule(); @@ -116,6 +117,9 @@ PyMOD_INIT_FUNC(_PartDesign) PartDesign::Loft ::init(); PartDesign::AdditiveLoft ::init(); PartDesign::SubtractiveLoft ::init(); + PartDesign::Helix ::init(); + PartDesign::AdditiveHelix ::init(); + PartDesign::SubtractiveHelix ::init(); PartDesign::ShapeBinder ::init(); PartDesign::SubShapeBinder ::init(); PartDesign::Plane ::init(); diff --git a/src/Mod/PartDesign/App/Body.cpp b/src/Mod/PartDesign/App/Body.cpp index d29e5b90d6..face2936eb 100644 --- a/src/Mod/PartDesign/App/Body.cpp +++ b/src/Mod/PartDesign/App/Body.cpp @@ -140,7 +140,7 @@ App::DocumentObject* Body::getPrevSolidFeature(App::DocumentObject *start) if (rvIt != features.rend()) { // the solid found in model list return *rvIt; } - + return nullptr; } @@ -197,7 +197,7 @@ bool Body::isMemberOfMultiTransform(const App::DocumentObject* f) // This can be recognized because the Originals property is empty (it is contained // in the MultiTransform instead) // COMMENT ON THE COMMENT: - // This is wrong because at the creation (addObject) and before assigning the originals, that + // This is wrong because at the creation (addObject) and before assigning the originals, that // is when this code is executed, the originals property is indeed empty. // // However, for the purpose of setting the base feature, the transform feature has been modified @@ -244,7 +244,7 @@ Body* Body::findBodyOf(const App::DocumentObject* feature) { if(!feature) return nullptr; - + return static_cast(BodyBase::findBodyOf(feature)); } @@ -253,15 +253,15 @@ std::vector Body::addObject(App::DocumentObject *feature) { if(!isAllowed(feature)) throw Base::ValueError("Body: object is not allowed"); - + //TODO: features should not add all links - + //only one group per object. If it is in a body the single feature will be removed auto *group = App::GroupExtension::getGroupOfObject(feature); if(group && group != getExtendedObject()) group->getExtensionByType()->removeObject(feature); - - + + insertObject (feature, getNextSolidFeature (), /*after = */ false); // Move the Tip if we added a solid if (isSolidFeature(feature)) { @@ -272,22 +272,22 @@ std::vector Body::addObject(App::DocumentObject *feature) && feature->isDerivedFrom(PartDesign::Feature::getClassTypeId())) { for(auto obj : Group.getValues()) { - if(obj->Visibility.getValue() - && obj!=feature + if(obj->Visibility.getValue() + && obj!=feature && obj->isDerivedFrom(PartDesign::Feature::getClassTypeId())) obj->Visibility.setValue(false); } } - + std::vector result = {feature}; return result; } std::vector< App::DocumentObject* > Body::addObjects(std::vector< App::DocumentObject* > objs) { - + for(auto obj : objs) addObject(obj); - + return objs; } @@ -298,7 +298,7 @@ void Body::insertObject(App::DocumentObject* feature, App::DocumentObject* targe if (target && !hasObject (target)) { throw Base::ValueError("Body: the feature we should insert relative to is not part of that body"); } - + //ensure that all origin links are ok relinkToOrigin(feature); @@ -445,7 +445,7 @@ void Body::onSettingDocument() { void Body::onChanged (const App::Property* prop) { // we neither load a project nor perform undo/redo - if (!this->isRestoring() + if (!this->isRestoring() && this->getDocument() && !this->getDocument()->isPerformingTransaction()) { if (prop == &BaseFeature) { @@ -506,7 +506,7 @@ std::vector Body::getSubObjects(int reason) const { return {}; } -App::DocumentObject *Body::getSubObject(const char *subname, +App::DocumentObject *Body::getSubObject(const char *subname, PyObject **pyObj, Base::Matrix4D *pmat, bool transform, int depth) const { #if 1 @@ -525,8 +525,8 @@ App::DocumentObject *Body::getSubObject(const char *subname, // We return the shape only if there are feature visible inside for(auto obj : Group.getValues()) { - if(obj->Visibility.getValue() && - obj->isDerivedFrom(PartDesign::Feature::getClassTypeId())) + if(obj->Visibility.getValue() && + obj->isDerivedFrom(PartDesign::Feature::getClassTypeId())) { return Part::BodyBase::getSubObject(subname,pyObj,pmat,transform,depth); } @@ -546,3 +546,14 @@ void Body::onDocumentRestored() _GroupTouched.setStatus(App::Property::Output,true); DocumentObject::onDocumentRestored(); } + +// a body is solid if it has features that are solid +bool Body::isSolid() +{ + std::vector features = getFullModel(); + for (auto it = features.begin(); it!=features.end(); ++it){ + if (isSolidFeature((*it))) + return true; + } + return false; +} diff --git a/src/Mod/PartDesign/App/Body.h b/src/Mod/PartDesign/App/Body.h index 3ba7bfbbc7..d26852987d 100644 --- a/src/Mod/PartDesign/App/Body.h +++ b/src/Mod/PartDesign/App/Body.h @@ -118,7 +118,7 @@ public: PyObject *getPyObject(void) override; virtual std::vector getSubObjects(int reason=0) const override; - virtual App::DocumentObject *getSubObject(const char *subname, + virtual App::DocumentObject *getSubObject(const char *subname, PyObject **pyObj, Base::Matrix4D *pmat, bool transform, int depth) const override; void setShowTip(bool enable) { @@ -137,6 +137,9 @@ public: */ App::DocumentObject *getNextSolidFeature(App::DocumentObject* start = NULL); + // a body is solid if it has features that are solid according to member isSolidFeature. + bool isSolid(void); + protected: virtual void onSettingDocument() override; diff --git a/src/Mod/PartDesign/App/CMakeLists.txt b/src/Mod/PartDesign/App/CMakeLists.txt index 112a4ab12b..a623563930 100644 --- a/src/Mod/PartDesign/App/CMakeLists.txt +++ b/src/Mod/PartDesign/App/CMakeLists.txt @@ -109,6 +109,8 @@ SET(FeaturesSketchBased_SRCS FeaturePipe.cpp FeatureLoft.h FeatureLoft.cpp + FeatureHelix.h + FeatureHelix.cpp ) SOURCE_GROUP("SketchBasedFeatures" FILES ${FeaturesSketchBased_SRCS}) diff --git a/src/Mod/PartDesign/App/FeatureHelix.cpp b/src/Mod/PartDesign/App/FeatureHelix.cpp new file mode 100644 index 0000000000..9a8dbc8e3c --- /dev/null +++ b/src/Mod/PartDesign/App/FeatureHelix.cpp @@ -0,0 +1,553 @@ +/*************************************************************************** + * Copyright (c) 2010 Juergen Riegel * + * 2020 David Österberg * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif + +# include +# include +# include +# include +# include +# include + +# include +# include + +# include "FeatureHelix.h" + +const double PI = 3.14159265359; + +using namespace PartDesign; + +const char* Helix::ModeEnums[] = {"pitch-height", "pitch-turns", "height-turns", NULL}; + +PROPERTY_SOURCE(PartDesign::Helix, PartDesign::ProfileBased) + +Helix::Helix() +{ + addSubType = FeatureAddSub::Additive; + + ADD_PROPERTY_TYPE(Base,(Base::Vector3d(0.0,0.0,0.0)),"Helix", App::Prop_ReadOnly, "Base"); + ADD_PROPERTY_TYPE(Axis,(Base::Vector3d(0.0,1.0,0.0)),"Helix", App::Prop_ReadOnly, "Axis"); + ADD_PROPERTY_TYPE(Pitch,(10.),"Helix", App::Prop_None, "Pitch"); + ADD_PROPERTY_TYPE(Height,(30.0),"Helix", App::Prop_None, "Height"); + ADD_PROPERTY_TYPE(Turns,(3.0),"Helix", App::Prop_None, "Turns"); + ADD_PROPERTY_TYPE(LeftHanded,(long(0)),"Helix", App::Prop_None, "LeftHanded"); + ADD_PROPERTY_TYPE(Reversed,(long(0)),"Helix", App::Prop_None, "Reversed"); + ADD_PROPERTY_TYPE(Angle,(0.0),"Helix", App::Prop_None, "Angle"); + ADD_PROPERTY_TYPE(ReferenceAxis,(0),"Helix", App::Prop_None, "Reference axis of revolution"); + ADD_PROPERTY_TYPE(Mode, (long(0)), "Helix", App::Prop_None, "Helix input mode"); + ADD_PROPERTY_TYPE(Outside,(long(0)),"Helix", App::Prop_None, "Outside"); + ADD_PROPERTY_TYPE(HasBeenEdited,(long(0)),"Helix", App::Prop_None, "HasBeenEdited"); + Mode.setEnums(ModeEnums); + +} + +short Helix::mustExecute() const +{ + if (Placement.isTouched() || + ReferenceAxis.isTouched() || + Axis.isTouched() || + Base.isTouched() || + Angle.isTouched()) + return 1; + return ProfileBased::mustExecute(); +} + +App::DocumentObjectExecReturn *Helix::execute(void) +{ + // Validate and normalize parameters + switch (Mode.getValue()) { + case 0: // pitch - height + if (Pitch.getValue() < Precision::Confusion()) + return new App::DocumentObjectExecReturn("Error: Pitch too small"); + if (Height.getValue() < Precision::Confusion()) + return new App::DocumentObjectExecReturn("Error: height too small!"); + break; + case 1: // pitch - turns + if (Pitch.getValue() < Precision::Confusion()) + return new App::DocumentObjectExecReturn("Error: pitch too small!"); + if (Turns.getValue() < Precision::Confusion()) + return new App::DocumentObjectExecReturn("Error: turns too small!"); + Height.setValue(Turns.getValue()*Pitch.getValue()); + break; + case 2: // height - turns + if (Height.getValue() < Precision::Confusion()) + return new App::DocumentObjectExecReturn("Error: height too small!"); + if (Turns.getValue() < Precision::Confusion()) + return new App::DocumentObjectExecReturn("Error turns too small!"); + Pitch.setValue(Height.getValue()/Turns.getValue()); + break; + default: + return new App::DocumentObjectExecReturn("Error: unsupported mode"); + } + + TopoDS_Shape sketchshape; + try { + sketchshape = getVerifiedFace(); + } catch (const Base::Exception& e) { + return new App::DocumentObjectExecReturn(e.what()); + } + + if (sketchshape.IsNull()) + return new App::DocumentObjectExecReturn("Error: No valid sketch or face"); + else { + //TODO: currently we only allow planar faces. the reason for this is that with other faces in front, we could + //not use the current simulate approach and build the start and end face from the wires. As the shell + //begins always at the spine and not the profile, the sketchshape cannot be used directly as front face. + //We would need a method to translate the front shape to match the shell starting position somehow... + TopoDS_Face face = TopoDS::Face(sketchshape); + BRepAdaptor_Surface adapt(face); + if(adapt.GetType() != GeomAbs_Plane) + return new App::DocumentObjectExecReturn("Error: Face must be planar"); + } + + // if the Base property has a valid shape, fuse the AddShape into it + TopoDS_Shape base; + try { + base = getBaseShape(); + } catch (const Base::Exception&) { + // fall back to support (for legacy features) + base = TopoDS_Shape(); + } + + + // update Axis from ReferenceAxis + try { + updateAxis(); + } catch (const Base::Exception& e) { + return new App::DocumentObjectExecReturn(e.what()); + } + + // get revolve axis + Base::Vector3d b = Base.getValue(); + gp_Pnt pnt(b.x,b.y,b.z); + Base::Vector3d v = Axis.getValue(); + gp_Dir dir(v.x,v.y,v.z); + + try { + this->positionByPrevious(); + TopLoc_Location invObjLoc = this->getLocation().Inverted(); + + base.Move(invObjLoc); + + // generate the helix path + TopoDS_Shape path = generateHelixPath(); + + // Below is basically a copy paste (with some simplification) from FeaturePipe.cpp Pipe::execute + // TODO: find a way to reduce code repetition. E.g can I rip out this functionality of Pipe:execute to a static helper + // function and call from here? + + std::vector wires; + try { + wires = getProfileWires(); + } catch (const Base::Exception& e) { + return new App::DocumentObjectExecReturn(e.what()); + } + + std::vector> wiresections; + for(TopoDS_Wire& wire : wires) + wiresections.emplace_back(1, wire); + + //maybe we need a scaling law + Handle(Law_Function) scalinglaw; + + //build all shells + std::vector shells; + std::vector frontwires, backwires; + for(std::vector& wires : wiresections) { + + BRepOffsetAPI_MakePipeShell mkPS(TopoDS::Wire(path)); + + mkPS.SetTolerance(Precision::Confusion()); + mkPS.SetTransitionMode(BRepBuilderAPI_Transformed); + + mkPS.SetMode(true); //This is for frenet + //mkPipeShell.SetMode(TopoDS::Wire(auxpath), true); // this is for two rails + + + if(!scalinglaw) { + for(TopoDS_Wire& wire : wires) { + wire.Move(invObjLoc); + mkPS.Add(wire); + } + } + else { + for(TopoDS_Wire& wire : wires) { + wire.Move(invObjLoc); + mkPS.SetLaw(wire, scalinglaw); + } + } + + if (!mkPS.IsReady()) + return new App::DocumentObjectExecReturn("Error: Could not build"); + + shells.push_back(mkPS.Shape()); + + + if (!mkPS.Shape().Closed()) { + // shell is not closed - use simulate to get the end wires + TopTools_ListOfShape sim; + mkPS.Simulate(2, sim); + + frontwires.push_back(TopoDS::Wire(sim.First())); + backwires.push_back(TopoDS::Wire(sim.Last())); + } + } + + BRepBuilderAPI_MakeSolid mkSolid; + + if (!frontwires.empty()) { + // build the end faces, sew the shell and build the final solid + TopoDS_Shape front = Part::FaceMakerCheese::makeFace(frontwires); + TopoDS_Shape back = Part::FaceMakerCheese::makeFace(backwires); + + BRepBuilderAPI_Sewing sewer; + sewer.SetTolerance(Precision::Confusion()); + sewer.Add(front); + sewer.Add(back); + + for(TopoDS_Shape& s : shells) + sewer.Add(s); + + sewer.Perform(); + mkSolid.Add(TopoDS::Shell(sewer.SewedShape())); + } else { + // shells are already closed - add them directly + for (TopoDS_Shape& s : shells) { + mkSolid.Add(TopoDS::Shell(s)); + } + } + + if(!mkSolid.IsDone()) + return new App::DocumentObjectExecReturn("Error: Result is not a solid"); + + TopoDS_Shape result = mkSolid.Shape(); + BRepClass3d_SolidClassifier SC(result); + SC.PerformInfinitePoint(Precision::Confusion()); + if (SC.State() == TopAbs_IN) + result.Reverse(); + + AddSubShape.setValue(result); + + + if(base.IsNull()) { + + if (getAddSubType() == FeatureAddSub::Subtractive) + return new App::DocumentObjectExecReturn("Error: There is nothing to subtract\n"); + + int solidCount = countSolids(result); + if (solidCount > 1) { + return new App::DocumentObjectExecReturn("Error: Result has multiple solids"); + } + Shape.setValue(getSolid(result)); + return App::DocumentObject::StdReturn; + } + + if(getAddSubType() == FeatureAddSub::Additive) { + + BRepAlgoAPI_Fuse mkFuse(base, result); + if (!mkFuse.IsDone()) + return new App::DocumentObjectExecReturn("Error: Adding the helix failed"); + // we have to get the solids (fuse sometimes creates compounds) + TopoDS_Shape boolOp = this->getSolid(mkFuse.Shape()); + // lets check if the result is a solid + if (boolOp.IsNull()) + return new App::DocumentObjectExecReturn("Error: Result is not a solid"); + + int solidCount = countSolids(boolOp); + if (solidCount > 1) { + return new App::DocumentObjectExecReturn("Error: Result has multiple solids"); + } + + boolOp = refineShapeIfActive(boolOp); + Shape.setValue(getSolid(boolOp)); + } + else if(getAddSubType() == FeatureAddSub::Subtractive) { + + TopoDS_Shape boolOp; + + if (Outside.getValue()) { // are we subtracting the inside or the outside of the profile. + BRepAlgoAPI_Common mkCom(result, base); + if (!mkCom.IsDone()) + return new App::DocumentObjectExecReturn("Error: Intersecting the helix failed"); + boolOp = this->getSolid(mkCom.Shape()); + + } else { + BRepAlgoAPI_Cut mkCut(base, result); + if (!mkCut.IsDone()) + return new App::DocumentObjectExecReturn("Error: Subtracting the helix failed"); + boolOp = this->getSolid(mkCut.Shape()); + } + + // lets check if the result is a solid + if (boolOp.IsNull()) + return new App::DocumentObjectExecReturn("Error: Result is not a solid"); + + int solidCount = countSolids(boolOp); + if (solidCount > 1) { + return new App::DocumentObjectExecReturn("Error: Result has multiple solids"); + } + + boolOp = refineShapeIfActive(boolOp); + Shape.setValue(getSolid(boolOp)); + } + + return App::DocumentObject::StdReturn; + } + catch (Standard_Failure& e) { + + if (std::string(e.GetMessageString()) == "TopoDS::Face") + return new App::DocumentObjectExecReturn("Error: Could not create face from sketch"); + else + return new App::DocumentObjectExecReturn(e.GetMessageString()); + } + catch (Base::Exception& e) { + return new App::DocumentObjectExecReturn(e.what()); + } +} + + + +void Helix::updateAxis(void) +{ + App::DocumentObject *pcReferenceAxis = ReferenceAxis.getValue(); + const std::vector &subReferenceAxis = ReferenceAxis.getSubValues(); + Base::Vector3d base; + Base::Vector3d dir; + getAxis(pcReferenceAxis, subReferenceAxis, base, dir, false); + + Base.setValue(base.x,base.y,base.z); + Axis.setValue(dir.x,dir.y,dir.z); +} + + +TopoDS_Shape Helix::generateHelixPath(void) +{ + double pitch = Pitch.getValue(); + double height = Height.getValue(); + bool leftHanded = LeftHanded.getValue(); + bool reversed = Reversed.getValue(); + double angle = Angle.getValue(); + if (angle < Precision::Confusion() && angle > -Precision::Confusion()) + angle = 0.0; + + // get revolve axis + Base::Vector3d b = Base.getValue(); + gp_Pnt pnt(b.x,b.y,b.z); + Base::Vector3d v = Axis.getValue(); + gp_Dir dir(v.x,v.y,v.z); + + Base::Vector3d normal = getProfileNormal(); + Base::Vector3d start = v.Cross(normal); // pointing towards the desired helix start point. + gp_Dir dir_start(start.x, start.y, start.z); + + // Determine radius as the minimum distance between sketchshape and axis. + // also find out in what quadrant relative to the axis the profile is located. + double radius = 1e99; + bool turned = false; + double startOffset = 1e99; + TopoDS_Shape sketchshape = getVerifiedFace(); + BRepBuilderAPI_MakeEdge axisEdge(gp_Lin(pnt, dir)); + BRepBuilderAPI_MakeEdge startEdge(gp_Lin(pnt, dir_start)); + for (TopExp_Explorer xp(sketchshape, TopAbs_FACE); xp.More(); xp.Next()) { + const TopoDS_Face face = TopoDS::Face(xp.Current()); + TopoDS_Wire wire = ShapeAnalysis::OuterWire(face); + BRepExtrema_DistShapeShape distR(wire, axisEdge.Shape(), Precision::Confusion()); + if (distR.IsDone()) { + if (distR.Value() < radius) { + radius = distR.Value(); + const gp_Pnt p1 = distR.PointOnShape1(1); + const gp_Pnt p2 = distR.PointOnShape2(1); + double offsetProfile = p1.X()*dir_start.X() + p1.Y()*dir_start.Y() + p1.Z()*dir_start.Z(); + double offsetAxis = p2.X()*dir_start.X() + p2.Y()*dir_start.Y() + p2.Z()*dir_start.Z(); + turned = (offsetProfile < offsetAxis); + } + } + BRepExtrema_DistShapeShape distStart(wire, startEdge.Shape(), Precision::Confusion()); + if (distStart.IsDone()) { + if (distStart.Value() < abs(startOffset)) { + const gp_Pnt p1 = distStart.PointOnShape1(1); + const gp_Pnt p2 = distStart.PointOnShape2(1); + double offsetProfile = p1.X()*dir.X() + p1.Y()*dir.Y() + p1.Z()*dir.Z(); + double offsetAxis = p2.X()*dir.X() + p2.Y()*dir.Y() + p2.Z()*dir.Z(); + startOffset = offsetProfile - offsetAxis; + } + } + + } + + if (radius < Precision::Confusion()) { + // in this case ensure that axis is not in the sketch plane + if (v*normal < Precision::Confusion()) + throw Base::ValueError("Error: Result is self intersecting"); + radius = 1.0; //fallback to radius 1 + startOffset = 0.0; + } + + //build the helix path + TopoShape helix = TopoShape().makeLongHelix(pitch, height, radius, angle, leftHanded); + TopoDS_Shape path = helix.getShape(); + + + /* + * The helix wire is created with the axis coinciding with z-axis and the start point at (radius, 0, 0) + * We want to move it so that the axis becomes aligned with "dir" and "pnt", we also want (radius,0,0) to + * map to the sketch plane. + */ + + + gp_Pnt origo(0.0, 0.0, 0.0); + gp_Dir dir_axis1(0.0, 0.0, 1.0); // pointing along the helix axis, as created. + gp_Dir dir_axis2(1.0, 0.0, 0.0); // pointing towards the helix start point, as created. + + gp_Trsf mov; + + + if (reversed) { + mov.SetRotation(gp_Ax1(origo, dir_axis2), PI); + TopLoc_Location loc(mov); + path.Move(loc); + } + + if (abs(startOffset) > 0) { // translate the helix so that the starting point aligns with the profile + mov.SetTranslation(startOffset*gp_Vec(dir_axis1)); + TopLoc_Location loc(mov); + path.Move(loc); + } + + if (turned) { // turn the helix so that the starting point aligns with the profile + mov.SetRotation(gp_Ax1(origo, dir_axis1), PI); + TopLoc_Location loc(mov); + path.Move(loc); + } + + gp_Ax3 sourceCS(origo, dir_axis1, dir_axis2); + gp_Ax3 targetCS(pnt, dir, dir_start); + + mov.SetTransformation(sourceCS, targetCS); + TopLoc_Location loc(mov); + path.Move(loc.Inverted()); + + +# if OCC_VERSION_HEX < 0x70500 + /* I initially tried using path.Move(invObjLoc) like usual. But it does not give the right result + * The starting point of the helix is not correct and I don't know why! With below hack it works. + */ + Base::Vector3d placeAxis; + double placeAngle; + this->Placement.getValue().getRotation().getValue(placeAxis, placeAngle); + gp_Dir placeDir(placeAxis.x, placeAxis.y, placeAxis.z); + mov.SetRotation(gp_Ax1(origo, placeDir), placeAngle); + TopLoc_Location loc2(mov); + path.Move(loc2.Inverted()); +# else + TopLoc_Location invObjLoc = this->getLocation().Inverted(); + path.Move(invObjLoc); +# endif + + return path; +} + +// this function calculates self intersection safe pitch based on the profile bounding box. +double Helix::safePitch() +{ + // Below is an approximation. It is possible to do the general way by solving for the pitch + // where the helix is self intersecting. + + double angle = Angle.getValue()/180.0*PI; + + TopoDS_Shape sketchshape = getVerifiedFace(); + Bnd_Box bb; + BRepBndLib::Add(sketchshape, bb); + + double Xmin, Ymin, Zmin, Xmax, Ymax, Zmax; + bb.Get(Xmin, Ymin, Zmin, Xmax, Ymax, Zmax); + + double X = Xmax - Xmin, Y = Ymax - Ymin, Z = Zmax - Zmin; + + Base::Vector3d v = Axis.getValue(); + gp_Dir dir(v.x,v.y,v.z); + gp_Vec bbvec(X, Y, Z); + + double p0 = bbvec*dir; // safe pitch if angle=0 + + Base::Vector3d n = getProfileNormal(); + Base::Vector3d s = v.Cross(n); // pointing towards the desired helix start point. + gp_Dir dir_s(s.x, s.y, s.z); + + if (tan(abs(angle))*p0 > abs(bbvec*dir_s)) + return abs(bbvec*dir_s)/tan(abs(angle)); + else + return p0; +} + +// this function proposes pitch and height +void Helix::proposeParameters(bool force) +{ + if (force || !HasBeenEdited.getValue()) { + double pitch = 1.1*safePitch(); + Pitch.setValue(pitch); + Height.setValue(pitch*3.0); + HasBeenEdited.setValue(1); + } +} + + +PROPERTY_SOURCE(PartDesign::AdditiveHelix, PartDesign::Helix) +AdditiveHelix::AdditiveHelix() { + addSubType = Additive; +} + +PROPERTY_SOURCE(PartDesign::SubtractiveHelix, PartDesign::Helix) +SubtractiveHelix::SubtractiveHelix() { + addSubType = Subtractive; +} diff --git a/src/Mod/PartDesign/App/FeatureHelix.h b/src/Mod/PartDesign/App/FeatureHelix.h new file mode 100644 index 0000000000..ad5fad0111 --- /dev/null +++ b/src/Mod/PartDesign/App/FeatureHelix.h @@ -0,0 +1,103 @@ +/*************************************************************************** + * Copyright (c) 2010 Juergen Riegel * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#ifndef PARTDESIGN_Helix_H +#define PARTDESIGN_Helix_H + +#include +#include "FeatureSketchBased.h" +#include + +namespace PartDesign +{ + +class PartDesignExport Helix : public ProfileBased +{ + PROPERTY_HEADER(PartDesign::Helix); + +public: + Helix(); + + App::PropertyVector Base; + App::PropertyVector Axis; + App::PropertyLength Pitch; + App::PropertyLength Height; + App::PropertyFloat Turns; + App::PropertyBool LeftHanded; + App::PropertyAngle Angle; + App::PropertyEnumeration Mode; + App::PropertyBool Outside; + App::PropertyBool HasBeenEdited; + + /** if this property is set to a valid link, both Axis and Base properties + * are calculated according to the linked line + */ + App::PropertyLinkSub ReferenceAxis; + + /** @name methods override feature */ + //@{ + App::DocumentObjectExecReturn *execute(void); + short mustExecute() const; + /// returns the type name of the view provider + const char* getViewProviderName(void) const { + return "PartDesignGui::ViewProviderHelix"; + } + //@} + + void proposeParameters(bool force = false); + double safePitch(void); + +protected: + /// updates Axis from ReferenceAxis + void updateAxis(void); + + /// generate helix and move it to the right location. + TopoDS_Shape generateHelixPath(void); + + // project shape on plane. Used for detecting self intersection. + TopoDS_Shape projectShape(const TopoDS_Shape& input, const gp_Ax2& plane); + +private: + static const char* ModeEnums[]; +}; + + +class PartDesignExport AdditiveHelix : public Helix { + + PROPERTY_HEADER(PartDesign::AdditiveHelix); +public: + AdditiveHelix(); +}; + + +class PartDesignExport SubtractiveHelix : public Helix { + + PROPERTY_HEADER(PartDesign::SubtractiveHelix); +public: + SubtractiveHelix(); +}; + +} //namespace PartDesign + + +#endif // PART_Helix_H diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.cpp b/src/Mod/PartDesign/App/FeatureSketchBased.cpp index 8fe9f1dc58..f92b3299fc 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.cpp +++ b/src/Mod/PartDesign/App/FeatureSketchBased.cpp @@ -208,7 +208,7 @@ TopoDS_Shape ProfileBased::getVerifiedFace(bool silent) const { if(faces.empty()) { if(!shape.hasSubShape(TopAbs_WIRE)) shape = shape.makEWires(); - if(shape.hasSubShape(TopAbs_WIRE)) + if(shape.hasSubShape(TopAbs_WIRE)) shape = shape.makEFace(0,"Part::FaceMakerCheese"); else err = "Cannot make face from profile"; @@ -1012,7 +1012,7 @@ double ProfileBased::getReversedAngle(const Base::Vector3d &b, const Base::Vecto } void ProfileBased::getAxis(const App::DocumentObject *pcReferenceAxis, const std::vector &subReferenceAxis, - Base::Vector3d& base, Base::Vector3d& dir) + Base::Vector3d& base, Base::Vector3d& dir, bool checkPerpendicular) { dir = Base::Vector3d(0,0,0); // If unchanged signals that no valid axis was found if (pcReferenceAxis == NULL) @@ -1071,7 +1071,7 @@ void ProfileBased::getAxis(const App::DocumentObject *pcReferenceAxis, const std dir = line->getDirection(); // Check that axis is perpendicular with sketch plane! - if (sketchplane.Axis().Direction().IsParallel(gp_Dir(dir.x, dir.y, dir.z), Precision::Angular())) + if (checkPerpendicular && sketchplane.Axis().Direction().IsParallel(gp_Dir(dir.x, dir.y, dir.z), Precision::Angular())) throw Base::ValueError("Rotation axis must not be perpendicular with the sketch plane"); return; } @@ -1082,7 +1082,7 @@ void ProfileBased::getAxis(const App::DocumentObject *pcReferenceAxis, const std line->Placement.getValue().multVec(Base::Vector3d (1,0,0), dir); // Check that axis is perpendicular with sketch plane! - if (sketchplane.Axis().Direction().IsParallel(gp_Dir(dir.x, dir.y, dir.z), Precision::Angular())) + if (checkPerpendicular && sketchplane.Axis().Direction().IsParallel(gp_Dir(dir.x, dir.y, dir.z), Precision::Angular())) throw Base::ValueError("Rotation axis must not be perpendicular with the sketch plane"); return; } diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.h b/src/Mod/PartDesign/App/FeatureSketchBased.h index 207bde6d85..7e13019ba4 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.h +++ b/src/Mod/PartDesign/App/FeatureSketchBased.h @@ -78,7 +78,7 @@ public: * Default is false. */ Part::Part2DObject* getVerifiedSketch(bool silent=false) const; - + /** * Verifies the linked Profile and returns it if it is a valid object * @param silent if profile property is malformed and the parameter is true @@ -86,7 +86,7 @@ public: * Default is false. */ Part::Feature* getVerifiedObject(bool silent=false) const; - + /** * Verifies the linked Object and returns the shape used as profile * @param silent if profirle property is malformed and the parameter is true @@ -94,25 +94,25 @@ public: * Default is false. */ TopoDS_Shape getVerifiedFace(bool silent = false) const; - + /// Returns the wires the sketch is composed of std::vector getProfileWires() const; - + /// Returns the face of the sketch support (if any) const TopoDS_Face getSupportFace() const; - + Base::Vector3d getProfileNormal() const; Part::TopoShape getProfileShape() const; /// retrieves the number of axes in the linked sketch (defined as construction lines) - int getSketchAxisCount(void) const; + int getSketchAxisCount(void) const; virtual Part::Feature* getBaseObject(bool silent=false) const; - + //backwards compatibility: profile property was renamed and has different type now virtual void Restore(Base::XMLReader& reader); - + protected: void remapSupportShape(const TopoDS_Shape&); @@ -167,8 +167,8 @@ protected: double getReversedAngle(const Base::Vector3d& b, const Base::Vector3d& v); /// get Axis from ReferenceAxis void getAxis(const App::DocumentObject* pcReferenceAxis, const std::vector& subReferenceAxis, - Base::Vector3d& base, Base::Vector3d& dir); - + Base::Vector3d& base, Base::Vector3d& dir, bool checkPerpendicular=true); + void onChanged(const App::Property* prop); private: bool isParallelPlane(const TopoDS_Shape&, const TopoDS_Shape&) const; diff --git a/src/Mod/PartDesign/Gui/AppPartDesignGui.cpp b/src/Mod/PartDesign/Gui/AppPartDesignGui.cpp index 880ffddefb..32fcab7467 100644 --- a/src/Mod/PartDesign/Gui/AppPartDesignGui.cpp +++ b/src/Mod/PartDesign/Gui/AppPartDesignGui.cpp @@ -61,6 +61,7 @@ #include "ViewProviderThickness.h" #include "ViewProviderPipe.h" #include "ViewProviderLoft.h" +#include "ViewProviderHelix.h" #include "ViewProviderShapeBinder.h" #include "ViewProviderBase.h" @@ -156,6 +157,7 @@ PyMOD_INIT_FUNC(PartDesignGui) PartDesignGui::ViewProviderPrimitive ::init(); PartDesignGui::ViewProviderPipe ::init(); PartDesignGui::ViewProviderLoft ::init(); + PartDesignGui::ViewProviderHelix ::init(); PartDesignGui::ViewProviderBase ::init(); // add resources and reloads the translators diff --git a/src/Mod/PartDesign/Gui/CMakeLists.txt b/src/Mod/PartDesign/Gui/CMakeLists.txt index 292221c8c6..2a2ce18401 100644 --- a/src/Mod/PartDesign/Gui/CMakeLists.txt +++ b/src/Mod/PartDesign/Gui/CMakeLists.txt @@ -55,6 +55,7 @@ set(PartDesignGui_MOC_HDRS TaskPrimitiveParameters.h TaskPipeParameters.h TaskLoftParameters.h + TaskHelixParameters.h ) fc_wrap_cpp(PartDesignGui_MOC_SRCS ${PartDesignGui_MOC_HDRS}) SOURCE_GROUP("Moc" FILES ${PartDesignGui_MOC_SRCS}) @@ -89,6 +90,7 @@ set(PartDesignGui_UIC_SRCS TaskPipeScaling.ui TaskLoftParameters.ui DlgReference.ui + TaskHelixParameters.ui ) if(BUILD_QT5) @@ -158,7 +160,9 @@ SET(PartDesignGuiViewProvider_SRCS ViewProviderPipe.cpp ViewProviderLoft.h ViewProviderLoft.cpp - ViewProviderBase.h + ViewProviderHelix.h + ViewProviderHelix.cpp + ViewProviderBase.h ViewProviderBase.cpp ) SOURCE_GROUP("ViewProvider" FILES ${PartDesignGuiViewProvider_SRCS}) @@ -237,6 +241,9 @@ SET(PartDesignGuiTaskDlgs_SRCS TaskLoftParameters.ui TaskLoftParameters.h TaskLoftParameters.cpp + TaskHelixParameters.ui + TaskHelixParameters.h + TaskHelixParameters.cpp ) SOURCE_GROUP("TaskDialogs" FILES ${PartDesignGuiTaskDlgs_SRCS}) diff --git a/src/Mod/PartDesign/Gui/Command.cpp b/src/Mod/PartDesign/Gui/Command.cpp index 22be4e650b..547821c59b 100644 --- a/src/Mod/PartDesign/Gui/Command.cpp +++ b/src/Mod/PartDesign/Gui/Command.cpp @@ -55,6 +55,7 @@ #include #include #include + #include #include #include @@ -384,7 +385,7 @@ void CmdPartDesignSubShapeBinder::activated(int iMsg) } values = std::move(links); } - + PartDesign::SubShapeBinder *binder = 0; try { openCommand(QT_TRANSLATE_NOOP("Command", "Create SubShapeBinder")); @@ -403,7 +404,7 @@ void CmdPartDesignSubShapeBinder::activated(int iMsg) commitCommand(); } catch (Base::Exception &e) { e.ReportException(); - QMessageBox::critical(Gui::getMainWindow(), + QMessageBox::critical(Gui::getMainWindow(), QObject::tr("Sub-Shape Binder"), QString::fromUtf8(e.what())); abortCommand(); } @@ -994,7 +995,7 @@ void prepareProfileBased(PartDesign::Body *pcActiveBody, Gui::Command* cmd, cons FCMD_OBJ_CMD(pcActiveBody,"newObject('PartDesign::" << which << "','" << FeatName << "')"); auto Feat = pcActiveBody->getDocument()->getObject(FeatName.c_str()); - + auto objCmd = Gui::Command::getObjectCmd(feature); if (feature->isDerivedFrom(Part::Part2DObject::getClassTypeId()) || subs.empty()) { FCMD_OBJ_CMD(Feat,"Profile = " << objCmd); @@ -1003,7 +1004,7 @@ void prepareProfileBased(PartDesign::Body *pcActiveBody, Gui::Command* cmd, cons std::ostringstream ss; for (auto &s : subs) ss << "'" << s << "',"; - FCMD_OBJ_CMD(Feat,"Profile = (" << objCmd << ", [" << ss.str() << "])"); + FCMD_OBJ_CMD(Feat,"Profile = (" << objCmd << ", [" << ss.str() << "])"); } //for additive and subtractive lofts allow the user to preselect the sections @@ -1042,10 +1043,44 @@ void prepareProfileBased(PartDesign::Body *pcActiveBody, Gui::Command* cmd, cons func(static_cast(feature), Feat); }; + + // in case of subtractive types, check that there is something to subtract from + if ((which.find("Subtractive") != std::string::npos) || + (which.compare("Groove") == 0) || + (which.compare("Pocket") == 0)) { + + if (!pcActiveBody->isSolid()) { + QMessageBox msgBox; + msgBox.setText(QObject::tr("Cannot use this command as there is no solid to subtract from.")); + msgBox.setInformativeText(QObject::tr("Ensure that the body contains a feature before attempting a subtractive command.")); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setDefaultButton(QMessageBox::Ok); + msgBox.exec(); + return; + } + } + + //if a profile is selected we can make our life easy and fast std::vector selection = cmd->getSelection().getSelectionEx(); if (!selection.empty()) { - base_worker(selection.front().getObject(), selection.front().getSubNames()); + bool onlyAllowed = true; + for (auto it = selection.begin(); it!=selection.end(); ++it){ + if (PartDesign::Body::findBodyOf((*it).getObject()) != pcActiveBody) { // the selected objects must belong to the body + onlyAllowed = false; + break; + } + } + if (!onlyAllowed) { + QMessageBox msgBox; + msgBox.setText(QObject::tr("Cannot use selected object. Selected object must belong to the active body")); + msgBox.setInformativeText(QObject::tr("Consider using a ShapeBinder or a BaseFeature to reference external geometry in a body.")); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setDefaultButton(QMessageBox::Ok); + msgBox.exec(); + } else { + base_worker(selection.front().getObject(), selection.front().getSubNames()); + } return; } @@ -1418,7 +1453,7 @@ void CmdPartDesignGroove::activated(int iMsg) else { FCMD_OBJ_CMD(Feat,"ReferenceAxis = ("<getOrigin()->getY())<<",[''])"); } - + FCMD_OBJ_CMD(Feat,"Angle = 360.0"); try { @@ -1643,6 +1678,119 @@ bool CmdPartDesignSubtractiveLoft::isActive(void) return hasActiveDocument(); } +//=========================================================================== +// PartDesign_Additive_Helix +//=========================================================================== +DEF_STD_CMD_A(CmdPartDesignAdditiveHelix) + +CmdPartDesignAdditiveHelix::CmdPartDesignAdditiveHelix() + : Command("PartDesign_AdditiveHelix") +{ + sAppModule = "PartDesign"; + sGroup = QT_TR_NOOP("PartDesign"); + sMenuText = QT_TR_NOOP("Additive helix"); + sToolTipText = QT_TR_NOOP("Sweep a selected sketch along a helix"); + sWhatsThis = "PartDesign_AdditiveHelix"; + sStatusTip = sToolTipText; + sPixmap = "PartDesign_Additive_Helix"; +} + +void CmdPartDesignAdditiveHelix::activated(int iMsg) +{ + Q_UNUSED(iMsg); + App::Document *doc = getDocument(); + if (!PartDesignGui::assureModernWorkflow(doc)) + return; + + PartDesign::Body *pcActiveBody = PartDesignGui::getBody(true); + + if (!pcActiveBody) + return; + + Gui::Command* cmd = this; + auto worker = [cmd, &pcActiveBody](Part::Feature* sketch, App::DocumentObject *Feat) { + + if (!Feat) return; + + // specific parameters for helix + Gui::Command::updateActive(); + + if (sketch->isDerivedFrom(Part::Part2DObject::getClassTypeId())) { + FCMD_OBJ_CMD(Feat,"ReferenceAxis = (" << getObjectCmd(sketch) << ",['V_Axis'])"); + } + else { + FCMD_OBJ_CMD(Feat,"ReferenceAxis = (" << getObjectCmd(pcActiveBody->getOrigin()->getY()) << ",[''])"); + } + + finishProfileBased(cmd, sketch, Feat); + cmd->adjustCameraPosition(); + }; + + prepareProfileBased(pcActiveBody, this, "AdditiveHelix", worker); +} + +bool CmdPartDesignAdditiveHelix::isActive(void) +{ + return hasActiveDocument(); +} + + +//=========================================================================== +// PartDesign_Subtractive_Helix +//=========================================================================== +DEF_STD_CMD_A(CmdPartDesignSubtractiveHelix) + +CmdPartDesignSubtractiveHelix::CmdPartDesignSubtractiveHelix() + : Command("PartDesign_SubtractiveHelix") +{ + sAppModule = "PartDesign"; + sGroup = QT_TR_NOOP("PartDesign"); + sMenuText = QT_TR_NOOP("Subtractive helix"); + sToolTipText = QT_TR_NOOP("Sweep a selected sketch along a helix and remove it from the body"); + sWhatsThis = "PartDesign_SubtractiveHelix"; + sStatusTip = sToolTipText; + sPixmap = "PartDesign_Subtractive_Helix"; +} + +void CmdPartDesignSubtractiveHelix::activated(int iMsg) +{ + Q_UNUSED(iMsg); + App::Document *doc = getDocument(); + if (!PartDesignGui::assureModernWorkflow(doc)) + return; + + PartDesign::Body *pcActiveBody = PartDesignGui::getBody(true); + + if (!pcActiveBody) + return; + + Gui::Command* cmd = this; + auto worker = [cmd, &pcActiveBody](Part::Feature* sketch, App::DocumentObject *Feat) { + + if (!Feat) return; + + // specific parameters for helix + Gui::Command::updateActive(); + + if (sketch->isDerivedFrom(Part::Part2DObject::getClassTypeId())) { + FCMD_OBJ_CMD(Feat,"ReferenceAxis = (" << getObjectCmd(sketch) << ",['V_Axis'])"); + } + else { + FCMD_OBJ_CMD(Feat,"ReferenceAxis = (" << getObjectCmd(pcActiveBody->getOrigin()->getY()) << ",[''])"); + } + + finishProfileBased(cmd, sketch, Feat); + cmd->adjustCameraPosition(); + }; + + prepareProfileBased(pcActiveBody, this, "SubtractiveHelix", worker); +} + +bool CmdPartDesignSubtractiveHelix::isActive(void) +{ + return hasActiveDocument(); +} + //=========================================================================== // Common utility functions for Dressup features //=========================================================================== @@ -2176,7 +2324,7 @@ void CmdPartDesignPolarPattern::activated(int iMsg) } if (!direction) { auto body = static_cast(Part::BodyBase::findBodyOf(features.front())); - if (body) { + if (body) { FCMD_OBJ_CMD(Feat,"Axis = ("<getOrigin()->getZ())<<",[''])"); } } @@ -2397,7 +2545,7 @@ void CmdPartDesignBoolean::activated(int iMsg) std::string FeatName = getUniqueObjectName("Boolean",pcActiveBody); FCMD_OBJ_CMD(pcActiveBody,"newObject('PartDesign::Boolean','"<getDocument()->getObject(FeatName.c_str()); - + // If we don't add an object to the boolean group then don't update the body // as otherwise this will fail and it will be marked as invalid bool updateDocument = false; @@ -2456,6 +2604,8 @@ void CreatePartDesignCommands(void) rcCmdMgr.addCommand(new CmdPartDesignSubtractivePipe); rcCmdMgr.addCommand(new CmdPartDesignAdditiveLoft); rcCmdMgr.addCommand(new CmdPartDesignSubtractiveLoft); + rcCmdMgr.addCommand(new CmdPartDesignAdditiveHelix); + rcCmdMgr.addCommand(new CmdPartDesignSubtractiveHelix); rcCmdMgr.addCommand(new CmdPartDesignFillet()); rcCmdMgr.addCommand(new CmdPartDesignDraft()); diff --git a/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc b/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc index 729a9c3b9a..bb55317411 100644 --- a/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc +++ b/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc @@ -6,6 +6,7 @@ icons/PartDesign_Additive_Ellipsoid.svg icons/PartDesign_Additive_Loft.svg icons/PartDesign_Additive_Pipe.svg + icons/PartDesign_Additive_Helix.svg icons/PartDesign_Additive_Prism.svg icons/PartDesign_Additive_Sphere.svg icons/PartDesign_Additive_Torus.svg @@ -50,6 +51,7 @@ icons/PartDesign_Subtractive_Ellipsoid.svg icons/PartDesign_Subtractive_Loft.svg icons/PartDesign_Subtractive_Pipe.svg + icons/PartDesign_Subtractive_Helix.svg icons/PartDesign_Subtractive_Prism.svg icons/PartDesign_Subtractive_Sphere.svg icons/PartDesign_Subtractive_Torus.svg diff --git a/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Additive_Helix.svg b/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Additive_Helix.svg new file mode 100644 index 0000000000..ed20f4cb1c --- /dev/null +++ b/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Additive_Helix.svg @@ -0,0 +1,1456 @@ + + + PartDesign_Additive_Helix + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + PartDesign_Additive_Helix + + + bitacovir, davidosterberg + + + PartDesign_Revolution + 2020/12/30 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Subtractive_Helix.svg b/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Subtractive_Helix.svg new file mode 100644 index 0000000000..91e1d56a6d --- /dev/null +++ b/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Subtractive_Helix.svg @@ -0,0 +1,1456 @@ + + + PartDesign_Subtractive_Helix + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + PartDesign_Subtractive_Helix + + + bitacovir, davidosterberg + + + PartDesign_Revolution + 2020/12/30 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp b/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp new file mode 100644 index 0000000000..2fa03e580c --- /dev/null +++ b/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp @@ -0,0 +1,511 @@ +/*************************************************************************** + * Copyright (c) 2011 Juergen Riegel * + * 2020 David Österberg * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ReferenceSelection.h" +#include "Utils.h" + +#include "ui_TaskHelixParameters.h" +#include "TaskHelixParameters.h" + +using namespace PartDesignGui; +using namespace Gui; + + +/* TRANSLATOR PartDesignGui::TaskHelixParameters */ + +TaskHelixParameters::TaskHelixParameters(PartDesignGui::ViewProviderHelix *HelixView, QWidget *parent) + : TaskSketchBasedParameters(HelixView, parent, "PartDesign_Additive_Helix",tr("Helix parameters")), + ui (new Ui_TaskHelixParameters) +{ + // we need a separate container widget to add all controls to + proxy = new QWidget(this); + ui->setupUi(proxy); + QMetaObject::connectSlotsByName(this); + + connect(ui->pitch, SIGNAL(valueChanged(double)), + this, SLOT(onPitchChanged(double))); + connect(ui->height, SIGNAL(valueChanged(double)), + this, SLOT(onHeightChanged(double))); + connect(ui->turns, SIGNAL(valueChanged(double)), + this, SLOT(onTurnsChanged(double))); + connect(ui->coneAngle, SIGNAL(valueChanged(double)), + this, SLOT(onAngleChanged(double))); + connect(ui->axis, SIGNAL(activated(int)), + this, SLOT(onAxisChanged(int))); + connect(ui->checkBoxLeftHanded, SIGNAL(toggled(bool)), + this, SLOT(onLeftHandedChanged(bool))); + connect(ui->checkBoxReversed, SIGNAL(toggled(bool)), + this, SLOT(onReversedChanged(bool))); + connect(ui->checkBoxUpdateView, SIGNAL(toggled(bool)), + this, SLOT(onUpdateView(bool))); + connect(ui->inputMode, SIGNAL(activated(int)), + this, SLOT(onModeChanged(int))); + connect(ui->checkBoxOutside, SIGNAL(toggled(bool)), + this, SLOT(onOutsideChanged(bool))); + + this->groupLayout()->addWidget(proxy); + + // Temporarily prevent unnecessary feature recomputes + ui->axis->blockSignals(true); + ui->pitch->blockSignals(true); + ui->height->blockSignals(true); + ui->turns->blockSignals(true); + ui->coneAngle->blockSignals(true); + ui->checkBoxLeftHanded->blockSignals(true); + ui->checkBoxReversed->blockSignals(true); + ui->checkBoxOutside->blockSignals(true); + + //bind property mirrors + PartDesign::ProfileBased* pcFeat = static_cast(vp->getObject()); + + PartDesign::Helix* rev = static_cast(vp->getObject()); + + if (!(rev->HasBeenEdited).getValue()) { + rev->proposeParameters(); + recomputeFeature(); + } + + this->propAngle = &(rev->Angle); + this->propPitch = &(rev->Pitch); + this->propHeight = &(rev->Height); + this->propTurns = &(rev->Turns); + this->propReferenceAxis = &(rev->ReferenceAxis); + this->propLeftHanded = &(rev->LeftHanded); + this->propReversed = &(rev->Reversed); + this->propMode = &(rev->Mode); + this->propOutside = &(rev->Outside); + + double pitch = propPitch->getValue(); + double height = propHeight->getValue(); + double turns = propTurns->getValue(); + double angle = propAngle->getValue(); + bool leftHanded = propLeftHanded->getValue(); + bool reversed = propReversed->getValue(); + int index = propMode->getValue(); + bool outside = propOutside->getValue(); + + ui->pitch->setValue(pitch); + ui->height->setValue(height); + ui->turns->setValue(turns); + ui->coneAngle->setValue(angle); + ui->checkBoxLeftHanded->setChecked(leftHanded); + ui->checkBoxReversed->setChecked(reversed); + ui->inputMode->setCurrentIndex(index); + ui->checkBoxOutside->setChecked(outside); + + blockUpdate = false; + updateUI(); + + // enable use of parametric expressions for the numerical fields + ui->pitch->bind(static_cast(pcFeat)->Pitch); + ui->height->bind(static_cast(pcFeat)->Height); + ui->turns->bind(static_cast(pcFeat)->Turns); + ui->coneAngle->bind(static_cast(pcFeat)->Angle); + + ui->axis->blockSignals(false); + ui->pitch->blockSignals(false); + ui->height->blockSignals(false); + ui->turns->blockSignals(false); + ui->coneAngle->blockSignals(false); + ui->checkBoxLeftHanded->blockSignals(false); + ui->checkBoxReversed->blockSignals(false); + ui->checkBoxOutside->blockSignals(false); + + setFocus (); + + //show the parts coordinate system axis for selection + PartDesign::Body * body = PartDesign::Body::findBodyOf ( vp->getObject () ); + if(body) { + try { + App::Origin *origin = body->getOrigin(); + ViewProviderOrigin* vpOrigin; + vpOrigin = static_cast(Gui::Application::Instance->getViewProvider(origin)); + vpOrigin->setTemporaryVisibility(true, false); + } catch (const Base::Exception &ex) { + ex.ReportException(); + } + } +} + +void TaskHelixParameters::fillAxisCombo(bool forceRefill) +{ + bool oldVal_blockUpdate = blockUpdate; + blockUpdate = true; + + if (axesInList.empty()) + forceRefill = true;//not filled yet, full refill + + if (forceRefill){ + ui->axis->clear(); + + this->axesInList.clear(); + + //add sketch axes + PartDesign::ProfileBased* pcFeat = static_cast(vp->getObject()); + Part::Part2DObject* pcSketch = dynamic_cast(pcFeat->Profile.getValue()); + if (pcSketch){ + addAxisToCombo(pcSketch,"V_Axis",QObject::tr("Vertical sketch axis")); + addAxisToCombo(pcSketch,"H_Axis",QObject::tr("Horizontal sketch axis")); + for (int i=0; i < pcSketch->getAxisCount(); i++) { + QString itemText = QObject::tr("Construction line %1").arg(i+1); + std::stringstream sub; + sub << "Axis" << i; + addAxisToCombo(pcSketch,sub.str(),itemText); + } + } + + //add part axes + PartDesign::Body * body = PartDesign::Body::findBodyOf ( pcFeat ); + if (body) { + try { + App::Origin* orig = body->getOrigin(); + addAxisToCombo(orig->getX(),"",tr("Base X axis")); + addAxisToCombo(orig->getY(),"",tr("Base Y axis")); + addAxisToCombo(orig->getZ(),"",tr("Base Z axis")); + } catch (const Base::Exception &ex) { + ex.ReportException(); + } + } + + //add "Select reference" + addAxisToCombo(0,std::string(),tr("Select reference...")); + }//endif forceRefill + + //add current link, if not in list + //first, figure out the item number for current axis + int indexOfCurrent = -1; + App::DocumentObject* ax = propReferenceAxis->getValue(); + const std::vector &subList = propReferenceAxis->getSubValues(); + for (size_t i = 0; i < axesInList.size(); i++) { + if (ax == axesInList[i]->getValue() && subList == axesInList[i]->getSubValues()) + indexOfCurrent = i; + } + if (indexOfCurrent == -1 && ax) { + assert(subList.size() <= 1); + std::string sub; + if (!subList.empty()) + sub = subList[0]; + addAxisToCombo(ax, sub, getRefStr(ax, subList)); + indexOfCurrent = axesInList.size()-1; + } + + //highlight current. + if (indexOfCurrent != -1) + ui->axis->setCurrentIndex(indexOfCurrent); + + blockUpdate = oldVal_blockUpdate; +} + +void TaskHelixParameters::addAxisToCombo(App::DocumentObject* linkObj, + std::string linkSubname, + QString itemText) +{ + this->ui->axis->addItem(itemText); + this->axesInList.emplace_back(new App::PropertyLinkSub); + App::PropertyLinkSub &lnk = *(axesInList[axesInList.size()-1]); + lnk.setValue(linkObj,std::vector(1,linkSubname)); +} + +void TaskHelixParameters::updateUI() +{ + fillAxisCombo(); + + auto pcHelix = static_cast(vp->getObject()); + auto status = std::string(pcHelix->getStatusString()); + if (status.compare("Valid")==0 || status.compare("Touched")==0) { + if (pcHelix->safePitch() > propPitch->getValue()) + status = "Warning: helix might be self intersecting"; + else + status = ""; + } + ui->labelMessage->setText(QString::fromUtf8(status.c_str())); + + bool isPitchVisible = false; + bool isHeightVisible = false; + bool isTurnsVisible = false; + bool isOutsideVisible = false; + + if(pcHelix->getAddSubType() == PartDesign::FeatureAddSub::Subtractive) + isOutsideVisible = true; + + switch (propMode->getValue()) { + case 0: + isPitchVisible = true; + isHeightVisible = true; + break; + case 1: + isPitchVisible = true; + isTurnsVisible = true; + break; + default: + isHeightVisible = true; + isTurnsVisible = true; + } + + ui->pitch->setVisible(isPitchVisible); + ui->labelPitch->setVisible(isPitchVisible); + + ui->height->setVisible(isHeightVisible); + ui->labelHeight->setVisible(isHeightVisible); + + ui->turns->setVisible(isTurnsVisible); + ui->labelTurns->setVisible(isTurnsVisible); + + ui->checkBoxOutside->setVisible(isOutsideVisible); + +} + +void TaskHelixParameters::onSelectionChanged(const Gui::SelectionChanges& msg) +{ + if (msg.Type == Gui::SelectionChanges::AddSelection) { + + exitSelectionMode(); + std::vector axis; + App::DocumentObject* selObj; + if (getReferencedSelection(vp->getObject(), msg, selObj, axis) && selObj) { + propReferenceAxis->setValue(selObj, axis); + recomputeFeature(); + updateUI(); + } + } +} + + +void TaskHelixParameters::onPitchChanged(double len) +{ + propPitch->setValue(len); + recomputeFeature(); + updateUI(); +} + +void TaskHelixParameters::onHeightChanged(double len) +{ + propHeight->setValue(len); + recomputeFeature(); + updateUI(); +} + +void TaskHelixParameters::onTurnsChanged(double len) +{ + propTurns->setValue(len); + recomputeFeature(); + updateUI(); +} + +void TaskHelixParameters::onAngleChanged(double len) +{ + propAngle->setValue(len); + recomputeFeature(); + updateUI(); +} + +void TaskHelixParameters::onAxisChanged(int num) +{ + PartDesign::ProfileBased* pcHelix = static_cast(vp->getObject()); + + if (axesInList.empty()) + return; + + App::DocumentObject *oldRefAxis = propReferenceAxis->getValue(); + std::vector oldSubRefAxis = propReferenceAxis->getSubValues(); + std::string oldRefName; + if (!oldSubRefAxis.empty()) + oldRefName = oldSubRefAxis.front(); + + App::PropertyLinkSub &lnk = *(axesInList[num]); + if (lnk.getValue() == 0) { + // enter reference selection mode + TaskSketchBasedParameters::onSelectReference(true, true, false, true); + } else { + if (!pcHelix->getDocument()->isIn(lnk.getValue())){ + Base::Console().Error("Object was deleted\n"); + return; + } + propReferenceAxis->Paste(lnk); + exitSelectionMode(); + } + + try { + App::DocumentObject *newRefAxis = propReferenceAxis->getValue(); + const std::vector &newSubRefAxis = propReferenceAxis->getSubValues(); + std::string newRefName; + if (!newSubRefAxis.empty()) + newRefName = newSubRefAxis.front(); + + if (oldRefAxis != newRefAxis || + oldSubRefAxis.size() != newSubRefAxis.size() || + oldRefName != newRefName) { + bool reversed = propReversed->getValue(); + if (reversed != propReversed->getValue()) { + propReversed->setValue(reversed); + ui->checkBoxReversed->blockSignals(true); + ui->checkBoxReversed->setChecked(reversed); + ui->checkBoxReversed->blockSignals(false); + } + } + + recomputeFeature(); + } + catch (const Base::Exception& e) { + e.ReportException(); + } +} + +void TaskHelixParameters::onModeChanged(int index) +{ + + propMode->setValue(index); + + ui->pitch->setValue(propPitch->getValue()); + ui->height->setValue(propHeight->getValue()); + ui->turns->setValue((propHeight->getValue())/(propPitch->getValue())); + + recomputeFeature(); + updateUI(); +} + +void TaskHelixParameters::onLeftHandedChanged(bool on) +{ + propLeftHanded->setValue(on); + recomputeFeature(); +} + +void TaskHelixParameters::onReversedChanged(bool on) +{ + propReversed->setValue(on); + recomputeFeature(); + updateUI(); +} + +void TaskHelixParameters::onOutsideChanged(bool on) +{ + propOutside->setValue(on); + recomputeFeature(); + updateUI(); +} + + +TaskHelixParameters::~TaskHelixParameters() +{ + try { + //hide the parts coordinate system axis for selection + PartDesign::Body * body = vp ? PartDesign::Body::findBodyOf(vp->getObject()) : 0; + if (body) { + App::Origin *origin = body->getOrigin(); + ViewProviderOrigin* vpOrigin; + vpOrigin = static_cast(Gui::Application::Instance->getViewProvider(origin)); + vpOrigin->resetTemporaryVisibility(); + } + } catch (const Base::Exception &ex) { + ex.ReportException(); + } + +} + +void TaskHelixParameters::changeEvent(QEvent *e) +{ + TaskBox::changeEvent(e); + if (e->type() == QEvent::LanguageChange) { + ui->retranslateUi(proxy); + } +} + +void TaskHelixParameters::getReferenceAxis(App::DocumentObject*& obj, std::vector& sub) const +{ + if (axesInList.empty()) + throw Base::RuntimeError("Not initialized!"); + + int num = ui->axis->currentIndex(); + const App::PropertyLinkSub &lnk = *(axesInList[num]); + if (lnk.getValue() == 0) { + throw Base::RuntimeError("Still in reference selection mode; reference wasn't selected yet"); + } else { + PartDesign::ProfileBased* pcRevolution = static_cast(vp->getObject()); + if (!pcRevolution->getDocument()->isIn(lnk.getValue())){ + throw Base::RuntimeError("Object was deleted"); + } + + obj = lnk.getValue(); + sub = lnk.getSubValues(); + } +} + +// this is used for logging the command fully when recording macros +void TaskHelixParameters::apply() +{ + std::vector sub; + App::DocumentObject* obj; + getReferenceAxis(obj, sub); + std::string axis = buildLinkSingleSubPythonStr(obj, sub); + auto tobj = vp->getObject(); + FCMD_OBJ_CMD(tobj,"ReferenceAxis = " << axis); + FCMD_OBJ_CMD(tobj,"Mode = " << propMode->getValue()); + FCMD_OBJ_CMD(tobj,"Pitch = " << propPitch->getValue()); + FCMD_OBJ_CMD(tobj,"Height = " << propHeight->getValue()); + FCMD_OBJ_CMD(tobj,"Turns = " << propTurns->getValue()); + FCMD_OBJ_CMD(tobj,"Angle = " << propAngle->getValue()); + FCMD_OBJ_CMD(tobj,"LeftHanded = " << (propLeftHanded->getValue() ? 1 : 0)); + FCMD_OBJ_CMD(tobj,"Reversed = " << (propReversed->getValue() ? 1 : 0)); +} + + +//************************************************************************** +//************************************************************************** +// TaskDialog +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +TaskDlgHelixParameters::TaskDlgHelixParameters(ViewProviderHelix *HelixView) + : TaskDlgSketchBasedParameters(HelixView) +{ + assert(HelixView); + Content.push_back(new TaskHelixParameters(HelixView)); +} + + +#include "moc_TaskHelixParameters.cpp" diff --git a/src/Mod/PartDesign/Gui/TaskHelixParameters.h b/src/Mod/PartDesign/Gui/TaskHelixParameters.h new file mode 100644 index 0000000000..442c8376e6 --- /dev/null +++ b/src/Mod/PartDesign/Gui/TaskHelixParameters.h @@ -0,0 +1,131 @@ +/*************************************************************************** + * Copyright (c) 2011 Juergen Riegel * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#ifndef GUI_TASKVIEW_TaskHelixParameters_H +#define GUI_TASKVIEW_TaskHelixParameters_H + +#include +#include +#include + +#include "TaskSketchBasedParameters.h" +#include "ViewProviderHelix.h" + +class Ui_TaskHelixParameters; + +namespace App { +class Property; +} + +namespace Gui { +class ViewProvider; +} + +namespace PartDesignGui { + + + +class TaskHelixParameters : public TaskSketchBasedParameters +{ + Q_OBJECT + +public: + TaskHelixParameters(ViewProviderHelix *HelixView,QWidget *parent = 0); + ~TaskHelixParameters(); + + virtual void apply() override; + + /** + * @brief fillAxisCombo fills the combo and selects the item according to + * current value of revolution object's axis reference. + * @param forceRefill if true, the combo box will be completely refilled. If + * false, the current value of revolution object's axis will be added to the + * list (if necessary), and selected. If the list is empty, it will be refilled anyway. + */ + void fillAxisCombo(bool forceRefill = false); + void addAxisToCombo(App::DocumentObject *linkObj, std::string linkSubname, QString itemText); + +private Q_SLOTS: + void onPitchChanged(double); + void onHeightChanged(double); + void onTurnsChanged(double); + void onAngleChanged(double); + void onAxisChanged(int); + void onLeftHandedChanged(bool); + void onReversedChanged(bool); + void onModeChanged(int); + void onOutsideChanged(bool); + + +protected: + void onSelectionChanged(const Gui::SelectionChanges& msg) override; + void changeEvent(QEvent *e) override; + bool updateView() const; + void getReferenceAxis(App::DocumentObject *&obj, std::vector &sub) const; + + + //mirrors of helixes's properties + App::PropertyLength* propPitch; + App::PropertyLength* propHeight; + App::PropertyFloat* propTurns; + App::PropertyBool* propLeftHanded; + App::PropertyBool* propReversed; + App::PropertyLinkSub* propReferenceAxis; + App::PropertyAngle* propAngle; + App::PropertyEnumeration* propMode; + App::PropertyBool* propOutside; + + +private: + void updateUI(); + +private: + QWidget* proxy; + Ui_TaskHelixParameters* ui; + + /** + * @brief axesInList is the list of links corresponding to axis combo; must + * be kept in sync with the combo. A special value of zero-pointer link is + * for "Select axis" item. + * + * It is a list of pointers, because properties prohibit assignment. Use new + * when adding stuff, and delete when removing stuff. + */ + std::vector> axesInList; +}; + +/// simulation dialog for the TaskView +class TaskDlgHelixParameters : public TaskDlgSketchBasedParameters +{ + Q_OBJECT + +public: + TaskDlgHelixParameters(ViewProviderHelix *HelixView); + + ViewProviderHelix* getHelixView() const + { return static_cast(vp); } +}; + +} //namespace PartDesignGui + +#endif // GUI_TASKVIEW_TaskHelixParameters_H diff --git a/src/Mod/PartDesign/Gui/TaskHelixParameters.ui b/src/Mod/PartDesign/Gui/TaskHelixParameters.ui new file mode 100644 index 0000000000..ac4215ac14 --- /dev/null +++ b/src/Mod/PartDesign/Gui/TaskHelixParameters.ui @@ -0,0 +1,303 @@ + + + PartDesignGui::TaskHelixParameters + + + + 0 + 0 + 278 + 193 + + + + Form + + + + + + + + + Status: + + + + + + + Valid + + + + + + + + + + + + Axis: + + + + + + + + Base X axis + + + + + Base Y axis + + + + + Base Z axis + + + + + Horizontal sketch axis + + + + + Vertical sketch axis + + + + + Select reference... + + + + + + + + + + + + + Mode: + + + + + + + + Pitch-Height + + + + + Pitch-Turns + + + + + Height-Turns + + + + + + + + + + + + + Pitch: + + + + + + + false + + + mm + + + 0.000000000000000 + + + 1.000000000000000 + + + 10.000000000000000 + + + + + + + + + + + + Height: + + + + + + + false + + + mm + + + 0.000000000000000 + + + 1.000000000000000 + + + 30.000000000000000 + + + + + + + + + + + + Turns: + + + + + + + false + + + 0.000000000000000 + + + 1.000000000000000 + + + 3.0000000000000 + + + + + + + + + + + + Cone angle: + + + + + + + false + + + deg + + + -89.000000000000000 + + + 89.000000000000000 + + + 5.000000000000000 + + + 0.000000000000000 + + + + + + + + + + true + + + Left handed + + + + + + + + true + + + Reversed + + + + + + + + Remove outside of profile + + + false + + + + + + + + Qt::Horizontal + + + + + + + + Update view + + + true + + + + + + + + + + + Gui::QuantitySpinBox + QWidget +
      Gui/QuantitySpinBox.h
      +
      +
      + + +
      diff --git a/src/Mod/PartDesign/Gui/ViewProviderHelix.cpp b/src/Mod/PartDesign/Gui/ViewProviderHelix.cpp new file mode 100644 index 0000000000..d1ec60c4c7 --- /dev/null +++ b/src/Mod/PartDesign/Gui/ViewProviderHelix.cpp @@ -0,0 +1,120 @@ +/*************************************************************************** + * Copyright (c) 2011 Juergen Riegel * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" + +#ifndef _PreComp_ +# include +# include +#endif + +#include +#include + +#include +#include +#include + +#include "TaskHelixParameters.h" +#include "ViewProviderHelix.h" + +using namespace PartDesignGui; + +PROPERTY_SOURCE(PartDesignGui::ViewProviderHelix,PartDesignGui::ViewProvider) + + +ViewProviderHelix::ViewProviderHelix() +{ +} + +ViewProviderHelix::~ViewProviderHelix() +{ +} + +void ViewProviderHelix::setupContextMenu(QMenu* menu, QObject* receiver, const char* member) +{ + QAction* act; + act = menu->addAction(QObject::tr("Edit helix"), receiver, member); + act->setData(QVariant((int)ViewProvider::Default)); + PartDesignGui::ViewProviderAddSub::setupContextMenu(menu, receiver, member); +} + +TaskDlgFeatureParameters *ViewProviderHelix::getEditDialog() +{ + return new TaskDlgHelixParameters( this ); +} + +QIcon ViewProviderHelix::getIcon(void) const { + QString str = QString::fromLatin1("PartDesign_"); + auto* prim = static_cast(getObject()); + if(prim->getAddSubType() == PartDesign::FeatureAddSub::Additive) + str += QString::fromLatin1("Additive_"); + else + str += QString::fromLatin1("Subtractive_"); + + str += QString::fromLatin1("Helix.svg"); + return PartDesignGui::ViewProvider::mergeGreyableOverlayIcons(Gui::BitmapFactory().pixmap(str.toStdString().c_str())); +} + +bool ViewProviderHelix::setEdit(int ModNum) +{ + + if (ModNum == ViewProvider::Default ) { + auto* prim = static_cast(getObject()); + setPreviewDisplayMode(prim->getAddSubType() == PartDesign::FeatureAddSub::Subtractive); + } + return ViewProviderAddSub::setEdit(ModNum); +} + +void ViewProviderHelix::unsetEdit(int ModNum) +{ + setPreviewDisplayMode(false); + // Rely on parent class to: + // restitute old workbench (set setEdit above) and close the dialog if exiting editing + PartDesignGui::ViewProvider::unsetEdit(ModNum); +} + +std::vector ViewProviderHelix::claimChildren(void) const { + std::vector temp; + App::DocumentObject* sketch = static_cast(getObject())->Profile.getValue(); + if (sketch != NULL && sketch->isDerivedFrom(Part::Part2DObject::getClassTypeId())) + temp.push_back(sketch); + + return temp; +} + +bool ViewProviderHelix::onDelete(const std::vector &s) { + PartDesign::ProfileBased* feature = static_cast(getObject()); + + // get the Sketch + Sketcher::SketchObject *pcSketch = 0; + if (feature->Profile.getValue()) + pcSketch = static_cast(feature->Profile.getValue()); + + // if abort command deleted the object the sketch is visible again + if (pcSketch && Gui::Application::Instance->getViewProvider(pcSketch)) + Gui::Application::Instance->getViewProvider(pcSketch)->show(); + + return ViewProvider::onDelete(s); +} + diff --git a/src/Mod/PartDesign/Gui/ViewProviderHelix.h b/src/Mod/PartDesign/Gui/ViewProviderHelix.h new file mode 100644 index 0000000000..83dd7d6d55 --- /dev/null +++ b/src/Mod/PartDesign/Gui/ViewProviderHelix.h @@ -0,0 +1,62 @@ +/*************************************************************************** + * Copyright (c) 2011 Juergen Riegel * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#ifndef PARTGUI_ViewProviderHelix_H +#define PARTGUI_ViewProviderHelix_H + +#include "ViewProviderAddSub.h" + + +namespace PartDesignGui { + +class PartDesignGuiExport ViewProviderHelix : public ViewProviderAddSub +{ + PROPERTY_HEADER(PartDesignGui::ViewProviderHelix); + +public: + /// constructor + ViewProviderHelix(); + /// destructor + virtual ~ViewProviderHelix(); + + void setupContextMenu(QMenu*, QObject*, const char*); + + /// grouping handling + std::vector claimChildren(void)const; + + virtual bool onDelete(const std::vector &); + +protected: + virtual QIcon getIcon(void) const; + + /// Returns a newly created TaskDlgHelixParameters + virtual TaskDlgFeatureParameters *getEditDialog(); + virtual bool setEdit(int ModNum); + virtual void unsetEdit(int ModNum); +}; + + +} // namespace PartDesignGui + + +#endif // PARTGUI_ViewProviderHelix_H diff --git a/src/Mod/PartDesign/Gui/ViewProviderSketchBased.cpp b/src/Mod/PartDesign/Gui/ViewProviderSketchBased.cpp index fb47925a07..399ad44242 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderSketchBased.cpp +++ b/src/Mod/PartDesign/Gui/ViewProviderSketchBased.cpp @@ -72,3 +72,4 @@ bool ViewProviderSketchBased::onDelete(const std::vector &s) { return ViewProvider::onDelete(s); } + diff --git a/src/Mod/PartDesign/Gui/ViewProviderSketchBased.h b/src/Mod/PartDesign/Gui/ViewProviderSketchBased.h index 3831c3c923..cb5d9e18d3 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderSketchBased.h +++ b/src/Mod/PartDesign/Gui/ViewProviderSketchBased.h @@ -23,7 +23,7 @@ #ifndef VIEWPROVIDERSKETCHBASED_H_QKP3UG9A #define VIEWPROVIDERSKETCHBASED_H_QKP3UG9A -#include "ViewProvider.h" +#include "ViewProviderAddSub.h" namespace PartDesignGui { @@ -44,6 +44,7 @@ public: std::vector claimChildren(void)const; virtual bool onDelete(const std::vector &); + }; } /* PartDesignGui */ diff --git a/src/Mod/PartDesign/Gui/Workbench.cpp b/src/Mod/PartDesign/Gui/Workbench.cpp index 073473aa8e..73b7576c16 100644 --- a/src/Mod/PartDesign/Gui/Workbench.cpp +++ b/src/Mod/PartDesign/Gui/Workbench.cpp @@ -409,6 +409,8 @@ void Workbench::activated() "PartDesign_SubtractivePipe", "PartDesign_AdditiveLoft", "PartDesign_SubtractiveLoft", + "PartDesign_AdditiveHelix", + "PartDesign_SubtractiveHelix", 0}; Watcher.push_back(new Gui::TaskView::TaskWatcherCommands( "SELECT Sketcher::SketchObject COUNT 1", @@ -496,14 +498,14 @@ Gui::MenuItem* Workbench::setupMenuBar() const Gui::MenuItem* additives = new Gui::MenuItem; additives->setCommand("Create an additive feature"); *additives << "PartDesign_Pad" << "PartDesign_Revolution" - << "PartDesign_AdditiveLoft" << "PartDesign_AdditivePipe"; + << "PartDesign_AdditiveLoft" << "PartDesign_AdditivePipe" << "PartDesign_AdditiveHelix"; // subtractives Gui::MenuItem* subtractives = new Gui::MenuItem; subtractives->setCommand("Create a subtractive feature"); *subtractives << "PartDesign_Pocket" << "PartDesign_Hole" << "PartDesign_Groove" << "PartDesign_SubtractiveLoft" - << "PartDesign_SubtractivePipe"; + << "PartDesign_SubtractivePipe" << "PartDesign_SubtractiveHelix"; // transformations Gui::MenuItem* transformations = new Gui::MenuItem; @@ -598,6 +600,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const << "PartDesign_Revolution" << "PartDesign_AdditiveLoft" << "PartDesign_AdditivePipe" + << "PartDesign_AdditiveHelix" << "PartDesign_CompPrimitiveAdditive" << "Separator" << "PartDesign_Pocket" @@ -605,6 +608,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const << "PartDesign_Groove" << "PartDesign_SubtractiveLoft" << "PartDesign_SubtractivePipe" + << "PartDesign_SubtractiveHelix" << "PartDesign_CompPrimitiveSubtractive" << "Separator" << "PartDesign_Mirrored" From 67ee247c0cfb8f3f6bcd837521d5f5b755b90181 Mon Sep 17 00:00:00 2001 From: ceanwang Date: Sun, 1 Nov 2020 08:49:05 +1100 Subject: [PATCH 141/168] Added readNastran95() --- src/Mod/Fem/App/FemMesh.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mod/Fem/App/FemMesh.h b/src/Mod/Fem/App/FemMesh.h index a46c6b7777..ce1bd8d88f 100644 --- a/src/Mod/Fem/App/FemMesh.h +++ b/src/Mod/Fem/App/FemMesh.h @@ -169,6 +169,7 @@ public: private: void copyMeshData(const FemMesh&); void readNastran(const std::string &Filename); + void readNastran95(const std::string &Filename); void readZ88(const std::string &Filename); void readAbaqus(const std::string &Filename); From 172d2d8a27a5963bb269c1061692f9762837aac3 Mon Sep 17 00:00:00 2001 From: ceanwang Date: Sun, 1 Nov 2020 08:52:23 +1100 Subject: [PATCH 142/168] Added readNastran95() --- src/Mod/Fem/App/FemMesh.cpp | 430 +++++++++++++++++++++++++++++++++++- 1 file changed, 429 insertions(+), 1 deletion(-) diff --git a/src/Mod/Fem/App/FemMesh.cpp b/src/Mod/Fem/App/FemMesh.cpp index 601df01c6b..2255b97982 100644 --- a/src/Mod/Fem/App/FemMesh.cpp +++ b/src/Mod/Fem/App/FemMesh.cpp @@ -1280,6 +1280,433 @@ void FemMesh::readNastran(const std::string &Filename) } +void FemMesh::readNastran95(const std::string &Filename) +{ + Base::TimeInfo Start; + Base::Console().Log("Start: FemMesh::readNastran95() =================================\n"); + + _Mtrx = Base::Matrix4D(); + + std::ifstream inputfile; + inputfile.open(Filename.c_str()); + inputfile.seekg(std::ifstream::beg); + std::string line1,line2,temp,tcard; + float cx, cy, cz; + std::vector token_results; + token_results.clear(); + Base::Vector3d current_node; + std::vector vertices; + vertices.clear(); + std::vector nodal_id; + + nodal_id.clear(); + + std::vector bar_element; + std::vector tri_element; + std::vector quad_element; + std::vector tetra_element; + std::vector wedge_element; + std::vector hexa_element; + + std::vector > all_elements; + + std::vector element_id; + std::vector element_type; + + element_id.clear(); + element_type.clear(); + + bool nastran_free_format = false; + do + { + std::getline(inputfile,line1); + //cout << line1 << endl; + if (line1.size() == 0) continue; + //if (!nastran_free_format && line1.find(',')!= std::string::npos) + // nastran_free_format = true; + tcard = line1.substr(0, 8).c_str(); + //boost::algorithm::trim(tcard); + if (!nastran_free_format && line1.find("GRID*")!= std::string::npos ) //We found a Grid line + { + //Now lets extract the GRID Points = Nodes + //As each GRID Line consists of two subsequent lines we have to + //take care of that as well + std::getline(inputfile,line2); + //Get the Nodal ID + nodal_id.push_back(atoi(line1.substr(8,24).c_str())); + //Extract X Value + current_node.x = atof(line1.substr(40,56).c_str()); + //Extract Y Value + current_node.y = atof(line1.substr(56,72).c_str()); + //Extract Z Value + current_node.z = atof(line2.substr(8,24).c_str()); + + vertices.push_back(current_node); + } + else if (!nastran_free_format && line1.find("GRID") != std::string::npos) //We found a Grid line + { + //Base::Console().Log("Found a GRID\n"); + //D06.inp + //GRID 109 .9 .7 + //Now lets extract the GRID Points = Nodes + //Get the Nodal ID + unsigned int id = atoi(line1.substr(8, 16).c_str()); + + //Extract X Value + cx = atof(line1.substr(24, 32).c_str()); + current_node.x = cx; + //Extract Y Value + cy = atof(line1.substr(32, 40).c_str()); + current_node.y = cy; + //Extract Z Value + cz = atof(line1.substr(40, 48).c_str()); + current_node.z = cz; + // atof(line1.substr(40, 48).c_str()); + //Base::Console().Log("nid = %d %f %f %f\n", id, cx, cy, cz); + + nodal_id.push_back(id); + vertices.push_back(current_node); + } + + //1D + else if (line1.substr(0,6)=="CBAR") + { + //Base::Console().Log("Found a CTRMEM\n"); + bar_element.clear(); + unsigned int id = atoi(line1.substr(8,16).c_str()); + + element_type.push_back(100); + element_id.push_back(id); + bar_element.push_back(atoi(line1.substr(24,32).c_str())); + bar_element.push_back(atoi(line1.substr(32,40).c_str())); + + all_elements.push_back(bar_element); + } + //2d +// else if (!nastran_free_format && line1.find("CTRMEM")!= std::string::npos) + else if (line1.substr(0,6)=="CTRMEM") + { + //Base::Console().Log("Found a CTRMEM\n"); + tri_element.clear(); + unsigned int id = atoi(line1.substr(8,16).c_str()); + + //D06 + //CTRMEM 322 1 179 180 185 + element_type.push_back(230); + element_id.push_back(id); + tri_element.push_back(atoi(line1.substr(24,32).c_str())); + tri_element.push_back(atoi(line1.substr(32,40).c_str())); + tri_element.push_back(atoi(line1.substr(40,48).c_str())); + + all_elements.push_back(tri_element); + } + else if (line1.substr(0, 6) == "CTRIA1") + { + //Base::Console().Log("Found a CTRMEM\n"); + tri_element.clear(); + unsigned int id = atoi(line1.substr(8, 16).c_str()); + + //D06 + //CTRMEM 322 1 179 180 185 + element_type.push_back(231); + element_id.push_back(id); + tri_element.push_back(atoi(line1.substr(24, 32).c_str())); + tri_element.push_back(atoi(line1.substr(32, 40).c_str())); + tri_element.push_back(atoi(line1.substr(40, 48).c_str())); + + all_elements.push_back(tri_element); + } + else if (line1.substr(0, 6) == "CQUAD1") + { + //Base::Console().Log("Found a CQUAD1\n"); + quad_element.clear(); + unsigned int id = atoi(line1.substr(8, 16).c_str()); + + //D06 + //CTRMEM 322 1 179 180 185 + element_type.push_back(241); + element_id.push_back(id); + quad_element.push_back(atoi(line1.substr(24, 32).c_str())); + quad_element.push_back(atoi(line1.substr(32, 40).c_str())); + quad_element.push_back(atoi(line1.substr(40, 48).c_str())); + quad_element.push_back(atoi(line1.substr(48, 56).c_str())); + + all_elements.push_back(quad_element); + } + + //3d element + else if (!nastran_free_format && line1.find("CTETRA")!= std::string::npos) + { + //d011121a.inp + //CTETRA 3 200 104 114 3 103 + tetra_element.clear(); + unsigned int id = atoi(line1.substr(8,16).c_str()); + element_type.push_back(340); + + element_id.push_back(id); + tetra_element.push_back(atoi(line1.substr(24,32).c_str())); + tetra_element.push_back(atoi(line1.substr(32,40).c_str())); + tetra_element.push_back(atoi(line1.substr(40,48).c_str())); + tetra_element.push_back(atoi(line1.substr(48,56).c_str())); + //tetra_element.push_back(atoi(line1.substr(56,64).c_str())); + //tetra_element.push_back(atoi(line1.substr(64,72).c_str())); + + all_elements.push_back(tetra_element); + } + else if (!nastran_free_format && line1.find("CWEDGE")!= std::string::npos) + { + //d011121a.inp + //CWEDGE 11 200 6 17 16 106 117 116 + tetra_element.clear(); + unsigned int id = atoi(line1.substr(8,16).c_str()); + element_type.push_back(360); + + element_id.push_back(id); + wedge_element.push_back(atoi(line1.substr(24,32).c_str())); + wedge_element.push_back(atoi(line1.substr(32,40).c_str())); + wedge_element.push_back(atoi(line1.substr(40,48).c_str())); + wedge_element.push_back(atoi(line1.substr(48,56).c_str())); + wedge_element.push_back(atoi(line1.substr(56,64).c_str())); + wedge_element.push_back(atoi(line1.substr(64,72).c_str())); + + all_elements.push_back(wedge_element); + } + else if (!nastran_free_format && line1.find("CHEXA1")!= std::string::npos) + { + //d011121a.inp + //CHEXA1 1 200 1 2 13 12 101 102 +SOL1 + //+SOL1 113 112 + tetra_element.clear(); + std::getline(inputfile,line2); + unsigned int id = atoi(line1.substr(8,16).c_str()); + element_type.push_back(381); + int offset = 0; + + element_id.push_back(id); + hexa_element.push_back(atoi(line1.substr(24,32).c_str())); + hexa_element.push_back(atoi(line1.substr(32,40).c_str())); + hexa_element.push_back(atoi(line1.substr(40,48).c_str())); + hexa_element.push_back(atoi(line1.substr(48,56).c_str())); + hexa_element.push_back(atoi(line1.substr(56,64).c_str())); + hexa_element.push_back(atoi(line1.substr(64,72).c_str())); + + hexa_element.push_back(atoi(line2.substr(8,16).c_str())); + hexa_element.push_back(atoi(line2.substr(16,24).c_str())); + + all_elements.push_back(hexa_element); + } + else if (!nastran_free_format && line1.find("CHEXA2")!= std::string::npos) + { + //d011121a.inp + //CHEXA1 1 200 1 2 13 12 101 102 +SOL1 + //+SOL1 113 112 + tetra_element.clear(); + std::getline(inputfile,line2); + unsigned int id = atoi(line1.substr(8,16).c_str()); + element_type.push_back(382); + int offset = 0; + + element_id.push_back(id); + hexa_element.push_back(atoi(line1.substr(24,32).c_str())); + hexa_element.push_back(atoi(line1.substr(32,40).c_str())); + hexa_element.push_back(atoi(line1.substr(40,48).c_str())); + hexa_element.push_back(atoi(line1.substr(48,56).c_str())); + hexa_element.push_back(atoi(line1.substr(56,64).c_str())); + hexa_element.push_back(atoi(line1.substr(64,72).c_str())); + + hexa_element.push_back(atoi(line2.substr(8,16).c_str())); + hexa_element.push_back(atoi(line2.substr(16,24).c_str())); + + all_elements.push_back(hexa_element); + } + +// free format + else if (nastran_free_format && line1.find("GRID")!= std::string::npos ) //We found a Grid line + { + //Base::Console().Log("Found a free format GRID\n"); + char_separator sep(","); + tokenizer > tokens(line1, sep); + token_results.assign(tokens.begin(),tokens.end()); + if (token_results.size() < 3) + continue;//Line does not include Nodal coordinates + nodal_id.push_back(atoi(token_results[1].c_str())); + current_node.x = atof(token_results[3].c_str()); + current_node.y = atof(token_results[4].c_str()); + current_node.z = atof(token_results[5].c_str()); + vertices.push_back(current_node); + } + else if (nastran_free_format && line1.find("CTETRA")!= std::string::npos) + { + //Base::Console().Log("Found a CTETRA\n"); + tetra_element.clear(); + //Lets extract the elements + //As each Element Line consists of two subsequent lines as well + //we have to take care of that + //At a first step we only extract Quadratic Tetrahedral Elements + std::getline(inputfile,line2); + char_separator sep(","); + tokenizer > tokens(line1.append(line2), sep); + token_results.assign(tokens.begin(),tokens.end()); + if (token_results.size() < 11) + continue;//Line does not include enough nodal IDs + element_id.push_back(atoi(token_results[1].c_str())); + tetra_element.push_back(atoi(token_results[3].c_str())); + tetra_element.push_back(atoi(token_results[4].c_str())); + tetra_element.push_back(atoi(token_results[5].c_str())); + tetra_element.push_back(atoi(token_results[6].c_str())); + tetra_element.push_back(atoi(token_results[7].c_str())); + tetra_element.push_back(atoi(token_results[8].c_str())); + tetra_element.push_back(atoi(token_results[10].c_str())); + tetra_element.push_back(atoi(token_results[11].c_str())); + tetra_element.push_back(atoi(token_results[12].c_str())); + tetra_element.push_back(atoi(token_results[13].c_str())); + + all_elements.push_back(tetra_element); + } + + } + while (inputfile.good()); + inputfile.close(); + +// Base::Console().Log("Done saving node.\n"); + Base::Console().Log(" %f: File read, start building mesh\n",Base::TimeInfo::diffTimeF(Start,Base::TimeInfo())); + + //Now fill the SMESH datastructure + std::vector::const_iterator anodeiterator; + SMESHDS_Mesh* meshds = this->myMesh->GetMeshDS(); + meshds->ClearMesh(); + unsigned int j=0; + for(anodeiterator=vertices.begin(); anodeiterator!=vertices.end(); anodeiterator++) + { + meshds->AddNodeWithID((*anodeiterator).x,(*anodeiterator).y,(*anodeiterator).z,nodal_id[j]); + //Base::Console().Log("nid = %d %f %f %f\n", nodal_id[j], (*anodeiterator).x, (*anodeiterator).y, (*anodeiterator).z); + j++; + } + + + for(unsigned int i=0;iAddVolumeWithID + ( + //tetra 10 point + meshds->FindNode(all_elements[i][1]), + meshds->FindNode(all_elements[i][0]), + meshds->FindNode(all_elements[i][2]), + meshds->FindNode(all_elements[i][3]), + meshds->FindNode(all_elements[i][4]), + meshds->FindNode(all_elements[i][6]), + meshds->FindNode(all_elements[i][5]), + meshds->FindNode(all_elements[i][8]), + meshds->FindNode(all_elements[i][7]), + meshds->FindNode(all_elements[i][9]), + element_id[i] + ); + + //1D element + } else if (element_type[i] == 100) + { + //Base::Console().Log("eid = %d %d %d %d\n", element_id[i], all_elements[i][0], all_elements[i][1], all_elements[i][2]); + //cbar + meshds->AddEdgeWithID( + all_elements[i][0], + all_elements[i][1], + element_id[i] + ); + //2d element + } else if (element_type[i] == 230) + { + //Base::Console().Log("eid = %d %d %d %d\n", element_id[i], all_elements[i][0], all_elements[i][1], all_elements[i][2]); + //ctramem + meshds->AddFaceWithID( + all_elements[i][0], + all_elements[i][1], + all_elements[i][2], + element_id[i] + ); + } else if (element_type[i] == 231) + { + //ctria1 + meshds->AddFaceWithID( + all_elements[i][0], + all_elements[i][1], + all_elements[i][2], + element_id[i] + ); + } + else if (element_type[i] == 241) + { + //cquad1 + meshds->AddFaceWithID( + all_elements[i][0], + all_elements[i][1], + all_elements[i][2], + all_elements[i][3], + element_id[i] + ); + } + + //3d element + else if (element_type[i] == 340) + { + //ctetra + meshds->AddVolumeWithID( + all_elements[i][0], + all_elements[i][1], + all_elements[i][2], + all_elements[i][3], + element_id[i] + ); + } + else if (element_type[i] == 360) + { + //cwedge + meshds->AddVolumeWithID( + all_elements[i][0], + all_elements[i][1], + all_elements[i][2], + all_elements[i][3], + all_elements[i][4], + all_elements[i][5], + element_id[i] + ); + } + else if (element_type[i] == 381) + { + //chexa1 + meshds->AddVolumeWithID( + all_elements[i][0], + all_elements[i][1], + all_elements[i][2], + all_elements[i][3], + all_elements[i][4], + all_elements[i][5], + all_elements[i][6], + all_elements[i][7], + element_id[i] + ); + } + else if (element_type[i] == 382) + { + //chexa2 + meshds->AddVolumeWithID( + all_elements[i][0], + all_elements[i][1], + all_elements[i][2], + all_elements[i][3], + all_elements[i][4], + all_elements[i][5], + all_elements[i][6], + all_elements[i][7], + element_id[i] + ); + } + } + Base::Console().Log(" %f: Done \n",Base::TimeInfo::diffTimeF(Start,Base::TimeInfo())); + +} + void FemMesh::readAbaqus(const std::string &FileName) { Base::TimeInfo Start; @@ -1370,7 +1797,8 @@ void FemMesh::read(const char *FileName) } else if (File.hasExtension("inp") ) { // read Abaqus inp mesh file - readAbaqus(File.filePath()); + // readAbaqus(File.filePath()); + readNastran95(File.filePath()); } else if (File.hasExtension("stl") ) { // read brep-file From 3ad8367b5e7de70b72c24589002fc949d93b79b2 Mon Sep 17 00:00:00 2001 From: ceanwang Date: Thu, 26 Nov 2020 21:21:20 +1100 Subject: [PATCH 143/168] Changed comment for free format CTETRA element --- src/Mod/Fem/App/FemMesh.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Mod/Fem/App/FemMesh.cpp b/src/Mod/Fem/App/FemMesh.cpp index 2255b97982..edb14ae3d8 100644 --- a/src/Mod/Fem/App/FemMesh.cpp +++ b/src/Mod/Fem/App/FemMesh.cpp @@ -1537,12 +1537,9 @@ void FemMesh::readNastran95(const std::string &Filename) } else if (nastran_free_format && line1.find("CTETRA")!= std::string::npos) { + //Quadratic Tetrahedral Elements //Base::Console().Log("Found a CTETRA\n"); tetra_element.clear(); - //Lets extract the elements - //As each Element Line consists of two subsequent lines as well - //we have to take care of that - //At a first step we only extract Quadratic Tetrahedral Elements std::getline(inputfile,line2); char_separator sep(","); tokenizer > tokens(line1.append(line2), sep); From 5903ff442657e7b6630d5867374adb62924037d8 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 4 Feb 2021 13:56:15 +0100 Subject: [PATCH 144/168] FEM: add basic support of Nastran-95 --- src/Mod/Fem/App/FemMesh.cpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Mod/Fem/App/FemMesh.cpp b/src/Mod/Fem/App/FemMesh.cpp index edb14ae3d8..8a57f250cf 100644 --- a/src/Mod/Fem/App/FemMesh.cpp +++ b/src/Mod/Fem/App/FemMesh.cpp @@ -1290,7 +1290,7 @@ void FemMesh::readNastran95(const std::string &Filename) std::ifstream inputfile; inputfile.open(Filename.c_str()); inputfile.seekg(std::ifstream::beg); - std::string line1,line2,temp,tcard; + std::string line1,line2,tcard; float cx, cy, cz; std::vector token_results; token_results.clear(); @@ -1300,7 +1300,7 @@ void FemMesh::readNastran95(const std::string &Filename) std::vector nodal_id; nodal_id.clear(); - + std::vector bar_element; std::vector tri_element; std::vector quad_element; @@ -1309,7 +1309,7 @@ void FemMesh::readNastran95(const std::string &Filename) std::vector hexa_element; std::vector > all_elements; - + std::vector element_id; std::vector element_type; @@ -1480,7 +1480,6 @@ void FemMesh::readNastran95(const std::string &Filename) std::getline(inputfile,line2); unsigned int id = atoi(line1.substr(8,16).c_str()); element_type.push_back(381); - int offset = 0; element_id.push_back(id); hexa_element.push_back(atoi(line1.substr(24,32).c_str())); @@ -1504,7 +1503,6 @@ void FemMesh::readNastran95(const std::string &Filename) std::getline(inputfile,line2); unsigned int id = atoi(line1.substr(8,16).c_str()); element_type.push_back(382); - int offset = 0; element_id.push_back(id); hexa_element.push_back(atoi(line1.substr(24,32).c_str())); @@ -1581,7 +1579,7 @@ void FemMesh::readNastran95(const std::string &Filename) } - for(unsigned int i=0;iAddFaceWithID( @@ -1701,7 +1702,6 @@ void FemMesh::readNastran95(const std::string &Filename) } } Base::Console().Log(" %f: Done \n",Base::TimeInfo::diffTimeF(Start,Base::TimeInfo())); - } void FemMesh::readAbaqus(const std::string &FileName) @@ -1794,8 +1794,12 @@ void FemMesh::read(const char *FileName) } else if (File.hasExtension("inp") ) { // read Abaqus inp mesh file - // readAbaqus(File.filePath()); - readNastran95(File.filePath()); + readAbaqus(File.filePath()); + + // if the file doesn't contain supported geometries try Nastran95 + SMESHDS_Mesh* meshds = this->myMesh->GetMeshDS(); + if (meshds->NbNodes() == 0) + readNastran95(File.filePath()); } else if (File.hasExtension("stl") ) { // read brep-file From 7add058dc5abad4a8de9645416d728da00c75a76 Mon Sep 17 00:00:00 2001 From: luz paz Date: Thu, 4 Feb 2021 06:32:47 -0500 Subject: [PATCH 145/168] LGTM: exclude zipios++ and kdl (orocos) libraries from analysis Ref: https://lgtm.com/help/lgtm/lgtm.yml-configuration-file --- lgtm.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lgtm.yml b/lgtm.yml index 3622f4d692..e4414ab0b2 100644 --- a/lgtm.yml +++ b/lgtm.yml @@ -5,6 +5,9 @@ path_classifiers: - "src/Mod/Import/App/config_control_design.py" - "src/Mod/Import/App/ifc2x3.py" - "src/Mod/Import/App/ifc4.py" +queries: + - exclude: "src/zipios++/**"" + - exclude: "src/Mod/Robot/App/kdl_cp/**" extraction: javascript: index: From 6d0357379c813c7d81487208de06cc6fe11767e8 Mon Sep 17 00:00:00 2001 From: luz paz Date: Thu, 4 Feb 2021 08:37:01 -0500 Subject: [PATCH 146/168] Fix typo in previous commit 7add058dc [skip ci] Removed superfluous double-quote char. (7add058dc) --- lgtm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lgtm.yml b/lgtm.yml index e4414ab0b2..1812d50024 100644 --- a/lgtm.yml +++ b/lgtm.yml @@ -6,7 +6,7 @@ path_classifiers: - "src/Mod/Import/App/ifc2x3.py" - "src/Mod/Import/App/ifc4.py" queries: - - exclude: "src/zipios++/**"" + - exclude: "src/zipios++/**" - exclude: "src/Mod/Robot/App/kdl_cp/**" extraction: javascript: From 48fca8c2bc4285ceb883d5cfa0757ce3f17a903a Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Wed, 16 Dec 2020 12:04:33 -0600 Subject: [PATCH 147/168] Add default filename for exports When exporting a single file, the filename defaults to the current FCStd name plus a dash and the name of the object. If multiple objects are selected, the default is the basename of the FCStd file. No extension is added. This behavior is controllable via two hidden preferences, BaseApp/Preferences/General/ExportDefaultFilenameSingle BaseApp/Preferences/General/ExportDefaultFilenameMultiple _Allow regeneration of default on new exports_ If an export has been done and it used the default filename, on the next export regenerate the filename (potentially updating the selected object name in that filename) instead of just defaulting to the last name. _Search for extension in chosen filter first_ Originally the file dialog simply searched for the first available extension in the overall filter list. This commit modifies it to first check the selected extension, and only if that is empty to search the full filter list. This section of code only runs if a default filename is set but does not have an extension ("suffix" in Qt's terms). --- src/Gui/CommandDoc.cpp | 206 ++++++++++++++++++++++++++++++++++++++--- src/Gui/FileDialog.cpp | 15 ++- 2 files changed, 204 insertions(+), 17 deletions(-) diff --git a/src/Gui/CommandDoc.cpp b/src/Gui/CommandDoc.cpp index ae83d95d7d..d671a151d8 100644 --- a/src/Gui/CommandDoc.cpp +++ b/src/Gui/CommandDoc.cpp @@ -25,6 +25,7 @@ #ifndef _PreComp_ # include # include +# include # include # include # include @@ -279,37 +280,200 @@ StdCmdExport::StdCmdExport() eType = 0; } +/** +Create a default filename from a user-specified format string + +Format options are: +%F - the basename of the .FCStd file (or the label, if it is not saved yet) +%Lx - the label of the selected object(s), separated by character 'x' +%Px - the label of the selected object(s) and their first parent, separated by character 'x' +%U - the date and time, in UTC, ISO 8601 +%D - the date and time, in local timezone, ISO 8601 +Any other characters are treated literally, though if the filename is illegal +it will be changed on saving. +*/ +QString createDefaultExportBasename() +{ + QString defaultFilename; + + auto selection = Gui::Selection().getObjectsOfType(App::DocumentObject::getClassTypeId()); + QString exportFormatString; + if (selection.size() == 1) { + exportFormatString = QString::fromStdString (App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")-> + GetASCII("ExportDefaultFilenameSingle", "%F-%P-")); + } + else { + exportFormatString = QString::fromStdString (App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")-> + GetASCII("ExportDefaultFilenameMultiple", "%F")); + } + + // For code simplicity, pull all values we might need + + // %F - the basename of the.FCStd file(or the label, if it is not saved yet) + QString docFilename = QString::fromUtf8(App::GetApplication().getActiveDocument()->getFileName()); + QFileInfo fi(docFilename); + QString fcstdBasename = fi.completeBaseName(); + if (fcstdBasename.isEmpty()) { + fcstdBasename = QString::fromStdString(App::GetApplication().getActiveDocument()->Label.getStrValue()); + } + + // %L - the label of the selected object(s) + QStringList objectLabels; + for (const auto& object : selection) { + objectLabels.push_back(QString::fromStdString(object->Label.getStrValue())); + } + + // %P - the label of the selected objects and their first parent + QStringList parentLabels; + for (const auto& object : selection) { + auto parents = object->getParents(); + QString firstParent; + if (!parents.empty()) + firstParent = QString::fromStdString(parents.front().first->Label.getStrValue()); + parentLabels.append(firstParent + QString::fromStdString(object->Label.getStrValue())); + } + + // %U - the date and time, in UTC, ISO 8601 + QDateTime utc = QDateTime(QDateTime::currentDateTimeUtc()); + QString utcISO8601 = utc.toString(Qt::ISODate); + + // %D - the date and time, in local timezone, ISO 8601 + QDateTime local = utc.toLocalTime(); + QString localISO8601 = local.toString(Qt::ISODate); + + for (int i = 0; i < exportFormatString.size(); ++i) { + auto c = exportFormatString.at(i); + if (c != QLatin1Char('%')) { + defaultFilename.append(c); + } + else { + if (i < exportFormatString.size() - 1) { + ++i; + auto formatChar = exportFormatString.at(i); + QChar separatorChar = QLatin1Char('-'); + if (formatChar == QLatin1Char('L') || + formatChar == QLatin1Char('P')) { + if (i < exportFormatString.size() - 1) { + ++i; + separatorChar = exportFormatString.at(i); + } + } + + // Handle our format characters: + if (formatChar == QLatin1Char('F')) { + defaultFilename.append(fcstdBasename); + } + else if (formatChar == QLatin1Char('L')) { + defaultFilename.append(objectLabels.join(separatorChar)); + } + else if (formatChar == QLatin1Char('P')) { + defaultFilename.append(parentLabels.join(separatorChar)); + } + else if (formatChar == QLatin1Char('U')) { + defaultFilename.append(utcISO8601); + } + else if (formatChar == QLatin1Char('D')) { + defaultFilename.append(localISO8601); + } + else { + FC_WARN("When parsing default export filename format string, %" + << QString(formatChar).toStdString() + << " is not a known format string."); + } + } + } + } + + // Finally, clean the string: + QString invalidCharacters = QLatin1String("/\\?%*:|\"<>"); + for (const auto &c : invalidCharacters) + defaultFilename.replace(c,QLatin1String("_")); + + return defaultFilename; +} + void StdCmdExport::activated(int iMsg) { Q_UNUSED(iMsg); - if (Gui::Selection().countObjectsOfType(App::DocumentObject::getClassTypeId()) == 0) { + static QString lastExportFullPath = QString(); + static bool lastExportUsedGeneratedFilename = true; + static QString lastExportFilterUsed = QString(); + + auto selection = Gui::Selection().getObjectsOfType(App::DocumentObject::getClassTypeId()); + if (selection.size() == 0) { QMessageBox::warning(Gui::getMainWindow(), QString::fromUtf8(QT_TR_NOOP("No selection")), - QString::fromUtf8(QT_TR_NOOP("Please select first the objects you want to export."))); + QString::fromUtf8(QT_TR_NOOP("Select the objects to export before choosing Export."))); return; } - // fill the list of registered endings + // fill the list of registered suffixes QStringList filterList; - std::map FilterList = App::GetApplication().getExportFilters(); - std::map::const_iterator jt; - for (jt=FilterList.begin();jt != FilterList.end();++jt) { + std::map filterMap = App::GetApplication().getExportFilters(); + for (const auto &filter : filterMap) { // ignore the project file format - if (jt->first.find("(*.FCStd)") == std::string::npos) { - filterList << QString::fromLatin1(jt->first.c_str()); + if (filter.first.find("(*.FCStd)") == std::string::npos) { + filterList << QString::fromStdString(filter.first); + } + } + QString formatList = filterList.join(QLatin1String(";;")); + Base::Reference hPath = + App::GetApplication().GetUserParameter().GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("General"); + QString selectedFilter = QString::fromStdString(hPath->GetASCII("FileExportFilter")); + if (!lastExportFilterUsed.isEmpty()) { + selectedFilter = lastExportFilterUsed; + } + + // Create a default filename for the export + // * If this is the first export this session default, generate a new default. + // * If this is a repeated export during the same session: + // * If the user accepted the default filename last time, regenerate a new + // default, potentially updating the object label. + // * If not, default to their previously-set export filename. + QString defaultFilename = lastExportFullPath; + + bool filenameWasGenerated = false; + // We want to generate a new default name in two cases: + if (defaultFilename.isEmpty() || lastExportUsedGeneratedFilename) { + // First, get the name and path of the current .FCStd file, if there is one: + QString docFilename = QString::fromUtf8( + App::GetApplication().getActiveDocument()->getFileName()); + + // Find the default location for our exported file. Three possibilities: + QString defaultExportPath; + if (!lastExportFullPath.isEmpty()) { + QFileInfo fi(lastExportFullPath); + defaultExportPath = fi.path(); + } + else if (!docFilename.isEmpty()) { + QFileInfo fi(docFilename); + defaultExportPath = fi.path(); + } + else { + defaultExportPath = Gui::FileDialog::getWorkingDirectory(); + } + + if (lastExportUsedGeneratedFilename /*<- static, true on first call*/ ) { + defaultFilename = defaultExportPath + QLatin1Char('/') + createDefaultExportBasename(); + + // Append the last extension used, if there is one. + if (!lastExportFullPath.isEmpty()) { + QFileInfo lastExportFile(lastExportFullPath); + if (!lastExportFile.suffix().isEmpty()) { + defaultFilename += QLatin1String(".") + lastExportFile.suffix(); + } + } + filenameWasGenerated = true; } } - QString formatList = filterList.join(QLatin1String(";;")); - Base::Reference hPath = App::GetApplication().GetUserParameter().GetGroup("BaseApp") - ->GetGroup("Preferences")->GetGroup("General"); - QString selectedFilter = QString::fromStdString(hPath->GetASCII("FileExportFilter")); - + // Launch the file selection modal dialog QString fileName = FileDialog::getSaveFileName(getMainWindow(), - QObject::tr("Export file"), QString(), formatList, &selectedFilter); + QObject::tr("Export file"), defaultFilename, formatList, &selectedFilter); if (!fileName.isEmpty()) { hPath->SetASCII("FileExportFilter", selectedFilter.toLatin1().constData()); + lastExportFilterUsed = selectedFilter; // So we can select the same one next time SelectModule::Dict dict = SelectModule::exportHandler(fileName, selectedFilter); // export the files with the associated modules for (SelectModule::Dict::iterator it = dict.begin(); it != dict.end(); ++it) { @@ -317,6 +481,20 @@ void StdCmdExport::activated(int iMsg) getActiveGuiDocument()->getDocument()->getName(), it.value().toLatin1()); } + + // Keep a record of if the user used our suggested generated filename. If they + // did, next time we can recreate it, which will update the object label if + // there is one. + QFileInfo defaultExportFI(defaultFilename); + QFileInfo thisExportFI(fileName); + if (filenameWasGenerated && + thisExportFI.completeBaseName() == defaultExportFI.completeBaseName()) { + lastExportUsedGeneratedFilename = true; + } + else { + lastExportUsedGeneratedFilename = false; + } + lastExportFullPath = fileName; } } diff --git a/src/Gui/FileDialog.cpp b/src/Gui/FileDialog.cpp index 139b1bdf78..a07f13b2bb 100644 --- a/src/Gui/FileDialog.cpp +++ b/src/Gui/FileDialog.cpp @@ -145,13 +145,21 @@ QString FileDialog::getSaveFileName (QWidget * parent, const QString & caption, dirName += fi.fileName(); } - // get the suffix for the filter + // get the suffix for the filter: use the selected filter if there is one, + // otherwise find the first valid suffix in the complete list of filters + const QString *filterToSearch; + if (selectedFilter != nullptr) { + filterToSearch = selectedFilter; + } + else { + filterToSearch = &filter; + } QRegExp rx; rx.setPattern(QLatin1String("\\s(\\(\\*\\.\\w{1,})\\W")); - int index = rx.indexIn(filter); + int index = rx.indexIn(*filterToSearch); if (index != -1) { // get the suffix with the leading dot - QString suffix = filter.mid(index+3, rx.matchedLength()-4); + QString suffix = filterToSearch->mid(index+3, rx.matchedLength()-4); if (fi.suffix().isEmpty()) dirName += suffix; } @@ -199,6 +207,7 @@ QString FileDialog::getSaveFileName (QWidget * parent, const QString & caption, dlg.setFileMode(QFileDialog::AnyFile); dlg.setAcceptMode(QFileDialog::AcceptSave); dlg.setDirectory(dirName); + dlg.selectFile(dirName); dlg.setOptions(options); dlg.setNameFilters(filter.split(QLatin1String(";;"))); if (selectedFilter && !selectedFilter->isEmpty()) From dc09810e981908a6141dec8d3a8db8a543cc561c Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Mon, 21 Dec 2020 13:48:31 -0600 Subject: [PATCH 148/168] Check for filename before selecting Correct an error caught by @davidosterberg -- the non-native QFileDialog did not behave as expected when not provided with a default filename, so that case is now caught and the `selectFile()` call is bypassed. --- src/Gui/FileDialog.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Gui/FileDialog.cpp b/src/Gui/FileDialog.cpp index a07f13b2bb..f2ddf091e3 100644 --- a/src/Gui/FileDialog.cpp +++ b/src/Gui/FileDialog.cpp @@ -135,6 +135,7 @@ QString FileDialog::getSaveFileName (QWidget * parent, const QString & caption, const QString & filter, QString * selectedFilter, Options options) { QString dirName = dir; + bool hasFilename = false; if (dirName.isEmpty()) { dirName = getWorkingDirectory(); } else { @@ -144,6 +145,9 @@ QString FileDialog::getSaveFileName (QWidget * parent, const QString & caption, dirName += QLatin1String("/"); dirName += fi.fileName(); } + if (!fi.fileName().isEmpty()) { + hasFilename = true; + } // get the suffix for the filter: use the selected filter if there is one, // otherwise find the first valid suffix in the complete list of filters @@ -207,7 +211,8 @@ QString FileDialog::getSaveFileName (QWidget * parent, const QString & caption, dlg.setFileMode(QFileDialog::AnyFile); dlg.setAcceptMode(QFileDialog::AcceptSave); dlg.setDirectory(dirName); - dlg.selectFile(dirName); + if (hasFilename) + dlg.selectFile(dirName); dlg.setOptions(options); dlg.setNameFilters(filter.split(QLatin1String(";;"))); if (selectedFilter && !selectedFilter->isEmpty()) From e3f8847450ecfaef0caf6b31976d26cceae35b32 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Thu, 24 Dec 2020 00:47:21 -0600 Subject: [PATCH 149/168] Conformed to FreeCAD coding conventions --- src/Gui/CommandDoc.cpp | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/Gui/CommandDoc.cpp b/src/Gui/CommandDoc.cpp index d671a151d8..67a3cc8397 100644 --- a/src/Gui/CommandDoc.cpp +++ b/src/Gui/CommandDoc.cpp @@ -291,6 +291,10 @@ Format options are: %D - the date and time, in local timezone, ISO 8601 Any other characters are treated literally, though if the filename is illegal it will be changed on saving. + +The format string is stored in two user preferences (not currently exposed in the GUI): +* BaseApp/Preferences/General/ExportDefaultFilenameSingle +* BaseApp/Preferences/General/ExportDefaultFilenameMultiple */ QString createDefaultExportBasename() { @@ -301,7 +305,7 @@ QString createDefaultExportBasename() if (selection.size() == 1) { exportFormatString = QString::fromStdString (App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")-> GetASCII("ExportDefaultFilenameSingle", "%F-%P-")); - } + } else { exportFormatString = QString::fromStdString (App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")-> GetASCII("ExportDefaultFilenameMultiple", "%F")); @@ -313,15 +317,13 @@ QString createDefaultExportBasename() QString docFilename = QString::fromUtf8(App::GetApplication().getActiveDocument()->getFileName()); QFileInfo fi(docFilename); QString fcstdBasename = fi.completeBaseName(); - if (fcstdBasename.isEmpty()) { + if (fcstdBasename.isEmpty()) fcstdBasename = QString::fromStdString(App::GetApplication().getActiveDocument()->Label.getStrValue()); - } // %L - the label of the selected object(s) QStringList objectLabels; - for (const auto& object : selection) { + for (const auto& object : selection) objectLabels.push_back(QString::fromStdString(object->Label.getStrValue())); - } // %P - the label of the selected objects and their first parent QStringList parentLabels; @@ -341,16 +343,22 @@ QString createDefaultExportBasename() QDateTime local = utc.toLocalTime(); QString localISO8601 = local.toString(Qt::ISODate); + // Parse the format string one character at a time: for (int i = 0; i < exportFormatString.size(); ++i) { auto c = exportFormatString.at(i); if (c != QLatin1Char('%')) { + // Anything that's not a format start character is just a literal defaultFilename.append(c); } else { + // The format start character now requires us to look at at least the next single + // character (if there isn't another character, the % just gets eaten) if (i < exportFormatString.size() - 1) { ++i; auto formatChar = exportFormatString.at(i); QChar separatorChar = QLatin1Char('-'); + // If this format type requires an additional char, read that now (or default to + // '-' if the format string ends) if (formatChar == QLatin1Char('L') || formatChar == QLatin1Char('P')) { if (i < exportFormatString.size() - 1) { @@ -384,7 +392,7 @@ QString createDefaultExportBasename() } } - // Finally, clean the string: + // Finally, clean the string so it's valid for all operating systems: QString invalidCharacters = QLatin1String("/\\?%*:|\"<>"); for (const auto &c : invalidCharacters) defaultFilename.replace(c,QLatin1String("_")); @@ -413,17 +421,15 @@ void StdCmdExport::activated(int iMsg) std::map filterMap = App::GetApplication().getExportFilters(); for (const auto &filter : filterMap) { // ignore the project file format - if (filter.first.find("(*.FCStd)") == std::string::npos) { + if (filter.first.find("(*.FCStd)") == std::string::npos) filterList << QString::fromStdString(filter.first); - } } QString formatList = filterList.join(QLatin1String(";;")); Base::Reference hPath = App::GetApplication().GetUserParameter().GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("General"); QString selectedFilter = QString::fromStdString(hPath->GetASCII("FileExportFilter")); - if (!lastExportFilterUsed.isEmpty()) { + if (!lastExportFilterUsed.isEmpty()) selectedFilter = lastExportFilterUsed; - } // Create a default filename for the export // * If this is the first export this session default, generate a new default. @@ -460,9 +466,8 @@ void StdCmdExport::activated(int iMsg) // Append the last extension used, if there is one. if (!lastExportFullPath.isEmpty()) { QFileInfo lastExportFile(lastExportFullPath); - if (!lastExportFile.suffix().isEmpty()) { + if (!lastExportFile.suffix().isEmpty()) defaultFilename += QLatin1String(".") + lastExportFile.suffix(); - } } filenameWasGenerated = true; } @@ -488,12 +493,10 @@ void StdCmdExport::activated(int iMsg) QFileInfo defaultExportFI(defaultFilename); QFileInfo thisExportFI(fileName); if (filenameWasGenerated && - thisExportFI.completeBaseName() == defaultExportFI.completeBaseName()) { + thisExportFI.completeBaseName() == defaultExportFI.completeBaseName()) lastExportUsedGeneratedFilename = true; - } - else { + else lastExportUsedGeneratedFilename = false; - } lastExportFullPath = fileName; } } From 656c4919bcb148e39f78ad0b6ebff767bb59fe4e Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 4 Feb 2021 15:31:06 +0100 Subject: [PATCH 150/168] Qt4: [skip ci] fix build failure --- src/Mod/Sketcher/Gui/DrawSketchHandler.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp b/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp index eac2fdb258..16d32274bd 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp +++ b/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp @@ -29,7 +29,9 @@ # include # include # include +# if QT_VERSION >= 0x050000 # include +# endif # include #endif // #ifndef _PreComp_ @@ -116,11 +118,13 @@ void DrawSketchHandler::setSvgCursor(const QString & cursorName, int x, int y, c qreal defaultCursorSize = isRatioOne ? 64 : 32; qreal hotX = x; qreal hotY = y; +#if QT_VERSION >= 0x050000 #if !defined(Q_OS_WIN32) && !defined(Q_OS_MAC) if (qGuiApp->platformName() == QLatin1String("xcb")) { hotX *= pRatio; hotY *= pRatio; } +#endif #endif qreal cursorSize = defaultCursorSize * pRatio; @@ -159,11 +163,13 @@ void DrawSketchHandler::setCursor(const QPixmap &p,int x,int y, bool autoScale) #endif qreal hotX = x; qreal hotY = y; +#if QT_VERSION >= 0x050000 #if !defined(Q_OS_WIN32) && !defined(Q_OS_MAC) if (qGuiApp->platformName() == QLatin1String("xcb")) { hotX *= pRatio; hotY *= pRatio; } +#endif #endif cursor = QCursor(p1, hotX, hotY); } else { From d9e7ee46e67e415a8bde508db7b43f1b42e6abb0 Mon Sep 17 00:00:00 2001 From: Amritpal Singh Date: Thu, 4 Feb 2021 22:53:41 +0530 Subject: [PATCH 151/168] Draft: Fix bug in getCubicDimensions function --- src/Mod/Draft/draftgeoutils/cuboids.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Mod/Draft/draftgeoutils/cuboids.py b/src/Mod/Draft/draftgeoutils/cuboids.py index 9de283a152..833e46634c 100644 --- a/src/Mod/Draft/draftgeoutils/cuboids.py +++ b/src/Mod/Draft/draftgeoutils/cuboids.py @@ -99,6 +99,8 @@ def getCubicDimensions(shape): # getting length and width vx = vec(base.Edges[0]) vy = vec(base.Edges[1]) + if round(vx.Length) == round(vy.Length): + vy = vec(base.Edges[2]) # getting rotations rotZ = DraftVecUtils.angle(vx) From bd8a3c5021761dcc3019987a741577dc2bf02c79 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Mon, 21 Dec 2020 16:42:47 -0600 Subject: [PATCH 152/168] Add pref window that loads unloaded workbenches A new group is added to the Preferences window that explains why some preferences may appear to be missing due to the workbench being unloaded. It lists the unloaded workbenches, and offers to load them, updating the preferences dialog as necessary. This at least partially resolves issue #4474. --- src/Gui/CMakeLists.txt | 5 + src/Gui/DlgPreferencesImp.cpp | 178 +++-- src/Gui/DlgPreferencesImp.h | 14 +- src/Gui/DlgSettingsLazyLoaded.ui | 106 +++ src/Gui/DlgSettingsLazyLoadedImp.cpp | 118 +++ src/Gui/DlgSettingsLazyLoadedImp.h | 66 ++ src/Gui/Icons/preferences-workbenches.svg | 831 ++++++++++++++++++++++ src/Gui/Icons/resource.qrc | 1 + src/Gui/resource.cpp | 26 +- 9 files changed, 1286 insertions(+), 59 deletions(-) create mode 100644 src/Gui/DlgSettingsLazyLoaded.ui create mode 100644 src/Gui/DlgSettingsLazyLoadedImp.cpp create mode 100644 src/Gui/DlgSettingsLazyLoadedImp.h create mode 100644 src/Gui/Icons/preferences-workbenches.svg diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index e2412c0c6d..3b6bb31bc8 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -322,6 +322,7 @@ set(Gui_MOC_HDRS DlgSettingsColorGradientImp.h DlgSettingsDocumentImp.h DlgSettingsImageImp.h + DlgSettingsLazyLoadedImp.h DlgSettingsMacroImp.h DlgSettingsUnitsImp.h DlgCheckableMessageBox.h @@ -450,6 +451,7 @@ SET(Gui_UIC_SRCS DlgSettingsColorGradient.ui DlgSettingsDocument.ui DlgSettingsImage.ui + DlgSettingsLazyLoaded.ui DlgSettingsMacro.ui DlgCheckableMessageBox.ui DlgToolbars.ui @@ -681,6 +683,7 @@ SET(Dialog_Settings_CPP_SRCS DlgSettingsColorGradientImp.cpp DlgSettingsDocumentImp.cpp DlgSettingsImageImp.cpp + DlgSettingsLazyLoadedImp.cpp DlgSettingsMacroImp.cpp ) SET(Dialog_Settings_HPP_SRCS @@ -697,6 +700,7 @@ SET(Dialog_Settings_HPP_SRCS DlgSettingsColorGradientImp.h DlgSettingsDocumentImp.h DlgSettingsImageImp.h + DlgSettingsLazyLoadedImp.h DlgSettingsMacroImp.h ) SET(Dialog_Settings_SRCS @@ -715,6 +719,7 @@ SET(Dialog_Settings_SRCS DlgSettingsColorGradient.ui DlgSettingsDocument.ui DlgSettingsImage.ui + DlgSettingsLazyLoaded.ui DlgSettingsMacro.ui ) SOURCE_GROUP("Dialog\\Settings" FILES ${Dialog_Settings_SRCS}) diff --git a/src/Gui/DlgPreferencesImp.cpp b/src/Gui/DlgPreferencesImp.cpp index 6ac6bd973f..d0be413089 100644 --- a/src/Gui/DlgPreferencesImp.cpp +++ b/src/Gui/DlgPreferencesImp.cpp @@ -48,16 +48,18 @@ #include "BitmapFactory.h" #include "MainWindow.h" - using namespace Gui::Dialog; +const int DlgPreferencesImp::GroupNameRole = Qt::UserRole; + /* TRANSLATOR Gui::Dialog::DlgPreferencesImp */ std::list DlgPreferencesImp::_pages; +DlgPreferencesImp* DlgPreferencesImp::_activeDialog = nullptr; /** - * Constructs a DlgPreferencesImp which is a child of 'parent', with the - * name 'name' and widget flags set to 'f' + * Constructs a DlgPreferencesImp which is a child of 'parent', with + * widget flags set to 'fl' * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. @@ -67,8 +69,8 @@ DlgPreferencesImp::DlgPreferencesImp(QWidget* parent, Qt::WindowFlags fl) invalidParameter(false), canEmbedScrollArea(true) { ui->setupUi(this); - ui->listBox->setFixedWidth(130); - ui->listBox->setGridSize(QSize(108, 120)); + ui->listBox->setFixedWidth(108); + ui->listBox->setGridSize(QSize(90, 75)); connect(ui->buttonBox, SIGNAL (helpRequested()), getMainWindow(), SLOT (whatsThis())); @@ -76,6 +78,11 @@ DlgPreferencesImp::DlgPreferencesImp(QWidget* parent, Qt::WindowFlags fl) this, SLOT(changeGroup(QListWidgetItem *, QListWidgetItem*))); setupPages(); + + // Maintain a static pointer to the current active dialog (if there is one) so that + // if the static page manipulation functions are called while the dialog is showing + // it can update its content. + DlgPreferencesImp::_activeDialog = this; } /** @@ -83,50 +90,19 @@ DlgPreferencesImp::DlgPreferencesImp(QWidget* parent, Qt::WindowFlags fl) */ DlgPreferencesImp::~DlgPreferencesImp() { - // no need to delete child widgets, Qt does it all for us - delete ui; + if (DlgPreferencesImp::_activeDialog == this) { + DlgPreferencesImp::_activeDialog = nullptr; + } } void DlgPreferencesImp::setupPages() { // make sure that pages are ready to create GetWidgetFactorySupplier(); - for (std::list::iterator it = _pages.begin(); it != _pages.end(); ++it) { - QTabWidget* tabWidget = new QTabWidget; - ui->tabWidgetStack->addWidget(tabWidget); - - QByteArray group = it->first.c_str(); - QListWidgetItem *item = new QListWidgetItem(ui->listBox); - item->setData(Qt::UserRole, QVariant(group)); - item->setText(QObject::tr(group.constData())); - std::string fileName = it->first; - for (std::string::iterator ch = fileName.begin(); ch != fileName.end(); ++ch) { - if (*ch == ' ') *ch = '_'; - else *ch = tolower(*ch); - } - fileName = std::string("preferences-") + fileName; - QPixmap icon = Gui::BitmapFactory().pixmapFromSvg(fileName.c_str(), QSize(96,96)); - if (icon.isNull()) { - icon = Gui::BitmapFactory().pixmap(fileName.c_str()); - if (icon.isNull()) { - qWarning() << "No group icon found for " << fileName.c_str(); - } - else if (icon.size() != QSize(96,96)) { - qWarning() << "Group icon for " << fileName.c_str() << " is not of size 96x96"; - } - } - item->setIcon(icon); - item->setTextAlignment(Qt::AlignHCenter); - item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - for (std::list::iterator jt = it->second.begin(); jt != it->second.end(); ++jt) { - PreferencePage* page = WidgetFactory().createPreferencePage(jt->c_str()); - if (page) { - tabWidget->addTab(page, page->windowTitle()); - page->loadSettings(); - } - else { - Base::Console().Warning("%s is not a preference page\n", jt->c_str()); - } + for (const auto &group : _pages) { + QTabWidget* groupTab = createTabForGroup(group.first); + for (const auto &page : group.second) { + createPageInGroup(groupTab, page); } } @@ -134,6 +110,63 @@ void DlgPreferencesImp::setupPages() ui->listBox->setCurrentRow(0); } +/** + * Create the necessary widgets for a new group named \a groupName. Returns a + * pointer to the group's QTabWidget: that widget's lifetime is managed by the + * tabWidgetStack, do not manually deallocate. + */ +QTabWidget* DlgPreferencesImp::createTabForGroup(const std::string &groupName) +{ + QString groupNameQString = QString::fromStdString(groupName); + + QTabWidget* tabWidget = new QTabWidget; + ui->tabWidgetStack->addWidget(tabWidget); + tabWidget->setProperty("GroupName", QVariant(groupNameQString)); + + QListWidgetItem* item = new QListWidgetItem(ui->listBox); + item->setData(GroupNameRole, QVariant(groupNameQString)); + item->setText(QObject::tr(groupNameQString.toLatin1())); + std::string fileName = groupName; + for (auto & ch : fileName) { + if (ch == ' ') ch = '_'; + else ch = tolower(ch); + } + fileName = std::string("preferences-") + fileName; + QPixmap icon = Gui::BitmapFactory().pixmapFromSvg(fileName.c_str(), QSize(48, 48)); + if (icon.isNull()) { + icon = Gui::BitmapFactory().pixmap(fileName.c_str()); + if (icon.isNull()) { + qWarning() << "No group icon found for " << fileName.c_str(); + } + else if (icon.size() != QSize(48, 48)) { + icon = Gui::BitmapFactory().resize(48, 48, icon, Qt::TransparentMode); + qWarning() << "Group icon for " << fileName.c_str() << " is not of size 48x48, so it was scaled"; + } + } + item->setIcon(icon); + item->setTextAlignment(Qt::AlignHCenter); + item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + + return tabWidget; +} + +/** + * Create a new preference page called \a pageName on the group tab \a tabWidget. + */ +void DlgPreferencesImp::createPageInGroup(QTabWidget *tabWidget, const std::string &pageName) +{ + PreferencePage* page = WidgetFactory().createPreferencePage(pageName.c_str()); + if (page) { + tabWidget->addTab(page, page->windowTitle()); + page->loadSettings(); + page->setProperty("GroupName", tabWidget->property("GroupName")); + page->setProperty("PageName", QVariant(QString::fromStdString(pageName))); + } + else { + Base::Console().Warning("%s is not a preference page\n", pageName.c_str()); + } +} + void DlgPreferencesImp::changeGroup(QListWidgetItem *current, QListWidgetItem *previous) { if (!current) @@ -160,6 +193,10 @@ void DlgPreferencesImp::addPage(const std::string& className, const std::string& std::list pages; pages.push_back(className); _pages.push_back(std::make_pair(group, pages)); + + if (DlgPreferencesImp::_activeDialog != nullptr) { + _activeDialog->reloadPages(); + } } void DlgPreferencesImp::removePage(const std::string& className, const std::string& group) @@ -193,7 +230,7 @@ void DlgPreferencesImp::activateGroupPage(const QString& group, int index) int ct = ui->listBox->count(); for (int i=0; ilistBox->item(i); - if (item->data(Qt::UserRole).toString() == group) { + if (item->data(GroupNameRole).toString() == group) { ui->listBox->setCurrentItem(item); QTabWidget* tabWidget = (QTabWidget*)ui->tabWidgetStack->widget(i); tabWidget->setCurrentIndex(index); @@ -250,6 +287,57 @@ void DlgPreferencesImp::restoreDefaults() } } +/** + * If the dialog is currently showing and the static variable _pages changed, this function + * will rescan that list of pages and add any that are new to the current dialog. It will not + * remove any pages that are no longer in the list, and will not change the user's current + * active page. + */ +void DlgPreferencesImp::reloadPages() +{ + // Make sure that pages are ready to create + GetWidgetFactorySupplier(); + + for (const auto &group : _pages) { + QString groupName = QString::fromStdString(group.first); + + // First, does this group already exist? + QTabWidget* tabWidget = nullptr; + for (int tabNumber = 0; tabNumber < ui->tabWidgetStack->count(); ++tabNumber) { + auto thisTabWidget = qobject_cast(ui->tabWidgetStack->widget(tabNumber)); + if (thisTabWidget->property("GroupName").toString() == groupName) { + tabWidget = thisTabWidget; + break; + } + } + + // This is a new tab that wasn't there when we started this instance of the dialog: + if (!tabWidget) { + tabWidget = createTabForGroup(group.first); + } + + // Move on to the pages in the group to see if we need to add any + for (const auto& page : group.second) { + + // Does this page already exist? + QString pageName = QString::fromStdString(page); + bool pageExists = false; + for (int pageNumber = 0; pageNumber < tabWidget->count(); ++pageNumber) { + PreferencePage* prefPage = qobject_cast(tabWidget->widget(pageNumber)); + if (prefPage && prefPage->property("PageName").toString() == pageName) { + pageExists = true; + break; + } + } + + // This is a new page that wasn't there when we started this instance of the dialog: + if (!pageExists) { + createPageInGroup(tabWidget, page); + } + } + } +} + void DlgPreferencesImp::applyChanges() { // Checks if any of the classes that represent several pages of settings @@ -366,7 +454,7 @@ void DlgPreferencesImp::changeEvent(QEvent *e) // update the items' text for (int i=0; ilistBox->count(); i++) { QListWidgetItem *item = ui->listBox->item(i); - QByteArray group = item->data(Qt::UserRole).toByteArray(); + QByteArray group = item->data(GroupNameRole).toByteArray(); item->setText(QObject::tr(group.constData())); } } else { diff --git a/src/Gui/DlgPreferencesImp.h b/src/Gui/DlgPreferencesImp.h index 6b6ff17ee4..172baba36d 100644 --- a/src/Gui/DlgPreferencesImp.h +++ b/src/Gui/DlgPreferencesImp.h @@ -25,9 +25,11 @@ #define GUI_DIALOG_DLGPREFERENCESIMP_H #include +#include class QAbstractButton; class QListWidgetItem; +class QTabWidget; namespace Gui { namespace Dialog { @@ -123,6 +125,7 @@ protected: void showEvent(QShowEvent*); void resizeEvent(QResizeEvent*); + protected Q_SLOTS: void changeGroup(QListWidgetItem *current, QListWidgetItem *previous); void on_buttonBox_clicked(QAbstractButton*); @@ -132,16 +135,23 @@ private: /** @name for internal use only */ //@{ void setupPages(); + QTabWidget* createTabForGroup(const std::string& groupName); + void createPageInGroup(QTabWidget* tabWidget, const std::string& pageName); void applyChanges(); void restoreDefaults(); + void reloadPages(); //@} private: - typedef std::pair > TGroupPages; + typedef std::pair> TGroupPages; static std::list _pages; /**< Name of all registered preference pages */ - Ui_DlgPreferences* ui; + std::unique_ptr ui; bool invalidParameter; bool canEmbedScrollArea; + + static const int GroupNameRole; /**< A name for our Qt::UserRole, used when storing user data in a list item */ + + static DlgPreferencesImp* _activeDialog; /**< Defaults to the nullptr, points to the current instance if there is one */ }; } // namespace Dialog diff --git a/src/Gui/DlgSettingsLazyLoaded.ui b/src/Gui/DlgSettingsLazyLoaded.ui new file mode 100644 index 0000000000..3bfd0e9490 --- /dev/null +++ b/src/Gui/DlgSettingsLazyLoaded.ui @@ -0,0 +1,106 @@ + + + Gui::Dialog::DlgSettingsLazyLoaded + + + + 0 + 0 + 607 + 859 + + + + Unloaded Workbenches + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p>Load the selected workbenches, adding their preference windows to the preferences dialog.</p></body></html> + + + Load Selected + + + + + + + + + + 0 + 0 + + + + + 0 + 150 + + + + <html><head/><body><p>Available unloaded workbenches</p></body></html> + + + QAbstractItemView::ExtendedSelection + + + + + + + + 0 + 0 + + + + + 0 + 50 + + + + <html><head/><body><p>To preserve resources, FreeCAD does not load workbenches until they are used. Loading them may provide access to additional preferences related to their functionality.</p><p>The following workbenches are available in your installation, but are not yet loaded:</p></body></html> + + + true + + + + + + + Qt::Vertical + + + + 429 + 37 + + + + + + + + + + diff --git a/src/Gui/DlgSettingsLazyLoadedImp.cpp b/src/Gui/DlgSettingsLazyLoadedImp.cpp new file mode 100644 index 0000000000..5f2a625d34 --- /dev/null +++ b/src/Gui/DlgSettingsLazyLoadedImp.cpp @@ -0,0 +1,118 @@ +/*************************************************************************** + * Copyright (c) 2020 Chris Hennes (chennes@pioneerlibrarysystem.org) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" + +#include "DlgSettingsLazyLoadedImp.h" +#include "ui_DlgSettingsLazyLoaded.h" +#include "PrefWidgets.h" +#include "AutoSaver.h" + +#include "Application.h" +#include "WorkbenchManager.h" +#include "Workbench.h" + +using namespace Gui::Dialog; + +const uint DlgSettingsLazyLoadedImp::WorkbenchNameRole = Qt::UserRole; + +/* TRANSLATOR Gui::Dialog::DlgSettingsLazyLoadedImp */ + +/** + * Constructs a DlgSettingsLazyLoadedImp + */ +DlgSettingsLazyLoadedImp::DlgSettingsLazyLoadedImp( QWidget* parent ) + : PreferencePage( parent ) + , ui(new Ui_DlgSettingsLazyLoaded) +{ + ui->setupUi(this); + buildUnloadedWorkbenchList(); + connect(ui->loadButton, SIGNAL(clicked()), this, SLOT(onLoadClicked())); +} + +/** + * Destroys the object and frees any allocated resources + */ +DlgSettingsLazyLoadedImp::~DlgSettingsLazyLoadedImp() +{ +} + + +void DlgSettingsLazyLoadedImp::saveSettings() +{ + +} + +void DlgSettingsLazyLoadedImp::loadSettings() +{ + +} + +void DlgSettingsLazyLoadedImp::onLoadClicked() +{ + Workbench* originalActiveWB = WorkbenchManager::instance()->active(); + auto selection = ui->workbenchList->selectedItems(); + for (const auto& item : selection) { + auto name = item->data(WorkbenchNameRole).toString().toStdString(); + Application::Instance->activateWorkbench(name.c_str()); + } + Application::Instance->activateWorkbench(originalActiveWB->name().c_str()); + buildUnloadedWorkbenchList(); +} + + +/** +Build the list of unloaded workbenches. +*/ +void DlgSettingsLazyLoadedImp::buildUnloadedWorkbenchList() +{ + ui->workbenchList->clear(); + QStringList workbenches = Application::Instance->workbenches(); + for (const auto& wbName : workbenches) { + const auto& wb = WorkbenchManager::instance()->getWorkbench(wbName.toStdString()); + if (!wb) { + auto wbIcon = Application::Instance->workbenchIcon(wbName); + auto wbDisplayName = Application::Instance->workbenchMenuText(wbName); + auto wbTooltip = Application::Instance->workbenchToolTip(wbName); + QListWidgetItem *wbRow = new QListWidgetItem(wbIcon, wbDisplayName); + wbRow->setData(WorkbenchNameRole, QVariant(wbName)); // Store the actual internal name for easier loading + wbRow->setToolTip(wbTooltip); + ui->workbenchList->addItem(wbRow); // Transfers ownership to the QListWidget + } + } +} + +/** + * Sets the strings of the subwidgets using the current language. + */ +void DlgSettingsLazyLoadedImp::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::LanguageChange) { + ui->retranslateUi(this); + } + else { + QWidget::changeEvent(e); + } +} + +#include "moc_DlgSettingsLazyLoadedImp.cpp" \ No newline at end of file diff --git a/src/Gui/DlgSettingsLazyLoadedImp.h b/src/Gui/DlgSettingsLazyLoadedImp.h new file mode 100644 index 0000000000..71c9ae07d0 --- /dev/null +++ b/src/Gui/DlgSettingsLazyLoadedImp.h @@ -0,0 +1,66 @@ +/*************************************************************************** + * Copyright (c) 2020 Chris Hennes (chennes@pioneerlibrarysystem.org) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#ifndef GUI_DIALOG_DLGSETTINGSLAZYLOADED_IMP_H +#define GUI_DIALOG_DLGSETTINGSLAZYLOADED_IMP_H + +#include "PropertyPage.h" +#include + +namespace Gui { +namespace Dialog { +class Ui_DlgSettingsLazyLoaded; + + +/** + * The DlgSettingsLazyLoadedImp class implements a pseudo-preference page explain why + * the remaining preference pages aren't loaded yet, and to help the user do so on demand. + * \author Jürgen Riegel + */ +class DlgSettingsLazyLoadedImp : public PreferencePage +{ + Q_OBJECT + +public: + DlgSettingsLazyLoadedImp( QWidget* parent = 0 ); + ~DlgSettingsLazyLoadedImp(); + + void saveSettings(); + void loadSettings(); + +protected Q_SLOTS: + void onLoadClicked(); + +protected: + void buildUnloadedWorkbenchList(); + void changeEvent(QEvent *e); + +private: + std::unique_ptr ui; + static const uint WorkbenchNameRole; +}; + +} // namespace Dialog +} // namespace Gui + +#endif // GUI_DIALOG_DLGSETTINGSLAZYLOADED_IMP_H diff --git a/src/Gui/Icons/preferences-workbenches.svg b/src/Gui/Icons/preferences-workbenches.svg new file mode 100644 index 0000000000..370abc9f77 --- /dev/null +++ b/src/Gui/Icons/preferences-workbenches.svg @@ -0,0 +1,831 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + 2005-10-15 + + + Andreas Nilsson + + + + + edit + copy + + + + + + Jakub Steiner + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Gui/Icons/resource.qrc b/src/Gui/Icons/resource.qrc index b626752ac7..28d9c0b170 100644 --- a/src/Gui/Icons/resource.qrc +++ b/src/Gui/Icons/resource.qrc @@ -26,6 +26,7 @@ preferences-display.svg preferences-general.svg preferences-import-export.svg + preferences-workbenches.svg utilities-terminal.svg ClassBrowser/const_member.png ClassBrowser/member.png diff --git a/src/Gui/resource.cpp b/src/Gui/resource.cpp index d3d3647690..099539a5a1 100644 --- a/src/Gui/resource.cpp +++ b/src/Gui/resource.cpp @@ -27,7 +27,7 @@ #include "WidgetFactory.h" #include "Workbench.h" -// INCLUDE YOUR PREFERENCFE PAGES HERE +// INCLUDE YOUR PREFERENCE PAGES HERE // #include "DlgPreferencesImp.h" #include "DlgSettings3DViewImp.h" @@ -41,6 +41,7 @@ #include "DlgSettingsDocumentImp.h" //#include "DlgOnlineHelpImp.h" #include "DlgReportViewImp.h" +#include "DlgSettingsLazyLoadedImp.h" #include "DlgToolbarsImp.h" #include "DlgWorkbenchesImp.h" @@ -64,17 +65,18 @@ WidgetFactorySupplier::WidgetFactorySupplier() // ADD YOUR PREFERENCE PAGES HERE // // - new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); - //new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); - new PrefPageProducer( QT_TRANSLATE_NOOP("QObject","General") ); - new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); - new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); - new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); - new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); - new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); - new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","Display") ); - new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","Display") ); - new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","Display") ); + new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); + //new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); + new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); + new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); + new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); + new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); + new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); + new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","General") ); + new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","Display") ); + new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","Display") ); + new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","Display") ); + new PrefPageProducer ( QT_TRANSLATE_NOOP("QObject","Workbenches") ); // ADD YOUR CUSTOMIZE PAGES HERE // From 13e2f93baec120a14dc24a6a3832424250d446a0 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 3 Jan 2021 16:01:08 -0600 Subject: [PATCH 153/168] Fix bug due to early return Github user @marioalexis84 found a bug that caused only the first page of the most recently-added workbench to show. This refactors the AddPage() static function to eliminate the early return statement that was the cause of that bug. --- src/Gui/DlgPreferencesImp.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Gui/DlgPreferencesImp.cpp b/src/Gui/DlgPreferencesImp.cpp index d0be413089..d5d327090c 100644 --- a/src/Gui/DlgPreferencesImp.cpp +++ b/src/Gui/DlgPreferencesImp.cpp @@ -183,18 +183,27 @@ void DlgPreferencesImp::changeGroup(QListWidgetItem *current, QListWidgetItem *p */ void DlgPreferencesImp::addPage(const std::string& className, const std::string& group) { + std::list::iterator groupToAddTo = _pages.end(); for (std::list::iterator it = _pages.begin(); it != _pages.end(); ++it) { if (it->first == group) { - it->second.push_back(className); - return; + groupToAddTo = it; + break; } } - std::list pages; - pages.push_back(className); - _pages.push_back(std::make_pair(group, pages)); + if (groupToAddTo != _pages.end()) { + // The group exists: add this page to the end of the list + groupToAddTo->second.push_back(className); + } + else { + // This is a new group: create it, with its one page + std::list pages; + pages.push_back(className); + _pages.push_back(std::make_pair(group, pages)); + } if (DlgPreferencesImp::_activeDialog != nullptr) { + // If the dialog is currently showing, tell it to insert the new page _activeDialog->reloadPages(); } } From d5b68febbb4989b290b9af36bb2cb21d61a87a2e Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 3 Jan 2021 16:14:43 -0600 Subject: [PATCH 154/168] Add Workbench name as Tab item tooltip In cases where the user's font is large or the Workbench name is long, the new, narrower sidebar may not display the entire workbench name. To address those cases, the tooltip for the element is now also set to the workbench name. Thanks to Github user @marioalexis84 for the suggestion. --- src/Gui/DlgPreferencesImp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Gui/DlgPreferencesImp.cpp b/src/Gui/DlgPreferencesImp.cpp index d5d327090c..20757c309b 100644 --- a/src/Gui/DlgPreferencesImp.cpp +++ b/src/Gui/DlgPreferencesImp.cpp @@ -126,6 +126,7 @@ QTabWidget* DlgPreferencesImp::createTabForGroup(const std::string &groupName) QListWidgetItem* item = new QListWidgetItem(ui->listBox); item->setData(GroupNameRole, QVariant(groupNameQString)); item->setText(QObject::tr(groupNameQString.toLatin1())); + item->setToolTip(QObject::tr(groupNameQString.toLatin1())); std::string fileName = groupName; for (auto & ch : fileName) { if (ch == ' ') ch = '_'; From 7560a35ea0e5050009e3f71b636e8df4a29d4652 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Thu, 4 Feb 2021 20:06:30 -0600 Subject: [PATCH 155/168] Fix minor issues with Workbenches preference panel This fixes three issues with the new Workbench Loader preferences panel reported by users: * Some icons were not being properly scaled to the new 48x48 size * Some text was being incorrectly cut off, despite fitting in the column * The sort order of the workbenches was incorrect when external workbenches were added --- src/Gui/DlgPreferencesImp.cpp | 4 ++-- src/Gui/DlgSettingsLazyLoadedImp.cpp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Gui/DlgPreferencesImp.cpp b/src/Gui/DlgPreferencesImp.cpp index 20757c309b..e70981521a 100644 --- a/src/Gui/DlgPreferencesImp.cpp +++ b/src/Gui/DlgPreferencesImp.cpp @@ -70,7 +70,7 @@ DlgPreferencesImp::DlgPreferencesImp(QWidget* parent, Qt::WindowFlags fl) { ui->setupUi(this); ui->listBox->setFixedWidth(108); - ui->listBox->setGridSize(QSize(90, 75)); + ui->listBox->setGridSize(QSize(108, 75)); connect(ui->buttonBox, SIGNAL (helpRequested()), getMainWindow(), SLOT (whatsThis())); @@ -140,7 +140,7 @@ QTabWidget* DlgPreferencesImp::createTabForGroup(const std::string &groupName) qWarning() << "No group icon found for " << fileName.c_str(); } else if (icon.size() != QSize(48, 48)) { - icon = Gui::BitmapFactory().resize(48, 48, icon, Qt::TransparentMode); + icon = icon.scaled(48, 48, Qt::KeepAspectRatio, Qt::SmoothTransformation); qWarning() << "Group icon for " << fileName.c_str() << " is not of size 48x48, so it was scaled"; } } diff --git a/src/Gui/DlgSettingsLazyLoadedImp.cpp b/src/Gui/DlgSettingsLazyLoadedImp.cpp index 5f2a625d34..660c10c79b 100644 --- a/src/Gui/DlgSettingsLazyLoadedImp.cpp +++ b/src/Gui/DlgSettingsLazyLoadedImp.cpp @@ -100,6 +100,7 @@ void DlgSettingsLazyLoadedImp::buildUnloadedWorkbenchList() ui->workbenchList->addItem(wbRow); // Transfers ownership to the QListWidget } } + ui->workbenchList->sortItems(); } /** From 21dc72ed101b76d01ced681572f89d7689a8b263 Mon Sep 17 00:00:00 2001 From: donovaly Date: Fri, 5 Feb 2021 02:56:05 +0100 Subject: [PATCH 156/168] [TD] make more pointers to the UI std::unique_ptr addendum to PR #4293 (as noted in https://github.com/FreeCAD/FreeCAD/pull/4271#discussion_r554673632 the pointer to the UI should be a unique pointer. This PR does this for remaining TD dialogs that don't already use a unique_ptr.) --- src/Mod/TechDraw/Gui/SymbolChooser.h | 2 +- src/Mod/TechDraw/Gui/TaskLineDecor.cpp | 1 - src/Mod/TechDraw/Gui/TaskLineDecor.h | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Mod/TechDraw/Gui/SymbolChooser.h b/src/Mod/TechDraw/Gui/SymbolChooser.h index 3f4bdec4b6..81d26c23d1 100644 --- a/src/Mod/TechDraw/Gui/SymbolChooser.h +++ b/src/Mod/TechDraw/Gui/SymbolChooser.h @@ -54,7 +54,7 @@ protected: void loadSymbolNames(QString pathToSymbols); private: - Ui_SymbolChooser* ui; + std::unique_ptr ui; QString m_symbolDir; QString m_symbolPath; QString m_source; diff --git a/src/Mod/TechDraw/Gui/TaskLineDecor.cpp b/src/Mod/TechDraw/Gui/TaskLineDecor.cpp index 789be010c8..4758f4f62f 100644 --- a/src/Mod/TechDraw/Gui/TaskLineDecor.cpp +++ b/src/Mod/TechDraw/Gui/TaskLineDecor.cpp @@ -79,7 +79,6 @@ TaskLineDecor::TaskLineDecor(TechDraw::DrawViewPart* partFeat, TaskLineDecor::~TaskLineDecor() { - delete ui; } void TaskLineDecor::initUi() diff --git a/src/Mod/TechDraw/Gui/TaskLineDecor.h b/src/Mod/TechDraw/Gui/TaskLineDecor.h index c5205a754f..d64b8fe3ac 100644 --- a/src/Mod/TechDraw/Gui/TaskLineDecor.h +++ b/src/Mod/TechDraw/Gui/TaskLineDecor.h @@ -70,7 +70,7 @@ protected: void getDefaults(void); private: - Ui_TaskLineDecor* ui; + std::unique_ptr ui; TechDraw::DrawViewPart* m_partFeat; std::vector m_edges; int m_style; From 9c9244dfb31db5d28f41dd9474696eb58c68330e Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Tue, 2 Feb 2021 22:21:45 -0600 Subject: [PATCH 157/168] Guard against segfaults due to out-of-order dialog deletion --- src/Gui/TaskView/TaskView.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Gui/TaskView/TaskView.cpp b/src/Gui/TaskView/TaskView.cpp index 12fa730d60..a6026d0552 100644 --- a/src/Gui/TaskView/TaskView.cpp +++ b/src/Gui/TaskView/TaskView.cpp @@ -775,6 +775,11 @@ void TaskView::removeTaskWatcher(void) void TaskView::accept() { + if (!ActiveDialog) { // Protect against segfaults due to out-of-order deletions + Base::Console().Warning("ActiveDialog was null in call to TaskView::accept()\n"); + return; + } + // Make sure that if 'accept' calls 'closeDialog' the deletion is postponed until // the dialog leaves the 'accept' method ActiveDialog->setProperty("taskview_accept_or_reject", true); @@ -786,6 +791,11 @@ void TaskView::accept() void TaskView::reject() { + if (!ActiveDialog) { // Protect against segfaults due to out-of-order deletions + Base::Console().Warning("ActiveDialog was null in call to TaskView::reject()\n"); + return; + } + // Make sure that if 'reject' calls 'closeDialog' the deletion is postponed until // the dialog leaves the 'reject' method ActiveDialog->setProperty("taskview_accept_or_reject", true); From 1e7929ba59ab4078701afa135f53cd50bac84209 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 5 Feb 2021 16:36:19 +0100 Subject: [PATCH 158/168] Gui: add virtual method 'closed' to TaskDialog that is called when deleting a dialog --- src/Gui/TaskView/TaskDialog.cpp | 5 +++++ src/Gui/TaskView/TaskDialog.h | 2 ++ src/Gui/TaskView/TaskView.cpp | 1 + 3 files changed, 8 insertions(+) diff --git a/src/Gui/TaskView/TaskDialog.cpp b/src/Gui/TaskView/TaskDialog.cpp index 75643b1837..e36279c843 100644 --- a/src/Gui/TaskView/TaskDialog.cpp +++ b/src/Gui/TaskView/TaskDialog.cpp @@ -79,6 +79,11 @@ void TaskDialog::open() } +void TaskDialog::closed() +{ + +} + void TaskDialog::clicked(int) { diff --git a/src/Gui/TaskView/TaskDialog.h b/src/Gui/TaskView/TaskDialog.h index e26bbc8fb8..51c44ba359 100644 --- a/src/Gui/TaskView/TaskDialog.h +++ b/src/Gui/TaskView/TaskDialog.h @@ -103,6 +103,8 @@ public: public: /// is called by the framework when the dialog is opened virtual void open(); + /// is called by the framework when the dialog is closed + virtual void closed(); /// is called by the framework if a button is clicked which has no accept or reject role virtual void clicked(int); /// is called by the framework if the dialog is accepted (Ok) diff --git a/src/Gui/TaskView/TaskView.cpp b/src/Gui/TaskView/TaskView.cpp index a6026d0552..456c411905 100644 --- a/src/Gui/TaskView/TaskView.cpp +++ b/src/Gui/TaskView/TaskView.cpp @@ -651,6 +651,7 @@ void TaskView::removeDialog(void) addTaskWatcher(); if (remove) { + remove->closed(); remove->emitDestructionSignal(); delete remove; } From 9c3c562f7bd0d18d50a5ac1ac6085867aafebf3d Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 5 Feb 2021 16:37:00 +0100 Subject: [PATCH 159/168] Surface: re-implement closed() in TaskFilling --- src/Mod/Surface/Gui/TaskFilling.cpp | 13 ++++++------- src/Mod/Surface/Gui/TaskFilling.h | 1 + 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Mod/Surface/Gui/TaskFilling.cpp b/src/Mod/Surface/Gui/TaskFilling.cpp index 30b7f00a8b..3b07bee830 100644 --- a/src/Mod/Surface/Gui/TaskFilling.cpp +++ b/src/Mod/Surface/Gui/TaskFilling.cpp @@ -91,13 +91,7 @@ bool ViewProviderFilling::setEdit(int ModNum) void ViewProviderFilling::unsetEdit(int ModNum) { - if (ModNum == ViewProvider::Default) { - // when pressing ESC make sure to close the dialog - QTimer::singleShot(0, &Gui::Control(), SLOT(closeDialog())); - } - else { - PartGui::ViewProviderSpline::unsetEdit(ModNum); - } + PartGui::ViewProviderSpline::unsetEdit(ModNum); } QIcon ViewProviderFilling::getIcon(void) const @@ -902,6 +896,11 @@ void TaskFilling::open() widget3->open(); } +void TaskFilling::closed() +{ + widget1->reject(); +} + bool TaskFilling::accept() { bool ok = widget1->accept(); diff --git a/src/Mod/Surface/Gui/TaskFilling.h b/src/Mod/Surface/Gui/TaskFilling.h index 5d5d825444..c069187adc 100644 --- a/src/Mod/Surface/Gui/TaskFilling.h +++ b/src/Mod/Surface/Gui/TaskFilling.h @@ -116,6 +116,7 @@ public: public: void open(); + void closed(); bool accept(); bool reject(); From 65656f6d22fc7beefa652fbee179a06f02fa22a8 Mon Sep 17 00:00:00 2001 From: donovaly Date: Fri, 5 Feb 2021 01:38:34 +0100 Subject: [PATCH 160/168] [Sketcher] make more pointers to the UI std::unique_ptr addendum to PR #4362 (Same as PR #4293, just for Sketcher as noted in https://github.com/FreeCAD/FreeCAD/pull/4271#discussion_r554673632 the pointer to the UI should be a unique pointer. This PR does this for all Sketcher dialogs that don't already use a unique_ptr.) --- src/Mod/Sketcher/Gui/SketchMirrorDialog.cpp | 1 - src/Mod/Sketcher/Gui/SketchMirrorDialog.h | 2 +- src/Mod/Sketcher/Gui/SketchOrientationDialog.cpp | 1 - src/Mod/Sketcher/Gui/SketchOrientationDialog.h | 2 +- src/Mod/Sketcher/Gui/SketchRectangularArrayDialog.cpp | 1 - src/Mod/Sketcher/Gui/SketchRectangularArrayDialog.h | 2 +- src/Mod/Sketcher/Gui/SketcherRegularPolygonDialog.cpp | 1 - src/Mod/Sketcher/Gui/SketcherRegularPolygonDialog.h | 2 +- src/Mod/Sketcher/Gui/SketcherSettings.cpp | 3 --- src/Mod/Sketcher/Gui/SketcherSettings.h | 7 ++++--- 10 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/Mod/Sketcher/Gui/SketchMirrorDialog.cpp b/src/Mod/Sketcher/Gui/SketchMirrorDialog.cpp index 149f9abc48..c26801db82 100644 --- a/src/Mod/Sketcher/Gui/SketchMirrorDialog.cpp +++ b/src/Mod/Sketcher/Gui/SketchMirrorDialog.cpp @@ -49,7 +49,6 @@ SketchMirrorDialog::SketchMirrorDialog(void) SketchMirrorDialog::~SketchMirrorDialog() { - delete ui; } void SketchMirrorDialog::accept() diff --git a/src/Mod/Sketcher/Gui/SketchMirrorDialog.h b/src/Mod/Sketcher/Gui/SketchMirrorDialog.h index a59a3ecaba..debea06eec 100644 --- a/src/Mod/Sketcher/Gui/SketchMirrorDialog.h +++ b/src/Mod/Sketcher/Gui/SketchMirrorDialog.h @@ -43,7 +43,7 @@ public: void accept(); private: - Ui_SketchMirrorDialog* ui; + std::unique_ptr ui; }; } diff --git a/src/Mod/Sketcher/Gui/SketchOrientationDialog.cpp b/src/Mod/Sketcher/Gui/SketchOrientationDialog.cpp index 3b4f86e731..329ffe0276 100644 --- a/src/Mod/Sketcher/Gui/SketchOrientationDialog.cpp +++ b/src/Mod/Sketcher/Gui/SketchOrientationDialog.cpp @@ -52,7 +52,6 @@ SketchOrientationDialog::SketchOrientationDialog(void) SketchOrientationDialog::~SketchOrientationDialog() { - delete ui; } void SketchOrientationDialog::accept() diff --git a/src/Mod/Sketcher/Gui/SketchOrientationDialog.h b/src/Mod/Sketcher/Gui/SketchOrientationDialog.h index a2782c0e4e..f8629cf2f0 100644 --- a/src/Mod/Sketcher/Gui/SketchOrientationDialog.h +++ b/src/Mod/Sketcher/Gui/SketchOrientationDialog.h @@ -46,7 +46,7 @@ protected Q_SLOTS: void onPreview(); private: - Ui_SketchOrientationDialog* ui; + std::unique_ptr ui; }; } diff --git a/src/Mod/Sketcher/Gui/SketchRectangularArrayDialog.cpp b/src/Mod/Sketcher/Gui/SketchRectangularArrayDialog.cpp index cc37c5f59c..fdd23e42df 100644 --- a/src/Mod/Sketcher/Gui/SketchRectangularArrayDialog.cpp +++ b/src/Mod/Sketcher/Gui/SketchRectangularArrayDialog.cpp @@ -53,7 +53,6 @@ SketchRectangularArrayDialog::SketchRectangularArrayDialog(void) SketchRectangularArrayDialog::~SketchRectangularArrayDialog() { - delete ui; } void SketchRectangularArrayDialog::accept() diff --git a/src/Mod/Sketcher/Gui/SketchRectangularArrayDialog.h b/src/Mod/Sketcher/Gui/SketchRectangularArrayDialog.h index 620c7e7796..602fb9fdc6 100644 --- a/src/Mod/Sketcher/Gui/SketchRectangularArrayDialog.h +++ b/src/Mod/Sketcher/Gui/SketchRectangularArrayDialog.h @@ -48,7 +48,7 @@ public: protected: void updateValues(void); private: - Ui_SketchRectangularArrayDialog* ui; + std::unique_ptr ui; }; } diff --git a/src/Mod/Sketcher/Gui/SketcherRegularPolygonDialog.cpp b/src/Mod/Sketcher/Gui/SketcherRegularPolygonDialog.cpp index bf7dc3ef88..91bbe2bd5a 100644 --- a/src/Mod/Sketcher/Gui/SketcherRegularPolygonDialog.cpp +++ b/src/Mod/Sketcher/Gui/SketcherRegularPolygonDialog.cpp @@ -49,7 +49,6 @@ SketcherRegularPolygonDialog::SketcherRegularPolygonDialog(void) SketcherRegularPolygonDialog::~SketcherRegularPolygonDialog() { - delete ui; } void SketcherRegularPolygonDialog::accept() diff --git a/src/Mod/Sketcher/Gui/SketcherRegularPolygonDialog.h b/src/Mod/Sketcher/Gui/SketcherRegularPolygonDialog.h index f450693620..1a21de3945 100644 --- a/src/Mod/Sketcher/Gui/SketcherRegularPolygonDialog.h +++ b/src/Mod/Sketcher/Gui/SketcherRegularPolygonDialog.h @@ -44,7 +44,7 @@ public: protected: void updateValues(void); private: - Ui_SketcherRegularPolygonDialog* ui; + std::unique_ptr ui; }; } diff --git a/src/Mod/Sketcher/Gui/SketcherSettings.cpp b/src/Mod/Sketcher/Gui/SketcherSettings.cpp index e63f8e2b3b..02e41de8fe 100644 --- a/src/Mod/Sketcher/Gui/SketcherSettings.cpp +++ b/src/Mod/Sketcher/Gui/SketcherSettings.cpp @@ -62,7 +62,6 @@ SketcherSettings::SketcherSettings(QWidget* parent) SketcherSettings::~SketcherSettings() { // no need to delete child widgets, Qt does it all for us - delete ui; } void SketcherSettings::saveSettings() @@ -141,7 +140,6 @@ SketcherSettingsDisplay::SketcherSettingsDisplay(QWidget* parent) SketcherSettingsDisplay::~SketcherSettingsDisplay() { // no need to delete child widgets, Qt does it all for us - delete ui; } void SketcherSettingsDisplay::saveSettings() @@ -241,7 +239,6 @@ SketcherSettingsColors::SketcherSettingsColors(QWidget* parent) SketcherSettingsColors::~SketcherSettingsColors() { // no need to delete child widgets, Qt does it all for us - delete ui; } void SketcherSettingsColors::saveSettings() diff --git a/src/Mod/Sketcher/Gui/SketcherSettings.h b/src/Mod/Sketcher/Gui/SketcherSettings.h index 4d94156920..003c0fe0b9 100644 --- a/src/Mod/Sketcher/Gui/SketcherSettings.h +++ b/src/Mod/Sketcher/Gui/SketcherSettings.h @@ -25,6 +25,7 @@ #define SKETCHERGUI_SKETCHERSETTINGS_H #include +#include namespace SketcherGui { class Ui_SketcherSettings; @@ -50,7 +51,7 @@ protected: void changeEvent(QEvent *e); private: - Ui_SketcherSettings* ui; + std::unique_ptr ui; SketcherGeneralWidget* form; }; @@ -76,7 +77,7 @@ private Q_SLOTS: void onBtnTVApplyClicked(bool); private: - Ui_SketcherSettingsDisplay* ui; + std::unique_ptr ui; }; /** @@ -98,7 +99,7 @@ protected: void changeEvent(QEvent *e); private: - Ui_SketcherSettingsColors* ui; + std::unique_ptr ui; }; } // namespace SketcherGui From 603cbdbfd5e819f245c53081b7e471e831b7df57 Mon Sep 17 00:00:00 2001 From: donovaly Date: Fri, 5 Feb 2021 02:13:53 +0100 Subject: [PATCH 161/168] [Part] make pointers to the UI std::unique_ptr Same as PR #4293, just for Part as noted in https://github.com/FreeCAD/FreeCAD/pull/4271#discussion_r554673632 the pointer to the UI should be a unique pointer. This PR does this for all Part dialogs that don't already use a unique_ptr. --- src/Mod/Part/Gui/CrossSections.cpp | 4 +--- src/Mod/Part/Gui/CrossSections.h | 2 +- src/Mod/Part/Gui/DlgBooleanOperation.cpp | 1 - src/Mod/Part/Gui/DlgBooleanOperation.h | 2 +- src/Mod/Part/Gui/DlgExtrusion.cpp | 1 - src/Mod/Part/Gui/DlgExtrusion.h | 2 +- src/Mod/Part/Gui/DlgRevolution.cpp | 5 +---- src/Mod/Part/Gui/DlgRevolution.h | 2 +- src/Mod/Part/Gui/DlgSettingsGeneral.cpp | 12 +++--------- src/Mod/Part/Gui/DlgSettingsGeneral.h | 6 +++--- src/Mod/Part/Gui/Mirroring.cpp | 1 - src/Mod/Part/Gui/Mirroring.h | 2 +- src/Mod/Part/Gui/TaskAttacher.cpp | 5 ++--- src/Mod/Part/Gui/TaskAttacher.h | 2 +- 14 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/Mod/Part/Gui/CrossSections.cpp b/src/Mod/Part/Gui/CrossSections.cpp index 0703dfb275..57e43a6a46 100644 --- a/src/Mod/Part/Gui/CrossSections.cpp +++ b/src/Mod/Part/Gui/CrossSections.cpp @@ -124,9 +124,8 @@ private: } CrossSections::CrossSections(const Base::BoundBox3d& bb, QWidget* parent, Qt::WindowFlags fl) - : QDialog(parent, fl), bbox(bb) + : QDialog(parent, fl), bbox(bb), ui(new Ui_CrossSections) { - ui = new Ui_CrossSections(); ui->setupUi(this); ui->position->setRange(-DBL_MAX, DBL_MAX); ui->position->setUnit(Base::Unit::Length); @@ -151,7 +150,6 @@ CrossSections::CrossSections(const Base::BoundBox3d& bb, QWidget* parent, Qt::Wi CrossSections::~CrossSections() { // no need to delete child widgets, Qt does it all for us - delete ui; if (view) { view->getViewer()->removeViewProvider(vp); } diff --git a/src/Mod/Part/Gui/CrossSections.h b/src/Mod/Part/Gui/CrossSections.h index d73947bea8..089b0bd012 100644 --- a/src/Mod/Part/Gui/CrossSections.h +++ b/src/Mod/Part/Gui/CrossSections.h @@ -71,7 +71,7 @@ private: Plane plane() const; private: - Ui_CrossSections* ui; + std::unique_ptr ui; Base::BoundBox3d bbox; ViewProviderCrossSections* vp; QPointer view; diff --git a/src/Mod/Part/Gui/DlgBooleanOperation.cpp b/src/Mod/Part/Gui/DlgBooleanOperation.cpp index 5a0b478ee0..027a822bb1 100644 --- a/src/Mod/Part/Gui/DlgBooleanOperation.cpp +++ b/src/Mod/Part/Gui/DlgBooleanOperation.cpp @@ -102,7 +102,6 @@ DlgBooleanOperation::DlgBooleanOperation(QWidget* parent) DlgBooleanOperation::~DlgBooleanOperation() { // no need to delete child widgets, Qt does it all for us - delete ui; this->connectNewObject.disconnect(); this->connectModObject.disconnect(); } diff --git a/src/Mod/Part/Gui/DlgBooleanOperation.h b/src/Mod/Part/Gui/DlgBooleanOperation.h index 0a5a624a8c..53c915cce4 100644 --- a/src/Mod/Part/Gui/DlgBooleanOperation.h +++ b/src/Mod/Part/Gui/DlgBooleanOperation.h @@ -63,7 +63,7 @@ private Q_SLOTS: void currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*); private: - Ui_DlgBooleanOperation* ui; + std::unique_ptr ui; Connection connectNewObject; Connection connectModObject; std::list observe; diff --git a/src/Mod/Part/Gui/DlgExtrusion.cpp b/src/Mod/Part/Gui/DlgExtrusion.cpp index 2ebc8bc113..1982aa7b42 100644 --- a/src/Mod/Part/Gui/DlgExtrusion.cpp +++ b/src/Mod/Part/Gui/DlgExtrusion.cpp @@ -139,7 +139,6 @@ DlgExtrusion::~DlgExtrusion() } // no need to delete child widgets, Qt does it all for us - delete ui; } void DlgExtrusion::changeEvent(QEvent *e) diff --git a/src/Mod/Part/Gui/DlgExtrusion.h b/src/Mod/Part/Gui/DlgExtrusion.h index d1db3d860e..a4baae7238 100644 --- a/src/Mod/Part/Gui/DlgExtrusion.h +++ b/src/Mod/Part/Gui/DlgExtrusion.h @@ -91,7 +91,7 @@ private: void autoSolid(); private: - Ui_DlgExtrusion* ui; + std::unique_ptr ui; std::string document, label; class EdgeSelection; EdgeSelection* filter; diff --git a/src/Mod/Part/Gui/DlgRevolution.cpp b/src/Mod/Part/Gui/DlgRevolution.cpp index 86b2b14a73..de27f30624 100644 --- a/src/Mod/Part/Gui/DlgRevolution.cpp +++ b/src/Mod/Part/Gui/DlgRevolution.cpp @@ -101,10 +101,8 @@ public: }; DlgRevolution::DlgRevolution(QWidget* parent, Qt::WindowFlags fl) - : QDialog(parent, fl), filter(0) + : QDialog(parent, fl), filter(0), ui(new Ui_DlgRevolution) { - ui = new Ui_DlgRevolution(); - ui->setupUi(this); ui->xPos->setRange(-DBL_MAX,DBL_MAX); @@ -141,7 +139,6 @@ DlgRevolution::~DlgRevolution() { // no need to delete child widgets, Qt does it all for us Gui::Selection().rmvSelectionGate(); - delete ui; } Base::Vector3d DlgRevolution::getDirection() const diff --git a/src/Mod/Part/Gui/DlgRevolution.h b/src/Mod/Part/Gui/DlgRevolution.h index 2a8bf2bc8b..9151088aef 100644 --- a/src/Mod/Part/Gui/DlgRevolution.h +++ b/src/Mod/Part/Gui/DlgRevolution.h @@ -76,7 +76,7 @@ private: void autoSolid(); private: - Ui_DlgRevolution* ui; + std::unique_ptr ui; class EdgeSelection; EdgeSelection* filter; }; diff --git a/src/Mod/Part/Gui/DlgSettingsGeneral.cpp b/src/Mod/Part/Gui/DlgSettingsGeneral.cpp index 2dee4b9150..fbd6762478 100644 --- a/src/Mod/Part/Gui/DlgSettingsGeneral.cpp +++ b/src/Mod/Part/Gui/DlgSettingsGeneral.cpp @@ -40,9 +40,8 @@ using namespace PartGui; DlgSettingsGeneral::DlgSettingsGeneral(QWidget* parent) - : PreferencePage(parent) + : PreferencePage(parent), ui(new Ui_DlgSettingsGeneral) { - ui = new Ui_DlgSettingsGeneral(); ui->setupUi(this); } @@ -52,7 +51,6 @@ DlgSettingsGeneral::DlgSettingsGeneral(QWidget* parent) DlgSettingsGeneral::~DlgSettingsGeneral() { // no need to delete child widgets, Qt does it all for us - delete ui; } void DlgSettingsGeneral::saveSettings() @@ -87,9 +85,8 @@ void DlgSettingsGeneral::changeEvent(QEvent *e) // ---------------------------------------------------------------------------- DlgImportExportIges::DlgImportExportIges(QWidget* parent) - : PreferencePage(parent) + : PreferencePage(parent), ui(new Ui_DlgImportExportIges) { - ui = new Ui_DlgImportExportIges(); ui->setupUi(this); ui->lineEditProduct->setReadOnly(true); @@ -113,7 +110,6 @@ DlgImportExportIges::DlgImportExportIges(QWidget* parent) DlgImportExportIges::~DlgImportExportIges() { // no need to delete child widgets, Qt does it all for us - delete ui; } void DlgImportExportIges::saveSettings() @@ -193,9 +189,8 @@ void DlgImportExportIges::changeEvent(QEvent *e) // ---------------------------------------------------------------------------- DlgImportExportStep::DlgImportExportStep(QWidget* parent) - : PreferencePage(parent) + : PreferencePage(parent), ui(new Ui_DlgImportExportStep) { - ui = new Ui_DlgImportExportStep(); ui->setupUi(this); ui->comboBoxSchema->setItemData(0, QByteArray("AP203")); @@ -229,7 +224,6 @@ DlgImportExportStep::DlgImportExportStep(QWidget* parent) DlgImportExportStep::~DlgImportExportStep() { // no need to delete child widgets, Qt does it all for us - delete ui; } void DlgImportExportStep::saveSettings() diff --git a/src/Mod/Part/Gui/DlgSettingsGeneral.h b/src/Mod/Part/Gui/DlgSettingsGeneral.h index fc995cce3f..0054d24f16 100644 --- a/src/Mod/Part/Gui/DlgSettingsGeneral.h +++ b/src/Mod/Part/Gui/DlgSettingsGeneral.h @@ -45,7 +45,7 @@ protected: void changeEvent(QEvent *e); private: - Ui_DlgSettingsGeneral* ui; + std::unique_ptr ui; }; class Ui_DlgImportExportIges; @@ -63,7 +63,7 @@ protected: void changeEvent(QEvent *e); private: - Ui_DlgImportExportIges* ui; + std::unique_ptr ui; QButtonGroup* bg; }; @@ -82,7 +82,7 @@ protected: void changeEvent(QEvent *e); private: - Ui_DlgImportExportStep* ui; + std::unique_ptr ui; }; } // namespace Gui diff --git a/src/Mod/Part/Gui/Mirroring.cpp b/src/Mod/Part/Gui/Mirroring.cpp index 94beb847b2..14f413f199 100644 --- a/src/Mod/Part/Gui/Mirroring.cpp +++ b/src/Mod/Part/Gui/Mirroring.cpp @@ -77,7 +77,6 @@ Mirroring::Mirroring(QWidget* parent) Mirroring::~Mirroring() { // no need to delete child widgets, Qt does it all for us - delete ui; } void Mirroring::changeEvent(QEvent *e) diff --git a/src/Mod/Part/Gui/Mirroring.h b/src/Mod/Part/Gui/Mirroring.h index 2f8cad91e9..768704b450 100644 --- a/src/Mod/Part/Gui/Mirroring.h +++ b/src/Mod/Part/Gui/Mirroring.h @@ -52,7 +52,7 @@ private: private: QString document; - Ui_Mirroring* ui; + std::unique_ptr ui; }; class TaskMirroring : public Gui::TaskView::TaskDialog diff --git a/src/Mod/Part/Gui/TaskAttacher.cpp b/src/Mod/Part/Gui/TaskAttacher.cpp index 682443e34d..393623b895 100644 --- a/src/Mod/Part/Gui/TaskAttacher.cpp +++ b/src/Mod/Part/Gui/TaskAttacher.cpp @@ -114,7 +114,8 @@ TaskAttacher::TaskAttacher(Gui::ViewProviderDocumentObject *ViewProvider, QWidge : TaskBox(Gui::BitmapFactory().pixmap(picture.toLatin1()), text, true, parent), SelectionObserver(ViewProvider), ViewProvider(ViewProvider), - visibilityFunc(visFunc) + visibilityFunc(visFunc), + ui(new Ui_TaskAttacher) { //check if we are attachable if (!ViewProvider->getObject()->hasExtension(Part::AttachExtension::getExtensionClassTypeId())) @@ -122,7 +123,6 @@ TaskAttacher::TaskAttacher(Gui::ViewProviderDocumentObject *ViewProvider, QWidge // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskAttacher(); ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); @@ -235,7 +235,6 @@ TaskAttacher::~TaskAttacher() connectDelObject.disconnect(); connectDelDocument.disconnect(); - delete ui; } void TaskAttacher::objectDeleted(const Gui::ViewProviderDocumentObject& view) diff --git a/src/Mod/Part/Gui/TaskAttacher.h b/src/Mod/Part/Gui/TaskAttacher.h index ee1a4a3abc..bdbaa9ad90 100644 --- a/src/Mod/Part/Gui/TaskAttacher.h +++ b/src/Mod/Part/Gui/TaskAttacher.h @@ -130,7 +130,7 @@ protected: private: QWidget* proxy; - Ui_TaskAttacher* ui; + std::unique_ptr ui; VisibilityFunction visibilityFunc; // TODO fix documentation here (2015-11-10, Fat-Zer) From 99c686f0614b8ceaa6acabb9048860ffbf15fe6d Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 5 Feb 2021 17:26:47 +0100 Subject: [PATCH 162/168] Part: [skip ci] fix -Wreorder --- src/Mod/Part/Gui/CrossSections.cpp | 4 +++- src/Mod/Part/Gui/DlgRevolution.cpp | 4 +++- src/Mod/Part/Gui/TaskAttacher.cpp | 10 +++++----- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Mod/Part/Gui/CrossSections.cpp b/src/Mod/Part/Gui/CrossSections.cpp index 57e43a6a46..ab9adb6631 100644 --- a/src/Mod/Part/Gui/CrossSections.cpp +++ b/src/Mod/Part/Gui/CrossSections.cpp @@ -124,7 +124,9 @@ private: } CrossSections::CrossSections(const Base::BoundBox3d& bb, QWidget* parent, Qt::WindowFlags fl) - : QDialog(parent, fl), bbox(bb), ui(new Ui_CrossSections) + : QDialog(parent, fl) + , ui(new Ui_CrossSections) + , bbox(bb) { ui->setupUi(this); ui->position->setRange(-DBL_MAX, DBL_MAX); diff --git a/src/Mod/Part/Gui/DlgRevolution.cpp b/src/Mod/Part/Gui/DlgRevolution.cpp index de27f30624..a30ac5a1d3 100644 --- a/src/Mod/Part/Gui/DlgRevolution.cpp +++ b/src/Mod/Part/Gui/DlgRevolution.cpp @@ -101,7 +101,9 @@ public: }; DlgRevolution::DlgRevolution(QWidget* parent, Qt::WindowFlags fl) - : QDialog(parent, fl), filter(0), ui(new Ui_DlgRevolution) + : QDialog(parent, fl) + , ui(new Ui_DlgRevolution) + , filter(0) { ui->setupUi(this); diff --git a/src/Mod/Part/Gui/TaskAttacher.cpp b/src/Mod/Part/Gui/TaskAttacher.cpp index 393623b895..fcb46fddef 100644 --- a/src/Mod/Part/Gui/TaskAttacher.cpp +++ b/src/Mod/Part/Gui/TaskAttacher.cpp @@ -111,11 +111,11 @@ void TaskAttacher::makeRefStrings(std::vector& refstrings, std::vector< TaskAttacher::TaskAttacher(Gui::ViewProviderDocumentObject *ViewProvider, QWidget *parent, QString picture, QString text, TaskAttacher::VisibilityFunction visFunc) - : TaskBox(Gui::BitmapFactory().pixmap(picture.toLatin1()), text, true, parent), - SelectionObserver(ViewProvider), - ViewProvider(ViewProvider), - visibilityFunc(visFunc), - ui(new Ui_TaskAttacher) + : TaskBox(Gui::BitmapFactory().pixmap(picture.toLatin1()), text, true, parent) + , SelectionObserver(ViewProvider) + , ViewProvider(ViewProvider) + , ui(new Ui_TaskAttacher) + , visibilityFunc(visFunc) { //check if we are attachable if (!ViewProvider->getObject()->hasExtension(Part::AttachExtension::getExtensionClassTypeId())) From 2ac84872ecd8f820b644cbe442648ccc7ff270bf Mon Sep 17 00:00:00 2001 From: donovaly Date: Fri, 5 Feb 2021 02:45:37 +0100 Subject: [PATCH 163/168] [PD] make pointers to the UI std::unique_ptr Same as PR #4293, just for PartDesign as noted in https://github.com/FreeCAD/FreeCAD/pull/4271#discussion_r554673632 the pointer to the UI should be a unique pointer. This PR does this for all PartDesign dialogs that don't already use a unique_ptr. --- src/Mod/PartDesign/Gui/TaskBooleanParameters.cpp | 6 +++--- src/Mod/PartDesign/Gui/TaskBooleanParameters.h | 2 +- src/Mod/PartDesign/Gui/TaskChamferParameters.cpp | 4 +--- src/Mod/PartDesign/Gui/TaskChamferParameters.h | 2 +- src/Mod/PartDesign/Gui/TaskDraftParameters.cpp | 4 +--- src/Mod/PartDesign/Gui/TaskDraftParameters.h | 2 +- src/Mod/PartDesign/Gui/TaskFeaturePick.h | 2 +- src/Mod/PartDesign/Gui/TaskFilletParameters.cpp | 4 +--- src/Mod/PartDesign/Gui/TaskFilletParameters.h | 2 +- src/Mod/PartDesign/Gui/TaskHelixParameters.cpp | 2 +- src/Mod/PartDesign/Gui/TaskHelixParameters.h | 2 +- src/Mod/PartDesign/Gui/TaskHoleParameters.cpp | 5 ++--- src/Mod/PartDesign/Gui/TaskHoleParameters.h | 4 ++-- .../Gui/TaskLinearPatternParameters.cpp | 8 +++----- .../PartDesign/Gui/TaskLinearPatternParameters.h | 2 +- src/Mod/PartDesign/Gui/TaskLoftParameters.cpp | 5 ++--- src/Mod/PartDesign/Gui/TaskLoftParameters.h | 2 +- .../PartDesign/Gui/TaskMirroredParameters.cpp | 8 +++----- src/Mod/PartDesign/Gui/TaskMirroredParameters.h | 2 +- .../Gui/TaskMultiTransformParameters.cpp | 7 ++++--- .../Gui/TaskMultiTransformParameters.h | 2 +- src/Mod/PartDesign/Gui/TaskPadParameters.cpp | 5 ++--- src/Mod/PartDesign/Gui/TaskPadParameters.h | 2 +- src/Mod/PartDesign/Gui/TaskPipeParameters.cpp | 16 +++++++--------- src/Mod/PartDesign/Gui/TaskPipeParameters.h | 6 +++--- src/Mod/PartDesign/Gui/TaskPocketParameters.cpp | 5 ++--- src/Mod/PartDesign/Gui/TaskPocketParameters.h | 2 +- .../Gui/TaskPolarPatternParameters.cpp | 8 +++----- .../PartDesign/Gui/TaskPolarPatternParameters.h | 2 +- .../PartDesign/Gui/TaskRevolutionParameters.cpp | 6 ++---- .../PartDesign/Gui/TaskRevolutionParameters.h | 2 +- src/Mod/PartDesign/Gui/TaskScaledParameters.cpp | 8 +++----- src/Mod/PartDesign/Gui/TaskScaledParameters.h | 2 +- src/Mod/PartDesign/Gui/TaskShapeBinder.cpp | 3 +-- src/Mod/PartDesign/Gui/TaskShapeBinder.h | 2 +- .../PartDesign/Gui/TaskThicknessParameters.cpp | 4 +--- src/Mod/PartDesign/Gui/TaskThicknessParameters.h | 2 +- .../PartDesign/Gui/TaskTransformedMessages.cpp | 7 +++---- src/Mod/PartDesign/Gui/TaskTransformedMessages.h | 2 +- .../PartDesign/Gui/TaskTransformedParameters.cpp | 14 ++++++-------- 40 files changed, 74 insertions(+), 101 deletions(-) diff --git a/src/Mod/PartDesign/Gui/TaskBooleanParameters.cpp b/src/Mod/PartDesign/Gui/TaskBooleanParameters.cpp index 056aa99434..c72e241686 100644 --- a/src/Mod/PartDesign/Gui/TaskBooleanParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskBooleanParameters.cpp @@ -53,13 +53,14 @@ using namespace Gui; /* TRANSLATOR PartDesignGui::TaskBooleanParameters */ TaskBooleanParameters::TaskBooleanParameters(ViewProviderBoolean *BooleanView,QWidget *parent) - : TaskBox(Gui::BitmapFactory().pixmap("PartDesign_Boolean"),tr("Boolean parameters"),true, parent),BooleanView(BooleanView) + : TaskBox(Gui::BitmapFactory().pixmap("PartDesign_Boolean"), tr("Boolean parameters"), true, parent) + , ui(new Ui_TaskBooleanParameters) + , BooleanView(BooleanView) { selectionMode = none; // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskBooleanParameters(); ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); @@ -289,7 +290,6 @@ void TaskBooleanParameters::onBodyDeleted(void) TaskBooleanParameters::~TaskBooleanParameters() { - delete ui; } void TaskBooleanParameters::changeEvent(QEvent *e) diff --git a/src/Mod/PartDesign/Gui/TaskBooleanParameters.h b/src/Mod/PartDesign/Gui/TaskBooleanParameters.h index 6020dd850e..6f9739ce55 100644 --- a/src/Mod/PartDesign/Gui/TaskBooleanParameters.h +++ b/src/Mod/PartDesign/Gui/TaskBooleanParameters.h @@ -70,7 +70,7 @@ protected: private: QWidget* proxy; - Ui_TaskBooleanParameters* ui; + std::unique_ptr ui; ViewProviderBoolean *BooleanView; enum selectionModes { none, bodyAdd, bodyRemove }; diff --git a/src/Mod/PartDesign/Gui/TaskChamferParameters.cpp b/src/Mod/PartDesign/Gui/TaskChamferParameters.cpp index 31e84795c8..75745dc5fb 100644 --- a/src/Mod/PartDesign/Gui/TaskChamferParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskChamferParameters.cpp @@ -57,10 +57,10 @@ using namespace Gui; TaskChamferParameters::TaskChamferParameters(ViewProviderDressUp *DressUpView, QWidget *parent) : TaskDressUpParameters(DressUpView, true, true, parent) + , ui(new Ui_TaskChamferParameters) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskChamferParameters(); ui->setupUi(proxy); this->groupLayout()->addWidget(proxy); @@ -311,8 +311,6 @@ TaskChamferParameters::~TaskChamferParameters() { Gui::Selection().clearSelection(); Gui::Selection().rmvSelectionGate(); - - delete ui; } bool TaskChamferParameters::event(QEvent *e) diff --git a/src/Mod/PartDesign/Gui/TaskChamferParameters.h b/src/Mod/PartDesign/Gui/TaskChamferParameters.h index c33c47c14f..436c879c0c 100644 --- a/src/Mod/PartDesign/Gui/TaskChamferParameters.h +++ b/src/Mod/PartDesign/Gui/TaskChamferParameters.h @@ -67,7 +67,7 @@ protected: private: void setUpUI(PartDesign::Chamfer* pcChamfer); - Ui_TaskChamferParameters* ui; + std::unique_ptr ui; }; /// simulation dialog for the TaskView diff --git a/src/Mod/PartDesign/Gui/TaskDraftParameters.cpp b/src/Mod/PartDesign/Gui/TaskDraftParameters.cpp index 3429c99be6..3a9298fc13 100644 --- a/src/Mod/PartDesign/Gui/TaskDraftParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskDraftParameters.cpp @@ -56,10 +56,10 @@ using namespace Gui; TaskDraftParameters::TaskDraftParameters(ViewProviderDressUp *DressUpView, QWidget *parent) : TaskDressUpParameters(DressUpView, false, true, parent) + , ui(new Ui_TaskDraftParameters) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskDraftParameters(); ui->setupUi(proxy); this->groupLayout()->addWidget(proxy); @@ -319,8 +319,6 @@ TaskDraftParameters::~TaskDraftParameters() { Gui::Selection().clearSelection(); Gui::Selection().rmvSelectionGate(); - - delete ui; } bool TaskDraftParameters::event(QEvent *e) diff --git a/src/Mod/PartDesign/Gui/TaskDraftParameters.h b/src/Mod/PartDesign/Gui/TaskDraftParameters.h index dcff0bbdaf..390e9c278c 100644 --- a/src/Mod/PartDesign/Gui/TaskDraftParameters.h +++ b/src/Mod/PartDesign/Gui/TaskDraftParameters.h @@ -60,7 +60,7 @@ protected: virtual void onSelectionChanged(const Gui::SelectionChanges& msg); private: - Ui_TaskDraftParameters* ui; + std::unique_ptr ui; }; /// simulation dialog for the TaskView diff --git a/src/Mod/PartDesign/Gui/TaskFeaturePick.h b/src/Mod/PartDesign/Gui/TaskFeaturePick.h index fa8ff1a1a9..3f1ed083f6 100644 --- a/src/Mod/PartDesign/Gui/TaskFeaturePick.h +++ b/src/Mod/PartDesign/Gui/TaskFeaturePick.h @@ -84,7 +84,7 @@ protected: virtual void slotDeleteDocument(const Gui::Document& Doc); private: - Ui_TaskFeaturePick* ui; + std::unique_ptr ui; QWidget* proxy; std::vector origins; bool doSelection; diff --git a/src/Mod/PartDesign/Gui/TaskFilletParameters.cpp b/src/Mod/PartDesign/Gui/TaskFilletParameters.cpp index 1749edbb8d..0e8b8e424b 100644 --- a/src/Mod/PartDesign/Gui/TaskFilletParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskFilletParameters.cpp @@ -54,10 +54,10 @@ using namespace Gui; TaskFilletParameters::TaskFilletParameters(ViewProviderDressUp *DressUpView, QWidget *parent) : TaskDressUpParameters(DressUpView, true, true, parent) + , ui(new Ui_TaskFilletParameters) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskFilletParameters(); ui->setupUi(proxy); this->groupLayout()->addWidget(proxy); @@ -214,8 +214,6 @@ TaskFilletParameters::~TaskFilletParameters() { Gui::Selection().clearSelection(); Gui::Selection().rmvSelectionGate(); - - delete ui; } bool TaskFilletParameters::event(QEvent *e) diff --git a/src/Mod/PartDesign/Gui/TaskFilletParameters.h b/src/Mod/PartDesign/Gui/TaskFilletParameters.h index ea4fb684e6..e4eeb57ab0 100644 --- a/src/Mod/PartDesign/Gui/TaskFilletParameters.h +++ b/src/Mod/PartDesign/Gui/TaskFilletParameters.h @@ -53,7 +53,7 @@ protected: virtual void onSelectionChanged(const Gui::SelectionChanges& msg); private: - Ui_TaskFilletParameters* ui; + std::unique_ptr ui; }; /// simulation dialog for the TaskView diff --git a/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp b/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp index 2fa03e580c..ca17897522 100644 --- a/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp @@ -61,7 +61,7 @@ using namespace Gui; /* TRANSLATOR PartDesignGui::TaskHelixParameters */ TaskHelixParameters::TaskHelixParameters(PartDesignGui::ViewProviderHelix *HelixView, QWidget *parent) - : TaskSketchBasedParameters(HelixView, parent, "PartDesign_Additive_Helix",tr("Helix parameters")), + : TaskSketchBasedParameters(HelixView, parent, "PartDesign_Additive_Helix", tr("Helix parameters")), ui (new Ui_TaskHelixParameters) { // we need a separate container widget to add all controls to diff --git a/src/Mod/PartDesign/Gui/TaskHelixParameters.h b/src/Mod/PartDesign/Gui/TaskHelixParameters.h index 442c8376e6..aaa1c7ed7b 100644 --- a/src/Mod/PartDesign/Gui/TaskHelixParameters.h +++ b/src/Mod/PartDesign/Gui/TaskHelixParameters.h @@ -101,7 +101,7 @@ private: private: QWidget* proxy; - Ui_TaskHelixParameters* ui; + std::unique_ptr ui; /** * @brief axesInList is the list of links corresponding to axis combo; must diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp b/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp index 6dc77ad8ef..dbd5a866bd 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp @@ -57,13 +57,13 @@ namespace bp = boost::placeholders; #endif TaskHoleParameters::TaskHoleParameters(ViewProviderHole *HoleView, QWidget *parent) - : TaskSketchBasedParameters(HoleView, parent, "PartDesign_Hole",tr("Hole parameters")) + : TaskSketchBasedParameters(HoleView, parent, "PartDesign_Hole", tr("Hole parameters")) , observer(new Observer(this, static_cast(vp->getObject()))) , isApplying(false) + , ui(new Ui_TaskHoleParameters) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskHoleParameters(); ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); @@ -203,7 +203,6 @@ TaskHoleParameters::TaskHoleParameters(ViewProviderHole *HoleView, QWidget *pare TaskHoleParameters::~TaskHoleParameters() { - delete ui; } void TaskHoleParameters::threadedChanged() diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.h b/src/Mod/PartDesign/Gui/TaskHoleParameters.h index 2ec266cac8..dc366dc55e 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.h +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.h @@ -130,9 +130,9 @@ private: Connection connectPropChanged; std::unique_ptr observer; - QWidget* proxy; - Ui_TaskHoleParameters* ui; bool isApplying; + QWidget* proxy; + std::unique_ptr ui; }; /// simulation dialog for the TaskView diff --git a/src/Mod/PartDesign/Gui/TaskLinearPatternParameters.cpp b/src/Mod/PartDesign/Gui/TaskLinearPatternParameters.cpp index a1fa891a12..2be4b8cdc6 100644 --- a/src/Mod/PartDesign/Gui/TaskLinearPatternParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskLinearPatternParameters.cpp @@ -63,11 +63,11 @@ using namespace Gui; /* TRANSLATOR PartDesignGui::TaskLinearPatternParameters */ TaskLinearPatternParameters::TaskLinearPatternParameters(ViewProviderTransformed *TransformedView,QWidget *parent) - : TaskTransformedParameters(TransformedView, parent) + : TaskTransformedParameters(TransformedView, parent) + , ui(new Ui_TaskLinearPatternParameters) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskLinearPatternParameters(); ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); @@ -83,10 +83,9 @@ TaskLinearPatternParameters::TaskLinearPatternParameters(ViewProviderTransformed } TaskLinearPatternParameters::TaskLinearPatternParameters(TaskMultiTransformParameters *parentTask, QLayout *layout) - : TaskTransformedParameters(parentTask) + : TaskTransformedParameters(parentTask), ui(new Ui_TaskLinearPatternParameters) { proxy = new QWidget(parentTask); - ui = new Ui_TaskLinearPatternParameters(); ui->setupUi(proxy); connect(ui->buttonOK, SIGNAL(pressed()), parentTask, SLOT(onSubTaskButtonOK())); @@ -415,7 +414,6 @@ TaskLinearPatternParameters::~TaskLinearPatternParameters() Base::Console().Error ("%s\n", ex.what () ); } - delete ui; if (proxy) delete proxy; } diff --git a/src/Mod/PartDesign/Gui/TaskLinearPatternParameters.h b/src/Mod/PartDesign/Gui/TaskLinearPatternParameters.h index b492c965cd..d48dbeb67a 100644 --- a/src/Mod/PartDesign/Gui/TaskLinearPatternParameters.h +++ b/src/Mod/PartDesign/Gui/TaskLinearPatternParameters.h @@ -85,7 +85,7 @@ private: void kickUpdateViewTimer() const; private: - Ui_TaskLinearPatternParameters* ui; + std::unique_ptr ui; QTimer* updateViewTimer; ComboLinks dirLinks; diff --git a/src/Mod/PartDesign/Gui/TaskLoftParameters.cpp b/src/Mod/PartDesign/Gui/TaskLoftParameters.cpp index 2106604e87..33040229c1 100644 --- a/src/Mod/PartDesign/Gui/TaskLoftParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskLoftParameters.cpp @@ -56,11 +56,11 @@ using namespace Gui; /* TRANSLATOR PartDesignGui::TaskLoftParameters */ TaskLoftParameters::TaskLoftParameters(ViewProviderLoft *LoftView,bool /*newObj*/, QWidget *parent) - : TaskSketchBasedParameters(LoftView, parent, "PartDesign_Additive_Loft",tr("Loft parameters")) + : TaskSketchBasedParameters(LoftView, parent, "PartDesign_Additive_Loft", tr("Loft parameters")) + , ui(new Ui_TaskLoftParameters) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskLoftParameters(); ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); @@ -134,7 +134,6 @@ TaskLoftParameters::TaskLoftParameters(ViewProviderLoft *LoftView,bool /*newObj* TaskLoftParameters::~TaskLoftParameters() { - delete ui; } void TaskLoftParameters::updateUI(int index) diff --git a/src/Mod/PartDesign/Gui/TaskLoftParameters.h b/src/Mod/PartDesign/Gui/TaskLoftParameters.h index 4e5d276c49..b99a771edf 100644 --- a/src/Mod/PartDesign/Gui/TaskLoftParameters.h +++ b/src/Mod/PartDesign/Gui/TaskLoftParameters.h @@ -75,7 +75,7 @@ private: private: QWidget* proxy; - Ui_TaskLoftParameters* ui; + std::unique_ptr ui; enum selectionModes { none, refAdd, refRemove, refProfile }; selectionModes selectionMode = none; diff --git a/src/Mod/PartDesign/Gui/TaskMirroredParameters.cpp b/src/Mod/PartDesign/Gui/TaskMirroredParameters.cpp index 74533f9efa..4f1bfce4ef 100644 --- a/src/Mod/PartDesign/Gui/TaskMirroredParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskMirroredParameters.cpp @@ -60,11 +60,11 @@ using namespace Gui; /* TRANSLATOR PartDesignGui::TaskMirroredParameters */ TaskMirroredParameters::TaskMirroredParameters(ViewProviderTransformed *TransformedView, QWidget *parent) - : TaskTransformedParameters(TransformedView, parent) + : TaskTransformedParameters(TransformedView, parent) + , ui(new Ui_TaskMirroredParameters) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskMirroredParameters(); ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); @@ -80,10 +80,9 @@ TaskMirroredParameters::TaskMirroredParameters(ViewProviderTransformed *Transfor } TaskMirroredParameters::TaskMirroredParameters(TaskMultiTransformParameters *parentTask, QLayout *layout) - : TaskTransformedParameters(parentTask) + : TaskTransformedParameters(parentTask), ui(new Ui_TaskMirroredParameters) { proxy = new QWidget(parentTask); - ui = new Ui_TaskMirroredParameters(); ui->setupUi(proxy); connect(ui->buttonOK, SIGNAL(pressed()), parentTask, SLOT(onSubTaskButtonOK())); @@ -318,7 +317,6 @@ TaskMirroredParameters::~TaskMirroredParameters() Base::Console().Error ("%s\n", ex.what () ); } - delete ui; if (proxy) delete proxy; } diff --git a/src/Mod/PartDesign/Gui/TaskMirroredParameters.h b/src/Mod/PartDesign/Gui/TaskMirroredParameters.h index 906c0d4516..297b572391 100644 --- a/src/Mod/PartDesign/Gui/TaskMirroredParameters.h +++ b/src/Mod/PartDesign/Gui/TaskMirroredParameters.h @@ -79,7 +79,7 @@ private: ComboLinks planeLinks; private: - Ui_TaskMirroredParameters* ui; + std::unique_ptr ui; }; diff --git a/src/Mod/PartDesign/Gui/TaskMultiTransformParameters.cpp b/src/Mod/PartDesign/Gui/TaskMultiTransformParameters.cpp index 48ff9329f9..a5f4616615 100644 --- a/src/Mod/PartDesign/Gui/TaskMultiTransformParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskMultiTransformParameters.cpp @@ -61,11 +61,13 @@ using namespace Gui; /* TRANSLATOR PartDesignGui::TaskMultiTransformParameters */ TaskMultiTransformParameters::TaskMultiTransformParameters(ViewProviderTransformed *TransformedView,QWidget *parent) - : TaskTransformedParameters(TransformedView, parent), subTask(nullptr), subFeature(nullptr) + : TaskTransformedParameters(TransformedView, parent) + , ui(new Ui_TaskMultiTransformParameters) + , subTask(nullptr) + , subFeature(nullptr) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskMultiTransformParameters(); ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); this->groupLayout()->addWidget(proxy); @@ -484,7 +486,6 @@ void TaskMultiTransformParameters::apply() TaskMultiTransformParameters::~TaskMultiTransformParameters() { closeSubTask(); - delete ui; if (proxy) delete proxy; } diff --git a/src/Mod/PartDesign/Gui/TaskMultiTransformParameters.h b/src/Mod/PartDesign/Gui/TaskMultiTransformParameters.h index f309247fb1..57188b73d6 100644 --- a/src/Mod/PartDesign/Gui/TaskMultiTransformParameters.h +++ b/src/Mod/PartDesign/Gui/TaskMultiTransformParameters.h @@ -100,7 +100,7 @@ private: void finishAdd(std::string &newFeatName); private: - Ui_TaskMultiTransformParameters* ui; + std::unique_ptr ui; /// The subTask and subFeature currently active in the UI TaskTransformedParameters* subTask; PartDesign::Transformed* subFeature; diff --git a/src/Mod/PartDesign/Gui/TaskPadParameters.cpp b/src/Mod/PartDesign/Gui/TaskPadParameters.cpp index 539c33fc85..2604b60aad 100644 --- a/src/Mod/PartDesign/Gui/TaskPadParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPadParameters.cpp @@ -54,11 +54,11 @@ using namespace Gui; /* TRANSLATOR PartDesignGui::TaskPadParameters */ TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView, QWidget *parent, bool newObj) - : TaskSketchBasedParameters(PadView, parent, "PartDesign_Pad",tr("Pad parameters")) + : TaskSketchBasedParameters(PadView, parent, "PartDesign_Pad", tr("Pad parameters")) + , ui(new Ui_TaskPadParameters) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskPadParameters(); ui->setupUi(proxy); #if QT_VERSION >= 0x040700 ui->lineFaceName->setPlaceholderText(tr("No face selected")); @@ -487,7 +487,6 @@ QString TaskPadParameters::getFaceName(void) const TaskPadParameters::~TaskPadParameters() { - delete ui; } void TaskPadParameters::changeEvent(QEvent *e) diff --git a/src/Mod/PartDesign/Gui/TaskPadParameters.h b/src/Mod/PartDesign/Gui/TaskPadParameters.h index 6a0f64f796..17172344ed 100644 --- a/src/Mod/PartDesign/Gui/TaskPadParameters.h +++ b/src/Mod/PartDesign/Gui/TaskPadParameters.h @@ -90,7 +90,7 @@ private: private: QWidget* proxy; - Ui_TaskPadParameters* ui; + std::unique_ptr ui; }; /// simulation dialog for the TaskView diff --git a/src/Mod/PartDesign/Gui/TaskPipeParameters.cpp b/src/Mod/PartDesign/Gui/TaskPipeParameters.cpp index 62312c1d95..bfea0fd500 100644 --- a/src/Mod/PartDesign/Gui/TaskPipeParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPipeParameters.cpp @@ -72,11 +72,11 @@ using namespace Gui; //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TaskPipeParameters::TaskPipeParameters(ViewProviderPipe *PipeView, bool /*newObj*/, QWidget *parent) - : TaskSketchBasedParameters(PipeView, parent, "PartDesign_Additive_Pipe",tr("Pipe parameters")) + : TaskSketchBasedParameters(PipeView, parent, "PartDesign_Additive_Pipe", tr("Pipe parameters")) + , ui(new Ui_TaskPipeParameters) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskPipeParameters(); ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); @@ -163,8 +163,6 @@ TaskPipeParameters::~TaskPipeParameters() // getDocument() may raise an exception e.ReportException(); } - - delete ui; } void TaskPipeParameters::updateUI() @@ -441,11 +439,11 @@ void TaskPipeParameters::exitSelectionMode() { //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TaskPipeOrientation::TaskPipeOrientation(ViewProviderPipe* PipeView, bool /*newObj*/, QWidget* parent) - : TaskSketchBasedParameters(PipeView, parent, "PartDesign_Additive_Pipe", tr("Section orientation")) { - + : TaskSketchBasedParameters(PipeView, parent, "PartDesign_Additive_Pipe", tr("Section orientation")), + ui(new Ui_TaskPipeOrientation) +{ // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskPipeOrientation(); ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); @@ -768,11 +766,11 @@ void TaskPipeOrientation::updateUI(int idx) { // Task Scaling //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TaskPipeScaling::TaskPipeScaling(ViewProviderPipe* PipeView, bool /*newObj*/, QWidget* parent) - : TaskSketchBasedParameters(PipeView, parent, "PartDesign_Additive_Pipe", tr("Section transformation")) + : TaskSketchBasedParameters(PipeView, parent, "PartDesign_Additive_Pipe", tr("Section transformation")), + ui(new Ui_TaskPipeScaling) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskPipeScaling(); ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); diff --git a/src/Mod/PartDesign/Gui/TaskPipeParameters.h b/src/Mod/PartDesign/Gui/TaskPipeParameters.h index cd4ad127b2..5d1f95b0ed 100644 --- a/src/Mod/PartDesign/Gui/TaskPipeParameters.h +++ b/src/Mod/PartDesign/Gui/TaskPipeParameters.h @@ -84,7 +84,7 @@ private: private: QWidget* proxy; - Ui_TaskPipeParameters* ui; + std::unique_ptr ui; }; class TaskPipeOrientation : public TaskSketchBasedParameters @@ -123,7 +123,7 @@ private: private: QWidget* proxy; - Ui_TaskPipeOrientation* ui; + std::unique_ptr ui; }; @@ -157,7 +157,7 @@ private: private: QWidget* proxy; - Ui_TaskPipeScaling* ui; + std::unique_ptr ui; }; diff --git a/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp b/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp index a163f4c048..6f491c5d15 100644 --- a/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp @@ -53,12 +53,12 @@ using namespace Gui; /* TRANSLATOR PartDesignGui::TaskPocketParameters */ TaskPocketParameters::TaskPocketParameters(ViewProviderPocket *PocketView,QWidget *parent, bool newObj) - : TaskSketchBasedParameters(PocketView, parent, "PartDesign_Pocket",tr("Pocket parameters")) + : TaskSketchBasedParameters(PocketView, parent, "PartDesign_Pocket", tr("Pocket parameters")) + , ui(new Ui_TaskPocketParameters) , oldLength(0) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskPocketParameters(); ui->setupUi(proxy); #if QT_VERSION >= 0x040700 ui->lineFaceName->setPlaceholderText(tr("No face selected")); @@ -422,7 +422,6 @@ QString TaskPocketParameters::getFaceName(void) const TaskPocketParameters::~TaskPocketParameters() { - delete ui; } void TaskPocketParameters::changeEvent(QEvent *e) diff --git a/src/Mod/PartDesign/Gui/TaskPocketParameters.h b/src/Mod/PartDesign/Gui/TaskPocketParameters.h index 1925b5b69f..397b545d16 100644 --- a/src/Mod/PartDesign/Gui/TaskPocketParameters.h +++ b/src/Mod/PartDesign/Gui/TaskPocketParameters.h @@ -82,7 +82,7 @@ private: private: QWidget* proxy; - Ui_TaskPocketParameters* ui; + std::unique_ptr ui; double oldLength; }; diff --git a/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.cpp b/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.cpp index 1812549652..2e4272d389 100644 --- a/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.cpp @@ -61,11 +61,11 @@ using namespace Gui; /* TRANSLATOR PartDesignGui::TaskPolarPatternParameters */ TaskPolarPatternParameters::TaskPolarPatternParameters(ViewProviderTransformed *TransformedView,QWidget *parent) - : TaskTransformedParameters(TransformedView, parent) + : TaskTransformedParameters(TransformedView, parent) + , ui(new Ui_TaskPolarPatternParameters) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskPolarPatternParameters(); ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); @@ -81,10 +81,9 @@ TaskPolarPatternParameters::TaskPolarPatternParameters(ViewProviderTransformed * } TaskPolarPatternParameters::TaskPolarPatternParameters(TaskMultiTransformParameters *parentTask, QLayout *layout) - : TaskTransformedParameters(parentTask) + : TaskTransformedParameters(parentTask), ui(new Ui_TaskPolarPatternParameters) { proxy = new QWidget(parentTask); - ui = new Ui_TaskPolarPatternParameters(); ui->setupUi(proxy); connect(ui->buttonOK, SIGNAL(pressed()), parentTask, SLOT(onSubTaskButtonOK())); @@ -406,7 +405,6 @@ TaskPolarPatternParameters::~TaskPolarPatternParameters() Base::Console().Error ("%s\n", ex.what () ); } - delete ui; if (proxy) delete proxy; } diff --git a/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.h b/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.h index 3cbb777a7b..d9864cee4a 100644 --- a/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.h +++ b/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.h @@ -87,7 +87,7 @@ private: void kickUpdateViewTimer() const; private: - Ui_TaskPolarPatternParameters* ui; + std::unique_ptr ui; QTimer* updateViewTimer; ComboLinks axesLinks; diff --git a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp index bc1326b71b..7da3ea44a4 100644 --- a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp @@ -58,11 +58,11 @@ using namespace Gui; /* TRANSLATOR PartDesignGui::TaskRevolutionParameters */ TaskRevolutionParameters::TaskRevolutionParameters(PartDesignGui::ViewProvider* RevolutionView, QWidget *parent) - : TaskSketchBasedParameters(RevolutionView, parent, "PartDesign_Revolution",tr("Revolution parameters")) + : TaskSketchBasedParameters(RevolutionView, parent, "PartDesign_Revolution", tr("Revolution parameters")) + , ui(new Ui_TaskRevolutionParameters) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskRevolutionParameters(); ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); @@ -379,8 +379,6 @@ TaskRevolutionParameters::~TaskRevolutionParameters() ex.ReportException(); } - delete ui; - for (size_t i = 0; i < axesInList.size(); i++) { delete axesInList[i]; } diff --git a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.h b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.h index bc810e4d34..1f28cf8657 100644 --- a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.h +++ b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.h @@ -92,7 +92,7 @@ private: private: QWidget* proxy; - Ui_TaskRevolutionParameters* ui; + std::unique_ptr ui; /** * @brief axesInList is the list of links corresponding to axis combo; must diff --git a/src/Mod/PartDesign/Gui/TaskScaledParameters.cpp b/src/Mod/PartDesign/Gui/TaskScaledParameters.cpp index 3bd6bb84c4..dcf82058b8 100644 --- a/src/Mod/PartDesign/Gui/TaskScaledParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskScaledParameters.cpp @@ -51,11 +51,11 @@ using namespace Gui; /* TRANSLATOR PartDesignGui::TaskScaledParameters */ TaskScaledParameters::TaskScaledParameters(ViewProviderTransformed *TransformedView,QWidget *parent) - : TaskTransformedParameters(TransformedView, parent) + : TaskTransformedParameters(TransformedView, parent) + , ui(new Ui_TaskScaledParameters) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskScaledParameters(); ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); @@ -69,10 +69,9 @@ TaskScaledParameters::TaskScaledParameters(ViewProviderTransformed *TransformedV } TaskScaledParameters::TaskScaledParameters(TaskMultiTransformParameters *parentTask, QLayout *layout) - : TaskTransformedParameters(parentTask) + : TaskTransformedParameters(parentTask), ui(new Ui_TaskScaledParameters) { proxy = new QWidget(parentTask); - ui = new Ui_TaskScaledParameters(); ui->setupUi(proxy); connect(ui->buttonOK, SIGNAL(pressed()), parentTask, SLOT(onSubTaskButtonOK())); @@ -242,7 +241,6 @@ unsigned TaskScaledParameters::getOccurrences(void) const TaskScaledParameters::~TaskScaledParameters() { - delete ui; if (proxy) delete proxy; } diff --git a/src/Mod/PartDesign/Gui/TaskScaledParameters.h b/src/Mod/PartDesign/Gui/TaskScaledParameters.h index 2e21501503..7fc844ff18 100644 --- a/src/Mod/PartDesign/Gui/TaskScaledParameters.h +++ b/src/Mod/PartDesign/Gui/TaskScaledParameters.h @@ -76,7 +76,7 @@ private: void updateUI(); private: - Ui_TaskScaledParameters* ui; + std::unique_ptr ui; }; diff --git a/src/Mod/PartDesign/Gui/TaskShapeBinder.cpp b/src/Mod/PartDesign/Gui/TaskShapeBinder.cpp index 7a556afd29..ff869fdba5 100644 --- a/src/Mod/PartDesign/Gui/TaskShapeBinder.cpp +++ b/src/Mod/PartDesign/Gui/TaskShapeBinder.cpp @@ -60,10 +60,10 @@ TaskShapeBinder::TaskShapeBinder(ViewProviderShapeBinder *view, bool /*newObj*/, : Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("PartDesign_ShapeBinder"), tr("Datum shape parameters"), true, parent) , SelectionObserver(view) + , ui(new Ui_TaskShapeBinder) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskShapeBinder(); ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); @@ -159,7 +159,6 @@ TaskShapeBinder::~TaskShapeBinder() } static_cast(vp)->highlightReferences(false, false); */ - delete ui; } void TaskShapeBinder::changeEvent(QEvent *) diff --git a/src/Mod/PartDesign/Gui/TaskShapeBinder.h b/src/Mod/PartDesign/Gui/TaskShapeBinder.h index a886961759..fdf1364a0d 100644 --- a/src/Mod/PartDesign/Gui/TaskShapeBinder.h +++ b/src/Mod/PartDesign/Gui/TaskShapeBinder.h @@ -76,7 +76,7 @@ private: private: QWidget* proxy; - Ui_TaskShapeBinder* ui; + std::unique_ptr ui; ViewProviderShapeBinder* vp; }; diff --git a/src/Mod/PartDesign/Gui/TaskThicknessParameters.cpp b/src/Mod/PartDesign/Gui/TaskThicknessParameters.cpp index f752dc91e5..44b3424da4 100644 --- a/src/Mod/PartDesign/Gui/TaskThicknessParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskThicknessParameters.cpp @@ -54,10 +54,10 @@ using namespace Gui; TaskThicknessParameters::TaskThicknessParameters(ViewProviderDressUp *DressUpView, QWidget *parent) : TaskDressUpParameters(DressUpView, false, true, parent) + , ui(new Ui_TaskThicknessParameters) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskThicknessParameters(); ui->setupUi(proxy); this->groupLayout()->addWidget(proxy); @@ -290,8 +290,6 @@ TaskThicknessParameters::~TaskThicknessParameters() { Gui::Selection().clearSelection(); Gui::Selection().rmvSelectionGate(); - - delete ui; } bool TaskThicknessParameters::event(QEvent *e) diff --git a/src/Mod/PartDesign/Gui/TaskThicknessParameters.h b/src/Mod/PartDesign/Gui/TaskThicknessParameters.h index bf5aed26e7..d2ad5490ba 100644 --- a/src/Mod/PartDesign/Gui/TaskThicknessParameters.h +++ b/src/Mod/PartDesign/Gui/TaskThicknessParameters.h @@ -60,7 +60,7 @@ protected: virtual void onSelectionChanged(const Gui::SelectionChanges& msg); private: - Ui_TaskThicknessParameters* ui; + std::unique_ptr ui; }; /// simulation dialog for the TaskView diff --git a/src/Mod/PartDesign/Gui/TaskTransformedMessages.cpp b/src/Mod/PartDesign/Gui/TaskTransformedMessages.cpp index 5d3290d78d..0674829203 100644 --- a/src/Mod/PartDesign/Gui/TaskTransformedMessages.cpp +++ b/src/Mod/PartDesign/Gui/TaskTransformedMessages.cpp @@ -40,12 +40,12 @@ using namespace Gui::TaskView; namespace bp = boost::placeholders; TaskTransformedMessages::TaskTransformedMessages(ViewProviderTransformed *transformedView_) - : TaskBox(Gui::BitmapFactory().pixmap("document-new"),tr("Transformed feature messages"),true, 0), - transformedView(transformedView_) + : TaskBox(Gui::BitmapFactory().pixmap("document-new"), tr("Transformed feature messages"), true, 0) + , transformedView(transformedView_) + , ui(new Ui_TaskTransformedMessages) { // we need a separate container widget to add all controls to proxy = new QWidget(this); - ui = new Ui_TaskTransformedMessages(); ui->setupUi(proxy); // set a minimum height to avoid a sudden resize and to // lose focus of the currently used spin boxes @@ -61,7 +61,6 @@ TaskTransformedMessages::TaskTransformedMessages(ViewProviderTransformed *transf TaskTransformedMessages::~TaskTransformedMessages() { connectionDiagnosis.disconnect(); - delete ui; } void TaskTransformedMessages::slotDiagnosis(QString msg) diff --git a/src/Mod/PartDesign/Gui/TaskTransformedMessages.h b/src/Mod/PartDesign/Gui/TaskTransformedMessages.h index ad1b349488..3445dfdd06 100644 --- a/src/Mod/PartDesign/Gui/TaskTransformedMessages.h +++ b/src/Mod/PartDesign/Gui/TaskTransformedMessages.h @@ -56,7 +56,7 @@ protected: private: QWidget* proxy; - Ui_TaskTransformedMessages* ui; + std::unique_ptr ui; }; } //namespace PartDesignGui diff --git a/src/Mod/PartDesign/Gui/TaskTransformedParameters.cpp b/src/Mod/PartDesign/Gui/TaskTransformedParameters.cpp index 7c43fe671d..fa5b10a5cf 100644 --- a/src/Mod/PartDesign/Gui/TaskTransformedParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskTransformedParameters.cpp @@ -65,14 +65,12 @@ using namespace Gui; TaskTransformedParameters::TaskTransformedParameters(ViewProviderTransformed *TransformedView, QWidget *parent) : TaskBox(Gui::BitmapFactory().pixmap((std::string("PartDesign_") + TransformedView->featureName).c_str()), - QString::fromLatin1((TransformedView->featureName + " parameters").c_str()), - true, - parent), - proxy(nullptr), - TransformedView(TransformedView), - parentTask(nullptr), - insideMultiTransform(false), - blockUpdate(false) + QString::fromLatin1((TransformedView->featureName + " parameters").c_str()), true, parent) + , proxy(nullptr) + , TransformedView(TransformedView) + , parentTask(nullptr) + , insideMultiTransform(false) + , blockUpdate(false) { selectionMode = none; From 8efbe8861f1544e7cb34e8daee2efcad80babfba Mon Sep 17 00:00:00 2001 From: David Osterberg Date: Fri, 5 Feb 2021 14:20:54 +0100 Subject: [PATCH 164/168] PartDesign: [Helix] Fix helix starting point bug --- src/Mod/PartDesign/App/FeatureHelix.cpp | 61 ++++++++----------------- src/Mod/PartDesign/App/FeatureHelix.h | 3 ++ 2 files changed, 21 insertions(+), 43 deletions(-) diff --git a/src/Mod/PartDesign/App/FeatureHelix.cpp b/src/Mod/PartDesign/App/FeatureHelix.cpp index 9a8dbc8e3c..695c02dde7 100644 --- a/src/Mod/PartDesign/App/FeatureHelix.cpp +++ b/src/Mod/PartDesign/App/FeatureHelix.cpp @@ -390,48 +390,12 @@ TopoDS_Shape Helix::generateHelixPath(void) Base::Vector3d start = v.Cross(normal); // pointing towards the desired helix start point. gp_Dir dir_start(start.x, start.y, start.z); - // Determine radius as the minimum distance between sketchshape and axis. - // also find out in what quadrant relative to the axis the profile is located. - double radius = 1e99; - bool turned = false; - double startOffset = 1e99; - TopoDS_Shape sketchshape = getVerifiedFace(); - BRepBuilderAPI_MakeEdge axisEdge(gp_Lin(pnt, dir)); - BRepBuilderAPI_MakeEdge startEdge(gp_Lin(pnt, dir_start)); - for (TopExp_Explorer xp(sketchshape, TopAbs_FACE); xp.More(); xp.Next()) { - const TopoDS_Face face = TopoDS::Face(xp.Current()); - TopoDS_Wire wire = ShapeAnalysis::OuterWire(face); - BRepExtrema_DistShapeShape distR(wire, axisEdge.Shape(), Precision::Confusion()); - if (distR.IsDone()) { - if (distR.Value() < radius) { - radius = distR.Value(); - const gp_Pnt p1 = distR.PointOnShape1(1); - const gp_Pnt p2 = distR.PointOnShape2(1); - double offsetProfile = p1.X()*dir_start.X() + p1.Y()*dir_start.Y() + p1.Z()*dir_start.Z(); - double offsetAxis = p2.X()*dir_start.X() + p2.Y()*dir_start.Y() + p2.Z()*dir_start.Z(); - turned = (offsetProfile < offsetAxis); - } - } - BRepExtrema_DistShapeShape distStart(wire, startEdge.Shape(), Precision::Confusion()); - if (distStart.IsDone()) { - if (distStart.Value() < abs(startOffset)) { - const gp_Pnt p1 = distStart.PointOnShape1(1); - const gp_Pnt p2 = distStart.PointOnShape2(1); - double offsetProfile = p1.X()*dir.X() + p1.Y()*dir.Y() + p1.Z()*dir.Z(); - double offsetAxis = p2.X()*dir.X() + p2.Y()*dir.Y() + p2.Z()*dir.Z(); - startOffset = offsetProfile - offsetAxis; - } - } - - } - - if (radius < Precision::Confusion()) { - // in this case ensure that axis is not in the sketch plane - if (v*normal < Precision::Confusion()) - throw Base::ValueError("Error: Result is self intersecting"); - radius = 1.0; //fallback to radius 1 - startOffset = 0.0; - } + // Find out in what quadrant relative to the axis the profile is located, and the exact position. + Base::Vector3d profileCenter = getProfileCenterPoint(); + double axisOffset = profileCenter*start - b*start; + double startOffset = profileCenter*v - b*v; + double radius = std::fabs(axisOffset); + bool turned = axisOffset < 0; //build the helix path TopoShape helix = TopoShape().makeLongHelix(pitch, height, radius, angle, leftHanded); @@ -444,7 +408,6 @@ TopoDS_Shape Helix::generateHelixPath(void) * map to the sketch plane. */ - gp_Pnt origo(0.0, 0.0, 0.0); gp_Dir dir_axis1(0.0, 0.0, 1.0); // pointing along the helix axis, as created. gp_Dir dir_axis2(1.0, 0.0, 0.0); // pointing towards the helix start point, as created. @@ -541,6 +504,18 @@ void Helix::proposeParameters(bool force) } } +Base::Vector3d Helix::getProfileCenterPoint() +{ + TopoDS_Shape profileshape; + profileshape = getVerifiedFace(); + Bnd_Box box; + BRepBndLib::Add(profileshape, box); + box.SetGap(0.0); + double xmin, ymin, zmin, xmax, ymax, zmax; + box.Get(xmin, ymin, zmin, xmax, ymax, zmax); + return Base::Vector3d(0.5*(xmin+xmax), 0.5*(ymin+ymax), 0.5*(zmin+zmax)); +} + PROPERTY_SOURCE(PartDesign::AdditiveHelix, PartDesign::Helix) AdditiveHelix::AdditiveHelix() { diff --git a/src/Mod/PartDesign/App/FeatureHelix.h b/src/Mod/PartDesign/App/FeatureHelix.h index ad5fad0111..a623022f03 100644 --- a/src/Mod/PartDesign/App/FeatureHelix.h +++ b/src/Mod/PartDesign/App/FeatureHelix.h @@ -77,6 +77,9 @@ protected: // project shape on plane. Used for detecting self intersection. TopoDS_Shape projectShape(const TopoDS_Shape& input, const gp_Ax2& plane); + // center of profile bounding box + Base::Vector3d getProfileCenterPoint(); + private: static const char* ModeEnums[]; }; From 75dee2ff7df0b45655a79284991dfb6526fb8a07 Mon Sep 17 00:00:00 2001 From: Aapo Date: Thu, 4 Feb 2021 23:51:39 +0200 Subject: [PATCH 165/168] [TD] Fix XSource handling of sub-objects of links to another document(s) in TD Views. --- src/Mod/TechDraw/Gui/Command.cpp | 44 ++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/Mod/TechDraw/Gui/Command.cpp b/src/Mod/TechDraw/Gui/Command.cpp index 8fc52fbdd7..ea9bacd75e 100644 --- a/src/Mod/TechDraw/Gui/Command.cpp +++ b/src/Mod/TechDraw/Gui/Command.cpp @@ -318,6 +318,7 @@ void CmdTechDrawView::activated(int iMsg) resolve, single); for (auto& sel: selection) { + bool is_linked = false; auto obj = sel.getObject(); if (obj->isDerivedFrom(TechDraw::DrawPage::getClassTypeId()) ) { continue; @@ -325,6 +326,27 @@ void CmdTechDrawView::activated(int iMsg) if ( obj->isDerivedFrom(App::LinkElement::getClassTypeId()) || obj->isDerivedFrom(App::LinkGroup::getClassTypeId()) || obj->isDerivedFrom(App::Link::getClassTypeId()) ) { + is_linked = true; + } + // If parent of the obj is a link to another document, we possibly need to treat non-link obj as linked, too + // 1st, is obj in another document? + if (obj->getDocument() != this->getDocument()) { + std::set parents = obj->getInListEx(true); + for (auto &parent: parents) { + // Only consider parents in the current document, i.e. possible links in this View's document + if (parent->getDocument() != this->getDocument()) { + continue; + } + // 2nd, do we really have a link to obj? + if (parent->isDerivedFrom(App::LinkElement::getClassTypeId()) || + parent->isDerivedFrom(App::LinkGroup::getClassTypeId()) || + parent->isDerivedFrom(App::Link::getClassTypeId())) { + // We have a link chain from this document to obj, and obj is in another document -> it's an XLink target + is_linked = true; + } + } + } + if (is_linked) { xShapes.push_back(obj); continue; } @@ -574,6 +596,7 @@ void CmdTechDrawProjectionGroup::activated(int iMsg) resolve, single); for (auto& sel: selection) { + bool is_linked = false; auto obj = sel.getObject(); if (obj->isDerivedFrom(TechDraw::DrawPage::getClassTypeId()) ) { continue; @@ -581,6 +604,27 @@ void CmdTechDrawProjectionGroup::activated(int iMsg) if ( obj->isDerivedFrom(App::LinkElement::getClassTypeId()) || obj->isDerivedFrom(App::LinkGroup::getClassTypeId()) || obj->isDerivedFrom(App::Link::getClassTypeId()) ) { + is_linked = true; + } + // If parent of the obj is a link to another document, we possibly need to treat non-link obj as linked, too + // 1st, is obj in another document? + if (obj->getDocument() != this->getDocument()) { + std::set parents = obj->getInListEx(true); + for (auto &parent: parents) { + // Only consider parents in the current document, i.e. possible links in this View's document + if (parent->getDocument() != this->getDocument()) { + continue; + } + // 2nd, do we really have a link to obj? + if (parent->isDerivedFrom(App::LinkElement::getClassTypeId()) || + parent->isDerivedFrom(App::LinkGroup::getClassTypeId()) || + parent->isDerivedFrom(App::Link::getClassTypeId())) { + // We have a link chain from this document to obj, and obj is in another document -> it's an XLink target + is_linked = true; + } + } + } + if (is_linked) { xShapes.push_back(obj); continue; } From 692900d0851a32e84f00f27ed94c6f9fd2e0a349 Mon Sep 17 00:00:00 2001 From: David Osterberg Date: Fri, 5 Feb 2021 20:42:11 +0100 Subject: [PATCH 166/168] PartDesign: fix regression in Helix --- src/Mod/PartDesign/App/FeatureHelix.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Mod/PartDesign/App/FeatureHelix.cpp b/src/Mod/PartDesign/App/FeatureHelix.cpp index 695c02dde7..b571df9a78 100644 --- a/src/Mod/PartDesign/App/FeatureHelix.cpp +++ b/src/Mod/PartDesign/App/FeatureHelix.cpp @@ -397,6 +397,14 @@ TopoDS_Shape Helix::generateHelixPath(void) double radius = std::fabs(axisOffset); bool turned = axisOffset < 0; + if (radius < Precision::Confusion()) { + // in this case ensure that axis is not in the sketch plane + if (v*normal < Precision::Confusion()) + throw Base::ValueError("Error: Result is self intersecting"); + radius = 1.0; //fallback to radius 1 + startOffset = 0.0; + } + //build the helix path TopoShape helix = TopoShape().makeLongHelix(pitch, height, radius, angle, leftHanded); TopoDS_Shape path = helix.getShape(); From ea54ed3e90eba98c669d672ba9ffc29c9b300c8a Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Fri, 5 Feb 2021 14:21:15 -0600 Subject: [PATCH 167/168] Modify LGTM config to exclude legacy/unmaintained --- lgtm.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lgtm.yml b/lgtm.yml index 1812d50024..6481c55a72 100644 --- a/lgtm.yml +++ b/lgtm.yml @@ -5,9 +5,19 @@ path_classifiers: - "src/Mod/Import/App/config_control_design.py" - "src/Mod/Import/App/ifc2x3.py" - "src/Mod/Import/App/ifc4.py" -queries: - - exclude: "src/zipios++/**" - - exclude: "src/Mod/Robot/App/kdl_cp/**" + library: + - "src/zipios++/" + - "src/3rdParty/" + - "src/Mod/Import/App/SCL" + template: + - "src/Tools/examplePy2wiki.py" + unmaintained: + - "src/Mod/Robot/" + - "src/Mod/Ship/" + legacy: + - "src/Mod/Assembly/" + - "src/Mod/Drawing/" + extraction: javascript: index: From 9318c4c1f2946771cf52660c33fe9918b69b26bb Mon Sep 17 00:00:00 2001 From: Aapo Date: Fri, 5 Feb 2021 22:02:59 +0200 Subject: [PATCH 168/168] [TD] ShapeExtractor.cpp: Fix linked View source bug, link targets were translated but not scaled in TD. --- src/Mod/TechDraw/App/ShapeExtractor.cpp | 36 +++++++++++++++++-------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/Mod/TechDraw/App/ShapeExtractor.cpp b/src/Mod/TechDraw/App/ShapeExtractor.cpp index e2d070683d..292f146a14 100644 --- a/src/Mod/TechDraw/App/ShapeExtractor.cpp +++ b/src/Mod/TechDraw/App/ShapeExtractor.cpp @@ -163,32 +163,44 @@ std::vector ShapeExtractor::getXShapes(const App::Link* xLink) return xSourceShapes; } + bool needsTransform = false; std::vector children = xLink->getLinkedChildren(); - Base::Placement linkPlm; + Base::Placement linkPlm; // default constructor is an identity placement, i.e. no rotation nor translation if (xLink->hasPlacement()) { linkPlm = xLink->getLinkPlacementProperty()->getValue(); + needsTransform = true; + } + Base::Matrix4D linkScale; // default constructor is an identity matrix, possibly scale it with link's scale + if(xLink->getScaleProperty() || xLink->getScaleVectorProperty()) { + linkScale.scale(xLink->getScaleVector()); + needsTransform = true; } + Base::Matrix4D netTransform; if (!children.empty()) { for (auto& l:children) { -//What to do with LinkGroup??? -// if (l->getTypeId().isDerivedFrom(App::LinkGroup::getClassTypeId())) { -// Base::Console().Message("SE::getXShapes - found a LinkGroup\n"); -// } + bool childNeedsTransform = false; Base::Placement childPlm; + Base::Matrix4D childScale; if (l->getTypeId().isDerivedFrom(App::LinkElement::getClassTypeId())) { App::LinkElement* cLinkElem = static_cast(l); if (cLinkElem->hasPlacement()) { childPlm = cLinkElem->getLinkPlacementProperty()->getValue(); + childNeedsTransform = true; + } + if(cLinkElem->getScaleProperty() || cLinkElem->getScaleVectorProperty()) { + childScale.scale(cLinkElem->getScaleVector()); + childNeedsTransform = true; } } auto shape = Part::Feature::getShape(l); if(!shape.IsNull()) { - Base::Placement netPlm = linkPlm; - netPlm *= childPlm; - if (xLink->hasPlacement()) { + if (needsTransform || childNeedsTransform) { + // Multiplication is associative, but the braces show the idea of combining the two transforms: + // ( link placement and scale ) combined to ( child placement and scale ) + netTransform = (linkPlm.toMatrix() * linkScale) * (childPlm.toMatrix() * childScale); Part::TopoShape ts(shape); - ts.setPlacement(netPlm); + ts.transformGeometry(netTransform); shape = ts.getShape(); } if (shape.ShapeType() > TopAbs_COMPSOLID) { //simple shape @@ -209,9 +221,11 @@ std::vector ShapeExtractor::getXShapes(const App::Link* xLink) if (link != nullptr) { auto shape = Part::Feature::getShape(link); if(!shape.IsNull()) { - if (xLink->hasPlacement()) { + if (needsTransform) { + // Transform is just link placement and scale, no child objects + netTransform = linkPlm.toMatrix() * linkScale; Part::TopoShape ts(shape); - ts.setPlacement(linkPlm); + ts.transformGeometry(netTransform); shape = ts.getShape(); }