From 07cad4ab644923486dfbdd63d03ef1af859d6782 Mon Sep 17 00:00:00 2001 From: Ajinkya Dahale Date: Tue, 28 Dec 2021 21:10:41 -0500 Subject: [PATCH] [Sketcher] Add `insertBSplineKnot` to `SketcherObject` [Sketcher] Workaround for segfault on knot insertion --- src/Mod/Sketcher/App/SketchObject.cpp | 158 ++++++++++++++++++++- src/Mod/Sketcher/App/SketchObject.h | 9 ++ src/Mod/Sketcher/App/SketchObjectPy.xml | 5 + src/Mod/Sketcher/App/SketchObjectPyImp.cpp | 19 +++ 4 files changed, 189 insertions(+), 2 deletions(-) diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index 8fb0e23249..d62d1db9f4 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -1082,8 +1082,8 @@ int SketchObject::delGeometriesExclusiveList(const std::vector& GeoIds) for (const auto ptr : this->Constraints.getValues()) constraints.push_back(ptr->clone()); std::vector< Constraint* > filteredConstraints(0); - for (auto it = sGeoIds.rbegin(); it != sGeoIds.rend(); ++it) { - int GeoId = *it; + for (auto itGeo = sGeoIds.rbegin(); itGeo != sGeoIds.rend(); ++itGeo) { + int GeoId = *itGeo; for (std::vector::const_iterator it = constraints.begin(); it != constraints.end(); ++it) { @@ -5925,6 +5925,160 @@ bool SketchObject::modifyBSplineKnotMultiplicity(int GeoId, int knotIndex, int m return true; } +bool SketchObject::insertBSplineKnot(int GeoId, double param, int multiplicity) +{ + Base::StateLocker lock(managedoperation, true); // TODO: Check if this is still valid: no need to check input data validity as this is an sketchobject managed operation. + + #if OCC_VERSION_HEX < 0x060900 + THROWMT(Base::NotImplementedError, QT_TRANSLATE_NOOP("Exceptions", "This version of OCE/OCC does not support knot operation. You need 6.9.0 or higher.")) + #endif + + // handling unacceptable cases + if (GeoId < 0 || GeoId > getHighestCurveIndex()) + THROWMT(Base::ValueError,QT_TRANSLATE_NOOP("Exceptions", "BSpline Geometry Index (GeoID) is out of bounds.")); + + if (multiplicity == 0) + THROWMT(Base::ValueError,QT_TRANSLATE_NOOP("Exceptions", "Knot cannot have zero multiplicity.")); + + const Part::Geometry *geo = getGeometry(GeoId); + + if(geo->getTypeId() != Part::GeomBSplineCurve::getClassTypeId()) + THROWMT(Base::TypeError,QT_TRANSLATE_NOOP("Exceptions", "The Geometry Index (GeoId) provided is not a B-spline curve.")); + + const Part::GeomBSplineCurve *bsp = static_cast(geo); + + int degree = bsp->getDegree(); + double firstParam = bsp->getFirstParameter(); + double lastParam = bsp->getLastParameter(); + + if (multiplicity > degree) + THROWMT(Base::ValueError,QT_TRANSLATE_NOOP("Exceptions", "Knot multiplicity cannot be higher than the degree of the BSpline.")); + + if (param > lastParam || param < firstParam) + THROWMT(Base::ValueError,QT_TRANSLATE_NOOP("Exceptions", "Knot cannot be inserted outside the BSpline parameter range.")); + + std::unique_ptr bspline; + + // run the command + try { + bspline.reset(static_cast(bsp->clone())); + + bspline->insertKnot(param, multiplicity); + } + catch (const Base::Exception& e) { + Base::Console().Error("%s\n", e.what()); + return false; + } + + // once command is run update the internal geometries + std::vector delGeoId; + + std::vector poles = bsp->getPoles(); + std::vector newpoles = bspline->getPoles(); + std::vector prevpole(bsp->countPoles()); + + for(int i = 0; i < int(poles.size()); i++) + prevpole[i] = -1; + + int taken = 0; + for(int j = 0; j < int(poles.size()); j++){ + for(int i = taken; i < int(newpoles.size()); i++){ + if( newpoles[i] == poles[j] ) { + prevpole[j] = i; + taken++; + break; + } + } + } + + // on fully removing a knot the knot geometry changes + std::vector knots = bsp->getKnots(); + std::vector newknots = bspline->getKnots(); + std::vector prevknot(bsp->countKnots()); + + for(int i = 0; i < int(knots.size()); i++) + prevknot[i] = -1; + + taken = 0; + for(int j = 0; j < int(knots.size()); j++){ + for(int i = taken; i < int(newknots.size()); i++){ + if( newknots[i] == knots[j] ) { + prevknot[j] = i; + taken++; + break; + } + } + } + + const std::vector< Sketcher::Constraint * > &cvals = Constraints.getValues(); + + std::vector< Constraint * > newcVals(0); + + // modify pole constraints + for (std::vector< Sketcher::Constraint * >::const_iterator it= cvals.begin(); it != cvals.end(); ++it) { + if((*it)->Type == Sketcher::InternalAlignment && (*it)->Second == GeoId) + { + if((*it)->AlignmentType == Sketcher::BSplineControlPoint) { + if (prevpole[(*it)->InternalAlignmentIndex]!=-1) { + assert(prevpole[(*it)->InternalAlignmentIndex] < bspline->countPoles()); + Constraint * newConstr = (*it)->clone(); + newConstr->InternalAlignmentIndex = prevpole[(*it)->InternalAlignmentIndex]; + newcVals.push_back(newConstr); + } + else { // it is an internal alignment geometry that is no longer valid => delete it and the pole circle + delGeoId.push_back((*it)->First); + } + } + else if((*it)->AlignmentType == Sketcher::BSplineKnotPoint) { + if (prevknot[(*it)->InternalAlignmentIndex]!=-1) { + assert(prevknot[(*it)->InternalAlignmentIndex] < bspline->countKnots()); + Constraint * newConstr = (*it)->clone(); + newConstr->InternalAlignmentIndex = prevknot[(*it)->InternalAlignmentIndex]; + newcVals.push_back(newConstr); + } + else { // it is an internal alignment geometry that is no longer valid => delete it and the knot point + delGeoId.push_back((*it)->First); + } + } + else { // it is a bspline geometry, but not a controlpoint or knot + newcVals.push_back(*it); + } + } + else { + newcVals.push_back(*it); + } + } + + const std::vector< Part::Geometry * > &vals = getInternalGeometry(); + + std::vector< Part::Geometry * > newVals(vals); + + newVals[GeoId] = bspline.release(); + + // Block acceptGeometry in OnChanged to avoid unnecessary checks and updates + { + Base::StateLocker lock(internaltransaction, true); + Geometry.setValues(std::move(newVals)); + + this->Constraints.setValues(std::move(newcVals)); + } + + // Trigger update now + // Update geometry indices and rebuild vertexindex now via onChanged, so that ViewProvider::UpdateData is triggered. + if (!delGeoId.empty()) { + // FIXME: Somehow this extra `Geometry.touch()` fixes a segfault. + // See https://forum.freecadweb.org/viewtopic.php?f=19&t=64962&sid=10272db50a635c633260517b14ecad37 + Geometry.touch(); + delGeometriesExclusiveList(delGeoId); + } + else { + Geometry.touch(); + } + + // handle this last return + return true; +} + int SketchObject::carbonCopy(App::DocumentObject * pObj, bool construction) { Base::StateLocker lock(managedoperation, true); // no need to check input data validity as this is an sketchobject managed operation. diff --git a/src/Mod/Sketcher/App/SketchObject.h b/src/Mod/Sketcher/App/SketchObject.h index 85fa186607..ba7f6a9a1b 100644 --- a/src/Mod/Sketcher/App/SketchObject.h +++ b/src/Mod/Sketcher/App/SketchObject.h @@ -340,6 +340,15 @@ public: */ bool modifyBSplineKnotMultiplicity(int GeoId, int knotIndex, int multiplicityincr = 1); + /*! + \brief Inserts a knot in the BSpline at `param` with given `multiplicity`. If the knot already exists, its multiplicity is increased by `multiplicity`. + \param GeoId - the geometry of type bspline to increase the degree + \param param - the parameter value where the knot is to be placed + \param multiplicity - multiplicity of the inserted knot + \retval bool - returns true if the operation succeeded, or false if it did not succeed. + */ + bool insertBSplineKnot(int GeoId, double param, int multiplicity = 1); + /// retrieves for a Vertex number the corresponding GeoId and PosId void getGeoVertexIndex(int VertexId, int &GeoId, PointPos &PosId) const; int getHighestVertexIndex(void) const { return VertexId2GeoId.size() - 1; } // Most recently created diff --git a/src/Mod/Sketcher/App/SketchObjectPy.xml b/src/Mod/Sketcher/App/SketchObjectPy.xml index e0fc4ef408..bf054202d2 100644 --- a/src/Mod/Sketcher/App/SketchObjectPy.xml +++ b/src/Mod/Sketcher/App/SketchObjectPy.xml @@ -287,6 +287,11 @@ If there is no such constraint an exception is raised. Increases or reduces the given BSpline knot multiplicity + + + Inserts a knot into the BSpline at the given param with given multiplicity. If the knot already exists, this increases the knot multiplicity by the given multiplicity. + + diff --git a/src/Mod/Sketcher/App/SketchObjectPyImp.cpp b/src/Mod/Sketcher/App/SketchObjectPyImp.cpp index c4c0352f30..8eb7bfea42 100644 --- a/src/Mod/Sketcher/App/SketchObjectPyImp.cpp +++ b/src/Mod/Sketcher/App/SketchObjectPyImp.cpp @@ -1551,6 +1551,25 @@ PyObject* SketchObjectPy::modifyBSplineKnotMultiplicity(PyObject *args) Py_Return; } +PyObject* SketchObjectPy::insertBSplineKnot(PyObject *args) +{ + int GeoId; + double knotParam; + int multiplicity = 1; + + if (!PyArg_ParseTuple(args, "id|i", &GeoId, &knotParam, &multiplicity)) + return 0; + + if (this->getSketchObjectPtr()->insertBSplineKnot(GeoId, knotParam, multiplicity)==false) { + std::stringstream str; + str << "Knot insertion failed for: " << GeoId; + PyErr_SetString(PyExc_ValueError, str.str().c_str()); + return 0; + } + + Py_Return; +} + PyObject* SketchObjectPy::autoconstraint(PyObject *args) { double precision = Precision::Confusion() * 1000;