From d586ea62387e041afe86686a890d5f4747af6bc5 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sat, 11 Feb 2023 23:05:52 +0100 Subject: [PATCH] Sketcher/Part: Grid - Architecture ================================== - Move all grid specific code out of ViewProviderSketch and EditModeCoinManager. - The code in made into a new extension in Part - ViewProviderGridExtension - ViewProviderSketch starts deriving from this new extension - ViewProviderSketch configures the extension according to its Grid preferences - Grid code refactored to remove hardcoded sketcher preference parameters. - ViewProviderGridExtension handles property name/type changes within its competence. --- src/Mod/Part/Gui/AppPartGui.cpp | 3 + src/Mod/Part/Gui/CMakeLists.txt | 2 + .../Part/Gui/ViewProviderGridExtension.cpp | 496 ++ src/Mod/Part/Gui/ViewProviderGridExtension.h | 95 + src/Mod/Sketcher/Gui/CMakeLists.txt | 2 - src/Mod/Sketcher/Gui/EditModeCoinManager.cpp | 27 - src/Mod/Sketcher/Gui/EditModeCoinManager.h | 5 - .../Gui/EditModeCoinManagerParameters.h | 5 - .../Sketcher/Gui/EditModeGridCoinManager.cpp | 242 - .../Sketcher/Gui/EditModeGridCoinManager.h | 76 - src/Mod/Sketcher/Gui/ViewProviderSketch.cpp | 7589 ++++++++--------- src/Mod/Sketcher/Gui/ViewProviderSketch.h | 1585 ++-- .../Gui/ViewProviderSketchCoinAttorney.h | 1 - 13 files changed, 5175 insertions(+), 4953 deletions(-) create mode 100644 src/Mod/Part/Gui/ViewProviderGridExtension.cpp create mode 100644 src/Mod/Part/Gui/ViewProviderGridExtension.h delete mode 100644 src/Mod/Sketcher/Gui/EditModeGridCoinManager.cpp delete mode 100644 src/Mod/Sketcher/Gui/EditModeGridCoinManager.h diff --git a/src/Mod/Part/Gui/AppPartGui.cpp b/src/Mod/Part/Gui/AppPartGui.cpp index e5ed4896f2..53df09bf63 100644 --- a/src/Mod/Part/Gui/AppPartGui.cpp +++ b/src/Mod/Part/Gui/AppPartGui.cpp @@ -50,6 +50,7 @@ #include "ViewProvider.h" #include "ViewProvider2DObject.h" #include "ViewProviderAttachExtension.h" +#include "ViewProviderGridExtension.h" #include "ViewProviderBoolean.h" #include "ViewProviderBox.h" #include "ViewProviderCircleParametric.h" @@ -155,6 +156,8 @@ PyMOD_INIT_FUNC(PartGui) PartGui::SoFCControlPoints ::initClass(); PartGui::ViewProviderAttachExtension ::init(); PartGui::ViewProviderAttachExtensionPython ::init(); + PartGui::ViewProviderGridExtension ::init(); + PartGui::ViewProviderGridExtensionPython ::init(); PartGui::ViewProviderSplineExtension ::init(); PartGui::ViewProviderSplineExtensionPython ::init(); PartGui::ViewProviderPartExt ::init(); diff --git a/src/Mod/Part/Gui/CMakeLists.txt b/src/Mod/Part/Gui/CMakeLists.txt index 7e9ffacd03..679f66f5f3 100644 --- a/src/Mod/Part/Gui/CMakeLists.txt +++ b/src/Mod/Part/Gui/CMakeLists.txt @@ -174,6 +174,8 @@ SET(PartGui_SRCS ViewProviderPointParametric.h ViewProviderEllipseParametric.cpp ViewProviderEllipseParametric.h + ViewProviderGridExtension.cpp + ViewProviderGridExtension.h ViewProviderHelixParametric.cpp ViewProviderHelixParametric.h ViewProviderPlaneParametric.cpp diff --git a/src/Mod/Part/Gui/ViewProviderGridExtension.cpp b/src/Mod/Part/Gui/ViewProviderGridExtension.cpp new file mode 100644 index 0000000000..49e36e2e5a --- /dev/null +++ b/src/Mod/Part/Gui/ViewProviderGridExtension.cpp @@ -0,0 +1,496 @@ +/*************************************************************************** + * Copyright (c) 2023 Abdullah Tahiri * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +# include + +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ViewProviderGridExtension.h" + + +using namespace PartGui; +using namespace std; + +EXTENSION_PROPERTY_SOURCE(PartGui::ViewProviderGridExtension, Gui::ViewProviderExtension) + +enum class GridStyle { + Dashed = 0, + Light = 1, +}; + +const char* ViewProviderGridExtension::GridStyleEnums[] = { "Dashed","Light",nullptr }; +App::PropertyQuantityConstraint::Constraints ViewProviderGridExtension::GridSizeRange = { 0.001,DBL_MAX,1.0 }; + +namespace PartGui { + +class GridExtensionP : public ParameterGrp::ObserverType { +public: + explicit GridExtensionP(ViewProviderGridExtension *); + ~GridExtensionP(); + + void drawGrid(bool cameraUpdate = false); + + void setEnabled(bool enable); + bool getEnabled(); + + SoSeparator * getGridRoot(); + + // Configurable parameters (to be configured by specific VP) + int GridSizePixelThreshold = 15; + int GridNumberSubdivision = 10; + int GridLinePattern = 0x0f0f; + int GridDivLinePattern = 0xffff; + int GridLineWidth = 1; + int GridDivLineWidth = 2; + unsigned int GridLineColor; + unsigned int GridDivLineColor; + + // VP Independent parameters through observer + int unitsUserSchema = 0; + + /** Observer for parameter group. */ + void OnChange(Base::Subject &rCaller, const char * sReason) override; + +private: + double computeGridSize(const Gui::View3DInventorViewer* viewer); + void createGrid(bool cameraUpdate = false); + void createGridPart(double Step, int numberSubdiv, bool divLines, bool subDivLines, int pattern, SoBaseColor* color, int lineWidth = 1); + + bool checkCameraZoomChange(const Gui::View3DInventorViewer* viewer); + bool checkCameraTranslationChange(const Gui::View3DInventorViewer* viewer); + + void createEditModeInventorNodes(); + + SbVec3f camCenterOnSketch; + float camMaxDimension; + +private: + ViewProviderGridExtension * vp; + + bool enabled = false; + + // scenograph + SoSeparator * GridRoot; +}; + +} // namespace PartGui + + +GridExtensionP::GridExtensionP(ViewProviderGridExtension * vp): + camCenterOnSketch(SbVec3f(0., 0., 0.)), + camMaxDimension(200.), + vp(vp), + GridRoot(nullptr) +{ + SbColor lineCol(0.7f, 0.7f, 0.7f); + GridLineColor = lineCol.getPackedValue(); + GridDivLineColor = GridLineColor; + + createEditModeInventorNodes(); + + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Units"); + unitsUserSchema = hGrp->GetInt("UserSchema", 0); //2 3 5 7 are imperial schemas. 2 3 inches, 5 7 feet + hGrp->Attach(this); +} + +GridExtensionP::~GridExtensionP() +{ + Gui::coinRemoveAllChildren(GridRoot); + GridRoot->unref(); + + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Units"); + hGrp->Detach(this); +} + +bool GridExtensionP::checkCameraZoomChange(const Gui::View3DInventorViewer* viewer) +{ + float newCamMaxDimension = viewer->getMaxDimension(); + if (fabs(newCamMaxDimension - camMaxDimension) > 0) { //ie if user zoomed. + camMaxDimension = newCamMaxDimension; + return true; + } + + return false; +} + +bool GridExtensionP::checkCameraTranslationChange(const Gui::View3DInventorViewer* viewer) +{ + //Then we check if user moved by more than 10% of camera dimension (must be after updating camera dimension). + SbVec3f newCamCenterOnSketch = viewer->getCenterPointOnFocalPlane(); + + if ((camCenterOnSketch - newCamCenterOnSketch).length() > 0.1 * camMaxDimension) { + camCenterOnSketch = newCamCenterOnSketch; + return true; + } + + return false; +} + +double GridExtensionP::computeGridSize(const Gui::View3DInventorViewer* viewer) +{ + short pixelWidth = -1; + short pixelHeight = -1; + viewer->getViewportRegion().getViewportSizePixels().getValue(pixelWidth, pixelHeight); + if (pixelWidth < 0 || pixelHeight < 0) + return vp->GridSize.getValue(); + + int numberOfLines = static_cast(std::max(pixelWidth, pixelHeight)) / GridSizePixelThreshold; + + double unitMultiplier = (unitsUserSchema == 2 || unitsUserSchema == 3) ? 25.4 : (unitsUserSchema == 5 || unitsUserSchema == 7) ? 304.8 : 1; + + double newGridSize = unitMultiplier * pow(GridNumberSubdivision, 1 + floor(log(camMaxDimension / unitMultiplier / numberOfLines) / log(GridNumberSubdivision))); + + //cap the grid size + newGridSize = std::max(newGridSize, 0.000001); + newGridSize = std::min(newGridSize, 10000000.0); + + if (newGridSize != vp->GridSize.getValue()) // avoid unnecessary property update + vp->GridSize.setValue(newGridSize); //grid size must be set for grid snap. But we need to block it from calling createGrid. + + return newGridSize; +} + +void GridExtensionP::createGrid(bool cameraUpdate) +{ + Gui::MDIView* mdi = Gui::Application::Instance->editDocument()->getActiveView(); + Gui::View3DInventorViewer* viewer = static_cast(mdi)->getViewer(); + + bool cameraDimensionsChanged = checkCameraZoomChange(viewer); + + bool cameraCenterMoved = checkCameraTranslationChange(viewer); + + bool gridNeedUpdating = cameraDimensionsChanged || cameraCenterMoved; + + if (!gridNeedUpdating && cameraUpdate) + return; + + Gui::coinRemoveAllChildren(GridRoot); + + double step; + if (vp->GridAuto.getValue()) + step = computeGridSize(viewer); + else + step = vp->GridSize.getValue(); + + auto getColor = [](auto unpackedcolor) { + SoBaseColor* lineColor = new SoBaseColor; + float transparency; + SbColor lineCol(0.7f, 0.7f, 0.7f); + lineCol.setPackedValue(unpackedcolor, transparency); + lineColor->rgb.setValue(lineCol); + return lineColor; + }; + + //First we create the subdivision lines + createGridPart(step, GridNumberSubdivision, true, + (GridNumberSubdivision == 1), GridLinePattern, + getColor(GridLineColor), GridLineWidth); + + //Second we create the wider lines marking every nth lines + if (GridNumberSubdivision > 1) { + createGridPart(step, GridNumberSubdivision, false, true, + GridDivLinePattern, getColor(GridDivLineColor), GridDivLineWidth); + } +} + +void GridExtensionP::createGridPart(double Step, int numberSubdiv, bool subDivLines, bool divLines, int pattern, SoBaseColor* color, int lineWidth) +{ + SoGroup* parent = new Gui::SoSkipBoundingGroup(); + GridRoot->addChild(parent); + SoVertexProperty* vts; + + parent->addChild(color); + + if (vp->GridStyle.getValue() == static_cast(GridStyle::Dashed)) { + SoDrawStyle* DefaultStyle = new SoDrawStyle; + DefaultStyle->lineWidth = lineWidth; + DefaultStyle->linePattern = pattern; + parent->addChild(DefaultStyle); + } + else { + SoMaterial* LightStyle = new SoMaterial; + LightStyle->transparency = 0.7f; + parent->addChild(LightStyle); + } + + SoPickStyle* PickStyle = new SoPickStyle; + PickStyle->style = SoPickStyle::UNPICKABLE; + parent->addChild(PickStyle); + + SoLineSet* grid = new SoLineSet; + vts = new SoVertexProperty; + grid->vertexProperty = vts; + + float gridDimension = 1.5 * camMaxDimension; + int vlines = static_cast(gridDimension / Step); // total number of vertical lines + int nlines = 2 * vlines; // total number of lines + if (nlines > 2000) { + Gui::coinRemoveAllChildren(GridRoot); + return; + } + + // set the grid indices + grid->numVertices.setNum(nlines); + auto * vertices = grid->numVertices.startEditing(); + for (int i = 0; i < nlines; i++) + vertices[i] = 2; + grid->numVertices.finishEditing(); + + // set the grid coordinates + vts->vertex.setNum(2 * nlines); + SbVec3f* vertex_coords = vts->vertex.startEditing(); + + float minX, minY, maxX, maxY, z; + camCenterOnSketch.getValue(minX, minY, z); + minX -= (gridDimension / 2); + minY -= (gridDimension / 2); + maxX = minX + gridDimension; + maxY = minY + gridDimension; + + // vertical lines + int i_offset_x = static_cast(minX / Step); + for (int i = 0; i < vlines; i++) { + int iStep = (i + i_offset_x); + if (((iStep % numberSubdiv == 0) && divLines) || ((iStep % numberSubdiv != 0) && subDivLines)) { + vertex_coords[2 * i].setValue(iStep * Step, minY, 0); + vertex_coords[2 * i + 1].setValue(iStep * Step, maxY, 0); + } + else { + /*the number of vertices is defined before. To know the number of vertices ahead it would require + to run the loop once before, which would double computation time. + If vertices are not filled then there're visual bugs so there are here filled with dummy values.*/ + vertex_coords[2 * i].setValue(0, 0, 0); + vertex_coords[2 * i + 1].setValue(0, 0, 0); + } + } + + // horizontal lines + int i_offset_y = static_cast(minY / Step) - vlines; + for (int i = vlines; i < nlines; i++) { + int iStep = (i + i_offset_y); + if (((iStep % numberSubdiv == 0) && divLines) || ((iStep % numberSubdiv != 0) && subDivLines)) { + vertex_coords[2 * i].setValue(minX, iStep * Step, 0); + vertex_coords[2 * i + 1].setValue(maxX, iStep * Step, 0); + } + else { + vertex_coords[2 * i].setValue(0, 0, 0); + vertex_coords[2 * i + 1].setValue(0, 0, 0); + } + } + vts->vertex.finishEditing(); + + parent->addChild(vts); + parent->addChild(grid); +} + +void GridExtensionP::setEnabled(bool enable) +{ + enabled=enable; + + drawGrid(); + +} + +bool GridExtensionP::getEnabled() +{ + return enabled; +} + +void GridExtensionP::createEditModeInventorNodes() +{ + // Create Grid Coin nodes ++++++++++++++++++++++++++++++++++++++++++ + GridRoot = new SoSeparator(); + GridRoot->ref(); + GridRoot->setName("GridRoot"); + +} + +SoSeparator * GridExtensionP::getGridRoot() +{ + return GridRoot; +} + +void GridExtensionP::drawGrid(bool cameraUpdate) { + if (vp->ShowGrid.getValue() && enabled) { + createGrid(cameraUpdate); + } + else { + Gui::coinRemoveAllChildren(GridRoot); + } +} + +void GridExtensionP::OnChange(Base::Subject &rCaller, const char * sReason) +{ + (void) rCaller; + + if (strcmp(sReason, "UserSchema") == 0) { + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Units"); + unitsUserSchema = hGrp->GetInt("UserSchema", 0); //2 3 5 7 are imperial schemas. 2 3 inches, 5 7 feet + } +} + +ViewProviderGridExtension::ViewProviderGridExtension() +{ + + EXTENSION_ADD_PROPERTY_TYPE(ShowGrid, (false), "Grid", (App::PropertyType)(App::Prop_None), "Switch the grid on/off"); + EXTENSION_ADD_PROPERTY_TYPE(GridSize, (10.0), "Grid", (App::PropertyType)(App::Prop_None), "Gap size of the grid"); + EXTENSION_ADD_PROPERTY_TYPE(GridStyle, (0L), "Grid", (App::PropertyType)(App::Prop_None), "Appearance style of the grid"); + EXTENSION_ADD_PROPERTY_TYPE(GridSnap, (false), "Grid", (App::PropertyType)(App::Prop_None), "Switch the grid snap on/off"); + EXTENSION_ADD_PROPERTY_TYPE(GridAuto, (true), "Grid", (App::PropertyType)(App::Prop_None), "Change size of grid based on view area."); + + initExtensionType(ViewProviderGridExtension::getExtensionClassTypeId()); + + GridStyle.setEnums(GridStyleEnums); + GridSize.setConstraints(&GridSizeRange); + + pImpl = std::make_unique(this); +} + +ViewProviderGridExtension::~ViewProviderGridExtension() +{} + +void ViewProviderGridExtension::setGridEnabled(bool enable) +{ + pImpl->setEnabled(enable); +} + +void ViewProviderGridExtension::drawGrid(bool cameraUpdate) +{ + pImpl->drawGrid(cameraUpdate); +} + +SoSeparator* ViewProviderGridExtension::getGridNode() +{ + return pImpl->getGridRoot(); +} + +void ViewProviderGridExtension::extensionUpdateData(const App::Property* prop) +{ + if(pImpl->getEnabled()) { + if (prop->getTypeId() == Part::PropertyPartShape::getClassTypeId()) { + pImpl->drawGrid(); + } + } +} + +void ViewProviderGridExtension::extensionOnChanged(const App::Property* prop) +{ + if(pImpl->getEnabled()) { + if (prop == &ShowGrid || + prop == &GridSize || + prop == &GridStyle) { + pImpl->drawGrid(); + } + } +} + +void ViewProviderGridExtension::setGridSizePixelThreshold(int value) +{ + pImpl->GridSizePixelThreshold = value; +} + +void ViewProviderGridExtension::setGridNumberSubdivision(int value) +{ + pImpl->GridNumberSubdivision = value; +} + +void ViewProviderGridExtension::setGridLinePattern(int pattern) +{ + pImpl->GridLinePattern = pattern; +} + +void ViewProviderGridExtension::setGridDivLinePattern(int pattern) +{ + pImpl->GridDivLinePattern = pattern; +} + +void ViewProviderGridExtension::setGridLineWidth(int width) +{ + pImpl->GridLineWidth = width; +} + +void ViewProviderGridExtension::setGridDivLineWidth(int width) +{ + pImpl->GridDivLineWidth = width; +} + +void ViewProviderGridExtension::setGridLineColor(unsigned int color) +{ + pImpl->GridLineColor = color; +} + +void ViewProviderGridExtension::setGridDivLineColor(unsigned int color) +{ + pImpl->GridDivLineColor = color; +} + +bool ViewProviderGridExtension::extensionHandleChangedPropertyType(Base::XMLReader& reader, + const char* TypeName, + App::Property* prop) +{ + Base::Type inputType = Base::Type::fromName(TypeName); + + if (prop->getTypeId().isDerivedFrom(App::PropertyFloat::getClassTypeId()) && + inputType.isDerivedFrom(App::PropertyFloat::getClassTypeId())) { + // Do not directly call the property's Restore method in case the implementation + // has changed. So, create a temporary PropertyFloat object and assign the value. + App::PropertyFloat floatProp; + floatProp.Restore(reader); + static_cast(prop)->setValue(floatProp.getValue()); + return true; + } + + return false; +} + +namespace Gui { + EXTENSION_PROPERTY_SOURCE_TEMPLATE(PartGui::ViewProviderGridExtensionPython, PartGui::ViewProviderGridExtension) + +// explicit template instantiation + template class PartGuiExport ViewProviderExtensionPythonT; +} diff --git a/src/Mod/Part/Gui/ViewProviderGridExtension.h b/src/Mod/Part/Gui/ViewProviderGridExtension.h new file mode 100644 index 0000000000..fe30c556e1 --- /dev/null +++ b/src/Mod/Part/Gui/ViewProviderGridExtension.h @@ -0,0 +1,95 @@ +/*************************************************************************** + * Copyright (c) 2023 Abdullah Tahiri * + * * + * 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 PARTGUI_VIEWPROVIDERGRIDEXTENSION_H +#define PARTGUI_VIEWPROVIDERGRIDEXTENSION_H + +#include +#include +#include +#include + +namespace PartGui { + +class GridExtensionP; + +class PartGuiExport ViewProviderGridExtension : public Gui::ViewProviderExtension +{ + EXTENSION_PROPERTY_HEADER_WITH_OVERRIDE(PartGui::ViewProviderGridExtension); + +public: + App::PropertyBool ShowGrid; + App::PropertyLength GridSize; + App::PropertyEnumeration GridStyle; + App::PropertyBool GridSnap; + App::PropertyBool GridAuto; + + /// constructor + ViewProviderGridExtension(); + ~ViewProviderGridExtension() override; + + void setGridEnabled(bool enable); + + void drawGrid(bool cameraUpdate); + + void extensionUpdateData(const App::Property*) override; + + SoSeparator* getGridNode(); + +protected: + void extensionOnChanged(const App::Property* /*prop*/) override; + + // configuration parameters + void setGridSizePixelThreshold(int value); + void setGridNumberSubdivision(int value); + void setGridLinePattern(int pattern); + void setGridDivLinePattern(int pattern); + void setGridLineWidth(int width); + void setGridDivLineWidth(int width); + void setGridLineColor(unsigned int color); + void setGridDivLineColor(unsigned int color); + + + //void extensionRestore(Base::XMLReader& reader) override; + //virtual void extHandleChangedPropertyName(Base::XMLReader &reader, const char* TypeName, const char* PropName) override; + //void handleChangedPropertyType(Base::XMLReader& reader, const char* TypeName, App::Property* prop) override; + virtual bool extensionHandleChangedPropertyType(Base::XMLReader &reader, const char * TypeName, App::Property * prop) override; + + +private: + static const char* GridStyleEnums[]; + static App::PropertyQuantityConstraint::Constraints GridSizeRange; + + std::unique_ptr pImpl; + + +}; + +using ViewProviderGridExtensionPython = Gui::ViewProviderExtensionPythonT; + + +} // namespace PartGui + + +#endif // PARTGUI_VIEWPROVIDERGRIDEXTENSION_H + diff --git a/src/Mod/Sketcher/Gui/CMakeLists.txt b/src/Mod/Sketcher/Gui/CMakeLists.txt index ff3338aea5..ea7062111f 100644 --- a/src/Mod/Sketcher/Gui/CMakeLists.txt +++ b/src/Mod/Sketcher/Gui/CMakeLists.txt @@ -120,8 +120,6 @@ SET(SketcherGui_SRCS EditModeCoinManagerParameters.cpp EditModeCoinManager.cpp EditModeCoinManager.h - EditModeGridCoinManager.cpp - EditModeGridCoinManager.h EditModeGeometryCoinManager.cpp EditModeGeometryCoinManager.h EditModeConstraintCoinManager.cpp diff --git a/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp b/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp index 6de1918122..5f2949d657 100644 --- a/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp +++ b/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp @@ -57,15 +57,6 @@ #include "ViewProviderSketch.h" #include "ViewProviderSketchCoinAttorney.h" -#include "EditModeGridCoinManager.h" - -#include "EditModeGeometryCoinManager.h" -#include "EditModeConstraintCoinManager.h" - -#include "EditModeCoinManager.h" - -#include "Utils.h" - using namespace SketcherGui; using namespace Sketcher; @@ -350,9 +341,6 @@ EditModeCoinManager::EditModeCoinManager(ViewProviderSketch &vp):viewProvider(vp analysisResults, editModeScenegraphNodes, coinMapping); - - pEditModeGridCoinManager = std::make_unique( viewProvider, - editModeScenegraphNodes); // Create Edit Mode Scenograph createEditModeInventorNodes(); @@ -659,14 +647,6 @@ void EditModeCoinManager::createEditModeInventorNodes() ViewProviderSketchCoinAttorney::addNodeToRoot(viewProvider, editModeScenegraphNodes.EditRoot); editModeScenegraphNodes.EditRoot->renderCaching = SoSeparator::OFF ; - // Create Grid Coin nodes ++++++++++++++++++++++++++++++++++++++++++ - editModeScenegraphNodes.GridRoot = new SoSeparator(); - editModeScenegraphNodes.GridRoot->ref(); - editModeScenegraphNodes.GridRoot->setName("GridRoot"); - editModeScenegraphNodes.EditRoot->addChild(editModeScenegraphNodes.GridRoot); - if (viewProvider.ShowGrid.getValue()) - pEditModeGridCoinManager->createGrid(); - // Create Geometry Coin nodes ++++++++++++++++++++++++++++++++++++++ pEditModeGeometryCoinManager->createEditModeInventorNodes(); @@ -814,13 +794,6 @@ void EditModeCoinManager::updateVirtualSpace() pEditModeConstraintCoinManager->updateVirtualSpace(); } -void EditModeCoinManager::drawGrid(bool cameraUpdate) { - if (viewProvider.ShowGrid.getValue()) - pEditModeGridCoinManager->createGrid(cameraUpdate); - else - Gui::coinRemoveAllChildren(editModeScenegraphNodes.GridRoot); -} - /************************ Resizing of coin nodes ************************/ int EditModeCoinManager::defaultApplicationFontSizePixels() const { diff --git a/src/Mod/Sketcher/Gui/EditModeCoinManager.h b/src/Mod/Sketcher/Gui/EditModeCoinManager.h index d4630fd856..561c7a7a76 100644 --- a/src/Mod/Sketcher/Gui/EditModeCoinManager.h +++ b/src/Mod/Sketcher/Gui/EditModeCoinManager.h @@ -60,7 +60,6 @@ namespace SketcherGui { class ViewProviderSketch; class EditModeConstraintCoinManager; class EditModeGeometryCoinManager; -class EditModeGridCoinManager; using GeoList = Sketcher::GeoList; using GeoListFacade = Sketcher::GeoListFacade; @@ -217,9 +216,6 @@ public: void drawConstraintIcons(const GeoListFacade & geolistfacade); //@} - //Draw the grid - void drawGrid(bool cameraUpdate = false); - /** @name coin node access*/ SoSeparator* getRootEditNode(); //@} @@ -287,7 +283,6 @@ private: // Coin Helpers std::unique_ptr pEditModeConstraintCoinManager; std::unique_ptr pEditModeGeometryCoinManager; - std::unique_ptr pEditModeGridCoinManager; }; diff --git a/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.h b/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.h index 3f82baf867..9e4781a9d5 100644 --- a/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.h +++ b/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.h @@ -281,11 +281,6 @@ struct ConstraintParameters { /** @brief Helper struct adapted to store the pointer to edit mode scenegraph objects. */ struct EditModeScenegraphNodes { - /** @name Grid nodes*/ - //@{ - SoSeparator * GridRoot; - //@} - /** @name Point nodes*/ //@{ SoSeparator * EditRoot; diff --git a/src/Mod/Sketcher/Gui/EditModeGridCoinManager.cpp b/src/Mod/Sketcher/Gui/EditModeGridCoinManager.cpp deleted file mode 100644 index 5db5429691..0000000000 --- a/src/Mod/Sketcher/Gui/EditModeGridCoinManager.cpp +++ /dev/null @@ -1,242 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2021 Abdullah Tahiri * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Library General Public License for more details. * - * * - * You should have received a copy of the GNU Library General Public * - * License along with this library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ***************************************************************************/ - - -#include "PreCompiled.h" - -#ifndef _PreComp_ -# include -# include -# include -# include -# include -# include -# include -# include -# include -#endif // #ifndef _PreComp_ - -#include -#include -#include -#include - -#include "ViewProviderSketch.h" -#include "EditModeCoinManager.h" -#include "EditModeGridCoinManager.h" - -using namespace SketcherGui; - -//**************************** EditModeGridCoinManager class ****************************** - -EditModeGridCoinManager::EditModeGridCoinManager( ViewProviderSketch &vp, - EditModeScenegraphNodes & editModeScenegraph): - camCenterOnSketch(SbVec3f(0., 0., 0.)), - camMaxDimension(200.), - viewProvider(vp), - editModeScenegraphNodes(editModeScenegraph) -{ -} - -EditModeGridCoinManager::~EditModeGridCoinManager() -{} - -double EditModeGridCoinManager::getGridSize(const Gui::View3DInventorViewer* viewer) -{ - short pixelWidth = -1; - short pixelHeight = -1; - viewer->getViewportRegion().getViewportSizePixels().getValue(pixelWidth, pixelHeight); - if (pixelWidth < 0 || pixelHeight < 0) - return viewProvider.GridSize.getValue(); - - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/General"); - int numberOfLines = std::max(pixelWidth, pixelHeight) / hGrp->GetInt("GridSizePixelThreshold", 15); - ParameterGrp::handle hGrp2 = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Units"); - int units = hGrp2->GetInt("UserSchema", 0); //2 3 5 7 are imperial schemas. 2 3 inches, 5 7 feet - double unitMultiplier = (units == 2 || units == 3) ? 25.4 : (units == 5 || units == 7) ? 304.8 : 1; - - int subdivisions = hGrp->GetInt("GridNumberSubdivision", 10); - double newGridSize = unitMultiplier * pow(subdivisions, 1 + floor(log(camMaxDimension / unitMultiplier / numberOfLines) / log(subdivisions))); - - //cap the grid size - newGridSize = std::max(newGridSize, 0.000001); - newGridSize = std::min(newGridSize, 10000000.0); - - if (newGridSize != viewProvider.GridSize.getValue()) // protect from recursive calls - viewProvider.GridSize.setValue(newGridSize); //grid size must be set for grid snap. But we need to block it from calling createGrid. - - return newGridSize; -} - -SoSeparator* EditModeGridCoinManager::createGrid(bool cameraUpdate) -{ - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/General"); - Gui::MDIView* mdi = Gui::Application::Instance->editDocument()->getActiveView(); - Gui::View3DInventorViewer* viewer = static_cast(mdi)->getViewer(); - - //First check if the user zoomed in or out by getting the camera dimension. - float newCamMaxDimension = viewer->getMaxDimension(); - bool cameraDimensionsChanged = false; - if (fabs(newCamMaxDimension - camMaxDimension) > 0) { //ie if user zoomed. - camMaxDimension = newCamMaxDimension; - cameraDimensionsChanged = true; - } - - //Then we check if user moved by more than 10% of camera dimension (must be after updating camera dimension). - SbVec3f newCamCenterOnSketch = viewer->getCenterPointOnFocalPlane(); - bool cameraCenterMoved = false; - if ((camCenterOnSketch - newCamCenterOnSketch).length() > 0.1 * camMaxDimension) { - camCenterOnSketch = newCamCenterOnSketch; - cameraCenterMoved = true; - } - - bool gridNeedUpdating = cameraDimensionsChanged || cameraCenterMoved; - - if (!gridNeedUpdating && cameraUpdate) - return editModeScenegraphNodes.GridRoot; - - Gui::coinRemoveAllChildren(editModeScenegraphNodes.GridRoot); - - double step; - if (viewProvider.GridAuto.getValue()) - step = getGridSize(viewer); - else - step = viewProvider.GridSize.getValue(); - - int numberSubdivision = hGrp->GetInt("GridNumberSubdivision", 10); - - auto getColor = [](auto prefName) { - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/General"); - SoBaseColor* lineColor = new SoBaseColor; - float transparency; - SbColor lineCol(0.7f, 0.7f, 0.7f); - unsigned long lineColorL = hGrp->GetUnsigned(prefName, (unsigned long)(lineCol.getPackedValue())); - lineCol.setPackedValue((uint32_t)lineColorL, transparency); - lineColor->rgb.setValue(lineCol); - return lineColor; - - }; - - //First we create the subdivision lines - createGridPart(step, numberSubdivision, true, (numberSubdivision == 1) ? true : false, - hGrp->GetInt("GridLinePattern", 0x0f0f), getColor("GridLineColor"), hGrp->GetInt("GridLineWidth", 1)); - - //Second we create the wider lines marking every nth lines - if (numberSubdivision > 1) { - createGridPart(step, numberSubdivision, false, true, - hGrp->GetInt("GridDivLinePattern", 0xffff), getColor("GridDivLineColor"), hGrp->GetInt("GridDivLineWidth", 2)); - } - - return editModeScenegraphNodes.GridRoot; -} - -SoSeparator* EditModeGridCoinManager::createGridPart(double Step, int numberSubdiv, bool subDivLines, bool divLines, int pattern, SoBaseColor* color, int lineWidth) -{ - SoGroup* parent = new Gui::SoSkipBoundingGroup(); - editModeScenegraphNodes.GridRoot->addChild(parent); - SoVertexProperty* vts; - - parent->addChild(color); - - if (viewProvider.GridStyle.getValue() == 0) { - SoDrawStyle* DefaultStyle = new SoDrawStyle; - DefaultStyle->lineWidth = lineWidth; - DefaultStyle->linePattern = pattern; - parent->addChild(DefaultStyle); - } - else { - SoMaterial* LightStyle = new SoMaterial; - LightStyle->transparency = 0.7f; - parent->addChild(LightStyle); - } - - SoPickStyle* PickStyle = new SoPickStyle; - PickStyle->style = SoPickStyle::UNPICKABLE; - parent->addChild(PickStyle); - - SoLineSet* grid = new SoLineSet; - vts = new SoVertexProperty; - grid->vertexProperty = vts; - - float gridDimension = 1.5 * camMaxDimension; - int vlines = static_cast(gridDimension / Step); - int nlines = 2 * vlines; - if (nlines > 2000) { - Gui::coinRemoveAllChildren(editModeScenegraphNodes.GridRoot); - return editModeScenegraphNodes.GridRoot; - } - - // set the grid indices - grid->numVertices.setNum(nlines); - int32_t* vertices = grid->numVertices.startEditing(); - for (int i = 0; i < nlines; i++) - vertices[i] = 2; - grid->numVertices.finishEditing(); - - // set the grid coordinates - vts->vertex.setNum(2 * nlines); - SbVec3f* vertex_coords = vts->vertex.startEditing(); - - float minX, minY, maxX, maxY, z; - camCenterOnSketch.getValue(minX, minY, z); - minX -= (gridDimension / 2); - minY -= (gridDimension / 2); - maxX = minX + gridDimension; - maxY = minY + gridDimension; - - // vertical lines - int i_offset_x = static_cast(minX / Step); - for (int i = 0; i < vlines; i++) { - int iStep = (i + i_offset_x); - if (((iStep % numberSubdiv == 0) && divLines) || ((iStep % numberSubdiv != 0) && subDivLines)) { - vertex_coords[2 * i].setValue(iStep * Step, minY, 0); - vertex_coords[2 * i + 1].setValue(iStep * Step, maxY, 0); - } - else { - /*the number of vertices is defined before. To know the number of vertices ahead it would require - to run the loop once before, which would double computation time. - If vertices are not filled then there're visual bugs so there are here filled with dummy values.*/ - vertex_coords[2 * i].setValue(0, 0, 0); - vertex_coords[2 * i + 1].setValue(0, 0, 0); - } - } - - // horizontal lines - int i_offset_y = static_cast(minY / Step) - vlines; - for (int i = vlines; i < nlines; i++) { - int iStep = (i + i_offset_y); - if (((iStep % numberSubdiv == 0) && divLines) || ((iStep % numberSubdiv != 0) && subDivLines)) { - vertex_coords[2 * i].setValue(minX, iStep * Step, 0); - vertex_coords[2 * i + 1].setValue(maxX, iStep * Step, 0); - } - else { - vertex_coords[2 * i].setValue(0, 0, 0); - vertex_coords[2 * i + 1].setValue(0, 0, 0); - } - } - vts->vertex.finishEditing(); - - parent->addChild(vts); - parent->addChild(grid); - - return editModeScenegraphNodes.GridRoot; -} diff --git a/src/Mod/Sketcher/Gui/EditModeGridCoinManager.h b/src/Mod/Sketcher/Gui/EditModeGridCoinManager.h deleted file mode 100644 index 99d353a6eb..0000000000 --- a/src/Mod/Sketcher/Gui/EditModeGridCoinManager.h +++ /dev/null @@ -1,76 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2021 Abdullah Tahiri * - * * - * 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_EditModeGridCoinManager_H -#define SKETCHERGUI_EditModeGridCoinManager_H - - -#include -#include - -#include "EditModeCoinManagerParameters.h" - -class SoSeparator; -class SoBaseColor; -class SbVec3f; -class View3DInventorViewer; - -namespace SketcherGui { - -class ViewProviderSketch; - -/** @brief Class for managing the Edit mode coin nodes of ViewProviderSketch relating to grid. - * @details - * - * EditModeGridCoinManager is a helper of EditModeCoinManager specialised in grid management. - * - * 1. Creation of Edit mode coin nodes to handle grid representation. - * - */ -class SketcherGuiExport EditModeGridCoinManager -{ - -public: - explicit EditModeGridCoinManager( ViewProviderSketch &vp, - EditModeScenegraphNodes & editModeScenegraph); - ~EditModeGridCoinManager(); - - double getGridSize(const Gui::View3DInventorViewer* viewer); - SoSeparator* createGrid(bool cameraUpdate = false); - SoSeparator* createGridPart(double Step, int numberSubdiv, bool divLines, bool subDivLines, int pattern, SoBaseColor* color, int lineWidth = 1); - - SbVec3f camCenterOnSketch; - float camMaxDimension; - -private: - ViewProviderSketch & viewProvider; - - EditModeScenegraphNodes & editModeScenegraphNodes; -}; - - -} // namespace SketcherGui - - -#endif // SKETCHERGUI_EditModeGridCoinManager_H - diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index ebfc00cd7a..1c3cf6cb67 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -1,3798 +1,3791 @@ -/*************************************************************************** - * Copyright (c) 2009 Juergen Riegel * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Library General Public License for more details. * - * * - * You should have received a copy of the GNU Library General Public * - * License along with this library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ***************************************************************************/ - -#include "PreCompiled.h" -#ifndef _PreComp_ -# include -# include -# include -# include -# include -# include -# include -# include - -# include -# include -# include -# include -# include -# include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "Gui/ToolBarManager.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ViewProviderSketch.h" -#include "DrawSketchHandler.h" -#include "EditDatumDialog.h" -#include "EditModeCoinManager.h" -#include "TaskDlgEditSketch.h" -#include "TaskSketcherValidation.h" -#include "Utils.h" -#include "ViewProviderSketchGeometryExtension.h" - - -FC_LOG_LEVEL_INIT("Sketch",true,true) - -using namespace SketcherGui; -using namespace Sketcher; -namespace bp = boost::placeholders; - -/************** ViewProviderSketch::ParameterObserver *********************/ - -ViewProviderSketch::ParameterObserver::ParameterObserver(ViewProviderSketch &client): Client(client) -{ -} - -ViewProviderSketch::ParameterObserver::~ParameterObserver() -{ - unsubscribeToParameters(); -} - -void ViewProviderSketch::ParameterObserver::updateBoolProperty(const std::string & string, App::Property * property, bool defaultvalue) -{ - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/General"); - - auto boolprop = static_cast(property); - boolprop->setValue(hGrp->GetBool(string.c_str(), defaultvalue)); -} - -void ViewProviderSketch::ParameterObserver::updateColorProperty(const std::string & string, App::Property * property, float r, float g, float b) -{ - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"); - - auto colorprop = static_cast(property); - - colorprop->setValue(r,g,b); - - App::Color elementAppColor = colorprop->getValue(); - unsigned long color = (unsigned long)(elementAppColor.getPackedValue()); - color = hGrp->GetUnsigned(string.c_str(), color); - elementAppColor.setPackedValue((uint32_t)color); - colorprop->setValue( elementAppColor); - -} - -void ViewProviderSketch::ParameterObserver::updateGridSize(const std::string & string, App::Property * property) -{ - (void) property; - (void) string; - - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/General"); - - Client.GridSize.setValue(Base::Quantity::parse(QString::fromLatin1(hGrp->GetGroup("GridSize")->GetASCII("GridSize", "10.0").c_str())).getValue()); -} - -void ViewProviderSketch::ParameterObserver::updateEscapeKeyBehaviour(const std::string & string, App::Property * property) -{ - (void) property; - (void) string; - - ParameterGrp::handle hSketch = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); - Client.viewProviderParameters.handleEscapeButton = !hSketch->GetBool("LeaveSketchWithEscape", true); -} - -void ViewProviderSketch::ParameterObserver::updateAutoRecompute(const std::string & string, App::Property * property) -{ - (void) property; - (void) string; - - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); - Client.viewProviderParameters.autoRecompute = hGrp->GetBool("AutoRecompute",false); -} - -void ViewProviderSketch::ParameterObserver::updateRecalculateInitialSolutionWhileDragging(const std::string & string, App::Property * property) -{ - (void) property; - (void) string; - - ParameterGrp::handle hGrp2 = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); - - Client.viewProviderParameters.recalculateInitialSolutionWhileDragging = hGrp2->GetBool("RecalculateInitialSolutionWhileDragging",true); -} - -void ViewProviderSketch::ParameterObserver::subscribeToParameters() -{ - try { - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/General"); - hGrp->Attach(this); - - ParameterGrp::handle hGrp2 = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); - hGrp2->Attach(this); - - ParameterGrp::handle hGrpv = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"); - hGrpv->Attach(this); - } - catch(const Base::ValueError & e) { // ensure that if parameter strings are not well-formed, the exception is not propagated - Base::Console().Error("ViewProviderSketch: Malformed parameter string: %s\n", e.what()); - } -} - -void ViewProviderSketch::ParameterObserver::unsubscribeToParameters() -{ - try { - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/General"); - hGrp->Detach(this); - - ParameterGrp::handle hGrp2 = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); - hGrp2->Detach(this); - - ParameterGrp::handle hGrpv = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"); - hGrpv->Detach(this); - } - catch(const Base::ValueError & e) { // ensure that if parameter strings are not well-formed, the exception is not propagated - Base::Console().Error("ViewProviderSketch: Malformed parameter string: %s\n", e.what()); - } -} - -void ViewProviderSketch::ParameterObserver::initParameters() -{ - // once initialize the map with the properties - - parameterMap = { - {"HideDependent", - {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, true);}, &Client.HideDependent }}, - {"ShowLinks", - {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, true);}, &Client.ShowLinks }}, - {"ShowSupport", - {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, true);}, &Client.ShowSupport }}, - {"RestoreCamera", - {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, true);}, &Client.RestoreCamera }}, - {"ForceOrtho", - {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, false);}, &Client.ForceOrtho }}, - {"SectionView", - {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, false);}, &Client.SectionView }}, - {"AutoConstraints", - {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, true);}, &Client.Autoconstraints }}, - {"AvoidRedundantAutoconstraints", - {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, true);}, &Client.AvoidRedundant }}, - {"ShowGrid", - {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, false);}, &Client.ShowGrid }}, - {"GridSnap", - {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, false);}, &Client.GridSnap }}, - {"GridAuto", - {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, true); }, &Client.GridAuto }}, - {"GridSize", - {[this](const std::string & string, App::Property * property){ updateGridSize(string, property);}, &Client.GridSize }}, - {"SketchEdgeColor", - {[this](const std::string & string, App::Property * property){ updateColorProperty(string, property, 1.f, 1.f, 1.f);}, &Client.LineColor }}, - {"SketchVertexColor", - {[this](const std::string & string, App::Property * property){ updateColorProperty(string, property, 1.f, 1.f, 1.f);}, &Client.PointColor }}, - {"updateEscapeKeyBehaviour", - {[this](const std::string & string, App::Property * property){ updateEscapeKeyBehaviour(string, property);}, nullptr }}, - {"AutoRecompute", - {[this](const std::string & string, App::Property * property){ updateAutoRecompute(string, property);}, nullptr }}, - {"RecalculateInitialSolutionWhileDragging", - {[this](const std::string & string, App::Property * property){ updateRecalculateInitialSolutionWhileDragging(string, property);}, nullptr }}, - }; - - for( auto & val : parameterMap){ - auto string = val.first; - auto update = std::get<0>(val.second); - auto property = std::get<1>(val.second); - - update(string, property); - } - -} - -void ViewProviderSketch::ParameterObserver::OnChange(Base::Subject &rCaller, const char * sReason) -{ - (void) rCaller; - - auto key = parameterMap.find(sReason); - if( key != parameterMap.end()) { - auto string = key->first; - auto update = std::get<0>(key->second); - auto property = std::get<1>(key->second); - - update(string, property); - } - -} - -/*************************** ViewProviderSketch **************************/ - -// Struct for holding previous click information -SbTime ViewProviderSketch::DoubleClick::prvClickTime; -SbVec2s ViewProviderSketch::DoubleClick::prvClickPos; //used by double-click-detector -SbVec2s ViewProviderSketch::DoubleClick::prvCursorPos; -SbVec2s ViewProviderSketch::DoubleClick::newCursorPos; - -//************************************************************************** -// Construction/Destruction - -/* TRANSLATOR SketcherGui::ViewProviderSketch */ -const char* ViewProviderSketch::GridStyleEnums[] = { "Dashed","Light",nullptr }; -App::PropertyQuantityConstraint::Constraints ViewProviderSketch::GridSizeRange = { 0.001,DBL_MAX,1.0 }; - -PROPERTY_SOURCE_WITH_EXTENSIONS(SketcherGui::ViewProviderSketch, PartGui::ViewProvider2DObject) - - -ViewProviderSketch::ViewProviderSketch() - : SelectionObserver(false), - Mode(STATUS_NONE), - listener(nullptr), - editCoinManager(nullptr), - pObserver(std::make_unique(*this)), - sketchHandler(nullptr), - viewOrientationFactor(1) -{ - PartGui::ViewProviderAttachExtension::initExtension(this); - - ADD_PROPERTY_TYPE(Autoconstraints,(true),"Auto Constraints",(App::PropertyType)(App::Prop_None),"Create auto constraints"); - ADD_PROPERTY_TYPE(AvoidRedundant,(true),"Auto Constraints",(App::PropertyType)(App::Prop_None),"Avoid redundant autoconstraint"); - ADD_PROPERTY_TYPE(TempoVis,(Py::None()),"Visibility automation",(App::PropertyType)(App::Prop_ReadOnly),"Object that handles hiding and showing other objects when entering/leaving sketch."); - ADD_PROPERTY_TYPE(HideDependent,(true),"Visibility automation",(App::PropertyType)(App::Prop_ReadOnly),"If true, all objects that depend on the sketch are hidden when opening editing."); - ADD_PROPERTY_TYPE(ShowLinks,(true),"Visibility automation",(App::PropertyType)(App::Prop_ReadOnly),"If true, all objects used in links to external geometry are shown when opening sketch."); - ADD_PROPERTY_TYPE(ShowSupport,(true),"Visibility automation",(App::PropertyType)(App::Prop_ReadOnly),"If true, all objects this sketch is attached to are shown when opening sketch."); - ADD_PROPERTY_TYPE(RestoreCamera,(true),"Visibility automation",(App::PropertyType)(App::Prop_ReadOnly),"If true, camera position before entering sketch is remembered, and restored after closing it."); - ADD_PROPERTY_TYPE(ForceOrtho,(false),"Visibility automation",(App::PropertyType)(App::Prop_ReadOnly),"If true, camera type will be forced to orthographic view when entering editing mode."); - ADD_PROPERTY_TYPE(SectionView,(false),"Visibility automation",(App::PropertyType)(App::Prop_ReadOnly),"If true, only objects (or part of) located behind the sketch plane are visible."); - ADD_PROPERTY_TYPE(EditingWorkbench,("SketcherWorkbench"),"Visibility automation",(App::PropertyType)(App::Prop_ReadOnly),"Name of the workbench to activate when editing this sketch."); - ADD_PROPERTY_TYPE(ShowGrid, (false), "Grid", (App::PropertyType)(App::Prop_None), "Switch the grid on/off"); - ADD_PROPERTY_TYPE(GridSize, (10.0), "Grid", (App::PropertyType)(App::Prop_None), "Gap size of the grid"); - ADD_PROPERTY_TYPE(GridStyle, (0L), "Grid", (App::PropertyType)(App::Prop_None), "Appearance style of the grid"); - ADD_PROPERTY_TYPE(GridSnap, (false), "Grid", (App::PropertyType)(App::Prop_None), "Switch the grid snap on/off"); - ADD_PROPERTY_TYPE(GridAuto, (true), "Grid", (App::PropertyType)(App::Prop_None), "Change size of grid based on view area."); - - // Default values that will be overridden by preferences (if existing) - PointSize.setValue(4); - - // visibility automation and other parameters: update parameter and property defaults to follow preferences - pObserver->initParameters(); - pObserver->subscribeToParameters(); - - GridStyle.setEnums(GridStyleEnums); - GridSize.setConstraints(&GridSizeRange); - - sPixmap = "Sketcher_Sketch"; - - //rubberband selection - rubberband = std::make_unique(); - - cameraSensor.setFunction(&ViewProviderSketch::camSensCB); -} - -ViewProviderSketch::~ViewProviderSketch() -{ -} - -void ViewProviderSketch::slotUndoDocument(const Gui::Document& /*doc*/) -{ - // Note 1: this slot is only operative during edit mode (see signal connection/disconnection) - // Note 2: ViewProviderSketch::UpdateData does not generate updates during undo/redo - // transactions as mid-transaction data may not be in a valid state (e.g. constraints - // may reference invalid geometry). However undo/redo notify SketchObject after the undo/redo - // and before this slot is called. - // Note 3: Note that recomputes are no longer inhibited during the call to this slot. - forceUpdateData(); -} - -void ViewProviderSketch::slotRedoDocument(const Gui::Document& /*doc*/) -{ - // Note 1: this slot is only operative during edit mode (see signal connection/disconnection) - // Note 2: ViewProviderSketch::UpdateData does not generate updates during undo/redo - // transactions as mid-transaction data may not be in a valid state (e.g. constraints - // may reference invalid geometry). However undo/redo notify SketchObject after the undo/redo - // and before this slot is called. - // Note 3: Note that recomputes are no longer inhibited during the call to this slot. - forceUpdateData(); -} - -void ViewProviderSketch::forceUpdateData() -{ - if(!getSketchObject()->noRecomputes) { // the sketch was already solved in SketchObject in onUndoRedoFinished - Gui::Command::updateActive(); - } -} - -/***************************** handler management ************************************/ - -void ViewProviderSketch::activateHandler(DrawSketchHandler *newHandler) -{ - assert(editCoinManager); - assert(!sketchHandler); - - sketchHandler = std::unique_ptr(newHandler); - Mode = STATUS_SKETCH_UseHandler; - sketchHandler->activate(this); - - // make sure receiver has focus so immediately pressing Escape will be handled by - // ViewProviderSketch::keyPressed() and dismiss the active handler, and not the entire - // sketcher editor - Gui::MDIView *mdi = Gui::Application::Instance->activeDocument()->getActiveView(); - mdi->setFocus(); -} - -void ViewProviderSketch::deactivateHandler() -{ - assert(isInEditMode()); - if(sketchHandler){ - sketchHandler->deactivate(); - sketchHandler = nullptr; - } - Mode = STATUS_NONE; -} - -/// removes the active handler -void ViewProviderSketch::purgeHandler() -{ - deactivateHandler(); - Gui::Selection().clearSelection(); - - // ensure that we are in sketch only selection mode - Gui::MDIView *mdi = Gui::Application::Instance->editDocument()->getActiveView(); - Gui::View3DInventorViewer *viewer; - viewer = static_cast(mdi)->getViewer(); - - SoNode* root = viewer->getSceneGraph(); - static_cast(root)->selectionRole.setValue(false); -} - -void ViewProviderSketch::setAxisPickStyle(bool on) -{ - assert(isInEditMode()); - editCoinManager->setAxisPickStyle(on); -} - -void ViewProviderSketch::moveCursorToSketchPoint(Base::Vector2d point) { - - SbVec3f sbpoint(point.x,point.y,0.f); - - Gui::MDIView *mdi = this->getActiveView(); - Gui::View3DInventor *view = qobject_cast(mdi); - - if (!view) - return; - - Gui::View3DInventorViewer* viewer = view->getViewer(); - - SbVec2s screencoords = viewer->getPointOnScreen(sbpoint); - - short x,y; screencoords.getValue(x,y); - - short height = viewer->getGLWidget()->height(); // Coin3D origin bottom left, QT origin top left - - QPoint newPos = viewer->getGLWidget()->mapToGlobal(QPoint(x,height-y)); - - - // QScreen *screen = view->windowHandle()->screen(); - //QScreen *screen = QGuiApplication::primaryScreen(); - - //QCursor::setPos(screen, newPos); - QCursor::setPos(newPos); -} - -void ViewProviderSketch::preselectAtPoint(Base::Vector2d point) -{ - if (Mode != STATUS_SELECT_Point && - Mode != STATUS_SELECT_Edge && - Mode != STATUS_SELECT_Constraint && - Mode != STATUS_SKETCH_DragPoint && - Mode != STATUS_SKETCH_DragCurve && - Mode != STATUS_SKETCH_DragConstraint && - Mode != STATUS_SKETCH_UseRubberBand) { - - SbVec3f sbpoint(point.x,point.y,0.f); - - Gui::MDIView *mdi = this->getActiveView(); - Gui::View3DInventor *view = qobject_cast(mdi); - - if (!view) - return; - - Gui::View3DInventorViewer* viewer = view->getViewer(); - - SbVec2s screencoords = viewer->getPointOnScreen(sbpoint); - - std::unique_ptr Point(this->getPointOnRay(screencoords, viewer)); - - detectAndShowPreselection(Point.get(), screencoords); - } -} - -// ********************************************************************************** - -bool ViewProviderSketch::keyPressed(bool pressed, int key) -{ - switch (key) - { - case SoKeyboardEvent::ESCAPE: - { - // make the handler quit but not the edit mode - if (isInEditMode() && sketchHandler) { - if (!pressed) - sketchHandler->quit(); - return true; - } - if (isInEditMode() && !drag.DragConstraintSet.empty()) { - if (!pressed) { - drag.DragConstraintSet.clear(); - } - return true; - } - if (isInEditMode() && drag.isDragCurveValid()) { - if (!pressed) { - getSketchObject()->movePoint(drag.DragCurve, Sketcher::PointPos::none, Base::Vector3d(0,0,0), true); - drag.DragCurve = Drag::InvalidCurve; - resetPositionText(); - Mode = STATUS_NONE; - } - return true; - } - if (isInEditMode() && drag.isDragPointValid()) { - if (!pressed) { - int GeoId; - Sketcher::PointPos PosId; - getSketchObject()->getGeoVertexIndex(drag.DragPoint, GeoId, PosId); - getSketchObject()->movePoint(GeoId, PosId, Base::Vector3d(0,0,0), true); - drag.DragPoint = Drag::InvalidPoint; - resetPositionText(); - Mode = STATUS_NONE; - } - return true; - } - if (isInEditMode()) { - // #0001479: 'Escape' key dismissing dialog cancels Sketch editing - // If we receive a button release event but not a press event before - // then ignore this one. - if (!pressed && !viewProviderParameters.buttonPress) - return true; - viewProviderParameters.buttonPress = pressed; - - // More control over Sketcher edit mode Esc key behavior - // https://forum.freecadweb.org/viewtopic.php?f=3&t=42207 - return viewProviderParameters.handleEscapeButton; - } - return false; - } - break; - default: - { - if (isInEditMode() && sketchHandler) - sketchHandler->registerPressedKey(pressed,key); - } - } - - return true; // handle all other key events -} - -void ViewProviderSketch::snapToGrid(double &x, double &y) -{ - if (GridSnap.getValue() && ShowGrid.getValue()) { - // Snap Tolerance in pixels - const double snapTol = GridSize.getValue() / 5; - - double tmpX = x, tmpY = y; - - // Find Nearest Snap points - tmpX = tmpX / GridSize.getValue(); - tmpX = tmpX < 0.0 ? ceil(tmpX - 0.5) : floor(tmpX + 0.5); - tmpX *= GridSize.getValue(); - - tmpY = tmpY / GridSize.getValue(); - tmpY = tmpY < 0.0 ? ceil(tmpY - 0.5) : floor(tmpY + 0.5); - tmpY *= GridSize.getValue(); - - // Check if x within snap tolerance - if (x < tmpX + snapTol && x > tmpX - snapTol) - x = tmpX; // Snap X Mouse Position - - // Check if y within snap tolerance - if (y < tmpY + snapTol && y > tmpY - snapTol) - y = tmpY; // Snap Y Mouse Position - } -} - -void ViewProviderSketch::getProjectingLine(const SbVec2s& pnt, const Gui::View3DInventorViewer *viewer, SbLine& line) const -{ - const SbViewportRegion& vp = viewer->getSoRenderManager()->getViewportRegion(); - - short x, y; - pnt.getValue(x,y); - SbVec2f VPsize = vp.getViewportSize(); - float dX, dY; - VPsize.getValue(dX, dY); - - float fRatio = vp.getViewportAspectRatio(); - float pX = (float)x / float(vp.getViewportSizePixels()[0]); - float pY = (float)y / float(vp.getViewportSizePixels()[1]); - - // now calculate the real points respecting aspect ratio information - // - if (fRatio > 1.0f) { - pX = (pX - 0.5f * dX) * fRatio + 0.5f * dX; - } - else if (fRatio < 1.0f) { - pY = (pY - 0.5f * dY) / fRatio + 0.5f * dY; - } - - SoCamera* pCam = viewer->getSoRenderManager()->getCamera(); - if (!pCam) - return; - SbViewVolume vol = pCam->getViewVolume(); - - vol.projectPointToLine(SbVec2f(pX, pY), line); -} - -Base::Placement ViewProviderSketch::getEditingPlacement() const { - auto doc = Gui::Application::Instance->editDocument(); - if (!doc || doc->getInEdit() != this) - return getSketchObject()->globalPlacement(); - - // TODO: won't work if there is scale. Hmm... what to do... - return Base::Placement(doc->getEditingTransform()); -} - -void ViewProviderSketch::getCoordsOnSketchPlane(const SbVec3f &point, const SbVec3f &normal, double &u, double &v) const -{ - // Plane form - Base::Vector3d R0(0, 0, 0), RN(0, 0, 1), RX(1, 0, 0), RY(0, 1, 0); - - // move to position of Sketch - Base::Placement Plz = getEditingPlacement(); - R0 = Plz.getPosition(); - Base::Rotation tmp(Plz.getRotation()); - tmp.multVec(RN, RN); - tmp.multVec(RX, RX); - tmp.multVec(RY, RY); - Plz.setRotation(tmp); - - // line - Base::Vector3d R1(point[0], point[1], point[2]), RA(normal[0], normal[1], normal[2]); - if (fabs(RN*RA) < FLT_EPSILON) - throw Base::ZeroDivisionError("View direction is parallel to sketch plane"); - // intersection point on plane - Base::Vector3d S = R1 + ((RN * (R0 - R1)) / (RN * RA)) * RA; - - // distance to x Axle of the sketch - S.TransformToCoordinateSystem(R0, RX, RY); - - u = S.x; - v = S.y; -} - -bool ViewProviderSketch::mouseButtonPressed(int Button, bool pressed, const SbVec2s &cursorPos, - const Gui::View3DInventorViewer *viewer) -{ - assert(isInEditMode()); - - // Calculate 3d point to the mouse position - SbLine line; - getProjectingLine(cursorPos, viewer, line); - SbVec3f point = line.getPosition(); - SbVec3f normal = line.getDirection(); - - // use scoped_ptr to make sure that instance gets deleted in all cases - boost::scoped_ptr pp(this->getPointOnRay(cursorPos, viewer)); - - // Radius maximum to allow double click event - const int dblClickRadius = 5; - - double x,y; - SbVec3f pos = point; - if (pp) { - const SoDetail *detail = pp->getDetail(); - if (detail && detail->getTypeId() == SoPointDetail::getClassTypeId()) { - pos = pp->getPoint(); - } - } - - try { - getCoordsOnSketchPlane(pos,normal,x,y); - snapToGrid(x, y); - } - catch (const Base::ZeroDivisionError&) { - return false; - } - - // Left Mouse button **************************************************** - if (Button == 1) { - if (pressed) { - // Do things depending on the mode of the user interaction - switch (Mode) { - case STATUS_NONE:{ - bool done=false; - if (preselection.isPreselectPointValid()) { - //Base::Console().Log("start dragging, point:%d\n",this->DragPoint); - Mode = STATUS_SELECT_Point; - done = true; - } else if (preselection.isPreselectCurveValid()) { - //Base::Console().Log("start dragging, point:%d\n",this->DragPoint); - Mode = STATUS_SELECT_Edge; - done = true; - } else if (preselection.isCrossPreselected()) { - //Base::Console().Log("start dragging, point:%d\n",this->DragPoint); - Mode = STATUS_SELECT_Cross; - done = true; - } else if (!preselection.PreselectConstraintSet.empty()) { - //Base::Console().Log("start dragging, point:%d\n",this->DragPoint); - Mode = STATUS_SELECT_Constraint; - done = true; - } - - // Double click events variables - float dci = (float) QApplication::doubleClickInterval()/1000.0f; - - if (done && - SbVec2f(cursorPos - DoubleClick::prvClickPos).length() < dblClickRadius && - (SbTime::getTimeOfDay() - DoubleClick::prvClickTime).getValue() < dci) { - - // Double Click Event Occurred - editDoubleClicked(); - // Reset Double Click Static Variables - DoubleClick::prvClickTime = SbTime(); - DoubleClick::prvClickPos = SbVec2s(-16000,-16000); //certainly far away from any clickable place, to avoid re-trigger of double-click if next click happens fast. - - Mode = STATUS_NONE; - } else { - DoubleClick::prvClickTime = SbTime::getTimeOfDay(); - DoubleClick::prvClickPos = cursorPos; - DoubleClick::prvCursorPos = cursorPos; - DoubleClick::newCursorPos = cursorPos; - if (!done) - Mode = STATUS_SKETCH_StartRubberBand; - } - - return done; - } - case STATUS_SKETCH_UseHandler: - return sketchHandler->pressButton(Base::Vector2d(x,y)); - default: - return false; - } - } else { // Button 1 released - // Do things depending on the mode of the user interaction - switch (Mode) { - case STATUS_SELECT_Point: - if (pp) { - //Base::Console().Log("Select Point:%d\n",this->DragPoint); - // Do selection - std::stringstream ss; - ss << "Vertex" << preselection.getPreselectionVertexIndex(); - - if (isSelected(ss.str())) { - rmvSelection(ss.str()); - } else { - addSelection2(ss.str(), pp->getPoint()[0], pp->getPoint()[1],pp->getPoint()[2]); - drag.resetIds(); - } - } - Mode = STATUS_NONE; - return true; - case STATUS_SELECT_Edge: - if (pp) { - //Base::Console().Log("Select Point:%d\n",this->DragPoint); - std::stringstream ss; - if (preselection.isEdge()) - ss << "Edge" << preselection.getPreselectionEdgeIndex(); - else // external geometry - ss << "ExternalEdge" << preselection.getPreselectionExternalEdgeIndex(); - - // If edge already selected move from selection - if (isSelected(ss.str()) ) { - rmvSelection(ss.str()); - } else { - // Add edge to the selection - addSelection2(ss.str(), pp->getPoint()[0], pp->getPoint()[1], pp->getPoint()[2]); - drag.resetIds(); - } - } - Mode = STATUS_NONE; - return true; - case STATUS_SELECT_Cross: - if (pp) { - //Base::Console().Log("Select Point:%d\n",this->DragPoint); - std::stringstream ss; - switch(preselection.PreselectCross){ - case Preselection::Axes::RootPoint: ss << "RootPoint" ; break; - case Preselection::Axes::HorizontalAxis: ss << "H_Axis" ; break; - case Preselection::Axes::VerticalAxis: ss << "V_Axis" ; break; - default: break; - } - - // If cross already selected move from selection - if (isSelected(ss.str()) ) { - rmvSelection(ss.str()); - } else { - // Add cross to the selection - addSelection2(ss.str(), pp->getPoint()[0], pp->getPoint()[1], pp->getPoint()[2]); - drag.resetIds(); - } - } - Mode = STATUS_NONE; - return true; - case STATUS_SELECT_Constraint: - if (pp) { - auto sels = preselection.PreselectConstraintSet; - for(int id : sels) { - std::stringstream ss; - ss << Sketcher::PropertyConstraintList::getConstraintName(id); - - // If the constraint already selected remove - if (isSelected(ss.str()) ) { - rmvSelection(ss.str()); - } else { - // Add constraint to current selection - addSelection2(ss.str(), pp->getPoint()[0], pp->getPoint()[1], pp->getPoint()[2]); - drag.resetIds(); - } - } - } - Mode = STATUS_NONE; - return true; - case STATUS_SKETCH_DragPoint: - if (drag.isDragPointValid()) { - int GeoId; - Sketcher::PointPos PosId; - - getSketchObject()->getGeoVertexIndex(drag.DragPoint, GeoId, PosId); - - if (GeoId != Sketcher::GeoEnum::GeoUndef && PosId != Sketcher::PointPos::none) { - getDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Drag Point")); - try { - Gui::cmdAppObjectArgs(getObject(), "movePoint(%i,%i,App.Vector(%f,%f,0),%i)" - ,GeoId, static_cast(PosId), x-drag.xInit, y-drag.yInit, 0); - - getDocument()->commitCommand(); - - tryAutoRecomputeIfNotSolve(getSketchObject()); - } - catch (const Base::Exception& e) { - getDocument()->abortCommand(); - Base::Console().Error("Drag point: %s\n", e.what()); - } - } - setPreselectPoint(drag.DragPoint); - drag.DragPoint = Drag::InvalidPoint; - //updateColor(); - } - resetPositionText(); - Mode = STATUS_NONE; - return true; - case STATUS_SKETCH_DragCurve: - if (drag.isDragCurveValid()) { - const Part::Geometry *geo = getSketchObject()->getGeometry(drag.DragCurve); - if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId() || - geo->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() || - geo->getTypeId() == Part::GeomCircle::getClassTypeId() || - geo->getTypeId() == Part::GeomEllipse::getClassTypeId()|| - geo->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()|| - geo->getTypeId() == Part::GeomArcOfParabola::getClassTypeId()|| - geo->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId()|| - geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { - getDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Drag Curve")); - - auto geo = getSketchObject()->getGeometry(drag.DragCurve); - auto gf = GeometryFacade::getFacade(geo); - - Base::Vector3d vec(x-drag.xInit,y-drag.yInit,0); - - // BSpline weights have a radius corresponding to the weight value - // However, in order for them proportional to the B-Spline size, - // the scenograph has a size scalefactor times the weight - // This code normalizes the information sent to the solver. - if(gf->getInternalType() == InternalType::BSplineControlPoint) { - auto circle = static_cast(geo); - Base::Vector3d center = circle->getCenter(); - - Base::Vector3d dir = vec - center; - - double scalefactor = 1.0; - - if(circle->hasExtension(SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId())) - { - auto vpext = std::static_pointer_cast( - circle->getExtension(SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId()).lock()); - - scalefactor = vpext->getRepresentationFactor(); - } - - vec = center + dir / scalefactor; - } - - try { - Gui::cmdAppObjectArgs(getObject(), "movePoint(%i,%i,App.Vector(%f,%f,0),%i)" - ,drag.DragCurve, static_cast(Sketcher::PointPos::none), vec.x, vec.y, drag.relative ? 1 : 0); - - getDocument()->commitCommand(); - - tryAutoRecomputeIfNotSolve(getSketchObject()); - } - catch (const Base::Exception& e) { - getDocument()->abortCommand(); - Base::Console().Error("Drag curve: %s\n", e.what()); - } - } - preselection.PreselectCurve = drag.DragCurve; - drag.DragCurve = Drag::InvalidCurve; - //updateColor(); - } - resetPositionText(); - Mode = STATUS_NONE; - return true; - case STATUS_SKETCH_DragConstraint: - if (!drag.DragConstraintSet.empty()) { - getDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Drag Constraint")); - auto idset = drag.DragConstraintSet; - for(int id : idset) { - moveConstraint(id, Base::Vector2d(x, y)); - //updateColor(); - } - preselection.PreselectConstraintSet = drag.DragConstraintSet; - drag.DragConstraintSet.clear(); - getDocument()->commitCommand(); - } - Mode = STATUS_NONE; - return true; - case STATUS_SKETCH_StartRubberBand: // a single click happened, so clear selection unless user hold control. - if (!(QApplication::keyboardModifiers() & Qt::ControlModifier)) { - Gui::Selection().clearSelection(); - } - Mode = STATUS_NONE; - return true; - case STATUS_SKETCH_UseRubberBand: - doBoxSelection(DoubleClick::prvCursorPos, cursorPos, viewer); - rubberband->setWorking(false); - - // a redraw is required in order to clear the rubberband - draw(true,false); - const_cast(viewer)->redraw(); - Mode = STATUS_NONE; - return true; - case STATUS_SKETCH_UseHandler: { - return sketchHandler->releaseButton(Base::Vector2d(x,y)); - } - case STATUS_NONE: - default: - return false; - } - } - } - // Right mouse button **************************************************** - else if (Button == 2) { - if (!pressed) { - switch (Mode) { - case STATUS_SKETCH_UseHandler: - // make the handler quit - sketchHandler->quit(); - return true; - case STATUS_NONE: - { - // A right click shouldn't change the Edit Mode - if (preselection.isPreselectPointValid()) { - return true; - } else if (preselection.isPreselectCurveValid()) { - return true; - } else if (!preselection.PreselectConstraintSet.empty()) { - return true; - } else { - Gui::MenuItem geom; - geom.setCommand("Sketcher geoms"); - geom << "Sketcher_CreatePoint" - << "Sketcher_CreateArc" - << "Sketcher_Create3PointArc" - << "Sketcher_CreateCircle" - << "Sketcher_Create3PointCircle" - << "Sketcher_CreateLine" - << "Sketcher_CreatePolyline" - << "Sketcher_CreateRectangle" - << "Sketcher_CreateHexagon" - << "Sketcher_CreateFillet" - << "Sketcher_CreatePointFillet" - << "Sketcher_Trimming" - << "Sketcher_Extend" - << "Sketcher_External" - << "Sketcher_ToggleConstruction" - /*<< "Sketcher_CreateText"*/ - /*<< "Sketcher_CreateDraftLine"*/ - << "Separator"; - - Gui::Application::Instance->setupContextMenu("View", &geom); - //Create the Context Menu using the Main View Qt Widget - QMenu contextMenu(viewer->getGLWidget()); - Gui::MenuManager::getInstance()->setupContextMenu(&geom, contextMenu); - contextMenu.exec(QCursor::pos()); - - return true; - } - } - case STATUS_SELECT_Point: - break; - case STATUS_SELECT_Edge: - { - Gui::MenuItem geom; - geom.setCommand("Sketcher constraints"); - geom << "Sketcher_ConstrainVertical" - << "Sketcher_ConstrainHorizontal"; - - // Gets a selection vector - std::vector selection = Gui::Selection().getSelectionEx(); - - bool rightClickOnSelectedLine = false; - - /* - * Add Multiple Line Constraints to the menu - */ - // only one sketch with its subelements are allowed to be selected - if (selection.size() == 1) { - // get the needed lists and objects - const std::vector &SubNames = selection[0].getSubNames(); - - // Two Objects are selected - if (SubNames.size() == 2) { - // go through the selected subelements - for (std::vector::const_iterator it=SubNames.begin(); - it!=SubNames.end();++it) { - - // If the object selected is of type edge - if (it->size() > 4 && it->substr(0,4) == "Edge") { - // Get the index of the object selected - int GeoId = std::atoi(it->substr(4,4000).c_str()) - 1; - if (preselection.PreselectCurve == GeoId) - rightClickOnSelectedLine = true; - } else { - // The selection is not exclusively edges - rightClickOnSelectedLine = false; - } - } // End of Iteration - } - } - - if (rightClickOnSelectedLine) { - geom << "Sketcher_ConstrainParallel" - << "Sketcher_ConstrainPerpendicular"; - } - - Gui::Application::Instance->setupContextMenu("View", &geom); - //Create the Context Menu using the Main View Qt Widget - QMenu contextMenu(viewer->getGLWidget()); - Gui::MenuManager::getInstance()->setupContextMenu(&geom, contextMenu); - contextMenu.exec(QCursor::pos()); - - return true; - } - case STATUS_SELECT_Cross: - case STATUS_SELECT_Constraint: - case STATUS_SKETCH_DragPoint: - case STATUS_SKETCH_DragCurve: - case STATUS_SKETCH_DragConstraint: - case STATUS_SKETCH_StartRubberBand: - case STATUS_SKETCH_UseRubberBand: - break; - } - } - } - - return false; -} - -bool ViewProviderSketch::mouseWheelEvent(int delta, const SbVec2s &cursorPos, const Gui::View3DInventorViewer* viewer) -{ - assert(isInEditMode()); - - Q_UNUSED(delta); - Q_UNUSED(cursorPos); - Q_UNUSED(viewer); - - editCoinManager->drawConstraintIcons(); - - return true; -} - -void ViewProviderSketch::editDoubleClicked() -{ - if (preselection.isPreselectPointValid()) { - Base::Console().Log("double click point:%d\n",preselection.PreselectPoint); - } - else if (preselection.isPreselectCurveValid()) { - Base::Console().Log("double click edge:%d\n",preselection.PreselectCurve); - } - else if (preselection.isCrossPreselected()) { - Base::Console().Log("double click cross:%d\n",preselection.PreselectCross); - } - else if (!preselection.PreselectConstraintSet.empty()) { - // Find the constraint - const std::vector &constrlist = getSketchObject()->Constraints.getValues(); - - auto sels = preselection.PreselectConstraintSet; - for(int id : sels) { - - Constraint *Constr = constrlist[id]; - - // if its the right constraint - if (Constr->isDimensional()) { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Modify sketch constraints")); - EditDatumDialog editDatumDialog(this, id); - editDatumDialog.exec(); - } - } - } -} - -bool ViewProviderSketch::mouseMove(const SbVec2s &cursorPos, Gui::View3DInventorViewer *viewer) -{ - // maximum radius for mouse moves when selecting a geometry before switching to drag mode - const int dragIgnoredDistance = 3; - - static bool selectableConstraints = true; - - if (Mode < STATUS_SKETCH_UseHandler) { - bool tmpSelCons = QApplication::keyboardModifiers() & Qt::ShiftModifier; - if (tmpSelCons != !selectableConstraints) { - selectableConstraints = !tmpSelCons; - editCoinManager->setConstraintSelectability(selectableConstraints); - } - } - - if (!isInEditMode()) - return false; - - // ignore small moves after selection - switch (Mode) { - case STATUS_SELECT_Point: - case STATUS_SELECT_Edge: - case STATUS_SELECT_Constraint: - case STATUS_SKETCH_StartRubberBand: - short dx, dy; - (cursorPos - DoubleClick::prvCursorPos).getValue(dx, dy); - if(std::abs(dx) < dragIgnoredDistance && std::abs(dy) < dragIgnoredDistance) - return false; - default: - break; - } - - // Calculate 3d point to the mouse position - SbLine line; - getProjectingLine(cursorPos, viewer, line); - - double x,y; - try { - getCoordsOnSketchPlane(line.getPosition(),line.getDirection(),x,y); - snapToGrid(x, y); - } - catch (const Base::ZeroDivisionError&) { - return false; - } - - bool preselectChanged = false; - if (Mode != STATUS_SELECT_Point && - Mode != STATUS_SELECT_Edge && - Mode != STATUS_SELECT_Constraint && - Mode != STATUS_SKETCH_DragPoint && - Mode != STATUS_SKETCH_DragCurve && - Mode != STATUS_SKETCH_DragConstraint && - Mode != STATUS_SKETCH_UseRubberBand) { - - std::unique_ptr Point(this->getPointOnRay(cursorPos, viewer)); - - preselectChanged = detectAndShowPreselection(Point.get(), cursorPos); - } - - switch (Mode) { - case STATUS_NONE: - if (preselectChanged) { - editCoinManager->drawConstraintIcons(); - this->updateColor(); - return true; - } - return false; - case STATUS_SELECT_Point: - if (!getSolvedSketch().hasConflicts() && - preselection.isPreselectPointValid() && drag.DragPoint != preselection.PreselectPoint) { - Mode = STATUS_SKETCH_DragPoint; - drag.DragPoint = preselection.PreselectPoint; - int GeoId; - Sketcher::PointPos PosId; - - getSketchObject()->getGeoVertexIndex(drag.DragPoint, GeoId, PosId); - - if (GeoId != Sketcher::GeoEnum::GeoUndef && PosId != Sketcher::PointPos::none) { - getSketchObject()->initTemporaryMove(GeoId, PosId, false); - drag.resetVector(); - } - } else { - Mode = STATUS_NONE; - } - resetPreselectPoint(); - return true; - case STATUS_SELECT_Edge: - if (!getSolvedSketch().hasConflicts() && - preselection.isPreselectCurveValid() && drag.DragCurve != preselection.PreselectCurve) { - Mode = STATUS_SKETCH_DragCurve; - drag.DragCurve = preselection.PreselectCurve; - const Part::Geometry *geo = getSketchObject()->getGeometry(drag.DragCurve); - - // BSpline Control points are edge draggable only if their radius is movable - // This is because dragging gives unwanted cosmetic results due to the scale ratio. - // This is an heuristic as it does not check all indirect routes. - if(GeometryFacade::isInternalType(geo, InternalType::BSplineControlPoint)) { - if(geo->hasExtension(Sketcher::SolverGeometryExtension::getClassTypeId())) { - auto solvext = std::static_pointer_cast( - geo->getExtension(Sketcher::SolverGeometryExtension::getClassTypeId()).lock()); - - // Edge parameters are Independent, so weight won't move - if(solvext->getEdge()==Sketcher::SolverGeometryExtension::Independent) { - Mode = STATUS_NONE; - return false; - } - - // The B-Spline is constrained to be non-rational (equal weights), moving produces a bad effect - // because OCCT will normalize the values of the weights. - auto grp = getSolvedSketch().getDependencyGroup(drag.DragCurve, Sketcher::PointPos::none); - - int bsplinegeoid = -1; - - std::vector polegeoids; - - for( auto c : getSketchObject()->Constraints.getValues()) { - if( c->Type == Sketcher::InternalAlignment && - c->AlignmentType == BSplineControlPoint && - c->First == drag.DragCurve ) { - - bsplinegeoid = c->Second; - break; - } - } - - if(bsplinegeoid == -1) { - Mode = STATUS_NONE; - return false; - } - - for( auto c : getSketchObject()->Constraints.getValues()) { - if( c->Type == Sketcher::InternalAlignment && - c->AlignmentType == BSplineControlPoint && - c->Second == bsplinegeoid ) { - - polegeoids.push_back(c->First); - } - } - - bool allingroup = true; - - for( auto polegeoid : polegeoids ) { - std::pair< int, Sketcher::PointPos > thispole = std::make_pair(polegeoid,Sketcher::PointPos::none); - - if(grp.find(thispole) == grp.end()) // not found - allingroup = false; - } - - if(allingroup) { // it is constrained to be non-rational - Mode = STATUS_NONE; - return false; - } - - } - - } - - if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId() || - geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { - drag.relative = true; - - // Since the cursor moved from where it was clicked, and this is a relative move, - // calculate the click position and use it as initial point. - SbLine line2; - getProjectingLine(DoubleClick::prvCursorPos, viewer, line2); - getCoordsOnSketchPlane(line2.getPosition(),line2.getDirection(),drag.xInit,drag.yInit); - snapToGrid(drag.xInit, drag.yInit); - } else { - drag.resetVector(); - } - - if (geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { - getSketchObject()->initTemporaryBSplinePieceMove( - drag.DragCurve, Sketcher::PointPos::none, - Base::Vector3d(drag.xInit, drag.yInit, 0.0), false); - } else { - getSketchObject()->initTemporaryMove(drag.DragCurve, Sketcher::PointPos::none, false); - } - } else { - Mode = STATUS_NONE; - } - resetPreselectPoint(); - return true; - case STATUS_SELECT_Constraint: - Mode = STATUS_SKETCH_DragConstraint; - drag.DragConstraintSet = preselection.PreselectConstraintSet; - resetPreselectPoint(); - return true; - case STATUS_SKETCH_DragPoint: - if (drag.isDragPointValid()) { - //Base::Console().Log("Drag Point:%d\n",edit->DragPoint); - int GeoId; - Sketcher::PointPos PosId; - getSketchObject()->getGeoVertexIndex(drag.DragPoint, GeoId, PosId); - Base::Vector3d vec(x,y,0); - - if (GeoId != Sketcher::GeoEnum::GeoUndef && PosId != Sketcher::PointPos::none) { - if (getSketchObject()->moveTemporaryPoint(GeoId, PosId, vec, false) == 0) { - setPositionText(Base::Vector2d(x,y)); - draw(true,false); - } - } - } - return true; - case STATUS_SKETCH_DragCurve: - if (drag.isDragCurveValid()) { - auto geo = getSketchObject()->getGeometry(drag.DragCurve); - auto gf = GeometryFacade::getFacade(geo); - - Base::Vector3d vec(x-drag.xInit,y-drag.yInit,0); - - // BSpline weights have a radius corresponding to the weight value - // However, in order for them proportional to the B-Spline size, - // the scenograph has a size scalefactor times the weight - // This code normalizes the information sent to the solver. - if(gf->getInternalType() == InternalType::BSplineControlPoint) { - auto circle = static_cast(geo); - Base::Vector3d center = circle->getCenter(); - - Base::Vector3d dir = vec - center; - - double scalefactor = 1.0; - - if(circle->hasExtension(SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId())) - { - auto vpext = std::static_pointer_cast( - circle->getExtension(SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId()).lock()); - - scalefactor = vpext->getRepresentationFactor(); - } - - vec = center + dir / scalefactor; - } - - if (getSketchObject()->moveTemporaryPoint(drag.DragCurve, Sketcher::PointPos::none, vec, drag.relative) == 0) { - setPositionText(Base::Vector2d(x,y)); - draw(true,false); - } - } - return true; - case STATUS_SKETCH_DragConstraint: - if (!drag.DragConstraintSet.empty()) { - auto idset = drag.DragConstraintSet; - for(int id : idset) - moveConstraint(id, Base::Vector2d(x,y)); - } - return true; - case STATUS_SKETCH_UseHandler: - sketchHandler->mouseMove(Base::Vector2d(x,y)); - if (preselectChanged) { - editCoinManager->drawConstraintIcons(); - this->updateColor(); - } - return true; - case STATUS_SKETCH_StartRubberBand: { - Mode = STATUS_SKETCH_UseRubberBand; - rubberband->setWorking(true); - return true; - } - case STATUS_SKETCH_UseRubberBand: { - // Here we must use the device-pixel-ratio to compute the correct y coordinate (#0003130) - qreal dpr = viewer->getGLWidget()->devicePixelRatioF(); - DoubleClick::newCursorPos = cursorPos; - rubberband->setCoords(DoubleClick::prvCursorPos.getValue()[0], - viewer->getGLWidget()->height()*dpr - DoubleClick::prvCursorPos.getValue()[1], - DoubleClick::newCursorPos.getValue()[0], - viewer->getGLWidget()->height()*dpr - DoubleClick::newCursorPos.getValue()[1]); - viewer->redraw(); - return true; - } - default: - return false; - } - - return false; -} - -void ViewProviderSketch::moveConstraint(int constNum, const Base::Vector2d &toPos) -{ - // are we in edit? - if (!isInEditMode()) - return; - - const std::vector &constrlist = getSketchObject()->Constraints.getValues(); - Constraint *Constr = constrlist[constNum]; - -#ifdef FC_DEBUG - int intGeoCount = getSketchObject()->getHighestCurveIndex() + 1; - int extGeoCount = getSketchObject()->getExternalGeometryCount(); -#endif - - // with memory allocation - const std::vector geomlist = getSolvedSketch().extractGeometry(true, true); - -#ifdef FC_DEBUG - assert(int(geomlist.size()) == extGeoCount + intGeoCount); - assert((Constr->First >= -extGeoCount && Constr->First < intGeoCount) - || Constr->First != GeoEnum::GeoUndef); -#endif - - if (Constr->Type == Distance || Constr->Type == DistanceX || Constr->Type == DistanceY || - Constr->Type == Radius || Constr->Type == Diameter || Constr-> Type == Weight) { - - Base::Vector3d p1(0.,0.,0.), p2(0.,0.,0.); - if (Constr->SecondPos != Sketcher::PointPos::none) { // point to point distance - p1 = getSolvedSketch().getPoint(Constr->First, Constr->FirstPos); - p2 = getSolvedSketch().getPoint(Constr->Second, Constr->SecondPos); - } else if (Constr->Second != GeoEnum::GeoUndef) { // point to line distance - p1 = getSolvedSketch().getPoint(Constr->First, Constr->FirstPos); - const Part::Geometry *geo = GeoList::getGeometryFromGeoId (geomlist, Constr->Second); - if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { - const Part::GeomLineSegment *lineSeg = static_cast(geo); - Base::Vector3d l2p1 = lineSeg->getStartPoint(); - Base::Vector3d l2p2 = lineSeg->getEndPoint(); - // calculate the projection of p1 onto line2 - p2.ProjectToLine(p1-l2p1, l2p2-l2p1); - p2 += p1; - } else - return; - } else if (Constr->FirstPos != Sketcher::PointPos::none) { - p2 = getSolvedSketch().getPoint(Constr->First, Constr->FirstPos); - } else if (Constr->First != GeoEnum::GeoUndef) { - const Part::Geometry *geo = GeoList::getGeometryFromGeoId (geomlist, Constr->First); - if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { - const Part::GeomLineSegment *lineSeg = static_cast(geo); - p1 = lineSeg->getStartPoint(); - p2 = lineSeg->getEndPoint(); - } else if (geo->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { - const Part::GeomArcOfCircle *arc = static_cast(geo); - double radius = arc->getRadius(); - Base::Vector3d center = arc->getCenter(); - p1 = center; - - double angle = Constr->LabelPosition; - if (angle == 10) { - double startangle, endangle; - arc->getRange(startangle, endangle, /*emulateCCW=*/true); - angle = (startangle + endangle)/2; - } - else { - Base::Vector3d tmpDir = Base::Vector3d(toPos.x, toPos.y, 0) - p1; - angle = atan2(tmpDir.y, tmpDir.x); - } - - if(Constr->Type == Sketcher::Diameter) - p1 = center - radius * Base::Vector3d(cos(angle),sin(angle),0.); - - p2 = center + radius * Base::Vector3d(cos(angle),sin(angle),0.); - } - else if (geo->getTypeId() == Part::GeomCircle::getClassTypeId()) { - const Part::GeomCircle *circle = static_cast(geo); - double radius = circle->getRadius(); - Base::Vector3d center = circle->getCenter(); - p1 = center; - - Base::Vector3d tmpDir = Base::Vector3d(toPos.x, toPos.y, 0) - p1; - - Base::Vector3d dir = radius * tmpDir.Normalize(); - - if(Constr->Type == Sketcher::Diameter) - p1 = center - dir; - - if(Constr->Type == Sketcher::Weight) { - - double scalefactor = 1.0; - - if(circle->hasExtension(SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId())) - { - auto vpext = std::static_pointer_cast( - circle->getExtension(SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId()).lock()); - - scalefactor = vpext->getRepresentationFactor(); - } - - p2 = center + dir * scalefactor; - - } - else - p2 = center + dir; - } - else - return; - } else - return; - - Base::Vector3d vec = Base::Vector3d(toPos.x, toPos.y, 0) - p2; - - Base::Vector3d dir; - if (Constr->Type == Distance || Constr->Type == Radius || Constr->Type == Diameter || Constr->Type == Weight) - dir = (p2-p1).Normalize(); - else if (Constr->Type == DistanceX) - dir = Base::Vector3d( (p2.x - p1.x >= FLT_EPSILON) ? 1 : -1, 0, 0); - else if (Constr->Type == DistanceY) - dir = Base::Vector3d(0, (p2.y - p1.y >= FLT_EPSILON) ? 1 : -1, 0); - - if (Constr->Type == Radius || Constr->Type == Diameter || Constr->Type == Weight) { - Constr->LabelDistance = vec.x * dir.x + vec.y * dir.y; - Constr->LabelPosition = atan2(dir.y, dir.x); - } else { - Base::Vector3d normal(-dir.y,dir.x,0); - Constr->LabelDistance = vec.x * normal.x + vec.y * normal.y; - if (Constr->Type == Distance || - Constr->Type == DistanceX || Constr->Type == DistanceY) { - vec = Base::Vector3d(toPos.x, toPos.y, 0) - (p2 + p1) / 2; - Constr->LabelPosition = vec.x * dir.x + vec.y * dir.y; - } - } - } - else if (Constr->Type == Angle) { - - Base::Vector3d p0(0.,0.,0.); - double factor = 0.5; - if (Constr->Second != GeoEnum::GeoUndef) { // line to line angle - Base::Vector3d dir1, dir2; - - if(Constr->Third == GeoEnum::GeoUndef) { //angle between two lines - const Part::Geometry *geo1 = GeoList::getGeometryFromGeoId (geomlist, Constr->First); - const Part::Geometry *geo2 = GeoList::getGeometryFromGeoId (geomlist, Constr->Second); - - if (geo1->getTypeId() != Part::GeomLineSegment::getClassTypeId() || - geo2->getTypeId() != Part::GeomLineSegment::getClassTypeId()) - return; - const Part::GeomLineSegment *lineSeg1 = static_cast(geo1); - const Part::GeomLineSegment *lineSeg2 = static_cast(geo2); - - bool flip1 = (Constr->FirstPos == Sketcher::PointPos::end); - bool flip2 = (Constr->SecondPos == Sketcher::PointPos::end); - - dir1 = (flip1 ? -1. : 1.) * (lineSeg1->getEndPoint()-lineSeg1->getStartPoint()); - dir2 = (flip2 ? -1. : 1.) * (lineSeg2->getEndPoint()-lineSeg2->getStartPoint()); - Base::Vector3d pnt1 = flip1 ? lineSeg1->getEndPoint() : lineSeg1->getStartPoint(); - Base::Vector3d pnt2 = flip2 ? lineSeg2->getEndPoint() : lineSeg2->getStartPoint(); - - // line-line intersection - { - double det = dir1.x*dir2.y - dir1.y*dir2.x; - if ((det > 0 ? det : -det) < 1e-10) - return;// lines are parallel - constraint unmoveable (DeepSOIC: why?..) - double c1 = dir1.y*pnt1.x - dir1.x*pnt1.y; - double c2 = dir2.y*pnt2.x - dir2.x*pnt2.y; - double x = (dir1.x*c2 - dir2.x*c1)/det; - double y = (dir1.y*c2 - dir2.y*c1)/det; - // intersection point - p0 = Base::Vector3d(x,y,0); - - Base::Vector3d vec = Base::Vector3d(toPos.x, toPos.y, 0) - p0; - factor = factor * Base::sgn((dir1+dir2) * vec); - } - } else {//angle-via-point - Base::Vector3d p = getSolvedSketch().getPoint(Constr->Third, Constr->ThirdPos); - p0 = Base::Vector3d(p.x, p.y, 0); - dir1 = getSolvedSketch().calculateNormalAtPoint(Constr->First, p.x, p.y); - dir1.RotateZ(-M_PI/2);//convert to vector of tangency by rotating - dir2 = getSolvedSketch().calculateNormalAtPoint(Constr->Second, p.x, p.y); - dir2.RotateZ(-M_PI/2); - - Base::Vector3d vec = Base::Vector3d(toPos.x, toPos.y, 0) - p0; - factor = factor * Base::sgn((dir1+dir2) * vec); - } - - } else if (Constr->First != GeoEnum::GeoUndef) { // line/arc angle - const Part::Geometry *geo = GeoList::getGeometryFromGeoId (geomlist, Constr->First); - - if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { - const Part::GeomLineSegment *lineSeg = static_cast(geo); - p0 = (lineSeg->getEndPoint()+lineSeg->getStartPoint())/2; - } - else if (geo->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { - const Part::GeomArcOfCircle *arc = static_cast(geo); - p0 = arc->getCenter(); - } - else { - return; - } - } else - return; - - Base::Vector3d vec = Base::Vector3d(toPos.x, toPos.y, 0) - p0; - Constr->LabelDistance = factor * vec.Length(); - } - - // delete the cloned objects - for (std::vector::const_iterator it=geomlist.begin(); it != geomlist.end(); ++it) - if (*it) delete *it; - - draw(true,false); -} - -bool ViewProviderSketch::isSelectable() const -{ - if (isEditing()) - return false; - else - return PartGui::ViewProvider2DObject::isSelectable(); -} - -void ViewProviderSketch::onSelectionChanged(const Gui::SelectionChanges& msg) -{ - // are we in edit? - if (isInEditMode()) { - // ignore external object - if(!msg.Object.getObjectName().empty() && msg.Object.getDocument()!=getObject()->getDocument()) - return; - - bool handled=false; - if (Mode == STATUS_SKETCH_UseHandler) { - App::AutoTransaction committer; - handled = sketchHandler->onSelectionChanged(msg); - } - if (handled) - return; - - std::string temp; - if (msg.Type == Gui::SelectionChanges::ClrSelection) { - // if something selected in this object? - if (!selection.SelPointSet.empty() || !selection.SelCurvSet.empty() || !selection.SelConstraintSet.empty()) { - // clear our selection and update the color of the viewed edges and points - clearSelectPoints(); - selection.SelCurvSet.clear(); - selection.SelConstraintSet.clear(); - editCoinManager->drawConstraintIcons(); - this->updateColor(); - } - } - else if (msg.Type == Gui::SelectionChanges::AddSelection) { - // is it this object?? - if (strcmp(msg.pDocName,getSketchObject()->getDocument()->getName())==0 - && strcmp(msg.pObjectName,getSketchObject()->getNameInDocument())== 0) { - if (msg.pSubName) { - std::string shapetype(msg.pSubName); - if (shapetype.size() > 4 && shapetype.substr(0,4) == "Edge") { - int GeoId = std::atoi(&shapetype[4]) - 1; - selection.SelCurvSet.insert(GeoId); - this->updateColor(); - } - else if (shapetype.size() > 12 && shapetype.substr(0,12) == "ExternalEdge") { - int GeoId = std::atoi(&shapetype[12]) - 1; - GeoId = -GeoId - 3; - selection.SelCurvSet.insert(GeoId); - this->updateColor(); - } - else if (shapetype.size() > 6 && shapetype.substr(0,6) == "Vertex") { - int VtId = std::atoi(&shapetype[6]) - 1; - addSelectPoint(VtId); - this->updateColor(); - } - else if (shapetype == "RootPoint") { - addSelectPoint(Selection::RootPoint); - this->updateColor(); - } - else if (shapetype == "H_Axis") { - selection.SelCurvSet.insert(Selection::HorizontalAxis); - this->updateColor(); - } - else if (shapetype == "V_Axis") { - selection.SelCurvSet.insert(Selection::VerticalAxis); - this->updateColor(); - } - else if (shapetype.size() > 10 && shapetype.substr(0,10) == "Constraint") { - int ConstrId = Sketcher::PropertyConstraintList::getIndexFromConstraintName(shapetype); - selection.SelConstraintSet.insert(ConstrId); - editCoinManager->drawConstraintIcons(); - this->updateColor(); - } - } - } - } - else if (msg.Type == Gui::SelectionChanges::RmvSelection) { - // Are there any objects selected - if (!selection.SelPointSet.empty() || !selection.SelCurvSet.empty() || !selection.SelConstraintSet.empty()) { - // is it this object?? - if (strcmp(msg.pDocName,getSketchObject()->getDocument()->getName())==0 - && strcmp(msg.pObjectName,getSketchObject()->getNameInDocument())== 0) { - if (msg.pSubName) { - std::string shapetype(msg.pSubName); - if (shapetype.size() > 4 && shapetype.substr(0,4) == "Edge") { - int GeoId = std::atoi(&shapetype[4]) - 1; - selection.SelCurvSet.erase(GeoId); - this->updateColor(); - } - else if (shapetype.size() > 12 && shapetype.substr(0,12) == "ExternalEdge") { - int GeoId = std::atoi(&shapetype[12]) - 1; - GeoId = -GeoId - 3; - selection.SelCurvSet.erase(GeoId); - this->updateColor(); - } - else if (shapetype.size() > 6 && shapetype.substr(0,6) == "Vertex") { - int VtId = std::atoi(&shapetype[6]) - 1; - removeSelectPoint(VtId); - this->updateColor(); - } - else if (shapetype == "RootPoint") { - removeSelectPoint(Sketcher::GeoEnum::RtPnt); - this->updateColor(); - } - else if (shapetype == "H_Axis") { - selection.SelCurvSet.erase(Sketcher::GeoEnum::HAxis); - this->updateColor(); - } - else if (shapetype == "V_Axis") { - selection.SelCurvSet.erase(Sketcher::GeoEnum::VAxis); - this->updateColor(); - } - else if (shapetype.size() > 10 && shapetype.substr(0,10) == "Constraint") { - int ConstrId = Sketcher::PropertyConstraintList::getIndexFromConstraintName(shapetype); - selection.SelConstraintSet.erase(ConstrId); - editCoinManager->drawConstraintIcons(); - this->updateColor(); - } - } - } - } - } - else if (msg.Type == Gui::SelectionChanges::SetSelection) { - // remove all items - //selectionView->clear(); - //std::vector objs = Gui::Selection().getSelection(Reason.pDocName); - //for (std::vector::iterator it = objs.begin(); it != objs.end(); ++it) { - // // build name - // temp = it->DocName; - // temp += "."; - // temp += it->FeatName; - // if (it->SubName && it->SubName[0] != '\0') { - // temp += "."; - // temp += it->SubName; - // } - // new QListWidgetItem(QString::fromLatin1(temp.c_str()), selectionView); - //} - } - else if (msg.Type == Gui::SelectionChanges::SetPreselect) { - if (strcmp(msg.pDocName,getSketchObject()->getDocument()->getName())==0 - && strcmp(msg.pObjectName,getSketchObject()->getNameInDocument())== 0) { - if (msg.pSubName) { - std::string shapetype(msg.pSubName); - if (shapetype.size() > 4 && shapetype.substr(0,4) == "Edge") { - int GeoId = std::atoi(&shapetype[4]) - 1; - resetPreselectPoint(); - preselection.PreselectCurve = GeoId; - - if (sketchHandler) - sketchHandler->applyCursor(); - this->updateColor(); - } - else if (shapetype.size() > 6 && shapetype.substr(0,6) == "Vertex") { - int PtIndex = std::atoi(&shapetype[6]) - 1; - setPreselectPoint(PtIndex); - - if (sketchHandler) - sketchHandler->applyCursor(); - this->updateColor(); - } - } - } - } - else if (msg.Type == Gui::SelectionChanges::RmvPreselect) { - resetPreselectPoint(); - if (sketchHandler) - sketchHandler->applyCursor(); - this->updateColor(); - } - } -} - -bool ViewProviderSketch::detectAndShowPreselection(SoPickedPoint * Point, const SbVec2s &cursorPos) -{ - assert(isInEditMode()); - - if (Point) { - - EditModeCoinManager::PreselectionResult result = - editCoinManager->detectPreselection(Point, cursorPos); - - if (result.PointIndex != -1 - && result.PointIndex != preselection.PreselectPoint) {// if a new point is hit - std::stringstream ss; - ss << "Vertex" << result.PointIndex + 1; - bool accepted = setPreselect(ss.str(), Point->getPoint()[0], Point->getPoint()[1], - Point->getPoint()[2]) - != 0; - preselection.blockedPreselection = !accepted; - if (accepted) { - setPreselectPoint(result.PointIndex); - - if (sketchHandler) - sketchHandler->applyCursor(); - return true; - } - } - else if (result.GeoIndex != -1 - && result.GeoIndex != preselection.PreselectCurve) {// if a new curve is hit - std::stringstream ss; - if (result.GeoIndex >= 0) - ss << "Edge" << result.GeoIndex + 1; - else // external geometry - ss << "ExternalEdge" - << -result.GeoIndex + Sketcher::GeoEnum::RefExt - + 1;// convert index start from -3 to 1 - bool accepted = setPreselect(ss.str(), Point->getPoint()[0], Point->getPoint()[1], - Point->getPoint()[2]) - != 0; - preselection.blockedPreselection = !accepted; - if (accepted) { - resetPreselectPoint(); - preselection.PreselectCurve = result.GeoIndex; - - if (sketchHandler) - sketchHandler->applyCursor(); - return true; - } - } - else if (result.Cross != EditModeCoinManager::PreselectionResult::Axes::None - && static_cast(result.Cross) - != static_cast(preselection.PreselectCross)) {// if a cross line is hit - std::stringstream ss; - switch (result.Cross) { - case EditModeCoinManager::PreselectionResult::Axes::RootPoint: - ss << "RootPoint"; - break; - case EditModeCoinManager::PreselectionResult::Axes::HorizontalAxis: - ss << "H_Axis"; - break; - case EditModeCoinManager::PreselectionResult::Axes::VerticalAxis: - ss << "V_Axis"; - break; - case EditModeCoinManager::PreselectionResult::Axes::None: - break;// silent warning - be explicit - } - bool accepted = setPreselect(ss.str(), Point->getPoint()[0], Point->getPoint()[1], - Point->getPoint()[2]) - != 0; - preselection.blockedPreselection = !accepted; - if (accepted) { - if (result.Cross == EditModeCoinManager::PreselectionResult::Axes::RootPoint) - setPreselectRootPoint(); - else - resetPreselectPoint(); - preselection.PreselectCross = - static_cast(static_cast(result.Cross)); - - if (sketchHandler) - sketchHandler->applyCursor(); - return true; - } - } - else if (!result.ConstrIndices.empty() - && result.ConstrIndices - != preselection.PreselectConstraintSet) {// if a constraint is hit - bool accepted = true; - for (std::set::iterator it = result.ConstrIndices.begin(); - it != result.ConstrIndices.end(); ++it) { - std::stringstream ss; - ss << Sketcher::PropertyConstraintList::getConstraintName(*it); - - accepted &= setPreselect(ss.str(), Point->getPoint()[0], Point->getPoint()[1], - Point->getPoint()[2]) - != 0; - - preselection.blockedPreselection = !accepted; - //TODO: Should we clear preselections that went through, if one fails? - } - if (accepted) { - resetPreselectPoint(); - preselection.PreselectConstraintSet = result.ConstrIndices; - - if (sketchHandler) - sketchHandler->applyCursor(); - return true;//Preselection changed - } - } - else if ((result.PointIndex == -1 && result.GeoIndex == -1 - && result.Cross == EditModeCoinManager::PreselectionResult::Axes::None - && result.ConstrIndices.empty()) - && (preselection.isPreselectPointValid() || preselection.isPreselectCurveValid() - || preselection.isCrossPreselected() - || !preselection.PreselectConstraintSet.empty() - || preselection.blockedPreselection)) { - // we have just left a preselection - resetPreselectPoint(); - preselection.blockedPreselection = false; - if (sketchHandler) - sketchHandler->applyCursor(); - return true; - } - Gui::Selection().setPreselectCoord(Point->getPoint()[0], Point->getPoint()[1], - Point->getPoint()[2]); - } - else if (preselection.isPreselectCurveValid() || preselection.isPreselectPointValid() - || !preselection.PreselectConstraintSet.empty() || preselection.isCrossPreselected() - || preselection.blockedPreselection) { - resetPreselectPoint(); - preselection.blockedPreselection = false; - if (sketchHandler) - sketchHandler->applyCursor(); - return true; - } - - return false; -} - -void ViewProviderSketch::centerSelection() -{ - Gui::MDIView *mdi = this->getActiveView(); - Gui::View3DInventor *view = qobject_cast(mdi); - if (!view || !isInEditMode()) - return; - - SoGroup* group = editCoinManager->getSelectedConstraints(); - - Gui::View3DInventorViewer* viewer = view->getViewer(); - SoGetBoundingBoxAction action(viewer->getSoRenderManager()->getViewportRegion()); - action.apply(group); - group->unref(); - - SbBox3f box = action.getBoundingBox(); - if (!box.isEmpty()) { - SoCamera* camera = viewer->getSoRenderManager()->getCamera(); - SbVec3f direction; - camera->orientation.getValue().multVec(SbVec3f(0, 0, 1), direction); - SbVec3f box_cnt = box.getCenter(); - SbVec3f cam_pos = box_cnt + camera->focalDistance.getValue() * direction; - camera->position.setValue(cam_pos); - } -} - -void ViewProviderSketch::doBoxSelection(const SbVec2s &startPos, const SbVec2s &endPos, - const Gui::View3DInventorViewer *viewer) -{ - std::vector corners0; - corners0.push_back(startPos); - corners0.push_back(endPos); - std::vector corners = viewer->getGLPolygon(corners0); - - // all calculations with polygon and proj are in dimensionless [0 1] screen coordinates - Base::Polygon2d polygon; - polygon.Add(Base::Vector2d(corners[0].getValue()[0], corners[0].getValue()[1])); - polygon.Add(Base::Vector2d(corners[0].getValue()[0], corners[1].getValue()[1])); - polygon.Add(Base::Vector2d(corners[1].getValue()[0], corners[1].getValue()[1])); - polygon.Add(Base::Vector2d(corners[1].getValue()[0], corners[0].getValue()[1])); - - Gui::ViewVolumeProjection proj(viewer->getSoRenderManager()->getCamera()->getViewVolume()); - - Sketcher::SketchObject *sketchObject = getSketchObject(); - - Base::Placement Plm = getEditingPlacement(); - - int intGeoCount = sketchObject->getHighestCurveIndex() + 1; - int extGeoCount = sketchObject->getExternalGeometryCount(); - - const std::vector geomlist = sketchObject->getCompleteGeometry(); // without memory allocation - assert(int(geomlist.size()) == extGeoCount + intGeoCount); - assert(int(geomlist.size()) >= 2); - - Base::Vector3d pnt0, pnt1, pnt2, pnt; - int VertexId = -1; // the loop below should be in sync with the main loop in ViewProviderSketch::draw - // so that the vertex indices are calculated correctly - int GeoId = 0; - - bool touchMode = false; - //check if selection goes from the right to the left side (for touch-selection where even partially boxed objects get selected) - if(corners[0].getValue()[0] > corners[1].getValue()[0]) - touchMode = true; - - for (std::vector::const_iterator it = geomlist.begin(); it != geomlist.end()-2; ++it, ++GeoId) { - - if (GeoId >= intGeoCount) - GeoId = -extGeoCount; - - if ((*it)->getTypeId() == Part::GeomPoint::getClassTypeId()) { - // ----- Check if single point lies inside box selection -----/ - const Part::GeomPoint *point = static_cast(*it); - Plm.multVec(point->getPoint(), pnt0); - pnt0 = proj(pnt0); - VertexId += 1; - - if (polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y))) { - std::stringstream ss; - ss << "Vertex" << VertexId + 1; - addSelection2(ss.str()); - } - - } else if ((*it)->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { - // ----- Check if line segment lies inside box selection -----/ - const Part::GeomLineSegment *lineSeg = static_cast(*it); - Plm.multVec(lineSeg->getStartPoint(), pnt1); - Plm.multVec(lineSeg->getEndPoint(), pnt2); - pnt1 = proj(pnt1); - pnt2 = proj(pnt2); - VertexId += 2; - - bool pnt1Inside = polygon.Contains(Base::Vector2d(pnt1.x, pnt1.y)); - bool pnt2Inside = polygon.Contains(Base::Vector2d(pnt2.x, pnt2.y)); - if (pnt1Inside) { - std::stringstream ss; - ss << "Vertex" << VertexId; - addSelection2(ss.str()); - } - - if (pnt2Inside) { - std::stringstream ss; - ss << "Vertex" << VertexId + 1; - addSelection2(ss.str()); - } - - if ((pnt1Inside && pnt2Inside) && !touchMode) { - std::stringstream ss; - ss << "Edge" << GeoId + 1; - addSelection2(ss.str()); - } - //check if line intersects with polygon - else if (touchMode) { - Base::Polygon2d lineAsPolygon; - lineAsPolygon.Add(Base::Vector2d(pnt1.x, pnt1.y)); - lineAsPolygon.Add(Base::Vector2d(pnt2.x, pnt2.y)); - std::list resultList; - polygon.Intersect(lineAsPolygon, resultList); - if (!resultList.empty()) { - std::stringstream ss; - ss << "Edge" << GeoId + 1; - addSelection2(ss.str()); - } - } - - } else if ((*it)->getTypeId() == Part::GeomCircle::getClassTypeId()) { - // ----- Check if circle lies inside box selection -----/ - ///TODO: Make it impossible to miss the circle if it's big and the selection pretty thin. - const Part::GeomCircle *circle = static_cast(*it); - pnt0 = circle->getCenter(); - VertexId += 1; - - Plm.multVec(pnt0, pnt0); - pnt0 = proj(pnt0); - - if (polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y)) || touchMode) { - if (polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y))) { - std::stringstream ss; - ss << "Vertex" << VertexId + 1; - addSelection2(ss.str()); - } - int countSegments = 12; - if (touchMode) - countSegments = 36; - - float segment = float(2 * M_PI) / countSegments; - - // circumscribed polygon radius - float radius = float(circle->getRadius()) / cos(segment/2); - - bool bpolyInside = true; - pnt0 = circle->getCenter(); - float angle = 0.f; - for (int i = 0; i < countSegments; ++i, angle += segment) { - pnt = Base::Vector3d(pnt0.x + radius * cos(angle), - pnt0.y + radius * sin(angle), - 0.f); - Plm.multVec(pnt, pnt); - pnt = proj(pnt); - if (!polygon.Contains(Base::Vector2d(pnt.x, pnt.y))) { - bpolyInside = false; - if (!touchMode) - break; - } - else if (touchMode) { - bpolyInside = true; - break; - } - } - - if (bpolyInside) { - std::stringstream ss; - ss << "Edge" << GeoId + 1; - addSelection2(ss.str()); - } - } - } else if ((*it)->getTypeId() == Part::GeomEllipse::getClassTypeId()) { - // ----- Check if ellipse lies inside box selection -----/ - const Part::GeomEllipse *ellipse = static_cast(*it); - pnt0 = ellipse->getCenter(); - VertexId += 1; - - Plm.multVec(pnt0, pnt0); - pnt0 = proj(pnt0); - - if (polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y)) || touchMode) { - if (polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y))) { - std::stringstream ss; - ss << "Vertex" << VertexId + 1; - addSelection2(ss.str()); - } - - int countSegments = 12; - if (touchMode) - countSegments = 24; - double segment = (2 * M_PI) / countSegments; - - // circumscribed polygon radius - double a = (ellipse->getMajorRadius()) / cos(segment/2); - double b = (ellipse->getMinorRadius()) / cos(segment/2); - Base::Vector3d majdir = ellipse->getMajorAxisDir(); - Base::Vector3d mindir = Base::Vector3d(-majdir.y, majdir.x, 0.0); - - bool bpolyInside = true; - pnt0 = ellipse->getCenter(); - double angle = 0.; - for (int i = 0; i < countSegments; ++i, angle += segment) { - pnt = pnt0 + (cos(angle)*a)*majdir + sin(angle)*b*mindir; - Plm.multVec(pnt, pnt); - pnt = proj(pnt); - if (!polygon.Contains(Base::Vector2d(pnt.x, pnt.y))) { - bpolyInside = false; - if (!touchMode) - break; - } - else if (touchMode) { - bpolyInside = true; - break; - } - } - - if (bpolyInside) { - std::stringstream ss; - ss << "Edge" << GeoId + 1; - addSelection2(ss.str()); - } - } - - } else if ((*it)->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { - // Check if arc lies inside box selection - const Part::GeomArcOfCircle *aoc = static_cast(*it); - - pnt0 = aoc->getStartPoint(/*emulateCCW=*/true); - pnt1 = aoc->getEndPoint(/*emulateCCW=*/true); - pnt2 = aoc->getCenter(); - VertexId += 3; - - Plm.multVec(pnt0, pnt0); - Plm.multVec(pnt1, pnt1); - Plm.multVec(pnt2, pnt2); - pnt0 = proj(pnt0); - pnt1 = proj(pnt1); - pnt2 = proj(pnt2); - - bool pnt0Inside = polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y)); - bool pnt1Inside = polygon.Contains(Base::Vector2d(pnt1.x, pnt1.y)); - bool bpolyInside = true; - - if ((pnt0Inside && pnt1Inside) || touchMode) { - double startangle, endangle; - aoc->getRange(startangle, endangle, /*emulateCCW=*/true); - - if (startangle > endangle) // if arc is reversed - std::swap(startangle, endangle); - - double range = endangle-startangle; - int countSegments = std::max(2, int(12.0 * range / (2 * M_PI))); - if (touchMode) - countSegments=countSegments*2.5; - float segment = float(range) / countSegments; - - // circumscribed polygon radius - float radius = float(aoc->getRadius()) / cos(segment/2); - - pnt0 = aoc->getCenter(); - float angle = float(startangle) + segment/2; - for (int i = 0; i < countSegments; ++i, angle += segment) { - pnt = Base::Vector3d(pnt0.x + radius * cos(angle), - pnt0.y + radius * sin(angle), - 0.f); - Plm.multVec(pnt, pnt); - pnt = proj(pnt); - if (!polygon.Contains(Base::Vector2d(pnt.x, pnt.y))) { - bpolyInside = false; - if (!touchMode) - break; - } - else if(touchMode) { - bpolyInside = true; - break; - } - } - - if (bpolyInside) { - std::stringstream ss; - ss << "Edge" << GeoId + 1; - addSelection2(ss.str()); - } - } - - if (pnt0Inside) { - std::stringstream ss; - ss << "Vertex" << VertexId - 1; - addSelection2(ss.str()); - } - - if (pnt1Inside) { - std::stringstream ss; - ss << "Vertex" << VertexId; - addSelection2(ss.str()); - } - - if (polygon.Contains(Base::Vector2d(pnt2.x, pnt2.y))) { - std::stringstream ss; - ss << "Vertex" << VertexId + 1; - addSelection2(ss.str()); - } - } else if ((*it)->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) { - // Check if arc lies inside box selection - const Part::GeomArcOfEllipse *aoe = static_cast(*it); - - pnt0 = aoe->getStartPoint(/*emulateCCW=*/true); - pnt1 = aoe->getEndPoint(/*emulateCCW=*/true); - pnt2 = aoe->getCenter(); - - VertexId += 3; - - Plm.multVec(pnt0, pnt0); - Plm.multVec(pnt1, pnt1); - Plm.multVec(pnt2, pnt2); - pnt0 = proj(pnt0); - pnt1 = proj(pnt1); - pnt2 = proj(pnt2); - - bool pnt0Inside = polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y)); - bool pnt1Inside = polygon.Contains(Base::Vector2d(pnt1.x, pnt1.y)); - bool bpolyInside = true; - - if ((pnt0Inside && pnt1Inside) || touchMode) { - double startangle, endangle; - aoe->getRange(startangle, endangle, /*emulateCCW=*/true); - - if (startangle > endangle) // if arc is reversed - std::swap(startangle, endangle); - - double range = endangle-startangle; - int countSegments = std::max(2, int(12.0 * range / (2 * M_PI))); - if (touchMode) - countSegments=countSegments*2.5; - double segment = (range) / countSegments; - - // circumscribed polygon radius - double a = (aoe->getMajorRadius()) / cos(segment/2); - double b = (aoe->getMinorRadius()) / cos(segment/2); - Base::Vector3d majdir = aoe->getMajorAxisDir(); - Base::Vector3d mindir = Base::Vector3d(-majdir.y, majdir.x, 0.0); - - pnt0 = aoe->getCenter(); - double angle = (startangle) + segment/2; - for (int i = 0; i < countSegments; ++i, angle += segment) { - pnt = pnt0 + cos(angle)*a*majdir + sin(angle)*b*mindir; - - Plm.multVec(pnt, pnt); - pnt = proj(pnt); - if (!polygon.Contains(Base::Vector2d(pnt.x, pnt.y))) { - bpolyInside = false; - if (!touchMode) - break; - } - else if (touchMode) { - bpolyInside = true; - break; - } - } - - if (bpolyInside) { - std::stringstream ss; - ss << "Edge" << GeoId + 1; - addSelection2(ss.str()); - } - } - if (pnt0Inside) { - std::stringstream ss; - ss << "Vertex" << VertexId - 1; - addSelection2(ss.str()); - } - - if (pnt1Inside) { - std::stringstream ss; - ss << "Vertex" << VertexId; - addSelection2(ss.str()); - } - - if (polygon.Contains(Base::Vector2d(pnt2.x, pnt2.y))) { - std::stringstream ss; - ss << "Vertex" << VertexId + 1; - addSelection2(ss.str()); - } - - } else if ((*it)->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId()) { - // Check if arc lies inside box selection - const Part::GeomArcOfHyperbola *aoh = static_cast(*it); - pnt0 = aoh->getStartPoint(); - pnt1 = aoh->getEndPoint(); - pnt2 = aoh->getCenter(); - - VertexId += 3; - - Plm.multVec(pnt0, pnt0); - Plm.multVec(pnt1, pnt1); - Plm.multVec(pnt2, pnt2); - pnt0 = proj(pnt0); - pnt1 = proj(pnt1); - pnt2 = proj(pnt2); - - bool pnt0Inside = polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y)); - bool pnt1Inside = polygon.Contains(Base::Vector2d(pnt1.x, pnt1.y)); - bool bpolyInside = true; - - if ((pnt0Inside && pnt1Inside) || touchMode) { - double startangle, endangle; - - aoh->getRange(startangle, endangle, /*emulateCCW=*/true); - - if (startangle > endangle) // if arc is reversed - std::swap(startangle, endangle); - - double range = endangle-startangle; - int countSegments = std::max(2, int(12.0 * range / (2 * M_PI))); - if (touchMode) - countSegments=countSegments*2.5; - - float segment = float(range) / countSegments; - - // circumscribed polygon radius - float a = float(aoh->getMajorRadius()) / cos(segment/2); - float b = float(aoh->getMinorRadius()) / cos(segment/2); - float phi = float(aoh->getAngleXU()); - - pnt0 = aoh->getCenter(); - float angle = float(startangle) + segment/2; - for (int i = 0; i < countSegments; ++i, angle += segment) { - pnt = Base::Vector3d(pnt0.x + a * cosh(angle) * cos(phi) - b * sinh(angle) * sin(phi), - pnt0.y + a * cosh(angle) * sin(phi) + b * sinh(angle) * cos(phi), - 0.f); - - Plm.multVec(pnt, pnt); - pnt = proj(pnt); - if (!polygon.Contains(Base::Vector2d(pnt.x, pnt.y))) { - bpolyInside = false; - if (!touchMode) - break; - } - else if (touchMode) { - bpolyInside = true; - break; - } - } - - if (bpolyInside) { - std::stringstream ss; - ss << "Edge" << GeoId + 1; - addSelection2(ss.str()); - } - if (pnt0Inside) { - std::stringstream ss; - ss << "Vertex" << VertexId - 1; - addSelection2(ss.str()); - } - - if (pnt1Inside) { - std::stringstream ss; - ss << "Vertex" << VertexId; - addSelection2(ss.str()); - } - - if (polygon.Contains(Base::Vector2d(pnt2.x, pnt2.y))) { - std::stringstream ss; - ss << "Vertex" << VertexId + 1; - addSelection2(ss.str()); - } - - } - - } else if ((*it)->getTypeId() == Part::GeomArcOfParabola::getClassTypeId()) { - // Check if arc lies inside box selection - const Part::GeomArcOfParabola *aop = static_cast(*it); - - pnt0 = aop->getStartPoint(); - pnt1 = aop->getEndPoint(); - pnt2 = aop->getCenter(); - - VertexId += 3; - - Plm.multVec(pnt0, pnt0); - Plm.multVec(pnt1, pnt1); - Plm.multVec(pnt2, pnt2); - pnt0 = proj(pnt0); - pnt1 = proj(pnt1); - pnt2 = proj(pnt2); - - bool pnt0Inside = polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y)); - bool pnt1Inside = polygon.Contains(Base::Vector2d(pnt1.x, pnt1.y)); - bool bpolyInside = true; - - if ((pnt0Inside && pnt1Inside) || touchMode) { - double startangle, endangle; - - aop->getRange(startangle, endangle, /*emulateCCW=*/true); - - if (startangle > endangle) // if arc is reversed - std::swap(startangle, endangle); - - double range = endangle-startangle; - int countSegments = std::max(2, int(12.0 * range / (2 * M_PI))); - if (touchMode) - countSegments=countSegments*2.5; - - float segment = float(range) / countSegments; - //In local coordinate system, value() of parabola is: - //P(U) = O + U*U/(4.*F)*XDir + U*YDir - // circumscribed polygon radius - float focal = float(aop->getFocal()) / cos(segment/2); - float phi = float(aop->getAngleXU()); - - pnt0 = aop->getCenter(); - float angle = float(startangle) + segment/2; - for (int i = 0; i < countSegments; ++i, angle += segment) { - 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); - - Plm.multVec(pnt, pnt); - pnt = proj(pnt); - if (!polygon.Contains(Base::Vector2d(pnt.x, pnt.y))) { - bpolyInside = false; - if (!touchMode) - break; - } - else if (touchMode) { - bpolyInside = true; - break; - } - } - - if (bpolyInside) { - std::stringstream ss; - ss << "Edge" << GeoId + 1; - addSelection2(ss.str()); - } - if (pnt0Inside) { - std::stringstream ss; - ss << "Vertex" << VertexId - 1; - addSelection2(ss.str()); - } - - if (pnt1Inside) { - std::stringstream ss; - ss << "Vertex" << VertexId; - addSelection2(ss.str()); - } - - if (polygon.Contains(Base::Vector2d(pnt2.x, pnt2.y))) { - std::stringstream ss; - ss << "Vertex" << VertexId + 1; - addSelection2(ss.str()); - } - } - - } else if ((*it)->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { - const Part::GeomBSplineCurve *spline = static_cast(*it); - //std::vector poles = spline->getPoles(); - VertexId += 2; - - Plm.multVec(spline->getStartPoint(), pnt1); - Plm.multVec(spline->getEndPoint(), pnt2); - pnt1 = proj(pnt1); - pnt2 = proj(pnt2); - - bool pnt1Inside = polygon.Contains(Base::Vector2d(pnt1.x, pnt1.y)); - bool pnt2Inside = polygon.Contains(Base::Vector2d(pnt2.x, pnt2.y)); - if (pnt1Inside || (touchMode && pnt2Inside)) { - std::stringstream ss; - ss << "Vertex" << VertexId; - addSelection2(ss.str()); - } - - if (pnt2Inside || (touchMode && pnt1Inside)) { - std::stringstream ss; - ss << "Vertex" << VertexId + 1; - addSelection2(ss.str()); - } - - // This is a rather approximated approach. No it does not guarantee that the whole curve is boxed, specially - // for periodic curves, but it works reasonably well. Including all poles, which could be done, generally - // forces the user to select much more than the curve (all the poles) and it would not select the curve in cases - // where it is indeed comprised in the box. - // The implementation of the touch mode is also far from a desirable "touch" as it only recognizes touched points not the curve itself - if ((pnt1Inside && pnt2Inside) || (touchMode && (pnt1Inside || pnt2Inside))) { - std::stringstream ss; - ss << "Edge" << GeoId + 1; - addSelection2(ss.str()); - } - } - } - - pnt0 = proj(Plm.getPosition()); - if (polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y))) { - std::stringstream ss; - ss << "RootPoint"; - addSelection2(ss.str()); - } -} - -void ViewProviderSketch::updateColor() -{ - assert(isInEditMode()); - - editCoinManager->updateColor(); -} - -bool ViewProviderSketch::doubleClicked() -{ - Gui::Application::Instance->activeDocument()->setEdit(this); - return true; -} - -float ViewProviderSketch::getScaleFactor() const -{ - assert(isInEditMode()); - Gui::MDIView *mdi = Gui::Application::Instance->editViewOfNode(editCoinManager->getRootEditNode()); - if (mdi && mdi->isDerivedFrom(Gui::View3DInventor::getClassTypeId())) { - Gui::View3DInventorViewer *viewer = static_cast(mdi)->getViewer(); - SoCamera* camera = viewer->getSoRenderManager()->getCamera(); - float scale = camera->getViewVolume(camera->aspectRatio.getValue()).getWorldToScreenScale(SbVec3f(0.f, 0.f, 0.f), 0.1f) / 3; - return scale; - } - else { - return 1.f; - } -} - -// This function ensures that the geometry used for drawing takes into account: -// 1. the OCC mandated weight, which is normalised for non-rational BSplines, but not normalised for rational BSplines. -// That includes properly sizing for drawing any weight constraint. -// This function ensures that both the geometry of the SketchObject and solver are updated with the new value of the scaling factor (via the extension) -// 2. the scaling factor, including inserting the scaling factor into the ViewProviderSketchGeometryExtension so as to enable -// That ensures that dragging operations on the circles of the poles of the B-Splines are properly rendered. -// -// This function takes a reference to a vector of deep copies to delete. These deep copies are necessary to transparently perform (1) while doing (2). -void ViewProviderSketch::scaleBSplinePoleCirclesAndUpdateSolverAndSketchObjectGeometry( - GeoListFacade & geolistfacade, - bool geometrywithmemoryallocation) -{ - // In order to allow to tweak geometry and insert scaling factors, this function needs to - // change the geometry vector. This is highly exceptional for a drawing function and special - // care needs to be taken. This is valid because: - // 1. The treatment is exceptional and no other appropriate place is available to perform this tweak - // 2. The original object needs to remain const for the benefit of all other class hierarchy of drawing functions - // 3. When referring to actual geometry, the modified pointers are short lived, as they are destroyed after drawing - auto & tempGeo = geolistfacade.geomlist; - - int GeoId = 0; - for (auto it = tempGeo.begin(); it != tempGeo.end()-2; ++it, GeoId++) { - if (GeoId >= geolistfacade.getInternalCount()) - GeoId = -geolistfacade.getExternalCount(); - - if ((*it)->getGeometry()->getTypeId() == Part::GeomCircle::getClassTypeId()) { // circle - const Part::GeomCircle *circle = static_cast((*it)->getGeometry()); - auto & gf = (*it); - - // BSpline weights have a radius corresponding to the weight value - // However, in order for them proportional to the B-Spline size, - // the scenograph has a size scalefactor times the weight - // - // This code produces the scaled up version of the geometry for the scenograph - if(gf->getInternalType() == InternalType::BSplineControlPoint) { - for( auto c : getSketchObject()->Constraints.getValues()) { - if( c->Type == InternalAlignment && c->AlignmentType == BSplineControlPoint && c->First == GeoId) { - auto bspline = dynamic_cast(tempGeo[c->Second]->getGeometry()); - - if(bspline){ - auto weights = bspline->getWeights(); - - double weight = 1.0; - if(c->InternalAlignmentIndex < int(weights.size())) - weight = weights[c->InternalAlignmentIndex]; - - // tentative scaling factor: - // proportional to the length of the bspline - // inversely proportional to the number of poles - double scalefactor = bspline->length(bspline->getFirstParameter(), bspline->getLastParameter())/10.0/weights.size(); - - double vradius = weight*scalefactor; - if(!bspline->isRational()) { - // OCCT sets the weights to 1.0 if a bspline is non-rational, but if the user has a weight constraint on any - // pole it would cause a visual artifact of having a constraint with a different radius and an unscaled circle - // so better scale the circles. - std::vector polegeoids; - polegeoids.reserve(weights.size()); - - for ( auto ic : getSketchObject()->Constraints.getValues()) { - if( ic->Type == InternalAlignment && ic->AlignmentType == BSplineControlPoint && ic->Second == c->Second) { - polegeoids.push_back(ic->First); - } - } - - for ( auto ic : getSketchObject()->Constraints.getValues()) { - if( ic->Type == Weight ) { - auto pos = std::find(polegeoids.begin(), polegeoids.end(), ic->First); - - if(pos != polegeoids.end()) { - vradius = ic->getValue() * scalefactor; - break; // one is enough, otherwise it would not be non-rational - } - } - } - } - - Part::GeomCircle * tmpcircle; - - if(geometrywithmemoryallocation) { // with memory allocation - tmpcircle = const_cast(circle); - tmpcircle->setRadius(vradius); - } - else { // without memory allocation - tmpcircle = static_cast(circle->clone()); - tmpcircle->setRadius(vradius); - tempGeo[GeoId] = GeometryFacade::getFacade(tmpcircle, true); // this is the circle that will be drawn, with the updated vradius, the facade takes ownership and will deallocate. - } - - // save scale factor for any prospective dragging operation - // 1. Solver must be updated, in case a dragging operation starts - // 2. if temp geometry is being used (with memory allocation), then the copy we have here must be updated. If - // no temp geometry is being used, then the normal geometry must be updated. - {// make solver be ready for a dragging operation - auto vpext = std::make_unique(); - vpext->setRepresentationFactor(scalefactor); - - getSketchObject()->updateSolverExtension(GeoId, std::move(vpext)); - } - - if(!circle->hasExtension(SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId())) - { - // It is ok to add this kind of extension to a const geometry because: - // 1. It does not modify the object in a way that affects property state, just ViewProvider representation - // 2. If it is lost (for example upon undo), redrawing will reinstate it with the correct value - const_cast(circle)->setExtension(std::make_unique()); - } - - auto vpext = std::const_pointer_cast( - std::static_pointer_cast( - circle->getExtension(SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId()).lock())); - - vpext->setRepresentationFactor(scalefactor); - } - break; - } - } - } - } - } -} - -void ViewProviderSketch::draw(bool temp /*=false*/, bool rebuildinformationoverlay /*=true*/) -{ - assert(isInEditMode()); - - // ============== Retrieve geometry to be represented ================================= - - auto geolistfacade = temp ? - getSolvedSketch().extractGeoListFacade(): // with memory allocation - getSketchObject()->getGeoListFacade(); // without memory allocation - - assert(int( geolistfacade.geomlist.size()) >= 2); - - // ============== Prepare geometry for representation ================================== - - // ************ Manage BSpline pole circle scaling **************************** - - // This function ensures that the geometry used for drawing takes into account: - // 1. the OCC mandated weight, which is normalised for non-rational BSplines, but not normalised for rational BSplines. - // That includes properly sizing for drawing any weight constraint. - // This function ensures that both the geometry of the SketchObject and solver are updated with the new value of the scaling factor (via the extension) - // 2. the scaling factor, including inserting the scaling factor into the ViewProviderSketchGeometryExtension so as to enable - // That ensures that dragging operations on the circles of the poles of the B-Splines are properly rendered. - // - // This function takes a reference to a vector of deep copies to delete. These deep copies are necessary to transparently perform (1) while doing (2). - - scaleBSplinePoleCirclesAndUpdateSolverAndSketchObjectGeometry(geolistfacade, temp); - - // ============== Render geometry, constraints and geometry information overlays ================================== - - editCoinManager->processGeometryConstraintsInformationOverlay(geolistfacade, rebuildinformationoverlay); - - // Avoids unneeded calls to pixmapFromSvg - if(Mode==STATUS_NONE || Mode==STATUS_SKETCH_UseHandler) { - editCoinManager->drawConstraintIcons(geolistfacade); - editCoinManager->updateColor(geolistfacade); - } - - Gui::MDIView *mdi = this->getActiveView(); - if (mdi && mdi->isDerivedFrom(Gui::View3DInventor::getClassTypeId())) { - static_cast(mdi)->getViewer()->redraw(); - } -} - -void ViewProviderSketch::setIsShownVirtualSpace(bool isshownvirtualspace) -{ - viewProviderParameters.isShownVirtualSpace = isshownvirtualspace; - - editCoinManager->updateVirtualSpace(); - - signalConstraintsChanged(); -} - -bool ViewProviderSketch::getIsShownVirtualSpace() const -{ - return viewProviderParameters.isShownVirtualSpace; -} - - -void ViewProviderSketch::drawEdit(const std::vector &EditCurve) -{ - editCoinManager->drawEdit(EditCurve); -} - -void ViewProviderSketch::drawEdit(const std::list> &list) -{ - editCoinManager->drawEdit(list); -} - -void ViewProviderSketch::drawEditMarkers(const std::vector &EditMarkers, unsigned int augmentationlevel) -{ - editCoinManager->drawEditMarkers(EditMarkers, augmentationlevel); -} - -void ViewProviderSketch::updateData(const App::Property *prop) -{ - ViewProvider2DObject::updateData(prop); - - if (prop->getTypeId() == Part::PropertyPartShape::getClassTypeId()) { - if (isInEditMode()) - editCoinManager->drawGrid(); - } - - // In the case of an undo/redo transaction, updateData is triggered by SketchObject::onUndoRedoFinished() in the solve() - // In the case of an internal transaction, touching the geometry results in a call to updateData. - if ( isInEditMode() && !getSketchObject()->getDocument()->isPerformingTransaction() && - !getSketchObject()->isPerformingInternalTransaction() && - (prop == &(getSketchObject()->Geometry) || prop == &(getSketchObject()->Constraints))) { - - // At this point, we do not need to solve the Sketch - // If we are adding geometry an update can be triggered before the sketch is actually solved. - // Because a solve is mandatory to any addition (at least to update the DoF of the solver), - // only when the solver geometry is the same in number than the sketch geometry an update - // should trigger a redraw. This reduces even more the number of redraws per insertion of geometry - - // solver information is also updated when no matching geometry, so that if a solving fails - // this failed solving info is presented to the user - UpdateSolverInformation(); // just update the solver window with the last SketchObject solving information - - if(getSketchObject()->getExternalGeometryCount()+getSketchObject()->getHighestCurveIndex() + 1 == - getSolvedSketch().getGeometrySize()) { - Gui::MDIView *mdi = Gui::Application::Instance->editDocument()->getActiveView(); - if (mdi->isDerivedFrom(Gui::View3DInventor::getClassTypeId())) - draw(false,true); - - signalConstraintsChanged(); - } - - if(prop != &getSketchObject()->Constraints) - signalElementsChanged(); - } -} - -void ViewProviderSketch::onChanged(const App::Property *prop) -{ - // call father - ViewProviderPart::onChanged(prop); - - if (prop == &ShowGrid && isInEditMode()) - editCoinManager->drawGrid(); - - if (prop == &GridSize && isInEditMode()) - editCoinManager->drawGrid(); - - if (prop == &GridStyle && isInEditMode()) - editCoinManager->drawGrid(); -} - -void ViewProviderSketch::attach(App::DocumentObject *pcFeat) -{ - ViewProviderPart::attach(pcFeat); -} - -void ViewProviderSketch::setupContextMenu(QMenu *menu, QObject *receiver, const char *member) -{ - menu->addAction(tr("Edit sketch"), receiver, member); - // Call the extensions - Gui::ViewProvider::setupContextMenu(menu, receiver, member); -} - -namespace -{ - inline const QStringList editModeToolbarNames() - { - return QStringList{ QString::fromLatin1("Sketcher Edit Mode"), - QString::fromLatin1("Sketcher geometries"), - QString::fromLatin1("Sketcher constraints"), - QString::fromLatin1("Sketcher tools"), - QString::fromLatin1("Sketcher B-spline tools"), - QString::fromLatin1("Sketcher virtual space") }; - } - - inline const QStringList nonEditModeToolbarNames() - { - return QStringList{ QString::fromLatin1("Structure"), - QString::fromLatin1("Sketcher") }; - } -} - -bool ViewProviderSketch::setEdit(int ModNum) -{ - // When double-clicking on the item for this sketch the - // object unsets and sets its edit mode without closing - // the task panel - Gui::TaskView::TaskDialog *dlg = Gui::Control().activeDialog(); - TaskDlgEditSketch *sketchDlg = qobject_cast(dlg); - if (sketchDlg && sketchDlg->getSketchView() != this) - sketchDlg = nullptr; // another sketch left open its task panel - if (dlg && !sketchDlg) { - QMessageBox msgBox; - msgBox.setText(tr("A dialog is already open in the task panel")); - msgBox.setInformativeText(tr("Do you want to close this dialog?")); - msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - msgBox.setDefaultButton(QMessageBox::Yes); - int ret = msgBox.exec(); - if (ret == QMessageBox::Yes) - Gui::Control().reject(); - else - return false; - } - - Sketcher::SketchObject* sketch = getSketchObject(); - if (!sketch->evaluateConstraints()) { - QMessageBox box(Gui::getMainWindow()); - box.setIcon(QMessageBox::Critical); - box.setWindowTitle(tr("Invalid sketch")); - box.setText(tr("Do you want to open the sketch validation tool?")); - box.setInformativeText(tr("The sketch is invalid and cannot be edited.")); - box.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - box.setDefaultButton(QMessageBox::Yes); - switch (box.exec()) - { - case QMessageBox::Yes: - Gui::Control().showDialog(new TaskSketcherValidation(getSketchObject())); - break; - default: - break; - } - return false; - } - - // clear the selection (convenience) - Gui::Selection().clearSelection(); - Gui::Selection().rmvPreselect(); - - this->attachSelection(); - - // create the container for the additional edit data - assert(!isInEditMode()); - preselection.reset(); - selection.reset(); - editCoinManager = std::make_unique(*this); - - auto editDoc = Gui::Application::Instance->editDocument(); - App::DocumentObject *editObj = getSketchObject(); - std::string editSubName; - ViewProviderDocumentObject *editVp = nullptr; - if(editDoc) { - editDoc->getInEdit(&editVp,&editSubName); - if(editVp) - editObj = editVp->getObject(); - } - - //visibility automation - try{ - Gui::Command::addModule(Gui::Command::Gui,"Show"); - try{ - QString cmdstr = QString::fromLatin1( - "ActiveSketch = App.getDocument('%1').getObject('%2')\n" - "tv = Show.TempoVis(App.ActiveDocument, tag= ActiveSketch.ViewObject.TypeId)\n" - "ActiveSketch.ViewObject.TempoVis = tv\n" - "if ActiveSketch.ViewObject.EditingWorkbench:\n" - " tv.activateWorkbench(ActiveSketch.ViewObject.EditingWorkbench)\n" - "if ActiveSketch.ViewObject.HideDependent:\n" - " tv.hide(tv.get_all_dependent(%3, '%4'))\n" - "if ActiveSketch.ViewObject.ShowSupport:\n" - " tv.show([ref[0] for ref in ActiveSketch.Support if not ref[0].isDerivedFrom(\"PartDesign::Plane\")])\n" - "if ActiveSketch.ViewObject.ShowLinks:\n" - " tv.show([ref[0] for ref in ActiveSketch.ExternalGeometry])\n" - "tv.sketchClipPlane(ActiveSketch, ActiveSketch.ViewObject.SectionView)\n" - "tv.hide(ActiveSketch)\n" - "del(tv)\n" - "del(ActiveSketch)\n" - ).arg(QString::fromLatin1(getDocument()->getDocument()->getName()), - QString::fromLatin1(getSketchObject()->getNameInDocument()), - QString::fromLatin1(Gui::Command::getObjectCmd(editObj).c_str()), - QString::fromLatin1(editSubName.c_str())); - QByteArray cmdstr_bytearray = cmdstr.toLatin1(); - Gui::Command::runCommand(Gui::Command::Gui, cmdstr_bytearray); - } catch (Base::PyException &e){ - Base::Console().Error("ViewProviderSketch::setEdit: visibility automation failed with an error: \n"); - e.ReportException(); - } - } catch (Base::PyException &){ - Base::Console().Warning("ViewProviderSketch::setEdit: could not import Show module. Visibility automation will not work.\n"); - } - - // start the edit dialog - if (sketchDlg) - Gui::Control().showDialog(sketchDlg); - else - Gui::Control().showDialog(new TaskDlgEditSketch(this)); - - // This call to the solver is needed to initialize the DoF and solve time controls - // The false parameter indicates that the geometry of the SketchObject shall not be updateData - // so as not to trigger an onChanged that would set the document as modified and trigger a recompute - // if we just close the sketch without touching anything. - if (getSketchObject()->Support.getValue()) { - if (!getSketchObject()->evaluateSupport()) - getSketchObject()->validateExternalLinks(); - } - - // There are geometry extensions introduced by the solver and geometry extensions introduced by the viewprovider. - // 1. It is important that the solver has geometry with updated extensions. - // 2. It is important that the viewprovider has up-to-date solver information - // - // The decision is to maintain the "first solve then draw" order, which is consistent with the rest of the Sketcher - // for example in geometry creation. Then, the ViewProvider is responsible for updating the solver geometry when - // appropriate, as it is the ViewProvider that is introducing its geometry extensions. - // - // In order to have updated solver information, solve must take "true", this cause the Geometry property to be updated - // with the solver information, including solver extensions, and triggers a draw(true) via ViewProvider::UpdateData. - getSketchObject()->solve(true); - - connectUndoDocument = getDocument() - ->signalUndoDocument.connect(boost::bind(&ViewProviderSketch::slotUndoDocument, this, bp::_1)); - connectRedoDocument = getDocument() - ->signalRedoDocument.connect(boost::bind(&ViewProviderSketch::slotRedoDocument, this, bp::_1)); - - // Enable solver initial solution update while dragging. - getSketchObject()->setRecalculateInitialSolutionWhileMovingPoint(viewProviderParameters.recalculateInitialSolutionWhileDragging); - - // intercept del key press from main app - listener = new ShortcutListener(this); - - Gui::getMainWindow()->installEventFilter(listener); - - /*Modify toolbars dynamically. - First save state of toolbars in case user changed visibility of a toolbar but he's not changing the wb. - This happens in someone works directly from sketcher, changing from edit mode to not-edit-mode*/ - Gui::ToolBarManager::getInstance()->saveState(); - - Gui::ToolBarManager::getInstance()->setToolbarVisibility(true, editModeToolbarNames()); - Gui::ToolBarManager::getInstance()->setToolbarVisibility(false, nonEditModeToolbarNames()); - - return true; -} - -QString ViewProviderSketch::appendConflictMsg(const std::vector &conflicting) -{ - return appendConstraintMsg(tr("Please remove the following constraint:"), - tr("Please remove at least one of the following constraints:"), - conflicting); -} - -QString ViewProviderSketch::appendRedundantMsg(const std::vector &redundant) -{ - return appendConstraintMsg(tr("Please remove the following redundant constraint:"), - tr("Please remove the following redundant constraints:"), - redundant); -} - -QString ViewProviderSketch::appendPartiallyRedundantMsg(const std::vector &partiallyredundant) -{ - return appendConstraintMsg(tr("The following constraint is partially redundant:"), - tr("The following constraints are partially redundant:"), - partiallyredundant); -} - -QString ViewProviderSketch::appendMalformedMsg(const std::vector &malformed) -{ - return appendConstraintMsg(tr("Please remove the following malformed constraint:"), - tr("Please remove the following malformed constraints:"), - malformed); -} - -QString ViewProviderSketch::appendConstraintMsg(const QString & singularmsg, - const QString & pluralmsg, - const std::vector &vector) -{ - QString msg; - QTextStream ss(&msg); - if (!vector.empty()) { - if (vector.size() == 1) - ss << singularmsg; - else - ss << pluralmsg; - ss << "\n"; - ss << vector[0]; - for (unsigned int i=1; i < vector.size(); i++) - ss << ", " << vector[i]; - - ss << "\n"; - } - return msg; -} - -inline QString intListHelper(const std::vector &ints) -{ - QString results; - if (ints.size() < 8) { // The 8 is a bit heuristic... more than that and we shift formats - for (const auto i : ints) { - if (results.isEmpty()) - results.append(QString::fromUtf8("%1").arg(i)); - else - results.append(QString::fromUtf8(", %1").arg(i)); - } - } - else { - const int numToShow = 3; - int more = ints.size() - numToShow; - for (int i = 0; i < numToShow; ++i) { - results.append(QString::fromUtf8("%1, ").arg(ints[i])); - } - results.append(QCoreApplication::translate("ViewProviderSketch","and %1 more").arg(more)); - } - std::string testString = results.toStdString(); - return results; -} - -void ViewProviderSketch::UpdateSolverInformation() -{ - // Updates Solver Information with the Last solver execution at SketchObject level - int dofs = getSketchObject()->getLastDoF(); - bool hasConflicts = getSketchObject()->getLastHasConflicts(); - bool hasRedundancies = getSketchObject()->getLastHasRedundancies(); - bool hasPartiallyRedundant = getSketchObject()->getLastHasPartialRedundancies(); - bool hasMalformed = getSketchObject()->getLastHasMalformedConstraints(); - - if (getSketchObject()->Geometry.getSize() == 0) { - signalSetUp(QString::fromUtf8("empty_sketch"), tr("Empty sketch"), QString(), QString()); - } - else if (dofs < 0 || hasConflicts) { // over-constrained sketch - signalSetUp(QString::fromUtf8("conflicting_constraints"), - tr("Over-constrained: "), - QString::fromUtf8("#conflicting"), - QString::fromUtf8("(%1)").arg(intListHelper(getSketchObject()->getLastConflicting()))); - } - else if (hasMalformed) { // malformed constraints - signalSetUp(QString::fromUtf8("malformed_constraints"), - tr("Malformed constraints: "), - QString::fromUtf8("#malformed"), - QString::fromUtf8("(%1)").arg(intListHelper(getSketchObject()->getLastMalformedConstraints()))); - } - else if (hasRedundancies) { - signalSetUp(QString::fromUtf8("redundant_constraints"), - tr("Redundant constraints:"), - QString::fromUtf8("#redundant"), - QString::fromUtf8("(%1)").arg(intListHelper(getSketchObject()->getLastRedundant()))); - } - else if (hasPartiallyRedundant) { - signalSetUp(QString::fromUtf8("partially_redundant_constraints"), - tr("Partially redundant:"), - QString::fromUtf8("#partiallyredundant"), - QString::fromUtf8("(%1)").arg(intListHelper(getSketchObject()->getLastPartiallyRedundant()))); - } - else if (getSketchObject()->getLastSolverStatus() != 0) { - signalSetUp(QString::fromUtf8("solver_failed"), - tr("Solver failed to converge"), - QString::fromUtf8(""), - QString::fromUtf8("")); - } else if (dofs > 0) { - signalSetUp(QString::fromUtf8("under_constrained"), - tr("Under constrained:"), - QString::fromUtf8("#dofs"), - tr("%n DoF(s)","",dofs)); - } - else { - signalSetUp(QString::fromUtf8("fully_constrained"), tr("Fully constrained"), QString(), QString()); - } -} - -void ViewProviderSketch::unsetEdit(int ModNum) -{ - Q_UNUSED(ModNum); - - /*Modify toolbars dynamically. - First save state of toolbars in case user changed visibility of a toolbar but he's not changing the wb. - This happens in someone works directly from sketcher, changing from edit mode to not-edit-mode*/ - Gui::ToolBarManager::getInstance()->saveState(); - - Gui::ToolBarManager::getInstance()->setToolbarVisibility(false, editModeToolbarNames()); - Gui::ToolBarManager::getInstance()->setToolbarVisibility(true, nonEditModeToolbarNames()); - - if(listener) { - Gui::getMainWindow()->removeEventFilter(listener); - delete listener; - } - - if (isInEditMode()) { - if (sketchHandler) - deactivateHandler(); - - editCoinManager = nullptr; - preselection.reset(); - selection.reset(); - this->detachSelection(); - - App::AutoTransaction trans("Sketch recompute"); - try { - // and update the sketch - // getSketchObject()->getDocument()->recompute(); - Gui::Command::updateActive(); - } - catch (...) { - } - } - - // clear the selection and set the new/edited sketch(convenience) - Gui::Selection().clearSelection(); - Gui::Selection().addSelection(editDocName.c_str(),editObjName.c_str(),editSubName.c_str()); - - connectUndoDocument.disconnect(); - connectRedoDocument.disconnect(); - - // when pressing ESC make sure to close the dialog - Gui::Control().closeDialog(); - - //visibility autoation - try{ - QString cmdstr = QString::fromLatin1( - "ActiveSketch = App.getDocument('%1').getObject('%2')\n" - "tv = ActiveSketch.ViewObject.TempoVis\n" - "if tv:\n" - " tv.restore()\n" - "ActiveSketch.ViewObject.TempoVis = None\n" - "del(tv)\n" - "del(ActiveSketch)\n" - ).arg(QString::fromLatin1(getDocument()->getDocument()->getName()), - QString::fromLatin1(getSketchObject()->getNameInDocument())); - QByteArray cmdstr_bytearray = cmdstr.toLatin1(); - Gui::Command::runCommand(Gui::Command::Gui, cmdstr_bytearray); - } catch (Base::PyException &e){ - Base::Console().Error("ViewProviderSketch::unsetEdit: visibility automation failed with an error: \n"); - e.ReportException(); - } -} - -void ViewProviderSketch::setEditViewer(Gui::View3DInventorViewer* viewer, int ModNum) -{ - Q_UNUSED(ModNum); - //visibility automation: save camera - if (! this->TempoVis.getValue().isNone()){ - try{ - QString cmdstr = QString::fromLatin1( - "ActiveSketch = App.getDocument('%1').getObject('%2')\n" - "if ActiveSketch.ViewObject.RestoreCamera:\n" - " ActiveSketch.ViewObject.TempoVis.saveCamera()\n" - " if ActiveSketch.ViewObject.ForceOrtho:\n" - " ActiveSketch.ViewObject.Document.ActiveView.setCameraType('Orthographic')\n" - ).arg(QString::fromLatin1(getDocument()->getDocument()->getName()), - QString::fromLatin1(getSketchObject()->getNameInDocument())); - QByteArray cmdstr_bytearray = cmdstr.toLatin1(); - Gui::Command::runCommand(Gui::Command::Gui, cmdstr_bytearray); - } catch (Base::PyException &e){ - Base::Console().Error("ViewProviderSketch::setEdit: visibility automation failed with an error: \n"); - e.ReportException(); - } - } - - auto editDoc = Gui::Application::Instance->editDocument(); - editDocName.clear(); - if(editDoc) { - ViewProviderDocumentObject *parent=nullptr; - editDoc->getInEdit(&parent,&editSubName); - if(parent) { - editDocName = editDoc->getDocument()->getName(); - editObjName = parent->getObject()->getNameInDocument(); - } - } - if(editDocName.empty()) { - editDocName = getObject()->getDocument()->getName(); - editObjName = getObject()->getNameInDocument(); - editSubName.clear(); - } - const char *dot = strrchr(editSubName.c_str(),'.'); - if(!dot) - editSubName.clear(); - else - editSubName.resize(dot-editSubName.c_str()+1); - - Base::Placement plm = getEditingPlacement(); - Base::Rotation tmp(plm.getRotation()); - - SbRotation rot((float)tmp[0],(float)tmp[1],(float)tmp[2],(float)tmp[3]); - - // Will the sketch be visible from the new position (#0000957)? - // - SoCamera* camera = viewer->getSoRenderManager()->getCamera(); - SbVec3f curdir; // current view direction - camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), curdir); - SbVec3f focal = camera->position.getValue() + - camera->focalDistance.getValue() * curdir; - - SbVec3f newdir; // future view direction - rot.multVec(SbVec3f(0, 0, -1), newdir); - SbVec3f newpos = focal - camera->focalDistance.getValue() * newdir; - - SbVec3f plnpos = Base::convertTo(plm.getPosition()); - double dist = (plnpos - newpos).dot(newdir); - if (dist < 0) { - float focalLength = camera->focalDistance.getValue() - dist + 5; - camera->position = focal - focalLength * curdir; - camera->focalDistance.setValue(focalLength); - } - - viewer->setCameraOrientation(rot); - - viewer->setEditing(true); - SoNode* root = viewer->getSceneGraph(); - static_cast(root)->selectionRole.setValue(false); - - viewer->addGraphicsItem(rubberband.get()); - rubberband->setViewer(viewer); - - viewer->setupEditingRoot(); - - cameraSensor.setData(new VPRender{this, viewer->getSoRenderManager()}); - cameraSensor.attach(viewer->getSoRenderManager()->getSceneGraph()); -} - -void ViewProviderSketch::unsetEditViewer(Gui::View3DInventorViewer* viewer) -{ - auto dataPtr = static_cast(cameraSensor.getData()); - delete dataPtr; - cameraSensor.setData(nullptr); - cameraSensor.detach(); - - viewer->removeGraphicsItem(rubberband.get()); - viewer->setEditing(false); - SoNode* root = viewer->getSceneGraph(); - static_cast(root)->selectionRole.setValue(true); -} - -void ViewProviderSketch::camSensCB(void *data, SoSensor *) -{ - VPRender *proxyVPrdr = static_cast(data); - if (!proxyVPrdr) - return; - - auto vp = proxyVPrdr->vp; - auto cam = proxyVPrdr->renderMgr->getCamera(); - - vp->onCameraChanged(cam); -} - -void ViewProviderSketch::onCameraChanged(SoCamera* cam) -{ - auto rotSk = Base::Rotation(getDocument()->getEditingTransform()); //sketch orientation - auto rotc = cam->orientation.getValue().getValue(); - auto rotCam = Base::Rotation(rotc[0], rotc[1], rotc[2], rotc[3]); // camera orientation (needed because float to double conversion) - - // Is camera in the same hemisphere as positive sketch normal ? - auto orientation = (rotCam.invert() * rotSk).multVec(Base::Vector3d(0, 0, 1)); - auto tmpFactor = orientation.z < 0 ? -1 : 1; - - if (tmpFactor != viewOrientationFactor) { // redraw only if viewing side changed - Base::Console().Log("Switching side, now %s, redrawing\n", tmpFactor < 0 ? "back" : "front"); - viewOrientationFactor = tmpFactor; - draw(); - - QString cmdStr = QStringLiteral( - "ActiveSketch.ViewObject.TempoVis.sketchClipPlane(ActiveSketch, ActiveSketch.ViewObject.SectionView, %1)\n") - .arg(tmpFactor < 0 ? QLatin1String("True") : QLatin1String("False")); - Base::Interpreter().runStringObject(cmdStr.toLatin1()); - } - - editCoinManager->drawGrid(true); -} - -int ViewProviderSketch::getPreselectPoint() const -{ - if (isInEditMode()) - return preselection.PreselectPoint; - return -1; -} - -int ViewProviderSketch::getPreselectCurve() const -{ - if (isInEditMode()) - return preselection.PreselectCurve; - return -1; -} - -int ViewProviderSketch::getPreselectCross() const -{ - // TODO: This function spreads over several files. It should be refactored into something less "numeric" at a second stage. - if (isInEditMode()) - return static_cast(preselection.PreselectCross); - return -1; -} - -Sketcher::SketchObject *ViewProviderSketch::getSketchObject() const -{ - return dynamic_cast(pcObject); -} - -const Sketcher::Sketch &ViewProviderSketch::getSolvedSketch() const -{ - return const_cast(getSketchObject())->getSolvedSketch(); -} - -void ViewProviderSketch::deleteSelected() -{ - std::vector selection; - selection = Gui::Selection().getSelectionEx(nullptr, Sketcher::SketchObject::getClassTypeId()); - - // only one sketch with its subelements are allowed to be selected - if (selection.size() != 1) { - Base::Console().Warning("Delete: Selection not restricted to one sketch and its subelements\n"); - return; - } - - // get the needed lists and objects - const std::vector &SubNames = selection[0].getSubNames(); - - if(!SubNames.empty()) { - App::Document* doc = getSketchObject()->getDocument(); - - doc->openTransaction("Delete sketch geometry"); - - onDelete(SubNames); - - doc->commitTransaction(); - } -} - -bool ViewProviderSketch::onDelete(const std::vector &subList) -{ - if (isInEditMode()) { - std::vector SubNames = subList; - - Gui::Selection().clearSelection(); - resetPreselectPoint(); - - std::set delInternalGeometries, delExternalGeometries, delCoincidents, delConstraints; - // go through the selected subelements - for (std::vector::const_iterator it=SubNames.begin(); it != SubNames.end(); ++it) { - if (it->size() > 4 && it->substr(0,4) == "Edge") { - int GeoId = std::atoi(it->substr(4,4000).c_str()) - 1; - if( GeoId >= 0 ) { - delInternalGeometries.insert(GeoId); - } - else - delExternalGeometries.insert(Sketcher::GeoEnum::RefExt - GeoId); - } else if (it->size() > 12 && it->substr(0,12) == "ExternalEdge") { - int GeoId = std::atoi(it->substr(12,4000).c_str()) - 1; - delExternalGeometries.insert(GeoId); - } else if (it->size() > 6 && it->substr(0,6) == "Vertex") { - int VtId = std::atoi(it->substr(6,4000).c_str()) - 1; - int GeoId; - Sketcher::PointPos PosId; - getSketchObject()->getGeoVertexIndex(VtId, GeoId, PosId); - if (getSketchObject()->getGeometry(GeoId)->getTypeId() - == Part::GeomPoint::getClassTypeId()) { - if(GeoId>=0) - delInternalGeometries.insert(GeoId); - else - delExternalGeometries.insert(Sketcher::GeoEnum::RefExt - GeoId); - } - else - delCoincidents.insert(VtId); - } else if (*it == "RootPoint") { - delCoincidents.insert(Sketcher::GeoEnum::RtPnt); - } else if (it->size() > 10 && it->substr(0,10) == "Constraint") { - int ConstrId = Sketcher::PropertyConstraintList::getIndexFromConstraintName(*it); - delConstraints.insert(ConstrId); - } - } - - // We stored the vertices, but is there really a coincident constraint? Check - const std::vector< Sketcher::Constraint * > &vals = getSketchObject()->Constraints.getValues(); - - std::set::const_reverse_iterator rit; - - for (rit = delConstraints.rbegin(); rit != delConstraints.rend(); ++rit) { - try { - Gui::cmdAppObjectArgs(getObject(), "delConstraint(%i)", *rit); - } - catch (const Base::Exception& e) { - Base::Console().Error("%s\n", e.what()); - } - } - - for (rit = delCoincidents.rbegin(); rit != delCoincidents.rend(); ++rit) { - int GeoId; - PointPos PosId; - - if (*rit == GeoEnum::RtPnt) { // RootPoint - GeoId = Sketcher::GeoEnum::RtPnt; - PosId = Sketcher::PointPos::start; - - } else { - getSketchObject()->getGeoVertexIndex(*rit, GeoId, PosId); - } - - if (GeoId != GeoEnum::GeoUndef) { - for (std::vector< Sketcher::Constraint * >::const_iterator it= vals.begin(); it != vals.end(); ++it) { - if (((*it)->Type == Sketcher::Coincident) && (((*it)->First == GeoId && (*it)->FirstPos == PosId) || - ((*it)->Second == GeoId && (*it)->SecondPos == PosId)) ) { - try { - Gui::cmdAppObjectArgs(getObject(), "delConstraintOnPoint(%i,%i)", GeoId, (int)PosId); - } - catch (const Base::Exception& e) { - Base::Console().Error("%s\n", e.what()); - } - break; - } - } - } - } - - if(!delInternalGeometries.empty()) { - std::stringstream stream; - - // NOTE: SketchObject delGeometries will sort the array, so filling it here with a reverse iterator would - // lead to the worst case scenario for sorting. - auto endit = std::prev(delInternalGeometries.end()); - for (auto it = delInternalGeometries.begin(); it != endit; ++it) { - stream << *it << ","; - } - - stream << *endit; - - try { - Gui::cmdAppObjectArgs(getObject(), "delGeometries([%s])", stream.str().c_str()); - } - catch (const Base::Exception& e) { - Base::Console().Error("%s\n", e.what()); - } - - stream.str(std::string()); - } - - for (rit = delExternalGeometries.rbegin(); rit != delExternalGeometries.rend(); ++rit) { - try { - Gui::cmdAppObjectArgs(getObject(), "delExternal(%i)", *rit); - } - catch (const Base::Exception& e) { - Base::Console().Error("%s\n", e.what()); - } - } - - int ret=getSketchObject()->solve(); - - if(ret!=0){ - // if the sketched could not be solved, we first redraw to update the UI geometry as - // onChanged did not update it. - UpdateSolverInformation(); - draw(false,true); - - signalConstraintsChanged(); - signalElementsChanged(); - } - - // Notes on solving and recomputing: - // - // This function is generally called from StdCmdDelete::activated - // Since 2015-05-03 that function includes a recompute at the end. - - // Since December 2018, the function is no longer called from StdCmdDelete::activated, - // as there is an event filter installed that intercepts the del key event. So now we do - // need to tidy up after ourselves again. - - if (viewProviderParameters.autoRecompute) { - Gui::Command::updateActive(); - } - else { - editCoinManager->drawConstraintIcons(); - this->updateColor(); - } - - // if in edit not delete the object - return false; - } - // if not in edit delete the whole object - return PartGui::ViewProviderPart::onDelete(subList); -} - -QIcon ViewProviderSketch::mergeColorfulOverlayIcons (const QIcon & orig) const -{ - QIcon mergedicon = orig; - - if(!getSketchObject()->FullyConstrained.getValue()) { - QPixmap px; - - static const char * const sketcher_notfullyconstrained_xpm[]={ - "9 9 3 1", - ". c None", - "# c #dbaf00", - "a c #ffcc00", - "##.....##", - "#a#...#a#", - "#aa#.#aa#", - ".#a#.#a#.", - ".#a#.#a#.", - ".#a#.#a#.", - "#aa#.#aa#", - "#a#...#a#", - "##.....##"}; - px = QPixmap( sketcher_notfullyconstrained_xpm ); - - mergedicon = Gui::BitmapFactoryInst::mergePixmap(mergedicon, px, Gui::BitmapFactoryInst::BottomRight); - - } - - return Gui::ViewProvider::mergeColorfulOverlayIcons (mergedicon); -} - -void ViewProviderSketch::handleChangedPropertyType(Base::XMLReader& reader, - const char* TypeName, - App::Property* prop) -{ - Base::Type inputType = Base::Type::fromName(TypeName); - if (prop->getTypeId().isDerivedFrom(App::PropertyFloat::getClassTypeId()) && - inputType.isDerivedFrom(App::PropertyFloat::getClassTypeId())) { - // Do not directly call the property's Restore method in case the implementation - // has changed. So, create a temporary PropertyFloat object and assign the value. - App::PropertyFloat floatProp; - floatProp.Restore(reader); - static_cast(prop)->setValue(floatProp.getValue()); - } - else { - ViewProviderPart::handleChangedPropertyType(reader, TypeName, prop); - } -} - -void ViewProviderSketch::Restore(Base::XMLReader& reader) -{ - ViewProviderPart::Restore(reader); -} - -/*************************** functions ViewProviderSketch offers to friends such as DrawHandlerSketch ************************/ - -void ViewProviderSketch::setConstraintSelectability(bool enabled /*= true*/) -{ - editCoinManager->setConstraintSelectability(enabled); -} - -void ViewProviderSketch::setPositionText(const Base::Vector2d &Pos, const SbString &text) -{ - editCoinManager->setPositionText(Pos,text); -} - -void ViewProviderSketch::setPositionText(const Base::Vector2d &Pos) -{ - editCoinManager->setPositionText(Pos); -} - -void ViewProviderSketch::resetPositionText() -{ - editCoinManager->resetPositionText(); -} - -void ViewProviderSketch::setPreselectPoint(int PreselectPoint) -{ - preselection.PreselectPoint = PreselectPoint; - preselection.PreselectCurve = Preselection::InvalidCurve; - preselection.PreselectCross = Preselection::Axes::None;; - preselection.PreselectConstraintSet.clear(); -} - -void ViewProviderSketch::setPreselectRootPoint() -{ - preselection.PreselectPoint = Preselection::InvalidPoint; - preselection.PreselectCurve = Preselection::InvalidCurve; - preselection.PreselectCross = Preselection::Axes::RootPoint; - preselection.PreselectConstraintSet.clear(); -} - - -void ViewProviderSketch::resetPreselectPoint() -{ - preselection.PreselectPoint = Preselection::InvalidPoint; - preselection.PreselectCurve = Preselection::InvalidCurve; - preselection.PreselectCross = Preselection::Axes::None;; - preselection.PreselectConstraintSet.clear(); -} - -void ViewProviderSketch::addSelectPoint(int SelectPoint) -{ - selection.SelPointSet.insert(SelectPoint); -} - -void ViewProviderSketch::removeSelectPoint(int SelectPoint) -{ - selection.SelPointSet.erase(SelectPoint); -} - -void ViewProviderSketch::clearSelectPoints() -{ - selection.SelPointSet.clear(); -} - -bool ViewProviderSketch::isSelected(const std::string &subNameSuffix) const -{ - return Gui::Selection().isSelected(editDocName.c_str(), editObjName.c_str(), (editSubName+subNameSuffix).c_str()); -} - -void ViewProviderSketch::rmvSelection(const std::string &subNameSuffix) -{ - Gui::Selection().rmvSelection(editDocName.c_str(), editObjName.c_str(), (editSubName+subNameSuffix).c_str()); -} - -bool ViewProviderSketch::addSelection(const std::string &subNameSuffix, float x, float y, float z) -{ - return Gui::Selection().addSelection(editDocName.c_str(), editObjName.c_str(), (editSubName+subNameSuffix).c_str(), x , y, z); -} - -bool ViewProviderSketch::addSelection2(const std::string &subNameSuffix, float x, float y, float z) -{ - return Gui::Selection().addSelection2(editDocName.c_str(), editObjName.c_str(), (editSubName+subNameSuffix).c_str(), x , y, z); -} - -bool ViewProviderSketch::setPreselect(const std::string &subNameSuffix, float x, float y, float z) -{ - return Gui::Selection().setPreselect(editDocName.c_str(), editObjName.c_str(), (editSubName+subNameSuffix).c_str(), x , y, z); -} - -/*************************** private functions to decouple Attorneys and Clients ********************************************/ - -// Establishes a private collaboration interface with EditModeCoinManager to perform EditModeCoinManager tasks, while abstracting EditModeCoinManager -// from the specific ViewProviderSketch implementation, while allowing ViewProviderSketch to fully delegate coin management. - -const std::vector ViewProviderSketch::getConstraints() const -{ - return getSketchObject()->Constraints.getValues(); -} - -const GeoList ViewProviderSketch::getGeoList() const -{ - const std::vector tempGeo = getSketchObject()->getCompleteGeometry(); // without memory allocation - - int intGeoCount = getSketchObject()->getHighestCurveIndex() + 1; - - auto geolist = GeoList::getGeoListModel(std::move(tempGeo), intGeoCount); - - return geolist; -} - -bool ViewProviderSketch::constraintHasExpression(int constrid) const -{ - return getSketchObject()->constraintHasExpression(constrid); -} - -std::unique_ptr ViewProviderSketch::getRayPickAction() const -{ - assert(isInEditMode()); - Gui::MDIView *mdi = Gui::Application::Instance->editViewOfNode(editCoinManager->getRootEditNode()); - if (!(mdi && mdi->isDerivedFrom(Gui::View3DInventor::getClassTypeId()))) - return nullptr; - Gui::View3DInventorViewer *viewer = static_cast(mdi)->getViewer(); - - return std::make_unique(viewer->getSoRenderManager()->getViewportRegion()); -} - -SbVec2f ViewProviderSketch::getScreenCoordinates(SbVec2f sketchcoordinates) const -{ - - Base::Placement sketchPlacement = getEditingPlacement(); - Base::Vector3d sketchPos(sketchPlacement.getPosition()); - Base::Rotation sketchRot(sketchPlacement.getRotation()); - - // get global coordinates from sketcher coordinates - Base::Vector3d pos(sketchcoordinates[0], sketchcoordinates[1],0); - sketchRot.multVec(pos,pos); - pos = pos + sketchPos; - - Gui::MDIView *mdi = this->getActiveView(); - Gui::View3DInventor *view = qobject_cast(mdi); - if (!view || !isInEditMode()) - return SbVec2f(0,0); - - Gui::View3DInventorViewer* viewer = view->getViewer(); - - SoCamera* pCam = viewer->getSoRenderManager()->getCamera(); - - if (!pCam) - return SbVec2f(0,0); - - SbViewVolume vol = pCam->getViewVolume(); - Gui::ViewVolumeProjection proj(vol); - - // dimensionless [0 1] (or 1.5 see View3DInventorViewer.cpp ) - Base::Vector3d screencoords = proj(pos); - - int width = viewer->getGLWidget()->width(), - height = viewer->getGLWidget()->height(); - - if (width >= height) { - // "Landscape" orientation, to square - screencoords.x *= height; - screencoords.x += (width-height) / 2.0; - screencoords.y *= height; - } - else { - // "Portrait" orientation - screencoords.x *= width; - screencoords.y *= width; - screencoords.y += (height-width) / 2.0; - } - - SbVec2f iconCoords(screencoords.x,screencoords.y); - - return iconCoords; -} - -QFont ViewProviderSketch::getApplicationFont() const -{ - return QApplication::font(); -} - -int ViewProviderSketch::defaultFontSizePixels() const -{ - QFontMetricsF metrics(QApplication::font()); - return static_cast(metrics.height()); -} - -int ViewProviderSketch::getApplicationLogicalDPIX() const { - return int(QApplication::primaryScreen()->logicalDotsPerInchX()); -} - -int ViewProviderSketch::getViewOrientationFactor() const { - return viewOrientationFactor; -} - -double ViewProviderSketch::getRotation(SbVec3f pos0, SbVec3f pos1) const -{ - double x0,y0,x1,y1; - - Gui::MDIView *mdi = Gui::Application::Instance->editViewOfNode(editCoinManager->getRootEditNode()); - if (!(mdi && mdi->isDerivedFrom(Gui::View3DInventor::getClassTypeId()))) - return 0; - Gui::View3DInventorViewer *viewer = static_cast(mdi)->getViewer(); - SoCamera* pCam = viewer->getSoRenderManager()->getCamera(); - if (!pCam) - return 0; - - try { - SbViewVolume vol = pCam->getViewVolume(); - - getCoordsOnSketchPlane(pos0,vol.getProjectionDirection(),x0,y0); - getCoordsOnSketchPlane(pos1,vol.getProjectionDirection(),x1,y1); - - return -atan2((y1-y0),(x1-x0))*180/M_PI; - } - catch (const Base::ZeroDivisionError&) { - return 0; - } -} - - -GeoListFacade ViewProviderSketch::getGeoListFacade() const -{ - return getSketchObject()->getGeoListFacade(); -} - -bool ViewProviderSketch::isSketchInvalid() const -{ - - bool sketchinvalid = getSketchObject()->getLastHasRedundancies() || - getSketchObject()->getLastHasConflicts() || - getSketchObject()->getLastHasMalformedConstraints(); - return sketchinvalid; -} - -bool ViewProviderSketch::isSketchFullyConstrained() const -{ - return getSketchObject()->FullyConstrained.getValue(); -} - -bool ViewProviderSketch::haveConstraintsInvalidGeometry() const -{ - return getSketchObject()->Constraints.hasInvalidGeometry(); -} - -void ViewProviderSketch::addNodeToRoot(SoSeparator * node) -{ - pcRoot->addChild(node); -} - -void ViewProviderSketch::removeNodeFromRoot(SoSeparator * node) -{ - pcRoot->removeChild(node); -} - -bool ViewProviderSketch::isConstraintPreselected(int constraintId) const -{ - return preselection.PreselectConstraintSet.count(constraintId); -} - -bool ViewProviderSketch::isPointSelected(int pointId) const -{ - return selection.SelPointSet.find(pointId) != selection.SelPointSet.end(); -} - -bool ViewProviderSketch::isCurveSelected(int curveId) const -{ - return selection.SelCurvSet.find(curveId) != selection.SelCurvSet.end(); -} - -bool ViewProviderSketch::isConstraintSelected(int constraintId) const -{ - return selection.SelConstraintSet.find(constraintId) != selection.SelConstraintSet.end(); -} - -void ViewProviderSketch::executeOnSelectionPointSet(std::function && operation) const -{ - for(const auto v : selection.SelPointSet) - operation(v); -} - -bool ViewProviderSketch::isInEditMode() const -{ - return editCoinManager != nullptr; -} +/*************************************************************************** + * Copyright (c) 2009 Juergen Riegel * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +# include +# include +# include +# include +# include +# include + +# include +# include +# include +# include +# include +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Gui/ToolBarManager.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ViewProviderSketch.h" +#include "DrawSketchHandler.h" +#include "EditDatumDialog.h" +#include "EditModeCoinManager.h" +#include "TaskDlgEditSketch.h" +#include "TaskSketcherValidation.h" +#include "Utils.h" +#include "ViewProviderSketchGeometryExtension.h" + + +FC_LOG_LEVEL_INIT("Sketch",true,true) + +using namespace SketcherGui; +using namespace Sketcher; +namespace bp = boost::placeholders; + +/************** ViewProviderSketch::ParameterObserver *********************/ + +template +T getSketcherGeneralParameter(const std::string & string, T defaultvalue) { + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/General"); + + if constexpr (std::is_same_v) { + return static_cast(hGrp->GetUnsigned(string.c_str(), defaultvalue)); + } + else if constexpr (std::is_same_v) { + return static_cast(hGrp->GetInt(string.c_str(), defaultvalue)); + } +} + +ViewProviderSketch::ParameterObserver::ParameterObserver(ViewProviderSketch &client): Client(client) +{ +} + +ViewProviderSketch::ParameterObserver::~ParameterObserver() +{ + unsubscribeToParameters(); +} + +void ViewProviderSketch::ParameterObserver::updateBoolProperty(const std::string & string, App::Property * property, bool defaultvalue) +{ + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/General"); + + auto boolprop = static_cast(property); + boolprop->setValue(hGrp->GetBool(string.c_str(), defaultvalue)); +} + +void ViewProviderSketch::ParameterObserver::updateColorProperty(const std::string & string, App::Property * property, float r, float g, float b) +{ + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"); + + auto colorprop = static_cast(property); + + colorprop->setValue(r,g,b); + + App::Color elementAppColor = colorprop->getValue(); + unsigned long color = (unsigned long)(elementAppColor.getPackedValue()); + color = hGrp->GetUnsigned(string.c_str(), color); + elementAppColor.setPackedValue((uint32_t)color); + colorprop->setValue( elementAppColor); + +} + +void ViewProviderSketch::ParameterObserver::updateGridSize(const std::string & string, App::Property * property) +{ + (void) property; + (void) string; + + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/General"); + + Client.GridSize.setValue(Base::Quantity::parse(QString::fromLatin1(hGrp->GetGroup("GridSize")->GetASCII("GridSize", "10.0").c_str())).getValue()); +} + +void ViewProviderSketch::ParameterObserver::updateEscapeKeyBehaviour(const std::string & string, App::Property * property) +{ + (void) property; + (void) string; + + ParameterGrp::handle hSketch = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); + Client.viewProviderParameters.handleEscapeButton = !hSketch->GetBool("LeaveSketchWithEscape", true); +} + +void ViewProviderSketch::ParameterObserver::updateAutoRecompute(const std::string & string, App::Property * property) +{ + (void) property; + (void) string; + + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); + Client.viewProviderParameters.autoRecompute = hGrp->GetBool("AutoRecompute",false); +} + +void ViewProviderSketch::ParameterObserver::updateRecalculateInitialSolutionWhileDragging(const std::string & string, App::Property * property) +{ + (void) property; + (void) string; + + ParameterGrp::handle hGrp2 = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); + + Client.viewProviderParameters.recalculateInitialSolutionWhileDragging = hGrp2->GetBool("RecalculateInitialSolutionWhileDragging",true); +} + +void ViewProviderSketch::ParameterObserver::subscribeToParameters() +{ + try { + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/General"); + hGrp->Attach(this); + + ParameterGrp::handle hGrp2 = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); + hGrp2->Attach(this); + + ParameterGrp::handle hGrpv = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"); + hGrpv->Attach(this); + } + catch(const Base::ValueError & e) { // ensure that if parameter strings are not well-formed, the exception is not propagated + Base::Console().Error("ViewProviderSketch: Malformed parameter string: %s\n", e.what()); + } +} + +void ViewProviderSketch::ParameterObserver::unsubscribeToParameters() +{ + try { + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/General"); + hGrp->Detach(this); + + ParameterGrp::handle hGrp2 = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); + hGrp2->Detach(this); + + ParameterGrp::handle hGrpv = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"); + hGrpv->Detach(this); + } + catch(const Base::ValueError & e) { // ensure that if parameter strings are not well-formed, the exception is not propagated + Base::Console().Error("ViewProviderSketch: Malformed parameter string: %s\n", e.what()); + } +} + +void ViewProviderSketch::ParameterObserver::initParameters() +{ + // once initialize the map with the properties + + SbColor defaultGridColor(0.7f, 0.7f, 0.7f); + unsigned int packedDefaultGridColor = defaultGridColor.getPackedValue(); + + parameterMap = { + {"HideDependent", + {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, true);}, &Client.HideDependent }}, + {"ShowLinks", + {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, true);}, &Client.ShowLinks }}, + {"ShowSupport", + {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, true);}, &Client.ShowSupport }}, + {"RestoreCamera", + {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, true);}, &Client.RestoreCamera }}, + {"ForceOrtho", + {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, false);}, &Client.ForceOrtho }}, + {"SectionView", + {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, false);}, &Client.SectionView }}, + {"AutoConstraints", + {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, true);}, &Client.Autoconstraints }}, + {"AvoidRedundantAutoconstraints", + {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, true);}, &Client.AvoidRedundant }}, + {"ShowGrid", + {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, false);}, &Client.ShowGrid }}, + {"GridSnap", + {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, false);}, &Client.GridSnap }}, + {"GridAuto", + {[this](const std::string & string, App::Property * property){ updateBoolProperty(string, property, true); }, &Client.GridAuto }}, + {"GridSize", + {[this](const std::string & string, App::Property * property){ updateGridSize(string, property);}, &Client.GridSize }}, + {"SketchEdgeColor", + {[this](const std::string & string, App::Property * property){ updateColorProperty(string, property, 1.f, 1.f, 1.f);}, &Client.LineColor }}, + {"SketchVertexColor", + {[this](const std::string & string, App::Property * property){ updateColorProperty(string, property, 1.f, 1.f, 1.f);}, &Client.PointColor }}, + {"updateEscapeKeyBehaviour", + {[this](const std::string & string, App::Property * property){ updateEscapeKeyBehaviour(string, property);}, nullptr }}, + {"AutoRecompute", + {[this](const std::string & string, App::Property * property){ updateAutoRecompute(string, property);}, nullptr }}, + {"RecalculateInitialSolutionWhileDragging", + {[this](const std::string & string, App::Property * property){ updateRecalculateInitialSolutionWhileDragging(string, property);}, nullptr }}, + {"GridSizePixelThreshold", + {[this](const std::string & string, [[maybe_unused]] App::Property * property){ auto v = getSketcherGeneralParameter(string, 15); Client.setGridSizePixelThreshold(v); }, nullptr }}, + {"GridNumberSubdivision", + {[this](const std::string & string, [[maybe_unused]] App::Property * property){ auto v = getSketcherGeneralParameter(string, 10); Client.setGridSizePixelThreshold(v); }, nullptr }}, + {"GridLinePattern", + {[this](const std::string & string, [[maybe_unused]] App::Property * property){ auto v = getSketcherGeneralParameter(string, 0x0f0f); Client.setGridLinePattern(v); }, nullptr }}, + {"GridDivLinePattern", + {[this](const std::string & string, [[maybe_unused]] App::Property * property){ auto v = getSketcherGeneralParameter(string, 0xffff); Client.setGridDivLinePattern(v); }, nullptr }}, + {"GridLineWidth", + {[this](const std::string & string, [[maybe_unused]] App::Property * property){ auto v = getSketcherGeneralParameter(string, 1); Client.setGridLineWidth(v); }, nullptr }}, + {"GridDivLineWidth", + {[this](const std::string & string, [[maybe_unused]] App::Property * property){ auto v = getSketcherGeneralParameter(string, 2); Client.setGridDivLineWidth(v); }, nullptr }}, + {"GridLineColor", + {[this, packedDefaultGridColor](const std::string & string, [[maybe_unused]] App::Property * property){ auto v = getSketcherGeneralParameter(string, packedDefaultGridColor); Client.setGridLineColor(v); }, nullptr }}, + {"GridDivLineColor", + {[this, packedDefaultGridColor](const std::string & string, [[maybe_unused]] App::Property * property){ auto v = getSketcherGeneralParameter(string, packedDefaultGridColor); Client.setGridDivLineColor(v); }, nullptr }}, + }; + + for( auto & val : parameterMap){ + auto string = val.first; + auto update = std::get<0>(val.second); + auto property = std::get<1>(val.second); + + update(string, property); + } +} + +void ViewProviderSketch::ParameterObserver::OnChange(Base::Subject &rCaller, const char * sReason) +{ + (void) rCaller; + + auto key = parameterMap.find(sReason); + if( key != parameterMap.end()) { + auto string = key->first; + auto update = std::get<0>(key->second); + auto property = std::get<1>(key->second); + + update(string, property); + } + +} + +/*************************** ViewProviderSketch **************************/ + +// Struct for holding previous click information +SbTime ViewProviderSketch::DoubleClick::prvClickTime; +SbVec2s ViewProviderSketch::DoubleClick::prvClickPos; //used by double-click-detector +SbVec2s ViewProviderSketch::DoubleClick::prvCursorPos; +SbVec2s ViewProviderSketch::DoubleClick::newCursorPos; + +//************************************************************************** +// Construction/Destruction + +/* TRANSLATOR SketcherGui::ViewProviderSketch */ + +PROPERTY_SOURCE_WITH_EXTENSIONS(SketcherGui::ViewProviderSketch, PartGui::ViewProvider2DObject) + + +ViewProviderSketch::ViewProviderSketch() + : SelectionObserver(false), + Mode(STATUS_NONE), + listener(nullptr), + editCoinManager(nullptr), + pObserver(std::make_unique(*this)), + sketchHandler(nullptr), + viewOrientationFactor(1) +{ + PartGui::ViewProviderAttachExtension::initExtension(this); + PartGui::ViewProviderGridExtension::initExtension(this); + + ADD_PROPERTY_TYPE(Autoconstraints,(true),"Auto Constraints",(App::PropertyType)(App::Prop_None),"Create auto constraints"); + ADD_PROPERTY_TYPE(AvoidRedundant,(true),"Auto Constraints",(App::PropertyType)(App::Prop_None),"Avoid redundant autoconstraint"); + ADD_PROPERTY_TYPE(TempoVis,(Py::None()),"Visibility automation",(App::PropertyType)(App::Prop_ReadOnly),"Object that handles hiding and showing other objects when entering/leaving sketch."); + ADD_PROPERTY_TYPE(HideDependent,(true),"Visibility automation",(App::PropertyType)(App::Prop_ReadOnly),"If true, all objects that depend on the sketch are hidden when opening editing."); + ADD_PROPERTY_TYPE(ShowLinks,(true),"Visibility automation",(App::PropertyType)(App::Prop_ReadOnly),"If true, all objects used in links to external geometry are shown when opening sketch."); + ADD_PROPERTY_TYPE(ShowSupport,(true),"Visibility automation",(App::PropertyType)(App::Prop_ReadOnly),"If true, all objects this sketch is attached to are shown when opening sketch."); + ADD_PROPERTY_TYPE(RestoreCamera,(true),"Visibility automation",(App::PropertyType)(App::Prop_ReadOnly),"If true, camera position before entering sketch is remembered, and restored after closing it."); + ADD_PROPERTY_TYPE(ForceOrtho,(false),"Visibility automation",(App::PropertyType)(App::Prop_ReadOnly),"If true, camera type will be forced to orthographic view when entering editing mode."); + ADD_PROPERTY_TYPE(SectionView,(false),"Visibility automation",(App::PropertyType)(App::Prop_ReadOnly),"If true, only objects (or part of) located behind the sketch plane are visible."); + ADD_PROPERTY_TYPE(EditingWorkbench,("SketcherWorkbench"),"Visibility automation",(App::PropertyType)(App::Prop_ReadOnly),"Name of the workbench to activate when editing this sketch."); + + // Default values that will be overridden by preferences (if existing) + PointSize.setValue(4); + + // visibility automation and other parameters: update parameter and property defaults to follow preferences + pObserver->initParameters(); + pObserver->subscribeToParameters(); + + sPixmap = "Sketcher_Sketch"; + + //rubberband selection + rubberband = std::make_unique(); + + cameraSensor.setFunction(&ViewProviderSketch::camSensCB); +} + +ViewProviderSketch::~ViewProviderSketch() +{ +} + +void ViewProviderSketch::slotUndoDocument(const Gui::Document& /*doc*/) +{ + // Note 1: this slot is only operative during edit mode (see signal connection/disconnection) + // Note 2: ViewProviderSketch::UpdateData does not generate updates during undo/redo + // transactions as mid-transaction data may not be in a valid state (e.g. constraints + // may reference invalid geometry). However undo/redo notify SketchObject after the undo/redo + // and before this slot is called. + // Note 3: Note that recomputes are no longer inhibited during the call to this slot. + forceUpdateData(); +} + +void ViewProviderSketch::slotRedoDocument(const Gui::Document& /*doc*/) +{ + // Note 1: this slot is only operative during edit mode (see signal connection/disconnection) + // Note 2: ViewProviderSketch::UpdateData does not generate updates during undo/redo + // transactions as mid-transaction data may not be in a valid state (e.g. constraints + // may reference invalid geometry). However undo/redo notify SketchObject after the undo/redo + // and before this slot is called. + // Note 3: Note that recomputes are no longer inhibited during the call to this slot. + forceUpdateData(); +} + +void ViewProviderSketch::forceUpdateData() +{ + if(!getSketchObject()->noRecomputes) { // the sketch was already solved in SketchObject in onUndoRedoFinished + Gui::Command::updateActive(); + } +} + +/***************************** handler management ************************************/ + +void ViewProviderSketch::activateHandler(DrawSketchHandler *newHandler) +{ + assert(editCoinManager); + assert(!sketchHandler); + + sketchHandler = std::unique_ptr(newHandler); + Mode = STATUS_SKETCH_UseHandler; + sketchHandler->activate(this); + + // make sure receiver has focus so immediately pressing Escape will be handled by + // ViewProviderSketch::keyPressed() and dismiss the active handler, and not the entire + // sketcher editor + Gui::MDIView *mdi = Gui::Application::Instance->activeDocument()->getActiveView(); + mdi->setFocus(); +} + +void ViewProviderSketch::deactivateHandler() +{ + assert(isInEditMode()); + if(sketchHandler){ + sketchHandler->deactivate(); + sketchHandler = nullptr; + } + Mode = STATUS_NONE; +} + +/// removes the active handler +void ViewProviderSketch::purgeHandler() +{ + deactivateHandler(); + Gui::Selection().clearSelection(); + + // ensure that we are in sketch only selection mode + Gui::MDIView *mdi = Gui::Application::Instance->editDocument()->getActiveView(); + Gui::View3DInventorViewer *viewer; + viewer = static_cast(mdi)->getViewer(); + + SoNode* root = viewer->getSceneGraph(); + static_cast(root)->selectionRole.setValue(false); +} + +void ViewProviderSketch::setAxisPickStyle(bool on) +{ + assert(isInEditMode()); + editCoinManager->setAxisPickStyle(on); +} + +void ViewProviderSketch::moveCursorToSketchPoint(Base::Vector2d point) { + + SbVec3f sbpoint(point.x,point.y,0.f); + + Gui::MDIView *mdi = this->getActiveView(); + Gui::View3DInventor *view = qobject_cast(mdi); + + if (!view) + return; + + Gui::View3DInventorViewer* viewer = view->getViewer(); + + SbVec2s screencoords = viewer->getPointOnScreen(sbpoint); + + short x,y; screencoords.getValue(x,y); + + short height = viewer->getGLWidget()->height(); // Coin3D origin bottom left, QT origin top left + + QPoint newPos = viewer->getGLWidget()->mapToGlobal(QPoint(x,height-y)); + + + // QScreen *screen = view->windowHandle()->screen(); + //QScreen *screen = QGuiApplication::primaryScreen(); + + //QCursor::setPos(screen, newPos); + QCursor::setPos(newPos); +} + +void ViewProviderSketch::preselectAtPoint(Base::Vector2d point) +{ + if (Mode != STATUS_SELECT_Point && + Mode != STATUS_SELECT_Edge && + Mode != STATUS_SELECT_Constraint && + Mode != STATUS_SKETCH_DragPoint && + Mode != STATUS_SKETCH_DragCurve && + Mode != STATUS_SKETCH_DragConstraint && + Mode != STATUS_SKETCH_UseRubberBand) { + + SbVec3f sbpoint(point.x,point.y,0.f); + + Gui::MDIView *mdi = this->getActiveView(); + Gui::View3DInventor *view = qobject_cast(mdi); + + if (!view) + return; + + Gui::View3DInventorViewer* viewer = view->getViewer(); + + SbVec2s screencoords = viewer->getPointOnScreen(sbpoint); + + std::unique_ptr Point(this->getPointOnRay(screencoords, viewer)); + + detectAndShowPreselection(Point.get(), screencoords); + } +} + +// ********************************************************************************** + +bool ViewProviderSketch::keyPressed(bool pressed, int key) +{ + switch (key) + { + case SoKeyboardEvent::ESCAPE: + { + // make the handler quit but not the edit mode + if (isInEditMode() && sketchHandler) { + if (!pressed) + sketchHandler->quit(); + return true; + } + if (isInEditMode() && !drag.DragConstraintSet.empty()) { + if (!pressed) { + drag.DragConstraintSet.clear(); + } + return true; + } + if (isInEditMode() && drag.isDragCurveValid()) { + if (!pressed) { + getSketchObject()->movePoint(drag.DragCurve, Sketcher::PointPos::none, Base::Vector3d(0,0,0), true); + drag.DragCurve = Drag::InvalidCurve; + resetPositionText(); + Mode = STATUS_NONE; + } + return true; + } + if (isInEditMode() && drag.isDragPointValid()) { + if (!pressed) { + int GeoId; + Sketcher::PointPos PosId; + getSketchObject()->getGeoVertexIndex(drag.DragPoint, GeoId, PosId); + getSketchObject()->movePoint(GeoId, PosId, Base::Vector3d(0,0,0), true); + drag.DragPoint = Drag::InvalidPoint; + resetPositionText(); + Mode = STATUS_NONE; + } + return true; + } + if (isInEditMode()) { + // #0001479: 'Escape' key dismissing dialog cancels Sketch editing + // If we receive a button release event but not a press event before + // then ignore this one. + if (!pressed && !viewProviderParameters.buttonPress) + return true; + viewProviderParameters.buttonPress = pressed; + + // More control over Sketcher edit mode Esc key behavior + // https://forum.freecadweb.org/viewtopic.php?f=3&t=42207 + return viewProviderParameters.handleEscapeButton; + } + return false; + } + break; + default: + { + if (isInEditMode() && sketchHandler) + sketchHandler->registerPressedKey(pressed,key); + } + } + + return true; // handle all other key events +} + +void ViewProviderSketch::snapToGrid(double &x, double &y) +{ + if (GridSnap.getValue() && ShowGrid.getValue()) { + // Snap Tolerance in pixels + const double snapTol = GridSize.getValue() / 5; + + double tmpX = x, tmpY = y; + + // Find Nearest Snap points + tmpX = tmpX / GridSize.getValue(); + tmpX = tmpX < 0.0 ? ceil(tmpX - 0.5) : floor(tmpX + 0.5); + tmpX *= GridSize.getValue(); + + tmpY = tmpY / GridSize.getValue(); + tmpY = tmpY < 0.0 ? ceil(tmpY - 0.5) : floor(tmpY + 0.5); + tmpY *= GridSize.getValue(); + + // Check if x within snap tolerance + if (x < tmpX + snapTol && x > tmpX - snapTol) + x = tmpX; // Snap X Mouse Position + + // Check if y within snap tolerance + if (y < tmpY + snapTol && y > tmpY - snapTol) + y = tmpY; // Snap Y Mouse Position + } +} + +void ViewProviderSketch::getProjectingLine(const SbVec2s& pnt, const Gui::View3DInventorViewer *viewer, SbLine& line) const +{ + const SbViewportRegion& vp = viewer->getSoRenderManager()->getViewportRegion(); + + short x, y; + pnt.getValue(x,y); + SbVec2f VPsize = vp.getViewportSize(); + float dX, dY; + VPsize.getValue(dX, dY); + + float fRatio = vp.getViewportAspectRatio(); + float pX = (float)x / float(vp.getViewportSizePixels()[0]); + float pY = (float)y / float(vp.getViewportSizePixels()[1]); + + // now calculate the real points respecting aspect ratio information + // + if (fRatio > 1.0f) { + pX = (pX - 0.5f * dX) * fRatio + 0.5f * dX; + } + else if (fRatio < 1.0f) { + pY = (pY - 0.5f * dY) / fRatio + 0.5f * dY; + } + + SoCamera* pCam = viewer->getSoRenderManager()->getCamera(); + if (!pCam) + return; + SbViewVolume vol = pCam->getViewVolume(); + + vol.projectPointToLine(SbVec2f(pX, pY), line); +} + +Base::Placement ViewProviderSketch::getEditingPlacement() const { + auto doc = Gui::Application::Instance->editDocument(); + if (!doc || doc->getInEdit() != this) + return getSketchObject()->globalPlacement(); + + // TODO: won't work if there is scale. Hmm... what to do... + return Base::Placement(doc->getEditingTransform()); +} + +void ViewProviderSketch::getCoordsOnSketchPlane(const SbVec3f &point, const SbVec3f &normal, double &u, double &v) const +{ + // Plane form + Base::Vector3d R0(0, 0, 0), RN(0, 0, 1), RX(1, 0, 0), RY(0, 1, 0); + + // move to position of Sketch + Base::Placement Plz = getEditingPlacement(); + R0 = Plz.getPosition(); + Base::Rotation tmp(Plz.getRotation()); + tmp.multVec(RN, RN); + tmp.multVec(RX, RX); + tmp.multVec(RY, RY); + Plz.setRotation(tmp); + + // line + Base::Vector3d R1(point[0], point[1], point[2]), RA(normal[0], normal[1], normal[2]); + if (fabs(RN*RA) < FLT_EPSILON) + throw Base::ZeroDivisionError("View direction is parallel to sketch plane"); + // intersection point on plane + Base::Vector3d S = R1 + ((RN * (R0 - R1)) / (RN * RA)) * RA; + + // distance to x Axle of the sketch + S.TransformToCoordinateSystem(R0, RX, RY); + + u = S.x; + v = S.y; +} + +bool ViewProviderSketch::mouseButtonPressed(int Button, bool pressed, const SbVec2s &cursorPos, + const Gui::View3DInventorViewer *viewer) +{ + assert(isInEditMode()); + + // Calculate 3d point to the mouse position + SbLine line; + getProjectingLine(cursorPos, viewer, line); + SbVec3f point = line.getPosition(); + SbVec3f normal = line.getDirection(); + + // use scoped_ptr to make sure that instance gets deleted in all cases + boost::scoped_ptr pp(this->getPointOnRay(cursorPos, viewer)); + + // Radius maximum to allow double click event + const int dblClickRadius = 5; + + double x,y; + SbVec3f pos = point; + if (pp) { + const SoDetail *detail = pp->getDetail(); + if (detail && detail->getTypeId() == SoPointDetail::getClassTypeId()) { + pos = pp->getPoint(); + } + } + + try { + getCoordsOnSketchPlane(pos,normal,x,y); + snapToGrid(x, y); + } + catch (const Base::ZeroDivisionError&) { + return false; + } + + // Left Mouse button **************************************************** + if (Button == 1) { + if (pressed) { + // Do things depending on the mode of the user interaction + switch (Mode) { + case STATUS_NONE:{ + bool done=false; + if (preselection.isPreselectPointValid()) { + //Base::Console().Log("start dragging, point:%d\n",this->DragPoint); + Mode = STATUS_SELECT_Point; + done = true; + } else if (preselection.isPreselectCurveValid()) { + //Base::Console().Log("start dragging, point:%d\n",this->DragPoint); + Mode = STATUS_SELECT_Edge; + done = true; + } else if (preselection.isCrossPreselected()) { + //Base::Console().Log("start dragging, point:%d\n",this->DragPoint); + Mode = STATUS_SELECT_Cross; + done = true; + } else if (!preselection.PreselectConstraintSet.empty()) { + //Base::Console().Log("start dragging, point:%d\n",this->DragPoint); + Mode = STATUS_SELECT_Constraint; + done = true; + } + + // Double click events variables + float dci = (float) QApplication::doubleClickInterval()/1000.0f; + + if (done && + SbVec2f(cursorPos - DoubleClick::prvClickPos).length() < dblClickRadius && + (SbTime::getTimeOfDay() - DoubleClick::prvClickTime).getValue() < dci) { + + // Double Click Event Occurred + editDoubleClicked(); + // Reset Double Click Static Variables + DoubleClick::prvClickTime = SbTime(); + DoubleClick::prvClickPos = SbVec2s(-16000,-16000); //certainly far away from any clickable place, to avoid re-trigger of double-click if next click happens fast. + + Mode = STATUS_NONE; + } else { + DoubleClick::prvClickTime = SbTime::getTimeOfDay(); + DoubleClick::prvClickPos = cursorPos; + DoubleClick::prvCursorPos = cursorPos; + DoubleClick::newCursorPos = cursorPos; + if (!done) + Mode = STATUS_SKETCH_StartRubberBand; + } + + return done; + } + case STATUS_SKETCH_UseHandler: + return sketchHandler->pressButton(Base::Vector2d(x,y)); + default: + return false; + } + } else { // Button 1 released + // Do things depending on the mode of the user interaction + switch (Mode) { + case STATUS_SELECT_Point: + if (pp) { + //Base::Console().Log("Select Point:%d\n",this->DragPoint); + // Do selection + std::stringstream ss; + ss << "Vertex" << preselection.getPreselectionVertexIndex(); + + if (isSelected(ss.str())) { + rmvSelection(ss.str()); + } else { + addSelection2(ss.str(), pp->getPoint()[0], pp->getPoint()[1],pp->getPoint()[2]); + drag.resetIds(); + } + } + Mode = STATUS_NONE; + return true; + case STATUS_SELECT_Edge: + if (pp) { + //Base::Console().Log("Select Point:%d\n",this->DragPoint); + std::stringstream ss; + if (preselection.isEdge()) + ss << "Edge" << preselection.getPreselectionEdgeIndex(); + else // external geometry + ss << "ExternalEdge" << preselection.getPreselectionExternalEdgeIndex(); + + // If edge already selected move from selection + if (isSelected(ss.str()) ) { + rmvSelection(ss.str()); + } else { + // Add edge to the selection + addSelection2(ss.str(), pp->getPoint()[0], pp->getPoint()[1], pp->getPoint()[2]); + drag.resetIds(); + } + } + Mode = STATUS_NONE; + return true; + case STATUS_SELECT_Cross: + if (pp) { + //Base::Console().Log("Select Point:%d\n",this->DragPoint); + std::stringstream ss; + switch(preselection.PreselectCross){ + case Preselection::Axes::RootPoint: ss << "RootPoint" ; break; + case Preselection::Axes::HorizontalAxis: ss << "H_Axis" ; break; + case Preselection::Axes::VerticalAxis: ss << "V_Axis" ; break; + default: break; + } + + // If cross already selected move from selection + if (isSelected(ss.str()) ) { + rmvSelection(ss.str()); + } else { + // Add cross to the selection + addSelection2(ss.str(), pp->getPoint()[0], pp->getPoint()[1], pp->getPoint()[2]); + drag.resetIds(); + } + } + Mode = STATUS_NONE; + return true; + case STATUS_SELECT_Constraint: + if (pp) { + auto sels = preselection.PreselectConstraintSet; + for(int id : sels) { + std::stringstream ss; + ss << Sketcher::PropertyConstraintList::getConstraintName(id); + + // If the constraint already selected remove + if (isSelected(ss.str()) ) { + rmvSelection(ss.str()); + } else { + // Add constraint to current selection + addSelection2(ss.str(), pp->getPoint()[0], pp->getPoint()[1], pp->getPoint()[2]); + drag.resetIds(); + } + } + } + Mode = STATUS_NONE; + return true; + case STATUS_SKETCH_DragPoint: + if (drag.isDragPointValid()) { + int GeoId; + Sketcher::PointPos PosId; + + getSketchObject()->getGeoVertexIndex(drag.DragPoint, GeoId, PosId); + + if (GeoId != Sketcher::GeoEnum::GeoUndef && PosId != Sketcher::PointPos::none) { + getDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Drag Point")); + try { + Gui::cmdAppObjectArgs(getObject(), "movePoint(%i,%i,App.Vector(%f,%f,0),%i)" + ,GeoId, static_cast(PosId), x-drag.xInit, y-drag.yInit, 0); + + getDocument()->commitCommand(); + + tryAutoRecomputeIfNotSolve(getSketchObject()); + } + catch (const Base::Exception& e) { + getDocument()->abortCommand(); + Base::Console().Error("Drag point: %s\n", e.what()); + } + } + setPreselectPoint(drag.DragPoint); + drag.DragPoint = Drag::InvalidPoint; + //updateColor(); + } + resetPositionText(); + Mode = STATUS_NONE; + return true; + case STATUS_SKETCH_DragCurve: + if (drag.isDragCurveValid()) { + const Part::Geometry *geo = getSketchObject()->getGeometry(drag.DragCurve); + if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId() || + geo->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() || + geo->getTypeId() == Part::GeomCircle::getClassTypeId() || + geo->getTypeId() == Part::GeomEllipse::getClassTypeId()|| + geo->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()|| + geo->getTypeId() == Part::GeomArcOfParabola::getClassTypeId()|| + geo->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId()|| + geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { + getDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Drag Curve")); + + auto geo = getSketchObject()->getGeometry(drag.DragCurve); + auto gf = GeometryFacade::getFacade(geo); + + Base::Vector3d vec(x-drag.xInit,y-drag.yInit,0); + + // BSpline weights have a radius corresponding to the weight value + // However, in order for them proportional to the B-Spline size, + // the scenograph has a size scalefactor times the weight + // This code normalizes the information sent to the solver. + if(gf->getInternalType() == InternalType::BSplineControlPoint) { + auto circle = static_cast(geo); + Base::Vector3d center = circle->getCenter(); + + Base::Vector3d dir = vec - center; + + double scalefactor = 1.0; + + if(circle->hasExtension(SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId())) + { + auto vpext = std::static_pointer_cast( + circle->getExtension(SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId()).lock()); + + scalefactor = vpext->getRepresentationFactor(); + } + + vec = center + dir / scalefactor; + } + + try { + Gui::cmdAppObjectArgs(getObject(), "movePoint(%i,%i,App.Vector(%f,%f,0),%i)" + ,drag.DragCurve, static_cast(Sketcher::PointPos::none), vec.x, vec.y, drag.relative ? 1 : 0); + + getDocument()->commitCommand(); + + tryAutoRecomputeIfNotSolve(getSketchObject()); + } + catch (const Base::Exception& e) { + getDocument()->abortCommand(); + Base::Console().Error("Drag curve: %s\n", e.what()); + } + } + preselection.PreselectCurve = drag.DragCurve; + drag.DragCurve = Drag::InvalidCurve; + //updateColor(); + } + resetPositionText(); + Mode = STATUS_NONE; + return true; + case STATUS_SKETCH_DragConstraint: + if (!drag.DragConstraintSet.empty()) { + getDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Drag Constraint")); + auto idset = drag.DragConstraintSet; + for(int id : idset) { + moveConstraint(id, Base::Vector2d(x, y)); + //updateColor(); + } + preselection.PreselectConstraintSet = drag.DragConstraintSet; + drag.DragConstraintSet.clear(); + getDocument()->commitCommand(); + } + Mode = STATUS_NONE; + return true; + case STATUS_SKETCH_StartRubberBand: // a single click happened, so clear selection unless user hold control. + if (!(QApplication::keyboardModifiers() & Qt::ControlModifier)) { + Gui::Selection().clearSelection(); + } + Mode = STATUS_NONE; + return true; + case STATUS_SKETCH_UseRubberBand: + doBoxSelection(DoubleClick::prvCursorPos, cursorPos, viewer); + rubberband->setWorking(false); + + // a redraw is required in order to clear the rubberband + draw(true,false); + const_cast(viewer)->redraw(); + Mode = STATUS_NONE; + return true; + case STATUS_SKETCH_UseHandler: { + return sketchHandler->releaseButton(Base::Vector2d(x,y)); + } + case STATUS_NONE: + default: + return false; + } + } + } + // Right mouse button **************************************************** + else if (Button == 2) { + if (!pressed) { + switch (Mode) { + case STATUS_SKETCH_UseHandler: + // make the handler quit + sketchHandler->quit(); + return true; + case STATUS_NONE: + { + // A right click shouldn't change the Edit Mode + if (preselection.isPreselectPointValid()) { + return true; + } else if (preselection.isPreselectCurveValid()) { + return true; + } else if (!preselection.PreselectConstraintSet.empty()) { + return true; + } else { + Gui::MenuItem geom; + geom.setCommand("Sketcher geoms"); + geom << "Sketcher_CreatePoint" + << "Sketcher_CreateArc" + << "Sketcher_Create3PointArc" + << "Sketcher_CreateCircle" + << "Sketcher_Create3PointCircle" + << "Sketcher_CreateLine" + << "Sketcher_CreatePolyline" + << "Sketcher_CreateRectangle" + << "Sketcher_CreateHexagon" + << "Sketcher_CreateFillet" + << "Sketcher_CreatePointFillet" + << "Sketcher_Trimming" + << "Sketcher_Extend" + << "Sketcher_External" + << "Sketcher_ToggleConstruction" + /*<< "Sketcher_CreateText"*/ + /*<< "Sketcher_CreateDraftLine"*/ + << "Separator"; + + Gui::Application::Instance->setupContextMenu("View", &geom); + //Create the Context Menu using the Main View Qt Widget + QMenu contextMenu(viewer->getGLWidget()); + Gui::MenuManager::getInstance()->setupContextMenu(&geom, contextMenu); + contextMenu.exec(QCursor::pos()); + + return true; + } + } + case STATUS_SELECT_Point: + break; + case STATUS_SELECT_Edge: + { + Gui::MenuItem geom; + geom.setCommand("Sketcher constraints"); + geom << "Sketcher_ConstrainVertical" + << "Sketcher_ConstrainHorizontal"; + + // Gets a selection vector + std::vector selection = Gui::Selection().getSelectionEx(); + + bool rightClickOnSelectedLine = false; + + /* + * Add Multiple Line Constraints to the menu + */ + // only one sketch with its subelements are allowed to be selected + if (selection.size() == 1) { + // get the needed lists and objects + const std::vector &SubNames = selection[0].getSubNames(); + + // Two Objects are selected + if (SubNames.size() == 2) { + // go through the selected subelements + for (std::vector::const_iterator it=SubNames.begin(); + it!=SubNames.end();++it) { + + // If the object selected is of type edge + if (it->size() > 4 && it->substr(0,4) == "Edge") { + // Get the index of the object selected + int GeoId = std::atoi(it->substr(4,4000).c_str()) - 1; + if (preselection.PreselectCurve == GeoId) + rightClickOnSelectedLine = true; + } else { + // The selection is not exclusively edges + rightClickOnSelectedLine = false; + } + } // End of Iteration + } + } + + if (rightClickOnSelectedLine) { + geom << "Sketcher_ConstrainParallel" + << "Sketcher_ConstrainPerpendicular"; + } + + Gui::Application::Instance->setupContextMenu("View", &geom); + //Create the Context Menu using the Main View Qt Widget + QMenu contextMenu(viewer->getGLWidget()); + Gui::MenuManager::getInstance()->setupContextMenu(&geom, contextMenu); + contextMenu.exec(QCursor::pos()); + + return true; + } + case STATUS_SELECT_Cross: + case STATUS_SELECT_Constraint: + case STATUS_SKETCH_DragPoint: + case STATUS_SKETCH_DragCurve: + case STATUS_SKETCH_DragConstraint: + case STATUS_SKETCH_StartRubberBand: + case STATUS_SKETCH_UseRubberBand: + break; + } + } + } + + return false; +} + +bool ViewProviderSketch::mouseWheelEvent(int delta, const SbVec2s &cursorPos, const Gui::View3DInventorViewer* viewer) +{ + assert(isInEditMode()); + + Q_UNUSED(delta); + Q_UNUSED(cursorPos); + Q_UNUSED(viewer); + + editCoinManager->drawConstraintIcons(); + + return true; +} + +void ViewProviderSketch::editDoubleClicked() +{ + if (preselection.isPreselectPointValid()) { + Base::Console().Log("double click point:%d\n",preselection.PreselectPoint); + } + else if (preselection.isPreselectCurveValid()) { + Base::Console().Log("double click edge:%d\n",preselection.PreselectCurve); + } + else if (preselection.isCrossPreselected()) { + Base::Console().Log("double click cross:%d\n",preselection.PreselectCross); + } + else if (!preselection.PreselectConstraintSet.empty()) { + // Find the constraint + const std::vector &constrlist = getSketchObject()->Constraints.getValues(); + + auto sels = preselection.PreselectConstraintSet; + for(int id : sels) { + + Constraint *Constr = constrlist[id]; + + // if its the right constraint + if (Constr->isDimensional()) { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Modify sketch constraints")); + EditDatumDialog editDatumDialog(this, id); + editDatumDialog.exec(); + } + } + } +} + +bool ViewProviderSketch::mouseMove(const SbVec2s &cursorPos, Gui::View3DInventorViewer *viewer) +{ + // maximum radius for mouse moves when selecting a geometry before switching to drag mode + const int dragIgnoredDistance = 3; + + static bool selectableConstraints = true; + + if (Mode < STATUS_SKETCH_UseHandler) { + bool tmpSelCons = QApplication::keyboardModifiers() & Qt::ShiftModifier; + if (tmpSelCons != !selectableConstraints) { + selectableConstraints = !tmpSelCons; + editCoinManager->setConstraintSelectability(selectableConstraints); + } + } + + if (!isInEditMode()) + return false; + + // ignore small moves after selection + switch (Mode) { + case STATUS_SELECT_Point: + case STATUS_SELECT_Edge: + case STATUS_SELECT_Constraint: + case STATUS_SKETCH_StartRubberBand: + short dx, dy; + (cursorPos - DoubleClick::prvCursorPos).getValue(dx, dy); + if(std::abs(dx) < dragIgnoredDistance && std::abs(dy) < dragIgnoredDistance) + return false; + default: + break; + } + + // Calculate 3d point to the mouse position + SbLine line; + getProjectingLine(cursorPos, viewer, line); + + double x,y; + try { + getCoordsOnSketchPlane(line.getPosition(),line.getDirection(),x,y); + snapToGrid(x, y); + } + catch (const Base::ZeroDivisionError&) { + return false; + } + + bool preselectChanged = false; + if (Mode != STATUS_SELECT_Point && + Mode != STATUS_SELECT_Edge && + Mode != STATUS_SELECT_Constraint && + Mode != STATUS_SKETCH_DragPoint && + Mode != STATUS_SKETCH_DragCurve && + Mode != STATUS_SKETCH_DragConstraint && + Mode != STATUS_SKETCH_UseRubberBand) { + + std::unique_ptr Point(this->getPointOnRay(cursorPos, viewer)); + + preselectChanged = detectAndShowPreselection(Point.get(), cursorPos); + } + + switch (Mode) { + case STATUS_NONE: + if (preselectChanged) { + editCoinManager->drawConstraintIcons(); + this->updateColor(); + return true; + } + return false; + case STATUS_SELECT_Point: + if (!getSolvedSketch().hasConflicts() && + preselection.isPreselectPointValid() && drag.DragPoint != preselection.PreselectPoint) { + Mode = STATUS_SKETCH_DragPoint; + drag.DragPoint = preselection.PreselectPoint; + int GeoId; + Sketcher::PointPos PosId; + + getSketchObject()->getGeoVertexIndex(drag.DragPoint, GeoId, PosId); + + if (GeoId != Sketcher::GeoEnum::GeoUndef && PosId != Sketcher::PointPos::none) { + getSketchObject()->initTemporaryMove(GeoId, PosId, false); + drag.resetVector(); + } + } else { + Mode = STATUS_NONE; + } + resetPreselectPoint(); + return true; + case STATUS_SELECT_Edge: + if (!getSolvedSketch().hasConflicts() && + preselection.isPreselectCurveValid() && drag.DragCurve != preselection.PreselectCurve) { + Mode = STATUS_SKETCH_DragCurve; + drag.DragCurve = preselection.PreselectCurve; + const Part::Geometry *geo = getSketchObject()->getGeometry(drag.DragCurve); + + // BSpline Control points are edge draggable only if their radius is movable + // This is because dragging gives unwanted cosmetic results due to the scale ratio. + // This is an heuristic as it does not check all indirect routes. + if(GeometryFacade::isInternalType(geo, InternalType::BSplineControlPoint)) { + if(geo->hasExtension(Sketcher::SolverGeometryExtension::getClassTypeId())) { + auto solvext = std::static_pointer_cast( + geo->getExtension(Sketcher::SolverGeometryExtension::getClassTypeId()).lock()); + + // Edge parameters are Independent, so weight won't move + if(solvext->getEdge()==Sketcher::SolverGeometryExtension::Independent) { + Mode = STATUS_NONE; + return false; + } + + // The B-Spline is constrained to be non-rational (equal weights), moving produces a bad effect + // because OCCT will normalize the values of the weights. + auto grp = getSolvedSketch().getDependencyGroup(drag.DragCurve, Sketcher::PointPos::none); + + int bsplinegeoid = -1; + + std::vector polegeoids; + + for( auto c : getSketchObject()->Constraints.getValues()) { + if( c->Type == Sketcher::InternalAlignment && + c->AlignmentType == BSplineControlPoint && + c->First == drag.DragCurve ) { + + bsplinegeoid = c->Second; + break; + } + } + + if(bsplinegeoid == -1) { + Mode = STATUS_NONE; + return false; + } + + for( auto c : getSketchObject()->Constraints.getValues()) { + if( c->Type == Sketcher::InternalAlignment && + c->AlignmentType == BSplineControlPoint && + c->Second == bsplinegeoid ) { + + polegeoids.push_back(c->First); + } + } + + bool allingroup = true; + + for( auto polegeoid : polegeoids ) { + std::pair< int, Sketcher::PointPos > thispole = std::make_pair(polegeoid,Sketcher::PointPos::none); + + if(grp.find(thispole) == grp.end()) // not found + allingroup = false; + } + + if(allingroup) { // it is constrained to be non-rational + Mode = STATUS_NONE; + return false; + } + + } + + } + + if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId() || + geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { + drag.relative = true; + + // Since the cursor moved from where it was clicked, and this is a relative move, + // calculate the click position and use it as initial point. + SbLine line2; + getProjectingLine(DoubleClick::prvCursorPos, viewer, line2); + getCoordsOnSketchPlane(line2.getPosition(),line2.getDirection(),drag.xInit,drag.yInit); + snapToGrid(drag.xInit, drag.yInit); + } else { + drag.resetVector(); + } + + if (geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { + getSketchObject()->initTemporaryBSplinePieceMove( + drag.DragCurve, Sketcher::PointPos::none, + Base::Vector3d(drag.xInit, drag.yInit, 0.0), false); + } else { + getSketchObject()->initTemporaryMove(drag.DragCurve, Sketcher::PointPos::none, false); + } + } else { + Mode = STATUS_NONE; + } + resetPreselectPoint(); + return true; + case STATUS_SELECT_Constraint: + Mode = STATUS_SKETCH_DragConstraint; + drag.DragConstraintSet = preselection.PreselectConstraintSet; + resetPreselectPoint(); + return true; + case STATUS_SKETCH_DragPoint: + if (drag.isDragPointValid()) { + //Base::Console().Log("Drag Point:%d\n",edit->DragPoint); + int GeoId; + Sketcher::PointPos PosId; + getSketchObject()->getGeoVertexIndex(drag.DragPoint, GeoId, PosId); + Base::Vector3d vec(x,y,0); + + if (GeoId != Sketcher::GeoEnum::GeoUndef && PosId != Sketcher::PointPos::none) { + if (getSketchObject()->moveTemporaryPoint(GeoId, PosId, vec, false) == 0) { + setPositionText(Base::Vector2d(x,y)); + draw(true,false); + } + } + } + return true; + case STATUS_SKETCH_DragCurve: + if (drag.isDragCurveValid()) { + auto geo = getSketchObject()->getGeometry(drag.DragCurve); + auto gf = GeometryFacade::getFacade(geo); + + Base::Vector3d vec(x-drag.xInit,y-drag.yInit,0); + + // BSpline weights have a radius corresponding to the weight value + // However, in order for them proportional to the B-Spline size, + // the scenograph has a size scalefactor times the weight + // This code normalizes the information sent to the solver. + if(gf->getInternalType() == InternalType::BSplineControlPoint) { + auto circle = static_cast(geo); + Base::Vector3d center = circle->getCenter(); + + Base::Vector3d dir = vec - center; + + double scalefactor = 1.0; + + if(circle->hasExtension(SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId())) + { + auto vpext = std::static_pointer_cast( + circle->getExtension(SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId()).lock()); + + scalefactor = vpext->getRepresentationFactor(); + } + + vec = center + dir / scalefactor; + } + + if (getSketchObject()->moveTemporaryPoint(drag.DragCurve, Sketcher::PointPos::none, vec, drag.relative) == 0) { + setPositionText(Base::Vector2d(x,y)); + draw(true,false); + } + } + return true; + case STATUS_SKETCH_DragConstraint: + if (!drag.DragConstraintSet.empty()) { + auto idset = drag.DragConstraintSet; + for(int id : idset) + moveConstraint(id, Base::Vector2d(x,y)); + } + return true; + case STATUS_SKETCH_UseHandler: + sketchHandler->mouseMove(Base::Vector2d(x,y)); + if (preselectChanged) { + editCoinManager->drawConstraintIcons(); + this->updateColor(); + } + return true; + case STATUS_SKETCH_StartRubberBand: { + Mode = STATUS_SKETCH_UseRubberBand; + rubberband->setWorking(true); + return true; + } + case STATUS_SKETCH_UseRubberBand: { + // Here we must use the device-pixel-ratio to compute the correct y coordinate (#0003130) + qreal dpr = viewer->getGLWidget()->devicePixelRatioF(); + DoubleClick::newCursorPos = cursorPos; + rubberband->setCoords(DoubleClick::prvCursorPos.getValue()[0], + viewer->getGLWidget()->height()*dpr - DoubleClick::prvCursorPos.getValue()[1], + DoubleClick::newCursorPos.getValue()[0], + viewer->getGLWidget()->height()*dpr - DoubleClick::newCursorPos.getValue()[1]); + viewer->redraw(); + return true; + } + default: + return false; + } + + return false; +} + +void ViewProviderSketch::moveConstraint(int constNum, const Base::Vector2d &toPos) +{ + // are we in edit? + if (!isInEditMode()) + return; + + const std::vector &constrlist = getSketchObject()->Constraints.getValues(); + Constraint *Constr = constrlist[constNum]; + +#ifdef FC_DEBUG + int intGeoCount = getSketchObject()->getHighestCurveIndex() + 1; + int extGeoCount = getSketchObject()->getExternalGeometryCount(); +#endif + + // with memory allocation + const std::vector geomlist = getSolvedSketch().extractGeometry(true, true); + +#ifdef FC_DEBUG + assert(int(geomlist.size()) == extGeoCount + intGeoCount); + assert((Constr->First >= -extGeoCount && Constr->First < intGeoCount) + || Constr->First != GeoEnum::GeoUndef); +#endif + + if (Constr->Type == Distance || Constr->Type == DistanceX || Constr->Type == DistanceY || + Constr->Type == Radius || Constr->Type == Diameter || Constr-> Type == Weight) { + + Base::Vector3d p1(0.,0.,0.), p2(0.,0.,0.); + if (Constr->SecondPos != Sketcher::PointPos::none) { // point to point distance + p1 = getSolvedSketch().getPoint(Constr->First, Constr->FirstPos); + p2 = getSolvedSketch().getPoint(Constr->Second, Constr->SecondPos); + } else if (Constr->Second != GeoEnum::GeoUndef) { // point to line distance + p1 = getSolvedSketch().getPoint(Constr->First, Constr->FirstPos); + const Part::Geometry *geo = GeoList::getGeometryFromGeoId (geomlist, Constr->Second); + if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + const Part::GeomLineSegment *lineSeg = static_cast(geo); + Base::Vector3d l2p1 = lineSeg->getStartPoint(); + Base::Vector3d l2p2 = lineSeg->getEndPoint(); + // calculate the projection of p1 onto line2 + p2.ProjectToLine(p1-l2p1, l2p2-l2p1); + p2 += p1; + } else + return; + } else if (Constr->FirstPos != Sketcher::PointPos::none) { + p2 = getSolvedSketch().getPoint(Constr->First, Constr->FirstPos); + } else if (Constr->First != GeoEnum::GeoUndef) { + const Part::Geometry *geo = GeoList::getGeometryFromGeoId (geomlist, Constr->First); + if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + const Part::GeomLineSegment *lineSeg = static_cast(geo); + p1 = lineSeg->getStartPoint(); + p2 = lineSeg->getEndPoint(); + } else if (geo->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { + const Part::GeomArcOfCircle *arc = static_cast(geo); + double radius = arc->getRadius(); + Base::Vector3d center = arc->getCenter(); + p1 = center; + + double angle = Constr->LabelPosition; + if (angle == 10) { + double startangle, endangle; + arc->getRange(startangle, endangle, /*emulateCCW=*/true); + angle = (startangle + endangle)/2; + } + else { + Base::Vector3d tmpDir = Base::Vector3d(toPos.x, toPos.y, 0) - p1; + angle = atan2(tmpDir.y, tmpDir.x); + } + + if(Constr->Type == Sketcher::Diameter) + p1 = center - radius * Base::Vector3d(cos(angle),sin(angle),0.); + + p2 = center + radius * Base::Vector3d(cos(angle),sin(angle),0.); + } + else if (geo->getTypeId() == Part::GeomCircle::getClassTypeId()) { + const Part::GeomCircle *circle = static_cast(geo); + double radius = circle->getRadius(); + Base::Vector3d center = circle->getCenter(); + p1 = center; + + Base::Vector3d tmpDir = Base::Vector3d(toPos.x, toPos.y, 0) - p1; + + Base::Vector3d dir = radius * tmpDir.Normalize(); + + if(Constr->Type == Sketcher::Diameter) + p1 = center - dir; + + if(Constr->Type == Sketcher::Weight) { + + double scalefactor = 1.0; + + if(circle->hasExtension(SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId())) + { + auto vpext = std::static_pointer_cast( + circle->getExtension(SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId()).lock()); + + scalefactor = vpext->getRepresentationFactor(); + } + + p2 = center + dir * scalefactor; + + } + else + p2 = center + dir; + } + else + return; + } else + return; + + Base::Vector3d vec = Base::Vector3d(toPos.x, toPos.y, 0) - p2; + + Base::Vector3d dir; + if (Constr->Type == Distance || Constr->Type == Radius || Constr->Type == Diameter || Constr->Type == Weight) + dir = (p2-p1).Normalize(); + else if (Constr->Type == DistanceX) + dir = Base::Vector3d( (p2.x - p1.x >= FLT_EPSILON) ? 1 : -1, 0, 0); + else if (Constr->Type == DistanceY) + dir = Base::Vector3d(0, (p2.y - p1.y >= FLT_EPSILON) ? 1 : -1, 0); + + if (Constr->Type == Radius || Constr->Type == Diameter || Constr->Type == Weight) { + Constr->LabelDistance = vec.x * dir.x + vec.y * dir.y; + Constr->LabelPosition = atan2(dir.y, dir.x); + } else { + Base::Vector3d normal(-dir.y,dir.x,0); + Constr->LabelDistance = vec.x * normal.x + vec.y * normal.y; + if (Constr->Type == Distance || + Constr->Type == DistanceX || Constr->Type == DistanceY) { + vec = Base::Vector3d(toPos.x, toPos.y, 0) - (p2 + p1) / 2; + Constr->LabelPosition = vec.x * dir.x + vec.y * dir.y; + } + } + } + else if (Constr->Type == Angle) { + + Base::Vector3d p0(0.,0.,0.); + double factor = 0.5; + if (Constr->Second != GeoEnum::GeoUndef) { // line to line angle + Base::Vector3d dir1, dir2; + + if(Constr->Third == GeoEnum::GeoUndef) { //angle between two lines + const Part::Geometry *geo1 = GeoList::getGeometryFromGeoId (geomlist, Constr->First); + const Part::Geometry *geo2 = GeoList::getGeometryFromGeoId (geomlist, Constr->Second); + + if (geo1->getTypeId() != Part::GeomLineSegment::getClassTypeId() || + geo2->getTypeId() != Part::GeomLineSegment::getClassTypeId()) + return; + const Part::GeomLineSegment *lineSeg1 = static_cast(geo1); + const Part::GeomLineSegment *lineSeg2 = static_cast(geo2); + + bool flip1 = (Constr->FirstPos == Sketcher::PointPos::end); + bool flip2 = (Constr->SecondPos == Sketcher::PointPos::end); + + dir1 = (flip1 ? -1. : 1.) * (lineSeg1->getEndPoint()-lineSeg1->getStartPoint()); + dir2 = (flip2 ? -1. : 1.) * (lineSeg2->getEndPoint()-lineSeg2->getStartPoint()); + Base::Vector3d pnt1 = flip1 ? lineSeg1->getEndPoint() : lineSeg1->getStartPoint(); + Base::Vector3d pnt2 = flip2 ? lineSeg2->getEndPoint() : lineSeg2->getStartPoint(); + + // line-line intersection + { + double det = dir1.x*dir2.y - dir1.y*dir2.x; + if ((det > 0 ? det : -det) < 1e-10) + return;// lines are parallel - constraint unmoveable (DeepSOIC: why?..) + double c1 = dir1.y*pnt1.x - dir1.x*pnt1.y; + double c2 = dir2.y*pnt2.x - dir2.x*pnt2.y; + double x = (dir1.x*c2 - dir2.x*c1)/det; + double y = (dir1.y*c2 - dir2.y*c1)/det; + // intersection point + p0 = Base::Vector3d(x,y,0); + + Base::Vector3d vec = Base::Vector3d(toPos.x, toPos.y, 0) - p0; + factor = factor * Base::sgn((dir1+dir2) * vec); + } + } else {//angle-via-point + Base::Vector3d p = getSolvedSketch().getPoint(Constr->Third, Constr->ThirdPos); + p0 = Base::Vector3d(p.x, p.y, 0); + dir1 = getSolvedSketch().calculateNormalAtPoint(Constr->First, p.x, p.y); + dir1.RotateZ(-M_PI/2);//convert to vector of tangency by rotating + dir2 = getSolvedSketch().calculateNormalAtPoint(Constr->Second, p.x, p.y); + dir2.RotateZ(-M_PI/2); + + Base::Vector3d vec = Base::Vector3d(toPos.x, toPos.y, 0) - p0; + factor = factor * Base::sgn((dir1+dir2) * vec); + } + + } else if (Constr->First != GeoEnum::GeoUndef) { // line/arc angle + const Part::Geometry *geo = GeoList::getGeometryFromGeoId (geomlist, Constr->First); + + if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + const Part::GeomLineSegment *lineSeg = static_cast(geo); + p0 = (lineSeg->getEndPoint()+lineSeg->getStartPoint())/2; + } + else if (geo->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { + const Part::GeomArcOfCircle *arc = static_cast(geo); + p0 = arc->getCenter(); + } + else { + return; + } + } else + return; + + Base::Vector3d vec = Base::Vector3d(toPos.x, toPos.y, 0) - p0; + Constr->LabelDistance = factor * vec.Length(); + } + + // delete the cloned objects + for (std::vector::const_iterator it=geomlist.begin(); it != geomlist.end(); ++it) + if (*it) delete *it; + + draw(true,false); +} + +bool ViewProviderSketch::isSelectable() const +{ + if (isEditing()) + return false; + else + return PartGui::ViewProvider2DObject::isSelectable(); +} + +void ViewProviderSketch::onSelectionChanged(const Gui::SelectionChanges& msg) +{ + // are we in edit? + if (isInEditMode()) { + // ignore external object + if(!msg.Object.getObjectName().empty() && msg.Object.getDocument()!=getObject()->getDocument()) + return; + + bool handled=false; + if (Mode == STATUS_SKETCH_UseHandler) { + App::AutoTransaction committer; + handled = sketchHandler->onSelectionChanged(msg); + } + if (handled) + return; + + std::string temp; + if (msg.Type == Gui::SelectionChanges::ClrSelection) { + // if something selected in this object? + if (!selection.SelPointSet.empty() || !selection.SelCurvSet.empty() || !selection.SelConstraintSet.empty()) { + // clear our selection and update the color of the viewed edges and points + clearSelectPoints(); + selection.SelCurvSet.clear(); + selection.SelConstraintSet.clear(); + editCoinManager->drawConstraintIcons(); + this->updateColor(); + } + } + else if (msg.Type == Gui::SelectionChanges::AddSelection) { + // is it this object?? + if (strcmp(msg.pDocName,getSketchObject()->getDocument()->getName())==0 + && strcmp(msg.pObjectName,getSketchObject()->getNameInDocument())== 0) { + if (msg.pSubName) { + std::string shapetype(msg.pSubName); + if (shapetype.size() > 4 && shapetype.substr(0,4) == "Edge") { + int GeoId = std::atoi(&shapetype[4]) - 1; + selection.SelCurvSet.insert(GeoId); + this->updateColor(); + } + else if (shapetype.size() > 12 && shapetype.substr(0,12) == "ExternalEdge") { + int GeoId = std::atoi(&shapetype[12]) - 1; + GeoId = -GeoId - 3; + selection.SelCurvSet.insert(GeoId); + this->updateColor(); + } + else if (shapetype.size() > 6 && shapetype.substr(0,6) == "Vertex") { + int VtId = std::atoi(&shapetype[6]) - 1; + addSelectPoint(VtId); + this->updateColor(); + } + else if (shapetype == "RootPoint") { + addSelectPoint(Selection::RootPoint); + this->updateColor(); + } + else if (shapetype == "H_Axis") { + selection.SelCurvSet.insert(Selection::HorizontalAxis); + this->updateColor(); + } + else if (shapetype == "V_Axis") { + selection.SelCurvSet.insert(Selection::VerticalAxis); + this->updateColor(); + } + else if (shapetype.size() > 10 && shapetype.substr(0,10) == "Constraint") { + int ConstrId = Sketcher::PropertyConstraintList::getIndexFromConstraintName(shapetype); + selection.SelConstraintSet.insert(ConstrId); + editCoinManager->drawConstraintIcons(); + this->updateColor(); + } + } + } + } + else if (msg.Type == Gui::SelectionChanges::RmvSelection) { + // Are there any objects selected + if (!selection.SelPointSet.empty() || !selection.SelCurvSet.empty() || !selection.SelConstraintSet.empty()) { + // is it this object?? + if (strcmp(msg.pDocName,getSketchObject()->getDocument()->getName())==0 + && strcmp(msg.pObjectName,getSketchObject()->getNameInDocument())== 0) { + if (msg.pSubName) { + std::string shapetype(msg.pSubName); + if (shapetype.size() > 4 && shapetype.substr(0,4) == "Edge") { + int GeoId = std::atoi(&shapetype[4]) - 1; + selection.SelCurvSet.erase(GeoId); + this->updateColor(); + } + else if (shapetype.size() > 12 && shapetype.substr(0,12) == "ExternalEdge") { + int GeoId = std::atoi(&shapetype[12]) - 1; + GeoId = -GeoId - 3; + selection.SelCurvSet.erase(GeoId); + this->updateColor(); + } + else if (shapetype.size() > 6 && shapetype.substr(0,6) == "Vertex") { + int VtId = std::atoi(&shapetype[6]) - 1; + removeSelectPoint(VtId); + this->updateColor(); + } + else if (shapetype == "RootPoint") { + removeSelectPoint(Sketcher::GeoEnum::RtPnt); + this->updateColor(); + } + else if (shapetype == "H_Axis") { + selection.SelCurvSet.erase(Sketcher::GeoEnum::HAxis); + this->updateColor(); + } + else if (shapetype == "V_Axis") { + selection.SelCurvSet.erase(Sketcher::GeoEnum::VAxis); + this->updateColor(); + } + else if (shapetype.size() > 10 && shapetype.substr(0,10) == "Constraint") { + int ConstrId = Sketcher::PropertyConstraintList::getIndexFromConstraintName(shapetype); + selection.SelConstraintSet.erase(ConstrId); + editCoinManager->drawConstraintIcons(); + this->updateColor(); + } + } + } + } + } + else if (msg.Type == Gui::SelectionChanges::SetSelection) { + // remove all items + //selectionView->clear(); + //std::vector objs = Gui::Selection().getSelection(Reason.pDocName); + //for (std::vector::iterator it = objs.begin(); it != objs.end(); ++it) { + // // build name + // temp = it->DocName; + // temp += "."; + // temp += it->FeatName; + // if (it->SubName && it->SubName[0] != '\0') { + // temp += "."; + // temp += it->SubName; + // } + // new QListWidgetItem(QString::fromLatin1(temp.c_str()), selectionView); + //} + } + else if (msg.Type == Gui::SelectionChanges::SetPreselect) { + if (strcmp(msg.pDocName,getSketchObject()->getDocument()->getName())==0 + && strcmp(msg.pObjectName,getSketchObject()->getNameInDocument())== 0) { + if (msg.pSubName) { + std::string shapetype(msg.pSubName); + if (shapetype.size() > 4 && shapetype.substr(0,4) == "Edge") { + int GeoId = std::atoi(&shapetype[4]) - 1; + resetPreselectPoint(); + preselection.PreselectCurve = GeoId; + + if (sketchHandler) + sketchHandler->applyCursor(); + this->updateColor(); + } + else if (shapetype.size() > 6 && shapetype.substr(0,6) == "Vertex") { + int PtIndex = std::atoi(&shapetype[6]) - 1; + setPreselectPoint(PtIndex); + + if (sketchHandler) + sketchHandler->applyCursor(); + this->updateColor(); + } + } + } + } + else if (msg.Type == Gui::SelectionChanges::RmvPreselect) { + resetPreselectPoint(); + if (sketchHandler) + sketchHandler->applyCursor(); + this->updateColor(); + } + } +} + +bool ViewProviderSketch::detectAndShowPreselection(SoPickedPoint * Point, const SbVec2s &cursorPos) +{ + assert(isInEditMode()); + + if (Point) { + + EditModeCoinManager::PreselectionResult result = + editCoinManager->detectPreselection(Point, cursorPos); + + if (result.PointIndex != -1 + && result.PointIndex != preselection.PreselectPoint) {// if a new point is hit + std::stringstream ss; + ss << "Vertex" << result.PointIndex + 1; + bool accepted = setPreselect(ss.str(), Point->getPoint()[0], Point->getPoint()[1], + Point->getPoint()[2]) + != 0; + preselection.blockedPreselection = !accepted; + if (accepted) { + setPreselectPoint(result.PointIndex); + + if (sketchHandler) + sketchHandler->applyCursor(); + return true; + } + } + else if (result.GeoIndex != -1 + && result.GeoIndex != preselection.PreselectCurve) {// if a new curve is hit + std::stringstream ss; + if (result.GeoIndex >= 0) + ss << "Edge" << result.GeoIndex + 1; + else // external geometry + ss << "ExternalEdge" + << -result.GeoIndex + Sketcher::GeoEnum::RefExt + + 1;// convert index start from -3 to 1 + bool accepted = setPreselect(ss.str(), Point->getPoint()[0], Point->getPoint()[1], + Point->getPoint()[2]) + != 0; + preselection.blockedPreselection = !accepted; + if (accepted) { + resetPreselectPoint(); + preselection.PreselectCurve = result.GeoIndex; + + if (sketchHandler) + sketchHandler->applyCursor(); + return true; + } + } + else if (result.Cross != EditModeCoinManager::PreselectionResult::Axes::None + && static_cast(result.Cross) + != static_cast(preselection.PreselectCross)) {// if a cross line is hit + std::stringstream ss; + switch (result.Cross) { + case EditModeCoinManager::PreselectionResult::Axes::RootPoint: + ss << "RootPoint"; + break; + case EditModeCoinManager::PreselectionResult::Axes::HorizontalAxis: + ss << "H_Axis"; + break; + case EditModeCoinManager::PreselectionResult::Axes::VerticalAxis: + ss << "V_Axis"; + break; + case EditModeCoinManager::PreselectionResult::Axes::None: + break;// silent warning - be explicit + } + bool accepted = setPreselect(ss.str(), Point->getPoint()[0], Point->getPoint()[1], + Point->getPoint()[2]) + != 0; + preselection.blockedPreselection = !accepted; + if (accepted) { + if (result.Cross == EditModeCoinManager::PreselectionResult::Axes::RootPoint) + setPreselectRootPoint(); + else + resetPreselectPoint(); + preselection.PreselectCross = + static_cast(static_cast(result.Cross)); + + if (sketchHandler) + sketchHandler->applyCursor(); + return true; + } + } + else if (!result.ConstrIndices.empty() + && result.ConstrIndices + != preselection.PreselectConstraintSet) {// if a constraint is hit + bool accepted = true; + for (std::set::iterator it = result.ConstrIndices.begin(); + it != result.ConstrIndices.end(); ++it) { + std::stringstream ss; + ss << Sketcher::PropertyConstraintList::getConstraintName(*it); + + accepted &= setPreselect(ss.str(), Point->getPoint()[0], Point->getPoint()[1], + Point->getPoint()[2]) + != 0; + + preselection.blockedPreselection = !accepted; + //TODO: Should we clear preselections that went through, if one fails? + } + if (accepted) { + resetPreselectPoint(); + preselection.PreselectConstraintSet = result.ConstrIndices; + + if (sketchHandler) + sketchHandler->applyCursor(); + return true;//Preselection changed + } + } + else if ((result.PointIndex == -1 && result.GeoIndex == -1 + && result.Cross == EditModeCoinManager::PreselectionResult::Axes::None + && result.ConstrIndices.empty()) + && (preselection.isPreselectPointValid() || preselection.isPreselectCurveValid() + || preselection.isCrossPreselected() + || !preselection.PreselectConstraintSet.empty() + || preselection.blockedPreselection)) { + // we have just left a preselection + resetPreselectPoint(); + preselection.blockedPreselection = false; + if (sketchHandler) + sketchHandler->applyCursor(); + return true; + } + Gui::Selection().setPreselectCoord(Point->getPoint()[0], Point->getPoint()[1], + Point->getPoint()[2]); + } + else if (preselection.isPreselectCurveValid() || preselection.isPreselectPointValid() + || !preselection.PreselectConstraintSet.empty() || preselection.isCrossPreselected() + || preselection.blockedPreselection) { + resetPreselectPoint(); + preselection.blockedPreselection = false; + if (sketchHandler) + sketchHandler->applyCursor(); + return true; + } + + return false; +} + +void ViewProviderSketch::centerSelection() +{ + Gui::MDIView *mdi = this->getActiveView(); + Gui::View3DInventor *view = qobject_cast(mdi); + if (!view || !isInEditMode()) + return; + + SoGroup* group = editCoinManager->getSelectedConstraints(); + + Gui::View3DInventorViewer* viewer = view->getViewer(); + SoGetBoundingBoxAction action(viewer->getSoRenderManager()->getViewportRegion()); + action.apply(group); + group->unref(); + + SbBox3f box = action.getBoundingBox(); + if (!box.isEmpty()) { + SoCamera* camera = viewer->getSoRenderManager()->getCamera(); + SbVec3f direction; + camera->orientation.getValue().multVec(SbVec3f(0, 0, 1), direction); + SbVec3f box_cnt = box.getCenter(); + SbVec3f cam_pos = box_cnt + camera->focalDistance.getValue() * direction; + camera->position.setValue(cam_pos); + } +} + +void ViewProviderSketch::doBoxSelection(const SbVec2s &startPos, const SbVec2s &endPos, + const Gui::View3DInventorViewer *viewer) +{ + std::vector corners0; + corners0.push_back(startPos); + corners0.push_back(endPos); + std::vector corners = viewer->getGLPolygon(corners0); + + // all calculations with polygon and proj are in dimensionless [0 1] screen coordinates + Base::Polygon2d polygon; + polygon.Add(Base::Vector2d(corners[0].getValue()[0], corners[0].getValue()[1])); + polygon.Add(Base::Vector2d(corners[0].getValue()[0], corners[1].getValue()[1])); + polygon.Add(Base::Vector2d(corners[1].getValue()[0], corners[1].getValue()[1])); + polygon.Add(Base::Vector2d(corners[1].getValue()[0], corners[0].getValue()[1])); + + Gui::ViewVolumeProjection proj(viewer->getSoRenderManager()->getCamera()->getViewVolume()); + + Sketcher::SketchObject *sketchObject = getSketchObject(); + + Base::Placement Plm = getEditingPlacement(); + + int intGeoCount = sketchObject->getHighestCurveIndex() + 1; + int extGeoCount = sketchObject->getExternalGeometryCount(); + + const std::vector geomlist = sketchObject->getCompleteGeometry(); // without memory allocation + assert(int(geomlist.size()) == extGeoCount + intGeoCount); + assert(int(geomlist.size()) >= 2); + + Base::Vector3d pnt0, pnt1, pnt2, pnt; + int VertexId = -1; // the loop below should be in sync with the main loop in ViewProviderSketch::draw + // so that the vertex indices are calculated correctly + int GeoId = 0; + + bool touchMode = false; + //check if selection goes from the right to the left side (for touch-selection where even partially boxed objects get selected) + if(corners[0].getValue()[0] > corners[1].getValue()[0]) + touchMode = true; + + for (std::vector::const_iterator it = geomlist.begin(); it != geomlist.end()-2; ++it, ++GeoId) { + + if (GeoId >= intGeoCount) + GeoId = -extGeoCount; + + if ((*it)->getTypeId() == Part::GeomPoint::getClassTypeId()) { + // ----- Check if single point lies inside box selection -----/ + const Part::GeomPoint *point = static_cast(*it); + Plm.multVec(point->getPoint(), pnt0); + pnt0 = proj(pnt0); + VertexId += 1; + + if (polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y))) { + std::stringstream ss; + ss << "Vertex" << VertexId + 1; + addSelection2(ss.str()); + } + + } else if ((*it)->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + // ----- Check if line segment lies inside box selection -----/ + const Part::GeomLineSegment *lineSeg = static_cast(*it); + Plm.multVec(lineSeg->getStartPoint(), pnt1); + Plm.multVec(lineSeg->getEndPoint(), pnt2); + pnt1 = proj(pnt1); + pnt2 = proj(pnt2); + VertexId += 2; + + bool pnt1Inside = polygon.Contains(Base::Vector2d(pnt1.x, pnt1.y)); + bool pnt2Inside = polygon.Contains(Base::Vector2d(pnt2.x, pnt2.y)); + if (pnt1Inside) { + std::stringstream ss; + ss << "Vertex" << VertexId; + addSelection2(ss.str()); + } + + if (pnt2Inside) { + std::stringstream ss; + ss << "Vertex" << VertexId + 1; + addSelection2(ss.str()); + } + + if ((pnt1Inside && pnt2Inside) && !touchMode) { + std::stringstream ss; + ss << "Edge" << GeoId + 1; + addSelection2(ss.str()); + } + //check if line intersects with polygon + else if (touchMode) { + Base::Polygon2d lineAsPolygon; + lineAsPolygon.Add(Base::Vector2d(pnt1.x, pnt1.y)); + lineAsPolygon.Add(Base::Vector2d(pnt2.x, pnt2.y)); + std::list resultList; + polygon.Intersect(lineAsPolygon, resultList); + if (!resultList.empty()) { + std::stringstream ss; + ss << "Edge" << GeoId + 1; + addSelection2(ss.str()); + } + } + + } else if ((*it)->getTypeId() == Part::GeomCircle::getClassTypeId()) { + // ----- Check if circle lies inside box selection -----/ + ///TODO: Make it impossible to miss the circle if it's big and the selection pretty thin. + const Part::GeomCircle *circle = static_cast(*it); + pnt0 = circle->getCenter(); + VertexId += 1; + + Plm.multVec(pnt0, pnt0); + pnt0 = proj(pnt0); + + if (polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y)) || touchMode) { + if (polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y))) { + std::stringstream ss; + ss << "Vertex" << VertexId + 1; + addSelection2(ss.str()); + } + int countSegments = 12; + if (touchMode) + countSegments = 36; + + float segment = float(2 * M_PI) / countSegments; + + // circumscribed polygon radius + float radius = float(circle->getRadius()) / cos(segment/2); + + bool bpolyInside = true; + pnt0 = circle->getCenter(); + float angle = 0.f; + for (int i = 0; i < countSegments; ++i, angle += segment) { + pnt = Base::Vector3d(pnt0.x + radius * cos(angle), + pnt0.y + radius * sin(angle), + 0.f); + Plm.multVec(pnt, pnt); + pnt = proj(pnt); + if (!polygon.Contains(Base::Vector2d(pnt.x, pnt.y))) { + bpolyInside = false; + if (!touchMode) + break; + } + else if (touchMode) { + bpolyInside = true; + break; + } + } + + if (bpolyInside) { + std::stringstream ss; + ss << "Edge" << GeoId + 1; + addSelection2(ss.str()); + } + } + } else if ((*it)->getTypeId() == Part::GeomEllipse::getClassTypeId()) { + // ----- Check if ellipse lies inside box selection -----/ + const Part::GeomEllipse *ellipse = static_cast(*it); + pnt0 = ellipse->getCenter(); + VertexId += 1; + + Plm.multVec(pnt0, pnt0); + pnt0 = proj(pnt0); + + if (polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y)) || touchMode) { + if (polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y))) { + std::stringstream ss; + ss << "Vertex" << VertexId + 1; + addSelection2(ss.str()); + } + + int countSegments = 12; + if (touchMode) + countSegments = 24; + double segment = (2 * M_PI) / countSegments; + + // circumscribed polygon radius + double a = (ellipse->getMajorRadius()) / cos(segment/2); + double b = (ellipse->getMinorRadius()) / cos(segment/2); + Base::Vector3d majdir = ellipse->getMajorAxisDir(); + Base::Vector3d mindir = Base::Vector3d(-majdir.y, majdir.x, 0.0); + + bool bpolyInside = true; + pnt0 = ellipse->getCenter(); + double angle = 0.; + for (int i = 0; i < countSegments; ++i, angle += segment) { + pnt = pnt0 + (cos(angle)*a)*majdir + sin(angle)*b*mindir; + Plm.multVec(pnt, pnt); + pnt = proj(pnt); + if (!polygon.Contains(Base::Vector2d(pnt.x, pnt.y))) { + bpolyInside = false; + if (!touchMode) + break; + } + else if (touchMode) { + bpolyInside = true; + break; + } + } + + if (bpolyInside) { + std::stringstream ss; + ss << "Edge" << GeoId + 1; + addSelection2(ss.str()); + } + } + + } else if ((*it)->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { + // Check if arc lies inside box selection + const Part::GeomArcOfCircle *aoc = static_cast(*it); + + pnt0 = aoc->getStartPoint(/*emulateCCW=*/true); + pnt1 = aoc->getEndPoint(/*emulateCCW=*/true); + pnt2 = aoc->getCenter(); + VertexId += 3; + + Plm.multVec(pnt0, pnt0); + Plm.multVec(pnt1, pnt1); + Plm.multVec(pnt2, pnt2); + pnt0 = proj(pnt0); + pnt1 = proj(pnt1); + pnt2 = proj(pnt2); + + bool pnt0Inside = polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y)); + bool pnt1Inside = polygon.Contains(Base::Vector2d(pnt1.x, pnt1.y)); + bool bpolyInside = true; + + if ((pnt0Inside && pnt1Inside) || touchMode) { + double startangle, endangle; + aoc->getRange(startangle, endangle, /*emulateCCW=*/true); + + if (startangle > endangle) // if arc is reversed + std::swap(startangle, endangle); + + double range = endangle-startangle; + int countSegments = std::max(2, int(12.0 * range / (2 * M_PI))); + if (touchMode) + countSegments=countSegments*2.5; + float segment = float(range) / countSegments; + + // circumscribed polygon radius + float radius = float(aoc->getRadius()) / cos(segment/2); + + pnt0 = aoc->getCenter(); + float angle = float(startangle) + segment/2; + for (int i = 0; i < countSegments; ++i, angle += segment) { + pnt = Base::Vector3d(pnt0.x + radius * cos(angle), + pnt0.y + radius * sin(angle), + 0.f); + Plm.multVec(pnt, pnt); + pnt = proj(pnt); + if (!polygon.Contains(Base::Vector2d(pnt.x, pnt.y))) { + bpolyInside = false; + if (!touchMode) + break; + } + else if(touchMode) { + bpolyInside = true; + break; + } + } + + if (bpolyInside) { + std::stringstream ss; + ss << "Edge" << GeoId + 1; + addSelection2(ss.str()); + } + } + + if (pnt0Inside) { + std::stringstream ss; + ss << "Vertex" << VertexId - 1; + addSelection2(ss.str()); + } + + if (pnt1Inside) { + std::stringstream ss; + ss << "Vertex" << VertexId; + addSelection2(ss.str()); + } + + if (polygon.Contains(Base::Vector2d(pnt2.x, pnt2.y))) { + std::stringstream ss; + ss << "Vertex" << VertexId + 1; + addSelection2(ss.str()); + } + } else if ((*it)->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) { + // Check if arc lies inside box selection + const Part::GeomArcOfEllipse *aoe = static_cast(*it); + + pnt0 = aoe->getStartPoint(/*emulateCCW=*/true); + pnt1 = aoe->getEndPoint(/*emulateCCW=*/true); + pnt2 = aoe->getCenter(); + + VertexId += 3; + + Plm.multVec(pnt0, pnt0); + Plm.multVec(pnt1, pnt1); + Plm.multVec(pnt2, pnt2); + pnt0 = proj(pnt0); + pnt1 = proj(pnt1); + pnt2 = proj(pnt2); + + bool pnt0Inside = polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y)); + bool pnt1Inside = polygon.Contains(Base::Vector2d(pnt1.x, pnt1.y)); + bool bpolyInside = true; + + if ((pnt0Inside && pnt1Inside) || touchMode) { + double startangle, endangle; + aoe->getRange(startangle, endangle, /*emulateCCW=*/true); + + if (startangle > endangle) // if arc is reversed + std::swap(startangle, endangle); + + double range = endangle-startangle; + int countSegments = std::max(2, int(12.0 * range / (2 * M_PI))); + if (touchMode) + countSegments=countSegments*2.5; + double segment = (range) / countSegments; + + // circumscribed polygon radius + double a = (aoe->getMajorRadius()) / cos(segment/2); + double b = (aoe->getMinorRadius()) / cos(segment/2); + Base::Vector3d majdir = aoe->getMajorAxisDir(); + Base::Vector3d mindir = Base::Vector3d(-majdir.y, majdir.x, 0.0); + + pnt0 = aoe->getCenter(); + double angle = (startangle) + segment/2; + for (int i = 0; i < countSegments; ++i, angle += segment) { + pnt = pnt0 + cos(angle)*a*majdir + sin(angle)*b*mindir; + + Plm.multVec(pnt, pnt); + pnt = proj(pnt); + if (!polygon.Contains(Base::Vector2d(pnt.x, pnt.y))) { + bpolyInside = false; + if (!touchMode) + break; + } + else if (touchMode) { + bpolyInside = true; + break; + } + } + + if (bpolyInside) { + std::stringstream ss; + ss << "Edge" << GeoId + 1; + addSelection2(ss.str()); + } + } + if (pnt0Inside) { + std::stringstream ss; + ss << "Vertex" << VertexId - 1; + addSelection2(ss.str()); + } + + if (pnt1Inside) { + std::stringstream ss; + ss << "Vertex" << VertexId; + addSelection2(ss.str()); + } + + if (polygon.Contains(Base::Vector2d(pnt2.x, pnt2.y))) { + std::stringstream ss; + ss << "Vertex" << VertexId + 1; + addSelection2(ss.str()); + } + + } else if ((*it)->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId()) { + // Check if arc lies inside box selection + const Part::GeomArcOfHyperbola *aoh = static_cast(*it); + pnt0 = aoh->getStartPoint(); + pnt1 = aoh->getEndPoint(); + pnt2 = aoh->getCenter(); + + VertexId += 3; + + Plm.multVec(pnt0, pnt0); + Plm.multVec(pnt1, pnt1); + Plm.multVec(pnt2, pnt2); + pnt0 = proj(pnt0); + pnt1 = proj(pnt1); + pnt2 = proj(pnt2); + + bool pnt0Inside = polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y)); + bool pnt1Inside = polygon.Contains(Base::Vector2d(pnt1.x, pnt1.y)); + bool bpolyInside = true; + + if ((pnt0Inside && pnt1Inside) || touchMode) { + double startangle, endangle; + + aoh->getRange(startangle, endangle, /*emulateCCW=*/true); + + if (startangle > endangle) // if arc is reversed + std::swap(startangle, endangle); + + double range = endangle-startangle; + int countSegments = std::max(2, int(12.0 * range / (2 * M_PI))); + if (touchMode) + countSegments=countSegments*2.5; + + float segment = float(range) / countSegments; + + // circumscribed polygon radius + float a = float(aoh->getMajorRadius()) / cos(segment/2); + float b = float(aoh->getMinorRadius()) / cos(segment/2); + float phi = float(aoh->getAngleXU()); + + pnt0 = aoh->getCenter(); + float angle = float(startangle) + segment/2; + for (int i = 0; i < countSegments; ++i, angle += segment) { + pnt = Base::Vector3d(pnt0.x + a * cosh(angle) * cos(phi) - b * sinh(angle) * sin(phi), + pnt0.y + a * cosh(angle) * sin(phi) + b * sinh(angle) * cos(phi), + 0.f); + + Plm.multVec(pnt, pnt); + pnt = proj(pnt); + if (!polygon.Contains(Base::Vector2d(pnt.x, pnt.y))) { + bpolyInside = false; + if (!touchMode) + break; + } + else if (touchMode) { + bpolyInside = true; + break; + } + } + + if (bpolyInside) { + std::stringstream ss; + ss << "Edge" << GeoId + 1; + addSelection2(ss.str()); + } + if (pnt0Inside) { + std::stringstream ss; + ss << "Vertex" << VertexId - 1; + addSelection2(ss.str()); + } + + if (pnt1Inside) { + std::stringstream ss; + ss << "Vertex" << VertexId; + addSelection2(ss.str()); + } + + if (polygon.Contains(Base::Vector2d(pnt2.x, pnt2.y))) { + std::stringstream ss; + ss << "Vertex" << VertexId + 1; + addSelection2(ss.str()); + } + + } + + } else if ((*it)->getTypeId() == Part::GeomArcOfParabola::getClassTypeId()) { + // Check if arc lies inside box selection + const Part::GeomArcOfParabola *aop = static_cast(*it); + + pnt0 = aop->getStartPoint(); + pnt1 = aop->getEndPoint(); + pnt2 = aop->getCenter(); + + VertexId += 3; + + Plm.multVec(pnt0, pnt0); + Plm.multVec(pnt1, pnt1); + Plm.multVec(pnt2, pnt2); + pnt0 = proj(pnt0); + pnt1 = proj(pnt1); + pnt2 = proj(pnt2); + + bool pnt0Inside = polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y)); + bool pnt1Inside = polygon.Contains(Base::Vector2d(pnt1.x, pnt1.y)); + bool bpolyInside = true; + + if ((pnt0Inside && pnt1Inside) || touchMode) { + double startangle, endangle; + + aop->getRange(startangle, endangle, /*emulateCCW=*/true); + + if (startangle > endangle) // if arc is reversed + std::swap(startangle, endangle); + + double range = endangle-startangle; + int countSegments = std::max(2, int(12.0 * range / (2 * M_PI))); + if (touchMode) + countSegments=countSegments*2.5; + + float segment = float(range) / countSegments; + //In local coordinate system, value() of parabola is: + //P(U) = O + U*U/(4.*F)*XDir + U*YDir + // circumscribed polygon radius + float focal = float(aop->getFocal()) / cos(segment/2); + float phi = float(aop->getAngleXU()); + + pnt0 = aop->getCenter(); + float angle = float(startangle) + segment/2; + for (int i = 0; i < countSegments; ++i, angle += segment) { + 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); + + Plm.multVec(pnt, pnt); + pnt = proj(pnt); + if (!polygon.Contains(Base::Vector2d(pnt.x, pnt.y))) { + bpolyInside = false; + if (!touchMode) + break; + } + else if (touchMode) { + bpolyInside = true; + break; + } + } + + if (bpolyInside) { + std::stringstream ss; + ss << "Edge" << GeoId + 1; + addSelection2(ss.str()); + } + if (pnt0Inside) { + std::stringstream ss; + ss << "Vertex" << VertexId - 1; + addSelection2(ss.str()); + } + + if (pnt1Inside) { + std::stringstream ss; + ss << "Vertex" << VertexId; + addSelection2(ss.str()); + } + + if (polygon.Contains(Base::Vector2d(pnt2.x, pnt2.y))) { + std::stringstream ss; + ss << "Vertex" << VertexId + 1; + addSelection2(ss.str()); + } + } + + } else if ((*it)->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { + const Part::GeomBSplineCurve *spline = static_cast(*it); + //std::vector poles = spline->getPoles(); + VertexId += 2; + + Plm.multVec(spline->getStartPoint(), pnt1); + Plm.multVec(spline->getEndPoint(), pnt2); + pnt1 = proj(pnt1); + pnt2 = proj(pnt2); + + bool pnt1Inside = polygon.Contains(Base::Vector2d(pnt1.x, pnt1.y)); + bool pnt2Inside = polygon.Contains(Base::Vector2d(pnt2.x, pnt2.y)); + if (pnt1Inside || (touchMode && pnt2Inside)) { + std::stringstream ss; + ss << "Vertex" << VertexId; + addSelection2(ss.str()); + } + + if (pnt2Inside || (touchMode && pnt1Inside)) { + std::stringstream ss; + ss << "Vertex" << VertexId + 1; + addSelection2(ss.str()); + } + + // This is a rather approximated approach. No it does not guarantee that the whole curve is boxed, specially + // for periodic curves, but it works reasonably well. Including all poles, which could be done, generally + // forces the user to select much more than the curve (all the poles) and it would not select the curve in cases + // where it is indeed comprised in the box. + // The implementation of the touch mode is also far from a desirable "touch" as it only recognizes touched points not the curve itself + if ((pnt1Inside && pnt2Inside) || (touchMode && (pnt1Inside || pnt2Inside))) { + std::stringstream ss; + ss << "Edge" << GeoId + 1; + addSelection2(ss.str()); + } + } + } + + pnt0 = proj(Plm.getPosition()); + if (polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y))) { + std::stringstream ss; + ss << "RootPoint"; + addSelection2(ss.str()); + } +} + +void ViewProviderSketch::updateColor() +{ + assert(isInEditMode()); + + editCoinManager->updateColor(); +} + +bool ViewProviderSketch::doubleClicked() +{ + Gui::Application::Instance->activeDocument()->setEdit(this); + return true; +} + +float ViewProviderSketch::getScaleFactor() const +{ + assert(isInEditMode()); + Gui::MDIView *mdi = Gui::Application::Instance->editViewOfNode(editCoinManager->getRootEditNode()); + if (mdi && mdi->isDerivedFrom(Gui::View3DInventor::getClassTypeId())) { + Gui::View3DInventorViewer *viewer = static_cast(mdi)->getViewer(); + SoCamera* camera = viewer->getSoRenderManager()->getCamera(); + float scale = camera->getViewVolume(camera->aspectRatio.getValue()).getWorldToScreenScale(SbVec3f(0.f, 0.f, 0.f), 0.1f) / 3; + return scale; + } + else { + return 1.f; + } +} + +// This function ensures that the geometry used for drawing takes into account: +// 1. the OCC mandated weight, which is normalised for non-rational BSplines, but not normalised for rational BSplines. +// That includes properly sizing for drawing any weight constraint. +// This function ensures that both the geometry of the SketchObject and solver are updated with the new value of the scaling factor (via the extension) +// 2. the scaling factor, including inserting the scaling factor into the ViewProviderSketchGeometryExtension so as to enable +// That ensures that dragging operations on the circles of the poles of the B-Splines are properly rendered. +// +// This function takes a reference to a vector of deep copies to delete. These deep copies are necessary to transparently perform (1) while doing (2). +void ViewProviderSketch::scaleBSplinePoleCirclesAndUpdateSolverAndSketchObjectGeometry( + GeoListFacade & geolistfacade, + bool geometrywithmemoryallocation) +{ + // In order to allow to tweak geometry and insert scaling factors, this function needs to + // change the geometry vector. This is highly exceptional for a drawing function and special + // care needs to be taken. This is valid because: + // 1. The treatment is exceptional and no other appropriate place is available to perform this tweak + // 2. The original object needs to remain const for the benefit of all other class hierarchy of drawing functions + // 3. When referring to actual geometry, the modified pointers are short lived, as they are destroyed after drawing + auto & tempGeo = geolistfacade.geomlist; + + int GeoId = 0; + for (auto it = tempGeo.begin(); it != tempGeo.end()-2; ++it, GeoId++) { + if (GeoId >= geolistfacade.getInternalCount()) + GeoId = -geolistfacade.getExternalCount(); + + if ((*it)->getGeometry()->getTypeId() == Part::GeomCircle::getClassTypeId()) { // circle + const Part::GeomCircle *circle = static_cast((*it)->getGeometry()); + auto & gf = (*it); + + // BSpline weights have a radius corresponding to the weight value + // However, in order for them proportional to the B-Spline size, + // the scenograph has a size scalefactor times the weight + // + // This code produces the scaled up version of the geometry for the scenograph + if(gf->getInternalType() == InternalType::BSplineControlPoint) { + for( auto c : getSketchObject()->Constraints.getValues()) { + if( c->Type == InternalAlignment && c->AlignmentType == BSplineControlPoint && c->First == GeoId) { + auto bspline = dynamic_cast(tempGeo[c->Second]->getGeometry()); + + if(bspline){ + auto weights = bspline->getWeights(); + + double weight = 1.0; + if(c->InternalAlignmentIndex < int(weights.size())) + weight = weights[c->InternalAlignmentIndex]; + + // tentative scaling factor: + // proportional to the length of the bspline + // inversely proportional to the number of poles + double scalefactor = bspline->length(bspline->getFirstParameter(), bspline->getLastParameter())/10.0/weights.size(); + + double vradius = weight*scalefactor; + if(!bspline->isRational()) { + // OCCT sets the weights to 1.0 if a bspline is non-rational, but if the user has a weight constraint on any + // pole it would cause a visual artifact of having a constraint with a different radius and an unscaled circle + // so better scale the circles. + std::vector polegeoids; + polegeoids.reserve(weights.size()); + + for ( auto ic : getSketchObject()->Constraints.getValues()) { + if( ic->Type == InternalAlignment && ic->AlignmentType == BSplineControlPoint && ic->Second == c->Second) { + polegeoids.push_back(ic->First); + } + } + + for ( auto ic : getSketchObject()->Constraints.getValues()) { + if( ic->Type == Weight ) { + auto pos = std::find(polegeoids.begin(), polegeoids.end(), ic->First); + + if(pos != polegeoids.end()) { + vradius = ic->getValue() * scalefactor; + break; // one is enough, otherwise it would not be non-rational + } + } + } + } + + Part::GeomCircle * tmpcircle; + + if(geometrywithmemoryallocation) { // with memory allocation + tmpcircle = const_cast(circle); + tmpcircle->setRadius(vradius); + } + else { // without memory allocation + tmpcircle = static_cast(circle->clone()); + tmpcircle->setRadius(vradius); + tempGeo[GeoId] = GeometryFacade::getFacade(tmpcircle, true); // this is the circle that will be drawn, with the updated vradius, the facade takes ownership and will deallocate. + } + + // save scale factor for any prospective dragging operation + // 1. Solver must be updated, in case a dragging operation starts + // 2. if temp geometry is being used (with memory allocation), then the copy we have here must be updated. If + // no temp geometry is being used, then the normal geometry must be updated. + {// make solver be ready for a dragging operation + auto vpext = std::make_unique(); + vpext->setRepresentationFactor(scalefactor); + + getSketchObject()->updateSolverExtension(GeoId, std::move(vpext)); + } + + if(!circle->hasExtension(SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId())) + { + // It is ok to add this kind of extension to a const geometry because: + // 1. It does not modify the object in a way that affects property state, just ViewProvider representation + // 2. If it is lost (for example upon undo), redrawing will reinstate it with the correct value + const_cast(circle)->setExtension(std::make_unique()); + } + + auto vpext = std::const_pointer_cast( + std::static_pointer_cast( + circle->getExtension(SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId()).lock())); + + vpext->setRepresentationFactor(scalefactor); + } + break; + } + } + } + } + } +} + +void ViewProviderSketch::draw(bool temp /*=false*/, bool rebuildinformationoverlay /*=true*/) +{ + assert(isInEditMode()); + + // ============== Retrieve geometry to be represented ================================= + + auto geolistfacade = temp ? + getSolvedSketch().extractGeoListFacade(): // with memory allocation + getSketchObject()->getGeoListFacade(); // without memory allocation + + assert(int( geolistfacade.geomlist.size()) >= 2); + + // ============== Prepare geometry for representation ================================== + + // ************ Manage BSpline pole circle scaling **************************** + + // This function ensures that the geometry used for drawing takes into account: + // 1. the OCC mandated weight, which is normalised for non-rational BSplines, but not normalised for rational BSplines. + // That includes properly sizing for drawing any weight constraint. + // This function ensures that both the geometry of the SketchObject and solver are updated with the new value of the scaling factor (via the extension) + // 2. the scaling factor, including inserting the scaling factor into the ViewProviderSketchGeometryExtension so as to enable + // That ensures that dragging operations on the circles of the poles of the B-Splines are properly rendered. + // + // This function takes a reference to a vector of deep copies to delete. These deep copies are necessary to transparently perform (1) while doing (2). + + scaleBSplinePoleCirclesAndUpdateSolverAndSketchObjectGeometry(geolistfacade, temp); + + // ============== Render geometry, constraints and geometry information overlays ================================== + + editCoinManager->processGeometryConstraintsInformationOverlay(geolistfacade, rebuildinformationoverlay); + + // Avoids unneeded calls to pixmapFromSvg + if(Mode==STATUS_NONE || Mode==STATUS_SKETCH_UseHandler) { + editCoinManager->drawConstraintIcons(geolistfacade); + editCoinManager->updateColor(geolistfacade); + } + + Gui::MDIView *mdi = this->getActiveView(); + if (mdi && mdi->isDerivedFrom(Gui::View3DInventor::getClassTypeId())) { + static_cast(mdi)->getViewer()->redraw(); + } +} + +void ViewProviderSketch::setIsShownVirtualSpace(bool isshownvirtualspace) +{ + viewProviderParameters.isShownVirtualSpace = isshownvirtualspace; + + editCoinManager->updateVirtualSpace(); + + signalConstraintsChanged(); +} + +bool ViewProviderSketch::getIsShownVirtualSpace() const +{ + return viewProviderParameters.isShownVirtualSpace; +} + + +void ViewProviderSketch::drawEdit(const std::vector &EditCurve) +{ + editCoinManager->drawEdit(EditCurve); +} + +void ViewProviderSketch::drawEdit(const std::list> &list) +{ + editCoinManager->drawEdit(list); +} + +void ViewProviderSketch::drawEditMarkers(const std::vector &EditMarkers, unsigned int augmentationlevel) +{ + editCoinManager->drawEditMarkers(EditMarkers, augmentationlevel); +} + +void ViewProviderSketch::updateData(const App::Property *prop) +{ + ViewProvider2DObject::updateData(prop); + + // In the case of an undo/redo transaction, updateData is triggered by SketchObject::onUndoRedoFinished() in the solve() + // In the case of an internal transaction, touching the geometry results in a call to updateData. + if ( isInEditMode() && !getSketchObject()->getDocument()->isPerformingTransaction() && + !getSketchObject()->isPerformingInternalTransaction() && + (prop == &(getSketchObject()->Geometry) || prop == &(getSketchObject()->Constraints))) { + + // At this point, we do not need to solve the Sketch + // If we are adding geometry an update can be triggered before the sketch is actually solved. + // Because a solve is mandatory to any addition (at least to update the DoF of the solver), + // only when the solver geometry is the same in number than the sketch geometry an update + // should trigger a redraw. This reduces even more the number of redraws per insertion of geometry + + // solver information is also updated when no matching geometry, so that if a solving fails + // this failed solving info is presented to the user + UpdateSolverInformation(); // just update the solver window with the last SketchObject solving information + + if(getSketchObject()->getExternalGeometryCount()+getSketchObject()->getHighestCurveIndex() + 1 == + getSolvedSketch().getGeometrySize()) { + Gui::MDIView *mdi = Gui::Application::Instance->editDocument()->getActiveView(); + if (mdi->isDerivedFrom(Gui::View3DInventor::getClassTypeId())) + draw(false,true); + + signalConstraintsChanged(); + } + + if(prop != &getSketchObject()->Constraints) + signalElementsChanged(); + } +} + +void ViewProviderSketch::onChanged(const App::Property *prop) +{ + // call father + ViewProviderPart::onChanged(prop); +} + +void ViewProviderSketch::attach(App::DocumentObject *pcFeat) +{ + ViewProviderPart::attach(pcFeat); +} + +void ViewProviderSketch::setupContextMenu(QMenu *menu, QObject *receiver, const char *member) +{ + menu->addAction(tr("Edit sketch"), receiver, member); + // Call the extensions + Gui::ViewProvider::setupContextMenu(menu, receiver, member); +} + +namespace +{ + inline const QStringList editModeToolbarNames() + { + return QStringList{ QString::fromLatin1("Sketcher Edit Mode"), + QString::fromLatin1("Sketcher geometries"), + QString::fromLatin1("Sketcher constraints"), + QString::fromLatin1("Sketcher tools"), + QString::fromLatin1("Sketcher B-spline tools"), + QString::fromLatin1("Sketcher virtual space") }; + } + + inline const QStringList nonEditModeToolbarNames() + { + return QStringList{ QString::fromLatin1("Structure"), + QString::fromLatin1("Sketcher") }; + } +} + +bool ViewProviderSketch::setEdit(int ModNum) +{ + Q_UNUSED(ModNum) + // When double-clicking on the item for this sketch the + // object unsets and sets its edit mode without closing + // the task panel + Gui::TaskView::TaskDialog *dlg = Gui::Control().activeDialog(); + TaskDlgEditSketch *sketchDlg = qobject_cast(dlg); + if (sketchDlg && sketchDlg->getSketchView() != this) + sketchDlg = nullptr; // another sketch left open its task panel + if (dlg && !sketchDlg) { + QMessageBox msgBox; + msgBox.setText(tr("A dialog is already open in the task panel")); + msgBox.setInformativeText(tr("Do you want to close this dialog?")); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::Yes); + int ret = msgBox.exec(); + if (ret == QMessageBox::Yes) + Gui::Control().reject(); + else + return false; + } + + Sketcher::SketchObject* sketch = getSketchObject(); + if (!sketch->evaluateConstraints()) { + QMessageBox box(Gui::getMainWindow()); + box.setIcon(QMessageBox::Critical); + box.setWindowTitle(tr("Invalid sketch")); + box.setText(tr("Do you want to open the sketch validation tool?")); + box.setInformativeText(tr("The sketch is invalid and cannot be edited.")); + box.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + box.setDefaultButton(QMessageBox::Yes); + switch (box.exec()) + { + case QMessageBox::Yes: + Gui::Control().showDialog(new TaskSketcherValidation(getSketchObject())); + break; + default: + break; + } + return false; + } + + // clear the selection (convenience) + Gui::Selection().clearSelection(); + Gui::Selection().rmvPreselect(); + + this->attachSelection(); + + auto gridnode = getGridNode(); + addNodeToRoot(gridnode); + setGridEnabled(true); + + // create the container for the additional edit data + assert(!isInEditMode()); + preselection.reset(); + selection.reset(); + editCoinManager = std::make_unique(*this); + + auto editDoc = Gui::Application::Instance->editDocument(); + App::DocumentObject *editObj = getSketchObject(); + std::string editSubName; + ViewProviderDocumentObject *editVp = nullptr; + if(editDoc) { + editDoc->getInEdit(&editVp,&editSubName); + if(editVp) + editObj = editVp->getObject(); + } + + //visibility automation + try{ + Gui::Command::addModule(Gui::Command::Gui,"Show"); + try{ + QString cmdstr = QString::fromLatin1( + "ActiveSketch = App.getDocument('%1').getObject('%2')\n" + "tv = Show.TempoVis(App.ActiveDocument, tag= ActiveSketch.ViewObject.TypeId)\n" + "ActiveSketch.ViewObject.TempoVis = tv\n" + "if ActiveSketch.ViewObject.EditingWorkbench:\n" + " tv.activateWorkbench(ActiveSketch.ViewObject.EditingWorkbench)\n" + "if ActiveSketch.ViewObject.HideDependent:\n" + " tv.hide(tv.get_all_dependent(%3, '%4'))\n" + "if ActiveSketch.ViewObject.ShowSupport:\n" + " tv.show([ref[0] for ref in ActiveSketch.Support if not ref[0].isDerivedFrom(\"PartDesign::Plane\")])\n" + "if ActiveSketch.ViewObject.ShowLinks:\n" + " tv.show([ref[0] for ref in ActiveSketch.ExternalGeometry])\n" + "tv.sketchClipPlane(ActiveSketch, ActiveSketch.ViewObject.SectionView)\n" + "tv.hide(ActiveSketch)\n" + "del(tv)\n" + "del(ActiveSketch)\n" + ).arg(QString::fromLatin1(getDocument()->getDocument()->getName()), + QString::fromLatin1(getSketchObject()->getNameInDocument()), + QString::fromLatin1(Gui::Command::getObjectCmd(editObj).c_str()), + QString::fromLatin1(editSubName.c_str())); + QByteArray cmdstr_bytearray = cmdstr.toLatin1(); + Gui::Command::runCommand(Gui::Command::Gui, cmdstr_bytearray); + } catch (Base::PyException &e){ + Base::Console().Error("ViewProviderSketch::setEdit: visibility automation failed with an error: \n"); + e.ReportException(); + } + } catch (Base::PyException &){ + Base::Console().Warning("ViewProviderSketch::setEdit: could not import Show module. Visibility automation will not work.\n"); + } + + // start the edit dialog + if (sketchDlg) + Gui::Control().showDialog(sketchDlg); + else + Gui::Control().showDialog(new TaskDlgEditSketch(this)); + + // This call to the solver is needed to initialize the DoF and solve time controls + // The false parameter indicates that the geometry of the SketchObject shall not be updateData + // so as not to trigger an onChanged that would set the document as modified and trigger a recompute + // if we just close the sketch without touching anything. + if (getSketchObject()->Support.getValue()) { + if (!getSketchObject()->evaluateSupport()) + getSketchObject()->validateExternalLinks(); + } + + // There are geometry extensions introduced by the solver and geometry extensions introduced by the viewprovider. + // 1. It is important that the solver has geometry with updated extensions. + // 2. It is important that the viewprovider has up-to-date solver information + // + // The decision is to maintain the "first solve then draw" order, which is consistent with the rest of the Sketcher + // for example in geometry creation. Then, the ViewProvider is responsible for updating the solver geometry when + // appropriate, as it is the ViewProvider that is introducing its geometry extensions. + // + // In order to have updated solver information, solve must take "true", this cause the Geometry property to be updated + // with the solver information, including solver extensions, and triggers a draw(true) via ViewProvider::UpdateData. + getSketchObject()->solve(true); + + connectUndoDocument = getDocument() + ->signalUndoDocument.connect(boost::bind(&ViewProviderSketch::slotUndoDocument, this, bp::_1)); + connectRedoDocument = getDocument() + ->signalRedoDocument.connect(boost::bind(&ViewProviderSketch::slotRedoDocument, this, bp::_1)); + + // Enable solver initial solution update while dragging. + getSketchObject()->setRecalculateInitialSolutionWhileMovingPoint(viewProviderParameters.recalculateInitialSolutionWhileDragging); + + // intercept del key press from main app + listener = new ShortcutListener(this); + + Gui::getMainWindow()->installEventFilter(listener); + + /*Modify toolbars dynamically. + First save state of toolbars in case user changed visibility of a toolbar but he's not changing the wb. + This happens in someone works directly from sketcher, changing from edit mode to not-edit-mode*/ + Gui::ToolBarManager::getInstance()->saveState(); + + Gui::ToolBarManager::getInstance()->setToolbarVisibility(true, editModeToolbarNames()); + Gui::ToolBarManager::getInstance()->setToolbarVisibility(false, nonEditModeToolbarNames()); + + return true; +} + +QString ViewProviderSketch::appendConflictMsg(const std::vector &conflicting) +{ + return appendConstraintMsg(tr("Please remove the following constraint:"), + tr("Please remove at least one of the following constraints:"), + conflicting); +} + +QString ViewProviderSketch::appendRedundantMsg(const std::vector &redundant) +{ + return appendConstraintMsg(tr("Please remove the following redundant constraint:"), + tr("Please remove the following redundant constraints:"), + redundant); +} + +QString ViewProviderSketch::appendPartiallyRedundantMsg(const std::vector &partiallyredundant) +{ + return appendConstraintMsg(tr("The following constraint is partially redundant:"), + tr("The following constraints are partially redundant:"), + partiallyredundant); +} + +QString ViewProviderSketch::appendMalformedMsg(const std::vector &malformed) +{ + return appendConstraintMsg(tr("Please remove the following malformed constraint:"), + tr("Please remove the following malformed constraints:"), + malformed); +} + +QString ViewProviderSketch::appendConstraintMsg(const QString & singularmsg, + const QString & pluralmsg, + const std::vector &vector) +{ + QString msg; + QTextStream ss(&msg); + if (!vector.empty()) { + if (vector.size() == 1) + ss << singularmsg; + else + ss << pluralmsg; + ss << "\n"; + ss << vector[0]; + for (unsigned int i=1; i < vector.size(); i++) + ss << ", " << vector[i]; + + ss << "\n"; + } + return msg; +} + +inline QString intListHelper(const std::vector &ints) +{ + QString results; + if (ints.size() < 8) { // The 8 is a bit heuristic... more than that and we shift formats + for (const auto i : ints) { + if (results.isEmpty()) + results.append(QString::fromUtf8("%1").arg(i)); + else + results.append(QString::fromUtf8(", %1").arg(i)); + } + } + else { + const int numToShow = 3; + int more = ints.size() - numToShow; + for (int i = 0; i < numToShow; ++i) { + results.append(QString::fromUtf8("%1, ").arg(ints[i])); + } + results.append(QCoreApplication::translate("ViewProviderSketch","and %1 more").arg(more)); + } + std::string testString = results.toStdString(); + return results; +} + +void ViewProviderSketch::UpdateSolverInformation() +{ + // Updates Solver Information with the Last solver execution at SketchObject level + int dofs = getSketchObject()->getLastDoF(); + bool hasConflicts = getSketchObject()->getLastHasConflicts(); + bool hasRedundancies = getSketchObject()->getLastHasRedundancies(); + bool hasPartiallyRedundant = getSketchObject()->getLastHasPartialRedundancies(); + bool hasMalformed = getSketchObject()->getLastHasMalformedConstraints(); + + if (getSketchObject()->Geometry.getSize() == 0) { + signalSetUp(QString::fromUtf8("empty_sketch"), tr("Empty sketch"), QString(), QString()); + } + else if (dofs < 0 || hasConflicts) { // over-constrained sketch + signalSetUp(QString::fromUtf8("conflicting_constraints"), + tr("Over-constrained: "), + QString::fromUtf8("#conflicting"), + QString::fromUtf8("(%1)").arg(intListHelper(getSketchObject()->getLastConflicting()))); + } + else if (hasMalformed) { // malformed constraints + signalSetUp(QString::fromUtf8("malformed_constraints"), + tr("Malformed constraints: "), + QString::fromUtf8("#malformed"), + QString::fromUtf8("(%1)").arg(intListHelper(getSketchObject()->getLastMalformedConstraints()))); + } + else if (hasRedundancies) { + signalSetUp(QString::fromUtf8("redundant_constraints"), + tr("Redundant constraints:"), + QString::fromUtf8("#redundant"), + QString::fromUtf8("(%1)").arg(intListHelper(getSketchObject()->getLastRedundant()))); + } + else if (hasPartiallyRedundant) { + signalSetUp(QString::fromUtf8("partially_redundant_constraints"), + tr("Partially redundant:"), + QString::fromUtf8("#partiallyredundant"), + QString::fromUtf8("(%1)").arg(intListHelper(getSketchObject()->getLastPartiallyRedundant()))); + } + else if (getSketchObject()->getLastSolverStatus() != 0) { + signalSetUp(QString::fromUtf8("solver_failed"), + tr("Solver failed to converge"), + QString::fromUtf8(""), + QString::fromUtf8("")); + } else if (dofs > 0) { + signalSetUp(QString::fromUtf8("under_constrained"), + tr("Under constrained:"), + QString::fromUtf8("#dofs"), + tr("%n DoF(s)","",dofs)); + } + else { + signalSetUp(QString::fromUtf8("fully_constrained"), tr("Fully constrained"), QString(), QString()); + } +} + +void ViewProviderSketch::unsetEdit(int ModNum) +{ + Q_UNUSED(ModNum); + + setGridEnabled(false); + auto gridnode = getGridNode(); + pcRoot->removeChild(gridnode); + + /*Modify toolbars dynamically. + First save state of toolbars in case user changed visibility of a toolbar but he's not changing the wb. + This happens in someone works directly from sketcher, changing from edit mode to not-edit-mode*/ + Gui::ToolBarManager::getInstance()->saveState(); + + Gui::ToolBarManager::getInstance()->setToolbarVisibility(false, editModeToolbarNames()); + Gui::ToolBarManager::getInstance()->setToolbarVisibility(true, nonEditModeToolbarNames()); + + if(listener) { + Gui::getMainWindow()->removeEventFilter(listener); + delete listener; + } + + if (isInEditMode()) { + if (sketchHandler) + deactivateHandler(); + + editCoinManager = nullptr; + preselection.reset(); + selection.reset(); + this->detachSelection(); + + App::AutoTransaction trans("Sketch recompute"); + try { + // and update the sketch + // getSketchObject()->getDocument()->recompute(); + Gui::Command::updateActive(); + } + catch (...) { + } + } + + // clear the selection and set the new/edited sketch(convenience) + Gui::Selection().clearSelection(); + Gui::Selection().addSelection(editDocName.c_str(),editObjName.c_str(),editSubName.c_str()); + + connectUndoDocument.disconnect(); + connectRedoDocument.disconnect(); + + // when pressing ESC make sure to close the dialog + Gui::Control().closeDialog(); + + //visibility autoation + try{ + QString cmdstr = QString::fromLatin1( + "ActiveSketch = App.getDocument('%1').getObject('%2')\n" + "tv = ActiveSketch.ViewObject.TempoVis\n" + "if tv:\n" + " tv.restore()\n" + "ActiveSketch.ViewObject.TempoVis = None\n" + "del(tv)\n" + "del(ActiveSketch)\n" + ).arg(QString::fromLatin1(getDocument()->getDocument()->getName()), + QString::fromLatin1(getSketchObject()->getNameInDocument())); + QByteArray cmdstr_bytearray = cmdstr.toLatin1(); + Gui::Command::runCommand(Gui::Command::Gui, cmdstr_bytearray); + } catch (Base::PyException &e){ + Base::Console().Error("ViewProviderSketch::unsetEdit: visibility automation failed with an error: \n"); + e.ReportException(); + } +} + +void ViewProviderSketch::setEditViewer(Gui::View3DInventorViewer* viewer, int ModNum) +{ + Q_UNUSED(ModNum); + //visibility automation: save camera + if (! this->TempoVis.getValue().isNone()){ + try{ + QString cmdstr = QString::fromLatin1( + "ActiveSketch = App.getDocument('%1').getObject('%2')\n" + "if ActiveSketch.ViewObject.RestoreCamera:\n" + " ActiveSketch.ViewObject.TempoVis.saveCamera()\n" + " if ActiveSketch.ViewObject.ForceOrtho:\n" + " ActiveSketch.ViewObject.Document.ActiveView.setCameraType('Orthographic')\n" + ).arg(QString::fromLatin1(getDocument()->getDocument()->getName()), + QString::fromLatin1(getSketchObject()->getNameInDocument())); + QByteArray cmdstr_bytearray = cmdstr.toLatin1(); + Gui::Command::runCommand(Gui::Command::Gui, cmdstr_bytearray); + } catch (Base::PyException &e){ + Base::Console().Error("ViewProviderSketch::setEdit: visibility automation failed with an error: \n"); + e.ReportException(); + } + } + + auto editDoc = Gui::Application::Instance->editDocument(); + editDocName.clear(); + if(editDoc) { + ViewProviderDocumentObject *parent=nullptr; + editDoc->getInEdit(&parent,&editSubName); + if(parent) { + editDocName = editDoc->getDocument()->getName(); + editObjName = parent->getObject()->getNameInDocument(); + } + } + if(editDocName.empty()) { + editDocName = getObject()->getDocument()->getName(); + editObjName = getObject()->getNameInDocument(); + editSubName.clear(); + } + const char *dot = strrchr(editSubName.c_str(),'.'); + if(!dot) + editSubName.clear(); + else + editSubName.resize(dot-editSubName.c_str()+1); + + Base::Placement plm = getEditingPlacement(); + Base::Rotation tmp(plm.getRotation()); + + SbRotation rot((float)tmp[0],(float)tmp[1],(float)tmp[2],(float)tmp[3]); + + // Will the sketch be visible from the new position (#0000957)? + // + SoCamera* camera = viewer->getSoRenderManager()->getCamera(); + SbVec3f curdir; // current view direction + camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), curdir); + SbVec3f focal = camera->position.getValue() + + camera->focalDistance.getValue() * curdir; + + SbVec3f newdir; // future view direction + rot.multVec(SbVec3f(0, 0, -1), newdir); + SbVec3f newpos = focal - camera->focalDistance.getValue() * newdir; + + SbVec3f plnpos = Base::convertTo(plm.getPosition()); + double dist = (plnpos - newpos).dot(newdir); + if (dist < 0) { + float focalLength = camera->focalDistance.getValue() - dist + 5; + camera->position = focal - focalLength * curdir; + camera->focalDistance.setValue(focalLength); + } + + viewer->setCameraOrientation(rot); + + viewer->setEditing(true); + SoNode* root = viewer->getSceneGraph(); + static_cast(root)->selectionRole.setValue(false); + + viewer->addGraphicsItem(rubberband.get()); + rubberband->setViewer(viewer); + + viewer->setupEditingRoot(); + + cameraSensor.setData(new VPRender{this, viewer->getSoRenderManager()}); + cameraSensor.attach(viewer->getSoRenderManager()->getSceneGraph()); +} + +void ViewProviderSketch::unsetEditViewer(Gui::View3DInventorViewer* viewer) +{ + auto dataPtr = static_cast(cameraSensor.getData()); + delete dataPtr; + cameraSensor.setData(nullptr); + cameraSensor.detach(); + + viewer->removeGraphicsItem(rubberband.get()); + viewer->setEditing(false); + SoNode* root = viewer->getSceneGraph(); + static_cast(root)->selectionRole.setValue(true); +} + +void ViewProviderSketch::camSensCB(void *data, SoSensor *) +{ + VPRender *proxyVPrdr = static_cast(data); + if (!proxyVPrdr) + return; + + auto vp = proxyVPrdr->vp; + auto cam = proxyVPrdr->renderMgr->getCamera(); + + vp->onCameraChanged(cam); +} + +void ViewProviderSketch::onCameraChanged(SoCamera* cam) +{ + auto rotSk = Base::Rotation(getDocument()->getEditingTransform()); //sketch orientation + auto rotc = cam->orientation.getValue().getValue(); + auto rotCam = Base::Rotation(rotc[0], rotc[1], rotc[2], rotc[3]); // camera orientation (needed because float to double conversion) + + // Is camera in the same hemisphere as positive sketch normal ? + auto orientation = (rotCam.invert() * rotSk).multVec(Base::Vector3d(0, 0, 1)); + auto tmpFactor = orientation.z < 0 ? -1 : 1; + + if (tmpFactor != viewOrientationFactor) { // redraw only if viewing side changed + Base::Console().Log("Switching side, now %s, redrawing\n", tmpFactor < 0 ? "back" : "front"); + viewOrientationFactor = tmpFactor; + draw(); + + QString cmdStr = QStringLiteral( + "ActiveSketch.ViewObject.TempoVis.sketchClipPlane(ActiveSketch, ActiveSketch.ViewObject.SectionView, %1)\n") + .arg(tmpFactor < 0 ? QLatin1String("True") : QLatin1String("False")); + Base::Interpreter().runStringObject(cmdStr.toLatin1()); + } + + drawGrid(true); +} + +int ViewProviderSketch::getPreselectPoint() const +{ + if (isInEditMode()) + return preselection.PreselectPoint; + return -1; +} + +int ViewProviderSketch::getPreselectCurve() const +{ + if (isInEditMode()) + return preselection.PreselectCurve; + return -1; +} + +int ViewProviderSketch::getPreselectCross() const +{ + // TODO: This function spreads over several files. It should be refactored into something less "numeric" at a second stage. + if (isInEditMode()) + return static_cast(preselection.PreselectCross); + return -1; +} + +Sketcher::SketchObject *ViewProviderSketch::getSketchObject() const +{ + return dynamic_cast(pcObject); +} + +const Sketcher::Sketch &ViewProviderSketch::getSolvedSketch() const +{ + return const_cast(getSketchObject())->getSolvedSketch(); +} + +void ViewProviderSketch::deleteSelected() +{ + std::vector selection; + selection = Gui::Selection().getSelectionEx(nullptr, Sketcher::SketchObject::getClassTypeId()); + + // only one sketch with its subelements are allowed to be selected + if (selection.size() != 1) { + Base::Console().Warning("Delete: Selection not restricted to one sketch and its subelements\n"); + return; + } + + // get the needed lists and objects + const std::vector &SubNames = selection[0].getSubNames(); + + if(!SubNames.empty()) { + App::Document* doc = getSketchObject()->getDocument(); + + doc->openTransaction("Delete sketch geometry"); + + onDelete(SubNames); + + doc->commitTransaction(); + } +} + +bool ViewProviderSketch::onDelete(const std::vector &subList) +{ + if (isInEditMode()) { + std::vector SubNames = subList; + + Gui::Selection().clearSelection(); + resetPreselectPoint(); + + std::set delInternalGeometries, delExternalGeometries, delCoincidents, delConstraints; + // go through the selected subelements + for (std::vector::const_iterator it=SubNames.begin(); it != SubNames.end(); ++it) { + if (it->size() > 4 && it->substr(0,4) == "Edge") { + int GeoId = std::atoi(it->substr(4,4000).c_str()) - 1; + if( GeoId >= 0 ) { + delInternalGeometries.insert(GeoId); + } + else + delExternalGeometries.insert(Sketcher::GeoEnum::RefExt - GeoId); + } else if (it->size() > 12 && it->substr(0,12) == "ExternalEdge") { + int GeoId = std::atoi(it->substr(12,4000).c_str()) - 1; + delExternalGeometries.insert(GeoId); + } else if (it->size() > 6 && it->substr(0,6) == "Vertex") { + int VtId = std::atoi(it->substr(6,4000).c_str()) - 1; + int GeoId; + Sketcher::PointPos PosId; + getSketchObject()->getGeoVertexIndex(VtId, GeoId, PosId); + if (getSketchObject()->getGeometry(GeoId)->getTypeId() + == Part::GeomPoint::getClassTypeId()) { + if(GeoId>=0) + delInternalGeometries.insert(GeoId); + else + delExternalGeometries.insert(Sketcher::GeoEnum::RefExt - GeoId); + } + else + delCoincidents.insert(VtId); + } else if (*it == "RootPoint") { + delCoincidents.insert(Sketcher::GeoEnum::RtPnt); + } else if (it->size() > 10 && it->substr(0,10) == "Constraint") { + int ConstrId = Sketcher::PropertyConstraintList::getIndexFromConstraintName(*it); + delConstraints.insert(ConstrId); + } + } + + // We stored the vertices, but is there really a coincident constraint? Check + const std::vector< Sketcher::Constraint * > &vals = getSketchObject()->Constraints.getValues(); + + std::set::const_reverse_iterator rit; + + for (rit = delConstraints.rbegin(); rit != delConstraints.rend(); ++rit) { + try { + Gui::cmdAppObjectArgs(getObject(), "delConstraint(%i)", *rit); + } + catch (const Base::Exception& e) { + Base::Console().Error("%s\n", e.what()); + } + } + + for (rit = delCoincidents.rbegin(); rit != delCoincidents.rend(); ++rit) { + int GeoId; + PointPos PosId; + + if (*rit == GeoEnum::RtPnt) { // RootPoint + GeoId = Sketcher::GeoEnum::RtPnt; + PosId = Sketcher::PointPos::start; + + } else { + getSketchObject()->getGeoVertexIndex(*rit, GeoId, PosId); + } + + if (GeoId != GeoEnum::GeoUndef) { + for (std::vector< Sketcher::Constraint * >::const_iterator it= vals.begin(); it != vals.end(); ++it) { + if (((*it)->Type == Sketcher::Coincident) && (((*it)->First == GeoId && (*it)->FirstPos == PosId) || + ((*it)->Second == GeoId && (*it)->SecondPos == PosId)) ) { + try { + Gui::cmdAppObjectArgs(getObject(), "delConstraintOnPoint(%i,%i)", GeoId, (int)PosId); + } + catch (const Base::Exception& e) { + Base::Console().Error("%s\n", e.what()); + } + break; + } + } + } + } + + if(!delInternalGeometries.empty()) { + std::stringstream stream; + + // NOTE: SketchObject delGeometries will sort the array, so filling it here with a reverse iterator would + // lead to the worst case scenario for sorting. + auto endit = std::prev(delInternalGeometries.end()); + for (auto it = delInternalGeometries.begin(); it != endit; ++it) { + stream << *it << ","; + } + + stream << *endit; + + try { + Gui::cmdAppObjectArgs(getObject(), "delGeometries([%s])", stream.str().c_str()); + } + catch (const Base::Exception& e) { + Base::Console().Error("%s\n", e.what()); + } + + stream.str(std::string()); + } + + for (rit = delExternalGeometries.rbegin(); rit != delExternalGeometries.rend(); ++rit) { + try { + Gui::cmdAppObjectArgs(getObject(), "delExternal(%i)", *rit); + } + catch (const Base::Exception& e) { + Base::Console().Error("%s\n", e.what()); + } + } + + int ret=getSketchObject()->solve(); + + if(ret!=0){ + // if the sketched could not be solved, we first redraw to update the UI geometry as + // onChanged did not update it. + UpdateSolverInformation(); + draw(false,true); + + signalConstraintsChanged(); + signalElementsChanged(); + } + + // Notes on solving and recomputing: + // + // This function is generally called from StdCmdDelete::activated + // Since 2015-05-03 that function includes a recompute at the end. + + // Since December 2018, the function is no longer called from StdCmdDelete::activated, + // as there is an event filter installed that intercepts the del key event. So now we do + // need to tidy up after ourselves again. + + if (viewProviderParameters.autoRecompute) { + Gui::Command::updateActive(); + } + else { + editCoinManager->drawConstraintIcons(); + this->updateColor(); + } + + // if in edit not delete the object + return false; + } + // if not in edit delete the whole object + return PartGui::ViewProviderPart::onDelete(subList); +} + +QIcon ViewProviderSketch::mergeColorfulOverlayIcons (const QIcon & orig) const +{ + QIcon mergedicon = orig; + + if(!getSketchObject()->FullyConstrained.getValue()) { + QPixmap px; + + static const char * const sketcher_notfullyconstrained_xpm[]={ + "9 9 3 1", + ". c None", + "# c #dbaf00", + "a c #ffcc00", + "##.....##", + "#a#...#a#", + "#aa#.#aa#", + ".#a#.#a#.", + ".#a#.#a#.", + ".#a#.#a#.", + "#aa#.#aa#", + "#a#...#a#", + "##.....##"}; + px = QPixmap( sketcher_notfullyconstrained_xpm ); + + mergedicon = Gui::BitmapFactoryInst::mergePixmap(mergedicon, px, Gui::BitmapFactoryInst::BottomRight); + + } + + return Gui::ViewProvider::mergeColorfulOverlayIcons (mergedicon); +} + +/*************************** functions ViewProviderSketch offers to friends such as DrawHandlerSketch ************************/ + +void ViewProviderSketch::setConstraintSelectability(bool enabled /*= true*/) +{ + editCoinManager->setConstraintSelectability(enabled); +} + +void ViewProviderSketch::setPositionText(const Base::Vector2d &Pos, const SbString &text) +{ + editCoinManager->setPositionText(Pos,text); +} + +void ViewProviderSketch::setPositionText(const Base::Vector2d &Pos) +{ + editCoinManager->setPositionText(Pos); +} + +void ViewProviderSketch::resetPositionText() +{ + editCoinManager->resetPositionText(); +} + +void ViewProviderSketch::setPreselectPoint(int PreselectPoint) +{ + preselection.PreselectPoint = PreselectPoint; + preselection.PreselectCurve = Preselection::InvalidCurve; + preselection.PreselectCross = Preselection::Axes::None;; + preselection.PreselectConstraintSet.clear(); +} + +void ViewProviderSketch::setPreselectRootPoint() +{ + preselection.PreselectPoint = Preselection::InvalidPoint; + preselection.PreselectCurve = Preselection::InvalidCurve; + preselection.PreselectCross = Preselection::Axes::RootPoint; + preselection.PreselectConstraintSet.clear(); +} + + +void ViewProviderSketch::resetPreselectPoint() +{ + preselection.PreselectPoint = Preselection::InvalidPoint; + preselection.PreselectCurve = Preselection::InvalidCurve; + preselection.PreselectCross = Preselection::Axes::None;; + preselection.PreselectConstraintSet.clear(); +} + +void ViewProviderSketch::addSelectPoint(int SelectPoint) +{ + selection.SelPointSet.insert(SelectPoint); +} + +void ViewProviderSketch::removeSelectPoint(int SelectPoint) +{ + selection.SelPointSet.erase(SelectPoint); +} + +void ViewProviderSketch::clearSelectPoints() +{ + selection.SelPointSet.clear(); +} + +bool ViewProviderSketch::isSelected(const std::string &subNameSuffix) const +{ + return Gui::Selection().isSelected(editDocName.c_str(), editObjName.c_str(), (editSubName+subNameSuffix).c_str()); +} + +void ViewProviderSketch::rmvSelection(const std::string &subNameSuffix) +{ + Gui::Selection().rmvSelection(editDocName.c_str(), editObjName.c_str(), (editSubName+subNameSuffix).c_str()); +} + +bool ViewProviderSketch::addSelection(const std::string &subNameSuffix, float x, float y, float z) +{ + return Gui::Selection().addSelection(editDocName.c_str(), editObjName.c_str(), (editSubName+subNameSuffix).c_str(), x , y, z); +} + +bool ViewProviderSketch::addSelection2(const std::string &subNameSuffix, float x, float y, float z) +{ + return Gui::Selection().addSelection2(editDocName.c_str(), editObjName.c_str(), (editSubName+subNameSuffix).c_str(), x , y, z); +} + +bool ViewProviderSketch::setPreselect(const std::string &subNameSuffix, float x, float y, float z) +{ + return Gui::Selection().setPreselect(editDocName.c_str(), editObjName.c_str(), (editSubName+subNameSuffix).c_str(), x , y, z); +} + +/*************************** private functions to decouple Attorneys and Clients ********************************************/ + +// Establishes a private collaboration interface with EditModeCoinManager to perform EditModeCoinManager tasks, while abstracting EditModeCoinManager +// from the specific ViewProviderSketch implementation, while allowing ViewProviderSketch to fully delegate coin management. + +const std::vector ViewProviderSketch::getConstraints() const +{ + return getSketchObject()->Constraints.getValues(); +} + +const GeoList ViewProviderSketch::getGeoList() const +{ + const std::vector tempGeo = getSketchObject()->getCompleteGeometry(); // without memory allocation + + int intGeoCount = getSketchObject()->getHighestCurveIndex() + 1; + + auto geolist = GeoList::getGeoListModel(std::move(tempGeo), intGeoCount); + + return geolist; +} + +bool ViewProviderSketch::constraintHasExpression(int constrid) const +{ + return getSketchObject()->constraintHasExpression(constrid); +} + +std::unique_ptr ViewProviderSketch::getRayPickAction() const +{ + assert(isInEditMode()); + Gui::MDIView *mdi = Gui::Application::Instance->editViewOfNode(editCoinManager->getRootEditNode()); + if (!(mdi && mdi->isDerivedFrom(Gui::View3DInventor::getClassTypeId()))) + return nullptr; + Gui::View3DInventorViewer *viewer = static_cast(mdi)->getViewer(); + + return std::make_unique(viewer->getSoRenderManager()->getViewportRegion()); +} + +SbVec2f ViewProviderSketch::getScreenCoordinates(SbVec2f sketchcoordinates) const +{ + + Base::Placement sketchPlacement = getEditingPlacement(); + Base::Vector3d sketchPos(sketchPlacement.getPosition()); + Base::Rotation sketchRot(sketchPlacement.getRotation()); + + // get global coordinates from sketcher coordinates + Base::Vector3d pos(sketchcoordinates[0], sketchcoordinates[1],0); + sketchRot.multVec(pos,pos); + pos = pos + sketchPos; + + Gui::MDIView *mdi = this->getActiveView(); + Gui::View3DInventor *view = qobject_cast(mdi); + if (!view || !isInEditMode()) + return SbVec2f(0,0); + + Gui::View3DInventorViewer* viewer = view->getViewer(); + + SoCamera* pCam = viewer->getSoRenderManager()->getCamera(); + + if (!pCam) + return SbVec2f(0,0); + + SbViewVolume vol = pCam->getViewVolume(); + Gui::ViewVolumeProjection proj(vol); + + // dimensionless [0 1] (or 1.5 see View3DInventorViewer.cpp ) + Base::Vector3d screencoords = proj(pos); + + int width = viewer->getGLWidget()->width(), + height = viewer->getGLWidget()->height(); + + if (width >= height) { + // "Landscape" orientation, to square + screencoords.x *= height; + screencoords.x += (width-height) / 2.0; + screencoords.y *= height; + } + else { + // "Portrait" orientation + screencoords.x *= width; + screencoords.y *= width; + screencoords.y += (height-width) / 2.0; + } + + SbVec2f iconCoords(screencoords.x,screencoords.y); + + return iconCoords; +} + +QFont ViewProviderSketch::getApplicationFont() const +{ + return QApplication::font(); +} + +int ViewProviderSketch::defaultFontSizePixels() const +{ + QFontMetricsF metrics(QApplication::font()); + return static_cast(metrics.height()); +} + +int ViewProviderSketch::getApplicationLogicalDPIX() const { + return int(QApplication::primaryScreen()->logicalDotsPerInchX()); +} + +int ViewProviderSketch::getViewOrientationFactor() const { + return viewOrientationFactor; +} + +double ViewProviderSketch::getRotation(SbVec3f pos0, SbVec3f pos1) const +{ + double x0,y0,x1,y1; + + Gui::MDIView *mdi = Gui::Application::Instance->editViewOfNode(editCoinManager->getRootEditNode()); + if (!(mdi && mdi->isDerivedFrom(Gui::View3DInventor::getClassTypeId()))) + return 0; + Gui::View3DInventorViewer *viewer = static_cast(mdi)->getViewer(); + SoCamera* pCam = viewer->getSoRenderManager()->getCamera(); + if (!pCam) + return 0; + + try { + SbViewVolume vol = pCam->getViewVolume(); + + getCoordsOnSketchPlane(pos0,vol.getProjectionDirection(),x0,y0); + getCoordsOnSketchPlane(pos1,vol.getProjectionDirection(),x1,y1); + + return -atan2((y1-y0),(x1-x0))*180/M_PI; + } + catch (const Base::ZeroDivisionError&) { + return 0; + } +} + + +GeoListFacade ViewProviderSketch::getGeoListFacade() const +{ + return getSketchObject()->getGeoListFacade(); +} + +bool ViewProviderSketch::isSketchInvalid() const +{ + + bool sketchinvalid = getSketchObject()->getLastHasRedundancies() || + getSketchObject()->getLastHasConflicts() || + getSketchObject()->getLastHasMalformedConstraints(); + return sketchinvalid; +} + +bool ViewProviderSketch::isSketchFullyConstrained() const +{ + return getSketchObject()->FullyConstrained.getValue(); +} + +bool ViewProviderSketch::haveConstraintsInvalidGeometry() const +{ + return getSketchObject()->Constraints.hasInvalidGeometry(); +} + +void ViewProviderSketch::addNodeToRoot(SoSeparator * node) +{ + pcRoot->addChild(node); +} + +void ViewProviderSketch::removeNodeFromRoot(SoSeparator * node) +{ + pcRoot->removeChild(node); +} + +bool ViewProviderSketch::isConstraintPreselected(int constraintId) const +{ + return preselection.PreselectConstraintSet.count(constraintId); +} + +bool ViewProviderSketch::isPointSelected(int pointId) const +{ + return selection.SelPointSet.find(pointId) != selection.SelPointSet.end(); +} + +bool ViewProviderSketch::isCurveSelected(int curveId) const +{ + return selection.SelCurvSet.find(curveId) != selection.SelCurvSet.end(); +} + +bool ViewProviderSketch::isConstraintSelected(int constraintId) const +{ + return selection.SelConstraintSet.find(constraintId) != selection.SelConstraintSet.end(); +} + +void ViewProviderSketch::executeOnSelectionPointSet(std::function && operation) const +{ + for(const auto v : selection.SelPointSet) + operation(v); +} + +bool ViewProviderSketch::isInEditMode() const +{ + return editCoinManager != nullptr; +} diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.h b/src/Mod/Sketcher/Gui/ViewProviderSketch.h index 8debb6828b..e7b8de613e 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.h +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.h @@ -1,797 +1,788 @@ -/*************************************************************************** - * Copyright (c) 2009 Juergen Riegel * - * * - * 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_VIEWPROVIDERSKETCH_H -#define SKETCHERGUI_VIEWPROVIDERSKETCH_H - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ShortcutListener.h" - - -class TopoDS_Shape; -class TopoDS_Face; -class SoSeparator; -class SbLine; -class SbVec2f; -class SbVec3f; -class SoCoordinate3; -class SoInfo; -class SoPointSet; -class SoTransform; -class SoLineSet; -class SoMarkerSet; -class SoPickedPoint; -class SoRayPickAction; - -class SoImage; -class QImage; -class QColor; - -class SoText2; -class SoTranslation; -class SbString; -class SbTime; - -namespace Part { - class Geometry; -} - -namespace Gui { - class View3DInventorViewer; -} - -namespace Sketcher { - class Constraint; - class Sketch; - class SketchObject; -} - -namespace SketcherGui { - -class EditModeCoinManager; -class DrawSketchHandler; - -using GeoList = Sketcher::GeoList; -using GeoListFacade = Sketcher::GeoListFacade; - -/** @brief The Sketch ViewProvider - * - * @details - * - * As any ViewProvider, this class is responsible for the view representation - * of Sketches. - * - * Functionality inherited from parent classes deal with the majority of the - * representation of a sketch when it is ** not ** in edit mode. - * - * This class handles mainly the drawing and editing of the sketch. - * - * The class delegates a substantial part of this functionality on two main - * classes, DrawSketchHandler and EditModeCoinManager. - * - * In order to enforce a certain degree of encapsulation and promote a not - * too tight coupling, while still allowing well defined collaboration, - * DrawSketchHandler and EditModeCoinManager access ViewProviderSketch via - * two Attorney classes (Attorney-Client pattern), ViewProviderSketchDrawSketchHandlerAttorney - * and ViewProviderSketchCoinAttorney. - * - * Given the substantial amount of code involved in coin node management, EditModeCoinManager - * further delegates on other specialised helper classes. Some of them share the - * ViewProviderSketchCoinAttorney, which defines the maximum coupling and minimum encapsulation. - * - * DrawSketchHandler aids temporary edit mode drawing and is extensively used for the creation - * of geometry. - * - * EditModeCoinManager takes over the responsibility of creating the Coin (Inventor) scenograph - * and modifying it, including all the drawing of geometry, constraints and overlay layer. This - * is an exclusive responsibility under the Single Responsibility Principle. - * - * EditModeCoinManager exposes a public interface to be used by ViewProviderSketch. Where, - * EditModeCoinManager needs special access to facilities of ViewProviderSketch in order to fulfil - * its responsibility, this access is defined by ViewProviderSketchCoinAttorney. - * - * Similarly, DrawSketchHandler takes over the responsibility of drawing edit temporal curves and - * markers necessary to enable visual feedback to the user, as well as the UI interaction during - * such edits. This is its exclusive responsibility under the Single Responsibility Principle. - * - * A plethora of speciliased handlers derive from DrawSketchHandler for each specialised editing (see - * for example all the handlers for creation of new geometry). These derived classes do * not * have - * direct access to the ViewProviderSketchDrawSketchHandlerAttorney. This is intended to keep coupling - * under control. However, generic functionality requiring access to the Attorney can be implemented - * in DrawSketchHandler and used from its derived classes by virtue of the inheritance. This promotes a - * concentrating the coupling in a single point (and code reuse). - * - */ -class SketcherGuiExport ViewProviderSketch : public PartGui::ViewProvider2DObject - , public PartGui::ViewProviderAttachExtension - , public Gui::SelectionObserver -{ - Q_DECLARE_TR_FUNCTIONS(SketcherGui::ViewProviderSketch) - - PROPERTY_HEADER_WITH_OVERRIDE(SketcherGui::ViewProviderSketch); - -private: - /** - * @brief - * This nested class is responsible for attaching to the parameters relevant for - * ViewProviderSketch, initialising the ViewProviderSketch to the current configuration - * and handle in real time any change to their values. - */ - class ParameterObserver : public ParameterGrp::ObserverType - { - public: - explicit ParameterObserver(ViewProviderSketch & client); - ~ParameterObserver(); - - void initParameters(); - - void subscribeToParameters(); - - void unsubscribeToParameters(); - - /** Observer for parameter group. */ - void OnChange(Base::Subject &rCaller, const char * sReason) override; - - private: - - void updateBoolProperty(const std::string & string, App::Property * property, bool defaultvalue); - void updateGridSize(const std::string & string, App::Property * property); - - // Only for colors outside of edit mode, edit mode colors are handled by EditModeCoinManager. - void updateColorProperty(const std::string & string, App::Property * property, float r, float g, float b); - - void updateEscapeKeyBehaviour(const std::string & string, App::Property * property); - - void updateAutoRecompute(const std::string & string, App::Property * property); - - void updateRecalculateInitialSolutionWhileDragging(const std::string & string, App::Property * property); - - private: - ViewProviderSketch &Client; - std::map, App::Property * >> parameterMap; - }; - - /** @name Classes storing the state of Dragging, Selection and Preselection - * All these classes enable the identification of a Vertex, a Curve, the root - * Point, axes, and constraints. - * - * A word on indices and ways to identify elements and parts of them: - * - * The Sketcher has a general main way to identify geometry, {GeoId, PointPos}, - * these can be provided as separate data, or using Sketcher::GeoElementId. The - * latter defines comparison and equality operators enabling, for example to use - * it as key in a std::map. - * - * While it is indeed possible to refer to any point using that nomenclature, creating - * maps in certain circumnstances leads to a performance drawback. Additionally, the - * legacy selection mechanism refers to positive indexed Vertices (for both normal and - * external vertices). Both reasons discourage moving to a single identification. This - * situation has been identified at different levels: - * - * (1) In sketch.cpp, the solver facade defining the interface with GCS, both - * {GeoId, PointPos} and PointId are used. - * - * (2) In SketchObject.cpp, the actual document object, both {GeoId, PointPos} and VertexId - * are used (see VertexId2GeoId and VertexId2GeoPosId). - * - * (3) In ViewProviderSketch, both {GeoId, PointPos} an Point indices are used (see these structures) - * - * (4) At CoinManager level, {GeoId, PointPos}, Point indices (for selection) and MultiFieldIds (specific - * structure defining a coin multifield index and layer) are used. - * - * Using a single index instead of a multi-index field, allows mappings to be implemented via std::vectors - * instead of std::maps. Direct mappings using std::vectors are accessed in constant time. Multi-index mappings - * relying on std::maps involve a search for the key. This leads to a drop in performance. - * - * What are these indices and how do they relate each other? - * 1. PointId, VertexId depend on the order of the geometries in the sketch. GeoList and GeoListFacade enable to - * convert between indices. - * 2. CurveId is basically the GeoId assuming PointPos to be PointPos::none (edge) - * 3. Sometimes, Axes and root point are separated into a third index or enum. Legacy reasons aside, the root point - * is has GeoId=-1, which is sometimes used as invalid value in positive only indices. Additionally, root point and - * the Horizontal Axes both have GeoId=-1 (differing in PointPos only). Following the decision not to rely on PointPos, - * creating a separate index, best when enum-ed, appears justified. - */ - //@{ - - /** @brief Class to store vector and item Id for dragging. - * - * @details - * Ids are zero-indexed points and curves. - * - * The DragPoint indexing matches PreselectPoint indexing. - * - */ - class Drag { - public: - enum SpecialValues { - InvalidPoint = -1, - InvalidCurve = -1 - }; - - Drag() { - resetVector(); - resetIds(); - } - - void resetVector() { - xInit = 0; - yInit = 0; - relative = false; - } - - void resetIds() { - DragPoint = InvalidPoint; - DragCurve = InvalidCurve; - DragConstraintSet.clear(); - } - - bool isDragPointValid() { return DragPoint > InvalidPoint;} - bool isDragCurveValid() { return DragCurve > InvalidCurve;} - - double xInit, yInit; // starting point of the dragging operation - bool relative; // whether the dragging move vector is relative or absolute - - - int DragPoint; // dragged point id (only positive integers) - int DragCurve; // dragged curve id (only positive integers), negative external curves cannot be dragged. - std::set DragConstraintSet; // dragged constraints ids - }; - - // TODO: Selection and Preselection should use a same structure. Probably Drag should use the same structure too. To be refactored separately. - - /** @brief Class to store preselected element ids. - * - * @details - * - * PreselectPoint is the positive VertexId. - * - * PreselectCurve is the GeoID, but without the Axes (indices -1 and -2). - * - * VertexN, with N = PreselectPoint + 1, same as DragPoint indexing (NOTE -1 is NOT the root point) - * - * EdgeN, with N = PreselectCurve + 1 for positive values ; ExternalEdgeN, with N = -PreselectCurve - 2 - * - * The PreselectPoint indexing matches DragPoint indexing (it further includes negative edges, which are - * not meaningful for Dragging). - * - */ - class Preselection { - public: - enum SpecialValues { - InvalidPoint = -1, - InvalidCurve = -1, - ExternalCurve = -3 - }; - - enum class Axes { - None = -1, - RootPoint = 0, - HorizontalAxis = 1, - VerticalAxis = 2 - }; - - Preselection() { - reset(); - } - - void reset(){ - PreselectPoint = InvalidPoint; - PreselectCurve = InvalidCurve; - PreselectCross = Axes::None; - PreselectConstraintSet.clear(); - blockedPreselection = false; - } - - bool isPreselectPointValid() const { return PreselectPoint > InvalidPoint;} - bool isPreselectCurveValid() const { return PreselectCurve > InvalidCurve || PreselectCurve <= ExternalCurve;} - bool isCrossPreselected() const { return PreselectCross != Axes::None;} - bool isEdge() const { return PreselectCurve > InvalidCurve;} - bool isExternalEdge() const { return PreselectCurve <= ExternalCurve;} - - int getPreselectionVertexIndex() const { return PreselectPoint + 1;} - int getPreselectionEdgeIndex() const { return PreselectCurve + 1;} - int getPreselectionExternalEdgeIndex() const { return -PreselectCurve - 2;} - - int PreselectPoint; // VertexN, with N = PreselectPoint + 1, same as DragPoint indexing (NOTE -1 is NOT the root point) - int PreselectCurve; // EdgeN, with N = PreselectCurve + 1 for positive values ; ExternalEdgeN, with N = -PreselectCurve - 2 - Axes PreselectCross; // 0 => rootPoint, 1 => HAxis, 2 => VAxis - std::set PreselectConstraintSet; // ConstraintN, N = index + 1 - bool blockedPreselection; - }; - - /** @brief Class to store selected element ids. - * - * @details - * Selection follows yet a different mechanism than preselection. - * - * SelPointSet indices as PreselectPoint, with the addition that -1 is indeed the rootpoint. - * - * SelCurvSet indices as PreselectCurve, with the addition that -1 is the HAxis and -2 is the VAxis - * - */ - class Selection { - public: - enum SpecialValues { - RootPoint = -1, - HorizontalAxis = -1, - VerticalAxis = -2 - }; - - Selection() { - reset(); - } - - void reset() { - SelPointSet.clear(); - SelCurvSet.clear(); - SelConstraintSet.clear(); - } - - std::set SelPointSet; // Indices as PreselectPoint (and -1 for rootpoint) - std::set SelCurvSet; // also holds cross axes at -1 and -2 - std::set SelConstraintSet; // ConstraintN, N = index + 1. - }; - //@} - - /** @brief Private struct maintaining information necessary for detecting double click. - */ - struct DoubleClick { - static SbTime prvClickTime; - static SbVec2s prvClickPos; //used by double-click-detector - static SbVec2s prvCursorPos; - static SbVec2s newCursorPos; - }; - - /** @brief Private struct grouping ViewProvider parameters and internal variables - */ - struct ViewProviderParameters { - bool handleEscapeButton = false; - bool autoRecompute = false; - bool recalculateInitialSolutionWhileDragging = false; - - bool isShownVirtualSpace = false; // indicates whether the present virtual space view is the Real Space or the Virtual Space (virtual space 1 or 2) - bool buttonPress = false; - }; - - /** @brief Private struct grouping ViewProvider and RenderManager node, to be used as SoNode sensor data - */ - struct VPRender { - ViewProviderSketch* vp; - SoRenderManager* renderMgr; - }; - -public: - /// constructor - ViewProviderSketch(); - /// destructor - ~ViewProviderSketch() override; - - /** @name Properties */ - //@{ - App::PropertyBool Autoconstraints; - App::PropertyBool AvoidRedundant; - App::PropertyPythonObject TempoVis; - App::PropertyBool HideDependent; - App::PropertyBool ShowLinks; - App::PropertyBool ShowSupport; - App::PropertyBool RestoreCamera; - App::PropertyBool ForceOrtho; - App::PropertyBool SectionView; - App::PropertyString EditingWorkbench; - App::PropertyBool ShowGrid; - App::PropertyLength GridSize; - App::PropertyEnumeration GridStyle; - App::PropertyBool GridSnap; - App::PropertyBool GridAuto; - //@} - - // TODO: It is difficult to imagine that these functions are necessary in the public interface. This requires review at a second stage and possibly - // refactor it. - /** @name handler control */ - //@{ - /// sets an DrawSketchHandler in control - void activateHandler(DrawSketchHandler *newHandler); - /// removes the active handler - void purgeHandler(); - //@} - - - // TODO: SketchMode should be refactored. DrawSketchHandler, its inheritance and free functions should access this mode via the DrawSketchHandler - // Attorney. I will not refactor this at this moment, as the refactor will be even more extensive and difficult to review. But this should be done - // in a second stage. - - /** @name modus handling */ - //@{ - /// mode table - enum SketchMode{ - STATUS_NONE, /**< enum value View provider is in neutral. */ - STATUS_SELECT_Point, /**< enum value a point was selected. */ - STATUS_SELECT_Edge, /**< enum value an edge was selected. */ - STATUS_SELECT_Constraint, /**< enum value a constraint was selected. */ - STATUS_SELECT_Cross, /**< enum value the base coordinate system was selected. */ - STATUS_SKETCH_DragPoint, /**< enum value while dragging a point. */ - STATUS_SKETCH_DragCurve, /**< enum value while dragging a curve. */ - STATUS_SKETCH_DragConstraint, /**< enum value while dragging a compatible constraint. */ - STATUS_SKETCH_UseHandler, /**< enum value a DrawSketchHandler is in control. */ - STATUS_SKETCH_StartRubberBand, /**< enum value for initiating a rubber band selection */ - STATUS_SKETCH_UseRubberBand /**< enum value when making a rubber band selection */ - }; - - /// is called by GuiCommands to set the drawing mode - void setSketchMode(SketchMode mode) {Mode = mode;} - /// get the sketch mode - SketchMode getSketchMode() const {return Mode;} - //@} - - /** @name Drawing functions */ - //@{ - /// draw the sketch in the inventor nodes - /// temp => use temporary solver solution in SketchObject - /// recreateinformationscenography => forces a rebuild of the information overlay scenography - void draw(bool temp=false, bool rebuildinformationoverlay=true); - - /// helper change the color of the sketch according to selection and solver status - void updateColor(); - //@} - - /** @name Selection functions */ - //@{ - /// Is the view provider selectable - bool isSelectable() const override; - - /// Observer message from the Selection - void onSelectionChanged(const Gui::SelectionChanges& msg) override; - //@} - - /** @name Access to Sketch and Solver objects */ - //@{ - /// get the pointer to the sketch document object - Sketcher::SketchObject *getSketchObject() const; - - /** returns a const reference to the last solved sketch object. It guarantees that - * the solver object does not lose synchronisation with the SketchObject properties. - * - * NOTE: Operations requiring * write * access to the solver must be done via SketchObject - * interface. See for example functions: - * -> inline void setRecalculateInitialSolutionWhileMovingPoint(bool recalculateInitialSolutionWhileMovingPoint) - * -> inline int initTemporaryMove(int geoId, PointPos pos, bool fine=true) - * -> inline int moveTemporaryPoint(int geoId, PointPos pos, Base::Vector3d toPoint, bool relative=false) - * -> inline void updateSolverExtension(int geoId, std::unique_ptr && ext) - */ - const Sketcher::Sketch &getSolvedSketch() const; - //@} - - /** @name miscelanea utilities */ - //@{ - /*! Look at the center of the bounding of all selected items */ - void centerSelection(); - /// returns the scale factor - float getScaleFactor() const; - /// returns view orientation factor - int getViewOrientationFactor() const; - //@} - - /** @name constraint Virtual Space visibility management */ - //@{ - /// updates the visibility of the virtual space of constraints - void updateVirtualSpace(); - /// determines whether the constraints in the normal space or the ones in the virtual are to be shown - void setIsShownVirtualSpace(bool isshownvirtualspace); - /// returns whether the virtual space is being shown - bool getIsShownVirtualSpace() const; - //@} - - /** @name base class implementer */ - //@{ - void attach(App::DocumentObject *) override; - void updateData(const App::Property *) override; - - void setupContextMenu(QMenu *menu, QObject *receiver, const char *member) override; - /// is called when the Provider is in edit and a deletion request occurs - bool onDelete(const std::vector &) override; - /// Is called by the tree if the user double clicks on the object. It returns the string - /// for the transaction that will be shown in the undo/redo dialog. - /// If null is returned then no transaction will be opened. - const char* getTransactionText() const override { return nullptr; } - /// is called by the tree if the user double clicks on the object - bool doubleClicked() override; - /// is called when the Provider is in edit and the mouse is moved - bool mouseMove(const SbVec2s &pos, Gui::View3DInventorViewer *viewer) override; - /// is called when the Provider is in edit and a key event ocours. Only ESC ends edit. - bool keyPressed(bool pressed, int key) override; - /// is called when the Provider is in edit and the mouse is clicked - bool mouseButtonPressed(int Button, bool pressed, const SbVec2s& cursorPos, const Gui::View3DInventorViewer* viewer) override; - bool mouseWheelEvent(int delta, const SbVec2s &cursorPos, const Gui::View3DInventorViewer* viewer) override; - //@} - - - /// Control the overlays appearing on the Tree and reflecting different sketcher states - QIcon mergeColorfulOverlayIcons (const QIcon & orig) const override; - - /** @name Signals for controlling information in Task dialogs */ - //@{ - /// signals if the constraints list has changed - boost::signals2::signal signalConstraintsChanged; - /// signals if the sketch has been set up - boost::signals2::signal signalSetUp; - /// signals if the elements list has changed - boost::signals2::signal signalElementsChanged; - //@} - - /** @name Attorneys for collaboration with helper classes */ - //@{ - friend class ViewProviderSketchDrawSketchHandlerAttorney; - friend class ViewProviderSketchCoinAttorney; - friend class ViewProviderSketchShortcutListenerAttorney; - //@} -protected: - /** @name enter/exit edit mode */ - //@{ - bool setEdit(int ModNum) override; - void unsetEdit(int ModNum) override; - void setEditViewer(Gui::View3DInventorViewer*, int ModNum) override; - void unsetEditViewer(Gui::View3DInventorViewer*) override; - static void camSensCB(void *data, SoSensor *); // camera sensor callback - void onCameraChanged(SoCamera* cam); - //@} - - /** @name miscelanea editing functions */ - //@{ - /// purges the DrawHandler if existing and tidies up - void deactivateHandler(); - /// get called if a subelement is double clicked while editing - void editDoubleClicked(); - //@} - - /** @name Solver Information */ - //@{ - /// update solver information based on last solving at SketchObject - void UpdateSolverInformation(); - - /// Auxiliary function to generate messages about conflicting, redundant and malformed constraints - static QString appendConstraintMsg( const QString & singularmsg, - const QString & pluralmsg, - const std::vector &vector); - //@} - - /** @name manage updates during undo/redo operations */ - //@{ - void slotUndoDocument(const Gui::Document&); - void slotRedoDocument(const Gui::Document&); - void forceUpdateData(); - //@} - - /** @name base class implementer */ - //@{ - /// get called by the container whenever a property has been changed - void onChanged(const App::Property *prop) override; - //@} - -private: - /// function to handle OCCT BSpline weight calculation singularities and representation - void scaleBSplinePoleCirclesAndUpdateSolverAndSketchObjectGeometry( - GeoListFacade & geolist, - bool geometrywithmemoryallocation); - - /** @name geometry and coordinates auxiliary functions */ - //@{ - /// give the coordinates of a line on the sketch plane in sketcher (2D) coordinates - void getCoordsOnSketchPlane(const SbVec3f &point, const SbVec3f &normal, double &u, double &v) const; - - /// give projecting line of position - void getProjectingLine(const SbVec2s&, - const Gui::View3DInventorViewer *viewer, - SbLine&) const; - //@} - - /** @name preselection functions */ - //@{ - /// helper to detect preselection - bool detectAndShowPreselection (SoPickedPoint * Point, const SbVec2s &cursorPos); - int getPreselectPoint() const; - int getPreselectCurve() const; - int getPreselectCross() const; - void setPreselectPoint(int PreselectPoint); - void setPreselectRootPoint(); - void resetPreselectPoint(); - - bool setPreselect(const std::string &subNameSuffix, float x = 0, float y = 0, float z = 0); - //@} - - /** @name Selection functions */ - //@{ - /// box selection method - void doBoxSelection(const SbVec2s &startPos, const SbVec2s &endPos, - const Gui::View3DInventorViewer *viewer); - - void addSelectPoint(int SelectPoint); - void removeSelectPoint(int SelectPoint); - void clearSelectPoints(); - - bool isSelected(const std::string & ss) const; - void rmvSelection(const std::string &subNameSuffix); - bool addSelection(const std::string &subNameSuffix, float x = 0, float y = 0, float z = 0); - bool addSelection2(const std::string &subNameSuffix, float x = 0, float y = 0, float z = 0); - //@} - - /** @name miscelanea utilities */ - //@{ - /// snap points x,y (mouse coordinates) onto grid if enabled - void snapToGrid(double &x, double &y); - - /// moves a selected constraint - void moveConstraint(int constNum, const Base::Vector2d &toPos); - - /// returns whether the sketch is in edit mode. - bool isInEditMode() const; - //@} - - /** @name Attorney functions*/ - //@{ - /* private functions to decouple Attorneys and Clients from the internal implementation of - the ViewProvider and its members, such as sketchObject (see friend attorney classes) and - improve encapsulation. - */ - - //********* ViewProviderSketchCoinAttorney *********************** - - bool constraintHasExpression(int constrid) const; - - const std::vector getConstraints() const; - - // gets the list of geometry of the sketchobject or of the solver instance - const GeoList getGeoList() const; - - GeoListFacade getGeoListFacade() const; - - Base::Placement getEditingPlacement() const; - - std::unique_ptr getRayPickAction() const; - - SbVec2f getScreenCoordinates(SbVec2f sketchcoordinates) const; - - QFont getApplicationFont() const; - - int defaultFontSizePixels() const; - - int getApplicationLogicalDPIX() const; - - double getRotation(SbVec3f pos0, SbVec3f pos1) const; - - bool isSketchInvalid() const; - - bool isSketchFullyConstrained() const; - - bool haveConstraintsInvalidGeometry() const; - - void addNodeToRoot(SoSeparator * node); - - void removeNodeFromRoot(SoSeparator * node); - - bool isConstraintPreselected(int constraintId) const; - - bool isPointSelected(int pointId) const; - - void executeOnSelectionPointSet(std::function && operation) const; - - bool isCurveSelected(int curveId) const; - - bool isConstraintSelected(int constraintId) const; - - //********* ViewProviderSketchShortcutListenerAttorney ***********// - void deleteSelected(); - - //********* ViewProviderSketchDrawSketchHandlerAttorney **********// - void setConstraintSelectability(bool enabled = true); - void setPositionText(const Base::Vector2d &Pos, const SbString &txt); - void setPositionText(const Base::Vector2d &Pos); - void resetPositionText(); - - /// draw the edit curve - void drawEdit(const std::vector &EditCurve); - void drawEdit(const std::list> &list); - /// draw the edit markers - void drawEditMarkers(const std::vector &EditMarkers, unsigned int augmentationlevel = 0); - /// set the pick style of the sketch coordinate axes - void setAxisPickStyle(bool on); - - void moveCursorToSketchPoint(Base::Vector2d point); - - void preselectAtPoint(Base::Vector2d point); - //@} - -private: - /** @name Solver message creation*/ - //@{ - /* private functions to decouple Attorneys and Clients from the internal implementation of - the ViewProvider and its members, such as sketchObject (see friend attorney classes) and - improve encapsulation. - */ - /// generates a warning message about constraint conflicts and appends it to the given message - static QString appendConflictMsg(const std::vector &conflicting); - /// generates a warning message about redundant constraints and appends it to the given message - static QString appendRedundantMsg(const std::vector &redundant); - /// generates a warning message about partially redundant constraints and appends it to the given message - static QString appendPartiallyRedundantMsg(const std::vector &partiallyredundant); - /// generates a warning message about redundant constraints and appends it to the given message - static QString appendMalformedMsg(const std::vector &redundant); - //@} - -private: - boost::signals2::connection connectUndoDocument; - boost::signals2::connection connectRedoDocument; - - // modes while sketching - SketchMode Mode; - - // reference coordinates for relative operations - Drag drag; - - Preselection preselection; - Selection selection; - - std::unique_ptr rubberband; - - std::string editDocName; - std::string editObjName; - std::string editSubName; - - ShortcutListener* listener; - - std::unique_ptr editCoinManager; - - std::unique_ptr pObserver; - - std::unique_ptr sketchHandler; - - ViewProviderParameters viewProviderParameters; - - SoNodeSensor cameraSensor; - int viewOrientationFactor; // stores if sketch viewed from front or back - - void Restore(Base::XMLReader& reader) override; - void handleChangedPropertyType(Base::XMLReader& reader, const char* TypeName, App::Property* prop) override; - - static const char* GridStyleEnums[]; - static App::PropertyQuantityConstraint::Constraints GridSizeRange; -}; - -} // namespace PartGui - - -#endif // SKETCHERGUI_VIEWPROVIDERSKETCH_H - +/*************************************************************************** + * Copyright (c) 2009 Juergen Riegel * + * * + * 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_VIEWPROVIDERSKETCH_H +#define SKETCHERGUI_VIEWPROVIDERSKETCH_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ShortcutListener.h" + + +class TopoDS_Shape; +class TopoDS_Face; +class SoSeparator; +class SbLine; +class SbVec2f; +class SbVec3f; +class SoCoordinate3; +class SoInfo; +class SoPointSet; +class SoTransform; +class SoLineSet; +class SoMarkerSet; +class SoPickedPoint; +class SoRayPickAction; + +class SoImage; +class QImage; +class QColor; + +class SoText2; +class SoTranslation; +class SbString; +class SbTime; + +namespace Part { + class Geometry; +} + +namespace Gui { + class View3DInventorViewer; +} + +namespace Sketcher { + class Constraint; + class Sketch; + class SketchObject; +} + +namespace SketcherGui { + +class EditModeCoinManager; +class DrawSketchHandler; + +using GeoList = Sketcher::GeoList; +using GeoListFacade = Sketcher::GeoListFacade; + +/** @brief The Sketch ViewProvider + * + * @details + * + * As any ViewProvider, this class is responsible for the view representation + * of Sketches. + * + * Functionality inherited from parent classes deal with the majority of the + * representation of a sketch when it is ** not ** in edit mode. + * + * This class handles mainly the drawing and editing of the sketch. + * + * The class delegates a substantial part of this functionality on two main + * classes, DrawSketchHandler and EditModeCoinManager. + * + * In order to enforce a certain degree of encapsulation and promote a not + * too tight coupling, while still allowing well defined collaboration, + * DrawSketchHandler and EditModeCoinManager access ViewProviderSketch via + * two Attorney classes (Attorney-Client pattern), ViewProviderSketchDrawSketchHandlerAttorney + * and ViewProviderSketchCoinAttorney. + * + * Given the substantial amount of code involved in coin node management, EditModeCoinManager + * further delegates on other specialised helper classes. Some of them share the + * ViewProviderSketchCoinAttorney, which defines the maximum coupling and minimum encapsulation. + * + * DrawSketchHandler aids temporary edit mode drawing and is extensively used for the creation + * of geometry. + * + * EditModeCoinManager takes over the responsibility of creating the Coin (Inventor) scenograph + * and modifying it, including all the drawing of geometry, constraints and overlay layer. This + * is an exclusive responsibility under the Single Responsibility Principle. + * + * EditModeCoinManager exposes a public interface to be used by ViewProviderSketch. Where, + * EditModeCoinManager needs special access to facilities of ViewProviderSketch in order to fulfil + * its responsibility, this access is defined by ViewProviderSketchCoinAttorney. + * + * Similarly, DrawSketchHandler takes over the responsibility of drawing edit temporal curves and + * markers necessary to enable visual feedback to the user, as well as the UI interaction during + * such edits. This is its exclusive responsibility under the Single Responsibility Principle. + * + * A plethora of speciliased handlers derive from DrawSketchHandler for each specialised editing (see + * for example all the handlers for creation of new geometry). These derived classes do * not * have + * direct access to the ViewProviderSketchDrawSketchHandlerAttorney. This is intended to keep coupling + * under control. However, generic functionality requiring access to the Attorney can be implemented + * in DrawSketchHandler and used from its derived classes by virtue of the inheritance. This promotes a + * concentrating the coupling in a single point (and code reuse). + * + */ +class SketcherGuiExport ViewProviderSketch : public PartGui::ViewProvider2DObject + , public PartGui::ViewProviderGridExtension + , public PartGui::ViewProviderAttachExtension + , public Gui::SelectionObserver +{ + Q_DECLARE_TR_FUNCTIONS(SketcherGui::ViewProviderSketch) + + PROPERTY_HEADER_WITH_OVERRIDE(SketcherGui::ViewProviderSketch); + +private: + /** + * @brief + * This nested class is responsible for attaching to the parameters relevant for + * ViewProviderSketch, initialising the ViewProviderSketch to the current configuration + * and handle in real time any change to their values. + */ + class ParameterObserver : public ParameterGrp::ObserverType + { + public: + explicit ParameterObserver(ViewProviderSketch & client); + ~ParameterObserver(); + + void initParameters(); + + void subscribeToParameters(); + + void unsubscribeToParameters(); + + /** Observer for parameter group. */ + void OnChange(Base::Subject &rCaller, const char * sReason) override; + + private: + + void updateBoolProperty(const std::string & string, App::Property * property, bool defaultvalue); + void updateGridSize(const std::string & string, App::Property * property); + + // Only for colors outside of edit mode, edit mode colors are handled by EditModeCoinManager. + void updateColorProperty(const std::string & string, App::Property * property, float r, float g, float b); + + void updateEscapeKeyBehaviour(const std::string & string, App::Property * property); + + void updateAutoRecompute(const std::string & string, App::Property * property); + + void updateRecalculateInitialSolutionWhileDragging(const std::string & string, App::Property * property); + + private: + ViewProviderSketch &Client; + std::map, App::Property * >> parameterMap; + }; + + /** @name Classes storing the state of Dragging, Selection and Preselection + * All these classes enable the identification of a Vertex, a Curve, the root + * Point, axes, and constraints. + * + * A word on indices and ways to identify elements and parts of them: + * + * The Sketcher has a general main way to identify geometry, {GeoId, PointPos}, + * these can be provided as separate data, or using Sketcher::GeoElementId. The + * latter defines comparison and equality operators enabling, for example to use + * it as key in a std::map. + * + * While it is indeed possible to refer to any point using that nomenclature, creating + * maps in certain circumnstances leads to a performance drawback. Additionally, the + * legacy selection mechanism refers to positive indexed Vertices (for both normal and + * external vertices). Both reasons discourage moving to a single identification. This + * situation has been identified at different levels: + * + * (1) In sketch.cpp, the solver facade defining the interface with GCS, both + * {GeoId, PointPos} and PointId are used. + * + * (2) In SketchObject.cpp, the actual document object, both {GeoId, PointPos} and VertexId + * are used (see VertexId2GeoId and VertexId2GeoPosId). + * + * (3) In ViewProviderSketch, both {GeoId, PointPos} an Point indices are used (see these structures) + * + * (4) At CoinManager level, {GeoId, PointPos}, Point indices (for selection) and MultiFieldIds (specific + * structure defining a coin multifield index and layer) are used. + * + * Using a single index instead of a multi-index field, allows mappings to be implemented via std::vectors + * instead of std::maps. Direct mappings using std::vectors are accessed in constant time. Multi-index mappings + * relying on std::maps involve a search for the key. This leads to a drop in performance. + * + * What are these indices and how do they relate each other? + * 1. PointId, VertexId depend on the order of the geometries in the sketch. GeoList and GeoListFacade enable to + * convert between indices. + * 2. CurveId is basically the GeoId assuming PointPos to be PointPos::none (edge) + * 3. Sometimes, Axes and root point are separated into a third index or enum. Legacy reasons aside, the root point + * is has GeoId=-1, which is sometimes used as invalid value in positive only indices. Additionally, root point and + * the Horizontal Axes both have GeoId=-1 (differing in PointPos only). Following the decision not to rely on PointPos, + * creating a separate index, best when enum-ed, appears justified. + */ + //@{ + + /** @brief Class to store vector and item Id for dragging. + * + * @details + * Ids are zero-indexed points and curves. + * + * The DragPoint indexing matches PreselectPoint indexing. + * + */ + class Drag { + public: + enum SpecialValues { + InvalidPoint = -1, + InvalidCurve = -1 + }; + + Drag() { + resetVector(); + resetIds(); + } + + void resetVector() { + xInit = 0; + yInit = 0; + relative = false; + } + + void resetIds() { + DragPoint = InvalidPoint; + DragCurve = InvalidCurve; + DragConstraintSet.clear(); + } + + bool isDragPointValid() { return DragPoint > InvalidPoint;} + bool isDragCurveValid() { return DragCurve > InvalidCurve;} + + double xInit, yInit; // starting point of the dragging operation + bool relative; // whether the dragging move vector is relative or absolute + + + int DragPoint; // dragged point id (only positive integers) + int DragCurve; // dragged curve id (only positive integers), negative external curves cannot be dragged. + std::set DragConstraintSet; // dragged constraints ids + }; + + // TODO: Selection and Preselection should use a same structure. Probably Drag should use the same structure too. To be refactored separately. + + /** @brief Class to store preselected element ids. + * + * @details + * + * PreselectPoint is the positive VertexId. + * + * PreselectCurve is the GeoID, but without the Axes (indices -1 and -2). + * + * VertexN, with N = PreselectPoint + 1, same as DragPoint indexing (NOTE -1 is NOT the root point) + * + * EdgeN, with N = PreselectCurve + 1 for positive values ; ExternalEdgeN, with N = -PreselectCurve - 2 + * + * The PreselectPoint indexing matches DragPoint indexing (it further includes negative edges, which are + * not meaningful for Dragging). + * + */ + class Preselection { + public: + enum SpecialValues { + InvalidPoint = -1, + InvalidCurve = -1, + ExternalCurve = -3 + }; + + enum class Axes { + None = -1, + RootPoint = 0, + HorizontalAxis = 1, + VerticalAxis = 2 + }; + + Preselection() { + reset(); + } + + void reset(){ + PreselectPoint = InvalidPoint; + PreselectCurve = InvalidCurve; + PreselectCross = Axes::None; + PreselectConstraintSet.clear(); + blockedPreselection = false; + } + + bool isPreselectPointValid() const { return PreselectPoint > InvalidPoint;} + bool isPreselectCurveValid() const { return PreselectCurve > InvalidCurve || PreselectCurve <= ExternalCurve;} + bool isCrossPreselected() const { return PreselectCross != Axes::None;} + bool isEdge() const { return PreselectCurve > InvalidCurve;} + bool isExternalEdge() const { return PreselectCurve <= ExternalCurve;} + + int getPreselectionVertexIndex() const { return PreselectPoint + 1;} + int getPreselectionEdgeIndex() const { return PreselectCurve + 1;} + int getPreselectionExternalEdgeIndex() const { return -PreselectCurve - 2;} + + int PreselectPoint; // VertexN, with N = PreselectPoint + 1, same as DragPoint indexing (NOTE -1 is NOT the root point) + int PreselectCurve; // EdgeN, with N = PreselectCurve + 1 for positive values ; ExternalEdgeN, with N = -PreselectCurve - 2 + Axes PreselectCross; // 0 => rootPoint, 1 => HAxis, 2 => VAxis + std::set PreselectConstraintSet; // ConstraintN, N = index + 1 + bool blockedPreselection; + }; + + /** @brief Class to store selected element ids. + * + * @details + * Selection follows yet a different mechanism than preselection. + * + * SelPointSet indices as PreselectPoint, with the addition that -1 is indeed the rootpoint. + * + * SelCurvSet indices as PreselectCurve, with the addition that -1 is the HAxis and -2 is the VAxis + * + */ + class Selection { + public: + enum SpecialValues { + RootPoint = -1, + HorizontalAxis = -1, + VerticalAxis = -2 + }; + + Selection() { + reset(); + } + + void reset() { + SelPointSet.clear(); + SelCurvSet.clear(); + SelConstraintSet.clear(); + } + + std::set SelPointSet; // Indices as PreselectPoint (and -1 for rootpoint) + std::set SelCurvSet; // also holds cross axes at -1 and -2 + std::set SelConstraintSet; // ConstraintN, N = index + 1. + }; + //@} + + /** @brief Private struct maintaining information necessary for detecting double click. + */ + struct DoubleClick { + static SbTime prvClickTime; + static SbVec2s prvClickPos; //used by double-click-detector + static SbVec2s prvCursorPos; + static SbVec2s newCursorPos; + }; + + /** @brief Private struct grouping ViewProvider parameters and internal variables + */ + struct ViewProviderParameters { + bool handleEscapeButton = false; + bool autoRecompute = false; + bool recalculateInitialSolutionWhileDragging = false; + + bool isShownVirtualSpace = false; // indicates whether the present virtual space view is the Real Space or the Virtual Space (virtual space 1 or 2) + bool buttonPress = false; + }; + + /** @brief Private struct grouping ViewProvider and RenderManager node, to be used as SoNode sensor data + */ + struct VPRender { + ViewProviderSketch* vp; + SoRenderManager* renderMgr; + }; + +public: + /// constructor + ViewProviderSketch(); + /// destructor + ~ViewProviderSketch() override; + + /** @name Properties */ + //@{ + App::PropertyBool Autoconstraints; + App::PropertyBool AvoidRedundant; + App::PropertyPythonObject TempoVis; + App::PropertyBool HideDependent; + App::PropertyBool ShowLinks; + App::PropertyBool ShowSupport; + App::PropertyBool RestoreCamera; + App::PropertyBool ForceOrtho; + App::PropertyBool SectionView; + App::PropertyString EditingWorkbench; + //@} + + // TODO: It is difficult to imagine that these functions are necessary in the public interface. This requires review at a second stage and possibly + // refactor it. + /** @name handler control */ + //@{ + /// sets an DrawSketchHandler in control + void activateHandler(DrawSketchHandler *newHandler); + /// removes the active handler + void purgeHandler(); + //@} + + + // TODO: SketchMode should be refactored. DrawSketchHandler, its inheritance and free functions should access this mode via the DrawSketchHandler + // Attorney. I will not refactor this at this moment, as the refactor will be even more extensive and difficult to review. But this should be done + // in a second stage. + + /** @name modus handling */ + //@{ + /// mode table + enum SketchMode{ + STATUS_NONE, /**< enum value View provider is in neutral. */ + STATUS_SELECT_Point, /**< enum value a point was selected. */ + STATUS_SELECT_Edge, /**< enum value an edge was selected. */ + STATUS_SELECT_Constraint, /**< enum value a constraint was selected. */ + STATUS_SELECT_Cross, /**< enum value the base coordinate system was selected. */ + STATUS_SKETCH_DragPoint, /**< enum value while dragging a point. */ + STATUS_SKETCH_DragCurve, /**< enum value while dragging a curve. */ + STATUS_SKETCH_DragConstraint, /**< enum value while dragging a compatible constraint. */ + STATUS_SKETCH_UseHandler, /**< enum value a DrawSketchHandler is in control. */ + STATUS_SKETCH_StartRubberBand, /**< enum value for initiating a rubber band selection */ + STATUS_SKETCH_UseRubberBand /**< enum value when making a rubber band selection */ + }; + + /// is called by GuiCommands to set the drawing mode + void setSketchMode(SketchMode mode) {Mode = mode;} + /// get the sketch mode + SketchMode getSketchMode() const {return Mode;} + //@} + + /** @name Drawing functions */ + //@{ + /// draw the sketch in the inventor nodes + /// temp => use temporary solver solution in SketchObject + /// recreateinformationscenography => forces a rebuild of the information overlay scenography + void draw(bool temp=false, bool rebuildinformationoverlay=true); + + /// helper change the color of the sketch according to selection and solver status + void updateColor(); + //@} + + /** @name Selection functions */ + //@{ + /// Is the view provider selectable + bool isSelectable() const override; + + /// Observer message from the Selection + void onSelectionChanged(const Gui::SelectionChanges& msg) override; + //@} + + /** @name Access to Sketch and Solver objects */ + //@{ + /// get the pointer to the sketch document object + Sketcher::SketchObject *getSketchObject() const; + + /** returns a const reference to the last solved sketch object. It guarantees that + * the solver object does not lose synchronisation with the SketchObject properties. + * + * NOTE: Operations requiring * write * access to the solver must be done via SketchObject + * interface. See for example functions: + * -> inline void setRecalculateInitialSolutionWhileMovingPoint(bool recalculateInitialSolutionWhileMovingPoint) + * -> inline int initTemporaryMove(int geoId, PointPos pos, bool fine=true) + * -> inline int moveTemporaryPoint(int geoId, PointPos pos, Base::Vector3d toPoint, bool relative=false) + * -> inline void updateSolverExtension(int geoId, std::unique_ptr && ext) + */ + const Sketcher::Sketch &getSolvedSketch() const; + //@} + + /** @name miscelanea utilities */ + //@{ + /*! Look at the center of the bounding of all selected items */ + void centerSelection(); + /// returns the scale factor + float getScaleFactor() const; + /// returns view orientation factor + int getViewOrientationFactor() const; + //@} + + /** @name constraint Virtual Space visibility management */ + //@{ + /// updates the visibility of the virtual space of constraints + void updateVirtualSpace(); + /// determines whether the constraints in the normal space or the ones in the virtual are to be shown + void setIsShownVirtualSpace(bool isshownvirtualspace); + /// returns whether the virtual space is being shown + bool getIsShownVirtualSpace() const; + //@} + + /** @name base class implementer */ + //@{ + void attach(App::DocumentObject *) override; + void updateData(const App::Property *) override; + + void setupContextMenu(QMenu *menu, QObject *receiver, const char *member) override; + /// is called when the Provider is in edit and a deletion request occurs + bool onDelete(const std::vector &) override; + /// Is called by the tree if the user double clicks on the object. It returns the string + /// for the transaction that will be shown in the undo/redo dialog. + /// If null is returned then no transaction will be opened. + const char* getTransactionText() const override { return nullptr; } + /// is called by the tree if the user double clicks on the object + bool doubleClicked() override; + /// is called when the Provider is in edit and the mouse is moved + bool mouseMove(const SbVec2s &pos, Gui::View3DInventorViewer *viewer) override; + /// is called when the Provider is in edit and a key event ocours. Only ESC ends edit. + bool keyPressed(bool pressed, int key) override; + /// is called when the Provider is in edit and the mouse is clicked + bool mouseButtonPressed(int Button, bool pressed, const SbVec2s& cursorPos, const Gui::View3DInventorViewer* viewer) override; + bool mouseWheelEvent(int delta, const SbVec2s &cursorPos, const Gui::View3DInventorViewer* viewer) override; + //@} + + + /// Control the overlays appearing on the Tree and reflecting different sketcher states + QIcon mergeColorfulOverlayIcons (const QIcon & orig) const override; + + /** @name Signals for controlling information in Task dialogs */ + //@{ + /// signals if the constraints list has changed + boost::signals2::signal signalConstraintsChanged; + /// signals if the sketch has been set up + boost::signals2::signal signalSetUp; + /// signals if the elements list has changed + boost::signals2::signal signalElementsChanged; + //@} + + /** @name Attorneys for collaboration with helper classes */ + //@{ + friend class ViewProviderSketchDrawSketchHandlerAttorney; + friend class ViewProviderSketchCoinAttorney; + friend class ViewProviderSketchShortcutListenerAttorney; + //@} +protected: + /** @name enter/exit edit mode */ + //@{ + bool setEdit(int ModNum) override; + void unsetEdit(int ModNum) override; + void setEditViewer(Gui::View3DInventorViewer*, int ModNum) override; + void unsetEditViewer(Gui::View3DInventorViewer*) override; + static void camSensCB(void *data, SoSensor *); // camera sensor callback + void onCameraChanged(SoCamera* cam); + //@} + + /** @name miscelanea editing functions */ + //@{ + /// purges the DrawHandler if existing and tidies up + void deactivateHandler(); + /// get called if a subelement is double clicked while editing + void editDoubleClicked(); + //@} + + /** @name Solver Information */ + //@{ + /// update solver information based on last solving at SketchObject + void UpdateSolverInformation(); + + /// Auxiliary function to generate messages about conflicting, redundant and malformed constraints + static QString appendConstraintMsg( const QString & singularmsg, + const QString & pluralmsg, + const std::vector &vector); + //@} + + /** @name manage updates during undo/redo operations */ + //@{ + void slotUndoDocument(const Gui::Document&); + void slotRedoDocument(const Gui::Document&); + void forceUpdateData(); + //@} + + /** @name base class implementer */ + //@{ + /// get called by the container whenever a property has been changed + void onChanged(const App::Property *prop) override; + //@} + +private: + /// function to handle OCCT BSpline weight calculation singularities and representation + void scaleBSplinePoleCirclesAndUpdateSolverAndSketchObjectGeometry( + GeoListFacade & geolist, + bool geometrywithmemoryallocation); + + /** @name geometry and coordinates auxiliary functions */ + //@{ + /// give the coordinates of a line on the sketch plane in sketcher (2D) coordinates + void getCoordsOnSketchPlane(const SbVec3f &point, const SbVec3f &normal, double &u, double &v) const; + + /// give projecting line of position + void getProjectingLine(const SbVec2s&, + const Gui::View3DInventorViewer *viewer, + SbLine&) const; + //@} + + /** @name preselection functions */ + //@{ + /// helper to detect preselection + bool detectAndShowPreselection (SoPickedPoint * Point, const SbVec2s &cursorPos); + int getPreselectPoint() const; + int getPreselectCurve() const; + int getPreselectCross() const; + void setPreselectPoint(int PreselectPoint); + void setPreselectRootPoint(); + void resetPreselectPoint(); + + bool setPreselect(const std::string &subNameSuffix, float x = 0, float y = 0, float z = 0); + //@} + + /** @name Selection functions */ + //@{ + /// box selection method + void doBoxSelection(const SbVec2s &startPos, const SbVec2s &endPos, + const Gui::View3DInventorViewer *viewer); + + void addSelectPoint(int SelectPoint); + void removeSelectPoint(int SelectPoint); + void clearSelectPoints(); + + bool isSelected(const std::string & ss) const; + void rmvSelection(const std::string &subNameSuffix); + bool addSelection(const std::string &subNameSuffix, float x = 0, float y = 0, float z = 0); + bool addSelection2(const std::string &subNameSuffix, float x = 0, float y = 0, float z = 0); + //@} + + /** @name miscelanea utilities */ + //@{ + /// snap points x,y (mouse coordinates) onto grid if enabled + void snapToGrid(double &x, double &y); + + /// moves a selected constraint + void moveConstraint(int constNum, const Base::Vector2d &toPos); + + /// returns whether the sketch is in edit mode. + bool isInEditMode() const; + //@} + + /** @name Attorney functions*/ + //@{ + /* private functions to decouple Attorneys and Clients from the internal implementation of + the ViewProvider and its members, such as sketchObject (see friend attorney classes) and + improve encapsulation. + */ + + //********* ViewProviderSketchCoinAttorney *********************** + + bool constraintHasExpression(int constrid) const; + + const std::vector getConstraints() const; + + // gets the list of geometry of the sketchobject or of the solver instance + const GeoList getGeoList() const; + + GeoListFacade getGeoListFacade() const; + + Base::Placement getEditingPlacement() const; + + std::unique_ptr getRayPickAction() const; + + SbVec2f getScreenCoordinates(SbVec2f sketchcoordinates) const; + + QFont getApplicationFont() const; + + int defaultFontSizePixels() const; + + int getApplicationLogicalDPIX() const; + + double getRotation(SbVec3f pos0, SbVec3f pos1) const; + + bool isSketchInvalid() const; + + bool isSketchFullyConstrained() const; + + bool haveConstraintsInvalidGeometry() const; + + void addNodeToRoot(SoSeparator * node); + + void removeNodeFromRoot(SoSeparator * node); + + bool isConstraintPreselected(int constraintId) const; + + bool isPointSelected(int pointId) const; + + void executeOnSelectionPointSet(std::function && operation) const; + + bool isCurveSelected(int curveId) const; + + bool isConstraintSelected(int constraintId) const; + + //********* ViewProviderSketchShortcutListenerAttorney ***********// + void deleteSelected(); + + //********* ViewProviderSketchDrawSketchHandlerAttorney **********// + void setConstraintSelectability(bool enabled = true); + void setPositionText(const Base::Vector2d &Pos, const SbString &txt); + void setPositionText(const Base::Vector2d &Pos); + void resetPositionText(); + + /// draw the edit curve + void drawEdit(const std::vector &EditCurve); + void drawEdit(const std::list> &list); + /// draw the edit markers + void drawEditMarkers(const std::vector &EditMarkers, unsigned int augmentationlevel = 0); + /// set the pick style of the sketch coordinate axes + void setAxisPickStyle(bool on); + + void moveCursorToSketchPoint(Base::Vector2d point); + + void preselectAtPoint(Base::Vector2d point); + //@} + +private: + /** @name Solver message creation*/ + //@{ + /* private functions to decouple Attorneys and Clients from the internal implementation of + the ViewProvider and its members, such as sketchObject (see friend attorney classes) and + improve encapsulation. + */ + /// generates a warning message about constraint conflicts and appends it to the given message + static QString appendConflictMsg(const std::vector &conflicting); + /// generates a warning message about redundant constraints and appends it to the given message + static QString appendRedundantMsg(const std::vector &redundant); + /// generates a warning message about partially redundant constraints and appends it to the given message + static QString appendPartiallyRedundantMsg(const std::vector &partiallyredundant); + /// generates a warning message about redundant constraints and appends it to the given message + static QString appendMalformedMsg(const std::vector &redundant); + //@} + +private: + boost::signals2::connection connectUndoDocument; + boost::signals2::connection connectRedoDocument; + + // modes while sketching + SketchMode Mode; + + // reference coordinates for relative operations + Drag drag; + + Preselection preselection; + Selection selection; + + std::unique_ptr rubberband; + + std::string editDocName; + std::string editObjName; + std::string editSubName; + + ShortcutListener* listener; + + std::unique_ptr editCoinManager; + + std::unique_ptr pObserver; + + std::unique_ptr sketchHandler; + + ViewProviderParameters viewProviderParameters; + + SoNodeSensor cameraSensor; + int viewOrientationFactor; // stores if sketch viewed from front or back +}; + +} // namespace PartGui + + +#endif // SKETCHERGUI_VIEWPROVIDERSKETCH_H + diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketchCoinAttorney.h b/src/Mod/Sketcher/Gui/ViewProviderSketchCoinAttorney.h index 888c09b2bb..c9da259003 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketchCoinAttorney.h +++ b/src/Mod/Sketcher/Gui/ViewProviderSketchCoinAttorney.h @@ -113,7 +113,6 @@ private: friend class EditModeCoinManager; friend class EditModeConstraintCoinManager; friend class EditModeGeometryCoinManager; - friend class EditModeGridCoinManager; friend class EditModeInformationOverlayCoinConverter; friend class EditModeGeometryCoinConverter; };