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