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