updating properties based on object selection state

Being that I have Blender 2.64, I do not have some of the new features that Blender has to offer. In such, I wanted to develop a script that allows the user to transfer selected bone weights with the masking of vertex selection and a given a vertex group to be transferred onto.

However, I have been having some troubles with my script, where I need the properties to reset to their defaults when a user selects a new object. my biggest problem is how to workaround the illegal to change properties in draw state. So far, I was able to accomplish toggling the display of the Panel on and off by using certain criterias. My script is so screwed up there is really nothing good to post here except to give you a general idea. Basically, the first time running the script, it waits until a user selects a mesh object that has an armature attached to it and the context mode must be in ‘PAINT_WEIGHT’ mode. When this all falls true an EnumProperty of choice, listing the available vertex groups, and an Initialize button is posted in the panel. By clicking on Initialize, this initially sets the properties needed for the script. However, if you deselect->select new object, the old properites displays in the panel instead of going back to the initialize state. I want the Panel to display the default start-up state, so that the old data can be cleared and replaced with the new data. I have searched here and Google, and found very little except the possibillity of using handlers or callbacks, but I have never got into this type of programming with C/C++ or Blender Python. Furthermore, looking at the 2.64 docs, I do not see a handler that handles object selection state changes in the bpy.app.handler page. Is there a way I can get around this delima?

A callback for your properties might work in this case.

You can address properties you want to change by their self value in the callback. So when one value changes, you can change that self or other self values within the property class. You can also have a different callback for each property if you need to isolate certain types (i.e. ints vs floats) for certain operations.


def updateReGroup(self,context):
    try:
        scene = context.scene
    except:
        scene = None
    if scene != None:
        self.frame_offset = 10
        self.time_stretch = 1.0

class cls_ReGroup(bpy.types.PropertyGroup):
    frame_offset = bpy.props.IntProperty(name="Offset", description="Offset the reference animation by this many frames.", default = 0, min = -420, max = 420, update=updateReGroup)
    time_stretch = bpy.props.FloatProperty(name="Time Stretch", description="Stretch referenced animation time by this amount.", default=1.0, min=-420.00, max=420.0, update=updateReGroup)

Atom, unless I am wrong, I think I am not being that clear. From what I understand with Property update callbacks, the callback is only called when the value of its Property type changes, which Property values do not change just because the user changes from one object to another. The original way I thought I could do this is to set a StringProperty that holds the name of the selected object, which then the update callback would work. However, you cannot change properties in either the Panel.draw() nor Panel.poll() calls. For example, using the below script as a Panel script which properly toggles the UI Panel based on object selection…


import bpy

class TW_TransferWeightsPanel(bpy.types.Panel) :
    bl_name = "NormalizeWeightsPanel"
    bl_label = "Transfer Weights"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_options = {'DEFAULT_CLOSED'}
    bl_context = 'PAINT_WEIGHT'

    @classmethod
    def poll(self, context) :
        if context.active_object :
            ob = context.selected_objects
            if len(ob) == 1: 
                ob = ob[0]
            else :
                return False
            
            if ob.type == 'MESH' and ob.parent :
                return context.mode == 'PAINT_WEIGHT' and ob.parent.type == 'ARMATURE'#When the poll state is False, and if I do this....

context.scene.trans_weights.ob_selected = ""


#.... I get a Runtime error because you cannot change ob_selected while in draw state

        return False
    
    def draw(self, context) :
        ob = context.active_object
        armatureOB = ob.parent
        scn = context.scene
        layout = self.layout
tw = scn.trans_weights
if tw.groups_from_vertices :
[INDENT=2]#display groups and so on......
[/INDENT]
else :
[INDENT=2]row = layout.row()
row.operator("tw.update", text="Initialize")
[/INDENT]

#registry code and so forth....


couldn’t you register new props for bpy.types.Object and set them individually per object? Your panel would read the active object’s properties only.

I guess I could do that, and that might even reduce the amount of classes pointing to subclasses that I have. Basically as it is now, I have one big PointerProperty holding a vertex_group CollectionProperty which in there holds another CollectionProperty that stores the group_id and its weight value. So, I will need to take some time to think about this, unless you or others come up with other ideas. Thanks in the mean time

