// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2011 Jürgen Riegel * * Copyright (c) 2011 Werner Mayer * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #ifndef SRC_APP_DOCUMENTOBJECT_H_ #define SRC_APP_DOCUMENTOBJECT_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Base { class Matrix4D; } namespace App { class Document; class DocumentObjectGroup; class DocumentObjectPy; class Expression; // clang-format off enum ObjectStatus { Touch = 0, Error = 1, New = 2, Recompute = 3, // set when the object is currently being recomputed Restore = 4, Remove = 5, PythonCall = 6, Destroy = 7, Enforce = 8, Recompute2 = 9, // set when the object is being recomputed in the second pass PartialObject = 10, PendingRecompute = 11, // set by Document, indicating the object is in recomputation queue ObjImporting = 13, // Mark the object as importing NoTouch = 14, // no touch on any property change GeoExcluded = 15, // mark as a member but not claimed by GeoFeatureGroup Expand = 16, // indicate the object's tree item expansion status NoAutoExpand = 17, // disable tree item auto expand on selection for this object PendingTransactionUpdate = 18, // mark that the object expects a call to onUndoRedoFinished() after transaction is finished. RecomputeExtension = 19, // mark the object to recompute its extensions TouchOnColorChange = 20, // inform view provider touch object on color change Freeze = 21, // do not recompute ever }; // clang-format on /** Return object for feature execution */ class AppExport DocumentObjectExecReturn { public: explicit DocumentObjectExecReturn(const std::string& sWhy, DocumentObject* WhichObject = nullptr) : Why(sWhy) , Which(WhichObject) {} explicit DocumentObjectExecReturn(const char* sWhy, DocumentObject* WhichObject = nullptr) : Which(WhichObject) { if (sWhy) { Why = sWhy; } } std::string Why; DocumentObject* Which; }; /** * @brief %Base class of all objects handled in the @ref App::Document "Document". * @ingroup DocObject * @details For a more high-level overview see topic @ref DocObject "Document Object". */ class AppExport DocumentObject: public App::TransactionalObject { PROPERTY_HEADER_WITH_OVERRIDE(App::DocumentObject); private: // store read-only property names at freeze // in order to retablish correct status at unfreeze std::vector readOnlyProperties; public: PropertyString Label; PropertyString Label2; PropertyExpressionEngine ExpressionEngine; /// Allow control visibility status in App name space PropertyBool Visibility; // clang-format off /// signal before changing a property of this object fastsignals::signal signalBeforeChange; /// signal on changed property of this object fastsignals::signal signalChanged; /// signal on changed property of this object before document scoped signalChangedObject fastsignals::signal signalEarlyChanged; // clang-format on /// returns the type name of the ViewProvider virtual const char* getViewProviderName() const { return ""; } /** * This function is introduced to allow Python feature override its view provider. * The default implementation just returns \ref getViewProviderName(). * * The core will only accept the overridden view provider if it returns * true when calling Gui::ViewProviderDocumentObject::allowOverride(obj). * If not, the view provider will be reverted to the one returned from \ref * getViewProviderName(). */ virtual const char* getViewProviderNameOverride() const { return getViewProviderName(); } /// Constructor DocumentObject(); ~DocumentObject() override; /// returns a value that uniquely identifies this DocumentObject. const char* getDagKey() const; /// returns the name which is set in the document for this object (not the name property!) const char* getNameInDocument() const; /// Return the object ID that is unique within its owner document long getID() const { return _Id; } /// returns the name that is safe to be exported to other document std::string getExportName(bool forced = false) const; /// Return the object full name of the form DocName#ObjName std::string getFullName() const override; /// Return the object full label in the form DocName#ObjName std::string getFullLabel() const; bool isAttachedToDocument() const override; const char* detachFromDocument() override; /// gets the document in which this Object is handled App::Document* getDocument() const; /** Set the property touched -> changed, cause recomputation in Update() */ //@{ /// set this document object touched (cause recomputation on dependent features) void touch(bool noRecompute = false); /// test if this document object is touched bool isTouched() const; /// Enforce this document object to be recomputed void enforceRecompute(); /// Test if this document object must be recomputed bool mustRecompute() const; /// reset this document object touched void purgeTouched() { StatusBits.reset(ObjectStatus::Touch); StatusBits.reset(ObjectStatus::Enforce); setPropertyStatus(0, false); } /// set this feature to error bool isError() const { return StatusBits.test(ObjectStatus::Error); } bool isValid() const { return !StatusBits.test(ObjectStatus::Error); } /// remove the error from the object void purgeError() { StatusBits.reset(ObjectStatus::Error); } /// returns true if this objects is currently recomputing bool isRecomputing() const { return StatusBits.test(ObjectStatus::Recompute); } /// returns true if this objects is currently restoring from file bool isRestoring() const { return StatusBits.test(ObjectStatus::Restore); } /// returns true if this objects is currently removed from the document bool isRemoving() const { return StatusBits.test(ObjectStatus::Remove); } /// set this document object freezed (prevent recomputation) void freeze(); /// set this document object unfreezed (and touch it) void unfreeze(bool noRecompute = false); /// returns true if this objects is currently freezed bool isFreezed() const { return StatusBits.test(ObjectStatus::Freeze); } /// return the status bits unsigned long getStatus() const { return StatusBits.to_ulong(); } bool testStatus(ObjectStatus pos) const { return StatusBits.test(size_t(pos)); } void setStatus(ObjectStatus pos, bool on) { StatusBits.set(size_t(pos), on); } //@} int isExporting() const; /** Child element handling */ //@{ /** Set sub-element visibility * * For performance reason, \c element must not contain any further * sub-elements, i.e. there should be no '.' inside \c element. * * @return -1 if element visibility is not supported, 0 if element is not * found, 1 if success */ virtual int setElementVisible(const char* element, bool visible); /** Get sub-element visibility * * @return -1 if element visibility is not supported or element not found, 0 * if element is invisible, or else 1 */ virtual int isElementVisible(const char* element) const; /// return true to activate tree view group object handling and element visibility virtual bool hasChildElement() const; //@} /** DAG handling This part of the interface deals with viewing the document as a DAG (directed acyclic graph). */ //@{ /// OutList options enum OutListOption { /// Do not include link from expression engine OutListNoExpression = 1, /// Do not hide any link (i.e. include links with LinkScopeHidden) OutListNoHidden = 2, /// Do not include link from PropertyXLink OutListNoXLinked = 4, }; /// returns a list of objects this object is pointing to by Links const std::vector& getOutList() const; std::vector getOutList(int option) const; void getOutList(int option, std::vector& res) const; /// returns a list of objects linked by the property std::vector getOutListOfProperty(App::Property*) const; /// returns a list of objects this object is pointing to by Links and all further descended std::vector getOutListRecursive() const; /// clear internal out list cache void clearOutListCache() const; /// get all possible paths from this to another object following the OutList std::vector> getPathsByOutList(App::DocumentObject* to) const; /// get all objects link to this object const std::vector& getInList() const; /// get all objects link directly or indirectly to this object std::vector getInListRecursive() const; /** Get a set of all objects linking to this object, including possible external parent objects * * @param inSet [out]: a set containing all objects linking to this object. * @param recursive [in]: whether to obtain recursive in list * @param inList [in, out]: optional pointer to a vector holding the output * objects, with the furthest linking object ordered last. */ void getInListEx(std::set& inSet, bool recursive, std::vector* inList = nullptr) const; /** Return a set of all objects linking to this object, including possible external parent * objects * @param recursive [in]: whether to obtain recursive in list */ std::set getInListEx(bool recursive) const; /// get group if object is part of a group, otherwise 0 is returned DocumentObjectGroup* getGroup() const; /// test if this object is in the InList and recursive further down bool isInInListRecursive(DocumentObject* objToTest) const; /// test if this object is directly (non recursive) in the InList bool isInInList(DocumentObject* objToTest) const; /// test if the given object is in the OutList and recursive further down bool isInOutListRecursive(DocumentObject* objToTest) const; /// test if this object is directly (non recursive) in the OutList bool isInOutList(DocumentObject* objToTest) const; /// internal, used by PropertyLink to maintain DAG back links void _removeBackLink(DocumentObject*); /// internal, used by PropertyLink to maintain DAG back links void _addBackLink(DocumentObject*); //@} /** * @brief testIfLinkIsDAG tests a link that is about to be created for * circular references. * @param objToLinkIn (input). The object this object is to depend on after * the link is going to be created. * @return true if link can be created (no cycles will be made). False if * the link will cause a circular dependency and break recomputes. Throws an * error if the document already has a circular dependency. * That is, if the return is true, the link is allowed. */ bool testIfLinkDAGCompatible(DocumentObject* linkTo) const; bool testIfLinkDAGCompatible(const std::vector& linksTo) const; bool testIfLinkDAGCompatible(App::PropertyLinkSubList& linksTo) const; bool testIfLinkDAGCompatible(App::PropertyLinkSub& linkTo) const; /** Return the element map version of the geometry data stored in the given property * * @param prop: the geometry property to query for element map version * @param restored: whether to query for the restored element map version. * In case of version upgrade, the restored version may * be different from the current version. * * @return Return the element map version string. */ virtual std::string getElementMapVersion(const App::Property* prop, bool restored = false) const; /// Return true to signal re-generation of geometry element names virtual bool checkElementMapVersion(const App::Property* prop, const char* ver) const; public: /** mustExecute * We call this method to check if the object was modified to * be invoked. If the object label or an argument is modified. * If we must recompute the object - to call the method execute(). * 0: no recomputation is needed * 1: recomputation needed * * @remark If an object is marked as 'touched' then this does not * necessarily mean that it will be recomputed. It only means that all * objects that link it (i.e. its InList) will be recomputed. */ virtual short mustExecute() const; /** Recompute only this feature * * @param recursive: set to true to recompute any dependent objects as well */ bool recomputeFeature(bool recursive = false); /// get the status Message const char* getStatusString() const; /** Called in case of losing a link * Get called by the document when a object got deleted a link property of this * object ist pointing to. The standard behaviour of the DocumentObject implementation * is to reset the links to nothing. You may override this method to implement * additional or different behavior. */ virtual void onLostLinkToObject(DocumentObject*); PyObject* getPyObject() override; /** Get the sub element/object by name * * @param subname: a string which is dot separated name to refer to a sub * element or object. An empty string can be used to refer to the object * itself * * @param pyObj: if non zero, returns the python object corresponding to * this sub object. The actual type of this python object is implementation * dependent. For example, The current implementation of Part::Feature will * return the TopoShapePy, event if there is no sub-element reference, in * which case it returns the whole shape. * * @param mat: If non zero, it is used as the current transformation matrix * on input. And output as the accumulated transformation up until and * include the transformation applied by the final object reference in \c * subname. For Part::Feature, the transformation is applied to the * TopoShape inside \c pyObj before returning. * * @param transform: if false, then it will not apply the object's own * transformation to \c mat, which lets you override the object's placement * (and possibly scale). * * @param depth: depth limitation as hint for cyclic link detection * * @return The last document object referred in subname. If subname is empty, * then it shall return itself. If subname is invalid, then it shall return * zero. */ virtual DocumentObject* getSubObject(const char* subname, PyObject** pyObj = nullptr, Base::Matrix4D* mat = nullptr, bool transform = true, int depth = 0) const; /** Return a list of objects referenced by a given subname including this object * @param subname: the sub name path * @param subsizes: optional sub name sizes for each returned object, that is, * ret[i] = getSubObject(std::string(subname, subsizes[i]).c_str()); * @param flatten: whether to flatten the object hierarchies that belong to * the same geo feature group, e.g. (Part.Fusion.Box -> Part.Box) * * @return Return a list of objects along the path. */ std::vector getSubObjectList(const char* subname, std::vector* subsizes = nullptr, bool flatten = false) const; /// reason of calling getSubObjects() enum GSReason { /// default, mostly for exporting shape objects GS_DEFAULT, /// for element selection GS_SELECT, }; /** Return name reference of all sub-objects * * @param reason: indicate reason of obtaining the sub objects * * The default implementation returns all object references in * PropertyLink, and PropertyLinkList, if any * * @return Return a vector of subname references for all sub-objects. In * most cases, the name returned will be the object name plus an ending * '.', which can be passed directly to getSubObject() to retrieve the * name. The reason to return the name reference instead of the sub object * itself is because there may be no real sub object, or the sub object * need special transformation. For example, sub objects of an array type * of object. */ virtual std::vector getSubObjects(int reason = 0) const; /// Obtain top parents and subnames of this object using its InList std::vector> getParents(int depth = 0) const; /// Obtain the first parent group of this object App::DocumentObject* getFirstParent() const; /** Return the linked object with optional transformation * * @param recurse: If false, return the immediate linked object, or else * recursively call this function to return the final linked object. * * @param mat: If non zero, it is used as the current transformation matrix * on input. And output as the accumulated transformation till the final * linked object. * * @param transform: if false, then it will not accumulate the object's own * placement into \c mat, which lets you override the object's placement. * * @return Return the linked object. This function must return itself if the * it is not a link or the link is invalid. */ virtual DocumentObject* getLinkedObject(bool recurse = true, Base::Matrix4D* mat = nullptr, bool transform = false, int depth = 0) const; /* Return true to cause PropertyView to show linked object's property */ virtual bool canLinkProperties() const { return true; } /* Return whether this object is a link */ virtual bool isLink() const { return false; } /* Return whether this object is a link group */ virtual bool isLinkGroup() const { return false; } /* Return true to bypass duplicate label checking */ virtual bool allowDuplicateLabel() const { return false; } /// Handle Label changes, including forcing unique label values, /// signalling OnBeforeLabelChange, and arranging to update linked references, /// on the assumption that after returning the label will indeed be changed to /// the (altered) value of newLabel. /// Returns a vector of referenging (linking) properties as produced by /// PropertyLinkBase::updateLabelReferences which is needed for undo/redo purposes. std::vector>> onProposedLabelChange(std::string& newLabel); /*** Called to let object itself control relabeling * * @param newLabel: input as the new label, which can be modified by object itself * * This function is called before onBeforeChange() */ virtual void onBeforeChangeLabel(std::string& newLabel) { (void)newLabel; } friend class Document; friend class Transaction; friend class ObjectExecution; static DocumentObjectExecReturn* StdReturn; void Save(Base::Writer& writer) const override; /* Expression support */ virtual void setExpression(const ObjectIdentifier& path, std::shared_ptr expr); void clearExpression(const ObjectIdentifier& path); virtual const PropertyExpressionEngine::ExpressionInfo getExpression(const ObjectIdentifier& path) const; virtual void renameObjectIdentifiers(const std::map& paths); const std::string& getOldLabel() const { return oldLabel; } const char* getViewProviderNameStored() const { return _pcViewProviderName.c_str(); } bool removeDynamicProperty(const char* prop) override; bool renameDynamicProperty(Property *prop, const char *name) override; App::Property* addDynamicProperty(const char* type, const char* name = nullptr, const char* group = nullptr, const char* doc = nullptr, short attr = 0, bool ro = false, bool hidden = false) override; /** Resolve the last document object referenced in the subname * * @param subname: dot separated subname * @param parent: return the direct parent of the object * @param childName: return child name to be passed to is/setElementVisible() * @param subElement: return non-object sub-element name if found. The * pointer is guaranteed to be within the buffer pointed to by 'subname' * * @sa getSubObject() * @return Returns the last referenced document object in the subname. If no * such object in subname, return pObject. */ App::DocumentObject* resolve(const char* subname, App::DocumentObject** parent = nullptr, std::string* childName = nullptr, const char** subElement = nullptr, PyObject** pyObj = nullptr, Base::Matrix4D* mat = nullptr, 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 offset * to be rid of 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 offset 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& inList, std::set* visited = nullptr); /** allow partial loading of dependent objects * * @return Returns 0 means do not support partial loading. 1 means allow * dependent objects to be partially loaded, i.e. only create, but not * restored. 2 means this object itself can be partially loaded. */ virtual int canLoadPartial() const { return 0; } virtual void onUpdateElementReference(const Property*) {} /** Allow object to redirect a subname path * * @param ss: input as the current subname path from \a topParent leading * just before this object, i.e. ends at the parent of this object. This * function should append its own name to this path, or redirect the * subname to other place. * @param topParent: top parent of this subname path * @param child: the immediate child object in the path * * This function is called by tree view to generate a subname path when an * item is selected in the tree. Document object can use this function to * redirect the selection to some other objects. */ virtual bool redirectSubName(std::ostringstream& ss, DocumentObject* topParent, DocumentObject* child) const; /** Special marker to mark the object as hidden * * It is used by Gui::ViewProvider::getElementColors(), but exposed here * for convenience */ static const std::string& hiddenMarker(); /// Check if the subname reference ends with hidden marker. static const char* hasHiddenMarker(const char* subname); /* Find the placement of a target object as seen from this. If no targetObj given, the last object found in the subname is used as target. */ virtual Base::Placement getPlacementOf(const std::string& sub, DocumentObject* targetObj = nullptr); /* Returns the Placement property value if any.*/ virtual Base::Placement getPlacement() const; /* Returns the Placement property to use if any*/ virtual App::PropertyPlacement* getPlacementProperty() const; protected: /// recompute only this object virtual App::DocumentObjectExecReturn* recompute(); /** get called by the document to recompute this feature * Normally this method get called in the processing of * Document::recompute(). * In execute() the output properties get recomputed * with the data from linked objects and objects own * properties. */ virtual App::DocumentObjectExecReturn* execute(); /** * Executes the extensions of a document object. */ App::DocumentObjectExecReturn* executeExtensions(); /** Status bits of the document object * The first 8 bits are used for the base system the rest can be used in * descendent classes to mark special statuses on the objects. * The bits and their meaning are listed below: * 0 - object is marked as 'touched' * 1 - object is marked as 'erroneous' * 2 - object is marked as 'new' * 3 - object is marked as 'recompute', i.e. the object gets recomputed now * 4 - object is marked as 'restoring', i.e. the object gets loaded at the moment * 5 - object is marked as 'deleting', i.e. the object gets deleted at the moment * 6 - reserved * 7 - reserved * 16 - object is marked as 'expanded' in the tree view */ std::bitset<32> StatusBits; void setError() { StatusBits.set(ObjectStatus::Error); } void resetError() { StatusBits.reset(ObjectStatus::Error); } void setDocument(App::Document* doc); /// get called before the value is changed void onBeforeChange(const Property* prop) override; /// get called by the container when a property was changed void onChanged(const Property* prop) override; /// get called by the container when a property was changed void onEarlyChange(const Property* prop) override; /// get called after a document has been fully restored virtual void onDocumentRestored(); /// get called after an object finishes restoreContent. void restoreFinished() override; /// get called after an undo/redo transaction is finished virtual void onUndoRedoFinished(); /// get called after setting the document virtual void onSettingDocument(); /// get called after a brand new object was created virtual void setupObject(); /// get called when object is going to be removed from the document virtual void unsetupObject(); /// get called when a property status has changed void onPropertyStatusChanged(const Property& prop, unsigned long oldStatus) override; private: void printInvalidLinks() const; /// python object of this class and all descendent protected: // attributes Py::SmartPtr PythonObject; /// pointer to the document this object belongs to App::Document* _pDoc {nullptr}; /// Old label; used for renaming expressions std::string oldLabel; private: // pointer to the document name string (for performance) const std::string* pcNameInDocument {nullptr}; // accessed by App::Document to record and restore the correct view provider type std::string _pcViewProviderName; // unique identifier (among a document) of this object. long _Id {0}; private: // Back pointer to all the fathers in a DAG of the document // this is used by the document (via friend) to have a effective DAG handling std::vector _inList; mutable std::vector _outList; mutable std::unordered_map _outListMap; mutable bool _outListCached = false; }; } // namespace App #endif // SRC_APP_DOCUMENTOBJECT_H_