From 95b2a20228098abb573f890733e259e247b477ff Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 13 Dec 2020 21:51:31 -0800 Subject: [PATCH 01/65] Added preference option to suppress missing rapid speed warning --- src/Mod/Path/Gui/DlgSettingsPathColor.cpp | 2 + src/Mod/Path/Gui/DlgSettingsPathColor.ui | 36 ++++++++++++++-- src/Mod/Path/PathScripts/PathOp.py | 3 +- src/Mod/Path/PathScripts/PathPreferences.py | 48 +++++++++++---------- 4 files changed, 63 insertions(+), 26 deletions(-) diff --git a/src/Mod/Path/Gui/DlgSettingsPathColor.cpp b/src/Mod/Path/Gui/DlgSettingsPathColor.cpp index d2db05acb2..b68ed7de28 100644 --- a/src/Mod/Path/Gui/DlgSettingsPathColor.cpp +++ b/src/Mod/Path/Gui/DlgSettingsPathColor.cpp @@ -67,6 +67,7 @@ void DlgSettingsPathColor::saveSettings() ui->DefaultBBoxNormalColor->onSave(); ui->DefaultSelectionStyle->onSave(); ui->DefaultTaskPanelLayout->onSave(); + ui->WarningSuppressRapidSpeeds->onSave(); } void DlgSettingsPathColor::loadSettings() @@ -83,6 +84,7 @@ void DlgSettingsPathColor::loadSettings() ui->DefaultBBoxNormalColor->onRestore(); ui->DefaultSelectionStyle->onRestore(); ui->DefaultTaskPanelLayout->onRestore(); + ui->WarningSuppressRapidSpeeds->onRestore(); } /** diff --git a/src/Mod/Path/Gui/DlgSettingsPathColor.ui b/src/Mod/Path/Gui/DlgSettingsPathColor.ui index fe18cf2843..ecfbcc1882 100644 --- a/src/Mod/Path/Gui/DlgSettingsPathColor.ui +++ b/src/Mod/Path/Gui/DlgSettingsPathColor.ui @@ -6,12 +6,12 @@ 0 0 - 483 - 536 + 512 + 633 - Path colors + GUI @@ -449,6 +449,31 @@ + + + + Warnings + + + + + + Suppress warning about setting the rapid speed rates for accurate cycle time calculation + + + Suppress missing rapid speeds warning + + + WarningSuppressRapidSpeeds + + + Mod/Path + + + + + + @@ -472,6 +497,11 @@ QComboBox
Gui/PrefWidgets.h
+ + Gui::PrefCheckBox + QCheckBox +
Gui/PrefWidgets.h
+
DefaultNormalPathColor diff --git a/src/Mod/Path/PathScripts/PathOp.py b/src/Mod/Path/PathScripts/PathOp.py index ee1cde01dc..a618d9f026 100644 --- a/src/Mod/Path/PathScripts/PathOp.py +++ b/src/Mod/Path/PathScripts/PathOp.py @@ -27,6 +27,7 @@ from PySide import QtCore import Path import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog +import PathScripts.PathPreferences as PathPreferences import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils from PathScripts.PathUtils import waiting_effects @@ -551,7 +552,7 @@ class ObjectOp(object): PathLog.warning(translate("Path", "Tool Controller feedrates required to calculate the cycle time.")) return translate('Path', 'Feedrate Error') - if hRapidrate == 0 or vRapidrate == 0: + if (hRapidrate == 0 or vRapidrate == 0) and not PathPreferences.suppressRapidSpeedsWarning(): PathLog.warning(translate("Path", "Add Tool Controller Rapid Speeds on the SetupSheet for more accurate cycle times.")) # Get the cycle time in seconds diff --git a/src/Mod/Path/PathScripts/PathPreferences.py b/src/Mod/Path/PathScripts/PathPreferences.py index 6954c4b977..d41326ae30 100644 --- a/src/Mod/Path/PathScripts/PathPreferences.py +++ b/src/Mod/Path/PathScripts/PathPreferences.py @@ -29,35 +29,36 @@ import PathScripts.PathLog as PathLog # PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # PathLog.trackModule() -DefaultFilePath = "DefaultFilePath" -DefaultJobTemplate = "DefaultJobTemplate" -DefaultStockTemplate = "DefaultStockTemplate" -DefaultTaskPanelLayout = "DefaultTaskPanelLayout" +DefaultFilePath = "DefaultFilePath" +DefaultJobTemplate = "DefaultJobTemplate" +DefaultStockTemplate = "DefaultStockTemplate" +DefaultTaskPanelLayout = "DefaultTaskPanelLayout" -PostProcessorDefault = "PostProcessorDefault" -PostProcessorDefaultArgs = "PostProcessorDefaultArgs" -PostProcessorBlacklist = "PostProcessorBlacklist" -PostProcessorOutputFile = "PostProcessorOutputFile" -PostProcessorOutputPolicy = "PostProcessorOutputPolicy" +PostProcessorDefault = "PostProcessorDefault" +PostProcessorDefaultArgs = "PostProcessorDefaultArgs" +PostProcessorBlacklist = "PostProcessorBlacklist" +PostProcessorOutputFile = "PostProcessorOutputFile" +PostProcessorOutputPolicy = "PostProcessorOutputPolicy" -LastPathToolBit = "LastPathToolBit" -LastPathToolLibrary = "LastPathToolLibrary" -LastPathToolShape = "LastPathToolShape" -LastPathToolTable = "LastPathToolTable" +LastPathToolBit = "LastPathToolBit" +LastPathToolLibrary = "LastPathToolLibrary" +LastPathToolShape = "LastPathToolShape" +LastPathToolTable = "LastPathToolTable" -LastFileToolBit = "LastFileToolBit" -LastFileToolLibrary = "LastFileToolLibrary" -LastFileToolShape = "LastFileToolShape" +LastFileToolBit = "LastFileToolBit" +LastFileToolLibrary = "LastFileToolLibrary" +LastFileToolShape = "LastFileToolShape" -UseLegacyTools = "UseLegacyTools" -UseAbsoluteToolPaths = "UseAbsoluteToolPaths" -OpenLastLibrary = "OpenLastLibrary" +UseLegacyTools = "UseLegacyTools" +UseAbsoluteToolPaths = "UseAbsoluteToolPaths" +OpenLastLibrary = "OpenLastLibrary" # Linear tolerance to use when generating Paths, eg when tessellating geometry -GeometryTolerance = "GeometryTolerance" -LibAreaCurveAccuracy = "LibAreaCurveAccuarcy" +GeometryTolerance = "GeometryTolerance" +LibAreaCurveAccuracy = "LibAreaCurveAccuarcy" -EnableExperimentalFeatures = "EnableExperimentalFeatures" +WarningSuppressRapidSpeeds = "WarningSuppressRapidSpeeds" +EnableExperimentalFeatures = "EnableExperimentalFeatures" def preferences(): @@ -259,6 +260,9 @@ def setDefaultTaskPanelLayout(style): def experimentalFeaturesEnabled(): return preferences().GetBool(EnableExperimentalFeatures, False) +def suppressRapidSpeedsWarning(): + return preferences().GetBool(WarningSuppressRapidSpeeds, False) + def lastFileToolLibrary(): filename = preferences().GetString(LastFileToolLibrary) From eb9cd1e7dadd7c21a59c08660ebed6ed33ceb84a Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 13 Dec 2020 22:03:21 -0800 Subject: [PATCH 02/65] Preference setting so suppress logging the selection mode as a warning. --- src/Mod/Path/Gui/DlgSettingsPathColor.cpp | 2 + src/Mod/Path/Gui/DlgSettingsPathColor.ui | 16 +++++++ src/Mod/Path/PathScripts/PathPreferences.py | 4 ++ src/Mod/Path/PathScripts/PathSelection.py | 49 ++++++++++++++------- 4 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/Mod/Path/Gui/DlgSettingsPathColor.cpp b/src/Mod/Path/Gui/DlgSettingsPathColor.cpp index b68ed7de28..282018c81a 100644 --- a/src/Mod/Path/Gui/DlgSettingsPathColor.cpp +++ b/src/Mod/Path/Gui/DlgSettingsPathColor.cpp @@ -68,6 +68,7 @@ void DlgSettingsPathColor::saveSettings() ui->DefaultSelectionStyle->onSave(); ui->DefaultTaskPanelLayout->onSave(); ui->WarningSuppressRapidSpeeds->onSave(); + ui->WarningSuppressSelectionMode->onSave(); } void DlgSettingsPathColor::loadSettings() @@ -85,6 +86,7 @@ void DlgSettingsPathColor::loadSettings() ui->DefaultSelectionStyle->onRestore(); ui->DefaultTaskPanelLayout->onRestore(); ui->WarningSuppressRapidSpeeds->onRestore(); + ui->WarningSuppressSelectionMode->onRestore(); } /** diff --git a/src/Mod/Path/Gui/DlgSettingsPathColor.ui b/src/Mod/Path/Gui/DlgSettingsPathColor.ui index ecfbcc1882..9f0e630295 100644 --- a/src/Mod/Path/Gui/DlgSettingsPathColor.ui +++ b/src/Mod/Path/Gui/DlgSettingsPathColor.ui @@ -471,6 +471,22 @@ + + + + Suppress warning whenever a Path selection mode is activated + + + Suppress selection mode warning + + + WarningSuppressSelectionMode + + + Mod/Path + + + diff --git a/src/Mod/Path/PathScripts/PathPreferences.py b/src/Mod/Path/PathScripts/PathPreferences.py index d41326ae30..f750016372 100644 --- a/src/Mod/Path/PathScripts/PathPreferences.py +++ b/src/Mod/Path/PathScripts/PathPreferences.py @@ -58,6 +58,7 @@ GeometryTolerance = "GeometryTolerance" LibAreaCurveAccuracy = "LibAreaCurveAccuarcy" WarningSuppressRapidSpeeds = "WarningSuppressRapidSpeeds" +WarningSuppressSelectionMode = "WarningSuppressSelectionMode" EnableExperimentalFeatures = "EnableExperimentalFeatures" @@ -263,6 +264,9 @@ def experimentalFeaturesEnabled(): def suppressRapidSpeedsWarning(): return preferences().GetBool(WarningSuppressRapidSpeeds, False) +def suppressSelectionModeWarning(): + return preferences().GetBool(WarningSuppressSelectionMode, False) + def lastFileToolLibrary(): filename = preferences().GetString(LastFileToolLibrary) diff --git a/src/Mod/Path/PathScripts/PathSelection.py b/src/Mod/Path/PathScripts/PathSelection.py index 11a75b31a1..055bfb1757 100644 --- a/src/Mod/Path/PathScripts/PathSelection.py +++ b/src/Mod/Path/PathScripts/PathSelection.py @@ -25,6 +25,7 @@ import FreeCAD import FreeCADGui import PathScripts.PathLog as PathLog +import PathScripts.PathPreferences as PathPreferences import PathScripts.PathUtils as PathUtils import math @@ -286,52 +287,62 @@ class ALLGate(PathBaseGate): def contourselect(): FreeCADGui.Selection.addSelectionGate(CONTOURGate()) - FreeCAD.Console.PrintWarning("Contour Select Mode\n") + if not PathPreferences.suppressSelectionModeWarning(): + FreeCAD.Console.PrintWarning("Contour Select Mode\n") def eselect(): FreeCADGui.Selection.addSelectionGate(EGate()) - FreeCAD.Console.PrintWarning("Edge Select Mode\n") + if not PathPreferences.suppressSelectionModeWarning(): + FreeCAD.Console.PrintWarning("Edge Select Mode\n") def drillselect(): FreeCADGui.Selection.addSelectionGate(DRILLGate()) - FreeCAD.Console.PrintWarning("Drilling Select Mode\n") + if not PathPreferences.suppressSelectionModeWarning(): + FreeCAD.Console.PrintWarning("Drilling Select Mode\n") def engraveselect(): FreeCADGui.Selection.addSelectionGate(ENGRAVEGate()) - FreeCAD.Console.PrintWarning("Engraving Select Mode\n") + if not PathPreferences.suppressSelectionModeWarning(): + FreeCAD.Console.PrintWarning("Engraving Select Mode\n") def fselect(): FreeCADGui.Selection.addSelectionGate(FACEGate()) # Was PROFILEGate() - FreeCAD.Console.PrintWarning("Profiling Select Mode\n") + if not PathPreferences.suppressSelectionModeWarning(): + FreeCAD.Console.PrintWarning("Profiling Select Mode\n") def chamferselect(): FreeCADGui.Selection.addSelectionGate(CHAMFERGate()) - FreeCAD.Console.PrintWarning("Deburr Select Mode\n") + if not PathPreferences.suppressSelectionModeWarning(): + FreeCAD.Console.PrintWarning("Deburr Select Mode\n") def profileselect(): FreeCADGui.Selection.addSelectionGate(PROFILEGate()) - FreeCAD.Console.PrintWarning("Profiling Select Mode\n") + if not PathPreferences.suppressSelectionModeWarning(): + FreeCAD.Console.PrintWarning("Profiling Select Mode\n") def pocketselect(): FreeCADGui.Selection.addSelectionGate(POCKETGate()) - FreeCAD.Console.PrintWarning("Pocketing Select Mode\n") + if not PathPreferences.suppressSelectionModeWarning(): + FreeCAD.Console.PrintWarning("Pocketing Select Mode\n") def adaptiveselect(): FreeCADGui.Selection.addSelectionGate(ADAPTIVEGate()) - FreeCAD.Console.PrintWarning("Adaptive Select Mode\n") + if not PathPreferences.suppressSelectionModeWarning(): + FreeCAD.Console.PrintWarning("Adaptive Select Mode\n") def slotselect(): FreeCADGui.Selection.addSelectionGate(ALLGate()) - FreeCAD.Console.PrintWarning("Slot Cutter Select Mode\n") + if not PathPreferences.suppressSelectionModeWarning(): + FreeCAD.Console.PrintWarning("Slot Cutter Select Mode\n") def surfaceselect(): @@ -339,26 +350,31 @@ def surfaceselect(): if(MESHGate() or FACEGate()): gate = True FreeCADGui.Selection.addSelectionGate(gate) - FreeCAD.Console.PrintWarning("Surfacing Select Mode\n") + if not PathPreferences.suppressSelectionModeWarning(): + FreeCAD.Console.PrintWarning("Surfacing Select Mode\n") def vcarveselect(): FreeCADGui.Selection.addSelectionGate(VCARVEGate()) - FreeCAD.Console.PrintWarning("Vcarve Select Mode\n") + if not PathPreferences.suppressSelectionModeWarning(): + FreeCAD.Console.PrintWarning("Vcarve Select Mode\n") def probeselect(): FreeCADGui.Selection.addSelectionGate(PROBEGate()) - FreeCAD.Console.PrintWarning("Probe Select Mode\n") + if not PathPreferences.suppressSelectionModeWarning(): + FreeCAD.Console.PrintWarning("Probe Select Mode\n") def customselect(): - FreeCAD.Console.PrintWarning("Custom Select Mode\n") + if not PathPreferences.suppressSelectionModeWarning(): + FreeCAD.Console.PrintWarning("Custom Select Mode\n") def turnselect(): FreeCADGui.Selection.addSelectionGate(TURNGate()) - FreeCAD.Console.PrintWarning("Turning Select Mode\n") + if not PathPreferences.suppressSelectionModeWarning(): + FreeCAD.Console.PrintWarning("Turning Select Mode\n") def select(op): @@ -392,4 +408,5 @@ def select(op): def clear(): FreeCADGui.Selection.removeSelectionGate() - FreeCAD.Console.PrintWarning("Free Select\n") + if not PathPreferences.suppressSelectionModeWarning(): + FreeCAD.Console.PrintWarning("Free Select\n") From 69e9639a9eef6e117faa56948630d2bb5f8ab616 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 13 Dec 2020 22:06:15 -0800 Subject: [PATCH 03/65] Replaced print messages with PathLog statements --- src/Mod/Path/PathScripts/PathUtils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index c53b7a2ab6..5bc5011756 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -401,7 +401,7 @@ def getToolControllers(obj, proxy=None): except Exception: # pylint: disable=broad-except job = None - print("op={} ({})".format(obj.Label, type(obj))) + PathLog.debug("op={} ({})".format(obj.Label, type(obj))) if job: return [c for c in job.ToolController if proxy.isToolSupported(obj, c.Tool)] return [] @@ -538,7 +538,7 @@ def arc(cx, cy, sx, sy, ex, ey, horizFeed=0, ez=None, ccw=False): eps = 0.01 if (math.sqrt((cx - sx)**2 + (cy - sy)**2) - math.sqrt((cx - ex)**2 + (cy - ey)**2)) >= eps: - print("ERROR: Illegal arc: Start and end radii not equal") + PathLog.error(translate("Path", "Illegal arc: Start and end radii not equal")) return "" retstr = "" From c7e914ecc075138e3c023a513ffd2f37a3c1e01d Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 13 Dec 2020 22:15:32 -0800 Subject: [PATCH 04/65] Added hidden preference to suppress the opencamlib error --- src/Mod/Path/InitGui.py | 3 ++- src/Mod/Path/PathScripts/PathPreferences.py | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index d1feaff6a5..3b7f906e91 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -129,7 +129,8 @@ class PathWorkbench (Workbench): threedcmdgroup = ['Path_3dTools'] FreeCADGui.addCommand('Path_3dTools', PathCommandGroup(threedopcmdlist, QtCore.QT_TRANSLATE_NOOP("Path", '3D Operations'))) except ImportError: - FreeCAD.Console.PrintError("OpenCamLib is not working!\n") + if not PathPreferences.suppressOpenCamLibWarning(): + FreeCAD.Console.PrintError("OpenCamLib is not working!\n") self.appendToolbar(QtCore.QT_TRANSLATE_NOOP("Path", "Project Setup"), projcmdlist) self.appendToolbar(QtCore.QT_TRANSLATE_NOOP("Path", "Tool Commands"), toolcmdlist) diff --git a/src/Mod/Path/PathScripts/PathPreferences.py b/src/Mod/Path/PathScripts/PathPreferences.py index f750016372..ee6c842c43 100644 --- a/src/Mod/Path/PathScripts/PathPreferences.py +++ b/src/Mod/Path/PathScripts/PathPreferences.py @@ -58,7 +58,8 @@ GeometryTolerance = "GeometryTolerance" LibAreaCurveAccuracy = "LibAreaCurveAccuarcy" WarningSuppressRapidSpeeds = "WarningSuppressRapidSpeeds" -WarningSuppressSelectionMode = "WarningSuppressSelectionMode" +WarningSuppressSelectionMode = "WarningSuppressSelectionMode" +WarningSuppressOpenCamLib = "WarningSuppressOpenCamLib" EnableExperimentalFeatures = "EnableExperimentalFeatures" @@ -267,6 +268,9 @@ def suppressRapidSpeedsWarning(): def suppressSelectionModeWarning(): return preferences().GetBool(WarningSuppressSelectionMode, False) +def suppressOpenCamLibWarning(): + return preferences().GetBool(WarningSuppressOpenCamLib, False) + def lastFileToolLibrary(): filename = preferences().GetString(LastFileToolLibrary) From 933e13131e58e21e105310cab8f586c3159bec13 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 14 Dec 2020 18:25:27 -0800 Subject: [PATCH 05/65] Added warning suppression for all missing speeds and changed default to true --- src/Mod/Path/App/Path.cpp | 24 +++++---- src/Mod/Path/Gui/DlgSettingsPathColor.cpp | 2 + src/Mod/Path/Gui/DlgSettingsPathColor.ui | 29 +++++++++- src/Mod/Path/PathScripts/PathOp.py | 2 +- src/Mod/Path/PathScripts/PathPreferences.py | 60 +++++++++++---------- 5 files changed, 77 insertions(+), 40 deletions(-) diff --git a/src/Mod/Path/App/Path.cpp b/src/Mod/Path/App/Path.cpp index 12b0119e56..c47a03e9de 100644 --- a/src/Mod/Path/App/Path.cpp +++ b/src/Mod/Path/App/Path.cpp @@ -27,11 +27,13 @@ # include #endif -#include +#include +#include +#include +#include #include #include -#include -#include +#include // KDL stuff - at the moment, not used //#include "Mod/Robot/App/kdl_cp/path_line.hpp" @@ -150,27 +152,31 @@ double Toolpath::getLength() double Toolpath::getCycleTime(double hFeed, double vFeed, double hRapid, double vRapid) { // check the feedrates are set - if ((hFeed == 0) || (vFeed == 0)){ - Base::Console().Warning("Feed Rate Error: Check Tool Controllers have Feed Rates"); + if ((hFeed == 0) || (vFeed == 0)) { + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Path"); + if (!hGrp->GetBool("WarningsSuppressAllSpeeds", true)) { + Base::Console().Warning("Feed Rate Error: Check Tool Controllers have Feed Rates"); + } return 0; } - if (hRapid == 0){ + if (hRapid == 0) { hRapid = hFeed; } - if (vRapid == 0){ + if (vRapid == 0) { vRapid = vFeed; } - if(vpcCommands.size()==0) + if (vpcCommands.size() == 0) { return 0; + } double l = 0; double time = 0; bool verticalMove = false; Vector3d last(0,0,0); Vector3d next; - for(std::vector::const_iterator it = vpcCommands.begin();it!=vpcCommands.end();++it) { + for (std::vector::const_iterator it = vpcCommands.begin();it!=vpcCommands.end();++it) { std::string name = (*it)->Name; float feedrate = (*it)->getParam("F"); diff --git a/src/Mod/Path/Gui/DlgSettingsPathColor.cpp b/src/Mod/Path/Gui/DlgSettingsPathColor.cpp index 282018c81a..ed1e365af5 100644 --- a/src/Mod/Path/Gui/DlgSettingsPathColor.cpp +++ b/src/Mod/Path/Gui/DlgSettingsPathColor.cpp @@ -67,6 +67,7 @@ void DlgSettingsPathColor::saveSettings() ui->DefaultBBoxNormalColor->onSave(); ui->DefaultSelectionStyle->onSave(); ui->DefaultTaskPanelLayout->onSave(); + ui->WarningSuppressAllSpeeds->onSave(); ui->WarningSuppressRapidSpeeds->onSave(); ui->WarningSuppressSelectionMode->onSave(); } @@ -85,6 +86,7 @@ void DlgSettingsPathColor::loadSettings() ui->DefaultBBoxNormalColor->onRestore(); ui->DefaultSelectionStyle->onRestore(); ui->DefaultTaskPanelLayout->onRestore(); + ui->WarningSuppressAllSpeeds->onRestore(); ui->WarningSuppressRapidSpeeds->onRestore(); ui->WarningSuppressSelectionMode->onRestore(); } diff --git a/src/Mod/Path/Gui/DlgSettingsPathColor.ui b/src/Mod/Path/Gui/DlgSettingsPathColor.ui index 9f0e630295..f3d7e8aa1f 100644 --- a/src/Mod/Path/Gui/DlgSettingsPathColor.ui +++ b/src/Mod/Path/Gui/DlgSettingsPathColor.ui @@ -7,7 +7,7 @@ 0 0 512 - 633 + 691 @@ -455,14 +455,36 @@ Warnings + + + + Suppress all warnings about setting speed rates for accurate cycle time calculation + + + Suppress all missing speeds warning + + + true + + + WarningSuppressAllSpeeds + + + Mod/Path + + + - Suppress warning about setting the rapid speed rates for accurate cycle time calculation + Suppress warning about setting the rapid speed rates for accurate cycle time calculation. Ignored if all speed warnings are already suppressed. Suppress missing rapid speeds warning + + true + WarningSuppressRapidSpeeds @@ -479,6 +501,9 @@ Suppress selection mode warning + + true + WarningSuppressSelectionMode diff --git a/src/Mod/Path/PathScripts/PathOp.py b/src/Mod/Path/PathScripts/PathOp.py index a618d9f026..8b2679a295 100644 --- a/src/Mod/Path/PathScripts/PathOp.py +++ b/src/Mod/Path/PathScripts/PathOp.py @@ -548,7 +548,7 @@ class ObjectOp(object): hRapidrate = tc.HorizRapid.Value vRapidrate = tc.VertRapid.Value - if hFeedrate == 0 or vFeedrate == 0: + if (hFeedrate == 0 or vFeedrate == 0) and not PathPreferences.suppressAllSpeedsWarning(): PathLog.warning(translate("Path", "Tool Controller feedrates required to calculate the cycle time.")) return translate('Path', 'Feedrate Error') diff --git a/src/Mod/Path/PathScripts/PathPreferences.py b/src/Mod/Path/PathScripts/PathPreferences.py index ee6c842c43..d914568631 100644 --- a/src/Mod/Path/PathScripts/PathPreferences.py +++ b/src/Mod/Path/PathScripts/PathPreferences.py @@ -29,38 +29,39 @@ import PathScripts.PathLog as PathLog # PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # PathLog.trackModule() -DefaultFilePath = "DefaultFilePath" -DefaultJobTemplate = "DefaultJobTemplate" -DefaultStockTemplate = "DefaultStockTemplate" -DefaultTaskPanelLayout = "DefaultTaskPanelLayout" +DefaultFilePath = "DefaultFilePath" +DefaultJobTemplate = "DefaultJobTemplate" +DefaultStockTemplate = "DefaultStockTemplate" +DefaultTaskPanelLayout = "DefaultTaskPanelLayout" -PostProcessorDefault = "PostProcessorDefault" -PostProcessorDefaultArgs = "PostProcessorDefaultArgs" -PostProcessorBlacklist = "PostProcessorBlacklist" -PostProcessorOutputFile = "PostProcessorOutputFile" -PostProcessorOutputPolicy = "PostProcessorOutputPolicy" +PostProcessorDefault = "PostProcessorDefault" +PostProcessorDefaultArgs = "PostProcessorDefaultArgs" +PostProcessorBlacklist = "PostProcessorBlacklist" +PostProcessorOutputFile = "PostProcessorOutputFile" +PostProcessorOutputPolicy = "PostProcessorOutputPolicy" -LastPathToolBit = "LastPathToolBit" -LastPathToolLibrary = "LastPathToolLibrary" -LastPathToolShape = "LastPathToolShape" -LastPathToolTable = "LastPathToolTable" +LastPathToolBit = "LastPathToolBit" +LastPathToolLibrary = "LastPathToolLibrary" +LastPathToolShape = "LastPathToolShape" +LastPathToolTable = "LastPathToolTable" -LastFileToolBit = "LastFileToolBit" -LastFileToolLibrary = "LastFileToolLibrary" -LastFileToolShape = "LastFileToolShape" +LastFileToolBit = "LastFileToolBit" +LastFileToolLibrary = "LastFileToolLibrary" +LastFileToolShape = "LastFileToolShape" -UseLegacyTools = "UseLegacyTools" -UseAbsoluteToolPaths = "UseAbsoluteToolPaths" -OpenLastLibrary = "OpenLastLibrary" +UseLegacyTools = "UseLegacyTools" +UseAbsoluteToolPaths = "UseAbsoluteToolPaths" +OpenLastLibrary = "OpenLastLibrary" # Linear tolerance to use when generating Paths, eg when tessellating geometry -GeometryTolerance = "GeometryTolerance" -LibAreaCurveAccuracy = "LibAreaCurveAccuarcy" +GeometryTolerance = "GeometryTolerance" +LibAreaCurveAccuracy = "LibAreaCurveAccuarcy" -WarningSuppressRapidSpeeds = "WarningSuppressRapidSpeeds" -WarningSuppressSelectionMode = "WarningSuppressSelectionMode" -WarningSuppressOpenCamLib = "WarningSuppressOpenCamLib" -EnableExperimentalFeatures = "EnableExperimentalFeatures" +WarningSuppressRapidSpeeds = "WarningSuppressRapidSpeeds" +WarningSuppressAllSpeeds = "WarningSuppressAllSpeeds" +WarningSuppressSelectionMode = "WarningSuppressSelectionMode" +WarningSuppressOpenCamLib = "WarningSuppressOpenCamLib" +EnableExperimentalFeatures = "EnableExperimentalFeatures" def preferences(): @@ -262,14 +263,17 @@ def setDefaultTaskPanelLayout(style): def experimentalFeaturesEnabled(): return preferences().GetBool(EnableExperimentalFeatures, False) +def suppressAllSpeedsWarning(): + return preferences().GetBool(WarningSuppressAllSpeeds, True) + def suppressRapidSpeedsWarning(): - return preferences().GetBool(WarningSuppressRapidSpeeds, False) + return suppressAllSpeedsWarning() or preferences().GetBool(WarningSuppressRapidSpeeds, True) def suppressSelectionModeWarning(): - return preferences().GetBool(WarningSuppressSelectionMode, False) + return preferences().GetBool(WarningSuppressSelectionMode, True) def suppressOpenCamLibWarning(): - return preferences().GetBool(WarningSuppressOpenCamLib, False) + return preferences().GetBool(WarningSuppressOpenCamLib, True) def lastFileToolLibrary(): From 698414058495344399d53b747f10309dd9b7f413 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 15 Dec 2020 20:09:55 -0800 Subject: [PATCH 06/65] Disabled '... postprocessor loaded' messages --- src/Mod/Path/PathScripts/post/centroid_post.py | 2 +- src/Mod/Path/PathScripts/post/dumper_post.py | 2 +- src/Mod/Path/PathScripts/post/dynapath_post.py | 2 +- src/Mod/Path/PathScripts/post/example_post.py | 2 +- src/Mod/Path/PathScripts/post/fablin_post.py | 2 +- src/Mod/Path/PathScripts/post/grbl_post.py | 2 +- src/Mod/Path/PathScripts/post/jtech_post.py | 2 +- src/Mod/Path/PathScripts/post/linuxcnc_post.py | 2 +- src/Mod/Path/PathScripts/post/mach3_mach4_post.py | 2 +- src/Mod/Path/PathScripts/post/marlin_post.py | 2 +- src/Mod/Path/PathScripts/post/opensbp_post.py | 2 +- src/Mod/Path/PathScripts/post/rml_post.py | 2 +- src/Mod/Path/PathScripts/post/smoothie_post.py | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Mod/Path/PathScripts/post/centroid_post.py b/src/Mod/Path/PathScripts/post/centroid_post.py index 002a325ab1..747d65792a 100644 --- a/src/Mod/Path/PathScripts/post/centroid_post.py +++ b/src/Mod/Path/PathScripts/post/centroid_post.py @@ -324,4 +324,4 @@ def parse(pathobj): return out -print(__name__ + " gcode postprocessor loaded.") +# print(__name__ + " gcode postprocessor loaded.") diff --git a/src/Mod/Path/PathScripts/post/dumper_post.py b/src/Mod/Path/PathScripts/post/dumper_post.py index 197d2f8ced..efa8262e07 100644 --- a/src/Mod/Path/PathScripts/post/dumper_post.py +++ b/src/Mod/Path/PathScripts/post/dumper_post.py @@ -94,4 +94,4 @@ def parse(pathobj): out += str(c) + "\n" return out -print(__name__ + " gcode postprocessor loaded.") +# print(__name__ + " gcode postprocessor loaded.") diff --git a/src/Mod/Path/PathScripts/post/dynapath_post.py b/src/Mod/Path/PathScripts/post/dynapath_post.py index f59ee7c40e..20fd1c65b2 100644 --- a/src/Mod/Path/PathScripts/post/dynapath_post.py +++ b/src/Mod/Path/PathScripts/post/dynapath_post.py @@ -367,4 +367,4 @@ def parse(pathobj): return out -print(__name__ + " gcode postprocessor loaded.") +# print(__name__ + " gcode postprocessor loaded.") diff --git a/src/Mod/Path/PathScripts/post/example_post.py b/src/Mod/Path/PathScripts/post/example_post.py index 4509b4d216..8163008fd9 100644 --- a/src/Mod/Path/PathScripts/post/example_post.py +++ b/src/Mod/Path/PathScripts/post/example_post.py @@ -100,4 +100,4 @@ def parse(inputstring): print("done postprocessing.") return output -print(__name__ + " gcode postprocessor loaded.") +# print(__name__ + " gcode postprocessor loaded.") diff --git a/src/Mod/Path/PathScripts/post/fablin_post.py b/src/Mod/Path/PathScripts/post/fablin_post.py index 3b6aca293a..fd96b4474e 100644 --- a/src/Mod/Path/PathScripts/post/fablin_post.py +++ b/src/Mod/Path/PathScripts/post/fablin_post.py @@ -300,4 +300,4 @@ def parse(pathobj): return out -print(__name__ + " gcode postprocessor loaded.") +# print(__name__ + " gcode postprocessor loaded.") diff --git a/src/Mod/Path/PathScripts/post/grbl_post.py b/src/Mod/Path/PathScripts/post/grbl_post.py index 47e3c92c87..c32fe1396b 100755 --- a/src/Mod/Path/PathScripts/post/grbl_post.py +++ b/src/Mod/Path/PathScripts/post/grbl_post.py @@ -570,4 +570,4 @@ def drill_translate(outstring, cmd, params): return trBuff -print(__name__ + ": GCode postprocessor loaded.") +# print(__name__ + ": GCode postprocessor loaded.") diff --git a/src/Mod/Path/PathScripts/post/jtech_post.py b/src/Mod/Path/PathScripts/post/jtech_post.py index ef83220551..ff9825dc46 100644 --- a/src/Mod/Path/PathScripts/post/jtech_post.py +++ b/src/Mod/Path/PathScripts/post/jtech_post.py @@ -333,4 +333,4 @@ def parse(pathobj): return out -print(__name__ + " gcode postprocessor loaded.") +# print(__name__ + " gcode postprocessor loaded.") diff --git a/src/Mod/Path/PathScripts/post/linuxcnc_post.py b/src/Mod/Path/PathScripts/post/linuxcnc_post.py index 691b018f85..6f22d93142 100644 --- a/src/Mod/Path/PathScripts/post/linuxcnc_post.py +++ b/src/Mod/Path/PathScripts/post/linuxcnc_post.py @@ -398,4 +398,4 @@ def parse(pathobj): return out -print(__name__ + " gcode postprocessor loaded.") +# print(__name__ + " gcode postprocessor loaded.") diff --git a/src/Mod/Path/PathScripts/post/mach3_mach4_post.py b/src/Mod/Path/PathScripts/post/mach3_mach4_post.py index cb1392ab95..f2ca58259d 100644 --- a/src/Mod/Path/PathScripts/post/mach3_mach4_post.py +++ b/src/Mod/Path/PathScripts/post/mach3_mach4_post.py @@ -434,4 +434,4 @@ def parse(pathobj): return out -print(__name__ + " gcode postprocessor loaded.") \ No newline at end of file +# print(__name__ + " gcode postprocessor loaded.") diff --git a/src/Mod/Path/PathScripts/post/marlin_post.py b/src/Mod/Path/PathScripts/post/marlin_post.py index 5e4c6ea02d..44fad8210e 100644 --- a/src/Mod/Path/PathScripts/post/marlin_post.py +++ b/src/Mod/Path/PathScripts/post/marlin_post.py @@ -763,7 +763,7 @@ def drill_translate(outlist, cmd, params): return Drill.gcode -print(__name__ + ': GCode postprocessor loaded.') +# print(__name__ + ': GCode postprocessor loaded.') # PEP8 format passed using: http://pep8online.com/, which primarily covers # indentation and line length. Some other aspects of PEP8 which have not diff --git a/src/Mod/Path/PathScripts/post/opensbp_post.py b/src/Mod/Path/PathScripts/post/opensbp_post.py index b99c86602f..9a33ddec30 100644 --- a/src/Mod/Path/PathScripts/post/opensbp_post.py +++ b/src/Mod/Path/PathScripts/post/opensbp_post.py @@ -364,4 +364,4 @@ def linenumber(): return "" -print(__name__ + " gcode postprocessor loaded.") +# print(__name__ + " gcode postprocessor loaded.") diff --git a/src/Mod/Path/PathScripts/post/rml_post.py b/src/Mod/Path/PathScripts/post/rml_post.py index fd28d957f4..ad2c83c2f5 100644 --- a/src/Mod/Path/PathScripts/post/rml_post.py +++ b/src/Mod/Path/PathScripts/post/rml_post.py @@ -256,5 +256,5 @@ def parse(inputstring): return '\n'.join(output) -print (__name__ + " gcode postprocessor loaded.") +# print (__name__ + " gcode postprocessor loaded.") diff --git a/src/Mod/Path/PathScripts/post/smoothie_post.py b/src/Mod/Path/PathScripts/post/smoothie_post.py index 8ce7d99a45..34d41b3bab 100644 --- a/src/Mod/Path/PathScripts/post/smoothie_post.py +++ b/src/Mod/Path/PathScripts/post/smoothie_post.py @@ -425,4 +425,4 @@ def parse(pathobj): return out -print(__name__ + " gcode postprocessor loaded.") +# print(__name__ + " gcode postprocessor loaded.") From 354af74b61e717069eedd24b76d3c6aabc4b6597 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 15 Dec 2020 20:11:40 -0800 Subject: [PATCH 07/65] Disabled path wb dis-/activated msgs --- src/Mod/Path/InitGui.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 3b7f906e91..e26c2fe0da 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -168,10 +168,11 @@ class PathWorkbench (Workbench): def Activated(self): # update the translation engine FreeCADGui.updateLocale() - Msg("Path workbench activated\n") + # Msg("Path workbench activated\n") def Deactivated(self): - Msg("Path workbench deactivated\n") + # Msg("Path workbench deactivated\n") + pass def ContextMenu(self, recipient): import PathScripts From 5049a98748e257867aab7ea3bd72de55daa0cb93 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 15 Dec 2020 20:16:06 -0800 Subject: [PATCH 08/65] Changed print to debug msg --- src/Mod/Path/PathScripts/PathMillFace.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathMillFace.py b/src/Mod/Path/PathScripts/PathMillFace.py index e87a24e80b..a932b3efbc 100644 --- a/src/Mod/Path/PathScripts/PathMillFace.py +++ b/src/Mod/Path/PathScripts/PathMillFace.py @@ -41,12 +41,8 @@ __doc__ = "Class and implementation of Mill Facing operation." __contributors__ = "russ4262 (Russell Johnson)" -DEBUG = False -if DEBUG: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule() -else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +# PathLog.trackModule() # Qt translation handling @@ -82,7 +78,7 @@ class ObjectFace(PathPocketBase.ObjectPocket): obj.OpStartDepth = job.Stock.Shape.BoundBox.ZMax if len(obj.Base) >= 1: - print('processing') + PathLog.debug('processing') sublist = [] for i in obj.Base: o = i[0] From bb747142350890f9a1456c226deb625071cebba7 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 15 Dec 2020 21:55:27 -0800 Subject: [PATCH 09/65] Print deburr tool info only once. --- src/Mod/Path/PathScripts/PathDeburr.py | 45 ++++++++++------ src/Mod/Path/PathTests/TestPathDeburr.py | 67 +++++++++++++++++++++--- 2 files changed, 88 insertions(+), 24 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDeburr.py b/src/Mod/Path/PathScripts/PathDeburr.py index 4f23603d05..ba530dc366 100644 --- a/src/Mod/Path/PathScripts/PathDeburr.py +++ b/src/Mod/Path/PathScripts/PathDeburr.py @@ -23,6 +23,7 @@ import FreeCAD import PathScripts.PathEngraveBase as PathEngraveBase +import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathOpTools as PathOpTools @@ -48,35 +49,44 @@ def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) -def toolDepthAndOffset(width, extraDepth, tool): +def toolDepthAndOffset(width, extraDepth, tool, printInfo): '''toolDepthAndOffset(width, extraDepth, tool) ... return tuple for given\n parameters.''' if not hasattr(tool, 'Diameter'): raise ValueError('Deburr requires tool with diameter\n') - if not hasattr(tool, 'CuttingEdgeAngle'): - angle = 180 - FreeCAD.Console.PrintMessage('The selected tool has No CuttingEdgeAngle property. Assuming Endmill\n') - else: + suppressInfo = False + if hasattr(tool, 'CuttingEdgeAngle'): angle = float(tool.CuttingEdgeAngle) - - if not hasattr(tool, 'FlatRadius'): - toolOffset = float(tool.Diameter / 2) - FreeCAD.Console.PrintMessage('The selected tool has no FlatRadius property. Using Diameter\n') + if PathGeom.isRoughly(angle, 180) or PathGeom.isRoughly(angle, 0): + angle = 180 + toolOffset = float(tool.Diameter) / 2 + else: + if hasattr(tool, 'TipDiameter'): + toolOffset = float(tool.TipDiameter) / 2 + elif hasattr(tool, 'FlatRadius'): + toolOffset = float(tool.FlatRadius) + else: + toolOffset = 0.0 + if printInfo and not suppressInfo: + FreeCAD.Console.PrintMessage(translate('PathDeburr', "The selected tool has no FlatRadius and no TipDiameter property. Assuming {}\n").format("Endmill" if angle == 180 else "V-Bit")) + suppressInfo = True else: - toolOffset = float(tool.FlatRadius) - - if angle == 0: angle = 180 + toolOffset = float(tool.Diameter) / 2 + if printInfo: + FreeCAD.Console.PrintMessage(translate('PathDeburr', 'The selected tool has no CuttingEdgeAngle property. Assuming Endmill\n')) + suppressInfo = True + tan = math.tan(math.radians(angle / 2)) - toolDepth = 0 if 0 == tan else width / tan + toolDepth = 0 if PathGeom.isRoughly(tan, 0) else width / tan depth = toolDepth + extraDepth - extraOffset = float(tool.Diameter) / 2 - width if angle == 180 else extraDepth / tan + extraOffset = -width if angle == 180 else (extraDepth / tan) offset = toolOffset + extraOffset - return (depth, offset) + return (depth, offset, suppressInfo) class ObjectDeburr(PathEngraveBase.ObjectOp): @@ -110,8 +120,11 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): def opExecute(self, obj): PathLog.track(obj.Label) + if not hasattr(self, 'printInfo'): + self.printInfo = True try: - (depth, offset) = toolDepthAndOffset(obj.Width.Value, obj.ExtraDepth.Value, self.tool) + (depth, offset, suppressInfo) = toolDepthAndOffset(obj.Width.Value, obj.ExtraDepth.Value, self.tool, self.printInfo) + self.printInfo = not suppressInfo except ValueError as e: msg = "{} \n No path will be generated".format(e) raise ValueError(msg) diff --git a/src/Mod/Path/PathTests/TestPathDeburr.py b/src/Mod/Path/PathTests/TestPathDeburr.py index 48692a4e48..901851143b 100644 --- a/src/Mod/Path/PathTests/TestPathDeburr.py +++ b/src/Mod/Path/PathTests/TestPathDeburr.py @@ -38,16 +38,18 @@ class TestPathDeburr(PathTestUtils.PathTestBase): tool.FlatRadius = 0 tool.CuttingEdgeAngle = 180 - (depth, offset) = PathDeburr.toolDepthAndOffset(1, 0.01, tool) + (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0.01, tool, True) self.assertRoughly(0.01, depth) self.assertRoughly(9, offset) + self.assertFalse(info) # legacy tools - no problem, same result tool.CuttingEdgeAngle = 0 - (depth, offset) = PathDeburr.toolDepthAndOffset(1, 0.01, tool) + (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0.01, tool, True) self.assertRoughly(0.01, depth) self.assertRoughly(9, offset) + self.assertFalse(info) def test01(self): '''Verify chamfer depth and offset for a 90° v-bit.''' @@ -55,13 +57,15 @@ class TestPathDeburr(PathTestUtils.PathTestBase): tool.FlatRadius = 0 tool.CuttingEdgeAngle = 90 - (depth, offset) = PathDeburr.toolDepthAndOffset(1, 0, tool) + (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0, tool, True) self.assertRoughly(1, depth) self.assertRoughly(0, offset) + self.assertFalse(info) - (depth, offset) = PathDeburr.toolDepthAndOffset(1, 0.2, tool) + (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0.2, tool, True) self.assertRoughly(1.2, depth) self.assertRoughly(0.2, offset) + self.assertFalse(info) def test02(self): '''Verify chamfer depth and offset for a 90° v-bit with non 0 flat radius.''' @@ -69,13 +73,15 @@ class TestPathDeburr(PathTestUtils.PathTestBase): tool.FlatRadius = 0.3 tool.CuttingEdgeAngle = 90 - (depth, offset) = PathDeburr.toolDepthAndOffset(1, 0, tool) + (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0, tool, True) self.assertRoughly(1, depth) self.assertRoughly(0.3, offset) + self.assertFalse(info) - (depth, offset) = PathDeburr.toolDepthAndOffset(2, 0.2, tool) + (depth, offset, info) = PathDeburr.toolDepthAndOffset(2, 0.2, tool, True) self.assertRoughly(2.2, depth) self.assertRoughly(0.5, offset) + self.assertFalse(info) def test03(self): '''Verify chamfer depth and offset for a 60° v-bit with non 0 flat radius.''' @@ -85,10 +91,55 @@ class TestPathDeburr(PathTestUtils.PathTestBase): td = 1.73205 - (depth, offset) = PathDeburr.toolDepthAndOffset(1, 0, tool) + (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0, tool, True) self.assertRoughly(td, depth) self.assertRoughly(10, offset) + self.assertFalse(info) - (depth, offset) = PathDeburr.toolDepthAndOffset(3, 1, tool) + (depth, offset, info) = PathDeburr.toolDepthAndOffset(3, 1, tool, True) self.assertRoughly(td * 3 + 1, depth) self.assertRoughly(10 + td, offset) + self.assertFalse(info) + + def test10(self): + '''Verify missing cutting endge angle info prints only once.''' + + class FakeEndmill(object): + def __init__(self, dia): + self.Diameter = dia + + tool = FakeEndmill(10) + (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0.1, tool, True) + self.assertRoughly(0.1, depth) + self.assertRoughly(4, offset) + self.assertTrue(info) + (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0.1, tool, not info) + self.assertRoughly(0.1, depth) + self.assertRoughly(4, offset) + self.assertTrue(info) + (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0.1, tool, not info) + self.assertRoughly(0.1, depth) + self.assertRoughly(4, offset) + self.assertTrue(info) + + def test11(self): + '''Verify missing tip diameter info prints only once.''' + + class FakePointyBit(object): + def __init__(self, dia, angle): + self.Diameter = dia + self.CuttingEdgeAngle = angle + + tool = FakePointyBit(10, 90) + (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0.1, tool, True) + self.assertRoughly(1.1, depth) + self.assertRoughly(0.1, offset) + self.assertTrue(info) + (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0.1, tool, not info) + self.assertRoughly(1.1, depth) + self.assertRoughly(0.1, offset) + self.assertTrue(info) + (depth, offset, info) = PathDeburr.toolDepthAndOffset(1, 0.1, tool, not info) + self.assertRoughly(1.1, depth) + self.assertRoughly(0.1, offset) + self.assertTrue(info) From 804b5e590ed6ee3dc9d80911c914431c21be4c09 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Thu, 17 Dec 2020 09:39:33 -0600 Subject: [PATCH 10/65] fixes 4466 --- .../Resources/panels/DlgJobTemplateExport.ui | 24 ++++++++- src/Mod/Path/PathScripts/PathJobCmd.py | 16 ++++-- src/Mod/Path/PathScripts/PathJobDlg.py | 21 ++++++-- src/Mod/Path/PathScripts/PathSetupSheet.py | 53 ++++++++++++------- 4 files changed, 85 insertions(+), 29 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/DlgJobTemplateExport.ui b/src/Mod/Path/Gui/Resources/panels/DlgJobTemplateExport.ui index fddb21dc45..0e55f17f46 100644 --- a/src/Mod/Path/Gui/Resources/panels/DlgJobTemplateExport.ui +++ b/src/Mod/Path/Gui/Resources/panels/DlgJobTemplateExport.ui @@ -119,6 +119,19 @@ + + + + <html><head/><body><p>Enable to include the default coolant mode in the template.</p></body></html> + + + Coolant Mode + + + true + + + @@ -192,7 +205,16 @@ - + + 0 + + + 0 + + + 0 + + 0 diff --git a/src/Mod/Path/PathScripts/PathJobCmd.py b/src/Mod/Path/PathScripts/PathJobCmd.py index 0a9efcb765..9b600dc32b 100644 --- a/src/Mod/Path/PathScripts/PathJobCmd.py +++ b/src/Mod/Path/PathScripts/PathJobCmd.py @@ -33,12 +33,15 @@ import os from PySide import QtCore, QtGui + # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) + class CommandJobCreate: ''' @@ -87,7 +90,6 @@ class CommandJobTemplateExport: on Job creation and be available for selection. ''' - def __init__(self): pass @@ -111,7 +113,6 @@ class CommandJobTemplateExport: return job return None - def IsActive(self): return self.GetJob() is not None @@ -163,7 +164,12 @@ class CommandJobTemplateExport: # setup sheet setupSheetAttrs = None if dialog: - setupSheetAttrs = job.Proxy.setupSheet.templateAttributes(dialog.includeSettingToolRapid(), dialog.includeSettingOperationHeights(), dialog.includeSettingOperationDepths(), dialog.includeSettingOpsSettings()) + setupSheetAttrs = job.Proxy.setupSheet.templateAttributes( + dialog.includeSettingToolRapid(), + dialog.includeSettingCoolant(), + dialog.includeSettingOperationHeights(), + dialog.includeSettingOperationDepths(), + dialog.includeSettingOpsSettings()) else: setupSheetAttrs = job.Proxy.setupSheet.templateAttributes(True, True, True) if setupSheetAttrs: @@ -174,10 +180,10 @@ class CommandJobTemplateExport: with open(PathUtil.toUnicode(path), 'w') as fp: json.dump(encoded, fp, sort_keys=True, indent=2) + if FreeCAD.GuiUp: # register the FreeCAD command FreeCADGui.addCommand('Path_Job', CommandJobCreate()) FreeCADGui.addCommand('Path_ExportTemplate', CommandJobTemplateExport()) FreeCAD.Console.PrintLog("Loading PathJobCmd... done\n") - diff --git a/src/Mod/Path/PathScripts/PathJobDlg.py b/src/Mod/Path/PathScripts/PathJobDlg.py index 6a303caa83..a28466eb3c 100644 --- a/src/Mod/Path/PathScripts/PathJobDlg.py +++ b/src/Mod/Path/PathScripts/PathJobDlg.py @@ -32,12 +32,15 @@ import os from PySide import QtCore, QtGui from collections import Counter + # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) + class _ItemDelegate(QtGui.QStyledItemDelegate): @@ -148,7 +151,6 @@ class JobCreate: self.model = QtGui.QStandardItemModel(self.dialog) self.model.setHorizontalHeaderLabels(['Model', 'Count']) - if self.itemsSolid.hasChildren(): self.model.appendRow(self.itemsSolid) if expandSolids or not (expand2Ds or expandJobs): @@ -244,7 +246,7 @@ class JobCreate: models = [] for i in range(self.itemsSolid.rowCount()): - for j in range(self.itemsSolid.child(i, 1).data(QtCore.Qt.EditRole)): # pylint: disable=unused-variable + for j in range(self.itemsSolid.child(i, 1).data(QtCore.Qt.EditRole)): # pylint: disable=unused-variable models.append(self.itemsSolid.child(i).data(self.DataObject)) for i in range(self.items2D.rowCount()): @@ -320,12 +322,14 @@ class JobTemplateExport: rapidChanged = not job.SetupSheet.Proxy.hasDefaultToolRapids() depthsChanged = not job.SetupSheet.Proxy.hasDefaultOperationDepths() heightsChanged = not job.SetupSheet.Proxy.hasDefaultOperationHeights() + coolantChanged = not job.SetupSheet.Proxy.hasDefaultCoolantMode() opsWithSettings = job.SetupSheet.Proxy.operationsWithSettings() - settingsChanged = rapidChanged or depthsChanged or heightsChanged or 0 != len(opsWithSettings) + settingsChanged = rapidChanged or depthsChanged or heightsChanged or coolantChanged or 0 != len(opsWithSettings) self.dialog.settingsGroup.setChecked(settingsChanged) self.dialog.settingToolRapid.setChecked(rapidChanged) self.dialog.settingOperationDepths.setChecked(depthsChanged) self.dialog.settingOperationHeights.setChecked(heightsChanged) + self.dialog.settingCoolant.setChecked(coolantChanged) self.dialog.settingsOpsList.clear() for op in opsWithSettings: @@ -358,20 +362,28 @@ class JobTemplateExport: def includeStock(self): return self.dialog.stockGroup.isChecked() + def includeStockExtent(self): return self.dialog.stockExtent.isChecked() + def includeStockPlacement(self): return self.dialog.stockPlacement.isChecked() def includeSettings(self): return self.dialog.settingsGroup.isChecked() + def includeSettingToolRapid(self): return self.dialog.settingToolRapid.isChecked() + def includeSettingOperationHeights(self): return self.dialog.settingOperationHeights.isChecked() + def includeSettingOperationDepths(self): return self.dialog.settingOperationDepths.isChecked() + def includeSettingCoolant(self): + return self.dialog.settingCoolant.isChecked() + def includeSettingOpsSettings(self): ops = [] for i in range(self.dialog.settingsOpsList.count()): @@ -382,4 +394,3 @@ class JobTemplateExport: def exec_(self): return self.dialog.exec_() - diff --git a/src/Mod/Path/PathScripts/PathSetupSheet.py b/src/Mod/Path/PathScripts/PathSetupSheet.py index 59f70967ac..87203d6a95 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheet.py +++ b/src/Mod/Path/PathScripts/PathSetupSheet.py @@ -35,11 +35,13 @@ __doc__ = "A container for all default values and job specific configuration val _RegisteredOps = {} PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) + def translate(context, text, disambig=None): return PySide.QtCore.QCoreApplication.translate(context, text, disambig) + class Template: # pylint: disable=no-init @@ -59,7 +61,7 @@ class Template: def _traverseTemplateAttributes(attrs, codec): coded = {} - for key,value in PathUtil.keyValueIter(attrs): + for key, value in PathUtil.keyValueIter(attrs): if type(value) == dict: PathLog.debug("%s is a dict" % key) coded[key] = _traverseTemplateAttributes(value, codec) @@ -74,6 +76,7 @@ def _traverseTemplateAttributes(attrs, codec): coded[key] = value return coded + class SetupSheet: '''Property container object used by a Job to hold global reference values. ''' @@ -92,20 +95,20 @@ class SetupSheet: def __init__(self, obj): self.obj = obj - obj.addProperty('App::PropertySpeed', 'VertRapid', 'ToolController', translate('PathSetupSheet', 'Default speed for horizontal rapid moves.')) + obj.addProperty('App::PropertySpeed', 'VertRapid', 'ToolController', translate('PathSetupSheet', 'Default speed for horizontal rapid moves.')) obj.addProperty('App::PropertySpeed', 'HorizRapid', 'ToolController', translate('PathSetupSheet', 'Default speed for vertical rapid moves.')) obj.addProperty('App::PropertyStringList', 'CoolantModes', 'CoolantMode', translate('PathSetupSheet', 'Coolant Modes')) obj.addProperty('App::PropertyEnumeration', 'CoolantMode', 'CoolantMode', translate('PathSetupSheet', 'Default coolant mode.')) - obj.addProperty('App::PropertyLength', 'SafeHeightOffset', 'OperationHeights', translate('PathSetupSheet', 'The usage of this field depends on SafeHeightExpression - by default its value is added to StartDepth and used for SafeHeight of an operation.')) - obj.addProperty('App::PropertyString', 'SafeHeightExpression', 'OperationHeights', translate('PathSetupSheet', 'Expression set for the SafeHeight of new operations.')) - obj.addProperty('App::PropertyLength', 'ClearanceHeightOffset', 'OperationHeights', translate('PathSetupSheet', 'The usage of this field depends on ClearanceHeightExpression - by default is value is added to StartDepth and used for ClearanceHeight of an operation.')) + obj.addProperty('App::PropertyLength', 'SafeHeightOffset', 'OperationHeights', translate('PathSetupSheet', 'The usage of this field depends on SafeHeightExpression - by default its value is added to StartDepth and used for SafeHeight of an operation.')) + obj.addProperty('App::PropertyString', 'SafeHeightExpression', 'OperationHeights', translate('PathSetupSheet', 'Expression set for the SafeHeight of new operations.')) + obj.addProperty('App::PropertyLength', 'ClearanceHeightOffset', 'OperationHeights', translate('PathSetupSheet', 'The usage of this field depends on ClearanceHeightExpression - by default is value is added to StartDepth and used for ClearanceHeight of an operation.')) obj.addProperty('App::PropertyString', 'ClearanceHeightExpression', 'OperationHeights', translate('PathSetupSheet', 'Expression set for the ClearanceHeight of new operations.')) obj.addProperty('App::PropertyString', 'StartDepthExpression', 'OperationDepths', translate('PathSetupSheet', 'Expression used for StartDepth of new operations.')) obj.addProperty('App::PropertyString', 'FinalDepthExpression', 'OperationDepths', translate('PathSetupSheet', 'Expression used for FinalDepth of new operations.')) - obj.addProperty('App::PropertyString', 'StepDownExpression', 'OperationDepths', translate('PathSetupSheet', 'Expression used for StepDown of new operations.')) + obj.addProperty('App::PropertyString', 'StepDownExpression', 'OperationDepths', translate('PathSetupSheet', 'Expression used for StepDown of new operations.')) obj.SafeHeightOffset = self.decodeAttributeString(self.DefaultSafeHeightOffset) obj.ClearanceHeightOffset = self.decodeAttributeString(self.DefaultClearanceHeightOffset) @@ -154,31 +157,38 @@ class SetupSheet: return False return True + def hasDefaultCoolantMode(self): + return self.obj.CoolantMode == "None" + def setFromTemplate(self, attrs): '''setFromTemplate(attrs) ... sets the default values from the given dictionary.''' for name in Template.All: if attrs.get(name) is not None: setattr(self.obj, name, attrs[name]) - for opName,op in PathUtil.keyValueIter(_RegisteredOps): + for opName, op in PathUtil.keyValueIter(_RegisteredOps): opSetting = attrs.get(opName) if opSetting is not None: prototype = op.prototype(opName) for propName in op.properties(): value = opSetting.get(propName) - if not value is None: + if value is not None: prop = prototype.getProperty(propName) propertyName = OpPropertyName(opName, propName) propertyGroup = OpPropertyGroup(opName) prop.setupProperty(self.obj, propertyName, propertyGroup, prop.valueFromString(value)) - - def templateAttributes(self, includeRapids=True, includeCoolantMode=True, includeHeights=True, includeDepths=True, includeOps=None): + def templateAttributes(self, + includeRapids=True, + includeCoolantMode=True, + includeHeights=True, + includeDepths=True, + includeOps=None): '''templateAttributes(includeRapids, includeHeights, includeDepths) ... answers a dictionary with the default values.''' attrs = {} if includeRapids: - attrs[Template.VertRapid] = self.obj.VertRapid.UserString + attrs[Template.VertRapid] = self.obj.VertRapid.UserString attrs[Template.HorizRapid] = self.obj.HorizRapid.UserString if includeCoolantMode: @@ -232,6 +242,7 @@ class SetupSheet: def encodeAttributeString(self, attr): '''encodeAttributeString(attr) ... return the encoded string of a template attribute.''' return PathUtil.toUnicode(attr.replace(self.expressionReference(), self.TemplateReference)) + def decodeAttributeString(self, attr): '''decodeAttributeString(attr) ... return the decoded string of a template attribute.''' return PathUtil.toUnicode(attr.replace(self.TemplateReference, self.expressionReference())) @@ -247,7 +258,7 @@ class SetupSheet: def operationsWithSettings(self): '''operationsWithSettings() ... returns a list of operations which currently have some settings defined.''' ops = [] - for name,value in PathUtil.keyValueIter(_RegisteredOps): + for name, value in PathUtil.keyValueIter(_RegisteredOps): for prop in value.registeredPropertyNames(name): if hasattr(self.obj, prop): ops.append(name) @@ -262,9 +273,9 @@ class SetupSheet: propName = OpPropertyName(opName, prop) if hasattr(self.obj, propName): setattr(obj, prop, getattr(self.obj, propName)) - except Exception: # pylint: disable=broad-except + except Exception: PathLog.info("SetupSheet has no support for {}".format(opName)) - #traceback.print_exc() + # traceback.print_exc() def onDocumentRestored(self, obj): @@ -272,16 +283,17 @@ class SetupSheet: obj.addProperty('App::PropertyStringList', 'CoolantModes', 'CoolantMode', translate('PathSetupSheet', 'Coolant Modes')) obj.CoolantModes = self.DefaultCoolantModes - if not hasattr(obj, 'CoolantMode'): obj.addProperty('App::PropertyEnumeration', 'CoolantMode', 'CoolantMode', translate('PathSetupSheet', 'Default coolant mode.')) obj.CoolantMode = self.DefaultCoolantModes -def Create(name = 'SetupSheet'): + +def Create(name='SetupSheet'): obj = FreeCAD.ActiveDocument.addObject('App::FeaturePython', name) obj.Proxy = SetupSheet(obj) return obj + class _RegisteredOp(object): def __init__(self, factory, properties): @@ -296,14 +308,19 @@ class _RegisteredOp(object): self.factory("OpPrototype.%s" % name, ptt) return ptt + def RegisterOperation(name, objFactory, setupProperties): - global _RegisteredOps # pylint: disable=global-statement + global _RegisteredOps _RegisteredOps[name] = _RegisteredOp(objFactory, setupProperties) + def OpNamePrefix(name): return name.replace('Path', '').replace(' ', '').replace('_', '') + def OpPropertyName(opName, propName): return "{}{}".format(OpNamePrefix(opName), propName) + + def OpPropertyGroup(opName): return "Op {}".format(opName) From 6b5a99319a753876f588a2f75d4b9ff940462fe0 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 19:14:44 -0800 Subject: [PATCH 11/65] Only update the shape if it has changed. --- src/Mod/Path/PathScripts/PathToolBitEdit.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index 0ee54747bb..c9cf238ebd 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -194,12 +194,14 @@ class ToolBitEditor(object): def updateShape(self): PathLog.track() - self.tool.BitShape = str(self.form.shapePath.text()) - self.setupTool(self.tool) - self.form.toolName.setText(self.tool.Label) + shapePath = str(self.form.shapePath.text()) + if self.tool.BitShape != shapePath: + self.tool.BitShape = shapePath + self.setupTool(self.tool) + self.form.toolName.setText(self.tool.Label) - for editor in self.bitEditor: - self.bitEditor[editor].updateSpinBox() + for editor in self.bitEditor: + self.bitEditor[editor].updateSpinBox() def updateTool(self): PathLog.track() From 7e2f088833e0bc0222f6a3a644fdd0c8df1b012f Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 19:55:59 -0800 Subject: [PATCH 12/65] Hide document when loading a ToolBit. --- src/Mod/Path/PathScripts/PathToolBit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 9cd6f488de..4c8929b26f 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -252,7 +252,7 @@ class ToolBit(object): p = findShape(p) if not path and p != obj.BitShape: obj.BitShape = p - doc = FreeCAD.open(p) + doc = FreeCAD.openDocument(p, True) obj.ShapeName = doc.Name docOpened = True return (doc, docOpened) From 1a2249387e08ebe829118e7bc8849e50205e71bb Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 19:59:44 -0800 Subject: [PATCH 13/65] Make BitShape read/writeable, so the tool can be edited when shared to a different system --- src/Mod/Path/PathScripts/PathToolBit.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 4c8929b26f..4c502c528a 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -200,7 +200,9 @@ class ToolBit(object): return [prop for prop in obj.PropertiesList if obj.getGroupOfProperty(prop) == PropertyGroupAttribute] def onDocumentRestored(self, obj): - obj.setEditorMode('BitShape', 1) + # when files are shared it is essential to be able to change/set the shape file, + # otherwise the file is hard to use + # obj.setEditorMode('BitShape', 1) obj.setEditorMode('BitBody', 2) obj.setEditorMode('File', 1) obj.setEditorMode('Shape', 2) From be221dd25d7b540d91fa28374c73257d190947e1 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 20:27:42 -0800 Subject: [PATCH 14/65] Rearranged BitTool shape update so dependent ops don't execute with invalid tool. --- src/Mod/Path/PathScripts/PathToolBit.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 4c502c528a..5ffe7503dd 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -288,21 +288,22 @@ class ToolBit(object): self._removeBitBody(obj) def _setupBitShape(self, obj, path=None): + PathLog.track(obj.Label) + activeDoc = FreeCAD.ActiveDocument (doc, docOpened) = self._loadBitBody(obj, path) obj.Label = doc.RootObjects[0].Label self._deleteBitSetup(obj) - obj.BitBody = obj.Document.copyObject(doc.RootObjects[0], True) + bitBody = obj.Document.copyObject(doc.RootObjects[0], True) if docOpened: FreeCAD.setActiveDocument(activeDoc.Name) FreeCAD.closeDocument(doc.Name) - if obj.BitBody.ViewObject: - obj.BitBody.ViewObject.Visibility = False - self._copyBitShape(obj) + if bitBody.ViewObject: + bitBody.ViewObject.Visibility = False - for sketch in [o for o in obj.BitBody.Group if o.TypeId == 'Sketcher::SketchObject']: + 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) @@ -318,6 +319,9 @@ class ToolBit(object): if constraint.Type == 'Angle': value = value * 180 / math.pi PathUtil.setProperty(obj, prop, value) + # has to happen last because it could trigger op.execute evaluations + obj.BitBody = bitBody + self._copyBitShape(obj) def getBitThumbnail(self, obj): if obj.BitShape: From b2dce8c76a3c52e5941bf726a3256b62b9c346e1 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 22:56:42 -0800 Subject: [PATCH 15/65] Allowing QuantitySpinBox to be reused for different attribute; using properties to get values to work around build differences. --- src/Mod/Path/PathScripts/PathGui.py | 41 ++++++++++++++++------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathGui.py b/src/Mod/Path/PathScripts/PathGui.py index d7fdd6be32..c9fee42062 100644 --- a/src/Mod/Path/PathScripts/PathGui.py +++ b/src/Mod/Path/PathScripts/PathGui.py @@ -35,13 +35,8 @@ __doc__ = "A collection of helper and utility functions for the Path GUI." def translate(context, text, disambig=None): return PySide.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()) +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) def updateInputField(obj, prop, widget, onBeforeChange=None): @@ -53,7 +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). ''' - value = FreeCAD.Units.Quantity(widget.text()).Value + value = widget.property('rawValue') attr = PathUtil.getProperty(obj, prop) attrValue = attr.Value if hasattr(attr, 'Value') else attr @@ -72,10 +67,10 @@ def updateInputField(obj, prop, widget, onBeforeChange=None): isDiff = True break if noExpr: - widget.setReadOnly(False) + widget.setProperty('readonly', False) widget.setStyleSheet("color: black") else: - widget.setReadOnly(True) + widget.setProperty('readonly', True) widget.setStyleSheet("color: gray") widget.update() @@ -100,19 +95,26 @@ class QuantitySpinBox: ''' def __init__(self, widget, obj, prop, onBeforeChange=None): - self.obj = obj + PathLog.track(widget) self.widget = widget - self.prop = prop self.onBeforeChange = onBeforeChange + self.attachTo(obj, prop) - attr = PathUtil.getProperty(self.obj, self.prop) - if attr is not None: - if hasattr(attr, 'Value'): - widget.setProperty('unit', attr.getUserPreferred()[2]) - widget.setProperty('binding', "%s.%s" % (obj.Name, prop)) - self.valid = True + def attachTo(self, obj, prop = None): + '''attachTo(obj, prop=None) ... use an existing editor for the given object and property''' + self.obj = obj + self.prop = prop + if obj and prop: + attr = PathUtil.getProperty(obj, prop) + if attr is not None: + if hasattr(attr, 'Value'): + self.widget.setProperty('unit', attr.getUserPreferred()[2]) + self.widget.setProperty('binding', "%s.%s" % (obj.Name, prop)) + self.valid = True + else: + PathLog.warning(translate('PathGui', "Cannot find property %s of %s") % (prop, obj.Label)) + self.valid = False else: - PathLog.warning(translate('PathGui', "Cannot find property %s of %s") % (prop, obj.Label)) self.valid = False def expression(self): @@ -122,6 +124,7 @@ class QuantitySpinBox: return '' def setMinimum(self, quantity): + '''setMinimum(quantity) ... set the minimum''' if self.valid: value = quantity.Value if hasattr(quantity, 'Value') else quantity self.widget.setProperty('setMinimum', value) From 1f839e5ea11a62716142481f009aa12732aa728f Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 23:01:55 -0800 Subject: [PATCH 16/65] Reuse existing QuantitySpinBox'es in order to avoid segfault on focus change. --- src/Mod/Path/PathScripts/PathToolBitEdit.py | 67 ++++++++++++++++----- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index c9cf238ebd..3488bcc95d 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -62,24 +62,60 @@ class ToolBitEditor(object): if self.loadbitbody: self.tool.Proxy.loadBitBody(self.tool) + # remove example widgets + layout = self.form.bitParams.layout() + for i in range(layout.rowCount() - 1, -1, -1): + layout.removeRow(i) + # used to track property widgets and editors + self.widgets = [] + self.setupTool(self.tool) self.setupAttributes(self.tool) def setupTool(self, tool): PathLog.track() + # Can't delete and add fields to the form because of dangling references in case of + # a focus change. see https://forum.freecadweb.org/viewtopic.php?f=10&t=52246#p458583 + # Instead we keep widgets once created and use them for new properties, and hide all + # which aren't being needed anymore. + + def labelText(name): + return re.sub('([A-Z][a-z]+)', r' \1', re.sub('([A-Z]+)', r' \1', name)) + layout = self.form.bitParams.layout() - for i in range(layout.rowCount() - 1, -1, -1): - layout.removeRow(i) - editor = {} 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: - qsb = ui.createWidget('Gui::QuantitySpinBox') - editor[name] = PathGui.QuantitySpinBox(qsb, tool, name) - label = QtGui.QLabel(re.sub('([A-Z][a-z]+)', r' \1', - re.sub('([A-Z]+)', r' \1', name))) - layout.addRow(label, qsb) - self.bitEditor = editor + 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 + + # hide all rows which aren't being used + for i in range(nr, len(self.widgets)): + label, qsb, editor = self.widgets[i] + label.hide() + qsb.hide() + editor.attachTo(None) + PathLog.debug(" hide row: {}".format(i)) + img = tool.Proxy.getBitThumbnail(tool) if img: self.form.image.setPixmap(QtGui.QPixmap(QtGui.QImage.fromData(img))) @@ -189,27 +225,28 @@ class ToolBitEditor(object): self.form.toolName.setText(self.tool.Label) self.form.shapePath.setText(self.tool.BitShape) - for editor in self.bitEditor: - self.bitEditor[editor].updateSpinBox() + for lbl, qsb, editor in self.widgets: + editor.updateSpinBox() 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) - for editor in self.bitEditor: - self.bitEditor[editor].updateSpinBox() + for lbl, qsb, editor in self.widgets: + editor.updateSpinBox() def updateTool(self): PathLog.track() self.tool.Label = str(self.form.toolName.text()) self.tool.BitShape = str(self.form.shapePath.text()) - for editor in self.bitEditor: - self.bitEditor[editor].updateProperty() + for lbl, qsb, editor in self.widgets: + editor.updateProperty() # self.tool.Proxy._updateBitShape(self.tool) From 08f708d2bd2617a155e7e7866ab662ee04562de3 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 23:11:02 -0800 Subject: [PATCH 17/65] Added provision for gcc peculiarities in unit tests --- src/Mod/Path/PathTests/TestPathCore.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathTests/TestPathCore.py b/src/Mod/Path/PathTests/TestPathCore.py index dae7c091e5..34d84d6477 100644 --- a/src/Mod/Path/PathTests/TestPathCore.py +++ b/src/Mod/Path/PathTests/TestPathCore.py @@ -152,7 +152,9 @@ G0 Z0.500000 table.addTools(t2) self.assertEqual(len(table.Tools), 2) - self.assertEqual(str(table.Tools), '{1: Tool 12.7mm Drill Bit, 2: Tool my other tool}' ) + # gcc7 build needs some special treatment (makes 1L out of a 1) ... + if str(table.Tools) != '{1L: Tool 12.7mm Drill Bit, 2L: Tool my other tool}': + self.assertEqual(str(table.Tools), '{1: Tool 12.7mm Drill Bit, 2: Tool my other tool}') def test50(self): """Test Path.Length calculation""" From 0385e56b695754df1a7c4d645a96ba1d8b146555 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 18 Dec 2020 11:46:31 +0100 Subject: [PATCH 18/65] Gui: [skip ci] fix crash when using a dialog instance twice for a task panel --- src/Gui/TaskView/TaskDialogPython.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Gui/TaskView/TaskDialogPython.cpp b/src/Gui/TaskView/TaskDialogPython.cpp index b353fcb0a1..5a9cb4b864 100644 --- a/src/Gui/TaskView/TaskDialogPython.cpp +++ b/src/Gui/TaskView/TaskDialogPython.cpp @@ -337,8 +337,20 @@ TaskDialogPython::~TaskDialogPython() std::vector< QPointer > guarded; guarded.insert(guarded.begin(), Content.begin(), Content.end()); Content.clear(); + Base::PyGILStateLocker lock; + + // The widgets stored in the 'form' attribute will be deleted. + // Thus, set this attribute to None to make sure that when using + // the same dialog instance for a task panel won't segfault. + if (this->dlg.hasAttr(std::string("form"))) { + this->dlg.setAttr(std::string("form"), Py::None()); + } this->dlg = Py::None(); + + // Assigning None to 'dlg' may destroy some of the stored widgets. + // By guarding them with QPointer their pointers will be set to null + // so that the destructor of the base class can reliably call 'delete'. Content.insert(Content.begin(), guarded.begin(), guarded.end()); } From 7d260787b9abfc7452112040cb370fe17134cd65 Mon Sep 17 00:00:00 2001 From: Aapo Date: Tue, 15 Dec 2020 22:56:51 +0200 Subject: [PATCH 19/65] [TD] Balloon, change ordering of the data properties to more logical. --- src/Mod/TechDraw/App/DrawViewBalloon.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewBalloon.cpp b/src/Mod/TechDraw/App/DrawViewBalloon.cpp index 9f8a397c39..ed9f6ca837 100644 --- a/src/Mod/TechDraw/App/DrawViewBalloon.cpp +++ b/src/Mod/TechDraw/App/DrawViewBalloon.cpp @@ -99,15 +99,15 @@ DrawViewBalloon::DrawViewBalloon(void) EndType.setEnums(ArrowPropEnum::ArrowTypeEnums); ADD_PROPERTY(EndType,(prefEnd())); + ADD_PROPERTY_TYPE(EndTypeScale,(1.0),"",(App::PropertyType)(App::Prop_None),"EndType shape scale"); + ShapeScale.setConstraints(&SymbolScaleRange); + BubbleShape.setEnums(balloonTypeEnums); ADD_PROPERTY(BubbleShape,(prefShape())); ADD_PROPERTY_TYPE(ShapeScale,(1.0),"",(App::PropertyType)(App::Prop_None),"Balloon shape scale"); ShapeScale.setConstraints(&SymbolScaleRange); - ADD_PROPERTY_TYPE(EndTypeScale,(1.0),"",(App::PropertyType)(App::Prop_None),"EndType shape scale"); - ShapeScale.setConstraints(&SymbolScaleRange); - ADD_PROPERTY_TYPE(TextWrapLen,(-1),"",(App::PropertyType)(App::Prop_None),"Text wrap length; -1 means no wrap"); ADD_PROPERTY_TYPE(KinkLength,(prefKinkLength()),"",(App::PropertyType)(App::Prop_None), From 294136e9bd2ce5695534190d141eebd3567a13e3 Mon Sep 17 00:00:00 2001 From: Aapo Date: Tue, 15 Dec 2020 23:32:11 +0200 Subject: [PATCH 20/65] [TD] Balloon, move property LineVisible to View tab and fix a refresh bug. --- src/Mod/TechDraw/App/DrawViewBalloon.cpp | 2 -- src/Mod/TechDraw/App/DrawViewBalloon.h | 1 - src/Mod/TechDraw/Gui/QGIViewBalloon.cpp | 2 +- src/Mod/TechDraw/Gui/ViewProviderBalloon.cpp | 4 +++- src/Mod/TechDraw/Gui/ViewProviderBalloon.h | 1 + 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewBalloon.cpp b/src/Mod/TechDraw/App/DrawViewBalloon.cpp index ed9f6ca837..2f40d08e37 100644 --- a/src/Mod/TechDraw/App/DrawViewBalloon.cpp +++ b/src/Mod/TechDraw/App/DrawViewBalloon.cpp @@ -113,8 +113,6 @@ DrawViewBalloon::DrawViewBalloon(void) ADD_PROPERTY_TYPE(KinkLength,(prefKinkLength()),"",(App::PropertyType)(App::Prop_None), "Distance from symbol to leader kink"); - ADD_PROPERTY_TYPE(LineVisible,(true),"",(App::PropertyType)(App::Prop_None),"Balloon line visible or hidden"); - SourceView.setScope(App::LinkScope::Global); Rotation.setStatus(App::Property::Hidden,true); Caption.setStatus(App::Property::Hidden,true); diff --git a/src/Mod/TechDraw/App/DrawViewBalloon.h b/src/Mod/TechDraw/App/DrawViewBalloon.h index 0892aac32a..ac72ae8699 100644 --- a/src/Mod/TechDraw/App/DrawViewBalloon.h +++ b/src/Mod/TechDraw/App/DrawViewBalloon.h @@ -59,7 +59,6 @@ public: App::PropertyDistance OriginY; App::PropertyFloat TextWrapLen; App::PropertyDistance KinkLength; - App::PropertyBool LineVisible; short mustExecute() const override; diff --git a/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp b/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp index 1b7b062a0e..205cbfe093 100644 --- a/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp @@ -766,7 +766,7 @@ void QGIViewBalloon::draw() balloonLines->setPath(dLinePath); // This overwrites the previously created QPainterPath with empty one, in case it should be hidden. Should be refactored. - if (!balloon->LineVisible.getValue()) { + if (!vp->LineVisible.getValue()) { arrow->hide(); balloonLines->setPath(QPainterPath()); } diff --git a/src/Mod/TechDraw/Gui/ViewProviderBalloon.cpp b/src/Mod/TechDraw/Gui/ViewProviderBalloon.cpp index f88dbb6f09..f79910b5e5 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderBalloon.cpp +++ b/src/Mod/TechDraw/Gui/ViewProviderBalloon.cpp @@ -75,6 +75,7 @@ ViewProviderBalloon::ViewProviderBalloon() double weight = lg->getWeight("Thin"); delete lg; //Coverity CID 174670 ADD_PROPERTY_TYPE(LineWidth,(weight),group,(App::PropertyType)(App::Prop_None),"Leader line width"); + ADD_PROPERTY_TYPE(LineVisible,(true),group,(App::PropertyType)(App::Prop_None),"Balloon line visible or hidden"); ADD_PROPERTY_TYPE(Color,(PreferencesGui::dimColor()), group,App::Prop_None,"Color of the balloon"); @@ -148,7 +149,8 @@ void ViewProviderBalloon::onChanged(const App::Property* p) if ((p == &Font) || (p == &Fontsize) || (p == &Color) || - (p == &LineWidth)) { + (p == &LineWidth) || + (p == &LineVisible)) { QGIView* qgiv = getQView(); if (qgiv) { qgiv->updateView(true); diff --git a/src/Mod/TechDraw/Gui/ViewProviderBalloon.h b/src/Mod/TechDraw/Gui/ViewProviderBalloon.h index fd9cb6ad27..2f88a50434 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderBalloon.h +++ b/src/Mod/TechDraw/Gui/ViewProviderBalloon.h @@ -48,6 +48,7 @@ public: App::PropertyFont Font; App::PropertyLength Fontsize; App::PropertyLength LineWidth; + App::PropertyBool LineVisible; App::PropertyColor Color; virtual void attach(App::DocumentObject *); From d81e377546be6fd0073bc39e68f575d05a5a1e7a Mon Sep 17 00:00:00 2001 From: Aapo Date: Tue, 15 Dec 2020 23:39:14 +0200 Subject: [PATCH 21/65] [TD] Balloon, fix EndTypeScale refresh bug. --- src/Mod/TechDraw/App/DrawViewBalloon.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Mod/TechDraw/App/DrawViewBalloon.cpp b/src/Mod/TechDraw/App/DrawViewBalloon.cpp index 2f40d08e37..8331d0809d 100644 --- a/src/Mod/TechDraw/App/DrawViewBalloon.cpp +++ b/src/Mod/TechDraw/App/DrawViewBalloon.cpp @@ -129,7 +129,8 @@ void DrawViewBalloon::onChanged(const App::Property* prop) if ( (prop == &EndType) || (prop == &BubbleShape) || (prop == &Text) || - (prop == &KinkLength) ) { + (prop == &KinkLength) || + (prop == &EndTypeScale) ) { requestPaint(); } } From 5f497ab37a2e13fd5ea144120f272f59ba6f0f47 Mon Sep 17 00:00:00 2001 From: Aapo Date: Wed, 16 Dec 2020 14:10:41 +0200 Subject: [PATCH 22/65] [TD] Balloon, fix Origin and scale refresh bugs, make Balloon scale change more reasonable per UI click. --- src/Mod/TechDraw/App/DrawViewBalloon.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewBalloon.cpp b/src/Mod/TechDraw/App/DrawViewBalloon.cpp index 8331d0809d..2a19855a21 100644 --- a/src/Mod/TechDraw/App/DrawViewBalloon.cpp +++ b/src/Mod/TechDraw/App/DrawViewBalloon.cpp @@ -65,7 +65,7 @@ using namespace TechDraw; App::PropertyFloatConstraint::Constraints DrawViewBalloon::SymbolScaleRange = { Precision::Confusion(), std::numeric_limits::max(), - (1.0) }; + (0.1) }; //=========================================================================== // DrawViewBalloon @@ -100,7 +100,7 @@ DrawViewBalloon::DrawViewBalloon(void) ADD_PROPERTY(EndType,(prefEnd())); ADD_PROPERTY_TYPE(EndTypeScale,(1.0),"",(App::PropertyType)(App::Prop_None),"EndType shape scale"); - ShapeScale.setConstraints(&SymbolScaleRange); + EndTypeScale.setConstraints(&SymbolScaleRange); BubbleShape.setEnums(balloonTypeEnums); ADD_PROPERTY(BubbleShape,(prefShape())); @@ -128,9 +128,12 @@ void DrawViewBalloon::onChanged(const App::Property* prop) if (!isRestoring()) { if ( (prop == &EndType) || (prop == &BubbleShape) || + (prop == &ShapeScale) || (prop == &Text) || (prop == &KinkLength) || - (prop == &EndTypeScale) ) { + (prop == &EndTypeScale) || + (prop == &OriginX) || + (prop == &OriginY) ) { requestPaint(); } } From a7506326c9af96db4c4a8a345d184077ea2e8ff6 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 18 Dec 2020 14:35:17 +0100 Subject: [PATCH 23/65] Gui: [skip ci] avoid adding a wrong file name to the recent files list --- src/Gui/Document.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Gui/Document.cpp b/src/Gui/Document.cpp index e6463eec6a..51e97c648a 100644 --- a/src/Gui/Document.cpp +++ b/src/Gui/Document.cpp @@ -1189,6 +1189,8 @@ bool Document::saveAs(void) escapedstr = Base::Tools::escapeEncodeFilename(escapedstr); Command::doCommand(Command::Doc,"App.getDocument(\"%s\").saveAs(u\"%s\")" , DocName, escapedstr.c_str()); + // App::Document::saveAs() may modify the passed file name + fi.setFile(QString::fromUtf8(d->_pcDocument->FileName.getValue())); setModified(false); getMainWindow()->appendRecentFile(fi.filePath()); } From d4bd0098c21a20a17c1a434d13a3234691b8db14 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 18 Dec 2020 16:28:35 +0100 Subject: [PATCH 24/65] PartDesign: [skip ci] fix layout of Drill point controls --- src/Mod/PartDesign/Gui/TaskHoleParameters.ui | 60 +++++++++----------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui index 7d0a694760..eef3d883f5 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui @@ -267,7 +267,7 @@ Only available for holes without thread - + @@ -314,41 +314,37 @@ Only available for holes without thread - - - - - - 0 - 0 - - - - Angled - - - - - - - - 0 - 0 - - - - deg - - - 0.000000000000000 - - - - + + + + 0 + 0 + + + + Angled + + + + + + + 0 + 0 + + + + deg + + + 0.000000000000000 + + + From a2b1e6226aae5d0b4900c92365be5ca3a62d5564 Mon Sep 17 00:00:00 2001 From: M G Berberich Date: Fri, 18 Dec 2020 15:19:31 +0100 Subject: [PATCH 25/65] cleanup of hole dialog * replaced widget with radiobuttons by button Group, to improve alignemt of DrillPointAngle with grid-layout * made Thread Pitch/Angle/Cutoffs widgets wider --- src/Mod/PartDesign/Gui/TaskHoleParameters.ui | 972 +++++++++---------- 1 file changed, 461 insertions(+), 511 deletions(-) diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui index eef3d883f5..e0c34186be 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui @@ -6,8 +6,8 @@ 0 0 - 373 - 560 + 441 + 710 @@ -20,21 +20,65 @@ Task Hole Parameters - - - - false - - - mm - - - 0.000000000000000 + + + + Clearance - - + + + + + 0 + 0 + + + + <b>Drill point</b> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Depth + + + + + + + false + + + Angle + + + + + + + false + + + Cutoff outer + + + + + + + Tapered + + + + + 0 @@ -43,48 +87,36 @@ - 140 + 16777215 16777215 - - - Dimension - - - - - Through all - - - - - - - 0 - 0 - - - - - 110 - 16777215 - - + + - Hole diameter + - - mm - - - 0.000000000000000 + + Right hand + + directionButtonGroup + - + + + + Left hand + + + directionButtonGroup + + + + @@ -119,94 +151,91 @@ Only available for holes without thread - - - - false + + + + + 0 + 0 + - Angle + Profile - - - - Qt::Horizontal + + + + + 0 + 0 + - - QSizePolicy::Fixed + + Direction - + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + 0 + 0 + + + - 13 - 20 + 140 + 16777215 - - - - - - <b>Hole cut</b> - + + + Dimension + + + + + Through all + + - - - - Thread direction + + + + + 0 + 0 + - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - Right hand - - - - - - - Left hand - - - - - - - - - - false + + + 16777215 + 16777215 + - mm + deg 0.000000000000000 - + + + + Countersink angle + + + + @@ -231,281 +260,7 @@ Only available for holes without thread - - - - false - - - Model actual thread - - - - - - - - 0 - 0 - - - - Depth - - - - - - - - 0 - 0 - - - - Profile - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Ending of the hole if 'Depth' is set to 'Dimension' - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Flat - - - - - - - - 0 - 0 - - - - Angled - - - - - - - - - - - 0 - 0 - - - - deg - - - 0.000000000000000 - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - - - - - 0 - 0 - - - - Class - - - - - - - Tapered - - - - - - - false - - - Cutoff inner - - - - - - - - 0 - 0 - - - - - 140 - 16777215 - - - - Tolerance class for threaded holes according to hole profile - - - - - - - - 0 - 0 - - - - mm - - - - - - - - 0 - 0 - - - - Size - - - - - - - - 0 - 0 - - - - <b>Drill point</b> - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - Type - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - false - - - Cutoff outer - - - - - - - - 0 - 0 - - - - Type - - - - - - - false - - - deg - - - 0.000000000000000 - - - - - - - <b>Threading and size</b> - - - - + @@ -524,102 +279,13 @@ Only available for holes without thread - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - deg - - - 0.000000000000000 - - - - - - - <b>Misc</b> - - - - - - - - 0 - 0 - - - - Direction - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - - 0 - 0 - - - - Diameter - - - - - - - Depth - - - - - - - Countersink angle - - - - - - - Diameter - - - - - - - Clearance - - - - - + + false - - mm - - - 0.000000000000000 + + Pitch @@ -633,7 +299,174 @@ Only available for holes without thread - + + + + + 0 + 0 + + + + Class + + + + + + + false + + + Cutoff inner + + + + + + + Taper angle for the hole +90 degree: straight hole +under 90: smaller hole radius at the bottom +over 90: larger hole radius at the bottom + + + deg + + + 0.000000000000000 + + + + + + + false + + + mm + + + 0.000000000000000 + + + + + + + <b>Hole cut</b> + + + + + + + false + + + deg + + + 0.000000000000000 + + + + + + + + 0 + 0 + + + + deg + + + 0.000000000000000 + + + + + + + + 0 + 0 + + + + Flat + + + drillPointButtonGroup + + + + + + + <b>Threading and size</b> + + + + + + + + 0 + 0 + + + + Diameter + + + + + + + + 0 + 0 + + + + Type + + + + + + + <b>Misc</b> + + + + + + + + 0 + 0 + + + + mm + + + + + + + false + + + Model actual thread + + + + @@ -649,39 +482,23 @@ Only available for holes without thread - - - - - 120 - 16777215 - - - - Taper angle for the hole -90 degree: straight hole -under 90: smaller hole radius at the bottom -over 90: larger hole radius at the bottom - - - deg - - - 0.000000000000000 - - - - - - - false + + + + + 0 + 0 + - Pitch + Angled + + drillPointButtonGroup + - + @@ -709,7 +526,136 @@ over 90: larger hole radius at the bottom - + + + + + 0 + 0 + + + + Depth + + + + + + + Diameter + + + + + + + + 0 + 0 + + + + + 110 + 16777215 + + + + Hole diameter + + + mm + + + 0.000000000000000 + + + + + + + + 0 + 0 + + + + + 140 + 16777215 + + + + Tolerance class for threaded holes according to hole profile + + + + + + + Type + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + false + + + mm + + + 0.000000000000000 + + + + + + + + 0 + 0 + + + + Size + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 13 + 20 + + + + + + + + false + + + mm + + + 0.000000000000000 + + + + Reverses the hole direction @@ -784,4 +730,8 @@ over 90: larger hole radius at the bottom + + + + From 2db10d82368a1b7423dff83deaef2ba28003b6d4 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 18 Dec 2020 17:04:27 +0100 Subject: [PATCH 26/65] PartDesign: [skip ci] fix order of controls so that by pressing TAB always the next control gets focus --- src/Mod/PartDesign/Gui/TaskHoleParameters.ui | 732 +++++++++---------- 1 file changed, 366 insertions(+), 366 deletions(-) diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui index e0c34186be..c723acb05c 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui @@ -20,15 +20,15 @@ Task Hole Parameters - - + + - Clearance + <b>Threading and size</b> - - + + 0 @@ -36,44 +36,7 @@ - <b>Drill point</b> - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - Depth - - - - - - - false - - - Angle - - - - - - - false - - - Cutoff outer - - - - - - - Tapered + Profile @@ -93,6 +56,141 @@ + + + + Whether the hole gets a thread + + + Threaded + + + + + + + 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 + + + + + + + + 0 + 0 + + + + Direction + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + @@ -116,6 +214,42 @@ + + + + + 0 + 0 + + + + Size + + + + + + + + 0 + 0 + + + + + 140 + 16777215 + + + + + + + + Clearance + + + @@ -151,8 +285,8 @@ Only available for holes without thread - - + + 0 @@ -160,12 +294,72 @@ Only available for holes without thread - Profile + Class - - + + + + + 0 + 0 + + + + + 140 + 16777215 + + + + Tolerance class for threaded holes according to hole profile + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 13 + 20 + + + + + + + + + 0 + 0 + + + + + 110 + 16777215 + + + + Hole diameter + + + mm + + + 0.000000000000000 + + + + + 0 @@ -173,10 +367,20 @@ Only available for holes without thread - Direction + Diameter - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + 0 + 0 + + + + Depth @@ -206,57 +410,29 @@ Only available for holes without thread - - + + 0 0 - - - 16777215 - 16777215 - - - - deg - - - 0.000000000000000 - - - - - - - Countersink angle - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - mm - - 0.000000000000000 + + + + + + + 0 + 0 + - - 0.100000000000000 + + Type @@ -279,225 +455,13 @@ Only available for holes without thread - - - - false - - - Pitch - - - - - - - Whether the hole gets a thread - - - Threaded - - - - - - - - 0 - 0 - - - - Class - - - - - - - false - - - Cutoff inner - - - - - - - Taper angle for the hole -90 degree: straight hole -under 90: smaller hole radius at the bottom -over 90: larger hole radius at the bottom - - - deg - - - 0.000000000000000 - - - - - - - false - - - mm - - - 0.000000000000000 - - - - - - - <b>Hole cut</b> - - - - - - - false - - - deg - - - 0.000000000000000 - - - - - - - - 0 - 0 - - - - deg - - - 0.000000000000000 - - - - - - - - 0 - 0 - - - - Flat - - - drillPointButtonGroup - - - - - - - <b>Threading and size</b> - - - - - - - - 0 - 0 - - + + Diameter - - - - - 0 - 0 - - - - Type - - - - - - - <b>Misc</b> - - - - - - - - 0 - 0 - - - - mm - - - - - - - false - - - Model actual thread - - - - - - - - 0 - 0 - - - - - 140 - 16777215 - - - - - - - - - 0 - 0 - - - - Angled - - - drillPointButtonGroup - - - @@ -526,28 +490,15 @@ over 90: larger hole radius at the bottom - - - - - 0 - 0 - - + + Depth - - - - Diameter - - - - - + + 0 @@ -556,37 +507,63 @@ over 90: larger hole radius at the bottom - 110 + 16777215 16777215 - - Hole diameter - mm 0.000000000000000 + + 0.100000000000000 + - - + + + + Countersink angle + + + + + - + 0 0 - 140 + 16777215 16777215 - - Tolerance class for threaded holes according to hole profile + + deg + + + 0.000000000000000 + + + + + + + + 0 + 0 + + + + <b>Drill point</b> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop @@ -600,55 +577,78 @@ over 90: larger hole radius at the bottom - - - - false + + + + + 0 + 0 + + + + Flat + + + drillPointButtonGroup + + + + + + + + 0 + 0 + + + + Angled + + + drillPointButtonGroup + + + + + + + + 0 + 0 + - mm + deg 0.000000000000000 - - - - - 0 - 0 - - + + - Size + <b>Misc</b> - - - - Qt::Horizontal + + + + Tapered - - QSizePolicy::Fixed - - - - 13 - 20 - - - + - - - - false + + + + Taper angle for the hole +90 degree: straight hole +under 90: smaller hole radius at the bottom +over 90: larger hole radius at the bottom - mm + deg 0.000000000000000 From ff69f7fba3352edc82571747482097e15e465f6b Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 18 Dec 2020 21:18:41 +0100 Subject: [PATCH 27/65] PartDesign: [skip ci] fix Hole feature * by default ThreadDirection must be set read-only since Threaded by default is None * use suitable values to initialize the class properties --- src/Mod/PartDesign/App/FeatureHole.cpp | 31 +++++++++++++------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/Mod/PartDesign/App/FeatureHole.cpp b/src/Mod/PartDesign/App/FeatureHole.cpp index 383a15021f..1808d0b1e7 100644 --- a/src/Mod/PartDesign/App/FeatureHole.cpp +++ b/src/Mod/PartDesign/App/FeatureHole.cpp @@ -547,32 +547,33 @@ Hole::Hole() readCutDefinitions(); - ADD_PROPERTY_TYPE(Threaded, ((long)0), "Hole", App::Prop_None, "Threaded"); + ADD_PROPERTY_TYPE(Threaded, (false), "Hole", App::Prop_None, "Threaded"); - ADD_PROPERTY_TYPE(ModelActualThread, ((long)0), "Hole", App::Prop_None, "Model actual thread"); - ADD_PROPERTY_TYPE(ThreadPitch, ((long)0), "Hole", App::Prop_None, "Thread pitch"); - ADD_PROPERTY_TYPE(ThreadAngle, ((long)0), "Hole", App::Prop_None, "Thread angle"); - ADD_PROPERTY_TYPE(ThreadCutOffInner, ((long)0), "Hole", App::Prop_None, "Thread CutOff Inner"); - ADD_PROPERTY_TYPE(ThreadCutOffOuter, ((long)0), "Hole", App::Prop_None, "Thread CutOff Outer"); + ADD_PROPERTY_TYPE(ModelActualThread, (false), "Hole", App::Prop_None, "Model actual thread"); + ADD_PROPERTY_TYPE(ThreadPitch, (0.0), "Hole", App::Prop_None, "Thread pitch"); + ADD_PROPERTY_TYPE(ThreadAngle, (0.0), "Hole", App::Prop_None, "Thread angle"); + ADD_PROPERTY_TYPE(ThreadCutOffInner, (0.0), "Hole", App::Prop_None, "Thread CutOff Inner"); + ADD_PROPERTY_TYPE(ThreadCutOffOuter, (0.0), "Hole", App::Prop_None, "Thread CutOff Outer"); - ADD_PROPERTY_TYPE(ThreadType, ((long)0), "Hole", App::Prop_None, "Thread type"); + ADD_PROPERTY_TYPE(ThreadType, (0L), "Hole", App::Prop_None, "Thread type"); ThreadType.setEnums(ThreadTypeEnums); - ADD_PROPERTY_TYPE(ThreadSize, ((long)0), "Hole", App::Prop_None, "Thread size"); + ADD_PROPERTY_TYPE(ThreadSize, (0L), "Hole", App::Prop_None, "Thread size"); ThreadSize.setEnums(ThreadSize_None_Enums); - ADD_PROPERTY_TYPE(ThreadClass, ((long)0), "Hole", App::Prop_None, "Thread class"); + ADD_PROPERTY_TYPE(ThreadClass, (0L), "Hole", App::Prop_None, "Thread class"); ThreadClass.setEnums(ThreadClass_None_Enums); - ADD_PROPERTY_TYPE(ThreadFit, ((long)0), "Hole", App::Prop_None, "Thread fit"); + ADD_PROPERTY_TYPE(ThreadFit, (0L), "Hole", App::Prop_None, "Thread fit"); ThreadFit.setEnums(ThreadFitEnums); ADD_PROPERTY_TYPE(Diameter, (6.0), "Hole", App::Prop_None, "Diameter"); - ADD_PROPERTY_TYPE(ThreadDirection, ((long)0), "Hole", App::Prop_None, "Thread direction"); + ADD_PROPERTY_TYPE(ThreadDirection, (0L), "Hole", App::Prop_None, "Thread direction"); ThreadDirection.setEnums(ThreadDirectionEnums); + ThreadDirection.setReadOnly(true); - ADD_PROPERTY_TYPE(HoleCutType, ((long)0), "Hole", App::Prop_None, "Head cut type"); + ADD_PROPERTY_TYPE(HoleCutType, (0L), "Hole", App::Prop_None, "Head cut type"); HoleCutType.setEnums(HoleCutType_None_Enums); ADD_PROPERTY_TYPE(HoleCutDiameter, (0.0), "Hole", App::Prop_None, "Head cut diameter"); @@ -581,17 +582,17 @@ Hole::Hole() ADD_PROPERTY_TYPE(HoleCutCountersinkAngle, (90.0), "Hole", App::Prop_None, "Head cut countersink angle"); - ADD_PROPERTY_TYPE(DepthType, ((long)0), "Hole", App::Prop_None, "Type"); + ADD_PROPERTY_TYPE(DepthType, (0L), "Hole", App::Prop_None, "Type"); DepthType.setEnums(DepthTypeEnums); ADD_PROPERTY_TYPE(Depth, (25.0), "Hole", App::Prop_None, "Length"); - ADD_PROPERTY_TYPE(DrillPoint, ((long)1), "Hole", App::Prop_None, "Drill point type"); + ADD_PROPERTY_TYPE(DrillPoint, (1L), "Hole", App::Prop_None, "Drill point type"); DrillPoint.setEnums(DrillPointEnums); ADD_PROPERTY_TYPE(DrillPointAngle, (118.0), "Hole", App::Prop_None, "Drill point angle"); - ADD_PROPERTY_TYPE(Tapered, ((bool)false),"Hole", App::Prop_None, "Tapered"); + ADD_PROPERTY_TYPE(Tapered, (false),"Hole", App::Prop_None, "Tapered"); ADD_PROPERTY_TYPE(TaperedAngle, (90.0), "Hole", App::Prop_None, "Tapered angle"); } From f7be6f5051a8b802fd889858f2c0b2605709abd4 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Tue, 15 Dec 2020 12:07:24 +0100 Subject: [PATCH 28/65] Part: Geometry - BSplineCurve - add IsRational member function --- src/Mod/Part/App/Geometry.cpp | 5 +++++ src/Mod/Part/App/Geometry.h | 1 + 2 files changed, 6 insertions(+) diff --git a/src/Mod/Part/App/Geometry.cpp b/src/Mod/Part/App/Geometry.cpp index 9c3ba11ce2..be49c911c0 100644 --- a/src/Mod/Part/App/Geometry.cpp +++ b/src/Mod/Part/App/Geometry.cpp @@ -1329,6 +1329,11 @@ bool GeomBSplineCurve::isPeriodic() const return myCurve->IsPeriodic()==Standard_True; } +bool GeomBSplineCurve::isRational() const +{ + return myCurve->IsRational()==Standard_True; +} + bool GeomBSplineCurve::join(const Handle(Geom_BSplineCurve)& spline) { GeomConvert_CompCurveToBSplineCurve ccbc(this->myCurve); diff --git a/src/Mod/Part/App/Geometry.h b/src/Mod/Part/App/Geometry.h index e594ce5f47..d52d09fa40 100644 --- a/src/Mod/Part/App/Geometry.h +++ b/src/Mod/Part/App/Geometry.h @@ -296,6 +296,7 @@ public: int getMultiplicity(int index) const; int getDegree() const; bool isPeriodic() const; + bool isRational() const; bool join(const Handle(Geom_BSplineCurve)&); void makeC1Continuous(double, double); std::list toBiArcs(double tolerance) const; From 533ba5b15938a9606665c9741db7556b275f9690 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Fri, 18 Dec 2020 17:16:33 +0100 Subject: [PATCH 29/65] Sketcher: delete internal alignment geometry using new delGeometries function --- src/Mod/Sketcher/App/SketchObject.cpp | 29 ++++++--------------------- src/Mod/Sketcher/App/SketchObject.h | 2 -- 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index 55ffdfa7d0..ed1fa11b4c 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -146,7 +146,6 @@ SketchObject::SketchObject() internaltransaction=false; managedoperation=false; - deletinginternalgeometry=false; } SketchObject::~SketchObject() @@ -957,10 +956,6 @@ int SketchObject::delGeometry(int GeoId, bool deleteinternalgeo) this->deleteUnusedInternalGeometry(GeoId, true); - Geometry.touch(); - - if(noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver - solve(); return 0; } } @@ -1002,13 +997,12 @@ int SketchObject::delGeometry(int GeoId, bool deleteinternalgeo) this->Geometry.setValues(newVals); this->Constraints.setValues(std::move(newConstraints)); } - // Update geometry indices and rebuild vertexindex now via onChanged, so that ViewProvider::UpdateData is triggered. - if(!deletinginternalgeometry) { - Geometry.touch(); - if(noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver - solve(); - } + // Update geometry indices and rebuild vertexindex now via onChanged, so that ViewProvider::UpdateData is triggered. + Geometry.touch(); + + if(noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver + solve(); return 0; } @@ -4979,8 +4973,6 @@ int SketchObject::exposeInternalGeometry(int GeoId) int SketchObject::deleteUnusedInternalGeometry(int GeoId, bool delgeoid) { - Base::StateLocker lock(deletinginternalgeometry, true); - if (GeoId < 0 || GeoId > getHighestCurveIndex()) return -1; @@ -5264,16 +5256,7 @@ int SketchObject::deleteUnusedInternalGeometry(int GeoId, bool delgeoid) if(delgeoid) delgeometries.push_back(GeoId); - std::sort(delgeometries.begin(), delgeometries.end()); // indices over an erased element get automatically updated!! - - if (delgeometries.size()>0) { - for (std::vector::reverse_iterator it=delgeometries.rbegin(); it!=delgeometries.rend(); ++it) { - delGeometry(*it,false); - } - } - - int ndeleted = delgeometries.size(); - delgeometries.clear(); + int ndeleted = delGeometries(delgeometries); return ndeleted; //number of deleted elements } diff --git a/src/Mod/Sketcher/App/SketchObject.h b/src/Mod/Sketcher/App/SketchObject.h index f937bbc3c9..c3386cabda 100644 --- a/src/Mod/Sketcher/App/SketchObject.h +++ b/src/Mod/Sketcher/App/SketchObject.h @@ -511,8 +511,6 @@ private: bool internaltransaction; bool managedoperation; // indicates whether changes to properties are the deed of SketchObject or not (for input validation) - - bool deletinginternalgeometry; // sets a lock to deletinginternalgeometryoperation so that no individual triggers are perform on each element. }; typedef App::FeaturePythonT SketchObjectPython; From 4d9fcb0b27fa3819a50efbdd5bd924c61f383334 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Fri, 18 Dec 2020 17:50:49 +0100 Subject: [PATCH 30/65] Sketcher: Fix crash when creating an angle constrain on a line segment ====================================================================== Most of these crashes come from previous code not checking for Constraint::GeoUndef. Most of these crashes come from isBSpline(), any of the two overloads. isBSpline is made to throw exception when null, which should prevent the crash while creating a reportable error. --- src/Mod/Sketcher/Gui/CommandConstraints.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index acf6ec759b..289791edbd 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -260,11 +260,15 @@ bool SketcherGui::isBsplinePole(const Part::Geometry * geo) { auto gf = GeometryFacade::getFacade(geo); - return gf->getInternalType() == InternalType::BSplineControlPoint; + if(gf) + return gf->getInternalType() == InternalType::BSplineControlPoint; + + THROWM(Base::ValueError, "Null geometry in isBsplinePole - please report") } bool SketcherGui::isBsplinePole(const Sketcher::SketchObject* Obj, int GeoId) { + auto geom = Obj->getGeometry(GeoId); return isBsplinePole(geom); From a4481a327c42d458884293235dbe375f84e50727 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sat, 19 Dec 2020 09:43:38 +0100 Subject: [PATCH 31/65] Sketcher: ViewProviderSketch update of geometry extensions ========================================================== The order of any operation, including setedit is first solve() and then draw(). This is consistent with geometry addition. If ViewProviderSketch must insert its own extensions, for example for scaling weights, then it is its responsibility to set this information wherever needed. This includes the temporal geometry vector used in draw(true), the solver to enable dragging operations, and SketchObject Geometry property. --- src/Mod/Sketcher/Gui/ViewProviderSketch.cpp | 29 +++++++++++++++------ 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index 9f378b3247..25b93ffc73 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -3769,6 +3769,16 @@ void ViewProviderSketch::draw(bool temp /*=false*/, bool rebuildinformationlayer Coords.emplace_back(x, y, 0); // save scale factor for any prospective dragging operation + // 1. Solver must be updated, in case a dragging operation starts + // 2. if temp geometry is being used (with memory allocation), then the copy we have here must be updated. If + // no temp geometry is being used, then the normal geometry must be updated. + {// make solver be ready for a dragging operation + auto vpext = std::make_unique(); + vpext->setRepresentationFactor(scalefactor); + + getSketchObject()->getSolvedSketch().updateExtension(GeoId, std::move(vpext)); + } + if(!circle->hasExtension(SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId())) { // It is ok to add this kind of extension to a const geometry because: @@ -6101,14 +6111,17 @@ bool ViewProviderSketch::setEdit(int ModNum) getSketchObject()->validateExternalLinks(); } - // First drawing with non-temporal geometry, then updating solver information - // This ensures that any ViewProvider geometry extension is set before the geometry - // is loaded into the solver, which ensures that any prospective draw using temporal - // geometry (draw with first parameter true) has the right ViewProvider geometry extensions - // set - This fixes Weight constraint dragging on a just opened sketch. - getSketchObject()->solve(false); - UpdateSolverInformation(); - draw(false,true); + // There are geometry extensions introduced by the solver and geometry extensions introduced by the viewprovider. + // 1. It is important that the solver has geometry with updated extensions. + // 2. It is important that the viewprovider has up-to-date solver information + // + // The decision is to maintain the "first solve then draw" order, which is consistent with the rest of the Sketcher + // for example in geometry creation. Then, the ViewProvider is responsible for updating the solver geometry when + // appropriate, as it is the ViewProvider that is introducing its geometry extensions. + // + // In order to have updated solver information, solve must take "true", this cause the Geometry property to be updated + // with the solver information, including solver extensions, and triggers a draw(true) via ViewProvider::UpdateData. + getSketchObject()->solve(true); connectUndoDocument = getDocument() ->signalUndoDocument.connect(boost::bind(&ViewProviderSketch::slotUndoDocument, this, bp::_1)); From 417727754f33288131e2516ba19b42e187492b6b Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sun, 13 Dec 2020 06:46:09 +0100 Subject: [PATCH 32/65] GCS: Increase documentation of diagnose() routine --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 42 +++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index d7629e37c1..a80bedbd30 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -25,12 +25,12 @@ #pragma warning(disable : 4996) #endif -//#define _GCS_DEBUG -//#define _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX -//#define _DEBUG_TO_FILE // Many matrices surpass the report view string size. -#undef _GCS_DEBUG -#undef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX -#undef _DEBUG_TO_FILE +#define _GCS_DEBUG +#define _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX +#define _DEBUG_TO_FILE // Many matrices surpass the report view string size. +//#undef _GCS_DEBUG +//#undef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX +//#undef _DEBUG_TO_FILE // This has to be included BEFORE any EIGEN include // This format is Sage compatible, so you can just copy/paste the matrix into Sage @@ -3880,6 +3880,36 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); makeReducedJacobian(J, jacobianconstraintmap, pdiagnoselist, tagmultiplicity); + // There is a legacy decision to use QR decomposition. I (abdullah) do not know all the + // consideration taken in that decisions. I see that: + // - QR decomposition is able to provide information about the rank and redundant/conflicting constraints + // - The QR decomposition of J and the QR decomposition of the transpose of J are unrelated (for reasons see below): + https://mathoverflow.net/questions/338729/translate-between-qr-decomposition-of-a-and-a-transpose + // - QR is cheaper than a SVD decomposition + // - QR is more expensive than a rank revealing LU factorization + // - QR is less stable than SVD with respect to rank + // - It is unclear whether it is possible to obtain information about redundancy with SVD and LU + + // Given this legacy decision, the following is observed: + // - A = QR decomposition can be used for the diagonise of dependency of the "columns" of A. the + // reason is that matrix R is upper triangular with columns of A showing the dependencies. + // - The same does not apply to the "rows". + // - For this reason, to enable a full diagnose of constraints, a QR decomposition must be done on + // the transpose of the Jacobian matrix (J), this is JT. + + // Eigen capabilities: + // - If Eigen full pivoting QR decomposition is used, it is possible to track the rows of JT during + // the decomposition. This can be leveraged to identify a set of independent rows of JT (geometry) + // that form a rank N basis. However, because the R matrix is of the JT decomposition and not the J + // decomposition, it is not possible to reduce the system to identify exactly which rows are dependent. + // - The effect is that it provides a set of parameters of geometry that are not constraint, but it does not + // identify ALL geometries that are not fixed. + // - If SpareQR is used, then it is not possible to track the rows of JT during decomposition. I do not know + // if it is still possible to obtain geometry information at all from SparseQR. After several years these + // questions remain open: + // https://stackoverflow.com/questions/49009771/getting-rows-transpositions-with-sparse-qr + // https://forum.kde.org/viewtopic.php?f=74&t=151239 + // QR decomposition method selection: SparseQR vs DenseQR #ifdef EIGEN_SPARSEQR_COMPATIBLE From 083aa1099a2ed33156e3dd8082c9f7def4d1b73b Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sun, 13 Dec 2020 07:37:08 +0100 Subject: [PATCH 33/65] GCS: Refactor QR decomposition into separate functions for Sparse and Dense --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 252 ++++++++++++++------------ src/Mod/Sketcher/App/planegcs/GCS.h | 19 ++ 2 files changed, 158 insertions(+), 113 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index a80bedbd30..ad77b95526 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -25,12 +25,12 @@ #pragma warning(disable : 4996) #endif -#define _GCS_DEBUG -#define _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX -#define _DEBUG_TO_FILE // Many matrices surpass the report view string size. -//#undef _GCS_DEBUG -//#undef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX -//#undef _DEBUG_TO_FILE +//#define _GCS_DEBUG +//#define _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX +//#define _DEBUG_TO_FILE // Many matrices surpass the report view string size. +#undef _GCS_DEBUG +#undef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX +#undef _DEBUG_TO_FILE // This has to be included BEFORE any EIGEN include // This format is Sage compatible, so you can just copy/paste the matrix into Sage @@ -61,9 +61,7 @@ // until Eigen library fixes its own problem with the assertion (definitely not solved in 3.2.0 branch) // NOTE2: solved in eigen3.3 -#define EIGEN_VERSION (EIGEN_WORLD_VERSION * 10000 \ -+ EIGEN_MAJOR_VERSION * 100 \ -+ EIGEN_MINOR_VERSION) + // Extraction of Q matrix for Debugging used to crash #ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX @@ -72,9 +70,6 @@ #endif #endif - -#if EIGEN_VERSION >= 30202 -#define EIGEN_SPARSEQR_COMPATIBLE #if EIGEN_VERSION > 30290 // This regulates that only starting in Eigen 3.3, the problem with // http://forum.freecadweb.org/viewtopic.php?f=3&t=4651&start=40 // was solved in Eigen: @@ -82,15 +77,13 @@ // https://forum.kde.org/viewtopic.php?f=74&t=129439 #define EIGEN_STOCK_FULLPIVLU_COMPUTE #endif -#endif //#undef EIGEN_SPARSEQR_COMPATIBLE -#include + #ifdef EIGEN_SPARSEQR_COMPATIBLE -#include -#include + #include #endif // _GCS_EXTRACT_SOLVER_SUBSYSTEM_ to be enabled in Constraints.h when needed. @@ -3884,7 +3877,7 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); // consideration taken in that decisions. I see that: // - QR decomposition is able to provide information about the rank and redundant/conflicting constraints // - The QR decomposition of J and the QR decomposition of the transpose of J are unrelated (for reasons see below): - https://mathoverflow.net/questions/338729/translate-between-qr-decomposition-of-a-and-a-transpose + // https://mathoverflow.net/questions/338729/translate-between-qr-decomposition-of-a-and-a-transpose // - QR is cheaper than a SVD decomposition // - QR is more expensive than a rank revealing LU factorization // - QR is less stable than SVD with respect to rank @@ -3913,16 +3906,6 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); // QR decomposition method selection: SparseQR vs DenseQR #ifdef EIGEN_SPARSEQR_COMPATIBLE - Eigen::SparseMatrix SJ; - - if(qrAlgorithm==EigenSparseQR){ - // this creation is not optimized (done using triplets) - // however the time this takes is negligible compared to the - // time the QR decomposition itself takes - SJ = J.sparseView(); - SJ.makeCompressed(); - } - Eigen::SparseQR, Eigen::COLAMDOrdering > SqrJT; #else if(qrAlgorithm==EigenSparseQR){ @@ -3931,104 +3914,21 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); } #endif - - -#ifdef _GCS_DEBUG - SolverReportingManager::Manager().LogMatrix("J",J); -#endif - Eigen::MatrixXd R; - -#ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX - Eigen::MatrixXd Q; // Obtaining the Q matrix with Sparse QR is buggy, see comments below - Eigen::MatrixXd R2; // Intended for a trapezoidal matrix, where R is the top triangular matrix of the R2 trapezoidal matrix -#endif - - int paramsNum = 0; int constrNum = 0; int rank = 0; + Eigen::FullPivHouseholderQR qrJT; if(qrAlgorithm==EigenDenseQR){ - if (J.rows() > 0) { - qrJT.compute(J.topRows(jacobianconstraintmap.size()).transpose()); - //Eigen::MatrixXd Q = qrJT.matrixQ (); - - paramsNum = qrJT.rows(); - constrNum = qrJT.cols(); - qrJT.setThreshold(qrpivotThreshold); - rank = qrJT.rank(); - - if (constrNum >= paramsNum) - R = qrJT.matrixQR().triangularView(); - else - R = qrJT.matrixQR().topRows(constrNum) - .triangularView(); - -#ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX - R2 = qrJT.matrixQR(); - Q = qrJT.matrixQ(); -#endif - } + makeDenseQRDecomposition( J, jacobianconstraintmap, qrJT, paramsNum, constrNum, rank, R); } -#ifdef EIGEN_SPARSEQR_COMPATIBLE else if(qrAlgorithm==EigenSparseQR){ - if (SJ.rows() > 0) { - auto SJT = SJ.topRows(jacobianconstraintmap.size()).transpose(); - if (SJT.rows() > 0 && SJT.cols() > 0) { - SqrJT.compute(SJT); - // Do not ask for Q Matrix!! - // At Eigen 3.2 still has a bug that this only works for square matrices - // if enabled it will crash - #ifdef SPARSE_Q_MATRIX - Q = SqrJT.matrixQ(); - //Q = QS; - #endif - - paramsNum = SqrJT.rows(); - constrNum = SqrJT.cols(); - SqrJT.setPivotThreshold(qrpivotThreshold); - rank = SqrJT.rank(); - - if (constrNum >= paramsNum) - R = SqrJT.matrixR().triangularView(); - else - R = SqrJT.matrixR().topRows(constrNum) - .triangularView(); - - #ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX - R2 = SqrJT.matrixR(); - #endif - } - else { - paramsNum = SJT.rows(); - constrNum = SJT.cols(); - } - } - } -#endif - - if(debugMode==IterationLevel) { - SolverReportingManager::Manager().LogQRSystemInformation(*this, paramsNum, constrNum, rank); + makeSparseQRDecomposition( J, jacobianconstraintmap, SqrJT, paramsNum, constrNum, rank, R); } if (J.rows() > 0) { -#ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX - SolverReportingManager::Manager().LogMatrix("R", R); - - SolverReportingManager::Manager().LogMatrix("R2", R2); - - if(qrAlgorithm == EigenDenseQR){ // There is no rowsTranspositions in SparseQR. obtaining Q is buggy in Eigen for SparseQR - SolverReportingManager::Manager().LogMatrix("Q", Q); - SolverReportingManager::Manager().LogMatrix("RowTransp", qrJT.rowsTranspositions()); - } -#ifdef SPARSE_Q_MATRIX - else if(qrAlgorithm == EigenSparseQR) { - SolverReportingManager::Manager().LogMatrix("Q", Q); - } -#endif -#endif // DETECTING CONSTRAINT SOLVER PARAMETERS // @@ -4339,6 +4239,132 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); return dofs; } +void System::makeDenseQRDecomposition( Eigen::MatrixXd &J, std::map &jacobianconstraintmap, + Eigen::FullPivHouseholderQR& qrJT, + int ¶msNum, int &constrNum, int &rank, Eigen::MatrixXd & R) +{ + +#ifdef _GCS_DEBUG + SolverReportingManager::Manager().LogMatrix("J",J); +#endif + +#ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX + Eigen::MatrixXd Q; // Obtaining the Q matrix with Sparse QR is buggy, see comments below + Eigen::MatrixXd R2; // Intended for a trapezoidal matrix, where R is the top triangular matrix of the R2 trapezoidal matrix +#endif + + if (J.rows() > 0) { + qrJT.compute(J.topRows(jacobianconstraintmap.size()).transpose()); + //Eigen::MatrixXd Q = qrJT.matrixQ (); + + paramsNum = qrJT.rows(); + constrNum = qrJT.cols(); + qrJT.setThreshold(qrpivotThreshold); + rank = qrJT.rank(); + + if (constrNum >= paramsNum) + R = qrJT.matrixQR().triangularView(); + else + R = qrJT.matrixQR().topRows(constrNum) + .triangularView(); + +#ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX + R2 = qrJT.matrixQR(); + Q = qrJT.matrixQ(); +#endif + } + + if(debugMode==IterationLevel) { + SolverReportingManager::Manager().LogQRSystemInformation(*this, paramsNum, constrNum, rank); + } + +#ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX + if (J.rows() > 0) { + SolverReportingManager::Manager().LogMatrix("R", R); + + SolverReportingManager::Manager().LogMatrix("R2", R2); + + SolverReportingManager::Manager().LogMatrix("Q", Q); + SolverReportingManager::Manager().LogMatrix("RowTransp", qrJT.rowsTranspositions()); + } +#endif +} + +void System::makeSparseQRDecomposition( Eigen::MatrixXd &J, std::map &jacobianconstraintmap, + Eigen::SparseQR, Eigen::COLAMDOrdering > &SqrJT, + int ¶msNum, int &constrNum, int &rank, Eigen::MatrixXd & R) +{ +#ifdef EIGEN_SPARSEQR_COMPATIBLE + Eigen::SparseMatrix SJ; + + // this creation is not optimized (done using triplets) + // however the time this takes is negligible compared to the + // time the QR decomposition itself takes + SJ = J.sparseView(); + SJ.makeCompressed(); +#endif + +#ifdef _GCS_DEBUG + SolverReportingManager::Manager().LogMatrix("J",J); +#endif + +#ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX + Eigen::MatrixXd Q; // Obtaining the Q matrix with Sparse QR is buggy, see comments below + Eigen::MatrixXd R2; // Intended for a trapezoidal matrix, where R is the top triangular matrix of the R2 trapezoidal matrix +#endif + +#ifdef EIGEN_SPARSEQR_COMPATIBLE + if (SJ.rows() > 0) { + auto SJT = SJ.topRows(jacobianconstraintmap.size()).transpose(); + if (SJT.rows() > 0 && SJT.cols() > 0) { + SqrJT.compute(SJT); + // Do not ask for Q Matrix!! + // At Eigen 3.2 still has a bug that this only works for square matrices + // if enabled it will crash + #ifdef SPARSE_Q_MATRIX + Q = SqrJT.matrixQ(); + //Q = QS; + #endif + + paramsNum = SqrJT.rows(); + constrNum = SqrJT.cols(); + SqrJT.setPivotThreshold(qrpivotThreshold); + rank = SqrJT.rank(); + + if (constrNum >= paramsNum) + R = SqrJT.matrixR().triangularView(); + else + R = SqrJT.matrixR().topRows(constrNum) + .triangularView(); + + #ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX + R2 = SqrJT.matrixR(); + #endif + } + else { + paramsNum = SJT.rows(); + constrNum = SJT.cols(); + } + } +#endif + + if(debugMode==IterationLevel) { + SolverReportingManager::Manager().LogQRSystemInformation(*this, paramsNum, constrNum, rank); + } + + if (J.rows() > 0) { +#ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX + SolverReportingManager::Manager().LogMatrix("R", R); + + SolverReportingManager::Manager().LogMatrix("R2", R2); + +#ifdef SPARSE_Q_MATRIX + SolverReportingManager::Manager().LogMatrix("Q", Q); +#endif +#endif + } +} + void System::clearSubSystems() { isInit = false; diff --git a/src/Mod/Sketcher/App/planegcs/GCS.h b/src/Mod/Sketcher/App/planegcs/GCS.h index 85a5fd7130..cee752fa36 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.h +++ b/src/Mod/Sketcher/App/planegcs/GCS.h @@ -27,6 +27,17 @@ #include #include +#include + +#define EIGEN_VERSION (EIGEN_WORLD_VERSION * 10000 \ ++ EIGEN_MAJOR_VERSION * 100 \ ++ EIGEN_MINOR_VERSION) + +#if EIGEN_VERSION >= 30202 + #define EIGEN_SPARSEQR_COMPATIBLE + #include +#endif + namespace GCS { /////////////////////////////////////// @@ -122,6 +133,14 @@ namespace GCS void makeReducedJacobian(Eigen::MatrixXd &J, std::map &jacobianconstraintmap, GCS::VEC_pD &pdiagnoselist, std::map< int , int> &tagmultiplicity); + void makeDenseQRDecomposition( Eigen::MatrixXd &J, std::map &jacobianconstraintmap, + Eigen::FullPivHouseholderQR& qrJT, + int ¶msNum, int &constrNum, int &rank, Eigen::MatrixXd &R); + + void makeSparseQRDecomposition( Eigen::MatrixXd &J, std::map &jacobianconstraintmap, + Eigen::SparseQR, Eigen::COLAMDOrdering > &SqrJT, + int ¶msNum, int &constrNum, int &rank, Eigen::MatrixXd &R); + #ifdef _GCS_EXTRACT_SOLVER_SUBSYSTEM_ void extractSubsystem(SubSystem *subsys, bool isRedundantsolving); #endif From 6e53dd0034ba7c59b54fd6977543b9d75a7601dd Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sun, 13 Dec 2020 08:43:02 +0100 Subject: [PATCH 34/65] GCS: Refactor dependent parameters identification which is only available for DenseQR --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 206 ++++++++++++-------------- src/Mod/Sketcher/App/planegcs/GCS.h | 9 ++ 2 files changed, 106 insertions(+), 109 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index ad77b95526..d76b568be8 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -3923,122 +3923,22 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); if(qrAlgorithm==EigenDenseQR){ makeDenseQRDecomposition( J, jacobianconstraintmap, qrJT, paramsNum, constrNum, rank, R); + + if (J.rows() > 0) { + identifyDependentGeometryParametersInTransposedJacobianDenseQRDecomposition( + qrJT, + pdiagnoselist, + paramsNum, rank); + } } else if(qrAlgorithm==EigenSparseQR){ makeSparseQRDecomposition( J, jacobianconstraintmap, SqrJT, paramsNum, constrNum, rank, R); } + + if (J.rows() > 0) { - // DETECTING CONSTRAINT SOLVER PARAMETERS - // - // NOTE: This is only true for dense QR with full pivoting, because solve parameters get reordered. - // I am unable to adapt it to Sparse QR. (abdullah). See: - // - // https://stackoverflow.com/questions/49009771/getting-rows-transpositions-with-sparse-qr - // https://forum.kde.org/viewtopic.php?f=74&t=151239 - // - // R (original version, not R here which is trimmed to not have empty rows) - // has paramsNum rows, the first "rank" rows correspond to parameters that are constraint - - // Calculate the Permutation matrix from the Transposition matrix - Eigen::PermutationMatrix rowPermutations; - - rowPermutations.setIdentity(paramsNum); - - if(qrAlgorithm==EigenDenseQR){ // P.J.P' = Q.R see https://eigen.tuxfamily.org/dox/classEigen_1_1FullPivHouseholderQR.html - const MatrixIndexType rowTranspositions = qrJT.rowsTranspositions(); - - for(int k = 0; k < rank; ++k) - rowPermutations.applyTranspositionOnTheRight(k, rowTranspositions.coeff(k)); - } -#ifdef EIGEN_SPARSEQR_COMPATIBLE - else if(qrAlgorithm==EigenSparseQR){ - // J.P = Q.R, see https://eigen.tuxfamily.org/dox/classEigen_1_1SparseQR.html - // There is no rowsTransposition in this QR decomposition. - // TODO: This detection method won't work for SparseQR - } - -#endif - -#ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX - std::stringstream stream; -#endif - -// params (in the order of J) shown as independent from QR - std::set indepParamCols; - std::set depParamCols; - - for (int j=0; j < rank; j++) { - - int origRow = rowPermutations.indices()[j]; - - indepParamCols.insert(origRow); - - // NOTE: Q*R = transpose(J), so the row of R corresponds to the col of J (the rows of transpose(J)). - // The cols of J are the parameters, the rows are the constraints. -#ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX - stream << "R row " << j << " = J col " << origRow << std::endl; -#endif - } - -#ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX - std::string tmp = stream.str(); - - SolverReportingManager::Manager().LogString(tmp); -#endif - - // If not independent, must be dependent - for(int j=0; j < paramsNum; j++) { - auto result = indepParamCols.find(j); - if(result == indepParamCols.end()) { - depParamCols.insert(j); - } - } - - // Last (NumParams-rank) rows of Q construct the dependent part of J - // in conjunction with the R matrix - // Last (NumParams-rank) cols of Q never contribute as R is zero after the rank - /*std::set associatedParamCols; - for(int i = rank; i < paramsNum; i++) { - for(int j = 0; j < rank; j++) { - if(fabs(Q(i,j))>1e-10) { - for(int k = j; k < constrNum; k++) { - if(fabs(R(j,k))>1e-10) { - associatedParamCols.insert(k); - } - } - } - } - } - - for( auto param : associatedParamCols) { - auto pos = indepParamCols.find(rowPermutations.indices()[param]); - - if(pos!=indepParamCols.end()) { - indepParamCols.erase(pos); - } - }*/ -#ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX - stream.flush(); - - stream << "Indep params: ["; - for(auto indep :indepParamCols) - stream << indep; - stream << "]" << std::endl; - - stream << "Dep params: ["; - for(auto dep :depParamCols) - stream << dep; - stream << "]" << std::endl; - - tmp = stream.str(); - SolverReportingManager::Manager().LogString(tmp); -#endif - for( auto param : depParamCols) { - pdependentparameters.push_back(pdiagnoselist[param]); - } - // Detecting conflicting or redundant constraints if (constrNum > rank) { // conflicting or redundant constraints for (int i=1; i < rank; i++) { @@ -4365,6 +4265,94 @@ void System::makeSparseQRDecomposition( Eigen::MatrixXd &J, std::map &j } } +void System::identifyDependentGeometryParametersInTransposedJacobianDenseQRDecomposition( + const Eigen::FullPivHouseholderQR& qrJT, + const GCS::VEC_pD &pdiagnoselist, + int paramsNum, int rank) +{ + // DETECTING CONSTRAINT SOLVER PARAMETERS + // + // NOTE: This is only true for dense QR with full pivoting, because solve parameters get reordered. + // I am unable to adapt it to Sparse QR. (abdullah). See: + // + // https://stackoverflow.com/questions/49009771/getting-rows-transpositions-with-sparse-qr + // https://forum.kde.org/viewtopic.php?f=74&t=151239 + // + // R (original version, not R here which is trimmed to not have empty rows) + // has paramsNum rows, the first "rank" rows correspond to parameters that are constraint + + // Calculate the Permutation matrix from the Transposition matrix + Eigen::PermutationMatrix rowPermutations; + + rowPermutations.setIdentity(paramsNum); + + // P.J.P' = Q.R see https://eigen.tuxfamily.org/dox/classEigen_1_1FullPivHouseholderQR.html + const MatrixIndexType rowTranspositions = qrJT.rowsTranspositions(); + + for(int k = 0; k < rank; ++k) + rowPermutations.applyTranspositionOnTheRight(k, rowTranspositions.coeff(k)); + +#ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX + std::stringstream stream; +#endif + + // params (in the order of J) shown as independent from QR + std::set indepParamCols; + std::set depParamCols; + + for (int j=0; j < rank; j++) { + + int origRow = rowPermutations.indices()[j]; + + indepParamCols.insert(origRow); + + // NOTE: Q*R = transpose(J), so the row of R corresponds to the col of J (the rows of transpose(J)). + // The cols of J are the parameters, the rows are the constraints. +#ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX + stream << "R row " << j << " = J col " << origRow << std::endl; +#endif + } + +#ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX + std::string tmp = stream.str(); + + SolverReportingManager::Manager().LogString(tmp); +#endif + + // If not independent, must be dependent + for(int j=0; j < paramsNum; j++) { + auto result = indepParamCols.find(j); + if(result == indepParamCols.end()) { + depParamCols.insert(j); + } + } + +#ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX + stream.flush(); + + stream << "Indep params: ["; + for(auto indep :indepParamCols) + stream << indep; + stream << "]" << std::endl; + + stream << "Dep params: ["; + for(auto dep :depParamCols) + stream << dep; + stream << "]" << std::endl; + + tmp = stream.str(); + SolverReportingManager::Manager().LogString(tmp); +#endif + + + for( auto param : depParamCols) { + pdependentparameters.push_back(pdiagnoselist[param]); + } + +} + + + void System::clearSubSystems() { isInit = false; diff --git a/src/Mod/Sketcher/App/planegcs/GCS.h b/src/Mod/Sketcher/App/planegcs/GCS.h index cee752fa36..5f1fc73fdd 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.h +++ b/src/Mod/Sketcher/App/planegcs/GCS.h @@ -141,6 +141,15 @@ namespace GCS Eigen::SparseQR, Eigen::COLAMDOrdering > &SqrJT, int ¶msNum, int &constrNum, int &rank, Eigen::MatrixXd &R); + // This function name is long for a reason: + // - Only for DenseQR + // - Only for Transposed Jacobian QR decomposition + void identifyDependentGeometryParametersInTransposedJacobianDenseQRDecomposition( + const Eigen::FullPivHouseholderQR& qrJT, + const GCS::VEC_pD &pdiagnoselist, + int paramsNum, int rank + ); + #ifdef _GCS_EXTRACT_SOLVER_SUBSYSTEM_ void extractSubsystem(SubSystem *subsys, bool isRedundantsolving); #endif From 28f6978a0dad22904a4fb1c0ef3401465c317b58 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sun, 13 Dec 2020 14:06:30 +0100 Subject: [PATCH 35/65] GCS: Refactor diagnose identification of conflicting and redundant constraints --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 408 ++++++++++++++------------ src/Mod/Sketcher/App/planegcs/GCS.h | 16 +- 2 files changed, 229 insertions(+), 195 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index d76b568be8..becbefc867 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -3929,217 +3929,59 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); qrJT, pdiagnoselist, paramsNum, rank); + + // Detecting conflicting or redundant constraints + if (constrNum > rank) { // conflicting or redundant constraints + + int nonredundantconstrNum; + + identifyConflictingRedundantConstraints(alg, qrJT, jacobianconstraintmap, tagmultiplicity, pdiagnoselist, + R, constrNum, rank, nonredundantconstrNum); + + if (paramsNum == rank && nonredundantconstrNum > rank) { // over-constrained + hasDiagnosis = true; + dofs = paramsNum - nonredundantconstrNum; + return dofs; + } + } + + hasDiagnosis = true; + dofs = paramsNum - rank; + return dofs; } } else if(qrAlgorithm==EigenSparseQR){ makeSparseQRDecomposition( J, jacobianconstraintmap, SqrJT, paramsNum, constrNum, rank, R); - } + if (J.rows() > 0) { + // Detecting conflicting or redundant constraints + if (constrNum > rank) { // conflicting or redundant constraints + int nonredundantconstrNum; - if (J.rows() > 0) { + identifyConflictingRedundantConstraints(alg, SqrJT, jacobianconstraintmap, tagmultiplicity, pdiagnoselist, + R, constrNum, rank, nonredundantconstrNum); - // Detecting conflicting or redundant constraints - if (constrNum > rank) { // conflicting or redundant constraints - for (int i=1; i < rank; i++) { - // eliminate non zeros above pivot - assert(R(i,i) != 0); - for (int row=0; row < i; row++) { - if (R(row,i) != 0) { - double coef=R(row,i)/R(i,i); - R.block(row,i+1,1,constrNum-i-1) -= coef * R.block(i,i+1,1,constrNum-i-1); - R(row,i) = 0; - } - } - } - std::vector< std::vector > conflictGroups(constrNum-rank); - for (int j=rank; j < constrNum; j++) { - for (int row=0; row < rank; row++) { - if (fabs(R(row,j)) > 1e-10) { - int origCol = 0; - - if(qrAlgorithm==EigenDenseQR) - origCol=qrJT.colsPermutation().indices()[row]; -#ifdef EIGEN_SPARSEQR_COMPATIBLE - else if(qrAlgorithm==EigenSparseQR) - origCol=SqrJT.colsPermutation().indices()[row]; -#endif - //conflictGroups[j-rank].push_back(clist[origCol]); - conflictGroups[j-rank].push_back(clist[jacobianconstraintmap.at(origCol)]); - } - } - int origCol = 0; - - if(qrAlgorithm==EigenDenseQR) - origCol=qrJT.colsPermutation().indices()[j]; - -#ifdef EIGEN_SPARSEQR_COMPATIBLE - else if(qrAlgorithm==EigenSparseQR) - origCol=SqrJT.colsPermutation().indices()[j]; -#endif - //conflictGroups[j-rank].push_back(clist[origCol]); - conflictGroups[j-rank].push_back(clist[jacobianconstraintmap.at(origCol)]); - } - - // Augment the information regarding the group of constraints that are conflicting or redundant. - if(debugMode==IterationLevel) { - SolverReportingManager::Manager().LogGroupOfConstraints("Analysing groups of constraints of special interest", conflictGroups); - } - - // try to remove the conflicting constraints and solve the - // system in order to check if the removed constraints were - // just redundant but not really conflicting - std::set skipped; - SET_I satisfiedGroups; - while (1) { - std::map< Constraint *, SET_I > conflictingMap; - for (std::size_t i=0; i < conflictGroups.size(); i++) { - if (satisfiedGroups.count(i) == 0) { - for (std::size_t j=0; j < conflictGroups[i].size(); j++) { - Constraint *constr = conflictGroups[i][j]; - if (constr->getTag() != 0) // exclude constraints tagged with zero - conflictingMap[constr].insert(i); - } - } - } - if (conflictingMap.empty()) - break; - - int maxPopularity = 0; - Constraint *mostPopular = NULL; - for (std::map< Constraint *, SET_I >::const_iterator it=conflictingMap.begin(); - it != conflictingMap.end(); ++it) { - if (static_cast(it->second.size()) > maxPopularity || - (static_cast(it->second.size()) == maxPopularity && mostPopular && - tagmultiplicity.at(it->first->getTag()) < tagmultiplicity.at(mostPopular->getTag())) || - - (static_cast(it->second.size()) == maxPopularity && mostPopular && - tagmultiplicity.at(it->first->getTag()) == tagmultiplicity.at(mostPopular->getTag()) && - it->first->getTag() > mostPopular->getTag()) - - ) { - mostPopular = it->first; - maxPopularity = it->second.size(); - } - } - if (maxPopularity > 0) { - skipped.insert(mostPopular); - for (SET_I::const_iterator it=conflictingMap[mostPopular].begin(); - it != conflictingMap[mostPopular].end(); ++it) - satisfiedGroups.insert(*it); + if (paramsNum == rank && nonredundantconstrNum > rank) { // over-constrained + hasDiagnosis = true; + dofs = paramsNum - nonredundantconstrNum; + return dofs; } } - std::vector clistTmp; - clistTmp.reserve(clist.size()); - for (std::vector::iterator constr=clist.begin(); - constr != clist.end(); ++constr) { - if ((*constr)->isDriving() && skipped.count(*constr) == 0) - clistTmp.push_back(*constr); - } - - SubSystem *subSysTmp = new SubSystem(clistTmp, pdiagnoselist); - int res = solve(subSysTmp,true,alg,true); - - if(debugMode==Minimal || debugMode==IterationLevel) { - std::string solvername; - switch (alg) { - case 0: - solvername = "BFGS"; - break; - case 1: // solving with the LevenbergMarquardt solver - solvername = "LevenbergMarquardt"; - break; - case 2: // solving with the BFGS solver - solvername = "DogLeg"; - break; - } - - Base::Console().Log("Sketcher::RedundantSolving-%s-\n",solvername.c_str()); - } - - if (res == Success) { - subSysTmp->applySolution(); - for (std::set::const_iterator constr=skipped.begin(); - constr != skipped.end(); ++constr) { - double err = (*constr)->error(); - if (err * err < convergenceRedundant) - redundant.insert(*constr); - } - resetToReference(); - - if(debugMode==Minimal || debugMode==IterationLevel) { - Base::Console().Log("Sketcher Redundant solving: %d redundants\n",redundant.size()); - } - - std::vector< std::vector > conflictGroupsOrig=conflictGroups; - conflictGroups.clear(); - for (int i=conflictGroupsOrig.size()-1; i >= 0; i--) { - bool isRedundant = false; - for (std::size_t j=0; j < conflictGroupsOrig[i].size(); j++) { - if (redundant.count(conflictGroupsOrig[i][j]) > 0) { - isRedundant = true; - - if(debugMode==IterationLevel) { - Base::Console().Log("(Partially) Redundant, Group %d, index %d, Tag: %d\n", i,j, (conflictGroupsOrig[i][j])->getTag()); - } - - break; - } - } - if (!isRedundant) - conflictGroups.push_back(conflictGroupsOrig[i]); - else - constrNum--; - } - } - delete subSysTmp; - - // simplified output of conflicting tags - SET_I conflictingTagsSet; - for (std::size_t i=0; i < conflictGroups.size(); i++) { - for (std::size_t j=0; j < conflictGroups[i].size(); j++) { - conflictingTagsSet.insert(conflictGroups[i][j]->getTag()); - } - } - conflictingTagsSet.erase(0); // exclude constraints tagged with zero - conflictingTags.resize(conflictingTagsSet.size()); - std::copy(conflictingTagsSet.begin(), conflictingTagsSet.end(), - conflictingTags.begin()); - - // output of redundant tags - SET_I redundantTagsSet; - for (std::set::iterator constr=redundant.begin(); - constr != redundant.end(); ++constr) - redundantTagsSet.insert((*constr)->getTag()); - // remove tags represented at least in one non-redundant constraint - for (std::vector::iterator constr=clist.begin(); - constr != clist.end(); ++constr) { - if (redundant.count(*constr) == 0) - redundantTagsSet.erase((*constr)->getTag()); - } - redundantTags.resize(redundantTagsSet.size()); - std::copy(redundantTagsSet.begin(), redundantTagsSet.end(), - redundantTags.begin()); - - if (paramsNum == rank && constrNum > rank) { // over-constrained - hasDiagnosis = true; - dofs = paramsNum - constrNum; - return dofs; - } + hasDiagnosis = true; + dofs = paramsNum - rank; + return dofs; } - - hasDiagnosis = true; - dofs = paramsNum - rank; - return dofs; } + hasDiagnosis = true; dofs = pdiagnoselist.size(); return dofs; } -void System::makeDenseQRDecomposition( Eigen::MatrixXd &J, std::map &jacobianconstraintmap, +void System::makeDenseQRDecomposition( const Eigen::MatrixXd &J, std::map &jacobianconstraintmap, Eigen::FullPivHouseholderQR& qrJT, int ¶msNum, int &constrNum, int &rank, Eigen::MatrixXd & R) { @@ -4190,7 +4032,7 @@ void System::makeDenseQRDecomposition( Eigen::MatrixXd &J, std::map &j #endif } -void System::makeSparseQRDecomposition( Eigen::MatrixXd &J, std::map &jacobianconstraintmap, +void System::makeSparseQRDecomposition( const Eigen::MatrixXd &J, std::map &jacobianconstraintmap, Eigen::SparseQR, Eigen::COLAMDOrdering > &SqrJT, int ¶msNum, int &constrNum, int &rank, Eigen::MatrixXd & R) { @@ -4351,6 +4193,186 @@ void System::identifyDependentGeometryParametersInTransposedJacobianDenseQRDecom } +template +void System::identifyConflictingRedundantConstraints( Algorithm alg, + const T & qrJT, + const std::map &jacobianconstraintmap, + const std::map< int , int> &tagmultiplicity, + GCS::VEC_pD &pdiagnoselist, + Eigen::MatrixXd &R, + int constrNum, int rank, + int &nonredundantconstrNum + ) +{ + for (int i=1; i < rank; i++) { + // eliminate non zeros above pivot + assert(R(i,i) != 0); + for (int row=0; row < i; row++) { + if (R(row,i) != 0) { + double coef=R(row,i)/R(i,i); + R.block(row,i+1,1,constrNum-i-1) -= coef * R.block(i,i+1,1,constrNum-i-1); + R(row,i) = 0; + } + } + } + + std::vector< std::vector > conflictGroups(constrNum-rank); + for (int j=rank; j < constrNum; j++) { + for (int row=0; row < rank; row++) { + if (fabs(R(row,j)) > 1e-10) { + int origCol = qrJT.colsPermutation().indices()[row]; + + conflictGroups[j-rank].push_back(clist[jacobianconstraintmap.at(origCol)]); + } + } + int origCol = qrJT.colsPermutation().indices()[j]; + + conflictGroups[j-rank].push_back(clist[jacobianconstraintmap.at(origCol)]); + } + + // Augment the information regarding the group of constraints that are conflicting or redundant. + if(debugMode==IterationLevel) { + SolverReportingManager::Manager().LogGroupOfConstraints("Analysing groups of constraints of special interest", conflictGroups); + } + + // try to remove the conflicting constraints and solve the + // system in order to check if the removed constraints were + // just redundant but not really conflicting + std::set skipped; + SET_I satisfiedGroups; + while (1) { + std::map< Constraint *, SET_I > conflictingMap; + for (std::size_t i=0; i < conflictGroups.size(); i++) { + if (satisfiedGroups.count(i) == 0) { + for (std::size_t j=0; j < conflictGroups[i].size(); j++) { + Constraint *constr = conflictGroups[i][j]; + if (constr->getTag() != 0) // exclude constraints tagged with zero + conflictingMap[constr].insert(i); + } + } + } + if (conflictingMap.empty()) + break; + + int maxPopularity = 0; + Constraint *mostPopular = NULL; + for (std::map< Constraint *, SET_I >::const_iterator it=conflictingMap.begin(); + it != conflictingMap.end(); ++it) { + if (static_cast(it->second.size()) > maxPopularity || + (static_cast(it->second.size()) == maxPopularity && mostPopular && + tagmultiplicity.at(it->first->getTag()) < tagmultiplicity.at(mostPopular->getTag())) || + + (static_cast(it->second.size()) == maxPopularity && mostPopular && + tagmultiplicity.at(it->first->getTag()) == tagmultiplicity.at(mostPopular->getTag()) && + it->first->getTag() > mostPopular->getTag()) + + ) { + mostPopular = it->first; + maxPopularity = it->second.size(); + } + } + if (maxPopularity > 0) { + skipped.insert(mostPopular); + for (SET_I::const_iterator it=conflictingMap[mostPopular].begin(); + it != conflictingMap[mostPopular].end(); ++it) + satisfiedGroups.insert(*it); + } + } + + std::vector clistTmp; + clistTmp.reserve(clist.size()); + for (std::vector::iterator constr=clist.begin(); + constr != clist.end(); ++constr) { + if ((*constr)->isDriving() && skipped.count(*constr) == 0) + clistTmp.push_back(*constr); + } + + SubSystem *subSysTmp = new SubSystem(clistTmp, pdiagnoselist); + int res = solve(subSysTmp,true,alg,true); + + if(debugMode==Minimal || debugMode==IterationLevel) { + std::string solvername; + switch (alg) { + case 0: + solvername = "BFGS"; + break; + case 1: // solving with the LevenbergMarquardt solver + solvername = "LevenbergMarquardt"; + break; + case 2: // solving with the BFGS solver + solvername = "DogLeg"; + break; + } + + Base::Console().Log("Sketcher::RedundantSolving-%s-\n",solvername.c_str()); + } + + if (res == Success) { + subSysTmp->applySolution(); + for (std::set::const_iterator constr=skipped.begin(); + constr != skipped.end(); ++constr) { + double err = (*constr)->error(); + if (err * err < convergenceRedundant) + redundant.insert(*constr); + } + resetToReference(); + + if(debugMode==Minimal || debugMode==IterationLevel) { + Base::Console().Log("Sketcher Redundant solving: %d redundants\n",redundant.size()); + } + + std::vector< std::vector > conflictGroupsOrig=conflictGroups; + conflictGroups.clear(); + for (int i=conflictGroupsOrig.size()-1; i >= 0; i--) { + bool isRedundant = false; + for (std::size_t j=0; j < conflictGroupsOrig[i].size(); j++) { + if (redundant.count(conflictGroupsOrig[i][j]) > 0) { + isRedundant = true; + + if(debugMode==IterationLevel) { + Base::Console().Log("(Partially) Redundant, Group %d, index %d, Tag: %d\n", i,j, (conflictGroupsOrig[i][j])->getTag()); + } + + break; + } + } + if (!isRedundant) + conflictGroups.push_back(conflictGroupsOrig[i]); + else + constrNum--; + } + } + delete subSysTmp; + + // simplified output of conflicting tags + SET_I conflictingTagsSet; + for (std::size_t i=0; i < conflictGroups.size(); i++) { + for (std::size_t j=0; j < conflictGroups[i].size(); j++) { + conflictingTagsSet.insert(conflictGroups[i][j]->getTag()); + } + } + conflictingTagsSet.erase(0); // exclude constraints tagged with zero + conflictingTags.resize(conflictingTagsSet.size()); + std::copy(conflictingTagsSet.begin(), conflictingTagsSet.end(), + conflictingTags.begin()); + + // output of redundant tags + SET_I redundantTagsSet; + for (std::set::iterator constr=redundant.begin(); + constr != redundant.end(); ++constr) + redundantTagsSet.insert((*constr)->getTag()); + // remove tags represented at least in one non-redundant constraint + for (std::vector::iterator constr=clist.begin(); + constr != clist.end(); ++constr) { + if (redundant.count(*constr) == 0) + redundantTagsSet.erase((*constr)->getTag()); + } + redundantTags.resize(redundantTagsSet.size()); + std::copy(redundantTagsSet.begin(), redundantTagsSet.end(), + redundantTags.begin()); + + nonredundantconstrNum = constrNum; +} void System::clearSubSystems() diff --git a/src/Mod/Sketcher/App/planegcs/GCS.h b/src/Mod/Sketcher/App/planegcs/GCS.h index 5f1fc73fdd..7442866f42 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.h +++ b/src/Mod/Sketcher/App/planegcs/GCS.h @@ -133,11 +133,11 @@ namespace GCS void makeReducedJacobian(Eigen::MatrixXd &J, std::map &jacobianconstraintmap, GCS::VEC_pD &pdiagnoselist, std::map< int , int> &tagmultiplicity); - void makeDenseQRDecomposition( Eigen::MatrixXd &J, std::map &jacobianconstraintmap, + void makeDenseQRDecomposition( const Eigen::MatrixXd &J, std::map &jacobianconstraintmap, Eigen::FullPivHouseholderQR& qrJT, int ¶msNum, int &constrNum, int &rank, Eigen::MatrixXd &R); - void makeSparseQRDecomposition( Eigen::MatrixXd &J, std::map &jacobianconstraintmap, + void makeSparseQRDecomposition( const Eigen::MatrixXd &J, std::map &jacobianconstraintmap, Eigen::SparseQR, Eigen::COLAMDOrdering > &SqrJT, int ¶msNum, int &constrNum, int &rank, Eigen::MatrixXd &R); @@ -150,6 +150,18 @@ namespace GCS int paramsNum, int rank ); + template + void identifyConflictingRedundantConstraints( Algorithm alg, + const T & qrJT, + const std::map &jacobianconstraintmap, + const std::map< int , int> &tagmultiplicity, + GCS::VEC_pD &pdiagnoselist, + Eigen::MatrixXd &R, + int constrNum, int rank, + int &nonredundantconstrNum + ); + + #ifdef _GCS_EXTRACT_SOLVER_SUBSYSTEM_ void extractSubsystem(SubSystem *subsys, bool isRedundantsolving); #endif From 909adbd93d119ca9ceb394b71ada81a6f25c9d5e Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sun, 13 Dec 2020 16:24:29 +0100 Subject: [PATCH 36/65] GCS: Refactor Non-Zero elimination over pivot --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 27 ++++++++++++++++----------- src/Mod/Sketcher/App/planegcs/GCS.h | 1 + 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index becbefc867..b4ccaa5277 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -4193,6 +4193,21 @@ void System::identifyDependentGeometryParametersInTransposedJacobianDenseQRDecom } +void System::eliminateNonZerosOverPivotInUpperTriangularMatrix( Eigen::MatrixXd &R, int rank) +{ + for (int i=1; i < rank; i++) { + // eliminate non zeros above pivot + assert(R(i,i) != 0); + for (int row=0; row < i; row++) { + if (R(row,i) != 0) { + double coef=R(row,i)/R(i,i); + R.block(row,i+1,1,R.cols()-i-1) -= coef * R.block(i,i+1,1,R.cols()-i-1); + R(row,i) = 0; + } + } + } +} + template void System::identifyConflictingRedundantConstraints( Algorithm alg, const T & qrJT, @@ -4204,17 +4219,7 @@ void System::identifyConflictingRedundantConstraints( Algorithm alg, int &nonredundantconstrNum ) { - for (int i=1; i < rank; i++) { - // eliminate non zeros above pivot - assert(R(i,i) != 0); - for (int row=0; row < i; row++) { - if (R(row,i) != 0) { - double coef=R(row,i)/R(i,i); - R.block(row,i+1,1,constrNum-i-1) -= coef * R.block(i,i+1,1,constrNum-i-1); - R(row,i) = 0; - } - } - } + eliminateNonZerosOverPivotInUpperTriangularMatrix(R, rank); std::vector< std::vector > conflictGroups(constrNum-rank); for (int j=rank; j < constrNum; j++) { diff --git a/src/Mod/Sketcher/App/planegcs/GCS.h b/src/Mod/Sketcher/App/planegcs/GCS.h index 7442866f42..13d4ba852d 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.h +++ b/src/Mod/Sketcher/App/planegcs/GCS.h @@ -161,6 +161,7 @@ namespace GCS int &nonredundantconstrNum ); + void eliminateNonZerosOverPivotInUpperTriangularMatrix(Eigen::MatrixXd &R, int rank); #ifdef _GCS_EXTRACT_SOLVER_SUBSYSTEM_ void extractSubsystem(SubSystem *subsys, bool isRedundantsolving); From 72c2830a52d349d4b42952f27bc46214ff92b0b8 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sun, 13 Dec 2020 16:44:41 +0100 Subject: [PATCH 37/65] GCS: reduce input parameters in makeDenseQRDecomposition --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 24 +++++++++++++++++------- src/Mod/Sketcher/App/planegcs/GCS.h | 4 ++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index b4ccaa5277..45628392ec 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -3915,14 +3915,15 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); #endif Eigen::MatrixXd R; - int paramsNum = 0; - int constrNum = 0; - int rank = 0; + int rank = 0; // rank is not cheap to retrieve from qrJT in DenseQR Eigen::FullPivHouseholderQR qrJT; if(qrAlgorithm==EigenDenseQR){ - makeDenseQRDecomposition( J, jacobianconstraintmap, qrJT, paramsNum, constrNum, rank, R); + makeDenseQRDecomposition( J, jacobianconstraintmap, qrJT, rank, R); + + int paramsNum = qrJT.rows(); + int constrNum = qrJT.cols(); if (J.rows() > 0) { identifyDependentGeometryParametersInTransposedJacobianDenseQRDecomposition( @@ -3951,7 +3952,10 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); } } else if(qrAlgorithm==EigenSparseQR){ - makeSparseQRDecomposition( J, jacobianconstraintmap, SqrJT, paramsNum, constrNum, rank, R); + makeSparseQRDecomposition( J, jacobianconstraintmap, SqrJT, rank, R); + + int paramsNum = SqrJT.rows(); + int constrNum = SqrJT.cols(); if (J.rows() > 0) { // Detecting conflicting or redundant constraints @@ -3983,7 +3987,7 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); void System::makeDenseQRDecomposition( const Eigen::MatrixXd &J, std::map &jacobianconstraintmap, Eigen::FullPivHouseholderQR& qrJT, - int ¶msNum, int &constrNum, int &rank, Eigen::MatrixXd & R) + int &rank, Eigen::MatrixXd & R) { #ifdef _GCS_DEBUG @@ -3995,6 +3999,9 @@ void System::makeDenseQRDecomposition( const Eigen::MatrixXd &J, std::map 0) { qrJT.compute(J.topRows(jacobianconstraintmap.size()).transpose()); //Eigen::MatrixXd Q = qrJT.matrixQ (); @@ -4034,7 +4041,7 @@ void System::makeDenseQRDecomposition( const Eigen::MatrixXd &J, std::map &jacobianconstraintmap, Eigen::SparseQR, Eigen::COLAMDOrdering > &SqrJT, - int ¶msNum, int &constrNum, int &rank, Eigen::MatrixXd & R) + int &rank, Eigen::MatrixXd & R) { #ifdef EIGEN_SPARSEQR_COMPATIBLE Eigen::SparseMatrix SJ; @@ -4055,6 +4062,9 @@ void System::makeSparseQRDecomposition( const Eigen::MatrixXd &J, std::map 0) { auto SJT = SJ.topRows(jacobianconstraintmap.size()).transpose(); diff --git a/src/Mod/Sketcher/App/planegcs/GCS.h b/src/Mod/Sketcher/App/planegcs/GCS.h index 13d4ba852d..8140f34e6c 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.h +++ b/src/Mod/Sketcher/App/planegcs/GCS.h @@ -135,11 +135,11 @@ namespace GCS void makeDenseQRDecomposition( const Eigen::MatrixXd &J, std::map &jacobianconstraintmap, Eigen::FullPivHouseholderQR& qrJT, - int ¶msNum, int &constrNum, int &rank, Eigen::MatrixXd &R); + int &rank, Eigen::MatrixXd &R); void makeSparseQRDecomposition( const Eigen::MatrixXd &J, std::map &jacobianconstraintmap, Eigen::SparseQR, Eigen::COLAMDOrdering > &SqrJT, - int ¶msNum, int &constrNum, int &rank, Eigen::MatrixXd &R); + int &rank, Eigen::MatrixXd &R); // This function name is long for a reason: // - Only for DenseQR From 0d5e120a2c9858c27388441b459ca016e78499b6 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sun, 13 Dec 2020 16:47:27 +0100 Subject: [PATCH 38/65] GCS: SparseQR compilation guards --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index 45628392ec..0855881808 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -3951,6 +3951,7 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); return dofs; } } +#ifdef EIGEN_SPARSEQR_COMPATIBLE else if(qrAlgorithm==EigenSparseQR){ makeSparseQRDecomposition( J, jacobianconstraintmap, SqrJT, rank, R); @@ -3978,7 +3979,7 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); return dofs; } } - +#endif hasDiagnosis = true; dofs = pdiagnoselist.size(); From c565ef6793d76b87101cb7bb742cdfb464d9fa11 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sun, 13 Dec 2020 17:20:40 +0100 Subject: [PATCH 39/65] GCS: makeSparseQRDecomposition generalise for transposed and non-transposed matrices --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 64 +++++++++++++++------------ src/Mod/Sketcher/App/planegcs/GCS.h | 2 +- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index 0855881808..421450777c 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -4042,35 +4042,40 @@ void System::makeDenseQRDecomposition( const Eigen::MatrixXd &J, std::map &jacobianconstraintmap, Eigen::SparseQR, Eigen::COLAMDOrdering > &SqrJT, - int &rank, Eigen::MatrixXd & R) + int &rank, Eigen::MatrixXd & R, bool transposeJ) { #ifdef EIGEN_SPARSEQR_COMPATIBLE - Eigen::SparseMatrix SJ; + Eigen::SparseMatrix SJ; // this creation is not optimized (done using triplets) // however the time this takes is negligible compared to the // time the QR decomposition itself takes SJ = J.sparseView(); SJ.makeCompressed(); -#endif -#ifdef _GCS_DEBUG + #ifdef _GCS_DEBUG SolverReportingManager::Manager().LogMatrix("J",J); -#endif + #endif -#ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX + #ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX Eigen::MatrixXd Q; // Obtaining the Q matrix with Sparse QR is buggy, see comments below Eigen::MatrixXd R2; // Intended for a trapezoidal matrix, where R is the top triangular matrix of the R2 trapezoidal matrix -#endif + #endif - int paramsNum = 0; - int constrNum = 0; + // For a transposed J SJG rows are paramsNum and cols are constrNum + // For a non-transposed J SJG rows are constrNum and cols are paramsNum + int rowsNum = 0; + int colsNum = 0; -#ifdef EIGEN_SPARSEQR_COMPATIBLE if (SJ.rows() > 0) { - auto SJT = SJ.topRows(jacobianconstraintmap.size()).transpose(); - if (SJT.rows() > 0 && SJT.cols() > 0) { - SqrJT.compute(SJT); + Eigen::SparseMatrix SJG; + if(transposeJ) + SJG = SJ.topRows(jacobianconstraintmap.size()).transpose(); + else + SJG = SJ.topRows(jacobianconstraintmap.size()); + + if (SJG.rows() > 0 && SJG.cols() > 0) { + SqrJT.compute(SJG); // Do not ask for Q Matrix!! // At Eigen 3.2 still has a bug that this only works for square matrices // if enabled it will crash @@ -4079,15 +4084,15 @@ void System::makeSparseQRDecomposition( const Eigen::MatrixXd &J, std::map= paramsNum) + if (colsNum >= rowsNum) R = SqrJT.matrixR().triangularView(); else - R = SqrJT.matrixR().topRows(constrNum) + R = SqrJT.matrixR().topRows(colsNum) .triangularView(); #ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX @@ -4095,27 +4100,28 @@ void System::makeSparseQRDecomposition( const Eigen::MatrixXd &J, std::map 0) { -#ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX - SolverReportingManager::Manager().LogMatrix("R", R); - SolverReportingManager::Manager().LogMatrix("R2", R2); + SolverReportingManager::Manager().LogMatrix("R", R); -#ifdef SPARSE_Q_MATRIX - SolverReportingManager::Manager().LogMatrix("Q", Q); -#endif -#endif + SolverReportingManager::Manager().LogMatrix("R2", R2); + + #ifdef SPARSE_Q_MATRIX + SolverReportingManager::Manager().LogMatrix("Q", Q); + #endif } + #endif //_GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX +#endif // EIGEN_SPARSEQR_COMPATIBLE } void System::identifyDependentGeometryParametersInTransposedJacobianDenseQRDecomposition( diff --git a/src/Mod/Sketcher/App/planegcs/GCS.h b/src/Mod/Sketcher/App/planegcs/GCS.h index 8140f34e6c..a771bd8728 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.h +++ b/src/Mod/Sketcher/App/planegcs/GCS.h @@ -139,7 +139,7 @@ namespace GCS void makeSparseQRDecomposition( const Eigen::MatrixXd &J, std::map &jacobianconstraintmap, Eigen::SparseQR, Eigen::COLAMDOrdering > &SqrJT, - int &rank, Eigen::MatrixXd &R); + int &rank, Eigen::MatrixXd &R, bool transposeJ = true); // This function name is long for a reason: // - Only for DenseQR From 0ef05d4f99f9915ea381599a2695cde86ef091af Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Mon, 14 Dec 2020 14:40:13 +0100 Subject: [PATCH 40/65] GCS: Detect dependent parameters (geometry) during diagnosis using two SparseQR factorisations --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index 421450777c..12c6f3dcc5 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -3959,6 +3959,43 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); int constrNum = SqrJT.cols(); if (J.rows() > 0) { + + // Get dependent parameters + { + Eigen::MatrixXd Rparams; + Eigen::SparseQR, Eigen::COLAMDOrdering > SqrJ; + + int nontransprank; // will be the same as for the transpose, but better use a different name + + makeSparseQRDecomposition( J, jacobianconstraintmap, SqrJ, nontransprank, Rparams, false); // do not transpose allow to diagnose parameters + + //int constrNum = SqrJ.rows(); // this is the other way around than for the transposed J + //int paramsNum = SqrJ.cols(); + + eliminateNonZerosOverPivotInUpperTriangularMatrix(Rparams, rank); + +#ifdef _GCS_DEBUG + SolverReportingManager::Manager().LogMatrix("Rparams", Rparams); +#endif + //std::vector< std::vector > dependencyGroups(SqrJ.cols()-rank); + for (int j=nontransprank; j < SqrJ.cols(); j++) { + for (int row=0; row < rank; row++) { + if (fabs(Rparams(row,j)) > 1e-10) { + int origCol = SqrJ.colsPermutation().indices()[row]; + + //dependencyGroups[j-rank].push_back(pdiagnoselist[origCol]); + pdependentparameters.push_back(pdiagnoselist[origCol]); + } + } + int origCol = SqrJ.colsPermutation().indices()[j]; + + //dependencyGroups[j-rank].push_back(pdiagnoselist[origCol]); + pdependentparameters.push_back(pdiagnoselist[origCol]); + } + + } + + // Detecting conflicting or redundant constraints if (constrNum > rank) { // conflicting or redundant constraints From 82b4cfeba6c5c2f871363850311af136518a5f1f Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Mon, 14 Dec 2020 16:14:06 +0100 Subject: [PATCH 41/65] GCS: Refactor new SparseQR Dependent Parameter detection code and const correctness --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 90 +++++++++++++++------------ src/Mod/Sketcher/App/planegcs/GCS.h | 13 +++- 2 files changed, 59 insertions(+), 44 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index 12c6f3dcc5..c5093970b9 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -3961,40 +3961,7 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); if (J.rows() > 0) { // Get dependent parameters - { - Eigen::MatrixXd Rparams; - Eigen::SparseQR, Eigen::COLAMDOrdering > SqrJ; - - int nontransprank; // will be the same as for the transpose, but better use a different name - - makeSparseQRDecomposition( J, jacobianconstraintmap, SqrJ, nontransprank, Rparams, false); // do not transpose allow to diagnose parameters - - //int constrNum = SqrJ.rows(); // this is the other way around than for the transposed J - //int paramsNum = SqrJ.cols(); - - eliminateNonZerosOverPivotInUpperTriangularMatrix(Rparams, rank); - -#ifdef _GCS_DEBUG - SolverReportingManager::Manager().LogMatrix("Rparams", Rparams); -#endif - //std::vector< std::vector > dependencyGroups(SqrJ.cols()-rank); - for (int j=nontransprank; j < SqrJ.cols(); j++) { - for (int row=0; row < rank; row++) { - if (fabs(Rparams(row,j)) > 1e-10) { - int origCol = SqrJ.colsPermutation().indices()[row]; - - //dependencyGroups[j-rank].push_back(pdiagnoselist[origCol]); - pdependentparameters.push_back(pdiagnoselist[origCol]); - } - } - int origCol = SqrJ.colsPermutation().indices()[j]; - - //dependencyGroups[j-rank].push_back(pdiagnoselist[origCol]); - pdependentparameters.push_back(pdiagnoselist[origCol]); - } - - } - + identifyDependentParametersSparseQR(J, jacobianconstraintmap, pdiagnoselist, true); // Detecting conflicting or redundant constraints if (constrNum > rank) { // conflicting or redundant constraints @@ -4023,7 +3990,8 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); return dofs; } -void System::makeDenseQRDecomposition( const Eigen::MatrixXd &J, std::map &jacobianconstraintmap, +void System::makeDenseQRDecomposition( const Eigen::MatrixXd &J, + const std::map &jacobianconstraintmap, Eigen::FullPivHouseholderQR& qrJT, int &rank, Eigen::MatrixXd & R) { @@ -4077,9 +4045,10 @@ void System::makeDenseQRDecomposition( const Eigen::MatrixXd &J, std::map &jacobianconstraintmap, +void System::makeSparseQRDecomposition( const Eigen::MatrixXd &J, + const std::map &jacobianconstraintmap, Eigen::SparseQR, Eigen::COLAMDOrdering > &SqrJT, - int &rank, Eigen::MatrixXd & R, bool transposeJ) + int &rank, Eigen::MatrixXd & R, bool transposeJ, bool silent) { #ifdef EIGEN_SPARSEQR_COMPATIBLE Eigen::SparseMatrix SJ; @@ -4091,7 +4060,8 @@ void System::makeSparseQRDecomposition( const Eigen::MatrixXd &J, std::map 0) { + if (J.rows() > 0 && !silent) { SolverReportingManager::Manager().LogMatrix("R", R); @@ -4161,6 +4130,45 @@ void System::makeSparseQRDecomposition( const Eigen::MatrixXd &J, std::map &jacobianconstraintmap, + const GCS::VEC_pD &pdiagnoselist, + bool silent) +{ + Eigen::MatrixXd Rparams; + Eigen::SparseQR, Eigen::COLAMDOrdering > SqrJ; + + int nontransprank; + + makeSparseQRDecomposition( J, jacobianconstraintmap, SqrJ, nontransprank, Rparams, false, true); // do not transpose allow to diagnose parameters + + //int constrNum = SqrJ.rows(); // this is the other way around than for the transposed J + //int paramsNum = SqrJ.cols(); + + eliminateNonZerosOverPivotInUpperTriangularMatrix(Rparams, nontransprank); + +#ifdef _GCS_DEBUG + if(!silent) + SolverReportingManager::Manager().LogMatrix("Rparams", Rparams); +#endif + //std::vector< std::vector > dependencyGroups(SqrJ.cols()-rank); + for (int j=nontransprank; j < SqrJ.cols(); j++) { + for (int row=0; row < nontransprank; row++) { + if (fabs(Rparams(row,j)) > 1e-10) { + int origCol = SqrJ.colsPermutation().indices()[row]; + + //dependencyGroups[j-rank].push_back(pdiagnoselist[origCol]); + pdependentparameters.push_back(pdiagnoselist[origCol]); + } + } + int origCol = SqrJ.colsPermutation().indices()[j]; + + //dependencyGroups[j-rank].push_back(pdiagnoselist[origCol]); + pdependentparameters.push_back(pdiagnoselist[origCol]); + } +} + + void System::identifyDependentGeometryParametersInTransposedJacobianDenseQRDecomposition( const Eigen::FullPivHouseholderQR& qrJT, const GCS::VEC_pD &pdiagnoselist, diff --git a/src/Mod/Sketcher/App/planegcs/GCS.h b/src/Mod/Sketcher/App/planegcs/GCS.h index a771bd8728..3690899827 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.h +++ b/src/Mod/Sketcher/App/planegcs/GCS.h @@ -133,13 +133,15 @@ namespace GCS void makeReducedJacobian(Eigen::MatrixXd &J, std::map &jacobianconstraintmap, GCS::VEC_pD &pdiagnoselist, std::map< int , int> &tagmultiplicity); - void makeDenseQRDecomposition( const Eigen::MatrixXd &J, std::map &jacobianconstraintmap, + void makeDenseQRDecomposition( const Eigen::MatrixXd &J, + const std::map &jacobianconstraintmap, Eigen::FullPivHouseholderQR& qrJT, int &rank, Eigen::MatrixXd &R); - void makeSparseQRDecomposition( const Eigen::MatrixXd &J, std::map &jacobianconstraintmap, + void makeSparseQRDecomposition( const Eigen::MatrixXd &J, + const std::map &jacobianconstraintmap, Eigen::SparseQR, Eigen::COLAMDOrdering > &SqrJT, - int &rank, Eigen::MatrixXd &R, bool transposeJ = true); + int &rank, Eigen::MatrixXd &R, bool transposeJ = true, bool silent = false); // This function name is long for a reason: // - Only for DenseQR @@ -163,6 +165,11 @@ namespace GCS void eliminateNonZerosOverPivotInUpperTriangularMatrix(Eigen::MatrixXd &R, int rank); + void identifyDependentParametersSparseQR( const Eigen::MatrixXd &J, + const std::map &jacobianconstraintmap, + const GCS::VEC_pD &pdiagnoselist, + bool silent=true); + #ifdef _GCS_EXTRACT_SOLVER_SUBSYSTEM_ void extractSubsystem(SubSystem *subsys, bool isRedundantsolving); #endif From f0dcb12475663fc43c30609c76d18d6a6898eb78 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Mon, 14 Dec 2020 16:23:08 +0100 Subject: [PATCH 42/65] GCS: Diagnose() - Separate DenseQR and SparseQR --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index c5093970b9..5c50a1bd69 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -3905,21 +3905,18 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); // QR decomposition method selection: SparseQR vs DenseQR -#ifdef EIGEN_SPARSEQR_COMPATIBLE - Eigen::SparseQR, Eigen::COLAMDOrdering > SqrJT; -#else +#ifndef EIGEN_SPARSEQR_COMPATIBLE if(qrAlgorithm==EigenSparseQR){ Base::Console().Warning("SparseQR not supported by you current version of Eigen. It requires Eigen 3.2.2 or higher. Falling back to Dense QR\n"); qrAlgorithm=EigenDenseQR; } #endif - Eigen::MatrixXd R; - int rank = 0; // rank is not cheap to retrieve from qrJT in DenseQR - - Eigen::FullPivHouseholderQR qrJT; - if(qrAlgorithm==EigenDenseQR){ + int rank = 0; // rank is not cheap to retrieve from qrJT in DenseQR + Eigen::MatrixXd R; + Eigen::FullPivHouseholderQR qrJT; + makeDenseQRDecomposition( J, jacobianconstraintmap, qrJT, rank, R); int paramsNum = qrJT.rows(); @@ -3953,6 +3950,11 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); } #ifdef EIGEN_SPARSEQR_COMPATIBLE else if(qrAlgorithm==EigenSparseQR){ + int rank = 0; + Eigen::MatrixXd R; + Eigen::SparseQR, Eigen::COLAMDOrdering > SqrJT; + + makeSparseQRDecomposition( J, jacobianconstraintmap, SqrJT, rank, R); int paramsNum = SqrJT.rows(); From fac1ffa6240f9237be02171bd55b7652cf634f6e Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Mon, 14 Dec 2020 17:14:39 +0100 Subject: [PATCH 43/65] GCS: Rewrite diagnose() so that it has a single return point --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 30 +++++++++++---------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index 5c50a1bd69..9c46f4a001 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -3873,6 +3873,10 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); makeReducedJacobian(J, jacobianconstraintmap, pdiagnoselist, tagmultiplicity); + // this function will exit with a diagnosis and, unless overriden by functions below, with full DoFs + hasDiagnosis = true; + dofs = pdiagnoselist.size(); + // There is a legacy decision to use QR decomposition. I (abdullah) do not know all the // consideration taken in that decisions. I see that: // - QR decomposition is able to provide information about the rank and redundant/conflicting constraints @@ -3928,6 +3932,8 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); pdiagnoselist, paramsNum, rank); + dofs = paramsNum - rank; // unless overconstraint, which will be overridden below + // Detecting conflicting or redundant constraints if (constrNum > rank) { // conflicting or redundant constraints @@ -3936,20 +3942,15 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); identifyConflictingRedundantConstraints(alg, qrJT, jacobianconstraintmap, tagmultiplicity, pdiagnoselist, R, constrNum, rank, nonredundantconstrNum); - if (paramsNum == rank && nonredundantconstrNum > rank) { // over-constrained - hasDiagnosis = true; + if (paramsNum == rank && nonredundantconstrNum > rank) // over-constrained dofs = paramsNum - nonredundantconstrNum; - return dofs; - } } - - hasDiagnosis = true; - dofs = paramsNum - rank; - return dofs; } } + #ifdef EIGEN_SPARSEQR_COMPATIBLE else if(qrAlgorithm==EigenSparseQR){ + int rank = 0; Eigen::MatrixXd R; Eigen::SparseQR, Eigen::COLAMDOrdering > SqrJT; @@ -3965,6 +3966,8 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); // Get dependent parameters identifyDependentParametersSparseQR(J, jacobianconstraintmap, pdiagnoselist, true); + dofs = paramsNum - rank; // unless overconstraint, which will be overridden below + // Detecting conflicting or redundant constraints if (constrNum > rank) { // conflicting or redundant constraints @@ -3973,22 +3976,13 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); identifyConflictingRedundantConstraints(alg, SqrJT, jacobianconstraintmap, tagmultiplicity, pdiagnoselist, R, constrNum, rank, nonredundantconstrNum); - if (paramsNum == rank && nonredundantconstrNum > rank) { // over-constrained - hasDiagnosis = true; + if (paramsNum == rank && nonredundantconstrNum > rank) // over-constrained dofs = paramsNum - nonredundantconstrNum; - return dofs; - } } - - hasDiagnosis = true; - dofs = paramsNum - rank; - return dofs; } } #endif - hasDiagnosis = true; - dofs = pdiagnoselist.size(); return dofs; } From 56705d0c3a4c8ef1a52372f00cbb5a909303c73c Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Mon, 14 Dec 2020 17:49:09 +0100 Subject: [PATCH 44/65] GCS: QR profiling debug code --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index 9c46f4a001..f072d6f3db 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -28,6 +28,7 @@ //#define _GCS_DEBUG //#define _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX //#define _DEBUG_TO_FILE // Many matrices surpass the report view string size. +#define PROFILE_DIAGNOSE #undef _GCS_DEBUG #undef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX #undef _DEBUG_TO_FILE @@ -3917,6 +3918,9 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); #endif if(qrAlgorithm==EigenDenseQR){ + #ifdef PROFILE_DIAGNOSE + Base::TimeInfo DenseQR_start_time; + #endif int rank = 0; // rank is not cheap to retrieve from qrJT in DenseQR Eigen::MatrixXd R; Eigen::FullPivHouseholderQR qrJT; @@ -3946,11 +3950,20 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); dofs = paramsNum - nonredundantconstrNum; } } + #ifdef PROFILE_DIAGNOSE + Base::TimeInfo DenseQR_end_time; + + auto SolveTime = Base::TimeInfo::diffTimeF(DenseQR_start_time,DenseQR_end_time); + + Base::Console().Log("\nDenseQR - Lapsed Time: %f seconds\n", SolveTime); + #endif } #ifdef EIGEN_SPARSEQR_COMPATIBLE else if(qrAlgorithm==EigenSparseQR){ - + #ifdef PROFILE_DIAGNOSE + Base::TimeInfo SparseQR_start_time; + #endif int rank = 0; Eigen::MatrixXd R; Eigen::SparseQR, Eigen::COLAMDOrdering > SqrJT; @@ -3980,6 +3993,14 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); dofs = paramsNum - nonredundantconstrNum; } } + + #ifdef PROFILE_DIAGNOSE + Base::TimeInfo SparseQR_end_time; + + auto SolveTime = Base::TimeInfo::diffTimeF(SparseQR_start_time,SparseQR_end_time); + + Base::Console().Log("\nSparseQR - Lapsed Time: %f seconds\n", SolveTime); + #endif } #endif From 718d17b6344086e169d6138c8643d1a8d9d7f501 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Mon, 14 Dec 2020 19:37:04 +0100 Subject: [PATCH 45/65] GCS: Make QR decompositions for constraints and parameters run asynchronously --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 28 +++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index f072d6f3db..10269e3104 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -53,6 +53,7 @@ #include #include #include +#include #include "GCS.h" #include "qp_eq.h" @@ -3964,20 +3965,23 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); #ifdef PROFILE_DIAGNOSE Base::TimeInfo SparseQR_start_time; #endif - int rank = 0; - Eigen::MatrixXd R; - Eigen::SparseQR, Eigen::COLAMDOrdering > SqrJT; - - - makeSparseQRDecomposition( J, jacobianconstraintmap, SqrJT, rank, R); - - int paramsNum = SqrJT.rows(); - int constrNum = SqrJT.cols(); - if (J.rows() > 0) { + int rank = 0; + Eigen::MatrixXd R; + Eigen::SparseQR, Eigen::COLAMDOrdering > SqrJT; + // Here we enforce calculating the two QR decompositions in parallel + // Care to wait() for the future before any prospective detection of conflicting/redundant, because the redundant solve + // modifies pdiagnoselist and it would not be thread-safe + // + // identifyDependentParametersSparseQR(J, jacobianconstraintmap, pdiagnoselist, true) + auto fut = std::async(std::launch::async,&System::identifyDependentParametersSparseQR,this,J,jacobianconstraintmap, pdiagnoselist, true); - // Get dependent parameters - identifyDependentParametersSparseQR(J, jacobianconstraintmap, pdiagnoselist, true); + makeSparseQRDecomposition( J, jacobianconstraintmap, SqrJT, rank, R); + + int paramsNum = SqrJT.rows(); + int constrNum = SqrJT.cols(); + + fut.wait(); // wait for the execution of identifyDependentParametersSparseQR to finish dofs = paramsNum - rank; // unless overconstraint, which will be overridden below From 9bb917b91bb669dd714fb37c7ec5ab915795ceca Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Mon, 14 Dec 2020 20:23:27 +0100 Subject: [PATCH 46/65] GCS: Asynchronous full geometry parameter identification for Dense QR --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 126 ++++++++++++++++++-------- src/Mod/Sketcher/App/planegcs/GCS.h | 14 ++- 2 files changed, 100 insertions(+), 40 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index 10269e3104..5977a1b76c 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -3922,20 +3922,27 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); #ifdef PROFILE_DIAGNOSE Base::TimeInfo DenseQR_start_time; #endif - int rank = 0; // rank is not cheap to retrieve from qrJT in DenseQR - Eigen::MatrixXd R; - Eigen::FullPivHouseholderQR qrJT; - - makeDenseQRDecomposition( J, jacobianconstraintmap, qrJT, rank, R); - - int paramsNum = qrJT.rows(); - int constrNum = qrJT.cols(); - if (J.rows() > 0) { - identifyDependentGeometryParametersInTransposedJacobianDenseQRDecomposition( - qrJT, - pdiagnoselist, - paramsNum, rank); + int rank = 0; // rank is not cheap to retrieve from qrJT in DenseQR + Eigen::MatrixXd R; + Eigen::FullPivHouseholderQR qrJT; + // Here we enforce calculating the two QR decompositions in parallel + // Care to wait() for the future before any prospective detection of conflicting/redundant, because the redundant solve + // modifies pdiagnoselist and it would not be thread-safe + // + // identifyDependentParametersSparseQR(J, jacobianconstraintmap, pdiagnoselist, true) + auto fut = std::async(std::launch::async,&System::identifyDependentParametersDenseQR,this,J,jacobianconstraintmap, pdiagnoselist, true); + + makeDenseQRDecomposition( J, jacobianconstraintmap, qrJT, rank, R); + + int paramsNum = qrJT.rows(); + int constrNum = qrJT.cols(); + + // This function is legacy code that was used to obtain partial geometry dependency information from a SINGLE Dense QR + // decomposition. I am reluctant to remove it from here until everything new is well tested. + //identifyDependentGeometryParametersInTransposedJacobianDenseQRDecomposition( qrJT, pdiagnoselist, paramsNum, rank); + + fut.wait(); // wait for the execution of identifyDependentParametersSparseQR to finish dofs = paramsNum - rank; // unless overconstraint, which will be overridden below @@ -4014,11 +4021,12 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); void System::makeDenseQRDecomposition( const Eigen::MatrixXd &J, const std::map &jacobianconstraintmap, Eigen::FullPivHouseholderQR& qrJT, - int &rank, Eigen::MatrixXd & R) + int &rank, Eigen::MatrixXd & R, bool transposeJ, bool silent) { #ifdef _GCS_DEBUG - SolverReportingManager::Manager().LogMatrix("J",J); + if(!silent) + SolverReportingManager::Manager().LogMatrix("J",J); #endif #ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX @@ -4026,23 +4034,37 @@ void System::makeDenseQRDecomposition( const Eigen::MatrixXd &J, Eigen::MatrixXd R2; // Intended for a trapezoidal matrix, where R is the top triangular matrix of the R2 trapezoidal matrix #endif - int paramsNum = 0; - int constrNum = 0; + // For a transposed J SJG rows are paramsNum and cols are constrNum + // For a non-transposed J SJG rows are constrNum and cols are paramsNum + int rowsNum = 0; + int colsNum = 0; if (J.rows() > 0) { - qrJT.compute(J.topRows(jacobianconstraintmap.size()).transpose()); - //Eigen::MatrixXd Q = qrJT.matrixQ (); - - paramsNum = qrJT.rows(); - constrNum = qrJT.cols(); - qrJT.setThreshold(qrpivotThreshold); - rank = qrJT.rank(); - - if (constrNum >= paramsNum) - R = qrJT.matrixQR().triangularView(); + Eigen::MatrixXd JG; + if(transposeJ) + JG = J.topRows(jacobianconstraintmap.size()).transpose(); else - R = qrJT.matrixQR().topRows(constrNum) - .triangularView(); + JG = J.topRows(jacobianconstraintmap.size()); + + if (JG.rows() > 0 && JG.cols() > 0) { + + qrJT.compute(JG); + + rowsNum = qrJT.rows(); + colsNum = qrJT.cols(); + qrJT.setThreshold(qrpivotThreshold); + rank = qrJT.rank(); + + if (colsNum >= rowsNum) + R = qrJT.matrixQR().triangularView(); + else + R = qrJT.matrixQR().topRows(colsNum) + .triangularView(); + } + else { + rowsNum = JG.rows(); + colsNum = JG.cols(); + } #ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX R2 = qrJT.matrixQR(); @@ -4050,12 +4072,12 @@ void System::makeDenseQRDecomposition( const Eigen::MatrixXd &J, #endif } - if(debugMode==IterationLevel) { - SolverReportingManager::Manager().LogQRSystemInformation(*this, paramsNum, constrNum, rank); + if(debugMode==IterationLevel && !silent) { + SolverReportingManager::Manager().LogQRSystemInformation(*this, rowsNum, colsNum, rank); } #ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX - if (J.rows() > 0) { + if (J.rows() > 0 && !silent) { SolverReportingManager::Manager().LogMatrix("R", R); SolverReportingManager::Manager().LogMatrix("R2", R2); @@ -4151,45 +4173,71 @@ void System::makeSparseQRDecomposition( const Eigen::MatrixXd &J, #endif // EIGEN_SPARSEQR_COMPATIBLE } +void System::identifyDependentParametersDenseQR( const Eigen::MatrixXd &J, + const std::map &jacobianconstraintmap, + const GCS::VEC_pD &pdiagnoselist, + bool silent) +{ + Eigen::FullPivHouseholderQR qrJ; + Eigen::MatrixXd Rparams; + + int rank; + + makeDenseQRDecomposition( J, jacobianconstraintmap, qrJ, rank, Rparams, false, true); + + identifyDependentParameters(qrJ, Rparams, rank, pdiagnoselist, silent); +} + void System::identifyDependentParametersSparseQR( const Eigen::MatrixXd &J, const std::map &jacobianconstraintmap, const GCS::VEC_pD &pdiagnoselist, bool silent) { - Eigen::MatrixXd Rparams; Eigen::SparseQR, Eigen::COLAMDOrdering > SqrJ; + Eigen::MatrixXd Rparams; int nontransprank; makeSparseQRDecomposition( J, jacobianconstraintmap, SqrJ, nontransprank, Rparams, false, true); // do not transpose allow to diagnose parameters + identifyDependentParameters(SqrJ, Rparams, nontransprank, pdiagnoselist, silent); +} + +template +void System::identifyDependentParameters( T & qrJ, + Eigen::MatrixXd &Rparams, + int rank, + const GCS::VEC_pD &pdiagnoselist, + bool silent) +{ + + //int constrNum = SqrJ.rows(); // this is the other way around than for the transposed J //int paramsNum = SqrJ.cols(); - eliminateNonZerosOverPivotInUpperTriangularMatrix(Rparams, nontransprank); + eliminateNonZerosOverPivotInUpperTriangularMatrix(Rparams, rank); #ifdef _GCS_DEBUG if(!silent) SolverReportingManager::Manager().LogMatrix("Rparams", Rparams); #endif //std::vector< std::vector > dependencyGroups(SqrJ.cols()-rank); - for (int j=nontransprank; j < SqrJ.cols(); j++) { - for (int row=0; row < nontransprank; row++) { + for (int j=rank; j < qrJ.cols(); j++) { + for (int row=0; row < rank; row++) { if (fabs(Rparams(row,j)) > 1e-10) { - int origCol = SqrJ.colsPermutation().indices()[row]; + int origCol = qrJ.colsPermutation().indices()[row]; //dependencyGroups[j-rank].push_back(pdiagnoselist[origCol]); pdependentparameters.push_back(pdiagnoselist[origCol]); } } - int origCol = SqrJ.colsPermutation().indices()[j]; + int origCol = qrJ.colsPermutation().indices()[j]; //dependencyGroups[j-rank].push_back(pdiagnoselist[origCol]); pdependentparameters.push_back(pdiagnoselist[origCol]); } } - void System::identifyDependentGeometryParametersInTransposedJacobianDenseQRDecomposition( const Eigen::FullPivHouseholderQR& qrJT, const GCS::VEC_pD &pdiagnoselist, diff --git a/src/Mod/Sketcher/App/planegcs/GCS.h b/src/Mod/Sketcher/App/planegcs/GCS.h index 3690899827..88f44e5dc2 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.h +++ b/src/Mod/Sketcher/App/planegcs/GCS.h @@ -136,7 +136,7 @@ namespace GCS void makeDenseQRDecomposition( const Eigen::MatrixXd &J, const std::map &jacobianconstraintmap, Eigen::FullPivHouseholderQR& qrJT, - int &rank, Eigen::MatrixXd &R); + int &rank, Eigen::MatrixXd &R, bool transposeJ = true, bool silent = false); void makeSparseQRDecomposition( const Eigen::MatrixXd &J, const std::map &jacobianconstraintmap, @@ -170,6 +170,18 @@ namespace GCS const GCS::VEC_pD &pdiagnoselist, bool silent=true); + void identifyDependentParametersDenseQR( const Eigen::MatrixXd &J, + const std::map &jacobianconstraintmap, + const GCS::VEC_pD &pdiagnoselist, + bool silent=true); + + template + void identifyDependentParameters( T & qrJ, + Eigen::MatrixXd &Rparams, + int rank, + const GCS::VEC_pD &pdiagnoselist, + bool silent=true); + #ifdef _GCS_EXTRACT_SOLVER_SUBSYSTEM_ void extractSubsystem(SubSystem *subsys, bool isRedundantsolving); #endif From 229c515b7240398c359b674bc90f6ea3124ab78e Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Tue, 15 Dec 2020 06:24:28 +0100 Subject: [PATCH 47/65] GCS: Deactivate Debug code and fix warnings in non-Debug mode --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index 5977a1b76c..34b0725041 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -28,7 +28,7 @@ //#define _GCS_DEBUG //#define _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX //#define _DEBUG_TO_FILE // Many matrices surpass the report view string size. -#define PROFILE_DIAGNOSE +//#define PROFILE_DIAGNOSE #undef _GCS_DEBUG #undef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX #undef _DEBUG_TO_FILE @@ -4210,7 +4210,7 @@ void System::identifyDependentParameters( T & qrJ, const GCS::VEC_pD &pdiagnoselist, bool silent) { - + (void) silent; // silent is only used in debug code, but it is important as Base::Console is not thread-safe. Removes warning in non Debug mode. //int constrNum = SqrJ.rows(); // this is the other way around than for the transposed J //int paramsNum = SqrJ.cols(); From 3605770b68b718c39ad525a2f577ba9ee7eac93f Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Tue, 15 Dec 2020 19:14:38 +0100 Subject: [PATCH 48/65] GCS: Interface to query whether the used diagnose matrix is empty ================================================================= Diagnose constraints do not include among others driven constraints. This function allows to know whether the solver is considering an empty matrix. --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 7 +++++++ src/Mod/Sketcher/App/planegcs/GCS.h | 3 +++ 2 files changed, 10 insertions(+) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index 34b0725041..f4542bcacd 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -417,6 +417,7 @@ System::System() , hasUnknowns(false) , hasDiagnosis(false) , isInit(false) + , emptyDiagnoseMatrix(true) , maxIter(100) , maxIterRedundant(100) , sketchSizeMultiplier(false) @@ -533,6 +534,8 @@ void System::clear() hasUnknowns = false; hasDiagnosis = false; + emptyDiagnoseMatrix = true; + redundant.clear(); conflictingTags.clear(); redundantTags.clear(); @@ -3844,6 +3847,7 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); // eventually crash. This fixes issues #0002372/#0002373. if (plist.empty() || (plist.size() - pdrivenlist.size()) == 0) { hasDiagnosis = true; + emptyDiagnoseMatrix = true; dofs = 0; return dofs; } @@ -3879,6 +3883,9 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); hasDiagnosis = true; dofs = pdiagnoselist.size(); + if(J.rows() > 0) + emptyDiagnoseMatrix = false; + // There is a legacy decision to use QR decomposition. I (abdullah) do not know all the // consideration taken in that decisions. I see that: // - QR decomposition is able to provide information about the rank and redundant/conflicting constraints diff --git a/src/Mod/Sketcher/App/planegcs/GCS.h b/src/Mod/Sketcher/App/planegcs/GCS.h index 88f44e5dc2..c441252b17 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.h +++ b/src/Mod/Sketcher/App/planegcs/GCS.h @@ -127,6 +127,8 @@ namespace GCS bool hasDiagnosis; // if dofs, conflictingTags, redundantTags are up to date bool isInit; // if plists, clists, reductionmaps are up to date + bool emptyDiagnoseMatrix; // false only if there is at least one driving constraint. + int solve_BFGS(SubSystem *subsys, bool isFine=true, bool isRedundantsolving=false); int solve_LM(SubSystem *subsys, bool isRedundantsolving=false); int solve_DL(SubSystem *subsys, bool isRedundantsolving=false); @@ -354,6 +356,7 @@ namespace GCS { redundantOut = hasDiagnosis ? redundantTags : VEC_I(0); } void getDependentParams(VEC_pD &pconstraintplistOut) const { pconstraintplistOut = pdependentparameters;} + bool isEmptyDiagnoseMatrix() const {return emptyDiagnoseMatrix;} }; From 54373798cd7557f9e85b02569041cc6ec20576ac Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sat, 19 Dec 2020 07:58:49 +0100 Subject: [PATCH 49/65] GCS: Augment information provided by GCS with groups of dependent parameters --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 7 ++++--- src/Mod/Sketcher/App/planegcs/GCS.h | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index f4542bcacd..aab3eea25f 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -531,6 +531,7 @@ void System::clear() pdrivenlist.clear(); pIndex.clear(); pdependentparameters.clear(); + pdependentelementgroups.clear(); hasUnknowns = false; hasDiagnosis = false; @@ -4228,19 +4229,19 @@ void System::identifyDependentParameters( T & qrJ, if(!silent) SolverReportingManager::Manager().LogMatrix("Rparams", Rparams); #endif - //std::vector< std::vector > dependencyGroups(SqrJ.cols()-rank); + pdependentelementgroups.resize(qrJ.cols()-rank); for (int j=rank; j < qrJ.cols(); j++) { for (int row=0; row < rank; row++) { if (fabs(Rparams(row,j)) > 1e-10) { int origCol = qrJ.colsPermutation().indices()[row]; - //dependencyGroups[j-rank].push_back(pdiagnoselist[origCol]); + pdependentelementgroups[j-rank].push_back(pdiagnoselist[origCol]); pdependentparameters.push_back(pdiagnoselist[origCol]); } } int origCol = qrJ.colsPermutation().indices()[j]; - //dependencyGroups[j-rank].push_back(pdiagnoselist[origCol]); + pdependentelementgroups[j-rank].push_back(pdiagnoselist[origCol]); pdependentparameters.push_back(pdiagnoselist[origCol]); } } diff --git a/src/Mod/Sketcher/App/planegcs/GCS.h b/src/Mod/Sketcher/App/planegcs/GCS.h index c441252b17..8b6b82e34c 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.h +++ b/src/Mod/Sketcher/App/planegcs/GCS.h @@ -104,6 +104,10 @@ namespace GCS VEC_pD pdependentparameters; // list of dependent parameters by the system + // This is a map of primary and secondary identifiers that are found dependent by the solver + // GCS ignores from a type point + std::vector< std::vector > pdependentelementgroups; + std::vector clist; std::map c2p; // constraint to parameter adjacency list std::map > p2c; // parameter to constraint adjacency list From 813afc0617592d161c2b37b9fd27814bd1b9c4f8 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Fri, 18 Dec 2020 15:51:18 +0100 Subject: [PATCH 50/65] GCS: renaming parameters to make them more descriptive --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 18 +++++++++--------- src/Mod/Sketcher/App/planegcs/GCS.h | 10 ++++++---- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index aab3eea25f..bea0c3ce5d 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -406,7 +406,7 @@ typedef boost::adjacency_list Gra System::System() : plist(0) , pdrivenlist(0) - , pdependentparameters(0) + , pDependentParameters(0) , clist(0) , c2p() , p2c() @@ -530,8 +530,8 @@ void System::clear() plist.clear(); pdrivenlist.clear(); pIndex.clear(); - pdependentparameters.clear(); - pdependentelementgroups.clear(); + pDependentParameters.clear(); + pDependentParametersGroups.clear(); hasUnknowns = false; hasDiagnosis = false; @@ -4229,20 +4229,20 @@ void System::identifyDependentParameters( T & qrJ, if(!silent) SolverReportingManager::Manager().LogMatrix("Rparams", Rparams); #endif - pdependentelementgroups.resize(qrJ.cols()-rank); + pDependentParametersGroups.resize(qrJ.cols()-rank); for (int j=rank; j < qrJ.cols(); j++) { for (int row=0; row < rank; row++) { if (fabs(Rparams(row,j)) > 1e-10) { int origCol = qrJ.colsPermutation().indices()[row]; - pdependentelementgroups[j-rank].push_back(pdiagnoselist[origCol]); - pdependentparameters.push_back(pdiagnoselist[origCol]); + pDependentParametersGroups[j-rank].insert(pdiagnoselist[origCol]); + pDependentParameters.push_back(pdiagnoselist[origCol]); } } int origCol = qrJ.colsPermutation().indices()[j]; - pdependentelementgroups[j-rank].push_back(pdiagnoselist[origCol]); - pdependentparameters.push_back(pdiagnoselist[origCol]); + pDependentParametersGroups[j-rank].insert(pdiagnoselist[origCol]); + pDependentParameters.push_back(pdiagnoselist[origCol]); } } @@ -4327,7 +4327,7 @@ void System::identifyDependentGeometryParametersInTransposedJacobianDenseQRDecom for( auto param : depParamCols) { - pdependentparameters.push_back(pdiagnoselist[param]); + pDependentParameters.push_back(pdiagnoselist[param]); } } diff --git a/src/Mod/Sketcher/App/planegcs/GCS.h b/src/Mod/Sketcher/App/planegcs/GCS.h index 8b6b82e34c..5c862cee06 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.h +++ b/src/Mod/Sketcher/App/planegcs/GCS.h @@ -102,11 +102,11 @@ namespace GCS VEC_pD pdrivenlist; // list of parameters of driven constraints MAP_pD_I pIndex; - VEC_pD pdependentparameters; // list of dependent parameters by the system + VEC_pD pDependentParameters; // list of dependent parameters by the system // This is a map of primary and secondary identifiers that are found dependent by the solver // GCS ignores from a type point - std::vector< std::vector > pdependentelementgroups; + std::vector< std::set > pDependentParametersGroups; std::vector clist; std::map c2p; // constraint to parameter adjacency list @@ -358,8 +358,10 @@ namespace GCS { conflictingOut = hasDiagnosis ? conflictingTags : VEC_I(0); } void getRedundant(VEC_I &redundantOut) const { redundantOut = hasDiagnosis ? redundantTags : VEC_I(0); } - void getDependentParams(VEC_pD &pconstraintplistOut) const - { pconstraintplistOut = pdependentparameters;} + void getDependentParams(VEC_pD &pdependentparameterlist) const + { pdependentparameterlist = pDependentParameters;} + void getDependentParamsGroups(std::vector> &pdependentparametergroups) const + { pdependentparametergroups = pDependentParametersGroups;} bool isEmptyDiagnoseMatrix() const {return emptyDiagnoseMatrix;} }; From 076eebc0a9fc58bb99492052d7997d3179ce9b3c Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sat, 19 Dec 2020 06:38:46 +0100 Subject: [PATCH 51/65] GCS: Change asynchronous launch policy to default to avoid having to take care of exceptions on oversubscription ================================================================================================================ std:async can take two policies one that forces the task to be run in parallel another (deferred) that runs the task when wait is called on the future (kind of lazy evaluation). Default policy is let the system decide (an or of both policies). Nobody is a better position than the system to know its load and whether it is possible to run the task in parallel or not. If the system cannot allocate a thread and parallel processing is enforced (as before this commit), then it will throw an exception. In the present case we would catch it and run the task sequencially. Thus, it makes sense to let the system decide from the beginning and save the exception and the handling. In tests I have only managed to see it run in parallel with the default policy. --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index bea0c3ce5d..35a4a97b24 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -3934,12 +3934,15 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); int rank = 0; // rank is not cheap to retrieve from qrJT in DenseQR Eigen::MatrixXd R; Eigen::FullPivHouseholderQR qrJT; - // Here we enforce calculating the two QR decompositions in parallel + // Here we give the system the possibility to run the two QR decompositions in parallel, depending on the load of the system + // so we are using the default std::launch::async | std::launch::deferred policy, as nobody better than the system + // nows if it can run the task in parallel or is oversubscribed and should deferred it. // Care to wait() for the future before any prospective detection of conflicting/redundant, because the redundant solve - // modifies pdiagnoselist and it would not be thread-safe + // modifies pdiagnoselist and it would NOT be thread-safe. Care to call the thread with silent=true, unless the present thread + // does not use Base::Console, as it is not thread-safe to use them in both at the same time. // - // identifyDependentParametersSparseQR(J, jacobianconstraintmap, pdiagnoselist, true) - auto fut = std::async(std::launch::async,&System::identifyDependentParametersDenseQR,this,J,jacobianconstraintmap, pdiagnoselist, true); + // identifyDependentParametersDenseQR(J, jacobianconstraintmap, pdiagnoselist, true) + auto fut = std::async(&System::identifyDependentParametersDenseQR,this,J,jacobianconstraintmap, pdiagnoselist, true); makeDenseQRDecomposition( J, jacobianconstraintmap, qrJT, rank, R); @@ -3984,12 +3987,15 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); int rank = 0; Eigen::MatrixXd R; Eigen::SparseQR, Eigen::COLAMDOrdering > SqrJT; - // Here we enforce calculating the two QR decompositions in parallel + // Here we give the system the possibility to run the two QR decompositions in parallel, depending on the load of the system + // so we are using the default std::launch::async | std::launch::deferred policy, as nobody better than the system + // nows if it can run the task in parallel or is oversubscribed and should deferred it. // Care to wait() for the future before any prospective detection of conflicting/redundant, because the redundant solve - // modifies pdiagnoselist and it would not be thread-safe + // modifies pdiagnoselist and it would NOT be thread-safe. Care to call the thread with silent=true, unless the present thread + // does not use Base::Console, as it is not thread-safe to use them in both at the same time. // // identifyDependentParametersSparseQR(J, jacobianconstraintmap, pdiagnoselist, true) - auto fut = std::async(std::launch::async,&System::identifyDependentParametersSparseQR,this,J,jacobianconstraintmap, pdiagnoselist, true); + auto fut = std::async(&System::identifyDependentParametersSparseQR,this,J,jacobianconstraintmap, pdiagnoselist, true); makeSparseQRDecomposition( J, jacobianconstraintmap, SqrJT, rank, R); From 7a76b9b7d2aa5081a264bd8cc7898f1a5df18c39 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sat, 19 Dec 2020 10:27:20 +0100 Subject: [PATCH 52/65] GCS: Set SparseQR conditional compilation guards --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 7 +++++-- src/Mod/Sketcher/App/planegcs/GCS.h | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index 35a4a97b24..292e9fe73d 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -4102,12 +4102,13 @@ void System::makeDenseQRDecomposition( const Eigen::MatrixXd &J, #endif } +#ifdef EIGEN_SPARSEQR_COMPATIBLE void System::makeSparseQRDecomposition( const Eigen::MatrixXd &J, const std::map &jacobianconstraintmap, Eigen::SparseQR, Eigen::COLAMDOrdering > &SqrJT, int &rank, Eigen::MatrixXd & R, bool transposeJ, bool silent) { -#ifdef EIGEN_SPARSEQR_COMPATIBLE + Eigen::SparseMatrix SJ; // this creation is not optimized (done using triplets) @@ -4184,8 +4185,8 @@ void System::makeSparseQRDecomposition( const Eigen::MatrixXd &J, #endif } #endif //_GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX -#endif // EIGEN_SPARSEQR_COMPATIBLE } +#endif // EIGEN_SPARSEQR_COMPATIBLE void System::identifyDependentParametersDenseQR( const Eigen::MatrixXd &J, const std::map &jacobianconstraintmap, @@ -4202,6 +4203,7 @@ void System::identifyDependentParametersDenseQR( const Eigen::MatrixXd &J, identifyDependentParameters(qrJ, Rparams, rank, pdiagnoselist, silent); } +#ifdef EIGEN_SPARSEQR_COMPATIBLE void System::identifyDependentParametersSparseQR( const Eigen::MatrixXd &J, const std::map &jacobianconstraintmap, const GCS::VEC_pD &pdiagnoselist, @@ -4216,6 +4218,7 @@ void System::identifyDependentParametersSparseQR( const Eigen::MatrixXd &J, identifyDependentParameters(SqrJ, Rparams, nontransprank, pdiagnoselist, silent); } +#endif template void System::identifyDependentParameters( T & qrJ, diff --git a/src/Mod/Sketcher/App/planegcs/GCS.h b/src/Mod/Sketcher/App/planegcs/GCS.h index 5c862cee06..7a1bd83524 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.h +++ b/src/Mod/Sketcher/App/planegcs/GCS.h @@ -144,11 +144,12 @@ namespace GCS Eigen::FullPivHouseholderQR& qrJT, int &rank, Eigen::MatrixXd &R, bool transposeJ = true, bool silent = false); +#ifdef EIGEN_SPARSEQR_COMPATIBLE void makeSparseQRDecomposition( const Eigen::MatrixXd &J, const std::map &jacobianconstraintmap, Eigen::SparseQR, Eigen::COLAMDOrdering > &SqrJT, int &rank, Eigen::MatrixXd &R, bool transposeJ = true, bool silent = false); - +#endif // This function name is long for a reason: // - Only for DenseQR // - Only for Transposed Jacobian QR decomposition @@ -171,10 +172,12 @@ namespace GCS void eliminateNonZerosOverPivotInUpperTriangularMatrix(Eigen::MatrixXd &R, int rank); +#ifdef EIGEN_SPARSEQR_COMPATIBLE void identifyDependentParametersSparseQR( const Eigen::MatrixXd &J, const std::map &jacobianconstraintmap, const GCS::VEC_pD &pdiagnoselist, bool silent=true); +#endif void identifyDependentParametersDenseQR( const Eigen::MatrixXd &J, const std::map &jacobianconstraintmap, From 573a850b993452b7a16c9f3ba8cbc8a308333222 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Tue, 15 Dec 2020 07:22:58 +0100 Subject: [PATCH 53/65] Sketcher: Add ability to update a GeometryExtension of the solver --- src/Mod/Sketcher/App/Sketch.cpp | 8 ++++++++ src/Mod/Sketcher/App/Sketch.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/Mod/Sketcher/App/Sketch.cpp b/src/Mod/Sketcher/App/Sketch.cpp index 108ff572cc..308511440b 100644 --- a/src/Mod/Sketcher/App/Sketch.cpp +++ b/src/Mod/Sketcher/App/Sketch.cpp @@ -1063,6 +1063,14 @@ std::vector Sketch::extractGeometry(bool withConstructionEleme return temp; } +void Sketch::updateExtension(int geoId, std::unique_ptr && ext) +{ + geoId = checkGeoId(geoId); + + Geoms[geoId].geo->setExtension(std::move(ext)); + +} + Py::Tuple Sketch::getPyGeometry(void) const { Py::Tuple tuple(Geoms.size()); diff --git a/src/Mod/Sketcher/App/Sketch.h b/src/Mod/Sketcher/App/Sketch.h index 2e35a40cdb..7ecd0a0f00 100644 --- a/src/Mod/Sketcher/App/Sketch.h +++ b/src/Mod/Sketcher/App/Sketch.h @@ -88,6 +88,8 @@ public: /// returns the actual geometry std::vector extractGeometry(bool withConstructionElements=true, bool withExternalElements=false) const; + + void updateExtension(int geoId, std::unique_ptr && ext); /// get the geometry as python objects Py::Tuple getPyGeometry(void) const; From 5a20215e65c79531baf04c1c58ef29ec86a3d458 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Tue, 15 Dec 2020 06:37:44 +0100 Subject: [PATCH 54/65] Sketcher: new SolverGeometryExtension to store solver information within the geometry --- src/Mod/Sketcher/App/AppSketcher.cpp | 2 + src/Mod/Sketcher/App/CMakeLists.txt | 2 + .../Sketcher/App/SolverGeometryExtension.cpp | 66 +++++++++++++ .../Sketcher/App/SolverGeometryExtension.h | 94 +++++++++++++++++++ 4 files changed, 164 insertions(+) create mode 100644 src/Mod/Sketcher/App/SolverGeometryExtension.cpp create mode 100644 src/Mod/Sketcher/App/SolverGeometryExtension.h diff --git a/src/Mod/Sketcher/App/AppSketcher.cpp b/src/Mod/Sketcher/App/AppSketcher.cpp index c5ba7c20a8..2fa61fea1f 100644 --- a/src/Mod/Sketcher/App/AppSketcher.cpp +++ b/src/Mod/Sketcher/App/AppSketcher.cpp @@ -33,6 +33,7 @@ #include "SketchObject.h" #include "SketchGeometryExtension.h" #include "ExternalGeometryExtension.h" +#include "SolverGeometryExtension.h" #include "GeometryFacade.h" #include "ExternalGeometryFacade.h" #include "Constraint.h" @@ -80,6 +81,7 @@ PyMOD_INIT_FUNC(Sketcher) Sketcher::SketchGeometryExtension ::init(); Sketcher::ExternalGeometryExtension ::init(); + Sketcher::SolverGeometryExtension ::init(); Sketcher::GeometryFacade ::init(); Sketcher::ExternalGeometryFacade ::init(); Sketcher::SketchObjectSF ::init(); diff --git a/src/Mod/Sketcher/App/CMakeLists.txt b/src/Mod/Sketcher/App/CMakeLists.txt index 4ab1022875..c934e31fe5 100644 --- a/src/Mod/Sketcher/App/CMakeLists.txt +++ b/src/Mod/Sketcher/App/CMakeLists.txt @@ -58,6 +58,8 @@ SET(Features_SRCS SketchGeometryExtension.h ExternalGeometryExtension.cpp ExternalGeometryExtension.h + SolverGeometryExtension.cpp + SolverGeometryExtension.h SketchObject.cpp SketchObject.h SketchAnalysis.h diff --git a/src/Mod/Sketcher/App/SolverGeometryExtension.cpp b/src/Mod/Sketcher/App/SolverGeometryExtension.cpp new file mode 100644 index 0000000000..c05cfbe73b --- /dev/null +++ b/src/Mod/Sketcher/App/SolverGeometryExtension.cpp @@ -0,0 +1,66 @@ +/*************************************************************************** + * Copyright (c) 2019 Abdullah Tahiri * + * * + * 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 + +#include "SolverGeometryExtension.h" + +using namespace Sketcher; + + +//---------- Geometry Extension +TYPESYSTEM_SOURCE(Sketcher::SolverGeometryExtension,Part::GeometryExtension) + +SolverGeometryExtension::SolverGeometryExtension(): + Edge(SolverGeometryExtension::Dependent), + Start(SolverGeometryExtension::Dependent), + Mid(SolverGeometryExtension::Dependent), + End(SolverGeometryExtension::Dependent) +{ + +} + +std::unique_ptr SolverGeometryExtension::copy(void) const +{ + auto cpy = std::make_unique(); + + cpy->Edge = this->Edge; + cpy->Start = this->Start; + cpy->End = this->End; + cpy->Mid = this->Mid; + + cpy->setName(this->getName()); // Base Class + +#if defined (__GNUC__) && (__GNUC__ <=4) + return std::move(cpy); +#else + return cpy; +#endif +} + +PyObject * SolverGeometryExtension::getPyObject(void) +{ + THROWM(Base::NotImplementedError, "SolverGeometryExtension does not have a Python counterpart"); +} + diff --git a/src/Mod/Sketcher/App/SolverGeometryExtension.h b/src/Mod/Sketcher/App/SolverGeometryExtension.h new file mode 100644 index 0000000000..abc531ec39 --- /dev/null +++ b/src/Mod/Sketcher/App/SolverGeometryExtension.h @@ -0,0 +1,94 @@ +/*************************************************************************** + * Copyright (c) 2019 Abdullah Tahiri * + * * + * 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 SKETCHER_SOLVERGEOMETRYEXTENSION_H +#define SKETCHER_SOLVERGEOMETRYEXTENSION_H + +#include + +#include + +namespace Sketcher +{ + +class SketcherExport SolverGeometryExtension : public Part::GeometryExtension +{ + TYPESYSTEM_HEADER_WITH_OVERRIDE(); +public: + enum SolverStatus { + FullyConstraint = 0, + NotFullyConstraint = 1, + NumSolverStatus + }; + + enum ParameterStatus { + Dependent = 0, + Independent = 1, + NumParameterStatus + }; + + SolverGeometryExtension(); + SolverGeometryExtension(long cid); + virtual ~SolverGeometryExtension() override = default; + + virtual std::unique_ptr copy(void) const override; + + virtual PyObject *getPyObject(void) override; + + SolverStatus getGeometry() const {return ( Edge == Independent && + Start == Independent && + End == Independent && + Mid == Independent) ? FullyConstraint : NotFullyConstraint;} + + ParameterStatus getEdge() const {return Edge;} + void setEdge(ParameterStatus status) {Edge = status;} + + ParameterStatus getStart() const {return Start;} + void setStart(ParameterStatus status) {Start = status;} + + ParameterStatus getMid() const {return Mid;} + void setMid(ParameterStatus status) {Mid = status;} + + ParameterStatus getEnd() const {return End;} + void setEnd(ParameterStatus status) {End = status;} + + void init(ParameterStatus status) { + Edge = status; + Start = status; + Mid = status; + End = status; + } + +private: + SolverGeometryExtension(const SolverGeometryExtension&) = default; + +private: + ParameterStatus Edge; + ParameterStatus Start; + ParameterStatus Mid; + ParameterStatus End; +}; + +} //namespace Sketcher + + +#endif // SKETCHER_SOLVERGEOMETRYEXTENSION_H From 0888c0dd6457728babef1ada819dfc7fc5ee6335 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Tue, 15 Dec 2020 06:40:50 +0100 Subject: [PATCH 55/65] Sketcher: Sketch.cpp - use SolverGeometryExtension as mechanism to convey dependent parameter information ========================================================================================================= Former mechanism with hasDependentParameters is not flexible enough for the new kind of information. This commit further enhances the calculation of dependent parameters and dependent parameter groups by caching the parameter-geoelement (GeoId, PointPos) relationship during geometry creation. This commit provides traditional information whether a parameter is dependent via SolverGeometryExtension. New enhanced information about groups of dependent parameters are available via the Sketch API. --- src/Mod/Sketcher/App/Sketch.cpp | 294 ++++++++++++++++++++++---------- src/Mod/Sketcher/App/Sketch.h | 12 +- 2 files changed, 214 insertions(+), 92 deletions(-) diff --git a/src/Mod/Sketcher/App/Sketch.cpp b/src/Mod/Sketcher/App/Sketch.cpp index 308511440b..6ff57bde8b 100644 --- a/src/Mod/Sketcher/App/Sketch.cpp +++ b/src/Mod/Sketcher/App/Sketch.cpp @@ -55,10 +55,13 @@ #include #include -#include "Sketch.h" #include "Constraint.h" #include "GeometryFacade.h" +#include "SolverGeometryExtension.h" + +#include "Sketch.h" + using namespace Sketcher; using namespace Base; @@ -105,6 +108,9 @@ void Sketch::clear(void) if (*it) delete *it; FixParameters.clear(); + param2geoelement.clear(); + pDependencyGroups.clear(); + // deleting the geometry copied into this sketch for (std::vector::iterator it = Geoms.begin(); it != Geoms.end(); ++it) if (it->geo) delete it->geo; @@ -159,7 +165,7 @@ int Sketch::setUpSketch(const std::vector &GeoList, GCSsys.initSolution(defaultSolverRedundant); GCSsys.getConflicting(Conflicting); GCSsys.getRedundant(Redundant); - GCSsys.getDependentParams(pconstraintplistOut); + GCSsys.getDependentParams(pDependentParametersList); calculateDependentParametersElements(); @@ -179,106 +185,114 @@ void Sketch::clearTemporaryConstraints(void) void Sketch::calculateDependentParametersElements(void) { + // initialize solve extensions to a know state for(auto geo : Geoms) { - std::vector ownparams; - GCS::Curve * pCurve = nullptr; - switch(geo.type) { - case Point: - { - GCS::Point & point = Points[geo.startPointId]; - for(auto param : pconstraintplistOut) { - if (param == point.x || param == point.y) { - point.hasDependentParameters = true; - break; - } - } - } - break; - case Line: - { - GCS::Line & line = Lines[geo.index]; - line.PushOwnParams(ownparams); - pCurve = &line; + if(!geo.geo->hasExtension(Sketcher::SolverGeometryExtension::getClassTypeId())) + geo.geo->setExtension(std::make_unique()); + auto solvext = std::static_pointer_cast( + geo.geo->getExtension(Sketcher::SolverGeometryExtension::getClassTypeId()).lock()); + + if(GCSsys.isEmptyDiagnoseMatrix()) + solvext->init(SolverGeometryExtension::Dependent); + else + solvext->init(SolverGeometryExtension::Independent); + } + + for(auto param : pDependentParametersList) { + + //auto element = param2geoelement.at(param); + auto element = param2geoelement.find(param); + + if (element != param2geoelement.end()) { + auto solvext = std::static_pointer_cast( + Geoms[element->second.first].geo->getExtension(Sketcher::SolverGeometryExtension::getClassTypeId()).lock()); + + switch(element->second.second) { + case none: + solvext->setEdge(SolverGeometryExtension::Dependent); + break; + case start: + solvext->setStart(SolverGeometryExtension::Dependent); + break; + case end: + solvext->setEnd(SolverGeometryExtension::Dependent); + break; + case mid: + solvext->setMid(SolverGeometryExtension::Dependent); + break; } - break; - case Arc: - { - GCS::Arc & arc = Arcs[geo.index]; - arc.PushOwnParams(ownparams); - pCurve = &arc; - } - break; - case Circle: - { - GCS::Circle & c = Circles[geo.index]; - c.PushOwnParams(ownparams); - pCurve = &c; - } - break; - case Ellipse: - { - GCS::Ellipse & e = Ellipses[geo.index]; - e.PushOwnParams(ownparams); - pCurve = &e; - } - break; - case ArcOfEllipse: - { - GCS::ArcOfEllipse & aoe = ArcsOfEllipse[geo.index]; - aoe.PushOwnParams(ownparams); - pCurve = &aoe; - } - break; - case ArcOfHyperbola: - { - GCS::ArcOfHyperbola & aoh = ArcsOfHyperbola[geo.index]; - aoh.PushOwnParams(ownparams); - pCurve = &aoh; - } - break; - case ArcOfParabola: - { - GCS::ArcOfParabola & aop = ArcsOfParabola[geo.index]; - aop.PushOwnParams(ownparams); - pCurve = &aop; - } - break; - case BSpline: - { - GCS::BSpline & bsp = BSplines[geo.index]; - bsp.PushOwnParams(ownparams); - pCurve = &bsp; - } - break; - case None: - break; + } - // Points (this is single point elements, not vertices of other elements) are not derived from Curve - if(geo.type != Point && geo.type != None) { - for(auto param : pconstraintplistOut) { - for(auto ownparam : ownparams) { - if (param == ownparam) { - pCurve->hasDependentParameters = true; - break; - } - } + } + + std::vector < std::set < double*>> groups; + GCSsys.getDependentParamsGroups(groups); + + pDependencyGroups.resize(groups.size()); + + // translate parameters into elements (Geoid, PointPos) + for(size_t i = 0; i < groups.size(); i++) { + for(size_t j = 0; j < groups[i].size(); j++) { + + auto element = param2geoelement.find(*std::next(groups[i].begin(), j)); + + if (element != param2geoelement.end()) { + pDependencyGroups[i].insert(element->second); } } } - // Points are the only element that defines other elements, so these points (as opposed to those points - // above which are just GeomPoints), have to be handled separately. - for(auto & point : Points) { - for(auto param : pconstraintplistOut) { - if (param == point.x || param == point.y) { - point.hasDependentParameters = true; - break; + + // check if groups have a common element, if yes merge the groups + auto havecommonelement = [] ( std::set < std::pair< int, Sketcher::PointPos>>::iterator begin1, + std::set < std::pair< int, Sketcher::PointPos>>::iterator end1, + std::set < std::pair< int, Sketcher::PointPos>>::iterator begin2, + std::set < std::pair< int, Sketcher::PointPos>>::iterator end2) { + + while (begin1 != end1 && begin2 != end2) { + if (*begin1 < *begin2) + ++begin1; + else if (*begin2 < *begin1) + ++begin2; + else + return true; + } + + return false; + }; + + if(pDependencyGroups.size() > 1) { // only if there is more than 1 group + size_t endcount = pDependencyGroups.size()-1; + + for(size_t i=0; i < endcount; i++) { + if(havecommonelement(pDependencyGroups[i].begin(), pDependencyGroups[i].end(), pDependencyGroups[i+1].begin(), pDependencyGroups[i+1].end())){ + pDependencyGroups[i].insert(pDependencyGroups[i+1].begin(), pDependencyGroups[i+1].end()); + pDependencyGroups.erase(pDependencyGroups.begin()+i+1); + endcount--; } } } } +std::set < std::pair< int, Sketcher::PointPos>> Sketch::getDependencyGroup(int geoId, PointPos pos) const +{ + geoId = checkGeoId(geoId); + + std::set < std::pair< int, Sketcher::PointPos>> group; + + auto key = std::make_pair(geoId, pos); + + for( auto & set : pDependencyGroups) { + if (set.find(key) != set.end()) { + group = set; + break; + } + } + + return group; +} + int Sketch::resetSolver() { clearTemporaryConstraints(); @@ -287,7 +301,7 @@ int Sketch::resetSolver() GCSsys.initSolution(defaultSolverRedundant); GCSsys.getConflicting(Conflicting); GCSsys.getRedundant(Redundant); - GCSsys.getDependentParams(pconstraintplistOut); + GCSsys.getDependentParams(pDependentParametersList); calculateDependentParametersElements(); @@ -422,6 +436,11 @@ int Sketch::addPoint(const Part::GeomPoint &point, bool fixed) // store complete set Geoms.push_back(def); + if(!fixed) { + param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::start)); + param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::start)); + } + // return the position of the newly added geometry return Geoms.size()-1; } @@ -476,6 +495,13 @@ int Sketch::addLineSegment(const Part::GeomLineSegment &lineSegment, bool fixed) // store complete set Geoms.push_back(def); + if(!fixed) { + param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::start)); + param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::start)); + param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p2.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::end)); + param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p2.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::end)); + } + // return the position of the newly added geometry return Geoms.size()-1; } @@ -547,6 +573,18 @@ int Sketch::addArc(const Part::GeomArcOfCircle &circleSegment, bool fixed) if (!fixed) GCSsys.addConstraintArcRules(a); + if(!fixed) { + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p1.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::start)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p1.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::start)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p2.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::end)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p2.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::end)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p3.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::mid)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p3.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::mid)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(r), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(a1), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(a2), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + } + // return the position of the newly added geometry return Geoms.size()-1; } @@ -635,6 +673,20 @@ int Sketch::addArcOfEllipse(const Part::GeomArcOfEllipse &ellipseSegment, bool f if (!fixed) GCSsys.addConstraintArcOfEllipseRules(a); + if(!fixed) { + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p1.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::start)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p1.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::start)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p2.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::end)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p2.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::end)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p3.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::mid)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p3.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::mid)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(f1X), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(f1Y), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(rmin), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(a1), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(a2), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + } + // return the position of the newly added geometry return Geoms.size()-1; } @@ -721,6 +773,21 @@ int Sketch::addArcOfHyperbola(const Part::GeomArcOfHyperbola &hyperbolaSegment, if (!fixed) GCSsys.addConstraintArcOfHyperbolaRules(a); + if(!fixed) { + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p1.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::start)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p1.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::start)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p2.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::end)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p2.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::end)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p3.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::mid)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p3.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::mid)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(f1X), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(f1Y), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(rmin), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(a1), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(a2), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + } + + // return the position of the newly added geometry return Geoms.size()-1; } @@ -797,6 +864,19 @@ int Sketch::addArcOfParabola(const Part::GeomArcOfParabola ¶bolaSegment, boo if (!fixed) GCSsys.addConstraintArcOfParabolaRules(a); + if(!fixed) { + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p1.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::start)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p1.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::start)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p2.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::end)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p2.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::end)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p3.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::mid)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p3.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::mid)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p4.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p4.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(a1), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(a2), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + } + // return the position of the newly added geometry return Geoms.size()-1; } @@ -859,13 +939,23 @@ int Sketch::addBSpline(const Part::GeomBSplineCurve &bspline, bool fixed) p.y = params[params.size()-1]; spoles.push_back(p); + + if(!fixed) { + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + } } std::vector sweights; for(std::vector::const_iterator it = weights.begin(); it != weights.end(); ++it) { - params.push_back(new double( (*it) )); + auto r = new double( (*it) ); + params.push_back(r); sweights.push_back(params[params.size()-1]); + + if(!fixed) { + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(r), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + } } std::vector sknots; @@ -944,6 +1034,14 @@ int Sketch::addBSpline(const Part::GeomBSplineCurve &bspline, bool fixed) GCSsys.addConstraintP2PCoincident(*(bs.poles.end()-1),bs.end); } + if(!fixed) { + // Note: Poles and weight parameters are emplaced above + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p1.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::start)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p1.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::start)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p2.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::end)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p2.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::end)); + } + // return the position of the newly added geometry return Geoms.size()-1; } @@ -987,6 +1085,12 @@ int Sketch::addCircle(const Part::GeomCircle &cir, bool fixed) // store complete set Geoms.push_back(def); + if(!fixed) { + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p1.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::mid)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(p1.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::mid)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(r), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + } + // return the position of the newly added geometry return Geoms.size()-1; } @@ -1045,6 +1149,14 @@ int Sketch::addEllipse(const Part::GeomEllipse &elip, bool fixed) // store complete set Geoms.push_back(def); + if(!fixed) { + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(c.x), std::forward_as_tuple(Geoms.size()-1, Sketcher::mid)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(c.y), std::forward_as_tuple(Geoms.size()-1, Sketcher::mid)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(f1X), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(f1Y), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + param2geoelement.emplace(std::piecewise_construct, std::forward_as_tuple(rmin), std::forward_as_tuple(Geoms.size()-1, Sketcher::none)); + } + // return the position of the newly added geometry return Geoms.size()-1; } diff --git a/src/Mod/Sketcher/App/Sketch.h b/src/Mod/Sketcher/App/Sketch.h index 7ecd0a0f00..c1beac27b8 100644 --- a/src/Mod/Sketcher/App/Sketch.h +++ b/src/Mod/Sketcher/App/Sketch.h @@ -108,6 +108,11 @@ public: inline const std::vector &getRedundant(void) const { return Redundant; } inline bool hasMalformedConstraints(void) const { return malformedConstraints; } +public: + std::set < std::pair< int, Sketcher::PointPos>> getDependencyGroup(int geoId, PointPos pos) const; + + +public: /** set the datum of a distance or angle constraint to a certain value and solve * This can cause the solving to fail! @@ -411,7 +416,12 @@ protected: std::vector Conflicting; std::vector Redundant; - std::vector pconstraintplistOut; + std::vector pDependentParametersList; + + std::vector < std::set < std::pair< int, Sketcher::PointPos>>> pDependencyGroups; + + // this map is intended to convert a parameter (double *) into a GeoId/PointPos pair + std::map> param2geoelement; // solving parameters std::vector Parameters; // with memory allocation From 6c6c2860477470704f84bf5ade78cae1a7a4c59d Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Fri, 18 Dec 2020 15:28:49 +0100 Subject: [PATCH 56/65] Sketcher: rewrite SketchObject getGeometryWithDependentParameters to use SolverGeometryExtension --- src/Mod/Sketcher/App/SketchObject.cpp | 78 +++++++++++++-------------- 1 file changed, 36 insertions(+), 42 deletions(-) diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index ed1fa11b4c..5ad7d19137 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -79,10 +79,12 @@ #include #include -#include "SketchObject.h" -#include "Sketch.h" +#include #include #include +#include + +#include "SketchObject.h" #undef DEBUG @@ -7008,55 +7010,47 @@ void SketchObject::getGeometryWithDependentParameters(std::vectorgetTypeId() == Part::GeomPoint::getClassTypeId()) { - addelement(geoid, Sketcher::start); - } - else if(geo->getTypeId() == Part::GeomLineSegment::getClassTypeId() || - geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { + if(geo) { + if(geo->hasExtension(Sketcher::SolverGeometryExtension::getClassTypeId())) { - addelement(geoid, Sketcher::start); - addelement(geoid, Sketcher::end); - addelement(geoid, Sketcher::none); - } - else if(geo->getTypeId() == Part::GeomCircle::getClassTypeId() || - geo->getTypeId() == Part::GeomEllipse::getClassTypeId() ) { + auto solvext = std::static_pointer_cast( + geo->getExtension(Sketcher::SolverGeometryExtension::getClassTypeId()).lock()); - addelement(geoid, Sketcher::mid); - addelement(geoid, Sketcher::none); - } - else if(geo->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() || - geo->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || - geo->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() || - geo->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() ) { - - addelement(geoid, Sketcher::start); - addelement(geoid, Sketcher::end); - addelement(geoid, Sketcher::mid); - addelement(geoid, Sketcher::none); + if (solvext->getGeometry() == Sketcher::SolverGeometryExtension::NotFullyConstraint) { + // The solver differentiates whether the parameters that are dependent are not those + // of start, end, mid, and assigns them to the edge (edge params = curve params - parms of start, end, mid). + // The user looking at the UI expects that the edge of a NotFullyConstraint geometry + // will always move, even if the edge parameters are independent, for example if mid is + // the only dependent parameter. In other words, the user could reasonably restrict the edge + // to reach a fully constrained element. Under this understanding, the edge parameter would + // always be dependent, unless the element is fully constrained. + // + // While this is ok from a user visual expectation point of view, it leads to a loss of information + // of whether restricting the point start, end, mid that is dependent may suffice, or even if such + // points are restricted, the edge would still need to be restricted. + // + // Because Python gets the information in this function, it would lead to Python users having access + // to a lower amount of detail. + // + // For this reason, this function returns edge as dependent parameter if and only if constraining the + // parameters of the points would not suffice to constraint the element. + if (solvext->getEdge() == SolverGeometryExtension::Dependent) + geometrymap.emplace_back(geoid,Sketcher::none); + if (solvext->getStart() == SolverGeometryExtension::Dependent) + geometrymap.emplace_back(geoid,Sketcher::start); + if (solvext->getEnd() == SolverGeometryExtension::Dependent) + geometrymap.emplace_back(geoid,Sketcher::start); + if (solvext->getMid() == SolverGeometryExtension::Dependent) + geometrymap.emplace_back(geoid,Sketcher::start); + } + } } geoid++; } - - if(curQRAlg == GCS::EigenSparseQR) { - getSolvedSketch().setQRAlgorithm(GCS::EigenSparseQR); - } } bool SketchObject::evaluateConstraint(const Constraint *constraint) const From 219e98441899af5fdbb96a488b852c8ef3c61eed Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Fri, 18 Dec 2020 15:43:50 +0100 Subject: [PATCH 57/65] Sketcher: rewrite Cmd to select DoFs using SolverGeometryExtensions framework --- src/Mod/Sketcher/Gui/CommandSketcherTools.cpp | 73 +++++++------------ 1 file changed, 28 insertions(+), 45 deletions(-) diff --git a/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp b/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp index 34e74f0f5a..7c4ffc9a4b 100644 --- a/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp +++ b/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp @@ -48,6 +48,7 @@ #include #include +#include #include "ViewProviderSketch.h" #include "SketchRectangularArrayDialog.h" @@ -739,69 +740,51 @@ void CmdSketcherSelectElementsWithDoFs::activated(int iMsg) auto geos = Obj->getInternalGeometry(); - // Solver parameter detection algorithm only works for Dense QR with full pivoting. If we are using Sparse QR, we - // have to re-solve using Dense QR. - GCS::QRAlgorithm curQRAlg = Obj->getSolvedSketch().getQRAlgorithm(); - - if (curQRAlg == GCS::EigenSparseQR) { - Obj->getSolvedSketch().setQRAlgorithm(GCS::EigenDenseQR); - Obj->solve(false); - } - auto testselectvertex = [&Obj, &ss, &doc_name, &obj_name](int geoId, PointPos pos) { ss.str(std::string()); - if (Obj->getSolvedSketch().hasDependentParameters(geoId, pos)) { - int vertex = Obj->getVertexIndexGeoPos(geoId, pos); - if (vertex > -1) { - ss << "Vertex" << vertex + 1; + int vertex = Obj->getVertexIndexGeoPos(geoId, pos); + if (vertex > -1) { + ss << "Vertex" << vertex + 1; - Gui::Selection().addSelection(doc_name.c_str(), obj_name.c_str(), ss.str().c_str()); - } + Gui::Selection().addSelection(doc_name.c_str(), obj_name.c_str(), ss.str().c_str()); } }; - auto testselectedge = [&Obj, &ss, &doc_name, &obj_name](int geoId) { + auto testselectedge = [&ss, &doc_name, &obj_name](int geoId) { ss.str(std::string()); - if(Obj->getSolvedSketch().hasDependentParameters(geoId, Sketcher::none)) { - ss << "Edge" << geoId + 1; - Gui::Selection().addSelection(doc_name.c_str(), obj_name.c_str(), ss.str().c_str()); - } + ss << "Edge" << geoId + 1; + Gui::Selection().addSelection(doc_name.c_str(), obj_name.c_str(), ss.str().c_str()); }; int geoid = 0; for (auto geo : geos) { - if (geo->getTypeId() == Part::GeomPoint::getClassTypeId()) { - testselectvertex(geoid, Sketcher::start); - } - else if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId() || - geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { - testselectvertex(geoid, Sketcher::start); - testselectvertex(geoid, Sketcher::end); - testselectedge(geoid); - } - else if (geo->getTypeId() == Part::GeomCircle::getClassTypeId() || - geo->getTypeId() == Part::GeomEllipse::getClassTypeId()) { - testselectvertex(geoid, Sketcher::mid); - testselectedge(geoid); - } - else if (geo->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() || - geo->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || - geo->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() || - geo->getTypeId() == Part::GeomArcOfParabola::getClassTypeId()) { - testselectvertex(geoid, Sketcher::start); - testselectvertex(geoid, Sketcher::end); - testselectvertex(geoid, Sketcher::mid); - testselectedge(geoid); + if(geo) { + if(geo->hasExtension(Sketcher::SolverGeometryExtension::getClassTypeId())) { + + auto solvext = std::static_pointer_cast( + geo->getExtension(Sketcher::SolverGeometryExtension::getClassTypeId()).lock()); + + if (solvext->getGeometry() == Sketcher::SolverGeometryExtension::NotFullyConstraint) { + // Coded for consistency with getGeometryWithDependentParameters, read the comments + // on that function + if (solvext->getEdge() == SolverGeometryExtension::Dependent) + testselectedge(geoid); + if (solvext->getStart() == SolverGeometryExtension::Dependent) + testselectvertex(geoid, Sketcher::start); + if (solvext->getEnd() == SolverGeometryExtension::Dependent) + testselectvertex(geoid, Sketcher::end); + if (solvext->getMid() == SolverGeometryExtension::Dependent) + testselectvertex(geoid, Sketcher::mid); + } + } } + geoid++; } - if (curQRAlg == GCS::EigenSparseQR) { - Obj->getSolvedSketch().setQRAlgorithm(GCS::EigenSparseQR); - } } bool CmdSketcherSelectElementsWithDoFs::isActive(void) From 4328b6b3c80b5c9fa9a7eafb7983f2700baf8b71 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Fri, 18 Dec 2020 15:33:14 +0100 Subject: [PATCH 58/65] Sketcher: ViewProviderSketch - fix BSpline pole dragging based on SolverGeometryExtension --- src/Mod/Sketcher/Gui/ViewProviderSketch.cpp | 70 ++++++++++++++++----- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index 25b93ffc73..d4e64bd154 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -1166,24 +1166,64 @@ bool ViewProviderSketch::mouseMove(const SbVec2s &cursorPos, Gui::View3DInventor // This is because dragging gives unwanted cosmetic results due to the scale ratio. // This is an heuristic as it does not check all indirect routes. if(GeometryFacade::isInternalType(geo, InternalType::BSplineControlPoint)) { - bool weight = false; - bool weight_driven = false; - bool equal = false; - bool block = false; + if(geo->hasExtension(Sketcher::SolverGeometryExtension::getClassTypeId())) { + auto solvext = std::static_pointer_cast( + geo->getExtension(Sketcher::SolverGeometryExtension::getClassTypeId()).lock()); + + // Edge parameters are Independent, so weight won't move + if(solvext->getEdge()==Sketcher::SolverGeometryExtension::Independent) { + Mode = STATUS_NONE; + return false; + } + + // The B-Spline is constrained to be non-rational (equal weights), moving produces a bad effect + // because OCCT will normalize the values of the weights. + auto grp = getSketchObject()->getSolvedSketch().getDependencyGroup(edit->DragCurve, Sketcher::none); + + int bsplinegeoid = -1; + + std::vector polegeoids; + + for( auto c : getSketchObject()->Constraints.getValues()) { + if( c->Type == Sketcher::InternalAlignment && + c->AlignmentType == BSplineControlPoint && + c->First == edit->DragCurve ) { + + bsplinegeoid = c->Second; + break; + } + } + + if(bsplinegeoid == -1) { + Mode = STATUS_NONE; + return false; + } + + for( auto c : getSketchObject()->Constraints.getValues()) { + if( c->Type == Sketcher::InternalAlignment && + c->AlignmentType == BSplineControlPoint && + c->Second == bsplinegeoid ) { + + polegeoids.push_back(c->First); + } + } + + bool allingroup = true; + + for( auto polegeoid : polegeoids ) { + std::pair< int, Sketcher::PointPos > thispole = std::make_pair(polegeoid,Sketcher::none); + + if(grp.find(thispole) == grp.end()) // not found + allingroup = false; + } + + if(allingroup) { // it is constrained to be non-rational + Mode = STATUS_NONE; + return false; + } - for(auto c : getSketchObject()->Constraints.getValues()) { - weight = weight || (c->Type == Sketcher::Weight && c->First == edit->DragCurve); - weight_driven = weight_driven || (c->Type == Sketcher::Weight && !c->isDriving && c->First == edit->DragCurve); - equal = equal || (c->Type == Sketcher::Equal && (c->First == edit->DragCurve || c->Second == edit->DragCurve)); - block = block || (c->Type == Sketcher::Block && c->First == edit->DragCurve); } - if( (weight && !weight_driven) || - (equal && !weight_driven) || - block) { - Mode = STATUS_NONE; - return false; - } } getSketchObject()->getSolvedSketch().initMove(edit->DragCurve, Sketcher::none, false); From 10352931b5d2ddc44bfec6285e14f80af7ae4cd4 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sat, 19 Dec 2020 09:31:50 +0100 Subject: [PATCH 59/65] Sketcher: ViewProviderSketch - Show constrained elements in new color ===================================================================== Provide different colors for full constrained edge, construction edge, internal alignment element and construction vertex. This should enable users to select what they want in their specific situation. --- src/Mod/Sketcher/Gui/ViewProviderSketch.cpp | 104 +++++++++++++++----- src/Mod/Sketcher/Gui/ViewProviderSketch.h | 4 + 2 files changed, 82 insertions(+), 26 deletions(-) diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index d4e64bd154..9f8b0ac95e 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -121,6 +121,7 @@ #include "TaskSketcherValidation.h" #include "CommandConstraints.h" #include "ViewProviderSketchGeometryExtension.h" +#include FC_LOG_LEVEL_INIT("Sketch",true,true) @@ -143,24 +144,28 @@ using namespace SketcherGui; using namespace Sketcher; namespace bp = boost::placeholders; -SbColor ViewProviderSketch::VertexColor (1.0f,0.149f,0.0f); // #FF2600 -> (255, 38, 0) -SbColor ViewProviderSketch::CurveColor (1.0f,1.0f,1.0f); // #FFFFFF -> (255,255,255) -SbColor ViewProviderSketch::CurveDraftColor (0.0f,0.0f,0.86f); // #0000DC -> ( 0, 0,220) -SbColor ViewProviderSketch::CurveExternalColor (0.8f,0.2f,0.6f); // #CC3399 -> (204, 51,153) -SbColor ViewProviderSketch::CrossColorH (0.8f,0.4f,0.4f); // #CC6666 -> (204,102,102) -SbColor ViewProviderSketch::CrossColorV (0.4f,0.8f,0.4f); // #66CC66 -> (102,204,102) -SbColor ViewProviderSketch::FullyConstrainedColor (0.0f,1.0f,0.0f); // #00FF00 -> ( 0,255, 0) -SbColor ViewProviderSketch::ConstrDimColor (1.0f,0.149f,0.0f); // #FF2600 -> (255, 38, 0) -SbColor ViewProviderSketch::ConstrIcoColor (1.0f,0.149f,0.0f); // #FF2600 -> (255, 38, 0) -SbColor ViewProviderSketch::NonDrivingConstrDimColor (0.0f,0.149f,1.0f); // #0026FF -> ( 0, 38,255) -SbColor ViewProviderSketch::ExprBasedConstrDimColor (1.0f,0.5f,0.149f); // #FF7F26 -> (255, 127, 38) -SbColor ViewProviderSketch::InformationColor (0.0f,1.0f,0.0f); // #00FF00 -> ( 0,255, 0) -SbColor ViewProviderSketch::PreselectColor (0.88f,0.88f,0.0f); // #E1E100 -> (225,225, 0) -SbColor ViewProviderSketch::SelectColor (0.11f,0.68f,0.11f); // #1CAD1C -> ( 28,173, 28) -SbColor ViewProviderSketch::PreselectSelectedColor (0.36f,0.48f,0.11f); // #5D7B1C -> ( 93,123, 28) -SbColor ViewProviderSketch::CreateCurveColor (0.8f,0.8f,0.8f); // #CCCCCC -> (204,204,204) -SbColor ViewProviderSketch::DeactivatedConstrDimColor (0.8f,0.8f,0.8f); // #CCCCCC -> (204,204,204) -SbColor ViewProviderSketch::InternalAlignedGeoColor (0.7f,0.7f,0.5f); // #B2B27F -> (178,178,127) +SbColor ViewProviderSketch::VertexColor (1.0f,0.149f,0.0f); // #FF2600 -> (255, 38, 0) +SbColor ViewProviderSketch::CurveColor (1.0f,1.0f,1.0f); // #FFFFFF -> (255,255,255) +SbColor ViewProviderSketch::CurveDraftColor (0.0f,0.0f,0.86f); // #0000DC -> ( 0, 0,220) +SbColor ViewProviderSketch::CurveExternalColor (0.8f,0.2f,0.6f); // #CC3399 -> (204, 51,153) +SbColor ViewProviderSketch::CrossColorH (0.8f,0.4f,0.4f); // #CC6666 -> (204,102,102) +SbColor ViewProviderSketch::CrossColorV (0.4f,0.8f,0.4f); // #66CC66 -> (102,204,102) +SbColor ViewProviderSketch::FullyConstrainedColor (0.0f,1.0f,0.0f); // #00FF00 -> ( 0,255, 0) +SbColor ViewProviderSketch::ConstrDimColor (1.0f,0.149f,0.0f); // #FF2600 -> (255, 38, 0) +SbColor ViewProviderSketch::ConstrIcoColor (1.0f,0.149f,0.0f); // #FF2600 -> (255, 38, 0) +SbColor ViewProviderSketch::NonDrivingConstrDimColor (0.0f,0.149f,1.0f); // #0026FF -> ( 0, 38,255) +SbColor ViewProviderSketch::ExprBasedConstrDimColor (1.0f,0.5f,0.149f); // #FF7F26 -> (255, 127, 38) +SbColor ViewProviderSketch::InformationColor (0.0f,1.0f,0.0f); // #00FF00 -> ( 0,255, 0) +SbColor ViewProviderSketch::PreselectColor (0.88f,0.88f,0.0f); // #E1E100 -> (225,225, 0) +SbColor ViewProviderSketch::SelectColor (0.11f,0.68f,0.11f); // #1CAD1C -> ( 28,173, 28) +SbColor ViewProviderSketch::PreselectSelectedColor (0.36f,0.48f,0.11f); // #5D7B1C -> ( 93,123, 28) +SbColor ViewProviderSketch::CreateCurveColor (0.8f,0.8f,0.8f); // #CCCCCC -> (204,204,204) +SbColor ViewProviderSketch::DeactivatedConstrDimColor (0.8f,0.8f,0.8f); // #CCCCCC -> (204,204,204) +SbColor ViewProviderSketch::InternalAlignedGeoColor (0.7f,0.7f,0.5f); // #B2B27F -> (178,178,127) +SbColor ViewProviderSketch::FullyConstraintElementColor (0.50f,0.81f,0.62f); // #80D0A0 -> (128,208,160) +SbColor ViewProviderSketch::FullyConstraintConstructionElementColor (0.56f,0.66f,0.99f); // #80D0A0 -> (128,208,160) +SbColor ViewProviderSketch::FullyConstraintInternalAlignmentColor (0.87f,0.87f,0.78f); // #80D0A0 -> (128,208,160) +SbColor ViewProviderSketch::FullyConstraintConstructionPointColor (1.0f,0.58f,0.50f); // #80D0A0 -> (128,208,160) // Variables for holding previous click SbTime ViewProviderSketch::prvClickTime; SbVec2s ViewProviderSketch::prvClickPos; @@ -2796,6 +2801,22 @@ void ViewProviderSketch::updateColor(void) return false; }; + auto isFullyConstraintElement = [](Sketcher::SketchObject* obj, int GeoId) -> bool { + + const Part::Geometry* geom = obj->getGeometry(GeoId); + + if(geom) { + if(geom->hasExtension(Sketcher::SolverGeometryExtension::getClassTypeId())) { + + auto solvext = std::static_pointer_cast( + geom->getExtension(Sketcher::SolverGeometryExtension::getClassTypeId()).lock()); + + return (solvext->getGeometry() == Sketcher::SolverGeometryExtension::FullyConstraint); + } + } + return false; + }; + // colors of the point set if (edit->FullyConstrained) { for (int i=0; i < PtNum; i++) @@ -2805,10 +2826,20 @@ void ViewProviderSketch::updateColor(void) for (int i=0; i < PtNum; i++) { int GeoId = edit->PointIdToGeoId[i]; - if(isInternalAlignedGeom(getSketchObject(), GeoId)) - pcolor[i] = InternalAlignedGeoColor; - else - pcolor[i] = VertexColor; + bool constrainedElement = isFullyConstraintElement(getSketchObject(), GeoId); + + if(isInternalAlignedGeom(getSketchObject(), GeoId)) { + if(constrainedElement) + pcolor[i] = FullyConstraintInternalAlignmentColor; + else + pcolor[i] = InternalAlignedGeoColor; + } + else { + if(constrainedElement) + pcolor[i] = FullyConstraintConstructionPointColor; + else + pcolor[i] = VertexColor; + } } } @@ -2860,6 +2891,8 @@ void ViewProviderSketch::updateColor(void) bool selected = (edit->SelCurvSet.find(GeoId) != edit->SelCurvSet.end()); bool preselected = (edit->PreselectCurve == GeoId); + bool constrainedElement = isFullyConstraintElement(getSketchObject(), GeoId); + if (selected && preselected) { color[i] = PreselectSelectedColor; for (int k=j; jGetUnsigned("InternalAlignedGeoColor", color); InternalAlignedGeoColor.setPackedValue((uint32_t)color, transparency); + // set fullyconstraint element color + color = (unsigned long)(FullyConstraintElementColor.getPackedValue()); + color = hGrp->GetUnsigned("FullyConstraintElementColor", color); + FullyConstraintElementColor.setPackedValue((uint32_t)color, transparency); // set the cross lines color //CrossColorV.setPackedValue((uint32_t)color, transparency); //CrossColorH.setPackedValue((uint32_t)color, transparency); diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.h b/src/Mod/Sketcher/Gui/ViewProviderSketch.h index ab0aad8701..ab64445c54 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.h +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.h @@ -401,6 +401,10 @@ protected: static SbColor InformationColor; static SbColor DeactivatedConstrDimColor; static SbColor InternalAlignedGeoColor; + static SbColor FullyConstraintElementColor; + static SbColor FullyConstraintConstructionElementColor; + static SbColor FullyConstraintInternalAlignmentColor; + static SbColor FullyConstraintConstructionPointColor; static SbTime prvClickTime; static SbVec2s prvClickPos; //used by double-click-detector From 0a2d3ddd1ab5803e69b5df0350129069966c162f Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sat, 19 Dec 2020 07:44:12 +0100 Subject: [PATCH 60/65] Sketcher: Bring new colors to preferences --- src/Mod/Sketcher/Gui/SketcherSettings.cpp | 8 + .../Sketcher/Gui/SketcherSettingsColors.ui | 170 ++++++++++++++++-- src/Mod/Sketcher/Gui/ViewProviderSketch.cpp | 24 ++- 3 files changed, 179 insertions(+), 23 deletions(-) diff --git a/src/Mod/Sketcher/Gui/SketcherSettings.cpp b/src/Mod/Sketcher/Gui/SketcherSettings.cpp index dbf99822e7..63fd0a0cc2 100644 --- a/src/Mod/Sketcher/Gui/SketcherSettings.cpp +++ b/src/Mod/Sketcher/Gui/SketcherSettings.cpp @@ -261,6 +261,10 @@ void SketcherSettingsColors::saveSettings() ui->ExternalColor->onSave(); ui->FullyConstrainedColor->onSave(); ui->InternalAlignedGeoColor->onSave(); + ui->FullyConstraintElementColor->onSave(); + ui->FullyConstraintConstructionElementColor->onSave(); + ui->FullyConstraintInternalAlignmentColor->onSave(); + ui->FullyConstraintConstructionPointColor->onSave(); ui->ConstrainedColor->onSave(); ui->NonDrivingConstraintColor->onSave(); @@ -288,6 +292,10 @@ void SketcherSettingsColors::loadSettings() ui->ExternalColor->onRestore(); ui->FullyConstrainedColor->onRestore(); ui->InternalAlignedGeoColor->onRestore(); + ui->FullyConstraintElementColor->onRestore(); + ui->FullyConstraintConstructionElementColor->onRestore(); + ui->FullyConstraintInternalAlignmentColor->onRestore(); + ui->FullyConstraintConstructionPointColor->onRestore(); ui->ConstrainedColor->onRestore(); ui->NonDrivingConstraintColor->onRestore(); diff --git a/src/Mod/Sketcher/Gui/SketcherSettingsColors.ui b/src/Mod/Sketcher/Gui/SketcherSettingsColors.ui index 895c864824..253960cf86 100644 --- a/src/Mod/Sketcher/Gui/SketcherSettingsColors.ui +++ b/src/Mod/Sketcher/Gui/SketcherSettingsColors.ui @@ -320,6 +320,138 @@ + + + + 182 + 0 + + + + Fully constrained edit edge color + + + + + + + Color of fully constrained edge color in edit mode + + + + 128 + 208 + 160 + + + + FullyConstraintElementColor + + + View + + + + + + + + 182 + 0 + + + + Fully constrained edit construction edge color + + + + + + + Color of fully constrained construction edge color in edit mode + + + + 143 + 169 + 253 + + + + FullyConstraintConstructionElementColor + + + View + + + + + + + + 182 + 0 + + + + Fully constrained edit internal alignment edge color + + + + + + + Color of fully constrained internal alignment edge color in edit mode + + + + 222 + 222 + 200 + + + + FullyConstraintInternalAlignmentColor + + + View + + + + + + + + 182 + 0 + + + + Fully constrained edit vertex color + + + + + + + Color of fully constrained vertex color in edit mode + + + + 255 + 149 + 128 + + + + FullyConstraintConstructionPointColor + + + View + + + + @@ -332,7 +464,7 @@ - + Color of driving constraints in edit mode @@ -352,14 +484,14 @@ - + Reference constraint color - + Color of reference constraints in edit mode @@ -379,14 +511,14 @@ - + Expression dependent constraint color - + Color of expression dependent constraints in edit mode @@ -406,14 +538,14 @@ - + Deactivated constraint color - + Color of deactivated constraints in edit mode @@ -433,7 +565,7 @@ - + @@ -446,7 +578,7 @@ - + Color of the datum portion of a driving constraint @@ -466,7 +598,7 @@ - + @@ -479,7 +611,7 @@ - + The default line thickness for new shapes @@ -501,7 +633,7 @@ - + @@ -514,7 +646,7 @@ - + The default line thickness for new shapes @@ -536,7 +668,7 @@ - + @@ -549,7 +681,7 @@ - + The default line thickness for new shapes @@ -571,7 +703,7 @@ - + @@ -584,7 +716,7 @@ - + Text color of the coordinates @@ -604,7 +736,7 @@ - + @@ -617,7 +749,7 @@ - + Color of crosshair cursor. diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index 9f8b0ac95e..aac96821b8 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -163,9 +163,9 @@ SbColor ViewProviderSketch::CreateCurveColor (0.8f,0.8f,0 SbColor ViewProviderSketch::DeactivatedConstrDimColor (0.8f,0.8f,0.8f); // #CCCCCC -> (204,204,204) SbColor ViewProviderSketch::InternalAlignedGeoColor (0.7f,0.7f,0.5f); // #B2B27F -> (178,178,127) SbColor ViewProviderSketch::FullyConstraintElementColor (0.50f,0.81f,0.62f); // #80D0A0 -> (128,208,160) -SbColor ViewProviderSketch::FullyConstraintConstructionElementColor (0.56f,0.66f,0.99f); // #80D0A0 -> (128,208,160) -SbColor ViewProviderSketch::FullyConstraintInternalAlignmentColor (0.87f,0.87f,0.78f); // #80D0A0 -> (128,208,160) -SbColor ViewProviderSketch::FullyConstraintConstructionPointColor (1.0f,0.58f,0.50f); // #80D0A0 -> (128,208,160) +SbColor ViewProviderSketch::FullyConstraintConstructionElementColor (0.56f,0.66f,0.99f); // #8FA9FD -> (143,169,253) +SbColor ViewProviderSketch::FullyConstraintInternalAlignmentColor (0.87f,0.87f,0.78f); // #DEDEC8 -> (222,222,200) +SbColor ViewProviderSketch::FullyConstraintConstructionPointColor (1.0f,0.58f,0.50f); // #FF9580 -> (255,149,128) // Variables for holding previous click SbTime ViewProviderSketch::prvClickTime; SbVec2s ViewProviderSketch::prvClickPos; @@ -6138,10 +6138,26 @@ bool ViewProviderSketch::setEdit(int ModNum) color = (unsigned long)(CurveDraftColor.getPackedValue()); color = hGrp->GetUnsigned("ConstructionColor", color); CurveDraftColor.setPackedValue((uint32_t)color, transparency); - // set the construction curve color + // set the internal alignment geometry color color = (unsigned long)(InternalAlignedGeoColor.getPackedValue()); color = hGrp->GetUnsigned("InternalAlignedGeoColor", color); InternalAlignedGeoColor.setPackedValue((uint32_t)color, transparency); + // set the color for a fully constrained element + color = (unsigned long)(FullyConstraintElementColor.getPackedValue()); + color = hGrp->GetUnsigned("FullyConstraintElementColor", color); + FullyConstraintElementColor.setPackedValue((uint32_t)color, transparency); + // set the color for fully constrained construction element + color = (unsigned long)(FullyConstraintConstructionElementColor.getPackedValue()); + color = hGrp->GetUnsigned("FullyConstraintConstructionElementColor", color); + FullyConstraintConstructionElementColor.setPackedValue((uint32_t)color, transparency); + // set the color for fully constrained internal alignment element + color = (unsigned long)(FullyConstraintInternalAlignmentColor.getPackedValue()); + color = hGrp->GetUnsigned("FullyConstraintInternalAlignmentColor", color); + FullyConstraintInternalAlignmentColor.setPackedValue((uint32_t)color, transparency); + // set the color for fully constrained construction points + color = (unsigned long)(FullyConstraintConstructionPointColor.getPackedValue()); + color = hGrp->GetUnsigned("FullyConstraintConstructionPointColor", color); + FullyConstraintConstructionPointColor.setPackedValue((uint32_t)color, transparency); // set fullyconstraint element color color = (unsigned long)(FullyConstraintElementColor.getPackedValue()); color = hGrp->GetUnsigned("FullyConstraintElementColor", color); From 04923686ebb06966aecd2875b1e1d20cffc17ca8 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sat, 19 Dec 2020 08:47:43 +0100 Subject: [PATCH 61/65] Sketcher: Sketch.cpp clean up old interface --- src/Mod/Sketcher/App/Sketch.cpp | 110 -------------------------------- src/Mod/Sketcher/App/Sketch.h | 3 - 2 files changed, 113 deletions(-) diff --git a/src/Mod/Sketcher/App/Sketch.cpp b/src/Mod/Sketcher/App/Sketch.cpp index 6ff57bde8b..361dbd5aa3 100644 --- a/src/Mod/Sketcher/App/Sketch.cpp +++ b/src/Mod/Sketcher/App/Sketch.cpp @@ -3812,116 +3812,6 @@ Base::Vector3d Sketch::getPoint(int geoId, PointPos pos) const return Base::Vector3d(); } -bool Sketch::hasDependentParameters(int geoId, PointPos pos) const -{ - try { - geoId = checkGeoId(geoId); - } - catch (Base::Exception&) { - return false; - } - - if(Geoms[geoId].external) - return true; - - switch(Geoms[geoId].type) { - case Point: - { - switch(pos) { // NOTE: points are added to all the cases, see addition. - case none: return Points[Geoms[geoId].index].hasDependentParameters;break; - case start: return Points[Geoms[geoId].startPointId].hasDependentParameters;break; - case end: return Points[Geoms[geoId].endPointId].hasDependentParameters;break; - case mid: return Points[Geoms[geoId].midPointId].hasDependentParameters;break; - } - } - break; - case Line: - { - switch(pos) { - case none: return Lines[Geoms[geoId].index].hasDependentParameters;break; - case start: return Points[Geoms[geoId].startPointId].hasDependentParameters;break; - case end: return Points[Geoms[geoId].endPointId].hasDependentParameters;break; - case mid: return false;break; - } - } - break; - case Arc: - { - switch(pos) { - case none: return Arcs[Geoms[geoId].index].hasDependentParameters;break; - case start: return Points[Geoms[geoId].startPointId].hasDependentParameters;break; - case end: return Points[Geoms[geoId].endPointId].hasDependentParameters;break; - case mid: return Points[Geoms[geoId].midPointId].hasDependentParameters;break; - } - } - break; - case Circle: - { - switch(pos) { // NOTE: points are added to all the cases, see addition. - case none: return Circles[Geoms[geoId].index].hasDependentParameters;break; - case start: return false;break; - case end: return false;break; - case mid: return Points[Geoms[geoId].midPointId].hasDependentParameters;break; - } - } - break; - case Ellipse: - { - switch(pos) { // NOTE: points are added to all the cases, see addition. - case none: return Ellipses[Geoms[geoId].index].hasDependentParameters;break; - case start: return false;break; - case end: return false;break; - case mid: return Points[Geoms[geoId].midPointId].hasDependentParameters;break; - } - } - break; - case ArcOfEllipse: - { - switch(pos) { - case none: return ArcsOfEllipse[Geoms[geoId].index].hasDependentParameters;break; - case start: return Points[Geoms[geoId].startPointId].hasDependentParameters;break; - case end: return Points[Geoms[geoId].endPointId].hasDependentParameters;break; - case mid: return Points[Geoms[geoId].midPointId].hasDependentParameters;break; - } - } - break; - case ArcOfHyperbola: - { - switch(pos) { - case none: return ArcsOfHyperbola[Geoms[geoId].index].hasDependentParameters;break; - case start: return Points[Geoms[geoId].startPointId].hasDependentParameters;break; - case end: return Points[Geoms[geoId].endPointId].hasDependentParameters;break; - case mid: return Points[Geoms[geoId].midPointId].hasDependentParameters;break; - } - } - break; - case ArcOfParabola: - { - switch(pos) { - case none: return ArcsOfParabola[Geoms[geoId].index].hasDependentParameters;break; - case start: return Points[Geoms[geoId].startPointId].hasDependentParameters;break; - case end: return Points[Geoms[geoId].endPointId].hasDependentParameters;break; - case mid: return Points[Geoms[geoId].midPointId].hasDependentParameters;break; - } - } - break; - case BSpline: - { - switch(pos) { - case none: return BSplines[Geoms[geoId].index].hasDependentParameters;break; - case start: return Points[Geoms[geoId].startPointId].hasDependentParameters;break; - case end: return Points[Geoms[geoId].endPointId].hasDependentParameters;break; - case mid: return false;break; - } - } - break; - case None: - return false; break; - } - - return false; -} - TopoShape Sketch::toShape(void) const { TopoShape result; diff --git a/src/Mod/Sketcher/App/Sketch.h b/src/Mod/Sketcher/App/Sketch.h index c1beac27b8..8448935830 100644 --- a/src/Mod/Sketcher/App/Sketch.h +++ b/src/Mod/Sketcher/App/Sketch.h @@ -98,9 +98,6 @@ public: /// retrieves a point Base::Vector3d getPoint(int geoId, PointPos pos) const; - /// retrieves whether a geometry has dependent parameters or not - bool hasDependentParameters(int geoId, PointPos pos) const; - // Inline methods inline bool hasConflicts(void) const { return !Conflicting.empty(); } inline const std::vector &getConflicting(void) const { return Conflicting; } From 9245062358577e7c380c675d487f1454eb6375a9 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Fri, 18 Dec 2020 15:52:00 +0100 Subject: [PATCH 62/65] GCS: clean up old interface --- src/Mod/Sketcher/App/planegcs/Geo.h | 35 ++++++++++++----------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/Geo.h b/src/Mod/Sketcher/App/planegcs/Geo.h index 4dc994f24d..cb234285f0 100644 --- a/src/Mod/Sketcher/App/planegcs/Geo.h +++ b/src/Mod/Sketcher/App/planegcs/Geo.h @@ -28,14 +28,7 @@ namespace GCS { - class DependentParameters - { - public: - DependentParameters():hasDependentParameters(false) {} - bool hasDependentParameters; - }; - - class Point : public DependentParameters + class Point { public: Point(){x = 0; y = 0;} @@ -45,7 +38,7 @@ namespace GCS }; typedef std::vector VEC_P; - + ///Class DeriVector2 holds a vector value and its derivative on the ///parameter that the derivatives are being calculated for now. x,y is the ///actual vector (v). dx,dy is a derivative of the vector by a parameter @@ -96,7 +89,7 @@ namespace GCS // Geometries /////////////////////////////////////// - class Curve: public DependentParameters //a base class for all curve-based objects (line, circle/arc, ellipse/arc) + class Curve //a base class for all curve-based objects (line, circle/arc, ellipse/arc) { public: virtual ~Curve(){} @@ -169,7 +162,7 @@ namespace GCS virtual void ReconstructOnNewPvec (VEC_pD &pvec, int &cnt); virtual Arc* Copy(); }; - + class MajorRadiusConic: public Curve { public: @@ -179,13 +172,13 @@ namespace GCS virtual double getRadMaj() = 0; DeriVector2 CalculateNormal(Point &p, double* derivparam = 0) = 0; }; - + class Ellipse: public MajorRadiusConic { public: Ellipse(){ radmin = 0;} virtual ~Ellipse(){} - Point center; + Point center; Point focus1; double *radmin; virtual double getRadMaj(const DeriVector2 ¢er, const DeriVector2 &f1, double b, double db, double &ret_dRadMaj); @@ -197,7 +190,7 @@ namespace GCS virtual void ReconstructOnNewPvec (VEC_pD &pvec, int &cnt); virtual Ellipse* Copy(); }; - + class ArcOfEllipse: public Ellipse { public: @@ -215,13 +208,13 @@ namespace GCS virtual void ReconstructOnNewPvec (VEC_pD &pvec, int &cnt); virtual ArcOfEllipse* Copy(); }; - + class Hyperbola: public MajorRadiusConic { public: Hyperbola(){ radmin = 0;} virtual ~Hyperbola(){} - Point center; + Point center; Point focus1; double *radmin; virtual double getRadMaj(const DeriVector2 ¢er, const DeriVector2 &f1, double b, double db, double &ret_dRadMaj); @@ -232,7 +225,7 @@ namespace GCS virtual int PushOwnParams(VEC_pD &pvec); virtual void ReconstructOnNewPvec (VEC_pD &pvec, int &cnt); virtual Hyperbola* Copy(); - }; + }; class ArcOfHyperbola: public Hyperbola { @@ -249,20 +242,20 @@ namespace GCS virtual void ReconstructOnNewPvec (VEC_pD &pvec, int &cnt); virtual ArcOfHyperbola* Copy(); }; - + class Parabola: public Curve { public: Parabola(){ } virtual ~Parabola(){} - Point vertex; + Point vertex; Point focus1; DeriVector2 CalculateNormal(Point &p, double* derivparam = 0); virtual DeriVector2 Value(double u, double du, double* derivparam = 0); virtual int PushOwnParams(VEC_pD &pvec); virtual void ReconstructOnNewPvec (VEC_pD &pvec, int &cnt); virtual Parabola* Copy(); - }; + }; class ArcOfParabola: public Parabola { @@ -306,7 +299,7 @@ namespace GCS virtual void ReconstructOnNewPvec (VEC_pD &pvec, int &cnt); virtual BSpline* Copy(); }; - + } //namespace GCS #endif // PLANEGCS_GEO_H From 116b0bae55b57f5c52bdca32d0ed00cbb85b506d Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sat, 19 Dec 2020 10:45:25 +0100 Subject: [PATCH 63/65] Sketcher: Remove unnecesary header --- src/Mod/Sketcher/App/SolverGeometryExtension.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Mod/Sketcher/App/SolverGeometryExtension.h b/src/Mod/Sketcher/App/SolverGeometryExtension.h index abc531ec39..b21e4e1f8d 100644 --- a/src/Mod/Sketcher/App/SolverGeometryExtension.h +++ b/src/Mod/Sketcher/App/SolverGeometryExtension.h @@ -25,8 +25,6 @@ #include -#include - namespace Sketcher { From 51245f385101a5df06537e79a9680487809f6b77 Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 19 Dec 2020 16:07:00 +0100 Subject: [PATCH 64/65] Part: [skip ci] add TopoShape::isInfinite --- src/Mod/Part/App/TopoShape.cpp | 27 +++++++++++++++++++++++++++ src/Mod/Part/App/TopoShape.h | 2 ++ src/Mod/Part/App/TopoShapePy.xml | 5 +++++ src/Mod/Part/App/TopoShapePyImp.cpp | 11 +++++++++++ 4 files changed, 45 insertions(+) diff --git a/src/Mod/Part/App/TopoShape.cpp b/src/Mod/Part/App/TopoShape.cpp index 3246fbe4b5..32d817cd82 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -4206,6 +4206,33 @@ bool TopoShape::findPlane(gp_Pln &pln, double tol) const { } } +bool TopoShape::isInfinite() const +{ + if (_Shape.IsNull()) + return false; + + try { + // If the shape is empty an exception may be thrown + Bnd_Box bounds; + BRepBndLib::Add(_Shape, bounds); + bounds.SetGap(0.0); + Standard_Real xMin, yMin, zMin, xMax, yMax, zMax; + bounds.Get(xMin, yMin, zMin, xMax, yMax, zMax); + + if (Precision::IsInfinite(xMax - xMin)) + return true; + if (Precision::IsInfinite(yMax - yMin)) + return true; + if (Precision::IsInfinite(zMax - zMin)) + return true; + + return false; + } + catch (Standard_Failure&) { + return false; + } +} + bool TopoShape::isCoplanar(const TopoShape &other, double tol) const { if(isNull() || other.isNull()) return false; diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 96ccb0df9f..2ef6027191 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -198,6 +198,8 @@ public: bool isClosed() const; bool isCoplanar(const TopoShape &other, double tol=-1) const; bool findPlane(gp_Pln &pln, double tol=-1) const; + /// Returns true if the expansion of the shape is infinite, false otherwise + bool isInfinite() const; //@} /** @name Boolean operation*/ diff --git a/src/Mod/Part/App/TopoShapePy.xml b/src/Mod/Part/App/TopoShapePy.xml index f0924ebc59..f50d37e9c1 100644 --- a/src/Mod/Part/App/TopoShapePy.xml +++ b/src/Mod/Part/App/TopoShapePy.xml @@ -552,6 +552,11 @@ If the shape is an edge it returns True if its vertices are the same. isCoplanar(shape,tol=None) -- Checks if this shape is coplanar with the given shape. + + + isInfinite() -- Checks if this shape has an infinite expansion. + + findPlane(tol=None) -- return a plane if the shape is planar diff --git a/src/Mod/Part/App/TopoShapePyImp.cpp b/src/Mod/Part/App/TopoShapePyImp.cpp index 4d5f69718b..4b4870ca06 100644 --- a/src/Mod/Part/App/TopoShapePyImp.cpp +++ b/src/Mod/Part/App/TopoShapePyImp.cpp @@ -1896,6 +1896,17 @@ PyObject* TopoShapePy::isCoplanar(PyObject *args) }PY_CATCH_OCC } +PyObject* TopoShapePy::isInfinite(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) + return nullptr; + + PY_TRY { + return Py::new_reference_to(Py::Boolean(getTopoShapePtr()->isInfinite())); + } + PY_CATCH_OCC +} + PyObject* TopoShapePy::findPlane(PyObject *args) { double tol = -1; From 590cbd85e3fc5d140eaf40c3ee65c93bda2ce332 Mon Sep 17 00:00:00 2001 From: luz paz Date: Sat, 19 Dec 2020 07:28:39 -0500 Subject: [PATCH 65/65] Fix 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,click,dum,eiter,elemente,ende,feld,finde,findf,freez,hist,iff,indicies,initialisation,initialise,initialised,initialises,initialisiert,ist,kilometre,lod,mantatory,methode,metres,millim,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,./ChangeLogpios++,./src/Base/swig*,./src/Mod/Robot/App/kdl_cp,./src/Mod/Import/App/SCL,./src/WindowsInstaller,./src/Doc/FreeCAD.uml ``` --- src/Mod/Path/PathScripts/PathProfile.py | 2 +- src/Mod/Sketcher/App/GeometryFacade.h | 2 +- src/Mod/Sketcher/App/planegcs/GCS.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathProfile.py b/src/Mod/Path/PathScripts/PathProfile.py index 7fb10fe979..8112b81840 100644 --- a/src/Mod/Path/PathScripts/PathProfile.py +++ b/src/Mod/Path/PathScripts/PathProfile.py @@ -262,7 +262,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): return params def areaOpAreaParamsExpandProfile(self, obj, isHole): - '''areaOpPathParamsExpandProfile(obj, isHole) ... return dictionary with area parameters for expaned profile''' + '''areaOpPathParamsExpandProfile(obj, isHole) ... return dictionary with area parameters for expanded profile''' params = {} params['Fill'] = 1 diff --git a/src/Mod/Sketcher/App/GeometryFacade.h b/src/Mod/Sketcher/App/GeometryFacade.h index 579f14805a..31cac6809d 100644 --- a/src/Mod/Sketcher/App/GeometryFacade.h +++ b/src/Mod/Sketcher/App/GeometryFacade.h @@ -83,7 +83,7 @@ class GeometryFacadePy; // } // // -// Note: The standard GeometryFacade stores Part::Geometry derived clases as a Part::Geometry *, while +// Note: The standard GeometryFacade stores Part::Geometry derived classes as a Part::Geometry *, while // it has the ability to return a dynamic_cast-ed version to a provided type as follows: // // HLine->getGeometry(); diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index 292e9fe73d..aca4cad8dd 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -3880,7 +3880,7 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); makeReducedJacobian(J, jacobianconstraintmap, pdiagnoselist, tagmultiplicity); - // this function will exit with a diagnosis and, unless overriden by functions below, with full DoFs + // this function will exit with a diagnosis and, unless overridden by functions below, with full DoFs hasDiagnosis = true; dofs = pdiagnoselist.size();