ummm… first thing first… I just realized that by selecting a new object the initalize button worked, but with errors because my clean function has improper code. Other than that, I guess I got scared when I saw that the PointerProperty was not refreshing when re-selecting the same object. To work around the reselecting issue, I guess the proper thing to do is to have a refresh button that the user can click on, am I right on that?

to update collections you really need the user to click a button, yes.

It was possible to at least add new elements to a collection, but since 2.67, it’s completely forbidden to do in draw().

You can try my thread hack as well. The premise is that when you are in the Draw event and you want to change a property instead of attempting the change right then, which is illegal, you launch a thread with the required information. When the draw event finishes the lock is released. The thread fires a short time later and the change can take place then.

In this example I am adding three new properties to an object in the draw event.


import threading, time

############################################################################
# Thread processing for parameters that are invalid to set in a DRAW context.
# By performing those operations in these threads we can get around the invalid CONTEXT error within a draw event.
# This is fairly abusive to the system and may result in instability of operation.
# Then again as long as you trap for stale data it just might work..?
# For best result keep the sleep time as quick as possible...no long delays here.
############################################################################
def reticular_add_properties(lock, passedSourceName, passedSleepTime):
    time.sleep(passedSleepTime) # Feel free to alter time in seconds as needed.   
    ob_source = bpy.data.objects.get(passedSourceName)
    if ob_source != None:
        ob_source["enabled"] = 1
        ob_source["REticular_Selection_Index"] = 1
        ob_source["REticular_Simultaneous"] = 1
.
.
.
#Inside of draw.
 
# Launch a thread to set the remaining values that would generate an error if issued now.
lock = threading.Lock()
lock_holder = threading.Thread(target=reticular_add_properties, args=(lock,ob.name,0.02), name='myThreadName')
lock_holder.setDaemon(True)
lock_holder.start()
# draw event exits releasing lock.

CoDEmanX,
Thanks for the tip, and actually updating properties and collections in the draw event is not allowed in Blender 2.64 as well.

So far my script is moving along. It is not suitable for release yet, but I am able to properly calculate the total weight of the selected deformed bones and add the remaing balance to chosen vertex group. I would like to add a few other tools as well. Also, I am curious if there would be any bad side-effects if I added a callback function to a scene property or the selected object for when it is first initialized. If it is not so costly or causing Blender to crash, the callback function would be used for keeping the properties up to date, and when the object is deselected or changed, it would remove the appropriate properties and stop the callback function.

Lastly, I have one more question. If do add a callback, what happens to the added callback if my script has an error at run-time? Does that mean the callback is still in memory? If so, I guess the best method would be to restart Blender?

Atom, I did not mean to ignore you. I am sorry. I forgot to add my comment about your post. I wanted to ask you a question about your hack, because I heard that threading can get expensive and they are not the safest thing to use if you do not know what you are doing. Is that not so? Because I sure don’t know anything about threading except that it allows you to do multiple processing at once. I would be really scared to use it.

Threads can be problematic if you launch them while rendering. But in my case posted above, they are always initiated from user driven input. Expensive is fairly irrelevant in Blender terms, we are stuck at single 1 CPU anyway. Also my thread does not hang around too long. It fires, does it’s job and exits. just think of it as your own special event system. I think if you find yourself launching 100’s or 1,000s of threads using this technique you might want to find another solution but just to get something up and running give it a try. Threads are not that scary. And this one-shot approach is probably a good way to get your feet wet.

If you just want to see the technique in action, click the link in my signature and pull down one of my recent AddOns, 2.66 or better. Basically I use it to create a collection of properties and add to that collection inside a draw event. After that it is business as usual in the draw event because the collection exists and can be safely operated upon.

i don’t think you really need a callback, maybe not even properties. Couldn’t you run all your calculations and assignments in an operator, so the user would click a button and everything is performed in that moment?

