Native addon compatibility

1.0.0:

- Compatible with official grease pencil tools
  - removed box deform and rotate canvas that existed in other
gpv2
Pullusb 2021-01-19 19:49:16 +01:00
parent 6774484226
commit fdbfaaa1e0
6 changed files with 17 additions and 944 deletions

View File

@ -1,579 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
'''
Based on Box_deform addon
! Standalone file ! Stripped preference, and set best default auto transform)
'''
import bpy
import numpy as np
""" def get_addon_prefs():
import os
addon_name = os.path.splitext(__name__)[0]
preferences = bpy.context.preferences
addon_prefs = preferences.addons[addon_name].preferences
return (addon_prefs) """
def location_to_region(worldcoords):
from bpy_extras import view3d_utils
return view3d_utils.location_3d_to_region_2d(bpy.context.region, bpy.context.space_data.region_3d, worldcoords)
def region_to_location(viewcoords, depthcoords):
from bpy_extras import view3d_utils
return view3d_utils.region_2d_to_location_3d(bpy.context.region, bpy.context.space_data.region_3d, viewcoords, depthcoords)
def assign_vg(obj, vg_name):
## create vertex group
vg = obj.vertex_groups.get(vg_name)
if vg:
# remove to start clean
obj.vertex_groups.remove(vg)
vg = obj.vertex_groups.new(name=vg_name)
bpy.ops.gpencil.vertex_group_assign()
return vg
def view_cage(obj):
lattice_interp = 'KEY_LINEAR'#get_addon_prefs().default_deform_type
gp = obj.data
gpl = gp.layers
coords = []
initial_mode = bpy.context.mode
## get points
if bpy.context.mode == 'EDIT_GPENCIL':
for l in gpl:
if l.lock or l.hide or not l.active_frame:#or len(l.frames)
continue
if gp.use_multiedit:
target_frames = [f for f in l.frames if f.select]
else:
target_frames = [l.active_frame]
for f in target_frames:
for s in f.strokes:
if not s.select:
continue
for p in s.points:
if p.select:
# get real location
coords.append(obj.matrix_world @ p.co)
elif bpy.context.mode == 'OBJECT':#object mode -> all points
for l in gpl:# if l.hide:continue# only visible ? (might break things)
if not len(l.frames):
continue#skip frameless layer
for s in l.active_frame.strokes:
for p in s.points:
coords.append(obj.matrix_world @ p.co)
elif bpy.context.mode == 'PAINT_GPENCIL':
# get last stroke points coordinated
if not gpl.active or not gpl.active.active_frame:
return 'No frame to deform'
if not len(gpl.active.active_frame.strokes):
return 'No stroke found to deform'
paint_id = -1
if bpy.context.scene.tool_settings.use_gpencil_draw_onback:
paint_id = 0
coords = [obj.matrix_world @ p.co for p in gpl.active.active_frame.strokes[paint_id].points]
else:
return 'Wrong mode!'
if not coords:
## maybe silent return instead (need special str code to manage errorless return)
return 'No points found!'
if bpy.context.mode in ('EDIT_GPENCIL', 'PAINT_GPENCIL') and len(coords) < 2:
# Dont block object mod
return 'Less than two point selected'
vg_name = 'lattice_cage_deform_group'
if bpy.context.mode == 'EDIT_GPENCIL':
vg = assign_vg(obj, vg_name)
if bpy.context.mode == 'PAINT_GPENCIL':
# points cannot be assign to API yet(ugly and slow workaround but only way)
# -> https://developer.blender.org/T56280 so, hop'in'ops !
# store selection and deselect all
plist = []
for s in gpl.active.active_frame.strokes:
for p in s.points:
plist.append([p, p.select])
p.select = False
# select
## foreach_set does not update
# gpl.active.active_frame.strokes[paint_id].points.foreach_set('select', [True]*len(gpl.active.active_frame.strokes[paint_id].points))
for p in gpl.active.active_frame.strokes[paint_id].points:
p.select = True
# assign
bpy.ops.object.mode_set(mode='EDIT_GPENCIL')
vg = assign_vg(obj, vg_name)
# restore
for pl in plist:
pl[0].select = pl[1]
## View axis Mode ---
## get view coordinate of all points
coords2D = [location_to_region(co) for co in coords]
# find centroid for depth (or more economic, use obj origin...)
centroid = np.mean(coords, axis=0)
# not a mean ! a mean of extreme ! centroid2d = np.mean(coords2D, axis=0)
all_x, all_y = np.array(coords2D)[:, 0], np.array(coords2D)[:, 1]
min_x, min_y = np.min(all_x), np.min(all_y)
max_x, max_y = np.max(all_x), np.max(all_y)
width = (max_x - min_x)
height = (max_y - min_y)
center_x = min_x + (width/2)
center_y = min_y + (height/2)
centroid2d = (center_x,center_y)
center = region_to_location(centroid2d, centroid)
# bpy.context.scene.cursor.location = center#Dbg
#corner Bottom-left to Bottom-right
x0 = region_to_location((min_x, min_y), centroid)
x1 = region_to_location((max_x, min_y), centroid)
x_worldsize = (x0 - x1).length
#corner Bottom-left to top-left
y0 = region_to_location((min_x, min_y), centroid)
y1 = region_to_location((min_x, max_y), centroid)
y_worldsize = (y0 - y1).length
## in case of 3
lattice_name = 'lattice_cage_deform'
# cleaning
cage = bpy.data.objects.get(lattice_name)
if cage:
bpy.data.objects.remove(cage)
lattice = bpy.data.lattices.get(lattice_name)
if lattice:
bpy.data.lattices.remove(lattice)
# create lattice object
lattice = bpy.data.lattices.new(lattice_name)
cage = bpy.data.objects.new(lattice_name, lattice)
cage.show_in_front = True
## Master (root) collection
bpy.context.scene.collection.objects.link(cage)
# spawn cage and align it to view (Again ! align something to a vector !!! argg)
r3d = bpy.context.space_data.region_3d
viewmat = r3d.view_matrix
cage.matrix_world = viewmat.inverted()
cage.scale = (x_worldsize, y_worldsize, 1)
## Z aligned in view direction (need minus X 90 degree to be aligned FRONT)
# cage.rotation_euler.x -= radians(90)
# cage.scale = (x_worldsize, 1, y_worldsize)
cage.location = center
lattice.points_u = 2
lattice.points_v = 2
lattice.points_w = 1
lattice.interpolation_type_u = lattice_interp#'KEY_LINEAR'-'KEY_BSPLINE'
lattice.interpolation_type_v = lattice_interp#'KEY_LINEAR'-'KEY_BSPLINE'
lattice.interpolation_type_w = lattice_interp#'KEY_LINEAR'-'KEY_BSPLINE'
mod = obj.grease_pencil_modifiers.new('tmp_lattice', 'GP_LATTICE')
# move to top if modifiers exists
for _ in range(len(obj.grease_pencil_modifiers)):
bpy.ops.object.gpencil_modifier_move_up(modifier='tmp_lattice')
mod.object = cage
if initial_mode == 'PAINT_GPENCIL':
mod.layer = gpl.active.info
# note : if initial was Paint, changed to Edit
# so vertex attribution is valid even for paint
if bpy.context.mode == 'EDIT_GPENCIL':
mod.vertex_group = vg.name
#Go in object mode if not already
if bpy.context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')
# Store name of deformed object in case of 'revive modal'
cage.vertex_groups.new(name=obj.name)
## select and make cage active
# cage.select_set(True)
bpy.context.view_layer.objects.active = cage
obj.select_set(False)#deselect GP object
bpy.ops.object.mode_set(mode='EDIT')# go in lattice edit mode
bpy.ops.lattice.select_all(action='SELECT')# select all points
## Eventually change tool mode to tweak for direct point editing (reset after before leaving)
bpy.ops.wm.tool_set_by_id(name="builtin.select")# Tweaktoolcode
return cage
def back_to_obj(obj, gp_mode, org_lattice_toolset, context):
if context.mode == 'EDIT_LATTICE' and org_lattice_toolset:# Tweaktoolcode - restore the active tool used by lattice edit..
bpy.ops.wm.tool_set_by_id(name = org_lattice_toolset)# Tweaktoolcode
# gp object active and selected
bpy.ops.object.mode_set(mode='OBJECT')
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
def delete_cage(cage):
lattice = cage.data
bpy.data.objects.remove(cage)
bpy.data.lattices.remove(lattice)
def apply_cage(gp_obj, cage):
mod = gp_obj.grease_pencil_modifiers.get('tmp_lattice')
if mod:
bpy.ops.object.gpencil_modifier_apply(apply_as='DATA', modifier=mod.name)
else:
print('tmp_lattice modifier not found to apply...')
delete_cage(cage)
def cancel_cage(gp_obj, cage):
#remove modifier
mod = gp_obj.grease_pencil_modifiers.get('tmp_lattice')
if mod:
gp_obj.grease_pencil_modifiers.remove(mod)
else:
print('tmp_lattice modifier not found to remove...')
delete_cage(cage)
class GP_OT_latticeGpDeform(bpy.types.Operator):
"""Create a lattice to use as quad corner transform"""
bl_idname = "gp.latticedeform"
bl_label = "Box deform"
bl_description = "Use lattice for free box transforms on grease pencil points (Ctrl+T)"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.object is not None and context.object.type in ('GPENCIL','LATTICE')
# local variable
tab_press_ct = 0
def modal(self, context, event):
display_text = f"Deform Cage size: {self.lat.points_u}x{self.lat.points_v} (1-9 or ctrl + ←→↑↓]) | \
mode (M) : {'Linear' if self.lat.interpolation_type_u == 'KEY_LINEAR' else 'Spline'} | \
valid:Spacebar/Enter/Tab, cancel:Del/Backspace"
context.area.header_text_set(display_text)
## Handle ctrl+Z
if event.type in {'Z'} and event.value == 'PRESS' and event.ctrl:
## Disable (capture key)
return {"RUNNING_MODAL"}
## Not found how possible to find modal start point in undo stack to
# print('ops list', context.window_manager.operators.keys())
# if context.window_manager.operators:#can be empty
# print('\nlast name', context.window_manager.operators[-1].name)
# Auto interpo check
if self.auto_interp:
if event.type in {'TWO', 'THREE', 'FOUR', 'FIVE', 'SIX', 'SEVEN', 'EIGHT', 'NINE', 'ZERO',} and event.value == 'PRESS':
self.set_lattice_interp('KEY_BSPLINE')
if event.type in {'DOWN_ARROW', "UP_ARROW", "RIGHT_ARROW", "LEFT_ARROW"} and event.value == 'PRESS' and event.ctrl:
self.set_lattice_interp('KEY_BSPLINE')
if event.type in {'ONE'} and event.value == 'PRESS':
self.set_lattice_interp('KEY_LINEAR')
# Single keys
if event.type in {'H'} and event.value == 'PRESS':
# self.report({'INFO'}, "Can't hide")
return {"RUNNING_MODAL"}
if event.type in {'ONE'} and event.value == 'PRESS':# , 'NUMPAD_1'
self.lat.points_u = self.lat.points_v = 2
return {"RUNNING_MODAL"}
if event.type in {'TWO'} and event.value == 'PRESS':# , 'NUMPAD_2'
self.lat.points_u = self.lat.points_v = 3
return {"RUNNING_MODAL"}
if event.type in {'THREE'} and event.value == 'PRESS':# , 'NUMPAD_3'
self.lat.points_u = self.lat.points_v = 4
return {"RUNNING_MODAL"}
if event.type in {'FOUR'} and event.value == 'PRESS':# , 'NUMPAD_4'
self.lat.points_u = self.lat.points_v = 5
return {"RUNNING_MODAL"}
if event.type in {'FIVE'} and event.value == 'PRESS':# , 'NUMPAD_5'
self.lat.points_u = self.lat.points_v = 6
return {"RUNNING_MODAL"}
if event.type in {'SIX'} and event.value == 'PRESS':# , 'NUMPAD_6'
self.lat.points_u = self.lat.points_v = 7
return {"RUNNING_MODAL"}
if event.type in {'SEVEN'} and event.value == 'PRESS':# , 'NUMPAD_7'
self.lat.points_u = self.lat.points_v = 8
return {"RUNNING_MODAL"}
if event.type in {'EIGHT'} and event.value == 'PRESS':# , 'NUMPAD_8'
self.lat.points_u = self.lat.points_v = 9
return {"RUNNING_MODAL"}
if event.type in {'NINE'} and event.value == 'PRESS':# , 'NUMPAD_9'
self.lat.points_u = self.lat.points_v = 10
return {"RUNNING_MODAL"}
if event.type in {'ZERO'} and event.value == 'PRESS':# , 'NUMPAD_0'
self.lat.points_u = 2
self.lat.points_v = 1
return {"RUNNING_MODAL"}
if event.type in {'RIGHT_ARROW'} and event.value == 'PRESS' and event.ctrl:
if self.lat.points_u < 20:
self.lat.points_u += 1
return {"RUNNING_MODAL"}
if event.type in {'LEFT_ARROW'} and event.value == 'PRESS' and event.ctrl:
if self.lat.points_u > 1:
self.lat.points_u -= 1
return {"RUNNING_MODAL"}
if event.type in {'UP_ARROW'} and event.value == 'PRESS' and event.ctrl:
if self.lat.points_v < 20:
self.lat.points_v += 1
return {"RUNNING_MODAL"}
if event.type in {'DOWN_ARROW'} and event.value == 'PRESS' and event.ctrl:
if self.lat.points_v > 1:
self.lat.points_v -= 1
return {"RUNNING_MODAL"}
# change modes
if event.type in {'M'} and event.value == 'PRESS':
self.auto_interp = False
interp = 'KEY_BSPLINE' if self.lat.interpolation_type_u == 'KEY_LINEAR' else 'KEY_LINEAR'
self.set_lattice_interp(interp)
return {"RUNNING_MODAL"}
# Valid
if event.type in {'RET', 'SPACE'}:
if event.value == 'PRESS':
#bpy.ops.ed.flush_edits()# TODO: find a way to get rid of undo-registered lattices tweaks
self.restore_prefs(context)
back_to_obj(self.gp_obj, self.gp_mode, self.org_lattice_toolset, context)
apply_cage(self.gp_obj, self.cage)#must be in object mode
# back to original mode
if self.gp_mode != 'OBJECT':
bpy.ops.object.mode_set(mode=self.gp_mode)
context.area.header_text_set(None)#reset header
return {'FINISHED'}
# Abort ---
# One Warning for Tab cancellation.
if event.type == 'TAB' and event.value == 'PRESS':
self.tab_press_ct += 1
if self.tab_press_ct < 2:
self.report({'WARNING'}, "Pressing TAB again will Cancel")
return {"RUNNING_MODAL"}
if event.type in {'T'} and event.value == 'PRESS' and event.ctrl:# Retyped same shortcut
self.cancel(context)
return {'CANCELLED'}
if event.type in {'DEL', 'BACK_SPACE'} or self.tab_press_ct >= 2:#'ESC',
self.cancel(context)
return {'CANCELLED'}
return {'PASS_THROUGH'}
def set_lattice_interp(self, interp):
self.lat.interpolation_type_u = self.lat.interpolation_type_v = self.lat.interpolation_type_w = interp
def cancel(self, context):
self.restore_prefs(context)
back_to_obj(self.gp_obj, self.gp_mode, self.org_lattice_toolset, context)
cancel_cage(self.gp_obj, self.cage)
context.area.header_text_set(None)
if self.gp_mode != 'OBJECT':
bpy.ops.object.mode_set(mode=self.gp_mode)
def store_prefs(self, context):
# store_valierables <-< preferences
self.use_drag_immediately = context.preferences.inputs.use_drag_immediately
self.drag_threshold_mouse = context.preferences.inputs.drag_threshold_mouse
self.drag_threshold_tablet = context.preferences.inputs.drag_threshold_tablet
self.use_overlays = context.space_data.overlay.show_overlays
def restore_prefs(self, context):
# preferences <-< store_valierables
context.preferences.inputs.use_drag_immediately = self.use_drag_immediately
context.preferences.inputs.drag_threshold_mouse = self.drag_threshold_mouse
context.preferences.inputs.drag_threshold_tablet = self.drag_threshold_tablet
context.space_data.overlay.show_overlays = self.use_overlays
def set_prefs(self, context):
context.preferences.inputs.use_drag_immediately = True
context.preferences.inputs.drag_threshold_mouse = 1
context.preferences.inputs.drag_threshold_tablet = 3
context.space_data.overlay.show_overlays = True
def invoke(self, context, event):
## Restrict to 3D view
if context.area.type != 'VIEW_3D':
self.report({'WARNING'}, "View3D not found, cannot run operator")
return {'CANCELLED'}
if not context.object:#do it in poll ?
self.report({'ERROR'}, "No active objects found")
return {'CANCELLED'}
# self.prefs = get_addon_prefs()#get_prefs
self.org_lattice_toolset = None
self.gp_mode = 'EDIT_GPENCIL'
# --- special Case of lattice revive modal, just after ctrl+Z back into lattice with modal stopped
if context.mode == 'EDIT_LATTICE' and context.object.name == 'lattice_cage_deform' and len(context.object.vertex_groups):
self.gp_obj = context.scene.objects.get(context.object.vertex_groups[0].name)
if not self.gp_obj:
self.report({'ERROR'}, "/!\\ Box Deform : Cannot find object to target")
return {'CANCELLED'}
if not self.gp_obj.grease_pencil_modifiers.get('tmp_lattice'):
self.report({'ERROR'}, "/!\\ No 'tmp_lattice' modifiers on GP object")
return {'CANCELLED'}
self.cage = context.object
self.lat = self.cage.data
self.set_prefs(context)
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
if context.object.type != 'GPENCIL':
# self.report({'ERROR'}, "Works only on gpencil objects")
## silent return
return {'CANCELLED'}
#paint need VG workaround. object need good shortcut
if context.mode not in ('EDIT_GPENCIL', 'OBJECT', 'PAINT_GPENCIL'):
# self.report({'WARNING'}, "Works only in following GPencil modes: edit")# ERROR
## silent return
return {'CANCELLED'}
# bpy.ops.ed.undo_push(message="Box deform step")#don't work as expected (+ might be obsolete)
# https://developer.blender.org/D6147 <- undo forget
self.gp_obj = context.object
# Clean potential failed previous job (delete tmp lattice)
mod = self.gp_obj.grease_pencil_modifiers.get('tmp_lattice')
if mod:
print('Deleted remaining lattice modifiers')
self.gp_obj.grease_pencil_modifiers.remove(mod)
phantom_obj = context.scene.objects.get('lattice_cage_deform')
if phantom_obj:
print('Deleted remaining lattice object')
delete_cage(phantom_obj)
if [m for m in self.gp_obj.grease_pencil_modifiers if m.type == 'GP_LATTICE']:
self.report({'ERROR'}, "Grease pencil object already has a lattice modifier (can only have one)")
return {'CANCELLED'}
self.gp_mode = context.mode#store mode for restore
# All good, create lattice and start modal
# Create lattice (and switch to lattice edit) ----
self.cage = view_cage(self.gp_obj)
if isinstance(self.cage, str):#error, cage not created, display error
self.report({'ERROR'}, self.cage)
return {'CANCELLED'}
self.lat = self.cage.data
## usability toggles
## pref for clic drag -> if self.prefs.use_clic_drag:#Store the active tool since we will change it
self.org_lattice_toolset = bpy.context.workspace.tools.from_space_view3d_mode(bpy.context.mode, create=False).idname# Tweaktoolcode
self.auto_interp = True#self.prefs.auto_swap_deform_type
#store (scene properties needed in case of ctrlZ revival)
self.store_prefs(context)
self.set_prefs(context)
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
## --- KEYMAP
addon_keymaps = []
def register_keymaps():
addon = bpy.context.window_manager.keyconfigs.addon
km = addon.keymaps.new(name = "Grease Pencil", space_type = "EMPTY", region_type='WINDOW')
kmi = km.keymap_items.new("gp.latticedeform", type ='T', value = "PRESS", ctrl = True)
kmi.repeat = False
addon_keymaps.append((km, kmi))
def unregister_keymaps():
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
### --- REGISTER ---
def register():
if bpy.app.background:
return
bpy.utils.register_class(GP_OT_latticeGpDeform)
register_keymaps()
def unregister():
if bpy.app.background:
return
unregister_keymaps()
bpy.utils.unregister_class(GP_OT_latticeGpDeform)

