/*************************************************************************** * Copyright (c) 2010 Jürgen Riegel (juergen.riegel@web.de) * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ # include # include # include # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ViewProviderSketch.h" #include "DrawSketchHandler.h" #include "ui_InsertDatum.h" #include "EditDatumDialog.h" #include "CommandConstraints.h" using namespace std; using namespace SketcherGui; using namespace Sketcher; /***** Creation Mode ************/ namespace SketcherGui { enum ConstraintCreationMode { Driving, Reference }; } ConstraintCreationMode constraintCreationMode = Driving; void ActivateHandler(Gui::Document *doc, DrawSketchHandler *handler); bool isCreateGeoActive(Gui::Document *doc); bool isCreateConstraintActive(Gui::Document *doc) { if (doc) { // checks if a Sketch View provider is in Edit and is in no special mode if (doc->getInEdit() && doc->getInEdit()->isDerivedFrom(SketcherGui::ViewProviderSketch::getClassTypeId())) { if (static_cast(doc->getInEdit()) ->getSketchMode() == ViewProviderSketch::STATUS_NONE) { if (Gui::Selection().countObjectsOfType(Sketcher::SketchObject::getClassTypeId()) > 0) return true; } } } return false; } void openEditDatumDialog(Sketcher::SketchObject* sketch, int ConstrNbr) { const std::vector &Constraints = sketch->Constraints.getValues(); Sketcher::Constraint* Constr = Constraints[ConstrNbr]; // Return if constraint doesn't have editable value if (Constr->Type == Sketcher::Distance || Constr->Type == Sketcher::DistanceX || Constr->Type == Sketcher::DistanceY || Constr->Type == Sketcher::Radius || Constr->Type == Sketcher::Angle || Constr->Type == Sketcher::SnellsLaw) { QDialog dlg(Gui::getMainWindow()); Ui::InsertDatum ui_ins_datum; ui_ins_datum.setupUi(&dlg); double datum = Constr->getValue(); Base::Quantity init_val; if (Constr->Type == Sketcher::Angle) { datum = Base::toDegrees(datum); dlg.setWindowTitle(EditDatumDialog::tr("Insert angle")); init_val.setUnit(Base::Unit::Angle); ui_ins_datum.label->setText(EditDatumDialog::tr("Angle:")); ui_ins_datum.labelEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/SketcherAngle")); } else if (Constr->Type == Sketcher::Radius) { dlg.setWindowTitle(EditDatumDialog::tr("Insert radius")); init_val.setUnit(Base::Unit::Length); ui_ins_datum.label->setText(EditDatumDialog::tr("Radius:")); ui_ins_datum.labelEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/SketcherLength")); } else if (Constr->Type == Sketcher::SnellsLaw) { dlg.setWindowTitle(EditDatumDialog::tr("Refractive index ratio", "Constraint_SnellsLaw")); ui_ins_datum.label->setText(EditDatumDialog::tr("Ratio n2/n1:", "Constraint_SnellsLaw")); ui_ins_datum.labelEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/SketcherRefrIndexRatio")); } else { dlg.setWindowTitle(EditDatumDialog::tr("Insert length")); init_val.setUnit(Base::Unit::Length); ui_ins_datum.label->setText(EditDatumDialog::tr("Length:")); ui_ins_datum.labelEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/SketcherLength")); } // e.g. an angle or a distance X or Y applied on a line or two vertexes init_val.setValue(datum); ui_ins_datum.labelEdit->setValue(init_val); ui_ins_datum.labelEdit->selectNumber(); ui_ins_datum.labelEdit->bind(sketch->Constraints.createPath(ConstrNbr)); ui_ins_datum.name->setText(Base::Tools::fromStdString(Constr->Name)); if (dlg.exec()) { Base::Quantity newQuant = ui_ins_datum.labelEdit->value(); if (newQuant.isQuantity() || (Constr->Type == Sketcher::SnellsLaw && newQuant.isDimensionless())) { // save the value for the history ui_ins_datum.labelEdit->pushToHistory(); double newDatum = newQuant.getValue(); try { if (ui_ins_datum.labelEdit->hasExpression()) ui_ins_datum.labelEdit->apply(); else Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.setDatum(%i,App.Units.Quantity('%f %s'))", sketch->getNameInDocument(), ConstrNbr, newDatum, (const char*)newQuant.getUnit().getString().toUtf8()); QString constraintName = ui_ins_datum.name->text().trimmed(); if (Base::Tools::toStdString(constraintName) != sketch->Constraints[ConstrNbr]->Name) { std::string escapedstr = Base::Tools::escapedUnicodeFromUtf8(constraintName.toUtf8().constData()); Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.renameConstraint(%d, u'%s')", sketch->getNameInDocument(), ConstrNbr, escapedstr.c_str()); } Gui::Command::commitCommand(); if (sketch->noRecomputes && sketch->ExpressionEngine.depsAreTouched()) { sketch->ExpressionEngine.execute(); sketch->solve(); } tryAutoRecompute(); } catch (const Base::Exception& e) { QMessageBox::critical(qApp->activeWindow(), QObject::tr("Dimensional constraint"), QString::fromUtf8(e.what())); Gui::Command::abortCommand(); tryAutoRecomputeIfNotSolve(sketch); } } } else { // command canceled Gui::Command::abortCommand(); tryAutoRecomputeIfNotSolve(sketch); // we have to update the solver after this aborted addition. } } } // Utility method to avoid repeating the same code over and over again void finishDistanceConstraint(Gui::Command* cmd, Sketcher::SketchObject* sketch, bool isDriven=true) { // Get the latest constraint const std::vector &ConStr = sketch->Constraints.getValues(); Sketcher::Constraint *constr = ConStr[ConStr.size() -1]; // Guess some reasonable distance for placing the datum text Gui::Document *doc = cmd->getActiveGuiDocument(); float sf = 1.f; if (doc && doc->getInEdit() && doc->getInEdit()->isDerivedFrom(SketcherGui::ViewProviderSketch::getClassTypeId())) { SketcherGui::ViewProviderSketch *vp = static_cast(doc->getInEdit()); sf = vp->getScaleFactor(); constr->LabelDistance = 2. * sf; vp->draw(false,false); // Redraw } ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool show = hGrp->GetBool("ShowDialogOnDistanceConstraint", true); // Ask for the value of the distance immediately if (show && isDriven) { openEditDatumDialog(sketch, ConStr.size() - 1); } else { // no dialog was shown so commit the command cmd->commitCommand(); } tryAutoRecompute(); cmd->getSelection().clearSelection(); } void showNoConstraintBetweenExternal() { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Cannot add a constraint between two external geometries!")); } void showNoConstraintBetweenFixedGeometry() { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Cannot add a constraint between two fixed geometries! Fixed geometries involve external geometry, blocked geometry or special points as B-Spline knot points.")); } bool SketcherGui::checkBothExternal(int GeoId1, int GeoId2) { if (GeoId1 == Constraint::GeoUndef || GeoId2 == Constraint::GeoUndef) return false; else return (GeoId1 < 0 && GeoId2 < 0); } bool SketcherGui::checkBothExternalOrConstructionPoints(const Sketcher::SketchObject* Obj,int GeoId1, int GeoId2) { if (GeoId1 == Constraint::GeoUndef || GeoId2 == Constraint::GeoUndef) return false; else return (GeoId1 < 0 && GeoId2 < 0) || (isConstructionPoint(Obj,GeoId1) && isConstructionPoint(Obj,GeoId2)) || (GeoId1 < 0 && isConstructionPoint(Obj,GeoId2)) || (GeoId2 < 0 && isConstructionPoint(Obj,GeoId1)); } bool SketcherGui::isPointOrSegmentFixed(const Sketcher::SketchObject* Obj, int GeoId) { const std::vector< Sketcher::Constraint * > &vals = Obj->Constraints.getValues(); if (GeoId == Constraint::GeoUndef) return false; else return checkConstraint(vals, Sketcher::Block, GeoId, Sketcher::none) || GeoId <= Sketcher::GeoEnum::RefExt || isConstructionPoint(Obj,GeoId); } bool SketcherGui::areBothPointsOrSegmentsFixed(const Sketcher::SketchObject* Obj, int GeoId1, int GeoId2) { const std::vector< Sketcher::Constraint * > &vals = Obj->Constraints.getValues(); if (GeoId1 == Constraint::GeoUndef || GeoId2 == Constraint::GeoUndef) return false; else return ((checkConstraint(vals, Sketcher::Block, GeoId1, Sketcher::none) || GeoId1 <= Sketcher::GeoEnum::RefExt || isConstructionPoint(Obj,GeoId1)) && (checkConstraint(vals, Sketcher::Block, GeoId2, Sketcher::none) || GeoId2 <= Sketcher::GeoEnum::RefExt || isConstructionPoint(Obj,GeoId2))); } bool SketcherGui::areAllPointsOrSegmentsFixed(const Sketcher::SketchObject* Obj, int GeoId1, int GeoId2, int GeoId3) { const std::vector< Sketcher::Constraint * > &vals = Obj->Constraints.getValues(); if (GeoId1 == Constraint::GeoUndef || GeoId2 == Constraint::GeoUndef || GeoId3 == Constraint::GeoUndef) return false; else return ((checkConstraint(vals, Sketcher::Block, GeoId1, Sketcher::none) || GeoId1 <= Sketcher::GeoEnum::RefExt || isConstructionPoint(Obj,GeoId1)) && (checkConstraint(vals, Sketcher::Block, GeoId2, Sketcher::none) || GeoId2 <= Sketcher::GeoEnum::RefExt || isConstructionPoint(Obj,GeoId2)) && (checkConstraint(vals, Sketcher::Block, GeoId3, Sketcher::none) || GeoId3 <= Sketcher::GeoEnum::RefExt || isConstructionPoint(Obj,GeoId3))); } void SketcherGui::getIdsFromName(const std::string &name, const Sketcher::SketchObject* Obj, int &GeoId, PointPos &PosId) { GeoId = Constraint::GeoUndef; PosId = Sketcher::none; if (name.size() > 4 && name.substr(0,4) == "Edge") { GeoId = std::atoi(name.substr(4,4000).c_str()) - 1; } else if (name.size() == 9 && name.substr(0,9) == "RootPoint") { GeoId = Sketcher::GeoEnum::RtPnt; PosId = Sketcher::start; } else if (name.size() == 6 && name.substr(0,6) == "H_Axis") GeoId = Sketcher::GeoEnum::HAxis; else if (name.size() == 6 && name.substr(0,6) == "V_Axis") GeoId = Sketcher::GeoEnum::VAxis; else if (name.size() > 12 && name.substr(0,12) == "ExternalEdge") GeoId = Sketcher::GeoEnum::RefExt + 1 - std::atoi(name.substr(12,4000).c_str()); else if (name.size() > 6 && name.substr(0,6) == "Vertex") { int VtId = std::atoi(name.substr(6,4000).c_str()) - 1; Obj->getGeoVertexIndex(VtId,GeoId,PosId); } } bool inline SketcherGui::isVertex(int GeoId, PointPos PosId) { return (GeoId != Constraint::GeoUndef && PosId != Sketcher::none); } bool inline SketcherGui::isEdge(int GeoId, PointPos PosId) { return (GeoId != Constraint::GeoUndef && PosId == Sketcher::none); } bool SketcherGui::isSimpleVertex(const Sketcher::SketchObject* Obj, int GeoId, PointPos PosId) { if (PosId == Sketcher::start && (GeoId == Sketcher::GeoEnum::HAxis || GeoId == Sketcher::GeoEnum::VAxis)) return true; const Part::Geometry *geo = Obj->getGeometry(GeoId); if (geo->getTypeId() == Part::GeomPoint::getClassTypeId()) return true; else if (PosId == Sketcher::mid) return true; else return false; } bool SketcherGui::isConstructionPoint(const Sketcher::SketchObject* Obj, int GeoId) { const Part::Geometry * geo = Obj->getGeometry(GeoId); return (geo->getTypeId() == Part::GeomPoint::getClassTypeId() && geo->Construction == true); } bool SketcherGui::IsPointAlreadyOnCurve(int GeoIdCurve, int GeoIdPoint, Sketcher::PointPos PosIdPoint, Sketcher::SketchObject* Obj) { //This func is a "smartness" behind three-element tangent-, perp.- and angle-via-point. //We want to find out, if the point supplied by user is already on // both of the curves. If not, necessary point-on-object constraints // are to be added automatically. //Simple geometric test seems to be the best, because a point can be // constrained to a curve in a number of ways (e.g. it is an endpoint of an // arc, or is coincident to endpoint of an arc, or it is an endpoint of an // ellipse's majopr diameter line). Testing all those possibilities is way // too much trouble, IMO(DeepSOIC). Base::Vector3d p = Obj->getPoint(GeoIdPoint, PosIdPoint); return Obj->isPointOnCurve(GeoIdCurve, p.x, p.y); } /// Makes a simple tangency constraint using extra point + tangent via point /// geom1 => an ellipse /// geom2 => any of an ellipse, an arc of ellipse, a circle, or an arc (of circle) /// NOTE: A command must be opened before calling this function, which this function /// commits or aborts as appropriate. The reason is for compatibility reasons with /// other code e.g. "Autoconstraints" in DrawSketchHandler.cpp void SketcherGui::makeTangentToEllipseviaNewPoint(const Sketcher::SketchObject* Obj, const Part::Geometry *geom1, const Part::Geometry *geom2, int geoId1, int geoId2 ) { const Part::GeomEllipse *ellipse = static_cast(geom1); Base::Vector3d center=ellipse->getCenter(); double majord=ellipse->getMajorRadius(); double minord=ellipse->getMinorRadius(); double phi=atan2(ellipse->getMajorAxisDir().y, ellipse->getMajorAxisDir().x); Base::Vector3d center2; if( geom2->getTypeId() == Part::GeomEllipse::getClassTypeId() ) center2= (static_cast(geom2))->getCenter(); else if( geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) center2= (static_cast(geom2))->getCenter(); else if( geom2->getTypeId() == Part::GeomCircle::getClassTypeId()) center2= (static_cast(geom2))->getCenter(); else if( geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) center2= (static_cast(geom2))->getCenter(); Base::Vector3d direction=center2-center; double tapprox=atan2(direction.y,direction.x)-phi; // we approximate the eccentric anomally by the polar Base::Vector3d PoE = Base::Vector3d(center.x+majord*cos(tapprox)*cos(phi)-minord*sin(tapprox)*sin(phi), center.y+majord*cos(tapprox)*sin(phi)+minord*sin(tapprox)*cos(phi), 0); try { // Add a point Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addGeometry(Part.Point(App.Vector(%f,%f,0)))", Obj->getNameInDocument(), PoE.x,PoE.y); int GeoIdPoint = Obj->getHighestCurveIndex(); // Point on first object Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoIdPoint,Sketcher::start,geoId1); // constrain major axis // Point on second object Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoIdPoint,Sketcher::start,geoId2); // constrain major axis // tangent via point Gui::Command::doCommand( Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d))", Obj->getNameInDocument(), geoId1, geoId2 ,GeoIdPoint, Sketcher::start); } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); Gui::Command::abortCommand(); tryAutoRecompute(); return; } Gui::Command::commitCommand(); tryAutoRecompute(); } /// Makes a simple tangency constraint using extra point + tangent via point /// geom1 => an arc of ellipse /// geom2 => any of an arc of ellipse, a circle, or an arc (of circle) /// NOTE: A command must be opened before calling this function, which this function /// commits or aborts as appropriate. The reason is for compatibility reasons with /// other code e.g. "Autoconstraints" in DrawSketchHandler.cpp void SketcherGui::makeTangentToArcOfEllipseviaNewPoint(const Sketcher::SketchObject* Obj, const Part::Geometry *geom1, const Part::Geometry *geom2, int geoId1, int geoId2 ) { const Part::GeomArcOfEllipse *aoe = static_cast(geom1); Base::Vector3d center=aoe->getCenter(); double majord=aoe->getMajorRadius(); double minord=aoe->getMinorRadius(); double phi=atan2(aoe->getMajorAxisDir().y, aoe->getMajorAxisDir().x); Base::Vector3d center2; if( geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) center2= (static_cast(geom2))->getCenter(); else if( geom2->getTypeId() == Part::GeomCircle::getClassTypeId()) center2= (static_cast(geom2))->getCenter(); else if( geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) center2= (static_cast(geom2))->getCenter(); Base::Vector3d direction=center2-center; double tapprox=atan2(direction.y,direction.x)-phi; // we approximate the eccentric anomally by the polar Base::Vector3d PoE = Base::Vector3d(center.x+majord*cos(tapprox)*cos(phi)-minord*sin(tapprox)*sin(phi), center.y+majord*cos(tapprox)*sin(phi)+minord*sin(tapprox)*cos(phi), 0); try { // Add a point Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addGeometry(Part.Point(App.Vector(%f,%f,0)))", Obj->getNameInDocument(), PoE.x,PoE.y); int GeoIdPoint = Obj->getHighestCurveIndex(); // Point on first object Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoIdPoint,Sketcher::start,geoId1); // constrain major axis // Point on second object Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoIdPoint,Sketcher::start,geoId2); // constrain major axis // tangent via point Gui::Command::doCommand( Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d))", Obj->getNameInDocument(), geoId1, geoId2 ,GeoIdPoint, Sketcher::start); } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); Gui::Command::abortCommand(); tryAutoRecompute(); return; } Gui::Command::commitCommand(); tryAutoRecompute(); } /// Makes a simple tangency constraint using extra point + tangent via point /// geom1 => an arc of hyperbola /// geom2 => any of an arc of hyperbola, an arc of ellipse, a circle, or an arc (of circle) /// NOTE: A command must be opened before calling this function, which this function /// commits or aborts as appropriate. The reason is for compatibility reasons with /// other code e.g. "Autoconstraints" in DrawSketchHandler.cpp void SketcherGui::makeTangentToArcOfHyperbolaviaNewPoint(const Sketcher::SketchObject* Obj, const Part::Geometry *geom1, const Part::Geometry *geom2, int geoId1, int geoId2 ) { const Part::GeomArcOfHyperbola *aoh = static_cast(geom1); Base::Vector3d center=aoh->getCenter(); double majord=aoh->getMajorRadius(); double minord=aoh->getMinorRadius(); Base::Vector3d dirmaj = aoh->getMajorAxisDir(); double phi=atan2(dirmaj.y, dirmaj.x); double df = sqrt(majord*majord+minord*minord); Base::Vector3d focus = center+df*dirmaj; // positive focus Base::Vector3d center2; if( geom2->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId()){ const Part::GeomArcOfHyperbola *aoh2 = static_cast(geom2); Base::Vector3d dirmaj2 = aoh2->getMajorAxisDir(); double majord2 = aoh2->getMajorRadius(); double minord2 = aoh2->getMinorRadius(); double df2 = sqrt(majord2*majord2+minord2*minord2); center2 = aoh2->getCenter()+df2*dirmaj2; // positive focus } else if( geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) center2= (static_cast(geom2))->getCenter(); else if( geom2->getTypeId() == Part::GeomEllipse::getClassTypeId()) center2= (static_cast(geom2))->getCenter(); else if( geom2->getTypeId() == Part::GeomCircle::getClassTypeId()) center2= (static_cast(geom2))->getCenter(); else if( geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) center2= (static_cast(geom2))->getCenter(); else if( geom2->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { const Part::GeomLineSegment *l2 = static_cast(geom2); center2= (l2->getStartPoint() + l2->getEndPoint())/2; } Base::Vector3d direction=center2-focus; double tapprox=atan2(direction.y,direction.x)-phi; Base::Vector3d PoH = Base::Vector3d(center.x+majord*cosh(tapprox)*cos(phi)-minord*sinh(tapprox)*sin(phi), center.y+majord*cosh(tapprox)*sin(phi)+minord*sinh(tapprox)*cos(phi), 0); try { // Add a point Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addGeometry(Part.Point(App.Vector(%f,%f,0)))", Obj->getNameInDocument(), PoH.x,PoH.y); int GeoIdPoint = Obj->getHighestCurveIndex(); // Point on first object Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoIdPoint,Sketcher::start,geoId1); // constrain major axis // Point on second object Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoIdPoint,Sketcher::start,geoId2); // constrain major axis // tangent via point Gui::Command::doCommand( Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d))", Obj->getNameInDocument(), geoId1, geoId2 ,GeoIdPoint, Sketcher::start); } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); Gui::Command::abortCommand(); tryAutoRecompute(); return; } Gui::Command::commitCommand(); tryAutoRecompute(); } /// Makes a simple tangency constraint using extra point + tangent via point /// geom1 => an arc of parabola /// geom2 => any of an arc of parabola, an arc of hyperbola an arc of ellipse, a circle, or an arc (of circle) /// NOTE: A command must be opened before calling this function, which this function /// commits or aborts as appropriate. The reason is for compatibility reasons with /// other code e.g. "Autoconstraints" in DrawSketchHandler.cpp void SketcherGui::makeTangentToArcOfParabolaviaNewPoint(const Sketcher::SketchObject* Obj, const Part::Geometry *geom1, const Part::Geometry *geom2, int geoId1, int geoId2 ) { const Part::GeomArcOfParabola *aop = static_cast(geom1); //Base::Vector3d center=aop->getCenter(); //Base::Vector3d dirx = aop->getXAxisDir(); //double phi=atan2(dirx.y, dirx.x); //double df = aop->getFocal(); Base::Vector3d focus = aop->getFocus(); Base::Vector3d center2; if( geom2->getTypeId() == Part::GeomArcOfParabola::getClassTypeId()) center2= (static_cast(geom2))->getFocus(); else if( geom2->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId()){ const Part::GeomArcOfHyperbola *aoh2 = static_cast(geom2); Base::Vector3d dirmaj2 = aoh2->getMajorAxisDir(); double majord2 = aoh2->getMajorRadius(); double minord2 = aoh2->getMinorRadius(); double df2 = sqrt(majord2*majord2+minord2*minord2); center2 = aoh2->getCenter()+df2*dirmaj2; // positive focus } else if( geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) center2= (static_cast(geom2))->getCenter(); else if( geom2->getTypeId() == Part::GeomEllipse::getClassTypeId()) center2= (static_cast(geom2))->getCenter(); else if( geom2->getTypeId() == Part::GeomCircle::getClassTypeId()) center2= (static_cast(geom2))->getCenter(); else if( geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) center2= (static_cast(geom2))->getCenter(); else if( geom2->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { const Part::GeomLineSegment *l2 = static_cast(geom2); center2= (l2->getStartPoint() + l2->getEndPoint())/2; } Base::Vector3d direction = center2-focus; /*double angle = atan2(direction.y,direction.x)-phi; double tapprox = 4*df*tan(angle);*/ Base::Vector3d PoP = focus + direction / 2; /*Base::Vector3d(center.x + tapprox * tapprox / 4 / df * cos(phi) - tapprox * sin(phi), center.y + tapprox * tapprox / 4 / df * sin(phi) + tapprox * cos(phi), 0);*/ try { // Add a point Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addGeometry(Part.Point(App.Vector(%f,%f,0)))", Obj->getNameInDocument(), PoP.x,PoP.y); int GeoIdPoint = Obj->getHighestCurveIndex(); // Point on first object Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoIdPoint,Sketcher::start,geoId1); // constrain major axis // Point on second object Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoIdPoint,Sketcher::start,geoId2); // constrain major axis // tangent via point Gui::Command::doCommand( Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d))", Obj->getNameInDocument(), geoId1, geoId2 ,GeoIdPoint, Sketcher::start); } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); Gui::Command::abortCommand(); tryAutoRecompute(); return; } Gui::Command::commitCommand(); tryAutoRecompute(); } std::string SketcherGui::getStrippedPythonExceptionString(const Base::Exception e) { std::string msg = e.what(); if( msg.length() > 26 && msg.substr(0,26) == "FreeCAD exception thrown (") { return msg.substr(26, msg.length()-27); } else return msg; } bool SketcherGui::tryAutoRecompute() { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool autoRecompute = hGrp->GetBool("AutoRecompute",false); if (autoRecompute) Gui::Command::updateActive(); return autoRecompute; } void SketcherGui::tryAutoRecomputeIfNotSolve(Sketcher::SketchObject* obj) { if(!tryAutoRecompute()) obj->solve(); } bool SketcherGui::checkConstraint(const std::vector< Sketcher::Constraint * > &vals, ConstraintType type, int geoid, PointPos pos) { for (std::vector< Sketcher::Constraint * >::const_iterator itc= vals.begin(); itc != vals.end(); ++itc) { if ((*itc)->Type == type && (*itc)->First == geoid && (*itc)->FirstPos == pos){ return true; } } return false; } namespace SketcherGui { struct SelIdPair{ int GeoId; Sketcher::PointPos PosId; }; struct SketchSelection{ enum GeoType { Point, Line, Circle, Arc }; int setUp(void); struct SketchSelectionItem { GeoType type; int GeoId; bool Extern; }; std::list Items; QString ErrorMsg; }; int SketchSelection::setUp(void) { std::vector selection = Gui::Selection().getSelectionEx(); Sketcher::SketchObject *SketchObj=0; std::vector SketchSubNames; std::vector SupportSubNames; // only one sketch with its subelements are allowed to be selected if (selection.size() == 1) { // if one selectetd, only sketch allowed. should be done by activity of command if(!selection[0].getObject()->getTypeId().isDerivedFrom(Sketcher::SketchObject::getClassTypeId())) { ErrorMsg = QObject::tr("Only sketch and its support is allowed to select"); return -1; } SketchObj = static_cast(selection[0].getObject()); SketchSubNames = selection[0].getSubNames(); } else if(selection.size() == 2) { if(selection[0].getObject()->getTypeId().isDerivedFrom(Sketcher::SketchObject::getClassTypeId())){ SketchObj = static_cast(selection[0].getObject()); // check if the none sketch object is the support of the sketch if(selection[1].getObject() != SketchObj->Support.getValue()){ ErrorMsg = QObject::tr("Only sketch and its support is allowed to select"); return-1; } // assume always a Part::Feature derived object as support assert(selection[1].getObject()->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())); SketchSubNames = selection[0].getSubNames(); SupportSubNames = selection[1].getSubNames(); } else if (selection[1].getObject()->getTypeId().isDerivedFrom(Sketcher::SketchObject::getClassTypeId())) { SketchObj = static_cast(selection[1].getObject()); // check if the none sketch object is the support of the sketch if(selection[0].getObject() != SketchObj->Support.getValue()){ ErrorMsg = QObject::tr("Only sketch and its support is allowed to select"); return -1; } // assume always a Part::Feature derived object as support assert(selection[0].getObject()->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())); SketchSubNames = selection[1].getSubNames(); SupportSubNames = selection[0].getSubNames(); } else { ErrorMsg = QObject::tr("One of the selected has to be on the sketch"); return -1; } } return Items.size(); } } // namespace SketcherGui /* Constrain commands =======================================================*/ namespace SketcherGui { /** * @brief The SelType enum * Types of sketch elements that can be (pre)selected. The root/origin and the * axes are put up separately so that they can be specifically disallowed, for * example, when in lock, horizontal, or vertical constraint modes. */ enum SelType { SelUnknown = 0, SelVertex = 1, SelVertexOrRoot = 64, SelRoot = 2, SelEdge = 4, SelEdgeOrAxis = 128, SelHAxis = 8, SelVAxis = 16, SelExternalEdge = 32 }; /** * @brief The GenericConstraintSelection class * SelectionFilterGate with changeable filters. In a constraint creation mode * like point on object, if the first selection object can be a point, the next * has to be a curve for the constraint to make sense. Thus filters are * changeable so that same filter can be kept on while in one mode. */ class GenericConstraintSelection : public Gui::SelectionFilterGate { App::DocumentObject* object; public: GenericConstraintSelection(App::DocumentObject* obj) : Gui::SelectionFilterGate((Gui::SelectionFilter*)0) , object(obj), allowedSelTypes(0) {} bool allow(App::Document *, App::DocumentObject *pObj, const char *sSubName) { if (pObj != this->object) return false; if (!sSubName || sSubName[0] == '\0') return false; std::string element(sSubName); if ( (allowedSelTypes & (SelRoot | SelVertexOrRoot) && element.substr(0,9) == "RootPoint") || (allowedSelTypes & (SelVertex | SelVertexOrRoot) && element.substr(0,6) == "Vertex") || (allowedSelTypes & (SelEdge | SelEdgeOrAxis) && element.substr(0,4) == "Edge") || (allowedSelTypes & (SelHAxis | SelEdgeOrAxis) && element.substr(0,6) == "H_Axis") || (allowedSelTypes & (SelVAxis | SelEdgeOrAxis) && element.substr(0,6) == "V_Axis") || (allowedSelTypes & SelExternalEdge && element.substr(0,12) == "ExternalEdge")) return true; return false; } void setAllowedSelTypes(unsigned int types) { if (types < 256) allowedSelTypes = types; } protected: int allowedSelTypes; }; } /** * @brief The CmdSketcherConstraint class * Superclass for all sketcher constraints to ease generation of constraint * creation modes. */ class CmdSketcherConstraint : public Gui::Command { friend class DrawSketchHandlerGenConstraint; public: CmdSketcherConstraint(const char* name) : Command(name) {} virtual ~CmdSketcherConstraint(){} virtual const char* className() const { return "CmdSketcherConstraint"; } protected: /** * @brief allowedSelSequences * Each element is a vector representing sequence of selections allowable. * DrawSketchHandlerGenConstraint will use these to filter elements and * generate sequences to be passed to applyConstraint(). * Whenever any sequence is completed, applyConstraint() is called, so it's * best to keep them prefix-free. * Be mindful that when SelVertex and SelRoot are given preference over * SelVertexOrRoot, and similar for edges/axes. Thus if a vertex is selected * when SelVertex and SelVertexOrRoot are both applicable, only sequences with * SelVertex will be continue. * * TODO: Introduce structs to allow keeping first selection */ std::vector > allowedSelSequences; const char** constraintCursor = 0; virtual void applyConstraint(std::vector &, int) {} virtual void activated(int /*iMsg*/); virtual bool isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } }; /* XPM */ static const char *cursor_genericconstraint[]={ "32 32 3 1", " c None", ". c #FFFFFF", "+ c #FF0000", " . ", " . ", " . ", " . ", " . ", " ", "..... ..... ", " ", " . ", " . ", " . ", " . ", " . ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ",}; class DrawSketchHandlerGenConstraint: public DrawSketchHandler { public: DrawSketchHandlerGenConstraint(const char* cursor[], CmdSketcherConstraint *_cmd) : constraintCursor(cursor), cmd(_cmd), seqIndex(0) {} virtual ~DrawSketchHandlerGenConstraint() { Gui::Selection().rmvSelectionGate(); } virtual void activated(ViewProviderSketch *) { selFilterGate = new GenericConstraintSelection(sketchgui->getObject()); resetOngoingSequences(); selSeq.clear(); Gui::Selection().rmvSelectionGate(); Gui::Selection().addSelectionGate(selFilterGate); // Constrait icon size in px int iconSize = 16; QPixmap cursorPixmap(cursor_genericconstraint), icon = Gui::BitmapFactory().pixmap(cmd->sPixmap).scaledToWidth(iconSize); QPainter cursorPainter; cursorPainter.begin(&cursorPixmap); cursorPainter.drawPixmap(16, 16, icon); cursorPainter.end(); setCursor(cursorPixmap, 7, 7); } virtual void mouseMove(Base::Vector2d /*onSketchPos*/) {} virtual bool pressButton(Base::Vector2d /*onSketchPos*/) { return true; } virtual bool releaseButton(Base::Vector2d onSketchPos) { SelIdPair selIdPair; selIdPair.GeoId = Constraint::GeoUndef; selIdPair.PosId = Sketcher::none; std::stringstream ss; SelType newSelType = SelUnknown; //For each SelType allowed, check if button is released there and assign it to selIdPair int VtId = sketchgui->getPreselectPoint(); int CrvId = sketchgui->getPreselectCurve(); int CrsId = sketchgui->getPreselectCross(); if (allowedSelTypes & (SelRoot | SelVertexOrRoot) && CrsId == 0) { selIdPair.GeoId = Sketcher::GeoEnum::RtPnt; selIdPair.PosId = Sketcher::start; newSelType = (allowedSelTypes & SelRoot) ? SelRoot : SelVertexOrRoot; ss << "RootPoint"; } else if (allowedSelTypes & (SelVertex | SelVertexOrRoot) && VtId >= 0) { sketchgui->getSketchObject()->getGeoVertexIndex(VtId, selIdPair.GeoId, selIdPair.PosId); newSelType = (allowedSelTypes & SelVertex) ? SelVertex : SelVertexOrRoot; ss << "Vertex" << VtId + 1; } else if (allowedSelTypes & (SelEdge | SelEdgeOrAxis) && CrvId >= 0) { selIdPair.GeoId = CrvId; newSelType = (allowedSelTypes & SelEdge) ? SelEdge : SelEdgeOrAxis; ss << "Edge" << CrvId + 1; } else if (allowedSelTypes & (SelHAxis | SelEdgeOrAxis) && CrsId == 1) { selIdPair.GeoId = Sketcher::GeoEnum::HAxis; newSelType = (allowedSelTypes & SelHAxis) ? SelHAxis : SelEdgeOrAxis; ss << "H_Axis"; } else if (allowedSelTypes & (SelVAxis | SelEdgeOrAxis) && CrsId == 2) { selIdPair.GeoId = Sketcher::GeoEnum::VAxis; newSelType = (allowedSelTypes & SelVAxis) ? SelVAxis : SelEdgeOrAxis; ss << "V_Axis"; } else if (allowedSelTypes & SelExternalEdge && CrvId <= Sketcher::GeoEnum::RefExt) { //TODO: Figure out how this works selIdPair.GeoId = CrvId; newSelType = SelExternalEdge; ss << "ExternalEdge" << Sketcher::GeoEnum::RefExt + 1 - CrvId; } if (selIdPair.GeoId == Constraint::GeoUndef) { // If mouse is released on "blank" space, start over selSeq.clear(); resetOngoingSequences(); Gui::Selection().clearSelection(); } else { // If mouse is released on something allowed, select it and move forward selSeq.push_back(selIdPair); Gui::Selection().addSelection(sketchgui->getSketchObject()->getDocument()->getName(), sketchgui->getSketchObject()->getNameInDocument(), ss.str().c_str(), onSketchPos.x, onSketchPos.y, 0.f); _tempOnSequences.clear(); allowedSelTypes = 0; for (std::set::iterator token = ongoingSequences.begin(); token != ongoingSequences.end(); ++token) { if ((cmd->allowedSelSequences).at(*token).at(seqIndex) == newSelType) { if (seqIndex == (cmd->allowedSelSequences).at(*token).size()-1) { // One of the sequences is completed. Pass to cmd->applyConstraint cmd->applyConstraint(selSeq, *token); // replace arg 2 by ongoingToken selSeq.clear(); resetOngoingSequences(); return true; } _tempOnSequences.insert(*token); allowedSelTypes = allowedSelTypes | (cmd->allowedSelSequences).at(*token).at(seqIndex+1); } } // Progress to next seqIndex std::swap(_tempOnSequences, ongoingSequences); seqIndex++; selFilterGate->setAllowedSelTypes(allowedSelTypes); } return true; } protected: const char** constraintCursor; CmdSketcherConstraint* cmd; GenericConstraintSelection* selFilterGate = nullptr; std::vector selSeq; unsigned int allowedSelTypes = 0; /// indices of currently ongoing sequences in cmd->allowedSequences std::set ongoingSequences, _tempOnSequences; /// Index within the selection sequences active unsigned int seqIndex; void resetOngoingSequences() { ongoingSequences.clear(); for (unsigned int i = 0; i < cmd->allowedSelSequences.size(); i++) { ongoingSequences.insert(i); } seqIndex = 0; // Estimate allowed selections from the first types in allowedSelTypes allowedSelTypes = 0; for (std::vector< std::vector< SelType > >::const_iterator it = cmd->allowedSelSequences.begin(); it != cmd->allowedSelSequences.end(); ++it) { allowedSelTypes = allowedSelTypes | (*it).at(seqIndex); } selFilterGate->setAllowedSelTypes(allowedSelTypes); Gui::Selection().clearSelection(); } }; void CmdSketcherConstraint::activated(int /*iMsg*/) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(constraintCursor, this)); getSelection().clearSelection(); } // ============================================================================ /* XPM */ static const char *cursor_createhoriconstraint[]={ "32 32 3 1", "+ c white", "# c red", ". c None", "......+.........................", "......+.........................", "......+.........................", "......+.........................", "......+.........................", "................................", "+++++...+++++...................", "................................", "......+.........................", "......+.........................", "......+.........................", "......+.........................", "......+......#..........#.......", ".............#..........#.......", ".............#..........#.......", ".............#..........#.......", ".............#..........#.......", ".............#..........#.......", ".............############.......", ".............#..........#.......", ".............#..........#.......", ".............#..........#.......", ".............#..........#.......", ".............#..........#.......", ".............#..........#.......", "................................", "................................", "................................", "................................", "................................", "................................", "................................"}; class CmdSketcherConstrainHorizontal : public CmdSketcherConstraint { public: CmdSketcherConstrainHorizontal(); virtual ~CmdSketcherConstrainHorizontal(){} virtual const char* className() const { return "CmdSketcherConstrainHorizontal"; } protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); }; CmdSketcherConstrainHorizontal::CmdSketcherConstrainHorizontal() :CmdSketcherConstraint("Sketcher_ConstrainHorizontal") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Constrain horizontally"); sToolTipText = QT_TR_NOOP("Create a horizontal constraint on the selected item"); sWhatsThis = "Sketcher_ConstrainHorizontal"; sStatusTip = sToolTipText; sPixmap = "Constraint_Horizontal"; sAccel = "H"; eType = ForEdit; allowedSelSequences = {{SelEdge}}; constraintCursor = cursor_createhoriconstraint; } void CmdSketcherConstrainHorizontal::activated(int iMsg) { Q_UNUSED(iMsg); // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true); if (constraintMode) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(constraintCursor, this)); getSelection().clearSelection(); } else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select an edge from the sketch.")); } return; } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); const std::vector< Sketcher::Constraint * > &vals = Obj->Constraints.getValues(); std::vector edgegeoids; std::vector pointgeoids; std::vector pointpos; int fixedpoints = 0; // go through the selected subelements for (std::vector::const_iterator it=SubNames.begin(); it != SubNames.end(); ++it) { int GeoId; Sketcher::PointPos PosId; getIdsFromName((*it), Obj, GeoId, PosId); if (isEdge(GeoId,PosId)) {// it is an edge const Part::Geometry *geo = Obj->getGeometry(GeoId); if (geo->getTypeId() != Part::GeomLineSegment::getClassTypeId()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Impossible constraint"), QObject::tr("The selected edge is not a line segment")); return; } // check if the edge has already a Horizontal/Vertical/Block constraint for (std::vector< Sketcher::Constraint * >::const_iterator it= vals.begin(); it != vals.end(); ++it) { if ((*it)->Type == Sketcher::Horizontal && (*it)->First == GeoId && (*it)->FirstPos == Sketcher::none){ QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Double constraint"), QObject::tr("The selected edge has already a horizontal constraint!")); return; } if ((*it)->Type == Sketcher::Vertical && (*it)->First == GeoId && (*it)->FirstPos == Sketcher::none) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Impossible constraint"), QObject::tr("The selected edge has already a vertical constraint!")); return; } // check if the edge has already a Block constraint if ((*it)->Type == Sketcher::Block && (*it)->First == GeoId && (*it)->FirstPos == Sketcher::none) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Impossible constraint"), QObject::tr("The selected edge has already a Block constraint!")); return; } } edgegeoids.push_back(GeoId); } else if(isVertex(GeoId,PosId)) { // can be a point, a construction point, an external point or root if(isPointOrSegmentFixed(Obj, GeoId)) fixedpoints++; pointgeoids.push_back(GeoId); pointpos.push_back(PosId); } } if (edgegeoids.empty() && pointgeoids.empty()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Impossible constraint"), QObject::tr("The selected item(s) can't accept a horizontal constraint!")); return; } // if there is at least one edge selected, ignore the point alignment functionality if (!edgegeoids.empty()) { // undo command open openCommand("add horizontal constraint"); for (std::vector::iterator it=edgegeoids.begin(); it != edgegeoids.end(); it++) { // issue the actual commands to create the constraint doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Horizontal',%d)) " ,selection[0].getFeatName(),*it); } } else if (fixedpoints <= 1) { // pointgeoids // undo command open openCommand("add horizontal alignment"); std::vector::iterator it; std::vector::iterator itp; for (it=pointgeoids.begin(), itp=pointpos.begin(); it != std::prev(pointgeoids.end()) && itp != std::prev(pointpos.end()); it++,itp++) { // issue the actual commands to create the constraint doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Horizontal',%d,%d,%d,%d)) " ,selection[0].getFeatName(),*it,*itp,*std::next(it),*std::next(itp)); } } else { // vertex mode, fixedpoints > 1 QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Impossible constraint"), QObject::tr("There are more than one fixed point selected. Select a maximum of one fixed point!")); return; } // finish the transaction and update commitCommand(); tryAutoRecompute(); // clear the selection (convenience) getSelection().clearSelection(); } void CmdSketcherConstrainHorizontal::applyConstraint(std::vector &selSeq, int seqIndex) { switch (seqIndex) { case 0: // {Edge} // create the constraint SketcherGui::ViewProviderSketch* sketchgui = static_cast(getActiveGuiDocument()->getInEdit()); Sketcher::SketchObject* Obj = sketchgui->getSketchObject(); const std::vector< Sketcher::Constraint * > &vals = Obj->Constraints.getValues(); int CrvId = selSeq.front().GeoId; if (CrvId != -1) { const Part::Geometry *geo = Obj->getGeometry(CrvId); if (geo->getTypeId() != Part::GeomLineSegment::getClassTypeId()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Impossible constraint"), QObject::tr("The selected edge is not a line segment")); return; } // check if the edge has already a Horizontal/Vertical/Block constraint for (std::vector< Sketcher::Constraint * >::const_iterator it= vals.begin(); it != vals.end(); ++it) { if ((*it)->Type == Sketcher::Horizontal && (*it)->First == CrvId){ QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Double constraint"), QObject::tr("The selected edge has already a horizontal constraint!")); return; } if ((*it)->Type == Sketcher::Vertical && (*it)->First == CrvId) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Impossible constraint"), QObject::tr("The selected edge has already a vertical constraint!")); return; } // check if the edge has already a Block constraint if ((*it)->Type == Sketcher::Block && (*it)->First == CrvId && (*it)->FirstPos == Sketcher::none) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Impossible constraint"), QObject::tr("The selected edge has already a Block constraint!")); return; } } // undo command open Gui::Command::openCommand("add horizontal constraint"); // issue the actual commands to create the constraint Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Horizontal',%d)) ", sketchgui->getObject()->getNameInDocument(),CrvId); // finish the transaction and update Gui::Command::commitCommand(); tryAutoRecompute(); } break; } } // ================================================================================ static const char *cursor_createvertconstraint[]={ "32 32 3 1", "+ c white", "# c red", ". c None", "......+.........................", "......+.........................", "......+.........................", "......+.........................", "......+.........................", "................................", "+++++...+++++...................", "................................", "......+.........................", "......+.........................", "......+.........................", "......+.........................", "......+......#..........#.......", ".............#..........#.......", ".............#..........#.......", ".............#..........#.......", ".............#..........#.......", ".............#..........#.......", ".............#..........#.......", ".............#..........#.......", "..............#........#........", "...............#......#.........", "................#....#..........", ".................#..#...........", "..................##............", "................................", "................................", "................................", "................................", "................................", "................................", "................................"}; class CmdSketcherConstrainVertical : public CmdSketcherConstraint { public: CmdSketcherConstrainVertical(); virtual ~CmdSketcherConstrainVertical(){} virtual const char* className() const { return "CmdSketcherConstrainVertical"; } protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); }; CmdSketcherConstrainVertical::CmdSketcherConstrainVertical() :CmdSketcherConstraint("Sketcher_ConstrainVertical") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Constrain vertically"); sToolTipText = QT_TR_NOOP("Create a vertical constraint on the selected item"); sWhatsThis = "Sketcher_ConstrainVertical"; sStatusTip = sToolTipText; sPixmap = "Constraint_Vertical"; sAccel = "V"; eType = ForEdit; allowedSelSequences = {{SelEdge}}; constraintCursor = cursor_createvertconstraint; } void CmdSketcherConstrainVertical::activated(int iMsg) { Q_UNUSED(iMsg); // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true); if (constraintMode) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(constraintCursor, this)); getSelection().clearSelection(); } else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select an edge from the sketch.")); } return; } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); const std::vector< Sketcher::Constraint * > &vals = Obj->Constraints.getValues(); std::vector edgegeoids; std::vector pointgeoids; std::vector pointpos; int fixedpoints = 0; // go through the selected subelements for (std::vector::const_iterator it=SubNames.begin(); it != SubNames.end(); ++it) { int GeoId; Sketcher::PointPos PosId; getIdsFromName((*it), Obj, GeoId, PosId); if (isEdge(GeoId,PosId)) {// it is an edge const Part::Geometry *geo = Obj->getGeometry(GeoId); if (geo->getTypeId() != Part::GeomLineSegment::getClassTypeId()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Impossible constraint"), QObject::tr("The selected edge is not a line segment")); return; } // check if the edge has already a Horizontal/Vertical/Block constraint for (std::vector< Sketcher::Constraint * >::const_iterator it= vals.begin(); it != vals.end(); ++it) { if ((*it)->Type == Sketcher::Vertical && (*it)->First == GeoId && (*it)->FirstPos == Sketcher::none){ QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Double constraint"), QObject::tr("The selected edge has already a vertical constraint!")); return; } if ((*it)->Type == Sketcher::Horizontal && (*it)->First == GeoId && (*it)->FirstPos == Sketcher::none) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Impossible constraint"), QObject::tr("The selected edge has already a horizontral constraint!")); return; } // check if the edge has already a Block constraint if ((*it)->Type == Sketcher::Block && (*it)->First == GeoId && (*it)->FirstPos == Sketcher::none) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Impossible constraint"), QObject::tr("The selected edge has already a Block constraint!")); return; } } edgegeoids.push_back(GeoId); } else if(isVertex(GeoId,PosId)) { // can be a point, a construction point, an external point, root or a blocked geometry if(isPointOrSegmentFixed(Obj, GeoId)) fixedpoints++; pointgeoids.push_back(GeoId); pointpos.push_back(PosId); } } if (edgegeoids.empty() && pointgeoids.empty()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Impossible constraint"), QObject::tr("The selected item(s) can't accept a vertical constraint!")); return; } // if there is at least one edge selected, ignore the point alignment functionality if (!edgegeoids.empty()) { // undo command open openCommand("add vertical constraint"); for (std::vector::iterator it=edgegeoids.begin(); it != edgegeoids.end(); it++) { // issue the actual commands to create the constraint doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Vertical',%d)) " ,selection[0].getFeatName(),*it); } } else if (fixedpoints <= 1) { // vertex mode, maximum one fixed point // undo command open openCommand("add vertical alignment"); std::vector::iterator it; std::vector::iterator itp; for (it=pointgeoids.begin(), itp=pointpos.begin(); it != std::prev(pointgeoids.end()) && itp != std::prev(pointpos.end()); it++,itp++) { // issue the actual commands to create the constraint doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Vertical',%d,%d,%d,%d)) " ,selection[0].getFeatName(),*it,*itp,*std::next(it),*std::next(itp)); } } else { // vertex mode, fixedpoints > 1 QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Impossible constraint"), QObject::tr("There are more than one fixed point selected. Select a maximum of one fixed point!")); return; } // finish the transaction and update commitCommand(); tryAutoRecompute(); // clear the selection (convenience) getSelection().clearSelection(); } void CmdSketcherConstrainVertical::applyConstraint(std::vector &selSeq, int seqIndex) { switch (seqIndex) { case 0: // {Edge} // create the constraint SketcherGui::ViewProviderSketch* sketchgui = static_cast(getActiveGuiDocument()->getInEdit()); Sketcher::SketchObject* Obj = sketchgui->getSketchObject(); const std::vector< Sketcher::Constraint * > &vals = Obj->Constraints.getValues(); int CrvId = selSeq.front().GeoId; if (CrvId != -1) { const Part::Geometry *geo = Obj->getGeometry(CrvId); if (geo->getTypeId() != Part::GeomLineSegment::getClassTypeId()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Impossible constraint"), QObject::tr("The selected edge is not a line segment")); return; } // check if the edge has already a Horizontal or Vertical constraint for (std::vector< Sketcher::Constraint * >::const_iterator it= vals.begin(); it != vals.end(); ++it) { if ((*it)->Type == Sketcher::Horizontal && (*it)->First == CrvId){ QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Impossible constraint"), QObject::tr("The selected edge has already a horizontal constraint!")); return; } if ((*it)->Type == Sketcher::Vertical && (*it)->First == CrvId) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Double constraint"), QObject::tr("The selected edge has already a vertical constraint!")); return; } // check if the edge has already a Block constraint if ((*it)->Type == Sketcher::Block && (*it)->First == CrvId && (*it)->FirstPos == Sketcher::none) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Impossible constraint"), QObject::tr("The selected edge has already a Block constraint!")); return; } } // undo command open Gui::Command::openCommand("add vertical constraint"); // issue the actual commands to create the constraint Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Vertical',%d)) ", sketchgui->getObject()->getNameInDocument(),CrvId); // finish the transaction and update Gui::Command::commitCommand(); tryAutoRecompute(); } break; } } // ====================================================================================== /* XPM */ static const char *cursor_createlock[]={ "32 32 3 1", "+ c white", "# c red", ". c None", "......+.........................", "......+.........................", "......+.........................", "......+.........................", "......+.........................", "................................", "+++++...+++++...................", "................................", "......+.........................", "......+.........................", "......+.........................", "......+.........................", "......+..........###............", "................######..........", "...............##....##.........", "..............##......##........", "..............##......##........", ".............############.......", ".............############.......", ".............############.......", ".............############.......", ".............############.......", ".............############.......", ".............############.......", ".............############.......", "................................", "................................", "................................", "................................", "................................", "................................", "................................"}; class CmdSketcherConstrainLock : public CmdSketcherConstraint { public: CmdSketcherConstrainLock(); virtual ~CmdSketcherConstrainLock(){} virtual void updateAction(int mode); virtual const char* className() const { return "CmdSketcherConstrainLock"; } protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); }; CmdSketcherConstrainLock::CmdSketcherConstrainLock() :CmdSketcherConstraint("Sketcher_ConstrainLock") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Constrain lock"); sToolTipText = QT_TR_NOOP("Create a lock constraint on the selected item"); sWhatsThis = "Sketcher_ConstrainLock"; sStatusTip = sToolTipText; sPixmap = "Sketcher_ConstrainLock"; eType = ForEdit; allowedSelSequences = {{SelVertex}}; constraintCursor = cursor_createlock; } void CmdSketcherConstrainLock::activated(int iMsg) { Q_UNUSED(iMsg); // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true); if (constraintMode) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(constraintCursor, this)); getSelection().clearSelection(); } else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select vertices from the sketch.")); } return; } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); std::vector GeoId; std::vector PosId; for (std::vector::const_iterator it = SubNames.begin(); it != SubNames.end(); ++it) { int GeoIdt; Sketcher::PointPos PosIdt; getIdsFromName((*it), Obj, GeoIdt, PosIdt); GeoId.push_back(GeoIdt); PosId.push_back(PosIdt); if ((it != std::prev(SubNames.end()) && (isEdge(GeoIdt,PosIdt) || (GeoIdt < 0 && GeoIdt >= Sketcher::GeoEnum::VAxis))) || (it == std::prev(SubNames.end()) && isEdge(GeoIdt,PosIdt)) ) { if(selection.size() == 1) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select one vertex from the sketch other than the origin.")); } else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select only vertices from the sketch. The last selected vertex may be the origin.")); } // clear the selection (convenience) getSelection().clearSelection(); return; } } int lastconstraintindex = Obj->Constraints.getSize()-1; if( GeoId.size() == 1 ) { // absolute mode // check if the edge has already a Block constraint bool edgeisblocked = false; if ( isPointOrSegmentFixed(Obj, GeoId[0])) { edgeisblocked = true; } Base::Vector3d pnt = Obj->getPoint(GeoId[0],PosId[0]); // undo command open openCommand("add fixed constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%f)) ", selection[0].getFeatName(),GeoId[0],PosId[0],pnt.x); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%f)) ", selection[0].getFeatName(),GeoId[0],PosId[0],pnt.y); lastconstraintindex+=2; if (edgeisblocked || GeoId[0] <= Sketcher::GeoEnum::RefExt || isConstructionPoint(Obj,GeoId[0]) || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", selection[0].getFeatName(),lastconstraintindex-2,"False"); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", selection[0].getFeatName(),lastconstraintindex,"False"); } } else { std::vector::const_iterator itg; std::vector::const_iterator itp; Base::Vector3d pntr = Obj->getPoint(GeoId.back(),PosId.back()); // check if the edge has already a Block constraint bool refpointfixed = false; if ( isPointOrSegmentFixed(Obj, GeoId.back())) refpointfixed = true; for (itg = GeoId.begin(), itp = PosId.begin(); itg != std::prev(GeoId.end()) && itp != std::prev(PosId.end()); ++itp, ++itg) { bool pointfixed = false; if ( isPointOrSegmentFixed(Obj, *itg)) pointfixed = true; Base::Vector3d pnt = Obj->getPoint(*itg,*itp); // undo command open openCommand("add relative lock constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%d,%d,%f)) ", selection[0].getFeatName(),*itg,*itp,GeoId.back(),PosId.back(),pntr.x-pnt.x); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%d,%d,%f)) ", selection[0].getFeatName(),*itg,*itp,GeoId.back(),PosId.back(),pntr.y-pnt.y); lastconstraintindex+=2; if ( (refpointfixed && pointfixed) || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", selection[0].getFeatName(),lastconstraintindex-1,"False"); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", selection[0].getFeatName(),lastconstraintindex,"False"); } } } // finish the transaction and update commitCommand(); tryAutoRecompute(); // clear the selection (convenience) getSelection().clearSelection(); } void CmdSketcherConstrainLock::applyConstraint(std::vector &selSeq, int seqIndex) { switch (seqIndex) { case 0: // {Vertex} // Create the constraints SketcherGui::ViewProviderSketch* sketchgui = static_cast(getActiveGuiDocument()->getInEdit()); Sketcher::SketchObject* Obj = sketchgui->getSketchObject(); // check if the edge has already a Block constraint bool pointfixed = false; if ( isPointOrSegmentFixed(Obj, selSeq.front().GeoId)) pointfixed = true; Base::Vector3d pnt = Obj->getPoint(selSeq.front().GeoId, selSeq.front().PosId); // undo command open Gui::Command::openCommand("add fixed constraint"); Gui::Command::doCommand( Gui::Command::Doc, "App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceX', %d, %d, %f)) ", sketchgui->getObject()->getNameInDocument(), selSeq.front().GeoId, selSeq.front().PosId, pnt.x); Gui::Command::doCommand( Gui::Command::Doc, "App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceY', %d, %d, %f)) ", sketchgui->getObject()->getNameInDocument(), selSeq.front().GeoId, selSeq.front().PosId, pnt.y); if (pointfixed || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.%s.setDriving(%i, %s)", sketchgui->getObject()->getNameInDocument(), ConStr.size()-2, "False"); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.%s.setDriving(%i, %s)", sketchgui->getObject()->getNameInDocument(), ConStr.size()-1, "False"); } // finish the transaction and update Gui::Command::commitCommand(); ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool autoRecompute = hGrp->GetBool("AutoRecompute", false); if(autoRecompute) Gui::Command::updateActive(); break; } } void CmdSketcherConstrainLock::updateAction(int mode) { switch (mode) { case Reference: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().pixmap("Sketcher_ConstrainLock_Driven")); break; case Driving: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().pixmap("Sketcher_ConstrainLock")); break; } } // ====================================================================================== /* XPM */ static const char *cursor_createblock[]={ "32 32 3 1", "+ c white", "# c red", ". c None", "......+.........................", "......+.........................", "......+.........................", "......+.........................", "......+.........................", "................................", "+++++...+++++...................", "................................", "......+.........................", "......+.........................", "......+.........................", "......+.........................", "......+..........###............", "....................##..........", ".....................##.........", "......................##........", "......................##........", ".............############.......", ".............###########........", ".............##########.........", ".............########...........", ".............#######............", ".............########...........", ".............##########.........", ".............###########........", ".............############.......", "......................##........", "......................##........", ".....................##.........", "....................##..........", ".................###............", "................................"}; class CmdSketcherConstrainBlock : public CmdSketcherConstraint { public: CmdSketcherConstrainBlock(); virtual ~CmdSketcherConstrainBlock(){} virtual const char* className() const { return "CmdSketcherConstrainBlock"; } protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); }; CmdSketcherConstrainBlock::CmdSketcherConstrainBlock() :CmdSketcherConstraint("Sketcher_ConstrainBlock") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Constrain Block"); sToolTipText = QT_TR_NOOP("Create a Block constraint on the selected item"); sWhatsThis = "Sketcher_ConstrainBlock"; sStatusTip = sToolTipText; sPixmap = "Sketcher_ConstrainBlock"; eType = ForEdit; allowedSelSequences = {{SelEdge}}; constraintCursor = cursor_createblock; } void CmdSketcherConstrainBlock::activated(int iMsg) { Q_UNUSED(iMsg); // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true); if (constraintMode) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(constraintCursor, this)); getSelection().clearSelection(); } else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select vertices from the sketch.")); } return; } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); // Check that the solver does not report redundant/conflicting constraints if(Obj->getLastSolverStatus()!=GCS::Success || Obj->getLastHasConflicts() || Obj->getLastHasRedundancies()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong solver status"), QObject::tr("A Block constraint can not be added if the sketch is unsolved or there are redundant and/or conflicting constraints.")); return; } std::vector GeoId; const std::vector< Sketcher::Constraint * > &vals = Obj->Constraints.getValues(); for (std::vector::const_iterator it = SubNames.begin(); it != SubNames.end(); ++it) { int GeoIdt; Sketcher::PointPos PosIdt; getIdsFromName((*it), Obj, GeoIdt, PosIdt); if ( isVertex(GeoIdt,PosIdt) || GeoIdt < 0 ) { if(selection.size() == 1) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select one edge from the sketch.")); } else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select only edges from the sketch.")); } // clear the selection getSelection().clearSelection(); return; } // check if the edge has already a Block constraint if ( checkConstraint(vals, Sketcher::Block, GeoIdt, Sketcher::none)) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Double constraint"), QObject::tr("The selected edge has already a Block constraint!")); return; } GeoId.push_back(GeoIdt); } for (std::vector::iterator itg = GeoId.begin(); itg != GeoId.end(); ++itg) { // undo command open openCommand("add block constraint"); try { Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Block',%d)) ", selection[0].getFeatName(),(*itg)); } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Error"), QString::fromLatin1(e.what())); Gui::Command::abortCommand(); tryAutoRecompute(); return; } commitCommand(); tryAutoRecompute(); } // clear the selection (convenience) getSelection().clearSelection(); } void CmdSketcherConstrainBlock::applyConstraint(std::vector &selSeq, int seqIndex) { switch (seqIndex) { case 0: // {Edge} { // Create the constraints SketcherGui::ViewProviderSketch* sketchgui = static_cast(getActiveGuiDocument()->getInEdit()); // check if the edge has already a Block constraint const std::vector< Sketcher::Constraint * > &vals = static_cast(sketchgui->getObject())->Constraints.getValues(); if ( checkConstraint(vals, Sketcher::Block, selSeq.front().GeoId, Sketcher::none)) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Double constraint"), QObject::tr("The selected edge has already a Block constraint!")); return; } // undo command open openCommand("add block constraint"); try { Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Block',%d)) ", sketchgui->getObject()->getNameInDocument(),selSeq.front().GeoId); } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Error"), QString::fromLatin1(e.what())); Gui::Command::abortCommand(); tryAutoRecompute(); return; } commitCommand(); tryAutoRecompute(); } break; default: break; } } // ====================================================================================== /* XPM */ static const char *cursor_createcoincident[]={ "32 32 3 1", "+ c white", "# c red", ". c None", "......+.........................", "......+.........................", "......+.........................", "......+.........................", "......+.........................", "................................", "+++++...+++++...................", "................................", "......+.........................", "......+.........................", "......+.........................", "......+.........................", "......+.........................", "................................", "................................", "................................", ".................####...........", "................######..........", "...............########.........", "...............########.........", "...............########.........", "...............########.........", "................######..........", ".................####...........", "................................", "................................", "................................", "................................", "................................", "................................", "................................", "................................"}; class DrawSketchHandlerCoincident: public DrawSketchHandler { public: DrawSketchHandlerCoincident() { GeoId1 = GeoId2 = Constraint::GeoUndef; PosId1 = PosId2 = Sketcher::none; } virtual ~DrawSketchHandlerCoincident() { Gui::Selection().rmvSelectionGate(); } virtual void activated(ViewProviderSketch *) { Gui::Selection().rmvSelectionGate(); GenericConstraintSelection* selFilterGate = new GenericConstraintSelection(sketchgui->getObject()); selFilterGate->setAllowedSelTypes(SelVertex|SelRoot); Gui::Selection().addSelectionGate(selFilterGate); setCursor(QPixmap(cursor_createcoincident), 7, 7); } virtual void mouseMove(Base::Vector2d onSketchPos) {Q_UNUSED(onSketchPos);} virtual bool pressButton(Base::Vector2d onSketchPos) { Q_UNUSED(onSketchPos); return true; } virtual bool releaseButton(Base::Vector2d onSketchPos) { int VtId = sketchgui->getPreselectPoint(); int CrsId = sketchgui->getPreselectCross(); std::stringstream ss; int GeoId_temp; Sketcher::PointPos PosId_temp; if (VtId != -1) { sketchgui->getSketchObject()->getGeoVertexIndex(VtId,GeoId_temp,PosId_temp); ss << "Vertex" << VtId + 1; } else if (CrsId == 0){ GeoId_temp = Sketcher::GeoEnum::RtPnt; PosId_temp = Sketcher::start; ss << "RootPoint"; } else { GeoId1 = GeoId2 = Constraint::GeoUndef; PosId1 = PosId2 = Sketcher::none; Gui::Selection().clearSelection(); return true; } if (GeoId1 == Constraint::GeoUndef) { GeoId1 = GeoId_temp; PosId1 = PosId_temp; Gui::Selection().addSelection(sketchgui->getSketchObject()->getDocument()->getName(), sketchgui->getSketchObject()->getNameInDocument(), ss.str().c_str(), onSketchPos.x, onSketchPos.y, 0.f); } else { GeoId2 = GeoId_temp; PosId2 = PosId_temp; // Apply the constraint Sketcher::SketchObject* Obj = static_cast(sketchgui->getObject()); // undo command open Gui::Command::openCommand("add coincident constraint"); // check if this coincidence is already enforced (even indirectly) bool constraintExists = Obj->arePointsCoincident(GeoId1,PosId1,GeoId2,PosId2); if (!constraintExists && (GeoId1 != GeoId2)) { Gui::Command::doCommand( Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Coincident',%d,%d,%d,%d)) ", sketchgui->getObject()->getNameInDocument(),GeoId1,PosId1,GeoId2,PosId2); Gui::Command::commitCommand(); } else { Gui::Command::abortCommand(); } } return true; } protected: int GeoId1, GeoId2; Sketcher::PointPos PosId1, PosId2; }; class CmdSketcherConstrainCoincident : public CmdSketcherConstraint { public: CmdSketcherConstrainCoincident(); virtual ~CmdSketcherConstrainCoincident(){} virtual const char* className() const { return "CmdSketcherConstrainCoincident"; } protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); }; CmdSketcherConstrainCoincident::CmdSketcherConstrainCoincident() :CmdSketcherConstraint("Sketcher_ConstrainCoincident") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Constrain coincident"); sToolTipText = QT_TR_NOOP("Create a coincident constraint on the selected item"); sWhatsThis = "Sketcher_ConstrainCoincident"; sStatusTip = sToolTipText; sPixmap = "Constraint_PointOnPoint"; sAccel = "C"; eType = ForEdit; allowedSelSequences = {{SelVertex, SelVertexOrRoot}, {SelRoot, SelVertex}}; constraintCursor = cursor_createcoincident; } void CmdSketcherConstrainCoincident::activated(int iMsg) { Q_UNUSED(iMsg); // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true); if (constraintMode) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(constraintCursor, this)); getSelection().clearSelection(); } else { // TODO: Get the exact message from git history and put it here QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select two or more points from the sketch.")); } return; } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); if (SubNames.size() < 2) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select two or more vertexes from the sketch.")); return; } for (std::vector::const_iterator it = SubNames.begin(); it != SubNames.end(); ++it) { int GeoId; Sketcher::PointPos PosId; getIdsFromName(*it, Obj, GeoId, PosId); if (isEdge(GeoId,PosId)) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select two or more vertexes from the sketch.")); return; } } int GeoId1, GeoId2; Sketcher::PointPos PosId1, PosId2; getIdsFromName(SubNames[0], Obj, GeoId1, PosId1); // undo command open bool constraintsAdded = false; openCommand("add coincident constraint"); for (std::size_t i=1; iarePointsCoincident(GeoId1,PosId1,GeoId2,PosId2); if (!constraintExists) { constraintsAdded = true; Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Coincident',%d,%d,%d,%d)) ", selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2); } } // finish or abort the transaction and update if (constraintsAdded) commitCommand(); else abortCommand(); tryAutoRecompute(); // clear the selection (convenience) getSelection().clearSelection(); } void CmdSketcherConstrainCoincident::applyConstraint(std::vector &selSeq, int seqIndex) { switch (seqIndex) { case 0: // {SelVertex, SelVertexOrRoot} case 1: // {SelRoot, SelVertex} // create the constraint SketcherGui::ViewProviderSketch* sketchgui = static_cast(getActiveGuiDocument()->getInEdit()); Sketcher::SketchObject* Obj = sketchgui->getSketchObject(); int GeoId1 = selSeq.at(0).GeoId, GeoId2 = selSeq.at(1).GeoId; Sketcher::PointPos PosId1 = selSeq.at(0).PosId, PosId2 = selSeq.at(1).PosId; // check if the edge has already a Block constraint if ( areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2) ) { showNoConstraintBetweenFixedGeometry(); return; } // undo command open Gui::Command::openCommand("add coincident constraint"); // check if this coincidence is already enforced (even indirectly) bool constraintExists = Obj->arePointsCoincident(GeoId1, PosId1, GeoId2, PosId2); if (!constraintExists && (GeoId1 != GeoId2)) { Gui::Command::doCommand( Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Coincident', %d, %d, %d, %d)) ", sketchgui->getObject()->getNameInDocument(), GeoId1, PosId1, GeoId2, PosId2); Gui::Command::commitCommand(); } else { Gui::Command::abortCommand(); } break; } } // ====================================================================================== class CmdSketcherConstrainDistance : public CmdSketcherConstraint { public: CmdSketcherConstrainDistance(); virtual ~CmdSketcherConstrainDistance(){} virtual void updateAction(int mode); virtual const char* className() const { return "CmdSketcherConstrainDistance"; } protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); }; CmdSketcherConstrainDistance::CmdSketcherConstrainDistance() :CmdSketcherConstraint("Sketcher_ConstrainDistance") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Constrain distance"); sToolTipText = QT_TR_NOOP("Fix a length of a line or the distance between a line and a vertex"); sWhatsThis = "Sketcher_ConstrainDistance"; sStatusTip = sToolTipText; sPixmap = "Constraint_Length"; sAccel = "SHIFT+D"; eType = ForEdit; allowedSelSequences = {{SelVertex, SelVertexOrRoot}, {SelRoot, SelVertex}, {SelEdge}, {SelExternalEdge}, {SelVertex, SelEdgeOrAxis}, {SelRoot, SelEdge}, {SelVertex, SelExternalEdge}, {SelRoot, SelExternalEdge}}; constraintCursor = cursor_genericconstraint; } void CmdSketcherConstrainDistance::activated(int iMsg) { Q_UNUSED(iMsg); // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true); if (constraintMode) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(constraintCursor, this)); getSelection().clearSelection(); } else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select vertexes from the sketch.")); } return; } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); if (SubNames.size() < 1 || SubNames.size() > 2) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select exactly one line or one point and one line or two points from the sketch.")); return; } int GeoId1, GeoId2=Constraint::GeoUndef; Sketcher::PointPos PosId1, PosId2=Sketcher::none; getIdsFromName(SubNames[0], Obj, GeoId1, PosId1); if (SubNames.size() == 2) getIdsFromName(SubNames[1], Obj, GeoId2, PosId2); bool arebothpointsorsegmentsfixed=areBothPointsOrSegmentsFixed(Obj,GeoId1, GeoId2); if (isVertex(GeoId1,PosId1) && (GeoId2 == Sketcher::GeoEnum::VAxis || GeoId2 == Sketcher::GeoEnum::HAxis)) { std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); } if ((isVertex(GeoId1,PosId1) || GeoId1 == Sketcher::GeoEnum::VAxis || GeoId1 == Sketcher::GeoEnum::HAxis) && isVertex(GeoId2,PosId2)) { // point to point distance Base::Vector3d pnt2 = Obj->getPoint(GeoId2,PosId2); if (GeoId1 == Sketcher::GeoEnum::HAxis && PosId1 == Sketcher::none) { PosId1 = Sketcher::start; openCommand("add distance from horizontal axis constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%d,%d,%f)) ", selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2,pnt2.y); } else if (GeoId1 == Sketcher::GeoEnum::VAxis && PosId1 == Sketcher::none) { PosId1 = Sketcher::start; openCommand("add distance from vertical axis constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%d,%d,%f)) ", selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2,pnt2.x); } else { Base::Vector3d pnt1 = Obj->getPoint(GeoId1,PosId1); openCommand("add point to point distance constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%d,%f)) ", selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2,(pnt2-pnt1).Length()); } if (arebothpointsorsegmentsfixed || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", selection[0].getFeatName(),ConStr.size()-1,"False"); finishDistanceConstraint(this, Obj,false); } else finishDistanceConstraint(this, Obj,true); return; } else if ((isVertex(GeoId1,PosId1) && isEdge(GeoId2,PosId2)) || (isEdge(GeoId1,PosId1) && isVertex(GeoId2,PosId2))) { // point to line distance if (isVertex(GeoId2,PosId2)) { std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); } Base::Vector3d pnt = Obj->getPoint(GeoId1,PosId1); const Part::Geometry *geom = Obj->getGeometry(GeoId2); if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { const Part::GeomLineSegment *lineSeg; lineSeg = static_cast(geom); Base::Vector3d pnt1 = lineSeg->getStartPoint(); Base::Vector3d pnt2 = lineSeg->getEndPoint(); Base::Vector3d d = pnt2-pnt1; double ActDist = std::abs(-pnt.x*d.y+pnt.y*d.x+pnt1.x*pnt2.y-pnt2.x*pnt1.y) / d.Length(); openCommand("add point to line Distance constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%f)) ", selection[0].getFeatName(),GeoId1,PosId1,GeoId2,ActDist); if (arebothpointsorsegmentsfixed || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", selection[0].getFeatName(),ConStr.size()-1,"False"); finishDistanceConstraint(this, Obj,false); } else finishDistanceConstraint(this, Obj,true); return; } } else if (isEdge(GeoId1,PosId1)) { // line length if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Cannot add a length constraint on an axis!")); return; } arebothpointsorsegmentsfixed=isPointOrSegmentFixed(Obj,GeoId1); const Part::Geometry *geom = Obj->getGeometry(GeoId1); if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { const Part::GeomLineSegment *lineSeg; lineSeg = static_cast(geom); double ActLength = (lineSeg->getEndPoint()-lineSeg->getStartPoint()).Length(); openCommand("add length constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Distance',%d,%f)) ", selection[0].getFeatName(),GeoId1,ActLength); if (arebothpointsorsegmentsfixed || GeoId1 <= Sketcher::GeoEnum::RefExt || isConstructionPoint(Obj,GeoId1) || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", selection[0].getFeatName(),ConStr.size()-1,"False"); finishDistanceConstraint(this, Obj,false); } else finishDistanceConstraint(this, Obj,true); return; } } QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select exactly one line or one point and one line or two points from the sketch.")); return; } void CmdSketcherConstrainDistance::applyConstraint(std::vector &selSeq, int seqIndex) { SketcherGui::ViewProviderSketch* sketchgui = static_cast(getActiveGuiDocument()->getInEdit()); Sketcher::SketchObject* Obj = sketchgui->getSketchObject(); int GeoId1 = Constraint::GeoUndef, GeoId2 = Constraint::GeoUndef; Sketcher::PointPos PosId1 = Sketcher::none, PosId2 = Sketcher::none; bool arebothpointsorsegmentsfixed=areBothPointsOrSegmentsFixed(Obj,GeoId1, GeoId2); switch (seqIndex) { case 0: // {SelVertex, SelVertexOrRoot} case 1: // {SelRoot, SelVertex} { GeoId1 = selSeq.at(0).GeoId; GeoId2 = selSeq.at(1).GeoId; PosId1 = selSeq.at(0).PosId; PosId2 = selSeq.at(1).PosId; Base::Vector3d pnt2 = Obj->getPoint(GeoId2,PosId2); if (GeoId1 == Sketcher::GeoEnum::HAxis && PosId1 == Sketcher::none) { PosId1 = Sketcher::start; openCommand("add distance from horizontal axis constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%d,%d,%f)) ", Obj->getNameInDocument(),GeoId1,PosId1,GeoId2,PosId2,pnt2.y); } else if (GeoId1 == Sketcher::GeoEnum::VAxis && PosId1 == Sketcher::none) { PosId1 = Sketcher::start; openCommand("add distance from vertical axis constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%d,%d,%f)) ", Obj->getNameInDocument(),GeoId1,PosId1,GeoId2,PosId2,pnt2.x); } else { Base::Vector3d pnt1 = Obj->getPoint(GeoId1,PosId1); openCommand("add point to point distance constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%d,%f)) ", Obj->getNameInDocument(),GeoId1,PosId1,GeoId2,PosId2,(pnt2-pnt1).Length()); } if (arebothpointsorsegmentsfixed || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", Obj->getNameInDocument(),ConStr.size()-1,"False"); finishDistanceConstraint(this, Obj,false); } else finishDistanceConstraint(this, Obj,true); return; } case 2: // {SelEdge} case 3: // {SelExternalEdge} { GeoId1 = GeoId2 = selSeq.at(0).GeoId; PosId1 = Sketcher::start; PosId2 = Sketcher::end; arebothpointsorsegmentsfixed=isPointOrSegmentFixed(Obj,GeoId1); const Part::Geometry *geom = Obj->getGeometry(GeoId1); if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { const Part::GeomLineSegment *lineSeg; lineSeg = static_cast(geom); double ActLength = (lineSeg->getEndPoint()-lineSeg->getStartPoint()).Length(); openCommand("add length constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Distance',%d,%f)) ", Obj->getNameInDocument(),GeoId1,ActLength); if (arebothpointsorsegmentsfixed || GeoId1 <= Sketcher::GeoEnum::RefExt || isConstructionPoint(Obj,GeoId1) || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", Obj->getNameInDocument(),ConStr.size()-1,"False"); finishDistanceConstraint(this, Obj,false); } else finishDistanceConstraint(this, Obj,true); } else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("This constraint does not make sense for non-linear curves")); } return; } case 4: // {SelVertex, SelEdgeOrAxis} case 5: // {SelRoot, SelEdge} case 6: // {SelVertex, SelExternalEdge} case 7: // {SelRoot, SelExternalEdge} { GeoId1 = selSeq.at(0).GeoId; GeoId2 = selSeq.at(1).GeoId; PosId1 = selSeq.at(0).PosId; PosId2 = selSeq.at(1).PosId; Base::Vector3d pnt = Obj->getPoint(GeoId1,PosId1); const Part::Geometry *geom = Obj->getGeometry(GeoId2); if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { const Part::GeomLineSegment *lineSeg; lineSeg = static_cast(geom); Base::Vector3d pnt1 = lineSeg->getStartPoint(); Base::Vector3d pnt2 = lineSeg->getEndPoint(); Base::Vector3d d = pnt2-pnt1; double ActDist = std::abs(-pnt.x*d.y+pnt.y*d.x+pnt1.x*pnt2.y-pnt2.x*pnt1.y) / d.Length(); openCommand("add point to line Distance constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%f)) ", Obj->getNameInDocument(),GeoId1,PosId1,GeoId2,ActDist); if (arebothpointsorsegmentsfixed || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", Obj->getNameInDocument(),ConStr.size()-1,"False"); finishDistanceConstraint(this, Obj,false); } else finishDistanceConstraint(this, Obj,true); } return; } default: break; } } void CmdSketcherConstrainDistance::updateAction(int mode) { switch (mode) { case Reference: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().pixmap("Constraint_Length_Driven")); break; case Driving: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().pixmap("Constraint_Length")); break; } } // ====================================================================================== /* XPM */ static const char * cursor_createpointonobj[] = { "32 32 3 1", " c None", ". c #FFFFFF", "+ c #FF0000", " . ", " . ++++", " . ++++++", " . +++++++ ", " . ++++++ ", " ++++++ ", "..... ..... +++++ ", " +++++ ", " . +++ ++++ ", " . +++++++++ ", " . ++++++++ ", " . +++++++++ ", " . +++++++++ ", " +++++++++ ", " +++++++ ", " ++++++++ ", " +++++++ ", " +++ ", " +++ ", " +++ ", " +++ ", " +++ ", " +++ ", " +++ ", " +++ ", " ++ ", " +++ ", " ++ ", " +++ ", " +++ ", " ++ ", " ++ "}; class CmdSketcherConstrainPointOnObject : public CmdSketcherConstraint { public: CmdSketcherConstrainPointOnObject(); virtual ~CmdSketcherConstrainPointOnObject(){} virtual const char* className() const { return "CmdSketcherConstrainPointOnObject"; } protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); }; //DEF_STD_CMD_A(CmdSketcherConstrainPointOnObject); CmdSketcherConstrainPointOnObject::CmdSketcherConstrainPointOnObject() :CmdSketcherConstraint("Sketcher_ConstrainPointOnObject") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Constrain point onto object"); sToolTipText = QT_TR_NOOP("Fix a point onto an object"); sWhatsThis = "Sketcher_ConstrainPointOnObject"; sStatusTip = sToolTipText; sPixmap = "Constraint_PointOnObject"; sAccel = "SHIFT+O"; eType = ForEdit; allowedSelSequences = {{SelVertex, SelEdgeOrAxis}, {SelRoot, SelEdge}, {SelVertex, SelExternalEdge}, {SelEdge, SelVertexOrRoot}, {SelEdgeOrAxis, SelVertex}, {SelExternalEdge, SelVertex}}; constraintCursor = cursor_createpointonobj; } void CmdSketcherConstrainPointOnObject::activated(int iMsg) { Q_UNUSED(iMsg); // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true); if (constraintMode) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(constraintCursor, this)); getSelection().clearSelection(); } else { // TODO: Get the exact message from git history and put it here QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select the right things from the sketch.")); } return; } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); //count curves and points std::vector points; std::vector curves; for (std::size_t i = 0 ; i < SubNames.size() ; i++){ SelIdPair id; getIdsFromName(SubNames[i], Obj, id.GeoId, id.PosId); if (isEdge(id.GeoId, id.PosId)) curves.push_back(id); if (isVertex(id.GeoId, id.PosId)) points.push_back(id); } if ((points.size() == 1 && curves.size() >= 1) || (points.size() >= 1 && curves.size() == 1)) { openCommand("add point on object constraint"); int cnt = 0; for (std::size_t iPnt = 0; iPnt < points.size(); iPnt++) { for (std::size_t iCrv = 0; iCrv < curves.size(); iCrv++) { if (areBothPointsOrSegmentsFixed(Obj, points[iPnt].GeoId, curves[iCrv].GeoId)){ showNoConstraintBetweenFixedGeometry(); continue; } 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 B-spline at any point implemented. QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Point on B-spline edge currently unsupported.")); continue; } cnt++; Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", selection[0].getFeatName(),points[iPnt].GeoId, points[iPnt].PosId, curves[iCrv].GeoId); } } if (cnt) { commitCommand(); getSelection().clearSelection(); } else { abortCommand(); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("None of the selected points were constrained onto the respective curves, either " "because they are parts of the same element, or because they are both external geometry.")); } return; } QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select either one point and several curves, or one curve and several points. " "You have selected %1 curves and %2 points.").arg(curves.size()).arg(points.size())); return; } void CmdSketcherConstrainPointOnObject::applyConstraint(std::vector &selSeq, int seqIndex) { int GeoIdVt, GeoIdCrv; Sketcher::PointPos PosIdVt; switch (seqIndex) { case 0: // {SelVertex, SelEdgeOrAxis} case 1: // {SelRoot, SelEdge} case 2: // {SelVertex, SelExternalEdge} GeoIdVt = selSeq.at(0).GeoId; GeoIdCrv = selSeq.at(1).GeoId; PosIdVt = selSeq.at(0).PosId; break; case 3: // {SelEdge, SelVertexOrRoot} case 4: // {SelEdgeOrAxis, SelVertex} case 5: // {SelExternalEdge, SelVertex} GeoIdVt = selSeq.at(1).GeoId; GeoIdCrv = selSeq.at(0).GeoId; PosIdVt = selSeq.at(1).PosId; break; default: return; } SketcherGui::ViewProviderSketch* sketchgui = static_cast(getActiveGuiDocument()->getInEdit()); Sketcher::SketchObject* Obj = sketchgui->getSketchObject(); openCommand("add point on object constraint"); bool allOK = true; if (areBothPointsOrSegmentsFixed(Obj, GeoIdVt, GeoIdCrv)){ showNoConstraintBetweenFixedGeometry(); allOK = false; } if (GeoIdVt == GeoIdCrv) allOK = false; //constraining a point of an element onto the element is a bad idea... const Part::Geometry *geom = Obj->getGeometry(GeoIdCrv); if( geom && geom->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() ){ // unsupported until normal to B-spline at any point implemented. QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Point on B-spline edge currently unsupported.")); abortCommand(); return; } if (allOK) { Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", sketchgui->getObject()->getNameInDocument(), GeoIdVt, PosIdVt, GeoIdCrv); commitCommand(); tryAutoRecompute(); } else { abortCommand(); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("None of the selected points were constrained onto the respective curves, either " "because they are parts of the same element, or because they are both external geometry.")); } return; } // ====================================================================================== class CmdSketcherConstrainDistanceX : public CmdSketcherConstraint { public: CmdSketcherConstrainDistanceX(); virtual ~CmdSketcherConstrainDistanceX(){} virtual void updateAction(int mode); virtual const char* className() const { return "CmdSketcherConstrainDistanceX"; } protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); }; CmdSketcherConstrainDistanceX::CmdSketcherConstrainDistanceX() :CmdSketcherConstraint("Sketcher_ConstrainDistanceX") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Constrain horizontal distance"); sToolTipText = QT_TR_NOOP("Fix the horizontal distance between two points or line ends"); sWhatsThis = "Sketcher_ConstrainDistanceX"; sStatusTip = sToolTipText; sPixmap = "Constraint_HorizontalDistance"; sAccel = "SHIFT+H"; eType = ForEdit; allowedSelSequences = {{SelVertex, SelVertexOrRoot}, {SelRoot, SelVertex}, {SelEdge}, {SelExternalEdge}}; // Can't do single vertex because its a prefix for 2 vertices constraintCursor = cursor_genericconstraint; } void CmdSketcherConstrainDistanceX::activated(int iMsg) { Q_UNUSED(iMsg); // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true); if (constraintMode) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(constraintCursor, this)); getSelection().clearSelection(); } else { // TODO: Get the exact message from git history and put it here QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select the right things from the sketch.")); } return; } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); if (SubNames.size() < 1 || SubNames.size() > 2) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select exactly one line or up to two points from the sketch.")); return; } int GeoId1, GeoId2=Constraint::GeoUndef; Sketcher::PointPos PosId1, PosId2=Sketcher::none; getIdsFromName(SubNames[0], Obj, GeoId1, PosId1); if (SubNames.size() == 2) getIdsFromName(SubNames[1], Obj, GeoId2, PosId2); bool arebothpointsorsegmentsfixed=areBothPointsOrSegmentsFixed(Obj,GeoId1, GeoId2); if (GeoId2 == Sketcher::GeoEnum::HAxis || GeoId2 == Sketcher::GeoEnum::VAxis) { std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); } if (GeoId1 == Sketcher::GeoEnum::HAxis && PosId1 == Sketcher::none) // reject horizontal axis from selection GeoId1 = Constraint::GeoUndef; else if (GeoId1 == Sketcher::GeoEnum::VAxis && PosId1 == Sketcher::none) { GeoId1 = Sketcher::GeoEnum::HAxis; PosId1 = Sketcher::start; } if (isEdge(GeoId1,PosId1) && GeoId2 == Constraint::GeoUndef) { // horizontal length of a line if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Cannot add a horizontal length constraint on an axis!")); return; } arebothpointsorsegmentsfixed = isPointOrSegmentFixed(Obj,GeoId1); const Part::Geometry *geom = Obj->getGeometry(GeoId1); if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { //convert to as if two endpoints of the line have been selected PosId1 = Sketcher::start; GeoId2 = GeoId1; PosId2 = Sketcher::end; } } if (isVertex(GeoId1,PosId1) && isVertex(GeoId2,PosId2)) { // point to point horizontal distance Base::Vector3d pnt1 = Obj->getPoint(GeoId1,PosId1); Base::Vector3d pnt2 = Obj->getPoint(GeoId2,PosId2); double ActLength = pnt2.x-pnt1.x; //negative sign avoidance: swap the points to make value positive if (ActLength < -Precision::Confusion()) { std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); std::swap(pnt1, pnt2); ActLength = -ActLength; } openCommand("add point to point horizontal distance constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%d,%d,%f)) ", selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2,ActLength); if (arebothpointsorsegmentsfixed || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", selection[0].getFeatName(),ConStr.size()-1,"False"); finishDistanceConstraint(this, Obj,false); } else finishDistanceConstraint(this, Obj,true); return; } else if (isVertex(GeoId1,PosId1) && GeoId2 == Constraint::GeoUndef) { // point on fixed x-coordinate if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Cannot add a fixed x-coordinate constraint on the root point!")); return; } Base::Vector3d pnt = Obj->getPoint(GeoId1,PosId1); double ActX = pnt.x; arebothpointsorsegmentsfixed=isPointOrSegmentFixed(Obj,GeoId1); openCommand("add fixed x-coordinate constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%f)) ", selection[0].getFeatName(),GeoId1,PosId1,ActX); if (arebothpointsorsegmentsfixed || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", selection[0].getFeatName(),ConStr.size()-1,"False"); finishDistanceConstraint(this, Obj,false); } else finishDistanceConstraint(this, Obj,true); return; } QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select exactly one line or up to two points from the sketch.")); return; } void CmdSketcherConstrainDistanceX::applyConstraint(std::vector &selSeq, int seqIndex) { SketcherGui::ViewProviderSketch* sketchgui = static_cast(getActiveGuiDocument()->getInEdit()); Sketcher::SketchObject* Obj = sketchgui->getSketchObject(); int GeoId1 = Constraint::GeoUndef, GeoId2 = Constraint::GeoUndef; Sketcher::PointPos PosId1 = Sketcher::none, PosId2 = Sketcher::none; switch (seqIndex) { case 0: // {SelVertex, SelVertexOrRoot} case 1: // {SelRoot, SelVertex} { GeoId1 = selSeq.at(0).GeoId; GeoId2 = selSeq.at(1).GeoId; PosId1 = selSeq.at(0).PosId; PosId2 = selSeq.at(1).PosId; break; } case 2: // {SelEdge} case 4: // {SelExternalEdge} { GeoId1 = GeoId2 = selSeq.at(0).GeoId; PosId1 = Sketcher::start; PosId2 = Sketcher::end; const Part::Geometry *geom = Obj->getGeometry(GeoId1); if (geom->getTypeId() != Part::GeomLineSegment::getClassTypeId()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("This constraint only makes sense on a line segment or a pair of points")); return; } break; } default: break; } Base::Vector3d pnt1 = Obj->getPoint(GeoId1,PosId1); Base::Vector3d pnt2 = Obj->getPoint(GeoId2,PosId2); double ActLength = pnt2.x-pnt1.x; //negative sign avoidance: swap the points to make value positive if (ActLength < -Precision::Confusion()) { std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); std::swap(pnt1, pnt2); ActLength = -ActLength; } openCommand("add point to point horizontal distance constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%d,%d,%f)) ", Obj->getNameInDocument(),GeoId1,PosId1,GeoId2,PosId2,ActLength); if (areBothPointsOrSegmentsFixed(Obj,GeoId1, GeoId2) || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", Obj->getNameInDocument(),ConStr.size()-1,"False"); finishDistanceConstraint(this, Obj, false); } else finishDistanceConstraint(this, Obj, true); } void CmdSketcherConstrainDistanceX::updateAction(int mode) { switch (mode) { case Reference: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().pixmap("Constraint_HorizontalDistance_Driven")); break; case Driving: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().pixmap("Constraint_HorizontalDistance")); break; } } // ====================================================================================== class CmdSketcherConstrainDistanceY : public CmdSketcherConstraint { public: CmdSketcherConstrainDistanceY(); virtual ~CmdSketcherConstrainDistanceY(){} virtual void updateAction(int mode); virtual const char* className() const { return "CmdSketcherConstrainDistanceY"; } protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); }; CmdSketcherConstrainDistanceY::CmdSketcherConstrainDistanceY() :CmdSketcherConstraint("Sketcher_ConstrainDistanceY") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Constrain vertical distance"); sToolTipText = QT_TR_NOOP("Fix the vertical distance between two points or line ends"); sWhatsThis = "Sketcher_ConstrainDistanceY"; sStatusTip = sToolTipText; sPixmap = "Constraint_VerticalDistance"; sAccel = "SHIFT+V"; eType = ForEdit; allowedSelSequences = {{SelVertex, SelVertexOrRoot}, {SelRoot, SelVertex}, {SelEdge}, {SelExternalEdge}}; // Can't do single vertex because its a prefix for 2 vertices constraintCursor = cursor_genericconstraint; } void CmdSketcherConstrainDistanceY::activated(int iMsg) { Q_UNUSED(iMsg); // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true); if (constraintMode) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(constraintCursor, this)); getSelection().clearSelection(); } else { // TODO: Get the exact message from git history and put it here QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select the right things from the sketch.")); } return; } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); if (SubNames.size() < 1 || SubNames.size() > 2) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select exactly one line or up to two points from the sketch.")); return; } int GeoId1, GeoId2=Constraint::GeoUndef; Sketcher::PointPos PosId1, PosId2=Sketcher::none; getIdsFromName(SubNames[0], Obj, GeoId1, PosId1); if (SubNames.size() == 2) getIdsFromName(SubNames[1], Obj, GeoId2, PosId2); bool arebothpointsorsegmentsfixed=areBothPointsOrSegmentsFixed(Obj,GeoId1, GeoId2); if (GeoId2 == Sketcher::GeoEnum::HAxis || GeoId2 == Sketcher::GeoEnum::VAxis) { std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); } if (GeoId1 == Sketcher::GeoEnum::VAxis && PosId1 == Sketcher::none) // reject vertical axis from selection GeoId1 = Constraint::GeoUndef; else if (GeoId1 == Sketcher::GeoEnum::HAxis && PosId1 == Sketcher::none) PosId1 = Sketcher::start; if (isEdge(GeoId1,PosId1) && GeoId2 == Constraint::GeoUndef) { // vertical length of a line if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Cannot add a vertical length constraint on an axis!")); return; } arebothpointsorsegmentsfixed = isPointOrSegmentFixed(Obj,GeoId1); const Part::Geometry *geom = Obj->getGeometry(GeoId1); if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { //convert to as if two endpoints of the line have been selected PosId1 = Sketcher::start; GeoId2 = GeoId1; PosId2 = Sketcher::end; } } if (isVertex(GeoId1,PosId1) && isVertex(GeoId2,PosId2)) { // point to point vertical distance Base::Vector3d pnt1 = Obj->getPoint(GeoId1,PosId1); Base::Vector3d pnt2 = Obj->getPoint(GeoId2,PosId2); double ActLength = pnt2.y-pnt1.y; //negative sign avoidance: swap the points to make value positive if (ActLength < -Precision::Confusion()) { std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); std::swap(pnt1, pnt2); ActLength = -ActLength; } openCommand("add point to point vertical distance constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%d,%d,%f)) ", selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2,ActLength); if (arebothpointsorsegmentsfixed || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", selection[0].getFeatName(),ConStr.size()-1,"False"); finishDistanceConstraint(this, Obj,false); } else finishDistanceConstraint(this, Obj,true); return; } else if (isVertex(GeoId1,PosId1) && GeoId2 == Constraint::GeoUndef) { // point on fixed y-coordinate if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Cannot add a fixed y-coordinate constraint on the root point!")); return; } Base::Vector3d pnt = Obj->getPoint(GeoId1,PosId1); double ActY = pnt.y; arebothpointsorsegmentsfixed=isPointOrSegmentFixed(Obj,GeoId1); openCommand("add fixed y-coordinate constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%f)) ", selection[0].getFeatName(),GeoId1,PosId1,ActY); if (GeoId1 <= Sketcher::GeoEnum::RefExt || isConstructionPoint(Obj,GeoId1) || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", selection[0].getFeatName(),ConStr.size()-1,"False"); finishDistanceConstraint(this, Obj,false); } else finishDistanceConstraint(this, Obj,true); return; } QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select exactly one line or up to two points from the sketch.")); return; } void CmdSketcherConstrainDistanceY::applyConstraint(std::vector &selSeq, int seqIndex) { SketcherGui::ViewProviderSketch* sketchgui = static_cast(getActiveGuiDocument()->getInEdit()); Sketcher::SketchObject* Obj = sketchgui->getSketchObject(); int GeoId1 = Constraint::GeoUndef, GeoId2 = Constraint::GeoUndef; Sketcher::PointPos PosId1 = Sketcher::none, PosId2 = Sketcher::none; switch (seqIndex) { case 0: // {SelVertex, SelVertexOrRoot} case 1: // {SelRoot, SelVertex} { GeoId1 = selSeq.at(0).GeoId; GeoId2 = selSeq.at(1).GeoId; PosId1 = selSeq.at(0).PosId; PosId2 = selSeq.at(1).PosId; break; } case 2: // {SelEdge} case 3: // {SelExternalEdge} { GeoId1 = GeoId2 = selSeq.at(0).GeoId; PosId1 = Sketcher::start; PosId2 = Sketcher::end; const Part::Geometry *geom = Obj->getGeometry(GeoId1); if (geom->getTypeId() != Part::GeomLineSegment::getClassTypeId()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("This constraint only makes sense on a line segment or a pair of points")); return; } break; } default: break; } Base::Vector3d pnt1 = Obj->getPoint(GeoId1,PosId1); Base::Vector3d pnt2 = Obj->getPoint(GeoId2,PosId2); double ActLength = pnt2.y-pnt1.y; //negative sign avoidance: swap the points to make value positive if (ActLength < -Precision::Confusion()) { std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); std::swap(pnt1, pnt2); ActLength = -ActLength; } openCommand("add point to point vertical distance constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%d,%d,%f)) ", Obj->getNameInDocument(),GeoId1,PosId1,GeoId2,PosId2,ActLength); if (areBothPointsOrSegmentsFixed(Obj,GeoId1, GeoId2) || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", Obj->getNameInDocument(),ConStr.size()-1,"False"); finishDistanceConstraint(this, Obj,false); } else finishDistanceConstraint(this, Obj,true); } void CmdSketcherConstrainDistanceY::updateAction(int mode) { switch (mode) { case Reference: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().pixmap("Constraint_VerticalDistance_Driven")); break; case Driving: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().pixmap("Constraint_VerticalDistance")); break; } } //================================================================================= /* XPM */ static const char *cursor_createparallel[]={ "32 32 3 1", " c None", ". c #FFFFFF", "+ c #FF0000", " . ", " . ", " . ", " . ", " . ", " ", "..... ..... ", " ", " . ", " . ", " . + + ", " . ++ ++ ", " . + + ", " ++ ++ ", " + + ", " ++ ++ ", " + + ", " ++ ++ ", " + + ", " ++ ++ ", " + + ", " ++ ++ ", " + + ", " ++ ++ ", " + + ", " ++ ++ ", " + + ", " ++ ++ ", " + + ", " ", " ", " "}; class CmdSketcherConstrainParallel : public CmdSketcherConstraint { public: CmdSketcherConstrainParallel(); virtual ~CmdSketcherConstrainParallel(){} virtual const char* className() const { return "CmdSketcherConstrainParallel"; } protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); }; CmdSketcherConstrainParallel::CmdSketcherConstrainParallel() :CmdSketcherConstraint("Sketcher_ConstrainParallel") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Constrain parallel"); sToolTipText = QT_TR_NOOP("Create a parallel constraint between two lines"); sWhatsThis = "Sketcher_ConstrainParallel"; sStatusTip = sToolTipText; sPixmap = "Constraint_Parallel"; sAccel = "SHIFT+P"; eType = ForEdit; // TODO: Also needed: ExternalEdges allowedSelSequences = {{SelEdge, SelEdgeOrAxis}, {SelEdgeOrAxis, SelEdge}, {SelEdge, SelExternalEdge}, {SelExternalEdge, SelEdge}}; constraintCursor = cursor_createparallel; } void CmdSketcherConstrainParallel::activated(int iMsg) { Q_UNUSED(iMsg); // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true); if (constraintMode) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(constraintCursor, this)); getSelection().clearSelection(); } else { // TODO: Get the exact message from git history and put it here QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select two or more lines from the sketch.")); } return; } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); // go through the selected subelements if (SubNames.size() < 2) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select at least two lines from the sketch.")); return; } std::vector ids; bool hasAlreadyExternal=false; for (std::vector::const_iterator it=SubNames.begin();it!=SubNames.end();++it) { int GeoId; Sketcher::PointPos PosId; getIdsFromName(*it, Obj, GeoId, PosId); if (!isEdge(GeoId,PosId)) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select a valid line")); return; } else if (isPointOrSegmentFixed(Obj,GeoId)) { if (hasAlreadyExternal) { showNoConstraintBetweenFixedGeometry(); return; } else hasAlreadyExternal = true; } // Check that the curve is a line segment const Part::Geometry *geo = Obj->getGeometry(GeoId); if (geo->getTypeId() != Part::GeomLineSegment::getClassTypeId()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("The selected edge is not a valid line")); return; } ids.push_back(GeoId); } // undo command open openCommand("add parallel constraint"); for (int i=0; i < int(ids.size()-1); i++) { Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Parallel',%d,%d)) ", selection[0].getFeatName(),ids[i],ids[i+1]); } // finish the transaction and update commitCommand(); tryAutoRecompute(); // clear the selection (convenience) getSelection().clearSelection(); } void CmdSketcherConstrainParallel::applyConstraint(std::vector &selSeq, int seqIndex) { switch (seqIndex) { case 0: // {SelEdge, SelEdgeOrAxis} case 1: // {SelEdgeOrAxis, SelEdge} case 2: // {SelEdge, SelExternalEdge} case 3: // {SelExternalEdge, SelEdge} // create the constraint SketcherGui::ViewProviderSketch* sketchgui = static_cast(getActiveGuiDocument()->getInEdit()); Sketcher::SketchObject* Obj = sketchgui->getSketchObject(); int GeoId1 = selSeq.at(0).GeoId, GeoId2 = selSeq.at(1).GeoId; // Check that the curves are line segments if ( Obj->getGeometry(GeoId1)->getTypeId() != Part::GeomLineSegment::getClassTypeId() || Obj->getGeometry(GeoId2)->getTypeId() != Part::GeomLineSegment::getClassTypeId()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("The selected edge is not a valid line")); return; } if( areBothPointsOrSegmentsFixed(Obj,GeoId1, GeoId2)) { showNoConstraintBetweenFixedGeometry(); return; } // undo command open openCommand("add parallel constraint"); Gui::Command::doCommand( Doc, "App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Parallel',%d,%d)) ", sketchgui->getObject()->getNameInDocument(), GeoId1, GeoId2); // finish the transaction and update commitCommand(); tryAutoRecompute(); } } // ====================================================================================== /* XPM */ static const char *cursor_createperpconstraint[] = { "32 32 3 1", " c None", ". c #FFFFFF", "+ c #FF0000", " . ", " . ", " . ", " . ", " . ", " ", "..... ..... ", " ", " . ", " . ", " . ", " . ++ ", " . ++ ", " ++ ", " ++ ", " ++ ", " ++ ", " ++ ", " ++ ", " ++ ", " ++ ", " ++ ", " ++ ", " ++ ", " ++ ", " ++ ", " ++ ", " ++ ", " ++++++++++++++++++++ ", " ++++++++++++++++++++ ", " ", " "}; class CmdSketcherConstrainPerpendicular : public CmdSketcherConstraint { public: CmdSketcherConstrainPerpendicular(); virtual ~CmdSketcherConstrainPerpendicular(){} virtual const char* className() const { return "CmdSketcherConstrainPerpendicular"; } protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); }; CmdSketcherConstrainPerpendicular::CmdSketcherConstrainPerpendicular() :CmdSketcherConstraint("Sketcher_ConstrainPerpendicular") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Constrain perpendicular"); sToolTipText = QT_TR_NOOP("Create a perpendicular constraint between two lines"); sWhatsThis = "Sketcher_ConstrainPerpendicular"; sStatusTip = sToolTipText; sPixmap = "Constraint_Perpendicular"; sAccel = "N"; eType = ForEdit; // TODO: there are two more combos: endpoint then curve and endpoint then endpoint allowedSelSequences = {{SelEdge, SelEdgeOrAxis}, {SelEdgeOrAxis, SelEdge}, {SelEdge, SelExternalEdge}, {SelExternalEdge, SelEdge}, {SelVertexOrRoot, SelEdge, SelEdgeOrAxis}, {SelVertexOrRoot, SelEdgeOrAxis, SelEdge}, {SelVertexOrRoot, SelEdge, SelExternalEdge}, {SelVertexOrRoot, SelExternalEdge, SelEdge}, {SelEdge, SelVertexOrRoot, SelEdgeOrAxis}, {SelEdgeOrAxis, SelVertexOrRoot, SelEdge}, {SelEdge, SelVertexOrRoot, SelExternalEdge}, {SelExternalEdge, SelVertexOrRoot, SelEdge}}; ; constraintCursor = cursor_createperpconstraint; } void CmdSketcherConstrainPerpendicular::activated(int iMsg) { Q_UNUSED(iMsg); QString strBasicHelp = QObject::tr( "There is a number of ways this constraint can be applied.\n\n" "Accepted combinations: two curves; an endpoint and a curve; two endpoints; two curves and a point.", /*disambig.:*/ "perpendicular constraint"); QString strError; // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true); if (constraintMode) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(constraintCursor, this)); getSelection().clearSelection(); } else { // TODO: Get the exact message from git history and put it here strError = QObject::tr("Select some geometry from the sketch.", "perpendicular constraint"); if (!strError.isEmpty()) strError.append(QString::fromLatin1("\n\n")); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), strError+strBasicHelp); } return; } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = dynamic_cast(selection[0].getObject()); if (!Obj || (SubNames.size() != 2 && SubNames.size() != 3)) { strError = QObject::tr("Wrong number of selected objects!","perpendicular constraint"); if (!strError.isEmpty()) strError.append(QString::fromLatin1("\n\n")); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), strError+strBasicHelp); return; } int GeoId1, GeoId2, GeoId3; Sketcher::PointPos PosId1, PosId2, PosId3; getIdsFromName(SubNames[0], Obj, GeoId1, PosId1); getIdsFromName(SubNames[1], Obj, GeoId2, PosId2); if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) { //checkBothExternal displays error message showNoConstraintBetweenFixedGeometry(); return; } if (SubNames.size() == 3) { //perpendicular via point getIdsFromName(SubNames[2], Obj, GeoId3, PosId3); //let's sink the point to be GeoId3. We want to keep the order the two curves have been selected in. if ( isVertex(GeoId1, PosId1) ){ std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); }; if ( isVertex(GeoId2, PosId2) ){ std::swap(GeoId2,GeoId3); std::swap(PosId2,PosId3); }; if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) { openCommand("add perpendicular constraint"); try{ //add missing point-on-object constraints if(! IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)){ Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", selection[0].getFeatName(),GeoId3,PosId3,GeoId1); }; if(! IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)){ Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", selection[0].getFeatName(),GeoId3,PosId3,GeoId2); }; if(! IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)){//FIXME: it's a good idea to add a check if the sketch is solved Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", selection[0].getFeatName(),GeoId3,PosId3,GeoId1); }; Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PerpendicularViaPoint',%d,%d,%d,%d)) ", selection[0].getFeatName(),GeoId1,GeoId2,GeoId3,PosId3); } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Error"), QString::fromLatin1(e.what())); Gui::Command::abortCommand(); tryAutoRecompute(); return; } commitCommand(); tryAutoRecompute(); getSelection().clearSelection(); return; }; strError = QObject::tr("With 3 objects, there must be 2 curves and 1 point.", "tangent constraint"); } else if (SubNames.size() == 2) { if (isVertex(GeoId1,PosId1) && isVertex(GeoId2,PosId2)) { //endpoint-to-endpoint perpendicularity if (isSimpleVertex(Obj, GeoId1, PosId1) || isSimpleVertex(Obj, GeoId2, PosId2)) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Cannot add a perpendicularity constraint at an unconnected point!")); return; } // This code supports simple B-spline 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 B-spline now } // end of code supports simple B-spline endpoint tangency openCommand("add perpendicular constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Perpendicular',%d,%d,%d,%d)) ", selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2); commitCommand(); tryAutoRecompute(); getSelection().clearSelection(); return; } else if ((isVertex(GeoId1,PosId1) && isEdge(GeoId2,PosId2)) || (isEdge(GeoId1,PosId1) && isVertex(GeoId2,PosId2))) { // endpoint-to-curve if (isVertex(GeoId2,PosId2)) { std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); } if (isSimpleVertex(Obj, GeoId1, PosId1)) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), 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 B-spline at any point implemented. QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Perpendicular to B-spline edge currently unsupported.")); return; } openCommand("add perpendicularity constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Perpendicular',%d,%d,%d)) ", selection[0].getFeatName(),GeoId1,PosId1,GeoId2); commitCommand(); tryAutoRecompute(); getSelection().clearSelection(); return; } else if (isEdge(GeoId1,PosId1) && isEdge(GeoId2,PosId2)) { // simple perpendicularity between GeoId1 and GeoId2 const Part::Geometry *geo1 = Obj->getGeometry(GeoId1); const Part::Geometry *geo2 = Obj->getGeometry(GeoId2); if (!geo1 || !geo2) { return; } 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->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() || geo2->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()){ // unsupported until tangent to B-spline at any point implemented. QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Perpendicular to B-spline edge currently unsupported.")); return; } if (geo1->getTypeId() == Part::GeomLineSegment::getClassTypeId()) std::swap(GeoId1,GeoId2); // GeoId2 is the line geo1 = Obj->getGeometry(GeoId1); geo2 = Obj->getGeometry(GeoId2); if (geo1->getTypeId() == Part::GeomEllipse::getClassTypeId() || geo1->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || geo1->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() || geo1->getTypeId() == Part::GeomArcOfParabola::getClassTypeId()) { Base::Vector3d center; Base::Vector3d majdir; Base::Vector3d focus; double majord = 0; double minord = 0; double phi = 0; if( geo1->getTypeId() == Part::GeomEllipse::getClassTypeId() ){ const Part::GeomEllipse *ellipse = static_cast(geo1); center=ellipse->getCenter(); majord=ellipse->getMajorRadius(); minord=ellipse->getMinorRadius(); majdir=ellipse->getMajorAxisDir(); phi=atan2(majdir.y, majdir.x); } else if( geo1->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() ){ const Part::GeomArcOfEllipse *aoe = static_cast(geo1); center=aoe->getCenter(); majord=aoe->getMajorRadius(); minord=aoe->getMinorRadius(); majdir=aoe->getMajorAxisDir(); phi=atan2(majdir.y, majdir.x); } else if( geo1->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() ){ const Part::GeomArcOfHyperbola *aoh = static_cast(geo1); center=aoh->getCenter(); majord=aoh->getMajorRadius(); minord=aoh->getMinorRadius(); majdir=aoh->getMajorAxisDir(); phi=atan2(majdir.y, majdir.x); } else if( geo1->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() ){ const Part::GeomArcOfParabola *aop = static_cast(geo1); center=aop->getCenter(); focus=aop->getFocus(); } const Part::GeomLineSegment *line = static_cast(geo2); Base::Vector3d point1=line->getStartPoint(); Base::Vector3d PoO; if( geo1->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() ) { double df=sqrt(majord*majord+minord*minord); Base::Vector3d direction=point1-(center+majdir*df); // towards the focus double tapprox=atan2(direction.y,direction.x)-phi; PoO = Base::Vector3d(center.x+majord*cosh(tapprox)*cos(phi)-minord*sinh(tapprox)*sin(phi), center.y+majord*cosh(tapprox)*sin(phi)+minord*sinh(tapprox)*cos(phi), 0); } else if( geo1->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() ) { Base::Vector3d direction=point1-focus; // towards the focus PoO = point1 + direction / 2; } else { Base::Vector3d direction=point1-center; double tapprox=atan2(direction.y,direction.x)-phi; // we approximate the eccentric anomally by the polar PoO = Base::Vector3d(center.x+majord*cos(tapprox)*cos(phi)-minord*sin(tapprox)*sin(phi), center.y+majord*cos(tapprox)*sin(phi)+minord*sin(tapprox)*cos(phi), 0); } openCommand("add perpendicular constraint"); try { // Add a point Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addGeometry(Part.Point(App.Vector(%f,%f,0)))", Obj->getNameInDocument(), PoO.x,PoO.y); int GeoIdPoint = Obj->getHighestCurveIndex(); // Point on first object (ellipse, arc of ellipse) Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", selection[0].getFeatName(),GeoIdPoint,Sketcher::start,GeoId1); // Point on second object Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", selection[0].getFeatName(),GeoIdPoint,Sketcher::start,GeoId2); // add constraint: Perpendicular-via-point Gui::Command::doCommand( Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PerpendicularViaPoint',%d,%d,%d,%d))", Obj->getNameInDocument(), GeoId1, GeoId2 ,GeoIdPoint, Sketcher::start); } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); Gui::Command::abortCommand(); tryAutoRecompute(); return; } commitCommand(); tryAutoRecompute(); getSelection().clearSelection(); return; } openCommand("add perpendicular constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Perpendicular',%d,%d)) ", selection[0].getFeatName(),GeoId1,GeoId2); commitCommand(); tryAutoRecompute(); getSelection().clearSelection(); return; } } if (!strError.isEmpty()) strError.append(QString::fromLatin1("\n\n")); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), strError+strBasicHelp); return; } void CmdSketcherConstrainPerpendicular::applyConstraint(std::vector &selSeq, int seqIndex) { SketcherGui::ViewProviderSketch* sketchgui = static_cast(getActiveGuiDocument()->getInEdit()); Sketcher::SketchObject* Obj = sketchgui->getSketchObject(); int GeoId1 = Constraint::GeoUndef, GeoId2 = Constraint::GeoUndef, GeoId3 = Constraint::GeoUndef; Sketcher::PointPos PosId1 = Sketcher::none, PosId2 = Sketcher::none, PosId3 = Sketcher::none; // check if the edge has already a Block constraint if ( areBothPointsOrSegmentsFixed(Obj,GeoId1,GeoId2) ) { showNoConstraintBetweenFixedGeometry(); return; } switch (seqIndex) { case 0: // {SelEdge, SelEdgeOrAxis} case 1: // {SelEdgeOrAxis, SelEdge} case 2: // {SelEdge, SelExternalEdge} case 3: // {SelExternalEdge, SelEdge} { GeoId1 = selSeq.at(0).GeoId; GeoId2 = selSeq.at(1).GeoId; const Part::Geometry *geo1 = Obj->getGeometry(GeoId1); const Part::Geometry *geo2 = Obj->getGeometry(GeoId2); if (!geo1 || !geo2) { return; } 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->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() || geo2->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()){ // unsupported until tangent to B-spline at any point implemented. QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Perpendicular to B-spline edge currently unsupported.")); return; } if (geo1->getTypeId() == Part::GeomLineSegment::getClassTypeId()) std::swap(GeoId1,GeoId2); // GeoId2 is the line geo1 = Obj->getGeometry(GeoId1); geo2 = Obj->getGeometry(GeoId2); if( geo1->getTypeId() == Part::GeomEllipse::getClassTypeId() || geo1->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || geo1->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() || geo1->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() ) { Base::Vector3d center; Base::Vector3d majdir; Base::Vector3d focus; double majord = 0; double minord = 0; double phi = 0; if( geo1->getTypeId() == Part::GeomEllipse::getClassTypeId() ){ const Part::GeomEllipse *ellipse = static_cast(geo1); center=ellipse->getCenter(); majord=ellipse->getMajorRadius(); minord=ellipse->getMinorRadius(); majdir=ellipse->getMajorAxisDir(); phi=atan2(majdir.y, majdir.x); } else if( geo1->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() ){ const Part::GeomArcOfEllipse *aoe = static_cast(geo1); center=aoe->getCenter(); majord=aoe->getMajorRadius(); minord=aoe->getMinorRadius(); majdir=aoe->getMajorAxisDir(); phi=atan2(majdir.y, majdir.x); } else if( geo1->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() ){ const Part::GeomArcOfHyperbola *aoh = static_cast(geo1); center=aoh->getCenter(); majord=aoh->getMajorRadius(); minord=aoh->getMinorRadius(); majdir=aoh->getMajorAxisDir(); phi=atan2(majdir.y, majdir.x); } else if( geo1->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() ){ const Part::GeomArcOfParabola *aop = static_cast(geo1); center=aop->getCenter(); focus=aop->getFocus(); } const Part::GeomLineSegment *line = static_cast(geo2); Base::Vector3d point1=line->getStartPoint(); Base::Vector3d PoO; if( geo1->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() ) { double df=sqrt(majord*majord+minord*minord); Base::Vector3d direction=point1-(center+majdir*df); // towards the focus double tapprox=atan2(direction.y,direction.x)-phi; PoO = Base::Vector3d(center.x+majord*cosh(tapprox)*cos(phi)-minord*sinh(tapprox)*sin(phi), center.y+majord*cosh(tapprox)*sin(phi)+minord*sinh(tapprox)*cos(phi), 0); } else if( geo1->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() ) { Base::Vector3d direction=point1-focus; // towards the focus PoO = point1 + direction / 2; } else { Base::Vector3d direction=point1-center; double tapprox=atan2(direction.y,direction.x)-phi; // we approximate the eccentric anomally by the polar PoO = Base::Vector3d(center.x+majord*cos(tapprox)*cos(phi)-minord*sin(tapprox)*sin(phi), center.y+majord*cos(tapprox)*sin(phi)+minord*sin(tapprox)*cos(phi), 0); } openCommand("add perpendicular constraint"); try { // Add a point Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addGeometry(Part.Point(App.Vector(%f,%f,0)))", Obj->getNameInDocument(), PoO.x,PoO.y); int GeoIdPoint = Obj->getHighestCurveIndex(); // Point on first object (ellipse, arc of ellipse) Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoIdPoint,Sketcher::start,GeoId1); // Point on second object Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoIdPoint,Sketcher::start,GeoId2); // add constraint: Perpendicular-via-point Gui::Command::doCommand( Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PerpendicularViaPoint',%d,%d,%d,%d))", Obj->getNameInDocument(), GeoId1, GeoId2 ,GeoIdPoint, Sketcher::start); commitCommand(); } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); Gui::Command::abortCommand(); } tryAutoRecompute(); getSelection().clearSelection(); return; } openCommand("add perpendicular constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Perpendicular',%d,%d)) ", Obj->getNameInDocument(),GeoId1,GeoId2); commitCommand(); tryAutoRecompute(); return; } case 4: // {SelVertexOrRoot, SelEdge, SelEdgeOrAxis} case 5: // {SelVertexOrRoot, SelEdgeOrAxis, SelEdge} case 6: // {SelVertexOrRoot, SelEdge, SelExternalEdge} case 7: // {SelVertexOrRoot, SelExternalEdge, SelEdge} { //let's sink the point to be GeoId3. GeoId1 = selSeq.at(1).GeoId; GeoId2 = selSeq.at(2).GeoId; GeoId3 = selSeq.at(0).GeoId; PosId3 = selSeq.at(0).PosId; break; } case 8: // {SelEdge, SelVertexOrRoot, SelEdgeOrAxis} case 9: // {SelEdgeOrAxis, SelVertexOrRoot, SelEdge} case 10: // {SelEdge, SelVertexOrRoot, SelExternalEdge} case 11: // {SelExternalEdge, SelVertexOrRoot, SelEdge} { //let's sink the point to be GeoId3. GeoId1 = selSeq.at(0).GeoId; GeoId2 = selSeq.at(2).GeoId; GeoId3 = selSeq.at(1).GeoId; PosId3 = selSeq.at(1).PosId; break; } default: return; } if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) { openCommand("add perpendicular constraint"); try{ //add missing point-on-object constraints if(! IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)){ Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoId3,PosId3,GeoId1); }; if(! IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)){ Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoId3,PosId3,GeoId2); }; if(! IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)){//FIXME: it's a good idea to add a check if the sketch is solved Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoId3,PosId3,GeoId1); }; Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PerpendicularViaPoint',%d,%d,%d,%d)) ", Obj->getNameInDocument(),GeoId1,GeoId2,GeoId3,PosId3); } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Error"), QString::fromLatin1(e.what())); Gui::Command::abortCommand(); tryAutoRecompute(); return; } commitCommand(); tryAutoRecompute(); getSelection().clearSelection(); return; }; } // ====================================================================================== class CmdSketcherConstrainTangent : public CmdSketcherConstraint { public: CmdSketcherConstrainTangent(); virtual ~CmdSketcherConstrainTangent(){} virtual const char* className() const { return "CmdSketcherConstrainTangent"; } protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); }; CmdSketcherConstrainTangent::CmdSketcherConstrainTangent() :CmdSketcherConstraint("Sketcher_ConstrainTangent") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Constrain tangent"); sToolTipText = QT_TR_NOOP("Create a tangent constraint between two entities"); sWhatsThis = "Sketcher_ConstrainTangent"; sStatusTip = sToolTipText; sPixmap = "Constraint_Tangent"; sAccel = "T"; eType = ForEdit; allowedSelSequences = {{SelEdge, SelEdgeOrAxis}, {SelEdgeOrAxis, SelEdge}, {SelEdge, SelExternalEdge}, {SelExternalEdge, SelEdge},/* Two Curves */ {SelVertexOrRoot, SelEdge, SelEdgeOrAxis}, {SelVertexOrRoot, SelEdgeOrAxis, SelEdge}, {SelVertexOrRoot, SelEdge, SelExternalEdge}, {SelVertexOrRoot, SelExternalEdge, SelEdge}, {SelEdge, SelVertexOrRoot, SelEdgeOrAxis}, {SelEdgeOrAxis, SelVertexOrRoot, SelEdge}, {SelEdge, SelVertexOrRoot, SelExternalEdge}, {SelExternalEdge, SelVertexOrRoot, SelEdge}, /* Two Curves and a Point */ {SelVertexOrRoot, SelVertex} /*Two Endpoints*/ /*No Place for One Endpoint and One Curve*/}; constraintCursor = cursor_genericconstraint; } void CmdSketcherConstrainTangent::activated(int iMsg) { Q_UNUSED(iMsg); QString strBasicHelp = QObject::tr( "There are a number of ways this constraint can be applied.\n\n" "Accepted combinations: two curves; an endpoint and a curve; two endpoints; two curves and a point.", /*disambig.:*/ "tangent constraint"); QString strError; // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true); if (constraintMode) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(constraintCursor, this)); getSelection().clearSelection(); } else { strError = QObject::tr("Select some geometry from the sketch.", "tangent constraint"); if (!strError.isEmpty()) strError.append(QString::fromLatin1("\n\n")); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), strError+strBasicHelp); } return; } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); if (SubNames.size() != 2 && SubNames.size() != 3){ strError = QObject::tr("Wrong number of selected objects!","tangent constraint"); if (!strError.isEmpty()) strError.append(QString::fromLatin1("\n\n")); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), strError+strBasicHelp); return; } int GeoId1, GeoId2, GeoId3; Sketcher::PointPos PosId1, PosId2, PosId3; getIdsFromName(SubNames[0], Obj, GeoId1, PosId1); getIdsFromName(SubNames[1], Obj, GeoId2, PosId2); if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)){ //checkBothExternal displays error message showNoConstraintBetweenFixedGeometry(); return; } if (SubNames.size() == 3) { //tangent via point getIdsFromName(SubNames[2], Obj, GeoId3, PosId3); //let's sink the point to be GeoId3. We want to keep the order the two curves have been selected in. if ( isVertex(GeoId1, PosId1) ){ std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); }; if ( isVertex(GeoId2, PosId2) ){ std::swap(GeoId2,GeoId3); std::swap(PosId2,PosId3); }; if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) { openCommand("add tangent constraint"); try{ //add missing point-on-object constraints if(! IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)){ Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", selection[0].getFeatName(),GeoId3,PosId3,GeoId1); }; if(! IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)){ Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", selection[0].getFeatName(),GeoId3,PosId3,GeoId2); }; if(! IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)){//FIXME: it's a good idea to add a check if the sketch is solved Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", selection[0].getFeatName(),GeoId3,PosId3,GeoId1); }; Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d)) ", selection[0].getFeatName(),GeoId1,GeoId2,GeoId3,PosId3); } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Error"), QString::fromLatin1(e.what())); Gui::Command::abortCommand(); tryAutoRecompute(); return; } commitCommand(); tryAutoRecompute(); getSelection().clearSelection(); return; }; strError = QObject::tr("With 3 objects, there must be 2 curves and 1 point.", "tangent constraint"); } else if (SubNames.size() == 2) { if (isVertex(GeoId1,PosId1) && isVertex(GeoId2,PosId2)) { // endpoint-to-endpoint tangency if (isSimpleVertex(Obj, GeoId1, PosId1) || isSimpleVertex(Obj, GeoId2, PosId2)) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Cannot add a tangency constraint at an unconnected point!")); return; } // This code supports simple B-spline 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 B-spline now } // end of code supports simple B-spline endpoint tangency openCommand("add tangent constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d,%d)) ", selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2); commitCommand(); tryAutoRecompute(); getSelection().clearSelection(); return; } else if ((isVertex(GeoId1,PosId1) && isEdge(GeoId2,PosId2)) || (isEdge(GeoId1,PosId1) && isVertex(GeoId2,PosId2))) { // endpoint-to-curve tangency if (isVertex(GeoId2,PosId2)) { std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); } if (isSimpleVertex(Obj, GeoId1, PosId1)) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), 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 B-spline at any point implemented. QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Tangency to B-spline edge currently unsupported.")); return; } openCommand("add tangent constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d)) ", selection[0].getFeatName(),GeoId1,PosId1,GeoId2); commitCommand(); tryAutoRecompute(); getSelection().clearSelection(); return; } else if (isEdge(GeoId1,PosId1) && isEdge(GeoId2,PosId2)) { // simple tangency between GeoId1 and GeoId2 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 B-spline at any point implemented. QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Tangency to B-spline edge currently unsupported.")); return; } if( geom1 && geom2 && ( geom1->getTypeId() == Part::GeomEllipse::getClassTypeId() || geom2->getTypeId() == Part::GeomEllipse::getClassTypeId() )){ if(geom1->getTypeId() != Part::GeomEllipse::getClassTypeId()) std::swap(GeoId1,GeoId2); // GeoId1 is the ellipse geom1 = Obj->getGeometry(GeoId1); geom2 = Obj->getGeometry(GeoId2); if( geom2->getTypeId() == Part::GeomEllipse::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || geom2->getTypeId() == Part::GeomCircle::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() ) { Gui::Command::openCommand("add tangent constraint point"); makeTangentToEllipseviaNewPoint(Obj,geom1,geom2,GeoId1,GeoId2); getSelection().clearSelection(); return; } else if( geom2->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() ) { Gui::Command::openCommand("add tangent constraint point"); makeTangentToArcOfHyperbolaviaNewPoint(Obj,geom2,geom1,GeoId2,GeoId1); getSelection().clearSelection(); return; } else if( geom2->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() ) { Gui::Command::openCommand("add tangent constraint point"); makeTangentToArcOfParabolaviaNewPoint(Obj,geom2,geom1,GeoId2,GeoId1); getSelection().clearSelection(); return; } } else if( geom1 && geom2 && ( geom1->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() )){ if(geom1->getTypeId() != Part::GeomArcOfHyperbola::getClassTypeId()) std::swap(GeoId1,GeoId2); // GeoId1 is the arc of hyperbola geom1 = Obj->getGeometry(GeoId1); geom2 = Obj->getGeometry(GeoId2); if( geom2->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || geom2->getTypeId() == Part::GeomCircle::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() || geom2->getTypeId() == Part::GeomLineSegment::getClassTypeId() ) { Gui::Command::openCommand("add tangent constraint point"); makeTangentToArcOfHyperbolaviaNewPoint(Obj,geom1,geom2,GeoId1,GeoId2); getSelection().clearSelection(); return; } else if( geom2->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() ) { Gui::Command::openCommand("add tangent constraint point"); makeTangentToArcOfParabolaviaNewPoint(Obj,geom2,geom1,GeoId2,GeoId1); getSelection().clearSelection(); return; } } else if( geom1 && geom2 && ( geom1->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() )){ if(geom1->getTypeId() != Part::GeomArcOfParabola::getClassTypeId()) std::swap(GeoId1,GeoId2); // GeoId1 is the arc of hyperbola geom1 = Obj->getGeometry(GeoId1); geom2 = Obj->getGeometry(GeoId2); if (geom2->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || geom2->getTypeId() == Part::GeomCircle::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() || geom2->getTypeId() == Part::GeomLineSegment::getClassTypeId() ) { Gui::Command::openCommand("add tangent constraint point"); makeTangentToArcOfParabolaviaNewPoint(Obj,geom1,geom2,GeoId1,GeoId2); getSelection().clearSelection(); return; } } openCommand("add tangent constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d)) ", selection[0].getFeatName(),GeoId1,GeoId2); commitCommand(); tryAutoRecompute(); getSelection().clearSelection(); return; } } if (!strError.isEmpty()) strError.append(QString::fromLatin1("\n\n")); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), strError+strBasicHelp); return; } void CmdSketcherConstrainTangent::applyConstraint(std::vector &selSeq, int seqIndex) { SketcherGui::ViewProviderSketch* sketchgui = static_cast(getActiveGuiDocument()->getInEdit()); Sketcher::SketchObject* Obj = sketchgui->getSketchObject(); QString strError; int GeoId1 = Constraint::GeoUndef, GeoId2 = Constraint::GeoUndef, GeoId3 = Constraint::GeoUndef; Sketcher::PointPos PosId1 = Sketcher::none, PosId2 = Sketcher::none, PosId3 = Sketcher::none; // check if the edge has already a Block constraint if ( areBothPointsOrSegmentsFixed(Obj,GeoId1,GeoId2) ) { showNoConstraintBetweenFixedGeometry(); return; } switch (seqIndex) { case 0: // {SelEdge, SelEdgeOrAxis} case 1: // {SelEdgeOrAxis, SelEdge} case 2: // {SelEdge, SelExternalEdge} case 3: // {SelExternalEdge, SelEdge} { GeoId1 = selSeq.at(0).GeoId; GeoId2 = selSeq.at(1).GeoId; 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 B-spline at any point implemented. QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Tangency to B-spline edge currently unsupported.")); return; } if( geom1 && geom2 && ( geom1->getTypeId() == Part::GeomEllipse::getClassTypeId() || geom2->getTypeId() == Part::GeomEllipse::getClassTypeId() )){ if(geom1->getTypeId() != Part::GeomEllipse::getClassTypeId()) std::swap(GeoId1,GeoId2); // GeoId1 is the ellipse geom1 = Obj->getGeometry(GeoId1); geom2 = Obj->getGeometry(GeoId2); if( geom2->getTypeId() == Part::GeomEllipse::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || geom2->getTypeId() == Part::GeomCircle::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() ) { Gui::Command::openCommand("add tangent constraint point"); makeTangentToEllipseviaNewPoint(Obj,geom1,geom2,GeoId1,GeoId2); getSelection().clearSelection(); return; } else if( geom2->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() ) { Gui::Command::openCommand("add tangent constraint point"); makeTangentToArcOfHyperbolaviaNewPoint(Obj,geom2,geom1,GeoId2,GeoId1); getSelection().clearSelection(); return; } else if( geom2->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() ) { Gui::Command::openCommand("add tangent constraint point"); makeTangentToArcOfParabolaviaNewPoint(Obj,geom2,geom1,GeoId2,GeoId1); getSelection().clearSelection(); return; } } else if( geom1 && geom2 && ( geom1->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() )){ if(geom1->getTypeId() != Part::GeomArcOfHyperbola::getClassTypeId()) std::swap(GeoId1,GeoId2); // GeoId1 is the arc of hyperbola geom1 = Obj->getGeometry(GeoId1); geom2 = Obj->getGeometry(GeoId2); if( geom2->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || geom2->getTypeId() == Part::GeomCircle::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() || geom2->getTypeId() == Part::GeomLineSegment::getClassTypeId() ) { Gui::Command::openCommand("add tangent constraint point"); makeTangentToArcOfHyperbolaviaNewPoint(Obj,geom1,geom2,GeoId1,GeoId2); getSelection().clearSelection(); return; } else if( geom2->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() ) { Gui::Command::openCommand("add tangent constraint point"); makeTangentToArcOfParabolaviaNewPoint(Obj,geom2,geom1,GeoId2,GeoId1); getSelection().clearSelection(); return; } } else if( geom1 && geom2 && ( geom1->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() )){ if(geom1->getTypeId() != Part::GeomArcOfParabola::getClassTypeId()) std::swap(GeoId1,GeoId2); // GeoId1 is the arc of hyperbola geom1 = Obj->getGeometry(GeoId1); geom2 = Obj->getGeometry(GeoId2); if (geom2->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || geom2->getTypeId() == Part::GeomCircle::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() || geom2->getTypeId() == Part::GeomLineSegment::getClassTypeId() ) { Gui::Command::openCommand("add tangent constraint point"); makeTangentToArcOfParabolaviaNewPoint(Obj,geom1,geom2,GeoId1,GeoId2); getSelection().clearSelection(); return; } } openCommand("add tangent constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d)) ", Obj->getNameInDocument(),GeoId1,GeoId2); commitCommand(); tryAutoRecompute(); return; } case 4: // {SelVertexOrRoot, SelEdge, SelEdgeOrAxis} case 5: // {SelVertexOrRoot, SelEdgeOrAxis, SelEdge} case 6: // {SelVertexOrRoot, SelEdge, SelExternalEdge} case 7: // {SelVertexOrRoot, SelExternalEdge, SelEdge} { //let's sink the point to be GeoId3. GeoId1 = selSeq.at(1).GeoId; GeoId2 = selSeq.at(2).GeoId; GeoId3 = selSeq.at(0).GeoId; PosId3 = selSeq.at(0).PosId; break; } case 8: // {SelEdge, SelVertexOrRoot, SelEdgeOrAxis} case 9: // {SelEdgeOrAxis, SelVertexOrRoot, SelEdge} case 10: // {SelEdge, SelVertexOrRoot, SelExternalEdge} case 11: // {SelExternalEdge, SelVertexOrRoot, SelEdge} { //let's sink the point to be GeoId3. GeoId1 = selSeq.at(0).GeoId; GeoId2 = selSeq.at(2).GeoId; GeoId3 = selSeq.at(1).GeoId; PosId3 = selSeq.at(1).PosId; break; } case 12: // {SelVertexOrRoot, SelVertex} { // Different notation than the previous places GeoId1 = selSeq.at(0).GeoId; GeoId2 = selSeq.at(1).GeoId; PosId1 = selSeq.at(0).PosId; PosId2 = selSeq.at(1).PosId; if (isSimpleVertex(Obj, GeoId1, PosId1) || isSimpleVertex(Obj, GeoId2, PosId2)) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Cannot add a tangency constraint at an unconnected point!")); return; } // This code supports simple B-spline 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 B-spline now } // end of code supports simple B-spline endpoint tangency openCommand("add tangent constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d,%d)) ", Obj->getNameInDocument(),GeoId1,PosId1,GeoId2,PosId2); commitCommand(); tryAutoRecompute(); getSelection().clearSelection(); return; } default: return; } if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) { openCommand("add tangent constraint"); try{ //add missing point-on-object constraints if(! IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)){ Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoId3,PosId3,GeoId1); }; if(! IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)){ Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoId3,PosId3,GeoId2); }; if(! IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)){//FIXME: it's a good idea to add a check if the sketch is solved Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoId3,PosId3,GeoId1); }; Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d)) ", Obj->getNameInDocument(), GeoId1,GeoId2,GeoId3,PosId3); } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Error"), QString::fromLatin1(e.what())); Gui::Command::abortCommand(); tryAutoRecompute(); return; } commitCommand(); tryAutoRecompute(); getSelection().clearSelection(); return; }; } // ====================================================================================== class CmdSketcherConstrainRadius : public CmdSketcherConstraint { public: CmdSketcherConstrainRadius(); virtual ~CmdSketcherConstrainRadius(){} virtual void updateAction(int mode); virtual const char* className() const { return "CmdSketcherConstrainRadius"; } protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); }; CmdSketcherConstrainRadius::CmdSketcherConstrainRadius() :CmdSketcherConstraint("Sketcher_ConstrainRadius") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Constrain radius"); sToolTipText = QT_TR_NOOP("Fix the radius of a circle or an arc"); sWhatsThis = "Sketcher_ConstrainRadius"; sStatusTip = sToolTipText; sPixmap = "Constraint_Radius"; sAccel = "SHIFT+R"; eType = ForEdit; allowedSelSequences = {{SelEdge}, {SelExternalEdge}}; constraintCursor = cursor_genericconstraint; } void CmdSketcherConstrainRadius::activated(int iMsg) { Q_UNUSED(iMsg); // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true); if (constraintMode) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(constraintCursor, this)); getSelection().clearSelection(); } else { // TODO: Get the exact message from git history and put it here QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select the right things from the sketch.")); } return; } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); if (SubNames.empty()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select one or more arcs or circles from the sketch.")); return; } // check for which selected geometry the constraint can be applied std::vector< std::pair > geoIdRadiusMap; std::vector< std::pair > externalGeoIdRadiusMap; for (std::vector::const_iterator it = SubNames.begin(); it != SubNames.end(); ++it) { bool issegmentfixed = false; int GeoId; if (it->size() > 4 && it->substr(0,4) == "Edge") { GeoId = std::atoi(it->substr(4,4000).c_str()) - 1; issegmentfixed = isPointOrSegmentFixed(Obj,GeoId); } else if (it->size() > 4 && it->substr(0,12) == "ExternalEdge") { GeoId = -std::atoi(it->substr(12,4000).c_str()) - 2; issegmentfixed = true; } else continue; const Part::Geometry *geom = Obj->getGeometry(GeoId); if (geom && geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { const Part::GeomArcOfCircle *arc = static_cast(geom); double radius = arc->getRadius(); if(issegmentfixed) { externalGeoIdRadiusMap.push_back(std::make_pair(GeoId, radius)); } else { geoIdRadiusMap.push_back(std::make_pair(GeoId, radius)); } } else if (geom && geom->getTypeId() == Part::GeomCircle::getClassTypeId()) { const Part::GeomCircle *circle = static_cast(geom); double radius = circle->getRadius(); if(issegmentfixed) { externalGeoIdRadiusMap.push_back(std::make_pair(GeoId, radius)); } else { geoIdRadiusMap.push_back(std::make_pair(GeoId, radius)); } } } if (geoIdRadiusMap.empty() && externalGeoIdRadiusMap.empty()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select one or more arcs or circles from the sketch.")); } bool commitNeeded=false; bool updateNeeded=false; bool commandopened=false; if(!externalGeoIdRadiusMap.empty()) { // Create the non-driving radius constraints now openCommand("Add radius constraint"); commandopened=true; unsigned int constrSize = 0; for (std::vector< std::pair >::iterator it = externalGeoIdRadiusMap.begin(); it != externalGeoIdRadiusMap.end(); ++it) { Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Radius',%d,%f)) ", selection[0].getFeatName(),it->first,it->second); const std::vector &ConStr = Obj->Constraints.getValues(); constrSize=ConStr.size(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", selection[0].getFeatName(),constrSize-1,"False"); } const std::vector &ConStr = Obj->Constraints.getValues(); std::size_t indexConstr = constrSize - externalGeoIdRadiusMap.size(); // Guess some reasonable distance for placing the datum text Gui::Document *doc = getActiveGuiDocument(); float sf = 1.f; if (doc && doc->getInEdit() && doc->getInEdit()->isDerivedFrom(SketcherGui::ViewProviderSketch::getClassTypeId())) { SketcherGui::ViewProviderSketch *vp = static_cast(doc->getInEdit()); sf = vp->getScaleFactor(); for (std::size_t i=0; iLabelDistance = 2. * sf; } vp->draw(false,false); // Redraw } commitNeeded=true; updateNeeded=true; } if(!geoIdRadiusMap.empty()) { bool constrainEqual = false; if (geoIdRadiusMap.size() > 1 && constraintCreationMode==Driving) { int ret = QMessageBox::question(Gui::getMainWindow(), QObject::tr("Constrain equal"), QObject::tr("Do you want to share the same radius for all selected elements?"), QMessageBox::Yes, QMessageBox::No, QMessageBox::Cancel); // use an equality constraint if (ret == QMessageBox::Yes) { constrainEqual = true; } else if (ret == QMessageBox::Cancel) { // do nothing return; } } if (constrainEqual) { // Create the one radius constraint now int refGeoId = geoIdRadiusMap.front().first; double radius = geoIdRadiusMap.front().second; if(!commandopened) openCommand("Add radius constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Radius',%d,%f)) ", selection[0].getFeatName(),refGeoId,radius); // Add the equality constraints for (std::vector< std::pair >::iterator it = geoIdRadiusMap.begin()+1; it != geoIdRadiusMap.end(); ++it) { Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Equal',%d,%d)) ", selection[0].getFeatName(),refGeoId,it->first); } } else { // Create the radius constraints now if(!commandopened) openCommand("Add radius constraint"); for (std::vector< std::pair >::iterator it = geoIdRadiusMap.begin(); it != geoIdRadiusMap.end(); ++it) { Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Radius',%d,%f)) ", selection[0].getFeatName(),it->first,it->second); if(constraintCreationMode==Reference) { const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", selection[0].getFeatName(),ConStr.size()-1,"False"); } } } const std::vector &ConStr = Obj->Constraints.getValues(); std::size_t indexConstr = ConStr.size() - geoIdRadiusMap.size(); // Guess some reasonable distance for placing the datum text Gui::Document *doc = getActiveGuiDocument(); float sf = 1.f; if (doc && doc->getInEdit() && doc->getInEdit()->isDerivedFrom(SketcherGui::ViewProviderSketch::getClassTypeId())) { SketcherGui::ViewProviderSketch *vp = static_cast(doc->getInEdit()); sf = vp->getScaleFactor(); for (std::size_t i=0; iLabelDistance = 2. * sf; } vp->draw(false,false); // Redraw } ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool show = hGrp->GetBool("ShowDialogOnDistanceConstraint", true); // Ask for the value of the radius immediately if (show && constraintCreationMode==Driving) { QDialog dlg(Gui::getMainWindow()); Ui::InsertDatum ui_Datum; ui_Datum.setupUi(&dlg); dlg.setWindowTitle(EditDatumDialog::tr("Change radius")); ui_Datum.label->setText(EditDatumDialog::tr("Radius:")); Base::Quantity init_val; init_val.setUnit(Base::Unit::Length); init_val.setValue(geoIdRadiusMap.front().second); ui_Datum.labelEdit->setValue(init_val); ui_Datum.labelEdit->selectNumber(); if (constrainEqual || geoIdRadiusMap.size() == 1) ui_Datum.labelEdit->bind(Obj->Constraints.createPath(indexConstr)); else ui_Datum.name->setDisabled(true); if (dlg.exec() == QDialog::Accepted) { Base::Quantity newQuant = ui_Datum.labelEdit->value(); double newRadius = newQuant.getValue(); try { if (constrainEqual || geoIdRadiusMap.size() == 1) { doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.setDatum(%i,App.Units.Quantity('%f %s'))", Obj->getNameInDocument(), indexConstr, newRadius, (const char*)newQuant.getUnit().getString().toUtf8()); QString constraintName = ui_Datum.name->text().trimmed(); if (Base::Tools::toStdString(constraintName) != Obj->Constraints[indexConstr]->Name) { std::string escapedstr = Base::Tools::escapedUnicodeFromUtf8(constraintName.toUtf8().constData()); Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.renameConstraint(%d, u'%s')", Obj->getNameInDocument(), indexConstr, escapedstr.c_str()); } } else { for (std::size_t i=0; igetNameInDocument(), indexConstr+i, newRadius, (const char*)newQuant.getUnit().getString().toUtf8()); } } commitCommand(); if (Obj->noRecomputes && Obj->ExpressionEngine.depsAreTouched()) { Obj->ExpressionEngine.execute(); Obj->solve(); } tryAutoRecompute(); commitNeeded=false; updateNeeded=false; } catch (const Base::Exception& e) { QMessageBox::critical(qApp->activeWindow(), QObject::tr("Dimensional constraint"), QString::fromUtf8(e.what())); abortCommand(); tryAutoRecomputeIfNotSolve(Obj); // we have to update the solver after this aborted addition. } } else { // command canceled abortCommand(); updateNeeded=true; } } else { // now dialog was shown so commit the command commitCommand(); commitNeeded=false; } //updateActive(); getSelection().clearSelection(); } if (commitNeeded) commitCommand(); if(updateNeeded) { tryAutoRecomputeIfNotSolve(Obj); // we have to update the solver after this aborted addition. } } void CmdSketcherConstrainRadius::applyConstraint(std::vector &selSeq, int seqIndex) { SketcherGui::ViewProviderSketch* sketchgui = static_cast(getActiveGuiDocument()->getInEdit()); Sketcher::SketchObject* Obj = sketchgui->getSketchObject(); int GeoId = selSeq.at(0).GeoId; double radius = 0.0; bool commitNeeded=false; bool updateNeeded=false; switch (seqIndex) { case 0: // {SelEdge} case 1: // {SelExternalEdge} { const Part::Geometry *geom = Obj->getGeometry(GeoId); if (geom && geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { const Part::GeomArcOfCircle *arc = static_cast(geom); radius = arc->getRadius(); } else if (geom && geom->getTypeId() == Part::GeomCircle::getClassTypeId()) { const Part::GeomCircle *circle = static_cast(geom); radius = circle->getRadius(); } else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Constraint only applies to arcs or circles.")); return; } // Create the radius constraint now openCommand("Add radius constraint"); Gui::Command::doCommand(Doc, "App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Radius',%d,%f)) ", Obj->getNameInDocument(), GeoId, radius); const std::vector &ConStr = Obj->Constraints.getValues(); int indexConstr = ConStr.size() - 1; bool fixed = isPointOrSegmentFixed(Obj,GeoId); if(fixed || constraintCreationMode==Reference) { Gui::Command::doCommand(Doc, "App.ActiveDocument.%s.setDriving(%i,%s)", Obj->getNameInDocument(), ConStr.size()-1, "False"); } // Guess some reasonable distance for placing the datum text Gui::Document *doc = getActiveGuiDocument(); float sf = 1.f; if (doc && doc->getInEdit() && doc->getInEdit()->isDerivedFrom(SketcherGui::ViewProviderSketch::getClassTypeId())) { SketcherGui::ViewProviderSketch *vp = static_cast(doc->getInEdit()); sf = vp->getScaleFactor(); Sketcher::Constraint *constr = ConStr[ConStr.size()-1]; constr->LabelDistance = 2. * sf; vp->draw(); // Redraw } ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool show = hGrp->GetBool("ShowDialogOnDistanceConstraint", true); // Ask for the value of the radius immediately if (show && constraintCreationMode==Driving && !fixed) { QDialog dlg(Gui::getMainWindow()); Ui::InsertDatum ui_Datum; ui_Datum.setupUi(&dlg); dlg.setWindowTitle(EditDatumDialog::tr("Change radius")); ui_Datum.label->setText(EditDatumDialog::tr("Radius:")); Base::Quantity init_val; init_val.setUnit(Base::Unit::Length); init_val.setValue(radius); ui_Datum.labelEdit->setValue(init_val); ui_Datum.labelEdit->selectNumber(); ui_Datum.labelEdit->bind(Obj->Constraints.createPath(indexConstr)); if (dlg.exec() == QDialog::Accepted) { Base::Quantity newQuant = ui_Datum.labelEdit->value(); double newRadius = newQuant.getValue(); try { doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.setDatum(%i,App.Units.Quantity('%f %s'))", Obj->getNameInDocument(), indexConstr, newRadius, (const char*)newQuant.getUnit().getString().toUtf8()); QString constraintName = ui_Datum.name->text().trimmed(); if (Base::Tools::toStdString(constraintName) != Obj->Constraints[indexConstr]->Name) { std::string escapedstr = Base::Tools::escapedUnicodeFromUtf8(constraintName.toUtf8().constData()); Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.renameConstraint(%d, u'%s')", Obj->getNameInDocument(), indexConstr, escapedstr.c_str()); } commitCommand(); if (Obj->noRecomputes && Obj->ExpressionEngine.depsAreTouched()) { Obj->ExpressionEngine.execute(); Obj->solve(); } tryAutoRecompute(); commitNeeded=false; updateNeeded=false; } catch (const Base::Exception& e) { QMessageBox::critical(qApp->activeWindow(), QObject::tr("Dimensional constraint"), QString::fromUtf8(e.what())); abortCommand(); tryAutoRecomputeIfNotSolve(Obj); // we have to update the solver after this aborted addition. } } else { // command canceled abortCommand(); updateNeeded=true; } } else { // now dialog was shown so commit the command commitCommand(); commitNeeded=false; } //updateActive(); getSelection().clearSelection(); if(commitNeeded) commitCommand(); if(updateNeeded) { tryAutoRecomputeIfNotSolve(Obj); // we have to update the solver after this aborted addition. } } } } void CmdSketcherConstrainRadius::updateAction(int mode) { switch (mode) { case Reference: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().pixmap("Constraint_Radius_Driven")); break; case Driving: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().pixmap("Constraint_Radius")); break; } } // ====================================================================================== class CmdSketcherConstrainAngle : public CmdSketcherConstraint { public: CmdSketcherConstrainAngle(); virtual ~CmdSketcherConstrainAngle(){} virtual void updateAction(int mode); virtual const char* className() const { return "CmdSketcherConstrainAngle"; } protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); }; CmdSketcherConstrainAngle::CmdSketcherConstrainAngle() :CmdSketcherConstraint("Sketcher_ConstrainAngle") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Constrain angle"); sToolTipText = QT_TR_NOOP("Fix the angle of a line or the angle between two lines"); sWhatsThis = "Sketcher_ConstrainAngle"; sStatusTip = sToolTipText; sPixmap = "Constraint_InternalAngle"; sAccel = "A"; eType = ForEdit; allowedSelSequences = {{SelEdge, SelEdgeOrAxis}, {SelEdgeOrAxis, SelEdge}, {SelEdge, SelExternalEdge}, {SelExternalEdge, SelEdge}, {SelExternalEdge, SelExternalEdge}, {SelEdge, SelVertexOrRoot, SelEdgeOrAxis}, {SelEdgeOrAxis, SelVertexOrRoot, SelEdge}, {SelEdge, SelVertexOrRoot, SelExternalEdge}, {SelExternalEdge, SelVertexOrRoot, SelEdge}, {SelExternalEdge, SelVertexOrRoot, SelExternalEdge}, {SelVertexOrRoot, SelEdge, SelEdgeOrAxis}, {SelVertexOrRoot, SelEdgeOrAxis, SelEdge}, {SelVertexOrRoot, SelEdge, SelExternalEdge}, {SelVertexOrRoot, SelExternalEdge, SelEdge}, {SelVertexOrRoot, SelExternalEdge, SelExternalEdge}}; constraintCursor = cursor_genericconstraint; } void CmdSketcherConstrainAngle::activated(int iMsg) { Q_UNUSED(iMsg); //TODO: comprehensive messages, like in CmdSketcherConstrainTangent // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true); if (constraintMode) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(constraintCursor, this)); getSelection().clearSelection(); } else { // TODO: Get the exact message from git history and put it here QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select the right things from the sketch.")); } return; } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); if (SubNames.size() < 1 || SubNames.size() > 3) { //goto ExitWithMessage; QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select one or two lines from the sketch. Or select two edges and a point.")); } int GeoId1, GeoId2=Constraint::GeoUndef, GeoId3 = Constraint::GeoUndef; Sketcher::PointPos PosId1, PosId2=Sketcher::none, PosId3 = Sketcher::none; getIdsFromName(SubNames[0], Obj, GeoId1, PosId1); if (SubNames.size() > 1) getIdsFromName(SubNames[1], Obj, GeoId2, PosId2); if (SubNames.size() > 2) getIdsFromName(SubNames[2], Obj, GeoId3, PosId3); if (SubNames.size() == 3){//standalone implementation of angle-via-point //let's sink the point to be GeoId3. We want to keep the order the two curves have been selected in. if ( isVertex(GeoId1, PosId1) ){ std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); }; if ( isVertex(GeoId2, PosId2) ){ std::swap(GeoId2,GeoId3); std::swap(PosId2,PosId3); }; bool bothexternal=areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2); if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) { double ActAngle = 0.0; openCommand("Add angle constraint"); //add missing point-on-object constraints if(! IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)){ Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", selection[0].getFeatName(),GeoId3,PosId3,GeoId1); }; if(! IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)){ Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", selection[0].getFeatName(),GeoId3,PosId3,GeoId2); }; if(! IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)){//FIXME: it's a good idea to add a check if the sketch is solved Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", selection[0].getFeatName(),GeoId3,PosId3,GeoId1); }; //assuming point-on-curves have been solved, calculate the angle. //DeepSOIC: this may be slow, but I wanted to reuse the conversion from Geometry to GCS shapes that is done in Sketch Base::Vector3d p = Obj->getPoint(GeoId3, PosId3 ); ActAngle = Obj->calculateAngleViaPoint(GeoId1,GeoId2,p.x,p.y); //negative constraint value avoidance if (ActAngle < -Precision::Angular()){ std::swap(GeoId1, GeoId2); std::swap(PosId1, PosId2); ActAngle = -ActAngle; } Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('AngleViaPoint',%d,%d,%d,%d,%f)) ", selection[0].getFeatName(),GeoId1,GeoId2,GeoId3,PosId3,ActAngle); if (bothexternal || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", selection[0].getFeatName(),ConStr.size()-1,"False"); finishDistanceConstraint(this, Obj,false); } else finishDistanceConstraint(this, Obj,true); return; }; } else if (SubNames.size() < 3) { bool bothexternal = areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2); if (isVertex(GeoId1,PosId1) && isEdge(GeoId2,PosId2)) { std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); } if (isEdge(GeoId2,PosId2)) { // line to line angle const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); if (geom1->getTypeId() == Part::GeomLineSegment::getClassTypeId() && geom2->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { const Part::GeomLineSegment *lineSeg1 = static_cast(geom1); const Part::GeomLineSegment *lineSeg2 = static_cast(geom2); // find the two closest line ends Sketcher::PointPos PosId1 = Sketcher::none; Sketcher::PointPos PosId2 = Sketcher::none; Base::Vector3d p1[2], p2[2]; p1[0] = lineSeg1->getStartPoint(); p1[1] = lineSeg1->getEndPoint(); p2[0] = lineSeg2->getStartPoint(); p2[1] = lineSeg2->getEndPoint(); // Get the intersection point in 2d of the two lines if possible Base::Line2d line1(Base::Vector2d(p1[0].x, p1[0].y), Base::Vector2d(p1[1].x, p1[1].y)); Base::Line2d line2(Base::Vector2d(p2[0].x, p2[0].y), Base::Vector2d(p2[1].x, p2[1].y)); Base::Vector2d s; if (line1.Intersect(line2, s)) { // get the end points of the line segments that are closest to the intersection point Base::Vector3d s3d(s.x, s.y, p1[0].z); if (Base::DistanceP2(s3d, p1[0]) < Base::DistanceP2(s3d, p1[1])) PosId1 = Sketcher::start; else PosId1 = Sketcher::end; if (Base::DistanceP2(s3d, p2[0]) < Base::DistanceP2(s3d, p2[1])) PosId2 = Sketcher::start; else PosId2 = Sketcher::end; } else { // if all points are collinear double length = DBL_MAX; for (int i=0; i <= 1; i++) { for (int j=0; j <= 1; j++) { double tmp = Base::DistanceP2(p2[j], p1[i]); if (tmp < length) { length = tmp; PosId1 = i ? Sketcher::end : Sketcher::start; PosId2 = j ? Sketcher::end : Sketcher::start; } } } } Base::Vector3d dir1 = ((PosId1 == Sketcher::start) ? 1. : -1.) * (lineSeg1->getEndPoint()-lineSeg1->getStartPoint()); Base::Vector3d dir2 = ((PosId2 == Sketcher::start) ? 1. : -1.) * (lineSeg2->getEndPoint()-lineSeg2->getStartPoint()); // check if the two lines are parallel, in this case an angle is not possible Base::Vector3d dir3 = dir1 % dir2; if (dir3.Length() < Precision::Intersection()) { Base::Vector3d dist = (p1[0] - p2[0]) % dir1; if (dist.Sqr() > Precision::Intersection()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Parallel lines"), QObject::tr("An angle constraint cannot be set for two parallel lines.")); return; } } double ActAngle = atan2(dir1.x*dir2.y-dir1.y*dir2.x, dir1.y*dir2.y+dir1.x*dir2.x); if (ActAngle < 0) { ActAngle *= -1; std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); } openCommand("Add angle constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Angle',%d,%d,%d,%d,%f)) ", selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2,ActAngle); if (bothexternal || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", selection[0].getFeatName(),ConStr.size()-1,"False"); finishDistanceConstraint(this, Obj,false); } else finishDistanceConstraint(this, Obj,true); return; } } else if (isEdge(GeoId1,PosId1)) { // line angle if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Cannot add an angle constraint on an axis!")); return; } const Part::Geometry *geom = Obj->getGeometry(GeoId1); if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { const Part::GeomLineSegment *lineSeg; lineSeg = static_cast(geom); Base::Vector3d dir = lineSeg->getEndPoint()-lineSeg->getStartPoint(); double ActAngle = atan2(dir.y,dir.x); openCommand("Add angle constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Angle',%d,%f)) ", selection[0].getFeatName(),GeoId1,ActAngle); if (GeoId1 <= Sketcher::GeoEnum::RefExt || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", selection[0].getFeatName(),ConStr.size()-1,"False"); finishDistanceConstraint(this, Obj,false); } else finishDistanceConstraint(this, Obj,true); return; } else if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { const Part::GeomArcOfCircle *arc; arc = static_cast(geom); double startangle, endangle; arc->getRange(startangle, endangle, /*EmulateCCWXY=*/true); double angle = endangle - startangle; openCommand("Add angle constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Angle',%d,%f)) ", selection[0].getFeatName(),GeoId1,angle); if (GeoId1 <= Sketcher::GeoEnum::RefExt || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", selection[0].getFeatName(),ConStr.size()-1,"False"); finishDistanceConstraint(this, Obj,false); } else finishDistanceConstraint(this, Obj,true); return; } } }; QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select one or two lines from the sketch. Or select two edges and a point.")); return; } void CmdSketcherConstrainAngle::applyConstraint(std::vector &selSeq, int seqIndex) { SketcherGui::ViewProviderSketch* sketchgui = static_cast(getActiveGuiDocument()->getInEdit()); Sketcher::SketchObject* Obj = sketchgui->getSketchObject(); int GeoId1 = Constraint::GeoUndef, GeoId2 = Constraint::GeoUndef, GeoId3 = Constraint::GeoUndef; Sketcher::PointPos PosId1 = Sketcher::none, PosId2 = Sketcher::none, PosId3 = Sketcher::none; switch (seqIndex) { case 0: // {SelEdge, SelEdgeOrAxis} case 1: // {SelEdgeOrAxis, SelEdge} case 2: // {SelEdge, SelExternalEdge} case 3: // {SelExternalEdge, SelEdge} case 4: // {SelExternalEdge, SelExternalEdge} { GeoId1 = selSeq.at(0).GeoId; GeoId2 = selSeq.at(1).GeoId; const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); if (geom1->getTypeId() == Part::GeomLineSegment::getClassTypeId() && geom2->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { const Part::GeomLineSegment *lineSeg1 = static_cast(geom1); const Part::GeomLineSegment *lineSeg2 = static_cast(geom2); // find the two closest line ends Sketcher::PointPos PosId1 = Sketcher::none; Sketcher::PointPos PosId2 = Sketcher::none; Base::Vector3d p1[2], p2[2]; p1[0] = lineSeg1->getStartPoint(); p1[1] = lineSeg1->getEndPoint(); p2[0] = lineSeg2->getStartPoint(); p2[1] = lineSeg2->getEndPoint(); // Get the intersection point in 2d of the two lines if possible Base::Line2d line1(Base::Vector2d(p1[0].x, p1[0].y), Base::Vector2d(p1[1].x, p1[1].y)); Base::Line2d line2(Base::Vector2d(p2[0].x, p2[0].y), Base::Vector2d(p2[1].x, p2[1].y)); Base::Vector2d s; if (line1.Intersect(line2, s)) { // get the end points of the line segments that are closest to the intersection point Base::Vector3d s3d(s.x, s.y, p1[0].z); if (Base::DistanceP2(s3d, p1[0]) < Base::DistanceP2(s3d, p1[1])) PosId1 = Sketcher::start; else PosId1 = Sketcher::end; if (Base::DistanceP2(s3d, p2[0]) < Base::DistanceP2(s3d, p2[1])) PosId2 = Sketcher::start; else PosId2 = Sketcher::end; } else { // if all points are collinear double length = DBL_MAX; for (int i=0; i <= 1; i++) { for (int j=0; j <= 1; j++) { double tmp = Base::DistanceP2(p2[j], p1[i]); if (tmp < length) { length = tmp; PosId1 = i ? Sketcher::end : Sketcher::start; PosId2 = j ? Sketcher::end : Sketcher::start; } } } } Base::Vector3d dir1 = ((PosId1 == Sketcher::start) ? 1. : -1.) * (lineSeg1->getEndPoint()-lineSeg1->getStartPoint()); Base::Vector3d dir2 = ((PosId2 == Sketcher::start) ? 1. : -1.) * (lineSeg2->getEndPoint()-lineSeg2->getStartPoint()); // check if the two lines are parallel, in this case an angle is not possible Base::Vector3d dir3 = dir1 % dir2; if (dir3.Length() < Precision::Intersection()) { Base::Vector3d dist = (p1[0] - p2[0]) % dir1; if (dist.Sqr() > Precision::Intersection()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Parallel lines"), QObject::tr("An angle constraint cannot be set for two parallel lines.")); return; } } double ActAngle = atan2(dir1.x*dir2.y-dir1.y*dir2.x, dir1.y*dir2.y+dir1.x*dir2.x); if (ActAngle < 0) { ActAngle *= -1; std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); } openCommand("Add angle constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Angle',%d,%d,%d,%d,%f)) ", Obj->getNameInDocument(),GeoId1,PosId1,GeoId2,PosId2,ActAngle); if (areBothPointsOrSegmentsFixed(Obj,GeoId1, GeoId2) || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", Obj->getNameInDocument(),ConStr.size()-1,"False"); finishDistanceConstraint(this, Obj,false); } else finishDistanceConstraint(this, Obj,true); return; } return; } case 5: // {SelEdge, SelVertexOrRoot, SelEdgeOrAxis} case 6: // {SelEdgeOrAxis, SelVertexOrRoot, SelEdge} case 7: // {SelEdge, SelVertexOrRoot, SelExternalEdge} case 8: // {SelExternalEdge, SelVertexOrRoot, SelEdge} case 9: // {SelExternalEdge, SelVertexOrRoot, SelExternalEdge} { GeoId1 = selSeq.at(0).GeoId; GeoId2 = selSeq.at(2).GeoId; GeoId3 = selSeq.at(1).GeoId; PosId3 = selSeq.at(1).PosId; break; } case 10: // {SelVertexOrRoot, SelEdge, SelEdgeOrAxis} case 11: // {SelVertexOrRoot, SelEdgeOrAxis, SelEdge} case 12: // {SelVertexOrRoot, SelEdge, SelExternalEdge} case 13: // {SelVertexOrRoot, SelExternalEdge, SelEdge} case 14: // {SelVertexOrRoot, SelExternalEdge, SelExternalEdge} { GeoId1 = selSeq.at(1).GeoId; GeoId2 = selSeq.at(2).GeoId; GeoId3 = selSeq.at(0).GeoId; PosId3 = selSeq.at(0).PosId; break; } } bool bothexternal=areBothPointsOrSegmentsFixed(Obj,GeoId1, GeoId2); if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) { double ActAngle = 0.0; openCommand("Add angle constraint"); //add missing point-on-object constraints if(! IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)){ Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoId3,PosId3,GeoId1); }; if(! IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)){ Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoId3,PosId3,GeoId2); }; if(! IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)){//FIXME: it's a good idea to add a check if the sketch is solved Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", Obj->getNameInDocument(),GeoId3,PosId3,GeoId1); }; //assuming point-on-curves have been solved, calculate the angle. //DeepSOIC: this may be slow, but I wanted to reuse the conversion from Geometry to GCS shapes that is done in Sketch Base::Vector3d p = Obj->getPoint(GeoId3, PosId3 ); ActAngle = Obj->calculateAngleViaPoint(GeoId1,GeoId2,p.x,p.y); //negative constraint value avoidance if (ActAngle < -Precision::Angular()){ std::swap(GeoId1, GeoId2); std::swap(PosId1, PosId2); ActAngle = -ActAngle; } Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('AngleViaPoint',%d,%d,%d,%d,%f)) ", Obj->getNameInDocument(),GeoId1,GeoId2,GeoId3,PosId3,ActAngle); if (bothexternal || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", Obj->getNameInDocument(),ConStr.size()-1,"False"); finishDistanceConstraint(this, Obj,false); } else finishDistanceConstraint(this, Obj,true); return; }; } void CmdSketcherConstrainAngle::updateAction(int mode) { switch (mode) { case Reference: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().pixmap("Constraint_InternalAngle_Driven")); break; case Driving: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().pixmap("Constraint_InternalAngle")); break; } } // ====================================================================================== class CmdSketcherConstrainEqual : public CmdSketcherConstraint { public: CmdSketcherConstrainEqual(); virtual ~CmdSketcherConstrainEqual(){} virtual const char* className() const { return "CmdSketcherConstrainEqual"; } protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); }; CmdSketcherConstrainEqual::CmdSketcherConstrainEqual() :CmdSketcherConstraint("Sketcher_ConstrainEqual") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Constrain equal"); sToolTipText = QT_TR_NOOP("Create an equality constraint between two lines or between circles and arcs"); sWhatsThis = "Sketcher_ConstrainEqual"; sStatusTip = sToolTipText; sPixmap = "Constraint_EqualLength"; sAccel = "E"; eType = ForEdit; allowedSelSequences = {{SelEdge, SelEdge}, {SelEdge, SelExternalEdge}, {SelExternalEdge, SelEdge}}; // Only option for equal constraint constraintCursor = cursor_genericconstraint; } void CmdSketcherConstrainEqual::activated(int iMsg) { Q_UNUSED(iMsg); // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true); if (constraintMode) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(constraintCursor, this)); getSelection().clearSelection(); } else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select two edges from the sketch.")); } return; } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); // go through the selected subelements if (SubNames.size() < 2) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select at least two lines from the sketch.")); return; } std::vector ids; bool lineSel = false, arcSel = false, circSel = false, ellipsSel = false, arcEllipsSel=false, hasAlreadyExternal = false; for (std::vector::const_iterator it=SubNames.begin(); it != SubNames.end(); ++it) { int GeoId; Sketcher::PointPos PosId; getIdsFromName(*it, Obj, GeoId, PosId); if (!isEdge(GeoId,PosId)) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select two or more compatible edges")); return; } else if (isPointOrSegmentFixed(Obj,GeoId)) { if (GeoId == Sketcher::GeoEnum::HAxis || GeoId == Sketcher::GeoEnum::VAxis) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Sketch axes cannot be used in equality constraints")); return; } else if (hasAlreadyExternal) { showNoConstraintBetweenFixedGeometry(); return; } 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 B-spline edge currently unsupported.")); return; } if (geo->getTypeId() != Part::GeomLineSegment::getClassTypeId()) lineSel = true; else if (geo->getTypeId() != Part::GeomArcOfCircle::getClassTypeId()) arcSel = true; else if (geo->getTypeId() != Part::GeomCircle::getClassTypeId()) circSel = true; else if (geo->getTypeId() != Part::GeomEllipse::getClassTypeId()) ellipsSel = true; else if (geo->getTypeId() != Part::GeomArcOfEllipse::getClassTypeId()) arcEllipsSel = true; else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select two or more edges of similar type")); return; } ids.push_back(GeoId); } if (lineSel && (arcSel || circSel) && (ellipsSel || arcEllipsSel)) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select two or more edges of similar type")); return; } // undo command open openCommand("add equality constraint"); for (int i=0; i < int(ids.size()-1); i++) { Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Equal',%d,%d)) ", selection[0].getFeatName(),ids[i],ids[i+1]); } // finish the transaction and update commitCommand(); tryAutoRecompute(); // clear the selection (convenience) getSelection().clearSelection(); } void CmdSketcherConstrainEqual::applyConstraint(std::vector &selSeq, int seqIndex) { SketcherGui::ViewProviderSketch* sketchgui = static_cast(getActiveGuiDocument()->getInEdit()); Sketcher::SketchObject* Obj = sketchgui->getSketchObject(); QString strError; int GeoId1 = Constraint::GeoUndef, GeoId2 = Constraint::GeoUndef; switch (seqIndex) { case 0: // {SelEdge, SelEdge} case 1: // {SelEdge, SelExternalEdge} case 2: // {SelExternalEdge, SelEdge} { GeoId1 = selSeq.at(0).GeoId; GeoId2 = selSeq.at(1).GeoId; // check if the edge has already a Block constraint if ( areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2) ) { showNoConstraintBetweenFixedGeometry(); return; } // undo command open openCommand("add equality constraint"); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Equal',%d,%d)) ", Obj->getNameInDocument(), GeoId1, GeoId2); // finish the transaction and update commitCommand(); tryAutoRecompute(); return; } default: break; } } // ====================================================================================== class CmdSketcherConstrainSymmetric : public CmdSketcherConstraint { public: CmdSketcherConstrainSymmetric(); virtual ~CmdSketcherConstrainSymmetric(){} virtual const char* className() const { return "CmdSketcherConstrainSymmetric"; } protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); }; CmdSketcherConstrainSymmetric::CmdSketcherConstrainSymmetric() :CmdSketcherConstraint("Sketcher_ConstrainSymmetric") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Constrain symmetrical"); sToolTipText = QT_TR_NOOP("Create a symmetry constraint between two points with respect to a line or a third point"); sWhatsThis = "Sketcher_ConstrainSymmetric"; sStatusTip = sToolTipText; sPixmap = "Constraint_Symmetric"; sAccel = "S"; eType = ForEdit; allowedSelSequences = {{SelEdge, SelVertexOrRoot}, {SelExternalEdge, SelVertex}, {SelVertex, SelEdge, SelVertexOrRoot}, {SelRoot, SelEdge, SelVertex}, {SelVertex, SelExternalEdge, SelVertexOrRoot}, {SelRoot, SelExternalEdge, SelVertex}, {SelVertex, SelEdgeOrAxis, SelVertex}, {SelVertex, SelVertexOrRoot, SelVertex}, {SelVertex, SelVertex, SelVertexOrRoot}, {SelVertexOrRoot, SelVertex, SelVertex}}; constraintCursor = cursor_genericconstraint; } void CmdSketcherConstrainSymmetric::activated(int iMsg) { Q_UNUSED(iMsg); // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true); if (constraintMode) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(constraintCursor, this)); getSelection().clearSelection(); } else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select two points and a symmetry line, two points and a symmetry point " "or a line and a symmetry point from the sketch.")); } return; } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); if (SubNames.size() != 3 && SubNames.size() != 2) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select two points and a symmetry line, two points and a symmetry point " "or a line and a symmetry point from the sketch.")); return; } int GeoId1, GeoId2, GeoId3; Sketcher::PointPos PosId1, PosId2, PosId3; getIdsFromName(SubNames[0], Obj, GeoId1, PosId1); getIdsFromName(SubNames[1], Obj, GeoId2, PosId2); if (SubNames.size() == 2) { if ( areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2) ) { showNoConstraintBetweenFixedGeometry(); return; } if (isVertex(GeoId1,PosId1) && isEdge(GeoId2,PosId2)) { std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); } if (isEdge(GeoId1,PosId1) && isVertex(GeoId2,PosId2)) { const Part::Geometry *geom = Obj->getGeometry(GeoId1); if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { if (GeoId1 == GeoId2) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Cannot add a symmetry constraint between a line and its end points!")); return; } // undo command open openCommand("add symmetric constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d,%d)) ", selection[0].getFeatName(),GeoId1,Sketcher::start,GeoId1,Sketcher::end,GeoId2,PosId2); // finish the transaction and update commitCommand(); tryAutoRecompute(); // clear the selection (convenience) getSelection().clearSelection(); return; } } QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select two points and a symmetry line, two points and a symmetry point " "or a line and a symmetry point from the sketch.")); return; } getIdsFromName(SubNames[2], Obj, GeoId3, PosId3); if (isEdge(GeoId1,PosId1) && isVertex(GeoId3,PosId3)) { std::swap(GeoId1,GeoId3); std::swap(PosId1,PosId3); } else if (isEdge(GeoId2,PosId2) && isVertex(GeoId3,PosId3)) { std::swap(GeoId2,GeoId3); std::swap(PosId2,PosId3); } if ( areAllPointsOrSegmentsFixed(Obj, GeoId1, GeoId2, GeoId3) ) { showNoConstraintBetweenFixedGeometry(); return; } if (isVertex(GeoId1,PosId1) && isVertex(GeoId2,PosId2)) { if (isEdge(GeoId3,PosId3)) { const Part::Geometry *geom = Obj->getGeometry(GeoId3); if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { if (GeoId1 == GeoId2 && GeoId2 == GeoId3) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Cannot add a symmetry constraint between a line and its end points!")); return; } // undo command open openCommand("add symmetric constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d)) ", selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2,GeoId3); // finish the transaction and update commitCommand(); tryAutoRecompute(); // clear the selection (convenience) getSelection().clearSelection(); return; } } else if (isVertex(GeoId3,PosId3)) { // undo command open openCommand("add symmetric constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d,%d)) ", selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2,GeoId3,PosId3); // finish the transaction and update commitCommand(); tryAutoRecompute(); // clear the selection (convenience) getSelection().clearSelection(); return; } } QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select two points and a symmetry line, two points and a symmetry point " "or a line and a symmetry point from the sketch.")); } void CmdSketcherConstrainSymmetric::applyConstraint(std::vector &selSeq, int seqIndex) { SketcherGui::ViewProviderSketch* sketchgui = static_cast(getActiveGuiDocument()->getInEdit()); Sketcher::SketchObject* Obj = sketchgui->getSketchObject(); QString strError; int GeoId1 = Constraint::GeoUndef, GeoId2 = Constraint::GeoUndef, GeoId3 = Constraint::GeoUndef; Sketcher::PointPos PosId1 = Sketcher::none, PosId2 = Sketcher::none, PosId3 = Sketcher::none; switch (seqIndex) { case 0: // {SelEdge, SelVertexOrRoot} case 1: // {SelExternalEdge, SelVertex} { GeoId1 = GeoId2 = selSeq.at(0).GeoId; GeoId3 = selSeq.at(1).GeoId; PosId1 = Sketcher::start; PosId2 = Sketcher::end; PosId3 = selSeq.at(1).PosId; if (GeoId1 == GeoId3) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Cannot add a symmetry constraint between a line and its end points!")); return; } if ( areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2) ) { showNoConstraintBetweenFixedGeometry(); return; } break; } case 2: // {SelVertex, SelEdge, SelVertexOrRoot} case 3: // {SelRoot, SelEdge, SelVertex} case 4: // {SelVertex, SelExternalEdge, SelVertexOrRoot} case 5: // {SelRoot, SelExternalEdge, SelVertex} case 6: // {SelVertex, SelEdgeOrAxis, SelVertex} { GeoId1 = selSeq.at(0).GeoId; GeoId2 = selSeq.at(2).GeoId; GeoId3 = selSeq.at(1).GeoId; PosId1 = selSeq.at(0).PosId; PosId2 = selSeq.at(2).PosId; if ( areAllPointsOrSegmentsFixed(Obj, GeoId1, GeoId2, GeoId3) ) { showNoConstraintBetweenFixedGeometry(); return; } const Part::Geometry *geom = Obj->getGeometry(GeoId3); if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { if (GeoId1 == GeoId2 && GeoId2 == GeoId3) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Cannot add a symmetry constraint between a line and its end points!")); return; } // undo command open openCommand("add symmetric constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d)) ", Obj->getNameInDocument(),GeoId1,PosId1,GeoId2,PosId2,GeoId3); // finish the transaction and update commitCommand(); tryAutoRecompute(); } else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select two points and a symmetry line, two points and a symmetry point " "or a line and a symmetry point from the sketch.")); } return; } case 7: // {SelVertex, SelVertexOrRoot, SelVertex} case 8: // {SelVertex, SelVertex, SelVertexOrRoot} case 9: // {SelVertexOrRoot, SelVertex, SelVertex} { GeoId1 = selSeq.at(0).GeoId; GeoId2 = selSeq.at(2).GeoId; GeoId3 = selSeq.at(1).GeoId; PosId1 = selSeq.at(0).PosId; PosId2 = selSeq.at(2).PosId; PosId3 = selSeq.at(1).PosId; if ( areAllPointsOrSegmentsFixed(Obj, GeoId1, GeoId2, GeoId3) ) { showNoConstraintBetweenFixedGeometry(); return; } break; } default: break; } // undo command open openCommand("add symmetric constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d,%d)) ", Obj->getNameInDocument(),GeoId1,PosId1,GeoId2,PosId2,GeoId3,PosId3); // finish the transaction and update commitCommand(); tryAutoRecompute(); // clear the selection (convenience) getSelection().clearSelection(); return; } // ====================================================================================== DEF_STD_CMD_A(CmdSketcherConstrainSnellsLaw); CmdSketcherConstrainSnellsLaw::CmdSketcherConstrainSnellsLaw() :Command("Sketcher_ConstrainSnellsLaw") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Constrain refraction (Snell's law')"); sToolTipText = QT_TR_NOOP("Create a refraction law (Snell's law) constraint between two endpoints of rays and an edge as an interface."); sWhatsThis = "Sketcher_ConstrainSnellsLaw"; sStatusTip = sToolTipText; sPixmap = "Constraint_SnellsLaw"; sAccel = ""; eType = ForEdit; } void CmdSketcherConstrainSnellsLaw::activated(int iMsg) { Q_UNUSED(iMsg); QString strHelp = QObject::tr("Select two endpoints of lines to act as rays, and" " an edge representing a boundary. The first" " selected point corresponds to index n1, second" " - to n2, and datum value sets the ratio n2/n1.", "Constraint_SnellsLaw"); QString strError; const char dmbg[] = "Constraint_SnellsLaw"; try{ // get the selection std::vector selection = getSelection().getSelectionEx(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { strError = QObject::tr("Selected objects are not just geometry from one sketch.", dmbg); throw(Base::Exception("")); } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); if (SubNames.size() != 3) { strError = QObject::tr("Number of selected objects is not 3 (is %1).", dmbg).arg(SubNames.size()); throw(Base::Exception("")); } int GeoId1, GeoId2, GeoId3; Sketcher::PointPos PosId1, PosId2, PosId3; getIdsFromName(SubNames[0], Obj, GeoId1, PosId1); getIdsFromName(SubNames[1], Obj, GeoId2, PosId2); getIdsFromName(SubNames[2], Obj, GeoId3, PosId3); //sink the edge to be the last item if (isEdge(GeoId1,PosId1) ) { std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); } if (isEdge(GeoId2,PosId2) ) { std::swap(GeoId2,GeoId3); std::swap(PosId2,PosId3); } //a bunch of validity checks if (areAllPointsOrSegmentsFixed(Obj, GeoId1, GeoId2, GeoId3) ) { strError = QObject::tr("Can not create constraint with external geometry only!!", dmbg); throw(Base::Exception("")); } if (!(isVertex(GeoId1,PosId1) && !isSimpleVertex(Obj, GeoId1, PosId1) && isVertex(GeoId2,PosId2) && !isSimpleVertex(Obj, GeoId2, PosId2) && isEdge(GeoId3,PosId3) )) { 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 B-spline at any point implemented. QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("SnellsLaw on B-spline edge currently unsupported.")); return; } double n2divn1=0; //the essence. //Unlike other constraints, we'll ask for a value immediately. QDialog dlg(Gui::getMainWindow()); Ui::InsertDatum ui_Datum; ui_Datum.setupUi(&dlg); dlg.setWindowTitle(EditDatumDialog::tr("Refractive index ratio", dmbg)); ui_Datum.label->setText(EditDatumDialog::tr("Ratio n2/n1:", dmbg)); Base::Quantity init_val; init_val.setUnit(Base::Unit()); init_val.setValue(0.0); ui_Datum.labelEdit->setValue(init_val); ui_Datum.labelEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/SketcherRefrIndexRatio")); ui_Datum.labelEdit->setToLastUsedValue(); ui_Datum.labelEdit->selectNumber(); // Unable to bind, because the constraint does not yet exist if (dlg.exec() != QDialog::Accepted) return; ui_Datum.labelEdit->pushToHistory(); Base::Quantity newQuant = ui_Datum.labelEdit->value(); n2divn1 = newQuant.getValue(); //add constraint openCommand("add Snell's law constraint"); if (! IsPointAlreadyOnCurve(GeoId2,GeoId1,PosId1,Obj)) Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Coincident',%d,%d,%d,%d)) ", selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2); if (! IsPointAlreadyOnCurve(GeoId3,GeoId1,PosId1,Obj)) Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", selection[0].getFeatName(),GeoId1,PosId1,GeoId3); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('SnellsLaw',%d,%d,%d,%d,%d,%.12f)) ", selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2,GeoId3,n2divn1); /*if (allexternal || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", selection[0].getFeatName(),ConStr.size()-1,"False"); }*/ commitCommand(); tryAutoRecompute(); // clear the selection (convenience) getSelection().clearSelection(); } catch (Base::Exception &e) { if (strError.isEmpty()) strError = QString::fromLatin1(e.what()); if (!strError.isEmpty()) strError.append(QString::fromLatin1("\n\n")); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Error"), strError + strHelp); } } bool CmdSketcherConstrainSnellsLaw::isActive(void) { return isCreateConstraintActive( getActiveGuiDocument() ); } // ====================================================================================== DEF_STD_CMD_A(CmdSketcherConstrainInternalAlignment); CmdSketcherConstrainInternalAlignment::CmdSketcherConstrainInternalAlignment() :Command("Sketcher_ConstrainInternalAlignment") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Constrain InternalAlignment"); sToolTipText = QT_TR_NOOP("Constrains an element to be aligned with the internal geometry of another element"); sWhatsThis = sToolTipText; sStatusTip = sToolTipText; sPixmap = "Constraint_InternalAlignment"; sAccel = "Ctrl+A"; eType = ForEdit; } void CmdSketcherConstrainInternalAlignment::activated(int iMsg) { Q_UNUSED(iMsg); // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select at least one ellipse and one edge from the sketch.")); return; } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); // go through the selected subelements if (SubNames.size() < 2) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select at least one ellipse and one edge from the sketch.")); return; } std::vector pointids; std::vector lineids; std::vector ellipseids; std::vector arcsofellipseids; bool hasAlreadyExternal = false; for (std::vector::const_iterator it=SubNames.begin(); it != SubNames.end(); ++it) { int GeoId; Sketcher::PointPos PosId; getIdsFromName(*it, Obj, GeoId, PosId); if (isPointOrSegmentFixed(Obj,GeoId)) { if (GeoId == Sketcher::GeoEnum::HAxis || GeoId == Sketcher::GeoEnum::VAxis) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Sketch axes cannot be used in internal alignment constraint")); return; } else if (hasAlreadyExternal) { showNoConstraintBetweenExternal(); return; } else hasAlreadyExternal = true; } const Part::Geometry *geo = Obj->getGeometry(GeoId); if (geo->getTypeId() == Part::GeomPoint::getClassTypeId()) pointids.push_back(GeoId); else if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) lineids.push_back(GeoId); else if (geo->getTypeId() == Part::GeomEllipse::getClassTypeId()) ellipseids.push_back(GeoId); else if (geo->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) arcsofellipseids.push_back(GeoId); else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select two or more compatible edges")); return; } } int GeoId; Sketcher::PointPos PosId; getIdsFromName(SubNames[SubNames.size()-1], Obj, GeoId, PosId); // last selected element const Part::Geometry *geo = Obj->getGeometry(GeoId); // Currently it is only supported for ellipses if(geo->getTypeId() == Part::GeomEllipse::getClassTypeId()) { // Priority list // EllipseMajorDiameter = 1, // EllipseMinorDiameter = 2, // EllipseFocus1 = 3, // EllipseFocus2 = 4 if(ellipseids.size()>1){ QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("You can not internally constraint an ellipse on other ellipse. Select only one ellipse.")); return; } if (pointids.size()>2) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Maximum 2 points are supported.")); return; } if (lineids.size()>2) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Maximum 2 lines are supported.")); return; } // look for which internal constraints are already applied bool major=false; bool minor=false; bool focus1=false; bool focus2=false; bool extra_elements=false; const std::vector< Sketcher::Constraint * > &vals = Obj->Constraints.getValues(); 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::EllipseMajorDiameter: major=true; break; case Sketcher::EllipseMinorDiameter: minor=true; break; case Sketcher::EllipseFocus1: focus1=true; break; case Sketcher::EllipseFocus2: focus2=true; break; default: break; } } } if(major && minor && focus1 && focus2) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Nothing to constraint"), QObject::tr("Currently all internal geometry of the ellipse is already exposed.")); return; } if((!(focus1 && focus2) && pointids.size()>=1) || // if some element is missing and we are adding an element of that type (!(major && minor) && lineids.size()>=1) ){ openCommand("add internal alignment constraint"); if(pointids.size()>=1) { if(!focus1) { Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('InternalAlignment:EllipseFocus1',%d,%d,%d)) ", selection[0].getFeatName(),pointids[0],Sketcher::start,ellipseids[0]); } else if(!focus2) { Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('InternalAlignment:EllipseFocus2',%d,%d,%d)) ", selection[0].getFeatName(),pointids[0],Sketcher::start,ellipseids[0]); focus2=true; } else extra_elements=true; } if(pointids.size()==2) { if(!focus2) { Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('InternalAlignment:EllipseFocus2',%d,%d,%d)) ", selection[0].getFeatName(),pointids[1],Sketcher::start,ellipseids[0]); } else extra_elements=true; } if(lineids.size()>=1) { if(!major) { Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('InternalAlignment:EllipseMajorDiameter',%d,%d)) ", selection[0].getFeatName(),lineids[0],ellipseids[0]); const Part::GeomLineSegment *geo = static_cast(Obj->getGeometry(lineids[0])); if(!geo->Construction) Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.toggleConstruction(%d) ",selection[0].getFeatName(),lineids[0]); } else if(!minor) { Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('InternalAlignment:EllipseMinorDiameter',%d,%d)) ", selection[0].getFeatName(),lineids[0],ellipseids[0]); const Part::GeomLineSegment *geo = static_cast(Obj->getGeometry(lineids[0])); if(!geo->Construction) Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.toggleConstruction(%d) ",selection[0].getFeatName(),lineids[0]); minor=true; } else extra_elements=true; } if(lineids.size()==2) { if(!minor){ Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('InternalAlignment:EllipseMinorDiameter',%d,%d)) ", selection[0].getFeatName(),lineids[1],ellipseids[0]); const Part::GeomLineSegment *geo = static_cast(Obj->getGeometry(lineids[1])); if(!geo->Construction) Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.toggleConstruction(%d) ",selection[0].getFeatName(),lineids[1]); } else extra_elements=true; } // finish the transaction and update commitCommand(); tryAutoRecompute(); if(extra_elements){ QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Extra elements"), QObject::tr("More elements than possible for the given ellipse were provided. These were ignored.")); } // clear the selection (convenience) getSelection().clearSelection(); } else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Extra elements"), QObject::tr("More elements than possible for the given ellipse were provided. These were ignored.")); } } else if(geo->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) { // Priority list // EllipseMajorDiameter = 1, // EllipseMinorDiameter = 2, // EllipseFocus1 = 3, // EllipseFocus2 = 4 if(arcsofellipseids.size()>1){ QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("You can not internally constraint an arc of ellipse on other arc of ellipse. Select only one arc of ellipse.")); return; } if(ellipseids.size()>0){ QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("You can not internally constraint an ellipse on an arc of ellipse. Select only one ellipse or arc of ellipse.")); return; } if (pointids.size()>2) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Maximum 2 points are supported.")); return; } if (lineids.size()>2) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Maximum 2 lines are supported.")); return; } // look for which internal constraints are already applied bool major=false; bool minor=false; bool focus1=false; bool focus2=false; bool extra_elements=false; const std::vector< Sketcher::Constraint * > &vals = Obj->Constraints.getValues(); for (std::vector< Sketcher::Constraint * >::const_iterator it= vals.begin(); it != vals.end(); ++it) { if((*it)->Type == Sketcher::InternalAlignment && (*it)->First == GeoId) { switch((*it)->AlignmentType){ case Sketcher::EllipseMajorDiameter: major=true; break; case Sketcher::EllipseMinorDiameter: minor=true; break; case Sketcher::EllipseFocus1: focus1=true; break; case Sketcher::EllipseFocus2: focus2=true; break; default: break; } } } if(major && minor && focus1 && focus2) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Nothing to constraint"), QObject::tr("Currently all internal geometry of the arc of ellipse is already exposed.")); return; } if((!(focus1 && focus2) && pointids.size()>=1) || // if some element is missing and we are adding an element of that type (!(major && minor) && lineids.size()>=1) ){ openCommand("add internal alignment constraint"); if(pointids.size()>=1) { if(!focus1) { Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('InternalAlignment:EllipseFocus1',%d,%d,%d)) ", selection[0].getFeatName(),pointids[0],Sketcher::start,arcsofellipseids[0]); } else if(!focus2) { Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('InternalAlignment:EllipseFocus2',%d,%d,%d)) ", selection[0].getFeatName(),pointids[0],Sketcher::start,arcsofellipseids[0]); focus2=true; } else extra_elements=true; } if(pointids.size()==2) { if(!focus2) { Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('InternalAlignment:EllipseFocus2',%d,%d,%d)) ", selection[0].getFeatName(),pointids[1],Sketcher::start,arcsofellipseids[0]); } else extra_elements=true; } if(lineids.size()>=1) { if(!major) { Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('InternalAlignment:EllipseMajorDiameter',%d,%d)) ", selection[0].getFeatName(),lineids[0],arcsofellipseids[0]); const Part::GeomLineSegment *geo = static_cast(Obj->getGeometry(lineids[0])); if(!geo->Construction) Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.toggleConstruction(%d) ",selection[0].getFeatName(),lineids[0]); } else if(!minor) { Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('InternalAlignment:EllipseMinorDiameter',%d,%d)) ", selection[0].getFeatName(),lineids[0],arcsofellipseids[0]); const Part::GeomLineSegment *geo = static_cast(Obj->getGeometry(lineids[0])); if(!geo->Construction) Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.toggleConstruction(%d) ",selection[0].getFeatName(),lineids[0]); minor=true; } else extra_elements=true; } if(lineids.size()==2) { if(!minor){ Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('InternalAlignment:EllipseMinorDiameter',%d,%d)) ", selection[0].getFeatName(),lineids[1],arcsofellipseids[0]); const Part::GeomLineSegment *geo = static_cast(Obj->getGeometry(lineids[1])); if(!geo->Construction) Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.toggleConstruction(%d) ",selection[0].getFeatName(),lineids[1]); } else extra_elements=true; } // finish the transaction and update commitCommand(); tryAutoRecompute(); if(extra_elements){ QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Extra elements"), QObject::tr("More elements than possible for the given ellipse were provided. These were ignored.")); } // clear the selection (convenience) getSelection().clearSelection(); } else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Extra elements"), QObject::tr("More elements than possible for the given arc of ellipse were provided. These were ignored.")); } } else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Currently internal geometry is only supported for ellipse or arc of ellipse. The last selected element must be an ellipse or an arc of ellipse.")); } } bool CmdSketcherConstrainInternalAlignment::isActive(void) { return isCreateConstraintActive( getActiveGuiDocument() ); } // ====================================================================================== /*** Creation Mode / Toggle to or from Reference ***/ DEF_STD_CMD_A(CmdSketcherToggleDrivingConstraint); CmdSketcherToggleDrivingConstraint::CmdSketcherToggleDrivingConstraint() :Command("Sketcher_ToggleDrivingConstraint") { sAppModule = "Sketcher"; sGroup = QT_TR_NOOP("Sketcher"); sMenuText = QT_TR_NOOP("Toggle reference/driving constraint"); sToolTipText = QT_TR_NOOP("Toggles the toolbar or selected constraints to/from reference mode"); sWhatsThis = "Sketcher_ToggleDrivingConstraint"; sStatusTip = sToolTipText; sPixmap = "Sketcher_ToggleConstraint"; sAccel = ""; eType = ForEdit; // list of toggle driving constraint commands Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainLock"); rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainDistance"); rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainDistanceX"); rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainDistanceY"); rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainRadius"); rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainAngle"); //rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainSnellsLaw"); } void CmdSketcherToggleDrivingConstraint::activated(int iMsg) { Q_UNUSED(iMsg); bool modeChange=true; std::vector selection; if (Gui::Selection().countObjectsOfType(Sketcher::SketchObject::getClassTypeId()) > 0){ // Now we check whether we have a constraint selected or not. // get the selection selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select constraint(s) from the sketch.")); return; } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); if (SubNames.empty()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select constraint(s) from the sketch.")); return; } for (std::vector::const_iterator it=SubNames.begin();it!=SubNames.end();++it){ // see if we have constraints, if we do it is not a mode change, but a toggle. if (it->size() > 10 && it->substr(0,10) == "Constraint") modeChange=false; } } if (modeChange) { // Here starts the code for mode change Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); if (constraintCreationMode == Driving) { constraintCreationMode = Reference; } else { constraintCreationMode = Driving; } rcCmdMgr.updateCommands("ToggleDrivingConstraint", static_cast(constraintCreationMode)); } else // toggle the selected constraint(s) { // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); if (SubNames.empty()) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select constraint(s) from the sketch.")); return; } // undo command open openCommand("Toggle driving from/to non-driving"); int successful=SubNames.size(); // go through the selected subelements for (std::vector::const_iterator it=SubNames.begin();it!=SubNames.end();++it){ // only handle constraints if (it->size() > 10 && it->substr(0,10) == "Constraint") { int ConstrId = Sketcher::PropertyConstraintList::getIndexFromConstraintName(*it); try { // issue the actual commands to toggle doCommand(Doc,"App.ActiveDocument.%s.toggleDriving(%d) ",selection[0].getFeatName(),ConstrId); } catch(const Base::Exception&) { successful--; } } } if (successful > 0) commitCommand(); else abortCommand(); tryAutoRecompute(); // clear the selection (convenience) getSelection().clearSelection(); } } bool CmdSketcherToggleDrivingConstraint::isActive(void) { return isCreateGeoActive( getActiveGuiDocument() ); } void CreateSketcherCommandsConstraints(void) { Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); rcCmdMgr.addCommand(new CmdSketcherConstrainHorizontal()); rcCmdMgr.addCommand(new CmdSketcherConstrainVertical()); rcCmdMgr.addCommand(new CmdSketcherConstrainLock()); rcCmdMgr.addCommand(new CmdSketcherConstrainBlock()); rcCmdMgr.addCommand(new CmdSketcherConstrainCoincident()); rcCmdMgr.addCommand(new CmdSketcherConstrainParallel()); rcCmdMgr.addCommand(new CmdSketcherConstrainPerpendicular()); rcCmdMgr.addCommand(new CmdSketcherConstrainTangent()); rcCmdMgr.addCommand(new CmdSketcherConstrainDistance()); rcCmdMgr.addCommand(new CmdSketcherConstrainDistanceX()); rcCmdMgr.addCommand(new CmdSketcherConstrainDistanceY()); rcCmdMgr.addCommand(new CmdSketcherConstrainRadius()); rcCmdMgr.addCommand(new CmdSketcherConstrainAngle()); rcCmdMgr.addCommand(new CmdSketcherConstrainEqual()); rcCmdMgr.addCommand(new CmdSketcherConstrainPointOnObject()); rcCmdMgr.addCommand(new CmdSketcherConstrainSymmetric()); rcCmdMgr.addCommand(new CmdSketcherConstrainSnellsLaw()); rcCmdMgr.addCommand(new CmdSketcherConstrainInternalAlignment()); rcCmdMgr.addCommand(new CmdSketcherToggleDrivingConstraint()); }