Skip to content

Core Changes

luz paz edited this page Feb 15, 2021 · 9 revisions

Here is a list of core changes sorted by C++ namespace, and classes. With only very few exception, most of the core changes are exposed to Python as well.

App Namespace

Property

The property status bits are expanded for dynamic access control, just like file attributes in file system. It will be persisted when the document is saved. This allows for more flexible control of the properties of an object. For example, the programmer can now set a Trasient bit on the Shape of a Part::Feature derived object to make it not persistent, but dynamically generated when restore, which is the more efficient way of handling OCCT compound. Python code can use the following code to manipulate status bits of a property dynamically. This function are implemented in PropertyContainerPy class.

# make some property immutable, i.e. read-only in python
obj.setPropertyStatus(property_name, "Immutable")

# remove immutable from some property
obj.setPropertyStatus(property_name, "-Immutable")

# add multiple status bit
obj.setPropertyStatus(property_name, ["Immutable", "Hidden"])

# get property status of some property
obj.getPropertyStatus(property_name)

# get a list of available property status name
obj.getPropertyStatus()

Currently supported property status are,

  • Touched: this property status cannot be set using setPropertyStatus() through python, but can be returned by getPropertyStatus(). Use touch() instead.
  • Immutable: mark the property as read-only in python. C++ code is not affected.
  • ReadOnly: make the property read-only in the property view window.
  • Hidden: hide the property from property view window.
  • Transient: do not store the property when saving the document.
  • MaterialEdit: enable material editing in property view.
  • NoMaterialListEdit: disable material list editing in property view.
  • Output: property marked with output will not trigger its container's onChanged() function
  • LockDynamic: prevent dynamic property from being removed.
  • NoModify: prevent calling Gui::Document::setModified() on property change.

DynamicProperties and PropertyContainer

In upstream, class DynamicProperties is used by FeaturePython and ViewProviderPythonFeature to hold dynamically created properties by Python code. However, it's current implementation breaks polymorphism of the containing object, because it directly calls the ancestor's implementation which will bypass the implement of any parent class (of e.g. FeaturePython).

DynamicProperties has been modified and moved into PropertyContainer which means that all of its derived classes can now dynamically adding properties, that includes Document, DocumentObject, and ViewProvider. App::Transaction has also been modified such that user can now undo or redo adding and removing of any dynamic properties.

AtomicPropertyChangeInterface

This is a helper class to optimize property aboutToSetValue/hasSetValue() calls. The original implementation uses an internal reference counter to make sure only call aboutToSetValue() only once with multiple instance of AtomicPropertyChangeInterface on the call stack, and call hasSetValue() when the last instance goes out of scope. It has been modified to allow user code to choose when to call aboutToSetValue() using a new API AtomicPropertyChangeInterface::aboutToChange(). As the name implies, this is intended to trigger property before change event only at the time of change.

PropertyLists

Class PropertyLists, and most of its derived classed has been refactored for more code reuse. The original motivation is to add a TouchList function, which allows the programmer to discover exactly which entry/entries has been changed to save potentially expensive operations.

A new class, PropertyListsBase, is added as an abstract parent class that is NOT derived from Property. This is to make it possible for other list like property class to reuse the list handling code without having to derive from PropertyLists. One example is the PropertyLinkList class described later. This class, and all other link type property classes, are changed to derived from a new PropertyLinkBase.

PropertyListBase provides a helper function _setPyObject() to set value from a Python object. It accepts Python object as a tuple, list, or newly supported dict(index:value). For dict, it also supports appending by using index of -1. The implementation of _setPyObject() extract the Python objects for items from the input Python sequence or dictionary, put them into C++ vector, and then calls the virtual function setPyValues() for further assignment. The reason of the underscore in _setPyObject() is to avoid name clash with Property::setPyObject(), as PropertyListBase is NOT derived from Property.

PropertyListT is a template class that implements the generic type logic of list handling, and provides the implementation of setPyValues(). It is also derived from AtomicPropertyChangeInterface to allow more efficient handling of changes to multiple entries in the list. The implementation is designed to be compatible with original various list properties. It unifies the API for all list type properties,

  • get/setSize()

  • getValue()/getValues(), same function for returning the whole list, provided for backward compatibility

  • setValue(const_reference value), clear the list and store one value into the list.

  • setValue/setValues(list), set whole list.

  • operator[](int), indexer operator to return one value

  • set1Value(), change value of one entry. It creates AtomicPropertyChange internally to signal property change event. User code can create their own instance of AtomicPropertyChange when changing values of multiple entries, so that the change event can be signaled only once.

  • setPyObject(), for property assignment from Python object. It first tries to assign the Python object as a single item. If failed, then call PropertyListBase::_setPyObject() to assign it as a list.

  • getPyValue(), a template pure virtual function for derived class to provide logic of converting a Python object into a single item C++ object.

  • setPyValues(), convert vector of Python object of items into vector of C++ object. It relies on getPyValue() for converting.

TouchList is supported by API getTouchList() which returns a set of indices of the changed values. Python code can use obj.getPropertyTouchList(prop_name) to obtain the touch list. PropertyListT will take care of maintaining the touch list on property changes.

Link Properties

Current FreeCAD lacks a proper abstraction of the link type properties. The involved core logic enumerates all link property types and deals with them separately. However, link type properties become a lot more complicated due to the introduction of external links and topological naming. The need of a proper abstraction become a must.

The main difficulty of providing a common abstract class for link property is that the list type link property is derived from PropertyLists, while others are derived directly from Property. The above refactor of PropertyLists solved this problem by isolating the list handling logic to a non property class.

A new class PropertyLinkBase, derived from Proeprty, is used as the common parent class of all link type properties. It defines a rich set of abstract interface APIs and helper functions to perform various complex link manipulations when exporting(copy), importing(paste), object removing, relative link adjustment during drag and drop, object replacing, label changing, topological name changing, and so on. Please consult the class document for more details on the interface and helpers.

A new link scope, Hidden, has been added to allow some form of cyclic dependency. The PropertyLinkBase::getLinks() API above will not return linked object if the property has Hidden link scope. And various link property with this scope will not call DocumentObject::_addBackLink(). Therefore, the dependency information of Hidden link scope will be hidden from the dependency checking logic in the core. However, the link property with Hidden scope still enjoys benefits such as auto element reference update, auto link breaking and many more through PropertyLinkBase interface. An example use case is the DocumentObject::ColoredElements property which holds the element reference for coloring. This PropertyLinkSubHidden property links to its own container.

A new link type property, PropertyXLink, is added to support linking of object from external document. It is derived from PropertyLink which makes it a drop-in replacement in case external linking is required. There corresponding external linking capable properties for other existing properties, such as PropertyXLinkSub and PropertyXLinkSubList.

A new class PropertyXLinkContainer is added to support more complex external link usage, such as PropertyExpressionEngine and Spreadsheet::PropertySheet. As as result, both of these two properties can be treated just like another link type properties, with unified handling of link manipulation through the standard interfaced defined in ProeprtyLinkBase.

PropertyPersistentObject

A new property is introduced to allow Python code to add any FreeCAD core objects that are derived from Base::Persistence as a property. This allows one to use the composite pattern, in other word, to implement an object using one or more existing objects.

