App/Gui: support Link refresh on change of originally linked configurable object

This commit is contained in:
Zheng, Lei
2021-10-18 17:03:56 +08:00
committed by Chris Hennes
parent 1d95f7b58d
commit d1b6bb78d1
5 changed files with 1033 additions and 103 deletions

View File

@@ -22,14 +22,17 @@
#include "PreCompiled.h"
#include <boost/range.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <Base/Tools.h>
#include <Base/Uuid.h>
#include "Application.h"
#include "ComplexGeoData.h"
#include "ComplexGeoDataPy.h"
#include "Document.h"
#include "GroupExtension.h"
#include "DocumentObserver.h"
#include "GeoFeatureGroupExtension.h"
#include "Link.h"
#include "LinkBaseExtensionPy.h"
@@ -45,6 +48,99 @@ using namespace App;
using namespace Base;
namespace bp = boost::placeholders;
typedef boost::iterator_range<const char*> CharRange;
////////////////////////////////////////////////////////////////////////
/*[[[cog
import LinkParams
LinkParams.define()
]]]*/
// Auto generated code. See class document of LinkParams.
class LinkParamsP: public ParameterGrp::ObserverType {
public:
// Auto generated code. See class document of LinkParams.
ParameterGrp::handle handle;
// Auto generated code. See class document of LinkParams.
std::unordered_map<const char *,void(*)(LinkParamsP*),App::CStringHasher,App::CStringHasher> funcs;
bool CopyOnChangeApplyToAll; // Auto generated code. See class document of LinkParams.
// Auto generated code. See class document of LinkParams.
LinkParamsP() {
handle = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Link");
handle->Attach(this);
CopyOnChangeApplyToAll = handle->GetBool("CopyOnChangeApplyToAll", true);
funcs["CopyOnChangeApplyToAll"] = &LinkParamsP::updateCopyOnChangeApplyToAll;
}
// Auto generated code. See class document of LinkParams.
~LinkParamsP() {
}
// Auto generated code. See class document of LinkParams.
void OnChange(Base::Subject<const char*> &, const char* sReason) {
if(!sReason)
return;
auto it = funcs.find(sReason);
if(it == funcs.end())
return;
it->second(this);
}
// Auto generated code. See class document of LinkParams.
static void updateCopyOnChangeApplyToAll(LinkParamsP *self) {
self->CopyOnChangeApplyToAll = self->handle->GetBool("CopyOnChangeApplyToAll", true);
}
};
// Auto generated code. See class document of LinkParams.
LinkParamsP *instance() {
static LinkParamsP *inst = new LinkParamsP;
return inst;
}
// Auto generated code. See class document of LinkParams.
ParameterGrp::handle LinkParams::getHandle() {
return instance()->handle;
}
// Auto generated code. See class document of LinkParams.
const char *LinkParams::docCopyOnChangeApplyToAll() {
return QT_TRANSLATE_NOOP("LinkParams",
"Stores the last user choice of whether to apply CopyOnChange setup to all link\n"
"that links to the same configurable object");
}
// Auto generated code. See class document of LinkParams.
const bool & LinkParams::getCopyOnChangeApplyToAll() {
return instance()->CopyOnChangeApplyToAll;
}
// Auto generated code. See class document of LinkParams.
const bool & LinkParams::defaultCopyOnChangeApplyToAll() {
const static bool def = true;
return def;
}
// Auto generated code. See class document of LinkParams.
void LinkParams::setCopyOnChangeApplyToAll(const bool &v) {
instance()->handle->SetBool("CopyOnChangeApplyToAll",v);
instance()->CopyOnChangeApplyToAll = v;
}
// Auto generated code. See class document of LinkParams.
void LinkParams::removeCopyOnChangeApplyToAll() {
instance()->handle->RemoveBool("CopyOnChangeApplyToAll");
}
//[[[end]]]
///////////////////////////////////////////////////////////////////////////////
EXTENSION_PROPERTY_SOURCE(App::LinkBaseExtension, App::DocumentObjectExtension)
LinkBaseExtension::LinkBaseExtension(void)
@@ -130,18 +226,23 @@ void LinkBaseExtension::setProperty(int idx, Property *prop) {
switch(idx) {
case PropLinkMode: {
static const char *linkModeEnums[] = {"None","Auto Delete","Auto Link","Auto Unlink",nullptr};
auto propLinkMode = freecad_dynamic_cast<PropertyEnumeration>(prop);
auto propLinkMode = static_cast<PropertyEnumeration*>(prop);
if(!propLinkMode->getEnums())
propLinkMode->setEnums(linkModeEnums);
break;
}
case PropLinkCopyOnChange: {
static const char *enums[] = {"Disabled","Enabled","Owned",nullptr};
auto propEnum = freecad_dynamic_cast<PropertyEnumeration>(prop);
static const char *enums[] = {"Disabled","Enabled","Owned","Tracking",nullptr};
auto propEnum = static_cast<PropertyEnumeration*>(prop);
if(!propEnum->getEnums())
propEnum->setEnums(enums);
break;
}
case PropLinkCopyOnChangeTouched:
case PropLinkCopyOnChangeSource:
case PropLinkCopyOnChangeGroup:
prop->setStatus(Property::Hidden, true);
break;
case PropLinkTransform:
case PropLinkPlacement:
case PropPlacement:
@@ -155,6 +256,7 @@ void LinkBaseExtension::setProperty(int idx, Property *prop) {
}
break;
case PropElementList:
getElementListProperty()->setScope(LinkScope::Global);
getElementListProperty()->setStatus(Property::Hidden,true);
// fall through
case PropLinkedObject:
@@ -187,33 +289,35 @@ void LinkBaseExtension::setProperty(int idx, Property *prop) {
static const char _GroupPrefix[] = "Configuration (";
App::DocumentObjectExecReturn *LinkBaseExtension::extensionExecute(void) {
// The actual value of LinkRecompouted is not important, just to notify view
// The actual value of LinkTouched is not important, just to notify view
// provider that the link (in fact, its dependents, i.e. linked ones) have
// recomputed.
_LinkTouched.touch();
if(getLinkedObjectProperty()) {
DocumentObject *linked = getTrueLinkedObject(true);
if(!linked)
return new App::DocumentObjectExecReturn("Link broken");
if(!linked) {
std::ostringstream ss;
ss << "Link broken!";
auto xlink = Base::freecad_dynamic_cast<PropertyXLink>(
getLinkedObjectProperty());
if (xlink) {
const char *objname = xlink->getObjectName();
if (objname && objname[0])
ss << "\nObject: " << objname;
const char *filename = xlink->getFilePath();
if (filename && filename[0])
ss << "\nFile: " << filename;
}
return new App::DocumentObjectExecReturn(ss.str().c_str());
}
App::DocumentObject *container = getContainer();
setupCopyOnChange(container);
if(hasCopyOnChange && getLinkCopyOnChangeValue()==0) {
hasCopyOnChange = false;
std::vector<Property*> props;
container->getPropertyList(props);
for(auto prop : props) {
if(isCopyOnChangeProperty(container, *prop)) {
try {
container->removeDynamicProperty(prop->getName());
} catch (Base::Exception &e) {
e.ReportException();
} catch (...) {
}
}
}
auto source = getLinkCopyOnChangeSourceValue();
if (source && getLinkCopyOnChangeValue() == CopyOnChangeTracking
&& getLinkCopyOnChangeTouchedValue())
{
syncCopyOnChange();
}
PropertyPythonObject *proxy = nullptr;
@@ -266,6 +370,25 @@ App::DocumentObjectExecReturn *LinkBaseExtension::extensionExecute(void) {
return new App::DocumentObjectExecReturn(errMsg);
}
}
auto parent = getContainer();
setupCopyOnChange(parent);
if(hasCopyOnChange && getLinkCopyOnChangeValue()==CopyOnChangeDisabled) {
hasCopyOnChange = false;
std::vector<Property*> props;
parent->getPropertyList(props);
for(auto prop : props) {
if(isCopyOnChangeProperty(parent, *prop)) {
try {
parent->removeDynamicProperty(prop->getName());
} catch (Base::Exception &e) {
e.ReportException();
} catch (...) {
}
}
}
}
}
return inherited::extensionExecute();
}
@@ -276,6 +399,273 @@ short LinkBaseExtension::extensionMustExecute(void) {
return link->mustExecute();
}
std::vector<App::DocumentObject*>
LinkBaseExtension::getOnChangeCopyObjects(
std::vector<App::DocumentObject *> *excludes,
App::DocumentObject *src)
{
auto parent = getContainer();
if (!src)
src = getLinkCopyOnChangeSourceValue();
if (!src || getLinkCopyOnChangeValue() == CopyOnChangeDisabled)
return {};
auto res = Document::getDependencyList({src}, Document::DepSort);
for (auto it=res.begin(); it!=res.end();) {
auto obj = *it;
if (obj == src) {
++it;
continue;
}
auto prop = Base::freecad_dynamic_cast<PropertyMap>(
obj->getPropertyByName("_CopyOnChangeControl"));
static std::map<std::string, std::string> dummy;
const auto & map = prop && prop->getContainer()==obj ? prop->getValues() : dummy;
const char *v = "";
if (src->getDocument() != obj->getDocument())
v = "-";
auto iter = map.find("*");
if (iter != map.end())
v = iter->second.c_str();
else if ((iter = map.find(parent->getNameInDocument())) != map.end())
v = iter->second.c_str();
if (boost::equals(v, "-")) {
if (excludes)
excludes->push_back(obj);
else {
it = res.erase(it);
continue;
}
}
++it;
}
return res;
}
void LinkBaseExtension::setOnChangeCopyObject(
App::DocumentObject *obj, OnChangeCopyOptions options)
{
auto parent = getContainer();
Base::Flags<OnChangeCopyOptions> flags(options);
bool exclude = flags.testFlag(OnChangeCopyOptions::Exclude);
bool external = parent->getDocument() != obj->getDocument();
auto prop = Base::freecad_dynamic_cast<PropertyMap>(
obj->getPropertyByName("_CopyOnChangeControl"));
if (external == exclude && !prop)
return;
prop = static_cast<PropertyMap*>(
obj->addDynamicProperty("App::PropertyMap", "_CopyOnChangeControl"));
if (!prop) {
FC_ERR("Failed to setup copy on change object " << obj->getFullName());
return;
}
const char *key = flags.testFlag(OnChangeCopyOptions::ApplyAll) ? "*" : parent->getNameInDocument();
if (external)
prop->setValue(key, exclude ? nullptr : "+");
else
prop->setValue(key, exclude ? "-" : nullptr);
}
// The purpose of this function is to synchronize the mutated copy to the
// original linked CopyOnChange object. It will make a new copy if any of the
// non-CopyOnChange property of the original object has changed.
void LinkBaseExtension::syncCopyOnChange()
{
if (!getLinkCopyOnChangeValue())
return;
auto linkProp = getLinkedObjectProperty();
auto srcProp = getLinkCopyOnChangeSourceProperty();
auto srcTouched = getLinkCopyOnChangeTouchedProperty();
if (!linkProp
|| !srcProp
|| !srcTouched
|| !srcProp->getValue()
|| !linkProp->getValue()
|| srcProp->getValue() == linkProp->getValue())
return;
auto parent = getContainer();
auto linked = linkProp->getValue();
std::vector<App::DocumentObjectT> oldObjs;
std::vector<App::DocumentObject*> objs;
// CopyOnChangeGroup is a hidden dynamic property for holding a LinkGroup
// for holding the mutated copy of the original linked object and all its
// dependencies.
LinkGroup *copyOnChangeGroup = nullptr;
if (auto prop = getLinkCopyOnChangeGroupProperty()) {
copyOnChangeGroup = Base::freecad_dynamic_cast<LinkGroup>(prop->getValue());
if (!copyOnChangeGroup) {
// Create the LinkGroup if not exist
auto group = new LinkGroup;
group->LinkMode.setValue(LinkModeAutoDelete);
parent->getDocument()->addObject(group, "CopyOnChangeGroup");
prop->setValue(group);
} else {
// If it exists, then obtain all copied objects. Note that we stores
// the dynamic property _SourceUUID in oldObjs if possible, in order
// to match the possible new copy later.
objs = copyOnChangeGroup->ElementList.getValues();
for (auto obj : objs) {
if (!obj->getNameInDocument())
continue;
auto prop = Base::freecad_dynamic_cast<PropertyUUID>(
obj->getPropertyByName("_SourceUUID"));
if (prop && prop->getContainer() == obj)
oldObjs.emplace_back(prop);
else
oldObjs.emplace_back(obj);
}
std::sort(objs.begin(), objs.end());
}
}
// Obtain the original linked object and its dependency in depending order.
// The last being the original linked object.
auto srcObjs = getOnChangeCopyObjects();
// Refresh signal connection to monitor changes
monitorOnChangeCopyObjects(srcObjs);
// Copy the objects. Document::export/importObjects() (called by
// copyObject()) will generate a _ObjectUUID for each source object and
// match it with a _SourceUUID in the copy.
auto copiedObjs = parent->getDocument()->copyObject(srcObjs);
if(copiedObjs.empty())
return;
// copyObject() will return copy in order of the same order of the input,
// so the last object will be the copy of the original linked object
auto newLinked = copiedObjs.back();
// We are coping from the original linked object and we've already mutated
// it, so we need to copy all CopyOnChange properties from the mutated
// object to the new copy.
std::vector<App::Property*> propList;
linked->getPropertyList(propList);
for (auto prop : propList) {
if(!prop->testStatus(Property::CopyOnChange)
|| prop->getContainer()!=linked)
continue;
auto p = newLinked->getPropertyByName(prop->getName());
if (p && p->getTypeId() == prop->getTypeId()) {
std::unique_ptr<Property> pCopy(prop->Copy());
p->Paste(*pCopy);
}
}
if (copyOnChangeGroup) {
// The order of the copied objects is in dependency order (because of
// getOnChangeCopyObjects()). We reverse it here so that we can later
// on delete it in reverse order to avoid error (because some parent
// objects may want to delete their own children).
std::reverse(copiedObjs.begin(), copiedObjs.end());
copyOnChangeGroup->ElementList.setValues(copiedObjs);
}
// Create a map to find the corresponding replacement of the new copies to
// the mutated object. The reason for doing so is that we are copying from
// the original linked object and its dependency, not the mutated objects
// which are old copies. There could be arbitary changes in the originals
// which may add or remove or change dependending orders, while the
// replacement happen between the new and old copies.
std::map<Base::Uuid, App::DocumentObjectT> newObjs;
for (auto obj : copiedObjs) {
auto prop = Base::freecad_dynamic_cast<PropertyUUID>(
obj->getPropertyByName("_SourceUUID"));
if (prop)
newObjs.insert(std::make_pair(prop->getValue(), obj));
}
std::vector<std::pair<App::DocumentObject*, App::DocumentObject*> > replacements;
for (const auto &objT : oldObjs) {
auto prop = Base::freecad_dynamic_cast<PropertyUUID>(objT.getProperty());
if (!prop)
continue;
auto it = newObjs.find(prop->getValue());
if (it == newObjs.end())
continue;
auto oldObj = objT.getObject();
auto newObj = it->second.getObject();
if (oldObj && newObj)
replacements.emplace_back(oldObj, newObj);
}
std::vector<std::pair<App::DocumentObjectT, std::unique_ptr<App::Property> > > propChanges;
if (!replacements.empty()) {
std::sort(copiedObjs.begin(), copiedObjs.end());
// Global search for links affected by the replacement. We accumulate
// the changes in propChanges without applying, in order to avoid any
// side effect of changing while searching.
for(auto doc : App::GetApplication().getDocuments()) {
for(auto o : doc->getObjects()) {
if (o == parent
|| std::binary_search(objs.begin(), objs.end(), o)
|| std::binary_search(copiedObjs.begin(), copiedObjs.end(), o))
continue;
propList.clear();
o->getPropertyList(propList);
for(auto prop : propList) {
if (prop->getContainer() != o)
continue;
auto linkProp = Base::freecad_dynamic_cast<App::PropertyLinkBase>(prop);
if(!linkProp)
continue;
for (const auto &v : replacements) {
std::unique_ptr<App::Property> copy(
linkProp->CopyOnLinkReplace(parent,v.first,v.second));
if(!copy)
continue;
propChanges.emplace_back(App::DocumentObjectT(prop),std::move(copy));
}
}
}
}
}
Base::StateLocker guard(pauseCopyOnChange);
linkProp->setValue(newLinked);
newLinked->Visibility.setValue(false);
srcTouched->setValue(false);
// Apply the global link changes.
for(const auto &v : propChanges) {
auto prop = v.first.getProperty();
if(prop)
prop->Paste(*v.second.get());
}
// Finally, remove all old copies. oldObjs stores type of DocumentObjectT
// which stores the object name. Before removing, we need to make sure the
// object exists, and it is not some new object with the same name, by
// checking objs which stores DocumentObject pointer.
for (const auto &objT : oldObjs) {
auto obj = objT.getObject();
if (obj && std::binary_search(objs.begin(), objs.end(), obj))
obj->getDocument()->removeObject(obj->getNameInDocument());
}
}
bool LinkBaseExtension::isLinkedToConfigurableObject() const
{
if (auto linked = getLinkedObjectValue()) {
std::vector<App::Property*> propList;
linked->getPropertyList(propList);
for (auto prop : propList) {
if(prop->testStatus(Property::CopyOnChange)
&& prop->getContainer()==linked)
return true;
}
}
return false;
}
bool LinkBaseExtension::isCopyOnChangeProperty(DocumentObject *obj, const App::Property &prop) {
if(obj!=prop.getContainer() || !prop.testStatus(App::Property::PropDynamic))
return false;
@@ -283,14 +673,30 @@ bool LinkBaseExtension::isCopyOnChangeProperty(DocumentObject *obj, const App::P
return group && boost::starts_with(group,_GroupPrefix);
}
void LinkBaseExtension::setupCopyOnChange(DocumentObject *parent) {
void LinkBaseExtension::setupCopyOnChange(DocumentObject *parent, bool checkSource) {
copyOnChangeConns.clear();
copyOnChangeSrcConns.clear();
auto linked = getTrueLinkedObject(false);
if(!linked || getLinkCopyOnChangeValue()==0)
if(!linked || getLinkCopyOnChangeValue()==CopyOnChangeDisabled)
return;
if (checkSource && !pauseCopyOnChange) {
PropertyLink *source = getLinkCopyOnChangeSourceProperty();
if (source) {
source->setValue(linked);
if (auto touched = getLinkCopyOnChangeTouchedProperty())
touched->setValue(false);
}
}
hasCopyOnChange = setupCopyOnChange(parent,linked,&copyOnChangeConns,hasCopyOnChange);
if (hasCopyOnChange && getLinkCopyOnChangeValue() == CopyOnChangeOwned
&& getLinkedObjectValue()
&& getLinkedObjectValue() == getLinkCopyOnChangeSourceValue())
{
makeCopyOnChange();
}
}
bool LinkBaseExtension::setupCopyOnChange(DocumentObject *parent, DocumentObject *linked,
@@ -371,7 +777,7 @@ bool LinkBaseExtension::setupCopyOnChange(DocumentObject *parent, DocumentObject
if(!copyOnChangeConns)
return res;
for(auto &v : newProps) {
for(const auto &v : newProps) {
// sync configuration properties
copyOnChangeConns->push_back(v.second->signalChanged.connect([parent](const Property &prop) {
if(!prop.testStatus(Property::CopyOnChange))
@@ -396,15 +802,19 @@ bool LinkBaseExtension::setupCopyOnChange(DocumentObject *parent, DocumentObject
void LinkBaseExtension::checkCopyOnChange(
App::DocumentObject *parent, const App::Property &prop)
{
if(parent->getDocument()->isPerformingTransaction())
if(!parent || !parent->getDocument()
|| parent->getDocument()->isPerformingTransaction())
return;
auto linked = getTrueLinkedObject(false);
if(!linked || getLinkCopyOnChangeValue()==0
auto linked = getLinkedObjectValue();
if(!linked || getLinkCopyOnChangeValue()==CopyOnChangeDisabled
|| !isCopyOnChangeProperty(parent,prop))
return;
if(getLinkCopyOnChangeValue()==2) {
if(getLinkCopyOnChangeValue() == CopyOnChangeOwned ||
(getLinkCopyOnChangeValue() == CopyOnChangeTracking
&& linked != getLinkCopyOnChangeSourceValue()))
{
auto p = linked->getPropertyByName(prop.getName());
if(p && p->getTypeId()==prop.getTypeId()) {
std::unique_ptr<Property> pcopy(prop.Copy());
@@ -418,21 +828,80 @@ void LinkBaseExtension::checkCopyOnChange(
if(!linkedProp || linkedProp->getTypeId()!=prop.getTypeId() || linkedProp->isSame(prop))
return;
auto objs = parent->getDocument()->copyObject({linked},true);
auto copied = makeCopyOnChange();
if (copied) {
linkedProp = copied->getPropertyByName(prop.getName());
if(linkedProp && linkedProp->getTypeId()==prop.getTypeId()) {
std::unique_ptr<Property> pcopy(prop.Copy());
if(pcopy)
linkedProp->Paste(*pcopy);
}
}
}
App::DocumentObject *LinkBaseExtension::makeCopyOnChange() {
auto linked = getLinkedObjectValue();
if (pauseCopyOnChange || !linked)
return nullptr;
auto parent = getContainer();
auto srcobjs = getOnChangeCopyObjects(nullptr, linked);
for (auto obj : srcobjs) {
if (obj->testStatus(App::PartialObject)) {
FC_THROWM(Base::RuntimeError, "Cannot copy partial loaded object: "
<< obj->getFullName());
}
}
auto objs = parent->getDocument()->copyObject(srcobjs);
if(objs.empty())
return;
return nullptr;
monitorOnChangeCopyObjects(srcobjs);
linked = objs.back();
linked->Visibility.setValue(false);
linkedProp = linked->getPropertyByName(prop.getName());
if(linkedProp && linkedProp->getTypeId()==prop.getTypeId()) {
std::unique_ptr<Property> pcopy(prop.Copy());
if(pcopy)
linkedProp->Paste(*pcopy);
}
getLinkCopyOnChangeProperty()->setValue((long)0);
Base::StateLocker guard(pauseCopyOnChange);
getLinkedObjectProperty()->setValue(linked);
getLinkCopyOnChangeProperty()->setValue(2);
if (getLinkCopyOnChangeValue() == CopyOnChangeEnabled)
getLinkCopyOnChangeProperty()->setValue(CopyOnChangeOwned);
if (auto prop = getLinkCopyOnChangeGroupProperty()) {
if (auto obj = prop->getValue()) {
if (obj->getNameInDocument() && obj->getDocument())
obj->getDocument()->removeObject(obj->getNameInDocument());
}
auto group = new LinkGroup;
group->LinkMode.setValue(LinkModeAutoDelete);
getContainer()->getDocument()->addObject(group, "CopyOnChangeGroup");
prop->setValue(group);
// The order of the copied objects is in dependency order (because of
// getOnChangeCopyObjects()). We reverse it here so that we can later
// on delete it in reverse order to avoid error (because some parent
// objects may want to delete their own children).
std::reverse(objs.begin(), objs.end());
group->ElementList.setValues(objs);
}
return linked;
}
void LinkBaseExtension::monitorOnChangeCopyObjects(
const std::vector<App::DocumentObject*> &objs)
{
copyOnChangeSrcConns.clear();
if (getLinkCopyOnChangeValue() == CopyOnChangeDisabled)
return;
for(auto obj : objs) {
obj->setStatus(App::ObjectStatus::TouchOnColorChange, true);
copyOnChangeSrcConns.push_back(obj->signalChanged.connect(
[this](const DocumentObject &, const Property &) {
if (auto prop = this->getLinkCopyOnChangeTouchedProperty()) {
if (this->getLinkCopyOnChangeValue() != CopyOnChangeDisabled)
prop->setValue(true);
}
}));
}
}
App::GroupExtension *LinkBaseExtension::linkedPlainGroup() const {
@@ -492,6 +961,8 @@ bool LinkBaseExtension::extensionHasChildElement() const {
if(_getElementListValue().size()
|| (_getElementCountValue() && _getShowElementValue()))
return true;
if (getLinkClaimChildValue())
return false;
DocumentObject *linked = getTrueLinkedObject(false);
if(linked) {
if(linked->hasChildElement())
@@ -560,7 +1031,8 @@ DocumentObject *LinkBaseExtension::getContainer(){
}
DocumentObject *LinkBaseExtension::getLink(int depth) const{
GetApplication().checkLinkDepth(depth,false);
if (!GetApplication().checkLinkDepth(depth,true))
return nullptr;
if(getLinkedObjectProperty())
return getLinkedObjectValue();
return nullptr;
@@ -628,15 +1100,18 @@ int LinkBaseExtension::getElementIndex(const char *subname, const char **psubnam
// 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)
CharRange sub(subname+1, dot);
if (boost::equals(sub, linked->Label.getValue()))
idx = 0;
}else if(sub==linked->getNameInDocument())
idx = 0;
} else {
CharRange sub(subname, dot);
if (boost::equals(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());
auto sobj = linked->getSubObject(std::string(subname, dot-subname+1).c_str());
if(!sobj)
return -1;
if(psubname)
@@ -849,8 +1324,44 @@ bool LinkBaseExtension::extensionGetSubObject(DocumentObject *&ret, const char *
return true;
Base::Matrix4D matNext;
if(mat) matNext = *mat;
ret = linked->getSubObject(subname,pyObj,mat?&matNext:nullptr,false,depth+1);
// Because of the addition of LinkClaimChild, the linked object may be
// claimed as the first child. Regardless of the current value of
// LinkClaimChild, we must accept sub-object path that contains the linked
// object, because other link property may store such reference.
if (const char* dot=strchr(subname,'.')) {
auto group = getLinkCopyOnChangeGroupValue();
if (subname[0] == '$') {
CharRange sub(subname+1,dot);
if (group && boost::equals(sub, group->Label.getValue()))
linked = group;
else if(!boost::equals(sub, linked->Label.getValue()))
dot = nullptr;
} else {
CharRange sub(subname,dot);
if (group && boost::equals(sub, group->getNameInDocument()))
linked = group;
else if (!boost::equals(sub, linked->getNameInDocument()))
dot = nullptr;
}
if (dot) {
// Because of external linked object, It is possible for and
// child object to have the exact same internal name or label
// as the parent object. To resolve this potential ambiguity,
// try assuming the current subname is referring to the parent
// (i.e. the linked object), and if it fails, try again below.
if(mat) matNext = *mat;
ret = linked->getSubObject(dot+1,pyObj,mat?&matNext:nullptr,false,depth+1);
if (ret && dot[1])
subname = dot+1;
}
}
if (!ret) {
if(mat) matNext = *mat;
ret = linked->getSubObject(subname,pyObj,mat?&matNext:nullptr,false,depth+1);
}
std::string postfix;
if(ret) {
// do not resolve the link if we are the last referenced object
@@ -892,10 +1403,11 @@ void LinkBaseExtension::checkGeoElementMap(const App::DocumentObject *obj,
void LinkBaseExtension::onExtendedUnsetupObject() {
if(!getElementListProperty())
return;
auto objs = getElementListValue();
getElementListProperty()->setValue();
for(auto obj : objs)
detachElement(obj);
detachElements();
if (auto obj = getLinkCopyOnChangeGroupValue()) {
if(obj->getNameInDocument() && !obj->isRemoving())
obj->getDocument()->removeObject(obj->getNameInDocument());
}
}
DocumentObject *LinkBaseExtension::getTrueLinkedObject(
@@ -1281,13 +1793,32 @@ void LinkBaseExtension::update(App::DocumentObject *parent, const Property *prop
parseSubName();
syncElementList();
if(getLinkCopyOnChangeValue()==2)
getLinkCopyOnChangeProperty()->setValue(1);
if(getLinkCopyOnChangeValue()==CopyOnChangeOwned
&& !pauseCopyOnChange
&& !parent->getDocument()->isPerformingTransaction())
getLinkCopyOnChangeProperty()->setValue(CopyOnChangeEnabled);
else
setupCopyOnChange(parent);
setupCopyOnChange(parent, true);
}else if(prop == getLinkCopyOnChangeProperty()) {
setupCopyOnChange(parent);
setupCopyOnChange(parent, getLinkCopyOnChangeSourceValue() == nullptr);
} else if (prop == getLinkCopyOnChangeSourceProperty()) {
if (auto source = getLinkCopyOnChangeSourceValue()) {
this->connCopyOnChangeSource = source->signalChanged.connect(
[this](const DocumentObject & obj, const Property &prop) {
auto src = getLinkCopyOnChangeSourceValue();
if (src != &obj || getLinkCopyOnChangeValue()==CopyOnChangeDisabled)
return;
if (App::Document::isAnyRestoring()
|| obj.testStatus(ObjectStatus::NoTouch)
|| (prop.getType() & Prop_Output)
|| prop.testStatus(Property::Output))
return;
if (auto propTouch = getLinkCopyOnChangeTouchedProperty())
propTouch->setValue(true);
});
} else
this->connCopyOnChangeSource.disconnect();
}else if(prop == getLinkTransformProperty()) {
auto linkPlacement = getLinkPlacementProperty();
@@ -1396,7 +1927,7 @@ void LinkBaseExtension::onExtendedDocumentRestored() {
sub.resize(element - sub.c_str());
}
std::vector<std::string> subs;
for(auto &s : subset)
for(const auto &s : subset)
subs.push_back(sub + s);
xlink->setSubValues(std::move(subs));
}
@@ -1409,8 +1940,14 @@ void LinkBaseExtension::onExtendedDocumentRestored() {
getScaleVectorProperty()->setValue(s,s,s);
}
update(parent,getVisibilityListProperty());
update(parent,getLinkedObjectProperty());
if (auto prop = getLinkedObjectProperty()) {
Base::StateLocker guard(pauseCopyOnChange);
update(parent,prop);
}
update(parent,getLinkCopyOnChangeSourceProperty());
update(parent,getElementListProperty());
if (getLinkCopyOnChangeValue() != CopyOnChangeDisabled)
monitorOnChangeCopyObjects(getOnChangeCopyObjects());
}
void LinkBaseExtension::_handleChangedPropertyName(
@@ -1515,11 +2052,7 @@ void LinkBaseExtension::setLink(int index, DocumentObject *obj,
if(obj || !getElementListProperty())
LINK_THROW(Base::RuntimeError,"No PropertyLink or PropertyLinkList configured");
auto objs = getElementListValue();
getElementListProperty()->setValue();
for(auto thisObj : objs)
detachElement(thisObj);
detachElements();
return;
}
@@ -1554,6 +2087,16 @@ void LinkBaseExtension::setLink(int index, DocumentObject *obj,
xlink->setValue(obj,std::move(subs));
}
void LinkBaseExtension::detachElements()
{
std::vector<App::DocumentObjectT> objs;
for (auto obj : getElementListValue())
objs.push_back(obj);
getElementListProperty()->setValue();
for(const auto &objT : objs)
detachElement(objT.getObject());
}
void LinkBaseExtension::detachElement(DocumentObject *obj) {
if(!obj || !obj->getNameInDocument() || obj->isRemoving())
return;
@@ -1642,6 +2185,14 @@ Property *LinkBaseExtension::extensionGetPropertyByName(const char* name) const
return nullptr;
}
bool LinkBaseExtension::isLinkMutated() const
{
return getLinkCopyOnChangeValue() != CopyOnChangeDisabled
&& getLinkedObjectValue()
&& (!getLinkCopyOnChangeSourceValue()
|| (getLinkedObjectValue() != getLinkCopyOnChangeSourceValue()));
}
///////////////////////////////////////////////////////////////////////////////////////////
namespace App {

View File

@@ -24,7 +24,8 @@
#define APP_LINK_H
#include <unordered_set>
#include <Base/Parameter.h>
#include <Base/Bitmask.h>
#include "DocumentObject.h"
#include "DocumentObjectExtension.h"
#include "FeaturePython.h"
@@ -89,6 +90,10 @@ public:
(LinkTransform, bool, App::PropertyBool, false, \
"Set to false to override linked object's placement", ##__VA_ARGS__)
#define LINK_PARAM_CLAIM_CHILD(...) \
(LinkClaimChild, bool, App::PropertyBool, false, \
"Claim the linked object as a child", ##__VA_ARGS__)
#define LINK_PARAM_COPY_ON_CHANGE(...) \
(LinkCopyOnChange, long, App::PropertyEnumeration, ((long)0), \
"Disabled: disable copy on change\n"\
@@ -97,6 +102,16 @@ public:
" the link will try to sync any change of the original linked object back to the copy.",\
##__VA_ARGS__)
#define LINK_PARAM_COPY_ON_CHANGE_SOURCE(...) \
(LinkCopyOnChangeSource, App::DocumentObject*, App::PropertyLink, 0, "The copy on change source object", ##__VA_ARGS__)
#define LINK_PARAM_COPY_ON_CHANGE_GROUP(...) \
(LinkCopyOnChangeGroup, App::DocumentObject*, App::PropertyLink, 0, \
"Linked to a internal group object for holding on change copies", ##__VA_ARGS__)
#define LINK_PARAM_COPY_ON_CHANGE_TOUCHED(...) \
(LinkCopyOnChangeTouched, bool, App::PropertyBool, 0, "Indicating the copy on change source object has been changed", ##__VA_ARGS__)
#define LINK_PARAM_SCALE(...) \
(Scale, double, App::PropertyFloat, 1.0, "Scale factor", ##__VA_ARGS__)
@@ -151,6 +166,7 @@ public:
LINK_PARAM(PLACEMENT)\
LINK_PARAM(LINK_PLACEMENT)\
LINK_PARAM(OBJECT)\
LINK_PARAM(CLAIM_CHILD)\
LINK_PARAM(TRANSFORM)\
LINK_PARAM(SCALE)\
LINK_PARAM(SCALE_VECTOR)\
@@ -164,6 +180,9 @@ public:
LINK_PARAM(LINK_EXECUTE)\
LINK_PARAM(COLORED_ELEMENTS)\
LINK_PARAM(COPY_ON_CHANGE)\
LINK_PARAM(COPY_ON_CHANGE_SOURCE)\
LINK_PARAM(COPY_ON_CHANGE_GROUP)\
LINK_PARAM(COPY_ON_CHANGE_TOUCHED)\
enum PropIndex {
#define LINK_PINDEX_DEFINE(_1,_2,_param) LINK_PINDEX(_param),
@@ -201,6 +220,13 @@ public:
typedef std::map<std::string, PropInfo> PropInfoMap;
virtual const PropInfoMap &getPropertyInfoMap() const;
enum LinkCopyOnChangeType {
CopyOnChangeDisabled = 0,
CopyOnChangeEnabled = 1,
CopyOnChangeOwned = 2,
CopyOnChangeTracking = 3
};
#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)];\
@@ -300,15 +326,46 @@ public:
static bool isCopyOnChangeProperty(App::DocumentObject *obj, const Property &prop);
void syncCopyOnChange();
/** Options used in setOnChangeCopyObject()
* Multiple options can be combined by bitwise or operator
*/
enum class OnChangeCopyOptions {
/// If set, then exclude the input from object list to copy on change, or else, include the input object.
Exclude = 1,
/// If set , then apply the setting to all links to the input object, or else, apply only to this link.
ApplyAll = 2,
};
/** Include or exclude object from list of objects to copy on change
* @param obj: input object
* @param options: control options. @sa OnChangeCopyOptions.
*/
void setOnChangeCopyObject(App::DocumentObject *obj, OnChangeCopyOptions options);
std::vector<App::DocumentObject *> getOnChangeCopyObjects(
std::vector<App::DocumentObject *> *excludes = nullptr,
App::DocumentObject *src = nullptr);
bool isLinkedToConfigurableObject() const;
void monitorOnChangeCopyObjects(const std::vector<App::DocumentObject*> &objs);
/// Check if the linked object is a copy on change
bool isLinkMutated() const;
protected:
void _handleChangedPropertyName(Base::XMLReader &reader,
const char * TypeName, const char *PropName);
void parseSubName() const;
void update(App::DocumentObject *parent, const Property *prop);
void checkCopyOnChange(App::DocumentObject *parent, const App::Property &prop);
void setupCopyOnChange(App::DocumentObject *parent);
void setupCopyOnChange(App::DocumentObject *parent, bool checkSource = false);
App::DocumentObject *makeCopyOnChange();
void syncElementList();
void detachElement(App::DocumentObject *obj);
void detachElements();
void checkGeoElementMap(const App::DocumentObject *obj,
const App::DocumentObject *linked, PyObject **pyObj, const char *postfix) const;
void updateGroup();
@@ -329,10 +386,14 @@ protected:
mutable bool enableLabelCache;
bool hasOldSubElement;
mutable bool checkingProperty = false;
std::vector<boost::signals2::scoped_connection> copyOnChangeConns;
std::vector<boost::signals2::scoped_connection> copyOnChangeSrcConns;
bool hasCopyOnChange;
mutable bool checkingProperty = false;
bool pauseCopyOnChange = false;
boost::signals2::scoped_connection connCopyOnChangeSource;
};
///////////////////////////////////////////////////////////////////////////
@@ -460,6 +521,7 @@ public:
#define LINK_PARAMS_LINK \
LINK_PARAM_EXT_TYPE(OBJECT, App::PropertyXLink)\
LINK_PARAM_EXT(CLAIM_CHILD)\
LINK_PARAM_EXT(TRANSFORM)\
LINK_PARAM_EXT(LINK_PLACEMENT)\
LINK_PARAM_EXT(PLACEMENT)\
@@ -468,6 +530,9 @@ public:
LINK_PARAM_EXT(LINK_EXECUTE)\
LINK_PARAM_EXT_ATYPE(COLORED_ELEMENTS,App::Prop_Hidden)\
LINK_PARAM_EXT(COPY_ON_CHANGE)\
LINK_PARAM_EXT_TYPE(COPY_ON_CHANGE_SOURCE, App::PropertyXLink)\
LINK_PARAM_EXT(COPY_ON_CHANGE_GROUP)\
LINK_PARAM_EXT(COPY_ON_CHANGE_TOUCHED)\
LINK_PROPS_DEFINE(LINK_PARAMS_LINK)
@@ -508,6 +573,9 @@ public:
LINK_PARAM_EXT(LINK_PLACEMENT)\
LINK_PARAM_EXT(PLACEMENT)\
LINK_PARAM_EXT(COPY_ON_CHANGE)\
LINK_PARAM_EXT_TYPE(COPY_ON_CHANGE_SOURCE, App::PropertyXLink)\
LINK_PARAM_EXT(COPY_ON_CHANGE_GROUP)\
LINK_PARAM_EXT(COPY_ON_CHANGE_TOUCHED)\
// defines the actual properties
LINK_PROPS_DEFINE(LINK_PARAMS_ELEMENT)
@@ -566,6 +634,66 @@ typedef App::FeaturePythonT<LinkGroup> LinkGroupPython;
} //namespace App
ENABLE_BITMASK_OPERATORS(App::Link::OnChangeCopyOptions)
/*[[[cog
import LinkParams
LinkParams.declare()
]]]*/
namespace App {
/** Convenient class to obtain App::Link related parameters
* The parameters are under group "User parameter:BaseApp/Preferences/Link"
*
* This class is auto generated by LinkParams.py. Modify that file
* instead of this one, if you want to add any parameter. You need
* to install Cog Python package for code generation:
* @code
* pip install cogapp
* @endcode
*
* Once modified, you can regenerate the header and the source file,
* @code
* python3 -m cogapp -r Link.h Link.cpp
* @endcode
*
* You can add a new parameter by adding lines in LinkParams.py. Available
* parameter types are 'Int, UInt, String, Bool, Float'. For example, to add
* a new Int type parameter,
* @code
* ParamInt(parameter_name, default_value, documentation, on_change=False)
* @endcode
*
* If there is special handling on parameter change, pass in on_change=True.
* And you need to provide a function implementation in Link.cpp with
* the following signature.
* @code
* void LinkParams:on<parameter_name>Changed()
* @endcode
*/
class AppExport LinkParams {
public:
static ParameterGrp::handle getHandle();
//@{
/** Accessor for parameter CopyOnChangeApplyToAll
*
* Stores the last user choice of whether to apply CopyOnChange setup to all link
* that links to the same configurable object
*/
static const bool & getCopyOnChangeApplyToAll();
static const bool & defaultCopyOnChangeApplyToAll();
static void removeCopyOnChangeApplyToAll();
static void setCopyOnChangeApplyToAll(const bool &v);
static const char *docCopyOnChangeApplyToAll();
//@}
};
} // namespace App
//[[[end]]]
#if defined(__clang__)
# pragma clang diagnostic pop

30
src/App/LinkParams.py Normal file
View File

@@ -0,0 +1,30 @@
import sys
from os import sys, path
# Actual code generation is done in Base/param_utils.py.
# The following code is to import param_util.py without needing __init__.py in Base directory
sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'Base'))
import params_utils
from params_utils import ParamBool, ParamInt, ParamString, ParamUInt, ParamFloat
NameSpace = 'App'
ClassName = 'LinkParams'
ParamPath = 'User parameter:BaseApp/Preferences/Link'
ClassDoc = 'Convenient class to obtain App::Link related parameters'
HeaderFile = 'Link.h'
SourceFile = 'Link.cpp'
Params = [
ParamBool('CopyOnChangeApplyToAll', True, '''\
Stores the last user choice of whether to apply CopyOnChange setup to all link
that links to the same configurable object'''),
]
def declare():
params_utils.declare_begin(sys.modules[__name__], header=False)
params_utils.declare_end(sys.modules[__name__])
def define():
params_utils.define(sys.modules[__name__], header=False)

View File

@@ -45,6 +45,7 @@
#include <QMenu>
#endif
#include <boost/range.hpp>
#include <App/ComplexGeoData.h>
#include <App/Document.h>
#include <Base/BoundBoxPy.h>
@@ -67,12 +68,15 @@
#include "ViewParams.h"
#include "ViewProviderGeometryObject.h"
#include "ActionFunction.h"
#include "Command.h"
FC_LOG_LEVEL_INIT("App::Link", true, true)
using namespace Gui;
using namespace Base;
typedef boost::iterator_range<const char*> CharRange;
////////////////////////////////////////////////////////////////////////////
static inline bool appendPathSafe(SoPath *path, SoNode *node) {
@@ -218,6 +222,10 @@ public:
return pcLinked->getObject()->getNameInDocument();
}
const char *getLinkedLabel() const {
return pcLinked->getObject()->Label.getValue();
}
const char *getLinkedNameSafe() const {
if(isLinked())
return getLinkedName();
@@ -965,7 +973,7 @@ void LinkView::renderDoubleSide(bool enable) {
if(enable) {
if(!pcShapeHints) {
pcShapeHints = new SoShapeHints;
pcShapeHints->vertexOrdering = SoShapeHints::UNKNOWN_ORDERING;
pcShapeHints->vertexOrdering = SoShapeHints::CLOCKWISE;
pcShapeHints->shapeType = SoShapeHints::UNKNOWN_SHAPE_TYPE;
pcLinkRoot->insertChild(pcShapeHints,0);
}
@@ -1485,14 +1493,53 @@ bool LinkView::linkGetDetailPath(const char *subname, SoFullPath *path, SoDetail
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(!appendPathSafe(path,pcLinkRoot))
return false;
} else {
int idx = -1;
if (subname[0]>='0' && subname[0]<='9') {
idx = App::LinkBaseExtension::getArrayIndex(subname,&subname);
} else {
while(1) {
const char *dot = strchr(subname,'.');
if(!dot)
break;
int i = 0;
if (subname[0] == '$') {
CharRange name(subname+1,dot);
for(auto &info : nodeArray) {
if(info->isLinked() && boost::equals(name,info->linkInfo->getLinkedLabel())) {
idx = i;
break;
}
++i;
}
} else {
CharRange name(subname,dot);
for(auto &info : nodeArray) {
if(info->isLinked() && boost::equals(name,info->linkInfo->getLinkedName())) {
idx = i;
break;
}
++i;
}
}
if(idx<0)
return false;
subname = dot+1;
if(!subname[0] || nodeArray[idx]->isGroup==0)
break;
idx = -1;
}
}
if(idx<0 || idx>=(int)nodeArray.size())
return false;
auto &info = *nodeArray[idx];
appendPath(path,pcLinkRoot);
if(!appendPathSafe(path,pcLinkRoot))
return false;
if(info.groupIndex>=0 && !getGroupHierarchy(info.groupIndex,path))
return false;
appendPath(path,info.pcSwitch);
@@ -1561,7 +1608,7 @@ void LinkView::unlink(LinkInfoPtr info) {
else {
for(auto &info : nodeArray) {
int idx;
if(!info->isLinked() &&
if(info->isLinked() &&
(idx=info->pcRoot->findChild(pcLinkedRoot))>=0)
info->pcRoot->removeChild(idx);
}
@@ -1792,6 +1839,10 @@ void ViewProviderLink::updateData(const App::Property *prop) {
return inherited::updateData(prop);
}
static inline bool canScale(const Base::Vector3d &v) {
return fabs(v.x)>1e-7 && fabs(v.y)>1e-7 && fabs(v.z)>1e-7;
}
void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App::Property *prop) {
if(!prop) return;
if(prop == &ext->_ChildCache) {
@@ -1807,8 +1858,10 @@ void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App:
}else if(prop==ext->getScaleProperty() || prop==ext->getScaleVectorProperty()) {
if(!prop->testStatus(App::Property::User3)) {
const auto &v = ext->getScaleVector();
pcTransform->scaleFactor.setValue(v.x,v.y,v.z);
linkView->renderDoubleSide(v.x*v.y*v.z < 0);
if(canScale(v))
pcTransform->scaleFactor.setValue(v.x,v.y,v.z);
SbMatrix matrix = convert(ext->getTransform(false));
linkView->renderDoubleSide(matrix.det3() < 0);
}
}else if(prop == ext->getPlacementProperty() || prop == ext->getLinkPlacementProperty()) {
auto propLinkPlacement = ext->getLinkPlacementProperty();
@@ -1816,8 +1869,19 @@ void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App:
const auto &pla = static_cast<const App::PropertyPlacement*>(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);
if(canScale(v))
pcTransform->scaleFactor.setValue(v.x,v.y,v.z);
SbMatrix matrix = convert(ext->getTransform(false));
linkView->renderDoubleSide(matrix.det3() < 0);
}
}else if(prop == ext->getLinkCopyOnChangeGroupProperty()) {
if (auto group = ext->getLinkCopyOnChangeGroupValue()) {
auto vp = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(
Application::Instance->getViewProvider(group));
if (vp) {
vp->hide();
vp->ShowInTree.setValue(false);
}
}
}else if(prop == ext->getLinkedObjectProperty()) {
@@ -1918,9 +1982,9 @@ void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App:
if(touched.empty()) {
for(int i=0;i<linkView->getSize();++i) {
Base::Matrix4D mat;
if(propPlacements->getSize()>i)
if(propPlacements && propPlacements->getSize()>i)
mat = (*propPlacements)[i].toMatrix();
if(propScales && propScales->getSize()>i) {
if(propScales && propScales->getSize()>i && canScale((*propScales)[i])) {
Base::Matrix4D s;
s.scale((*propScales)[i]);
mat *= s;
@@ -1932,9 +1996,9 @@ void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App:
if(i<0 || i>=linkView->getSize())
continue;
Base::Matrix4D mat;
if(propPlacements->getSize()>i)
if(propPlacements && propPlacements->getSize()>i)
mat = (*propPlacements)[i].toMatrix();
if(propScales && propScales->getSize()>i) {
if(propScales && propScales->getSize()>i && canScale((*propScales)[i])) {
Base::Matrix4D s;
s.scale((*propScales)[i]);
mat *= s;
@@ -2076,24 +2140,33 @@ ViewProvider *ViewProviderLink::getLinkedView(
std::vector<App::DocumentObject*> ViewProviderLink::claimChildren(void) const {
auto ext = getLinkExtension();
std::vector<App::DocumentObject*> ret;
if(ext && !ext->_getShowElementValue() && ext->_getElementCountValue()) {
// in array mode without element objects, we'd better not show the
// linked object's children to avoid inconsistent behavior on selection.
// We claim the linked object instead
std::vector<App::DocumentObject*> ret;
if(ext) {
auto obj = ext->getTrueLinkedObject(true);
auto obj = ext->getLinkedObjectValue();
if(obj) ret.push_back(obj);
}
return ret;
} else if(hasElements(ext) || isGroup(ext))
return ext->getElementListValue();
if(!hasSubName) {
} else if(hasElements(ext) || isGroup(ext)) {
ret = ext->getElementListValue();
if (ext->_getElementCountValue()
&& ext->getLinkClaimChildValue()
&& ext->getLinkedObjectValue())
ret.insert(ret.begin(), ext->getLinkedObjectValue());
} else if(!hasSubName) {
auto linked = getLinkedView(true);
if(linked)
return linked->claimChildren();
if(linked) {
ret = linked->claimChildren();
if (ext->getLinkClaimChildValue() && ext->getLinkedObjectValue())
ret.insert(ret.begin(), ext->getLinkedObjectValue());
}
}
return std::vector<App::DocumentObject*>();
if (ext && ext->getLinkCopyOnChangeGroupValue())
ret.insert(ret.begin(), ext->getLinkCopyOnChangeGroupValue());
return ret;
}
bool ViewProviderLink::canDragObject(App::DocumentObject* obj) const {
@@ -2279,12 +2352,29 @@ bool ViewProviderLink::getDetailPath(
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(subname && subname[0]) {
if (auto linked = ext->getLinkedObjectValue()) {
if (const char *dot = strchr(subname,'.')) {
if(subname[0]=='$') {
CharRange sub(subname+1, dot);
if (!boost::equals(sub, linked->Label.getValue()))
dot = nullptr;
} else {
CharRange sub(subname, dot);
if (!boost::equals(sub, linked->getNameInDocument()))
dot = nullptr;
}
if (dot && linked->getSubObject(dot+1))
subname = dot+1;
}
}
if (isGroup(ext,true) || hasElements(ext) || ext->getElementCountValue()) {
int index = ext->getElementIndex(subname,&subname);
if(index>=0) {
_subname = std::to_string(index)+'.'+subname;
subname = _subname.c_str();
}
}
}
if(linkView->linkGetDetailPath(subname,pPath,det))
@@ -2295,7 +2385,27 @@ bool ViewProviderLink::getDetailPath(
bool ViewProviderLink::onDelete(const std::vector<std::string> &) {
auto element = freecad_dynamic_cast<App::LinkElement>(getObject());
return !element || element->canDelete();
if (element && !element->canDelete())
return false;
auto ext = getLinkExtension();
if (ext->isLinkMutated()) {
auto linked = ext->getLinkedObjectValue();
auto doc = ext->getContainer()->getDocument();
if (linked->getDocument() == doc) {
std::deque<std::string> objs;
for (auto obj : ext->getOnChangeCopyObjects(nullptr, linked)) {
if (obj->getDocument() == doc) {
// getOnChangeCopyObjects() returns object in depending
// order. So we delete it in reverse to avoid error
// reported by some parent object failing to find child
objs.emplace_front(obj->getNameInDocument());
}
}
for (auto &name : objs)
doc->removeObject(name.c_str());
}
}
return true;
}
bool ViewProviderLink::canDelete(App::DocumentObject *obj) const {
@@ -2334,12 +2444,122 @@ void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const ch
if (!ext)
return;
_setupContextMenu(ext, menu, receiver, member);
Gui::ActionFunction* func = nullptr;
if (ext->isLinkedToConfigurableObject()) {
if (ext->getLinkCopyOnChangeValue() == 0) {
auto submenu = menu->addMenu(QObject::tr("Copy on change"));
auto act = submenu->addAction(QObject::tr("Enable"));
act->setToolTip(QObject::tr(
"Enable auto copy of linked object when its configuration is changed"));
act->setData(-1);
if (!func) func = new Gui::ActionFunction(menu);
func->trigger(act, [ext](){
try {
App::AutoTransaction guard("Enable Link copy on change");
ext->getLinkCopyOnChangeProperty()->setValue(1);
Command::updateActive();
} catch (Base::Exception &e) {
e.ReportException();
}
});
act = submenu->addAction(QObject::tr("Tracking"));
act->setToolTip(QObject::tr(
"Copy the linked object when its configuration is changed.\n"
"Also auto redo the copy if the original linked object is changed.\n"));
act->setData(-1);
func->trigger(act, [ext](){
try {
App::AutoTransaction guard("Enable Link tracking");
ext->getLinkCopyOnChangeProperty()->setValue(3);
Command::updateActive();
} catch (Base::Exception &e) {
e.ReportException();
}
});
}
}
if (ext->getLinkCopyOnChangeValue() != 2
&& ext->getLinkCopyOnChangeValue() != 0) {
QAction *act = menu->addAction(
QObject::tr("Disable copy on change"));
act->setData(-1);
if (!func) func = new Gui::ActionFunction(menu);
func->trigger(act, [ext](){
try {
App::AutoTransaction guard("Disable copy on change");
ext->getLinkCopyOnChangeProperty()->setValue((long)0);
Command::updateActive();
} catch (Base::Exception &e) {
e.ReportException();
}
});
}
if (ext->isLinkMutated()) {
QAction* act = menu->addAction(QObject::tr("Rerefresh configurable object"));
act->setToolTip(QObject::tr(
"Synchronize the original configurable source object by\n"
"creating a new deep copy. Note that any changes made to\n"
"the current copy will be lost.\n"));
act->setData(-1);
if (!func) func = new Gui::ActionFunction(menu);
func->trigger(act, [ext](){
try {
App::AutoTransaction guard("Link refresh");
ext->syncCopyOnChange();
Command::updateActive();
} catch (Base::Exception &e) {
e.ReportException();
}
});
}
}
void ViewProviderLink::_setupContextMenu(
App::LinkBaseExtension *ext, QMenu* menu, QObject* receiver, const char* member)
{
if(linkEdit(ext)) {
linkView->getLinkedView()->setupContextMenu(menu,receiver,member);
} else if(ext->getPlacementProperty() || ext->getLinkPlacementProperty()) {
QIcon iconObject = mergeGreyableOverlayIcons(Gui::BitmapFactory().pixmap("Std_TransformManip.svg"));
QAction* act = menu->addAction(iconObject, QObject::tr("Transform"), receiver, member);
act->setData(QVariant((int)ViewProvider::Transform));
if (auto linkvp = Base::freecad_dynamic_cast<ViewProviderLink>(linkView->getLinkedView()))
linkvp->_setupContextMenu(ext, menu, receiver, member);
else
linkView->getLinkedView()->setupContextMenu(menu,receiver,member);
}
if(ext->getLinkedObjectProperty()
&& ext->_getShowElementProperty()
&& ext->_getElementCountValue() > 1)
{
auto action = menu->addAction(QObject::tr("Toggle array elements"), [ext] {
try {
App::AutoTransaction guard(QT_TRANSLATE_NOOP("Command", "Toggle array elements"));
ext->getShowElementProperty()->setValue(!ext->getShowElementValue());
Command::updateActive();
} catch (Base::Exception &e) {
e.ReportException();
}
});
action->setToolTip(QObject::tr(
"Change whether show each link array element as individual objects"));
}
if((ext->getPlacementProperty() && !ext->getPlacementProperty()->isReadOnly())
|| (ext->getLinkPlacementProperty() && !ext->getLinkPlacementProperty()->isReadOnly()))
{
bool found = false;
for(auto action : menu->actions()) {
if(action->data().toInt() == ViewProvider::Transform) {
found = true;
break;
}
}
if (!found) {
QIcon iconObject = mergeGreyableOverlayIcons(Gui::BitmapFactory().pixmap("Std_TransformManip.svg"));
QAction* act = menu->addAction(iconObject, QObject::tr("Transform"), receiver, member);
act->setToolTip(QObject::tr("Transform at the origin of the placement"));
act->setData(QVariant((int)ViewProvider::Transform));
}
}
if(ext->getColoredElementsProperty()) {

View File

@@ -273,6 +273,7 @@ protected:
void setEditViewer(View3DInventorViewer*, int ModNum) override;
void unsetEditViewer(View3DInventorViewer*) override;
bool linkEdit(const App::LinkBaseExtension *ext=nullptr) const;
void _setupContextMenu(App::LinkBaseExtension *ext, QMenu*, QObject*, const char*);
enum LinkType {
LinkTypeNone,