Not sure what happens to registered callbacks if the script crashes, but shouldn’t be too bad for your memory (the only problem you may experience with a crashed script is that a lot of error messages are raised in the system console, and printing like 20x per second is really slow, at least on windows).

CoDEmanX,
ummm, I took some time to think about your idea of not using properties. Actually, it turned out that I still needed to add almost the same amount of PropertyGroup classes and they did not include any functions or methods ( I get the 2 confused) as opposed to my original idea. My reasons for this have to deal with displaying in the UI what is selected. For example, if the head bone is selected with 5 vertices selected, my panel displays the head bone as the column label and the selected vertices display their weights in the column.props.
In the mean-time I still need some time to work on this, as I am having some troubles calculating the results properly, and it seems like Auto Normalize does not work when assigning a new weight to a vertex from one vertex group to another through python. For example, let’s say that vertex 102 weights are assigned to vertex groups neck and jaw; neck’s weight is 0.32 and jaw’s weight is 0.68. Now, after using a script that assigns vertex 102 a weight of 1 to vertex group head, the vertex weights of neck and jaw are still the same instead of 0 even though I have Auto Normalize set to True. Is my assumption correct? or is something wrong?

all-righty then: So I have started a working progress of my script which so far does not need properties. It is nothing fancy yet since I see that my script is simular to show vertex weights, which that’s what my script does. The only difference is that you can edit your weights Weight Paint context mode, which I found more handy since I can see the shared vertex groups to each selected vertex and it also upates the heat color as I change a vertex group value. here is my code so far…

class MainPanel(bpy.types.Panel) :
    bl_name = "ReplaceWeights"
    bl_label = "Replace Weights"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_options = {'DEFAULT_CLOSED'}
    bl_context = "PAINT_WEIGHT"
    
    @classmethod
    def poll(self, context) :
        if context.object.select :
            ob = context.object
            if ob.parent and ob.parent.type == 'ARMATURE' : 
               return context.mode == "PAINT_WEIGHT"
        
        return False            
           
    def draw(self, context) :
        ob = context.object
        scn = context.scene
        
        layout = self.layout
        col = layout.column()
        
        selected_verts = [v for v in ob.data.vertices if v.select]
        deformed_groups = {}
        
        if len(selected_verts) < 11 and ob.data.use_paint_mask_vertex: 
            for vert in selected_verts :
                deformed_groups[vert.index] = []
                for grp in ob.vertex_groups :
                    if grp.name in ob.parent.pose.bones : #[grp.name].bone.use_deform :
                        for vert_grp in vert.groups :
                            if vert_grp.group == grp.index :
                                deformed_groups[vert.index].append(vert_grp)
                            
    
            box = layout.box()
    
            for gindex, vtx_grps in deformed_groups.items() : #v in selected_verts :
                col = box.column(align=True)
                col.label(text="vtx[%i]"%(gindex))
                        
                split = col.split(percentage=0.80)
                col80 = split.column()
                col20 = split.column()
                for grp in vtx_grps :
                    col80.prop(ob.vertex_groups[grp.group], "name", text="") #"vtx[%i]"%(index))
                    col20.prop(grp, "weight", text="")
        
        
def register() :
    bpy.utils.register_class(MainOperator)
    bpy.utils.register_class(MainPanel)
    
    bpy.types.Scene.ReplaceGroup = bpy.props.EnumProperty(items=getAvailableVertexGroups, name = "Avaiable Vertex Groups")
    bpy.types.Scene.ob_selected = bpy.props.StringProperty()
    
def unregister() :
    bpy.utils.unregister_class(MainOperator)
    bpy.utils.unregister_class(MainPanel)
    
if __name__ == '__main__' :
    register()

… the only thing that I am having problems with right now is that I cannot mirror the left side of the head bone to the right side. From what I gather, if I am correct on this, this has to do with a mesh not being symmetrical. However, I cannot see where I lost symmetry when my geometry was symmetrical before skinning. As a matter of fact, when I initially parented my armature, I noticed that I was having problems from that get go where the whole right side of my character’s head did not get weighted to the head bone. Meaning, as an example, the left ear was propery assigned red to the head bone but the right ear was completly blue!!!