Core: Add getPlacementOf replacing previous getGlobalPlacement logic. (#26059)
* Core: Add getPlacementOf replacing previous getGlobalPlacement logic. * Update src/App/DocumentObject.cpp Co-authored-by: Kacper Donat <[email protected]> * Update DocumentObject.cpp * Fix error when called from python with targetObj == None --------- Co-authored-by: Kacper Donat <[email protected]>
This commit is contained in:
co-authored by
Kacper Donat
parent
c12ca7b3ba
commit
7a8135d863
@@ -30,6 +30,7 @@
|
||||
|
||||
#include <Base/Console.h>
|
||||
#include <Base/Matrix.h>
|
||||
#include <Base/Placement.h>
|
||||
#include <Base/Tools.h>
|
||||
#include <Base/Writer.h>
|
||||
|
||||
@@ -1615,3 +1616,31 @@ void DocumentObject::onPropertyStatusChanged(const Property& prop, unsigned long
|
||||
getDocument()->signalChangePropertyEditor(*getDocument(), prop);
|
||||
}
|
||||
}
|
||||
|
||||
Base::Placement DocumentObject::getPlacementOf(const std::string& sub, DocumentObject* targetObj)
|
||||
{
|
||||
Base::Placement plc;
|
||||
auto* propPlacement = freecad_cast<App::PropertyPlacement*>(getPropertyByName("Placement"));
|
||||
if (propPlacement) {
|
||||
// If the object has no placement (like a Group), plc stays identity so we can proceed.
|
||||
plc = propPlacement->getValue();
|
||||
}
|
||||
|
||||
std::vector<std::string> names = Base::Tools::splitSubName(sub);
|
||||
|
||||
if (names.empty() || this == targetObj) {
|
||||
return plc;
|
||||
}
|
||||
|
||||
DocumentObject* subObj = getDocument()->getObject(names.front().c_str());
|
||||
|
||||
if (!subObj) {
|
||||
return plc;
|
||||
}
|
||||
|
||||
std::vector<std::string> newNames(names.begin() + 1, names.end());
|
||||
std::string newSub = Base::Tools::joinList(newNames, ".");
|
||||
|
||||
return plc * subObj->getPlacementOf(newSub, targetObj);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <App/PropertyLinks.h>
|
||||
#include <App/PropertyStandard.h>
|
||||
#include <Base/SmartPtrPy.h>
|
||||
#include <Base/Placement.h>
|
||||
|
||||
#include <bitset>
|
||||
#include <unordered_map>
|
||||
@@ -708,6 +709,11 @@ public:
|
||||
/// 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);
|
||||
|
||||
protected:
|
||||
/// recompute only this object
|
||||
virtual App::DocumentObjectExecReturn* recompute();
|
||||
|
||||
@@ -325,3 +325,10 @@ class DocumentObject(ExtensionContainer):
|
||||
Return true if the object is part of a document, false otherwise.
|
||||
"""
|
||||
...
|
||||
|
||||
def getPlacementOf(self, subname: str, target: DocumentObject = None, /) -> Any:
|
||||
"""
|
||||
Return the placement of the sub-object relative to the link object.
|
||||
getPlacementOf(subname, [targetObj]) -> Base.Placement
|
||||
"""
|
||||
...
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <Base/GeometryPyCXX.h>
|
||||
#include <Base/MatrixPy.h>
|
||||
#include <Base/PlacementPy.h>
|
||||
#include <Base/PyWrapParseTupleAndKeywords.h>
|
||||
|
||||
#include "DocumentObject.h"
|
||||
@@ -1003,3 +1004,32 @@ void DocumentObjectPy::setNoTouch(Py::Boolean value)
|
||||
{
|
||||
getDocumentObjectPtr()->setStatus(ObjectStatus::NoTouch, value.isTrue());
|
||||
}
|
||||
|
||||
PyObject* DocumentObjectPy::getPlacementOf(PyObject* args)
|
||||
{
|
||||
char* subname;
|
||||
PyObject* target = Py_None; // Initialize to None
|
||||
|
||||
if (!PyArg_ParseTuple(args, "s|O", &subname, &target)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
App::DocumentObject* targetObj = nullptr;
|
||||
|
||||
// Check if a target was provided and is not None
|
||||
if (target && target != Py_None) {
|
||||
// Now perform the type check manually
|
||||
if (!PyObject_TypeCheck(target, &DocumentObjectPy::Type)) {
|
||||
PyErr_SetString(PyExc_TypeError, "Target argument must be a DocumentObject or None");
|
||||
return nullptr;
|
||||
}
|
||||
targetObj = static_cast<DocumentObjectPy*>(target)->getDocumentObjectPtr();
|
||||
}
|
||||
|
||||
PY_TRY
|
||||
{
|
||||
Base::Placement p = getDocumentObjectPtr()->getPlacementOf(subname, targetObj);
|
||||
return new Base::PlacementPy(new Base::Placement(p));
|
||||
}
|
||||
PY_CATCH
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <App/DocumentObjectPy.h>
|
||||
#include <Base/Interpreter.h>
|
||||
#include <Base/MatrixPy.h>
|
||||
#include <Base/PlacementPy.h>
|
||||
#include <Base/Tools.h>
|
||||
|
||||
#include "FeaturePython.h"
|
||||
@@ -632,6 +633,103 @@ bool FeaturePythonImp::editProperty(const char* name)
|
||||
return false;
|
||||
}
|
||||
|
||||
FeaturePythonImp::ValueT FeaturePythonImp::isLink() const
|
||||
{
|
||||
_FC_PY_CALL_CHECK(isLink, return (NotImplemented));
|
||||
Base::PyGILStateLocker lock;
|
||||
try {
|
||||
Py::Tuple args(1);
|
||||
args.setItem(0, Py::Object(object->getPyObject(), true));
|
||||
Py::Boolean ok(Base::pyCall(py_isLink.ptr(), args.ptr()));
|
||||
return ok ? Accepted : Rejected;
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
if (PyErr_ExceptionMatches(PyExc_NotImplementedError)) {
|
||||
PyErr_Clear();
|
||||
return NotImplemented;
|
||||
}
|
||||
|
||||
Base::PyException e; // extract the Python error text
|
||||
e.reportException();
|
||||
return Rejected;
|
||||
}
|
||||
}
|
||||
|
||||
FeaturePythonImp::ValueT FeaturePythonImp::isLinkGroup() const
|
||||
{
|
||||
_FC_PY_CALL_CHECK(isLinkGroup, return (NotImplemented));
|
||||
Base::PyGILStateLocker lock;
|
||||
try {
|
||||
Py::Tuple args(1);
|
||||
args.setItem(0, Py::Object(object->getPyObject(), true));
|
||||
Py::Boolean ok(Base::pyCall(py_isLinkGroup.ptr(), args.ptr()));
|
||||
return ok ? Accepted : Rejected;
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
if (PyErr_ExceptionMatches(PyExc_NotImplementedError)) {
|
||||
PyErr_Clear();
|
||||
return NotImplemented;
|
||||
}
|
||||
|
||||
Base::PyException e; // extract the Python error text
|
||||
e.reportException();
|
||||
return Rejected;
|
||||
}
|
||||
}
|
||||
|
||||
bool FeaturePythonImp::getPlacementOf(
|
||||
Base::Placement& ret,
|
||||
const char* subname,
|
||||
App::DocumentObject* target
|
||||
) const
|
||||
{
|
||||
FC_PY_CALL_CHECK(getPlacementOf); // Standard macro check
|
||||
Base::PyGILStateLocker lock;
|
||||
try {
|
||||
Py::Tuple args(3);
|
||||
|
||||
// Arg 0: The object itself
|
||||
args.setItem(0, Py::Object(object->getPyObject(), true));
|
||||
|
||||
// Arg 1: The subname string
|
||||
args.setItem(1, Py::String(subname ? subname : ""));
|
||||
|
||||
// Arg 2: The target object (or None)
|
||||
if (target) {
|
||||
args.setItem(2, Py::Object(target->getPyObject(), true));
|
||||
}
|
||||
else {
|
||||
args.setItem(2, Py::None());
|
||||
}
|
||||
|
||||
// Call the Python method
|
||||
Py::Object res(Base::pyCall(py_getPlacementOf.ptr(), args.ptr()));
|
||||
|
||||
// Check if Python returned None (implies "use base implementation")
|
||||
if (res.isNone()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check return type
|
||||
if (!PyObject_TypeCheck(res.ptr(), &Base::PlacementPy::Type)) {
|
||||
throw Py::TypeError("getPlacementOf expects a Base.Placement object return");
|
||||
}
|
||||
|
||||
// Convert Python object back to C++ Placement
|
||||
ret = *static_cast<Base::PlacementPy*>(res.ptr())->getPlacementPtr();
|
||||
return true;
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
if (PyErr_ExceptionMatches(PyExc_NotImplementedError)) {
|
||||
PyErr_Clear();
|
||||
return false;
|
||||
}
|
||||
Base::PyException e;
|
||||
e.reportException();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
|
||||
namespace App
|
||||
|
||||
+44
-1
@@ -91,6 +91,11 @@ public:
|
||||
|
||||
bool editProperty(const char* propName);
|
||||
|
||||
ValueT isLink() const;
|
||||
ValueT isLinkGroup() const;
|
||||
|
||||
bool getPlacementOf(Base::Placement& ret, const char* subname, App::DocumentObject* target) const;
|
||||
|
||||
private:
|
||||
App::DocumentObject* object;
|
||||
bool has__object__ {false};
|
||||
@@ -114,7 +119,10 @@ private:
|
||||
FC_PY_ELEMENT(hasChildElement) \
|
||||
FC_PY_ELEMENT(isElementVisible) \
|
||||
FC_PY_ELEMENT(setElementVisible) \
|
||||
FC_PY_ELEMENT(editProperty)
|
||||
FC_PY_ELEMENT(editProperty) \
|
||||
FC_PY_ELEMENT(isLink) \
|
||||
FC_PY_ELEMENT(isLinkGroup) \
|
||||
FC_PY_ELEMENT(getPlacementOf)
|
||||
|
||||
#define FC_PY_ELEMENT_DEFINE(_name) Py::Object py_##_name;
|
||||
|
||||
@@ -349,6 +357,41 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
bool isLink() const override
|
||||
{
|
||||
switch (imp->isLink()) {
|
||||
case FeaturePythonImp::Accepted:
|
||||
return true;
|
||||
case FeaturePythonImp::Rejected:
|
||||
return false;
|
||||
default:
|
||||
return FeatureT::isLink();
|
||||
}
|
||||
}
|
||||
|
||||
bool isLinkGroup() const override
|
||||
{
|
||||
switch (imp->isLinkGroup()) {
|
||||
case FeaturePythonImp::Accepted:
|
||||
return true;
|
||||
case FeaturePythonImp::Rejected:
|
||||
return false;
|
||||
default:
|
||||
return FeatureT::isLinkGroup();
|
||||
}
|
||||
}
|
||||
|
||||
Base::Placement getPlacementOf(const std::string& sub, DocumentObject* targetObj = nullptr) override
|
||||
{
|
||||
Base::Placement ret;
|
||||
// Try to call the python implementation first
|
||||
if (imp->getPlacementOf(ret, sub.c_str(), targetObj)) {
|
||||
return ret;
|
||||
}
|
||||
// Fallback to C++ implementation
|
||||
return FeatureT::getPlacementOf(sub, targetObj);
|
||||
}
|
||||
|
||||
PyObject* getPyObject() override
|
||||
{
|
||||
if (FeatureT::PythonObject.is(Py::_None())) {
|
||||
|
||||
+2
-33
@@ -316,42 +316,11 @@ Base::Placement GeoFeature::getGlobalPlacement(App::DocumentObject* targetObj,
|
||||
App::DocumentObject* rootObj,
|
||||
const std::string& sub)
|
||||
{
|
||||
if (!targetObj || !rootObj) {
|
||||
if (!rootObj) {
|
||||
return Base::Placement();
|
||||
}
|
||||
std::vector<std::string> names = Base::Tools::splitSubName(sub);
|
||||
|
||||
App::Document* doc = rootObj->getDocument();
|
||||
Base::Placement plc = getPlacementFromProp(rootObj, "Placement");
|
||||
|
||||
if (targetObj == rootObj) {
|
||||
return plc;
|
||||
}
|
||||
|
||||
if (rootObj->isLink()) {
|
||||
// Update doc in case its an external link.
|
||||
doc = rootObj->getLinkedObject()->getDocument();
|
||||
}
|
||||
|
||||
for (auto& name : names) {
|
||||
App::DocumentObject* obj = doc->getObject(name.c_str());
|
||||
if (!obj) {
|
||||
return Base::Placement();
|
||||
}
|
||||
|
||||
plc = plc * getPlacementFromProp(obj, "Placement");
|
||||
|
||||
if (obj == targetObj) {
|
||||
return plc;
|
||||
}
|
||||
if (obj->isLink()) {
|
||||
// Update doc in case its an external link.
|
||||
doc = obj->getLinkedObject()->getDocument();
|
||||
}
|
||||
}
|
||||
|
||||
// If targetObj has not been found there's a problem
|
||||
return Base::Placement();
|
||||
return rootObj->getPlacementOf(sub, targetObj);
|
||||
}
|
||||
|
||||
Base::Placement GeoFeature::getGlobalPlacement(App::DocumentObject* targetObj,
|
||||
|
||||
@@ -2635,6 +2635,76 @@ bool Link::canLinkProperties() const
|
||||
return true;
|
||||
}
|
||||
|
||||
Base::Placement Link::getPlacementOf(const std::string& sub, DocumentObject* targetObj)
|
||||
{
|
||||
if (isLinkGroup() && _getShowElementValue()) {
|
||||
// In this case we have child document objects, the subname can be used.
|
||||
// and in this case the Link itself acts as a normal DocumentObject
|
||||
return DocumentObject::getPlacementOf(sub, targetObj);
|
||||
}
|
||||
|
||||
Base::Placement plc;
|
||||
auto* propPlacement = dynamic_cast<App::PropertyPlacement*>(getPropertyByName("Placement"));
|
||||
if (!propPlacement) {
|
||||
return plc;
|
||||
}
|
||||
plc = propPlacement->getValue();
|
||||
|
||||
std::vector<std::string> names = Base::Tools::splitSubName(sub);
|
||||
|
||||
if (names.empty() || this == targetObj || !getLinkedObject()) {
|
||||
return plc;
|
||||
}
|
||||
|
||||
Document* doc = getLinkedObject()->getDocument();
|
||||
|
||||
if (isLinkGroup()) {
|
||||
// !ShowElement, so there are no objects.
|
||||
// The subname looks like : '1.pad.face3' or '1.face3'
|
||||
// So names.front() is supposed to be an integer. If not something is wrong.
|
||||
int i = -1;
|
||||
try {
|
||||
i = std::stoi(names.front());
|
||||
}
|
||||
catch (...) { // Conversion failed (not an integer)
|
||||
return plc;
|
||||
}
|
||||
|
||||
std::vector<Base::Placement> plcs = PlacementList.getValues();
|
||||
if (plcs.size() <= i) {
|
||||
return plc;
|
||||
}
|
||||
plc = plc * plcs[i];
|
||||
|
||||
// now that we handled the non-existing LinkElement, we can remove this subname and continue.
|
||||
if (names.size() < 2) { // Subname was just "1", we are done.
|
||||
return plc;
|
||||
}
|
||||
|
||||
DocumentObject* subObj = doc->getObject(names[1].c_str());
|
||||
if (!subObj) {
|
||||
return plc;
|
||||
}
|
||||
|
||||
std::vector<std::string> newNames(names.begin() + 2, names.end());
|
||||
std::string newSub = Base::Tools::joinList(newNames, ".");
|
||||
|
||||
return plc * subObj->getPlacementOf(newSub, targetObj);
|
||||
}
|
||||
|
||||
// case of a normal link
|
||||
DocumentObject* subObj = doc->getObject(names.front().c_str());
|
||||
|
||||
if (!subObj) {
|
||||
return plc;
|
||||
}
|
||||
|
||||
std::vector<std::string> newNames(names.begin() + 1, names.end());
|
||||
std::string newSub = Base::Tools::joinList(newNames, ".");
|
||||
|
||||
return plc * subObj->getPlacementOf(newSub, targetObj);
|
||||
}
|
||||
|
||||
bool Link::isLink() const
|
||||
{
|
||||
return ElementCount.getValue() == 0;
|
||||
@@ -2701,6 +2771,35 @@ App::Link* LinkElement::getLinkGroup() const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Base::Placement LinkElement::getPlacementOf(const std::string& sub, DocumentObject* targetObj)
|
||||
{
|
||||
Base::Placement plc;
|
||||
auto* propPlacement = dynamic_cast<App::PropertyPlacement*>(getPropertyByName("Placement"));
|
||||
if (!propPlacement) {
|
||||
return plc;
|
||||
}
|
||||
plc = propPlacement->getValue();
|
||||
|
||||
std::vector<std::string> names = Base::Tools::splitSubName(sub);
|
||||
|
||||
if (names.empty() || this == targetObj || !getLinkedObject()) {
|
||||
return plc;
|
||||
}
|
||||
|
||||
Document* doc = getLinkedObject()->getDocument();
|
||||
|
||||
DocumentObject* subObj = doc->getObject(names.front().c_str());
|
||||
|
||||
if (!subObj) {
|
||||
return plc;
|
||||
}
|
||||
|
||||
std::vector<std::string> newNames(names.begin() + 1, names.end());
|
||||
std::string newSub = Base::Tools::joinList(newNames, ".");
|
||||
|
||||
return plc * subObj->getPlacementOf(newSub, targetObj);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace App
|
||||
|
||||
@@ -664,6 +664,8 @@ public:
|
||||
|
||||
bool canLinkProperties() const override;
|
||||
|
||||
Base::Placement getPlacementOf(const std::string& sub, DocumentObject* targetObj = nullptr) override;
|
||||
|
||||
bool isLink() const override;
|
||||
|
||||
bool isLinkGroup() const override;
|
||||
@@ -718,6 +720,8 @@ public:
|
||||
bool isLink() const override;
|
||||
|
||||
App::Link* getLinkGroup() const;
|
||||
|
||||
Base::Placement getPlacementOf(const std::string& sub, DocumentObject* targetObj = nullptr) override;
|
||||
};
|
||||
|
||||
using LinkElementPython = App::FeaturePythonT<LinkElement>;
|
||||
|
||||
@@ -383,7 +383,7 @@ def getGlobalPlacement(ref, targetObj=None):
|
||||
rootObj = ref[0]
|
||||
subName = ref[1][0]
|
||||
|
||||
return App.GeoFeature.getGlobalPlacementOf(targetObj, rootObj, subName)
|
||||
return rootObj.getPlacementOf(subName, targetObj)
|
||||
|
||||
|
||||
def isThereOneRootAssembly():
|
||||
|
||||
@@ -253,6 +253,54 @@ class DraftLink(DraftObject):
|
||||
else:
|
||||
obj.setPropertyStatus("PlacementList", "Immutable")
|
||||
|
||||
def getPlacementOf(self, fp, subname, targetObj=None):
|
||||
"""
|
||||
Return the placement of the sub-object relative to the link object.
|
||||
"""
|
||||
|
||||
# _getShowElementValue is mapped to ExpandArray.
|
||||
if getattr(fp, "ExpandArray", False):
|
||||
# Return None to fall back to the C++ implementation
|
||||
return None
|
||||
|
||||
# We start with the object's own placement.
|
||||
plc = fp.Placement
|
||||
|
||||
if not subname:
|
||||
return plc
|
||||
|
||||
names = subname.split(".")
|
||||
|
||||
linked_obj = getattr(fp, "Base", None)
|
||||
if not names or fp == targetObj or not linked_obj:
|
||||
return plc
|
||||
|
||||
doc = linked_obj.Document
|
||||
|
||||
try:
|
||||
i = int(names[0])
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
# Check bounds in PlacementList
|
||||
plcs = fp.PlacementList
|
||||
if i < 0 or i >= len(plcs):
|
||||
return plc
|
||||
|
||||
# Accumulate the element placement
|
||||
plc = plc * plcs[i]
|
||||
|
||||
if len(names) < 2:
|
||||
return plc
|
||||
|
||||
subObj = doc.getObject(names[1])
|
||||
if not subObj:
|
||||
return plc
|
||||
|
||||
newSub = ".".join(names[2:])
|
||||
|
||||
return plc * subObj.getPlacementOf(newSub, targetObj)
|
||||
|
||||
|
||||
# Alias for compatibility with old versions of v0.19
|
||||
_DraftLink = DraftLink
|
||||
|
||||
Reference in New Issue
Block a user