Change the Location/Rotation Locks/Limits button operators into properties

Issue #681 resolved
ViSlArT created an issue

Visually they look like properties but they’re actually one-click operators, meaning unlike actual properties, they can’t by click-dragged to multi-select.

If however they were actual properties, they could be click-dragged to perform the same function, as well as be animated/driven if you wanted.

If the reason for you not doing this is that you didn’t know about it (as it’s not currently done anywhere in the addon), here’s the documentation for it: https://docs.blender.org/api/master/bpy.props.html#update-example .
The only functional reason I can see for not doing it is that it “may” not work with your operator/error system (as I don’t fully understand what/why they’re doing).

Comments (10)

  1. Thomas Larsson repo owner

    Previous commit had some problems, should work now. I also looked into doing the same trick for the mhx properties, but there it doesn’t seem to work, because those are armature properties which cannot access the armature object.

  2. ViSlArT reporter

    I don’t know what you’re talking about; if you clarify, I could give specific advice, so the following will be generic advice/info.

    I have a bunch of utility code, for things I do in my addon(s). One code I have is to find the rig (object) of “something”.

    For example, a posebone or editbone. What my code does for things like editbones, where their only source is the armature_data, is just scan the scene's data to find any object that's using it.

    def get_rig(context, arm):
        for ob in context.scene.collection.objects:
            if (ob.data == arm):
                return ob
    

    This probably wouldn't work as expected for an instanced rig, given the data is used by multiple objects but I label that as a non-issue for my purposes.  But if it were a concern, you could still do it but prioritize for example "selected" objects and the active object. 

    I should mention however, you can also use context pointers https://docs.blender.org/api/master/bpy.types.UILayout.html#bpy.types.UILayout.context_pointer_set

    The way you do it is if you have the object already:

    def draw(self, context):
        layout = self.layout
        ob = context.object
        layout.context_pointer_set('armature_object', ob)
        layout.prop(ob.data, 'MhaBoolProp')
    

    and in MhaBoolProp's update function:

    def update(self, context):
        if hasattr(context, 'armature_object'):
            ob = context.armature_object
        else:
            ob = get_rig(context, self)  # try to find the object using the function above
    

    You can also send custom attributes to the context of operators like this, as well as when you call the operator itself.

    Most people do context.copy() for that but you can just put a dictionary of the custom props: bpy.ops.some.operator({'null': None})

  3. Thomas Larsson repo owner

    Now the mhx operators have been converted to properties. However, I don’t understand what the context_pointer_set function is good for. I simply saved the active rig in a global variable in the draw function. Seems to work without problems.

  4. ViSlArT reporter

    That global is pointing to an object (as in data block, not literally bpy.data.objects but that too).
    Pointers can lose focus for any reason despite the text path not changing, which is why if I HAD to store something, I use repr(src) then get it back with eval(src).
    context pointers is for safely storing temporary data blocks, into the commonly used context. It is used to send data to operators in the regular UI, such as constraints.

    Using a global is “functional”, yes, unless it randomly glitches and then you just have to undo a try it again. It is dirty but it is still “functional”.

    Think of it this way: if you moved the code to two separate files, the only way to send the correct object to the property would be to import the module, which would be even more effort for someone else with a script/addon that wanted to access the property for whatever reason. They couldn’t send the standard custom context, and would have to import the module to send it instead.

    Again, for the sole use of your addon, it “functions”, which is always the #1 priority.

    I don’t know if I have to say this but context_pointer_set doesn't require new names. You can just overwrite pre-existing variables.
    I didn’t know this before but apparently when you call bpy.context from inside a function with a custom context, it’s not different from the context that was sent to the function. I was expecting bpy.context to always stay as the unmodified context.

  5. Thomas Larsson repo owner

    I tested my code with multiple mhx rigs, and I think it is safe. I don’t just use the global variable, but first check that it is not None and has the right armature data, and otherwise loop over the objects in the view layer to find one with the right data. And if you delete the rig the panel and the armature context go away, so there is no way to access the properties.

    The stretch and toe-tarsal toggle fail with library overrides, and less nicely so than before, because you cannot go into edit mode. But that is a separate problem; the right rig is still selected. Rigify is using some trick with a special armature modifier which could perhaps work.

  6. Thomas Larsson repo owner

    I removed the global variable altogether. Instead the script loops over all objects in the view layer to find with matching data. This should still be reasonably fast since the loop is done in C, and safer if there is some case that I missed where the global variable can mess things up.

  7. Log in to comment