Files
create/src/Mod/Part/Gui/SectionCutting.cpp
2022-07-29 18:50:35 +02:00

1651 lines
69 KiB
C++

/***************************************************************************
* Copyright (c) 2022 Uwe Stöhr <uwestoehr@lyx.org> *
* *
* 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_
// to avoid compiler warnings of redefining contents of basic.h
// later by #include <Gui/ViewProviderGeometryObject.h>
# define _USE_MATH_DEFINES
# include <cmath>
# include <Inventor/actions/SoGetBoundingBoxAction.h>
# include <Inventor/nodes/SoCamera.h>
# include <Inventor/nodes/SoOrthographicCamera.h>
# include <QDialog>
# include <QDockWidget>
# include <QDoubleSpinBox>
# include <QSlider>
# include <QToolTip>
#endif
#include <App/Document.h>
#include <App/Link.h>
#include <App/Part.h>
#include <Base/UnitsApi.h>
#include <Gui/Application.h>
#include <Gui/Command.h>
#include <Gui/DockWindowManager.h>
#include <Gui/Document.h>
#include <Gui/MainWindow.h>
#include <Gui/PrefWidgets.h>
#include <Gui/View3DInventor.h>
#include <Gui/View3DInventorViewer.h>
#include <Gui/ViewProviderGeometryObject.h>
#include <Mod/Part/App/FeatureCompound.h>
#include <Mod/Part/App/FeaturePartBox.h>
#include <Mod/Part/App/FeaturePartCommon.h>
#include <Mod/Part/App/FeaturePartCut.h>
#include <Mod/Part/App/FeaturePartFuse.h>
#include <Mod/Part/App/PartFeatures.h>
#include "SectionCutting.h"
#include "ui_SectionCutting.h"
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(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);
// get all objects in the document
auto docGui = Gui::Application::Instance->activeDocument();
if (!docGui) {
Base::Console().Error("SectionCut error: there is no document\n");
return;
}
doc = docGui->getDocument();
if (!doc) {
Base::Console().Error("SectionCut error: there is no document\n");
return;
}
std::vector<App::DocumentObject*> ObjectsList = doc->getObjects();
if (ObjectsList.empty()) {
Base::Console().Error("SectionCut error: there are no objects in the document\n");
return;
}
// now store those that are currently visible
for (auto it = ObjectsList.begin(); it != ObjectsList.end(); ++it) {
if ((*it)->Visibility.getValue())
ObjectsListVisible.push_back(*it);
}
// lambda function to set color and transparency
auto setColorTransparency = [&](Part::Box* pcBox) {
App::Color cutColor;
long cutTransparency;
auto vpBox = dynamic_cast<Gui::ViewProviderGeometryObject*>(
Gui::Application::Instance->getViewProvider(pcBox));
if (vpBox) {
cutColor = vpBox->ShapeColor.getValue();
cutTransparency = vpBox->Transparency.getValue();
ui->CutColor->setColor(cutColor.asValue<QColor>());
ui->CutTransparency->setValue(cutTransparency);
ui->CutTransparency->setToolTip(QString::number(cutTransparency) + QString::fromLatin1(" %"));
}
};
// if we can have existing cut boxes, take their values
// to access the flip state we must compare the bounding boxes of the cutbox and the compound
Base::BoundBox3d BoundCompound;
Base::BoundBox3d BoundCutBox;
if (doc->getObject(BoxXName) || doc->getObject(BoxYName) || doc->getObject(BoxZName)) {
// automatic coloring must be disabled
ui->AutoCutfaceColor->setChecked(false);
if (doc->getObject(CompoundName)) {
auto compoundObject = doc->getObject(CompoundName);
Part::Compound* pcCompound = dynamic_cast<Part::Compound*>(compoundObject);
if (!pcCompound) {
Base::Console().Error("SectionCut error: compound is incorrectly named, cannot proceed\n");
return;
}
BoundCompound = pcCompound->Shape.getBoundingBox();
}
}
if (doc->getObject(BoxZName)) {
Part::Box* pcBox = dynamic_cast<Part::Box*>(doc->getObject(BoxZName));
if (!pcBox) {
Base::Console().Error("SectionCut error: cut box is incorrectly named, cannot proceed\n");
return;
}
hasBoxZ = true;
ui->groupBoxZ->setChecked(true);
// if z of cutbox bounding is greater than z of compound bounding
// we know that the cutbox is in flipped state
BoundCutBox = pcBox->Shape.getBoundingBox();
if (BoundCutBox.MinZ > BoundCompound.MinZ){
ui->cutZ->setValue(pcBox->Placement.getValue().getPosition().z);
ui->flipZ->setChecked(true);
}
else {
ui->cutZ->setValue(pcBox->Height.getValue() + pcBox->Placement.getValue().getPosition().z);
ui->flipZ->setChecked(false);
}
// set color and transparency
setColorTransparency(pcBox);
}
if (doc->getObject(BoxYName)) {
Part::Box* pcBox = dynamic_cast<Part::Box*>(doc->getObject(BoxYName));
if (!pcBox) {
Base::Console().Error("SectionCut error: cut box is incorrectly named, cannot proceed\n");
return;
}
hasBoxY = true;
ui->groupBoxY->setChecked(true);
BoundCutBox = pcBox->Shape.getBoundingBox();
if (BoundCutBox.MinY > BoundCompound.MinY) {
ui->cutY->setValue(pcBox->Placement.getValue().getPosition().y);
ui->flipY->setChecked(true);
}
else {
ui->cutY->setValue(pcBox->Width.getValue() + pcBox->Placement.getValue().getPosition().y);
ui->flipY->setChecked(false);
}
setColorTransparency(pcBox);
}
if (doc->getObject(BoxXName)) {
Part::Box* pcBox = dynamic_cast<Part::Box*>(doc->getObject(BoxXName));
if (!pcBox) {
Base::Console().Error("SectionCut error: cut box is incorrectly named, cannot proceed\n");
return;
}
hasBoxX = true;
ui->groupBoxX->setChecked(true);
BoundCutBox = pcBox->Shape.getBoundingBox();
if (BoundCutBox.MinX > BoundCompound.MinX) {
ui->cutX->setValue(pcBox->Placement.getValue().getPosition().x);
ui->flipX->setChecked(true);
}
else {
ui->cutX->setValue(pcBox->Length.getValue() + pcBox->Placement.getValue().getPosition().x);
ui->flipX->setChecked(false);
}
setColorTransparency(pcBox);
}
// 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<double>(&QDoubleSpinBox::valueChanged), this, &SectionCut::onCutXvalueChanged);
connect(ui->cutY, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &SectionCut::onCutYvalueChanged);
connect(ui->cutZ, qOverload<double>(&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->cutXHS, &QSlider::valueChanged, this, &SectionCut::onCutXHSChanged);
connect(ui->cutYHS, &QSlider::valueChanged, this, &SectionCut::onCutYHSChanged);
connect(ui->cutZHS, &QSlider::valueChanged, this, &SectionCut::onCutZHSChanged);
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);
connect(ui->CutColor, &QPushButton::clicked, this, &SectionCut::onCutColorclicked);
connect(ui->CutTransparency, &QSlider::sliderMoved, this, &SectionCut::onTransparencySliderMoved);
connect(ui->CutTransparency, &QSlider::valueChanged, this, &SectionCut::onTransparencyChanged);
// 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()
{
ui->groupBoxX->blockSignals(true);
ui->groupBoxY->blockSignals(true);
ui->groupBoxZ->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);
ui->groupBoxX->blockSignals(false);
ui->groupBoxY->blockSignals(false);
ui->groupBoxZ->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();
App::DocumentObject* anObject = nullptr;
std::vector<App::DocumentObjectT>::iterator it;
// lambda function to delete objects
auto deleteObject = [&](const char* objectName) {
anObject = doc->getObject(objectName);
// the deleted object might have been visible before, thus check and delete it from the list
auto found = std::find_if(ObjectsListVisible.begin(), ObjectsListVisible.end(), [anObject](const App::DocumentObjectT& obj) {
return (obj.getObject() == anObject);
});
if (found != ObjectsListVisible.end())
ObjectsListVisible.erase(found);
doc->removeObject(objectName);
};
// 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
if (doc->getObject(CutZName))
deleteObject(CutZName);
if (doc->getObject(BoxZName))
deleteObject(BoxZName);
if (doc->getObject(CutYName))
deleteObject(CutYName);
if (doc->getObject(BoxYName))
deleteObject(BoxYName);
if (doc->getObject(CutXName))
deleteObject(CutXName);
if (doc->getObject(BoxXName))
deleteObject(BoxXName);
if (doc->getObject(CompoundName)) {
auto compoundObject = doc->getObject(CompoundName);
Part::Compound* pcCompoundDel = dynamic_cast<Part::Compound*>(compoundObject);
if (!pcCompoundDel) {
Base::Console().Error("SectionCut error: compound is incorrectly named, cannot proceed\n");
return;
}
std::vector<App::DocumentObject*> compoundObjects;
pcCompoundDel->Links.getLinks(compoundObjects);
// first delete the compound
auto foundObj = std::find_if(ObjectsListVisible.begin(), ObjectsListVisible.end(), [anObject](const App::DocumentObjectT& obj) {
return (obj.getObject() == anObject);
});
if (foundObj != ObjectsListVisible.end())
ObjectsListVisible.erase(foundObj);
doc->removeObject(CompoundName);
// now delete the objects that have been part of the compound
for (auto itCompound = compoundObjects.begin(); itCompound != compoundObjects.end(); itCompound++) {
anObject = doc->getObject((*itCompound)->getNameInDocument());
auto foundObjInner = std::find_if(ObjectsListVisible.begin(), ObjectsListVisible.end(), [anObject](const App::DocumentObjectT& objInner) {
return (objInner.getObject() == anObject);
});
if (foundObjInner != ObjectsListVisible.end())
ObjectsListVisible.erase((foundObjInner));
doc->removeObject((*itCompound)->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->getObject()) // a formerly visible object might have been deleted
it->getObject()->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<App::DocumentObject*> ObjectsListCut;
bool isLinkAssembly = false;
for (it = ObjectsListVisible.begin(); it != ObjectsListVisible.end(); ++it) {
// we need all Link objects in App::Part for example for Assembly 4
if (it->getObject()->getTypeId() == Base::Type::fromName("App::Part")) {
App::Part* pcPart = static_cast<App::Part*>(it->getObject());
// 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;
}
}
}
// get all shapes that are also Part::Features
if (it->getObject()->getPropertyByName("Shape")
&& it->getObject()->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->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Part2DObject"))
&& !it->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Datum"))
&& !it->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("PartDesign::Feature"))
&& !it->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Compound"))
&& it->getObject()->getTypeId() != Base::Type::fromName("App::Part"))
ObjectsListCut.push_back(it->getObject());
}
// get Links that are derived from Part objects
if (it->getObject()->getTypeId() == Base::Type::fromName("App::Link")) {
App::Link* pcLink = static_cast<App::Link*>(it->getObject());
auto linkedObject = doc->getObject(pcLink->LinkedObject.getObjectName());
if (linkedObject && linkedObject->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Feature")))
ObjectsListCut.push_back(it->getObject());
}
}
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);
}
// sort out objects that are part of Part::Boolean, Part::MultiCommon, Part::MultiFuse,
// Part::Thickness and Part::FilletBase
std::vector<App::DocumentObject*>::iterator it2;
std::vector<App::DocumentObject*>::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->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Boolean"))
|| it->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("Part::MultiCommon"))
|| it->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("Part::MultiFuse"))
|| it->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Thickness"))
|| it->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("Part::FilletBase")) ) {
// get possible links
auto subObjectList = it->getObject()->getOutList();
// if there are 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
ui->groupBoxX->blockSignals(true);
ui->groupBoxY->blockSignals(true);
ui->groupBoxZ->blockSignals(true);
ui->groupBoxX->setChecked(false);
ui->groupBoxY->setChecked(false);
ui->groupBoxZ->setChecked(false);
ui->RefreshCutPB->setEnabled(true);
ui->groupBoxX->blockSignals(false);
ui->groupBoxY->blockSignals(false);
ui->groupBoxZ->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<Part::Compound*>(CutCompound);
// store color and transparency of first object
App::Color cutColor;
int cutTransparency;
bool autoColor = true;
bool autoTransparency = true;
auto vpFirstObject = dynamic_cast<Gui::ViewProviderGeometryObject*>(
Gui::Application::Instance->getViewProvider(*ObjectsListCut.begin()));
if (vpFirstObject) {
cutColor = vpFirstObject->ShapeColor.getValue();
cutTransparency = vpFirstObject->Transparency.getValue();
}
// fill it with all found elements with the copies of the elements
int count = 0;
for (auto itCuts = ObjectsListCut.begin(); itCuts != ObjectsListCut.end(); ++itCuts, count++) {
// first create a link with a unique name
std::string newName;
// since links to normal Part objects all have the document name "Link", use their label text instead
if ((*itCuts)->getTypeId() == Base::Type::fromName("App::Link"))
newName = (*itCuts)->Label.getValue();
else
newName = (*itCuts)->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<App::Link*>(newObject);
// set the object to the created empty link object
pcLink->LinkedObject.setValue((*itCuts));
// we want to get the link at the same position as the original
pcLink->LinkTransform.setValue(true);
// if the object is part of an App::Part container, the link needs to get the container placement
auto parents = (*itCuts)->getInList();
if (!parents.empty()) {
for (auto itParents = parents.begin(); itParents != parents.end(); ++itParents) {
if ((*itParents)->getTypeId() == Base::Type::fromName("App::Part")) {
App::Part* pcPartParent = static_cast<App::Part*>((*itParents));
auto placement = Base::freecad_dynamic_cast<App::PropertyPlacement>(
pcPartParent->getPropertyByName("Placement"));
if (placement)
pcLink->Placement.setValue(placement->getValue());
}
}
}
// add the link to the compound
pcCompound->Links.set1Value(count, newObject);
// hide the objects since only the cut should later be visible
(*itCuts)->Visibility.setValue(false);
// check if all objects have same color and transparency
if (ui->AutoCutfaceColor->isChecked()) {
auto vpObject = dynamic_cast<Gui::ViewProviderGeometryObject*>(
Gui::Application::Instance->getViewProvider(*itCuts));
if (vpObject) {
if (cutColor != vpObject->ShapeColor.getValue())
autoColor = false;
if (cutTransparency != vpObject->Transparency.getValue())
autoTransparency = 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->getObject()->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<float> BoundingBoxSize = { 0.0, 0.0, 0.0 };
CompoundBoundingBox.getSize(BoundingBoxSize[0], BoundingBoxSize[1], BoundingBoxSize[2]);
// get placement of the bounding box origin
std::vector<float> 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 automatic, we take this color for the cut
if (ui->AutoCutfaceColor->isChecked()) {
if (autoColor) {
ui->CutColor->blockSignals(true);
ui->CutColor->setColor(cutColor.asValue<QColor>());
ui->CutColor->blockSignals(false);
}
if (autoTransparency) {
ui->CutTransparency->blockSignals(true);
ui->CutTransparency->setValue(cutTransparency);
ui->CutTransparency->setToolTip(QString::number(cutTransparency) + QString::fromLatin1(" %"));
ui->CutTransparency->blockSignals(false);
}
}
// read cutface color for the cut box
App::Color boxColor;
boxColor.setValue<QColor>(ui->CutColor->color());
int boxTransparency = ui->CutTransparency->value();
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<Part::Box*>(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);
// set box color
pcBox->Placement.setValue(placement);
auto vpBox = dynamic_cast<Gui::ViewProviderGeometryObject*>(
Gui::Application::Instance->getViewProvider(pcBox));
if (vpBox) {
vpBox->ShapeColor.setValue(boxColor);
vpBox->Transparency.setValue(boxTransparency);
}
// 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<Part::Cut*>(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<Part::Box*>(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 vpBox = dynamic_cast<Gui::ViewProviderGeometryObject*>(
Gui::Application::Instance->getViewProvider(pcBox));
if (vpBox) {
vpBox->ShapeColor.setValue(boxColor);
vpBox->Transparency.setValue(boxTransparency);
}
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<Part::Cut*>(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<Part::Box*>(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 vpBox = dynamic_cast<Gui::ViewProviderGeometryObject*>(
Gui::Application::Instance->getViewProvider(pcBox));
if (vpBox) {
vpBox->ShapeColor.setValue(boxColor);
vpBox->Transparency.setValue(boxTransparency);
}
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<Part::Cut*>(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(QWidget* parent)
{
// embed this dialog into a QDockWidget
SectionCut* sectionCut = new SectionCut(parent);
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->getObject()) // a formerly visible object might have been deleted
it->getObject()->Visibility.setValue(true);
}
}
}
void SectionCut::reject()
{
QDialog::reject();
QDockWidget* dw = qobject_cast<QDockWidget*>(parent());
if (dw) {
dw->deleteLater();
}
}
void SectionCut::onGroupBoxXtoggled()
{
// reset the cut
startCutting();
}
void SectionCut::onGroupBoxYtoggled()
{
startCutting();
}
void SectionCut::onGroupBoxZtoggled()
{
startCutting();
}
// helper function for the onFlip_clicked signal
void SectionCut::CutValueHelper(double value, QDoubleSpinBox* SpinBox, QSlider* Slider)
{
// 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
if (Slider->isEnabled()) {
Slider->blockSignals(true);
Slider->setValue(
int((value - SpinBox->minimum())
/ (SpinBox->maximum() - SpinBox->minimum()) * 100.0));
Slider->setToolTip(QString::number(value, 'g', Base::UnitsApi::getDecimals()));
Slider->blockSignals(false);
}
// 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 (SpinBox->value() == SpinBox->maximum()) {
SpinBox->setValue(SpinBox->maximum() - 0.1);
return;
}
if (SpinBox->value() == SpinBox->minimum()) {
SpinBox->setValue(SpinBox->minimum() + 0.1);
return;
}
}
void SectionCut::onCutXvalueChanged(double val)
{
CutValueHelper(val, ui->cutX, ui->cutXHS);
// 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 = dynamic_cast<Part::Box*>(CutBox);
if (!pcBox) {
Base::Console().Error((std::string("SectionCut error: ") + std::string(BoxXName)
+ std::string(" is no Part::Box object. Cannot proceed.\n")).c_str());
return;
}
// 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<Part::Cut*>(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<Part::Cut*>(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 = dynamic_cast<Part::Cut*>(CutObject);
if (!pcCut) {
Base::Console().Error((std::string("SectionCut error: ") + std::string(CutZName)
+ std::string(" is no Part::Cut object. Cannot proceed.\n")).c_str());
return;
}
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::onCutXHSChanged(int val)
{
onCutXHSsliderMoved(val);
}
void SectionCut::onCutYvalueChanged(double val)
{
CutValueHelper(val, ui->cutY, ui->cutYHS);
auto CutBox = doc->getObject(BoxYName);
if (!CutBox)
return;
Part::Box* pcBox = dynamic_cast<Part::Box*>(CutBox);
if (!pcBox) {
Base::Console().Error((std::string("SectionCut error: ") + std::string(BoxYName)
+ std::string(" is no Part::Box object. Cannot proceed.\n")).c_str());
return;
}
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 bounding 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<Part::Cut*>(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 = dynamic_cast<Part::Cut*>(CutObject);
if (!pcCut) {
Base::Console().Error((std::string("SectionCut error: ") + std::string(CutZName)
+ std::string(" is no Part::Cut object. Cannot proceed.\n")).c_str());
return;
}
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 direction 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::onCutYHSChanged(int val)
{
onCutYHSsliderMoved(val);
}
void SectionCut::onCutZvalueChanged(double val)
{
CutValueHelper(val, ui->cutZ, ui->cutZHS);
auto CutBox = doc->getObject(BoxZName);
if (!CutBox)
return;
Part::Box* pcBox = dynamic_cast<Part::Box*>(CutBox);
if (!pcBox) {
Base::Console().Error((std::string("SectionCut error: ") + std::string(BoxZName)
+ std::string(" is no Part::Box object. Cannot proceed.\n")).c_str());
return;
}
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 = dynamic_cast<Part::Cut*>(CutObject);
if (!pcCut) {
Base::Console().Error((std::string("SectionCut error: ") + std::string(CutZName)
+ std::string(" is no Part::Cut object. Cannot proceed.\n")).c_str());
return;
}
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 direction 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::onCutZHSChanged(int val)
{
onCutZHSsliderMoved(val);
}
// helper function for the onFlip_clicked signal
void SectionCut::FlipClickedHelper(const char* BoxName)
{
// 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 e.g. in y-direction by its Width
auto CutBox = doc->getObject(BoxName);
// 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(BoxName) + std::string(", trying to recreate it\n")).c_str());
// recreate the box
startCutting();
return;
}
Part::Box* pcBox = dynamic_cast<Part::Box*>(CutBox);
if (!pcBox) {
Base::Console().Error((std::string("SectionCut error: ") + std::string(BoxName)
+ std::string(" is no Part::Box object. Cannot proceed.\n")).c_str());
return;
}
// get its placement and size
Base::Placement placement = pcBox->Placement.getValue();
Base::Vector3d BoxPosition = placement.getPosition();
// flip the box
switch (std::string(BoxName).back())
{
case 'X':
if (ui->flipX->isChecked())
BoxPosition.x = BoxPosition.x + pcBox->Length.getValue();
else
BoxPosition.x = BoxPosition.x - pcBox->Length.getValue();
break;
case 'Y':
if (ui->flipY->isChecked())
BoxPosition.y = BoxPosition.y + pcBox->Width.getValue();
else
BoxPosition.y = BoxPosition.y - pcBox->Width.getValue();
break;
case 'Z':
if (ui->flipZ->isChecked())
BoxPosition.z = BoxPosition.z + pcBox->Height.getValue();
else
BoxPosition.z = BoxPosition.z - pcBox->Height.getValue();
break;
}
placement.setPosition(BoxPosition);
pcBox->Placement.setValue(placement);
}
void SectionCut::onFlipXclicked()
{
FlipClickedHelper(BoxXName);
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 = dynamic_cast<Part::Cut*>(CutFeatureY);
if (!pcCutY) {
Base::Console().Error((std::string("SectionCut error: ") + std::string(CutYName)
+ std::string(" is no Part::Cut object. Cannot proceed.\n")).c_str());
return;
}
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 = dynamic_cast<Part::Cut*>(CutFeatureZ);
if (!pcCutZ) {
Base::Console().Error((std::string("SectionCut error: ") + std::string(CutZName)
+ std::string(" is no Part::Cut object. Cannot proceed.\n")).c_str());
return;
}
pcCutZ->recomputeFeature(true);
}
else { // only do this when there is no other box to save recomputes
Part::Cut* pcCut = dynamic_cast<Part::Cut*>(CutObject);
if (!pcCut) {
Base::Console().Error((std::string("SectionCut error: ") + std::string(CutXName)
+ std::string(" is no Part::Cut object. Cannot proceed.\n")).c_str());
return;
}
pcCut->recomputeFeature(true);
}
}
void SectionCut::onFlipYclicked()
{
FlipClickedHelper(BoxYName);
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 = dynamic_cast<Part::Cut*>(CutFeatureZ);
if (!pcCutZ) {
Base::Console().Error((std::string("SectionCut error: ") + std::string(CutZName)
+ std::string(" is no Part::Cut object. Cannot proceed.\n")).c_str());
return;
}
pcCutZ->recomputeFeature(true);
}
else {
Part::Cut* pcCut = dynamic_cast<Part::Cut*>(CutObject);
if (!pcCut) {
Base::Console().Error((std::string("SectionCut error: ") + std::string(CutYName)
+ std::string(" is no Part::Cut object. Cannot proceed.\n")).c_str());
return;
}
pcCut->recomputeFeature(true);
}
}
void SectionCut::onFlipZclicked()
{
FlipClickedHelper(BoxZName);
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 = dynamic_cast<Part::Cut*>(CutObject);
if (!pcCut) {
Base::Console().Error((std::string("SectionCut error: ") + std::string(CutZName)
+ std::string(" is no Part::Cut object. Cannot proceed.\n")).c_str());
return;
}
pcCut->recomputeFeature(true);
}
// changes the cutface color
void SectionCut::onCutColorclicked()
{
// re-cut to change the color of all cut boxes
if (ui->groupBoxX->isChecked() || ui->groupBoxY->isChecked() || ui->groupBoxZ->isChecked())
startCutting();
}
void SectionCut::onTransparencySliderMoved(int val)
{
ui->CutTransparency->setToolTip(QString::number(val) + QString::fromLatin1(" %"));
// highlight the tooltip
QToolTip::showText(QCursor::pos(), QString::number(val) + QString::fromLatin1(" %"), nullptr);
// re-cut to change the color of all cut boxes
if (ui->groupBoxX->isChecked() || ui->groupBoxY->isChecked() || ui->groupBoxZ->isChecked())
startCutting();
}
void SectionCut::onTransparencyChanged(int val)
{
onTransparencySliderMoved(val);
}
// refreshes the list of document objects and the visible objects
void SectionCut::onRefreshCutPBclicked()
{
// get document
auto docGui = Gui::Application::Instance->activeDocument();
if (!docGui) {
Base::Console().Error("SectionCut error: there is no document\n");
return;
}
doc = docGui->getDocument();
// get all objects in the document
std::vector<App::DocumentObject*> 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;
ui->groupBoxZ->blockSignals(true);
ui->groupBoxZ->setChecked(true);
ui->groupBoxZ->blockSignals(false);
}
if (doc->getObject(CutYName)) {
hasBoxY = true;
ui->groupBoxY->blockSignals(true);
ui->groupBoxY->setChecked(true);
ui->groupBoxY->blockSignals(false);
}
if (doc->getObject(CutXName)) {
hasBoxX = true;
ui->groupBoxX->blockSignals(true);
ui->groupBoxX->setChecked(true);
ui->groupBoxX->blockSignals(false);
}
// if there is a cut, disable the button
if (hasBoxX || hasBoxY || hasBoxZ)
ui->RefreshCutPB->setEnabled(false);
}
SbBox3f SectionCut::getViewBoundingBox()
{
SbBox3f Box;
auto docGui = Gui::Application::Instance->activeDocument();
if (!docGui) {
Base::Console().Error("SectionCut error: there is no active document\n");
return Box; // return an empty box
}
Gui::View3DInventor* view = dynamic_cast<Gui::View3DInventor*>(docGui->getActiveView());
if (!view) {
Base::Console().Error("SectionCut error: could not get the active view\n");
return Box; // return an empty box
}
Gui::View3DInventorViewer* viewer = view->getViewer();
SoCamera* camera = viewer->getSoRenderManager()->getCamera();
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 center = 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 = center[0] - (lenx / 2);
rangeMax = center[0] + (lenx / 2);
ui->cutX->setRange(rangeMin, rangeMax);
// determine the single step values
lenx = lenx / steps;
int dim = static_cast<int>(log10(lenx));
double singleStep = pow(10.0, dim);
ui->cutX->setSingleStep(singleStep);
}
if (forYRange) {
rangeMin = center[1] - (leny / 2);
rangeMax = center[1] + (leny / 2);
ui->cutY->setRange(rangeMin, rangeMax);
leny = leny / steps;
int dim = static_cast<int>(log10(leny));
double singleStep = pow(10.0, dim);
ui->cutY->setSingleStep(singleStep);
}
if (forZRange) {
rangeMin = center[2] - (lenz / 2);
rangeMax = center[2] + (lenz / 2);
ui->cutZ->setRange(rangeMin, rangeMax);
lenz = lenz / steps;
int dim = static_cast<int>(log10(lenz));
double singleStep = pow(10.0, dim);
ui->cutZ->setSingleStep(singleStep);
}
if (forXValue) {
ui->cutX->setValue(center[0]);
ui->cutXHS->setValue(50);
}
if (forYValue) {
ui->cutY->setValue(center[1]);
ui->cutYHS->setValue(50);
}
if (forZValue) {
ui->cutZ->setValue(center[2]);
ui->cutZHS->setValue(50);
}
// set decimals
ui->cutX->setDecimals(minDecimals);
ui->cutY->setDecimals(minDecimals);
ui->cutZ->setDecimals(minDecimals);
}
}
#include "moc_SectionCutting.cpp"