Dev:Py/Scripts/Cookbook/Code snippets/Properties

提供: wiki
移動先: 案内検索

Properties

RNA properties versus ID properties

In Blender there are two different types of properties: ID properties and RNA properties. An RNA property extends the definition of a given data structure. It must be declared before it is used.

bpy.types.Object.myRnaInt = bpy.props.IntProperty(
    name = "RNA int",
    min = -100,
    max = 100,
    default = 33)

Once declared, RNA properties are accessed with the dot syntax:

cube.myRnaInt = -99

Since the declaration of the RNA property myRnaInt extends the definition of the Object data structure, every object will have this property.

An ID property is added to a single datablock, without affecting other data of the same type. It does not need any prior declaration, but is automatically defined when it is set, e.g

cube.data["MyIdInt"] = 4711

ID properties can only be integers, floats, and strings; other types will be automatically converted. Hence the line

cube.data["MyIdBool"] = True

defines an integer ID property and not a boolean.

Properties are stored in the blend file, but property declarations are not.

Here is a script wich creates three meshes, assigns various properties and prints their values to the console.

#----------------------------------------------------------
# File properties.py
#----------------------------------------------------------
import bpy
from bpy.props import *

# Clean the scene and create some objects
bpy.ops.object.select_by_type(type='MESH')
bpy.ops.object.delete()
bpy.ops.mesh.primitive_cube_add(location=(-3,0,0))
cube = bpy.context.object
bpy.ops.mesh.primitive_cylinder_add(location=(0,0,0))
cyl = bpy.context.object
bpy.ops.mesh.primitive_uv_sphere_add(location=(3,0,0))
sphere = bpy.context.object

# Define RNA props for every object
bpy.types.Object.myRnaInt = IntProperty(
    name = "RNA int", 
    min = -100, max = 100,
    default = 33)

bpy.types.Object.myRnaFloat = FloatProperty(
    name = "RNA float", 
    default = 12.345,
    min = 1, max = 20)

bpy.types.Object.myRnaString = StringProperty(
    name = "RNA string", 
    default = "Ribonucleic acid")

bpy.types.Object.myRnaBool = BoolProperty(
    name = "RNA bool")

bpy.types.Object.myRnaEnum = EnumProperty(
    items = [('one', 'eins', 'un'), 
            ('two', 'zwei', 'deux'), 
            ('three', 'drei', 'trois')],
    name = "RNA enum")

# Set the cube's RNA props
cube.myRnaInt = -99
cube.myRnaFloat = -1
cube.myRnaString = "I am an RNA prop"
cube.myRnaBool = True
cube.myRnaEnum = 'three'

# Create ID props fore cube mesh by setting them.
cube.data["MyIdInt"] = 4711
cube.data["MyIdFloat"] = 666.777
cube.data["MyIdString"] = "I am an ID prop"
cube.data["MyIdBool"] = True

# Print all properties
def printProp(rna, path):
    try:
        print('    %s%s =' % (rna.name, path), eval("rna"+path))
    except:
        print('    %s%s does not exist' % (rna.name, path))

for ob in [cube, cyl, sphere]:
    print("%s RNA properties" % ob)
    printProp(ob, ".myRnaInt")
    printProp(ob, ".myRnaFloat")
    printProp(ob, ".myRnaString")
    printProp(ob, ".myRnaBool")
    printProp(ob, ".myRnaEnum")
    print("%s ID properties" % ob.data)
    printProp(ob.data, '["MyIdInt"]')
    printProp(ob.data, '["MyIdFloat"]')
    printProp(ob.data, '["MyIdString"]')
    printProp(ob.data, '["MyIdBool"]')

The script print the following output to the console:

<bpy_struct, Object("Cube")> RNA properties
    Cube.myRnaInt = -99
    Cube.myRnaFloat = 1.0
    Cube.myRnaString = I am an RNA prop
    Cube.myRnaBool = True
    Cube.myRnaEnum = three
<bpy_struct, Mesh("Cube.001")> ID properties
    Cube.001["MyIdInt"] = 4711
    Cube.001["MyIdFloat"] = 666.777
    Cube.001["MyIdString"] = I am an ID prop
    Cube.001["MyIdBool"] = 1
<bpy_struct, Object("Cylinder")> RNA properties
    Cylinder.myRnaInt = 33
    Cylinder.myRnaFloat = 12.345000267028809
    Cylinder.myRnaString = Ribonucleic acid
    Cylinder.myRnaBool = False
    Cylinder.myRnaEnum = one
<bpy_struct, Mesh("Cylinder")> ID properties
    Cylinder["MyIdInt"] does not exist
    Cylinder["MyIdFloat"] does not exist
    Cylinder["MyIdString"] does not exist
    Cylinder["MyIdBool"] does not exist
<bpy_struct, Object("Sphere")> RNA properties
    Sphere.myRnaInt = 33
    Sphere.myRnaFloat = 12.345000267028809
    Sphere.myRnaString = Ribonucleic acid
    Sphere.myRnaBool = False
    Sphere.myRnaEnum = one
<bpy_struct, Mesh("Sphere")> ID properties
    Sphere["MyIdInt"] does not exist
    Sphere["MyIdFloat"] does not exist
    Sphere["MyIdString"] does not exist
    Sphere["MyIdBool"] does not exist

Code Snippets PropsCube.png All three objects have RNA properties, because they are an extension of the Object datatype. The cube's RNA properties have the values assigned by the program, except the myRnaFloat value which is not allowed to be smaller than 1. No properties were set for the cylinder and the sphere, but they still have RNA properties with default values.

