'_RestrictData' object has no attribute... 'scenes': How to avoid it?

I understand that with the newest API change, scripts cannot access some stuff… a lot of stuff, during their loading. Like, they can’t modify scene’s props. Which is why I’m writing this. See, I got an export script for a custom model format that I’ve made and you could add “tags” to model parts (like, if the model had a gun turret, you’d add a tag to let the game engine know it can rotate that part when aiming at an enemy, for example). It’s very flexible, you can chose from preset tags or you could add a custom tag, set its ID and value and it’d get exported in the file.

I used a PropertyGroup called TagProperties (it actually had just one property, type) and a CollectionProperty based on it. For preset tags, I had a dictionary, like this:


# key = tag name
# value[0] = tag's export value
# value[1] = tag's type, we check this when drawing the panel
# value[2] = desription, it's also used for panel
VALID_TAGS = {
    'ROT_AXIS':                 (150, 'str', 'Single character, X, Y or Z'),
    'ROT_LIMIT_PLUS':           (151, 'int', 'Rotation limit in degrees (positive)'),
    'ROT_LIMIT_MINUS':          (152, 'int', 'Rotation limit in degrees (negative)'),
    'OBJECT_GROUP':             (160, 'int', 'Sets a relation beetween different objects through a common ID number'),
    'LOC_SLIDE_AXIS':           (170, 'str', 'Slide axis'),
    'AIM_ROT_YAW_AXIS':         (180, 'str', 'Aim axis (yaw)'),
    'AIM_ROT_YAW_FACTOR':       (181, 'float', 'Aim axis factor (yaw)'),
    'AIM_ROT_PITCH_AXIS':       (182, 'float', 'Aim axis (pitch)'),
    'AIM_ROT_PITCH_FACTOR':     (183, 'int', 'Aim axis factor (pitch)'),
    'AUTO_ANIM_TYPE':           (120, 'str', 'rot, slide'),
    'AUTO_ANIM_AXIS':           (121, 'str', 'X, Y or Z'),
    'AUTO_ANIM_MAX':            (122, 'float', 'Max'),
    'AUTO_ANIM_MIN':            (123, 'float', 'Min')
}

and when the script executed, ran:


bpy.context.scene.valid_tags.clear()
for tag in VALID_TAGS:
    new_tag = bpy.context.scene.valid_tags.add()
    new_tag.name = tag
    new_tag.type = VALID_TAGS[tag][0]

To first clear the valid_tags CollectionProperty of the current active scene and then populate it with preset tags. This is how it looked like when the addon loaded:


But now, my script cannot access the current scene through bpy.context.scene. I also tried to set valid_tags of all scenes, like this:


for s in bpy.data.scenes:
    s.valid_tags.clear()
    for tag in VALID_TAGS:
        new_tag = s.valid_tags.add()
        new_tag.name = tag
        new_tag.type = VALID_TAGS[tag][0]

but no, still can’t do it. And I don’t understand why, when I open a .blend file and go to the user preferences dialog to turn on my addon, there should be no reason for it to not be able to access the scene’s properties and modify them. Why they restricted this seems simply mind bogglingly stupid to me.

I know I can load the script in text editor and Alt+P it from there and it’d get exectuted. Also, I know I can add an operator to a panel and populate the collection property from there but - I don’t want to do all that every time a new .blend file is loaded. Is there any way to do this on an event of sorts, like “on-file-loaded” -> run this? Or is there an other way of achieving this functionality?

It is hard to say without seeing the entire AddOn.

Do you have an example version of your AddOn you could post or attach?

Certainly. Lines 73-78 are commented out; that’s where the problem lies. Put this in scripts/addon directory and turn it on in user preferences. Then load it in Blender’s text editor and execute from there (that way it works as intended, when aformentioned lines are uncommented, the script fails to load).

io_rbm_tools_example.py.zip (3.03 KB)

What about something like this…

The problem essentially is a problem of timing. BPY is just not ready, but if we defer our operation for a few milliseconds, it seems to work.

I have converted 73-78 to this.


############################################################################
# Thread processing for parameters that are invalid to set in a DRAW context.
# By performing those operations in these threads we can get around the invalid CONTEXT error within a draw event.
# This is fairly abusive to the system and may result in instability of operation.
# Then again as long as you trap for stale data it just might work..?
# For best result keep the sleep time as quick as possible...no long delays here.
############################################################################
def scene_init(lock,passedSleepTime):
    time.sleep(passedSleepTime) # Feel free to alter time in seconds as needed.   
    print("Threading: scene_init")
    doSceneWork()
        
def doSceneWork ():
    for s in bpy.data.scenes:
        s.valid_tags.clear()
        for tag in VALID_TAGS:
            print(tag)
            new_tag = s.valid_tags.add()
            new_tag.name = tag
            new_tag.type = VALID_TAGS[tag][0]

