/*************************************************************************** * Copyright (c) 2022 Uwe Stöhr * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ # include # include # include # include # include # include # include # include # include # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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() { constexpr int max = std::numeric_limits::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::Application::Instance->getViewProvider(pcBox)); if (vpBox) { cutColor = vpBox->ShapeAppearance.getDiffuseColor(); cutTransparency = vpBox->Transparency.getValue(); ui->CutColor->setColor(cutColor.asValue()); 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& 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& 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& 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()); 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& 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(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 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(compoundObject); auto pcPartFeature = dynamic_cast(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(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(&QDoubleSpinBox::valueChanged), this, &SectionCut::onCutXvalueChanged); connect(ui->cutY, qOverload(&QDoubleSpinBox::valueChanged), this, &SectionCut::onCutYvalueChanged); connect(ui->cutZ, qOverload(&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::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 compoundChilds; // check if this is a BooleanFragments or a Part::Compound auto pcCompoundDelPart = dynamic_cast(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(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 SectionCut::tryCreateXBoxAndCut(const Base::Vector3f& pos, const Base::Vector3f& size) { auto pcBox = tryCreateXBox(pos, size); auto pcCut = tryCreateCut(CutXName); return {pcBox, pcCut}; } std::tuple SectionCut::tryCreateYBoxAndCut(const Base::Vector3f& pos, const Base::Vector3f& size) { auto pcBox = tryCreateYBox(pos, size); auto pcCut = tryCreateCut(CutYName); return {pcBox, pcCut}; } std::tuple 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(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& 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(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()) { // sort out 2D objects, datums, App:Parts, compounds and objects that are // part of a PartDesign body if (!object->isDerivedFrom() && !object->isDerivedFrom() && !object->isDerivedFrom(Base::Type::fromName("PartDesign::Feature")) && !object->isDerivedFrom() && object->getTypeId() != Base::Type::fromName("App::Part")) { objects.push_back(object); } } // get Links that are derived from Part objects if (auto pcLink = dynamic_cast(object)) { auto linkedObject = doc->getObject(pcLink->LinkedObject.getObjectName()); if (linkedObject != nullptr && linkedObject->isDerivedFrom()) { objects.push_back(object); } } } return isLinkAssembly; } void SectionCut::filterObjects(std::vector& 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() || object->isDerivedFrom() || object->isDerivedFrom() || object->isDerivedFrom() || object->isDerivedFrom()) { // 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& objects) { Base::Color cutColor; auto vpFirstObject = dynamic_cast( Gui::Application::Instance->getViewProvider(objects.front())); if (vpFirstObject) { cutColor = vpFirstObject->ShapeAppearance.getDiffuseColor(); } return cutColor; } long getFirstTransparency(const std::vector& objects) { long cutTransparency {0}; auto vpFirstObject = dynamic_cast( Gui::Application::Instance->getViewProvider(objects.front())); if (vpFirstObject) { cutTransparency = vpFirstObject->Transparency.getValue(); } return cutTransparency; } bool isAutoColor(const Base::Color& color, const std::vector& objects) { bool autoColor = true; for (auto itCuts : objects) { auto vpObject = dynamic_cast( Gui::Application::Instance->getViewProvider(itCuts)); if (vpObject) { if (color != vpObject->ShapeAppearance.getDiffuseColor()) { autoColor = false; break; } } } return autoColor; } bool isAutoTransparency(long transparency, const std::vector& objects) { bool autoTransparency = true; for (auto itCuts : objects) { auto vpObject = dynamic_cast( Gui::Application::Instance->getViewProvider(itCuts)); if (vpObject) { if (transparency != vpObject->Transparency.getValue()) { autoTransparency = false; break; } } } return autoTransparency; } std::vector createLinks(App::Document* doc, const std::vector& objects) { std::vector 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(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(parent)) { if (auto placement = pcPartParent->getPropertyByName("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 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 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& 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 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()); } if (autoTransparency) { setAutoTransparency(int(cutTransparency)); } // read cutface color for the cut box Base::Color boxColor; boxColor.setValue(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::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::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(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(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(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(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(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(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(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(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(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(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(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(boxVP); if (boxVPGO) { Base::Color boxColor; boxColor.setValue(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(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(CompoundBFVP); if (CutCompoundBFGeom) { Base::Color BFColor; BFColor.setValue(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(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 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(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(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(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(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& 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( 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::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& links, int transparency) { auto CutCompoundPart = doc->addObject(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::Application::Instance->getViewProvider(CutCompoundPart)); vpCompound->Transparency.setValue(transparency); CutCompoundPart->recomputeFeature(); return CutCompoundPart; } // NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) #include "moc_SectionCutting.cpp"