blender3.6
ChristopheSeux 2023-11-09 10:29:56 +01:00
parent 57efcc2998
commit b28e80d6d5
19 changed files with 515 additions and 1112 deletions

4
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"python.linting.pylintEnabled": true,
"python.linting.enabled": true
}

View File

@ -12,29 +12,20 @@ bl_info = {
import importlib
modules = [
'.op_material',
'.op_picker',
'.op_shape',
modules = (
'.operators',
'.properties',
'.panels',
'.area',
'.gizmo',
'.picker'
]
functions = [
#'.func_picker',
'.func_shape',
'.snapping_utils',
'.utils'
]
'.draw_handlers'
)
import bpy
if "bpy" in locals():
if not bpy.app.background:
for name in modules + functions:
for name in modules:
module = importlib.import_module(name, __name__)
importlib.reload(module)

View File

@ -1,289 +0,0 @@
import bpy
import blf
import gpu
from mathutils import Vector
from .func_picker import *
from .utils import intersect_rectangles, point_inside_rectangle,\
point_over_shape, border_over_shape, canvas_space
from gpu_extras.batch import batch_for_shader
from .constants import PICKERS
from .picker import Picker
"""
def draw_polygon_2d(verts, indices, loops, color, contour, width=None):
dpi = int(bpy.context.user_preferences.system.pixel_size)
bgl.glColor4f(*color)
gpu.state.blend_set('ALPHA')
#bgl.glEnable(bgl.GL_LINE_SMOOTH)
bgl.glColor4f(*color)
for face in faces:
bgl.glBegin(bgl.GL_POLYGON)
for v_index in face:
coord = verts[v_index]
bgl.glVertex2f(coord[0],coord[1])
bgl.glEnd()
if width:
gpu.state.line_width_set(width*dpi)
bgl.glColor4f(*contour)
for loop in loops:
if faces:
bgl.glBegin(bgl.GL_LINE_LOOP)
else:
bgl.glBegin(bgl.GL_LINE_STRIP)
for v_index in loop:
coord = verts[v_index]
bgl.glVertex2f(coord[0],coord[1])
bgl.glEnd()
gpu.state.blend_set('NONE')
#bgl.glDisable(bgl.GL_LINE_SMOOTH)
bgl.glEnd()
return
def draw_border(border):
gpu.state.blend_set('ALPHA')
bgl.glColor4f(1,1,1,0.2)
bgl.glBegin(bgl.GL_POLYGON)
for v in border:
bgl.glVertex2f(v[0],v[1])
bgl.glEnd()
bgl.glColor4f(0.0, 0.0, 0.0, 0.5)
gpu.state.line_width_set(2.0)
bgl.glLineStipple(3, 0xAAAA)
bgl.glEnable(bgl.GL_LINE_STIPPLE)
bgl.glBegin(bgl.GL_LINE_LOOP)
for v in border:
bgl.glVertex2f(v[0],v[1])
bgl.glEnd()
bgl.glColor4f(1.0, 1.0, 1.0, 1)
gpu.state.line_width_set(1.0)
bgl.glBegin(bgl.GL_LINE_LOOP)
for v in border:
bgl.glVertex2f(v[0],v[1])
bgl.glEnd()
bgl.glDisable(bgl.GL_LINE_STIPPLE)
gpu.state.blend_set('NONE')
def draw_text(mouse,text,color):
dpi = int(bpy.context.user_preferences.system.pixel_size)
gpu.state.blend_set('ALPHA')
font_id =0 # XXX, need to find out how best to get this.
# draw some text
bgl.glColor4f(0,0,0,0.75)
blf.blur(font_id,5)
blf.position(font_id, mouse[0]+10*dpi, mouse[1]-20*dpi, 0)
blf.size(font_id, 9*dpi, 96)
blf.draw(font_id, text)
bgl.glEnd()
bgl.glColor4f(*color)
blf.blur(font_id,0)
blf.draw(font_id, text)
gpu.state.blend_set('NONE')
def select_bone(self,context,event):
ob = context.object
if ob and ob.type =='ARMATURE' and ob.data.rig_picker:
shape_data = ob.data.rig_picker
selected_bones = [b for b in ob.pose.bones if b.bone.select]
if not event.shift and not event.alt:
for b in ob.pose.bones:
b.bone.select= False
for shape in [s for s in shape_data['shapes'] if not s['shape_type']==["DISPLAY"]]:
points = [canvas_space(p,self.scale,self.offset) for p in shape['points']]
bound = [canvas_space(p,self.scale,self.offset) for p in shape['bound']]
loops = shape['loops']
## Colision check
over = False
if self.is_border:
if intersect_rectangles(self.border,bound): #start check on over bound_box
over = border_over_shape(self.border,points,loops)
else:
if point_inside_rectangle(self.end,bound):
over = point_over_shape(self.end,points,loops)
if over:
if shape['shape_type'] == 'BONE':
bone = context.object.pose.bones.get(shape['bone'])
if bone:
if event.alt:
bone.bone.select = False
else:
bone.bone.select = True
context.object.data.bones.active = bone.bone
if shape['shape_type'] == 'FUNCTION' and event.value== 'RELEASE' and not self.is_border:
# restore selection
for b in selected_bones:
b.bone.select = True
function = shape['function']
if shape.get('variables'):
variables=shape['variables'].to_dict()
else:
variables={}
variables['event']=event
print(variables)
globals()[function](variables)
"""
def draw_callback_view():
ob = bpy.context.object
if not ob or ob.type !='ARMATURE' or 'shapes' not in ob.data.rig_picker.keys():
return
if ob not in PICKERS:
shapes = [s.to_dict() for s in ob.data.rig_picker['shapes']]
PICKERS[ob] = Picker(ob, shapes=shapes)
picker = PICKERS.get(ob)
picker.draw()
handle_view = None
handle_pixel = None
def register():
global handle_view, handle_pixel
handle_view = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_view, (), 'WINDOW', 'POST_VIEW')
handle_pixel = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_px, (), 'WINDOW', 'POST_PIXEL')
def unregister():
global handle_view, handle_pixel
bpy.types.SpaceNodeEditor.draw_handler_remove(handle_view, 'WINDOW')
bpy.types.SpaceNodeEditor.draw_handler_remove(handle_pixel, 'WINDOW')
handle_view = None
handle_pixel = None
'''
return
shape_data = ob.data.rig_picker
rig_layers = [i for i,l in enumerate(ob.data.layers) if l]
r = context.region
#self.scale = region.height
#draw BG
canvas = shape_data['shapes'][0]
#bg_point = [(0, r.height), (r.width, r.height), (r.width, 0),(0, 0)]
bg_color = [*canvas['color'], 1]
draw_polygon_2d(bg_point,[[0,1,2,3]],[[0,1,2,3]], bg_color,(0,0,0,1),0)
show_tooltip = False
for shape in shape_data['shapes']:
color = shape['color']
points = [canvas_space(p,self.scale,self.offset) for p in shape['points']]
bound = [canvas_space(p,self.scale,self.offset) for p in shape['bound']]
loops = shape['loops']
faces = shape['faces']
select=None
contour_color = [0,0,0]
contour_alpha = 1
width = 0
shape_color = [c for c in color]
shape_alpha = 1
if shape['shape_type'] == 'DISPLAY' and not faces:
width = 1
if shape['shape_type'] != 'DISPLAY':
if shape['shape_type'] == 'BONE':
bone = ob.pose.bones.get(shape['bone'])
if bone:
b_layers = [i for i,l in enumerate(bone.bone.layers) if l]
if bone.bone_group:
group_color = list(bone.bone_group.colors.normal)
contour_color = [c*0.85 for c in group_color]
width = 1
if bone.bone.select :
shape_color = [c*1.2+0.1 for c in color]
if bone.bone_group:
contour_color = [0.05,0.95,0.95]
if ob.data.bones.active and shape['bone'] == ob.data.bones.active.name:
if bone.bone_group:
if bone.bone.select :
shape_color = [c*1.2+0.2 for c in color]
contour_color = [1,1,1]
width = 1.5
else:
shape_color = [c*1.2+0.15 for c in color]
contour_color = [0.9,0.9,0.9]
width = 1
if bone.bone.hide or not len(set(b_layers).intersection(rig_layers)):
shape_alpha = 0.33
contour_alpha = 0.33
elif shape['shape_type'] == 'FUNCTION':
if shape['function'] == 'boolean':
path = shape['variables']['data_path']
if ob.path_resolve(path):
shape_color = [c*1.4+0.08 for c in color]
else:
shape_color = [color[0],color[1],color[2]]
## On mouse over checking
over = False
if self.is_border:
if intersect_rectangles(self.border,bound): #start check on over bound_box
over = border_over_shape(self.border,points,loops)
else:
if point_inside_rectangle(self.end,bound):
over = point_over_shape(self.end,points,loops)
if over:
show_tooltip = True
tooltip = shape['tooltip']
if not self.press:
shape_color = [c*1.02+0.05 for c in shape_color]
contour_color = [c*1.03+0.05 for c in contour_color]
shape_color.append(shape_alpha)
contour_color.append(contour_alpha)
draw_polygon_2d(points,faces,loops,shape_color,contour_color,width)
if show_tooltip:
draw_text(self.end,tooltip,(1,1,1,1))
if self.is_border:
draw_border(self.border)
'''