This property accepts a string of the type name with its setValue(), and will create the object of the given type. And the object can be retrieved by its getValue()

One use case is the ChildViewProvider property of Gui::ViewProviderLink, which are intended to hold any object derived from Gui::ViewProviderDocumentObject. ViewProviderLink is designed to be able to work with any object with LinkBaseExtension. It does not provide any actual visual representation, but instead, relies on the linked object's view provider to provide them. However, there may be cases where it makes sense for ViewProviderLink to provide the visual for its attached object directly. With ChildViewProvider, user code can now attach a secondary child view provider, which may not be reside in the core.

A concrete use case is the AsmPartGroup container used by Assembly3. This container is of type Part::FeaturePython, but it normally does not hold any Shape of its own, instead, it uses ViewProviderLinkPython to borrow its contained part object's visual. The assembly can later on be frozen, such that any changes of its children parts has no effect on the assembly. In order to achieve this, the part group's Shape now holds a compound of the containing part shapes before freezing. And a child view provider of type PartGui::ViewProviderPart is created to provide the visuals.

Application

A few of new APIs are added to App::Application class.

  • addPendingDocument(), used by PropertyXLink to pend external documents when opening document with external links, so that the linked documents can be automatically opened together with the main document.

  • get/set/closeActiveTransaction(), these APIs are part of the implementation of the new core functionality called Auto Transaction. The other part exists in App::Document. Programmer can now call Application::setActiveTransaction() to set a potential transaction with a given name. The transaction is only created when there is an actual change. Moreover, each transaction now has an associated integer ID. setActiveTransaction() will create a new ID each time it is called. Each changed document will check for active transaction by calling getActiveTransaction() and create the actual transaction with the current active transaction ID. The net effect is that, all modified documents during a single logical operation will use the same ID, so that all related transactions can be undo/redo together like a single logical transaction. This function is essential to support editing document with external links.

    To make the new functionality transparent and yet backward compatible in exiting code, App::Document::open/commit/abortTransaction() has been modified to call instead Application::set/closeActiveTransaction(). The actual transaction operation is done by App::Document::_open/_commit/_abortTranaction().

    A helper class App::AutoTransaction is created to simplify auto transaction. It sets up an active transaction when the first instance is created on the call stack, and will cause Application to ignore any further call of setActiveTransaction(), and will make sure to call commitTransaction() when the last instance go out of scope. Gui::Command demonstrates a more advanced usage of this class. Command::invoke() now sets up an instance of AutoTransaction using the command display name as the transaction name. However, the name is marked as temporary, meaning that the user code in the actual command can override the transaction name either by manually calling Application::setTransaction() or creating its own AutoTransaction instance. The transaction can be aborted at any time regardless of how many instance of AutoTransaction in the call stack. But it will only calls commitTransaction() when the last instance of AutoTransaction goes out of scope. This makes sure that the transaction holds the complete change of a command regardless if the command invokes any other command recursively, or commits the transaction prematurely. For example, almost all sketcher command commits the transaction first, and then perform a recompute. However, the sketcher solver may sometimes get a drastically wrong solution due to missing external geometries, an undo cannot solved problem because the original Shape property is not captured in the transaction. The usage of AutoTransaction solves this problem without modifying Sketcher.

  • signalUndo/Redo, new signals triggered after undo and redo. The difference between these and existing signalUndo/RedoDocument is that these two are signaled once even though the transaction may involve multiple documents. These signaled as exposed to App::DocumentObserverPython.

  • signal(Before)CloseTransaction, new signals triggered before and after a new transaction is either aborted or committed. Same as above, these signals are triggered once per transaction regardless how many documents are involved.

  • checkLinkDepth/getLinksTo/hasLinksTo(), a few helper functions to obtain Link object across all opened documents.

Although not part of Application, ObjectLabelObserver was implemented in Application.cpp. This class is removed, and label duplication detection logic is moved into PropertyString for more efficiency, and also to allow object itself to control labeling policy. See description below for more details.

Document

A new property, ShowHidden, has been added to tell tree view whether to show hidden object of this document. And each ViewProviderDocumentObject has a new property, ShowInTree, to mark if this object should be hidden in tree view. These are added to make it more flexible for user to organize the tree view hierarchy.

Another new property, UseHasher, is added to control whether to use hasher to shorten element names generated by topological naming.

API changes are listed below,

  • getObjectByID(), a new API to retrieve object based on its integer ID, which is auto generated by the document for each new object. The ID is unique within the document, it is mainly used for encoding shape history for topological naming.

  • _buildDependencyList(), helper function to obtain a list of all dependent objects of the given object(s). It supports external linked object, meaning that the obtained object list may include objects from other document.

  • getDependencyList(), replace the original implementation with _buildDependencyList(). In addition, improve error reporting by finding the objects involved in dependency loops.

  • getDependentDocuments(), get a list of documents that are dependent to this document.

  • mustExecute(), check if any of the objects in this document must be executed. This function will account for external dependencies, so that it will report true if any externally dependent objects are touched. The Recompute button on the toolbar will be activated or deactivated by checking with this function.

  • recompute(), there are several changes to recomputation logic,

    • recompute() now accepts an optional input vector of objects, in order to support partial recomputation. Only the given objects, and all their dependent objects will be recomputed. And since it uses getDependencyList() to obtain the dependent object list, the recomputation may consist objects from multiple documents.

    • recompute() accepts a second parameter force to force recompute regardless of SkipRecompute flag of its owner document. Because of the support of partial recompute, it is now possible to force recompute part of the objects without affecting other objects. So when the user sets the SkipRecompute of some document, he can still edit object, and after finished, only recompute the editing object and all its dependent objects. Note that without this force recomputation functionality, many object will report error when finish editing if its owner document is marked as SkipRecompute, because some editing logic expects the recomputation to work, and will check the recomputation result for verification. This option is exposed through tree view context menu action Allow partial recompute

    • Add support for some form of dependency inversion using a two-pass iteration. An example of dependency inversion is Sketcher::SketchExport object, which is a child object of some Sketcher::SketchObject. However it requires the parent being update first in order to obtain the updated exported geometry.

      The two-pass recomputation works like this. The first pass proceed as normal. After its done, any object that is still touched will be marked with a newly added ObjectStatus::Recompute2 flag. The second pass will then be launched. Those objects that need dependency inversion shall check its dependent objects for this flag, and do not touch them if found. If there are still objects touched after the second pass, an error message will be printed.

      In the above SketchExport example, the parent SketchObject will update all its children SketchExport in its execute() function in the first recomputation pass. Since all children (i.e. dependent) objects are recomputed before its parent. Those SketchExport object will still be touched after the first pass, and will be marked with Recompute2 in the second pass. SketchObject will then skip updating its children and complete the second pass without any problem.

    • Error handling logic is changed. When some object reports error during recomputation, this object and all objects in its InListRecursive will be removed from pending object list, after which recomputation resumes as normal.

  • recomputeFeature() has an extra argument recursive. When set to true it will recompute the given object and all its dependent objects.

  • open/commit/abortTransaction(), and undo/redo() has been modified to support grouped transactions. See here for more details.

  • isExporting(), a new function added to distinguish different type of exporting. It can return the following value,

    • NotExporting

    • Exporting, normal exporting with all selected objects being copied.

    • ExportKeepExternal, like normal export but keep the external linked object as it is. This signals the various link properties to not auto rename its external references.

  • copyObject() and exportObjects() have been modified to be aware of external linked objects, and offer user a choice of whether to copy those external objects or not. The object name mapping logic has been moved from helper class MergeDocuments to various link properties.

  • moveObject() is reimplemented using export (copy) and import (paste). This allows the operation to be undo and redo. It now also support objects that may delete its children when being removed.

  • importLinks(), with a given list of objects, this new function will copy all external referenced object into this document, and update all external linking property with internal reference, including any intermediate reference inside the subname. Consider a PropertyLinkSub containing a subname reference of group1.group2.cube, where the group2 is externally linked by a Link object. After import, the object name may be changed due to name clash, to say group3. importLinks() will make sure to update the subname reference to group1.group3.cube as expected.

  • addObject() has an extra argument viewType to allow overriding the objects view provider. For example, Gui::ViewProviderLink is designed to work for any type of object that contains an App::LinkBaseExtension. This makes it possible to change a Part::FeaturePython object's view provider to ViewProviderLink, so that the object can behave like a Link object and still be used by other object that only accept Part::Feature derived objects. See here for and example.

  • Restore(), Save(), modified to support partial document loading

  • restore(), importObjects(), modified to unified logic of finishing restoring objects by calling a new helper function afterRestore().

  • afterRestore(), called after objects are restored either from physical document or clipboard (i.e. import). Comparing to upstream, there are a lot more needed to be done after restoring a document. In fact, in case of multiple document project, a lot of the post processing can only be done after all depending documents have been restored, which is why we have this dedicated afterRestore(). The flow goes like this:

    • Calls Property::afterRestore() (click the link for more details) for every properties in every objects.

    • Check if there is any touched object, and signal reload if the current document is partial loaded.

    • For each object in dependency order

      • Call Property::onContainerRestored().

      • Call PropertyExpressionEngine::execute() with ExecuteOnRestore option. This only executes the expressions for properties marked as Property::EvalOnRestore.

      • Call DocumentObject::onDocumentRestored()

      • Check each link property to see if the link is properly restored.

