/**************************************************************************** * Copyright (c) 2017 Zheng, Lei (realthunder) * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ****************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "Application.h" #include "BitmapFactory.h" #include "Document.h" #include "Selection.h" #include "MainWindow.h" #include "ViewProviderLink.h" #include "ViewProviderLinkPy.h" #include "LinkViewPy.h" #include "ViewProviderGeometryObject.h" #include "ViewProviderGroupExtension.h" #include "View3DInventor.h" #include "SoFCUnifiedSelection.h" #include "SoFCCSysDragger.h" #include "Control.h" #include "TaskCSysDragger.h" #include "TaskElementColors.h" #include "ViewParams.h" FC_LOG_LEVEL_INIT("App::Link",true,true) using namespace Gui; using namespace Base; //////////////////////////////////////////////////////////////////////////// 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 ref; typedef boost::signals2::scoped_connection Connection; Connection connChangeIcon; ViewProviderDocumentObject *pcLinked; std::unordered_set links; typedef LinkInfoPtr Pointer; SoNodeSensor sensor; SoNodeSensor switchSensor; SoNodeSensor childSensor; SoNodeSensor transformSensor; std::array,LinkView::SnapshotMax> pcSnapshots; std::array,LinkView::SnapshotMax> pcSwitches; CoinPtr pcLinkedSwitch; // for group type view providers CoinPtr pcChildGroup; typedef std::unordered_map NodeMap; NodeMap nodeMap; std::map iconMap; static ViewProviderDocumentObject *getView(App::DocumentObject *obj) { if(obj && obj->getNameInDocument()) { Document *pDoc = Application::Instance->getDocument(obj->getDocument()); if(pDoc) { ViewProvider *vp = pDoc->getViewProvider(obj); if(vp && vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) return static_cast(vp); } } return 0; } 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(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(data)->update(); } static void switchSensorCB(void *data, SoSensor *) { static_cast(data)->updateSwitch(); } static void childSensorCB(void *data, SoSensor *) { static_cast(data)->updateChildren(); } static void transformSensorCB(void *data, SoSensor *) { auto self = static_cast(data); for(size_t i=0;ipcSnapshots.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()); connChangeIcon = vp->signalChangeIcon.connect( boost::bind(&LinkInfo::slotChangeIcon,this)); 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() { } 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()->getNameInDocument(); } const char *getLinkedName() const { return pcLinked->getObject()->getNameInDocument(); } const char *getLinkedNameSafe() const { if(isLinked()) return getLinkedName(); return ""; } 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.size()) { auto link = *links.begin(); links.erase(links.begin()); link->unlink(me); } } sensor.detach(); switchSensor.detach(); childSensor.detach(); transformSensor.detach(); for(auto &node : pcSnapshots) { if(node) coinRemoveAllChildren(node); } for(auto &node : pcSwitches) { if(node) coinRemoveAllChildren(node); } pcLinkedSwitch.reset(); if(pcChildGroup) { coinRemoveAllChildren(pcChildGroup); pcChildGroup.reset(); } pcLinked = 0; connChangeIcon.disconnect(); } void updateSwitch(SoSwitch *node=0) { if(!isLinked() || !pcLinkedSwitch) return; int index = pcLinkedSwitch->whichChild.getValue(); for(size_t i=0;igetNumChildren(); 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(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 oppsite, hence the conditional // compilation here. // #ifdef _MSC_VER friend void ::intrusive_ptr_add_ref(LinkInfo *px); friend void ::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 0; SoSeparator *root; if(!isLinked() || !(root=pcLinked->getRoot())) return 0; 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->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();igetChild(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) { SoTransform *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(node); if(switchSensor.getAttachedNode() != pcLinkedSwitch) { switchSensor.detach(); switchSensor.attach(pcLinkedSwitch); pcUpdateSwitch = 0; } pcSnapshot->addChild(pcModeSwitch); for(int i=0,count=pcLinkedSwitch->getNumChildren();igetChild(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;igetChildRoot()) { 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,0); 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<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(1) { 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::ComplexGeoData::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(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; } }; #ifdef _MSC_VER 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(getExtendedContainer()); linkInfo->update(); } } void ViewProviderLinkObserver::extensionOnChanged(const App::Property *prop) { #if 0 auto owner = freecad_dynamic_cast(getExtendedContainer()); if(!owner || !linkInfo) return; if(prop != &owner->Visibility && prop != &owner->DisplayMode) linkInfo->update(); #else (void)prop; #endif } void ViewProviderLinkObserver::extensionModeSwitchChange() { auto owner = freecad_dynamic_cast(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 pcNode; CoinPtr pcTransform; std::set subElements; friend LinkView; SubInfo(LinkView &handle):handle(handle) { pcNode = new SoFCSelectionRoot(true); pcTransform = new SoTransform; pcNode->addChild(pcTransform); } ~SubInfo() { unlink(); auto root = handle.getLinkRoot(); if(root) { int idx = root->findChild(pcNode); if(idx>=0) root->removeChild(idx); } } virtual void onLinkedIconChange(LinkInfoPtr) override { if(handle.autoSubLink && handle.subInfo.size()==1) handle.onLinkedIconChange(handle.linkInfo); } virtual 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 pcSwitch; CoinPtr pcRoot; CoinPtr 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() { unlink(); auto root = handle.getLinkRoot(); if(root) { int idx = root->findChild(pcRoot); if(idx>=0) root->removeChild(idx); } } virtual 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(void) { if (PythonObject.is(Py::_None())) PythonObject = Py::Object(new LinkViewPy(this),true); return Py::new_reference_to(PythonObject); } void LinkView::setInvalid(void) { if (!PythonObject.is(Py::_None())){ Base::PyObjectBase* obj = (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(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 0; } 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::UNKNOWN_ORDERING; 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= (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 &subs) { setLinkViewObject(Base::freecad_dynamic_cast( Application::Instance->getViewProvider(obj)),subs); } void LinkView::setLinkViewObject(ViewProviderDocumentObject *vpd, const std::vector &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::ComplexGeoData::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())).first; it->second.reset(new SubInfo(*this)); } if(subelement[0]) it->second->subElements.insert(subelement); } updateLink(); } void LinkView::setTransform(SoTransform *pcTransform, const Base::Matrix4D &mat) { #if 1 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])); #else // extract scale factor from column vector length double sx = Base::Vector3d(mat[0][0],mat[1][0],mat[2][0]).Sqr(); double sy = Base::Vector3d(mat[0][1],mat[1][1],mat[2][1]).Sqr(); double sz = Base::Vector3d(mat[0][2],mat[1][2],mat[2][2]).Sqr(); bool bx,by,bz; if((bx=fabs(sx-1.0)>=1e-10)) sx = sqrt(sx); else sx = 1.0; if((by=fabs(sy-1.0)>=1e-10)) sy = sqrt(sy); else sy = 1.0; if((bz=fabs(sz-1.0)>=1e-10)) sz = sqrt(sz); else sz = 1.0; // TODO: how to deal with negative scale? pcTransform->scaleFactor.setValue(sx,sy,sz); Base::Matrix4D matRotate; if(bx) { matRotate[0][0] = mat[0][0]/sx; matRotate[1][0] = mat[1][0]/sx; matRotate[2][0] = mat[2][0]/sx; }else{ matRotate[0][0] = mat[0][0]; matRotate[1][0] = mat[1][0]; matRotate[2][0] = mat[2][0]; } if(by) { matRotate[0][1] = mat[0][1]/sy; matRotate[1][1] = mat[1][1]/sy; matRotate[2][1] = mat[2][1]/sy; }else{ matRotate[0][1] = mat[0][1]; matRotate[1][1] = mat[1][1]; matRotate[2][1] = mat[2][1]; } if(bz) { matRotate[0][2] = mat[0][2]/sz; matRotate[1][2] = mat[1][2]/sz; matRotate[2][2] = mat[2][2]/sz; }else{ matRotate[0][2] = mat[0][2]; matRotate[1][2] = mat[1][2]; matRotate[2][2] = mat[2][2]; } Base::Rotation rot(matRotate); pcTransform->rotation.setValue(rot[0],rot[1],rot[2],rot[3]); pcTransform->translation.setValue(mat[0][3],mat[1][3],mat[2][3]); pcTransform->center.setValue(0.0f,0.0f,0.0f); #endif } 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(sizepcSwitch); nodeArray.resize(size); } for(auto &info : nodeArray) pcLinkRoot->addChild(info->pcSwitch); while(nodeArray.size()(new 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 &children, const boost::dynamic_bitset<> &vis, SnapshotType type) { if(children.empty()) { if(nodeArray.size()) { 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 groups; for(size_t i=0;i(new 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;ipcLinked->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 LinkView::getChildren() const { std::vector ret; for(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:0; } std::vector LinkView::getSubNames() const { std::vector ret; for(auto &v : subInfo) { if(v.second->subElements.empty()) { ret.push_back(v.first); continue; } for(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(new SoFCSelectionRoot)); }else if(nodeType<0 && type>=0) { if(isLinked()) replaceLinkedRoot(linkInfo->getSnapshot(type)); else replaceLinkedRoot(0); } 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(auto &info : nodeArray) info->pcRoot->replaceChild(pcLinkedRoot,root); }else if(root) { for(auto &info : nodeArray) info->pcRoot->addChild(root); }else{ for(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(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 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(auto &v : subInfo) { auto &sub = *v.second; Base::Matrix4D mat; App::DocumentObject *sobj = obj->getSubObject( v.first.c_str(), 0, &mat, nodeType==SnapshotContainer); if(!sobj) { sub.unlink(); continue; } sub.link(sobj); linkedRoot->addChild(sub.pcNode); setTransform(sub.pcTransform,mat); if(sub.subElements.size()) { path.truncate(1); appendPath(&path,sub.pcNode); SoSelectionElementAction action(SoSelectionElementAction::Append,true); for(const auto &subelement : sub.subElements) { path.truncate(2); SoDetail *det = 0; 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 path = pp->getPath(); if(nodeArray.size()) { 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(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.size()) { 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()) { appendPath(path,pcLinkRoot); }else{ int idx = App::LinkBaseExtension::getArrayIndex(subname,&subname); if(idx<0 || idx>=(int)nodeArray.size()) return false; auto &info = *nodeArray[idx]; appendPath(path,pcLinkRoot); 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(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.size() && 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(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.size(); } /////////////////////////////////////////////////////////////////////////////////// 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(0),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",NULL}; 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,0); 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 ViewProviderLink::getDisplayModes(void) const { std::vector StrList = inherited::getDisplayModes(); StrList.push_back("Link"); StrList.push_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(); if(ext && ext->getLinkedObjectProperty() && ext->_getElementCountValue()) return BitmapFactory().pixmap("LinkArrayOverlay"); else if(hasSubElement) return BitmapFactory().pixmap("LinkSubElement"); else if(hasSubName) return BitmapFactory().pixmap("LinkSubOverlay"); else return BitmapFactory().pixmap("LinkOverlay"); } void ViewProviderLink::onChanged(const App::Property* prop) { if(prop==&ChildViewProvider) { childVp = freecad_dynamic_cast(ChildViewProvider.getObject().get()); if(childVp) { childVp->setPropertyPrefix("ChildViewProvider."); childVp->Visibility.setValue(getObject()->Visibility.getValue()); childVp->attach(getObject()); childVp->updateView(); childVp->setActiveMode(); if(pcModeSwitch->getNumChildren()>1){ childVpLink = LinkInfo::get(childVp,0); 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->getNameInDocument()) return 0; return pcObject->getExtensionByType(true); } const App::LinkBaseExtension *ViewProviderLink::getLinkExtension() const{ if(!pcObject || !pcObject->getNameInDocument()) return 0; return const_cast(pcObject)->getExtensionByType(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); } 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()) { const auto &v = ext->getScaleVector(); pcTransform->scaleFactor.setValue(v.x,v.y,v.z); linkView->renderDoubleSide(v.x*v.y*v.z < 0); }else if(prop == ext->getPlacementProperty() || prop == ext->getLinkPlacementProperty()) { auto propLinkPlacement = ext->getLinkPlacementProperty(); if(!propLinkPlacement || propLinkPlacement == prop) { const auto &pla = static_cast(prop)->getValue(); ViewProviderGeometryObject::updateTransform(pla, pcTransform); const auto &v = ext->getScaleVector(); pcTransform->scaleFactor.setValue(v.x,v.y,v.z); linkView->renderDoubleSide(v.x*v.y*v.z < 0); } }else if(prop == ext->getLinkedObjectProperty() || prop == ext->getSubElementsProperty()) { if(!prop->testStatus(App::Property::User3)) { std::vector subs; const char *subname = ext->getSubName(); std::string sub; if(subname) sub = subname; const char *subElement = ext->getSubElement(); if(subElement) { hasSubElement = true; subs.push_back(sub+subElement); }else hasSubElement = false; for(const auto &s : ext->getSubElementsValue()) { if(s.empty()) continue; hasSubElement = true; subs.push_back(sub+s); } if(subs.empty() && sub.size()) 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(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.size()) { std::vector 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[i]); if(!element) continue; auto vp = freecad_dynamic_cast( 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;igetSize();++i) { Base::Matrix4D mat; if(propPlacements->getSize()>i) mat = (*propPlacements)[i].toMatrix(); if(propScales && propScales->getSize()>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->getSize()>i) mat = (*propPlacements)[i].toMatrix(); if(propScales && propScales->getSize()>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( 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(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;igetSize();++i) { if(MaterialList.getSize()>i && OverrideMaterialList.getSize()>i && OverrideMaterialList[i]) linkView->setMaterial(i,&MaterialList[i]); else linkView->setMaterial(i,0); } linkView->setMaterial(-1,0); } } 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.size() && (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 0; } std::vector ViewProviderLink::claimChildren(void) const { auto ext = getLinkExtension(); 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 std::vector ret; if(ext) { auto obj = ext->getTrueLinkedObject(true); if(obj) ret.push_back(obj); } return ret; } else if(hasElements(ext) || isGroup(ext)) return ext->getElementListValue(); if(!hasSubName) { auto linked = getLinkedView(true); if(linked) return linked->claimChildren(); } return std::vector(); } 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;isetLink(i,0); 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 &elements) 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(linked); if(linkedVdp) { if(linkedVdp->getObject()==obj || linkedVdp->getObject()==owner) return false; } return linked->canDropObjectEx(obj,owner,subname,elements); } } if(obj->getDocument() != getObject()->getDocument() && !freecad_dynamic_cast(ext->getLinkedObjectValue())) return false; return true; } std::string ViewProviderLink::dropObjectEx(App::DocumentObject* obj, App::DocumentObject *owner, const char *subname, const std::vector &elements) { auto ext = getLinkExtension(); if(isGroup(ext)) { size_t size = ext->getElementListValue().size(); ext->setLink(size,obj); return std::to_string(size)+"."; } if(!ext || !ext->getLinkedObjectProperty() || hasElements(ext)) return std::string(); if(!hasSubName) { auto linked = getLinkedView(false,ext); if(linked) return linked->dropObjectEx(obj,owner,subname,elements); } ext->setLink(-1,owner,subname); return std::string(); } bool ViewProviderLink::canDragAndDropObject(App::DocumentObject* obj) const { auto ext = getLinkExtension(); if(!ext) return true; if(isGroup(ext)) { return ext->getLinkModeValue()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)) { const char *sub = 0; 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] && (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 &) { auto element = freecad_dynamic_cast(getObject()); return !element || element->canDelete(); } 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(linkEdit(ext)) linkView->getLinkedView()->setupContextMenu(menu,receiver,member); if(ext && ext->getColoredElementsProperty()) { bool found = false; for(auto action : menu->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->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { Py::Object feature = static_cast(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.reset(new DraggerContext); dragCtx->initialPlacement = *static_cast(pypla)->getPlacementPtr(); dragCtx->preTransform = *static_cast(pymat)->getMatrixPtr(); dragCtx->bbox = *static_cast(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.reset(new DraggerContext); const auto &pla = ext->getPlacementProperty()? ext->getPlacementValue():ext->getLinkPlacementValue(); dragCtx->preTransform = doc->getEditingTransform(); auto plaMat = pla.toMatrix(); plaMat.inverse(); dragCtx->preTransform *= plaMat; dragCtx->bbox = linkView->getBoundBox(); const auto &offset = Base::Placement( dragCtx->bbox.GetCenter(),Base::Rotation()); dragCtx->initialPlacement = pla * offset; dragCtx->mat = offset.toMatrix(); dragCtx->mat.inverse(); 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); } if(mode==ViewProvider::Transform) { if(!initDraggingPlacement()) return 0; if(useCenterballDragger) pcDragger = CoinPtr(new SoCenterballDragger); else pcDragger = CoinPtr(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 0; } // TODO: the 0x8000 mask here is for caller to disambiguate the intention // here, whether he wants 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; auto doc = Application::Instance->editDocument(); if(!doc) { FC_ERR("no editing document"); return 0; } // 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 0; } auto vpd = freecad_dynamic_cast( Application::Instance->getViewProvider(linked)); if(!vpd) { FC_ERR("no linked viewprovider"); return 0; } // amend the editing transformation with the link transformation doc->setEditingTransform(doc->getEditingTransform()*mat); 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) { SoPickStyle *rootPickStyle = new SoPickStyle(); rootPickStyle->style = SoPickStyle::UNPICKABLE; static_cast( viewer->getSceneGraph())->insertChild(rootPickStyle, 0); if(useCenterballDragger) { auto dragger = static_cast(pcDragger.get()); SoSeparator *group = new SoAnnotation; SoPickStyle *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 = (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{ SoFCCSysDragger* dragger = static_cast(pcDragger.get()); dragger->draggerSize.setValue(0.05f); dragger->setUpAutoScale(viewer->getSoRenderManager()->getCamera()); viewer->setupEditingRoot(pcDragger,&dragCtx->preTransform); TaskCSysDragger *task = new TaskCSysDragger(this, dragger); Gui::Control().showDialog(task); } } } void ViewProviderLink::unsetEditViewer(Gui::View3DInventorViewer* viewer) { SoNode *child = static_cast(viewer->getSceneGraph())->getChild(0); if (child && child->isOfType(SoPickStyle::getClassTypeId())) static_cast(viewer->getSceneGraph())->removeChild(child); pcDragger.reset(); dragCtx.reset(); Gui::Control().closeDialog(); } Base::Placement ViewProviderLink::currentDraggingPlacement() const{ assert(pcDragger); SbVec3f v; SbRotation r; if(useCenterballDragger) { SoCenterballDragger *dragger = static_cast(pcDragger.get()); v = dragger->center.getValue(); r = dragger->rotation.getValue(); }else{ SoFCCSysDragger *dragger = static_cast(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) { SoCenterballDragger *dragger = static_cast(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{ SoFCCSysDragger *dragger = static_cast(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->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { Py::Object feature = static_cast(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(data); me->dragCtx->initialPlacement = me->currentDraggingPlacement(); if(!me->callDraggerProxy("onDragStart",false)) { me->dragCtx->cmdPending = true; me->getDocument()->openCommand("Link Transform"); }else me->dragCtx->cmdPending = false; } void ViewProviderLink::dragFinishCallback(void *data, SoDragger *) { auto me = static_cast(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(data); me->callDraggerProxy("onDragMotion",true); } void ViewProviderLink::updateLinks(ViewProvider *vp) { auto ext = vp->getExtensionByType(true); if(ext && ext->linkInfo) ext->linkInfo->update(); } PyObject *ViewProviderLink::getPyObject() { if (!pyViewObject) pyViewObject = new ViewProviderLinkPy(this); pyViewObject->IncRef(); return pyViewObject; } PyObject *ViewProviderLink::getPyLinkView() { return linkView->getPyObject(); } std::map ViewProviderLink::getElementColors(const char *subname) const { bool isPrefix = true; if(!subname) subname = ""; else { auto len = strlen(subname); isPrefix = !len || subname[len-1]=='.'; } std::map 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.size()) 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.size()) { for(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(1) { 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( Application::Instance->getViewProvider(link)); if(!next) break; for(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(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; } for(auto &sub : subs) { if(++i >= size) break; if((isPrefix && (boost::starts_with(sub.first,subname) || boost::starts_with(sub.second,subname))) || (!isPrefix && (sub.first==subname || sub.second==subname))) 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 ret; for(auto &v : colors) { const char *pos = 0; auto sobj = getObject()->resolve(v.first.c_str(),0,0,&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(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 &colorMap) { auto ext = getLinkExtension(); if(!ext || ! ext->getColoredElementsProperty()) return; std::vector subs; std::vector colors; App::Color faceColor; bool hasFaceColor = false; for(auto &v : colorMap) { if(!hasFaceColor && v.first == "Face") { hasFaceColor = true; faceColor = v.second; }else{ subs.push_back(v.first); colors.push_back(v.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 > colorMap; std::set hideList; auto colors = getElementColors(); colors.erase("Face"); for(auto &v : colors) { const char *subname = v.first.c_str(); const char *element = 0; auto sobj = getObject()->resolve(subname,0,0,&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=0; path.truncate(0); if(getDetailPath(v.first.c_str(), &path, false, det)) action.apply(&path); delete det; } action.setType(SoSelectionElementAction::Hide); for(auto &sub : hideList) { SoDetail *det=0; path.truncate(0); if(sub.size() && 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 = 0; } } 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 = 0; } if(pcObject && pcObject->canLinkProperties()) { auto linked = getLinkedViewProvider(0,true); if(linked && linked!=this) prop = linked->getPropertyByName(name); } return prop; } void ViewProviderLink::getPropertyMap(std::map &Map) const { inherited::getPropertyMap(Map); if(!childVp) return; std::map childMap; childVp->getPropertyMap(childMap); for(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 &List) const { std::map Map; getPropertyMap(Map); List.reserve(List.size()+Map.size()); for(auto &v:Map) List.push_back(v.second); } ViewProviderDocumentObject *ViewProviderLink::getLinkedViewProvider( std::string *subname, bool recursive) const { auto self = const_cast(this); auto ext = getLinkExtension(); if(!ext) return self; App::DocumentObject *linked = 0; 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( Application::Instance->getViewProvider(linked)); if(res) return res; return self; } //////////////////////////////////////////////////////////////////////////////////////// namespace Gui { PROPERTY_SOURCE_TEMPLATE(Gui::ViewProviderLinkPython, Gui::ViewProviderLink) template class GuiExport ViewProviderPythonFeatureT; }