From c31a73f80fca0867e766254476972ad0493ae165 Mon Sep 17 00:00:00 2001 From: ChristopheSeux Date: Mon, 19 Feb 2024 11:53:15 +0100 Subject: [PATCH] Picker Group --- __init__.py | 2 +- area.py | 73 ++++--- core/bl_utils.py | 39 +--- core/geometry_utils.py | 18 +- core/picker.py | 325 ++++++++++++++++++------------ core/shape.py | 12 +- draw_handlers.py | 108 ++++++++-- operators/picker.py | 244 ++++++++++------------ operators/shape.py | 132 ++++++------ properties.py | 13 +- resources/KohinoorZerone-Zero.otf | Bin 0 -> 21064 bytes ui.py | 142 +++++++++++++ 12 files changed, 690 insertions(+), 418 deletions(-) create mode 100644 resources/KohinoorZerone-Zero.otf create mode 100644 ui.py diff --git a/__init__.py b/__init__.py index 34e9890..2479b1b 100644 --- a/__init__.py +++ b/__init__.py @@ -15,7 +15,7 @@ import importlib modules = ( '.operators', '.properties', - '.panels', + '.ui', '.area', '.gizmo', '.draw_handlers' diff --git a/area.py b/area.py index b3e5dd3..3b5a358 100644 --- a/area.py +++ b/area.py @@ -1,5 +1,6 @@ import bpy from bpy.types import NodeTree, NODE_PT_tools_active, NODE_HT_header +from .constants import PICKERS # Derived from the NodeTree base type, similar to Menu, Operator, Panel, etc. @@ -13,36 +14,52 @@ class RigPickerTree(NodeTree): bl_icon = 'OUTLINER_DATA_ARMATURE' - - - - - - def draw_header(self, context): - if context.space_data.tree_type == 'RigPickerTree': - layout = self.layout - layout.template_header() - #layout.separator_spacer() - - if not context.space_data.node_tree: - ntree = bpy.data.node_groups.get('.rig_picker') - if not ntree: - ntree = bpy.data.node_groups.new('.rig_picker', 'RigPickerTree') - - context.space_data.node_tree = ntree - - #layout.template_ID(context.space_data, "node_tree", new="node.new_node_tree") - #layout.separator_spacer() - - layout.operator('rigpicker.reload_picker', icon='FILE_REFRESH', text='') - - #layout.prop('rigpicker.reload_picker', icon='FILE_REFRESH', text='') - - layout.separator_spacer() - - else: + if not context.space_data.tree_type == 'RigPickerTree': self._draw(context) + return + + layout = self.layout + layout.template_header() + #layout.separator_spacer() + + if not context.space_data.node_tree: + ntree = bpy.data.node_groups.get('.rig_picker') + if not ntree: + ntree = bpy.data.node_groups.new('.rig_picker', 'RigPickerTree') + + context.space_data.node_tree = ntree + + #layout.template_ID(context.space_data, "node_tree", new="node.new_node_tree") + #layout.separator_spacer() + + #layout.operator('rigpicker.reload_picker', icon='FILE_REFRESH', text='') + ob = context.object + if not ob or not ob.type == 'ARMATURE': + return + + picker_group = PICKERS.get(ob) + if not picker_group: + return + + layout.separator_spacer() + layout.label(text=ob.name) + + layout.separator_spacer() + #row.alignment = 'RIGHT' + + row = layout.row(align=True) + + + + for i, picker in enumerate(picker_group.pickers): + row.operator('rigpicker.fit_picker', text=f'{i+1}').index=i + #row.separator_spacer() + row.operator('rigpicker.fit_picker', text='', icon='FULLSCREEN_ENTER').index = -1 + + #layout.prop('rigpicker.reload_picker', icon='FILE_REFRESH', text='') + + def tools_from_context(context, mode=None): sp = context.space_data diff --git a/core/bl_utils.py b/core/bl_utils.py index 4a08126..1f858ac 100644 --- a/core/bl_utils.py +++ b/core/bl_utils.py @@ -31,40 +31,15 @@ def link_mat_to_object(ob): 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 +def flip_name(name): + if not name: + return + if name.startswith('[') and name.endswith(']'): #It's a custom property + flipped_name = bpy.utils.flip_name(name[:-2][2:]) + return f'["{flipped_name}"]' else: - return None + return bpy.utils.flip_name(name) def hide_layers(args): """ """ diff --git a/core/geometry_utils.py b/core/geometry_utils.py index f061898..ec2ea5c 100644 --- a/core/geometry_utils.py +++ b/core/geometry_utils.py @@ -21,15 +21,25 @@ def bound_box_center(ob): return (sum(x) / len(points), sum(y) / len(points),sum(z) / len(points)) +def bounding_rect(points): + x_points = sorted(p[0] for p in points) + y_points = sorted(p[1] for p in 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]) + return [ + Vector((x_points[0], y_points[-1])), + Vector((x_points[-1], y_points[-1])), + Vector((x_points[-1], y_points[0])), + Vector((x_points[0], y_points[0])), + ] + +def intersect_rects(rect, border): # returns None if rectangles don't intersect + dx = min(border[1][0],rect[1][0]) - max(border[0][0],rect[0][0]) + dy = min(border[0][1],rect[0][1]) - max(border[2][1],rect[2][1]) if (dx>=0) and (dy>=0): return dx*dy -def point_inside_rectangle(point, rect): +def point_inside_rect(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)): diff --git a/core/picker.py b/core/picker.py index 83623ca..e6b3659 100644 --- a/core/picker.py +++ b/core/picker.py @@ -4,9 +4,11 @@ import gpu from gpu_extras.batch import batch_for_shader import blf 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 .addon_utils import get_operator_from_id +from .geometry_utils import bounding_rect from pathlib import Path import re @@ -16,7 +18,6 @@ import os import threading - class Shape: def __init__(self, picker, points, polygons=None, edges=None, tooltip='', color=None, source_name=''): @@ -28,7 +29,6 @@ class Shape: self.hover = False self.press = False - self.shader = gpu.shader.from_builtin('UNIFORM_COLOR') #self.hover_shader = gpu.shader.from_builtin('UNIFORM_COLOR') @@ -41,29 +41,10 @@ class Shape: 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)) - ] + self.rect = bounding_rect(points) @property def color(self): @@ -97,7 +78,7 @@ class Shape: self.e_batch.draw(self.shader) def move_event(self, location): - if not intersect_point_quad_2d(location, *self.bound): + if not intersect_point_quad_2d(location, *self.rect): self.hover = False return False @@ -224,6 +205,7 @@ class BoneShape(Shape): #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) + gpu.state.blend_set('NONE') #self.contour_shader.bind() @@ -238,7 +220,6 @@ class BoneShape(Shape): self.e_batch.draw(self.shader) gpu.state.line_width_set(1.0) - gpu.state.blend_set('NONE') def assign_bone_event(self): @@ -275,11 +256,6 @@ class BoneShape(Shape): 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 @@ -287,16 +263,16 @@ class BoneShape(Shape): 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] + rect_tri1 = self.rect[0], self.rect[1], self.rect[2] + rect_tri2 = self.rect[2], self.rect[3], self.rect[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)): + if (not intersect_tri_tri_2d(*border_tri1, *rect_tri1) and + not intersect_tri_tri_2d(*border_tri1, *rect_tri2) and + not intersect_tri_tri_2d(*border_tri2, *rect_tri1) and + not intersect_tri_tri_2d(*border_tri2, *rect_tri2)): return select = True @@ -388,116 +364,89 @@ class Picker: self.region = bpy.context.region self.rig = rig + self.translation = Vector((0, 0)) self.shapes = [] self.box_select = None self.hover_shape = None self.shader = gpu.shader.from_builtin('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']: + for shape_data in shapes: + if not shape_data['points']: continue - if s['type'] in ('CANVAS', 'DISPLAY'): + if shape_data['type'] in ('CANVAS', 'DISPLAY'): shape = Shape( self, - points=s['points'], - polygons=s['polygons'], - edges=s['edges'], - color=s['color'] + points=shape_data['points'], + polygons=shape_data['polygons'], + edges=shape_data['edges'], + color=shape_data['color'] ) - elif s['type'] == 'BONE': - bone = rig.pose.bones.get(s['bone']) + elif shape_data['type'] == 'BONE': + bone = rig.pose.bones.get(shape_data['bone']) #if not bone: - # print(f'Bone {s["bone"]} not exist') + # print(f'Bone {shape_data["bone"]} not exist') # continue shape = BoneShape( self, - source_name=s['source_name'], - points=s['points'], - polygons=s['polygons'], - edges=s['edges'], + source_name=shape_data['source_name'], + points=shape_data['points'], + polygons=shape_data['polygons'], + edges=shape_data['edges'], bone=bone, - color=s['color'] + color=shape_data['color'] ) - elif s['type'] == 'OPERATOR': + elif shape_data['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'], + source_name=shape_data['source_name'], + points=shape_data['points'], + polygons=shape_data['polygons'], + operator=shape_data['operator'], + color=shape_data['color'], + tooltip=shape_data['tooltip'], ) self.shapes.append(shape) + self.rect = bounding_rect([p for s in self.shapes for p in s.points]) + def assign_bone_event(self): - for s in self.shapes: - if s.type=='bone' and s.hover: - s.assign_bone_event() + for shape in self.shapes: + if shape.type=='bone' and shape.hover: + shape.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) + for shape in self.shapes: + #print(s) + if shape.hover: + shape.press_event(mode) else: - s.press = False + shape.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) + for shape in self.shapes: + if shape.hover: + shape.release_event(mode) - s.press = False + shape.press = False #bpy.context.area.tag_redraw() - - 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() @@ -524,27 +473,27 @@ class Picker: ''' def border_select(self, border, mode): - border = [bpy.context.region.view2d.region_to_view(*b) for b in border] + border = [Vector(b) - Vector(self.translation) 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) + + for shape in self.shapes: + if shape.type != 'bone': + continue + + shape.border_select(border, mode) def move_event(self, location): - self.mouse = location - location = self.region.view2d.region_to_view(*location) + self.mouse = Vector(location) - Vector(self.translation) self.hover_shape = None for shape in reversed(self.shapes): if self.hover_shape: shape.hover = False - elif shape.move_event(location): + elif shape.move_event(self.mouse): self.hover_shape = shape - #if point_inside_rectangle(self.end, bound): + #if point_inside_rect(self.end, rect): # over = point_over_shape(self.end,points, edges) #if bpy.app.timers.is_registered(self.tooltip_event): @@ -552,6 +501,85 @@ class Picker: # bpy.app.timers.unregister(self.tooltip_event) #except: # pass + + #bpy.app.timers.register(self.tooltip_event, first_interval=1) + + def draw(self): + with gpu.matrix.push_pop(): + gpu.matrix.translate(self.translation) + for shape in self.shapes: + shape.draw() + + def under_mouse(self, location): + location = Vector(location) - Vector(self.translation) + return intersect_point_quad_2d(location, *self.rect) + + @property + def center(self): + relative_center = (self.rect[1] + self.rect[3]) * 0.5 + return relative_center + self.translation + + +class PickerGroup: + def __init__(self, rig, pickers): + + self.view_location = Vector((0, 0)) + self.region = bpy.context.region + self.rig = rig + self.pickers = [] + + self.mouse = Vector((0, 0)) + + self.hover_shape = None + + self.tooltip_shape = None + self.tooltip_mouse = Vector((0, 0)) + self.tooltip = '' + + self.timer = None + + for picker in pickers: + self.add_picker(picker) + + def add_picker(self, picker): + self.pickers.append(Picker(self.rig, picker)) + + def draw(self): + y = 0 + for picker in self.pickers: + height = picker.rect[1][1] - picker.rect[-1][1] + + picker.translation = Vector((0, -y-height*0.5)) + picker.draw() + + y += height + 50 + + #break #TODO for now only draw first picker + + def move_event(self, mouse): + self.mouse = mouse + location = self.region.view2d.region_to_view(*mouse) + + view_location = self.region.view2d.region_to_view(0, 0) + if view_location != self.view_location: + self.view_location = view_location + self.tooltip = '' + if self.timer: + self.timer.cancel() + return + + hover_shape = None + for picker in self.pickers: + if not picker.under_mouse(location): + continue + + picker.move_event(location) + + if picker.hover_shape: + hover_shape = picker.hover_shape + + self.hover_shape = hover_shape + if self.tooltip_shape is not self.hover_shape: self.tooltip = '' @@ -561,27 +589,70 @@ class Picker: self.timer = threading.Timer(0.5, self.tooltip_event) self.timer.start() + def press_event(self, mode='SET'): + self.clear_tooltip() - #bpy.app.timers.register(self.tooltip_event, first_interval=1) + for picker in self.pickers: + picker.press_event(mode) - def draw(self): - for s in self.shapes: - s.draw() - ''' - if self.box_select: + def release_event(self, mode='SET'): - self.box_shader.uniform_float("color", self.box_select_color) - batch = batch_for_shader(self.shader, 'LINE_LOOP', {"pos": []}) + if mode == 'SET': + for bone in self.rig.data.bones: + bone.select = False + for picker in self.pickers: + picker.release_event(mode) + + def border_select(self, border, mode): + border = [self.region.view2d.region_to_view(*b) for b in border] + + if mode == 'SET': + for bone in self.rig.data.bones: + bone.select = False + + for picker in self.pickers: + picker.border_select(border, mode) + + def clear_tooltip(self): + self.tooltip = '' + self.tooltip_shape = None + self.timer.cancel() + self.region.tag_redraw() + + 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: + return self.clear_tooltip() - for b in self.contour_batches: - b.draw(self.contour_shader) - self.batch.draw(self.shader) + self.tooltip_mouse = self.mouse + + self.timer.cancel() + + #print(self.tooltip) + + self.region.tag_redraw() + + @property + def rect(self): + return bounding_rect([co - Vector(p.translation) for p in self.pickers for co in p.rect]) + + @property + def center(self): + center = sum(self.rect, Vector((0, 0))) / len(self.rect) + center[1] = -center[1] + return center - - gpu.state.blend_set('NONE') - ''' def get_picker_path(rig, start=None): picker_path = rig.data.get('rig_picker', {}).get('source') @@ -592,13 +663,15 @@ def get_picker_path(rig, start=None): 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()) + rig.data['rig_picker']['picker'] = json.loads(picker_path.read_text(encoding='utf-8')) + def unpack_picker(rig): if 'rig_picker' not in rig.data.keys(): diff --git a/core/shape.py b/core/shape.py index dcc15ce..91690e4 100644 --- a/core/shape.py +++ b/core/shape.py @@ -20,7 +20,7 @@ def border_over_shape(border,verts,loops): return True for point in verts: - if point_inside_rectangle(point,border): + if point_inside_rect(point,border): return True for point in border: @@ -29,7 +29,7 @@ def border_over_shape(border,verts,loops): def border_loop(vert, loop): - border_edge =[e for e in vert.link_edges if e.is_boundary] + border_edge =[e for e in vert.link_edges if e.is_rectary] if border_edge: for edge in border_edge: @@ -61,7 +61,7 @@ def contour_loops(bm, vert_index=0, loops=None, vert_indices=None): return loops -def get_picker_datas(objects, canvas, rig): +def get_picker_data(objects, canvas, rig): picker_datas = [] gamma = 1 / 2.2 @@ -72,11 +72,11 @@ def get_picker_datas(objects, canvas, rig): canvas_coords = [canvas.matrix_world@Vector((p.co)) for p in canvas_points] - canvas_x = [p[0] for p in canvas_coords] - canvas_y = [p[1] for p in canvas_coords] + height = max(canvas_coords).y - min(canvas_coords).y + width = max(canvas_coords).x - min(canvas_coords).x canvas_center = sum(canvas_coords, Vector()) / len(canvas_coords) - canvas_scale_fac = 4096 / (max(canvas_y) - min(canvas_y))# Reference height for the canvas + canvas_scale_fac = 4096 / max(height, width)# Reference height for the canvas objects.append(canvas) diff --git a/draw_handlers.py b/draw_handlers.py index 901b62c..42a070a 100644 --- a/draw_handlers.py +++ b/draw_handlers.py @@ -1,39 +1,91 @@ import bpy +import gpu +from gpu_extras.batch import batch_for_shader import blf from pathlib import Path from .constants import PICKERS -from .core.picker import Picker +from .core.picker import Picker, PickerGroup import json +def draw_rect_2d(position, width, height, color): + """ + Draw a 2d rectangele. + + :arg position: Position of the lower left corner. + :type position: 2D Vector + :arg width: Width of the rect. + :type width: float + :arg height: Height of the rect. + :type height: float + """ + + coords = ((0, 0), (1, 0), (1, 1), (0, 1)) + + shader = gpu.shader.from_builtin('UNIFORM_COLOR') + batch = batch_for_shader( + shader, 'TRI_FAN', + {"pos": coords}, + ) + + with gpu.matrix.push_pop(): + gpu.matrix.translate(position) + gpu.matrix.scale((width, height)) + shader.uniform_float("color", color) + + batch.draw(shader) + + def draw_callback_view(): sp = bpy.context.space_data if not sp or not sp.tree_type == 'RigPickerTree': return + if not sp.pin: + sp.pin = True + sp.show_region_ui = False + ob = bpy.context.object - if not ob or ob.type !='ARMATURE' or not ob.data.rig_picker.source: + if not ob or ob.type !='ARMATURE' or not ob.data.rig_picker.sources: 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']] + if 'pickers' in ob.data.rig_picker: + picker_datas = [s.to_dict() for s in ob.data.rig_picker['pickers']] else: - picker_path = Path(bpy.path.abspath(ob.data.rig_picker.source, library=ob.data.library)) + picker_datas = [] + for picker in ob.data.rig_picker.sources: + picker_path = Path(bpy.path.abspath(picker.source, library=ob.data.library)) - if not picker_path.exists(): - print(f'Picker path not exists: {picker_path.resolve()}') - return + if not picker_path.exists(): + print(f'Picker path not exists: {picker_path.resolve()}') + continue - 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) + print('Load picker from', picker_path.resolve()) + picker_data = json.loads(picker_path.read_text(encoding='utf-8')) + picker_datas.append(picker_data) + #shapes = [s.to_dict() for s in ob.data.rig_picker['shapes']] - picker = PICKERS.get(ob) - picker.draw() + PICKERS[ob] = PickerGroup(ob, picker_datas)#[Picker(ob, shapes=picker_data) for picker_data in picker_datas] + + # 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_group = PICKERS.get(ob) + picker_group.draw() def draw_callback_px(): sp = bpy.context.space_data @@ -42,23 +94,36 @@ def draw_callback_px(): ob = bpy.context.object - picker = PICKERS.get(ob) - if not picker or not picker.tooltip: + picker_group = PICKERS.get(ob) + if not picker_group or not picker_group.tooltip: return - text = picker.tooltip + text = picker_group.tooltip + mouse = picker_group.tooltip_mouse + ui_scale = bpy.context.preferences.system.ui_scale #print('Draw text', text) - font_id = 0 + blf.size(font_id, int(13 * ui_scale)) + + text_size = blf.dimensions(font_id, text) + text_pos = (mouse[0] - text_size[0]*0.33, mouse[1] - (34*ui_scale)) + + bg_margins = [8, 4] + bg_pos = (text_pos[0] - bg_margins[0], text_pos[1] - bg_margins[1]-1) + bg_size = (text_size[0] + 2*bg_margins[0], text_size[1] + 2*bg_margins[1]) + + gpu.state.blend_set('ALPHA') + draw_rect_2d(bg_pos, *bg_size, (0, 0, 0, 0.75)) + gpu.state.blend_set('NONE') + + #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.position(font_id, round(text_pos[0]), round(text_pos[1]), 0) blf.color(font_id, 1, 1, 1, 1) blf.shadow(font_id , 5, 0.0, 0.0, 0.0, 1) @@ -67,7 +132,6 @@ def draw_callback_px(): blf.draw(font_id, text) blf.disable(font_id, blf.SHADOW) - #gpu.state.blend_set('NONE') handle_view = None handle_pixel = None diff --git a/operators/picker.py b/operators/picker.py index e5006f0..db5b443 100644 --- a/operators/picker.py +++ b/operators/picker.py @@ -1,7 +1,8 @@ import bpy -from bpy.props import EnumProperty +from bpy.props import EnumProperty, IntProperty from ..constants import PICKERS from ..core.bl_utils import get_view_3d_override +from ..core.geometry_utils import bounding_rect from ..core.picker import get_picker_path, pack_picker, unpack_picker #from .func_bgl import draw_callback_px #from .func_bgl import select_bone @@ -151,15 +152,8 @@ class RP_OT_box_select(bpy.types.Operator): def modal(self, context, event): self.mouse = event.mouse_region_x, event.mouse_region_y - points_x = [v[0] for v in (self.start_mouse, self.mouse)] - points_y = [v[1] for v in (self.start_mouse, self.mouse)] - self.border = [ - (min(points_x), max(points_y)), - (max(points_x), max(points_y)), - (max(points_x), min(points_y)), - (min(points_x), min(points_y)) - ] + self.border = bounding_rect((self.start_mouse, self.mouse)) self.bg_batch = batch_for_shader(self.color_shader, 'TRI_FAN', {"pos": self.border}) self.contour_batch = batch_for_shader(self.dash_shader, 'LINE_LOOP', {"pos": self.border}) @@ -179,7 +173,7 @@ class RP_OT_box_select(bpy.types.Operator): if scn.rig_picker.use_pick_bone: self.picker.assign_bone_event() - elif (self.start_mouse[0] != self.mouse[0] and self.start_mouse[1] != self.mouse[1]): + elif (self.start_mouse[0] != self.mouse[0] or self.start_mouse[1] != self.mouse[1]): self.picker.border_select(self.border, self.mode) else: self.picker.move_event(self.mouse) @@ -255,7 +249,7 @@ class RP_OT_function_execute(bpy.types.Operator): bl_idname = "rigpicker.function_execute" bl_label = 'Function Execute' - shape_index = bpy.props.IntProperty() + shape_index = IntProperty() @classmethod def poll(cls, context): @@ -295,7 +289,7 @@ class RP_OT_reload_picker(bpy.types.Operator): def execute(self,context): PICKERS.clear() - for a in context.screen.areas: + for a in context.screen.areas: a.tag_redraw() return {"FINISHED"} @@ -379,24 +373,35 @@ class RP_OT_pack_picker(bpy.types.Operator): class RP_OT_call_operator(bpy.types.Operator): bl_idname = "rigpicker.call_operator" - bl_label = 'Toogle Bone Layer' + bl_label = 'Call operator' - operator: bpy.props.StringProperty() + operator : bpy.props.StringProperty() + view_3d : bpy.props.BoolProperty() @classmethod def poll(cls, context): return is_picker_space(context) def execute(self, context): - - print('CALL OPERATOR', self.operator) - - try: - exec(self.operator) - except Exception as e: - self.report({"ERROR"}, e) + if not self.operator.startswith('bpy.ops'): + print("Operator call_operator can only execute operator") + return {"FINISHED"} - context.region.tag_redraw() + print('CAll Operator') + + context.area.tag_redraw() + + override = {} + if self.view_3d: + override = get_view_3d_override() + + with context.temp_override(**override): + try: + exec(self.operator) + except Exception as e: + self.report({"ERROR"}, e) + + context.area.tag_redraw() return {"FINISHED"} @@ -423,127 +428,80 @@ class RP_MT_context_menu(bpy.types.Menu): #layout.operator("rigpicker.show_bone_layer", text="Show Bone Layer", ).type = 'ACTIVE' #layout.operator("rigidbody.objects_add", text="B Add Passive").type = 'PASSIVE' -''' -class RP_OT_ui_draw(bpy.types.Operator): - bl_idname = "rigpicker.ui_draw" - bl_label = "Rig UI Draw" - _handle = None - tmp_ob = None - tmp_bones = [] - start = (0,0) - end = (0,0) - border = ((0,0),(0,0),(0,0),(0,0)) - is_border = False - press = False - scale = 1 - offset = 0 - outside_point = (-1,-1) - addon_keymaps = [] +class RP_OT_add_picker_source(bpy.types.Operator): + bl_idname = "rigpicker.add_picker_source" + bl_label = "Add a Picker source" + bl_description = "Add a Picker source" + bl_options = {'UNDO', 'REGISTER'} - def set_shorcut(self,context): + def execute(self, context): + arm = context.object.data + arm.rig_picker.sources.add() + + return {'FINISHED'} + + +class RP_OT_remove_picker_source(bpy.types.Operator): + bl_idname = "rigpicker.remove_picker_source" + bl_label = "Delete a Picker source" + bl_description = "Delete a Picker source" + bl_options = {'UNDO', 'REGISTER'} + + index : IntProperty(default=-1) + + def execute(self, context): + arm = context.object.data + arm.rig_picker.sources.remove(self.index) + + return {'FINISHED'} + + +class RP_OT_fit_picker(bpy.types.Operator): + bl_idname = "rigpicker.fit_picker" + bl_label = "Fit Picker" + bl_description = "Fit Picker in 2d view" + bl_options = {'UNDO', 'REGISTER'} + + index : IntProperty() + + @classmethod + def poll(cls, context): + return is_picker_space(context) and PICKERS.get(context.object) + + def execute(self, context): ob = context.object - addon = bpy.context.window_manager.keyconfigs.addon + picker_group = PICKERS[ob] - if ob and ob.type =='ARMATURE' and ob.data.rig_picker and addon: - for i,shape in [(i,s) for i,s in enumerate(ob.data.rig_picker['shapes']) if s.get('function') and s.get('shortcut')]: - km = addon.keymaps.new(name = 'Image Generic', space_type = 'IMAGE_EDITOR',region_type = 'WINDOW') + if self.index >= len(picker_group.pickers): + return {"CANCELLED"} - split = shape["shortcut"].split(' ') - if len(split)==1: - shortcut = shape["shortcut"].upper() - modifier = None - else: - shortcut = split[1].upper() - modifier = split[0].lower() + picker = picker_group.pickers[self.index] + view2d = context.region.view2d - kmi = km.keymap_items.new("rigpicker.function_execute", type = shortcut, value = "CLICK") - kmi.properties.shape_index = i + if self.index == -1: + #print('Picker Group') + picker = picker_group - if modifier: - setattr(kmi,modifier,True) + view_rect = [view2d.view_to_region(*co, clip=False) for co in picker.rect] + bpy.ops.view2d.zoom_border( + xmin=round(view_rect[3][0]), xmax=round(view_rect[1][0]), + ymin=round(view_rect[3][1]), ymax=round(view_rect[1][1]), + wait_for_input=False, zoom_out=False + ) - self.addon_keymaps.append(km) + region_center = Vector((context.region.width * 0.5, context.region.height * 0.5)) + view_center = view2d.region_to_view(*region_center) - def remove_shorcut(self,context): - # Remove Shortcut - wm = bpy.context.window_manager - for km in self.addon_keymaps: - for kmi in km.keymap_items: - km.keymap_items.remove(kmi) + view_offset = Vector(view_center) + (picker.center - Vector(view_center)) + region_offset = view2d.view_to_region(*view_offset, clip=False) - self.addon_keymaps.clear() + delta = Vector(region_offset) - region_center - def modal(self, context, event): - inside = is_over_region(self,context,event) + bpy.ops.view2d.pan(deltax=round(delta[0]), deltay=round(delta[1])) - if context.object and context.object.type == 'ARMATURE' and context.area: - if not context.screen.is_animation_playing: - if self.tmp_ob != context.object: - context.area.tag_redraw() - self.remove_shorcut(context) - self.set_shorcut(context) - self.tmp_ob = context.object + return {'FINISHED'} - if self.tmp_bones != context.selected_pose_bones: - context.area.tag_redraw() - self.tmp_bones = context.selected_pose_bones - - if inside: - context.area.tag_redraw() - - if event.type == 'LEFTMOUSE': - if event.value == 'PRESS': # start selection - if inside: - self.start = (event.mouse_region_x,event.mouse_region_y) - self.press = True - - elif event.value == 'RELEASE' and self.press: - self.end = (event.mouse_region_x, event.mouse_region_y) - - select_bone(self, context, event) - bpy.ops.ed.undo_push() - - self.is_border= False - self.press = False - - if event.type == 'MOUSEMOVE': - self.end = (event.mouse_region_x, event.mouse_region_y) - - if self.press: - b_x = (min(self.start[0], self.end[0]), max(self.start[0], self.end[0])) - b_y = (min(self.start[1], self.end[1]), max(self.start[1], self.end[1])) - self.border = ((b_x[0], b_y[1]), (b_x[1], b_y[1]), (b_x[1], b_y[0]), (b_x[0], b_y[0])) - self.is_border = True if (b_x[1]-b_x[0])+(b_y[1]-b_y[0]) > 4 else False - - if self.is_border: - select_bone(self, context, event) - - elif event.type in {'ESC',} and inside: - bpy.types.SpaceImageEditor.draw_handler_remove(self._handle, 'WINDOW') - self.remove_shorcut(context) - - - return {'CANCELLED'} - - return {'PASS_THROUGH'} - - def invoke(self, context, event): - #shortcut Creation - - - - context.space_data.image = None - self.adress = context.area.as_pointer() - args = (self, context) - - self._handle = bpy.types.SpaceImageEditor.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL') - - - context.window_manager.modal_handler_add(self) - - return {'RUNNING_MODAL'} -''' keymaps = [] def register_keymaps(): @@ -551,10 +509,16 @@ def register_keymaps(): km = wm.keyconfigs.addon.keymaps.new(name="Node Editor", space_type="NODE_EDITOR") - kmi = km.keymap_items.new("rigpicker.call_operator", type="X", value="PRESS") - kmi.properties.operator = "bpy.ops.animtoolbox.reset_bone()" + for i in range(10): + kmi = km.keymap_items.new("rigpicker.fit_picker", type=f"NUMPAD_{i}", value="PRESS") + kmi.properties.index = i - 1 + keymaps.append((km, kmi)) + + kmi = km.keymap_items.new("rigpicker.call_operator", type="NUMPAD_PERIOD", value="PRESS") + kmi.properties.operator = "bpy.ops.view3d.view_selected()" + kmi.properties.view_3d = True keymaps.append((km, kmi)) - + kmi = km.keymap_items.new("rigpicker.call_operator", type="A", value="PRESS") kmi.properties.operator = "bpy.ops.pose.select_all(action='SELECT')" keymaps.append((km, kmi)) @@ -581,6 +545,8 @@ def register_keymaps(): 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") kmi.properties.mode = 'SET' keymaps.append((km, kmi)) @@ -592,9 +558,14 @@ def register_keymaps(): #kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS", ctrl=True) kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS", alt=True) kmi.properties.mode = 'SUBSTRACT' - keymaps.append((km, kmi)) + #km = wm.keyconfigs.addon.keymaps.new(name="View2D") + kmi = km.keymap_items.new("rigpicker.call_operator", type="MIDDLEMOUSE", value="PRESS") + kmi.properties.operator = "bpy.ops.view2d.pan('INVOKE_DEFAULT')" + keymaps.append((km, kmi)) + + def unregister_keymaps(): for km, kmi in keymaps: km.keymap_items.remove(kmi) @@ -609,7 +580,10 @@ classes = ( RP_MT_context_menu, RP_OT_picker_transform, RP_OT_context_menu_picker, - RP_OT_pack_picker + RP_OT_pack_picker, + RP_OT_add_picker_source, + RP_OT_remove_picker_source, + RP_OT_fit_picker #RP_OT_ui_draw ) diff --git a/operators/shape.py b/operators/shape.py index b9fa8ef..cafb196 100644 --- a/operators/shape.py +++ b/operators/shape.py @@ -1,13 +1,16 @@ -import bpy -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 +from pathlib import Path +from mathutils import Matrix +import bpy +from bpy.types import Operator + +from ..core.shape import get_picker_data +from ..core.addon_utils import is_shape +from ..core.bl_utils import link_mat_to_object, flip_name -class RP_OT_create_shape(bpy.types.Operator): +class RP_OT_create_shape(Operator): bl_label = 'Create UI shape' bl_idname = 'rigpicker.create_shape' #bl_options = {'REGISTER', 'UNDO'} @@ -52,7 +55,7 @@ class RP_OT_create_shape(bpy.types.Operator): return {'FINISHED'} -class RP_OT_name_from_bone(bpy.types.Operator): +class RP_OT_name_from_bone(Operator): bl_label = 'Name Shape from selected bones' bl_idname = 'rigpicker.name_from_bone' #bl_options = {'REGISTER', 'UNDO'} @@ -69,67 +72,67 @@ class RP_OT_name_from_bone(bpy.types.Operator): return {'FINISHED'} -class RP_OT_mirror_shape(bpy.types.Operator): +class RP_OT_mirror_shape(Operator): bl_label = 'Mirror UI shape' bl_idname = 'rigpicker.mirror_shape' #bl_options = {'REGISTER', 'UNDO'} + @classmethod + def poll(cls, context): + return (context.object and context.object.type in ('MESH', 'CURVE', 'TEXT')) + def execute(self,context): scn = context.scene + ob = context.object + collection = next((c for c in ob.users_collection if c.rig_picker.enabled), None) + objects = context.selected_objects - for ob in bpy.context.selected_objects: + # Remove mirror object: + for ob in list(collection.all_objects): + if ob in objects: + continue - name = find_mirror(ob.name) - link_mat_to_object(ob) + for mod in ob.modifiers: + if (mod.type == 'NODES' and mod.node_group.name == 'Symmetrize' and + mod.get('Socket_2') in objects): + + bpy.data.objects.remove(ob) - if not name: - name = ob.name + '_flip' + if collection.rig_picker.symmetry: + x_axis = collection.rig_picker.symmetry.matrix_world.to_translation()[0] + else: + x_axis = 0 - old_shape = bpy.data.objects.get(name) - old_mat = None - if old_shape: - old_mat = next((sl.material for sl in old_shape.material_slots if sl.material), None) - bpy.data.objects.remove(old_shape) + for ob in objects: + flipped_name = flip_name(ob.name) + if flipped_name == ob.name: + suffix = '.L' + flipped_suffix = '.R' + # Determine side of the object + if ob.matrix_world.to_translation()[0] < x_axis: + suffix, flipped_suffix = flipped_suffix, suffix + + flipped_name = f'{ob.name}{flipped_suffix}' + ob.name = f'{ob.name}{suffix}' - mirror_ob = ob.copy() - mirror_ob.data = ob.data - mirror_ob.name = name + flipped_object = ob.copy() + flipped_object.name = flipped_name + flipped_object.parent = None + flipped_object.matrix_world = Matrix() + for mod in list(flipped_object.modifiers): + flipped_object.modifiers.remove(mod) - if old_mat: - #mirror_ob.data.materials.clear() - #mirror_ob.data.materials.append(None) - #mirror_ob.material_slots[0].link = 'OBJECT' + # Add symmetrize modifier + mod = flipped_object.modifiers.new(name='Symmetrize', type='NODES') + mod.node_group = bpy.data.node_groups['Symmetrize'] + mod['Socket_2'] = ob + mod['Socket_3'] = collection.rig_picker.symmetry - mirror_ob.material_slots[0].material = old_mat - #mirror_ob = bpy.data.objects.new(name,ob.data.copy()) - - for c in ob.users_collection: - c.objects.link(mirror_ob) - - if scn.rig_picker.symmetry: - symmetry_loc = scn.rig_picker.symmetry.matrix_world.to_translation()[0] - else: - symmetry_loc = 0 - - #print(symmetry_loc) - mirror_ob.matrix_world = ob.matrix_world - - #if mirror_ob.location[0] < symmetry_loc: - mirror_ob.location.x = symmetry_loc + (symmetry_loc - ob.location.x) - #else: - # mirror_ob.location[0]= symmetry_loc+ (symmetry_loc- ob.location[0]) - - mirror_ob.rotation_euler.y = -ob.rotation_euler.y - mirror_ob.rotation_euler.z = -ob.rotation_euler.z - - mirror_ob.scale.x = -ob.scale.x - - for key, value in ob.items(): - if key not in ['_RNA_UI','cycles']: - mirror_ob[key] = value + for col in ob.users_collection: + col.objects.link(flipped_object) if ob.rig_picker.shape_type == 'BONE': - mirror_ob.rig_picker.name = find_mirror(ob.rig_picker.name) + flipped_object.rig_picker.name = flip_name(ob.rig_picker.name) elif ob.rig_picker.shape_type == 'FUNCTION': args = {} @@ -137,18 +140,18 @@ class RP_OT_mirror_shape(bpy.types.Operator): if type(value) == list: mirrored_value = [] for item in value: - mirrored_value.append(find_mirror(item)) + mirrored_value.append(flip_name(item)) elif type(value) == str: - mirrored_value = find_mirror(value) + mirrored_value = flip_name(value) args[key] = mirrored_value - mirror_ob.rig_picker.arguments = str(args) + flipped_object.rig_picker.arguments = str(args) return {'FINISHED'} -class RP_OT_select_shape_type(bpy.types.Operator): +class RP_OT_select_shape_type(Operator): bl_label = 'Select Shape by Type' bl_idname = 'rigpicker.select_shape_type' #bl_options = {'REGISTER', 'UNDO'} @@ -177,15 +180,18 @@ class RP_OT_select_shape_type(bpy.types.Operator): return wm.invoke_props_dialog(self, width=150) -class RP_OT_save_picker(bpy.types.Operator): +class RP_OT_save_picker(Operator): bl_label = 'Store UI Data' bl_idname = 'rigpicker.save_picker' def execute(self, context): scn = context.scene - canvas = scn.rig_picker.canvas - rig = scn.rig_picker.rig - shapes = [o for o in scn.objects if o != canvas and is_shape(o)] + ob = context.object + collection = next((c for c in ob.users_collection if c.rig_picker.enabled), None) + + canvas = collection.rig_picker.canvas + rig = collection.rig_picker.rig + shapes = [o for o in collection.all_objects if o != canvas and o.type in ('MESH', 'CURVE', 'FONT')] if not rig: self.report({'ERROR'}, 'Choose a Rig') @@ -196,9 +202,9 @@ class RP_OT_save_picker(bpy.types.Operator): return {'CANCELLED'} - data = get_picker_datas(shapes, canvas, rig) + data = get_picker_data(shapes, canvas, rig) - picker_path = Path(bpy.path.abspath(scn.rig_picker.destination)) + picker_path = Path(bpy.path.abspath(collection.rig_picker.destination)) picker_path.write_text(json.dumps(data)) bpy.ops.rigpicker.reload_picker() diff --git a/properties.py b/properties.py index 21efaf5..66caa77 100644 --- a/properties.py +++ b/properties.py @@ -1,5 +1,7 @@ import bpy -from bpy.props import EnumProperty, StringProperty, PointerProperty, BoolProperty +from bpy.props import (EnumProperty, StringProperty, PointerProperty, BoolProperty, + CollectionProperty, IntProperty) + import inspect ''' @@ -22,9 +24,15 @@ def bones_item(self,context): items.append((bone.name,bone.name,'')) return items + +class RP_PG_picker_source(bpy.types.PropertyGroup): + source : StringProperty(subtype='FILE_PATH') + + class RP_PG_armature_ui_settings(bpy.types.PropertyGroup): name: StringProperty() source: StringProperty(subtype='FILE_PATH') + sources: CollectionProperty(type=RP_PG_picker_source) class RP_PG_object_ui_settings(bpy.types.PropertyGroup): @@ -37,6 +45,7 @@ class RP_PG_object_ui_settings(bpy.types.PropertyGroup): class RP_PG_scene_ui_settings(bpy.types.PropertyGroup): + enabled : BoolProperty(default=False) rig: PointerProperty(type=bpy.types.Object) canvas: PointerProperty(type=bpy.types.Object) symmetry: PointerProperty(type=bpy.types.Object) @@ -67,6 +76,7 @@ class RP_OT_operator_selector(bpy.types.Operator): classes = ( + RP_PG_picker_source, RP_PG_object_ui_settings, RP_PG_scene_ui_settings, RP_PG_armature_ui_settings, @@ -80,6 +90,7 @@ def register(): bpy.types.Armature.rig_picker = bpy.props.PointerProperty(type=RP_PG_armature_ui_settings) bpy.types.Object.rig_picker = bpy.props.PointerProperty(type=RP_PG_object_ui_settings) bpy.types.Scene.rig_picker = bpy.props.PointerProperty(type=RP_PG_scene_ui_settings) + bpy.types.Collection.rig_picker = bpy.props.PointerProperty(type=RP_PG_scene_ui_settings) def unregister(): del bpy.types.Scene.rig_picker diff --git a/resources/KohinoorZerone-Zero.otf b/resources/KohinoorZerone-Zero.otf new file mode 100644 index 0000000000000000000000000000000000000000..594e3b1ccd0a4ba24605d26c3747e1a0f3f2a635 GIT binary patch literal 21064 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 BE&l)j literal 0 HcmV?d00001 diff --git a/ui.py b/ui.py new file mode 100644 index 0000000..8215ab5 --- /dev/null +++ b/ui.py @@ -0,0 +1,142 @@ +import bpy +from bpy.types import UIList +#import collections +#import inspect +from .core.addon_utils import get_operator_from_id +from .core.bl_utils import get_mat + +import re + + +# class RP_UL_picker_source(UIList): +# def draw_item(self, context, layout, data, item, _icon, _active_data, _active_propname, _index): +# ob = context.object +# row = layout.row(align=True) +# is_packed = ('picker' in ob.data.rig_picker.keys()) +# sub_row = row.row(align=True) +# sub_row.prop(item, 'source', text='') +# sub_row.enabled = not is_packed +# #row.operator('rigpicker.pack_picker', icon='PACKAGE' if is_packed else 'UGLYPACKAGE', text='') + + +class RP_PT_picker_maker_panel(bpy.types.Panel): + bl_label = 'Rig Picker' + bl_category = 'Rigging' + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + + @classmethod + def poll(cls, context): + return context.object + + def draw(self, context): + ob = context.object + scn = context.scene + collection = next((c for c in ob.users_collection if c.rig_picker.enabled), None) + + layout = self.layout + col = layout.column(align=False) + + if collection: + col.prop_search(collection.rig_picker, 'rig', scn, 'objects', text='Rig ') + col.prop_search(collection.rig_picker, 'canvas', scn, 'objects', text='Canvas ') + col.prop_search(collection.rig_picker, 'symmetry', scn, 'objects', text='Symmetry ') + + if ob.type == 'ARMATURE': + box = col.box() + box.enabled = not ob.data.library + col = box.column(align=False) + + row = col.row(align=True) + row.separator(factor=0.5) + row.label(text="Sources") + + is_packed = ('picker' in ob.data.rig_picker.keys()) + row.operator('rigpicker.pack_picker', icon='PACKAGE' if is_packed else 'UGLYPACKAGE', text='', emboss=False) + row.operator("rigpicker.add_picker_source", icon ='ADD', text="", emboss=False) + + for i, item in enumerate(ob.data.rig_picker.sources): + row = col.row(align=True) + is_packed = ('pickers' in ob.data.rig_picker.keys()) + row.prop(item, 'source', text='') + row.operator("rigpicker.remove_picker_source", icon ='PANEL_CLOSE', text="", emboss=False).index=i + + if collection: + #if context.collection and context.collection.rig_picker.enabled: + col = layout.column(align=True) + row = col.row(align=True) + row.operator('rigpicker.create_shape', icon='MESH_DATA', text='Create shape') + row.operator('rigpicker.mirror_shape', icon='MOD_MIRROR', text='Mirror shape') + col.operator('rigpicker.name_from_bone', icon='SORTALPHA' , text='Name from bones') + col.prop(scn.rig_picker, 'use_pick_bone', icon='EYEDROPPER', text='Auto bone assign') + + if ob.type != 'ARMATURE': + box = layout.box() + col = box.column(align=False) + + material_row = col.row(align=True) + material_row.operator('rigpicker.remove_mat', icon='REMOVE', text='') + material_row.operator('rigpicker.add_mat', icon='ADD', text='') + mat = False + if ob.type in ('MESH', 'CURVE', 'FONT') and ob.data.materials: + mat = get_mat(ob) + if mat and mat.node_tree: + emission_nodes = [n for n in mat.node_tree.nodes if n.type =='EMISSION'] + if emission_nodes: + material_row.prop(emission_nodes[0].inputs[0], 'default_value', text='') + mat = True + if not mat: + material_row.label(text='No Material') + material_row.operator('rigpicker.eyedropper_mat', icon='EYEDROPPER', text='') + + shape_type_row = col.row(align=True) + shape_type_row.prop(ob.rig_picker,'shape_type',expand = True) + shape_type_row.operator('rigpicker.select_shape_type', text='', icon='RESTRICT_SELECT_OFF') + + + if ob.rig_picker.shape_type == 'OPERATOR': + + op_row = col.row(align=True) + op_row.prop(ob.rig_picker, 'operator', text='') + op_row.operator('rigpicker.operator_selector', text='', icon='COLLAPSEMENU') + + if ob.rig_picker.operator: + col.prop(ob.rig_picker, 'name', text='Tooltip') + col.prop(ob.rig_picker,'shortcut', text='Shortcut') + + else: + col.prop(ob.rig_picker, 'name', text='Tooltip') + + ''' + if ob.rig_picker.operator: + op = get_operator_from_id(ob.rig_picker.idname) + if op: + doc = re.findall(r'\(([^\)]+)\)', op.__doc__)[0] + else: + doc = 'Operator not found' + + col.prop(ob.rig_picker,'name', text='Tooltip') + if op: + col.prop(ob.rig_picker,'shortcut', text='Shortcut') + col.prop(ob.rig_picker,'arguments', text='') + col.label(text=doc) + else: + col.label(text=doc) + ''' + + + if ob.rig_picker.shape_type == 'BONE': + if collection and collection.rig_picker.rig: + col.prop_search(ob.rig_picker, 'name', collection.rig_picker.rig.pose, 'bones', text='Bone') + + #layout.separator() + layout.prop(collection.rig_picker, 'destination', text='Filepath') + layout.operator('rigpicker.save_picker', icon='PASTEDOWN', text='Save Picker') + + +classes = ( + #RP_UL_picker_source, + RP_PT_picker_maker_panel, +) + +register, unregister = bpy.utils.register_classes_factory(classes) \ No newline at end of file