Doc:2.6/Manual/Extensions/Python/Properties
目次
Properties, ID-Properties and their differences
There are several ways to define custom properties in Blender using Python. One of them is to add a new property to an existing datablock. Another is to define a new ID-property for a datablock. This might seem to be the same, but there are some differences. So when to use what?
This document assumes that the reader has a basic knowledge of Python and Blender. Any code examples can be tested successfully in the default scene. Any user-interface elements that are created will be displayed in the properties sidebar of the 3d-view (toggle with N-key). As they show up at the bottom of this bar, it might be useful to collapse the panels above it (like Transform, View, etc.)
Properties
Basics
By default these are the normal properties that Blender itself uses. For instance, an object has a location property. This is a vector which contains information on the location of the object. This property can be both read and written. Other properties, like for instance users are read-only.
import bpy
ob = bpy.context.active_object # get the active object
print("Location:", ob.location) # print its location
ob.location = [1,1,1] # set it to a new location
print("Users:", ob.users) # print its number of users
#ob.users = 3 # this will raise an error
Custom properties
You can also add a new property yourself. The first thing you need to decide is the property type: a boolean, float, string, etc. Here is the full list. Simply add the new property (a string in this case) by using:
bpy.types.Object.myProperty = bpy.props.StringProperty()
This adds the property myProperty to all objects.
Once you've added the property you can interact with it just like it were any other normal property.
ob = bpy.context.active_object # get the active object
bpy.types.Object.foo = bpy.props.StringProperty() # add a new property, called "foo"
ob.foo = "bar" # assign a value to the property
print("foo:", ob.foo) # prints "foo: bar"
print("foo:", ob['foo']) # also prints "foo: bar"
class myPanel(bpy.types.Panel): # panel to display new property
bl_space_type = "VIEW_3D" # show up in: 3d-window
bl_region_type = "UI" # show up in: properties panel
bl_label = "My Panel" # name of the new panel
def draw(self, context):
# display value of "foo", of the active object
self.layout.prop(bpy.context.active_object, "foo")
bpy.utils.register_class(myPanel) # register panel
Spaces
If you have spaces in your property name you can't call it with ob.foo barUse getattr(ob, "foo bar") instead
One thing to keep in mind is that after saving and reloading your blend-file, your new property will have disappeared. So print(ob.foo) won't work anymore. Any object that had a value assigned to the property will still have it available though, but only as an ID-property. For more information on those, look further down this page.
To make the values you assigned in a previous session available again, you need to redefine the property. So after reopening the blend-file you need to rerun:
bpy.types.Object.myProperty = bpy.props.StringProperty()
This will also reassign the values to the property that were assigned to it in a previous session (they are retrieved from the corresponding ID-properties), so you don't need to reassign those. print(ob.foo) will still return bar
Dynamic custom properties
The previous method will work fine for cases in which you wish to store static information on certain datablocks, but won't be of much help when you want the property to be dynamic. In that case you need to assign a function to the property. The way to do this is to use python's built-in property() function. For example:
bpy.types.Lamp.foo = property(bar)
A further difference is that dynamic custom properties aren't stored when saving a blend-file. Not even as an ID-property.
Below is a full example of creating a new dynamic property, called distance
import bpy #needed in a script-text-window!
def fget(self): # custom get function
"""Distance from origin""" # description of property
loc = self.location # location of object
distance = loc.length # distance from origin
return distance # return value
def fset(self, value): # custom set function
if self.location.length < 1E-6: # if object is at origin
self.location = [1, 0, 0] # direction to move in
self.location.length = value # set distance from origin
bpy.types.Object.distance = property(fget, fset)# assign function to property
ob = bpy.context.active_object # get the active object
print(ob.distance) # print distance to the console
ob.distance = 2 # set the distance
class myPanel(bpy.types.Panel): # panel to display new property
bl_space_type = "VIEW_3D" # show up in: 3d-window
bl_region_type = "UI" # show up in: properties panel
bl_label = "My Panel" # name of the new panel
def draw(self, context):
# display "distance" of the active object
self.layout.label(text=str(bpy.context.active_object.distance))
bpy.utils.register_class(myPanel) # register panel
To have your property available across different blend-files (even after saving and reloading), you'll have to add your property in ../.blender/scripts/modules/bpy_types.py (where .. is the folder in which blender is installed).
Find the class to which you wish to add your property and simply add the functions that define your property. There are two small differences, concerning the syntax. The first one is the use of a decorator. The second one (resulting from this) is that the function name is used as the property name.
Here's the same example as above, but now added to bpy_types.py
@property # decorator
def distance(self): # attribute name
"""Distance from origin"""
loc = self.location
distance = loc.length
return distance
@distance.setter # decorator
def distance(self, value):
if self.location.length < 1E-6:
self.location = [1, 0, 0]
self.location.length = value
ID-Properties
Basics
ID-properties are properties that are assigned to individual datablocks. And not just any datablock, but only datablocks that are subclassed from the ID-type. Examples are Lamps, Meshes, Objects and WindowManagers. Look at the full list for more information about ID subclasses.
Creating a new ID-property is very easy:
ob = bpy.context.active_object # get the active object
ob["foo"] = "bar" # create and assign an ID-property
print(ob["foo"]) # prints "bar"
# print(ob.foo) # won't work, as foo isn't an attribute
print(ob.items()) # prints all ID-properties of ob
Creating the ID-property and assigning it a value is all done at the same time. The ID-properties are stored as a dictionary on the datablock.
Besides the types of datablocks they can be assigned to, ID-properties have another limitation to be aware of. You can only assign strings, integers, floats, and lists to an ID-property. And the lists are limited, because they may only contain floats and integers.
Referencing
ID-properties have the advantages that they are stored in a blend-file when it is saved, and that they are easy to keep track of. The main reason they are easy to track, is that by default they are shown in the user interface. They show up in the appropriate Custom Properties panel. So if you add an ID-property to a lamp, it will show up in the Custom Properties panel in the Lamp window. This makes it very easy to change their values, even for normal users. This can also be used to add new ID-properties, without having to use python.
To display an ID-property using UILayout.prop(), you can't simply pass "foo" as the property, but you need to pass a one-item list as a string. So '["foo"]'
class myPanel(bpy.types.Panel): # panel to display new property
bl_space_type = "VIEW_3D" # show up in: 3d-window
bl_region_type = "UI" # show up in: properties panel
bl_label = "My Panel" # name of the new panel
def draw(self, context):
# display "foo" ID-property, of the active object
self.layout.prop(bpy.context.active_object, '["foo"]')
bpy.utils.register_class(myPanel) # register panel
Note that this won't work if the active object hasn't got the ID-property, so make sure that you have a fallback in your code instead of raising an error.
So far ID-properties were pretty straightforward, but it gets a bit more tricky when you wish to set parameters for the ID-property. For instance the minimum and maximum value of an integer. These parameters are stored in a special property called _RNA_UI It can be accessed like any other ID-property and is a dictionary with the other ID-properties as key and their parameters as values.
ob = bpy.context.active_object # get the active object
ob["foo"] = 7 # create the ID-property
ob["_RNA_UI"] = {"foo": {"min":3, "max":42}} # set parameters
To keep this example script short it simply overwrites _RNA_UI and thus erases any already existing parameters. In normal scripts it's better to insert a new key for your new parameters.
Summary
Property (static) |
Property (dynamic) |
ID-Property | |
---|---|---|---|
Creation | bpy.types.Object.foo = bpy.props.StringProperty() | bpy.types.Object.foo = property(bar) | ob["foo"] = "bar" |
Declared for | all datablocks of same type | all datablocks of same type | single datablock |
Assigning values | ob.foo = "bar" | ob.foo = "bar" | ob["foo"] = "bar" |
Accepted values | Any property | Functions | Floats, integers, lists*, strings |
Stored in .blend | Yes** | No | Yes |
* List may only contain floats and integers.
** Datablocks that have a value assigned to the property, save that value as an ID-property.