The cube mesh has ID properties set by the program. Note that the MyIdBool property is the integer 1 rather than the boolean True.

The object properties are displayed in the in the UI panel under Properties, and also in the object context. The mesh properties be found in the mesh context.


Code Snippets PropsSphere.png As we saw in the printout, we can access the sphere object's RNA properties. However, they do not show up in user interface. Apparently only set properties are stored in the Object data block. We can use an RNA property that has not been set in a script; it then takes the default value. In contrast, a error is raised if we try to access a non-set ID property.


Code Snippets PropsLinkedCube.png Properties are compatible with file linking. Save the blend file and link the cube into a new file. Both the RNA and ID properties appear in the new file, but they are greyed out because they can not be accessed in the linking file.


Code Snippets PropsProxyCube.png If we proxify the linked cube, the object properties belong to the proxy object datablock, and can be modified in the linking file. In contrast, the mesh properties belong to the mesh datablock and can not be changed.


Code Snippets PropsBlenderRestarted.png As mentioned above, properties are stored in the blend files but property declarations are not. Quit and restart Blender and open the file which we save above. The myRnaBool and myRnaEnum properties have been converted to integers. They were in fact stored as integers all the time, but were displayed as booleans and enums due to the property declarations stored in the Object data type.

To confirm that the RNA properties have turned into ID properties, execute the following script.

#----------------------------------------------------------
# File print_props.py
#----------------------------------------------------------
import bpy

def printProp(rna, path):
    try:
        print('    %s%s =' % (rna.name, path), eval("rna"+path))
    except:
        print('    %s%s does not exist' % (rna.name, path))

ob = bpy.context.object
print("%s RNA properties" % ob)
printProp(ob, ".myRnaInt")
printProp(ob, ".myRnaFloat")
printProp(ob, ".myRnaString")
printProp(ob, ".myRnaBool")
printProp(ob, ".myRnaEnum")
print("%s ID properties" % ob)
printProp(ob, '["myRnaInt"]')
printProp(ob, '["myRnaFloat"]')
printProp(ob, '["myRnaString"]')
printProp(ob, '["myRnaBool"]')
printProp(ob, '["myRnaEnum"]')
print("%s ID properties" % ob.data)
printProp(ob.data, '["MyIdInt"]')
printProp(ob.data, '["MyIdFloat"]')
printProp(ob.data, '["MyIdString"]')
printProp(ob.data, '["MyIdBool"]')

This script prints the following text to the terminal.

 RNA properties
    Cube.myRnaInt does not exist
    Cube.myRnaFloat does not exist
    Cube.myRnaString does not exist
    Cube.myRnaBool does not exist
    Cube.myRnaEnum does not exist
<bpy_struct, Object("Cube")> ID properties
    Cube["myRnaInt"] = -99
    Cube["myRnaFloat"] = 1.0
    Cube["myRnaString"] = I am an RNA prop
    Cube["myRnaBool"] = 1
    Cube["myRnaEnum"] = 2
<bpy_struct, Mesh("Cube.001")> ID properties
    Cube.001["MyIdInt"] = 4711
    Cube.001["MyIdFloat"] = 666.777
    Cube.001["MyIdString"] = I am an ID prop
    Cube.001["MyIdBool"] = 1

If we restore the property declarations, the ID properties are converted into RNA properties again.

Bone roll

This program expects that the active object is an armature. It stores the roll angle of each editbone as a property of the corresponding bone, and finally prints the property values in the terminal. When executed with the armature in the picture selected, the terminal output is as follows.

    Head    3.1416
    Arm_L   1.5708
    Leg_R  -2.7646
    Leg_L   2.7646
    Arm_R  -1.5708
    Torso   3.1416

Note that the property values are in radians. Angles are displayed in the viewport in degrees, but when accessed from Python they are expressed in radians. However, the Roll property is just some float property, and Blender does not know that is supposed to be an angle.

To find the property in the user interface, we need to select the bone in pose mode and then toggle into edit mode, as shown in the picture. Code Snippets BoneRoll.png This code is actually somewhat useful for a script that retargets motion capture data. In order to do so properly, we need to know the roll angles. However, they can not be found if the armature has been linked into another file and proxified. To access the roll angle rig.data.edit_bones[name].roll, the armature must be toggled into edit mode, which is not possible with linked assets. But if the script has been executed in the file where the armature is defined, the Roll property can be accessed from the linking file as rig.pose.bones[name].bone["Roll"].

#----------------------------------------------------------
# File bone_roll.py
#----------------------------------------------------------
import bpy

def createBoneRollProps(rig):
    if rig.type != 'ARMATURE':
        raise NameError("Object not an armature")
    bpy.context.scene.objects.active = rig
    try:
        bpy.ops.object.mode_set(mode='EDIT')    
        editable = (len(rig.data.edit_bones) > 0)
    except:
        editable = False

    rolls = {}
    if editable:
        for eb in rig.data.edit_bones:
            rolls[eb.name] = eb.roll
        bpy.ops.object.mode_set(mode='POSE')    
        for pb in rig.pose.bones:
            pb.bone["Roll"] = rolls[pb.name]
    else:
        try:
            bpy.ops.object.mode_set(mode='POSE')    
        except:
            raise NameError("Armature is not posable. Create proxy")
        for pb in rig.pose.bones:
            try:
                rolls[pb.name] = pb.bone["Roll"]
            except:
                raise NameError("Create roll props in asset file")
    return rolls

rolls = createBoneRollProps(bpy.context.object)
for (bname, roll) in rolls.items():
        print("  %16s %8.4f" % (bname, roll))