666 lines
19 KiB
Python
666 lines
19 KiB
Python
|
|
import bpy
|
|
import gpu
|
|
from gpu_extras.batch import batch_for_shader
|
|
import bgl
|
|
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 pathlib import Path
|
|
import re
|
|
import json
|
|
|
|
import threading
|
|
|
|
|
|
|
|
class Shape:
|
|
def __init__(self, picker, points, polygons=None, edges=None, tooltip='', color=None, source_name=''):
|
|
|
|
self.type = 'display'
|
|
self.picker = picker
|
|
self.rig = picker.rig
|
|
self.source_name = source_name
|
|
|
|
self.hover = False
|
|
self.press = False
|
|
|
|
|
|
self.shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
|
|
|
|
#self.hover_shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
|
|
#self.hover_shader.uniform_float("color", [1, 1, 1, 0.1])
|
|
self.hover_color = [1, 1, 1, 0.1]
|
|
|
|
self.tooltip = tooltip
|
|
self.color = color
|
|
self.points = points
|
|
self.polygons = polygons or []
|
|
self.edges = edges or []
|
|
|
|
#print(points, self.polygons)
|
|
|
|
self.p_batch = batch_for_shader(self.shader, 'TRIS', {"pos": self.points}, indices=self.polygons)
|
|
self.e_batch = batch_for_shader(self.shader, 'LINES', {"pos": self.points}, indices=self.edges)
|
|
|
|
#if polygons:
|
|
# self.batch = batch_for_shader(self.shader, 'TRIS', {"pos": points}, indices=polygons)
|
|
#else:
|
|
#pts = []
|
|
#for loop in self.edges:
|
|
# pts += [self.points[i] for i in loop]
|
|
|
|
# self.batch = batch_for_shader(self.shader, 'LINES', {"pos": points}, indices=indices)
|
|
|
|
points_x = [v[0] for v in points]
|
|
points_y = [v[1] for v in points]
|
|
|
|
self.bound = [
|
|
(min(points_x), max(points_y)),
|
|
(max(points_x), max(points_y)),
|
|
(max(points_x), min(points_y)),
|
|
(min(points_x), min(points_y))
|
|
]
|
|
|
|
@property
|
|
def color(self):
|
|
return self._color
|
|
|
|
@color.setter
|
|
def color(self, color=None):
|
|
if not color:
|
|
color = [0.5, 0.5, 0.5, 1]
|
|
elif isinstance(color, (tuple, list)) and len(color) in (3, 4):
|
|
if len(color) == 3:
|
|
color = [*color, 1]
|
|
elif len(color) == 4:
|
|
color = list(color)
|
|
else:
|
|
raise Exception('color must have a len of 3 or 4')
|
|
else:
|
|
raise Exception(f'color is {type(color)} must be None or (tuple, list)')
|
|
|
|
#self.shader.uniform_float("color", color)
|
|
self._color = color
|
|
|
|
def draw(self):
|
|
|
|
self.shader.bind()
|
|
self.shader.uniform_float("color", self.color)
|
|
|
|
if self.polygons:
|
|
self.p_batch.draw(self.shader)
|
|
if self.edges:
|
|
self.e_batch.draw(self.shader)
|
|
|
|
def move_event(self, location):
|
|
if not intersect_point_quad_2d(location, *self.bound):
|
|
self.hover = False
|
|
return False
|
|
|
|
for p in self.polygons:
|
|
if intersect_point_tri_2d(location, *[self.points[i] for i in p]):
|
|
self.hover = True
|
|
return True
|
|
|
|
self.hover = False
|
|
return False
|
|
|
|
def press_event(self, mode='SET'):
|
|
self.press = True
|
|
|
|
def release_event(self, mode='SET'):
|
|
self.press = False
|
|
|
|
|
|
class BoneShape(Shape):
|
|
def __init__(self, picker, points, polygons, edges, bone, tooltip='', color=None, source_name=''):
|
|
super().__init__(picker, points=points, polygons=polygons, edges=edges,
|
|
tooltip=tooltip, color=color, source_name=source_name)
|
|
|
|
self.type = 'bone'
|
|
self.bone = bone
|
|
self.active_color = [1, 1, 1, 0.1]
|
|
#self.contour_shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
|
|
|
|
#self.contour_batches = []
|
|
|
|
#self.line_batch =
|
|
#for loop in edges:
|
|
# loop_points = [points[i] for i in loop]
|
|
# batch = batch_for_shader(self.shader, 'LINE_LOOP', {"pos": loop_points})
|
|
# #self.contour_batches.append(batch)
|
|
|
|
theme = bpy.context.preferences.themes['Default']
|
|
self.bone_colors = {
|
|
'select': [*theme.view_3d.bone_pose, 1],
|
|
'normal': [0.05, 0.05, 0.05, 1],
|
|
'active': [*theme.view_3d.bone_pose_active, 1],
|
|
'hide': [0.85, 0.85, 0.85, 0.2],
|
|
}
|
|
|
|
if bone and bone.bone_group:
|
|
normal_color = bone.bone_group.colors.normal.copy()
|
|
normal_color.s *= 0.75
|
|
self.bone_colors['normal'] = [*normal_color, 1]
|
|
self.bone_colors['select'] = [*bone.bone_group.colors.select, 1]
|
|
self.bone_colors['active'] = [*bone.bone_group.colors.active, 1]
|
|
self.bone_colors['hide'] = [*normal_color, 0.1]
|
|
|
|
#self.color = [i for i in self.color]
|
|
|
|
@property
|
|
def select(self):
|
|
if not self.bone:
|
|
return False
|
|
|
|
return self.bone in (bpy.context.selected_pose_bones or []) #self.bone.bone.select
|
|
|
|
@property
|
|
def active(self):
|
|
if not self.bone:
|
|
return False
|
|
|
|
return self.bone == bpy.context.active_pose_bone #self.rig.data.bones.active == self.bone.bone
|
|
|
|
@property
|
|
def hide(self):
|
|
if not self.bone:
|
|
return False
|
|
|
|
#return self.bone not in (bpy.context.visible_pose_bones or [])
|
|
bl = [i for i, l in enumerate(self.bone.bone.layers) if l]
|
|
rl = [i for i, l in enumerate(self.rig.data.layers) if l]
|
|
return self.bone.bone.hide or not len(set(bl).intersection(rl))
|
|
|
|
@property
|
|
def bone_color(self):
|
|
if not self.bone:
|
|
return [0, 0, 0, 1]
|
|
|
|
bone = self.bone.bone
|
|
|
|
bl = bone.layers
|
|
rl = self.rig.data.layers
|
|
|
|
if self.select and self.active:
|
|
return self.bone_colors['active']
|
|
elif self.select:
|
|
return self.bone_colors['select']
|
|
elif self.hide:
|
|
return self.bone_colors['hide']
|
|
else:
|
|
return self.bone_colors['normal']
|
|
|
|
def draw(self):
|
|
bgl.glEnable(bgl.GL_BLEND)
|
|
|
|
if self.hide:
|
|
self.shader.uniform_float("color", (*self.color[:3], 0.4))
|
|
self.p_batch.draw(self.shader)
|
|
#elif self.select:
|
|
# self.shader.uniform_float("color", self.bone_color)
|
|
# self.p_batch.draw(self.shader)
|
|
else:
|
|
super().draw()
|
|
|
|
# Overlay the fill slightly with the bone color
|
|
#self.shader.uniform_float("color", (*self.bone_color[:3], 0.1))
|
|
#self.p_batch.draw(self.shader)
|
|
|
|
if self.select:
|
|
color = self.hover_color
|
|
|
|
if self.select or self.hover:
|
|
color = self.hover_color
|
|
|
|
if self.select and self.hover:
|
|
color = self.active_color
|
|
|
|
self.shader.uniform_float("color", color)
|
|
self.p_batch.draw(self.shader)
|
|
|
|
#Overlay the fill slightly with the bone color
|
|
self.shader.uniform_float("color", (*self.bone_colors['normal'][:3], 0.1))
|
|
self.p_batch.draw(self.shader)
|
|
|
|
|
|
#self.contour_shader.bind()
|
|
|
|
#print(self.bone_color)
|
|
if self.select or self.active:
|
|
bgl.glLineWidth(2)
|
|
#if not self.hide:
|
|
self.shader.uniform_float("color", self.bone_color)
|
|
#for b in self.contour_batches:
|
|
self.e_batch.draw(self.shader)
|
|
|
|
bgl.glLineWidth(1)
|
|
bgl.glDisable(bgl.GL_BLEND)
|
|
|
|
def assign_bone_event(self):
|
|
|
|
#print('assign_bone_event', self)
|
|
scn = bpy.context.scene
|
|
rig = scn.rig_picker.rig
|
|
source_object = scn.objects.get(self.source_name)
|
|
if not source_object:
|
|
print(f'Source object {self.source_name} not found')
|
|
return
|
|
|
|
active_bone = rig.data.bones.active
|
|
if not active_bone:
|
|
print('You need to have an active bone')
|
|
return
|
|
|
|
source_object.rig_picker.name = rig.data.bones.active.name
|
|
|
|
def release_event(self, mode='SET'):
|
|
super().release_event(mode)
|
|
|
|
if self.hide or not self.bone:
|
|
return
|
|
|
|
select = True
|
|
if mode == 'SUBSTRACT':
|
|
select = False
|
|
|
|
self.bone.bone.select = select
|
|
if self.hover:
|
|
|
|
if mode != 'SUBSTRACT':
|
|
self.rig.data.bones.active = self.bone.bone
|
|
|
|
|
|
def border_select(self, border, mode='SET'):
|
|
'''
|
|
if ( not any(intersect_point_quad_2d(b, *self.bound) for b in border) and
|
|
not any(intersect_point_quad_2d(b, *border) for b in self.bound) ):
|
|
return
|
|
'''
|
|
if not self.bone:
|
|
return
|
|
|
|
if self.hide:
|
|
self.bone.bone.select = False
|
|
return
|
|
|
|
bound_tri1 = self.bound[0], self.bound[1], self.bound[2]
|
|
bound_tri2 = self.bound[2], self.bound[3], self.bound[0]
|
|
|
|
border_tri1 = border[0], border[1], border[2]
|
|
border_tri2 = border[2], border[3], border[0]
|
|
|
|
if (not intersect_tri_tri_2d(*border_tri1, *bound_tri1) and
|
|
not intersect_tri_tri_2d(*border_tri1, *bound_tri2) and
|
|
not intersect_tri_tri_2d(*border_tri2, *bound_tri1) and
|
|
not intersect_tri_tri_2d(*border_tri2, *bound_tri2)):
|
|
return
|
|
|
|
select = True
|
|
if mode == 'SUBSTRACT':
|
|
select = False
|
|
|
|
for polygon in self.polygons:
|
|
points = [self.points[i] for i in polygon]
|
|
|
|
if intersect_tri_tri_2d(*border_tri1, *points):
|
|
self.bone.bone.select = select
|
|
return
|
|
|
|
if intersect_tri_tri_2d(*border_tri2, *points):
|
|
self.bone.bone.select = select
|
|
return
|
|
|
|
'''
|
|
for b in border:
|
|
if intersect_point_tri_2d(b, *points):
|
|
self.bone.bone.select = select
|
|
return
|
|
|
|
for p in points:
|
|
if intersect_point_quad_2d(p, *border):
|
|
self.bone.bone.select = select
|
|
return
|
|
'''
|
|
|
|
class OperatorShape(Shape):
|
|
def __init__(self, picker, points, polygons, operator, tooltip='', color=None, source_name=''):
|
|
super().__init__(picker, points=points, polygons=polygons, tooltip=tooltip,
|
|
color=color, source_name=source_name)
|
|
|
|
self.type = 'operator'
|
|
self.active_color = [1, 1, 1, 0.15]
|
|
self.press_color = [0, 0, 0, 0.25]
|
|
self.operator = operator
|
|
#self.arguments = arguments#{k:eval(v)}
|
|
#self.operator = get_operator_from_id(self.operator)
|
|
|
|
if not tooltip:
|
|
self.tooltip = self.operator.replace('bpy.ops.', '').replace("'INVOKE_DEFAULT', ", '')
|
|
|
|
#self.reg_args = re.compile(r'(\w+)=')
|
|
'''
|
|
def parse_args(self):
|
|
|
|
args = self.reg_args.split(self.arguments)[1:]
|
|
#print(args, zip(args[::2], args[1::2]))
|
|
return {k: eval(v) for k, v in zip(args[::2], args[1::2])}
|
|
#return {k:eval(v) for k, v in self.reg_args.split(self.arguments)}
|
|
'''
|
|
|
|
def release_event(self, mode='SET'):
|
|
super().release_event(mode)
|
|
|
|
#args = self.parse_args()
|
|
if not self.operator:
|
|
return
|
|
|
|
exec(self.operator)
|
|
#f'bpy.ops;{idname}'
|
|
|
|
#print(self.idname)
|
|
#print(self.arguments)
|
|
#else:
|
|
# self.bone.bone.select = False
|
|
|
|
def draw(self):
|
|
super().draw()
|
|
|
|
if self.press:
|
|
color = self.press_color
|
|
elif self.hover:
|
|
color = self.hover_color
|
|
else:
|
|
return
|
|
|
|
bgl.glEnable(bgl.GL_BLEND)
|
|
self.shader.uniform_float("color", color)
|
|
self.p_batch.draw(self.shader)
|
|
bgl.glDisable(bgl.GL_BLEND)
|
|
|
|
|
|
|
|
|
|
class Picker:
|
|
def __init__(self, rig, shapes):
|
|
|
|
self.region = bpy.context.region
|
|
self.rig = rig
|
|
|
|
self.shapes = []
|
|
self.box_select = None
|
|
self.hover_shape = None
|
|
self.shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
|
|
|
|
self.tooltip_shape = None
|
|
self.tooltip_mouse = None
|
|
self.tooltip = ''
|
|
|
|
self.timer = None
|
|
self.mouse = None
|
|
|
|
for s in shapes:
|
|
if not s['points']:
|
|
continue
|
|
|
|
if s['type'] in ('CANVAS', 'DISPLAY'):
|
|
shape = Shape(
|
|
self,
|
|
points=s['points'],
|
|
polygons=s['polygons'],
|
|
edges=s['edges'],
|
|
color=s['color']
|
|
)
|
|
|
|
elif s['type'] == 'BONE':
|
|
bone = rig.pose.bones.get(s['bone'])
|
|
#if not bone:
|
|
# print(f'Bone {s["bone"]} not exist')
|
|
# continue
|
|
|
|
shape = BoneShape(
|
|
self,
|
|
source_name=s['source_name'],
|
|
points=s['points'],
|
|
polygons=s['polygons'],
|
|
edges=s['edges'],
|
|
bone=bone,
|
|
color=s['color']
|
|
)
|
|
|
|
elif s['type'] == 'OPERATOR':
|
|
shape = OperatorShape(
|
|
self,
|
|
source_name=s['source_name'],
|
|
points=s['points'],
|
|
polygons=s['polygons'],
|
|
operator=s['operator'],
|
|
color=s['color'],
|
|
tooltip=s['tooltip'],
|
|
)
|
|
|
|
self.shapes.append(shape)
|
|
|
|
def assign_bone_event(self):
|
|
for s in self.shapes:
|
|
if s.type=='bone' and s.hover:
|
|
s.assign_bone_event()
|
|
|
|
bpy.ops.rigpicker.save_picker()
|
|
|
|
def press_event(self, mode='SET'):
|
|
for s in self.shapes:
|
|
if s.hover:
|
|
s.press_event(mode)
|
|
else:
|
|
s.press = False
|
|
|
|
def release_event(self, mode='SET'):
|
|
if mode == 'SET':
|
|
for b in self.rig.pose.bones:
|
|
b.bone.select = False
|
|
|
|
#bpy.ops.pose.select_all(action='DESELECT')
|
|
|
|
#print('PICKER release event', mode)
|
|
#print(f'type={event.type}, value={event.value}, ctrl={event.ctrl}, shift={event.shift}, alt={event.alt}')
|
|
|
|
for s in self.shapes:
|
|
if s.hover:
|
|
s.release_event(mode)
|
|
|
|
s.press = False
|
|
|
|
|
|
def tooltip_event(self):
|
|
#print('Tooltip Event', self)
|
|
|
|
#print(self.hover_shape, self.hover_shape.type)
|
|
|
|
if self.hover_shape and self.hover_shape.type != 'display':
|
|
if self.hover_shape.type == 'bone':
|
|
self.tooltip = self.hover_shape.bone.name
|
|
else:
|
|
self.tooltip = self.hover_shape.tooltip
|
|
self.tooltip_shape = self.hover_shape
|
|
else:
|
|
self.tooltip = ''
|
|
self.tooltip_shape = None
|
|
|
|
self.tooltip_mouse = self.mouse
|
|
|
|
self.timer.cancel()
|
|
|
|
#print(self.tooltip)
|
|
|
|
self.region.tag_redraw()
|
|
|
|
'''
|
|
picker.tooltip_event(event='SHOW')
|
|
region.tag_redraw()
|
|
|
|
|
|
|
|
#context.region.tag_redraw()
|
|
|
|
picker.tooltip_event(event='HIDE')
|
|
bpy.app.timers.register(partial(tooltip, context.region), first_interval=1)
|
|
'''
|
|
'''
|
|
def tooltip_event(self, event):
|
|
|
|
|
|
|
|
self.tooltip = ''
|
|
|
|
if event == 'SHOW':
|
|
if self.hover_shape.type == 'bone':
|
|
self.tooltip = self.hover_shape.bone.name
|
|
|
|
#bpy.context.region.tag_redraw()
|
|
'''
|
|
|
|
def border_select(self, border, mode):
|
|
border = [bpy.context.region.view2d.region_to_view(*b) for b in border]
|
|
|
|
if mode == 'SET':
|
|
for b in self.rig.pose.bones:
|
|
b.bone.select = False
|
|
|
|
for s in (s for s in self.shapes if s.type=='bone'):
|
|
s.border_select(border, mode)
|
|
|
|
def move_event(self, location):
|
|
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
|
|
|
|
#if point_inside_rectangle(self.end, bound):
|
|
# over = point_over_shape(self.end,points, edges)
|
|
|
|
#if bpy.app.timers.is_registered(self.tooltip_event):
|
|
#try:
|
|
# bpy.app.timers.unregister(self.tooltip_event)
|
|
#except:
|
|
# pass
|
|
if self.tooltip_shape is not self.hover_shape:
|
|
self.tooltip = ''
|
|
|
|
if self.timer:
|
|
self.timer.cancel()
|
|
|
|
self.timer = threading.Timer(0.5, self.tooltip_event)
|
|
self.timer.start()
|
|
|
|
|
|
#bpy.app.timers.register(self.tooltip_event, first_interval=1)
|
|
|
|
def draw(self):
|
|
for s in self.shapes:
|
|
s.draw()
|
|
'''
|
|
if self.box_select:
|
|
|
|
self.box_shader.uniform_float("color", self.box_select_color)
|
|
batch = batch_for_shader(self.shader, 'LINE_LOOP', {"pos": []})
|
|
|
|
|
|
for b in self.contour_batches:
|
|
b.draw(self.contour_shader)
|
|
|
|
self.batch.draw(self.shader)
|
|
|
|
|
|
bgl.glDisable(bgl.GL_BLEND)
|
|
'''
|
|
|
|
|
|
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)
|
|
#bgl.glEnable(bgl.GL_BLEND)
|
|
|
|
|
|
# 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)
|
|
#bgl.glDisable(bgl.GL_BLEND)
|
|
|
|
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 |