Download src/Gui/Selection/SoFCUnifiedSelection.cpp from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 73.2 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Gui/Selection/SoFCUnifiedSelection.cpp
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Gui/Selection/SoFCUnifiedSelection.cpp
-
curl -L -o SoFCUnifiedSelection.cpp https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Gui/Selection/SoFCUnifiedSelection.cpp
73.2 kB
| // SPDX-License-Identifier: LGPL-2.1-or-later | |
| /*************************************************************************** | |
| * Copyright (c) 2005 Jürgen Riegel <juergen.riegel@web.de> * | |
| * * | |
| * This file is part of the FreeCAD CAx development system. * | |
| * * | |
| * This library is free software; you can redistribute it and/or * | |
| * modify it under the terms of the GNU Library General Public * | |
| * License as published by the Free Software Foundation; either * | |
| * version 2 of the License, or (at your option) any later version. * | |
| * * | |
| * This library is distributed in the hope that it will be useful, * | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
| * GNU Library General Public License for more details. * | |
| * * | |
| * You should have received a copy of the GNU Library General Public * | |
| * License along with this library; see the file COPYING.LIB. If not, * | |
| * write to the Free Software Foundation, Inc., 59 Temple Place, * | |
| * Suite 330, Boston, MA 02111-1307, USA * | |
| * * | |
| ***************************************************************************/ | |
| FC_LOG_LEVEL_INIT("SoFCUnifiedSelection", false, true, true) | |
| using namespace Gui; | |
| namespace Gui | |
| { | |
| void printPreselectionInfo( | |
| const char* documentName, | |
| const char* objectName, | |
| const char* subElementName, | |
| float x, | |
| float y, | |
| float z, | |
| double precision | |
| ); | |
| } | |
| SoFullPath* Gui::SoFCUnifiedSelection::currentHighlightPath = nullptr; | |
| // ************************************************************************* | |
| SO_NODE_SOURCE(SoFCUnifiedSelection) | |
| /*! | |
| Constructor. | |
| */ | |
| SoFCUnifiedSelection::SoFCUnifiedSelection() | |
| { | |
| SO_NODE_CONSTRUCTOR(SoFCUnifiedSelection); | |
| SO_NODE_ADD_FIELD(colorHighlight, (SbColor(1.0f, 0.6f, 0.0f))); | |
| SO_NODE_ADD_FIELD(colorSelection, (SbColor(0.1f, 0.8f, 0.1f))); | |
| SO_NODE_ADD_FIELD(preselectionMode, (AUTO)); | |
| SO_NODE_ADD_FIELD(selectionMode, (ON)); | |
| SO_NODE_ADD_FIELD(selectionEnabled, (true)); | |
| SO_NODE_ADD_FIELD(useNewSelection, (true)); | |
| SO_NODE_DEFINE_ENUM_VALUE(PreselectionModes, AUTO); | |
| SO_NODE_DEFINE_ENUM_VALUE(PreselectionModes, ON); | |
| SO_NODE_DEFINE_ENUM_VALUE(PreselectionModes, OFF); | |
| SO_NODE_SET_SF_ENUM_TYPE(preselectionMode, PreselectionModes); | |
| // Documentation of SoFullPath: | |
| // Since the SoFullPath is derived from SoPath and contains no private data, you can cast SoPath | |
| // instances to the SoFullPath type. This will allow you to examine hidden children. Actually, | |
| // you are not supposed to allocate instances of this class at all. It is only available as an | |
| // "extended interface" into the superclass SoPath. | |
| detailPath = static_cast<SoFullPath*>(new SoPath(20)); | |
| detailPath->ref(); | |
| setPreSelection = false; | |
| preSelection = -1; | |
| useNewSelection = ViewParams::instance()->getUseNewSelection(); | |
| } | |
| /*! | |
| Destructor. | |
| */ | |
| SoFCUnifiedSelection::~SoFCUnifiedSelection() | |
| { | |
| // If we're being deleted and we're the current highlight, | |
| // NULL out that variable | |
| if (currentHighlightPath) { | |
| currentHighlightPath->unref(); | |
| currentHighlightPath = nullptr; | |
| } | |
| if (detailPath) { | |
| detailPath->unref(); | |
| detailPath = nullptr; | |
| } | |
| } | |
| // doc from parent | |
| void SoFCUnifiedSelection::initClass() | |
| { | |
| SO_NODE_INIT_CLASS(SoFCUnifiedSelection, SoSeparator, "Separator"); | |
| } | |
| void SoFCUnifiedSelection::finish() | |
| { | |
| atexit_cleanup(); | |
| } | |
| bool SoFCUnifiedSelection::hasHighlight() | |
| { | |
| return currentHighlightPath != nullptr; | |
| } | |
| void SoFCUnifiedSelection::applySettings() | |
| { | |
| float transparency; | |
| ParameterGrp::handle hGrp = Gui::WindowParameter::getDefaultParameter()->GetGroup("View"); | |
| bool enablePreselection = hGrp->GetBool("EnablePreselection", true); | |
| if (!enablePreselection) { | |
| this->preselectionMode = SoFCUnifiedSelection::OFF; | |
| } | |
| else { | |
| // Search for a user defined value with the current color as default | |
| SbColor highlightColor = this->colorHighlight.getValue(); | |
| auto highlight = (unsigned long)(highlightColor.getPackedValue()); | |
| highlight = hGrp->GetUnsigned("HighlightColor", highlight); | |
| highlightColor.setPackedValue((uint32_t)highlight, transparency); | |
| this->colorHighlight.setValue(highlightColor); | |
| } | |
| bool enableSelection = hGrp->GetBool("EnableSelection", true); | |
| if (!enableSelection) { | |
| this->selectionMode = SoFCUnifiedSelection::OFF; | |
| } | |
| else { | |
| // Do the same with the selection color | |
| SbColor selectionColor = this->colorSelection.getValue(); | |
| auto selection = (unsigned long)(selectionColor.getPackedValue()); | |
| selection = hGrp->GetUnsigned("SelectionColor", selection); | |
| selectionColor.setPackedValue((uint32_t)selection, transparency); | |
| this->colorSelection.setValue(selectionColor); | |
| } | |
| } | |
| const char* SoFCUnifiedSelection::getFileFormatName() const | |
| { | |
| return "Separator"; | |
| } | |
| void SoFCUnifiedSelection::write(SoWriteAction* action) | |
| { | |
| SoOutput* out = action->getOutput(); | |
| if (out->getStage() == SoOutput::WRITE) { | |
| // Do not write out the fields of this class | |
| if (this->writeHeader(out, true, false)) { | |
| return; | |
| } | |
| SoGroup::doAction(static_cast<SoAction*>(action)); | |
| this->writeFooter(out); | |
| } | |
| else { | |
| inherited::write(action); | |
| } | |
| } | |
| int SoFCUnifiedSelection::getPriority(const SoPickedPoint* p) | |
| { | |
| const SoDetail* detail = p->getDetail(); | |
| if (!detail) { | |
| return 0; | |
| } | |
| if (detail->isOfType(SoFaceDetail::getClassTypeId())) { | |
| return 1; | |
| } | |
| if (detail->isOfType(SoLineDetail::getClassTypeId())) { | |
| return 2; | |
| } | |
| if (detail->isOfType(SoPointDetail::getClassTypeId())) { | |
| return 3; | |
| } | |
| return 0; | |
| } | |
| std::vector<SoFCUnifiedSelection::PickedInfo> SoFCUnifiedSelection::getPickedList( | |
| SoHandleEventAction* action, | |
| bool singlePick | |
| ) const | |
| { | |
| ViewProvider* last_vp = nullptr; | |
| std::vector<PickedInfo> ret; | |
| const SoPickedPointList& points = action->getPickedPointList(); | |
| for (int i = 0, count = points.getLength(); i < count; ++i) { | |
| PickedInfo info; | |
| info.pp = points[i]; | |
| info.vpd = nullptr; | |
| ViewProvider* vp = nullptr; | |
| auto path = static_cast<SoFullPath*>(info.pp->getPath()); | |
| if (this->pcDocument && path && path->containsPath(action->getCurPath())) { | |
| vp = this->pcDocument->getViewProviderByPathFromHead(path); | |
| if (singlePick && last_vp && last_vp != vp) { | |
| return ret; | |
| } | |
| } | |
| if (!vp || !vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { | |
| if (!singlePick) { | |
| continue; | |
| } | |
| if (ret.empty()) { | |
| ret.push_back(info); | |
| } | |
| break; | |
| } | |
| info.vpd = static_cast<ViewProviderDocumentObject*>(vp); | |
| if (!(useNewSelection.getValue() || info.vpd->useNewSelectionModel()) | |
| || !info.vpd->isSelectable()) { | |
| if (!singlePick) { | |
| continue; | |
| } | |
| if (ret.empty()) { | |
| info.vpd = nullptr; | |
| ret.push_back(info); | |
| } | |
| break; | |
| } | |
| if (!info.vpd->getElementPicked(info.pp, info.element)) { | |
| continue; | |
| } | |
| if (singlePick) { | |
| last_vp = vp; | |
| } | |
| ret.push_back(info); | |
| } | |
| if (ret.size() <= 1) { | |
| return ret; | |
| } | |
| // To identify the picking of lines in a concave area we have to | |
| // get all intersection points. If we have two or more intersection | |
| // points where the first is of a face and the second of a line with | |
| // almost similar coordinates we use the second point, instead. | |
| int picked_prio = getPriority(ret[0].pp); | |
| auto last_vpd = ret[0].vpd; | |
| const SbVec3f& picked_pt = ret.front().pp->getPoint(); | |
| auto itPicked = ret.begin(); | |
| for (auto it = ret.begin() + 1; it != ret.end(); ++it) { | |
| auto& info = *it; | |
| if (last_vpd != info.vpd) { | |
| break; | |
| } | |
| int cur_prio = getPriority(info.pp); | |
| const SbVec3f& cur_pt = info.pp->getPoint(); | |
| if ((cur_prio > picked_prio) && picked_pt.equals(cur_pt, 0.2F)) { | |
| itPicked = it; | |
| picked_prio = cur_prio; | |
| } | |
| } | |
| if (singlePick) { | |
| std::vector<PickedInfo> sret(itPicked, itPicked + 1); | |
| return sret; | |
| } | |
| if (itPicked != ret.begin()) { | |
| std::swap(*itPicked, *ret.begin()); | |
| } | |
| return ret; | |
| } | |
| void SoFCUnifiedSelection::doAction(SoAction* action) | |
| { | |
| if (action->getTypeId() == SoFCEnablePreselectionAction::getClassTypeId()) { | |
| auto enablePreselectionAction = static_cast<SoFCEnablePreselectionAction*>(action); | |
| if (enablePreselectionAction->enabled) { | |
| this->preselectionMode = SoFCUnifiedSelection::AUTO; | |
| } | |
| else { | |
| this->preselectionMode = SoFCUnifiedSelection::OFF; | |
| } | |
| } | |
| if (action->getTypeId() == SoFCEnableSelectionAction::getClassTypeId()) { | |
| auto enableSelectionAction = static_cast<SoFCEnableSelectionAction*>(action); | |
| if (enableSelectionAction->enabled) { | |
| this->selectionMode = SoFCUnifiedSelection::ON; | |
| } | |
| else { | |
| this->selectionMode = SoFCUnifiedSelection::OFF; | |
| } | |
| } | |
| if (action->getTypeId() == SoFCSelectionColorAction::getClassTypeId()) { | |
| auto selectionColorAction = static_cast<SoFCSelectionColorAction*>(action); | |
| this->colorSelection = selectionColorAction->selectionColor; | |
| } | |
| if (action->getTypeId() == SoFCHighlightColorAction::getClassTypeId()) { | |
| auto highlightColorAction = static_cast<SoFCHighlightColorAction*>(action); | |
| this->colorHighlight = highlightColorAction->highlightColor; | |
| } | |
| if (action->getTypeId() == SoFCPreselectionAction::getClassTypeId()) { | |
| auto preselectAction = static_cast<SoFCPreselectionAction*>(action); | |
| // Do not clear currently preselected object when setting new preselection | |
| if (!setPreSelection && preselectAction->SelChange.Type == SelectionChanges::RmvPreselect) { | |
| if (currentHighlightPath) { | |
| SoHighlightElementAction highlightAction; | |
| highlightAction.apply(currentHighlightPath); | |
| currentHighlightPath->unref(); | |
| currentHighlightPath = nullptr; | |
| } | |
| } | |
| else if (preselectionMode.getValue() != OFF | |
| && preselectAction->SelChange.Type == SelectionChanges::SetPreselect) { | |
| if (currentHighlightPath) { | |
| SoHighlightElementAction highlightAction; | |
| highlightAction.apply(currentHighlightPath); | |
| currentHighlightPath->unref(); | |
| currentHighlightPath = nullptr; | |
| } | |
| App::Document* doc = App::GetApplication().getDocument(preselectAction->SelChange.pDocName); | |
| App::DocumentObject* obj = doc->getObject(preselectAction->SelChange.pObjectName); | |
| ViewProvider* vp = Application::Instance->getViewProvider(obj); | |
| // use getDetailPath() like selection does, instead of just getDetail() | |
| SoDetail* detail = nullptr; | |
| detailPath->truncate(0); | |
| auto subName = preselectAction->SelChange.pSubName; | |
| SoFullPath* pathToHighlight = nullptr; | |
| if (vp && vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId()) | |
| && (useNewSelection.getValue() || vp->useNewSelectionModel()) && vp->isSelectable()) { | |
| // get proper detail path for sub-objects (like Assembly parts) | |
| if (!subName || !subName[0] || vp->getDetailPath(subName, detailPath, true, detail)) { | |
| if (detailPath->getLength()) { | |
| pathToHighlight = detailPath; | |
| } | |
| else { | |
| // fallback to ViewProvider root if no specific path | |
| pathToHighlight = static_cast<SoFullPath*>(new SoPath(2)); | |
| pathToHighlight->ref(); | |
| pathToHighlight->append(vp->getRoot()); | |
| } | |
| } | |
| } | |
| else { | |
| detail = vp->getDetail(subName); | |
| pathToHighlight = static_cast<SoFullPath*>(new SoPath(2)); | |
| pathToHighlight->ref(); | |
| pathToHighlight->append(vp->getRoot()); | |
| } | |
| if (pathToHighlight) { | |
| SoHighlightElementAction highlightAction; | |
| highlightAction.setHighlighted(true); | |
| highlightAction.setColor(this->colorHighlight.getValue()); | |
| highlightAction.setElement(detail); | |
| highlightAction.apply(pathToHighlight); | |
| currentHighlightPath = static_cast<SoFullPath*>(pathToHighlight->copy()); | |
| currentHighlightPath->ref(); | |
| // clean up temporary path if we created one | |
| if (pathToHighlight != detailPath) { | |
| pathToHighlight->unref(); | |
| } | |
| } | |
| delete detail; | |
| } | |
| if (useNewSelection.getValue()) { | |
| return; | |
| } | |
| } | |
| if (action->getTypeId() == SoFCSelectionAction::getClassTypeId()) { | |
| auto selectionAction = static_cast<SoFCSelectionAction*>(action); | |
| if (selectionMode.getValue() == ON | |
| && (selectionAction->SelChange.Type == SelectionChanges::AddSelection | |
| || selectionAction->SelChange.Type == SelectionChanges::RmvSelection)) { | |
| // selection changes inside the 3d view are handled in handleEvent() | |
| App::Document* doc = App::GetApplication().getDocument(selectionAction->SelChange.pDocName); | |
| App::DocumentObject* obj = doc->getObject(selectionAction->SelChange.pObjectName); | |
| ViewProvider* vp = Application::Instance->getViewProvider(obj); | |
| // check if the subobject and all its parent containers are selectable | |
| bool isSelectable = false; | |
| if (vp) { | |
| if (selectionAction->SelChange.pSubName && selectionAction->SelChange.pSubName[0]) { | |
| // get the entire object hierarchy from root to leaf | |
| auto objList = obj->getSubObjectList(selectionAction->SelChange.pSubName); | |
| isSelectable = true; | |
| for (auto* checkObj : objList) { | |
| if (auto checkVp = Application::Instance->getViewProvider(checkObj)) { | |
| if (!checkVp->isSelectable()) { | |
| isSelectable = false; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| else { | |
| isSelectable = vp->isSelectable(); | |
| } | |
| } | |
| if (vp && (useNewSelection.getValue() || vp->useNewSelectionModel()) && isSelectable) { | |
| SoDetail* detail = nullptr; | |
| detailPath->truncate(0); | |
| auto subName = selectionAction->SelChange.pSubName; | |
| App::ElementNamePair elementName; | |
| ; | |
| App::GeoFeature::resolveElement(obj, subName, elementName); | |
| if (Data::isMappedElement(subName) | |
| && !elementName.oldName.empty()) { // If we have a shortened element name | |
| subName = elementName.oldName.c_str(); // use it. | |
| } | |
| if (!selectionAction->SelChange.pSubName || !selectionAction->SelChange.pSubName[0] | |
| || vp->getDetailPath(subName, detailPath, true, detail)) { | |
| SoSelectionElementAction::Type type = SoSelectionElementAction::None; | |
| if (selectionAction->SelChange.Type == SelectionChanges::AddSelection) { | |
| if (detail) { | |
| type = SoSelectionElementAction::Append; | |
| } | |
| else { | |
| type = SoSelectionElementAction::All; | |
| } | |
| } | |
| else { | |
| if (detail) { | |
| type = SoSelectionElementAction::Remove; | |
| } | |
| else { | |
| type = SoSelectionElementAction::None; | |
| } | |
| } | |
| SoSelectionElementAction selectionAction(type); | |
| selectionAction.setColor(this->colorSelection.getValue()); | |
| selectionAction.setElement(detail); | |
| if (detailPath->getLength()) { | |
| selectionAction.apply(detailPath); | |
| } | |
| else { | |
| selectionAction.apply(vp->getRoot()); | |
| } | |
| } | |
| detailPath->truncate(0); | |
| delete detail; | |
| } | |
| } | |
| else if (selectionAction->SelChange.Type == SelectionChanges::ClrSelection) { | |
| SoSelectionElementAction selectionAction(SoSelectionElementAction::None); | |
| for (int i = 0; i < this->getNumChildren(); ++i) { | |
| selectionAction.apply(this->getChild(i)); | |
| } | |
| } | |
| else if (selectionMode.getValue() == ON | |
| && selectionAction->SelChange.Type == SelectionChanges::SetSelection) { | |
| std::vector<ViewProvider*> vps; | |
| if (this->pcDocument) { | |
| vps = this->pcDocument->getViewProvidersOfType( | |
| ViewProviderDocumentObject::getClassTypeId() | |
| ); | |
| } | |
| for (const auto& vp : vps) { | |
| auto vpd = static_cast<ViewProviderDocumentObject*>(vp); | |
| if (useNewSelection.getValue() || vpd->useNewSelectionModel()) { | |
| SoSelectionElementAction::Type type; | |
| if (Selection().isSelected(vpd->getObject()) && vpd->isSelectable()) { | |
| type = SoSelectionElementAction::All; | |
| } | |
| else { | |
| type = SoSelectionElementAction::None; | |
| } | |
| SoSelectionElementAction selectionAction(type); | |
| selectionAction.setColor(this->colorSelection.getValue()); | |
| selectionAction.apply(vpd->getRoot()); | |
| } | |
| } | |
| } | |
| else if (selectionAction->SelChange.Type == SelectionChanges::SetPreselectSignal) { | |
| // selection changes inside the 3d view are handled in handleEvent() | |
| App::Document* doc = App::GetApplication().getDocument(selectionAction->SelChange.pDocName); | |
| App::DocumentObject* obj = doc->getObject(selectionAction->SelChange.pObjectName); | |
| ViewProvider* vp = Application::Instance->getViewProvider(obj); | |
| if (vp && vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId()) | |
| && (useNewSelection.getValue() || vp->useNewSelectionModel()) && vp->isSelectable()) { | |
| detailPath->truncate(0); | |
| SoDetail* det = nullptr; | |
| if (vp->getDetailPath(selectionAction->SelChange.pSubName, detailPath, true, det)) { | |
| setPreselect( | |
| detailPath, | |
| det, | |
| static_cast<ViewProviderDocumentObject*>(vp), | |
| selectionAction->SelChange.pSubName, | |
| selectionAction->SelChange.x, | |
| selectionAction->SelChange.y, | |
| selectionAction->SelChange.z | |
| ); | |
| } | |
| delete det; | |
| } | |
| } | |
| if (useNewSelection.getValue()) { | |
| return; | |
| } | |
| } | |
| inherited::doAction(action); | |
| } | |
| bool SoFCUnifiedSelection::setPreselect(const PickedInfo& info) | |
| { | |
| if (!info.pp) { | |
| return setPreselect(nullptr, nullptr, nullptr, nullptr, 0.0, 0.0, 0.0); | |
| } | |
| const auto& pt = info.pp->getPoint(); | |
| return setPreselect( | |
| static_cast<SoFullPath*>(info.pp->getPath()), | |
| info.pp->getDetail(), | |
| info.vpd, | |
| info.element.c_str(), | |
| pt[0], | |
| pt[1], | |
| pt[2] | |
| ); | |
| } | |
| bool SoFCUnifiedSelection::setPreselect( | |
| SoFullPath* path, | |
| const SoDetail* det, | |
| ViewProviderDocumentObject* vpd, | |
| const char* element, | |
| float x, | |
| float y, | |
| float z | |
| ) | |
| { | |
| Base::FlagToggler<SbBool> flag(setPreSelection); | |
| bool highlighted = false; | |
| if (path && path->getLength() && vpd && vpd->getObject() | |
| && vpd->getObject()->isAttachedToDocument()) { | |
| const char* docname = vpd->getObject()->getDocument()->getName(); | |
| const char* objname = vpd->getObject()->getNameInDocument(); | |
| this->preSelection = 1; | |
| printPreselectionInfo(docname, objname, element, x, y, z, 1e-7); | |
| int ret = Gui::Selection().setPreselect(docname, objname, element, x, y, z); | |
| if (ret < 0 && currentHighlightPath) { | |
| return true; | |
| } | |
| if (ret) { | |
| if (currentHighlightPath) { | |
| SoHighlightElementAction action; | |
| action.setHighlighted(false); | |
| action.apply(currentHighlightPath); | |
| currentHighlightPath->unref(); | |
| currentHighlightPath = nullptr; | |
| } | |
| currentHighlightPath = static_cast<SoFullPath*>(path->copy()); | |
| currentHighlightPath->ref(); | |
| highlighted = true; | |
| } | |
| } | |
| if (currentHighlightPath) { | |
| SoHighlightElementAction action; | |
| action.setHighlighted(highlighted); | |
| action.setColor(this->colorHighlight.getValue()); | |
| action.setElement(det); | |
| action.apply(currentHighlightPath); | |
| if (!highlighted) { | |
| currentHighlightPath->unref(); | |
| currentHighlightPath = nullptr; | |
| Selection().rmvPreselect(); | |
| } | |
| this->touch(); | |
| } | |
| return highlighted; | |
| } | |
| bool SoFCUnifiedSelection::setSelection(const std::vector<PickedInfo>& infos, bool ctrlDown) | |
| { | |
| 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; | |
| } | |
| SelectionSingleton::SelObj sel; | |
| sel.pResolvedObject = nullptr; | |
| sel.pObject = info.vpd->getObject(); | |
| sel.pDoc = sel.pObject->getDocument(); | |
| sel.DocName = sel.pDoc->getName(); | |
| sel.FeatName = sel.pObject->getNameInDocument(); | |
| sel.TypeName = sel.pObject->getTypeId().getName(); | |
| sel.SubName = info.element.c_str(); | |
| const auto& pt = info.pp->getPoint(); | |
| sel.x = pt[0]; | |
| sel.y = pt[1]; | |
| sel.z = pt[2]; | |
| sels.push_back(sel); | |
| } | |
| } | |
| const auto& info = infos[0]; | |
| auto vpd = info.vpd; | |
| if (!vpd) { | |
| return false; | |
| } | |
| 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 SoDetail* det = pp->getDetail(); | |
| SoDetail* detNext = nullptr; | |
| auto pPath = static_cast<SoFullPath*>(pp->getPath()); | |
| const auto& pt = pp->getPoint(); | |
| SoSelectionElementAction::Type type = SoSelectionElementAction::None; | |
| auto preselectionMode = static_cast<SelectionModes>(this->preselectionMode.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); | |
| return true; | |
| } | |
| else { | |
| // Changing the selection may result in destroying this view provider. | |
| // So, make sure that the object still exists afterwards (#17965) | |
| ViewProviderWeakPtrT guard(vpd); | |
| getFullSubElementName(subName); | |
| bool ok = Gui::Selection() | |
| .addSelection(docname, objname, subName.c_str(), pt[0], pt[1], pt[2], &sels); | |
| if (guard.expired()) { | |
| return false; | |
| } | |
| if (ok && preselectionMode == 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 | |
| ); | |
| getMainWindow()->showMessage(QString::fromLatin1(buf)); | |
| } | |
| detailPath->truncate(0); | |
| 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 { | |
| // don't apply any visual action when selection fails - | |
| // in a case when we press ctrl and select a geometry that should be | |
| // filtered out, we don't want to apply any visual action | |
| pPath = nullptr; | |
| } | |
| } | |
| } | |
| } | |
| 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 (preselectionMode == 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); | |
| action.setColor(this->colorSelection.getValue()); | |
| action.setElement(det); | |
| action.apply(pPath); | |
| FC_TRACE("applied action"); | |
| this->touch(); | |
| } | |
| if (detNext) { | |
| delete detNext; | |
| } | |
| return true; | |
| } | |
| // doc from parent | |
| void SoFCUnifiedSelection::handleEvent(SoHandleEventAction* action) | |
| { | |
| // If off then don't handle this event | |
| if (!selectionEnabled.getValue()) { | |
| inherited::handleEvent(action); | |
| return; | |
| } | |
| auto preselectionMode = static_cast<SelectionModes>(this->preselectionMode.getValue()); | |
| const SoEvent* event = action->getEvent(); | |
| // | |
| // If this is a mouseMotion event, then check for preselected entities. | |
| // | |
| bool isMouseMotionEvent = event->isOfType(SoLocation2Event::getClassTypeId()); | |
| if (isMouseMotionEvent) { | |
| // NOTE: If preselection is off then we do not check for a picked point because otherwise | |
| // this search may slow down extremely the system on really big data sets. In this case we | |
| // just check for a picked point if the data set has been selected. | |
| if (preselectionMode == AUTO || preselectionMode == ON) { | |
| // check to see if the mouse is over our geometry... | |
| auto infos = this->getPickedList(action, true); | |
| if (!infos.empty()) { | |
| setPreselect(infos[0]); | |
| } | |
| else { | |
| setPreselect(PickedInfo()); | |
| if (this->preSelection > 0) { | |
| this->preSelection = 0; | |
| // touch() makes sure to call GLRenderBelowPath so that the cursor can be updated | |
| // because only from there the SoGLWidgetElement delivers the OpenGL window | |
| this->touch(); | |
| } | |
| } | |
| } | |
| } | |
| // mouse press events for (de)selection | |
| else if (event->isOfType(SoMouseButtonEvent::getClassTypeId()) | |
| && selectionMode.getValue() == SoFCUnifiedSelection::ON) { | |
| const auto e = static_cast<const SoMouseButtonEvent*>(event); | |
| if (SoMouseButtonEvent::isButtonReleaseEvent(e, SoMouseButtonEvent::BUTTON1)) { | |
| // check to see if the mouse is over a geometry... | |
| auto infos = this->getPickedList(action, !Selection().needPickedList()); | |
| bool greedySel = Gui::Selection().getSelectionStyle() | |
| == Gui::SelectionSingleton::SelectionStyle::GreedySelection; | |
| greedySel = greedySel || event->wasCtrlDown(); | |
| if (setSelection(infos, greedySel) || greedySel) { | |
| action->setHandled(); | |
| } | |
| } // mouse release | |
| } | |
| inherited::handleEvent(action); | |
| } | |
| void SoFCUnifiedSelection::GLRenderBelowPath(SoGLRenderAction* action) | |
| { | |
| inherited::GLRenderBelowPath(action); | |
| // nothing picked, so restore the arrow cursor if needed | |
| if (this->preSelection == 0) { | |
| // this is called when a selection gate forbade to select an object | |
| // and the user moved the mouse to an empty area | |
| this->preSelection = -1; | |
| QOpenGLWidget* window; | |
| SoState* state = action->getState(); | |
| SoGLWidgetElement::get(state, window); | |
| QWidget* parent = window ? window->parentWidget() : nullptr; | |
| if (parent) { | |
| QCursor c = parent->cursor(); | |
| if (c.shape() == Qt::ForbiddenCursor) { | |
| c.setShape(Qt::ArrowCursor); | |
| parent->setCursor(c); | |
| } | |
| } | |
| } | |
| } | |
| // --------------------------------------------------------------- | |
| SO_ACTION_SOURCE(SoHighlightElementAction) | |
| void SoHighlightElementAction::initClass() | |
| { | |
| SO_ACTION_INIT_CLASS(SoHighlightElementAction, SoAction); | |
| SO_ENABLE(SoHighlightElementAction, SoSwitchElement); | |
| SO_ENABLE(SoHighlightElementAction, SoModelMatrixElement); | |
| SO_ACTION_ADD_METHOD(SoNode, nullAction); | |
| SO_ENABLE(SoHighlightElementAction, SoCoordinateElement); | |
| SO_ACTION_ADD_METHOD(SoGroup, callDoAction); | |
| SO_ACTION_ADD_METHOD(SoIndexedLineSet, callDoAction); | |
| SO_ACTION_ADD_METHOD(SoIndexedFaceSet, callDoAction); | |
| SO_ACTION_ADD_METHOD(SoPointSet, callDoAction); | |
| } | |
| SoHighlightElementAction::SoHighlightElementAction() | |
| { | |
| SO_ACTION_CONSTRUCTOR(SoHighlightElementAction); | |
| } | |
| SoHighlightElementAction::~SoHighlightElementAction() = default; | |
| void SoHighlightElementAction::beginTraversal(SoNode* node) | |
| { | |
| traverse(node); | |
| } | |
| void SoHighlightElementAction::callDoAction(SoAction* action, SoNode* node) | |
| { | |
| node->doAction(action); | |
| } | |
| void SoHighlightElementAction::setHighlighted(SbBool ok) | |
| { | |
| this->_highlight = ok; | |
| } | |
| SbBool SoHighlightElementAction::isHighlighted() const | |
| { | |
| return this->_highlight; | |
| } | |
| void SoHighlightElementAction::setColor(const SbColor& c) | |
| { | |
| this->_color = c; | |
| } | |
| const SbColor& SoHighlightElementAction::getColor() const | |
| { | |
| return this->_color; | |
| } | |
| void SoHighlightElementAction::setElement(const SoDetail* det) | |
| { | |
| this->_det = det; | |
| } | |
| const SoDetail* SoHighlightElementAction::getElement() const | |
| { | |
| return this->_det; | |
| } | |
| // --------------------------------------------------------------- | |
| SO_ACTION_SOURCE(SoSelectionElementAction) | |
| void SoSelectionElementAction::initClass() | |
| { | |
| SO_ACTION_INIT_CLASS(SoSelectionElementAction, SoAction); | |
| SO_ENABLE(SoSelectionElementAction, SoSwitchElement); | |
| SO_ENABLE(SoSelectionElementAction, SoModelMatrixElement); | |
| SO_ACTION_ADD_METHOD(SoNode, nullAction); | |
| SO_ENABLE(SoSelectionElementAction, SoCoordinateElement); | |
| SO_ACTION_ADD_METHOD(SoCoordinate3, callDoAction); | |
| SO_ACTION_ADD_METHOD(SoGroup, callDoAction); | |
| SO_ACTION_ADD_METHOD(SoIndexedLineSet, callDoAction); | |
| SO_ACTION_ADD_METHOD(SoIndexedFaceSet, callDoAction); | |
| SO_ACTION_ADD_METHOD(SoPointSet, callDoAction); | |
| } | |
| SoSelectionElementAction::SoSelectionElementAction(Type t, bool secondary) | |
| : _type(t) | |
| , _secondary(secondary) | |
| { | |
| SO_ACTION_CONSTRUCTOR(SoSelectionElementAction); | |
| } | |
| SoSelectionElementAction::~SoSelectionElementAction() = default; | |
| void SoSelectionElementAction::beginTraversal(SoNode* node) | |
| { | |
| traverse(node); | |
| } | |
| void SoSelectionElementAction::callDoAction(SoAction* action, SoNode* node) | |
| { | |
| node->doAction(action); | |
| } | |
| SoSelectionElementAction::Type SoSelectionElementAction::getType() const | |
| { | |
| return this->_type; | |
| } | |
| void SoSelectionElementAction::setColor(const SbColor& c) | |
| { | |
| this->_color = c; | |
| } | |
| const SbColor& SoSelectionElementAction::getColor() const | |
| { | |
| return this->_color; | |
| } | |
| void SoSelectionElementAction::setElement(const SoDetail* det) | |
| { | |
| this->_det = det; | |
| } | |
| const SoDetail* SoSelectionElementAction::getElement() const | |
| { | |
| return this->_det; | |
| } | |
| // --------------------------------------------------------------- | |
| SO_ACTION_SOURCE(SoVRMLAction) | |
| void SoVRMLAction::initClass() | |
| { | |
| SO_ACTION_INIT_CLASS(SoVRMLAction, SoAction); | |
| SO_ENABLE(SoVRMLAction, SoSwitchElement); | |
| SO_ACTION_ADD_METHOD(SoNode, nullAction); | |
| SO_ENABLE(SoVRMLAction, SoCoordinateElement); | |
| SO_ENABLE(SoVRMLAction, SoMaterialBindingElement); | |
| SO_ENABLE(SoVRMLAction, SoLazyElement); | |
| SO_ENABLE(SoVRMLAction, SoShapeStyleElement); | |
| SO_ACTION_ADD_METHOD(SoCoordinate3, callDoAction); | |
| SO_ACTION_ADD_METHOD(SoMaterialBinding, callDoAction); | |
| SO_ACTION_ADD_METHOD(SoMaterial, callDoAction); | |
| SO_ACTION_ADD_METHOD(SoNormalBinding, callDoAction); | |
| SO_ACTION_ADD_METHOD(SoGroup, callDoAction); | |
| SO_ACTION_ADD_METHOD(SoIndexedLineSet, callDoAction); | |
| SO_ACTION_ADD_METHOD(SoIndexedFaceSet, callDoAction); | |
| SO_ACTION_ADD_METHOD(SoPointSet, callDoAction); | |
| } | |
| SoVRMLAction::SoVRMLAction() | |
| { | |
| SO_ACTION_CONSTRUCTOR(SoVRMLAction); | |
| } | |
| SoVRMLAction::~SoVRMLAction() = default; | |
| void SoVRMLAction::setOverrideMode(SbBool on) | |
| { | |
| overrideMode = on; | |
| } | |
| SbBool SoVRMLAction::isOverrideMode() const | |
| { | |
| return overrideMode; | |
| } | |
| void SoVRMLAction::callDoAction(SoAction* action, SoNode* node) | |
| { | |
| if (node->getTypeId().isDerivedFrom(SoNormalBinding::getClassTypeId()) | |
| && action->isOfType(SoVRMLAction::getClassTypeId())) { | |
| auto vrmlAction = static_cast<SoVRMLAction*>(action); | |
| if (vrmlAction->overrideMode) { | |
| auto bind = static_cast<SoNormalBinding*>(node); | |
| vrmlAction->bindList.push_back(bind->value.getValue()); | |
| // this normal binding causes some problems for the part view provider | |
| // See also #0002222: Number of normals in exported VRML is wrong | |
| if (bind->value.getValue() == static_cast<int>(SoNormalBinding::PER_VERTEX_INDEXED)) { | |
| bind->value = SoNormalBinding::OVERALL; | |
| } | |
| } | |
| else if (!vrmlAction->bindList.empty()) { | |
| static_cast<SoNormalBinding*>(node)->value = static_cast<SoNormalBinding::Binding>( | |
| vrmlAction->bindList.front() | |
| ); | |
| vrmlAction->bindList.pop_front(); | |
| } | |
| } | |
| node->doAction(action); | |
| } | |
| // --------------------------------------------------------------------------------- | |
| bool SoFCSelectionRoot::StackComp::operator()(const Stack& a, const Stack& b) const | |
| { | |
| if (a.size() - a.offset < b.size() - b.offset) { | |
| return true; | |
| } | |
| if (a.size() - a.offset > b.size() - b.offset) { | |
| return false; | |
| } | |
| auto it1 = a.rbegin(), end1 = a.rend() - a.offset; | |
| auto it2 = b.rbegin(); | |
| for (; it1 != end1; ++it1, ++it2) { | |
| if (*it1 < *it2) { | |
| return true; | |
| } | |
| if (*it1 > *it2) { | |
| return false; | |
| } | |
| } | |
| return false; | |
| } | |
| // --------------------------------------------------------------------------------- | |
| SoSeparator::CacheEnabled SoFCSeparator::CacheMode = SoSeparator::AUTO; | |
| SO_NODE_SOURCE(SoFCSeparator) | |
| SoFCSeparator::SoFCSeparator(bool trackCacheMode) | |
| : trackCacheMode(trackCacheMode) | |
| { | |
| SO_NODE_CONSTRUCTOR(SoFCSeparator); | |
| if (!trackCacheMode) { | |
| renderCaching = SoSeparator::OFF; | |
| boundingBoxCaching = SoSeparator::OFF; | |
| } | |
| } | |
| void SoFCSeparator::GLRenderBelowPath(SoGLRenderAction* action) | |
| { | |
| if (trackCacheMode && renderCaching.getValue() != CacheMode) { | |
| renderCaching = CacheMode; | |
| boundingBoxCaching = CacheMode; | |
| } | |
| inherited::GLRenderBelowPath(action); | |
| } | |
| void SoFCSeparator::initClass() | |
| { | |
| SO_NODE_INIT_CLASS(SoFCSeparator, SoSeparator, "FCSeparator"); | |
| } | |
| void SoFCSeparator::finish() | |
| { | |
| atexit_cleanup(); | |
| } | |
| // --------------------------------------------------------------------------------- | |
| // Thread local data for bounding box rendering | |
| // | |
| // The code is inspred by Coin SoLevelOfDetails.cpp. | |
| using SoFCBBoxRenderInfo = struct | |
| { | |
| SoGetBoundingBoxAction* bboxaction; | |
| SoCube* cube; | |
| SoColorPacker* packer; | |
| }; | |
| static void so_bbox_construct_data(void* closure) | |
| { | |
| auto data = static_cast<SoFCBBoxRenderInfo*>(closure); | |
| data->bboxaction = nullptr; | |
| data->cube = nullptr; | |
| data->packer = nullptr; | |
| } | |
| static void so_bbox_destruct_data(void* closure) | |
| { | |
| auto data = static_cast<SoFCBBoxRenderInfo*>(closure); | |
| delete data->bboxaction; | |
| if (data->cube) { | |
| data->cube->unref(); | |
| } | |
| delete data->packer; | |
| } | |
| static SbStorage* so_bbox_storage = nullptr; | |
| // called from atexit | |
| static void so_bbox_cleanup() | |
| { | |
| delete so_bbox_storage; | |
| } | |
| // --------------------------------------------------------------------------------- | |
| SoFCSelectionRoot::Stack SoFCSelectionRoot::SelStack; | |
| std::unordered_map<SoAction*, SoFCSelectionRoot::Stack> SoFCSelectionRoot::ActionStacks; | |
| SoFCSelectionRoot::ColorStack SoFCSelectionRoot::SelColorStack; | |
| SoFCSelectionRoot::ColorStack SoFCSelectionRoot::HlColorStack; | |
| SoFCSelectionRoot* SoFCSelectionRoot::ShapeColorNode; | |
| SO_NODE_SOURCE(SoFCSelectionRoot) | |
| SoFCSelectionRoot::SoFCSelectionRoot(bool trackCacheMode) | |
| : SoFCSeparator(trackCacheMode) | |
| { | |
| SO_NODE_CONSTRUCTOR(SoFCSelectionRoot); | |
| SO_NODE_ADD_FIELD(selectionStyle, (Full)); | |
| SO_NODE_DEFINE_ENUM_VALUE(SelectStyles, Full); | |
| SO_NODE_DEFINE_ENUM_VALUE(SelectStyles, Box); | |
| SO_NODE_DEFINE_ENUM_VALUE(SelectStyles, PassThrough); | |
| SO_NODE_SET_SF_ENUM_TYPE(selectionStyle, SelectStyles); | |
| } | |
| SoFCSelectionRoot::~SoFCSelectionRoot() = default; | |
| void SoFCSelectionRoot::initClass() | |
| { | |
| SO_NODE_INIT_CLASS(SoFCSelectionRoot, SoFCSeparator, "FCSelectionRoot"); | |
| so_bbox_storage | |
| = new SbStorage(sizeof(SoFCBBoxRenderInfo), so_bbox_construct_data, so_bbox_destruct_data); | |
| // cc_coin_atexit((coin_atexit_f*) so_bbox_cleanup); | |
| } | |
| void SoFCSelectionRoot::finish() | |
| { | |
| so_bbox_cleanup(); | |
| atexit_cleanup(); | |
| } | |
| SoNode* SoFCSelectionRoot::getCurrentRoot(bool front, SoNode* def) | |
| { | |
| if (!SelStack.empty()) { | |
| return front ? SelStack.front() : SelStack.back(); | |
| } | |
| return def; | |
| } | |
| SoFCSelectionContextBasePtr SoFCSelectionRoot::getNodeContext( | |
| Stack& stack, | |
| SoNode* node, | |
| SoFCSelectionContextBasePtr def | |
| ) | |
| { | |
| if (stack.empty()) { | |
| return def; | |
| } | |
| auto front = dynamic_cast<SoFCSelectionRoot*>(stack.front()); | |
| if (front == nullptr) { | |
| return SoFCSelectionContextBasePtr(); | |
| } | |
| stack.front() = node; | |
| auto it = front->contextMap.find(stack); | |
| stack.front() = front; | |
| if (it != front->contextMap.end()) { | |
| return it->second; | |
| } | |
| return {}; | |
| } | |
| SoFCSelectionContextBasePtr SoFCSelectionRoot::getNodeContext2( | |
| Stack& stack, | |
| SoNode* node, | |
| SoFCSelectionContextBase::MergeFunc* merge | |
| ) | |
| { | |
| SoFCSelectionContextBasePtr ret; | |
| if (stack.empty()) { | |
| return ret; | |
| } | |
| auto* back = dynamic_cast<SoFCSelectionRoot*>(stack.back()); | |
| if (back == nullptr || back->contextMap2.empty()) { | |
| return ret; | |
| } | |
| int status = 0; | |
| auto& map = back->contextMap2; | |
| stack.back() = node; | |
| for (stack.offset = 0; stack.offset < stack.size(); ++stack.offset) { | |
| auto it = map.find(stack); | |
| SoFCSelectionContextBasePtr ctx; | |
| if (it != map.end()) { | |
| ctx = it->second; | |
| } | |
| status | |
| = merge(status, ret, ctx, stack.offset == stack.size() - 1 ? nullptr : stack[stack.offset]); | |
| if (status < 0) { | |
| break; | |
| } | |
| } | |
| stack.offset = 0; | |
| stack.back() = back; | |
| return ret; | |
| } | |
| std::pair<bool, SoFCSelectionContextBasePtr*> SoFCSelectionRoot::findActionContext( | |
| SoAction* action, | |
| SoNode* _node, | |
| bool create, | |
| bool erase | |
| ) | |
| { | |
| std::pair<bool, SoFCSelectionContextBasePtr*> res(false, 0); | |
| if (action->isOfType(SoSelectionElementAction::getClassTypeId())) { | |
| res.first = static_cast<SoSelectionElementAction*>(action)->isSecondary(); | |
| } | |
| auto it = ActionStacks.find(action); | |
| if (it == ActionStacks.end() || it->second.empty()) { | |
| return res; | |
| } | |
| auto& stack = it->second; | |
| if (res.first) { | |
| auto back = dynamic_cast<SoFCSelectionRoot*>(stack.back()); | |
| if (back != nullptr) { | |
| stack.back() = _node; | |
| if (create) { | |
| res.second = &back->contextMap2[stack]; | |
| } | |
| else { | |
| auto it = back->contextMap2.find(stack); | |
| if (it != back->contextMap2.end()) { | |
| res.second = &it->second; | |
| if (erase) { | |
| back->contextMap2.erase(it); | |
| } | |
| } | |
| } | |
| stack.back() = back; | |
| } | |
| } | |
| else { | |
| auto front = dynamic_cast<SoFCSelectionRoot*>(stack.front()); | |
| if (front != nullptr) { | |
| stack.front() = _node; | |
| if (create) { | |
| res.second = &front->contextMap[stack]; | |
| } | |
| else { | |
| auto it = front->contextMap.find(stack); | |
| if (it != front->contextMap.end()) { | |
| res.second = &it->second; | |
| if (erase) { | |
| front->contextMap.erase(it); | |
| } | |
| } | |
| } | |
| stack.front() = front; | |
| } | |
| } | |
| return res; | |
| } | |
| bool SoFCSelectionRoot::renderBBox(SoGLRenderAction* action, SoNode* node, SbColor color) | |
| { | |
| auto data = static_cast<SoFCBBoxRenderInfo*>(so_bbox_storage->get()); | |
| if (!data->bboxaction) { | |
| // The viewport region will be replaced every time the action is | |
| // used, so we can just feed it a dummy here. | |
| data->bboxaction = new SoGetBoundingBoxAction(SbViewportRegion()); | |
| data->cube = new SoCube; | |
| data->cube->ref(); | |
| data->packer = new SoColorPacker; | |
| } | |
| SbBox3f bbox; | |
| data->bboxaction->setViewportRegion(action->getViewportRegion()); | |
| data->bboxaction->apply(node); | |
| bbox = data->bboxaction->getBoundingBox(); | |
| if (bbox.isEmpty()) { | |
| return false; | |
| } | |
| auto state = action->getState(); | |
| state->push(); | |
| SoMaterialBindingElement::set(state, SoMaterialBindingElement::OVERALL); | |
| SoLazyElement::setEmissive(state, &color); | |
| SoLazyElement::setDiffuse(state, node, 1, &color, data->packer); | |
| SoDrawStyleElement::set(state, node, SoDrawStyleElement::LINES); | |
| SoLineWidthElement::set(state, node, 1.0f); | |
| const static float trans = 0.0; | |
| SoLazyElement::setTransparency(state, node, 1, &trans, data->packer); | |
| float x, y, z; | |
| bbox.getSize(x, y, z); | |
| data->cube->width = x + 0.001; | |
| data->cube->height = y + 0.001; | |
| data->cube->depth = z + 0.001; | |
| SoModelMatrixElement::translateBy(state, node, bbox.getCenter()); | |
| SoMaterialBundle mb(action); | |
| mb.sendFirst(); | |
| data->cube->GLRender(action); | |
| state->pop(); | |
| return true; | |
| } | |
| static std::time_t _CyclicLastReported; | |
| void SoFCSelectionRoot::renderPrivate(SoGLRenderAction* action, bool inPath) | |
| { | |
| if (ViewParams::instance()->getCoinCycleCheck() && !SelStack.nodeSet.insert(this).second) { | |
| std::time_t t = std::time(nullptr); | |
| if (_CyclicLastReported < t) { | |
| _CyclicLastReported = t + 5; | |
| FC_ERR("Cyclic scene graph: " << getName()); | |
| } | |
| return; | |
| } | |
| SelStack.push_back(this); | |
| if (_renderPrivate(action, inPath)) { | |
| if (inPath) { | |
| SoSeparator::GLRenderInPath(action); | |
| } | |
| else { | |
| SoSeparator::GLRenderBelowPath(action); | |
| } | |
| } | |
| SelStack.pop_back(); | |
| SelStack.nodeSet.erase(this); | |
| } | |
| bool SoFCSelectionRoot::_renderPrivate(SoGLRenderAction* action, bool inPath) | |
| { | |
| auto ctx2 = std::static_pointer_cast<SelContext>( | |
| getNodeContext2(SelStack, this, SelContext::merge) | |
| ); | |
| if (ctx2 && ctx2->hideAll) { | |
| return false; | |
| } | |
| auto state = action->getState(); | |
| SelContextPtr ctx = getRenderContext<SelContext>(this); | |
| int style = selectionStyle.getValue(); | |
| if ((style == SoFCSelectionRoot::Box || ViewParams::instance()->getShowSelectionBoundingBox()) | |
| && ctx && !ctx->hideAll && (ctx->selAll || ctx->hlAll)) { | |
| if (style == SoFCSelectionRoot::PassThrough) { | |
| style = SoFCSelectionRoot::Box; | |
| } | |
| else { | |
| renderBBox(action, this, ctx->hlAll ? ctx->hlColor : ctx->selColor); | |
| return true; | |
| } | |
| } | |
| // Here, we are not setting (pre)selection color override here. | |
| // Instead, we are checking and setting up for any secondary context | |
| // color override. | |
| // | |
| // When the current selection style is full highlight, we should ignore any | |
| // secondary override. If the style is bounding box, however, we should | |
| // honour the secondary color override. | |
| bool colorPushed = false; | |
| if (!ShapeColorNode && overrideColor && !SoOverrideElement::getDiffuseColorOverride(state) | |
| && (style == SoFCSelectionRoot::Box || !ctx || (!ctx->selAll && !ctx->hideAll))) { | |
| ShapeColorNode = this; | |
| colorPushed = true; | |
| state->push(); | |
| auto& packer = ShapeColorNode->shapeColorPacker; | |
| auto& trans = ShapeColorNode->transOverride; | |
| auto& color = ShapeColorNode->colorOverride; | |
| if (!SoOverrideElement::getTransparencyOverride(state)) { | |
| SoLazyElement::setTransparency(state, ShapeColorNode, 1, &trans, &packer); | |
| SoOverrideElement::setTransparencyOverride(state, ShapeColorNode, true); | |
| } | |
| SoLazyElement::setDiffuse(state, ShapeColorNode, 1, &color, &packer); | |
| SoOverrideElement::setDiffuseColorOverride(state, ShapeColorNode, true); | |
| SoMaterialBindingElement::set(state, ShapeColorNode, SoMaterialBindingElement::OVERALL); | |
| SoOverrideElement::setMaterialBindingOverride(state, ShapeColorNode, true); | |
| SoTextureEnabledElement::set(state, ShapeColorNode, false); | |
| } | |
| if (!ctx) { | |
| if (inPath) { | |
| SoSeparator::GLRenderInPath(action); | |
| } | |
| else { | |
| SoSeparator::GLRenderBelowPath(action); | |
| } | |
| } | |
| else { | |
| bool selPushed; | |
| bool hlPushed; | |
| if ((selPushed = ctx->selAll)) { | |
| SelColorStack.push_back(ctx->selColor); | |
| if (style != SoFCSelectionRoot::Box) { | |
| state->push(); | |
| auto& color = SelColorStack.back(); | |
| SoLazyElement::setEmissive(state, &color); | |
| SoOverrideElement::setEmissiveColorOverride(state, this, true); | |
| if (SoLazyElement::getLightModel(state) == SoLazyElement::BASE_COLOR) { | |
| auto& packer = shapeColorPacker; | |
| SoLazyElement::setDiffuse(state, this, 1, &color, &packer); | |
| SoOverrideElement::setDiffuseColorOverride(state, this, true); | |
| SoMaterialBindingElement::set(state, this, SoMaterialBindingElement::OVERALL); | |
| SoOverrideElement::setMaterialBindingOverride(state, this, true); | |
| } | |
| } | |
| } | |
| if ((hlPushed = ctx->hlAll)) { | |
| HlColorStack.push_back(ctx->hlColor); | |
| } | |
| if (inPath) { | |
| SoSeparator::GLRenderInPath(action); | |
| } | |
| else { | |
| SoSeparator::GLRenderBelowPath(action); | |
| } | |
| if (selPushed) { | |
| SelColorStack.pop_back(); | |
| if (style != SoFCSelectionRoot::Box) { | |
| state->pop(); | |
| } | |
| } | |
| if (hlPushed) { | |
| HlColorStack.pop_back(); | |
| } | |
| } | |
| if (colorPushed) { | |
| ShapeColorNode = nullptr; | |
| state->pop(); | |
| } | |
| return false; | |
| } | |
| void SoFCSelectionRoot::GLRenderBelowPath(SoGLRenderAction* action) | |
| { | |
| renderPrivate(action, false); | |
| } | |
| void SoFCSelectionRoot::GLRenderInPath(SoGLRenderAction* action) | |
| { | |
| if (action->getCurPathCode() == SoAction::BELOW_PATH) { | |
| return GLRenderBelowPath(action); | |
| } | |
| renderPrivate(action, true); | |
| } | |
| bool SoFCSelectionRoot::checkColorOverride(SoState* state) | |
| { | |
| if (ShapeColorNode) { | |
| if (!SoOverrideElement::getDiffuseColorOverride(state)) { | |
| state->push(); | |
| auto& packer = ShapeColorNode->shapeColorPacker; | |
| auto& trans = ShapeColorNode->transOverride; | |
| auto& color = ShapeColorNode->colorOverride; | |
| if (!SoOverrideElement::getTransparencyOverride(state)) { | |
| SoLazyElement::setTransparency(state, ShapeColorNode, 1, &trans, &packer); | |
| SoOverrideElement::setTransparencyOverride(state, ShapeColorNode, true); | |
| } | |
| SoLazyElement::setDiffuse(state, ShapeColorNode, 1, &color, &packer); | |
| SoOverrideElement::setDiffuseColorOverride(state, ShapeColorNode, true); | |
| SoMaterialBindingElement::set(state, ShapeColorNode, SoMaterialBindingElement::OVERALL); | |
| SoOverrideElement::setMaterialBindingOverride(state, ShapeColorNode, true); | |
| SoTextureEnabledElement::set(state, ShapeColorNode, false); | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| void SoFCSelectionRoot::checkSelection(bool& sel, SbColor& selColor, bool& hl, SbColor& hlColor) | |
| { | |
| sel = false; | |
| hl = false; | |
| if ((sel = !SelColorStack.empty())) { | |
| selColor = SelColorStack.back(); | |
| } | |
| if ((hl = !HlColorStack.empty())) { | |
| hlColor = HlColorStack.back(); | |
| } | |
| } | |
| void SoFCSelectionRoot::resetContext() | |
| { | |
| contextMap.clear(); | |
| } | |
| void SoFCSelectionRoot::moveActionStack(SoAction* from, SoAction* to, bool erase) | |
| { | |
| auto it = ActionStacks.find(from); | |
| if (it == ActionStacks.end()) { | |
| return; | |
| } | |
| auto& stack = ActionStacks[to]; | |
| assert(stack.empty()); | |
| stack.swap(it->second); | |
| if (erase) { | |
| ActionStacks.erase(it); | |
| } | |
| } | |
| auto& stack = ActionStacks[action]; \ | |
| if (ViewParams::instance()->getCoinCycleCheck() && !stack.nodeSet.insert(this).second) { \ | |
| std::time_t t = std::time(0); \ | |
| if (_CyclicLastReported < t) { \ | |
| _CyclicLastReported = t + 5; \ | |
| FC_ERR("Cyclic scene graph: " << getName()); \ | |
| } \ | |
| return; \ | |
| } \ | |
| stack.push_back(this); \ | |
| auto size = stack.size(); | |
| if (stack.size() != size || stack.back() != this) \ | |
| FC_ERR("action stack fault"); \ | |
| else { \ | |
| stack.nodeSet.erase(this); \ | |
| stack.pop_back(); \ | |
| if (stack.empty()) \ | |
| ActionStacks.erase(action); \ | |
| } | |
| void SoFCSelectionRoot::pick(SoPickAction* action) | |
| { | |
| BEGIN_ACTION; | |
| if (doActionPrivate(stack, action)) { | |
| inherited::pick(action); | |
| } | |
| END_ACTION; | |
| } | |
| void SoFCSelectionRoot::rayPick(SoRayPickAction* action) | |
| { | |
| BEGIN_ACTION; | |
| if (doActionPrivate(stack, action)) { | |
| inherited::rayPick(action); | |
| } | |
| END_ACTION; | |
| } | |
| void SoFCSelectionRoot::handleEvent(SoHandleEventAction* action) | |
| { | |
| BEGIN_ACTION; | |
| inherited::handleEvent(action); | |
| END_ACTION; | |
| } | |
| void SoFCSelectionRoot::search(SoSearchAction* action) | |
| { | |
| BEGIN_ACTION; | |
| inherited::search(action); | |
| END_ACTION; | |
| } | |
| void SoFCSelectionRoot::getPrimitiveCount(SoGetPrimitiveCountAction* action) | |
| { | |
| BEGIN_ACTION; | |
| inherited::getPrimitiveCount(action); | |
| END_ACTION; | |
| } | |
| void SoFCSelectionRoot::getBoundingBox(SoGetBoundingBoxAction* action) | |
| { | |
| BEGIN_ACTION; | |
| if (doActionPrivate(stack, action)) { | |
| inherited::getBoundingBox(action); | |
| } | |
| END_ACTION; | |
| } | |
| void SoFCSelectionRoot::getMatrix(SoGetMatrixAction* action) | |
| { | |
| BEGIN_ACTION; | |
| if (doActionPrivate(stack, action)) { | |
| inherited::getMatrix(action); | |
| } | |
| END_ACTION; | |
| } | |
| void SoFCSelectionRoot::callback(SoCallbackAction* action) | |
| { | |
| BEGIN_ACTION; | |
| inherited::callback(action); | |
| END_ACTION; | |
| } | |
| void SoFCSelectionRoot::doAction(SoAction* action) | |
| { | |
| BEGIN_ACTION | |
| if (doActionPrivate(stack, action)) { | |
| inherited::doAction(action); | |
| } | |
| END_ACTION | |
| } | |
| bool SoFCSelectionRoot::doActionPrivate(Stack& stack, SoAction* action) | |
| { | |
| // Selection action short-circuit optimization. In case of whole object | |
| // selection/preselection, we shall store a SelContext keyed by ourself. | |
| // And the action traversal can be short-curcuited once the first targeted | |
| // SoFCSelectionRoot is found here. New function checkSelection() is exposed | |
| // to check for whole object selection. This greatly improve performance on | |
| // large group. | |
| SelContextPtr ctx2; | |
| bool ctx2Searched = false; | |
| bool isTail = false; | |
| if (action->getCurPathCode() == SoAction::IN_PATH) { | |
| auto path = action->getPathAppliedTo(); | |
| if (path) { | |
| isTail = path->getTail() == this | |
| || (path->getLength() > 1 && path->getNodeFromTail(1) == this | |
| && path->getTail()->isOfType(SoSwitch::getClassTypeId())); | |
| } | |
| if (!action->isOfType(SoSelectionElementAction::getClassTypeId())) { | |
| ctx2Searched = true; | |
| ctx2 = std::static_pointer_cast<SelContext>( | |
| getNodeContext2(stack, this, SelContext::merge) | |
| ); | |
| if (ctx2 && ctx2->hideAll) { | |
| return false; | |
| } | |
| } | |
| if (!isTail) { | |
| return true; | |
| } | |
| } | |
| else if (action->getWhatAppliedTo() != SoAction::NODE | |
| && action->getCurPathCode() != SoAction::BELOW_PATH) { | |
| return true; | |
| } | |
| if (action->isOfType(SoSelectionElementAction::getClassTypeId())) { | |
| auto selAction = static_cast<SoSelectionElementAction*>(action); | |
| if (selAction->isSecondary()) { | |
| if (selAction->getType() == SoSelectionElementAction::Show | |
| || (selAction->getType() == SoSelectionElementAction::Color | |
| && selAction->getColors().empty() | |
| && action->getWhatAppliedTo() == SoAction::NODE)) { | |
| auto ctx = getActionContext(action, this, SelContextPtr(), false); | |
| if (ctx && ctx->hideAll) { | |
| ctx->hideAll = false; | |
| if (!ctx->hlAll && !ctx->selAll) { | |
| removeActionContext(action, this); | |
| } | |
| touch(); | |
| } | |
| // applied to a node means clear all visibility setting, so | |
| // return true to propagate the action | |
| return selAction->getType() == SoSelectionElementAction::Color | |
| || action->getWhatAppliedTo() == SoAction::NODE; | |
| } | |
| else if (selAction->getType() == SoSelectionElementAction::Hide) { | |
| if (action->getCurPathCode() == SoAction::BELOW_PATH || isTail) { | |
| auto ctx = getActionContext(action, this, SelContextPtr()); | |
| if (ctx && !ctx->hideAll) { | |
| ctx->hideAll = true; | |
| touch(); | |
| } | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| if (selAction->getType() == SoSelectionElementAction::None) { | |
| if (action->getWhatAppliedTo() == SoAction::NODE) { | |
| // Here the 'select none' action is applied to a node, and we | |
| // are the first SoFCSelectionRoot encountered (which means all | |
| // children stores selection context here, both whole object | |
| // and element selection), then we can simply perform the | |
| // action by clearing the selection context here, and save the | |
| // time for traversing a potentially large amount of children | |
| // nodes. | |
| resetContext(); | |
| touch(); | |
| return false; | |
| } | |
| auto ctx = getActionContext(action, this, SelContextPtr(), false); | |
| if (ctx && ctx->selAll) { | |
| ctx->selAll = false; | |
| touch(); | |
| return false; | |
| } | |
| } | |
| else if (selAction->getType() == SoSelectionElementAction::All) { | |
| auto ctx = getActionContext(action, this, SelContextPtr()); | |
| assert(ctx); | |
| ctx->selAll = true; | |
| ctx->selColor = selAction->getColor(); | |
| touch(); | |
| return false; | |
| } | |
| return true; | |
| } | |
| if (action->isOfType(SoHighlightElementAction::getClassTypeId())) { | |
| auto highlightAction = static_cast<SoHighlightElementAction*>(action); | |
| if (highlightAction->isHighlighted()) { | |
| if (highlightAction->getElement()) { | |
| auto ctx = getActionContext(action, this, SelContextPtr(), false); | |
| if (ctx && ctx->hlAll) { | |
| ctx->hlAll = false; | |
| touch(); | |
| } | |
| } | |
| else { | |
| auto ctx = getActionContext(action, this, SelContextPtr()); | |
| assert(ctx); | |
| ctx->hlAll = true; | |
| ctx->hlColor = highlightAction->getColor(); | |
| touch(); | |
| return false; | |
| } | |
| } | |
| else { | |
| auto ctx = getActionContext(action, this, SelContextPtr(), false); | |
| if (ctx && ctx->hlAll) { | |
| ctx->hlAll = false; | |
| touch(); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| if (!ctx2Searched) { | |
| ctx2 = std::static_pointer_cast<SelContext>(getNodeContext2(stack, this, SelContext::merge)); | |
| if (ctx2 && ctx2->hideAll) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| int SoFCSelectionRoot::SelContext::merge( | |
| int status, | |
| SoFCSelectionContextBasePtr& output, | |
| SoFCSelectionContextBasePtr input, | |
| SoNode* | |
| ) | |
| { | |
| auto ctx = std::dynamic_pointer_cast<SelContext>(input); | |
| if (ctx && ctx->hideAll) { | |
| output = ctx; | |
| return -1; | |
| } | |
| return status; | |
| } | |
| ///////////////////////////////////////////////////////////////////////////// | |
| SO_NODE_SOURCE(SoFCPathAnnotation) | |
| SoFCPathAnnotation::SoFCPathAnnotation() | |
| { | |
| SO_NODE_CONSTRUCTOR(SoFCPathAnnotation); | |
| path = nullptr; | |
| tmpPath = nullptr; | |
| det = nullptr; | |
| } | |
| SoFCPathAnnotation::~SoFCPathAnnotation() | |
| { | |
| if (path) { | |
| path->unref(); | |
| } | |
| if (tmpPath) { | |
| tmpPath->unref(); | |
| } | |
| delete det; | |
| } | |
| void SoFCPathAnnotation::finish() | |
| { | |
| atexit_cleanup(); | |
| } | |
| void SoFCPathAnnotation::initClass() | |
| { | |
| SO_NODE_INIT_CLASS(SoFCPathAnnotation, SoSeparator, "Separator"); | |
| } | |
| void SoFCPathAnnotation::GLRender(SoGLRenderAction* action) | |
| { | |
| switch (action->getCurPathCode()) { | |
| case SoAction::NO_PATH: | |
| case SoAction::BELOW_PATH: | |
| this->GLRenderBelowPath(action); | |
| break; | |
| case SoAction::OFF_PATH: | |
| break; | |
| case SoAction::IN_PATH: | |
| this->GLRenderInPath(action); | |
| break; | |
| } | |
| } | |
| void SoFCPathAnnotation::GLRenderBelowPath(SoGLRenderAction* action) | |
| { | |
| if (!path || !path->getLength() || !tmpPath || !tmpPath->getLength()) { | |
| return; | |
| } | |
| if (path->getLength() != tmpPath->getLength()) { | |
| // The auditing SoPath may be truncated due to harmless things such as | |
| // flipping a SoSwitch sibling node. So we keep an unauditing SoTempPath | |
| // around to try to restore the path. | |
| for (int i = path->getLength() - 1; i < tmpPath->getLength() - 1; ++i) { | |
| auto children = path->getNode(i)->getChildren(); | |
| if (children) { | |
| int idx = children->find(tmpPath->getNode(i + 1)); | |
| if (idx >= 0) { | |
| path->append(idx); | |
| continue; | |
| } | |
| } | |
| tmpPath->unref(); | |
| tmpPath = nullptr; | |
| return; | |
| } | |
| } | |
| SoState* state = action->getState(); | |
| SoGLCacheContextElement::shouldAutoCache(state, SoGLCacheContextElement::DONT_AUTO_CACHE); | |
| if (action->isRenderingDelayedPaths()) { | |
| SbBool zbenabled = glIsEnabled(GL_DEPTH_TEST); | |
| if (zbenabled) { | |
| glDisable(GL_DEPTH_TEST); | |
| } | |
| if (det) { | |
| inherited::GLRenderInPath(action); | |
| } | |
| else { | |
| bool bbox = ViewParams::instance()->getShowSelectionBoundingBox(); | |
| if (!bbox) { | |
| for (int i = 0, count = path->getLength(); i < count; ++i) { | |
| if (!path->getNode(i)->isOfType(SoFCSelectionRoot::getClassTypeId())) { | |
| continue; | |
| } | |
| auto node = dynamic_cast<SoFCSelectionRoot*>(path->getNode(i)); | |
| if (node != nullptr && node->selectionStyle.getValue() == SoFCSelectionRoot::Box) { | |
| bbox = true; | |
| break; | |
| } | |
| } | |
| } | |
| if (!bbox) { | |
| inherited::GLRenderInPath(action); | |
| } | |
| else { | |
| bool sel = false; | |
| bool hl = false; | |
| SbColor selColor, hlColor; | |
| SoFCSelectionRoot::checkSelection(sel, selColor, hl, hlColor); | |
| if (sel || hl) { | |
| SoFCSelectionRoot::renderBBox(action, this, hl ? hlColor : selColor); | |
| } | |
| else { | |
| inherited::GLRenderInPath(action); | |
| } | |
| } | |
| } | |
| if (zbenabled) { | |
| glEnable(GL_DEPTH_TEST); | |
| } | |
| } | |
| else { | |
| SoCacheElement::invalidate(action->getState()); | |
| auto curPath = action->getCurPath(); | |
| auto newPath = new SoPath(curPath->getLength() + path->getLength()); | |
| newPath->append(curPath); | |
| newPath->append(path); | |
| action->addDelayedPath(newPath); | |
| } | |
| } | |
| void SoFCPathAnnotation::GLRenderInPath(SoGLRenderAction* action) | |
| { | |
| GLRenderBelowPath(action); | |
| } | |
| void SoFCPathAnnotation::setDetail(SoDetail* d) | |
| { | |
| if (d != det) { | |
| delete det; | |
| det = d; | |
| } | |
| } | |
| void SoFCPathAnnotation::setPath(SoPath* newPath) | |
| { | |
| if (path) { | |
| path->unref(); | |
| coinRemoveAllChildren(this); | |
| path = nullptr; | |
| if (tmpPath) { | |
| tmpPath->unref(); | |
| tmpPath = nullptr; | |
| } | |
| } | |
| if (!newPath || !newPath->getLength()) { | |
| return; | |
| } | |
| tmpPath = new SoTempPath(newPath->getLength()); | |
| tmpPath->ref(); | |
| for (int i = 0; i < newPath->getLength(); ++i) { | |
| tmpPath->append(newPath->getNode(i)); | |
| } | |
| path = newPath->copy(); | |
| path->ref(); | |
| addChild(path->getNode(0)); | |
| } | |
| void SoFCPathAnnotation::getBoundingBox(SoGetBoundingBoxAction* action) | |
| { | |
| if (path) { | |
| SoGetBoundingBoxAction bboxAction(action->getViewportRegion()); | |
| SoFCSelectionRoot::moveActionStack(action, &bboxAction, false); | |
| bboxAction.apply(path); | |
| SoFCSelectionRoot::moveActionStack(&bboxAction, action, true); | |
| auto bbox = bboxAction.getBoundingBox(); | |
| if (!bbox.isEmpty()) { | |
| action->extendBy(bbox); | |
| } | |
| } | |
| } | |