1100 lines
39 KiB
C++
1100 lines
39 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 <cmath>
|
|
|
|
#include <QGuiApplication>
|
|
#include <QPainter>
|
|
|
|
#include <Inventor/events/SoKeyboardEvent.h>
|
|
#endif // #ifndef _PreComp_
|
|
|
|
#include <Base/Console.h>
|
|
#include <Base/Exception.h>
|
|
#include <Gui/Application.h>
|
|
#include <Gui/BitmapFactory.h>
|
|
#include <Gui/CommandT.h>
|
|
#include <Gui/MainWindow.h>
|
|
#include <Gui/View3DInventor.h>
|
|
#include <Gui/View3DInventorViewer.h>
|
|
#include <Mod/Sketcher/App/SketchObject.h>
|
|
|
|
#include "CommandConstraints.h"
|
|
#include "DrawSketchHandler.h"
|
|
#include "Utils.h"
|
|
#include "ViewProviderSketch.h"
|
|
|
|
|
|
using namespace SketcherGui;
|
|
using namespace Sketcher;
|
|
|
|
/************************************ Attorney *******************************************/
|
|
|
|
inline void
|
|
ViewProviderSketchDrawSketchHandlerAttorney::setConstraintSelectability(ViewProviderSketch& vp,
|
|
bool enabled /*= true*/)
|
|
{
|
|
vp.setConstraintSelectability(enabled);
|
|
}
|
|
|
|
inline void ViewProviderSketchDrawSketchHandlerAttorney::setPositionText(ViewProviderSketch& vp,
|
|
const Base::Vector2d& Pos,
|
|
const SbString& txt)
|
|
{
|
|
vp.setPositionText(Pos, txt);
|
|
}
|
|
|
|
inline void ViewProviderSketchDrawSketchHandlerAttorney::setPositionText(ViewProviderSketch& vp,
|
|
const Base::Vector2d& Pos)
|
|
{
|
|
vp.setPositionText(Pos);
|
|
}
|
|
|
|
inline void ViewProviderSketchDrawSketchHandlerAttorney::resetPositionText(ViewProviderSketch& vp)
|
|
{
|
|
vp.resetPositionText();
|
|
}
|
|
|
|
inline void
|
|
ViewProviderSketchDrawSketchHandlerAttorney::drawEdit(ViewProviderSketch& vp,
|
|
const std::vector<Base::Vector2d>& EditCurve)
|
|
{
|
|
vp.drawEdit(EditCurve);
|
|
}
|
|
|
|
inline void ViewProviderSketchDrawSketchHandlerAttorney::drawEdit(
|
|
ViewProviderSketch& vp,
|
|
const std::list<std::vector<Base::Vector2d>>& list)
|
|
{
|
|
vp.drawEdit(list);
|
|
}
|
|
|
|
inline void ViewProviderSketchDrawSketchHandlerAttorney::drawEditMarkers(
|
|
ViewProviderSketch& vp,
|
|
const std::vector<Base::Vector2d>& EditMarkers,
|
|
unsigned int augmentationlevel)
|
|
{
|
|
vp.drawEditMarkers(EditMarkers, augmentationlevel);
|
|
}
|
|
|
|
inline void ViewProviderSketchDrawSketchHandlerAttorney::setAxisPickStyle(ViewProviderSketch& vp,
|
|
bool on)
|
|
{
|
|
vp.setAxisPickStyle(on);
|
|
}
|
|
|
|
inline void
|
|
ViewProviderSketchDrawSketchHandlerAttorney::moveCursorToSketchPoint(ViewProviderSketch& vp,
|
|
Base::Vector2d point)
|
|
{
|
|
vp.moveCursorToSketchPoint(point);
|
|
}
|
|
|
|
inline void ViewProviderSketchDrawSketchHandlerAttorney::ensureFocus(ViewProviderSketch& vp)
|
|
{
|
|
vp.ensureFocus();
|
|
}
|
|
|
|
inline void ViewProviderSketchDrawSketchHandlerAttorney::preselectAtPoint(ViewProviderSketch& vp,
|
|
Base::Vector2d point)
|
|
{
|
|
vp.preselectAtPoint(point);
|
|
}
|
|
|
|
inline int
|
|
ViewProviderSketchDrawSketchHandlerAttorney::getPreselectPoint(const ViewProviderSketch& vp)
|
|
{
|
|
return vp.getPreselectPoint();
|
|
}
|
|
|
|
inline int
|
|
ViewProviderSketchDrawSketchHandlerAttorney::getPreselectCurve(const ViewProviderSketch& vp)
|
|
{
|
|
return vp.getPreselectCurve();
|
|
}
|
|
|
|
inline int
|
|
ViewProviderSketchDrawSketchHandlerAttorney::getPreselectCross(const ViewProviderSketch& vp)
|
|
{
|
|
return vp.getPreselectCross();
|
|
}
|
|
|
|
inline void
|
|
ViewProviderSketchDrawSketchHandlerAttorney::setAngleSnapping(ViewProviderSketch& vp,
|
|
bool enable,
|
|
Base::Vector2d referencePoint)
|
|
{
|
|
vp.setAngleSnapping(enable, referencePoint);
|
|
}
|
|
|
|
inline void ViewProviderSketchDrawSketchHandlerAttorney::moveConstraint(ViewProviderSketch& vp,
|
|
int constNum,
|
|
const Base::Vector2d& toPos)
|
|
{
|
|
vp.moveConstraint(constNum, toPos);
|
|
}
|
|
|
|
inline void
|
|
ViewProviderSketchDrawSketchHandlerAttorney::signalToolChanged(const ViewProviderSketch& vp,
|
|
const std::string& toolname)
|
|
{
|
|
vp.signalToolChanged(toolname);
|
|
}
|
|
|
|
/**************************** CurveConverter **********************************************/
|
|
|
|
CurveConverter::CurveConverter()
|
|
{
|
|
try {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/View");
|
|
hGrp->Attach(this);
|
|
}
|
|
catch (const Base::ValueError& e) { // ensure that if parameter strings are not well-formed,
|
|
// the exception is not propagated
|
|
Base::Console().DeveloperError("CurveConverter",
|
|
"Malformed parameter string: %s\n",
|
|
e.what());
|
|
}
|
|
|
|
updateCurvedEdgeCountSegmentsParameter();
|
|
}
|
|
|
|
CurveConverter::~CurveConverter()
|
|
{
|
|
// Do not detach from the parameter group.
|
|
// So far there is only a single static instance of CurveConverter inside
|
|
// DrawSketchHandler::drawEdit. This static instance will be destroyed after
|
|
// the main() function has been exited so that any attempt to access the
|
|
// parameter managers is undefined behaviour. See issue #13622.
|
|
}
|
|
|
|
std::vector<Base::Vector2d> CurveConverter::toVector2D(const Part::Geometry* geometry)
|
|
{
|
|
std::vector<Base::Vector2d> vector2d;
|
|
|
|
auto emplaceasvector2d = [&vector2d](const Base::Vector3d& point) {
|
|
vector2d.emplace_back(point.x, point.y);
|
|
};
|
|
|
|
auto isconic = geometry->isDerivedFrom<Part::GeomConic>();
|
|
auto isbounded = geometry->isDerivedFrom<Part::GeomBoundedCurve>();
|
|
|
|
if (geometry->is<Part::GeomLineSegment>()) { // add a line
|
|
auto geo = static_cast<const Part::GeomLineSegment*>(geometry);
|
|
|
|
emplaceasvector2d(geo->getStartPoint());
|
|
emplaceasvector2d(geo->getEndPoint());
|
|
}
|
|
else if (isconic || isbounded) {
|
|
|
|
auto geo = static_cast<const Part::GeomConic*>(geometry);
|
|
|
|
double segment =
|
|
(geo->getLastParameter() - geo->getFirstParameter()) / curvedEdgeCountSegments;
|
|
|
|
for (int i = 0; i < curvedEdgeCountSegments; i++) {
|
|
emplaceasvector2d(geo->value(geo->getFirstParameter() + i * segment));
|
|
}
|
|
|
|
// either close the curve for untrimmed conic or set the last point for bounded curves
|
|
emplaceasvector2d(isconic ? geo->value(0) : geo->value(geo->getLastParameter()));
|
|
}
|
|
|
|
return vector2d;
|
|
}
|
|
|
|
std::list<std::vector<Base::Vector2d>>
|
|
CurveConverter::toVector2DList(const std::vector<Part::Geometry*>& geometries)
|
|
{
|
|
std::list<std::vector<Base::Vector2d>> list;
|
|
|
|
for (const auto& geo : geometries) {
|
|
list.push_back(toVector2D(geo));
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
void CurveConverter::updateCurvedEdgeCountSegmentsParameter()
|
|
{
|
|
ParameterGrp::handle hGrp =
|
|
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View");
|
|
int stdcountsegments = hGrp->GetInt("SegmentsPerGeometry", 50);
|
|
|
|
// value cannot be smaller than 6
|
|
if (stdcountsegments < 6) {
|
|
stdcountsegments = 6;
|
|
}
|
|
|
|
curvedEdgeCountSegments = stdcountsegments;
|
|
};
|
|
|
|
/** Observer for parameter group. */
|
|
void CurveConverter::OnChange(Base::Subject<const char*>& rCaller, const char* sReason)
|
|
{
|
|
(void)rCaller;
|
|
|
|
if (strcmp(sReason, "SegmentsPerGeometry") == 0) {
|
|
updateCurvedEdgeCountSegmentsParameter();
|
|
}
|
|
}
|
|
|
|
/**************************** DrawSketchHandler *******************************************/
|
|
|
|
|
|
//**************************************************************************
|
|
// Construction/Destruction
|
|
|
|
DrawSketchHandler::DrawSketchHandler()
|
|
: Gui::ToolHandler()
|
|
, sketchgui(nullptr)
|
|
{}
|
|
|
|
DrawSketchHandler::~DrawSketchHandler()
|
|
{}
|
|
|
|
std::string DrawSketchHandler::getToolName() const
|
|
{
|
|
return "DSH_None";
|
|
}
|
|
|
|
std::unique_ptr<QWidget> DrawSketchHandler::createWidget() const
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
bool DrawSketchHandler::isWidgetVisible() const
|
|
{
|
|
return false;
|
|
};
|
|
|
|
QPixmap DrawSketchHandler::getToolIcon() const
|
|
{
|
|
return QPixmap();
|
|
}
|
|
|
|
QString DrawSketchHandler::getToolWidgetText() const
|
|
{
|
|
return QString();
|
|
}
|
|
|
|
|
|
void DrawSketchHandler::activate(ViewProviderSketch* vp)
|
|
{
|
|
sketchgui = vp;
|
|
|
|
if (!Gui::ToolHandler::activate()) {
|
|
sketchgui->purgeHandler();
|
|
}
|
|
}
|
|
|
|
void DrawSketchHandler::deactivate()
|
|
{
|
|
Gui::ToolHandler::deactivate();
|
|
ViewProviderSketchDrawSketchHandlerAttorney::setConstraintSelectability(*sketchgui, true);
|
|
|
|
// clear temporary Curve and Markers from the scenograph
|
|
clearEdit();
|
|
clearEditMarkers();
|
|
resetPositionText();
|
|
setAngleSnapping(false);
|
|
|
|
ViewProviderSketchDrawSketchHandlerAttorney::signalToolChanged(*sketchgui, "DSH_None");
|
|
}
|
|
|
|
void DrawSketchHandler::preActivated()
|
|
{
|
|
this->signalToolChanged();
|
|
ViewProviderSketchDrawSketchHandlerAttorney::setConstraintSelectability(*sketchgui, false);
|
|
}
|
|
|
|
void DrawSketchHandler::registerPressedKey(bool pressed, int key)
|
|
{
|
|
// the default behaviour is to quit - specific handler categories may
|
|
// override this behaviour, for example to implement a continuous mode
|
|
if (key == SoKeyboardEvent::ESCAPE && !pressed) {
|
|
quit();
|
|
}
|
|
}
|
|
|
|
void DrawSketchHandler::pressRightButton(Base::Vector2d /*onSketchPos*/)
|
|
{
|
|
// the default behaviour is to quit - specific handler categories may
|
|
// override this behaviour, for example to implement a continuous mode
|
|
quit();
|
|
}
|
|
|
|
|
|
void DrawSketchHandler::quit()
|
|
{
|
|
assert(sketchgui);
|
|
|
|
Gui::Selection().rmvSelectionGate();
|
|
Gui::Selection().rmvPreselect();
|
|
|
|
sketchgui->purgeHandler();
|
|
}
|
|
|
|
void DrawSketchHandler::toolWidgetChanged(QWidget* newwidget)
|
|
{
|
|
toolwidget = newwidget;
|
|
onWidgetChanged();
|
|
}
|
|
|
|
//**************************************************************************
|
|
// Helpers
|
|
|
|
int DrawSketchHandler::getHighestVertexIndex()
|
|
{
|
|
return sketchgui->getSketchObject()->getHighestVertexIndex();
|
|
}
|
|
|
|
int DrawSketchHandler::getHighestCurveIndex()
|
|
{
|
|
return sketchgui->getSketchObject()->getHighestCurveIndex();
|
|
}
|
|
|
|
std::vector<QPixmap>
|
|
DrawSketchHandler::suggestedConstraintsPixmaps(std::vector<AutoConstraint>& suggestedConstraints)
|
|
{
|
|
std::vector<QPixmap> pixmaps;
|
|
// Iterate through AutoConstraints types and get their pixmaps
|
|
for (auto& autoCstr : suggestedConstraints) {
|
|
QString iconType;
|
|
switch (autoCstr.Type) {
|
|
case Horizontal:
|
|
iconType = QStringLiteral("Constraint_Horizontal");
|
|
break;
|
|
case Vertical:
|
|
iconType = QStringLiteral("Constraint_Vertical");
|
|
break;
|
|
case Coincident:
|
|
iconType = QStringLiteral("Constraint_PointOnPoint");
|
|
break;
|
|
case PointOnObject:
|
|
iconType = QStringLiteral("Constraint_PointOnObject");
|
|
break;
|
|
case Symmetric:
|
|
iconType = QStringLiteral("Constraint_Symmetric");
|
|
break;
|
|
case Tangent:
|
|
iconType = QStringLiteral("Constraint_Tangent");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!iconType.isEmpty()) {
|
|
constexpr int iconWidth = 16;
|
|
QPixmap icon = Gui::BitmapFactory().pixmapFromSvg(iconType.toStdString().c_str(),
|
|
QSize(iconWidth, iconWidth));
|
|
pixmaps.push_back(icon);
|
|
}
|
|
}
|
|
return pixmaps;
|
|
}
|
|
|
|
DrawSketchHandler::PreselectionData DrawSketchHandler::getPreselectionData()
|
|
{
|
|
SketchObject* obj = sketchgui->getSketchObject();
|
|
|
|
// Extract preselection information (vertex, curve, cross)
|
|
PreselectionData preSelData;
|
|
int preSelPnt = getPreselectPoint();
|
|
int preSelCrv = getPreselectCurve();
|
|
int preSelCrs = getPreselectCross();
|
|
|
|
if (preSelPnt != -1) {
|
|
obj->getGeoVertexIndex(preSelPnt, preSelData.geoId, preSelData.posId);
|
|
}
|
|
else if (preSelCrv != -1) {
|
|
const Part::Geometry* geom = obj->getGeometry(preSelCrv);
|
|
if (geom) {
|
|
preSelData.geoId = preSelCrv;
|
|
if (geom->is<Part::GeomLineSegment>()) {
|
|
auto* line = static_cast<const Part::GeomLineSegment*>(geom);
|
|
preSelData.hitShapeDir = line->getEndPoint() - line->getStartPoint();
|
|
preSelData.isLine = true;
|
|
}
|
|
}
|
|
}
|
|
else if (preSelCrs == 0) {
|
|
preSelData.geoId = Sketcher::GeoEnum::RtPnt;
|
|
preSelData.posId = PointPos::start;
|
|
}
|
|
else if (preSelCrs == 1) {
|
|
preSelData.geoId = Sketcher::GeoEnum::HAxis;
|
|
preSelData.hitShapeDir = Base::Vector3d(1, 0, 0);
|
|
preSelData.isLine = true;
|
|
}
|
|
else if (preSelCrs == 2) {
|
|
preSelData.geoId = Sketcher::GeoEnum::VAxis;
|
|
preSelData.hitShapeDir = Base::Vector3d(0, 1, 0);
|
|
preSelData.isLine = true;
|
|
}
|
|
return preSelData;
|
|
}
|
|
|
|
bool DrawSketchHandler::isLineCenterAutoConstraint(int GeoId, const Base::Vector2d& Pos) const
|
|
{
|
|
SketchObject* obj = sketchgui->getSketchObject();
|
|
|
|
auto* geo = obj->getGeometry(GeoId);
|
|
if (geo->isDerivedFrom<Part::GeomLineSegment>()) {
|
|
auto* line = static_cast<const Part::GeomLineSegment*>(geo);
|
|
|
|
Base::Vector2d startPoint = toVector2d(line->getStartPoint());
|
|
Base::Vector2d endPoint = toVector2d(line->getEndPoint());
|
|
Base::Vector2d midPoint = (startPoint + endPoint) / 2;
|
|
|
|
// Check if we are at middle of the line
|
|
if ((Pos - midPoint).Length() < (endPoint - startPoint).Length() * 0.05) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void DrawSketchHandler::seekPreselectionAutoConstraint(
|
|
std::vector<AutoConstraint>& suggestedConstraints,
|
|
const Base::Vector2d& Pos,
|
|
const Base::Vector2d& Dir,
|
|
AutoConstraint::TargetType type)
|
|
{
|
|
PreselectionData preSel = getPreselectionData();
|
|
|
|
if (preSel.geoId != GeoEnum::GeoUndef) {
|
|
// Currently only considers objects in current Sketcher
|
|
AutoConstraint constr;
|
|
constr.Type = Sketcher::None;
|
|
constr.GeoId = preSel.geoId;
|
|
constr.PosId = preSel.posId;
|
|
if (type == AutoConstraint::VERTEX || type == AutoConstraint::VERTEX_NO_TANGENCY) {
|
|
if (preSel.posId == PointPos::none) {
|
|
bool lineCenter = isLineCenterAutoConstraint(preSel.geoId, Pos);
|
|
constr.Type = lineCenter ? Sketcher::Symmetric : Sketcher::PointOnObject;
|
|
}
|
|
else {
|
|
constr.Type = Sketcher::Coincident;
|
|
}
|
|
}
|
|
else if (type == AutoConstraint::CURVE && preSel.posId != PointPos::none) {
|
|
constr.Type = Sketcher::PointOnObject;
|
|
}
|
|
else if (type == AutoConstraint::CURVE && preSel.posId == PointPos::none) {
|
|
constr.Type = Sketcher::Tangent;
|
|
}
|
|
|
|
if (constr.Type == Sketcher::Tangent && preSel.isLine) {
|
|
if (Dir.Length() < 1e-8 || preSel.hitShapeDir.Length() < 1e-8) {
|
|
return; // Direction not set so return;
|
|
}
|
|
|
|
// We are hitting a line and have hitting vector information
|
|
Base::Vector3d dir3d = Base::Vector3d(Dir.x, Dir.y, 0);
|
|
double cosangle = dir3d.Normalize() * preSel.hitShapeDir.Normalize();
|
|
|
|
// the angle between the line and the hitting direction are over around 6 degrees
|
|
if (fabs(cosangle) > 0.995f) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (constr.Type != Sketcher::None) {
|
|
suggestedConstraints.push_back(constr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrawSketchHandler::seekAlignmentAutoConstraint(
|
|
std::vector<AutoConstraint>& suggestedConstraints,
|
|
const Base::Vector2d& Dir)
|
|
{
|
|
using std::numbers::pi;
|
|
constexpr double angleDevRad = Base::toRadians<double>(2);
|
|
|
|
AutoConstraint constr;
|
|
constr.Type = Sketcher::None;
|
|
constr.GeoId = GeoEnum::GeoUndef;
|
|
constr.PosId = PointPos::none;
|
|
double angle = std::abs(atan2(Dir.y, Dir.x));
|
|
if (angle < angleDevRad || (pi - angle) < angleDevRad) {
|
|
// Suggest horizontal constraint
|
|
constr.Type = Sketcher::Horizontal;
|
|
}
|
|
else if (std::abs(angle - pi / 2) < angleDevRad) {
|
|
// Suggest vertical constraint
|
|
constr.Type = Sketcher::Vertical;
|
|
}
|
|
|
|
if (constr.Type != Sketcher::None) {
|
|
suggestedConstraints.push_back(constr);
|
|
}
|
|
}
|
|
|
|
void DrawSketchHandler::seekTangentAutoConstraint(std::vector<AutoConstraint>& suggestedConstraints,
|
|
const Base::Vector2d& Pos,
|
|
const Base::Vector2d& Dir)
|
|
{
|
|
using std::numbers::pi;
|
|
SketchObject* obj = sketchgui->getSketchObject();
|
|
int tangId = GeoEnum::GeoUndef;
|
|
|
|
// Do not consider if distance is more than that.
|
|
// Decrease this value when a candidate is found.
|
|
double tangDeviation = 0.1 * sketchgui->getScaleFactor();
|
|
|
|
// Get geometry list
|
|
const std::vector<Part::Geometry*> geomlist = obj->getCompleteGeometry();
|
|
|
|
Base::Vector3d tmpPos(Pos.x, Pos.y, 0.f); // Current cursor point
|
|
Base::Vector3d tmpDir(Dir.x, Dir.y, 0.f); // Direction of line
|
|
Base::Vector3d tmpStart(Pos.x - Dir.x, Pos.y - Dir.y, 0.f); // Start point
|
|
|
|
int i = -1;
|
|
for (auto* geo : geomlist) {
|
|
i++;
|
|
|
|
if (geo->is<Part::GeomCircle>()) {
|
|
auto* circle = static_cast<const Part::GeomCircle*>(geo);
|
|
|
|
Base::Vector3d center = circle->getCenter();
|
|
|
|
double radius = circle->getRadius();
|
|
|
|
// ignore if no touch (use dot product)
|
|
if (tmpDir * (center - tmpPos) > 0 || tmpDir * (center - tmpStart) < 0) {
|
|
continue;
|
|
}
|
|
|
|
Base::Vector3d projPnt(0.f, 0.f, 0.f);
|
|
projPnt = projPnt.ProjectToLine(center - tmpPos, tmpDir);
|
|
double projDist = std::abs(projPnt.Length() - radius);
|
|
|
|
// Find if nearest
|
|
if (projDist < tangDeviation) {
|
|
tangId = i;
|
|
tangDeviation = projDist;
|
|
}
|
|
}
|
|
else if (geo->is<Part::GeomEllipse>()) {
|
|
auto* ellipse = static_cast<const Part::GeomEllipse*>(geo);
|
|
|
|
Base::Vector3d center = ellipse->getCenter();
|
|
|
|
double a = ellipse->getMajorRadius();
|
|
double b = ellipse->getMinorRadius();
|
|
Base::Vector3d majdir = ellipse->getMajorAxisDir();
|
|
|
|
double cf = sqrt(a * a - b * b);
|
|
|
|
Base::Vector3d focus1P = center + cf * majdir;
|
|
Base::Vector3d focus2P = center - cf * majdir;
|
|
|
|
Base::Vector3d norm = Base::Vector3d(Dir.y, -Dir.x).Normalize();
|
|
|
|
double distancetoline = norm * (tmpPos - focus1P); // distance focus1 to line
|
|
|
|
// mirror of focus1 with respect to the line
|
|
Base::Vector3d focus1PMirrored = focus1P + 2 * distancetoline * norm;
|
|
|
|
double error = fabs((focus1PMirrored - focus2P).Length() - 2 * a);
|
|
|
|
if (error < tangDeviation) {
|
|
tangId = i;
|
|
tangDeviation = error;
|
|
}
|
|
}
|
|
else if (geo->is<Part::GeomArcOfCircle>()) {
|
|
auto* arc = static_cast<const Part::GeomArcOfCircle*>(geo);
|
|
|
|
Base::Vector3d center = arc->getCenter();
|
|
double radius = arc->getRadius();
|
|
|
|
// ignore if no touch (use dot product)
|
|
if (tmpDir * (center - tmpPos) > 0 || tmpDir * (center - tmpStart) < 0) {
|
|
continue;
|
|
}
|
|
|
|
Base::Vector3d projPnt(0.f, 0.f, 0.f);
|
|
projPnt = projPnt.ProjectToLine(center - tmpPos, tmpDir);
|
|
double projDist = std::abs(projPnt.Length() - radius);
|
|
|
|
if (projDist < tangDeviation) {
|
|
double startAngle, endAngle;
|
|
arc->getRange(startAngle, endAngle, /*emulateCCW=*/true);
|
|
|
|
double angle = atan2(projPnt.y, projPnt.x);
|
|
while (angle < startAngle) {
|
|
angle += 2 * pi; // Bring it to range of arc
|
|
}
|
|
|
|
// if the point is on correct side of arc
|
|
if (angle <= endAngle) { // Now need to check only one side
|
|
tangId = i;
|
|
tangDeviation = projDist;
|
|
}
|
|
}
|
|
}
|
|
else if (geo->is<Part::GeomArcOfEllipse>()) {
|
|
auto* aoe = static_cast<const Part::GeomArcOfEllipse*>(geo);
|
|
|
|
Base::Vector3d center = aoe->getCenter();
|
|
|
|
double a = aoe->getMajorRadius();
|
|
double b = aoe->getMinorRadius();
|
|
Base::Vector3d majdir = aoe->getMajorAxisDir();
|
|
|
|
double cf = sqrt(a * a - b * b);
|
|
|
|
Base::Vector3d focus1P = center + cf * majdir;
|
|
Base::Vector3d focus2P = center - cf * majdir;
|
|
|
|
Base::Vector3d norm = Base::Vector3d(Dir.y, -Dir.x).Normalize();
|
|
|
|
double distancetoline = norm * (tmpPos - focus1P); // distance focus1 to line
|
|
|
|
// mirror of focus1 with respect to the line
|
|
Base::Vector3d focus1PMirrored = focus1P + 2 * distancetoline * norm;
|
|
|
|
double error = fabs((focus1PMirrored - focus2P).Length() - 2 * a);
|
|
|
|
if (error < tangDeviation) {
|
|
double startAngle, endAngle;
|
|
aoe->getRange(startAngle, endAngle, /*emulateCCW=*/true);
|
|
|
|
double angle = Base::fmod(
|
|
atan2(
|
|
-aoe->getMajorRadius()
|
|
* ((tmpPos.x - center.x) * majdir.y - (tmpPos.y - center.y) * majdir.x),
|
|
aoe->getMinorRadius()
|
|
* ((tmpPos.x - center.x) * majdir.x + (tmpPos.y - center.y) * majdir.y))
|
|
- startAngle,
|
|
2.f * pi);
|
|
|
|
while (angle < startAngle) {
|
|
angle += 2 * pi; // Bring it to range of arc
|
|
}
|
|
|
|
// if the point is on correct side of arc
|
|
if (angle <= endAngle) { // Now need to check only one side
|
|
tangId = i;
|
|
tangDeviation = error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tangId != GeoEnum::GeoUndef) {
|
|
if (tangId > getHighestCurveIndex()) { // external Geometry
|
|
tangId = getHighestCurveIndex() - tangId;
|
|
}
|
|
AutoConstraint constr;
|
|
constr.Type = Tangent;
|
|
constr.GeoId = tangId;
|
|
constr.PosId = PointPos::none;
|
|
suggestedConstraints.push_back(constr);
|
|
}
|
|
}
|
|
|
|
int DrawSketchHandler::seekAutoConstraint(std::vector<AutoConstraint>& suggestedConstraints,
|
|
const Base::Vector2d& Pos,
|
|
const Base::Vector2d& Dir,
|
|
AutoConstraint::TargetType type)
|
|
{
|
|
suggestedConstraints.clear();
|
|
|
|
if (!sketchgui->Autoconstraints.getValue()) {
|
|
return 0; // If Autoconstraints property is not set quit
|
|
}
|
|
|
|
seekPreselectionAutoConstraint(suggestedConstraints, Pos, Dir, type);
|
|
|
|
if (Dir.Length() > 1e-8 && type != AutoConstraint::CURVE) {
|
|
seekAlignmentAutoConstraint(suggestedConstraints, Dir);
|
|
|
|
if (type != AutoConstraint::VERTEX_NO_TANGENCY) {
|
|
seekTangentAutoConstraint(suggestedConstraints, Pos, Dir);
|
|
}
|
|
}
|
|
|
|
return suggestedConstraints.size();
|
|
}
|
|
|
|
void DrawSketchHandler::createAutoConstraints(const std::vector<AutoConstraint>& autoConstrs,
|
|
int geoId1,
|
|
Sketcher::PointPos posId1,
|
|
bool createowncommand /*= true*/)
|
|
{
|
|
if (!sketchgui->Autoconstraints.getValue()) {
|
|
return; // If Autoconstraints property is not set quit
|
|
}
|
|
|
|
if (!autoConstrs.empty()) {
|
|
|
|
if (createowncommand) {
|
|
// Open the Command
|
|
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add auto constraints"));
|
|
}
|
|
|
|
// Iterate through constraints
|
|
for (auto& cstr : autoConstrs) {
|
|
int geoId2 = cstr.GeoId;
|
|
|
|
switch (cstr.Type) {
|
|
case Sketcher::Coincident: {
|
|
if (posId1 == Sketcher::PointPos::none) {
|
|
continue;
|
|
}
|
|
// If the auto constraint has a point create a coincident otherwise it is an
|
|
// edge on a point
|
|
Gui::cmdAppObjectArgs(
|
|
sketchgui->getObject(),
|
|
"addConstraint(Sketcher.Constraint('Coincident',%d,%d,%d,%d)) ",
|
|
geoId1,
|
|
static_cast<int>(posId1),
|
|
cstr.GeoId,
|
|
static_cast<int>(cstr.PosId));
|
|
} break;
|
|
case Sketcher::PointOnObject: {
|
|
Sketcher::PointPos posId2 = cstr.PosId;
|
|
if (posId1 == Sketcher::PointPos::none) {
|
|
// Auto constraining an edge so swap parameters
|
|
std::swap(geoId1, geoId2);
|
|
std::swap(posId1, posId2);
|
|
}
|
|
|
|
Gui::cmdAppObjectArgs(
|
|
sketchgui->getObject(),
|
|
"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ",
|
|
geoId1,
|
|
static_cast<int>(posId1),
|
|
geoId2);
|
|
} break;
|
|
case Sketcher::Symmetric: {
|
|
Gui::cmdAppObjectArgs(
|
|
sketchgui->getObject(),
|
|
"addConstraint(Sketcher.Constraint('Symmetric',%d,1,%d,2,%d,%d)) ",
|
|
geoId2,
|
|
geoId2,
|
|
geoId1,
|
|
static_cast<int>(posId1));
|
|
} break;
|
|
// In special case of Horizontal/Vertical constraint, geoId2 is normally unused
|
|
// and should be 'Constraint::GeoUndef' However it can be used as a way to
|
|
// require the function to apply these constraints on another geometry In this
|
|
// case the caller as to set geoId2, then it will be used as target instead of
|
|
// geoId2
|
|
case Sketcher::Horizontal: {
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(),
|
|
"addConstraint(Sketcher.Constraint('Horizontal',%d)) ",
|
|
geoId2 != GeoEnum::GeoUndef ? geoId2 : geoId1);
|
|
} break;
|
|
case Sketcher::Vertical: {
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(),
|
|
"addConstraint(Sketcher.Constraint('Vertical',%d)) ",
|
|
geoId2 != GeoEnum::GeoUndef ? geoId2 : geoId1);
|
|
} break;
|
|
case Sketcher::Tangent: {
|
|
Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
|
|
|
|
const Part::Geometry* geom1 = Obj->getGeometry(geoId1);
|
|
const Part::Geometry* geom2 = Obj->getGeometry(cstr.GeoId);
|
|
|
|
// ellipse tangency support using construction elements (lines)
|
|
if (geom1 && geom2
|
|
&& (geom1->is<Part::GeomEllipse>() || geom2->is<Part::GeomEllipse>())) {
|
|
|
|
if (!geom1->is<Part::GeomEllipse>()) {
|
|
std::swap(geoId1, geoId2);
|
|
}
|
|
|
|
// geoId1 is the ellipse
|
|
geom1 = Obj->getGeometry(geoId1);
|
|
geom2 = Obj->getGeometry(geoId2);
|
|
|
|
if (geom2->is<Part::GeomEllipse>() || geom2->is<Part::GeomArcOfEllipse>()
|
|
|| geom2->is<Part::GeomCircle>()
|
|
|| geom2->is<Part::GeomArcOfCircle>()) {
|
|
// in all these cases an intermediate element is needed
|
|
makeTangentToEllipseviaNewPoint(
|
|
Obj,
|
|
static_cast<const Part::GeomEllipse*>(geom1),
|
|
geom2,
|
|
geoId1,
|
|
geoId2);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// arc of ellipse tangency support using external elements
|
|
if (geom1 && geom2
|
|
&& (geom1->is<Part::GeomArcOfEllipse>()
|
|
|| geom2->is<Part::GeomArcOfEllipse>())) {
|
|
|
|
if (!geom1->is<Part::GeomArcOfEllipse>()) {
|
|
std::swap(geoId1, geoId2);
|
|
}
|
|
|
|
// geoId1 is the arc of ellipse
|
|
geom1 = Obj->getGeometry(geoId1);
|
|
geom2 = Obj->getGeometry(geoId2);
|
|
|
|
if (geom2->is<Part::GeomArcOfEllipse>() || geom2->is<Part::GeomCircle>()
|
|
|| geom2->is<Part::GeomArcOfCircle>()) {
|
|
// in all these cases an intermediate element is needed
|
|
makeTangentToArcOfEllipseviaNewPoint(
|
|
Obj,
|
|
static_cast<const Part::GeomArcOfEllipse*>(geom1),
|
|
geom2,
|
|
geoId1,
|
|
geoId2);
|
|
return;
|
|
}
|
|
}
|
|
|
|
Gui::cmdAppObjectArgs(sketchgui->getObject(),
|
|
"addConstraint(Sketcher.Constraint('Tangent',%d, %d)) ",
|
|
geoId1,
|
|
cstr.GeoId);
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (createowncommand) {
|
|
Gui::Command::commitCommand();
|
|
}
|
|
// Gui::Command::updateActive(); // There is already an recompute in each command
|
|
// creation, this is redundant.
|
|
}
|
|
}
|
|
}
|
|
|
|
int DrawSketchHandler::seekAndRenderAutoConstraint(
|
|
std::vector<AutoConstraint>& suggestedConstraints,
|
|
const Base::Vector2d& Pos,
|
|
const Base::Vector2d& Dir,
|
|
AutoConstraint::TargetType type)
|
|
{
|
|
if (seekAutoConstraint(suggestedConstraints, Pos, Dir, type)) {
|
|
renderSuggestConstraintsCursor(suggestedConstraints);
|
|
}
|
|
else {
|
|
applyCursor();
|
|
}
|
|
|
|
return suggestedConstraints.size();
|
|
}
|
|
|
|
void DrawSketchHandler::renderSuggestConstraintsCursor(
|
|
std::vector<AutoConstraint>& suggestedConstraints)
|
|
{
|
|
std::vector<QPixmap> pixmaps = suggestedConstraintsPixmaps(suggestedConstraints);
|
|
addCursorTail(pixmaps);
|
|
}
|
|
|
|
void DrawSketchHandler::setPositionText(const Base::Vector2d& Pos, const SbString& text)
|
|
{
|
|
ViewProviderSketchDrawSketchHandlerAttorney::setPositionText(*sketchgui, Pos, text);
|
|
}
|
|
|
|
|
|
void DrawSketchHandler::setPositionText(const Base::Vector2d& Pos)
|
|
{
|
|
ViewProviderSketchDrawSketchHandlerAttorney::setPositionText(*sketchgui, Pos);
|
|
}
|
|
|
|
void DrawSketchHandler::resetPositionText()
|
|
{
|
|
ViewProviderSketchDrawSketchHandlerAttorney::resetPositionText(*sketchgui);
|
|
}
|
|
|
|
void DrawSketchHandler::drawEdit(const std::vector<Base::Vector2d>& EditCurve) const
|
|
{
|
|
ViewProviderSketchDrawSketchHandlerAttorney::drawEdit(*sketchgui, EditCurve);
|
|
}
|
|
|
|
void DrawSketchHandler::drawEdit(const std::list<std::vector<Base::Vector2d>>& list) const
|
|
{
|
|
ViewProviderSketchDrawSketchHandlerAttorney::drawEdit(*sketchgui, list);
|
|
}
|
|
|
|
void DrawSketchHandler::drawEdit(const std::vector<Part::Geometry*>& geometries) const
|
|
{
|
|
static CurveConverter c;
|
|
|
|
auto list = c.toVector2DList(geometries);
|
|
|
|
drawEdit(list);
|
|
}
|
|
|
|
void DrawSketchHandler::clearEdit() const
|
|
{
|
|
drawEdit(std::vector<Base::Vector2d>());
|
|
}
|
|
|
|
void DrawSketchHandler::clearEditMarkers() const
|
|
{
|
|
drawEditMarkers(std::vector<Base::Vector2d>());
|
|
}
|
|
|
|
void DrawSketchHandler::drawPositionAtCursor(const Base::Vector2d& position)
|
|
{
|
|
setPositionText(position);
|
|
}
|
|
|
|
void DrawSketchHandler::drawDirectionAtCursor(const Base::Vector2d& position,
|
|
const Base::Vector2d& origin)
|
|
{
|
|
if (!showCursorCoords()) {
|
|
return;
|
|
}
|
|
|
|
float length = (position - origin).Length();
|
|
float angle = (position - origin).GetAngle(Base::Vector2d(1.f, 0.f));
|
|
|
|
SbString text;
|
|
std::string lengthString = lengthToDisplayFormat(length, 1);
|
|
std::string angleString = angleToDisplayFormat(angle * 180.0 / std::numbers::pi, 1);
|
|
text.sprintf(" (%s, %s)", lengthString.c_str(), angleString.c_str());
|
|
setPositionText(position, text);
|
|
}
|
|
|
|
void DrawSketchHandler::drawWidthHeightAtCursor(const Base::Vector2d& position,
|
|
const double val1,
|
|
const double val2)
|
|
{
|
|
if (!showCursorCoords()) {
|
|
return;
|
|
}
|
|
|
|
SbString text;
|
|
std::string val1String = lengthToDisplayFormat(val1, 1);
|
|
std::string val2String = lengthToDisplayFormat(val2, 1);
|
|
text.sprintf(" (%s x %s)", val1String.c_str(), val2String.c_str());
|
|
setPositionText(position, text);
|
|
}
|
|
|
|
void DrawSketchHandler::drawDoubleAtCursor(const Base::Vector2d& position,
|
|
const double val,
|
|
Base::Unit unit)
|
|
{
|
|
if (!showCursorCoords()) {
|
|
return;
|
|
}
|
|
|
|
SbString text;
|
|
std::string doubleString = unit == Base::Unit::Length
|
|
? lengthToDisplayFormat(val, 1)
|
|
: angleToDisplayFormat(Base::toDegrees(val), 1);
|
|
text.sprintf(" (%s)", doubleString.c_str());
|
|
setPositionText(position, text);
|
|
}
|
|
|
|
std::unique_ptr<QWidget> DrawSketchHandler::createToolWidget() const
|
|
{
|
|
return createWidget(); // NVI
|
|
}
|
|
|
|
bool DrawSketchHandler::isToolWidgetVisible() const
|
|
{
|
|
return isWidgetVisible(); // NVI
|
|
}
|
|
|
|
QPixmap DrawSketchHandler::getToolWidgetHeaderIcon() const
|
|
{
|
|
return getToolIcon();
|
|
}
|
|
|
|
QString DrawSketchHandler::getToolWidgetHeaderText() const
|
|
{
|
|
return getToolWidgetText();
|
|
}
|
|
|
|
void DrawSketchHandler::drawEditMarkers(const std::vector<Base::Vector2d>& EditMarkers,
|
|
unsigned int augmentationlevel) const
|
|
{
|
|
ViewProviderSketchDrawSketchHandlerAttorney::drawEditMarkers(*sketchgui,
|
|
EditMarkers,
|
|
augmentationlevel);
|
|
}
|
|
|
|
void DrawSketchHandler::setAxisPickStyle(bool on)
|
|
{
|
|
ViewProviderSketchDrawSketchHandlerAttorney::setAxisPickStyle(*sketchgui, on);
|
|
}
|
|
|
|
void DrawSketchHandler::moveCursorToSketchPoint(Base::Vector2d point)
|
|
{
|
|
ViewProviderSketchDrawSketchHandlerAttorney::moveCursorToSketchPoint(*sketchgui, point);
|
|
}
|
|
|
|
void DrawSketchHandler::ensureFocus()
|
|
{
|
|
ViewProviderSketchDrawSketchHandlerAttorney::ensureFocus(*sketchgui);
|
|
}
|
|
|
|
void DrawSketchHandler::preselectAtPoint(Base::Vector2d point)
|
|
{
|
|
ViewProviderSketchDrawSketchHandlerAttorney::preselectAtPoint(*sketchgui, point);
|
|
}
|
|
|
|
int DrawSketchHandler::getPreselectPoint() const
|
|
{
|
|
return ViewProviderSketchDrawSketchHandlerAttorney::getPreselectPoint(*sketchgui);
|
|
}
|
|
|
|
int DrawSketchHandler::getPreselectCurve() const
|
|
{
|
|
return ViewProviderSketchDrawSketchHandlerAttorney::getPreselectCurve(*sketchgui);
|
|
}
|
|
|
|
int DrawSketchHandler::getPreselectCross() const
|
|
{
|
|
return ViewProviderSketchDrawSketchHandlerAttorney::getPreselectCross(*sketchgui);
|
|
}
|
|
|
|
Sketcher::SketchObject* DrawSketchHandler::getSketchObject()
|
|
{
|
|
return sketchgui->getSketchObject();
|
|
}
|
|
|
|
void DrawSketchHandler::setAngleSnapping(bool enable, Base::Vector2d referencePoint)
|
|
{
|
|
ViewProviderSketchDrawSketchHandlerAttorney::setAngleSnapping(*sketchgui,
|
|
enable,
|
|
referencePoint);
|
|
}
|
|
|
|
void DrawSketchHandler::moveConstraint(int constNum, const Base::Vector2d& toPos)
|
|
{
|
|
ViewProviderSketchDrawSketchHandlerAttorney::moveConstraint(*sketchgui, constNum, toPos);
|
|
}
|
|
|
|
void DrawSketchHandler::signalToolChanged() const
|
|
{
|
|
ViewProviderSketchDrawSketchHandlerAttorney::signalToolChanged(*sketchgui, this->getToolName());
|
|
}
|