Partial Document Loading

A new feature is introduced to improve FreeCAD's scalability of handling complex and nested hierarchical models, such as a mechanical assembly. A document object can now report if it can be loaded partially when restoring, using the new API canLoadPartial().

When saving the document, before writing the actual objects, Document will now save a dependency list of each object, containing the names of its direct depending objects. Document will also call each object's canLoadPartial() and save the result together with dependency list.

When a document is opened by the end-user, which we'll be calling as the main document, it will always be fully loaded. But when a document is automatically opened due to external linking inside the main document, the dependency list in the external document will be consulted, and only the linked objects and their dependent objects will be considered for restoring. The candidate objects will be further pruned using their stored canLoadPartial() return values. If an object returned 1, then all its direct depending object will only be created, but not restored, which means the further depending objects may not be created at all, because a newly created object normally has no dependency. If an object returned 2, then the object itself will be created but not restored. All other candidate objects will be restored as normal.

The tree view will not show any partially loaded objects, i.e. those created but not restored. The partially loaded documents will hide their window to discourage the user to set them as active document. Any modification to the partially loaded document will generate a warning, and will not be saved. The tree view shows a grey icon for partial document. The user can double click the grey icon to reload the document fully, Or right click the main document and select 'Reload document' to reload all its direct depending document fully.

An example of implementing an object that supports partial loading is the Assembly container in Assembly3. Each Assembly always has three child objects, Constraints, Elements and Parts. An Assembly can be frozen, making it immune to any changes of its composing part objects in Parts. This is achieved by store a compound shape of all child parts before freezing in the Shape property of Parts. The freezing also allows the assembly to be loaded partially, because it makes the assembly self sufficient without relying on any other external dependencies. The Assembly container itself will return 0 in its canLoadPartial() function, which causes all three of its direct depending children to be loaded. However, Constraints will return 2 in its canLoadPartial(), because a frozen assembly do not need to be solved again, and all its associated constraint objects will not be created at all. Parts will return 1, so that Parts itself is fully restored, because we need the Shape. All the part objects, which are the direct depending objects, will be created but not restored, and therefore no further dependency will be introduced.

The following screen cast shows the effect of partial document loading. The example assembly consists of four externally linked part objects. The screen cast shows that when you open the main document, the external depending documents will be opened automatically. Then we proceed to freeze the assembly object. A new file named assembly is created, and an external link is added to link to the frozen assembly. As you can see, after the assembly file is reopened, none of the external part files are opened automatically. The original main document is partially opened with a grey icon. None of the partially loaded part objects is shown in the tree. Finally, we double click the partially opened document to reload it fully, and every part objects reappeared in the linking document.

DocumentObject

A new property, Visibility, has been added to make it possible to set and get visibility status of an object in App namespace. This makes it possible to determine the children visibilities in some group object without loading GUI, for example, to generate a compound shape representation of a group.

