diff --git a/src/Mod/PartDesign/Gui/Command.cpp b/src/Mod/PartDesign/Gui/Command.cpp index a36619ef4d..e62037e8bf 100644 --- a/src/Mod/PartDesign/Gui/Command.cpp +++ b/src/Mod/PartDesign/Gui/Command.cpp @@ -315,13 +315,26 @@ void CmdPartDesignNewSketch::activated(int iMsg) { Q_UNUSED(iMsg); App::Document *doc = getDocument (); - PartDesign::Body *pcActiveBody = PartDesignGui::getBody( - /*messageIfNot = */ PartDesignGui::assureModernWorkflow ( doc ) ); + PartDesign::Body *pcActiveBody( nullptr ); + auto shouldMakeBody( false ); - // No PartDesign feature without Body past FreeCAD 0.13 - if ( !pcActiveBody ) { - // Call normal sketch command for old workflow - if ( PartDesignGui::isLegacyWorkflow ( doc) ) { + if ( PartDesignGui::assureModernWorkflow( doc ) ) { + // We need either an active Body, or for there to be no Body + // objects (in which case, just make one) to make a new sketch. + + pcActiveBody = PartDesignGui::getBody( /* messageIfNot = */ false ); + if (pcActiveBody == nullptr) { + if ( doc->getObjectsOfType(PartDesign::Body::getClassTypeId()).empty() ) { + shouldMakeBody = true; + } else { + PartDesignGui::needActiveBodyError(); + return; + } + } + + } else { + // No PartDesign feature without Body past FreeCAD 0.13 + if ( PartDesignGui::isLegacyWorkflow( doc ) ) { Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); rcCmdMgr.runCommandByName("Sketcher_NewSketch"); } @@ -332,6 +345,7 @@ void CmdPartDesignNewSketch::activated(int iMsg) Gui::SelectionFilter FaceFilter ("SELECT Part::Feature SUBELEMENT Face COUNT 1"); Gui::SelectionFilter PlaneFilter ("SELECT App::Plane COUNT 1"); Gui::SelectionFilter PlaneFilter2("SELECT PartDesign::Plane COUNT 1"); + if (PlaneFilter2.match()) PlaneFilter = PlaneFilter2; @@ -340,7 +354,14 @@ void CmdPartDesignNewSketch::activated(int iMsg) openCommand("Edit Sketch"); doCommand(Gui,"Gui.activeDocument().setEdit('%s')",Sketch->getNameInDocument()); } - else if (FaceFilter.match() || PlaneFilter.match()) { + else if ( FaceFilter.match() || PlaneFilter.match() ) { + if (!pcActiveBody) { + // We shouldn't make a new Body in this case, because that means + // the source shape of the face/plane would be outside the Body. + PartDesignGui::getBody( /* messageIfNot = */ true ); + return; + } + // get the selected object std::string supportString; App::DocumentObject* obj; @@ -385,6 +406,7 @@ void CmdPartDesignNewSketch::activated(int iMsg) supportString = std::string("(App.activeDocument().") + obj->getNameInDocument() + ", '')"; } + if (!pcActiveBody->hasObject(obj)) { if ( !obj->isDerivedFrom ( App::Plane::getClassTypeId() ) ) { // TODO check here if the plane associated with right part/body (2015-09-01, Fat-Zer) @@ -436,39 +458,49 @@ void CmdPartDesignNewSketch::activated(int iMsg) doCommand(Gui,"Gui.activeDocument().setEdit('%s')",FeatName.c_str()); } else { - // Get a valid plane from the user - unsigned validPlanes = 0; - - auto group = App::GeoFeatureGroupExtension::getGroupOfObject ( pcActiveBody ); - App::GeoFeatureGroupExtension* geoGroup = nullptr; - if(group) - geoGroup = group->getExtensionByType(); + App::GeoFeatureGroupExtension *geoGroup( nullptr ); + if (pcActiveBody) { + auto group( App::GeoFeatureGroupExtension::getGroupOfObject(pcActiveBody) ); + if (group) { + geoGroup = group->getExtensionByType(); + } + } std::vector planes; std::vector status; - // Baseplanes are preaprooved - if ( pcActiveBody ) { - try { - for ( auto plane: pcActiveBody->getOrigin ()->planes() ) { - planes.push_back (plane); - status.push_back(PartDesignGui::TaskFeaturePick::basePlane); - validPlanes++; - } - } catch (const Base::Exception &ex) { - Base::Console().Error ("%s\n", ex.what() ); + // Start command early, so undo will undo any Body creation + Gui::Command::openCommand("Create a new Sketch"); + if (shouldMakeBody) { + pcActiveBody = PartDesignGui::makeBody(doc); + if ( !pcActiveBody ) { + Base::Console().Error("Failed to create a Body object"); + return; } } - std::vector datumPlanes = - getDocument()->getObjectsOfType(PartDesign::Plane::getClassTypeId()); + // At this point, we have pcActiveBody + unsigned validPlaneCount = 0; + + // Baseplanes are preaprooved + try { + for ( auto plane: pcActiveBody->getOrigin ()->planes() ) { + planes.push_back (plane); + status.push_back(PartDesignGui::TaskFeaturePick::basePlane); + validPlaneCount++; + } + } catch (const Base::Exception &ex) { + Base::Console().Error ("%s\n", ex.what() ); + } + + auto datumPlanes( getDocument()->getObjectsOfType(PartDesign::Plane::getClassTypeId()) ); for (auto plane: datumPlanes) { planes.push_back ( plane ); // Check whether this plane belongs to the active body if ( pcActiveBody && pcActiveBody->hasObject(plane) ) { if ( !pcActiveBody->isAfterInsertPoint ( plane ) ) { - validPlanes++; + validPlaneCount++; status.push_back(PartDesignGui::TaskFeaturePick::validFeature); } else { status.push_back(PartDesignGui::TaskFeaturePick::afterTip); @@ -489,34 +521,25 @@ void CmdPartDesignNewSketch::activated(int iMsg) } else if (pcActiveBody) { status.push_back ( PartDesignGui::TaskFeaturePick::notInBody ); } else { // if we are outside a body count it as valid - validPlanes++; + validPlaneCount++; status.push_back(PartDesignGui::TaskFeaturePick::validFeature); } } } } - if (validPlanes == 0) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("No valid planes in this document"), - QObject::tr("Please create a plane first or select a face to sketch on")); - return; - } - - auto accepter = [=](const std::vector& features) -> bool { - - if(features.empty()) - return false; - - return true; + // Determines if user made a valid selection in dialog + auto accepter = [](const std::vector& features) -> bool { + return !features.empty(); }; + // Called by dialog when user hits "OK" and accepter returns true auto worker = [=](const std::vector& features) { App::Plane* plane = static_cast(features.front()); std::string FeatName = getUniqueObjectName("Sketch"); std::string supportString = std::string("(App.activeDocument().") + plane->getNameInDocument() + ", [''])"; - Gui::Command::openCommand("Create a new Sketch"); Gui::Command::doCommand(Doc,"App.activeDocument().addObject('Sketcher::SketchObject','%s')",FeatName.c_str()); Gui::Command::doCommand(Doc,"App.activeDocument().%s.Support = %s",FeatName.c_str(),supportString.c_str()); Gui::Command::doCommand(Doc,"App.activeDocument().%s.MapMode = '%s'",FeatName.c_str(),Attacher::AttachEngine::getModeName(Attacher::mmFlatFace).c_str()); @@ -527,9 +550,20 @@ void CmdPartDesignNewSketch::activated(int iMsg) Gui::Command::doCommand(Gui,"Gui.activeDocument().setEdit('%s')",FeatName.c_str()); }; - // If there is more than one possibility, show dialog and let user pick plane - if (validPlanes > 1) { + // Called by dialog for "Cancel", or "OK" iff accepter returns false + auto quitter( Gui::Command::abortCommand ); + if (validPlaneCount == 0) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("No valid planes in this document"), + QObject::tr("Please create a plane first or select a face to sketch on")); + quitter(); + return; + + } else if (validPlaneCount == 1) { + worker(planes); + + } else if (validPlaneCount > 1) { + // Show dialog and let user pick plane Gui::TaskView::TaskDialog *dlg = Gui::Control().activeDialog(); PartDesignGui::TaskDlgFeaturePick *pickDlg = qobject_cast(dlg); if (dlg && !pickDlg) { @@ -541,18 +575,17 @@ void CmdPartDesignNewSketch::activated(int iMsg) int ret = msgBox.exec(); if (ret == QMessageBox::Yes) Gui::Control().closeDialog(); - else + else { + quitter(); return; + } } if(dlg) Gui::Control().closeDialog(); Gui::Selection().clearSelection(); - Gui::Control().showDialog(new PartDesignGui::TaskDlgFeaturePick(planes, status, accepter, worker)); - } - else { - worker(planes); + Gui::Control().showDialog(new PartDesignGui::TaskDlgFeaturePick(planes, status, accepter, worker, quitter)); } } } diff --git a/src/Mod/PartDesign/Gui/CommandPrimitive.cpp b/src/Mod/PartDesign/Gui/CommandPrimitive.cpp index 788eaa703f..6ecc7d69c9 100644 --- a/src/Mod/PartDesign/Gui/CommandPrimitive.cpp +++ b/src/Mod/PartDesign/Gui/CommandPrimitive.cpp @@ -79,10 +79,20 @@ void CmdPrimtiveCompAdditive::activated(int iMsg) if (!PartDesignGui::assureModernWorkflow(doc)) return; - PartDesign::Body *pcActiveBody = PartDesignGui::getBody(true); + // We need either an active Body, or for there to be no Body objects + // (in which case, just make one) to make a new additive shape. - if (!pcActiveBody) - return; + PartDesign::Body *pcActiveBody = PartDesignGui::getBody( /* messageIfNot = */ false ); + + auto shouldMakeBody( false ); + if (pcActiveBody == nullptr) { + if ( doc->getObjectsOfType(PartDesign::Body::getClassTypeId()).empty() ) { + shouldMakeBody = true; + } else { + PartDesignGui::needActiveBodyError(); + return; + } + } Gui::ActionGroup* pcAction = qobject_cast(_pcAction); pcAction->setIcon(pcAction->actions().at(iMsg)->icon()); @@ -91,6 +101,10 @@ void CmdPrimtiveCompAdditive::activated(int iMsg) auto FeatName( getUniqueObjectName(shapeType) ); Gui::Command::openCommand( (std::string("Make additive ") + shapeType).c_str() ); + if (shouldMakeBody) { + pcActiveBody = PartDesignGui::makeBody(doc); + } + Gui::Command::doCommand( Gui::Command::Doc, "App.ActiveDocument.addObject(\'PartDesign::Additive%s\',\'%s\')", diff --git a/src/Mod/PartDesign/Gui/Utils.cpp b/src/Mod/PartDesign/Gui/Utils.cpp index 4e3740322a..0b0ed74581 100644 --- a/src/Mod/PartDesign/Gui/Utils.cpp +++ b/src/Mod/PartDesign/Gui/Utils.cpp @@ -82,6 +82,30 @@ PartDesign::Body *getBody(bool messageIfNot) return activeBody; } +void needActiveBodyError(void) +{ + QMessageBox::warning( Gui::getMainWindow(), + QObject::tr("Active Body Required"), + QObject::tr("To create a new PartDesign object, there must be " + "an active Body object in the document. Please make " + "one active (double click) or create a new Body.") ); +} + +PartDesign::Body * makeBody(App::Document *doc) +{ + // This is intended as a convenience when starting a new document. + auto bodyName( doc->getUniqueObjectName("Body") ); + Gui::Command::doCommand( Gui::Command::Doc, + "App.activeDocument().addObject('PartDesign::Body','%s')", + bodyName.c_str() ); + Gui::Command::doCommand( Gui::Command::Gui, + "Gui.activeView().setActiveObject('%s', App.activeDocument().%s)", + PDBODYKEY, bodyName.c_str() ); + + auto activeView( Gui::Application::Instance->activeView() ); + return activeView->getActiveObject(PDBODYKEY); +} + PartDesign::Body *getBodyFor(const App::DocumentObject* obj, bool messageIfNot) { if(!obj) diff --git a/src/Mod/PartDesign/Gui/Utils.h b/src/Mod/PartDesign/Gui/Utils.h index 920ed19c35..b0b428d460 100644 --- a/src/Mod/PartDesign/Gui/Utils.h +++ b/src/Mod/PartDesign/Gui/Utils.h @@ -32,6 +32,7 @@ namespace PartDesign { } namespace App { + class Document; class DocumentObject; class Part; } @@ -44,6 +45,13 @@ namespace PartDesignGui { /// Return active body or show a warning message PartDesign::Body *getBody(bool messageIfNot); + +/// Display error when there are existing Body objects, but none are active +void needActiveBodyError(void); + +/// Create a Body object in doc, set it active, and return pointer to it +PartDesign::Body * makeBody(App::Document *doc); + /** * Finds a body for the given feature. And shows a message if not found * Also unlike Body::findBodyFor it checks if the active body has the feature first.