Dave, Frank,

Thank you for reporting your feelings and needs for a better pythonOCC.
Maybe it's time to discuss with you the future of this library, and the
paths we should The project was born 2 years ago, and my initial goal is
reached: it's now possible to do the same things with pythonOCC as with OCC,
but from the Python language: all algorithms posted on the OCC forum can be
easily ported to python, and they work the same way.

When starting the project, I wanted pythonOCC to be as close as possible to
the original C++ API. In my mind, python was just a convenient way to
prototype     C++ applications, so it was really important to me that the
move from python to C++ (or the opposite) was as simple as possible: same
classes/method names, same algorithms etc.

However, when developing with pythonOCC, Jelle and I realized that the OCC
API is not so convenient and provide very low level objects. A more
'pythonic' pythonOCC then raised in our minds. We then started to implement
what we called a 'high level API' (but we're currently discussing the name
of this additional layer): this layer provides useful and *pythonic*
classes/methods/functions on top of OCC. The 'Parametric Application
Framework' is the best example for this API, and is the work I'm the most
proud of: I think Jelle and I achieved a very elegant and pythonic way to
use and extend the features provided by both OCC/SGEOM libraries (see for
instance
http://code.google.com/p/pythonocc/source/browse/trunk/src/addons/PAF/Context.py).
There are also geometric/topological 'high level' functions very similar to
the Frank's MakeEdge function. They are available from the Utils.Construct
module (http://api.pythonocc.org/OCC.Utils.Construct-module.html), but we
never posted anything or documented these functions.

I agree with both of you Dave, Frank (and I know Jelle also agrees) that the
API needs now to be more pythonic. However, making the library more pythonic
will make us reach an irreversible point: it will not be possible anymore to
move from python code to C++ code. But I think that pythonOCC has now to
live it's own life, and may have to move away from its C++ 'mother' (or
'father', don't know what is the gender of OCC).

In order to achieve that point, here are the points we could focus on from
now:
- move TCollection*, TColgp* etc. to python lists. We developed ugly hacks
to transform such classes to python lists, but's it's not perfect and should
be done at the SWIG level,
- implement more 'high level api' functions/methods (as suggested by Frank).
Many of these functions have already been developed (the high level API
source  code is here:
http://code.google.com/p/pythonocc/source/browse/#svn/trunk/src/addons).
Maybe they should be more documented,
- for the topics related with memory management, Frank, let me think about
your suggestions the coming two days. Point (1) is closely related to the
way SWIG handles C++ references. OpenCascades makes an extensive use of
these awful '&' symbol. At the asme time, both OCC and python use a ref
counting mechanism for object deletion management: for each class, the SWIG
wrapper provides a python proxy to a C++ object (this C++ object can be
accessed by the  'this' attribute of any pythonOCC object). When deleting a
python class, you have to take care about the deletion of the proxy (python
ref count - gc) and the deletion of the pointed object (OCC ref counting). I
spent *many* hours studying this mechanism, and I can help you to save time
if you ever decide to dive into SWIG. Point (2) is interesting, never
thought about that. I don't know yet whether it's better to do it from the
Python or the SWIG/C++ level.

Best Regards,

Thomas

2010/3/17 Dave Cowden <dave.cow...@gmail.com>

> Yes I understand, and agree that it would be better if the memory
> management was a bit easier
>
> I've also found that there is a fairly blurry line between 'pythonicness'
> and 'poor/ugly API'.  Having written about 2000+ lines of code, i've written
> a lot of wrapper classes.
>
> Many of these wrappers i'd wager are nearly identical to those any other
> python developer writes as soon as they deal with openCascade.  for example
> generators to convert topexp iteration to a for/in loop, edge wrappers to
> return points convieniently, convienience methods to created edges and
> curves ( I have a function nearly the same as yours in my code btw  , case
> in point ).
>
> I guess what i'm saying is that, though garbage collection helps, its also
> true that your MakeEdge method is  somewhat compensating for a klunky api
> that doesnt just return Shape() and throw an exception instead of doing
> Perform() then IsDone() and such.  Its clear that the solution to this is to
> let people contribute helper and wrappers over time so that we all benefit.
>
> I think what we need from Thomas/Jelle is a place that these can be checked
> in and then apidoc is autogenerated. I'd be more than happy to commit some
> of my utilty/wrapper code, but i'd want to know where i can look to see the
> apidoc for the other such code is before I add it...
>
>
> On Wed, Mar 17, 2010 at 1:09 PM, Frank Conradie <fr...@qfin.net> wrote:
>
>>  Hi Dave
>>
>> Thanks for the suggestion - closures would certainly help. However, I am
>> currently less interested in ways to work *around* the "un-pythonic"
>> behavior of the wrapper, but would rather like to discuss the technicalities
>> and politics(!) of making the wrapper more "pythonic". Here is what I would
>> ideally like to do:
>>
>> * Clear up exactly what "un-pythonic" behavior I have run into while using
>> the library for a fairly complicated algorithm (two modules, 3000+ lines of
>> code).
>> * Discuss whether it is *technically* possible to change any of this
>> behavior to be more "pythonic"
>> * If it is indeed *technically* possible, discuss whether it is feasible
>> and worthwhile to actually implement
>>
>> Here is a summary of the current "unexpected" behavior as I see it:
>>
>> (1). When using OCC objects in Python, an unsuspecting programmer would
>> expect them to behave like Python objects, i.e. use Python ref counting and
>> garbage collection behavior. E.g. a BRepBuilderAPI object has a TopoDS_Shape
>> member, so this "myShape" has a refcount of 1 initially, and if it is
>> retrieved via the Shape() method and put into, say, a list, its refcount
>> must be 2, etc. So even if the BRepBuilderAPI object goes out of scope, its
>> internal shape is expected to still have a refcount of 1 and "survive" a
>> garbage collector purge. However, this refcounting obviously does not happen
>> on the C++ side, so the BRepBuilderAPI object would have to be kept "alive"
>> as long as its internal shape is needed.
>>
>> (2). Getting the "internal" shape from a BRepBuilderAPI, BRepPrimAPI,
>> BRepAlgoAPI, etc. object:
>>     * C++: calling Shape() always returns the *same* myShape instance
>>     * Python: calling Shape() returns a *new* SWIG wrapper object every
>> time, each with the same __hash__ value so they "look" like the same
>> instance (at least to sets and dicts)
>>     * The same unexpected behavior occurs when "exploring" a shape, i.e.
>> new SWIG wrapper objects are instantiated for every returned shape
>>     * The fact that different SWIG wrapper objects hash to the same value
>> at least allows us to identify unique shapes when put in sets and dicts, but
>> it confuses the GarbageCollector, as the same C++ object can be "collected"
>> multiple times, once for each SWIG object wrapping it
>>
>> Now, I can live with point (1), as I can see how this would be near
>> impossible to implement to work like it would in Python. However, I really
>> would like to discuss whether point (2) can be addressed, so that there is
>> always a 1-to-1 correspondence between a C++ object and its SWIG wrapper
>> object. The SWIG wrapper already generates a single unique __hash__ value
>> for a single C++ object, so would it not be feasible to keep a map of C++
>> object --> hash value --> SWIG wrapper object?
>>
>> In pseudo-code, when the wrapper is asked for an OCC object:
>>
>> h = hash(occobject)
>> if h in occ2swig_map:
>>     swig_wrapper = occ2swig_map[h]
>> else:
>>     swig_wrapper = new SwigWrapper(occobject)
>>     occ2swig_map[h] = swig_wrapper
>> return swig_wrapper
>>
>> I have never used SWIG myself, so excuse any ignorance on my side (I will
>> try and spend some time looking deeper into SWIG over the next few days).
>>
>> Hopefully we can have a fruitful and open discussion about this. I can
>> certainly work with the library as-is, and am indeed doing so successfully
>> at the moment. I just find that it is no fun having to deal with the very
>> kinds of C++ issues I am trying to avoid by using Python in the 1st place
>> ;-)
>>
>> Cheers,
>> Frank
>>
>>
>>
>>
>> On 17/03/2010 4:22 AM, Dave Cowden wrote:
>>
>> Hi, Frank:
>>
>>  I am not sure, but I think you could implement your utility function
>> using a python closure. This would allow the original object to remain in
>> scope until the nested function has completed. so for example:
>>
>>  To do closures in python, you have to use lamda, which creates a new
>> function having access to the outer scope, even after the function returns.
>> So
>>
>>  def MakeEdge(p1, p2):
>>     gp1,gp2 = [gp_Pnt(x,y,z) for x,y,z in (p1,p2)]
>>     mkedge = BRepBuilderAPI_MakeEdge(gp1, gp2)
>>     return mkedge.Shape()
>>
>>  Becomes:
>>
>>  def MakeEdge(p1,p2):
>>     gp1,gp2 = [gp_Pnt(x,y,z) for x,y,z in (p1,p2)]
>>      mkedge = BRepBuilderAPI_MakeEdge(gp1, gp2)
>>       return lambda e: mkedge.Shape();
>>
>>  now you can do
>> me = MakeEdge(p1,p2);
>>
>>  me.Shape() //return the generated shape
>> me.Shape() //return the generated shap again. It will be the same object
>> because the underlying brepbuilder is still in a closure
>>
>>  Now if you kill me, the closure will be discarded.
>>
>>  Dont know if this helps any or not.
>>
>> On Tue, Mar 16, 2010 at 7:53 PM, Frank Conradie <fr...@qfin.net> wrote:
>>
>>>  Hi Thomas
>>>
>>> After much investigation today, I have now added a flag to
>>> GarbageCollecter to disable it for our use, as it really does not work as
>>> intended for relatively complex code (I now manually track which objects I
>>> create and need to call _kill_pointed for). There are two main issues that
>>> we run into with the current GarbageCollector:
>>>
>>> 1. As already discussed, classes like the BRepPrimAPI and BRepAlgoAPI
>>> have an internal "myShape" data member that gets destroyed when the parent
>>> object's _kill_pointed method is called, causing all kinds of issues. As an
>>> example, it makes utility functions like the one below unusable, as the
>>> local BRepPrimAPI object goes out of scope and is thus collected by
>>> GarbageCollector for purging, even though the caller of the function may
>>> want to use the resulting shape for some time to come, after the parent is
>>> "purged":
>>>
>>> def MakeEdge(p1, p2):
>>>     gp1,gp2 = [gp_Pnt(x,y,z) for x,y,z in (p1,p2)]
>>>     mkedge = BRepBuilderAPI_MakeEdge(gp1, gp2)
>>>     return mkedge.Shape()
>>>
>>> 2. Every time a C++ OCC object is retrieved from Python in any way, a
>>> brand new SWIG wrapper object is instantiated for it, very easily/quickly
>>> leading to multiple wrapper objects on the Python side for the same
>>> underlying C++ object (I see this especially when using OCC shape containers
>>> repeatedly). When any one of these wrapper objects goes out of scope and is
>>> collected and purged, the underlying C++ object is destroyed (via
>>> _kill_pointed), even though there may well be other Python wrapper objects
>>> still needing access to it.
>>>     I guess the ideal solution to this issue would be to keep a mapping
>>> of C++ object -> SWIG wrapper object on the C++ side, so that there is
>>> always a 1-to-1 correspondence between the two, but I'm not sure how
>>> plausible this really is from an implementation point-of-view. As an example
>>> of what I mean, consider this: every time you call "mkedge.Shape()" in my
>>> example above, you get a new/different Python object, wrapping the same
>>> underlying myShape instance, which is totally unexpected and unintuitive
>>> from a Python perspective. A "pure" Python programmer would expect to get
>>> the exact same Python object back every time, and not different wrapper
>>> objects with just an "artificial" identical hash value.
>>>
>>> I realize that this is a very difficult issue, and that as users of the
>>> PythonOCC wrapper library we will need to take responsibility for memory
>>> management issues, but I can see lots of confusion for Python programmers
>>> using the library and expecting "Python-like" behaviour out of the box.
>>>
>>> I wish I new more about the SWIG wrapper object instantiation process so
>>> that I could make more concrete suggestions, instead of just complaining
>>> ;-)  What is your gut-feeling about this Thomas - do you think these two
>>> issues can be addressed in the wrapper, or is the current way the only
>>> viable implementation? If you can give me a short description of the SWIG
>>> wrapper instantiation process, I may be able to delve into it myself.
>>>
>>> Thanks for your help with this thus far,
>>> Frank
>>>
>>>
>>>
>>>
>>> On 16/03/2010 9:04 AM, Thomas Paviot wrote:
>>>
>>> Hi Frank,
>>>
>>>  I also suggest you use the push_context() and pop_context() methods at
>>> the beginning and the end of your algorithm. garbage.push_context() creates
>>> a new collector. Objects collected will go to this new list. Then, at the
>>> end of your algorithm, just call .smart_purge() and then pop_context().
>>> Otherwise, when calling the smart_purge() method, you'll erase all other
>>> objects that might have been stored in the gc thus causing strange things.
>>>
>>>  def my_high_memory_consuming_algorithm():
>>>     GarbageCollector.garbage.push_context()
>>>
>>>      ... do whatever you want ...
>>>
>>>      GarbageCollector.garbage.smart_purge() #only the objects created
>>> since the call to push_context are erased. Use MMGT_OPT=0 to make sure
>>> freeing #memory.
>>>     GarbageCollector.pop_context()
>>>     return data
>>>
>>>  There's also a garbage.prevent_object(object) method, that will take
>>> care to not delete an object that it's important to keep reference.
>>>
>>>  These three functions are quite new (but validated with a unittest).
>>> I'm curious to have your feedback!
>>>
>>>  Regards,
>>>
>>>  Thomas
>>>
>>>  These two functions are quite new,
>>> 2010/3/16 Frank Conradie <fr...@qfin.net>
>>>
>>>> Hi Thomas
>>>>
>>>> Well, I'm glad it's not a bug then! I can think of a few ways to handle
>>>> this situation in my algorithm, so it should not be a problem. I'm just
>>>> wondering how many other classes behave in this "unexpected" way :O
>>>>
>>>> Cheers,
>>>> Frank
>>>>
>>>>
>>>> On 15/03/2010 10:26 PM, Thomas Paviot wrote:
>>>>
>>>> Hi Frank,
>>>>
>>>>  This behaviour does not come from SWIG but from OpenCASCADE itself.
>>>> Let me explain you in a few words what happens here.
>>>>
>>>>  The BRepPrimAPI_MakeBox class inherits from the
>>>> BRepBuilderAPI_MakeShape one. This class provides the Shape() method you 
>>>> can
>>>> find in BRepPrimAPI_Make* basic geometry constructors. If you have a look 
>>>> at
>>>>  the Shape() method in the file BRepBuilderAPI_MakeShape.cxx, you will 
>>>> read:
>>>>
>>>>  """
>>>>
>>>> //=======================================================================
>>>> //function : Shape
>>>> //purpose  :
>>>>
>>>> //=======================================================================
>>>>
>>>>  const TopoDS_Shape&  BRepBuilderAPI_MakeShape::Shape() const
>>>> {
>>>>   if (!IsDone()) {
>>>>     // the following is const cast away
>>>>     ((BRepBuilderAPI_MakeShape*) (void*) this)->Build();
>>>>     Check();
>>>>   }
>>>>   return myShape;
>>>> }
>>>>  """
>>>>
>>>>  The Shape() method then returns a reference to the protected attribute
>>>> 'myShape' of the class BRepBuilderAPI_MakeBox. In other words, if you 
>>>> delete
>>>> the BRepPrimAPI_MakeBox instance, you loose the reference to the shape.
>>>>
>>>>  For instance:
>>>>
>>>>  >>> from OCC.BRepPrimAPI_MakeBox import *
>>>> >>> from OCC.TopoDS import *
>>>> >>> box = BRepPrimAPI_MakeBox(10,10,10)
>>>> >>> shp  = box.Shape()
>>>> >>> print shp.IsNull()
>>>> 0
>>>> >>> del box #object is garbage collected, not deleted yet
>>>> >>> print shp.IsNull()
>>>> 0
>>>> >>> GarbageCollector.garbage.smart_purge() # all objects are deleted,
>>>> loose the reference to myShape attribute
>>>> >>> print shp.IsNull()
>>>> 1
>>>>
>>>>  Then force objects deletion only if you're sure you don't need the
>>>> shapes anymore. I don't know what you're trying to do exactly, so it's
>>>> difficult to suggest any best practice.
>>>>
>>>>  Regards,
>>>>
>>>>  Thomas
>>>>
>>>> 2010/3/15 Frank Conradie <fr...@qfin.net>
>>>>
>>>>> Hi Thomas
>>>>>
>>>>> I am getting crash behaviour in the interim 0.5 PythonOCC release,
>>>>> related to the new GarbageCollector implementation. The code below
>>>>> crashes on my PC:
>>>>>
>>>>> ----------------------------------------
>>>>> from OCC.gp import *
>>>>> from OCC.BRepPrimAPI import *
>>>>> from OCC.BRepBuilderAPI import *
>>>>> from OCC.TopExp import *
>>>>> from OCC.TopoDS import *
>>>>>
>>>>> o = 0.0
>>>>> w = 0.1
>>>>> fp1 = (o,o,o)
>>>>> fp2 = (w,w,w)
>>>>>
>>>>> mkbox = BRepPrimAPI_MakeBox(gp_Pnt(fp1[0],fp1[1],fp1[2]),
>>>>> gp_Pnt(fp2[0],fp2[1],fp2[2]))
>>>>> s = mkbox.Shape()
>>>>> del mkbox
>>>>>
>>>>> print s.ShapeType()
>>>>> GarbageCollector.garbage.smart_purge()
>>>>> # The next line crashes my system
>>>>> print s.ShapeType()
>>>>> ----------------------------------------
>>>>>
>>>>> It is almost as if calling _kill_pointed for the mkbox causes shape "s"
>>>>> to become "corrupted" in some way. I dug through the SWIG code a bit,
>>>>> but cannot see anything obviously wrong, so I was hoping you could shed
>>>>> some light on the issue.
>>>>>
>>>>> By the way, I did get the SVN compiled myself as well, and get the same
>>>>> crash with my compiled lib as well as the one you made available on
>>>>> your
>>>>> website.
>>>>>
>>>>> Thanks,
>>>>> Frank
>>>>>
>>>>>
>>>>> _______________________________________________
>>>>> Pythonocc-users mailing list
>>>>> Pythonocc-users@gna.org
>>>>> https://mail.gna.org/listinfo/pythonocc-users
>>>>>
>>>>
>>>>
>>>> _______________________________________________
>>>> Pythonocc-users mailing 
>>>> listpythonocc-us...@gna.orghttps://mail.gna.org/listinfo/pythonocc-users
>>>>
>>>>
>>>> _______________________________________________
>>>> Pythonocc-users mailing list
>>>> Pythonocc-users@gna.org
>>>> https://mail.gna.org/listinfo/pythonocc-users
>>>>
>>>>
>>>
>>> _______________________________________________
>>> Pythonocc-users mailing 
>>> listpythonocc-us...@gna.orghttps://mail.gna.org/listinfo/pythonocc-users
>>>
>>>
>>> _______________________________________________
>>> Pythonocc-users mailing list
>>> Pythonocc-users@gna.org
>>> https://mail.gna.org/listinfo/pythonocc-users
>>>
>>>
>>
>> _______________________________________________
>> Pythonocc-users mailing 
>> listpythonocc-us...@gna.orghttps://mail.gna.org/listinfo/pythonocc-users
>>
>>
>> _______________________________________________
>> Pythonocc-users mailing list
>> Pythonocc-users@gna.org
>> https://mail.gna.org/listinfo/pythonocc-users
>>
>>
>
> _______________________________________________
> Pythonocc-users mailing list
> Pythonocc-users@gna.org
> https://mail.gna.org/listinfo/pythonocc-users
>
>
_______________________________________________
Pythonocc-users mailing list
Pythonocc-users@gna.org
https://mail.gna.org/listinfo/pythonocc-users

Reply via email to