3
$\begingroup$

I have learnt that it's possible to link 2 properties with drivers by adding a driver and creating variable that comes from another object's property. This allows me to update one property by modifying the other. However, this works only one-way and locks the first property from being changable directly.

In my case I have a way to represent rotation of sun (elevation, azimuth) where they're expressions of sun's x and z euler rotations (elevation = PI / 2 - sun.rotation_euler.x, azimuth = PI - sun.rotation_euler.z). These properties are used for more convenient controls of the sun in the UI for my use case. However, I have run into an issue of being unable to link them the way I want. Essentially, I need sun's rotation to match these values, but these values also to match sun rotation, as in if you rotate the sun object, these numbers change, but if you change these numbers, the sun rotates. I am wondering if it's possible to do this. Perhaps instead of using a separate property, is there a way to display an existing property (in this case sun.rotation_euler's x and z channels), but filtered through an equation that works 2 ways?

$\endgroup$

1 Answer 1

1
+100
$\begingroup$

Why 2-direction driver is not allow in blender?

Suppose there are 2 attributes control each other

attr_A.expression = attr_B + 1
attr_B.expression = attr_A * 2

when attr_B is 1, attr_A = 2 because attr_B is changed
Then attr_A control back to attr_B = 4 because attr_A is changed
It caused a Infinite Loop

Can computer auto get a Inverse function of a expression?

It can't. Because some function does not exist a Inverse function.
For example:

attr_A.expression = attr_B * attr_B  

when attr_A = -2 or 2, attr_B = 4,
but when attr_B is 4, you don't know attr_A is -2 or 2

Is possible to do 2 attributes with interdependent relationship?

You can make properties in UI via script to control some value
Here is the clock example: enter image description here

Run script to get the example scene

  1. run script in text editor

enter image description here

import bpy, math
import mathutils
from mathutils import Vector

# go to object mode first
# select and delete all object
if bpy.context.object:
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.select_all(action = "SELECT")
    bpy.ops.object.delete(use_global=True, confirm=False)

vec = Vector((0, 1))
rot = mathutils.Matrix.Rotation(math.radians(30.0), 2)
size = 0.05
for r in range(12):
    bpy.ops.mesh.primitive_uv_sphere_add(radius=1, enter_editmode=False, align='WORLD', location=(vec[0], vec[1], 0), scale=(size, size, size))
    bpy.ops.object.shade_smooth()
    vec.rotate(rot)

bpy.ops.object.select_all(action = "SELECT")
bpy.ops.object.join()
bpy.ops.mesh.primitive_plane_add(size=0.05, enter_editmode=False, align='WORLD', location=(0, 0.05, 0), scale=(1, 1, 1))
obj_short = bpy.context.object
obj_short.name = "short"
obj_short.rotation_euler[0] = math.pi / 2
md = obj_short.modifiers.new(type = "SOLIDIFY", name = "long")
md.thickness = -0.4
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)

bpy.ops.mesh.primitive_plane_add(size=0.025, enter_editmode=False, align='WORLD', location=(0, 0.05, 0.04), scale=(1, 1, 1))
obj_long = bpy.context.object
obj_long.name = "long"
obj_long.rotation_euler[0] = math.pi / 2
md = obj_long.modifiers.new(type = "SOLIDIFY", name = "long")
md.thickness = -0.8
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)

fc = obj_short.driver_add("rotation_euler")
dr = fc[2].driver
var = dr.variables.new()
var.targets[0].id = obj_long
var.targets[0].data_path = "rotation_euler[2]"
dr.expression = "var / 12"


## Create N-panel Category

def empty_fn(self): pass
def get_obj():
    try:    return bpy.data.objects["long"], bpy.data.objects["short"]
    except: return None, None

def fn_short(self): # sub function in update_fn_short
    obj_long, obj_short = get_obj()
    if obj_long is not None:
        upd_off()
        obj_long.rotation_euler[2] = math.radians(self.my_prop_short) * 12
        self.my_prop_long = math.degrees(obj_long.rotation_euler[2])
        upd_on()

def fn_long(self): # sub function in update_fn_long
    obj_long, obj_short = get_obj()
    if obj_long is not None:
        upd_off()
        obj_long.rotation_euler[2] = math.radians(self.my_prop_long)
        self.my_prop_short = math.degrees(obj_short.rotation_euler[2])
        upd_on()

can_short_upd = fn_short # sub function container
can_long_upd = fn_long
def upd_off():  # make sub function to a empty function
    global can_short_upd, can_long_upd
    can_short_upd = empty_fn
    can_long_upd = empty_fn
def upd_on():
    global can_short_upd, can_long_upd
    can_short_upd = fn_short
    can_long_upd = fn_long


def update_fn_short(self, context):
    can_short_upd(self)

def update_fn_long(self, context):
    can_long_upd(self)


class My_PT_Panel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "PANEL"
    bl_idname = "MY_PT_PANEL"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "My Category"

    def draw(self, context):
        self.layout.prop(bpy.context.scene, "my_prop_short")
        self.layout.prop(bpy.context.scene, "my_prop_long")

def register():
    bpy.utils.register_class(My_PT_Panel)
    bpy.types.Scene.my_prop_short = bpy.props.FloatProperty(
        name = "Short Rot",
        update = update_fn_short,
    )
    bpy.types.Scene.my_prop_long = bpy.props.FloatProperty(
        name = "Long Rot",
        update = update_fn_long, 
    )

if __name__ == "__main__":
    register()
  1. Find the Category in N-panel

enter image description here

$\endgroup$

You must log in to answer this question.

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