Here is a list of changed/added APIs,

  • getID(), obtain the internal integer ID of this object.

  • getViewProviderNameOverride(), obtain the user specified overridden view provider. App::Document::addObject() allows user to override the view provider when creating an object. This overridden view provider type is persisted when saving the document, and can be retrieved using this function.

  • getExportName(), obtain a suitable object name for exporting, depending on the exporting type reported by Document::isExporting()

  • getLinkedObject(), obtain the linked object, and optionally with the accumulated transformation matrix. For non-Link type object, or, a Link type object that is not linked, this function shall return the object itself. So it is always safe to call this function, and expecting it to return a valid object. In case there are multiple levels of linking involved, this function allows the caller to retrieve the linked object recursively.

    In most cases, programmer does not need to explicitly call this function to be able work with Link, which will automatically delegate most of the APIs to linked object. One particular use case of this function is that when given a subname reference, say group1.group2.cubeLink, if the last object cubeLink is a Link object that links to the actual cube, the resolve() function will return that Link as the resolved object, not the actual cube. The caller can use getLinkedObject() to obtain the linked object.

  • getSubObject(), obtain a sub-object through a given subname reference, and optionally return the accumulated transformation matrix, and associated Python object. This is the most important API, created specifically to make Link work with group type objects.

    The default implementation in DocumentObject is to search the OutList of the object for the next hierarchy sub-object. And once found, recursively calls the sub-object's getSubObject() for the next hierarchy. Group type object may override this behavior. Non group type object may also override this function to return the associated Python object when called with an empty subname reference, i.e. when reference to the object itself, or with a sub-element reference, i.e. a reference that is not ending with a dot. For example, Part::Feature overrides this function to return the object's shape, or sub-shape through the Python object. Link type object overrides this function to delegate the call to the linked object.

  • getSubObjectList(), a helper function to return a list of sub-objects referenced by the given subname.

  • getSubObjects(reason), return a list of subname references for all the child sub-objects. The purpose of this function is mostly for exporting a group type object (with reason = GS_DEFAULT). Each subname reference returned may contain more than one hierarchy. For example, The Assembly container in Assembly3 workbench uses this function to skip one hierarchy that contains constraints and stuff during exporting. The caller is expected to call getSubObject() with the returned subname reference to obtain the actual child sub-objects.

    A newly added Box Element Selection also uses getSubObjects() for enumerating sub objects, with reason = GS_SELECT. PartDesign::Body will return child features when given this reason. For GS_DEFAULT, i.e. shape exporting, Body will not return its child feature so as to output its own shape instead.

  • canLinkProperties(), tells Gui::PropertyView whether to show the properties of the linked object together with properties of Link object itself. For example, App::Link will only allow linking property when it is not linked to a sub-element, such as a face or an edge.

  • hasChildElement(), set/isElementVisible(), controls the children visibility for a group type object. Because the child object may be claimed by other objects, it is essential to have independent control of children visibilities. These APIs are designed to abstract how group manages the child visibility. For example, App::LinkGroup stores the children visibilities as a PropertyBoolList

  • resolve(), helper function to parse subname reference and resolve the final object, and optionally the immediate parent of the final object, the final object reference name (for calling set/isElementVisible()), and the sub-element reference if there is one.

  • resolveRelativeLink(), helper function to adjust a subname reference in case the two given object belongs to the same parent. See the code comments for an example.

  • recomputeFeature(), has an extra argument, recursive, to recompute the dependent objects together with this object if set to true.

  • getInListEx(), obtain the (optionally recursive) InList of this object. It offers a more efficient algorithm than getInListRecursive() in upstream.

  • getOutList(), is slightly modified to keep an internal cache, so that it does not have to re-scan all properties every time to construct the OutList. The cache is invalidated when any property changes.

  • allowDuplicateLabel(), onBeforeChangeLable(), new APIs to let object control if it allows its label to be a duplicate with others. PropertyString, once detected it is a label, will call its owner's allowDuplicateLabel() to see if duplicate label is allowed. If not so, and the label is about to be changed, PropertyString will call its owner's onBeforeChangeLable() function to notify the new label. For example, App::Link supports label inside subname reference, and will auto adjust all affected label subname reference in onBeforeChangeLabel() function.

  • onUpdateElementReference(), will be called when any element reference in some of the object's link property is changed, due to topological changes in the linked model.

  • canLoadPartial(), allow the object to inform the document whether it can can be partially loaded when restoring as an externally linked object.

    Partial loading means the object will be created but not restored when its document is restored.

    The return value of this function has the following meaning,

    • 0, the object must be fully loaded,
    • 1, the object must be fully loaded, but all its directly depending objects can be partially loaded.
    • 2, the object itself can be partially loaded.
  • redirectSubname(), allows an object to redirect selection to some other object when (pre)selected in the tree view. An example will be the Relation object in Assembly3, which redirects the selection to the corresponding part object. The Relation Group can be consider as an alternative organization of the assembly object tree. The redirection functionality greatly simplified the implementation by reusing the part object's visual.

DocumentObjectExtension

The following new extension point as been added for the new APIs of DocumentObject, which are used by App::LinkBaseExtension

  • extensionGetLinkedObject()
  • extensionGetSubObject()
  • extensionGetSubObjects()
  • extensionHasChildElement()
  • extensionIsElementVisible()
  • extensionSetElementVisible()

FeaturePython

Here is a list of new APIs that can be overridden with Python code. The syntax is shown here in Python.

  • mustExecute(obj), return True to indicate the object must be executed.

  • allowDuplicateLabel(obj), return True to enable duplicated label of this object regardless of preference setting.

  • onBeforeChangeLabel(obj,newLabel), called when label is about to change to the string given by newLabel. The object can customize label change by returning a string that is different from newLabel.

  • getViewProviderNameOverride(obj), return a string of the view provider type of this object. This allows Python code to override the view provider of an object.

  • getSubObject(obj,subname,retType,matrix,transform,depth), return the sub object referenced by subname. Python code is expected to resolve the first hierarchy referenced in subname among its children, and then recursively calls the resolved child's getSubObject() to continue down the hierarchy. The getSubObject() exposed by DocumentObjectPy has almost exactly the same argument as this function. The meaning of the argument is described below,

    • obj, this object

    • subname, dot separated subname reference.

    • retType, indicating the requested return type of this function

      • 1 indicates a return type of tuple(subObj,matrix), where subObj is the resolve sub-object, and matrix is the accumulated transformation matrix.
      • 2 indicates a return type of tuple(subObj,matrix,pyObj), where the actual type of pyObj is implementation dependent. For example,Part::Feature derived object returns the Shape in pyObj.
      • 0 means to return only pyObj. This value is only meaningful when calling DocumentObjectPy.getSubObject(), but never for FeaturePython.getSubObject().
    • matrix is the input transformation matrix.

    • transform, indicate whether to accumulate the placement of this object into the transformation matrix. The purpose of this argument is so that a Link object can choose whether or not to override the placement of its linked object.

    • depth, is an integer indicating the current recursive level. It is used to detect cyclic references. Increase this depth value when calling child object's getSubObject().

  • getLinkedObject(obj,recurse,matrix,transform,depth), it has similar argument as getSubObject(), except recurse, which indicates whether to follow the link recursively in case of multi-level linking.

  • getSubObjects(obj), return a list of subname string referencing the child objects for exporting.

  • hasChildElement(obj), return True means this is a group type object

  • isElementVisible(obj,subname), return -1 if the object does not support children visibility control, and 1 if the child referenced in subname is visible, 0 if hidden. For performance reason, subname will only contain one hierarchy, i.e. reference to the immediate child object.

  • setElementVisible(obj,subname), change child object visibility

  • canLinkProperties(obj), return True to allow PropertyView to display linked object properties together with the object's own properties.

Besides API changes, FeaturePython, along with ViewProviderPythonFeature, SelectionObserverPython, and DocumentObserverPython, includes an optimization of early binding of Python functions. Instead of calling getAttr() (which is a really expensive call comparing to pure C++ code) inside C++ API and checking if a Python handler exists, it is checked at the time Proxy property is set (or modified), and the obtained Python callable is cached for future calling.

Expression and Spreadsheet

I had done a major refactor of the Expression and SpreadSheet. However, because of the potential security issue implied, I don't consider it matured enough for upstream right now. There are some minor changes include, though.

  • ObjectIdentifier is indeed refactored quite a bit, but the public interface remains unchanged. It now supports referencing linked property, and can deal better with potential ambiguity in object name vs. Property name.

  • A new expression syntax is introduced to unambiguously reference a property of the owner object, by omitting the object reference and starting with a leading ., kind of similar to Python relative import syntax.

  • Add new expression class PyObjectExpression so that an expression can now return any Python object, which also means that an expression can now be bound to most types of properties.

  • Improved external reference handling in expression, which behave the same as if using a PropertyXLink.

  • Improved spreadsheet recomputation logic.

  • Improved spreadsheet cell copy and paste functions.

Gui Namespace

