diff --git a/data/tests/PocketTest.fcstd b/data/tests/PocketTest.fcstd new file mode 100644 index 0000000000..6acf91ba05 Binary files /dev/null and b/data/tests/PocketTest.fcstd differ diff --git a/src/Mod/PartDesign/App/FeaturePocket.cpp b/src/Mod/PartDesign/App/FeaturePocket.cpp index b6aec7dcdd..a0ef1cdd4b 100644 --- a/src/Mod/PartDesign/App/FeaturePocket.cpp +++ b/src/Mod/PartDesign/App/FeaturePocket.cpp @@ -41,17 +41,20 @@ # include # include # include +# include +# include #endif #include #include +#include #include "FeaturePocket.h" using namespace PartDesign; -const char* Pocket::TypeEnums[]= {"Length","UpToLast","UpToFirst",NULL}; +const char* Pocket::TypeEnums[]= {"Length","UpToLast","UpToFirst","ThroughAll","UpToFace",NULL}; PROPERTY_SOURCE(PartDesign::Pocket, PartDesign::SketchBased) @@ -60,13 +63,15 @@ Pocket::Pocket() ADD_PROPERTY(Type,((long)0)); Type.setEnums(TypeEnums); ADD_PROPERTY(Length,(100.0)); + ADD_PROPERTY(FaceName,("")); } short Pocket::mustExecute() const { if (Placement.isTouched() || Sketch.isTouched() || - Length.isTouched()) + Length.isTouched() || + FaceName.isTouched()) return 1; return 0; } @@ -114,13 +119,22 @@ App::DocumentObjectExecReturn *Pocket::execute(void) if (!SupportObject) return new App::DocumentObjectExecReturn("No support in Sketch!"); + const TopoDS_Shape& support = SupportObject->Shape.getValue(); + if (support.IsNull()) + return new App::DocumentObjectExecReturn("Support shape is invalid"); + TopExp_Explorer xp (support, TopAbs_SOLID); + if (!xp.More()) + return new App::DocumentObjectExecReturn("Support shape is not a solid"); + TopoDS_Shape aFace = makeFace(wires); if (aFace.IsNull()) return new App::DocumentObjectExecReturn("Creating a face from sketch failed"); // This is a trick to avoid problems with the cut operation. Sometimes a cut doesn't - // work as expected if faces or coincident. Thus, we move the face in normal direction + // work as expected if faces are coincident. Thus, we move the face in normal direction // but make it longer by one unit in the opposite direction. + // TODO: Isn't one unit (one millimeter) a lot, assuming someone models a really tiny solid? + // What about using 2 * Precision::Confusion() ? gp_Trsf mov; mov.SetTranslation(gp_Vec(SketchVector.x,SketchVector.y,SketchVector.z)); TopLoc_Location loc(mov); @@ -135,39 +149,133 @@ App::DocumentObjectExecReturn *Pocket::execute(void) this->positionBySketch(); TopLoc_Location invObjLoc = this->getLocation().Inverted(); - // extrude the face to a solid - gp_Vec vec(SketchVector.x,SketchVector.y,SketchVector.z); - vec.Transform(invObjLoc.Transformation()); - BRepPrimAPI_MakePrism PrismMaker(aFace.Moved(invObjLoc),vec,0,1); - if (PrismMaker.IsDone()) { - // if the sketch has a support fuse them to get one result object (PAD!) - if (SupportObject) { - const TopoDS_Shape& support = SupportObject->Shape.getValue(); - if (support.IsNull()) - return new App::DocumentObjectExecReturn("Support shape is invalid"); - TopExp_Explorer xp (support, TopAbs_SOLID); - if (!xp.More()) - return new App::DocumentObjectExecReturn("Support shape is not a solid"); - // Let's call algorithm computing a fuse operation: - BRepAlgoAPI_Cut mkCut(support.Moved(invObjLoc), PrismMaker.Shape()); - // Let's check if the fusion has been successful - if (!mkCut.IsDone()) - return new App::DocumentObjectExecReturn("Cut with support failed"); + try { + // extrude the face to a solid + TopoDS_Shape prism; - // we have to get the solids (fuse create seldomly compounds) - TopoDS_Shape solRes = this->getSolid(mkCut.Shape()); - if (solRes.IsNull()) - return new App::DocumentObjectExecReturn("Resulting shape is not a solid"); + if ((std::string(Type.getValueAsString()) == "UpToLast") || + (std::string(Type.getValueAsString()) == "UpToFirst") || + (std::string(Type.getValueAsString()) == "UpToFace")) + { + TopoDS_Face upToFace; + gp_Dir dir(SketchVector.x,SketchVector.y,SketchVector.z); - this->Shape.setValue(solRes); - } - else { - return new App::DocumentObjectExecReturn("Cannot create a tool out of sketch with no support"); + if ((std::string(Type.getValueAsString()) == "UpToLast") || + (std::string(Type.getValueAsString()) == "UpToFirst")) + { + TopoDS_Shape origFace = makeFace(wires); // original sketch face before moving one unit + std::vector cfaces = Part::findAllFacesCutBy(support, origFace, dir); + if (cfaces.empty()) + return new App::DocumentObjectExecReturn("No faces found in this direction"); + + // Find nearest/furthest face + std::vector::const_iterator it, it_near, it_far; + it_near = it_far = cfaces.begin(); + for (it = cfaces.begin(); it != cfaces.end(); it++) + if (it->distsq > it_far->distsq) + it_far = it; + else if (it->distsq < it_near->distsq) + it_near = it; + upToFace = (std::string(Type.getValueAsString()) == "UpToLast" ? it_far->face : it_near->face); + } else { + if (FaceName.getValue() == "") + return new App::DocumentObjectExecReturn("Cannot extrude up to face: No face selected"); + + // Get active object, this is the object that the user referenced when he clicked on the face! + App::DocumentObject* baseLink = this->getDocument()->getActiveObject(); + + if (!baseLink) + return new App::DocumentObjectExecReturn("Cannot extrude up to face: No object linked"); + if (!baseLink->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) + return new App::DocumentObjectExecReturn("Cannot extrude up to face: Linked object is not a Part object"); + Part::Feature *base = static_cast(baseLink); + const Part::TopoShape& baseShape = base->Shape.getShape(); + if (baseShape._Shape.IsNull()) + return new App::DocumentObjectExecReturn("Cannot extrude up to face: Cannot work on invalid shape"); + + TopoDS_Shape sub = baseShape.getSubShape(FaceName.getValue()); + if (!sub.IsNull() && sub.ShapeType() == TopAbs_FACE) + upToFace = TopoDS::Face(sub); + else + return new App::DocumentObjectExecReturn("Cannot extrude up to face: Selection is not a face"); + + // Find the origin of this face (i.e. a vertex or a edge in a sketch) + TopoDS_Shape origin = base->findOriginOf(sub); + + // Validate face + // TODO: This would also exclude faces that are valid but not cut by the line + // So for now we trust to the intelligence of the user when picking the face + /*std::vector cfaces = findAllFacesCutBy(upToFace, origFace, dir); + if (cfaces.empty()) + return new App::DocumentObjectExecReturn("No faces found in this direction");*/ + } + + // Create semi-infinite prism from sketch in direction dir + dir.Transform(invObjLoc.Transformation()); + BRepPrimAPI_MakePrism PrismMaker(aFace.Moved(invObjLoc),dir,0,0,1); + if (!PrismMaker.IsDone()) + return new App::DocumentObjectExecReturn("Cannot extrude up to face: Could not extrude the sketch!"); + + // Cut off the prism at the face we found + // Grab any point from the sketch + TopExp_Explorer exp; + exp.Init(aFace, TopAbs_VERTEX); + if (!exp.More()) + return new App::DocumentObjectExecReturn("Cannot extrude up to face: Sketch without points?"); + gp_Pnt aPnt = BRep_Tool::Pnt(TopoDS::Vertex(exp.Current())); + + // Create a halfspace from the face, extending in direction of sketch plane + BRepPrimAPI_MakeHalfSpace mkHalfSpace(upToFace, aPnt); + if (!mkHalfSpace.IsDone()) + return new App::DocumentObjectExecReturn("Cannot extrude up to face: HalfSpace creation failed"); + + // Find common material between halfspace and prism + BRepAlgoAPI_Common mkCommon(PrismMaker.Shape(), mkHalfSpace.Solid().Moved(invObjLoc)); + if (!mkCommon.IsDone()) + return new App::DocumentObjectExecReturn("Cannot extrude up to face: Common creation failed"); + + prism = this->getSolid(mkCommon.Shape()); + if (prism.IsNull()) + return new App::DocumentObjectExecReturn("Cannot extrude up to face: Resulting shape is not a solid"); + } else if (std::string(Type.getValueAsString()) == "ThroughAll") { + gp_Dir dir(SketchVector.x,SketchVector.y,SketchVector.z); + dir.Transform(invObjLoc.Transformation()); + BRepPrimAPI_MakePrism PrismMaker(aFace.Moved(invObjLoc),dir,1,0,1); // infinite prism (in both directions!) + if (!PrismMaker.IsDone()) + return new App::DocumentObjectExecReturn("Could not extrude the sketch!"); + prism = PrismMaker.Shape(); + } else if (std::string(Type.getValueAsString()) == "Length") { + gp_Vec vec(SketchVector.x,SketchVector.y,SketchVector.z); + vec.Transform(invObjLoc.Transformation()); + BRepPrimAPI_MakePrism PrismMaker(aFace.Moved(invObjLoc),vec,0,1); // finite prism + if (!PrismMaker.IsDone()) + return new App::DocumentObjectExecReturn("Could not extrude the sketch!"); + prism = PrismMaker.Shape(); + } else { + return new App::DocumentObjectExecReturn("Internal error: Unknown type for Pocket feature"); } + + // TODO: Set the subtractive shape property for later usage in e.g. pattern + //this->SubShape.setValue(prism); // This crashes with "Illegal storage access". Why? + + // Cut out the pocket + BRepAlgoAPI_Cut mkCut(support.Moved(invObjLoc), prism); + + // Let's check if the fusion has been successful + if (!mkCut.IsDone()) + return new App::DocumentObjectExecReturn("Cut with support failed"); + + // we have to get the solids (fuse sometimes creates compounds) + TopoDS_Shape solRes = this->getSolid(mkCut.Shape()); + if (solRes.IsNull()) + return new App::DocumentObjectExecReturn("Resulting shape is not a solid"); + + this->Shape.setValue(solRes); + + return App::DocumentObject::StdReturn; + } catch (Standard_Failure) { + Handle_Standard_Failure e = Standard_Failure::Caught(); + return new App::DocumentObjectExecReturn(e->GetMessageString()); } - else - return new App::DocumentObjectExecReturn("Could not extrude the sketch!"); - - return App::DocumentObject::StdReturn; } diff --git a/src/Mod/PartDesign/App/FeaturePocket.h b/src/Mod/PartDesign/App/FeaturePocket.h index 7a7d579628..b9b47aa72d 100644 --- a/src/Mod/PartDesign/App/FeaturePocket.h +++ b/src/Mod/PartDesign/App/FeaturePocket.h @@ -39,6 +39,7 @@ public: App::PropertyEnumeration Type; App::PropertyLength Length; + App::PropertyString FaceName; /** @name methods override feature */ //@{ diff --git a/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp b/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp index 30618f6214..2122bafd1e 100644 --- a/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp @@ -58,16 +58,39 @@ TaskPocketParameters::TaskPocketParameters(ViewProviderPocket *PocketView,QWidge connect(ui->doubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(onLengthChanged(double))); + connect(ui->changeMode, SIGNAL(currentIndexChanged(int)), + this, SLOT(onModeChanged(int))); + connect(ui->lineFaceName, SIGNAL(textEdited(QString)), + this, SLOT(onFaceName(QString))); this->groupLayout()->addWidget(proxy); PartDesign::Pocket* pcPocket = static_cast(PocketView->getObject()); double l = pcPocket->Length.getValue(); + int index = pcPocket->Type.getValue(); // must extract value here, clear() kills it! + const char* upToFace = pcPocket->FaceName.getValue(); - ui->doubleSpinBox->setMaximum(INT_MAX); - ui->doubleSpinBox->setValue(l); - ui->doubleSpinBox->selectAll(); - QMetaObject::invokeMethod(ui->doubleSpinBox, "setFocus", Qt::QueuedConnection); + ui->changeMode->clear(); + ui->changeMode->insertItem(0, tr("Dimension")); + ui->changeMode->insertItem(1, tr("To last")); + ui->changeMode->insertItem(2, tr("To first")); + ui->changeMode->insertItem(3, tr("Through all")); + ui->changeMode->insertItem(4, tr("Up to face")); + ui->changeMode->setCurrentIndex(index); + + if (index == 0) { // Only this option requires a numeric value + ui->doubleSpinBox->setMaximum(INT_MAX); + ui->doubleSpinBox->setValue(l); + ui->doubleSpinBox->selectAll(); + QMetaObject::invokeMethod(ui->doubleSpinBox, "setFocus", Qt::QueuedConnection); + ui->lineFaceName->setEnabled(false); + } else if (index == 4) { // Only this option requires to select a face + ui->doubleSpinBox->setEnabled(false); + ui->lineFaceName->setText(upToFace == "" ? tr("No face selected") : tr(upToFace)); + } else { // Neither value nor face required + ui->doubleSpinBox->setEnabled(false); + ui->lineFaceName->setEnabled(false); + } //// check if the sketch has support //Sketcher::SketchObject *pcSketch; @@ -81,6 +104,25 @@ TaskPocketParameters::TaskPocketParameters(ViewProviderPocket *PocketView,QWidge //} } +void TaskPocketParameters::onSelectionChanged(const Gui::SelectionChanges& msg) +{ + PartDesign::Pocket* pcPocket = static_cast(PocketView->getObject()); + if (pcPocket->Type.getValue() != 4) // ignore user selections if mode is not upToFace + return; + + if (!msg.pSubName || msg.pSubName[0] == '\0') + return; + std::string element(msg.pSubName); + if (element.substr(0,4) != "Face") + return; + + if (msg.Type == Gui::SelectionChanges::AddSelection) { + pcPocket->FaceName.setValue(element); + pcPocket->getDocument()->recomputeFeature(pcPocket); + ui->lineFaceName->setText(tr(element.c_str())); + } +} + void TaskPocketParameters::onLengthChanged(double len) { PartDesign::Pocket* pcPocket = static_cast(PocketView->getObject()); @@ -88,11 +130,59 @@ void TaskPocketParameters::onLengthChanged(double len) pcPocket->getDocument()->recomputeFeature(pcPocket); } +void TaskPocketParameters::onModeChanged(int index) +{ + PartDesign::Pocket* pcPocket = static_cast(PocketView->getObject()); + + switch (index) { + case 0: pcPocket->Type.setValue("Length"); break; + case 1: pcPocket->Type.setValue("UpToLast"); break; + case 2: pcPocket->Type.setValue("UpToFirst"); break; + case 3: pcPocket->Type.setValue("ThroughAll"); break; + case 4: pcPocket->Type.setValue("UpToFace"); break; + default: pcPocket->Type.setValue("Length"); + } + + if (index == 0) { + ui->doubleSpinBox->setEnabled(true); + ui->lineFaceName->setEnabled(false); + ui->doubleSpinBox->setValue(pcPocket->Length.getValue()); + } else if (index == 4) { + ui->lineFaceName->setEnabled(true); + ui->doubleSpinBox->setEnabled(false); + ui->lineFaceName->setText(tr(pcPocket->FaceName.getValue())); + } else { + ui->doubleSpinBox->setEnabled(false); + ui->lineFaceName->setEnabled(false); + } + + pcPocket->getDocument()->recomputeFeature(pcPocket); +} + +void TaskPocketParameters::onFaceName(const QString& text) +{ + if (text.left(4) != tr("Face")) + return; + + PartDesign::Pocket* pcPocket = static_cast(PocketView->getObject()); + pcPocket->FaceName.setValue(text.toUtf8()); + pcPocket->getDocument()->recomputeFeature(pcPocket); +} + double TaskPocketParameters::getLength(void) const { return ui->doubleSpinBox->value(); } +int TaskPocketParameters::getMode(void) const +{ + return ui->changeMode->currentIndex(); +} + +const QString TaskPocketParameters::getFaceName(void) const +{ + return ui->lineFaceName->text(); +} TaskPocketParameters::~TaskPocketParameters() { @@ -145,6 +235,8 @@ bool TaskDlgPocketParameters::accept() //Gui::Command::openCommand("Pocket changed"); Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.Length = %f",name.c_str(),parameter->getLength()); + Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.Type = %u",name.c_str(),parameter->getMode()); + Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.FaceName = \"%s\"",name.c_str(),parameter->getFaceName().toAscii().data()); Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.recompute()"); Gui::Command::doCommand(Gui::Command::Gui,"Gui.activeDocument().resetEdit()"); Gui::Command::commitCommand(); @@ -163,7 +255,7 @@ bool TaskDlgPocketParameters::reject() pcSupport = pcSketch->Support.getValue(); } - // role back the done things + // roll back the done things Gui::Command::abortCommand(); Gui::Command::doCommand(Gui::Command::Gui,"Gui.activeDocument().resetEdit()"); diff --git a/src/Mod/PartDesign/Gui/TaskPocketParameters.h b/src/Mod/PartDesign/Gui/TaskPocketParameters.h index 077a9ef0c9..bbb6bbb3fe 100644 --- a/src/Mod/PartDesign/Gui/TaskPocketParameters.h +++ b/src/Mod/PartDesign/Gui/TaskPocketParameters.h @@ -44,7 +44,7 @@ namespace PartDesignGui { -class TaskPocketParameters : public Gui::TaskView::TaskBox +class TaskPocketParameters : public Gui::TaskView::TaskBox, public Gui::SelectionObserver { Q_OBJECT @@ -53,14 +53,19 @@ public: ~TaskPocketParameters(); double getLength(void) const; + int getMode(void) const; + const QString getFaceName(void) const; private Q_SLOTS: void onLengthChanged(double); + void onModeChanged(int); + void onFaceName(const QString& text); protected: void changeEvent(QEvent *e); private: + void onSelectionChanged(const Gui::SelectionChanges& msg); private: QWidget* proxy; diff --git a/src/Mod/PartDesign/Gui/TaskPocketParameters.ui b/src/Mod/PartDesign/Gui/TaskPocketParameters.ui index 34c8979a17..4f313be764 100644 --- a/src/Mod/PartDesign/Gui/TaskPocketParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskPocketParameters.ui @@ -6,49 +6,97 @@ 0 0 - 137 - 68 + 241 + 134 + + + 0 + 0 + + + + + 233 + 134 + + Form - - - - - + + + + 10 + 10 + 211 + 34 + + + + + + + Type + + + + + + - Type: + Dimension - - - - - - - Dimension - - - - - - - - - - - - Length - - - - - - - - - + + + + + + + + + 10 + 90 + 211 + 34 + + + + + + + Face + + + + + + + + + + + + 10 + 50 + 211 + 34 + + + + + + + Length + + + + + + + +