"Professional CMake" book suggest the following: "Targets should build successfully with or without compiler support for precompiled headers. It should be considered an optimization, not a requirement. In particular, do not explicitly include a precompile header (e.g. stdafx.h) in the source code, let CMake force-include an automatically generated precompile header on the compiler command line instead. This is more portable across the major compilers and is likely to be easier to maintain. It will also avoid warnings being generated from certain code checking tools like iwyu (include what you use)." Therefore, removed the "#include <PreCompiled.h>" from sources, also there is no need for the "#ifdef _PreComp_" anymore
2743 lines
94 KiB
C++
2743 lines
94 KiB
C++
/****************************************************************************
|
|
* Copyright (c) 2017 Zheng Lei (realthunder) <realthunder.dev@gmail.com> *
|
|
* *
|
|
* This file is part of the FreeCAD CAx development system. *
|
|
* *
|
|
* This library is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU Library General Public *
|
|
* License as published by the Free Software Foundation; either *
|
|
* version 2 of the License, or (at your option) any later version. *
|
|
* *
|
|
* This library is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU Library General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Library General Public *
|
|
* License along with this library; see the file COPYING.LIB. If not, *
|
|
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
|
* Suite 330, Boston, MA 02111-1307, USA *
|
|
* *
|
|
****************************************************************************/
|
|
|
|
#include <limits>
|
|
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
#include <boost/range.hpp>
|
|
#include <boost/property_map/property_map.hpp>
|
|
|
|
#include <Base/Tools.h>
|
|
#include <Base/Uuid.h>
|
|
|
|
#include "Application.h"
|
|
#include "ElementNamingUtils.h"
|
|
#include "ComplexGeoDataPy.h"
|
|
#include "Document.h"
|
|
#include "DocumentObserver.h"
|
|
#include "GeoFeatureGroupExtension.h"
|
|
#include "Link.h"
|
|
#include "LinkBaseExtensionPy.h"
|
|
|
|
// FIXME: ISO C++11 requires at least one argument for the "..." in a variadic macro
|
|
#if defined(__clang__)
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
|
|
#endif
|
|
|
|
FC_LOG_LEVEL_INIT("App::Link", true, true)
|
|
|
|
using namespace App;
|
|
using namespace Base;
|
|
namespace sp = std::placeholders;
|
|
|
|
using CharRange = boost::iterator_range<const char*>;
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
/*[[[cog
|
|
import LinkParams
|
|
LinkParams.define()
|
|
]]]*/
|
|
|
|
namespace
|
|
{
|
|
|
|
// 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() override = default;
|
|
|
|
// Auto generated code. See class document of LinkParams.
|
|
void OnChange(Base::Subject<const char*>&, const char* sReason) override
|
|
{
|
|
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;
|
|
}
|
|
|
|
} // Anonymous namespace
|
|
|
|
// 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 links\n"
|
|
"that reference 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()
|
|
{
|
|
static const 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()
|
|
{
|
|
initExtensionType(LinkBaseExtension::getExtensionClassTypeId());
|
|
EXTENSION_ADD_PROPERTY_TYPE(_LinkTouched,
|
|
(false),
|
|
" Link",
|
|
PropertyType(Prop_Hidden | Prop_NoPersist),
|
|
0);
|
|
EXTENSION_ADD_PROPERTY_TYPE(_ChildCache,
|
|
(),
|
|
" Link",
|
|
PropertyType(Prop_Hidden | Prop_NoPersist | Prop_ReadOnly),
|
|
0);
|
|
_ChildCache.setScope(LinkScope::Global);
|
|
EXTENSION_ADD_PROPERTY_TYPE(_LinkOwner,
|
|
(0),
|
|
" Link",
|
|
PropertyType(Prop_Hidden | Prop_Output),
|
|
0);
|
|
props.resize(PropMax, nullptr);
|
|
}
|
|
|
|
PyObject* LinkBaseExtension::getExtensionPyObject()
|
|
{
|
|
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::PropInfo>& LinkBaseExtension::getPropertyInfo() const
|
|
{
|
|
static std::vector<LinkBaseExtension::PropInfo> 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 nullptr;
|
|
}
|
|
|
|
Property* LinkBaseExtension::getProperty(const char* name)
|
|
{
|
|
const auto& info = getPropertyInfoMap();
|
|
auto it = info.find(name);
|
|
if (it == info.end()) {
|
|
return nullptr;
|
|
}
|
|
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] = nullptr;
|
|
}
|
|
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",
|
|
nullptr};
|
|
auto propLinkMode = static_cast<PropertyEnumeration*>(prop);
|
|
if (!propLinkMode->hasEnums()) {
|
|
propLinkMode->setEnums(linkModeEnums);
|
|
}
|
|
break;
|
|
}
|
|
case PropLinkCopyOnChange: {
|
|
static const char* enums[] = {"Disabled", "Enabled", "Owned", "Tracking", nullptr};
|
|
auto propEnum = static_cast<PropertyEnumeration*>(prop);
|
|
if (!propEnum->hasEnums()) {
|
|
propEnum->setEnums(enums);
|
|
}
|
|
break;
|
|
}
|
|
case PropLinkCopyOnChangeSource:
|
|
case PropLinkCopyOnChangeGroup:
|
|
if (auto linkProp = freecad_cast<PropertyLinkBase*>(prop)) {
|
|
linkProp->setScope(LinkScope::Global);
|
|
}
|
|
// fall through
|
|
case PropLinkCopyOnChangeTouched:
|
|
prop->setStatus(Property::Hidden, true);
|
|
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()->setScope(LinkScope::Global);
|
|
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() != nullptr);
|
|
}
|
|
if (auto linkProp = getLinkedObjectProperty()) {
|
|
linkProp->setScope(LinkScope::Global);
|
|
}
|
|
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 = "<null>";
|
|
}
|
|
else if (prop->getContainer()) {
|
|
propName = prop->getName();
|
|
}
|
|
else {
|
|
propName = extensionGetPropertyName(prop);
|
|
}
|
|
if (!Property::isValidName(propName)) {
|
|
propName = "?";
|
|
}
|
|
FC_TRACE("set property " << infos[idx].name << ": " << propName);
|
|
}
|
|
}
|
|
|
|
static const char _GroupPrefix[] = "Configuration (";
|
|
|
|
App::DocumentObjectExecReturn* LinkBaseExtension::extensionExecute()
|
|
{
|
|
// 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) {
|
|
std::ostringstream ss;
|
|
ss << "Link broken!";
|
|
auto xlink = freecad_cast<PropertyXLink*>(getLinkedObjectProperty());
|
|
if (xlink) {
|
|
const char* objname = xlink->getObjectName();
|
|
if (!Base::Tools::isNullOrEmpty(objname)) {
|
|
ss << "\nObject: " << objname;
|
|
}
|
|
const char* filename = xlink->getFilePath();
|
|
if (!Base::Tools::isNullOrEmpty(filename)) {
|
|
ss << "\nFile: " << filename;
|
|
}
|
|
}
|
|
return new App::DocumentObjectExecReturn(ss.str().c_str());
|
|
}
|
|
|
|
App::DocumentObject* container = getContainer();
|
|
auto source = getLinkCopyOnChangeSourceValue();
|
|
if (source && getLinkCopyOnChangeValue() == CopyOnChangeTracking
|
|
&& getLinkCopyOnChangeTouchedValue()) {
|
|
syncCopyOnChange();
|
|
}
|
|
|
|
// the previous linked object could be deleted by syncCopyOnChange - #12281
|
|
linked = getTrueLinkedObject(true);
|
|
if (!linked) {
|
|
return new App::DocumentObjectExecReturn("Error in processing variable link");
|
|
}
|
|
|
|
PropertyPythonObject* proxy = nullptr;
|
|
if (getLinkExecuteProperty() && !boost::iequals(getLinkExecuteValue(), "none")
|
|
&& (!_LinkOwner.getValue()
|
|
|| !container->getDocument()->getObjectByID(_LinkOwner.getValue()))) {
|
|
// Check if this is an element link. Do not invoke appLinkExecute()
|
|
// if so, because it will be called from the link array.
|
|
proxy = freecad_cast<PropertyPythonObject*>(
|
|
linked->getPropertyByName("Proxy"));
|
|
}
|
|
if (proxy) {
|
|
Base::PyGILStateLocker lock;
|
|
const char* errMsg = "Linked proxy execute failed";
|
|
try {
|
|
Py::Tuple args(3);
|
|
Py::Object proxyValue = proxy->getValue();
|
|
const char* method = getLinkExecuteValue();
|
|
if (!method || !method[0]) {
|
|
method = "appLinkExecute";
|
|
}
|
|
if (proxyValue.hasAttr(method)) {
|
|
Py::Object attr = proxyValue.getAttr(method);
|
|
if (attr.ptr() && attr.isCallable()) {
|
|
Py::Tuple args(4);
|
|
args.setItem(0, Py::asObject(linked->getPyObject()));
|
|
args.setItem(1, Py::asObject(container->getPyObject()));
|
|
if (!_getElementCountValue()) {
|
|
Py::Callable(attr).apply(args);
|
|
}
|
|
else {
|
|
const auto& elements = _getElementListValue();
|
|
for (int i = 0; i < _getElementCountValue(); ++i) {
|
|
args.setItem(2, Py::Long(i));
|
|
if (i < (int)elements.size()) {
|
|
args.setItem(3, Py::asObject(elements[i]->getPyObject()));
|
|
}
|
|
else {
|
|
args.setItem(3, Py::Object());
|
|
}
|
|
Py::Callable(attr).apply(args);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
return new App::DocumentObjectExecReturn(errMsg);
|
|
}
|
|
catch (Base::Exception& e) {
|
|
e.reportException();
|
|
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();
|
|
}
|
|
|
|
short LinkBaseExtension::extensionMustExecute()
|
|
{
|
|
auto link = getLink();
|
|
if (!link) {
|
|
return 0;
|
|
}
|
|
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 =
|
|
freecad_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 =
|
|
freecad_cast<PropertyMap*>(obj->getPropertyByName("_CopyOnChangeControl"));
|
|
|
|
if (external == exclude && !prop) {
|
|
return;
|
|
}
|
|
|
|
if (!prop) {
|
|
try {
|
|
prop = static_cast<PropertyMap*>(
|
|
obj->addDynamicProperty("App::PropertyMap", "_CopyOnChangeControl"));
|
|
}
|
|
catch (Base::Exception& e) {
|
|
e.reportException();
|
|
}
|
|
if (!prop) {
|
|
FC_ERR("Failed to setup copy on change object " << obj->getFullName());
|
|
return;
|
|
}
|
|
}
|
|
|
|
const char* key = flags.testFlag(OnChangeCopyOptions::ApplyAll) ? "*" : parent->getDagKey();
|
|
if (external) {
|
|
prop->setValue(key, exclude ? "" : "+");
|
|
}
|
|
else {
|
|
prop->setValue(key, exclude ? "-" : "");
|
|
}
|
|
}
|
|
|
|
// 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 = freecad_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->isAttachedToDocument()) {
|
|
continue;
|
|
}
|
|
auto prop =
|
|
freecad_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 copying 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 arbitrary changes in the originals
|
|
// which may add or remove or change depending orders, while the
|
|
// replacement happens between the new and old copies.
|
|
|
|
std::map<Base::Uuid, App::DocumentObjectT> newObjs;
|
|
for (auto obj : copiedObjs) {
|
|
auto prop = freecad_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 = freecad_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 = freecad_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;
|
|
}
|
|
auto group = prop.getGroup();
|
|
return group && boost::starts_with(group, _GroupPrefix);
|
|
}
|
|
|
|
void LinkBaseExtension::setupCopyOnChange(DocumentObject* parent, bool checkSource)
|
|
{
|
|
copyOnChangeConns.clear();
|
|
copyOnChangeSrcConns.clear();
|
|
|
|
auto linked = getTrueLinkedObject(false);
|
|
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, ©OnChangeConns, hasCopyOnChange);
|
|
if (hasCopyOnChange && getLinkCopyOnChangeValue() == CopyOnChangeOwned && getLinkedObjectValue()
|
|
&& getLinkedObjectValue() == getLinkCopyOnChangeSourceValue()) {
|
|
makeCopyOnChange();
|
|
}
|
|
}
|
|
|
|
bool LinkBaseExtension::setupCopyOnChange(
|
|
DocumentObject* parent,
|
|
DocumentObject* linked,
|
|
std::vector<boost::signals2::scoped_connection>* copyOnChangeConns,
|
|
bool checkExisting)
|
|
{
|
|
if (!parent || !linked) {
|
|
return false;
|
|
}
|
|
|
|
bool res = false;
|
|
|
|
std::unordered_map<Property*, Property*> newProps;
|
|
std::vector<Property*> props;
|
|
linked->getPropertyList(props);
|
|
for (auto prop : props) {
|
|
if (!prop->testStatus(Property::CopyOnChange) || prop->getContainer() != linked) {
|
|
continue;
|
|
}
|
|
|
|
res = true;
|
|
|
|
const char* linkedGroupName = prop->getGroup();
|
|
if (!linkedGroupName || !linkedGroupName[0]) {
|
|
linkedGroupName = "Base";
|
|
}
|
|
|
|
std::string groupName;
|
|
groupName = _GroupPrefix;
|
|
if (boost::starts_with(linkedGroupName, _GroupPrefix)) {
|
|
groupName += linkedGroupName + sizeof(_GroupPrefix) - 1;
|
|
}
|
|
else {
|
|
groupName += linkedGroupName;
|
|
groupName += ")";
|
|
}
|
|
|
|
auto p = parent->getPropertyByName(prop->getName());
|
|
if (p) {
|
|
if (p->getContainer() != parent) {
|
|
p = nullptr;
|
|
}
|
|
else {
|
|
const char* otherGroupName = p->getGroup();
|
|
if (!otherGroupName || !boost::starts_with(otherGroupName, _GroupPrefix)) {
|
|
FC_WARN(p->getFullName()
|
|
<< " shadows another CopyOnChange property " << prop->getFullName());
|
|
continue;
|
|
}
|
|
if (p->getTypeId() != prop->getTypeId() || groupName != otherGroupName) {
|
|
parent->removeDynamicProperty(p->getName());
|
|
p = nullptr;
|
|
}
|
|
}
|
|
}
|
|
if (!p) {
|
|
p = parent->addDynamicProperty(prop->getTypeId().getName(),
|
|
prop->getName(),
|
|
groupName.c_str(),
|
|
prop->getDocumentation());
|
|
std::unique_ptr<Property> pcopy(prop->Copy());
|
|
Base::ObjectStatusLocker<Property::Status, Property> guard(Property::User3, p);
|
|
if (pcopy) {
|
|
p->Paste(*pcopy);
|
|
}
|
|
p->setStatusValue(prop->getStatus());
|
|
}
|
|
newProps[p] = prop;
|
|
}
|
|
|
|
if (checkExisting) {
|
|
props.clear();
|
|
parent->getPropertyList(props);
|
|
for (auto prop : props) {
|
|
if (prop->getContainer() != parent) {
|
|
continue;
|
|
}
|
|
auto gname = prop->getGroup();
|
|
if (!gname || !boost::starts_with(gname, _GroupPrefix)) {
|
|
continue;
|
|
}
|
|
if (!newProps.contains(prop)) {
|
|
parent->removeDynamicProperty(prop->getName());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!copyOnChangeConns) {
|
|
return res;
|
|
}
|
|
|
|
for (const auto& v : newProps) {
|
|
// sync configuration properties
|
|
copyOnChangeConns->push_back(
|
|
v.second->signalChanged.connect([parent](const Property& prop) {
|
|
if (!prop.testStatus(Property::CopyOnChange)) {
|
|
return;
|
|
}
|
|
auto p = parent->getPropertyByName(prop.getName());
|
|
if (p && p->getTypeId() == prop.getTypeId()) {
|
|
std::unique_ptr<Property> pcopy(prop.Copy());
|
|
// temperoray set Output to prevent touching
|
|
p->setStatus(Property::Output, true);
|
|
// temperoray block copy on change
|
|
Base::ObjectStatusLocker<Property::Status, Property> guard(Property::User3, p);
|
|
if (pcopy) {
|
|
p->Paste(*pcopy);
|
|
}
|
|
p->setStatusValue(prop.getStatus());
|
|
}
|
|
}));
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
void LinkBaseExtension::checkCopyOnChange(App::DocumentObject* parent, const App::Property& prop)
|
|
{
|
|
if (!parent || !parent->getDocument() || parent->getDocument()->isPerformingTransaction()) {
|
|
return;
|
|
}
|
|
|
|
auto linked = getLinkedObjectValue();
|
|
if (!linked || getLinkCopyOnChangeValue() == CopyOnChangeDisabled
|
|
|| !isCopyOnChangeProperty(parent, prop)) {
|
|
return;
|
|
}
|
|
|
|
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());
|
|
if (pcopy) {
|
|
p->Paste(*pcopy);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
auto linkedProp = linked->getPropertyByName(prop.getName());
|
|
if (!linkedProp || linkedProp->getTypeId() != prop.getTypeId() || linkedProp->isSame(prop)) {
|
|
return;
|
|
}
|
|
|
|
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 nullptr;
|
|
}
|
|
|
|
monitorOnChangeCopyObjects(srcobjs);
|
|
|
|
linked = objs.back();
|
|
linked->Visibility.setValue(false);
|
|
|
|
Base::StateLocker guard(pauseCopyOnChange);
|
|
getLinkedObjectProperty()->setValue(linked);
|
|
if (getLinkCopyOnChangeValue() == CopyOnChangeEnabled) {
|
|
getLinkCopyOnChangeProperty()->setValue(CopyOnChangeOwned);
|
|
}
|
|
|
|
if (auto prop = getLinkCopyOnChangeGroupProperty()) {
|
|
if (auto obj = prop->getValue()) {
|
|
if (obj->isAttachedToDocument() && 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.emplace_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
|
|
{
|
|
if (!mySubElements.empty() && !mySubElements[0].empty()) {
|
|
return nullptr;
|
|
}
|
|
auto linked = getTrueLinkedObject(false);
|
|
if (!linked) {
|
|
return nullptr;
|
|
}
|
|
return linked->getExtensionByType<GroupExtension>(true, false);
|
|
}
|
|
|
|
App::PropertyLinkList* LinkBaseExtension::_getElementListProperty() const
|
|
{
|
|
auto group = linkedPlainGroup();
|
|
if (group) {
|
|
return &group->Group;
|
|
}
|
|
return const_cast<PropertyLinkList*>(getElementListProperty());
|
|
}
|
|
|
|
const std::vector<App::DocumentObject*>& LinkBaseExtension::_getElementListValue() const
|
|
{
|
|
if (_ChildCache.getSize()) {
|
|
return _ChildCache.getValues();
|
|
}
|
|
if (getElementListProperty()) {
|
|
return getElementListProperty()->getValues();
|
|
}
|
|
static const std::vector<DocumentObject*> empty;
|
|
return empty;
|
|
}
|
|
|
|
App::PropertyBool* LinkBaseExtension::_getShowElementProperty() const
|
|
{
|
|
auto prop = getShowElementProperty();
|
|
if (prop && !linkedPlainGroup()) {
|
|
return const_cast<App::PropertyBool*>(prop);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
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<App::PropertyInteger*>(prop);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
int LinkBaseExtension::_getElementCountValue() const
|
|
{
|
|
auto prop = _getElementCountProperty();
|
|
if (prop) {
|
|
return prop->getValue();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool LinkBaseExtension::extensionHasChildElement() const
|
|
{
|
|
if (!_getElementListValue().empty() || (_getElementCountValue() && _getShowElementValue())) {
|
|
return true;
|
|
}
|
|
if (getLinkClaimChildValue()) {
|
|
return false;
|
|
}
|
|
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>()) {
|
|
LINK_THROW(Base::RuntimeError, "Link: container not derived from document object");
|
|
}
|
|
return static_cast<const DocumentObject*>(ext);
|
|
}
|
|
|
|
DocumentObject* LinkBaseExtension::getContainer()
|
|
{
|
|
auto ext = getExtendedContainer();
|
|
if (!ext || !ext->isDerivedFrom<DocumentObject>()) {
|
|
LINK_THROW(Base::RuntimeError, "Link: container not derived from document object");
|
|
}
|
|
return static_cast<DocumentObject*>(ext);
|
|
}
|
|
|
|
DocumentObject* LinkBaseExtension::getLink(int depth) const
|
|
{
|
|
if (!GetApplication().checkLinkDepth(depth, MessageOption::Error)) {
|
|
return nullptr;
|
|
}
|
|
if (getLinkedObjectProperty()) {
|
|
return getLinkedObjectValue();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
int LinkBaseExtension::getArrayIndex(const char* subname, const char** psubname)
|
|
{
|
|
if (!subname || Data::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::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, nullptr);
|
|
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->isAttachedToDocument()) {
|
|
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, nullptr);
|
|
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->isAttachedToDocument()) {
|
|
return -1;
|
|
}
|
|
if (subname[0] == '$') {
|
|
CharRange sub(subname + 1, dot);
|
|
if (boost::equals(sub, linked->Label.getValue())) {
|
|
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(std::string(subname, dot - subname + 1).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.findUsingMap(name, &idx);
|
|
if (obj) {
|
|
auto group = obj->getExtensionByType<GroupExtension>(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<GroupExtension>(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<std::string>& ret, int reason) const
|
|
{
|
|
if (!getLinkedObjectProperty() && getElementListProperty()) {
|
|
for (auto obj : getElementListProperty()->getValues()) {
|
|
if (obj && obj->isAttachedToDocument()) {
|
|
std::string name(obj->getNameInDocument());
|
|
name += '.';
|
|
ret.push_back(name);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
if (mySubElements.empty() || mySubElements[0].empty()) {
|
|
DocumentObject* linked = getTrueLinkedObject(true);
|
|
if (linked) {
|
|
if (!_getElementCountValue()) {
|
|
ret = linked->getSubObjects(reason);
|
|
}
|
|
else {
|
|
char index[30];
|
|
for (int i = 0, count = _getElementCountValue(); i < count; ++i) {
|
|
snprintf(index, sizeof(index), "%d.", i);
|
|
ret.emplace_back(index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (mySubElements.size() > 1) {
|
|
ret = mySubElements;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LinkBaseExtension::extensionGetSubObject(DocumentObject*& ret,
|
|
const char* subname,
|
|
PyObject** pyObj,
|
|
Base::Matrix4D* mat,
|
|
bool transform,
|
|
int depth) const
|
|
{
|
|
ret = nullptr;
|
|
auto obj = getContainer();
|
|
if (!subname || !subname[0]) {
|
|
ret = const_cast<DocumentObject*>(obj);
|
|
Base::Matrix4D _mat;
|
|
if (mat) {
|
|
// 'mat' here is used as an output to return the accumulated
|
|
// transformation up until this object. Since 'subname' is empty
|
|
// here, it means the we are at the end of the hierarchy. We shall
|
|
// not include scale in the output transformation.
|
|
//
|
|
// Think of it this way, the transformation along object hierarchy
|
|
// is public, while transformation through linkage is private to
|
|
// link itself.
|
|
if (transform) {
|
|
if (getLinkPlacementProperty()) {
|
|
*mat *= getLinkPlacementValue().toMatrix();
|
|
}
|
|
else if (getPlacementProperty()) {
|
|
*mat *= getPlacementValue().toMatrix();
|
|
}
|
|
}
|
|
_mat = *mat;
|
|
}
|
|
|
|
if (pyObj && !_getElementCountValue() && _getElementListValue().empty()
|
|
&& mySubElements.size() <= 1) {
|
|
// Scale will be included here
|
|
if (getScaleProperty() || getScaleVectorProperty()) {
|
|
Base::Matrix4D s;
|
|
s.scale(getScaleVector());
|
|
_mat *= s;
|
|
}
|
|
auto linked = getTrueLinkedObject(false, &_mat, depth);
|
|
if (linked && linked != obj) {
|
|
linked->getSubObject(mySubElements.empty() ? nullptr
|
|
: mySubElements.front().c_str(),
|
|
pyObj,
|
|
&_mat,
|
|
false,
|
|
depth + 1);
|
|
checkGeoElementMap(obj, linked, pyObj, nullptr);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (mat) {
|
|
*mat *= getTransform(transform);
|
|
}
|
|
|
|
// DocumentObject *element = 0;
|
|
bool isElement = false;
|
|
int idx = getElementIndex(subname, &subname);
|
|
if (idx >= 0) {
|
|
const auto& elements = _getElementListValue();
|
|
if (!elements.empty()) {
|
|
if (idx >= (int)elements.size() || !elements[idx]
|
|
|| !elements[idx]->isAttachedToDocument()) {
|
|
return true;
|
|
}
|
|
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::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;
|
|
|
|
// 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
|
|
if (subname && !Data::isMappedElement(subname) && strchr(subname, '.')) {
|
|
if (mat) {
|
|
*mat = matNext;
|
|
}
|
|
}
|
|
// This is a useless check as 'element' is never set to a value other than null
|
|
// else if(element) {
|
|
// ret = element;
|
|
//}
|
|
else if (!isElement) {
|
|
ret = const_cast<DocumentObject*>(obj);
|
|
}
|
|
else {
|
|
if (idx) {
|
|
postfix = Data::POSTFIX_INDEX;
|
|
postfix += std::to_string(idx);
|
|
}
|
|
if (mat) {
|
|
*mat = matNext;
|
|
}
|
|
}
|
|
}
|
|
checkGeoElementMap(obj, linked, pyObj, !postfix.empty() ? postfix.c_str() : nullptr);
|
|
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<Data::ComplexGeoDataPy*>(*pyObj)->getComplexGeoDataPtr();
|
|
// geoData->reTagElementMap(obj->getID(),obj->getDocument()->Hasher,postfix);
|
|
|
|
auto geoData = static_cast<Data::ComplexGeoDataPy*>(*pyObj)->getComplexGeoDataPtr();
|
|
std::string _postfix;
|
|
if (linked && obj && linked->getDocument() != obj->getDocument()) {
|
|
_postfix = Data::POSTFIX_EXTERNAL_TAG;
|
|
if (postfix) {
|
|
if (!boost::starts_with(postfix, Data::ComplexGeoData::elementMapPrefix())) {
|
|
_postfix += Data::ComplexGeoData::elementMapPrefix();
|
|
}
|
|
_postfix += postfix;
|
|
}
|
|
postfix = _postfix.c_str();
|
|
}
|
|
geoData->reTagElementMap(obj->getID(), obj->getDocument()->getStringHasher(), postfix);
|
|
}
|
|
|
|
void LinkBaseExtension::onExtendedUnsetupObject()
|
|
{
|
|
if (!getElementListProperty()) {
|
|
return;
|
|
}
|
|
detachElements();
|
|
if (auto obj = getLinkCopyOnChangeGroupValue()) {
|
|
if (obj->isAttachedToDocument() && !obj->isRemoving()) {
|
|
obj->getDocument()->removeObject(obj->getNameInDocument());
|
|
}
|
|
}
|
|
}
|
|
|
|
DocumentObject* LinkBaseExtension::getTrueLinkedObject(bool recurse,
|
|
Base::Matrix4D* mat,
|
|
int depth,
|
|
bool noElement) const
|
|
{
|
|
if (noElement && extensionIsDerivedFrom(LinkElement::getExtensionClassTypeId())
|
|
&& !static_cast<const LinkElement*>(this)->canDelete()) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto ret = getLink(depth);
|
|
if (!ret) {
|
|
return nullptr;
|
|
}
|
|
bool transform = linkTransform();
|
|
const char* subname = getSubName();
|
|
if (subname || (mat && transform)) {
|
|
ret = ret->getSubObject(subname, nullptr, mat, transform, depth + 1);
|
|
transform = false;
|
|
}
|
|
if (ret && recurse) {
|
|
ret = ret->getLinkedObject(recurse, mat, transform, depth + 1);
|
|
}
|
|
if (ret && !ret->isAttachedToDocument()) {
|
|
return nullptr;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool LinkBaseExtension::extensionGetLinkedObject(DocumentObject*& ret,
|
|
bool recurse,
|
|
Base::Matrix4D* mat,
|
|
bool transform,
|
|
int depth) const
|
|
{
|
|
if (mat) {
|
|
*mat *= getTransform(transform);
|
|
}
|
|
ret = nullptr;
|
|
if (!_getElementCountValue()) {
|
|
ret = getTrueLinkedObject(recurse, mat, depth);
|
|
}
|
|
if (!ret) {
|
|
ret = const_cast<DocumentObject*>(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
|
|
{
|
|
// If user has ever linked to some sub-element, the Link shall always accept
|
|
// sub-element linking in the future, which affects how ViewProviderLink
|
|
// dropObjectEx() behave. So we will push an empty string later even if no
|
|
// sub-element linking this time.
|
|
bool hasSubElement = !mySubElements.empty();
|
|
mySubElements.clear();
|
|
mySubName.clear();
|
|
auto xlink = freecad_cast<const PropertyXLink*>(getLinkedObjectProperty());
|
|
if (!xlink || xlink->getSubValues().empty()) {
|
|
if (hasSubElement) {
|
|
mySubElements.emplace_back("");
|
|
}
|
|
return;
|
|
}
|
|
const auto& subs = xlink->getSubValues();
|
|
auto subname = subs.front().c_str();
|
|
auto element = Data::findElementName(subname);
|
|
if (!element || !element[0]) {
|
|
mySubName = subs[0];
|
|
if (hasSubElement) {
|
|
mySubElements.emplace_back("");
|
|
}
|
|
return;
|
|
}
|
|
mySubElements.emplace_back(element);
|
|
mySubName = std::string(subname, element - subname);
|
|
for (std::size_t i = 1; i < subs.size(); ++i) {
|
|
auto& sub = subs[i];
|
|
element = Data::findElementName(sub.c_str());
|
|
if (!Base::Tools::isNullOrEmpty(element) && boost::starts_with(sub, mySubName)) {
|
|
mySubElements.emplace_back(element);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LinkBaseExtension::slotChangedPlainGroup(const App::DocumentObject& obj,
|
|
const App::Property& prop)
|
|
{
|
|
auto group = obj.getExtensionByType<GroupExtension>(true, false);
|
|
if (group && &prop == &group->Group) {
|
|
updateGroup();
|
|
}
|
|
}
|
|
|
|
void LinkBaseExtension::updateGroup()
|
|
{
|
|
std::vector<GroupExtension*> groups;
|
|
std::unordered_set<const App::DocumentObject*> groupSet;
|
|
auto group = linkedPlainGroup();
|
|
if (group) {
|
|
groups.push_back(group);
|
|
groupSet.insert(group->getExtendedObject());
|
|
}
|
|
else {
|
|
for (auto o : getElementListProperty()->getValues()) {
|
|
if (!o || !o->isAttachedToDocument()) {
|
|
continue;
|
|
}
|
|
auto ext = o->getExtensionByType<GroupExtension>(true, false);
|
|
if (ext) {
|
|
groups.push_back(ext);
|
|
groupSet.insert(o);
|
|
}
|
|
}
|
|
}
|
|
std::vector<App::DocumentObject*> children;
|
|
if (!groups.empty()) {
|
|
children = getElementListValue();
|
|
std::set<DocumentObject*> 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());
|
|
// NOLINTBEGIN
|
|
conn = group->signalChanged.connect(
|
|
std::bind(&LinkBaseExtension::slotChangedPlainGroup, this, sp::_1, sp::_2));
|
|
// NOLINTEND
|
|
}
|
|
std::size_t count = children.size();
|
|
ext->getAllChildren(children, childSet);
|
|
for (; count < children.size(); ++count) {
|
|
auto child = children[count];
|
|
if (!child->getExtensionByType<GroupExtension>(true, false)) {
|
|
continue;
|
|
}
|
|
groupSet.insert(child);
|
|
auto& conn = plainGroupConns[child];
|
|
if (!conn.connected()) {
|
|
FC_LOG("new group connection " << getExtendedObject()->getFullName() << " -> "
|
|
<< child->getFullName());
|
|
// NOLINTBEGIN
|
|
conn = child->signalChanged.connect(
|
|
std::bind(&LinkBaseExtension::slotChangedPlainGroup, this, sp::_1, sp::_2));
|
|
// NOLINTEND
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (auto it = plainGroupConns.begin(); it != plainGroupConns.end();) {
|
|
if (!groupSet.contains(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 == getScaleProperty()) {
|
|
if (!prop->testStatus(Property::User3) && getScaleVectorProperty()) {
|
|
auto s = getScaleValue();
|
|
auto p = getScaleVectorProperty();
|
|
p->setStatus(Property::User3, true);
|
|
p->setValue(s, s, s);
|
|
p->setStatus(Property::User3, false);
|
|
}
|
|
}
|
|
else if (prop == getScaleVectorProperty()) {
|
|
if (!prop->testStatus(Property::User3) && getScaleProperty()) {
|
|
const auto& v = getScaleVectorValue();
|
|
if (v.x == v.y && v.x == v.z) {
|
|
auto p = getScaleProperty();
|
|
p->setStatus(Property::User3, true);
|
|
p->setValue(v.x);
|
|
p->setStatus(Property::User3, false);
|
|
}
|
|
}
|
|
}
|
|
else if (prop == _getShowElementProperty()) {
|
|
if (_getShowElementValue()) {
|
|
update(parent, _getElementCountProperty());
|
|
}
|
|
else {
|
|
auto objs = getElementListValue();
|
|
|
|
// preserve element properties in ourself
|
|
std::vector<Base::Placement> placements;
|
|
placements.reserve(objs.size());
|
|
std::vector<Base::Vector3d> scales;
|
|
scales.reserve(objs.size());
|
|
for (auto obj : objs) {
|
|
auto element = freecad_cast<LinkElement*>(obj);
|
|
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<App::DocumentObject*>());
|
|
|
|
if (getPlacementListProperty()) {
|
|
getPlacementListProperty()->setStatus(Property::User3,
|
|
getScaleListProperty() != nullptr);
|
|
getPlacementListProperty()->setValue(placements);
|
|
getPlacementListProperty()->setStatus(Property::User3, false);
|
|
}
|
|
if (getScaleListProperty()) {
|
|
getScaleListProperty()->setValue(scales);
|
|
}
|
|
|
|
for (auto obj : objs) {
|
|
if (obj && obj->isAttachedToDocument()) {
|
|
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() < elementCount) {
|
|
for (size_t i = placements.size(); i < elementCount; ++i) {
|
|
placements.emplace_back(Base::Vector3d(i % 10, (i / 10) % 10, i / 100),
|
|
Base::Rotation());
|
|
}
|
|
}
|
|
else {
|
|
placements.resize(elementCount);
|
|
}
|
|
getPlacementListProperty()->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 owner = getContainer();
|
|
long ownerID = owner ? owner->getID() : 0;
|
|
|
|
for (size_t i = objs.size(); i < elementCount; ++i) {
|
|
name.resize(offset);
|
|
name += std::to_string(i);
|
|
|
|
// It is possible to have orphan LinkElement here due to,
|
|
// for example, undo and redo. So we try to re-claim the
|
|
// children element first.
|
|
auto obj = freecad_cast<LinkElement*>(doc->getObject(name.c_str()));
|
|
if (obj
|
|
&& (!obj->_LinkOwner.getValue() || obj->_LinkOwner.getValue() == ownerID)) {
|
|
obj->Visibility.setValue(false);
|
|
}
|
|
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 < objs.size()) {
|
|
std::vector<App::DocumentObject*> tmpObjs;
|
|
auto owner = getContainer();
|
|
long ownerID = owner ? owner->getID() : 0;
|
|
while (objs.size() > elementCount) {
|
|
auto element = freecad_cast<LinkElement*>(objs.back());
|
|
if (element && element->_LinkOwner.getValue() == ownerID) {
|
|
tmpObjs.push_back(objs.back());
|
|
}
|
|
objs.pop_back();
|
|
}
|
|
getElementListProperty()->setValue(objs);
|
|
for (auto obj : tmpObjs) {
|
|
if (obj && obj->isAttachedToDocument()) {
|
|
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 < vis.size(); ++i) {
|
|
if (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<const App::DocumentObject*> hiddenElements;
|
|
for (size_t i = 0; i < elements.size(); ++i) {
|
|
if (myHiddenElements.find(elements[i]) != myHiddenElements.end()) {
|
|
hiddenElements.insert(elements[i]);
|
|
vis[i] = false;
|
|
}
|
|
}
|
|
myHiddenElements.swap(hiddenElements);
|
|
if (vis != getVisibilityListValue()) {
|
|
auto propVis = getVisibilityListProperty();
|
|
propVis->setStatus(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();
|
|
|
|
if (getLinkCopyOnChangeValue() == CopyOnChangeOwned && !pauseCopyOnChange
|
|
&& !parent->getDocument()->isPerformingTransaction()) {
|
|
getLinkCopyOnChangeProperty()->setValue(CopyOnChangeEnabled);
|
|
}
|
|
else {
|
|
setupCopyOnChange(parent, true);
|
|
}
|
|
}
|
|
else if (prop == getLinkCopyOnChangeProperty()) {
|
|
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();
|
|
auto placement = getPlacementProperty();
|
|
if (linkPlacement && placement) {
|
|
bool transform = getLinkTransformValue();
|
|
placement->setStatus(Property::Hidden, transform);
|
|
linkPlacement->setStatus(Property::Hidden, !transform);
|
|
}
|
|
syncElementList();
|
|
}
|
|
else {
|
|
checkCopyOnChange(parent, *prop);
|
|
}
|
|
}
|
|
|
|
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->isAttachedToDocument()) {
|
|
myLabelCache[child->Label.getStrValue()] = idx;
|
|
}
|
|
++idx;
|
|
}
|
|
}
|
|
|
|
bool LinkBaseExtension::linkTransform() const
|
|
{
|
|
if (!getLinkTransformProperty() && !getLinkPlacementProperty() && !getPlacementProperty()) {
|
|
return true;
|
|
}
|
|
return getLinkTransformValue();
|
|
}
|
|
|
|
void LinkBaseExtension::syncElementList()
|
|
{
|
|
auto transform = getLinkTransformProperty();
|
|
auto link = getLinkedObjectProperty();
|
|
auto xlink = freecad_cast<const PropertyXLink*>(link);
|
|
|
|
auto owner = getContainer();
|
|
auto ownerID = owner ? owner->getID() : 0;
|
|
auto elements = getElementListValue();
|
|
for (auto i : elements) {
|
|
auto element = freecad_cast<LinkElement*>(i);
|
|
if (!element
|
|
|| (element->_LinkOwner.getValue() && element->_LinkOwner.getValue() != ownerID)) {
|
|
continue;
|
|
}
|
|
|
|
element->_LinkOwner.setValue(ownerID);
|
|
|
|
element->LinkTransform.setStatus(Property::Hidden, transform != nullptr);
|
|
element->LinkTransform.setStatus(Property::Immutable, transform != nullptr);
|
|
if (transform && element->LinkTransform.getValue() != transform->getValue()) {
|
|
element->LinkTransform.setValue(transform->getValue());
|
|
}
|
|
|
|
element->LinkedObject.setStatus(Property::Hidden, link != nullptr);
|
|
element->LinkedObject.setStatus(Property::Immutable, link != nullptr);
|
|
if (element->LinkCopyOnChange.getValue() == 2) {
|
|
continue;
|
|
}
|
|
if (xlink) {
|
|
if (element->LinkedObject.getValue() != xlink->getValue()
|
|
|| element->LinkedObject.getSubValues() != xlink->getSubValues()) {
|
|
element->LinkedObject.setValue(xlink->getValue(), xlink->getSubValues());
|
|
}
|
|
}
|
|
else if (element->LinkedObject.getValue() != link->getValue()
|
|
|| !element->LinkedObject.getSubValues().empty()) {
|
|
element->setLink(-1, link->getValue());
|
|
}
|
|
}
|
|
}
|
|
|
|
void LinkBaseExtension::onExtendedDocumentRestored()
|
|
{
|
|
inherited::onExtendedDocumentRestored();
|
|
myHiddenElements.clear();
|
|
auto parent = getContainer();
|
|
if (!parent) {
|
|
return;
|
|
}
|
|
if (hasOldSubElement) {
|
|
hasOldSubElement = false;
|
|
// SubElements was stored as a PropertyStringList. It is now migrated to be
|
|
// stored inside PropertyXLink.
|
|
auto xlink = freecad_cast<PropertyXLink*>(getLinkedObjectProperty());
|
|
if (!xlink) {
|
|
FC_ERR("Failed to restore SubElements for " << parent->getFullName());
|
|
}
|
|
else if (!xlink->getValue()) {
|
|
FC_ERR("Discard SubElements of " << parent->getFullName() << " due to null link");
|
|
}
|
|
else if (xlink->getSubValues().size() > 1) {
|
|
FC_ERR("Failed to restore SubElements for " << parent->getFullName()
|
|
<< " due to conflict subnames");
|
|
}
|
|
else if (xlink->getSubValues().empty()) {
|
|
auto subs = xlink->getSubValues();
|
|
xlink->setSubValues(std::move(subs));
|
|
}
|
|
else {
|
|
std::set<std::string> subset(mySubElements.begin(), mySubElements.end());
|
|
auto sub = xlink->getSubValues().front();
|
|
auto element = Data::findElementName(sub.c_str());
|
|
if (!Base::Tools::isNullOrEmpty(element)) {
|
|
subset.insert(element);
|
|
sub.resize(element - sub.c_str());
|
|
}
|
|
std::vector<std::string> subs;
|
|
for (const auto& s : subset) {
|
|
subs.push_back(sub + s);
|
|
}
|
|
xlink->setSubValues(std::move(subs));
|
|
}
|
|
}
|
|
if (getScaleVectorProperty() && getScaleProperty()) {
|
|
// Scale vector is added later. The code here is for migration.
|
|
const auto& v = getScaleVectorValue();
|
|
double s = getScaleValue();
|
|
if (v.x == v.y && v.x == v.z && v.x != s) {
|
|
getScaleVectorProperty()->setValue(s, s, s);
|
|
}
|
|
}
|
|
update(parent, getVisibilityListProperty());
|
|
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(Base::XMLReader& reader,
|
|
const char* TypeName,
|
|
const char* PropName)
|
|
{
|
|
if (strcmp(PropName, "SubElements") == 0
|
|
&& strcmp(TypeName, PropertyStringList::getClassTypeId().getName()) == 0) {
|
|
PropertyStringList prop;
|
|
prop.setContainer(getContainer());
|
|
prop.Restore(reader);
|
|
if (prop.getSize()) {
|
|
mySubElements = prop.getValues();
|
|
hasOldSubElement = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void LinkBaseExtension::setLink(int index,
|
|
DocumentObject* obj,
|
|
const char* subname,
|
|
const std::vector<std::string>& 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 = nullptr;
|
|
const auto& elements = getElementListProperty()->getValues();
|
|
if (!obj) {
|
|
if (index >= (int)elements.size()) {
|
|
LINK_THROW(Base::ValueError, "Link element index out of bound");
|
|
}
|
|
std::vector<DocumentObject*> 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->isAttachedToDocument()) {
|
|
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 || !Base::Tools::isNullOrEmpty(subname)
|
|
|| !subElements.empty() || obj->getDocument() != parent->getDocument()
|
|
|| (getElementListProperty()->find(obj->getNameInDocument(), &idx)
|
|
&& idx != index)) {
|
|
std::string name = parent->getDocument()->getUniqueObjectName("Link");
|
|
auto link = new Link;
|
|
link->_LinkOwner.setValue(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_cast<PropertyPlacement*>(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");
|
|
}
|
|
detachElements();
|
|
return;
|
|
}
|
|
|
|
// Here means we are assigning a Link
|
|
|
|
auto xlink = freecad_cast<PropertyXLink*>(linkProp);
|
|
if (obj) {
|
|
if (!obj->isAttachedToDocument()) {
|
|
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 (!xlink) {
|
|
if (!subElements.empty() || !Base::Tools::isNullOrEmpty(subname)) {
|
|
LINK_THROW(Base::RuntimeError, "SubName/SubElement link requires PropertyXLink");
|
|
}
|
|
linkProp->setValue(obj);
|
|
return;
|
|
}
|
|
|
|
std::vector<std::string> subs;
|
|
if (!subElements.empty()) {
|
|
subs.reserve(subElements.size());
|
|
for (const auto& s : subElements) {
|
|
subs.emplace_back(subname ? subname : "");
|
|
subs.back() += s;
|
|
}
|
|
}
|
|
else if (!Base::Tools::isNullOrEmpty(subname)) {
|
|
subs.emplace_back(subname);
|
|
}
|
|
xlink->setValue(obj, std::move(subs));
|
|
}
|
|
|
|
void LinkBaseExtension::detachElements()
|
|
{
|
|
std::vector<App::DocumentObjectT> objs;
|
|
for (auto obj : getElementListValue()) {
|
|
objs.emplace_back(obj);
|
|
}
|
|
getElementListProperty()->setValue();
|
|
for (const auto& objT : objs) {
|
|
detachElement(objT.getObject());
|
|
}
|
|
}
|
|
|
|
void LinkBaseExtension::detachElement(DocumentObject* obj)
|
|
{
|
|
if (!obj || !obj->isAttachedToDocument() || obj->isRemoving()) {
|
|
return;
|
|
}
|
|
auto ext = obj->getExtensionByType<LinkBaseExtension>(true);
|
|
auto owner = getContainer();
|
|
long ownerID = owner ? owner->getID() : 0;
|
|
if (getLinkModeValue() == LinkModeAutoUnlink) {
|
|
if (!ext || ext->_LinkOwner.getValue() != ownerID) {
|
|
return;
|
|
}
|
|
}
|
|
else if (getLinkModeValue() != LinkModeAutoDelete) {
|
|
if (ext && ext->_LinkOwner.getValue() == ownerID) {
|
|
ext->_LinkOwner.setValue(0);
|
|
}
|
|
return;
|
|
}
|
|
obj->getDocument()->removeObject(obj->getNameInDocument());
|
|
}
|
|
|
|
std::vector<App::DocumentObject*> LinkBaseExtension::getLinkedChildren(bool filter) const
|
|
{
|
|
if (!filter) {
|
|
return _getElementListValue();
|
|
}
|
|
std::vector<App::DocumentObject*> 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 = nullptr;
|
|
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 = nullptr;
|
|
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
|
|
{
|
|
if (checkingProperty) {
|
|
return inherited::extensionGetPropertyByName(name);
|
|
}
|
|
Base::StateLocker guard(checkingProperty);
|
|
if (isExcludedProperties(name)) {
|
|
return nullptr;
|
|
}
|
|
auto owner = getContainer();
|
|
if (owner) {
|
|
App::Property* prop = owner->getPropertyByName(name);
|
|
if (prop) {
|
|
return prop;
|
|
}
|
|
if (owner->canLinkProperties()) {
|
|
auto linked = getTrueLinkedObject(true);
|
|
if (linked) {
|
|
return linked->getPropertyByName(name);
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool LinkBaseExtension::isLinkMutated() const
|
|
{
|
|
return getLinkCopyOnChangeValue() != CopyOnChangeDisabled && getLinkedObjectValue()
|
|
&& (!getLinkCopyOnChangeSourceValue()
|
|
|| (getLinkedObjectValue() != getLinkCopyOnChangeSourceValue()));
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace App
|
|
{
|
|
EXTENSION_PROPERTY_SOURCE_TEMPLATE(App::LinkBaseExtensionPython, App::LinkBaseExtension)
|
|
|
|
// explicit template instantiation
|
|
template class AppExport ExtensionPythonT<LinkBaseExtension>;
|
|
|
|
} // namespace App
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
EXTENSION_PROPERTY_SOURCE(App::LinkExtension, App::LinkBaseExtension)
|
|
|
|
LinkExtension::LinkExtension()
|
|
{
|
|
initExtensionType(LinkExtension::getExtensionClassTypeId());
|
|
|
|
LINK_PROPS_ADD_EXTENSION(LINK_PARAMS_EXT);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace App
|
|
{
|
|
EXTENSION_PROPERTY_SOURCE_TEMPLATE(App::LinkExtensionPython, App::LinkExtension)
|
|
|
|
// explicit template instantiation
|
|
template class AppExport ExtensionPythonT<App::LinkExtension>;
|
|
|
|
} // namespace App
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
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, std::numeric_limits<int>::max(), 1};
|
|
ElementCount.setConstraints(&s_constraints);
|
|
}
|
|
|
|
bool Link::canLinkProperties() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool Link::isLink() const
|
|
{
|
|
return ElementCount.getValue() == 0;
|
|
}
|
|
|
|
bool Link::isLinkGroup() const
|
|
{
|
|
return ElementCount.getValue() > 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace App
|
|
{
|
|
PROPERTY_SOURCE_TEMPLATE(App::LinkPython, App::Link)
|
|
template<>
|
|
const char* App::LinkPython::getViewProviderName() const
|
|
{
|
|
return "Gui::ViewProviderLinkPython";
|
|
}
|
|
template class AppExport FeaturePythonT<App::Link>;
|
|
} // namespace App
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
PROPERTY_SOURCE_WITH_EXTENSIONS(App::LinkElement, App::DocumentObject)
|
|
|
|
LinkElement::LinkElement()
|
|
{
|
|
LINK_PROPS_ADD(LINK_PARAMS_ELEMENT);
|
|
LinkBaseExtension::initExtension(this);
|
|
}
|
|
|
|
bool LinkElement::canDelete() const
|
|
{
|
|
if (!_LinkOwner.getValue()) {
|
|
return true;
|
|
}
|
|
|
|
auto owner = getContainer();
|
|
return !owner || !owner->getDocument()->getObjectByID(_LinkOwner.getValue());
|
|
}
|
|
|
|
bool LinkElement::isLink() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
App::Link* LinkElement::getLinkGroup() const
|
|
{
|
|
std::vector<App::DocumentObject*> inList = getInList();
|
|
for (auto* obj : inList) {
|
|
auto* link = dynamic_cast<App::Link*>(obj);
|
|
if (!link) {
|
|
continue;
|
|
}
|
|
std::vector<App::DocumentObject*> elts = link->ElementList.getValues();
|
|
for (auto* elt : elts) {
|
|
if (elt == this) {
|
|
return link;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace App
|
|
{
|
|
PROPERTY_SOURCE_TEMPLATE(App::LinkElementPython, App::LinkElement)
|
|
template<>
|
|
const char* App::LinkElementPython::getViewProviderName() const
|
|
{
|
|
return "Gui::ViewProviderLinkPython";
|
|
}
|
|
template class AppExport FeaturePythonT<App::LinkElement>;
|
|
} // namespace App
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
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() const
|
|
{
|
|
return "Gui::ViewProviderLinkPython";
|
|
}
|
|
template class AppExport FeaturePythonT<App::LinkGroup>;
|
|
} // namespace App
|
|
|
|
#if defined(__clang__)
|
|
#pragma clang diagnostic pop
|
|
#endif
|