ViewProvider

  • getModeSwitch(), getTransformNode(), exposes the internal Coin3D node used by the view provider. It is used by ViewProviderLink for 3D representation sharing.

  • getDefaultMode(), expose the current display mode regardless of the object's visibility. Used by ViewProviderLink to link object with independent visibility control.

  • getElementPicked(), this new function is the core API for implementing independent selection of instances of the same object. The default implementation is to call the original getElement() function, which makes this new API backward compatible. For hierarchical view provider, such as, ViewProviderLink or group type view provider, the implementation can follow the Coin3D SoPath within the input SoPickedPoint to disambiguate the user selection, an then translate the path to a subname reference as output.

  • getDetailPath(), this is the reverse function of the above getElementPicked(). It accepts a subname reference as input, and output the Coin3D SoPath, along with the Coin3D SoDetail to locate user selected the Coin3D node.

  • partialRender(), new helper function to activate partial rendering

  • beforeDelete(), new API that is guaranteed to be called before the object is about to be deleted, either when the object is being removed, or the document is about to be closed.

  • canDragAndDropObject(), new API to tell tree view whether this object supports removing the dropped object from its original parent. Current FreeCAD supports holding CTRL key while dropping to signal the user's intention to drop the object without removing it from its original parent. For some type object, such as Link, it sometimes does not make sense to remove the dropping object from its original parent.

  • canDropObjectEx(), dropObjectEx(), new API for dropping object with object's hierarchical information, pass in as a subname reference and top parent object. It is added to support relative linking functionality of Link object, where it links directly to the top parent, and indirectly to the dropping object through a subname reference. In other words, the linking to the dropping object is relative to the given top parent.

  • replaceObject(), the signature of this API is changed from upstream. The default implementation go through all link type properties in the parent object, and calls PropertyLinkBase::CopyOnLinkReplace() to do the actual job. The default implementation is powerful enough that all specializations of this API from upstream have been removed().

  • canRemoveChildrenFromRoot(), this function tells tree view to not remove children claimed by this object from the root. This API is originally added to demonstrate the fact that LinkGroup does not remove object from the global coordinate system, by leaving the object added to the group at the root level. You can toggle the visibility of the instances independent of each other.

  • update(), this function is made virtual, and is overridden in ViewProviderDocumentObject to correctly handle change of the newly added Visibility property of DocumentObject.

  • get/setElementColors(), new API for managing sub-object/element colors. They are implemented in PartGui::ViewProviderPartExt for setting colors of faces, edges, and vertices, and also in Gui::ViewProviderLink to override colors of its linked object.

  • startEditing(), this function is made virtual so that ViewProviderLink can redirect the call to its linked object.

  • setRenderCacheMode(), new API for configuring render caching. Currently only implemented in PartGui::ViewProviderPartExt.

  • signalChangeIcon, new signal used to inform tree view of icon change, exposed to Python through a method of the same name.

ViewProviderDocumentObject

  • showInTree(), override to let user control the tree item visibility. It simply reports the value of a newly added property, ShowInTree. User can hide the item by right click the item in the tree view, and select menu action Hide item. To review the hidden items, right click any item, or the document item, and select Show hidden item.

  • OnTopWhenSelected, a new enumeration property added to provide always-on-top effect when selected. The actual visual effect is implemented in View3DInventorViewer. Supported values are,

    • Disabled, default value.
    • Enabled, make the object always-on-top when either the whole object or any sub-element is selected.
    • Object, on-top only when the whole object is selected
    • Element, on-top only when any sub-element is selected
  • reattach(), new virtual API to be called when an object removal is undone.

  • update(), override to handle change of the newly added Visibility from DocumentObject.

  • canDropObjectEx(), override to disable dropping of external object, that is, object from an external document.

  • getElementPicked(), getDetailPath(), override to support hierarchical selection of group type object, i.e. those object returns a non null node in getChildRoot().

  • getBoundingBox(), new function to obtain object bounding box using Coin3D SoGetBoundingBoxAction, regardless of object's visibility. This function is used in the implementation of Box element selection command.

  • forceUpdate(), isUpdateForced(), new API added because some object (e.g. Part::Feature) has an optimization to disable visual update when the object is hidden, and ViewProviderLink needs a way to force update the visual in case there is a visible Link to that hidden object.

ViewProviderExtension

New extension point corresponding to the newly added APIs,

  • extensionBeforeDelete()
  • extensionCanDragAndDropObject()
  • extensionCanDropObjectEx()
  • extensionDropObjectEx()
  • extensionModeSwitchChange(), this extension function is called by setModeSwith() and setOverrideMode() function of ViewProvider
  • extensionStartRestoring()
  • extensionFinishRestoring()
  • extensionGetElementPicked()
  • extensionGetDetailPath()

ViewProviderPythonFeature

Here is a list of new APIs that can be overridden with Python code. The syntax is shown here in Python.

  • getElementPicked(pp) -> String, expect to return a string as the subname reference given the pick point. Return None means no selection is found with this object. pp here is an object of pivy.coin.SoPickedPoint.

  • getDetailPath(subname,path,append) -> SoDetail|Bool, expect to return either an object of pivy.coin.SoDetail or Boolean given a subname reference. True means the whole object is referenced, or else no reference is found.

    • subname: a String containing the subname reference
    • path: pivy.coin.SoFullPath, on input this is current path of the selection, as this function maybe recursively called by a higher hierarchy. On output, append SoNode of the current hierarchy.
    • append: if True, then append the view provider root node and switch node. Or else, append node below the switch node. This is used by ViewProviderLink, because it will replace the root and switch node.
  • setEditViewer(obj, viewer, mode) -> Bool

  • unsetEditViewer(obj, viewer)

  • canRemoveChildFromRoot() -> Bool

  • canDragAndDropObject(obj) -> Bool

  • canDropObjectEx(obj,parent,subname) -> Bool

    • obj: the dropping object
    • parent: the top parent of the dropping object
    • subname: the subname reference representing the hierarchy path leading from top parent to the dropping object.
  • dropObjectEx(obj,parent,subname), this function as the same argument as canDropObjectEx() above.

  • startRestoring(), finishRestoring(), these functions are modified to call C++ object counterpart as well as Python code, to make sure the view provider extension has a chance of handling restoring.

  • getIcon(), add support for Python code to directly return a QIcon Python object. The actual implementation of extracting QIcon from Python object is in Gui::PythonWrapper::toQIcon().

Application

  • signalShowHidden, new signal triggered when a document changes its ShowHidden property, i.e. whether to show the hidden object in tree view.

  • editDocument(), setEditDocument(), for getting and setting the current editing document.

