Global Var Breaking Between External Files

I have an add-on that sets a material color, and as it grows to include more color sets, I’m making it modular.

Moving the color classes to external files was easy, but moving the individual sub-panels is breaking them.

The sub-panels load a color thumbnail image for each button, but when I move them to an external file, the link to the image breaks.and the error message I get is

Traceback (most recent call last):
  File "/XXX/Blender/3.2/scripts/addons/blender-mc/panel_f58.py", line 23, in draw
    scol.label(text="", icon_value=c_icons["f58a_raven_black"].icon_id)
NameError: name 'c_icons' is not defined

The files are on GitHub, but here are the relevant code snippets:

In __init__.py, I open with

import os
import bpy
import bpy.utils.previews

The parent panel is defined, and then where I used to have the sub-panel class, I import an external file

from .panel_f58 import *

and at the end, the image loader is included in register/unregister:

def register():
    global c_icons
    c_icons = bpy.utils.previews.new()
    addon_path =  os.path.dirname(__file__)
    icons_dir = os.path.join(addon_path, "icons")
    for entry in os.listdir(icons_dir):
        c_icons.load(os.path.splitext(entry)[0], os.path.join(icons_dir, entry), "IMAGE")

def unregister():
    global c_icons
    bpy.utils.previews.remove(c_icons)

The panel_f58.py file opens with

import bpy

and the relevant code for each button is

    def draw(self, context):
        global c_icons
        layout = self.layout

        srow = layout.row()
        scol = srow.column(align=True)
        scol.scale_y = 1.25
        scol.label(text="", icon_value=c_icons["f58a_raven_black"].icon_id)
        ...

        scol = srow.column(align=True)
        scol.scale_y = 1.25
        scol.scale_x = 3.0
        scol.operator("color.f58a_raven_black", text="A Raven Black")
        ...

and that’s about it.

I’ve tried copying the __init__.py imports and register/unregister into my sub-file, but nada.

It’s probably something very basic to do with global c_icons connecting/pointing to the wrong location or os.path being incorrectly defined or something, but I’m pretty much hurr-durr at this point.

If anyone has any suggestions, I’d be grateful.

1 Like

Global variables are bad practice, they cause a huge range of problems (as you’ve noticed). Instead of doing global c_icons, do this:

class Globals():
    c_icons = <something>
g = Globals()

...
scol.label(text="", icon_value=g.c_icons["f58a_raven_black"].icon_id)
...

Ah, and should I pull class Globals() out of register(): ?

You can put it wherever, I usually put it at the top of my main file right under the bl_info

Okay, so I put

# LOAD CUSTOM ICONS
class Globals(bpy.types.UnknownType):
    c_icons = bpy.utils.previews.new()

g = Globals()

at the start of __init__.py,
(I was getting a type error, so I assigned it UnknownType)

added Globals to the reg/unreg classes array,

changed r/un to

def register():
    addon_path =  os.path.dirname(__file__)
    icons_dir = os.path.join(addon_path, "icons")
    for entry in os.listdir(icons_dir):
        g.c_icons.load(os.path.splitext(entry)[0], os.path.join(icons_dir, entry), "IMAGE")
    for cls in classes:
        bpy.utils.register_class(cls)


def unregister():
    bpy.utils.previews.remove(g.c_icons)
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)

and changed the panels to

    def draw(self, context):
        g.c_icons
        layout = self.layout

        srow = layout.row()
        scol = srow.column(align=True)
        scol.scale_y = 1.25
        scol.label(text="", icon_value=g.c_icons["ja_salon_rose"].icon_id)

and here’s the current Error Message:

Traceback (most recent call last):
  File "/Applications/Blender.app/Contents/Resources/3.2/scripts/modules/addon_utils.py", line 335, in enable
    mod = __import__(module_name)
  File "/XXX/Blender/3.2/scripts/addons/blender-mc/__init__.py", line 53, in <module>
    g = Globals()
TypeError: bpy_struct.__new__(type): expected a single argument
Exception ignored in: <function ImagePreviewCollection.__del__ at 0x13c8c24d0>
Traceback (most recent call last):
  File "/Applications/Blender.app/Contents/Resources/3.2/scripts/modules/bpy/utils/previews.py", line 66, in __del__
    raise ResourceWarning(
ResourceWarning: <ImagePreviewCollection id=0x13e50cd60[0], <super: <class 'ImagePreviewCollection'>, <ImagePreviewCollection object>>>: left open, remove with 'bpy.utils.previews.remove()'

Any thoughts?

Moved the class Globals() into r/un

def register():
    class Globals():
        c_icons = bpy.utils.previews.new()
    g = Globals()
    addon_path =  os.path.dirname(__file__)
    icons_dir = os.path.join(addon_path, "icons")
    for entry in os.listdir(icons_dir):
        g.c_icons.load(os.path.splitext(entry)[0], os.path.join(icons_dir, entry), "IMAGE")

def unregister():
    g = Globals()
    bpy.utils.previews.remove(g.c_icons)

Tab is now loading, but buttons are not.

Traceback (most recent call last):
  File "/Applications/Blender.app/Contents/Resources/3.2/scripts/modules/bpy/utils/previews.py", line 66, in __del__
    raise ResourceWarning(
ResourceWarning: <ImagePreviewCollection id=0x143a2d4e0[104], <super: <class 'ImagePreviewCollection'>, <ImagePreviewCollection object>>>: left open, remove with 'bpy.utils.previews.remove()'

And opening/closing panel returns this error:

Traceback (most recent call last):
  File "/XXX/Blender/3.2/scripts/addons/blender-mc/panel_f58.py", line 15, in draw
    g.c_icons
NameError: name 'g' is not defined

I’m thinking your errors are seemingly related but actually separate- this should work, it’s standard practice for using global variables in this context :thinking:

It seems to be two issues:

  • With Global()' inside register(), it seems to be unavailable to unregister()` (or improperly named)
  • The sub-file can’t find Global() at all, which is the same issue I had before

For the second issue, you should be able to import Global from < filename > and then do g=Global(). For your first, you shouldn’t need to unregister it, I think you can just remove that line

EUREKA!!! Moving Globals() to an external file did the trick! Everything is working fine now.

Thank you so much for your help!

1 Like

Here’s the final working solution:

panel_classes.py

import bpy

class Globals():
    c_icons = bpy.utils.previews.new()
g = Globals()

panel_f58.py

import bpy

from .panel_classes import *

class F1958Panel(bpy.types.Panel):
    ... bl_info here ...

    def draw(self, context):
        g.c_icons
        layout = self.layout

        srow = layout.row()
        scol = srow.column(align=True)
        scol.scale_y = 1.25
        scol.label(text="", icon_value=g.c_icons["f58a_raven_black"].icon_id)
        ...

        scol = srow.column(align=True)
        scol.scale_y = 1.25
        scol.scale_x = 3.0
        scol.operator("color.f58a_raven_black", text="A Raven Black")
        ...

__init__.py

bl_info = {}

import os
import bpy
import bpy.utils.previews

from .panel_classes import *
from .panel_f58 import *

classes = [
    F1958Panel,
    ...
]

def register():
    addon_path =  os.path.dirname(__file__)
    icons_dir = os.path.join(addon_path, "icons")
    for entry in os.listdir(icons_dir):
        g.c_icons.load(os.path.splitext(entry)[0], os.path.join(icons_dir, entry), "IMAGE")

    for cls in classes:
        bpy.utils.register_class(cls)

def unregister():
    bpy.utils.previews.remove(g.c_icons)

    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)


if __name__ == "__main__":
    __file__ = bpy.data.filepath
    register()
1 Like