How to Proceed: To Module or not to Module?

I’m less lost in Blender Python than I was a week ago, but I believe I still have some way to go before I can write the tool I have in mind.

I think I need to write a Blender Python module, but I’m not 100% sure. A straight script seems to be too limited in that the data can’t really be stored in a central container of some kind where it would be accessible to a number of different operators. Also, with the re-execute-on-redraw approach to Blender’s UI, I don’t see how data can be maintained over a period of time during which the user is interacting with the tool.

So, I’d like to outline what I’m aiming for with an eye toward nailing down what I need to know to write it. If someone would then be so kind as to advise me, perhaps I can get started in the right direction. I’ve written a list of questions at the end which may or may not indicate just how far off-base I am at the moment.

The working title for this tool is Tweener Onionskin Poser. It’s inspired by Brian Horgan’s bhGhost for Maya. The purpose of the tool is to help animators adjust the timing/spacing of poses.

User POV

  • This tool will work on any polygonal object or group of objects, with or without an armature. I’ll call this a pose-mesh set.

  • One or more polygonal objects are selected, effectively choosing those objects to be reproduced for each onionskin/ghost pose. This limits the onionskin/ghost poses to just those polygonal objects. For instance, if the user has a rigged single-mesh character with separate objects for hair, eyes, and maybe a belt (none of which are being animated in that particular pass) the body/clothing mesh(es) can be added to the pose-mesh set while the eyes, hair, etc. are not.

  • After setting two or more keyframes, the user will be able to move the Timeline cursor to any frame between the two keyframes, click the “Add Onionskin Pose” button and an onionskin/ghost pose will be created at that frame. The frame where the onionskin pose is created doesn’t need to have any keys set.

  • Onionskin/ghost poses can have several different looks:
    [LIST]

  • halo (vertices only)

  • wireframe

  • transparent faces

  • wireframe on transparent faces

  • solid

  • The subdivision level can be controlled from a single slider control for both the onionskin/ghost objects as well as the original objects allowing for ‘gross’ posing as well as finer detailed work.
    [/LIST]

User Controls (control names are preliminary)

  • Prep button: Select one or more mesh objects, then click to make them a pose-mesh set.

  • Add button: Select one or more mesh objects, then click to add these to an already-existing pose-mesh set. These mesh objects will then be immeditately added to any existing onionskin/ghost poses.

  • Remove button: Select one or more mesh objects already in the pose-mesh set and click to remove them from the pose-mesh set. These mesh objects will then be removed from any existing onionskin/ghost poses.

  • Subsurf slider: Sets the subsurf modifier level from 0 to 3 for all mesh objects in the pose-mesh set as well as all objects in existing onionskin/ghost poses.

  • Appearance drop-down: Allows the user to select between:
    [LIST]

  • halo

  • wireframe

  • transparent, or

  • wireframe on transparent

  • Transparency slider: allows setting the transparency on all onionskin/ghost poses simultaneously from invisible to solid
    [/LIST]

The Code
Coding all these things individually in Blender Python is relatively straightforward:

  • build a list or dictionary of pose-frames recording (for each pose-frame) the frame number and a list of objects created for that pose-frame.

  • copying objects and making them non-selectable is a no-brainer

  • subtracting from the list of objects to be onionskinned/ghosted is fairly easy. All this entails is looking up each onionskin/ghost pose, finding the object(s) in question and deleting them.

  • adding to the list of objects is also relatively easy. For each onionskin/ghost pose, find its frame number, make a copy of the new mesh objects, freeze them (I’ll talk more about that in a moment) and add them/it to the list of objects for that pose-frame.

  • Standardizing the subsurf modifier settings for all objects (original and poses) is pretty simple. When the user selects the objects to be onionskinned/ghosted, the list of modifiers for each object is checked, all Subsurf modifiers are deleted and a single Subsurf modidifier is added back in. This is done this way for two reasons:
    [LIST]

  • it’s far faster than checking for multiple Subsurfs, finding out if they all have the name ‘Subsurf’ and renaming them if they don’t

  • all Subsurfs having the same name makes adjusting them far easier and their names don’t have to be tracked

  • All appearance variables (halo, wire, wire on transparent, etc.) are a combination of Object > Display settings and Material settings.

  • ‘freezing’ objects is simple. In the case of an object without a parent, just copying it will do. If the object has a parent, just unparent it using: bpy.ops.object.parent_clear(type=‘CLEAR_KEEP_TRANSFORM’)
    [/LIST]