Document

  • signalShowHidden, new signal triggered when this document changes its ShowHidden property, i.e. whether to show the hidden object in tree view.

  • signalShowItem, new signal triggered when an object of this document changes its ShowInTree property.

  • getViewProviderByPathFromHead(), new API to return the first view provider found given a Coin3D SoPath, used to support context-aware hierarchical selection of group type object. This function obsoletes getViewProviderByPathFromTail(), because the default implementation in ViewProviderDocumentObject::getElementPicked() is able to walk down the hierarchy by itself.

  • getViewProvider(SoNode*), obtain a view provider given its root Coin3D node. The private class Gui::DocumentP now maintains a map (_CoinMap) from root node to view provider to accelerate view provider lookup.

  • setEdit(), set/getEditingTransform(), get/setInEdit(), these APIs are either new or modified to support in-place editing. Link type object makes it possible for the same object to appear in multiple placements, and even across document boundary. These APIs, together with View3DInvertorVewer::setupEditingRoot(), allows the user to edit an object out of its original placement.

    It works like this. setEdit() is modified to accept an additional argument, subname, in order to support editing sub-object. If subname is null, then the subname reference will be deduced from the current selection by querying Gui::SelectionSingleton. setEdit() uses the subname to obtain the current object transformation, by calling App::Document::getSubObject(), and the resulting transformation matrix is exposed through getEditingTransform(), but can be overridden by user code through setEditingTransformation().

    An editing root node is added to each View3DInventorViewer scene graph. ViewProvider that supports editing in multiple placements can either,

    • Add any editing helper nodes manually into the editing root by calling View3DInventorViewer::setupEditingRoot(node,matrix) with some transformation obtained from getEditingTransform() or through other meaning. This method is demonstrated in Gui::ViewProviderDragger.

    • Simply calls setupEditingRoot(0,0) which will cause View3DInventorViewer to transfer all children nodes of the editing view provider root node into the editing root with a replaced transformation node initialized using transformation obtained from getEditingTransform(). Node changes will be automatically reverted after editing is done. This method has the side effect of automatically hiding all other non editing instance of the editing object, which is desired in some use cases, while the first method keeps all other instances untouched. This method of editing is demonstrated in SketcherGui::ViewProviderSketch

    The ideal place to call View3DInventorViewer::setupEditingRoot() is in ViewProvider::setEditViewer() function.

  • handleChildren3D(), improve efficiency of node tree building of group type view provider.

  • checkTransactiveID(), private helper function to check and warn user if a multi-step undo/redo is about to break a grouped transaction.

  • undo(), redo(), modified to check other document for grouped transaction by calling checkTransactionID()

  • isPerformingTransaction(), unlike App::Document::isPerformingTransaction(), this function will report True while undo/redo this and possibly other document's grouped transaction.

  • beforeDelete(), new function that will be called before this document is about to be deleted. It will call all its children view provider's new ViewProvider::beforeDelete() API.

  • save(), modified to check for dependent external documents, and ask user for permission to save them before saving the current document, so that the external file time stamp is up-to-date when current document is saved.

  • Restore(), add toggling view provider isRestoring flag.

  • slotFinishRestore(), catch the new App::Document::signalFinishRestoreObject() signal and calls the ViewProvider::finishRestoring(), and reset its isRestoring flag. This unifies the logic of both restoring from physical document and importing from clipboard, and fixed the missing call of ViewProvider::finishRestoring() when importing.

  • slotFinishRestoreDocument(), modified to check for any object that is still touched, and not reset Modified status if found.

  • slotNewObject(), modified to call the new DocumentObject::getViewProviderStored() function to allow Python code to override C++ view provider type. And also call the new API ViewProviderDocumentObject::reattach() in case the new object is the result of undoing a previous delete operation.

  • slotDeleteObject(), modified to handle the new in-place editing view provider, and call the new ViewProvider::beforeDelete() API.

Command

Because the addition of external Link, Python code can not always use App.ActiveDocument.getObject() function to obtain the correct object in commands. Great effort has been spent to correct this coding habit in various workbenches, such as Part, PartDesign, and Sketcher. The following helper macros, declared in Gui/Command.h, are provided to ease the migration.

  • FCMD_DOC_CMD(doc,cmd), execute the string command in cmd using C++ document object doc, it expands to the following simplified code with safety checking omitted. Note that cmd can be a composite expression joined by operator <<.
std::ostringstream ss;
ss << "App.getDocument('" << doc->getName() << "')." << cmd;
Gui::Command::runCommand(Gui::Command::Doc,ss.str().c_str());

// example
//      FCMD_DOC_CMD(doc,"addObject('Part::Feature','" << name << "')")
// issue command
//      App.getDocument(doc_name).addObject('Part::Feature',name)
  • FCMD_OBJ_DOC_CMD(obj,cmd), equivalent to the following macro call, with safety check on object pointer obj.
