diff --git a/src/Mod/Part/App/AppPart.cpp b/src/Mod/Part/App/AppPart.cpp index bc4c86d7b..fa8392328 100644 --- a/src/Mod/Part/App/AppPart.cpp +++ b/src/Mod/Part/App/AppPart.cpp @@ -460,6 +460,7 @@ PyMODINIT_FUNC initPart() Part::Geometry ::init(); Part::GeomPoint ::init(); Part::GeomCurve ::init(); + Part::GeomBoundedCurve ::init(); Part::GeomBezierCurve ::init(); Part::GeomBSplineCurve ::init(); Part::GeomConic ::init(); diff --git a/src/Mod/Part/App/BSplineCurvePyImp.cpp b/src/Mod/Part/App/BSplineCurvePyImp.cpp index ab16b4b9e..fd5de9641 100644 --- a/src/Mod/Part/App/BSplineCurvePyImp.cpp +++ b/src/Mod/Part/App/BSplineCurvePyImp.cpp @@ -71,7 +71,21 @@ int BSplineCurvePy::PyInit(PyObject* args, PyObject* /*kwd*/) return 0; } + PyObject* obj; + // poles, [ periodic, degree, interpolate ] + + obj = buildFromPoles(args); + + if (obj) { + Py_DECREF(obj); + return 0; + } + else if (PyErr_ExceptionMatches(PartExceptionOCCError)) { + return -1; + } + PyErr_SetString(PyExc_TypeError, "B-Spline constructor accepts:\n" + "-- poles, [ periodic, degree, interpolate ]\n" "-- empty parameter list\n"); return -1; } diff --git a/src/Mod/Part/App/Geometry.cpp b/src/Mod/Part/App/Geometry.cpp index 71d8843fb..4758687a6 100644 --- a/src/Mod/Part/App/Geometry.cpp +++ b/src/Mod/Part/App/Geometry.cpp @@ -421,10 +421,37 @@ bool GeomCurve::closestParameterToBasicCurve(const Base::Vector3d& point, double } } +// ------------------------------------------------- +TYPESYSTEM_SOURCE_ABSTRACT(Part::GeomBoundedCurve, Part::GeomCurve) + +GeomBoundedCurve::GeomBoundedCurve() +{ +} + +GeomBoundedCurve::~GeomBoundedCurve() +{ +} + +Base::Vector3d GeomBoundedCurve::getStartPoint() const +{ + Handle_Geom_BoundedCurve curve = Handle_Geom_BoundedCurve::DownCast(handle()); + gp_Pnt pnt = curve->StartPoint(); + + return Base::Vector3d(pnt.X(), pnt.Y(), pnt.Z()); +} + +Base::Vector3d GeomBoundedCurve::getEndPoint() const +{ + Handle_Geom_BoundedCurve curve = Handle_Geom_BoundedCurve::DownCast(handle()); + gp_Pnt pnt = curve->EndPoint(); + + return Base::Vector3d(pnt.X(), pnt.Y(), pnt.Z()); +} + // ------------------------------------------------- -TYPESYSTEM_SOURCE(Part::GeomBezierCurve,Part::GeomCurve) +TYPESYSTEM_SOURCE(Part::GeomBezierCurve,Part::GeomBoundedCurve) GeomBezierCurve::GeomBezierCurve() { @@ -473,7 +500,7 @@ PyObject *GeomBezierCurve::getPyObject(void) // ------------------------------------------------- -TYPESYSTEM_SOURCE(Part::GeomBSplineCurve,Part::GeomCurve) +TYPESYSTEM_SOURCE(Part::GeomBSplineCurve,Part::GeomBoundedCurve) GeomBSplineCurve::GeomBSplineCurve() { @@ -538,6 +565,27 @@ void GeomBSplineCurve::setPole(int index, const Base::Vector3d& pole, double wei } } +void GeomBSplineCurve::setPoles(const std::vector& poles, const std::vector& weights) +{ + Standard_Integer index=0; + + std::vector::const_iterator it1; + std::vector::const_iterator it2; + + for(it1 = poles.begin(), it2 = weights.begin(); it1 != poles.end() && it2 != weights.end(); ++it1, ++it2, index++){ + setPole(index, (*it1), (*it2) ); + } +} + +void GeomBSplineCurve::setPoles(const std::vector& poles) +{ + Standard_Integer index=0; + + for(std::vector::const_iterator it1 = poles.begin(); it1 != poles.end(); ++it1, index++){ + setPole(index, (*it1)); + } +} + std::vector GeomBSplineCurve::getPoles() const { std::vector poles; @@ -552,6 +600,109 @@ std::vector GeomBSplineCurve::getPoles() const return poles; } +std::vector GeomBSplineCurve::getWeights() const +{ + std::vector weights; + weights.reserve(myCurve->NbPoles()); + TColStd_Array1OfReal w(1,myCurve->NbPoles()); + myCurve->Weights(w); + + for (Standard_Integer i=w.Lower(); i<=w.Upper(); i++) { + const Standard_Real& real = w(i); + weights.push_back(real); + } + return weights; +} + + +void GeomBSplineCurve::setWeights(const std::vector& weights) +{ + try { + Standard_Integer index=0; + + for(std::vector::const_iterator it = weights.begin(); it != weights.end(); ++it, index++){ + myCurve->SetWeight(index,(*it)); + } + } + catch (Standard_Failure) { + Handle_Standard_Failure e = Standard_Failure::Caught(); + std::cout << e->GetMessageString() << std::endl; + } +} + +void GeomBSplineCurve::setKnot(int index, const double val, int mult) +{ + try { + if (mult < 0) + myCurve->SetKnot(index+1, val); + else + myCurve->SetKnot(index+1, val, mult); + } + catch (Standard_Failure) { + Handle_Standard_Failure e = Standard_Failure::Caught(); + std::cout << e->GetMessageString() << std::endl; + } +} + +void GeomBSplineCurve::setKnots(const std::vector& knots) +{ + Standard_Integer index=0; + + for(std::vector::const_iterator it1 = knots.begin(); it1 != knots.end(); ++it1, index++){ + setKnot(index, (*it1)); + } +} + +void GeomBSplineCurve::setKnots(const std::vector& knots, const std::vector& multiplicities) +{ + Standard_Integer index=0; + + std::vector::const_iterator it1; + std::vector::const_iterator it2; + + for(it1 = knots.begin(), it2 = multiplicities.begin(); it1 != knots.end() && it2 != multiplicities.end(); ++it1, ++it2, index++){ + setKnot(index, (*it1), (*it2) ); + } +} + +std::vector GeomBSplineCurve::getKnots() const +{ + std::vector knots; + knots.reserve(myCurve->NbKnots()); + TColStd_Array1OfReal k(1,myCurve->NbKnots()); + myCurve->Knots(k); + + for (Standard_Integer i=k.Lower(); i<=k.Upper(); i++) { + const Standard_Real& real = k(i); + knots.push_back(real); + } + return knots; +} + +std::vector GeomBSplineCurve::getMultiplicities() const +{ + std::vector mults; + mults.reserve(myCurve->NbKnots()); + TColStd_Array1OfInteger m(1,myCurve->NbKnots()); + myCurve->Multiplicities(m); + + for (Standard_Integer i=m.Lower(); i<=m.Upper(); i++) { + const Standard_Integer& nm = m(i); + mults.push_back(nm); + } + return mults; +} + +int GeomBSplineCurve::getDegree() const +{ + return myCurve->Degree(); +} + +bool GeomBSplineCurve::isPeriodic() const +{ + return myCurve->IsPeriodic()==Standard_True; +} + bool GeomBSplineCurve::join(const Handle_Geom_BSplineCurve& spline) { GeomConvert_CompCurveToBSplineCurve ccbc(this->myCurve); @@ -651,9 +802,121 @@ void GeomBSplineCurve::makeC1Continuous(double tol, double ang_tol) } // Persistence implementer -unsigned int GeomBSplineCurve::getMemSize (void) const {assert(0); return 0;/* not implemented yet */} -void GeomBSplineCurve::Save (Base::Writer &/*writer*/) const {assert(0); /* not implemented yet */} -void GeomBSplineCurve::Restore (Base::XMLReader &/*reader*/) {assert(0); /* not implemented yet */} +unsigned int GeomBSplineCurve::getMemSize (void) const +{ + return sizeof(Geom_BSplineCurve); +} + +void GeomBSplineCurve::Save(Base::Writer& writer) const +{ + // save the attributes of the father class + GeomCurve::Save(writer); + + std::vector poles = this->getPoles(); + std::vector weights = this->getWeights(); + std::vector knots = this->getKnots(); + std::vector mults = this->getMultiplicities(); + int degree = this->getDegree(); + bool isperiodic = this->isPeriodic(); + + writer.Stream() + << writer.ind() + << "" << endl; + + writer.incInd(); + + std::vector::const_iterator itp; + std::vector::const_iterator itw; + + for(itp = poles.begin(), itw = weights.begin(); itp != poles.end() && itw != weights.end(); ++itp, ++itw){ + writer.Stream() + << writer.ind() + << "" << endl; + } + + std::vector::const_iterator itk; + std::vector::const_iterator itm; + + for(itk = knots.begin(), itm = mults.begin(); itk != knots.end() && itm != mults.end(); ++itk, ++itm){ + writer.Stream() + << writer.ind() + << "" << endl; + } + + writer.decInd(); + writer.Stream() << writer.ind() << "" << endl ; +} + +void GeomBSplineCurve::Restore(Base::XMLReader& reader) +{ + // read the attributes of the father class + GeomCurve::Restore(reader); + + reader.readElement("BSplineCurve"); + // get the value of my attribute + int polescount = reader.getAttributeAsInteger("PolesCount"); + int knotscount = reader.getAttributeAsInteger("KnotsCount"); + int degree = reader.getAttributeAsInteger("Degree"); + bool isperiodic = (bool) reader.getAttributeAsInteger("IsPeriodic"); + + // Handle_Geom_BSplineCurve spline = new + // Geom_BSplineCurve(occpoles,occweights,occknots,occmults,degree, + // PyObject_IsTrue(periodic) ? Standard_True : Standard_False, + // PyObject_IsTrue(CheckRational) ? Standard_True : Standard_False); + + TColgp_Array1OfPnt p(1,polescount); + TColStd_Array1OfReal w(1,polescount); + TColStd_Array1OfReal k(1,knotscount); + TColStd_Array1OfInteger m(1,knotscount); + + for (int i = 1; i <= polescount; i++) { + reader.readElement("Pole"); + double X = reader.getAttributeAsFloat("X"); + double Y = reader.getAttributeAsFloat("Y"); + double Z = reader.getAttributeAsFloat("Z"); + double W = reader.getAttributeAsFloat("Weight"); + p.SetValue(i, gp_Pnt(X,Y,Z)); + w.SetValue(i, W); + } + + for (int i = 1; i <= knotscount; i++) { + reader.readElement("Knot"); + double val = reader.getAttributeAsFloat("Value"); + Standard_Integer mult = reader.getAttributeAsInteger("Mult"); + k.SetValue(i, val); + m.SetValue(i, mult); + } + + reader.readEndElement("BSplineCurve"); + // Geom_BSplineCurve(occpoles,occweights,occknots,occmults,degree,periodic,CheckRational + + try { + Handle_Geom_BSplineCurve spline = new Geom_BSplineCurve(p, w, k, m, degree, isperiodic==true?Standard_True:Standard_False, Standard_False); + + if (!spline.IsNull()) + this->myCurve = spline; + else + throw Base::Exception("BSpline restore failed"); + } + catch (Standard_Failure) { + Handle_Standard_Failure e = Standard_Failure::Caught(); + throw Base::Exception(e->GetMessageString()); + } +} + PyObject *GeomBSplineCurve::getPyObject(void) { diff --git a/src/Mod/Part/App/Geometry.h b/src/Mod/Part/App/Geometry.h index 391b1e783..aa0eeee88 100644 --- a/src/Mod/Part/App/Geometry.h +++ b/src/Mod/Part/App/Geometry.h @@ -130,7 +130,19 @@ public: bool closestParameterToBasicCurve(const Base::Vector3d& point, double &u) const; }; -class PartExport GeomBezierCurve : public GeomCurve +class PartExport GeomBoundedCurve : public GeomCurve +{ + TYPESYSTEM_HEADER(); +public: + GeomBoundedCurve(); + virtual ~GeomBoundedCurve(); + + // Geometry helper + virtual Base::Vector3d getStartPoint() const; + virtual Base::Vector3d getEndPoint() const; +}; + +class PartExport GeomBezierCurve : public GeomBoundedCurve { TYPESYSTEM_HEADER(); public: @@ -153,7 +165,7 @@ private: Handle_Geom_BezierCurve myCurve; }; -class PartExport GeomBSplineCurve : public GeomCurve +class PartExport GeomBSplineCurve : public GeomBoundedCurve { TYPESYSTEM_HEADER(); public: @@ -183,7 +195,18 @@ public: int countPoles() const; void setPole(int index, const Base::Vector3d&, double weight=-1); + void setPoles(const std::vector& poles, const std::vector& weights); + void setPoles(const std::vector& poles); + void setWeights(const std::vector& weights); + void setKnot(int index, const double val, int mult=-1); + void setKnots(const std::vector& knots); + void setKnots(const std::vector& knots, const std::vector& multiplicities); std::vector getPoles() const; + std::vector getWeights() const; + std::vector getKnots() const; + std::vector getMultiplicities() const; + int getDegree() const; + bool isPeriodic() const; bool join(const Handle_Geom_BSplineCurve&); void makeC1Continuous(double, double); std::list toBiArcs(double tolerance) const; diff --git a/src/Mod/Sketcher/App/Constraint.cpp b/src/Mod/Sketcher/App/Constraint.cpp index 1f846b740..cd32c1e98 100644 --- a/src/Mod/Sketcher/App/Constraint.cpp +++ b/src/Mod/Sketcher/App/Constraint.cpp @@ -56,7 +56,8 @@ Constraint::Constraint() ThirdPos(none), LabelDistance(10.f), LabelPosition(0.f), - isDriving(true) + isDriving(true), + InternalAlignmentIndex(-1) { // Initialize a random number generator, to avoid Valgrind false positives. static boost::mt19937 ran; @@ -85,7 +86,8 @@ Constraint::Constraint(const Constraint& from) LabelDistance(from.LabelDistance), LabelPosition(from.LabelPosition), isDriving(from.isDriving), - tag(from.tag) + tag(from.tag), + InternalAlignmentIndex(from.InternalAlignmentIndex) { } @@ -114,6 +116,7 @@ Constraint *Constraint::copy(void) const temp->LabelDistance = this->LabelDistance; temp->LabelPosition = this->LabelPosition; temp->isDriving = this->isDriving; + temp->InternalAlignmentIndex = this->InternalAlignmentIndex; // Do not copy tag, otherwise it is considered a clone, and a "rename" by the expression engine. return temp; } @@ -172,22 +175,24 @@ unsigned int Constraint::getMemSize (void) const void Constraint::Save (Writer &writer) const { writer.Stream() << writer.ind() << "Type==InternalAlignment) writer.Stream() - << "InternalAlignmentType=\"" << (int)AlignmentType << "\" "; + << "InternalAlignmentType=\"" << (int)AlignmentType << "\" " + << "InternalAlignmentIndex=\"" << InternalAlignmentIndex << "\" "; writer.Stream() - << "Value=\"" << Value << "\" " - << "First=\"" << First << "\" " - << "FirstPos=\"" << (int) FirstPos << "\" " - << "Second=\"" << Second << "\" " - << "SecondPos=\"" << (int) SecondPos << "\" " - << "Third=\"" << Third << "\" " - << "ThirdPos=\"" << (int) ThirdPos << "\" " - << "LabelDistance=\"" << LabelDistance << "\" " - << "LabelPosition=\"" << LabelPosition << "\" " - << "IsDriving=\"" << (int)isDriving << "\" />" + << "Value=\"" << Value << "\" " + << "First=\"" << First << "\" " + << "FirstPos=\"" << (int) FirstPos << "\" " + << "Second=\"" << Second << "\" " + << "SecondPos=\"" << (int) SecondPos << "\" " + << "Third=\"" << Third << "\" " + << "ThirdPos=\"" << (int) ThirdPos << "\" " + << "LabelDistance=\"" << LabelDistance << "\" " + << "LabelPosition=\"" << LabelPosition << "\" " + << "IsDriving=\"" << (int)isDriving << "\" />" + << std::endl; } @@ -202,10 +207,15 @@ void Constraint::Restore(XMLReader &reader) Second = reader.getAttributeAsInteger("Second"); SecondPos = (PointPos) reader.getAttributeAsInteger("SecondPos"); - if(this->Type==InternalAlignment) + if(this->Type==InternalAlignment) { AlignmentType = (InternalAlignmentType) reader.getAttributeAsInteger("InternalAlignmentType"); - else + + if (reader.hasAttribute("InternalAlignmentIndex")) + InternalAlignmentIndex = reader.getAttributeAsInteger("InternalAlignmentIndex"); + } + else { AlignmentType = Undef; + } // read the third geo group if present if (reader.hasAttribute("Third")) { diff --git a/src/Mod/Sketcher/App/Constraint.h b/src/Mod/Sketcher/App/Constraint.h index f7b78de9d..e46691d07 100644 --- a/src/Mod/Sketcher/App/Constraint.h +++ b/src/Mod/Sketcher/App/Constraint.h @@ -67,7 +67,8 @@ enum InternalAlignmentType { HyperbolaMajor = 5, HyperbolaMinor = 6, HyperbolaFocus = 7, - ParabolaFocus = 8 + ParabolaFocus = 8, + BSplineControlPoint = 9 // in this constraint "Third" is used to indicate the index of the control point (0-poles), it is not a GeoId }; /// define if you want to use the end or start point @@ -110,11 +111,12 @@ public: PointPos FirstPos; int Second; PointPos SecondPos; - int Third; + int Third; PointPos ThirdPos; float LabelDistance; float LabelPosition; bool isDriving; + int InternalAlignmentIndex; // Note: for InternalAlignment Type this index indexes equal internal geometry elements (e.g. index of pole in a bspline). It is not a GeoId!! protected: boost::uuids::uuid tag; diff --git a/src/Mod/Sketcher/App/Sketch.cpp b/src/Mod/Sketcher/App/Sketch.cpp index 370f376f9..b0f2ae6d5 100644 --- a/src/Mod/Sketcher/App/Sketch.cpp +++ b/src/Mod/Sketcher/App/Sketch.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include @@ -87,6 +88,7 @@ void Sketch::clear(void) ArcsOfEllipse.clear(); ArcsOfHyperbola.clear(); ArcsOfParabola.clear(); + BSplines.clear(); // deleting the doubles allocated with new for (std::vector::iterator it = Parameters.begin(); it != Parameters.end(); ++it) @@ -182,6 +184,8 @@ const char* nameByType(Sketch::GeoType type) return "arcofhyperbola"; case Sketch::ArcOfParabola: return "arcofparabola"; + case Sketch::BSpline: + return "bspline"; case Sketch::None: default: return "unknown"; @@ -224,6 +228,10 @@ int Sketch::addGeometry(const Part::Geometry *geo, bool fixed) const GeomArcOfParabola *aop = dynamic_cast(geo); // create the definition struct for that geom return addArcOfParabola(*aop, fixed); + } else if (geo->getTypeId() == GeomBSplineCurve::getClassTypeId()) { // add a bspline + const GeomBSplineCurve *bsp = static_cast(geo); + // create the definition struct for that geom + return addBSpline(*bsp, fixed); } else { throw Base::TypeError("Sketch::addGeometry(): Unknown or unsupported type added to a sketch"); @@ -646,6 +654,113 @@ int Sketch::addArcOfParabola(const Part::GeomArcOfParabola ¶bolaSegment, boo return Geoms.size()-1; } +int Sketch::addBSpline(const Part::GeomBSplineCurve &bspline, bool fixed) +{ + std::vector ¶ms = fixed ? FixParameters : Parameters; + + // create our own copy + GeomBSplineCurve *bsp = static_cast(bspline.clone()); + // create the definition struct for that geom + GeoDef def; + def.geo = bsp; + def.type = BSpline; + + std::vector poles = bsp->getPoles(); + std::vector weights = bsp->getWeights(); + std::vector knots = bsp->getKnots(); + std::vector mult = bsp->getMultiplicities(); + int degree = bsp->getDegree(); + bool periodic = bsp->isPeriodic(); + + Base::Vector3d startPnt = bsp->getStartPoint(); + Base::Vector3d endPnt = bsp->getEndPoint(); + + std::vector spoles; + + for(std::vector::const_iterator it = poles.begin(); it != poles.end(); ++it){ + params.push_back(new double( (*it).x )); + params.push_back(new double( (*it).y )); + + GCS::Point p; + p.x = params[params.size()-2]; + p.y = params[params.size()-1]; + + spoles.push_back(p); + } + + std::vector sweights; + + for(std::vector::const_iterator it = weights.begin(); it != weights.end(); ++it) { + params.push_back(new double( (*it) )); + sweights.push_back(params[params.size()-1]); + } + + std::vector sknots; + + for(std::vector::const_iterator it = knots.begin(); it != knots.end(); ++it) { + double * knot = new double( (*it) ); + //params.push_back(knot); + sknots.push_back(knot); + } + + GCS::Point p1, p2; + + double * p1x = new double(startPnt.x); + double * p1y = new double(startPnt.y); + + // if periodic, startpoint and endpoint do not play a role in the solver, this removes unnecesarry DoF of determining where in the curve + // the start and the stop should be + if(!periodic) { + params.push_back(p1x); + params.push_back(p1y); + } + + p1.x = p1x; + p1.y = p1y; + + double * p2x = new double(endPnt.x); + double * p2y = new double(endPnt.y); + + // if periodic, startpoint and endpoint do not play a role in the solver, this removes unnecesarry DoF of determining where in the curve + // the start and the stop should be + if(!periodic) { + params.push_back(p2x); + params.push_back(p2y); + } + p2.x = p2x; + p2.y = p2y; + + def.startPointId = Points.size(); + Points.push_back(p1); + def.endPointId = Points.size(); + Points.push_back(p2); + + GCS::BSpline bs; + bs.start = p1; + bs.end = p2; + bs.poles = spoles; + bs.weights = sweights; + bs.knots = sknots; + bs.mult = mult; + bs.degree = degree; + bs.periodic = periodic; + def.index = BSplines.size(); + BSplines.push_back(bs); + + // store complete set + Geoms.push_back(def); + + // WARNING: This is only valid where the multiplicity of the endpoints conforms with a BSpline + // only then the startpoint is the first control point and the endpoint is the last control point + // accordingly, it is never the case for a periodic BSpline. + if(!bs.periodic && bs.mult[0] > bs.degree && bs.mult[mult.size()-1] > bs.degree) { + GCSsys.addConstraintP2PCoincident(*(bs.poles.begin()),bs.start); + GCSsys.addConstraintP2PCoincident(*(bs.poles.end()-1),bs.end); + } + // return the position of the newly added geometry + return Geoms.size()-1; +} + int Sketch::addCircle(const Part::GeomCircle &cir, bool fixed) { std::vector ¶ms = fixed ? FixParameters : Parameters; @@ -789,6 +904,9 @@ Py::Tuple Sketch::getPyGeometry(void) const } else if (it->type == ArcOfParabola) { GeomArcOfParabola *aop = dynamic_cast(it->geo->clone()); tuple[i] = Py::asObject(new ArcOfParabolaPy(aop)); + } else if (it->type == BSpline) { + GeomBSplineCurve *bsp = dynamic_cast(it->geo->clone()); + tuple[i] = Py::asObject(new BSplineCurvePy(bsp)); } else { // not implemented type in the sketch! } @@ -830,6 +948,9 @@ GCS::Curve* Sketch::getGCSCurveByGeoId(int geoId) case ArcOfParabola: return &ArcsOfParabola[Geoms[geoId].index]; break; + case BSpline: + return &BSplines[Geoms[geoId].index]; + break; default: return 0; }; @@ -1095,6 +1216,8 @@ int Sketch::addConstraint(const Constraint *constraint) case ParabolaFocus: rtn = addInternalAlignmentParabolaFocus(constraint->First,constraint->Second); break; + case BSplineControlPoint: + rtn = addInternalAlignmentBSplineControlPoint(constraint->First,constraint->Second, constraint->InternalAlignmentIndex); default: break; } @@ -2294,6 +2417,32 @@ int Sketch::addInternalAlignmentParabolaFocus(int geoId1, int geoId2) return -1; } +int Sketch::addInternalAlignmentBSplineControlPoint(int geoId1, int geoId2, int poleindex) +{ + std::swap(geoId1, geoId2); + + geoId1 = checkGeoId(geoId1); + geoId2 = checkGeoId(geoId2); + + if (Geoms[geoId1].type != BSpline) + return -1; + if (Geoms[geoId2].type != Circle) + return -1; + + int pointId1 = getPointId(geoId2, mid); + + if (pointId1 >= 0 && pointId1 < int(Points.size())) { + GCS::Circle &c = Circles[Geoms[geoId2].index]; + + GCS::BSpline &b = BSplines[Geoms[geoId1].index]; + + int tag = ++ConstraintsCounter; + GCSsys.addConstraintInternalAlignmentBSplineControlPoint(b, c, poleindex, tag); + return ConstraintsCounter; + } + return -1; +} + double Sketch::calculateAngleViaPoint(int geoId1, int geoId2, double px, double py) { geoId1 = checkGeoId(geoId1); @@ -2454,6 +2603,37 @@ bool Sketch::updateGeometry() aop->setFocal(fd.Length()); aop->setRange(*myArc.startAngle, *myArc.endAngle, /*emulateCCW=*/true); + } else if (it->type == BSpline) { + GCS::BSpline &mybsp = BSplines[it->index]; + + GeomBSplineCurve *bsp = dynamic_cast(it->geo); + + std::vector poles; + std::vector weights; + + std::vector::const_iterator it1; + std::vector::const_iterator it2; + + for( it1 = mybsp.poles.begin(), it2 = mybsp.weights.begin(); it1 != mybsp.poles.end() && it2 != mybsp.weights.end(); ++it1, ++it2) { + poles.push_back(Vector3d( *(*it1).x , *(*it1).y , 0.0)); + weights.push_back(*(*it2)); + } + + bsp->setPoles(poles, weights); + + std::vector knots; + std::vector mult; + + std::vector::const_iterator it3; + std::vector::const_iterator it4; + + for( it3 = mybsp.knots.begin(), it4 = mybsp.mult.begin(); it3 != mybsp.knots.end() && it4 != mybsp.mult.end(); ++it3, ++it4) { + knots.push_back(*(*it3)); + mult.push_back((*it4)); + } + + bsp->setKnots(knots,mult); + } } catch (Base::Exception e) { Base::Console().Error("Updating geometry: Error build geometry(%d): %s\n", @@ -2828,6 +3008,41 @@ int Sketch::initMove(int geoId, PointPos pos, bool fine) GCSsys.rescaleConstraint(i-1, 0.01); GCSsys.rescaleConstraint(i, 0.01); + } + } else if (Geoms[geoId].type == BSpline) { + if (pos == start || pos == end) { + MoveParameters.resize(2); // x,y + GCS::Point p0; + p0.x = &MoveParameters[0]; + p0.y = &MoveParameters[1]; + if (pos == start) { + GCS::Point &p = Points[Geoms[geoId].startPointId]; + *p0.x = *p.x; + *p0.y = *p.y; + GCSsys.addConstraintP2PCoincident(p0,p,-1); + } else if (pos == end) { + GCS::Point &p = Points[Geoms[geoId].endPointId]; + *p0.x = *p.x; + *p0.y = *p.y; + GCSsys.addConstraintP2PCoincident(p0,p,-1); + } + } else if (pos == none || pos == mid) { + GCS::BSpline &bsp = BSplines[Geoms[geoId].index]; + MoveParameters.resize(bsp.poles.size()*2); // x0,y0,x1,y1,....xp,yp + + int mvindex = 0; + for(std::vector::iterator it = bsp.poles.begin(); it != bsp.poles.end() ; it++, mvindex++) { + GCS::Point p1; + p1.x = &MoveParameters[mvindex]; + mvindex++; + p1.y = &MoveParameters[mvindex]; + + *p1.x = *(*it).x; + *p1.y = *(*it).y; + + GCSsys.addConstraintP2PCoincident(p1,(*it),-1); + } + } } else if (Geoms[geoId].type == Arc) { GCS::Point ¢er = Points[Geoms[geoId].midPointId]; @@ -2936,7 +3151,30 @@ int Sketch::movePoint(int geoId, PointPos pos, Base::Vector3d toPoint, bool rela MoveParameters[0] = toPoint.x; MoveParameters[1] = toPoint.y; } - } + } else if (Geoms[geoId].type == BSpline) { + if (pos == start || pos == end) { + MoveParameters[0] = toPoint.x; + MoveParameters[1] = toPoint.y; + } else if (pos == none || pos == mid) { + GCS::BSpline &bsp = BSplines[Geoms[geoId].index]; + + double cx = 0, cy = 0; // geometric center + for (int i=0; i < int(InitParameters.size()-1); i+=2) { + cx += InitParameters[i]; + cy += InitParameters[i+1]; + } + + cx /= bsp.poles.size(); + cy /= bsp.poles.size(); + + for (int i=0; i < int(MoveParameters.size()-1); i+=2) { + + MoveParameters[i] = toPoint.x + InitParameters[i] - cx; + MoveParameters[i+1] = toPoint.y + InitParameters[i+1] - cy; + } + + } + } return solve(); } diff --git a/src/Mod/Sketcher/App/Sketch.h b/src/Mod/Sketcher/App/Sketch.h index 7ff73c9cd..812088ccb 100644 --- a/src/Mod/Sketcher/App/Sketch.h +++ b/src/Mod/Sketcher/App/Sketch.h @@ -132,6 +132,8 @@ public: int addArcOfHyperbola(const Part::GeomArcOfHyperbola &hyperbolaSegment, bool fixed=false); /// add an arc of parabola int addArcOfParabola(const Part::GeomArcOfParabola ¶bolaSegment, bool fixed=false); + /// add a BSpline + int addBSpline(const Part::GeomBSplineCurve &spline, bool fixed=false); //@} @@ -312,6 +314,7 @@ public: int addInternalAlignmentHyperbolaMinorDiameter(int geoId1, int geoId2); int addInternalAlignmentHyperbolaFocus(int geoId1, int geoId2); int addInternalAlignmentParabolaFocus(int geoId1, int geoId2); + int addInternalAlignmentBSplineControlPoint(int geoId1, int geoId2, int poleindex); //@} public: //This func is to be used during angle-via-point constraint creation. It calculates @@ -339,7 +342,8 @@ public: Ellipse = 5, // 1 Point(mid), 5 Parameters(x,y,r1,r2,phi) phi=angle xaxis of elipse with respect of sketch xaxis ArcOfEllipse = 6, ArcOfHyperbola = 7, - ArcOfParabola = 8 + ArcOfParabola = 8, + BSpline = 9 }; float SolveTime; @@ -388,6 +392,7 @@ protected: std::vector ArcsOfEllipse; std::vector ArcsOfHyperbola; std::vector ArcsOfParabola; + std::vector BSplines; bool isInitMove; bool isFine; diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index 9a33e90b0..9847c1fdc 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -43,6 +43,7 @@ # include # include # include +# include # include # include # include @@ -514,6 +515,12 @@ Base::Vector3d SketchObject::getPoint(int GeoId, PointPos PosId) const return aop->getEndPoint(); else if (PosId == mid) return aop->getCenter(); + } else if (geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { + const Part::GeomBSplineCurve *bsp = static_cast(geo); + if (PosId == start) + return bsp->getStartPoint(); + else if (PosId == end) + return bsp->getEndPoint(); } return Base::Vector3d(); @@ -571,6 +578,7 @@ bool SketchObject::isSupportedGeometry(const Part::Geometry *geo) const geo->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || geo->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() || geo->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() || + geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() || geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { return true; } @@ -2132,6 +2140,20 @@ int SketchObject::addSymmetric(const std::vector &geoIdList, int refGeoId, geosymaoe->setRange(theta1,theta2,true); isStartEndInverted.insert(std::make_pair(*it, true)); } + else if(geosym->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()){ + Part::GeomBSplineCurve *geosymbsp = static_cast(geosym); + + std::vector poles = geosymbsp->getPoles(); + + for(std::vector::iterator it = poles.begin(); it != poles.end(); ++it){ + + (*it) = (*it) + 2.0*((*it).Perpendicular(refGeoLine->getStartPoint(),vectline)-(*it)); + } + + geosymbsp->setPoles(poles); + + isStartEndInverted.insert(std::make_pair(*it, false)); + } else if(geosym->getTypeId() == Part::GeomPoint::getClassTypeId()){ Part::GeomPoint *geosympoint = static_cast(geosym); Base::Vector3d cp = geosympoint->getPoint(); @@ -2181,6 +2203,9 @@ int SketchObject::addSymmetric(const std::vector &geoIdList, int refGeoId, else if(georef->getTypeId() == Part::GeomArcOfParabola::getClassTypeId()){ const Part::GeomArcOfParabola *geosymaoe = static_cast(georef); refpoint = geosymaoe->getStartPoint(true); + } else if(georef->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()){ + const Part::GeomBSplineCurve *geosymbsp = static_cast(georef); + refpoint = geosymbsp->getStartPoint(); } break; case Sketcher::end: @@ -2204,6 +2229,10 @@ int SketchObject::addSymmetric(const std::vector &geoIdList, int refGeoId, const Part::GeomArcOfParabola *geosymaoe = static_cast(georef); refpoint = geosymaoe->getEndPoint(true); } + else if(georef->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()){ + const Part::GeomBSplineCurve *geosymbsp = static_cast(georef); + refpoint = geosymbsp->getEndPoint(); + } break; case Sketcher::mid: if(georef->getTypeId() == Part::GeomCircle::getClassTypeId()){ @@ -2375,6 +2404,20 @@ int SketchObject::addSymmetric(const std::vector &geoIdList, int refGeoId, geosymaoe->setRange(theta1,theta2,true); isStartEndInverted.insert(std::make_pair(*it, false)); } + else if(geosym->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()){ + Part::GeomBSplineCurve *geosymbsp = static_cast(geosym); + + std::vector poles = geosymbsp->getPoles(); + + for(std::vector::iterator it = poles.begin(); it != poles.end(); ++it){ + + (*it) = (*it) + 2.0*(refpoint-(*it)); + } + + geosymbsp->setPoles(poles); + + //isStartEndInverted.insert(std::make_pair(*it, false)); + } else if(geosym->getTypeId() == Part::GeomPoint::getClassTypeId()){ Part::GeomPoint *geosympoint = static_cast(geosym); Base::Vector3d cp = geosympoint->getPoint(); @@ -2624,7 +2667,22 @@ int SketchObject::addCopy(const std::vector &geoIdList, const Base::Vector3 if(it == geoIdList.begin()) iterfirstpoint = geoaoe->getStartPoint(true); - } + } + else if(geocopy->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()){ + Part::GeomBSplineCurve *geobsp = static_cast(geocopy); + + std::vector poles = geobsp->getPoles(); + + for(std::vector::iterator it = poles.begin(); it != poles.end(); ++it){ + + (*it) = (*it) + double(x)*displacement + double(y)*perpendicularDisplacement; + } + + geobsp->setPoles(poles); + + if(it == geoIdList.begin()) + iterfirstpoint = geobsp->getStartPoint(); + } else if(geocopy->getTypeId() == Part::GeomPoint::getClassTypeId()){ Part::GeomPoint *geopoint = static_cast(geocopy); Base::Vector3d cp = geopoint->getPoint(); @@ -3292,6 +3350,131 @@ int SketchObject::ExposeInternalGeometry(int GeoId) return incrgeo; //number of added elements } + else if(geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { + + const Part::GeomBSplineCurve *bsp = static_cast(geo); + // First we search what has to be restored + std::vector controlpoints(bsp->countPoles()); + std::vector controlpointgeoids(bsp->countPoles()); + + bool isfirstweightconstrained = false; + + std::vector::iterator itb; + std::vector::iterator it; + + for(it=controlpointgeoids.begin(), itb=controlpoints.begin(); it!=controlpointgeoids.end() && itb!=controlpoints.end(); ++it, ++itb) { + (*it)=-1; + (*itb)=false; + } + + const std::vector< Sketcher::Constraint * > &vals = Constraints.getValues(); + + // search for existing poles + for (std::vector< Sketcher::Constraint * >::const_iterator it= vals.begin(); + it != vals.end(); ++it) { + if((*it)->Type == Sketcher::InternalAlignment && (*it)->Second == GeoId) + { + switch((*it)->AlignmentType){ + case Sketcher::BSplineControlPoint: + controlpoints[(*it)->InternalAlignmentIndex] = true; + controlpointgeoids[(*it)->InternalAlignmentIndex] = (*it)->First; + break; + default: + return -1; + } + } + } + + if(controlpoints[0]) { + // search for first pole weight constraint + for (std::vector< Sketcher::Constraint * >::const_iterator it= vals.begin(); + it != vals.end(); ++it) { + if((*it)->Type == Sketcher::Radius && (*it)->First == controlpointgeoids[0]) { + isfirstweightconstrained = true ; + } + } + } + + int currentgeoid = getHighestCurveIndex(); + int incrgeo = 0; + + std::vector igeo; + std::vector icon; + + std::vector poles = bsp->getPoles(); + + double distance_p0_p1 = (poles[1]-poles[0]).Length(); // for visual purposes only + + int index=0; + + for(it=controlpointgeoids.begin(), itb=controlpoints.begin(); it!=controlpointgeoids.end() && itb!=controlpoints.end(); ++it, ++itb, index++) { + + if(!(*itb)) // if controlpoint not existing + { + Part::GeomCircle *pc = new Part::GeomCircle(); + pc->setCenter(poles[index]); + pc->setRadius(distance_p0_p1/6); + + igeo.push_back(pc); + + Sketcher::Constraint *newConstr = new Sketcher::Constraint(); + newConstr->Type = Sketcher::InternalAlignment; + newConstr->AlignmentType = Sketcher::BSplineControlPoint; + newConstr->First = currentgeoid+incrgeo+1; + newConstr->FirstPos = Sketcher::mid; + newConstr->Second = GeoId; + newConstr->InternalAlignmentIndex = index; + + icon.push_back(newConstr); + + if(it != controlpointgeoids.begin()) { + // if pole-weight newly created make it equal to first weight by default + Sketcher::Constraint *newConstr2 = new Sketcher::Constraint(); + newConstr2->Type = Sketcher::Equal; + newConstr2->First = currentgeoid+incrgeo+1; + newConstr2->FirstPos = Sketcher::none; + newConstr2->Second = controlpointgeoids[0]; + newConstr2->SecondPos = Sketcher::none; + + icon.push_back(newConstr2); + } + else { + controlpointgeoids[0] = currentgeoid+incrgeo+1; + } + + incrgeo++; + } + } + + // constraint the first weight to allow for seamless weight modification and proper visualization + if(!isfirstweightconstrained) { + + Sketcher::Constraint *newConstr = new Sketcher::Constraint(); + newConstr->Type = Sketcher::Radius; + newConstr->First = controlpointgeoids[0]; + newConstr->FirstPos = Sketcher::none; + newConstr->setValue( round(distance_p0_p1/6)); // 1/6 is just an estimation for acceptable general visualization + + icon.push_back(newConstr); + + } + + this->addGeometry(igeo,true); + this->addConstraints(icon); + + for (std::vector::iterator it=igeo.begin(); it != igeo.end(); ++it) + if (*it) + delete *it; + + for (std::vector::iterator it=icon.begin(); it != icon.end(); ++it) + if (*it) + delete *it; + + icon.clear(); + igeo.clear(); + + return incrgeo; //number of added elements + } else return -1; // not supported type } @@ -3479,7 +3662,98 @@ int SketchObject::DeleteUnusedInternalGeometry(int GeoId) return delgeometries.size(); //number of deleted elements } - else + else if( geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { + + const Part::GeomBSplineCurve *bsp = static_cast(geo); + + // First we search existing IA + std::vector controlpointgeoids(bsp->countPoles()); + std::vector associatedcontraints(bsp->countPoles()); + + std::vector::iterator it; + std::vector::iterator ita; + + for(it=controlpointgeoids.begin(), ita=associatedcontraints.begin(); it!=controlpointgeoids.end() && ita!=associatedcontraints.end(); ++it, ++ita) { + (*it) = -1; + (*ita) = 0; + } + + const std::vector< Sketcher::Constraint * > &vals = Constraints.getValues(); + + // search for existing poles + for (std::vector< Sketcher::Constraint * >::const_iterator it= vals.begin(); + it != vals.end(); ++it) { + if((*it)->Type == Sketcher::InternalAlignment && (*it)->Second == GeoId) + { + switch((*it)->AlignmentType){ + case Sketcher::BSplineControlPoint: + controlpointgeoids[(*it)->InternalAlignmentIndex] = (*it)->First; + break; + default: + return -1; + } + } + } + + std::vector delgeometries; + bool firstpoledeleted = false; + + for( it=controlpointgeoids.begin(), ita=associatedcontraints.begin(); it!=controlpointgeoids.end() && ita!=associatedcontraints.end(); ++it, ++ita) { + if((*it) != -1) { + // look for a circle at geoid index + for (std::vector< Sketcher::Constraint * >::const_iterator itc= vals.begin(); + itc != vals.end(); ++itc) { + + if((*itc)->Second == (*it) || (*itc)->First == (*it) || (*itc)->Third == (*it)) + (*ita)++; + } + + if((*ita)<3 ) { // IA + Weight + delgeometries.push_back((*it)); + + if (it == controlpointgeoids.begin()) + firstpoledeleted = true; + } + } + } + + 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); + } + } + + // retest the first pole after removal of equality constraints from other poles + associatedcontraints[0] = 0; + delgeometries.clear(); + + if(controlpointgeoids[0] != -1 && !firstpoledeleted) { + // look for a circle at geoid index + for (std::vector< Sketcher::Constraint * >::const_iterator itc= vals.begin(); + itc != vals.end(); ++itc) { + + if((*itc)->Second == controlpointgeoids[0] || (*itc)->First == controlpointgeoids[0] || (*itc)->Third == controlpointgeoids[0]) + associatedcontraints[0]++; + } + + if(associatedcontraints[0]<4 ) // IA + Weight + Radius + delgeometries.push_back(controlpointgeoids[0]); + } + + if(delgeometries.size()>0) + { + for (std::vector::reverse_iterator it=delgeometries.rbegin(); it!=delgeometries.rend(); ++it) { + delGeometry(*it); + } + } + + + return delgeometries.size(); //number of deleted elements + } + else return -1; // not supported type } @@ -4213,6 +4487,11 @@ void SketchObject::rebuildVertexIndex(void) VertexId2PosId.push_back(end); VertexId2GeoId.push_back(i); VertexId2PosId.push_back(mid); + } else if ((*it)->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { + VertexId2GeoId.push_back(i); + VertexId2PosId.push_back(start); + VertexId2GeoId.push_back(i); + VertexId2PosId.push_back(end); } } } diff --git a/src/Mod/Sketcher/App/SketchObjectPyImp.cpp b/src/Mod/Sketcher/App/SketchObjectPyImp.cpp index a66348aa7..de7a3f52a 100644 --- a/src/Mod/Sketcher/App/SketchObjectPyImp.cpp +++ b/src/Mod/Sketcher/App/SketchObjectPyImp.cpp @@ -114,6 +114,7 @@ PyObject* SketchObjectPy::addGeometry(PyObject *args) geo->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || geo->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() || geo->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() || + geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() || geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { ret = this->getSketchObjectPtr()->addGeometry(geo,isConstruction); } @@ -167,6 +168,7 @@ PyObject* SketchObjectPy::addGeometry(PyObject *args) geo->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || geo->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() || geo->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() || + geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() || geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { geoList.push_back(geo); } diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index 89ece2d11..3272e882c 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -1007,6 +1007,13 @@ int System::addConstraintInternalAlignmentParabolaFocus(Parabola &e, Point &p1, return addConstraintEqual(e.focus1.y, p1.y, tagId); } +int System::addConstraintInternalAlignmentBSplineControlPoint(BSpline &b, Circle &c, int poleindex, int tagId) +{ + addConstraintEqual(b.poles[poleindex].x, c.center.x, tagId); + addConstraintEqual(b.poles[poleindex].y, c.center.y, tagId); + return addConstraintEqual(b.weights[poleindex], c.rad, tagId); +} + //calculates angle between two curves at point of their intersection p. If two //points are supplied, p is used for first curve and p2 for second, yielding a diff --git a/src/Mod/Sketcher/App/planegcs/GCS.h b/src/Mod/Sketcher/App/planegcs/GCS.h index 7674e8a85..193cfb08e 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.h +++ b/src/Mod/Sketcher/App/planegcs/GCS.h @@ -233,6 +233,7 @@ namespace GCS int addConstraintInternalAlignmentHyperbolaMinorDiameter(Hyperbola &e, Point &p1, Point &p2, int tagId=0); int addConstraintInternalAlignmentHyperbolaFocus(Hyperbola &e, Point &p1, int tagId=0); int addConstraintInternalAlignmentParabolaFocus(Parabola &e, Point &p1, int tagId=0); + int addConstraintInternalAlignmentBSplineControlPoint(BSpline &b, Circle &c, int poleindex, int tag=0); double calculateAngleViaPoint(Curve &crv1, Curve &crv2, Point &p); double calculateAngleViaPoint(Curve &crv1, Curve &crv2, Point &p1, Point &p2); diff --git a/src/Mod/Sketcher/App/planegcs/Geo.cpp b/src/Mod/Sketcher/App/planegcs/Geo.cpp index 66cb59d47..71b236332 100644 --- a/src/Mod/Sketcher/App/planegcs/Geo.cpp +++ b/src/Mod/Sketcher/App/planegcs/Geo.cpp @@ -607,4 +607,117 @@ ArcOfParabola* ArcOfParabola::Copy() return crv; } +// bspline +DeriVector2 BSpline::CalculateNormal(Point& p, double* derivparam) +{ + // place holder + DeriVector2 ret; + + if (mult[0] > degree && mult[mult.size()-1] > degree) { + // if endpoints thru end poles + if(*p.x == *start.x && *p.y == *start.y) { + // and you are asking about the normal at start point + // then tangency is defined by first to second poles + DeriVector2 endpt(this->poles[1], derivparam); + DeriVector2 spt(this->poles[0], derivparam); + DeriVector2 npt(this->poles[2], derivparam); // next pole to decide normal direction + + DeriVector2 tg = endpt.subtr(spt); + DeriVector2 nv = npt.subtr(spt); + + if ( tg.scalarProd(nv) > 0 ) + ret = tg.rotate90cw(); + else + ret = tg.rotate90ccw(); + } + else if(*p.x == *end.x && *p.y == *end.y) { + // and you are asking about the normal at end point + // then tangency is defined by last to last but one poles + DeriVector2 endpt(this->poles[poles.size()-1], derivparam); + DeriVector2 spt(this->poles[poles.size()-2], derivparam); + DeriVector2 npt(this->poles[poles.size()-3], derivparam); // next pole to decide normal direction + + DeriVector2 tg = endpt.subtr(spt); + DeriVector2 nv = npt.subtr(spt); + + if ( tg.scalarProd(nv) > 0 ) + ret = tg.rotate90ccw(); + else + ret = tg.rotate90cw(); + } else { + // another point and we have no clue until we implement De Boor + ret = DeriVector2(); + } + } + else { + // either periodic or abnormal endpoint multiplicity, we have no clue so currently unsupported + ret = DeriVector2(); + } + + + return ret; +} + +DeriVector2 BSpline::Value(double u, double du, double* derivparam) +{ + + // place holder + DeriVector2 ret = DeriVector2(); + + return ret; +} + +int BSpline::PushOwnParams(VEC_pD &pvec) +{ + int cnt=0; + + for(VEC_P::const_iterator it = poles.begin(); it != poles.end(); ++it) { + pvec.push_back( (*it).x ); + pvec.push_back( (*it).y ); + } + + cnt = cnt + poles.size() * 2; + + pvec.insert(pvec.end(), weights.begin(), weights.end()); + cnt = cnt + weights.size(); + + pvec.insert(pvec.end(), knots.begin(), knots.end()); + cnt = cnt + knots.size(); + + pvec.push_back(start.x); cnt++; + pvec.push_back(start.y); cnt++; + pvec.push_back(end.x); cnt++; + pvec.push_back(end.y); cnt++; + + return cnt; +} + +void BSpline::ReconstructOnNewPvec(VEC_pD &pvec, int &cnt) +{ + for(VEC_P::iterator it = poles.begin(); it != poles.end(); ++it) { + (*it).x = pvec[cnt]; cnt++; + (*it).y = pvec[cnt]; cnt++; + } + + for(VEC_pD::iterator it = weights.begin(); it != weights.end(); ++it) { + (*it) = pvec[cnt]; cnt++; + } + + for(VEC_pD::iterator it = knots.begin(); it != knots.end(); ++it) { + (*it) = pvec[cnt]; cnt++; + } + + start.x=pvec[cnt]; cnt++; + start.y=pvec[cnt]; cnt++; + end.x=pvec[cnt]; cnt++; + end.y=pvec[cnt]; cnt++; + +} + +BSpline* BSpline::Copy() +{ + BSpline* crv = new BSpline(*this); + return crv; +} + }//namespace GCS diff --git a/src/Mod/Sketcher/App/planegcs/Geo.h b/src/Mod/Sketcher/App/planegcs/Geo.h index b71790400..0855f7534 100644 --- a/src/Mod/Sketcher/App/planegcs/Geo.h +++ b/src/Mod/Sketcher/App/planegcs/Geo.h @@ -36,6 +36,8 @@ namespace GCS double *y; }; + 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 @@ -270,6 +272,32 @@ namespace GCS virtual ArcOfParabola* Copy(); }; + class BSpline: public Curve + { + public: + BSpline(){periodic=false;degree=2;} + virtual ~BSpline(){} + // parameters + VEC_P poles; + VEC_pD weights; + VEC_pD knots; + // dependent parameters (depends on previous parameters, + // but an "arcrules" constraint alike would be required to gain the commodity of simple coincident + // with endpoint constraints) + Point start; + Point end; + // not solver parameters + VEC_I mult; + int degree; + bool periodic; + // interface helpers + 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 BSpline* Copy(); + }; + } //namespace GCS #endif // PLANEGCS_GEO_H diff --git a/src/Mod/Sketcher/Gui/CommandAlterGeometry.cpp b/src/Mod/Sketcher/Gui/CommandAlterGeometry.cpp index 43aec54be..c97b0aef3 100644 --- a/src/Mod/Sketcher/Gui/CommandAlterGeometry.cpp +++ b/src/Mod/Sketcher/Gui/CommandAlterGeometry.cpp @@ -88,6 +88,7 @@ CmdSketcherToggleConstruction::CmdSketcherToggleConstruction() rcCmdMgr.addCommandMode("ToggleConstruction", "Sketcher_CompCreateConic"); rcCmdMgr.addCommandMode("ToggleConstruction", "Sketcher_CompCreateCircle"); rcCmdMgr.addCommandMode("ToggleConstruction", "Sketcher_CompCreateRegularPolygon"); + rcCmdMgr.addCommandMode("ToggleConstruction", "Sketcher_CompCreateBSpline"); } void CmdSketcherToggleConstruction::activated(int iMsg) diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index 7458111b8..58f3fb8dc 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -1939,6 +1939,16 @@ void CmdSketcherConstrainPointOnObject::activated(int iMsg) } if (points[iPnt].GeoId == curves[iCrv].GeoId) continue; //constraining a point of an element onto the element is a bad idea... + + const Part::Geometry *geom = Obj->getGeometry(curves[iCrv].GeoId); + + if( geom && geom->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() ){ + // unsupported until normal to BSpline at any point implemented. + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Point on BSpline edge currently unsupported.")); + continue; + } + cnt++; Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", @@ -2525,6 +2535,21 @@ void CmdSketcherConstrainPerpendicular::activated(int iMsg) QObject::tr("Cannot add a perpendicularity constraint at an unconnected point!")); return; } + + // This code supports simple bspline endpoint perp to any other geometric curve + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); + + if( geom1 && geom2 && + ( geom1->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() || + geom2->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() )){ + + if(geom1->getTypeId() != Part::GeomBSplineCurve::getClassTypeId()) { + std::swap(GeoId1,GeoId2); + std::swap(PosId1,PosId2); + } + // GeoId1 is the bspline now + } // end of code supports simple bspline endpoint tangency openCommand("add perpendicular constraint"); Gui::Command::doCommand( @@ -2553,6 +2578,15 @@ void CmdSketcherConstrainPerpendicular::activated(int iMsg) QObject::tr("Cannot add a perpendicularity constraint at an unconnected point!")); return; } + + const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); + + if( geom2 && geom2->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() ){ + // unsupported until normal to BSpline at any point implemented. + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Perpendicular to BSpline edge currently unsupported.")); + return; + } openCommand("add perpendicularity constraint"); Gui::Command::doCommand( @@ -2573,12 +2607,23 @@ void CmdSketcherConstrainPerpendicular::activated(int iMsg) const Part::Geometry *geo1 = Obj->getGeometry(GeoId1); const Part::Geometry *geo2 = Obj->getGeometry(GeoId2); + if (geo1->getTypeId() != Part::GeomLineSegment::getClassTypeId() && geo2->getTypeId() != Part::GeomLineSegment::getClassTypeId()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("One of the selected edges should be a line.")); return; } + + if( geo1 && geo2 && + ( geo1->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() || + geo2->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() )){ + + // unsupported until tangent to BSpline at any point implemented. + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Perpendicular to BSpline edge currently unsupported.")); + return; + } if (geo1->getTypeId() == Part::GeomLineSegment::getClassTypeId()) std::swap(GeoId1,GeoId2); @@ -2875,6 +2920,21 @@ void CmdSketcherConstrainTangent::activated(int iMsg) return; } + // This code supports simple bspline endpoint tangency to any other geometric curve + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); + + if( geom1 && geom2 && + ( geom1->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() || + geom2->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() )){ + + if(geom1->getTypeId() != Part::GeomBSplineCurve::getClassTypeId()) { + std::swap(GeoId1,GeoId2); + std::swap(PosId1,PosId2); + } + // GeoId1 is the bspline now + } // end of code supports simple bspline endpoint tangency + openCommand("add tangent constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d,%d)) ", @@ -2902,6 +2962,15 @@ void CmdSketcherConstrainTangent::activated(int iMsg) QObject::tr("Cannot add a tangency constraint at an unconnected point!")); return; } + + const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); + + if( geom2 && geom2->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() ){ + // unsupported until tangent to BSpline at any point implemented. + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Tangency to BSpline edge currently unsupported.")); + return; + } openCommand("add tangent constraint"); Gui::Command::doCommand( @@ -2923,6 +2992,17 @@ void CmdSketcherConstrainTangent::activated(int iMsg) const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); + if( geom1 && geom2 && + ( geom1->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() || + geom2->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() )){ + + // unsupported until tangent to BSpline at any point implemented. + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Tangency to BSpline edge currently unsupported.")); + return; + } + + if( geom1 && geom2 && ( geom1->getTypeId() == Part::GeomEllipse::getClassTypeId() || geom2->getTypeId() == Part::GeomEllipse::getClassTypeId() )){ @@ -3729,8 +3809,16 @@ void CmdSketcherConstrainEqual::activated(int iMsg) else hasAlreadyExternal = true; } - + const Part::Geometry *geo = Obj->getGeometry(GeoId); + + if(geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { + // unsupported as they are generally hereogeneus shapes + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Equality for BSpline edge currently unsupported.")); + return; + } + if (geo->getTypeId() != Part::GeomLineSegment::getClassTypeId()) lineSel = true; else if (geo->getTypeId() != Part::GeomArcOfCircle::getClassTypeId()) @@ -4027,6 +4115,15 @@ void CmdSketcherConstrainSnellsLaw::activated(int iMsg) strError = QObject::tr("Incompatible geometry is selected!", dmbg); throw(Base::Exception("")); }; + + const Part::Geometry *geo = Obj->getGeometry(GeoId3); + + if( geo && geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() ){ + // unsupported until normal to BSpline at any point implemented. + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("SnellsLaw on BSpline edge currently unsupported.")); + return; + } double n2divn1=0; diff --git a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp index 9ab42282b..65288bb1f 100644 --- a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp +++ b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp @@ -4023,9 +4023,10 @@ public: currentgeoid++; Gui::Command::doCommand(Gui::Command::Doc, - "App.ActiveDocument.%s.ExposeInternalGeometry(%d)", - sketchgui->getObject()->getNameInDocument(), - currentgeoid); + "App.ActiveDocument.%s.ExposeInternalGeometry(%d)", + sketchgui->getObject()->getNameInDocument(), + currentgeoid); + } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); @@ -4276,6 +4277,430 @@ bool CmdSketcherCompCreateConic::isActive(void) return isCreateGeoActive(getActiveGuiDocument()); } +// ====================================================================================== + +/* XPM */ +static const char *cursor_createbspline[]={ +"32 32 3 1", +"+ c white", +"# c red", +". c None", +"......+.........................", +"......+.........................", +"......+.........................", +"......+.........................", +"......+.........................", +"................................", +"+++++...+++++...................", +"................................", +"......+...............###.......", +"......+...............#.#.......", +"......+...............###.......", +"......+..............#..#.......", +"......+.............#....#......", +"....................#.+..#......", +"..................+#+..+..#...+.", +"................++#.....+.#..+..", +"......+........+..#......++#+...", +".......+......+..#.........#....", +"........++..++..#..........###..", +"..........++....#..........#.#..", +"......#........#...........###..", +".......#......#.................", +"........#.....#.................", +".........#...#..................", +"..........###...................", +"..........#.#...................", +"..........###...................", +"................................", +"................................", +"................................", +"................................", +"................................"}; + +class DrawSketchHandlerBSpline: public DrawSketchHandler +{ +public: + DrawSketchHandlerBSpline(int constructionMethod) + : Mode(STATUS_SEEK_FIRST_CONTROLPOINT) + , EditCurve(2) + , ConstrMethod(constructionMethod) + , CurrentConstraint(0) + { + std::vector sugConstr1; + sugConstr.push_back(sugConstr1); + } + + virtual ~DrawSketchHandlerBSpline() {} + /// modes + enum SELECT_MODE { + STATUS_SEEK_FIRST_CONTROLPOINT, + STATUS_SEEK_ADDITIONAL_CONTROLPOINTS, + STATUS_CLOSE + }; + + virtual void activated(ViewProviderSketch *) + { + setCursor(QPixmap(cursor_createbspline),7,7); + } + + virtual void mouseMove(Base::Vector2d onSketchPos) + { + if (Mode==STATUS_SEEK_FIRST_CONTROLPOINT) { + setPositionText(onSketchPos); + if (seekAutoConstraint(sugConstr[CurrentConstraint], onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr[CurrentConstraint]); + return; + } + } + else if (Mode==STATUS_SEEK_ADDITIONAL_CONTROLPOINTS){ + + EditCurve[EditCurve.size()-1] = onSketchPos; + + sketchgui->drawEdit(EditCurve); + + float length = (EditCurve[EditCurve.size()-1] - EditCurve[EditCurve.size()-2]).Length(); + float angle = (EditCurve[EditCurve.size()-1] - EditCurve[EditCurve.size()-2]).GetAngle(Base::Vector2d(1.f,0.f)); + + SbString text; + text.sprintf(" (%.1f,%.1fdeg)", length, angle * 180 / M_PI); + setPositionText(EditCurve[EditCurve.size()-1], text); + + if (seekAutoConstraint(sugConstr[CurrentConstraint], onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr[CurrentConstraint]); + return; + } + + } + applyCursor(); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) + { + if (Mode == STATUS_SEEK_FIRST_CONTROLPOINT) { + + EditCurve[0] = onSketchPos; + + Mode = STATUS_SEEK_ADDITIONAL_CONTROLPOINTS; + + std::vector sugConstrN; + sugConstr.push_back(sugConstrN); + CurrentConstraint++; + } + else if (Mode == STATUS_SEEK_ADDITIONAL_CONTROLPOINTS) { + + EditCurve[EditCurve.size()-1] = onSketchPos; + + // finish adding controlpoints on double click + if (EditCurve[EditCurve.size()-2] == EditCurve[EditCurve.size()-1]) { + EditCurve.pop_back(); + Mode = STATUS_CLOSE; + } + else { + EditCurve.resize(EditCurve.size() + 1); // add one place for a pole + std::vector sugConstrN; + sugConstr.push_back(sugConstrN); + CurrentConstraint++; + } + + } + return true; + } + + virtual bool releaseButton(Base::Vector2d /*onSketchPos*/) + { + if (Mode==STATUS_CLOSE) { + unsetCursor(); + resetPositionText(); + + std::stringstream stream; + + for (std::vector::const_iterator it=EditCurve.begin(); + it != EditCurve.end(); ++it) { + stream << "App.Vector(" << (*it).x << "," << (*it).y << "),"; + } + + std::string controlpoints = stream.str(); + + // remove last comma and add brackets + int index = controlpoints.rfind(','); + controlpoints.resize(index); + + controlpoints.insert(0,1,'['); + controlpoints.append(1,']'); + + int currentgeoid = getHighestCurveIndex(); + + try { + + Gui::Command::openCommand("Add sketch BSplineCurve"); + + //Add arc of parabola + Gui::Command::doCommand(Gui::Command::Doc, + "App.ActiveDocument.%s.addGeometry(Part.BSplineCurve" + "(%s,%s)," + "%s)", + sketchgui->getObject()->getNameInDocument(), + controlpoints.c_str(), + ConstrMethod == 0 ?"False":"True", + geometryCreationMode==Construction?"True":"False"); + + currentgeoid++; + + Gui::Command::doCommand(Gui::Command::Doc, + "App.ActiveDocument.%s.ExposeInternalGeometry(%d)", + sketchgui->getObject()->getNameInDocument(), + currentgeoid); + + } + catch (const Base::Exception& e) { + Base::Console().Error("%s\n", e.what()); + Gui::Command::abortCommand(); + + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); + bool autoRecompute = hGrp->GetBool("AutoRecompute",false); + + if(autoRecompute) + Gui::Command::updateActive(); + else + static_cast(sketchgui->getObject())->solve(); + + return false; + } + + Gui::Command::commitCommand(); + + int poleindex=0; + for(std::vector>::iterator it=sugConstr.begin(); it != sugConstr.end(); it++, poleindex++) { + // add auto constraints + if ((*it).size() > 0) { + createAutoConstraints((*it), currentgeoid+1+poleindex, Sketcher::mid); + (*it).clear(); + } + } + + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); + bool autoRecompute = hGrp->GetBool("AutoRecompute",false); + + if(autoRecompute) + Gui::Command::updateActive(); + else + static_cast(sketchgui->getObject())->solve(); + + bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true); + + if(continuousMode){ + // This code enables the continuous creation mode. + Mode = STATUS_SEEK_FIRST_CONTROLPOINT; + EditCurve.clear(); + sketchgui->drawEdit(EditCurve); + EditCurve.resize(2); + applyCursor(); + /* It is ok not to call to purgeHandler + * in continuous creation mode because the + * handler is destroyed by the quit() method on pressing the + * right button of the mouse */ + } + else{ + sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider + } + } + return true; + } +protected: + SELECT_MODE Mode; + + std::vector EditCurve; + + std::vector> sugConstr; + + int CurrentConstraint; + int ConstrMethod; + +}; + +DEF_STD_CMD_A(CmdSketcherCreateBSpline); + +CmdSketcherCreateBSpline::CmdSketcherCreateBSpline() + : Command("Sketcher_CreateBSpline") +{ + sAppModule = "Sketcher"; + sGroup = QT_TR_NOOP("Sketcher"); + sMenuText = QT_TR_NOOP("Create B-Spline"); + sToolTipText = QT_TR_NOOP("Create a B-Spline via control point in the sketch."); + sWhatsThis = "Sketcher_CreateBSpline"; + sStatusTip = sToolTipText; + sPixmap = "Sketcher_CreateBSpline"; + eType = ForEdit; +} + +void CmdSketcherCreateBSpline::activated(int iMsg) +{ + Q_UNUSED(iMsg); + ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerBSpline(0) ); +} + +/*void CmdSketcherCreateBSpline::updateAction(int mode) +{ + switch (mode) { + case Normal: + if (getAction()) + getAction()->setIcon(Gui::BitmapFactory().pixmap("Sketcher_CreateBSpline")); + break; + case Construction: + if (getAction()) + getAction()->setIcon(Gui::BitmapFactory().pixmap("Sketcher_CreateBSpline_Constr")); + break; + } +}*/ + +bool CmdSketcherCreateBSpline::isActive(void) +{ + return isCreateGeoActive(getActiveGuiDocument()); +} + +/// @brief Macro that declares a new sketcher command class 'CmdSketcherCreateBSpline' +DEF_STD_CMD_A(CmdSketcherCreatePeriodicBSpline); + +/** + * @brief ctor + */ +CmdSketcherCreatePeriodicBSpline::CmdSketcherCreatePeriodicBSpline() +: Command("Sketcher_CreatePeriodicBSpline") +{ + sAppModule = "Sketcher"; + sGroup = QT_TR_NOOP("Sketcher"); + sMenuText = QT_TR_NOOP("Create periodic B-Spline"); + sToolTipText = QT_TR_NOOP("Create a periodic B-Spline via control point in the sketch."); + sWhatsThis = sToolTipText; + sStatusTip = sToolTipText; + sPixmap = "Sketcher_Create_Periodic_BSpline"; + eType = ForEdit; +} + +void CmdSketcherCreatePeriodicBSpline::activated(int iMsg) +{ + Q_UNUSED(iMsg); + ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerEllipse(1) ); +} + +bool CmdSketcherCreatePeriodicBSpline::isActive(void) +{ + return isCreateGeoActive(getActiveGuiDocument()); +} + + +/// @brief Macro that declares a new sketcher command class 'CmdSketcherCompCreateBSpline' +DEF_STD_CMD_ACLU(CmdSketcherCompCreateBSpline); + +/** + * @brief ctor + */ +CmdSketcherCompCreateBSpline::CmdSketcherCompCreateBSpline() +: Command("Sketcher_CompCreateBSpline") +{ + sAppModule = "Sketcher"; + sGroup = QT_TR_NOOP("Sketcher"); + sMenuText = QT_TR_NOOP("Create a bspline"); + sToolTipText = QT_TR_NOOP("Create a bspline in the sketch"); + sWhatsThis = sToolTipText; + sStatusTip = sToolTipText; + eType = ForEdit; +} + +/** + * @brief Instantiates the bspline handler when the bspline command activated + * @param int iMsg + */ +void CmdSketcherCompCreateBSpline::activated(int iMsg) +{ + if (iMsg == 0) { + ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBSpline(iMsg)); + } else if (iMsg == 1) { + ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBSpline(iMsg)); + } else { + return; + } + + // Since the default icon is reset when enabing/disabling the command we have + // to explicitly set the icon of the used command. + Gui::ActionGroup* pcAction = qobject_cast(_pcAction); + QList a = pcAction->actions(); + + assert(iMsg < a.size()); + pcAction->setIcon(a[iMsg]->icon()); +} + +Gui::Action * CmdSketcherCompCreateBSpline::createAction(void) +{ + Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow()); + pcAction->setDropDownMenu(true); + applyCommandData(this->className(), pcAction); + + QAction* bspline = pcAction->addAction(QString()); + bspline->setIcon(Gui::BitmapFactory().pixmap("Sketcher_CreateBSpline")); + + QAction* periodicbspline = pcAction->addAction(QString()); + periodicbspline->setIcon(Gui::BitmapFactory().pixmap("Sketcher_Create_Periodic_BSpline")); + + _pcAction = pcAction; + languageChange(); + + // default + pcAction->setIcon(Gui::BitmapFactory().pixmap("Sketcher_CreateBSpline")); + int defaultId = 0; + pcAction->setProperty("defaultAction", QVariant(defaultId)); + + return pcAction; +} + +void CmdSketcherCompCreateBSpline::updateAction(int mode) +{ + Gui::ActionGroup* pcAction = qobject_cast(getAction()); + if (!pcAction) + return; + + QList a = pcAction->actions(); + int index = pcAction->property("defaultAction").toInt(); + switch (mode) { + case Normal: + a[0]->setIcon(Gui::BitmapFactory().pixmap("Sketcher_CreateBSpline")); + a[1]->setIcon(Gui::BitmapFactory().pixmap("Sketcher_Create_Periodic_BSpline")); + getAction()->setIcon(a[index]->icon()); + break; + case Construction: + a[0]->setIcon(Gui::BitmapFactory().pixmap("Sketcher_CreateBSpline_Constr")); + a[1]->setIcon(Gui::BitmapFactory().pixmap("Sketcher_Create_Periodic_BSpline_Constr")); + getAction()->setIcon(a[index]->icon()); + break; + } +} + +void CmdSketcherCompCreateBSpline::languageChange() +{ + Command::languageChange(); + + if (!_pcAction) + return; + Gui::ActionGroup* pcAction = qobject_cast(_pcAction); + QList a = pcAction->actions(); + + QAction* bspline = a[0]; + bspline->setText(QApplication::translate("Sketcher_CreateBSpline","BSpline by control points or poles")); + bspline->setToolTip(QApplication::translate("Sketcher_CreateBSpline","Create a BSpline by control points or poles")); + bspline->setStatusTip(QApplication::translate("Sketcher_CreateBSpline","Create a BSpline by control points or poles")); + QAction* periodicbspline = a[1]; + periodicbspline->setText(QApplication::translate("Sketcher_Create_Periodic_BSpline","Periodic BSpline by control points or poles")); + periodicbspline->setToolTip(QApplication::translate("Sketcher_Create_Periodic_BSpline","Create a periodic BSpline by control points or poles")); + periodicbspline->setStatusTip(QApplication::translate("Sketcher_Create_Periodic_BSpline","Create a periodic BSpline by control points or poles")); +} + +bool CmdSketcherCompCreateBSpline::isActive(void) +{ + return isCreateGeoActive(getActiveGuiDocument()); +} + + // ====================================================================================== /* XPM */ @@ -6343,6 +6768,9 @@ void CreateSketcherCommandsCreateGeo(void) rcCmdMgr.addCommand(new CmdSketcherCreateArcOfEllipse()); rcCmdMgr.addCommand(new CmdSketcherCreateArcOfHyperbola()); rcCmdMgr.addCommand(new CmdSketcherCreateArcOfParabola()); + rcCmdMgr.addCommand(new CmdSketcherCreateBSpline()); + rcCmdMgr.addCommand(new CmdSketcherCreatePeriodicBSpline()); + rcCmdMgr.addCommand(new CmdSketcherCompCreateBSpline()); rcCmdMgr.addCommand(new CmdSketcherCreateLine()); rcCmdMgr.addCommand(new CmdSketcherCreatePolyline()); rcCmdMgr.addCommand(new CmdSketcherCreateRectangle()); diff --git a/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp b/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp index e129176bd..bbe94719f 100644 --- a/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp +++ b/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp @@ -765,7 +765,8 @@ void CmdSketcherRestoreInternalAlignmentGeometry::activated(int iMsg) if( geo->getTypeId() == Part::GeomEllipse::getClassTypeId() || geo->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || geo->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() || - geo->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() ) { + geo->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() || + geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() ) { int currentgeoid = Obj->getHighestCurveIndex(); diff --git a/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc b/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc index b7639491b..a4141ec79 100644 --- a/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc +++ b/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc @@ -62,6 +62,8 @@ icons/Sketcher_Create3PointCircle_Constr.svg icons/Sketcher_CreateArc.svg icons/Sketcher_CreateArc_Constr.svg + icons/Sketcher_CreateBSpline.svg + icons/Sketcher_CreateBSpline_Constr.svg icons/Sketcher_CreateCircle.svg icons/Sketcher_CreateCircle_Constr.svg icons/Sketcher_CreateEllipse.svg @@ -91,12 +93,17 @@ icons/Sketcher_CreateText.svg icons/Sketcher_CreateTriangle.svg icons/Sketcher_CreateTriangle_Constr.svg + icons/Sketcher_Create_Periodic_BSpline.svg + icons/Sketcher_Create_Periodic_BSpline_Constr.svg icons/Sketcher_DraftLine.svg icons/Sketcher_EditSketch.svg icons/Sketcher_Element_Arc_Edge.svg icons/Sketcher_Element_Arc_EndPoint.svg icons/Sketcher_Element_Arc_MidPoint.svg icons/Sketcher_Element_Arc_StartingPoint.svg + icons/Sketcher_Element_BSpline_Edge.svg + icons/Sketcher_Element_BSpline_EndPoint.svg + icons/Sketcher_Element_BSpline_StartPoint.svg icons/Sketcher_Element_Circle_Edge.svg icons/Sketcher_Element_Circle_MidPoint.svg icons/Sketcher_Element_Ellipse_All.svg diff --git a/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_CreateBSpline.svg b/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_CreateBSpline.svg new file mode 100644 index 000000000..515b50e49 --- /dev/null +++ b/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_CreateBSpline.svg @@ -0,0 +1,539 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_CreateBSpline_Constr.svg b/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_CreateBSpline_Constr.svg new file mode 100644 index 000000000..04c6f5b33 --- /dev/null +++ b/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_CreateBSpline_Constr.svg @@ -0,0 +1,539 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_Create_Periodic_BSpline.svg b/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_Create_Periodic_BSpline.svg new file mode 100644 index 000000000..be7873673 --- /dev/null +++ b/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_Create_Periodic_BSpline.svg @@ -0,0 +1,488 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_Create_Periodic_BSpline_Constr.svg b/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_Create_Periodic_BSpline_Constr.svg new file mode 100644 index 000000000..851eed083 --- /dev/null +++ b/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_Create_Periodic_BSpline_Constr.svg @@ -0,0 +1,484 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_Element_BSpline_Edge.svg b/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_Element_BSpline_Edge.svg new file mode 100644 index 000000000..a8f5f6690 --- /dev/null +++ b/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_Element_BSpline_Edge.svg @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_Element_BSpline_EndPoint.svg b/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_Element_BSpline_EndPoint.svg new file mode 100644 index 000000000..14d2cf083 --- /dev/null +++ b/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_Element_BSpline_EndPoint.svg @@ -0,0 +1,385 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_Element_BSpline_StartPoint.svg b/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_Element_BSpline_StartPoint.svg new file mode 100644 index 000000000..2ba3d8b41 --- /dev/null +++ b/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_Element_BSpline_StartPoint.svg @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp b/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp index 4a88f1627..242b6ca67 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp @@ -667,6 +667,9 @@ void TaskSketcherElements::slotElementsChanged(void) QIcon Sketcher_Element_ArcOfParabola_MidPoint( Gui::BitmapFactory().pixmap("Sketcher_Element_Parabolic_Arc_Centre_Point") ); QIcon Sketcher_Element_ArcOfParabola_StartingPoint( Gui::BitmapFactory().pixmap("Sketcher_Element_Parabolic_Arc_Start_Point") ); QIcon Sketcher_Element_ArcOfParabola_EndPoint( Gui::BitmapFactory().pixmap("Sketcher_Element_Parabolic_Arc_End_Point") ); + QIcon Sketcher_Element_BSpline_Edge( Gui::BitmapFactory().pixmap("Sketcher_Element_BSpline_Edge") ); + QIcon Sketcher_Element_BSpline_StartingPoint( Gui::BitmapFactory().pixmap("Sketcher_Element_BSpline_StartPoint") ); + QIcon Sketcher_Element_BSpline_EndPoint( Gui::BitmapFactory().pixmap("Sketcher_Element_BSpline_EndPoint") ); QIcon none( Gui::BitmapFactory().pixmap("Sketcher_Element_SelectionTypeInvalid") ); assert(sketchView); @@ -706,6 +709,9 @@ void TaskSketcherElements::slotElementsChanged(void) (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfParabola_StartingPoint : (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfParabola_EndPoint : (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfParabola_MidPoint : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? Sketcher_Element_BSpline_Edge : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? Sketcher_Element_BSpline_StartingPoint : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? Sketcher_Element_BSpline_EndPoint : none, type == Part::GeomPoint::getClassTypeId() ? ( isNamingBoxChecked ? (tr("Point") + QString::fromLatin1("(Edge%1)").arg(i)): @@ -731,6 +737,9 @@ void TaskSketcherElements::slotElementsChanged(void) type == Part::GeomArcOfParabola::getClassTypeId() ? ( isNamingBoxChecked ? (tr("Parabolic Arc") + QString::fromLatin1("(Edge%1)").arg(i)): (QString::fromLatin1("%1-").arg(i)+tr("Parabolic Arc"))) : + type == Part::GeomBSplineCurve::getClassTypeId() ? ( isNamingBoxChecked ? + (tr("BSpline") + QString::fromLatin1("(Edge%1)").arg(i)): + (QString::fromLatin1("%1-").arg(i)+tr("BSpline"))) : ( isNamingBoxChecked ? (tr("Other") + QString::fromLatin1("(Edge%1)").arg(i)): (QString::fromLatin1("%1-").arg(i)+tr("Other"))), @@ -918,7 +927,10 @@ void TaskSketcherElements::updateIcons(int element) QIcon Sketcher_Element_ArcOfParabola_Edge( Gui::BitmapFactory().pixmap("Sketcher_Element_Parabolic_Arc_Edge") ); QIcon Sketcher_Element_ArcOfParabola_MidPoint( Gui::BitmapFactory().pixmap("Sketcher_Element_Parabolic_Arc_Centre_Point") ); QIcon Sketcher_Element_ArcOfParabola_StartingPoint( Gui::BitmapFactory().pixmap("Sketcher_Element_Parabolic_Arc_Start_Point") ); - QIcon Sketcher_Element_ArcOfParabola_EndPoint( Gui::BitmapFactory().pixmap("Sketcher_Element_Parabolic_Arc_End_Point") ); + QIcon Sketcher_Element_ArcOfParabola_EndPoint( Gui::BitmapFactory().pixmap("Sketcher_Element_Parabolic_Arc_End_Point") ); + QIcon Sketcher_Element_BSpline_Edge( Gui::BitmapFactory().pixmap("Sketcher_Element_BSpline_Edge") ); + QIcon Sketcher_Element_BSpline_StartingPoint( Gui::BitmapFactory().pixmap("Sketcher_Element_BSpline_StartPoint") ); + QIcon Sketcher_Element_BSpline_EndPoint( Gui::BitmapFactory().pixmap("Sketcher_Element_BSpline_EndPoint") ); QIcon none( Gui::BitmapFactory().pixmap("Sketcher_Element_SelectionTypeInvalid") ); for (int i=0;ilistWidgetElements->count(); i++) { @@ -949,6 +961,9 @@ void TaskSketcherElements::updateIcons(int element) (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfParabola_StartingPoint : (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfParabola_EndPoint : (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfParabola_MidPoint : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? Sketcher_Element_BSpline_Edge : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? Sketcher_Element_BSpline_StartingPoint : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? Sketcher_Element_BSpline_EndPoint : none); } } diff --git a/src/Mod/Sketcher/Gui/TaskSketcherValidation.cpp b/src/Mod/Sketcher/Gui/TaskSketcherValidation.cpp index aba66511b..b6a89b81a 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherValidation.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherValidation.cpp @@ -245,6 +245,18 @@ void SketcherValidation::on_findButton_clicked() id.v = segm->getEndPoint(); vertexIds.push_back(id); } + else if (g->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { + const Part::GeomBSplineCurve *segm = static_cast(g); + VertexIds id; + id.GeoId = (int)i; + id.PosId = Sketcher::start; + id.v = segm->getStartPoint(); + vertexIds.push_back(id); + id.GeoId = (int)i; + id.PosId = Sketcher::end; + id.v = segm->getEndPoint(); + vertexIds.push_back(id); + } } std::set coincidences; diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index 4a3d4ec8e..6ad82e24d 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -780,7 +780,8 @@ bool ViewProviderSketch::mouseButtonPressed(int Button, bool pressed, const SbVe geo->getTypeId() == Part::GeomEllipse::getClassTypeId()|| geo->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()|| geo->getTypeId() == Part::GeomArcOfParabola::getClassTypeId()|| - geo->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId()) { + geo->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId()|| + geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { Gui::Command::openCommand("Drag Curve"); try { Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.movePoint(%i,%i,App.Vector(%f,%f,0),%i)" @@ -1080,7 +1081,8 @@ bool ViewProviderSketch::mouseMove(const SbVec2s &cursorPos, Gui::View3DInventor edit->DragCurve = edit->PreselectCurve; getSketchObject()->getSolvedSketch().initMove(edit->DragCurve, Sketcher::none, false); const Part::Geometry *geo = getSketchObject()->getGeometry(edit->DragCurve); - if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId() || + geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() ) { relative = true; //xInit = x; //yInit = y; @@ -2353,9 +2355,37 @@ void ViewProviderSketch::doBoxSelection(const SbVec2s &startPos, const SbVec2s & } else if ((*it)->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { const Part::GeomBSplineCurve *spline = static_cast(*it); - std::vector poles = spline->getPoles(); - VertexId += poles.size(); - // TODO + //std::vector poles = spline->getPoles(); + VertexId += 2; + + Plm.multVec(spline->getStartPoint(), pnt1); + Plm.multVec(spline->getEndPoint(), pnt2); + pnt1 = proj(pnt1); + pnt2 = proj(pnt2); + + bool pnt1Inside = polygon.Contains(Base::Vector2d(pnt1.x, pnt1.y)); + bool pnt2Inside = polygon.Contains(Base::Vector2d(pnt2.x, pnt2.y)); + if (pnt1Inside) { + std::stringstream ss; + ss << "Vertex" << VertexId; + Gui::Selection().addSelection(doc->getName(), sketchObject->getNameInDocument(), ss.str().c_str()); + } + + if (pnt2Inside) { + std::stringstream ss; + ss << "Vertex" << VertexId + 1; + Gui::Selection().addSelection(doc->getName(), sketchObject->getNameInDocument(), ss.str().c_str()); + } + + // This is a rather approximated approach. No it does not guarantie that the whole curve is boxed, specially + // for periodic curves, but it works reasonably well. Including all poles, which could be done, generally + // forces the user to select much more than the curve (all the poles) and it would not select the curve in cases + // where it is indeed comprised in the box. + if (pnt1Inside && pnt2Inside) { + std::stringstream ss; + ss << "Edge" << GeoId + 1; + Gui::Selection().addSelection(doc->getName(), sketchObject->getNameInDocument(), ss.str().c_str()); + } } } @@ -3354,6 +3384,9 @@ void ViewProviderSketch::draw(bool temp) const Part::GeomBSplineCurve *spline = static_cast(*it); Handle_Geom_BSplineCurve curve = Handle_Geom_BSplineCurve::DownCast(spline->handle()); + Base::Vector3d startp = spline->getStartPoint(); + Base::Vector3d endp = spline->getEndPoint(); + double first = curve->FirstParameter(); double last = curve->LastParameter(); if (first > last) // if arc is reversed @@ -3373,13 +3406,16 @@ void ViewProviderSketch::draw(bool temp) gp_Pnt end = curve->Value(last); Coords.push_back(Base::Vector3d(end.X(), end.Y(), end.Z())); - std::vector poles = spline->getPoles(); + // abdullah: Poles thought as internal geometry + /*std::vector poles = spline->getPoles(); for (std::vector::iterator it = poles.begin(); it != poles.end(); ++it) { Points.push_back(*it); - } + }*/ Index.push_back(countSegments+1); edit->CurvIdToGeoId.push_back(GeoId); + Points.push_back(startp); + Points.push_back(endp); } else { } diff --git a/src/Mod/Sketcher/Gui/Workbench.cpp b/src/Mod/Sketcher/Gui/Workbench.cpp index 7943a0121..5779f0660 100644 --- a/src/Mod/Sketcher/Gui/Workbench.cpp +++ b/src/Mod/Sketcher/Gui/Workbench.cpp @@ -141,13 +141,16 @@ inline void SketcherAddWorkspaceArcs(Gui::MenuItem& geom){ << "Sketcher_CreateEllipseBy3Points" << "Sketcher_CreateArcOfEllipse" << "Sketcher_CreateArcOfHyperbola" - << "Sketcher_CreateArcOfParabola"; + << "Sketcher_CreateArcOfParabola" + << "Sketcher_CreateBSpline" + << "Sketcher_CreatePeriodicBSpline"; } template <> inline void SketcherAddWorkspaceArcs(Gui::ToolBarItem& geom){ geom << "Sketcher_CompCreateArc" << "Sketcher_CompCreateCircle" - << "Sketcher_CompCreateConic"; + << "Sketcher_CompCreateConic" + << "Sketcher_CompCreateBSpline"; } template void SketcherAddWorkspaceRegularPolygon(T& geom);