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()); } 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; 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; 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"); } diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui index 7d0a694760..c723acb05c 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,7 +20,149 @@ Task Hole Parameters - + + + + <b>Threading and size</b> + + + + + + + + 0 + 0 + + + + Profile + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + + + + 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 @@ -33,8 +175,60 @@ + + + + + 0 + 0 + + + + Direction + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + + Right hand + + + directionButtonGroup + + + + + + + Left hand + + + directionButtonGroup + + + + + + + + 0 + 0 + + + + Size + + + - + 0 @@ -47,44 +241,16 @@ 16777215 - - - Dimension - - - - - Through all - - - - - - - 0 - 0 - - - - - 110 - 16777215 - - - - Hole diameter - - - mm - - - 0.000000000000000 + + + + Clearance - + @@ -119,253 +285,7 @@ Only available for holes without thread - - - - false - - - Angle - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 13 - 20 - - - - - - - <b>Hole cut</b> - - - - - - - Thread direction - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - Right hand - - - - - - - Left hand - - - - - - - - - - false - - - mm - - - 0.000000000000000 - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - mm - - - 0.000000000000000 - - - 0.100000000000000 - - - - - - - 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 - - - - - @@ -378,24 +298,7 @@ Only available for holes without thread - - - - Tapered - - - - - - - false - - - Cutoff inner - - - - + @@ -414,7 +317,100 @@ Only available for holes without thread - + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 13 + 20 + + + + + + + + + 0 + 0 + + + + + 110 + 16777215 + + + + Hole diameter + + + mm + + + 0.000000000000000 + + + + + + + + 0 + 0 + + + + Diameter + + + + + + + + 0 + 0 + + + + Depth + + + + + + + + 0 + 0 + + + + + 140 + 16777215 + + + + + Dimension + + + + + Through all + + + + + @@ -427,56 +423,7 @@ Only available for holes without thread - - - - - 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 - - - - + @@ -489,27 +436,7 @@ Only available for holes without thread - - - - false - - - deg - - - 0.000000000000000 - - - - - - - <b>Threading and size</b> - - - - + @@ -528,164 +455,14 @@ 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 - - - - - - - Whether the hole gets a thread - - - Threaded - - - - - - - - 0 - 0 - - - - - 140 - 16777215 - - - - - - - - - 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 - - - Pitch - - - - + @@ -713,7 +490,172 @@ over 90: larger hole radius at the bottom - + + + + Depth + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + mm + + + 0.000000000000000 + + + 0.100000000000000 + + + + + + + Countersink angle + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + deg + + + 0.000000000000000 + + + + + + + + 0 + 0 + + + + <b>Drill point</b> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Type + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + 0 + 0 + + + + Flat + + + drillPointButtonGroup + + + + + + + + 0 + 0 + + + + Angled + + + drillPointButtonGroup + + + + + + + + 0 + 0 + + + + deg + + + 0.000000000000000 + + + + + + + <b>Misc</b> + + + + + + + Tapered + + + + + + + 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 + + + + Reverses the hole direction @@ -788,4 +730,8 @@ over 90: larger hole radius at the bottom + + + + 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/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) 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/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/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) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 9cd6f488de..5ffe7503dd 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) @@ -252,7 +254,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) @@ -286,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) @@ -316,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: diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index 0ee54747bb..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,25 +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() - 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()) + # 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) 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""" 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/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/Sketch.cpp b/src/Mod/Sketcher/App/Sketch.cpp index 108ff572cc..361dbd5aa3 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; } @@ -1063,6 +1175,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()); @@ -3692,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 2e35a40cdb..8448935830 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; @@ -96,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; } @@ -106,6 +105,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! @@ -409,7 +413,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 diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index 55ffdfa7d0..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 @@ -146,7 +148,6 @@ SketchObject::SketchObject() internaltransaction=false; managedoperation=false; - deletinginternalgeometry=false; } SketchObject::~SketchObject() @@ -957,10 +958,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 +999,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 +4975,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 +5258,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 } @@ -7025,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 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; 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..b21e4e1f8d --- /dev/null +++ b/src/Mod/Sketcher/App/SolverGeometryExtension.h @@ -0,0 +1,92 @@ +/*************************************************************************** + * 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 + +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 diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index d7629e37c1..aca4cad8dd 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 @@ -52,6 +53,7 @@ #include #include #include +#include #include "GCS.h" #include "qp_eq.h" @@ -61,9 +63,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 +72,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 +79,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. @@ -411,7 +406,7 @@ typedef boost::adjacency_list Gra System::System() : plist(0) , pdrivenlist(0) - , pdependentparameters(0) + , pDependentParameters(0) , clist(0) , c2p() , p2c() @@ -422,6 +417,7 @@ System::System() , hasUnknowns(false) , hasDiagnosis(false) , isInit(false) + , emptyDiagnoseMatrix(true) , maxIter(100) , maxIterRedundant(100) , sketchSizeMultiplier(false) @@ -534,10 +530,13 @@ void System::clear() plist.clear(); pdrivenlist.clear(); pIndex.clear(); - pdependentparameters.clear(); + pDependentParameters.clear(); + pDependentParametersGroups.clear(); hasUnknowns = false; hasDiagnosis = false; + emptyDiagnoseMatrix = true; + redundant.clear(); conflictingTags.clear(); redundantTags.clear(); @@ -3849,6 +3848,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; } @@ -3880,434 +3880,653 @@ SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); makeReducedJacobian(J, jacobianconstraintmap, pdiagnoselist, tagmultiplicity); + // this function will exit with a diagnosis and, unless overridden by functions below, with full DoFs + 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 + // - 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 - 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 +#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 + if(qrAlgorithm==EigenDenseQR){ + #ifdef PROFILE_DIAGNOSE + Base::TimeInfo DenseQR_start_time; + #endif + if (J.rows() > 0) { + int rank = 0; // rank is not cheap to retrieve from qrJT in DenseQR + Eigen::MatrixXd R; + Eigen::FullPivHouseholderQR qrJT; + // 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. 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. + // + // identifyDependentParametersDenseQR(J, jacobianconstraintmap, pdiagnoselist, true) + auto fut = std::async(&System::identifyDependentParametersDenseQR,this,J,jacobianconstraintmap, pdiagnoselist, true); + makeDenseQRDecomposition( J, jacobianconstraintmap, qrJT, rank, R); -#ifdef _GCS_DEBUG - SolverReportingManager::Manager().LogMatrix("J",J); + 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 + + // 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 + 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 + if (J.rows() > 0) { + int rank = 0; + Eigen::MatrixXd R; + Eigen::SparseQR, Eigen::COLAMDOrdering > SqrJT; + // 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. 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(&System::identifyDependentParametersSparseQR,this,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 + + // Detecting conflicting or redundant constraints + if (constrNum > rank) { // conflicting or redundant constraints + + int nonredundantconstrNum; + + identifyConflictingRedundantConstraints(alg, SqrJT, jacobianconstraintmap, tagmultiplicity, pdiagnoselist, + R, constrNum, rank, nonredundantconstrNum); + + if (paramsNum == rank && nonredundantconstrNum > rank) // over-constrained + 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 - Eigen::MatrixXd R; + return dofs; +} + +void System::makeDenseQRDecomposition( const Eigen::MatrixXd &J, + const std::map &jacobianconstraintmap, + Eigen::FullPivHouseholderQR& qrJT, + int &rank, Eigen::MatrixXd & R, bool transposeJ, bool silent) +{ + +#ifdef _GCS_DEBUG + if(!silent) + 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 + // 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; - int paramsNum = 0; - int constrNum = 0; - int rank = 0; - Eigen::FullPivHouseholderQR qrJT; + if (J.rows() > 0) { + Eigen::MatrixXd JG; + if(transposeJ) + JG = J.topRows(jacobianconstraintmap.size()).transpose(); + else + JG = J.topRows(jacobianconstraintmap.size()); - if(qrAlgorithm==EigenDenseQR){ - if (J.rows() > 0) { - qrJT.compute(J.topRows(jacobianconstraintmap.size()).transpose()); - //Eigen::MatrixXd Q = qrJT.matrixQ (); + if (JG.rows() > 0 && JG.cols() > 0) { - paramsNum = qrJT.rows(); - constrNum = qrJT.cols(); + qrJT.compute(JG); + + rowsNum = qrJT.rows(); + colsNum = qrJT.cols(); qrJT.setThreshold(qrpivotThreshold); rank = qrJT.rank(); - if (constrNum >= paramsNum) + if (colsNum >= rowsNum) R = qrJT.matrixQR().triangularView(); else - R = qrJT.matrixQR().topRows(constrNum) + 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(); - Q = qrJT.matrixQ(); + R2 = qrJT.matrixQR(); + Q = qrJT.matrixQ(); #endif - } - } -#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); } - if (J.rows() > 0) { + if(debugMode==IterationLevel && !silent) { + SolverReportingManager::Manager().LogQRSystemInformation(*this, rowsNum, colsNum, rank); + } + #ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX + if (J.rows() > 0 && !silent) { 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); - } + SolverReportingManager::Manager().LogMatrix("Q", Q); + SolverReportingManager::Manager().LogMatrix("RowTransp", qrJT.rowsTranspositions()); + } #endif -#endif - - // 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++) { - // 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)]); - } +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) +{ - // 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); - } + Eigen::SparseMatrix SJ; - // 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; + // 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(); - 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())) || + #ifdef _GCS_DEBUG + if(!silent) + SolverReportingManager::Manager().LogMatrix("J",J); + #endif - (static_cast(it->second.size()) == maxPopularity && mostPopular && - tagmultiplicity.at(it->first->getTag()) == tagmultiplicity.at(mostPopular->getTag()) && - it->first->getTag() > mostPopular->getTag()) + #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 - ) { - 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); - } - } + // 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; - 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); - } + if (SJ.rows() > 0) { + Eigen::SparseMatrix SJG; + if(transposeJ) + SJG = SJ.topRows(jacobianconstraintmap.size()).transpose(); + else + SJG = SJ.topRows(jacobianconstraintmap.size()); - SubSystem *subSysTmp = new SubSystem(clistTmp, pdiagnoselist); - int res = solve(subSysTmp,true,alg,true); + 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 + #ifdef SPARSE_Q_MATRIX + Q = SqrJT.matrixQ(); + //Q = QS; + #endif - 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; - } + rowsNum = SqrJT.rows(); + colsNum = SqrJT.cols(); + SqrJT.setPivotThreshold(qrpivotThreshold); + rank = SqrJT.rank(); - Base::Console().Log("Sketcher::RedundantSolving-%s-\n",solvername.c_str()); - } + if (colsNum >= rowsNum) + R = SqrJT.matrixR().triangularView(); + else + R = SqrJT.matrixR().topRows(colsNum) + .triangularView(); - 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; - } + #ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX + R2 = SqrJT.matrixR(); + #endif + } + else { + rowsNum = SJG.rows(); + colsNum = SJG.cols(); } - - hasDiagnosis = true; - dofs = paramsNum - rank; - return dofs; } - hasDiagnosis = true; - dofs = pdiagnoselist.size(); - return dofs; + if(debugMode==IterationLevel && !silent) + SolverReportingManager::Manager().LogQRSystemInformation(*this, rowsNum, colsNum, rank); + + #ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX + if (J.rows() > 0 && !silent) { + + SolverReportingManager::Manager().LogMatrix("R", R); + + 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::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); +} + +#ifdef EIGEN_SPARSEQR_COMPATIBLE +void System::identifyDependentParametersSparseQR( const Eigen::MatrixXd &J, + const std::map &jacobianconstraintmap, + const GCS::VEC_pD &pdiagnoselist, + bool silent) +{ + 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); +} +#endif + +template +void System::identifyDependentParameters( T & qrJ, + Eigen::MatrixXd &Rparams, + int rank, + 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(); + + eliminateNonZerosOverPivotInUpperTriangularMatrix(Rparams, rank); + +#ifdef _GCS_DEBUG + if(!silent) + SolverReportingManager::Manager().LogMatrix("Rparams", Rparams); +#endif + 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]; + + pDependentParametersGroups[j-rank].insert(pdiagnoselist[origCol]); + pDependentParameters.push_back(pdiagnoselist[origCol]); + } + } + int origCol = qrJ.colsPermutation().indices()[j]; + + pDependentParametersGroups[j-rank].insert(pdiagnoselist[origCol]); + pDependentParameters.push_back(pdiagnoselist[origCol]); + } +} + +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::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, + const std::map &jacobianconstraintmap, + const std::map< int , int> &tagmultiplicity, + GCS::VEC_pD &pdiagnoselist, + Eigen::MatrixXd &R, + int constrNum, int rank, + int &nonredundantconstrNum + ) +{ + eliminateNonZerosOverPivotInUpperTriangularMatrix(R, rank); + + 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 85a5fd7130..7a1bd83524 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 { /////////////////////////////////////// @@ -91,7 +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::set > pDependentParametersGroups; std::vector clist; std::map c2p; // constraint to parameter adjacency list @@ -116,12 +131,66 @@ 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); void makeReducedJacobian(Eigen::MatrixXd &J, std::map &jacobianconstraintmap, GCS::VEC_pD &pdiagnoselist, std::map< int , int> &tagmultiplicity); + void makeDenseQRDecomposition( const Eigen::MatrixXd &J, + const std::map &jacobianconstraintmap, + 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 + void identifyDependentGeometryParametersInTransposedJacobianDenseQRDecomposition( + const Eigen::FullPivHouseholderQR& qrJT, + const GCS::VEC_pD &pdiagnoselist, + 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 + ); + + 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, + 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 @@ -292,8 +361,11 @@ 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;} }; 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 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); 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) 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 9f378b3247..aac96821b8 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); // #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; @@ -1166,24 +1171,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); @@ -2756,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++) @@ -2765,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; + } } } @@ -2820,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; j(); + 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: @@ -6040,10 +6138,30 @@ 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); + FullyConstraintElementColor.setPackedValue((uint32_t)color, transparency); // set the cross lines color //CrossColorV.setPackedValue((uint32_t)color, transparency); //CrossColorH.setPackedValue((uint32_t)color, transparency); @@ -6101,14 +6219,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)); 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