/*************************************************************************** * 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_ // to avoid compiler warnings of redefining contents of basic.h // later by #include # define _USE_MATH_DEFINES # 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 "SectionCutting.h" #include "ui_SectionCutting.h" using namespace PartGui; enum Refresh : bool { notXValue = false, notYValue = false, notZValue = false, notXRange = false, notYRange = false, notZRange = false, XValue = true, YValue = true, ZValue = true, XRange = true, YRange = true, ZRange = true }; SectionCut::SectionCut(QWidget* parent) : QDialog(parent) , ui(new Ui_SectionCut) { // create widgets ui->setupUi(this); ui->cutX->setRange(-INT_MAX, INT_MAX); ui->cutY->setRange(-INT_MAX, INT_MAX); ui->cutZ->setRange(-INT_MAX, INT_MAX); // get all objects in the document auto docGui = Gui::Application::Instance->activeDocument(); if (!docGui) { Base::Console().Error("SectionCut error: there is no document\n"); return; } doc = docGui->getDocument(); if (!doc) { Base::Console().Error("SectionCut error: there is no document\n"); return; } std::vector ObjectsList = doc->getObjects(); if (ObjectsList.empty()) { Base::Console().Error("SectionCut error: there are no objects in the document\n"); return; } // now store those that are currently visible for (auto it = ObjectsList.begin(); it != ObjectsList.end(); ++it) { if ((*it)->Visibility.getValue()) ObjectsListVisible.push_back(*it); } // lambda function to set color and transparency auto setColorTransparency = [&](Part::Box* pcBox) { App::Color cutColor; long cutTransparency; auto vpBox = dynamic_cast( Gui::Application::Instance->getViewProvider(pcBox)); if (vpBox) { cutColor = vpBox->ShapeColor.getValue(); cutTransparency = vpBox->Transparency.getValue(); ui->CutColor->setColor(cutColor.asValue()); ui->CutTransparency->setValue(cutTransparency); ui->CutTransparency->setToolTip(QString::number(cutTransparency) + QString::fromLatin1(" %")); } }; // if we can have existing cut boxes, take their values // to access the flip state we must compare the bounding boxes of the cutbox and the compound Base::BoundBox3d BoundCompound; Base::BoundBox3d BoundCutBox; if (doc->getObject(BoxXName) || doc->getObject(BoxYName) || doc->getObject(BoxZName)) { // automatic coloring must be disabled ui->AutoCutfaceColor->setChecked(false); if (doc->getObject(CompoundName)) { auto compoundObject = doc->getObject(CompoundName); Part::Compound* pcCompound = dynamic_cast(compoundObject); if (!pcCompound) { Base::Console().Error("SectionCut error: compound is incorrectly named, cannot proceed\n"); return; } BoundCompound = pcCompound->Shape.getBoundingBox(); } } if (doc->getObject(BoxZName)) { Part::Box* pcBox = dynamic_cast(doc->getObject(BoxZName)); if (!pcBox) { Base::Console().Error("SectionCut error: cut box is incorrectly named, cannot proceed\n"); return; } hasBoxZ = true; ui->groupBoxZ->setChecked(true); // if z of cutbox bounding is greater than z of compound bounding // we know that the cutbox is in flipped state BoundCutBox = pcBox->Shape.getBoundingBox(); if (BoundCutBox.MinZ > BoundCompound.MinZ){ ui->cutZ->setValue(pcBox->Placement.getValue().getPosition().z); ui->flipZ->setChecked(true); } else { ui->cutZ->setValue(pcBox->Height.getValue() + pcBox->Placement.getValue().getPosition().z); ui->flipZ->setChecked(false); } // set color and transparency setColorTransparency(pcBox); } if (doc->getObject(BoxYName)) { Part::Box* pcBox = dynamic_cast(doc->getObject(BoxYName)); if (!pcBox) { Base::Console().Error("SectionCut error: cut box is incorrectly named, cannot proceed\n"); return; } hasBoxY = true; ui->groupBoxY->setChecked(true); BoundCutBox = pcBox->Shape.getBoundingBox(); if (BoundCutBox.MinY > BoundCompound.MinY) { ui->cutY->setValue(pcBox->Placement.getValue().getPosition().y); ui->flipY->setChecked(true); } else { ui->cutY->setValue(pcBox->Width.getValue() + pcBox->Placement.getValue().getPosition().y); ui->flipY->setChecked(false); } setColorTransparency(pcBox); } if (doc->getObject(BoxXName)) { Part::Box* pcBox = dynamic_cast(doc->getObject(BoxXName)); if (!pcBox) { Base::Console().Error("SectionCut error: cut box is incorrectly named, cannot proceed\n"); return; } hasBoxX = true; ui->groupBoxX->setChecked(true); BoundCutBox = pcBox->Shape.getBoundingBox(); if (BoundCutBox.MinX > BoundCompound.MinX) { ui->cutX->setValue(pcBox->Placement.getValue().getPosition().x); ui->flipX->setChecked(true); } else { ui->cutX->setValue(pcBox->Length.getValue() + pcBox->Placement.getValue().getPosition().x); ui->flipX->setChecked(false); } setColorTransparency(pcBox); } // hide existing cuts to check if there are objects to be cut visible if (doc->getObject(CutXName)) doc->getObject(CutXName)->Visibility.setValue(false); if (doc->getObject(CutYName)) doc->getObject(CutYName)->Visibility.setValue(false); if (doc->getObject(CutZName)) doc->getObject(CutZName)->Visibility.setValue(false); // get bounding box SbBox3f box = getViewBoundingBox(); if (!box.isEmpty()) { // if there is a cut box, perform the cut if (hasBoxX || hasBoxY || hasBoxZ) { // refresh only the range since we set the values above already refreshCutRanges(box, Refresh::notXValue, Refresh::notYValue, Refresh::notZValue, Refresh::XRange, Refresh::YRange, Refresh::ZRange); } else refreshCutRanges(box); } // the case of an empty box and having cuts will be handles later by startCutting(true) connect(ui->groupBoxX, &QGroupBox::toggled, this, &SectionCut::onGroupBoxXtoggled); connect(ui->groupBoxY, &QGroupBox::toggled, this, &SectionCut::onGroupBoxYtoggled); connect(ui->groupBoxZ, &QGroupBox::toggled, this, &SectionCut::onGroupBoxZtoggled); connect(ui->cutX, qOverload(&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->CutTransparency, &QSlider::sliderMoved, this, &SectionCut::onTransparencySliderMoved); connect(ui->CutTransparency, &QSlider::valueChanged, this, &SectionCut::onTransparencyChanged); // if there is a cut, perform it if (hasBoxX || hasBoxY || hasBoxZ) { ui->RefreshCutPB->setEnabled(false); startCutting(true); } } // actions to be done when document was closed void SectionCut::noDocumentActions() { ui->groupBoxX->blockSignals(true); ui->groupBoxY->blockSignals(true); ui->groupBoxZ->blockSignals(true); doc = nullptr; // reset the cut group boxes ui->groupBoxX->setChecked(false); ui->groupBoxY->setChecked(false); ui->groupBoxZ->setChecked(false); ui->RefreshCutPB->setEnabled(true); ui->groupBoxX->blockSignals(false); ui->groupBoxY->blockSignals(false); ui->groupBoxZ->blockSignals(false); } void SectionCut::startCutting(bool isInitial) { // there might be no document if (!Gui::Application::Instance->activeDocument()) { noDocumentActions(); return; } // the document might have been changed if (doc != Gui::Application::Instance->activeDocument()->getDocument()) // refresh documents list onRefreshCutPBclicked(); App::DocumentObject* anObject = nullptr; std::vector::iterator it; // lambda function to delete objects auto deleteObject = [&](const char* objectName) { anObject = doc->getObject(objectName); // the deleted object might have been visible before, thus check and delete it from the list auto found = std::find_if(ObjectsListVisible.begin(), ObjectsListVisible.end(), [anObject](const App::DocumentObjectT& obj) { return (obj.getObject() == anObject); }); if (found != ObjectsListVisible.end()) ObjectsListVisible.erase(found); doc->removeObject(objectName); }; // delete the objects we might have already created to cut // we must do this because we support several cuts at once and // it is dangerous to deal with the fact that the user is free // to uncheck cutting planes and to add/remove objects while this dialog is open // We must remove in this order because the tree hierary of the features is Z->Y->X and Cut->Box if (doc->getObject(CutZName)) deleteObject(CutZName); if (doc->getObject(BoxZName)) deleteObject(BoxZName); if (doc->getObject(CutYName)) deleteObject(CutYName); if (doc->getObject(BoxYName)) deleteObject(BoxYName); if (doc->getObject(CutXName)) deleteObject(CutXName); if (doc->getObject(BoxXName)) deleteObject(BoxXName); if (doc->getObject(CompoundName)) { auto compoundObject = doc->getObject(CompoundName); Part::Compound* pcCompoundDel = dynamic_cast(compoundObject); if (!pcCompoundDel) { Base::Console().Error("SectionCut error: compound is incorrectly named, cannot proceed\n"); return; } std::vector compoundObjects; pcCompoundDel->Links.getLinks(compoundObjects); // first delete the compound auto foundObj = std::find_if(ObjectsListVisible.begin(), ObjectsListVisible.end(), [anObject](const App::DocumentObjectT& obj) { return (obj.getObject() == anObject); }); if (foundObj != ObjectsListVisible.end()) ObjectsListVisible.erase(foundObj); doc->removeObject(CompoundName); // now delete the objects that have been part of the compound for (auto itCompound = compoundObjects.begin(); itCompound != compoundObjects.end(); itCompound++) { anObject = doc->getObject((*itCompound)->getNameInDocument()); auto foundObjInner = std::find_if(ObjectsListVisible.begin(), ObjectsListVisible.end(), [anObject](const App::DocumentObjectT& objInner) { return (objInner.getObject() == anObject); }); if (foundObjInner != ObjectsListVisible.end()) ObjectsListVisible.erase((foundObjInner)); doc->removeObject((*itCompound)->getNameInDocument()); } } // make all objects visible that have been visible when the dialog was called // because we made them invisible when we created cuts for (it = ObjectsListVisible.begin(); it != ObjectsListVisible.end(); ++it) { if (it->getObject()) // a formerly visible object might have been deleted it->getObject()->Visibility.setValue(true); else { // we must refresh the ObjectsListVisible list onRefreshCutPBclicked(); } } // we enable the sliders because for assemblies we disabled them ui->cutXHS->setEnabled(true); ui->cutYHS->setEnabled(true); ui->cutZHS->setEnabled(true); // ObjectsListVisible contains all visible objects of the document, but we can only cut // those that have a solid shape std::vector ObjectsListCut; bool isLinkAssembly = false; for (it = ObjectsListVisible.begin(); it != ObjectsListVisible.end(); ++it) { // we need all Link objects in App::Part for example for Assembly 4 if (it->getObject()->getTypeId() == Base::Type::fromName("App::Part")) { App::Part* pcPart = static_cast(it->getObject()); // collect all its link objects auto groupObjects = pcPart->Group.getValue(); for (auto itGO = groupObjects.begin(); itGO != groupObjects.end(); ++itGO) { if ((*itGO)->getTypeId() == Base::Type::fromName("App::Link")) { ObjectsListCut.push_back((*itGO)); // we assume that App::Links inside a App::Part are an assembly isLinkAssembly = true; } } } // get all shapes that are also Part::Features if (it->getObject()->getPropertyByName("Shape") && it->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Feature"))) { // sort out 2D objects, datums, App:Parts, compounds and objects that are part of a PartDesign body if (!it->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Part2DObject")) && !it->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Datum")) && !it->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("PartDesign::Feature")) && !it->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Compound")) && it->getObject()->getTypeId() != Base::Type::fromName("App::Part")) ObjectsListCut.push_back(it->getObject()); } // get Links that are derived from Part objects if (it->getObject()->getTypeId() == Base::Type::fromName("App::Link")) { App::Link* pcLink = static_cast(it->getObject()); auto linkedObject = doc->getObject(pcLink->LinkedObject.getObjectName()); if (linkedObject && linkedObject->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Feature"))) ObjectsListCut.push_back(it->getObject()); } } if (isLinkAssembly) { // we disable the sliders because for assemblies it will takes ages to do several dozen recomputes QString SliderToolTip = tr("Sliders are disabled for assemblies"); ui->cutXHS->setEnabled(false); ui->cutXHS->setToolTip(SliderToolTip); ui->cutYHS->setEnabled(false); ui->cutYHS->setToolTip(SliderToolTip); ui->cutZHS->setEnabled(false); ui->cutZHS->setToolTip(SliderToolTip); } // sort out objects that are part of Part::Boolean, Part::MultiCommon, Part::MultiFuse, // Part::Thickness and Part::FilletBase std::vector::iterator it2; std::vector::iterator it3; // check list of visible objects and not cut list because we want to repove from the cut list for (it = ObjectsListVisible.begin(); it != ObjectsListVisible.end(); ++it) { if ( it->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Boolean")) || it->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("Part::MultiCommon")) || it->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("Part::MultiFuse")) || it->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Thickness")) || it->getObject()->getTypeId().isDerivedFrom(Base::Type::fromName("Part::FilletBase")) ) { // get possible links auto subObjectList = it->getObject()->getOutList(); // if there are links, delete them if (!subObjectList.empty()) { for (it2 = subObjectList.begin(); it2 != subObjectList.end(); ++it2) { for (it3 = ObjectsListCut.begin(); it3 != ObjectsListCut.end(); ++it3) { if ((*it2) == (*it3)) { ObjectsListCut.erase(it3); break; } } } } } } // we might have no objects that can be cut if (ObjectsListCut.empty()) { if (isInitial) Base::Console().Error("SectionCut error: there are no visible objects to be cut\n"); else Base::Console().Error("SectionCut error: there are no objects in the document that can be cut\n"); // block signals to be able to reset the cut group boxes without calling startCutting again ui->groupBoxX->blockSignals(true); ui->groupBoxY->blockSignals(true); ui->groupBoxZ->blockSignals(true); ui->groupBoxX->setChecked(false); ui->groupBoxY->setChecked(false); ui->groupBoxZ->setChecked(false); ui->RefreshCutPB->setEnabled(true); ui->groupBoxX->blockSignals(false); ui->groupBoxY->blockSignals(false); ui->groupBoxZ->blockSignals(false); return; } // we cut this way: // 1. put all existing objects into a part compound // 2. create a box with the size of the bounding box // 3. cut the box from the compound // depending on how many cuts should be performed, we need as many boxes // if nothing is yet to be cut, we can return if (!ui->groupBoxX->isChecked() && !ui->groupBoxY->isChecked() && !ui->groupBoxZ->isChecked()) { // there is no active cut, so we can enable refresh button ui->RefreshCutPB->setEnabled(true); return; } // disable refresh button ui->RefreshCutPB->setEnabled(false); // create an empty compound auto CutCompound = doc->addObject("Part::Compound", CompoundName); if (!CutCompound) { Base::Console().Error( (std::string("SectionCut error: ") + std::string(CompoundName) + std::string(" could not be added\n")).c_str() ); return; } Part::Compound* pcCompound = static_cast(CutCompound); // store color and transparency of first object App::Color cutColor; int cutTransparency; bool autoColor = true; bool autoTransparency = true; auto vpFirstObject = dynamic_cast( Gui::Application::Instance->getViewProvider(*ObjectsListCut.begin())); if (vpFirstObject) { cutColor = vpFirstObject->ShapeColor.getValue(); cutTransparency = vpFirstObject->Transparency.getValue(); } // fill it with all found elements with the copies of the elements int count = 0; for (auto itCuts = ObjectsListCut.begin(); itCuts != ObjectsListCut.end(); ++itCuts, count++) { // first create a link with a unique name std::string newName; // since links to normal Part objects all have the document name "Link", use their label text instead if ((*itCuts)->getTypeId() == Base::Type::fromName("App::Link")) newName = (*itCuts)->Label.getValue(); else newName = (*itCuts)->getNameInDocument(); newName = newName + "_CutLink"; auto newObject = doc->addObject("App::Link", newName.c_str()); if (!newObject) { Base::Console().Error("SectionCut error: 'App::Link' could not be added\n"); return; } App::Link* pcLink = static_cast(newObject); // set the object to the created empty link object pcLink->LinkedObject.setValue((*itCuts)); // we want to get the link at the same position as the original pcLink->LinkTransform.setValue(true); // if the object is part of an App::Part container, the link needs to get the container placement auto parents = (*itCuts)->getInList(); if (!parents.empty()) { for (auto itParents = parents.begin(); itParents != parents.end(); ++itParents) { if ((*itParents)->getTypeId() == Base::Type::fromName("App::Part")) { App::Part* pcPartParent = static_cast((*itParents)); auto placement = Base::freecad_dynamic_cast( pcPartParent->getPropertyByName("Placement")); if (placement) pcLink->Placement.setValue(placement->getValue()); } } } // add the link to the compound pcCompound->Links.set1Value(count, newObject); // hide the objects since only the cut should later be visible (*itCuts)->Visibility.setValue(false); // check if all objects have same color and transparency if (ui->AutoCutfaceColor->isChecked()) { auto vpObject = dynamic_cast( Gui::Application::Instance->getViewProvider(*itCuts)); if (vpObject) { if (cutColor != vpObject->ShapeColor.getValue()) autoColor = false; if (cutTransparency != vpObject->Transparency.getValue()) autoTransparency = false; } } } // compute the filled compound pcCompound->recomputeFeature(); // make all objects invisible so that only the compound remains for (it = ObjectsListVisible.begin(); it != ObjectsListVisible.end(); ++it) { it->getObject()->Visibility.setValue(false); } // the area in which we can cut is the size of the compound // we get its size by its bounding box SbBox3f CompoundBoundingBox = getViewBoundingBox(); if (CompoundBoundingBox.isEmpty()) { Base::Console().Error("SectionCut error: the CompoundBoundingBox is empty\n"); return; } // store the current cut positions te reset them later if possible double CutPosX = ui->cutX->value(); double CutPosY = ui->cutY->value(); double CutPosZ = ui->cutZ->value(); // refresh all cut limits according to the new bounding box refreshCutRanges(CompoundBoundingBox); // prepare the cut box size according to the bounding box size std::vector BoundingBoxSize = { 0.0, 0.0, 0.0 }; CompoundBoundingBox.getSize(BoundingBoxSize[0], BoundingBoxSize[1], BoundingBoxSize[2]); // get placement of the bounding box origin std::vector BoundingBoxOrigin = { 0.0, 0.0, 0.0 }; CompoundBoundingBox.getOrigin(BoundingBoxOrigin[0], BoundingBoxOrigin[1], BoundingBoxOrigin[2]); // now we can create the cut boxes Base::Vector3d BoxOriginSet; Base::Placement placement; SbBox3f CutBoundingBox; hasBoxX = false; hasBoxY = false; hasBoxZ = false; hasBoxCustom = false; // if automatic, we take this color for the cut if (ui->AutoCutfaceColor->isChecked()) { if (autoColor) { ui->CutColor->blockSignals(true); ui->CutColor->setColor(cutColor.asValue()); ui->CutColor->blockSignals(false); } if (autoTransparency) { ui->CutTransparency->blockSignals(true); ui->CutTransparency->setValue(cutTransparency); ui->CutTransparency->setToolTip(QString::number(cutTransparency) + QString::fromLatin1(" %")); ui->CutTransparency->blockSignals(false); } } // read cutface color for the cut box App::Color boxColor; boxColor.setValue(ui->CutColor->color()); int boxTransparency = ui->CutTransparency->value(); if (ui->groupBoxX->isChecked()) { // create a box auto CutBox = doc->addObject("Part::Box", BoxXName); if (!CutBox) { Base::Console().Error( (std::string("SectionCut error: ") + std::string(BoxXName) + std::string(" could not be added\n")).c_str() ); return; } Part::Box* pcBox = static_cast(CutBox); // it appears that because of internal rounding errors, the bounding box is sometimes // a bit too small, for example for epplipsoides, thus make the box a bit larger pcBox->Length.setValue(BoundingBoxSize[0] + 1.0); pcBox->Width.setValue(BoundingBoxSize[1] + 1.0); pcBox->Height.setValue(BoundingBoxSize[2] + 1.0); // set the previous cut value because refreshCutRanges changed it // in case the there was previously no cut, nothing will actually be changed // the previous value might now be outside the current possible range, then reset it if (CutPosX >= ui->cutX->maximum()) { CutPosX = ui->cutX->maximum() - 0.1; // short below the maximum } else if (CutPosX <= ui->cutX->minimum()) { CutPosX = ui->cutX->minimum() + 0.1; // short above the minimum } // we don't set the value to ui->cutX because this would refresh the cut // which we don't have yet, thus do this later //set the box position if (!ui->flipX->isChecked()) BoxOriginSet.x = CutPosX - (BoundingBoxSize[0] + 1.0); else //flipped BoxOriginSet.x = CutPosX; // we made the box 1.0 larger that we can place it 0.5 below the bounding box BoxOriginSet.y = BoundingBoxOrigin[1] - 0.5; BoxOriginSet.z = BoundingBoxOrigin[2] - 0.5; placement.setPosition(BoxOriginSet); // set box color pcBox->Placement.setValue(placement); auto vpBox = dynamic_cast( Gui::Application::Instance->getViewProvider(pcBox)); if (vpBox) { vpBox->ShapeColor.setValue(boxColor); vpBox->Transparency.setValue(boxTransparency); } // create a cut feature auto CutFeature = doc->addObject("Part::Cut", CutXName); if (!CutFeature) { Base::Console().Error( (std::string("SectionCut error: ") + std::string(CutXName) + std::string(" could not be added\n")).c_str() ); return; } Part::Cut* pcCut = static_cast(CutFeature); pcCut->Base.setValue(CutCompound); pcCut->Tool.setValue(CutBox); // set the cut value ui->cutX->setValue(CutPosX); // recomputing recursively is especially for assemblies very time-consuming // however there must be a final recursicve recompute and we do this at the end // so only recomute recursively if there are no other cuts if (!ui->groupBoxY->isChecked() && !ui->groupBoxZ->isChecked()) pcCut->recomputeFeature(true); else pcCut->recomputeFeature(false); hasBoxX = true; } if (ui->groupBoxY->isChecked()) { // if there is a X cut, its size defines the possible range for the Y cut // the cut box size is not affected, it can be as large as the compound if (hasBoxX) { CutBoundingBox = getViewBoundingBox(); // refresh the Y cut limits according to the new bounding box refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::notZValue, Refresh::notXRange, Refresh::YRange, Refresh::notZRange); } auto CutBox = doc->addObject("Part::Box", BoxYName); if (!CutBox) { Base::Console().Error((std::string("SectionCut error: ") + std::string(BoxYName) + std::string(" could not be added\n")).c_str() ); return; } Part::Box* pcBox = static_cast(CutBox); pcBox->Length.setValue(BoundingBoxSize[0] + 1.0); pcBox->Width.setValue(BoundingBoxSize[1] + 1.0); pcBox->Height.setValue(BoundingBoxSize[2] + 1.0); // reset previous cut value if (CutPosY >= ui->cutY->maximum()) { CutPosY = ui->cutY->maximum() - 0.1; // short below the maximum } else if (CutPosY <= ui->cutY->minimum()) { CutPosY = ui->cutY->minimum() + 0.1; // short above the minimum } //set the box position BoxOriginSet.x = BoundingBoxOrigin[0] - 0.5; if (!ui->flipY->isChecked()) BoxOriginSet.y = CutPosY - (BoundingBoxSize[1] + 1.0); else //flipped BoxOriginSet.y = CutPosY; BoxOriginSet.z = BoundingBoxOrigin[2] - 0.5; placement.setPosition(BoxOriginSet); pcBox->Placement.setValue(placement); auto vpBox = dynamic_cast( Gui::Application::Instance->getViewProvider(pcBox)); if (vpBox) { vpBox->ShapeColor.setValue(boxColor); vpBox->Transparency.setValue(boxTransparency); } auto CutFeature = doc->addObject("Part::Cut", CutYName); if (!CutFeature) { Base::Console().Error((std::string("SectionCut error: ") + std::string(CutYName) + std::string(" could not be added\n")).c_str() ); return; } Part::Cut* pcCut = static_cast(CutFeature); // if there is already a cut, we must take it as feature to be cut if (hasBoxX) pcCut->Base.setValue(doc->getObject(CutXName)); else pcCut->Base.setValue(CutCompound); pcCut->Tool.setValue(CutBox); // set the cut value ui->cutY->setValue(CutPosY); if (!ui->groupBoxZ->isChecked()) pcCut->recomputeFeature(true); else pcCut->recomputeFeature(false); hasBoxY = true; } if (ui->groupBoxZ->isChecked()) { if (hasBoxX || hasBoxY) { CutBoundingBox = getViewBoundingBox(); refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::notZValue, Refresh::notXRange, Refresh::notYRange, Refresh::ZRange); } auto CutBox = doc->addObject("Part::Box", BoxZName); if (!CutBox) { Base::Console().Error((std::string("SectionCut error: ") + std::string(BoxZName) + std::string(" could not be added\n")).c_str() ); return; } Part::Box* pcBox = static_cast(CutBox); pcBox->Length.setValue(BoundingBoxSize[0] + 1.0); pcBox->Width.setValue(BoundingBoxSize[1] + 1.0); pcBox->Height.setValue(BoundingBoxSize[2] + 1.0); // reset previous cut value if (CutPosZ >= ui->cutZ->maximum()) { CutPosZ = ui->cutZ->maximum() - 0.1; // short below the maximum } else if (CutPosZ <= ui->cutZ->minimum()) { CutPosZ = ui->cutZ->minimum() + 0.1; // short above the minimum } //set the box position BoxOriginSet.x = BoundingBoxOrigin[0] - 0.5; BoxOriginSet.y = BoundingBoxOrigin[1] - 0.5; if (!ui->flipY->isChecked()) BoxOriginSet.z = CutPosZ - (BoundingBoxSize[2] + 1.0); else //flipped BoxOriginSet.z = CutPosZ; placement.setPosition(BoxOriginSet); pcBox->Placement.setValue(placement); auto vpBox = dynamic_cast( Gui::Application::Instance->getViewProvider(pcBox)); if (vpBox) { vpBox->ShapeColor.setValue(boxColor); vpBox->Transparency.setValue(boxTransparency); } auto CutFeature = doc->addObject("Part::Cut", CutZName); if (!CutFeature) { Base::Console().Error( (std::string("SectionCut error: ") + std::string(CutZName) + std::string(" could not be added\n")).c_str() ); return; } Part::Cut* pcCut = static_cast(CutFeature); // if there is already a cut, we must take it as feature to be cut if (hasBoxY) { pcCut->Base.setValue(doc->getObject(CutYName)); } else if (hasBoxX && !hasBoxY) { pcCut->Base.setValue(doc->getObject(CutXName)); } else { pcCut->Base.setValue(CutCompound); } pcCut->Tool.setValue(CutBox); // set the cut value ui->cutZ->setValue(CutPosZ); pcCut->recomputeFeature(true); hasBoxZ = true; } } SectionCut* SectionCut::makeDockWidget(QWidget* parent) { // embed this dialog into a QDockWidget SectionCut* sectionCut = new SectionCut(parent); Gui::DockWindowManager* pDockMgr = Gui::DockWindowManager::instance(); // the dialog is designed that you can see the tree, thus put it to the right side QDockWidget* dw = pDockMgr->addDockWindow("Section Cutting", sectionCut, Qt::RightDockWidgetArea); dw->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); //dw->setFloating(true); dw->show(); return sectionCut; } /** Destroys the object and frees any allocated resources */ SectionCut::~SectionCut() { // there might be no document if (!Gui::Application::Instance->activeDocument()) { noDocumentActions(); return; } if (!ui->keepOnlyCutCB->isChecked()) { // make all objects visible that have been visible when the dialog was called // because we made them invisible when we created cuts for (auto it = ObjectsListVisible.begin(); it != ObjectsListVisible.end(); ++it) { if (it->getObject()) // a formerly visible object might have been deleted it->getObject()->Visibility.setValue(true); } } } void SectionCut::reject() { QDialog::reject(); QDockWidget* dw = qobject_cast(parent()); if (dw) { dw->deleteLater(); } } void SectionCut::onGroupBoxXtoggled() { // reset the cut startCutting(); } void SectionCut::onGroupBoxYtoggled() { startCutting(); } void SectionCut::onGroupBoxZtoggled() { startCutting(); } // helper function for the onFlip_clicked signal void SectionCut::CutValueHelper(double value, QDoubleSpinBox* SpinBox, QSlider* Slider) { // there might be no document if (!Gui::Application::Instance->activeDocument()) { noDocumentActions(); return; } // refresh objects and return in case the document was changed if (doc != Gui::Application::Instance->activeDocument()->getDocument()) { onRefreshCutPBclicked(); return; } // update slider position and tooltip // the slider value is % of the cut range if (Slider->isEnabled()) { Slider->blockSignals(true); Slider->setValue( int((value - SpinBox->minimum()) / (SpinBox->maximum() - SpinBox->minimum()) * 100.0)); Slider->setToolTip(QString::number(value, 'g', Base::UnitsApi::getDecimals())); Slider->blockSignals(false); } // we cannot cut to the edge because then the result is an empty shape // we chose purposely not to simply set the range for cutX previously // because everything is allowed just not the min/max if (SpinBox->value() == SpinBox->maximum()) { SpinBox->setValue(SpinBox->maximum() - 0.1); return; } if (SpinBox->value() == SpinBox->minimum()) { SpinBox->setValue(SpinBox->minimum() + 0.1); return; } } void SectionCut::onCutXvalueChanged(double val) { CutValueHelper(val, ui->cutX, ui->cutXHS); // get the cut box auto CutBox = doc->getObject(BoxXName); // when the value has been set after resetting the compound bounding box // there is not yet a cut and we do nothing if (!CutBox) return; Part::Box* pcBox = dynamic_cast(CutBox); if (!pcBox) { Base::Console().Error((std::string("SectionCut error: ") + std::string(BoxXName) + std::string(" is no Part::Box object. Cannot proceed.\n")).c_str()); return; } // get its placement and size Base::Placement placement = pcBox->Placement.getValue(); Base::Vector3d BoxPosition = placement.getPosition(); // change the placement if (!ui->flipX->isChecked()) BoxPosition.x = ui->cutX->value() - pcBox->Length.getValue(); else //flipped BoxPosition.x = ui->cutX->value(); placement.setPosition(BoxPosition); pcBox->Placement.setValue(placement); auto CutObject = doc->getObject(CutXName); // there should be a box, but maybe the user deleted it meanwhile if (!CutObject) { Base::Console().Warning((std::string("SectionCut warning: there is no ") + std::string(CutXName) + std::string(", trying to recreate it\n")).c_str()); // recreate the box startCutting(); return; } // if there is another cut, we must recalculate it too // we might have cut so that the range for Y and Z is now smaller // the hierarchy is always Z->Y->X if (hasBoxY && !hasBoxZ) { // only Y auto CutFeatureY = doc->getObject(CutYName); if (!CutFeatureY) { Base::Console().Warning((std::string("SectionCut warning: there is no ") + std::string(CutYName) + std::string(", trying to recreate it\n")).c_str()); startCutting(); return; } // refresh the Y and Z cut limits according to the new bounding box of the cut result // make the SectionCutY invisible CutFeatureY->Visibility.setValue(false); // make SectionCutX visible CutObject->Visibility.setValue(true); // get new bounding box auto CutBoundingBox = getViewBoundingBox(); // refresh Y limits and Z limits + Z value refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::ZValue, Refresh::notXRange, Refresh::YRange, Refresh::ZRange); // the value of Y can now be outside or at the limit, in this case reset the value too if ((ui->cutY->value() >= ui->cutY->maximum()) || (ui->cutY->value() <= ui->cutY->minimum())) refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::YValue, Refresh::ZValue, Refresh::notXRange, Refresh::YRange, Refresh::ZRange); // make the SectionCutY visible again CutFeatureY->Visibility.setValue(true); // make SectionCutX invisible again CutObject->Visibility.setValue(false); // recompute the cut Part::Cut* pcCutY = static_cast(CutFeatureY); pcCutY->recomputeFeature(true); } else if (hasBoxZ) { // at least Z // the main cut is Z, no matter if there is a cut in Y auto CutFeatureZ = doc->getObject(CutZName); if (!CutFeatureZ) { Base::Console().Error((std::string("SectionCut error: there is no ") + std::string(CutZName) + std::string("\n")).c_str()); return; } // refresh the Y and Z cut limits according to the new bounding box of the cut result // make the SectionCutZ invisible CutFeatureZ->Visibility.setValue(false); // make SectionCutX visible CutObject->Visibility.setValue(true); // get new bounding box auto CutBoundingBox = getViewBoundingBox(); // refresh Y and Z limits if (hasBoxY) { refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::notZValue, Refresh::notXRange, Refresh::YRange, Refresh::ZRange); // the value of Y or Z can now be outside or at the limit, in this case reset the value too if ((ui->cutY->value() >= ui->cutY->maximum()) || (ui->cutY->value() <= ui->cutY->minimum())) refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::YValue, Refresh::notZValue, Refresh::notXRange, Refresh::YRange, Refresh::ZRange); if ((ui->cutZ->value() >= ui->cutZ->maximum()) || (ui->cutZ->value() <= ui->cutZ->minimum())) refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::ZValue, Refresh::notXRange, Refresh::YRange, Refresh::ZRange); } else { // there is no Y cut yet so we can set the Y value too refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::YValue, Refresh::notZValue, Refresh::notXRange, Refresh::YRange, Refresh::ZRange); // the value of Z can now be outside or at the limit, in this case reset the value too if ((ui->cutZ->value() >= ui->cutZ->maximum()) || (ui->cutZ->value() <= ui->cutZ->minimum())) refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::YValue, Refresh::ZValue, Refresh::notXRange, Refresh::YRange, Refresh::ZRange); } // make the SectionCutZ visible again CutFeatureZ->Visibility.setValue(true); // make SectionCutX invisible again CutObject->Visibility.setValue(false); // recompute the cut Part::Cut* pcCutZ = static_cast(CutFeatureZ); pcCutZ->recomputeFeature(true); } else { // just X // refresh Y and Z limits + values auto CutBoundingBox = getViewBoundingBox(); refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::YValue, Refresh::ZValue, Refresh::notXRange, Refresh::YRange, Refresh::ZRange); // recompute the cut Part::Cut* pcCut = dynamic_cast(CutObject); if (!pcCut) { Base::Console().Error((std::string("SectionCut error: ") + std::string(CutZName) + std::string(" is no Part::Cut object. Cannot proceed.\n")).c_str()); return; } pcCut->recomputeFeature(true); } } void SectionCut::onCutXHSsliderMoved(int val) { // we cannot cut to the edge because then the result is an empty shape // we chose purposely not to simply set the range for cutXHS previously // because everything is allowed just not the min/max // we set it one slider step below the min/max if (val == ui->cutXHS->maximum()) { ui->cutXHS->setValue(ui->cutXHS->maximum() - ui->cutXHS->singleStep()); return; } if (val == ui->cutXHS->minimum()) { ui->cutXHS->setValue(ui->cutXHS->minimum() + ui->cutXHS->singleStep()); return; } // the slider value is % of the cut range double NewCutValue = ui->cutX->minimum() + val / 100.0 * (ui->cutX->maximum() - ui->cutX->minimum()); ui->cutXHS->setToolTip(QString::number(NewCutValue, 'g', Base::UnitsApi::getDecimals())); ui->cutX->setValue(NewCutValue); } void SectionCut::onCutXHSChanged(int val) { onCutXHSsliderMoved(val); } void SectionCut::onCutYvalueChanged(double val) { CutValueHelper(val, ui->cutY, ui->cutYHS); auto CutBox = doc->getObject(BoxYName); if (!CutBox) return; Part::Box* pcBox = dynamic_cast(CutBox); if (!pcBox) { Base::Console().Error((std::string("SectionCut error: ") + std::string(BoxYName) + std::string(" is no Part::Box object. Cannot proceed.\n")).c_str()); return; } Base::Placement placement = pcBox->Placement.getValue(); Base::Vector3d BoxPosition = placement.getPosition(); if (!ui->flipY->isChecked()) BoxPosition.y = ui->cutY->value() - pcBox->Width.getValue(); else //flipped BoxPosition.y = ui->cutY->value(); placement.setPosition(BoxPosition); pcBox->Placement.setValue(placement); auto CutObject = doc->getObject(CutYName); if (!CutObject) { Base::Console().Warning((std::string("SectionCut warning: there is no ") + std::string(CutYName) + std::string(", trying to recreate it\n")).c_str()); startCutting(); return; } // if there is another cut, we must recalculate it too // we might have cut so that the range for Z is now smaller // we only need to check for Z since the hierarchy is always Z->Y->X if (hasBoxZ) { auto CutFeatureZ = doc->getObject(CutZName); if (!CutFeatureZ) { Base::Console().Error((std::string("SectionCut error: there is no ") + std::string(CutZName) + std::string("\n")).c_str()); return; } // refresh the Z cut limits according to the new bounding box of the cut result // make the SectionCutZ invisible CutFeatureZ->Visibility.setValue(false); // make SectionCutX visible CutObject->Visibility.setValue(true); // get new bounding box auto CutBoundingBox = getViewBoundingBox(); // refresh Z limits refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::notZValue, Refresh::notXRange, Refresh::notYRange, Refresh::ZRange); // the value of Z can now be outside or at the limit, in this case reset the value too if ((ui->cutZ->value() >= ui->cutZ->maximum()) || (ui->cutZ->value() <= ui->cutZ->minimum())) refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::ZValue, Refresh::notXRange, Refresh::notYRange, Refresh::ZRange); // make the SectionCutZ visible again CutFeatureZ->Visibility.setValue(true); // make SectionCutX invisible again CutObject->Visibility.setValue(false); // recompute the cut Part::Cut* pcCutZ = static_cast(CutFeatureZ); pcCutZ->recomputeFeature(true); } else { // just Y // refresh Z limits + values auto CutBoundingBox = getViewBoundingBox(); refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::ZValue, Refresh::notXRange, Refresh::notYRange, Refresh::ZRange); // recompute the cut Part::Cut* pcCut = dynamic_cast(CutObject); if (!pcCut) { Base::Console().Error((std::string("SectionCut error: ") + std::string(CutZName) + std::string(" is no Part::Cut object. Cannot proceed.\n")).c_str()); return; } pcCut->recomputeFeature(true); // refresh X limits // this is done by // first making the cut X box visible, the setting the limits only for X // if x-limit in box direction is larger than object, reset value to saved limit if (hasBoxX) { auto CutBoxX = doc->getObject(BoxXName); if (!CutBoxX) return; // first store the values double storedX; if (!ui->flipX->isChecked()) storedX = ui->cutX->minimum(); else storedX = ui->cutX->maximum(); // show the cutting box CutBoxX->Visibility.setValue(true); // set new XRange auto CutBoundingBox = getViewBoundingBox(); refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::notZValue, Refresh::XRange, Refresh::notYRange, Refresh::notZRange); // hide cutting box and compare resultwith stored value CutBoxX->Visibility.setValue(false); if (!ui->flipX->isChecked()) { if (storedX > ui->cutX->minimum()) ui->cutX->setMinimum(storedX); } else { if (storedX < ui->cutX->maximum()) ui->cutX->setMaximum(storedX); } } } } void SectionCut::onCutYHSsliderMoved(int val) { // we cannot cut to the edge because then the result is an empty shape if (val == ui->cutYHS->maximum()) { ui->cutYHS->setValue(ui->cutYHS->maximum() - ui->cutYHS->singleStep()); return; } if (val == ui->cutYHS->minimum()) { ui->cutYHS->setValue(ui->cutYHS->minimum() + ui->cutYHS->singleStep()); return; } // the slider value is % of the cut range double NewCutValue = ui->cutY->minimum() + val / 100.0 * (ui->cutY->maximum() - ui->cutY->minimum()); ui->cutYHS->setToolTip(QString::number(NewCutValue, 'g', Base::UnitsApi::getDecimals())); ui->cutY->setValue(NewCutValue); } void SectionCut::onCutYHSChanged(int val) { onCutYHSsliderMoved(val); } void SectionCut::onCutZvalueChanged(double val) { CutValueHelper(val, ui->cutZ, ui->cutZHS); auto CutBox = doc->getObject(BoxZName); if (!CutBox) return; Part::Box* pcBox = dynamic_cast(CutBox); if (!pcBox) { Base::Console().Error((std::string("SectionCut error: ") + std::string(BoxZName) + std::string(" is no Part::Box object. Cannot proceed.\n")).c_str()); return; } Base::Placement placement = pcBox->Placement.getValue(); Base::Vector3d BoxPosition = placement.getPosition(); if (!ui->flipZ->isChecked()) BoxPosition.z = ui->cutZ->value() - pcBox->Height.getValue(); else //flipped BoxPosition.z = ui->cutZ->value(); placement.setPosition(BoxPosition); pcBox->Placement.setValue(placement); auto CutObject = doc->getObject(CutZName); if (!CutObject) { Base::Console().Warning((std::string("SectionCut warning: there is no ") + std::string(CutZName) + std::string(", trying to recreate it\n")).c_str()); startCutting(); return; } Part::Cut* pcCut = dynamic_cast(CutObject); if (!pcCut) { Base::Console().Error((std::string("SectionCut error: ") + std::string(CutZName) + std::string(" is no Part::Cut object. Cannot proceed.\n")).c_str()); return; } pcCut->recomputeFeature(true); // refresh X and Y limits // this is done e.g. for X by // first making the cut X box visible, the setting the limits only for X // if x-limit in box direction is larger than object, reset value to saved limit SbBox3f CutBoundingBox; if (hasBoxX) { auto CutBoxX = doc->getObject(BoxXName); if (!CutBoxX) return; // first store the values double storedX; if (!ui->flipX->isChecked()) storedX = ui->cutX->minimum(); else storedX = ui->cutX->maximum(); // show the cutting box CutBoxX->Visibility.setValue(true); // set new XRange CutBoundingBox = getViewBoundingBox(); refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::notZValue, Refresh::XRange, Refresh::notYRange, Refresh::notZRange); // hide cutting box and compare resultwith stored value CutBoxX->Visibility.setValue(false); if (!ui->flipX->isChecked()) { if (storedX > ui->cutX->minimum()) ui->cutX->setMinimum(storedX); } else { if (storedX < ui->cutX->maximum()) ui->cutX->setMaximum(storedX); } } if (hasBoxY) { auto CutBoxY = doc->getObject(BoxYName); if (!CutBoxY) return; double storedY; if (!ui->flipY->isChecked()) storedY = ui->cutY->minimum(); else storedY = ui->cutY->maximum(); CutBoxY->Visibility.setValue(true); CutBoundingBox = getViewBoundingBox(); refreshCutRanges(CutBoundingBox, Refresh::notXValue, Refresh::notYValue, Refresh::notZValue, Refresh::notXRange, Refresh::YRange, Refresh::notZRange); CutBoxY->Visibility.setValue(false); if (!ui->flipY->isChecked()) { if (storedY > ui->cutY->minimum()) ui->cutY->setMinimum(storedY); } else { if (storedY < ui->cutY->maximum()) ui->cutY->setMaximum(storedY); } } } void SectionCut::onCutZHSsliderMoved(int val) { // we cannot cut to the edge because then the result is an empty shape if (val == ui->cutZHS->maximum()) { ui->cutZHS->setValue(ui->cutZHS->maximum() - ui->cutZHS->singleStep()); return; } if (val == ui->cutZHS->minimum()) { ui->cutZHS->setValue(ui->cutZHS->minimum() + ui->cutZHS->singleStep()); return; } // the slider value is % of the cut range double NewCutValue = ui->cutZ->minimum() + val / 100.0 * (ui->cutZ->maximum() - ui->cutZ->minimum()); ui->cutZHS->setToolTip(QString::number(NewCutValue, 'g', Base::UnitsApi::getDecimals())); ui->cutZ->setValue(NewCutValue); } void SectionCut::onCutZHSChanged(int val) { onCutZHSsliderMoved(val); } // helper function for the onFlip_clicked signal void SectionCut::FlipClickedHelper(const char* BoxName) { // there might be no document if (!Gui::Application::Instance->activeDocument()) { noDocumentActions(); return; } // refresh objects and return in case the document was changed if (doc != Gui::Application::Instance->activeDocument()->getDocument()) { onRefreshCutPBclicked(); return; } // we must move the box e.g. in y-direction by its Width auto CutBox = doc->getObject(BoxName); // there should be a box, but maybe the user deleted it meanwhile if (!CutBox) { Base::Console().Warning((std::string("SectionCut warning: there is no ") + std::string(BoxName) + std::string(", trying to recreate it\n")).c_str()); // recreate the box startCutting(); return; } Part::Box* pcBox = dynamic_cast(CutBox); if (!pcBox) { Base::Console().Error((std::string("SectionCut error: ") + std::string(BoxName) + std::string(" is no Part::Box object. Cannot proceed.\n")).c_str()); return; } // get its placement and size Base::Placement placement = pcBox->Placement.getValue(); Base::Vector3d BoxPosition = placement.getPosition(); // flip the box switch (std::string(BoxName).back()) { case 'X': if (ui->flipX->isChecked()) BoxPosition.x = BoxPosition.x + pcBox->Length.getValue(); else BoxPosition.x = BoxPosition.x - pcBox->Length.getValue(); break; case 'Y': if (ui->flipY->isChecked()) BoxPosition.y = BoxPosition.y + pcBox->Width.getValue(); else BoxPosition.y = BoxPosition.y - pcBox->Width.getValue(); break; case 'Z': if (ui->flipZ->isChecked()) BoxPosition.z = BoxPosition.z + pcBox->Height.getValue(); else BoxPosition.z = BoxPosition.z - pcBox->Height.getValue(); break; } placement.setPosition(BoxPosition); pcBox->Placement.setValue(placement); } void SectionCut::onFlipXclicked() { FlipClickedHelper(BoxXName); auto CutObject = doc->getObject(CutXName); // there should be a cut, but maybe the user deleted it meanwhile if (!CutObject) { Base::Console().Warning((std::string("SectionCut warning: there is no ") + std::string(CutXName) + std::string(", trying to recreate it\n")).c_str()); // recreate the box startCutting(); return; } // if there is another cut, we must recalculate it too // the hierarchy is always Z->Y->X if (hasBoxY && !hasBoxZ) { // only Y auto CutFeatureY = doc->getObject(CutYName); if (!CutFeatureY) { Base::Console().Warning( (std::string("SectionCut warning: the expected ") + std::string(CutYName) + std::string(" is missing, trying to recreate it\n")).c_str()); // recreate the box startCutting(); return; } Part::Cut* pcCutY = dynamic_cast(CutFeatureY); if (!pcCutY) { Base::Console().Error((std::string("SectionCut error: ") + std::string(CutYName) + std::string(" is no Part::Cut object. Cannot proceed.\n")).c_str()); return; } pcCutY->recomputeFeature(true); } else if ((!hasBoxY && hasBoxZ) || (hasBoxY && hasBoxZ)) { // at least Z // the main cut is Z, no matter if there is a cut in Y auto CutFeatureZ = doc->getObject(CutZName); if (!CutFeatureZ) { Base::Console().Warning((std::string("SectionCut warning: the expected ") + std::string(CutZName) + std::string(" is missing, trying to recreate it\n")).c_str()); // recreate the box startCutting(); return; } Part::Cut* pcCutZ = dynamic_cast(CutFeatureZ); if (!pcCutZ) { Base::Console().Error((std::string("SectionCut error: ") + std::string(CutZName) + std::string(" is no Part::Cut object. Cannot proceed.\n")).c_str()); return; } pcCutZ->recomputeFeature(true); } else { // only do this when there is no other box to save recomputes Part::Cut* pcCut = dynamic_cast(CutObject); if (!pcCut) { Base::Console().Error((std::string("SectionCut error: ") + std::string(CutXName) + std::string(" is no Part::Cut object. Cannot proceed.\n")).c_str()); return; } pcCut->recomputeFeature(true); } } void SectionCut::onFlipYclicked() { FlipClickedHelper(BoxYName); auto CutObject = doc->getObject(CutYName); // there should be a cut, but maybe the user deleted it meanwhile if (!CutObject) { Base::Console().Warning((std::string("SectionCut warning: there is no ") + std::string(CutYName) + std::string(", trying to recreate it\n")).c_str()); // recreate the box startCutting(); return; } // if there is another cut, we must recalculate it too // we only need to check for Z since the hierarchy is always Z->Y->X if (hasBoxZ) { auto CutFeatureZ = doc->getObject(CutZName); Part::Cut* pcCutZ = dynamic_cast(CutFeatureZ); if (!pcCutZ) { Base::Console().Error((std::string("SectionCut error: ") + std::string(CutZName) + std::string(" is no Part::Cut object. Cannot proceed.\n")).c_str()); return; } pcCutZ->recomputeFeature(true); } else { Part::Cut* pcCut = dynamic_cast(CutObject); if (!pcCut) { Base::Console().Error((std::string("SectionCut error: ") + std::string(CutYName) + std::string(" is no Part::Cut object. Cannot proceed.\n")).c_str()); return; } pcCut->recomputeFeature(true); } } void SectionCut::onFlipZclicked() { FlipClickedHelper(BoxZName); auto CutObject = doc->getObject(CutZName); // there should be a cut, but maybe the user deleted it meanwhile if (!CutObject) { Base::Console().Warning((std::string("SectionCut warning: there is no ") + std::string(CutZName) + std::string(", trying to recreate it\n")).c_str()); // recreate the box startCutting(); return; } Part::Cut* pcCut = dynamic_cast(CutObject); if (!pcCut) { Base::Console().Error((std::string("SectionCut error: ") + std::string(CutZName) + std::string(" is no Part::Cut object. Cannot proceed.\n")).c_str()); return; } pcCut->recomputeFeature(true); } // changes the cutface color void SectionCut::onCutColorclicked() { // re-cut to change the color of all cut boxes if (ui->groupBoxX->isChecked() || ui->groupBoxY->isChecked() || ui->groupBoxZ->isChecked()) startCutting(); } void SectionCut::onTransparencySliderMoved(int val) { ui->CutTransparency->setToolTip(QString::number(val) + QString::fromLatin1(" %")); // highlight the tooltip QToolTip::showText(QCursor::pos(), QString::number(val) + QString::fromLatin1(" %"), nullptr); // re-cut to change the color of all cut boxes if (ui->groupBoxX->isChecked() || ui->groupBoxY->isChecked() || ui->groupBoxZ->isChecked()) startCutting(); } void SectionCut::onTransparencyChanged(int val) { onTransparencySliderMoved(val); } // refreshes the list of document objects and the visible objects void SectionCut::onRefreshCutPBclicked() { // get document auto docGui = Gui::Application::Instance->activeDocument(); if (!docGui) { Base::Console().Error("SectionCut error: there is no document\n"); return; } doc = docGui->getDocument(); // get all objects in the document std::vector ObjectsList = doc->getObjects(); if (ObjectsList.empty()) { Base::Console().Error("SectionCut error: there are no objects in the document\n"); return; } // empty the ObjectsListVisible ObjectsListVisible.clear(); // now store those that are currently visible for (auto it = ObjectsList.begin(); it != ObjectsList.end(); ++it) { if ((*it)->Visibility.getValue()) { ObjectsListVisible.push_back(*it); } } // reset defaults hasBoxX = false; hasBoxY = false; hasBoxZ = false; // we can have existing cuts if (doc->getObject(CutZName)) { hasBoxZ = true; ui->groupBoxZ->blockSignals(true); ui->groupBoxZ->setChecked(true); ui->groupBoxZ->blockSignals(false); } if (doc->getObject(CutYName)) { hasBoxY = true; ui->groupBoxY->blockSignals(true); ui->groupBoxY->setChecked(true); ui->groupBoxY->blockSignals(false); } if (doc->getObject(CutXName)) { hasBoxX = true; ui->groupBoxX->blockSignals(true); ui->groupBoxX->setChecked(true); ui->groupBoxX->blockSignals(false); } // if there is a cut, disable the button if (hasBoxX || hasBoxY || hasBoxZ) ui->RefreshCutPB->setEnabled(false); } SbBox3f SectionCut::getViewBoundingBox() { SbBox3f Box; auto docGui = Gui::Application::Instance->activeDocument(); if (!docGui) { Base::Console().Error("SectionCut error: there is no active document\n"); return Box; // return an empty box } Gui::View3DInventor* view = dynamic_cast(docGui->getActiveView()); if (!view) { Base::Console().Error("SectionCut error: could not get the active view\n"); return Box; // return an empty box } Gui::View3DInventorViewer* viewer = view->getViewer(); SoCamera* camera = viewer->getSoRenderManager()->getCamera(); if (!camera || !camera->isOfType(SoOrthographicCamera::getClassTypeId())) return Box; // return an empty box // get scene bounding box SoGetBoundingBoxAction action(viewer->getSoRenderManager()->getViewportRegion()); action.apply(viewer->getSceneGraph()); return action.getBoundingBox(); } void SectionCut::refreshCutRanges(SbBox3f BoundingBox, bool forXValue, bool forYValue, bool forZValue, bool forXRange, bool forYRange, bool forZRange) { if (!BoundingBox.isEmpty()) { SbVec3f center = BoundingBox.getCenter(); int minDecimals = Base::UnitsApi::getDecimals(); float lenx, leny, lenz; BoundingBox.getSize(lenx, leny, lenz); int steps = 100; // set the ranges float rangeMin; // to silence a compiler warning we use a float float rangeMax; if (forXRange) { rangeMin = center[0] - (lenx / 2); rangeMax = center[0] + (lenx / 2); ui->cutX->setRange(rangeMin, rangeMax); // determine the single step values lenx = lenx / steps; int dim = static_cast(log10(lenx)); double singleStep = pow(10.0, dim); ui->cutX->setSingleStep(singleStep); } if (forYRange) { rangeMin = center[1] - (leny / 2); rangeMax = center[1] + (leny / 2); ui->cutY->setRange(rangeMin, rangeMax); leny = leny / steps; int dim = static_cast(log10(leny)); double singleStep = pow(10.0, dim); ui->cutY->setSingleStep(singleStep); } if (forZRange) { rangeMin = center[2] - (lenz / 2); rangeMax = center[2] + (lenz / 2); ui->cutZ->setRange(rangeMin, rangeMax); lenz = lenz / steps; int dim = static_cast(log10(lenz)); double singleStep = pow(10.0, dim); ui->cutZ->setSingleStep(singleStep); } if (forXValue) { ui->cutX->setValue(center[0]); ui->cutXHS->setValue(50); } if (forYValue) { ui->cutY->setValue(center[1]); ui->cutYHS->setValue(50); } if (forZValue) { ui->cutZ->setValue(center[2]); ui->cutZHS->setValue(50); } // set decimals ui->cutX->setDecimals(minDecimals); ui->cutY->setDecimals(minDecimals); ui->cutZ->setDecimals(minDecimals); } } #include "moc_SectionCutting.cpp"