Issue: property() not generating attribute

Anybody knows why this:

class SoBaseColor(SoNode):
    r"""Proxy of C++ SoBaseColor class."""
    rgb = property(_coin.SoBaseColor_rgb_get, _coin.SoBaseColor_rgb_set)

is not working:

>>> test = pivy.coin.SoBaseColor()
>>> test.rgb
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'SoBaseColor' object has no attribute 'rgb'

I can manually create a a = property(_coin.SoBaseColor_rgb_get, _coin.SoBaseColor_rgb_set) and call color = a.fget(test) and a.fset(test,color) without problems

(Code is generated by swig, Manually build python 3.11.5 from vcpkg, issues is nearly with all property() calls except for thisown for some reson)

Any idea how to debug this issue?

Are you sure that pivy.coin.SoBaseColor in the second code block refers to the SoBaseColor class in the first?

What results do you get from:

  • pivy.coin.SoBaseColor.rgb
  • dir(test)
  • dir(pivy.coin.SoBaseColor)
  • pivy.coin.__file__
>>> import pivy
>>> test = pivy.coin.SoBaseColor()
TEST # Note: This was added by me in the __init__ def of the class
>>> pivy.coin.SoBaseColor.rgb
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'SoBaseColor' has no attribute 'rgb'
>>> dir(test)
['COIN_1_0', 'COIN_2_0', 'COIN_2_2', 'COIN_2_3', 'COIN_2_4', 'COIN_2_5', 'COIN_3_0', 'COIN_4_0', 'EXTENSION', 'FIRST_INSTANCE', 'GLRender', 'GLRenderBelowPath', 'GLRenderInPath', 'GLRenderOffPath', 'GLRenderS', 'INVENTOR', 'INVENTOR_1', 'INVENTOR_2_0', 'INVENTOR_2_1', 'INVENTOR_2_5', 'INVENTOR_2_6', 'INVENTOR_5_0', 'INVENTOR_6_0', 'OTHER_INSTANCE', 'PROTO_INSTANCE', 'VRML1', 'VRML2', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__swig_destroy__', '__weakref__', 'addAuditor', 'addCopy', 'addName', 'addToCopyDict', 'addWriteReference', 'affectsState', 'assertAlive', 'audioRender', 'audioRenderS', 'callback', 'callbackS', 'checkCopy', 'cleanupClass', 'connectRoute', 'copy', 'copyContents', 'copyDone', 'copyFieldValues', 'copyThroughConnection', 'decrementCurrentWriteCounter', 'doAction', 'enableNotify', 'fieldsAreEqual', 'findCopy', 'get', 'getActionMethodIndex', 'getAllFields', 'getAuditors', 'getBoundingBox', 'getBoundingBoxS', 'getByName', 'getChildren', 'getClassTypeId', 'getCompatibilityTypes', 'getEventIn', 'getEventOut', 'getField', 'getFieldData', 'getFieldName', 'getFields', 'getFieldsMemorySize', 'getIsBuiltIn', 'getMatrix', 'getMatrixS', 'getName', 'getNamedBase', 'getNamedBases', 'getNextNodeId', 'getNodeId', 'getNodeType', 'getPrimitiveCount', 'getPrimitiveCountS', 'getRefCount', 'getTraceRefs', 'getTypeId', 'getUserData', 'grabEventsCleanup', 'grabEventsSetup', 'handleEvent', 'handleEventS', 'hasDefaultValues', 'incrementCurrentWriteCounter', 'initClass', 'initClasses', 'initCopyDict', 'isNotifyEnabled', 'isOfType', 'isOverride', 'notify', 'pick', 'pickS', 'rayPick', 'rayPickS', 'read', 'readRoute', 'ref', 'removeAuditor', 'removeName', 'search', 'searchS', 'set', 'setInstancePrefix', 'setName', 'setNodeType', 'setOverride', 'setToDefaults', 'setTraceRefs', 'setUserData', 'shouldWrite', 'startNotify', 'this', 'thisown', 'touch', 'unref', 'unrefNoDelete', 'validateNewFieldValue', 'write', 'writeInstance', 'writeS']
>>> dir(pivy.coin.SoBaseColor)
['COIN_1_0', 'COIN_2_0', 'COIN_2_2', 'COIN_2_3', 'COIN_2_4', 'COIN_2_5', 'COIN_3_0', 'COIN_4_0', 'EXTENSION', 'FIRST_INSTANCE', 'GLRender', 'GLRenderBelowPath', 'GLRenderInPath', 'GLRenderOffPath', 'GLRenderS', 'INVENTOR', 'INVENTOR_1', 'INVENTOR_2_0', 'INVENTOR_2_1', 'INVENTOR_2_5', 'INVENTOR_2_6', 'INVENTOR_5_0', 'INVENTOR_6_0', 'OTHER_INSTANCE', 'PROTO_INSTANCE', 'VRML1', 'VRML2', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__swig_destroy__', '__weakref__', 'addAuditor', 'addCopy', 'addName', 'addToCopyDict', 'addWriteReference', 'affectsState', 'assertAlive', 'audioRender', 'audioRenderS', 'callback', 'callbackS', 'checkCopy', 'cleanupClass', 'connectRoute', 'copy', 'copyContents', 'copyDone', 'copyFieldValues', 'copyThroughConnection', 'decrementCurrentWriteCounter', 'doAction', 'enableNotify', 'fieldsAreEqual', 'findCopy', 'get', 'getActionMethodIndex', 'getAllFields', 'getAuditors', 'getBoundingBox', 'getBoundingBoxS', 'getByName', 'getChildren', 'getClassTypeId', 'getCompatibilityTypes', 'getEventIn', 'getEventOut', 'getField', 'getFieldData', 'getFieldName', 'getFields', 'getFieldsMemorySize', 'getIsBuiltIn', 'getMatrix', 'getMatrixS', 'getName', 'getNamedBase', 'getNamedBases', 'getNextNodeId', 'getNodeId', 'getNodeType', 'getPrimitiveCount', 'getPrimitiveCountS', 'getRefCount', 'getTraceRefs', 'getTypeId', 'getUserData', 'grabEventsCleanup', 'grabEventsSetup', 'handleEvent', 'handleEventS', 'hasDefaultValues', 'incrementCurrentWriteCounter', 'initClass', 'initClasses', 'initCopyDict', 'isNotifyEnabled', 'isOfType', 'isOverride', 'notify', 'pick', 'pickS', 'rayPick', 'rayPickS', 'read', 'readRoute', 'ref', 'removeAuditor', 'removeName', 'search', 'searchS', 'set', 'setInstancePrefix', 'setName', 'setNodeType', 'setOverride', 'setToDefaults', 'setTraceRefs', 'setUserData', 'shouldWrite', 'startNotify', 'thisown', 'touch', 'unref', 'unrefNoDelete', 'validateNewFieldValue', 'write', 'writeInstance', 'writeS']
>>> pivy.coin.__file__
'E:\\all\\vcpkg\\installed\\x64-win-llvm-opt-rel\\tools\\python3\\Lib\\site-packages\\pivy\\coin.py' # Note: That is the file I expect it to be. 

Is SoNode a normal class? What is type(SoBaseClass)?

>>> type(pivy.coin.SoBaseColor)
<class 'type'>
>>> type(pivy.coin.SoNode)
<class 'type'>

If you don’t inherit from SoNode, does the class SoBaseColor then have the rgb attribute? Since it appears to be being removed.

>>> import pivy
>>> test = pivy.coin.SoBaseColor()
TEST
>>> dir(test)
['GLRender', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'callback', 'doAction', 'getClassTypeId', 'getTypeId', 'initClass', 'rgb', 'this', 'thisown']
>>> test.rgb
<pivy.coin.SoMFColor; proxy of <Swig Object of type 'SoMFColor *' at 0x0000022E84DD16E0> >

So It works without inheriting. So when does an attribute get removed? I followed the inheritance chain which is SoNode -> SoFieldContainer -> SoBase it fails with SoFieldContainer while SoBase works. So what in SoFieldContainer could remove the attribute?

I also have a comparison of working vs not working generated wrappers and for SoFieldContainer it shows:

diff --git a/coin.py b/coin-new.py
index 902ad94..2561563 100644
--- a/coin.py
+++ b/coin-new.py
@@ -1,5 +1,5 @@
 # This file was automatically generated by SWIG (https://www.swig.org).
-# Version 4.1.0
+# Version 4.2.0
 #
 # Do not make changes to this file unless you know what you are doing - modify
 # the SWIG interface file instead.
@@ -3449,6 +3094,10 @@ class SoFieldContainer(SoBase):
         r"""getEventOut(SoFieldContainer self, SbName name) -> SoField"""
         return _coin.SoFieldContainer_getEventOut(self, name)
 
+    def getFieldName(self, field, name):
+        r"""getFieldName(SoFieldContainer self, SoField field, SbName name) -> SbBool"""
+        return _coin.SoFieldContainer_getFieldName(self, field, name)
+
     def enableNotify(self, flag):
         r"""enableNotify(SoFieldContainer self, SbBool const flag) -> SbBool"""
         return _coin.SoFieldContainer_enableNotify(self, flag)
@@ -3540,62 +3189,6 @@ class SoFieldContainer(SoBase):
         r"""getUserData(SoFieldContainer self) -> void *"""
         return _coin.SoFieldContainer_getUserData(self)
 
-    def getFieldName(self, *args):
-        r"""
-        getFieldName(SoFieldContainer self, SoField field, SbName name) -> SbBool
-        getFieldName(SoFieldContainer self, SoField field) -> PyObject *
-        """
-        return _coin.SoFieldContainer_getFieldName(self, *args)
-
-    def __getattr__(self, name):
-        try:
-            return SoBase.__getattribute__(self, name)
-        except AttributeError as e:
-    ##############################################################
-            if name == "this":
-                raise AttributeError
-    ##############################################################
-            field = self.getField(name)
-            if field is None:
-                raise e
-            return field
-
-    def __setattr__(self, name, value):
-    # I don't understand why we need this, but otherwise it does not work :/
-        if name == 'this':
-            return SoBase.__setattr__(self, name, value)
-        field = self.getField(name)
-        if field is None:
-            return SoBase.__setattr__(self, name, value)
-        field.setValue(value)
-        return field
-
-    def __dir__(self):
-        from pivy import coin
-        fl = coin.SoFieldList()
-        num_fields = self.getAllFields(fl)
-        fields = [self.getFieldName(fl[i]) for i in range(num_fields)]
-        return super(SoFieldContainer, self).__dir__() + fields
-
-
-    @property
-    def values(self):
-        def _values(obj):
-            for value in obj:
-                if hasattr(value, "__iter__"):
-                    yield list(_values(value))
-                else:
-                    yield value
-        out = _values(self)
-        return list(out)
-
-    @values.setter
-    def values(self, arr):
-        self.deleteValues(0)
-        self.setValues(0, len(arr), arr)
-
-
-
 # Register SoFieldContainer in _coin:
 _coin.SoFieldContainer_swigregister(SoFieldContainer)
 class SoNode(SoFieldContainer):

And adding that Code makes It work. So thanks for the help I think I know what is going on since I also found that code in an *.i file which SWIG should consume.
I build pivy out-of-source using CMake and I think what is happening is that SWIG does not see that file and thus does not added the required code above.
Would still be interesting to know why the code is necessary in the first place.