/*************************************************************************** * 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 "PreCompiled.h" #ifndef _PreComp_ # 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 #endif #ifdef FC_OS_MACOSX # include #else # ifdef FC_OS_WIN32 # include # endif # include #endif #include #include #include #include #include #include "SoFCUnifiedSelection.h" #include "Application.h" #include "Document.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::currenthighlight = 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(highlightMode, (AUTO)); SO_NODE_ADD_FIELD(selectionMode, (ON)); SO_NODE_ADD_FIELD(selectionRole, (true)); SO_NODE_ADD_FIELD(useNewSelection, (true)); SO_NODE_DEFINE_ENUM_VALUE(HighlightModes, AUTO); SO_NODE_DEFINE_ENUM_VALUE(HighlightModes, ON); SO_NODE_DEFINE_ENUM_VALUE(HighlightModes, OFF); SO_NODE_SET_SF_ENUM_TYPE (highlightMode, HighlightModes); // 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 (currenthighlight) { currenthighlight->unref(); currenthighlight = 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 currenthighlight != nullptr; } void SoFCUnifiedSelection::applySettings() { float transparency; ParameterGrp::handle hGrp = Gui::WindowParameter::getDefaultParameter()->GetGroup("View"); bool enablePre = hGrp->GetBool("EnablePreselection", true); bool enableSel = hGrp->GetBool("EnableSelection", true); if (!enablePre) { this->highlightMode = 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); } if (!enableSel) { 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(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() == SoFCEnableHighlightAction::getClassTypeId()) { auto preaction = static_cast(action); if (preaction->highlight) { this->highlightMode = SoFCUnifiedSelection::AUTO; } else { this->highlightMode = SoFCUnifiedSelection::OFF; } } if (action->getTypeId() == SoFCEnableSelectionAction::getClassTypeId()) { auto selaction = static_cast(action); if (selaction->selection) { this->selectionMode = SoFCUnifiedSelection::ON; } else { this->selectionMode = SoFCUnifiedSelection::OFF; } } if (action->getTypeId() == SoFCSelectionColorAction::getClassTypeId()) { auto colaction = static_cast(action); this->colorSelection = colaction->selectionColor; } if (action->getTypeId() == SoFCHighlightColorAction::getClassTypeId()) { auto colaction = static_cast(action); this->colorHighlight = colaction->highlightColor; } if (action->getTypeId() == SoFCHighlightAction::getClassTypeId()) { auto hilaction = static_cast(action); // Do not clear currently highlighted object when setting new pre-selection if (!setPreSelection && hilaction->SelChange.Type == SelectionChanges::RmvPreselect) { if (currenthighlight) { SoHighlightElementAction hlAction; hlAction.apply(currenthighlight); currenthighlight->unref(); currenthighlight = nullptr; } } else if (highlightMode.getValue() != OFF && hilaction->SelChange.Type == SelectionChanges::SetPreselect) { if (currenthighlight) { SoHighlightElementAction hlAction; hlAction.apply(currenthighlight); currenthighlight->unref(); currenthighlight = nullptr; } App::Document* doc = App::GetApplication().getDocument(hilaction->SelChange.pDocName); App::DocumentObject* obj = doc->getObject(hilaction->SelChange.pObjectName); ViewProvider*vp = Application::Instance->getViewProvider(obj); SoDetail* detail = vp->getDetail(hilaction->SelChange.pSubName); SoHighlightElementAction hlAction; hlAction.setHighlighted(true); hlAction.setColor(this->colorHighlight.getValue()); hlAction.setElement(detail); hlAction.apply(vp->getRoot()); delete detail; SoSearchAction sa; sa.setNode(vp->getRoot()); sa.apply(vp->getRoot()); currenthighlight = static_cast(sa.getPath()->copy()); currenthighlight->ref(); } if (useNewSelection.getValue()) return; } if (action->getTypeId() == SoFCSelectionAction::getClassTypeId()) { auto selaction = static_cast(action); if(selectionMode.getValue() == ON && (selaction->SelChange.Type == SelectionChanges::AddSelection || selaction->SelChange.Type == SelectionChanges::RmvSelection)) { // selection changes inside the 3d view are handled in handleEvent() App::Document* doc = App::GetApplication().getDocument(selaction->SelChange.pDocName); App::DocumentObject* obj = doc->getObject(selaction->SelChange.pObjectName); ViewProvider*vp = Application::Instance->getViewProvider(obj); if (vp && (useNewSelection.getValue()||vp->useNewSelectionModel()) && vp->isSelectable()) { SoDetail *detail = nullptr; detailPath->truncate(0); auto subName = selaction->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(!selaction->SelChange.pSubName || !selaction->SelChange.pSubName[0] || vp->getDetailPath(subName,detailPath,true,detail)) { SoSelectionElementAction::Type type = SoSelectionElementAction::None; if (selaction->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 (selaction->SelChange.Type == SelectionChanges::ClrSelection) { SoSelectionElementAction selectionAction(SoSelectionElementAction::None); for(int i=0;igetNumChildren();++i) selectionAction.apply(this->getChild(i)); } else if(selectionMode.getValue() == ON && selaction->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 (selaction->SelChange.Type == SelectionChanges::SetPreselectSignal) { // selection changes inside the 3d view are handled in handleEvent() App::Document* doc = App::GetApplication().getDocument(selaction->SelChange.pDocName); App::DocumentObject* obj = doc->getObject(selaction->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(selaction->SelChange.pSubName,detailPath,true,det)) { setHighlight(detailPath,det,static_cast(vp), selaction->SelChange.pSubName, selaction->SelChange.x, selaction->SelChange.y, selaction->SelChange.z); } delete det; } } if (useNewSelection.getValue()) return; } inherited::doAction( action ); } bool SoFCUnifiedSelection::setHighlight(const PickedInfo &info) { if(!info.pp) return setHighlight(nullptr,nullptr,nullptr,nullptr,0.0,0.0,0.0); const auto &pt = info.pp->getPoint(); return setHighlight(static_cast(info.pp->getPath()), info.pp->getDetail(), info.vpd, info.element.c_str(), pt[0],pt[1],pt[2]); } bool SoFCUnifiedSelection::setHighlight(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 && currenthighlight) return true; if(ret) { if (currenthighlight) { SoHighlightElementAction action; action.setHighlighted(false); action.apply(currenthighlight); currenthighlight->unref(); currenthighlight = nullptr; } currenthighlight = static_cast(path->copy()); currenthighlight->ref(); highlighted = true; } } if(currenthighlight) { SoHighlightElementAction action; action.setHighlighted(highlighted); action.setColor(this->colorHighlight.getValue()); action.setElement(det); action.apply(currenthighlight); if(!highlighted) { currenthighlight->unref(); currenthighlight = 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 mymode = static_cast(this->highlightMode.getValue()); static char buf[513]; auto subName = info.element; std::string objectName = objname; if (ctrlDown) { if (Gui::Selection().isSelected(docname, objname, info.element.c_str(), ResolveMode::NoResolve)) Gui::Selection().rmvSelection(docname, objname, info.element.c_str(), &sels); else { getFullSubElementName(subName); bool ok = Gui::Selection().addSelection(docname, objname, subName.c_str(), pt[0], pt[1], pt[2], &sels); if (ok && mymode == OFF) { snprintf(buf, 512, "Selected: %s.%s.%s (%g, %g, %g)", docname, objname, info.element.c_str(), fabs(pt[0]) > 1e-7 ? pt[0] : 0.0, fabs(pt[1]) > 1e-7 ? pt[1] : 0.0, fabs(pt[2]) > 1e-7 ? pt[2] : 0.0); 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 { // Hierarchy ascending // // If the clicked subelement is already selected, check if there is an // upper hierarchy, and select that hierarchy instead. // // For example, let's suppose PickedInfo above reports // 'link.link2.box.Face1', and below Selection().getSelectedElement returns // 'link.link2.box.', meaning that 'box' is the current selected hierarchy, // and the user is clicking the box again. So we shall go up one level, // and select 'link.link2.' // // We need to convert the short name in the selection to a full element path to look it up // Ex: Body.Pad.Face9 to Body.Pad.;g3;SKT;:H12dc,E;FAC;:H12dc:4,F;:G0;XTR;:H12dc:8,F.Face9 getFullSubElementName(subName); const char *subSelected = Gui::Selection().getSelectedElement( vpd->getObject(), subName.c_str()); FC_TRACE("select " << (subSelected ? subSelected : "'null'") << ", " << objectName << ", " << subName); std::string newElement; if (subSelected) { newElement = Data::newElementName(subSelected); subSelected = newElement.c_str(); std::string nextsub; const char *next = strrchr(subSelected, '.'); if (next && next != subSelected) { if (next[1] == 0) { // The convention of dot separated SubName demands a mandatory // ending dot for every object name reference inside SubName. // The non-object sub-element, however, must not end with a dot. // So, next[1]==0 here means current selection is a whole object // selection (because no sub-element), so we shall search // upwards for the second last dot, which is the end of the // parent name of the current selected object for (--next; next != subSelected; --next) { if (*next == '.') break; } } if (*next == '.') nextsub = std::string(subSelected, next - subSelected + 1); } if (nextsub.length() || *subSelected != 0) { hasNext = true; subName = nextsub; detailPath->truncate(0); if (vpd->getDetailPath(subName.c_str(), detailPath, true, detNext) && detailPath->getLength()) { pPath = detailPath; det = detNext; FC_TRACE("select next " << objectName << ", " << subName); } } } FC_TRACE("clearing selection"); Gui::Selection().clearSelection(); FC_TRACE("add selection"); bool ok = Gui::Selection().addSelection(docname, objectName.c_str(), subName.c_str(), pt[0], pt[1], pt[2], &sels); if (ok) type = hasNext ? SoSelectionElementAction::All : SoSelectionElementAction::Append; if (mymode == OFF) { snprintf(buf, 512, "Selected: %s.%s.%s (%g, %g, %g)", docname, objectName.c_str(), subName.c_str(), fabs(pt[0]) > 1e-7 ? pt[0] : 0.0, fabs(pt[1]) > 1e-7 ? pt[1] : 0.0, fabs(pt[2]) > 1e-7 ? pt[2] : 0.0); getMainWindow()->showMessage(QString::fromLatin1(buf)); } } 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 (!selectionRole.getValue()) { inherited::handleEvent(action); return; } auto mymode = static_cast(this->highlightMode.getValue()); const SoEvent * event = action->getEvent(); // If we don't need to pick for locate highlighting, // then just behave as separator and return. // NOTE: we still have to pick for ON even though we don't have // to re-render, because the app needs to be notified as the mouse // goes over locate highlight nodes. //if (highlightMode.getValue() == OFF) { // inherited::handleEvent( action ); // return; //} // // If this is a mouseMotion event, then check for locate highlighting // if (event->isOfType(SoLocation2Event::getClassTypeId())) { // 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 (mymode == AUTO || mymode == ON) { // check to see if the mouse is over our geometry... auto infos = this->getPickedList(action,true); if(!infos.empty()) setHighlight(infos[0]); else { setHighlight(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; QtGLWidget* 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.offsetsecond; 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) && trans) { 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) && trans) { 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/pre-selection, 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 hlAction = static_cast(action); if(hlAction->isHighlighted()) { if(hlAction->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 = hlAction->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;igetLength()-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();igetNode(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;igetLength();++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); } }