The big show-stopper for me is I don’t know how to make the data for a list of objects, a list of pose-frames and their data persistent from redraw to redraw and through saving/closing/opening the file in Blender. My understanding from reading the API and related material in the wiki is that ordinary Operator classes are run each time there’s a redraw.

Also, from the coding experiments I’ve written so far, there doesn’t seem to be a way to instantiate an operator class in such a way that I can instantiate a class containing the list/dictionary and still be able to manipulate that same instance to add/remove objects to/from the list and keep a list of frames for the poses. From what I can tell, standard OOP programming practices don’t apply 100% in BP. Or maybe they don’t apply with any embedded scripting language or they do apply, but I’m just not seeing it; I don’t know.

So, here is my list of questions:

  • Do I need to create a module for this or can it be done with just a more run-of-the-mill addon script?
  • If this does need to be a module, are there any barebones module templates I can look at?
  • Again, assuming this needs to be a module: I’ve read everything in the wiki to do with modules, but I’m not finding enough information applicable to this situation. Is there a tutorial on how to write BP modules that goes into more detail?
  • If it can be done more simply than writing a module: Is there a way to store the data so it can be manipulated and survive redraw and saving/reloading the .blend file?
  • Or am I totally misunderstanding how BP and addons written with it work?

(whew) I think that’s it. If anything is unclear, please let me know.

EDIT: This is the wrong code example. Please see below for the correct one. END EDIT

Okay, I’m half-way there. I’m still unsure of how to preserve the data through a save/reload operation. This is a simple example of what I have so far:

import bpy

class my_increment(bpy.types.Object):
    my_variable = 0

    def increment(self):
        self.my_variable += 1

    def reset(self):
        self.my_variable = 0

mine = my_increment("Mine")

class View3D_UI_PT_Panel(bpy.types.Panel):
    bl_label = "Hello Panel" # the label identifying the UI panel
    bl_space_type = "VIEW_3D"   # the window where the panel will appear and...
    bl_region_type = "UI"       # the sub-window (sidebar in this case)

    def draw(self, context):
        layout = self.layout
        column = layout.column()
        row = column.row(align = True)
        row.operator('behaviour.increment')
        row.operator('behaviour.reset')


class OBJECT_OT_Increment(bpy.types.Operator):
    bl_idname = "behaviour.increment"
    bl_label = "Increment"

    def execute(self, context):
        mine.increment()
        print("variable is now", mine.my_variable)
        return{'FINISHED'}

class OBJECT_OT_Reset(bpy.types.Operator):
    bl_idname = "behaviour.reset"
    bl_label = "Reset"

    def execute(self, context):
        mine.reset()
        print("variable is now", mine.my_variable)
        return{'FINISHED'}


def register():
    bpy.utils.register_class(View3D_UI_PT_Panel)
    bpy.utils.register_class(OBJECT_OT_Increment)
    bpy.utils.register_class(OBJECT_OT_Reset)


def unregister():
    bpy.utils.unregister_class(View3D_UI_PT_Panel)
    bpy.utils.unregister_class(OBJECT_OT_Increment)
    bpy.utils.unregister_class(OBJECT_OT_Reset)


if __name__ == "__main__":
    register()


you may use any property type available, Int, Float, Bool, String, Collection. You can store them in bpy.types.Scene.some_name, but preferably using a PointerProperty, so that all of your props are available under that name, like:

bpy.context.scene.some_name.your_first_prop
bpy.context.scene.some_name.your_second_prop

without “some_name.”, it would get messy and may give you name conflicts.

I like your idea, the motion paths are not very convenient, onion skinning for non-armature stuff would rock! But this is rather something to be c-coded…

i dont know if you know this, its a bit hidden.
if you animate and select a pose bone with >1 keyframe, there is a Ghost Panel in the Properties Editor -> Object Data.

  • Range to 8.
    i think this only works for pose bones.

source: scripts\startup\bl_ui\properties_armature_data.py
class DATA_PT_ghost(ArmatureButtonsPanel, Panel):
class DATA_PT_onion_skinning(OnionSkinButtonsPanel):
source: scripts\startup\bl_ui\properties_animviz.py
class OnionSkinButtonsPanel():

onion skinning data doesnt need to be saved, the drawing data can be generated from the bone keyframes.
the user input settings do need to be saved, transparency, range, step.

