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 <mailto: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 <mailto: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
        <mailto: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 <mailto:Pythonocc-users@gna.org>
            https://mail.gna.org/listinfo/pythonocc-users



        _______________________________________________
        Pythonocc-users mailing list
        Pythonocc-users@gna.org  <mailto:Pythonocc-users@gna.org>
        https://mail.gna.org/listinfo/pythonocc-users

        _______________________________________________
        Pythonocc-users mailing list
        Pythonocc-users@gna.org <mailto:Pythonocc-users@gna.org>
        https://mail.gna.org/listinfo/pythonocc-users



    _______________________________________________
    Pythonocc-users mailing list
    Pythonocc-users@gna.org  <mailto:Pythonocc-users@gna.org>
    https://mail.gna.org/listinfo/pythonocc-users

    _______________________________________________
    Pythonocc-users mailing list
    Pythonocc-users@gna.org <mailto: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