From bb204842f2a786b0edddec53ae835d01fa715998 Mon Sep 17 00:00:00 2001 From: Uwe Date: Sat, 5 Feb 2022 01:57:57 +0100 Subject: [PATCH] [Part] feature to make persistent section cut views (#4118) * [Part] feature to make persistent section cut views * one aim is to have a feature to make real cuts, not hollow ones like with clipping * the second one is to have a persistent view, so an object in the tree that can be further exported, meshed etc. For a detailed description with examples see https://forum.freecadweb.org/viewtopic.php?f=17&t=52441 --- src/Gui/Workbench.cpp | 2 +- src/Mod/Part/Gui/CMakeLists.txt | 4 + src/Mod/Part/Gui/Command.cpp | 65 ++ src/Mod/Part/Gui/SectionCutting.cpp | 1475 +++++++++++++++++++++++++++ src/Mod/Part/Gui/SectionCutting.h | 86 ++ src/Mod/Part/Gui/SectionCutting.ui | 331 ++++++ 6 files changed, 1962 insertions(+), 1 deletion(-) create mode 100644 src/Mod/Part/Gui/SectionCutting.cpp create mode 100644 src/Mod/Part/Gui/SectionCutting.h create mode 100644 src/Mod/Part/Gui/SectionCutting.ui diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index 82164c4ec1..950f442ffe 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -651,7 +651,7 @@ MenuItem* StdWorkbench::setupMenuBar() const *view << "Std_ViewCreate" << "Std_OrthographicCamera" << "Std_PerspectiveCamera" << "Std_MainFullscreen" << "Separator" << stdviews << "Std_FreezeViews" << "Std_DrawStyle" << "Std_SelBoundingBox" << "Separator" << view3d << zoom - << "Std_ViewDockUndockFullscreen" << "Std_AxisCross" << "Std_ToggleClipPlane" + << "Std_ViewDockUndockFullscreen" << "Std_AxisCross" << "Std_ToggleClipPlane" << "Part_SectionCut" << "Std_TextureMapping" #ifdef BUILD_VR << "Std_ViewVR" diff --git a/src/Mod/Part/Gui/CMakeLists.txt b/src/Mod/Part/Gui/CMakeLists.txt index 007ef00cd0..fa5c542ac8 100644 --- a/src/Mod/Part/Gui/CMakeLists.txt +++ b/src/Mod/Part/Gui/CMakeLists.txt @@ -63,6 +63,7 @@ set(PartGui_UIC_SRCS DlgSettingsGeneral.ui DlgSettingsObjectColor.ui DlgProjectionOnSurface.ui + SectionCutting.ui ShapeFromMesh.ui TaskFaceColors.ui TaskShapeBuilder.ui @@ -210,6 +211,9 @@ SET(PartGui_SRCS Workbench.h ReferenceHighlighter.cpp ReferenceHighlighter.h + SectionCutting.cpp + SectionCutting.h + SectionCutting.ui ShapeFromMesh.cpp ShapeFromMesh.h TaskFaceColors.cpp diff --git a/src/Mod/Part/Gui/Command.cpp b/src/Mod/Part/Gui/Command.cpp index ecb8b80fe0..daa02eaca5 100644 --- a/src/Mod/Part/Gui/Command.cpp +++ b/src/Mod/Part/Gui/Command.cpp @@ -68,6 +68,7 @@ #include "DlgProjectionOnSurface.h" #include "DlgRevolution.h" #include "Mirroring.h" +#include "SectionCutting.h" #include "TaskCheckGeometry.h" #include "TaskDimension.h" #include "TaskLoft.h" @@ -2468,6 +2469,69 @@ bool CmdPartProjectionOnSurface::isActive(void) return (hasActiveDocument() && !Gui::Control().activeDialog()); } +//=========================================================================== +// Part_SectionCut +//=========================================================================== + +DEF_STD_CMD_AC(CmdPartSectionCut) + +CmdPartSectionCut::CmdPartSectionCut() + : Command("Part_SectionCut") +{ + sAppModule = "Part"; + sGroup = QT_TR_NOOP("Part"); + sMenuText = QT_TR_NOOP("Persistent section cut"); + sToolTipText = QT_TR_NOOP("Creates a persistent section cut of visible part objects"); + sWhatsThis = "Part_SectionCut"; + sStatusTip = sToolTipText; + eType = Alter3DView; +} + +Gui::Action* CmdPartSectionCut::createAction(void) +{ + Gui::Action* pcAction = (Gui::Action*)Gui::Command::createAction(); +#if 0 + pcAction->setCheckable(true); +#endif + return pcAction; +} + +void CmdPartSectionCut::activated(int iMsg) +{ + Q_UNUSED(iMsg); + static QPointer sectionCut = nullptr; + if (!sectionCut) { + Gui::View3DInventor* view = qobject_cast(Gui::getMainWindow()->activeWindow()); + if (view) { + sectionCut = PartGui::SectionCut::makeDockWidget(view); + } + } +} + +bool CmdPartSectionCut::isActive(void) +{ +#if 0 + Gui::View3DInventor* view = qobject_cast(Gui::getMainWindow()->activeWindow()); + if (view) { + Gui::Action* action = qobject_cast(_pcAction); + if (action->isChecked() != view->hasClippingPlane()) + action->setChecked(view->hasClippingPlane()); + return true; + } + else { + Gui::Action* action = qobject_cast(_pcAction); + if (action->isChecked()) + action->setChecked(false); + return false; + } +#else + Gui::View3DInventor* view = qobject_cast(Gui::getMainWindow()->activeWindow()); + return view ? true : false; +#endif +} + +//--------------------------------------------------------------- + void CreatePartCommands(void) { Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); @@ -2518,4 +2582,5 @@ void CreatePartCommands(void) rcCmdMgr.addCommand(new CmdMeasureToggleDelta()); rcCmdMgr.addCommand(new CmdBoxSelection()); rcCmdMgr.addCommand(new CmdPartProjectionOnSurface()); + rcCmdMgr.addCommand(new CmdPartSectionCut()); } diff --git a/src/Mod/Part/Gui/SectionCutting.cpp b/src/Mod/Part/Gui/SectionCutting.cpp new file mode 100644 index 0000000000..4e19174140 --- /dev/null +++ b/src/Mod/Part/Gui/SectionCutting.cpp @@ -0,0 +1,1475 @@ +/*************************************************************************** + * Copyright (c) 2022 Uwe Stöhr * + * * + * 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 "SectionCutting.h" +#include "ui_SectionCutting.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace PartGui; + +enum Refresh : bool +{ + notXValue = false, + notYValue = false, + notZValue = false, + notXRange = false, + notYRange = false, + notZRange = false, + XValue = true, + YValue = true, + ZValue = true, + XRange = true, + YRange = true, + ZRange = true +}; + +SectionCut::SectionCut(Gui::View3DInventor* view, QWidget* parent) + : QDialog(parent) + , ui(new Ui_SectionCut) +{ + // create widgets + ui->setupUi(this); + ui->cutX->setRange(-INT_MAX, INT_MAX); + ui->cutY->setRange(-INT_MAX, INT_MAX); + ui->cutZ->setRange(-INT_MAX, INT_MAX); + + view = view; + Gui::View3DInventorViewer* viewer = view->getViewer(); + + // get all objects in the document + doc = Gui::Application::Instance->activeDocument()->getDocument(); + if (!doc) { + Base::Console().Error("SectionCut error: there is no document\n"); + return; + } + + std::vector ObjectsList = doc->getObjects(); + if (ObjectsList.empty()) { + Base::Console().Error("SectionCut error: there are no objects in the document\n"); + return; + } + // empty the ObjectsListVisible + ObjectsListVisible.clear(); + // now store those that are currently visible + for (auto it = ObjectsList.begin(); it != ObjectsList.end(); ++it) { + if ((*it)->Visibility.getValue()) + ObjectsListVisible.push_back((*it)); + } + + // we can have existing cut boxes take their values + // the flip state cannot be readout of the box position, therefore readout the position + // is if it was unflipped + if (doc->getObject(BoxZName)) { + hasBoxZ = true; + ui->groupBoxZ->setChecked(true); + Part::Box* pcBox = static_cast(doc->getObject(BoxZName)); + if (!pcBox) { + Base::Console().Error("SectionCut error: cut box is incorrectly named, cannot proceed\n"); + return; + } + ui->cutZ->setValue(pcBox->Height.getValue() - fabs(pcBox->Placement.getValue().getPosition().z)); + } + if (doc->getObject(BoxYName)) { + hasBoxY = true; + ui->groupBoxY->setChecked(true); + Part::Box* pcBox = static_cast(doc->getObject(BoxYName)); + if (!pcBox) { + Base::Console().Error("SectionCut error: cut box is incorrectly named, cannot proceed\n"); + return; + } + ui->cutY->setValue(pcBox->Width.getValue() - fabs(pcBox->Placement.getValue().getPosition().y)); + } + if (doc->getObject(BoxXName)) { + hasBoxX = true; + ui->groupBoxX->setChecked(true); + Part::Box* pcBox = static_cast(doc->getObject(BoxXName)); + if (!pcBox) { + Base::Console().Error("SectionCut error: cut box is incorrectly named, cannot proceed\n"); + return; + } + ui->cutX->setValue(pcBox->Length.getValue() - fabs(pcBox->Placement.getValue().getPosition().x)); + } + + // hide existing cuts to check if there are objects to be cut visible + if (doc->getObject(CutXName)) + doc->getObject(CutXName)->Visibility.setValue(false); + if (doc->getObject(CutYName)) + doc->getObject(CutYName)->Visibility.setValue(false); + if (doc->getObject(CutZName)) + doc->getObject(CutZName)->Visibility.setValue(false); + + // get bounding box + SbBox3f box = getViewBoundingBox(); + if (!box.isEmpty()) { + // if there is a cut box, perform the cut + if (hasBoxX || hasBoxY || hasBoxZ) { + // refresh only the range since we set the values above already + refreshCutRanges(box, Refresh::notXValue, Refresh::notYValue, Refresh::notZValue, + Refresh::XRange, Refresh::YRange, Refresh::ZRange); + } + else + refreshCutRanges(box); + } + // the case of an empty box and having cuts will be handles later by startCutting(true) + + connect(ui->groupBoxX, &QGroupBox::toggled, this, &SectionCut::onGroupBoxXtoggled); + connect(ui->groupBoxY, &QGroupBox::toggled, this, &SectionCut::onGroupBoxYtoggled); + connect(ui->groupBoxZ, &QGroupBox::toggled, this, &SectionCut::onGroupBoxZtoggled); + connect(ui->cutX, QOverload::of(&QDoubleSpinBox::valueChanged), this, &SectionCut::onCutXvalueChanged); + connect(ui->cutY, QOverload::of(&QDoubleSpinBox::valueChanged), this, &SectionCut::onCutYvalueChanged); + connect(ui->cutZ, QOverload::of(&QDoubleSpinBox::valueChanged), this, &SectionCut::onCutZvalueChanged); + connect(ui->cutXHS, &QSlider::sliderMoved, this, &SectionCut::onCutXHSsliderMoved); + connect(ui->cutYHS, &QSlider::sliderMoved, this, &SectionCut::onCutYHSsliderMoved); + connect(ui->cutZHS, &QSlider::sliderMoved, this, &SectionCut::onCutZHSsliderMoved); + connect(ui->flipX, &QPushButton::clicked, this, &SectionCut::onFlipXclicked); + connect(ui->flipY, &QPushButton::clicked, this, &SectionCut::onFlipYclicked); + connect(ui->flipZ, &QPushButton::clicked, this, &SectionCut::onFlipZclicked); + connect(ui->RefreshCutPB, &QPushButton::clicked, this, &SectionCut::onRefreshCutPBclicked); + + // if there is a cut, perform it + if (hasBoxX || hasBoxY || hasBoxZ) { + ui->RefreshCutPB->setEnabled(false); + startCutting(true); + } +} + +// actions to be done when document was closed +void SectionCut::noDocumentActions() +{ + blockSignals(true); + doc = nullptr; + // reset the cut group boxes + ui->groupBoxX->setChecked(false); + ui->groupBoxY->setChecked(false); + ui->groupBoxZ->setChecked(false); + ui->RefreshCutPB->setEnabled(true); + blockSignals(false); +} + +void SectionCut::startCutting(bool isInitial) +{ + // there might be no document + if (!Gui::Application::Instance->activeDocument()) { + noDocumentActions(); + return; + } + // the document might have been changed + if (doc != Gui::Application::Instance->activeDocument()->getDocument()) + // refresh documents list + onRefreshCutPBclicked(); + + // we will reuse it several times + std::vector::iterator it; + + // delete the objects we might have already created to cut + // we must do this because we support several cuts at once and + // it is dangerous to deal with the fact that the user is free + // to uncheck cutting planes and to add/remove objects while this dialog is open + // We must remove in this order because the tree hierary of the features is Z->Y->X and Cut->Box + App::DocumentObject* anObject; + if (doc->getObject(CutZName)) { + anObject = doc->getObject(CutZName); + // the deleted object might have been visible before, thus check and delete it from the list + for (it = ObjectsListVisible.begin(); it != ObjectsListVisible.end(); ++it) { + if ((*it) == anObject) { + ObjectsListVisible.erase((it)); + break; + } + } + doc->removeObject(CutZName); + } + if (doc->getObject(BoxZName)) { + anObject = doc->getObject(BoxZName); + for (it = ObjectsListVisible.begin(); it != ObjectsListVisible.end(); ++it) { + if ((*it) == anObject) { + ObjectsListVisible.erase((it)); + break; + } + } + doc->removeObject(BoxZName); + } + if (doc->getObject(CutYName)) { + anObject = doc->getObject(CutYName); + for (it = ObjectsListVisible.begin(); it != ObjectsListVisible.end(); ++it) { + if ((*it) == anObject) { + ObjectsListVisible.erase((it)); + break; + } + } + doc->removeObject(CutYName); + } + if (doc->getObject(BoxYName)) { + anObject = doc->getObject(BoxYName); + for (it = ObjectsListVisible.begin(); it != ObjectsListVisible.end(); ++it) { + if ((*it) == anObject) { + ObjectsListVisible.erase((it)); + break; + } + } + doc->removeObject(BoxYName); + } + if (doc->getObject(CutXName)) { + anObject = doc->getObject(CutXName); + for (it = ObjectsListVisible.begin(); it != ObjectsListVisible.end(); ++it) { + if ((*it) == anObject) { + ObjectsListVisible.erase((it)); + break; + } + } + doc->removeObject(CutXName); + } + if (doc->getObject(BoxXName)) { + anObject = doc->getObject(BoxXName); + for (it = ObjectsListVisible.begin(); it != ObjectsListVisible.end(); ++it) { + if ((*it) == anObject) { + ObjectsListVisible.erase((it)); + break; + } + } + doc->removeObject(BoxXName); + } + if (doc->getObject(CompoundName)) { + auto compoundObject = doc->getObject(CompoundName); + Part::Compound* pcCompoundDel = static_cast(compoundObject); + std::vector compoundObjects; + pcCompoundDel->Links.getLinks(compoundObjects); + // first delete the compound + for (it = ObjectsListVisible.begin(); it != ObjectsListVisible.end(); ++it) { + if ((*it) == anObject) { + ObjectsListVisible.erase((it)); + break; + } + } + doc->removeObject(CompoundName); + // now delete the objects that have been part of the compound + for (it = compoundObjects.begin(); it != compoundObjects.end(); it++) { + for (auto itOV = ObjectsListVisible.begin(); itOV != ObjectsListVisible.end(); ++itOV) { + if ((*itOV) == doc->getObject((*it)->getNameInDocument())) { + ObjectsListVisible.erase((itOV)); + break; + } + } + doc->removeObject((*it)->getNameInDocument()); + } + } + + // make all objects visible that have been visible when the dialog was called + // because we made them invisible when we created cuts + for (it = ObjectsListVisible.begin(); it != ObjectsListVisible.end(); ++it) { + if ((*it)->isValid()) // a formerly visible object might have been deleted + (*it)->Visibility.setValue(true); + else { + // we must refresh the ObjectsListVisible list + onRefreshCutPBclicked(); + } + } + + // we enable the sliders because for assemblies we disabled them + ui->cutXHS->setEnabled(true); + ui->cutYHS->setEnabled(true); + ui->cutZHS->setEnabled(true); + + // ObjectsListVisible contains all visible objects of the document, but we can only cut + // those that have a solid shape + std::vector ObjectsListCut; + for (it = ObjectsListVisible.begin(); it != ObjectsListVisible.end(); ++it) { + // we need all Link objects in App::Parts for example for Assembly 4 + if ((*it)->getTypeId() == Base::Type::fromName("App::Part")) { + App::Part* pcPart = static_cast((*it)); + bool isLinkAssembly = false; + // collect all its link objects + auto groupObjects = pcPart->Group.getValue(); + for (auto itGO = groupObjects.begin(); itGO != groupObjects.end(); ++itGO) { + if ((*itGO)->getTypeId() == Base::Type::fromName("App::Link")) { + ObjectsListCut.push_back((*itGO)); + // we assume that App::Links inside a App::Part are an assembly + isLinkAssembly = true; + } + } + if (isLinkAssembly) { + // we disable the sliders because for assemblies it will takes ages to do several dozen recomputes + QString SliderToolTip = tr("Sliders are disabled for assemblies"); + ui->cutXHS->setEnabled(false); + ui->cutXHS->setToolTip(SliderToolTip); + ui->cutYHS->setEnabled(false); + ui->cutYHS->setToolTip(SliderToolTip); + ui->cutZHS->setEnabled(false); + ui->cutZHS->setToolTip(SliderToolTip); + } + } + // get all shapes that are also Part::Features + if ((*it)->getPropertyByName("Shape") + && (*it)->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Feature"))) { + // sort out 2D objects, datums, App:Parts, compounds and objects that are part of a PartDesign body + if (!(*it)->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Part2DObject")) + && !(*it)->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Datum")) + && !(*it)->getTypeId().isDerivedFrom(Base::Type::fromName("PartDesign::Feature")) + && !(*it)->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Compound")) + && (*it)->getTypeId() != Base::Type::fromName("App::Part")) + ObjectsListCut.push_back((*it)); + } + } + + // sort out objects that are part of Part::Boolean, Part::MultiCommon, Part::MultiFuse, + // Part::Thickness and Part::FilletBase + std::vector::iterator it2; + std::vector::iterator it3; + // check list of visible objects and not cut list because we want to repove from the cut list + for (it = ObjectsListVisible.begin(); it != ObjectsListVisible.end(); ++it) { + if ( (*it)->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Boolean")) + || (*it)->getTypeId().isDerivedFrom(Base::Type::fromName("Part::MultiCommon")) + || (*it)->getTypeId().isDerivedFrom(Base::Type::fromName("Part::MultiFuse")) + || (*it)->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Thickness")) + || (*it)->getTypeId().isDerivedFrom(Base::Type::fromName("Part::FilletBase")) ) { + // get possible links + auto subObjectList = (*it)->getOutList(); + // if there links, delete them + if (!subObjectList.empty()) { + for (it2 = subObjectList.begin(); it2 != subObjectList.end(); ++it2) { + for (it3 = ObjectsListCut.begin(); it3 != ObjectsListCut.end(); ++it3) { + if ((*it2) == (*it3)) { + ObjectsListCut.erase(it3); + break; + } + } + } + } + } + } + + // we might have no objects that can be cut + if (ObjectsListCut.empty()) { + if (isInitial) + Base::Console().Error("SectionCut error: there are no visible objects to be cut\n"); + else + Base::Console().Error("SectionCut error: there are no objects in the document that can be cut\n"); + // block signals to be able to reset the cut group boxes without calling startCutting again + blockSignals(true); + ui->groupBoxX->setChecked(false); + ui->groupBoxY->setChecked(false); + ui->groupBoxZ->setChecked(false); + ui->RefreshCutPB->setEnabled(true); + blockSignals(false); + return; + } + + // we cut this way: + // 1. put all existing objects into a part compound + // 2. create a box with the size of the bounding box + // 3. cut the box from the compound + + // depending on how many cuts should be performed, we need as many boxes + // if nothing is yet to be cut, we can return + if (!ui->groupBoxX->isChecked() && !ui->groupBoxY->isChecked() + && !ui->groupBoxZ->isChecked()) { + // there is no active cut, so we can enable refresh button + ui->RefreshCutPB->setEnabled(true); + return; + } + + // disable refresh button + ui->RefreshCutPB->setEnabled(false); + + // create an empty compound + auto CutCompound = doc->addObject("Part::Compound", CompoundName); + if (!CutCompound) { + Base::Console().Error( (std::string("SectionCut error: ") + + std::string(CompoundName) + std::string(" could not be added\n")).c_str() ); + return; + } + Part::Compound* pcCompound = static_cast(CutCompound); + // fill it with all found elements with the copies of the elements + int i = 0; + for (it = ObjectsListCut.begin(), i = 0; it != ObjectsListCut.end(); ++it, i++) { + // first create a link with a unique name + std::string newName = (*it)->getNameInDocument(); + newName = newName + "_CutLink"; + + auto newObject = doc->addObject("App::Link", newName.c_str()); + if (!newObject) { + Base::Console().Error("SectionCut error: 'App::Link' could not be added\n"); + return; + } + App::Link* pcLink = static_cast(newObject); + // set the object to the created empty link object + pcLink->LinkedObject.setValue((*it)); + // we want to get the link at the same position as the original + pcLink->LinkTransform.setValue(true); + // add the link to the compound + pcCompound->Links.set1Value(i, newObject); + + // hide the objects since only the cut should later be visible + (*it)->Visibility.setValue(false); + } + + // compute the filled compound + pcCompound->recomputeFeature(); + + // make all objects invisible so that only the compound remains + for (it = ObjectsListVisible.begin(); it != ObjectsListVisible.end(); ++it) { + (*it)->Visibility.setValue(false); + } + + // the area in which we can cut is the size of the compound + // we get its size by its bounding box + SbBox3f CompoundBoundingBox = getViewBoundingBox(); + if (CompoundBoundingBox.isEmpty()) { + Base::Console().Error("SectionCut error: the CompoundBoundingBox is empty\n"); + return; + } + + // store the current cut positions te reset them later if possible + double CutPosX = ui->cutX->value(); + double CutPosY = ui->cutY->value(); + double CutPosZ = ui->cutZ->value(); + + // refresh all cut limits according to the new bounding box + refreshCutRanges(CompoundBoundingBox); + + // prepare the cut box size according to the bounding box size + std::vector BoundingBoxSize = { 0.0, 0.0, 0.0 }; + CompoundBoundingBox.getSize(BoundingBoxSize[0], BoundingBoxSize[1], BoundingBoxSize[2]); + // get placement of the bunding box origin + std::vector BoundingBoxOrigin = { 0.0, 0.0, 0.0 }; + CompoundBoundingBox.getOrigin(BoundingBoxOrigin[0], BoundingBoxOrigin[1], BoundingBoxOrigin[2]); + + // now we can create the cut boxes + Base::Vector3d BoxOriginSet; + Base::Placement placement; + SbBox3f CutBoundingBox; + hasBoxX = false; + hasBoxY = false; + hasBoxZ = false; + hasBoxCustom = false; + + if (ui->groupBoxX->isChecked()) { + // create a box + auto CutBox = doc->addObject("Part::Box", BoxXName); + if (!CutBox) { + Base::Console().Error( (std::string("SectionCut error: ") + + std::string(BoxXName) + std::string(" could not be added\n")).c_str() ); + return; + } + Part::Box* pcBox = static_cast(CutBox); + // it appears that because of internal rounding errors, the bounding box is sometimes + // a bit too small, for example for epplipsoides, thus make the box a bit larger + pcBox->Length.setValue(BoundingBoxSize[0] + 1.0); + pcBox->Width.setValue(BoundingBoxSize[1] + 1.0); + pcBox->Height.setValue(BoundingBoxSize[2] + 1.0); + // set the previous cut value because refreshCutRanges changed it + // in case the there was previously no cut, nothing will actually be changed + // the previous value might now be outside the current possible range, then reset it + if (CutPosX >= ui->cutX->maximum()) { + CutPosX = ui->cutX->maximum() - 0.1; // short below the maximum + } + else if (CutPosX <= ui->cutX->minimum()) { + CutPosX = ui->cutX->minimum() + 0.1; // short above the minimum + } + // we don't set the value to ui->cutX because this would refresh the cut + // which we don't have yet, thus do this later + //set the box position + if (!ui->flipX->isChecked()) + BoxOriginSet.x = CutPosX - (BoundingBoxSize[0] + 1.0); + else //flipped + BoxOriginSet.x = CutPosX; + // we made the box 1.0 larger that we can place it 0.5 below the bounding box + BoxOriginSet.y = BoundingBoxOrigin[1] - 0.5; + BoxOriginSet.z = BoundingBoxOrigin[2] - 0.5; + placement.setPosition(BoxOriginSet); + pcBox->Placement.setValue(placement); + + // create a cut feature + auto CutFeature = doc->addObject("Part::Cut", CutXName); + if (!CutFeature) { + Base::Console().Error( (std::string("SectionCut error: ") + + std::string(CutXName) + std::string(" could not be added\n")).c_str() ); + return; + } + Part::Cut* pcCut = static_cast(CutFeature); + pcCut->Base.setValue(CutCompound); + pcCut->Tool.setValue(CutBox); + + // set the cut value + ui->cutX->setValue(CutPosX); + // recomputing recursively is especially for assemblies very time-consuming + // however there must be a final recursicve recompute and we do this at the end + // so only recomute recursively if there are no other cuts + if (!ui->groupBoxY->isChecked() && !ui->groupBoxZ->isChecked()) + pcCut->recomputeFeature(true); + else + pcCut->recomputeFeature(false); + hasBoxX = true; + } + if (ui->groupBoxY->isChecked()) { + // if there is a X cut, its size defines the possible range for the Y cut + // the cut box size is not affected, it can be as large as the compound + if (hasBoxX) { + CutBoundingBox = getViewBoundingBox(); + // refresh the Y cut limits according to the new bounding box + refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::notZValue, + Refresh::notXRange, Refresh::YRange, Refresh::notZRange); + } + auto CutBox = doc->addObject("Part::Box", BoxYName); + if (!CutBox) { + Base::Console().Error((std::string("SectionCut error: ") + + std::string(BoxYName) + std::string(" could not be added\n")).c_str() ); + return; + } + Part::Box* pcBox = static_cast(CutBox); + pcBox->Length.setValue(BoundingBoxSize[0] + 1.0); + pcBox->Width.setValue(BoundingBoxSize[1] + 1.0); + pcBox->Height.setValue(BoundingBoxSize[2] + 1.0); + // reset previous cut value + if (CutPosY >= ui->cutY->maximum()) { + CutPosY = ui->cutY->maximum() - 0.1; // short below the maximum + } + else if (CutPosY <= ui->cutY->minimum()) { + CutPosY = ui->cutY->minimum() + 0.1; // short above the minimum + } + //set the box position + BoxOriginSet.x = BoundingBoxOrigin[0] - 0.5; + if (!ui->flipY->isChecked()) + BoxOriginSet.y = CutPosY - (BoundingBoxSize[1] + 1.0); + else //flipped + BoxOriginSet.y = CutPosY; + BoxOriginSet.z = BoundingBoxOrigin[2] - 0.5; + placement.setPosition(BoxOriginSet); + pcBox->Placement.setValue(placement); + + auto CutFeature = doc->addObject("Part::Cut", CutYName); + if (!CutFeature) { + Base::Console().Error((std::string("SectionCut error: ") + + std::string(CutYName) + std::string(" could not be added\n")).c_str() ); + return; + } + Part::Cut* pcCut = static_cast(CutFeature); + // if there is already a cut, we must take it as feature to be cut + if (hasBoxX) + pcCut->Base.setValue(doc->getObject(CutXName)); + else + pcCut->Base.setValue(CutCompound); + pcCut->Tool.setValue(CutBox); + + // set the cut value + ui->cutY->setValue(CutPosY); + if (!ui->groupBoxZ->isChecked()) + pcCut->recomputeFeature(true); + else + pcCut->recomputeFeature(false); + hasBoxY = true; + } + if (ui->groupBoxZ->isChecked()) { + if (hasBoxX || hasBoxY) { + CutBoundingBox = getViewBoundingBox(); + refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::notZValue, + Refresh::notXRange, Refresh::notYRange, Refresh::ZRange); + } + auto CutBox = doc->addObject("Part::Box", BoxZName); + if (!CutBox) { + Base::Console().Error((std::string("SectionCut error: ") + + std::string(BoxZName) + std::string(" could not be added\n")).c_str() ); + return; + } + Part::Box* pcBox = static_cast(CutBox); + pcBox->Length.setValue(BoundingBoxSize[0] + 1.0); + pcBox->Width.setValue(BoundingBoxSize[1] + 1.0); + pcBox->Height.setValue(BoundingBoxSize[2] + 1.0); + // reset previous cut value + if (CutPosZ >= ui->cutZ->maximum()) { + CutPosZ = ui->cutZ->maximum() - 0.1; // short below the maximum + } + else if (CutPosZ <= ui->cutZ->minimum()) { + CutPosZ = ui->cutZ->minimum() + 0.1; // short above the minimum + } + //set the box position + BoxOriginSet.x = BoundingBoxOrigin[0] - 0.5; + BoxOriginSet.y = BoundingBoxOrigin[1] - 0.5; + if (!ui->flipY->isChecked()) + BoxOriginSet.z = CutPosZ - (BoundingBoxSize[2] + 1.0); + else //flipped + BoxOriginSet.z = CutPosZ; + placement.setPosition(BoxOriginSet); + pcBox->Placement.setValue(placement); + + auto CutFeature = doc->addObject("Part::Cut", CutZName); + if (!CutFeature) { + Base::Console().Error( (std::string("SectionCut error: ") + + std::string(CutZName) + std::string(" could not be added\n")).c_str() ); + return; + } + Part::Cut* pcCut = static_cast(CutFeature); + // if there is already a cut, we must take it as feature to be cut + if (hasBoxY) { + pcCut->Base.setValue(doc->getObject(CutYName)); + } + else if (hasBoxX && !hasBoxY) { + pcCut->Base.setValue(doc->getObject(CutXName)); + } + else { + pcCut->Base.setValue(CutCompound); + } + pcCut->Tool.setValue(CutBox); + + // set the cut value + ui->cutZ->setValue(CutPosZ); + pcCut->recomputeFeature(true); + hasBoxZ = true; + } +} + +SectionCut* SectionCut::makeDockWidget(Gui::View3DInventor* view) +{ + // embed this dialog into a QDockWidget + SectionCut* sectionCut = new SectionCut(view); + Gui::DockWindowManager* pDockMgr = Gui::DockWindowManager::instance(); + // the dialog is designed that you can see the tree, thus put it to the right side + QDockWidget* dw = pDockMgr->addDockWindow("Section Cutting", sectionCut, Qt::RightDockWidgetArea); + dw->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); + //dw->setFloating(true); + dw->show(); + + return sectionCut; +} + +/** Destroys the object and frees any allocated resources */ +SectionCut::~SectionCut() +{ + // there might be no document + if (!Gui::Application::Instance->activeDocument()) { + noDocumentActions(); + return; + } + if (!ui->keepOnlyCutCB->isChecked()) { + // make all objects visible that have been visible when the dialog was called + // because we made them invisible when we created cuts + for (auto it = ObjectsListVisible.begin(); it != ObjectsListVisible.end(); ++it) { + if ((*it)->isValid()) // a formerly visible object might have been deleted + (*it)->Visibility.setValue(true); + } + } +} + +void SectionCut::reject() +{ + QDialog::reject(); + QDockWidget* dw = qobject_cast(parent()); + if (dw) { + dw->deleteLater(); + } +} + +void SectionCut::onGroupBoxXtoggled() +{ + // reset the cut + startCutting(); +} + +void SectionCut::onGroupBoxYtoggled() +{ + startCutting(); +} + +void SectionCut::onGroupBoxZtoggled() +{ + startCutting(); +} + +void SectionCut::onCutXvalueChanged(double val) +{ + // there might be no document + if (!Gui::Application::Instance->activeDocument()) { + noDocumentActions(); + return; + } + // refresh objects and return in case the document was changed + if (doc != Gui::Application::Instance->activeDocument()->getDocument()) { + onRefreshCutPBclicked(); + return; + } + // update slider position and tooltip + // the slider value is % of the cut range + ui->cutXHS->setValue( + int((val - ui->cutX->minimum()) + / (ui->cutX->maximum() - ui->cutX->minimum()) * 100.0)); + ui->cutXHS->setToolTip(QString::number(val, 'g', Base::UnitsApi::getDecimals())); + + // we cannot cut to the edge because then the result is an empty shape + // we chose purposely not to simply set the range for cutX previously + // because everything is allowed just not the min/max + if (ui->cutX->value() == ui->cutX->maximum()) { + ui->cutX->setValue(ui->cutX->maximum() - 0.1); + return; + } + if (ui->cutX->value() == ui->cutX->minimum()) { + ui->cutX->setValue(ui->cutX->minimum() + 0.1); + return; + } + // get the cut box + auto CutBox = doc->getObject(BoxXName); + // when the value has been set after resetting the compound bounding box + // there is not yet a cut and we do nothing + if (!CutBox) + return; + Part::Box* pcBox = static_cast(CutBox); + // get its placement and size + Base::Placement placement = pcBox->Placement.getValue(); + Base::Vector3d BoxPosition = placement.getPosition(); + // change the placement + if (!ui->flipX->isChecked()) + BoxPosition.x = ui->cutX->value() - pcBox->Length.getValue(); + else //flipped + BoxPosition.x = ui->cutX->value(); + placement.setPosition(BoxPosition); + pcBox->Placement.setValue(placement); + + auto CutObject = doc->getObject(CutXName); + // there should be a box, but maybe the user deleted it meanwhile + if (!CutObject) { + Base::Console().Warning((std::string("SectionCut warning: there is no ") + + std::string(CutXName) + std::string(", trying to recreate it\n")).c_str()); + // recreate the box + startCutting(); + return; + } + + // if there is another cut, we must recalculate it too + // we might have cut so that the range for Y and Z is now smaller + // the hierarchy is always Z->Y->X + if (hasBoxY && !hasBoxZ) { // only Y + auto CutFeatureY = doc->getObject(CutYName); + if (!CutFeatureY) { + Base::Console().Warning((std::string("SectionCut warning: there is no ") + + std::string(CutYName) + std::string(", trying to recreate it\n")).c_str()); + startCutting(); + return; + } + // refresh the Y and Z cut limits according to the new bounding box of the cut result + // make the SectionCutY invisible + CutFeatureY->Visibility.setValue(false); + // make SectionCutX visible + CutObject->Visibility.setValue(true); + // get new bounding box + auto CutBoundingBox = getViewBoundingBox(); + // refresh Y limits and Z limits + Z value + refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::ZValue, + Refresh::notXRange, Refresh::YRange, Refresh::ZRange); + // the value of Y can now be outside or at the limit, in this case reset the value too + if ((ui->cutY->value() >= ui->cutY->maximum()) + || (ui->cutY->value() <= ui->cutY->minimum())) + refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::YValue, Refresh::ZValue, + Refresh::notXRange, Refresh::YRange, Refresh::ZRange); + // make the SectionCutY visible again + CutFeatureY->Visibility.setValue(true); + // make SectionCutX invisible again + CutObject->Visibility.setValue(false); + // recompute the cut + Part::Cut* pcCutY = static_cast(CutFeatureY); + pcCutY->recomputeFeature(true); + } + else if (hasBoxZ) { // at least Z + // the main cut is Z, no matter if there is a cut in Y + auto CutFeatureZ = doc->getObject(CutZName); + if (!CutFeatureZ) { + Base::Console().Error((std::string("SectionCut error: there is no ") + + std::string(CutZName) + std::string("\n")).c_str()); + return; + } + // refresh the Y and Z cut limits according to the new bounding box of the cut result + // make the SectionCutZ invisible + CutFeatureZ->Visibility.setValue(false); + // make SectionCutX visible + CutObject->Visibility.setValue(true); + // get new bounding box + auto CutBoundingBox = getViewBoundingBox(); + // refresh Y and Z limits + if (hasBoxY) { + refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::notZValue, + Refresh::notXRange, Refresh::YRange, Refresh::ZRange); + // the value of Y or Z can now be outside or at the limit, in this case reset the value too + if ((ui->cutY->value() >= ui->cutY->maximum()) + || (ui->cutY->value() <= ui->cutY->minimum())) + refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::YValue, Refresh::notZValue, + Refresh::notXRange, Refresh::YRange, Refresh::ZRange); + if ((ui->cutZ->value() >= ui->cutZ->maximum()) + || (ui->cutZ->value() <= ui->cutZ->minimum())) + refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::ZValue, + Refresh::notXRange, Refresh::YRange, Refresh::ZRange); + } + else { // there is no Y cut yet so we can set the Y value too + refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::YValue, Refresh::notZValue, + Refresh::notXRange, Refresh::YRange, Refresh::ZRange); + // the value of Z can now be outside or at the limit, in this case reset the value too + if ((ui->cutZ->value() >= ui->cutZ->maximum()) + || (ui->cutZ->value() <= ui->cutZ->minimum())) + refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::YValue, Refresh::ZValue, + Refresh::notXRange, Refresh::YRange, Refresh::ZRange); + } + // make the SectionCutZ visible again + CutFeatureZ->Visibility.setValue(true); + // make SectionCutX invisible again + CutObject->Visibility.setValue(false); + // recompute the cut + Part::Cut* pcCutZ = static_cast(CutFeatureZ); + pcCutZ->recomputeFeature(true); + } + else { // just X + // refresh Y and Z limits + values + auto CutBoundingBox = getViewBoundingBox(); + refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::YValue, Refresh::ZValue, + Refresh::notXRange, Refresh::YRange, Refresh::ZRange); + // recompute the cut + Part::Cut* pcCut = static_cast(CutObject); + pcCut->recomputeFeature(true); + } +} + +void SectionCut::onCutXHSsliderMoved(int val) +{ + // we cannot cut to the edge because then the result is an empty shape + // we chose purposely not to simply set the range for cutXHS previously + // because everything is allowed just not the min/max + // we set it one slider step below the min/max + if (val == ui->cutXHS->maximum()) { + ui->cutXHS->setValue(ui->cutXHS->maximum() - ui->cutXHS->singleStep()); + return; + } + if (val == ui->cutXHS->minimum()) { + ui->cutXHS->setValue(ui->cutXHS->minimum() + ui->cutXHS->singleStep()); + return; + } + // the slider value is % of the cut range + double NewCutValue = ui->cutX->minimum() + + val / 100.0 * (ui->cutX->maximum() - ui->cutX->minimum()); + ui->cutXHS->setToolTip(QString::number(NewCutValue, 'g', Base::UnitsApi::getDecimals())); + ui->cutX->setValue(NewCutValue); +} + +void SectionCut::onCutYvalueChanged(double val) +{ + // there might be no document + if (!Gui::Application::Instance->activeDocument()) { + noDocumentActions(); + return; + } + // refresh objects and return in case the document was changed + if (doc != Gui::Application::Instance->activeDocument()->getDocument()) { + onRefreshCutPBclicked(); + return; + } + // update slider position and tooltip + // the slider value is % of the cut range + ui->cutYHS->setValue( + int((val - ui->cutY->minimum()) + / (ui->cutY->maximum() - ui->cutY->minimum()) * 100.0)); + ui->cutYHS->setToolTip(QString::number(val, 'g', Base::UnitsApi::getDecimals())); + + // we cannot cut to the edge because then the result is an empty shape + if (ui->cutY->value() == ui->cutY->maximum()) { + ui->cutY->setValue(ui->cutY->maximum() - 0.1); + return; + } + if (ui->cutY->value() == ui->cutY->minimum()) { + ui->cutY->setValue(ui->cutY->minimum() + 0.1); + return; + } + auto CutBox = doc->getObject(BoxYName); + if (!CutBox) + return; + Part::Box* pcBox = static_cast(CutBox); + Base::Placement placement = pcBox->Placement.getValue(); + Base::Vector3d BoxPosition = placement.getPosition(); + if (!ui->flipY->isChecked()) + BoxPosition.y = ui->cutY->value() - pcBox->Width.getValue(); + else //flipped + BoxPosition.y = ui->cutY->value(); + placement.setPosition(BoxPosition); + pcBox->Placement.setValue(placement); + + auto CutObject = doc->getObject(CutYName); + if (!CutObject) { + Base::Console().Warning((std::string("SectionCut warning: there is no ") + + std::string(CutYName) + std::string(", trying to recreate it\n")).c_str()); + startCutting(); + return; + } + + // if there is another cut, we must recalculate it too + // we might have cut so that the range for Z is now smaller + // we only need to check for Z since the hierarchy is always Z->Y->X + if (hasBoxZ) { + auto CutFeatureZ = doc->getObject(CutZName); + if (!CutFeatureZ) { + Base::Console().Error((std::string("SectionCut error: there is no ") + + std::string(CutZName) + std::string("\n")).c_str()); + return; + } + // refresh the Z cut limits according to the new bounding box of the cut result + // make the SectionCutZ invisible + CutFeatureZ->Visibility.setValue(false); + // make SectionCutX visible + CutObject->Visibility.setValue(true); + // get new bunding box + auto CutBoundingBox = getViewBoundingBox(); + // refresh Z limits + refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::notZValue, + Refresh::notXRange, Refresh::notYRange, Refresh::ZRange); + // the value of Z can now be outside or at the limit, in this case reset the value too + if ((ui->cutZ->value() >= ui->cutZ->maximum()) + || (ui->cutZ->value() <= ui->cutZ->minimum())) + refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::ZValue, + Refresh::notXRange, Refresh::notYRange, Refresh::ZRange); + // make the SectionCutZ visible again + CutFeatureZ->Visibility.setValue(true); + // make SectionCutX invisible again + CutObject->Visibility.setValue(false); + // recompute the cut + Part::Cut* pcCutZ = static_cast(CutFeatureZ); + pcCutZ->recomputeFeature(true); + } + else { // just Y + // refresh Z limits + values + auto CutBoundingBox = getViewBoundingBox(); + refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::ZValue, + Refresh::notXRange, Refresh::notYRange, Refresh::ZRange); + // recompute the cut + Part::Cut* pcCut = static_cast(CutObject); + pcCut->recomputeFeature(true); + // refresh X limits + // this is done by + // first making the cut X box visible, the setting the limits only for X + // if x-limit in box direcion is larger than object, reset value to saved limit + if (hasBoxX) { + auto CutBoxX = doc->getObject(BoxXName); + if (!CutBoxX) + return; + // first store the values + double storedX; + if (!ui->flipX->isChecked()) + storedX = ui->cutX->minimum(); + else + storedX = ui->cutX->maximum(); + // show the cutting box + CutBoxX->Visibility.setValue(true); + // set new XRange + auto CutBoundingBox = getViewBoundingBox(); + refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::notZValue, + Refresh::XRange, Refresh::notYRange, Refresh::notZRange); + // hide cutting box and compare resultwith stored value + CutBoxX->Visibility.setValue(false); + if (!ui->flipX->isChecked()) { + if (storedX > ui->cutX->minimum()) + ui->cutX->setMinimum(storedX); + } + else { + if (storedX < ui->cutX->maximum()) + ui->cutX->setMaximum(storedX); + } + } + } +} + +void SectionCut::onCutYHSsliderMoved(int val) +{ + // we cannot cut to the edge because then the result is an empty shape + if (val == ui->cutYHS->maximum()) { + ui->cutYHS->setValue(ui->cutYHS->maximum() - ui->cutYHS->singleStep()); + return; + } + if (val == ui->cutYHS->minimum()) { + ui->cutYHS->setValue(ui->cutYHS->minimum() + ui->cutYHS->singleStep()); + return; + } + // the slider value is % of the cut range + double NewCutValue = ui->cutY->minimum() + + val / 100.0 * (ui->cutY->maximum() - ui->cutY->minimum()); + ui->cutYHS->setToolTip(QString::number(NewCutValue, 'g', Base::UnitsApi::getDecimals())); + ui->cutY->setValue(NewCutValue); +} + +void SectionCut::onCutZvalueChanged(double val) +{ + // there might be no document + if (!Gui::Application::Instance->activeDocument()) { + noDocumentActions(); + return; + } + // refresh objects and return in case the document was changed + if (doc != Gui::Application::Instance->activeDocument()->getDocument()) { + onRefreshCutPBclicked(); + return; + } + // update slider position and tooltip + // the slider value is % of the cut range + ui->cutZHS->setValue( + int((val - ui->cutZ->minimum()) + / (ui->cutZ->maximum() - ui->cutZ->minimum()) * 100.0)); + ui->cutZHS->setToolTip(QString::number(val, 'g', Base::UnitsApi::getDecimals())); + + // we cannot cut to the edge because then the result is an empty shape + if (ui->cutZ->value() == ui->cutZ->maximum()) { + ui->cutZ->setValue(ui->cutZ->maximum() - 0.1); + return; + } + if (ui->cutZ->value() == ui->cutZ->minimum()) { + ui->cutZ->setValue(ui->cutZ->minimum() + 0.1); + return; + } + auto CutBox = doc->getObject(BoxZName); + if (!CutBox) + return; + Part::Box* pcBox = static_cast(CutBox); + Base::Placement placement = pcBox->Placement.getValue(); + Base::Vector3d BoxPosition = placement.getPosition(); + if (!ui->flipZ->isChecked()) + BoxPosition.z = ui->cutZ->value() - pcBox->Height.getValue(); + else //flipped + BoxPosition.z = ui->cutZ->value(); + placement.setPosition(BoxPosition); + pcBox->Placement.setValue(placement); + + auto CutObject = doc->getObject(CutZName); + if (!CutObject) { + Base::Console().Warning((std::string("SectionCut warning: there is no ") + + std::string(CutZName) + std::string(", trying to recreate it\n")).c_str()); + startCutting(); + return; + } + Part::Cut* pcCut = static_cast(CutObject); + pcCut->recomputeFeature(true); + // refresh X and Y limits + // this is done e.g. for X by + // first making the cut X box visible, the setting the limits only for X + // if x-limit in box direcion is larger than object, reset value to saved limit + SbBox3f CutBoundingBox; + if (hasBoxX) { + auto CutBoxX = doc->getObject(BoxXName); + if (!CutBoxX) + return; + // first store the values + double storedX; + if (!ui->flipX->isChecked()) + storedX = ui->cutX->minimum(); + else + storedX = ui->cutX->maximum(); + // show the cutting box + CutBoxX->Visibility.setValue(true); + // set new XRange + CutBoundingBox = getViewBoundingBox(); + refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::notZValue, + Refresh::XRange, Refresh::notYRange, Refresh::notZRange); + // hide cutting box and compare resultwith stored value + CutBoxX->Visibility.setValue(false); + if (!ui->flipX->isChecked()) { + if (storedX > ui->cutX->minimum()) + ui->cutX->setMinimum(storedX); + } + else { + if (storedX < ui->cutX->maximum()) + ui->cutX->setMaximum(storedX); + } + } + if (hasBoxY) { + auto CutBoxY = doc->getObject(BoxYName); + if (!CutBoxY) + return; + double storedY; + if (!ui->flipY->isChecked()) + storedY = ui->cutY->minimum(); + else + storedY = ui->cutY->maximum(); + CutBoxY->Visibility.setValue(true); + CutBoundingBox = getViewBoundingBox(); + refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::notZValue, + Refresh::notXRange, Refresh::YRange, Refresh::notZRange); + CutBoxY->Visibility.setValue(false); + if (!ui->flipY->isChecked()) { + if (storedY > ui->cutY->minimum()) + ui->cutY->setMinimum(storedY); + } + else { + if (storedY < ui->cutY->maximum()) + ui->cutY->setMaximum(storedY); + } + } +} + +void SectionCut::onCutZHSsliderMoved(int val) +{ + // we cannot cut to the edge because then the result is an empty shape + if (val == ui->cutZHS->maximum()) { + ui->cutZHS->setValue(ui->cutZHS->maximum() - ui->cutZHS->singleStep()); + return; + } + if (val == ui->cutZHS->minimum()) { + ui->cutZHS->setValue(ui->cutZHS->minimum() + ui->cutZHS->singleStep()); + return; + } + // the slider value is % of the cut range + double NewCutValue = ui->cutZ->minimum() + + val / 100.0 * (ui->cutZ->maximum() - ui->cutZ->minimum()); + ui->cutZHS->setToolTip(QString::number(NewCutValue, 'g', Base::UnitsApi::getDecimals())); + ui->cutZ->setValue(NewCutValue); +} + +void SectionCut::onFlipXclicked() +{ + // there might be no document + if (!Gui::Application::Instance->activeDocument()) { + noDocumentActions(); + return; + } + // refresh objects and return in case the document was changed + if (doc != Gui::Application::Instance->activeDocument()->getDocument()) { + onRefreshCutPBclicked(); + return; + } + // we must move the box in x-direction by its Length + // get the cut box + auto CutBox = doc->getObject(BoxXName); + // there should be a box, but maybe the user deleted it meanwhile + if (!CutBox) { + Base::Console().Warning((std::string("SectionCut warning: there is no ") + + std::string(BoxXName) + std::string(", trying to recreate it\n")).c_str()); + // recreate the box + startCutting(); + return; + } + Part::Box* pcBox = static_cast(CutBox); + // get its placement and size + Base::Placement placement = pcBox->Placement.getValue(); + Base::Vector3d BoxPosition = placement.getPosition(); + // flip the box + if (ui->flipX->isChecked()) + BoxPosition.x = BoxPosition.x + pcBox->Length.getValue(); + else + BoxPosition.x = BoxPosition.x - pcBox->Length.getValue(); + placement.setPosition(BoxPosition); + pcBox->Placement.setValue(placement); + + auto CutObject = doc->getObject(CutXName); + // there should be a cut, but maybe the user deleted it meanwhile + if (!CutObject) { + Base::Console().Warning((std::string("SectionCut warning: there is no ") + + std::string(CutXName) + std::string(", trying to recreate it\n")).c_str()); + // recreate the box + startCutting(); + return; + } + + // if there is another cut, we must recalculate it too + // the hierarchy is always Z->Y->X + if (hasBoxY && !hasBoxZ) { // only Y + auto CutFeatureY = doc->getObject(CutYName); + if (!CutFeatureY) { + Base::Console().Warning( + (std::string("SectionCut warning: the expected ") + + std::string(CutYName) + std::string(" is missing, trying to recreate it\n")).c_str()); + // recreate the box + startCutting(); + return; + } + Part::Cut* pcCutY = static_cast(CutFeatureY); + pcCutY->recomputeFeature(true); + } + else if ((!hasBoxY && hasBoxZ) || (hasBoxY && hasBoxZ)) { // at least Z + // the main cut is Z, no matter if there is a cut in Y + auto CutFeatureZ = doc->getObject(CutZName); + if (!CutFeatureZ) { + Base::Console().Warning((std::string("SectionCut warning: the expected ") + + std::string(CutZName) + std::string(" is missing, trying to recreate it\n")).c_str()); + // recreate the box + startCutting(); + return; + } + Part::Cut* pcCutZ = static_cast(CutFeatureZ); + pcCutZ->recomputeFeature(true); + } + else { // only do this when no other box to save recomputes + Part::Cut* pcCut = static_cast(CutObject); + pcCut->recomputeFeature(true); + } +} + +void SectionCut::onFlipYclicked() +{ + // there might be no document + if (!Gui::Application::Instance->activeDocument()) { + noDocumentActions(); + return; + } + // refresh objects and return in case the document was changed + if (doc != Gui::Application::Instance->activeDocument()->getDocument()) { + onRefreshCutPBclicked(); + return; + } + // we must move the box in y-direction by its Width + // get the cut box + auto CutBox = doc->getObject(BoxYName); + // there should be a box, but maybe the user deleted it meanwhile + if (!CutBox) { + Base::Console().Warning((std::string("SectionCut warning: there is no ") + + std::string(BoxYName) + std::string(", trying to recreate it\n")).c_str()); + // recreate the box + startCutting(); + return; + } + Part::Box* pcBox = static_cast(CutBox); + // get its placement and size + Base::Placement placement = pcBox->Placement.getValue(); + Base::Vector3d BoxPosition = placement.getPosition(); + // flip the box + if (ui->flipY->isChecked()) + BoxPosition.y = BoxPosition.y + pcBox->Width.getValue(); + else + BoxPosition.y = BoxPosition.y - pcBox->Width.getValue(); + placement.setPosition(BoxPosition); + pcBox->Placement.setValue(placement); + + auto CutObject = doc->getObject(CutYName); + // there should be a cut, but maybe the user deleted it meanwhile + if (!CutObject) { + Base::Console().Warning((std::string("SectionCut warning: there is no ") + + std::string(CutYName) + std::string(", trying to recreate it\n")).c_str()); + // recreate the box + startCutting(); + return; + } + + // if there is another cut, we must recalculate it too + // we only need to check for Z since the hierarchy is always Z->Y->X + if (hasBoxZ) { + auto CutFeatureZ = doc->getObject(CutZName); + Part::Cut* pcCutZ = static_cast(CutFeatureZ); + pcCutZ->recomputeFeature(true); + } + else { + Part::Cut* pcCut = static_cast(CutObject); + pcCut->recomputeFeature(true); + } +} + +void SectionCut::onFlipZclicked() +{ + // there might be no document + if (!Gui::Application::Instance->activeDocument()) { + noDocumentActions(); + return; + } + // refresh objects and return in case the document was changed + if (doc != Gui::Application::Instance->activeDocument()->getDocument()) { + onRefreshCutPBclicked(); + return; + } + // we must move the box in z-direction by its Height + // get the cut box + auto CutBox = doc->getObject(BoxZName); + // there should be a box, but maybe the user deleted it meanwhile + if (!CutBox) { + Base::Console().Warning((std::string("SectionCut warning: there is no ") + + std::string(BoxZName) + std::string(", trying to recreate it\n")).c_str()); + // recreate the box + startCutting(); + return; + } + Part::Box* pcBox = static_cast(CutBox); + // get its placement and size + Base::Placement placement = pcBox->Placement.getValue(); + Base::Vector3d BoxPosition = placement.getPosition(); + // flip the box + if (ui->flipZ->isChecked()) + BoxPosition.z = BoxPosition.z + pcBox->Height.getValue(); + else + BoxPosition.z = BoxPosition.z - pcBox->Height.getValue(); + placement.setPosition(BoxPosition); + pcBox->Placement.setValue(placement); + + auto CutObject = doc->getObject(CutZName); + // there should be a cut, but maybe the user deleted it meanwhile + if (!CutObject) { + Base::Console().Warning((std::string("SectionCut warning: there is no ") + + std::string(CutZName) + std::string(", trying to recreate it\n")).c_str()); + // recreate the box + startCutting(); + return; + } + Part::Cut* pcCut = static_cast(CutObject); + pcCut->recomputeFeature(true); +} + +// refreshes the list of document objects and the visible objects +void SectionCut::onRefreshCutPBclicked() +{ + // get document + doc = Gui::Application::Instance->activeDocument()->getDocument(); + if (!doc) { + Base::Console().Error("SectionCut error: there is no document\n"); + return; + } + // get all objects in the document + std::vector ObjectsList = doc->getObjects(); + if (ObjectsList.empty()) { + Base::Console().Error("SectionCut error: there are no objects in the document\n"); + return; + } + // empty the ObjectsListVisible + ObjectsListVisible.clear(); + // now store those that are currently visible + for (auto it = ObjectsList.begin(); it != ObjectsList.end(); ++it) { + if ((*it)->Visibility.getValue()) + ObjectsListVisible.push_back((*it)); + } + // reset defaults + hasBoxX = false; + hasBoxY = false; + hasBoxZ = false; + // we can have existing cuts + if (doc->getObject(CutZName)) { + hasBoxZ = true; + blockSignals(true); + ui->groupBoxZ->setChecked(true); + blockSignals(false); + } + if (doc->getObject(CutYName)) { + hasBoxY = true; + blockSignals(true); + ui->groupBoxY->setChecked(true); + blockSignals(false); + } + if (doc->getObject(CutXName)) { + hasBoxX = true; + blockSignals(true); + ui->groupBoxX->setChecked(true); + blockSignals(false); + } + // if there is a cut, disable the button + if (hasBoxX || hasBoxY || hasBoxZ) + ui->RefreshCutPB->setEnabled(false); +} + +SbBox3f SectionCut::getViewBoundingBox() +{ + auto docGui = Gui::Application::Instance->activeDocument(); + Gui::View3DInventor* view = static_cast(docGui->getActiveView()); + Gui::View3DInventorViewer* viewer = view->getViewer(); + SoCamera* camera = viewer->getSoRenderManager()->getCamera(); + SbBox3f Box; + if (!camera || !camera->isOfType(SoOrthographicCamera::getClassTypeId())) + return Box; // return an empty box + // get scene bounding box + SoGetBoundingBoxAction action(viewer->getSoRenderManager()->getViewportRegion()); + action.apply(viewer->getSceneGraph()); + return action.getBoundingBox(); +} + +void SectionCut::refreshCutRanges(SbBox3f BoundingBox, + bool forXValue, bool forYValue, bool forZValue, + bool forXRange, bool forYRange, bool forZRange) +{ + if (!BoundingBox.isEmpty()) { + SbVec3f cnt = BoundingBox.getCenter(); + int minDecimals = Base::UnitsApi::getDecimals(); + float lenx, leny, lenz; + BoundingBox.getSize(lenx, leny, lenz); + int steps = 100; + + // set the ranges + float rangeMin; // to silence a compiler warning we use a float + float rangeMax; + if (forXRange) { + rangeMin = cnt[0] - (lenx / 2); + rangeMax = cnt[0] + (lenx / 2); + ui->cutX->setRange(rangeMin, rangeMax); + // determine the single step values + lenx = lenx / steps; + int dim = static_cast(log10(lenx)); + double singleStep = pow(10.0, dim); + ui->cutX->setSingleStep(singleStep); + } + if (forYRange) { + rangeMin = cnt[1] - (leny / 2); + rangeMax = cnt[1] + (leny / 2); + ui->cutY->setRange(rangeMin, rangeMax); + leny = leny / steps; + int dim = static_cast(log10(leny)); + double singleStep = pow(10.0, dim); + ui->cutY->setSingleStep(singleStep); + } + if (forZRange) { + rangeMin = cnt[2] - (lenz / 2); + rangeMax = cnt[2] + (lenz / 2); + ui->cutZ->setRange(rangeMin, rangeMax); + lenz = lenz / steps; + int dim = static_cast(log10(lenz)); + double singleStep = pow(10.0, dim); + ui->cutZ->setSingleStep(singleStep); + } + if (forXValue) { + ui->cutX->setValue(cnt[0]); + ui->cutXHS->setValue(50); + } + if (forYValue) { + ui->cutY->setValue(cnt[1]); + ui->cutYHS->setValue(50); + } + if (forZValue) { + ui->cutZ->setValue(cnt[2]); + ui->cutZHS->setValue(50); + } + + // set decimals + ui->cutX->setDecimals(minDecimals); + ui->cutY->setDecimals(minDecimals); + ui->cutZ->setDecimals(minDecimals); + } +} + +#include "moc_SectionCutting.cpp" diff --git a/src/Mod/Part/Gui/SectionCutting.h b/src/Mod/Part/Gui/SectionCutting.h new file mode 100644 index 0000000000..5969528516 --- /dev/null +++ b/src/Mod/Part/Gui/SectionCutting.h @@ -0,0 +1,86 @@ +/*************************************************************************** + * Copyright (c) 2022 Uwe Stöhr * + * * + * 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_SECTIONCUTTING_H +#define PARTGUI_SECTIONCUTTING_H + +#include +#include + +namespace Gui { class View3DInventor; } + +namespace PartGui { + +class Ui_SectionCut; + +class SectionCut : public QDialog +{ + Q_OBJECT + +public: + static SectionCut* makeDockWidget(Gui::View3DInventor*); + SectionCut(Gui::View3DInventor* view, QWidget* parent = nullptr); + ~SectionCut(); + +protected Q_SLOTS: + void onGroupBoxXtoggled(); + void onGroupBoxYtoggled(); + void onGroupBoxZtoggled(); + void onCutXvalueChanged(double); + void onCutYvalueChanged(double); + void onCutZvalueChanged(double); + void onCutXHSsliderMoved(int); + void onCutYHSsliderMoved(int); + void onCutZHSsliderMoved(int); + void onFlipXclicked(); + void onFlipYclicked(); + void onFlipZclicked(); + void onRefreshCutPBclicked(); + +public: + void reject(); + +private: + Ui_SectionCut* ui; + std::vector ObjectsListVisible; + App::Document* doc; //pointer to active document + bool hasBoxX = false; + bool hasBoxY = false; + bool hasBoxZ = false; + bool hasBoxCustom = false; + void noDocumentActions(); + void startCutting(bool isInitial = false); + SbBox3f getViewBoundingBox(); + void refreshCutRanges(SbBox3f, bool forXValue = true, bool forYValue = true, bool forZValue = true, + bool forXRange = true, bool forYRange = true, bool forZRange = true); + const char* CompoundName = "SectionCutCompound"; + const char* BoxXName = "SectionCutBoxX"; + const char* BoxYName = "SectionCutBoxY"; + const char* BoxZName = "SectionCutBoxZ"; + const char* CutXName = "SectionCutX"; + const char* CutYName = "SectionCutY"; + const char* CutZName = "SectionCutZ"; +}; + +} // namespace PartGui + +#endif // PARTGUI_SECTIONCUTTING_H diff --git a/src/Mod/Part/Gui/SectionCutting.ui b/src/Mod/Part/Gui/SectionCutting.ui new file mode 100644 index 0000000000..01dc1ca877 --- /dev/null +++ b/src/Mod/Part/Gui/SectionCutting.ui @@ -0,0 +1,331 @@ + + + PartGui::SectionCut + + + + 0 + 0 + 233 + 340 + + + + + 450 + 16777215 + + + + Permanent Section Cutting + + + + + + + 215 + 16777215 + + + + Cutting X + + + true + + + false + + + + + + Offset + + + + + + + false + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + Flip + + + true + + + false + + + + + + + 100 + + + 50 + + + Qt::Horizontal + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + true + + + Refreshes the list of visible objects + + + Refresh view + + + + + + + + 215 + 16777215 + + + + Cutting Y + + + true + + + false + + + + + + Offset + + + + + + + false + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + Flip + + + true + + + + + + + 50 + + + Qt::Horizontal + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + When the dialog is closed, +only created cuts will be visible + + + Keep only cuts visible when closing + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + + 215 + 16777215 + + + + Cutting Z + + + true + + + false + + + + + + Offset + + + + + + + false + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + Flip + + + true + + + + + + + 0 + + + 100 + + + 50 + + + Qt::Horizontal + + + + + + + + + + groupBoxX + cutX + flipX + groupBoxY + cutY + flipY + groupBoxZ + cutZ + flipZ + + + + + buttonBox + rejected() + PartGui::SectionCut + reject() + + + 316 + 260 + + + 286 + 274 + + + + +