View File

@ -1,284 +0,0 @@
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()

View File

@ -54,8 +54,6 @@ Add an "on save" Handler that trigger relative remap of all path.
- Choose key to Auto bind in addon prefs (since 0.9.3).
> Manual setup: Add two keymap shortcut in _windows_ or _screen(global)_ with indentifier `screen.gp_keyframe_jump`, one should have `next` toggled off to jump back
- Rotate canvas with a clic + modifier combo (default is `ctrl + alt + MID-mouse`), can be change in addon preferences.
- GP paint cutter tool temporary switch shortcut
- Map manually to a key with `wm.temp_cutter` (This one needs "Any" as press mode) or `wm.sticky_cutter` (Modal sticky-key version)
@ -103,6 +101,11 @@ Panel in sidebar : 3D view > sidebar 'N' > Gpencil
## Changelog:
1.0.0:
- Compatible with official grease pencil tools
- removed box deform and rotate canvas that existed in other
0.9.3:
- feat: keyframe jump keys are now auto-binded

View File

@ -37,10 +37,6 @@ Expose les options suivantes:
L'action cam (peut-être renommé en follow_cam ?) fonctionne sur le même principe que la draw sauf qu'elle se parente à l'objet selectionné. Le but est de pouvoir suivre un objet en mouvement pour le dessiner en continu sans nécessairement désactiver les anims. Concrètement, désactiver les animation d'objets est sans doute plus clean et sans doute moins galère donc option a potentiellement retirer dans le futur.
**Rotate canvas** (`ctrl + alt + clic-droit`) - Différence avec celui intégré a grease pencil tools : la rotation en vue cam n'est possible que si on est dans une caméra de manipulation (`manip_cam`) pour éviter de casser l'anim le roll de la cam principale.
**Box deform** (`Ctrl+T`) - Déformation 4 coins (Déjà dans _Grease pencil tools_ donc a potentiellement retirer pour éviter de possible confits/redondances, mais d'un autre côté la version intégrée peut être customisée et corriger d'éventuel souci sans attendre les updates de la version native...)
**GP keyframe jump** (auto bind et personnalisable dans les addon prefs depuis 0.9.3) - Essentiel ! Permet d'ajouter une ou plusieurs paire de raccourcis pour aller de keyframe en keyframe (filtrage du saut personnalisable). Lance l'operateur `screen.gp_keyframe_jump`, (si ajout manuel, rajouter une seconde keymap avec la propriété `next` décoché pour faire un saut arrière)
**Breakdown en mode objet** (`Shift+E`) - Breakdown en pourcentage entre les deux keyframes (pose une clé si lauto-key est actif et utilise le keying set si actif). Même comportement et raccourci que le breakdown de bones en pose mode (juste qu'il n'existait bizzarement pas en objet)
@ -68,6 +64,10 @@ Permet également de copier l'intégralité des layers selectionnés avec le bou
- Warn if there are some disabled animation (and list datapath)
- Set onion skin filter to 'All type' (this became default in blender 2.91, guess who asked ;) )
EDIT: _rotate canvas_ et _box deform_ ont été retiré dans la version 1.0 car déja intégré à l'addon natif **grease pencil tools** depuis la 2.91 (activez simplement cet addon)
> **Rotate canvas** (`ctrl + alt + clic-droit`) - Différence avec celui intégré a grease pencil tools : la rotation en vue cam n'est possible que si on est dans une caméra de manipulation (`manip_cam`) pour éviter de casser l'anim le roll de la cam principale.
> **Box deform** (`Ctrl+T`) - Déformation 4 coins (Déjà dans _Grease pencil tools_ donc a potentiellement retirer pour éviter de possible confits/redondances, mais d'un autre côté la version intégrée peut être customisée et corriger d'éventuel souci sans attendre les updates de la version native...)
### moins important

