Python code: how to create a Boolean Fragment in C++

Here's the place for discussion related to coding in FreeCAD, C++ or Python. Design, interfaces and structures.
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
User avatar
uwestoehr
Veteran
Posts: 4961
Joined: Sun Jan 27, 2019 3:21 am
Location: Germany
Contact:

Python code: how to create a Boolean Fragment in C++

Post by uwestoehr »

I thought I asked this some months back, but cannot find this thread, therefore please forgive me if I asking this again:

How can I create a boolean Fragment using C++ code?

I need more often the SectionCutting feature and since I have to deal with intersecting parts, the only way to get proper cross-sections is to use a Boolean Fragments compound. Boolean Fragments is Python code (SplitFeatures.py) and I don't know yet, how to execute this from within C++ code.

With Part Compound I can for example do this:

Code: Select all

// create
auto CutCompound = doc->addObject("Part::Compound", CompoundName);
// access it
Part::Compound* pcCompound = static_cast<Part::Compound*>(CutCompound);
// add an object to it
pcCompound->Links.set1Value(count, newObject);
How is this done for a Python object?

@chennes , @wmayer since you know Python and C++, do you have a hint for me or maybe example code where Python code is used in C++?
wmayer
Founder
Posts: 20243
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: Python code: how to create a Boolean Fragment in C++

Post by wmayer »

Code: Select all

#include <App/Document.h>
#include <Base/Interpreter.h>
#include <CXX/Objects.hxx>
#include <Gui/Application.h>
#include <Gui/ViewProvider.h>

App::DocumentObject* tryCreateBooleanFragments(App::Document* doc)
{
    Base::PyGILStateLocker lock;
    PyObject* pymodule = PyImport_ImportModule("BOPTools.SplitFeatures");
    if (!pymodule) {
        throw Py::ImportError();
    }

    Py::Module module(pymodule, true);
    App::DocumentObject* object = doc->addObject("Part::FeaturePython", "Boolean Fragments");
    module.callMemberFunction("FeatureBooleanFragments", Py::TupleN(Py::asObject(object->getPyObject())));

    Gui::ViewProvider* viewObject = Gui::Application::Instance->getViewProvider(object);
    module.callMemberFunction("ViewProviderBooleanFragments", Py::TupleN(Py::asObject(viewObject->getPyObject())));

    return object;
}

void setObjectLinks(App::DocumentObject* object)
{
    if (App::PropertyLinkList* prop = dynamic_cast<App::PropertyLinkList*>(object->getPropertyByName("Objects"))) {
        prop->setValue(...);
    }
}

void createBooleanFragments()
{
    try {
        App::Document* doc = ...
        App::DocumentObject* obj = tryCreateBooleanFragments(doc);
        setObjectLinks(obj);
    }
    catch (Py::Exception& e) {
        e.clear();
        // TODO: Report Py exception...
    }
}
User avatar
uwestoehr
Veteran
Posts: 4961
Joined: Sun Jan 27, 2019 3:21 am
Location: Germany
Contact:

Re: Python code: how to create a Boolean Fragment in C++

Post by uwestoehr »

Many thanks!
(Is this documented somewhere in our Wiki?)

I made progress but cannot resolve this issue:
- I got the ViewProvider of the BooleanFragments object
- now I need to get its BoundingBox
For a Part::Compound the ViewProvider has the property "Shape" that I can access:

Code: Select all

Shape.getBoundingBox()
How is this done for BooleanFragments?
wmayer wrote: Tue Nov 29, 2022 8:00 pm

Code: Select all

    Base::PyGILStateLocker lock;
For what does one need the lock?
wmayer wrote: Tue Nov 29, 2022 8:00 pm

Code: Select all

    module.callMemberFunction("FeatureBooleanFragments", Py::TupleN(Py::asObject(object->getPyObject())));
Why is is this behind the creation of the App::DocumentObject*? It obviously fills the App::DocumentObject* but I don't understand yet what is done.
wmayer
Founder
Posts: 20243
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: Python code: how to create a Boolean Fragment in C++

Post by wmayer »

(Is this documented somewhere in our Wiki?)
Why should this be documented? It's simply the same recipe to call Python code from within C++.
- now I need to get its BoundingBox
You have to access the shape property of the document object and get the bounding box from there.
How is this done for BooleanFragments?
It's exactly the same way. In C++ the corresponding document object is the Part::FeaturePython and this provides a Shape property because it inherits from Part::Feature.
For what does one need the lock?
Because Python's C API requires it for most of its function that you grab the GIL (Global Interpreter Lock) when using them. After having used the Python C function the GIL must be released (as otherwise you will run into a deadlock when a thread uses the Python C API). PyGILStateLocker grabs the GIL in its constructor and releases it in its destructor. Using the RAII idiom makes the handling with the GIL exception-safe.
Why is is this behind the creation of the App::DocumentObject*? It obviously fills the App::DocumentObject* but I don't understand yet what is done.
FeatureBooleanFragments is a pure Python class and there is no C++ counterpart of it. It's a proxy class that implements a certain set of functions that can be called from within C++ using the Python C API.
User avatar
uwestoehr
Veteran
Posts: 4961
Joined: Sun Jan 27, 2019 3:21 am
Location: Germany
Contact:

