More details can be found on my blender.stackexchange post, but basically I’m trying initialize an empty, globally available list that I can later populate with object.location tuples. Anyone handle this kind of thing before?
For such lists, I would suggest using an EnumProperty. If you want you can make the items parameter a function, in which case you can dynamically return items to your heart’s content. The items can’t contain arbitrary Python objects, though, so you’ll have to make your own mapping from the value in the EnumProperty to the object locations.
def enumlist_objects(self, context):
"""Populate Objects List from Parts Library.
Creates list of objects that optionally have search string contained in them
to populate variable pdt_lib_objects enumerator.
Args:
context: Current Blender bpy.context
Returns:
list of Object Names.
"""
scene = context.scene
path = os.path.join(bpy.utils.user_resource("SCRIPTS", "addons"), "clockworxpdt", "parts_library.blend")
with bpy.data.libraries.load(path) as (data_from, data_to):
if len(scene.pdt_obsearch) == 0:
object_names = [ob for ob in data_from.objects]
else:
object_names = [ob for ob in data_from.objects if scene.pdt_obsearch in ob]
items = []
for ob in object_names:
items.append((ob, ob, ""))
return items
items = []
for ob in object_names:
items.append((ob, ob, ""))
return items
As per the warning in the documentation, you have to keep a reference to the returned strings from within Python itself. Making items a module-global variable would help in this regard (and of course it would require a rename to make sense then).
About naming variables, I would always keep ob or obj to refer to actual objects. Just use for object_name in object_names instead, that avoids confusion.
Hmm, I have an issue here, because this is in the __init__.py file of an add-on, so the enumerator is declared in the register() function of the __init__.py file and is populated in a separate function in the __init__.py file, that reads the object library file. So I am not sure where I can declare the items list in this case? I am confused, because what I did definitely works and what I did before has not crashed Blender yet, but I accept it might…
Should I just have a separate items_list.py file with the three of these I require and then import them into the __init__.py file?
Sorry to seem thick, but this is a bit new to me…
EDIT:
If it helps here is the declaration of the EnumProperty:
A module-global name is just one that’s declared at the module level (so not inside a function). Doing that in one file or another file (like the proposed some_module.py) has no effect on this.
The global keyword is only necessary inside a function, if you want to assign something. Python is quite a simple language; if a function contains any line like x = Y, it assumes the name x is local to that function. If you wanted to assign to the global x, you have to use the global keyword. If you look at this from the other side: if you can avoid the assignment, you can also remove the global.
This would work:
_enum_items = []
def enum_items(self, context):
_enum_items.clear()
for ob in object_names:
_enum_items.append((ob, ob, ""))
return _enum_items
Because there is no assignment to _enum_items inside the function, it’s clear to Python what you mean, and you don’t need global.
Thank you so much for your help here, now I understand! I am very new to making add-ons and new to Python, it is so different to the languages I used in my career all those years ago…
Here is my revised code showing just one of the three functions to populate the three EnumProperties:
# Declare enum items variables
#
_pdt_obj_items = []
_pdt_col_items = []
_pdt_mat_items = []
def enumlist_objects(self, context):
"""Populate Objects List from Parts Library.
Creates list of objects that optionally have search string contained in them
to populate variable pdt_lib_objects enumerator.
Args:
context: Current Blender bpy.context
Returns:
list of Object Names.
"""
scene = context.scene
path = os.path.join(str(Path(__file__).parents[0]), "parts_library.blend")
_pdt_obj_items.clear()
if Path(path).is_file():
with bpy.data.libraries.load(path) as (data_from, data_to):
if len(scene.pdt_obsearch) == 0:
object_names = [ob for ob in data_from.objects]
else:
object_names = [ob for ob in data_from.objects if scene.pdt_obsearch in ob]
for object_name in object_names:
_pdt_obj_items.append((object_name, object_name, ""))
else:
_pdt_obj_items.append(("MISSING","Library is Missing",""))
return _pdt_obj_items
Ok, that’s overly complex. If you’re using pathlib.Path (which I really can recommend because it makes life easy), you can avoid using os.path. This does pretty much the same:
The only difference that here path is still a Path object. Probably easier to work with, and you can always convert it to a string with str(path) when you need to pass it to some code that doesn’t understand pathlib.Path objects. However, later you do Path(path) anyway, so having a Path is handy.
path = Path(__file__).parent / "parts_library.blend"
_pdt_obj_items.clear()
if path.is_file():
with bpy.data.libraries.load(str(path)) as (data_from, data_to):