0
core/__init__.py Normal file
View File

43
core/addon_utils.py Normal file
View File

@ -0,0 +1,43 @@
import bpy
from .bl_utils import get_mat
def is_shape(ob):
scn = bpy.context.scene
canvas = scn.rig_picker.canvas
if not canvas or ob.hide_render:
return False
shapes = {ob for col in canvas.users_collection for ob in col.all_objects}
if ob.type in ('MESH', 'CURVE', 'FONT') and ob in shapes:
return True
return False
def get_object_color(ob):
if not ob.data.materials:
return
mat = get_mat(ob)
if not mat or not mat.node_tree or not mat.node_tree.nodes:
return
emit_node = mat.node_tree.nodes.get('Emission')
if not emit_node:
return
return emit_node.inputs['Color'].default_value
def get_operator_from_id(idname):
if not '.' in idname:
return
m, o = idname.split(".")
try:
op = getattr(getattr(bpy.ops, m), o)
op.get_rna_type()
except Exception:
return
return op

119
core/bl_utils.py Normal file
View File

@ -0,0 +1,119 @@
import bpy
def get_view_3d_override():
windows = bpy.context.window_manager.windows
areas = [a for w in windows for a in w.screen.areas if a.type == 'VIEW_3D']
if not areas:
print('No view 3d found')
return
view_3d = None
for area in areas:
if area.spaces.active.camera:
view_3d = area
if not view_3d:
view_3d = max(areas, key=lambda x :x.width)
return {'area': view_3d, 'region': view_3d.regions[-1]}
def get_mat(ob):
for sl in ob.material_slots:
if sl.material:
return sl.material
def link_mat_to_object(ob):
for sl in ob.material_slots:
m = sl.material
sl.link = 'OBJECT'
sl.material = m
def find_mirror(name):
mirror = None
prop= False
if name:
if name.startswith('[')and name.endswith(']'):
prop = True
name= name[:-2][2:]
match={
'R': 'L',
'r': 'l',
'L': 'R',
'l': 'r',
}
separator=['.','_']
if name.startswith(tuple(match.keys())):
if name[1] in separator:
mirror = match[name[0]]+name[1:]
if name.endswith(tuple(match.keys())):
if name[-2] in separator:
mirror = name[:-1]+match[name[-1]]
if mirror and prop == True:
mirror='["%s"]'%mirror
return mirror
else:
return None
def hide_layers(args):
""" """
ob = bpy.context.object
layers = []
for bone in [b for b in ob.pose.bones if b.bone.select]:
for i,l in enumerate(bone.bone.layers):
if l and i not in layers:
layers.append(i)
for i in layers:
ob.data.layers[i] = not ob.data.layers[i]
def select_layer(args):
ob = bpy.context.object
layers =[]
for bone in [b for b in ob.pose.bones if b.bone.select]:
bone_layers = [i for i,l in enumerate(bone.bone.layers) if l]
for l in bone_layers:
if l not in layers:
layers.append(l)
for bone in ob.pose.bones:
bone_layers = [i for i,l in enumerate(bone.bone.layers) if l]
if len(set(bone_layers).intersection(layers)):
bone.bone.select = True
def hide_bones(args):
ob = bpy.context.object
selected_bone = [b for b in ob.pose.bones if b.bone.select]
hide = [b.bone.hide for b in selected_bone if not b.bone.hide]
visibility = True if len(hide) else False
for bone in selected_bone:
bone.bone.hide = visibility
def select_all(args):
ob = bpy.context.object
shapes = ob.data.rig_picker['shapes']
bones = [s['bone'] for s in shapes if s['shape_type']=='BONE']
for bone_name in bones:
bone = ob.pose.bones.get(bone_name)
if bone:
bone.bone.select = True

