From d07441a0dd0a075951602d46b7632fe73e52c3ac Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 1 Nov 2023 15:19:49 +0100 Subject: [PATCH] Part: refactor SectionCut::startCutting() --- src/Mod/Part/Gui/SectionCutting.cpp | 982 ++++++++++++++++++---------- src/Mod/Part/Gui/SectionCutting.h | 42 ++ 2 files changed, 673 insertions(+), 351 deletions(-) diff --git a/src/Mod/Part/Gui/SectionCutting.cpp b/src/Mod/Part/Gui/SectionCutting.cpp index 83d20f2684..9b151b3829 100644 --- a/src/Mod/Part/Gui/SectionCutting.cpp +++ b/src/Mod/Part/Gui/SectionCutting.cpp @@ -260,6 +260,34 @@ void SectionCut::setAutoColoringChecked(bool 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 @@ -416,19 +444,40 @@ void SectionCut::noDocumentActions() ui->groupBoxZ->blockSignals(false); } -void SectionCut::startCutting(bool isInitial) +void SectionCut::setAutoColor(const QColor& color) { - // there might be no document - if (!Gui::Application::Instance->activeDocument()) { - noDocumentActions(); - return; + if (ui->autoCutfaceColorCB->isChecked()) { + ui->CutColor->blockSignals(true); + ui->CutColor->setColor(color); + ui->CutColor->blockSignals(false); } - // the document might have been changed - if (doc != Gui::Application::Instance->activeDocument()->getDocument()) { - // refresh documents list - onRefreshCutPBclicked(); + 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) + + QString::fromLatin1(" %")); + ui->CutTransparencyHS->blockSignals(false); + } + if (ui->autoBFColorCB->isChecked()) { + ui->BFragTransparencyHS->blockSignals(true); + ui->BFragTransparencyHS->setValue(value); + ui->BFragTransparencyHS->setToolTip(QString::number(value) + + QString::fromLatin1(" %")); + ui->BFragTransparencyHS->blockSignals(false); + } +} + +void SectionCut::deleteObejcts() +{ App::DocumentObject* anObject = nullptr; // lambda function to delete objects @@ -454,7 +503,7 @@ void SectionCut::startCutting(bool isInitial) 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 @@ -482,9 +531,13 @@ void SectionCut::startCutting(bool isInitial) if (doc->getObject(BoxXName)) { deleteObject(BoxXName); } - if (doc->getObject(CompoundName)) { - // get the object with the right name - auto compoundObject = doc->getObject(CompoundName); +} + +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; @@ -504,7 +557,7 @@ void SectionCut::startCutting(bool isInitial) else { pcCompoundDelPart->Links.getLinks(compoundChilds); } - + // first delete the compound auto foundObj = std::find_if( ObjectsListVisible.begin(), ObjectsListVisible.end(), @@ -527,7 +580,10 @@ void SectionCut::startCutting(bool isInitial) 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) { @@ -537,18 +593,247 @@ void SectionCut::startCutting(bool isInitial) } else { // we must refresh the ObjectsListVisible list - onRefreshCutPBclicked(); + // 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 = dynamic_cast(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 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 = dynamic_cast(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 - ui->cutXHS->setEnabled(true); - ui->cutYHS->setEnabled(true); - ui->cutZHS->setEnabled(true); + setSlidersEnabled(true); - // ObjectsListVisible contains all visible objects of the document, but we can only cut - // those that have a solid shape - std::vector ObjectsListCut; + 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(); @@ -561,7 +846,7 @@ void SectionCut::startCutting(bool isInitial) auto groupObjects = pcPart->Group.getValue(); for (auto aGroupObject : groupObjects) { if (aGroupObject->getTypeId() == Base::Type::fromName("App::Link")) { - ObjectsListCut.push_back(aGroupObject); + objects.push_back(aGroupObject); // we assume that App::Links inside a App::Part are an assembly isLinkAssembly = true; } @@ -582,7 +867,7 @@ void SectionCut::startCutting(bool isInitial) && !object->getTypeId().isDerivedFrom( Base::Type::fromName("Part::Compound")) && object->getTypeId() != Base::Type::fromName("App::Part")) { - ObjectsListCut.push_back(object); + objects.push_back(object); } } // get Links that are derived from Part objects @@ -590,22 +875,16 @@ void SectionCut::startCutting(bool isInitial) auto linkedObject = doc->getObject(pcLink->LinkedObject.getObjectName()); if (linkedObject != nullptr && linkedObject->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Feature"))) { - ObjectsListCut.push_back(object); + objects.push_back(object); } } } - if (isLinkAssembly) { - // we disable the sliders because for assemblies it will takes ages to do several dozen recomputes - QString SliderToolTip = tr("Sliders are disabled for assemblies"); - ui->cutXHS->setEnabled(false); - ui->cutXHS->setToolTip(SliderToolTip); - ui->cutYHS->setEnabled(false); - ui->cutYHS->setToolTip(SliderToolTip); - ui->cutZHS->setEnabled(false); - ui->cutZHS->setToolTip(SliderToolTip); - } + 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 @@ -628,10 +907,10 @@ void SectionCut::startCutting(bool isInitial) // if there are links, delete them if (!subObjectList.empty()) { for (auto aSubObj : subObjectList) { - for (auto itCutObj = ObjectsListCut.begin(); itCutObj != ObjectsListCut.end(); + for (auto itCutObj = objects.begin(); itCutObj != objects.end(); ++itCutObj) { if (aSubObj == *itCutObj) { - ObjectsListCut.erase(itCutObj); + objects.erase(itCutObj); break; } } @@ -639,64 +918,86 @@ void SectionCut::startCutting(bool isInitial) } } } +} - // we might have no objects that can be cut - if (ObjectsListCut.empty()) { - if (isInitial) { - Base::Console().Error("SectionCut error: there are no visible objects to be cut\n"); - } - else { - Base::Console().Error( - "SectionCut error: there are no objects in the document that can be cut\n"); - } - // block signals to be able to reset the cut group boxes without calling startCutting again - ui->groupBoxX->blockSignals(true); - ui->groupBoxY->blockSignals(true); - ui->groupBoxZ->blockSignals(true); - ui->groupBoxX->setChecked(false); - ui->groupBoxY->setChecked(false); - ui->groupBoxZ->setChecked(false); - ui->RefreshCutPB->setEnabled(true); - ui->groupBoxX->blockSignals(false); - ui->groupBoxY->blockSignals(false); - ui->groupBoxZ->blockSignals(false); - return; +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"); } - // disable intersection option because BooleanFragments requires at least 2 objects - ui->groupBoxIntersecting->setEnabled(ObjectsListCut.size() > 1); + throw Base::RuntimeError("There are no objects in the document that can be cut"); +} - // 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 +bool SectionCut::isCuttingEnabled() const +{ + return ui->groupBoxX->isChecked() || ui->groupBoxY->isChecked() || ui->groupBoxZ->isChecked(); +} - // depending on how many cuts should be performed, we need as many boxes - // if nothing is yet to be cut, we can return - if (!ui->groupBoxX->isChecked() && !ui->groupBoxY->isChecked() - && !ui->groupBoxZ->isChecked()) { - // there is no active cut, so we can enable refresh button - ui->RefreshCutPB->setEnabled(true); - return; - } - - // disable refresh button - ui->RefreshCutPB->setEnabled(false); - - // store color and transparency of first object +namespace { +App::Color getFirstColor(const std::vector& objects) +{ App::Color cutColor; - int cutTransparency {0}; - bool autoColor = true; - bool autoTransparency = true; auto vpFirstObject = dynamic_cast( - Gui::Application::Instance->getViewProvider(*ObjectsListCut.begin())); + Gui::Application::Instance->getViewProvider(objects.front())); if (vpFirstObject) { cutColor = vpFirstObject->ShapeColor.getValue(); - cutTransparency = int(vpFirstObject->Transparency.getValue()); } - // create link objects for all found elements - std::vector ObjectsListLinks; - for (auto itCuts : ObjectsListCut) { + 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 App::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->ShapeColor.getValue()) { + 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", @@ -711,8 +1012,7 @@ void SectionCut::startCutting(bool isInitial) auto pcLink = dynamic_cast(doc->addObject("App::Link", newName.c_str())); if (!pcLink) { - Base::Console().Error("SectionCut error: 'App::Link' could not be added\n"); - return; + throw Base::RuntimeError("'App::Link' could not be added"); } // set the object to the created empty link object @@ -721,7 +1021,7 @@ void SectionCut::startCutting(bool isInitial) pcLink->LinkTransform.setValue(true); // add link to list to later add this to the compound object - ObjectsListLinks.push_back(pcLink); + links.push_back(pcLink); // if the object is part of an App::Part container, // the link needs to get the container placement @@ -740,27 +1040,26 @@ void SectionCut::startCutting(bool isInitial) // hide the objects since only the cut should later be visible itCuts->Visibility.setValue(false); + } + return links; +} - // check if all objects have same color and transparency - if (ui->autoCutfaceColorCB->isChecked() || ui->autoBFColorCB->isChecked()) { - auto vpObject = dynamic_cast( - Gui::Application::Instance->getViewProvider(itCuts)); - if (vpObject) { - if (cutColor != vpObject->ShapeColor.getValue()) { - autoColor = false; - } - if (cutTransparency != vpObject->Transparency.getValue()) { - autoTransparency = false; - } - } +} + +void SectionCut::setObjectsVisible(bool value) +{ + for (auto &aVisObject : ObjectsListVisible) { + App::DocumentObject* object = aVisObject.getObject(); + if (object) { + object->Visibility.setValue(value); } } +} - App::DocumentObject* CutCompoundBF = nullptr; - Part::Compound* CutCompoundPart = nullptr; - - // specify transparency for the compound +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(); } @@ -768,117 +1067,193 @@ void SectionCut::startCutting(bool isInitial) compoundTransparency = ui->CutTransparencyHS->value(); } - // create BooleanFragments and fill it - if (ui->groupBoxIntersecting->isChecked() && ObjectsListCut.size() > 1) { - 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) { - Base::Console().Error((std::string("SectionCut error: ") + std::string(CompoundName) - + std::string(" could not be added\n")).c_str()); - return; - } - CutLinkList->setValue(ObjectsListLinks); - // make all objects in the BooleanFragments object invisible to later only show the cut - for (auto aLinkObj : ObjectsListLinks) { - aLinkObj->Visibility.setValue(false); - } - // set the transparency - auto vpCompound = dynamic_cast( - Gui::Application::Instance->getViewProvider(CutCompoundBF)); - vpCompound->Transparency.setValue(compoundTransparency); - // set the color - // setBooleanFragmentsColor also does a non-recursive recompute - setBooleanFragmentsColor(); - } - else { // create Part::Compound and fill it - // if there is only one object to be cut, we cannot create a BooleanFragments object - CutCompoundPart = dynamic_cast(doc->addObject("Part::Compound", CompoundName)); - if (!CutCompoundPart) { - Base::Console().Error((std::string("SectionCut error: ") + std::string(CompoundName) - + std::string(" could not be added\n")).c_str()); - return; - } + return compoundTransparency; +} - // add the link to the compound - CutCompoundPart->Links.setValue(ObjectsListLinks); - // set the transparency - auto vpCompound = dynamic_cast( - Gui::Application::Instance->getViewProvider(CutCompoundPart)); - vpCompound->Transparency.setValue(compoundTransparency); - CutCompoundPart->recomputeFeature(); +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")); } - // make all objects invisible so that only the compound remains - for (auto &aVisObject : ObjectsListVisible) { - App::DocumentObject* object = aVisObject.getObject(); - if (object) { - object->Visibility.setValue(false); - } + 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 - Base::Console().Error("SectionCut error: the CompoundBoundingBox is empty\n"); - return; + throw Base::RuntimeError("SectionCut error: the CompoundBoundingBox is empty"); } - // store the current cut positions te reset them later if possible - double CutPosX = ui->cutX->value(); - double CutPosY = ui->cutY->value(); - double CutPosZ = ui->cutZ->value(); - // refresh all cut limits according to the new bounding box - refreshCutRanges(CompoundBoundingBox); + 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 - std::vector BoundingBoxSize = { 0.0, 0.0, 0.0 }; + Base::Vector3f BoundingBoxSize; CompoundBoundingBox.getSize(BoundingBoxSize[0], BoundingBoxSize[1], BoundingBoxSize[2]); // get placement of the bounding box origin - std::vector BoundingBoxOrigin = { 0.0, 0.0, 0.0 }; + Base::Vector3f BoundingBoxOrigin; CompoundBoundingBox.getOrigin(BoundingBoxOrigin[0], BoundingBoxOrigin[1], BoundingBoxOrigin[2]); - // now we can create the cut boxes - Base::Vector3d BoxOriginSet; - Base::Placement placement; - SbBox3f CutBoundingBox; + 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::createAllObjects(const std::vector& ObjectsListCut) +{ + // store color and transparency of first object + App::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) { - if (ui->autoCutfaceColorCB->isChecked()) { - ui->CutColor->blockSignals(true); - ui->CutColor->setColor(cutColor.asValue()); - ui->CutColor->blockSignals(false); - } - if (ui->autoBFColorCB->isChecked()) { - ui->BFragColor->blockSignals(true); - ui->BFragColor->setColor(cutColor.asValue()); - ui->BFragColor->blockSignals(false); - } + setAutoColor(cutColor.asValue()); } if (autoTransparency) { - if (ui->autoCutfaceColorCB->isChecked()) { - ui->CutTransparencyHS->blockSignals(true); - ui->CutTransparencyHS->setValue(cutTransparency); - ui->CutTransparencyHS->setToolTip(QString::number(cutTransparency) - + QString::fromLatin1(" %")); - ui->CutTransparencyHS->blockSignals(false); - } - if (ui->autoBFColorCB->isChecked()) { - ui->BFragTransparencyHS->blockSignals(true); - ui->BFragTransparencyHS->setValue(cutTransparency); - ui->BFragTransparencyHS->setToolTip(QString::number(cutTransparency) - + QString::fromLatin1(" %")); - ui->BFragTransparencyHS->blockSignals(false); - } + setAutoTransparency(int(cutTransparency)); } // read cutface color for the cut box @@ -886,9 +1261,8 @@ void SectionCut::startCutting(bool isInitial) boxColor.setValue(ui->CutColor->color()); int boxTransparency = ui->CutTransparencyHS->value(); - // lambda function to set placement, shape color and transparency - auto setPlaceColorTransparency = [&](Part::Box* pcBox) { - pcBox->Placement.setValue(placement); + // lambda function to set shape color and transparency + auto setColorTransparency = [&](Part::Box* pcBox) { auto vpBox = dynamic_cast( Gui::Application::Instance->getViewProvider(pcBox)); if (vpBox) { @@ -908,64 +1282,19 @@ void SectionCut::startCutting(bool isInitial) if (ui->groupBoxX->isChecked()) { // create a box - auto pcBox = dynamic_cast(doc->addObject("Part::Box", BoxXName)); - if (!pcBox) { - Base::Console().Error( (std::string("SectionCut error: ") - + std::string(BoxXName) + std::string(" could not be added\n")).c_str() ); + auto [pcBox, pcCut] = tryCreateXBoxAndCut(BoundingBoxOrigin, BoundingBoxSize); + if (!pcBox || !pcCut) { return; } - // it appears that because of internal rounding errors, the bounding box is sometimes - // a bit too small, for example for epplipsoides, thus make the box a bit larger - pcBox->Length.setValue(BoundingBoxSize[0] + 1.0); - pcBox->Width.setValue(BoundingBoxSize[1] + 1.0); - pcBox->Height.setValue(BoundingBoxSize[2] + 1.0); - // set the previous cut value because refreshCutRanges changed it - // in case the there was previously no cut, nothing will actually be changed - // the previous value might now be outside the current possible range, then reset it - if (CutPosX >= ui->cutX->maximum()) { - CutPosX = ui->cutX->maximum() - 0.1; // short below the maximum - } - else if (CutPosX <= ui->cutX->minimum()) { - CutPosX = ui->cutX->minimum() + 0.1; // short above the minimum - } - // we don't set the value to ui->cutX because this would refresh the cut - // which we don't have yet, thus do this later - // set the box position - if (!ui->flipX->isChecked()) { - BoxOriginSet.x = CutPosX - (BoundingBoxSize[0] + 1.0); - } - else { - // flipped - BoxOriginSet.x = CutPosX; - } - // we made the box 1.0 larger that we can place it 0.5 below the bounding box - BoxOriginSet.y = BoundingBoxOrigin[1] - 0.5; - BoxOriginSet.z = BoundingBoxOrigin[2] - 0.5; - placement.setPosition(BoxOriginSet); - // set box placement, color and transparency - setPlaceColorTransparency(pcBox); + // set box color and transparency + setColorTransparency(pcBox); - // create a cut feature - auto pcCut = dynamic_cast(doc->addObject("Part::Cut", CutXName)); - if (!pcCut) { - Base::Console().Error( (std::string("SectionCut error: ") - + std::string(CutXName) + std::string(" could not be added\n")).c_str() ); - return; - } - - if (ObjectsListCut.size() == 1 || !(ui->groupBoxIntersecting->isChecked())) { - pcCut->Base.setValue(CutCompoundPart); - } - else { - pcCut->Base.setValue(CutCompoundBF); - } + pcCut->Base.setValue(getCutXBase(ObjectsListCut.size(), CutCompoundPart, CutCompoundBF)); pcCut->Tool.setValue(pcBox); // we must set the compoundTransparency also for the cut setTransparency(pcCut); - // set the cut value - ui->cutX->setValue(CutPosX); // recomputing recursively is especially for assemblies very time-consuming // however there must be a final recursicve recompute and we do this at the end // so only recomute recursively if there are no other cuts @@ -981,66 +1310,21 @@ void SectionCut::startCutting(bool isInitial) // if there is a X cut, its size defines the possible range for the Y cut // the cut box size is not affected, it can be as large as the compound if (hasBoxX) { - CutBoundingBox = getViewBoundingBox(); - // refresh the Y cut limits according to the new bounding box - refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, - Refresh::notZValue, Refresh::notXRange, Refresh::YRange, - Refresh::notZRange); + adjustYRange(); } - auto pcBox = dynamic_cast(doc->addObject("Part::Box", BoxYName)); - if (!pcBox) { - Base::Console().Error((std::string("SectionCut error: ") - + std::string(BoxYName) + std::string(" could not be added\n")).c_str() ); + + auto [pcBox, pcCut] = tryCreateYBoxAndCut(BoundingBoxOrigin, BoundingBoxSize); + if (!pcBox || !pcCut) { return; } - pcBox->Length.setValue(BoundingBoxSize[0] + 1.0); - pcBox->Width.setValue(BoundingBoxSize[1] + 1.0); - pcBox->Height.setValue(BoundingBoxSize[2] + 1.0); - // reset previous cut value - if (CutPosY >= ui->cutY->maximum()) { - CutPosY = ui->cutY->maximum() - 0.1; // short below the maximum - } - else if (CutPosY <= ui->cutY->minimum()) { - CutPosY = ui->cutY->minimum() + 0.1; // short above the minimum - } - // set the box position - BoxOriginSet.x = BoundingBoxOrigin[0] - 0.5; - if (!ui->flipY->isChecked()) { - BoxOriginSet.y = CutPosY - (BoundingBoxSize[1] + 1.0); - } - else { - // flipped - BoxOriginSet.y = CutPosY; - } - BoxOriginSet.z = BoundingBoxOrigin[2] - 0.5; - placement.setPosition(BoxOriginSet); - setPlaceColorTransparency(pcBox); - - auto pcCut = dynamic_cast(doc->addObject("Part::Cut", CutYName)); - if (!pcCut) { - Base::Console().Error((std::string("SectionCut error: ") - + std::string(CutYName) + std::string(" could not be added\n")).c_str() ); - return; - } + setColorTransparency(pcBox); // if there is already a cut, we must take it as feature to be cut - if (hasBoxX) { - pcCut->Base.setValue(doc->getObject(CutXName)); - } - else { - if (ObjectsListCut.size() == 1 || !(ui->groupBoxIntersecting->isChecked())) { - pcCut->Base.setValue(CutCompoundPart); - } - else { - pcCut->Base.setValue(CutCompoundBF); - } - } + pcCut->Base.setValue(getCutYBase(ObjectsListCut.size(), CutCompoundPart, CutCompoundBF)); pcCut->Tool.setValue(pcBox); setTransparency(pcCut); - // set the cut value - ui->cutY->setValue(CutPosY); if (!ui->groupBoxZ->isChecked()) { pcCut->recomputeFeature(true); } @@ -1051,68 +1335,20 @@ void SectionCut::startCutting(bool isInitial) } if (ui->groupBoxZ->isChecked()) { if (hasBoxX || hasBoxY) { - CutBoundingBox = getViewBoundingBox(); - refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, - Refresh::notZValue, Refresh::notXRange, Refresh::notYRange, - Refresh::ZRange); + adjustZRange(); } - auto pcBox = dynamic_cast(doc->addObject("Part::Box", BoxZName)); - if (!pcBox) { - Base::Console().Error((std::string("SectionCut error: ") - + std::string(BoxZName) + std::string(" could not be added\n")).c_str() ); + + auto [pcBox, pcCut] = tryCreateZBoxAndCut(BoundingBoxOrigin, BoundingBoxSize); + if (!pcBox || !pcCut) { return; } - pcBox->Length.setValue(BoundingBoxSize[0] + 1.0); - pcBox->Width.setValue(BoundingBoxSize[1] + 1.0); - pcBox->Height.setValue(BoundingBoxSize[2] + 1.0); - // reset previous cut value - if (CutPosZ >= ui->cutZ->maximum()) { - CutPosZ = ui->cutZ->maximum() - 0.1; // short below the maximum - } - else if (CutPosZ <= ui->cutZ->minimum()) { - CutPosZ = ui->cutZ->minimum() + 0.1; // short above the minimum - } - // set the box position - BoxOriginSet.x = BoundingBoxOrigin[0] - 0.5; - BoxOriginSet.y = BoundingBoxOrigin[1] - 0.5; - if (!ui->flipY->isChecked()) { - BoxOriginSet.z = CutPosZ - (BoundingBoxSize[2] + 1.0); - } - else { - // flipped - BoxOriginSet.z = CutPosZ; - } - placement.setPosition(BoxOriginSet); - setPlaceColorTransparency(pcBox); + setColorTransparency(pcBox); - auto pcCut = dynamic_cast(doc->addObject("Part::Cut", CutZName)); - if (!pcCut) { - Base::Console().Error( (std::string("SectionCut error: ") - + std::string(CutZName) + std::string(" could not be added\n")).c_str() ); - return; - } - - // if there is already a cut, we must take it as feature to be cut - if (hasBoxY) { - pcCut->Base.setValue(doc->getObject(CutYName)); - } - else if (hasBoxX && !hasBoxY) { - pcCut->Base.setValue(doc->getObject(CutXName)); - } - else { - if (ObjectsListCut.size() == 1 || !(ui->groupBoxIntersecting->isChecked())) { - pcCut->Base.setValue(CutCompoundPart); - } - else { - pcCut->Base.setValue(CutCompoundBF); - } - } + pcCut->Base.setValue(getCutZBase(ObjectsListCut.size(), CutCompoundPart, CutCompoundBF)); pcCut->Tool.setValue(pcBox); setTransparency(pcCut); - // set the cut value - ui->cutZ->setValue(CutPosZ); pcCut->recomputeFeature(true); hasBoxZ = true; } @@ -1144,12 +1380,7 @@ SectionCut::~SectionCut() if (!ui->keepOnlyCutCB->isChecked()) { // make all objects visible that have been visible when the dialog was called // because we made them invisible when we created cuts - for (auto& aVisObj : ObjectsListVisible) { - if (aVisObj.getObject()) { - // a formerly visible object might have been deleted - aVisObj.getObject()->Visibility.setValue(true); - } - } + setObjectsVisible(true); } } @@ -2155,6 +2386,55 @@ App::DocumentObject* SectionCut::CreateBooleanFragments(App::Document* doc) 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 = dynamic_cast(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::Application::Instance->getViewProvider(CutCompoundPart)); + vpCompound->Transparency.setValue(transparency); + CutCompoundPart->recomputeFeature(); + + return CutCompoundPart; +} // NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) #include "moc_SectionCutting.cpp" diff --git a/src/Mod/Part/Gui/SectionCutting.h b/src/Mod/Part/Gui/SectionCutting.h index 252b5cd5a3..35092479cb 100644 --- a/src/Mod/Part/Gui/SectionCutting.h +++ b/src/Mod/Part/Gui/SectionCutting.h @@ -39,6 +39,8 @@ class ViewProviderGeometryObject; namespace Part { class Box; +class Cut; +class Compound; } namespace PartGui { @@ -92,23 +94,63 @@ private: void setupConnections(); void tryStartCutting(); void setAutoColoringChecked(bool on); + void setSlidersEnabled(bool on); + void setSlidersToolTip(const QString& text); + void setGroupsDisabled(); + void setAutoColor(const QColor& color); + void setAutoTransparency(int value); void initBooleanFragmentControls(Gui::ViewProviderGeometryObject* compoundBF); Base::BoundBox3d collectObjects(); void collectAndShowLinks(const std::vector& objects); void noDocumentActions(); void startCutting(bool isInitial = false); + void startObjectCutting(bool isInitial); + bool findObjects(std::vector& objects); + void filterObjects(std::vector& objects); + void throwMissingObjectsError(bool isInitial); + bool isCuttingEnabled() const; + void setObjectsVisible(bool value); + int getCompoundTransparency() const; static SbBox3f getViewBoundingBox(); void refreshCutRanges(SbBox3f, bool forXValue = true, bool forYValue = true, bool forZValue = true, bool forXRange = true, bool forYRange = true, bool forZRange = true); void CutValueHelper(double val, QDoubleSpinBox* SpinBox, QSlider* Slider); void FlipClickedHelper(const char* BoxName); void changeCutBoxColors(); + void createAllObjects(const std::vector& ObjectsListCut); App::DocumentObject* CreateBooleanFragments(App::Document* doc); + App::DocumentObject* createBooleanFragments(const std::vector& links, int transparency); + Part::Compound* createCompound(const std::vector& links, int transparency); void setBooleanFragmentsColor(); Part::Box* findCutBox(const char* name) const; App::DocumentObject* findObject(const char* objName) const; void hideCutObjects(); App::DocumentObject* flipCutObject(const char* cutName); + void deleteObejcts(); + void deleteCompound(); + void restoreVisibility(); + std::tuple adjustRanges(); + void adjustYRange(); + void adjustZRange(); + void resetHasBoxes(); + App::DocumentObject* getCutXBase(size_t num, App::DocumentObject* comp, App::DocumentObject* frag) const; + App::DocumentObject* getCutYBase(size_t num, App::DocumentObject* comp, App::DocumentObject* frag) const; + App::DocumentObject* getCutZBase(size_t num, App::DocumentObject* comp, App::DocumentObject* frag) const; + Part::Cut* createCut(const char* name); + Part::Cut* tryCreateCut(const char* name); + Part::Box* createBox(const char* name, const Base::Vector3f& size); + std::tuple tryCreateXBoxAndCut(const Base::Vector3f& pos, + const Base::Vector3f& size); + Part::Box* tryCreateXBox(const Base::Vector3f& pos, const Base::Vector3f& size); + Part::Box* createXBox(const Base::Vector3f& pos, const Base::Vector3f& size); + std::tuple tryCreateYBoxAndCut(const Base::Vector3f& pos, + const Base::Vector3f& size); + Part::Box* tryCreateYBox(const Base::Vector3f& pos, const Base::Vector3f& size); + Part::Box* createYBox(const Base::Vector3f& pos, const Base::Vector3f& size); + std::tuple tryCreateZBoxAndCut(const Base::Vector3f& pos, + const Base::Vector3f& size); + Part::Box* tryCreateZBox(const Base::Vector3f& pos, const Base::Vector3f& size); + Part::Box* createZBox(const Base::Vector3f& pos, const Base::Vector3f& size); private: std::unique_ptr ui;