I’ve done some searching here and on Google and haven’t found anything, I apologize if this has already been asked.
I have an Empty named “test” with a Custom Property, type Python. I want to store an array in this property and load the array when I open the file.
My array is called “light_rot_array” and my custom property is called “light_rot”, the code that assigns it is this line: bpy.data.objects["test"]["light_rot"] = g.light_rot_array
This kind of works- it does save an array to the custom property, but the value of the custom property looks like this: [<bpy id property array [3]>, <bpy id property array [3]>, <bpy id property array [3]>]
So it’s definitely not what I’m looking for. That array should look more like a 2D array of numbers. I know it’s something super simple I’m missing, I just don’t know what it is. Thanks for the help!
I think what’s happening here is that Blender automatically converts the values that you assign to custom properties into native blender types. It’s just that for most of those types (e.g. floats, strings, booleans, etc.), they are automatically converted back into python types when you access them.
You can actually get the original blender representation of any property by using the .path_resolve(coerce=False) method:
Which will return a reference that shows <bpy_int, CyclesRenderSettings.preview_samples> when we convert it to a string using str(). (Make sure to set coerce to False in order to stop blender from converting it to a native python type)
The difference with arrays is that they cannot be added like normal (e.g. not using prop: ArrayProperty()), and cannot be drawn in the UI. As such, I think they’ve been a bit neglected, and they don’t return a native python list, instead keeping it as the blender <bpy id property array> type.
As such, you need to convert them to lists manually, either by using list(bpy_array), or by using the built-in bpy_array.to_list() method.
As a disclaimer, I don’t really know any of this for certain, and it’s just what I’ve kind of guessed by using the API a lot, and hopefully it answers your question.
I tried using list() both when setting the custom property and getting from it. It didn’t seem to have an effect, I’m still getting [<bpy id property array [3]>].
I’m going to try the bpy_array.to_list() now, where do I import bpy_array from? I tried it and it gives me an error: bpy_array is not defined. I tried bpy.bpy_array but that’s not it
For sure. That’s my plan if I can’t get it working this way, is write it to JSON and save it to an external file. That’s super easy. I just would rather keep the data in Blender if possible
Ah, I should have been a bit clearer, you not only have one bpy_array, but a bunch of nested ones since you are storing vectors, aka, it is stored as a bpy_array containing lots of other bpy_arrays, and so you also have to convert those to python types as well, using something like this:
my_list = [array.to_list() for array in obj["light_rot"].to_list()]
This will not only convert the top array to a python list, but also the sub lists (aka the individual light rotation vectors)
Also, when I say bpy_array, what I really mean is the blender type that lists are converted to when they are stored. So you don’t have to import any module to use it, the to_list method is on every <bpy id property array> already.
OH that makes more sense, thank you! That looks like it will work, I will try it right now
You make a great point, I could just store the data as a string, since you can have a string custom property. My one hangup is that it would take an extra step to convert the string back to an array, and I’m trying to minimize overhead since this custom property is going to be updated fairly often.
So I tried [array.to_list() for array in bpy.data.objects["test"]["light_rot"].to_list()] and I’m getting an error:
AttributeError: ‘list’ object has no attribute ‘to_list’
It seems like Blender does think it’s a list instead of a bpy_array, which is annoying
I think I’m just going to with JSON, since it seems like Blender’s list comprehension is not very good. Thank you for your help though @Strike_Digital , I really appreciate it
I got it working with JSON, it’s a little weird going list > string > JSON > string for storage, but it gets the job done. Thanks again for your help!!
[array.to_list() for array in bpy.data.objects["test"]["light_rot"]]
Should work like you want.
The JSON idea is pretty smart as well! You could probably simplify the whole process by just using str(light_rot_list), which should be a bit faster than formatting it with JSON.
You can then use eval(the_string) to get the list back at the other end.
The “officially supported” way to store arrays is with collection properties. You can tweak the values in the interface, and it should support undo / redo without a sweat.
import bpy
class VectorPropertyGroup(bpy.types.PropertyGroup):
value: bpy.props.FloatVectorProperty(size=3)
class HelloWorldPanel(bpy.types.Panel):
bl_label = "Hello World Panel"
bl_idname = "OBJECT_PT_hello"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "scene"
def draw(self, context):
layout = self.layout
for entry in context.scene.my_vector_list:
row = layout.row(align=True)
row.label(text=entry.name)
row.prop(entry, "value", text="")
def register():
bpy.utils.register_class(HelloWorldPanel)
bpy.utils.register_class(VectorPropertyGroup)
bpy.types.Scene.my_vector_list = bpy.props.CollectionProperty(type=VectorPropertyGroup)
def unregister():
bpy.utils.unregister_class(HelloWorldPanel)
bpy.utils.register_class(VectorPropertyGroup)
del bpy.types.Scene.my_vector_list
if __name__ == "__main__":
register()
bpy.context.scene.my_vector_list.add()
bpy.context.scene.my_vector_list[-1].name = "My Awesome first value"
bpy.context.scene.my_vector_list[-1].value = (2, 0, 3)
bpy.context.scene.my_vector_list.add()
bpy.context.scene.my_vector_list[-1].name = "My Awesome second value"
bpy.context.scene.my_vector_list[-1].value = (-1, 5, 2)
bpy.context.scene.my_vector_list.add()
bpy.context.scene.my_vector_list[-1].name = "My Awesome third value"
bpy.context.scene.my_vector_list[-1].value = (3, 0, 7)
Thank you! This is another excellent option! I’m a bit old-fashioned and I really love the ease of JSON- especially if I want to export it to an CSV- but I do like the ease of editing your solution offers. I may at some point convert my pure JSON solution to use your UI. Right now the UI is on the backburner, as I have a lot of horribly complicated math to figure out first
I needed to store an python list with colours in the objects and this was my implementation.
In case it would be useful to anyone. This information is stored with the objects in the blender file, so it is persistent.
from bpy.utils import (
register_class,
unregister_class
)
class Colors(bpy.types.PropertyGroup):
color: bpy.props.FloatVectorProperty(
name='Color',
size=4,
precision=3,
subtype='COLOR',
min=0.0,
max=1.0
)
class ColorStack(bpy.types.PropertyGroup):
name = 'ColosStack'
# usage:
# C.object.color_stack.add_color([1, 1, 1, 1])
# C.object.color_stack.add_color([2, 2, 2, 1])
# C.object.color_stack.add_color([3, 3, 3, 1])
# C.object.color_stack.get_color(0)
# C.object.color_stack.get_last_color()
# C.object.color_stack.get_all_colors()
colors: bpy.props.CollectionProperty(type=Colors)
def add_color(self, a_c):
if a_c != self.get_last_color():
_item = self.colors.add()
_item.color = a_c
def get_len_colors(self):
return len(self.colors)
def is_void(self):
if self.get_len_colors() == 0:
return True
return False
def overwrite_first_color(self, color: list):
if self.get_len_colors() > 0:
self.colors[0].color = color
def get_color(self, index: int):
if self.get_len_colors() > 0:
color = self.colors[index].color
return list(color)
return None
def get_last_color(self):
if self.get_len_colors() > 0:
last_color = self.colors[-1].color
return list(last_color)
return None
def get_first_color(self):
if self.get_len_colors() > 0:
first_color = self.colors[0].color
return list(first_color)
return None
def get_all_colors(self):
if self.get_len_colors() > 0:
tmp_a = []
for col in self.colors:
color = list(col.color)
tmp_a.append(color)
return list(tmp_a)
return None
def rm_color(self, color: list):
if self.is_void():
return None
colors = self.get_all_colors()
if color not in colors:
return None
idx = colors.index(color)
self.colors.remove(idx)
def rm_by_index_color(self, index: int):
if self.is_void():
return None
self.colors.remove(index)
def rm_last_color(self):
if self.is_void():
return None
self.colors.remove(len(self.colors)-1)
def register():
register_class(Colors)
register_class(ColorStack)
bpy.types.Object.color_data = bpy.props.CollectionProperty(type=Colors)
bpy.types.Object.color_stack = bpy.props.PointerProperty(type=ColorStack)
def unregister():
del bpy.types.Object.color_data
del bpy.types.Object.color_stack
unregister_class(Colors)
unregister_class(ColorStack)