49
core/geometry_utils.py Normal file
View File

@ -0,0 +1,49 @@
import bpy
from mathutils import Vector
from mathutils.geometry import intersect_line_line_2d
def is_over_region(self,context,event):
inside = 2 < event.mouse_region_x < context.region.width -2 and \
2 < event.mouse_region_y < context.region.height -2 and \
[a for a in context.screen.areas if a.as_pointer()==self.adress] and \
not context.screen.show_fullscreen
return inside
def bound_box_center(ob):
points = [ob.matrix_world@Vector(p) for p in ob.bound_box]
x = [v[0] for v in points]
y = [v[1] for v in points]
z = [v[2] for v in points]
return (sum(x) / len(points), sum(y) / len(points),sum(z) / len(points))
def intersect_rectangles(bound, border): # returns None if rectangles don't intersect
dx = min(border[1][0],bound[1][0]) - max(border[0][0],bound[0][0])
dy = min(border[0][1],bound[0][1]) - max(border[2][1],bound[2][1])
if (dx>=0) and (dy>=0):
return dx*dy
def point_inside_rectangle(point, rect):
return rect[0][0]< point[0]< rect[1][0] and rect[2][1]< point[1]< rect[0][1]
def point_over_shape(point,verts,loops,outside_point=(-1,-1)):
out = Vector(outside_point)
pt = Vector(point)
intersections = 0
for loop in loops:
for i,p in enumerate(loop):
a = Vector(verts[loop[i-1]])
b = Vector(verts[p])
if intersect_line_line_2d(pt,out,a,b):
intersections += 1
if intersections%2 == 1: #chek if the nb of intersection is odd
return True

View File