Re: Python code: how to create a Boolean Fragment in C++

Post by uwestoehr »

wmayer wrote: Tue Nov 29, 2022 11:05 pm
(Is this documented somewhere in our Wiki?)
Why should this be documented? It's simply the same recipe to call Python code from within C++.
I mean where in our Wiki is the recipe to call Python documented and also details like the PyGILStateLocker.
This is very valuable info.
In our https://wiki.freecadweb.org/Power_users_hub
I only find how to use C++ code from within Python.
wmayer wrote: Tue Nov 29, 2022 11:05 pm
- now I need to get its BoundingBox
You have to access the shape property of the document object and get the bounding box from there.
Thanks. I thought I have to use the ViewProvider but my attempts to do so failed.
User avatar
uwestoehr
Veteran
Posts: 4961
Joined: Sun Jan 27, 2019 3:21 am
Location: Germany
Contact:

Re: Python code: how to create a Boolean Fragment in C++

Post by uwestoehr »

wmayer wrote: Tue Nov 29, 2022 8:00 pm

Code: Select all

void createBooleanFragments()
{
    try {
        App::Document* doc = ...
        App::DocumentObject* obj = tryCreateBooleanFragments(doc);
        setObjectLinks(obj);
    }
    catch (Py::Exception& e) {
        e.clear();
        // TODO: Report Py exception...
    }
}
I can't get this to work. After I used

Code: Select all

App::DocumentObject* obj = tryCreateBooleanFragments(doc);
I get a new oject in the tree view but it is not a valid BooleanFragments object. It does not have the correct icon and one cannot toggle its visibility.
When I recompute the object, I get an error that a BooleanFragments must have at least 2 objects.

OK, so I tried to
- first call

Code: Select all

App::DocumentObject* obj = tryCreateBooleanFragments(doc);
- then adding 2 objects using

Code: Select all

App::PropertyLinkList* CutLinkList =
                    dynamic_cast<App::PropertyLinkList*>(CutCompound->getPropertyByName("Objects")))
and then calling this on several objects:

Code: Select all

                CutLinkList->set1Value(count, newObject);
This fills me correctly the Objects list of the BooleanFragments object. However, the added objects are in the tree view not added to the BooleanFragments object. Also when I get the BoundingBox of the Shape of the BooleanFragments object, I see that it does not have the size if the added objects.

I also tried to
1. create the BooleanFragments object
2. calling

Code: Select all

CutLinkList->setValue(ObjectsListLinks);
to set directly a vector of App::DocumentObjects.
Unfortunately the result is the same than by adding several single DocumentObjects.

So I must do something wrong. I mean when I create a BooleanFragments object via Python, e.g. using this code:

Code: Select all

boolfrag = SplitFeatures.makeBooleanFragments(name='BooleanFragments')
    boolfrag.Objects = [upper_tube, force_point]
I get a valid BooleanFragments object and the Objects are in the TreeView part of it.

Do you have an idea what I am doing wrong?
wmayer
Founder
Posts: 20243
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: Python code: how to create a Boolean Fragment in C++

Post by wmayer »

I mean where in our Wiki is the recipe to call Python documented and also details like the PyGILStateLocker.
This is very valuable info.
In our https://wiki.freecadweb.org/Power_users_hub
Not our wiki. You will find plenty of information about embedding Python in the Python documentation or in this case the PyCXX documentation.
I get a new oject in the tree view but it is not a valid BooleanFragments object. It does not have the correct icon and one cannot toggle its visibility.
When I recompute the object, I get an error that a BooleanFragments must have at least 2 objects.
Of course. That's why I put the "..." to the function setObjectLinks() and it's up to you to add the two missing objects. My example code is only about the creation of the BooleanFragments objects and how to access the link property.
App::PropertyLinkList* CutLinkList =
dynamic_cast<App::PropertyLinkList*>(CutCompound->getPropertyByName("Objects")))
What is CutCompound?
This fills me correctly the Objects list of the BooleanFragments object. However, the added objects are in the tree view not added to the BooleanFragments object. Also when I get the BoundingBox of the Shape of the BooleanFragments object, I see that it does not have the size if the added objects.
To me it looks like you add them to the wrong object.
So I must do something wrong. I mean when I create a BooleanFragments object via Python, e.g. using this code:
Actually it would be the right way to call the function makeBooleanFragments() in the C++ code (as argument of module.callMemberFunction) but the implementation of this function has a code smell because it always uses the active document to add the object to. Although, it's OK in 99% of all cases it causes an unexpected behaviour in the remaining 1%. To fix this I suggest to extend the argument list by an optional parameter document which by default is set to None. Then inside the function it can use the active document if it's None and the passed document otherwise.
User avatar
uwestoehr
Veteran
Posts: 4961
Joined: Sun Jan 27, 2019 3:21 am
Location: Germany
Contact:

