diff --git a/src/App/Application.cpp b/src/App/Application.cpp index fb1c12c4b1..4dcad653a9 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -112,6 +112,7 @@ #include "Transactions.h" #include #include +#include "Link.h" // If you stumble here, run the target "BuildExtractRevision" on Windows systems // or the Python script "SubWCRev.py" on Linux based systems which builds @@ -1835,6 +1836,10 @@ void Application::initTypes(void) App ::GeoFeatureGroupExtensionPython::init(); App ::OriginGroupExtension ::init(); App ::OriginGroupExtensionPython ::init(); + App ::LinkBaseExtension ::init(); + App ::LinkBaseExtensionPython ::init(); + App ::LinkExtension ::init(); + App ::LinkExtensionPython ::init(); // Document classes App ::TransactionalObject ::init(); @@ -1862,6 +1867,12 @@ void Application::initTypes(void) App ::Line ::init(); App ::Part ::init(); App ::Origin ::init(); + App ::Link ::init(); + App ::LinkPython ::init(); + App ::LinkElement ::init(); + App ::LinkElementPython ::init(); + App ::LinkGroup ::init(); + App ::LinkGroupPython ::init(); // Expression classes App ::Expression ::init(); diff --git a/src/App/CMakeLists.txt b/src/App/CMakeLists.txt index 59af9e6d66..0b97d641ca 100644 --- a/src/App/CMakeLists.txt +++ b/src/App/CMakeLists.txt @@ -90,6 +90,7 @@ generate_from_xml(ExtensionPy) generate_from_xml(ExtensionContainerPy) generate_from_xml(DocumentObjectExtensionPy) generate_from_xml(GroupExtensionPy) +generate_from_xml(LinkBaseExtensionPy) generate_from_xml(DocumentObjectGroupPy) generate_from_xml(GeoFeaturePy) generate_from_xml(GeoFeatureGroupExtensionPy) @@ -108,6 +109,7 @@ SET(FreeCADApp_XML_SRCS ExtensionContainerPy.xml DocumentObjectExtensionPy.xml GroupExtensionPy.xml + LinkBaseExtensionPy.xml DocumentObjectGroupPy.xml DocumentObjectPy.xml GeoFeaturePy.xml @@ -165,6 +167,8 @@ SET(Document_CPP_SRCS MaterialObject.cpp MergeDocuments.cpp TextDocument.cpp + Link.cpp + LinkBaseExtensionPyImp.cpp ) SET(Document_HPP_SRCS @@ -203,6 +207,7 @@ SET(Document_HPP_SRCS MaterialObject.h MergeDocuments.h TextDocument.h + Link.h ) SET(Document_SRCS ${Document_CPP_SRCS} diff --git a/src/App/Link.cpp b/src/App/Link.cpp new file mode 100644 index 0000000000..d89870c133 --- /dev/null +++ b/src/App/Link.cpp @@ -0,0 +1,1390 @@ +/**************************************************************************** + * 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_ +#endif + +#include +#include +#include "Application.h" +#include "Document.h" +#include "GroupExtension.h" +#include "Link.h" +#include "LinkBaseExtensionPy.h" +#include +#include "ComplexGeoData.h" +#include "ComplexGeoDataPy.h" + +FC_LOG_LEVEL_INIT("App::Link", true,true) + +using namespace App; +using namespace Base; + +EXTENSION_PROPERTY_SOURCE(App::LinkBaseExtension, App::DocumentObjectExtension) + +LinkBaseExtension::LinkBaseExtension(void) + :enableLabelCache(false),myOwner(0) +{ + initExtensionType(LinkBaseExtension::getExtensionClassTypeId()); + EXTENSION_ADD_PROPERTY_TYPE(_LinkRecomputed, (false), " Link", + PropertyType(Prop_Hidden|Prop_Transient),0); + EXTENSION_ADD_PROPERTY_TYPE(_ChildCache, (), " Link", + PropertyType(Prop_Hidden|Prop_Transient|Prop_ReadOnly),0); + _ChildCache.setScope(LinkScope::Global); + props.resize(PropMax,0); +} + +LinkBaseExtension::~LinkBaseExtension() +{ +} + +PyObject* LinkBaseExtension::getExtensionPyObject(void) { + if (ExtensionPythonObject.is(Py::_None())){ + // ref counter is set to 1 + ExtensionPythonObject = Py::Object(new LinkBaseExtensionPy(this),true); + } + return Py::new_reference_to(ExtensionPythonObject); +} + +const std::vector &LinkBaseExtension::getPropertyInfo() const { + static std::vector PropsInfo; + if(PropsInfo.empty()) { + BOOST_PP_SEQ_FOR_EACH(LINK_PROP_INFO,PropsInfo,LINK_PARAMS); + } + return PropsInfo; +} + +const LinkBaseExtension::PropInfoMap &LinkBaseExtension::getPropertyInfoMap() const { + static PropInfoMap PropsMap; + if(PropsMap.empty()) { + const auto &infos = getPropertyInfo(); + for(const auto &info : infos) + PropsMap[info.name] = info; + } + return PropsMap; +} + +Property *LinkBaseExtension::getProperty(int idx) { + if(idx>=0 && idx<(int)props.size()) + return props[idx]; + return 0; +} + +Property *LinkBaseExtension::getProperty(const char *name) { + const auto &info = getPropertyInfoMap(); + auto it = info.find(name); + if(it == info.end()) + return 0; + return getProperty(it->second.index); +} + +void LinkBaseExtension::setProperty(int idx, Property *prop) { + const auto &infos = getPropertyInfo(); + if(idx<0 || idx>=(int)infos.size()) + LINK_THROW(Base::RuntimeError,"App::LinkBaseExtension: property index out of range"); + + if(props[idx]) { + props[idx]->setStatus(Property::LockDynamic,false); + props[idx] = 0; + } + if(!prop) + return; + if(!prop->isDerivedFrom(infos[idx].type)) { + std::ostringstream str; + str << "App::LinkBaseExtension: expected property type '" << + infos[idx].type.getName() << "', instead of '" << + prop->getClassTypeId().getName() << "'"; + LINK_THROW(Base::TypeError,str.str().c_str()); + } + + props[idx] = prop; + props[idx]->setStatus(Property::LockDynamic,true); + + switch(idx) { + case PropLinkMode: { + static const char *linkModeEnums[] = {"None","Auto Delete","Auto Link","Auto Unlink",0}; + auto propLinkMode = freecad_dynamic_cast(prop); + if(!propLinkMode->getEnums()) + propLinkMode->setEnums(linkModeEnums); + break; + } case PropLinkTransform: + case PropLinkPlacement: + case PropPlacement: + if(getLinkTransformProperty() && + getLinkPlacementProperty() && + getPlacementProperty()) + { + bool transform = getLinkTransformValue(); + getPlacementProperty()->setStatus(Property::Hidden,transform); + getLinkPlacementProperty()->setStatus(Property::Hidden,!transform); + } + break; + case PropElementList: + getElementListProperty()->setStatus(Property::Hidden,true); + // fall through + case PropLinkedObject: + // Make ElementList as read-only if we are not a group (i.e. having + // LinkedObject property), because it is for holding array elements. + if(getElementListProperty()) + getElementListProperty()->setStatus( + Property::Immutable,getLinkedObjectProperty()!=0); + break; + case PropVisibilityList: + getVisibilityListProperty()->setStatus(Property::Immutable,true); + getVisibilityListProperty()->setStatus(Property::Hidden,true); + break; + } + + if(FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_TRACE)) { + const char *propName; + if(!prop) + propName = ""; + else if(prop->getContainer()) + propName = prop->getName(); + else + propName = extensionGetPropertyName(prop); + if(!propName) + propName = "?"; + FC_TRACE("set property " << infos[idx].name << ": " << propName); + } +} + +App::DocumentObjectExecReturn *LinkBaseExtension::extensionExecute(void) { + // The actual value of LinkRecompouted is not important, just to notify view + // provider that the link (in fact, its dependents, i.e. linked ones) have + // recomputed. + _LinkRecomputed.touch(); + + if(getLinkedObjectProperty() && !getTrueLinkedObject(true)) + return new App::DocumentObjectExecReturn("Link broken"); + return inherited::extensionExecute(); +} + +short LinkBaseExtension::extensionMustExecute(void) { + auto link = getLink(); + if(!link) return 0; + return link->mustExecute(); +} + +App::GroupExtension *LinkBaseExtension::linkedPlainGroup() const { + auto subs = getSubElementsProperty(); + if(subs && subs->getSize()) + return 0; + auto linked = getTrueLinkedObject(false); + if(!linked) + return 0; + return linked->getExtensionByType(true,false); +} + +App::PropertyLinkList *LinkBaseExtension::_getElementListProperty() const { + auto group = linkedPlainGroup(); + if(group) + return &group->Group; + return const_cast(getElementListProperty()); +} + +const std::vector &LinkBaseExtension::_getElementListValue() const { + if(_ChildCache.getSize()) + return _ChildCache.getValues(); + if(getElementListProperty()) + return getElementListProperty()->getValues(); + static const std::vector empty; + return empty; +} + +App::PropertyBool *LinkBaseExtension::_getShowElementProperty() const { + auto prop = getShowElementProperty(); + if(prop && !linkedPlainGroup()) + return const_cast(prop); + return 0; +} + +bool LinkBaseExtension::_getShowElementValue() const { + auto prop = _getShowElementProperty(); + if(prop) + return prop->getValue(); + return true; +} + +App::PropertyInteger *LinkBaseExtension::_getElementCountProperty() const { + auto prop = getElementCountProperty(); + if(prop && !linkedPlainGroup()) + return const_cast(prop); + return 0; +} + +int LinkBaseExtension::_getElementCountValue() const { + auto prop = _getElementCountProperty(); + if(prop) + return prop->getValue(); + return 0; +} + +bool LinkBaseExtension::extensionHasChildElement() const { + if(_getElementListProperty() || _getElementCountValue()) + return true; + DocumentObject *linked = getTrueLinkedObject(false); + if(linked) { + if(linked->hasChildElement()) + return true; + } + return false; +} + +int LinkBaseExtension::extensionSetElementVisible(const char *element, bool visible) { + int index = _getShowElementValue()?getElementIndex(element):getArrayIndex(element); + if(index>=0) { + auto propElementVis = getVisibilityListProperty(); + if(!propElementVis || !element || !element[0]) + return -1; + if(propElementVis->getSize()<=index) { + if(visible) return 1; + propElementVis->setSize(index+1, true); + } + propElementVis->setStatus(Property::User3,true); + propElementVis->set1Value(index,visible); + propElementVis->setStatus(Property::User3,false); + const auto &elements = _getElementListValue(); + if(index<(int)elements.size()) { + if(!visible) + myHiddenElements.insert(elements[index]); + else + myHiddenElements.erase(elements[index]); + } + return 1; + } + DocumentObject *linked = getTrueLinkedObject(false); + if(linked) + return linked->setElementVisible(element,visible); + return -1; +} + +int LinkBaseExtension::extensionIsElementVisible(const char *element) { + int index = _getShowElementValue()?getElementIndex(element):getArrayIndex(element); + if(index>=0) { + auto propElementVis = getVisibilityListProperty(); + if(propElementVis) { + if(propElementVis->getSize()<=index || propElementVis->getValues()[index]) + return 1; + return 0; + } + return -1; + } + DocumentObject *linked = getTrueLinkedObject(false); + if(linked) + return linked->isElementVisible(element); + return -1; +} + +const DocumentObject *LinkBaseExtension::getContainer() const { + auto ext = getExtendedContainer(); + if(!ext || !ext->isDerivedFrom(DocumentObject::getClassTypeId())) + LINK_THROW(Base::RuntimeError,"Link: container not derived from document object"); + return static_cast(ext); +} + +DocumentObject *LinkBaseExtension::getContainer(){ + auto ext = getExtendedContainer(); + if(!ext || !ext->isDerivedFrom(DocumentObject::getClassTypeId())) + LINK_THROW(Base::RuntimeError,"Link: container not derived from document object"); + return static_cast(ext); +} + +DocumentObject *LinkBaseExtension::getLink(int depth) const{ + GetApplication().checkLinkDepth(depth,false); + if(getLinkedObjectProperty()) + return getLinkedObjectValue(); + return 0; +} + +int LinkBaseExtension::getArrayIndex(const char *subname, const char **psubname) { + if(!subname || Data::ComplexGeoData::isMappedElement(subname)) + return -1; + const char *dot = strchr(subname,'.'); + if(!dot) dot= subname+strlen(subname); + if(dot == subname) return -1; + int idx = 0; + for(const char *c=subname;c!=dot;++c) { + if(!isdigit(*c)) return -1; + idx = idx*10 + *c -'0'; + } + if(psubname) { + if(*dot) + *psubname = dot+1; + else + *psubname = dot; + } + return idx; +} + +int LinkBaseExtension::getElementIndex(const char *subname, const char **psubname) const { + if(!subname || Data::ComplexGeoData::isMappedElement(subname)) + return -1; + int idx = -1; + const char *dot = strchr(subname,'.'); + if(!dot) dot= subname+strlen(subname); + + if(isdigit(subname[0])) { + // If the name start with digits, treat as index reference + idx = getArrayIndex(subname,0); + if(idx<0) return -1; + if(_getElementCountProperty()) { + if(idx>=_getElementCountValue()) + return -1; + }else if(idx>=(int)_getElementListValue().size()) + return -1; + }else if(!_getShowElementValue() && _getElementCountValue()) { + // If elements are collapsed, we check first for LinkElement naming + // pattern, which is the owner object name + "_i" + index + const char *name = subname[0]=='$'?subname+1:subname; + auto owner = getContainer(); + if(owner && owner->getNameInDocument()) { + std::string ownerName(owner->getNameInDocument()); + ownerName += '_'; + if(boost::algorithm::starts_with(name,ownerName.c_str())) { + for(const char *txt=dot-1;txt>=name+ownerName.size();--txt) { + if(*txt == 'i') { + idx = getArrayIndex(txt+1,0); + if(idx<0 || idx>=_getElementCountValue()) + idx = -1; + break; + } + if(!isdigit(*txt)) + break; + } + } + } + if(idx<0) { + // Then check for the actual linked object's name or label, and + // redirect that reference to the first array element + auto linked = getTrueLinkedObject(false); + if(!linked || !linked->getNameInDocument()) return -1; + std::string sub(subname,dot-subname); + if(subname[0]=='$') { + if(strcmp(sub.c_str()+1,linked->Label.getValue())==0) + idx = 0; + }else if(sub==linked->getNameInDocument()) + idx = 0; + if(idx<0) { + // Lastly, try to get sub object directly from the linked object + auto sobj = linked->getSubObject(sub.c_str()); + if(!sobj) + return -1; + if(psubname) + *psubname = subname; + return 0; + } + } + }else if(subname[0]!='$') { + // Try search by element objects' name + std::string name(subname,dot); + if(_ChildCache.getSize()) { + auto obj=_ChildCache.find(name,&idx); + if(obj) { + auto group = obj->getExtensionByType(true,false); + if(group) { + int nidx = getElementIndex(dot+1,psubname); + if(nidx >= 0) + return nidx; + } + } + } else if(getElementListProperty()) + getElementListProperty()->find(name.c_str(),&idx); + if(idx<0) + return -1; + }else { + // Try search by label if the reference name start with '$' + ++subname; + std::string name(subname,dot-subname); + const auto &elements = _getElementListValue(); + if(enableLabelCache) { + if(myLabelCache.empty()) + cacheChildLabel(1); + auto it = myLabelCache.find(name); + if(it == myLabelCache.end()) + return -1; + idx = it->second; + }else{ + idx = 0; + for(auto element : elements) { + if(element->Label.getStrValue() == name) + break; + ++idx; + } + } + if(idx<0 || idx>=(int)elements.size()) + return -1; + auto obj = elements[idx]; + if(obj && _ChildCache.getSize()) { + auto group = obj->getExtensionByType(true,false); + if(group) { + int nidx = getElementIndex(dot+1,psubname); + if(nidx >= 0) + return nidx; + } + } + } + if(psubname) + *psubname = dot[0]?dot+1:dot; + return idx; +} + +void LinkBaseExtension::elementNameFromIndex(int idx, std::ostream &ss) const { + const auto &elements = _getElementListValue(); + if(idx < 0 || idx >= (int)elements.size()) + return; + + auto obj = elements[idx]; + if(_ChildCache.getSize()) { + auto group = GroupExtension::getGroupOfObject(obj); + if(group && _ChildCache.find(group->getNameInDocument(),&idx)) + elementNameFromIndex(idx,ss); + } + ss << obj->getNameInDocument() << '.'; +} + +Base::Vector3d LinkBaseExtension::getScaleVector() const { + if(getScaleVectorProperty()) + return getScaleVectorValue(); + double s = getScaleValue(); + return Base::Vector3d(s,s,s); +} + +Base::Matrix4D LinkBaseExtension::getTransform(bool transform) const { + Base::Matrix4D mat; + if(transform) { + if(getLinkPlacementProperty()) + mat = getLinkPlacementValue().toMatrix(); + else if(getPlacementProperty()) + mat = getPlacementValue().toMatrix(); + } + if(getScaleProperty() || getScaleVectorProperty()) { + Base::Matrix4D s; + s.scale(getScaleVector()); + mat *= s; + } + return mat; +} + +bool LinkBaseExtension::extensionGetSubObjects(std::vector &ret, int reason) const { + if(!getLinkedObjectProperty() && getElementListProperty()) { + for(auto obj : getElementListProperty()->getValues()) { + if(obj && obj->getNameInDocument()) { + std::string name(obj->getNameInDocument()); + name+='.'; + ret.push_back(name); + } + } + return true; + } + if(mySubElement.empty() && getSubElementsValue().empty()) { + DocumentObject *linked = getTrueLinkedObject(true); + if(linked) { + if(!_getElementCountValue()) + ret = linked->getSubObjects(reason); + else{ + char index[30]; + for(int i=0,count=_getElementCountValue();i(obj); + if(!_getElementListProperty() && !_getElementCountValue() && pyObj) { + Base::Matrix4D matNext; + if(mat) matNext = *mat; + auto linked = getTrueLinkedObject(false,mat?&matNext:0,depth); + if(linked && linked!=obj) { + if(mat) *mat = matNext; + linked->getSubObject(mySubElement.c_str(),pyObj,mat,false,depth+1); + checkGeoElementMap(obj,linked,pyObj,0); + } + } + return true; + } + + DocumentObject *element = 0; + bool isElement = false; + int idx = getElementIndex(subname,&subname); + if(idx>=0) { + const auto &elements = _getElementListValue(); + if(elements.size()) { + if(idx>=(int)elements.size() || !elements[idx] || !elements[idx]->getNameInDocument()) + return true; + if(!subname || !subname[0]) + subname = mySubElement.c_str(); + ret = elements[idx]->getSubObject(subname,pyObj,mat,true,depth+1); + // do not resolve the link if this element is the last referenced object + if(!subname || Data::ComplexGeoData::isMappedElement(subname) || !strchr(subname,'.')) + ret = elements[idx]; + return true; + } + + int elementCount = _getElementCountValue(); + if(idx>=elementCount) + return true; + isElement = true; + if(mat) { + auto placementList = getPlacementListProperty(); + if(placementList && placementList->getSize()>idx) + *mat *= (*placementList)[idx].toMatrix(); + auto scaleList = getScaleListProperty(); + if(scaleList && scaleList->getSize()>idx) { + Base::Matrix4D s; + s.scale((*scaleList)[idx]); + *mat *= s; + } + } + } + + auto linked = getTrueLinkedObject(false,mat,depth); + if(!linked || linked==obj) + return true; + + Base::Matrix4D matNext; + if(mat) matNext = *mat; + if(!subname || !subname[0]) + subname = mySubElement.c_str(); + ret = linked->getSubObject(subname,pyObj,mat?&matNext:0,false,depth+1); + std::string postfix; + if(ret) { + // do not resolve the link if we are the last referenced object + if(subname && !Data::ComplexGeoData::isMappedElement(subname) && strchr(subname,'.')) { + if(mat) + *mat = matNext; + }else if(element) + ret = element; + else if(!isElement) { + ret = const_cast(obj); + } else { + if(idx) { + postfix = Data::ComplexGeoData::indexPostfix(); + postfix += std::to_string(idx); + } + if(mat) + *mat = matNext; + } + } + checkGeoElementMap(obj,linked,pyObj,postfix.size()?postfix.c_str():0); + return true; +} + +void LinkBaseExtension::checkGeoElementMap(const App::DocumentObject *obj, + const App::DocumentObject *linked, PyObject **pyObj, const char *postfix) const +{ + if(!pyObj || !*pyObj || (!postfix && obj->getDocument()==linked->getDocument()) || + !PyObject_TypeCheck(*pyObj, &Data::ComplexGeoDataPy::Type)) + return; + + // auto geoData = static_cast(*pyObj)->getComplexGeoDataPtr(); + // geoData->reTagElementMap(obj->getID(),obj->getDocument()->Hasher,postfix); +} + +void LinkBaseExtension::onExtendedUnsetupObject() { + if(!getElementListProperty()) + return; + auto objs = getElementListValue(); + getElementListProperty()->setValue(); + for(auto obj : objs) + detachElement(obj); +} + +DocumentObject *LinkBaseExtension::getTrueLinkedObject( + bool recurse, Base::Matrix4D *mat, int depth, bool noElement) const +{ + if(noElement && extensionIsDerivedFrom(LinkElement::getExtensionClassTypeId()) + && !static_cast(this)->canDelete()) + { + return 0; + } + + auto ret = getLink(depth); + if(!ret) return 0; + bool transform = linkTransform(); + const char *subname = getSubName(); + if(subname) { + ret = ret->getSubObject(subname,0,mat,transform,depth+1); + transform = false; + } + if(ret && recurse) + ret = ret->getLinkedObject(recurse,mat,transform,depth+1); + if(ret && !ret->getNameInDocument()) + return 0; + return ret; +} + +bool LinkBaseExtension::extensionGetLinkedObject(DocumentObject *&ret, + bool recurse, Base::Matrix4D *mat, bool transform, int depth) const +{ + if(mat) + *mat *= getTransform(transform); + ret = 0; + if(!_getElementCountValue()) + ret = getTrueLinkedObject(recurse,mat,depth); + if(!ret) + ret = const_cast(getContainer()); + // always return true to indicate we've handled getLinkObject() call + return true; +} + +void LinkBaseExtension::extensionOnChanged(const Property *prop) { + auto parent = getContainer(); + if(parent && !parent->isRestoring() && prop && !prop->testStatus(Property::User3)) + update(parent,prop); + inherited::extensionOnChanged(prop); +} + +void LinkBaseExtension::parseSubName() const { + auto xlink = freecad_dynamic_cast(getLinkedObjectProperty()); + const char* subname = xlink?xlink->getSubName():0; + mySubName.clear(); + mySubElement.clear(); + if(!subname || !subname[0]) + return; + auto element = Data::ComplexGeoData::findElementName(subname); + mySubName = std::string(subname,element-subname); + mySubElement = element; +} + +void LinkBaseExtension::slotChangedPlainGroup(const App::DocumentObject &obj, const App::Property &prop) { + auto group = obj.getExtensionByType(true,false); + if(group && &prop == &group->Group) + updateGroup(); +} + +void LinkBaseExtension::updateGroup() { + std::vector groups; + std::unordered_set groupSet; + auto group = linkedPlainGroup(); + if(group) { + groups.push_back(group); + groupSet.insert(group->getExtendedObject()); + }else{ + for(auto o : getElementListProperty()->getValues()) { + if(!o || !o->getNameInDocument()) + continue; + auto ext = o->getExtensionByType(true,false); + if(ext) { + groups.push_back(ext); + groupSet.insert(o); + } + } + } + std::vector children; + if(groups.size()) { + children = getElementListValue(); + std::set childSet(children.begin(),children.end()); + for(auto ext : groups) { + auto group = ext->getExtendedObject(); + auto &conn = plainGroupConns[group]; + if(!conn.connected()) { + FC_LOG("new group connection " << getExtendedObject()->getFullName() + << " -> " << group->getFullName()); + conn = group->signalChanged.connect( + boost::bind(&LinkBaseExtension::slotChangedPlainGroup,this,_1,_2)); + } + std::size_t count = children.size(); + ext->getAllChildren(children,childSet); + for(;countgetExtensionByType(true,false)) + continue; + groupSet.insert(child); + auto &conn = plainGroupConns[child]; + if(!conn.connected()) { + FC_LOG("new group connection " << getExtendedObject()->getFullName() + << " -> " << child->getFullName()); + conn = child->signalChanged.connect( + boost::bind(&LinkBaseExtension::slotChangedPlainGroup,this,_1,_2)); + } + } + } + } + for(auto it=plainGroupConns.begin();it!=plainGroupConns.end();) { + if(!groupSet.count(it->first)) + it = plainGroupConns.erase(it); + else + ++it; + } + if(children != _ChildCache.getValues()) + _ChildCache.setValue(children); +} + +void LinkBaseExtension::update(App::DocumentObject *parent, const Property *prop) { + if(!prop) return; + + if(prop == getLinkPlacementProperty() || prop == getPlacementProperty()) { + auto src = getLinkPlacementProperty(); + auto dst = getPlacementProperty(); + if(src!=prop) std::swap(src,dst); + if(src && dst) { + dst->setStatus(Property::User3,true); + dst->setValue(src->getValue()); + dst->setStatus(Property::User3,false); + } + }else if(prop == _getShowElementProperty()) { + if(_getShowElementValue()) + update(parent,_getElementCountProperty()); + else { + auto objs = getElementListValue(); + + // preseve element properties in ourself + std::vector placements; + placements.reserve(objs.size()); + std::vector scales; + scales.reserve(objs.size()); + for(size_t i=0;i(objs[i]); + if(element) { + placements.push_back(element->Placement.getValue()); + scales.push_back(element->getScaleVector()); + }else{ + placements.emplace_back(); + scales.emplace_back(1,1,1); + } + } + // touch the property again to make sure view provider has been + // signaled before clearing the elements + getShowElementProperty()->setStatus(App::Property::User3,true); + getShowElementProperty()->touch(); + getShowElementProperty()->setStatus(App::Property::User3,false); + + getElementListProperty()->setValues(std::vector()); + + if(getPlacementListProperty()) { + getPlacementListProperty()->setStatus(Property::User3,getScaleListProperty()!=0); + getPlacementListProperty()->setValue(placements); + getPlacementListProperty()->setStatus(Property::User3,false); + } + if(getScaleListProperty()) + getScaleListProperty()->setValue(scales); + + for(auto obj : objs) { + if(obj && obj->getNameInDocument()) + obj->getDocument()->removeObject(obj->getNameInDocument()); + } + } + }else if(prop == _getElementCountProperty()) { + size_t elementCount = getElementCountValue()<0?0:(size_t)getElementCountValue(); + + auto propVis = getVisibilityListProperty(); + if(propVis) { + if(propVis->getSize()>(int)elementCount) + propVis->setSize(getElementCountValue(),true); + } + + if(!_getShowElementValue()) { + if(getScaleListProperty()) { + auto scales = getScaleListValue(); + scales.resize(elementCount,Base::Vector3d(1,1,1)); + getScaleListProperty()->setStatus(Property::User3,true); + getScaleListProperty()->setValue(scales); + getScaleListProperty()->setStatus(Property::User3,false); + } + if(getPlacementListProperty()) { + auto placements = getPlacementListValue(); + if(placements.size()setStatus(Property::User3,true); + getPlacementListProperty()->setValue(placements); + getPlacementListProperty()->setStatus(Property::User3,false); + } + }else if(getElementListProperty()) { + auto objs = getElementListValue(); + if(elementCount>objs.size()) { + std::string name = parent->getNameInDocument(); + auto doc = parent->getDocument(); + name += "_i"; + name = doc->getUniqueObjectName(name.c_str()); + if(name[name.size()-1] != 'i') + name += "_i"; + auto offset = name.size(); + auto placementProp = getPlacementListProperty(); + auto scaleProp = getScaleListProperty(); + const auto &vis = getVisibilityListValue(); + auto proxy = freecad_dynamic_cast(parent->getPropertyByName("Proxy")); + Py::Callable method; + Py::Tuple args(3); + if(proxy) { + Py::Object proxyValue = proxy->getValue(); + const char *fname = "onCreateLinkElement"; + if (proxyValue.hasAttr(fname)) { + method = proxyValue.getAttr(fname); + args.setItem(0,Py::Object(parent->getPyObject(),true)); + } + } + + auto owner = getContainer(); + long ownerID = owner?owner->getID():0; + + for(size_t i=objs.size();i(doc->getObject(name.c_str())); + if(obj && (!obj->myOwner || obj->myOwner==ownerID)) + obj->Visibility.setValue(false); + else { + if(!method.isNone()) { + obj = new LinkElementPython; + args.setItem(1,Py::Object(obj->getPyObject(),true)); + args.setItem(2,Py::Int((int)i)); + method.apply(args); + } else + obj = new LinkElement; + parent->getDocument()->addObject(obj,name.c_str()); + } + + if(vis.size()>i && !vis[i]) + myHiddenElements.insert(obj); + + if(placementProp && placementProp->getSize()>(int)i) + obj->Placement.setValue(placementProp->getValues()[i]); + else{ + Base::Placement pla(Base::Vector3d(i%10,(i/10)%10,i/100),Base::Rotation()); + obj->Placement.setValue(pla); + } + if(scaleProp && scaleProp->getSize()>(int)i) + obj->Scale.setValue(scaleProp->getValues()[i].x); + else + obj->Scale.setValue(1); + objs.push_back(obj); + } + if(getPlacementListProperty()) + getPlacementListProperty()->setSize(0); + if(getScaleListProperty()) + getScaleListProperty()->setSize(0); + + getElementListProperty()->setValue(objs); + + }else if(elementCount tmpObjs; + auto owner = getContainer(); + long ownerID = owner?owner->getID():0; + while(objs.size()>elementCount) { + auto element = freecad_dynamic_cast(objs.back()); + if(element && element->myOwner==ownerID) + tmpObjs.push_back(objs.back()); + objs.pop_back(); + } + getElementListProperty()->setValue(objs); + for(auto obj : tmpObjs) { + if(obj && obj->getNameInDocument()) + obj->getDocument()->removeObject(obj->getNameInDocument()); + } + } + } + }else if(prop == getVisibilityListProperty()) { + if(_getShowElementValue()) { + const auto &elements = _getElementListValue(); + const auto &vis = getVisibilityListValue(); + myHiddenElements.clear(); + for(size_t i=0;i=elements.size()) + break; + if(!vis[i]) + myHiddenElements.insert(elements[i]); + } + } + }else if(prop == getElementListProperty() || prop == &_ChildCache) { + + if(prop == getElementListProperty()) { + _ChildCache.setStatus(Property::User3,true); + updateGroup(); + _ChildCache.setStatus(Property::User3,false); + } + + const auto &elements = _getElementListValue(); + + if(enableLabelCache) + myLabelCache.clear(); + + // Element list changed, we need to sychrnoize VisibilityList. + if(_getShowElementValue() && getVisibilityListProperty()) { + if(parent->getDocument()->isPerformingTransaction()) { + update(parent,getVisibilityListProperty()); + }else{ + boost::dynamic_bitset<> vis; + vis.resize(elements.size(),true); + std::unordered_set hiddenElements; + for(size_t i=0;isetStatus(Property::User3,true); + propVis->setValue(vis); + propVis->setStatus(Property::User3,false); + } + } + } + syncElementList(); + if(_getShowElementValue() + && _getElementCountProperty() + && getElementListProperty() + && getElementCountValue()!=getElementListProperty()->getSize()) + { + getElementCountProperty()->setValue( + getElementListProperty()->getSize()); + } + }else if(prop == getLinkedObjectProperty()) { + auto group = linkedPlainGroup(); + if(getShowElementProperty()) + getShowElementProperty()->setStatus(Property::Hidden, !!group); + if(getElementCountProperty()) + getElementCountProperty()->setStatus(Property::Hidden, !!group); + if(group) + updateGroup(); + else if(_ChildCache.getSize()) + _ChildCache.setValue(); + parseSubName(); + syncElementList(); + }else if(prop == getSubElementsProperty()) { + syncElementList(); + }else if(prop == getLinkTransformProperty()) { + auto linkPlacement = getLinkPlacementProperty(); + auto placement = getPlacementProperty(); + if(linkPlacement && placement) { + bool transform = getLinkTransformValue(); + placement->setStatus(Property::Hidden,transform); + linkPlacement->setStatus(Property::Hidden,!transform); + } + syncElementList(); + } +} + +void LinkBaseExtension::cacheChildLabel(int enable) const { + enableLabelCache = enable?true:false; + myLabelCache.clear(); + if(enable<=0) + return; + + int idx = 0; + for(auto child : _getElementListValue()) { + if(child && child->getNameInDocument()) + myLabelCache[child->Label.getStrValue()] = idx; + ++idx; + } +} + +bool LinkBaseExtension::linkTransform() const { + if(!getLinkTransformProperty() && + !getLinkPlacementProperty() && + !getPlacementProperty()) + return true; + return getLinkTransformValue(); +} + +void LinkBaseExtension::syncElementList() { + const auto &subElements = getSubElementsValue(); + auto sub = getSubElementsProperty(); + auto transform = getLinkTransformProperty(); + auto link = getLinkedObjectProperty(); + auto xlink = freecad_dynamic_cast(link); + std::string subname; + if(xlink) + subname = xlink->getSubName(); + + auto owner = getContainer(); + auto ownerID = owner?owner->getID():0; + auto elements = getElementListValue(); + for(size_t i=0;i(elements[i]); + if(!element || (element->myOwner && element->myOwner!=ownerID)) + continue; + + element->myOwner = ownerID; + + element->SubElements.setStatus(Property::Hidden,sub!=0); + element->SubElements.setStatus(Property::Immutable,sub!=0); + + element->LinkTransform.setStatus(Property::Hidden,transform!=0); + element->LinkTransform.setStatus(Property::Immutable,transform!=0); + if(transform && element->LinkTransform.getValue()!=transform->getValue()) + element->LinkTransform.setValue(transform->getValue()); + + element->LinkedObject.setStatus(Property::Hidden,link!=0); + element->LinkedObject.setStatus(Property::Immutable,link!=0); + if(link) { + if(element->LinkedObject.getValue()!=link->getValue() || + subname != element->LinkedObject.getSubName() || + subElements != element->SubElements.getValue()) + { + element->setLink(-1,link->getValue(),subname.c_str(),subElements); + } + } + } +} + +void LinkBaseExtension::onExtendedDocumentRestored() { + inherited::onExtendedDocumentRestored(); + auto parent = getContainer(); + myHiddenElements.clear(); + if(parent) { + update(parent,getVisibilityListProperty()); + update(parent,getLinkedObjectProperty()); + update(parent,getElementListProperty()); + } +} + +void LinkBaseExtension::setLink(int index, DocumentObject *obj, + const char *subname, const std::vector &subElements) +{ + auto parent = getContainer(); + if(!parent) + LINK_THROW(Base::RuntimeError,"No parent container"); + + if(obj && !App::Document::isAnyRestoring()) { + auto inSet = parent->getInListEx(true); + inSet.insert(parent); + if(inSet.find(obj)!=inSet.end()) + LINK_THROW(Base::RuntimeError,"Cyclic dependency"); + } + + auto linkProp = getLinkedObjectProperty(); + + // If we are a group (i.e. no LinkObject property), and the index is + // negative with a non-zero 'obj' assignment, we treat this as group + // expansion by changing the index to one pass the existing group size + if(index<0 && obj && !linkProp && getElementListProperty()) + index = getElementListProperty()->getSize(); + + if(index>=0) { + // LinkGroup assignment + + if(linkProp || !getElementListProperty()) + LINK_THROW(Base::RuntimeError,"Cannot set link element"); + + DocumentObject *old = 0; + const auto &elements = getElementListProperty()->getValues(); + if(!obj) { + if(index>=(int)elements.size()) + LINK_THROW(Base::ValueError,"Link element index out of bound"); + std::vector objs; + old = elements[index]; + for(int i=0;i<(int)elements.size();++i) { + if(i!=index) + objs.push_back(elements[i]); + } + getElementListProperty()->setValue(objs); + }else if(!obj->getNameInDocument()) + LINK_THROW(Base::ValueError,"Invalid object"); + else{ + if(index>(int)elements.size()) + LINK_THROW(Base::ValueError,"Link element index out of bound"); + + if(index < (int)elements.size()) + old = elements[index]; + + int idx = -1; + if(getLinkModeValue()>=LinkModeAutoLink || + (subname && subname[0]) || + subElements.size() || + obj->getDocument()!=parent->getDocument() || + (getElementListProperty()->find(obj->getNameInDocument(),&idx) && idx!=index)) + { + std::string name = parent->getDocument()->getUniqueObjectName("Link"); + auto link = new Link; + link->myOwner = parent->getID(); + parent->getDocument()->addObject(link,name.c_str()); + link->setLink(-1,obj,subname,subElements); + auto linked = link->getTrueLinkedObject(true); + if(linked) + link->Label.setValue(linked->Label.getValue()); + auto pla = freecad_dynamic_cast(obj->getPropertyByName("Placement")); + if(pla) + link->Placement.setValue(pla->getValue()); + link->Visibility.setValue(false); + obj = link; + } + + if(old == obj) + return; + + getElementListProperty()->set1Value(index,obj); + } + detachElement(old); + return; + } + + if(!linkProp) { + // Reaching here means, we are group (i.e. no LinkedObject), and + // index<0, and 'obj' is zero. We shall clear the whole group + + if(obj || !getElementListProperty()) + LINK_THROW(Base::RuntimeError,"No PropertyLink or PropertyLinkList configured"); + + auto objs = getElementListValue(); + getElementListProperty()->setValue(); + for(auto obj : objs) + detachElement(obj); + return; + } + + // Here means we are assigning a Link + + auto xlink = freecad_dynamic_cast(linkProp); + auto subElementProp = getSubElementsProperty(); + if(subElements.size() && !subElementProp) + LINK_THROW(Base::RuntimeError,"No SubElements Property configured"); + + if(obj) { + if(!obj->getNameInDocument()) + LINK_THROW(Base::ValueError,"Invalid document object"); + if(!xlink) { + if(parent && obj->getDocument()!=parent->getDocument()) + LINK_THROW(Base::ValueError,"Cannot link to external object without PropertyXLink"); + } + } + + if(subname && subname[0] && !xlink) + LINK_THROW(Base::RuntimeError,"SubName link requires PropertyXLink"); + + if(subElementProp && subElements.size()) { + subElementProp->setStatus(Property::User3, true); + subElementProp->setValue(subElements); + subElementProp->setStatus(Property::User3, false); + } + if(xlink) + xlink->setValue(obj,subname); + else + linkProp->setValue(obj); +} + +void LinkBaseExtension::detachElement(DocumentObject *obj) { + if(!obj || !obj->getNameInDocument() || obj->isRemoving()) + return; + auto ext = obj->getExtensionByType(true); + auto owner = getContainer(); + long ownerID = owner?owner->getID():0; + if(getLinkModeValue()==LinkModeAutoUnlink) { + if(!ext || ext->myOwner!=ownerID) + return; + }else if(getLinkModeValue()!=LinkModeAutoDelete) { + if(ext && ext->myOwner==ownerID) + ext->myOwner = 0; + return; + } + obj->getDocument()->removeObject(obj->getNameInDocument()); +} + +std::vector LinkBaseExtension::getLinkedChildren(bool filter) const{ + if(!filter) + return _getElementListValue(); + std::vector ret; + for(auto o : _getElementListValue()) { + if(!o->hasExtension(GroupExtension::getExtensionClassTypeId(),false)) + ret.push_back(o); + } + return ret; +} + +const char *LinkBaseExtension::flattenSubname(const char *subname) const { + if(subname && _ChildCache.getSize()) { + const char *sub = subname; + std::string s; + for(const char* dot=strchr(sub,'.');dot;sub=dot+1,dot=strchr(sub,'.')) { + DocumentObject *obj = 0; + s.clear(); + s.append(sub,dot+1); + extensionGetSubObject(obj,s.c_str()); + if(!obj) + break; + if(!obj->hasExtension(GroupExtension::getExtensionClassTypeId(),false)) + return sub; + } + } + return subname; +} + +void LinkBaseExtension::expandSubname(std::string &subname) const { + if(!_ChildCache.getSize()) + return; + + const char *pos = 0; + int index = getElementIndex(subname.c_str(),&pos); + if(index<0) + return; + std::ostringstream ss; + elementNameFromIndex(index,ss); + ss << pos; + subname = ss.str(); +} + +static bool isExcludedProperties(const char *name) { +#define CHECK_EXCLUDE_PROP(_name) if(strcmp(name,#_name)==0) return true; + CHECK_EXCLUDE_PROP(Shape); + CHECK_EXCLUDE_PROP(Proxy); + CHECK_EXCLUDE_PROP(Placement); + return false; +} + +Property *LinkBaseExtension::extensionGetPropertyByName(const char* name) const { + auto prop = inherited::extensionGetPropertyByName(name); + if(prop || isExcludedProperties(name)) + return prop; + auto owner = getContainer(); + if(owner && owner->canLinkProperties()) { + auto linked = getTrueLinkedObject(true); + if(linked) + return linked->getPropertyByName(name); + } + return 0; +} + +/////////////////////////////////////////////////////////////////////////////////////////// + +namespace App { +EXTENSION_PROPERTY_SOURCE_TEMPLATE(App::LinkBaseExtensionPython, App::LinkBaseExtension) + +// explicit template instantiation +template class AppExport ExtensionPythonT; + +} + +////////////////////////////////////////////////////////////////////////////// + +EXTENSION_PROPERTY_SOURCE(App::LinkExtension, App::LinkBaseExtension) + +LinkExtension::LinkExtension(void) +{ + initExtensionType(LinkExtension::getExtensionClassTypeId()); + + LINK_PROPS_ADD_EXTENSION(LINK_PARAMS_EXT); +} + +LinkExtension::~LinkExtension() +{ +} + +/////////////////////////////////////////////////////////////////////////////////////////// + +namespace App { +EXTENSION_PROPERTY_SOURCE_TEMPLATE(App::LinkExtensionPython, App::LinkExtension) + +// explicit template instantiation +template class AppExport ExtensionPythonT; + +} + +/////////////////////////////////////////////////////////////////////////////////////////// + +PROPERTY_SOURCE_WITH_EXTENSIONS(App::Link, App::DocumentObject) + +Link::Link() { + LINK_PROPS_ADD(LINK_PARAMS_LINK); + LinkExtension::initExtension(this); + static const PropertyIntegerConstraint::Constraints s_constraints = {0,INT_MAX,1}; + ElementCount.setConstraints(&s_constraints); +} + +bool Link::canLinkProperties() const { + auto prop = freecad_dynamic_cast(getLinkedObjectProperty()); + const char *subname; + if(prop && (subname=prop->getSubName()) && *subname) { + auto len = strlen(subname); + // Do not link properties when we are linking to a sub-element (i.e. + // vertex, edge or face) + return subname[len-1]=='.'; + } + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////// + +namespace App { +PROPERTY_SOURCE_TEMPLATE(App::LinkPython, App::Link) +template<> const char* App::LinkPython::getViewProviderName(void) const { + return "Gui::ViewProviderLinkPython"; +} +template class AppExport FeaturePythonT; +} + +////////////////////////////////////////////////////////////////////////////////////////// + +PROPERTY_SOURCE_WITH_EXTENSIONS(App::LinkElement, App::DocumentObject) + +LinkElement::LinkElement() { + LINK_PROPS_ADD(LINK_PARAMS_ELEMENT); + LinkBaseExtension::initExtension(this); +} + +bool LinkElement::canDelete() const { + if(!myOwner) + return true; + + auto owner = getContainer(); + return !owner || !owner->getDocument()->getObjectByID(myOwner); +} + +////////////////////////////////////////////////////////////////////////////////////////// + +namespace App { +PROPERTY_SOURCE_TEMPLATE(App::LinkElementPython, App::LinkElement) +template<> const char* App::LinkElementPython::getViewProviderName(void) const { + return "Gui::ViewProviderLinkPython"; +} +template class AppExport FeaturePythonT; +} + +////////////////////////////////////////////////////////////////////////////////////////// + +PROPERTY_SOURCE_WITH_EXTENSIONS(App::LinkGroup, App::DocumentObject) + +LinkGroup::LinkGroup() { + LINK_PROPS_ADD(LINK_PARAMS_GROUP); + LinkBaseExtension::initExtension(this); +} + +////////////////////////////////////////////////////////////////////////////////////////// + +namespace App { +PROPERTY_SOURCE_TEMPLATE(App::LinkGroupPython, App::LinkGroup) +template<> const char* App::LinkGroupPython::getViewProviderName(void) const { + return "Gui::ViewProviderLinkPython"; +} +template class AppExport FeaturePythonT; +} diff --git a/src/App/Link.h b/src/App/Link.h new file mode 100644 index 0000000000..367dc4dda1 --- /dev/null +++ b/src/App/Link.h @@ -0,0 +1,529 @@ +/**************************************************************************** + * 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 * + * * + ****************************************************************************/ + +#ifndef APP_LINK_H +#define APP_LINK_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "DocumentObject.h" +#include "FeaturePython.h" +#include "PropertyLinks.h" +#include "DocumentObjectExtension.h" +#include "FeaturePython.h" +#include "GroupExtension.h" + +#define LINK_THROW(_type,_msg) do{\ + if(FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG))\ + FC_ERR(_msg);\ + throw _type(_msg);\ +}while(0) + +namespace App +{ + +class AppExport LinkBaseExtension : public App::DocumentObjectExtension +{ + EXTENSION_PROPERTY_HEADER(App::LinkExtension); + typedef App::DocumentObjectExtension inherited; + +public: + LinkBaseExtension(); + virtual ~LinkBaseExtension(); + + PropertyBool _LinkRecomputed; + PropertyLinkList _ChildCache; // cache for plain group expansion + + enum { + LinkModeNone, + LinkModeAutoDelete, + LinkModeAutoLink, + LinkModeAutoUnlink, + }; + + /** \name Parameter definition + * + * Parameter definition (Name, Type, Property Type, Default, Document). + * The variadic is here so that the parameter can be extended by adding + * extra fields. See LINK_PARAM_EXT() for an example + */ + //@{ + +#define LINK_PARAM_LINK_PLACEMENT(...) \ + (LinkPlacement, Base::Placement, App::PropertyPlacement, Base::Placement(), "Link placement", ##__VA_ARGS__) + +#define LINK_PARAM_PLACEMENT(...) \ + (Placement, Base::Placement, App::PropertyPlacement, Base::Placement(), \ + "Alias to LinkPlacement to make the link object compatibale with other objects", ##__VA_ARGS__) + +#define LINK_PARAM_OBJECT(...) \ + (LinkedObject, App::DocumentObject*, App::PropertyLink, 0, "Linked object", ##__VA_ARGS__) + +#define LINK_PARAM_SUB_ELEMENT(...) \ + (SubElements, std::vector, App::PropertyStringList, std::vector(), \ + "Non-object Sub-element list of the linked object, e.g. Face1", ##__VA_ARGS__) + +#define LINK_PARAM_TRANSFORM(...) \ + (LinkTransform, bool, App::PropertyBool, false, \ + "Set to false to override linked object's placement", ##__VA_ARGS__) + +#define LINK_PARAM_SCALE(...) \ + (Scale, double, App::PropertyFloat, 1.0, "Scale factor", ##__VA_ARGS__) + +#define LINK_PARAM_SCALE_VECTOR(...) \ + (ScaleVector, Base::Vector3d, App::PropertyVector, Base::Vector3d(1,1,1), "Scale factors", ##__VA_ARGS__) + +#define LINK_PARAM_PLACEMENTS(...) \ + (PlacementList, std::vector, App::PropertyPlacementList, std::vector(),\ + "The placement for each link element", ##__VA_ARGS__) + +#define LINK_PARAM_SCALES(...) \ + (ScaleList, std::vector, App::PropertyVectorList, std::vector(),\ + "The scale factors for each link element", ##__VA_ARGS__) + +#define LINK_PARAM_VISIBILITIES(...) \ + (VisibilityList, boost::dynamic_bitset<>, App::PropertyBoolList, boost::dynamic_bitset<>(),\ + "The visibility state of each link element", ##__VA_ARGS__) + +#define LINK_PARAM_COUNT(...) \ + (ElementCount, int, App::PropertyInteger, 0, "Link element count", ##__VA_ARGS__) + +#define LINK_PARAM_ELEMENTS(...) \ + (ElementList, std::vector, App::PropertyLinkList, std::vector(),\ + "The link element object list", ##__VA_ARGS__) + +#define LINK_PARAM_SHOW_ELEMENT(...) \ + (ShowElement, bool, App::PropertyBool, true, "Enable link element list", ##__VA_ARGS__) + +#define LINK_PARAM_MODE(...) \ + (LinkMode, long, App::PropertyEnumeration, ((long)0), "Link group mode", ##__VA_ARGS__) + +#define LINK_PARAM_COLORED_ELEMENTS(...) \ + (ColoredElements, App::DocumentObject*, App::PropertyLinkSubHidden, \ + 0, "Link colored elements", ##__VA_ARGS__) + +#define LINK_PARAM(_param) (LINK_PARAM_##_param()) + +#define LINK_PNAME(_param) BOOST_PP_TUPLE_ELEM(0,_param) +#define LINK_PTYPE(_param) BOOST_PP_TUPLE_ELEM(1,_param) +#define LINK_PPTYPE(_param) BOOST_PP_TUPLE_ELEM(2,_param) +#define LINK_PDEF(_param) BOOST_PP_TUPLE_ELEM(3,_param) +#define LINK_PDOC(_param) BOOST_PP_TUPLE_ELEM(4,_param) + +#define LINK_PINDEX(_param) BOOST_PP_CAT(Prop,LINK_PNAME(_param)) + //@} + +#define LINK_PARAMS \ + LINK_PARAM(PLACEMENT)\ + LINK_PARAM(LINK_PLACEMENT)\ + LINK_PARAM(OBJECT)\ + LINK_PARAM(SUB_ELEMENT)\ + LINK_PARAM(TRANSFORM)\ + LINK_PARAM(SCALE)\ + LINK_PARAM(SCALE_VECTOR)\ + LINK_PARAM(PLACEMENTS)\ + LINK_PARAM(SCALES)\ + LINK_PARAM(VISIBILITIES)\ + LINK_PARAM(COUNT)\ + LINK_PARAM(ELEMENTS)\ + LINK_PARAM(SHOW_ELEMENT)\ + LINK_PARAM(MODE)\ + LINK_PARAM(COLORED_ELEMENTS) + + enum PropIndex { +#define LINK_PINDEX_DEFINE(_1,_2,_param) LINK_PINDEX(_param), + + // defines Prop##Name enumeration value + BOOST_PP_SEQ_FOR_EACH(LINK_PINDEX_DEFINE,_,LINK_PARAMS) + PropMax + }; + + virtual void setProperty(int idx, Property *prop); + Property *getProperty(int idx); + Property *getProperty(const char *); + + struct PropInfo { + int index; + const char *name; + Base::Type type; + const char *doc; + + PropInfo(int index, const char *name,Base::Type type,const char *doc) + :index(index),name(name),type(type),doc(doc) + {} + + PropInfo() {} + }; + +#define LINK_PROP_INFO(_1,_var,_param) \ + _var.push_back(PropInfo(BOOST_PP_CAT(Prop,LINK_PNAME(_param)),\ + BOOST_PP_STRINGIZE(LINK_PNAME(_param)),\ + LINK_PPTYPE(_param)::getClassTypeId(), \ + LINK_PDOC(_param))); + + virtual const std::vector &getPropertyInfo() const; + + typedef std::map PropInfoMap; + virtual const PropInfoMap &getPropertyInfoMap() const; + +#define LINK_PROP_GET(_1,_2,_param) \ + LINK_PTYPE(_param) BOOST_PP_SEQ_CAT((get)(LINK_PNAME(_param))(Value)) () const {\ + auto prop = props[LINK_PINDEX(_param)];\ + if(!prop) return LINK_PDEF(_param);\ + return static_cast(prop)->getValue();\ + }\ + const LINK_PPTYPE(_param) *BOOST_PP_SEQ_CAT((get)(LINK_PNAME(_param))(Property)) () const {\ + auto prop = props[LINK_PINDEX(_param)];\ + return static_cast(prop);\ + }\ + LINK_PPTYPE(_param) *BOOST_PP_SEQ_CAT((get)(LINK_PNAME(_param))(Property)) () {\ + auto prop = props[LINK_PINDEX(_param)];\ + return static_cast(prop);\ + }\ + + // defines get##Name() and get##Name##Property() accessor + BOOST_PP_SEQ_FOR_EACH(LINK_PROP_GET,_,LINK_PARAMS) + + PropertyLinkList *_getElementListProperty() const; + const std::vector &_getElementListValue() const; + + PropertyBool *_getShowElementProperty() const; + bool _getShowElementValue() const; + + PropertyInteger *_getElementCountProperty() const; + int _getElementCountValue() const; + + std::vector getLinkedChildren(bool filter=true) const; + + const char *flattenSubname(const char *subname) const; + void expandSubname(std::string &subname) const; + + DocumentObject *getLink(int depth=0) const; + + Base::Matrix4D getTransform(bool transform) const; + Base::Vector3d getScaleVector() const; + + App::GroupExtension *linkedPlainGroup() const; + + bool linkTransform() const; + + const char *getSubName() const { + parseSubName(); + return mySubName.size()?mySubName.c_str():0; + } + const char *getSubElement() const { + parseSubName(); + return mySubElement.size()?mySubElement.c_str():0; + } + + bool extensionGetSubObject(DocumentObject *&ret, const char *subname, + PyObject **pyObj=0, Base::Matrix4D *mat=0, bool transform=false, int depth=0) const override; + + bool extensionGetSubObjects(std::vector&ret, int reason) const override; + + bool extensionGetLinkedObject(DocumentObject *&ret, + bool recurse, Base::Matrix4D *mat, bool transform, int depth) const override; + + virtual App::DocumentObjectExecReturn *extensionExecute(void) override; + virtual short extensionMustExecute(void) override; + virtual void extensionOnChanged(const Property* p) override; + virtual void onExtendedUnsetupObject () override; + virtual void onExtendedDocumentRestored() override; + + virtual int extensionSetElementVisible(const char *, bool) override; + virtual int extensionIsElementVisible(const char *) override; + virtual bool extensionHasChildElement() const override; + + virtual PyObject* getExtensionPyObject(void) override; + + virtual Property *extensionGetPropertyByName(const char* name) const override; + + static int getArrayIndex(const char *subname, const char **psubname=0); + int getElementIndex(const char *subname, const char **psubname=0) const; + void elementNameFromIndex(int idx, std::ostream &ss) const; + + DocumentObject *getContainer(); + const DocumentObject *getContainer() const; + + void setLink(int index, DocumentObject *obj, const char *subname=0, + const std::vector &subs = std::vector()); + + DocumentObject *getTrueLinkedObject(bool recurse, + Base::Matrix4D *mat=0,int depth=0, bool noElement=false) const; + + typedef std::map > LinkPropMap; + + bool hasPlacement() const { + return getLinkPlacementProperty() || getPlacementProperty(); + } + + void cacheChildLabel(int enable=-1) const; + +protected: + void parseSubName() const; + void update(App::DocumentObject *parent, const Property *prop); + void syncElementList(); + void detachElement(App::DocumentObject *obj); + void checkGeoElementMap(const App::DocumentObject *obj, + const App::DocumentObject *linked, PyObject **pyObj, const char *postfix) const; + void updateGroup(); + void slotChangedPlainGroup(const App::DocumentObject &, const App::Property &); + +protected: + std::vector props; + std::unordered_set myHiddenElements; + mutable std::string mySubElement; + mutable std::string mySubName; + + std::unordered_map plainGroupConns; + + mutable std::unordered_map myLabelCache; // for label based subname lookup + mutable bool enableLabelCache; + + long myOwner; +}; + +/////////////////////////////////////////////////////////////////////////// + +typedef ExtensionPythonT LinkBaseExtensionPython; + +/////////////////////////////////////////////////////////////////////////// + +class AppExport LinkExtension : public LinkBaseExtension +{ + EXTENSION_PROPERTY_HEADER(App::LinkExtension); + typedef LinkBaseExtension inherited; + +public: + LinkExtension(); + virtual ~LinkExtension(); + + /** \name Helpers for defining extended parameter + * + * extended parameter definition + * (Name, Type, Property_Type, Default, Document, Property_Name, + * Derived_Property_Type, App_Property_Type, Group) + * + * This helper simply reuses Name as Property_Name, Property_Type as + * Derived_Property_type, Prop_None as App_Propert_Type + * + * Note: Because PropertyView will merge linked object's properties into + * ours, we set the default group name as ' Link' with a leading space to + * try to make our group before others + */ + //@{ + +#define LINK_ENAME(_param) BOOST_PP_TUPLE_ELEM(5,_param) +#define LINK_ETYPE(_param) BOOST_PP_TUPLE_ELEM(6,_param) +#define LINK_EPTYPE(_param) BOOST_PP_TUPLE_ELEM(7,_param) +#define LINK_EGROUP(_param) BOOST_PP_TUPLE_ELEM(8,_param) + +#define _LINK_PROP_ADD(_add_property, _param) \ + _add_property(BOOST_PP_STRINGIZE(LINK_ENAME(_param)),LINK_ENAME(_param),\ + (LINK_PDEF(_param)),LINK_EGROUP(_param),LINK_EPTYPE(_param),LINK_PDOC(_param));\ + setProperty(LINK_PINDEX(_param),&LINK_ENAME(_param)); + +#define LINK_PROP_ADD(_1,_2,_param) \ + _LINK_PROP_ADD(_ADD_PROPERTY_TYPE,_param); + +#define LINK_PROP_ADD_EXTENSION(_1,_2,_param) \ + _LINK_PROP_ADD(_EXTENSION_ADD_PROPERTY_TYPE,_param); + +#define LINK_PROPS_ADD(_seq) \ + BOOST_PP_SEQ_FOR_EACH(LINK_PROP_ADD,_,_seq) + +#define LINK_PROPS_ADD_EXTENSION(_seq) \ + BOOST_PP_SEQ_FOR_EACH(LINK_PROP_ADD_EXTENSION,_,_seq) + +#define _LINK_PROP_SET(_1,_2,_param) \ + setProperty(LINK_PINDEX(_param),&LINK_ENAME(_param)); + +#define LINK_PROPS_SET(_seq) BOOST_PP_SEQ_FOR_EACH(_LINK_PROP_SET,_,_seq) + + /// Helper for defining default extended parameter +#define _LINK_PARAM_EXT(_name,_type,_ptype,_def,_doc,...) \ + ((_name,_type,_ptype,_def,_doc,_name,_ptype,App::Prop_None," Link")) + + /** Define default extended parameter + * It simply reuses Name as Property_Name, Property_Type as + * Derived_Property_Type, and App::Prop_None as App::PropertyType + */ +#define LINK_PARAM_EXT(_param) BOOST_PP_EXPAND(_LINK_PARAM_EXT LINK_PARAM_##_param()) + + /// Helper for extended parameter with app property type +#define _LINK_PARAM_EXT_ATYPE(_name,_type,_ptype,_def,_doc,_atype) \ + ((_name,_type,_ptype,_def,_doc,_name,_ptype,_atype," Link")) + + /// Define extended parameter with app property type +#define LINK_PARAM_EXT_ATYPE(_param,_atype) \ + BOOST_PP_EXPAND(_LINK_PARAM_EXT_ATYPE LINK_PARAM_##_param(_atype)) + + /// Helper for extended parameter with derived property type +#define _LINK_PARAM_EXT_TYPE(_name,_type,_ptype,_def,_doc,_dtype) \ + ((_name,_type,_ptype,_def,_doc,_name,_dtype,App::Prop_None," Link")) + + /// Define extended parameter with derived property type +#define LINK_PARAM_EXT_TYPE(_param,_dtype) \ + BOOST_PP_EXPAND(_LINK_PARAM_EXT_TYPE LINK_PARAM_##_param(_dtype)) + + /// Helper for extended parameter with a different property name +#define _LINK_PARAM_EXT_NAME(_name,_type,_ptype,_def,_doc,_pname) \ + ((_name,_type,_ptype,_def,_doc,_pname,_ptype,App::Prop_None," Link")) + + /// Define extended parameter with a different property name +#define LINK_PARAM_EXT_NAME(_param,_pname) BOOST_PP_EXPAND(_LINK_PARAM_EXT_NAME LINK_PARAM_##_param(_pname)) + //@} + +#define LINK_PARAMS_EXT \ + LINK_PARAM_EXT(SCALE)\ + LINK_PARAM_EXT(SCALES)\ + LINK_PARAM_EXT(VISIBILITIES)\ + LINK_PARAM_EXT(PLACEMENTS)\ + LINK_PARAM_EXT(ELEMENTS) + +#define LINK_PROP_DEFINE(_1,_2,_param) LINK_ETYPE(_param) LINK_ENAME(_param); +#define LINK_PROPS_DEFINE(_seq) BOOST_PP_SEQ_FOR_EACH(LINK_PROP_DEFINE,_,_seq) + + // defines the actual properties + LINK_PROPS_DEFINE(LINK_PARAMS_EXT) + + void onExtendedDocumentRestored() override { + LINK_PROPS_SET(LINK_PARAMS_EXT); + inherited::onExtendedDocumentRestored(); + } +}; + +/////////////////////////////////////////////////////////////////////////// + +typedef ExtensionPythonT LinkExtensionPython; + +/////////////////////////////////////////////////////////////////////////// + +class AppExport Link : public App::DocumentObject, public App::LinkExtension +{ + PROPERTY_HEADER_WITH_EXTENSIONS(App::Link); + typedef App::DocumentObject inherited; +public: + +#define LINK_PARAMS_LINK \ + LINK_PARAM_EXT_TYPE(OBJECT, App::PropertyXLink)\ + LINK_PARAM_EXT(TRANSFORM)\ + LINK_PARAM_EXT(LINK_PLACEMENT)\ + LINK_PARAM_EXT(PLACEMENT)\ + LINK_PARAM_EXT(SUB_ELEMENT)\ + LINK_PARAM_EXT(SHOW_ELEMENT)\ + LINK_PARAM_EXT_TYPE(COUNT,App::PropertyIntegerConstraint)\ + LINK_PARAM_EXT_ATYPE(COLORED_ELEMENTS,App::Prop_Hidden) + + LINK_PROPS_DEFINE(LINK_PARAMS_LINK) + + Link(void); + + const char* getViewProviderName(void) const override{ + return "Gui::ViewProviderLink"; + } + + void onDocumentRestored() override { + LINK_PROPS_SET(LINK_PARAMS_LINK); + inherited::onDocumentRestored(); + } + + bool canLinkProperties() const override; +}; + +typedef App::FeaturePythonT LinkPython; + +/////////////////////////////////////////////////////////////////////////// + +class AppExport LinkElement : public App::DocumentObject, public App::LinkBaseExtension { + PROPERTY_HEADER_WITH_EXTENSIONS(App::LinkElement); + typedef App::DocumentObject inherited; +public: + +#define LINK_PARAMS_ELEMENT \ + LINK_PARAM_EXT(SCALE)\ + LINK_PARAM_EXT_TYPE(OBJECT, App::PropertyXLink)\ + LINK_PARAM_EXT(TRANSFORM) \ + LINK_PARAM_EXT(LINK_PLACEMENT)\ + LINK_PARAM_EXT(PLACEMENT)\ + LINK_PARAM_EXT(SUB_ELEMENT) + + // defines the actual properties + LINK_PROPS_DEFINE(LINK_PARAMS_ELEMENT) + + LinkElement(); + const char* getViewProviderName(void) const override{ + return "Gui::ViewProviderLink"; + } + + void onDocumentRestored() override { + LINK_PROPS_SET(LINK_PARAMS_ELEMENT); + inherited::onDocumentRestored(); + } + + bool canDelete() const; +}; + +typedef App::FeaturePythonT LinkElementPython; + +/////////////////////////////////////////////////////////////////////////// + +class AppExport LinkGroup : public App::DocumentObject, public App::LinkBaseExtension { + PROPERTY_HEADER_WITH_EXTENSIONS(App::LinkGroup); + typedef App::DocumentObject inherited; +public: + +#define LINK_PARAMS_GROUP \ + LINK_PARAM_EXT(ELEMENTS)\ + LINK_PARAM_EXT(PLACEMENT)\ + LINK_PARAM_EXT(VISIBILITIES)\ + LINK_PARAM_EXT(MODE)\ + LINK_PARAM_EXT_ATYPE(COLORED_ELEMENTS,App::Prop_Hidden) + + // defines the actual properties + LINK_PROPS_DEFINE(LINK_PARAMS_GROUP) + + LinkGroup(); + + const char* getViewProviderName(void) const override{ + return "Gui::ViewProviderLink"; + } + + void onDocumentRestored() override { + LINK_PROPS_SET(LINK_PARAMS_GROUP); + inherited::onDocumentRestored(); + } +}; + +typedef App::FeaturePythonT LinkGroupPython; + +} //namespace App + + +#endif // APP_LINK_H diff --git a/src/App/LinkBaseExtensionPy.xml b/src/App/LinkBaseExtensionPy.xml new file mode 100644 index 0000000000..fa1ec34f83 --- /dev/null +++ b/src/App/LinkBaseExtensionPy.xml @@ -0,0 +1,117 @@ + + + + + + Link extension base class + + + + +configLinkProperty(key=val,...): property configuration +configLinkProperty(key,...): property configuration with default name + +This methode is here to impelement what I called Property Design +Pattern. The extension operates on a predefined set of properties, +but it relies on the extended object to supply the actual property by +calling this methode. You can choose a sub set of functionality of +this extension by supplying only some of the supported properties. + +The 'key' are names used to refer to properties supported by this +extension, and 'val' is the actual name of the property of your +object. You can obtain the key names and expected types using +getLinkPropertyInfo(). You can use property of derived type when +calling configLinkProperty(). Other types will cause exception to +ben thrown. The actual properties supported may be different +depending on the actual extension object underlying this python +object. + +If 'val' is omitted, i.e. calling configLinkProperty(key,...), then +it is assumed the the actualy property name is the same as 'key' + + + + + + getLinkExtProperty(name): return the property value by its predefined name + + + + + getLinkExtPropertyName(name): lookup the property name by its predefined name + + + + + +getLinkPropertyInfo(): return a tuple of (name,type,doc) for all supported properties. + +getLinkPropertyInfo(index): return (name,type,doc) of a specific property + +getLinkPropertyInfo(name): return (type,doc) of a specific property + + + + + + +setLink(obj,subName=None,subElements=None): Set link object. + +setLink([obj,...]), +setLink([(obj,subName,subElements),...]), +setLink({index:obj,...}), +setLink({index:(obj,subName,subElements),...}): set link element of a link group. + +obj (DocumentObject): the object to link to. If this is None, then the link is cleared + +subName (String): Dot separated object path. + +subElements (String|tuple(String)): non-object sub-elements, e.g. Face1, Edge2. + + + + + + +cacheChildLabel(enable=True): enable/disable child label cache + +The cache is not updated on child label change for performance reason. You must +call this function on any child label change + + + + + + +flattenSubname(subname) -> string + +Return a flattened subname in case it references an object inside a linked plain group + + + + + + +expandSubname(subname) -> string + +Return an expanded subname in case it references an object inside a linked plain group + + + + + + + Return a flattend (in case grouped by plain group) list of linked children + + + + + diff --git a/src/App/LinkBaseExtensionPyImp.cpp b/src/App/LinkBaseExtensionPyImp.cpp new file mode 100644 index 0000000000..90f44f3e46 --- /dev/null +++ b/src/App/LinkBaseExtensionPyImp.cpp @@ -0,0 +1,316 @@ +/**************************************************************************** + * 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 +#endif + +#include "DocumentObjectPy.h" +#include "LinkBaseExtensionPy.h" +#include "LinkBaseExtensionPy.cpp" + +using namespace App; + +// returns a string which represent the object e.g. when printed in python +std::string LinkBaseExtensionPy::representation(void) const +{ + std::ostringstream str; + str << "<" << getLinkBaseExtensionPtr()->getExtensionClassTypeId().getName() << ">"; + return str.str(); +} + +typedef std::map > PropTmpMap; +typedef std::map PropMap; + +static bool getProperty(PropTmpMap &props, const LinkBaseExtension::PropInfoMap &infoMap, + const PropMap &propMap, PyObject *key, PyObject *value) +{ + std::ostringstream str; + + #if PY_MAJOR_VERSION < 3 + if(!PyString_Check(key)) { + PyErr_SetString(PyExc_TypeError, "key must be string"); + return false; + } + const char *keyStr = PyString_AsString(key); +#else + if(!PyUnicode_Check(key)) { + PyErr_SetString(PyExc_TypeError, "key must be a unicode string"); + return false; + } + const char *keyStr = PyUnicode_AsUTF8(key); +#endif + auto it = infoMap.find(keyStr); + if(it == infoMap.end()){ + str << "unknown key '" << keyStr << "'"; + PyErr_SetString(PyExc_KeyError, str.str().c_str()); + return false; + } + + const char *valStr = 0; + if(key == value) + valStr = keyStr; + else if (value!=Py_None) { +#if PY_MAJOR_VERSION < 3 + if(!PyString_Check(value)) { + PyErr_SetString(PyExc_TypeError, "value must be string"); + return false; + } + valStr = PyString_AsString(value); +#else + if(!PyUnicode_Check(value)) { + PyErr_SetString(PyExc_TypeError, "value must be unicode string"); + return false; + } + valStr = PyUnicode_AsUTF8(value); +#endif + } + + App::Property *prop = 0; + auto &info = it->second; + if(valStr) { + auto pIt = propMap.find(valStr); + if(pIt == propMap.end()) { + str << "cannot find property '" << valStr << "'"; + PyErr_SetString(PyExc_ValueError, str.str().c_str()); + return false; + } + prop = pIt->second; + if(!prop->isDerivedFrom(info.type)) { + str << "expect property '" << keyStr << "(" << valStr + << ") to be derived from '" << info.type.getName() + << "', instead of '" << prop->getTypeId().getName() << "'"; + PyErr_SetString(PyExc_TypeError, str.str().c_str()); + } + } + props[keyStr] = std::make_pair(info.index,prop); + return true; +} + +PyObject* LinkBaseExtensionPy::configLinkProperty(PyObject *args, PyObject *keywds) { + auto ext = getLinkBaseExtensionPtr(); + const auto &info = ext->getPropertyInfoMap(); + + PropMap propMap; + ext->getExtendedContainer()->getPropertyMap(propMap); + + PropTmpMap props; + + if(args && PyTuple_Check(args)) { + for(Py_ssize_t pos=0;possetProperty(v.second.first,v.second.second); + Py_Return; +} + +PyObject* LinkBaseExtensionPy::getLinkExtProperty(PyObject *args) +{ + const char *name; + if(!PyArg_ParseTuple(args,"s",&name)) + return 0; + auto prop = getLinkBaseExtensionPtr()->getProperty(name); + if(!prop) { + PyErr_SetString(PyExc_AttributeError, "unknown property name"); + return 0; + } + return prop->getPyObject(); +} + +PyObject* LinkBaseExtensionPy::getLinkExtPropertyName(PyObject *args) { + const char *name; + if(!PyArg_ParseTuple(args,"s",&name)) + return 0; + auto prop = getLinkBaseExtensionPtr()->getProperty(name); + if(!prop) { + PyErr_SetString(PyExc_AttributeError, "unknown property name"); + return 0; + } + auto container = getLinkBaseExtensionPtr()->getExtendedContainer(); + if(!container) { + PyErr_SetString(PyExc_RuntimeError, "no extended container"); + return 0; + } + name = container->getPropertyName(prop); + if(!name) { + PyErr_SetString(PyExc_RuntimeError, "cannot find property name"); + return 0; + } + return Py::new_reference_to(Py::String(name)); +} + +PyObject* LinkBaseExtensionPy::getLinkPropertyInfo(PyObject *args) +{ + auto ext = getLinkBaseExtensionPtr(); + + const auto &infos = ext->getPropertyInfo(); + + if(PyArg_ParseTuple(args,"")) { + Py::Tuple ret(infos.size()); + int i=0; + for(const auto &info : infos) { + ret.setItem(i++,Py::TupleN(Py::String(info.name), + Py::String(info.type.getName()),Py::String(info.doc))); + } + return Py::new_reference_to(ret); + } + + short index = 0; + if(PyArg_ParseTuple(args,"h",&index)) { + if(index<0 || index>=(int)infos.size()) { + PyErr_SetString(PyExc_ValueError, "index out of range"); + return 0; + } + Py::TupleN ret(Py::String(infos[index].name), + Py::String(infos[index].type.getName()),Py::String(infos[index].doc)); + return Py::new_reference_to(ret); + } + + char *name; + if(PyArg_ParseTuple(args,"s",&name)) { + for(int i=0;i<(int)infos.size();++i) { + if(strcmp(infos[i].name,name)==0) { + Py::TupleN ret(Py::String(infos[i].type.getName()), + Py::String(infos[i].doc)); + return Py::new_reference_to(ret); + } + } + PyErr_SetString(PyExc_ValueError, "unknown property name"); + return 0; + } + + PyErr_SetString(PyExc_ValueError, "invalid arguments"); + return 0; +} + +void parseLink(LinkBaseExtension *ext, int index, PyObject *value) { + App::DocumentObject *obj = 0; + PropertyStringList subs; + PropertyString sub; + if(value!=Py_None) { + if(PyObject_TypeCheck(value,&DocumentObjectPy::Type)) { + obj = static_cast(value)->getDocumentObjectPtr(); + }else if(!PySequence_Check(value)) + throw Base::TypeError("Expects type of DocumentObject or sequence"); + else{ + Py::Sequence seq(value); + if(seq[0].ptr() != Py_None) { + if(!PyObject_TypeCheck(seq[0].ptr(),&DocumentObjectPy::Type)) + throw Base::TypeError("Expects the first argument to be DocumentObject in sequence"); + obj = static_cast(seq[0].ptr())->getDocumentObjectPtr(); + if(seq.size()>1) { + sub.setPyObject(seq[1].ptr()); + if(seq.size()>2) + subs.setPyObject(seq[2].ptr()); + } + } + } + } + ext->setLink(index,obj,sub.getValue(),subs.getValue()); +} + +PyObject* LinkBaseExtensionPy::setLink(PyObject *_args) +{ + Py::Sequence args(_args); + PY_TRY { + auto ext = getLinkBaseExtensionPtr(); + PyObject *pcObj = args.size()?args[0].ptr():Py_None; + if(pcObj == Py_None) { + ext->setLink(-1,0); + }else if(PyDict_Check(pcObj)) { + PyObject *key, *value; + Py_ssize_t pos = 0; + while(PyDict_Next(pcObj, &pos, &key, &value)) + parseLink(ext,Py::Int(key),value); + }else if(PySequence_Check(pcObj)) { + ext->setLink(-1,0); + Py::Sequence seq(pcObj); + for(size_t i=0;icacheChildLabel(PyObject_IsTrue(enable)?-1:0); + Py_Return; + }PY_CATCH; +} + +PyObject* LinkBaseExtensionPy::flattenSubname(PyObject *args) { + const char *subname; + if(!PyArg_ParseTuple(args,"s",&subname)) + return 0; + PY_TRY { + return Py::new_reference_to(Py::String( + getLinkBaseExtensionPtr()->flattenSubname(subname))); + }PY_CATCH; +} + +PyObject* LinkBaseExtensionPy::expandSubname(PyObject *args) { + const char *subname; + if(!PyArg_ParseTuple(args,"s",&subname)) + return 0; + PY_TRY { + std::string sub(subname); + getLinkBaseExtensionPtr()->expandSubname(sub); + return Py::new_reference_to(Py::String(sub)); + }PY_CATCH; +} + +Py::List LinkBaseExtensionPy::getLinkedChildren() const { + Py::List ret; + for(auto o : getLinkBaseExtensionPtr()->getLinkedChildren(true)) + ret.append(Py::asObject(o->getPyObject())); + return ret; +} + +PyObject *LinkBaseExtensionPy::getCustomAttributes(const char* /*attr*/) const +{ + return 0; +} + +int LinkBaseExtensionPy::setCustomAttributes(const char* /*attr*/, PyObject * /*obj*/) +{ + return 0; +} diff --git a/src/App/Material.h b/src/App/Material.h index d6aeb681c4..9f1531019e 100644 --- a/src/App/Material.h +++ b/src/App/Material.h @@ -282,6 +282,18 @@ public: float transparency; //@} + bool operator==(const Material& m) const + { + return _matType!=m._matType || shininess!=m.shininess || + transparency!=m.transparency || ambientColor!=m.ambientColor || + diffuseColor!=m.diffuseColor || specularColor!=m.specularColor || + emissiveColor!=m.emissiveColor; + } + bool operator!=(const Material& m) const + { + return !operator==(m); + } + private: MaterialType _matType; }; diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 2bb61f208d..9543e793c7 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -119,6 +119,8 @@ #include "ViewProviderMaterialObject.h" #include "ViewProviderTextDocument.h" #include "ViewProviderGroupExtension.h" +#include "ViewProviderLink.h" +#include "LinkViewPy.h" #include "Language/Translator.h" #include "TaskView/TaskView.h" @@ -401,6 +403,7 @@ Application::Application(bool GUIenabled) Gui::TaskView::ControlPy::init_type(); Py::Module(module).setAttr(std::string("Control"), Py::Object(Gui::TaskView::ControlPy::getInstance(), true)); + Base::Interpreter().addType(&LinkViewPy::Type,module,"LinkView"); } Base::PyGILStateLocker lock; @@ -668,6 +671,7 @@ void Application::createStandardOperations() Gui::CreateWindowStdCommands(); Gui::CreateStructureCommands(); Gui::CreateTestCommands(); + Gui::CreateLinkCommands(); } void Application::slotNewDocument(const App::Document& Doc, bool isMainDoc) @@ -1681,6 +1685,10 @@ void Application::initTypes(void) Gui::ViewProviderMaterialObject ::init(); Gui::ViewProviderMaterialObjectPython ::init(); Gui::ViewProviderTextDocument ::init(); + Gui::ViewProviderLinkObserver ::init(); + Gui::LinkView ::init(); + Gui::ViewProviderLink ::init(); + Gui::ViewProviderLinkPython ::init(); // Workbench Gui::Workbench ::init(); diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index abcc6fc225..7d97ec72d9 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -210,6 +210,8 @@ generate_from_xml(ViewProviderPy) generate_from_xml(ViewProviderDocumentObjectPy) generate_from_xml(WorkbenchPy) generate_from_xml(SelectionObjectPy) +generate_from_xml(LinkViewPy) +generate_from_xml(ViewProviderLinkPy) generate_from_py(FreeCADGuiInit GuiInitScript.h) @@ -221,6 +223,8 @@ SET(FreeCADGui_XML_SRCS WorkbenchPy.xml SelectionObjectPy.xml DocumentPy.xml + LinkViewPy.xml + ViewProviderLinkPy.xml ) SOURCE_GROUP("XML" FILES ${FreeCADApp_XML_SRCS}) @@ -361,6 +365,7 @@ set(Gui_MOC_HDRS TaskView/TaskView.h DAGView/DAGView.h DAGView/DAGModel.h + TaskElementColors.h DlgObjectSelection.h ${FreeCADGui_SDK_MOC_HDRS} ) @@ -434,6 +439,7 @@ SET(Gui_UIC_SRCS TextureMapping.ui TaskView/TaskAppearance.ui TaskView/TaskSelectLinkProperty.ui + TaskElementColors.ui DlgObjectSelection.ui ) @@ -466,6 +472,7 @@ SET(Command_CPP_SRCS CommandTest.cpp CommandView.cpp CommandStructure.cpp + CommandLink.cpp ) SET(Command_SRCS ${Command_CPP_SRCS} @@ -507,6 +514,7 @@ SET(Dialog_CPP_SRCS DownloadItem.cpp DownloadManager.cpp DocumentRecovery.cpp + TaskElementColors.cpp DlgObjectSelection.cpp ) @@ -541,6 +549,7 @@ SET(Dialog_HPP_SRCS DownloadItem.h DownloadManager.h DocumentRecovery.h + TaskElementColors.h DlgObjectSelection.h ) @@ -577,6 +586,7 @@ SET(Dialog_SRCS Placement.ui SceneInspector.ui TextureMapping.ui + TaskElementColors.ui DlgObjectSelection.ui ) SOURCE_GROUP("Dialog" FILES ${Dialog_SRCS}) @@ -983,6 +993,9 @@ SET(Viewprovider_CPP_SRCS ViewProviderOrigin.cpp ViewProviderMaterialObject.cpp ViewProviderTextDocument.cpp + ViewProviderLink.cpp + LinkViewPyImp.cpp + ViewProviderLinkPyImp.cpp ) SET(Viewprovider_SRCS ${Viewprovider_CPP_SRCS} @@ -1013,6 +1026,7 @@ SET(Viewprovider_SRCS ViewProviderOrigin.h ViewProviderMaterialObject.h ViewProviderTextDocument.h + ViewProviderLink.h ) SOURCE_GROUP("View3D\\Viewprovider" FILES ${Viewprovider_SRCS}) diff --git a/src/Gui/CommandFeat.cpp b/src/Gui/CommandFeat.cpp index 943ec59a57..50eba0a78b 100644 --- a/src/Gui/CommandFeat.cpp +++ b/src/Gui/CommandFeat.cpp @@ -31,6 +31,7 @@ #include "Selection.h" #include "ViewProvider.h" #include "ViewProviderDocumentObject.h" +#include "ViewProviderLink.h" using namespace Gui; @@ -89,11 +90,17 @@ void StdCmdRandomColor::activated(int iMsg) float fBlu = (float)rand()/fMax; ViewProvider* view = Application::Instance->getDocument(it->pDoc)->getViewProvider(it->pObject); - App::Property* color = view->getPropertyByName("ShapeColor"); - if (color && color->getTypeId() == App::PropertyColor::getClassTypeId()) { + auto vpLink = dynamic_cast(view); + if(vpLink) { + if(!vpLink->OverrideMaterial.getValue()) + FCMD_VOBJ_CMD2("OverrideMaterial = True",it->pObject); + FCMD_VOBJ_CMD2("ShapeMaterial.DiffuseColor=(%.2f,%.2f,%.2f)", it->pObject, fRed, fGrn, fBlu); + continue; + } + auto color = dynamic_cast(view->getPropertyByName("ShapeColor")); + if (color) { // get the view provider of the selected object and set the shape color - doCommand(Gui, "Gui.getDocument(\"%s\").getObject(\"%s\").ShapeColor=(%.2f,%.2f,%.2f)" - , it->DocName, it->FeatName, fRed, fGrn, fBlu); + FCMD_VOBJ_CMD2("ShapeColor=(%.2f,%.2f,%.2f)" , it->pObject, fRed, fGrn, fBlu); } } } diff --git a/src/Gui/CommandLink.cpp b/src/Gui/CommandLink.cpp new file mode 100644 index 0000000000..0fa38a3db7 --- /dev/null +++ b/src/Gui/CommandLink.cpp @@ -0,0 +1,810 @@ +/**************************************************************************** + * 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 +#endif + +#include +#include "Command.h" +#include "Action.h" +#include "Application.h" +#include "MainWindow.h" +#include "Tree.h" +#include "Document.h" +#include "Selection.h" +#include "WaitCursor.h" +#include "ViewProviderDocumentObject.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +FC_LOG_LEVEL_INIT("CommandLink",true,true); + +using namespace Gui; + +static void setLinkLabel(App::DocumentObject *obj, const char *doc, const char *name) { + const char *label = obj->Label.getValue(); + Command::doCommand(Command::Doc,"App.getDocument('%s').getObject('%s').Label='%s'",doc,name,label); +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +class StdCmdLinkMakeGroup : public Gui::Command +{ +public: + StdCmdLinkMakeGroup(); + const char* className() const + { return "StdCmdLinkMakeGroup"; } + +protected: + virtual void activated(int iMsg); + virtual bool isActive(void); + virtual Action * createAction(void); + virtual void languageChange(); +}; + +StdCmdLinkMakeGroup::StdCmdLinkMakeGroup() + : Command("Std_LinkMakeGroup") +{ + sGroup = QT_TR_NOOP("Link"); + sMenuText = QT_TR_NOOP("Make link group"); + sToolTipText = QT_TR_NOOP("Create a group of links"); + sWhatsThis = "Std_LinkMakeGroup"; + sStatusTip = sToolTipText; + eType = AlterDoc; + sPixmap = "LinkGroup"; +} + +bool StdCmdLinkMakeGroup::isActive() { + return !!App::GetApplication().getActiveDocument(); +} + +Action * StdCmdLinkMakeGroup::createAction(void) +{ + ActionGroup* pcAction = new ActionGroup(this, getMainWindow()); + pcAction->setDropDownMenu(true); + applyCommandData(this->className(), pcAction); + + // add the action items + pcAction->addAction(QObject::tr("Simple group")); + pcAction->addAction(QObject::tr("Group with links")); + pcAction->addAction(QObject::tr("Group with transform links")); + return pcAction; +} + +void StdCmdLinkMakeGroup::languageChange() +{ + Command::languageChange(); + + if (!_pcAction) + return; + ActionGroup* pcAction = qobject_cast(_pcAction); + QList acts = pcAction->actions(); + acts[0]->setText(QObject::tr("Simple group")); + acts[1]->setText(QObject::tr("Group with links")); + acts[2]->setText(QObject::tr("Group with transform links")); +} + + +void StdCmdLinkMakeGroup::activated(int option) { + + std::vector objs; + std::set objset; + + auto doc = App::GetApplication().getActiveDocument(); + if(!doc) { + FC_ERR("no active document"); + return; + } + + for(auto &sel : Selection().getCompleteSelection()) { + if(sel.pObject && sel.pObject->getNameInDocument() && + objset.insert(sel.pObject).second) + objs.push_back(sel.pObject); + } + + Selection().selStackPush(); + Selection().clearCompleteSelection(); + + Command::openCommand("Make link group"); + try { + std::string groupName = doc->getUniqueObjectName("LinkGroup"); + Command::doCommand(Command::Doc, + "App.getDocument('%s').addObject('App::LinkGroup','%s')",doc->getName(),groupName.c_str()); + if(objs.empty()) { + Selection().addSelection(doc->getName(),groupName.c_str()); + Selection().selStackPush(); + }else{ + Command::doCommand(Command::Doc,"__objs__ = []"); + for(auto obj : objs) { + std::string name; + if(option!=0 || doc!=obj->getDocument()) { + name = doc->getUniqueObjectName("Link"); + Command::doCommand(Command::Doc, + "App.getDocument('%s').addObject('App::Link','%s').setLink(" + "App.getDocument('%s').getObject('%s'))", + doc->getName(),name.c_str(),obj->getDocument()->getName(),obj->getNameInDocument()); + setLinkLabel(obj,doc->getName(),name.c_str()); + if(option==2) + Command::doCommand(Command::Doc, + "App.getDocument('%s').getObject('%s').LinkTransform = True", + doc->getName(),name.c_str()); + else if(obj->getPropertyByName("Placement")) + Command::doCommand(Command::Doc, + "App.getDocument('%s').getObject('%s').Placement = " + "App.getDocument('%s').getObject('%s').Placement", + doc->getName(),name.c_str(),obj->getDocument()->getName(),obj->getNameInDocument()); + }else + name = obj->getNameInDocument(); + Command::doCommand(Command::Doc,"__objs__.append(App.getDocument('%s').getObject('%s'))", + doc->getName(),name.c_str()); + Command::doCommand(Command::Doc, + "App.getDocument('%s').getObject('%s').ViewObject.Visibility=False", + doc->getName(),name.c_str()); + } + Command::doCommand(Command::Doc,"App.getDocument('%s').getObject('%s').setLink(__objs__)", + doc->getName(),groupName.c_str()); + Command::doCommand(Command::Doc,"del __objs__"); + + for(size_t i=0;igetName(),groupName.c_str(),name.c_str()); + } + Selection().selStackPush(); + } + if(option!=0) { + Command::doCommand(Command::Doc, + "App.getDocument('%s').getObject('%s').LinkMode = 'Auto Delete'", + doc->getName(),groupName.c_str()); + } + Command::commitCommand(); + } catch (const Base::Exception& e) { + QMessageBox::critical(getMainWindow(), QObject::tr("Create link group failed"), + QString::fromLatin1(e.what())); + Command::abortCommand(); + e.ReportException(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +DEF_STD_CMD(StdCmdLinkMake) + +StdCmdLinkMake::StdCmdLinkMake() + : Command("Std_LinkMake") +{ + sGroup = QT_TR_NOOP("Link"); + sMenuText = QT_TR_NOOP("Make link"); + sToolTipText = QT_TR_NOOP("Create a link to the selected object(s)"); + sWhatsThis = "Std_LinkMake"; + sStatusTip = sToolTipText; + eType = AlterDoc; + sPixmap = "Link"; +} + +void StdCmdLinkMake::activated(int) { + auto doc = App::GetApplication().getActiveDocument(); + if(!doc) { + FC_ERR("no active document"); + return; + } + + std::set objs; + for(auto &sel : Selection().getCompleteSelection()) { + if(sel.pObject && sel.pObject->getNameInDocument()) + objs.insert(sel.pObject); + } + + Selection().selStackPush(); + Selection().clearCompleteSelection(); + + Command::openCommand("Make link"); + try { + if(objs.empty()) { + std::string name = doc->getUniqueObjectName("Link"); + Command::doCommand(Command::Doc, "App.getDocument('%s').addObject('App::Link','%s')", + doc->getName(),name.c_str()); + Selection().addSelection(doc->getName(),name.c_str()); + }else{ + for(auto obj : objs) { + std::string name = doc->getUniqueObjectName("Link"); + Command::doCommand(Command::Doc, + "App.getDocument('%s').addObject('App::Link','%s').setLink(App.getDocument('%s').%s)", + doc->getName(),name.c_str(),obj->getDocument()->getName(),obj->getNameInDocument()); + setLinkLabel(obj,doc->getName(),name.c_str()); + Selection().addSelection(doc->getName(),name.c_str()); + } + } + Selection().selStackPush(); + Command::commitCommand(); + } catch (const Base::Exception& e) { + Command::abortCommand(); + QMessageBox::critical(getMainWindow(), QObject::tr("Create link failed"), + QString::fromLatin1(e.what())); + e.ReportException(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +DEF_STD_CMD_A(StdCmdLinkMakeRelative) + +StdCmdLinkMakeRelative::StdCmdLinkMakeRelative() + : Command("Std_LinkMakeRelative") +{ + sGroup = QT_TR_NOOP("Link"); + sMenuText = QT_TR_NOOP("Make relative link"); + sToolTipText = QT_TR_NOOP("Create a relative link of two selected objects"); + sWhatsThis = "Std_LinkMakeRelative"; + sStatusTip = sToolTipText; + eType = AlterDoc; + sPixmap = "LinkSub"; +} + +bool StdCmdLinkMakeRelative::isActive() { + return Selection().hasSubSelection(); +} + +void StdCmdLinkMakeRelative::activated(int) { + auto doc = App::GetApplication().getActiveDocument(); + if(!doc) { + FC_ERR("no active document"); + return; + } + Command::openCommand("Make relative link"); + try { + std::vector newNames; + for(auto &sel : Selection().getCompleteSelection(0)) { + std::string name = doc->getUniqueObjectName("Link"); + FCMD_DOC_CMD(doc,"addObject('App::Link','" << name << "').setLink(" + << getObjectCmd(sel.pObject) << ",'" << sel.SubName << "')"); + auto link = doc->getObject(name.c_str()); + FCMD_OBJ_CMD(link,"LinkTransform = True"); + setLinkLabel(sel.pResolvedObject,doc->getName(),name.c_str()); + + newNames.push_back(std::move(name)); + } + Selection().selStackPush(); + Selection().clearCompleteSelection(); + for(auto &name : newNames) + Selection().addSelection(doc->getName(),name.c_str()); + Selection().selStackPush(); + + Command::commitCommand(); + } catch (const Base::Exception& e) { + Command::abortCommand(); + QMessageBox::critical(getMainWindow(), QObject::tr("Failed to create relative link"), + QString::fromLatin1(e.what())); + e.ReportException(); + } + return; +} + +///////////////////////////////////////////////////////////////////////////////////// + +struct Info { + bool inited = false; + App::DocumentObjectT topParent; + std::string subname; + App::DocumentObjectT parent; + App::DocumentObjectT obj; +}; + +static void linkConvert(bool unlink) { + // We are trying to replace an object with a link (App::Link), or replace a + // link back to its linked object (i.e. unlink). This is a very complex + // operation. It works by reassign the link property of the parent of the + // selected object(s) to a newly created link to the original object. + // Everything should remain the same. This complexity is now largely handled + // by ViewProviderDocumentObject::replaceObject(), which in turn relies on + // PropertyLinkBase::CopyOnLinkReplace(). + + std::map, Info> infos; + for(auto sel : TreeWidget::getSelection()) { + auto obj = sel.vp->getObject(); + auto parent = sel.parentVp; + if(!parent) { + FC_WARN("skip '" << obj->getFullName() << "' with no parent"); + continue; + } + auto parentObj = parent->getObject(); + auto &info = infos[std::make_pair(parentObj,obj)]; + if(info.inited) + continue; + info.inited = true; + if(unlink) { + auto linked = obj->getLinkedObject(false); + if(!linked || !linked->getNameInDocument() || linked == obj) { + FC_WARN("skip non link"); + continue; + } + } + info.topParent = sel.topParent; + info.parent = parentObj; + info.obj = obj; + } + + if(infos.empty()) + return; + + Selection().selStackPush(); + Selection().clearCompleteSelection(); + + // now, do actual operation + const char *transactionName = unlink?"Unlink":"Replace with link"; + Command::openCommand(transactionName); + try { + std::unordered_map recomputeSet; + for(auto &v : infos) { + auto &info = v.second; + auto parent = info.parent.getObject(); + auto parentVp = Base::freecad_dynamic_cast( + Application::Instance->getViewProvider(parent)); + auto obj = info.obj.getObject(); + if(!parent || !obj || !parentVp) + continue; + if(!recomputeSet.count(parent)) + recomputeSet.emplace(parent,parent); + auto doc = parent->getDocument(); + App::DocumentObject *replaceObj; + if(unlink) { + replaceObj = obj->getLinkedObject(false); + if(!replaceObj || !replaceObj->getNameInDocument() || replaceObj == obj) + continue; + }else{ + auto name = doc->getUniqueObjectName("Link"); + auto link = static_cast(doc->addObject("App::Link",name.c_str())); + if(!link) + FC_THROWM(Base::RuntimeError,"Failed to create link"); + link->setLink(-1,obj); + link->Label.setValue(obj->Label.getValue()); + auto pla = Base::freecad_dynamic_cast( + obj->getPropertyByName("Placement")); + if(pla) + link->Placement.setValue(pla->getValue()); + else + link->LinkTransform.setValue(true); + replaceObj = link; + } + + // adjust subname for the the new object + auto pos = info.subname.rfind('.'); + if(pos==std::string::npos && pos) + info.subname.clear(); + else { + pos = info.subname.rfind('.',pos-1); + if(pos==std::string::npos) + info.subname.clear(); + else { + info.subname.resize(pos+1); + info.subname += replaceObj->getNameInDocument(); + info.subname += "."; + } + } + + // do the replacement operation + if(parentVp->replaceObject(obj,replaceObj)<=0) + FC_THROWM(Base::RuntimeError, + "Failed to change link for " << parent->getFullName()); + } + + std::vector recomputes; + for(auto &v : recomputeSet) { + auto obj = v.second.getObject(); + if(obj) + recomputes.push_back(obj); + } + if(recomputes.size()) + recomputes.front()->getDocument()->recompute(recomputes); + + Command::commitCommand(); + + } catch (const Base::Exception& e) { + Command::abortCommand(); + auto title = unlink?QObject::tr("Unlink failed"):QObject::tr("Replace link failed"); + QMessageBox::critical(getMainWindow(), title, QString::fromLatin1(e.what())); + e.ReportException(); + return; + } +} + +static bool linkConvertible(bool unlink) { + int count = 0; + for(auto &sel : TreeWidget::getSelection()) { + auto parent = sel.parentVp; + if(!parent) return false; + auto obj = sel.vp->getObject(); + if(unlink) { + auto linked = obj->getLinkedObject(false); + if(!linked || linked == obj) + return false; + } + ++count; + } + return count!=0; +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +DEF_STD_CMD_A(StdCmdLinkReplace) + +StdCmdLinkReplace::StdCmdLinkReplace() + : Command("Std_LinkReplace") +{ + sGroup = QT_TR_NOOP("Link"); + sMenuText = QT_TR_NOOP("Replace with link"); + sToolTipText = QT_TR_NOOP("Replace the selected object(s) with link"); + sWhatsThis = "Std_LinkReplace"; + sStatusTip = sToolTipText; + eType = AlterDoc; + sPixmap = "LinkReplace"; +} + +bool StdCmdLinkReplace::isActive() { + return linkConvertible(false); +} + +void StdCmdLinkReplace::activated(int) { + linkConvert(false); +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +DEF_STD_CMD_A(StdCmdLinkUnlink) + +StdCmdLinkUnlink::StdCmdLinkUnlink() + : Command("Std_LinkUnlink") +{ + sGroup = QT_TR_NOOP("Link"); + sMenuText = QT_TR_NOOP("Unlink"); + sToolTipText = QT_TR_NOOP("Strip on level of link"); + sWhatsThis = "Std_LinkUnlink"; + sStatusTip = sToolTipText; + eType = AlterDoc; + sPixmap = "Unlink"; +} + +bool StdCmdLinkUnlink::isActive() { + return linkConvertible(true); +} + +void StdCmdLinkUnlink::activated(int) { + linkConvert(true); +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +DEF_STD_CMD_A(StdCmdLinkImport) + +StdCmdLinkImport::StdCmdLinkImport() + : Command("Std_LinkImport") +{ + sGroup = QT_TR_NOOP("Link"); + sMenuText = QT_TR_NOOP("Import links"); + sToolTipText = QT_TR_NOOP("Import selected external link(s)"); + sWhatsThis = "Std_LinkImport"; + sStatusTip = sToolTipText; + eType = AlterDoc; + sPixmap = "LinkImport"; +} + +static std::map > getLinkImportSelections() +{ + std::map > objMap; + for(auto &sel : Selection().getCompleteSelection(false)) { + auto obj = sel.pObject->resolve(sel.SubName); + if(!obj || !obj->getNameInDocument()) + continue; + for(auto o : obj->getOutList()) { + if(o && o->getNameInDocument() && o->getDocument()!=obj->getDocument()) { + objMap[obj->getDocument()].push_back(obj); + break; + } + } + } + return objMap; +} + +bool StdCmdLinkImport::isActive() { + auto links = getLinkImportSelections(); + if(links.empty()) + return false; + for(auto &v : links) { + if(v.first->testStatus(App::Document::PartialDoc)) + return false; + } + return true; +} + +void StdCmdLinkImport::activated(int) { + Command::openCommand("Import links"); + try { + WaitCursor wc; + wc.setIgnoreEvents(WaitCursor::NoEvents); + for(auto &v : getLinkImportSelections()) { + auto doc = v.first; + // TODO: Is it possible to do this using interpreter? + for(auto obj : doc->importLinks(v.second)) + obj->Visibility.setValue(false); + } + Command::commitCommand(); + }catch (const Base::Exception& e) { + Command::abortCommand(); + QMessageBox::critical(getMainWindow(), QObject::tr("Failed to import links"), + QString::fromLatin1(e.what())); + e.ReportException(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +DEF_STD_CMD_A(StdCmdLinkImportAll) + +StdCmdLinkImportAll::StdCmdLinkImportAll() + : Command("Std_LinkImportAll") +{ + sGroup = QT_TR_NOOP("Link"); + sMenuText = QT_TR_NOOP("Import all links"); + sToolTipText = QT_TR_NOOP("Import all links of the active document"); + sWhatsThis = "Std_LinkImportAll"; + sStatusTip = sToolTipText; + eType = AlterDoc; + sPixmap = "LinkImportAll"; +} + +bool StdCmdLinkImportAll::isActive() { + auto doc = App::GetApplication().getActiveDocument(); + return doc && !doc->testStatus(App::Document::PartialDoc) && App::PropertyXLink::hasXLink(doc); +} + +void StdCmdLinkImportAll::activated(int) { + Command::openCommand("Import all links"); + try { + WaitCursor wc; + wc.setIgnoreEvents(WaitCursor::NoEvents); + auto doc = App::GetApplication().getActiveDocument(); + if(doc) { + for(auto obj : doc->importLinks()) + obj->Visibility.setValue(false); + } + Command::commitCommand(); + } catch (const Base::Exception& e) { + QMessageBox::critical(getMainWindow(), QObject::tr("Failed to import all links"), + QString::fromLatin1(e.what())); + Command::abortCommand(); + e.ReportException(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +DEF_STD_CMD_A(StdCmdLinkSelectLinked) + +StdCmdLinkSelectLinked::StdCmdLinkSelectLinked() + : Command("Std_LinkSelectLinked") +{ + sGroup = QT_TR_NOOP("Link"); + sMenuText = QT_TR_NOOP("Select linked object"); + sToolTipText = QT_TR_NOOP("Select the linked object"); + sWhatsThis = "Std_LinkSelectLinked"; + sStatusTip = sToolTipText; + eType = AlterSelection; + sPixmap = "LinkSelect"; + sAccel = "S, G"; +} + +static App::DocumentObject *getSelectedLink(bool finalLink, std::string *subname=0) { + const auto &sels = Selection().getSelection("*",0,true); + if(sels.empty()) + return 0; + auto sobj = sels[0].pObject->getSubObject(sels[0].SubName); + if(!sobj) + return 0; + auto vp = Base::freecad_dynamic_cast( + Application::Instance->getViewProvider(sobj)); + if(!vp) + return 0; + + auto linkedVp = vp->getLinkedViewProvider(subname,finalLink); + if(!linkedVp || linkedVp==vp) + return 0; + + if(finalLink && linkedVp == vp->getLinkedViewProvider()) + return 0; + + auto linked = linkedVp->getObject(); + if(!linked || !linked->getNameInDocument()) + return 0; + + if(subname && sels[0].pObject!=sobj && sels[0].SubName) { + bool found = false; + int pre_len=0; + std::size_t post_len=0; + std::string prefix; + std::string prefix2; + // An object can be claimed by multiple objects. Let's try select one + // that causes minimum jump in tree view, and prefer upper over lower + // hierarchy (because of less depth/complexity of tree expansion) + for(auto &v : linked->getParents()) { + if(v.first != sels[0].pObject) + continue; + + const char *sub = v.second.c_str(); + const char *dot = sub; + for(const char *s=sels[0].SubName; *s && *sub==*s; ++s,++sub) { + if(*sub == '.') + dot = sub; + } + found = true; + if(dot-v.second.c_str() > pre_len + || (dot-v.second.c_str()==pre_len + && v.second.size() v.second.size()) + prefix2 = v.second; + } + } + + if(found) { + linked = sels[0].pObject; + *subname = prefix.size()?prefix:prefix2 + *subname; + } + } + + return linked; +} + +bool StdCmdLinkSelectLinked::isActive() { + return getSelectedLink(false)!=0; +} + +void StdCmdLinkSelectLinked::activated(int) +{ + std::string subname; + auto linked = getSelectedLink(false,&subname); + if(!linked){ + FC_WARN("invalid selection"); + return; + } + Selection().selStackPush(); + Selection().clearCompleteSelection(); + if(subname.size()) { + Selection().addSelection(linked->getDocument()->getName(),linked->getNameInDocument(),subname.c_str()); + auto doc = Application::Instance->getDocument(linked->getDocument()); + if(doc) { + auto vp = dynamic_cast(Application::Instance->getViewProvider(linked)); + doc->setActiveView(vp); + } + } else { + for(auto tree : getMainWindow()->findChildren()) + tree->selectLinkedObject(linked); + } + Selection().selStackPush(); +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +DEF_STD_CMD_A(StdCmdLinkSelectLinkedFinal) + +StdCmdLinkSelectLinkedFinal::StdCmdLinkSelectLinkedFinal() + : Command("Std_LinkSelectLinkedFinal") +{ + sGroup = QT_TR_NOOP("Link"); + sMenuText = QT_TR_NOOP("Select the deepest linked object"); + sToolTipText = QT_TR_NOOP("Select the deepest linked object"); + sWhatsThis = "Std_LinkSelectLinkedFinal"; + sStatusTip = sToolTipText; + eType = AlterSelection; + sPixmap = "LinkSelectFinal"; + sAccel = "S, D"; +} + +bool StdCmdLinkSelectLinkedFinal::isActive() { + return getSelectedLink(true)!=0; +} + +void StdCmdLinkSelectLinkedFinal::activated(int) { + auto linked = getSelectedLink(true); + if(!linked){ + FC_WARN("invalid selection"); + return; + } + Selection().selStackPush(); + Selection().clearCompleteSelection(); + for(auto tree : getMainWindow()->findChildren()) + tree->selectLinkedObject(linked); + Selection().selStackPush(); +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +DEF_STD_CMD_A(StdCmdLinkSelectAllLinks) + +StdCmdLinkSelectAllLinks::StdCmdLinkSelectAllLinks() + : Command("Std_LinkSelectAllLinks") +{ + sGroup = QT_TR_NOOP("Link"); + sMenuText = QT_TR_NOOP("Select all links"); + sToolTipText = QT_TR_NOOP("Select all links to the current selected object"); + sWhatsThis = "Std_LinkSelectAllLinks"; + sStatusTip = sToolTipText; + eType = AlterSelection; + sPixmap = "LinkSelectAll"; +} + +bool StdCmdLinkSelectAllLinks::isActive() { + const auto &sels = Selection().getSelection("*",true,true); + if(sels.empty()) + return false; + return App::GetApplication().hasLinksTo(sels[0].pObject); +} + +void StdCmdLinkSelectAllLinks::activated(int) +{ + auto sels = Selection().getSelection("*",true,true); + if(sels.empty()) + return; + Selection().selStackPush(); + Selection().clearCompleteSelection(); + for(auto tree : getMainWindow()->findChildren()) + tree->selectAllLinks(sels[0].pObject); + Selection().selStackPush(); +} + +//=========================================================================== +// Instantiation +//=========================================================================== + + +namespace Gui { + +void CreateLinkCommands(void) +{ + CommandManager &rcCmdMgr = Application::Instance->commandManager(); + rcCmdMgr.addCommand(new StdCmdLinkSelectLinked()); + rcCmdMgr.addCommand(new StdCmdLinkSelectLinkedFinal()); + rcCmdMgr.addCommand(new StdCmdLinkSelectAllLinks()); + rcCmdMgr.addCommand(new StdCmdLinkMake()); + rcCmdMgr.addCommand(new StdCmdLinkMakeRelative()); + rcCmdMgr.addCommand(new StdCmdLinkMakeGroup()); + rcCmdMgr.addCommand(new StdCmdLinkReplace()); + rcCmdMgr.addCommand(new StdCmdLinkUnlink()); + rcCmdMgr.addCommand(new StdCmdLinkImport()); + rcCmdMgr.addCommand(new StdCmdLinkImportAll()); +} + +} // namespace Gui + diff --git a/src/Gui/LinkViewPy.xml b/src/Gui/LinkViewPy.xml new file mode 100644 index 0000000000..14c01bfb83 --- /dev/null +++ b/src/Gui/LinkViewPy.xml @@ -0,0 +1,165 @@ + + + + + + Helper class to link to a view object + + + + Reset the link view and clear the links + + + + + +setMaterial(Material): set the override material of the entire linked object + +setMaterial([Material,...]): set the materials for the elements of the link + array/group. + +setMaterial({Int:Material,...}): set the material for the elements of the + link array/group by index. + +If material is None, then the material is unset. If the material of an element +is unset, it defaults to the override material of the linked object, if there +is one + + + + + + +setType(type, sublink=True): set the link type. + +type=0: override transformation and visibility +type=1: override visibility +type=2: no override +type=-1: sub-object link with override visibility +type=-2: sub-object link with override transformation and visibility + +sublink: auto delegate to the sub-object references in the link, if there is + one and only one. + + + + + + +setTransform(matrix): set transformation of the linked object + +setTransform([matrix,...]): set transformation for the elements of the link + array/group + +setTransform({index:matrix,...}): set transformation for elements of the link + array/group by index + + + + + + +setChildren([obj...],vis=[],type=0) +Group a list of children objects. Note, this mode of operation is incompatible +with link array. Calling this function will deactivate link array. And calling +setSize() will reset all linked children. + +vis: initial visibility status of the children + +type: children linking type, + 0: override transformation and visibility, + 1: override visibility, + 2: override none. + + + + + + +setLink(object): Set the link + +setLink(object, subname): Set the link with a sub-object reference + +setLink(object, [subname,...]): Set the link with a list of sub object references + +object: The linked document object or its view object + +subname: a string or tuple/list of strings sub-name references to sub object + or sub elements (e.g. Face1, Edge2) belonging to the linked object. + The sub-name must end with a '.' if it is referencing an sub-object, + or else it is considered a sub-element reference. + + + + + + +getDetailPath(element): get the 3d path an detail of an element. + +Return a tuple(path,detail) for the coin3D SoPath and SoDetail of the element + + + + + + getElementPicked(pickPoint): get the element under a 3d pick point. + + + + + getBoundBox(vobj=None): get the bounding box. + + + + + The linked view object + + + + + + The sub-object reference of the link + + + + + + A pivy node holding the cloned representation of the linked view object + + + + + + The owner view object of this link handle + + + + + + Get/set the child element visibility + + + + + + Set the element size to create an array of linked object + + + + + + Get children view objects + + + + diff --git a/src/Gui/LinkViewPyImp.cpp b/src/Gui/LinkViewPyImp.cpp new file mode 100644 index 0000000000..6a8eb6aabc --- /dev/null +++ b/src/Gui/LinkViewPyImp.cpp @@ -0,0 +1,403 @@ +/**************************************************************************** + * 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 +#endif + +#include +#include +#include +#include +#include + +#include "ViewProviderDocumentObjectPy.h" +#include "ViewProviderLink.h" +#include "WidgetFactory.h" + +#include "LinkViewPy.h" +#include "LinkViewPy.cpp" + +using namespace Gui; + +PyObject *LinkViewPy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Python wrapper +{ + return new LinkViewPy(new LinkView); +} + +int LinkViewPy::PyInit(PyObject* /*args*/, PyObject* /*kwd*/) +{ + return 0; +} + + +// returns a string which represent the object e.g. when printed in python +std::string LinkViewPy::representation(void) const +{ + return ""; +} + +PyObject* LinkViewPy::reset(PyObject *args) { + if (!PyArg_ParseTuple(args, "")) + return 0; + + PY_TRY { + auto lv = getLinkViewPtr(); + lv->setSize(0); + lv->setLink(0); + Py_Return; + } PY_CATCH; +} + +PyObject* LinkViewPy::setMaterial(PyObject *args) { + PyObject *pyObj; + if (!PyArg_ParseTuple(args, "O", &pyObj)) + return 0; + + PY_TRY { + auto lv = getLinkViewPtr(); + if(pyObj == Py_None) { + lv->setMaterial(-1,0); + Py_Return; + } + if(PyObject_TypeCheck(&pyObj,&App::MaterialPy::Type)) { + lv->setMaterial(-1,static_cast(pyObj)->getMaterialPtr()); + Py_Return; + } + if(PyDict_Check(pyObj)) { + PyObject *key, *value; + Py_ssize_t pos = 0; + std::map materials; + while(PyDict_Next(pyObj, &pos, &key, &value)) { + Py::Int idx(key); + if(value == Py_None) + materials[(int)idx] = 0; + else if(!PyObject_TypeCheck(&value,&App::MaterialPy::Type)) { + PyErr_SetString(PyExc_TypeError, "exepcting a type of material"); + return 0; + }else + materials[(int)idx] = static_cast(value)->getMaterialPtr(); + } + for(auto &v : materials) + lv->setMaterial(v.first,v.second); + Py_Return; + } + if(PySequence_Check(pyObj)) { + Py::Sequence seq(pyObj); + std::vector materials; + materials.resize(seq.size(),0); + for(size_t i=0;i(item)->getMaterialPtr(); + } + for(size_t i=0;isetMaterial(i,materials[i]); + Py_Return; + } + + PyErr_SetString(PyExc_TypeError, "exepcting a type of Material, [Material,...] or {Int:Material,}"); + return 0; + } PY_CATCH; +} + +PyObject* LinkViewPy::setTransform(PyObject *args) { + PyObject *pyObj; + if (!PyArg_ParseTuple(args, "O", &pyObj)) + return 0; + + PY_TRY { + auto lv = getLinkViewPtr(); + if(PyObject_TypeCheck(pyObj,&Base::MatrixPy::Type)) { + lv->setTransform(-1,*static_cast(pyObj)->getMatrixPtr()); + Py_Return; + } + if(PyDict_Check(pyObj)) { + PyObject *key, *value; + Py_ssize_t pos = 0; + std::map mat; + while(PyDict_Next(pyObj, &pos, &key, &value)) { + Py::Int idx(key); + if(!PyObject_TypeCheck(&value,&Base::MatrixPy::Type)) { + PyErr_SetString(PyExc_TypeError, "exepcting a type of Matrix"); + return 0; + }else + mat[(int)idx] = static_cast(value)->getMatrixPtr(); + } + for(auto &v : mat) + lv->setTransform(v.first,*v.second); + Py_Return; + } + if(PySequence_Check(pyObj)) { + Py::Sequence seq(pyObj); + std::vector mat; + mat.resize(seq.size(),0); + for(size_t i=0;i(item)->getMatrixPtr(); + } + for(size_t i=0;isetTransform(i,*mat[i]); + Py_Return; + } + + PyErr_SetString(PyExc_TypeError, "exepcting a type of Matrix, [Matrix,...] or {Int:Matrix,...}"); + return 0; + } PY_CATCH; +} + +PyObject* LinkViewPy::setType(PyObject *args) { + short type; + PyObject *sublink = Py_True; + if (!PyArg_ParseTuple(args, "h|O", &type,&sublink)) + return 0; + + PY_TRY{ + getLinkViewPtr()->setNodeType((LinkView::SnapshotType)type,PyObject_IsTrue(sublink)); + Py_Return; + } PY_CATCH; +} + +PyObject* LinkViewPy::setChildren(PyObject *args) { + PyObject *pyObj; + PyObject *pyVis = Py_None; + short type=0; + if (!PyArg_ParseTuple(args, "O|Os",&pyObj,&pyVis,&type)) + return 0; + + PY_TRY{ + App::PropertyBoolList vis; + App::PropertyLinkList links; + if(pyObj!=Py_None) + links.setPyObject(pyObj); + if(pyVis!=Py_None) + vis.setPyObject(pyVis); + getLinkViewPtr()->setChildren(links.getValue(),vis.getValue(),(LinkView::SnapshotType)type); + Py_Return; + } PY_CATCH; +} + +PyObject* LinkViewPy::setLink(PyObject *args) +{ + PyObject *pyObj; + PyObject *pySubName = Py_None; + if (!PyArg_ParseTuple(args, "O|O",&pyObj,&pySubName)) + return 0; + + PY_TRY { + ViewProviderDocumentObject *vpd = 0; + App::DocumentObject *obj = 0; + if(pyObj!=Py_None) { + if(PyObject_TypeCheck(pyObj,&App::DocumentObjectPy::Type)) + obj = static_cast(pyObj)->getDocumentObjectPtr(); + else if(PyObject_TypeCheck(pyObj,&ViewProviderDocumentObjectPy::Type)) + vpd = static_cast(pyObj)->getViewProviderDocumentObjectPtr(); + else { + PyErr_SetString(PyExc_TypeError, + "exepcting a type of DocumentObject or ViewProviderDocumentObject"); + return 0; + } + } + + // Too lazy to parse the argument... + App::PropertyStringList prop; + if(pySubName!=Py_None) + prop.setPyObject(pySubName); + + if(obj) + getLinkViewPtr()->setLink(obj,prop.getValue()); + else + getLinkViewPtr()->setLinkViewObject(vpd,prop.getValue()); + Py_Return; + } PY_CATCH; +} + +Py::Object LinkViewPy::getOwner() const { + auto owner = getLinkViewPtr()->getOwner(); + if(!owner) return Py::Object(); + return Py::Object(owner->getPyObject(),true); +} + +void LinkViewPy::setOwner(Py::Object owner) { + ViewProviderDocumentObject *vp = 0; + if(!owner.isNone()) { + if(!PyObject_TypeCheck(owner.ptr(),&ViewProviderDocumentObjectPy::Type)) + throw Py::TypeError("exepcting the owner to be of ViewProviderDocumentObject"); + vp = static_cast( + owner.ptr())->getViewProviderDocumentObjectPtr(); + } + getLinkViewPtr()->setOwner(vp); +} + +Py::Object LinkViewPy::getLinkedView() const { + auto linked = getLinkViewPtr()->getLinkedView(); + if(!linked) + return Py::Object(); + return Py::Object(linked->getPyObject(),true); +} + +Py::Object LinkViewPy::getSubNames() const { + const auto &subs = getLinkViewPtr()->getSubNames(); + if(subs.empty()) + return Py::Object(); + Py::Tuple ret(subs.size()); + int i=0; + for(auto &s : subs) + ret.setItem(i++,Py::String(s.c_str())); + return ret; +} + +PyObject* LinkViewPy::getElementPicked(PyObject* args) +{ + PyObject *obj; + if (!PyArg_ParseTuple(args, "O",&obj)) + return NULL; + void *ptr = 0; + Base::Interpreter().convertSWIGPointerObj("pivy.coin", "SoPickedPoint *", obj, &ptr, 0); + SoPickedPoint *pp = reinterpret_cast(ptr); + if(!pp) + throw Py::TypeError("type must be of coin.SoPickedPoint"); + PY_TRY{ + std::string name; + if(!getLinkViewPtr()->linkGetElementPicked(pp,name)) + Py_Return; + return Py::new_reference_to(Py::String(name)); + }PY_CATCH +} + +PyObject* LinkViewPy::getDetailPath(PyObject* args) +{ + const char *sub; + PyObject *path; + if (!PyArg_ParseTuple(args, "sO",&sub,&path)) + return NULL; + void *ptr = 0; + Base::Interpreter().convertSWIGPointerObj("pivy.coin", "SoPath *", path, &ptr, 0); + SoPath *pPath = reinterpret_cast(ptr); + if(!pPath) + throw Py::TypeError("type must be of coin.SoPath"); + PY_TRY{ + SoDetail *det = 0; + getLinkViewPtr()->linkGetDetailPath(sub,static_cast(pPath),det); + if(!det) + Py_Return; + return Base::Interpreter().createSWIGPointerObj("pivy.coin", "SoDetail *", (void*)det, 0); + }PY_CATCH +} + +PyObject* LinkViewPy::getBoundBox(PyObject* args) { + PyObject *vobj = Py_None; + if (!PyArg_ParseTuple(args, "O",&vobj)) + return 0; + ViewProviderDocumentObject *vpd = 0; + if(vobj!=Py_None) { + if(!PyObject_TypeCheck(vobj,&ViewProviderDocumentObjectPy::Type)) { + PyErr_SetString(PyExc_TypeError, "exepcting a type of ViewProviderDocumentObject"); + return 0; + } + vpd = static_cast(vobj)->getViewProviderDocumentObjectPtr(); + } + PY_TRY { + auto bbox = getLinkViewPtr()->getBoundBox(vpd); + Py::Object ret(new Base::BoundBoxPy(new Base::BoundBox3d(bbox))); + return Py::new_reference_to(ret); + }PY_CATCH +} + +PyObject *LinkViewPy::getCustomAttributes(const char*) const +{ + return 0; +} + +int LinkViewPy::setCustomAttributes(const char*, PyObject*) +{ + return 0; +} + +Py::Object LinkViewPy::getRootNode(void) const +{ + try { + SoNode* node = getLinkViewPtr()->getLinkRoot(); + PyObject* Ptr = Base::Interpreter().createSWIGPointerObj("pivy.coin","SoSeparator *", node, 1); + node->ref(); + return Py::Object(Ptr, true); + } + catch (const Base::Exception& e) { + throw Py::RuntimeError(e.what()); + } +} + +Py::Object LinkViewPy::getVisibilities() const { + auto linked = getLinkViewPtr(); + if(!linked->getSize()) + return Py::Object(); + Py::Tuple ret(linked->getSize()); + for(int i=0;igetSize();++i) + ret.setItem(i,Py::Boolean(linked->isElementVisible(i))); + return ret; +} + +void LinkViewPy::setVisibilities(Py::Object value) { + App::PropertyBoolList v; + if(!value.isNone()) + v.setPyObject(value.ptr()); + + auto linked = getLinkViewPtr(); + const auto &vis = v.getValue(); + for(int i=0;igetSize();++i) + linked->setElementVisible(i,i>=(int)vis.size()||vis[i]); +} + +PyObject* LinkViewPy::getChildren(PyObject *args) { + if (!PyArg_ParseTuple(args, "")) + return 0; + auto children = getLinkViewPtr()->getChildren(); + if(children.empty()) + Py_Return; + Py::Tuple ret(children.size()); + int i=0; + for(auto vp : children) + ret.setItem(i++,Py::Object(vp->getPyObject(),true)); + return Py::new_reference_to(ret); +} + +Py::Int LinkViewPy::getCount() const { + return Py::Int(getLinkViewPtr()->getSize()); +} + +void LinkViewPy::setCount(Py::Int count) { + try { + getLinkViewPtr()->setSize((int)count); + } catch (const Base::Exception& e) { + throw Py::RuntimeError(e.what()); + } +} + diff --git a/src/Gui/TaskCSysDragger.cpp b/src/Gui/TaskCSysDragger.cpp index fb8bb4b068..30502ae698 100644 --- a/src/Gui/TaskCSysDragger.cpp +++ b/src/Gui/TaskCSysDragger.cpp @@ -57,7 +57,7 @@ static double degreesToRadains(const double °reesIn) static double lastTranslationIncrement = 1.0; static double lastRotationIncrement = degreesToRadains(15.0); -TaskCSysDragger::TaskCSysDragger(Gui::ViewProviderDragger* vpObjectIn, Gui::SoFCCSysDragger* draggerIn) : +TaskCSysDragger::TaskCSysDragger(Gui::ViewProviderDocumentObject* vpObjectIn, Gui::SoFCCSysDragger* draggerIn) : dragger(draggerIn) { assert(vpObjectIn); diff --git a/src/Gui/TaskCSysDragger.h b/src/Gui/TaskCSysDragger.h index ec07e02d71..3a3e3bdbc6 100644 --- a/src/Gui/TaskCSysDragger.h +++ b/src/Gui/TaskCSysDragger.h @@ -37,7 +37,7 @@ namespace Gui { Q_OBJECT public: - TaskCSysDragger(ViewProviderDragger *vpObjectIn, SoFCCSysDragger *draggerIn); + TaskCSysDragger(ViewProviderDocumentObject *vpObjectIn, SoFCCSysDragger *draggerIn); virtual ~TaskCSysDragger() override; virtual QDialogButtonBox::StandardButtons getStandardButtons() const override { return QDialogButtonBox::Ok;} diff --git a/src/Gui/TaskElementColors.cpp b/src/Gui/TaskElementColors.cpp new file mode 100644 index 0000000000..b61b60724b --- /dev/null +++ b/src/Gui/TaskElementColors.cpp @@ -0,0 +1,533 @@ +/**************************************************************************** + * Copyright (c) 2018 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 +#endif + +#include + +#include +#include + +#include "ui_TaskElementColors.h" +#include "TaskElementColors.h" +#include "ViewProviderLink.h" + +#include +#include + +#include "Application.h" +#include "Control.h" +#include "Document.h" +#include "MainWindow.h" +#include "Selection.h" +#include "BitmapFactory.h" +#include "Command.h" + +#include +#include + +FC_LOG_LEVEL_INIT("Gui",true,true) + +using namespace Gui; + +class ElementColors::Private: public Gui::SelectionGate +{ +public: + typedef boost::signals2::connection Connection; + std::unique_ptr ui; + ViewProviderDocumentObject *vp; + ViewProviderDocumentObject *vpParent; + Document *vpDoc; + std::map elements; + std::vector items; + std::string hiddenSub; + Connection connectDelDoc; + Connection connectDelObj; + QPixmap px; + bool busy; + long onTopMode; + bool touched; + + std::string editDoc; + std::string editObj; + std::string editSub; + std::string editElement; + + Private(ViewProviderDocumentObject* vp, const char *element="") + : ui(new Ui_TaskElementColors()), vp(vp),editElement(element) + { + vpDoc = vp->getDocument(); + vpParent = vp; + auto doc = Application::Instance->editDocument(); + if(doc) { + auto editVp = doc->getInEdit(&vpParent,&editSub); + if(editVp == vp) { + auto obj = vpParent->getObject(); + editDoc = obj->getDocument()->getName(); + editObj = obj->getNameInDocument(); + editSub = Data::ComplexGeoData::noElementName(editSub.c_str()); + } + } + if(editDoc.empty()) { + vpParent = vp; + editDoc = vp->getObject()->getDocument()->getName(); + editObj = vp->getObject()->getNameInDocument(); + editSub.clear(); + } + onTopMode = vpParent->OnTopWhenSelected.getValue(); + busy = false; + touched = false; + int w = QApplication::style()->standardPixmap(QStyle::SP_DirClosedIcon).width(); + px = QPixmap(w,w); + } + + ~Private() { + vpParent->OnTopWhenSelected.setValue(onTopMode); + } + + bool allow(App::Document *doc, App::DocumentObject *obj, const char *subname) { + if(editDoc!=doc->getName() || + editObj!=obj->getNameInDocument() || + !boost::starts_with(subname,editSub)) + return false; + if(editElement.empty()) + return true; + const char *dot = strrchr(subname,'.'); + if(!dot) + dot = subname; + else + ++dot; + return *dot==0 || boost::starts_with(dot,editElement); + } + + void populate() { + int i=0; + for(auto &v : vp->getElementColors()) + addItem(i++,v.first.c_str()); + apply(); + } + + void addItem(int i,const char *sub, bool push=false) { + auto itE = elements.find(sub); + if(i<0 && itE!=elements.end()) { + if(push && !ViewProvider::hasHiddenMarker(sub)) + items.push_back(itE->second); + return; + } + + const char *marker = ViewProvider::hasHiddenMarker(sub); + if(marker) { + auto icon = BitmapFactory().pixmap("Invisible"); + QListWidgetItem* item = new QListWidgetItem(icon, + QString::fromLatin1(std::string(sub,marker-sub).c_str()), ui->elementList); + item->setData(Qt::UserRole,QColor()); + item->setData(Qt::UserRole+1,QString::fromLatin1(sub)); + elements.emplace(sub,item); + return; + } + + for(auto &v : vp->getElementColors(sub)) { + auto it = elements.find(v.first.c_str()); + if(it!=elements.end()) { + if(push) + items.push_back(it->second); + continue; + } + auto color = v.second; + QColor c; + c.setRgbF(color.r,color.g,color.b,1.0-color.a); + px.fill(c); + QListWidgetItem* item = new QListWidgetItem(QIcon(px), + QString::fromLatin1(Data::ComplexGeoData::oldElementName(v.first.c_str()).c_str()), + ui->elementList); + item->setData(Qt::UserRole,c); + item->setData(Qt::UserRole+1,QString::fromLatin1(v.first.c_str())); + if(push) + items.push_back(item); + elements.emplace(v.first,item); + } + } + + void apply() { + std::map info; + int count = ui->elementList->count(); + for(int i=0;ielementList->item(i); + auto color = item->data(Qt::UserRole).value(); + std::string sub = qPrintable(item->data(Qt::UserRole+1).value()); + info.emplace(qPrintable(item->data(Qt::UserRole+1).value()), + App::Color(color.redF(),color.greenF(),color.blueF(),1.0-color.alphaF())); + } + if(!App::GetApplication().getActiveTransaction()) + App::GetApplication().setActiveTransaction("Set colors"); + vp->setElementColors(info); + touched = true; + Selection().clearSelection(); + } + + void reset() { + touched = false; + App::GetApplication().closeActiveTransaction(true); + Selection().clearSelection(); + } + + void accept() { + if(touched && ui->recompute->isChecked()) { + auto obj = vp->getObject(); + obj->touch(); + obj->getDocument()->recompute(obj->getInListRecursive()); + touched = false; + } + App::GetApplication().closeActiveTransaction(); + } + + void removeAll() { + if(elements.size()) { + hiddenSub.clear(); + ui->elementList->clear(); + elements.clear(); + apply(); + } + } + + void removeItems() { + for(auto item : ui->elementList->selectedItems()) { + std::string sub = qPrintable(item->data(Qt::UserRole+1).value()); + if(sub == hiddenSub) + hiddenSub.clear(); + elements.erase(sub); + delete item; + } + apply(); + } + + void editItem(QWidget *parent, QListWidgetItem *item) { + std::string sub = qPrintable(item->data(Qt::UserRole+1).value()); + if(ViewProvider::hasHiddenMarker(sub.c_str())) + return; + auto color = item->data(Qt::UserRole).value(); + QColorDialog cd(color, parent); + cd.setOption(QColorDialog::ShowAlphaChannel); + if (cd.exec()!=QDialog::Accepted || color==cd.selectedColor()) + return; + color = cd.selectedColor(); + item->setData(Qt::UserRole,color); + px.fill(color); + item->setIcon(QIcon(px)); + apply(); + } + + void onSelectionChanged(const SelectionChanges &msg) { + // no object selected in the combobox or no sub-element was selected + if (busy) + return; + busy = true; + switch(msg.Type) { + case SelectionChanges::ClrSelection: + ui->elementList->clearSelection(); + break; + case SelectionChanges::AddSelection: + case SelectionChanges::RmvSelection: + if(msg.pDocName && msg.pObjectName && msg.pSubName && msg.pSubName[0]) { + if(editDoc == msg.pDocName && + editObj == msg.pObjectName && + boost::starts_with(msg.pSubName,editSub)) + { + for(auto item : ui->elementList->findItems( + QString::fromLatin1(msg.pSubName-editSub.size()),0)) + item->setSelected(msg.Type==SelectionChanges::AddSelection); + } + } + default: + break; + } + busy = false; + } + + void onSelectionChanged() { + if(busy) return; + busy = true; + std::map sels; + for(auto &sel : Selection().getSelectionEx( + editDoc.c_str(),App::DocumentObject::getClassTypeId(),0)) + { + if(sel.getFeatName()!=editObj) continue; + for(auto &sub : sel.getSubNames()) { + if(boost::starts_with(sub,editSub)) + sels[sub.c_str()+editSub.size()] = 1; + } + break; + } + for(auto item : ui->elementList->selectedItems()) { + std::string name(qPrintable(item->data(Qt::UserRole+1).value())); + if(ViewProvider::hasHiddenMarker(name.c_str())) + continue; + auto &v = sels[name]; + if(!v) + Selection().addSelection(editDoc.c_str(), + editObj.c_str(), (editSub+name).c_str()); + v = 2; + } + for(auto &v : sels) { + if(v.second!=2) { + Selection().rmvSelection(editDoc.c_str(), + editObj.c_str(), (editSub+v.first).c_str()); + } + } + busy = false; + } +}; + +/* TRANSLATOR Gui::TaskElementColors */ + +ElementColors::ElementColors(ViewProviderDocumentObject* vp, bool noHide) + :d(new Private(vp)) +{ + d->ui->setupUi(this); + d->ui->objectLabel->setText(QString::fromUtf8(vp->getObject()->Label.getValue())); + d->ui->elementList->setMouseTracking(true); // needed for itemEntered() to work + + if(noHide) + d->ui->hideSelection->setVisible(false); + + ParameterGrp::handle hPart = App::GetApplication().GetParameterGroupByPath + ("User parameter:BaseApp/Preferences/View"); + d->ui->recompute->setChecked(hPart->GetBool("ColorRecompute",true)); + d->ui->onTop->setChecked(hPart->GetBool("ColorOnTop",true)); + if(d->ui->onTop->isChecked()) + d->vpParent->OnTopWhenSelected.setValue(3); + + Selection().addSelectionGate(d,0); + + d->connectDelDoc = Application::Instance->signalDeleteDocument.connect(boost::bind + (&ElementColors::slotDeleteDocument, this, _1)); + d->connectDelObj = Application::Instance->signalDeletedObject.connect(boost::bind + (&ElementColors::slotDeleteObject, this, _1)); + + d->populate(); +} + +ElementColors::~ElementColors() +{ + d->connectDelDoc.disconnect(); + d->connectDelObj.disconnect(); + Selection().rmvSelectionGate(); +} + +void ElementColors::on_recompute_clicked(bool checked) { + ParameterGrp::handle hPart = App::GetApplication().GetParameterGroupByPath + ("User parameter:BaseApp/Preferences/View"); + hPart->SetBool("ColorRecompute",checked); +} + +void ElementColors::on_onTop_clicked(bool checked) { + ParameterGrp::handle hPart = App::GetApplication().GetParameterGroupByPath + ("User parameter:BaseApp/Preferences/View"); + hPart->SetBool("ColorOnTop",checked); + d->vpParent->OnTopWhenSelected.setValue(checked?3:d->onTopMode); +} + +void ElementColors::slotDeleteDocument(const Document& Doc) +{ + if (d->vpDoc==&Doc || d->editDoc==Doc.getDocument()->getName()) + Control().closeDialog(); +} + +void ElementColors::slotDeleteObject(const ViewProvider& obj) +{ + if (d->vp==&obj) + Control().closeDialog(); +} + +void ElementColors::on_removeSelection_clicked() +{ + d->removeItems(); +} + +void ElementColors::on_boxSelect_clicked() +{ + auto cmd = Application::Instance->commandManager().getCommandByName("Std_BoxElementSelection"); + if(cmd) + cmd->invoke(0); +} + +void ElementColors::on_hideSelection_clicked() { + auto sels = Selection().getSelectionEx(d->editDoc.c_str(),App::DocumentObject::getClassTypeId(),0); + for(auto &sel : sels) { + if(d->editObj!=sel.getFeatName()) + continue; + const auto &subs = sel.getSubNames(); + if(subs.size()) { + for(auto &sub : subs) { + if(boost::starts_with(sub,d->editSub)) { + auto name = Data::ComplexGeoData::noElementName(sub.c_str()+d->editSub.size()); + name += ViewProvider::hiddenMarker(); + d->addItem(-1,name.c_str()); + } + } + d->apply(); + } + return; + } +} + +void ElementColors::on_addSelection_clicked() +{ + auto sels = Selection().getSelectionEx(d->editDoc.c_str(),App::DocumentObject::getClassTypeId(),0); + d->items.clear(); + if(sels.empty()) + d->addItem(-1,"Face",true); + else { + for(auto &sel : sels) { + if(d->editObj!=sel.getFeatName()) + continue; + const auto &subs = sel.getSubNames(); + if(subs.empty()) { + d->addItem(-1,"Face",true); + break; + } + for(auto &sub : subs) { + if(boost::starts_with(sub,d->editSub)) + d->addItem(-1,sub.c_str()+d->editSub.size(),true); + } + break; + } + } + if(d->items.size()) { + auto color = d->items.front()->data(Qt::UserRole).value(); + QColorDialog cd(color, this); + cd.setOption(QColorDialog::ShowAlphaChannel); + if (cd.exec()!=QDialog::Accepted) + return; + color = cd.selectedColor(); + for(auto item : d->items) { + item->setData(Qt::UserRole,color); + d->px.fill(color); + item->setIcon(QIcon(d->px)); + } + d->apply(); + } +} + +void ElementColors::on_removeAll_clicked() +{ + d->removeAll(); +} + +bool ElementColors::accept() +{ + d->accept(); + Application::Instance->setEditDocument(0); + return true; +} + +bool ElementColors::reject() +{ + d->reset(); + Application::Instance->setEditDocument(0); + return true; +} + +void ElementColors::changeEvent(QEvent *e) +{ + QWidget::changeEvent(e); + if (e->type() == QEvent::LanguageChange) { + d->ui->retranslateUi(this); + } +} + +void ElementColors::leaveEvent(QEvent *e) { + QWidget::leaveEvent(e); + Selection().rmvPreselect(); + if(d->hiddenSub.size()) { + d->vp->partialRender({d->hiddenSub},false); + d->hiddenSub.clear(); + } +} + +void ElementColors::on_elementList_itemEntered(QListWidgetItem *item) { + std::string name(qPrintable(item->data(Qt::UserRole+1).value())); + if(d->hiddenSub.size()) { + d->vp->partialRender({d->hiddenSub},false); + d->hiddenSub.clear(); + } + if(ViewProvider::hasHiddenMarker(name.c_str())) { + d->hiddenSub = name; + d->vp->partialRender({name},true); + name.resize(name.size()-ViewProvider::hiddenMarker().size()); + } + Selection().setPreselect(d->editDoc.c_str(), + d->editObj.c_str(), (d->editSub+name).c_str(),0,0,0, + d->ui->onTop->isChecked()?2:1); +} + +void ElementColors::on_elementList_itemSelectionChanged() { + d->onSelectionChanged(); +} + +void ElementColors::onSelectionChanged(const SelectionChanges& msg) +{ + d->onSelectionChanged(msg); +} + +void ElementColors::on_elementList_itemDoubleClicked(QListWidgetItem *item) { + d->editItem(this,item); +} + +/* TRANSLATOR Gui::TaskElementColors */ + +TaskElementColors::TaskElementColors(ViewProviderDocumentObject* vp, bool noHide) +{ + widget = new ElementColors(vp,noHide); + taskbox = new TaskView::TaskBox( + QPixmap(), widget->windowTitle(), true, 0); + taskbox->groupLayout()->addWidget(widget); + Content.push_back(taskbox); +} + +TaskElementColors::~TaskElementColors() +{ +} + +void TaskElementColors::open() +{ +} + +void TaskElementColors::clicked(int) +{ +} + +bool TaskElementColors::accept() +{ + return widget->accept(); +} + +bool TaskElementColors::reject() +{ + return widget->reject(); +} + +#include "moc_TaskElementColors.cpp" diff --git a/src/Gui/TaskElementColors.h b/src/Gui/TaskElementColors.h new file mode 100644 index 0000000000..f672dcd90c --- /dev/null +++ b/src/Gui/TaskElementColors.h @@ -0,0 +1,93 @@ +/**************************************************************************** + * Copyright (c) 2018 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 * + * * + ****************************************************************************/ + +#ifndef GUI_TASKELEMENTCOLORS_H +#define GUI_TASKELEMENTCOLORS_H + +#include +#include "TaskView/TaskView.h" +#include "TaskView/TaskDialog.h" + +namespace Gui { +class Document; +class ViewProvider; +class ViewProviderDocumentObject; + +class GuiExport ElementColors : public QWidget, public SelectionObserver +{ + Q_OBJECT + +public: + ElementColors(ViewProviderDocumentObject* vp, bool noHide=false); + ~ElementColors(); + + bool accept(); + bool reject(); + +private Q_SLOTS: + void on_removeSelection_clicked(); + void on_addSelection_clicked(); + void on_removeAll_clicked(); + void on_elementList_itemDoubleClicked(QListWidgetItem *item); + void on_elementList_itemSelectionChanged(); + void on_elementList_itemEntered(QListWidgetItem *item); + void on_recompute_clicked(bool checked); + void on_onTop_clicked(bool checked); + void on_hideSelection_clicked(); + void on_boxSelect_clicked(); + +protected: + void onSelectionChanged(const SelectionChanges& msg); + void changeEvent(QEvent *e); + void leaveEvent(QEvent *); + void slotDeleteDocument(const Document&); + void slotDeleteObject(const ViewProvider&); +private: + class Private; + Private *d; +}; + +class GuiExport TaskElementColors : public TaskView::TaskDialog +{ + Q_OBJECT + +public: + TaskElementColors(ViewProviderDocumentObject* vp, bool noHide=false); + ~TaskElementColors(); + +public: + void open(); + bool accept(); + bool reject(); + void clicked(int); + + QDialogButtonBox::StandardButtons getStandardButtons() const + { return QDialogButtonBox::Ok|QDialogButtonBox::Cancel; } + +private: + ElementColors* widget; + TaskView::TaskBox* taskbox; +}; + +} //namespace Gui + +#endif // GUI_TASKELEMENTCOLORS_H diff --git a/src/Gui/TaskElementColors.ui b/src/Gui/TaskElementColors.ui new file mode 100644 index 0000000000..a2810bd741 --- /dev/null +++ b/src/Gui/TaskElementColors.ui @@ -0,0 +1,92 @@ + + + Gui::TaskElementColors + + + + 0 + 0 + 483 + 406 + + + + Set element color + + + + + + + + TextLabel + + + + + + + QAbstractItemView::ExtendedSelection + + + + + + + + + Recompute after commit + + + + + + + + + Remove + + + + + + + Edit + + + + + + + Remove all + + + + + + + Hide + + + + + + + Box select + + + + + + + + + On-top when selected + + + + + + + + diff --git a/src/Gui/ViewProviderLink.cpp b/src/Gui/ViewProviderLink.cpp new file mode 100644 index 0000000000..cd6821097d --- /dev/null +++ b/src/Gui/ViewProviderLink.cpp @@ -0,0 +1,2976 @@ +/**************************************************************************** + * 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; + + 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(); + } + + 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); + } + + ~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(); + 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(); + + if(type!=LinkView::SnapshotTransform) + pcSnapshot->addChild(pcLinked->getTransformNode()); + + for(int i=0,count=root->getNumChildren();igetChild(i); + if(node==pcLinked->getTransformNode()) + 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 futher 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 colum 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->_LinkRecomputed.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->_LinkRecomputed) { + 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->_LinkRecomputed); + + 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 intension + // here. Whether he wants to, say transform the link itself or the linked + // object. Use a mask here will allow forwarding those editing mode that + // 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 actually geometry, + // we use an invisible cube sized by the bound 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; +} diff --git a/src/Gui/ViewProviderLink.h b/src/Gui/ViewProviderLink.h new file mode 100644 index 0000000000..9edc486421 --- /dev/null +++ b/src/Gui/ViewProviderLink.h @@ -0,0 +1,339 @@ +/**************************************************************************** + * 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 * + * * + ****************************************************************************/ + + +#ifndef GUI_VIEWPROVIDER_LINK_H +#define GUI_VIEWPROVIDER_LINK_H + +#include +#include +#include +#include "SoFCUnifiedSelection.h" +#include "ViewProviderPythonFeature.h" +#include "ViewProviderDocumentObject.h" +#include "ViewProviderExtension.h" + +class SoBase; +class SoDragger; +class SoMaterialBinding; + +namespace Gui { + +class LinkInfo; +typedef boost::intrusive_ptr LinkInfoPtr; + +class GuiExport ViewProviderLinkObserver: public ViewProviderExtension { + EXTENSION_TYPESYSTEM_HEADER_WITH_OVERRIDE(); +public: + ViewProviderLinkObserver(); + virtual ~ViewProviderLinkObserver(); + void extensionReattach(App::DocumentObject *) override; + void extensionBeforeDelete() override; + void extensionOnChanged(const App::Property *) override; + void extensionUpdateData(const App::Property*) override; + void extensionFinishRestoring() override; + bool extensionCanDragObject(App::DocumentObject*) const override { return false; } + bool extensionCanDropObject(App::DocumentObject*) const override { return false; } + void extensionModeSwitchChange(void) override; + + bool isLinkVisible() const; + void setLinkVisible(bool); + + LinkInfoPtr linkInfo; +}; + +class GuiExport LinkOwner { +public: + virtual void unlink(LinkInfoPtr) {} + virtual void onLinkedIconChange(LinkInfoPtr) {} + virtual void onLinkedUpdateData(LinkInfoPtr,const App::Property *) {} +protected: + virtual ~LinkOwner() {} +}; + +class GuiExport LinkView : public Base::BaseClass, public LinkOwner { + TYPESYSTEM_HEADER(); +public: + + LinkView(); + ~LinkView(); + LinkView &operator=(const LinkView&) = delete; + LinkView(const LinkView&) = delete; + + virtual PyObject *getPyObject(void); + + virtual void unlink(LinkInfoPtr) override; + virtual void onLinkedIconChange(LinkInfoPtr) override; + virtual void onLinkedUpdateData(LinkInfoPtr, const App::Property *) override; + + bool isLinked() const; + + SoFCSelectionRoot *getLinkRoot() const {return pcLinkRoot;} + + QIcon getLinkedIcon(QPixmap overlay) const; + + void updateLink(); + + void setLink(App::DocumentObject *obj, + const std::vector &subs = std::vector()); + + void setLinkViewObject(ViewProviderDocumentObject *vpd, + const std::vector &subs = std::vector()); + + std::vector getChildren() const; + + void setMaterial(int index, const App::Material *material); + void setDrawStyle(int linePattern, double lineWidth=0, double pointSize=0); + void setTransform(int index, const Base::Matrix4D &mat); + void renderDoubleSide(bool); + void setSize(int size); + + int getSize() const { return nodeArray.size(); } + + static void setTransform(SoTransform *pcTransform, const Base::Matrix4D &mat); + + enum SnapshotType { + //three type of snapshot to override linked root node: + + //override transform and visibility + SnapshotTransform = 0, + //override visibility + SnapshotVisible = 1, + //override none (for child objects of a container) + SnapshotChild = 2, + + SnapshotMax, + + //special type for sub object linking + SnapshotContainer = -1, + // sub object linking with transform override + SnapshotContainerTransform = -2, + }; + void setNodeType(SnapshotType type, bool sublink=true); + + void setChildren(const std::vector &children, + const boost::dynamic_bitset<> &vis, SnapshotType type=SnapshotVisible); + + bool linkGetDetailPath(const char *, SoFullPath *, SoDetail *&) const; + bool linkGetElementPicked(const SoPickedPoint *, std::string &) const; + + void setElementVisible(int index, bool visible); + bool isElementVisible(int index) const; + + ViewProviderDocumentObject *getOwner() const; + void setOwner(ViewProviderDocumentObject *vpd); + + bool hasSubs() const; + + std::vector getSubNames() const; + ViewProviderDocumentObject *getLinkedView() const; + + Base::BoundBox3d getBoundBox(ViewProviderDocumentObject *vpd=0) const; + + void setInvalid(); + +protected: + void replaceLinkedRoot(SoSeparator *); + void resetRoot(); + bool getGroupHierarchy(int index, SoFullPath *path) const; + +protected: + LinkInfoPtr linkOwner; + LinkInfoPtr linkInfo; + CoinPtr pcLinkRoot; + CoinPtr pcTransform; + CoinPtr pcLinkedRoot; + CoinPtr pcDrawStyle; // for override line width and point size + CoinPtr pcShapeHints; // for override double side rendering for mirror + SnapshotType nodeType; + SnapshotType childType; + bool autoSubLink; //auto delegate to linked sub object if there is only one sub object + + class SubInfo; + friend class SubInfo; + std::map > subInfo; + + class Element; + std::vector > nodeArray; + std::unordered_map nodeMap; + + Py::Object PythonObject; +}; + +class GuiExport ViewProviderLink : public ViewProviderDocumentObject +{ + PROPERTY_HEADER(Gui::ViewProviderLink); + typedef ViewProviderDocumentObject inherited; + +public: + App::PropertyBool OverrideMaterial; + App::PropertyMaterial ShapeMaterial; + App::PropertyEnumeration DrawStyle; + App::PropertyFloatConstraint LineWidth; + App::PropertyFloatConstraint PointSize; + App::PropertyMaterialList MaterialList; + App::PropertyBoolList OverrideMaterialList; + App::PropertyBool Selectable; + App::PropertyColorList OverrideColorList; + App::PropertyPersistentObject ChildViewProvider; + + ViewProviderLink(); + virtual ~ViewProviderLink(); + + void attach(App::DocumentObject *pcObj) override; + void reattach(App::DocumentObject *pcObj) override; + + bool isSelectable(void) const override; + + bool useNewSelectionModel(void) const override {return true;} + + void updateData(const App::Property*) override; + void onChanged(const App::Property* prop) override; + std::vector claimChildren(void) const override; + bool getElementPicked(const SoPickedPoint *, std::string &) const override; + bool getDetailPath(const char *, SoFullPath *, bool, SoDetail *&) const override; + + void finishRestoring() override; + + QIcon getIcon(void) const override; + + bool canDragObjects() const override; + bool canDragObject(App::DocumentObject*) const override; + void dragObject(App::DocumentObject*) override; + bool canDropObjects() const override; + bool canDragAndDropObject(App::DocumentObject*) const override; + bool canDropObjectEx(App::DocumentObject *obj, App::DocumentObject *owner, + const char *subname, const std::vector &elements) const override; + std::string dropObjectEx(App::DocumentObject*, App::DocumentObject*, + const char *subname, const std::vector &elements) override; + + bool onDelete(const std::vector &) override; + bool canDelete(App::DocumentObject* obj) const override; + + std::vector getDisplayModes(void) const override; + + void setupContextMenu(QMenu*, QObject*, const char*) override; + + virtual QPixmap getOverlayPixmap() const; + + ViewProvider *startEditing(int ModNum) override; + bool doubleClicked() override; + + PyObject *getPyObject() override; + PyObject *getPyLinkView(); + + static void updateLinks(ViewProvider *vp); + + void updateDraggingPlacement(const Base::Placement &pla, bool force=false); + Base::Placement currentDraggingPlacement() const; + void enableCenterballDragger(bool enable); + bool isUsingCenterballDragger() const { return useCenterballDragger; } + + std::map getElementColors(const char *subname=0) const override; + void setElementColors(const std::map &colors) override; + + void setOverrideMode(const std::string &mode) override; + + virtual void onBeforeChange(const App::Property*) override; + ViewProviderDocumentObject *getChildViewProvider() const { + return childVp; + } + + virtual App::Property *getPropertyByName(const char* name) const override; + virtual void getPropertyMap(std::map &Map) const override; + virtual void getPropertyList(std::vector &List) const override; + + virtual ViewProviderDocumentObject *getLinkedViewProvider( + std::string *subname=0, bool recursive=false) const override; + +protected: + bool setEdit(int ModNum) override; + void setEditViewer(View3DInventorViewer*, int ModNum) override; + void unsetEditViewer(View3DInventorViewer*) override; + bool linkEdit(const App::LinkBaseExtension *ext=0) const; + + enum LinkType { + LinkTypeNone, + LinkTypeNormal, + LinkTypeSubs, + }; + + bool hasElements(const App::LinkBaseExtension *ext = 0) const; + bool isGroup(const App::LinkBaseExtension *ext=0, bool plainGroup=false) const; + const App::LinkBaseExtension *getLinkExtension() const; + App::LinkBaseExtension *getLinkExtension(); + + void updateDataPrivate(App::LinkBaseExtension *ext, const App::Property*); + void updateElementList(App::LinkBaseExtension *ext); + + bool setLinkType(App::LinkBaseExtension *ext); + + void onChangeIcon() const; + std::vector claimChildrenPrivate() const; + + void applyMaterial(); + void applyColors(); + + void checkIcon(const App::LinkBaseExtension *ext=0); + + ViewProvider *getLinkedView(bool real,const App::LinkBaseExtension *ext=0) const; + + bool initDraggingPlacement(); + bool callDraggerProxy(const char *fname, bool update); + +private: + static void dragStartCallback(void * data, SoDragger * d); + static void dragFinishCallback(void * data, SoDragger * d); + static void dragMotionCallback(void * data, SoDragger * d); + +protected: + LinkView *linkView; + LinkType linkType; + bool hasSubName; + bool hasSubElement; + bool useCenterballDragger; + + struct DraggerContext{ + Base::Matrix4D preTransform; + Base::Placement initialPlacement; + Base::Matrix4D mat; + Base::BoundBox3d bbox; + bool cmdPending; + }; + std::unique_ptr dragCtx; + CoinPtr pcDragger; + ViewProviderDocumentObject *childVp; + LinkInfoPtr childVpLink; + mutable qint64 overlayCacheKey; +}; + +typedef ViewProviderPythonFeatureT ViewProviderLinkPython; + +} //namespace Gui + +#ifdef _MSC_VER +// forward decleration to please VC 2013 +void intrusive_ptr_add_ref(Gui::LinkInfo *px); +void intrusive_ptr_release(Gui::LinkInfo *px); +#endif + +#endif // GUI_VIEWPROVIDER_LINK_H diff --git a/src/Gui/ViewProviderLinkPy.xml b/src/Gui/ViewProviderLinkPy.xml new file mode 100644 index 0000000000..8ce280b73d --- /dev/null +++ b/src/Gui/ViewProviderLinkPy.xml @@ -0,0 +1,35 @@ + + + + + + This is the ViewProviderLink class + + + + Get/set dragger placement during dragging + + + + + + Get/set dragger type + + + + + + Get the associated LinkView object + + + + + diff --git a/src/Gui/ViewProviderLinkPyImp.cpp b/src/Gui/ViewProviderLinkPyImp.cpp new file mode 100644 index 0000000000..d3c769db24 --- /dev/null +++ b/src/Gui/ViewProviderLinkPyImp.cpp @@ -0,0 +1,83 @@ +/**************************************************************************** + * 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 +#endif + +#include "Gui/ViewProviderLink.h" +#include +#include +// inclusion of the generated files (generated out of ViewProviderLinkPy.xml) +#include "ViewProviderLinkPy.h" +#include "ViewProviderLinkPy.cpp" + +using namespace Gui; + +// returns a string which represents the object e.g. when printed in python +std::string ViewProviderLinkPy::representation(void) const +{ + std::stringstream str; + str << ""; + + return str.str(); +} + +Py::Object ViewProviderLinkPy::getDraggingPlacement() const { + return Py::Object(new Base::PlacementPy(new Base::Placement( + getViewProviderLinkPtr()->currentDraggingPlacement()))); +} + +void ViewProviderLinkPy::setDraggingPlacement(Py::Object arg) { + if(!PyObject_TypeCheck(arg.ptr(),&Base::PlacementPy::Type)) + throw Py::TypeError("expects a placement"); + getViewProviderLinkPtr()->updateDraggingPlacement( + *static_cast(arg.ptr())->getPlacementPtr()); +} + +Py::Boolean ViewProviderLinkPy::getUseCenterballDragger() const { + return Py::Boolean(getViewProviderLinkPtr()->isUsingCenterballDragger()); +} + +void ViewProviderLinkPy::setUseCenterballDragger(Py::Boolean arg) { + try { + getViewProviderLinkPtr()->enableCenterballDragger(arg); + }catch(const Base::Exception &e){ + throw Py::Exception(Base::BaseExceptionFreeCADError,e.what()); + } +} + +Py::Object ViewProviderLinkPy::getLinkView() const { + return Py::Object(getViewProviderLinkPtr()->getPyLinkView(),true); +} + +PyObject *ViewProviderLinkPy::getCustomAttributes(const char* /*attr*/) const +{ + return 0; +} + +int ViewProviderLinkPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +}