1
$\begingroup$

I want to move the object on its local axes, I think by default there isn't a Blender Python property that allows it, I have to draw the X/Y/Z sliders of the local location in the user interface to move the object.

My idea is a custom property called local_location or local_location_x local_location_y local_location_z

Using this, create a callback function:

def update_local_location():
    ob = self.id_data

# Property declared into PropertyGroup (bpy.types.Object)

local_location_x: FloatPropery(update=update_local_location)

Normally to move a location object it is sufficient to use this line in the interface:

layout.prop(ob, "location")

or to show only one slider, in this case X-axis:

layout.prop(ob, "location", index=0)

The goal is to design a slider that can move the object on its local axis and not on the Global one.

I'm missing figure out how to properly create that Callback function in case blender doesn't have a python API to move to local location

I also tried using Batfinger's answer below, but it doesn't seem to work well:

Change Object's Location on local Z-Axis using Slider

$\endgroup$
4
  • $\begingroup$ moving an object along its local axis is a built in feature... so (to me), your question is not clear. $\endgroup$
    – lemon
    Commented Apr 6, 2023 at 18:50
  • $\begingroup$ @lemon Yes, but usually it will move on the global axis. I have to move it on the Local axis, so it depends on the orientation of the object. I just need to draw 3 prop sliders that can move the object on the local X/Y/Z axis $\endgroup$
    – Noob Cat
    Commented Apr 6, 2023 at 21:05
  • $\begingroup$ So grad along XX (twice) for X local axis is not what you're looking for? why? $\endgroup$
    – lemon
    Commented Apr 6, 2023 at 21:08
  • $\begingroup$ Sorry, I do not understand what you mean. I simply have to draw 3 sliders, which move the object on its local axes. I don't understand if Blender already has a Python API that allows it. $\endgroup$
    – Noob Cat
    Commented Apr 6, 2023 at 21:29

3 Answers 3

1
$\begingroup$

I don't consider myself a reputable source, because I'm quite a noob, but I do something similar to what I think it is that you're asking.

Maybe you can do something with this code. I think in your case all that might be of interest is the bpy.ops.transform.translate line, but I don't know how you would use it with a slider. I use a checkbox to select the axis. I use a button click to move the desired distance that comes from what I think you call a slider.

This is a snippet that moves the object a specified distance based on the measurement unit selected, in this case inches, on a selected axis, on any transformation orientation:

class KF_OT_MOVOBJ(bpy.types.Operator):
"""Move object along selected axis"""
bl_label = "Move Object"
bl_idname = "kf.movobj"

def execute(self, context):

    if (bpy.data.scenes["Scene"].my_props.dist_enum == 'U_INCH' and bpy.data.scenes["Scene"].my_props.x_bool):
        mov_dist_num = bpy.data.scenes["Scene"].my_props.mov_dist_num * 0.0254
        bpy.ops.transform.translate(value=(mov_dist_num, 0, 0), orient_axis_ortho='X', constraint_axis=(True, False, False))

    elif (bpy.data.scenes["Scene"].my_props.dist_enum == 'U_INCH' and bpy.data.scenes["Scene"].my_props.y_bool):
        mov_dist_num = bpy.data.scenes["Scene"].my_props.mov_dist_num * 0.0254
        bpy.ops.transform.translate(value=(0, mov_dist_num, 0), orient_axis_ortho='X', constraint_axis=(False, True, False))

    elif (bpy.data.scenes["Scene"].my_props.dist_enum == 'U_INCH' and bpy.data.scenes["Scene"].my_props.z_bool):
        mov_dist_num = bpy.data.scenes["Scene"].my_props.mov_dist_num * 0.0254
        bpy.ops.transform.translate(value=(0, 0, mov_dist_num), orient_axis_ortho='X', constraint_axis=(False, False, True))   # etc.,etc.  

    return {'FINISHED'}

The code I left out is the same as above, but it covers different measurement unit conversions and the various axis selections.

Here is the link to the transform operators: https://docs.blender.org/api/current/bpy.ops.transform.html

$\endgroup$
0
$\begingroup$

For object mode

enter image description here

import bpy
from bpy.types import Panel
from bpy.props import FloatVectorProperty
from mathutils import Vector

import bpy
from bpy.app.handlers import persistent


is_updating = False
@persistent
def upd_handler(dummy): # update the n-panel prop when active object location changed
    global is_updating
    if is_updating: return
    is_updating = True

    try:
        if bpy.context.scene.my_tool.loc != bpy.context.object.location:
            bpy.context.scene.my_tool.loc[:] = bpy.context.object.location
            print("update")
    except:
        pass

    is_updating = False