View File

@ -12,10 +12,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
bl_info = {
"name": "gp toolbox",
"name": "GP toolbox",
"description": "Set of tools for Grease Pencil in animation production",
"author": "Samuel Bernou",
"version": (0, 9, 3),
"version": (1, 0, 0),
"blender": (2, 91, 0),
"location": "sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
"warning": "",
@ -35,12 +35,10 @@ from .GP_guided_colorize import GP_colorize
## direct tools
from . import OP_breakdowner
from . import OP_temp_cutter
from . import OP_canvas_rotate
from . import OP_playblast_bg
from . import OP_playblast
from . import OP_helpers
from . import OP_keyframe_jump
from . import OP_box_deform
from . import OP_cursor_snap_canvas
from . import OP_palettes
from . import OP_file_checker
@ -82,7 +80,6 @@ class GPTB_prefs(bpy.types.AddonPreferences):
items=(('PREF', "Preferences", "Change some preferences of the modal"),
('MAN_OPS', "Operator", "Operator to add Manually"),
# ('TUTO', "Tutorial", "How to use the tool"),
# ('GMIC', "Gmic color", "Options to use gmic to colorize"),
('UPDATE', "Update", "Check and apply updates"),
# ('KEYMAP', "Keymap", "customise the default keymap"),
),
@ -176,36 +173,6 @@ class GPTB_prefs(bpy.types.AddonPreferences):
default=False,
)
## Canvas rotate
canvas_use_shortcut: BoolProperty(
name = "Use Default Shortcut",
description = "Use default shortcut: mouse double-click + modifier",
default = True)
mouse_click : EnumProperty(
name="Mouse button", description="click on right/left/middle mouse button in combination with a modifier to trigger alignement",
default='RIGHTMOUSE',
items=(
('RIGHTMOUSE', 'Right click', 'Use click on Right mouse button', 'MOUSE_RMB', 0),
('LEFTMOUSE', 'Left click', 'Use click on Left mouse button', 'MOUSE_LMB', 1),
('MIDDLEMOUSE', 'Mid click', 'Use click on Mid mouse button', 'MOUSE_MMB', 2),
))
use_shift: BoolProperty(
name = "combine with shift",
description = "add shift combined with double click to trigger alignement",
default = False)
use_alt: BoolProperty(
name = "combine with alt",
description = "add alt combined with double click to trigger alignement (default)",
default = True)
use_ctrl: BoolProperty(
name = "combine with ctrl",
description = "add ctrl combined with double click to trigger alignement",
default = True)
## default active tool to use
select_active_tool : EnumProperty(
name="Default selection tool", description="Active tool to set when launching check fix scene",
@ -301,10 +268,6 @@ class GPTB_prefs(bpy.types.AddonPreferences):
if self.pref_tabs == 'PREF':
box = layout.box()
box.label(text='Random color options:')
box.prop(self, 'separator')
box = layout.box()
box.label(text='Project settings')
@ -367,34 +330,6 @@ class GPTB_prefs(bpy.types.AddonPreferences):
box.label(text="No Jump hotkey auto set. Following operators needs to be set manually", icon="ERROR")
box.label(text="screen.gp_keyframe_jump - preferably in 'screen' category to jump from any editor")
# box.separator()## Canvas
box = layout.box()
box.label(text='Canvas rotate options:')
box.prop(self, "canvas_use_shortcut", text='Bind shortcuts')
if self.canvas_use_shortcut:
row = box.row()
row.label(text="After changes, use the Bind/Rebind button")#icon=""
row.operator("prefs.rebind_shortcut", text='Bind/Rebind shortcuts', icon='FILE_REFRESH')#EVENT_SPACEKEY
row = box.row(align = True)
row.prop(self, "use_ctrl", text='Ctrl')#, expand=True
row.prop(self, "use_alt", text='Alt')#, expand=True
row.prop(self, "use_shift", text='Shift')#, expand=True
row.prop(self, "mouse_click",text='')#expand=True
if not self.use_ctrl and not self.use_alt and not self.use_shift:
box.label(text="Choose at least one modifier to combine with click (default: Ctrl+Alt)", icon="ERROR")# INFO
else:
box.label(text="No hotkey has been set automatically. Following operators needs to be set manually:", icon="ERROR")
box.label(text="view3d.rotate_canvas")
# TODO get that off once proper register update from properties
if self.canvas_use_shortcut:
OP_canvas_rotate.register_keymaps()
else:
OP_canvas_rotate.unregister_keymaps()
## Active tool
box = layout.box()
box.label(text='Autofix check button options:')
@ -402,7 +337,12 @@ class GPTB_prefs(bpy.types.AddonPreferences):
box.prop(self, "render_obj_exclusion", icon='FILTER')#
## random color character separator
box = layout.box()
box.label(text='Random color options:')
box.prop(self, 'separator')
if self.pref_tabs == 'MAN_OPS':
# layout.separator()## notes
@ -466,7 +406,6 @@ def register():
addon_updater_ops.register(bl_info)
for cls in classes:
bpy.utils.register_class(cls)
OP_box_deform.register()
OP_helpers.register()
OP_keyframe_jump.register()
OP_file_checker.register()
@ -476,7 +415,6 @@ def register():
OP_playblast_bg.register()
OP_playblast.register()
OP_palettes.register()
OP_canvas_rotate.register()
OP_cursor_snap_canvas.register()
OP_render.register()
OP_copy_paste.register()
@ -497,7 +435,6 @@ def unregister():
OP_copy_paste.unregister()
OP_render.unregister()
OP_cursor_snap_canvas.unregister()
OP_canvas_rotate.unregister()
OP_palettes.unregister()
OP_file_checker.unregister()
OP_helpers.unregister()
@ -507,7 +444,6 @@ def unregister():
GP_colorize.unregister()## GP_guided_colorize.
OP_playblast_bg.unregister()
OP_playblast.unregister()
OP_box_deform.unregister()
del bpy.types.Scene.gptoolprops

View File

@ -8,7 +8,6 @@ def register_keymaps():
# km = addon.keymaps.new(name = "3D View", space_type = "VIEW_3D")# in 3D context
# km = addon.keymaps.new(name = "Window", space_type = "EMPTY")# from everywhere
## try initiate
km = addon.keymaps.new(name = "Grease Pencil Stroke Sculpt Mode", space_type = "EMPTY", region_type='WINDOW')
kmi = km.keymap_items.new('wm.context_toggle', type='ONE', value='PRESS')
@ -24,11 +23,9 @@ def register_keymaps():
addon_keymaps.append((km, kmi))
def unregister_keymaps():
# wm = bpy.context.window_manager
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
# wm.keyconfigs.addon.keymaps.remove(km)
addon_keymaps.clear()