Toponaming: Fix ctrl selection issues by looking up element names and setting up detail path
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user