2
$\begingroup$

Prompted by my previous question, I was trying to create an addon to move background images with the mouse.

However, I can't figure out a good way to determine what image to move.

I thought I might somehow add a button to each background image panel in 3D view > Properties region, but is there a way to get which image's button was pressed?

Here is a modal operator (based on the template) that picks an image based on a bunch of hacks that fail in too many situations:

import bpy, math
from bpy.props import IntProperty, FloatProperty


class MoveBackgroundImage(bpy.types.Operator):
    """Move background images with the mouse"""
    bl_idname = "view3d.background_image_move"
    bl_label = "Move backgound image"

    first_mouse_x = IntProperty()
    first_mouse_y = IntProperty()
    first_x = FloatProperty()
    first_y = FloatProperty()

    def modal(self, context, event):
        if event.type == 'MOUSEMOVE':
            delta_x = self.first_mouse_x - event.mouse_x
            delta_y = self.first_mouse_y - event.mouse_y
            if event.shift:
                backimage.offset_x = self.first_x - delta_x * 0.01
                backimage.offset_y = self.first_y - delta_y * 0.01
            else:
                backimage.offset_x = self.first_x - delta_x * 0.05
                backimage.offset_y = self.first_y - delta_y * 0.05
#                backimage.size = self.first_y - delta_y * 0.05

        elif event.type == 'LEFTMOUSE':
            return {'FINISHED'}

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            backimage.offset_x = self.first_x
            backimage.offset_y = self.first_y
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        go_hack = 0
        def get_view_orientation(view_matrix):
            r = lambda x: round(x, 2)
            view_rot = view_matrix.to_euler()

            orientation_dict = {(0.0, 0.0, 0.0) : 'TOP',
                                (r(math.pi), 0.0, 0.0) : 'BOTTOM',
                                (r(-math.pi/2), 0.0, 0.0) : 'FRONT',
                                (r(math.pi/2), 0.0, r(-math.pi)) : 'BACK',
                                (r(-math.pi/2), r(math.pi/2), 0.0) : 'LEFT',
                                (r(-math.pi/2), r(-math.pi/2), 0.0) : 'RIGHT'}
            return orientation_dict.get(tuple(map(r, view_rot)), 'UNDEFINED')
        for area in bpy.context.screen.areas:
            if area.type == 'VIEW_3D':
                for space in area.spaces:
                    if space.type == 'VIEW_3D':
                        vmatrix = space.region_3d.view_matrix
                        view_orientation = get_view_orientation(vmatrix)
                        view_persp = space.region_3d.view_perspective
                        global backimage
                        for bgimage in space.background_images:
                            fail_hack = 1
                            bgi_axis = bgimage.view_axis
                            expanded = bgimage.show_expanded
                            if view_persp == 'ORTHO':
                                go_hack = 1
                            if view_persp == 'CAMERA':
                                go_hack = 1
                            if go_hack == 1:
                                if expanded: #HACK
                                    if view_orientation != 'UNDEFINED':
                                        if view_orientation == bgi_axis:
                                            fail_hack = 0
                                            backimage = bgimage
                                            self.first_mouse_x = event.mouse_x
                                            self.first_mouse_y = event.mouse_y
                                            self.first_x = backimage.offset_x
                                            self.first_y = backimage.offset_y

                                            context.window_manager.modal_handler_add(self)
                                            return {'RUNNING_MODAL'}
                                        else:
                                            fail_hack = 1
                                            fail_message = "No active image, could not finish"
                                    else:
                                        if view_persp == 'CAMERA':
                                            if bgi_axis == 'CAMERA':
                                                fail_hack = 0
                                                backimage = bgimage
                                                self.first_mouse_x = event.mouse_x
                                                self.first_mouse_y = event.mouse_y
                                                self.first_x = backimage.offset_x
                                                self.first_y = backimage.offset_y
                                                context.window_manager.modal_handler_add(self)
                                                return {'RUNNING_MODAL'}
                                            else:
                                                fail_hack = 1
                                                fail_message = "No images visible in camera view"
                                            if bgi_axis == 'ALL':
                                                fail_hack = 0
                                                backimage = bgimage
                                                self.first_mouse_x = event.mouse_x
                                                self.first_mouse_y = event.mouse_y
                                                self.first_x = backimage.offset_x
                                                self.first_y = backimage.offset_y
                                                context.window_manager.modal_handler_add(self)
                                                return {'RUNNING_MODAL'}
                                            else:
                                                fail_hack = 1
                                                fail_message = "No images visible in camera view"
                                        else:
                                            fail_hack = 1
                                            fail_message = "View not aligned to an axis, no images visible"
                                else:
                                    fail_hack = 1
                                    fail_message = "No active image, could not finish"
                            else:
                                fail_hack = 1
                                fail_message = "View not in ortho or camera perspective"
                        if fail_hack == 1:
                            self.report({'WARNING'}, fail_message)
                            return {'CANCELLED'}
