Files
create/src/Gui/ViewProviderLink.cpp
PaddleStroke 0a010b1300 Core: move getPlacementProperty to be more accessible (#26088)
* Core: move getPlacementProperty to be more accessible

* Update DocumentObject.h

* Update DocumentObject.cpp

* Update ViewProviderDragger.h

* Update ViewProviderDragger.cpp

* Update DocumentObject.h

* Update DocumentObject.cpp

* Update ViewProviderLink.cpp

* Update DocumentObject.h

* Update DocumentObject.cpp

* Update DocumentObject.h
2025-12-12 11:59:03 +00:00

4009 lines
121 KiB
C++

/****************************************************************************
* Copyright (c) 2017 Zheng Lei (realthunder) <realthunder.dev@gmail.com> *
* *
* 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 <atomic>
#include <cctype>
#include <unordered_set>
#include <unordered_map>
#include <utility>
#include <deque>
#include <memory>
#include <algorithm>
#include <vector>
#include <set>
#include <map>
#include <string>
#include <boost/algorithm/string/predicate.hpp>
#include <Inventor/SoPickedPoint.h>
#include <Inventor/actions/SoGetBoundingBoxAction.h>
#include <Inventor/details/SoDetail.h>
#include <Inventor/draggers/SoCenterballDragger.h>
#include <Inventor/misc/SoChildList.h>
#include <Inventor/nodes/SoAnnotation.h>
#include <Inventor/nodes/SoCube.h>
#include <Inventor/nodes/SoDrawStyle.h>
#include <Inventor/nodes/SoPickStyle.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoShapeHints.h>
#include <Inventor/nodes/SoSurroundScale.h>
#include <Inventor/nodes/SoSwitch.h>
#include <Inventor/nodes/SoTransform.h>
#include <Inventor/sensors/SoNodeSensor.h>
#include <QApplication>
#include <QMenu>
#include <QCheckBox>
#include <boost/range.hpp>
#include <App/ElementNamingUtils.h>
#include <App/Document.h>
#include <Base/BoundBoxPy.h>
#include <Base/MatrixPy.h>
#include <Base/PlacementPy.h>
#include <Base/Tools.h>
#include "Action.h"
#include "MainWindow.h"
#include "ViewProviderLink.h"
#include "ViewProviderLinkPy.h"
#include "Application.h"
#include "BitmapFactory.h"
#include "Control.h"
#include "Inventor/Draggers/SoTransformDragger.h"
#include "LinkViewPy.h"
#include "Selection.h"
#include "SoFCUnifiedSelection.h"
#include "TaskTransform.h"
#include "TaskElementColors.h"
#include "View3DInventor.h"
#include "ViewParams.h"
#include "ViewProviderGeometryObject.h"
#include "ActionFunction.h"
#include "Command.h"
#include "Dialogs/DlgObjectSelection.h"
FC_LOG_LEVEL_INIT("App::Link", true, true)
using namespace Gui;
using namespace Base;
using CharRange = boost::iterator_range<const char*>;
////////////////////////////////////////////////////////////////////////////
static inline bool appendPathSafe(SoPath* path, SoNode* node)
{
if (path->getLength()) {
SoNode* tail = path->getTail();
const SoChildList* children = tail->getChildren();
if (!children || children->find((void*)node) < 0) {
return false;
}
}
path->append(node);
return true;
}
#ifdef FC_DEBUG
# define appendPath(_path, _node) \
do { \
if (!appendPathSafe(_path, _node)) \
FC_ERR("LinkView: coin path error"); \
} while (0)
#else
# define appendPath(_path, _node) (_path)->append(_node)
#endif
////////////////////////////////////////////////////////////////////////////
class Gui::LinkInfo
{
public:
std::atomic<int> ref;
using Connection = boost::signals2::scoped_connection;
Connection connChangeIcon;
ViewProviderDocumentObject* pcLinked;
std::unordered_set<Gui::LinkOwner*> links;
using Pointer = LinkInfoPtr;
SoNodeSensor sensor;
SoNodeSensor switchSensor;
SoNodeSensor childSensor;
SoNodeSensor transformSensor;
std::array<CoinPtr<SoSeparator>, LinkView::SnapshotMax> pcSnapshots;
std::array<CoinPtr<SoSwitch>, LinkView::SnapshotMax> pcSwitches;
CoinPtr<SoSwitch> pcLinkedSwitch;
// for group type view providers
CoinPtr<SoGroup> pcChildGroup;
using NodeMap = std::unordered_map<SoNode*, Pointer>;
NodeMap nodeMap;
std::map<qint64, QIcon> iconMap;
static ViewProviderDocumentObject* getView(App::DocumentObject* obj)
{
if (obj && obj->isAttachedToDocument()) {
Document* pDoc = Application::Instance->getDocument(obj->getDocument());
if (pDoc) {
ViewProvider* vp = pDoc->getViewProvider(obj);
if (vp && vp->isDerivedFrom<ViewProviderDocumentObject>()) {
return static_cast<ViewProviderDocumentObject*>(vp);
}
}
}
return nullptr;
}
static Pointer get(App::DocumentObject* obj, Gui::LinkOwner* owner)
{
return get(getView(obj), owner);
}
static Pointer get(ViewProviderDocumentObject* vp, LinkOwner* owner)
{
if (!vp) {
return Pointer();
}
auto ext = vp->getExtensionByType<ViewProviderLinkObserver>(true);
if (!ext) {
ext = new ViewProviderLinkObserver();
ext->initExtension(vp);
}
if (!ext->linkInfo) {
// extension can be created automatically when restored from document,
// with an empty linkInfo. So we need to check here.
ext->linkInfo = Pointer(new LinkInfo(vp));
ext->linkInfo->update();
}
if (owner) {
ext->linkInfo->links.insert(owner);
}
return ext->linkInfo;
}
static void sensorCB(void* data, SoSensor*)
{
static_cast<LinkInfo*>(data)->update();
}
static void switchSensorCB(void* data, SoSensor*)
{
static_cast<LinkInfo*>(data)->updateSwitch();
}
static void childSensorCB(void* data, SoSensor*)
{
static_cast<LinkInfo*>(data)->updateChildren();
}
static void transformSensorCB(void* data, SoSensor*)
{
auto self = static_cast<LinkInfo*>(data);
for (size_t i = 0; i < self->pcSnapshots.size(); ++i) {
if (self->pcSnapshots[i] && i != LinkView::SnapshotTransform) {
self->getSnapshot(i, true);
}
}
}
explicit LinkInfo(ViewProviderDocumentObject* vp)
: ref(0)
, pcLinked(vp)
{
FC_LOG("new link to " << pcLinked->getObject()->getFullName());
// NOLINTBEGIN
connChangeIcon = vp->signalChangeIcon.connect(std::bind(&LinkInfo::slotChangeIcon, this));
// NOLINTEND
vp->forceUpdate(true);
sensor.setFunction(sensorCB);
sensor.setData(this);
switchSensor.setFunction(switchSensorCB);
switchSensor.setData(this);
childSensor.setFunction(childSensorCB);
childSensor.setData(this);
transformSensor.setFunction(transformSensorCB);
transformSensor.setData(this);
}
~LinkInfo() = default;
bool checkName(const char* name) const
{
return isLinked() && strcmp(name, getLinkedName()) == 0;
}
void remove(LinkOwner* owner)
{
links.erase(owner);
}
bool isLinked() const
{
return pcLinked && pcLinked->getObject() && pcLinked->getObject()->isAttachedToDocument();
}
const char* getLinkedName() const
{
return pcLinked->getObject()->getDagKey();
}
const char* getLinkedLabel() const
{
return pcLinked->getObject()->Label.getValue();
}
const char* getLinkedNameSafe() const
{
if (isLinked()) {
return getLinkedName();
}
return "<nil>";
}
const char* getDocName() const
{
return pcLinked->getDocument()->getDocument()->getName();
}
void detach(bool unlink)
{
FC_LOG("link detach " << getLinkedNameSafe());
auto me = LinkInfoPtr(this);
if (unlink) {
while (!links.empty()) {
auto link = *links.begin();
links.erase(links.begin());
link->unlink(me);
}
}
sensor.detach();
switchSensor.detach();
childSensor.detach();
transformSensor.detach();
for (const auto& node : pcSnapshots) {
if (node) {
coinRemoveAllChildren(node);
}
}
for (const auto& node : pcSwitches) {
if (node) {
coinRemoveAllChildren(node);
}
}
pcLinkedSwitch.reset();
if (pcChildGroup) {
coinRemoveAllChildren(pcChildGroup);
pcChildGroup.reset();
}
pcLinked = nullptr;
connChangeIcon.disconnect();
}
void updateSwitch(SoSwitch* node = nullptr)
{
if (!isLinked() || !pcLinkedSwitch) {
return;
}
int index = pcLinkedSwitch->whichChild.getValue();
for (size_t i = 0; i < pcSwitches.size(); ++i) {
if (!pcSwitches[i] || (node && node != pcSwitches[i])) {
continue;
}
int count = pcSwitches[i]->getNumChildren();
if ((index < 0 && i == LinkView::SnapshotChild) || !count) {
pcSwitches[i]->whichChild = SO_SWITCH_NONE;
}
else if (count > pcLinked->getDefaultMode()) {
pcSwitches[i]->whichChild = pcLinked->getDefaultMode();
}
else {
pcSwitches[i]->whichChild = 0;
}
}
}
inline void addref()
{
++ref;
}
inline void release()
{
int r = --ref;
assert(r >= 0);
if (r == 0) {
delete this;
}
else if (r == 1) {
if (pcLinked) {
FC_LOG("link release " << getLinkedNameSafe());
auto ext = pcLinked->getExtensionByType<ViewProviderLinkObserver>(true);
if (ext && ext->linkInfo == this) {
pcLinked->forceUpdate(false);
detach(true);
ext->linkInfo.reset();
}
}
}
}
// MSVC has trouble with template argument dependent lookup in
// namespace. Have to put the below functions in global namespace.
#if defined(_MSC_VER)
friend void Gui::intrusive_ptr_add_ref(LinkInfo* px);
friend void Gui::intrusive_ptr_release(LinkInfo* px);
#else
friend inline void intrusive_ptr_add_ref(LinkInfo* px)
{
px->addref();
}
friend inline void intrusive_ptr_release(LinkInfo* px)
{
px->release();
}
#endif
bool isVisible() const
{
if (!isLinked()) {
return true;
}
int indices[] = {LinkView::SnapshotTransform, LinkView::SnapshotVisible};
for (int idx : indices) {
if (!pcSwitches[idx]) {
continue;
}
if (pcSwitches[idx]->whichChild.getValue() == SO_SWITCH_NONE) {
return false;
}
}
return true;
}
void setVisible(bool visible)
{
if (!isLinked()) {
return;
}
int indices[] = {LinkView::SnapshotTransform, LinkView::SnapshotVisible};
for (int idx : indices) {
if (!pcSwitches[idx]) {
continue;
}
if (!visible) {
pcSwitches[idx]->whichChild = SO_SWITCH_NONE;
}
else if (pcSwitches[idx]->getNumChildren() > pcLinked->getDefaultMode()) {
pcSwitches[idx]->whichChild = pcLinked->getDefaultMode();
}
}
}
SoSeparator* getSnapshot(int type, bool update = false)
{
if (type < 0 || type >= LinkView::SnapshotMax) {
return nullptr;
}
SoSeparator* root;
if (!isLinked() || !(root = pcLinked->getRoot())) {
return nullptr;
}
if (sensor.getAttachedNode() != root) {
sensor.detach();
sensor.attach(root);
}
auto& pcSnapshot = pcSnapshots[type];
auto& pcModeSwitch = pcSwitches[type];
if (pcSnapshot) {
if (!update) {
return pcSnapshot;
}
}
else {
if (ViewParams::instance()->getUseSelectionRoot()) {
pcSnapshot = new SoFCSelectionRoot;
}
else {
pcSnapshot = new SoSeparator;
}
pcSnapshot->boundingBoxCaching = SoSeparator::OFF;
pcSnapshot->renderCaching = SoSeparator::OFF;
std::ostringstream ss;
ss << pcLinked->getObject()->getNameInDocument() << "(" << type << ')';
pcSnapshot->setName(ss.str().c_str());
pcModeSwitch = new SoSwitch;
}
pcLinkedSwitch.reset();
coinRemoveAllChildren(pcSnapshot);
pcModeSwitch->whichChild = SO_SWITCH_NONE;
coinRemoveAllChildren(pcModeSwitch);
SoSwitch* pcUpdateSwitch = pcModeSwitch;
auto childRoot = pcLinked->getChildRoot();
for (int i = 0, count = root->getNumChildren(); i < count; ++i) {
SoNode* node = root->getChild(i);
// Exclude the linked object's pick style from the snapshot,
// so that the Link's own pick style is the only one in effect.
if (node->isOfType(SoPickStyle::getClassTypeId())) {
continue;
}
if (node == pcLinked->getTransformNode()) {
if (type != LinkView::SnapshotTransform) {
pcSnapshot->addChild(node);
}
else {
auto transform = pcLinked->getTransformNode();
const auto& scale = transform->scaleFactor.getValue();
if (scale[0] != 1.0 || scale[1] != 1.0 || scale[2] != 1.0) {
auto trans = new SoTransform;
pcSnapshot->addChild(trans);
trans->scaleFactor.setValue(scale);
trans->scaleOrientation = transform->scaleOrientation;
if (transformSensor.getAttachedNode() != transform) {
transformSensor.detach();
transformSensor.attach(transform);
}
}
}
continue;
}
else if (node != pcLinked->getModeSwitch()) {
pcSnapshot->addChild(node);
continue;
}
pcLinkedSwitch = static_cast<SoSwitch*>(node);
if (switchSensor.getAttachedNode() != pcLinkedSwitch) {
switchSensor.detach();
switchSensor.attach(pcLinkedSwitch);
pcUpdateSwitch = nullptr;
}
pcSnapshot->addChild(pcModeSwitch);
for (int i = 0, count = pcLinkedSwitch->getNumChildren(); i < count; ++i) {
auto child = pcLinkedSwitch->getChild(i);
if (pcChildGroup && child == childRoot) {
pcModeSwitch->addChild(pcChildGroup);
}
else {
pcModeSwitch->addChild(child);
}
}
}
updateSwitch(pcUpdateSwitch);
return pcSnapshot;
}
void updateData(const App::Property* prop)
{
LinkInfoPtr me(this);
for (auto link : links) {
link->onLinkedUpdateData(me, prop);
}
// update();
}
void update()
{
if (!isLinked() || pcLinked->isRestoring()) {
return;
}
updateChildren();
for (size_t i = 0; i < pcSnapshots.size(); ++i) {
if (pcSnapshots[i]) {
getSnapshot(i, true);
}
}
}
void updateChildren()
{
if (!isLinked()) {
return;
}
if (!pcLinked->getChildRoot()) {
childSensor.detach();
if (pcChildGroup) {
coinRemoveAllChildren(pcChildGroup);
}
return;
}
if (childSensor.getAttachedNode() != pcLinked->getChildRoot()) {
childSensor.detach();
childSensor.attach(pcLinked->getChildRoot());
}
if (!pcChildGroup) {
pcChildGroup = new SoGroup;
}
else {
coinRemoveAllChildren(pcChildGroup);
}
NodeMap nodeMap;
for (auto child : pcLinked->claimChildren3D()) {
Pointer info = get(child, nullptr);
if (!info) {
continue;
}
SoNode* node = info->getSnapshot(LinkView::SnapshotChild);
if (!node) {
continue;
}
nodeMap[node] = info;
pcChildGroup->addChild(node);
}
// Use swap instead of clear() here to avoid potential link
// destruction
this->nodeMap.swap(nodeMap);
}
bool getElementPicked(bool addname, int type, const SoPickedPoint* pp, std::ostream& str) const
{
if (!pp || !isLinked()) {
return false;
}
if (addname) {
str << getLinkedName() << '.';
}
auto pcSwitch = pcSwitches[type];
if (pcChildGroup && pcSwitch && pcSwitch->whichChild.getValue() >= 0
&& pcSwitch->getChild(pcSwitch->whichChild.getValue()) == pcChildGroup) {
SoPath* path = pp->getPath();
int index = path->findNode(pcChildGroup);
if (index <= 0) {
return false;
}
auto it = nodeMap.find(path->getNode(index + 1));
if (it == nodeMap.end()) {
return false;
}
return it->second->getElementPicked(true, LinkView::SnapshotChild, pp, str);
}
else {
std::string subname;
if (!pcLinked->getElementPicked(pp, subname)) {
return false;
}
str << subname;
}
return true;
}
static const char* checkSubname(App::DocumentObject* obj, const char* subname)
{
#define CHECK_NAME(_name, _end) \
do { \
if (!_name) \
return 0; \
const char* _n = _name; \
for (; *subname && *_n; ++subname, ++_n) \
if (*subname != *_n) \
break; \
if (*_n || (*subname != 0 && *subname != _end)) \
return 0; \
if (*subname == _end) \
++subname; \
} while (0)
// if(subname[0] == '*') {
// ++subname;
// CHECK_NAME(obj->getDocument()->getName(),'*');
// }
CHECK_NAME(obj->getNameInDocument(), '.');
return subname;
}
bool getDetail(bool checkname, int type, const char* subname, SoDetail*& det, SoFullPath* path) const
{
if (!isLinked()) {
return false;
}
if (checkname) {
subname = checkSubname(pcLinked->getObject(), subname);
if (!subname) {
return false;
}
}
if (pcSnapshots[type]->findChild(pcSwitches[type]) < 0) {
if (path) {
if (!appendPathSafe(path, pcSnapshots[type])) {
return false;
}
}
// this is possible in case of editing, where the switch node
// of the linked view object is temparaly removed from its root
return true;
}
int len = 0;
if (path) {
len = path->getLength();
if (!appendPathSafe(path, pcSnapshots[type])) {
return false;
}
appendPath(path, pcSwitches[type]);
}
if (*subname == 0) {
return true;
}
auto pcSwitch = pcSwitches[type];
if (!pcChildGroup || !pcSwitch || pcSwitch->whichChild.getValue() < 0
|| pcSwitch->getChild(pcSwitch->whichChild.getValue()) != pcChildGroup) {
return pcLinked->getDetailPath(subname, path, false, det);
}
if (path) {
appendPath(path, pcChildGroup);
if (pcLinked->getChildRoot()) {
type = LinkView::SnapshotChild;
}
else {
type = LinkView::SnapshotVisible;
}
}
// Special handling of nodes with childRoot, especially geo feature
// group. It's object hierarchy in the tree view (i.e. in subname) is
// difference from its coin hierarchy. All objects under a geo feature
// group is visually grouped directly under the group's childRoot,
// even though some object has secondary hierarchy in subname. E.g.
//
// Body
// |--Pad
// |--Sketch
//
// Both Sketch and Pad's coin nodes are grouped directly under Body as,
//
// Body
// |--Pad
// |--Sketch
const char* dot = strchr(subname, '.');
const char* nextsub = subname;
if (!dot) {
return false;
}
auto geoGroup = pcLinked->getObject();
auto sobj = geoGroup;
while (true) {
std::string objname = std::string(nextsub, dot - nextsub + 1);
if (!geoGroup->getSubObject(objname.c_str())) {
// this object is not found under the geo group, abort.
break;
}
// Object found under geo group, remember this subname
subname = nextsub;
auto ssobj = sobj->getSubObject(objname.c_str());
if (!ssobj) {
FC_ERR("invalid sub name " << nextsub << " of object " << sobj->getFullName());
return false;
}
sobj = ssobj;
auto vp = Application::Instance->getViewProvider(sobj);
if (!vp) {
FC_ERR("cannot find view provider of " << sobj->getFullName());
return false;
}
if (vp->getChildRoot()) {
// In case the children is also a geo group, it will visually
// hold all of its own children, so stop going further down.
break;
}
// new style mapped sub-element
if (Data::isMappedElement(dot + 1)) {
break;
}
auto next = strchr(dot + 1, '.');
if (!next) {
// no dot any more, the following must be a sub-element
break;
}
nextsub = dot + 1;
dot = next;
}
for (const auto& v : nodeMap) {
if (v.second->getDetail(true, type, subname, det, path)) {
return true;
}
}
if (path) {
path->truncate(len);
}
return false;
}
void slotChangeIcon()
{
iconMap.clear();
if (!isLinked()) {
return;
}
LinkInfoPtr me(this);
for (auto link : links) {
link->onLinkedIconChange(me);
}
}
QIcon getIcon(QPixmap px)
{
static int iconSize = -1;
if (iconSize < 0) {
auto sampleIcon = QApplication::style()->standardPixmap(QStyle::SP_DirClosedIcon);
double pixelRatio = sampleIcon.devicePixelRatio();
iconSize = static_cast<int>(sampleIcon.width() / pixelRatio);
}
if (!isLinked()) {
return QIcon();
}
if (px.isNull()) {
return pcLinked->getIcon();
}
QIcon& iconLink = iconMap[px.cacheKey()];
if (iconLink.isNull()) {
QIcon icon = pcLinked->getIcon();
iconLink = QIcon();
iconLink.addPixmap(
BitmapFactory().merge(
icon.pixmap(iconSize, iconSize, QIcon::Normal, QIcon::Off),
px,
BitmapFactoryInst::BottomLeft
),
QIcon::Normal,
QIcon::Off
);
iconLink.addPixmap(
BitmapFactory().merge(
icon.pixmap(iconSize, iconSize, QIcon::Normal, QIcon::On),
px,
BitmapFactoryInst::BottomLeft
),
QIcon::Normal,
QIcon::On
);
}
return iconLink;
}
};
#if defined(_MSC_VER)
namespace Gui
{
void intrusive_ptr_add_ref(Gui::LinkInfo* px)
{
px->addref();
}
void intrusive_ptr_release(Gui::LinkInfo* px)
{
px->release();
}
} // namespace Gui
#endif
////////////////////////////////////////////////////////////////////////////////////
EXTENSION_TYPESYSTEM_SOURCE(Gui::ViewProviderLinkObserver, Gui::ViewProviderExtension)
ViewProviderLinkObserver::ViewProviderLinkObserver()
{
// TODO: any better way to get deleted automatically?
m_isPythonExtension = true;
initExtensionType(ViewProviderLinkObserver::getExtensionClassTypeId());
}
ViewProviderLinkObserver::~ViewProviderLinkObserver()
{
if (linkInfo) {
linkInfo->detach(true);
linkInfo.reset();
}
}
bool ViewProviderLinkObserver::isLinkVisible() const
{
if (linkInfo) {
return linkInfo->isVisible();
}
return true;
}
void ViewProviderLinkObserver::setLinkVisible(bool visible)
{
if (linkInfo) {
linkInfo->setVisible(visible);
}
}
void ViewProviderLinkObserver::extensionBeforeDelete()
{
if (linkInfo) {
linkInfo->detach(false);
}
}
void ViewProviderLinkObserver::extensionReattach(App::DocumentObject*)
{
if (linkInfo) {
linkInfo->pcLinked = freecad_cast<ViewProviderDocumentObject*>(getExtendedContainer());
linkInfo->update();
}
}
void ViewProviderLinkObserver::extensionOnChanged(const App::Property* prop)
{
(void)prop;
}
void ViewProviderLinkObserver::extensionModeSwitchChange()
{
auto owner = freecad_cast<ViewProviderDocumentObject*>(getExtendedContainer());
if (owner && linkInfo) {
linkInfo->updateSwitch();
}
}
void ViewProviderLinkObserver::extensionUpdateData(const App::Property* prop)
{
if (linkInfo && linkInfo->pcLinked && linkInfo->pcLinked->getObject()
&& prop != &linkInfo->pcLinked->getObject()->Visibility) {
linkInfo->updateData(prop);
}
}
void ViewProviderLinkObserver::extensionFinishRestoring()
{
if (linkInfo) {
FC_TRACE("linked finish restoing");
linkInfo->update();
}
}
class LinkView::SubInfo: public LinkOwner
{
public:
LinkInfoPtr linkInfo;
LinkView& handle;
CoinPtr<SoSeparator> pcNode;
CoinPtr<SoTransform> pcTransform;
std::set<std::string> subElements;
friend LinkView;
SubInfo(LinkView& handle)
: handle(handle)
{
pcNode = new SoFCSelectionRoot(true);
pcTransform = new SoTransform;
pcNode->addChild(pcTransform);
}
~SubInfo() override
{
unlink();
auto root = handle.getLinkRoot();
if (root) {
int idx = root->findChild(pcNode);
if (idx >= 0) {
root->removeChild(idx);
}
}
}
void onLinkedIconChange(LinkInfoPtr) override
{
if (handle.autoSubLink && handle.subInfo.size() == 1) {
handle.onLinkedIconChange(handle.linkInfo);
}
}
void unlink(LinkInfoPtr info = LinkInfoPtr()) override
{
(void)info;
if (linkInfo) {
linkInfo->remove(this);
linkInfo.reset();
}
coinRemoveAllChildren(pcNode);
pcNode->addChild(pcTransform);
}
void link(App::DocumentObject* obj)
{
if (isLinked() && linkInfo->pcLinked->getObject() == obj) {
return;
}
unlink();
linkInfo = LinkInfo::get(obj, this);
if (linkInfo) {
pcNode->addChild(linkInfo->getSnapshot(LinkView::SnapshotTransform));
}
}
bool isLinked() const
{
return linkInfo && linkInfo->isLinked();
}
};
//////////////////////////////////////////////////////////////////////////////////
class LinkView::Element: public LinkOwner
{
public:
LinkInfoPtr linkInfo;
LinkView& handle;
CoinPtr<SoSwitch> pcSwitch;
CoinPtr<SoFCSelectionRoot> pcRoot;
CoinPtr<SoTransform> pcTransform;
int groupIndex = -1;
bool isGroup = false;
friend LinkView;
Element(LinkView& handle)
: handle(handle)
{
pcTransform = new SoTransform;
pcRoot = new SoFCSelectionRoot(true);
pcSwitch = new SoSwitch;
pcSwitch->addChild(pcRoot);
pcSwitch->whichChild = 0;
}
~Element() override
{
unlink();
auto root = handle.getLinkRoot();
if (root) {
int idx = root->findChild(pcRoot);
if (idx >= 0) {
root->removeChild(idx);
}
}
}
void unlink(LinkInfoPtr info = LinkInfoPtr()) override
{
(void)info;
if (linkInfo) {
linkInfo->remove(this);
linkInfo.reset();
}
coinRemoveAllChildren(pcRoot);
}
void link(App::DocumentObject* obj)
{
if (isLinked() && linkInfo->pcLinked->getObject() == obj) {
return;
}
unlink();
linkInfo = LinkInfo::get(obj, this);
if (isLinked()) {
pcRoot->addChild(linkInfo->getSnapshot(handle.childType));
}
}
bool isLinked() const
{
return linkInfo && linkInfo->isLinked();
}
};
///////////////////////////////////////////////////////////////////////////////////
TYPESYSTEM_SOURCE(Gui::LinkView, Base::BaseClass)
LinkView::LinkView()
: nodeType(SnapshotTransform)
, childType((SnapshotType)-1)
, autoSubLink(true)
{
pcLinkRoot = new SoFCSelectionRoot;
}
LinkView::~LinkView()
{
unlink(linkInfo);
unlink(linkOwner);
}
PyObject* LinkView::getPyObject()
{
if (PythonObject.is(Py::_None())) {
PythonObject = Py::Object(new LinkViewPy(this), true);
}
return Py::new_reference_to(PythonObject);
}
void LinkView::setInvalid()
{
if (!PythonObject.is(Py::_None())) {
auto obj = static_cast<Base::PyObjectBase*>(PythonObject.ptr());
obj->setInvalid();
obj->DecRef();
}
else {
delete this;
}
}
Base::BoundBox3d _getBoundBox(ViewProviderDocumentObject* vpd, SoNode* rootNode)
{
auto doc = vpd->getDocument();
if (!doc) {
LINK_THROW(Base::RuntimeError, "no document");
}
Gui::MDIView* view = doc->getViewOfViewProvider(vpd);
if (!view) {
LINK_THROW(Base::RuntimeError, "no view");
}
Gui::View3DInventorViewer* viewer = static_cast<Gui::View3DInventor*>(view)->getViewer();
SoGetBoundingBoxAction bboxAction(viewer->getSoRenderManager()->getViewportRegion());
bboxAction.apply(rootNode);
auto bbox = bboxAction.getBoundingBox();
float minX, minY, minZ, maxX, maxY, maxZ;
bbox.getMax().getValue(maxX, maxY, maxZ);
bbox.getMin().getValue(minX, minY, minZ);
return Base::BoundBox3d(minX, minY, minZ, maxX, maxY, maxZ);
}
Base::BoundBox3d LinkView::getBoundBox(ViewProviderDocumentObject* vpd) const
{
if (!vpd) {
if (!linkOwner || !linkOwner->isLinked()) {
LINK_THROW(Base::ValueError, "no ViewProvider");
}
vpd = linkOwner->pcLinked;
}
return _getBoundBox(vpd, pcLinkRoot);
}
ViewProviderDocumentObject* LinkView::getOwner() const
{
if (linkOwner && linkOwner->isLinked()) {
return linkOwner->pcLinked;
}
return nullptr;
}
void LinkView::setOwner(ViewProviderDocumentObject* vpd)
{
unlink(linkOwner);
linkOwner = LinkInfo::get(vpd, this);
}
bool LinkView::isLinked() const
{
return linkInfo && linkInfo->isLinked();
}
void LinkView::setDrawStyle(int style, double lineWidth, double pointSize)
{
if (!pcDrawStyle) {
if (!style) {
return;
}
pcDrawStyle = new SoDrawStyle;
pcDrawStyle->style = SoDrawStyle::FILLED;
pcLinkRoot->insertChild(pcDrawStyle, 0);
}
if (!style) {
pcDrawStyle->setOverride(false);
return;
}
pcDrawStyle->lineWidth = lineWidth;
pcDrawStyle->pointSize = pointSize;
switch (style) {
case 2:
pcDrawStyle->linePattern = 0xf00f;
break;
case 3:
pcDrawStyle->linePattern = 0x0f0f;
break;
case 4:
pcDrawStyle->linePattern = 0xff88;
break;
default:
pcDrawStyle->linePattern = 0xffff;
}
pcDrawStyle->setOverride(true);
}
void LinkView::renderDoubleSide(bool enable)
{
if (enable) {
if (!pcShapeHints) {
pcShapeHints = new SoShapeHints;
pcShapeHints->vertexOrdering = SoShapeHints::CLOCKWISE;
pcShapeHints->shapeType = SoShapeHints::UNKNOWN_SHAPE_TYPE;
pcLinkRoot->insertChild(pcShapeHints, 0);
}
pcShapeHints->setOverride(true);
}
else if (pcShapeHints) {
pcShapeHints->setOverride(false);
}
}
void LinkView::setMaterial(int index, const App::Material* material)
{
if (index < 0) {
if (!material) {
pcLinkRoot->removeColorOverride();
return;
}
Base::Color c = material->diffuseColor;
c.setTransparency(material->transparency);
pcLinkRoot->setColorOverride(c);
for (int i = 0; i < getSize(); ++i) {
setMaterial(i, nullptr);
}
}
else if (index >= (int)nodeArray.size()) {
LINK_THROW(Base::ValueError, "LinkView: material index out of range");
}
else {
auto& info = *nodeArray[index];
if (!material) {
info.pcRoot->removeColorOverride();
return;
}
Base::Color c = material->diffuseColor;
c.setTransparency(material->transparency);
info.pcRoot->setColorOverride(c);
}
}
void LinkView::setLink(App::DocumentObject* obj, const std::vector<std::string>& subs)
{
setLinkViewObject(
freecad_cast<ViewProviderDocumentObject*>(Application::Instance->getViewProvider(obj)),
subs
);
}
void LinkView::setLinkViewObject(ViewProviderDocumentObject* vpd, const std::vector<std::string>& subs)
{
if (!isLinked() || linkInfo->pcLinked != vpd) {
unlink(linkInfo);
linkInfo = LinkInfo::get(vpd, this);
if (!linkInfo) {
return;
}
}
subInfo.clear();
for (const auto& sub : subs) {
if (sub.empty()) {
continue;
}
const char* subelement = Data::findElementName(sub.c_str());
std::string subname = sub.substr(0, subelement - sub.c_str());
auto it = subInfo.find(subname);
if (it == subInfo.end()) {
it = subInfo.insert(std::make_pair(subname, std::unique_ptr<SubInfo>())).first;
it->second = std::make_unique<SubInfo>(*this);
}
if (subelement[0]) {
it->second->subElements.insert(subelement);
}
}
updateLink();
}
void LinkView::setTransform(SoTransform* pcTransform, const Base::Matrix4D& mat)
{
double dMtrx[16];
mat.getGLMatrix(dMtrx);
pcTransform->setMatrix(SbMatrix(
dMtrx[0],
dMtrx[1],
dMtrx[2],
dMtrx[3],
dMtrx[4],
dMtrx[5],
dMtrx[6],
dMtrx[7],
dMtrx[8],
dMtrx[9],
dMtrx[10],
dMtrx[11],
dMtrx[12],
dMtrx[13],
dMtrx[14],
dMtrx[15]
));
}
void LinkView::setSize(int _size)
{
size_t size = _size < 0 ? 0 : (size_t)_size;
if (childType < 0 && size == nodeArray.size()) {
return;
}
resetRoot();
if (!size || childType >= 0) {
nodeArray.clear();
nodeMap.clear();
if (!size && childType < 0) {
if (pcLinkedRoot) {
pcLinkRoot->addChild(pcLinkedRoot);
}
return;
}
childType = SnapshotContainer;
}
if (size < nodeArray.size()) {
for (size_t i = size; i < nodeArray.size(); ++i) {
nodeMap.erase(nodeArray[i]->pcSwitch);
}
nodeArray.resize(size);
}
for (const auto& info : nodeArray) {
pcLinkRoot->addChild(info->pcSwitch);
}
while (nodeArray.size() < size) {
nodeArray.push_back(std::make_unique<Element>(*this));
auto& info = *nodeArray.back();
info.pcRoot->addChild(info.pcTransform);
if (pcLinkedRoot) {
info.pcRoot->addChild(pcLinkedRoot);
}
pcLinkRoot->addChild(info.pcSwitch);
nodeMap.emplace(info.pcSwitch, (int)nodeArray.size() - 1);
}
}
void LinkView::resetRoot()
{
coinRemoveAllChildren(pcLinkRoot);
if (pcTransform) {
pcLinkRoot->addChild(pcTransform);
}
if (pcShapeHints) {
pcLinkRoot->addChild(pcShapeHints);
}
if (pcDrawStyle) {
pcLinkRoot->addChild(pcDrawStyle);
}
}
void LinkView::setChildren(
const std::vector<App::DocumentObject*>& children,
const boost::dynamic_bitset<>& vis,
SnapshotType type
)
{
if (children.empty()) {
if (!nodeArray.empty()) {
nodeArray.clear();
nodeMap.clear();
childType = SnapshotContainer;
resetRoot();
if (pcLinkedRoot) {
pcLinkRoot->addChild(pcLinkedRoot);
}
}
return;
}
if (type < 0 || type >= SnapshotMax) {
LINK_THROW(Base::ValueError, "invalid children type");
}
resetRoot();
if (childType < 0) {
nodeArray.clear();
}
childType = type;
if (nodeArray.size() > children.size()) {
nodeArray.resize(children.size());
}
else {
nodeArray.reserve(children.size());
}
std::map<App::DocumentObject*, size_t> groups;
for (size_t i = 0; i < children.size(); ++i) {
auto obj = children[i];
if (nodeArray.size() <= i) {
nodeArray.push_back(std::make_unique<Element>(*this));
}
auto& info = *nodeArray[i];
info.isGroup = false;
info.groupIndex = -1;
info.pcSwitch->whichChild = (vis.size() <= i || vis[i]) ? 0 : SO_SWITCH_NONE;
info.link(obj);
if (obj->hasExtension(App::GroupExtension::getExtensionClassTypeId(), false)) {
info.isGroup = true;
coinRemoveAllChildren(info.pcRoot);
groups.emplace(obj, i);
}
}
nodeMap.clear();
for (size_t i = 0; i < nodeArray.size(); ++i) {
auto& info = *nodeArray[i];
nodeMap.emplace(info.pcSwitch, i);
if (info.isLinked() && !groups.empty()) {
auto iter = groups.find(
App::GroupExtension::getGroupOfObject(info.linkInfo->pcLinked->getObject())
);
if (iter != groups.end()) {
info.groupIndex = iter->second;
auto& groupInfo = *nodeArray[iter->second];
groupInfo.pcRoot->addChild(info.pcSwitch);
continue;
}
}
pcLinkRoot->addChild(info.pcSwitch);
}
}
std::vector<ViewProviderDocumentObject*> LinkView::getChildren() const
{
std::vector<ViewProviderDocumentObject*> ret;
for (const auto& info : nodeArray) {
if (info->isLinked()) {
ret.push_back(info->linkInfo->pcLinked);
}
}
return ret;
}
void LinkView::setTransform(int index, const Base::Matrix4D& mat)
{
if (index < 0) {
if (!pcTransform) {
pcTransform = new SoTransform;
pcLinkRoot->insertChild(pcTransform, 0);
}
setTransform(pcTransform, mat);
return;
}
if (index < 0 || index >= (int)nodeArray.size()) {
LINK_THROW(Base::ValueError, "LinkView: index out of range");
}
setTransform(nodeArray[index]->pcTransform, mat);
}
void LinkView::setElementVisible(int idx, bool visible)
{
if (idx >= 0 && idx < (int)nodeArray.size()) {
nodeArray[idx]->pcSwitch->whichChild = visible ? 0 : SO_SWITCH_NONE;
}
}
bool LinkView::isElementVisible(int idx) const
{
if (idx >= 0 && idx < (int)nodeArray.size()) {
return nodeArray[idx]->pcSwitch->whichChild.getValue() >= 0;
}
return false;
}
ViewProviderDocumentObject* LinkView::getLinkedView() const
{
auto link = linkInfo;
if (autoSubLink && subInfo.size() == 1) {
link = subInfo.begin()->second->linkInfo;
}
return link ? link->pcLinked : nullptr;
}
std::vector<std::string> LinkView::getSubNames() const
{
std::vector<std::string> ret;
for (const auto& v : subInfo) {
if (v.second->subElements.empty()) {
ret.push_back(v.first);
continue;
}
for (const auto& s : v.second->subElements) {
ret.push_back(v.first + s);
}
}
return ret;
}
void LinkView::setNodeType(SnapshotType type, bool sublink)
{
autoSubLink = sublink;
if (nodeType == type) {
return;
}
if (type >= SnapshotMax
|| (type < 0 && type != SnapshotContainer && type != SnapshotContainerTransform)) {
LINK_THROW(Base::ValueError, "LinkView: invalid node type");
}
if (nodeType >= 0 && type < 0) {
if (pcLinkedRoot) {
SoSelectionElementAction action(SoSelectionElementAction::None, true);
action.apply(pcLinkedRoot);
}
replaceLinkedRoot(CoinPtr<SoSeparator>(new SoFCSelectionRoot));
}
else if (nodeType < 0 && type >= 0) {
if (isLinked()) {
replaceLinkedRoot(linkInfo->getSnapshot(type));
}
else {
replaceLinkedRoot(nullptr);
}
}
nodeType = type;
updateLink();
}
void LinkView::replaceLinkedRoot(SoSeparator* root)
{
if (root == pcLinkedRoot) {
return;
}
if (nodeArray.empty()) {
if (pcLinkedRoot && root) {
pcLinkRoot->replaceChild(pcLinkedRoot, root);
}
else if (root) {
pcLinkRoot->addChild(root);
}
else {
resetRoot();
}
}
else if (childType < 0) {
if (pcLinkedRoot && root) {
for (const auto& info : nodeArray) {
info->pcRoot->replaceChild(pcLinkedRoot, root);
}
}
else if (root) {
for (const auto& info : nodeArray) {
info->pcRoot->addChild(root);
}
}
else {
for (const auto& info : nodeArray) {
info->pcRoot->removeChild(pcLinkedRoot);
}
}
}
pcLinkedRoot = root;
}
void LinkView::onLinkedIconChange(LinkInfoPtr info)
{
if (info == linkInfo && info != linkOwner && linkOwner && linkOwner->isLinked()) {
linkOwner->pcLinked->signalChangeIcon();
}
}
void LinkView::onLinkedUpdateData(LinkInfoPtr info, const App::Property* prop)
{
if (info != linkInfo || !linkOwner || !linkOwner->isLinked() || info == linkOwner) {
return;
}
auto ext = linkOwner->pcLinked->getObject()->getExtensionByType<App::LinkBaseExtension>(true);
if (ext && !(prop->getType() & App::Prop_Output) && !prop->testStatus(App::Property::Output)) {
// propagate the signalChangedObject to potentially multiple levels
// of links, to inform tree view of children change, and other
// parent objects about the change. But we need to be careful to not
// touch the object if the property of change is marked as output.
ext->_LinkTouched.touch();
}
else {
// In case the owner object does not have link extension, here is a
// trick to link the signalChangedObject from linked object to the
// owner
linkOwner->pcLinked->getDocument()->signalChangedObject(
*linkOwner->pcLinked,
linkOwner->pcLinked->getObject()->Label
);
}
}
void LinkView::updateLink()
{
if (!isLinked()) {
return;
}
if (linkOwner && linkOwner->isLinked() && linkOwner->pcLinked->isRestoring()) {
FC_TRACE("restoring '" << linkOwner->pcLinked->getObject()->getFullName() << "'");
return;
}
// TODO: is it a good idea to clear any selection here?
pcLinkRoot->resetContext();
if (nodeType >= 0) {
replaceLinkedRoot(linkInfo->getSnapshot(nodeType));
return;
}
// rebuild link sub objects tree
CoinPtr<SoSeparator> linkedRoot = pcLinkedRoot;
if (!linkedRoot) {
linkedRoot = new SoFCSelectionRoot;
}
else {
SoSelectionElementAction action(SoSelectionElementAction::None, true);
action.apply(linkedRoot);
coinRemoveAllChildren(linkedRoot);
}
SoTempPath path(10);
path.ref();
appendPath(&path, linkedRoot);
auto obj = linkInfo->pcLinked->getObject();
for (const auto& v : subInfo) {
auto& sub = *v.second;
Base::Matrix4D mat;
App::DocumentObject* sobj
= obj->getSubObject(v.first.c_str(), nullptr, &mat, nodeType == SnapshotContainer);
if (!sobj) {
sub.unlink();
continue;
}
sub.link(sobj);
linkedRoot->addChild(sub.pcNode);
setTransform(sub.pcTransform, mat);
if (!sub.subElements.empty()) {
path.truncate(1);
appendPath(&path, sub.pcNode);
SoSelectionElementAction action(SoSelectionElementAction::Append, true);
for (const auto& subelement : sub.subElements) {
path.truncate(2);
SoDetail* det = nullptr;
if (!sub.linkInfo->getDetail(false, SnapshotTransform, subelement.c_str(), det, &path)) {
continue;
}
action.setElement(det);
action.apply(&path);
delete det;
}
}
}
path.unrefNoDelete();
replaceLinkedRoot(linkedRoot);
}
bool LinkView::linkGetElementPicked(const SoPickedPoint* pp, std::string& subname) const
{
std::ostringstream ss;
CoinPtr<SoPath> path = pp->getPath();
if (!nodeArray.empty()) {
auto idx = path->findNode(pcLinkRoot);
if (idx < 0 || idx + 2 >= path->getLength()) {
return false;
}
auto node = path->getNode(idx + 1);
auto it = nodeMap.find(node);
if (it == nodeMap.end() || !isElementVisible(it->second)) {
return false;
}
int nodeIdx = it->second;
++idx;
while (nodeArray[nodeIdx]->isGroup) {
auto& info = *nodeArray[nodeIdx];
if (!info.isLinked()) {
return false;
}
ss << info.linkInfo->getLinkedName() << '.';
idx += 2;
if (idx >= path->getLength()) {
return false;
}
auto iter = nodeMap.find(path->getNode(idx));
if (iter == nodeMap.end() || !isElementVisible(iter->second)) {
return false;
}
nodeIdx = iter->second;
}
auto& info = *nodeArray[nodeIdx];
if (!info.linkInfo) {
ss << it->second << '.';
}
else {
ss << info.linkInfo->getLinkedName() << '.';
}
if (info.isLinked()) {
if (!info.linkInfo->getElementPicked(false, childType, pp, ss)) {
return false;
}
subname = ss.str();
return true;
}
}
if (!isLinked()) {
return false;
}
if (nodeType >= 0) {
if (linkInfo->getElementPicked(false, nodeType, pp, ss)) {
subname = ss.str();
return true;
}
return false;
}
auto idx = path->findNode(pcLinkedRoot);
if (idx < 0 || idx + 1 >= path->getLength()) {
return false;
}
auto node = path->getNode(idx + 1);
for (const auto& v : subInfo) {
auto& sub = *v.second;
if (node != sub.pcNode) {
continue;
}
std::ostringstream ss2;
if (!sub.linkInfo->getElementPicked(false, SnapshotTransform, pp, ss2)) {
return false;
}
const std::string& element = ss2.str();
if (!sub.subElements.empty()) {
if (sub.subElements.find(element) == sub.subElements.end()) {
auto pos = element.find('.');
if (pos == std::string::npos
|| sub.subElements.find(element.c_str() + pos + 1) == sub.subElements.end()) {
return false;
}
}
}
if (!autoSubLink || subInfo.size() > 1) {
ss << v.first;
}
ss << element;
subname = ss.str();
return true;
}
return false;
}
bool LinkView::getGroupHierarchy(int index, SoFullPath* path) const
{
if (index > (int)nodeArray.size()) {
return false;
}
auto& info = *nodeArray[index];
if (info.groupIndex >= 0 && !getGroupHierarchy(info.groupIndex, path)) {
return false;
}
appendPath(path, info.pcSwitch);
appendPath(path, info.pcRoot);
return true;
}
bool LinkView::linkGetDetailPath(const char* subname, SoFullPath* path, SoDetail*& det) const
{
if (!subname || *subname == 0) {
return true;
}
auto len = path->getLength();
if (nodeArray.empty()) {
if (!appendPathSafe(path, pcLinkRoot)) {
return false;
}
}
else {
int idx = -1;
if (subname[0] >= '0' && subname[0] <= '9') {
idx = App::LinkBaseExtension::getArrayIndex(subname, &subname);
}
else {
while (true) {
const char* dot = strchr(subname, '.');
if (!dot) {
break;
}
int i = 0;
if (subname[0] == '$') {
CharRange name(subname + 1, dot);
for (const auto& info : nodeArray) {
if (info->isLinked()
&& boost::equals(name, info->linkInfo->getLinkedLabel())) {
idx = i;
break;
}
++i;
}
}
else {
CharRange name(subname, dot);
for (const auto& info : nodeArray) {
if (info->isLinked() && boost::equals(name, info->linkInfo->getLinkedName())) {
idx = i;
break;
}
++i;
}
}
if (idx < 0) {
return false;
}
subname = dot + 1;
if (!subname[0] || nodeArray[idx]->isGroup == 0) {
break;
}
idx = -1;
}
}
if (idx < 0 || idx >= (int)nodeArray.size()) {
return false;
}
auto& info = *nodeArray[idx];
if (!appendPathSafe(path, pcLinkRoot)) {
return false;
}
if (info.groupIndex >= 0 && !getGroupHierarchy(info.groupIndex, path)) {
return false;
}
appendPath(path, info.pcSwitch);
appendPath(path, info.pcRoot);
if (*subname == 0) {
return true;
}
if (info.isLinked()) {
if (info.linkInfo->getDetail(false, childType, subname, det, path)) {
return true;
}
}
}
if (isLinked()) {
if (nodeType >= 0) {
if (linkInfo->getDetail(false, nodeType, subname, det, path)) {
return true;
}
}
else {
appendPath(path, pcLinkedRoot);
for (const auto& v : subInfo) {
auto& sub = *v.second;
if (!sub.isLinked()) {
continue;
}
const char* nextsub;
if (autoSubLink && subInfo.size() == 1) {
nextsub = subname;
}
else {
if (!boost::algorithm::starts_with(subname, v.first)) {
continue;
}
nextsub = subname + v.first.size();
if (*nextsub != '.') {
continue;
}
++nextsub;
}
if (*nextsub && !sub.subElements.empty()
&& sub.subElements.find(nextsub) == sub.subElements.end()) {
break;
}
appendPath(path, sub.pcNode);
len = path->getLength();
if (sub.linkInfo->getDetail(false, SnapshotTransform, nextsub, det, path)) {
return true;
}
break;
}
}
}
path->truncate(len);
return false;
}
void LinkView::unlink(LinkInfoPtr info)
{
if (!info) {
return;
}
if (info == linkOwner) {
linkOwner->remove(this);
linkOwner.reset();
}
if (info != linkInfo) {
return;
}
if (linkInfo) {
linkInfo->remove(this);
linkInfo.reset();
}
pcLinkRoot->resetContext();
if (pcLinkedRoot) {
if (nodeArray.empty()) {
resetRoot();
}
else {
for (const auto& info : nodeArray) {
int idx;
if (info->isLinked() && (idx = info->pcRoot->findChild(pcLinkedRoot)) >= 0) {
info->pcRoot->removeChild(idx);
}
}
}
pcLinkedRoot.reset();
}
subInfo.clear();
return;
}
QIcon LinkView::getLinkedIcon(QPixmap px) const
{
auto link = linkInfo;
if (autoSubLink && subInfo.size() == 1) {
link = subInfo.begin()->second->linkInfo;
}
if (!link || !link->isLinked()) {
return QIcon();
}
return link->getIcon(px);
}
bool LinkView::hasSubs() const
{
return isLinked() && !subInfo.empty();
}
///////////////////////////////////////////////////////////////////////////////////
PROPERTY_SOURCE(Gui::ViewProviderLink, Gui::ViewProviderDocumentObject)
static const char* _LinkIcon = "Link";
// static const char *_LinkArrayIcon = "LinkArray";
static const char* _LinkGroupIcon = "LinkGroup";
static const char* _LinkElementIcon = "LinkElement";
ViewProviderLink::ViewProviderLink()
: linkType(LinkTypeNone)
, hasSubName(false)
, hasSubElement(false)
, childVp(nullptr)
, overlayCacheKey(0)
{
sPixmap = _LinkIcon;
ADD_PROPERTY_TYPE(Selectable, (true), " Link", App::Prop_None, 0);
ADD_PROPERTY_TYPE(
OverrideMaterial,
(false),
" Link",
App::Prop_None,
"Override linked object's material"
);
App::Material mat(App::Material::DEFAULT);
mat.diffuseColor.setPackedValue(ViewParams::instance()->getDefaultLinkColor());
ADD_PROPERTY_TYPE(ShapeMaterial, (mat), " Link", App::Prop_None, 0);
ShapeMaterial.setStatus(App::Property::MaterialEdit, true);
ADD_PROPERTY_TYPE(DrawStyle, ((long int)0), " Link", App::Prop_None, "");
static const char* DrawStyleEnums[] = {"None", "Solid", "Dashed", "Dotted", "Dashdot", nullptr};
DrawStyle.setEnums(DrawStyleEnums);
int lwidth = ViewParams::instance()->getDefaultShapeLineWidth();
ADD_PROPERTY_TYPE(LineWidth, (lwidth), " Link", App::Prop_None, "");
static App::PropertyFloatConstraint::Constraints sizeRange = {1.0, 64.0, 1.0};
LineWidth.setConstraints(&sizeRange);
ADD_PROPERTY_TYPE(PointSize, (lwidth), " Link", App::Prop_None, "");
PointSize.setConstraints(&sizeRange);
ADD_PROPERTY(MaterialList, ());
MaterialList.setStatus(App::Property::NoMaterialListEdit, true);
ADD_PROPERTY(OverrideMaterialList, ());
ADD_PROPERTY(OverrideColorList, ());
ADD_PROPERTY(ChildViewProvider, (""));
ChildViewProvider.setStatus(App::Property::Hidden, true);
DisplayMode.setStatus(App::Property::Status::Hidden, true);
linkView = new LinkView;
pcPickStyle = new SoPickStyle;
pcPickStyle->ref();
}
ViewProviderLink::~ViewProviderLink()
{
pcPickStyle->unref();
linkView->setInvalid();
}
bool ViewProviderLink::isSelectable() const
{
return Selectable.getValue();
}
void ViewProviderLink::attach(App::DocumentObject* pcObj)
{
if (pcRoot->findChild(pcPickStyle) < 0) {
pcRoot->insertChild(pcPickStyle, 0);
}
SoNode* node = linkView->getLinkRoot();
node->setName(pcObj->getFullName().c_str());
addDisplayMaskMode(node, "Link");
if (childVp) {
childVpLink = LinkInfo::get(childVp, nullptr);
node = childVpLink->getSnapshot(LinkView::SnapshotTransform);
}
addDisplayMaskMode(node, "ChildView");
setDisplayMaskMode("Link");
inherited::attach(pcObj);
checkIcon();
if (pcObj->isDerivedFrom<App::LinkElement>()) {
hide();
}
linkView->setOwner(this);
}
void ViewProviderLink::reattach(App::DocumentObject* obj)
{
linkView->setOwner(this);
if (childVp) {
childVp->reattach(obj);
}
ViewProviderDocumentObject::reattach(obj);
}
std::vector<std::string> ViewProviderLink::getDisplayModes() const
{
std::vector<std::string> StrList = inherited::getDisplayModes();
StrList.emplace_back("Link");
StrList.emplace_back("ChildView");
return StrList;
}
QIcon ViewProviderLink::getIcon() const
{
auto ext = getLinkExtension();
if (ext) {
auto link = ext->getLinkedObjectValue();
if (link && link != getObject()) {
QPixmap overlay = getOverlayPixmap();
overlayCacheKey = overlay.cacheKey();
QIcon icon = linkView->getLinkedIcon(overlay);
if (!icon.isNull()) {
return icon;
}
}
}
overlayCacheKey = 0;
return Gui::BitmapFactory().pixmap(sPixmap);
}
QPixmap ViewProviderLink::getOverlayPixmap() const
{
auto ext = getLinkExtension();
constexpr int px = 12;
if (ext && ext->getLinkedObjectProperty() && ext->_getElementCountValue()) {
return BitmapFactory().pixmapFromSvg("LinkArrayOverlay", QSizeF(px, px));
}
else if (hasSubElement) {
return BitmapFactory().pixmapFromSvg("LinkSubElement", QSizeF(px, px));
}
else if (hasSubName) {
return BitmapFactory().pixmapFromSvg("LinkSubOverlay", QSizeF(px, px));
}
else {
return BitmapFactory().pixmapFromSvg("LinkOverlay", QSizeF(px, px));
}
}
void ViewProviderLink::onChanged(const App::Property* prop)
{
if (prop == &Selectable) {
pcPickStyle->style = Selectable.getValue() ? SoPickStyle::SHAPE : SoPickStyle::UNPICKABLE;
}
if (prop == &ChildViewProvider) {
childVp = freecad_cast<ViewProviderDocumentObject*>(ChildViewProvider.getObject().get());
if (childVp && getObject()) {
if (strcmp(childVp->getTypeId().getName(), getObject()->getViewProviderName()) != 0
&& !childVp->allowOverride(*getObject())) {
FC_ERR(
"Child view provider type '" << childVp->getTypeId().getName()
<< "' does not support "
<< getObject()->getFullName()
);
}
else {
childVp->setPropertyPrefix("ChildViewProvider.");
childVp->Visibility.setValue(getObject()->Visibility.getValue());
childVp->attach(getObject());
childVp->updateView();
childVp->setActiveMode();
if (pcModeSwitch->getNumChildren() > 1) {
childVpLink = LinkInfo::get(childVp, nullptr);
pcModeSwitch->replaceChild(1, childVpLink->getSnapshot(LinkView::SnapshotTransform));
}
}
}
}
else if (!isRestoring()) {
if (prop == &OverrideMaterial || prop == &ShapeMaterial || prop == &MaterialList
|| prop == &OverrideMaterialList) {
applyMaterial();
}
else if (prop == &OverrideColorList) {
applyColors();
}
else if (prop == &DrawStyle || prop == &PointSize || prop == &LineWidth) {
if (!DrawStyle.getValue()) {
linkView->setDrawStyle(0);
}
else {
linkView->setDrawStyle(DrawStyle.getValue(), LineWidth.getValue(), PointSize.getValue());
}
}
}
inherited::onChanged(prop);
}
bool ViewProviderLink::setLinkType(App::LinkBaseExtension* ext)
{
auto propLink = ext->getLinkedObjectProperty();
if (!propLink) {
return false;
}
LinkType type;
if (hasSubName) {
type = LinkTypeSubs;
}
else {
type = LinkTypeNormal;
}
if (linkType != type) {
linkType = type;
}
switch (type) {
case LinkTypeSubs:
linkView->setNodeType(
ext->linkTransform() ? LinkView::SnapshotContainer : LinkView::SnapshotContainerTransform
);
break;
case LinkTypeNormal:
linkView->setNodeType(
ext->linkTransform() ? LinkView::SnapshotVisible : LinkView::SnapshotTransform
);
break;
default:
break;
}
return true;
}
App::LinkBaseExtension* ViewProviderLink::getLinkExtension()
{
if (!pcObject || !pcObject->isAttachedToDocument()) {
return nullptr;
}
return pcObject->getExtensionByType<App::LinkBaseExtension>(true);
}
const App::LinkBaseExtension* ViewProviderLink::getLinkExtension() const
{
if (!pcObject || !pcObject->isAttachedToDocument()) {
return nullptr;
}
return const_cast<App::DocumentObject*>(pcObject)->getExtensionByType<App::LinkBaseExtension>(true);
}
void ViewProviderLink::updateData(const App::Property* prop)
{
if (childVp) {
childVp->updateData(prop);
}
inherited::updateData(prop);
if (!isRestoring() && !pcObject->isRestoring()) {
auto ext = getLinkExtension();
if (ext) {
updateDataPrivate(getLinkExtension(), prop);
}
}
}
static inline bool canScale(const Base::Vector3d& v)
{
return fabs(v.x) > 1e-7 && fabs(v.y) > 1e-7 && fabs(v.z) > 1e-7;
}
void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension* ext, const App::Property* prop)
{
if (!prop) {
return;
}
if (prop == &ext->_ChildCache) {
updateElementList(ext);
}
else if (prop == &ext->_LinkTouched) {
if (linkView->hasSubs()) {
linkView->updateLink();
}
applyColors();
checkIcon(ext);
}
else if (prop == ext->getColoredElementsProperty()) {
if (!prop->testStatus(App::Property::User3)) {
applyColors();
}
}
else if (prop == ext->getScaleProperty() || prop == ext->getScaleVectorProperty()) {
if (!prop->testStatus(App::Property::User3)) {
const auto& v = ext->getScaleVector();
if (canScale(v)) {
pcTransform->scaleFactor.setValue(v.x, v.y, v.z);
}
SbMatrix matrix = convert(ext->getTransform(false));
linkView->renderDoubleSide(matrix.det3() < 1e-7);
}
}
else if (prop == ext->getPlacementProperty() || prop == ext->getLinkPlacementProperty()) {
auto propLinkPlacement = ext->getLinkPlacementProperty();
if (!propLinkPlacement || propLinkPlacement == prop || prop == ext->getPlacementProperty()) {
const auto& v = ext->getScaleVector();
if (canScale(v)) {
pcTransform->scaleFactor.setValue(v.x, v.y, v.z);
}
SbMatrix matrix = convert(ext->getTransform(false));
linkView->renderDoubleSide(matrix.det3() < 1e-7);
}
}
else if (prop == ext->getLinkCopyOnChangeGroupProperty()) {
if (auto group = ext->getLinkCopyOnChangeGroupValue()) {
auto vp = freecad_cast<ViewProviderDocumentObject*>(
Application::Instance->getViewProvider(group)
);
if (vp) {
vp->hide();
vp->ShowInTree.setValue(false);
}
}
}
else if (prop == ext->getLinkedObjectProperty()) {
if (!prop->testStatus(App::Property::User3)) {
std::vector<std::string> subs;
const char* subname = ext->getSubName();
std::string sub;
if (subname) {
sub = subname;
}
hasSubElement = false;
for (const auto& s : ext->getSubElements()) {
if (s.empty()) {
continue;
}
hasSubElement = true;
subs.push_back(sub + s);
}
if (subs.empty() && !sub.empty()) {
subs.push_back(sub);
}
hasSubName = !subs.empty();
setLinkType(ext);
auto obj = ext->getLinkedObjectValue();
linkView->setLink(obj, subs);
if (ext->_getShowElementValue()) {
updateElementList(ext);
}
else {
updateDataPrivate(ext, ext->_getElementCountProperty());
}
// applyColors();
signalChangeIcon();
}
}
else if (prop == ext->getLinkTransformProperty()) {
setLinkType(ext);
applyColors();
}
else if (prop == ext->_getElementCountProperty()) {
if (!ext->_getShowElementValue()) {
linkView->setSize(ext->_getElementCountValue());
updateDataPrivate(ext, ext->getVisibilityListProperty());
updateDataPrivate(ext, ext->getPlacementListProperty());
}
}
else if (prop == ext->_getShowElementProperty()) {
if (!ext->_getShowElementValue()) {
auto linked = freecad_cast<ViewProviderDocumentObject*>(getLinkedView(true, ext));
if (linked && linked->getDocument() == getDocument()) {
linked->hide();
}
const auto& elements = ext->_getElementListValue();
// elements is about to be collapsed, preserve the materials
if (!elements.empty()) {
std::vector<App::Material> materials;
boost::dynamic_bitset<> overrideMaterials;
overrideMaterials.resize(elements.size(), false);
bool overrideMaterial = false;
bool hasMaterial = false;
materials.reserve(elements.size());
for (size_t i = 0; i < elements.size(); ++i) {
auto element = freecad_cast<App::LinkElement*>(elements[i]);
if (!element) {
continue;
}
auto vp = freecad_cast<ViewProviderLink*>(
Application::Instance->getViewProvider(element)
);
if (!vp) {
continue;
}
overrideMaterial = overrideMaterial || vp->OverrideMaterial.getValue();
hasMaterial = overrideMaterial || hasMaterial
|| vp->ShapeMaterial.getValue() != ShapeMaterial.getValue();
materials.push_back(vp->ShapeMaterial.getValue());
overrideMaterials[i] = vp->OverrideMaterial.getValue();
}
if (!overrideMaterial) {
overrideMaterials.clear();
}
OverrideMaterialList.setStatus(App::Property::User3, true);
OverrideMaterialList.setValue(overrideMaterials);
OverrideMaterialList.setStatus(App::Property::User3, false);
if (!hasMaterial) {
materials.clear();
}
MaterialList.setStatus(App::Property::User3, true);
MaterialList.setValue(materials);
MaterialList.setStatus(App::Property::User3, false);
linkView->setSize(ext->_getElementCountValue());
updateDataPrivate(ext, ext->getVisibilityListProperty());
applyMaterial();
applyColors();
}
}
}
else if (prop == ext->getScaleListProperty() || prop == ext->getPlacementListProperty()) {
if (!prop->testStatus(App::Property::User3) && linkView->getSize()
&& !ext->_getShowElementValue()) {
auto propPlacements = ext->getPlacementListProperty();
auto propScales = ext->getScaleListProperty();
if (propPlacements && linkView->getSize()) {
const auto& touched = prop == propScales ? propScales->getTouchList()
: propPlacements->getTouchList();
if (touched.empty()) {
for (int i = 0; i < linkView->getSize(); ++i) {
Base::Matrix4D mat;
if (propPlacements && propPlacements->getSize() > i) {
mat = (*propPlacements)[i].toMatrix();
}
if (propScales && propScales->getSize() > i && canScale((*propScales)[i])) {
Base::Matrix4D s;
s.scale((*propScales)[i]);
mat *= s;
}
linkView->setTransform(i, mat);
}
}
else {
for (int i : touched) {
if (i < 0 || i >= linkView->getSize()) {
continue;
}
Base::Matrix4D mat;
if (propPlacements && propPlacements->getSize() > i) {
mat = (*propPlacements)[i].toMatrix();
}
if (propScales && propScales->getSize() > i && canScale((*propScales)[i])) {
Base::Matrix4D s;
s.scale((*propScales)[i]);
mat *= s;
}
linkView->setTransform(i, mat);
}
}
}
}
}
else if (prop == ext->getVisibilityListProperty()) {
const auto& vis = ext->getVisibilityListValue();
for (size_t i = 0; i < (size_t)linkView->getSize(); ++i) {
if (vis.size() > i) {
linkView->setElementVisible(i, vis[i]);
}
else {
linkView->setElementVisible(i, true);
}
}
}
else if (prop == ext->_getElementListProperty()) {
if (ext->_getShowElementValue()) {
updateElementList(ext);
}
}
}
void ViewProviderLink::updateElementList(App::LinkBaseExtension* ext)
{
const auto& elements = ext->_getElementListValue();
if (OverrideMaterialList.getSize() || MaterialList.getSize()) {
int i = -1;
for (auto obj : elements) {
++i;
auto vp = freecad_cast<ViewProviderLink*>(Application::Instance->getViewProvider(obj));
if (!vp) {
continue;
}
if (OverrideMaterialList.getSize() > i) {
vp->OverrideMaterial.setValue(OverrideMaterialList[i]);
}
if (MaterialList.getSize() > i) {
vp->ShapeMaterial.setValue(MaterialList[i]);
}
}
OverrideMaterialList.setSize(0);
MaterialList.setSize(0);
}
linkView->setChildren(elements, ext->getVisibilityListValue());
applyColors();
}
void ViewProviderLink::checkIcon(const App::LinkBaseExtension* ext)
{
if (!ext) {
ext = getLinkExtension();
if (!ext) {
return;
}
}
const char* icon;
auto element = getObject<App::LinkElement>();
if (element) {
icon = _LinkElementIcon;
}
else if (!ext->getLinkedObjectProperty() && ext->getElementListProperty()) {
icon = _LinkGroupIcon;
}
// else if(ext->_getElementCountValue())
// icon = _LinkArrayIcon;
else {
icon = _LinkIcon;
}
qint64 cacheKey = 0;
if (getObject()->getLinkedObject(false) != getObject()) {
cacheKey = getOverlayPixmap().cacheKey();
}
if (icon != sPixmap || cacheKey != overlayCacheKey) {
sPixmap = icon;
signalChangeIcon();
}
}
void ViewProviderLink::applyMaterial()
{
if (OverrideMaterial.getValue()) {
// Dispatch a generic Coin3D action to the linked object's scene graph.
// If the linked object is a Part shape, its SoBrepFaceSet node will
// handle this action and apply the color to all its faces.
// This is decoupled and respects the core/module architecture.
// 1. Get the material from our property.
const auto& material = ShapeMaterial.getValue();
// 2. Prepare the color for the rendering action. The action's ultimate
// consumer (SoBrepFaceSet) expects a Base::Color where .a is transparency,
// so we must perform the conversion here at the boundary.
Base::Color renderColor = material.diffuseColor;
renderColor.a = 1.0f - material.transparency;
// 3. Create a map with the "Face" wildcard to signify "all faces".
std::map<std::string, Base::Color> colorMap;
colorMap["Face"] = renderColor;
// 4. Create and dispatch the action. We use a secondary context action,
// which is the established mechanism for this kind of override.
SoSelectionElementAction action(
SoSelectionElementAction::Color,
true
); // true for secondary
action.swapColors(colorMap);
linkView->getLinkRoot()->doAction(&action);
// 5. Ensure the old global override mechanism is not used.
linkView->setMaterial(-1, nullptr);
}
else {
// OVERRIDE IS DISABLED:
// We must clear the per-face override we just applied.
// 1. Dispatch an empty Color action to clear the secondary context.
SoSelectionElementAction action(SoSelectionElementAction::Color, true);
linkView->getLinkRoot()->doAction(&action);
// 2. Re-apply any other material settings (e.g., for array elements,
// or clear the old global override if it was set).
linkView->setMaterial(-1, nullptr);
for (int i = 0; i < linkView->getSize(); ++i) {
if (MaterialList.getSize() > i && OverrideMaterialList.getSize() > i
&& OverrideMaterialList[i]) {
linkView->setMaterial(i, &MaterialList[i]);
}
else {
linkView->setMaterial(i, nullptr);
}
}
}
}
void ViewProviderLink::finishRestoring()
{
FC_TRACE("finish restoring");
auto ext = getLinkExtension();
if (!ext) {
return;
}
linkView->setDrawStyle(DrawStyle.getValue(), LineWidth.getValue(), PointSize.getValue());
updateDataPrivate(ext, ext->getLinkedObjectProperty());
if (ext->getLinkPlacementProperty()) {
updateDataPrivate(ext, ext->getLinkPlacementProperty());
}
else {
updateDataPrivate(ext, ext->getPlacementProperty());
}
updateDataPrivate(ext, ext->_getElementCountProperty());
if (ext->getPlacementListProperty()) {
updateDataPrivate(ext, ext->getPlacementListProperty());
}
else {
updateDataPrivate(ext, ext->getScaleListProperty());
}
updateDataPrivate(ext, ext->_getElementListProperty());
applyMaterial();
applyColors();
// TODO: notify the tree. This is ugly, any other way?
getDocument()->signalChangedObject(*this, ext->_LinkTouched);
if (childVp) {
childVp->finishRestoring();
}
}
bool ViewProviderLink::hasElements(const App::LinkBaseExtension* ext) const
{
if (!ext) {
ext = getLinkExtension();
if (!ext) {
return false;
}
}
const auto& elements = ext->getElementListValue();
return !elements.empty() && (int)elements.size() == ext->_getElementCountValue();
}
bool ViewProviderLink::isGroup(const App::LinkBaseExtension* ext, bool plainGroup) const
{
if (!ext) {
ext = getLinkExtension();
if (!ext) {
return false;
}
}
return (plainGroup && ext->linkedPlainGroup())
|| (ext->getElementListProperty() && !ext->getLinkedObjectProperty());
}
ViewProvider* ViewProviderLink::getLinkedView(bool real, const App::LinkBaseExtension* ext) const
{
if (!ext) {
ext = getLinkExtension();
}
auto obj = ext && real ? ext->getTrueLinkedObject(true) : getObject()->getLinkedObject(true);
if (obj && obj != getObject()) {
return Application::Instance->getViewProvider(obj);
}
return nullptr;
}
std::vector<App::DocumentObject*> ViewProviderLink::claimChildren() const
{
auto ext = getLinkExtension();
std::vector<App::DocumentObject*> ret;
if (ext && !ext->_getShowElementValue() && ext->_getElementCountValue()) {
// in array mode without element objects, we'd better not show the
// linked object's children to avoid inconsistent behavior on selection.
// We claim the linked object instead
if (ext) {
auto obj = ext->getLinkedObjectValue();
if (obj) {
ret.push_back(obj);
}
}
}
else if (hasElements(ext) || isGroup(ext)) {
ret = ext->getElementListValue();
if (ext->_getElementCountValue() && ext->getLinkClaimChildValue()
&& ext->getLinkedObjectValue()) {
ret.insert(ret.begin(), ext->getLinkedObjectValue());
}
}
else if (!hasSubName) {
auto linked = getLinkedView(true);
if (linked) {
ret = linked->claimChildren();
if (ext->getLinkClaimChildValue() && ext->getLinkedObjectValue()) {
ret.insert(ret.begin(), ext->getLinkedObjectValue());
}
}
}
if (ext && ext->getLinkCopyOnChangeGroupValue()) {
ret.insert(ret.begin(), ext->getLinkCopyOnChangeGroupValue());
}
return ret;
}
bool ViewProviderLink::canDragObject(App::DocumentObject* obj) const
{
auto ext = getLinkExtension();
if (isGroup(ext)) {
return true;
}
if (hasElements(ext)) {
return false;
}
auto linked = getLinkedView(false, ext);
if (linked) {
return linked->canDragObject(obj);
}
return false;
}
bool ViewProviderLink::canDragObjects() const
{
auto ext = getLinkExtension();
if (isGroup(ext)) {
return true;
}
if (hasElements(ext)) {
return false;
}
auto linked = getLinkedView(false, ext);
if (linked) {
return linked->canDragObjects();
}
return false;
}
void ViewProviderLink::dragObject(App::DocumentObject* obj)
{
auto ext = getLinkExtension();
if (isGroup(ext)) {
const auto& objs = ext->getElementListValue();
for (size_t i = 0; i < objs.size(); ++i) {
if (obj == objs[i]) {
ext->setLink(i, nullptr);
break;
}
}
return;
}
if (hasElements(ext)) {
return;
}
auto linked = getLinkedView(false);
if (linked) {
linked->dragObject(obj);
}
}
bool ViewProviderLink::canDropObjects() const
{
auto ext = getLinkExtension();
if (isGroup(ext)) {
return true;
}
if (hasElements(ext)) {
return false;
}
if (hasSubElement) {
return true;
}
else if (hasSubName) {
return false;
}
auto linked = getLinkedView(false, ext);
if (linked) {
return linked->canDropObjects();
}
return true;
}
bool ViewProviderLink::canDropObjectEx(
App::DocumentObject* obj,
App::DocumentObject* owner,
const char* subname,
const std::vector<std::string>& subElements
) const
{
if (pcObject == obj || pcObject == owner) {
return false;
}
auto ext = getLinkExtension();
if (isGroup(ext)) {
return true;
}
if (!ext || !ext->getLinkedObjectProperty() || hasElements(ext)) {
return false;
}
if (!hasSubName && linkView->isLinked()) {
auto linked = getLinkedView(false, ext);
if (linked) {
auto linkedVdp = freecad_cast<ViewProviderDocumentObject*>(linked);
if (linkedVdp) {
if (linkedVdp->getObject() == obj || linkedVdp->getObject() == owner) {
return false;
}
}
return linked->canDropObjectEx(obj, owner, subname, subElements);
}
}
if (obj->getDocument() != getObject()->getDocument()
&& !freecad_cast<App::PropertyXLink*>(ext->getLinkedObjectProperty())) {
return false;
}
return true;
}
std::string ViewProviderLink::dropObjectEx(
App::DocumentObject* obj,
App::DocumentObject* owner,
const char* subname,
const std::vector<std::string>& subElements
)
{
auto ext = getLinkExtension();
if (!ext) {
return std::string();
}
if (isGroup(ext)) {
size_t size = ext->getElementListValue().size();
ext->setLink(size, obj);
return std::to_string(size) + ".";
}
if (!ext->getLinkedObjectProperty() || hasElements(ext)) {
return std::string();
}
if (!hasSubName) {
auto linked = getLinkedView(false, ext);
if (linked) {
return linked->dropObjectEx(obj, owner, subname, subElements);
}
}
if (owner) {
if (!ext->getSubElements().empty()) {
ext->setLink(-1, owner, subname, subElements);
}
else {
ext->setLink(-1, owner, subname);
}
}
else if (!ext->getSubElements().empty()) {
ext->setLink(-1, obj, nullptr, subElements);
}
else {
ext->setLink(-1, obj, nullptr);
}
return std::string();
}
bool ViewProviderLink::canDragAndDropObject(App::DocumentObject* obj) const
{
auto ext = getLinkExtension();
if (!ext) {
return true;
}
if (isGroup(ext)) {
return ext->getLinkModeValue() < App::LinkBaseExtension::LinkModeAutoLink
&& obj->getDocument() == getObject()->getDocument();
}
if (!ext->getLinkedObjectProperty() || hasElements(ext)) {
return false;
}
if (!hasSubName) {
auto linked = getLinkedView(false, ext);
if (linked) {
return linked->canDragAndDropObject(obj);
}
}
return false;
}
bool ViewProviderLink::getElementPicked(const SoPickedPoint* pp, std::string& subname) const
{
if (!isSelectable()) {
return false;
}
auto ext = getLinkExtension();
if (!ext) {
return false;
}
if (childVpLink && childVp) {
auto path = pp->getPath();
int idx = path->findNode(childVpLink->getSnapshot(LinkView::SnapshotTransform));
if (idx >= 0) {
return childVp->getElementPicked(pp, subname);
}
}
bool ret = linkView->linkGetElementPicked(pp, subname);
if (!ret) {
return ret;
}
if (isGroup(ext, true)) {
const char* sub = nullptr;
int idx = App::LinkBaseExtension::getArrayIndex(subname.c_str(), &sub);
if (idx >= 0) {
--sub;
assert(*sub == '.');
const auto& elements = ext->_getElementListValue();
subname.replace(0, sub - subname.c_str(), elements[idx]->getNameInDocument());
}
}
return ret;
}
bool ViewProviderLink::getDetailPath(const char* subname, SoFullPath* pPath, bool append, SoDetail*& det) const
{
auto ext = getLinkExtension();
if (!ext) {
return false;
}
auto len = pPath->getLength();
if (append) {
appendPath(pPath, pcRoot);
appendPath(pPath, pcModeSwitch);
}
if (childVpLink && getDefaultMode() == 1) {
if (childVpLink->getDetail(false, LinkView::SnapshotTransform, subname, det, pPath)) {
return true;
}
pPath->truncate(len);
return false;
}
std::string _subname;
if (!Base::Tools::isNullOrEmpty(subname)) {
if (auto linked = ext->getLinkedObjectValue()) {
if (const char* dot = strchr(subname, '.')) {
if (subname[0] == '$') {
CharRange sub(subname + 1, dot);
if (!boost::equals(sub, linked->Label.getValue())) {
dot = nullptr;
}
}
else {
CharRange sub(subname, dot);
if (!boost::equals(sub, linked->getNameInDocument())) {
dot = nullptr;
}
}
if (dot && linked->getSubObject(dot + 1)) {
subname = dot + 1;
}
}
}
if (isGroup(ext, true) || hasElements(ext) || ext->getElementCountValue()) {
int index = ext->getElementIndex(subname, &subname);
if (index >= 0) {
_subname = std::to_string(index) + '.' + subname;
subname = _subname.c_str();
}
}
}
if (linkView->linkGetDetailPath(subname, pPath, det)) {
return true;
}
pPath->truncate(len);
return false;
}
bool ViewProviderLink::onDelete(const std::vector<std::string>&)
{
auto element = getObject<App::LinkElement>();
if (element && !element->canDelete()) {
return false;
}
auto link = getObject<App::Link>();
if (link && link->ElementCount.getValue() != 0) {
auto doc = link->getDocument();
auto elements = link->ElementList.getValues();
for (auto element : elements) {
doc->removeObject(element->getNameInDocument());
}
}
auto ext = getLinkExtension();
if (ext->isLinkMutated()) {
auto linked = ext->getLinkedObjectValue();
auto doc = ext->getContainer()->getDocument();
if (linked->getDocument() == doc) {
std::deque<std::string> objs;
for (auto obj : ext->getOnChangeCopyObjects(nullptr, linked)) {
if (obj->getDocument() == doc) {
// getOnChangeCopyObjects() returns object in depending
// order. So we delete it in reverse to avoid error
// reported by some parent object failing to find child
objs.emplace_front(obj->getNameInDocument());
}
}
for (const auto& name : objs) {
doc->removeObject(name.c_str());
}
}
}
return true;
}
bool ViewProviderLink::canDelete(App::DocumentObject* obj) const
{
auto ext = getLinkExtension();
if (isGroup(ext) || hasElements(ext) || hasSubElement) {
return true;
}
auto linked = getLinkedView(false, ext);
if (linked) {
return linked->canDelete(obj);
}
return false;
}
bool ViewProviderLink::linkEdit(const App::LinkBaseExtension* ext) const
{
if (!ext) {
ext = getLinkExtension();
}
if (!ext || (!ext->_getShowElementValue() && ext->_getElementCountValue()) || hasElements(ext)
|| isGroup(ext) || hasSubName) {
return false;
}
return linkView->isLinked();
}
bool ViewProviderLink::doubleClicked()
{
if (linkEdit()) {
return linkView->getLinkedView()->doubleClicked();
}
return getDocument()->setEdit(this, ViewProvider::Transform);
}
void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const char* member)
{
auto ext = getLinkExtension();
if (!ext) {
return;
}
_setupContextMenu(ext, menu, receiver, member);
Gui::ActionFunction* func = nullptr;
if (ext->isLinkedToConfigurableObject()) {
auto src = ext->getLinkCopyOnChangeSourceValue();
if (!src) {
src = ext->getLinkedObjectValue();
}
if (src && !ext->getOnChangeCopyObjects(nullptr, src).empty()) {
QAction* act = menu->addAction(QObject::tr("Setup Configurable Object"));
act->setToolTip(
QObject::tr(
"Selects which object to copy or exclude when configuration changes. "
"All external linked objects are excluded by default."
)
);
act->setData(-1);
if (!func) {
func = new Gui::ActionFunction(menu);
}
func->trigger(act, [ext]() {
try {
std::vector<App::DocumentObject*> excludes;
auto src = ext->getLinkCopyOnChangeSourceValue();
if (!src) {
src = ext->getLinkedObjectValue();
}
auto objs = ext->getOnChangeCopyObjects(&excludes, src);
if (objs.empty()) {
return;
}
DlgObjectSelection dlg({src}, excludes, getMainWindow());
dlg.setMessage(
QObject::tr("Select which objects to copy when the configuration is changed")
);
auto box = new QCheckBox(QObject::tr("Apply to all"), &dlg);
box->setToolTip(QObject::tr("Applies the setting to all links"));
box->setChecked(App::LinkParams::getCopyOnChangeApplyToAll());
dlg.addCheckBox(box);
if (dlg.exec() != QDialog::Accepted) {
return;
}
bool applyAll = box->isChecked();
App::LinkParams::setCopyOnChangeApplyToAll(applyAll);
App::Link::OnChangeCopyOptions options {App::Link::OnChangeCopyOptions::None};
if (applyAll) {
options |= App::Link::OnChangeCopyOptions::ApplyAll;
}
App::AutoTransaction guard("Setup configurable object");
auto sels = dlg.getSelections(DlgObjectSelection::SelectionOptions::InvertSort);
for (const auto& exclude : excludes) {
auto iter = std::lower_bound(sels.begin(), sels.end(), exclude);
if (iter == sels.end() || *iter != exclude) {
ext->setOnChangeCopyObject(exclude, options);
}
else {
sels.erase(iter);
}
}
options |= App::Link::OnChangeCopyOptions::Exclude;
for (auto obj : sels) {
ext->setOnChangeCopyObject(obj, options);
}
if (!applyAll) {
ext->monitorOnChangeCopyObjects(ext->getOnChangeCopyObjects());
}
else {
std::set<App::LinkBaseExtension*> exts;
for (auto o : App::Document::getDependencyList(objs)) {
if (auto ext = o->getExtensionByType<App::LinkBaseExtension>(true)) {
exts.insert(ext);
}
}
for (auto ext : exts) {
ext->monitorOnChangeCopyObjects(ext->getOnChangeCopyObjects());
}
}
Command::updateActive();
}
catch (Base::Exception& e) {
e.reportException();
}
});
}
if (ext->getLinkCopyOnChangeValue() == 0) {
auto submenu = menu->addMenu(QObject::tr("Copy on Change"));
auto act = submenu->addAction(QObject::tr("Enable"));
act->setToolTip(
QObject::tr("Enable auto copy of linked object when its configuration is changed")
);
act->setData(-1);
if (!func) {
func = new Gui::ActionFunction(menu);
}
func->trigger(act, [ext]() {
try {
App::AutoTransaction guard("Enable Link copy on change");
ext->getLinkCopyOnChangeProperty()->setValue(1);
Command::updateActive();
}
catch (Base::Exception& e) {
e.reportException();
}
});
act = submenu->addAction(QObject::tr("Tracking"));
act->setToolTip(
QObject::tr(
"Copies the linked object when its configuration is changed.\n"
"Also auto redo the copy if the original linked object is changed.\n"
)
);
act->setData(-1);
func->trigger(act, [ext]() {
try {
App::AutoTransaction guard("Enable Link tracking");
ext->getLinkCopyOnChangeProperty()->setValue(3);
Command::updateActive();
}
catch (Base::Exception& e) {
e.reportException();
}
});
}
}
if (ext->getLinkCopyOnChangeValue() != 2 && ext->getLinkCopyOnChangeValue() != 0) {
QAction* act = menu->addAction(QObject::tr("Disable Copy on Change"));
act->setData(-1);
if (!func) {
func = new Gui::ActionFunction(menu);
}
func->trigger(act, [ext]() {
try {
App::AutoTransaction guard("Disable copy on change");
ext->getLinkCopyOnChangeProperty()->setValue((long)0);
Command::updateActive();
}
catch (Base::Exception& e) {
e.reportException();
}
});
}
if (ext->isLinkMutated()) {
QAction* act = menu->addAction(QObject::tr("Refresh Configurable Object"));
act->setToolTip(
QObject::tr(
"Synchronizes the original configurable source object by\n"
"creating a new deep copy. Any changes made to\n"
"the current copy will be lost.\n"
)
);
act->setData(-1);
if (!func) {
func = new Gui::ActionFunction(menu);
}
func->trigger(act, [ext]() {
try {
App::AutoTransaction guard("Link refresh");
ext->syncCopyOnChange();
Command::updateActive();
}
catch (Base::Exception& e) {
e.reportException();
}
});
}
}
void ViewProviderLink::_setupContextMenu(
App::LinkBaseExtension* ext,
QMenu* menu,
QObject* receiver,
const char* member
)
{
if (linkEdit(ext)) {
if (auto linkvp = freecad_cast<ViewProviderLink*>(linkView->getLinkedView())) {
linkvp->_setupContextMenu(ext, menu, receiver, member);
}
else {
linkView->getLinkedView()->setupContextMenu(menu, receiver, member);
}
}
if (ext->getLinkedObjectProperty() && ext->_getShowElementProperty()
&& ext->_getElementCountValue() > 1) {
auto action = menu->addAction(QObject::tr("Toggle Array Elements"), [ext] {
try {
App::AutoTransaction guard(QT_TRANSLATE_NOOP("Command", "Toggle array elements"));
ext->getShowElementProperty()->setValue(!ext->getShowElementValue());
Command::updateActive();
}
catch (Base::Exception& e) {
e.reportException();
}
});
action->setToolTip(
QObject::tr("Changes whether to show each link array element as individual objects")
);
}
if ((ext->getPlacementProperty() && !ext->getPlacementProperty()->isReadOnly())
|| (ext->getLinkPlacementProperty() && !ext->getLinkPlacementProperty()->isReadOnly())) {
bool found = false;
const auto actions = menu->actions();
for (auto action : actions) {
if (action->data().toInt() == ViewProvider::Transform) {
found = true;
break;
}
}
if (!found) {
QIcon iconObject = mergeGreyableOverlayIcons(
Gui::BitmapFactory().pixmap("Std_TransformManip.svg")
);
QAction* act = menu->addAction(iconObject, QObject::tr("Transform"), receiver, member);
act->setToolTip(QObject::tr("Transforms the object at the origin of the placement"));
act->setData(QVariant((int)ViewProvider::Transform));
}
}
if (ext->getColoredElementsProperty()) {
bool found = false;
const auto actions = menu->actions();
for (auto action : actions) {
if (action->data().toInt() == ViewProvider::Color) {
action->setText(QObject::tr("Override Colors"));
found = true;
break;
}
}
if (!found) {
QAction* act = menu->addAction(QObject::tr("Override Colors"), receiver, member);
act->setData(QVariant((int)ViewProvider::Color));
}
}
auto cmd = Application::Instance->commandManager().getCommandByName("Std_LinkSelectLinked");
menu->addAction(cmd->getAction()->action());
}
bool ViewProviderLink::initDraggingPlacement()
{
Base::PyGILStateLocker lock;
try {
auto* proxy = getPropertyByName("Proxy");
if (proxy && proxy->is<App::PropertyPythonObject>()) {
Py::Object feature = static_cast<App::PropertyPythonObject*>(proxy)->getValue();
const char* fname = "initDraggingPlacement";
if (feature.hasAttr(fname)) {
Py::Callable method(feature.getAttr(fname));
Py::Tuple arg;
Py::Object ret(method.apply(arg));
if (ret.isTuple()) {
PyObject *pymat, *pypla, *pybbox;
if (!PyArg_ParseTuple(
ret.ptr(),
"O!O!O!",
&Base::MatrixPy::Type,
&pymat,
&Base::PlacementPy::Type,
&pypla,
&Base::BoundBoxPy::Type,
&pybbox
)) {
FC_ERR("initDraggingPlacement() expects return of type tuple(matrix,placement,boundbox)");
return false;
}
dragCtx = std::make_unique<DraggerContext>();
dragCtx->initialPlacement
= *static_cast<Base::PlacementPy*>(pypla)->getPlacementPtr();
dragCtx->preTransform = *static_cast<Base::MatrixPy*>(pymat)->getMatrixPtr();
dragCtx->bbox = *static_cast<Base::BoundBoxPy*>(pybbox)->getBoundBoxPtr();
return true;
}
else if (!ret.isTrue()) {
return false;
}
}
}
}
catch (Py::Exception&) {
Base::PyException e;
e.reportException();
return false;
}
auto ext = getLinkExtension();
if (!ext) {
FC_ERR("no link extension");
return false;
}
if (!ext->hasPlacement()) {
FC_ERR("no placement");
return false;
}
auto doc = Application::Instance->editDocument();
if (!doc) {
FC_ERR("no editing document");
return false;
}
dragCtx = std::make_unique<DraggerContext>();
dragCtx->preTransform = doc->getEditingTransform();
const auto& pla = getObject()->getPlacementProperty()->getValue();
// Cancel out our own transformation from the editing transform, because
// the dragger is meant to change our transformation.
dragCtx->preTransform *= pla.inverse().toMatrix();
dragCtx->bbox = getBoundingBox(nullptr, false);
// The returned bounding box is before our own transform, but we still need
// to scale it to get the correct center.
auto scale = ext->getScaleVector();
dragCtx->bbox.ScaleX(scale.x);
dragCtx->bbox.ScaleY(scale.y);
dragCtx->bbox.ScaleZ(scale.z);
App::PropertyPlacement* propPla = nullptr;
if (ext->getLinkTransformValue() && ext->getLinkedObjectValue()) {
propPla = freecad_cast<App::PropertyPlacement*>(
ext->getLinkedObjectValue()->getPropertyByName("Placement")
);
}
if (propPla) {
dragCtx->initialPlacement = pla * propPla->getValue();
dragCtx->mat *= propPla->getValue().inverse().toMatrix();
}
else {
dragCtx->initialPlacement = pla;
}
return true;
}
ViewProvider* ViewProviderLink::startEditing(int mode)
{
if (mode == ViewProvider::Color) {
auto ext = getLinkExtension();
if (!ext || !ext->getColoredElementsProperty()) {
if (linkEdit(ext)) {
return linkView->getLinkedView()->startEditing(mode);
}
}
return inherited::startEditing(mode);
}
static thread_local bool _pendingTransform;
static thread_local Matrix4D _editingTransform;
auto doc = Application::Instance->editDocument();
if (mode == ViewProvider::Transform) {
if (_pendingTransform && doc) {
doc->setEditingTransform(_editingTransform);
}
if (!initDraggingPlacement()) {
return nullptr;
}
if (auto result = inherited::startEditing(mode)) {
if (transformDragger.get()) {
transformDragger->addStartCallback(dragStartCallback, this);
transformDragger->addFinishCallback(dragFinishCallback, this);
transformDragger->addMotionCallback(dragMotionCallback, this);
setDraggerPlacement(dragCtx->initialPlacement);
}
return result;
}
}
if (!linkEdit()) {
FC_ERR("unsupported edit mode " << mode);
return nullptr;
}
// TODO: the 0x8000 mask here is for caller to disambiguate the intention
// here, whether they want to, say transform the link itself or the linked
// object. Use of a mask here will allow forwarding those editing modes that
// are supported by both the link and the linked object, such as transform
// and set color. We need to find a better place to declare this constant.
mode &= ~0x8000;
if (!doc) {
FC_ERR("no editing document");
return nullptr;
}
// We are forwarding the editing request to linked object. We need to
// adjust the editing transformation.
Base::Matrix4D mat;
auto linked = getObject()->getLinkedObject(true, &mat, false);
if (!linked || linked == getObject()) {
FC_ERR("no linked object");
return nullptr;
}
auto vpd = freecad_cast<ViewProviderDocumentObject*>(
Application::Instance->getViewProvider(linked)
);
if (!vpd) {
FC_ERR("no linked viewprovider");
return nullptr;
}
// Amend the editing transformation with the link transformation.
// But save it first in case the linked object reroute the editing request
// back to us.
_editingTransform = doc->getEditingTransform();
doc->setEditingTransform(doc->getEditingTransform() * mat);
Base::FlagToggler<> guard(_pendingTransform);
return vpd->startEditing(mode);
}
bool ViewProviderLink::setEdit(int ModNum)
{
if (ModNum == ViewProvider::Color) {
auto ext = getLinkExtension();
if (!ext || !ext->getColoredElementsProperty()) {
return false;
}
TaskView::TaskDialog* dlg = Control().activeDialog();
if (dlg) {
Control().showDialog(dlg);
return false;
}
Selection().clearSelection();
return true;
}
return inherited::setEdit(ModNum);
}
void ViewProviderLink::setEditViewer(Gui::View3DInventorViewer* viewer, int ModNum)
{
if (ModNum == ViewProvider::Color) {
Gui::Control().showDialog(new TaskElementColors(this));
return;
}
ViewProviderDragger::setEditViewer(viewer, ModNum);
viewer->setupEditingRoot(transformDragger, &dragCtx->preTransform);
}
void ViewProviderLink::unsetEditViewer(Gui::View3DInventorViewer* viewer)
{
dragCtx.reset();
inherited::unsetEditViewer(viewer);
}
bool ViewProviderLink::callDraggerProxy(const char* fname)
{
if (!transformDragger) {
return false;
}
Base::PyGILStateLocker lock;
try {
auto* proxy = getPropertyByName("Proxy");
if (proxy && proxy->is<App::PropertyPythonObject>()) {
Py::Object feature = static_cast<App::PropertyPythonObject*>(proxy)->getValue();
if (feature.hasAttr(fname)) {
Py::Callable method(feature.getAttr(fname));
Py::Tuple args;
if (method.apply(args).isTrue()) {
return true;
}
}
}
}
catch (Py::Exception&) {
Base::PyException e;
e.reportException();
return true;
}
return false;
}
void ViewProviderLink::dragStartCallback(void* data, SoDragger*)
{
auto me = static_cast<ViewProviderLink*>(data);
me->dragCtx->initialPlacement = me->getDraggerPlacement();
me->callDraggerProxy("onDragStart");
}
void ViewProviderLink::dragFinishCallback(void* data, SoDragger*)
{
auto me = static_cast<ViewProviderLink*>(data);
me->callDraggerProxy("onDragEnd");
if (me->dragCtx->cmdPending) {
if (me->getDraggerPlacement() == me->dragCtx->initialPlacement) {
me->getDocument()->abortCommand();
}
else {
me->getDocument()->commitCommand();
}
}
}
void ViewProviderLink::dragMotionCallback(void* data, SoDragger*)
{
auto me = static_cast<ViewProviderLink*>(data);
me->callDraggerProxy("onDragMotion");
}
void ViewProviderLink::updateLinks(ViewProvider* vp)
{
try {
auto ext = vp->getExtensionByType<ViewProviderLinkObserver>(true);
if (ext && ext->linkInfo) {
ext->linkInfo->update();
}
}
catch (const Base::TypeError& e) {
e.reportException();
}
catch (const Base::ValueError& e) {
e.reportException();
}
}
PyObject* ViewProviderLink::getPyObject()
{
if (!pyViewObject) {
pyViewObject = new ViewProviderLinkPy(this);
}
pyViewObject->IncRef();
return pyViewObject;
}
PyObject* ViewProviderLink::getPyLinkView()
{
return linkView->getPyObject();
}
std::map<std::string, Base::Color> ViewProviderLink::getElementColors(const char* subname) const
{
bool isPrefix = true;
if (!subname) {
subname = "";
}
else {
auto len = strlen(subname);
isPrefix = !len || subname[len - 1] == '.';
}
std::map<std::string, Base::Color> colors;
auto ext = getLinkExtension();
if (!ext || !ext->getColoredElementsProperty()) {
return colors;
}
const auto& subs = ext->getColoredElementsProperty()->getShadowSubs();
int size = OverrideColorList.getSize();
std::string wildcard(subname);
if (wildcard == "Face" || wildcard == "Face*" || wildcard.empty()) {
if (wildcard.size() == 4 || OverrideMaterial.getValue()) {
Base::Color c = ShapeMaterial.getValue().diffuseColor;
c.setTransparency(ShapeMaterial.getValue().transparency);
colors["Face"] = c;
if (wildcard.size() == 4) {
return colors;
}
}
if (!wildcard.empty()) {
wildcard.resize(4);
}
}
else if (wildcard == "Edge*") {
wildcard.resize(4);
}
else if (wildcard == "Vertex*") {
wildcard.resize(5);
}
else if (wildcard == ViewProvider::hiddenMarker() + "*") {
wildcard.resize(ViewProvider::hiddenMarker().size());
}
else {
wildcard.clear();
}
int i = -1;
if (!wildcard.empty()) {
for (const auto& sub : subs) {
if (++i >= size) {
break;
}
auto pos = sub.oldName.rfind('.');
if (pos == std::string::npos) {
pos = 0;
}
else {
++pos;
}
const char* element = sub.oldName.c_str() + pos;
if (boost::starts_with(element, wildcard)) {
colors[sub.oldName] = OverrideColorList[i];
}
else if (!element[0] && wildcard == "Face") {
colors[sub.oldName.substr(0, element - sub.oldName.c_str()) + wildcard]
= OverrideColorList[i];
}
}
// In case of multi-level linking, we recursively call into each level,
// and merge the colors
auto vp = this;
while (true) {
if (wildcard != ViewProvider::hiddenMarker() && vp->OverrideMaterial.getValue()) {
auto color = ShapeMaterial.getValue().diffuseColor;
color.setTransparency(ShapeMaterial.getValue().transparency);
colors.emplace(wildcard, color);
}
auto link = vp->getObject()->getLinkedObject(false);
if (!link || link == vp->getObject()) {
break;
}
auto next = freecad_cast<ViewProviderLink*>(Application::Instance->getViewProvider(link));
if (!next) {
break;
}
for (const auto& v : next->getElementColors(subname)) {
colors.insert(v);
}
vp = next;
}
if (wildcard != ViewProvider::hiddenMarker()) {
// Get collapsed array color override.
auto ext = vp->getLinkExtension();
if (ext->_getElementCountValue() && !ext->_getShowElementValue()) {
const auto& overrides = vp->OverrideMaterialList.getValues();
int i = -1;
for (const auto& mat : vp->MaterialList.getValues()) {
if (++i >= (int)overrides.size()) {
break;
}
if (!overrides[i]) {
continue;
}
auto color = mat.diffuseColor;
color.setTransparency(mat.transparency);
colors.emplace(std::to_string(i) + "." + wildcard, color);
}
}
}
return colors;
}
int element_count = ext->getElementCountValue();
for (const auto& sub : subs) {
if (++i >= size) {
break;
}
int offset = 0;
if (!sub.oldName.empty() && element_count && !std::isdigit(sub.oldName[0])) {
// For checking and expanding color override of array base
if (!subname[0]) {
std::ostringstream ss;
ss << "0." << sub.oldName;
if (getObject()->getSubObject(ss.str().c_str())) {
for (int j = 0; j < element_count; ++j) {
ss.str("");
ss << j << '.' << sub.oldName;
colors.emplace(ss.str(), OverrideColorList[i]);
}
continue;
}
}
else if (std::isdigit(subname[0])) {
const char* dot = strchr(subname, '.');
if (dot) {
offset = dot - subname + 1;
}
}
}
if (isPrefix) {
if (!boost::starts_with(sub.newName, subname + offset)
&& !boost::starts_with(sub.oldName, subname + offset)) {
continue;
}
}
else if (sub.newName != subname + offset && sub.oldName != subname + offset) {
continue;
}
if (offset) {
colors.emplace(std::string(subname, offset) + sub.oldName, OverrideColorList[i]);
}
else {
colors[sub.oldName] = OverrideColorList[i];
}
}
if (!subname[0]) {
return colors;
}
bool found = true;
if (colors.empty()) {
found = false;
colors.emplace(subname, Base::Color());
}
std::map<std::string, Base::Color> ret;
for (const auto& v : colors) {
const char* pos = nullptr;
auto sobj = getObject()->resolve(v.first.c_str(), nullptr, nullptr, &pos);
if (!sobj || !pos) {
continue;
}
auto link = sobj->getLinkedObject(true);
if (!link || link == getObject()) {
continue;
}
auto vp = Application::Instance->getViewProvider(sobj->getLinkedObject(true));
if (!vp) {
continue;
}
for (const auto& v2 : vp->getElementColors(!pos[0] ? "Face" : pos)) {
std::string name;
if (pos[0]) {
name = v.first.substr(0, pos - v.first.c_str()) + v2.first;
}
else {
name = v.first;
}
ret[name] = found ? v.second : v2.second;
}
}
return ret;
}
void ViewProviderLink::setElementColors(const std::map<std::string, Base::Color>& colorMap)
{
auto ext = getLinkExtension();
if (!ext || !ext->getColoredElementsProperty()) {
return;
}
// For checking and collapsing array element color
std::map<std::string, std::map<int, Base::Color>> subMap;
int element_count = ext->getElementCountValue();
std::vector<std::string> subs;
std::vector<Base::Color> colors;
Base::Color faceColor;
bool hasFaceColor = false;
for (const auto& v : colorMap) {
if (!hasFaceColor && v.first == "Face") {
hasFaceColor = true;
faceColor = v.second;
continue;
}
if (element_count && !v.first.empty() && std::isdigit(v.first[0])) {
// In case of array, check if there are override of the same
// sub-element for every array element. And collapse those overrides
// into one without the index.
const char* dot = strchr(v.first.c_str(), '.');
if (dot) {
subMap[dot + 1][std::atoi(v.first.c_str())] = v.second;
continue;
}
}
subs.push_back(v.first);
colors.push_back(v.second);
}
for (auto& v : subMap) {
if (element_count == (int)v.second.size()) {
Base::Color firstColor = v.second.begin()->second;
subs.push_back(v.first);
colors.push_back(firstColor);
for (auto it = v.second.begin(); it != v.second.end();) {
if (it->second == firstColor) {
it = v.second.erase(it);
}
else {
++it;
}
}
}
std::ostringstream ss;
for (const auto& colorInfo : v.second) {
ss.str("");
ss << colorInfo.first << '.' << v.first;
subs.push_back(ss.str());
colors.push_back(colorInfo.second);
}
}
auto prop = ext->getColoredElementsProperty();
if (subs != prop->getSubValues() || colors != OverrideColorList.getValues()) {
prop->setStatus(App::Property::User3, true);
prop->setValue(getObject(), subs);
prop->setStatus(App::Property::User3, false);
OverrideColorList.setValues(colors);
}
if (hasFaceColor) {
auto mat = ShapeMaterial.getValue();
mat.diffuseColor = faceColor;
mat.transparency = faceColor.transparency();
ShapeMaterial.setStatus(App::Property::User3, true);
ShapeMaterial.setValue(mat);
ShapeMaterial.setStatus(App::Property::User3, false);
}
OverrideMaterial.setValue(hasFaceColor);
}
void ViewProviderLink::applyColors()
{
auto ext = getLinkExtension();
if (!ext || !ext->getColoredElementsProperty()) {
return;
}
SoSelectionElementAction action(SoSelectionElementAction::Color, true);
// reset color and visibility first
action.apply(linkView->getLinkRoot());
std::map<std::string, std::map<std::string, Base::Color>> colorMap;
std::set<std::string> hideList;
auto colors = getElementColors();
colors.erase("Face");
for (const auto& v : colors) {
const char* subname = v.first.c_str();
const char* element = nullptr;
auto sobj = getObject()->resolve(subname, nullptr, nullptr, &element);
if (!sobj || !element) {
continue;
}
if (ViewProvider::hiddenMarker() == element) {
hideList.emplace(subname, element - subname);
}
else {
colorMap[std::string(subname, element - subname)][element] = v.second;
}
}
SoTempPath path(10);
path.ref();
for (auto& v : colorMap) {
action.swapColors(v.second);
if (v.first.empty()) {
action.apply(linkView->getLinkRoot());
continue;
}
SoDetail* det = nullptr;
path.truncate(0);
if (getDetailPath(v.first.c_str(), &path, false, det)) {
action.apply(&path);
}
delete det;
}
action.setType(SoSelectionElementAction::Hide);
for (const auto& sub : hideList) {
SoDetail* det = nullptr;
path.truncate(0);
if (!sub.empty() && getDetailPath(sub.c_str(), &path, false, det)) {
action.apply(&path);
}
delete det;
}
path.unrefNoDelete();
}
void ViewProviderLink::setOverrideMode(const std::string& mode)
{
auto ext = getLinkExtension();
if (!ext) {
return;
}
auto obj = ext->getTrueLinkedObject(false);
if (obj && obj != getObject()) {
auto vp = Application::Instance->getViewProvider(obj);
vp->setOverrideMode(mode);
}
if (childVp) {
childVp->setOverrideMode(mode);
}
}
void ViewProviderLink::onBeforeChange(const App::Property* prop)
{
if (prop == &ChildViewProvider) {
if (childVp) {
childVp->beforeDelete();
pcModeSwitch->replaceChild(1, linkView->getLinkRoot());
childVpLink.reset();
childVp = nullptr;
}
}
inherited::onBeforeChange(prop);
}
static bool isExcludedProperties(const char* name)
{
#define CHECK_EXCLUDE_PROP(_name) \
if (strcmp(name, #_name) == 0) \
return true;
CHECK_EXCLUDE_PROP(Proxy);
return false;
}
App::Property* ViewProviderLink::getPropertyByName(const char* name) const
{
auto prop = inherited::getPropertyByName(name);
if (prop || isExcludedProperties(name)) {
return prop;
}
if (childVp) {
prop = childVp->getPropertyByName(name);
if (prop && !prop->testStatus(App::Property::Hidden)) {
return prop;
}
prop = nullptr;
}
if (pcObject && pcObject->canLinkProperties()) {
auto linked = getLinkedViewProvider(nullptr, true);
if (linked && linked != this) {
prop = linked->getPropertyByName(name);
}
}
return prop;
}
void ViewProviderLink::getPropertyMap(std::map<std::string, App::Property*>& Map) const
{
inherited::getPropertyMap(Map);
if (!childVp) {
return;
}
std::map<std::string, App::Property*> childMap;
childVp->getPropertyMap(childMap);
for (const auto& v : childMap) {
auto ret = Map.insert(v);
if (!ret.second) {
auto myProp = ret.first->second;
if (myProp->testStatus(App::Property::Hidden)) {
ret.first->second = v.second;
}
}
}
}
void ViewProviderLink::visitProperties(const std::function<void(App::Property*)>& visitor) const
{
inherited::visitProperties(visitor);
if (childVp != nullptr) {
childVp->visitProperties(visitor);
}
}
void ViewProviderLink::getPropertyList(std::vector<App::Property*>& List) const
{
std::map<std::string, App::Property*> Map;
getPropertyMap(Map);
List.reserve(List.size() + Map.size());
for (const auto& v : Map) {
List.push_back(v.second);
}
}
ViewProviderDocumentObject* ViewProviderLink::getLinkedViewProvider(
std::string* subname,
bool recursive
) const
{
auto self = const_cast<ViewProviderLink*>(this);
auto ext = getLinkExtension();
if (!ext) {
return self;
}
App::DocumentObject* linked = nullptr;
if (!recursive) {
linked = ext->getLink();
const char* s = ext->getSubName();
if (subname && s) {
*subname = s;
}
}
else {
linked = ext->getTrueLinkedObject(recursive);
}
if (!linked) {
return self;
}
auto res = freecad_cast<ViewProviderDocumentObject*>(
Application::Instance->getViewProvider(linked)
);
if (res) {
return res;
}
return self;
}
void ViewProviderLink::setTransformation(const Base::Matrix4D& rcMatrix)
{
inherited::setTransformation(rcMatrix);
auto ext = getLinkExtension();
if (ext) {
if (ext->getScaleVectorProperty()) {
updateDataPrivate(getLinkExtension(), ext->getScaleVectorProperty());
}
else {
updateDataPrivate(getLinkExtension(), ext->getScaleProperty());
}
}
}
void ViewProviderLink::setTransformation(const SbMatrix& rcMatrix)
{
inherited::setTransformation(rcMatrix);
auto ext = getLinkExtension();
if (ext) {
if (ext->getScaleVectorProperty()) {
updateDataPrivate(getLinkExtension(), ext->getScaleVectorProperty());
}
else {
updateDataPrivate(getLinkExtension(), ext->getScaleProperty());
}
}
}
////////////////////////////////////////////////////////////////////////////////////////
namespace Gui
{
PROPERTY_SOURCE_TEMPLATE(Gui::ViewProviderLinkPython, Gui::ViewProviderLink)
template class GuiExport ViewProviderFeaturePythonT<ViewProviderLink>;
} // namespace Gui