Files
create/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp

7775 lines
317 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 <Inventor/nodes/SoPickStyle.h>
# include <QApplication>
# include <QMessageBox>
# include <stdlib.h>
# include <qdebug.h>
# include <QString>
# include <GC_MakeEllipse.hxx>
# include <boost/math/special_functions/fpclassify.hpp>
# include <memory>
#endif
#include <Base/Console.h>
#include <Base/Exception.h>
#include <Base/Tools.h>
#include <App/OriginFeature.h>
#include <Gui/Action.h>
#include <Gui/Application.h>
#include <Gui/BitmapFactory.h>
#include <Gui/Document.h>
#include <Gui/CommandT.h>
#include <Gui/MainWindow.h>
#include <Gui/DlgEditFileIncludePropertyExternal.h>
#include <Gui/Selection.h>
#include <Gui/SelectionFilter.h>
#include <Mod/Sketcher/App/SketchObject.h>
#include <Mod/Part/App/DatumFeature.h>
#include <Mod/Part/App/BodyBase.h>
#include <Mod/Sketcher/App/Constraint.h>
#include "ViewProviderSketch.h"
#include "DrawSketchHandler.h"
#include "CommandConstraints.h"
#include <Gui/View3DInventor.h>
#include <Gui/View3DInventorViewer.h>
#include <Gui/SoFCUnifiedSelection.h>
#include <Gui/ToolBarManager.h>
#include "GeometryCreationMode.h"
#include "SketcherRegularPolygonDialog.h"
using namespace std;
using namespace SketcherGui;
namespace SketcherGui {
GeometryCreationMode geometryCreationMode=Normal;
}
/* helper functions ======================================================*/
// Return counter-clockwise angle from horizontal out of p1 to p2 in radians.
double GetPointAngle (const Base::Vector2d &p1, const Base::Vector2d &p2)
{
double dX = p2.x - p1.x;
double dY = p2.y - p1.y;
return dY >= 0 ? atan2(dY, dX) : atan2(dY, dX) + 2*M_PI;
}
/*
Find the centerpoint of a circle drawn through any 3 points:
Given points p1-3, draw 2 lines: S12 and S23 which each connect two points. From the
midpoint of each line, draw a perpendicular line (S12p/S23p) across the circle. These
lines will cross at the centerpoint.
Mathematically, line S12 will have a slope of m12 which can be determined. Therefore,
the slope m12p is -1/m12. Line S12p will have an equation of y = m12p*x + b12p. b12p can
be solved for using the midpoint of the line. This can be done for both lines. Since
both S12p and S23p cross at the centerpoint, solving the two equations together will give
the location of the centerpoint.
*/
Base::Vector2d GetCircleCenter (const Base::Vector2d &p1, const Base::Vector2d &p2, const Base::Vector2d &p3)
{
Base::Vector2d u = p2-p1;
Base::Vector2d v = p3-p2;
Base::Vector2d w = p1-p3;
double uu = u*u;
double vv = v*v;
double ww = w*w;
double uv = -(u*v);
double vw = -(v*w);
double uw = -(u*w);
double w0 = (2 * sqrt(uu * ww - uw * uw) * uw / (uu * ww));
double w1 = (2 * sqrt(uu * vv - uv * uv) * uv / (uu * vv));
double w2 = (2 * sqrt(vv * ww - vw * vw) * vw / (vv * ww));
double wx = w0 + w1 + w2;
if( wx == 0)
THROWM(Base::ValueError,"Points are collinear");
double x = (w0*p1.x + w1*p2.x + w2*p3.x)/wx;
double y = (w0*p1.y + w1*p2.y + w2*p3.y)/wx;
return Base::Vector2d(x, y);
}
void ActivateHandler(Gui::Document *doc, DrawSketchHandler *handler)
{
std::unique_ptr<DrawSketchHandler> ptr(handler);
if (doc) {
if (doc->getInEdit() && doc->getInEdit()->isDerivedFrom(SketcherGui::ViewProviderSketch::getClassTypeId())) {
SketcherGui::ViewProviderSketch* vp = static_cast<SketcherGui::ViewProviderSketch*> (doc->getInEdit());
vp->purgeHandler();
vp->activateHandler(ptr.release());
}
}
}
bool isCreateGeoActive(Gui::Document *doc)
{
if (doc) {
// checks if a Sketch Viewprovider is in Edit and is in no special mode
if (doc->getInEdit() && doc->getInEdit()->isDerivedFrom
(SketcherGui::ViewProviderSketch::getClassTypeId())) {
/*if (dynamic_cast<SketcherGui::ViewProviderSketch*>(doc->getInEdit())->
getSketchMode() == ViewProviderSketch::STATUS_NONE)*/
return true;
}
}
return false;
}
SketcherGui::ViewProviderSketch* getSketchViewprovider(Gui::Document *doc)
{
if (doc) {
if (doc->getInEdit() && doc->getInEdit()->isDerivedFrom
(SketcherGui::ViewProviderSketch::getClassTypeId()) )
return dynamic_cast<SketcherGui::ViewProviderSketch*>(doc->getInEdit());
}
return 0;
}
void removeRedundantHorizontalVertical(Sketcher::SketchObject* psketch,
std::vector<AutoConstraint> &sug1,
std::vector<AutoConstraint> &sug2)
{
if(!sug1.empty() && !sug2.empty()) {
bool rmvhorvert = false;
// we look for:
// 1. Coincident to external on both endpoints
// 2. Coincident in one endpoint to origin and pointonobject/tangent to an axis on the other
auto detectredundant = [psketch](std::vector<AutoConstraint> &sug, bool &ext, bool &orig, bool &axis) {
ext = false;
orig = false;
axis = false;
for(std::vector<AutoConstraint>::const_iterator it = sug.begin(); it!=sug.end(); ++it) {
if( (*it).Type == Sketcher::Coincident && ext == false) {
const std::map<int, Sketcher::PointPos> coincidents = psketch->getAllCoincidentPoints((*it).GeoId, (*it).PosId);
if(!coincidents.empty()) {
// the keys are ordered, so if the first is negative, it is coincident with external
ext = coincidents.begin()->first < 0;
std::map<int, Sketcher::PointPos>::const_iterator geoId1iterator;
geoId1iterator = coincidents.find(-1);
if( geoId1iterator != coincidents.end()) {
if( (*geoId1iterator).second == Sketcher::start )
orig = true;
}
}
else { // it may be that there is no constraint at all, but there is external geometry
ext = (*it).GeoId < 0;
orig = ((*it).GeoId == -1 && (*it).PosId == Sketcher::start);
}
}
else if( (*it).Type == Sketcher::PointOnObject && axis == false) {
axis = (((*it).GeoId == -1 && (*it).PosId == Sketcher::none) || ((*it).GeoId == -2 && (*it).PosId == Sketcher::none));
}
}
};
bool firstext = false, secondext = false, firstorig = false, secondorig = false, firstaxis = false, secondaxis = false;
detectredundant(sug1, firstext, firstorig, firstaxis);
detectredundant(sug2, secondext, secondorig, secondaxis);
rmvhorvert = ((firstext && secondext) || // coincident with external on both endpoints
(firstorig && secondaxis) || // coincident origin and point on object on other
(secondorig && firstaxis));
if(rmvhorvert) {
for(std::vector<AutoConstraint>::reverse_iterator it = sug2.rbegin(); it!=sug2.rend(); ++it) {
if( (*it).Type == Sketcher::Horizontal || (*it).Type == Sketcher::Vertical) {
sug2.erase(std::next(it).base());
it = sug2.rbegin(); // erase invalidates the iterator
}
}
}
}
}
/* Sketch commands =======================================================*/
static const char cursor_crosshair_color_fmt[] = "+ c #%06lX";
char cursor_crosshair_color[12];
void DrawSketchHandler::setCrosshairColor()
{
unsigned long color = getCrosshairColor();
sprintf(cursor_crosshair_color, cursor_crosshair_color_fmt, color);
}
unsigned long DrawSketchHandler::getCrosshairColor()
{
unsigned long color = 0xFFFFFFFF; // white
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath
("User parameter:BaseApp/Preferences/View");
color = hGrp->GetUnsigned("CursorCrosshairColor", color);
// from rgba to rgb
color = (color >> 8) & 0xFFFFFF;
return color;
}
class DrawSketchHandlerLine: public DrawSketchHandler
{
public:
DrawSketchHandlerLine():Mode(STATUS_SEEK_First),EditCurve(2){}
virtual ~DrawSketchHandlerLine(){}
/// mode table
enum SelectMode {
STATUS_SEEK_First, /**< enum value ----. */
STATUS_SEEK_Second, /**< enum value ----. */
STATUS_End
};
virtual void activated(ViewProviderSketch *)
{
setCrosshairCursor("Sketcher_Pointer_Create_Line");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_First) {
setPositionText(onSketchPos);
if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr1);
return;
}
}
else if (Mode==STATUS_SEEK_Second){
float length = (onSketchPos - EditCurve[0]).Length();
float angle = (onSketchPos - EditCurve[0]).GetAngle(Base::Vector2d(1.f,0.f));
SbString text;
text.sprintf(" (%.1f,%.1fdeg)", length, angle * 180 / M_PI);
setPositionText(onSketchPos, text);
EditCurve[1] = onSketchPos;
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr2, onSketchPos, onSketchPos - EditCurve[0])) {
renderSuggestConstraintsCursor(sugConstr2);
return;
}
}
applyCursor();
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_First){
EditCurve[0] = onSketchPos;
Mode = STATUS_SEEK_Second;
}
else {
EditCurve[1] = onSketchPos;
sketchgui->drawEdit(EditCurve);
Mode = STATUS_End;
}
return true;
}
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
if (Mode==STATUS_End){
unsetCursor();
resetPositionText();
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch line"));
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)),%s)",
EditCurve[0].x,EditCurve[0].y,EditCurve[1].x,EditCurve[1].y,
geometryCreationMode==Construction?"True":"False");
Gui::Command::commitCommand();
}
catch (const Base::Exception& e) {
Base::Console().Error("Failed to add line: %s\n", e.what());
Gui::Command::abortCommand();
}
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool avoidredundant = sketchgui->AvoidRedundant.getValue() && sketchgui->Autoconstraints.getValue();
if(avoidredundant)
removeRedundantHorizontalVertical(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()),sugConstr1,sugConstr2);
// add auto constraints for the line segment start
if (!sugConstr1.empty()) {
createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::start);
sugConstr1.clear();
}
// add auto constraints for the line segment end
if (!sugConstr2.empty()) {
createAutoConstraints(sugConstr2, getHighestCurveIndex(), Sketcher::end);
sugConstr2.clear();
}
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if(continuousMode){
// This code enables the continuous creation mode.
Mode=STATUS_SEEK_First;
EditCurve.resize(2);
applyCursor();
/* It is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
}
else{
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
}
}
return true;
}
protected:
SelectMode Mode;
std::vector<Base::Vector2d> EditCurve;
std::vector<AutoConstraint> sugConstr1, sugConstr2;
};
DEF_STD_CMD_AU(CmdSketcherCreateLine)
CmdSketcherCreateLine::CmdSketcherCreateLine()
: Command("Sketcher_CreateLine")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create line");
sToolTipText = QT_TR_NOOP("Create a line in the sketch");
sWhatsThis = "Sketcher_CreateLine";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateLine";
sAccel = "L";
eType = ForEdit;
}
void CmdSketcherCreateLine::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerLine() );
}
void CmdSketcherCreateLine::updateAction(int mode)
{
switch (mode) {
case Normal:
if (getAction())
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateLine"));
break;
case Construction:
if (getAction())
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateLine_Constr"));
break;
}
}
bool CmdSketcherCreateLine::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
/* Create Box =======================================================*/
class DrawSketchHandlerBox: public DrawSketchHandler
{
public:
enum ConstructionMethod {
Diagonal,
CenterAndCorner
};
DrawSketchHandlerBox(ConstructionMethod constrMethod = Diagonal): Mode(STATUS_SEEK_First),
EditCurve(5),
constructionMethod(constrMethod){}
virtual ~DrawSketchHandlerBox(){}
/// mode table
enum BoxMode {
STATUS_SEEK_First, /**< enum value ----. */
STATUS_SEEK_Second, /**< enum value ----. */
STATUS_End
};
virtual void activated(ViewProviderSketch *)
{
setCrosshairCursor("Sketcher_Pointer_Create_Box");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_First) {
setPositionText(onSketchPos);
if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr1);
return;
}
}
else if (Mode==STATUS_SEEK_Second) {
if(constructionMethod == Diagonal) {
float dx = onSketchPos.x - EditCurve[0].x;
float dy = onSketchPos.y - EditCurve[0].y;
SbString text;
text.sprintf(" (%.1f x %.1f)", dx, dy);
setPositionText(onSketchPos, text);
EditCurve[2] = onSketchPos;
EditCurve[1] = Base::Vector2d(onSketchPos.x ,EditCurve[0].y);
EditCurve[3] = Base::Vector2d(EditCurve[0].x,onSketchPos.y);
}
else if (constructionMethod == CenterAndCorner) {
float dx = onSketchPos.x - center.x;
float dy = onSketchPos.y - center.y;
SbString text;
text.sprintf(" (%.1f x %.1f)", dx, dy);
setPositionText(onSketchPos, text);
EditCurve[0] = center - (onSketchPos - center);
EditCurve[1] = Base::Vector2d(EditCurve[0].x,onSketchPos.y);
EditCurve[2] = onSketchPos;
EditCurve[3] = Base::Vector2d(onSketchPos.x,EditCurve[0].y);
EditCurve[4] = EditCurve[0];
}
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.0,0.0))) {
renderSuggestConstraintsCursor(sugConstr2);
return;
}
}
applyCursor();
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_First){
if(constructionMethod == Diagonal) {
EditCurve[0] = onSketchPos;
EditCurve[4] = onSketchPos;
}
else if (constructionMethod == CenterAndCorner) {
center = onSketchPos;
}
Mode = STATUS_SEEK_Second;
}
else {
if(constructionMethod == Diagonal) {
EditCurve[2] = onSketchPos;
EditCurve[1] = Base::Vector2d(onSketchPos.x ,EditCurve[0].y);
EditCurve[3] = Base::Vector2d(EditCurve[0].x,onSketchPos.y);
sketchgui->drawEdit(EditCurve);
Mode = STATUS_End;
}
else if (constructionMethod == CenterAndCorner) {
EditCurve[0] = center - (onSketchPos - center);
EditCurve[1] = Base::Vector2d(EditCurve[0].x,onSketchPos.y);
EditCurve[2] = onSketchPos;
EditCurve[3] = Base::Vector2d(onSketchPos.x,EditCurve[0].y);
EditCurve[4] = EditCurve[0];
sketchgui->drawEdit(EditCurve);
Mode = STATUS_End;
}
}
return true;
}
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
if (Mode==STATUS_End){
unsetCursor();
resetPositionText();
int firstCurve = getHighestCurveIndex() + 1;
try {
if(constructionMethod == Diagonal) {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch box"));
Gui::Command::doCommand(Gui::Command::Doc,
"geoList = []\n"
"geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n"
"geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n"
"geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n"
"geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n"
"%s.addGeometry(geoList,%s)\n"
"conList = []\n"
"conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n"
"conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n"
"conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n"
"conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n"
"conList.append(Sketcher.Constraint('Horizontal',%i))\n"
"conList.append(Sketcher.Constraint('Horizontal',%i))\n"
"conList.append(Sketcher.Constraint('Vertical',%i))\n"
"conList.append(Sketcher.Constraint('Vertical',%i))\n"
"%s.addConstraint(conList)\n",
EditCurve[0].x,EditCurve[0].y,EditCurve[1].x,EditCurve[1].y, // line 1
EditCurve[1].x,EditCurve[1].y,EditCurve[2].x,EditCurve[2].y, // line 2
EditCurve[2].x,EditCurve[2].y,EditCurve[3].x,EditCurve[3].y, // line 3
EditCurve[3].x,EditCurve[3].y,EditCurve[0].x,EditCurve[0].y, // line 4
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch
geometryCreationMode==Construction?"True":"False", // geometry as construction or not
firstCurve,firstCurve+1, // coincident1
firstCurve+1,firstCurve+2, // coincident2
firstCurve+2,firstCurve+3, // coincident3
firstCurve+3,firstCurve, // coincident4
firstCurve, // horizontal1
firstCurve+2, // horizontal2
firstCurve+1, // vertical1
firstCurve+3, // vertical2
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch
Gui::Command::commitCommand();
}
else if (constructionMethod == CenterAndCorner) {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add centered sketch box"));
Gui::Command::doCommand(Gui::Command::Doc,
"geoList = []\n"
"geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n"
"geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n"
"geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n"
"geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n"
"geoList.append(Part.Point(App.Vector(%f,%f,0)))\n"
"%s.addGeometry(geoList,%s)\n"
"conList = []\n"
"conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n"
"conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n"
"conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n"
"conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n"
"conList.append(Sketcher.Constraint('Horizontal',%i))\n"
"conList.append(Sketcher.Constraint('Horizontal',%i))\n"
"conList.append(Sketcher.Constraint('Vertical',%i))\n"
"conList.append(Sketcher.Constraint('Vertical',%i))\n"
"conList.append(Sketcher.Constraint('Symmetric',%i,2,%i,1,%i,1))\n"
"%s.addConstraint(conList)\n",
EditCurve[0].x,EditCurve[0].y,EditCurve[1].x,EditCurve[1].y, // line 1
EditCurve[1].x,EditCurve[1].y,EditCurve[2].x,EditCurve[2].y, // line 2
EditCurve[2].x,EditCurve[2].y,EditCurve[3].x,EditCurve[3].y, // line 3
EditCurve[3].x,EditCurve[3].y,EditCurve[0].x,EditCurve[0].y, // line 4
center.x,center.y, // center point
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch
geometryCreationMode==Construction?"True":"False", // geometry as construction or not
firstCurve,firstCurve+1, // coincident1
firstCurve+1,firstCurve+2, // coincident2
firstCurve+2,firstCurve+3, // coincident3
firstCurve+3,firstCurve, // coincident4
firstCurve+1, // horizontal1
firstCurve+3, // horizontal2
firstCurve, // vertical1
firstCurve+2, // vertical2
firstCurve+1, firstCurve, firstCurve + 4, // Symmetric
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch
Gui::Command::commitCommand();
}
}
catch (const Base::Exception& e) {
Base::Console().Error("Failed to add box: %s\n", e.what());
Gui::Command::abortCommand();
}
if(constructionMethod == Diagonal) {
// add auto constraints at the start of the first side
if (sugConstr1.size() > 0) {
createAutoConstraints(sugConstr1, getHighestCurveIndex() - 3 , Sketcher::start);
sugConstr1.clear();
}
// add auto constraints at the end of the second side
if (sugConstr2.size() > 0) {
createAutoConstraints(sugConstr2, getHighestCurveIndex() - 2, Sketcher::end);
sugConstr2.clear();
}
}
else if (constructionMethod == CenterAndCorner) {
// add auto constraints at the start of the first side
if (sugConstr1.size() > 0) {
createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::start);
sugConstr1.clear();
}
// add auto constraints at the end of the second side
if (sugConstr2.size() > 0) {
createAutoConstraints(sugConstr2, getHighestCurveIndex() - 3, Sketcher::end);
sugConstr2.clear();
}
}
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if(continuousMode){
// This code enables the continuous creation mode.
Mode=STATUS_SEEK_First;
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
EditCurve.resize(5);
applyCursor();
/* this is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
}
else{
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
}
}
return true;
}
protected:
BoxMode Mode;
std::vector<Base::Vector2d> EditCurve;
std::vector<AutoConstraint> sugConstr1, sugConstr2;
ConstructionMethod constructionMethod;
Base::Vector2d center;
};
DEF_STD_CMD_AU(CmdSketcherCreateRectangle)
CmdSketcherCreateRectangle::CmdSketcherCreateRectangle()
: Command("Sketcher_CreateRectangle")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create rectangle");
sToolTipText = QT_TR_NOOP("Create a rectangle in the sketch");
sWhatsThis = "Sketcher_CreateRectangle";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateRectangle";
sAccel = "R";
eType = ForEdit;
}
void CmdSketcherCreateRectangle::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerBox(DrawSketchHandlerBox::Diagonal) );
}
void CmdSketcherCreateRectangle::updateAction(int mode)
{
switch (mode) {
case Normal:
if (getAction())
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle"));
break;
case Construction:
if (getAction())
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Constr"));
break;
}
}
bool CmdSketcherCreateRectangle::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
DEF_STD_CMD_AU(CmdSketcherCreateRectangleCenter)
CmdSketcherCreateRectangleCenter::CmdSketcherCreateRectangleCenter()
: Command("Sketcher_CreateRectangle_Center")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create centered rectangle");
sToolTipText = QT_TR_NOOP("Create a centered rectangle in the sketch");
sWhatsThis = "Sketcher_CreateRectangle_Center";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateRectangle_Center";
sAccel = "";
eType = ForEdit;
}
void CmdSketcherCreateRectangleCenter::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerBox(DrawSketchHandlerBox::CenterAndCorner) );
}
void CmdSketcherCreateRectangleCenter::updateAction(int mode)
{
switch (mode) {
case Normal:
if (getAction())
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Center"));
break;
case Construction:
if (getAction())
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Center_Constr"));
break;
}
}
bool CmdSketcherCreateRectangleCenter::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
/* Create rounded oblong =======================================================*/
class DrawSketchHandlerOblong : public DrawSketchHandler
{
public:
DrawSketchHandlerOblong()
: Mode(STATUS_SEEK_First)
, lengthX(0), lengthY(0), radius(0), signX(1), signY(1)
, EditCurve(37)
{
}
virtual ~DrawSketchHandlerOblong() {}
/// mode table
enum BoxMode {
STATUS_SEEK_First, /**< enum value ----. */
STATUS_SEEK_Second, /**< enum value ----. */
STATUS_End
};
virtual void activated(ViewProviderSketch*)
{
setCrosshairCursor("Sketcher_Pointer_Oblong");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
if (Mode == STATUS_SEEK_First) {
setPositionText(onSketchPos);
if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f, 0.f))) {
renderSuggestConstraintsCursor(sugConstr1);
return;
}
}
else if (Mode == STATUS_SEEK_Second) {
float distanceX = onSketchPos.x - StartPos.x;
float distanceY = onSketchPos.y - StartPos.y;
lengthX = distanceX; lengthY = distanceY;
signX = Base::sgn(distanceX);
signY = Base::sgn(distanceY);
if (fabs(distanceX) > fabs(distanceY)) {
radius = fabs(distanceY) / 4; // we use a fourth of the smaller distance as default radius
}
else {
radius = fabs(distanceX) / 4;
}
// we draw the lines with 36 segments, 8 for each arc and 4 lines
// draw the arcs
for (int i = 0; i < 8; i++) {
// calculate the x,y positions forming the the arc
double angle = i * M_PI / 16.0;
double x_i = -radius * sin(angle);
double y_i = -radius * cos(angle);
// we are drawing clockwise starting with the arc that is besides StartPos
if (signX == signY) {
EditCurve[i] = Base::Vector2d(StartPos.x + signX * (radius + x_i), StartPos.y + signY * (radius + y_i));
EditCurve[9 + i] = Base::Vector2d(StartPos.x + signY * (radius + y_i), StartPos.y + lengthY - signX * (radius + x_i));
EditCurve[18 + i] = Base::Vector2d(StartPos.x + lengthX - signX * (radius + x_i), StartPos.y + lengthY - signY * (radius + y_i));
EditCurve[27 + i] = Base::Vector2d(StartPos.x + lengthX - signY * (radius + y_i), StartPos.y + signX * (radius + x_i));
}
else {
EditCurve[i] = Base::Vector2d(StartPos.x - signY * (radius + y_i), StartPos.y - signX * (radius + x_i));
EditCurve[9 + i] = Base::Vector2d(StartPos.x + lengthX - signX * (radius + x_i), StartPos.y + signY * (radius + y_i));
EditCurve[18 + i] = Base::Vector2d(StartPos.x + lengthX + signY * (radius + y_i), StartPos.y + lengthY + signX * (radius + x_i));
EditCurve[27 + i] = Base::Vector2d(StartPos.x + signX * (radius + x_i), StartPos.y + lengthY - signY * (radius + y_i));
}
}
// draw the lines
if (signX == signY) {
EditCurve[8] = Base::Vector2d(StartPos.x, StartPos.y + (signY * radius));
EditCurve[17] = Base::Vector2d(StartPos.x + (signX * radius), StartPos.y + lengthY);
EditCurve[26] = Base::Vector2d(StartPos.x + lengthX, StartPos.y + lengthY - (signY * radius));
EditCurve[35] = Base::Vector2d(StartPos.x + lengthX - (signX * radius), StartPos.y);
}
else {
EditCurve[8] = Base::Vector2d(StartPos.x + (signX * radius), StartPos.y);
EditCurve[17] = Base::Vector2d(StartPos.x + lengthX, StartPos.y + (signY * radius));
EditCurve[26] = Base::Vector2d(StartPos.x + lengthX - (signX * radius), StartPos.y + lengthY);
EditCurve[35] = Base::Vector2d(StartPos.x, StartPos.y + lengthY - (signY * radius));
}
// close the curve
EditCurve[36] = EditCurve[0];
SbString text;
text.sprintf(" (%.1fR %.1fX %.1fY)", radius, lengthX, lengthY);
setPositionText(onSketchPos, text);
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f, 0.f))) {
renderSuggestConstraintsCursor(sugConstr2);
return;
}
}
applyCursor();
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
if (Mode == STATUS_SEEK_First) {
StartPos = onSketchPos;
Mode = STATUS_SEEK_Second;
}
else {
EndPos = onSketchPos;
Mode = STATUS_End;
}
return true;
}
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
if (Mode == STATUS_End) {
unsetCursor();
resetPositionText();
int firstCurve = getHighestCurveIndex() + 1;
// add the geometry to the sketch
// first determine the angles for the first arc
double start = 0;
double end = M_PI / 2;
if (signX > 0 && signY > 0) {
start = -2 * end;
end = -1 * end;
}
else if (signX > 0 && signY < 0) {
start = end;
end = 2 * end;
}
else if (signX < 0 && signY > 0) {
start = -1 * end;
end = 0;
}
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add rounded rectangle"));
Gui::Command::doCommand(Gui::Command::Doc,
// syntax for arcs: Part.ArcOfCircle(Part.Circle(center, axis, radius), startangle, endangle)
"geoList = []\n"
"geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n"
"geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n"
"geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n"
"geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n"
"geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n"
"geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n"
"geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n"
"geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n"
"%s.addGeometry(geoList, %s)\n"
"conList = []\n"
"conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n"
"conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n"
"conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n"
"conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n"
"conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n"
"conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n"
"conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n"
"conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n"
"conList.append(Sketcher.Constraint('Horizontal', %i))\n"
"conList.append(Sketcher.Constraint('Horizontal', %i))\n"
"conList.append(Sketcher.Constraint('Vertical', %i))\n"
"conList.append(Sketcher.Constraint('Vertical', %i))\n"
"conList.append(Sketcher.Constraint('Equal', %i, %i))\n"
"conList.append(Sketcher.Constraint('Equal', %i, %i))\n"
"conList.append(Sketcher.Constraint('Equal', %i, %i))\n"
"%s.addConstraint(conList)\n",
StartPos.x + (signX * radius), StartPos.y + (signY * radius), // center of the arc 1
radius,
start, end, // start and end angle of arc1
EditCurve[8].x, EditCurve[8].y, EditCurve[9].x, EditCurve[9].y, // line 1
signX == signY ? StartPos.x + (signX * radius) : StartPos.x + lengthX - (signX * radius), // center of the arc 2
signX == signY ? StartPos.y + lengthY - (signY * radius) : StartPos.y + (signY * radius),
radius,
// start and end angle of arc 2
// the logic is that end is start + M_PI / 2 and start is the previous end - M_PI
end - M_PI,
end - 0.5 * M_PI,
EditCurve[17].x, EditCurve[17].y, EditCurve[18].x, EditCurve[18].y, // line 2
StartPos.x + lengthX - (signX * radius), StartPos.y + lengthY - (signY * radius), // center of the arc 3
radius,
end - 1.5 * M_PI,
end - M_PI,
EditCurve[26].x, EditCurve[26].y, EditCurve[27].x, EditCurve[27].y, // line 3
signX == signY ? StartPos.x + lengthX - (signX * radius) : StartPos.x + (signX * radius), // center of the arc 4
signX == signY ? StartPos.y + (signY * radius) : StartPos.y + lengthY - (signY * radius),
radius,
end - 2 * M_PI,
end - 1.5 * M_PI,
EditCurve[35].x, EditCurve[35].y, EditCurve[36].x, EditCurve[36].y, // line 4
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch
geometryCreationMode == Construction ? "True" : "False", // geometry as construction or not
firstCurve, firstCurve + 1, // tangent 1
firstCurve + 1, firstCurve + 2, // tangent 2
firstCurve + 2, firstCurve + 3, // tangent 3
firstCurve + 3, firstCurve + 4, // tangent 4
firstCurve + 4, firstCurve + 5, // tangent 5
firstCurve + 5, firstCurve + 6, // tangent 6
firstCurve + 6, firstCurve + 7, // tangent 7
firstCurve + 7, firstCurve, // tangent 8
signX == signY ? firstCurve + 3 : firstCurve + 1, // horizontal constraint
signX == signY ? firstCurve + 7 : firstCurve + 5, // horizontal constraint
signX == signY ? firstCurve + 1 : firstCurve + 3, // vertical constraint
signX == signY ? firstCurve + 5 : firstCurve + 7, // vertical constraint
firstCurve, firstCurve + 2, // equal 1
firstCurve + 2, firstCurve + 4, // equal 2
firstCurve + 4, firstCurve + 6, // equal 3
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch
// now add construction geometry - two points used to take suggested constraints
Gui::Command::doCommand(Gui::Command::Doc,
"geoList = []\n"
"geoList.append(Part.Point(App.Vector(%f, %f, 0)))\n"
"geoList.append(Part.Point(App.Vector(%f, %f, 0)))\n"
"%s.addGeometry(geoList, True)\n" // geometry as construction
"conList = []\n"
"conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n"
"conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n"
"conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n"
"conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n"
"%s.addConstraint(conList)\n",
StartPos.x, StartPos.y, // point at StartPos
EndPos.x, EndPos.y, // point at EndPos
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch
firstCurve + 8, firstCurve + 1, // point on object constraint
firstCurve + 8, firstCurve + 7, // point on object constraint
firstCurve + 9, firstCurve + 3, // point on object constraint
firstCurve + 9, firstCurve + 5, // point on object constraint
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch
Gui::Command::commitCommand();
// add auto constraints at the StartPos auxiliary point
if (sugConstr1.size() > 0) {
createAutoConstraints(sugConstr1, getHighestCurveIndex() - 1, Sketcher::start);
sugConstr1.clear();
}
// add auto constraints at the EndPos auxiliary point
if (sugConstr2.size() > 0) {
createAutoConstraints(sugConstr2, getHighestCurveIndex(), Sketcher::start);
sugConstr2.clear();
}
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject*>(sketchgui->getObject()));
}
catch (const Base::Exception& e) {
Base::Console().Error("Failed to add rounded rectangle: %s\n", e.what());
Gui::Command::abortCommand();
tryAutoRecompute(static_cast<Sketcher::SketchObject*>(sketchgui->getObject()));
}
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode", true);
if (continuousMode) {
// This code enables the continuous creation mode.
Mode = STATUS_SEEK_First;
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
EditCurve.resize(37);
applyCursor();
/* this is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
}
else {
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
}
}
return true;
}
protected:
BoxMode Mode;
Base::Vector2d StartPos, EndPos;
double lengthX, lengthY, radius;
float signX, signY;
std::vector<Base::Vector2d> EditCurve;
std::vector<AutoConstraint> sugConstr1, sugConstr2;
};
DEF_STD_CMD_AU(CmdSketcherCreateOblong)
CmdSketcherCreateOblong::CmdSketcherCreateOblong()
: Command("Sketcher_CreateOblong")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create rounded rectangle");
sToolTipText = QT_TR_NOOP("Create a rounded rectangle in the sketch");
sWhatsThis = "Sketcher_CreateOblong";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateOblong";
sAccel = "";
eType = ForEdit;
}
void CmdSketcherCreateOblong::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerOblong());
}
void CmdSketcherCreateOblong::updateAction(int mode)
{
switch (mode) {
case Normal:
if (getAction())
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong"));
break;
case Construction:
if (getAction())
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong_Constr"));
break;
}
}
bool CmdSketcherCreateOblong::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
/* Rectangles Comp command =========================================*/
DEF_STD_CMD_ACLU(CmdSketcherCompCreateRectangles)
CmdSketcherCompCreateRectangles::CmdSketcherCompCreateRectangles()
: Command("Sketcher_CompCreateRectangles")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create rectangles");
sToolTipText = QT_TR_NOOP("Creates a rectangle in the sketch");
sWhatsThis = "Sketcher_CompCreateRectangles";
sStatusTip = sToolTipText;
eType = ForEdit;
}
void CmdSketcherCompCreateRectangles::activated(int iMsg)
{
if (iMsg == 0)
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBox(DrawSketchHandlerBox::Diagonal));
else if (iMsg == 1)
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBox(DrawSketchHandlerBox::CenterAndCorner));
else if (iMsg == 2)
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerOblong());
else
return;
// 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* CmdSketcherCompCreateRectangles::createAction(void)
{
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("Sketcher_CreateRectangle"));
QAction* arc2 = pcAction->addAction(QString());
arc2->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Center"));
QAction* arc3 = pcAction->addAction(QString());
arc3->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong"));
_pcAction = pcAction;
languageChange();
pcAction->setIcon(arc1->icon());
int defaultId = 0;
pcAction->setProperty("defaultAction", QVariant(defaultId));
return pcAction;
}
void CmdSketcherCompCreateRectangles::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 Normal:
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle"));
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Center"));
a[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong"));
getAction()->setIcon(a[index]->icon());
break;
case Construction:
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Constr"));
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Center_Constr"));
a[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong_Constr"));
getAction()->setIcon(a[index]->icon());
break;
}
}
void CmdSketcherCompCreateRectangles::languageChange()
{
Command::languageChange();
if (!_pcAction)
return;
Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
QList<QAction*> a = pcAction->actions();
QAction* rectangle1 = a[0];
rectangle1->setText(QApplication::translate("CmdSketcherCompCreateRectangles", "Rectangle"));
rectangle1->setToolTip(QApplication::translate("Sketcher_CreateRectangle", "Create a rectangle"));
rectangle1->setStatusTip(rectangle1->toolTip());
QAction* rectangle2 = a[1];
rectangle2->setText(QApplication::translate("CmdSketcherCompCreateRectangles", "Centered rectangle"));
rectangle2->setToolTip(QApplication::translate("Sketcher_CreateRectangle_Center", "Create a centered rectangle"));
rectangle2->setStatusTip(rectangle2->toolTip());
QAction* rectangle3 = a[2];
rectangle3->setText(QApplication::translate("CmdSketcherCompCreateRectangles", "Rounded rectangle"));
rectangle3->setToolTip(QApplication::translate("Sketcher_CreateOblong", "Create a rounded rectangle"));
rectangle3->setStatusTip(rectangle3->toolTip());
}
bool CmdSketcherCompCreateRectangles::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
// ======================================================================================
class DrawSketchHandlerLineSet: public DrawSketchHandler
{
public:
DrawSketchHandlerLineSet()
: Mode(STATUS_SEEK_First), SegmentMode(SEGMENT_MODE_Line)
, TransitionMode(TRANSITION_MODE_Free)
, SnapMode(SNAP_MODE_Free)
, suppressTransition(false)
, EditCurve(2)
, firstCurve(-1)
, previousCurve(-1)
, firstPosId(Sketcher::none)
, previousPosId(Sketcher::none)
, startAngle(0)
, endAngle(0)
, arcRadius(0)
, firstsegment(true)
{
}
virtual ~DrawSketchHandlerLineSet() {}
/// mode table
enum SELECT_MODE {
STATUS_SEEK_First, /**< enum value ----. */
STATUS_SEEK_Second, /**< enum value ----. */
STATUS_Do,
STATUS_Close
};
enum SEGMENT_MODE
{
SEGMENT_MODE_Arc,
SEGMENT_MODE_Line
};
enum TRANSITION_MODE
{
TRANSITION_MODE_Free,
TRANSITION_MODE_Tangent,
TRANSITION_MODE_Perpendicular_L,
TRANSITION_MODE_Perpendicular_R
};
enum SNAP_MODE
{
SNAP_MODE_Free,
SNAP_MODE_45Degree
};
virtual void registerPressedKey(bool pressed, int key)
{
if (Mode != STATUS_SEEK_Second)
return; // SegmentMode can be changed only in STATUS_SEEK_Second mode
if (key == SoKeyboardEvent::M && pressed && previousCurve != -1) {
// loop through the following modes:
// SEGMENT_MODE_Line, TRANSITION_MODE_Free / TRANSITION_MODE_Tangent
// SEGMENT_MODE_Line, TRANSITION_MODE_Perpendicular_L
// SEGMENT_MODE_Line, TRANSITION_MODE_Tangent / TRANSITION_MODE_Free
// SEGMENT_MODE_Arc, TRANSITION_MODE_Tangent
// SEGMENT_MODE_Arc, TRANSITION_MODE_Perpendicular_L
// SEGMENT_MODE_Arc, TRANSITION_MODE_Perpendicular_R
SnapMode = SNAP_MODE_Free;
Base::Vector2d onSketchPos;
if (SegmentMode == SEGMENT_MODE_Line)
onSketchPos = EditCurve[EditCurve.size()-1];
else
onSketchPos = EditCurve[29];
const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(previousCurve);
if (SegmentMode == SEGMENT_MODE_Line) {
switch (TransitionMode) {
case TRANSITION_MODE_Free:
if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { // 3rd mode
SegmentMode = SEGMENT_MODE_Arc;
TransitionMode = TRANSITION_MODE_Tangent;
}
else // 1st mode
TransitionMode = TRANSITION_MODE_Perpendicular_L;
break;
case TRANSITION_MODE_Perpendicular_L: // 2nd mode
if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId())
TransitionMode = TRANSITION_MODE_Free;
else
TransitionMode = TRANSITION_MODE_Tangent;
break;
case TRANSITION_MODE_Tangent:
if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) // 1st mode
TransitionMode = TRANSITION_MODE_Perpendicular_L;
else { // 3rd mode
SegmentMode = SEGMENT_MODE_Arc;
TransitionMode = TRANSITION_MODE_Tangent;
}
break;
default: // unexpected mode
TransitionMode = TRANSITION_MODE_Free;
break;
}
}
else {
switch (TransitionMode) {
case TRANSITION_MODE_Tangent: // 4th mode
TransitionMode = TRANSITION_MODE_Perpendicular_L;
break;
case TRANSITION_MODE_Perpendicular_L: // 5th mode
TransitionMode = TRANSITION_MODE_Perpendicular_R;
break;
default: // 6th mode (Perpendicular_R) + unexpected mode
SegmentMode = SEGMENT_MODE_Line;
if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId())
TransitionMode = TRANSITION_MODE_Tangent;
else
TransitionMode = TRANSITION_MODE_Free;
break;
}
}
if (SegmentMode == SEGMENT_MODE_Line)
EditCurve.resize(TransitionMode == TRANSITION_MODE_Free ? 2 : 3);
else
EditCurve.resize(32);
mouseMove(onSketchPos); // trigger an update of EditCurve
}
}
virtual void activated(ViewProviderSketch *)
{
setCrosshairCursor("Sketcher_Pointer_Create_Lineset");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
suppressTransition = false;
if (Mode==STATUS_SEEK_First) {
setPositionText(onSketchPos);
if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr1);
return;
}
}
else if (Mode==STATUS_SEEK_Second){
if (SegmentMode == SEGMENT_MODE_Line) {
EditCurve[EditCurve.size()-1] = onSketchPos;
if (TransitionMode == TRANSITION_MODE_Tangent) {
Base::Vector2d Tangent(dirVec.x,dirVec.y);
EditCurve[1].ProjectToLine(EditCurve[2] - EditCurve[0], Tangent);
if (EditCurve[1] * Tangent < 0) {
EditCurve[1] = EditCurve[2];
suppressTransition = true;
}
else
EditCurve[1] = EditCurve[0] + EditCurve[1];
}
else if (TransitionMode == TRANSITION_MODE_Perpendicular_L ||
TransitionMode == TRANSITION_MODE_Perpendicular_R) {
Base::Vector2d Perpendicular(-dirVec.y,dirVec.x);
EditCurve[1].ProjectToLine(EditCurve[2] - EditCurve[0], Perpendicular);
EditCurve[1] = EditCurve[0] + EditCurve[1];
}
sketchgui->drawEdit(EditCurve);
float length = (EditCurve[1] - EditCurve[0]).Length();
float angle = (EditCurve[1] - EditCurve[0]).GetAngle(Base::Vector2d(1.f,0.f));
SbString text;
text.sprintf(" (%.1f,%.1fdeg)", length, angle * 180 / M_PI);
setPositionText(EditCurve[1], text);
if (TransitionMode == TRANSITION_MODE_Free) {
if (seekAutoConstraint(sugConstr2, onSketchPos, onSketchPos - EditCurve[0])) {
renderSuggestConstraintsCursor(sugConstr2);
return;
}
}
}
else if (SegmentMode == SEGMENT_MODE_Arc) {
if(QApplication::keyboardModifiers() == Qt::ControlModifier)
SnapMode = SNAP_MODE_45Degree;
else
SnapMode = SNAP_MODE_Free;
Base::Vector2d Tangent;
if (TransitionMode == TRANSITION_MODE_Tangent)
Tangent = Base::Vector2d(dirVec.x,dirVec.y);
else if (TransitionMode == TRANSITION_MODE_Perpendicular_L)
Tangent = Base::Vector2d(-dirVec.y,dirVec.x);
else if (TransitionMode == TRANSITION_MODE_Perpendicular_R)
Tangent = Base::Vector2d(dirVec.y,-dirVec.x);
double theta = Tangent.GetAngle(onSketchPos - EditCurve[0]);
arcRadius = (onSketchPos - EditCurve[0]).Length()/(2.0*sin(theta));
// At this point we need a unit normal vector pointing towards
// the center of the arc we are drawing. Derivation of the formula
// used here can be found at http://people.richland.edu/james/lecture/m116/matrices/area.html
double x1 = EditCurve[0].x;
double y1 = EditCurve[0].y;
double x2 = x1 + Tangent.x;
double y2 = y1 + Tangent.y;
double x3 = onSketchPos.x;
double y3 = onSketchPos.y;
if ((x2*y3-x3*y2)-(x1*y3-x3*y1)+(x1*y2-x2*y1) > 0)
arcRadius *= -1;
if (boost::math::isnan(arcRadius) || boost::math::isinf(arcRadius))
arcRadius = 0.f;
CenterPoint = EditCurve[0] + Base::Vector2d(arcRadius * Tangent.y, -arcRadius * Tangent.x);
double rx = EditCurve[0].x - CenterPoint.x;
double ry = EditCurve[0].y - CenterPoint.y;
startAngle = atan2(ry,rx);
double rxe = onSketchPos.x - CenterPoint.x;
double rye = onSketchPos.y - CenterPoint.y;
double arcAngle = atan2(-rxe*ry + rye*rx, rxe*rx + rye*ry);
if (boost::math::isnan(arcAngle) || boost::math::isinf(arcAngle))
arcAngle = 0.f;
if (arcRadius >= 0 && arcAngle > 0)
arcAngle -= 2*M_PI;
if (arcRadius < 0 && arcAngle < 0)
arcAngle += 2*M_PI;
if (SnapMode == SNAP_MODE_45Degree)
arcAngle = round(arcAngle / (M_PI/4)) * M_PI/4;
endAngle = startAngle + arcAngle;
for (int i=1; i <= 29; i++) {
double angle = i*arcAngle/29.0;
double dx = rx * cos(angle) - ry * sin(angle);
double dy = rx * sin(angle) + ry * cos(angle);
EditCurve[i] = Base::Vector2d(CenterPoint.x + dx, CenterPoint.y + dy);
}
EditCurve[30] = CenterPoint;
EditCurve[31] = EditCurve[0];
sketchgui->drawEdit(EditCurve);
SbString text;
text.sprintf(" (%.1fR,%.1fdeg)", std::abs(arcRadius), arcAngle * 180 / M_PI);
setPositionText(onSketchPos, text);
if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr2);
return;
}
}
}
applyCursor();
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
if (Mode == STATUS_SEEK_First) {
EditCurve[0] = onSketchPos; // this may be overwritten if previousCurve is found
virtualsugConstr1 = sugConstr1; // store original autoconstraints.
// here we check if there is a preselected point and
// we set up a transition from the neighbouring segment.
// (peviousCurve, previousPosId, dirVec, TransitionMode)
for (unsigned int i=0; i < sugConstr1.size(); i++)
if (sugConstr1[i].Type == Sketcher::Coincident) {
const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(sugConstr1[i].GeoId);
if ((geom->getTypeId() == Part::GeomLineSegment::getClassTypeId() ||
geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) &&
(sugConstr1[i].PosId == Sketcher::start ||
sugConstr1[i].PosId == Sketcher::end)) {
previousCurve = sugConstr1[i].GeoId;
previousPosId = sugConstr1[i].PosId;
updateTransitionData(previousCurve,previousPosId); // -> dirVec, EditCurve[0]
if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) {
TransitionMode = TRANSITION_MODE_Tangent;
SnapMode = SNAP_MODE_Free;
}
sugConstr1.erase(sugConstr1.begin()+i); // actually we should clear the vector completely
break;
}
}
// remember our first point (even if we are doing a transition from a previous curve)
firstCurve = getHighestCurveIndex() + 1;
firstPosId = Sketcher::start;
if (SegmentMode == SEGMENT_MODE_Line)
EditCurve.resize(TransitionMode == TRANSITION_MODE_Free ? 2 : 3);
else if (SegmentMode == SEGMENT_MODE_Arc)
EditCurve.resize(32);
Mode = STATUS_SEEK_Second;
}
else if (Mode == STATUS_SEEK_Second) {
// exit on clicking exactly at the same position (e.g. double click)
if (onSketchPos == EditCurve[0]) {
unsetCursor();
resetPositionText();
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if(continuousMode){
// This code enables the continuous creation mode.
Mode=STATUS_SEEK_First;
SegmentMode=SEGMENT_MODE_Line;
TransitionMode=TRANSITION_MODE_Free;
SnapMode = SNAP_MODE_Free;
suppressTransition=false;
firstCurve=-1;
previousCurve=-1;
firstPosId=Sketcher::none;
previousPosId=Sketcher::none;
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
EditCurve.resize(2);
applyCursor();
/* this is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
return true;
}
else{
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
return true;
}
}
Mode = STATUS_Do;
if (sketchgui->getPreselectPoint() != -1 && firstPosId != Sketcher::none) {
int GeoId;
Sketcher::PointPos PosId;
sketchgui->getSketchObject()->getGeoVertexIndex(sketchgui->getPreselectPoint(),GeoId,PosId);
if (sketchgui->getSketchObject()->arePointsCoincident(GeoId,PosId,firstCurve,firstPosId))
Mode = STATUS_Close;
}
else if (sketchgui->getPreselectCross() == 0 && firstPosId != Sketcher::none) {
// close line started at root point
if (sketchgui->getSketchObject()->arePointsCoincident(-1,Sketcher::start,firstCurve,firstPosId))
Mode = STATUS_Close;
}
}
return true;
}
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
if (Mode == STATUS_Do || Mode == STATUS_Close) {
bool addedGeometry = true;
if (SegmentMode == SEGMENT_MODE_Line) {
// issue the geometry
try {
// open the transaction
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add line to sketch wire"));
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)),%s)",
EditCurve[0].x,EditCurve[0].y,EditCurve[1].x,EditCurve[1].y,
geometryCreationMode==Construction?"True":"False");
}
catch (const Base::Exception& e) {
addedGeometry = false;
Base::Console().Error("Failed to add line: %s\n", e.what());
Gui::Command::abortCommand();
}
firstsegment=false;
}
else if (SegmentMode == SEGMENT_MODE_Arc) { // We're dealing with an Arc
if (!boost::math::isnormal(arcRadius)) {
Mode = STATUS_SEEK_Second;
return true;
}
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add arc to sketch wire"));
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.ArcOfCircle"
"(Part.Circle(App.Vector(%f,%f,0),App.Vector(0,0,1),%f),%f,%f),%s)",
CenterPoint.x, CenterPoint.y, std::abs(arcRadius),
std::min(startAngle,endAngle), std::max(startAngle,endAngle),
geometryCreationMode==Construction?"True":"False");
}
catch (const Base::Exception& e) {
addedGeometry = false;
Base::Console().Error("Failed to add arc: %s\n", e.what());
Gui::Command::abortCommand();
}
firstsegment=false;
}
int lastCurve = getHighestCurveIndex();
// issue the constraint
if (addedGeometry && (previousPosId != Sketcher::none)) {
Sketcher::PointPos lastStartPosId = (SegmentMode == SEGMENT_MODE_Arc && startAngle > endAngle) ?
Sketcher::end : Sketcher::start;
Sketcher::PointPos lastEndPosId = (SegmentMode == SEGMENT_MODE_Arc && startAngle > endAngle) ?
Sketcher::start : Sketcher::end;
// in case of a tangency constraint, the coincident constraint is redundant
std::string constrType = "Coincident";
if (!suppressTransition && previousCurve != -1) {
if (TransitionMode == TRANSITION_MODE_Tangent)
constrType = "Tangent";
else if (TransitionMode == TRANSITION_MODE_Perpendicular_L ||
TransitionMode == TRANSITION_MODE_Perpendicular_R)
constrType = "Perpendicular";
}
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('%s',%i,%i,%i,%i)) ",
constrType.c_str(), previousCurve, previousPosId, lastCurve, lastStartPosId);
if(SnapMode == SNAP_MODE_45Degree && Mode != STATUS_Close) {
// -360, -315, -270, -225, -180, -135, -90, -45, 0, 45, 90, 135, 180, 225, 270, 315, 360
// N/A, a, perp, a, par, a,perp, a,N/A, a,perp, a, par, a,perp, a, N/A
// #3974: if in radians, the printf %f defaults to six decimals, which leads to loss of precision
double arcAngle = abs(round( (endAngle - startAngle) / (M_PI/4)) * 45); // in degrees
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Angle',%i,App.Units.Quantity('%f deg'))) ",
lastCurve, arcAngle);
}
if (Mode == STATUS_Close) {
// close the loop by constrain to the first curve point
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Coincident',%i,%i,%i,%i)) ",
lastCurve,lastEndPosId,firstCurve,firstPosId);
}
Gui::Command::commitCommand();
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
}
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool avoidredundant = sketchgui->AvoidRedundant.getValue() && sketchgui->Autoconstraints.getValue();
if (Mode == STATUS_Close) {
if(avoidredundant) {
if (SegmentMode == SEGMENT_MODE_Line) { // avoid redundant constraints.
if (sugConstr1.size() > 0)
removeRedundantHorizontalVertical(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()),sugConstr1,sugConstr2);
else
removeRedundantHorizontalVertical(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()),virtualsugConstr1,sugConstr2);
}
}
if (sugConstr2.size() > 0) {
// exclude any coincidence constraints
std::vector<AutoConstraint> sugConstr;
for (unsigned int i=0; i < sugConstr2.size(); i++) {
if (sugConstr2[i].Type != Sketcher::Coincident)
sugConstr.push_back(sugConstr2[i]);
}
createAutoConstraints(sugConstr, getHighestCurveIndex(), Sketcher::end);
sugConstr2.clear();
}
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
unsetCursor();
resetPositionText();
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if(continuousMode){
// This code enables the continuous creation mode.
Mode=STATUS_SEEK_First;
SegmentMode=SEGMENT_MODE_Line;
TransitionMode=TRANSITION_MODE_Free;
SnapMode = SNAP_MODE_Free;
suppressTransition=false;
firstCurve=-1;
previousCurve=-1;
firstPosId=Sketcher::none;
previousPosId=Sketcher::none;
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
EditCurve.resize(2);
applyCursor();
/* this is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
}
else{
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
}
}
else {
Gui::Command::commitCommand();
// Add auto constraints
if (sugConstr1.size() > 0) { // this is relevant only to the very first point
createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::start);
sugConstr1.clear();
}
if(avoidredundant) {
if (SegmentMode == SEGMENT_MODE_Line) { // avoid redundant constraints.
if (sugConstr1.size() > 0)
removeRedundantHorizontalVertical(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()),sugConstr1,sugConstr2);
else
removeRedundantHorizontalVertical(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()),virtualsugConstr1,sugConstr2);
}
}
virtualsugConstr1 = sugConstr2; // these are the initial constraints for the next iteration.
if (sugConstr2.size() > 0) {
createAutoConstraints(sugConstr2, getHighestCurveIndex(),
(SegmentMode == SEGMENT_MODE_Arc && startAngle > endAngle) ?
Sketcher::start : Sketcher::end);
sugConstr2.clear();
}
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
// remember the vertex for the next rounds constraint..
previousCurve = getHighestCurveIndex();
previousPosId = (SegmentMode == SEGMENT_MODE_Arc && startAngle > endAngle) ?
Sketcher::start : Sketcher::end; // cw arcs are rendered in reverse
// setup for the next line segment
// calculate dirVec and EditCurve[0]
updateTransitionData(previousCurve,previousPosId);
applyCursor();
Mode = STATUS_SEEK_Second;
if (SegmentMode == SEGMENT_MODE_Arc) {
TransitionMode = TRANSITION_MODE_Tangent;
EditCurve.resize(3);
EditCurve[2] = EditCurve[0];
}
else {
TransitionMode = TRANSITION_MODE_Free;
EditCurve.resize(2);
}
SegmentMode = SEGMENT_MODE_Line;
SnapMode = SNAP_MODE_Free;
EditCurve[1] = EditCurve[0];
mouseMove(onSketchPos); // trigger an update of EditCurve
}
}
return true;
}
virtual void quit(void) {
// We must see if we need to create a B-spline before cancelling everything
// and now just like any other Handler,
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if (firstsegment) {
// user when right-clicking with no segment in really wants to exit
DrawSketchHandler::quit();
}
else {
if(!continuousMode){
DrawSketchHandler::quit();
}
else {
// This code disregards existing data and enables the continuous creation mode.
Mode=STATUS_SEEK_First;
SegmentMode=SEGMENT_MODE_Line;
TransitionMode=TRANSITION_MODE_Free;
SnapMode = SNAP_MODE_Free;
suppressTransition=false;
firstCurve=-1;
previousCurve=-1;
firstPosId=Sketcher::none;
previousPosId=Sketcher::none;
firstsegment=true;
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
EditCurve.resize(2);
applyCursor();
}
}
}
protected:
SELECT_MODE Mode;
SEGMENT_MODE SegmentMode;
TRANSITION_MODE TransitionMode;
SNAP_MODE SnapMode;
bool suppressTransition;
std::vector<Base::Vector2d> EditCurve;
int firstCurve;
int previousCurve;
Sketcher::PointPos firstPosId;
Sketcher::PointPos previousPosId;
// the latter stores those constraints that a first point would have been given in absence of the transition mechanism
std::vector<AutoConstraint> sugConstr1, sugConstr2, virtualsugConstr1;
Base::Vector2d CenterPoint;
Base::Vector3d dirVec;
double startAngle, endAngle, arcRadius;
bool firstsegment;
void updateTransitionData(int GeoId, Sketcher::PointPos PosId) {
// Use updated startPoint/endPoint as autoconstraints can modify the position
const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(GeoId);
if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) {
const Part::GeomLineSegment *lineSeg = static_cast<const Part::GeomLineSegment *>(geom);
dirVec.Set(lineSeg->getEndPoint().x - lineSeg->getStartPoint().x,
lineSeg->getEndPoint().y - lineSeg->getStartPoint().y,
0.f);
if (PosId == Sketcher::start) {
dirVec *= -1;
EditCurve[0] = Base::Vector2d(lineSeg->getStartPoint().x, lineSeg->getStartPoint().y);
}
else
EditCurve[0] = Base::Vector2d(lineSeg->getEndPoint().x, lineSeg->getEndPoint().y);
}
else if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) {
const Part::GeomArcOfCircle *arcSeg = static_cast<const Part::GeomArcOfCircle *>(geom);
if (PosId == Sketcher::start) {
EditCurve[0] = Base::Vector2d(arcSeg->getStartPoint(/*emulateCCW=*/true).x,arcSeg->getStartPoint(/*emulateCCW=*/true).y);
dirVec = Base::Vector3d(0.f,0.f,-1.0) % (arcSeg->getStartPoint(/*emulateCCW=*/true)-arcSeg->getCenter());
}
else {
EditCurve[0] = Base::Vector2d(arcSeg->getEndPoint(/*emulateCCW=*/true).x,arcSeg->getEndPoint(/*emulateCCW=*/true).y);
dirVec = Base::Vector3d(0.f,0.f,1.0) % (arcSeg->getEndPoint(/*emulateCCW=*/true)-arcSeg->getCenter());
}
}
dirVec.Normalize();
}
};
DEF_STD_CMD_AU(CmdSketcherCreatePolyline)
CmdSketcherCreatePolyline::CmdSketcherCreatePolyline()
: Command("Sketcher_CreatePolyline")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create polyline");
sToolTipText = QT_TR_NOOP("Create a polyline in the sketch. 'M' Key cycles behaviour");
sWhatsThis = "Sketcher_CreatePolyline";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreatePolyline";
eType = ForEdit;
}
void CmdSketcherCreatePolyline::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerLineSet() );
}
void CmdSketcherCreatePolyline::updateAction(int mode)
{
switch (mode) {
case Normal:
if (getAction())
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreatePolyline"));
break;
case Construction:
if (getAction())
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreatePolyline_Constr"));
break;
}
}
bool CmdSketcherCreatePolyline::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
// ======================================================================================
class DrawSketchHandlerArc : public DrawSketchHandler
{
public:
DrawSketchHandlerArc()
: Mode(STATUS_SEEK_First)
, EditCurve(2)
, rx(0), ry(0)
, startAngle(0)
, endAngle(0)
, arcAngle(0)
{
}
virtual ~DrawSketchHandlerArc(){}
/// mode table
enum SelectMode {
STATUS_SEEK_First, /**< enum value ----. */
STATUS_SEEK_Second, /**< enum value ----. */
STATUS_SEEK_Third, /**< enum value ----. */
STATUS_End
};
virtual void activated(ViewProviderSketch *)
{
setCrosshairCursor("Sketcher_Pointer_Create_Arc");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_First) {
setPositionText(onSketchPos);
if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr1);
return;
}
}
else if (Mode==STATUS_SEEK_Second) {
double dx_ = onSketchPos.x - EditCurve[0].x;
double dy_ = onSketchPos.y - EditCurve[0].y;
for (int i=0; i < 16; i++) {
double angle = i*M_PI/16.0;
double dx = dx_ * cos(angle) + dy_ * sin(angle);
double dy = -dx_ * sin(angle) + dy_ * cos(angle);
EditCurve[1+i] = Base::Vector2d(EditCurve[0].x + dx, EditCurve[0].y + dy);
EditCurve[17+i] = Base::Vector2d(EditCurve[0].x - dx, EditCurve[0].y - dy);
}
EditCurve[33] = EditCurve[1];
// Display radius and start angle
float radius = (onSketchPos - EditCurve[0]).Length();
float angle = atan2f(dy_ , dx_);
SbString text;
text.sprintf(" (%.1fR,%.1fdeg)", radius, angle * 180 / M_PI);
setPositionText(onSketchPos, text);
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr2);
return;
}
}
else if (Mode==STATUS_SEEK_Third) {
double angle1 = atan2(onSketchPos.y - CenterPoint.y,
onSketchPos.x - CenterPoint.x) - startAngle;
double angle2 = angle1 + (angle1 < 0. ? 2 : -2) * M_PI ;
arcAngle = abs(angle1-arcAngle) < abs(angle2-arcAngle) ? angle1 : angle2;
for (int i=1; i <= 29; i++) {
double angle = i*arcAngle/29.0;
double dx = rx * cos(angle) - ry * sin(angle);
double dy = rx * sin(angle) + ry * cos(angle);
EditCurve[i] = Base::Vector2d(CenterPoint.x + dx, CenterPoint.y + dy);
}
// Display radius and arc angle
float radius = (onSketchPos - EditCurve[0]).Length();
SbString text;
text.sprintf(" (%.1fR,%.1fdeg)", radius, arcAngle * 180 / M_PI);
setPositionText(onSketchPos, text);
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.0,0.0))) {
renderSuggestConstraintsCursor(sugConstr3);
return;
}
}
applyCursor();
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_First){
CenterPoint = onSketchPos;
EditCurve.resize(34);
EditCurve[0] = onSketchPos;
Mode = STATUS_SEEK_Second;
}
else if (Mode==STATUS_SEEK_Second){
EditCurve.resize(31);
EditCurve[0] = onSketchPos;
EditCurve[30] = CenterPoint;
rx = EditCurve[0].x - CenterPoint.x;
ry = EditCurve[0].y - CenterPoint.y;
startAngle = atan2(ry, rx);
arcAngle = 0.;
Mode = STATUS_SEEK_Third;
}
else {
EditCurve.resize(30);
double angle1 = atan2(onSketchPos.y - CenterPoint.y,
onSketchPos.x - CenterPoint.x) - startAngle;
double angle2 = angle1 + (angle1 < 0. ? 2 : -2) * M_PI ;
arcAngle = abs(angle1-arcAngle) < abs(angle2-arcAngle) ? angle1 : angle2;
if (arcAngle > 0)
endAngle = startAngle + arcAngle;
else {
endAngle = startAngle;
startAngle += arcAngle;
}
sketchgui->drawEdit(EditCurve);
applyCursor();
Mode = STATUS_End;
}
return true;
}
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
if (Mode==STATUS_End) {
unsetCursor();
resetPositionText();
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch arc"));
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.ArcOfCircle"
"(Part.Circle(App.Vector(%f,%f,0),App.Vector(0,0,1),%f),%f,%f),%s)",
CenterPoint.x, CenterPoint.y, sqrt(rx*rx + ry*ry),
startAngle, endAngle,
geometryCreationMode==Construction?"True":"False"); //arcAngle > 0 ? 0 : 1);
Gui::Command::commitCommand();
}
catch (const Base::Exception& e) {
Base::Console().Error("Failed to add arc: %s\n", e.what());
Gui::Command::abortCommand();
}
// Auto Constraint center point
if (sugConstr1.size() > 0) {
createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::mid);
sugConstr1.clear();
}
// Auto Constraint first picked point
if (sugConstr2.size() > 0) {
createAutoConstraints(sugConstr2, getHighestCurveIndex(), (arcAngle > 0) ? Sketcher::start : Sketcher::end );
sugConstr2.clear();
}
// Auto Constraint second picked point
if (sugConstr3.size() > 0) {
createAutoConstraints(sugConstr3, getHighestCurveIndex(), (arcAngle > 0) ? Sketcher::end : Sketcher::start);
sugConstr3.clear();
}
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if(continuousMode){
// This code enables the continuous creation mode.
Mode=STATUS_SEEK_First;
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
EditCurve.resize(2);
applyCursor();
/* this is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
}
else{
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
}
}
return true;
}
protected:
SelectMode Mode;
std::vector<Base::Vector2d> EditCurve;
Base::Vector2d CenterPoint;
double rx, ry, startAngle, endAngle, arcAngle;
std::vector<AutoConstraint> sugConstr1, sugConstr2, sugConstr3;
};
DEF_STD_CMD_A(CmdSketcherCreateArc)
CmdSketcherCreateArc::CmdSketcherCreateArc()
: Command("Sketcher_CreateArc")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create arc by center");
sToolTipText = QT_TR_NOOP("Create an arc by its center and by its end points");
sWhatsThis = "Sketcher_CreateArc";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateArc";
eType = ForEdit;
}
void CmdSketcherCreateArc::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerArc() );
}
bool CmdSketcherCreateArc::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
// ======================================================================================
class DrawSketchHandler3PointArc : public DrawSketchHandler
{
public:
DrawSketchHandler3PointArc()
: Mode(STATUS_SEEK_First), EditCurve(2)
, radius(0), startAngle(0)
, endAngle(0), arcAngle(0)
, arcPos1(Sketcher::none)
, arcPos2(Sketcher::none)
{
}
virtual ~DrawSketchHandler3PointArc(){}
/// mode table
enum SelectMode {
STATUS_SEEK_First, /**< enum value ----. */
STATUS_SEEK_Second, /**< enum value ----. */
STATUS_SEEK_Third, /**< enum value ----. */
STATUS_End
};
virtual void activated(ViewProviderSketch *)
{
setCrosshairCursor("Sketcher_Pointer_Create_3PointArc");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_First) {
setPositionText(onSketchPos);
if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr1);
return;
}
}
else if (Mode==STATUS_SEEK_Second) {
CenterPoint = EditCurve[0] = (onSketchPos - FirstPoint)/2 + FirstPoint;
EditCurve[1] = EditCurve[33] = onSketchPos;
radius = (onSketchPos - CenterPoint).Length();
double lineAngle = GetPointAngle(CenterPoint, onSketchPos);
// Build a 32 point circle ignoring already constructed points
for (int i=1; i <= 32; i++) {
// Start at current angle
double angle = (i-1)*2*M_PI/32.0 + lineAngle; // N point closed circle has N segments
if (i != 1 && i != 17 ) {
EditCurve[i] = Base::Vector2d(CenterPoint.x + radius*cos(angle),
CenterPoint.y + radius*sin(angle));
}
}
// Display radius and start angle
// This lineAngle will report counter-clockwise from +X, not relatively
SbString text;
text.sprintf(" (%.1fR,%.1fdeg)", (float) radius, (float) lineAngle * 180 / M_PI);
setPositionText(onSketchPos, text);
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr2);
return;
}
}
else if (Mode==STATUS_SEEK_Third) {
/*
Centerline inverts when the arc flips sides. Easily taken care of by replacing
centerline with a point. It happens because the direction the curve is being drawn
reverses.
*/
try {
CenterPoint = EditCurve[30] = GetCircleCenter(FirstPoint, SecondPoint, onSketchPos);
radius = (SecondPoint - CenterPoint).Length();
double angle1 = GetPointAngle(CenterPoint, FirstPoint);
double angle2 = GetPointAngle(CenterPoint, SecondPoint);
double angle3 = GetPointAngle(CenterPoint, onSketchPos);
// Always build arc counter-clockwise
// Point 3 is between Point 1 and 2
if ( angle3 > min(angle1, angle2) && angle3 < max(angle1, angle2) ) {
if (angle2 > angle1) {
EditCurve[0] = FirstPoint;
EditCurve[29] = SecondPoint;
arcPos1 = Sketcher::start;
arcPos2 = Sketcher::end;
}
else {
EditCurve[0] = SecondPoint;
EditCurve[29] = FirstPoint;
arcPos1 = Sketcher::end;
arcPos2 = Sketcher::start;
}
startAngle = min(angle1, angle2);
endAngle = max(angle1, angle2);
arcAngle = endAngle - startAngle;
}
// Point 3 is not between Point 1 and 2
else {
if (angle2 > angle1) {
EditCurve[0] = SecondPoint;
EditCurve[29] = FirstPoint;
arcPos1 = Sketcher::end;
arcPos2 = Sketcher::start;
}
else {
EditCurve[0] = FirstPoint;
EditCurve[29] = SecondPoint;
arcPos1 = Sketcher::start;
arcPos2 = Sketcher::end;
}
startAngle = max(angle1, angle2);
endAngle = min(angle1, angle2);
arcAngle = 2*M_PI - (startAngle - endAngle);
}
// Build a 30 point circle ignoring already constructed points
for (int i=1; i <= 28; i++) {
double angle = startAngle + i*arcAngle/29.0; // N point arc has N-1 segments
EditCurve[i] = Base::Vector2d(CenterPoint.x + radius*cos(angle),
CenterPoint.y + radius*sin(angle));
}
SbString text;
text.sprintf(" (%.1fR,%.1fdeg)", (float) radius, (float) arcAngle * 180 / M_PI);
setPositionText(onSketchPos, text);
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.0,0.0),
AutoConstraint::CURVE)) {
renderSuggestConstraintsCursor(sugConstr3);
return;
}
}
catch(Base::ValueError &e) {
e.ReportException();
}
}
applyCursor();
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_First){
// 32 point curve + center + endpoint
EditCurve.resize(34);
// 17 is circle halfway point (1+32/2)
FirstPoint = EditCurve[17] = onSketchPos;
Mode = STATUS_SEEK_Second;
}
else if (Mode==STATUS_SEEK_Second){
// 30 point arc and center point
EditCurve.resize(31);
SecondPoint = onSketchPos;
Mode = STATUS_SEEK_Third;
}
else {
EditCurve.resize(30);
sketchgui->drawEdit(EditCurve);
applyCursor();
Mode = STATUS_End;
}
return true;
}
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
// Need to look at. rx might need fixing.
if (Mode==STATUS_End) {
unsetCursor();
resetPositionText();
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch arc"));
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.ArcOfCircle"
"(Part.Circle(App.Vector(%f,%f,0),App.Vector(0,0,1),%f),%f,%f),%s)",
CenterPoint.x, CenterPoint.y, radius,
startAngle, endAngle,
geometryCreationMode==Construction?"True":"False");
Gui::Command::commitCommand();
}
catch (const Base::Exception& e) {
Base::Console().Error("Failed to add arc: %s\n", e.what());
Gui::Command::abortCommand();
}
// Auto Constraint first picked point
if (sugConstr1.size() > 0) {
createAutoConstraints(sugConstr1, getHighestCurveIndex(), arcPos1);
sugConstr1.clear();
}
// Auto Constraint second picked point
if (sugConstr2.size() > 0) {
createAutoConstraints(sugConstr2, getHighestCurveIndex(), arcPos2);
sugConstr2.clear();
}
// Auto Constraint third picked point
if (sugConstr3.size() > 0) {
createAutoConstraints(sugConstr3, getHighestCurveIndex(), Sketcher::none);
sugConstr3.clear();
}
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if(continuousMode){
// This code enables the continuous creation mode.
Mode=STATUS_SEEK_First;
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
EditCurve.resize(2);
applyCursor();
/* this is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
}
else{
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
}
}
return true;
}
protected:
SelectMode Mode;
std::vector<Base::Vector2d> EditCurve;
Base::Vector2d CenterPoint, FirstPoint, SecondPoint;
double radius, startAngle, endAngle, arcAngle;
std::vector<AutoConstraint> sugConstr1, sugConstr2, sugConstr3;
Sketcher::PointPos arcPos1, arcPos2;
};
DEF_STD_CMD_A(CmdSketcherCreate3PointArc)
CmdSketcherCreate3PointArc::CmdSketcherCreate3PointArc()
: Command("Sketcher_Create3PointArc")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create arc by three points");
sToolTipText = QT_TR_NOOP("Create an arc by its end points and a point along the arc");
sWhatsThis = "Sketcher_Create3PointArc";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_Create3PointArc";
eType = ForEdit;
}
void CmdSketcherCreate3PointArc::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandler3PointArc() );
}
bool CmdSketcherCreate3PointArc::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
DEF_STD_CMD_ACLU(CmdSketcherCompCreateArc)
CmdSketcherCompCreateArc::CmdSketcherCompCreateArc()
: Command("Sketcher_CompCreateArc")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create arc");
sToolTipText = QT_TR_NOOP("Create an arc in the sketcher");
sWhatsThis = "Sketcher_CompCreateArc";
sStatusTip = sToolTipText;
eType = ForEdit;
}
void CmdSketcherCompCreateArc::activated(int iMsg)
{
if (iMsg==0)
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerArc());
else if (iMsg==1)
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandler3PointArc());
else
return;
// 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 * CmdSketcherCompCreateArc::createAction(void)
{
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("Sketcher_CreateArc"));
QAction* arc2 = pcAction->addAction(QString());
arc2->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Create3PointArc"));
_pcAction = pcAction;
languageChange();
pcAction->setIcon(arc1->icon());
int defaultId = 0;
pcAction->setProperty("defaultAction", QVariant(defaultId));
return pcAction;
}
void CmdSketcherCompCreateArc::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 Normal:
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateArc"));
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Create3PointArc"));
getAction()->setIcon(a[index]->icon());
break;
case Construction:
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateArc_Constr"));
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Create3PointArc_Constr"));
getAction()->setIcon(a[index]->icon());
break;
}
}
void CmdSketcherCompCreateArc::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("CmdSketcherCompCreateArc","Center and end points"));
arc1->setToolTip(QApplication::translate("Sketcher_CreateArc","Create an arc by its center and by its end points"));
arc1->setStatusTip(QApplication::translate("Sketcher_CreateArc","Create an arc by its center and by its end points"));
QAction* arc2 = a[1];
arc2->setText(QApplication::translate("CmdSketcherCompCreateArc","End points and rim point"));
arc2->setToolTip(QApplication::translate("Sketcher_Create3PointArc","Create an arc by its end points and a point along the arc"));
arc2->setStatusTip(QApplication::translate("Sketcher_Create3PointArc","Create an arc by its end points and a point along the arc"));
}
bool CmdSketcherCompCreateArc::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
// ======================================================================================
class DrawSketchHandlerCircle : public DrawSketchHandler
{
public:
DrawSketchHandlerCircle() : Mode(STATUS_SEEK_First),EditCurve(34){}
virtual ~DrawSketchHandlerCircle(){}
/// mode table
enum SelectMode {
STATUS_SEEK_First, /**< enum value ----. */
STATUS_SEEK_Second, /**< enum value ----. */
STATUS_Close
};
virtual void activated(ViewProviderSketch *)
{
setCrosshairCursor("Sketcher_Pointer_Create_Circle");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_First) {
setPositionText(onSketchPos);
if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr1);
return;
}
}
else if (Mode==STATUS_SEEK_Second) {
double rx0 = onSketchPos.x - EditCurve[0].x;
double ry0 = onSketchPos.y - EditCurve[0].y;
for (int i=0; i < 16; i++) {
double angle = i*M_PI/16.0;
double rx = rx0 * cos(angle) + ry0 * sin(angle);
double ry = -rx0 * sin(angle) + ry0 * cos(angle);
EditCurve[1+i] = Base::Vector2d(EditCurve[0].x + rx, EditCurve[0].y + ry);
EditCurve[17+i] = Base::Vector2d(EditCurve[0].x - rx, EditCurve[0].y - ry);
}
EditCurve[33] = EditCurve[1];
// Display radius for user
float radius = (onSketchPos - EditCurve[0]).Length();
SbString text;
text.sprintf(" (%.1fR)", radius);
setPositionText(onSketchPos, text);
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr2, onSketchPos, onSketchPos - EditCurve[0],
AutoConstraint::CURVE)) {
renderSuggestConstraintsCursor(sugConstr2);
return;
}
}
applyCursor();
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_First){
EditCurve[0] = onSketchPos;
Mode = STATUS_SEEK_Second;
} else {
EditCurve[1] = onSketchPos;
Mode = STATUS_Close;
}
return true;
}
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
if (Mode==STATUS_Close) {
double rx = EditCurve[1].x - EditCurve[0].x;
double ry = EditCurve[1].y - EditCurve[0].y;
unsetCursor();
resetPositionText();
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch circle"));
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.Circle"
"(App.Vector(%f,%f,0),App.Vector(0,0,1),%f),%s)",
EditCurve[0].x, EditCurve[0].y,
sqrt(rx*rx + ry*ry),
geometryCreationMode==Construction?"True":"False");
Gui::Command::commitCommand();
}
catch (const Base::Exception& e) {
Base::Console().Error("Failed to add circle: %s\n", e.what());
Gui::Command::abortCommand();
}
// add auto constraints for the center point
if (sugConstr1.size() > 0) {
createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::mid);
sugConstr1.clear();
}
// add suggested constraints for circumference
if (sugConstr2.size() > 0) {
createAutoConstraints(sugConstr2, getHighestCurveIndex(), Sketcher::none);
sugConstr2.clear();
}
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if(continuousMode){
// This code enables the continuous creation mode.
Mode=STATUS_SEEK_First;
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
EditCurve.resize(34);
applyCursor();
/* this is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
}
else{
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
}
}
return true;
}
protected:
SelectMode Mode;
std::vector<Base::Vector2d> EditCurve;
std::vector<AutoConstraint> sugConstr1, sugConstr2;
};
DEF_STD_CMD_A(CmdSketcherCreateCircle)
CmdSketcherCreateCircle::CmdSketcherCreateCircle()
: Command("Sketcher_CreateCircle")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create circle");
sToolTipText = QT_TR_NOOP("Create a circle in the sketch");
sWhatsThis = "Sketcher_CreateCircle";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateCircle";
eType = ForEdit;
}
void CmdSketcherCreateCircle::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerCircle() );
}
bool CmdSketcherCreateCircle::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
// ======================================================================================
/**
* @brief This class handles user interaction to draw and save the ellipse
*
* Two construction methods are implemented:
* -Periapsis, apoapsis, and b; and
* -Center, periapsis, and b.
*
* The first method limits the ellipse to a circle, while the second method allows for
* swapping of the semi-major and semi-minor axes.
*
* We use three reference frames in this class. The first (and primary), is the cartesian
* frame of the sketcher; all our work begins and ends in this frame. The second is the
* perifocal frame of the ellipse using polar coordinates. We use this frame for naming
* conventions and working with the ellipse. The last is a rotated right-handed cartesian
* frame centered at the ellipse center with the +X direction towards periapsis, +Z out of
* screen.
*
* When working with an ellipse in the perifocal frame, the following equations are useful:
*
* \f{eqnarray*}{
* r &\equiv& \textrm{ radial distance from the focus to a point on the ellipse}\\
* r_a &\equiv& \textrm{ radial distance from the focus to apopasis}\\
* r_p &\equiv& \textrm{ radial distance from the focus to periapsis}\\
* a &\equiv& \textrm{ length of the semi-major axis, colloquially 'radius'}\\
* b &\equiv& \textrm{ length of the semi-minor axis, colloquially 'radius'}\\
* e &\equiv& \textrm{ eccentricity of the ellipse}\\
* \theta_b &\equiv& \textrm{ angle to the intersection of the semi-minor axis and the ellipse, relative to the focus}\\
* ae &\equiv& \textrm{ distance from the focus to the centroid}\\
* r &=& \frac{a(1-e^2)}{1+e\cos(\theta)} = \frac{r_a(1-e)}{1+e\cos(\theta)} = \frac{r_p(1+e)}{1+e\cos(\theta)}\\
* r_a &=& a(1-e)\\
* r_p &=& a(1+e)\\
* a &=& \frac{r_p+r_a}{2}\\
* b &=& a\sqrt{1-e^2}\\
* e &=& \frac{r_a-r_p}{r_a+r_p} = \sqrt{1-\frac{b^2}{a^2}}\\
* \theta_b &=& \left[\pi - \arctan\left(\frac{b}{ae}\right)\right] \pm N\pi
* \f}
*
*/
class DrawSketchHandlerEllipse : public DrawSketchHandler
{
public:
DrawSketchHandlerEllipse(int constructionMethod)
: mode(STATUS_Close)
, method(CENTER_PERIAPSIS_B)
, constrMethod(constructionMethod)
, a(0), b(0), e(0), ratio(0), ae(0)
, num(0), r(0), theta(0), phi(0)
, editCurve(33), fixedAxisLength(0)
{
}
virtual ~DrawSketchHandlerEllipse(){}
/// Mode table, describes what step of the process we are in
enum SelectMode {
STATUS_SEEK_PERIAPSIS, /**< enum value, looking for click to set periapsis. */
STATUS_SEEK_APOAPSIS, /**< enum value, looking for click to set apoapsis. */
STATUS_SEEK_CENTROID, /**< enum value, looking for click to set centroid. */
STATUS_SEEK_A, /**< enum value, looking for click to set a. */
STATUS_SEEK_B, /**< enum value, looking for click to set b. */
STATUS_Close /**< enum value, finalizing and saving ellipse. */
};
/// Construction methods, describes the method used to construct the ellipse
enum ConstructionMethod {
CENTER_PERIAPSIS_B, /**< enum value, click on center, then periapsis, then b point. */
PERIAPSIS_APOAPSIS_B /**< enum value, click on periapsis, then apoapsis, then b point. */
};
/**
* @brief Slot called when the create ellipse command is activated
* @param sketchgui A pointer to the active sketch
*/
virtual void activated(ViewProviderSketch *)
{
setCrosshairCursor("Sketcher_Pointer_Create_Ellipse");
if (constrMethod == 0) {
method = CENTER_PERIAPSIS_B;
mode = STATUS_SEEK_CENTROID;
} else {
method = PERIAPSIS_APOAPSIS_B;
mode = STATUS_SEEK_PERIAPSIS;
}
}
/**
* @brief Updates the ellipse when the cursor moves
* @param onSketchPos the position of the cursor on the sketch
*/
virtual void mouseMove(Base::Vector2d onSketchPos)
{
if (method == PERIAPSIS_APOAPSIS_B) {
if (mode == STATUS_SEEK_PERIAPSIS) {
setPositionText(onSketchPos);
if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f),
AutoConstraint::CURVE)) {
renderSuggestConstraintsCursor(sugConstr1);
return;
}
} else if (mode == STATUS_SEEK_APOAPSIS) {
solveEllipse(onSketchPos);
approximateEllipse();
// Display radius for user
float semiMajorRadius = a * 2;
SbString text;
text.sprintf(" (%.1fR,%.1fR)", semiMajorRadius,semiMajorRadius);
setPositionText(onSketchPos, text);
sketchgui->drawEdit(editCurve);
// Suggestions for ellipse and curves are disabled because many tangent constraints
// need an intermediate point or line.
if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f),
AutoConstraint::CURVE)) {
renderSuggestConstraintsCursor(sugConstr2);
return;
}
} else if (mode == STATUS_SEEK_B) {
solveEllipse(onSketchPos);
approximateEllipse();
// Display radius for user
SbString text;
text.sprintf(" (%.1fR,%.1fR)", a, b);
setPositionText(onSketchPos, text);
sketchgui->drawEdit(editCurve);
if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.f,0.f),
AutoConstraint::CURVE)) {
renderSuggestConstraintsCursor(sugConstr3);
return;
}
}
} else { // method is CENTER_PERIAPSIS_B
if (mode == STATUS_SEEK_CENTROID) {
setPositionText(onSketchPos);
if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { // TODO: ellipse prio 1
renderSuggestConstraintsCursor(sugConstr1);
return;
}
} else if (mode == STATUS_SEEK_PERIAPSIS) {
solveEllipse(onSketchPos);
approximateEllipse();
// Display radius for user
float semiMajorRadius = a * 2;
SbString text;
text.sprintf(" (%.1fR,%.1fR)", semiMajorRadius,semiMajorRadius);
setPositionText(onSketchPos, text);
sketchgui->drawEdit(editCurve);
if (seekAutoConstraint(sugConstr2, onSketchPos, onSketchPos - centroid,
AutoConstraint::CURVE)) {
renderSuggestConstraintsCursor(sugConstr2);
return;
}
} else if ((mode == STATUS_SEEK_A) || (mode == STATUS_SEEK_B)) {
solveEllipse(onSketchPos);
approximateEllipse();
// Display radius for user
SbString text;
text.sprintf(" (%.1fR,%.1fR)", a, b);
setPositionText(onSketchPos, text);
sketchgui->drawEdit(editCurve);
if (seekAutoConstraint(sugConstr3, onSketchPos, onSketchPos - centroid,
AutoConstraint::CURVE)) {
renderSuggestConstraintsCursor(sugConstr3);
return;
}
}
}
applyCursor();
}
/**
* @brief Changes drawing mode on user-click
* @param onSketchPos the position of the cursor on the sketch
* @return
*/
virtual bool pressButton(Base::Vector2d onSketchPos)
{
if (method == PERIAPSIS_APOAPSIS_B) {
if (mode == STATUS_SEEK_PERIAPSIS) {
periapsis = onSketchPos;
mode = STATUS_SEEK_APOAPSIS;
}
else if (mode == STATUS_SEEK_APOAPSIS) {
apoapsis = onSketchPos;
mode = STATUS_SEEK_B;
}
else {
mode = STATUS_Close;
}
} else { // method is CENTER_PERIAPSIS_B
if (mode == STATUS_SEEK_CENTROID) {
centroid = onSketchPos;
mode = STATUS_SEEK_PERIAPSIS;
}
else if (mode == STATUS_SEEK_PERIAPSIS) {
periapsis = onSketchPos;
mode = STATUS_SEEK_B;
}
else {
mode = STATUS_Close;
}
}
return true;
}
/**
* @brief Calls \c saveEllipse() after last user input
* @param onSketchPos the position of the cursor on the sketch
* @return
*/
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
if (mode == STATUS_Close) {
saveEllipse();
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if(continuousMode){
if (constrMethod == 0) {
method = CENTER_PERIAPSIS_B;
mode = STATUS_SEEK_CENTROID;
} else {
method = PERIAPSIS_APOAPSIS_B;
mode = STATUS_SEEK_PERIAPSIS;
}
}
}
return true;
}
protected:
std::vector<AutoConstraint> sugConstr1, sugConstr2, sugConstr3;
private:
SelectMode mode;
/// the method of constructing the ellipse
ConstructionMethod method;
int constrMethod;
/// periapsis position vector, in standard position in sketch coordinate system
Base::Vector2d periapsis;
/// apoapsis position vector, in standard position in sketch coordinate system
Base::Vector2d apoapsis;
/// centroid position vector, in standard position in sketch coordinate system
Base::Vector2d centroid;
/**
* @brief position vector of positive b point, in standard position in sketch coordinate system
* I.E. in polar perifocal system, the first intersection of the semiminor axis with the ellipse
* as theta increases from 0. This always happens when:
* \f{eqnarray*}{
* \theta_b &=& \left[\pi - \arctan\left(\frac{b}{ae}\right)\right] \pm N 2\pi
* \f}
*
* In a rotated R^3 cartesian system, centered at the centroid, +X towards periapsis, and
* +Z coming out of the sketch, this b position is in the +Y direction from the centroid.
*/
Base::Vector2d positiveB;
/// the other b position
Base::Vector2d negativeB;
/// cart. position vector for primary focus
Base::Vector2d f;
/// cart. position vector for other focus
Base::Vector2d fPrime;
/// Unit vector for apse line
Base::Vector2d apseHat;
/// length of semimajor axis, i.e. 'radius' colloquially
double a;
/// length of semiminor axis, i.e. 'radius' colloquially
double b;
/// eccentricity [unitless]
double e;
/// optimization, holds a term that helps calculate b in terms of a and e
double ratio;
/// holds product of a * e
double ae;
/// holds numerator of orbit equation of form a(1-e^2)
double num;
/// holds a radial distance from f to the ellipse for a given theta
double r;
/// angle of a point in a perifocal frame centered at f
double theta;
/// angle of apse line relative to sketch coordinate system
double phi;
/// holds a position vector for a point on the ellipse from f
Base::Vector2d pos;
/// holds a position vector for a point on the ellipse from fPrime
Base::Vector2d posPrime;
/// holds position vectors for a points on the ellipse
std::vector<Base::Vector2d> editCurve;
/// local i_hat vector for ellipse, from centroid to periapsis
Base::Vector3d iPrime;
/// local j_hat vector for ellipse, from centroid to b point
Base::Vector3d jPrime;
/// length (radius) of the fixed axis
double fixedAxisLength;
/// position vector of fixed axis point in sketch coordinates
Base::Vector2d fixedAxis;
/**
* @brief Computes a vector of 2D points representing an ellipse
* @param onSketchPos Current position of the cursor on the sketch
*/
void solveEllipse(Base::Vector2d onSketchPos)
{
const double GOLDEN_RATIO = 1.6180339887;
Base::Vector3d k(0,0,1);
if (method == PERIAPSIS_APOAPSIS_B) {
if (mode == STATUS_SEEK_APOAPSIS) {
apoapsis = onSketchPos;
}
a = (apoapsis - periapsis).Length() / 2;
apseHat = (periapsis - apoapsis);
apseHat.Normalize();
centroid = apseHat;
centroid.Scale(-1 * a);
centroid = periapsis + centroid;
if (mode == STATUS_SEEK_APOAPSIS) {
// for first step, we draw an ellipse inscribed in a golden rectangle
ratio = 1 / GOLDEN_RATIO; // ~= 0.6180339887
e = sqrt(ratio); // ~= 0.7861513777
b = a * ratio;
}
else if (mode == STATUS_SEEK_B) {
// Get the closest distance from onSketchPos to apse line, as a 'requested' value for b
Base::Vector2d cursor = Base::Vector2d(onSketchPos - f); // vector from f to cursor pos
// decompose cursor with a projection, then length of w_2 will give us b
Base::Vector2d w_1 = cursor;
w_1.ProjectToLine(cursor, (periapsis - apoapsis)); // projection of cursor line onto apse line
Base::Vector2d w_2 = (cursor - w_1);
b = w_2.Length();
// limit us to ellipse or circles
if (b > a) {
b = a;
}
e = sqrt(1 - ((b * b) / (a * a)));
ratio = sqrt(1 - (e*e));
}
ae = a * e;
f = apseHat;
f.Scale(ae);
f = centroid + f;
fPrime = apseHat;
fPrime.Scale(-1 * ae);
fPrime = centroid + fPrime;
phi = atan2(apseHat.y, apseHat.x);
num = a * (1 - (e * e));
// The ellipse is now solved
} else { // method == CENTER_PERIAPSIS_B
if (mode == STATUS_SEEK_PERIAPSIS) {
// solve the ellipse inscribed in a golden rectangle
periapsis = onSketchPos;
a = (centroid - periapsis).Length();
iPrime.x = periapsis.x - centroid.x;
iPrime.y = periapsis.y - centroid.y;
iPrime.z = 0;
jPrime = k % iPrime; // j = k cross i
// these are constant for any ellipse inscribed in a golden rectangle
ratio = 1 / GOLDEN_RATIO; // ~= 0.6180339887
e = sqrt(ratio); // ~= 0.7861513777
b = a * ratio;
ae = a * e;
apseHat = (periapsis - centroid);
apseHat.Normalize();
f = apseHat;
f.Scale(ae);
f = centroid + f;
fPrime = apseHat;
fPrime.Scale(-1 * ae);
fPrime = centroid + fPrime;
apoapsis = apseHat;
apoapsis.Scale(-1 * a);
apoapsis = centroid + apoapsis;
phi = atan2(apseHat.y, apseHat.x);
num = a * (1 - (e * e));
fixedAxisLength = a;
fixedAxis = periapsis;
} else if ((mode == STATUS_SEEK_B) || (mode == STATUS_SEEK_A)) {
// while looking for the last click, we may switch back and forth
// between looking for a b point and looking for periapsis, so ensure
// we are in the right mode
Base::Vector2d cursor = Base::Vector2d(onSketchPos - centroid); // vector from centroid to cursor pos
// decompose cursor with a projection, then length of w_2 will give us b
Base::Vector2d w_1 = cursor;
w_1.ProjectToLine(cursor, (fixedAxis - centroid)); // projection of cursor line onto fixed axis line
Base::Vector2d w_2 = (cursor - w_1);
if (w_2.Length() > fixedAxisLength) {
// b is fixed, we are seeking a
mode = STATUS_SEEK_A;
jPrime.x = (fixedAxis - centroid).x;
jPrime.y = (fixedAxis - centroid).y;
jPrime.Normalize();
iPrime = jPrime % k; // cross
b = fixedAxisLength;
a = w_2.Length();
} else {
// a is fixed, we are seeking b
mode = STATUS_SEEK_B;
iPrime.x = (fixedAxis - centroid).x;
iPrime.y = (fixedAxis - centroid).y;
iPrime.Normalize();
jPrime = k % iPrime; // cross
a = fixedAxisLength;
b = w_2.Length();
}
// now finish solving the ellipse
periapsis.x = centroid.x + (iPrime * a).x;
periapsis.y = centroid.y + (iPrime * a).y;
e = sqrt(1 - ((b * b) / (a * a)));
ratio = sqrt(1 - (e*e));
ae = a * e;
apseHat = (periapsis - centroid);
apseHat.Normalize();
f = apseHat;
f.Scale(ae);
f = centroid + f;
fPrime = apseHat;
fPrime.Scale(-1 * ae);
fPrime = centroid + fPrime;
apoapsis = apseHat;
apoapsis.Scale(-1 * a);
apoapsis = centroid + apoapsis;
phi = atan2(apseHat.y, apseHat.x);
num = a * (1 - (e * e));
}
}
}
/**
* @brief Computes a sequence of 2D vectors to approximate the ellipse
*/
void approximateEllipse()
{
// We will approximate the ellipse as a sequence of connected chords
// Number of points per quadrant of the ellipse
int n = static_cast<int>((editCurve.size() - 1) / 4);
// We choose points in the perifocal frame then translate them to sketch cartesian.
// This gives us a better approximation of an ellipse, i.e. more points where the
// curvature is higher. If the eccentricity is high, we shift the points a bit towards
// the semi-minor axis.
double partitionAngle = (M_PI - atan2(b, ae)) / n;
double radianShift = 0;
if (e > 0.8) {radianShift = (partitionAngle / 5) * 4;}
for (int i=0; i < n; i++) {
theta = i * partitionAngle;
if (i > 0) {theta = theta + radianShift;}
r = num / (1 + (e * cos(theta)));
// r(pi/2) is semi-latus rectum, if we need it
pos.x = r*cos(theta+phi); // phi rotates, sin/cos translate
pos.y = r*sin(theta+phi);
pos = pos + f;
posPrime.x = r*cos(theta+phi+M_PI);
posPrime.y = r*sin(theta+phi+M_PI);
posPrime = posPrime + fPrime;
// over the loop, loads Quadrant I points, by using f as origin
editCurve[i] = pos;
// over the loop, loads Quadrant III points, by using fPrime as origin
editCurve[(2*n) + i] = posPrime;
// load points with negative theta angles (i.e. cw)
if (i>0) {
pos.x = r*cos(-1*theta+phi);
pos.y = r*sin(-1*theta+phi);
pos = pos + f;
// loads Quadrant IV points
editCurve[(4*n) - i] = pos;
posPrime.x = r*cos(-1*theta+phi+M_PI);
posPrime.y = r*sin(-1*theta+phi+M_PI);
posPrime = posPrime + fPrime;
// loads Quadrant II points
editCurve[(2*n) - i] = posPrime;
}
}
// load pos & neg b points
theta = M_PI - atan2(b, ae); // the angle from f to the positive b point
r = num / (1 + (e * cos(theta)));
pos.x = r*cos(theta+phi);
pos.y = r*sin(theta+phi);
pos = pos + f;
editCurve[n] = pos; // positive
pos.x = r*cos(-1*theta+phi);
pos.y = r*sin(-1*theta+phi);
pos = pos + f;
editCurve[(3*n)] = pos; // negative
// force the curve to be a closed shape
editCurve[(4*n)] = editCurve[0];
}
/**
* @brief Prints the ellipse data to STDOUT as an GNU Octave script
* @param onSketchPos position of the cursor on the sketch
*/
void ellipseToOctave(Base::Vector2d /*onSketchPos*/)
{
int n = static_cast<int>((editCurve.size() - 1) / 4);
// send a GNU Octave script to stdout to plot points for debugging
std::ostringstream octave;
octave << std::fixed << std::setprecision(12);
octave << "\nclear all;\nclose all;\nclc;\n\n";
octave << "periapsis = [" << periapsis.x << ", " << periapsis.y << "];\n";
octave << "apoapsis = [" << apoapsis.x << ", " << apoapsis.y << "];\n";
octave << "positiveB = [" << editCurve[n].x << ", " << editCurve[n].y << "];\n";
octave << "apseHat = [" << apseHat.x << ", " << apseHat.y << "];\n";
octave << "a = " << a << ";\n";
octave << "b = " << b << ";\n";
octave << "eccentricity = " << e << ";\n";
octave << "centroid = [" << centroid.x << ", " << centroid.y << "];\n";
octave << "f = [" << f.x << ", " << f.y << "];\n";
octave << "fPrime = [" << fPrime.x << ", " << fPrime.y << "];\n";
octave << "phi = " << phi << ";\n\n";
octave << "x = [";
for (int i=0; i < 4*n + 1; i++) {
octave << editCurve[i].x;
if (i < 4*n) {
octave << ", ";
}
}
octave << "];\n";
octave << "y = [";
for (int i=0; i < 4*n + 1; i++) {
octave << editCurve[i].y;
if (i < 4*n) {
octave << ", ";
}
}
octave << "];\n\n";
octave << "% Draw ellipse points in red;\n";
octave << "plot (x, y, \"r.\", \"markersize\", 5);\n";
octave << "axis ([-300, 300, -300, 300], \"square\");grid on;\n";
octave << "hold on;\n\n";
octave << "% Draw centroid in blue, f in cyan, and fPrime in magenta;\n";
octave << "plot(centroid(1), centroid(2), \"b.\", \"markersize\", 5);\n";
octave << "plot(f(1), f(2), \"c.\", \"markersize\", 5);\n";
octave << "plot(fPrime(1), fPrime(2), \"m.\", \"markersize\", 5);\n";
octave << "n = [periapsis(1) - f(1), periapsis(2) - f(2)];\n";
octave << "h = quiver(f(1),f(2),n(1),n(2), 0);\n";
octave << "set (h, \"maxheadsize\", 0.1);\n\n";
octave << "% Draw the three position vectors used for Gui::Command::doCommand(...)\n";
octave << "periapsisVec = quiver(0,0,periapsis(1),periapsis(2), 0);\n";
octave << "set (periapsisVec, \"maxheadsize\", 0.01, \"color\", \"black\");\n";
octave << "centroidVec = quiver(0,0,centroid(1),centroid(2), 0);\n";
octave << "set (centroidVec, \"maxheadsize\", 0.01, \"color\", \"black\");\n";
octave << "bVec = quiver(0,0,positiveB(1),positiveB(2), 0);\n";
octave << "set (bVec, \"maxheadsize\", 0.01, \"color\", \"black\");\n\n";
octave << "% Draw the local x & y basis vectors, scaled to a and b, in red and blue, respectively\n";
octave << "xLocalVec = quiver(centroid(1),centroid(2),periapsis(1)-centroid(1),periapsis(2)-centroid(2), 0);\n";
octave << "set (xLocalVec, \"maxheadsize\", 0.01, \"color\", \"red\");\n";
octave << "yLocalVec = quiver(centroid(1),centroid(2), positiveB(1)-centroid(1), positiveB(2)-centroid(2), 0);\n";
octave << "set (yLocalVec, \"maxheadsize\", 0.01, \"color\", \"blue\");\nhold off;\n";
qDebug() << QString::fromStdString(octave.str());
}
/**
* @brief Finalizes and saves the drawn ellipse
* @return nothing
*/
void saveEllipse()
{
unsetCursor();
resetPositionText();
/* There are a couple of issues with Gui::Command::doCommand(...) and
* GC_MakeEllipse(...) that cause bugs if not handled properly, even
* when we give them a mathematically-correct ellipse.
*
* GC_MakeEllipse may fail with a gce_InvertAxis error for a small
* circular ellipse when floating point roundoff or representation
* errors make the b axis slightly larger than the a axis.
*
* A similar, larger, issue arises in Gui::Command::doCommand(...) because
* we cast our double vector components into strings with a fixed
* precision of six, and then create new doubles from the strings
* in EllipsePy::PyInit(...). Thus, by the time we call GC_MakeEllipse(...)
* in EllipsePy::PyInit(...), our ellipse may not be valid anymore
* because b is now greater than a.
*
* To handle these issues, we simulate the effects Gui::Command::doCommand(...)
* has on our ellipse, and we adjust our ellipse parameters until
* GC_MakeEllipse successfully creates an ellipse with our mangled
* parameters.
*
* In almost all cases, we only have to make our test ellipse one time;
* it is only in the rare edge cases that require repeated test ellipses
* until we get a valid one, or fail due to excessive attempts. With a
* limit of 25 attempts, I have been unable to make it fail.
*/
// simulate loss of precision in centroid, periapsis, and apoapsis
char cx[64];
char cy[64];
char px[64];
char py[64];
char ax[64];
char ay[64];
sprintf(cx, "%.6lf\n", centroid.x);
sprintf(cy, "%.6lf\n", centroid.y);
sprintf(px, "%.6lf\n", periapsis.x);
sprintf(py, "%.6lf\n", periapsis.y);
sprintf(ax, "%.6lf\n", apoapsis.x);
sprintf(ay, "%.6lf\n", apoapsis.y);
centroid.x = atof(cx);
centroid.y = atof(cy);
periapsis.x = atof(px);
periapsis.y = atof(py);
apoapsis.x = atof(ax);
apoapsis.y = atof(ay);
double majorLength = (periapsis - apoapsis).Length();
double minorLength = 0;
/* GC_MakeEllipse requires a right-handed coordinate system, with +X
* from centroid to periapsis, +Z out of the page.
*/
Base::Vector3d k(0,0,1);
Base::Vector3d i(periapsis.x - centroid.x, periapsis.y - centroid.y, 0);
Base::Vector3d j = k % i; // j = k cross i
double beta = 1e-7;
int count = 0;
int limit = 25; // no infinite loops!
bool success = false;
double tempB = b;
// adjust b until our mangled vectors produce a good ellipse in GC_MakeEllipse
// and the mangled major and minor lines in LinePy::PyInit(...) are such that
// major is at least slightly larger than minor
do {
tempB = b - double(count * beta);
j = j.Normalize() * tempB;
positiveB.x = centroid.x + j.x;
positiveB.y = centroid.y + j.y;
negativeB.x = centroid.x + (j.x * -1);
negativeB.y = centroid.y + (j.y * -1);
char bpx[64];
char bpy[64];
char bnx[64];
char bny[64];
sprintf(bpx, "%.6lf\n", positiveB.x);
sprintf(bpy, "%.6lf\n", positiveB.y);
sprintf(bnx, "%.6lf\n", negativeB.x);
sprintf(bny, "%.6lf\n", negativeB.y);
positiveB.x = atof(bpx);
positiveB.y = atof(bpy);
negativeB.x = atof(bnx);
negativeB.y = atof(bny);
GC_MakeEllipse me(gp_Pnt(periapsis.x,periapsis.y,0),
gp_Pnt(positiveB.x,positiveB.y,0),
gp_Pnt(centroid.x,centroid.y,0));
minorLength = (negativeB - positiveB).Length();
count++;
success = me.IsDone() && (minorLength + beta < majorLength);
} while (!success && (count <= limit));
if (!success) {
qDebug() << "Failed to create a valid mangled ellipse after" << count << "attempts";
}
// save any changes to b, then recalculate ellipse as required due to change in b
b = tempB;
e = sqrt(1 - ((b * b) / (a * a)));
ae = a * e;
f = apseHat;
f.Scale(ae);
f = centroid + f;
fPrime = apseHat;
fPrime.Scale(-1 * ae);
fPrime = centroid + fPrime;
int currentgeoid = getHighestCurveIndex(); // index of the ellipse we just created
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch ellipse"));
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.Ellipse"
"(App.Vector(%f,%f,0),App.Vector(%f,%f,0),App.Vector(%f,%f,0)),%s)",
periapsis.x, periapsis.y,
positiveB.x, positiveB.y,
centroid.x, centroid.y,
geometryCreationMode==Construction?"True":"False");
currentgeoid++;
Gui::cmdAppObjectArgs(sketchgui->getObject(), "exposeInternalGeometry(%d)", currentgeoid);
}
catch (const Base::Exception& e) {
Base::Console().Error("%s\n", e.what());
Gui::Command::abortCommand();
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
return;
}
Gui::Command::commitCommand();
if (method == CENTER_PERIAPSIS_B) {
// add auto constraints for the center point
if (sugConstr1.size() > 0) {
createAutoConstraints(sugConstr1, currentgeoid, Sketcher::mid);
sugConstr1.clear();
}
if (sugConstr2.size() > 0) {
createAutoConstraints(sugConstr2, currentgeoid, Sketcher::none);
sugConstr2.clear();
}
if (sugConstr3.size() > 0) {
createAutoConstraints(sugConstr3, currentgeoid, Sketcher::none);
sugConstr3.clear();
}
}
if (method == PERIAPSIS_APOAPSIS_B) {
if (sugConstr1.size() > 0) {
createAutoConstraints(sugConstr1, currentgeoid, Sketcher::none);
sugConstr1.clear();
}
if (sugConstr2.size() > 0) {
createAutoConstraints(sugConstr2, currentgeoid, Sketcher::none);
sugConstr2.clear();
}
if (sugConstr3.size() > 0) {
createAutoConstraints(sugConstr3, currentgeoid, Sketcher::none);
sugConstr3.clear();
}
}
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
// This code enables the continuous creation mode.
if (constrMethod == 0) {
method = CENTER_PERIAPSIS_B;
mode = STATUS_SEEK_CENTROID;
} else {
method = PERIAPSIS_APOAPSIS_B;
mode = STATUS_SEEK_PERIAPSIS;
}
editCurve.clear();
sketchgui->drawEdit(editCurve);
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if(continuousMode){
// This code enables the continuous creation mode.
editCurve.resize(33);
applyCursor();
/* It is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
}
else{
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
}
}
};
/// @brief Macro that declares a new sketcher command class 'CmdSketcherCreateEllipseByCenter'
DEF_STD_CMD_A(CmdSketcherCreateEllipseByCenter)
/**
* @brief ctor
*/
CmdSketcherCreateEllipseByCenter::CmdSketcherCreateEllipseByCenter()
: Command("Sketcher_CreateEllipseByCenter")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create ellipse by center");
sToolTipText = QT_TR_NOOP("Create an ellipse by center in the sketch");
sWhatsThis = "Sketcher_CreateEllipseByCenter";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_Conics_Ellipse_Center";
eType = ForEdit;
}
void CmdSketcherCreateEllipseByCenter::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerEllipse(0) );
}
bool CmdSketcherCreateEllipseByCenter::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
/// @brief Macro that declares a new sketcher command class 'CmdSketcherCreateEllipseBy3Points'
DEF_STD_CMD_A(CmdSketcherCreateEllipseBy3Points)
/**
* @brief ctor
*/
CmdSketcherCreateEllipseBy3Points::CmdSketcherCreateEllipseBy3Points()
: Command("Sketcher_CreateEllipseBy3Points")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create ellipse by 3 points");
sToolTipText = QT_TR_NOOP("Create an ellipse by 3 points in the sketch");
sWhatsThis = "Sketcher_CreateEllipseBy3Points";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateEllipse_3points";
eType = ForEdit;
}
void CmdSketcherCreateEllipseBy3Points::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerEllipse(1) );
}
bool CmdSketcherCreateEllipseBy3Points::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
class DrawSketchHandlerArcOfEllipse : public DrawSketchHandler
{
public:
DrawSketchHandlerArcOfEllipse()
: Mode(STATUS_SEEK_First), EditCurve(34)
, rx(0), ry(0), startAngle(0), endAngle(0)
, arcAngle(0), arcAngle_t(0)
{
}
virtual ~DrawSketchHandlerArcOfEllipse(){}
/// mode table
enum SelectMode {
STATUS_SEEK_First, /**< enum value ----. */
STATUS_SEEK_Second, /**< enum value ----. */
STATUS_SEEK_Third, /**< enum value ----. */
STATUS_SEEK_Fourth, /**< enum value ----. */
STATUS_Close
};
virtual void activated(ViewProviderSketch *)
{
setCrosshairCursor("Sketcher_Pointer_Create_ArcOfEllipse");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_First) {
setPositionText(onSketchPos);
if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { // TODO: ellipse prio 1
renderSuggestConstraintsCursor(sugConstr1);
return;
}
}
else if (Mode==STATUS_SEEK_Second) {
double rx0 = onSketchPos.x - EditCurve[0].x;
double ry0 = onSketchPos.y - EditCurve[0].y;
for (int i=0; i < 16; i++) {
double angle = i*M_PI/16.0;
double rx1 = rx0 * cos(angle) + ry0 * sin(angle);
double ry1 = -rx0 * sin(angle) + ry0 * cos(angle);
EditCurve[1+i] = Base::Vector2d(EditCurve[0].x + rx1, EditCurve[0].y + ry1);
EditCurve[17+i] = Base::Vector2d(EditCurve[0].x - rx1, EditCurve[0].y - ry1);
}
EditCurve[33] = EditCurve[1];
// Display radius for user
float radius = (onSketchPos - EditCurve[0]).Length();
SbString text;
text.sprintf(" (%.1fR,%.1fR)", radius,radius);
setPositionText(onSketchPos, text);
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr2, onSketchPos, onSketchPos - centerPoint,
AutoConstraint::CURVE)) {
renderSuggestConstraintsCursor(sugConstr2);
return;
}
}
else if (Mode==STATUS_SEEK_Third) {
// angle between the major axis of the ellipse and the X axis
double a = (EditCurve[1]-EditCurve[0]).Length();
double phi = atan2(EditCurve[1].y-EditCurve[0].y,EditCurve[1].x-EditCurve[0].x);
// This is the angle at cursor point
double angleatpoint = acos((onSketchPos.x-EditCurve[0].x+(onSketchPos.y-EditCurve[0].y)*tan(phi))/(a*(cos(phi)+tan(phi)*sin(phi))));
double b=(onSketchPos.y-EditCurve[0].y-a*cos(angleatpoint)*sin(phi))/(sin(angleatpoint)*cos(phi));
for (int i=1; i < 16; i++) {
double angle = i*M_PI/16.0;
double rx1 = a * cos(angle) * cos(phi) - b * sin(angle) * sin(phi);
double ry1 = a * cos(angle) * sin(phi) + b * sin(angle) * cos(phi);
EditCurve[1+i] = Base::Vector2d(EditCurve[0].x + rx1, EditCurve[0].y + ry1);
EditCurve[17+i] = Base::Vector2d(EditCurve[0].x - rx1, EditCurve[0].y - ry1);
}
EditCurve[33] = EditCurve[1];
EditCurve[17] = EditCurve[16];
// Display radius for user
SbString text;
text.sprintf(" (%.1fR,%.1fR)", a, b);
setPositionText(onSketchPos, text);
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr3);
return;
}
}
else if (Mode==STATUS_SEEK_Fourth) { // here we differ from ellipse creation
// angle between the major axis of the ellipse and the X axis
double a = (axisPoint-centerPoint).Length();
double phi = atan2(axisPoint.y-centerPoint.y,axisPoint.x-centerPoint.x);
// This is the angle at cursor point
double angleatpoint = acos((startingPoint.x-centerPoint.x+(startingPoint.y-centerPoint.y)*tan(phi))/(a*(cos(phi)+tan(phi)*sin(phi))));
double b=abs((startingPoint.y-centerPoint.y-a*cos(angleatpoint)*sin(phi))/(sin(angleatpoint)*cos(phi)));
double rxs = startingPoint.x - centerPoint.x;
double rys = startingPoint.y - centerPoint.y;
startAngle = atan2(a*(rys*cos(phi)-rxs*sin(phi)), b*(rxs*cos(phi)+rys*sin(phi))); // eccentric anomaly angle
double angle1 = atan2(a*((onSketchPos.y - centerPoint.y)*cos(phi)-(onSketchPos.x - centerPoint.x)*sin(phi)),
b*((onSketchPos.x - centerPoint.x)*cos(phi)+(onSketchPos.y - centerPoint.y)*sin(phi)))- startAngle;
double angle2 = angle1 + (angle1 < 0. ? 2 : -2) * M_PI ;
arcAngle = abs(angle1-arcAngle) < abs(angle2-arcAngle) ? angle1 : angle2;
for (int i=0; i < 34; i++) {
double angle = startAngle+i*arcAngle/34.0;
double rx1 = a * cos(angle) * cos(phi) - b * sin(angle) * sin(phi);
double ry1 = a * cos(angle) * sin(phi) + b * sin(angle) * cos(phi);
EditCurve[i] = Base::Vector2d(centerPoint.x + rx1, centerPoint.y + ry1);
}
// EditCurve[33] = EditCurve[1];
// EditCurve[17] = EditCurve[16];
// Display radii and angle for user
SbString text;
text.sprintf(" (%.1fR,%.1fR,%.1fdeg)", a, b, arcAngle * 180 / M_PI);
setPositionText(onSketchPos, text);
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr4, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr4);
return;
}
}
applyCursor();
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_First){
EditCurve[0] = onSketchPos;
centerPoint = onSketchPos;
Mode = STATUS_SEEK_Second;
}
else if(Mode==STATUS_SEEK_Second) {
EditCurve[1] = onSketchPos;
axisPoint = onSketchPos;
Mode = STATUS_SEEK_Third;
}
else if(Mode==STATUS_SEEK_Third) {
startingPoint = onSketchPos;
arcAngle = 0.;
arcAngle_t= 0.;
Mode = STATUS_SEEK_Fourth;
}
else { // Fourth
endPoint = onSketchPos;
Mode = STATUS_Close;
}
return true;
}
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
if (Mode==STATUS_Close) {
unsetCursor();
resetPositionText();
// angle between the major axis of the ellipse and the X axisEllipse
double a = (axisPoint-centerPoint).Length();
double phi = atan2(axisPoint.y-centerPoint.y,axisPoint.x-centerPoint.x);
// This is the angle at cursor point
double angleatpoint = acos((startingPoint.x-centerPoint.x+(startingPoint.y-centerPoint.y)*tan(phi))/(a*(cos(phi)+tan(phi)*sin(phi))));
double b=abs((startingPoint.y-centerPoint.y-a*cos(angleatpoint)*sin(phi))/(sin(angleatpoint)*cos(phi)));
double angle1 = atan2(a*((endPoint.y - centerPoint.y)*cos(phi)-(endPoint.x - centerPoint.x)*sin(phi)),
b*((endPoint.x - centerPoint.x)*cos(phi)+(endPoint.y - centerPoint.y)*sin(phi)))- startAngle;
double angle2 = angle1 + (angle1 < 0. ? 2 : -2) * M_PI ;
arcAngle = abs(angle1-arcAngle) < abs(angle2-arcAngle) ? angle1 : angle2;
bool isOriginalArcCCW=true;
if (arcAngle > 0)
endAngle = startAngle + arcAngle;
else {
endAngle = startAngle;
startAngle += arcAngle;
isOriginalArcCCW=false;
}
Base::Vector2d majAxisDir,minAxisDir,minAxisPoint,majAxisPoint;
// We always create a CCW ellipse, because we want our XY reference system to be in the +X +Y direction
// Our normal will then always be in the +Z axis (local +Z axis of the sketcher)
if(a>b)
{
// force second semidiameter to be perpendicular to first semidiamater
majAxisDir = axisPoint - centerPoint;
Base::Vector2d perp(-majAxisDir.y,majAxisDir.x);
perp.Normalize();
perp.Scale(abs(b));
minAxisPoint = centerPoint+perp;
majAxisPoint = centerPoint+majAxisDir;
}
else {
// force second semidiameter to be perpendicular to first semidiamater
minAxisDir = axisPoint - centerPoint;
Base::Vector2d perp(minAxisDir.y,-minAxisDir.x);
perp.Normalize();
perp.Scale(abs(b));
majAxisPoint = centerPoint+perp;
minAxisPoint = centerPoint+minAxisDir;
endAngle += M_PI/2;
startAngle += M_PI/2;
phi-=M_PI/2;
double t=a; a=b; b=t;//swap a,b
}
int currentgeoid = getHighestCurveIndex();
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch arc of ellipse"));
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.ArcOfEllipse"
"(Part.Ellipse(App.Vector(%f,%f,0),App.Vector(%f,%f,0),App.Vector(%f,%f,0)),%f,%f),%s)",
majAxisPoint.x, majAxisPoint.y,
minAxisPoint.x, minAxisPoint.y,
centerPoint.x, centerPoint.y,
startAngle, endAngle,
geometryCreationMode==Construction?"True":"False");
currentgeoid++;
Gui::cmdAppObjectArgs(sketchgui->getObject(), "exposeInternalGeometry(%d)", currentgeoid);
}
catch (const Base::Exception& e) {
Base::Console().Error("%s\n", e.what());
Gui::Command::abortCommand();
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
return false;
}
Gui::Command::commitCommand();
// add auto constraints for the center point
if (sugConstr1.size() > 0) {
createAutoConstraints(sugConstr1, currentgeoid, Sketcher::mid);
sugConstr1.clear();
}
// add suggested constraints for arc
if (sugConstr2.size() > 0) {
createAutoConstraints(sugConstr2, currentgeoid, Sketcher::none);
sugConstr2.clear();
}
// add suggested constraints for start of arc
if (sugConstr3.size() > 0) {
createAutoConstraints(sugConstr3, currentgeoid, isOriginalArcCCW?Sketcher::start:Sketcher::end);
sugConstr3.clear();
}
// add suggested constraints for start of arc
if (sugConstr4.size() > 0) {
createAutoConstraints(sugConstr4, currentgeoid, isOriginalArcCCW?Sketcher::end:Sketcher::start);
sugConstr4.clear();
}
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if(continuousMode){
// This code enables the continuous creation mode.
Mode=STATUS_SEEK_First;
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
EditCurve.resize(34);
applyCursor();
/* this is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
}
else{
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
}
}
return true;
}
protected:
SelectMode Mode;
std::vector<Base::Vector2d> EditCurve;
Base::Vector2d centerPoint, axisPoint, startingPoint, endPoint;
double rx, ry, startAngle, endAngle, arcAngle, arcAngle_t;
std::vector<AutoConstraint> sugConstr1, sugConstr2, sugConstr3, sugConstr4;
};
DEF_STD_CMD_A(CmdSketcherCreateArcOfEllipse)
CmdSketcherCreateArcOfEllipse::CmdSketcherCreateArcOfEllipse()
: Command("Sketcher_CreateArcOfEllipse")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create an arc of ellipse");
sToolTipText = QT_TR_NOOP("Create an arc of ellipse in the sketch");
sWhatsThis = "Sketcher_CreateArcOfEllipse";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateElliptical_Arc";
eType = ForEdit;
}
void CmdSketcherCreateArcOfEllipse::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerArcOfEllipse() );
}
bool CmdSketcherCreateArcOfEllipse::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
class DrawSketchHandlerArcOfHyperbola : public DrawSketchHandler
{
public:
DrawSketchHandlerArcOfHyperbola()
: Mode(STATUS_SEEK_First)
, EditCurve(34)
, arcAngle(0)
, arcAngle_t(0)
{
}
virtual ~DrawSketchHandlerArcOfHyperbola(){}
/// mode table
enum SelectMode {
STATUS_SEEK_First, /**< enum value ----. */
STATUS_SEEK_Second, /**< enum value ----. */
STATUS_SEEK_Third, /**< enum value ----. */
STATUS_SEEK_Fourth, /**< enum value ----. */
STATUS_Close
};
virtual void activated(ViewProviderSketch * /*sketchgui*/)
{
setCrosshairCursor("Sketcher_Pointer_Create_ArcOfHyperbola");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_First) {
setPositionText(onSketchPos);
if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr1);
return;
}
}
else if (Mode==STATUS_SEEK_Second) {
EditCurve[1]= onSketchPos;
// Display radius for user
float radius = (onSketchPos - centerPoint).Length();
SbString text;
text.sprintf(" (%.1fR,%.1fR)", radius,radius);
setPositionText(onSketchPos, text);
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f),
AutoConstraint::CURVE)) {
renderSuggestConstraintsCursor(sugConstr2);
return;
}
}
else if (Mode==STATUS_SEEK_Third) {
// angle between the major axis of the hyperbola and the X axis
double a = (axisPoint-centerPoint).Length();
double phi = atan2(axisPoint.y-centerPoint.y,axisPoint.x-centerPoint.x);
// This is the angle at cursor point
double angleatpoint = acosh(((onSketchPos.x-centerPoint.x)*cos(phi)+(onSketchPos.y-centerPoint.y)*sin(phi))/a);
double b=(onSketchPos.y-centerPoint.y-a*cosh(angleatpoint)*sin(phi))/(sinh(angleatpoint)*cos(phi));
if(!boost::math::isnan(b)){
for (int i=15; i >= -15; i--) {
// P(U) = O + MajRad*Cosh(U)*XDir + MinRad*Sinh(U)*YDir
//double angle = i*M_PI/16.0;
double angle=i*angleatpoint/15;
double rx = a * cosh(angle) * cos(phi) - b * sinh(angle) * sin(phi);
double ry = a * cosh(angle) * sin(phi) + b * sinh(angle) * cos(phi);
EditCurve[15+i] = Base::Vector2d(centerPoint.x + rx, centerPoint.y + ry);
}
// Display radius for user
SbString text;
text.sprintf(" (%.1fR,%.1fR)", a, b);
setPositionText(onSketchPos, text);
}
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr3);
return;
}
}
else if (Mode==STATUS_SEEK_Fourth) {
// angle between the major axis of the hyperbola and the X axis
double a = (axisPoint-centerPoint).Length();
double phi = atan2(axisPoint.y-centerPoint.y,axisPoint.x-centerPoint.x);
// This is the angle at cursor point
double angleatstartingpoint = acosh(((startingPoint.x-centerPoint.x)*cos(phi)+(startingPoint.y-centerPoint.y)*sin(phi))/a);
double b=(startingPoint.y-centerPoint.y-a*cosh(angleatstartingpoint)*sin(phi))/(sinh(angleatstartingpoint)*cos(phi));
double startAngle = angleatstartingpoint;
//double angleatpoint = acosh(((onSketchPos.x-centerPoint.x)*cos(phi)+(onSketchPos.y-centerPoint.y)*sin(phi))/a);
double angleatpoint = atanh( (((onSketchPos.y-centerPoint.y)*cos(phi)-(onSketchPos.x-centerPoint.x)*sin(phi))*a) /
(((onSketchPos.x-centerPoint.x)*cos(phi)+(onSketchPos.y-centerPoint.y)*sin(phi))*b) );
/*double angle1 = angleatpoint - startAngle;
double angle2 = angle1 + (angle1 < 0. ? 2 : -2) * M_PI ;
arcAngle = abs(angle1-arcAngle) < abs(angle2-arcAngle) ? angle1 : angle2;*/
arcAngle = angleatpoint - startAngle;
//if(!boost::math::isnan(angle1) && !boost::math::isnan(angle2)){
if (!boost::math::isnan(arcAngle)) {
EditCurve.resize(33);
for (int i=0; i < 33; i++) {
// P(U) = O + MajRad*Cosh(U)*XDir + MinRad*Sinh(U)*YDir
//double angle=i*angleatpoint/16;
double angle = startAngle+i*arcAngle/32.0;
double rx = a * cosh(angle) * cos(phi) - b * sinh(angle) * sin(phi);
double ry = a * cosh(angle) * sin(phi) + b * sinh(angle) * cos(phi);
EditCurve[i] = Base::Vector2d(centerPoint.x + rx, centerPoint.y + ry);
}
// Display radius for user
SbString text;
text.sprintf(" (%.1fR,%.1fR)", a, b);
setPositionText(onSketchPos, text);
}
else {
arcAngle=0.;
}
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr4, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr4);
return;
}
}
applyCursor();
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_First){
EditCurve[0] = onSketchPos;
centerPoint = onSketchPos;
EditCurve.resize(2);
Mode = STATUS_SEEK_Second;
}
else if(Mode==STATUS_SEEK_Second) {
EditCurve[1] = onSketchPos;
axisPoint = onSketchPos;
EditCurve.resize(31);
Mode = STATUS_SEEK_Third;
}
else if(Mode==STATUS_SEEK_Third) {
startingPoint = onSketchPos;
arcAngle = 0.;
arcAngle_t= 0.;
Mode = STATUS_SEEK_Fourth;
}
else { // Fourth
endPoint = onSketchPos;
Mode = STATUS_Close;
}
return true;
}
virtual bool releaseButton(Base::Vector2d /*onSketchPos*/)
{
if (Mode==STATUS_Close) {
unsetCursor();
resetPositionText();
// angle between the major axis of the hyperbola and the X axis
double a = (axisPoint-centerPoint).Length();
double phi = atan2(axisPoint.y-centerPoint.y,axisPoint.x-centerPoint.x);
// This is the angle at cursor point
double angleatstartingpoint = acosh(((startingPoint.x-centerPoint.x)*cos(phi)+(startingPoint.y-centerPoint.y)*sin(phi))/a);
double b=(startingPoint.y-centerPoint.y-a*cosh(angleatstartingpoint)*sin(phi))/(sinh(angleatstartingpoint)*cos(phi));
double startAngle = angleatstartingpoint;
//double angleatpoint = acosh(((onSketchPos.x-centerPoint.x)*cos(phi)+(onSketchPos.y-centerPoint.y)*sin(phi))/a);
double endAngle = atanh( (((endPoint.y-centerPoint.y)*cos(phi)-(endPoint.x-centerPoint.x)*sin(phi))*a) /
(((endPoint.x-centerPoint.x)*cos(phi)+(endPoint.y-centerPoint.y)*sin(phi))*b) );
if (boost::math::isnan(startAngle) || boost::math::isnan(endAngle)) {
sketchgui->purgeHandler();
Base::Console().Error("Cannot create arc of hyperbola from invalid angles, try again!\n");
return false;
}
bool isOriginalArcCCW=true;
if (arcAngle > 0)
endAngle = startAngle + arcAngle;
else {
endAngle = startAngle;
startAngle += arcAngle;
isOriginalArcCCW=false;
}
Base::Vector2d majAxisDir,minAxisDir,minAxisPoint,majAxisPoint;
// We always create a CCW hyperbola, because we want our XY reference system to be in the +X +Y direction
// Our normal will then always be in the +Z axis (local +Z axis of the sketcher)
if(a>b)
{
// force second semidiameter to be perpendicular to first semidiamater
majAxisDir = axisPoint - centerPoint;
Base::Vector2d perp(-majAxisDir.y,majAxisDir.x);
perp.Normalize();
perp.Scale(abs(b));
minAxisPoint = centerPoint+perp;
majAxisPoint = centerPoint+majAxisDir;
}
else {
// force second semidiameter to be perpendicular to first semidiamater
minAxisDir = axisPoint - centerPoint;
Base::Vector2d perp(minAxisDir.y,-minAxisDir.x);
perp.Normalize();
perp.Scale(abs(b));
majAxisPoint = centerPoint+perp;
minAxisPoint = centerPoint+minAxisDir;
endAngle += M_PI/2;
startAngle += M_PI/2;
}
int currentgeoid = getHighestCurveIndex();
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch arc of hyperbola"));
//Add arc of hyperbola, point and constrain point as focus2. We add focus2 for it to balance
//the intrinsic focus1, in order to balance out the intrinsic invisible focus1 when AOE is
//dragged by its center
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.ArcOfHyperbola"
"(Part.Hyperbola(App.Vector(%f,%f,0),App.Vector(%f,%f,0),App.Vector(%f,%f,0)),%f,%f),%s)",
majAxisPoint.x, majAxisPoint.y,
minAxisPoint.x, minAxisPoint.y,
centerPoint.x, centerPoint.y,
startAngle, endAngle,
geometryCreationMode==Construction?"True":"False");
currentgeoid++;
Gui::cmdAppObjectArgs(sketchgui->getObject(), "exposeInternalGeometry(%d)", currentgeoid);
}
catch (const Base::Exception& e) {
Base::Console().Error("%s\n", e.what());
Gui::Command::abortCommand();
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
return false;
}
Gui::Command::commitCommand();
// add auto constraints for the center point
if (sugConstr1.size() > 0) {
createAutoConstraints(sugConstr1, currentgeoid, Sketcher::mid);
sugConstr1.clear();
}
// add suggested constraints for arc
if (sugConstr2.size() > 0) {
createAutoConstraints(sugConstr2, currentgeoid, Sketcher::none);
sugConstr2.clear();
}
// add suggested constraints for start of arc
if (sugConstr3.size() > 0) {
createAutoConstraints(sugConstr3, currentgeoid, isOriginalArcCCW?Sketcher::start:Sketcher::end);
sugConstr3.clear();
}
// add suggested constraints for start of arc
if (sugConstr4.size() > 0) {
createAutoConstraints(sugConstr4, currentgeoid, isOriginalArcCCW?Sketcher::end:Sketcher::start);
sugConstr4.clear();
}
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if(continuousMode){
// This code enables the continuous creation mode.
Mode = STATUS_SEEK_First;
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
EditCurve.resize(34);
applyCursor();
/* It is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
}
else{
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
}
}
return true;
}
protected:
SelectMode Mode;
std::vector<Base::Vector2d> EditCurve;
Base::Vector2d centerPoint, axisPoint, startingPoint, endPoint;
double arcAngle, arcAngle_t;
std::vector<AutoConstraint> sugConstr1, sugConstr2, sugConstr3, sugConstr4;
};
DEF_STD_CMD_A(CmdSketcherCreateArcOfHyperbola)
CmdSketcherCreateArcOfHyperbola::CmdSketcherCreateArcOfHyperbola()
: Command("Sketcher_CreateArcOfHyperbola")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create an arc of hyperbola");
sToolTipText = QT_TR_NOOP("Create an arc of hyperbola in the sketch");
sWhatsThis = "Sketcher_CreateArcOfHyperbola";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateHyperbolic_Arc";
eType = ForEdit;
}
void CmdSketcherCreateArcOfHyperbola::activated(int /*iMsg*/)
{
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerArcOfHyperbola() );
}
bool CmdSketcherCreateArcOfHyperbola::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
class DrawSketchHandlerArcOfParabola : public DrawSketchHandler
{
public:
DrawSketchHandlerArcOfParabola()
: Mode(STATUS_SEEK_First)
, EditCurve(34)
, startAngle(0)
, endAngle(0)
, arcAngle(0)
, arcAngle_t(0)
{
}
virtual ~DrawSketchHandlerArcOfParabola(){}
/// mode table
enum SelectMode {
STATUS_SEEK_First, /**< enum value ----. */
STATUS_SEEK_Second, /**< enum value ----. */
STATUS_SEEK_Third, /**< enum value ----. */
STATUS_SEEK_Fourth, /**< enum value ----. */
STATUS_Close
};
virtual void activated(ViewProviderSketch * /*sketchgui*/)
{
setCrosshairCursor("Sketcher_Pointer_Create_ArcOfParabola");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_First) {
setPositionText(onSketchPos);
if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr1);
return;
}
}
else if (Mode==STATUS_SEEK_Second) {
EditCurve[1]= onSketchPos;
// Display radius for user
float radius = (onSketchPos - focusPoint).Length();
SbString text;
text.sprintf(" (F%.1f)", radius);
setPositionText(onSketchPos, text);
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr2);
return;
}
}
else if (Mode==STATUS_SEEK_Third) {
double focal = (axisPoint-focusPoint).Length();
double phi = atan2(focusPoint.y-axisPoint.y,focusPoint.x-axisPoint.x);
// P(U) = O + U*U/(4.*F)*XDir + U*YDir
//
// pnt = Base::Vector3d(pnt0.x + angle * angle / 4 / focal * cos(phi) - angle * sin(phi),
// pnt0.y + angle * angle / 4 / focal * sin(phi) + angle * cos(phi),
// 0.f);
// This is the angle at cursor point
double u =
( cos(phi) * (onSketchPos.y - axisPoint.y) - (onSketchPos.x - axisPoint.x) * sin(phi));
for (int i=15; i >= -15; i--) {
double angle=i*u/15;
double rx = angle * angle / 4 / focal * cos(phi) - angle * sin(phi);
double ry = angle * angle / 4 / focal * sin(phi) + angle * cos(phi);
EditCurve[15+i] = Base::Vector2d(axisPoint.x + rx, axisPoint.y + ry);
}
// Display radius for user
SbString text;
text.sprintf(" (F%.1f)", focal);
setPositionText(onSketchPos, text);
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr3);
return;
}
}
else if (Mode==STATUS_SEEK_Fourth) {
double focal = (axisPoint-focusPoint).Length();
double phi = atan2(focusPoint.y-axisPoint.y,focusPoint.x-axisPoint.x);
// P(U) = O + U*U/(4.*F)*XDir + U*YDir
//
// pnt = Base::Vector3d(pnt0.x + angle * angle / 4 / focal * cos(phi) - angle * sin(phi),
// pnt0.y + angle * angle / 4 / focal * sin(phi) + angle * cos(phi),
// 0.f);
// This is the angle at starting point
double ustartpoint =
( cos(phi) * (startingPoint.y - axisPoint.y) - (startingPoint.x - axisPoint.x) * sin(phi));
double startValue = ustartpoint;
double u =
( cos(phi) * (onSketchPos.y - axisPoint.y) - (onSketchPos.x - axisPoint.x) * sin(phi));
arcAngle = u - startValue;
if (!boost::math::isnan(arcAngle)) {
EditCurve.resize(33);
for (std::size_t i=0; i < 33; i++) {
double angle = startValue+i*arcAngle/32.0;
double rx = angle * angle / 4 / focal * cos(phi) - angle * sin(phi);
double ry = angle * angle / 4 / focal * sin(phi) + angle * cos(phi);
EditCurve[i] = Base::Vector2d(axisPoint.x + rx, axisPoint.y + ry);
}
SbString text;
text.sprintf(" (F%.1f)", focal);
setPositionText(onSketchPos, text);
}
else {
arcAngle=0.;
}
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr4, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr4);
return;
}
}
applyCursor();
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_First){
EditCurve[0] = onSketchPos;
focusPoint = onSketchPos;
EditCurve.resize(2);
Mode = STATUS_SEEK_Second;
}
else if(Mode==STATUS_SEEK_Second) {
EditCurve[1] = onSketchPos;
axisPoint = onSketchPos;
EditCurve.resize(31);
Mode = STATUS_SEEK_Third;
}
else if(Mode==STATUS_SEEK_Third) {
startingPoint = onSketchPos;
arcAngle = 0.;
arcAngle_t= 0.;
Mode = STATUS_SEEK_Fourth;
}
else { // Fourth
endPoint = onSketchPos;
Mode = STATUS_Close;
}
return true;
}
virtual bool releaseButton(Base::Vector2d /*onSketchPos*/)
{
if (Mode==STATUS_Close) {
unsetCursor();
resetPositionText();
double phi = atan2(focusPoint.y-axisPoint.y,focusPoint.x-axisPoint.x);
double ustartpoint =
( cos(phi) * (startingPoint.y - axisPoint.y) - (startingPoint.x - axisPoint.x) * sin(phi));
double uendpoint =
( cos(phi) * (endPoint.y - axisPoint.y) - (endPoint.x - axisPoint.x) * sin(phi));
double startAngle = ustartpoint;
double endAngle = uendpoint;
bool isOriginalArcCCW=true;
if (arcAngle > 0) {
endAngle = startAngle + arcAngle;
}
else {
endAngle = startAngle;
startAngle += arcAngle;
isOriginalArcCCW=false;
}
int currentgeoid = getHighestCurveIndex();
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch arc of Parabola"));
//Add arc of parabola
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.ArcOfParabola"
"(Part.Parabola(App.Vector(%f,%f,0),App.Vector(%f,%f,0),App.Vector(0,0,1)),%f,%f),%s)",
focusPoint.x, focusPoint.y,
axisPoint.x, axisPoint.y,
startAngle, endAngle,
geometryCreationMode==Construction?"True":"False");
currentgeoid++;
Gui::cmdAppObjectArgs(sketchgui->getObject(), "exposeInternalGeometry(%d)", currentgeoid);
}
catch (const Base::Exception& e) {
Base::Console().Error("%s\n", e.what());
Gui::Command::abortCommand();
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
return false;
}
Gui::Command::commitCommand();
// add auto constraints for the focus point
if (sugConstr1.size() > 0) {
createAutoConstraints(sugConstr1, currentgeoid+1, Sketcher::start);
sugConstr1.clear();
}
// add suggested constraints for vertex point
if (sugConstr2.size() > 0) {
createAutoConstraints(sugConstr2, currentgeoid, Sketcher::mid);
sugConstr2.clear();
}
// add suggested constraints for start of arc
if (sugConstr3.size() > 0) {
createAutoConstraints(sugConstr3, currentgeoid, isOriginalArcCCW?Sketcher::start:Sketcher::end);
sugConstr3.clear();
}
// add suggested constraints for start of arc
if (sugConstr4.size() > 0) {
createAutoConstraints(sugConstr4, currentgeoid, isOriginalArcCCW?Sketcher::end:Sketcher::start);
sugConstr4.clear();
}
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if (continuousMode) {
// This code enables the continuous creation mode.
Mode = STATUS_SEEK_First;
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
EditCurve.resize(34);
applyCursor();
/* It is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
}
else {
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
}
}
return true;
}
protected:
SelectMode Mode;
std::vector<Base::Vector2d> EditCurve;
Base::Vector2d focusPoint, axisPoint, startingPoint, endPoint;
double startAngle, endAngle, arcAngle, arcAngle_t;
std::vector<AutoConstraint> sugConstr1, sugConstr2, sugConstr3, sugConstr4;
};
DEF_STD_CMD_A(CmdSketcherCreateArcOfParabola)
CmdSketcherCreateArcOfParabola::CmdSketcherCreateArcOfParabola()
: Command("Sketcher_CreateArcOfParabola")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create an arc of parabola");
sToolTipText = QT_TR_NOOP("Create an arc of parabola in the sketch");
sWhatsThis = "Sketcher_CreateArcOfParabola";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateParabolic_Arc";
eType = ForEdit;
}
void CmdSketcherCreateArcOfParabola::activated(int /*iMsg*/)
{
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerArcOfParabola() );
}
bool CmdSketcherCreateArcOfParabola::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
/// @brief Macro that declares a new sketcher command class 'CmdSketcherCompCreateEllipse'
DEF_STD_CMD_ACLU(CmdSketcherCompCreateConic)
/**
* @brief ctor
*/
CmdSketcherCompCreateConic::CmdSketcherCompCreateConic()
: Command("Sketcher_CompCreateConic")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create a conic");
sToolTipText = QT_TR_NOOP("Create a conic in the sketch");
sWhatsThis = "Sketcher_CompCreateConic";
sStatusTip = sToolTipText;
eType = ForEdit;
}
/**
* @brief Instantiates the conic handler when the conic command activated
* @param int iMsg
*/
void CmdSketcherCompCreateConic::activated(int iMsg)
{
if (iMsg == 0) {
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerEllipse(iMsg));
} else if (iMsg == 1) {
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerEllipse(iMsg));
} else if (iMsg == 2) {
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerArcOfEllipse());
} else if (iMsg == 3) {
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerArcOfHyperbola());
} else if (iMsg == 4) {
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerArcOfParabola());
} else {
return;
}
// 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 * CmdSketcherCompCreateConic::createAction(void)
{
Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow());
pcAction->setDropDownMenu(true);
applyCommandData(this->className(), pcAction);
QAction* ellipseByCenter = pcAction->addAction(QString());
ellipseByCenter->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateEllipse"));
/// @todo replace with correct icon
QAction* ellipseBy3Points = pcAction->addAction(QString());
ellipseBy3Points->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateEllipse_3points"));
QAction* arcofellipse = pcAction->addAction(QString());
arcofellipse->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateElliptical_Arc"));
QAction* arcofhyperbola = pcAction->addAction(QString());
arcofhyperbola->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateHyperbolic_Arc"));
QAction* arcofparabola = pcAction->addAction(QString());
arcofparabola->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateParabolic_Arc"));
_pcAction = pcAction;
languageChange();
// set ellipse by center, a, b as default method
pcAction->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Conics"));
int defaultId = 0;
pcAction->setProperty("defaultAction", QVariant(defaultId));
return pcAction;
}
void CmdSketcherCompCreateConic::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 Normal:
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateEllipse"));
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateEllipse_3points"));
a[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateElliptical_Arc"));
a[3]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateHyperbolic_Arc"));
a[4]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateParabolic_Arc"));
getAction()->setIcon(a[index]->icon());
break;
case Construction:
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateEllipse_Constr"));
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateEllipse_3points_Constr"));
a[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateElliptical_Arc_Constr"));
a[3]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateHyperbolic_Arc_Constr"));
a[4]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateParabolic_Arc_Constr"));
getAction()->setIcon(a[index]->icon());
break;
}
}
void CmdSketcherCompCreateConic::languageChange()
{
Command::languageChange();
if (!_pcAction)
return;
Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
QList<QAction*> a = pcAction->actions();
QAction* ellipseByCenter = a[0];
ellipseByCenter->setText(QApplication::translate("CmdSketcherCompCreateConic","Ellipse by center, major radius, point"));
ellipseByCenter->setToolTip(QApplication::translate("Sketcher_CreateEllipseByCenter","Create an ellipse by center, major radius and point"));
ellipseByCenter->setStatusTip(QApplication::translate("Sketcher_CreateEllipseByCenter","Create an ellipse by center, major radius and point"));
QAction* ellipseBy3Points = a[1];
ellipseBy3Points->setText(QApplication::translate("CmdSketcherCompCreateConic","Ellipse by periapsis, apoapsis, minor radius"));
ellipseBy3Points->setToolTip(QApplication::translate("Sketcher_CreateEllipseBy3Points","Create a ellipse by periapsis, apoapsis, and minor radius"));
ellipseBy3Points->setStatusTip(QApplication::translate("Sketcher_CreateEllipseBy3Points","Create a ellipse by periapsis, apoapsis, and minor radius"));
QAction* arcofellipse = a[2];
arcofellipse->setText(QApplication::translate("CmdSketcherCompCreateConic","Arc of ellipse by center, major radius, endpoints"));
arcofellipse->setToolTip(QApplication::translate("Sketcher_CreateArcOfEllipse","Create an arc of ellipse by its center, major radius, and endpoints"));
arcofellipse->setStatusTip(QApplication::translate("Sketcher_CreateArcOfEllipse","Create an arc of ellipse by its center, major radius, and endpoints"));
QAction* arcofhyperbola = a[3];
arcofhyperbola->setText(QApplication::translate("CmdSketcherCompCreateConic","Arc of hyperbola by center, major radius, endpoints"));
arcofhyperbola->setToolTip(QApplication::translate("Sketcher_CreateArcOfHyperbola","Create an arc of hyperbola by its center, major radius, and endpoints"));
arcofhyperbola->setStatusTip(QApplication::translate("Sketcher_CreateArcOfHyperbola","Create an arc of hyperbola by its center, major radius, and endpoints"));
QAction* arcofparabola = a[4];
arcofparabola->setText(QApplication::translate("CmdSketcherCompCreateConic","Arc of parabola by focus, vertex, endpoints"));
arcofparabola->setToolTip(QApplication::translate("Sketcher_CreateArcOfParabola","Create an arc of parabola by its focus, vertex, and endpoints"));
arcofparabola->setStatusTip(QApplication::translate("Sketcher_CreateArcOfParabola","Create an arc of parabola by its focus, vertex, and endpoints"));
}
bool CmdSketcherCompCreateConic::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
// ======================================================================================
class DrawSketchHandlerBSpline: public DrawSketchHandler
{
public:
DrawSketchHandlerBSpline(int constructionMethod)
: Mode(STATUS_SEEK_FIRST_CONTROLPOINT)
, EditCurve(2)
, CurrentConstraint(0)
, ConstrMethod(constructionMethod)
, IsClosed(false)
, FirstPoleGeoId(-2000)
{
std::vector<AutoConstraint> sugConstr1;
sugConstr.push_back(sugConstr1);
}
virtual ~DrawSketchHandlerBSpline() {}
/// modes
enum SELECT_MODE {
STATUS_SEEK_FIRST_CONTROLPOINT,
STATUS_SEEK_ADDITIONAL_CONTROLPOINTS,
STATUS_CLOSE
};
virtual void activated(ViewProviderSketch *)
{
setCrosshairCursor("Sketcher_Pointer_Create_BSpline");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_FIRST_CONTROLPOINT) {
setPositionText(onSketchPos);
if (seekAutoConstraint(sugConstr[CurrentConstraint], onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr[CurrentConstraint]);
return;
}
}
else if (Mode==STATUS_SEEK_ADDITIONAL_CONTROLPOINTS){
EditCurve[EditCurve.size()-1] = onSketchPos;
sketchgui->drawEdit(EditCurve);
float length = (EditCurve[EditCurve.size()-1] - EditCurve[EditCurve.size()-2]).Length();
float angle = (EditCurve[EditCurve.size()-1] - EditCurve[EditCurve.size()-2]).GetAngle(Base::Vector2d(1.f,0.f));
SbString text;
text.sprintf(" (%.1f,%.1fdeg)", length, angle * 180 / M_PI);
setPositionText(EditCurve[EditCurve.size()-1], text);
if (seekAutoConstraint(sugConstr[CurrentConstraint], onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr[CurrentConstraint]);
return;
}
}
applyCursor();
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
if (Mode == STATUS_SEEK_FIRST_CONTROLPOINT) {
EditCurve[0] = onSketchPos;
Mode = STATUS_SEEK_ADDITIONAL_CONTROLPOINTS;
// insert circle point for pole, defer internal alignment constraining.
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add Pole circle"));
//Add pole
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.Circle(App.Vector(%f,%f,0),App.Vector(0,0,1),10),True)",
EditCurve[0].x,EditCurve[0].y);
}
catch (const Base::Exception& e) {
Base::Console().Error("%s\n", e.what());
Gui::Command::abortCommand();
static_cast<Sketcher::SketchObject *>(sketchgui->getObject())->solve();
return false;
}
//Gui::Command::commitCommand();
//static_cast<Sketcher::SketchObject *>(sketchgui->getObject())->solve();
FirstPoleGeoId = getHighestCurveIndex();
// add auto constraints on pole
if (sugConstr[CurrentConstraint].size() > 0) {
createAutoConstraints(sugConstr[CurrentConstraint], FirstPoleGeoId, Sketcher::mid, false);
}
static_cast<Sketcher::SketchObject *>(sketchgui->getObject())->solve();
std::vector<AutoConstraint> sugConstrN;
sugConstr.push_back(sugConstrN);
CurrentConstraint++;
}
else if (Mode == STATUS_SEEK_ADDITIONAL_CONTROLPOINTS) {
EditCurve[EditCurve.size()-1] = onSketchPos;
// check if coincident with first pole
for(std::vector<AutoConstraint>::const_iterator it = sugConstr[CurrentConstraint].begin(); it != sugConstr[CurrentConstraint].end(); it++) {
if( (*it).Type == Sketcher::Coincident && (*it).GeoId == FirstPoleGeoId && (*it).PosId == Sketcher::mid ) {
IsClosed = true;
}
}
if (IsClosed) {
Mode = STATUS_CLOSE;
if (ConstrMethod == 1) { // if periodic we do not need the last pole
EditCurve.pop_back();
sugConstr.pop_back();
return true;
}
}
// insert circle point for pole, defer internal alignment constraining.
try {
//Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add Pole circle"));
//Add pole
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.Circle(App.Vector(%f,%f,0),App.Vector(0,0,1),10),True)",
EditCurve[EditCurve.size()-1].x,EditCurve[EditCurve.size()-1].y);
if(EditCurve.size() == 2) {
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Weight',%d,%f)) ",
FirstPoleGeoId, 1.0 ); // First pole defaults to 1.0 weight
}
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Equal',%d,%d)) ",
FirstPoleGeoId, FirstPoleGeoId+ EditCurve.size()-1);
}
catch (const Base::Exception& e) {
Base::Console().Error("%s\n", e.what());
Gui::Command::abortCommand();
static_cast<Sketcher::SketchObject *>(sketchgui->getObject())->solve();
return false;
}
//Gui::Command::commitCommand();
//static_cast<Sketcher::SketchObject *>(sketchgui->getObject())->solve();
// add auto constraints on pole
if (sugConstr[CurrentConstraint].size() > 0) {
createAutoConstraints(sugConstr[CurrentConstraint], FirstPoleGeoId + EditCurve.size()-1, Sketcher::mid, false);
}
//static_cast<Sketcher::SketchObject *>(sketchgui->getObject())->solve();
if (!IsClosed) {
EditCurve.resize(EditCurve.size() + 1); // add one place for a pole
std::vector<AutoConstraint> sugConstrN;
sugConstr.push_back(sugConstrN);
CurrentConstraint++;
}
}
return true;
}
virtual bool releaseButton(Base::Vector2d /*onSketchPos*/)
{
if (Mode==STATUS_CLOSE) {
unsetCursor();
resetPositionText();
std::stringstream stream;
for (std::vector<Base::Vector2d>::const_iterator it=EditCurve.begin();
it != EditCurve.end(); ++it) {
stream << "App.Vector(" << (*it).x << "," << (*it).y << "),";
}
std::string controlpoints = stream.str();
// remove last comma and add brackets
int index = controlpoints.rfind(',');
controlpoints.resize(index);
controlpoints.insert(0,1,'[');
controlpoints.append(1,']');
int currentgeoid = getHighestCurveIndex();
try {
//Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add B-spline curve"));
/*Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.BSplineCurve"
"(%s,%s),"
"%s)",
controlpoints.c_str(),
ConstrMethod == 0 ?"False":"True",
geometryCreationMode==Construction?"True":"False"); */
// {"poles", "mults", "knots", "periodic", "degree", "weights", "CheckRational", NULL};
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.BSplineCurve"
"(%s,None,None,%s,3,None,False),%s)",
controlpoints.c_str(),
ConstrMethod == 0 ?"False":"True",
geometryCreationMode==Construction?"True":"False");
currentgeoid++;
// autoconstraints were added to the circles of the poles, which is ok because they must go to the
// right position, or the user will freak-out if they appear out of the autoconstrained position.
// However, autoconstrains on the first and last pole, in normal non-periodic b-splines (with appropriate endpoint knot multiplicity)
// as the ones created by this tool are intended for the b-spline endpoints, and not for the poles,
// so here we retrieve any autoconstraint on those poles' center and mangle it to the endpoint.
if (ConstrMethod == 0) {
for(auto & constr : static_cast<Sketcher::SketchObject *>(sketchgui->getObject())->Constraints.getValues()) {
if(constr->First == FirstPoleGeoId && constr->FirstPos == Sketcher::mid) {
constr->First = currentgeoid;
constr->FirstPos = Sketcher::start;
}
else if(constr->First == (FirstPoleGeoId + CurrentConstraint - 1) && constr->FirstPos == Sketcher::mid) {
constr->First = currentgeoid;
constr->FirstPos = Sketcher::end;
}
}
}
// Constraint pole circles to B-spline.
std::stringstream cstream;
cstream << "conList = []\n";
for (size_t i = 0; i < EditCurve.size(); i++) {
cstream << "conList.append(Sketcher.Constraint('InternalAlignment:Sketcher::BSplineControlPoint'," << FirstPoleGeoId+i
<< "," << Sketcher::mid << "," << currentgeoid << "," << i << "))\n";
}
cstream << Gui::Command::getObjectCmd(sketchgui->getObject()) << ".addConstraint(conList)\n";
Gui::Command::doCommand(Gui::Command::Doc, cstream.str().c_str());
// for showing the knots on creation
Gui::cmdAppObjectArgs(sketchgui->getObject(), "exposeInternalGeometry(%d)", currentgeoid);
}
catch (const Base::Exception& e) {
Base::Console().Error("%s\n", e.what());
Gui::Command::abortCommand();
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
return false;
}
Gui::Command::commitCommand();
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if(continuousMode){
// This code enables the continuous creation mode.
Mode = STATUS_SEEK_FIRST_CONTROLPOINT;
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
EditCurve.resize(2);
applyCursor();
sugConstr.clear();
std::vector<AutoConstraint> sugConstr1;
sugConstr.push_back(sugConstr1);
CurrentConstraint=0;
IsClosed=false;
/* It is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
}
else{
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
}
}
return true;
}
virtual void quit(void) {
// We must see if we need to create a B-spline before cancelling everything
// and now just like any other Handler,
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if (CurrentConstraint > 1) {
// create B-spline from existing poles
Mode=STATUS_CLOSE;
EditCurve.pop_back();
this->releaseButton(Base::Vector2d(0.f,0.f));
}
else if(CurrentConstraint == 1) {
// if we just have one point and we can not close anything, then cancel this creation but continue according to continuous mode
//sketchgui->getDocument()->undo(1);
Gui::Command::abortCommand();
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
if(!continuousMode){
DrawSketchHandler::quit();
}
else {
// This code disregards existing data and enables the continuous creation mode.
Mode = STATUS_SEEK_FIRST_CONTROLPOINT;
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
EditCurve.resize(2);
applyCursor();
sugConstr.clear();
std::vector<AutoConstraint> sugConstr1;
sugConstr.push_back(sugConstr1);
CurrentConstraint=0;
IsClosed=false;
}
}
else { // we have no data (CurrentConstraint == 0) so user when right-clicking really wants to exit
DrawSketchHandler::quit();
}
}
protected:
SELECT_MODE Mode;
std::vector<Base::Vector2d> EditCurve;
std::vector<std::vector<AutoConstraint>> sugConstr;
int CurrentConstraint;
int ConstrMethod;
bool IsClosed;
int FirstPoleGeoId;
};
DEF_STD_CMD_A(CmdSketcherCreateBSpline)
CmdSketcherCreateBSpline::CmdSketcherCreateBSpline()
: Command("Sketcher_CreateBSpline")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create B-spline");
sToolTipText = QT_TR_NOOP("Create a B-spline via control points in the sketch.");
sWhatsThis = "Sketcher_CreateBSpline";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateBSpline";
eType = ForEdit;
}
void CmdSketcherCreateBSpline::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerBSpline(0) );
}
/*void CmdSketcherCreateBSpline::updateAction(int mode)
{
switch (mode) {
case Normal:
if (getAction())
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateBSpline"));
break;
case Construction:
if (getAction())
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateBSpline_Constr"));
break;
}
}*/
bool CmdSketcherCreateBSpline::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
/// @brief Macro that declares a new sketcher command class 'CmdSketcherCreateBSpline'
DEF_STD_CMD_A(CmdSketcherCreatePeriodicBSpline)
/**
* @brief ctor
*/
CmdSketcherCreatePeriodicBSpline::CmdSketcherCreatePeriodicBSpline()
: Command("Sketcher_CreatePeriodicBSpline")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create periodic B-spline");
sToolTipText = QT_TR_NOOP("Create a periodic B-spline via control points in the sketch.");
sWhatsThis = "Sketcher_CreatePeriodicBSpline";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_Create_Periodic_BSpline";
eType = ForEdit;
}
void CmdSketcherCreatePeriodicBSpline::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerBSpline(1) );
}
bool CmdSketcherCreatePeriodicBSpline::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
/// @brief Macro that declares a new sketcher command class 'CmdSketcherCompCreateBSpline'
DEF_STD_CMD_ACLU(CmdSketcherCompCreateBSpline)
/**
* @brief ctor
*/
CmdSketcherCompCreateBSpline::CmdSketcherCompCreateBSpline()
: Command("Sketcher_CompCreateBSpline")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create a B-spline");
sToolTipText = QT_TR_NOOP("Create a B-spline in the sketch");
sWhatsThis = "Sketcher_CompCreateBSpline";
sStatusTip = sToolTipText;
eType = ForEdit;
}
/**
* @brief Instantiates the B-spline handler when the B-spline command activated
* @param int iMsg
*/
void CmdSketcherCompCreateBSpline::activated(int iMsg)
{
if (iMsg == 0) {
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBSpline(iMsg));
} else if (iMsg == 1) {
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBSpline(iMsg));
} else {
return;
}
// 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 * CmdSketcherCompCreateBSpline::createAction(void)
{
Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow());
pcAction->setDropDownMenu(true);
applyCommandData(this->className(), pcAction);
QAction* bspline = pcAction->addAction(QString());
bspline->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateBSpline"));
QAction* periodicbspline = pcAction->addAction(QString());
periodicbspline->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Create_Periodic_BSpline"));
_pcAction = pcAction;
languageChange();
// default
pcAction->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateBSpline"));
int defaultId = 0;
pcAction->setProperty("defaultAction", QVariant(defaultId));
return pcAction;
}
void CmdSketcherCompCreateBSpline::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 Normal:
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateBSpline"));
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Create_Periodic_BSpline"));
getAction()->setIcon(a[index]->icon());
break;
case Construction:
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateBSpline_Constr"));
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Create_Periodic_BSpline_Constr"));
getAction()->setIcon(a[index]->icon());
break;
}
}
void CmdSketcherCompCreateBSpline::languageChange()
{
Command::languageChange();
if (!_pcAction)
return;
Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
QList<QAction*> a = pcAction->actions();
QAction* bspline = a[0];
bspline->setText(QApplication::translate("Sketcher_CreateBSpline","B-spline by control points"));
bspline->setToolTip(QApplication::translate("Sketcher_CreateBSpline","Create a B-spline by control points"));
bspline->setStatusTip(QApplication::translate("Sketcher_CreateBSpline","Create a B-spline by control points"));
QAction* periodicbspline = a[1];
periodicbspline->setText(QApplication::translate("Sketcher_Create_Periodic_BSpline","Periodic B-spline by control points"));
periodicbspline->setToolTip(QApplication::translate("Sketcher_Create_Periodic_BSpline","Create a periodic B-spline by control points"));
periodicbspline->setStatusTip(QApplication::translate("Sketcher_Create_Periodic_BSpline","Create a periodic B-spline by control points"));
}
bool CmdSketcherCompCreateBSpline::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
// ======================================================================================
class DrawSketchHandler3PointCircle : public DrawSketchHandler
{
public:
DrawSketchHandler3PointCircle()
: Mode(STATUS_SEEK_First),EditCurve(2),radius(1),N(32.0){}
virtual ~DrawSketchHandler3PointCircle(){}
/// mode table
enum SelectMode {
STATUS_SEEK_First, /**< enum value ----. */
STATUS_SEEK_Second, /**< enum value ----. */
STATUS_SEEK_Third, /**< enum value ----. */
STATUS_End
};
virtual void activated(ViewProviderSketch *)
{
setCrosshairCursor("Sketcher_Pointer_Create_3PointCircle");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
if (Mode == STATUS_SEEK_First) {
setPositionText(onSketchPos);
if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f),
AutoConstraint::CURVE)) {
renderSuggestConstraintsCursor(sugConstr1);
return;
}
}
else if (Mode == STATUS_SEEK_Second || Mode == STATUS_SEEK_Third) {
try
{
if (Mode == STATUS_SEEK_Second)
CenterPoint = EditCurve[N+1] = (onSketchPos - FirstPoint)/2 + FirstPoint;
else
CenterPoint = EditCurve[N+1] = GetCircleCenter(FirstPoint, SecondPoint, onSketchPos);
radius = (onSketchPos - CenterPoint).Length();
double lineAngle = GetPointAngle(CenterPoint, onSketchPos);
// Build a N point circle
for (int i=1; i < N; i++) {
// Start at current angle
double angle = i*2*M_PI/N + lineAngle; // N point closed circle has N segments
EditCurve[i] = Base::Vector2d(CenterPoint.x + radius*cos(angle),
CenterPoint.y + radius*sin(angle));
}
// Beginning and end of curve should be exact
EditCurve[0] = EditCurve[N] = onSketchPos;
// Display radius and start angle
// This lineAngle will report counter-clockwise from +X, not relatively
SbString text;
text.sprintf(" (%.1fR,%.1fdeg)", (float) radius, (float) lineAngle * 180 / M_PI);
setPositionText(onSketchPos, text);
sketchgui->drawEdit(EditCurve);
if (Mode == STATUS_SEEK_Second) {
if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f),
AutoConstraint::CURVE)) {
renderSuggestConstraintsCursor(sugConstr2);
return;
}
}
else {
if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.f,0.f),
AutoConstraint::CURVE)) {
renderSuggestConstraintsCursor(sugConstr3);
return;
}
}
}
catch(Base::ValueError &e) {
e.ReportException();
}
}
applyCursor();
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
if (Mode == STATUS_SEEK_First) {
// N point curve + center + endpoint
EditCurve.resize(N+2);
FirstPoint = onSketchPos;
Mode = STATUS_SEEK_Second;
}
else if (Mode == STATUS_SEEK_Second) {
SecondPoint = onSketchPos;
Mode = STATUS_SEEK_Third;
}
else {
EditCurve.resize(N);
sketchgui->drawEdit(EditCurve);
applyCursor();
Mode = STATUS_End;
}
return true;
}
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
// Need to look at. rx might need fixing.
if (Mode==STATUS_End) {
unsetCursor();
resetPositionText();
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch circle"));
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.Circle"
"(App.Vector(%f,%f,0),App.Vector(0,0,1),%f),%s)",
CenterPoint.x, CenterPoint.y,
radius,
geometryCreationMode==Construction?"True":"False");
Gui::Command::commitCommand();
}
catch (const Base::Exception& e) {
Base::Console().Error("Failed to add circle: %s\n", e.what());
Gui::Command::abortCommand();
}
// Auto Constraint first picked point
if (sugConstr1.size() > 0) {
createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::none);
sugConstr1.clear();
}
// Auto Constraint second picked point
if (sugConstr2.size() > 0) {
createAutoConstraints(sugConstr2, getHighestCurveIndex(), Sketcher::none);
sugConstr2.clear();
}
// Auto Constraint third picked point
if (sugConstr3.size() > 0) {
createAutoConstraints(sugConstr3, getHighestCurveIndex(), Sketcher::none);
sugConstr3.clear();
}
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if(continuousMode){
// This code enables the continuous creation mode.
Mode=STATUS_SEEK_First;
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
EditCurve.resize(2);
applyCursor();
/* this is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
}
else{
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
}
}
return true;
}
protected:
SelectMode Mode;
std::vector<Base::Vector2d> EditCurve;
Base::Vector2d CenterPoint, FirstPoint, SecondPoint;
double radius, N; // N should be even
std::vector<AutoConstraint> sugConstr1, sugConstr2, sugConstr3;
};
DEF_STD_CMD_A(CmdSketcherCreate3PointCircle)
CmdSketcherCreate3PointCircle::CmdSketcherCreate3PointCircle()
: Command("Sketcher_Create3PointCircle")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create circle by three points");
sToolTipText = QT_TR_NOOP("Create a circle by 3 perimeter points");
sWhatsThis = "Sketcher_Create3PointCircle";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_Create3PointCircle";
eType = ForEdit;
}
void CmdSketcherCreate3PointCircle::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandler3PointCircle() );
}
bool CmdSketcherCreate3PointCircle::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
DEF_STD_CMD_ACLU(CmdSketcherCompCreateCircle)
CmdSketcherCompCreateCircle::CmdSketcherCompCreateCircle()
: Command("Sketcher_CompCreateCircle")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create circle");
sToolTipText = QT_TR_NOOP("Create a circle in the sketcher");
sWhatsThis = "Sketcher_CompCreateCircle";
sStatusTip = sToolTipText;
eType = ForEdit;
}
void CmdSketcherCompCreateCircle::activated(int iMsg)
{
if (iMsg==0)
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerCircle());
else if (iMsg==1)
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandler3PointCircle());
else
return;
// 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 * CmdSketcherCompCreateCircle::createAction(void)
{
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("Sketcher_CreateCircle"));
QAction* arc2 = pcAction->addAction(QString());
arc2->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Create3PointCircle"));
_pcAction = pcAction;
languageChange();
pcAction->setIcon(arc1->icon());
int defaultId = 0;
pcAction->setProperty("defaultAction", QVariant(defaultId));
return pcAction;
}
void CmdSketcherCompCreateCircle::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 Normal:
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateCircle"));
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Create3PointCircle"));
getAction()->setIcon(a[index]->icon());
break;
case Construction:
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateCircle_Constr"));
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Create3PointCircle_Constr"));
getAction()->setIcon(a[index]->icon());
break;
}
}
void CmdSketcherCompCreateCircle::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("CmdSketcherCompCreateCircle", "Center and rim point"));
arc1->setToolTip(QApplication::translate("Sketcher_CreateCircle", "Create a circle by its center and by a rim point"));
arc1->setStatusTip(QApplication::translate("Sketcher_CreateCircle", "Create a circle by its center and by a rim point"));
QAction* arc2 = a[1];
arc2->setText(QApplication::translate("CmdSketcherCompCreateCircle", "3 rim points"));
arc2->setToolTip(QApplication::translate("Sketcher_Create3PointCircle", "Create a circle by 3 rim points"));
arc2->setStatusTip(QApplication::translate("Sketcher_Create3PointCircle", "Create a circle by 3 rim points"));
}
bool CmdSketcherCompCreateCircle::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
// ======================================================================================
class DrawSketchHandlerPoint: public DrawSketchHandler
{
public:
DrawSketchHandlerPoint() : selectionDone(false) {}
virtual ~DrawSketchHandlerPoint() {}
virtual void activated(ViewProviderSketch *)
{
setCrosshairCursor("Sketcher_Pointer_Create_Point");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
setPositionText(onSketchPos);
if (seekAutoConstraint(sugConstr, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr);
return;
}
applyCursor();
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
EditPoint = onSketchPos;
selectionDone = true;
return true;
}
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
if (selectionDone){
unsetCursor();
resetPositionText();
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch point"));
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.Point(App.Vector(%f,%f,0)))",
EditPoint.x,EditPoint.y);
Gui::Command::commitCommand();
}
catch (const Base::Exception& e) {
Base::Console().Error("Failed to add point: %s\n", e.what());
Gui::Command::abortCommand();
}
// add auto constraints for the line segment start
if (sugConstr.size() > 0) {
createAutoConstraints(sugConstr, getHighestCurveIndex(), Sketcher::start);
sugConstr.clear();
}
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if(continuousMode){
// This code enables the continuous creation mode.
applyCursor();
/* It is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
}
else{
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
}
}
return true;
}
protected:
bool selectionDone;
Base::Vector2d EditPoint;
std::vector<AutoConstraint> sugConstr;
};
DEF_STD_CMD_A(CmdSketcherCreatePoint)
CmdSketcherCreatePoint::CmdSketcherCreatePoint()
: Command("Sketcher_CreatePoint")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create point");
sToolTipText = QT_TR_NOOP("Create a point in the sketch");
sWhatsThis = "Sketcher_CreatePoint";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreatePoint";
eType = ForEdit;
}
void CmdSketcherCreatePoint::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerPoint());
}
bool CmdSketcherCreatePoint::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
// ======================================================================================
DEF_STD_CMD_A(CmdSketcherCreateText)
CmdSketcherCreateText::CmdSketcherCreateText()
: Command("Sketcher_CreateText")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create text");
sToolTipText = QT_TR_NOOP("Create text in the sketch");
sWhatsThis = "Sketcher_CreateText";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateText";
eType = ForEdit;
}
void CmdSketcherCreateText::activated(int iMsg)
{
Q_UNUSED(iMsg);
}
bool CmdSketcherCreateText::isActive(void)
{
return false;
}
// ======================================================================================
DEF_STD_CMD_A(CmdSketcherCreateDraftLine)
CmdSketcherCreateDraftLine::CmdSketcherCreateDraftLine()
: Command("Sketcher_CreateDraftLine")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create draft line");
sToolTipText = QT_TR_NOOP("Create a draft line in the sketch");
sWhatsThis = "Sketcher_CreateDraftLine";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_DraftLine";
eType = ForEdit;
}
void CmdSketcherCreateDraftLine::activated(int iMsg)
{
Q_UNUSED(iMsg);
}
bool CmdSketcherCreateDraftLine::isActive(void)
{
return false;
}
// ======================================================================================
namespace SketcherGui {
class FilletSelection : public Gui::SelectionFilterGate
{
App::DocumentObject* object;
public:
FilletSelection(App::DocumentObject* obj)
: Gui::SelectionFilterGate((Gui::SelectionFilter*)0), object(obj)
{}
bool allow(App::Document * /*pDoc*/, App::DocumentObject *pObj, const char *sSubName)
{
if (pObj != this->object)
return false;
if (!sSubName || sSubName[0] == '\0')
return false;
std::string element(sSubName);
if (element.substr(0,4) == "Edge") {
int GeoId = std::atoi(element.substr(4,4000).c_str()) - 1;
Sketcher::SketchObject *Sketch = static_cast<Sketcher::SketchObject*>(object);
const Part::Geometry *geom = Sketch->getGeometry(GeoId);
if (geom->getTypeId().isDerivedFrom(Part::GeomBoundedCurve::getClassTypeId()))
return true;
}
if (element.substr(0,6) == "Vertex") {
int VtId = std::atoi(element.substr(6,4000).c_str()) - 1;
Sketcher::SketchObject *Sketch = static_cast<Sketcher::SketchObject*>(object);
std::vector<int> GeoIdList;
std::vector<Sketcher::PointPos> PosIdList;
Sketch->getDirectlyCoincidentPoints(VtId, GeoIdList, PosIdList);
if (GeoIdList.size() == 2 && GeoIdList[0] >= 0 && GeoIdList[1] >= 0) {
const Part::Geometry *geom1 = Sketch->getGeometry(GeoIdList[0]);
const Part::Geometry *geom2 = Sketch->getGeometry(GeoIdList[1]);
if (geom1->getTypeId() == Part::GeomLineSegment::getClassTypeId() &&
geom2->getTypeId() == Part::GeomLineSegment::getClassTypeId())
return true;
}
}
return false;
}
};
}
class DrawSketchHandlerFillet: public DrawSketchHandler
{
public:
enum FilletType {
SimpleFillet,
ConstraintPreservingFillet
};
DrawSketchHandlerFillet(FilletType filletType) : filletType(filletType), Mode(STATUS_SEEK_First), firstCurve(0) {}
virtual ~DrawSketchHandlerFillet()
{
Gui::Selection().rmvSelectionGate();
}
enum SelectMode{
STATUS_SEEK_First,
STATUS_SEEK_Second
};
virtual void activated(ViewProviderSketch *)
{
Gui::Selection().rmvSelectionGate();
Gui::Selection().addSelectionGate(new FilletSelection(sketchgui->getObject()));
setCrosshairCursor("Sketcher_Pointer_Create_Fillet");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
return true;
}
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
bool construction=false;
int VtId = sketchgui->getPreselectPoint();
if (Mode == STATUS_SEEK_First && VtId != -1) {
int GeoId;
Sketcher::PointPos PosId=Sketcher::none;
sketchgui->getSketchObject()->getGeoVertexIndex(VtId,GeoId,PosId);
const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(GeoId);
if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId() &&
(PosId == Sketcher::start || PosId == Sketcher::end)) {
// guess fillet radius
double radius=-1;
std::vector<int> GeoIdList;
std::vector<Sketcher::PointPos> PosIdList;
sketchgui->getSketchObject()->getDirectlyCoincidentPoints(GeoId, PosId, GeoIdList, PosIdList);
if (GeoIdList.size() == 2 && GeoIdList[0] >= 0 && GeoIdList[1] >= 0) {
const Part::Geometry *geom1 = sketchgui->getSketchObject()->getGeometry(GeoIdList[0]);
const Part::Geometry *geom2 = sketchgui->getSketchObject()->getGeometry(GeoIdList[1]);
construction=Sketcher::GeometryFacade::getConstruction(geom1) && Sketcher::GeometryFacade::getConstruction(geom2);
if (geom1->getTypeId() == Part::GeomLineSegment::getClassTypeId() &&
geom2->getTypeId() == Part::GeomLineSegment::getClassTypeId()) {
const Part::GeomLineSegment *lineSeg1 = static_cast<const Part::GeomLineSegment *>(geom1);
const Part::GeomLineSegment *lineSeg2 = static_cast<const Part::GeomLineSegment *>(geom2);
Base::Vector3d dir1 = lineSeg1->getEndPoint() - lineSeg1->getStartPoint();
Base::Vector3d dir2 = lineSeg2->getEndPoint() - lineSeg2->getStartPoint();
if (PosIdList[0] == Sketcher::end)
dir1 *= -1;
if (PosIdList[1] == Sketcher::end)
dir2 *= -1;
double l1 = dir1.Length();
double l2 = dir2.Length();
double angle = dir1.GetAngle(dir2);
radius = (l1 < l2 ? l1 : l2) * 0.2 * sin(angle/2);
}
}
if (radius < 0)
return false;
int currentgeoid= getHighestCurveIndex();
// create fillet at point
try {
bool pointFillet = (filletType == 1);
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Create fillet"));
Gui::cmdAppObjectArgs(sketchgui->getObject(), "fillet(%d,%d,%f,%s,%s)", GeoId, PosId, radius, "True",
pointFillet ? "True":"False");
if (construction) {
Gui::cmdAppObjectArgs(sketchgui->getObject(), "toggleConstruction(%d) ", currentgeoid+1);
}
Gui::Command::commitCommand();
}
catch (const Base::Exception& e) {
Base::Console().Error("Failed to create fillet: %s\n", e.what());
Gui::Command::abortCommand();
}
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
}
return true;
}
int GeoId = sketchgui->getPreselectCurve();
if (GeoId > -1) {
const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(GeoId);
if (geom->getTypeId().isDerivedFrom(Part::GeomBoundedCurve::getClassTypeId())) {
if (Mode==STATUS_SEEK_First) {
firstCurve = GeoId;
firstPos = onSketchPos;
Mode = STATUS_SEEK_Second;
// add the line to the selection
std::stringstream ss;
ss << "Edge" << firstCurve + 1;
Gui::Selection().addSelection(sketchgui->getSketchObject()->getDocument()->getName()
,sketchgui->getSketchObject()->getNameInDocument()
,ss.str().c_str()
,onSketchPos.x
,onSketchPos.y
,0.f);
}
else if (Mode==STATUS_SEEK_Second) {
int secondCurve = GeoId;
Base::Vector2d secondPos = onSketchPos;
Base::Vector3d refPnt1(firstPos.x, firstPos.y, 0.f);
Base::Vector3d refPnt2(secondPos.x, secondPos.y, 0.f);
const Part::Geometry *geom1 = sketchgui->getSketchObject()->getGeometry(firstCurve);
double radius = 0;
if( geom->getTypeId() == Part::GeomLineSegment::getClassTypeId() &&
geom1->getTypeId() == Part::GeomLineSegment::getClassTypeId()) {
// guess fillet radius
const Part::GeomLineSegment *lineSeg1 = static_cast<const Part::GeomLineSegment *>
(sketchgui->getSketchObject()->getGeometry(firstCurve));
const Part::GeomLineSegment *lineSeg2 = static_cast<const Part::GeomLineSegment *>
(sketchgui->getSketchObject()->getGeometry(secondCurve));
radius = Part::suggestFilletRadius(lineSeg1, lineSeg2, refPnt1, refPnt2);
if (radius < 0)
return false;
construction=Sketcher::GeometryFacade::getConstruction(lineSeg1) && Sketcher::GeometryFacade::getConstruction(lineSeg2);
}
else { // other supported curves
const Part::Geometry *geo1 = static_cast<const Part::Geometry *>
(sketchgui->getSketchObject()->getGeometry(firstCurve));
const Part::Geometry *geo2 = static_cast<const Part::Geometry *>
(sketchgui->getSketchObject()->getGeometry(secondCurve));
construction=Sketcher::GeometryFacade::getConstruction(geo1) && Sketcher::GeometryFacade::getConstruction(geo2);
}
int currentgeoid= getHighestCurveIndex();
// create fillet between lines
try {
bool pointFillet = (filletType == 1);
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Create fillet"));
Gui::cmdAppObjectArgs(sketchgui->getObject(), "fillet(%d,%d,App.Vector(%f,%f,0),App.Vector(%f,%f,0),%f,%s,%s)",
firstCurve, secondCurve,
firstPos.x, firstPos.y,
secondPos.x, secondPos.y, radius,
"True", pointFillet ? "True":"False");
Gui::Command::commitCommand();
}
catch (const Base::CADKernelError& e) {
e.ReportException();
if(e.getTranslatable()) {
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("CAD Kernel Error"),
QObject::tr(e.getMessage().c_str()));
}
Gui::Selection().clearSelection();
Gui::Command::abortCommand();
Mode = STATUS_SEEK_First;
}
catch (const Base::ValueError& e) {
e.ReportException();
Gui::Selection().clearSelection();
Gui::Command::abortCommand();
Mode = STATUS_SEEK_First;
}
tryAutoRecompute(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
if(construction) {
Gui::cmdAppObjectArgs(sketchgui->getObject(), "toggleConstruction(%d) ",
currentgeoid+1);
}
Gui::Selection().clearSelection();
Mode = STATUS_SEEK_First;
}
}
}
if (VtId < 0 && GeoId < 0) // exit the fillet tool if the user clicked on empty space
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
return true;
}
protected:
int filletType;
SelectMode Mode;
int firstCurve;
Base::Vector2d firstPos;
};
DEF_STD_CMD_A(CmdSketcherCreateFillet)
CmdSketcherCreateFillet::CmdSketcherCreateFillet()
: Command("Sketcher_CreateFillet")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create fillet");
sToolTipText = QT_TR_NOOP("Create a fillet between two lines or at a coincident point");
sWhatsThis = "Sketcher_CreateFillet";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateFillet";
sAccel = "F";
eType = ForEdit;
}
void CmdSketcherCreateFillet::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerFillet(DrawSketchHandlerFillet::SimpleFillet));
}
bool CmdSketcherCreateFillet::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
// ======================================================================================
DEF_STD_CMD_A(CmdSketcherCreatePointFillet)
CmdSketcherCreatePointFillet::CmdSketcherCreatePointFillet()
: Command("Sketcher_CreatePointFillet")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create corner-preserving fillet");
sToolTipText = QT_TR_NOOP("Fillet that preserves intersection point and most constraints");
sWhatsThis = "Sketcher_CreatePointFillet";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateFillet";
sAccel = "";
eType = ForEdit;
}
void CmdSketcherCreatePointFillet::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerFillet(DrawSketchHandlerFillet::ConstraintPreservingFillet));
}
bool CmdSketcherCreatePointFillet::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
/// @brief Macro that declares a new sketcher command class 'CmdSketcherCompCreateFillets'
DEF_STD_CMD_ACLU(CmdSketcherCompCreateFillets)
/**
* @brief ctor
*/
CmdSketcherCompCreateFillets::CmdSketcherCompCreateFillets()
: Command("Sketcher_CompCreateFillets")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Fillets");
sToolTipText = QT_TR_NOOP("Create a fillet between two lines");
sWhatsThis = "Sketcher_CompCreateFillets";
sStatusTip = sToolTipText;
eType = ForEdit;
}
/**
* @brief Instantiates the fillet handler when the fillet command activated
* @param int iMsg
*/
void CmdSketcherCompCreateFillets::activated(int iMsg)
{
if (iMsg == 0) {
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerFillet(DrawSketchHandlerFillet::SimpleFillet));
} else if (iMsg == 1) {
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerFillet(DrawSketchHandlerFillet::ConstraintPreservingFillet));
} else {
return;
}
// 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 * CmdSketcherCompCreateFillets::createAction(void)
{
Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow());
pcAction->setDropDownMenu(true);
applyCommandData(this->className(), pcAction);
QAction* oldFillet = pcAction->addAction(QString());
oldFillet->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateFillet"));
QAction* pointFillet = pcAction->addAction(QString());
pointFillet->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreatePointFillet"));
_pcAction = pcAction;
languageChange();
pcAction->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateFillet"));
int defaultId = 0;
pcAction->setProperty("defaultAction", QVariant(defaultId));
return pcAction;
}
void CmdSketcherCompCreateFillets::updateAction(int mode)
{
Q_UNUSED(mode);
Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(getAction());
if (!pcAction)
return;
QList<QAction*> a = pcAction->actions();
int index = pcAction->property("defaultAction").toInt();
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateFillet"));
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreatePointFillet"));
getAction()->setIcon(a[index]->icon());
}
void CmdSketcherCompCreateFillets::languageChange()
{
Command::languageChange();
if (!_pcAction)
return;
Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
QList<QAction*> a = pcAction->actions();
QAction* oldFillet = a[0];
oldFillet->setText(QApplication::translate("CmdSketcherCompCreateFillets","Sketch fillet"));
oldFillet->setToolTip(QApplication::translate("Sketcher_CreateFillet","Creates a radius between two lines"));
oldFillet->setStatusTip(QApplication::translate("Sketcher_CreateFillet","Creates a radius between two lines"));
QAction* pointFillet = a[1];
pointFillet->setText(QApplication::translate("CmdSketcherCompCreateFillets","Constraint-preserving sketch fillet"));
pointFillet->setToolTip(QApplication::translate("Sketcher_CreatePointFillet","Fillet that preserves constraints and intersection point"));
pointFillet->setStatusTip(QApplication::translate("Sketcher_CreatePointFillet","Fillet that preserves constraints and intersection point"));
}
bool CmdSketcherCompCreateFillets::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
// ======================================================================================
namespace SketcherGui {
class TrimmingSelection : public Gui::SelectionFilterGate
{
App::DocumentObject* object;
public:
TrimmingSelection(App::DocumentObject* obj)
: Gui::SelectionFilterGate((Gui::SelectionFilter*)0), object(obj)
{}
bool allow(App::Document * /*pDoc*/, App::DocumentObject *pObj, const char *sSubName)
{
if (pObj != this->object)
return false;
if (!sSubName || sSubName[0] == '\0')
return false;
std::string element(sSubName);
if (element.substr(0,4) == "Edge") {
int GeoId = std::atoi(element.substr(4,4000).c_str()) - 1;
Sketcher::SketchObject *Sketch = static_cast<Sketcher::SketchObject*>(object);
const Part::Geometry *geom = Sketch->getGeometry(GeoId);
if (geom->getTypeId().isDerivedFrom(Part::GeomTrimmedCurve::getClassTypeId()) ||
geom->getTypeId() == Part::GeomCircle::getClassTypeId() ||
geom->getTypeId() == Part::GeomEllipse::getClassTypeId() ||
geom->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()
) {
// We do not trim internal geometry of complex geometries
if( Sketcher::GeometryFacade::isInternalType(geom, Sketcher::InternalType::None))
return true;
}
}
return false;
}
};
}
class DrawSketchHandlerTrimming: public DrawSketchHandler
{
public:
DrawSketchHandlerTrimming() {}
virtual ~DrawSketchHandlerTrimming()
{
Gui::Selection().rmvSelectionGate();
}
virtual void activated(ViewProviderSketch *sketchgui)
{
Gui::Selection().clearSelection();
Gui::Selection().rmvSelectionGate();
Gui::Selection().addSelectionGate(new TrimmingSelection(sketchgui->getObject()));
setCrosshairCursor("Sketcher_Pointer_Trimming");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
int GeoId = sketchgui->getPreselectCurve();
if (GeoId > -1) {
auto sk = static_cast<Sketcher::SketchObject *>(sketchgui->getObject());
int GeoId1, GeoId2;
Base::Vector3d intersect1, intersect2;
if(sk->seekTrimPoints(GeoId, Base::Vector3d(onSketchPos.x,onSketchPos.y,0),
GeoId1, intersect1,
GeoId2, intersect2)) {
EditMarkers.resize(0);
if(GeoId1 != Sketcher::Constraint::GeoUndef)
EditMarkers.emplace_back(intersect1.x, intersect1.y);
else {
auto start = sk->getPoint(GeoId, Sketcher::start);
EditMarkers.emplace_back(start.x, start.y);
}
if(GeoId2 != Sketcher::Constraint::GeoUndef)
EditMarkers.emplace_back(intersect2.x, intersect2.y);
else {
auto end = sk->getPoint(GeoId, Sketcher::end);
EditMarkers.emplace_back( end.x, end.y);
}
sketchgui->drawEditMarkers(EditMarkers, 2); // maker augmented by two sizes (see supported marker sizes)
}
}
else {
EditMarkers.resize(0);
sketchgui->drawEditMarkers(EditMarkers, 2);
}
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
return true;
}
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
int GeoId = sketchgui->getPreselectCurve();
if (GeoId > -1) {
const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(GeoId);
if (geom->getTypeId().isDerivedFrom(Part::GeomTrimmedCurve::getClassTypeId()) ||
geom->getTypeId() == Part::GeomCircle::getClassTypeId() ||
geom->getTypeId() == Part::GeomEllipse::getClassTypeId() ||
geom->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() ) {
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Trim edge"));
Gui::cmdAppObjectArgs(sketchgui->getObject(), "trim(%d,App.Vector(%f,%f,0))",
GeoId, onSketchPos.x, onSketchPos.y);
Gui::Command::commitCommand();
tryAutoRecompute(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
}
catch (const Base::Exception& e) {
Base::Console().Error("Failed to trim edge: %s\n", e.what());
Gui::Command::abortCommand();
}
}
EditMarkers.resize(0);
sketchgui->drawEditMarkers(EditMarkers);
}
else // exit the trimming tool if the user clicked on empty space
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
return true;
}
private:
std::vector<Base::Vector2d> EditMarkers;
};
DEF_STD_CMD_A(CmdSketcherTrimming)
CmdSketcherTrimming::CmdSketcherTrimming()
: Command("Sketcher_Trimming")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Trim edge");
sToolTipText = QT_TR_NOOP("Trim an edge with respect to the picked position");
sWhatsThis = "Sketcher_Trimming";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_Trimming";
sAccel = "T,R";
eType = ForEdit;
}
void CmdSketcherTrimming::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerTrimming());
}
bool CmdSketcherTrimming::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
// ======================================================================================
namespace SketcherGui {
class ExtendSelection : public Gui::SelectionFilterGate
{
App::DocumentObject* object;
public:
ExtendSelection(App::DocumentObject* obj)
: Gui::SelectionFilterGate((Gui::SelectionFilter*)0)
, object(obj)
, disabled(false)
{}
bool allow(App::Document * /*pDoc*/, App::DocumentObject *pObj, const char *sSubName)
{
if (pObj != this->object)
return false;
if (!sSubName || sSubName[0] == '\0')
return false;
if (disabled)
return true;
std::string element(sSubName);
if (element.substr(0, 4) == "Edge") {
int GeoId = std::atoi(element.substr(4, 4000).c_str()) - 1;
Sketcher::SketchObject *Sketch = static_cast<Sketcher::SketchObject*>(object);
const Part::Geometry *geom = Sketch->getGeometry(GeoId);
if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId() ||
geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId())
return true;
}
return false;
}
void setDisabled(bool isDisabled) {
disabled = isDisabled;
}
protected:
bool disabled;
};
}
class DrawSketchHandlerExtend: public DrawSketchHandler
{
public:
DrawSketchHandlerExtend()
: Mode(STATUS_SEEK_First)
, EditCurve(2)
, BaseGeoId(-1)
, ExtendFromStart(false)
, SavedExtendFromStart(false)
, Increment(0)
{
}
virtual ~DrawSketchHandlerExtend()
{
Gui::Selection().rmvSelectionGate();
}
enum SelectMode {
STATUS_SEEK_First,
STATUS_SEEK_Second,
};
virtual void activated(ViewProviderSketch *sketchgui)
{
Q_UNUSED(sketchgui)
Gui::Selection().clearSelection();
Gui::Selection().rmvSelectionGate();
filterGate = new ExtendSelection(sketchgui->getObject());
Gui::Selection().addSelectionGate(filterGate);
setCrosshairCursor("Sketcher_Pointer_Extension");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
if (Mode == STATUS_SEEK_Second) {
const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(BaseGeoId);
if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) {
const Part::GeomLineSegment *lineSeg = static_cast<const Part::GeomLineSegment *>(geom);
// project point to the existing curve
Base::Vector3d start3d = lineSeg->getStartPoint();
Base::Vector3d end3d = lineSeg->getEndPoint();
Base::Vector2d startPoint = Base::Vector2d(start3d.x, start3d.y);
Base::Vector2d endPoint = Base::Vector2d(end3d.x, end3d.y);
Base::Vector2d recenteredLine = endPoint - startPoint;
Base::Vector2d recenteredPoint = onSketchPos - startPoint;
Base::Vector2d projection;
projection.ProjectToLine(recenteredPoint, recenteredLine);
if (recenteredPoint.Length() < recenteredPoint.Distance(recenteredLine)) {
EditCurve[0] = startPoint + projection;
EditCurve[1] = endPoint;
} else {
EditCurve[0] = startPoint;
EditCurve[1] = startPoint + projection;
}
/**
* If in-curve, the intuitive behavior is for the line to shrink an amount from
* the original click-point.
*
* If out-of-curve, the intuitive behavior is for the closest line endpoint to
* expand.
*/
bool inCurve = (projection.Length() < recenteredLine.Length()
&& projection.GetAngle(recenteredLine) < 0.1); // Two possible values here, M_PI and 0, but 0.1 is to avoid floating point problems.
if (inCurve) {
Increment = SavedExtendFromStart ? -1 * projection.Length() : projection.Length() - recenteredLine.Length();
ExtendFromStart = SavedExtendFromStart;
} else {
ExtendFromStart = onSketchPos.Distance(startPoint) < onSketchPos.Distance(endPoint);
Increment = ExtendFromStart ? projection.Length() : projection.Length() - recenteredLine.Length();
}
sketchgui->drawEdit(EditCurve);
} else if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) {
const Part::GeomArcOfCircle *arc = static_cast<const Part::GeomArcOfCircle *>(geom);
Base::Vector3d center = arc->getCenter();
double radius = arc->getRadius();
double start, end;
arc->getRange(start, end, true);
double arcAngle = end - start;
Base::Vector2d angle = Base::Vector2d(onSketchPos.x - center.x, onSketchPos.y - center.y);
Base::Vector2d startAngle = Base::Vector2d(cos(start), sin(start));
Base::Vector2d endAngle = Base::Vector2d(cos(end), sin(end));
Base::Vector2d arcHalf = Base::Vector2d(cos(start + arcAngle/ 2.0), sin(start+ arcAngle / 2.0));
double angleToEndAngle = angle.GetAngle(endAngle);
double angleToStartAngle = angle.GetAngle(startAngle);
double modStartAngle = start;
double modArcAngle = end - start;
bool outOfArc = arcHalf.GetAngle(angle) * 2.0 > arcAngle;
if (ExtendFromStart) {
bool isCCWFromStart = crossProduct(angle, startAngle) < 0;
if (outOfArc) {
if (isCCWFromStart) {
modStartAngle -= 2*M_PI - angleToStartAngle;
modArcAngle += 2*M_PI - angleToStartAngle;
} else {
modStartAngle -= angleToStartAngle;
modArcAngle += angleToStartAngle;
}
} else {
if (isCCWFromStart) {
modStartAngle += angleToStartAngle;
modArcAngle -= angleToStartAngle;
} else {
modStartAngle += 2*M_PI - angleToStartAngle;
modArcAngle -= 2*M_PI - angleToStartAngle;
}
}
} else {
bool isCWFromEnd = crossProduct(angle, endAngle) >= 0;
if (outOfArc) {
if (isCWFromEnd) {
modArcAngle += 2*M_PI - angleToEndAngle;
} else {
modArcAngle += angleToEndAngle;
}
} else {
if (isCWFromEnd) {
modArcAngle -= angleToEndAngle;
} else {
modArcAngle -= 2*M_PI - angleToEndAngle;
}
}
}
Increment = modArcAngle - (end - start);
for (int i = 0; i < 31; i++) {
double angle = modStartAngle + i * modArcAngle/30.0;
EditCurve[i] = Base::Vector2d(center.x + radius * cos(angle), center.y + radius * sin(angle));
}
sketchgui->drawEdit(EditCurve);
}
int curveId = sketchgui->getPreselectCurve();
if (BaseGeoId != curveId && seekAutoConstraint(SugConstr, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(SugConstr);
return;
}
}
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
return true;
}
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
if (Mode == STATUS_SEEK_First) {
BaseGeoId = sketchgui->getPreselectCurve();
if (BaseGeoId > -1) {
const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(BaseGeoId);
if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) {
const Part::GeomLineSegment *seg = static_cast<const Part::GeomLineSegment *>(geom);
Base::Vector3d start3d = seg->getStartPoint();
Base::Vector3d end3d = seg->getEndPoint();
Base::Vector2d start = Base::Vector2d(start3d.x, start3d.y);
Base::Vector2d end = Base::Vector2d(end3d.x, end3d.y);
SavedExtendFromStart = (onSketchPos.Distance(start) < onSketchPos.Distance(end));
ExtendFromStart = SavedExtendFromStart;
Mode = STATUS_SEEK_Second;
} else if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) {
const Part::GeomArcOfCircle *arc = static_cast<const Part::GeomArcOfCircle *>(geom);
double start, end;
arc->getRange(start, end, true);
Base::Vector3d center = arc->getCenter();
Base::Vector2d angle = Base::Vector2d(onSketchPos.x - center.x, onSketchPos.y - center.y);
double angleToStart = angle.GetAngle(Base::Vector2d(cos(start), sin(start)));
double angleToEnd = angle.GetAngle(Base::Vector2d(cos(end), sin(end)));
ExtendFromStart = (angleToStart < angleToEnd); // move start point if closer to angle than end point
EditCurve.resize(31);
Mode = STATUS_SEEK_Second;
}
filterGate->setDisabled(true);
}
} else if (Mode == STATUS_SEEK_Second) {
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Extend edge"));
Gui::cmdAppObjectArgs(sketchgui->getObject(), "extend(%d, %f, %d)\n", // GeoId, increment, PointPos
BaseGeoId, Increment, ExtendFromStart ? Sketcher::start : Sketcher::end);
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();
// constrain chosen point
if (SugConstr.size() > 0) {
createAutoConstraints(SugConstr, BaseGeoId, (ExtendFromStart) ? Sketcher::start : Sketcher::end);
SugConstr.clear();
}
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if(continuousMode){
// This code enables the continuous creation mode.
Mode=STATUS_SEEK_First;
filterGate->setDisabled(false);
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
EditCurve.resize(2);
applyCursor();
/* this is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
} else{
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
}
}
catch (const Base::Exception& e) {
Base::Console().Error("Failed to extend edge: %s\n", e.what());
Gui::Command::abortCommand();
}
} else { // exit extension tool if user clicked on empty space
BaseGeoId = -1;
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
}
return true;
}
protected:
SelectMode Mode;
std::vector<Base::Vector2d> EditCurve;
int BaseGeoId;
ExtendSelection* filterGate = nullptr;
bool ExtendFromStart; // if true, extend from start, else extend from end (circle only)
bool SavedExtendFromStart;
double Increment;
std::vector<AutoConstraint> SugConstr;
private:
int crossProduct(Base::Vector2d &vec1, Base::Vector2d &vec2) {
return vec1.x * vec2.y - vec1.y * vec2.x;
}
};
DEF_STD_CMD_A(CmdSketcherExtend)
//TODO: fix the translations for this
CmdSketcherExtend::CmdSketcherExtend()
: Command("Sketcher_Extend")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Extend edge");
sToolTipText = QT_TR_NOOP("Extend an edge with respect to the picked position");
sWhatsThis = "Sketcher_Extend";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_Extend";
sAccel = "T,E";
eType = ForEdit;
}
void CmdSketcherExtend::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerExtend());
}
bool CmdSketcherExtend::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
// ======================================================================================
namespace SketcherGui {
class SplittingSelection : public Gui::SelectionFilterGate
{
App::DocumentObject* object;
public:
SplittingSelection(App::DocumentObject* obj)
: Gui::SelectionFilterGate((Gui::SelectionFilter*)0), object(obj)
{}
bool allow(App::Document * /*pDoc*/, App::DocumentObject *pObj, const char *sSubName)
{
if (pObj != this->object)
return false;
if (!sSubName || sSubName[0] == '\0')
return false;
std::string element(sSubName);
if (element.substr(0,4) == "Edge") {
int GeoId = std::atoi(element.substr(4,4000).c_str()) - 1;
Sketcher::SketchObject *Sketch = static_cast<Sketcher::SketchObject*>(object);
const Part::Geometry *geom = Sketch->getGeometry(GeoId);
if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()
|| geom->getTypeId() == Part::GeomCircle::getClassTypeId()
|| geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) {
return true;
}
}
return false;
}
};
}
class DrawSketchHandlerSplitting: public DrawSketchHandler
{
public:
DrawSketchHandlerSplitting() {}
virtual ~DrawSketchHandlerSplitting()
{
Gui::Selection().rmvSelectionGate();
}
virtual void activated(ViewProviderSketch *sketchgui)
{
Gui::Selection().clearSelection();
Gui::Selection().rmvSelectionGate();
Gui::Selection().addSelectionGate(new SplittingSelection(sketchgui->getObject()));
setCrosshairCursor("Sketcher_Pointer_Splitting");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
return true;
}
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
int GeoId = sketchgui->getPreselectCurve();
if (GeoId >= 0) {
const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(GeoId);
if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()
|| geom->getTypeId() == Part::GeomCircle::getClassTypeId()
|| geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) {
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Split edge"));
Gui::cmdAppObjectArgs(sketchgui->getObject(), "split(%d,App.Vector(%f,%f,0))",
GeoId, onSketchPos.x, onSketchPos.y);
Gui::Command::commitCommand();
tryAutoRecompute(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
}
catch (const Base::Exception& e) {
Base::Console().Error("Failed to split edge: %s\n", e.what());
Gui::Command::abortCommand();
}
}
}
else {
sketchgui->purgeHandler();
}
return true;
}
};
DEF_STD_CMD_A(CmdSketcherSplit)
//TODO: fix the translations for this
CmdSketcherSplit::CmdSketcherSplit()
: Command("Sketcher_Split")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Split edge");
sToolTipText = QT_TR_NOOP("Splits an edge into two while preserving constraints");
sWhatsThis = "Sketcher_Split";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_Split";
sAccel = "T,S";
eType = ForEdit;
}
void CmdSketcherSplit::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerSplitting());
}
bool CmdSketcherSplit::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
namespace SketcherGui {
class ExternalSelection : public Gui::SelectionFilterGate
{
App::DocumentObject* object;
public:
ExternalSelection(App::DocumentObject* obj)
: Gui::SelectionFilterGate((Gui::SelectionFilter*)0), object(obj)
{}
bool allow(App::Document *pDoc, App::DocumentObject *pObj, const char *sSubName)
{
Sketcher::SketchObject *sketch = static_cast<Sketcher::SketchObject*>(object);
this->notAllowedReason = "";
Sketcher::SketchObject::eReasonList msg;
if (!sketch->isExternalAllowed(pDoc, pObj, &msg)){
switch(msg){
case Sketcher::SketchObject::rlCircularReference:
this->notAllowedReason = QT_TR_NOOP("Linking this will cause circular dependency.");
break;
case Sketcher::SketchObject::rlOtherDoc:
this->notAllowedReason = QT_TR_NOOP("This object is in another document.");
break;
case Sketcher::SketchObject::rlOtherBody:
this->notAllowedReason = QT_TR_NOOP("This object belongs to another body, can't link.");
break;
case Sketcher::SketchObject::rlOtherPart:
this->notAllowedReason = QT_TR_NOOP("This object belongs to another part, can't link.");
break;
default:
break;
}
return false;
}
// Note: its better to search the support of the sketch in case the sketch support is a base plane
//Part::BodyBase* body = Part::BodyBase::findBodyOf(sketch);
//if ( body && body->hasFeature ( pObj ) && body->isAfter ( pObj, sketch ) ) {
// Don't allow selection after the sketch in the same body
// NOTE: allowness of features in other bodies is handled by SketchObject::isExternalAllowed()
// TODO may be this should be in SketchObject::isExternalAllowed() (2015-08-07, Fat-Zer)
//return false;
//}
if (!sSubName || sSubName[0] == '\0')
return false;
std::string element(sSubName);
if ((element.size() > 4 && element.substr(0,4) == "Edge") ||
(element.size() > 6 && element.substr(0,6) == "Vertex") ||
(element.size() > 4 && element.substr(0,4) == "Face")) {
return true;
}
if (pObj->getTypeId().isDerivedFrom(App::Plane::getClassTypeId()) ||
pObj->getTypeId().isDerivedFrom(Part::Datum::getClassTypeId()))
return true;
return false;
}
};
}
class DrawSketchHandlerExternal: public DrawSketchHandler
{
public:
DrawSketchHandlerExternal() {}
virtual ~DrawSketchHandlerExternal()
{
Gui::Selection().rmvSelectionGate();
}
virtual void activated(ViewProviderSketch *sketchgui)
{
sketchgui->setAxisPickStyle(false);
Gui::MDIView *mdi = Gui::Application::Instance->activeDocument()->getActiveView();
Gui::View3DInventorViewer *viewer;
viewer = static_cast<Gui::View3DInventor *>(mdi)->getViewer();
SoNode* root = viewer->getSceneGraph();
static_cast<Gui::SoFCUnifiedSelection*>(root)->selectionRole.setValue(true);
Gui::Selection().clearSelection();
Gui::Selection().rmvSelectionGate();
Gui::Selection().addSelectionGate(new ExternalSelection(sketchgui->getObject()));
setCrosshairCursor("Sketcher_Pointer_External");
}
virtual void deactivated(ViewProviderSketch *sketchgui)
{
sketchgui->setAxisPickStyle(true);
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
if (Gui::Selection().getPreselection().pObjectName)
applyCursor();
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
return true;
}
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
/* this is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
return true;
}
virtual bool onSelectionChanged(const Gui::SelectionChanges& msg)
{
if (msg.Type == Gui::SelectionChanges::AddSelection) {
App::DocumentObject* obj = sketchgui->getObject()->getDocument()->getObject(msg.pObjectName);
if (obj == NULL)
throw Base::ValueError("Sketcher: External geometry: Invalid object in selection");
std::string subName(msg.pSubName);
if (obj->getTypeId().isDerivedFrom(App::Plane::getClassTypeId()) ||
obj->getTypeId().isDerivedFrom(Part::Datum::getClassTypeId()) ||
(subName.size() > 4 && subName.substr(0,4) == "Edge") ||
(subName.size() > 6 && subName.substr(0,6) == "Vertex") ||
(subName.size() > 4 && subName.substr(0,4) == "Face")) {
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add external geometry"));
Gui::cmdAppObjectArgs(sketchgui->getObject(), "addExternal(\"%s\",\"%s\")",
msg.pObjectName, msg.pSubName);
Gui::Command::commitCommand();
// adding external geometry does not require a solve() per se (the DoF is the same),
// however a solve is required to update the amount of solver geometry, because we only
// redraw a changed Sketch if the solver geometry amount is the same as the SkethObject
// geometry amount (as this avoids other issues).
// This solver is a very low cost one anyway (there is actually nothing to solve).
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
Gui::Selection().clearSelection();
/* this is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
}
catch (const Base::Exception& e) {
Base::Console().Error("Failed to add external geometry: %s\n", e.what());
Gui::Selection().clearSelection();
Gui::Command::abortCommand();
}
return true;
}
}
return false;
}
};
DEF_STD_CMD_A(CmdSketcherExternal)
CmdSketcherExternal::CmdSketcherExternal()
: Command("Sketcher_External")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("External geometry");
sToolTipText = QT_TR_NOOP("Create an edge linked to an external geometry");
sWhatsThis = "Sketcher_External";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_External";
sAccel = "X";
eType = ForEdit;
}
void CmdSketcherExternal::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerExternal());
}
bool CmdSketcherExternal::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
// ======================================================================================
namespace SketcherGui {
class CarbonCopySelection : public Gui::SelectionFilterGate
{
App::DocumentObject* object;
public:
CarbonCopySelection(App::DocumentObject* obj)
: Gui::SelectionFilterGate((Gui::SelectionFilter*)0), object(obj)
{}
bool allow(App::Document *pDoc, App::DocumentObject *pObj, const char *sSubName)
{
Q_UNUSED(sSubName);
Sketcher::SketchObject *sketch = static_cast<Sketcher::SketchObject*>(object);
sketch->setAllowOtherBody(QApplication::keyboardModifiers() == Qt::ControlModifier || QApplication::keyboardModifiers() == (Qt::ControlModifier | Qt::AltModifier));
sketch->setAllowUnaligned(QApplication::keyboardModifiers() == (Qt::ControlModifier | Qt::AltModifier));
this->notAllowedReason = "";
Sketcher::SketchObject::eReasonList msg;
// Reusing code: All good reasons not to allow a carbon copy
bool xinv = false, yinv = false;
if (!sketch->isCarbonCopyAllowed(pDoc, pObj, xinv, yinv, &msg)){
switch(msg){
case Sketcher::SketchObject::rlCircularReference:
this->notAllowedReason = QT_TR_NOOP("Carbon copy would cause a circular dependency.");
break;
case Sketcher::SketchObject::rlOtherDoc:
this->notAllowedReason = QT_TR_NOOP("This object is in another document.");
break;
case Sketcher::SketchObject::rlOtherBody:
this->notAllowedReason = QT_TR_NOOP("This object belongs to another body. Hold Ctrl to allow cross-references.");
break;
case Sketcher::SketchObject::rlOtherBodyWithLinks:
this->notAllowedReason = QT_TR_NOOP("This object belongs to another body and it contains external geometry. Cross-reference not allowed.");
break;
case Sketcher::SketchObject::rlOtherPart:
this->notAllowedReason = QT_TR_NOOP("This object belongs to another part.");
break;
case Sketcher::SketchObject::rlNonParallel:
this->notAllowedReason = QT_TR_NOOP("The selected sketch is not parallel to this sketch. Hold Ctrl+Alt to allow non-parallel sketches.");
break;
case Sketcher::SketchObject::rlAxesMisaligned:
this->notAllowedReason = QT_TR_NOOP("The XY axes of the selected sketch do not have the same direction as this sketch. Hold Ctrl+Alt to disregard it.");
break;
case Sketcher::SketchObject::rlOriginsMisaligned:
this->notAllowedReason = QT_TR_NOOP("The origin of the selected sketch is not aligned with the origin of this sketch. Hold Ctrl+Alt to disregard it.");
break;
default:
break;
}
return false;
}
// Carbon copy only works on sketches that are not disallowed (e.g. would produce a circular reference)
return true;
}
};
}
class DrawSketchHandlerCarbonCopy: public DrawSketchHandler
{
public:
DrawSketchHandlerCarbonCopy() {}
virtual ~DrawSketchHandlerCarbonCopy()
{
Gui::Selection().rmvSelectionGate();
}
virtual void activated(ViewProviderSketch *sketchgui)
{
sketchgui->setAxisPickStyle(false);
Gui::MDIView *mdi = Gui::Application::Instance->activeDocument()->getActiveView();
Gui::View3DInventorViewer *viewer;
viewer = static_cast<Gui::View3DInventor *>(mdi)->getViewer();
SoNode* root = viewer->getSceneGraph();
static_cast<Gui::SoFCUnifiedSelection*>(root)->selectionRole.setValue(true);
Gui::Selection().clearSelection();
Gui::Selection().rmvSelectionGate();
Gui::Selection().addSelectionGate(new CarbonCopySelection(sketchgui->getObject()));
setCrosshairCursor("Sketcher_Pointer_CarbonCopy");
}
virtual void deactivated(ViewProviderSketch *sketchgui)
{
sketchgui->setAxisPickStyle(true);
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
if (Gui::Selection().getPreselection().pObjectName)
applyCursor();
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
return true;
}
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
/* this is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
return true;
}
virtual bool onSelectionChanged(const Gui::SelectionChanges& msg)
{
if (msg.Type == Gui::SelectionChanges::AddSelection) {
App::DocumentObject* obj = sketchgui->getObject()->getDocument()->getObject(msg.pObjectName);
if (obj == NULL)
throw Base::ValueError("Sketcher: Carbon Copy: Invalid object in selection");
if (obj->getTypeId() == Sketcher::SketchObject::getClassTypeId()) {
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add carbon copy"));
Gui::cmdAppObjectArgs(sketchgui->getObject(), "carbonCopy(\"%s\",%s)",
msg.pObjectName, geometryCreationMode==Construction?"True":"False");
Gui::Command::commitCommand();
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
Gui::Selection().clearSelection();
/* this is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
}
catch (const Base::Exception& e) {
Base::Console().Error("Failed to add carbon copy: %s\n", e.what());
Gui::Command::abortCommand();
}
return true;
}
}
return false;
}
};
DEF_STD_CMD_AU(CmdSketcherCarbonCopy)
CmdSketcherCarbonCopy::CmdSketcherCarbonCopy()
: Command("Sketcher_CarbonCopy")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Carbon copy");
sToolTipText = QT_TR_NOOP("Copies the geometry of another sketch");
sWhatsThis = "Sketcher_CarbonCopy";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CarbonCopy";
sAccel = "C,C";
eType = ForEdit;
}
void CmdSketcherCarbonCopy::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerCarbonCopy());
}
bool CmdSketcherCarbonCopy::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
void CmdSketcherCarbonCopy::updateAction(int mode)
{
switch (mode) {
case Normal:
if (getAction())
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CarbonCopy"));
break;
case Construction:
if (getAction())
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CarbonCopy_Constr"));
break;
}
}
/**
* Create Slot
*/
class DrawSketchHandlerSlot : public DrawSketchHandler
{
public:
DrawSketchHandlerSlot()
: Mode(STATUS_SEEK_First)
, dx(0), dy(0), r(0)
, EditCurve(35)
{
}
virtual ~DrawSketchHandlerSlot() {}
/// mode table
enum BoxMode {
STATUS_SEEK_First, /**< enum value ----. */
STATUS_SEEK_Second, /**< enum value ----. */
STATUS_End
};
virtual void activated(ViewProviderSketch*)
{
setCrosshairCursor("Sketcher_Pointer_Slot");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
if (Mode == STATUS_SEEK_First) {
setPositionText(onSketchPos);
if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f, 0.f))) {
renderSuggestConstraintsCursor(sugConstr1);
return;
}
}
else if (Mode == STATUS_SEEK_Second) {
dx = onSketchPos.x - StartPos.x;
dy = onSketchPos.y - StartPos.y;
double a = 0;
double rev = 0;
if (fabs(dx) > fabs(dy)) {
r = fabs(dx) / 4;
rev = Base::sgn(dx);
}
else {
r = fabs(dy) / 4;
a = 8;
rev = Base::sgn(dy);
}
// draw the arcs with each 16 segments
for (int i = 0; i < 17; i++) {
// first get the position at the arc
// if a is 0, the end points of the arc are at the y-axis, if it is 8, they are on the x-axis
double angle = (i + a) * M_PI / 16.0;
double rx = -r * rev * sin(angle);
double ry = r * rev * cos(angle);
// now apply the rotation matrix according to the angle between StartPos and onSketchPos
if (!(dx == 0 || dy == 0)) {
double rotAngle = atan(dy / dx);
if (a > 0)
rotAngle = -atan(dx / dy);
double rxRot = rx * cos(rotAngle) - ry * sin(rotAngle);
double ryRot = rx * sin(rotAngle) + ry * cos(rotAngle);
rx = rxRot;
ry = ryRot;
}
EditCurve[i] = Base::Vector2d(StartPos.x + rx, StartPos.y + ry);
EditCurve[17 + i] = Base::Vector2d(StartPos.x + dx - rx, StartPos.y + dy - ry);
}
EditCurve[34] = EditCurve[0];
SbString text;
text.sprintf(" (%.1fR %.1fL)", r, sqrt(dx * dx + dy * dy));
setPositionText(onSketchPos, text);
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f, 0.f))) {
renderSuggestConstraintsCursor(sugConstr2);
return;
}
}
applyCursor();
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
if (Mode == STATUS_SEEK_First) {
StartPos = onSketchPos;
Mode = STATUS_SEEK_Second;
}
else {
Mode = STATUS_End;
}
return true;
}
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
if (Mode == STATUS_End) {
unsetCursor();
resetPositionText();
int firstCurve = getHighestCurveIndex() + 1;
// add the geometry to the sketch
// first determine the rotation angle for the first arc
double start, end;
if (fabs(dx) > fabs(dy)) {
if (dx > 0) {
start = 0.5 * M_PI;
end = 1.5 * M_PI;
}
else {
start = 1.5 * M_PI;
end = 0.5 * M_PI;
}
}
else {
if (dy > 0) {
start = -M_PI;
end = 0;
}
else {
start = 0;
end = -M_PI;
}
}
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add slot"));
Gui::Command::doCommand(Gui::Command::Doc,
"geoList = []\n"
"geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n"
"geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f ,0), App.Vector(0, 0, 1), %f), %f, %f))\n"
"geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n"
"geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n"
"%s.addGeometry(geoList, %s)\n"
"conList = []\n"
"conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 1))\n"
"conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 1))\n"
"conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 1))\n"
"conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 1))\n"
"conList.append(Sketcher.Constraint('Equal', %i, %i))\n"
"%s.addConstraint(conList)\n",
StartPos.x, StartPos.y, // center of the arc1
r, // radius arc1
start, end, // start and end angle of arc1
StartPos.x + dx, StartPos.y + dy, // center of the arc2
r, // radius arc2
end, end + M_PI, // start and end angle of arc2
EditCurve[16].x, EditCurve[16].y, EditCurve[17].x, EditCurve[17].y, // line1
EditCurve[33].x, EditCurve[33].y, EditCurve[34].x, EditCurve[34].y, // line2
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch
geometryCreationMode == Construction ? "True" : "False", // geometry as construction or not
firstCurve, firstCurve + 2, // tangent1
firstCurve + 2, firstCurve + 1, // tangent2
firstCurve + 1, firstCurve + 3, // tangent3
firstCurve + 3, firstCurve, // tangent4
firstCurve, firstCurve + 1, // equal constraint
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch
Gui::Command::commitCommand();
// add auto constraints at the center of the first arc
if (sugConstr1.size() > 0) {
createAutoConstraints(sugConstr1, getHighestCurveIndex() - 3, Sketcher::mid);
sugConstr1.clear();
}
// add auto constraints at the center of the second arc
if (sugConstr2.size() > 0) {
createAutoConstraints(sugConstr2, getHighestCurveIndex() - 2, Sketcher::mid);
sugConstr2.clear();
}
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject*>(sketchgui->getObject()));
}
catch (const Base::Exception& e) {
Base::Console().Error("Failed to add slot: %s\n", e.what());
Gui::Command::abortCommand();
tryAutoRecompute(static_cast<Sketcher::SketchObject*>(sketchgui->getObject()));
}
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode", true);
if (continuousMode) {
// This code enables the continuous creation mode.
Mode = STATUS_SEEK_First;
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
EditCurve.resize(35);
applyCursor();
/* this is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
}
else {
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
}
}
return true;
}
protected:
BoxMode Mode;
Base::Vector2d StartPos;
double dx, dy, r;
std::vector<Base::Vector2d> EditCurve;
std::vector<AutoConstraint> sugConstr1, sugConstr2;
};
DEF_STD_CMD_AU(CmdSketcherCreateSlot)
CmdSketcherCreateSlot::CmdSketcherCreateSlot()
: Command("Sketcher_CreateSlot")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create slot");
sToolTipText = QT_TR_NOOP("Create a slot in the sketch");
sWhatsThis = "Sketcher_CreateSlot";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateSlot";
sAccel = "";
eType = ForEdit;
}
void CmdSketcherCreateSlot::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerSlot());
}
void CmdSketcherCreateSlot::updateAction(int mode)
{
switch (mode) {
case Normal:
if (getAction())
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateSlot"));
break;
case Construction:
if (getAction())
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateSlot_Constr"));
break;
}
}
bool CmdSketcherCreateSlot::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
/* Create Regular Polygon ==============================================*/
class DrawSketchHandlerRegularPolygon: public DrawSketchHandler
{
public:
DrawSketchHandlerRegularPolygon( size_t nof_corners ):
Corners( nof_corners ),
AngleOfSeparation( 2.0*M_PI/static_cast<double>(Corners) ),
cos_v( cos( AngleOfSeparation ) ),
sin_v( sin( AngleOfSeparation ) ),
Mode(STATUS_SEEK_First),
EditCurve(Corners+1)
{
}
virtual ~DrawSketchHandlerRegularPolygon(){}
/// mode table
enum SelectMode {
STATUS_SEEK_First, /**< enum value ----. */
STATUS_SEEK_Second, /**< enum value ----. */
STATUS_End
};
virtual void activated(ViewProviderSketch *)
{
setCrosshairCursor("Sketcher_Pointer_Regular_Polygon");
}
virtual void mouseMove(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_First) {
setPositionText(onSketchPos);
if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr1);
return;
}
}
else if (Mode==STATUS_SEEK_Second) {
EditCurve[0]= Base::Vector2d(onSketchPos.x, onSketchPos.y);
EditCurve[Corners]= Base::Vector2d(onSketchPos.x, onSketchPos.y);
Base::Vector2d dV = onSketchPos - StartPos;
double rx = dV.x;
double ry = dV.y;
for (int i=1; i < static_cast<int>(Corners); i++) {
const double old_rx = rx;
rx = cos_v * rx - sin_v * ry;
ry = cos_v * ry + sin_v * old_rx;
EditCurve[i] = Base::Vector2d(StartPos.x + rx, StartPos.y + ry);
}
// Display radius for user
const float radius = dV.Length();
const float angle = ( 180.0 / M_PI ) * atan2( dV.y, dV.x );
SbString text;
text.sprintf(" (%.1fR %.1fdeg)", radius, angle );
setPositionText(onSketchPos, text);
sketchgui->drawEdit(EditCurve);
if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f))) {
renderSuggestConstraintsCursor(sugConstr2);
return;
}
}
applyCursor();
}
virtual bool pressButton(Base::Vector2d onSketchPos)
{
if (Mode==STATUS_SEEK_First){
StartPos = onSketchPos;
Mode = STATUS_SEEK_Second;
}
else {
Mode = STATUS_End;
}
return true;
}
virtual bool releaseButton(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos);
if (Mode==STATUS_End){
unsetCursor();
resetPositionText();
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add hexagon"));
try {
Gui::Command::doCommand(Gui::Command::Doc,
"import ProfileLib.RegularPolygon\n"
"ProfileLib.RegularPolygon.makeRegularPolygon(%s,%i,App.Vector(%f,%f,0),App.Vector(%f,%f,0),%s)",
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(),
Corners,
StartPos.x,StartPos.y,EditCurve[0].x,EditCurve[0].y,
geometryCreationMode==Construction?"True":"False");
Gui::Command::commitCommand();
// add auto constraints at the center of the polygon
if (sugConstr1.size() > 0) {
createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::mid);
sugConstr1.clear();
}
// add auto constraints to the last side of the polygon
if (sugConstr2.size() > 0) {
createAutoConstraints(sugConstr2, getHighestCurveIndex() - 1, Sketcher::end);
sugConstr2.clear();
}
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
}
catch (const Base::Exception& e) {
Base::Console().Error("Failed to add hexagon: %s\n", e.what());
Gui::Command::abortCommand();
tryAutoRecompute(static_cast<Sketcher::SketchObject *>(sketchgui->getObject()));
}
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true);
if(continuousMode){
// This code enables the continuous creation mode.
Mode=STATUS_SEEK_First;
EditCurve.clear();
sketchgui->drawEdit(EditCurve);
EditCurve.resize(Corners+1);
applyCursor();
/* this is ok not to call to purgeHandler
* in continuous creation mode because the
* handler is destroyed by the quit() method on pressing the
* right button of the mouse */
}
else{
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
}
}
return true;
}
protected:
const size_t Corners;
const double AngleOfSeparation;
const double cos_v, sin_v;
SelectMode Mode;
Base::Vector2d StartPos;
std::vector<Base::Vector2d> EditCurve;
std::vector<AutoConstraint> sugConstr1, sugConstr2;
};
DEF_STD_CMD_A(CmdSketcherCreateTriangle)
CmdSketcherCreateTriangle::CmdSketcherCreateTriangle()
: Command("Sketcher_CreateTriangle")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create equilateral triangle");
sToolTipText = QT_TR_NOOP("Create an equilateral triangle in the sketch");
sWhatsThis = "Sketcher_CreateTriangle";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateTriangle";
sAccel = "";
eType = ForEdit;
}
void CmdSketcherCreateTriangle::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(3) );
}
bool CmdSketcherCreateTriangle::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
DEF_STD_CMD_A(CmdSketcherCreateSquare)
CmdSketcherCreateSquare::CmdSketcherCreateSquare()
: Command("Sketcher_CreateSquare")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create square");
sToolTipText = QT_TR_NOOP("Create a square in the sketch");
sWhatsThis = "Sketcher_CreateSquare";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateSquare";
sAccel = "";
eType = ForEdit;
}
void CmdSketcherCreateSquare::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(4) );
}
bool CmdSketcherCreateSquare::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
DEF_STD_CMD_A(CmdSketcherCreatePentagon)
CmdSketcherCreatePentagon::CmdSketcherCreatePentagon()
: Command("Sketcher_CreatePentagon")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create pentagon");
sToolTipText = QT_TR_NOOP("Create a pentagon in the sketch");
sWhatsThis = "Sketcher_CreatePentagon";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreatePentagon";
sAccel = "";
eType = ForEdit;
}
void CmdSketcherCreatePentagon::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(5) );
}
bool CmdSketcherCreatePentagon::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
DEF_STD_CMD_A(CmdSketcherCreateHexagon)
CmdSketcherCreateHexagon::CmdSketcherCreateHexagon()
: Command("Sketcher_CreateHexagon")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create hexagon");
sToolTipText = QT_TR_NOOP("Create a hexagon in the sketch");
sWhatsThis = "Sketcher_CreateHexagon";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateHexagon";
sAccel = "";
eType = ForEdit;
}
void CmdSketcherCreateHexagon::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(6) );
}
bool CmdSketcherCreateHexagon::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
DEF_STD_CMD_A(CmdSketcherCreateHeptagon)
CmdSketcherCreateHeptagon::CmdSketcherCreateHeptagon()
: Command("Sketcher_CreateHeptagon")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create heptagon");
sToolTipText = QT_TR_NOOP("Create a heptagon in the sketch");
sWhatsThis = "Sketcher_CreateHeptagon";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateHeptagon";
sAccel = "";
eType = ForEdit;
}
void CmdSketcherCreateHeptagon::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(7) );
}
bool CmdSketcherCreateHeptagon::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
DEF_STD_CMD_A(CmdSketcherCreateOctagon)
CmdSketcherCreateOctagon::CmdSketcherCreateOctagon()
: Command("Sketcher_CreateOctagon")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create octagon");
sToolTipText = QT_TR_NOOP("Create an octagon in the sketch");
sWhatsThis = "Sketcher_CreateOctagon";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateOctagon";
sAccel = "";
eType = ForEdit;
}
void CmdSketcherCreateOctagon::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(8) );
}
bool CmdSketcherCreateOctagon::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
DEF_STD_CMD_A(CmdSketcherCreateRegularPolygon)
CmdSketcherCreateRegularPolygon::CmdSketcherCreateRegularPolygon()
: Command("Sketcher_CreateRegularPolygon")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create regular polygon");
sToolTipText = QT_TR_NOOP("Create a regular polygon in the sketch");
sWhatsThis = "Sketcher_CreateRegularPolygon";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateRegularPolygon";
sAccel = "";
eType = ForEdit;
}
void CmdSketcherCreateRegularPolygon::activated(int iMsg)
{
Q_UNUSED(iMsg);
// Pop-up asking for values
SketcherRegularPolygonDialog srpd;
if (srpd.exec() == QDialog::Accepted)
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(srpd.sides));
}
bool CmdSketcherCreateRegularPolygon::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
DEF_STD_CMD_ACLU(CmdSketcherCompCreateRegularPolygon)
CmdSketcherCompCreateRegularPolygon::CmdSketcherCompCreateRegularPolygon()
: Command("Sketcher_CompCreateRegularPolygon")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Create regular polygon");
sToolTipText = QT_TR_NOOP("Create a regular polygon in the sketcher");
sWhatsThis = "Sketcher_CompCreateRegularPolygon";
sStatusTip = sToolTipText;
eType = ForEdit;
}
void CmdSketcherCompCreateRegularPolygon::activated(int iMsg)
{
switch( iMsg ){
case 0:
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(3)); break;
case 1:
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(4)); break;
case 2:
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(5)); break;
case 3:
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(6)); break;
case 4:
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(7)); break;
case 5:
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(8)); break;
case 6:
{
// Pop-up asking for values
SketcherRegularPolygonDialog srpd;
if (srpd.exec() == QDialog::Accepted)
ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(srpd.sides));
}
break;
default:
return;
}
// 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 * CmdSketcherCompCreateRegularPolygon::createAction(void)
{
Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow());
pcAction->setDropDownMenu(true);
applyCommandData(this->className(), pcAction);
QAction* triangle = pcAction->addAction(QString());
triangle->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateTriangle"));
QAction* square = pcAction->addAction(QString());
square->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateSquare"));
QAction* pentagon = pcAction->addAction(QString());
pentagon->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreatePentagon"));
QAction* hexagon = pcAction->addAction(QString());
hexagon->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateHexagon"));
QAction* heptagon = pcAction->addAction(QString());
heptagon->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateHeptagon"));
QAction* octagon = pcAction->addAction(QString());
octagon->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOctagon"));
QAction* regular = pcAction->addAction(QString());
regular->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRegularPolygon"));
_pcAction = pcAction;
languageChange();
pcAction->setIcon(hexagon->icon());
int defaultId = 3;
pcAction->setProperty("defaultAction", QVariant(defaultId));
return pcAction;
}
void CmdSketcherCompCreateRegularPolygon::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 Normal:
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateTriangle"));
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateSquare"));
a[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreatePentagon"));
a[3]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateHexagon"));
a[4]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateHeptagon"));
a[5]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOctagon"));
a[6]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRegularPolygon"));
getAction()->setIcon(a[index]->icon());
break;
case Construction:
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateTriangle_Constr"));
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateSquare_Constr"));
a[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreatePentagon_Constr"));
a[3]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateHexagon_Constr"));
a[4]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateHeptagon_Constr"));
a[5]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOctagon_Constr"));
a[6]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRegularPolygon_Constr"));
getAction()->setIcon(a[index]->icon());
break;
}
}
void CmdSketcherCompCreateRegularPolygon::languageChange()
{
Command::languageChange();
if (!_pcAction)
return;
Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
QList<QAction*> a = pcAction->actions();
QAction* triangle = a[0];
triangle->setText(QApplication::translate("CmdSketcherCompCreateRegularPolygon","Triangle"));
triangle->setToolTip(QApplication::translate("Sketcher_CreateTriangle","Create an equilateral triangle by its center and by one corner"));
triangle->setStatusTip(QApplication::translate("Sketcher_CreateTriangle","Create an equilateral triangle by its center and by one corner"));
QAction* square = a[1];
square->setText(QApplication::translate("CmdSketcherCompCreateRegularPolygon","Square"));
square->setToolTip(QApplication::translate("Sketcher_CreateSquare","Create a square by its center and by one corner"));
square->setStatusTip(QApplication::translate("Sketcher_CreateSquare","Create a square by its center and by one corner"));
QAction* pentagon = a[2];
pentagon->setText(QApplication::translate("CmdSketcherCompCreateRegularPolygon","Pentagon"));
pentagon->setToolTip(QApplication::translate("Sketcher_CreatePentagon","Create a pentagon by its center and by one corner"));
pentagon->setStatusTip(QApplication::translate("Sketcher_CreatePentagon","Create a pentagon by its center and by one corner"));
QAction* hexagon = a[3];
hexagon->setText(QApplication::translate("CmdSketcherCompCreateRegularPolygon","Hexagon"));
hexagon->setToolTip(QApplication::translate("Sketcher_CreateHexagon","Create a hexagon by its center and by one corner"));
hexagon->setStatusTip(QApplication::translate("Sketcher_CreateHexagon","Create a hexagon by its center and by one corner"));
QAction* heptagon = a[4];
heptagon->setText(QApplication::translate("CmdSketcherCompCreateRegularPolygon","Heptagon"));
heptagon->setToolTip(QApplication::translate("Sketcher_CreateHeptagon","Create a heptagon by its center and by one corner"));
heptagon->setStatusTip(QApplication::translate("Sketcher_CreateHeptagon","Create a heptagon by its center and by one corner"));
QAction* octagon = a[5];
octagon->setText(QApplication::translate("CmdSketcherCompCreateRegularPolygon","Octagon"));
octagon->setToolTip(QApplication::translate("Sketcher_CreateOctagon","Create an octagon by its center and by one corner"));
octagon->setStatusTip(QApplication::translate("Sketcher_CreateOctagon","Create an octagon by its center and by one corner"));
QAction* regular = a[6];
regular->setText(QApplication::translate("CmdSketcherCompCreateRegularPolygon","Regular polygon"));
regular->setToolTip(QApplication::translate("Sketcher_CreateOctagon","Create a regular polygon by its center and by one corner"));
regular->setStatusTip(QApplication::translate("Sketcher_CreateOctagon","Create a regular polygon by its center and by one corner"));
}
bool CmdSketcherCompCreateRegularPolygon::isActive(void)
{
return isCreateGeoActive(getActiveGuiDocument());
}
void CreateSketcherCommandsCreateGeo(void)
{
Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager();
rcCmdMgr.addCommand(new CmdSketcherCreatePoint());
rcCmdMgr.addCommand(new CmdSketcherCreateArc());
rcCmdMgr.addCommand(new CmdSketcherCreate3PointArc());
rcCmdMgr.addCommand(new CmdSketcherCompCreateArc());
rcCmdMgr.addCommand(new CmdSketcherCreateCircle());
rcCmdMgr.addCommand(new CmdSketcherCreate3PointCircle());
rcCmdMgr.addCommand(new CmdSketcherCompCreateCircle());
rcCmdMgr.addCommand(new CmdSketcherCreateEllipseByCenter());
rcCmdMgr.addCommand(new CmdSketcherCreateEllipseBy3Points());
rcCmdMgr.addCommand(new CmdSketcherCompCreateConic());
rcCmdMgr.addCommand(new CmdSketcherCreateArcOfEllipse());
rcCmdMgr.addCommand(new CmdSketcherCreateArcOfHyperbola());
rcCmdMgr.addCommand(new CmdSketcherCreateArcOfParabola());
rcCmdMgr.addCommand(new CmdSketcherCreateBSpline());
rcCmdMgr.addCommand(new CmdSketcherCreatePeriodicBSpline());
rcCmdMgr.addCommand(new CmdSketcherCompCreateBSpline());
rcCmdMgr.addCommand(new CmdSketcherCreateLine());
rcCmdMgr.addCommand(new CmdSketcherCreatePolyline());
rcCmdMgr.addCommand(new CmdSketcherCreateRectangle());
rcCmdMgr.addCommand(new CmdSketcherCreateRectangleCenter());
rcCmdMgr.addCommand(new CmdSketcherCreateOblong());
rcCmdMgr.addCommand(new CmdSketcherCompCreateRegularPolygon());
rcCmdMgr.addCommand(new CmdSketcherCreateTriangle());
rcCmdMgr.addCommand(new CmdSketcherCreateSquare());
rcCmdMgr.addCommand(new CmdSketcherCreatePentagon());
rcCmdMgr.addCommand(new CmdSketcherCreateHexagon());
rcCmdMgr.addCommand(new CmdSketcherCreateHeptagon());
rcCmdMgr.addCommand(new CmdSketcherCreateOctagon());
rcCmdMgr.addCommand(new CmdSketcherCreateRegularPolygon());
rcCmdMgr.addCommand(new CmdSketcherCompCreateRectangles());
rcCmdMgr.addCommand(new CmdSketcherCreateSlot());
rcCmdMgr.addCommand(new CmdSketcherCompCreateFillets());
rcCmdMgr.addCommand(new CmdSketcherCreateFillet());
rcCmdMgr.addCommand(new CmdSketcherCreatePointFillet());
//rcCmdMgr.addCommand(new CmdSketcherCreateText());
//rcCmdMgr.addCommand(new CmdSketcherCreateDraftLine());
rcCmdMgr.addCommand(new CmdSketcherTrimming());
rcCmdMgr.addCommand(new CmdSketcherExtend());
rcCmdMgr.addCommand(new CmdSketcherSplit());
rcCmdMgr.addCommand(new CmdSketcherExternal());
rcCmdMgr.addCommand(new CmdSketcherCarbonCopy());
}