Then at the bottom of the script we launch a thread that will delay execution for a very short time.


# Launch a thread to set the remaining values that would generate a CONTEXT error if issued now. (listed below)
lock = threading.Lock()
lock_holder = threading.Thread(target=scene_init, args=(lock,0.2), name='Init_Scene')
lock_holder.setDaemon(True)
lock_holder.start()

The net result is the code that was on line 73-78 now runs without error when the AddOn is activated. I am not sure how to use your exporter so give it a try and let me know if this solves the problem or not?

Attachments

io_rbm_tools_example_ATOM.zip (3.49 KB)

Thank you for your answer. I have tested the code you posted and it works. But using threads is not the best way of doing things like this. Although we can assume that in most cases, after 200 ms the bpy will be ready to provide us full access to its context data, we can’t really be sure that it will work in 100% of cases. I have found in Blender’s Python API some useful handlers that can be used for this kinds of work, specifically bpy.app.handlers.scene_update_post (callback functions in this list get called each time after a scene is updated). If you define a function like you did in your example, you can add it to scene_update_post list and it should get executed every time a scene gets updated. Since in this case I only want it to run once, I simply pop it out of the list when the handler function finishes its work. This is what I’ve come up with and it should work every time:


# (...)
def doSceneWork (dummy):
    print("Populating all scenes' valid_tags with preset values...")
    for s in bpy.data.scenes:
        s.valid_tags.clear()
        for tag in VALID_TAGS:
            print(tag)
            new_tag = s.valid_tags.add()
            new_tag.name = tag
            new_tag.type = VALID_TAGS[tag][0]
        if doSceneWork in bpy.app.handlers.scene_update_post:
            print("Removing doSceneWork from scene_update_post handler list...")
            bpy.app.handlers.scene_update_post.remove(doSceneWork)

def register():
    bpy.utils.register_module(__name__)
    print("scene_update_post contains {}".format(bpy.app.handlers.scene_update_post))
    if not doSceneWork in bpy.app.handlers.scene_update_post:
        print("Adding doSceneWork to scene_update_post handler list...")
        bpy.app.handlers.scene_update_post.append(doSceneWork)

def unregister():
    bpy.utils.unregister_module(__name__)

if __name__ == '__main__':
   register()

However, I have encountered a strange bug. If I have my script opened in Blender’s internal text editor, the first time I turn on the addon, everything goes fine. But if I turn it off and then on again, Blender segfaults. This rarely happens when the script is not opened, but may happen if it’s opened externally. Crash log indicates the PyUnicode_FromString function is the culprit (at first I thought my messing up with handlers during callback function’s execution was the problem, but it happens even if I remove that code).


# Blender 2.67 (sub 0), Revision: 57123
bpy.data.window_managers["WinMan"].addon_filter = 'Import-Export'  # Property
bpy.ops.transform.translate(value=(-0.863077, -7.15945, 2.21999), constraint_axis=(False, False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1, snap=False, snap_target='CLOSEST', snap_point=(0, 0, 0), snap_align=False, snap_normal=(0, 0, 0), texture_space=False, release_confirm=False)  # Operator
bpy.ops.transform.translate(value=(-3.17825, 11.1456, 0.195485), constraint_axis=(False, False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1, snap=False, snap_target='CLOSEST', snap_point=(0, 0, 0), snap_align=False, snap_normal=(0, 0, 0), texture_space=False, release_confirm=False)  # Operator
No mesh data to join  # Warning

# backtrace
./blender() [0x8a726cd]
./blender() [0x8a72913]
[0xb14400]
/lib/i386-linux-gnu/libc.so.6(+0x84756) [0xcab756]
./blender(PyUnicode_FromString+0x10) [0xa7d8240]
./blender(PyDict_GetItemString+0x10) [0xa799aa0]
./blender() [0x9370b24]
./blender(pyrna_struct_CreatePyObject+0x30) [0x93701c0]
./blender() [0x9374737]
./blender(PyEval_EvalFrameEx+0x5adb) [0xa80196b]
./blender(PyEval_EvalCodeEx+0x83e) [0xa7fbdae]
./blender() [0xa783f29]
./blender(PyObject_Call+0x57) [0xa75f797]
./blender(bpy_app_generic_callback+0x9a) [0x936aa0a]
./blender(BLI_callback_exec+0x41) [0x93dba21]
./blender(BKE_scene_update_tagged+0xe6) [0x9011e46]
./blender(wm_event_do_notifiers+0x4d4) [0x8a8f474]
./blender(WM_main+0x30) [0x8a7a740]
./blender(main+0x364) [0x8a74ebb]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0xc404d3]
./blender() [0x89b60e1]

Again, thanks for your help, it’s much appreciated.

The pop the handler off the stack after a fist run is a good trick to keep in mind. I’ll have to try that.