Re: Python code: how to create a Boolean Fragment in C++

Post by uwestoehr »

wmayer wrote: Wed Nov 30, 2022 9:12 am Not our wiki. You will find plenty of information about embedding Python in the Python documentation or in this case the PyCXX documentation.
OK. So I will have to add some. In my case I did not even know the term "PyCXX". the point is when you are a newbie like me in this regards, you don't know what to search for.
wmayer wrote: Wed Nov 30, 2022 9:12 am Of course. That's why I put the "..." to the function setObjectLinks() and it's up to you to add the two missing objects. My example code is only about the creation of the BooleanFragments objects and how to access the link property.
The point is that I do this, I create the BooleanFragments object and immediately fill its Objects property with 2 DocumentObjects.
The result is that the Objects property looks correct, but the objects are in the TreeView outside of it and the BooleanFragments object has not the correct icon.
When I change the Objects property from within GUI (as normal user), I can do whatever I want, but the BooleanFragments object keeps its wrong icon, cannot be made visible and the objects I set in the Objects property do not move into the BooleanFragments object.

So something went wrong when I created the BooleanFragments object and I have no clue yet what. I took exactly the code you proposed.
wmayer wrote: Wed Nov 30, 2022 9:12 am
App::PropertyLinkList* CutLinkList =
dynamic_cast<App::PropertyLinkList*>(CutCompound->getPropertyByName("Objects")))
What is CutCompound?
This is my BooleanFragments object.
wmayer wrote: Wed Nov 30, 2022 9:12 am To me it looks like you add them to the wrong object.
The BooleanFragments object contains correctly the objects I set, but as written above, that does not fix my problem.
wmayer wrote: Wed Nov 30, 2022 9:12 am Actually it would be the right way to call the function makeBooleanFragments() in the C++ code (as argument of module.callMemberFunction) but the implementation of this function has a code smell because it always uses the active document to add the object to. Although, it's OK in 99% of all cases it causes an unexpected behaviour in the remaining 1%. To fix this I suggest to extend the argument list by an optional parameter document which by default is set to None. Then inside the function it can use the active document if it's None and the passed document otherwise.
This is for the moment beyond my capability. At first I would be happy to be able to create a valid BooleanFragments object ;) . When this works, I can look deeper.
However, since you have already a clear method in mind, it is the best that you implement this.
User avatar
uwestoehr
Veteran
Posts: 4961
Joined: Sun Jan 27, 2019 3:21 am
Location: Germany
Contact:

Re: Python code: how to create a Boolean Fragment in C++

Post by uwestoehr »

uwestoehr wrote: Wed Nov 30, 2022 2:31 pm The result is that the Objects property looks correct, but the objects are in the TreeView outside of it and the BooleanFragments object has not the correct icon.
I am still stuck with this. This is my C++ code to create a BooleanFragements object:

Code: Select all

App::DocumentObject* SectionCut::CreateBooleanFragments(App::Document* doc)
{
    // import Python code
    Base::PyGILStateLocker lock;
    PyObject* pymodule = PyImport_ImportModule("BOPTools.SplitFeatures");
    if (!pymodule)
        throw Py::ImportError();
    Py::Module module(pymodule, true);

    // create the object
    const char* CompoundName = "SectionCutCompound";
    App::DocumentObject* object = doc->addObject("Part::FeaturePython", CompoundName);
    module.callMemberFunction("FeatureBooleanFragments",
                              Py::TupleN(Py::asObject(object->getPyObject())));
    if (!object) {
        Base::Console().Error((std::string("SectionCut error: ") + std::string(CompoundName)
                               + std::string(" could not be added\n")).c_str());
        return nullptr;
    }
    return object;
}
This gives me an object in the document tree that has not the right icon and when I try to add objects to it they are not listed in the tree under it.
I must do something wrong but don't know what.
User avatar
uwestoehr
Veteran
Posts: 4961
Joined: Sun Jan 27, 2019 3:21 am
Location: Germany
Contact:

Re: Python code: how to create a Boolean Fragment in C++

Post by uwestoehr »

I found not a solution:

Code: Select all

App::DocumentObject* SectionCut::CreateBooleanFragments(App::Document* doc)
{
    // create the object
    const char* CompoundName = "SectionCutCompound";
    Gui::Command::doCommand(Gui::Command::Doc, "import FreeCAD");
    Gui::Command::doCommand(Gui::Command::Doc, "from BOPTools import SplitFeatures");
    Gui::Command::doCommand(Gui::Command::Doc,
                            "SplitFeatures.makeBooleanFragments(name=\"%s\")",
                            CompoundName);

    App::DocumentObject* object = doc->getObject(CompoundName);
    if (!object) {
        Base::Console().Error((std::string("SectionCut error: ") + std::string(CompoundName)
                               + std::string(" could not be added\n")).c_str());
        return nullptr;
    }
    return object;
}
It seems the other approach failed because "makeBooleanFragments" was not executed. I played a bit to find the right syntax but failed.
Post Reply