gp_toolbox/OP_canvas_rotate.py

284 lines
11 KiB
Python
Raw Normal View History

2021-01-10 16:47:17 +01:00
from .utils import get_addon_prefs
## known issue: auto-perspective mess up when triggered out after rotation
import bpy
import math
import mathutils
from bpy_extras.view3d_utils import location_3d_to_region_2d
## draw utils
import gpu
import bgl
import blf
from gpu_extras.batch import batch_for_shader
from gpu_extras.presets import draw_circle_2d
"""
Notes:
Samuel.B:
OpenGL drawing can be disabled by passing self.hud to False in invoke (mainly used for debugging)
Base script by Jum, simplified and modified to work in both view and camera with rotate axis method suggested by Christophe Seux
Jum:
Script base. Thanks to bigLarry and Jum
https://blender.stackexchange.com/questions/136183/rotating-camera-view-in-grease-pencil-draw-mode-in-blender-2-8
"""
def draw_callback_px(self, context):
# 50% alpha, 2 pixel width line
shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
bgl.glEnable(bgl.GL_BLEND)
bgl.glLineWidth(2)
# init
batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": [self.center, self.initial_pos]})#self.vector_initial
shader.bind()
shader.uniform_float("color", (0.5, 0.5, 0.8, 0.6))
batch.draw(shader)
batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": [self.center, self.pos_current]})
shader.bind()
shader.uniform_float("color", (0.3, 0.7, 0.2, 0.5))
batch.draw(shader)
## vector init vector current (substracted by center)
# batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": [self.vector_initial, self.vector_current]})
# shader.bind()
# shader.uniform_float("color", (0.5, 0.5, 0.5, 0.5))
# batch.draw(shader)
# restore opengl defaults
bgl.glLineWidth(1)
bgl.glDisable(bgl.GL_BLEND)
## text
font_id = 0
## draw text debug infos
blf.position(font_id, 15, 30, 0)
blf.size(font_id, 20, 72)
blf.draw(font_id, f'angle: {math.degrees(self.angle):.1f}')
class RC_OT_RotateCanvas(bpy.types.Operator):
bl_idname = 'view3d.rotate_canvas'
bl_label = 'Rotate Canvas'
bl_options = {"REGISTER", "UNDO"}
# @classmethod
# def poll(cls, context):
# return context.region_data.view_perspective == 'CAMERA'
"""
def get_center_view(self, area, cam):
'''
https://blender.stackexchange.com/questions/6377/coordinates-of-corners-of-camera-view-border
Thanks to ideasman42
'''
region_3d = area.spaces[0].region_3d
for region in area.regions:
if region.type == 'WINDOW':
frame = cam.data.view_frame()
# if cam.parent:
# mat = cam.matrix_parent_inverse @ cam.matrix_world
# # mat = cam.parent.matrix_world @ cam.matrix_world# not inverse from parent
# else:
# mat = cam.matrix_world
mat = cam.matrix_world
frame = [mat @ v for v in frame]
## bpy.context.scene.cursor.location = frame[1]#DEBUG
frame_px = [location_3d_to_region_2d(region, region_3d, v) for v in frame]
center_x = frame_px[2].x + (frame_px[0].x - frame_px[2].x)/2
center_y = frame_px[1].y + (frame_px[0].y - frame_px[1].y)/2
return mathutils.Vector((center_x, center_y))
return None """
def get_center_view(self, context, cam):
'''
https://blender.stackexchange.com/questions/6377/coordinates-of-corners-of-camera-view-border
Thanks to ideasman42
'''
frame = cam.data.view_frame()
mat = cam.matrix_world
frame = [mat @ v for v in frame]
frame_px = [location_3d_to_region_2d(context.region, context.space_data.region_3d, v) for v in frame]
center_x = frame_px[2].x + (frame_px[0].x - frame_px[2].x)/2
center_y = frame_px[1].y + (frame_px[0].y - frame_px[1].y)/2
return mathutils.Vector((center_x, center_y))
def execute(self, context):
if self.hud:
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
context.area.tag_redraw()
if self.in_cam:
self.cam.rotation_mode = self.org_rotation_mode
return {'FINISHED'}
def modal(self, context, event):
if event.type in {'MOUSEMOVE','INBETWEEN_MOUSEMOVE'}:
# Get current mouse coordination (region)
self.pos_current = mathutils.Vector((event.mouse_region_x, event.mouse_region_y))
# Get current vector
self.vector_current = (self.pos_current - self.center).normalized()
# Calculates the angle between initial and current vectors
self.angle = self.vector_initial.angle_signed(self.vector_current)#radian
# print (math.degrees(self.angle), self.vector_initial, self.vector_current)
if self.in_cam:
self.cam.matrix_world = self.cam_matrix
# self.cam.rotation_euler = self.cam_init_euler
self.cam.rotation_euler.rotate_axis("Z", self.angle)
else:#free view
context.space_data.region_3d.view_matrix = self.view_matrix
rot = context.space_data.region_3d.view_rotation
rot = rot.to_euler()
rot.rotate_axis("Z", self.angle)
context.space_data.region_3d.view_rotation = rot.to_quaternion()
if event.type in {'RIGHTMOUSE', 'LEFTMOUSE', 'MIDDLEMOUSE'} and event.value == 'RELEASE':
self.execute(context)
return {'FINISHED'}
if event.type == 'ESC':#Cancel
self.execute(context)
if self.in_cam:
self.cam.matrix_world = self.cam_matrix
else:
context.space_data.region_3d.view_matrix = self.view_matrix
return {'CANCELLED'}
return {'RUNNING_MODAL'}
def invoke(self, context, event):
self.hud = False
self.angle = 0.0# for draw degub, else not needed
self.in_cam = context.region_data.view_perspective == 'CAMERA'
if self.in_cam:
# Get camera from scene
self.cam = bpy.context.scene.camera
## avoid manipulating real cam or locked cams
if not 'manip_cams' in [c.name for c in self.cam.users_collection]:
self.report({'WARNING'}, 'Not in manipulation cam (draw/obj cam)')
return {'CANCELLED'}
if self.cam.lock_rotation[:] != (False, False, False):
self.report({'WARNING'}, 'Camera rotation is locked')
return {'CANCELLED'}
self.center = self.get_center_view(context, self.cam)
# store original rotation mode
self.org_rotation_mode = self.cam.rotation_mode
# set to euler to works with quaternions, restored at finish
self.cam.rotation_mode = 'XYZ'
# store camera matrix world
self.cam_matrix = self.cam.matrix_world.copy()
# self.cam_init_euler = self.cam.rotation_euler.copy()
else:
self.center = mathutils.Vector((context.area.width/2, context.area.height/2))
# store current view matrix
self.view_matrix = context.space_data.region_3d.view_matrix.copy()
# Get current mouse coordination
self.pos_current = mathutils.Vector((event.mouse_region_x, event.mouse_region_y))
self.initial_pos = self.pos_current# for draw debug, else no need
# Calculate inital vector
self.vector_initial = self.pos_current - self.center
self.vector_initial.normalize()
# Initializes the current vector with the same initial vector.
self.vector_current = self.vector_initial.copy()
args = (self, context)
if self.hud:
self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
class PREFS_OT_rebind(bpy.types.Operator):
"""Rebind shortcuts canvas rotate shortcuts"""
bl_idname = "prefs.rebind_shortcut"
bl_label = "Rebind canvas rotate shortcut"
bl_options = {'REGISTER', 'INTERNAL'}
def execute(self, context):
unregister_keymaps()
register_keymaps()
return{'FINISHED'}
addon_keymaps = []
def register_keymaps():
pref = get_addon_prefs()
if not pref.canvas_use_shortcut:
return
addon = bpy.context.window_manager.keyconfigs.addon
""" ## NATIVE FREENAV BIND (left to right)
km = bpy.context.window_manager.keyconfigs.addon.keymaps.get("3D View")
if not km:
km = addon.keymaps.new(name = "3D View", space_type = "VIEW_3D")
# print('BINDING CANVAS ROTATE KEYMAPS')#Dbg
if 'view3d.view_roll' not in km.keymap_items:
# print('creating view3d.view_roll')#Dbg
# kmi = km.keymap_items.new("view3d.view_roll", type = 'MIDDLEMOUSE', value = "PRESS", ctrl=True, shift=False, alt=True)#PRESS#CLICK_DRAG
kmi = km.keymap_items.new("view3d.view_roll", type=pref.mouse_click, value = "PRESS", alt=pref.use_alt, ctrl=pref.use_ctrl, shift=pref.use_shift, any=False)#PRESS#CLICK_DRAG
kmi.properties.type = 'ANGLE'
addon_keymaps.append(km)
"""
km = bpy.context.window_manager.keyconfigs.addon.keymaps.get("3D View")
if not km:
km = addon.keymaps.new(name = "3D View", space_type = "VIEW_3D")
if 'view3d.rotate_canvas' not in km.keymap_items:
# print('creating view3d.rotate_canvas')#Dbg
## keymap to operator cam space (in grease pencil mode only ?)
km = addon.keymaps.new(name='3D View', space_type='VIEW_3D')#EMPTY #Grease Pencil
# kmi = km.keymap_items.new('view3d.rotate_canvas', 'MIDDLEMOUSE', 'PRESS', ctrl=True, shift=False, alt=True)
kmi = km.keymap_items.new('view3d.rotate_canvas', type=pref.mouse_click, value="PRESS", alt=pref.use_alt, ctrl=pref.use_ctrl, shift=pref.use_shift, any=False)
addon_keymaps.append((km, kmi))
# print(addon_keymaps)
def unregister_keymaps():
# print('UNBIND CANVAS ROTATE KEYMAPS')#Dbg
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
# del addon_keymaps[:]
canvas_classes = (
PREFS_OT_rebind,
RC_OT_RotateCanvas,
# RC_OT_RotateCanvasFreeNav
)
def register():
if not bpy.app.background:
for cls in canvas_classes:
bpy.utils.register_class(cls)
register_keymaps()
# wm = bpy.context.window_manager
# km = wm.keyconfigs.addon.keymaps.new(name='Grease Pencil', space_type='EMPTY')
# kmi = km.keymap_items.new('view3d.rotate_canvas', 'MIDDLEMOUSE', 'PRESS', ctrl=True, shift=False, alt=True)
# addon_keymaps.append(km)
def unregister():
if not bpy.app.background:
unregister_keymaps()
for cls in reversed(canvas_classes):
bpy.utils.unregister_class(cls)
# if __name__ == "__main__":
# register()