// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2005 Jürgen Riegel * * * * 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 * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef FC_OS_MACOSX # include #else # ifdef FC_OS_WIN32 # include # endif # include #endif #include #include #include #include #include #include #include "SoFCUnifiedSelection.h" #include "Application.h" #include "Document.h" #include "DocumentObserver.h" #include "MainWindow.h" #include "SoFCInteractiveElement.h" #include "SoFCSelectionAction.h" #include "ViewParams.h" #include "ViewProvider.h" #include "ViewProviderDocumentObject.h" 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(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(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::getPickedList( SoHandleEventAction* action, bool singlePick ) const { ViewProvider* last_vp = nullptr; std::vector 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(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(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 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(action); if (enablePreselectionAction->enabled) { this->preselectionMode = SoFCUnifiedSelection::AUTO; } else { this->preselectionMode = SoFCUnifiedSelection::OFF; } } if (action->getTypeId() == SoFCEnableSelectionAction::getClassTypeId()) { auto enableSelectionAction = static_cast(action); if (enableSelectionAction->enabled) { this->selectionMode = SoFCUnifiedSelection::ON; } else { this->selectionMode = SoFCUnifiedSelection::OFF; } } if (action->getTypeId() == SoFCSelectionColorAction::getClassTypeId()) { auto selectionColorAction = static_cast(action); this->colorSelection = selectionColorAction->selectionColor; } if (action->getTypeId() == SoFCHighlightColorAction::getClassTypeId()) { auto highlightColorAction = static_cast(action); this->colorHighlight = highlightColorAction->highlightColor; } if (action->getTypeId() == SoFCPreselectionAction::getClassTypeId()) { auto preselectAction = static_cast(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(new SoPath(2)); pathToHighlight->ref(); pathToHighlight->append(vp->getRoot()); } } } else { detail = vp->getDetail(subName); pathToHighlight = static_cast(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(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(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 vps; if (this->pcDocument) { vps = this->pcDocument->getViewProvidersOfType( ViewProviderDocumentObject::getClassTypeId() ); } for (const auto& vp : vps) { auto vpd = static_cast(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(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(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 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(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& infos, bool ctrlDown) { if (infos.empty() || !infos[0].vpd) { return false; } std::vector 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(pp->getPath()); const auto& pt = pp->getPoint(); SoSelectionElementAction::Type type = SoSelectionElementAction::None; auto preselectionMode = static_cast(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(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(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(action); if (vrmlAction->overrideMode) { auto bind = static_cast(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(SoNormalBinding::PER_VERTEX_INDEXED)) { bind->value = SoNormalBinding::OVERALL; } } else if (!vrmlAction->bindList.empty()) { static_cast(node)->value = static_cast( 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(closure); data->bboxaction = nullptr; data->cube = nullptr; data->packer = nullptr; } static void so_bbox_destruct_data(void* closure) { auto data = static_cast(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 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(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(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 SoFCSelectionRoot::findActionContext( SoAction* action, SoNode* _node, bool create, bool erase ) { std::pair res(false, 0); if (action->isOfType(SoSelectionElementAction::getClassTypeId())) { res.first = static_cast(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(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(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(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( getNodeContext2(SelStack, this, SelContext::merge) ); if (ctx2 && ctx2->hideAll) { return false; } auto state = action->getState(); SelContextPtr ctx = getRenderContext(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); } } #define BEGIN_ACTION \ 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(); #define END_ACTION \ 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( 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(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(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(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(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(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); } } }