Adding new OCCT classes to FreeCAD python

Here's the place for discussion related to coding in FreeCAD, C++ or Python. Design, interfaces and structures.
User avatar
Chris_G
Posts: 1250
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Adding new OCCT classes to FreeCAD python

Postby Chris_G » Wed Nov 27, 2019 2:02 pm

Hi,
I would like to experiment with OCCT advanced approximation functions.
It allows to approximate several lists of points (or several discretized curves) with a single Bezier or BSpline function.
This is, for example, a preliminary step to apply on the rails and profiles of a curve network, before building a Gordon surface.

To have an overview of how it works, these low-level functions are used in GeomAPI_PointsToBSpline (#186 and #256) that is used in FreeCAD by Part.BSplineSurface interpolate() and approximate().

So I started to implement the following OCCT classes :
- AppDef_MultiPointConstraint
- AppDef_MultiLine

And I will need to implement :
- AppDef_BSplineCompute

Having these 3 classes exposed to python should be enough to start experiments.

The MultiPointConstraint works OK so far.
But the problem is that I can't manage to feed my MultiLinePy object, with my MultiPointConstraintPy objects.

Lines 69 or 102 here :
https://github.com/tomate44/FreeCAD/blo ... ePyImp.cpp

And I will probably hit the same problem when I will want to feed my BSplineCompute class with my MultiLines.

Reading other source files in Part module, I am afraid I will need to first create the C++ classes ( of these OCCT classes), and use them in the python binding.

Here is my initial commit
ezzieyguywuf
Posts: 637
Joined: Tue May 19, 2015 1:11 am

Re: Adding new OCCT classes to FreeCAD python

Postby ezzieyguywuf » Wed Nov 27, 2019 2:30 pm

Chris_G wrote:
Wed Nov 27, 2019 2:02 pm
But the problem is that I can't manage to feed my MultiLinePy object, with my MultiPointConstraintPy objects.
What error exactly are you getting with the commented-out lines in your PyImp?

For starters, I can see that AppDef_MultiPointConstraint does not seem to have a copy constructor - in other words, it can't be constructed from another instance of AppDef_MultiPointConstraint.

Therefore:

Code: Select all

// this line....
// AppDef_MultiPointConstraint mpc(obj->getAppDef_MultiPointConstraintPtr());
// ...should probably be something like this
AppDef_MultiPointConstraint* ptr = getAppDef_MultiPointConstraintPtr();
// if you truly need a copy, you'll have to build it up manually
AppDef_MultiPointConstraint myMultipointConstraint(ptr->NbPoints(), ptr->NbPoints2d());
for(int i = 0; i < ptr->NbPoints; i++)
{
	myMultipointConstraint.SetPoint(i, ptr->Point(i);
	// Not sure if these two lines are needed or not....
	myMultipointConstraint.SetCurve(i, ptr->Curve(i));
	myMultipointConstraint.SetTang(i, ptr->Tang(i));
}
// loop over 2D points too....
All that being said, I'm interested to see if you can actually successfully wrap this pure OCC class using the FreeCAD python binding generators (as you're attempting to do) without having to write an intermediate FreeCAD class

edit:

The more I think about it, the more I believe that this should be doable using the FreeCAD python binding generators. I think you'll need to add a constructor, though, something like the following to your xml files:

Code: Select all

<Methode Name="__init__"/>
Otherwise, you won't have any way of creating the python object you want. Typically in FreeCAD, this is handled by way of a class which inherits from App::Document, and implements the GetPyObject virtual method. Since your class is "standalone", I believe you'll need to provide this constructor yourself.

The implementation for __init__ should be pretty straightforward, since AppDef_MultiPointConstraint has a default constructor.

Keep in mind, though, that the default-constructed AppDef_MultiPointConstraint is invalid. It still needs to be initialized. And per the OCC documentation, the order in which it is initialized matters (3D points first, 2D points next).

If you're simply using this just for "playing around", I wouldn't worry too much about "dummy proofing this" - in other words, I'd finish wrapping the balance of the OCC classes and then essentially use the python objects as:

Code: Select all

myMultipointConstraint = Part.MultiPointConstraint()
myMultiPointConstraint.SetPoint(0, Point1)
myMultiPointConstraint.SetPoint(1, Pont2)
# etc...
myMultiPointConstrait.SetPoint2d(0, Point2d1)
#etc...
If you mess up the order, then the part will be invalid, but if it's just for your own use then you can make sure you initialize correctly.

If you're trying to prepare this for a future Pull Request, then you can probably dummy proof it by requiring all the data in the __init__. Something like:

Code: Select all

//in AppDef_MultiLineConstraintPyImp.cpp
PyObject *AppDef_MultiLineConstraintPy::PyInit(PyObject* /*self*/, PyObject* args)
{
	PyObject* points3d = nullptr;
	PyObject* point2d  = nullptr;
	if (PyArg_ParseTuple(args, "OO", &nb))
	{
	    PyErr_SetString("Please provide a set of 3D and 2D points to the constructor");
	    return NULL;
	}
	if(not (PyList_Check(points3d) and PyList_Check(point2d))
	{
		PyErr_SetString("Both arguments _must_ be a list of points")
		return NULL;
	}
	// Probably loop through each point and ensure it is of the correct type.
	//...
	//...finally, use point data to initialize
}
User avatar
Chris_G
Posts: 1250
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: Adding new OCCT classes to FreeCAD python

Postby Chris_G » Wed Nov 27, 2019 4:29 pm

ezzieyguywuf wrote:
Wed Nov 27, 2019 2:30 pm
What error exactly are you getting with the commented-out lines in your PyImp?
I get the same error as the one I get with setValue() :

Code: Select all

error: ‘PyObject {aka struct _object}’ has no member named ‘getAppDef_MultiPointConstraintPtr’
What I don't understand, is that this same function is correctly compiled in AppDef_MultiPointConstraintPyImp.cpp#L103
(Be aware that I'm quite beginner with C++ and this is probably a dumb mistake I am doing)
ezzieyguywuf wrote:
Wed Nov 27, 2019 2:30 pm
All that being said, I'm interested to see if you can actually successfully wrap this pure OCC class using the FreeCAD python binding generators (as you're attempting to do) without having to write an intermediate FreeCAD class
That's my main question. From what I understand in Part/App, all shape and geometry classes, are kind of coded twice : one C++ (Geometry.cpp for example ) and one python (all the *Py.xml / *.PyImp.cpp).
But I don't really know if Python part can be implemented first, without the c++ counterpart.
My goal would be to PR this into FC master if it proves to be useful (I would love to have Gordon surfaces into master), so I wish to hear how to do it in the best possible "FreeCAD way".
ezzieyguywuf wrote:
Wed Nov 27, 2019 2:30 pm
edit:
...
I had to read this paragraph a couple of times, but now I get it. Thanks.
I'll see if I try this or if I try to code the C++ objects.
ezzieyguywuf wrote:
Wed Nov 27, 2019 2:30 pm
Otherwise, you won't have any way of creating the python object you want. Typically in FreeCAD, this is handled by way of a class which inherits from App::Document, and implements the GetPyObject virtual method. Since your class is "standalone", I believe you'll need to provide this constructor yourself.
This puts some light on how C++ and python objects are connected. Thanks again.
I saw this GetPyObject() method several times in Part code, but I didn't know its goal.
User avatar
DeepSOIC
Posts: 7479
Joined: Fri Aug 29, 2014 12:45 am
Location: Saint-Petersburg, Russia

Re: Adding new OCCT classes to FreeCAD python

Postby DeepSOIC » Wed Nov 27, 2019 4:51 pm

Chris_G wrote:
Wed Nov 27, 2019 4:29 pm
get the same error as the one I get with setValue() :
error: ‘PyObject {aka struct _object}’ has no member named ‘getAppDef_MultiPointConstraintPtr’
I guess, for this line:
https://github.com/tomate44/FreeCAD/blo ... p.cpp#L102
That is normal, you have to tell C++ what specific type of object you're dealing with. Since the typecheck has already been done by PyArg_ParseTuple, you can safely cast that obj to the type, like so:

Code: Select all

this->getAppDef_MultiLinePtr()->SetValue(idx, 
    static_cast<Part::AppDef_MultiPointConstraintPy*>(obj)->getAppDef_MultiPointConstraintPtr()
);
User avatar
Chris_G
Posts: 1250
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: Adding new OCCT classes to FreeCAD python

Postby Chris_G » Wed Nov 27, 2019 5:20 pm

Thanks Victor,
I add another error first, but it seems to be compiling fine this way :

Code: Select all

AppDef_MultiPointConstraint *mpc = static_cast<Part::AppDef_MultiPointConstraintPy*>(obj)->getAppDef_MultiPointConstraintPtr();
this->getAppDef_MultiLinePtr()->SetValue(idx, *mpc);
ezzieyguywuf
Posts: 637
Joined: Tue May 19, 2015 1:11 am

Re: Adding new OCCT classes to FreeCAD python

Postby ezzieyguywuf » Wed Nov 27, 2019 5:32 pm

Chris_G wrote:
Wed Nov 27, 2019 4:29 pm
From what I understand in Part/App, all shape and geometry classes, are kind of coded twice : one C++ (Geometry.cpp for example ) and one python (all the *Py.xml / *.PyImp.cpp).
Yes and no.

In your example, Geometry.cpp is what describes a Part::Geometry class (among other things). This is purely c++, and could realistically be used anywhere (inside or outside of FreeCAD) with:

Code: Select all

#include "/path/to/header/Geometry.h"
int main()
{
    Part::Geometery myGeom = Part::Geometery(); // not sure how these are initialized
    myGeom.DoSomething();
}
The GeometryPy.xml is a helper file - it describes parts of the pure c++ that we wish to expose to python.

The FreeCAD build system, by way of some fancy CMake stuff, ingests this xml file and literally creates source code with it. If you go to your build folder, you'll notice in src/Mod/Part that there exists GeometryPy.h and GeometryPy.cpp. In fact, I've found it helpful at times to inspect these files in order to troubleshoot issues in my xml file, or just to figure out what I can/can't do in my PyImp file.

Further, this is why your PyImp file needs to "#include" these generated files.

The last part of this equation is the PyImp.cpp file itself. While the freecad build system is able to take care of a lot of the "boiler plate" stuff using your xml (there's a lot of this "boiler plate" necessary when exposing c++ functionality to python), there are certain things that it just doesn't know ahead of time.

How is your object constructed? How many arguments does each method take? How do I call these methods? etc...

This is why the 'getMyClassPtr()' method exists - FreeCAD knows enough to create a pointer, and gives the developer access to this pointer. Later, the developer must use this pointer to redirect other python calls to utilize the correct c++ stuff.
Chris_G wrote:
Wed Nov 27, 2019 4:29 pm
But I don't really know if Python part can be implemented first, without the c++ counterpart.
Not really. You either have a python native class/functionality, which does not require any c++ or "wrappers", or you have some sort of c++ code that you wish to access in python.

The latter is the case for you, except you (nor the FreeCAD community) did not write the c++ code. Instead, the OCC developers did.
User avatar
Chris_G
Posts: 1250
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: Adding new OCCT classes to FreeCAD python

Postby Chris_G » Wed Nov 27, 2019 6:31 pm

Thanks. This is very instructive and puts some light on how FreeCAD works.
I think I will stick to the FreeCAD way, and make a AppDef.h / AppDef.cpp for the pure C++ objects.
ezzieyguywuf
Posts: 637
Joined: Tue May 19, 2015 1:11 am

Re: Adding new OCCT classes to FreeCAD python

Postby ezzieyguywuf » Thu Nov 28, 2019 4:00 am

Chris_G wrote:
Wed Nov 27, 2019 6:31 pm
I think I will stick to the FreeCAD way
Well, "the FreeCAD way" is really just "writing an xml file to describe what c++ functionality to expose in python."

In other words, writing a shallow wrapper around pre-existing c++ code shouldn't be necessary, thus my comment "I'm interested to see if...".

in other other words...just keep doing what you were already doing!
wmayer
Site Admin
Posts: 15484
Joined: Thu Feb 19, 2009 10:32 am

Re: Adding new OCCT classes to FreeCAD python

Postby wmayer » Thu Nov 28, 2019 9:17 am

In line 69 you do a static_cast on the wrong object. obj is supposed to be a sequence type (list, tuple, array, ... )

Code: Select all

                Py::Object pyo(*it1);
                AppDef_MultiPointConstraint *mympc = static_cast<Part::AppDef_MultiPointConstraintPy*>(obj)->getAppDef_MultiPointConstraintPtr();
                arraympc.SetValue(idx, *mympc);
Instead the cast must be done for "pyo". But before you can safely do the cast you must double check that it's of the expected type:

Code: Select all

if (PyObject_TypeCheck(pyo.ptr(), &(Part::AppDef_MultiPointConstraintPy::Type))) {
...
}
ickby
Posts: 2940
Joined: Wed Oct 05, 2011 7:36 am

Re: Adding new OCCT classes to FreeCAD python

Postby ickby » Thu Nov 28, 2019 10:12 am

Just a general question about the naming: Is this occt sheme of naming a good idea? it seems extremely un-freecad-like. It is already pretty bad for the "BRepOffsetAPI_" things already exposed: it is completely unintuitive in the freecad environment to use occt prefixes. I'm all for exposing more functionality, but why not using it in the existing shape API? Or creating just a some logical sounding subpackages in part, like

Code: Select all

Part.Approximation.MultiPointConstraint
IMHO the mixing of freecad style and ocp style makes the API much worse.