Last active
January 29, 2019 11:04
-
-
Save rraallvv/5963246 to your computer and use it in GitHub Desktop.
Enable turntable rotation with any axis up, the axis that is most aligned vertically is the new up axis of the turntable rotation until a new mouse gesture is initiated by either releasing the mouse button or the modifier keyboard key
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
Enable turntable rotation with any axis up, the axis that is most aligned vertically is the new up axis of the turntable rotation until a new mouse gesture is initiated by either releasing the mouse button or the modifier keyboard key | |
''' | |
bl_info = { | |
'name': 'Rotate Turntable ANY-axis Up', | |
'author': '', | |
'version': (0, 0, 1), | |
'blender': (2, 6, 7), | |
'location': '3d view > Ctrl + LMB-drag', | |
'description': 'Enable turntable rotation with any axis up, the axis that is most aligned verticaly is the new up axis.', | |
'wiki_url': '', | |
'tracker_url': '', | |
'category': '3D View'} | |
import bpy | |
from mathutils import * | |
from math import * | |
class RotateTurntableANYUp(bpy.types.Operator): | |
'''Turntable rotation ANY-axis up.''' | |
bl_idname = "view3d.turntable_any_up" | |
bl_label = "Turntable rotation ANY-axis up" | |
angle_yaw=0 | |
angle_pitch=0 | |
axis_selected=0 | |
do_alignment=True | |
def execute(self, context): | |
region_3d = context.space_data.region_3d | |
# world axis in world space | |
xa=Vector((1,0,0)) | |
ya=Vector((0,1,0)) | |
za=Vector((0,0,1)) | |
# camera axis in world space | |
cxa=xa.copy() | |
cxa.rotate(region_3d.view_rotation.inverted()) | |
cya=ya.copy() | |
cya.rotate(region_3d.view_rotation.inverted()) | |
cza=za.copy() | |
cza.rotate(region_3d.view_rotation.inverted()) | |
# relative distance rotation of the camera axes to the world Y axis, if two vectors are parallel the cross product magnitud is zero | |
rdx = cxa.cross(ya).length | |
rdy = cya.cross(ya).length | |
rdz = cza.cross(ya).length | |
# select both, the closest camera axis to the world up axis, and the world up axis | |
if self.axis_selected == 0: | |
if rdx < rdy and rdx < rdz: | |
self.axis_selected=1 | |
elif rdy < rdx and rdy < rdz: | |
self.axis_selected=2 | |
else: | |
self.axis_selected=3 | |
if self.axis_selected == 1: | |
camera_up_axis=cxa | |
world_up_axis=xa | |
elif self.axis_selected == 2: | |
camera_up_axis=cya | |
world_up_axis=ya | |
else: | |
camera_up_axis=cza | |
world_up_axis=za | |
# find rotation yaw in camera space, rotation yaw in world space, separation angle between them, and the rotation needed to align the camera axis up in the world space | |
if camera_up_axis.y > 0: | |
camera_rotation_yaw=Quaternion(camera_up_axis,self.angle_yaw) | |
world_rotation_yaw=Quaternion(world_up_axis,self.angle_yaw) | |
separation_angle=atan2(camera_up_axis.y,camera_up_axis.x)-pi*0.5 | |
rotation_alignment=Quaternion(za,separation_angle) | |
elif camera_up_axis.y < 0: | |
camera_rotation_yaw=Quaternion(camera_up_axis,-self.angle_yaw) | |
world_rotation_yaw=Quaternion(world_up_axis,-self.angle_yaw) | |
separation_angle=atan2(camera_up_axis.y,camera_up_axis.x)+pi*0.5 | |
rotation_alignment=Quaternion(za,separation_angle) | |
else: | |
camera_rotation_yaw=Quaternion((0,0,0,1)) | |
world_rotation_yaw=Quaternion((0,0,0,1)) | |
separation_angle = 0 | |
rotation_alignment=Quaternion((0,0,0,1)) | |
# check if there are selected objects in case of the user preference to rotate around the selection pivot | |
selected_objects = len(bpy.context.selected_objects) > 0 and bpy.context.user_preferences.view.use_rotate_around_active | |
# find the pivot point location | |
if selected_objects: | |
saved_location = bpy.context.scene.cursor_location.copy() | |
bpy.ops.view3d.snap_cursor_to_selected() | |
pivot_location = bpy.context.scene.cursor_location.copy() | |
bpy.context.scene.cursor_location = saved_location | |
# rotate the view_location and view_rotation to perform the turn table rotation | |
if self.do_alignment: | |
# on invocation rotate the view_location to avoid the origin or the pivot to jump abruptly | |
if selected_objects: | |
region_3d.view_location=region_3d.view_location-pivot_location | |
region_3d.view_rotation=region_3d.view_rotation*Quaternion(za, separation_angle) | |
camera_normal_axis=za | |
camera_normal_axis.rotate(region_3d.view_rotation) | |
region_3d.view_location.rotate(Quaternion(camera_normal_axis, separation_angle)) | |
if selected_objects: | |
region_3d.view_location=region_3d.view_location+pivot_location | |
self.do_alignment=False | |
else: | |
# oter wise do the alignment | |
region_3d.view_rotation=region_3d.view_rotation*rotation_alignment | |
# compensate the rotation is there is objects selected, to rotate around the pivot point | |
if selected_objects: | |
camera_horizontal_axis=xa.copy() | |
camera_horizontal_axis.rotate(region_3d.view_rotation) | |
world_rotation_pitch=Quaternion(camera_horizontal_axis,self.angle_pitch) | |
pivot_to_camera = region_3d.view_location - pivot_location | |
pivot_to_camera.rotate(world_rotation_yaw*world_rotation_pitch) | |
region_3d.view_location = pivot_location + pivot_to_camera | |
# find rotation pitch in camera space | |
camera_rotation_pitch=Quaternion(xa,self.angle_pitch) | |
region_3d.view_rotation=region_3d.view_rotation*camera_rotation_yaw*camera_rotation_pitch | |
return {'FINISHED'} | |
def modal(self, context, event): | |
self.report({'INFO'}, event.value) | |
if event.type == 'TRACKPADPAN': | |
region_3d = context.space_data.region_3d | |
#find the yaw and pitch angles from the mouse position variation | |
mouse_pos=Vector((event.mouse_x-event.mouse_prev_x,event.mouse_y - event.mouse_prev_y)) | |
self.angle_yaw=-mouse_pos.x/200.0 | |
self.angle_pitch=mouse_pos.y/200.0 | |
self.execute(context) | |
elif event.type in {'LEFTMOUSE', 'MIDDLEMOUSE', 'RIGHTMOUSE', 'ESC'} or event.value == 'RELEASE': | |
return {'FINISHED'} | |
return {'RUNNING_MODAL'} | |
def invoke(self, context, event): | |
if context.space_data.type == 'VIEW_3D': | |
region_3d = context.space_data.region_3d | |
mouse_pos=Vector((event.mouse_x-event.mouse_prev_x,event.mouse_y - event.mouse_prev_y)) | |
self.axis_selected=0 | |
self.do_alignment=True | |
context.window_manager.modal_handler_add(self) | |
if region_3d.view_perspective == 'CAMERA': | |
region_3d.view_perspective = 'PERSP' | |
return {'RUNNING_MODAL'} | |
else: | |
return {'CANCELLED'} | |
def register(): | |
bpy.utils.register_class(RotateTurntableANYUp) | |
wm = bpy.context.window_manager | |
km = wm.keyconfigs.addon.keymaps.new(name="3D View", space_type='VIEW_3D') | |
#kmi = km.keymap_items.new('view3d.turntable_any_up', 'EVT_TWEAK_L', 'ANY', shift=False, ctrl=True, alt=False) | |
kmi = km.keymap_items.new('view3d.turntable_any_up', 'TRACKPADPAN', 'ANY', shift=False, ctrl=False, alt=True, oskey=False) | |
def unregister(): | |
bpy.utils.unregister_class(RotateTurntableANYUp) | |
wm = bpy.context.window_manager | |
km = wm.keyconfigs.addon.keymaps['3D View'] | |
for kmi in km.keymap_items: | |
if kmi.idname == 'view3d.turntable_any_up': | |
km.keymap_items.remove(kmi) | |
if __name__ == "__main__": | |
register() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment