Picker Group

Gizmo
ChristopheSeux 2024-02-19 11:53:15 +01:00
parent b855e8ac37
commit c31a73f80f
12 changed files with 690 additions and 418 deletions

View File

@ -15,7 +15,7 @@ import importlib
modules = (
'.operators',
'.properties',
'.panels',
'.ui',
'.area',
'.gizmo',
'.draw_handlers'

39
area.py
View File

@ -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,14 +14,11 @@ class RigPickerTree(NodeTree):
bl_icon = 'OUTLINER_DATA_ARMATURE'
def draw_header(self, context):
if context.space_data.tree_type == 'RigPickerTree':
if not context.space_data.tree_type == 'RigPickerTree':
self._draw(context)
return
layout = self.layout
layout.template_header()
#layout.separator_spacer()
@ -35,14 +33,33 @@ def draw_header(self, context):
#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.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='')
layout.separator_spacer()
else:
self._draw(context)
def tools_from_context(context, mode=None):
sp = context.space_data

View File

@ -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):
""" """

View File

@ -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)):

View File

@ -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.tooltip_mouse = self.mouse
self.batch.draw(self.shader)
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():

View File

@ -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)

View File

@ -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
continue
print('Load picker from', picker_path.resolve())
picker_datas = json.loads(picker_path.read_text())
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']]
PICKERS[ob] = Picker(ob, shapes=picker_datas)
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

View File

@ -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):
@ -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):
if not self.operator.startswith('bpy.ops'):
print("Operator call_operator can only execute operator")
return {"FINISHED"}
print('CALL OPERATOR', self.operator)
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.region.tag_redraw()
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,8 +509,14 @@ 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")
@ -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
)

View File

@ -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):
if not name:
name = ob.name + '_flip'
bpy.data.objects.remove(ob)
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)
mirror_ob = ob.copy()
mirror_ob.data = ob.data
mirror_ob.name = name
if old_mat:
#mirror_ob.data.materials.clear()
#mirror_ob.data.materials.append(None)
#mirror_ob.material_slots[0].link = 'OBJECT'
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]
if collection.rig_picker.symmetry:
x_axis = collection.rig_picker.symmetry.matrix_world.to_translation()[0]
else:
symmetry_loc = 0
x_axis = 0
#print(symmetry_loc)
mirror_ob.matrix_world = ob.matrix_world
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
#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])
flipped_name = f'{ob.name}{flipped_suffix}'
ob.name = f'{ob.name}{suffix}'
mirror_ob.rotation_euler.y = -ob.rotation_euler.y
mirror_ob.rotation_euler.z = -ob.rotation_euler.z
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)
mirror_ob.scale.x = -ob.scale.x
# 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
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()

View File

@ -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

Binary file not shown.

142
ui.py Normal file
View File

@ -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)