class MyProperties(bpy.types.PropertyGroup):
    is_updating = False
    def upd_loc(self, context):
        cls = self.__class__
        if cls.is_updating: return
        cls.is_updating = True

        try:
            obj = bpy.context.object
            old_loc = obj.location
            prop = bpy.context.scene.my_tool.loc

            if old_loc[0] != prop[0]:
                vec = Vector((prop[0] - old_loc[0], 0, 0)) @ obj.matrix_world.inverted()
                obj.location += vec
            elif old_loc[1] != prop[1]:
                vec = Vector((0, prop[1] - old_loc[1], 0)) @ obj.matrix_world.inverted()
                obj.location += vec
            else:
                vec = Vector((0, 0, prop[2] - old_loc[2])) @ obj.matrix_world.inverted()
                obj.location += vec

        except:
            pass

        cls.is_updating = False

    loc: FloatVectorProperty(name="Location", size=3, subtype="XYZ", update=upd_loc)

class XX(Panel):
    bl_idname = "XX_PT_Panel"
    bl_label = "Panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "TEST"


    def draw(self, context):
        layout = self.layout
        mytool = context.scene.my_tool
        layout.prop(mytool, "loc")
        


def register():
    bpy.utils.register_class(MyProperties)
    bpy.utils.register_class(XX)
    bpy.types.Scene.my_tool = bpy.props.PointerProperty(type=MyProperties)

    if upd_handler not in bpy.app.handlers.depsgraph_update_post:
        bpy.app.handlers.depsgraph_update_post.append(upd_handler)

def unregister():
    bpy.utils.unregister_class(MyProperties)
    bpy.utils.unregister_class(XX)
    del bpy.types.Scene.my_tool

    if upd_handler in bpy.app.handlers.depsgraph_update_post:
        bpy.app.handlers.depsgraph_update_post.remove(upd_handler)

if __name__ == "__main__":
    register()
$\endgroup$
6
  • 1
    $\begingroup$ Sounds interesting, but I don't understand, in that video, it looks like you're interacting on Slider X. But why do the values also change on Y and Z? shouldn't be Local values, so they shouldn't actually change upon interaction on a Local X axis. Then, I wonder. Why a handler function for this? Isn't a callback function enough to update everything? $\endgroup$
    – Noob Cat
    Commented Apr 7, 2023 at 0:49
  • $\begingroup$ It is based on the rotation of the object, if the object euler_rotation is not (0, 0, 0), it moves the local space. $\endgroup$
    – X Y
    Commented Apr 7, 2023 at 0:52
  • $\begingroup$ If upd_handler is removed, it won't update the active object position, you can remove it and try. $\endgroup$
    – X Y
    Commented Apr 7, 2023 at 1:00
  • $\begingroup$ upd_loc only runs when the value you define changes, so it doesn't run when the active object changes. $\endgroup$
    – X Y
    Commented Apr 7, 2023 at 1:04
  • 1
    $\begingroup$ Get/Set Update only runs when getting/modifying the value. When you select other object, it doesn't update. You could consider using Message Bus to subscribe an existing attribute, but it will fail on drag the object using the move tool. docs.blender.org/api/3.5/bpy.msgbus.html $\endgroup$
    – X Y
    Commented Apr 7, 2023 at 1:25
0
$\begingroup$

If you want to control the local location so that it affects the normal location, you can just rotate the local rotation like so:

import bpy
from bpy import context as C, data as D
from bpy.types import SpaceView3D
from mathutils import Vector

last_pos = Vector()

def fun():
    global last_pos
    if C.area.type != 'VIEW_3D':
        return
    cube, empty = D.objects['Cube'], D.objects['Empty']
    if empty.location == last_pos:
        return
    last_pos = empty.location.copy()
    
    new_pos = last_pos.copy()
    new_pos.rotate(cube.rotation_euler)
    cube.location = new_pos


try:
    SpaceView3D.draw_handler_remove(SpaceView3D.my_handler, 'WINDOW')
except (AttributeError, ValueError):
    pass

SpaceView3D.my_handler = SpaceView3D.draw_handler_add(fun, (), 'WINDOW', 'PRE_VIEW')

Well you probably want to also scale that location, changing this line:

new_pos = last_pos.copy()

to

new_pos = last_pos.copy() * cube.scale

Reversing this operation is not hard, just invert the Euler rotation [I wonder if there's a more convenient way than the one I'm using, other than converting to a matrix to use its inverted() method?], divide by the scale [again, what's the best way to do this?] rather than multiplying it, and of course switch the source with the target (the empty and the cube):

def fun():
    global last_pos
    if C.area.type != 'VIEW_3D':
        return
    cube, empty = D.objects['Cube'], D.objects['Empty']
    if cube.location == last_pos:
        return
    last_pos = cube.location.copy()
    
    euler = cube.rotation_euler.copy()
    euler.x *= -1
    euler.y *= -1
    euler.z *= -1
    euler.order = euler.order[::-1]
    new_pos = last_pos.copy()
    new_pos.rotate(euler)
    new_pos.x /= cube.scale.x
    new_pos.y /= cube.scale.y
    new_pos.z /= cube.scale.z
    
    empty.location = new_pos

In regards to the interface part, I think it's beside the main point and requires another question phrased in a way the answers aren't opinion-based (so asking for the best way to implement this is out of the question, pun intended).

$\endgroup$

You must log in to answer this question.

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