Skip to content

Commit

Permalink
PropertyLinks: refactor property link API
Browse files Browse the repository at this point in the history
* Create new class PropertyLinkBase as the common parent class for all
  type of links. See the class document for details of its API.

* Added new link scope 'Hidden' that is ignored during normal object
  dependency calculation, but still keep tracks of object remove,
  relabel, etc.

* There is a new concept called 'Shadow subname' introduced in this
  patch, which will only be meaningful in future topological naming
  feature. See [here](https://git.io/fjXKR) for more details.

* DocumentObject added a new API adjustRelativeLink() and a helper
  resolveRelativeLink() function.
  • Loading branch information
realthunder committed Jul 16, 2019
1 parent d26cb0f commit 90f10fd
Show file tree
Hide file tree
Showing 8 changed files with 4,741 additions and 271 deletions.
11 changes: 11 additions & 0 deletions src/App/Application.cpp
Expand Up @@ -1577,18 +1577,28 @@ void Application::initTypes(void)
App ::PropertyUUID ::init();
App ::PropertyFont ::init();
App ::PropertyStringList ::init();
App ::PropertyLinkBase ::init();
App ::PropertyLinkListBase ::init();
App ::PropertyLink ::init();
App ::PropertyLinkChild ::init();
App ::PropertyLinkGlobal ::init();
App ::PropertyLinkHidden ::init();
App ::PropertyLinkSub ::init();
App ::PropertyLinkSubChild ::init();
App ::PropertyLinkSubGlobal ::init();
App ::PropertyLinkSubHidden ::init();
App ::PropertyLinkList ::init();
App ::PropertyLinkListChild ::init();
App ::PropertyLinkListGlobal ::init();
App ::PropertyLinkListHidden ::init();
App ::PropertyLinkSubList ::init();
App ::PropertyLinkSubListChild ::init();
App ::PropertyLinkSubListGlobal ::init();
App ::PropertyLinkSubListHidden ::init();
App ::PropertyXLink ::init();
App ::PropertyXLinkSub ::init();
App ::PropertyXLinkSubList ::init();
App ::PropertyXLinkContainer ::init();
App ::PropertyMatrix ::init();
App ::PropertyVector ::init();
App ::PropertyVectorDistance ::init();
Expand All @@ -1608,6 +1618,7 @@ void Application::initTypes(void)
App ::PropertyFile ::init();
App ::PropertyFileIncluded ::init();
App ::PropertyPythonObject ::init();
App ::PropertyExpressionContainer ::init();
App ::PropertyExpressionEngine ::init();

// Extension classes
Expand Down
85 changes: 85 additions & 0 deletions src/App/DocumentObject.cpp
Expand Up @@ -1008,6 +1008,91 @@ DocumentObject *DocumentObject::resolve(const char *subname,
return obj;
}

DocumentObject *DocumentObject::resolveRelativeLink(std::string &subname,
DocumentObject *&link, std::string &linkSub) const
{
if(!link || !link->getNameInDocument() || !getNameInDocument())
return 0;
auto ret = const_cast<DocumentObject*>(this);
if(link != ret) {
auto sub = subname.c_str();
auto nextsub = sub;
for(auto dot=strchr(nextsub,'.');dot;nextsub=dot+1,dot=strchr(nextsub,'.')) {
std::string subcheck(sub,nextsub-sub);
subcheck += link->getNameInDocument();
subcheck += '.';
if(getSubObject(subcheck.c_str())==link) {
ret = getSubObject(std::string(sub,dot+1-sub).c_str());
if(!ret)
return 0;
subname = std::string(dot+1);
break;
}
}
return ret;
}

size_t pos=0,linkPos=0;
std::string linkssub,ssub;
do {
linkPos = linkSub.find('.',linkPos);
if(linkPos == std::string::npos) {
link = 0;
return 0;
}
++linkPos;
pos = subname.find('.',pos);
if(pos == std::string::npos) {
subname.clear();
ret = 0;
break;
}
++pos;
}while(subname.compare(0,pos,linkSub,0,linkPos)==0);

if(pos != std::string::npos) {
ret = getSubObject(subname.substr(0,pos).c_str());
if(!ret) {
link = 0;
return 0;
}
subname = subname.substr(pos);
}
if(linkPos) {
link = link->getSubObject(linkSub.substr(0,linkPos).c_str());
if(!link)
return 0;
linkSub = linkSub.substr(linkPos);
}
return ret;
}

bool DocumentObject::adjustRelativeLinks(
const std::set<App::DocumentObject *> &inList,
std::set<App::DocumentObject *> *visited)
{
if(visited)
visited->insert(this);

bool touched = false;
std::vector<Property*> props;
getPropertyList(props);
for(auto prop : props) {
auto linkProp = Base::freecad_dynamic_cast<PropertyLinkBase>(prop);
if(linkProp && linkProp->adjustLink(inList))
touched = true;
}
if(visited) {
for(auto obj : getOutList()) {
if(!visited->count(obj)) {
if(obj->adjustRelativeLinks(inList,visited))
touched = true;
}
}
}
return touched;
}

const std::string &DocumentObject::hiddenMarker() {
static std::string marker("!hide");
return marker;
Expand Down
66 changes: 65 additions & 1 deletion src/App/DocumentObject.h
Expand Up @@ -388,6 +388,70 @@ class AppExport DocumentObject: public App::TransactionalObject
std::string *childName=0, const char **subElement=0,
PyObject **pyObj=0, Base::Matrix4D *mat=0, bool transform=true, int depth=0) const;

/** Resolve a link reference that is relative to this object reference
*
* @param subname: on input, this is the subname reference to the object
* that is to be assigned a link. On output, the reference may be offseted
* to be rid off any common parent.
* @param link: on input, this is the top parent of the link reference. On
* output, it may be altered to one of its child to be rid off any common
* parent.
* @param linkSub: on input, this the subname of the link reference. On
* output, it may be offseted to be rid off any common parent.
*
* @return The corrected top parent of the object that is to be assigned the
* link. If the output 'subname' is empty, then return the object itself.
*
* To avoid any cyclic reference, an object must not be assign a link to any
* of the object in its parent. This function can be used to resolve any
* common parents of an object and its link target.
*
* For example, with the following object hierarchy
*
* Group
* |--Group001
* | |--Box
* | |--Cylinder
* |--Group002
* |--Box001
* |--Cylinder001
*
* If you want add a link of Group.Group002.Box001 to Group.Group001, you
* can call with the following parameter (which are usually obtained from
* Selection.getSelectionEx(), check usage in TreeWidget::onDropEvent()):
* std::string subname("Group002.");
* auto link = Group;
* std::string linkSub("Group001.Box001.");
* parent = Group.resolveRelativeLink(subname,link,linkSub);
*
* The resolving result is as follow:
* return -> Group001
* subname -> ""
* link -> Group002
* linkSub -> "Box001."
*
* The common parent 'Group' is removed.
*/
App::DocumentObject *resolveRelativeLink(std::string &subname,
App::DocumentObject *&link, std::string &linkSub) const;

/** Called to adjust link properties to avoid cyclic links
*
* @param inList: the recursive in-list of the future parent object,
* including the parent itself.
* @param visited: optional set holding the visited objects. If null then
* only this object is adjusted, or else all object inside the out-list of
* this object will be checked.
*
* @return Return whether the object has been modified
*
* This function tries to adjust any relative link properties (i.e. link
* properties that can hold subnames) to avoid cyclic when added to the
* future parent.
*/
virtual bool adjustRelativeLinks(const std::set<App::DocumentObject*> &inList,
std::set<App::DocumentObject*> *visited=0);

/** Allow object to redirect a subname path
*
* @param ss: input as the current subname path from \a topParent leading
Expand All @@ -404,7 +468,7 @@ class AppExport DocumentObject: public App::TransactionalObject
virtual bool redirectSubName(std::ostringstream &ss,
DocumentObject *topParent, DocumentObject *child) const;

/** Sepecial marker to mark the object has hidden
/** Sepecial marker to mark the object as hidden
*
* It is used by Gui::ViewProvider::getElementColors(), but exposed here
* for convenience
Expand Down
5 changes: 5 additions & 0 deletions src/App/DocumentObjectPy.xml
Expand Up @@ -176,6 +176,11 @@ non-object sub-element name if any.
</UserDocu>
</Documentation>
</Methode>
<Methode Name="adjustRelativeLinks">
<Documentation>
<UserDocu>adjustRelativeLinks(parent,recursive=True) -- auto correct potential cyclic dependencies</UserDocu>
</Documentation>
</Methode>
<Attribute Name="OutList" ReadOnly="true">
<Documentation>
<UserDocu>A list of all objects this object links to.</UserDocu>
Expand Down
16 changes: 16 additions & 0 deletions src/App/DocumentObjectPyImp.cpp
Expand Up @@ -791,3 +791,19 @@ Py::List DocumentObjectPy::getParents() const {
ret.append(Py::TupleN(Py::Object(v.first->getPyObject(),true),Py::String(v.second)));
return ret;
}

PyObject *DocumentObjectPy::adjustRelativeLinks(PyObject *args) {
PyObject *pyobj;
PyObject *recursive = Py_True;
if (!PyArg_ParseTuple(args, "O!|O",&DocumentObjectPy::Type,&pyobj,&recursive))
return NULL;
PY_TRY {
auto obj = static_cast<DocumentObjectPy*>(pyobj)->getDocumentObjectPtr();
auto inList = obj->getInListEx(true);
inList.insert(obj);
std::set<App::DocumentObject *> visited;
return Py::new_reference_to(Py::Boolean(
getDocumentObjectPtr()->adjustRelativeLinks(inList,
PyObject_IsTrue(recursive)?&visited:nullptr)));
}PY_CATCH
}
11 changes: 11 additions & 0 deletions src/App/Property.h
Expand Up @@ -146,6 +146,12 @@ class AppExport Property : public Base::Persistence
/// Get valid paths for this property; used by auto completer
virtual void getPaths(std::vector<App::ObjectIdentifier> & paths) const;

/// Called at the begining of Document::afterRestore(). See comments there.
virtual void afterRestore() {}

/// Called before calling DocumentObject::onDocumentRestored()
virtual void onContainerRestored() {}

/** Property status handling
*/
//@{
Expand Down Expand Up @@ -189,6 +195,11 @@ class AppExport Property : public Base::Persistence
/// Paste the value from the property (mainly for Undo/Redo and transactions)
virtual void Paste(const Property &from) = 0;

/// Called when a child property has changed value
virtual void hasSetChildValue(Property &) {}
/// Called before a child property changing value
virtual void aboutToSetChildValue(Property &) {}

friend class PropertyContainer;
friend struct PropertyData;
friend class DynamicProperty;
Expand Down

0 comments on commit 90f10fd

Please sign in to comment.