@ -2,15 +2,16 @@
import bpy
import gpu
from gpu_extras.batch import batch_for_shader
import gpu
import blf
from mathutils import bvhtree, Vector
from mathutils.geometry import intersect_point_quad_2d, intersect_point_tri_2d, intersect_tri_tri_2d
from .constants import PICKERS
from .utils import get_operator_from_id
from ..constants import PICKERS
from .addon_utils import get_operator_from_id
from pathlib import Path
import re
import json
import os
import threading
@ -383,8 +384,6 @@ class OperatorShape(Shape):
gpu.state.blend_set('NONE')
class Picker:
def __init__(self, rig, shapes):
@ -475,6 +474,8 @@ class Picker:
s.press = False
#bpy.context.area.tag_redraw()
def tooltip_event(self):
#print('Tooltip Event', self)
@ -538,9 +539,12 @@ class Picker:
self.mouse = location
location = self.region.view2d.region_to_view(*location)
for s in self.shapes:
if s.move_event(location):
self.hover_shape = s
self.hover_shape = None
for shape in reversed(self.shapes):
if self.hover_shape:
shape.hover = False
elif shape.move_event(location):
self.hover_shape = shape
#if point_inside_rectangle(self.end, bound):
# over = point_over_shape(self.end,points, edges)
@ -581,86 +585,26 @@ class Picker:
gpu.state.blend_set('NONE')
'''
def draw_callback_view():
sp = bpy.context.space_data
if not sp or not sp.tree_type == 'RigPickerTree':
def get_picker_path(rig, start=None):
picker_path = rig.data.get('rig_picker', {}).get('source')
if not picker_path:
return
ob = bpy.context.object
if not ob or ob.type !='ARMATURE' or not ob.data.rig_picker.source:
picker_path = bpy.path.abspath(picker_path, library=rig.data.library, start=start)
return Path(os.path.abspath(picker_path))
def pack_picker(rig, start=None):
picker_path = get_picker_path(rig, start=start)
if picker_path and picker_path.exists():
if 'rig_picker' not in rig.data.keys():
rig.data['rig_picker'] = {}
rig.data['rig_picker']['picker'] = json.loads(picker_path.read_text())
def unpack_picker(rig):
if 'rig_picker' not in rig.data.keys():
return
if ob not in PICKERS:
if 'picker' in ob.data.rig_picker:
picker_datas = [s.to_dict() for s in ob.data.rig_picker['picker']]
else:
picker_path = Path(bpy.path.abspath(ob.data.rig_picker.source, library=ob.data.library))
if not picker_path.exists():
print(f'Picker path not exists: {picker_path.resolve()}')
return
print('Load picker from', picker_path.resolve())
picker_datas = json.loads(picker_path.read_text())
#shapes = [s.to_dict() for s in ob.data.rig_picker['shapes']]
PICKERS[ob] = Picker(ob, shapes=picker_datas)
picker = PICKERS.get(ob)
picker.draw()
def draw_callback_px():
sp = bpy.context.space_data
if not sp.tree_type == 'RigPickerTree':
return
ob = bpy.context.object
picker = PICKERS.get(ob)
if not picker or not picker.tooltip:
return
text = picker.tooltip
#print('Draw text', text)
font_id = 0
#blf.dimensions(font_id, text)
blf.enable(font_id, blf.SHADOW)
#gpu.state.blend_set('ALPHA')
# BLF drawing routine
blf.position(font_id, picker.tooltip_mouse[0]-5, picker.tooltip_mouse[1]+5, 0)
blf.size(font_id, 14)
blf.color(font_id, 1, 1, 1, 1)
blf.shadow(font_id , 5, 0.0, 0.0, 0.0, 1)
blf.shadow_offset(font_id, 2, -2)
blf.draw(font_id, text)
blf.disable(font_id, blf.SHADOW)
#gpu.state.blend_set('NONE')
handle_view = None
handle_pixel = None
def register():
global handle_view
global handle_pixel
handle_view = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_view, (), 'WINDOW', 'POST_VIEW')
handle_pixel = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_px, (), 'WINDOW', 'POST_PIXEL')
def unregister():
global handle_view
global handle_pixel
bpy.types.SpaceNodeEditor.draw_handler_remove(handle_view, 'WINDOW')
bpy.types.SpaceNodeEditor.draw_handler_remove(handle_pixel, 'WINDOW')
handle_view = None
handle_pixel = None
if 'picker' in rig.data['rig_picker'].keys():
del rig.data['rig_picker']['picker']

View File

@ -2,9 +2,65 @@ import bpy
import bmesh
from mathutils import Vector, Matrix
from bpy_extras import mesh_utils
from .utils import bound_box_center, contour_loops, get_object_color
from .geometry_utils import bound_box_center
from .addon_utils import get_object_color
def border_over_shape(border,verts,loops):
for loop in loops:
for i,p in enumerate(loop):
a = Vector(verts[loop[i-1]])
b = Vector(verts[p])
for j in range(0,4):
c = border[j-1]
d = border[j]
if intersect_line_line_2d(a,b,c,d):
return True
for point in verts:
if point_inside_rectangle(point,border):
return True
for point in border:
if point_over_shape(point,verts,loops):
return True
def border_loop(vert, loop):
border_edge =[e for e in vert.link_edges if e.is_boundary]
if border_edge:
for edge in border_edge:
other_vert = edge.other_vert(vert)
if not other_vert in loop:
loop.append(other_vert)
border_loop(other_vert, loop)
return loop
else:
return [vert]
def contour_loops(bm, vert_index=0, loops=None, vert_indices=None):
loops = loops or []
vert_indices = vert_indices or [v.index for v in bm.verts]
bm.verts.ensure_lookup_table()
loop = border_loop(bm.verts[vert_index], [bm.verts[vert_index]])
if len(loop) >1:
loops.append(loop)
for v in loop:
vert_indices.remove(v.index)
if len(vert_indices):
contour_loops(bm, vert_indices[0], loops, vert_indices)
return loops
def get_picker_datas(objects, canvas, rig):
picker_datas = []
gamma = 1 / 2.2

91
draw_handlers.py Normal file
View File

@ -0,0 +1,91 @@
import bpy
import blf
from pathlib import Path
from .constants import PICKERS
from .core.picker import Picker
import json
def draw_callback_view():
sp = bpy.context.space_data
if not sp or not sp.tree_type == 'RigPickerTree':
return
ob = bpy.context.object
if not ob or ob.type !='ARMATURE' or not ob.data.rig_picker.source:
return
if ob not in PICKERS:
if 'picker' in ob.data.rig_picker:
picker_datas = [s.to_dict() for s in ob.data.rig_picker['picker']]
else:
picker_path = Path(bpy.path.abspath(ob.data.rig_picker.source, library=ob.data.library))
if not picker_path.exists():
print(f'Picker path not exists: {picker_path.resolve()}')
return
print('Load picker from', picker_path.resolve())
picker_datas = json.loads(picker_path.read_text())
#shapes = [s.to_dict() for s in ob.data.rig_picker['shapes']]
PICKERS[ob] = Picker(ob, shapes=picker_datas)
picker = PICKERS.get(ob)
picker.draw()
def draw_callback_px():
sp = bpy.context.space_data
if not sp.tree_type == 'RigPickerTree':
return
ob = bpy.context.object
picker = PICKERS.get(ob)
if not picker or not picker.tooltip:
return
text = picker.tooltip
#print('Draw text', text)
font_id = 0
#blf.dimensions(font_id, text)
blf.enable(font_id, blf.SHADOW)
#gpu.state.blend_set('ALPHA')
# BLF drawing routine
blf.position(font_id, picker.tooltip_mouse[0]-5, picker.tooltip_mouse[1]+5, 0)
blf.size(font_id, 14)
blf.color(font_id, 1, 1, 1, 1)
blf.shadow(font_id , 5, 0.0, 0.0, 0.0, 1)
blf.shadow_offset(font_id, 2, -2)
blf.draw(font_id, text)
blf.disable(font_id, blf.SHADOW)
#gpu.state.blend_set('NONE')
handle_view = None
handle_pixel = None
def register():
global handle_view
global handle_pixel
handle_view = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_view, (), 'WINDOW', 'POST_VIEW')
handle_pixel = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_px, (), 'WINDOW', 'POST_PIXEL')
def unregister():
global handle_view
global handle_pixel
bpy.types.SpaceNodeEditor.draw_handler_remove(handle_view, 'WINDOW')
bpy.types.SpaceNodeEditor.draw_handler_remove(handle_pixel, 'WINDOW')
handle_view = None
handle_pixel = None

View File

@ -1,248 +0,0 @@
import bpy
#from. insert_keyframe import insert_keyframe
from. snapping_utils import *
from .utils import get_IK_bones
try:
from rigutils.driver_utils import split_path
from rigutils.insert_keyframe import insert_keyframe
from rigutils.snap_ik_fk import snap_ik_fk
from rigutils.utils import find_mirror
except:
print('You need to install the rigutils module in your blender modules path')
def hide_layers(args):
""" """
ob = bpy.context.object
layers=[]
for bone in [b for b in ob.pose.bones if b.bone.select]:
for i,l in enumerate(bone.bone.layers):
if l and i not in layers:
layers.append(i)
for i in layers:
ob.data.layers[i] = not ob.data.layers[i]
def boolean(args):
""" data_path, keyable """
ob = bpy.context.object
data_path = args['data_path']
keyable = args['keyable']
#bone,prop = split_path(data_path)
try:
value = ob.path_resolve(data_path)
#setattr(ob.pose.bones.get(bone),'["%s"]'%prop,not value)
try:
exec("ob.%s = %s"%(data_path,not value))
except:
exec("ob%s= %s"%(data_path,not value))
if keyable and bpy.context.scene.tool_settings.use_keyframe_insert_auto:
if not ob.animation_data:
ob.animation_data_create()
ob.keyframe_insert(data_path = data_path ,group = bone)
except ValueError:
print("Property don't exist")
def select_layer(args):
ob = bpy.context.object
layers =[]
for bone in [b for b in ob.pose.bones if b.bone.select]:
bone_layers = [i for i,l in enumerate(bone.bone.layers) if l]
for l in bone_layers:
if l not in layers:
layers.append(l)
for bone in ob.pose.bones:
bone_layers = [i for i,l in enumerate(bone.bone.layers) if l]
if len(set(bone_layers).intersection(layers)):
bone.bone.select = True
def hide_bones(args):
ob = bpy.context.object
selected_bone = [b for b in ob.pose.bones if b.bone.select]
hide = [b.bone.hide for b in selected_bone if not b.bone.hide]
visibility = True if len(hide) else False
for bone in selected_bone:
bone.bone.hide = visibility
def select_all(args):
ob = bpy.context.object
shapes = ob.data.rig_picker['shapes']
bones = [s['bone'] for s in shapes if s['shape_type']=='BONE']
for bone_name in bones:
bone = ob.pose.bones.get(bone_name)
if bone:
bone.bone.select = True
def select_bones(args):
"""bones (name list)"""
ob = bpy.context.object
pBones = ob.pose.bones
bones_name =args['bones']
event = args['event']
if not event.shift:
for bone in bpy.context.object.pose.bones:
bone.bone.select = False
bones = [pBones.get(b) for b in bones_name]
select = False
for bone in bones:
if bone.bone.select == False:
select =True
break
for bone in bones:
bone.bone.select = select
ob.data.bones.active = bones[-1].bone
def keyframe_bones(args):
print(args)
event=args['event']
bones=[]
for bone in bpy.context.object.pose.bones:
if not bone.name.startswith(('DEF','ORG','MCH')) and not bone.get('_unkeyable_') ==1:
if event.shift:
bones.append(bone)
elif not event.shift and bone.bone.select :
bones.append(bone)
for bone in bones:
insert_keyframe(bone)
def reset_bones(args):
event=args['event']
avoid_value =args['avoid_value']
ob = bpy.context.object
bones=[]
for bone in bpy.context.object.pose.bones:
if not bone.name.startswith(('DEF','ORG','MCH')) and not bone.get('_unkeyable_') ==1:
if event.shift:
bones.append(bone)
elif not event.shift and bone.bone.select :
bones.append(bone)
for bone in bones:
if bone.rotation_mode =='QUATERNION':
bone.rotation_quaternion = 1, 0, 0, 0
if bone.rotation_mode == 'AXIS_ANGLE':
bone.rotation_axis_angle = 0, 0, 1, 0
else:
bone.rotation_euler = 0, 0, 0
bone.location = 0, 0, 0
bone.scale = 1, 1, 1
for key,value in bone.items():
if key not in avoid_value and type(value) in (int,float):
if ob.data.get("DefaultValues") and ob.data.DefaultValues['bones'].get(bone.name):
if key in ob.data.DefaultValues['bones'][bone.name]:
bone[key] = ob.data.DefaultValues['bones'][bone.name][key]
else:
if type(value)== int:
bone[key]=0
else:
bone[key]=0.0
else:
if type(value)== int:
bone[key]=0
else:
bone[key]=0.0
if bpy.context.scene.tool_settings.use_keyframe_insert_auto:
insert_keyframe(bone)
def flip_bones(args):
event=args['event']
ob = bpy.context.object
arm = bpy.context.object.pose.bones
selected_bones = [bone for bone in ob.pose.bones if bone.bone.select==True ]
mirrorActive = None
for bone in selected_bones:
boneName = bone.name
mirrorBoneName= find_mirror(boneName)
mirrorBone = ob.pose.bones.get(mirrorBoneName) if mirrorBoneName else None
if bpy.context.active_pose_bone == bone:
mirrorActive = mirrorBone
#print(mirrorBone)
if not event.shift and mirrorBone:
bone.bone.select = False
if mirrorBone:
mirrorBone.bone.select = True
if mirrorActive:
ob.data.bones.active = mirrorActive.bone
def snap_ikfk(args):
""" way, chain_index """
way =args['way']
chain_index = args['chain_index']
#auto_switch = self.auto_switch
ob = bpy.context.object
armature = ob.data
SnappingChain = armature.get('SnappingChain')
poseBone = ob.pose.bones
dataBone = ob.data.bones
IKFK_chain = SnappingChain['IKFK_bones'][chain_index]
switch_prop = IKFK_chain['switch_prop']
FK_root = poseBone.get(IKFK_chain['FK_root'])
FK_mid = [poseBone.get(b['name']) for b in IKFK_chain['FK_mid']]
FK_tip = poseBone.get(IKFK_chain['FK_tip'])
IK_last = poseBone.get(IKFK_chain['IK_last'])
IK_tip = poseBone.get(IKFK_chain['IK_tip'])
IK_pole = poseBone.get(IKFK_chain['IK_pole'])
invert = IKFK_chain['invert_switch']
ik_fk_layer = (IKFK_chain['FK_layer'],IKFK_chain['IK_layer'])
for lock in ('lock_ik_x','lock_ik_y','lock_ik_z'):
if getattr(IK_last,lock):
full_snapping = False
break
snap_ik_fk(ob,way,switch_prop,FK_root,FK_tip,IK_last,IK_tip,IK_pole,FK_mid,full_snapping,invert,ik_fk_layer,auto_switch=True)

View File

@ -6,7 +6,7 @@ from bpy.types import (AddonPreferences, GizmoGroup, Operator, Gizmo)
from mathutils import Vector, Matrix, Euler
from .constants import PICKERS
from .picker import Picker
from .core.picker import Picker

20
operators/__init__.py Normal file
View File

@ -0,0 +1,20 @@
from . import material
from . import picker
from . import shape
modules = (
material,
picker,
shape
)
def register():
for mod in modules:
mod.register()
def unregister():
for mod in reversed(modules):
mod.unregister()

View File

@ -1,14 +1,18 @@
import bpy
from .constants import PICKERS
from bpy.props import EnumProperty
from ..constants import PICKERS
from ..core.bl_utils import get_view_3d_override
from ..core.picker import get_picker_path, pack_picker, unpack_picker
#from .func_bgl import draw_callback_px
#from .func_bgl import select_bone
#from .func_picker import *
#from core.picker import *
#from .utils import is_over_region
import gpu
from mathutils import Vector
from gpu_extras.batch import batch_for_shader
from pathlib import Path
import json
import os
vertex_shader = '''
@ -92,7 +96,7 @@ class RP_OT_box_select(bpy.types.Operator):
bl_idname = "node.rp_box_select"
bl_label = "Picker Box Select"
mode: bpy.props.EnumProperty(items=[(i, i.title(), '') for i in ('SET', 'EXTEND', 'SUBSTRACT')])
mode: EnumProperty(items=[(i, i.title(), '') for i in ('SET', 'EXTEND', 'SUBSTRACT')])
color_shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
dash_shader = gpu.types.GPUShader(vertex_shader, fragment_shader)
@ -194,47 +198,57 @@ class RP_OT_box_select(bpy.types.Operator):
class RP_OT_move_bone(bpy.types.Operator):
class RP_OT_picker_transform(bpy.types.Operator):
"""Tooltip"""
bl_idname = "node.move_bone"
bl_idname = "node.picker_transform"
bl_label = "Move Bone in Picker View"
mode : EnumProperty(items=[(m, m.title(), '') for m in ('TRANSLATE', 'ROTATE', 'SCALE')])
@classmethod
def poll(cls, context):
if not is_picker_space(context):
return
return is_picker_space(context)
def invoke(self, context, event):
self.mouse = event.mouse_region_x, event.mouse_region_y
def execute(self, context):
with context.temp_override(**get_view_3d_override()):
if self.mode == 'TRANSLATE':
bpy.ops.transform.translate("INVOKE_DEFAULT")
elif self.mode == 'ROTATE':
bpy.ops.transform.rotate("INVOKE_DEFAULT")
elif self.mode == 'SCALE':
bpy.ops.transform.resize("INVOKE_DEFAULT")
self.bone_data = {b: b.matrix.copy() for b in context.selected_pose_bones}
return {"FINISHED"}
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
# def invoke(self, context, event):
# self.mouse = event.mouse_region_x, event.mouse_region_y
def modal(self, context, event):
# self.bone_data = {b: b.matrix.copy() for b in context.selected_pose_bones}
delta_x = (event.mouse_region_x - self.mouse[0]) / 1000
delta_y = (event.mouse_region_y - self.mouse[1]) / 1000
# context.window_manager.modal_handler_add(self)
# return {'RUNNING_MODAL'}
# def modal(self, context, event):
# delta_x = (event.mouse_region_x - self.mouse[0]) / 1000
# delta_y = (event.mouse_region_y - self.mouse[1]) / 1000
# for bone, matrix in self.bone_data.items():
# bone.matrix.translation = matrix.translation + Vector((delta_x, 0, delta_y))
# #print(delta_x, delta_y)
for bone, matrix in self.bone_data.items():
bone.matrix.translation = matrix.translation + Vector((delta_x, 0, delta_y))
# if event.type=="LEFTMOUSE" and event.value == 'RELEASE':
# return {'FINISHED'}
# if event.type=="RIGHTMOUSE":
# for bone, matrix in self.bone_data.items():
# bone.matrix = matrix
#print(delta_x, delta_y)
# return {'CANCELLED'}
if event.type=="LEFTMOUSE" and event.value == 'RELEASE':
return {'FINISHED'}
if event.type=="RIGHTMOUSE":
for bone, matrix in self.bone_data.items():
bone.matrix = matrix
return {'CANCELLED'}
return {'RUNNING_MODAL'}
# return {'RUNNING_MODAL'}
class RP_OT_function_execute(bpy.types.Operator):
@ -309,20 +323,30 @@ class RP_OT_toogle_bone_layer(bpy.types.Operator):
bone = picker.hover_shape.bone
hide = picker.hover_shape.hide
if bone:
for i, l in enumerate(bone.bone.layers):
if l:
ob.data.layers[i] = hide
print('Bone Layer toogle')
#if picker.hover_bone:
context.region.tag_redraw()
return {"FINISHED"}
class RP_OT_context_menu_picker(bpy.types.Operator):
bl_idname = "node.context_menu_picker"
bl_label = 'Context Menu Picker'
@classmethod
def poll(cls, context):
return is_picker_space(context)
def execute(self, context):
bpy.ops.wm.call_menu(name='RP_MT_context_menu')
return {"FINISHED"}
class RP_OT_pack_picker(bpy.types.Operator):
"""Pack Unpack the picker on the rig"""
bl_idname = "rigpicker.pack_picker"
@ -336,18 +360,20 @@ class RP_OT_pack_picker(bpy.types.Operator):
def execute(self, context):
print('Pack Picker')
ob = context.object
picker_src = Path(bpy.path.abspath(ob.data.rig_picker.source, library=ob.data.library))
rig = context.object
picker_path = get_picker_path(rig)
if 'picker' in ob.data.rig_picker.keys():
del ob.data.rig_picker['picker']
if 'picker' in rig.data.rig_picker.keys():
unpack_picker(rig)
self.report({"INFO"}, f'The picker is unpacked')
return {"FINISHED"}
if not picker_src.exists():
self.report({"ERROR"}, f'The path of the picker not exist: {picker_src}')
if not picker_path.exists():
self.report({"ERROR"}, f'The path of the picker not exist: {picker_path}')
return {"CANCELLED"}
ob.data.rig_picker['picker'] = json.loads(picker_src.read_text())
pack_picker(rig)
self.report({"INFO"}, f'The picker is packed')
return {"FINISHED"}
@ -383,8 +409,19 @@ class RP_MT_context_menu(bpy.types.Menu):
# Set the menu operators and draw functions
def draw(self, context):
layout = self.layout
col = layout.column()
col.use_property_split = True
layout.operator("rigpicker.show_bone_layer", text="Show Bone Layer", ).type = 'ACTIVE'
ob = context.object
picker = PICKERS.get(ob)
if picker.hover_shape and picker.hover_shape.type == 'bone':
bone = picker.hover_shape.bone
for key in bone.keys():
layout.prop(bone,f'["{key}"]', slider=True)
#layout.operator("rigpicker.show_bone_layer", text="Show Bone Layer", ).type = 'ACTIVE'
#layout.operator("rigidbody.objects_add", text="B Add Passive").type = 'PASSIVE'
'''
@ -530,11 +567,19 @@ def register_keymaps():
kmi = km.keymap_items.new("rigpicker.toogle_bone_layer", type="LEFTMOUSE", value="DOUBLE_CLICK")
keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.move_bone", type="G", value="PRESS")
kmi = km.keymap_items.new("node.picker_transform", type="G", value="PRESS")
kmi.properties.mode = 'TRANSLATE'
keymaps.append((km, kmi))
kmi = km.keymap_items.new("wm.call_menu", type="RIGHTMOUSE", value="PRESS")
kmi.properties.name = "RP_MT_context_menu"
kmi = km.keymap_items.new("node.picker_transform", type="R", value="PRESS")
kmi.properties.mode = 'ROTATE'
keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.picker_transform", type="S", value="PRESS")
kmi.properties.mode = 'SCALE'
keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.context_menu_picker", type="RIGHTMOUSE", value="PRESS")
keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS")
@ -563,7 +608,8 @@ classes = (
RP_OT_toogle_bone_layer,
RP_OT_call_operator,
RP_MT_context_menu,
RP_OT_move_bone,
RP_OT_picker_transform,
RP_OT_context_menu_picker,
RP_OT_pack_picker
#RP_OT_ui_draw
)

View File

@ -1,8 +1,8 @@
import bpy
from .func_shape import get_picker_datas
from .utils import is_shape, find_mirror, link_mat_to_object
#import os
from ..core.shape import get_picker_datas
from ..core.addon_utils import is_shape
from ..core.bl_utils import link_mat_to_object, find_mirror
from pathlib import Path
import json

View File

@ -1,7 +1,8 @@
import bpy
#import collections
#import inspect
from .utils import get_operator_from_id, get_mat
from .core.addon_utils import get_operator_from_id
from .core.bl_utils import get_mat
import re

View File

@ -1,209 +0,0 @@
import bpy
from mathutils import Vector,Matrix
from math import acos, pi
#from .insert_keyframe import insert_keyframe
############################
## Math utility functions ##
############################
def perpendicular_vector(v):
""" Returns a vector that is perpendicular to the one given.
The returned vector is _not_ guaranteed to be normalized.
"""
# Create a vector that is not aligned with v.
# It doesn't matter what vector. Just any vector
# that's guaranteed to not be pointing in the same
# direction.
if abs(v[0]) < abs(v[1]):
tv = Vector((1,0,0))
else:
tv = Vector((0,1,0))
# Use cross prouct to generate a vector perpendicular to
# both tv and (more importantly) v.
return v.cross(tv)
def rotation_difference(mat1, mat2):
""" Returns the shortest-path rotational difference between two
matrices.
"""
q1 = mat1.to_quaternion()
q2 = mat2.to_quaternion()
angle = acos(min(1,max(-1,q1.dot(q2)))) * 2
if angle > pi:
angle = -angle + (2*pi)
return angle
#########################################
## "Visual Transform" helper functions ##
#########################################
def get_pose_matrix_in_other_space(mat, pose_bone):
""" Returns the transform matrix relative to pose_bone's current
transform space. In other words, presuming that mat is in
armature space, slapping the returned matrix onto pose_bone
should give it the armature-space transforms of mat.
TODO: try to handle cases with axis-scaled parents better.
"""
rest = pose_bone.bone.matrix_local.copy()
rest_inv = rest.inverted()
if pose_bone.parent:
par_mat = pose_bone.parent.matrix.copy()
par_inv = par_mat.inverted()
par_rest = pose_bone.parent.bone.matrix_local.copy()
else:
par_mat = Matrix()
par_inv = Matrix()
par_rest = Matrix()
# Get matrix in bone's current transform space
smat = rest_inv * (par_rest * (par_inv * mat))
# Compensate for non-local location
#if not pose_bone.bone.use_local_location:
# loc = smat.to_translation() * (par_rest.inverted() * rest).to_quaternion()
# smat.translation = loc
return smat
def get_local_pose_matrix(pose_bone):
""" Returns the local transform matrix of the given pose bone.
"""
return get_pose_matrix_in_other_space(pose_bone.matrix, pose_bone)
def set_pose_translation(pose_bone, mat):
""" Sets the pose bone's translation to the same translation as the given matrix.
Matrix should be given in bone's local space.
"""
if pose_bone.bone.use_local_location is True:
pose_bone.location = mat.to_translation()
else:
loc = mat.to_translation()
rest = pose_bone.bone.matrix_local.copy()
if pose_bone.bone.parent:
par_rest = pose_bone.bone.parent.matrix_local.copy()
else:
par_rest = Matrix()
q = (par_rest.inverted() * rest).to_quaternion()
pose_bone.location = q * loc
def set_pose_rotation(pose_bone, mat):
""" Sets the pose bone's rotation to the same rotation as the given matrix.
Matrix should be given in bone's local space.
"""
q = mat.to_quaternion()
if pose_bone.rotation_mode == 'QUATERNION':
pose_bone.rotation_quaternion = q
elif pose_bone.rotation_mode == 'AXIS_ANGLE':
pose_bone.rotation_axis_angle[0] = q.angle
pose_bone.rotation_axis_angle[1] = q.axis[0]
pose_bone.rotation_axis_angle[2] = q.axis[1]
pose_bone.rotation_axis_angle[3] = q.axis[2]
else:
pose_bone.rotation_euler = q.to_euler(pose_bone.rotation_mode)
def set_pose_scale(pose_bone, mat):
""" Sets the pose bone's scale to the same scale as the given matrix.
Matrix should be given in bone's local space.
"""
pose_bone.scale = mat.to_scale()
def match_pose_translation(pose_bone, target_bone):
""" Matches pose_bone's visual translation to target_bone's visual
translation.
This function assumes you are in pose mode on the relevant armature.
"""
mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
set_pose_translation(pose_bone, mat)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='POSE')
def match_pose_rotation(pose_bone, target_bone):
""" Matches pose_bone's visual rotation to target_bone's visual
rotation.
This function assumes you are in pose mode on the relevant armature.
"""
mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
set_pose_rotation(pose_bone, mat)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='POSE')
def match_pose_scale(pose_bone, target_bone):
""" Matches pose_bone's visual scale to target_bone's visual
scale.
This function assumes you are in pose mode on the relevant armature.
"""
mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
set_pose_scale(pose_bone, mat)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='POSE')
##############################
## IK/FK snapping functions ##
##############################
def match_pole_target(ik_first, ik_last, pole, match_bone, length):
""" Places an IK chain's pole target to match ik_first's
transforms to match_bone. All bones should be given as pose bones.
You need to be in pose mode on the relevant armature object.
ik_first: first bone in the IK chain
ik_last: last bone in the IK chain
pole: pole target bone for the IK chain
match_bone: bone to match ik_first to (probably first bone in a matching FK chain)
length: distance pole target should be placed from the chain center
"""
a = ik_first.matrix.to_translation()
b = ik_last.matrix.to_translation() + ik_last.vector
# Vector from the head of ik_first to the
# tip of ik_last
ikv = b - a
# Get a vector perpendicular to ikv
pv = perpendicular_vector(ikv).normalized() * length
def set_pole(pvi):
""" Set pole target's position based on a vector
from the arm center line.
"""
# Translate pvi into armature space
ploc = a + (ikv/2) + pvi
# Set pole target to location
mat = get_pose_matrix_in_other_space(Matrix.Translation(ploc), pole)
set_pose_translation(pole, mat)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='POSE')
set_pole(pv)
# Get the rotation difference between ik_first and match_bone
angle = rotation_difference(ik_first.matrix, match_bone.matrix)
# Try compensating for the rotation difference in both directions
pv1 = Matrix.Rotation(angle, 4, ikv) * pv
set_pole(pv1)
ang1 = rotation_difference(ik_first.matrix, match_bone.matrix)
pv2 = Matrix.Rotation(-angle, 4, ikv) * pv
set_pole(pv2)
ang2 = rotation_difference(ik_first.matrix, match_bone.matrix)
# Do the one with the smaller angle
if ang1 < ang2:
set_pole(pv1)

215
utils.py
View File

@ -1,215 +0,0 @@
import bpy
from mathutils import Vector
from mathutils.geometry import intersect_line_line_2d
def get_mat(ob):
for sl in ob.material_slots:
if sl.material:
return sl.material
def link_mat_to_object(ob):
for sl in ob.material_slots:
m = sl.material
sl.link = 'OBJECT'
sl.material = m
def get_operator_from_id(idname):
if not '.' in idname:
return
m, o = idname.split(".")
try:
op = getattr(getattr(bpy.ops, m), o)
op.get_rna_type()
except Exception:
return
return op
'''
def canvas_space(point,scale,offset):
return scale*Vector(point)+Vector(offset)
'''
def get_object_color(ob):
if not ob.data.materials:
return
mat = get_mat(ob)
if not mat or not mat.node_tree or not mat.node_tree.nodes:
return
emit_node = mat.node_tree.nodes.get('Emission')
if not emit_node:
return
return emit_node.inputs['Color'].default_value
def intersect_rectangles(bound, border): # returns None if rectangles don't intersect
dx = min(border[1][0],bound[1][0]) - max(border[0][0],bound[0][0])
dy = min(border[0][1],bound[0][1]) - max(border[2][1],bound[2][1])
if (dx>=0) and (dy>=0):
return dx*dy
def point_inside_rectangle(point, rect):
return rect[0][0]< point[0]< rect[1][0] and rect[2][1]< point[1]< rect[0][1]
def point_over_shape(point,verts,loops,outside_point=(-1,-1)):
out = Vector(outside_point)
pt = Vector(point)
intersections = 0
for loop in loops:
for i,p in enumerate(loop):
a = Vector(verts[loop[i-1]])
b = Vector(verts[p])
if intersect_line_line_2d(pt,out,a,b):
intersections += 1
if intersections%2 == 1: #chek if the nb of intersection is odd
return True
def border_over_shape(border,verts,loops):
for loop in loops:
for i,p in enumerate(loop):
a = Vector(verts[loop[i-1]])
b = Vector(verts[p])
for j in range(0,4):
c = border[j-1]
d = border[j]
if intersect_line_line_2d(a,b,c,d):
return True
for point in verts:
if point_inside_rectangle(point,border):
return True
for point in border:
if point_over_shape(point,verts,loops):
return True
def border_loop(vert, loop):
border_edge =[e for e in vert.link_edges if e.is_boundary]
if border_edge:
for edge in border_edge:
other_vert = edge.other_vert(vert)
if not other_vert in loop:
loop.append(other_vert)
border_loop(other_vert, loop)
return loop
else:
return [vert]
def contour_loops(bm, vert_index=0, loops=None, vert_indices=None):
loops = loops or []
vert_indices = vert_indices or [v.index for v in bm.verts]
bm.verts.ensure_lookup_table()
loop = border_loop(bm.verts[vert_index], [bm.verts[vert_index]])
if len(loop) >1:
loops.append(loop)
for v in loop:
vert_indices.remove(v.index)
if len(vert_indices):
contour_loops(bm, vert_indices[0], loops, vert_indices)
return loops
def get_IK_bones(IK_last):
ik_chain = IK_last.parent_recursive
ik_len = 0
#Get IK len:
for c in IK_last.constraints:
if c.type == 'IK':
ik_len = c.chain_count -2
break
IK_root = ik_chain[ik_len]
IK_mid= ik_chain[:ik_len]
IK_mid.reverse()
IK_mid.append(IK_last)
return IK_root,IK_mid
def find_mirror(name):
mirror = None
prop= False
if name:
if name.startswith('[')and name.endswith(']'):
prop = True
name= name[:-2][2:]
match={
'R': 'L',
'r': 'l',
'L': 'R',
'l': 'r',
}
separator=['.','_']
if name.startswith(tuple(match.keys())):
if name[1] in separator:
mirror = match[name[0]]+name[1:]
if name.endswith(tuple(match.keys())):
if name[-2] in separator:
mirror = name[:-1]+match[name[-1]]
if mirror and prop == True:
mirror='["%s"]'%mirror
return mirror
else:
return None
def is_shape(ob):
scn = bpy.context.scene
canvas = scn.rig_picker.canvas
if not canvas or ob.hide_render:
return False
shapes = {ob for col in canvas.users_collection for ob in col.all_objects}
if ob.type in ('MESH', 'CURVE', 'FONT') and ob in shapes:
return True
return False
def is_over_region(self,context,event):
inside = 2 < event.mouse_region_x < context.region.width -2 and \
2 < event.mouse_region_y < context.region.height -2 and \
[a for a in context.screen.areas if a.as_pointer()==self.adress] and \
not context.screen.show_fullscreen
return inside
def bound_box_center(ob):
points = [ob.matrix_world@Vector(p) for p in ob.bound_box]
x = [v[0] for v in points]
y = [v[1] for v in points]
z = [v[2] for v in points]
return (sum(x) / len(points), sum(y) / len(points),sum(z) / len(points))