diff --git a/src/Mod/Part/App/Part2DObject.h b/src/Mod/Part/App/Part2DObject.h index 3c7a62b37..c37c31d37 100644 --- a/src/Mod/Part/App/Part2DObject.h +++ b/src/Mod/Part/App/Part2DObject.h @@ -29,7 +29,7 @@ #include #include "PartFeature.h" -#include "Attacher.h" +#include "AttachableObject.h" class TopoDS_Face; diff --git a/src/Mod/Sketcher/Gui/Command.cpp b/src/Mod/Sketcher/Gui/Command.cpp index 1b087cfd0..57aba056a 100644 --- a/src/Mod/Sketcher/Gui/Command.cpp +++ b/src/Mod/Sketcher/Gui/Command.cpp @@ -33,6 +33,7 @@ #endif #include +#include #include #include #include @@ -43,6 +44,7 @@ #include #include +#include #include #include "SketchOrientationDialog.h" @@ -55,6 +57,112 @@ using namespace std; using namespace SketcherGui; using namespace Part; + +namespace SketcherGui { + + class ExceptionWrongInput : public Base::Exception { + public: + ExceptionWrongInput() + : ErrMsg(QString()) + { + + } + + //Pass untranslated strings, enclosed in QT_TR_NOOP() + ExceptionWrongInput(const char* ErrMsg){ + this->ErrMsg = QObject::tr( ErrMsg ); + this->setMessage(ErrMsg); + } + + QString ErrMsg; + }; + + /** + * @brief Selection2LinkSubList converts selection into App::PropertyLinkSubList. + * Throws ExceptionWrongInput if fails. + * @param selection input + * @param support output + */ + void Selection2LinkSubList(const Gui::SelectionSingleton &selection, + App::PropertyLinkSubList &support){ + std::vector sel = selection.getSelectionEx(); + std::vector objs; objs.reserve(sel.size()*2); + std::vector subs; subs.reserve(sel.size()*2); + for( int iobj = 0 ; iobj < sel.size() ; iobj++ ){ + Gui::SelectionObject &selitem = sel[iobj]; + App::DocumentObject* obj = selitem.getObject(); + if (!( obj->isDerivedFrom(Part::Feature::getClassTypeId()) + || obj->isDerivedFrom(App::Plane::getClassTypeId()) )) + throw ExceptionWrongInput(QT_TR_NOOP("Object of wrong type is selected, it is not suitable as a support")); + const std::vector &subnames = selitem.getSubNames(); + if (subnames.size() == 0){//whole object is selected + objs.push_back(obj); + subs.push_back(std::string()); + } else { + for( int isub = 0 ; isub < subnames.size() ; isub++ ){ + objs.push_back(obj); + subs.push_back(subnames[isub]); + } + } + } + assert(objs.size()==subs.size()); + support.setValues(objs, subs); + } + + Attacher::eMapMode SuggestAutoMapMode(Attacher::eSuggestResult* pMsgId = 0, + QString* message = 0, + std::vector* allmodes = 0){ + //convert pointers into valid references, to avoid checking for null pointers everywhere + Attacher::eSuggestResult buf; + if (pMsgId == 0) + pMsgId = &buf; + Attacher::eSuggestResult &msg = *pMsgId; + QString buf2; + if (message == 0) + message = &buf2; + QString &msg_str = *message; + + App::PropertyLinkSubList tmpSupport; + try{ + Selection2LinkSubList(Gui::Selection(), tmpSupport); + } catch (ExceptionWrongInput &e ){ + msg = Attacher::srLinkBroken; + msg_str = e.ErrMsg; + return Attacher::mmDeactivated; + } + + AttachEngine3D eng; + eng.setUp(tmpSupport); + Attacher::eMapMode ret; + ret = eng.listMapModes(msg, allmodes); + switch(msg){ + case Attacher::srOK: + break; + case Attacher::srNoModesFit: + msg_str = QObject::tr("There are no modes that accept the selected set of subelements"); + break; + case Attacher::srLinkBroken: + msg_str = QObject::tr("Broken link to support subelements"); + break; + case Attacher::srUnexpectedError: + msg_str = QObject::tr("Unexpected error"); + break; + case Attacher::srIncompatibleGeometry: + if(tmpSupport.getSubValues()[0].substr(0,4) == std::string("Face")) + msg_str = QObject::tr("Face is non-planar"); + else + msg_str = QObject::tr("Selected shapes are of wrong form (e.g., a curved edge where a straight one is needed)"); + break; + default: + msg_str = QObject::tr("Unexpected error"); + assert(0/*no message for eSuggestResult enum item*/); + } + + return ret; + } +} //namespace SketcherGui + + /* Sketch commands =======================================================*/ DEF_STD_CMD_A(CmdSketcherNewSketch); @@ -72,59 +180,77 @@ CmdSketcherNewSketch::CmdSketcherNewSketch() void CmdSketcherNewSketch::activated(int iMsg) { - Gui::SelectionFilter FaceFilter ("SELECT Part::Feature SUBELEMENT Face COUNT 1"); - - if (FaceFilter.match()) { - // get the selected object - Part::Feature *part = static_cast(FaceFilter.Result[0][0].getObject()); - Base::Placement ObjectPos = part->Placement.getValue(); - const std::vector &sub = FaceFilter.Result[0][0].getSubNames(); - if (sub.empty()) { - // No assert for wrong user input! + Attacher::eMapMode mapmode = Attacher::mmDeactivated; + bool bAttach = false; + if (Gui::Selection().hasSelection()){ + Attacher::eSuggestResult msgid = Attacher::srOK; + QString msg_str; + std::vector validModes; + mapmode = SuggestAutoMapMode(&msgid, &msg_str, &validModes); + if (msgid == Attacher::srOK) + bAttach = true; + if (msgid != Attacher::srOK && msgid != Attacher::srNoModesFit){ QMessageBox::warning(Gui::getMainWindow(), - qApp->translate(className(),"No sub-elements selected"), - qApp->translate(className(),"You have to select a single face as support for a sketch!")); + QObject::tr("Sketch mapping"), + QObject::tr("Can't map the skecth to selected object. %1.").arg(msg_str)); return; } - else if (sub.size() > 1) { - // No assert for wrong user input! - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Several sub-elements selected"), - QObject::tr("You have to select a single face as support for a sketch!")); - return; - } - // get the selected sub shape (a Face) - const Part::TopoShape &shape = part->Shape.getValue(); - TopoDS_Shape sh = shape.getSubShape(sub[0].c_str()); - const TopoDS_Face& face = TopoDS::Face(sh); - if (face.IsNull()){ - // No assert for wrong user input! - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("No support face selected"), - QObject::tr("You have to select a face as support for a sketch!")); - return; + if (validModes.size() > 1){ + validModes.insert(validModes.begin(), Attacher::mmDeactivated); + bool ok; + QStringList items; + items.push_back(QObject::tr("Don't attach")); + int iSugg = 0;//index of the auto-suggested mode in the list of valid modes + for (int i = 0 ; i < validModes.size() ; ++i){ + items.push_back(QString::fromLatin1(AttachEngine::eMapModeStrings[validModes[i]])); + if (validModes[i] == mapmode) + iSugg = items.size()-1; + } + QString text = QInputDialog::getItem(Gui::getMainWindow(), + qApp->translate(className(), "Sketch attachment"), + qApp->translate(className(), "Select the method to attach this sketch to selected object"), + items, iSugg, false, &ok); + if (!ok) return; + int index = items.indexOf(text); + if (index == 0){ + bAttach = false; + mapmode = Attacher::mmDeactivated; + } else { + bAttach = true; + mapmode = validModes[index-1]; + } } + } - BRepAdaptor_Surface adapt(face); - if (adapt.GetType() != GeomAbs_Plane){ - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("No planar support"), - QObject::tr("You need a planar face as support for a sketch!")); - return; - } + if (bAttach) { - std::string supportString = FaceFilter.Result[0][0].getAsPropertyLinkSubString(); + std::vector objects = Gui::Selection().getSelectionEx(); + //assert (objects.size() == 1); //should have been filtered out by SuggestAutoMapMode + //Gui::SelectionObject &sel_support = objects[0]; + App::PropertyLinkSubList support; + Selection2LinkSubList(Gui::Selection(),support); + std::string supportString = support.getPyReprString(); // create Sketch on Face std::string FeatName = getUniqueObjectName("Sketch"); openCommand("Create a Sketch on Face"); doCommand(Doc,"App.activeDocument().addObject('Sketcher::SketchObject','%s')",FeatName.c_str()); + if (mapmode >= 0 && mapmode < Attacher::mmDummy_NumberOfModes) + doCommand(Gui,"App.activeDocument().%s.MapMode = \"%s\"",FeatName.c_str(),AttachEngine::eMapModeStrings[mapmode]); + else + assert(0 /* mapmode index out of range */); doCommand(Gui,"App.activeDocument().%s.Support = %s",FeatName.c_str(),supportString.c_str()); doCommand(Gui,"App.activeDocument().recompute()"); // recompute the sketch placement based on its support - //doCommand(Gui,"Gui.activeDocument().activeView().setCamera('%s')",cam.c_str()); doCommand(Gui,"Gui.activeDocument().setEdit('%s')",FeatName.c_str()); - App::DocumentObjectGroup* grp = part->getGroup(); - if (grp) { - doCommand(Doc,"App.activeDocument().%s.addObject(App.activeDocument().%s)" - ,grp->getNameInDocument(),FeatName.c_str()); + + Part::Feature *part = static_cast(support.getValue());//if multi-part support, this will return 0 + if (part){ + App::DocumentObjectGroup* grp = part->getGroup(); + if (grp) { + doCommand(Doc,"App.activeDocument().%s.addObject(App.activeDocument().%s)" + ,grp->getNameInDocument(),FeatName.c_str()); + } } } else { @@ -163,6 +289,7 @@ void CmdSketcherNewSketch::activated(int iMsg) openCommand("Create a new Sketch"); doCommand(Doc,"App.activeDocument().addObject('Sketcher::SketchObject','%s')",FeatName.c_str()); doCommand(Doc,"App.activeDocument().%s.Placement = App.Placement(App.Vector(%f,%f,%f),App.Rotation(%f,%f,%f,%f))",FeatName.c_str(),p.x,p.y,p.z,r[0],r[1],r[2],r[3]); + doCommand(Doc,"App.activeDocument().%s.MapMode = \"%s\"",FeatName.c_str(),AttachEngine::eMapModeStrings[int(Attacher::mmDeactivated)]); doCommand(Gui,"Gui.activeDocument().activeView().setCamera('%s')",camstring.c_str()); doCommand(Gui,"Gui.activeDocument().setEdit('%s')",FeatName.c_str()); } @@ -351,103 +478,141 @@ CmdSketcherMapSketch::CmdSketcherMapSketch() void CmdSketcherMapSketch::activated(int iMsg) { - App::Document* doc = App::GetApplication().getActiveDocument(); - std::vector sel = doc->getObjectsOfType(Sketcher::SketchObject::getClassTypeId()); - if (sel.empty()) { + QString msg_str; + try{ + Attacher::eMapMode suggMapMode; + std::vector validModes; + + //check that selection is valid for at least some mapping mode. + Attacher::eSuggestResult msgid = Attacher::srOK; + suggMapMode = SuggestAutoMapMode(&msgid, &msg_str, &validModes); + + App::Document* doc = App::GetApplication().getActiveDocument(); + std::vector sketches = doc->getObjectsOfType(Part::Part2DObject::getClassTypeId()); + if (sketches.empty()) { + QMessageBox::warning(Gui::getMainWindow(), + qApp->translate(className(), "No sketch found"), + qApp->translate(className(), "The document doesn't have a sketch")); + return; + } + + bool ok; + QStringList items; + for (std::vector::iterator it = sketches.begin(); it != sketches.end(); ++it) + items.push_back(QString::fromUtf8((*it)->Label.getValue())); + QString text = QInputDialog::getItem(Gui::getMainWindow(), + qApp->translate(className(), "Select sketch"), + qApp->translate(className(), "Select a sketch from the list"), + items, 0, false, &ok); + if (!ok) return; + int index = items.indexOf(text); + Part2DObject &sketch = *(static_cast(sketches[index])); + + + // check circular dependency + std::vector selobjs = Gui::Selection().getSelectionEx(); + for( int i = 0 ; i < selobjs.size() ; i++){ + App::DocumentObject* part = static_cast(selobjs[i].getObject()); + if (!part) { + assert(0); + throw Base::Exception("Unexpected null pointer in CmdSketcherMapSketch::activated"); + } + std::vector input = part->getOutList(); + if (std::find(input.begin(), input.end(), &sketch) != input.end()) { + throw ExceptionWrongInput(QT_TR_NOOP("Some of the selected objects depend on the sketch to be mapped. Circular dependencies are not allowed!")); + } + } + + //Ask for a new mode. + //outline: + // * find out the modes that are compatible with selection. + // * Test if current mode is OK. + // * fill in the dialog + // * execute the dialog + // * collect dialog result + // * action + + bool bAttach = true; + bool bCurIncompatible = false; + // * find out the modes that are compatible with selection. + eMapMode curMapMode = eMapMode(sketch.MapMode.getValue()); + // * Test if current mode is OK. + if (std::find(validModes.begin(), validModes.end(), curMapMode) == validModes.end()) + bCurIncompatible = true; + + // * fill in the dialog + validModes.insert(validModes.begin(), Attacher::mmDeactivated); + if(bCurIncompatible) + validModes.push_back(curMapMode); + //bool ok; //already defined + //QStringList items; //already defined + items.clear(); + items.push_back(QObject::tr("Don't attach")); + int iSugg = 0;//index of the auto-suggested mode in the list of valid modes + int iCurr = 0;//index of current mode in the list of valid modes + for (int i = 0 ; i < validModes.size() ; ++i){ + items.push_back(QString::fromLatin1(AttachEngine::eMapModeStrings[validModes[i]])); + if (validModes[i] == curMapMode) { + iCurr = items.size() - 1; + items.back().append(bCurIncompatible? + qApp->translate(className()," (incompatible with selection)") + : + qApp->translate(className()," (current)") ); + } + if (validModes[i] == suggMapMode){ + iSugg = items.size() - 1; + if(iSugg == 1){ + iSugg = 0;//redirect deactivate to detach + } else { + items.back().append(qApp->translate(className()," (suggested)")); + } + } + } + // * execute the dialog + text = QInputDialog::getItem(Gui::getMainWindow() + ,qApp->translate(className(), "Sketch attachment") + ,bCurIncompatible? + qApp->translate(className(), "Current attachment mode is incompatible with the new selection. Select the method to attach this sketch to selected objects.") + : + qApp->translate(className(), "Select the method to attach this sketch to selected objects.") + ,items + , bCurIncompatible ? iSugg : iCurr + , false + , &ok); + // * collect dialog result + if (!ok) return; + index = items.indexOf(text); + if (index == 0){ + bAttach = false; + suggMapMode = Attacher::mmDeactivated; + } else { + bAttach = true; + suggMapMode = validModes[index-1]; + } + + // * action + std::string featName = sketch.getNameInDocument(); + if (bAttach) { + App::PropertyLinkSubList support; + Selection2LinkSubList(Gui::Selection(),support); + std::string supportString = support.getPyReprString(); + + openCommand("Attach Sketch"); + doCommand(Gui,"App.activeDocument().%s.MapMode = \"%s\"",featName.c_str(),AttachEngine::eMapModeStrings[suggMapMode]); + doCommand(Gui,"App.activeDocument().%s.Support = %s",featName.c_str(),supportString.c_str()); + commitCommand(); + } else { + openCommand("Detach Sketch"); + doCommand(Gui,"App.activeDocument().%s.MapMode = \"%s\"",featName.c_str(),AttachEngine::eMapModeStrings[suggMapMode]); + doCommand(Gui,"App.activeDocument().%s.Support = None",featName.c_str()); + commitCommand(); + } + } catch (ExceptionWrongInput &e) { QMessageBox::warning(Gui::getMainWindow(), - qApp->translate(className(), "No sketch found"), - qApp->translate(className(), "The document doesn't have a sketch")); - return; + qApp->translate(className(), "Map sketch"), + qApp->translate(className(), "Can't map a sketch to support:\n" + "%1").arg(e.ErrMsg.length() ? e.ErrMsg : msg_str)); } - - bool ok; - QStringList items; - for (std::vector::iterator it = sel.begin(); it != sel.end(); ++it) - items.push_back(QString::fromUtf8((*it)->Label.getValue())); - QString text = QInputDialog::getItem(Gui::getMainWindow(), - qApp->translate(className(), "Select sketch"), - qApp->translate(className(), "Select a sketch from the list"), - items, 0, false, &ok); - if (!ok) return; - int index = items.indexOf(text); - - std::string featName = sel[index]->getNameInDocument(); - Gui::SelectionFilter FaceFilter ("SELECT Part::Feature SUBELEMENT Face COUNT 1"); - Gui::SelectionFilter PlaneFilter ("SELECT PartDesign::Plane COUNT 1"); - Gui::SelectionFilter BasePlaneFilter ("SELECT App::Plane COUNT 1"); - - std::string supportString; - Part::Feature *part; - - if (FaceFilter.match()) { - // get the selected object - part = static_cast(FaceFilter.Result[0][0].getObject()); - const std::vector &sub = FaceFilter.Result[0][0].getSubNames(); - if (sub.empty()) { - // No assert for wrong user input! - QMessageBox::warning(Gui::getMainWindow(), - qApp->translate(className(),"No sub-elements selected"), - qApp->translate(className(),"You have to select a single face as support for a sketch!")); - return; - } - else if (sub.size() > 1) { - // No assert for wrong user input! - QMessageBox::warning(Gui::getMainWindow(), - qApp->translate(className(),"Several sub-elements selected"), - qApp->translate(className(),"You have to select a single face as support for a sketch!")); - return; - } - - // get the selected sub shape (a Face) - const Part::TopoShape &shape = part->Shape.getValue(); - TopoDS_Shape sh = shape.getSubShape(sub[0].c_str()); - const TopoDS_Face& face = TopoDS::Face(sh); - if (face.IsNull()) { - // No assert for wrong user input! - QMessageBox::warning(Gui::getMainWindow(), - qApp->translate(className(),"No support face selected"), - qApp->translate(className(),"You have to select a face as support for a sketch!")); - return; - } - - BRepAdaptor_Surface adapt(face); - if (adapt.GetType() != GeomAbs_Plane){ - QMessageBox::warning(Gui::getMainWindow(), - qApp->translate(className(),"No planar support"), - qApp->translate(className(),"You need a planar face as support for a sketch!")); - return; - } - - supportString = FaceFilter.Result[0][0].getAsPropertyLinkSubString(); - } else if (PlaneFilter.match()) { - part = static_cast(PlaneFilter.Result[0][0].getObject()); - supportString = std::string("(App.activeDocument().") + - part->getNameInDocument() + ", ['front'])"; - } else if (BasePlaneFilter.match()) { - part = NULL; - supportString = std::string("(App.activeDocument().") + - BasePlaneFilter.Result[0][0].getObject()->getNameInDocument() + ", ['front'])"; - } else { - QMessageBox::warning(Gui::getMainWindow(), - qApp->translate(className(), "No face or plane selected"), - qApp->translate(className(), "No face or datum plane was selected to map the sketch to")); - return; - } - - if (part != NULL) { - std::vector input = part->getOutList(); - if (std::find(input.begin(), input.end(), sel[index]) != input.end()) { - QMessageBox::warning(Gui::getMainWindow(), - qApp->translate(className(),"Cyclic dependency"), - qApp->translate(className(),"You cannot choose a support object depending on the selected sketch!")); - return; - } - } - - openCommand("Map a Sketch on Face"); - doCommand(Gui,"App.activeDocument().%s.Support = %s",featName.c_str(),supportString.c_str()); - doCommand(Gui,"App.activeDocument().recompute()"); - doCommand(Gui,"Gui.activeDocument().setEdit('%s')",featName.c_str()); } bool CmdSketcherMapSketch::isActive(void)