Files
create/src/Mod/Part/Gui/SectionCutting.cpp
2025-08-04 20:14:51 +02:00

2438 lines
87 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_
# include <cmath>
# include <limits>
# 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/Console.h>
#include <Base/Exception.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/DatumFeature.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/Part2DObject.h>
#include <Mod/Part/App/PartFeatures.h>
#include "SectionCutting.h"
#include "ui_SectionCutting.h"
using namespace PartGui;
namespace
{
struct Refresh
{
static const bool notXValue = false;
static const bool notYValue = false;
static const bool notZValue = false;
static const bool notXRange = false;
static const bool notYRange = false;
static const bool notZRange = false;
static const bool XValue = true;
static const bool YValue = true;
static const bool ZValue = true;
static const bool XRange = true;
static const bool YRange = true;
static const bool ZRange = true;
};
}
// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
SectionCut::SectionCut(QWidget* parent)
: QDialog(parent)
, ui(new Ui_SectionCut)
{
// create widgets
ui->setupUi(this);
initSpinBoxes();
// get all objects in the document
auto docGui = Gui::Application::Instance->activeDocument();
if (!docGui) {
throw Base::RuntimeError("Section cut error: there is no document");
}
doc = docGui->getDocument();
if (!doc) {
throw Base::RuntimeError("Section cut error: there is no document");
}
std::vector<App::DocumentObject*> ObjectsList = doc->getObjects();
if (ObjectsList.empty()) {
throw Base::RuntimeError("Section cut error: there are no objects in the document");
}
// now store those that are currently visible
for (auto anObject : ObjectsList) {
if (anObject->Visibility.getValue()) {
ObjectsListVisible.emplace_back(anObject);
}
}
// 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 = collectObjects();
initControls(BoundCompound);
// hide existing cuts to check if there are objects to be cut visible
hideCutObjects();
initCutRanges();
setupConnections();
tryStartCutting();
}
void SectionCut::initSpinBoxes()
{
constexpr int max = std::numeric_limits<int>::max();
ui->cutX->setRange(-max, max);
ui->cutY->setRange(-max, max);
ui->cutZ->setRange(-max, max);
}
void SectionCut::initControls(const Base::BoundBox3d& BoundCompound)
{
// lambda function to set color and transparency
auto setColorTransparency = [&](Part::Box* pcBox) {
Base::Color cutColor;
long cutTransparency{};
auto vpBox = dynamic_cast<Gui::ViewProviderGeometryObject*>(
Gui::Application::Instance->getViewProvider(pcBox));
if (vpBox) {
cutColor = vpBox->ShapeAppearance.getDiffuseColor();
cutTransparency = vpBox->Transparency.getValue();
ui->CutColor->setColor(cutColor.asValue<QColor>());
ui->CutTransparencyHS->setValue(int(cutTransparency));
ui->CutTransparencyHS->setToolTip(QString::number(cutTransparency)
+ QStringLiteral(" %"));
}
};
initZControls(BoundCompound, setColorTransparency);
initYControls(BoundCompound, setColorTransparency);
initXControls(BoundCompound, setColorTransparency);
}
void SectionCut::initXControls(const Base::BoundBox3d& BoundCompound,
const std::function<void(Part::Box*)>& setTransparency)
{
Base::BoundBox3d BoundCutBox;
if (auto pcBox = findCutBox(BoxXName)) {
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);
}
setTransparency(pcBox);
}
}
void SectionCut::initYControls(const Base::BoundBox3d& BoundCompound,
const std::function<void(Part::Box*)>& setTransparency)
{
Base::BoundBox3d BoundCutBox;
if (auto pcBox = findCutBox(BoxYName)) {
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);
}
setTransparency(pcBox);
}
}
void SectionCut::initZControls(const Base::BoundBox3d& BoundCompound,
const std::function<void(Part::Box*)>& setTransparency)
{
Base::BoundBox3d BoundCutBox;
if (auto pcBox = findCutBox(BoxZName)) {
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
setTransparency(pcBox);
}
}
void SectionCut::initCutRanges()
{
// get bounding box
SbBox3f box = getViewBoundingBox();
if (!box.isEmpty()) { // NOLINT
// 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, Refresh::XValue, Refresh::YValue, Refresh::ZValue,
Refresh::XRange, Refresh::YRange, Refresh::ZRange);
}
}
}
void SectionCut::tryStartCutting()
{
// if there is a cut, perform it
if (hasBoxX || hasBoxY || hasBoxZ) {
ui->RefreshCutPB->setEnabled(false);
startCutting(true);
}
}
void SectionCut::setAutoColoringChecked(bool on)
{
ui->autoCutfaceColorCB->setChecked(on);
ui->autoBFColorCB->setChecked(on);
}
void SectionCut::setSlidersEnabled(bool on)
{
ui->cutXHS->setEnabled(on);
ui->cutYHS->setEnabled(on);
ui->cutZHS->setEnabled(on);
}
void SectionCut::setSlidersToolTip(const QString& text)
{
ui->cutXHS->setToolTip(text);
ui->cutYHS->setToolTip(text);
ui->cutZHS->setToolTip(text);
}
void SectionCut::setGroupsDisabled()
{
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);
}
void SectionCut::initBooleanFragmentControls(Gui::ViewProviderGeometryObject* compoundBF)
{
// for BooleanFragments we also need to set the checkbox, transparency and color
ui->groupBoxIntersecting->setChecked(true);
if (compoundBF) {
Base::Color compoundColor = compoundBF->ShapeAppearance.getDiffuseColor();
ui->BFragColor->setColor(compoundColor.asValue<QColor>());
long compoundTransparency = compoundBF->Transparency.getValue();
ui->BFragTransparencyHS->setValue(int(compoundTransparency));
ui->BFragTransparencyHS->setToolTip(QString::number(compoundTransparency)
+ QStringLiteral(" %"));
// Part::Cut ignores the cutbox transparency when it is set
// to zero and the BooleanFragments transparency is not zero
// therefore limit the cutbox transparency to 1 in this case
ui->CutTransparencyHS->setMinimum(compoundTransparency > 0 ? 1 : 0);
}
}
void SectionCut::collectAndShowLinks(const std::vector<App::DocumentObject*>& objects)
{
// make parent objects of links visible to handle the case that
// the cutting is started when only an existing cut was visible
for (auto aCompoundObj : objects) {
auto pcLink = dynamic_cast<App::Link*>(aCompoundObj);
auto LinkedObject = pcLink ? pcLink->getLink() : nullptr;
if (LinkedObject) {
// only if not already visible
if (!(LinkedObject->Visibility.getValue())) {
LinkedObject->Visibility.setValue(true);
ObjectsListVisible.emplace_back(LinkedObject);
}
}
}
}
Base::BoundBox3d SectionCut::collectObjects()
{
Base::BoundBox3d BoundCompound;
if (doc->getObject(BoxXName) || doc->getObject(BoxYName) || doc->getObject(BoxZName)) {
// automatic coloring must be disabled
setAutoColoringChecked(false);
// get the object with the right name
if (auto compoundObject = doc->getObject(CompoundName)) {
// to later store the childs
std::vector<App::DocumentObject*> compoundChilds;
// check if this is a BooleanFragments or a Part::Compound
// Part::Compound is the case when there was only one object
auto pcCompoundPart = dynamic_cast<Part::Compound*>(compoundObject);
auto pcPartFeature = dynamic_cast<Part::Feature*>(compoundObject);
if (!pcCompoundPart && pcPartFeature) {
// for more security check for validity accessing its ViewProvider
auto pcCompoundBF = Gui::Application::Instance->getViewProvider(pcPartFeature);
compoundChilds = pcCompoundBF->claimChildren();
BoundCompound = pcPartFeature->Shape.getBoundingBox();
auto pcCompoundBFGO = dynamic_cast<Gui::ViewProviderGeometryObject*>(pcCompoundBF);
initBooleanFragmentControls(pcCompoundBFGO);
}
else if (pcCompoundPart) {
BoundCompound = pcCompoundPart->Shape.getBoundingBox();
pcCompoundPart->Links.getLinks(compoundChilds);
}
collectAndShowLinks(compoundChilds);
}
}
return BoundCompound;
}
void SectionCut::setupConnections()
{
// clang-format off
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->CutTransparencyHS, &QSlider::sliderMoved,
this, &SectionCut::onTransparencyHSMoved);
connect(ui->CutTransparencyHS, &QSlider::valueChanged,
this, &SectionCut::onTransparencyHSChanged);
connect(ui->groupBoxIntersecting, &QGroupBox::toggled,
this, &SectionCut::onGroupBoxIntersectingToggled);
connect(ui->BFragColor, &QPushButton::clicked,
this, &SectionCut::onBFragColorclicked);
connect(ui->BFragTransparencyHS, &QSlider::sliderMoved,
this, &SectionCut::onBFragTransparencyHSMoved);
connect(ui->BFragTransparencyHS,&QSlider::valueChanged,
this, &SectionCut::onBFragTransparencyHSChanged);
// clang-format on
}
void SectionCut::hideCutObjects()
{
if (auto obj = doc->getObject(CutXName)) {
obj->Visibility.setValue(false);
}
if (auto obj = doc->getObject(CutYName)) {
obj->Visibility.setValue(false);
}
if (auto obj = doc->getObject(CutZName)) {
obj->Visibility.setValue(false);
}
}
// 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::setAutoColor(const QColor& color)
{
if (ui->autoCutfaceColorCB->isChecked()) {
ui->CutColor->blockSignals(true);
ui->CutColor->setColor(color);
ui->CutColor->blockSignals(false);
}
if (ui->autoBFColorCB->isChecked()) {
ui->BFragColor->blockSignals(true);
ui->BFragColor->setColor(color);
ui->BFragColor->blockSignals(false);
}
}
void SectionCut::setAutoTransparency(int value)
{
if (ui->autoCutfaceColorCB->isChecked()) {
ui->CutTransparencyHS->blockSignals(true);
ui->CutTransparencyHS->setValue(value);
ui->CutTransparencyHS->setToolTip(QString::number(value)
+ QStringLiteral(" %"));
ui->CutTransparencyHS->blockSignals(false);
}
if (ui->autoBFColorCB->isChecked()) {
ui->BFragTransparencyHS->blockSignals(true);
ui->BFragTransparencyHS->setValue(value);
ui->BFragTransparencyHS->setToolTip(QString::number(value)
+ QStringLiteral(" %"));
ui->BFragTransparencyHS->blockSignals(false);
}
}
void SectionCut::deleteObejcts()
{
App::DocumentObject* anObject = nullptr;
// 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);
};
int compoundTransparency = -1;
// lambda to store the compoundTransparency
auto storeTransparency = [&](App::DocumentObject* cutObject) {
auto CompoundVP = dynamic_cast<Gui::ViewProviderGeometryObject*>(
Gui::Application::Instance->getViewProvider(cutObject));
if (CompoundVP && compoundTransparency == -1) {
compoundTransparency = int(CompoundVP->Transparency.getValue());
}
};
// 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)) {
// the topmost cut transparency determines the overall transparency
storeTransparency(doc->getObject(CutZName));
deleteObject(CutZName);
}
if (doc->getObject(BoxZName)) {
deleteObject(BoxZName);
}
if (doc->getObject(CutYName)) {
storeTransparency(doc->getObject(CutYName));
deleteObject(CutYName);
}
if (doc->getObject(BoxYName)) {
deleteObject(BoxYName);
}
if (doc->getObject(CutXName)) {
storeTransparency(doc->getObject(CutXName));
deleteObject(CutXName);
}
if (doc->getObject(BoxXName)) {
deleteObject(BoxXName);
}
}
void SectionCut::deleteCompound()
{
// get the object with the right name
if (auto compoundObject = doc->getObject(CompoundName)) {
App::DocumentObject* anObject = compoundObject;
// to later store the childs
std::vector<App::DocumentObject*> compoundChilds;
// check if this is a BooleanFragments or a Part::Compound
auto pcCompoundDelPart = dynamic_cast<Part::Compound*>(compoundObject);
Gui::ViewProvider* pcCompoundDelBF{};
if (!pcCompoundDelPart) {
// check for BooleanFragments
pcCompoundDelBF = Gui::Application::Instance->getViewProvider(compoundObject);
if (!pcCompoundDelBF) {
Base::Console().error(
"Section cut error: compound is incorrectly named, cannot proceed\n");
return;
}
compoundChilds = pcCompoundDelBF->claimChildren();
}
else {
pcCompoundDelPart->Links.getLinks(compoundChilds);
}
// 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 aChild : compoundChilds) {
anObject = doc->getObject(aChild->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(aChild->getNameInDocument());
}
}
}
void SectionCut::restoreVisibility()
{
// make all objects visible that have been visible when the dialog was called
// because we made them invisible when we created cuts
for (auto& aVisObject : ObjectsListVisible) {
if (aVisObject.getObject()) {
// a formerly visible object might have been deleted
aVisObject.getObject()->Visibility.setValue(true);
}
else {
// we must refresh the ObjectsListVisible list
// Disable this function call as modifying the container while iterating it
// causes a crash.
// onRefreshCutPBclicked();
}
}
}
Part::Box* SectionCut::createBox(const char* name, const Base::Vector3f& size) // NOLINT
{
// create a box
auto pcBox = doc->addObject<Part::Box>(name);
if (!pcBox) {
throw Base::RuntimeError(std::string("Section cut error: ")
+ std::string(name) + std::string(" could not be added\n"));
}
// it appears that because of internal rounding errors, the bounding box is sometimes
// a bit too small, for example for ellipsoids, thus make the box a bit larger
pcBox->Length.setValue(size[0] + 1.0);
pcBox->Width.setValue(size[1] + 1.0);
pcBox->Height.setValue(size[2] + 1.0);
return pcBox;
}
Part::Box* SectionCut::tryCreateXBox(const Base::Vector3f& pos, const Base::Vector3f& size) // NOLINT
{
try {
return createXBox(pos, size);
}
catch (const Base::Exception& e) {
e.reportException();
return nullptr;
}
}
std::tuple<Part::Box*, Part::Cut*> SectionCut::tryCreateXBoxAndCut(const Base::Vector3f& pos,
const Base::Vector3f& size)
{
auto pcBox = tryCreateXBox(pos, size);
auto pcCut = tryCreateCut(CutXName);
return {pcBox, pcCut};
}
std::tuple<Part::Box*, Part::Cut*> SectionCut::tryCreateYBoxAndCut(const Base::Vector3f& pos,
const Base::Vector3f& size)
{
auto pcBox = tryCreateYBox(pos, size);
auto pcCut = tryCreateCut(CutYName);
return {pcBox, pcCut};
}
std::tuple<Part::Box*, Part::Cut*> SectionCut::tryCreateZBoxAndCut(const Base::Vector3f& pos,
const Base::Vector3f& size)
{
auto pcBox = tryCreateZBox(pos, size);
auto pcCut = tryCreateCut(CutZName);
return {pcBox, pcCut};
}
Part::Box* SectionCut::createXBox(const Base::Vector3f& pos, const Base::Vector3f& size) // NOLINT
{
// create a box
auto pcBox = createBox(BoxXName, size);
// 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
double CutPosX = ui->cutX->value();
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
}
// set the cut value
ui->cutX->setValue(CutPosX);
// 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
Base::Vector3d BoxOriginSet;
if (!ui->flipX->isChecked()) {
BoxOriginSet.x = CutPosX - (size[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 = pos[1] - 0.5;
BoxOriginSet.z = pos[2] - 0.5;
Base::Placement placement;
placement.setPosition(BoxOriginSet);
// set box placement, color and transparency
pcBox->Placement.setValue(placement);
return pcBox;
}
Part::Box* SectionCut::tryCreateYBox(const Base::Vector3f& pos, const Base::Vector3f& size) // NOLINT
{
try {
return createYBox(pos, size);
}
catch (const Base::Exception& e) {
e.reportException();
return nullptr;
}
}
Part::Box* SectionCut::createYBox(const Base::Vector3f& pos, const Base::Vector3f& size) // NOLINT
{
auto pcBox = createBox(BoxYName, size);
// reset previous cut value
double CutPosY = ui->cutY->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 cut value
ui->cutY->setValue(CutPosY);
// set the box position
Base::Vector3d BoxOriginSet;
BoxOriginSet.x = pos[0] - 0.5;
if (!ui->flipY->isChecked()) {
BoxOriginSet.y = CutPosY - (size[1] + 1.0);
}
else {
// flipped
BoxOriginSet.y = CutPosY;
}
BoxOriginSet.z = pos[2] - 0.5;
Base::Placement placement;
placement.setPosition(BoxOriginSet);
pcBox->Placement.setValue(placement);
return pcBox;
}
Part::Box* SectionCut::tryCreateZBox(const Base::Vector3f& pos, const Base::Vector3f& size) // NOLINT
{
try {
return createZBox(pos, size);
}
catch (const Base::Exception& e) {
e.reportException();
return nullptr;
}
}
Part::Box* SectionCut::createZBox(const Base::Vector3f& pos, const Base::Vector3f& size) // NOLINT
{
auto pcBox = createBox(BoxZName, size);
// reset previous cut value
double CutPosZ = ui->cutZ->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 cut value
ui->cutZ->setValue(CutPosZ);
// set the box position
Base::Vector3d BoxOriginSet;
BoxOriginSet.x = pos[0] - 0.5;
BoxOriginSet.y = pos[1] - 0.5;
if (!ui->flipY->isChecked()) {
BoxOriginSet.z = CutPosZ - (size[2] + 1.0);
}
else {
// flipped
BoxOriginSet.z = CutPosZ;
}
Base::Placement placement;
placement.setPosition(BoxOriginSet);
pcBox->Placement.setValue(placement);
return pcBox;
}
Part::Cut* SectionCut::createCut(const char* name)
{
auto pcCut = doc->addObject<Part::Cut>(name);
if (!pcCut) {
throw Base::RuntimeError(std::string("Section cut error: ")
+ std::string(name) + std::string(" could not be added\n"));
}
return pcCut;
}
Part::Cut* SectionCut::tryCreateCut(const char* name)
{
try {
return createCut(name);
}
catch (const Base::Exception& e) {
e.reportException();
return nullptr;
}
}
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();
}
deleteObejcts();
deleteCompound();
restoreVisibility();
// we enable the sliders because for assemblies we disabled them
setSlidersEnabled(true);
try {
startObjectCutting(isInitial);
}
catch (const Base::Exception& e) {
e.reportException();
}
}
bool SectionCut::findObjects(std::vector<App::DocumentObject*>& objects)
{
bool isLinkAssembly = false;
for (auto& aVisObject : ObjectsListVisible) {
App::DocumentObject* object = aVisObject.getObject();
if (!object) {
continue;
}
// we need all Link objects in App::Part for example for Assembly 4
if (auto pcPart = dynamic_cast<App::Part*>(object)) {
// collect all its link objects
auto groupObjects = pcPart->Group.getValue();
for (auto aGroupObject : groupObjects) {
if (aGroupObject->getTypeId() == Base::Type::fromName("App::Link")) {
objects.push_back(aGroupObject);
// we assume that App::Links inside a App::Part are an assembly
isLinkAssembly = true;
}
}
}
// get all shapes that are also Part::Features
if (object->getPropertyByName("Shape") != nullptr
&& object->isDerivedFrom<Part::Feature>()) {
// sort out 2D objects, datums, App:Parts, compounds and objects that are
// part of a PartDesign body
if (!object->isDerivedFrom<Part::Part2DObject>()
&& !object->isDerivedFrom<Part::Datum>()
&& !object->isDerivedFrom(Base::Type::fromName("PartDesign::Feature"))
&& !object->isDerivedFrom<Part::Compound>()
&& object->getTypeId() != Base::Type::fromName("App::Part")) {
objects.push_back(object);
}
}
// get Links that are derived from Part objects
if (auto pcLink = dynamic_cast<App::Link*>(object)) {
auto linkedObject = doc->getObject(pcLink->LinkedObject.getObjectName());
if (linkedObject != nullptr
&& linkedObject->isDerivedFrom<Part::Feature>()) {
objects.push_back(object);
}
}
}
return isLinkAssembly;
}
void SectionCut::filterObjects(std::vector<App::DocumentObject*>& objects)
{
// sort out objects that are part of Part::Boolean, Part::MultiCommon, Part::MultiFuse,
// Part::Thickness and Part::FilletBase
// check list of visible objects and not cut list because we want to remove from the cut list
for (auto &aVisObject : ObjectsListVisible) {
App::DocumentObject* object = aVisObject.getObject();
if (!object) {
continue;
}
if (object->isDerivedFrom<Part::Boolean>()
|| object->isDerivedFrom<Part::MultiCommon>()
|| object->isDerivedFrom<Part::MultiFuse>()
|| object->isDerivedFrom<Part::Thickness>()
|| object->isDerivedFrom<Part::FilletBase>()) {
// get possible links
auto subObjectList = object->getOutList();
// if there are links, delete them
if (!subObjectList.empty()) {
for (auto aSubObj : subObjectList) {
for (auto itCutObj = objects.begin(); itCutObj != objects.end();
++itCutObj) {
if (aSubObj == *itCutObj) {
objects.erase(itCutObj);
break;
}
}
}
}
}
}
}
void SectionCut::throwMissingObjectsError(bool isInitial)
{
// block signals to be able to reset the cut group boxes without calling startCutting again
setGroupsDisabled();
if (isInitial) {
throw Base::RuntimeError("There are no visible objects to be cut");
}
throw Base::RuntimeError("There are no objects in the document that can be cut");
}
bool SectionCut::isCuttingEnabled() const
{
return ui->groupBoxX->isChecked() || ui->groupBoxY->isChecked() || ui->groupBoxZ->isChecked();
}
namespace {
Base::Color getFirstColor(const std::vector<App::DocumentObject*>& objects)
{
Base::Color cutColor;
auto vpFirstObject = dynamic_cast<Gui::ViewProviderGeometryObject*>(
Gui::Application::Instance->getViewProvider(objects.front()));
if (vpFirstObject) {
cutColor = vpFirstObject->ShapeAppearance.getDiffuseColor();
}
return cutColor;
}
long getFirstTransparency(const std::vector<App::DocumentObject*>& objects)
{
long cutTransparency {0};
auto vpFirstObject = dynamic_cast<Gui::ViewProviderGeometryObject*>(
Gui::Application::Instance->getViewProvider(objects.front()));
if (vpFirstObject) {
cutTransparency = vpFirstObject->Transparency.getValue();
}
return cutTransparency;
}
bool isAutoColor(const Base::Color& color, const std::vector<App::DocumentObject*>& objects)
{
bool autoColor = true;
for (auto itCuts : objects) {
auto vpObject = dynamic_cast<Gui::ViewProviderGeometryObject*>(
Gui::Application::Instance->getViewProvider(itCuts));
if (vpObject) {
if (color != vpObject->ShapeAppearance.getDiffuseColor()) {
autoColor = false;
break;
}
}
}
return autoColor;
}
bool isAutoTransparency(long transparency, const std::vector<App::DocumentObject*>& objects)
{
bool autoTransparency = true;
for (auto itCuts : objects) {
auto vpObject = dynamic_cast<Gui::ViewProviderGeometryObject*>(
Gui::Application::Instance->getViewProvider(itCuts));
if (vpObject) {
if (transparency != vpObject->Transparency.getValue()) {
autoTransparency = false;
break;
}
}
}
return autoTransparency;
}
std::vector<App::DocumentObject*> createLinks(App::Document* doc, const std::vector<App::DocumentObject*>& objects)
{
std::vector<App::DocumentObject*> links;
for (auto itCuts : objects) {
// 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 += "_CutLink";
auto pcLink = doc->addObject<App::Link>(newName.c_str());
if (!pcLink) {
throw Base::RuntimeError("'App::Link' could not be added");
}
// 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);
// add link to list to later add this to the compound object
links.push_back(pcLink);
// if the object is part of an App::Part container,
// the link needs to get the container placement
if (auto parents = itCuts->getInList(); !parents.empty()) {
for (auto parent : parents) {
if (auto pcPartParent = dynamic_cast<App::Part*>(parent)) {
if (auto placement = pcPartParent->getPropertyByName<App::PropertyPlacement>("Placement")) {
pcLink->Placement.setValue(placement->getValue());
}
}
}
}
// hide the objects since only the cut should later be visible
itCuts->Visibility.setValue(false);
}
return links;
}
double getMinOrMax(QPushButton* button, QDoubleSpinBox* spinBox)
{
return button->isChecked() ? spinBox->maximum() : spinBox->minimum();
}
void setMinOrMax(double value, QPushButton* button, QDoubleSpinBox* spinBox)
{
if (button->isChecked()) {
if (value < spinBox->maximum()) {
spinBox->setMaximum(value);
}
}
else {
if (value > spinBox->minimum()) {
spinBox->setMinimum(value);
}
}
}
}
void SectionCut::setObjectsVisible(bool value)
{
for (auto &aVisObject : ObjectsListVisible) {
App::DocumentObject* object = aVisObject.getObject();
if (object) {
object->Visibility.setValue(value);
}
}
}
int SectionCut::getCompoundTransparency() const
{
// if there was no compound, take the setting for the cut face
int compoundTransparency = -1;
if (ui->groupBoxIntersecting->isChecked()) {
compoundTransparency = ui->BFragTransparencyHS->value();
}
if (compoundTransparency == -1) {
compoundTransparency = ui->CutTransparencyHS->value();
}
return compoundTransparency;
}
void SectionCut::startObjectCutting(bool isInitial)
{
// 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 = findObjects(ObjectsListCut);
if (isLinkAssembly) {
// we disable the sliders because for assemblies it will takes ages to do several dozen recomputes
setSlidersEnabled(false);
setSlidersToolTip(tr("Sliders are disabled for assemblies"));
}
filterObjects(ObjectsListCut);
// we might have no objects that can be cut
if (ObjectsListCut.empty()) {
throwMissingObjectsError(isInitial);
}
// disable intersection option because BooleanFragments requires at least 2 objects
ui->groupBoxIntersecting->setEnabled(ObjectsListCut.size() > 1);
// we cut this way:
// 1. put all existing objects into a either a Part::Compound or a BooleanFragments object
// 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 (!isCuttingEnabled()) {
// there is no active cut, so we can enable refresh button
ui->RefreshCutPB->setEnabled(true);
return;
}
// disable refresh button
ui->RefreshCutPB->setEnabled(false);
createAllObjects(ObjectsListCut);
}
std::tuple<Base::Vector3f, Base::Vector3f> SectionCut::adjustRanges()
{
// 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()) { // NOLINT
throw Base::RuntimeError("Section cut error: the CompoundBoundingBox is empty");
}
// refresh all cut limits according to the new bounding box
refreshCutRanges(CompoundBoundingBox, Refresh::notXValue, Refresh::notYValue,
Refresh::notZValue, Refresh::XRange, Refresh::YRange,
Refresh::ZRange);
// prepare the cut box size according to the bounding box size
Base::Vector3f BoundingBoxSize;
CompoundBoundingBox.getSize(BoundingBoxSize[0], BoundingBoxSize[1], BoundingBoxSize[2]);
// get placement of the bounding box origin
Base::Vector3f BoundingBoxOrigin;
CompoundBoundingBox.getOrigin(BoundingBoxOrigin[0], BoundingBoxOrigin[1], BoundingBoxOrigin[2]);
return {BoundingBoxSize, BoundingBoxOrigin};
}
void SectionCut::adjustYRange()
{
auto 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);
}
void SectionCut::adjustZRange()
{
auto CutBoundingBox = getViewBoundingBox();
refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue,
Refresh::notZValue, Refresh::notXRange, Refresh::notYRange,
Refresh::ZRange);
}
void SectionCut::resetHasBoxes()
{
hasBoxX = false;
hasBoxY = false;
hasBoxZ = false;
hasBoxCustom = false;
}
App::DocumentObject* SectionCut::getCutXBase(size_t num,
App::DocumentObject* comp,
App::DocumentObject* frag) const
{
if (num == 1 || !(ui->groupBoxIntersecting->isChecked())) {
return comp;
}
return frag;
}
App::DocumentObject* SectionCut::getCutYBase(size_t num,
App::DocumentObject* comp,
App::DocumentObject* frag) const
{
// if there is already a cut, we must take it as feature to be cut
if (hasBoxX) {
return doc->getObject(CutXName);
}
if (num == 1 || !(ui->groupBoxIntersecting->isChecked())) {
return comp;
}
return frag;
}
App::DocumentObject* SectionCut::getCutZBase(size_t num,
App::DocumentObject* comp,
App::DocumentObject* frag) const
{
if (hasBoxY) {
return doc->getObject(CutYName);
}
if (hasBoxX && !hasBoxY) {
return doc->getObject(CutXName);
}
if (num == 1 || !(ui->groupBoxIntersecting->isChecked())) {
return comp;
}
return frag;
}
void SectionCut::processXBoxAndCut(const Args& args)
{
// create a box
auto [pcBox, pcCut] = tryCreateXBoxAndCut(args.origin, args.size);
if (!pcBox || !pcCut) {
return;
}
// set box color and transparency
args.boxFunc(pcBox);
pcCut->Base.setValue(getCutXBase(args.numObjects, args.partCompound, args.boolFragment));
pcCut->Tool.setValue(pcBox);
// we must set the compoundTransparency also for the cut
args.cutFunc(pcCut);
// 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
pcCut->recomputeFeature(!ui->groupBoxY->isChecked() && !ui->groupBoxZ->isChecked());
hasBoxX = true;
}
void SectionCut::processYBoxAndCut(const Args& args)
{
auto [pcBox, pcCut] = tryCreateYBoxAndCut(args.origin, args.size);
if (!pcBox || !pcCut) {
return;
}
args.boxFunc(pcBox);
// if there is already a cut, we must take it as feature to be cut
pcCut->Base.setValue(getCutYBase(args.numObjects, args.partCompound, args.boolFragment));
pcCut->Tool.setValue(pcBox);
args.cutFunc(pcCut);
pcCut->recomputeFeature(!ui->groupBoxZ->isChecked());
hasBoxY = true;
}
void SectionCut::processZBoxAndCut(const Args& args)
{
auto [pcBox, pcCut] = tryCreateZBoxAndCut(args.origin, args.size);
if (!pcBox || !pcCut) {
return;
}
args.boxFunc(pcBox);
pcCut->Base.setValue(getCutZBase(args.numObjects, args.partCompound, args.boolFragment));
pcCut->Tool.setValue(pcBox);
args.cutFunc(pcCut);
pcCut->recomputeFeature(true);
hasBoxZ = true;
}
void SectionCut::createAllObjects(const std::vector<App::DocumentObject*>& ObjectsListCut)
{
// store color and transparency of first object
Base::Color cutColor = getFirstColor(ObjectsListCut);
long cutTransparency = getFirstTransparency(ObjectsListCut);
bool autoColor = true;
bool autoTransparency = true;
// check if all objects have same color and transparency
if (ui->autoCutfaceColorCB->isChecked() || ui->autoBFColorCB->isChecked()) {
autoColor = isAutoColor(cutColor, ObjectsListCut);
autoTransparency = isAutoTransparency(cutTransparency, ObjectsListCut);
}
// create link objects for all found elements
std::vector<App::DocumentObject*> ObjectsListLinks;
ObjectsListLinks = createLinks(doc, ObjectsListCut);
App::DocumentObject* CutCompoundBF = nullptr;
Part::Compound* CutCompoundPart = nullptr;
// specify transparency for the compound
int compoundTransparency = getCompoundTransparency();
// create BooleanFragments and fill it
if (ui->groupBoxIntersecting->isChecked() && ObjectsListCut.size() > 1) {
CutCompoundBF = createBooleanFragments(ObjectsListLinks, compoundTransparency);
}
else { // create Part::Compound and fill it
// if there is only one object to be cut, we cannot create a BooleanFragments object
CutCompoundPart = createCompound(ObjectsListLinks, compoundTransparency);
}
// make all objects invisible so that only the compound remains
setObjectsVisible(false);
auto [BoundingBoxSize, BoundingBoxOrigin] = adjustRanges();
// now we can create the cut boxes
resetHasBoxes();
// if automatic, we take this color for the cut
if (autoColor) {
setAutoColor(cutColor.asValue<QColor>());
}
if (autoTransparency) {
setAutoTransparency(int(cutTransparency));
}
// read cutface color for the cut box
Base::Color boxColor;
boxColor.setValue<QColor>(ui->CutColor->color());
int boxTransparency = ui->CutTransparencyHS->value();
// lambda function to set shape color and transparency
auto setColorTransparency = [&](Part::Box* pcBox) {
auto vpBox = dynamic_cast<Gui::ViewProviderGeometryObject*>(
Gui::Application::Instance->getViewProvider(pcBox));
if (vpBox) {
vpBox->ShapeAppearance.setDiffuseColor(boxColor);
vpBox->Transparency.setValue(boxTransparency);
}
};
// lambda function to set transparency
auto setTransparency = [&](Part::Cut* pcCut) {
auto vpCut = dynamic_cast<Gui::ViewProviderGeometryObject*>(
Gui::Application::Instance->getViewProvider(pcCut));
if (vpCut) {
vpCut->Transparency.setValue(compoundTransparency);
}
};
if (ui->groupBoxX->isChecked()) {
processXBoxAndCut({BoundingBoxOrigin,
BoundingBoxSize,
ObjectsListCut.size(),
CutCompoundPart,
CutCompoundBF,
setColorTransparency,
setTransparency});
}
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) {
adjustYRange();
}
processYBoxAndCut({BoundingBoxOrigin,
BoundingBoxSize,
ObjectsListCut.size(),
CutCompoundPart,
CutCompoundBF,
setColorTransparency,
setTransparency});
}
if (ui->groupBoxZ->isChecked()) {
if (hasBoxX || hasBoxY) {
adjustZRange();
}
processZBoxAndCut({BoundingBoxOrigin,
BoundingBoxSize,
ObjectsListCut.size(),
CutCompoundPart,
CutCompoundBF,
setColorTransparency,
setTransparency});
}
}
SectionCut* SectionCut::makeDockWidget(QWidget* parent)
{
// embed this dialog into a QDockWidget
auto 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
setObjectsVisible(true);
}
}
void SectionCut::reject()
{
QDialog::reject();
auto 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;
}
}
double SectionCut::getPosX(Part::Box* box) const
{
double value {};
if (!ui->flipX->isChecked()) {
value = ui->cutX->value() - box->Length.getValue();
}
else {
//flipped
value = ui->cutX->value();
}
return value;
}
double SectionCut::getPosY(Part::Box* box) const
{
double value {};
if (!ui->flipY->isChecked()) {
value = ui->cutY->value() - box->Width.getValue();
}
else {
//flipped
value = ui->cutY->value();
}
return value;
}
double SectionCut::getPosZ(Part::Box* box) const
{
double value {};
if (!ui->flipZ->isChecked()) {
value = ui->cutZ->value() - box->Height.getValue();
}
else {
//flipped
value = ui->cutZ->value();
}
return value;
}
void SectionCut::adjustYZRanges(SbBox3f CutBoundingBox)
{
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);
}
}
}
void SectionCut::onCutXvalueChanged(double val)
{
CutValueHelper(val, ui->cutX, ui->cutXHS);
// get the cut box
auto CutBox = findObject(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;
}
auto pcBox = dynamic_cast<Part::Box*>(CutBox);
if (!pcBox) {
Base::Console().error((std::string("Section cut 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();
BoxPosition.x = getPosX(pcBox);
placement.setPosition(BoxPosition);
pcBox->Placement.setValue(placement);
auto CutObject = findOrCreateObject(CutXName);
// there should be a box, but maybe the user deleted it meanwhile
if (!CutObject) {
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 = findOrCreateObject(CutYName);
if (!CutFeatureY) {
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
CutFeatureY->recomputeFeature(true);
}
else if (hasBoxZ) { // at least Z
// the main cut is Z, no matter if there is a cut in Y
auto CutFeatureZ = findOrCreateObject(CutZName);
if (!CutFeatureZ) {
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);
// refresh Y and Z limits
adjustYZRanges(getViewBoundingBox());
// make the SectionCutZ visible again
CutFeatureZ->Visibility.setValue(true);
// make SectionCutX invisible again
CutObject->Visibility.setValue(false);
// recompute the cut
CutFeatureZ->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
auto pcCut = dynamic_cast<Part::Cut*>(CutObject);
if (!pcCut) {
Base::Console().error((std::string("Section cut 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 = findObject(BoxYName);
if (!CutBox) {
return;
}
auto pcBox = dynamic_cast<Part::Box*>(CutBox);
if (!pcBox) {
Base::Console().error((std::string("Section cut 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();
BoxPosition.y = getPosY(pcBox);
placement.setPosition(BoxPosition);
pcBox->Placement.setValue(placement);
auto CutObject = findOrCreateObject(CutYName);
if (!CutObject) {
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 = findObject(CutZName);
if (!CutFeatureZ) {
Base::Console().error((std::string("Section cut 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
CutFeatureZ->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
auto pcCut = dynamic_cast<Part::Cut*>(CutObject);
if (!pcCut) {
Base::Console().error((std::string("Section cut 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, then 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 = findObject(BoxXName);
if (!CutBoxX) {
return;
}
// first store the values
double storedX = getMinOrMax(ui->flipX, ui->cutX);
// 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);
setMinOrMax(storedX, ui->flipX, ui->cutX);
}
}
}
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 = findObject(BoxZName);
if (!CutBox) {
return;
}
auto pcBox = dynamic_cast<Part::Box*>(CutBox);
if (!pcBox) {
Base::Console().error((std::string("Section cut 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();
BoxPosition.z = getPosZ(pcBox);
placement.setPosition(BoxPosition);
pcBox->Placement.setValue(placement);
auto CutObject = findOrCreateObject(CutZName);
if (!CutObject) {
return;
}
auto pcCut = dynamic_cast<Part::Cut*>(CutObject);
if (!pcCut) {
Base::Console().error((std::string("Section cut 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, then 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 = findObject(BoxXName);
if (!CutBoxX) {
return;
}
// first store the values
double storedX = getMinOrMax(ui->flipX, ui->cutX);
// 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);
setMinOrMax(storedX, ui->flipX, ui->cutX);
}
if (hasBoxY) {
auto CutBoxY = findObject(BoxYName);
if (!CutBoxY) {
return;
}
double storedY = getMinOrMax(ui->flipY, ui->cutY);
CutBoxY->Visibility.setValue(true);
CutBoundingBox = getViewBoundingBox();
refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::notZValue,
Refresh::notXRange, Refresh::YRange, Refresh::notZRange);
CutBoxY->Visibility.setValue(false);
setMinOrMax(storedY, ui->flipY, ui->cutY);
}
}
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 = findOrCreateObject(BoxName);
// there should be a box, but maybe the user deleted it meanwhile
if (!CutBox) {
return;
}
auto pcBox = dynamic_cast<Part::Box*>(CutBox);
if (!pcBox) {
Base::Console().error((std::string("Section cut 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);
if (auto CutObject = findOrCreateObject(CutXName)) {
// if there is another cut, we must recalculate it too
// the hierarchy is always Z->Y->X
if (hasBoxY && !hasBoxZ) {
// only Y
CutObject = findOrCreateObject(CutYName);
}
else if ((!hasBoxY && hasBoxZ) || (hasBoxY && hasBoxZ)) {
// at least Z
CutObject = findOrCreateObject(CutZName);
}
if (auto cut = dynamic_cast<Part::Cut*>(CutObject)) {
// only do this when there is no other box to save recomputes
cut->recomputeFeature(true);
}
}
}
void SectionCut::onFlipYclicked()
{
FlipClickedHelper(BoxYName);
if (auto CutObject = findOrCreateObject(CutYName)) {
// 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) {
CutObject = findObject(CutZName);
}
if (auto cut = dynamic_cast<Part::Cut*>(CutObject)) {
cut->recomputeFeature(true);
}
}
}
void SectionCut::onFlipZclicked()
{
FlipClickedHelper(BoxZName);
if (auto CutObject = findOrCreateObject(CutZName)) {
CutObject->recomputeFeature(true);
}
}
Part::Box* SectionCut::findCutBox(const char* name) const
{
if (auto obj = doc->getObject(name)) {
auto pcBox = dynamic_cast<Part::Box*>(obj);
if (!pcBox) {
throw Base::RuntimeError("Section cut error: cut box is incorrectly named, cannot proceed");
}
return pcBox;
}
return nullptr;
}
App::DocumentObject* SectionCut::findObject(const char* objName) const
{
return doc ? doc->getObject(objName) : nullptr;
}
App::DocumentObject* SectionCut::findOrCreateObject(const char* objName)
{
auto object = findObject(objName);
if (!object) {
Base::Console().warning((std::string("Section cut warning: there is no ")
+ std::string(objName) + std::string(", trying to recreate it\n")).c_str());
startCutting();
return nullptr;
}
return object;
}
// changes the cutface color
void SectionCut::onCutColorclicked()
{
if (ui->groupBoxX->isChecked() || ui->groupBoxY->isChecked() || ui->groupBoxZ->isChecked()) {
changeCutBoxColors();
}
}
// changes cutbox colors
void SectionCut::changeCutBoxColors()
{
// a lambda to set box color and transparency
auto setColorTransparency = [&](App::DocumentObject* boxObject) {
auto boxVP = Gui::Application::Instance->getViewProvider(boxObject);
auto boxVPGO = dynamic_cast<Gui::ViewProviderGeometryObject*>(boxVP);
if (boxVPGO) {
Base::Color boxColor;
boxColor.setValue<QColor>(ui->CutColor->color());
boxVPGO->ShapeAppearance.setDiffuseColor(boxColor);
int boxTransparency = ui->CutTransparencyHS->value();
boxVPGO->Transparency.setValue(boxTransparency);
}
};
if (doc->getObject(BoxXName)) {
setColorTransparency(doc->getObject(BoxXName));
}
if (doc->getObject(BoxYName)) {
setColorTransparency(doc->getObject(BoxYName));
}
if (doc->getObject(BoxZName)) {
setColorTransparency(doc->getObject(BoxZName));
}
// we must recompute the topmost cut to make the color visible
// we must hereby first recompute ewvery cut non-recursively in the order X -> Y -> Z
// eventually recompute the topmost cut recursively
if (doc->getObject(CutXName)) {
doc->getObject(CutXName)->recomputeFeature(false);
}
if (doc->getObject(CutYName)) {
doc->getObject(CutYName)->recomputeFeature(false);
}
if (doc->getObject(CutZName)) {
doc->getObject(CutZName)->recomputeFeature(false);
}
if (doc->getObject(CutZName)) {
doc->getObject(CutZName)->recomputeFeature(true);
}
else if (doc->getObject(CutYName)) {
doc->getObject(CutYName)->recomputeFeature(true);
}
else if (doc->getObject(CutXName)) {
doc->getObject(CutXName)->recomputeFeature(true);
}
}
void SectionCut::onTransparencyHSMoved(int val)
{
ui->CutTransparencyHS->setToolTip(QString::number(val) + QStringLiteral(" %"));
// highlight the tooltip
QToolTip::showText(QCursor::pos(), QString::number(val) + QStringLiteral(" %"), nullptr);
if (ui->groupBoxX->isChecked() || ui->groupBoxY->isChecked() || ui->groupBoxZ->isChecked()) {
changeCutBoxColors();
}
}
void SectionCut::onTransparencyHSChanged(int val)
{
onTransparencyHSMoved(val);
}
// change from/to BooleanFragments compound
void SectionCut::onGroupBoxIntersectingToggled()
{
// re-cut
if (ui->groupBoxX->isChecked() || ui->groupBoxY->isChecked() || ui->groupBoxZ->isChecked()) {
startCutting();
}
}
// changes the BooleanFragments color
void SectionCut::onBFragColorclicked()
{
// when there is no cut yet, there is nothing to do
if (!(ui->groupBoxX->isChecked() || ui->groupBoxY->isChecked() || ui->groupBoxZ->isChecked())) {
return;
}
setBooleanFragmentsColor();
// we must recompute the topmost cut to make the color visible
if (doc->getObject(CutZName)) {
doc->getObject(CutZName)->recomputeFeature(true);
}
else if (doc->getObject(CutYName)) {
doc->getObject(CutYName)->recomputeFeature(true);
}
else if (doc->getObject(CutXName)) {
doc->getObject(CutXName)->recomputeFeature(true);
}
}
// sets BooleanFragments color
void SectionCut::setBooleanFragmentsColor()
{
App::DocumentObject* compoundObject{};
if (doc->getObject(CompoundName)) {
// get the object with the right name
compoundObject = doc->getObject(CompoundName);
}
else {
Base::Console().error("Section cut error: compound is incorrectly named, cannot proceed\n");
return;
}
// assure it is not a Part::Compound
auto pcCompound = dynamic_cast<Part::Compound*>(compoundObject);
if (!pcCompound && compoundObject) {
// check for valid BooleanFragments by accessing its ViewProvider
auto CompoundBFVP = Gui::Application::Instance->getViewProvider(compoundObject);
if (!CompoundBFVP) {
Base::Console().error("Section cut error: cannot access ViewProvider of cut compound\n");
return;
}
auto CutCompoundBFGeom = dynamic_cast<Gui::ViewProviderGeometryObject*>(CompoundBFVP);
if (CutCompoundBFGeom) {
Base::Color BFColor;
BFColor.setValue<QColor>(ui->BFragColor->color());
CutCompoundBFGeom->ShapeAppearance.setDiffuseColor(BFColor);
int BFTransparency = ui->BFragTransparencyHS->value();
CutCompoundBFGeom->Transparency.setValue(BFTransparency);
compoundObject->recomputeFeature(false);
}
}
}
void SectionCut::onBFragTransparencyHSMoved(int val)
{
// lambda to set transparency
auto setTransparency = [&](App::DocumentObject* cutObject) {
Gui::ViewProvider* CutVP = Gui::Application::Instance->getViewProvider(cutObject);
if (!CutVP) {
Base::Console().error(
"Section cut error: cannot access ViewProvider of cut object\n");
return;
}
auto CutVPGeom = dynamic_cast<Gui::ViewProviderGeometryObject*>(CutVP);
if (CutVPGeom) {
int BFTransparency = ui->BFragTransparencyHS->value();
CutVPGeom->Transparency.setValue(BFTransparency);
cutObject->recomputeFeature(true);
}
};
// Part::Cut ignores the cutbox transparency when it is set
// to zero and the BooleanFragments transparency is not zero
// therefore limit the cutbox transparency to 1 in this case
if (val > 0) {
ui->CutTransparencyHS->setMinimum(1);
}
else {
ui->CutTransparencyHS->setMinimum(0);
}
ui->BFragTransparencyHS->setToolTip(QString::number(val) + QStringLiteral(" %"));
// highlight the tooltip
QToolTip::showText(QCursor::pos(), QString::number(val) + QStringLiteral(" %"), nullptr);
// when there is no cut yet, there is nothing else to do
if (ui->groupBoxX->isChecked() || ui->groupBoxY->isChecked() || ui->groupBoxZ->isChecked()) {
setBooleanFragmentsColor();
// we must set the transparency to every cut and recompute in the order X -> Y -> Z
if (doc->getObject(CutXName)) {
setTransparency(doc->getObject(CutXName));
}
if (doc->getObject(CutYName)) {
setTransparency(doc->getObject(CutYName));
}
if (doc->getObject(CutZName)) {
setTransparency(doc->getObject(CutZName));
}
}
}
void SectionCut::onBFragTransparencyHSChanged(int val)
{
onBFragTransparencyHSMoved(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("Section cut 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("Section cut error: there are no objects in the document\n");
return;
}
// empty the ObjectsListVisible
ObjectsListVisible.clear();
// now store those that are currently visible
for (auto anObject : ObjectsList) {
if (anObject->Visibility.getValue()) {
ObjectsListVisible.emplace_back(anObject);
}
}
// disable intersection option because BooleanFragments requires at least 2 objects
ui->groupBoxIntersecting->setEnabled(ObjectsListVisible.size() > 1);
// 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("Section cut error: there is no active document\n");
return Box; // return an empty box
}
auto view = dynamic_cast<Gui::View3DInventor*>(docGui->getActiveView());
if (!view) {
Base::Console().error("Section cut 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) {
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()) { // NOLINT
SbVec3f center = BoundingBox.getCenter();
int minDecimals = Base::UnitsApi::getDecimals();
float lenx{};
float leny{};
float lenz{};
BoundingBox.getSize(lenx, leny, lenz);
const int steps = 100;
// set the ranges
float rangeMin{};
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 / float(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 / float(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 / float(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);
}
}
App::DocumentObject* SectionCut::CreateBooleanFragments(App::Document* doc)
{
// NOLINTBEGIN
// create the object
Gui::Command::doCommand(Gui::Command::Doc, "import FreeCAD");
Gui::Command::doCommand(Gui::Command::Doc, "from BOPTools import SplitFeatures");
Gui::Command::doCommand(Gui::Command::Doc,
"SplitFeatures.makeBooleanFragments(name=\"%s\")",
CompoundName);
// check for success
App::DocumentObject* object = doc->getObject(CompoundName);
if (!object) {
Base::Console().error((std::string("Section cut error: ") + std::string(CompoundName)
+ std::string(" could not be added\n")).c_str());
return nullptr;
}
return object;
// NOLINTEND
}
App::DocumentObject* SectionCut::createBooleanFragments(
const std::vector<App::DocumentObject*>& links,
int transparency)
{
App::DocumentObject* CutCompoundBF = CreateBooleanFragments(doc);
// the BooleanFragment implementation requires to first add at least 2 objects
// before any other setting to the BooleanFragment object can be made
auto CutLinkList = dynamic_cast<App::PropertyLinkList*>(
CutCompoundBF ? CutCompoundBF->getPropertyByName("Objects") : nullptr);
if (!CutLinkList) {
throw Base::RuntimeError((std::string("Section cut error: ") + std::string(CompoundName)
+ std::string(" could not be added\n")).c_str());
}
CutLinkList->setValue(links);
// make all objects in the BooleanFragments object invisible to later only show the cut
for (auto aLinkObj : links) {
aLinkObj->Visibility.setValue(false);
}
// set the transparency
auto vpCompound = dynamic_cast<Gui::ViewProviderGeometryObject*>(
Gui::Application::Instance->getViewProvider(CutCompoundBF));
vpCompound->Transparency.setValue(transparency);
// set the color
// setBooleanFragmentsColor also does a non-recursive recompute
setBooleanFragmentsColor();
return CutCompoundBF;
}
Part::Compound* SectionCut::createCompound(const std::vector<App::DocumentObject*>& links,
int transparency)
{
auto CutCompoundPart = doc->addObject<Part::Compound>(CompoundName);
if (!CutCompoundPart) {
throw Base::RuntimeError((std::string("Section cut error: ") + std::string(CompoundName)
+ std::string(" could not be added\n")).c_str());
}
// add the link to the compound
CutCompoundPart->Links.setValue(links);
// set the transparency
auto vpCompound = dynamic_cast<Gui::ViewProviderGeometryObject*>(
Gui::Application::Instance->getViewProvider(CutCompoundPart));
vpCompound->Transparency.setValue(transparency);
CutCompoundPart->recomputeFeature();
return CutCompoundPart;
}
// NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
#include "moc_SectionCutting.cpp"