Sketcher: BSpline code review and fix

=====================================

- Avoid using EditCurve for the double use of storing the pole positions and the last mouse position. Split into individual vector for poles and previous cursor position (with appropriate names).
- Store all pole geoids to better show the intend of the code.
- Avoid indexing when referring the last inserted value of a std::vector, use back() instead.
- Encapsulate code to draw the bspline polygon and the cursor text to the given position. This allow to select the right function for every case, while avoiding unclear names, such as "mousemove()".
- Refactor code to finish command, to avoid calling unclear names such as "releasebutton" to better show the intend of the function call.
- Ensure deleted pole does not appear anymore in the scenograph.
This commit is contained in:
Abdullah Tahiri
2022-03-11 20:16:51 +01:00
parent 1e852e6d6b
commit a6e67dafa2

View File

@@ -4563,15 +4563,12 @@ public:
DrawSketchHandlerBSpline(int constructionMethod)
: Mode(STATUS_SEEK_FIRST_CONTROLPOINT)
, MousePressMode(MOUSE_NOT_PRESSED)
, EditCurve(2)
, CurrentConstraint(0)
, ConstrMethod(constructionMethod)
, SplineDegree(3)
, IsClosed(false)
, FirstPoleGeoId(-2000)
{
std::vector<AutoConstraint> sugConstr1;
sugConstr.push_back(sugConstr1);
addSugConstraint();
applyCursor();
}
virtual ~DrawSketchHandlerBSpline() {}
@@ -4592,41 +4589,37 @@ public:
virtual void mouseMove(Base::Vector2d onSketchPos) override
{
prevCursorPosition = onSketchPos;
if (Mode==STATUS_SEEK_FIRST_CONTROLPOINT) {
setPositionText(onSketchPos);
if (seekAutoConstraint(sugConstr[CurrentConstraint], onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr[CurrentConstraint]);
if (seekAutoConstraint(sugConstr.back(), onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr.back());
return;
}
}
else if (Mode==STATUS_SEEK_ADDITIONAL_CONTROLPOINTS) {
EditCurve[EditCurve.size()-1] = onSketchPos;
drawEdit(EditCurve);
drawControlPolygonToPosition(onSketchPos);
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));
drawCursorToPosition(onSketchPos);
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]);
if (seekAutoConstraint(sugConstr.back(), onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr.back());
return;
}
}
applyCursor();
}
virtual bool pressButton(Base::Vector2d onSketchPos) override
{
prevCursorPosition = onSketchPos;
MousePressMode = MOUSE_PRESSED;
if (Mode == STATUS_SEEK_FIRST_CONTROLPOINT) {
EditCurve[0] = onSketchPos;
BSplinePoles.push_back(onSketchPos);
Mode = STATUS_SEEK_ADDITIONAL_CONTROLPOINTS;
@@ -4636,12 +4629,12 @@ public:
//Add pole
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.Circle(App.Vector(%f,%f,0),App.Vector(0,0,1),10),True)",
EditCurve[0].x,EditCurve[0].y);
BSplinePoles.back().x, BSplinePoles.back().y);
FirstPoleGeoId = getHighestCurveIndex();
poleGeoIds.push_back(getHighestCurveIndex());
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Weight',%d,%f)) ",
FirstPoleGeoId, 1.0 ); // First pole defaults to 1.0 weight
poleGeoIds.back(), 1.0 ); // First pole defaults to 1.0 weight
}
catch (const Base::Exception& e) {
Base::Console().Error("%s\n", e.what());
@@ -4657,33 +4650,30 @@ public:
//static_cast<Sketcher::SketchObject *>(sketchgui->getObject())->solve();
// add auto constraints on pole
if (sugConstr[CurrentConstraint].size() > 0) {
createAutoConstraints(sugConstr[CurrentConstraint], FirstPoleGeoId, Sketcher::PointPos::mid, false);
if (sugConstr.back().size() > 0) {
createAutoConstraints(sugConstr.back(), poleGeoIds.back(), Sketcher::PointPos::mid, false);
}
static_cast<Sketcher::SketchObject *>(sketchgui->getObject())->solve();
std::vector<AutoConstraint> sugConstrN;
sugConstr.push_back(sugConstrN);
CurrentConstraint++;
addSugConstraint();
}
else if (Mode == STATUS_SEEK_ADDITIONAL_CONTROLPOINTS) {
EditCurve[EditCurve.size()-1] = onSketchPos;
BSplinePoles.push_back(onSketchPos);
// check if coincident with first pole
for(std::vector<AutoConstraint>::const_iterator it = sugConstr[CurrentConstraint].begin(); it != sugConstr[CurrentConstraint].end(); it++) {
if( (*it).Type == Sketcher::Coincident && (*it).GeoId == FirstPoleGeoId && (*it).PosId == Sketcher::PointPos::mid ) {
for(auto & ac : sugConstr.back()) {
if( ac.Type == Sketcher::Coincident && ac.GeoId == poleGeoIds[0] && ac.PosId == Sketcher::PointPos::mid ) {
IsClosed = true;
}
}
}
if (IsClosed) {
Mode = STATUS_CLOSE;
if (ConstrMethod == 1) { // if periodic we do not need the last pole
EditCurve.pop_back();
BSplinePoles.pop_back();
sugConstr.pop_back();
return true;
@@ -4697,10 +4687,13 @@ public:
//Add pole
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.Circle(App.Vector(%f,%f,0),App.Vector(0,0,1),10),True)",
EditCurve[EditCurve.size()-1].x,EditCurve[EditCurve.size()-1].y);
BSplinePoles.back().x,BSplinePoles.back().y);
poleGeoIds.push_back(getHighestCurveIndex());
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Equal',%d,%d)) ",
FirstPoleGeoId, FirstPoleGeoId+ EditCurve.size()-1);
poleGeoIds[0], poleGeoIds.back());
}
catch (const Base::Exception& e) {
Base::Console().Error("%s\n", e.what());
@@ -4716,17 +4709,14 @@ public:
//static_cast<Sketcher::SketchObject *>(sketchgui->getObject())->solve();
// add auto constraints on pole
if (sugConstr[CurrentConstraint].size() > 0) {
createAutoConstraints(sugConstr[CurrentConstraint], FirstPoleGeoId + EditCurve.size()-1, Sketcher::PointPos::mid, false);
if (sugConstr.back().size() > 0) {
createAutoConstraints(sugConstr.back(), poleGeoIds.back(), Sketcher::PointPos::mid, false);
}
//static_cast<Sketcher::SketchObject *>(sketchgui->getObject())->solve();
if (!IsClosed) {
EditCurve.resize(EditCurve.size() + 1); // add one place for a pole
std::vector<AutoConstraint> sugConstrN;
sugConstr.push_back(sugConstrN);
CurrentConstraint++;
addSugConstraint();
}
}
@@ -4735,17 +4725,180 @@ public:
virtual bool releaseButton(Base::Vector2d onSketchPos) override
{
prevCursorPosition = onSketchPos;
MousePressMode = MOUSE_NOT_PRESSED;
return finishCommand(onSketchPos);
}
virtual void registerPressedKey(bool pressed, int key) override
{
if (SoKeyboardEvent::D == key && pressed) {
SplineDegree = QInputDialog::getInt(
Gui::getMainWindow(),
QObject::tr("B-Spline Degree"),
QObject::tr("Define B-Spline Degree, between 1 and %1:")
.arg(QString::number(Geom_BSplineCurve::MaxDegree())),
SplineDegree, 1, Geom_BSplineCurve::MaxDegree(), 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 pole
else if (SoKeyboardEvent::BACKSPACE == key && pressed) {
// when mouse is pressed we are in a transitional state so don't mess with it
if (MOUSE_PRESSED == MousePressMode)
return;
// can only delete last pole if it exists
if (STATUS_SEEK_FIRST_CONTROLPOINT == Mode ||
STATUS_CLOSE == Mode)
return;
// if only first pole exists it's equivalent to canceling current spline
if (poleGeoIds.size() == 1) {
// this also exits b-spline creation if continuous mode is off
this->quit();
return;
}
// reverse the steps of press/release button
try {
// already ensured that CurrentConstraint == EditCurve.size() > 1
const int delGeoId = poleGeoIds.back();
const auto& constraints = static_cast<Sketcher::SketchObject *>(sketchgui->getObject())->Constraints.getValues();
for (int i = constraints.size() - 1; i >= 0; --i) {
if (delGeoId == constraints[i]->First ||
delGeoId == constraints[i]->Second ||
delGeoId == constraints[i]->Third)
Gui::cmdAppObjectArgs(sketchgui->getObject(), "delConstraint(%d)", i);
}
// Remove pole
Gui::cmdAppObjectArgs(sketchgui->getObject(), "delGeometry(%d)", delGeoId);
static_cast<Sketcher::SketchObject *>(sketchgui->getObject())->solve();
// last entry is kept for tracking mouse movement
poleGeoIds.pop_back();
BSplinePoles.pop_back();
sugConstr.erase(std::prev(std::prev(sugConstr.end())));
// run this in the end to draw lines and position text
drawControlPolygonToPosition(prevCursorPosition);
drawCursorToPosition(prevCursorPosition);
}
catch (const Base::Exception& e) {
Base::Console().Error("%s\n", e.what());
// some commands might have already deleted some constraints/geometries but not others
Gui::Command::abortCommand();
static_cast<Sketcher::SketchObject *>(sketchgui->getObject())->solve();
return;
}
}
// TODO: On pressing, say, W, modify last pole's weight
// TODO: On pressing, say, M, modify next knot's multiplicity
return;
}
virtual void quit(void) override
{
// We must see if we need to create a B-spline before cancelling everything
// and now just like any other Handler,
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if (poleGeoIds.size() > 1) {
// create B-spline from existing poles
Mode=STATUS_CLOSE;
finishCommand(Base::Vector2d(0.f,0.f));
}
else if(poleGeoIds.size() == 1) {
// if we just have one point and we can not close anything, then cancel this creation but continue according to continuous mode
//sketchgui->getDocument()->undo(1);
Gui::Command::abortCommand();
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
if(!continuousMode){
DrawSketchHandler::quit();
}
else {
// This code disregards existing data and enables the continuous creation mode.
resetHandlerState();
}
}
else { // we have no data (CurrentConstraint == 0) so user when right-clicking really wants to exit
DrawSketchHandler::quit();
}
}
private:
void resetHandlerState()
{
Mode = STATUS_SEEK_FIRST_CONTROLPOINT;
applyCursor();
SplineDegree = 3;
sugConstr.clear();
poleGeoIds.clear();
BSplinePoles.clear();
eraseEditCurve();
addSugConstraint();
IsClosed = false;
}
virtual void activated() override
{
setCrosshairCursor("Sketcher_Pointer_Create_BSpline");
}
void addSugConstraint() {
std::vector<AutoConstraint> sugConstr1;
sugConstr.push_back(std::move(sugConstr1));
}
void drawControlPolygonToPosition(Base::Vector2d position) {
std::vector<Base::Vector2d> editcurve(BSplinePoles);
editcurve.push_back(position);
drawEdit(editcurve);
}
void drawCursorToPosition(Base::Vector2d position) {
float length = (position - BSplinePoles.back()).Length();
float angle = (position - BSplinePoles.back()).GetAngle(Base::Vector2d(1.f,0.f));
SbString text;
text.sprintf(" (%.1f,%.1fdeg)", length, (angle != -FLOAT_MAX) ? angle * 180 / M_PI : 0);
setPositionText(position, text);
}
void eraseEditCurve() {
drawEdit(std::vector<Base::Vector2d>());
}
bool finishCommand(Base::Vector2d position) {
if (Mode==STATUS_CLOSE) {
unsetCursor();
resetPositionText();
std::stringstream stream;
for (std::vector<Base::Vector2d>::const_iterator it=EditCurve.begin();
it != EditCurve.end(); ++it) {
stream << "App.Vector(" << (*it).x << "," << (*it).y << "),";
for (auto & pole : BSplinePoles) {
stream << "App.Vector(" << pole.x << "," << pole.y << "),";
}
std::string controlpoints = stream.str();
@@ -4786,11 +4939,11 @@ public:
// so here we retrieve any autoconstraint on those poles' center and mangle it to the endpoint.
if (ConstrMethod == 0) {
for(auto & constr : static_cast<Sketcher::SketchObject *>(sketchgui->getObject())->Constraints.getValues()) {
if(constr->First == FirstPoleGeoId && constr->FirstPos == Sketcher::PointPos::mid) {
if(constr->First == poleGeoIds[0] && constr->FirstPos == Sketcher::PointPos::mid) {
constr->First = currentgeoid;
constr->FirstPos = Sketcher::PointPos::start;
}
else if(constr->First == (FirstPoleGeoId + CurrentConstraint - 1) && constr->FirstPos == Sketcher::PointPos::mid) {
else if(constr->First == poleGeoIds.back() && constr->FirstPos == Sketcher::PointPos::mid) {
constr->First = currentgeoid;
constr->FirstPos = Sketcher::PointPos::end;
}
@@ -4802,8 +4955,8 @@ public:
cstream << "conList = []\n";
for (size_t i = 0; i < EditCurve.size(); i++) {
cstream << "conList.append(Sketcher.Constraint('InternalAlignment:Sketcher::BSplineControlPoint'," << FirstPoleGeoId+i
for (size_t i = 0; i < poleGeoIds.size(); i++) {
cstream << "conList.append(Sketcher.Constraint('InternalAlignment:Sketcher::BSplineControlPoint'," << poleGeoIds[0] + i
<< "," << static_cast<int>(Sketcher::PointPos::mid) << "," << currentgeoid << "," << i << "))\n";
}
@@ -4835,7 +4988,7 @@ public:
// This code enables the continuous creation mode.
resetHandlerState();
this->mouseMove(onSketchPos);
drawCursorToPosition(position);
/* It is ok not to call to purgeHandler
* in continuous creation mode because the
@@ -4847,156 +5000,25 @@ public:
}
}
else {
this->mouseMove(onSketchPos);
drawCursorToPosition(position);
}
return true;
}
virtual void registerPressedKey(bool pressed, int key) override
{
if (SoKeyboardEvent::D == key && pressed) {
SplineDegree = QInputDialog::getInt(
Gui::getMainWindow(),
QObject::tr("B-Spline Degree"),
QObject::tr("Define B-Spline Degree, between 1 and %1:")
.arg(QString::number(Geom_BSplineCurve::MaxDegree())),
SplineDegree, 1, Geom_BSplineCurve::MaxDegree(), 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 pole
else if (SoKeyboardEvent::BACKSPACE == key && pressed) {
// when mouse is pressed we are in a transitional state so don't mess with it
if (MOUSE_PRESSED == MousePressMode)
return;
// can only delete last pole if it exists
if (STATUS_SEEK_FIRST_CONTROLPOINT == Mode ||
STATUS_CLOSE == Mode)
return;
// if only first pole exists it's equivalent to canceling current spline
if (1 == CurrentConstraint) {
// this also exits b-spline creation if continuous mode is off
this->quit();
return;
}
// reverse the steps of press/release button
try {
// already ensured that CurrentConstraint == EditCurve.size() > 1
const int delGeoId = FirstPoleGeoId+EditCurve.size()-2;
const auto& constraints = static_cast<Sketcher::SketchObject *>(sketchgui->getObject())->Constraints.getValues();
for (int i = constraints.size() - 1; i >= 0; --i) {
if (delGeoId == constraints[i]->First ||
delGeoId == constraints[i]->Second ||
delGeoId == constraints[i]->Third)
Gui::cmdAppObjectArgs(sketchgui->getObject(), "delConstraint(%d)", i);
}
// Remove pole
Gui::cmdAppObjectArgs(sketchgui->getObject(), "delGeometry(%d)", delGeoId);
// last entry is kept for tracking mouse movement
EditCurve.erase(std::prev(std::prev(EditCurve.end())));
sugConstr.erase(std::prev(std::prev(sugConstr.end())));
--CurrentConstraint;
// run this in the end to draw lines and position text
this->mouseMove(EditCurve.back());
}
catch (const Base::Exception& e) {
Base::Console().Error("%s\n", e.what());
// some commands might have already deleted some constraints/geometries but not others
Gui::Command::abortCommand();
static_cast<Sketcher::SketchObject *>(sketchgui->getObject())->solve();
return;
}
}
// TODO: On pressing, say, W, modify last pole's weight
// TODO: On pressing, say, M, modify next knot's multiplicity
return;
}
virtual void quit(void) override
{
// We must see if we need to create a B-spline before cancelling everything
// and now just like any other Handler,
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if (CurrentConstraint > 1) {
// create B-spline from existing poles
Mode=STATUS_CLOSE;
EditCurve.pop_back();
this->releaseButton(Base::Vector2d(0.f,0.f));
}
else if(CurrentConstraint == 1) {
// if we just have one point and we can not close anything, then cancel this creation but continue according to continuous mode
//sketchgui->getDocument()->undo(1);
Gui::Command::abortCommand();
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
if(!continuousMode){
DrawSketchHandler::quit();
}
else {
// This code disregards existing data and enables the continuous creation mode.
resetHandlerState();
}
}
else { // we have no data (CurrentConstraint == 0) so user when right-clicking really wants to exit
DrawSketchHandler::quit();
}
}
private:
void resetHandlerState()
{
Mode = STATUS_SEEK_FIRST_CONTROLPOINT;
EditCurve.clear();
drawEdit(EditCurve);
EditCurve.resize(2);
applyCursor();
SplineDegree = 3;
sugConstr.clear();
std::vector<AutoConstraint> sugConstr1;
sugConstr.push_back(sugConstr1);
CurrentConstraint = 0;
IsClosed = false;
}
virtual void activated() override
{
setCrosshairCursor("Sketcher_Pointer_Create_BSpline");
}
protected:
SELECT_MODE Mode;
MOUSE_PRESS_MODE MousePressMode;
std::vector<Base::Vector2d> EditCurve;
std::vector<Base::Vector2d> BSplinePoles;
std::vector<std::vector<AutoConstraint>> sugConstr;
int CurrentConstraint;
int ConstrMethod;
int SplineDegree;
bool IsClosed;
int FirstPoleGeoId;
std::vector<int> poleGeoIds;
Base::Vector2d prevCursorPosition;
};
DEF_STD_CMD_A(CmdSketcherCreateBSpline)