def register():
    bpy.utils.register_class(MoveBackgroundImage)


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


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.view3d.background_image_move('INVOKE_DEFAULT')
$\endgroup$

1 Answer 1

2
$\begingroup$

Looking at space_view3d.py, I suggest you add a button for each background image and pass the index to your operator.

    def draw(self, context):
        layout = self.layout

        view = context.space_data

        col = layout.column()
        col.operator("view3d.background_image_add", text="Add Image")

        for i, bg in enumerate(view.background_images):
            # ...
            box.operator("view3d.background_image_move").index = i
            # ...

In your operator:

class MoveBackgroundImage(bpy.types.Operator):
    """Move background images with the mouse"""
    bl_idname = "view3d.background_image_move"
    bl_label = "Move backgound image"

    index = bpy.props.IntProperty(name="Background image index", default=-1)

    # ...

    def invoke(self, context, event):

        #...
        if self.index < 0:
            print("No valid background image index given")
            return {'CANCELLED'}

        try:
            backimage = space.background_images[self.index]
        except IndexError:
            print("No background image with index %i" % self.index)
            return {'CANCELLED'}

        # use backimage below

The index number used for the operator button in the space_view3d.py panel will always be up to date, since it is in the draw() function, which is called whenever a background image is added or removed.

The additional checks for the index in the operator are only needed if the op is used with python - you could use an invalid index as argument. But should always be valid if you use the button in the sidebar panel.


Here's a working script that moves the background image relative to cursor:

import bpy
from mathutils import Vector
from bpy.props import IntProperty, FloatProperty
from bpy_extras.view3d_utils import region_2d_to_location_3d


class ModalOperator(bpy.types.Operator):
    """Move an object with the mouse, example"""
    bl_idname = "object.modal_operator"
    bl_label = "Simple Modal Operator"

    first_mouse_x = IntProperty()
    first_mouse_y = IntProperty()
    first_value_x = FloatProperty()
    first_value_y = FloatProperty()

    def region_to_view(self, context, event):

        x, y = context.space_data.background_images[0].image.size
        aspect_ratio = x / y

        loc = region_2d_to_location_3d(context.region, context.space_data.region_3d, (event.mouse_region_x, event.mouse_region_y * aspect_ratio), Vector()).to_2d() # works for top view only, need axis mapping here
        offset = region_2d_to_location_3d(context.region, context.space_data.region_3d, (self.first_mouse_x, self.first_mouse_y * aspect_ratio), Vector()).to_2d() # ... and here too

        return (loc - offset + Vector((self.first_value_x, self.first_value_y)))

    def modal(self, context, event):
        bg = context.space_data.background_images[0]

        if event.type == 'MOUSEMOVE':
            bg.offset_x, bg.offset_y = self.region_to_view(context, event)

        elif event.type == 'LEFTMOUSE':
            return {'FINISHED'}

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            bg.offset_x = self.first_value_x
            bg.offset_y = self.first_value_y
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        bg = context.space_data.background_images[0]
        if context.object:
            self.first_mouse_x = event.mouse_region_x
            self.first_mouse_y = event.mouse_region_y
            self.first_value_x = bg.offset_x
            self.first_value_y = bg.offset_y

            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "No active object, could not finish")
            return {'CANCELLED'}


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


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


if __name__ == "__main__":
    register()

Note that it works on the first background image and top view only (you need to add axis mapping, view to screen).

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .