Toponaming: Fix ctrl selection issues by looking up element names and setting up detail path

This commit is contained in:
bgbsww
2024-09-01 16:13:18 -04:00
committed by Yorik van Havre
parent 806284deda
commit 5a1275ad60
2 changed files with 170 additions and 102 deletions

View File

@@ -546,13 +546,13 @@ bool SoFCUnifiedSelection::setHighlight(SoFullPath *path, const SoDetail *det,
}
bool SoFCUnifiedSelection::setSelection(const std::vector<PickedInfo> &infos, bool ctrlDown) {
if(infos.empty() || !infos[0].vpd)
if (infos.empty() || !infos[0].vpd)
return false;
std::vector<SelectionSingleton::SelObj> 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<PickedInfo> &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<SoFullPath*>(pp->getPath());
auto pPath = static_cast<SoFullPath *>(pp->getPath());
const auto &pt = pp->getPoint();
SoSelectionElementAction::Type type = SoSelectionElementAction::None;
auto mymode = static_cast<HighlightModes>(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<PickedInfo> &infos, bo
this->touch();
}
if(detNext) delete detNext;
if (detNext) delete detNext;
return true;
}

View File

@@ -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")