diff --git a/src/Mod/Part/App/AttachExtension.cpp b/src/Mod/Part/App/AttachExtension.cpp
index 530fc748ad..910c8e592b 100644
--- a/src/Mod/Part/App/AttachExtension.cpp
+++ b/src/Mod/Part/App/AttachExtension.cpp
@@ -23,6 +23,7 @@
#include "PreCompiled.h"
#include
+#include
#include "AttachExtension.h"
#include "AttachExtensionPy.h"
@@ -33,6 +34,512 @@ using namespace Attacher;
EXTENSION_PROPERTY_SOURCE(Part::AttachExtension, App::DocumentObjectExtension)
+#ifdef FC_USE_TNP_FIX
+
+AttachExtension::AttachExtension()
+{
+ EXTENSION_ADD_PROPERTY_TYPE(AttacherType,
+ ("Attacher::AttachEngine3D"),
+ "Attachment",
+ (App::PropertyType)(App::Prop_None),
+ "Class name of attach engine object driving the attachment.");
+ this->AttacherType.setStatus(App::Property::Status::Hidden, true);
+
+ EXTENSION_ADD_PROPERTY_TYPE(
+ Support,
+ (nullptr, nullptr),
+ "Attachment",
+ (App::PropertyType)(App::Prop_Hidden),
+ "Support of the 2D geometry (Deprecated! Use AttachmentSupport instead");
+ Support.setScope(App::LinkScope::Global);
+
+ EXTENSION_ADD_PROPERTY_TYPE(AttachmentSupport,
+ (nullptr, nullptr),
+ "Attachment",
+ (App::PropertyType)(App::Prop_None),
+ "Support of the 2D geometry");
+ AttachmentSupport.setScope(App::LinkScope::Global);
+
+ EXTENSION_ADD_PROPERTY_TYPE(MapMode,
+ (mmDeactivated),
+ "Attachment",
+ App::Prop_None,
+ "Mode of attachment to other object");
+ MapMode.setEditorName("PartGui::PropertyEnumAttacherItem");
+ MapMode.setEnums(AttachEngine::eMapModeStrings);
+ // a rough test if mode string list in Attacher.cpp is in sync with eMapMode enum.
+ assert(MapMode.getEnumVector().size() == mmDummy_NumberOfModes);
+
+ EXTENSION_ADD_PROPERTY_TYPE(MapReversed,
+ (false),
+ "Attachment",
+ App::Prop_None,
+ "Reverse Z direction (flip sketch upside down)");
+
+ EXTENSION_ADD_PROPERTY_TYPE(MapPathParameter,
+ (0.0),
+ "Attachment",
+ App::Prop_None,
+ "Sets point of curve to map the sketch to. 0..1 = start..end");
+
+ EXTENSION_ADD_PROPERTY_TYPE(
+ AttachmentOffset,
+ (Base::Placement()),
+ "Attachment",
+ App::Prop_None,
+ "Extra placement to apply in addition to attachment (in local coordinates)");
+
+ _props.attacherType = &AttacherType;
+ _props.attachment = &AttachmentSupport;
+ _props.mapMode = &MapMode;
+ _props.mapReversed = &MapReversed;
+ _props.mapPathParameter = &MapPathParameter;
+
+ setAttacher(new AttachEngine3D); // default attacher
+ _baseProps.attacher.reset(new AttachEngine3D);
+
+ updatePropertyStatus(false);
+
+ initExtensionType(AttachExtension::getExtensionClassTypeId());
+}
+
+AttachExtension::~AttachExtension()
+{}
+
+template
+static inline bool getProp(bool force,
+ T*& prop,
+ Base::Type type,
+ App::PropertyContainer* owner,
+ const char* name,
+ const char* doc)
+{
+ prop = Base::freecad_dynamic_cast(owner->getDynamicPropertyByName(name));
+ if (prop || !force) {
+ return false;
+ }
+ prop = static_cast(owner->addDynamicProperty(type.getName(), name, "Attachment", doc));
+ if (!prop) {
+ FC_THROWM(Base::RuntimeError, "Failed to add property " << owner->getFullName() << name);
+ }
+ prop->setStatus(App::Property::Status::LockDynamic, true);
+ prop->setStatus(App::Property::Status::Hidden, true);
+ return true;
+}
+
+template
+static inline bool
+getProp(bool force, T*& prop, App::PropertyContainer* owner, const char* name, const char* doc)
+{
+ return getProp(force, prop, T::getClassTypeId(), owner, name, doc);
+}
+
+void AttachExtension::initBase(bool force)
+{
+ if (_baseProps.attacherType) {
+ return;
+ }
+ auto obj = getExtendedObject();
+
+ // Temporary holding the properties so that we only handle onChanged() event
+ // when all relevant properties are ready.
+ Properties props;
+
+ if (getProp(
+ force,
+ props.attacherType,
+ obj,
+ "BaseAttacherType",
+ "Class name of attach engine object driving the attachment for base geometry.")) {
+ props.attacherType->setValue(_baseProps.attacher->getTypeId().getName());
+ }
+ else if (!props.attacherType) {
+ return;
+ }
+
+ getProp(force,
+ props.attachment,
+ App::PropertyLinkSubListHidden::getClassTypeId(),
+ obj,
+ "BaseAttachment",
+ "Link to base geometry.");
+
+ if (getProp(force,
+ props.mapMode,
+ obj,
+ "BaseMapMode",
+ "Mode of attachment for the base geometry")) {
+ props.mapMode->setStatus(App::Property::Status::Hidden, false);
+ }
+ if (props.mapMode) {
+ props.mapMode->setEditorName("PartGui::PropertyEnumAttacherItem");
+ props.mapMode->setEnums(AttachEngine::eMapModeStrings);
+ }
+
+ getProp(force,
+ props.mapReversed,
+ obj,
+ "BaseMapReversed",
+ "Reverse Z direction of the base geometry attachment");
+
+ getProp(force,
+ props.mapPathParameter,
+ obj,
+ "BaseMapPathParameter",
+ "Sets point of base curve to map 0..1 = start..end");
+
+ static_cast(_baseProps) = props;
+}
+
+void AttachExtension::setAttacher(AttachEngine* pAttacher, bool base)
+{
+ auto& props = base ? _baseProps : _props;
+ props.attacher.reset(pAttacher);
+ if (props.attacher) {
+ if (base) {
+ initBase(true);
+ }
+ const char* typeName = props.attacher->getTypeId().getName();
+ if (strcmp(props.attacherType->getValue(), typeName)
+ != 0) { // make sure we need to change, to break recursive
+ // onChange->changeAttacherType->onChange...
+ props.attacherType->setValue(typeName);
+ }
+ updateAttacherVals(base);
+ }
+ else {
+ if (props.attacherType
+ && strlen(props.attacherType->getValue())
+ != 0) { // make sure we need to change, to break recursive
+ // onChange->changeAttacherType->onChange...
+ props.attacherType->setValue("");
+ }
+ }
+}
+
+bool AttachExtension::changeAttacherType(const char* typeName, bool base)
+{
+ auto& prop = base ? _baseProps : _props;
+
+ // check if we need to actually change anything
+ if (prop.attacher) {
+ if (strcmp(prop.attacher->getTypeId().getName(), typeName) == 0) {
+ return false;
+ }
+ }
+ else if (strlen(typeName) == 0) {
+ return false;
+ }
+ if (strlen(typeName) == 0) {
+ setAttacher(nullptr, base);
+ return true;
+ }
+ Base::Type t = Base::Type::fromName(typeName);
+ if (t.isDerivedFrom(AttachEngine::getClassTypeId())) {
+ AttachEngine* pNewAttacher =
+ static_cast(Base::Type::createInstanceByName(typeName));
+ this->setAttacher(pNewAttacher, base);
+ return true;
+ }
+
+ std::stringstream errMsg;
+ errMsg << "Object if this type is not derived from AttachEngine: " << typeName;
+ throw AttachEngineException(errMsg.str());
+}
+
+bool AttachExtension::positionBySupport()
+{
+ _active = 0;
+ if (!_props.attacher) {
+ throw Base::RuntimeError(
+ "AttachExtension: can't positionBySupport, because no AttachEngine is set.");
+ }
+ Base::Placement plaOriginal = getPlacement().getValue();
+ try {
+ if (_props.attacher->mapMode == mmDeactivated) {
+ return false;
+ }
+ bool subChanged = false;
+
+ getPlacement().setValue(Base::Placement());
+
+ Base::Placement basePlacement;
+ if (_baseProps.attacher && _baseProps.attacher->mapMode != mmDeactivated) {
+ basePlacement =
+ _baseProps.attacher->calculateAttachedPlacement(Base::Placement(), &subChanged);
+ if (subChanged) {
+ _baseProps.attachment->setValues(_baseProps.attachment->getValues(),
+ _baseProps.attacher->getSubValues());
+ }
+ }
+
+ subChanged = false;
+ _props.attacher->setOffset(AttachmentOffset.getValue() * basePlacement.inverse());
+ auto placement = _props.attacher->calculateAttachedPlacement(plaOriginal, &subChanged);
+ if (subChanged) {
+ Base::ObjectStatusLocker guard(
+ App::Property::User3,
+ &AttachmentSupport);
+ AttachmentSupport.setValues(AttachmentSupport.getValues(),
+ _props.attacher->getSubValues());
+ }
+ getPlacement().setValue(placement);
+ _active = 1;
+ return true;
+ }
+ catch (ExceptionCancel&) {
+ // disabled, don't do anything
+ getPlacement().setValue(plaOriginal);
+ return false;
+ }
+ catch (Base::Exception&) {
+ getPlacement().setValue(plaOriginal);
+ throw;
+ }
+ catch (Standard_Failure&) {
+ getPlacement().setValue(plaOriginal);
+ throw;
+ }
+}
+
+bool AttachExtension::isAttacherActive() const
+{
+ if (_active < 0) {
+ _active = 0;
+ try {
+ updateAttacherVals(/*base*/ false);
+ updateAttacherVals(/*base*/ true);
+ _props.attacher->calculateAttachedPlacement(getPlacement().getValue());
+ _active = 1;
+ }
+ catch (Base::Exception&) {
+ }
+ }
+ return _active != 0;
+}
+
+short int AttachExtension::extensionMustExecute()
+{
+ return DocumentObjectExtension::extensionMustExecute();
+}
+
+
+App::DocumentObjectExecReturn* AttachExtension::extensionExecute()
+{
+ if (this->isTouched_Mapping()) {
+ try {
+ positionBySupport();
+ // we let all Base::Exceptions thru, so that App:DocumentObject can take appropriate
+ // action
+ /*} catch (Base::Exception &e) {
+ return new App::DocumentObjectExecReturn(e.what());*/
+ // Convert OCC exceptions to Base::Exception
+ }
+ catch (Standard_Failure& e) {
+ throw Base::RuntimeError(e.GetMessageString());
+ // return new App::DocumentObjectExecReturn(e.GetMessageString());
+ }
+ }
+ return App::DocumentObjectExtension::extensionExecute();
+}
+void dumpAttacher(std::string name, App::PropertyLinkSubList& a)
+{
+ std::cerr << name << ": ";
+ for (auto& sh : a.getShadowSubs()) {
+ std::cerr << "(" << sh.first << " : " << sh.second << ")";
+ }
+ for (auto& v : a.getValues()) {
+ std::cerr << v->getNameInDocument();
+ }
+ for (auto& sv : a.getSubValues()) {
+ std::cerr << "[" << sv << "]";
+ }
+ std::cerr << std::endl;
+}
+
+
+void AttachExtension::extensionOnChanged(const App::Property* prop)
+{
+ if (!getExtendedObject()->isRestoring()) {
+ if (prop == &Support) {
+ if (!prop->testStatus(App::Property::User3)) {
+ Base::ObjectStatusLocker guard(
+ App::Property::User3,
+ &Support);
+ AttachmentSupport.Paste(Support);
+ }
+ }
+ else if (_props.matchProperty(prop)) {
+ if (prop == &AttachmentSupport) {
+ Base::ObjectStatusLocker guard(
+ App::Property::User3,
+ &Support);
+ Support.Paste(AttachmentSupport);
+ dumpAttacher("PasteSupport", AttachmentSupport);
+ }
+ _active = -1;
+ updateAttacherVals(/*base*/ false);
+ updatePropertyStatus(isAttacherActive());
+ }
+ else if (_baseProps.matchProperty(prop)) {
+ _active = -1;
+ updateAttacherVals(/*base*/ true);
+ updatePropertyStatus(isAttacherActive(), /*base*/ true);
+ }
+ }
+ if (prop == &(this->AttacherType)) {
+ this->changeAttacherType(this->AttacherType.getValue());
+ }
+ else if (prop == _baseProps.attacherType) {
+ this->changeAttacherType(_baseProps.attacherType->getValue());
+ }
+
+ App::DocumentObjectExtension::extensionOnChanged(prop);
+}
+
+void AttachExtension::extHandleChangedPropertyName(Base::XMLReader& reader,
+ const char* TypeName,
+ const char* PropName)
+{
+ // Was superPlacement
+ Base::Type type = Base::Type::fromName(TypeName);
+ if (AttachmentOffset.getClassTypeId() == type && strcmp(PropName, "superPlacement") == 0) {
+ AttachmentOffset.Restore(reader);
+ }
+}
+
+void AttachExtension::onExtendedDocumentRestored()
+{
+ try {
+ if (Support.getValue()) {
+ AttachmentSupport.Paste(Support);
+ }
+ initBase(false);
+ if (_baseProps.attachment) {
+ _baseProps.attachment->setScope(App::LinkScope::Hidden);
+ }
+ if (_baseProps.attacherType) {
+ changeAttacherType(_baseProps.attacherType->getValue(), true);
+ }
+ _active = -1;
+ updatePropertyStatus(isAttacherActive());
+ }
+ catch (Base::Exception&) {
+ }
+ catch (Standard_Failure&) {
+ }
+}
+
+void AttachExtension::updatePropertyStatus(bool bAttached, bool base)
+{
+ auto& props = base ? this->_baseProps : this->_props;
+ if (!props.mapMode) {
+ return;
+ }
+
+ // Hide properties when not applicable to reduce user confusion
+ eMapMode mmode = eMapMode(props.mapMode->getValue());
+ bool modeIsPointOnCurve =
+ (mmode == mmNormalToPath || mmode == mmFrenetNB || mmode == mmFrenetTN
+ || mmode == mmFrenetTB || mmode == mmRevolutionSection || mmode == mmConcentric);
+
+ // MapPathParameter is only used if there is a reference to one edge and not edge + vertex
+ bool hasOneRef = false;
+ if (props.attacher && props.attacher->subnames.size() == 1) {
+ hasOneRef = true;
+ }
+ props.mapPathParameter->setStatus(App::Property::Status::Hidden,
+ !bAttached || !(modeIsPointOnCurve && hasOneRef));
+ props.mapReversed->setStatus(App::Property::Status::Hidden, !bAttached);
+
+ if (base) {
+ props.attachment->setStatus(App::Property::Status::Hidden, !bAttached);
+ }
+ else {
+ this->AttachmentOffset.setStatus(App::Property::Status::Hidden, !bAttached);
+ if (getExtendedContainer()) {
+ getPlacement().setReadOnly(
+ bAttached && mmode != mmTranslate); // for mmTranslate, orientation should remain
+ // editable even when attached.
+ }
+ updatePropertyStatus(bAttached, true);
+ }
+}
+
+void AttachExtension::updateAttacherVals(bool base) const
+{
+ auto& props = base ? this->_baseProps : this->_props;
+ if (!props.attachment) {
+ return;
+ }
+ dumpAttacher("updateAttacherVals", *props.attachment);
+ attacher(base).setUp(*props.attachment,
+ eMapMode(props.mapMode->getValue()),
+ props.mapReversed->getValue(),
+ props.mapPathParameter->getValue(),
+ 0.0,
+ 0.0);
+}
+
+AttachExtension::Properties AttachExtension::getProperties(bool base) const
+{
+ return base ? _baseProps : _props;
+}
+
+AttachExtension::Properties AttachExtension::getInitedProperties(bool base)
+{
+ if (base) {
+ initBase(true);
+ return _baseProps;
+ }
+ return _props;
+}
+
+App::PropertyPlacement& AttachExtension::getPlacement() const
+{
+ auto pla = Base::freecad_dynamic_cast(
+ getExtendedObject()->getPropertyByName("Placement"));
+ if (!pla) {
+ throw Base::RuntimeError("AttachExtension cannot find placement property");
+ }
+ return *pla;
+}
+
+PyObject* AttachExtension::getExtensionPyObject()
+{
+
+ if (ExtensionPythonObject.is(Py::_None())) {
+ // ref counter is set to 1
+ ExtensionPythonObject = Py::Object(new AttachExtensionPy(this), true);
+ }
+ return Py::new_reference_to(ExtensionPythonObject);
+}
+
+
+Attacher::AttachEngine& AttachExtension::attacher(bool base) const
+{
+ auto& props = base ? _baseProps : _props;
+ if (!props.attacher) {
+ throw AttachEngineException("AttachableObject: no attacher is set.");
+ }
+ return *props.attacher;
+}
+
+// ------------------------------------------------
+
+AttachEngineException::AttachEngineException()
+ : Base::Exception()
+{}
+
+AttachEngineException::AttachEngineException(const char* sMessage)
+ : Base::Exception(sMessage)
+{}
+
+AttachEngineException::AttachEngineException(const std::string& sMessage)
+ : Base::Exception(sMessage)
+{}
+
+#else
AttachExtension::AttachExtension()
{
EXTENSION_ADD_PROPERTY_TYPE(AttacherType, ("Attacher::AttachEngine3D"), "Attachment",(App::PropertyType)(App::Prop_None),"Class name of attach engine object driving the attachment.");
@@ -320,7 +827,7 @@ AttachEngineException::AttachEngineException(const std::string& sMessage)
: Base::Exception(sMessage)
{
}
-
+#endif
namespace App {
/// @cond DOXERR
diff --git a/src/Mod/Part/App/AttachExtension.h b/src/Mod/Part/App/AttachExtension.h
index 46e6fbc96f..e8f9df2348 100644
--- a/src/Mod/Part/App/AttachExtension.h
+++ b/src/Mod/Part/App/AttachExtension.h
@@ -40,7 +40,130 @@
namespace Part
{
+#ifdef FC_USE_TNP_FIX
+class PartExport AttachEngineException: public Base::Exception
+{
+public:
+ /// Construction
+ AttachEngineException();
+ explicit AttachEngineException(const char* sMessage);
+ explicit AttachEngineException(const std::string& sMessage);
+ /// Destruction
+ ~AttachEngineException() throw() override
+ {}
+};
+
+/**
+ * @brief The AttachableObject class is the thing to extend an object with
+ * that should be attachable. It includes the required properties, and
+ * shortcuts for accessing the attachment math class.
+ */
+class PartExport AttachExtension: public App::DocumentObjectExtension
+{
+ EXTENSION_PROPERTY_HEADER_WITH_OVERRIDE(Part::AttachableObject);
+
+public:
+ AttachExtension();
+ ~AttachExtension() override;
+
+ /**
+ * @brief setAttacher sets the AttachEngine object. The class takes the
+ * ownership of the pointer, it will be deleted when the class is
+ * destroyed, or when a new attacher is set. The default attacher is AttachEngine3D.
+ * @param attacher. AttachableObject takes ownership and will delete it eventually.
+ */
+ virtual void setAttacher(Attacher::AttachEngine* attacher, bool base = false);
+
+ /**
+ * @brief changeAttacherType
+ * @param typeName is the typename of new attacher class. Must be derived
+ * from Attacher::AttachEngine.
+ * @return true if attacher was changed. false if attacher is already of the
+ * type requested. Throws if invalid type is supplied.
+ */
+ bool changeAttacherType(const char* typeName, bool base = false);
+
+ Attacher::AttachEngine& attacher(bool base = false) const;
+
+ App::PropertyString AttacherType;
+ App::PropertyLinkSubList Support; // deprecated, leave here for backward compatibility
+ App::PropertyLinkSubList AttachmentSupport;
+ App::PropertyEnumeration MapMode; // see AttachEngine::eMapMode
+ App::PropertyBool MapReversed; // inverts Z and X internal axes
+ App::PropertyPlacement AttachmentOffset;
+
+ /**
+ * @brief MapPathParameter is a parameter value for mmNormalToPath (the
+ * sketch will be mapped normal to a curve at point specified by parameter
+ * (from 0.0 to 1.0, from start to end) )
+ */
+ App::PropertyFloat MapPathParameter;
+
+ /** calculate and update the Placement property based on the Support, and
+ * mode. Can throw FreeCAD and OCC exceptions. Returns true if attached,
+ * false if not, throws if attachment failed.
+ */
+ virtual bool positionBySupport();
+
+ /** Return whether this attacher is active
+ */
+ bool isAttacherActive() const;
+
+ virtual bool isTouched_Mapping()
+ {
+ return true; /*support.isTouched isn't true when linked objects are changed... why?..*/
+ }
+
+ short int extensionMustExecute() override;
+ App::DocumentObjectExecReturn* extensionExecute() override;
+ PyObject* getExtensionPyObject() override;
+ void onExtendedDocumentRestored() override;
+
+ struct Properties
+ {
+ App::PropertyString* attacherType = nullptr;
+ App::PropertyLinkSubList* attachment = nullptr;
+ App::PropertyEnumeration* mapMode = nullptr;
+ App::PropertyBool* mapReversed = nullptr;
+ App::PropertyFloat* mapPathParameter = nullptr;
+ bool matchProperty(const App::Property* prop) const
+ {
+ return prop == attachment || prop == mapMode || prop == mapReversed
+ || prop == mapPathParameter;
+ }
+ };
+ Properties getProperties(bool base) const;
+ Properties getInitedProperties(bool base);
+
+protected:
+ void extensionOnChanged(const App::Property* /*prop*/) override;
+ virtual void extHandleChangedPropertyName(Base::XMLReader& reader,
+ const char* TypeName,
+ const char* PropName);
+
+ App::PropertyPlacement& getPlacement() const;
+ void initBase(bool force);
+
+public:
+ void updateAttacherVals(bool base = false) const;
+ void updatePropertyStatus(bool attached, bool base = false);
+
+private:
+ struct _Properties: Properties
+ {
+ mutable std::unique_ptr attacher;
+ };
+ _Properties _props;
+ _Properties _baseProps;
+
+ mutable int _active = -1;
+};
+
+
+using AttachExtensionPython = App::ExtensionPythonT;
+
+#else
class PartExport AttachEngineException : public Base::Exception
{
public:
@@ -131,7 +254,7 @@ private:
using AttachExtensionPython = App::ExtensionPythonT;
-
+#endif
} // namespace Part
#endif // PARTATTACHABLEOBJECT_H
diff --git a/src/Mod/Part/App/Attacher.cpp b/src/Mod/Part/App/Attacher.cpp
index 1eafeca8b2..cce95345fa 100644
--- a/src/Mod/Part/App/Attacher.cpp
+++ b/src/Mod/Part/App/Attacher.cpp
@@ -257,6 +257,11 @@ void AttachEngine::setUp(const AttachEngine &another)
this->attachmentOffset = another.attachmentOffset;
}
+void AttachEngine::setOffset(const Base::Placement &offset)
+{
+ this->attachmentOffset = offset;
+}
+
Base::Placement AttachEngine::placementFactory(const gp_Dir &ZAxis,
gp_Vec XAxis,
gp_Pnt Origin,
diff --git a/src/Mod/Part/App/Attacher.h b/src/Mod/Part/App/Attacher.h
index da086becbd..2e395901be 100644
--- a/src/Mod/Part/App/Attacher.h
+++ b/src/Mod/Part/App/Attacher.h
@@ -223,6 +223,9 @@ public: //methods
double surfU = 0.0, double surfV = 0.0,
const Base::Placement &attachmentOffset = Base::Placement());
virtual void setUp(const AttachEngine &another);
+
+ void setOffset(const Base::Placement &offset);
+
virtual AttachEngine* copy() const = 0;
Base::Placement calculateAttachedPlacement(
diff --git a/src/Mod/Part/App/PartFeature.cpp b/src/Mod/Part/App/PartFeature.cpp
index 99341d5622..aae5563c10 100644
--- a/src/Mod/Part/App/PartFeature.cpp
+++ b/src/Mod/Part/App/PartFeature.cpp
@@ -72,6 +72,7 @@
#include "PartFeaturePy.h"
#include "PartPyCXX.h"
#include "TopoShapePy.h"
+#include "Base/Tools.h"
using namespace Part;
namespace sp = std::placeholders;
@@ -872,6 +873,7 @@ static TopoShape _getTopoShape(const App::DocumentObject* obj,
std::set nextHiddens = hiddens;
const App::DocumentObject* nextLink = lastLink;
+ // Todo: This might belong.
// Toponaming project March 2024: This appears to be a non toponaming feature:
// if (!checkLinkVisibility(nextHiddens, true, nextLink, owner, sub.c_str())) {
// cacheable = false;
@@ -967,6 +969,7 @@ TopoShape Feature::getTopoShape(const App::DocumentObject* obj,
const App::DocumentObject* lastLink = 0;
std::set hiddens;
// Toponaming project March 2024: This appears to be a non toponaming feature:
+ // Todo is this a cause behind #13886 ?
// if (!checkLinkVisibility(hiddens, false, lastLink, obj, subname)) {
// return TopoShape();
// }
@@ -1068,11 +1071,132 @@ App::DocumentObject *Feature::getShapeOwner(const App::DocumentObject *obj, cons
return owner;
}
+struct Feature::ElementCache
+{
+ TopoShape shape;
+ mutable std::vector names;
+ mutable bool searched;
+};
+
+void Feature::registerElementCache(const std::string& prefix, PropertyPartShape* prop)
+{
+ if (prop) {
+ _elementCachePrefixMap.emplace_back(prefix, prop);
+ return;
+ }
+ for (auto it = _elementCachePrefixMap.begin(); it != _elementCachePrefixMap.end();) {
+ if (it->first == prefix) {
+ _elementCachePrefixMap.erase(it);
+ break;
+ }
+ }
+}
+
+void Feature::onBeforeChange(const App::Property* prop)
+{
+ PropertyPartShape* propShape = nullptr;
+ const std::string* prefix = nullptr;
+ if (prop == &Shape) {
+ propShape = &Shape;
+ }
+ else {
+ for (const auto& v : _elementCachePrefixMap) {
+ if (prop == v.second) {
+ prefix = &v.first;
+ propShape = v.second;
+ }
+ }
+ }
+ if (propShape) {
+ if (_elementCachePrefixMap.empty()) {
+ _elementCache.clear();
+ }
+ else {
+ for (auto it = _elementCache.begin(); it != _elementCache.end();) {
+ bool remove;
+ if (prefix) {
+ remove = boost::starts_with(it->first, *prefix);
+ }
+ else {
+ remove = true;
+ for (const auto& v : _elementCache) {
+ if (boost::starts_with(it->first, v.first)) {
+ remove = false;
+ break;
+ }
+ }
+ }
+ if (remove) {
+ it = _elementCache.erase(it);
+ }
+ else {
+ ++it;
+ }
+ }
+ }
+ if (getDocument() && !getDocument()->testStatus(App::Document::Restoring)
+ && !getDocument()->isPerformingTransaction()) {
+ std::vector objs;
+ std::vector subs;
+ for (auto prop : App::PropertyLinkBase::getElementReferences(this)) {
+ if (!prop->getContainer()) {
+ continue;
+ }
+ objs.clear();
+ subs.clear();
+ prop->getLinks(objs, true, &subs, false);
+ for (auto& sub : subs) {
+ auto element = Data::findElementName(sub.c_str());
+ if (!element || !element[0] || Data::hasMissingElement(element)) {
+ continue;
+ }
+ if (prefix) {
+ if (!boost::starts_with(element, *prefix)) {
+ continue;
+ }
+ }
+ else {
+ bool found = false;
+ for (const auto& v : _elementCachePrefixMap) {
+ if (boost::starts_with(element, v.first)) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ continue;
+ }
+ }
+ auto res =
+ _elementCache.insert(std::make_pair(std::string(element), ElementCache()));
+ if (res.second) {
+ res.first->second.searched = false;
+ res.first->second.shape = propShape->getShape().getSubTopoShape(
+ element + (prefix ? prefix->size() : 0),
+ true);
+ }
+ }
+ }
+ }
+ }
+ GeoFeature::onBeforeChange(prop);
+}
+
void Feature::onChanged(const App::Property* prop)
{
// if the placement has changed apply the change to the point data as well
if (prop == &this->Placement) {
+#ifdef FC_USE_TNP_FIX
+ TopoShape shape = this->Shape.getShape();
+ shape.setTransform(this->Placement.getValue().toMatrix());
+ Base::ObjectStatusLocker guard(
+ App::Property::NoRecompute,
+ &this->Shape);
+ this->Shape.setValue(shape);
+
+#else
this->Shape.setTransform(this->Placement.getValue().toMatrix());
+#endif
}
// if the point data has changed check and adjust the transformation as well
else if (prop == &this->Shape) {
@@ -1085,8 +1209,9 @@ void Feature::onChanged(const App::Property* prop)
if (!this->Shape.getValue().IsNull()) {
try {
p.fromMatrix(this->Shape.getShape().getTransform());
- if (p != this->Placement.getValue())
+ if (p != this->Placement.getValue()) {
this->Placement.setValue(p);
+ }
}
catch (const Base::ValueError&) {
}
@@ -1097,6 +1222,51 @@ void Feature::onChanged(const App::Property* prop)
GeoFeature::onChanged(prop);
}
+#ifdef FC_USE_TNP_FIX
+
+const std::vector& Feature::searchElementCache(const std::string& element,
+ Data::SearchOptions options,
+ double tol,
+ double atol) const
+{
+ static std::vector none;
+ if (element.empty()) {
+ return none;
+ }
+ auto it = _elementCache.find(element);
+ if (it == _elementCache.end() || it->second.shape.isNull()) {
+ return none;
+ }
+ if (!it->second.searched) {
+ auto propShape = &Shape;
+ const std::string* prefix = nullptr;
+ for (const auto& v : _elementCachePrefixMap) {
+ if (boost::starts_with(element, v.first)) {
+ propShape = v.second;
+ prefix = &v.first;
+ break;
+ }
+ }
+ it->second.searched = true;
+ propShape->getShape().findSubShapesWithSharedVertex(it->second.shape,
+ &it->second.names,
+ static_cast(options),
+ tol,
+ atol);
+ if (prefix) {
+ for (auto& name : it->second.names) {
+ if (auto dot = strrchr(name.c_str(), '.')) {
+ name.insert(dot + 1 - name.c_str(), *prefix);
+ }
+ else {
+ name.insert(0, *prefix);
+ }
+ }
+ }
+ }
+ return it->second.names;
+}
+#endif
TopLoc_Location Feature::getLocation() const
{
Base::Placement pl = this->Placement.getValue();
@@ -1702,6 +1872,5 @@ std::pair Feature::getElementName(const char* name,
}
}
}
-
- return App::GeoFeature::getElementName(name, type);
+ return App::GeoFeature::_getElementName(name, mapped);
}
diff --git a/src/Mod/Part/App/PartFeature.h b/src/Mod/Part/App/PartFeature.h
index e3259089e0..6a1af4d48a 100644
--- a/src/Mod/Part/App/PartFeature.h
+++ b/src/Mod/Part/App/PartFeature.h
@@ -152,13 +152,23 @@ public:
create(const TopoShape& shape, const char* name = nullptr, App::Document* document = nullptr);
static bool isElementMappingDisabled(App::PropertyContainer *container);
+#ifdef FC_USE_TNP_FIX
+ const std::vector& searchElementCache(const std::string &element,
+ Data::SearchOptions options = Data::SearchOptions::CheckGeometry,
+ double tol = 1e-7,
+ double atol = 1e-10) const override;
+#endif
protected:
/// recompute only this object
App::DocumentObjectExecReturn *recompute() override;
/// recalculate the feature
App::DocumentObjectExecReturn *execute() override;
+ void onBeforeChange(const App::Property* prop) override;
void onChanged(const App::Property* prop) override;
+
+ void registerElementCache(const std::string &prefix, PropertyPartShape *prop);
+
/**
* Build a history of changes
* MakeShape: The operation that created the changes, e.g. BRepAlgoAPI_Common
@@ -169,6 +179,10 @@ protected:
ShapeHistory buildHistory(BRepBuilderAPI_MakeShape&, TopAbs_ShapeEnum type,
const TopoDS_Shape& newS, const TopoDS_Shape& oldS);
ShapeHistory joinHistory(const ShapeHistory&, const ShapeHistory&);
+private:
+ struct ElementCache;
+ std::map _elementCache;
+ std::vector> _elementCachePrefixMap;
};
class FilletBase : public Part::Feature
diff --git a/tests/src/Mod/Sketcher/App/SketchObject.cpp b/tests/src/Mod/Sketcher/App/SketchObject.cpp
index 8875fbf896..92d396452c 100644
--- a/tests/src/Mod/Sketcher/App/SketchObject.cpp
+++ b/tests/src/Mod/Sketcher/App/SketchObject.cpp
@@ -281,7 +281,7 @@ TEST_F(SketchObjectTest, testGetElementName)
EXPECT_STREQ(forward_normal_name.second.c_str(), "g39;SKT");
EXPECT_STREQ(reverse_normal_name.first.c_str(), "");
EXPECT_STREQ(reverse_normal_name.second.c_str(), "Vertex2");
- EXPECT_STREQ(reverse_export_name.first.c_str(), "");
+ EXPECT_STREQ(reverse_export_name.first.c_str(), ";g39v1;SKT.Vertex1");
EXPECT_STREQ(reverse_export_name.second.c_str(), "Vertex1");
#else
EXPECT_STREQ(forward_normal_name.first.c_str(), ";g39;SKT.Edge1");