diff --git a/src/Gui/SoFCUnifiedSelection.cpp b/src/Gui/SoFCUnifiedSelection.cpp index da5b44abcc..87d9202d3f 100644 --- a/src/Gui/SoFCUnifiedSelection.cpp +++ b/src/Gui/SoFCUnifiedSelection.cpp @@ -546,13 +546,13 @@ bool SoFCUnifiedSelection::setHighlight(SoFullPath *path, const SoDetail *det, } bool SoFCUnifiedSelection::setSelection(const std::vector &infos, bool ctrlDown) { - if(infos.empty() || !infos[0].vpd) + if (infos.empty() || !infos[0].vpd) return false; std::vector sels; - if(infos.size()>1) { - for(auto &info : infos) { - if(!info.vpd) continue; + if (infos.size() > 1) { + for (auto &info: infos) { + if (!info.vpd) continue; SelectionSingleton::SelObj sel; sel.pResolvedObject = nullptr; @@ -572,125 +572,132 @@ bool SoFCUnifiedSelection::setSelection(const std::vector &infos, bo const auto &info = infos[0]; auto vpd = info.vpd; - if(!vpd) + if (!vpd) return false; - if(!vpd->getObject()->isAttachedToDocument()) + if (!vpd->getObject()->isAttachedToDocument()) return false; const char *objname = vpd->getObject()->getNameInDocument(); const char *docname = vpd->getObject()->getDocument()->getName(); + auto getFullSubElementName = [vpd](std::string &subName) { + App::ElementNamePair elementName; + App::GeoFeature::resolveElement(vpd->getObject(), subName.c_str(), elementName); + if (!elementName.newName.empty()) { // If we have a mapped name use it + auto elementNameSuffix = Data::findElementName(subName.c_str()); // Only suffix + subName.erase(subName.find(elementNameSuffix)); // Everything except original suffix + subName = subName.append(elementName.newName); // Add the mapped name suffix, + } + }; + bool hasNext = false; - const SoPickedPoint * pp = info.pp; + const SoPickedPoint *pp = info.pp; const SoDetail *det = pp->getDetail(); SoDetail *detNext = nullptr; - auto pPath = static_cast(pp->getPath()); + auto pPath = static_cast(pp->getPath()); const auto &pt = pp->getPoint(); SoSelectionElementAction::Type type = SoSelectionElementAction::None; auto mymode = static_cast(this->highlightMode.getValue()); static char buf[513]; + auto subName = info.element; + std::string objectName = objname; if (ctrlDown) { - if(Gui::Selection().isSelected(docname, objname, info.element.c_str(), ResolveMode::NoResolve)) - Gui::Selection().rmvSelection(docname, objname,info.element.c_str(), &sels); + if (Gui::Selection().isSelected(docname, objname, info.element.c_str(), ResolveMode::NoResolve)) + Gui::Selection().rmvSelection(docname, objname, info.element.c_str(), &sels); else { - bool ok = Gui::Selection().addSelection(docname,objname, - info.element.c_str(), pt[0] ,pt[1] ,pt[2], &sels); + getFullSubElementName(subName); + bool ok = Gui::Selection().addSelection(docname, objname, + subName.c_str(), pt[0], pt[1], pt[2], &sels); if (ok && mymode == OFF) { - snprintf(buf,512,"Selected: %s.%s.%s (%g, %g, %g)", - docname,objname,info.element.c_str() - ,fabs(pt[0])>1e-7?pt[0]:0.0 - ,fabs(pt[1])>1e-7?pt[1]:0.0 - ,fabs(pt[2])>1e-7?pt[2]:0.0); + snprintf(buf, 512, "Selected: %s.%s.%s (%g, %g, %g)", + docname, objname, info.element.c_str(), fabs(pt[0]) > 1e-7 ? pt[0] : 0.0, + fabs(pt[1]) > 1e-7 ? pt[1] : 0.0, fabs(pt[2]) > 1e-7 ? pt[2] : 0.0); getMainWindow()->showMessage(QString::fromLatin1(buf)); } - } - return true; - } - - // Hierarchy ascending - // - // If the clicked subelement is already selected, check if there is an - // upper hierarchy, and select that hierarchy instead. - // - // For example, let's suppose PickedInfo above reports - // 'link.link2.box.Face1', and below Selection().getSelectedElement returns - // 'link.link2.box.', meaning that 'box' is the current selected hierarchy, - // and the user is clicking the box again. So we shall go up one level, - // and select 'link.link2.' - // - - std::string subName = info.element; - std::string objectName = objname; - - // We need to convert the short name in the selection to a full element path to look it up - // Ex: Body.Pad.Face9 to Body.Pad.;g3;SKT;:H12dc,E;FAC;:H12dc:4,F;:G0;XTR;:H12dc:8,F.Face9 - App::ElementNamePair elementName; - App::GeoFeature::resolveElement(vpd->getObject(), subName.c_str(), elementName); - if ( !elementName.newName.empty()) { // If we have a mapped name use it - auto elementNameSuffix = Data::findElementName(subName.c_str()); // Only suffix - subName.erase(subName.find(elementNameSuffix)); // Everything except original suffix suffix - subName = subName.append(elementName.newName); // Add the mapped name suffix, - } - const char *subSelected = Gui::Selection().getSelectedElement( - vpd->getObject(),subName.c_str()); - - FC_TRACE("select " << (subSelected?subSelected:"'null'") << ", " << - objectName << ", " << subName); - std::string newElement; - if(subSelected) { - newElement = Data::newElementName(subSelected); - subSelected = newElement.c_str(); - std::string nextsub; - const char *next = strrchr(subSelected,'.'); - if(next && next!=subSelected) { - if(next[1]==0) { - // The convention of dot separated SubName demands a mandatory - // ending dot for every object name reference inside SubName. - // The non-object sub-element, however, must not end with a dot. - // So, next[1]==0 here means current selection is a whole object - // selection (because no sub-element), so we shall search - // upwards for the second last dot, which is the end of the - // parent name of the current selected object - for(--next;next!=subSelected;--next) { - if(*next == '.') break; - } - } - if(*next == '.') - nextsub = std::string(subSelected,next-subSelected+1); - } - if(nextsub.length() || *subSelected!=0) { - hasNext = true; - subName = nextsub; detailPath->truncate(0); - if(vpd->getDetailPath(subName.c_str(),detailPath,true,detNext) && - detailPath->getLength()) - { + if (vpd->getDetailPath(info.element.c_str(), detailPath, true, detNext) && + detailPath->getLength()) { pPath = detailPath; det = detNext; FC_TRACE("select next " << objectName << ", " << subName); + if (ok) + type = hasNext ? SoSelectionElementAction::All : SoSelectionElementAction::Append; } } + } else { + // Hierarchy ascending + // + // If the clicked subelement is already selected, check if there is an + // upper hierarchy, and select that hierarchy instead. + // + // For example, let's suppose PickedInfo above reports + // 'link.link2.box.Face1', and below Selection().getSelectedElement returns + // 'link.link2.box.', meaning that 'box' is the current selected hierarchy, + // and the user is clicking the box again. So we shall go up one level, + // and select 'link.link2.' + // + + + // We need to convert the short name in the selection to a full element path to look it up + // Ex: Body.Pad.Face9 to Body.Pad.;g3;SKT;:H12dc,E;FAC;:H12dc:4,F;:G0;XTR;:H12dc:8,F.Face9 + getFullSubElementName(subName); + const char *subSelected = Gui::Selection().getSelectedElement( + vpd->getObject(), subName.c_str()); + + FC_TRACE("select " << (subSelected ? subSelected : "'null'") << ", " << + objectName << ", " << subName); + std::string newElement; + if (subSelected) { + newElement = Data::newElementName(subSelected); + subSelected = newElement.c_str(); + std::string nextsub; + const char *next = strrchr(subSelected, '.'); + if (next && next != subSelected) { + if (next[1] == 0) { + // The convention of dot separated SubName demands a mandatory + // ending dot for every object name reference inside SubName. + // The non-object sub-element, however, must not end with a dot. + // So, next[1]==0 here means current selection is a whole object + // selection (because no sub-element), so we shall search + // upwards for the second last dot, which is the end of the + // parent name of the current selected object + for (--next; next != subSelected; --next) { + if (*next == '.') break; + } + } + if (*next == '.') + nextsub = std::string(subSelected, next - subSelected + 1); + } + if (nextsub.length() || *subSelected != 0) { + hasNext = true; + subName = nextsub; + detailPath->truncate(0); + if (vpd->getDetailPath(subName.c_str(), detailPath, true, detNext) && + detailPath->getLength()) { + pPath = detailPath; + det = detNext; + FC_TRACE("select next " << objectName << ", " << subName); + } + } + } + + FC_TRACE("clearing selection"); + Gui::Selection().clearSelection(); + FC_TRACE("add selection"); + bool ok = Gui::Selection().addSelection(docname, objectName.c_str(), subName.c_str(), + pt[0], pt[1], pt[2], &sels); + if (ok) + type = hasNext ? SoSelectionElementAction::All : SoSelectionElementAction::Append; + + if (mymode == OFF) { + snprintf(buf, 512, "Selected: %s.%s.%s (%g, %g, %g)", + docname, objectName.c_str(), subName.c_str(), fabs(pt[0]) > 1e-7 ? pt[0] : 0.0, + fabs(pt[1]) > 1e-7 ? pt[1] : 0.0, fabs(pt[2]) > 1e-7 ? pt[2] : 0.0); + + getMainWindow()->showMessage(QString::fromLatin1(buf)); + } } - - FC_TRACE("clearing selection"); - Gui::Selection().clearSelection(); - FC_TRACE("add selection"); - bool ok = Gui::Selection().addSelection(docname, objectName.c_str() ,subName.c_str(), - pt[0] ,pt[1] ,pt[2], &sels); - if (ok) - type = hasNext?SoSelectionElementAction::All:SoSelectionElementAction::Append; - - if (mymode == OFF) { - snprintf(buf,512,"Selected: %s.%s.%s (%g, %g, %g)", - docname, objectName.c_str() ,subName.c_str() - ,fabs(pt[0])>1e-7?pt[0]:0.0 - ,fabs(pt[1])>1e-7?pt[1]:0.0 - ,fabs(pt[2])>1e-7?pt[2]:0.0); - - getMainWindow()->showMessage(QString::fromLatin1(buf)); - } - if (pPath) { FC_TRACE("applying action"); SoSelectionElementAction action(type); @@ -701,7 +708,7 @@ bool SoFCUnifiedSelection::setSelection(const std::vector &infos, bo this->touch(); } - if(detNext) delete detNext; + if (detNext) delete detNext; return true; } diff --git a/src/Mod/PartDesign/PartDesignTests/TestBoolean.py b/src/Mod/PartDesign/PartDesignTests/TestBoolean.py index 8af4ae2b22..bd68415d57 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestBoolean.py +++ b/src/Mod/PartDesign/PartDesignTests/TestBoolean.py @@ -22,6 +22,7 @@ import unittest import FreeCAD +from pivy import coin App = FreeCAD @@ -101,8 +102,68 @@ class TestBoolean(unittest.TestCase): self.Doc.recompute() self.assertAlmostEqual(self.BooleanCommon.Shape.Volume, 500) - def tearDown(self): - #closing doc - FreeCAD.closeDocument("PartDesignTestBoolean") - #print ("omit closing document for debugging") + def testBooleanSelectUI(self): + if not App.GuiUp: + return + # Arrange + self.Body = self.Doc.addObject('PartDesign::Body','Body') + self.Box = self.Doc.addObject('PartDesign::AdditiveBox','Box') + self.Box.Length=10 + self.Box.Width=10 + self.Box.Height=10 + self.Body.addObject(self.Box) + self.Doc.recompute() + self.Body001 = self.Doc.addObject('PartDesign::Body','Body001') + self.Box001 = self.Doc.addObject('PartDesign::AdditiveBox','Box001') + self.Box001.Length=10 + self.Box001.Width=10 + self.Box001.Height=10 + self.Box001.Placement.Base = App.Vector(-5,0,0) + self.Body001.addObject(self.Box001) + self.Body002 = self.Doc.addObject('PartDesign::Body','Body002') + self.BooleanFuse = self.Doc.addObject('PartDesign::Boolean','BooleanFuse') + self.BooleanFuse.Group = [ self.Body, self.Body001 ] + self.BooleanFuse.ViewObject.Display="Result" # TODO: This is a required redundancy or it doesn't init right + self.Body002.addObject(self.BooleanFuse) + self.Doc.recompute() + # Act + App.Gui.Selection.addSelection("", self.Body002.Name, "Face2") + App.Gui.Selection.addSelection("", self.Body002.Name, "Face6") + App.Gui.Selection.addSelection("", self.Body002.Name, "Face10") + App.Gui.Selection.addSelection("", self.Body002.Name, "Face3") + App.Gui.Selection.addSelection("", self.Body002.Name, "Face7") + App.Gui.Selection.addSelection("", self.Body002.Name, "Face11") + App.Gui.Selection.addSelection("", "Body002", "Face14") + App.Gui.updateGui() + # Assert + self.assertEqual( + len(App.Gui.Selection.getSelectionEx("", 0)[0].SubElementNames), 7 + ) + self.assertEqual( + App.Gui.Selection.getSelectionEx("", 0)[0].SubElementNames[0][-8:], + ",F.Face2", + ) + # Ideally we would be able to check and see the selection color set, but there is currently no way to + # see this either from python of c++ without either tapping into the coin GL renderer or writing a new + # test renderer. Prototypes of code moving in this direction would be: + # compSel = App.Gui.Selection.getCompleteSelection() + # selection0 = compSel[0] + # selection00Object = selection0.Object.ViewObject + # path = coin.SoPath() + # foundCount = App.Gui.ActiveDocument.Box.getDetailPath("Face14.",path,False) + # pathLength = path.getLength() + # # path.getHead() or path.getTail() or path.getNode(n) + # childCount = path.getHead().getNumChildren() + # children = path.getHead().getChildren() # Some pivy objects support this. + # child = children.get(0) + # childOpt2 = path.getHead().getChild(0) # And others support this. And some support both. + # rgb = App.Gui.ActiveDocument.Body002.RootNode.getChild(2).getChild(1).getChild(1).getChild(0).diffuseColor.getValues()[0].getValue() + # # Assert the rgb color value. + + def tearDown(self): + if hasattr(App, "KeepTestDoc") and App.KeepTestDoc: + return + #closing doc. + FreeCAD.closeDocument("PartDesignTestBoolean") +