Every basic data type is stored in Base module, color is standing out as one that does not. Moving it to Base opens possibilities to integrate it better with the rest of FreeCAD.
2440 lines
87 KiB
C++
2440 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_
|
|
|
|
// to avoid compiler warnings of redefining contents of basic.h
|
|
// later by #include <Gui/ViewProviderGeometryObject.h>
|
|
# define _USE_MATH_DEFINES // NOLINT
|
|
# 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/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("SectionCut error: there is no document");
|
|
}
|
|
doc = docGui->getDocument();
|
|
if (!doc) {
|
|
throw Base::RuntimeError("SectionCut error: there is no document");
|
|
}
|
|
|
|
std::vector<App::DocumentObject*> ObjectsList = doc->getObjects();
|
|
if (ObjectsList.empty()) {
|
|
throw Base::RuntimeError("SectionCut 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()
|
|
{
|
|
ui->cutX->setRange(-INT_MAX, INT_MAX);
|
|
ui->cutY->setRange(-INT_MAX, INT_MAX);
|
|
ui->cutZ->setRange(-INT_MAX, INT_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(
|
|
"SectionCut 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("SectionCut 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("SectionCut 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("SectionCut 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("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();
|
|
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("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 = findObject(BoxYName);
|
|
if (!CutBox) {
|
|
return;
|
|
}
|
|
auto 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();
|
|
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("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
|
|
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("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, 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("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();
|
|
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("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, 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("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);
|
|
|
|
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("SectionCut 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("SectionCut 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("SectionCut 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("SectionCut 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(
|
|
"SectionCut 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("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 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("SectionCut 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("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) {
|
|
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("SectionCut 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("SectionCut 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("SectionCut 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"
|