10142 lines
393 KiB
C++
10142 lines
393 KiB
C++
/***************************************************************************
|
|
* 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 <Precision.hxx>
|
|
#include <QPainter>
|
|
#include <cfloat>
|
|
#endif
|
|
|
|
#include <App/Application.h>
|
|
#include <Base/Tools.h>
|
|
#include <Base/Tools2D.h>
|
|
#include <Gui/Action.h>
|
|
#include <Gui/Application.h>
|
|
#include <Gui/BitmapFactory.h>
|
|
#include <Gui/CommandT.h>
|
|
#include <Gui/DlgCheckableMessageBox.h>
|
|
#include <Gui/Document.h>
|
|
#include <Gui/MainWindow.h>
|
|
#include <Gui/Notifications.h>
|
|
#include <Gui/Selection.h>
|
|
#include <Gui/SelectionFilter.h>
|
|
#include <Gui/SelectionObject.h>
|
|
#include <Mod/Sketcher/App/GeometryFacade.h>
|
|
#include <Mod/Sketcher/App/SketchObject.h>
|
|
#include <Mod/Sketcher/App/SolverGeometryExtension.h>
|
|
|
|
#include "CommandConstraints.h"
|
|
#include "DrawSketchHandler.h"
|
|
#include "EditDatumDialog.h"
|
|
#include "Utils.h"
|
|
#include "ViewProviderSketch.h"
|
|
#include "ui_InsertDatum.h"
|
|
#include <Inventor/events/SoKeyboardEvent.h>
|
|
|
|
// Remove this after pre-commit hook is activated
|
|
// clang-format off
|
|
using namespace std;
|
|
using namespace SketcherGui;
|
|
using namespace Sketcher;
|
|
|
|
/***** Creation Mode ************/
|
|
namespace SketcherGui
|
|
{
|
|
enum ConstraintCreationMode
|
|
{
|
|
Driving,
|
|
Reference
|
|
};
|
|
}
|
|
|
|
ConstraintCreationMode constraintCreationMode = Driving;
|
|
|
|
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<SketcherGui::ViewProviderSketch*>(doc->getInEdit())->getSketchMode()
|
|
== ViewProviderSketch::STATUS_NONE) {
|
|
if (Gui::Selection().countObjectsOfType(Sketcher::SketchObject::getClassTypeId())
|
|
> 0) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Utility method to avoid repeating the same code over and over again
|
|
void finishDatumConstraint(Gui::Command* cmd,
|
|
Sketcher::SketchObject* sketch,
|
|
bool isDriving = true,
|
|
unsigned int numberofconstraints = 1)
|
|
{
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
|
|
// Get the latest constraint
|
|
const std::vector<Sketcher::Constraint*>& ConStr = sketch->Constraints.getValues();
|
|
auto lastConstraintIndex = ConStr.size() - 1;
|
|
Sketcher::Constraint* constr = ConStr[lastConstraintIndex];
|
|
auto lastConstraintType = constr->Type;
|
|
|
|
// Guess some reasonable distance for placing the datum text
|
|
Gui::Document* doc = cmd->getActiveGuiDocument();
|
|
float scaleFactor = 1.0;
|
|
double labelPosition = 0.0;
|
|
float labelPositionRandomness = 0.0;
|
|
|
|
if (lastConstraintType == Radius || lastConstraintType == Diameter) {
|
|
labelPosition = hGrp->GetFloat("RadiusDiameterConstraintDisplayBaseAngle", 15.0)
|
|
* (M_PI / 180);// Get radius/diameter constraint display angle
|
|
labelPositionRandomness =
|
|
hGrp->GetFloat("RadiusDiameterConstraintDisplayAngleRandomness", 0.0)
|
|
* (M_PI / 180);// Get randomness
|
|
|
|
// Adds a random value around the base angle, so that possibly overlapping labels get likely
|
|
// a different position.
|
|
if (labelPositionRandomness != 0.0) {
|
|
labelPosition = labelPosition
|
|
+ labelPositionRandomness
|
|
* (static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX) - 0.5);
|
|
}
|
|
}
|
|
|
|
if (doc && doc->getInEdit()
|
|
&& doc->getInEdit()->isDerivedFrom(SketcherGui::ViewProviderSketch::getClassTypeId())) {
|
|
SketcherGui::ViewProviderSketch* vp =
|
|
static_cast<SketcherGui::ViewProviderSketch*>(doc->getInEdit());
|
|
scaleFactor = vp->getScaleFactor();
|
|
|
|
int firstConstraintIndex = lastConstraintIndex - numberofconstraints + 1;
|
|
|
|
for (int i = lastConstraintIndex; i >= firstConstraintIndex; i--) {
|
|
ConStr[i]->LabelDistance = 2. * scaleFactor;
|
|
|
|
if (lastConstraintType == Radius || lastConstraintType == Diameter) {
|
|
const Part::Geometry* geo = sketch->getGeometry(ConStr[i]->First);
|
|
|
|
if (geo && isCircle(*geo)) {
|
|
ConStr[i]->LabelPosition = labelPosition;
|
|
}
|
|
}
|
|
}
|
|
vp->draw(false, false);// Redraw
|
|
}
|
|
|
|
bool show = hGrp->GetBool("ShowDialogOnDistanceConstraint", true);
|
|
|
|
// Ask for the value of the distance immediately
|
|
if (show && isDriving) {
|
|
EditDatumDialog editDatumDialog(sketch, ConStr.size() - 1);
|
|
editDatumDialog.exec();
|
|
}
|
|
else {
|
|
// no dialog was shown so commit the command
|
|
cmd->commitCommand();
|
|
}
|
|
|
|
tryAutoRecompute(sketch);
|
|
cmd->getSelection().clearSelection();
|
|
}
|
|
|
|
void showNoConstraintBetweenExternal(const App::DocumentObject* obj)
|
|
{
|
|
Gui::TranslatedUserWarning(
|
|
obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Cannot add a constraint between two external geometries."));
|
|
}
|
|
|
|
void showNoConstraintBetweenFixedGeometry(const App::DocumentObject* obj)
|
|
{
|
|
Gui::TranslatedUserWarning(obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Cannot add a constraint between two fixed geometries. "
|
|
"Fixed geometries include external geometry, "
|
|
"blocked geometry, and special points "
|
|
"such as B-spline knot points."));
|
|
}
|
|
|
|
bool isGeoConcentricCompatible(const Part::Geometry* geo)
|
|
{
|
|
return (isEllipse(*geo) || isArcOfEllipse(*geo) || isCircle(*geo) || isArcOfCircle(*geo));
|
|
}
|
|
|
|
// Removes point-on-object constraints made redundant with certain constraints
|
|
// under certain conditions. Currently, that happens only when the constraint is on
|
|
// a B-spline, for 3-selection tangent, perpendicular, and angle constraints.
|
|
// Returns true if constraints were removed.
|
|
// GeoId3 HAS to be the point, and the other two are the curves.
|
|
bool removeRedundantPointOnObject(SketchObject* Obj, int GeoId1, int GeoId2, int GeoId3)
|
|
{
|
|
const std::vector<Constraint*>& cvals = Obj->Constraints.getValues();
|
|
|
|
std::vector<int> cidsToBeRemoved;
|
|
|
|
int cid = 0;
|
|
for (auto it = cvals.begin(); it != cvals.end(); ++it, ++cid) {
|
|
if ((*it)->Type == Sketcher::PointOnObject &&
|
|
(((*it)->First == GeoId3 && (*it)->Second == GeoId1) ||
|
|
((*it)->First == GeoId3 && (*it)->Second == GeoId2))) {
|
|
|
|
// ONLY do this if it is a B-spline (or any other where point
|
|
// on object is implied).
|
|
const Part::Geometry* geom = Obj->getGeometry((*it)->Second);
|
|
if (isBSplineCurve(*geom))
|
|
cidsToBeRemoved.push_back(cid);
|
|
}
|
|
}
|
|
|
|
if (!cidsToBeRemoved.empty()) {
|
|
for (auto it = cidsToBeRemoved.rbegin(); it != cidsToBeRemoved.rend(); ++it) {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"delConstraint(%d)",
|
|
*it);// remove the preexisting point on object constraint.
|
|
}
|
|
|
|
// A substitution requires a solve() so that the autoremove redundants works when
|
|
// Autorecompute not active. However, delConstraint includes such solve() internally. So
|
|
// at this point it is already solved.
|
|
tryAutoRecomputeIfNotSolve(Obj);
|
|
|
|
notifyConstraintSubstitutions(QObject::tr("One or two point on object constraint(s) was/were deleted, "
|
|
"since the latest constraint being applied internally applies point-on-object as well."));
|
|
|
|
// TODO: find way to get selection here, or clear elsewhere
|
|
// getSelection().clearSelection();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Makes an angle constraint between 2 lines
|
|
void SketcherGui::makeAngleBetweenTwoLines(Sketcher::SketchObject* Obj,
|
|
Gui::Command* cmd,
|
|
int geoId1,
|
|
int geoId2)
|
|
{
|
|
Sketcher::PointPos posId1 = Sketcher::PointPos::none;
|
|
Sketcher::PointPos posId2 = Sketcher::PointPos::none;
|
|
double actAngle;
|
|
|
|
if (!calculateAngle(Obj, geoId1, geoId2, posId1, posId2, actAngle)) {
|
|
return;
|
|
}
|
|
|
|
if (actAngle == 0.0) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Parallel lines"),
|
|
QObject::tr("An angle constraint cannot be set for two parallel lines."));
|
|
|
|
return;
|
|
}
|
|
|
|
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add angle constraint"));
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Angle',%d,%d,%d,%d,%f))",
|
|
geoId1,
|
|
static_cast<int>(posId1),
|
|
geoId2,
|
|
static_cast<int>(posId2),
|
|
actAngle);
|
|
|
|
if (areBothPointsOrSegmentsFixed(Obj, geoId1, geoId2)
|
|
|| constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
|
|
finishDatumConstraint(cmd, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(cmd, Obj, true);
|
|
}
|
|
}
|
|
|
|
bool SketcherGui::calculateAngle(Sketcher::SketchObject* Obj, int& GeoId1, int& GeoId2, Sketcher::PointPos& PosId1, Sketcher::PointPos& PosId2, double& ActAngle)
|
|
{
|
|
const Part::Geometry* geom1 = Obj->getGeometry(GeoId1);
|
|
const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
|
|
|
|
if (!(geom1->is<Part::GeomLineSegment>()) ||
|
|
!(geom2->is<Part::GeomLineSegment>())) {
|
|
return false;
|
|
}
|
|
|
|
const Part::GeomLineSegment* lineSeg1 = static_cast<const Part::GeomLineSegment*>(geom1);
|
|
const Part::GeomLineSegment* lineSeg2 = static_cast<const Part::GeomLineSegment*>(geom2);
|
|
|
|
// find the two closest line ends
|
|
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::PointPos::start;
|
|
else
|
|
PosId1 = Sketcher::PointPos::end;
|
|
if (Base::DistanceP2(s3d, p2[0]) < Base::DistanceP2(s3d, p2[1]))
|
|
PosId2 = Sketcher::PointPos::start;
|
|
else
|
|
PosId2 = Sketcher::PointPos::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::PointPos::end : Sketcher::PointPos::start;
|
|
PosId2 = j ? Sketcher::PointPos::end : Sketcher::PointPos::start;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Base::Vector3d dir1 = ((PosId1 == Sketcher::PointPos::start) ? 1. : -1.) *
|
|
(lineSeg1->getEndPoint() - lineSeg1->getStartPoint());
|
|
Base::Vector3d dir2 = ((PosId2 == Sketcher::PointPos::start) ? 1. : -1.) *
|
|
(lineSeg2->getEndPoint() - lineSeg2->getStartPoint());
|
|
|
|
// check if the two lines are parallel
|
|
Base::Vector3d dir3 = dir1 % dir2;
|
|
if (dir3.Length() < Precision::Intersection()) {
|
|
Base::Vector3d dist = (p1[0] - p2[0]) % dir1;
|
|
if (dist.Sqr() > Precision::Intersection()) {
|
|
ActAngle = 0.0;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Makes a simple tangency constraint using extra point + tangent via point
|
|
/// ellipse => an ellipse
|
|
/// geom2 => any of an ellipse, an arc of ellipse, a circle, or an arc (of circle)
|
|
/// geoId1 => geoid of the ellipse
|
|
/// geoId2 => geoid of geom2
|
|
/// 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(Sketcher::SketchObject* Obj,
|
|
const Part::GeomEllipse* ellipse,
|
|
const Part::Geometry* geom2,
|
|
int geoId1,
|
|
int geoId2)
|
|
{
|
|
|
|
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 (isEllipse(*geom2)) {
|
|
center2 = (static_cast<const Part::GeomEllipse*>(geom2))->getCenter();
|
|
}
|
|
else if (isArcOfEllipse(*geom2)) {
|
|
center2 = (static_cast<const Part::GeomArcOfEllipse*>(geom2))->getCenter();
|
|
}
|
|
else if (isCircle(*geom2)) {
|
|
center2 = (static_cast<const Part::GeomCircle*>(geom2))->getCenter();
|
|
}
|
|
else if (isArcOfCircle(*geom2)) {
|
|
center2 = (static_cast<const Part::GeomArcOfCircle*>(geom2))->getCenter();
|
|
}
|
|
|
|
Base::Vector3d direction = center2 - center;
|
|
double tapprox =
|
|
atan2(direction.y, direction.x) - phi;// we approximate the eccentric anomaly 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::cmdAppObjectArgs(Obj, "addGeometry(Part.Point(App.Vector(%f,%f,0)))", PoE.x, PoE.y);
|
|
int GeoIdPoint = Obj->getHighestCurveIndex();
|
|
|
|
// Point on first object
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoIdPoint,
|
|
static_cast<int>(Sketcher::PointPos::start),
|
|
geoId1);// constrain major axis
|
|
// Point on second object
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoIdPoint,
|
|
static_cast<int>(Sketcher::PointPos::start),
|
|
geoId2);// constrain major axis
|
|
// tangent via point
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d))",
|
|
geoId1,
|
|
geoId2,
|
|
GeoIdPoint,
|
|
static_cast<int>(Sketcher::PointPos::start));
|
|
}
|
|
catch (const Base::Exception& e) {
|
|
Gui::NotifyUserError(Obj,
|
|
QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"),
|
|
e.what());
|
|
Gui::Command::abortCommand();
|
|
|
|
tryAutoRecompute(Obj);
|
|
return;
|
|
}
|
|
|
|
Gui::Command::commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
|
|
/// Makes a simple tangency constraint using extra point + tangent via point
|
|
/// aoe => an arc of ellipse
|
|
/// geom2 => any of an arc of ellipse, a circle, or an arc (of circle)
|
|
/// geoId1 => geoid of the arc of ellipse
|
|
/// geoId2 => geoid of geom2
|
|
/// 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(Sketcher::SketchObject* Obj,
|
|
const Part::GeomArcOfEllipse* aoe,
|
|
const Part::Geometry* geom2,
|
|
int geoId1,
|
|
int geoId2)
|
|
{
|
|
|
|
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 (isArcOfEllipse(*geom2)) {
|
|
center2 = (static_cast<const Part::GeomArcOfEllipse*>(geom2))->getCenter();
|
|
}
|
|
else if (isCircle(*geom2)) {
|
|
center2 = (static_cast<const Part::GeomCircle*>(geom2))->getCenter();
|
|
}
|
|
else if (isArcOfCircle(*geom2)) {
|
|
center2 = (static_cast<const Part::GeomArcOfCircle*>(geom2))->getCenter();
|
|
}
|
|
|
|
Base::Vector3d direction = center2 - center;
|
|
double tapprox =
|
|
atan2(direction.y, direction.x) - phi;// we approximate the eccentric anomaly 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::cmdAppObjectArgs(Obj, "addGeometry(Part.Point(App.Vector(%f,%f,0)))", PoE.x, PoE.y);
|
|
int GeoIdPoint = Obj->getHighestCurveIndex();
|
|
|
|
// Point on first object
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoIdPoint,
|
|
static_cast<int>(Sketcher::PointPos::start),
|
|
geoId1);// constrain major axis
|
|
// Point on second object
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoIdPoint,
|
|
static_cast<int>(Sketcher::PointPos::start),
|
|
geoId2);// constrain major axis
|
|
// tangent via point
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d))",
|
|
geoId1,
|
|
geoId2,
|
|
GeoIdPoint,
|
|
static_cast<int>(Sketcher::PointPos::start));
|
|
}
|
|
catch (const Base::Exception& e) {
|
|
Gui::NotifyUserError(Obj,
|
|
QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"),
|
|
e.what());
|
|
Gui::Command::abortCommand();
|
|
|
|
tryAutoRecompute(Obj);
|
|
return;
|
|
}
|
|
|
|
Gui::Command::commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
|
|
/// Makes a simple tangency constraint using extra point + tangent via point
|
|
/// aoh => an arc of hyperbola
|
|
/// geom2 => any of an arc of hyperbola, an arc of ellipse, a circle, or an arc (of circle)
|
|
/// geoId1 => geoid of the arc of hyperbola
|
|
/// geoId2 => geoid of geom2
|
|
/// 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(Sketcher::SketchObject* Obj,
|
|
const Part::GeomArcOfHyperbola* aoh,
|
|
const Part::Geometry* geom2,
|
|
int geoId1,
|
|
int geoId2)
|
|
{
|
|
|
|
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 (isArcOfHyperbola(*geom2)) {
|
|
auto aoh2 = static_cast<const Part::GeomArcOfHyperbola*>(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 (isArcOfEllipse(*geom2)) {
|
|
center2 = (static_cast<const Part::GeomArcOfEllipse*>(geom2))->getCenter();
|
|
}
|
|
else if (isEllipse(*geom2)) {
|
|
center2 = (static_cast<const Part::GeomEllipse*>(geom2))->getCenter();
|
|
}
|
|
else if (isCircle(*geom2)) {
|
|
center2 = (static_cast<const Part::GeomCircle*>(geom2))->getCenter();
|
|
}
|
|
else if (isArcOfCircle(*geom2)) {
|
|
center2 = (static_cast<const Part::GeomArcOfCircle*>(geom2))->getCenter();
|
|
}
|
|
else if (isLineSegment(*geom2)) {
|
|
auto l2 = static_cast<const Part::GeomLineSegment*>(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::cmdAppObjectArgs(Obj, "addGeometry(Part.Point(App.Vector(%f,%f,0)))", PoH.x, PoH.y);
|
|
int GeoIdPoint = Obj->getHighestCurveIndex();
|
|
|
|
// Point on first object
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoIdPoint,
|
|
static_cast<int>(Sketcher::PointPos::start),
|
|
geoId1);// constrain major axis
|
|
// Point on second object
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoIdPoint,
|
|
static_cast<int>(Sketcher::PointPos::start),
|
|
geoId2);// constrain major axis
|
|
// tangent via point
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d))",
|
|
geoId1,
|
|
geoId2,
|
|
GeoIdPoint,
|
|
static_cast<int>(Sketcher::PointPos::start));
|
|
}
|
|
catch (const Base::Exception& e) {
|
|
Gui::NotifyUserError(Obj,
|
|
QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"),
|
|
e.what());
|
|
Gui::Command::abortCommand();
|
|
|
|
tryAutoRecompute(Obj);
|
|
return;
|
|
}
|
|
|
|
Gui::Command::commitCommand();
|
|
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
|
|
/// Makes a simple tangency constraint using extra point + tangent via point
|
|
/// aop => 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) geoId1 => geoid of the arc of parabola geoId2 => geoid of geom2 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(Sketcher::SketchObject* Obj,
|
|
const Part::GeomArcOfParabola* aop,
|
|
const Part::Geometry* geom2,
|
|
int geoId1,
|
|
int geoId2)
|
|
{
|
|
|
|
Base::Vector3d focus = aop->getFocus();
|
|
|
|
Base::Vector3d center2;
|
|
|
|
if (isArcOfParabola(*geom2)) {
|
|
center2 = (static_cast<const Part::GeomArcOfParabola*>(geom2))->getFocus();
|
|
}
|
|
else if (isArcOfHyperbola(*geom2)) {
|
|
auto aoh2 = static_cast<const Part::GeomArcOfHyperbola*>(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 (isArcOfEllipse(*geom2)) {
|
|
center2 = (static_cast<const Part::GeomArcOfEllipse*>(geom2))->getCenter();
|
|
}
|
|
else if (isEllipse(*geom2)) {
|
|
center2 = (static_cast<const Part::GeomEllipse*>(geom2))->getCenter();
|
|
}
|
|
else if (isCircle(*geom2)) {
|
|
center2 = (static_cast<const Part::GeomCircle*>(geom2))->getCenter();
|
|
}
|
|
else if (isArcOfCircle(*geom2)) {
|
|
center2 = (static_cast<const Part::GeomArcOfCircle*>(geom2))->getCenter();
|
|
}
|
|
else if (isLineSegment(*geom2)) {
|
|
auto l2 = static_cast<const Part::GeomLineSegment*>(geom2);
|
|
center2 = (l2->getStartPoint() + l2->getEndPoint()) / 2;
|
|
}
|
|
|
|
Base::Vector3d direction = center2 - focus;
|
|
|
|
Base::Vector3d PoP = focus + direction / 2;
|
|
|
|
try {
|
|
// Add a point
|
|
Gui::cmdAppObjectArgs(Obj, "addGeometry(Part.Point(App.Vector(%f,%f,0)))", PoP.x, PoP.y);
|
|
int GeoIdPoint = Obj->getHighestCurveIndex();
|
|
|
|
// Point on first object
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoIdPoint,
|
|
static_cast<int>(Sketcher::PointPos::start),
|
|
geoId1);// constrain major axis
|
|
// Point on second object
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoIdPoint,
|
|
static_cast<int>(Sketcher::PointPos::start),
|
|
geoId2);// constrain major axis
|
|
// tangent via point
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d))",
|
|
geoId1,
|
|
geoId2,
|
|
GeoIdPoint,
|
|
static_cast<int>(Sketcher::PointPos::start));
|
|
}
|
|
catch (const Base::Exception& e) {
|
|
Gui::NotifyUserError(Obj,
|
|
QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"),
|
|
e.what());
|
|
|
|
Gui::Command::abortCommand();
|
|
|
|
tryAutoRecompute(Obj);
|
|
return;
|
|
}
|
|
|
|
Gui::Command::commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
|
|
void SketcherGui::doEndpointTangency(Sketcher::SketchObject* Obj,
|
|
int GeoId1,
|
|
int GeoId2,
|
|
PointPos PosId1,
|
|
PointPos PosId2)
|
|
{
|
|
// 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 && (isBSplineCurve(*geom1) || isBSplineCurve(*geom2))) {
|
|
if (! isBSplineCurve(*geom1)) {
|
|
std::swap(GeoId1, GeoId2);
|
|
std::swap(PosId1, PosId2);
|
|
}
|
|
// GeoId1 is the B-spline now
|
|
}// end of code supports simple B-spline endpoint tangency
|
|
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d,%d))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2));
|
|
}
|
|
|
|
void SketcherGui::doEndpointToEdgeTangency(Sketcher::SketchObject* Obj,
|
|
int GeoId1,
|
|
PointPos PosId1,
|
|
int GeoId2)
|
|
{
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2);
|
|
}
|
|
|
|
void SketcherGui::notifyConstraintSubstitutions(const QString& message)
|
|
{
|
|
Gui::Dialog::DlgCheckableMessageBox::showMessage(
|
|
QObject::tr("Sketcher Constraint Substitution"),
|
|
message,
|
|
QLatin1String("User parameter:BaseApp/Preferences/Mod/Sketcher/General"),
|
|
QLatin1String("NotifyConstraintSubstitutions"),
|
|
true,// Default ParamEntry
|
|
true,// checkbox state
|
|
QObject::tr("Keep notifying me of constraint substitutions"));
|
|
}
|
|
|
|
// returns true if successful, false otherwise
|
|
bool addConstraintSafely(SketchObject* obj, std::function<void()> constraintadditionfunction)
|
|
{
|
|
try {
|
|
constraintadditionfunction();
|
|
}
|
|
catch (const Base::IndexError& e) {
|
|
// Exceptions originating in Python have already been reported by the Interpreter as
|
|
// Untranslated developer intended messages (i.e. to the Report View)
|
|
// Example of exception carrying a static string with a pair in the "Notifications" context
|
|
Gui::NotifyUserError(obj,
|
|
QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"),
|
|
e.what());
|
|
|
|
Gui::Command::abortCommand();
|
|
|
|
tryAutoRecompute(obj);
|
|
return false;
|
|
}
|
|
catch (const Base::Exception&) {
|
|
Gui::TranslatedUserError(
|
|
obj,
|
|
QObject::tr("Error"),
|
|
QObject::tr("Unexpected error. More information may be available in the Report View."));
|
|
|
|
Gui::Command::abortCommand();
|
|
|
|
tryAutoRecompute(obj);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
namespace SketcherGui
|
|
{
|
|
|
|
struct SelIdPair
|
|
{
|
|
int GeoId;
|
|
Sketcher::PointPos PosId;
|
|
};
|
|
|
|
struct SketchSelection
|
|
{
|
|
enum GeoType
|
|
{
|
|
Point,
|
|
Line,
|
|
Circle,
|
|
Arc
|
|
};
|
|
int setUp();
|
|
struct SketchSelectionItem
|
|
{
|
|
GeoType type;
|
|
int GeoId;
|
|
bool Extern;
|
|
};
|
|
std::list<SketchSelectionItem> Items;
|
|
QString ErrorMsg;
|
|
};
|
|
|
|
int SketchSelection::setUp()
|
|
{
|
|
std::vector<Gui::SelectionObject> selection = Gui::Selection().getSelectionEx();
|
|
|
|
Sketcher::SketchObject* SketchObj = nullptr;
|
|
std::vector<std::string> SketchSubNames;
|
|
std::vector<std::string> 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()->isDerivedFrom<Sketcher::SketchObject>()) {
|
|
ErrorMsg = QObject::tr("Only sketch and its support are allowed to be selected.");
|
|
return -1;
|
|
}
|
|
|
|
SketchSubNames = selection[0].getSubNames();
|
|
}
|
|
else if (selection.size() == 2) {
|
|
if (selection[0].getObject()->isDerivedFrom<Sketcher::SketchObject>()) {
|
|
SketchObj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
// check if the none sketch object is the support of the sketch
|
|
if (selection[1].getObject() != SketchObj->AttachmentSupport.getValue()) {
|
|
ErrorMsg = QObject::tr("Only sketch and its support are allowed to be selected.");
|
|
return -1;
|
|
}
|
|
// assume always a Part::Feature derived object as support
|
|
assert(selection[1].getObject()->isDerivedFrom<Part::Feature>());
|
|
SketchSubNames = selection[0].getSubNames();
|
|
SupportSubNames = selection[1].getSubNames();
|
|
}
|
|
else if (selection[1].getObject()->isDerivedFrom<Sketcher::SketchObject>()) {
|
|
SketchObj = static_cast<Sketcher::SketchObject*>(selection[1].getObject());
|
|
// check if the none sketch object is the support of the sketch
|
|
if (selection[0].getObject() != SketchObj->AttachmentSupport.getValue()) {
|
|
ErrorMsg = QObject::tr("Only sketch and its support are allowed to be selected.");
|
|
return -1;
|
|
}
|
|
// assume always a Part::Feature derived object as support
|
|
assert(selection[0].getObject()->isDerivedFrom<Part::Feature>());
|
|
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:
|
|
explicit GenericConstraintSelection(App::DocumentObject* obj)
|
|
: Gui::SelectionFilterGate(nullPointer())
|
|
, object(obj)
|
|
, allowedSelTypes(0)
|
|
{}
|
|
|
|
bool allow(App::Document*, App::DocumentObject* pObj, const char* sSubName) override
|
|
{
|
|
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;
|
|
};
|
|
}// namespace SketcherGui
|
|
|
|
/**
|
|
* @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:
|
|
explicit CmdSketcherConstraint(const char* name)
|
|
: Command(name)
|
|
{}
|
|
|
|
~CmdSketcherConstraint() override
|
|
{}
|
|
|
|
const char* className() const override
|
|
{
|
|
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<std::vector<SketcherGui::SelType>> allowedSelSequences;
|
|
|
|
virtual void applyConstraint(std::vector<SelIdPair>&, int)
|
|
{}
|
|
void activated(int /*iMsg*/) override;
|
|
bool isActive() override
|
|
{
|
|
return isCommandActive(getActiveGuiDocument());
|
|
}
|
|
};
|
|
|
|
class DrawSketchHandlerGenConstraint: public DrawSketchHandler
|
|
{
|
|
public:
|
|
explicit DrawSketchHandlerGenConstraint(CmdSketcherConstraint* _cmd)
|
|
: cmd(_cmd)
|
|
, seqIndex(0)
|
|
{}
|
|
~DrawSketchHandlerGenConstraint() override
|
|
{
|
|
Gui::Selection().rmvSelectionGate();
|
|
}
|
|
|
|
void mouseMove(Base::Vector2d /*onSketchPos*/) override
|
|
{}
|
|
|
|
bool pressButton(Base::Vector2d /*onSketchPos*/) override
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool releaseButton(Base::Vector2d onSketchPos) override
|
|
{
|
|
SelIdPair selIdPair;
|
|
selIdPair.GeoId = GeoEnum::GeoUndef;
|
|
selIdPair.PosId = Sketcher::PointPos::none;
|
|
std::stringstream ss;
|
|
SelType newSelType = SelUnknown;
|
|
|
|
// For each SelType allowed, check if button is released there and assign it to selIdPair
|
|
int VtId = getPreselectPoint();
|
|
int CrvId = getPreselectCurve();
|
|
int CrsId = getPreselectCross();
|
|
if (allowedSelTypes & (SelRoot | SelVertexOrRoot) && CrsId == 0) {
|
|
selIdPair.GeoId = Sketcher::GeoEnum::RtPnt;
|
|
selIdPair.PosId = Sketcher::PointPos::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 == GeoEnum::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<int>::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;
|
|
}
|
|
|
|
private:
|
|
void activated() override
|
|
{
|
|
selFilterGate = new GenericConstraintSelection(sketchgui->getObject());
|
|
|
|
resetOngoingSequences();
|
|
|
|
selSeq.clear();
|
|
|
|
Gui::Selection().rmvSelectionGate();
|
|
Gui::Selection().addSelectionGate(selFilterGate);
|
|
|
|
// Constrain icon size in px
|
|
qreal pixelRatio = devicePixelRatio();
|
|
const unsigned long defaultCrosshairColor = 0xFFFFFF;
|
|
unsigned long color = getCrosshairColor();
|
|
auto colorMapping = std::map<unsigned long, unsigned long>();
|
|
colorMapping[defaultCrosshairColor] = color;
|
|
|
|
qreal fullIconWidth = 32 * pixelRatio;
|
|
qreal iconWidth = 16 * pixelRatio;
|
|
QPixmap cursorPixmap =
|
|
Gui::BitmapFactory().pixmapFromSvg("Sketcher_Crosshair",
|
|
QSizeF(fullIconWidth, fullIconWidth),
|
|
colorMapping),
|
|
icon = Gui::BitmapFactory().pixmapFromSvg(cmd->getPixmap(),
|
|
QSizeF(iconWidth, iconWidth));
|
|
QPainter cursorPainter;
|
|
cursorPainter.begin(&cursorPixmap);
|
|
cursorPainter.drawPixmap(16 * pixelRatio, 16 * pixelRatio, icon);
|
|
cursorPainter.end();
|
|
int hotX = 8;
|
|
int hotY = 8;
|
|
cursorPixmap.setDevicePixelRatio(pixelRatio);
|
|
// only X11 needs hot point coordinates to be scaled
|
|
if (qGuiApp->platformName() == QLatin1String("xcb")) {
|
|
hotX *= pixelRatio;
|
|
hotY *= pixelRatio;
|
|
}
|
|
setCursor(cursorPixmap, hotX, hotY, false);
|
|
}
|
|
|
|
protected:
|
|
CmdSketcherConstraint* cmd;
|
|
|
|
GenericConstraintSelection* selFilterGate = nullptr;
|
|
|
|
std::vector<SelIdPair> selSeq;
|
|
unsigned int allowedSelTypes = 0;
|
|
|
|
/// indices of currently ongoing sequences in cmd->allowedSequences
|
|
std::set<int> 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(this));
|
|
getSelection().clearSelection();
|
|
}
|
|
|
|
// Comp for dimension tools =============================================
|
|
|
|
class CmdSketcherCompDimensionTools : public Gui::GroupCommand
|
|
{
|
|
public:
|
|
CmdSketcherCompDimensionTools()
|
|
: GroupCommand("Sketcher_CompDimensionTools")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Dimension");
|
|
sToolTipText = QT_TR_NOOP("Dimension tools.");
|
|
sWhatsThis = "Sketcher_CompDimensionTools";
|
|
sStatusTip = sToolTipText;
|
|
eType = ForEdit;
|
|
|
|
setCheckable(false);
|
|
setRememberLast(false);
|
|
|
|
addCommand("Sketcher_Dimension");
|
|
addCommand(); //separator
|
|
addCommand("Sketcher_ConstrainDistanceX");
|
|
addCommand("Sketcher_ConstrainDistanceY");
|
|
addCommand("Sketcher_ConstrainDistance");
|
|
addCommand("Sketcher_ConstrainRadiam");
|
|
addCommand("Sketcher_ConstrainRadius");
|
|
addCommand("Sketcher_ConstrainDiameter");
|
|
addCommand("Sketcher_ConstrainAngle");
|
|
addCommand("Sketcher_ConstrainLock");
|
|
}
|
|
|
|
void updateAction(int mode) override
|
|
{
|
|
Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(getAction());
|
|
if (!pcAction) {
|
|
return;
|
|
}
|
|
|
|
QList<QAction*> al = pcAction->actions();
|
|
int index = pcAction->property("defaultAction").toInt();
|
|
switch (mode) {
|
|
case Reference:
|
|
al[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Dimension_Driven"));
|
|
//al[1] is the separator
|
|
al[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_HorizontalDistance_Driven"));
|
|
al[3]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_VerticalDistance_Driven"));
|
|
al[4]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Length_Driven"));
|
|
al[5]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radiam_Driven"));
|
|
al[6]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radius_Driven"));
|
|
al[7]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Diameter_Driven"));
|
|
al[8]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_InternalAngle_Driven"));
|
|
al[9]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Lock_Driven"));
|
|
getAction()->setIcon(al[index]->icon());
|
|
break;
|
|
case Driving:
|
|
al[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Dimension"));
|
|
//al[1] is the separator
|
|
al[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_HorizontalDistance"));
|
|
al[3]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_VerticalDistance"));
|
|
al[4]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Length"));
|
|
al[5]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radiam"));
|
|
al[6]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radius"));
|
|
al[7]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Diameter"));
|
|
al[8]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_InternalAngle"));
|
|
al[9]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Lock"));
|
|
getAction()->setIcon(al[index]->icon());
|
|
break;
|
|
}
|
|
}
|
|
|
|
const char* className() const override { return "CmdSketcherCompDimensionTools"; }
|
|
};
|
|
|
|
// Comp for constrain tools =============================================
|
|
|
|
class CmdSketcherCompConstrainTools : public Gui::GroupCommand
|
|
{
|
|
public:
|
|
CmdSketcherCompConstrainTools()
|
|
: GroupCommand("Sketcher_CompConstrainTools")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Constrain");
|
|
sToolTipText = QT_TR_NOOP("Constrain tools.");
|
|
sWhatsThis = "Sketcher_CompConstrainTools";
|
|
sStatusTip = sToolTipText;
|
|
eType = ForEdit;
|
|
|
|
setCheckable(false);
|
|
setRememberLast(false);
|
|
|
|
addCommand("Sketcher_ConstrainCoincidentUnified");
|
|
addCommand("Sketcher_ConstrainHorVer");
|
|
addCommand("Sketcher_ConstrainParallel");
|
|
addCommand("Sketcher_ConstrainPerpendicular");
|
|
addCommand("Sketcher_ConstrainTangent");
|
|
addCommand("Sketcher_ConstrainEqual");
|
|
addCommand("Sketcher_ConstrainSymmetric");
|
|
addCommand("Sketcher_ConstrainBlock");
|
|
}
|
|
const char* className() const override { return "CmdSketcherCompConstrainTools"; }
|
|
};
|
|
|
|
// Dimension tool =======================================================
|
|
|
|
class GeomSelectionSizes
|
|
{
|
|
public:
|
|
GeomSelectionSizes(size_t s_pts, size_t s_lns, size_t s_cir, size_t s_ell, size_t s_spl) :
|
|
s_pts(s_pts), s_lns(s_lns), s_cir(s_cir), s_ell(s_ell), s_spl(s_spl) {}
|
|
~GeomSelectionSizes() {}
|
|
|
|
bool hasPoints() const { return s_pts > 0; }
|
|
bool hasLines() const { return s_lns > 0; }
|
|
bool hasCirclesOrArcs() const { return s_cir > 0; }
|
|
bool hasEllipseAndCo() const { return s_ell > 0; }
|
|
bool hasSplineAndCo() const { return s_spl > 0; }
|
|
|
|
bool has1Point() const { return s_pts == 1 && s_lns == 0 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
|
|
bool has2Points() const { return s_pts == 2 && s_lns == 0 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
|
|
bool has1Point1Line() const { return s_pts == 1 && s_lns == 1 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
|
|
bool has3Points() const { return s_pts == 3 && s_lns == 0 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
|
|
bool has4MorePoints() const { return s_pts >= 4 && s_lns == 0 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
|
|
bool has2Points1Line() const { return s_pts == 2 && s_lns == 1 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
|
|
bool has3MorePoints1Line() const { return s_pts >= 3 && s_lns == 1 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
|
|
bool has1Point1Circle() const { return s_pts == 1 && s_lns == 0 && s_cir == 1 && s_ell == 0 && s_spl == 0; }
|
|
bool has1MorePoint1Ellipse() const { return s_pts >= 1 && s_lns == 0 && s_cir == 0 && s_ell == 1 && s_spl == 0; }
|
|
|
|
bool has1Line() const { return s_pts == 0 && s_lns == 1 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
|
|
bool has2Lines() const { return s_pts == 0 && s_lns == 2 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
|
|
bool has3MoreLines() const { return s_pts == 0 && s_lns >= 3 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
|
|
bool has1Line1Circle() const { return s_pts == 0 && s_lns == 1 && s_cir == 1 && s_ell == 0 && s_spl == 0; }
|
|
bool has1Line2Circles() const { return s_pts == 0 && s_lns == 1 && s_cir == 2 && s_ell == 0 && s_spl == 0; }
|
|
bool has1Line1Ellipse() const { return s_pts == 0 && s_lns == 1 && s_cir == 0 && s_ell == 1 && s_spl == 0; }
|
|
|
|
bool has1Circle() const { return s_pts == 0 && s_lns == 0 && s_cir == 1 && s_ell == 0 && s_spl == 0; }
|
|
bool has2Circles() const { return s_pts == 0 && s_lns == 0 && s_cir == 2 && s_ell == 0 && s_spl == 0; }
|
|
bool has3MoreCircles() const { return s_pts == 0 && s_lns == 0 && s_cir >= 3 && s_ell == 0 && s_spl == 0; }
|
|
bool has1Circle1Ellipse() const { return s_pts == 0 && s_lns == 0 && s_cir == 1 && s_ell == 1 && s_spl == 0; }
|
|
|
|
bool has1Ellipse() const { return s_pts == 0 && s_lns == 0 && s_cir == 0 && s_ell == 1 && s_spl == 0; }
|
|
bool has2MoreEllipses() const { return s_pts == 0 && s_lns == 0 && s_cir == 0 && s_ell >= 2 && s_spl == 0; }
|
|
bool has1Point1Spline1MoreEdge() const { return s_pts == 1 && s_spl >= 1 && (s_lns + s_cir + s_ell + s_spl) == 2; }
|
|
|
|
size_t s_pts, s_lns, s_cir, s_ell, s_spl;
|
|
};
|
|
|
|
class DrawSketchHandlerDimension : public DrawSketchHandler
|
|
{
|
|
public:
|
|
explicit DrawSketchHandlerDimension(std::vector<std::string> SubNames)
|
|
: specialConstraint(SpecialConstraint::None)
|
|
, availableConstraint(AvailableConstraint::FIRST)
|
|
, previousOnSketchPos(Base::Vector2d(0.f, 0.f))
|
|
, selPoints({})
|
|
, selLine({})
|
|
, selCircleArc({})
|
|
, selEllipseAndCo({})
|
|
, initialSelection(std::move(SubNames))
|
|
, numberOfConstraintsCreated(0)
|
|
{
|
|
}
|
|
~DrawSketchHandlerDimension() override
|
|
{
|
|
}
|
|
|
|
enum class AvailableConstraint {
|
|
FIRST,
|
|
SECOND,
|
|
THIRD,
|
|
FOURTH,
|
|
FIFTH,
|
|
RESET
|
|
};
|
|
|
|
enum class SpecialConstraint {
|
|
LineOr2PointsDistance,
|
|
Block,
|
|
None
|
|
};
|
|
|
|
void activated() override
|
|
{
|
|
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Dimension"));
|
|
|
|
Obj = sketchgui->getSketchObject();
|
|
|
|
// Constrain icon size in px
|
|
qreal pixelRatio = devicePixelRatio();
|
|
const unsigned long defaultCrosshairColor = 0xFFFFFF;
|
|
unsigned long color = getCrosshairColor();
|
|
auto colorMapping = std::map<unsigned long, unsigned long>();
|
|
colorMapping[defaultCrosshairColor] = color;
|
|
|
|
qreal fullIconWidth = 32 * pixelRatio;
|
|
qreal iconWidth = 16 * pixelRatio;
|
|
QPixmap cursorPixmap = Gui::BitmapFactory().pixmapFromSvg("Sketcher_Crosshair", QSizeF(fullIconWidth, fullIconWidth), colorMapping),
|
|
icon = Gui::BitmapFactory().pixmapFromSvg("Constraint_Dimension", QSizeF(iconWidth, iconWidth));
|
|
QPainter cursorPainter;
|
|
cursorPainter.begin(&cursorPixmap);
|
|
cursorPainter.drawPixmap(16 * pixelRatio, 16 * pixelRatio, icon);
|
|
cursorPainter.end();
|
|
int hotX = 8;
|
|
int hotY = 8;
|
|
cursorPixmap.setDevicePixelRatio(pixelRatio);
|
|
// only X11 needs hot point coordinates to be scaled
|
|
if (qGuiApp->platformName() == QLatin1String("xcb")) {
|
|
hotX *= pixelRatio;
|
|
hotY *= pixelRatio;
|
|
}
|
|
setCursor(cursorPixmap, hotX, hotY, false);
|
|
|
|
handleInitialSelection();
|
|
}
|
|
|
|
void deactivated() override
|
|
{
|
|
Gui::Command::abortCommand();
|
|
Obj->solve();
|
|
sketchgui->draw(false, false); // Redraw
|
|
}
|
|
|
|
void registerPressedKey(bool pressed, int key) override
|
|
{
|
|
if (key == SoKeyboardEvent::M && pressed) {
|
|
if (availableConstraint == AvailableConstraint::FIRST) {
|
|
availableConstraint = AvailableConstraint::SECOND;
|
|
}
|
|
else if (availableConstraint == AvailableConstraint::SECOND) {
|
|
availableConstraint = AvailableConstraint::THIRD;
|
|
}
|
|
else if (availableConstraint == AvailableConstraint::THIRD) {
|
|
availableConstraint = AvailableConstraint::FOURTH;
|
|
}
|
|
else if (availableConstraint == AvailableConstraint::FOURTH) {
|
|
availableConstraint = AvailableConstraint::FIFTH;
|
|
}
|
|
else if (availableConstraint == AvailableConstraint::FIFTH || availableConstraint == AvailableConstraint::RESET) {
|
|
availableConstraint = AvailableConstraint::FIRST;
|
|
}
|
|
makeAppropriateConstraint(previousOnSketchPos);
|
|
}
|
|
else if (key == SoKeyboardEvent::Z && (QApplication::keyboardModifiers() & Qt::ControlModifier)) {
|
|
// User trying to cancel with Ctrl-Z
|
|
sketchgui->purgeHandler();
|
|
}
|
|
else {
|
|
DrawSketchHandler::registerPressedKey(pressed, key);
|
|
}
|
|
}
|
|
|
|
void mouseMove(Base::Vector2d onSketchPos) override
|
|
{
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
previousOnSketchPos = onSketchPos;
|
|
|
|
//Change distance constraint based on position of mouse.
|
|
if (specialConstraint == SpecialConstraint::LineOr2PointsDistance)
|
|
updateDistanceType(onSketchPos);
|
|
|
|
//Move constraints
|
|
if (numberOfConstraintsCreated > 0) {
|
|
bool oneMoved = false;
|
|
for (int i = 0; i < numberOfConstraintsCreated; i++) {
|
|
if (ConStr[ConStr.size() - 1 - i]->isDimensional()) {
|
|
Base::Vector2d pointWhereToMove = onSketchPos;
|
|
|
|
if (specialConstraint == SpecialConstraint::Block) {
|
|
if (i == 0)
|
|
pointWhereToMove.x = Obj->getPoint(selPoints[0].GeoId, selPoints[0].PosId).x;
|
|
else
|
|
pointWhereToMove.y = Obj->getPoint(selPoints[0].GeoId, selPoints[0].PosId).y;
|
|
}
|
|
moveConstraint(ConStr.size() - 1 - i, pointWhereToMove);
|
|
oneMoved = true;
|
|
}
|
|
}
|
|
if (oneMoved)
|
|
sketchgui->draw(false, false); // Redraw
|
|
}
|
|
}
|
|
|
|
bool pressButton(Base::Vector2d onSketchPos) override
|
|
{
|
|
Q_UNUSED(onSketchPos)
|
|
return true;
|
|
}
|
|
|
|
bool releaseButton(Base::Vector2d onSketchPos) override
|
|
{
|
|
Q_UNUSED(onSketchPos);
|
|
availableConstraint = AvailableConstraint::FIRST;
|
|
SelIdPair selIdPair;
|
|
selIdPair.GeoId = GeoEnum::GeoUndef;
|
|
selIdPair.PosId = Sketcher::PointPos::none;
|
|
std::stringstream ss;
|
|
Base::Type newselGeoType = Base::Type::badType();
|
|
|
|
int VtId = getPreselectPoint();
|
|
int CrvId = getPreselectCurve();
|
|
int CrsId = getPreselectCross();
|
|
|
|
if (VtId >= 0) { //Vertex
|
|
Obj->getGeoVertexIndex(VtId,
|
|
selIdPair.GeoId, selIdPair.PosId);
|
|
newselGeoType = Part::GeomPoint::getClassTypeId();
|
|
ss << "Vertex" << VtId + 1;
|
|
}
|
|
else if (CrsId == 0) { //RootPoint
|
|
selIdPair.GeoId = Sketcher::GeoEnum::RtPnt;
|
|
selIdPair.PosId = Sketcher::PointPos::start;
|
|
newselGeoType = Part::GeomPoint::getClassTypeId();
|
|
ss << "RootPoint";
|
|
}
|
|
else if (CrsId == 1) { //H_Axis
|
|
selIdPair.GeoId = Sketcher::GeoEnum::HAxis;
|
|
newselGeoType = Part::GeomLineSegment::getClassTypeId();
|
|
ss << "H_Axis";
|
|
}
|
|
else if (CrsId == 2) { //V_Axis
|
|
selIdPair.GeoId = Sketcher::GeoEnum::VAxis;
|
|
newselGeoType = Part::GeomLineSegment::getClassTypeId();
|
|
ss << "V_Axis";
|
|
}
|
|
else if (CrvId >= 0 || CrvId <= Sketcher::GeoEnum::RefExt) { //Curves
|
|
selIdPair.GeoId = CrvId;
|
|
const Part::Geometry* geo = Obj->getGeometry(CrvId);
|
|
|
|
newselGeoType = geo->getTypeId();
|
|
|
|
if (CrvId >= 0) {
|
|
ss << "Edge" << CrvId + 1;
|
|
}
|
|
else {
|
|
ss << "ExternalEdge" << Sketcher::GeoEnum::RefExt + 1 - CrvId;
|
|
}
|
|
}
|
|
|
|
if (selIdPair.GeoId == GeoEnum::GeoUndef) {
|
|
// If mouse is released on "blank" space, finalize and start over
|
|
finalizeCommand();
|
|
return true;
|
|
}
|
|
|
|
std::vector<SelIdPair>& selVector = getSelectionVector(newselGeoType);
|
|
|
|
if (notSelectedYet(selIdPair)) {
|
|
//add the geometry to its type vector. Temporarily if not selAllowed
|
|
selVector.push_back(selIdPair);
|
|
|
|
bool selAllowed = makeAppropriateConstraint(onSketchPos);
|
|
|
|
if (selAllowed) {
|
|
// If mouse is released on something allowed, select it
|
|
Gui::Selection().addSelection(Obj->getDocument()->getName(),
|
|
Obj->getNameInDocument(),
|
|
ss.str().c_str(), onSketchPos.x, onSketchPos.y, 0.f);
|
|
sketchgui->draw(false, false); // Redraw
|
|
}
|
|
else {
|
|
selVector.pop_back();
|
|
}
|
|
}
|
|
else {
|
|
//if it is already selected we unselect it.
|
|
selVector.pop_back();
|
|
if (!selectionEmpty()) {
|
|
makeAppropriateConstraint(onSketchPos);
|
|
}
|
|
else {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Dimension"));
|
|
}
|
|
|
|
Gui::Selection().rmvSelection(Obj->getDocument()->getName(),
|
|
Obj->getNameInDocument(),
|
|
ss.str().c_str());
|
|
sketchgui->draw(false, false); // Redraw
|
|
}
|
|
return true;
|
|
}
|
|
protected:
|
|
SpecialConstraint specialConstraint;
|
|
AvailableConstraint availableConstraint;
|
|
|
|
Base::Vector2d previousOnSketchPos;
|
|
|
|
std::vector<SelIdPair> selPoints;
|
|
std::vector<SelIdPair> selLine;
|
|
std::vector<SelIdPair> selCircleArc;
|
|
std::vector<SelIdPair> selEllipseAndCo;
|
|
std::vector<SelIdPair> selSplineAndCo;
|
|
|
|
std::vector<std::string> initialSelection;
|
|
|
|
int numberOfConstraintsCreated;
|
|
|
|
Sketcher::SketchObject* Obj;
|
|
|
|
void handleInitialSelection()
|
|
{
|
|
if (initialSelection.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
availableConstraint = AvailableConstraint::FIRST;
|
|
|
|
// Add the selected elements to their corresponding selection vectors
|
|
for (auto& selElement : initialSelection) {
|
|
SelIdPair selIdPair;
|
|
getIdsFromName(selElement, Obj, selIdPair.GeoId, selIdPair.PosId);
|
|
|
|
Base::Type newselGeoType = Base::Type::badType();
|
|
if (isEdge(selIdPair.GeoId, selIdPair.PosId)) {
|
|
const Part::Geometry* geo = Obj->getGeometry(selIdPair.GeoId);
|
|
newselGeoType = geo->getTypeId();
|
|
}
|
|
else if (isVertex(selIdPair.GeoId, selIdPair.PosId)) {
|
|
newselGeoType = Part::GeomPoint::getClassTypeId();
|
|
}
|
|
|
|
std::vector<SelIdPair>& selVector = getSelectionVector(newselGeoType);
|
|
|
|
//add the geometry to its type vector. Temporarily if not selAllowed
|
|
selVector.push_back(selIdPair);
|
|
}
|
|
|
|
// See if the selection is valid
|
|
bool selAllowed = makeAppropriateConstraint(Base::Vector2d(0.,0.));
|
|
|
|
if (!selAllowed) {
|
|
selPoints.clear();
|
|
selLine.clear();
|
|
selCircleArc.clear();
|
|
selEllipseAndCo.clear();
|
|
selSplineAndCo.clear();
|
|
}
|
|
}
|
|
|
|
void finalizeCommand()
|
|
{
|
|
// Ask for the value of datum constraints
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
bool show = hGrp->GetBool("ShowDialogOnDistanceConstraint", true);
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
bool commandHandledInEditDatum = false;
|
|
for (int i = numberOfConstraintsCreated - 1; i >= 0; i--) {
|
|
if (show && ConStr[ConStr.size() - 1 - i]->isDimensional() && ConStr[ConStr.size() - 1 - i]->isDriving) {
|
|
commandHandledInEditDatum = true;
|
|
EditDatumDialog editDatumDialog(sketchgui, ConStr.size() - 1 - i);
|
|
editDatumDialog.exec();
|
|
if (!editDatumDialog.isSuccess()) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!commandHandledInEditDatum)
|
|
Gui::Command::commitCommand();
|
|
|
|
// This code enables the continuous creation mode.
|
|
bool continuousMode = hGrp->GetBool("ContinuousCreationMode", true);
|
|
if (continuousMode) {
|
|
Gui::Selection().clearSelection();
|
|
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Dimension"));
|
|
numberOfConstraintsCreated = 0;
|
|
specialConstraint = SpecialConstraint::None;
|
|
previousOnSketchPos = Base::Vector2d(0.f, 0.f);
|
|
selPoints.clear();
|
|
selLine.clear();
|
|
selCircleArc.clear();
|
|
selEllipseAndCo.clear();
|
|
}
|
|
else {
|
|
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
|
|
}
|
|
}
|
|
|
|
std::vector<SelIdPair>& getSelectionVector(Base::Type selGeoType)
|
|
{
|
|
if (selGeoType == Part::GeomPoint::getClassTypeId()) {
|
|
return selPoints;
|
|
}
|
|
else if (selGeoType == Part::GeomLineSegment::getClassTypeId()) {
|
|
return selLine;
|
|
}
|
|
else if (selGeoType == Part::GeomArcOfCircle::getClassTypeId() ||
|
|
selGeoType == Part::GeomCircle::getClassTypeId()) {
|
|
return selCircleArc;
|
|
}
|
|
else if (selGeoType == Part::GeomEllipse::getClassTypeId() ||
|
|
selGeoType == Part::GeomArcOfEllipse::getClassTypeId() ||
|
|
selGeoType == Part::GeomArcOfHyperbola::getClassTypeId() ||
|
|
selGeoType == Part::GeomArcOfParabola::getClassTypeId()) {
|
|
return selEllipseAndCo;
|
|
}
|
|
else if (selGeoType == Part::GeomBSplineCurve::getClassTypeId()) {
|
|
return selSplineAndCo;
|
|
}
|
|
|
|
static std::vector<SelIdPair> emptyVector;
|
|
return emptyVector;
|
|
}
|
|
|
|
bool notSelectedYet(const SelIdPair& elem)
|
|
{
|
|
auto contains = [&](const std::vector<SelIdPair>& vec, const SelIdPair& elem) {
|
|
for (const auto& x : vec)
|
|
{
|
|
if (x.GeoId == elem.GeoId && x.PosId == elem.PosId)
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
return !contains(selPoints, elem)
|
|
&& !contains(selLine, elem)
|
|
&& !contains(selCircleArc, elem)
|
|
&& !contains(selEllipseAndCo, elem);
|
|
}
|
|
|
|
bool selectionEmpty()
|
|
{
|
|
return selPoints.empty() && selLine.empty() && selCircleArc.empty() && selEllipseAndCo.empty();
|
|
}
|
|
|
|
bool makeAppropriateConstraint(Base::Vector2d onSketchPos) {
|
|
bool selAllowed = false;
|
|
|
|
GeomSelectionSizes selection(selPoints.size(), selLine.size(), selCircleArc.size(), selEllipseAndCo.size(), selSplineAndCo.size());
|
|
|
|
if (selection.hasPoints()) {
|
|
if (selection.has1Point()) { makeCts_1Point(selAllowed, onSketchPos); }
|
|
else if (selection.has2Points()) { makeCts_2Point(selAllowed, onSketchPos); }
|
|
else if (selection.has1Point1Line()) { makeCts_1Point1Line(selAllowed, onSketchPos); }
|
|
else if (selection.has1Point1Spline1MoreEdge()) { makeCts_1Point1Spline1MoreEdge(selAllowed);}
|
|
else if (selection.has3Points()) { makeCts_3Point(selAllowed, selection.s_pts); }
|
|
else if (selection.has4MorePoints()) { makeCts_4MorePoint(selAllowed, selection.s_pts); }
|
|
else if (selection.has2Points1Line()) { makeCts_2Point1Line(selAllowed, onSketchPos, selection.s_pts); }
|
|
else if (selection.has3MorePoints1Line()) { makeCts_3MorePoint1Line(selAllowed, onSketchPos, selection.s_pts); }
|
|
else if (selection.has1Point1Circle()) { makeCts_1Point1Circle(selAllowed, onSketchPos); }
|
|
else if (selection.has1MorePoint1Ellipse()) { makeCts_1MorePoint1Ellipse(selAllowed); }
|
|
}
|
|
else if (selection.hasLines()) {
|
|
if (selection.has1Line()) { makeCts_1Line(selAllowed, onSketchPos); }
|
|
else if (selection.has2Lines()) { makeCts_2Line(selAllowed, onSketchPos); }
|
|
else if (selection.has3MoreLines()) { makeCts_3MoreLine(selAllowed, selection.s_lns); }
|
|
else if (selection.has1Line1Circle()) { makeCts_1Line1Circle(selAllowed, onSketchPos); }
|
|
else if (selection.has1Line2Circles()) { makeCts_1Line2Circle(selAllowed); }
|
|
else if (selection.has1Line1Ellipse()) { makeCts_1Line1Ellipse(selAllowed); }
|
|
}
|
|
else if (selection.hasCirclesOrArcs()) {
|
|
if (selection.has1Circle()) { makeCts_1Circle(selAllowed, onSketchPos); }
|
|
else if (selection.has2Circles()) { makeCts_2Circle(selAllowed, onSketchPos); }
|
|
else if (selection.has3MoreCircles()) { makeCts_3MoreCircle(selAllowed, selection.s_cir); }
|
|
else if (selection.has1Circle1Ellipse()) { makeCts_1Circle1Ellipse(selAllowed); }
|
|
}
|
|
else if (selection.hasEllipseAndCo()) {
|
|
if (selection.has1Ellipse()) { makeCts_1Ellipse(selAllowed); }
|
|
else if (selection.has2MoreEllipses()) { makeCts_2MoreEllipse(selAllowed, selection.s_ell); }
|
|
}
|
|
return selAllowed;
|
|
}
|
|
|
|
void makeCts_1Point(bool& selAllowed, Base::Vector2d onSketchPos)
|
|
{
|
|
//distance, lock
|
|
if (availableConstraint == AvailableConstraint::FIRST) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add 'Distance to origin' constraint"));
|
|
createDistanceConstrain(selPoints[0].GeoId, selPoints[0].PosId, Sketcher::GeoEnum::RtPnt, Sketcher::PointPos::start, onSketchPos);
|
|
selAllowed = true;
|
|
}
|
|
if (availableConstraint == AvailableConstraint::SECOND) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add lock constraint"));
|
|
specialConstraint = SpecialConstraint::Block;
|
|
createDistanceXYConstrain(Sketcher::DistanceX, selPoints[0].GeoId, selPoints[0].PosId, Sketcher::GeoEnum::RtPnt, Sketcher::PointPos::start, onSketchPos);
|
|
createDistanceXYConstrain(Sketcher::DistanceY, selPoints[0].GeoId, selPoints[0].PosId, Sketcher::GeoEnum::RtPnt, Sketcher::PointPos::start, onSketchPos);
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
|
|
void makeCts_2Point(bool& selAllowed, Base::Vector2d onSketchPos)
|
|
{
|
|
//distance, horizontal, vertical
|
|
if (availableConstraint == AvailableConstraint::FIRST) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Distance constraint"));
|
|
createDistanceConstrain(selPoints[0].GeoId, selPoints[0].PosId, selPoints[1].GeoId, selPoints[1].PosId, onSketchPos);
|
|
selAllowed = true;
|
|
}
|
|
if (availableConstraint == AvailableConstraint::SECOND) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add 'Horizontal' constraints"));
|
|
createHorizontalConstrain(selPoints[0].GeoId, selPoints[0].PosId, selPoints[1].GeoId, selPoints[1].PosId);
|
|
}
|
|
if (availableConstraint == AvailableConstraint::THIRD) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add 'Vertical' constraints"));
|
|
createVerticalConstrain(selPoints[0].GeoId, selPoints[0].PosId, selPoints[1].GeoId, selPoints[1].PosId);
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
|
|
void makeCts_1Point1Line(bool& selAllowed, Base::Vector2d onSketchPos)
|
|
{
|
|
//distance, Symmetry
|
|
if (availableConstraint == AvailableConstraint::FIRST) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add point to line Distance constraint"));
|
|
createDistanceConstrain(selPoints[0].GeoId, selPoints[0].PosId, selLine[0].GeoId, selLine[0].PosId, onSketchPos); // point to be on first parameter
|
|
selAllowed = true;
|
|
}
|
|
if (availableConstraint == AvailableConstraint::SECOND) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Symmetry constraint"));
|
|
createSymmetryConstrain(selLine[0].GeoId, Sketcher::PointPos::start, selLine[0].GeoId, Sketcher::PointPos::end, selPoints[0].GeoId, selPoints[0].PosId);
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
|
|
void makeCts_3Point(bool& selAllowed, size_t s_pts)
|
|
{
|
|
//Horizontal, vertical, symmetry
|
|
if (s_pts > 0 && availableConstraint == AvailableConstraint::FIRST) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add 'Horizontal' constraints"));
|
|
for (size_t i = 0; i < s_pts - 1; i++) {
|
|
createHorizontalConstrain(selPoints[i].GeoId, selPoints[i].PosId, selPoints[i + 1].GeoId, selPoints[i + 1].PosId);
|
|
}
|
|
selAllowed = true;
|
|
}
|
|
if (s_pts > 0 && availableConstraint == AvailableConstraint::SECOND) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add 'Vertical' constraints"));
|
|
for (size_t i = 0; i < s_pts - 1; i++) {
|
|
createVerticalConstrain(selPoints[i].GeoId, selPoints[i].PosId, selPoints[i + 1].GeoId, selPoints[i + 1].PosId);
|
|
}
|
|
}
|
|
if (availableConstraint == AvailableConstraint::THIRD) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Symmetry constraints"));
|
|
createSymmetryConstrain(selPoints[0].GeoId, selPoints[0].PosId, selPoints[1].GeoId, selPoints[1].PosId, selPoints[2].GeoId, selPoints[2].PosId);
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
|
|
void makeCts_1Point1Spline1MoreEdge(bool& /*selAllowed*/)
|
|
{
|
|
//angle
|
|
if (availableConstraint == AvailableConstraint::FIRST) {
|
|
// FIXME: Once everything is implemented uncomment restartCommand and setAllowed
|
|
// restartCommand(QT_TRANSLATE_NOOP("Command", "Add 'Angle' constraint"));
|
|
// TODO: Find the appropriate geoids and call createAngleConstrain
|
|
// selAllowed = true;
|
|
}
|
|
}
|
|
|
|
void makeCts_4MorePoint(bool& selAllowed, size_t s_pts)
|
|
{
|
|
//Horizontal, vertical
|
|
if (s_pts > 0 && availableConstraint == AvailableConstraint::FIRST) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add 'Horizontal' constraints"));
|
|
for (size_t i = 0; i < s_pts - 1; i++) {
|
|
createHorizontalConstrain(selPoints[i].GeoId, selPoints[i].PosId, selPoints[i + 1].GeoId, selPoints[i + 1].PosId);
|
|
}
|
|
selAllowed = true;
|
|
}
|
|
if (s_pts > 0 && availableConstraint == AvailableConstraint::SECOND) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add 'Vertical' constraints"));
|
|
for (size_t i = 0; i < s_pts - 1; i++) {
|
|
createVerticalConstrain(selPoints[i].GeoId, selPoints[i].PosId, selPoints[i + 1].GeoId, selPoints[i + 1].PosId);
|
|
}
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
|
|
void makeCts_2Point1Line(bool& selAllowed, Base::Vector2d onSketchPos, size_t s_pts)
|
|
{
|
|
//symmetry, distances
|
|
if (availableConstraint == AvailableConstraint::FIRST) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Symmetry constraint"));
|
|
createSymmetryConstrain(selPoints[0].GeoId, selPoints[0].PosId, selPoints[1].GeoId, selPoints[1].PosId, selLine[0].GeoId, selLine[0].PosId);
|
|
selAllowed = true;
|
|
}
|
|
if (availableConstraint == AvailableConstraint::SECOND) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Distance constraints"));
|
|
for (size_t i = 0; i < s_pts; i++) {
|
|
createDistanceConstrain(selPoints[i].GeoId, selPoints[i].PosId, selLine[0].GeoId, selLine[0].PosId, onSketchPos);
|
|
}
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
|
|
void makeCts_3MorePoint1Line(bool& selAllowed, Base::Vector2d onSketchPos, size_t s_pts)
|
|
{
|
|
//distances
|
|
if (availableConstraint == AvailableConstraint::FIRST) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Distance constraints"));
|
|
for (size_t i = 0; i < s_pts; i++) {
|
|
createDistanceConstrain(selPoints[i].GeoId, selPoints[i].PosId, selLine[0].GeoId, selLine[0].PosId, onSketchPos);
|
|
}
|
|
selAllowed = true;
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
|
|
void makeCts_1Point1Circle(bool& selAllowed, Base::Vector2d onSketchPos)
|
|
{
|
|
//Distance
|
|
if (availableConstraint == AvailableConstraint::FIRST) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add length constraint"));
|
|
createDistanceConstrain(selPoints[0].GeoId, selPoints[0].PosId, selCircleArc[0].GeoId, selCircleArc[0].PosId, onSketchPos);
|
|
selAllowed = true;
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
|
|
void makeCts_1MorePoint1Ellipse(bool& selAllowed)
|
|
{
|
|
Q_UNUSED(selAllowed)
|
|
//distance between 1 point and ellipse/arc of... not supported yet.
|
|
if (availableConstraint == AvailableConstraint::FIRST) {
|
|
//nothing yet
|
|
//availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
|
|
void makeCts_1Line(bool& selAllowed, Base::Vector2d onSketchPos)
|
|
{
|
|
//axis can be selected but we don't want distance on axis!
|
|
if ((selLine[0].GeoId != Sketcher::GeoEnum::VAxis && selLine[0].GeoId != Sketcher::GeoEnum::HAxis)) {
|
|
//distance, horizontal, vertical, block
|
|
if (availableConstraint == AvailableConstraint::FIRST) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add length constraint"));
|
|
createDistanceConstrain(selLine[0].GeoId, Sketcher::PointPos::start, selLine[0].GeoId, Sketcher::PointPos::end, onSketchPos);
|
|
selAllowed = true;
|
|
}
|
|
if (availableConstraint == AvailableConstraint::SECOND) {
|
|
if (isHorizontalVerticalBlock(selLine[0].GeoId)) {
|
|
//if the line has a vertical horizontal or block constraint then we don't switch to other modes as they are horizontal, vertical and block.
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
else {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Horizontal constraint"));
|
|
createHorizontalConstrain(selLine[0].GeoId, Sketcher::PointPos::none, GeoEnum::GeoUndef, Sketcher::PointPos::none);
|
|
}
|
|
}
|
|
if (availableConstraint == AvailableConstraint::THIRD) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Vertical constraint"));
|
|
createVerticalConstrain(selLine[0].GeoId, Sketcher::PointPos::none, GeoEnum::GeoUndef, Sketcher::PointPos::none);
|
|
}
|
|
if (availableConstraint == AvailableConstraint::FOURTH) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Block constraint"));
|
|
createBlockConstrain(selLine[0].GeoId);
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
else {
|
|
//But axis can still be selected
|
|
selAllowed = true;
|
|
}
|
|
}
|
|
|
|
void makeCts_2Line(bool& selAllowed, Base::Vector2d onSketchPos)
|
|
{
|
|
//angle (if parallel: Distance (see in createAngleConstrain)), equal.
|
|
if (availableConstraint == AvailableConstraint::FIRST) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Angle constraint"));
|
|
createAngleConstrain(selLine[0].GeoId, selLine[1].GeoId, onSketchPos);
|
|
selAllowed = true;
|
|
}
|
|
if (availableConstraint == AvailableConstraint::SECOND) {
|
|
if (selLine[0].GeoId == Sketcher::GeoEnum::VAxis || selLine[1].GeoId == Sketcher::GeoEnum::VAxis
|
|
|| selLine[0].GeoId == Sketcher::GeoEnum::HAxis || selLine[1].GeoId == Sketcher::GeoEnum::HAxis) {
|
|
//if one line is axis, then can't equal..
|
|
}
|
|
else {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Equality constraint"));
|
|
createEqualityConstrain(selLine[0].GeoId, selLine[1].GeoId);
|
|
}
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
|
|
void makeCts_3MoreLine(bool& selAllowed, size_t s_lns)
|
|
{
|
|
//equality.
|
|
if (s_lns > 0 && availableConstraint == AvailableConstraint::FIRST) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Equality constraints"));
|
|
for (size_t i = 0; i < s_lns - 1; i++) {
|
|
createEqualityConstrain(selLine[i].GeoId, selLine[i + 1].GeoId);
|
|
}
|
|
selAllowed = true;
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
|
|
void makeCts_1Line1Circle(bool& selAllowed, Base::Vector2d onSketchPos)
|
|
{
|
|
//Distance
|
|
if (availableConstraint == AvailableConstraint::FIRST) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add length constraint"));
|
|
createDistanceConstrain(selCircleArc[0].GeoId, selCircleArc[0].PosId, selLine[0].GeoId, selLine[0].PosId, onSketchPos); //Line second parameter
|
|
selAllowed = true;
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
|
|
void makeCts_1Line2Circle(bool& selAllowed)
|
|
{
|
|
//symmetry.
|
|
if (availableConstraint == AvailableConstraint::FIRST) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Symmetry constraints"));
|
|
createSymmetryConstrain(selCircleArc[0].GeoId, Sketcher::PointPos::mid, selCircleArc[1].GeoId, Sketcher::PointPos::mid, selLine[0].GeoId, selLine[0].PosId);
|
|
selAllowed = true;
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
|
|
void makeCts_1Line1Ellipse(bool& selAllowed)
|
|
{
|
|
Q_UNUSED(selAllowed)
|
|
//TODO distance between line and ellipse/arc of... not supported yet.
|
|
if (availableConstraint == AvailableConstraint::FIRST) {
|
|
//selAllowed = true;
|
|
//availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
|
|
void makeCts_1Circle(bool& selAllowed, Base::Vector2d onSketchPos)
|
|
{
|
|
int geoId = selCircleArc[0].GeoId;
|
|
bool reverseOrder = isRadiusDoF(geoId);
|
|
|
|
if (reverseOrder) {
|
|
if (availableConstraint == AvailableConstraint::FIRST) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add arc angle constraint"));
|
|
createArcAngleConstrain(geoId, onSketchPos);
|
|
selAllowed = true;
|
|
}
|
|
if (availableConstraint == AvailableConstraint::SECOND) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add arc length constraint"));
|
|
createArcLengthConstrain(geoId, onSketchPos);
|
|
}
|
|
if (availableConstraint == AvailableConstraint::THIRD) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Radius constraint"));
|
|
createRadiusDiameterConstrain(geoId, onSketchPos, true);
|
|
}
|
|
if (availableConstraint == AvailableConstraint::FOURTH) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Radius constraint"));
|
|
createRadiusDiameterConstrain(geoId, onSketchPos, false);
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
else {
|
|
if (availableConstraint == AvailableConstraint::FIRST) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Radius constraint"));
|
|
createRadiusDiameterConstrain(geoId, onSketchPos, true);
|
|
selAllowed = true;
|
|
}
|
|
if (availableConstraint == AvailableConstraint::SECOND) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Radius constraint"));
|
|
createRadiusDiameterConstrain(geoId, onSketchPos, false);
|
|
if (!isArcOfCircle(*Obj->getGeometry(geoId))) {
|
|
//This way if key is pressed again it goes back to FIRST
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
if (availableConstraint == AvailableConstraint::THIRD) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add arc angle constraint"));
|
|
createArcAngleConstrain(geoId, onSketchPos);
|
|
}
|
|
if (availableConstraint == AvailableConstraint::FOURTH) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add arc length constraint"));
|
|
createArcLengthConstrain(geoId, onSketchPos);
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void makeCts_2Circle(bool& selAllowed, Base::Vector2d onSketchPos)
|
|
{
|
|
//Distance, radial distance, equality
|
|
if (availableConstraint == AvailableConstraint::FIRST) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add length constraint"));
|
|
createDistanceConstrain(selCircleArc[0].GeoId, selCircleArc[0].PosId, selCircleArc[1].GeoId, selCircleArc[1].PosId, onSketchPos);
|
|
selAllowed = true;
|
|
}
|
|
if (availableConstraint == AvailableConstraint::SECOND) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add concentric and length constraint"));
|
|
bool created = createCoincidenceConstrain(selCircleArc[0].GeoId, Sketcher::PointPos::mid, selCircleArc[1].GeoId, Sketcher::PointPos::mid);
|
|
if (!created) { //Already concentric, so skip to next
|
|
availableConstraint = AvailableConstraint::THIRD;
|
|
}
|
|
else {
|
|
createDistanceConstrain(selCircleArc[0].GeoId, selCircleArc[0].PosId, selCircleArc[1].GeoId, selCircleArc[1].PosId, onSketchPos);
|
|
}
|
|
}
|
|
if (availableConstraint == AvailableConstraint::THIRD) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Equality constraint"));
|
|
createEqualityConstrain(selCircleArc[0].GeoId, selCircleArc[1].GeoId);
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
|
|
void makeCts_3MoreCircle(bool& selAllowed, size_t s_cir)
|
|
{
|
|
//equality.
|
|
if (s_cir > 0 && availableConstraint == AvailableConstraint::FIRST) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Equality constraint"));
|
|
for (size_t i = 0; i < s_cir - 1; i++) {
|
|
createEqualityConstrain(selCircleArc[i].GeoId, selCircleArc[i + 1].GeoId);
|
|
}
|
|
selAllowed = true;
|
|
availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
|
|
void makeCts_1Circle1Ellipse(bool& selAllowed)
|
|
{
|
|
Q_UNUSED(selAllowed)
|
|
//TODO distance between circle and ellipse/arc of... not supported yet.
|
|
if (availableConstraint == AvailableConstraint::FIRST) {
|
|
//selAllowed = true;
|
|
//availableConstraint = AvailableConstraint::RESET;
|
|
}
|
|
}
|
|
|
|
void makeCts_1Ellipse(bool& selAllowed)
|
|
{
|
|
//One ellipse or arc of ellipse/hyperbola/parabola - no constrain to attribute
|
|
selAllowed = true;
|
|
}
|
|
|
|
void makeCts_2MoreEllipse(bool& selAllowed, size_t s_ell)
|
|
{
|
|
//only ellipse or arc of of same kind, then equality of all radius.
|
|
bool allTheSame = 1;
|
|
const Part::Geometry* geom = Obj->getGeometry(selEllipseAndCo[0].GeoId);
|
|
Base::Type typeOf = geom->getTypeId();
|
|
for (size_t i = 1; i < s_ell; i++) {
|
|
const Part::Geometry* geomi = Obj->getGeometry(selEllipseAndCo[i].GeoId);
|
|
if (typeOf != geomi->getTypeId()) {
|
|
allTheSame = 0;
|
|
}
|
|
}
|
|
if (allTheSame) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Equality constraint"));
|
|
for (size_t i = 1; i < s_ell; i++) {
|
|
createEqualityConstrain(selEllipseAndCo[0].GeoId, selEllipseAndCo[i].GeoId);
|
|
}
|
|
selAllowed = true;
|
|
}
|
|
}
|
|
|
|
void createDistanceConstrain(int GeoId1, Sketcher::PointPos PosId1, int GeoId2, Sketcher::PointPos PosId2, Base::Vector2d onSketchPos) {
|
|
// If there's a point, it must be GeoId1. We could add a swap to make sure but as it's hardcoded it's not necessary.
|
|
|
|
if (GeoId1 == GeoId2 || (PosId1 != Sketcher::PointPos::none && PosId2 != Sketcher::PointPos::none)) {
|
|
specialConstraint = SpecialConstraint::LineOr2PointsDistance;
|
|
}
|
|
|
|
bool arebothpointsorsegmentsfixed = isPointOrSegmentFixed(Obj, GeoId1) && isPointOrSegmentFixed(Obj, GeoId2);
|
|
// Point-line case and point-circle/arc
|
|
if (PosId1 != Sketcher::PointPos::none && PosId2 == Sketcher::PointPos::none) {
|
|
Base::Vector3d pnt = Obj->getPoint(GeoId1, PosId1);
|
|
double ActDist = 0.;
|
|
const Part::Geometry* geom = Obj->getGeometry(GeoId2);
|
|
|
|
if (isLineSegment(*geom)) {
|
|
auto lineSeg = static_cast<const Part::GeomLineSegment*>(geom);
|
|
Base::Vector3d pnt1 = lineSeg->getStartPoint();
|
|
Base::Vector3d pnt2 = lineSeg->getEndPoint();
|
|
Base::Vector3d d = pnt2 - pnt1;
|
|
ActDist = std::abs(-pnt.x * d.y + pnt.y * d.x + pnt1.x * pnt2.y - pnt2.x * pnt1.y) / d.Length();
|
|
}
|
|
else if (isCircle(*geom)) {
|
|
auto circle = static_cast<const Part::GeomCircle*>(geom);
|
|
Base::Vector3d ct = circle->getCenter();
|
|
Base::Vector3d di = ct - pnt;
|
|
ActDist = std::abs(di.Length() - circle->getRadius());
|
|
}
|
|
else if (isArcOfCircle(*geom)) {
|
|
auto arc = static_cast<const Part::GeomArcOfCircle*>(geom);
|
|
Base::Vector3d ct = arc->getCenter();
|
|
Base::Vector3d di = ct - pnt;
|
|
ActDist = std::abs(di.Length() - arc->getRadius());
|
|
}
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%f)) ",
|
|
GeoId1, static_cast<int>(PosId1), GeoId2, ActDist);
|
|
}
|
|
// Circle/arc - line, circle/arc - circle/arc cases
|
|
else if (PosId1 == Sketcher::PointPos::none
|
|
&& PosId2 == Sketcher::PointPos::none) {
|
|
const Part::Geometry* geo1 = Obj->getGeometry(GeoId1);
|
|
const Part::Geometry* geo2 = Obj->getGeometry(GeoId2);
|
|
double radius1, radius2;
|
|
Base::Vector3d center1, center2;
|
|
if (isCircle(*geo1)) {
|
|
auto conic = static_cast<const Part::GeomCircle*>(geo1);
|
|
radius1 = conic->getRadius();
|
|
center1 = conic->getCenter();
|
|
}
|
|
else if (isArcOfCircle(*geo1)) {
|
|
auto conic = static_cast<const Part::GeomArcOfCircle*>(geo1);
|
|
radius1 = conic->getRadius();
|
|
center1 = conic->getCenter();
|
|
}
|
|
if (isCircle(*geo2)) {
|
|
auto conic = static_cast<const Part::GeomCircle*>(geo2);
|
|
radius2 = conic->getRadius();
|
|
center2 = conic->getCenter();
|
|
}
|
|
else if (isArcOfCircle(*geo2)){
|
|
auto conic = static_cast<const Part::GeomArcOfCircle*>(geo2);
|
|
radius2 = conic->getRadius();
|
|
center2 = conic->getCenter();
|
|
}
|
|
// Circle/arc - line case
|
|
if ((isCircle(*geo1) || isArcOfCircle(*geo1)) && isLineSegment(*geo2)) {
|
|
auto lineSeg = static_cast<const Part::GeomLineSegment*>(geo2);
|
|
Base::Vector3d pnt1 = lineSeg->getStartPoint();
|
|
Base::Vector3d pnt2 = lineSeg->getEndPoint();
|
|
Base::Vector3d d = pnt2 - pnt1;
|
|
double ActDist =
|
|
std::abs(-center1.x * d.y + center1.y * d.x + pnt1.x * pnt2.y - pnt2.x * pnt1.y)
|
|
/ d.Length()
|
|
- radius1;
|
|
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Distance',%d,%d,%f))",
|
|
GeoId1,
|
|
GeoId2,
|
|
ActDist);
|
|
}
|
|
// Circle/arc - circle/arc case
|
|
else if ((isCircle(*geo1) || isArcOfCircle(*geo1))
|
|
&& (isCircle(*geo2) || isArcOfCircle(*geo2))) {
|
|
double ActDist = 0.;
|
|
|
|
Base::Vector3d intercenter = center1 - center2;
|
|
double intercenterdistance = intercenter.Length();
|
|
|
|
if (intercenterdistance >= radius1 && intercenterdistance >= radius2) {
|
|
|
|
ActDist = intercenterdistance - radius1 - radius2;
|
|
}
|
|
else {
|
|
double bigradius = std::max(radius1, radius2);
|
|
double smallradius = std::min(radius1, radius2);
|
|
|
|
ActDist = bigradius - smallradius - intercenterdistance;
|
|
}
|
|
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Distance',%d,%d,%f))",
|
|
GeoId1,
|
|
GeoId2,
|
|
ActDist);
|
|
}
|
|
}
|
|
else { // both points
|
|
Base::Vector3d pnt1 = Obj->getPoint(GeoId1, PosId1);
|
|
Base::Vector3d pnt2 = Obj->getPoint(GeoId2, PosId2);
|
|
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%d,%f)) ",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2),
|
|
(pnt2 - pnt1).Length());
|
|
}
|
|
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
if (arebothpointsorsegmentsfixed || GeoId1 <= Sketcher::GeoEnum::RefExt
|
|
|| constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%i,%s)", ConStr.size() - 1, "False");
|
|
}
|
|
|
|
numberOfConstraintsCreated++;
|
|
moveConstraint(ConStr.size() - 1, onSketchPos);
|
|
}
|
|
|
|
void createDistanceXYConstrain(Sketcher::ConstraintType type, int GeoId1, Sketcher::PointPos PosId1, int GeoId2, Sketcher::PointPos PosId2, Base::Vector2d onSketchPos) {
|
|
Base::Vector3d pnt1 = Obj->getPoint(GeoId1, PosId1);
|
|
Base::Vector3d pnt2 = Obj->getPoint(GeoId2, PosId2);
|
|
double ActLength = pnt2.x - pnt1.x;
|
|
|
|
if (type == Sketcher::DistanceY) {
|
|
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;
|
|
}
|
|
|
|
if (type == Sketcher::DistanceY) {
|
|
Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%d,%d,%f)) ",
|
|
GeoId1, static_cast<int>(PosId1), GeoId2, static_cast<int>(PosId2), ActLength);
|
|
}
|
|
else {
|
|
Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%d,%d,%f)) ",
|
|
GeoId1, static_cast<int>(PosId1), GeoId2, static_cast<int>(PosId2), ActLength);
|
|
}
|
|
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2) || constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%i,%s)",
|
|
ConStr.size() - 1, "False");
|
|
}
|
|
|
|
numberOfConstraintsCreated++;
|
|
moveConstraint(ConStr.size() - 1, onSketchPos);
|
|
}
|
|
|
|
void createRadiusDiameterConstrain(int GeoId, Base::Vector2d onSketchPos, bool firstCstr) {
|
|
double radius = 0.0;
|
|
bool isCircleGeom = true;
|
|
|
|
const Part::Geometry* geom = Obj->getGeometry(GeoId);
|
|
|
|
if(!geom)
|
|
return;
|
|
|
|
if (geom && isArcOfCircle(*geom)) {
|
|
auto arc = static_cast<const Part::GeomArcOfCircle*>(geom);
|
|
radius = arc->getRadius();
|
|
isCircleGeom = false;
|
|
}
|
|
else if (geom && isCircle(*geom)) {
|
|
auto circle = static_cast<const Part::GeomCircle*>(geom);
|
|
radius = circle->getRadius();
|
|
}
|
|
|
|
if (isBsplinePole(geom)) {
|
|
Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Weight',%d,%f)) ",
|
|
GeoId, radius);
|
|
}
|
|
else {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/dimensioning");
|
|
bool dimensioningDiameter = hGrp->GetBool("DimensioningDiameter", true);
|
|
bool dimensioningRadius = hGrp->GetBool("DimensioningRadius", true);
|
|
|
|
if ((firstCstr && dimensioningRadius && !dimensioningDiameter) ||
|
|
(!firstCstr && !dimensioningRadius && dimensioningDiameter) ||
|
|
(firstCstr && dimensioningRadius && dimensioningDiameter && !isCircleGeom) ||
|
|
(!firstCstr && dimensioningRadius && dimensioningDiameter && isCircleGeom) ) {
|
|
Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Radius',%d,%f)) ",
|
|
GeoId, radius);
|
|
}
|
|
else {
|
|
Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Diameter',%d,%f)) ",
|
|
GeoId, radius * 2);
|
|
}
|
|
}
|
|
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
bool fixed = isPointOrSegmentFixed(Obj, GeoId);
|
|
if (fixed || constraintCreationMode == Reference || GeoId <= Sketcher::GeoEnum::RefExt) {
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%i,%s)", ConStr.size() - 1, "False");
|
|
}
|
|
|
|
moveConstraint(ConStr.size() - 1, onSketchPos);
|
|
|
|
numberOfConstraintsCreated ++;
|
|
}
|
|
|
|
bool createCoincidenceConstrain(int GeoId1, Sketcher::PointPos PosId1, int GeoId2, Sketcher::PointPos PosId2) {
|
|
// check if the edge already has a Block constraint
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
|
|
return false;
|
|
}
|
|
|
|
// check if this coincidence is already enforced (even indirectly)
|
|
bool constraintExists = Obj->arePointsCoincident(GeoId1, PosId1, GeoId2, PosId2);
|
|
if (!constraintExists && (GeoId1 != GeoId2)) {
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Coincident', %d, %d, %d, %d)) ",
|
|
GeoId1, static_cast<int>(PosId1), GeoId2, static_cast<int>(PosId2));
|
|
|
|
numberOfConstraintsCreated++;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void createEqualityConstrain(int GeoId1, int GeoId2) {
|
|
// check if the edge already has a Block constraint
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
|
|
return;
|
|
}
|
|
|
|
const Part::Geometry* geo1 = Obj->getGeometry(GeoId1);
|
|
const Part::Geometry* geo2 = Obj->getGeometry(GeoId2);
|
|
|
|
if ((isLineSegment(*geo1) && ! isLineSegment(*geo2))
|
|
|| (isArcOfHyperbola(*geo1) && ! isArcOfHyperbola(*geo2))
|
|
|| (isArcOfParabola(*geo1) && ! isArcOfParabola(*geo2))
|
|
|| (isBsplinePole(geo1) && !isBsplinePole(geo2))
|
|
|| ((isCircle(*geo1) || isArcOfCircle(*geo1)) && !(isCircle(*geo2) || isArcOfCircle(*geo2)))
|
|
|| ((isEllipse(*geo1) || isArcOfEllipse(*geo1)) && !(isEllipse(*geo2) || isArcOfEllipse(*geo2)))) {
|
|
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select two or more edges of similar type."));
|
|
return;
|
|
}
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Equal',%d,%d)) ",
|
|
GeoId1, GeoId2);
|
|
numberOfConstraintsCreated++;
|
|
}
|
|
|
|
void createAngleConstrain(int GeoId1, int GeoId2, Base::Vector2d onSketchPos) {
|
|
Sketcher::PointPos PosId1 = Sketcher::PointPos::none;
|
|
Sketcher::PointPos PosId2 = Sketcher::PointPos::none;
|
|
double ActAngle;
|
|
|
|
if (!calculateAngle(Obj, GeoId1, GeoId2, PosId1, PosId2, ActAngle)) {
|
|
return;
|
|
}
|
|
|
|
|
|
if (ActAngle == 0.0) {
|
|
//Here we are sure that GeoIds are lines. So 0.0 means that lines are parallel, we change to distance
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Distance constraint"));
|
|
createDistanceConstrain(selLine[1].GeoId, Sketcher::PointPos::start, selLine[0].GeoId, selLine[0].PosId, onSketchPos);
|
|
return;
|
|
}
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Angle',%d,%d,%d,%d,%f)) ",
|
|
GeoId1, static_cast<int>(PosId1), GeoId2, static_cast<int>(PosId2), ActAngle);
|
|
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2) || constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%i,%s)", ConStr.size() - 1, "False");
|
|
}
|
|
numberOfConstraintsCreated++;
|
|
moveConstraint(ConStr.size() - 1, onSketchPos);
|
|
}
|
|
|
|
void createArcLengthConstrain(int GeoId, Base::Vector2d onSketchPos) {
|
|
const Part::Geometry* geom = Obj->getGeometry(GeoId);
|
|
if (isArcOfCircle(*geom)) {
|
|
|
|
const auto* arc = static_cast<const Part::GeomArcOfCircle*>(geom);
|
|
double ActLength = arc->getAngle(false) * arc->getRadius();
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Distance',%d,%f))",
|
|
GeoId, ActLength);
|
|
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
if (isPointOrSegmentFixed(Obj, GeoId) || constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%i,%s)", ConStr.size() - 1, "False");
|
|
}
|
|
numberOfConstraintsCreated++;
|
|
moveConstraint(ConStr.size() - 1, onSketchPos);
|
|
}
|
|
}
|
|
|
|
void createArcAngleConstrain(int GeoId, Base::Vector2d onSketchPos) {
|
|
const Part::Geometry* geom = Obj->getGeometry(GeoId);
|
|
if (isArcOfCircle(*geom)) {
|
|
|
|
const auto* arc = static_cast<const Part::GeomArcOfCircle*>(geom);
|
|
double angle = arc->getAngle(/*EmulateCCWXY=*/true);
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Angle',%d,%f))",
|
|
GeoId, angle);
|
|
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
if (isPointOrSegmentFixed(Obj, GeoId) || constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%i,%s)", ConStr.size() - 1, "False");
|
|
}
|
|
numberOfConstraintsCreated++;
|
|
moveConstraint(ConStr.size() - 1, onSketchPos);
|
|
}
|
|
}
|
|
|
|
void createVerticalConstrain(int GeoId1, Sketcher::PointPos PosId1, int GeoId2, Sketcher::PointPos PosId2) {
|
|
if (selLine.size() == 1) {
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Vertical',%d)) ", GeoId1);
|
|
}
|
|
else { //2points
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
|
|
return;
|
|
}
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Vertical',%d,%d,%d,%d)) "
|
|
, GeoId1, static_cast<int>(PosId1), GeoId2, static_cast<int>(PosId2));
|
|
}
|
|
numberOfConstraintsCreated++;
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
void createHorizontalConstrain(int GeoId1, Sketcher::PointPos PosId1, int GeoId2, Sketcher::PointPos PosId2) {
|
|
if (selLine.size() == 1) {
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Horizontal',%d)) ", GeoId1);
|
|
}
|
|
else { //2points
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
|
|
return;
|
|
}
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Horizontal',%d,%d,%d,%d)) "
|
|
, GeoId1, static_cast<int>(PosId1), GeoId2, static_cast<int>(PosId2));
|
|
}
|
|
numberOfConstraintsCreated++;
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
|
|
void createBlockConstrain(int GeoId) {
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Block',%d)) ", GeoId);
|
|
|
|
numberOfConstraintsCreated++;
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
|
|
bool isHorizontalVerticalBlock(int GeoId) {
|
|
const std::vector< Sketcher::Constraint* >& vals = Obj->Constraints.getValues();
|
|
|
|
// check if the edge already has a Horizontal/Vertical/Block constraint
|
|
for (const auto& constraint : vals) {
|
|
if ((constraint->Type == Sketcher::Horizontal || constraint->Type == Sketcher::Vertical || constraint->Type == Sketcher::Block)
|
|
&& constraint->First == GeoId) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void createSymmetryConstrain(int GeoId1, Sketcher::PointPos PosId1, int GeoId2, Sketcher::PointPos PosId2, int GeoId3, Sketcher::PointPos PosId3) {
|
|
if (selPoints.size() == 2 && selLine.size() == 1) {
|
|
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)) {
|
|
return;
|
|
}
|
|
|
|
const Part::Geometry* geom = Obj->getGeometry(GeoId3);
|
|
|
|
if (isLineSegment(*geom)) {
|
|
if (GeoId1 == GeoId2 && GeoId2 == GeoId3) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Cannot add a symmetry constraint between a line and its end points!"));
|
|
return;
|
|
}
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d)) ",
|
|
GeoId1, static_cast<int>(PosId1), GeoId2, static_cast<int>(PosId2), GeoId3);
|
|
|
|
numberOfConstraintsCreated++;
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
}
|
|
else {
|
|
if (selPoints.size() == 1 && selLine.size() == 1) { //1line 1 point
|
|
if (GeoId1 == GeoId3) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Cannot add a symmetry constraint between a line and its end points!"));
|
|
return;
|
|
}
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
if (areAllPointsOrSegmentsFixed(Obj, GeoId1, GeoId2, GeoId3)) {
|
|
return;
|
|
}
|
|
}
|
|
Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d,%d)) ",
|
|
GeoId1, static_cast<int>(PosId1), GeoId2, static_cast<int>(PosId2), GeoId3, static_cast<int>(PosId3));
|
|
|
|
numberOfConstraintsCreated++;
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
}
|
|
|
|
void updateDistanceType(Base::Vector2d onSketchPos)
|
|
{
|
|
const std::vector< Sketcher::Constraint* >& vals = Obj->Constraints.getValues();
|
|
Sketcher::ConstraintType type = vals[vals.size() - 1]->Type;
|
|
|
|
Base::Vector3d pnt1, pnt2;
|
|
bool addedOrigin = false;
|
|
if (selPoints.size() == 1) {
|
|
//Case of single point selected, for distance constraint. We add temporarily the origin in the vector.
|
|
addedOrigin = true;
|
|
SelIdPair selIdPair;
|
|
selIdPair.GeoId = Sketcher::GeoEnum::RtPnt;
|
|
selIdPair.PosId = Sketcher::PointPos::start;
|
|
selPoints.push_back(selIdPair);
|
|
}
|
|
|
|
if (selLine.size() == 1) {
|
|
pnt1 = Obj->getPoint(selLine[0].GeoId, Sketcher::PointPos::start);
|
|
pnt2 = Obj->getPoint(selLine[0].GeoId, Sketcher::PointPos::end);
|
|
}
|
|
else {
|
|
pnt1 = Obj->getPoint(selPoints[0].GeoId, selPoints[0].PosId);
|
|
pnt2 = Obj->getPoint(selPoints[1].GeoId, selPoints[1].PosId);
|
|
}
|
|
|
|
double minX, minY, maxX, maxY;
|
|
minX = min(pnt1.x, pnt2.x);
|
|
maxX = max(pnt1.x, pnt2.x);
|
|
minY = min(pnt1.y, pnt2.y);
|
|
maxY = max(pnt1.y, pnt2.y);
|
|
if (onSketchPos.x > minX && onSketchPos.x < maxX
|
|
&& (onSketchPos.y < minY || onSketchPos.y > maxY) && type != Sketcher::DistanceX) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add DistanceX constraint"));
|
|
specialConstraint = SpecialConstraint::LineOr2PointsDistance;
|
|
if (selLine.size() == 1) {
|
|
createDistanceXYConstrain(Sketcher::DistanceX, selLine[0].GeoId, Sketcher::PointPos::start, selLine[0].GeoId, Sketcher::PointPos::end, onSketchPos);
|
|
}
|
|
else {
|
|
createDistanceXYConstrain(Sketcher::DistanceX, selPoints[0].GeoId, selPoints[0].PosId, selPoints[1].GeoId, selPoints[1].PosId, onSketchPos);
|
|
}
|
|
}
|
|
else if (onSketchPos.y > minY && onSketchPos.y < maxY
|
|
&& (onSketchPos.x < minX || onSketchPos.x > maxX) && type != Sketcher::DistanceY) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add DistanceY constraint"));
|
|
specialConstraint = SpecialConstraint::LineOr2PointsDistance;
|
|
if (selLine.size() == 1) {
|
|
createDistanceXYConstrain(Sketcher::DistanceY, selLine[0].GeoId, Sketcher::PointPos::start, selLine[0].GeoId, Sketcher::PointPos::end, onSketchPos);
|
|
}
|
|
else {
|
|
createDistanceXYConstrain(Sketcher::DistanceY, selPoints[0].GeoId, selPoints[0].PosId, selPoints[1].GeoId, selPoints[1].PosId, onSketchPos);
|
|
}
|
|
}
|
|
else if ((((onSketchPos.y < minY || onSketchPos.y > maxY) && (onSketchPos.x < minX || onSketchPos.x > maxX))
|
|
|| (onSketchPos.y > minY && onSketchPos.y < maxY && onSketchPos.x > minX && onSketchPos.x < maxX)) && type != Sketcher::Distance) {
|
|
restartCommand(QT_TRANSLATE_NOOP("Command", "Add Distance constraint"));
|
|
if (selLine.size() == 1) {
|
|
createDistanceConstrain(selLine[0].GeoId, Sketcher::PointPos::start, selLine[0].GeoId, Sketcher::PointPos::end, onSketchPos);
|
|
}
|
|
else {
|
|
createDistanceConstrain(selPoints[0].GeoId, selPoints[0].PosId, selPoints[1].GeoId, selPoints[1].PosId, onSketchPos);
|
|
}
|
|
}
|
|
|
|
if (addedOrigin) {
|
|
//remove origin
|
|
selPoints.pop_back();
|
|
}
|
|
}
|
|
|
|
bool isRadiusDoF(int geoId)
|
|
{
|
|
const Part::Geometry* geo = Obj->getGeometry(geoId);
|
|
if (!isArcOfCircle(*geo)) {
|
|
return false;
|
|
}
|
|
|
|
//make sure we are not taking into account the constraint created in previous mode.
|
|
Gui::Command::abortCommand();
|
|
Obj->solve();
|
|
|
|
auto solvext = Obj->getSolvedSketch().getSolverExtension(geoId);
|
|
|
|
if (solvext) {
|
|
auto arcInfo = solvext->getArc();
|
|
|
|
return !arcInfo.isRadiusDoF();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void restartCommand(const char* cstrName) {
|
|
specialConstraint = SpecialConstraint::None;
|
|
Gui::Command::abortCommand();
|
|
Obj->solve();
|
|
sketchgui->draw(false, false); // Redraw
|
|
Gui::Command::openCommand(cstrName);
|
|
|
|
numberOfConstraintsCreated = 0;
|
|
}
|
|
};
|
|
|
|
DEF_STD_CMD_AU(CmdSketcherDimension)
|
|
|
|
CmdSketcherDimension::CmdSketcherDimension()
|
|
: Command("Sketcher_Dimension")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Dimension");
|
|
sToolTipText = QT_TR_NOOP("Constrain contextually based on your selection.\n"
|
|
"Depending on your selection you might have several constraints available. You can cycle through them using M key.\n"
|
|
"Left clicking on empty space will validate the current constraint. Right clicking or pressing Esc will cancel.");
|
|
sWhatsThis = "Sketcher_Dimension";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "Constraint_Dimension";
|
|
sAccel = "D";
|
|
eType = ForEdit;
|
|
}
|
|
|
|
void CmdSketcherDimension::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
App::AutoTransaction::setEnable(false);
|
|
|
|
// get the selection
|
|
std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
|
|
std::vector<std::string> SubNames = {};
|
|
|
|
// only one sketch with its subelements are allowed to be selected
|
|
if (selection.size() == 1 && selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
SubNames = selection[0].getSubNames();
|
|
}
|
|
|
|
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerDimension(SubNames));
|
|
}
|
|
|
|
void CmdSketcherDimension::updateAction(int mode)
|
|
{
|
|
switch (mode) {
|
|
case Reference:
|
|
if (getAction())
|
|
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Dimension_Driven"));
|
|
break;
|
|
case Driving:
|
|
if (getAction())
|
|
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Dimension"));
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool CmdSketcherDimension::isActive(void)
|
|
{
|
|
return isCommandActive(getActiveGuiDocument());
|
|
}
|
|
|
|
// Comp for horizontal/vertical =============================================
|
|
|
|
class CmdSketcherCompHorizontalVertical : public Gui::GroupCommand
|
|
{
|
|
public:
|
|
CmdSketcherCompHorizontalVertical()
|
|
: GroupCommand("Sketcher_CompHorVer")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Constrain horizontal/vertical");
|
|
sToolTipText = QT_TR_NOOP("Constrains a single line to either horizontal or vertical.");
|
|
sWhatsThis = "Sketcher_CompHorVer";
|
|
sStatusTip = sToolTipText;
|
|
eType = ForEdit;
|
|
|
|
setCheckable(false);
|
|
setRememberLast(false);
|
|
|
|
addCommand("Sketcher_ConstrainHorVer");
|
|
addCommand("Sketcher_ConstrainHorizontal");
|
|
addCommand("Sketcher_ConstrainVertical");
|
|
}
|
|
|
|
const char* className() const override { return "CmdSketcherCompHorizontalVertical"; }
|
|
};
|
|
|
|
// ============================================================================
|
|
bool canHorVerBlock(Sketcher::SketchObject* Obj, int geoId)
|
|
{
|
|
const std::vector<Sketcher::Constraint*>& vals = Obj->Constraints.getValues();
|
|
|
|
// check if the edge already has a Horizontal/Vertical/Block constraint
|
|
for (auto& constr : vals) {
|
|
if (constr->Type == Sketcher::Horizontal && constr->First == geoId
|
|
&& constr->FirstPos == Sketcher::PointPos::none) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Double constraint"),
|
|
QObject::tr("The selected edge already has a horizontal constraint!"));
|
|
return false;
|
|
}
|
|
if (constr->Type == Sketcher::Vertical && constr->First == geoId
|
|
&& constr->FirstPos == Sketcher::PointPos::none) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Impossible constraint"),
|
|
QObject::tr("The selected edge already has a vertical constraint!"));
|
|
return false;
|
|
}
|
|
// check if the edge already has a Block constraint
|
|
if (constr->Type == Sketcher::Block && constr->First == geoId
|
|
&& constr->FirstPos == Sketcher::PointPos::none) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Impossible constraint"),
|
|
QObject::tr("The selected edge already has a Block constraint!"));
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void horVerActivated(CmdSketcherConstraint* cmd, std::string type)
|
|
{
|
|
// get the selection
|
|
std::vector<Gui::SelectionObject> selection = Gui::Command::getSelection().getSelectionEx();
|
|
|
|
// only one sketch with its subelements are allowed to be selected
|
|
if (selection.size() != 1
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
|
|
|
|
if (constraintMode) {
|
|
ActivateHandler(cmd->getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(cmd));
|
|
Gui::Command::getSelection().clearSelection();
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(cmd->getActiveGuiDocument(),
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge from the sketch."));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// get the needed lists and objects
|
|
const std::vector<std::string>& SubNames = selection[0].getSubNames();
|
|
auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
std::vector<int> edgegeoids;
|
|
std::vector<int> pointgeoids;
|
|
std::vector<Sketcher::PointPos> pointpos;
|
|
|
|
int fixedpoints = 0;
|
|
|
|
for (auto& name : SubNames) {
|
|
int GeoId;
|
|
Sketcher::PointPos PosId;
|
|
getIdsFromName(name, Obj, GeoId, PosId);
|
|
|
|
if (isEdge(GeoId, PosId)) {// it is an edge
|
|
const Part::Geometry* geo = Obj->getGeometry(GeoId);
|
|
|
|
if (!isLineSegment(*geo)) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Impossible constraint"),
|
|
QObject::tr("The selected edge is not a line segment."));
|
|
return;
|
|
}
|
|
|
|
if (canHorVerBlock(Obj, GeoId)) {
|
|
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.size() < 2) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Impossible constraint"),
|
|
QObject::tr("The selected item(s) can't accept a horizontal or vertical constraint!"));
|
|
return;
|
|
}
|
|
|
|
// if there is at least one edge selected, ignore the point alignment functionality
|
|
if (!edgegeoids.empty()) {
|
|
// undo command open
|
|
const char* cmdName = type == "Horizontal" ? "Add horizontal constraint" : type == "Vertical" ? "Add vertical constraint" : "Add horizontal/vertical constraint";
|
|
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", cmdName));
|
|
for (auto& geoId : edgegeoids) {
|
|
std::string typeToApply = type;
|
|
if (type == "HorVer") {
|
|
const Part::Geometry* geo = Obj->getGeometry(geoId);
|
|
auto* line = static_cast<const Part::GeomLineSegment*>(geo);
|
|
double angle = toVector2d(line->getEndPoint() - line->getStartPoint()).Angle();
|
|
typeToApply = fabs(sin(angle)) < fabs(cos(angle)) ? "Horizontal" : "Vertical";
|
|
}
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('%s',%d))",
|
|
typeToApply,
|
|
geoId);
|
|
}
|
|
}
|
|
else if (fixedpoints <= 1) {// pointgeoids
|
|
// undo command open
|
|
const char* cmdName = type == "Horizontal" ? "Add horizontal alignment" : type == "Vertical" ? "Add vertical alignment" : "Add horizontal/vertical alignment";
|
|
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", cmdName));
|
|
std::vector<int>::iterator it;
|
|
std::vector<Sketcher::PointPos>::iterator itp;
|
|
for (it = pointgeoids.begin(), itp = pointpos.begin();
|
|
it != std::prev(pointgeoids.end()) && itp != std::prev(pointpos.end());
|
|
it++, itp++) {
|
|
|
|
std::string typeToApply = type;
|
|
if (type == "HorVer") {
|
|
auto point1 = Obj->getPoint(*it, *itp);
|
|
auto point2 = Obj->getPoint(*std::next(it), *std::next(itp));
|
|
double angle = toVector2d(point2 - point1).Angle();
|
|
typeToApply = fabs(sin(angle)) < fabs(cos(angle)) ? "Horizontal" : "Vertical";
|
|
}
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('%s',%d,%d,%d,%d))",
|
|
typeToApply,
|
|
*it,
|
|
static_cast<int>(*itp),
|
|
*std::next(it),
|
|
static_cast<int>(*std::next(itp)));
|
|
}
|
|
}
|
|
else {// vertex mode, fixedpoints > 1
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Impossible constraint"),
|
|
QObject::tr("There are more than one fixed points selected. "
|
|
"Select a maximum of one fixed point!"));
|
|
return;
|
|
}
|
|
// finish the transaction and update
|
|
Gui::Command::commitCommand();
|
|
|
|
tryAutoRecompute(Obj);
|
|
|
|
// clear the selection (convenience)
|
|
Gui::Command::getSelection().clearSelection();
|
|
}
|
|
|
|
void horVerApplyConstraint(CmdSketcherConstraint* cmd, std::string type, std::vector<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
auto* sketchgui =
|
|
static_cast<SketcherGui::ViewProviderSketch*>(cmd->getActiveGuiDocument()->getInEdit());
|
|
Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
|
|
|
|
switch (seqIndex) {
|
|
case 0:// {Edge}
|
|
{
|
|
if (selSeq.empty()) {
|
|
return;
|
|
}
|
|
|
|
int CrvId = selSeq.front().GeoId;
|
|
if (CrvId != -1) {
|
|
const Part::Geometry* geo = Obj->getGeometry(CrvId);
|
|
|
|
if (!isLineSegment(*geo)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Impossible constraint"),
|
|
QObject::tr("The selected edge is not a line segment."));
|
|
return;
|
|
}
|
|
|
|
// check if the edge already has a Horizontal/Vertical/Block constraint
|
|
if (!canHorVerBlock(Obj, CrvId)) {
|
|
return;
|
|
}
|
|
|
|
std::string typeToApply = type;
|
|
if (type == "HorVer") {
|
|
const Part::Geometry* geo = Obj->getGeometry(CrvId);
|
|
auto* line = static_cast<const Part::GeomLineSegment*>(geo);
|
|
double angle = toVector2d(line->getEndPoint() - line->getStartPoint()).Angle();
|
|
typeToApply = fabs(sin(angle)) < fabs(cos(angle)) ? "Horizontal" : "Vertical";
|
|
}
|
|
|
|
const char* cmdName = typeToApply == "Horizontal" ? "Add horizontal constraint" : "Add vertical constraint";
|
|
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", cmdName));
|
|
|
|
// issue the actual commands to create the constraint
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(),
|
|
"addConstraint(Sketcher.Constraint('%s',%d))",
|
|
typeToApply,
|
|
CrvId);
|
|
// finish the transaction and update
|
|
Gui::Command::commitCommand();
|
|
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 1:// {SelVertex, SelVertexOrRoot}
|
|
case 2:// {SelRoot, SelVertex}
|
|
{
|
|
int GeoId1, GeoId2;
|
|
Sketcher::PointPos PosId1, PosId2;
|
|
GeoId1 = selSeq.at(0).GeoId;
|
|
GeoId2 = selSeq.at(1).GeoId;
|
|
PosId1 = selSeq.at(0).PosId;
|
|
PosId2 = selSeq.at(1).PosId;
|
|
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
|
|
showNoConstraintBetweenFixedGeometry(Obj);
|
|
return;
|
|
}
|
|
|
|
std::string typeToApply = type;
|
|
if (type == "HorVer") {
|
|
auto point1 = Obj->getPoint(GeoId1, PosId1);
|
|
auto point2 = Obj->getPoint(GeoId2, PosId2);
|
|
double angle = toVector2d(point2 - point1).Angle();
|
|
typeToApply = fabs(sin(angle)) < fabs(cos(angle)) ? "Horizontal" : "Vertical";
|
|
}
|
|
|
|
// undo command open
|
|
const char* cmdName = type == "Horizontal" ? "Add horizontal alignment" : "Add vertical alignment";
|
|
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", cmdName));
|
|
|
|
// issue the actual commands to create the constraint
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(),
|
|
"addConstraint(Sketcher.Constraint('%s',%d,%d,%d,%d))",
|
|
typeToApply,
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2));
|
|
// finish the transaction and update
|
|
Gui::Command::commitCommand();
|
|
|
|
tryAutoRecompute(Obj);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
class CmdSketcherConstrainHorVer : public CmdSketcherConstraint
|
|
{
|
|
public:
|
|
CmdSketcherConstrainHorVer();
|
|
~CmdSketcherConstrainHorVer() override
|
|
{}
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainHorVer";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
};
|
|
|
|
CmdSketcherConstrainHorVer::CmdSketcherConstrainHorVer()
|
|
: CmdSketcherConstraint("Sketcher_ConstrainHorVer")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Constrain horizontal/vertical");
|
|
sToolTipText = QT_TR_NOOP("Constrains a single line to either horizontal or vertical, whichever is closer to current alignment.");
|
|
sWhatsThis = "Sketcher_ConstrainHorVer";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "Constraint_HorVer";
|
|
sAccel = "A";
|
|
eType = ForEdit;
|
|
|
|
allowedSelSequences = { {SelEdge}, {SelVertex, SelVertexOrRoot}, {SelRoot, SelVertex} };
|
|
}
|
|
|
|
void CmdSketcherConstrainHorVer::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
horVerActivated(this, "HorVer");
|
|
}
|
|
|
|
void CmdSketcherConstrainHorVer::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
horVerApplyConstraint(this, "HorVer", selSeq, seqIndex);
|
|
}
|
|
|
|
|
|
// ============================================================================
|
|
|
|
class CmdSketcherConstrainHorizontal: public CmdSketcherConstraint
|
|
{
|
|
public:
|
|
CmdSketcherConstrainHorizontal();
|
|
~CmdSketcherConstrainHorizontal() override
|
|
{}
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainHorizontal";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
};
|
|
|
|
CmdSketcherConstrainHorizontal::CmdSketcherConstrainHorizontal()
|
|
: CmdSketcherConstraint("Sketcher_ConstrainHorizontal")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Constrain horizontal");
|
|
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}, {SelVertex, SelVertexOrRoot}, {SelRoot, SelVertex}};
|
|
}
|
|
|
|
void CmdSketcherConstrainHorizontal::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
horVerActivated(this, "Horizontal");
|
|
}
|
|
|
|
void CmdSketcherConstrainHorizontal::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
horVerApplyConstraint(this, "Horizontal", selSeq, seqIndex);
|
|
}
|
|
|
|
// ================================================================================
|
|
|
|
class CmdSketcherConstrainVertical: public CmdSketcherConstraint
|
|
{
|
|
public:
|
|
CmdSketcherConstrainVertical();
|
|
~CmdSketcherConstrainVertical() override
|
|
{}
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainVertical";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
};
|
|
|
|
CmdSketcherConstrainVertical::CmdSketcherConstrainVertical()
|
|
: CmdSketcherConstraint("Sketcher_ConstrainVertical")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Constrain vertical");
|
|
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}, {SelVertex, SelVertexOrRoot}, {SelRoot, SelVertex}};
|
|
}
|
|
|
|
void CmdSketcherConstrainVertical::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
horVerActivated(this, "Vertical");
|
|
}
|
|
|
|
void CmdSketcherConstrainVertical::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
horVerApplyConstraint(this, "Vertical", selSeq, seqIndex);
|
|
}
|
|
|
|
// ======================================================================================
|
|
|
|
class CmdSketcherConstrainLock: public CmdSketcherConstraint
|
|
{
|
|
public:
|
|
CmdSketcherConstrainLock();
|
|
~CmdSketcherConstrainLock() override
|
|
{}
|
|
void updateAction(int mode) override;
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainLock";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
};
|
|
|
|
CmdSketcherConstrainLock::CmdSketcherConstrainLock()
|
|
: CmdSketcherConstraint("Sketcher_ConstrainLock")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Constrain lock");
|
|
sToolTipText = QT_TR_NOOP("Create both a horizontal "
|
|
"and a vertical distance constraint\n"
|
|
"on the selected vertex");
|
|
sWhatsThis = "Sketcher_ConstrainLock";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "Constraint_Lock";
|
|
sAccel = "K, L";
|
|
eType = ForEdit;
|
|
|
|
allowedSelSequences = {{SelVertex}};
|
|
}
|
|
|
|
void CmdSketcherConstrainLock::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
|
|
// get the selection
|
|
std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
|
|
|
|
// only one sketch with its subelements are allowed to be selected
|
|
if (selection.size() != 1
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
|
|
|
|
if (constraintMode) {
|
|
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(this));
|
|
getSelection().clearSelection();
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(getActiveGuiDocument(),
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select vertices from the sketch."));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// get the needed lists and objects
|
|
const std::vector<std::string>& SubNames = selection[0].getSubNames();
|
|
auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
std::vector<int> GeoId;
|
|
std::vector<Sketcher::PointPos> PosId;
|
|
|
|
for (std::vector<std::string>::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) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select one vertex from the sketch other than the origin."));
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
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 already has 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(QT_TRANSLATE_NOOP("Command", "Add 'Lock' constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%f))",
|
|
GeoId[0],
|
|
static_cast<int>(PosId[0]),
|
|
pnt.x);
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%f))",
|
|
GeoId[0],
|
|
static_cast<int>(PosId[0]),
|
|
pnt.y);
|
|
|
|
lastconstraintindex += 2;
|
|
|
|
if (edgeisblocked || GeoId[0] <= Sketcher::GeoEnum::RefExt
|
|
|| constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
lastconstraintindex - 1,
|
|
"False");
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
lastconstraintindex,
|
|
"False");
|
|
}
|
|
}
|
|
else {
|
|
std::vector<int>::const_iterator itg;
|
|
std::vector<Sketcher::PointPos>::const_iterator itp;
|
|
|
|
Base::Vector3d pntr = Obj->getPoint(GeoId.back(), PosId.back());
|
|
|
|
// check if the edge already has 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(QT_TRANSLATE_NOOP("Command", "Add relative 'Lock' constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%d,%d,%f))",
|
|
*itg,
|
|
static_cast<int>(*itp),
|
|
GeoId.back(),
|
|
static_cast<int>(PosId.back()),
|
|
pntr.x - pnt.x);
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%d,%d,%f))",
|
|
*itg,
|
|
static_cast<int>(*itp),
|
|
GeoId.back(),
|
|
static_cast<int>(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::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
lastconstraintindex - 1,
|
|
"False");
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
lastconstraintindex,
|
|
"False");
|
|
}
|
|
}
|
|
}
|
|
|
|
// finish the transaction and update
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
|
|
// clear the selection (convenience)
|
|
getSelection().clearSelection();
|
|
}
|
|
|
|
void CmdSketcherConstrainLock::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
switch (seqIndex) {
|
|
case 0:// {Vertex}
|
|
// Create the constraints
|
|
SketcherGui::ViewProviderSketch* sketchgui =
|
|
static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
|
|
Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
|
|
|
|
// check if the edge already has a Block constraint
|
|
bool pointfixed = false;
|
|
|
|
if (selSeq.empty()) {
|
|
return;
|
|
}
|
|
|
|
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(QT_TRANSLATE_NOOP("Command", "Add fixed constraint"));
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(),
|
|
"addConstraint(Sketcher.Constraint('DistanceX', %d, %d, %f))",
|
|
selSeq.front().GeoId,
|
|
static_cast<int>(selSeq.front().PosId),
|
|
pnt.x);
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(),
|
|
"addConstraint(Sketcher.Constraint('DistanceY', %d, %d, %f))",
|
|
selSeq.front().GeoId,
|
|
static_cast<int>(selSeq.front().PosId),
|
|
pnt.y);
|
|
|
|
if (pointfixed || constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(),
|
|
"setDriving(%d, %s)",
|
|
ConStr.size() - 2,
|
|
"False");
|
|
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(),
|
|
"setDriving(%d, %s)",
|
|
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().iconFromTheme("Constraint_Lock_Driven"));
|
|
}
|
|
break;
|
|
case Driving:
|
|
if (getAction()) {
|
|
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Lock"));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ======================================================================================
|
|
|
|
class CmdSketcherConstrainBlock: public CmdSketcherConstraint
|
|
{
|
|
public:
|
|
CmdSketcherConstrainBlock();
|
|
~CmdSketcherConstrainBlock() override
|
|
{}
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainBlock";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
};
|
|
|
|
CmdSketcherConstrainBlock::CmdSketcherConstrainBlock()
|
|
: CmdSketcherConstraint("Sketcher_ConstrainBlock")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Constrain block");
|
|
sToolTipText = QT_TR_NOOP("Block the selected edge from moving");
|
|
sWhatsThis = "Sketcher_ConstrainBlock";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "Constraint_Block";
|
|
sAccel = "K, B";
|
|
eType = ForEdit;
|
|
|
|
allowedSelSequences = {{SelEdge}};
|
|
}
|
|
|
|
void CmdSketcherConstrainBlock::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
|
|
// get the selection
|
|
std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
|
|
|
|
// only one sketch with its subelements are allowed to be selected
|
|
if (selection.size() != 1
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
|
|
|
|
if (constraintMode) {
|
|
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(this));
|
|
getSelection().clearSelection();
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(getActiveGuiDocument(),
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select vertices from the sketch."));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// get the needed lists and objects
|
|
const std::vector<std::string>& SubNames = selection[0].getSubNames();
|
|
auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
// Check that the solver does not report redundant/conflicting constraints
|
|
if (Obj->getLastSolverStatus() != GCS::Success || Obj->getLastHasConflicts()
|
|
|| Obj->getLastHasRedundancies()) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong solver status"),
|
|
QObject::tr("A Block constraint cannot be added "
|
|
"if the sketch is unsolved "
|
|
"or there are redundant and "
|
|
"conflicting constraints."));
|
|
return;
|
|
}
|
|
|
|
std::vector<int> GeoId;
|
|
const std::vector<Sketcher::Constraint*>& vals = Obj->Constraints.getValues();
|
|
|
|
for (auto& subname : SubNames) {
|
|
int GeoIdt;
|
|
Sketcher::PointPos PosIdt;
|
|
getIdsFromName(subname, Obj, GeoIdt, PosIdt);
|
|
|
|
if (isVertex(GeoIdt, PosIdt) || GeoIdt < 0) {
|
|
if (selection.size() == 1) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select one edge from the sketch."));
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select only edges from the sketch."));
|
|
}
|
|
// clear the selection
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
|
|
// check if the edge already has a Block constraint
|
|
if (checkConstraint(vals, Sketcher::Block, GeoIdt, Sketcher::PointPos::none)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Double constraint"),
|
|
QObject::tr("The selected edge already has a Block constraint!"));
|
|
return;
|
|
}
|
|
|
|
GeoId.push_back(GeoIdt);
|
|
}
|
|
|
|
for (std::vector<int>::iterator itg = GeoId.begin(); itg != GeoId.end(); ++itg) {
|
|
// undo command open
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add 'Block' constraint"));
|
|
|
|
bool safe = addConstraintSafely(Obj, [&]() {
|
|
Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Block',%d))", (*itg));
|
|
});
|
|
|
|
if (!safe) {
|
|
return;
|
|
}
|
|
else {
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
}
|
|
|
|
// clear the selection (convenience)
|
|
getSelection().clearSelection();
|
|
}
|
|
|
|
void CmdSketcherConstrainBlock::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
switch (seqIndex) {
|
|
case 0:// {Edge}
|
|
{
|
|
// Create the constraints
|
|
SketcherGui::ViewProviderSketch* sketchgui =
|
|
static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
|
|
|
|
auto Obj = static_cast<Sketcher::SketchObject*>(sketchgui->getObject());
|
|
|
|
// check if the edge already has a Block constraint
|
|
const std::vector<Sketcher::Constraint*>& vals = Obj->Constraints.getValues();
|
|
|
|
if (selSeq.empty()) {
|
|
return;
|
|
}
|
|
|
|
if (checkConstraint(vals,
|
|
Sketcher::Block,
|
|
selSeq.front().GeoId,
|
|
Sketcher::PointPos::none)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Double constraint"),
|
|
QObject::tr("The selected edge already has a Block constraint!"));
|
|
return;
|
|
}
|
|
|
|
// undo command open
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add block constraint"));
|
|
|
|
bool safe = addConstraintSafely(Obj, [&]() {
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(),
|
|
"addConstraint(Sketcher.Constraint('Block',%d))",
|
|
selSeq.front().GeoId);
|
|
});
|
|
|
|
if (!safe) {
|
|
return;
|
|
}
|
|
else {
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ======================================================================================
|
|
|
|
class CmdSketcherConstrainCoincidentUnified : public CmdSketcherConstraint
|
|
{
|
|
public:
|
|
CmdSketcherConstrainCoincidentUnified(const char* initName = "Sketcher_ConstrainCoincidentUnified");
|
|
~CmdSketcherConstrainCoincidentUnified() override
|
|
{}
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainCoincidentUnified";
|
|
}
|
|
|
|
protected:
|
|
enum class CoincicenceType {
|
|
Coincident,
|
|
PointOnObject,
|
|
Both
|
|
};
|
|
|
|
void activated(int iMsg) override;
|
|
void onActivated(CoincicenceType type);
|
|
void activatedCoincident(SketchObject* obj, std::vector<SelIdPair> points, std::vector<SelIdPair> curves);
|
|
void activatedPointOnObject(SketchObject* obj, std::vector<SelIdPair> points, std::vector<SelIdPair> curves);
|
|
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
void applyConstraintCoincident(std::vector<SelIdPair>& selSeq, int seqIndex);
|
|
void applyConstraintPointOnObject(std::vector<SelIdPair>& selSeq, int seqIndex);
|
|
|
|
// returns true if a substitution took place
|
|
static bool substituteConstraintCombinationsPointOnObject(SketchObject* Obj, int GeoId1, PointPos PosId1, int GeoId2);
|
|
static bool substituteConstraintCombinationsCoincident(SketchObject* Obj, int GeoId1, PointPos PosId1, int GeoId2, PointPos PosId2);
|
|
};
|
|
|
|
CmdSketcherConstrainCoincidentUnified::CmdSketcherConstrainCoincidentUnified(const char* initName)
|
|
: CmdSketcherConstraint(initName)
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Constrain coincident");
|
|
sToolTipText = QT_TR_NOOP("Create a coincident constraint between points, or fix a point on an edge, "
|
|
"or a concentric constraint between circles, arcs, and ellipses");
|
|
sWhatsThis = "Sketcher_ConstrainCoincidentUnified";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "Constraint_Coincident";
|
|
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher/Constraints");
|
|
sAccel = hGrp->GetBool("UnifiedCoincident", false) ? "C" :"C,O";
|
|
|
|
eType = ForEdit;
|
|
|
|
allowedSelSequences = { {SelVertex, SelEdgeOrAxis},
|
|
{SelRoot, SelEdge},
|
|
{SelVertex, SelExternalEdge},
|
|
{SelEdge, SelVertexOrRoot},
|
|
{SelEdgeOrAxis, SelVertex},
|
|
{SelExternalEdge, SelVertex},
|
|
|
|
{SelVertex, SelVertexOrRoot},
|
|
{SelRoot, SelVertex},
|
|
{SelEdge, SelEdge},
|
|
{SelEdge, SelExternalEdge},
|
|
{SelExternalEdge, SelEdge} };
|
|
}
|
|
|
|
bool CmdSketcherConstrainCoincidentUnified::substituteConstraintCombinationsPointOnObject(SketchObject* Obj, int GeoId1, PointPos PosId1, int GeoId2)
|
|
{
|
|
const std::vector<Constraint*>& cvals = Obj->Constraints.getValues();
|
|
|
|
int cid = 0;
|
|
for (std::vector<Constraint*>::const_iterator it = cvals.begin(); it != cvals.end();
|
|
++it, ++cid) {
|
|
if ((*it)->Type == Sketcher::Tangent && (*it)->FirstPos == Sketcher::PointPos::none
|
|
&& (*it)->SecondPos == Sketcher::PointPos::none && (*it)->Third == GeoEnum::GeoUndef
|
|
&& (((*it)->First == GeoId1 && (*it)->Second == GeoId2)
|
|
|| ((*it)->Second == GeoId1 && (*it)->First == GeoId2))) {
|
|
|
|
// NOTE: This function does not either open or commit a command as it is used for group
|
|
// addition it relies on such infrastructure being provided by the caller.
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "delConstraint(%d)", cid);
|
|
|
|
doEndpointToEdgeTangency(Obj, GeoId1, PosId1, GeoId2);
|
|
|
|
notifyConstraintSubstitutions(
|
|
QObject::tr("Endpoint to edge tangency was applied instead."));
|
|
|
|
getSelection().clearSelection();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CmdSketcherConstrainCoincidentUnified::substituteConstraintCombinationsCoincident(SketchObject* Obj, int GeoId1, PointPos PosId1, int GeoId2, PointPos PosId2)
|
|
{
|
|
// checks for direct and indirect coincidence constraints
|
|
bool constraintExists = Obj->arePointsCoincident(GeoId1, PosId1, GeoId2, PosId2);
|
|
|
|
const std::vector<Constraint*>& cvals = Obj->Constraints.getValues();
|
|
|
|
// NOTE: This function does not either open or commit a command as it is used for group addition
|
|
// it relies on such infrastructure being provided by the caller.
|
|
|
|
int j = 0;
|
|
for (std::vector<Constraint*>::const_iterator it = cvals.begin(); it != cvals.end();
|
|
++it, ++j) {
|
|
if ((*it)->Type == Sketcher::Tangent && (*it)->Third == GeoEnum::GeoUndef
|
|
&& (((*it)->First == GeoId1 && (*it)->Second == GeoId2)
|
|
|| ((*it)->Second == GeoId1 && (*it)->First == GeoId2))) {
|
|
if ((*it)->FirstPos == Sketcher::PointPos::none
|
|
&& (*it)->SecondPos == Sketcher::PointPos::none) {
|
|
|
|
if (constraintExists) {
|
|
// try to remove any pre-existing direct coincident constraints
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"delConstraintOnPoint(%d,%d)",
|
|
GeoId1,
|
|
static_cast<int>(PosId1));
|
|
}
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "delConstraint(%d)", j);
|
|
|
|
doEndpointTangency(Obj, GeoId1, GeoId2, PosId1, PosId2);
|
|
|
|
notifyConstraintSubstitutions(
|
|
QObject::tr("Endpoint to endpoint tangency was applied instead."));
|
|
|
|
getSelection().clearSelection();
|
|
return true;
|
|
}
|
|
else if (isBsplineKnot(Obj, GeoId1) != isBsplineKnot(Obj, GeoId2)) {
|
|
// Replace with knot-to-endpoint tangency
|
|
|
|
if (isBsplineKnot(Obj, GeoId2)) {
|
|
std::swap(GeoId1, GeoId2);
|
|
std::swap(PosId1, PosId2);
|
|
}
|
|
|
|
// if a similar tangency already exists this must result in bad constraints
|
|
if ((*it)->SecondPos == Sketcher::PointPos::none) {
|
|
Gui::cmdAppObjectArgs(Obj, "delConstraint(%d)", j);
|
|
|
|
doEndpointTangency(Obj, GeoId1, GeoId2, PosId1, PosId2);
|
|
|
|
notifyConstraintSubstitutions(
|
|
QObject::tr("B-spline knot to endpoint tangency was applied instead."));
|
|
|
|
getSelection().clearSelection();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CmdSketcherConstrainCoincidentUnified::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
onActivated(CoincicenceType::Both);
|
|
}
|
|
|
|
void CmdSketcherConstrainCoincidentUnified::onActivated(CoincicenceType type)
|
|
{
|
|
QString errorMess;
|
|
if (type == CoincicenceType::Coincident) {
|
|
errorMess = QObject::tr("Select either several points, or several conics for concentricity.");
|
|
}
|
|
else if (type == CoincicenceType::PointOnObject) {
|
|
errorMess = QObject::tr("Select either one point and several curves, or one curve and several points");
|
|
}
|
|
else {
|
|
errorMess = QObject::tr("Select either one point and several curves or one curve and several"
|
|
" points for pointOnObject, or several points for coincidence, or several conics for concentricity.");
|
|
}
|
|
|
|
// get the selection
|
|
std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
|
|
|
|
// only one sketch with its subelements are allowed to be selected
|
|
if (selection.size() != 1
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
|
|
|
|
if (constraintMode) {
|
|
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(this));
|
|
getSelection().clearSelection();
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(getActiveGuiDocument(), QObject::tr("Wrong selection"), errorMess);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// get the needed lists and objects
|
|
const std::vector<std::string>& SubNames = selection[0].getSubNames();
|
|
auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
// count curves and points
|
|
std::vector<SelIdPair> points;
|
|
std::vector<SelIdPair> 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 (type != CoincicenceType::Coincident && ((points.size() == 1 && !curves.empty()) || (!points.empty() && curves.size() == 1))) {
|
|
activatedPointOnObject(Obj, points, curves);
|
|
}
|
|
else if (type != CoincicenceType::PointOnObject && ((!points.empty() && curves.empty()) || (points.empty() && !curves.empty()))) {
|
|
activatedCoincident(Obj, points, curves);
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(Obj, QObject::tr("Wrong selection"), errorMess);
|
|
}
|
|
}
|
|
|
|
void CmdSketcherConstrainCoincidentUnified::activatedPointOnObject(SketchObject* obj, std::vector<SelIdPair> points, std::vector<SelIdPair> curves)
|
|
{
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "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(obj);
|
|
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 && isBsplinePole(geom)) {
|
|
Gui::TranslatedUserWarning(
|
|
obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge that is not a B-spline weight."));
|
|
abortCommand();
|
|
|
|
continue;
|
|
}
|
|
|
|
if (substituteConstraintCombinationsPointOnObject(obj,
|
|
points[iPnt].GeoId,
|
|
points[iPnt].PosId,
|
|
curves[iCrv].GeoId)) {
|
|
cnt++;
|
|
continue;
|
|
}
|
|
|
|
cnt++;
|
|
Gui::cmdAppObjectArgs(
|
|
obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
points[iPnt].GeoId,
|
|
static_cast<int>(points[iPnt].PosId),
|
|
curves[iCrv].GeoId);
|
|
}
|
|
}
|
|
if (cnt) {
|
|
commitCommand();
|
|
getSelection().clearSelection();
|
|
}
|
|
else {
|
|
abortCommand();
|
|
Gui::TranslatedUserWarning(obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("None of the selected points were constrained "
|
|
"onto the respective curves, "
|
|
"because they are parts "
|
|
"of the same element, "
|
|
"because they are both external geometry, "
|
|
"or because the edge is not eligible."));
|
|
}
|
|
return;
|
|
}
|
|
|
|
void CmdSketcherConstrainCoincidentUnified::activatedCoincident(SketchObject* obj, std::vector<SelIdPair> points, std::vector<SelIdPair> curves)
|
|
{
|
|
bool allConicsEdges = true;// If user selects only conics (circle, ellipse, arc, arcOfEllipse)
|
|
// then we make concentric constraint.
|
|
for (auto& curve : curves) {
|
|
if (!isGeoConcentricCompatible(obj->getGeometry(curve.GeoId))) {
|
|
allConicsEdges = false;
|
|
}
|
|
|
|
if (!allConicsEdges) {
|
|
Gui::TranslatedUserWarning(
|
|
obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select two or more vertices from the sketch for a coincident "
|
|
"constraint, or two or more circles, ellipses, arcs or arcs of ellipse "
|
|
"for a concentric constraint."));
|
|
return;
|
|
}
|
|
curve.PosId = Sketcher::PointPos::mid;
|
|
}
|
|
|
|
std::vector<SelIdPair> vecOfSelIdToUse = curves.empty() ? points : curves;
|
|
|
|
int GeoId1 = vecOfSelIdToUse[0].GeoId;
|
|
Sketcher::PointPos PosId1 = vecOfSelIdToUse[0].PosId;
|
|
|
|
// undo command open
|
|
bool constraintsAdded = false;
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add coincident constraint"));
|
|
|
|
for (std::size_t i = 1; i < vecOfSelIdToUse.size(); i++) {
|
|
int GeoId2 = vecOfSelIdToUse[i].GeoId;
|
|
Sketcher::PointPos PosId2 = vecOfSelIdToUse[i].PosId;
|
|
|
|
// check if the edge already has a Block constraint
|
|
if (areBothPointsOrSegmentsFixed(obj, GeoId1, GeoId2)) {
|
|
showNoConstraintBetweenFixedGeometry(obj);
|
|
return;
|
|
}
|
|
|
|
// check if as a consequence of this command undesirable combinations of constraints would
|
|
// arise and substitute them with more appropriate counterparts, examples:
|
|
// - coincidence + tangency on edge
|
|
// - point on object + tangency on edge
|
|
if (substituteConstraintCombinationsCoincident(obj, GeoId1, PosId1, GeoId2, PosId2)) {
|
|
constraintsAdded = true;
|
|
break;
|
|
}
|
|
|
|
// check if this coincidence is already enforced (even indirectly)
|
|
bool constraintExists = obj->arePointsCoincident(GeoId1, PosId1, GeoId2, PosId2);
|
|
|
|
if (!constraintExists) {
|
|
constraintsAdded = true;
|
|
Gui::cmdAppObjectArgs(obj,
|
|
"addConstraint(Sketcher.Constraint('Coincident',%d,%d,%d,%d))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2));
|
|
}
|
|
}
|
|
|
|
// finish or abort the transaction and update
|
|
if (constraintsAdded) {
|
|
commitCommand();
|
|
}
|
|
else {
|
|
abortCommand();
|
|
}
|
|
|
|
tryAutoRecompute(obj);
|
|
|
|
// clear the selection (convenience)
|
|
getSelection().clearSelection();
|
|
}
|
|
|
|
void CmdSketcherConstrainCoincidentUnified::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
switch (seqIndex) {
|
|
case 0:// {SelVertex, SelEdgeOrAxis}
|
|
case 1:// {SelRoot, SelEdge}
|
|
case 2:// {SelVertex, SelExternalEdge}
|
|
case 3:// {SelEdge, SelVertexOrRoot}
|
|
case 4:// {SelEdgeOrAxis, SelVertex}
|
|
case 5:// {SelExternalEdge, SelVertex}
|
|
applyConstraintPointOnObject(selSeq, seqIndex);
|
|
break;
|
|
case 6:// {SelVertex, SelVertexOrRoot}
|
|
case 7:// {SelRoot, SelVertex}
|
|
case 8:// {SelEdge, SelEdge}
|
|
case 9:// {SelEdge, SelExternalEdge}
|
|
case 10:// {SelExternalEdge, SelEdge}
|
|
seqIndex -= 6;
|
|
applyConstraintCoincident(selSeq, seqIndex);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
void CmdSketcherConstrainCoincidentUnified::applyConstraintPointOnObject(std::vector<SelIdPair>& 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<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
|
|
Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add point on object constraint"));
|
|
bool allOK = true;
|
|
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoIdVt, GeoIdCrv)) {
|
|
showNoConstraintBetweenFixedGeometry(Obj);
|
|
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 && isBsplinePole(geom)) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge that is not a B-spline weight."));
|
|
abortCommand();
|
|
|
|
return;
|
|
}
|
|
|
|
if (allOK) {
|
|
if (!substituteConstraintCombinationsPointOnObject(Obj, GeoIdVt, PosIdVt, GeoIdCrv)) {
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(),
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoIdVt,
|
|
static_cast<int>(PosIdVt),
|
|
GeoIdCrv);
|
|
}
|
|
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
else {
|
|
abortCommand();
|
|
Gui::TranslatedUserWarning(Obj,
|
|
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;
|
|
}
|
|
|
|
void CmdSketcherConstrainCoincidentUnified::applyConstraintCoincident(std::vector<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
SketcherGui::ViewProviderSketch* sketchgui =
|
|
static_cast<SketcherGui::ViewProviderSketch*>(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;
|
|
|
|
switch (seqIndex) {
|
|
case 0:// {SelVertex, SelVertexOrRoot}
|
|
case 1:// {SelRoot, SelVertex}
|
|
// Nothing specific.
|
|
break;
|
|
case 2:// {SelEdge, SelEdge}
|
|
case 3:// {SelEdge, SelExternalEdge}
|
|
case 4:// {SelExternalEdge, SelEdge}
|
|
// Concentric for circles, ellipse, arc, arcofEllipse only.
|
|
if (!isGeoConcentricCompatible(Obj->getGeometry(GeoId1))
|
|
|| !isGeoConcentricCompatible(Obj->getGeometry(GeoId2))) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr(
|
|
"Select two vertices from the sketch for a coincident constraint, or two "
|
|
"circles, ellipses, arcs or arcs of ellipse for a concentric constraint."));
|
|
return;
|
|
}
|
|
PosId1 = Sketcher::PointPos::mid;
|
|
PosId2 = Sketcher::PointPos::mid;
|
|
break;
|
|
}
|
|
|
|
// check if the edge already has a Block constraint
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
|
|
showNoConstraintBetweenFixedGeometry(Obj);
|
|
return;
|
|
}
|
|
|
|
// undo command open
|
|
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add coincident constraint"));
|
|
|
|
// check if this coincidence is already enforced (even indirectly)
|
|
bool constraintExists = Obj->arePointsCoincident(GeoId1, PosId1, GeoId2, PosId2);
|
|
if (substituteConstraintCombinationsCoincident(Obj, GeoId1, PosId1, GeoId2, PosId2)) {}
|
|
else if (!constraintExists && (GeoId1 != GeoId2)) {
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(),
|
|
"addConstraint(Sketcher.Constraint('Coincident', %d, %d, %d, %d))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2));
|
|
}
|
|
else {
|
|
Gui::Command::abortCommand();
|
|
return;
|
|
}
|
|
Gui::Command::commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
|
|
|
|
// ======================================================================================
|
|
|
|
class CmdSketcherConstrainCoincident: public CmdSketcherConstrainCoincidentUnified
|
|
{
|
|
public:
|
|
CmdSketcherConstrainCoincident();
|
|
~CmdSketcherConstrainCoincident() override
|
|
{}
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainCoincident";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
};
|
|
|
|
CmdSketcherConstrainCoincident::CmdSketcherConstrainCoincident()
|
|
: CmdSketcherConstrainCoincidentUnified("Sketcher_ConstrainCoincident")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Constrain coincident");
|
|
sToolTipText = QT_TR_NOOP("Create a coincident constraint between points, or a concentric "
|
|
"constraint between circles, arcs, and ellipses");
|
|
sWhatsThis = "Sketcher_ConstrainCoincident";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "Constraint_PointOnPoint";
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher/Constraints");
|
|
sAccel = hGrp->GetBool("UnifiedCoincident", false) ? "C,C" : "C";
|
|
eType = ForEdit;
|
|
|
|
allowedSelSequences = {{SelVertex, SelVertexOrRoot},
|
|
{SelRoot, SelVertex},
|
|
{SelEdge, SelEdge},
|
|
{SelEdge, SelExternalEdge},
|
|
{SelExternalEdge, SelEdge}};
|
|
}
|
|
|
|
void CmdSketcherConstrainCoincident::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
onActivated(CoincicenceType::Coincident);
|
|
}
|
|
|
|
void CmdSketcherConstrainCoincident::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
applyConstraintCoincident(selSeq, seqIndex);
|
|
}
|
|
|
|
// ======================================================================================
|
|
|
|
class CmdSketcherConstrainPointOnObject: public CmdSketcherConstrainCoincidentUnified
|
|
{
|
|
public:
|
|
CmdSketcherConstrainPointOnObject();
|
|
~CmdSketcherConstrainPointOnObject() override
|
|
{}
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainPointOnObject";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
};
|
|
|
|
CmdSketcherConstrainPointOnObject::CmdSketcherConstrainPointOnObject()
|
|
: CmdSketcherConstrainCoincidentUnified("Sketcher_ConstrainPointOnObject")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Constrain point on object");
|
|
sToolTipText = QT_TR_NOOP("Fix a point onto an object");
|
|
sWhatsThis = "Sketcher_ConstrainPointOnObject";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "Constraint_PointOnObject";
|
|
sAccel = "O";
|
|
eType = ForEdit;
|
|
|
|
allowedSelSequences = {{SelVertex, SelEdgeOrAxis},
|
|
{SelRoot, SelEdge},
|
|
{SelVertex, SelExternalEdge},
|
|
{SelEdge, SelVertexOrRoot},
|
|
{SelEdgeOrAxis, SelVertex},
|
|
{SelExternalEdge, SelVertex}};
|
|
}
|
|
|
|
void CmdSketcherConstrainPointOnObject::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
onActivated(CoincicenceType::PointOnObject);
|
|
}
|
|
|
|
void CmdSketcherConstrainPointOnObject::applyConstraint(std::vector<SelIdPair>& selSeq,
|
|
int seqIndex)
|
|
{
|
|
applyConstraintPointOnObject(selSeq, seqIndex);
|
|
}
|
|
|
|
// ======================================================================================
|
|
|
|
class CmdSketcherConstrainDistance : public CmdSketcherConstraint
|
|
{
|
|
public:
|
|
CmdSketcherConstrainDistance();
|
|
~CmdSketcherConstrainDistance() override
|
|
{}
|
|
void updateAction(int mode) override;
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainDistance";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
};
|
|
|
|
CmdSketcherConstrainDistance::CmdSketcherConstrainDistance()
|
|
: CmdSketcherConstraint("Sketcher_ConstrainDistance")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "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 "
|
|
"or between two circles");
|
|
sWhatsThis = "Sketcher_ConstrainDistance";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "Constraint_Length";
|
|
sAccel = "K, D";
|
|
eType = ForEdit;
|
|
|
|
allowedSelSequences = { {SelVertex, SelVertexOrRoot},
|
|
{SelRoot, SelVertex},
|
|
{SelEdge},
|
|
{SelExternalEdge},
|
|
{SelVertex, SelEdgeOrAxis},
|
|
{SelRoot, SelEdge},
|
|
{SelVertex, SelExternalEdge},
|
|
{SelRoot, SelExternalEdge},
|
|
{SelEdge, SelEdge} };
|
|
}
|
|
|
|
void CmdSketcherConstrainDistance::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
// get the selection
|
|
std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
|
|
|
|
// only one sketch with its subelements are allowed to be selected
|
|
if (selection.size() != 1
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
|
|
|
|
if (constraintMode) {
|
|
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(this));
|
|
|
|
getSelection().clearSelection();
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(getActiveGuiDocument(),
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select vertices from the sketch."));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// get the needed lists and objects
|
|
const std::vector<std::string>& SubNames = selection[0].getSubNames();
|
|
auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
if (SubNames.empty() || SubNames.size() > 2) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
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 = GeoEnum::GeoUndef;
|
|
Sketcher::PointPos PosId1, PosId2 = Sketcher::PointPos::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::PointPos::none) {
|
|
PosId1 = Sketcher::PointPos::start;
|
|
|
|
openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Add distance from horizontal axis constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%d,%d,%f))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2),
|
|
pnt2.y);
|
|
}
|
|
else if (GeoId1 == Sketcher::GeoEnum::VAxis && PosId1 == Sketcher::PointPos::none) {
|
|
PosId1 = Sketcher::PointPos::start;
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add distance from vertical axis constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%d,%d,%f))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2),
|
|
pnt2.x);
|
|
}
|
|
else {
|
|
Base::Vector3d pnt1 = Obj->getPoint(GeoId1, PosId1);
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add point to point distance constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%d,%f))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2),
|
|
(pnt2 - pnt1).Length());
|
|
}
|
|
|
|
if (arebothpointsorsegmentsfixed || constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
ConStr.size() - 1,
|
|
"False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(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 (isLineSegment(*geom)) {
|
|
auto lineSeg = static_cast<const Part::GeomLineSegment*>(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(QT_TRANSLATE_NOOP("Command", "Add point to line Distance constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%f))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
ActDist);
|
|
|
|
if (arebothpointsorsegmentsfixed
|
|
|| constraintCreationMode
|
|
== Reference) {// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
ConStr.size() - 1,
|
|
"False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
else if (isCircleOrArc(*geom)) { // point to circle distance
|
|
auto [radius, center] = getRadiusCenterCircleArc(geom);
|
|
Base::Vector3d d = center - pnt;
|
|
double ActDist = std::abs(d.Length() - radius);
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add point to circle Distance constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%f))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
ActDist);
|
|
|
|
if (arebothpointsorsegmentsfixed
|
|
|| constraintCreationMode
|
|
== Reference) {// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
ConStr.size() - 1,
|
|
"False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
else if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2)) {
|
|
const Part::Geometry* geom1 = Obj->getGeometry(GeoId1);
|
|
const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
|
|
|
|
if(isCircleOrArc(*geom1) && isCircleOrArc(*geom2)) {
|
|
|
|
auto [radius1, center1] = getRadiusCenterCircleArc(geom1);
|
|
auto [radius2, center2] = getRadiusCenterCircleArc(geom2);
|
|
|
|
double ActDist = 0.0;
|
|
|
|
Base::Vector3d intercenter = center1 - center2;
|
|
double intercenterdistance = intercenter.Length();
|
|
|
|
if (intercenterdistance >= radius1 && intercenterdistance >= radius2) {
|
|
|
|
ActDist = intercenterdistance - radius1 - radius2;
|
|
}
|
|
else {
|
|
double bigradius = std::max(radius1, radius2);
|
|
double smallradius = std::min(radius1, radius2);
|
|
|
|
ActDist = bigradius - smallradius - intercenterdistance;
|
|
}
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add circle to circle distance constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Distance',%d,%d,%f))",
|
|
GeoId1,
|
|
GeoId2,
|
|
ActDist);
|
|
|
|
if (arebothpointsorsegmentsfixed
|
|
|| constraintCreationMode
|
|
== Reference) {// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
ConStr.size() - 1,
|
|
"False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
else if ((isCircleOrArc(*geom1) && isLineSegment(*geom2))
|
|
|| (isLineSegment(*geom1) && isCircleOrArc(*geom2))) {// circle to line distance
|
|
|
|
if (isLineSegment(*geom1)) {
|
|
std::swap(geom1, geom2);// Assume circle is first
|
|
std::swap(GeoId1, GeoId2);
|
|
}
|
|
|
|
auto [radius, center] = getRadiusCenterCircleArc(geom1);
|
|
|
|
auto lineSeg = static_cast<const Part::GeomLineSegment*>(geom2);
|
|
Base::Vector3d pnt1 = lineSeg->getStartPoint();
|
|
Base::Vector3d pnt2 = lineSeg->getEndPoint();
|
|
Base::Vector3d d = pnt2 - pnt1;
|
|
double ActDist =
|
|
std::abs(-center.x * d.y + center.y * d.x + pnt1.x * pnt2.y - pnt2.x * pnt1.y)
|
|
/ d.Length()
|
|
- radius;
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add circle to line distance constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Distance',%d,%d,%f)) ",
|
|
GeoId1,
|
|
GeoId2,
|
|
ActDist);
|
|
|
|
if (arebothpointsorsegmentsfixed
|
|
|| constraintCreationMode
|
|
== Reference) {// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%i,%s)",
|
|
ConStr.size() - 1,
|
|
"False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
else {
|
|
Gui::TranslatedNotification(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Cannot add a length constraint on this selection!"));
|
|
return;
|
|
}
|
|
}
|
|
else if (isEdge(GeoId1, PosId1)) {// line length
|
|
if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
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 (isLineSegment(*geom)) {
|
|
auto lineSeg = static_cast<const Part::GeomLineSegment*>(geom);
|
|
double ActLength = (lineSeg->getEndPoint() - lineSeg->getStartPoint()).Length();
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add length constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Distance',%d,%f))",
|
|
GeoId1,
|
|
ActLength);
|
|
|
|
// it is a constraint on a external line, make it non-driving
|
|
if (arebothpointsorsegmentsfixed || GeoId1 <= Sketcher::GeoEnum::RefExt
|
|
|| constraintCreationMode == Reference) {
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
ConStr.size() - 1,
|
|
"False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
else if (isArcOfCircle(*geom)) {
|
|
auto arc = static_cast<const Part::GeomArcOfCircle*>(geom);
|
|
double ActLength = arc->getAngle(false) * arc->getRadius();
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add length constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Distance',%d,%f))",
|
|
GeoId1,
|
|
ActLength);
|
|
|
|
// it is a constraint on a external line, make it non-driving
|
|
if (arebothpointsorsegmentsfixed || GeoId1 <= Sketcher::GeoEnum::RefExt
|
|
|| constraintCreationMode == Reference) {
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
ConStr.size() - 1,
|
|
"False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select exactly one line or one point and one line or "
|
|
"two points or two circles from the sketch."));
|
|
return;
|
|
}
|
|
|
|
void CmdSketcherConstrainDistance::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
SketcherGui::ViewProviderSketch* sketchgui =
|
|
static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
|
|
Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
|
|
|
|
int GeoId1 = GeoEnum::GeoUndef, GeoId2 = GeoEnum::GeoUndef;
|
|
Sketcher::PointPos PosId1 = Sketcher::PointPos::none, PosId2 = Sketcher::PointPos::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::PointPos::none) {
|
|
PosId1 = Sketcher::PointPos::start;
|
|
|
|
openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Add distance from horizontal axis constraint"));
|
|
Gui::cmdAppObjectArgs(
|
|
Obj,
|
|
"addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%d,%d,%f))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2),
|
|
pnt2.y);
|
|
}
|
|
else if (GeoId1 == Sketcher::GeoEnum::VAxis && PosId1 == Sketcher::PointPos::none) {
|
|
PosId1 = Sketcher::PointPos::start;
|
|
|
|
openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Add distance from vertical axis constraint"));
|
|
Gui::cmdAppObjectArgs(
|
|
Obj,
|
|
"addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%d,%d,%f))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2),
|
|
pnt2.x);
|
|
}
|
|
else {
|
|
Base::Vector3d pnt1 = Obj->getPoint(GeoId1, PosId1);
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add point to point distance constraint"));
|
|
Gui::cmdAppObjectArgs(
|
|
Obj,
|
|
"addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%d,%f))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2),
|
|
(pnt2 - pnt1).Length());
|
|
}
|
|
|
|
if (arebothpointsorsegmentsfixed || constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
case 2:// {SelEdge}
|
|
case 3:// {SelExternalEdge}
|
|
{
|
|
GeoId1 = selSeq.at(0).GeoId;
|
|
|
|
arebothpointsorsegmentsfixed = isPointOrSegmentFixed(Obj, GeoId1);
|
|
|
|
const Part::Geometry* geom = Obj->getGeometry(GeoId1);
|
|
|
|
if (isLineSegment(*geom)) {
|
|
auto lineSeg = static_cast<const Part::GeomLineSegment*>(geom);
|
|
double ActLength = (lineSeg->getEndPoint() - lineSeg->getStartPoint()).Length();
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add length constraint"));
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Distance',%d,%f))",
|
|
GeoId1,
|
|
ActLength);
|
|
|
|
if (arebothpointsorsegmentsfixed || GeoId1 <= Sketcher::GeoEnum::RefExt
|
|
|| constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
}
|
|
else if (isCircle(*geom)) {
|
|
// allow this selection but do nothing as it needs 2 circles or 1 circle and 1 line
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
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;
|
|
|
|
Base::Vector3d pnt = Obj->getPoint(GeoId1, PosId1);
|
|
const Part::Geometry* geom = Obj->getGeometry(GeoId2);
|
|
|
|
if (isLineSegment(*geom)) {
|
|
auto lineSeg = static_cast<const Part::GeomLineSegment*>(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(QT_TRANSLATE_NOOP("Command", "Add point to line Distance constraint"));
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%f))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
ActDist);
|
|
|
|
if (arebothpointsorsegmentsfixed || constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
case 8:// {SelEdge, 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 (isCircle(*geom1) && isCircle(*geom2)) {// circle to circle distance
|
|
auto circleSeg1 = static_cast<const Part::GeomCircle*>(geom1);
|
|
double radius1 = circleSeg1->getRadius();
|
|
Base::Vector3d center1 = circleSeg1->getCenter();
|
|
|
|
auto circleSeg2 = static_cast<const Part::GeomCircle*>(geom2);
|
|
double radius2 = circleSeg2->getRadius();
|
|
Base::Vector3d center2 = circleSeg2->getCenter();
|
|
|
|
double ActDist = 0.;
|
|
|
|
Base::Vector3d intercenter = center1 - center2;
|
|
double intercenterdistance = intercenter.Length();
|
|
|
|
if (intercenterdistance >= radius1 && intercenterdistance >= radius2) {
|
|
|
|
ActDist = intercenterdistance - radius1 - radius2;
|
|
}
|
|
else {
|
|
double bigradius = std::max(radius1, radius2);
|
|
double smallradius = std::min(radius1, radius2);
|
|
|
|
ActDist = bigradius - smallradius - intercenterdistance;
|
|
}
|
|
|
|
openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Add circle to circle distance constraint"));
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Distance',%d,%d,%f))",
|
|
GeoId1,
|
|
GeoId2,
|
|
ActDist);
|
|
|
|
if (arebothpointsorsegmentsfixed
|
|
|| constraintCreationMode
|
|
== Reference) {// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select exactly one line or one point and one line or two points "
|
|
"or two circles from the sketch."));
|
|
}
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CmdSketcherConstrainDistance::updateAction(int mode)
|
|
{
|
|
switch (mode) {
|
|
case Reference:
|
|
if (getAction()) {
|
|
getAction()->setIcon(
|
|
Gui::BitmapFactory().iconFromTheme("Constraint_Length_Driven"));
|
|
}
|
|
break;
|
|
case Driving:
|
|
if (getAction()) {
|
|
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Length"));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ======================================================================================
|
|
|
|
class CmdSketcherConstrainDistanceX: public CmdSketcherConstraint
|
|
{
|
|
public:
|
|
CmdSketcherConstrainDistanceX();
|
|
~CmdSketcherConstrainDistanceX() override
|
|
{}
|
|
void updateAction(int mode) override;
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainDistanceX";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
};
|
|
|
|
CmdSketcherConstrainDistanceX::CmdSketcherConstrainDistanceX()
|
|
: CmdSketcherConstraint("Sketcher_ConstrainDistanceX")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "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 = "L";
|
|
eType = ForEdit;
|
|
|
|
// Can't do single vertex because its a prefix for 2 vertices
|
|
allowedSelSequences = {{SelVertex, SelVertexOrRoot},
|
|
{SelRoot, SelVertex},
|
|
{SelEdge},
|
|
{SelExternalEdge}};
|
|
}
|
|
|
|
void CmdSketcherConstrainDistanceX::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
// get the selection
|
|
std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
|
|
|
|
// only one sketch with its subelements are allowed to be selected
|
|
if (selection.size() != 1
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
|
|
|
|
if (constraintMode) {
|
|
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(this));
|
|
getSelection().clearSelection();
|
|
}
|
|
else {
|
|
// TODO: Get the exact message from git history and put it here
|
|
Gui::TranslatedUserWarning(getActiveGuiDocument(),
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select the right things from the sketch."));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// get the needed lists and objects
|
|
const std::vector<std::string>& SubNames = selection[0].getSubNames();
|
|
auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
if (SubNames.empty() || SubNames.size() > 2) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select exactly one line or up to two points from the sketch."));
|
|
return;
|
|
}
|
|
|
|
int GeoId1, GeoId2 = GeoEnum::GeoUndef;
|
|
Sketcher::PointPos PosId1, PosId2 = Sketcher::PointPos::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::PointPos::none) {
|
|
// reject horizontal axis from selection
|
|
GeoId1 = GeoEnum::GeoUndef;
|
|
}
|
|
else if (GeoId1 == Sketcher::GeoEnum::VAxis && PosId1 == Sketcher::PointPos::none) {
|
|
GeoId1 = Sketcher::GeoEnum::HAxis;
|
|
PosId1 = Sketcher::PointPos::start;
|
|
}
|
|
|
|
if (isEdge(GeoId1, PosId1) && GeoId2 == GeoEnum::GeoUndef) {
|
|
// horizontal length of a line
|
|
if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
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 (isLineSegment(*geom)) {
|
|
// convert to as if two endpoints of the line have been selected
|
|
PosId1 = Sketcher::PointPos::start;
|
|
GeoId2 = GeoId1;
|
|
PosId2 = Sketcher::PointPos::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(
|
|
QT_TRANSLATE_NOOP("Command", "Add point to point horizontal distance constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%d,%d,%f))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2),
|
|
ActLength);
|
|
|
|
if (arebothpointsorsegmentsfixed || constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
ConStr.size() - 1,
|
|
"False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
else if (isVertex(GeoId1, PosId1) && GeoId2 == GeoEnum::GeoUndef) {
|
|
// point on fixed x-coordinate
|
|
|
|
if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Cannot add a fixed x-coordinate constraint on the origin point!"));
|
|
return;
|
|
}
|
|
|
|
Base::Vector3d pnt = Obj->getPoint(GeoId1, PosId1);
|
|
double ActX = pnt.x;
|
|
|
|
arebothpointsorsegmentsfixed = isPointOrSegmentFixed(Obj, GeoId1);
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add fixed x-coordinate constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%f))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
ActX);
|
|
|
|
if (arebothpointsorsegmentsfixed || constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
ConStr.size() - 1,
|
|
"False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select exactly one line or up to two points from the sketch."));
|
|
return;
|
|
}
|
|
|
|
void CmdSketcherConstrainDistanceX::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
SketcherGui::ViewProviderSketch* sketchgui =
|
|
static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
|
|
Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
|
|
|
|
int GeoId1 = GeoEnum::GeoUndef, GeoId2 = GeoEnum::GeoUndef;
|
|
Sketcher::PointPos PosId1 = Sketcher::PointPos::none, PosId2 = Sketcher::PointPos::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::PointPos::start;
|
|
PosId2 = Sketcher::PointPos::end;
|
|
|
|
const Part::Geometry* geom = Obj->getGeometry(GeoId1);
|
|
|
|
if (! isLineSegment(*geom)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
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(QT_TRANSLATE_NOOP("Command", "Add point to point horizontal distance constraint"));
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%d,%d,%f))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2),
|
|
ActLength);
|
|
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2) || constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
}
|
|
|
|
void CmdSketcherConstrainDistanceX::updateAction(int mode)
|
|
{
|
|
switch (mode) {
|
|
case Reference:
|
|
if (getAction()) {
|
|
getAction()->setIcon(
|
|
Gui::BitmapFactory().iconFromTheme("Constraint_HorizontalDistance_Driven"));
|
|
}
|
|
break;
|
|
case Driving:
|
|
if (getAction()) {
|
|
getAction()->setIcon(
|
|
Gui::BitmapFactory().iconFromTheme("Constraint_HorizontalDistance"));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// ======================================================================================
|
|
|
|
class CmdSketcherConstrainDistanceY: public CmdSketcherConstraint
|
|
{
|
|
public:
|
|
CmdSketcherConstrainDistanceY();
|
|
~CmdSketcherConstrainDistanceY() override
|
|
{}
|
|
void updateAction(int mode) override;
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainDistanceY";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
};
|
|
|
|
CmdSketcherConstrainDistanceY::CmdSketcherConstrainDistanceY()
|
|
: CmdSketcherConstraint("Sketcher_ConstrainDistanceY")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "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 = "I";
|
|
eType = ForEdit;
|
|
|
|
// Can't do single vertex because its a prefix for 2 vertices
|
|
allowedSelSequences = {{SelVertex, SelVertexOrRoot},
|
|
{SelRoot, SelVertex},
|
|
{SelEdge},
|
|
{SelExternalEdge}};
|
|
}
|
|
|
|
void CmdSketcherConstrainDistanceY::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
// get the selection
|
|
std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
|
|
|
|
// only one sketch with its subelements are allowed to be selected
|
|
if (selection.size() != 1
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
|
|
|
|
if (constraintMode) {
|
|
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(this));
|
|
getSelection().clearSelection();
|
|
}
|
|
else {
|
|
// TODO: Get the exact message from git history and put it here
|
|
Gui::TranslatedUserWarning(getActiveGuiDocument(),
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select the right things from the sketch."));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// get the needed lists and objects
|
|
const std::vector<std::string>& SubNames = selection[0].getSubNames();
|
|
auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
if (SubNames.empty() || SubNames.size() > 2) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select exactly one line or up to two points from the sketch."));
|
|
return;
|
|
}
|
|
|
|
int GeoId1, GeoId2 = GeoEnum::GeoUndef;
|
|
Sketcher::PointPos PosId1, PosId2 = Sketcher::PointPos::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::PointPos::none) {// reject vertical axis from selection
|
|
GeoId1 = GeoEnum::GeoUndef;
|
|
}
|
|
else if (GeoId1 == Sketcher::GeoEnum::HAxis && PosId1 == Sketcher::PointPos::none) {
|
|
PosId1 = Sketcher::PointPos::start;
|
|
}
|
|
|
|
if (isEdge(GeoId1, PosId1) && GeoId2 == GeoEnum::GeoUndef) {// vertical length of a line
|
|
if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
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 (isLineSegment(*geom)) {
|
|
// convert to as if two endpoints of the line have been selected
|
|
PosId1 = Sketcher::PointPos::start;
|
|
GeoId2 = GeoId1;
|
|
PosId2 = Sketcher::PointPos::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(
|
|
QT_TRANSLATE_NOOP("Command", "Add point to point vertical distance constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%d,%d,%f))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2),
|
|
ActLength);
|
|
|
|
if (arebothpointsorsegmentsfixed || constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
ConStr.size() - 1,
|
|
"False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
else if (isVertex(GeoId1, PosId1) && GeoId2 == GeoEnum::GeoUndef) {
|
|
// point on fixed y-coordinate
|
|
if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Cannot add a fixed y-coordinate constraint on the origin point!"));
|
|
return;
|
|
}
|
|
|
|
Base::Vector3d pnt = Obj->getPoint(GeoId1, PosId1);
|
|
double ActY = pnt.y;
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add fixed y-coordinate constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%f))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
ActY);
|
|
|
|
if (GeoId1 <= Sketcher::GeoEnum::RefExt || constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
ConStr.size() - 1,
|
|
"False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select exactly one line or up to two points from the sketch."));
|
|
return;
|
|
}
|
|
|
|
void CmdSketcherConstrainDistanceY::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
SketcherGui::ViewProviderSketch* sketchgui =
|
|
static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
|
|
Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
|
|
|
|
int GeoId1 = GeoEnum::GeoUndef, GeoId2 = GeoEnum::GeoUndef;
|
|
Sketcher::PointPos PosId1 = Sketcher::PointPos::none, PosId2 = Sketcher::PointPos::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::PointPos::start;
|
|
PosId2 = Sketcher::PointPos::end;
|
|
|
|
const Part::Geometry* geom = Obj->getGeometry(GeoId1);
|
|
|
|
if (! isLineSegment(*geom)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
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(QT_TRANSLATE_NOOP("Command", "Add point to point vertical distance constraint"));
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%d,%d,%f))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2),
|
|
ActLength);
|
|
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)
|
|
|| constraintCreationMode
|
|
== Reference) {// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
}
|
|
|
|
void CmdSketcherConstrainDistanceY::updateAction(int mode)
|
|
{
|
|
switch (mode) {
|
|
case Reference:
|
|
if (getAction()) {
|
|
getAction()->setIcon(
|
|
Gui::BitmapFactory().iconFromTheme("Constraint_VerticalDistance_Driven"));
|
|
}
|
|
break;
|
|
case Driving:
|
|
if (getAction()) {
|
|
getAction()->setIcon(
|
|
Gui::BitmapFactory().iconFromTheme("Constraint_VerticalDistance"));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//=================================================================================
|
|
|
|
class CmdSketcherConstrainParallel: public CmdSketcherConstraint
|
|
{
|
|
public:
|
|
CmdSketcherConstrainParallel();
|
|
~CmdSketcherConstrainParallel() override
|
|
{}
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainParallel";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
};
|
|
|
|
CmdSketcherConstrainParallel::CmdSketcherConstrainParallel()
|
|
: CmdSketcherConstraint("Sketcher_ConstrainParallel")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "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 = "P";
|
|
eType = ForEdit;
|
|
|
|
// TODO: Also needed: ExternalEdges
|
|
allowedSelSequences = {{SelEdge, SelEdgeOrAxis},
|
|
{SelEdgeOrAxis, SelEdge},
|
|
{SelEdge, SelExternalEdge},
|
|
{SelExternalEdge, SelEdge}};
|
|
}
|
|
|
|
void CmdSketcherConstrainParallel::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
// get the selection
|
|
std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
|
|
|
|
// only one sketch with its subelements are allowed to be selected
|
|
if (selection.size() != 1
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
|
|
|
|
if (constraintMode) {
|
|
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(this));
|
|
getSelection().clearSelection();
|
|
}
|
|
else {
|
|
// TODO: Get the exact message from git history and put it here
|
|
Gui::TranslatedUserWarning(getActiveGuiDocument(),
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select two or more lines from the sketch."));
|
|
}
|
|
return;
|
|
}
|
|
|
|
auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
// go through the selected subelements
|
|
std::vector<int> ids;
|
|
bool hasAlreadyExternal = false;
|
|
for (auto& subname : selection[0].getSubNames()) {
|
|
|
|
int GeoId;
|
|
Sketcher::PointPos PosId;
|
|
getIdsFromName(subname, Obj, GeoId, PosId);
|
|
|
|
if (!isEdge(GeoId, PosId)) {
|
|
continue;
|
|
}
|
|
else if (isPointOrSegmentFixed(Obj, GeoId)) {
|
|
if (hasAlreadyExternal) {
|
|
showNoConstraintBetweenFixedGeometry(Obj);
|
|
return;
|
|
}
|
|
else {
|
|
hasAlreadyExternal = true;
|
|
}
|
|
}
|
|
|
|
// Check that the curve is a line segment
|
|
const Part::Geometry* geo = Obj->getGeometry(GeoId);
|
|
|
|
if (!isLineSegment(*geo)) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("One selected edge is not a valid line."));
|
|
return;
|
|
}
|
|
ids.push_back(GeoId);
|
|
}
|
|
|
|
if (ids.size() < 2) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select at least two lines from the sketch."));
|
|
return;
|
|
}
|
|
|
|
// undo command open
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add parallel constraint"));
|
|
for (int i = 0; i < int(ids.size() - 1); i++) {
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Parallel',%d,%d))",
|
|
ids[i],
|
|
ids[i + 1]);
|
|
}
|
|
// finish the transaction and update
|
|
commitCommand();
|
|
|
|
tryAutoRecompute(Obj);
|
|
|
|
// clear the selection (convenience)
|
|
getSelection().clearSelection();
|
|
}
|
|
|
|
void CmdSketcherConstrainParallel::applyConstraint(std::vector<SelIdPair>& 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<SketcherGui::ViewProviderSketch*>(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 (! isLineSegment(*(Obj->getGeometry(GeoId1))) || ! isLineSegment(*(Obj->getGeometry(GeoId2)))) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("The selected edge is not a valid line."));
|
|
return;
|
|
}
|
|
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
|
|
showNoConstraintBetweenFixedGeometry(Obj);
|
|
return;
|
|
}
|
|
|
|
// undo command open
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add parallel constraint"));
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(),
|
|
"addConstraint(Sketcher.Constraint('Parallel',%d,%d))",
|
|
GeoId1,
|
|
GeoId2);
|
|
// finish the transaction and update
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
}
|
|
|
|
// ======================================================================================
|
|
|
|
class CmdSketcherConstrainPerpendicular: public CmdSketcherConstraint
|
|
{
|
|
public:
|
|
CmdSketcherConstrainPerpendicular();
|
|
~CmdSketcherConstrainPerpendicular() override
|
|
{}
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainPerpendicular";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
};
|
|
|
|
CmdSketcherConstrainPerpendicular::CmdSketcherConstrainPerpendicular()
|
|
: CmdSketcherConstraint("Sketcher_ConstrainPerpendicular")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "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}};
|
|
;
|
|
}
|
|
|
|
void CmdSketcherConstrainPerpendicular::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
|
|
// get the selection
|
|
std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
|
|
|
|
// only one sketch with its subelements are allowed to be selected
|
|
if (selection.size() != 1
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
|
|
|
|
if (constraintMode) {
|
|
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(this));
|
|
getSelection().clearSelection();
|
|
}
|
|
else {
|
|
// TODO: Get the exact message from git history and put it here
|
|
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 =
|
|
QObject::tr("Select some geometry from the sketch.", "perpendicular constraint");
|
|
strError.append(QString::fromLatin1("\n\n"));
|
|
strError.append(strBasicHelp);
|
|
Gui::TranslatedUserWarning(getActiveGuiDocument(),
|
|
QObject::tr("Wrong selection"),
|
|
std::move(strError));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// get the needed lists and objects
|
|
const std::vector<std::string>& SubNames = selection[0].getSubNames();
|
|
auto* Obj = dynamic_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
if (!Obj || (SubNames.size() != 2 && SubNames.size() != 3)) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Wrong number of selected objects!"));
|
|
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(Obj);
|
|
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)) {
|
|
|
|
if (isBsplinePole(Obj, GeoId1) || isBsplinePole(Obj, GeoId2)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge that is not a B-spline weight."));
|
|
return;
|
|
}
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add perpendicular constraint"));
|
|
|
|
bool safe = addConstraintSafely(Obj, [&]() {
|
|
// add missing point-on-object constraints
|
|
if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
|
|
const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
|
|
if (!(geom1 && isBSplineCurve(*geom1))) {
|
|
Gui::cmdAppObjectArgs(
|
|
selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId3,
|
|
static_cast<int>(PosId3),
|
|
GeoId1);
|
|
}
|
|
}
|
|
|
|
if (!IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)) {
|
|
const Part::Geometry *geom2 = Obj->getGeometry(GeoId2);
|
|
if (!(geom2 && isBSplineCurve(*geom2))) {
|
|
Gui::cmdAppObjectArgs(
|
|
selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId3,
|
|
static_cast<int>(PosId3),
|
|
GeoId2);
|
|
}
|
|
}
|
|
|
|
if (!IsPointAlreadyOnCurve(
|
|
GeoId1,
|
|
GeoId3,
|
|
PosId3,
|
|
Obj)) {
|
|
// FIXME: it's a good idea to add a check if the sketch is solved
|
|
const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
|
|
if (!(geom1 && isBSplineCurve(*geom1))) {
|
|
Gui::cmdAppObjectArgs(
|
|
selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId3,
|
|
static_cast<int>(PosId3),
|
|
GeoId1);
|
|
}
|
|
}
|
|
|
|
Gui::cmdAppObjectArgs(
|
|
selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('PerpendicularViaPoint',%d,%d,%d,%d))",
|
|
GeoId1,
|
|
GeoId2,
|
|
GeoId3,
|
|
static_cast<int>(PosId3));
|
|
|
|
removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3);
|
|
});
|
|
|
|
if (!safe) {
|
|
return;
|
|
}
|
|
else {
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
|
|
getSelection().clearSelection();
|
|
|
|
return;
|
|
}
|
|
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("With 3 objects, there must be 2 curves and 1 point."));
|
|
}
|
|
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)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
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 && (isBSplineCurve(*geom1) || isBSplineCurve(*geom2))) {
|
|
if (! isBSplineCurve(*geom1)) {
|
|
std::swap(GeoId1, GeoId2);
|
|
std::swap(PosId1, PosId2);
|
|
}
|
|
// GeoId1 is the B-spline now
|
|
}// end of code supports simple B-spline endpoint tangency
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add perpendicular constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Perpendicular',%d,%d,%d,%d))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2));
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
|
|
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)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr(
|
|
"Cannot add a perpendicularity constraint at an unconnected point!"));
|
|
return;
|
|
}
|
|
|
|
const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
|
|
|
|
if (isBsplinePole(geom2)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge that is not a B-spline weight."));
|
|
return;
|
|
}
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add perpendicularity constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Perpendicular',%d,%d,%d))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2);
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
|
|
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 (! isLineSegment(*geo1) && ! isLineSegment(*geo2)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("One of the selected edges should be a line."));
|
|
return;
|
|
}
|
|
|
|
// if (isBSplineCurve(*geo1) || isBSplineCurve(*geo2)) {
|
|
// // unsupported until tangent to B-spline at any point implemented.
|
|
// Gui::TranslatedUserWarning(
|
|
// Obj,
|
|
// QObject::tr("Wrong selection"),
|
|
// QObject::tr("Perpendicular to B-spline edge currently unsupported."));
|
|
// return;
|
|
// }
|
|
|
|
if (isLineSegment(*geo1)) {
|
|
std::swap(GeoId1, GeoId2);
|
|
}
|
|
|
|
if (isBsplinePole(Obj, GeoId1)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge that is not a B-spline weight."));
|
|
return;
|
|
}
|
|
|
|
// GeoId2 is the line
|
|
geo1 = Obj->getGeometry(GeoId1);
|
|
geo2 = Obj->getGeometry(GeoId2);
|
|
|
|
if (isEllipse(*geo1) || isArcOfEllipse(*geo1) || isArcOfHyperbola(*geo1) || isArcOfParabola(*geo1)) {
|
|
Base::Vector3d center;
|
|
Base::Vector3d majdir;
|
|
Base::Vector3d focus;
|
|
double majord = 0;
|
|
double minord = 0;
|
|
double phi = 0;
|
|
|
|
if (isEllipse(*geo1)) {
|
|
auto ellipse = static_cast<const Part::GeomEllipse*>(geo1);
|
|
center = ellipse->getCenter();
|
|
majord = ellipse->getMajorRadius();
|
|
minord = ellipse->getMinorRadius();
|
|
majdir = ellipse->getMajorAxisDir();
|
|
phi = atan2(majdir.y, majdir.x);
|
|
}
|
|
else if (isArcOfEllipse(*geo1)) {
|
|
auto aoe = static_cast<const Part::GeomArcOfEllipse*>(geo1);
|
|
center = aoe->getCenter();
|
|
majord = aoe->getMajorRadius();
|
|
minord = aoe->getMinorRadius();
|
|
majdir = aoe->getMajorAxisDir();
|
|
phi = atan2(majdir.y, majdir.x);
|
|
}
|
|
else if (isArcOfHyperbola(*geo1)) {
|
|
auto aoh = static_cast<const Part::GeomArcOfHyperbola*>(geo1);
|
|
center = aoh->getCenter();
|
|
majord = aoh->getMajorRadius();
|
|
minord = aoh->getMinorRadius();
|
|
majdir = aoh->getMajorAxisDir();
|
|
phi = atan2(majdir.y, majdir.x);
|
|
}
|
|
else if (isArcOfParabola(*geo1)) {
|
|
auto aop = static_cast<const Part::GeomArcOfParabola*>(geo1);
|
|
center = aop->getCenter();
|
|
focus = aop->getFocus();
|
|
}
|
|
|
|
const Part::GeomLineSegment* line = static_cast<const Part::GeomLineSegment*>(geo2);
|
|
|
|
Base::Vector3d point1 = line->getStartPoint();
|
|
Base::Vector3d PoO;
|
|
|
|
if (isArcOfHyperbola(*geo1)) {
|
|
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 (isArcOfParabola(*geo1)) {
|
|
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 anomaly 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(QT_TRANSLATE_NOOP("Command", "Add perpendicular constraint"));
|
|
|
|
try {
|
|
// Add a point
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addGeometry(Part.Point(App.Vector(%f,%f,0)))",
|
|
PoO.x,
|
|
PoO.y);
|
|
int GeoIdPoint = Obj->getHighestCurveIndex();
|
|
|
|
// Point on first object (ellipse, arc of ellipse)
|
|
Gui::cmdAppObjectArgs(
|
|
selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoIdPoint,
|
|
static_cast<int>(Sketcher::PointPos::start),
|
|
GeoId1);
|
|
// Point on second object
|
|
Gui::cmdAppObjectArgs(
|
|
selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoIdPoint,
|
|
static_cast<int>(Sketcher::PointPos::start),
|
|
GeoId2);
|
|
// add constraint: Perpendicular-via-point
|
|
Gui::cmdAppObjectArgs(
|
|
Obj,
|
|
"addConstraint(Sketcher.Constraint('PerpendicularViaPoint',%d,%d,%d,%d))",
|
|
GeoId1,
|
|
GeoId2,
|
|
GeoIdPoint,
|
|
static_cast<int>(Sketcher::PointPos::start));
|
|
}
|
|
catch (const Base::Exception& e) {
|
|
Gui::NotifyUserError(Obj,
|
|
QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"),
|
|
e.what());
|
|
Gui::Command::abortCommand();
|
|
|
|
tryAutoRecompute(Obj);
|
|
return;
|
|
}
|
|
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add perpendicular constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Perpendicular',%d,%d))",
|
|
GeoId1,
|
|
GeoId2);
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void CmdSketcherConstrainPerpendicular::applyConstraint(std::vector<SelIdPair>& selSeq,
|
|
int seqIndex)
|
|
{
|
|
SketcherGui::ViewProviderSketch* sketchgui =
|
|
static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
|
|
Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
|
|
|
|
int GeoId1 = GeoEnum::GeoUndef, GeoId2 = GeoEnum::GeoUndef, GeoId3 = GeoEnum::GeoUndef;
|
|
Sketcher::PointPos PosId1 = Sketcher::PointPos::none, PosId2 = Sketcher::PointPos::none,
|
|
PosId3 = Sketcher::PointPos::none;
|
|
|
|
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;
|
|
|
|
// check if the edge already has a Block constraint
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
|
|
showNoConstraintBetweenFixedGeometry(Obj);
|
|
return;
|
|
}
|
|
|
|
const Part::Geometry* geo1 = Obj->getGeometry(GeoId1);
|
|
const Part::Geometry* geo2 = Obj->getGeometry(GeoId2);
|
|
if (!geo1 || !geo2) {
|
|
return;
|
|
}
|
|
|
|
if (! isLineSegment(*geo1) && ! isLineSegment(*geo2)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("One of the selected edges should be a line."));
|
|
return;
|
|
}
|
|
|
|
// if (isBSplineCurve(*geo1) || isBSplineCurve(*geo2)) {
|
|
// // unsupported until tangent to B-spline at any point implemented.
|
|
// Gui::TranslatedUserWarning(
|
|
// Obj,
|
|
// QObject::tr("Wrong selection"),
|
|
// QObject::tr("Perpendicular to B-spline edge currently unsupported."));
|
|
// return;
|
|
// }
|
|
|
|
if (isLineSegment(*geo1)) {
|
|
std::swap(GeoId1, GeoId2);
|
|
}
|
|
|
|
if (isBsplinePole(Obj, GeoId1)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge that is not a B-spline weight."));
|
|
return;
|
|
}
|
|
|
|
// GeoId2 is the line
|
|
geo1 = Obj->getGeometry(GeoId1);
|
|
geo2 = Obj->getGeometry(GeoId2);
|
|
|
|
if (isEllipse(*geo1) || isArcOfEllipse(*geo1) || isArcOfHyperbola(*geo1) || isArcOfParabola(*geo1)) {
|
|
Base::Vector3d center;
|
|
Base::Vector3d majdir;
|
|
Base::Vector3d focus;
|
|
double majord = 0;
|
|
double minord = 0;
|
|
double phi = 0;
|
|
|
|
if (isEllipse(*geo1)) {
|
|
auto ellipse = static_cast<const Part::GeomEllipse*>(geo1);
|
|
center = ellipse->getCenter();
|
|
majord = ellipse->getMajorRadius();
|
|
minord = ellipse->getMinorRadius();
|
|
majdir = ellipse->getMajorAxisDir();
|
|
phi = atan2(majdir.y, majdir.x);
|
|
}
|
|
else if (isArcOfEllipse(*geo1)) {
|
|
auto aoe = static_cast<const Part::GeomArcOfEllipse*>(geo1);
|
|
center = aoe->getCenter();
|
|
majord = aoe->getMajorRadius();
|
|
minord = aoe->getMinorRadius();
|
|
majdir = aoe->getMajorAxisDir();
|
|
phi = atan2(majdir.y, majdir.x);
|
|
}
|
|
else if (isArcOfHyperbola(*geo1)) {
|
|
auto aoh = static_cast<const Part::GeomArcOfHyperbola*>(geo1);
|
|
center = aoh->getCenter();
|
|
majord = aoh->getMajorRadius();
|
|
minord = aoh->getMinorRadius();
|
|
majdir = aoh->getMajorAxisDir();
|
|
phi = atan2(majdir.y, majdir.x);
|
|
}
|
|
else if (isArcOfParabola(*geo1)) {
|
|
auto aop = static_cast<const Part::GeomArcOfParabola*>(geo1);
|
|
center = aop->getCenter();
|
|
focus = aop->getFocus();
|
|
}
|
|
|
|
const Part::GeomLineSegment* line = static_cast<const Part::GeomLineSegment*>(geo2);
|
|
|
|
Base::Vector3d point1 = line->getStartPoint();
|
|
Base::Vector3d PoO;
|
|
|
|
if (isArcOfHyperbola(*geo1)) {
|
|
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 (isArcOfParabola(*geo1)) {
|
|
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 anomaly 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(QT_TRANSLATE_NOOP("Command", "Add perpendicular constraint"));
|
|
|
|
try {
|
|
// Add a point
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addGeometry(Part.Point(App.Vector(%f,%f,0)))",
|
|
PoO.x,
|
|
PoO.y);
|
|
int GeoIdPoint = Obj->getHighestCurveIndex();
|
|
|
|
// Point on first object (ellipse, arc of ellipse)
|
|
Gui::cmdAppObjectArgs(
|
|
Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoIdPoint,
|
|
static_cast<int>(Sketcher::PointPos::start),
|
|
GeoId1);
|
|
// Point on second object
|
|
Gui::cmdAppObjectArgs(
|
|
Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoIdPoint,
|
|
static_cast<int>(Sketcher::PointPos::start),
|
|
GeoId2);
|
|
|
|
// add constraint: Perpendicular-via-point
|
|
Gui::cmdAppObjectArgs(
|
|
Obj,
|
|
"addConstraint(Sketcher.Constraint('PerpendicularViaPoint',%d,%d,%d,%d))",
|
|
GeoId1,
|
|
GeoId2,
|
|
GeoIdPoint,
|
|
static_cast<int>(Sketcher::PointPos::start));
|
|
|
|
commitCommand();
|
|
}
|
|
catch (const Base::Exception& e) {
|
|
Gui::NotifyUserError(Obj,
|
|
QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"),
|
|
e.what());
|
|
Gui::Command::abortCommand();
|
|
}
|
|
|
|
tryAutoRecompute(Obj);
|
|
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add perpendicular constraint"));
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Perpendicular',%d,%d))",
|
|
GeoId1,
|
|
GeoId2);
|
|
commitCommand();
|
|
|
|
tryAutoRecompute(Obj);
|
|
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)) {
|
|
|
|
// check if the edge already has a Block constraint
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
|
|
showNoConstraintBetweenFixedGeometry(Obj);
|
|
return;
|
|
}
|
|
|
|
if (isBsplinePole(Obj, GeoId1) || isBsplinePole(Obj, GeoId2)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge that is not a B-spline weight."));
|
|
return;
|
|
}
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add perpendicular constraint"));
|
|
|
|
bool safe = addConstraintSafely(Obj, [&]() {
|
|
// add missing point-on-object constraints
|
|
if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
|
|
const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
|
|
if (!(geom1 && isBSplineCurve(*geom1))) {
|
|
Gui::cmdAppObjectArgs(
|
|
Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId3,
|
|
static_cast<int>(PosId3),
|
|
GeoId1);
|
|
}
|
|
}
|
|
|
|
if (!IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)) {
|
|
const Part::Geometry *geom2 = Obj->getGeometry(GeoId2);
|
|
if (!(geom2 && isBSplineCurve(*geom2))) {
|
|
Gui::cmdAppObjectArgs(
|
|
Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId3,
|
|
static_cast<int>(PosId3),
|
|
GeoId2);
|
|
}
|
|
}
|
|
|
|
if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
|
|
// FIXME: it's a good idea to add a check if the sketch is solved
|
|
const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
|
|
if (!(geom1 && isBSplineCurve(*geom1))) {
|
|
Gui::cmdAppObjectArgs(
|
|
Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId3,
|
|
static_cast<int>(PosId3),
|
|
GeoId1);
|
|
}
|
|
}
|
|
|
|
Gui::cmdAppObjectArgs(
|
|
Obj,
|
|
"addConstraint(Sketcher.Constraint('PerpendicularViaPoint',%d,%d,%d,%d))",
|
|
GeoId1,
|
|
GeoId2,
|
|
GeoId3,
|
|
static_cast<int>(PosId3));
|
|
|
|
removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3);
|
|
});
|
|
|
|
if (!safe) {
|
|
return;
|
|
}
|
|
else {
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
|
|
getSelection().clearSelection();
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// ======================================================================================
|
|
|
|
class CmdSketcherConstrainTangent: public CmdSketcherConstraint
|
|
{
|
|
public:
|
|
CmdSketcherConstrainTangent();
|
|
~CmdSketcherConstrainTangent() override
|
|
{}
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainTangent";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
// returns true if a substitution took place
|
|
static bool substituteConstraintCombinations(SketchObject* Obj, int GeoId1, int GeoId2);
|
|
};
|
|
|
|
CmdSketcherConstrainTangent::CmdSketcherConstrainTangent()
|
|
: CmdSketcherConstraint("Sketcher_ConstrainTangent")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Constrain tangent or collinear");
|
|
sToolTipText = QT_TR_NOOP("Create a tangent or collinear 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*/};
|
|
}
|
|
|
|
bool CmdSketcherConstrainTangent::substituteConstraintCombinations(SketchObject* Obj,
|
|
int GeoId1,
|
|
int GeoId2)
|
|
{
|
|
const std::vector<Constraint*>& cvals = Obj->Constraints.getValues();
|
|
|
|
int cid = 0;
|
|
for (std::vector<Constraint*>::const_iterator it = cvals.begin(); it != cvals.end();
|
|
++it, ++cid) {
|
|
if ((*it)->Type == Sketcher::Coincident
|
|
&& (((*it)->First == GeoId1 && (*it)->Second == GeoId2)
|
|
|| ((*it)->Second == GeoId1 && (*it)->First == GeoId2))) {
|
|
|
|
// save values because 'doEndpointTangency' changes the
|
|
// constraint property and thus invalidates this iterator
|
|
int first = (*it)->First;
|
|
int firstpos = static_cast<int>((*it)->FirstPos);
|
|
|
|
Gui::Command::openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Swap coincident+tangency with ptp tangency"));
|
|
|
|
doEndpointTangency(Obj, (*it)->First, (*it)->Second, (*it)->FirstPos, (*it)->SecondPos);
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "delConstraintOnPoint(%d,%d)", first, firstpos);
|
|
|
|
commitCommand();
|
|
Obj->solve();// The substitution requires a solve() so that the autoremove redundants
|
|
// works when Autorecompute not active.
|
|
tryAutoRecomputeIfNotSolve(Obj);
|
|
|
|
notifyConstraintSubstitutions(QObject::tr("Endpoint to endpoint tangency was applied. "
|
|
"The coincident constraint was deleted."));
|
|
|
|
getSelection().clearSelection();
|
|
return true;
|
|
}
|
|
else if ((*it)->Type == Sketcher::PointOnObject
|
|
&& (((*it)->First == GeoId1 && (*it)->Second == GeoId2)
|
|
|| ((*it)->Second == GeoId1 && (*it)->First == GeoId2))) {
|
|
|
|
Gui::Command::openCommand(
|
|
QT_TRANSLATE_NOOP("Command",
|
|
"Swap PointOnObject+tangency with point to curve tangency"));
|
|
|
|
doEndpointToEdgeTangency(Obj, (*it)->First, (*it)->FirstPos, (*it)->Second);
|
|
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"delConstraint(%d)",
|
|
cid);// remove the preexisting point on object constraint.
|
|
|
|
commitCommand();
|
|
|
|
// A substitution requires a solve() so that the autoremove redundants works when
|
|
// Autorecompute not active. However, delConstraint includes such solve() internally. So
|
|
// at this point it is already solved.
|
|
tryAutoRecomputeIfNotSolve(Obj);
|
|
|
|
notifyConstraintSubstitutions(QObject::tr("Endpoint to edge tangency was applied. The "
|
|
"point on object constraint was deleted."));
|
|
|
|
getSelection().clearSelection();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CmdSketcherConstrainTangent::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
|
|
// get the selection
|
|
std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
|
|
|
|
// only one sketch with its subelements are allowed to be selected
|
|
if (selection.size() != 1
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
|
|
|
|
if (constraintMode) {
|
|
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(this));
|
|
getSelection().clearSelection();
|
|
}
|
|
else {
|
|
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 =
|
|
QObject::tr("Select some geometry from the sketch.", "tangent constraint");
|
|
strError.append(QString::fromLatin1("\n\n"));
|
|
strError.append(strBasicHelp);
|
|
Gui::TranslatedUserWarning(getActiveGuiDocument(),
|
|
QObject::tr("Wrong selection"),
|
|
std::move(strError));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// get the needed lists and objects
|
|
const std::vector<std::string>& SubNames = selection[0].getSubNames();
|
|
auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
if (SubNames.size() != 2 && SubNames.size() != 3) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Wrong number of selected objects!"));
|
|
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(Obj);
|
|
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)) {
|
|
|
|
if (isBsplinePole(Obj, GeoId1) || isBsplinePole(Obj, GeoId2)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge that is not a B-spline weight."));
|
|
return;
|
|
}
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint"));
|
|
|
|
bool safe = addConstraintSafely(Obj, [&]() {
|
|
// add missing point-on-object constraints
|
|
if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
|
|
const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
|
|
if (!(geom1 && isBSplineCurve(*geom1))) {
|
|
Gui::cmdAppObjectArgs(
|
|
selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId3,
|
|
static_cast<int>(PosId3),
|
|
GeoId1);
|
|
}
|
|
}
|
|
|
|
if (!IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)) {
|
|
const Part::Geometry *geom2 = Obj->getGeometry(GeoId2);
|
|
if (!(geom2 && isBSplineCurve(*geom2))) {
|
|
Gui::cmdAppObjectArgs(
|
|
selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId3,
|
|
static_cast<int>(PosId3),
|
|
GeoId2);
|
|
}
|
|
}
|
|
|
|
if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
|
|
// FIXME: it's a good idea to add a check if the sketch is solved
|
|
const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
|
|
if (!(geom1 && isBSplineCurve(*geom1))) {
|
|
Gui::cmdAppObjectArgs(
|
|
selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId3,
|
|
static_cast<int>(PosId3),
|
|
GeoId1);
|
|
}
|
|
}
|
|
|
|
Gui::cmdAppObjectArgs(
|
|
selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d))",
|
|
GeoId1,
|
|
GeoId2,
|
|
GeoId3,
|
|
static_cast<int>(PosId3));
|
|
|
|
removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3);
|
|
});
|
|
|
|
if (!safe) {
|
|
return;
|
|
}
|
|
else {
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
|
|
getSelection().clearSelection();
|
|
|
|
return;
|
|
}
|
|
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("With 3 objects, there must be 2 curves and 1 point."));
|
|
}
|
|
else if (SubNames.size() == 2) {
|
|
|
|
if (isVertex(GeoId1, PosId1) && isVertex(GeoId2, PosId2)) {// endpoint-to-endpoint tangency
|
|
|
|
if (isBsplineKnot(Obj, GeoId2)) {
|
|
std::swap(GeoId1, GeoId2);
|
|
std::swap(PosId1, PosId2);
|
|
}
|
|
|
|
if (isSimpleVertex(Obj, GeoId1, PosId1) || isSimpleVertex(Obj, GeoId2, PosId2)) {
|
|
|
|
if (isBsplineKnot(Obj, GeoId1)) {
|
|
const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
|
|
|
|
if (! geom2 || ! isLineSegment(*geom2)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Tangent constraint at B-spline knot is only supported "
|
|
"with lines!"));
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Cannot add a tangency constraint at an unconnected point!"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint"));
|
|
doEndpointTangency(Obj, GeoId1, GeoId2, PosId1, PosId2);
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
else if ((isVertex(GeoId1, PosId1) && isEdge(GeoId2, PosId2))
|
|
|| (isEdge(GeoId1, PosId1)
|
|
&& isVertex(GeoId2, PosId2))) {// endpoint-to-curve/knot-to-curve tangency
|
|
if (isVertex(GeoId2, PosId2)) {
|
|
std::swap(GeoId1, GeoId2);
|
|
std::swap(PosId1, PosId2);
|
|
}
|
|
|
|
if (isSimpleVertex(Obj, GeoId1, PosId1)) {
|
|
if (isBsplineKnot(Obj, GeoId1)) {
|
|
const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
|
|
|
|
if (!geom2 || ! isLineSegment(*geom2)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Tangent constraint at B-spline knot is only supported "
|
|
"with lines!"));
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Cannot add a tangency constraint at an unconnected point!"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
|
|
|
|
if (isBsplinePole(geom2)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge that is not a B-spline weight."));
|
|
return;
|
|
}
|
|
|
|
if (!substituteConstraintCombinations(Obj, GeoId1, GeoId2)) {
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2);
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
|
|
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 (isBsplinePole(geom1) || isBsplinePole(geom2)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge that is not a B-spline weight."));
|
|
return;
|
|
}
|
|
|
|
// check if as a consequence of this command undesirable combinations of constraints
|
|
// would arise and substitute them with more appropriate counterparts, examples:
|
|
// - coincidence + tangency on edge
|
|
// - point on object + tangency on edge
|
|
if (substituteConstraintCombinations(Obj, GeoId1, GeoId2)) {
|
|
return;
|
|
}
|
|
|
|
if (geom1 && geom2 && (isEllipse(*geom1) || isEllipse(*geom2))) {
|
|
if (! isEllipse(*geom1)) {
|
|
std::swap(GeoId1, GeoId2);
|
|
}
|
|
|
|
// GeoId1 is the ellipse
|
|
geom1 = Obj->getGeometry(GeoId1);
|
|
geom2 = Obj->getGeometry(GeoId2);
|
|
|
|
if (isEllipse(*geom2) || isArcOfEllipse(*geom2) || isCircle(*geom2) || isArcOfCircle(*geom2)) {
|
|
Gui::Command::openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
|
|
makeTangentToEllipseviaNewPoint(Obj,
|
|
static_cast<const Part::GeomEllipse*>(geom1),
|
|
geom2,
|
|
GeoId1,
|
|
GeoId2);
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
else if (isArcOfHyperbola(*geom2)) {
|
|
Gui::Command::openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
|
|
makeTangentToArcOfHyperbolaviaNewPoint(
|
|
Obj,
|
|
static_cast<const Part::GeomArcOfHyperbola*>(geom2),
|
|
geom1,
|
|
GeoId2,
|
|
GeoId1);
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
else if (isArcOfParabola(*geom2)) {
|
|
Gui::Command::openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
|
|
makeTangentToArcOfParabolaviaNewPoint(
|
|
Obj,
|
|
static_cast<const Part::GeomArcOfParabola*>(geom2),
|
|
geom1,
|
|
GeoId2,
|
|
GeoId1);
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
}
|
|
else if (geom1 && geom2 && (isArcOfEllipse(*geom1) || isArcOfEllipse(*geom2))) {
|
|
if (! isArcOfEllipse(*geom1)) {
|
|
std::swap(GeoId1, GeoId2);
|
|
}
|
|
|
|
// GeoId1 is the arc of ellipse
|
|
geom1 = Obj->getGeometry(GeoId1);
|
|
geom2 = Obj->getGeometry(GeoId2);
|
|
|
|
if (isArcOfHyperbola(*geom2) || isArcOfEllipse(*geom2)
|
|
|| isCircle(*geom2) || isArcOfCircle(*geom2) || isLineSegment(*geom2)) {
|
|
|
|
Gui::Command::openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
|
|
makeTangentToArcOfEllipseviaNewPoint(
|
|
Obj,
|
|
static_cast<const Part::GeomArcOfEllipse*>(geom1),
|
|
geom2,
|
|
GeoId1,
|
|
GeoId2);
|
|
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
else if (isArcOfParabola(*geom2)) {
|
|
Gui::Command::openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
|
|
makeTangentToArcOfParabolaviaNewPoint(
|
|
Obj,
|
|
static_cast<const Part::GeomArcOfParabola*>(geom2),
|
|
geom1,
|
|
GeoId2,
|
|
GeoId1);
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
}
|
|
else if (geom1 && geom2 && (isArcOfHyperbola(*geom1) || isArcOfHyperbola(*geom2))) {
|
|
if (! isArcOfHyperbola(*geom1)) {
|
|
std::swap(GeoId1, GeoId2);
|
|
}
|
|
|
|
// GeoId1 is the arc of hyperbola
|
|
geom1 = Obj->getGeometry(GeoId1);
|
|
geom2 = Obj->getGeometry(GeoId2);
|
|
|
|
if (isArcOfHyperbola(*geom2) || isArcOfEllipse(*geom2) || isCircle(*geom2)
|
|
|| isArcOfCircle(*geom2) || isLineSegment(*geom2)) {
|
|
|
|
Gui::Command::openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
|
|
makeTangentToArcOfHyperbolaviaNewPoint(
|
|
Obj,
|
|
static_cast<const Part::GeomArcOfHyperbola*>(geom1),
|
|
geom2,
|
|
GeoId1,
|
|
GeoId2);
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
else if (isArcOfParabola(*geom2)) {
|
|
Gui::Command::openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
|
|
makeTangentToArcOfParabolaviaNewPoint(
|
|
Obj,
|
|
static_cast<const Part::GeomArcOfParabola*>(geom2),
|
|
geom1,
|
|
GeoId2,
|
|
GeoId1);
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
}
|
|
else if (geom1 && geom2 && (isArcOfParabola(*geom1) || isArcOfParabola(*geom2))) {
|
|
if (! isArcOfParabola(*geom1)) {
|
|
std::swap(GeoId1, GeoId2);
|
|
}
|
|
|
|
// GeoId1 is the arc of hyperbola
|
|
geom1 = Obj->getGeometry(GeoId1);
|
|
geom2 = Obj->getGeometry(GeoId2);
|
|
|
|
if (isArcOfParabola(*geom2) || isArcOfHyperbola(*geom2)
|
|
|| isArcOfEllipse(*geom2) || isCircle(*geom2)
|
|
|| isArcOfCircle(*geom2) || isLineSegment(*geom2)) {
|
|
|
|
Gui::Command::openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
|
|
makeTangentToArcOfParabolaviaNewPoint(
|
|
Obj,
|
|
static_cast<const Part::GeomArcOfParabola*>(geom1),
|
|
geom2,
|
|
GeoId1,
|
|
GeoId2);
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
}
|
|
else if (geom1 && geom2 && (isBSplineCurve(*geom1) || isBSplineCurve(*geom2))) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Only tangent-via-point is supported with a B-spline."));
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Tangent',%d,%d))",
|
|
GeoId1,
|
|
GeoId2);
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void CmdSketcherConstrainTangent::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
SketcherGui::ViewProviderSketch* sketchgui =
|
|
static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
|
|
Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
|
|
|
|
int GeoId1 = GeoEnum::GeoUndef, GeoId2 = GeoEnum::GeoUndef, GeoId3 = GeoEnum::GeoUndef;
|
|
Sketcher::PointPos PosId1 = Sketcher::PointPos::none, PosId2 = Sketcher::PointPos::none,
|
|
PosId3 = Sketcher::PointPos::none;
|
|
|
|
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;
|
|
|
|
// check if the edge already has a Block constraint
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
|
|
showNoConstraintBetweenFixedGeometry(Obj);
|
|
return;
|
|
}
|
|
|
|
const Part::Geometry* geom1 = Obj->getGeometry(GeoId1);
|
|
const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
|
|
|
|
if (isBsplinePole(geom1) || isBsplinePole(geom2)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge that is not a B-spline weight."));
|
|
return;
|
|
}
|
|
|
|
// check if as a consequence of this command undesirable combinations of constraints
|
|
// would arise and substitute them with more appropriate counterparts, examples:
|
|
// - coincidence + tangency on edge
|
|
// - point on object + tangency on edge
|
|
if (substituteConstraintCombinations(Obj, GeoId1, GeoId2)) {
|
|
return;
|
|
}
|
|
|
|
if (geom1 && geom2 && (isEllipse(*geom1) || isEllipse(*geom2))) {
|
|
if (! isEllipse(*geom1)) {
|
|
std::swap(GeoId1, GeoId2);
|
|
}
|
|
|
|
// GeoId1 is the ellipse
|
|
geom1 = Obj->getGeometry(GeoId1);
|
|
geom2 = Obj->getGeometry(GeoId2);
|
|
|
|
if (isEllipse(*geom2) || isArcOfEllipse(*geom2)
|
|
|| isCircle(*geom2) || isArcOfCircle(*geom2)) {
|
|
|
|
Gui::Command::openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
|
|
makeTangentToEllipseviaNewPoint(Obj,
|
|
static_cast<const Part::GeomEllipse*>(geom1),
|
|
geom2,
|
|
GeoId1,
|
|
GeoId2);
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
else if (isArcOfHyperbola(*geom2)) {
|
|
Gui::Command::openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
|
|
makeTangentToArcOfHyperbolaviaNewPoint(
|
|
Obj,
|
|
static_cast<const Part::GeomArcOfHyperbola*>(geom2),
|
|
geom1,
|
|
GeoId2,
|
|
GeoId1);
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
else if (isArcOfParabola(*geom2)) {
|
|
Gui::Command::openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
|
|
makeTangentToArcOfParabolaviaNewPoint(
|
|
Obj,
|
|
static_cast<const Part::GeomArcOfParabola*>(geom2),
|
|
geom1,
|
|
GeoId2,
|
|
GeoId1);
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
}
|
|
else if (geom1 && geom2 && (isArcOfHyperbola(*geom1) || isArcOfHyperbola(*geom2))) {
|
|
if (! isArcOfHyperbola(*geom1)) {
|
|
std::swap(GeoId1, GeoId2);
|
|
}
|
|
|
|
// GeoId1 is the arc of hyperbola
|
|
geom1 = Obj->getGeometry(GeoId1);
|
|
geom2 = Obj->getGeometry(GeoId2);
|
|
|
|
if (isArcOfHyperbola(*geom2) || isArcOfEllipse(*geom2) || isCircle(*geom2)
|
|
|| isArcOfCircle(*geom2) || isLineSegment(*geom2)) {
|
|
|
|
Gui::Command::openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
|
|
makeTangentToArcOfHyperbolaviaNewPoint(
|
|
Obj,
|
|
static_cast<const Part::GeomArcOfHyperbola*>(geom1),
|
|
geom2,
|
|
GeoId1,
|
|
GeoId2);
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
else if (isArcOfParabola(*geom2)) {
|
|
Gui::Command::openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
|
|
makeTangentToArcOfParabolaviaNewPoint(
|
|
Obj,
|
|
static_cast<const Part::GeomArcOfParabola*>(geom2),
|
|
geom1,
|
|
GeoId2,
|
|
GeoId1);
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
}
|
|
else if (geom1 && geom2 && (isArcOfParabola(*geom1) || isArcOfParabola(*geom2))) {
|
|
if (! isArcOfParabola(*geom1)) {
|
|
std::swap(GeoId1, GeoId2);
|
|
}
|
|
|
|
// GeoId1 is the arc of hyperbola
|
|
geom1 = Obj->getGeometry(GeoId1);
|
|
geom2 = Obj->getGeometry(GeoId2);
|
|
|
|
if (isArcOfParabola(*geom2) || isArcOfHyperbola(*geom2) || isArcOfEllipse(*geom2)
|
|
|| isCircle(*geom2) || isArcOfCircle(*geom2) || isLineSegment(*geom2)) {
|
|
|
|
Gui::Command::openCommand(
|
|
QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
|
|
makeTangentToArcOfParabolaviaNewPoint(
|
|
Obj,
|
|
static_cast<const Part::GeomArcOfParabola*>(geom1),
|
|
geom2,
|
|
GeoId1,
|
|
GeoId2);
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
}
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint"));
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Tangent',%d,%d))",
|
|
GeoId1,
|
|
GeoId2);
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
|
|
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;
|
|
|
|
// check if the edge already has a Block constraint
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
|
|
showNoConstraintBetweenFixedGeometry(Obj);
|
|
return;
|
|
}
|
|
|
|
if (isSimpleVertex(Obj, GeoId1, PosId1) || isSimpleVertex(Obj, GeoId2, PosId2)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
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 && (isBSplineCurve(*geom1) || isBSplineCurve(*geom2))) {
|
|
if (! isBSplineCurve(*geom1)) {
|
|
std::swap(GeoId1, GeoId2);
|
|
std::swap(PosId1, PosId2);
|
|
}
|
|
// GeoId1 is the B-spline now
|
|
}// end of code supports simple B-spline endpoint tangency
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint"));
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d,%d))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2));
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) {
|
|
|
|
// check if the edge already has a Block constraint
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
|
|
showNoConstraintBetweenFixedGeometry(Obj);
|
|
return;
|
|
}
|
|
|
|
if (isBsplinePole(Obj, GeoId1) || isBsplinePole(Obj, GeoId2)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge that is not a B-spline weight."));
|
|
return;
|
|
}
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint"));
|
|
|
|
bool safe = addConstraintSafely(Obj, [&]() {
|
|
// add missing point-on-object constraints
|
|
if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
|
|
const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
|
|
if (!(geom1 && isBSplineCurve(*geom1))) {
|
|
Gui::cmdAppObjectArgs(
|
|
Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId3,
|
|
static_cast<int>(PosId3),
|
|
GeoId1);
|
|
}
|
|
}
|
|
|
|
if (!IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)) {
|
|
const Part::Geometry *geom2 = Obj->getGeometry(GeoId2);
|
|
if (!(geom2 && isBSplineCurve(*geom2))) {
|
|
Gui::cmdAppObjectArgs(
|
|
Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId3,
|
|
static_cast<int>(PosId3),
|
|
GeoId2);
|
|
}
|
|
}
|
|
|
|
if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
|
|
// FIXME: it's a good idea to add a check if the sketch is solved
|
|
const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
|
|
if (!(geom1 && isBSplineCurve(*geom1))) {
|
|
Gui::cmdAppObjectArgs(
|
|
Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId3,
|
|
static_cast<int>(PosId3),
|
|
GeoId1);
|
|
}
|
|
}
|
|
|
|
Gui::cmdAppObjectArgs(
|
|
Obj,
|
|
"addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d))",
|
|
GeoId1,
|
|
GeoId2,
|
|
GeoId3,
|
|
static_cast<int>(PosId3));
|
|
|
|
removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3);
|
|
});
|
|
|
|
if (!safe) {
|
|
return;
|
|
}
|
|
else {
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
|
|
getSelection().clearSelection();
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// ======================================================================================
|
|
|
|
class CmdSketcherConstrainRadius: public CmdSketcherConstraint
|
|
{
|
|
public:
|
|
CmdSketcherConstrainRadius();
|
|
~CmdSketcherConstrainRadius() override
|
|
{}
|
|
void updateAction(int mode) override;
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainRadius";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
};
|
|
|
|
CmdSketcherConstrainRadius::CmdSketcherConstrainRadius()
|
|
: CmdSketcherConstraint("Sketcher_ConstrainRadius")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "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 = "K, R";
|
|
eType = ForEdit;
|
|
|
|
allowedSelSequences = {{SelEdge}, {SelExternalEdge}};
|
|
}
|
|
|
|
void CmdSketcherConstrainRadius::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
// get the selection
|
|
std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
|
|
|
|
// only one sketch with its subelements are allowed to be selected
|
|
if (selection.size() != 1
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
|
|
|
|
if (constraintMode) {
|
|
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(this));
|
|
getSelection().clearSelection();
|
|
}
|
|
else {
|
|
// TODO: Get the exact message from git history and put it here
|
|
Gui::TranslatedUserWarning(getActiveGuiDocument(),
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select the right things from the sketch."));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// get the needed lists and objects
|
|
const std::vector<std::string>& SubNames = selection[0].getSubNames();
|
|
auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
if (SubNames.empty()) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
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<int, double>> geoIdRadiusMap;
|
|
std::vector<std::pair<int, double>> externalGeoIdRadiusMap;
|
|
|
|
bool poles = false;
|
|
bool nonpoles = false;
|
|
|
|
for (auto& subname : SubNames) {
|
|
bool issegmentfixed = false;
|
|
int GeoId;
|
|
|
|
if (subname.size() > 4 && subname.substr(0, 4) == "Edge") {
|
|
GeoId = std::atoi(subname.substr(4, 4000).c_str()) - 1;
|
|
issegmentfixed = isPointOrSegmentFixed(Obj, GeoId);
|
|
}
|
|
else if (subname.size() > 4 && subname.substr(0, 12) == "ExternalEdge") {
|
|
GeoId = -std::atoi(subname.substr(12, 4000).c_str()) - 2;
|
|
issegmentfixed = true;
|
|
}
|
|
else {
|
|
continue;
|
|
}
|
|
|
|
const Part::Geometry* geom = Obj->getGeometry(GeoId);
|
|
|
|
if (geom && isArcOfCircle(*geom)) {
|
|
auto arc = static_cast<const Part::GeomArcOfCircle*>(geom);
|
|
double radius = arc->getRadius();
|
|
|
|
if (issegmentfixed) {
|
|
externalGeoIdRadiusMap.emplace_back(GeoId, radius);
|
|
}
|
|
else {
|
|
geoIdRadiusMap.emplace_back(GeoId, radius);
|
|
}
|
|
|
|
nonpoles = true;
|
|
}
|
|
else if (geom && isCircle(*geom)) {
|
|
auto circle = static_cast<const Part::GeomCircle*>(geom);
|
|
double radius = circle->getRadius();
|
|
|
|
if (issegmentfixed) {
|
|
externalGeoIdRadiusMap.emplace_back(GeoId, radius);
|
|
}
|
|
else {
|
|
geoIdRadiusMap.emplace_back(GeoId, radius);
|
|
}
|
|
|
|
if (isBsplinePole(geom)) {
|
|
poles = true;
|
|
}
|
|
else {
|
|
nonpoles = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (geoIdRadiusMap.empty() && externalGeoIdRadiusMap.empty()) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select one or more arcs or circles from the sketch."));
|
|
return;
|
|
}
|
|
|
|
if (poles && nonpoles) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select either only one or more B-Spline poles or only one or more arcs or "
|
|
"circles from the sketch, but not mixed."));
|
|
return;
|
|
}
|
|
|
|
bool commitNeeded = false;
|
|
bool updateNeeded = false;
|
|
bool commandopened = false;
|
|
|
|
if (!externalGeoIdRadiusMap.empty()) {
|
|
// Create the non-driving radius constraints now
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add radius constraint"));
|
|
commandopened = true;
|
|
unsigned int constrSize = 0;
|
|
|
|
for (std::vector<std::pair<int, double>>::iterator it = externalGeoIdRadiusMap.begin();
|
|
it != externalGeoIdRadiusMap.end();
|
|
++it) {
|
|
|
|
if (nonpoles) {
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Radius',%d,%f))",
|
|
it->first,
|
|
it->second);
|
|
}
|
|
else {
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Weight',%d,%f))",
|
|
it->first,
|
|
it->second);
|
|
}
|
|
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
constrSize = ConStr.size();
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
constrSize - 1,
|
|
"False");
|
|
}
|
|
|
|
finishDatumConstraint(this, Obj, false, externalGeoIdRadiusMap.size());
|
|
|
|
commitNeeded = true;
|
|
updateNeeded = true;
|
|
}
|
|
|
|
if (!geoIdRadiusMap.empty()) {
|
|
if (geoIdRadiusMap.size() > 1 && constraintCreationMode == Driving) {
|
|
|
|
int refGeoId = geoIdRadiusMap.front().first;
|
|
double radius = geoIdRadiusMap.front().second;
|
|
|
|
if (!commandopened) {
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add radius constraint"));
|
|
}
|
|
|
|
// Add the equality constraints
|
|
for (std::vector<std::pair<int, double>>::iterator it = geoIdRadiusMap.begin() + 1;
|
|
it != geoIdRadiusMap.end();
|
|
++it) {
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Equal',%d,%d))",
|
|
refGeoId,
|
|
it->first);
|
|
}
|
|
|
|
if (nonpoles) {
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Radius',%d,%f))",
|
|
refGeoId,
|
|
radius);
|
|
}
|
|
else {
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Weight',%d,%f))",
|
|
refGeoId,
|
|
radius);
|
|
}
|
|
}
|
|
else {
|
|
// Create the radius constraints now
|
|
if (!commandopened) {
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add radius constraint"));
|
|
}
|
|
for (std::vector<std::pair<int, double>>::iterator it = geoIdRadiusMap.begin();
|
|
it != geoIdRadiusMap.end();
|
|
++it) {
|
|
if (nonpoles) {
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Radius',%d,%f))",
|
|
it->first,
|
|
it->second);
|
|
}
|
|
else {
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Weight',%d,%f))",
|
|
it->first,
|
|
it->second);
|
|
}
|
|
|
|
if (constraintCreationMode == Reference) {
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
ConStr.size() - 1,
|
|
"False");
|
|
}
|
|
}
|
|
}
|
|
|
|
finishDatumConstraint(this, Obj, constraintCreationMode == Driving);
|
|
|
|
// 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<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
SketcherGui::ViewProviderSketch* sketchgui =
|
|
static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
|
|
Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
|
|
|
|
int GeoId = selSeq.at(0).GeoId;
|
|
double radius = 0.0;
|
|
|
|
bool updateNeeded = false;
|
|
|
|
switch (seqIndex) {
|
|
case 0:// {SelEdge}
|
|
case 1:// {SelExternalEdge}
|
|
{
|
|
const Part::Geometry* geom = Obj->getGeometry(GeoId);
|
|
|
|
if (geom && isArcOfCircle(*geom)) {
|
|
auto arc = static_cast<const Part::GeomArcOfCircle*>(geom);
|
|
radius = arc->getRadius();
|
|
}
|
|
else if (geom && isCircle(*geom)) {
|
|
auto circle = static_cast<const Part::GeomCircle*>(geom);
|
|
radius = circle->getRadius();
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Constraint only applies to arcs or circles."));
|
|
return;
|
|
}
|
|
|
|
// Create the radius constraint now
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add radius constraint"));
|
|
|
|
bool ispole = isBsplinePole(geom);
|
|
|
|
if (ispole) {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Weight',%d,%f))",
|
|
GeoId,
|
|
radius);
|
|
}
|
|
else {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Radius',%d,%f))",
|
|
GeoId,
|
|
radius);
|
|
}
|
|
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
bool fixed = isPointOrSegmentFixed(Obj, GeoId);
|
|
if (fixed || constraintCreationMode == Reference) {
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
|
|
|
|
updateNeeded = true;// We do need to update the solver DoF after setting the
|
|
// constraint driving.
|
|
}
|
|
|
|
finishDatumConstraint(this, Obj, constraintCreationMode == Driving && !fixed);
|
|
|
|
// updateActive();
|
|
getSelection().clearSelection();
|
|
|
|
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().iconFromTheme("Constraint_Radius_Driven"));
|
|
}
|
|
break;
|
|
case Driving:
|
|
if (getAction()) {
|
|
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radius"));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ======================================================================================
|
|
|
|
class CmdSketcherConstrainDiameter: public CmdSketcherConstraint
|
|
{
|
|
public:
|
|
CmdSketcherConstrainDiameter();
|
|
~CmdSketcherConstrainDiameter() override
|
|
{}
|
|
void updateAction(int mode) override;
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainDiameter";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
};
|
|
|
|
CmdSketcherConstrainDiameter::CmdSketcherConstrainDiameter()
|
|
: CmdSketcherConstraint("Sketcher_ConstrainDiameter")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Constrain diameter");
|
|
sToolTipText = QT_TR_NOOP("Fix the diameter of a circle or an arc");
|
|
sWhatsThis = "Sketcher_ConstrainDiameter";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "Constraint_Diameter";
|
|
sAccel = "K, O";
|
|
eType = ForEdit;
|
|
|
|
allowedSelSequences = {{SelEdge}, {SelExternalEdge}};
|
|
}
|
|
|
|
void CmdSketcherConstrainDiameter::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
// get the selection
|
|
std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
|
|
|
|
// only one sketch with its subelements are allowed to be selected
|
|
if (selection.size() != 1
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
|
|
|
|
if (constraintMode) {
|
|
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(this));
|
|
getSelection().clearSelection();
|
|
}
|
|
else {
|
|
// TODO: Get the exact message from git history and put it here
|
|
Gui::TranslatedUserWarning(getActiveGuiDocument(),
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select the right things from the sketch."));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// get the needed lists and objects
|
|
const std::vector<std::string>& SubNames = selection[0].getSubNames();
|
|
auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
if (SubNames.empty()) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
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<int, double>> geoIdDiameterMap;
|
|
std::vector<std::pair<int, double>> externalGeoIdDiameterMap;
|
|
|
|
for (auto& subname : SubNames) {
|
|
bool issegmentfixed = false;
|
|
int GeoId;
|
|
|
|
if (subname.size() > 4 && subname.substr(0, 4) == "Edge") {
|
|
GeoId = std::atoi(subname.substr(4, 4000).c_str()) - 1;
|
|
issegmentfixed = isPointOrSegmentFixed(Obj, GeoId);
|
|
}
|
|
else if (subname.size() > 4 && subname.substr(0, 12) == "ExternalEdge") {
|
|
GeoId = -std::atoi(subname.substr(12, 4000).c_str()) - 2;
|
|
issegmentfixed = true;
|
|
}
|
|
else {
|
|
continue;
|
|
}
|
|
|
|
const Part::Geometry* geom = Obj->getGeometry(GeoId);
|
|
|
|
if (geom && isArcOfCircle(*geom)) {
|
|
auto arc = static_cast<const Part::GeomArcOfCircle*>(geom);
|
|
double radius = arc->getRadius();
|
|
|
|
if (issegmentfixed) {
|
|
externalGeoIdDiameterMap.emplace_back(GeoId, 2 * radius);
|
|
}
|
|
else {
|
|
geoIdDiameterMap.emplace_back(GeoId, 2 * radius);
|
|
}
|
|
}
|
|
else if (geom && isCircle(*geom)) {
|
|
auto circle = static_cast<const Part::GeomCircle*>(geom);
|
|
double radius = circle->getRadius();
|
|
|
|
if (isBsplinePole(geom)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge that is not a B-spline weight."));
|
|
|
|
continue;
|
|
}
|
|
|
|
if (issegmentfixed) {
|
|
externalGeoIdDiameterMap.emplace_back(GeoId, 2 * radius);
|
|
}
|
|
else {
|
|
geoIdDiameterMap.emplace_back(GeoId, 2 * radius);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (geoIdDiameterMap.empty() && externalGeoIdDiameterMap.empty()) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select one or more arcs or circles from the sketch."));
|
|
return;
|
|
}
|
|
|
|
bool commitNeeded = false;
|
|
bool updateNeeded = false;
|
|
bool commandopened = false;
|
|
|
|
if (!externalGeoIdDiameterMap.empty()) {
|
|
// Create the non-driving radius constraints now
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add diameter constraint"));
|
|
commandopened = true;
|
|
unsigned int constrSize = 0;
|
|
|
|
for (std::vector<std::pair<int, double>>::iterator it = externalGeoIdDiameterMap.begin();
|
|
it != externalGeoIdDiameterMap.end();
|
|
++it) {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Diameter',%d,%f))",
|
|
it->first,
|
|
it->second);
|
|
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
constrSize = ConStr.size();
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", constrSize - 1, "False");
|
|
}
|
|
|
|
finishDatumConstraint(this, Obj, false, externalGeoIdDiameterMap.size());
|
|
|
|
commitNeeded = true;
|
|
updateNeeded = true;
|
|
}
|
|
|
|
if (!geoIdDiameterMap.empty()) {
|
|
if (geoIdDiameterMap.size() > 1 && constraintCreationMode == Driving) {
|
|
|
|
int refGeoId = geoIdDiameterMap.front().first;
|
|
double diameter = geoIdDiameterMap.front().second;
|
|
|
|
if (!commandopened) {
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add diameter constraint"));
|
|
}
|
|
|
|
// Add the equality constraints
|
|
for (std::vector<std::pair<int, double>>::iterator it = geoIdDiameterMap.begin() + 1;
|
|
it != geoIdDiameterMap.end();
|
|
++it) {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Equal',%d,%d))",
|
|
refGeoId,
|
|
it->first);
|
|
}
|
|
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Diameter',%d,%f))",
|
|
refGeoId,
|
|
diameter);
|
|
}
|
|
else {
|
|
// Create the diameter constraints now
|
|
if (!commandopened) {
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add diameter constraint"));
|
|
}
|
|
for (std::vector<std::pair<int, double>>::iterator it = geoIdDiameterMap.begin();
|
|
it != geoIdDiameterMap.end();
|
|
++it) {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Diameter',%d,%f))",
|
|
it->first,
|
|
it->second);
|
|
|
|
if (constraintCreationMode == Reference) {
|
|
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
|
|
}
|
|
}
|
|
}
|
|
|
|
finishDatumConstraint(this, Obj, constraintCreationMode == Driving);
|
|
|
|
// updateActive();
|
|
getSelection().clearSelection();
|
|
}
|
|
|
|
if (commitNeeded) {
|
|
commitCommand();
|
|
}
|
|
|
|
if (updateNeeded) {
|
|
tryAutoRecomputeIfNotSolve(Obj);// we have to update the solver after this aborted addition.
|
|
}
|
|
}
|
|
|
|
void CmdSketcherConstrainDiameter::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
SketcherGui::ViewProviderSketch* sketchgui =
|
|
static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
|
|
Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
|
|
|
|
int GeoId = selSeq.at(0).GeoId;
|
|
double diameter = 0.0;
|
|
|
|
bool updateNeeded = false;
|
|
|
|
switch (seqIndex) {
|
|
case 0:// {SelEdge}
|
|
case 1:// {SelExternalEdge}
|
|
{
|
|
const Part::Geometry* geom = Obj->getGeometry(GeoId);
|
|
|
|
if (geom && isArcOfCircle(*geom)) {
|
|
auto arc = static_cast<const Part::GeomArcOfCircle*>(geom);
|
|
diameter = 2 * arc->getRadius();
|
|
}
|
|
else if (geom && isCircle(*geom)) {
|
|
auto circle = static_cast<const Part::GeomCircle*>(geom);
|
|
diameter = 2 * circle->getRadius();
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Constraint only applies to arcs or circles."));
|
|
return;
|
|
}
|
|
|
|
if (isBsplinePole(geom)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge that is not a B-spline weight."));
|
|
return;
|
|
}
|
|
|
|
// Create the diameter constraint now
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add diameter constraint"));
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Diameter',%d,%f))",
|
|
GeoId,
|
|
diameter);
|
|
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
bool fixed = isPointOrSegmentFixed(Obj, GeoId);
|
|
if (fixed || constraintCreationMode == Reference) {
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
|
|
updateNeeded = true;// We do need to update the solver DoF after setting the
|
|
// constraint driving.
|
|
}
|
|
|
|
finishDatumConstraint(this, Obj, constraintCreationMode == Driving && !fixed);
|
|
|
|
// updateActive();
|
|
getSelection().clearSelection();
|
|
|
|
commitCommand();
|
|
|
|
if (updateNeeded) {
|
|
tryAutoRecomputeIfNotSolve(
|
|
Obj);// we have to update the solver after this aborted addition.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmdSketcherConstrainDiameter::updateAction(int mode)
|
|
{
|
|
switch (mode) {
|
|
case Reference:
|
|
if (getAction()) {
|
|
getAction()->setIcon(
|
|
Gui::BitmapFactory().iconFromTheme("Constraint_Diameter_Driven"));
|
|
}
|
|
break;
|
|
case Driving:
|
|
if (getAction()) {
|
|
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Diameter"));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ======================================================================================
|
|
|
|
class CmdSketcherConstrainRadiam: public CmdSketcherConstraint
|
|
{
|
|
public:
|
|
CmdSketcherConstrainRadiam();
|
|
~CmdSketcherConstrainRadiam() override
|
|
{}
|
|
void updateAction(int mode) override;
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainRadiam";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
};
|
|
|
|
CmdSketcherConstrainRadiam::CmdSketcherConstrainRadiam()
|
|
: CmdSketcherConstraint("Sketcher_ConstrainRadiam")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Constrain auto radius/diameter");
|
|
sToolTipText = QT_TR_NOOP(
|
|
"Fix the diameter if a circle is chosen, or the radius if an arc/spline pole is chosen");
|
|
sWhatsThis = "Sketcher_ConstrainRadiam";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "Constraint_Radiam";
|
|
sAccel = "K, S";
|
|
eType = ForEdit;
|
|
|
|
allowedSelSequences = {{SelEdge}, {SelExternalEdge}};
|
|
}
|
|
|
|
void CmdSketcherConstrainRadiam::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
// get the selection
|
|
std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
|
|
|
|
// only one sketch with its subelements are allowed to be selected
|
|
if (selection.size() != 1
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
|
|
|
|
if (constraintMode) {
|
|
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(this));
|
|
getSelection().clearSelection();
|
|
}
|
|
else {
|
|
// TODO: Get the exact message from git history and put it here
|
|
Gui::TranslatedUserWarning(getActiveGuiDocument(),
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select the right things from the sketch."));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// get the needed lists and objects
|
|
const std::vector<std::string>& SubNames = selection[0].getSubNames();
|
|
auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
if (SubNames.empty()) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
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<int, double>> geoIdRadiamMap;
|
|
std::vector<std::pair<int, double>> externalGeoIdRadiamMap;
|
|
|
|
bool poles = false;
|
|
bool nonpoles = false;
|
|
|
|
for (auto& subname : SubNames) {
|
|
bool issegmentfixed = false;
|
|
int GeoId;
|
|
|
|
if (subname.size() > 4 && subname.substr(0, 4) == "Edge") {
|
|
GeoId = std::atoi(subname.substr(4, 4000).c_str()) - 1;
|
|
issegmentfixed = isPointOrSegmentFixed(Obj, GeoId);
|
|
}
|
|
else if (subname.size() > 4 && subname.substr(0, 12) == "ExternalEdge") {
|
|
GeoId = -std::atoi(subname.substr(12, 4000).c_str()) - 2;
|
|
issegmentfixed = true;
|
|
}
|
|
else {
|
|
continue;
|
|
}
|
|
|
|
const Part::Geometry* geom = Obj->getGeometry(GeoId);
|
|
double radius;
|
|
|
|
if (geom && isArcOfCircle(*geom)) {
|
|
auto arcir = static_cast<const Part::GeomArcOfCircle*>(geom);
|
|
radius = arcir->getRadius();
|
|
nonpoles = true;
|
|
}
|
|
else if (geom && isCircle(*geom)) {
|
|
auto arcir = static_cast<const Part::GeomCircle*>(geom);
|
|
radius = arcir->getRadius();
|
|
if (isBsplinePole(geom)) {
|
|
poles = true;
|
|
}
|
|
else {
|
|
nonpoles = true;
|
|
}
|
|
}
|
|
else {
|
|
continue;
|
|
}
|
|
|
|
if (issegmentfixed) {
|
|
externalGeoIdRadiamMap.emplace_back(GeoId, radius);
|
|
}
|
|
else {
|
|
geoIdRadiamMap.emplace_back(GeoId, radius);
|
|
}
|
|
}
|
|
|
|
if (geoIdRadiamMap.empty() && externalGeoIdRadiamMap.empty()) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select one or more arcs or circles from the sketch."));
|
|
return;
|
|
}
|
|
|
|
if (poles && nonpoles) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select either only one or more B-Spline poles or only one or more arcs or "
|
|
"circles from the sketch, but not mixed."));
|
|
return;
|
|
}
|
|
|
|
bool commitNeeded = false;
|
|
bool updateNeeded = false;
|
|
bool commandopened = false;
|
|
|
|
if (!externalGeoIdRadiamMap.empty()) {
|
|
// Create the non-driving radiam constraints now
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add radiam constraint"));
|
|
commandopened = true;
|
|
unsigned int constrSize = 0;
|
|
|
|
for (std::vector<std::pair<int, double>>::iterator it = externalGeoIdRadiamMap.begin();
|
|
it != externalGeoIdRadiamMap.end();
|
|
++it) {
|
|
if (isArcOfCircle(*(Obj->getGeometry(it->first)))) {
|
|
if (nonpoles) {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Radius',%d,%f))",
|
|
it->first,
|
|
it->second);
|
|
}
|
|
else {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Weight',%d,%f))",
|
|
it->first,
|
|
it->second);
|
|
}
|
|
}
|
|
else {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Diameter',%d,%f))",
|
|
it->first,
|
|
it->second * 2);
|
|
}
|
|
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
constrSize = ConStr.size();
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", constrSize - 1, "False");
|
|
}
|
|
|
|
finishDatumConstraint(this, Obj, false, externalGeoIdRadiamMap.size());
|
|
|
|
commitNeeded = true;
|
|
updateNeeded = true;
|
|
}
|
|
|
|
if (!geoIdRadiamMap.empty()) {
|
|
if (geoIdRadiamMap.size() > 1 && constraintCreationMode == Driving) {
|
|
|
|
int refGeoId = geoIdRadiamMap.front().first;
|
|
double radiam = geoIdRadiamMap.front().second;
|
|
|
|
if (!commandopened) {
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add radiam constraint"));
|
|
}
|
|
|
|
// Add the equality constraints
|
|
for (std::vector<std::pair<int, double>>::iterator it = geoIdRadiamMap.begin() + 1;
|
|
it != geoIdRadiamMap.end();
|
|
++it) {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Equal',%d,%d))",
|
|
refGeoId,
|
|
it->first);
|
|
}
|
|
|
|
if (poles) {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Weight',%d,%f))",
|
|
refGeoId,
|
|
radiam);
|
|
}
|
|
else if (isCircle(*(Obj->getGeometry(refGeoId)))) {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Diameter',%d,%f))",
|
|
refGeoId,
|
|
radiam * 2);
|
|
}
|
|
else {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Radius',%d,%f))",
|
|
refGeoId,
|
|
radiam);
|
|
}
|
|
}
|
|
else {
|
|
// Create the radiam constraints now
|
|
if (!commandopened) {
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add radiam constraint"));
|
|
}
|
|
for (std::vector<std::pair<int, double>>::iterator it = geoIdRadiamMap.begin();
|
|
it != geoIdRadiamMap.end();
|
|
++it) {
|
|
if (poles) {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Weight',%d,%f))",
|
|
it->first,
|
|
it->second);
|
|
}
|
|
else if (isCircle(*(Obj->getGeometry(it->first)))){
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Diameter',%d,%f))",
|
|
it->first,
|
|
it->second * 2);
|
|
}
|
|
else {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Radius',%d,%f))",
|
|
it->first,
|
|
it->second);
|
|
}
|
|
|
|
if (constraintCreationMode == Reference) {
|
|
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
|
|
}
|
|
}
|
|
}
|
|
|
|
finishDatumConstraint(this, Obj, constraintCreationMode == Driving);
|
|
|
|
// updateActive();
|
|
getSelection().clearSelection();
|
|
}
|
|
|
|
if (commitNeeded) {
|
|
commitCommand();
|
|
}
|
|
|
|
if (updateNeeded) {
|
|
tryAutoRecomputeIfNotSolve(Obj);// we have to update the solver after this aborted addition.
|
|
}
|
|
}
|
|
|
|
void CmdSketcherConstrainRadiam::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
SketcherGui::ViewProviderSketch* sketchgui =
|
|
static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
|
|
Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
|
|
|
|
int GeoId = selSeq.at(0).GeoId;
|
|
double radiam = 0.0;
|
|
|
|
bool updateNeeded = false;
|
|
|
|
bool isCircleGeom = false;
|
|
bool isPole = false;
|
|
|
|
switch (seqIndex) {
|
|
case 0:// {SelEdge}
|
|
case 1:// {SelExternalEdge}
|
|
{
|
|
const Part::Geometry* geom = Obj->getGeometry(GeoId);
|
|
|
|
if (geom && isArcOfCircle(*geom)) {
|
|
auto arc = static_cast<const Part::GeomArcOfCircle*>(geom);
|
|
radiam = arc->getRadius();
|
|
}
|
|
else if (geom && isCircle(*geom)) {
|
|
auto circle = static_cast<const Part::GeomCircle*>(geom);
|
|
radiam = circle->getRadius();
|
|
isCircleGeom= true;
|
|
if (isBsplinePole(geom)) {
|
|
isPole = true;
|
|
}
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Constraint only applies to arcs or circles."));
|
|
return;
|
|
}
|
|
|
|
// Create the radiam constraint now
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add radiam constraint"));
|
|
|
|
if (isPole) {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Weight',%d,%f))",
|
|
GeoId,
|
|
radiam);
|
|
}
|
|
else if (isCircleGeom) {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Diameter',%d,%f))",
|
|
GeoId,
|
|
radiam * 2);
|
|
}
|
|
else {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Radius',%d,%f))",
|
|
GeoId,
|
|
radiam);
|
|
}
|
|
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
bool fixed = isPointOrSegmentFixed(Obj, GeoId);
|
|
if (fixed || constraintCreationMode == Reference) {
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
|
|
updateNeeded = true;// We do need to update the solver DoF after setting the
|
|
// constraint driving.
|
|
}
|
|
|
|
finishDatumConstraint(this, Obj, constraintCreationMode == Driving && !fixed);
|
|
|
|
// updateActive();
|
|
getSelection().clearSelection();
|
|
|
|
commitCommand();
|
|
|
|
if (updateNeeded) {
|
|
tryAutoRecomputeIfNotSolve(
|
|
Obj);// we have to update the solver after this aborted addition.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmdSketcherConstrainRadiam::updateAction(int mode)
|
|
{
|
|
switch (mode) {
|
|
case Reference:
|
|
if (getAction()) {
|
|
getAction()->setIcon(
|
|
Gui::BitmapFactory().iconFromTheme("Constraint_Radiam_Driven"));
|
|
}
|
|
break;
|
|
case Driving:
|
|
if (getAction()) {
|
|
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radiam"));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ======================================================================================
|
|
|
|
DEF_STD_CMD_ACLU(CmdSketcherCompConstrainRadDia)
|
|
|
|
CmdSketcherCompConstrainRadDia::CmdSketcherCompConstrainRadDia()
|
|
: Command("Sketcher_CompConstrainRadDia")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Constrain arc or circle");
|
|
sToolTipText = QT_TR_NOOP("Constrain an arc or a circle");
|
|
sWhatsThis = "Sketcher_CompConstrainRadDia";
|
|
sStatusTip = sToolTipText;
|
|
sAccel = "R";
|
|
eType = ForEdit;
|
|
}
|
|
|
|
void CmdSketcherCompConstrainRadDia::activated(int iMsg)
|
|
{
|
|
Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager();
|
|
if (iMsg == 0) {
|
|
rcCmdMgr.runCommandByName("Sketcher_ConstrainRadius");
|
|
}
|
|
else if (iMsg == 1) {
|
|
rcCmdMgr.runCommandByName("Sketcher_ConstrainDiameter");
|
|
}
|
|
else if (iMsg == 2) {
|
|
rcCmdMgr.runCommandByName("Sketcher_ConstrainRadiam");
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
|
|
// Save new choice as default
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
hGrp->SetInt("CurRadDiaCons", iMsg);
|
|
|
|
// Since the default icon is reset when enabling/disabling the command we have
|
|
// to explicitly set the icon of the used command.
|
|
Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
|
|
QList<QAction*> a = pcAction->actions();
|
|
|
|
assert(iMsg < a.size());
|
|
pcAction->setIcon(a[iMsg]->icon());
|
|
}
|
|
|
|
Gui::Action* CmdSketcherCompConstrainRadDia::createAction()
|
|
{
|
|
Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow());
|
|
pcAction->setDropDownMenu(true);
|
|
applyCommandData(this->className(), pcAction);
|
|
|
|
QAction* arc1 = pcAction->addAction(QString());
|
|
arc1->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radius"));
|
|
QAction* arc2 = pcAction->addAction(QString());
|
|
arc2->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Diameter"));
|
|
QAction* arc3 = pcAction->addAction(QString());
|
|
arc3->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radiam"));
|
|
|
|
_pcAction = pcAction;
|
|
languageChange();
|
|
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
int curRadDiaCons = hGrp->GetInt("CurRadDiaCons", 2);
|
|
|
|
switch (curRadDiaCons) {
|
|
case 0:
|
|
pcAction->setIcon(arc1->icon());
|
|
break;
|
|
case 1:
|
|
pcAction->setIcon(arc2->icon());
|
|
break;
|
|
default:
|
|
pcAction->setIcon(arc3->icon());
|
|
curRadDiaCons = 2;
|
|
}
|
|
pcAction->setProperty("defaultAction", QVariant(curRadDiaCons));
|
|
pcAction->setShortcut(QString::fromLatin1(getAccel()));
|
|
|
|
return pcAction;
|
|
}
|
|
|
|
void CmdSketcherCompConstrainRadDia::updateAction(int mode)
|
|
{
|
|
Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(getAction());
|
|
if (!pcAction) {
|
|
return;
|
|
}
|
|
|
|
QList<QAction*> a = pcAction->actions();
|
|
int index = pcAction->property("defaultAction").toInt();
|
|
switch (mode) {
|
|
case Reference:
|
|
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radius_Driven"));
|
|
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Diameter_Driven"));
|
|
a[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radiam_Driven"));
|
|
getAction()->setIcon(a[index]->icon());
|
|
break;
|
|
case Driving:
|
|
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radius"));
|
|
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Diameter"));
|
|
a[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radiam"));
|
|
getAction()->setIcon(a[index]->icon());
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CmdSketcherCompConstrainRadDia::languageChange()
|
|
{
|
|
Command::languageChange();
|
|
|
|
if (!_pcAction) {
|
|
return;
|
|
}
|
|
Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
|
|
QList<QAction*> a = pcAction->actions();
|
|
|
|
QAction* arc1 = a[0];
|
|
arc1->setText(QApplication::translate("CmdSketcherCompConstrainRadDia", "Constrain radius"));
|
|
arc1->setToolTip(QApplication::translate("Sketcher_ConstrainRadius",
|
|
"Fix the radius of a circle or an arc"));
|
|
arc1->setStatusTip(QApplication::translate("Sketcher_ConstrainRadius",
|
|
"Fix the radius of a circle or an arc"));
|
|
QAction* arc2 = a[1];
|
|
arc2->setText(QApplication::translate("CmdSketcherCompConstrainRadDia", "Constrain diameter"));
|
|
arc2->setToolTip(QApplication::translate("Sketcher_ConstrainDiameter",
|
|
"Fix the diameter of a circle or an arc"));
|
|
arc2->setStatusTip(QApplication::translate("Sketcher_ConstrainDiameter",
|
|
"Fix the diameter of a circle or an arc"));
|
|
QAction* arc3 = a[2];
|
|
arc3->setText(QApplication::translate("CmdSketcherCompConstrainRadDia",
|
|
"Constrain auto radius/diameter"));
|
|
arc3->setToolTip(QApplication::translate("Sketcher_ConstrainRadiam",
|
|
"Fix the radius/diameter of a circle or an arc"));
|
|
arc3->setStatusTip(QApplication::translate("Sketcher_ConstrainRadiam",
|
|
"Fix the radius/diameter of a circle or an arc"));
|
|
}
|
|
|
|
bool CmdSketcherCompConstrainRadDia::isActive()
|
|
{
|
|
return isCommandActive(getActiveGuiDocument());
|
|
}
|
|
|
|
// ======================================================================================
|
|
|
|
class CmdSketcherConstrainAngle: public CmdSketcherConstraint
|
|
{
|
|
public:
|
|
CmdSketcherConstrainAngle();
|
|
~CmdSketcherConstrainAngle() override
|
|
{}
|
|
void updateAction(int mode) override;
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainAngle";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
};
|
|
|
|
CmdSketcherConstrainAngle::CmdSketcherConstrainAngle()
|
|
: CmdSketcherConstraint("Sketcher_ConstrainAngle")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "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 = "K, 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}};
|
|
}
|
|
|
|
void CmdSketcherConstrainAngle::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
// TODO: comprehensive messages, like in CmdSketcherConstrainTangent
|
|
// get the selection
|
|
std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
|
|
|
|
// only one sketch with its subelements are allowed to be selected
|
|
if (selection.size() != 1
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
|
|
|
|
if (constraintMode) {
|
|
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(this));
|
|
getSelection().clearSelection();
|
|
}
|
|
else {
|
|
// TODO: Get the exact message from git history and put it here
|
|
Gui::TranslatedUserWarning(getActiveGuiDocument(),
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select the right things from the sketch."));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// get the needed lists and objects
|
|
const std::vector<std::string>& SubNames = selection[0].getSubNames();
|
|
auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
if (SubNames.empty() || SubNames.size() > 3) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr(
|
|
"Select one or two lines from the sketch. Or select two edges and a point."));
|
|
return;
|
|
}
|
|
|
|
int GeoId1, GeoId2 = GeoEnum::GeoUndef, GeoId3 = GeoEnum::GeoUndef;
|
|
Sketcher::PointPos PosId1, PosId2 = Sketcher::PointPos::none, PosId3 = Sketcher::PointPos::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)) {
|
|
|
|
if (isBsplinePole(Obj, GeoId1) || isBsplinePole(Obj, GeoId2)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge that is not a B-spline weight."));
|
|
return;
|
|
}
|
|
|
|
double ActAngle = 0.0;
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add angle constraint"));
|
|
|
|
// add missing point-on-object constraints
|
|
if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
|
|
const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
|
|
if (!(geom1 && isBSplineCurve(*geom1))) {
|
|
Gui::cmdAppObjectArgs(
|
|
selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId3,
|
|
static_cast<int>(PosId3),
|
|
GeoId1);
|
|
}
|
|
}
|
|
if (!IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)) {
|
|
const Part::Geometry *geom2 = Obj->getGeometry(GeoId2);
|
|
if (!(geom2 && isBSplineCurve(*geom2))) {
|
|
Gui::cmdAppObjectArgs(
|
|
selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId3,
|
|
static_cast<int>(PosId3),
|
|
GeoId2);
|
|
}
|
|
}
|
|
if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
|
|
// FIXME: it's a good idea to add a check if the sketch is solved
|
|
const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
|
|
if (!(geom1 && isBSplineCurve(*geom1))) {
|
|
Gui::cmdAppObjectArgs(
|
|
selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId3,
|
|
static_cast<int>(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::cmdAppObjectArgs(
|
|
selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('AngleViaPoint',%d,%d,%d,%d,%f))",
|
|
GeoId1,
|
|
GeoId2,
|
|
GeoId3,
|
|
static_cast<int>(PosId3),
|
|
ActAngle);
|
|
|
|
removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3);
|
|
|
|
if (bothexternal
|
|
|| constraintCreationMode
|
|
== Reference) {// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
ConStr.size() - 1,
|
|
"False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
else if (SubNames.size() < 3) {
|
|
|
|
if (isVertex(GeoId1, PosId1) && isEdge(GeoId2, PosId2)) {
|
|
std::swap(GeoId1, GeoId2);
|
|
std::swap(PosId1, PosId2);
|
|
}
|
|
|
|
if (isBsplinePole(Obj, GeoId1)
|
|
|| (GeoId2 != GeoEnum::GeoUndef && isBsplinePole(Obj, GeoId2))) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge that is not a B-spline weight."));
|
|
return;
|
|
}
|
|
|
|
if (isEdge(GeoId2, PosId2)) {// line to line angle
|
|
makeAngleBetweenTwoLines(Obj, this, GeoId1, GeoId2);
|
|
return;
|
|
}
|
|
else if (isEdge(GeoId1, PosId1)) {// line angle or arc angle
|
|
if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Cannot add an angle constraint on an axis!"));
|
|
return;
|
|
}
|
|
|
|
const Part::Geometry* geom = Obj->getGeometry(GeoId1);
|
|
|
|
if (isLineSegment(*geom)) {
|
|
auto lineSeg = static_cast<const Part::GeomLineSegment*>(geom);
|
|
Base::Vector3d dir = lineSeg->getEndPoint() - lineSeg->getStartPoint();
|
|
double ActAngle = atan2(dir.y, dir.x);
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add angle constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Angle',%d,%f))",
|
|
GeoId1,
|
|
ActAngle);
|
|
|
|
if (GeoId1 <= Sketcher::GeoEnum::RefExt || constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
ConStr.size() - 1,
|
|
"False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
else if (isArcOfCircle(*geom)) {
|
|
auto arc = static_cast<const Part::GeomArcOfCircle*>(geom);
|
|
double angle = arc->getAngle(/*EmulateCCWXY=*/true);
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add angle constraint"));
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Angle',%d,%f))",
|
|
GeoId1,
|
|
angle);
|
|
|
|
if (GeoId1 <= Sketcher::GeoEnum::RefExt || constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"setDriving(%d,%s)",
|
|
ConStr.size() - 1,
|
|
"False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
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<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
SketcherGui::ViewProviderSketch* sketchgui =
|
|
static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
|
|
Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
|
|
|
|
int GeoId1 = GeoEnum::GeoUndef, GeoId2 = GeoEnum::GeoUndef, GeoId3 = GeoEnum::GeoUndef;
|
|
Sketcher::PointPos PosId1 = Sketcher::PointPos::none, PosId2 = Sketcher::PointPos::none,
|
|
PosId3 = Sketcher::PointPos::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;
|
|
|
|
makeAngleBetweenTwoLines(Obj, this, GeoId1, GeoId2);
|
|
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)) {
|
|
|
|
if (isBsplinePole(Obj, GeoId1) || isBsplinePole(Obj, GeoId2)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge that is not a B-spline weight."));
|
|
return;
|
|
}
|
|
|
|
double ActAngle = 0.0;
|
|
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add angle constraint"));
|
|
|
|
// add missing point-on-object constraints
|
|
if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
|
|
const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
|
|
if (!(geom1 && isBSplineCurve(*geom1))) {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId3,
|
|
static_cast<int>(PosId3),
|
|
GeoId1);
|
|
}
|
|
}
|
|
if (!IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)) {
|
|
const Part::Geometry *geom2 = Obj->getGeometry(GeoId2);
|
|
if (!(geom2 && isBSplineCurve(*geom2))) {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId3,
|
|
static_cast<int>(PosId3),
|
|
GeoId2);
|
|
}
|
|
}
|
|
if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
|
|
// FIXME: it's a good idea to add a check if the sketch is solved
|
|
const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
|
|
if (!(geom1 && isBSplineCurve(*geom1))) {
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId3,
|
|
static_cast<int>(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::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('AngleViaPoint',%d,%d,%d,%d,%f))",
|
|
GeoId1,
|
|
GeoId2,
|
|
GeoId3,
|
|
static_cast<int>(PosId3),
|
|
ActAngle);
|
|
|
|
removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3);
|
|
|
|
if (bothexternal || constraintCreationMode == Reference) {
|
|
// it is a constraint on a external line, make it non-driving
|
|
const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
|
|
finishDatumConstraint(this, Obj, false);
|
|
}
|
|
else {
|
|
finishDatumConstraint(this, Obj, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
void CmdSketcherConstrainAngle::updateAction(int mode)
|
|
{
|
|
switch (mode) {
|
|
case Reference:
|
|
if (getAction()) {
|
|
getAction()->setIcon(
|
|
Gui::BitmapFactory().iconFromTheme("Constraint_InternalAngle_Driven"));
|
|
}
|
|
break;
|
|
case Driving:
|
|
if (getAction()) {
|
|
getAction()->setIcon(
|
|
Gui::BitmapFactory().iconFromTheme("Constraint_InternalAngle"));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ======================================================================================
|
|
|
|
class CmdSketcherConstrainEqual: public CmdSketcherConstraint
|
|
{
|
|
public:
|
|
CmdSketcherConstrainEqual();
|
|
~CmdSketcherConstrainEqual() override
|
|
{}
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainEqual";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
};
|
|
|
|
CmdSketcherConstrainEqual::CmdSketcherConstrainEqual()
|
|
: CmdSketcherConstraint("Sketcher_ConstrainEqual")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "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
|
|
}
|
|
|
|
void CmdSketcherConstrainEqual::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
// get the selection
|
|
std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
|
|
|
|
// only one sketch with its subelements are allowed to be selected
|
|
if (selection.size() != 1
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
|
|
|
|
if (constraintMode) {
|
|
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(this));
|
|
getSelection().clearSelection();
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(),
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select two edges from the sketch."));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// get the needed lists and objects
|
|
const std::vector<std::string>& SubNames = selection[0].getSubNames();
|
|
auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
// go through the selected subelements
|
|
|
|
if (SubNames.size() < 2) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select at least two lines from the sketch."));
|
|
return;
|
|
}
|
|
|
|
std::vector<int> ids;
|
|
bool lineSel = false, arcSel = false, circSel = false, ellipsSel = false, arcEllipsSel = false,
|
|
hasAlreadyExternal = false;
|
|
bool hyperbSel = false, parabSel = false, weightSel = false;
|
|
|
|
for (auto& subname : SubNames) {
|
|
|
|
int GeoId;
|
|
Sketcher::PointPos PosId;
|
|
getIdsFromName(subname, Obj, GeoId, PosId);
|
|
|
|
if (!isEdge(GeoId, PosId)) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select two or more compatible edges."));
|
|
return;
|
|
}
|
|
else if (GeoId == Sketcher::GeoEnum::HAxis || GeoId == Sketcher::GeoEnum::VAxis) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Sketch axes cannot be used in equality constraints."));
|
|
return;
|
|
}
|
|
else if (isPointOrSegmentFixed(Obj, GeoId)) {
|
|
|
|
if (hasAlreadyExternal) {
|
|
showNoConstraintBetweenFixedGeometry(Obj);
|
|
return;
|
|
}
|
|
else {
|
|
hasAlreadyExternal = true;
|
|
}
|
|
}
|
|
|
|
const Part::Geometry* geo = Obj->getGeometry(GeoId);
|
|
|
|
if (isBSplineCurve(*geo)) {
|
|
// unsupported as they are generally hereogeneus shapes
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Equality for B-spline edge currently unsupported."));
|
|
return;
|
|
}
|
|
|
|
if (isLineSegment(*geo)) {
|
|
lineSel = true;
|
|
}
|
|
else if (isArcOfCircle(*geo)) {
|
|
arcSel = true;
|
|
}
|
|
else if (isCircle(*geo)) {
|
|
if (isBsplinePole(geo)) {
|
|
weightSel = true;
|
|
}
|
|
else {
|
|
circSel = true;
|
|
}
|
|
}
|
|
else if (isEllipse(*geo)) {
|
|
ellipsSel = true;
|
|
}
|
|
else if (isArcOfEllipse(*geo)) {
|
|
arcEllipsSel = true;
|
|
}
|
|
else if (isArcOfHyperbola(*geo)) {
|
|
hyperbSel = true;
|
|
}
|
|
else if (isArcOfParabola(*geo)) {
|
|
parabSel = true;
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select two or more edges of similar type."));
|
|
return;
|
|
}
|
|
|
|
ids.push_back(GeoId);
|
|
}
|
|
|
|
// Check for heterogeneous groups in selection
|
|
if ((lineSel
|
|
&& ((arcSel || circSel) || (ellipsSel || arcEllipsSel) || hyperbSel || parabSel || weightSel))
|
|
|| ((arcSel || circSel) && ((ellipsSel || arcEllipsSel) || hyperbSel || parabSel || weightSel))
|
|
|| ((ellipsSel || arcEllipsSel) && (hyperbSel || parabSel || weightSel))
|
|
|| (hyperbSel && (parabSel || weightSel)) || (parabSel && weightSel)) {
|
|
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select two or more edges of similar type."));
|
|
return;
|
|
}
|
|
|
|
// undo command open
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add equality constraint"));
|
|
for (int i = 0; i < int(ids.size() - 1); i++) {
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Equal',%d,%d))",
|
|
ids[i],
|
|
ids[i + 1]);
|
|
}
|
|
// finish the transaction and update
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
|
|
// clear the selection (convenience)
|
|
getSelection().clearSelection();
|
|
}
|
|
|
|
void CmdSketcherConstrainEqual::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
SketcherGui::ViewProviderSketch* sketchgui =
|
|
static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
|
|
Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
|
|
|
|
int GeoId1 = GeoEnum::GeoUndef, GeoId2 = GeoEnum::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 already has a Block constraint
|
|
if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
|
|
showNoConstraintBetweenFixedGeometry(Obj);
|
|
return;
|
|
}
|
|
|
|
const Part::Geometry* geo1 = Obj->getGeometry(GeoId1);
|
|
const Part::Geometry* geo2 = Obj->getGeometry(GeoId2);
|
|
|
|
if ((isLineSegment(*geo1) && ! isLineSegment(*geo2))
|
|
|| (isArcOfHyperbola(*geo1) && ! isArcOfHyperbola(*geo2))
|
|
|| (isArcOfParabola(*geo1) && ! isArcOfParabola(*geo2))
|
|
|| (isBsplinePole(geo1) && !isBsplinePole(geo2))
|
|
|| ((isCircle(*geo1) || isArcOfCircle(*geo1)) && !(isCircle(*geo2) || isArcOfCircle(*geo2)))
|
|
|| ((isEllipse(*geo1) || isArcOfEllipse(*geo1)) && !(isEllipse(*geo2) || isArcOfEllipse(*geo2)))) {
|
|
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select two or more edges of similar type."));
|
|
return;
|
|
}
|
|
|
|
// undo command open
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add equality constraint"));
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Equal',%d,%d))",
|
|
GeoId1,
|
|
GeoId2);
|
|
// finish the transaction and update
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
|
|
return;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ======================================================================================
|
|
|
|
class CmdSketcherConstrainSymmetric: public CmdSketcherConstraint
|
|
{
|
|
public:
|
|
CmdSketcherConstrainSymmetric();
|
|
~CmdSketcherConstrainSymmetric() override
|
|
{}
|
|
const char* className() const override
|
|
{
|
|
return "CmdSketcherConstrainSymmetric";
|
|
}
|
|
|
|
protected:
|
|
void activated(int iMsg) override;
|
|
void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
|
|
};
|
|
|
|
CmdSketcherConstrainSymmetric::CmdSketcherConstrainSymmetric()
|
|
: CmdSketcherConstraint("Sketcher_ConstrainSymmetric")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Constrain symmetric");
|
|
sToolTipText = QT_TR_NOOP("Create a symmetry constraint "
|
|
"between two points\n"
|
|
"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, SelEdge},
|
|
{SelRoot, SelVertex, SelEdge},
|
|
{SelVertex, SelVertexOrRoot, SelExternalEdge},
|
|
{SelRoot, SelVertex, SelExternalEdge},
|
|
{SelVertex, SelVertex, SelEdgeOrAxis},
|
|
{SelVertex, SelVertexOrRoot, SelVertex},
|
|
{SelVertex, SelVertex, SelVertexOrRoot},
|
|
{SelVertexOrRoot, SelVertex, SelVertex}};
|
|
}
|
|
|
|
void CmdSketcherConstrainSymmetric::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
// get the selection
|
|
std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
|
|
|
|
// only one sketch with its subelements are allowed to be selected
|
|
if (selection.size() != 1
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher");
|
|
bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
|
|
|
|
if (constraintMode) {
|
|
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerGenConstraint(this));
|
|
getSelection().clearSelection();
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(
|
|
getActiveGuiDocument()->getDocument(),
|
|
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<std::string>& SubNames = selection[0].getSubNames();
|
|
auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
if (SubNames.size() != 3 && SubNames.size() != 2) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
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(Obj);
|
|
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 (isLineSegment(*geom)) {
|
|
if (GeoId1 == GeoId2) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Cannot add a symmetry constraint "
|
|
"between a line and its end points."));
|
|
return;
|
|
}
|
|
|
|
// undo command open
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add symmetric constraint"));
|
|
Gui::cmdAppObjectArgs(
|
|
selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d,%d))",
|
|
GeoId1,
|
|
static_cast<int>(Sketcher::PointPos::start),
|
|
GeoId1,
|
|
static_cast<int>(Sketcher::PointPos::end),
|
|
GeoId2,
|
|
static_cast<int>(PosId2));
|
|
|
|
// finish the transaction and update
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
|
|
// clear the selection (convenience)
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
}
|
|
|
|
Gui::TranslatedUserWarning(Obj,
|
|
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(Obj);
|
|
return;
|
|
}
|
|
|
|
if (isVertex(GeoId1, PosId1) && isVertex(GeoId2, PosId2)) {
|
|
if (isEdge(GeoId3, PosId3)) {
|
|
const Part::Geometry* geom = Obj->getGeometry(GeoId3);
|
|
|
|
if (isLineSegment(*geom)) {
|
|
if (GeoId1 == GeoId2 && GeoId2 == GeoId3) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Cannot add a symmetry constraint "
|
|
"between a line and its end points!"));
|
|
return;
|
|
}
|
|
|
|
// undo command open
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add symmetric constraint"));
|
|
Gui::cmdAppObjectArgs(
|
|
selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2),
|
|
GeoId3);
|
|
|
|
// finish the transaction and update
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
|
|
// clear the selection (convenience)
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
}
|
|
else if (isVertex(GeoId3, PosId3)) {
|
|
// undo command open
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add symmetric constraint"));
|
|
Gui::cmdAppObjectArgs(
|
|
selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d,%d))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2),
|
|
GeoId3,
|
|
static_cast<int>(PosId3));
|
|
|
|
// finish the transaction and update
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
|
|
// clear the selection (convenience)
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
}
|
|
|
|
Gui::TranslatedUserWarning(Obj,
|
|
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<SelIdPair>& selSeq, int seqIndex)
|
|
{
|
|
SketcherGui::ViewProviderSketch* sketchgui =
|
|
static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
|
|
Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
|
|
|
|
int GeoId1 = GeoEnum::GeoUndef, GeoId2 = GeoEnum::GeoUndef, GeoId3 = GeoEnum::GeoUndef;
|
|
Sketcher::PointPos PosId1 = Sketcher::PointPos::none, PosId2 = Sketcher::PointPos::none,
|
|
PosId3 = Sketcher::PointPos::none;
|
|
|
|
switch (seqIndex) {
|
|
case 0:// {SelEdge, SelVertexOrRoot}
|
|
case 1:// {SelExternalEdge, SelVertex}
|
|
{
|
|
GeoId1 = GeoId2 = selSeq.at(0).GeoId;
|
|
GeoId3 = selSeq.at(1).GeoId;
|
|
PosId1 = Sketcher::PointPos::start;
|
|
PosId2 = Sketcher::PointPos::end;
|
|
PosId3 = selSeq.at(1).PosId;
|
|
|
|
if (GeoId1 == GeoId3) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
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(Obj);
|
|
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}
|
|
case 7: // {SelVertex, SelVertexOrRoot,SelEdge}
|
|
case 8: // {SelRoot, SelVertex, SelEdge}
|
|
case 9: // {SelVertex, SelVertexOrRoot, SelExternalEdge}
|
|
case 10:// {SelRoot, SelVertex, SelExternalEdge}
|
|
case 11:// {SelVertex, SelVertex, SelEdgeOrAxis}
|
|
{
|
|
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 (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(Obj);
|
|
return;
|
|
}
|
|
|
|
const Part::Geometry* geom = Obj->getGeometry(GeoId3);
|
|
|
|
if (isLineSegment(*geom)) {
|
|
if (GeoId1 == GeoId2 && GeoId2 == GeoId3) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Cannot add a symmetry constraint "
|
|
"between a line and its end points."));
|
|
return;
|
|
}
|
|
|
|
// undo command open
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add symmetric constraint"));
|
|
Gui::cmdAppObjectArgs(
|
|
Obj,
|
|
"addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2),
|
|
GeoId3);
|
|
|
|
// finish the transaction and update
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
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 12:// {SelVertex, SelVertexOrRoot, SelVertex}
|
|
case 13:// {SelVertex, SelVertex, SelVertexOrRoot}
|
|
case 14:// {SelVertexOrRoot, SelVertex, SelVertex}
|
|
{
|
|
GeoId1 = selSeq.at(0).GeoId;
|
|
GeoId2 = selSeq.at(1).GeoId;
|
|
GeoId3 = selSeq.at(2).GeoId;
|
|
PosId1 = selSeq.at(0).PosId;
|
|
PosId2 = selSeq.at(1).PosId;
|
|
PosId3 = selSeq.at(2).PosId;
|
|
|
|
if (areAllPointsOrSegmentsFixed(Obj, GeoId1, GeoId2, GeoId3)) {
|
|
showNoConstraintBetweenFixedGeometry(Obj);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// undo command open
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Add symmetric constraint"));
|
|
Gui::cmdAppObjectArgs(Obj,
|
|
"addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d,%d))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2),
|
|
GeoId3,
|
|
static_cast<int>(PosId3));
|
|
|
|
// finish the transaction and update
|
|
commitCommand();
|
|
|
|
tryAutoRecompute(Obj);
|
|
|
|
// clear the selection (convenience)
|
|
getSelection().clearSelection();
|
|
return;
|
|
}
|
|
|
|
// ======================================================================================
|
|
|
|
DEF_STD_CMD_A(CmdSketcherConstrainSnellsLaw)
|
|
|
|
CmdSketcherConstrainSnellsLaw::CmdSketcherConstrainSnellsLaw()
|
|
: Command("Sketcher_ConstrainSnellsLaw")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "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\n"
|
|
"and an edge as an interface.");
|
|
sWhatsThis = "Sketcher_ConstrainSnellsLaw";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "Constraint_SnellsLaw";
|
|
sAccel = "K, W";
|
|
eType = ForEdit;
|
|
}
|
|
|
|
void CmdSketcherConstrainSnellsLaw::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
|
|
// get the selection
|
|
std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
|
|
|
|
// only one sketch with its subelements are allowed to be selected
|
|
if (selection.size() != 1
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
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");
|
|
|
|
const char dmbg[] = "Constraint_SnellsLaw";
|
|
|
|
QString strError = QObject::tr("Selected objects are not just geometry "
|
|
"from one sketch.",
|
|
dmbg);
|
|
|
|
strError.append(strHelp);
|
|
Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(),
|
|
QObject::tr("Wrong selection"),
|
|
std::move(strError));
|
|
}
|
|
|
|
// get the needed lists and objects
|
|
auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
const std::vector<std::string>& SubNames = selection[0].getSubNames();
|
|
|
|
if (SubNames.size() != 3) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Number of selected objects is not 3"));
|
|
return;
|
|
}
|
|
|
|
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)) {
|
|
Gui::TranslatedUserWarning(
|
|
Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Cannot create constraint with external geometry only."));
|
|
return;
|
|
}
|
|
|
|
if (!(isVertex(GeoId1, PosId1) && !isSimpleVertex(Obj, GeoId1, PosId1)
|
|
&& isVertex(GeoId2, PosId2) && !isSimpleVertex(Obj, GeoId2, PosId2)
|
|
&& isEdge(GeoId3, PosId3))) {
|
|
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Incompatible geometry is selected."));
|
|
return;
|
|
}
|
|
|
|
const Part::Geometry* geo = Obj->getGeometry(GeoId3);
|
|
|
|
// if (geo && isBSplineCurve(*geo)) {
|
|
// // unsupported until normal to B-spline at any point implemented.
|
|
// Gui::TranslatedUserWarning(
|
|
// Obj,
|
|
// QObject::tr("Wrong selection"),
|
|
// QObject::tr("SnellsLaw on B-spline edge is currently unsupported."));
|
|
// return;
|
|
// }
|
|
|
|
if (isBsplinePole(geo)) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge that is not a B-spline weight."));
|
|
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"));
|
|
ui_Datum.label->setText(EditDatumDialog::tr("Ratio n2/n1:"));
|
|
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->setEntryName(QByteArray("DatumValue"));
|
|
ui_Datum.labelEdit->setToLastUsedValue();
|
|
ui_Datum.labelEdit->selectNumber();
|
|
ui_Datum.labelEdit->setSingleStep(0.05);
|
|
// 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(QT_TRANSLATE_NOOP("Command", "Add Snell's law constraint"));
|
|
|
|
bool safe = addConstraintSafely(Obj, [&]() {
|
|
if (!IsPointAlreadyOnCurve(GeoId2, GeoId1, PosId1, Obj)) {
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('Coincident',%d,%d,%d,%d))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2));
|
|
}
|
|
|
|
if (!IsPointAlreadyOnCurve(GeoId3, GeoId1, PosId1, Obj)) {
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId3);
|
|
}
|
|
|
|
Gui::cmdAppObjectArgs(
|
|
selection[0].getObject(),
|
|
"addConstraint(Sketcher.Constraint('SnellsLaw',%d,%d,%d,%d,%d,%.12f))",
|
|
GeoId1,
|
|
static_cast<int>(PosId1),
|
|
GeoId2,
|
|
static_cast<int>(PosId2),
|
|
GeoId3,
|
|
n2divn1);
|
|
|
|
/*if (allexternal || constraintCreationMode==Reference) { // it is a constraint on a
|
|
external line, make it non-driving const std::vector<Sketcher::Constraint *> &ConStr =
|
|
Obj->Constraints.getValues();
|
|
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(),"setDriving(%i,%s)",
|
|
ConStr.size()-1,"False");
|
|
}*/
|
|
});
|
|
|
|
if (!safe) {
|
|
return;
|
|
}
|
|
else {
|
|
commitCommand();
|
|
tryAutoRecompute(Obj);
|
|
}
|
|
|
|
// clear the selection (convenience)
|
|
getSelection().clearSelection();
|
|
}
|
|
|
|
bool CmdSketcherConstrainSnellsLaw::isActive()
|
|
{
|
|
return isCreateConstraintActive(getActiveGuiDocument());
|
|
}
|
|
|
|
// ======================================================================================
|
|
DEF_STD_CMD_A(CmdSketcherChangeDimensionConstraint)
|
|
|
|
CmdSketcherChangeDimensionConstraint::CmdSketcherChangeDimensionConstraint()
|
|
: Command("Sketcher_ChangeDimensionConstraint")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Change value");
|
|
sToolTipText = QT_TR_NOOP("Change the value of a dimensional constraint");
|
|
sWhatsThis = "Sketcher_ChangeDimensionConstraint";
|
|
sStatusTip = sToolTipText;
|
|
eType = ForEdit;
|
|
}
|
|
|
|
void CmdSketcherChangeDimensionConstraint::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
|
|
auto getDimConstraint = []() {
|
|
std::vector<Gui::SelectionObject> selection{getSelection().getSelectionEx()};
|
|
if (selection.size() != 1 || selection[0].getSubNames().size() != 1) {
|
|
throw Base::RuntimeError();
|
|
}
|
|
|
|
if (auto sketch = dynamic_cast<Sketcher::SketchObject*>(selection[0].getObject())) {
|
|
std::string subName = selection[0].getSubNames().at(0);
|
|
if (subName.size() > 10 && subName.substr(0, 10) == "Constraint") {
|
|
int ConstrId = Sketcher::PropertyConstraintList::getIndexFromConstraintName(subName);
|
|
return std::make_tuple(sketch, ConstrId);
|
|
}
|
|
}
|
|
|
|
throw Base::RuntimeError();
|
|
};
|
|
|
|
try {
|
|
auto value = getDimConstraint();
|
|
EditDatumDialog editDatumDialog(std::get<0>(value), std::get<1>(value));
|
|
editDatumDialog.exec(false);
|
|
}
|
|
catch (const Base::RuntimeError&) {
|
|
Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(),
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select one dimensional constraint from the sketch."));
|
|
}
|
|
}
|
|
|
|
bool CmdSketcherChangeDimensionConstraint::isActive()
|
|
{
|
|
return isCommandActive(getActiveGuiDocument());
|
|
}
|
|
|
|
// ======================================================================================
|
|
/*** Creation Mode / Toggle to or from Reference ***/
|
|
DEF_STD_CMD_AU(CmdSketcherToggleDrivingConstraint)
|
|
|
|
CmdSketcherToggleDrivingConstraint::CmdSketcherToggleDrivingConstraint()
|
|
: Command("Sketcher_ToggleDrivingConstraint")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Toggle driving/reference constraint");
|
|
sToolTipText = QT_TR_NOOP("Set the toolbar, "
|
|
"or the selected constraints,\n"
|
|
"into driving or reference mode");
|
|
sWhatsThis = "Sketcher_ToggleDrivingConstraint";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "Sketcher_ToggleConstraint";
|
|
sAccel = "K, X";
|
|
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_ConstrainDiameter");
|
|
rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainRadiam");
|
|
rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainAngle");
|
|
rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_CompConstrainRadDia");
|
|
rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_Dimension");
|
|
rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_CompDimensionTools");
|
|
rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ToggleDrivingConstraint");
|
|
// rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainSnellsLaw");
|
|
}
|
|
|
|
void CmdSketcherToggleDrivingConstraint::updateAction(int mode)
|
|
{
|
|
auto act = getAction();
|
|
if (act) {
|
|
switch (static_cast<ConstraintCreationMode>(mode)) {
|
|
case ConstraintCreationMode::Driving:
|
|
act->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_ToggleConstraint"));
|
|
break;
|
|
case ConstraintCreationMode::Reference:
|
|
act->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_ToggleConstraint_Driven"));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmdSketcherToggleDrivingConstraint::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
bool modeChange = true;
|
|
|
|
std::vector<Gui::SelectionObject> 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
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(),
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select constraints from the sketch."));
|
|
return;
|
|
}
|
|
|
|
Sketcher::SketchObject* Obj =
|
|
static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
// get the needed lists and objects
|
|
const std::vector<std::string>& SubNames = selection[0].getSubNames();
|
|
if (SubNames.empty()) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select constraints from the sketch."));
|
|
return;
|
|
}
|
|
|
|
for (auto& subname : SubNames) {
|
|
// see if we have constraints, if we do it is not a mode change, but a toggle.
|
|
if (subname.size() > 10 && subname.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<int>(constraintCreationMode));
|
|
}
|
|
else// toggle the selected constraint(s)
|
|
{
|
|
Sketcher::SketchObject* Obj =
|
|
static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
// get the needed lists and objects
|
|
const std::vector<std::string>& SubNames = selection[0].getSubNames();
|
|
if (SubNames.empty()) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select constraints from the sketch."));
|
|
return;
|
|
}
|
|
|
|
// undo command open
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Toggle constraint to driving/reference"));
|
|
|
|
int successful = SubNames.size();
|
|
// go through the selected subelements
|
|
for (auto& subname : SubNames) {
|
|
// only handle constraints
|
|
if (subname.size() > 10 && subname.substr(0, 10) == "Constraint") {
|
|
int ConstrId = Sketcher::PropertyConstraintList::getIndexFromConstraintName(subname);
|
|
try {
|
|
// issue the actual commands to toggle
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(), "toggleDriving(%d)", ConstrId);
|
|
}
|
|
catch (const Base::Exception&) {
|
|
successful--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (successful > 0) {
|
|
commitCommand();
|
|
}
|
|
else {
|
|
abortCommand();
|
|
}
|
|
|
|
tryAutoRecompute(Obj);
|
|
|
|
// clear the selection (convenience)
|
|
getSelection().clearSelection();
|
|
}
|
|
}
|
|
|
|
bool CmdSketcherToggleDrivingConstraint::isActive()
|
|
{
|
|
return isCommandActive(getActiveGuiDocument());
|
|
}
|
|
|
|
DEF_STD_CMD_A(CmdSketcherToggleActiveConstraint)
|
|
|
|
CmdSketcherToggleActiveConstraint::CmdSketcherToggleActiveConstraint()
|
|
: Command("Sketcher_ToggleActiveConstraint")
|
|
{
|
|
sAppModule = "Sketcher";
|
|
sGroup = "Sketcher";
|
|
sMenuText = QT_TR_NOOP("Activate/deactivate constraint");
|
|
sToolTipText = QT_TR_NOOP("Activates or deactivates "
|
|
"the selected constraints");
|
|
sWhatsThis = "Sketcher_ToggleActiveConstraint";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "Sketcher_ToggleActiveConstraint";
|
|
sAccel = "K, Z";
|
|
eType = ForEdit;
|
|
}
|
|
|
|
void CmdSketcherToggleActiveConstraint::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
|
|
std::vector<Gui::SelectionObject> 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
|
|
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
|
|
Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(),
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select constraints from the sketch."));
|
|
return;
|
|
}
|
|
|
|
Sketcher::SketchObject* Obj =
|
|
static_cast<Sketcher::SketchObject*>(selection[0].getObject());
|
|
|
|
// get the needed lists and objects
|
|
const std::vector<std::string>& SubNames = selection[0].getSubNames();
|
|
if (SubNames.empty()) {
|
|
Gui::TranslatedUserWarning(Obj,
|
|
QObject::tr("Wrong selection"),
|
|
QObject::tr("Select constraints from the sketch."));
|
|
return;
|
|
}
|
|
|
|
// undo command open
|
|
openCommand(QT_TRANSLATE_NOOP("Command", "Activate/Deactivate constraint"));
|
|
|
|
int successful = SubNames.size();
|
|
|
|
for (auto& subname : SubNames) {
|
|
|
|
if (subname.size() > 10 && subname.substr(0, 10) == "Constraint") {
|
|
int ConstrId = Sketcher::PropertyConstraintList::getIndexFromConstraintName(subname);
|
|
try {
|
|
// issue the actual commands to toggle
|
|
Gui::cmdAppObjectArgs(selection[0].getObject(), "toggleActive(%d)", ConstrId);
|
|
}
|
|
catch (const Base::Exception&) {
|
|
successful--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (successful > 0) {
|
|
commitCommand();
|
|
}
|
|
else {
|
|
abortCommand();
|
|
}
|
|
|
|
tryAutoRecompute(Obj);
|
|
|
|
// clear the selection (convenience)
|
|
getSelection().clearSelection();
|
|
}
|
|
}
|
|
|
|
bool CmdSketcherToggleActiveConstraint::isActive()
|
|
{
|
|
return isCreateConstraintActive(getActiveGuiDocument());
|
|
}
|
|
|
|
void CreateSketcherCommandsConstraints()
|
|
{
|
|
Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager();
|
|
|
|
rcCmdMgr.addCommand(new CmdSketcherConstrainHorizontal());
|
|
rcCmdMgr.addCommand(new CmdSketcherConstrainVertical());
|
|
rcCmdMgr.addCommand(new CmdSketcherConstrainHorVer());
|
|
rcCmdMgr.addCommand(new CmdSketcherCompHorizontalVertical());
|
|
rcCmdMgr.addCommand(new CmdSketcherConstrainLock());
|
|
rcCmdMgr.addCommand(new CmdSketcherConstrainBlock());
|
|
rcCmdMgr.addCommand(new CmdSketcherConstrainCoincidentUnified());
|
|
rcCmdMgr.addCommand(new CmdSketcherConstrainCoincident());
|
|
rcCmdMgr.addCommand(new CmdSketcherDimension());
|
|
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 CmdSketcherConstrainDiameter());
|
|
rcCmdMgr.addCommand(new CmdSketcherConstrainRadiam());
|
|
rcCmdMgr.addCommand(new CmdSketcherCompConstrainRadDia());
|
|
rcCmdMgr.addCommand(new CmdSketcherConstrainAngle());
|
|
rcCmdMgr.addCommand(new CmdSketcherConstrainEqual());
|
|
rcCmdMgr.addCommand(new CmdSketcherConstrainPointOnObject());
|
|
rcCmdMgr.addCommand(new CmdSketcherConstrainSymmetric());
|
|
rcCmdMgr.addCommand(new CmdSketcherConstrainSnellsLaw());
|
|
rcCmdMgr.addCommand(new CmdSketcherChangeDimensionConstraint());
|
|
rcCmdMgr.addCommand(new CmdSketcherToggleDrivingConstraint());
|
|
rcCmdMgr.addCommand(new CmdSketcherToggleActiveConstraint());
|
|
rcCmdMgr.addCommand(new CmdSketcherCompDimensionTools());
|
|
rcCmdMgr.addCommand(new CmdSketcherCompConstrainTools());
|
|
}
|
|
// clang-format on
|