Files
create/src/Mod/Sketcher/Gui/DrawSketchDefaultHandler.h
ppphp 12e8fb1695 fix unique ptr usage in sketch (#15008)
* fix unique ptr usage in sketch

* [pre-commit.ci] auto fixes from pre-commit.com hooks
2024-07-08 10:53:49 -05:00

1187 lines
45 KiB
C++

/***************************************************************************
* Copyright (c) 2022 Abdullah Tahiri <abdullah.tahiri.yo@gmail.com> *
* *
* 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 *
* *
***************************************************************************/
#ifndef SKETCHERGUI_DrawSketchDefaultHandler_H
#define SKETCHERGUI_DrawSketchDefaultHandler_H
#include <Inventor/events/SoKeyboardEvent.h>
#include <Base/Exception.h>
#include <Base/Console.h>
#include <Gui/Command.h>
#include <Mod/Sketcher/App/GeoEnum.h>
#include <Mod/Sketcher/App/GeometryFacade.h>
#include <Mod/Sketcher/App/PythonConverter.h>
#include <Mod/Sketcher/App/SketchObject.h>
#include <Mod/Sketcher/App/SolverGeometryExtension.h>
#include "DrawSketchHandler.h"
#include "ViewProviderSketch.h"
#include "Utils.h"
namespace SketcherGui
{
/*********************** Ancillary classes for DrawSketch Hierarchy *******************************/
/** @name Basic State Machines */
//@{
namespace StateMachines
{
enum class OneSeekEnd
{
SeekFirst,
End // MUST be the last one
};
enum class TwoSeekEnd
{
SeekFirst,
SeekSecond,
End // MUST be the last one
};
enum class ThreeSeekEnd
{
SeekFirst,
SeekSecond,
SeekThird,
End // MUST be the last one
};
enum class FourSeekEnd
{
SeekFirst,
SeekSecond,
SeekThird,
SeekFourth,
End // MUST be the last one
};
enum class FiveSeekEnd
{
SeekFirst,
SeekSecond,
SeekThird,
SeekFourth,
SeekFifth,
End // MUST be the last one
};
enum class TwoSeekDoEnd
{
SeekFirst,
SeekSecond,
Do,
End // MUST be the last one
};
} // namespace StateMachines
//@}
/** @brief A state machine to encapsulate a state
*
* @details
*
* A template class for a state machine defined by template type SelectModeT,
* automatically initialised to the first state, encapsulating the actual state,
* and enabling to change the state, while generating a call to onModeChanged()
* after every change.
*
* the getNextMode() returns the next mode in the state machine, unless it is in
* End mode, in which End mode is returned.
*
* The goal of this class to sense navigation through the states of the time machine (for example
* as triggered by one input method) and notifying of changes (for example to another input method
* to update it appropriately).
*
* NOTE: Some basic state machines are provided. However, it is possible for the user to provide its
* own customised time machine instead. The state machine provided MUST include a last state named
* End.
*/
template<typename SelectModeT>
class StateMachine
{
public:
StateMachine()
: Mode(static_cast<SelectModeT>(0))
{}
virtual ~StateMachine()
{}
protected:
void setState(SelectModeT mode)
{
Mode = mode;
onModeChanged();
}
void ensureState(SelectModeT mode)
{
if (Mode != mode) {
Mode = mode;
onModeChanged();
}
}
/** Ensure the state machine is the provided mode
* but only if the mode is an earlier state.
*
* This allows to return to previous states (e.g.
* for modification), only if that state has previously
* been completed.
*/
void ensureStateIfEarlier(SelectModeT mode)
{
if (Mode != mode) {
if (mode < Mode) {
Mode = mode;
onModeChanged();
}
}
}
SelectModeT state() const
{
return Mode;
}
bool isState(SelectModeT state) const
{
return Mode == state;
}
bool isFirstState() const
{
return Mode == (static_cast<SelectModeT>(0));
}
bool isLastState() const
{
return Mode == SelectModeT::End;
}
constexpr SelectModeT getFirstState() const
{
return static_cast<SelectModeT>(0);
}
SelectModeT getNextMode() const
{
auto modeint = static_cast<int>(state());
if (modeint < maxMode) {
auto newmode = static_cast<SelectModeT>(modeint + 1);
return newmode;
}
else {
return SelectModeT::End;
}
}
void moveToNextMode()
{
setState(getNextMode());
}
void reset()
{
if (Mode != static_cast<SelectModeT>(0)) {
setState(static_cast<SelectModeT>(0));
}
}
virtual bool onModeChanged()
{
return true;
};
private:
SelectModeT Mode;
static const constexpr int maxMode = static_cast<int>(SelectModeT::End);
};
namespace ConstructionMethods
{
enum class DefaultConstructionMethod
{
End // Must be the last one
};
} // namespace ConstructionMethods
/** @brief An encapsulation of a construction method
*
* @details
*
* A template class for a construction method defined by template type ConstructionMethodT.
*
* A construction method is an alternative UI sequence of steps or input parameters to achieve a
* goal.For example, for creation of a circle, point center + radius is a possible construction
* method. Three points on the rim is another possible construction method.
*
* The construction method is automatically initialised to the first or default construction method.
* It senses construction method change, triggering onModeChanged() after every change.
*
* the getNextMethod() returns the next construction method, in a round-robin fashion.
*
* NOTE: A construction method enum defining the different construction methods must be provided and
* the last item shall be ConstructionMethodT::End.
*/
template<typename ConstructionMethodT>
class ConstructionMethodMachine
{
public:
ConstructionMethodMachine(
ConstructionMethodT constructionmethod = static_cast<ConstructionMethodT>(0))
: ConstructionMode(constructionmethod)
{}
virtual ~ConstructionMethodMachine()
{}
protected:
void setConstructionMethod(ConstructionMethodT mode)
{
ConstructionMode = mode;
onConstructionMethodChanged();
}
ConstructionMethodT constructionMethod() const
{
return ConstructionMode;
}
bool isConstructionMethod(ConstructionMethodT state) const
{
return ConstructionMode == state;
}
void resetConstructionMode()
{
ConstructionMode = static_cast<ConstructionMethodT>(0);
}
void initConstructionMethod(ConstructionMethodT mode)
{
ConstructionMode = mode;
}
// Cyclically iterate construction methods
ConstructionMethodT getNextMethod() const
{
auto modeint = static_cast<int>(ConstructionMode);
if (modeint < (maxMode - 1)) {
auto newmode = static_cast<ConstructionMethodT>(modeint + 1);
return newmode;
}
else {
return static_cast<ConstructionMethodT>(0);
}
}
void iterateToNextConstructionMethod()
{
if (ConstructionMethodsCount() > 1) {
setConstructionMethod(getNextMethod());
}
}
virtual void onConstructionMethodChanged() {};
static constexpr int ConstructionMethodsCount()
{
return maxMode;
}
private:
ConstructionMethodT ConstructionMode;
static const constexpr int maxMode = static_cast<int>(ConstructionMethodT::End);
};
/** @brief A DrawSketchHandler template for geometry creation.
*
* @details
* A state machine DrawSketchHandler providing:
* - generic initialisation including setting the cursor
* - structured command finalisation
* - handling of continuous creation mode
*
* This class is intended to be used by instantiating the template with a new DSH type, and
* then derive the new type from the instantiated template. This allows to inherit all
* the functionality, have direct access to all handler members, while allowing the DSH creator to
* add additional data members and functions (and avoiding extensive usage of macros).
*
* Example:
*
* class DrawSketchHandlerPoint;
*
* using DrawSketchHandlerPointBase =
* DrawSketchDefaultHandler<DrawSketchHandlerPoint,
* StateMachines::OneSeekEnd,
* 1>;
*
* class DrawSketchHandlerPoint: public DrawSketchHandlerPointBase
* {
* ...
* }
*
* This approach enables seamless upgrading to a default or custom widget DSH.
*
* This class provides an NVI interface for extension.
*
* Question 1: Do I need to use this handler or derive from this handler to make a new handler?
*
* No, you do not NEED to. But you are encouraged to. Structuring a handler following this NVI,
* apart from savings in amount of code typed, enables a much easier and less verbose implementation
* of a handler using a default widget (toolwidget).
*
* For handlers using a custom widget it will also help by structuring the code in a way consistent
* with other handlers. It will result in an easier to maintain code.
*
* Question 2: I want to use the default widget, do I need to use this handler or derive from this
* handler?
*
* You should use DrawSketchDefaultWidgetHandler instead. However, both classes use the same
* interface, so if you derive from this class when implementing your handler and then decide to use
* the tool widget, all you have to do is to change the base class from DrawSketchDefaultHandler to
* DrawSketchDefaultWidgetHandler. Then you will have to implement the code that is exclusively
* necessary for the default widget to work.
*/
template<typename HandlerT, // A type for which the handler template is instantiated
typename SelectModeT, // The state machine defining the states that the handle iterates
int PInitAutoConstraintSize, // The initial size of the AutoConstraint>
typename ConstructionMethodT = ConstructionMethods::DefaultConstructionMethod>
class DrawSketchDefaultHandler: public DrawSketchHandler,
public StateMachine<SelectModeT>,
public ConstructionMethodMachine<ConstructionMethodT>
{
public:
DrawSketchDefaultHandler(
ConstructionMethodT constructionmethod = static_cast<ConstructionMethodT>(0))
: ConstructionMethodMachine<ConstructionMethodT>(constructionmethod)
, sugConstraints(PInitAutoConstraintSize)
, avoidRedundants(true)
, continuousMode(true)
{
applyCursor();
}
~DrawSketchDefaultHandler() override
{}
/** @name public DrawSketchHandler interface
* NOTE: Not intended to be specialised. It calls some functions intended to be
* overridden/specialised instead.
*/
//@{
void mouseMove(Base::Vector2d onSketchPos) override
{
updateDataAndDrawToPosition(onSketchPos);
}
bool pressButton(Base::Vector2d onSketchPos) override
{
onButtonPressed(onSketchPos);
return true;
}
bool releaseButton(Base::Vector2d onSketchPos) override
{
Q_UNUSED(onSketchPos);
finish();
return true;
}
void registerPressedKey(bool pressed, int key) override
{
if (key == SoKeyboardEvent::M && pressed && !this->isLastState()) {
this->iterateToNextConstructionMethod();
}
else if (key == SoKeyboardEvent::ESCAPE && pressed) {
rightButtonOrEsc();
}
}
void pressRightButton(Base::Vector2d onSketchPos) override
{
Q_UNUSED(onSketchPos);
rightButtonOrEsc();
}
virtual void rightButtonOrEsc()
{
if (this->isFirstState()) {
quit();
}
else {
handleContinuousMode();
}
}
//@}
protected:
using SelectMode = SelectModeT;
using ModeStateMachine = StateMachine<SelectModeT>;
using ConstructionMethod = ConstructionMethodT;
using ConstructionMachine = ConstructionMethodMachine<ConstructionMethodT>;
/** @name functions NOT intended for specialisation or to be hidden
These functions define a predefined structure and are extendable using NVI.
1. Call them from your handle
2. Do not hide them or otherwise redefine them UNLESS YOU HAVE A REALLY GOOD REASON TO
3. EXTEND their functionality, if needed, using the NVI interface (or if you do not need to
derive, by specialising these functions).*/
//@{
/** @brief This function finalises the creation operation. It works only if the state machine is
* in state End.
*
* @details
* The functionality need to be provided by extending these virtual private functions:
* 1. executeCommands() : Must be provided with the Commands to create the geometry
* 2. generateAutoConstraints() : When using AutoConstraints vector, this function populates the
* AutoConstraints vector, ensuring no redundant autoconstraints
* 2. beforeCreateAutoConstraints() : Enables derived classes to define specific actions before
* executeCommands and createAutoConstraints (optional).
* 3. createAutoConstraints() : Must be provided with the commands to create autoconstraints
*
* It recomputes if not solves and handles continuous mode automatically
*
* It returns true if the handler has been purged.
*/
bool finish()
{
if (this->isState(SelectMode::End)) {
unsetCursor();
resetPositionText();
try {
executeCommands();
if (sugConstraints.size() > 0) {
generateAutoConstraints();
beforeCreateAutoConstraints();
createAutoConstraints();
}
tryAutoRecomputeIfNotSolve(sketchgui->getSketchObject());
}
catch (const Base::RuntimeError& e) {
// RuntimeError exceptions inside of the block above must provide a translatable
// message. It is reported both to developer (report view) and user (notifications
// area).
Base::Console().Error(e.what());
}
return handleContinuousMode();
}
return false;
}
/** @brief This function resets the handler to the initial state.
*
* @details
* The functionality can be extended using these virtual private function:
* 1. onReset() : Any further initialisation applicable to your handler
*
* It clears the edit curve, resets the state machine and resizes edit curve and autoconstraints
* to initial size. Reapplies the cursor bitmap.
*/
void reset()
{
clearEdit();
ModeStateMachine::reset();
for (auto& ac : sugConstraints) {
ac.clear();
}
AutoConstraints.clear();
ShapeGeometry.clear();
ShapeConstraints.clear();
onReset();
applyCursor();
}
/** @brief This function handles the geometry continuous mode.
*
* @details
* The functionality can be extended using the virtual private function called from reset(),
* namely:
* 1. onReset() : Any further initialisation applicable to your handler
*
* It performs all the operations in reset().
*/
bool handleContinuousMode()
{
if (continuousMode) {
// This code enables the continuous creation mode.
reset();
// 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
return false;
}
else {
sketchgui->purgeHandler(); // no code after, Handler get deleted in ViewProvider
return true;
}
}
//@}
private:
/** @name functions are intended to be overridden/specialised to extend basic functionality
NVI interface. See documentation of the functions above.*/
//@{
virtual void onReset()
{}
virtual void executeCommands()
{}
virtual void generateAutoConstraints()
{}
virtual void beforeCreateAutoConstraints()
{}
virtual void createAutoConstraints()
{}
void onConstructionMethodChanged() override {};
virtual void updateDataAndDrawToPosition(Base::Vector2d onSketchPos)
{
Q_UNUSED(onSketchPos)
};
virtual void angleSnappingControl()
{}
// function intended to populate ShapeGeometry and ShapeConstraints
virtual void createShape(bool onlyeditoutline)
{
Q_UNUSED(onlyeditoutline)
}
//@}
protected:
/** @name functions are intended to be overridden/specialised to extend basic functionality
See documentation of the functions above*/
//@{
/** @brief Minimal handle activation respecting avoid redundants and continuous mode.*/
void activated() override
{
avoidRedundants =
sketchgui->AvoidRedundant.getValue() && sketchgui->Autoconstraints.getValue();
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Sketcher");
continuousMode = hGrp->GetBool("ContinuousCreationMode", true);
}
/** @brief Default button pressing implementation, which redraws and moves to the next machine
* state, until reaching end.
*
* If this behaviour is not acceptable, then the function must be specialised (or overloaded).*/
virtual void onButtonPressed(Base::Vector2d onSketchPos)
{
this->updateDataAndDrawToPosition(onSketchPos);
if (canGoToNextMode()) {
this->moveToNextMode();
}
}
virtual bool canGoToNextMode()
{
return true;
}
/** @brief Default behaviour that upon arriving to the End state of the state machine, the
* command is finished. */
bool onModeChanged() override
{
angleSnappingControl();
// internally checks that state is SelectMode::End, and only finishes then.
return !finish();
};
//@}
/** @name Helper functions
See documentation of the functions above*/
//@{
/** @brief Convenience function to automatically generate and add to the AutoConstraints vector
* the suggested constraints.
*
* This generates actual Sketcher::Constraint which can be used for diagnostic before addition.
*/
void generateAutoConstraintsOnElement(const std::vector<AutoConstraint>& autoConstrs,
int geoId1,
Sketcher::PointPos posId1)
{
if (!sketchgui->Autoconstraints.getValue()) {
return;
}
if (autoConstrs.size() > 0) {
for (auto& ac : autoConstrs) {
int geoId2 = ac.GeoId;
switch (ac.Type) {
case Sketcher::Coincident: {
if (posId1 == Sketcher::PointPos::none) {
continue;
}
// find if there is already a matching tangency
auto result = std::find_if(AutoConstraints.begin(),
AutoConstraints.end(),
[&](const auto& ace) {
return ace->Type == Sketcher::Tangent
&& ace->First == geoId1
&& ace->Second == ac.GeoId;
});
if (result
!= AutoConstraints.end()) { // modify tangency to endpoint-to-endpoint
(*result)->FirstPos = posId1;
(*result)->SecondPos = ac.PosId;
}
else {
auto c = std::make_unique<Sketcher::Constraint>();
c->Type = Sketcher::Coincident;
c->First = geoId1;
c->FirstPos = posId1;
c->Second = ac.GeoId;
c->SecondPos = ac.PosId;
AutoConstraints.push_back(std::move(c));
}
} break;
case Sketcher::PointOnObject: {
Sketcher::PointPos posId2 = ac.PosId;
if (posId1 == Sketcher::PointPos::none) {
// Auto constraining an edge so swap parameters
std::swap(geoId1, geoId2);
std::swap(posId1, posId2);
}
auto result = std::find_if(AutoConstraints.begin(),
AutoConstraints.end(),
[&](const auto& ace) {
return ace->Type == Sketcher::Tangent
&& ace->First == geoId1
&& ace->Second == ac.GeoId;
});
// if tangency, convert to point-to-edge tangency
if (result != AutoConstraints.end()) {
(*result)->FirstPos = posId1;
if ((*result)->First != geoId1) {
std::swap((*result)->Second, (*result)->First);
}
}
else {
auto c = std::make_unique<Sketcher::Constraint>();
c->Type = Sketcher::PointOnObject;
c->First = geoId1;
c->FirstPos = posId1;
c->Second = geoId2;
AutoConstraints.push_back(std::move(c));
}
} break;
case Sketcher::Symmetric: {
auto c = std::make_unique<Sketcher::Constraint>();
c->Type = Sketcher::Symmetric;
c->First = geoId2;
c->FirstPos = Sketcher::PointPos::start;
c->Second = geoId2;
c->SecondPos = Sketcher::PointPos::end;
c->Third = geoId1;
c->ThirdPos = posId1;
AutoConstraints.push_back(std::move(c));
} break;
// In special case of Horizontal/Vertical constraint, geoId2 is normally unused
// and should be 'Constraint::GeoUndef' However it can be used as a way to
// require the function to apply these constraints on another geometry In this
// case the caller as to set geoId2, then it will be used as target instead of
// geoId2
case Sketcher::Horizontal: {
auto c = std::make_unique<Sketcher::Constraint>();
c->Type = Sketcher::Horizontal;
c->First = (geoId2 != Sketcher::GeoEnum::GeoUndef ? geoId2 : geoId1);
AutoConstraints.push_back(std::move(c));
} break;
case Sketcher::Vertical: {
auto c = std::make_unique<Sketcher::Constraint>();
c->Type = Sketcher::Vertical;
c->First = (geoId2 != Sketcher::GeoEnum::GeoUndef ? geoId2 : geoId1);
AutoConstraints.push_back(std::move(c));
} break;
case Sketcher::Tangent: {
Sketcher::SketchObject* Obj =
static_cast<Sketcher::SketchObject*>(sketchgui->getObject());
const Part::Geometry* geom1 = Obj->getGeometry(geoId1);
const Part::Geometry* geom2 = Obj->getGeometry(ac.GeoId);
// ellipse tangency support using construction elements (lines)
if (geom1 && geom2
&& (geom1->getTypeId() == Part::GeomEllipse::getClassTypeId()
|| geom2->getTypeId() == Part::GeomEllipse::getClassTypeId())) {
if (geom1->getTypeId() != Part::GeomEllipse::getClassTypeId()) {
std::swap(geoId1, geoId2);
}
// geoId1 is the ellipse
geom1 = Obj->getGeometry(geoId1);
geom2 = Obj->getGeometry(geoId2);
if (geom2->getTypeId() == Part::GeomEllipse::getClassTypeId()
|| geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()
|| geom2->getTypeId() == Part::GeomCircle::getClassTypeId()
|| geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) {
// in all these cases an intermediate element is needed
/*makeTangentToEllipseviaNewPoint(Obj,
static_cast<const Part::GeomEllipse
*>(geom1), geom2, geoId1, geoId2);*/
// NOTE: Temporarily deactivated
return;
}
}
// arc of ellipse tangency support using external elements
if (geom1 && geom2
&& (geom1->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()
|| geom2->getTypeId()
== Part::GeomArcOfEllipse::getClassTypeId())) {
if (geom1->getTypeId() != Part::GeomArcOfEllipse::getClassTypeId()) {
std::swap(geoId1, geoId2);
}
// geoId1 is the arc of ellipse
geom1 = Obj->getGeometry(geoId1);
geom2 = Obj->getGeometry(geoId2);
if (geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()
|| geom2->getTypeId() == Part::GeomCircle::getClassTypeId()
|| geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) {
// in all these cases an intermediate element is needed
// makeTangentToArcOfEllipseviaNewPoint(Obj,
// static_cast<const
// Part::GeomArcOfEllipse
// *>(geom1), geom2, geoId1,
// geoId2);
// NOTE: Temporarily deactivated
return;
}
}
auto resultcoincident =
std::find_if(AutoConstraints.begin(),
AutoConstraints.end(),
[&](const auto& ace) {
return ace->Type == Sketcher::Coincident
&& ace->First == geoId1 && ace->Second == ac.GeoId;
});
auto resultpointonobject = std::find_if(
AutoConstraints.begin(),
AutoConstraints.end(),
[&](const auto& ace) {
return ace->Type == Sketcher::PointOnObject
&& ((ace->First == geoId1 && ace->Second == ac.GeoId)
|| (ace->First == ac.GeoId && ace->Second == geoId1));
});
if (resultcoincident
!= AutoConstraints.end()) { // endpoint-to-endpoint tangency
(*resultcoincident)->Type = Sketcher::Tangent;
}
else if (resultpointonobject
!= AutoConstraints.end()) { // endpoint-to-edge tangency
(*resultpointonobject)->Type = Sketcher::Tangent;
}
else { // regular edge to edge tangency
auto c = std::make_unique<Sketcher::Constraint>();
c->Type = Sketcher::Tangent;
c->First = geoId1;
c->Second = ac.GeoId;
AutoConstraints.push_back(std::move(c));
}
} break;
default:
break;
}
}
}
}
/** @brief Convenience function to automatically add to the SketchObjects (via Python command)
* all the constraints stored in the AutoConstraints vector. */
void createGeneratedAutoConstraints(bool owncommand)
{
try {
// add auto-constraints
if (owncommand) {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add auto constraints"));
}
tryAddAutoConstraints();
if (owncommand) {
Gui::Command::commitCommand();
}
}
catch (const Base::PyException&) {
if (owncommand) {
Gui::Command::abortCommand();
}
}
}
/** @brief Convenience function to automatically add to the SketchObjects
* all the constraints stored in the AutoConstraints vector. */
void tryAddAutoConstraints()
{
auto autoConstraints = toPointerVector(AutoConstraints);
Gui::Command::doCommand(
Gui::Command::Doc,
Sketcher::PythonConverter::convert(Gui::Command::getObjectCmd(sketchgui->getObject()),
autoConstraints)
.c_str());
}
/** @brief Convenience function to remove redundant autoconstraints from the AutoConstraints
* vector (before having added them to the SketchObject).
*
* @details The rationale is that constraints associated with the newly generated geometry SHALL
* not be redundant by design. Then, the only source of redundancy lies with the
* autoconstraints. This observation is also what motivates the separate treatment of both sets
* of constraints.
*
* This approach avoids the previous issues with old style DSHs, that the autoremoval mechanism
* sometimes removed a shape constraint, or a previously already existing constraint, instead of
* an autoconstraint.
*/
void removeRedundantAutoConstraints()
{
if (AutoConstraints.empty()) {
return;
}
auto sketchobject = getSketchObject();
auto autoConstraints = toPointerVector(AutoConstraints);
// Allows a diagnose with the new autoconstraints as if they were part of the sketchobject,
// but WITHOUT adding them to the sketchobject..
sketchobject->diagnoseAdditionalConstraints(autoConstraints);
if (sketchobject->getLastHasRedundancies()) {
Base::Console().Warning(
QT_TRANSLATE_NOOP("Notifications",
"Autoconstraints cause redundancy. Removing them") "\n");
auto lastsketchconstraintindex = sketchobject->Constraints.getSize() - 1;
auto redundants = sketchobject->getLastRedundant(); // redundants is always sorted
for (int index = redundants.size() - 1; index >= 0; index--) {
int redundantconstraintindex = redundants[index] - 1;
if (redundantconstraintindex > lastsketchconstraintindex) {
int removeindex = redundantconstraintindex - lastsketchconstraintindex - 1;
AutoConstraints.erase(std::next(AutoConstraints.begin(), removeindex));
}
else {
// This exception stops the procedure here, which means that:
// 1) Geometry (and constraints of the geometry in case of a multicurve shape)
// are created 2) No autoconstrains are actually added 3) No widget mandated
// constraints are added
THROWM(Base::RuntimeError,
QT_TRANSLATE_NOOP(
"Notifications",
"Redundant constraint is not an autoconstraint. No autoconstraints "
"or additional constraints were added. Please report!") "\n");
}
}
// NOTE: If we removed all redundants in the list, then at this moment there are no
// redundants anymore
}
// This is an awful situation. It should not be possible if the DSH works properly. It is
// just a safeguard.
if (sketchobject->getLastHasConflicts()) {
THROWM(Base::RuntimeError,
QT_TRANSLATE_NOOP(
"Notifications",
"Autoconstraints cause conflicting constraints - Please report!") "\n");
}
}
/** @brief Function that performs a sketcher solver diagnose (determination of DoF and dependent
* parameters), taking into account the suggested AutoConstraints.
*
* @details This function allows to refresh solver information by taking into account any added
* constraint, such as the ones introduced by a widget or on-screen parameters during the
* execution of the DSH.
*
* Ultimately, it is intended to operate in combination with functions obtaining point/element
* specific solver information, in order to decide whether to add constraints or not.
*/
void diagnoseWithAutoConstraints()
{
auto sketchobject = getSketchObject();
auto autoConstraints = toPointerVector(AutoConstraints);
sketchobject->diagnoseAdditionalConstraints(autoConstraints);
if (sketchobject->getLastHasRedundancies() || sketchobject->getLastHasConflicts()) {
THROWM(Base::RuntimeError,
QT_TRANSLATE_NOOP("Notifications",
"Unexpected Redundancy/Conflicting constraint. Check the "
"constraints and autoconstraints of this operation.") "\n");
}
}
/** @brief Function to obtain detailed solver information on one point type geometric element.*/
Sketcher::SolverGeometryExtension::PointParameterStatus
getPointInfo(const Sketcher::GeoElementId& element)
{
if (element.isCurve()) {
THROWM(Base::TypeError, "getPointInfo: Provided geometry element is not a point!")
}
auto sketchobject = getSketchObject();
// it is important not to rely on Geometry attached extension from the solver, as geometry
// is not updated during temporary diagnose of additional constraints
const auto& solvedsketch = sketchobject->getSolvedSketch();
auto solvext = solvedsketch.getSolverExtension(element.GeoId);
if (solvext) {
auto pointinfo = solvext->getPoint(element.Pos);
return pointinfo;
}
THROWM(Base::ValueError,
"Geometry element does not have solver information (possibly when trying to apply "
"widget constraints)!")
}
/** @brief Function to obtain detailed DoFs of one line type geometric element.*/
int getLineDoFs(int geoid)
{
auto startpointinfo =
getPointInfo(Sketcher::GeoElementId(geoid, Sketcher::PointPos::start));
auto endpointinfo = getPointInfo(Sketcher::GeoElementId(geoid, Sketcher::PointPos::end));
int DoFs = startpointinfo.getDoFs();
DoFs += endpointinfo.getDoFs();
return DoFs;
}
/** @brief Function to obtain detailed solver information on one edge.*/
Sketcher::SolverGeometryExtension::EdgeParameterStatus getEdgeInfo(int geoid)
{
auto sketchobject = getSketchObject();
// it is important not to rely on Geometry attached extension from the solver, as geometry
// is not updated during temporary diagnose of additional constraints
const auto& solvedsketch = sketchobject->getSolvedSketch();
auto solvext = solvedsketch.getSolverExtension(geoid);
if (solvext) {
Sketcher::SolverGeometryExtension::EdgeParameterStatus edgeinfo =
solvext->getEdgeParameters();
return edgeinfo;
}
THROWM(Base::ValueError,
"Geometry does not have solver extension when trying to apply widget constraints!")
}
/** @brief Function to add shape inherent constraints (the ones that define the shape) to the
* ShapeConstraints vector.
*
* @details These functions have the highest priority when adding the geometry as they are
* inherent part of it and the shape would not go without them. Lower priority constraints are
* AutoConstraints and constraints mandated by the widget/on-screen parameters.
* .*/
auto addToShapeConstraints(Sketcher::ConstraintType type,
int first,
Sketcher::PointPos firstPos = Sketcher::PointPos::none,
int second = -2000,
Sketcher::PointPos secondPos = Sketcher::PointPos::none,
int third = -2000,
Sketcher::PointPos thirdPos = Sketcher::PointPos::none)
{
auto constr = std::make_unique<Sketcher::Constraint>();
constr->Type = type;
constr->First = first;
constr->FirstPos = firstPos;
constr->Second = second;
constr->SecondPos = secondPos;
constr->Third = third;
constr->ThirdPos = thirdPos;
return ShapeConstraints.emplace_back(std::move(constr)).get();
}
/** @brief Function to add a line to the ShapeGeometry vector.*/
auto addLineToShapeGeometry(Base::Vector3d p1, Base::Vector3d p2, bool constructionMode)
{
auto line = std::make_unique<Part::GeomLineSegment>();
line->setPoints(p1, p2);
Sketcher::GeometryFacade::setConstruction(line.get(), constructionMode);
return static_cast<Part::GeomLineSegment*>(
ShapeGeometry.emplace_back(std::move(line)).get());
}
/** @brief Function to add an arc to the ShapeGeometry vector.*/
auto addArcToShapeGeometry(Base::Vector3d p1,
double start,
double end,
double radius,
bool constructionMode)
{
auto arc = std::make_unique<Part::GeomArcOfCircle>();
arc->setCenter(p1);
arc->setRange(start, end, true);
arc->setRadius(radius);
Sketcher::GeometryFacade::setConstruction(arc.get(), constructionMode);
return static_cast<Part::GeomArcOfCircle*>(
ShapeGeometry.emplace_back(std::move(arc)).get());
}
/** @brief Function to add a point to the ShapeGeometry vector.*/
auto addPointToShapeGeometry(Base::Vector3d p1, bool constructionMode)
{
auto point = std::make_unique<Part::GeomPoint>();
point->setPoint(p1);
Sketcher::GeometryFacade::setConstruction(point.get(), constructionMode);
return static_cast<Part::GeomPoint*>(ShapeGeometry.emplace_back(std::move(point)).get());
}
/** @brief Function to add an ellipse to the ShapeGeometry vector.*/
auto addEllipseToShapeGeometry(Base::Vector3d centerPoint,
Base::Vector3d majorAxisDirection,
double majorRadius,
double minorRadius,
bool constructionMode)
{
auto ellipse = std::make_unique<Part::GeomEllipse>();
ellipse->setMajorRadius(majorRadius);
ellipse->setMinorRadius(minorRadius);
ellipse->setMajorAxisDir(majorAxisDirection);
ellipse->setCenter(centerPoint);
Sketcher::GeometryFacade::setConstruction(ellipse.get(), constructionMode);
return static_cast<Part::GeomEllipse*>(
ShapeGeometry.emplace_back(std::move(ellipse)).get());
}
/** @brief Function to add a circle to the ShapeGeometry vector.*/
auto addCircleToShapeGeometry(Base::Vector3d centerPoint, double radius, bool constructionMode)
{
auto circle = std::make_unique<Part::GeomCircle>();
circle->setRadius(radius);
circle->setCenter(centerPoint);
Sketcher::GeometryFacade::setConstruction(circle.get(), constructionMode);
return static_cast<Part::GeomCircle*>(ShapeGeometry.emplace_back(std::move(circle)).get());
}
/** @brief Function to add all the geometries and constraints in ShapeGeometry and
* ShapeConstraints vectors to the SketchObject.*/
void commandAddShapeGeometryAndConstraints()
{
auto shapeGeometry = toPointerVector(ShapeGeometry);
Gui::Command::doCommand(Gui::Command::Doc,
Sketcher::PythonConverter::convert(
Gui::Command::getObjectCmd(sketchgui->getObject()),
shapeGeometry,
Sketcher::PythonConverter::Mode::OmitInternalGeometry)
.c_str());
auto shapeConstraints = toPointerVector(ShapeConstraints);
Gui::Command::doCommand(
Gui::Command::Doc,
Sketcher::PythonConverter::convert(Gui::Command::getObjectCmd(sketchgui->getObject()),
shapeConstraints)
.c_str());
}
/** @brief Function to draw as an edit curve all the geometry in the ShapeGeometry vector.*/
void DrawShapeGeometry()
{
drawEdit(toPointerVector(ShapeGeometry));
}
/** @brief Function to create a shape into ShapeGeometry vector and draw it.*/
void CreateAndDrawShapeGeometry()
{
createShape(true);
drawEdit(toPointerVector(ShapeGeometry));
}
//@}
protected:
std::vector<std::vector<AutoConstraint>> sugConstraints;
std::vector<std::unique_ptr<Part::Geometry>> ShapeGeometry;
std::vector<std::unique_ptr<Sketcher::Constraint>> ShapeConstraints;
std::vector<std::unique_ptr<Sketcher::Constraint>> AutoConstraints;
bool avoidRedundants;
bool continuousMode;
};
} // namespace SketcherGui
#endif // SKETCHERGUI_DrawSketchDefaultHandler_H