120 lines
4.4 KiB
Python
120 lines
4.4 KiB
Python
import bpy
|
|
from mathutils import Vector
|
|
|
|
class ODM_OT_depth_move(bpy.types.Operator):
|
|
bl_idname = "object.depth_proportional_move"
|
|
bl_label = "Depth move"
|
|
bl_description = "Move object in the depth from camera POV while retaining same size in framing"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object and context.object.type != 'CAMERA' # and context.scene.camera
|
|
|
|
def invoke(self, context, event):
|
|
self.init_mouse_x = event.mouse_x
|
|
self.cam = bpy.context.scene.camera
|
|
if not self.cam:
|
|
self.report({'ERROR'}, 'No active camera')
|
|
return {"CANCELLED"}
|
|
|
|
self.cam_pos = self.cam.matrix_world.translation
|
|
self.mode = 'distance'
|
|
self.objects = [o for o in context.selected_objects if o.type != 'CAMERA']
|
|
self.init_mats = [o.matrix_world.copy() for o in self.objects]
|
|
|
|
if self.cam.data.type == 'ORTHO':
|
|
context.area.header_text_set(f'Move factor: 0.00')
|
|
# distance is view vector based
|
|
self.view_vector = Vector((0,0,-1))
|
|
self.view_vector.rotate(self.cam.matrix_world)
|
|
else:
|
|
self.init_vecs = [o.matrix_world.translation - self.cam_pos for o in self.objects]
|
|
self.init_dists = [v.length for v in self.init_vecs]
|
|
context.area.header_text_set(f'Move factor: 0.00 | Mode: {self.mode} (M to switch)')
|
|
|
|
context.window_manager.modal_handler_add(self)
|
|
return {'RUNNING_MODAL'}
|
|
|
|
def modal(self, context, event):
|
|
if self.mode == 'distance':
|
|
factor = 0.1
|
|
if event.shift:
|
|
factor = 0.01
|
|
|
|
else:
|
|
# Smaller factor for proportional dist
|
|
factor = 0.01
|
|
if event.shift:
|
|
factor = 0.001
|
|
|
|
if event.type in {'MOUSEMOVE'}:
|
|
diff = (event.mouse_x - self.init_mouse_x) * factor
|
|
|
|
if self.cam.data.type == 'ORTHO':
|
|
# just push in view vector direction
|
|
context.area.header_text_set(f'Move factor: {diff:.2f}')
|
|
for i, obj in enumerate(self.objects):
|
|
new_vec = self.init_mats[i].translation + (self.view_vector * diff)
|
|
obj.matrix_world.translation = new_vec
|
|
else:
|
|
# Push from camera point and scale accordingly
|
|
context.area.header_text_set(f'Move factor: {diff:.2f} | Mode: {self.mode} (M to switch)')
|
|
|
|
for i, obj in enumerate(self.objects):
|
|
if self.mode == 'distance':
|
|
## move with the same length for everyone
|
|
new_vec = self.init_vecs[i] + (self.init_vecs[i].normalized() * diff)
|
|
|
|
else:
|
|
## move with proportional factor from individual distance vector to camera
|
|
new_vec = self.init_vecs[i] + (self.init_vecs[i] * diff)
|
|
|
|
obj.matrix_world.translation = self.cam_pos + new_vec
|
|
dist_percentage = new_vec.length / self.init_dists[i]
|
|
|
|
obj.scale = self.init_mats[i].to_scale() * dist_percentage
|
|
|
|
if event.type in {'M'} and event.value == 'PRESS':
|
|
# Switch mode
|
|
self.mode = 'distance' if self.mode == 'proportional' else 'proportional'
|
|
|
|
if event.type in {'LEFTMOUSE'} and event.value == 'PRESS':
|
|
context.area.header_text_set(None)
|
|
return {"FINISHED"}
|
|
|
|
if event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS':
|
|
for i, obj in enumerate(self.objects):
|
|
obj.matrix_world = self.init_mats[i]
|
|
context.area.header_text_set(None)
|
|
return {"CANCELLED"}
|
|
|
|
return {"RUNNING_MODAL"}
|
|
|
|
""" # Own standalone panel
|
|
class ODM_PT_sudden_depth_panel(bpy.types.Panel):
|
|
bl_space_type = "VIEW_3D"
|
|
bl_region_type = "UI"
|
|
bl_category = "Gpencil"
|
|
bl_label = "Depth move"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
row = layout.row()
|
|
row.operator('object.depth_proportional_move', text='Depth move', icon='TRANSFORM_ORIGINS')
|
|
"""
|
|
|
|
|
|
### --- REGISTER ---
|
|
|
|
classes=(
|
|
ODM_OT_depth_move,
|
|
)
|
|
|
|
def register():
|
|
for cls in classes:
|
|
bpy.utils.register_class(cls)
|
|
|
|
def unregister():
|
|
for cls in reversed(classes):
|
|
bpy.utils.unregister_class(cls) |