[Sketcher] Support variable multiplicities in B-splines

As a side-effect also prevents a segfault possible by providing just 3 points.

[Sketcher] Clarify that mults might not be followed

Some examples when this may be ignored include:
1. A number higher than 3 is given (because cubic B-splines are created)
2. If the knots just before and just after have multiplicities >=3, the piece
between these two is fully continuous, and this (middles) point will only be
constrained with point-on-object.

This commit is part of a project funded by the Open Toolchain Foundation under the title "Open Toolchain Foundation - Curve drawing tool in Sketcher Workbench"
This commit is contained in:
Ajinkya Dahale
2023-03-06 08:43:01 +05:30
committed by abdullahtahiriyo
parent 3e5f3a9a57
commit 7c1a2d738e

View File

@@ -97,6 +97,7 @@ public:
if (Mode == STATUS_SEEK_FIRST_POINT) {
BSplineKnots.push_back(onSketchPos);
BSplineMults.push_back(1); // NOTE: not strictly true for end-points
Mode = STATUS_SEEK_ADDITIONAL_POINTS;
@@ -135,6 +136,7 @@ public:
}
else if (Mode == STATUS_SEEK_ADDITIONAL_POINTS) {
BSplineKnots.push_back(onSketchPos);
BSplineMults.push_back(1); // NOTE: not strictly true for end-points
// check if coincident with first knot
for(auto & ac : sugConstr.back()) {
@@ -208,6 +210,21 @@ public:
// FIXME: Pressing Esc here also finishes the B-Spline creation.
// The user may only want to exit the dialog.
}
if (SoKeyboardEvent::M == key && pressed) {
// TODO: On pressing, say, M, modify the knot's multiplicity
if (BSplineMults.size() > 1) {
BSplineMults.back() = QInputDialog::getInt(
Gui::getMainWindow(),
QObject::tr("Set knot multiplicity"),
QObject::tr("NOTE 1: For construction by interpolation, the de facto maximum is currently 3.\n"
"NOTE 2: Under certain circumstances (details WIP), this value may be ignored.\n"
"Set knot multiplicity at the last point provided, between 1 and %1:")
.arg(QString::number(SplineDegree)),
BSplineMults.back(), 1, SplineDegree, 1);
}
// FIXME: Pressing Esc here also finishes the B-Spline creation.
// The user may only want to exit the dialog.
}
// On pressing Backspace delete last knot
else if (SoKeyboardEvent::BACKSPACE == key && pressed) {
// when mouse is pressed we are in a transitional state so don't mess with it
@@ -264,7 +281,6 @@ public:
return;
}
}
// TODO: On pressing, say, M, modify the knot's multiplicity
return;
}
@@ -315,6 +331,7 @@ private:
sugConstr.clear();
knotGeoIds.clear();
BSplineKnots.clear();
BSplineMults.clear();
eraseEditCurve();
@@ -372,50 +389,104 @@ private:
unsetCursor();
resetPositionText();
std::stringstream stream;
std::string controlpoints;
unsigned int myDegree = 3;
// TODO: for knots compute new control points and use those instead
for (auto & knot : BSplineKnots) {
stream << "App.Vector(" << knot.x << "," << knot.y << "),";
BSplineMults.front() = myDegree + 1; // FIXME: This is hardcoded until degree can be changed
BSplineMults.back() = myDegree + 1; // FIXME: This is hardcoded until degree can be changed
std::vector<std::stringstream> streams;
for (size_t i = 0; i < BSplineKnots.size(); ++i) {
if (!streams.empty())
streams.back() << "App.Vector(" << BSplineKnots[i].x << "," << BSplineKnots[i].y << "),";
if (BSplineMults[i] >= myDegree && i != (BSplineKnots.size() - 1)) {
streams.emplace_back();
streams.back() << "App.Vector(" << BSplineKnots[i].x << "," << BSplineKnots[i].y << "),";
}
}
controlpoints = stream.str();
std::vector<std::string> controlpointses; // Gollum gollum
controlpointses.reserve(streams.size());
for (auto & stream: streams) {
// TODO: Use subset of points between C0 knots.
controlpointses.emplace_back(stream.str());
// remove last comma and add brackets
int index = controlpoints.rfind(',');
controlpoints.resize(index);
auto & controlpoints = controlpointses.back();
controlpoints.insert(0,1,'[');
controlpoints.append(1,']');
// remove last comma and add brackets
int index = controlpoints.rfind(',');
controlpoints.resize(index);
controlpoints.insert(0,1,'[');
controlpoints.append(1,']');
}
// With just 3 points provided OCCT gives a quadratic spline where
// the middle point is NOT a knot. This needs to be treated differently.
// FIXME: Decide whether to force a knot or not.
std::vector<bool> isBetweenC0Points(BSplineKnots.size(), false);
for (size_t i = 1; i < BSplineKnots.size()-1; ++i) {
if (BSplineMults[i-1] >= myDegree && BSplineMults[i+1] >= myDegree)
isBetweenC0Points[i] = true;
}
int currentgeoid = getHighestCurveIndex();
// TODO: Adjust this or remove condition if needed
unsigned int maxDegree = 1;
try {
//Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add B-spline curve"));
// FIXME: can we get by without naming the variable?
// TODO: variable degrees?
QString cmdstr = QString::fromLatin1("_bsp = Part.BSplineCurve()\n"
"_bsp.interpolate(%1, PeriodicFlag=%2)\n"
).arg(QString::fromLatin1(controlpoints.c_str()),
QString::fromLatin1(ConstrMethod == 0 ?"False":"True"));
Gui::Command::runCommand(Gui::Command::Gui, cmdstr.toLatin1());
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(_bsp,%s)",
geometryCreationMode==Construction?"True":"False");
Gui::Command::runCommand(Gui::Command::Gui, "del(_bsp)\n");
// TODO: Bypass this for when there are no C0 knots
// TODO: Create B-spline in pieces between C0 knots
Gui::Command::runCommand(Gui::Command::Gui, "_finalbsp_poles = []");
Gui::Command::runCommand(Gui::Command::Gui, "_finalbsp_knots = []");
Gui::Command::runCommand(Gui::Command::Gui, "_finalbsp_mults = []");
Gui::Command::runCommand(Gui::Command::Gui, "_bsps = []");
for (auto & controlpoints: controlpointses) {
// FIXME: can we get by without naming the variable?
// TODO: variable degrees?
QString cmdstr = QString::fromLatin1("_bsps.append(Part.BSplineCurve())\n"
"_bsps[-1].interpolate(%1, PeriodicFlag=%2)\n"
"_bsps[-1].increaseDegree(%3)")
.arg(QString::fromLatin1(controlpoints.c_str()))
.arg(QString::fromLatin1(ConstrMethod == 0 ?"False":"True"))
.arg(myDegree);
Gui::Command::runCommand(Gui::Command::Gui, cmdstr.toLatin1());
// TODO: Adjust internal knots here (raise multiplicity)
// How this contributes to the final B-spline
if (controlpoints == controlpointses.front()) {
Gui::Command::runCommand(Gui::Command::Gui, "_finalbsp_poles.extend(_bsps[-1].getPoles())");
Gui::Command::runCommand(Gui::Command::Gui, "_finalbsp_knots.extend(_bsps[-1].getKnots())");
Gui::Command::runCommand(Gui::Command::Gui, "_finalbsp_mults.extend(_bsps[-1].getMultiplicities())");
}
else {
// TODO: Adjust for periodic
Gui::Command::runCommand(Gui::Command::Gui, "_finalbsp_poles.extend(_bsps[-1].getPoles()[1:])");
Gui::Command::runCommand(Gui::Command::Gui, "_finalbsp_knots.extend([_finalbsp_knots[-1] + i for i in _bsps[-1].getKnots()[1:]])");
Gui::Command::runCommand(Gui::Command::Gui, "_finalbsp_mults[-1] = 3"); // FIXME: Hardcoded
Gui::Command::runCommand(Gui::Command::Gui, "_finalbsp_mults.extend(_bsps[-1].getMultiplicities()[1:])");
}
}
// TODO: Join the pieces of the B-splines
// {"poles", "mults", "knots", "periodic", "degree", "weights", "CheckRational", NULL};
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.BSplineCurve"
"(_finalbsp_poles,_finalbsp_mults,_finalbsp_knots,%s,%d,None,False),%s)",
ConstrMethod == 0 ?"False":"True",
myDegree,
geometryCreationMode==Construction?"True":"False");
currentgeoid++;
// TODO: Confirm we do not need to delete individual elements
Gui::Command::runCommand(Gui::Command::Gui, "del(_bsps)\n");
Gui::Command::runCommand(Gui::Command::Gui, "del(_finalbsp_poles)\n");
Gui::Command::runCommand(Gui::Command::Gui, "del(_finalbsp_knots)\n");
Gui::Command::runCommand(Gui::Command::Gui, "del(_finalbsp_mults)\n");
// autoconstraints were added to the knots, which is ok because they must go to the
// right position, or the user will freak-out if they appear out of the autoconstrained position.
// However, autoconstraints on the first and last knot, in non-periodic b-splines (with appropriate endpoint knot multiplicity)
// as the ones created by this tool are intended for the b-spline endpoints, and not for the knots,
// so here we retrieve any autoconstraint on those knots and mangle it to the endpoint.
// TODO: this will be done to the first and last knots instead for ConstrMethod==2
if (ConstrMethod == 0) {
for(auto & constr : static_cast<Sketcher::SketchObject *>(sketchgui->getObject())->Constraints.getValues()) {
if(constr->First == knotGeoIds[0] && constr->FirstPos == Sketcher::PointPos::start) {
@@ -434,9 +505,26 @@ private:
cstream << "conList = []\n";
int knotNumber = 0;
for (size_t i = 0; i < knotGeoIds.size(); i++) {
cstream << "conList.append(Sketcher.Constraint('InternalAlignment:Sketcher::BSplineKnotPoint'," << knotGeoIds[0] + i
<< "," << static_cast<int>(Sketcher::PointPos::start) << "," << currentgeoid << "," << i << "))\n";
if (isBetweenC0Points[i]) {
// Constraint point on curve
cstream << "conList.append(Sketcher.Constraint('PointOnObject'," << knotGeoIds[0] + i
<< "," << static_cast<int>(Sketcher::PointPos::start) << "," << currentgeoid << "))\n";
}
else {
cstream << "conList.append(Sketcher.Constraint('InternalAlignment:Sketcher::BSplineKnotPoint'," << knotGeoIds[0] + i
<< "," << static_cast<int>(Sketcher::PointPos::start) << "," << currentgeoid << "," << knotNumber << "))\n";
// NOTE: Assume here that the spline shape doesn't change on increasing knot multiplicity.
// Change the knot multiplicity here because the user asked and it's not C0
// NOTE: The knot number here has to be provided in the OCCT ordering.
if (BSplineMults[i] > 1 && BSplineMults[i] < myDegree) {
Gui::cmdAppObjectArgs(sketchgui->getObject(),
"modifyBSplineKnotMultiplicity(%d, %d, %d) ",
currentgeoid, knotNumber+1, BSplineMults[i] - 1);
}
knotNumber++;
}
}
cstream << Gui::Command::getObjectCmd(sketchgui->getObject()) << ".addConstraint(conList)\n";
@@ -444,7 +532,7 @@ private:
Gui::Command::doCommand(Gui::Command::Doc, cstream.str().c_str());
// for showing the knots on creation
// for showing the rest of internal geometry on creation
Gui::cmdAppObjectArgs(sketchgui->getObject(), "exposeInternalGeometry(%d)", currentgeoid);
}
catch (const Base::Exception& e) {
@@ -491,6 +579,7 @@ protected:
// Stores position of the knots of the BSpline.
std::vector<Base::Vector2d> BSplineKnots;
std::vector<unsigned int> BSplineMults;
// suggested autoconstraints for knots.
// A new one must be added e.g. using addSugConstraint() before adding a new knot.