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