FCMD_DOC_CMD(obj->getDocument(),cmd)`,
  • FCMD_OBJ_CMD(obj,cmd), execute a command with an object, expands roughly to,
std::ostringstream ss;
ss << "App.getDocument('" << obj->getDocument()->getName() << "').getObject('" 
   << obj->getNameInDocument() << "')." << cmd;
Gui::Command::runCommand(Gui::Command::Doc,ss.str().c_str());

// example
//      double length = 100.0;
//      FCMD_OBJ_CMD(cube,"Length = " << length);
// issues command
        App.getDocument(doc_name).getObject(obj_name).Length = 100.0
  • FCMD_VOBJ_CMD(obj,cmd), same as above, but use view object instead of object. Note that the input argument obj is still required to be a document object.
std::ostringstream ss;
ss << "Gui.getDocument('" << obj->getDocument()->getName() << "').getObject('" 
   << obj->getNameInDocument() << "')." << cmd;
Gui::Command::runCommand(Gui::Command::Gui,ss.str().c_str());

// example
//      bool visibility = true;
//      FCMD_VOBJ_CMD(obj, "Visibility = " << (visibility?"True":"False"));
// issues command
        Gui.getDocument(doc_name).getObject(obj_name).Visibility = True
  • FCMD_OBJ_CMD2(cmd,obj,...), same purpose as FCMD_OBJ_CMD() but use conventional C variadic function, instead of C++ stream,
Gui::Command::doCommand(Gui::Command::Doc,"App.getDocument('%s').getObject('%s')." cmd,
    obj->getDocument()->getName(),obj->getNameInDocument(),## __VA_ARGS__);

// example
//      double length = 100.0;
//      FCMD_OBJ_CMD2("Length = %g", cube, length);
  • FCMD_OBJ_CMD2(cmd,obj,...), same purpose as FCMD_VOBJ_CMD() but use conventional C variadic function, instead of C++ stream,
Gui::Command::doCommand(Gui::Command::Gui,"Gui.getDocument('%s').getObject('%s')." cmd,
    obj->getDocument()->getName(),obj->getNameInDocument(),## __VA_ARGS__);

// example
//      bool visibility = true;
//      FCMD_VOBJ_CMD2("Visibility = %s", obj, visibility?"True":"False");
  • FCMD_OBJ_HIDE(obj), FCMD_OBJ_SHOW(obj), shortcut of toggle visibility using a document object with the newly added Visibility property. They simply expands to,
FCMD_OBJ_CMD(obj,"Visibility=True");
FCMD_OBJ_CMD(obj,"Visibility=False");

Below is a list of API changes of class Gui::Command,

  • isActive() function is made public. So that other class can create context menus with only active commands. An example usage is in Gui::Workbench::createLinkMenu()

  • getObjectCmd(obj,prefix=0,postfix=0), static helper function to generate Python code of accessing the given object. In case the object passed in is invalid or null, it returns a string of "None". For example,

    getObjectCmd(obj, "[", "]")

will generate,

    "[ App.getDocument(doc_name).getObject(obj_name) ]"
  • getUniqueObjectName(), accepts an extra optional argument obj of a pointer to document object. If obj is non-zero, it will call the object's owner document's getUniqueObjectName() function, or else the active document's.

  • The following APIs are renamed with a proceeding underscore to accept extra arguments of source file and line number for better debugging purpose. To maintain source code compatibility, macros are added with the same name as the original APIs

    • doCommand()
    • runCommand()
    • assureWorkbench()
    • copyVisual()

MainWindow

There are two changes in MainWindow

  • Action status are no longer updated using a periodic timer, this is to prevent lengthy Python code hogging processing time. Instead, the actions are updated in response to various signals. A single shot timer is used to limit the frequency of action status checking. A new function updateActions() is added for others to trigger the action update. Search the code for calling of this function to see which signals or events are responsible for triggering the update.

  • Originally, the actionLabel is used for display console log message in MainWindow status bar. And, QStatusBar::showMessage() is used to display a temporary message, e.g. tree status message, and 3D view pre-selection, etc.

    The problem is that QStatusBar temporary message takes precedence over normal message that displayed inside its child widget (i.e. actionLabel in this case), which means important messages may be easily overwritten by not so important information, like toolbar tips. To fix this problem, the message display location are swapped, such that QStatusBar::showMessage() is now used for console message, and actionLabel for the rest. The user now has a chance to catch the console message, especially errors and warnings, without the report view.

SelectionSingleton

The changes in Gui::SelectionSingleton is among the most critical ones in order to make Link work. The goal of the design change is first to make sure existing code remains working without modification, and then allow new code to obtain full object hierarchy information of the user selection. See here for more details.

APIs Extensions

Most of the APIs are extended by introducing an extra integer argument, resolve, which has the following meaning,

  • 0, no auto resolving, the returned selected object is the top level parent of the selection, and the hierarchical path of the actual selected object is returned as a subname reference.

  • 1, as the default value, meaning auto resolving for backward compatibility. Gui::SelectionSingleton will walk down the hierarchy and return the actual selected object without selection context. Non-object sub-element reference is still kept in the subname reference as before.

  • 2, like 1, but translate the sub-element reference to new style mapped element name, if there is one. This is used to make the new topological naming backward compatible. See here for more details

Signal for selection change is extended in a different way by adding two new signals, as shown below

  • signaleSelectionChanges, original selection change signal, for auto resolved object. Same as resolve=1;

  • signalSelectionChanges2, no auto resolve, i.e. resolve=0;

  • signalSelectionChange3, resolve with mapped element, i.e. resolve=2.

The APIs extended with the resolve argument are listed below,

  • addSelectionGate()
  • countObjectsOfType()
  • getObjectsOfType()
  • getSelection()
  • getSelectionEx()
  • getComplementeSelection()
  • hasSelection()

The extra resolve argument is also added to the following class constructor

  • SelectionObserver, in addition to resolve, another extra augment, attached is added to allow caller decide if the new constructed observer is attached or not.

  • SelectionObserverPython

Picked List

Picked list for holding a list of alternative objects picked by mouse selection in the 3D view. User code get access to picked list through the following APIs,

  • needPickedList(), check if picked list is activated. User activates the picked list by select an checkbox in SelectionView.

  • enablePickedList(), allow user code to enable picked list.

  • hasPickedList(), check if picked list is empty.

  • getPickedList(), obtain the picked list with the same format as getSelection()

  • getPickedListEx(), obtain the picked list with same format as getSelectionEx()

The actual implementation of constructing the picked list is in SoFCUnifiedSelection, and is passed to SelectionSingleton using the following API of Selection with an extra argument, pickedList,

  • addSelection(),
  • rmvSelection(),

The following screen cast shows the picked list in action.

Selection Stack

With deep nested hierarchical models, such as an assembly with lots of sub-assemblies, navigation among various objects become increasingly difficult. SelectionSingleton now offers a new facility, called Selection Stack, to help user keep track of their selections. It is exposed using the following APIs,

  • selStackGoBack(), exposed to Python and end-user through command Std_SelBack
  • selStackGoForward(), exposed to Python and end-user through command Std_SelForward
  • selStackPush(), exposed to Python as Selection.pushSelSback()

It works similar to document undo/redo command. There is no automatic saving of every selection changes. Only commands that are meant for navigation purpose will call selStackPush() command to manually save the current selection to the selection stack. The core currently provides the following navigation command,

  • Std_SelBack, go back to previous save selection
  • Std_SelForward, go forward to previous backed selection
  • Std_LinkSelectLinked, select the linked object
  • Std_LinkSelectLinkedFinal, select the deepest linked object in case of multi-level linking
  • Std_LinkSelectAllLinks, select all link object that links to the current selection
  • Std_SelectAllInstances, select all appearance of the current selection in all object hierarchies

These navigation command is best used together with the following new tree view options, activated by tree view context menu, Tree view options

  • Sync Selection, scroll the tree view to the selected item
  • Sync view, auto activate the document of the selected item

Python user code can easily implement navigation command as follow,

  • Call Selection.pushSelStack() to save the current selection for backing,
  • read the current selection, and calculate the next selection target(s)
  • Call Selection.clearSelection()
  • Call Selectino.addSelection() to select the intended navigation target(s)
  • Call Selection.pushSelStack() again to save the selection for potential forwarding.

See here for an demonstration of the Selection Stack in action.

Other Changes

  • setVisible(), a helper function to set/unset/toggle visibility of the current selected object. Because the introduction of Link, the same object can appear in multiple places, some may have separate visibility control, but some not. For example, there a two Link objects that links to the same group, and user selects the same child object under these two linked group. Although the same object is selected, but they in different selection context, and thus, exists as separate entries in Selection. However, when toggling visibility, we must only count them as one entry, or else the double toggling will negate each other, resulting no visibility changes. setVisible() is created to handle these kind of complex scenario.

  • updateSelection(), new API to re-synchronize the selection and preselection status of a given object. For example, it is called by setVisible() to update view provider selection highlight when its visibility is toggled.

  • setPreselect(), accepts an extra integer argument, signal with the following meaning,

    • 0, default, for informing a preselection has been made;
    • 1, user initiated preselection;
    • 2, reserved for preselection initiated by mouse over tree view item.

SelectionChanges

This is the class encoding the message of selection change. Current implementation of the class uses raw pointer to refer to the internal list of selected document and object names. This may cause problem when user code decides to add or remove the current selection while handling a selection change event. To make it more robust, SelectionChanges now holds all names inside the class itself.

A few new message types are introduced, mostly for internal uses,

  • SetPreselectSignal, indicating that the caller wants to initiate a preselection, unlike existing SetPreselect, which is for informing a preselection has been made. This message is handled in SoFCUnifiedSelection::doAction to make is easy for user code to initiate preselection.

  • Show/HideSelection, for synchronizing view object's selection visual highlight when its visibility status changes. This fixes the problem of missing selection highlight when an object is made visible. It is handled in View3DInventorViewer::onSelectionChanged()

  • PickedListChanged, for selection changes in the new PickedList from Selection. This message is meant to be used by user code. A new handler function pickedListChanged() is added to SelectionObserverPython for this purpose.

SelectionView

The parent class is changed from Gui::SelectionSingleton::ObserverType to Gui::SelectionObserver, so that SelectionView can obtain the full hierarchy information of the selection. In addition, the observer will be auto detached when the selection view widget is hidden to improve overall performance.

Add support for toggling PickedList function, and displaying of the current picked list.

TreeWidget

Gui::TreeWidget does not really expose any APIs for use by other classes. So here we will mainly describes the GUI changes, with a brief mentioning of the implementation changes, which is significant because of the introduction of selection context.

The internal object map has changed its key from the object name to object pointer, because the map may store external object brought in by a Link, which may have the same name as objects from the linking document. Currently, object map is still maintained per document, which may result in some inefficiency, but has the advantage of a clear logic separation. Future development may explore the possibility of a single unified map for objects of all opened documents.

There are actually two tree view in FreeCAD. The other tree view is from the combo view. For easy debugging, each TreeWidget instance is given a name. And its selection observer is detached when invisible.

Tree item status update is no longer triggered by a periodic timer for the sake of both performance and easy debugging. It is now triggered by various signals and events, similar to MainWindow::updateActions(). Search the code for calling of updateStatus() to find out the actual trigger. A single-shot timer is used to limited the frequency of update. The same technique is applied to selection changes to improve performance of large selections.

Three options are exposed through context menu Tree view options,

  • Pre-selection, enable pre-select of object in 3D view when mouse over tree item. The actual preselection logic is implemented by View3DInventorViewer::checkGroupOnTop()

  • Sync selection, auto scroll to the tree item when the corresponding object is selected in 3D view.

  • Sync view, auto activate the owner document and 3D view of the object selected in tree view.

The following new context menu actions are added when an object item is selected,

  • Show hidden items, toggle whether to show hidden items in an document.

  • Hide/Show item, toggle the visibility of object item in the tree view.

  • Recompute object, recompute only the selected objects and their dependencies.

The tree view will now catch signalRecomputed, and auto expand and scroll to the first object that reports error during recomputation.

New context aware drag and drop support, including dragging object across document boundary.

PropertyView

Add support for Link object, which display the properties of the linked object together with the ones from link itself. The linked properties are displayed with green color for differentiation.

Add context menu to expose more advanced functions, such as,

  • Revealing all properties, including hidden ones,

  • Binding expression on any property,

  • Add/remove dynamic properties of all types, removable properties will be shown with yellow background,

  • Change individual property status, such as hidden, read-only, transient, etc.

Added timer to limit the frequency of handling selection change, which fixes the severe slow down on large selection.

Clear property view on undo/redo to avoid potential crash.

Dynamically detach selection observer on hidden.

DlgPropertyLink

Added support for PropertyXLink by adding a combo box to switch between documents for linking to external objects.

Change list view to tree view in order to support linking into a sub-object.

Add an additional list view to let user filter objects by their type.

View3DInventorViewer

The parent class is changed from Gui::SelectionSingleton::ObserverType to Gui::SelectionObserver, so that SelectionView can obtain the full hierarchy information of the selection.

Added an On-Top Coin3D group node in the scene graph to support the new ViewProvider::OnTopWhenSelected property, and object highlight (which is also made on-top) on mouse over tree view item. The logic is implemented in function checkGroupOnTop(), and called in response to various selection change event. The 3D rendering of the on-top object relies on a new Coin3D node class SoFCPathAnnotation, which is similar to Coin3D SoAnnotation but is capable of rendering a specific Coin3D path, in order to show the correct object hierarchy.

The following APIs are added to support in-place editing.

  • setupEditingRoot(), called to initiate in-place editing.

  • resetEditingRoot(), revert scene graph changes after in-place editing.

  • setEditingTransform(), setup the editing transformation matrix.

  • getPointOnRay(), this helper function is moved from Gui::ViewProvider here because of the newly added in-place editing may move all its children node into the editing root node of View3DInventorViewer. The current only user of getPointOnRay(), SketcherGui::ViewProviderSketch, supports in-place editing this way.

SoFCUnifiedSelection

This class is the key to make selection context to work in 3D view. The selection and pre-selection logic is refactored and moved from doAction() into dedicated functions, setSelection() and setHighlight(). The implementation looks for the first view provider root node found in the Coin3D path inside a SoPickedPoint, and calls that view provider's getElementPicked() to obtained selection hierarchy context. See here for more details.

New support for PickedList is implemented by exploring the picked point list from Coin3D SoHandleEventAction.

SoFCSelection

This is the legacy class for handling selection in the past. It is still used to handle selection of non-Part derived objects, such as ViewProviderOriginFeature. In order to have the same selection context support as SoFCUnifiedSelection, the selection handling logic is skipped in its handleEvent() function, but instead, handled inside SoFCUnifiedSelection like everyone else, thus truly unifies 3D view selection logic. A new attribute, useNewSelection is added to control this behavior.

SoFCSelectionRoot

This class, derived from Coin3D SoSeparator, stores the actual selection context, and allows other shape classes to dynamically look up the current selection context, and property render selection and preselection highlight. Three shape classes, SoBrepPointSet, SoBrepEdgeSet, and SoBrepFaceSet (also SoFCSelection, for selection rendering of non-Part derived object) are modified to support context-aware selection rendering.

Part

The Part module does the actual geometry modeling. It has been modified so that other modules can obtain geometry shape through Link. The most import function here is a static helper function Part::Feature::getTopoShape(). It allows one to obtain a geometry shape from a object that is not derived from Part::Feature, including Link, and group type object. For group object, getTopoShape() relies on the new DocumentObject:getSubObjects() function to obtain the children object, and creates a compound shape on demand. To improve performance, it uses internal shape caches to reduce number of rebuilds.

getTopoShape() is exposed to Python as Part.getShape(). Please checkout its doc string for more details.

Various Part features and commands has been modified to make it work with Link. It's basically just removing type checking of input objects, and use Part.getShape() to obtain the shape of child objects.

PropertyContainerPy also contains a modification to return Shape attribute of any object that does not have a Shape property using Part.getShape(). This modification allows most Part Python feature to work with Link without code modification.

ViewProviderExt has been modified to support any type of object by using getTopoShape() to obtain the shape from the attached object.

PartDesign and Sketcher

Mostly contains modification to various commands to support in-place editing.

Draft

New class _DraftLink, along with its companion _ViewProviderDraftLink, are added As a demonstration of how to use App::LinkBaseExtension in Python to implement Link type object. _Array and _PathArray are modified to derive from this class to support linked array. A new attribute is added to turn on link function when the object is created. Linked array behave almost the same as original array object, with the additional benefit of external link. Moreover, when ShowElement property is turned on, each element of the array is revealed as a child object to let user customize the placement and material.

Import

Class ImportOCAF2 and ExportOCAF2 has been added as a new implementation of STEP importer/exporter, which uses Link and LinkGroup to greatly improve performance of handling nested assembly structures. Proper support for edge and visibility has been added, as well as support of assembly component instance styling override using Link's color and visibility override function. New options are exposed to STEP import/export preference page, including options to access original upstream importer.

TechDraw

Some minor changes are included to make it support Link and LinkGroup.