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$
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$

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.