i think most the ghost/onion code is written in C.
by the way, the generic mouse and keyboard input system is also called Ghost.

this is some of the draw code:
source: /source/blender/editors/space_view3d/drawarmature.c
draw_ghost_poses_range(…)

Thanks CoDEmanX.

I was reading about the PointerProperty, but I admit I don’t understand it or how to use it. The docs say a PointerProperty is an “RNA pointer property to point to another RNA struct” but since the words ‘pointer’ and ‘property’ are used in both the name and the definition, to me it’s kind of like saying a mobile phone is a phone that’s mobile. If you don’t know what a phone is (or what mobile looks like), you’re not very enlightened. :slight_smile:

What I do understand about the definition (I think):

  • what a pointer is (same as in C, right? Since everything in Python is a pointer, I think this is right)
  • struct (a C structure, part of the underlying stuff you can’t access directly through Python, but you can pretend to access it by using… [wait for it]… a PointerProperty!)
  • property (similar to, maybe even exactly like, a Python attribute; in other words, a variable of some type that’s part of another construct such as a list or a class/object)

What I don’t understand:

  • RNA (based on microbiology, but I have no idea what concept it refers to in Blender. I read some about it, but the details are hazy. My understanding is that it’s got something to do with the hierarchical structure of Blender’s code and data tracking system where everything is ‘descended’ from something else)

So, to make a guess:
A PointerProperty is a construct that can be used as a base type for extending Blender’s data storage. It most resembles an object, but is used as a behind-the-scenes structure to more easily access other child properties, giving them a unified parentage.

Can a PointerProperty hold a list or maybe even a list of lists?

The structure I have in mind is something like this:

pose_frames =
[
<frame_number>.[mesh, mesh, mesh, mesh],
<frame_number>.[mesh, mesh, mesh, mesh],
<frame_number>.[mesh, mesh, mesh, mesh]
]

Would a PointerProperty be able to store something like that? Is this a sensible construct for what I’m trying to achieve?

Yup, you’re right. I played around with that, but all that’s onionskinned are the bones. My aim is to onionskin the meshes. That way it’s more like working with a light-table.

.

Yup, figured as much. That’s why I came up with my own way of representing onionskinned poses visually. I think it’ll be a lot easier, too.

.

Interesting. I didn’t know that. I wasn’t really keen on using the term ‘ghost’ anyway. It seems to have crept into the lexicon for 3D, but I prefer the older term ‘onionskin.’ I considered ‘light-table’ but didn’t want this tool to be confused with lights and ‘flip-book’ doesn’t really fit since there’s no flipping going on.

Thanks for the reply, dirty-stick.

Oh, BTW, I forgot to mention one other feature I have planned. There’s one more button: ‘Hide.’ During the process of tweaking the poses, the user can hit ‘Hide’ to turn off visibility for the onionskinned poses making it possible for playback to run as close to full speed as possible. That’s also the reason for the one-slider-does-all Subsurf setting.

Another thought that popped into my head while I was writing this is to have a ‘Hide and Flip’ button instead of ‘Hide.’ Clicking the button would:

  • automatically set a playback range:
    [LIST]

  • starting from the last keyframe before the first onionskin pose, and

  • ending with the first keyframe after the last onionskin pose

  • hide all the onionskin poses, and

  • play the range of frames.
    [/LIST]

Hitting the button a second time (if it’s possible to do a single-button toggle like that) would stop playback, reset the range to whatever it was before and un-hide all the onionskin poses. All those things sound easily do-able.

Can you give me a clearer explanation of what a collection is? Again, I read the API docs as well as everything I could find at blender.org, but the light is not coming on.

The example above was the wrong version and it doesn’t actually work. So, here’s an update. This works as a script copied/pasted into the Text Editor or installed as an addon.

