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 import importlib
modules = [ modules = (
'.op_material', '.operators',
'.op_picker',
'.op_shape',
'.properties', '.properties',
'.panels', '.panels',
'.area', '.area',
'.gizmo', '.gizmo',
'.picker' '.draw_handlers'
] )
functions = [
#'.func_picker',
'.func_shape',
'.snapping_utils',
'.utils'
]
import bpy import bpy
if "bpy" in locals(): if "bpy" in locals():
if not bpy.app.background: if not bpy.app.background:
for name in modules + functions: for name in modules:
module = importlib.import_module(name, __name__) module = importlib.import_module(name, __name__)
importlib.reload(module) 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 bpy
import gpu import gpu
from gpu_extras.batch import batch_for_shader from gpu_extras.batch import batch_for_shader
import gpu import blf
from mathutils import bvhtree, Vector from mathutils import bvhtree, Vector
from mathutils.geometry import intersect_point_quad_2d, intersect_point_tri_2d, intersect_tri_tri_2d from mathutils.geometry import intersect_point_quad_2d, intersect_point_tri_2d, intersect_tri_tri_2d
from .constants import PICKERS from ..constants import PICKERS
from .utils import get_operator_from_id from .addon_utils import get_operator_from_id
from pathlib import Path from pathlib import Path
import re import re
import json import json
import os
import threading import threading
@ -383,8 +384,6 @@ class OperatorShape(Shape):
gpu.state.blend_set('NONE') gpu.state.blend_set('NONE')
class Picker: class Picker:
def __init__(self, rig, shapes): def __init__(self, rig, shapes):
@ -474,6 +473,8 @@ class Picker:
s.release_event(mode) s.release_event(mode)
s.press = False s.press = False
#bpy.context.area.tag_redraw()
def tooltip_event(self): def tooltip_event(self):
@ -538,9 +539,12 @@ class Picker:
self.mouse = location self.mouse = location
location = self.region.view2d.region_to_view(*location) location = self.region.view2d.region_to_view(*location)
for s in self.shapes: self.hover_shape = None
if s.move_event(location): for shape in reversed(self.shapes):
self.hover_shape = s if self.hover_shape:
shape.hover = False
elif shape.move_event(location):
self.hover_shape = shape
#if point_inside_rectangle(self.end, bound): #if point_inside_rectangle(self.end, bound):
# over = point_over_shape(self.end,points, edges) # over = point_over_shape(self.end,points, edges)
@ -581,86 +585,26 @@ class Picker:
gpu.state.blend_set('NONE') gpu.state.blend_set('NONE')
''' '''
def get_picker_path(rig, start=None):
def draw_callback_view(): picker_path = rig.data.get('rig_picker', {}).get('source')
sp = bpy.context.space_data if not picker_path:
if not sp or not sp.tree_type == 'RigPickerTree':
return return
ob = bpy.context.object picker_path = bpy.path.abspath(picker_path, library=rig.data.library, start=start)
if not ob or ob.type !='ARMATURE' or not ob.data.rig_picker.source:
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 return
if ob not in PICKERS: if 'picker' in rig.data['rig_picker'].keys():
if 'picker' in ob.data.rig_picker: del rig.data['rig_picker']['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

@ -2,9 +2,65 @@ import bpy
import bmesh import bmesh
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
from bpy_extras import mesh_utils 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): def get_picker_datas(objects, canvas, rig):
picker_datas = [] picker_datas = []
gamma = 1 / 2.2 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 mathutils import Vector, Matrix, Euler
from .constants import PICKERS 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 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 draw_callback_px
#from .func_bgl import select_bone #from .func_bgl import select_bone
#from .func_picker import * #from core.picker import *
#from .utils import is_over_region #from .utils import is_over_region
import gpu import gpu
from mathutils import Vector from mathutils import Vector
from gpu_extras.batch import batch_for_shader from gpu_extras.batch import batch_for_shader
from pathlib import Path from pathlib import Path
import json import json
import os
vertex_shader = ''' vertex_shader = '''
@ -92,7 +96,7 @@ class RP_OT_box_select(bpy.types.Operator):
bl_idname = "node.rp_box_select" bl_idname = "node.rp_box_select"
bl_label = "Picker 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') color_shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
dash_shader = gpu.types.GPUShader(vertex_shader, fragment_shader) 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""" """Tooltip"""
bl_idname = "node.move_bone" bl_idname = "node.picker_transform"
bl_label = "Move Bone in Picker View" bl_label = "Move Bone in Picker View"
mode : EnumProperty(items=[(m, m.title(), '') for m in ('TRANSLATE', 'ROTATE', 'SCALE')])
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
if not is_picker_space(context): return is_picker_space(context)
return
def invoke(self, context, event):
self.mouse = event.mouse_region_x, event.mouse_region_y
self.bone_data = {b: b.matrix.copy() for b in context.selected_pose_bones} 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")
context.window_manager.modal_handler_add(self) return {"FINISHED"}
return {'RUNNING_MODAL'}
def modal(self, context, event): # def invoke(self, context, event):
# self.mouse = event.mouse_region_x, event.mouse_region_y
delta_x = (event.mouse_region_x - self.mouse[0]) / 1000 # self.bone_data = {b: b.matrix.copy() for b in context.selected_pose_bones}
delta_y = (event.mouse_region_y - self.mouse[1]) / 1000
# context.window_manager.modal_handler_add(self)
# return {'RUNNING_MODAL'}
for bone, matrix in self.bone_data.items(): # def modal(self, context, event):
bone.matrix.translation = matrix.translation + Vector((delta_x, 0, delta_y))
# 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) # #print(delta_x, delta_y)
if event.type=="LEFTMOUSE" and event.value == 'RELEASE': # if event.type=="LEFTMOUSE" and event.value == 'RELEASE':
return {'FINISHED'} # return {'FINISHED'}
if event.type=="RIGHTMOUSE": # if event.type=="RIGHTMOUSE":
for bone, matrix in self.bone_data.items(): # for bone, matrix in self.bone_data.items():
bone.matrix = matrix # bone.matrix = matrix
return {'CANCELLED'} # return {'CANCELLED'}
return {'RUNNING_MODAL'} # return {'RUNNING_MODAL'}
class RP_OT_function_execute(bpy.types.Operator): 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 bone = picker.hover_shape.bone
hide = picker.hover_shape.hide hide = picker.hover_shape.hide
if bone: if bone:
for i, l in enumerate(bone.bone.layers): for i, l in enumerate(bone.bone.layers):
if l: if l:
ob.data.layers[i] = hide ob.data.layers[i] = hide
print('Bone Layer toogle')
#if picker.hover_bone:
context.region.tag_redraw() context.region.tag_redraw()
return {"FINISHED"} 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): class RP_OT_pack_picker(bpy.types.Operator):
"""Pack Unpack the picker on the rig""" """Pack Unpack the picker on the rig"""
bl_idname = "rigpicker.pack_picker" bl_idname = "rigpicker.pack_picker"
@ -335,19 +359,21 @@ class RP_OT_pack_picker(bpy.types.Operator):
def execute(self, context): def execute(self, context):
print('Pack Picker') print('Pack Picker')
rig = context.object
picker_path = get_picker_path(rig)
ob = context.object if 'picker' in rig.data.rig_picker.keys():
picker_src = Path(bpy.path.abspath(ob.data.rig_picker.source, library=ob.data.library)) unpack_picker(rig)
self.report({"INFO"}, f'The picker is unpacked')
if 'picker' in ob.data.rig_picker.keys():
del ob.data.rig_picker['picker']
return {"FINISHED"} return {"FINISHED"}
if not picker_src.exists(): if not picker_path.exists():
self.report({"ERROR"}, f'The path of the picker not exist: {picker_src}') self.report({"ERROR"}, f'The path of the picker not exist: {picker_path}')
return {"CANCELLED"} 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"} return {"FINISHED"}
@ -383,8 +409,19 @@ class RP_MT_context_menu(bpy.types.Menu):
# Set the menu operators and draw functions # Set the menu operators and draw functions
def draw(self, context): def draw(self, context):
layout = self.layout 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' #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") kmi = km.keymap_items.new("rigpicker.toogle_bone_layer", type="LEFTMOUSE", value="DOUBLE_CLICK")
keymaps.append((km, kmi)) 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)) keymaps.append((km, kmi))
kmi = km.keymap_items.new("wm.call_menu", type="RIGHTMOUSE", value="PRESS") kmi = km.keymap_items.new("node.picker_transform", type="R", value="PRESS")
kmi.properties.name = "RP_MT_context_menu" 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)) keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS") 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_toogle_bone_layer,
RP_OT_call_operator, RP_OT_call_operator,
RP_MT_context_menu, RP_MT_context_menu,
RP_OT_move_bone, RP_OT_picker_transform,
RP_OT_context_menu_picker,
RP_OT_pack_picker RP_OT_pack_picker
#RP_OT_ui_draw #RP_OT_ui_draw
) )

View File

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

View File

@ -1,7 +1,8 @@
import bpy import bpy
#import collections #import collections
#import inspect #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 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))