This patch substitutes by isAttachedToDocument() (almost) everywhere where getNameInDocument() is used for this purpose. The very few places not touched by this patch demand a (just a little) less trivial change. When we change the returning type of getNameInDocument() to std::string, those places will be easily found, because they shall generate a compiler error (converting std::string to bool). Rationale: The fact that getNameInDocument() return nullptr to indicate that the object is not attached to a document is responsible for lots of bugs where the developer does not check for "nullptr". The idea is to eliminate all those uses of getNameInDocument() and, in the near future, make getNameInDocument() return always a valid std::string.
3447 lines
117 KiB
C++
3447 lines
117 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 "PreCompiled.h"
|
|
|
|
#ifndef _PreComp_
|
|
# include <atomic>
|
|
# include <cctype>
|
|
# 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 <Inventor/SoPickedPoint.h>
|
|
# include <QApplication>
|
|
# include <QMenu>
|
|
# include <QCheckBox>
|
|
#endif
|
|
|
|
#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 "MainWindow.h"
|
|
#include "ViewProviderLink.h"
|
|
#include "ViewProviderLinkPy.h"
|
|
#include "Application.h"
|
|
#include "BitmapFactory.h"
|
|
#include "Control.h"
|
|
#include "LinkViewPy.h"
|
|
#include "Selection.h"
|
|
#include "SoFCCSysDragger.h"
|
|
#include "SoFCUnifiedSelection.h"
|
|
#include "TaskCSysDragger.h"
|
|
#include "TaskElementColors.h"
|
|
#include "View3DInventor.h"
|
|
#include "ViewParams.h"
|
|
#include "ViewProviderGeometryObject.h"
|
|
|
|
#include "ActionFunction.h"
|
|
#include "Command.h"
|
|
#include "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::getClassTypeId()))
|
|
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);
|
|
}
|
|
}
|
|
|
|
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()->getNameInDocument();
|
|
}
|
|
|
|
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 = -1;
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// VC2013 has trouble with template argument dependent lookup in
|
|
// namespace. Have to put the below functions in global namespace.
|
|
//
|
|
// However, gcc seems to behave the opposite, hence the conditional
|
|
// compilation here.
|
|
//
|
|
#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()==-1)
|
|
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 = -1;
|
|
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 = -1;
|
|
coinRemoveAllChildren(pcModeSwitch);
|
|
|
|
SoSwitch *pcUpdateSwitch = pcModeSwitch;
|
|
|
|
auto childRoot = pcLinked->getChildRoot();
|
|
|
|
for(int i=0,count=root->getNumChildren();i<count;++i) {
|
|
SoNode *node = root->getChild(i);
|
|
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() || !pcLinked->isSelectable())
|
|
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)
|
|
iconSize = QApplication::style()->standardPixmap(QStyle::SP_DirClosedIcon).width();
|
|
|
|
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();
|
|
}
|
|
}
|
|
#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 =
|
|
Base::freecad_dynamic_cast<ViewProviderDocumentObject>(getExtendedContainer());
|
|
linkInfo->update();
|
|
}
|
|
}
|
|
|
|
void ViewProviderLinkObserver::extensionOnChanged(const App::Property *prop) {
|
|
(void)prop;
|
|
}
|
|
|
|
void ViewProviderLinkObserver::extensionModeSwitchChange() {
|
|
auto owner = freecad_dynamic_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;
|
|
}
|
|
App::Color c = material->diffuseColor;
|
|
c.a = 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;
|
|
}
|
|
App::Color c = material->diffuseColor;
|
|
c.a = material->transparency;
|
|
info.pcRoot->setColorOverride(c);
|
|
}
|
|
}
|
|
|
|
void LinkView::setLink(App::DocumentObject *obj, const std::vector<std::string> &subs) {
|
|
setLinkViewObject(Base::freecad_dynamic_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:-1;
|
|
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:-1;
|
|
}
|
|
|
|
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(nodeIdx == it->second)
|
|
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()) {
|
|
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)
|
|
,useCenterballDragger(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;
|
|
}
|
|
|
|
ViewProviderLink::~ViewProviderLink()
|
|
{
|
|
linkView->setInvalid();
|
|
}
|
|
|
|
bool ViewProviderLink::isSelectable() const {
|
|
return !pcDragger && Selectable.getValue();
|
|
}
|
|
|
|
void ViewProviderLink::attach(App::DocumentObject *pcObj) {
|
|
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::getClassTypeId()))
|
|
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();
|
|
int px = 12 * getMainWindow()->devicePixelRatioF();
|
|
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==&ChildViewProvider) {
|
|
childVp = freecad_dynamic_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);
|
|
if(!isRestoring() && !pcObject->isRestoring()) {
|
|
auto ext = getLinkExtension();
|
|
if(ext) updateDataPrivate(getLinkExtension(),prop);
|
|
}
|
|
return inherited::updateData(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) {
|
|
const auto &pla = static_cast<const App::PropertyPlacement*>(prop)->getValue();
|
|
ViewProviderGeometryObject::updateTransform(pla, pcTransform);
|
|
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 = Base::freecad_dynamic_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_dynamic_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_dynamic_cast<App::LinkElement>(elements[i]);
|
|
if(!element) continue;
|
|
auto vp = freecad_dynamic_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_dynamic_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 = freecad_dynamic_cast<App::LinkElement>(getObject());
|
|
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())
|
|
linkView->setMaterial(-1,&ShapeMaterial.getValue());
|
|
else {
|
|
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);
|
|
}
|
|
linkView->setMaterial(-1,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_dynamic_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_dynamic_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(subname && subname[0]) {
|
|
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 = freecad_dynamic_cast<App::LinkElement>(getObject());
|
|
if (element && !element->canDelete())
|
|
return false;
|
|
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(
|
|
"Select 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(
|
|
"Please select which objects to copy when the configuration is changed"));
|
|
auto box = new QCheckBox(QObject::tr("Apply to all"), &dlg);
|
|
box->setToolTip(QObject::tr("Apply the setting to all links. Or, uncheck this\n"
|
|
"option to apply only to this link."));
|
|
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(
|
|
"Copy 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(
|
|
"Synchronize the original configurable source object by\n"
|
|
"creating a new deep copy. Note that 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 = Base::freecad_dynamic_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(
|
|
"Change whether 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("Transform 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));
|
|
}
|
|
}
|
|
}
|
|
|
|
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();
|
|
doc->setEditingTransform(dragCtx->preTransform);
|
|
|
|
const auto &pla = ext->getPlacementProperty()?
|
|
ext->getPlacementValue():ext->getLinkPlacementValue();
|
|
|
|
// 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);
|
|
|
|
auto modifier = QApplication::queryKeyboardModifiers();
|
|
// Determine the dragger base position
|
|
// if CTRL key is down, force to use bound box center,
|
|
// if SHIFT key is down, force to use origine,
|
|
// if not a sub link, use origine,
|
|
// else (e.g. group, array, sub link), use bound box center
|
|
if(modifier != Qt::ShiftModifier
|
|
&& ((ext->getLinkedObjectValue() && !linkView->hasSubs())
|
|
|| modifier == Qt::ControlModifier))
|
|
{
|
|
App::PropertyPlacement *propPla = nullptr;
|
|
if(ext->getLinkTransformValue() && ext->getLinkedObjectValue()) {
|
|
propPla = Base::freecad_dynamic_cast<App::PropertyPlacement>(
|
|
ext->getLinkedObjectValue()->getPropertyByName("Placement"));
|
|
}
|
|
if(propPla) {
|
|
dragCtx->initialPlacement = pla * propPla->getValue();
|
|
dragCtx->mat *= propPla->getValue().inverse().toMatrix();
|
|
} else
|
|
dragCtx->initialPlacement = pla;
|
|
|
|
} else {
|
|
auto offset = dragCtx->bbox.GetCenter();
|
|
|
|
// This determines the initial placement of the dragger. We place it at the
|
|
// center of our bounding box.
|
|
dragCtx->initialPlacement = pla * Base::Placement(offset, Base::Rotation());
|
|
|
|
// dragCtx->mat is to transform the dragger placement to our own placement.
|
|
// Since the dragger is placed at the center, we set the transformation by
|
|
// moving the same amount in reverse direction.
|
|
dragCtx->mat.move(Vector3d() - offset);
|
|
}
|
|
|
|
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 Base::Matrix4D _editingTransform;
|
|
|
|
auto doc = Application::Instance->editDocument();
|
|
|
|
if(mode==ViewProvider::Transform) {
|
|
if(_pendingTransform && doc)
|
|
doc->setEditingTransform(_editingTransform);
|
|
|
|
if(!initDraggingPlacement())
|
|
return nullptr;
|
|
if(useCenterballDragger)
|
|
pcDragger = CoinPtr<SoCenterballDragger>(new SoCenterballDragger);
|
|
else
|
|
pcDragger = CoinPtr<SoFCCSysDragger>(new SoFCCSysDragger);
|
|
updateDraggingPlacement(dragCtx->initialPlacement,true);
|
|
pcDragger->addStartCallback(dragStartCallback, this);
|
|
pcDragger->addFinishCallback(dragFinishCallback, this);
|
|
pcDragger->addMotionCallback(dragMotionCallback, this);
|
|
return inherited::startEditing(mode);
|
|
}
|
|
|
|
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_dynamic_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;
|
|
}
|
|
|
|
if (pcDragger && viewer)
|
|
{
|
|
auto rootPickStyle = new SoPickStyle();
|
|
rootPickStyle->style = SoPickStyle::UNPICKABLE;
|
|
static_cast<SoFCUnifiedSelection*>(
|
|
viewer->getSceneGraph())->insertChild(rootPickStyle, 0);
|
|
|
|
if(useCenterballDragger) {
|
|
auto dragger = static_cast<SoCenterballDragger*>(pcDragger.get());
|
|
auto group = new SoAnnotation;
|
|
auto pickStyle = new SoPickStyle;
|
|
pickStyle->setOverride(true);
|
|
group->addChild(pickStyle);
|
|
group->addChild(pcDragger);
|
|
|
|
// Because the dragger is not grouped with the actual geometry,
|
|
// we use an invisible cube sized by the bounding box obtained from
|
|
// initDraggingPlacement() to scale the centerball dragger properly
|
|
|
|
auto * ss = static_cast<SoSurroundScale*>(dragger->getPart("surroundScale", TRUE));
|
|
ss->numNodesUpToContainer = 3;
|
|
ss->numNodesUpToReset = 2;
|
|
|
|
auto *geoGroup = new SoGroup;
|
|
group->addChild(geoGroup);
|
|
auto *style = new SoDrawStyle;
|
|
style->style.setValue(SoDrawStyle::INVISIBLE);
|
|
style->setOverride(TRUE);
|
|
geoGroup->addChild(style);
|
|
auto *cube = new SoCube;
|
|
geoGroup->addChild(cube);
|
|
auto length = std::max(std::max(dragCtx->bbox.LengthX(),
|
|
dragCtx->bbox.LengthY()), dragCtx->bbox.LengthZ());
|
|
cube->width = length;
|
|
cube->height = length;
|
|
cube->depth = length;
|
|
|
|
viewer->setupEditingRoot(group,&dragCtx->preTransform);
|
|
}else{
|
|
auto dragger = static_cast<SoFCCSysDragger*>(pcDragger.get());
|
|
dragger->draggerSize.setValue(0.05f);
|
|
dragger->setUpAutoScale(viewer->getSoRenderManager()->getCamera());
|
|
viewer->setupEditingRoot(pcDragger,&dragCtx->preTransform);
|
|
|
|
auto task = new TaskCSysDragger(this, dragger);
|
|
Gui::Control().showDialog(task);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ViewProviderLink::unsetEditViewer(Gui::View3DInventorViewer* viewer)
|
|
{
|
|
SoNode *child = static_cast<SoFCUnifiedSelection*>(viewer->getSceneGraph())->getChild(0);
|
|
if (child && child->isOfType(SoPickStyle::getClassTypeId()))
|
|
static_cast<SoFCUnifiedSelection*>(viewer->getSceneGraph())->removeChild(child);
|
|
pcDragger.reset();
|
|
dragCtx.reset();
|
|
Gui::Control().closeDialog();
|
|
}
|
|
|
|
Base::Placement ViewProviderLink::currentDraggingPlacement() const
|
|
{
|
|
// if there isn't an active dragger return a default placement
|
|
if (!pcDragger)
|
|
return Base::Placement();
|
|
|
|
SbVec3f v;
|
|
SbRotation r;
|
|
if (useCenterballDragger) {
|
|
auto dragger = static_cast<SoCenterballDragger*>(pcDragger.get());
|
|
v = dragger->center.getValue();
|
|
r = dragger->rotation.getValue();
|
|
}
|
|
else {
|
|
auto dragger = static_cast<SoFCCSysDragger*>(pcDragger.get());
|
|
v = dragger->translation.getValue();
|
|
r = dragger->rotation.getValue();
|
|
}
|
|
|
|
float q1,q2,q3,q4;
|
|
r.getValue(q1,q2,q3,q4);
|
|
return Base::Placement(Base::Vector3d(v[0],v[1],v[2]),Base::Rotation(q1,q2,q3,q4));
|
|
}
|
|
|
|
void ViewProviderLink::enableCenterballDragger(bool enable) {
|
|
if(enable == useCenterballDragger)
|
|
return;
|
|
if(pcDragger)
|
|
LINK_THROW(Base::RuntimeError,"Cannot change dragger during dragging");
|
|
useCenterballDragger = enable;
|
|
}
|
|
|
|
void ViewProviderLink::updateDraggingPlacement(const Base::Placement &pla, bool force) {
|
|
if(pcDragger && (force || currentDraggingPlacement()!=pla)) {
|
|
const auto &pos = pla.getPosition();
|
|
const auto &rot = pla.getRotation();
|
|
FC_LOG("updating dragger placement (" << pos.x << ", " << pos.y << ", " << pos.z << ')');
|
|
if(useCenterballDragger) {
|
|
auto dragger = static_cast<SoCenterballDragger*>(pcDragger.get());
|
|
SbBool wasenabled = dragger->enableValueChangedCallbacks(FALSE);
|
|
SbMatrix matrix;
|
|
matrix = convert(pla.toMatrix());
|
|
dragger->center.setValue(SbVec3f(0,0,0));
|
|
dragger->setMotionMatrix(matrix);
|
|
if (wasenabled) {
|
|
dragger->enableValueChangedCallbacks(TRUE);
|
|
dragger->valueChanged();
|
|
}
|
|
}else{
|
|
auto dragger = static_cast<SoFCCSysDragger*>(pcDragger.get());
|
|
dragger->translation.setValue(SbVec3f(pos.x,pos.y,pos.z));
|
|
dragger->rotation.setValue(rot[0],rot[1],rot[2],rot[3]);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ViewProviderLink::callDraggerProxy(const char *fname, bool update) {
|
|
if(!pcDragger)
|
|
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;
|
|
}
|
|
|
|
if(update) {
|
|
auto ext = getLinkExtension();
|
|
if(ext) {
|
|
const auto &pla = currentDraggingPlacement();
|
|
auto prop = ext->getLinkPlacementProperty();
|
|
if(!prop)
|
|
prop = ext->getPlacementProperty();
|
|
if(prop) {
|
|
auto plaNew = pla * Base::Placement(dragCtx->mat);
|
|
if(prop->getValue()!=plaNew)
|
|
prop->setValue(plaNew);
|
|
}
|
|
updateDraggingPlacement(pla);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ViewProviderLink::dragStartCallback(void *data, SoDragger *) {
|
|
auto me = static_cast<ViewProviderLink*>(data);
|
|
me->dragCtx->initialPlacement = me->currentDraggingPlacement();
|
|
if(!me->callDraggerProxy("onDragStart",false)) {
|
|
me->dragCtx->cmdPending = true;
|
|
me->getDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Link Transform"));
|
|
}else
|
|
me->dragCtx->cmdPending = false;
|
|
}
|
|
|
|
void ViewProviderLink::dragFinishCallback(void *data, SoDragger *) {
|
|
auto me = static_cast<ViewProviderLink*>(data);
|
|
me->callDraggerProxy("onDragEnd",true);
|
|
if(me->dragCtx->cmdPending) {
|
|
if(me->currentDraggingPlacement() == 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",true);
|
|
}
|
|
|
|
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, App::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, App::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()) {
|
|
App::Color c = ShapeMaterial.getValue().diffuseColor;
|
|
c.a = 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.second.rfind('.');
|
|
if(pos == std::string::npos)
|
|
pos = 0;
|
|
else
|
|
++pos;
|
|
const char *element = sub.second.c_str()+pos;
|
|
if(boost::starts_with(element,wildcard))
|
|
colors[sub.second] = OverrideColorList[i];
|
|
else if(!element[0] && wildcard=="Face")
|
|
colors[sub.second.substr(0,element-sub.second.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.a = ShapeMaterial.getValue().transparency;
|
|
colors.emplace(wildcard,color);
|
|
}
|
|
auto link = vp->getObject()->getLinkedObject(false);
|
|
if(!link || link==vp->getObject())
|
|
break;
|
|
auto next = freecad_dynamic_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.a = 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.second.empty() && element_count && !std::isdigit(sub.second[0])) {
|
|
// For checking and expanding color override of array base
|
|
if(!subname[0]) {
|
|
std::ostringstream ss;
|
|
ss << "0." << sub.second;
|
|
if(getObject()->getSubObject(ss.str().c_str())) {
|
|
for(int j=0;j<element_count;++j) {
|
|
ss.str("");
|
|
ss << j << '.' << sub.second;
|
|
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.first,subname+offset)
|
|
&& !boost::starts_with(sub.second,subname+offset))
|
|
continue;
|
|
}else if(sub.first!=subname+offset && sub.second!=subname+offset)
|
|
continue;
|
|
|
|
if(offset)
|
|
colors.emplace(std::string(subname,offset)+sub.second, OverrideColorList[i]);
|
|
else
|
|
colors[sub.second] = OverrideColorList[i];
|
|
}
|
|
|
|
if(!subname[0])
|
|
return colors;
|
|
|
|
bool found = true;
|
|
if(colors.empty()) {
|
|
found = false;
|
|
colors.emplace(subname,App::Color());
|
|
}
|
|
std::map<std::string, App::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, App::Color> &colorMap) {
|
|
auto ext = getLinkExtension();
|
|
if(!ext || ! ext->getColoredElementsProperty())
|
|
return;
|
|
|
|
// For checking and collapsing array element color
|
|
std::map<std::string,std::map<int,App::Color> > subMap;
|
|
int element_count = ext->getElementCountValue();
|
|
|
|
std::vector<std::string> subs;
|
|
std::vector<App::Color> colors;
|
|
App::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()) {
|
|
App::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.a;
|
|
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,App::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::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 = Base::freecad_dynamic_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 ViewProviderPythonFeatureT<ViewProviderLink>;
|
|
}
|