bl_info = {
    "name": "Test Increment",
    "author": "Ron Tarrant",
    "version": (0, 1),
    "blender": (2, 66, ),
    "location": "View3D &gt; UI",
    "description": "Increments a number or resets it to zero",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Animation"}

import bpy

class my_increment():
    my_variable = 0

    def increment(self):
        self.my_variable += 1

    def reset(self):
        self.my_variable = 0

mine = my_increment()

class View3D_UI_PT_Panel(bpy.types.Panel):
    bl_label = "Hello Panel" # the label identifying the UI panel
    bl_space_type = "VIEW_3D"   # the window where the panel will appear and...
    bl_region_type = "UI"       # the sub-window (sidebar in this case)

    def draw(self, context):
        layout = self.layout
        column = layout.column()
        row = column.row(align = True)
        row.operator('behaviour.increment')
        row.operator('behaviour.reset')


class OBJECT_OT_Increment(bpy.types.Operator):
    bl_idname = "behaviour.increment"
    bl_label = "Increment"

    def execute(self, context):
        mine.increment()
        print("variable is now", mine.my_variable)
        return{'FINISHED'}

class OBJECT_OT_Reset(bpy.types.Operator):
    bl_idname = "behaviour.reset"
    bl_label = "Reset"

    def execute(self, context):
        mine.reset()
        print("variable is now", mine.my_variable)
        return{'FINISHED'}


def register():
    bpy.utils.register_class(View3D_UI_PT_Panel)
    bpy.utils.register_class(OBJECT_OT_Increment)
    bpy.utils.register_class(OBJECT_OT_Reset)


def unregister():
    bpy.utils.unregister_class(View3D_UI_PT_Panel)
    bpy.utils.unregister_class(OBJECT_OT_Increment)
    bpy.utils.unregister_class(OBJECT_OT_Reset)


if __name__ == "__main__":
    register()


What type(s) of object(s) did you use?

It depends upon what the script or AddOn does. For instance in RE:Phrase I just created a font object. For Meshfoot I created an Empty with a non-default display type to make it stand out in the scene. With RE:Lay, it did not matter it would relay animation from any object type.

All my scripts have the magic rename button associated with them. When you click it the operator renames the active object. When a refresh occurs the code can then detect that the newly renamed object (and any other like named objects) is available for processing. It is examined for custom properties, if they do not exist they are installed etc…

sorry, misunderstood.
you want to copy the object mesh, then duplicate different versions, to create onionskin.

id try the data like this.
pseudocode.

onion
…timeline_position … current timeline position
…frames … frames
…step … step
…transparency … property
…*poseframes … poseframes

poseframe
…timeline_position … poseframe timeline position, generated
…transparency … poseframe mesh transparency, generated
…mesh … poseframe mesh, generated

object.mesh.onion.frames=8
object.mesh.onion.transparency=0.2
object.mesh.onion.poseframes[0].timeline_position = timeline + offset
object.mesh.onion.poseframes[0].mesh = object.mesh
object.mesh.onion.poseframes[1].timeline_position = timeline + offset
object.mesh.onion.poseframes[1].mesh = object.mesh
object.mesh.onion.poseframes[2].timeline_position = timeline + offset
object.mesh.onion.poseframes[2].mesh = object.mesh

what a pointer is (same as in C, right? Since everything in Python is a pointer, I think this is right)

Everything is object in python - from the scripters view. I dunno how this is achieved internally in the python interpreter.

struct (a C structure, part of the underlying stuff you can’t access directly through Python, but you can pretend to access it by using… [wait for it]… a PointerProperty!)

The term struct is really ambiguous. Blender uses custom data types done with c-“struct” for sure. Some datatypes are availble in python and behave like python objects, although they come from c-code. I’m note sure about Blender DNA/RNA stuff either, but so far I think of RNA as data types, which get exposed to python via bpy.types.* and you may derive python classes from them (maybe I’m mistaken RNA and bpy structs here…)

RNA properties like MeshPolygon.index are available in c-code as well as in python. Some complex code allows us to change such props at runtime from both sides interactively.

Now, it is possible to add custom properties (are they RNA props?) to certain types, e.g. bpy.types.Scene, bpy.types.Object, bpy.types.Mesh. You do this like:

bpy.types.Scene.my_prop = bpy.props.BoolProperty()

Afterwards, all Scene objects will have an attribute “my_prop”, which you can change to True or False (as this one is a BoolProperty). To access that attribute/property of the current scene, you can do (for testing in Blender’s Python console):

bpy.context.scene.my_prop

or set:

bpy.context.scene.my_prop = Truebpy.context.scene.my_prop # print the value

Keep in mind that many types don’t support custom properties!

bpy.types.MeshVertex.my_prop = bpy.props.StringProperty()

bpy.context.object.data.vertices[0].my_prop
(&lt;built-in function StringProperty&gt;, {})

If you get this “built-in function …” something when it should return the property value, the type does not support custom properties.

Pointer properties may use c-pointers internally, but for you as a scripter it doesn’t really matter. They are a way to group your custom properties.

If you plan to add e.g. 5 properties to every Object type, you could do:

bpy.types.Object.your_prop_1 = bpy.props.IntProperty()bpy.types.Object.your_prop_2 = bpy.props.IntProperty()
bpy.types.Object.your_prop_3 = bpy.props.IntVectorProperty()
bpy.types.Object.your_prop_4 = bpy.props.FloatProperty()
bpy.types.Object.your_prop_5 = bpy.props.BoolVectorProperty()

Now you either have to name the props in a way that one can easily distinguish between these and the default properties of objects, or use a PointerProperty to group them together.

Easily distinguishable would be:

bpy.types.Object.onion_display_frame_start = ...bpy.types.Object.onion_display_frame_end = ...
bpy.types.Object.onion_display_enabled = ...

Really ugly ('cause the props are not self-explaining and may even conflict with existing properties):

bpy.types.Object.frame_start = ...
bpy.types.Object.frame_end = ...
bpy.types.Object.enabled = ...

With PointerProperty, you finally get this:

Object.onion_display.frame_start
Object.onion_display.frame_end
Object.onion_display.frame_enabled

And this is achieved by:

class OnionDisplay(bpy.types.PropertyGroup):
    frame_start = bpy.props.IntProperty()
    frame_end = bpy.props.IntProperty()
    enabled = bpy.props.BoolProperty()


# if you use bpy.utils.register_module(__name__) 
# and class definition is above PointerPoperty creation in code,
# it will work without the following line (if it's used for an addon):
bpy.utils.register_class(OnionDisplay)


bpy.types.Object.onion_display = bpy.props.PointerProperty(type=OnionDisplay)

I would call it “namespace organisation” :wink:

A PointerProperty is a construct that can be used as a base type for extending Blender’s data storage.

All Property types you find in bpy.props.* allow you to extend the data storage, PointerProperty is actually the only one which does not. It rather organizes the other property times into nice groups.

is used as a behind-the-scenes structure to more easily access other child properties, giving them a unified parentage.

That’s what I wanna say :slight_smile:

Can a PointerProperty hold a list or maybe even a list of lists?

Hm no, you need to pass a class to PointerProperty (type argument), and that class has to be derived from bpy.types.PropertyGroup. Inside of a property group, you can have as many bpy.props.* properties as you need. But as far as i know, you can’t store data types like python lists, dicts, tuples etc. I did a little test yesterday and they seem to vanish after .blend reload or Blender restart. So there is a difference between bpy.props. properties and other “properties” like python data types.

There is bpy.props.CollectionProperty, which is suitable for lists. Not sure if you could nest another collection inside for every item, maybe using an intermediate PointerProperty?! (ok, looks like you can nest CollectionProperty, without PointerProperty - see below)

Such collections are created similar to PointerProperty types: you derive a class from bpy.types.PropertyGroup and put bpy.props.* properties inside.

class MyCollection(bpy.types.PropertyGroup):
    # we actually have a default StringProperty here, called "name"
    some_value = bpy.props.IntProperty()


bpy.utils.register_class(MyCollection)


bpy.types.Scene.my_collection = bpy.props.CollectionProperty(type=MyCollection)


sce = bpy.context.scene


item = sce.my_collection.add() # add item to yet empty collection
item.name = "Foobar"
item.some_value = 1337


item = sce.my_collection.add() # add a 2nd item
item.name = "Second entry"
item.some_value = 123


for item in sce.my_collection:
    print(item.name, "has value", item.some_value)



Here’s an example how to use and iterate over nested Collections:

import bpy


class MySubCollection(bpy.types.PropertyGroup):
    sub = bpy.props.IntProperty()


bpy.utils.register_class(MySubCollection)


class MyCollection(bpy.types.PropertyGroup):
    # we actually have a default StringProperty here, called "name"
    some_value = bpy.props.IntProperty()
    some_coll = bpy.props.CollectionProperty(type=MySubCollection)
    
bpy.utils.register_class(MyCollection)


bpy.types.Scene.my_collection = bpy.props.CollectionProperty(type=MyCollection)


print("-" * 30)


sce = bpy.context.scene


# If you ran this code before, clear existing collection
if hasattr(sce, "my_collection"):
    sce.my_collection.clear()


item = sce.my_collection.add() # add item to yet empty collection
item.name = "Foobar"
item.some_value = 1337


subitem = item.some_coll.add()
subitem.sub = 222


subitem = item.some_coll.add()
subitem.sub = 6666


item = sce.my_collection.add() # add a 2nd item
item.name = "Second entry"
item.some_value = 123


for item in sce.my_collection:
    print(item.name, "has value", item.some_value)
    
    for subitem in item.some_coll:
        print(" - subitem", subitem.sub)



pose_frames =
[
<frame_number>.[mesh, mesh, mesh, mesh],
<frame_number>.[mesh, mesh, mesh, mesh],
<frame_number>.[mesh, mesh, mesh, mesh]
]

Would a PointerProperty be able to store something like that? Is this a sensible construct for what I’m trying to achieve?

Hm no, I would do this differently: Add the properties you need to bpy.types.Mesh, this type supports properties. Every mesh will get these props. Then all you need is a CollectionProperty with one item per frame. The problem is rather, how to store the data you got in that collection? There are no suitable bpy.props.* properties to store a lot of (animated) mesh data…

That’s why I believe you should do this in C :frowning:

Sorry, guys. I guess I’m not explaining this well.

What I have so far:

import bpy

# Assign a collection
class TweenerPoseObject(bpy.types.PropertyGroup):
    name = bpy.props.StringProperty(name="Test Prop", default="Unknown")
    value = bpy.props.IntProperty(name="Test Prop", default=22)
    <b>my_list = list()</b>

bpy.utils.register_class(TweenerPoseObject)

bpy.types.Scene.top_poses = bpy.props.CollectionProperty(type=TweenerPoseObject)

# Assume an armature object selected
print("Adding 3 values!")

my_item = bpy.context.scene.top_poses.add()
my_item.name = "Spam"
my_item.value = 1000
my_item.my_list.append(bpy.context.selected_objects)

my_item = bpy.context.scene.top_poses.add()
my_item.name = "Eggs"
my_item.value = 30

for my_item in bpy.context.scene.top_poses:
    print(my_item.name, my_item.value, my_item.my_list)

When I run this snippet, I get a PropertyGroup named top_poses and it has three attributes: name, value, and my_list.

The first two attributes (name and value) can be viewed in the Python Console by typing:

bpy.data.scenes['Scene']['top_poses']

If you hit Ctrl-Space at this point, auto-completion will list name and value, but not my_list. my_list is only available from within the code that’s run in the Text Editor. That leads me to believe that my_list isn’t a valid attribute (or property; at this point, I’m getting very confused with the way Blender bends terminology away from the norm, so please forgive me if I use the wrong Blender-term).

So, what I’m trying to figure out is: What can I use as a substitute for a Python list, something that is registerable with Blender the way StringProperty or IntProperty are?

The thing I need most for this substitute-for-a-Python-list is it needs to be able to handle an arbitrary list of mesh objects. In other words, it needs to hold any number of polygon objects (or pointers to mesh objects if that’s what they have to be).

Sorry for being so vague. My brain has been getting a bit over-taxed during the past week just trying to understand all this. I’ve been programming for almost 30 years and I’ve NEVER had such a hard time understanding an API before. I’ve worked in more than a dozen f#@!%% languages on every OS under the sun and learned more APIs that most people have had hot meals for f#*& sake! I’m beginning to think Blender is designed from the ground up to make both artists and programmers weep. Seriously.

see my post above, i believe you can only use bpy.props.* properties!

And there is no bpy.props.IDProperty, so you can’t reference an object (e.g. a mesh). Well, you can, by name using a StringProperty. But it will stop working if user renames the referenced object.

Yeah, blender api is tough, and it got a lot of limitations :confused:

btw. you should access your property like:

bpy.context.scene.top_poses

Scene[‘top_poses’] could be confused with IDProperty attributes:

bpy.context.scene[‘top_poses’] = 123 # only for this scene, see Scene tab > Custom properties!

Probably I don’t understand what you want :frowning:
AFAIK when you save a blender file only RNA properties are stored with it.
True python properties no !! This is the must important difference
Other difference:
Derivated class from bpy.types have very few to do with python!
And bpy.props.PropertyGroups it’s a very strange beast :frowning:
For example, property as my_list are not instance property!
So you end up with all your items, in top_poses collection, with the same values in my_list
But you can access it, during it’s life time, when you want.
to access my_list:
bpy.context.scene.top_poses[0].my_list
bpy.context.scene.top_poses[1].my_list

Yup, I was hoping to find a way around that, but I haven’t so far.

Yes, I was reading about that in the API. I’m not sure why this is so, but it seems like the fastest, easiest way to extend the concepts of the API. For instance, in the case of this project, I’d create a new ID type that’s a collection. Problem solved.

As it is, I’m stumped. Earlier in the thread, dirty-stick said this would have to be written in C and I’m beginning to think that’s the only option. I’d just rather not open that can of worms right now.

I’m glad I’m not the only one who thinks so. :slight_smile:

You’re right. Far safer and less confusion.

Right. Definitely food for thought.

Thanks, CoDEmanX.

orinoco56: It’s bpy.types.PropertyGroup, not bpy.props.PropertyGroup!

when you save a blender file only RNA properties are stored with it.

Yeah that seems true to me and it’s sad.

Also true: you may store anything in a top_poses[#] item “my_list” like orinoco said, it will only not save.

//edit:

bpy.context.scene[‘top_poses’] = 123 # only for this scene, see Scene tab > Custom properties!

Be careful with such (ID) props as they are listed in UI, i got Blender crashed more than once when i tried to store py lists and stuff like this :wink:

An example how i replaced Scene IDProp + eval by CollectionProperty:
mesh_show_vgroup_weights.py (text changed)

Well, I’m done for the day. I think I’ll go do something simpler for a while… like juggle knives or chain-saws.

Thanks for all the advice, guys!

No worries. I’m not sure I really understand it either. So cheer up, eh! :slight_smile:

That does seem to be the case. And so far, there’s no way around it which may kill this project before it’s properly off the ground.

And therein lies the rub, to misquote Billy S.

Yup. If I can find a way to save this stuff through a save/reload, all the rest is pretty much worked out. Even if it’s a cludge involving conversion, hiding on other layers or even attaching things somehow to legitimate objects (like Empties) it might still be possible to get this thing working.

Wouldn’t you know it. Just as I decide to stop for the day, I get an idea that just might solve this…

It was what you (CoDEmanX) said about the user changing the names of objects that got me going on this.

If I come up with a naming convention for the original objects the user wants to pose and another naming convention for the duplicated objects for each frame, I’ll be able to rebuild the lists of objects using simple Python lists after a reload. As long as the user knows about this convention before using the tool and is warned about not changing the names of objects, it should work fine.

What I had in mind is:

  • for objects to be duplicated, the names would all start with TOP_ORG
  • for duplicated objects: TOP_<frame_number>_ (ie. TOP_23_, TOP_12_, etc.)

After a file is saved and reloaded, an initialization method could search through bpy.data.objects and sort the objects into their proper lists.

As far as run-time goes, as I said, if the user is aware of the naming convention, it won’t be a problem to add lists, delete lists or add/remove from a specific list.

You are right, you can have meshes/objects in bpy.data named after a certain convention, and only the original ones linked to the scene. On load, you can access the hidden/unused objects, as long as you secure them - Blender loves to remove datablocks with zero users!

Using app handlers, you could initialize your script on .blend load (load_post) and enable fake users on save (save_pre) for your objects if needed.

BTW: it is possible to detect renamed objects, not sure how performance-decreasing this is though:

import bpy

bpy.types.Object.name_old = bpy.props.StringProperty()


for ob in bpy.data.objects:
    ob.name_old = ob.name


def upd(a):
    for ob in a.objects:
        #if ob.is_updated:
        if ob.name != ob.name_old:
            print(ob.name_old, "is now", ob.name)
            ob.name_old = ob.name


bpy.app.handlers.scene_update_pre.append(upd)

(Note: is_updated / is_updated_data doesn’t work for name changes for some reason :frowning: )

But if you don’t need to rename things in realtime, you could use a modal timer operator and check every N milliseconds for name changes and update object names accordingly.

I also tried to overwrite the default bpy.types.Object.name property, which works and didn’t lead to crash for yet, but unfortunately not like i desired. It basically makes object names inaccessible, the new prop is however used in UI. So user can still rename objects in UI, but it’s not affecting the actual object name (bpy.data.objects[‘THIS_NAME’] stays unchanged). Why did i do this in the first place? I tried to attach an update callback to the name property, but this seems impossible for built-in props (also looked into bl_rna.properties[‘name’] if there’s something to add an update function, but there isn’t).