11
$\begingroup$

With bpy.props I can assign bool, int, float and string to a custom property. But how can I assign a datablock (bpy.types.ID) object (lets say a material bpy.types.material) to a custom property?

Background

I create >1000 curve objects with a python script. For each curve I would like to store a list of materials (from other objects that these curve interacted with during creation). Later on I want to manipulate the curves depending on those materials with a script running on bpy.app.handler.frame_change_pre.

$\endgroup$
9
$\begingroup$

You can't. See the wiki page: Properties, ID-Properties and their differences.

+--------------------------------------------------------------------------------------+
|           |             Property             |        Property        | ID-Property  |
|           |             (static)             |       (dynamic)        |              |
|-----------+----------------------------------+------------------------+--------------|
| Creation  |      bpy.types.Object.foo =      | bpy.types.Object.foo = | ob["foo"] =  |
|           |    bpy.props.StringProperty()    |     property(bar)      |    "bar"     |
|-----------+----------------------------------+------------------------+--------------|
| Declared  |   all datablocks of same type    | all datablocks of same |    single    |
|    for    |                                  |          type          |  datablock   |
|-----------+----------------------------------+------------------------+--------------|
| Assigning |          ob.foo = "bar"          |     ob.foo = "bar"     | ob["foo"] =  |
|  values   |                                  |                        |    "bar"     |
|-----------+----------------------------------+------------------------+--------------|
|           |                                  |                        |   Floats,    |
| Accepted  |           Any property           |       Functions        |  integers,   |
|  values   |                                  |                        |   lists*,    |
|           |                                  |                        |   strings    |
|-----------+----------------------------------+------------------------+--------------|
| Stored in |              Yes**               |           No           |     Yes      |
|  .blend   |                                  |                        |              |
+--------------------------------------------------------------------------------------+

* List may only contain floats and integers.
** Datablocks that have a value assigned to the property, save that value as an ID-property.

You could instead store a pickled list of material names, e.g.

import pickle
import bpy

def fget(self):
    try:
        for name in pickle.loads(self["_interaction_materials"]):
            yield bpy.data.materials[name]
    except KeyError:
        raise StopIteration

def fset(self, materials):
    self["_interaction_materials"] = pickle.dumps([m.name for m in materials])

bpy.types.Object.interaction_materials = property(fget, fset)
$\endgroup$
  • $\begingroup$ Your link seems to be dead $\endgroup$ – Mörkö Apr 17 '18 at 9:17
5
$\begingroup$

You can store materials' names, and then, when you need to use it - search for materials by name.

Of course this metod requires you to be strict about your file. You must not delete any materials by accident or change names.

Let's assume you have a list of materials:

materials = [bpy.data.materials["Marble"], bpy.data.materials["Glass"]]
# To get materials from object, use:
#bpy.context.object.material_slots[0].material
# or from the material list if you don't care about slots:
#bpy.context.object.data.materials[0]

Then you can write them into a Custom Property, separated with colons:

materialNames = []
for mat in materials:
    materialNames.append(mat.name)

materialNames are now ["Marble", "Glass"]

materialString = ",".join(materialNames)

materialString is now "Marble,Glass".

You can save it to object's property:

bpy.context.object["materialString"] = materialString

And in other place, to get a real Blender material:

materialNames = bpy.context.object["materialString"].split( "," )
realMaterials = []
for matName in materialNames:
    realMat = bpy.data.materials[matName]
    realMaterials.append(realMat)

# check the results
# print(realMaterial[0].name)

First entry in realMaterials list now again points to bpy.data.materials["Marble"]

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.