1
$\begingroup$

I am trying to overwrite the bpy.ops.object.move_to_layer operator in Blender so that I can move other objects when the selected objects are moved.

When I try to move the selected objects by pressing the 'm' key and clicking on the new layer in the invoked props popup (from the 'layers' BoolVectorProperty), though, the object still remains in the old layer as well as being added to the new layer because both layers are active, as if I was holding the shift key in the original operator.

The following is my code to overwrite and replicate the bpy.ops.object.move_to_layer operator (I omitted extra code run when the operator is executed for simplicity):

# Blender imports
import bpy
from bpy.types import Operator
from bpy.props import *

class move_to_layer_override(Operator):
    """Move to Layer"""
    bl_idname = "object.move_to_layer"
    bl_label = "Move to Layer"
    bl_options = {'REGISTER', 'INTERNAL', 'UNDO'}

    ################################################
    # Blender Operator methods

    @classmethod
    def poll(self, context):
        return True

    def execute(self, context):
        self.runMove(context)
        return {'FINISHED'}

    def invoke(self, context, event):
        # sets self.layers to active layers of selected objects
        idxs = [i for i in range(20)]
        for obj in context.selected_objects:
            for i in idxs:
                layer = obj.layers[i]
                if layer:
                    self.layers[i] = True
                    idxs.remove(i)
        # Run confirmation popup for delete action
        return context.window_manager.invoke_props_popup(self, event)

    ###################################################
    # class variables

    # This does not behave like scn.layers.
    # only one layer should be selected at a time unless shift is held
    layers = BoolVectorProperty(
        name="Layers",
        subtype="LAYER",
        description="Object Layers",
        size=20,
        )

    ################################################
    # class methods

    def runMove(self, context):
        scn = context.scene
        for obj in bpy.context.selected_objects:
            obj.layers = self.layers
            # insert extra code to run when objects are moved here

def register():
    bpy.utils.register_module(__name__)

def unregister():
    addon_updater_ops.unregister()

if __name__ == "__main__":
    register()

It should behave exactly like the original operator – when a new layer is selected in the invoked props popup, the old layer(s) should be deselected unless the shift key is held. Could someone help me figure out how to do this?

$\endgroup$
  • $\begingroup$ Related $\endgroup$ – batFINGER Jun 27 '18 at 5:56
  • $\begingroup$ That article doesn't really address my question (at least as far as I understand), though it's similar. I'm asking about whether the shift key event can change the behavior of the BoolVectorProperty in the invoked popup, not whether it can change the behavior of the operator itself. $\endgroup$ – Christopher Gearhart Jun 27 '18 at 16:44
2
$\begingroup$

Use the event from invoking the operator

Using the method from the related answer here, since we can address the event in the invoke, can it be passed to the execute method of the popup as a class property. On very limited testing it appears so..

Added some more class properties to keep tabs of selection. Also copied the selected objects since if a non layer with non matching scene layer is selected context.selected_objects will vary those when op was invoked. (Btw obj.layers is gone in 2.8)

The big issue is the popup not redrawing correctly. I thought the operator check(..) method returning True would work, but not the case on my blender. Perhaps using the layers collection of the context object. when invoked, would be one way to solve this.

import bpy
from bpy.types import Operator
from bpy.props import BoolVectorProperty

class OBJECT_OT_move_to_layer2(Operator):
    """Move to Layer 2"""
    # class properties
    bl_idname = "object.move_to_layer2"
    bl_label = "Move to Layer [TESTING]"
    bl_options = {'REGISTER', 'UNDO'}

    event = None
    objects = []
    prev_sel = []

    # poll is a class method
    @classmethod
    def poll(self, context):
        return True

    def execute(self, context):
        ev = []
        event = self.event
        if event.ctrl:
            ev.append("Ctrl")
        if event.shift:
            ev.append("Shift")
        if event.alt:
            ev.append("Alt")
        if event.oskey:
            ev.append("OS")
        changed = [i for i, (l, s) in
                enumerate(zip(self.layers, self.prev_sel))
                if l != s]

        print("+".join(ev), event.type, event.value, changed)
        # pick only the changed one
        if not (event.ctrl or event.shift) and changed:
            self.layers = [i in changed for i in range(20)]
        self.prev_sel = self.layers[:]

        self.runMove(context)
        return {'FINISHED'}

    def check(self, context):
        return True # thought True updated.. not working

    def invoke(self, context, event):
        self.layers = [any(o.layers[i] for o in context.selected_objects)
                      for i in range(20)]
        self.event = event
        self.objects = [o.name for o in context.selected_objects]
        self.prev_sel = self.layers[:]
        return context.window_manager.invoke_props_popup(self, event)

    # properties

    layers = BoolVectorProperty(
        name="Layers",
        subtype="LAYER",
        description="Object Layers",
        size=20,
        )

    def runMove(self, context):
        for name in self.objects:
            obj = context.scene.objects.get(name)
            obj.layers = self.layers

def register():
    bpy.utils.register_class(OBJECT_OT_move_to_layer2)

def unregister():
    bpy.utils.unregister_class(OBJECT_OT_move_to_layer2_name)

if __name__ == "__main__":
    register()
    bpy.ops.object.move_to_layer2('INVOKE_DEFAULT')

Test print to console

 LEFTMOUSE PRESS [0]
Ctrl LEFTMOUSE PRESS [11]
Ctrl+Shift LEFTMOUSE PRESS [12]
Shift LEFTMOUSE PRESS [1]

Note if you want to call from a shortcut, and have the hacky event handling, need to invoke from another operator. Edit made to simple operator to call op above (just like calling it from space menu)

class SimpleOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.simple_operator"
    bl_label = "Simple Object Operator"

    def invoke(self, context, event):
        return bpy.ops.object.move_to_layer2('INVOKE_DEFAULT')

enter image description here

$\endgroup$
  • $\begingroup$ Thank you for spelling this out for me; I understand better now. Sadly, though, this only works for me when the operator is called via the script (as you've done in the last line). When invoked with the "m" key, though, the operator does not work correctly as the event object seems to hold faulty information. The printout to the console was seemingly random... Ctrl+Alt NONE ANY [2] or NONE ANY [12] or Alt NONE PRESS [1]. I was not holding down ctrl or alt when clicking on the layers. $\endgroup$ – Christopher Gearhart Jun 28 '18 at 0:58
  • 1
    $\begingroup$ or the space bar search menu, .. in which case see edit above. $\endgroup$ – batFINGER Jun 28 '18 at 5:32

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.