From 2de933793d12658783cb598a8cfae08be990bc8f Mon Sep 17 00:00:00 2001 From: wwmayer Date: Sun, 12 Oct 2025 20:08:04 +0200 Subject: [PATCH 1/3] App: Implement Save() & Restore() for int/float constraint properties Implement the methods Save() and Restore() for PropertyIntegerConstraint and PropertyFloatConstraint. Handle also the case of a user-defined ranges. This fixes https://github.com/FreeCAD/FreeCAD/issues/24571. Hint: For PropertyQuantityConstraint this is not doable because it doesn't support user-defined ranges --- src/App/PropertyStandard.cpp | 96 ++++++++++++++++++++++++++++++++++++ src/App/PropertyStandard.h | 6 +++ 2 files changed, 102 insertions(+) diff --git a/src/App/PropertyStandard.cpp b/src/App/PropertyStandard.cpp index 57f70d9886..fbf7da46d3 100644 --- a/src/App/PropertyStandard.cpp +++ b/src/App/PropertyStandard.cpp @@ -770,6 +770,54 @@ void PropertyIntegerConstraint::setPyObject(PyObject* value) } } +void PropertyIntegerConstraint::Save(Base::Writer& writer) const +{ + writer.Stream() << writer.ind() + << "isDeletable()) { + long minimum = _ConstStruct->LowerBound; + long maximum = _ConstStruct->UpperBound; + long stepsize = _ConstStruct->StepSize; + writer.Stream() << " min=\"" << minimum << "\"" + << " max=\"" << maximum << "\"" + << " step=\"" << stepsize << "\""; + } + writer.Stream() << "/>\n"; +} + +void PropertyIntegerConstraint::Restore(Base::XMLReader& reader) +{ + // read my Element + reader.readElement("Integer"); + // get the value of my Attribute + setValue(reader.getAttributeAsInteger("value")); + + bool createConstraint = false; + long minimum = std::numeric_limits::lowest(); + long maximum = std::numeric_limits::max(); + long stepsize = 1.0; + if (reader.hasAttribute("min")) { + minimum = reader.getAttributeAsInteger("min"); + createConstraint = true; + } + if (reader.hasAttribute("max")) { + maximum = reader.getAttributeAsInteger("max"); + createConstraint = true; + } + if (reader.hasAttribute("step")) { + stepsize = reader.getAttributeAsInteger("step"); + } + + if (createConstraint) { + Constraints* c = new Constraints(); + c->setDeletable(true); + c->LowerBound = minimum; + c->UpperBound = maximum; + c->StepSize = stepsize; + setConstraints(c); + } +} + //************************************************************************** //************************************************************************** // PropertyPercent @@ -1272,6 +1320,54 @@ void PropertyFloatConstraint::setPyObject(PyObject* value) } } +void PropertyFloatConstraint::Save(Base::Writer& writer) const +{ + writer.Stream() << writer.ind() + << "isDeletable()) { + double minimum = _ConstStruct->LowerBound; + double maximum = _ConstStruct->UpperBound; + double stepsize = _ConstStruct->StepSize; + writer.Stream() << " min=\"" << minimum << "\"" + << " max=\"" << maximum << "\"" + << " step=\"" << stepsize << "\""; + } + writer.Stream() << "/>\n"; +} + +void PropertyFloatConstraint::Restore(Base::XMLReader& reader) +{ + // read my Element + reader.readElement("Float"); + // get the value of my Attribute + setValue(reader.getAttributeAsFloat("value")); + + bool createConstraint = false; + double minimum = std::numeric_limits::lowest(); + double maximum = std::numeric_limits::max(); + double stepsize = 1.0; + if (reader.hasAttribute("min")) { + minimum = reader.getAttributeAsFloat("min"); + createConstraint = true; + } + if (reader.hasAttribute("max")) { + maximum = reader.getAttributeAsFloat("max"); + createConstraint = true; + } + if (reader.hasAttribute("step")) { + stepsize = reader.getAttributeAsFloat("step"); + } + + if (createConstraint) { + Constraints* c = new Constraints(); + c->setDeletable(true); + c->LowerBound = minimum; + c->UpperBound = maximum; + c->StepSize = stepsize; + setConstraints(c); + } +} + //************************************************************************** // PropertyPrecision //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/src/App/PropertyStandard.h b/src/App/PropertyStandard.h index b2b0a81d22..3eff182e84 100644 --- a/src/App/PropertyStandard.h +++ b/src/App/PropertyStandard.h @@ -339,6 +339,9 @@ public: } void setPyObject(PyObject* py) override; + void Save(Base::Writer& writer) const override; + void Restore(Base::XMLReader& reader) override; + protected: const Constraints* _ConstStruct {nullptr}; }; @@ -673,6 +676,9 @@ public: void setPyObject(PyObject* py) override; + void Save(Base::Writer& writer) const override; + void Restore(Base::XMLReader& reader) override; + protected: const Constraints* _ConstStruct {nullptr}; }; From 2c221883c16f9749c697249106b34a97c94448c9 Mon Sep 17 00:00:00 2001 From: wwmayer Date: Sun, 12 Oct 2025 20:18:44 +0200 Subject: [PATCH 2/3] Test: Add test case for issue 24571 --- src/Mod/Test/Document.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Mod/Test/Document.py b/src/Mod/Test/Document.py index 1fc88c5974..940449ddac 100644 --- a/src/Mod/Test/Document.py +++ b/src/Mod/Test/Document.py @@ -75,6 +75,23 @@ class DocumentBasicCases(unittest.TestCase): FreeCAD.closeDocument(doc.Name) self.Doc = FreeCAD.newDocument("CreateTest") + def testIssue24571(self): + obj = self.Doc.addObject("App::FeatureTest", "Object") + obj.ConstraintInt = (50, 0, 100, 1) + obj.ConstraintFloat = (50.0, 0.0, 100.0, 1.0) + self.Doc = self.saveAndRestore() + obj = self.Doc.getObject("Object") + # int + obj.ConstraintInt = -1 + self.assertEqual(obj.ConstraintInt, 0) + obj.ConstraintInt = 101 + self.assertEqual(obj.ConstraintInt, 100) + # float + obj.ConstraintFloat = -1.0 + self.assertEqual(obj.ConstraintFloat, 0.0) + obj.ConstraintFloat = 101.0 + self.assertEqual(obj.ConstraintFloat, 100.0) + def testAccessByNameOrID(self): obj = self.Doc.addObject("App::DocumentObject", "MyName") From d77e77f9f4fcbc8eb34fd3908c53879c6e7a6894 Mon Sep 17 00:00:00 2001 From: Furgo <148809153+furgo16@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:35:25 +0100 Subject: [PATCH 3/3] App: Replace getAttributeAs{Integer,Float} with templated getAttribute Template was introduced in https://github.com/FreeCAD/FreeCAD/pull/19907 --- src/App/PropertyStandard.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/App/PropertyStandard.cpp b/src/App/PropertyStandard.cpp index fbf7da46d3..ce5652e2b6 100644 --- a/src/App/PropertyStandard.cpp +++ b/src/App/PropertyStandard.cpp @@ -790,22 +790,22 @@ void PropertyIntegerConstraint::Restore(Base::XMLReader& reader) // read my Element reader.readElement("Integer"); // get the value of my Attribute - setValue(reader.getAttributeAsInteger("value")); + setValue(reader.getAttribute("value")); bool createConstraint = false; long minimum = std::numeric_limits::lowest(); long maximum = std::numeric_limits::max(); long stepsize = 1.0; if (reader.hasAttribute("min")) { - minimum = reader.getAttributeAsInteger("min"); + minimum = reader.getAttribute("min"); createConstraint = true; } if (reader.hasAttribute("max")) { - maximum = reader.getAttributeAsInteger("max"); + maximum = reader.getAttribute("max"); createConstraint = true; } if (reader.hasAttribute("step")) { - stepsize = reader.getAttributeAsInteger("step"); + stepsize = reader.getAttribute("step"); } if (createConstraint) { @@ -1340,22 +1340,22 @@ void PropertyFloatConstraint::Restore(Base::XMLReader& reader) // read my Element reader.readElement("Float"); // get the value of my Attribute - setValue(reader.getAttributeAsFloat("value")); + setValue(reader.getAttribute("value")); bool createConstraint = false; double minimum = std::numeric_limits::lowest(); double maximum = std::numeric_limits::max(); double stepsize = 1.0; if (reader.hasAttribute("min")) { - minimum = reader.getAttributeAsFloat("min"); + minimum = reader.getAttribute("min"); createConstraint = true; } if (reader.hasAttribute("max")) { - maximum = reader.getAttributeAsFloat("max"); + maximum = reader.getAttribute("max"); createConstraint = true; } if (reader.hasAttribute("step")) { - stepsize = reader.getAttributeAsFloat("step"); + stepsize = reader.getAttribute("step"); } if (createConstraint) {