Add menu and some cleanup

Gizmo
ChristopheSeux 2024-02-26 11:26:49 +01:00
parent 917531f59a
commit 150bae5920
13 changed files with 778 additions and 571 deletions

87
area.py
View File

@ -1,5 +1,5 @@
import bpy import bpy
from bpy.types import NodeTree, NODE_PT_tools_active, NODE_HT_header from bpy.types import NodeTree, NODE_PT_tools_active, NODE_HT_header, Menu
from .constants import PICKERS from .constants import PICKERS
@ -14,6 +14,69 @@ class RigPickerTree(NodeTree):
bl_icon = 'OUTLINER_DATA_ARMATURE' bl_icon = 'OUTLINER_DATA_ARMATURE'
class RP_MT_picker(Menu):
"""Picker"""
bl_label = "Picker"
def draw(self, context):
layout = self.layout
scn = context.scene
layout.operator('rigpicker.reload_picker', icon='FILE_REFRESH')
row = layout.row(align=True)
# Has at least one picker collection in the scene
if not [c.rig_picker.enabled for c in scn.collection.children_recursive]:
row.enabled = False
row.prop(scn.rig_picker, 'use_pick_bone', text='Auto Bone Assign')
class RP_MT_animation(Menu):
"""Picker"""
bl_label = "Animation"
def draw(self, context):
layout = self.layout
layout.operator("rigpicker.toogle_bone_layer", icon="MOUSE_LMB_DRAG")
row = layout.row()
op = row.operator("node.context_menu_picker", icon="MOUSE_RMB")
if not context.active_pose_bone:
row.enabled = False
layout.separator()
op = layout.operator("rigpicker.call_operator", text='Select All')
op.operator = "pose.select_all"
op.arguments = '{"action": "SELECT"}'
op = layout.operator("rigpicker.call_operator", text='Select All')
op.operator = "pose.select_all"
op.arguments = '{"action": "DESELECT"}'
op = layout.operator("rigpicker.call_operator", text='Frame Selected')
op.operator = "view3d.view_selected"
op.view_3d = True
layout.separator()
layout.operator("rigpicker.call_operator", text='Insert Keyframe').operator="animtoolbox.insert_keyframe"
layout.operator("anim.keyframe_delete_v3d", text='Delete Keyframe')
layout.separator()
op = layout.operator("rigpicker.call_operator", text='Move')
op.operator="transform.translate"
op.invoke = True
op.view_3d = True
layout.operator("node.picker_transform", text='Rotate').mode='ROTATE'
layout.operator("node.picker_transform", text='Scale').mode='SCALE'
layout.separator()
layout.operator("rigpicker.call_operator", text='Reset Bone').operator="animtoolbox.reset_bone"
layout.operator("rigpicker.call_operator", text='Clear Location').operator='pose.loc_clear'
layout.operator("rigpicker.call_operator", text='Clear Rotation').operator='pose.rot_clear'
layout.operator("rigpicker.call_operator", text='Clear Scale').operator='pose.scale_clear'
def draw_header(self, context): def draw_header(self, context):
if not context.space_data.tree_type == 'RigPickerTree': if not context.space_data.tree_type == 'RigPickerTree':
self._draw(context) self._draw(context)
@ -23,7 +86,6 @@ def draw_header(self, context):
layout = self.layout layout = self.layout
layout.template_header() layout.template_header()
#layout.separator_spacer()
if not context.space_data.node_tree: if not context.space_data.node_tree:
ntree = bpy.data.node_groups.get('.rig_picker') ntree = bpy.data.node_groups.get('.rig_picker')
@ -32,10 +94,10 @@ def draw_header(self, context):
context.space_data.node_tree = ntree context.space_data.node_tree = ntree
#layout.template_ID(context.space_data, "node_tree", new="node.new_node_tree") row = layout.row(align=True)
#layout.separator_spacer() row.menu("RP_MT_picker")
row.menu("RP_MT_animation")
#layout.operator('rigpicker.reload_picker', icon='FILE_REFRESH', text='')
ob = context.object ob = context.object
if not ob or not ob.type == 'ARMATURE': if not ob or not ob.type == 'ARMATURE':
return return
@ -50,22 +112,17 @@ def draw_header(self, context):
layout.prop(scn.rig_picker, 'use_pick_bone', icon='PANEL_CLOSE', text='', emboss=False) layout.prop(scn.rig_picker, 'use_pick_bone', icon='PANEL_CLOSE', text='', emboss=False)
layout.separator_spacer() layout.separator_spacer()
layout.label(text=ob.name) row = layout.row()
row.enabled = False
row.label(text=ob.name)
layout.separator_spacer() layout.separator_spacer()
#row.alignment = 'RIGHT'
row = layout.row(align=True) row = layout.row(align=True)
for i, picker in enumerate(picker_group.pickers): for i, picker in enumerate(picker_group.pickers):
row.operator('rigpicker.fit_picker', text=f'{i+1}').index=i 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 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): def tools_from_context(context, mode=None):
@ -92,6 +149,8 @@ def poll(cls, context):
classes = ( classes = (
RigPickerTree, RigPickerTree,
RP_MT_picker,
RP_MT_animation
) )

View File

@ -1,2 +1,29 @@
import gpu
from pathlib import Path
PICKERS = {} class LazyDict(dict):
def __getitem__(self, k):
v = super().__getitem__(k)
if callable(v):
v = v()
super().__setitem__(k, v)
return v
def get(self, k, default=None):
if k in self:
return self.__getitem__(k)
return default
PICKERS = {}
MODULE_DIR = Path(__file__).parent
SHADER_DIR = MODULE_DIR / 'shaders'
SHADERS = LazyDict()
vertex_shader = Path(SHADER_DIR, "dash_shader.vert").read_text(encoding='utf-8')
fragment_shader = Path(SHADER_DIR, "dash_shader.frag").read_text(encoding='utf-8')
SHADERS['dashed_line'] = lambda : gpu.types.GPUShader(vertex_shader, fragment_shader)

View File

@ -1,6 +1,21 @@
import bpy import bpy
from .bl_utils import get_mat from .bl_utils import get_mat, get_collection_parents
def get_picker_collection(ob=None):
"""Return the picker collection of an object"""
if not ob:
ob = bpy.context.object
for col in ob.users_collection:
if col.rig_picker.enabled:
return col
if picker_col := next((c for c in get_collection_parents(col) if c.rig_picker.enabled), None):
return picker_col
def is_shape(ob): def is_shape(ob):
scn = bpy.context.scene scn = bpy.context.scene
@ -15,6 +30,7 @@ def is_shape(ob):
return False return False
def get_object_color(ob): def get_object_color(ob):
if not ob.data.materials: if not ob.data.materials:
return return
@ -29,6 +45,7 @@ def get_object_color(ob):
return emit_node.inputs['Color'].default_value return emit_node.inputs['Color'].default_value
def get_operator_from_id(idname): def get_operator_from_id(idname):
if not '.' in idname: if not '.' in idname:
return return

View File

@ -2,6 +2,34 @@
import bpy import bpy
def get_collection_parents(col, root=None, cols=None):
"""Return all direct collection parents
Args:
col (bpy.types.Collection): collection to get parents from
root (bpy.types.Collection, optional): collection to search in (recursive) instead of the scene collection.
Defaults to None.
cols (_type_, optional): for recursivity, store the parent collections.
Defaults to None.
Returns:
list[bpy.types.Collection]: a list of direct parents
"""
if cols is None:
cols = []
if root is None:
root = bpy.context.scene.collection
for sub in root.children:
if sub == col:
cols.append(root)
if len(sub.children):
cols = get_collection_parents(col, root=sub, cols=cols)
return cols
def get_view_3d_override(): def get_view_3d_override():
windows = bpy.context.window_manager.windows windows = bpy.context.window_manager.windows
areas = [a for w in windows for a in w.screen.areas if a.type == 'VIEW_3D'] areas = [a for w in windows for a in w.screen.areas if a.type == 'VIEW_3D']
@ -31,6 +59,12 @@ def link_mat_to_object(ob):
sl.link = 'OBJECT' sl.link = 'OBJECT'
sl.material = m sl.material = m
def eval_attr(ob, name):
resolved = ob
for o in name.split("."):
resolved = getattr(resolved, o)
return resolved
def flip_name(name): def flip_name(name):
if not name: if not name:
return return
@ -41,6 +75,19 @@ def flip_name(name):
else: else:
return bpy.utils.flip_name(name) return bpy.utils.flip_name(name)
def split_path(path) :
try :
bone_name = path.split('["')[1].split('"]')[0]
except Exception:
bone_name = None
try :
prop_name = path.split('["')[2].split('"]')[0]
except Exception:
prop_name = None
return bone_name, prop_name
def hide_layers(args): def hide_layers(args):
""" """ """ """
ob = bpy.context.object ob = bpy.context.object

View File

@ -232,7 +232,7 @@ class BoneShape(Shape):
if self.hide: if self.hide:
return self.draw_hided() return self.draw_hided()
elif self.bone.custom_shape_scale_xyz.length < 0.00001: elif self.bone and self.bone.custom_shape_scale_xyz.length < 0.00001:
return self.draw_zero_scaled() return self.draw_zero_scaled()
# Draw Fill # Draw Fill
@ -258,8 +258,9 @@ class BoneShape(Shape):
def assign_bone_event(self): def assign_bone_event(self):
#print('assign_bone_event', self) #print('assign_bone_event', self)
scn = bpy.context.scene scn = bpy.context.scene
rig = scn.rig_picker.rig rig = self.picker.rig
source_object = scn.objects.get(self.source_name) source_object = scn.objects.get(self.source_name)
if not source_object: if not source_object:
print(f'Source object {self.source_name} not found') print(f'Source object {self.source_name} not found')
@ -270,6 +271,9 @@ class BoneShape(Shape):
print('You need to have an active bone') print('You need to have an active bone')
return return
#print(active_bone, source_object)
#print(rig)
source_object.name = rig.data.bones.active.name
source_object.rig_picker.name = rig.data.bones.active.name source_object.rig_picker.name = rig.data.bones.active.name
def release_event(self, mode='SET'): def release_event(self, mode='SET'):
@ -452,10 +456,10 @@ class Picker:
def assign_bone_event(self): def assign_bone_event(self):
for shape in self.shapes: for shape in self.shapes:
if shape.type=='bone' and shape.hover: if shape.type == 'bone' and shape.hover:
shape.assign_bone_event() shape.assign_bone_event()
bpy.ops.rigpicker.save_picker(index=self.index) bpy.ops._rigpicker.save_picker(index=self.index)
def press_event(self, mode='SET'): def press_event(self, mode='SET'):
for shape in self.shapes: for shape in self.shapes:
@ -626,11 +630,11 @@ class PickerGroup:
if self.timer: if self.timer:
self.timer.cancel() self.timer.cancel()
self.timer = threading.Timer(0.5, self.tooltip_event) self.timer = threading.Timer(0.4, self.tooltip_event)
self.timer.start() self.timer.start()
def press_event(self, mode='SET'): def press_event(self, mode='SET'):
self.clear_tooltip() #self.clear_tooltip()
for picker in self.pickers: for picker in self.pickers:
if picker.under_mouse(self.location): if picker.under_mouse(self.location):
@ -678,7 +682,7 @@ class PickerGroup:
#print(self.hover_shape, self.hover_shape.type) #print(self.hover_shape, self.hover_shape.type)
if self.hover_shape and self.hover_shape.type != 'display': if self.hover_shape and self.hover_shape.type != 'display':
if self.hover_shape.type == 'bone': if self.hover_shape.type == 'bone' and self.hover_shape.bone:
self.tooltip = self.hover_shape.bone.name self.tooltip = self.hover_shape.bone.name
else: else:
self.tooltip = self.hover_shape.tooltip self.tooltip = self.hover_shape.tooltip
@ -706,28 +710,47 @@ class PickerGroup:
return center return center
def get_picker_path(rig, start=None): def load_picker_data(rig):
picker_path = rig.data.get('rig_picker', {}).get('source') if 'pickers' in rig.data.rig_picker:
if not picker_path: picker_datas = [p.to_dict() for p in rig.rig_picker['pickers']]
return else:
picker_datas = []
picker_path = bpy.path.abspath(picker_path, library=rig.data.library, start=start) for picker in rig.data.rig_picker.sources:
picker_path = Path(bpy.path.abspath(picker.source, library=rig.data.library))
if not picker_path.exists():
print(f'Picker path not exists: {picker_path.resolve()}')
continue
print('Load picker from', picker_path.resolve())
picker_data = json.loads(picker_path.read_text(encoding='utf-8'))
picker_datas.append(picker_data)
PICKERS[rig] = PickerGroup(rig, picker_datas)
def get_picker_path(rig, source, start=None):
picker_path = bpy.path.abspath(source, library=rig.data.library, start=start)
return Path(os.path.abspath(picker_path)) return Path(os.path.abspath(picker_path))
def pack_picker(rig, start=None): def pack_picker(rig, start=None):
picker_path = get_picker_path(rig, start=start) pickers = []
if picker_path and picker_path.exists(): for picker_source in rig.data.get('rig_picker', {}).get('sources', {}):
if 'rig_picker' not in rig.data.keys(): picker_path = get_picker_path(rig, picker_source['source'], start)
rig.data['rig_picker'] = {} if not picker_path.exists():
print(f'{picker_path} not exists')
continue
picker_data = json.loads(picker_path.read_text(encoding='utf-8'))
pickers.append(picker_data)
rig.data['rig_picker']['picker'] = json.loads(picker_path.read_text(encoding='utf-8')) rig.data['rig_picker']['pickers'] = pickers
def unpack_picker(rig): def unpack_picker(rig):
if 'rig_picker' not in rig.data.keys(): if 'rig_picker' not in rig.data.keys():
return return
if 'picker' in rig.data['rig_picker'].keys(): if 'pickers' in rig.data['rig_picker'].keys():
del rig.data['rig_picker']['picker'] del rig.data['rig_picker']['pickers']

View File

@ -61,99 +61,119 @@ def contour_loops(bm, vert_index=0, loops=None, vert_indices=None):
return loops return loops
def get_picker_data(objects, canvas, rig):
picker_datas = []
gamma = 1 / 2.2
if canvas.type =='CURVE': def get_shape_data(ob, matrix=None, depsgraph=None):
if not matrix:
matrix = Matrix()
if not depsgraph:
depsgraph = bpy.context.evaluated_depsgraph_get()
gamma = 1 / 2.2
eval_ob = ob.evaluated_get(depsgraph)
mesh = eval_ob.to_mesh()
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.002)
bmesh.ops.dissolve_limit(bm, angle_limit=0.001745, verts=bm.verts, edges=bm.edges)
bmesh.ops.connect_verts_concave(bm, faces=bm.faces)
bmesh.ops.triangulate(bm, faces=bm.faces)
edges = [[v.index for v in e.verts] for e in bm.edges if len(e.link_faces)<=1]
bm.to_mesh(mesh)
mesh.update()
bm.clear()
points = []
polygons = []
depths = []
for vert in mesh.vertices:
co = matrix @ (ob.matrix_world @ Vector(vert.co))
points.append([round(co[0]), round(co[1])])
depths += [co[2]]
if depths:
depth = max(depths)
else:
print(f'{ob.name} has no vertices')
depth = 0
for face in mesh.polygons:
polygons.append([v for v in face.vertices])
color = get_object_color(ob)
if color:
color = [round(pow(c, gamma), 4) for c in color]
else:
color = [0.5, 0.5, 0.5]
shape = {
'source_name': ob.name,
'tooltip': ob.rig_picker.name,
'depth': depth,
'points': points,
'polygons': polygons,
'edges': edges,
'color': color,
'type': ob.rig_picker.shape_type
}
if shape['type'] =='OPERATOR':
shape['operator'] = ob.rig_picker.operator
shape['shortcut'] = ob.rig_picker.shortcut
elif shape['type'] =='BONE':
shape['bone'] = ob.rig_picker.name
eval_ob.to_mesh_clear()
return shape
def get_picker_data(collection):
picker_data = []
depsgraph = bpy.context.evaluated_depsgraph_get()
canvas = collection.rig_picker.canvas
if canvas.type == 'CURVE':
canvas_points = canvas.data.splines[0].points canvas_points = canvas.data.splines[0].points
else: else:
canvas_points = canvas.data.vertices canvas_points = canvas.data.vertices
canvas_coords = [canvas.matrix_world@Vector((p.co)) for p in canvas_points] canvas_coords = [canvas.matrix_world@Vector((p.co)) for p in canvas_points]
height = max(canvas_coords).y - min(canvas_coords).y height = abs(max(canvas_coords).y - min(canvas_coords).y)
width = max(canvas_coords).x - min(canvas_coords).x width = abs(max(canvas_coords).x - min(canvas_coords).x)
canvas_center = sum(canvas_coords, Vector()) / len(canvas_coords) center = sum(canvas_coords, Vector()) / len(canvas_coords)
canvas_scale_fac = 4096 / max(height, width)# Reference height for the canvas scale = 2048 / max(height, width)# Reference height for the canvas
objects.append(canvas) matrix = Matrix.Translation(-center)
matrix = Matrix.Scale(scale, 4) @ matrix
dg = bpy.context.evaluated_depsgraph_get()
#sorted by their z axes #sorted by their z axes
for ob in sorted(objects, key=lambda x: bound_box_center(x)[2]): for ob in collection.all_objects:
if ob.instance_collection:
for shape in ob.instance_collection.all_objects:
picker_data.append(get_shape_data(shape, matrix=matrix@ob.matrix_world, depsgraph=depsgraph))
elif ob.type in ('MESH', 'CURVE', 'FONT'):
picker_data.append(get_shape_data(ob, matrix=matrix, depsgraph=depsgraph))
print('Storing shape', ob.name)
mesh = bpy.data.meshes.new_from_object(ob.evaluated_get(dg))
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.002)
bmesh.ops.dissolve_limit(bm, angle_limit=0.001745, verts=bm.verts, edges=bm.edges)
bmesh.ops.connect_verts_concave(bm, faces=bm.faces)
bmesh.ops.triangulate(bm, faces=bm.faces)
#bm_loops = contour_loops(bm)
#loops = [[l.index for l in loop] for loop in bm_loops]
loops = []
edges = [[v.index for v in e.verts] for e in bm.edges if len(e.link_faces)<=1]
bm.to_mesh(mesh)
mesh.update()
bm.clear()
points = []
#edges = []
polygons = []
for p in mesh.vertices:
point = ob.matrix_world@Vector((p.co))
point = (point - canvas_center) * canvas_scale_fac
points.append([round(point[0]), round(point[1])])
for f in mesh.polygons:
polygons.append([v for v in f.vertices])
#for e in mesh.edges:
#
# edges.append([v for v in e.vertices])
color = get_object_color(ob)
if color:
color = [round(pow(c, gamma), 4) for c in color]
else: else:
color = [0.5, 0.5, 0.5] #print(f'{ob.name} of type {ob.type} not supported')
continue
shape = {
'source_name': ob.name,
'tooltip': ob.rig_picker.name,
'points': points,
'polygons': polygons,
'edges': edges,
'loops': loops,
'color': color,
'type': 'CANVAS' if ob == canvas else ob.rig_picker.shape_type
}
if shape['type'] =='OPERATOR':
shape['operator'] = ob.rig_picker.operator
#if ob.rig_picker.arguments:
#shape['arguments'] = ob.rig_picker.arguments
#if ob.rig_picker.shortcut:
shape['shortcut'] = ob.rig_picker.shortcut
elif shape['type'] =='BONE':
shape['bone'] = ob.rig_picker.name
picker_datas.append(shape)
picker_data.sort(key=lambda x : x['depth'])
#print(picker_datas) #print(picker_datas)
return picker_datas return picker_data
#rig.data.rig_picker['shapes'] = picker_datas #rig.data.rig_picker['shapes'] = picker_datas

View File

@ -5,7 +5,7 @@ from gpu_extras.batch import batch_for_shader
import blf import blf
from pathlib import Path from pathlib import Path
from .constants import PICKERS from .constants import PICKERS
from .core.picker import Picker, PickerGroup from .core.picker import Picker, PickerGroup, load_picker_data
import json import json
@ -38,55 +38,27 @@ def draw_rect_2d(position, width, height, color):
def draw_callback_view(): def draw_callback_view():
sp = bpy.context.space_data space_data = bpy.context.space_data
if not sp or not sp.tree_type == 'RigPickerTree': if not space_data or not space_data.tree_type == 'RigPickerTree':
return return
if not sp.pin: # Use the pin to know if this is the first time this picker window in created to hide the n panel
sp.pin = True if not space_data.pin:
sp.show_region_ui = False space_data.pin = True
space_data.show_region_ui = False
ob = bpy.context.object ob = bpy.context.object
if not ob or ob.type !='ARMATURE' or not ob.data.rig_picker.sources: if not ob or ob.type !='ARMATURE' or not ob.data.rig_picker.sources:
return return
if ob not in PICKERS: if ob not in PICKERS:
if 'pickers' in ob.data.rig_picker: load_picker_data(ob)
picker_datas = [s.to_dict() for s in ob.data.rig_picker['pickers']]
else:
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(): picker_group = PICKERS[ob]
print(f'Picker path not exists: {picker_path.resolve()}')
continue
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']]
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() picker_group.draw()
def draw_callback_px(): def draw_callback_px():
sp = bpy.context.space_data sp = bpy.context.space_data
if not sp.tree_type == 'RigPickerTree': if not sp.tree_type == 'RigPickerTree':
@ -100,23 +72,21 @@ def draw_callback_px():
region = bpy.context.region region = bpy.context.region
text = picker_group.tooltip text = picker_group.tooltip
mouse = picker_group.tooltip_mouse
ui_scale = bpy.context.preferences.system.ui_scale ui_scale = bpy.context.preferences.system.ui_scale
#print('Draw text', text) #print('Draw text', text)
font_id = 0 font_id = 0
blf.size(font_id, int(13 * ui_scale)) blf.size(font_id, int(13 * ui_scale))
margins = [12, 4] margins = [12, 5]
text_size = blf.dimensions(font_id, text) text_size = blf.dimensions(font_id, text)
#text_pos = (mouse[0] - text_size[0]*0.33, mouse[1] - (34*ui_scale))
text_pos = (region.width - text_size[0]-margins[0], margins[1]) text_pos = (region.width - text_size[0]-margins[0], margins[1])
bg_pos = (text_pos[0] - margins[0], text_pos[1] - margins[1]-1) bg_pos = (text_pos[0] - margins[0], text_pos[1] - margins[1]-1)
bg_size = (text_size[0] + 2*margins[0], text_size[1] + 2*margins[1]) bg_size = (text_size[0] + 2*margins[0], text_size[1] + 2*margins[1])
gpu.state.blend_set('ALPHA') gpu.state.blend_set('ALPHA')
draw_rect_2d(bg_pos, *bg_size, (0.1, 0.1, 0.1, 0.75)) draw_rect_2d(bg_pos, *bg_size, (0.1, 0.1, 0.1, 0.66))
gpu.state.blend_set('NONE') gpu.state.blend_set('NONE')
@ -126,7 +96,7 @@ def draw_callback_px():
# BLF drawing routine # BLF drawing routine
blf.position(font_id, round(text_pos[0]), round(text_pos[1]), 0) blf.position(font_id, round(text_pos[0]), round(text_pos[1]), 0)
blf.color(font_id, 1, 1, 1, 1) blf.color(font_id, 0.85, 0.85, 0.85, 1)
blf.shadow(font_id , 5, 0.0, 0.0, 0.0, 1) blf.shadow(font_id , 5, 0.0, 0.0, 0.0, 1)
blf.shadow_offset(font_id, 2, -2) blf.shadow_offset(font_id, 2, -2)

View File

@ -1,9 +1,11 @@
import bpy import bpy
from bpy.props import EnumProperty, IntProperty, BoolProperty from bpy.props import EnumProperty, IntProperty, BoolProperty, StringProperty
from ..constants import PICKERS from bpy.types import Operator, Menu
from ..core.bl_utils import get_view_3d_override from ..constants import PICKERS, SHADERS
from ..core.addon_utils import get_picker_collection
from ..core.bl_utils import get_view_3d_override, split_path, eval_attr
from ..core.geometry_utils import bounding_rect from ..core.geometry_utils import bounding_rect
from ..core.picker import get_picker_path, pack_picker, unpack_picker from ..core.picker import get_picker_path, pack_picker, unpack_picker, load_picker_data
#from .func_bgl import draw_callback_px #from .func_bgl import draw_callback_px
#from .func_bgl import select_bone #from .func_bgl import select_bone
#from core.picker import * #from core.picker import *
@ -16,95 +18,28 @@ import json
import os import os
vertex_shader = '''
layout (location = 0) in vec2 pos;
flat out vec2 startPos;
out vec2 vertPos;
uniform mat4 viewMatrix;
void main()
{
vec4 outPos = viewMatrix * vec4(pos.x, pos.y, 0.0, 1.0);
gl_Position = outPos;
vertPos = pos.xy / outPos.w;
startPos = vertPos;
}
'''
fragment_shader = '''
flat in vec2 startPos;
in vec2 vertPos;
out vec4 fragColor;
uniform vec4 color;
uniform float dashSize;
uniform float gapSize;
void main()
{
vec2 dir = (vertPos.xy - startPos.xy);
float dist = length(dir);
if (fract(dist / (dashSize + gapSize)) > dashSize/(dashSize + gapSize))
discard;
fragColor = color;
}
'''
def is_picker_space(space_data=None):
def draw_callback(self): if not space_data:
#print('draw callback border') space_data = bpy.context.space_data
if not self.draw_border: if space_data and (space_data.type == 'NODE_EDITOR' and space_data.tree_type == 'RigPickerTree'):
return
gpu.state.blend_set('ALPHA')
#print('DRAW BORDER')
self.color_shader.bind()
self.color_shader.uniform_float("color", self.bg_color)
self.bg_batch.draw(self.color_shader)
self.dash_shader.bind()
matrix = gpu.matrix.get_projection_matrix()
self.dash_shader.uniform_float("color", self.border_color)
self.dash_shader.uniform_float("viewMatrix", matrix)
self.dash_shader.uniform_float("dashSize", 5)
self.dash_shader.uniform_float("gapSize", 4)
self.contour_batch.draw(self.dash_shader)
gpu.state.blend_set('NONE')
def is_picker_space(context):
sp = context.space_data
if sp and (sp.type == 'NODE_EDITOR' and sp.tree_type == 'RigPickerTree'):
return True return True
return False return False
class RP_OT_box_select(bpy.types.Operator): class RP_OT_box_select(Operator):
"""Tooltip""" """Box Select bones in the picker view"""
bl_idname = "node.rp_box_select" bl_idname = "node.rp_box_select"
bl_label = "Picker Box Select" bl_label = "Picker Box Select"
mode: EnumProperty(items=[(i, i.title(), '') for i in ('SET', 'EXTEND', 'SUBSTRACT')]) mode: EnumProperty(items=[(i, i.title(), '') for i in ('SET', 'EXTEND', 'SUBSTRACT')])
color_shader = gpu.shader.from_builtin('UNIFORM_COLOR')
dash_shader = gpu.types.GPUShader(vertex_shader, fragment_shader)
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
if not is_picker_space(context): if not is_picker_space(context.space_data):
return return
ob = context.object ob = context.object
@ -120,6 +55,31 @@ class RP_OT_box_select(bpy.types.Operator):
return 'SET' return 'SET'
''' '''
def draw_callback(self):
#print('draw callback border')
if not self.draw_border:
return
gpu.state.blend_set('ALPHA')
#print('DRAW BORDER')
self.color_shader.bind()
self.color_shader.uniform_float("color", self.bg_color)
self.bg_batch.draw(self.color_shader)
self.dash_shader.bind()
matrix = gpu.matrix.get_projection_matrix()
self.dash_shader.uniform_float("color", self.border_color)
self.dash_shader.uniform_float("viewMatrix", matrix)
self.dash_shader.uniform_float("dashSize", 5)
self.dash_shader.uniform_float("gapSize", 4)
self.contour_batch.draw(self.dash_shader)
gpu.state.blend_set('NONE')
def invoke(self, context, event): def invoke(self, context, event):
#print(f'invoke: type={event.type}, value={event.value}, ctrl={event.ctrl}, shift={event.shift}, alt={event.alt}') #print(f'invoke: type={event.type}, value={event.value}, ctrl={event.ctrl}, shift={event.shift}, alt={event.alt}')
@ -140,7 +100,9 @@ class RP_OT_box_select(bpy.types.Operator):
self.bg_color = [1, 1, 1, 0.05] self.bg_color = [1, 1, 1, 0.05]
#args = (self, context) #args = (self, context)
self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback, (self,), 'WINDOW', 'POST_PIXEL') self.color_shader = gpu.shader.from_builtin('UNIFORM_COLOR')
self.dash_shader = SHADERS['dashed_line']
self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(self.draw_callback, (), 'WINDOW', 'POST_PIXEL')
context.window_manager.modal_handler_add(self) context.window_manager.modal_handler_add(self)
@ -163,12 +125,12 @@ class RP_OT_box_select(bpy.types.Operator):
self.region.tag_redraw() self.region.tag_redraw()
if event.value == 'RELEASE': if event.value == 'RELEASE':
return self.release_event() return self.release_event(context)
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
def release_event(self): def release_event(self, context):
scn = bpy.context.scene scn = context.scene
if scn.rig_picker.use_pick_bone: if scn.rig_picker.use_pick_bone:
self.picker.assign_bone_event() self.picker.assign_bone_event()
@ -181,173 +143,246 @@ class RP_OT_box_select(bpy.types.Operator):
bpy.ops.ed.undo_push(message="Box Select") bpy.ops.ed.undo_push(message="Box Select")
return self.exit() return self.exit(context)
def exit(self):
#print('Border Select Finished')
def exit(self, context):
bpy.types.SpaceNodeEditor.draw_handler_remove(self._handle, 'WINDOW') bpy.types.SpaceNodeEditor.draw_handler_remove(self._handle, 'WINDOW')
self.region.tag_redraw() context.region.tag_redraw()
return {'FINISHED'} return {'FINISHED'}
class RP_OT_picker_transform(Operator):
class RP_OT_picker_transform(bpy.types.Operator): """Transform Bones in the picker view"""
"""Tooltip"""
bl_idname = "node.picker_transform" bl_idname = "node.picker_transform"
bl_label = "Move Bone in Picker View" bl_label = "Transform Bone in Picker View"
mode : EnumProperty(items=[(m, m.title(), '') for m in ('TRANSLATE', 'ROTATE', 'SCALE')]) mode : EnumProperty(items=[(m, m.title(), '') for m in ('ROTATE', 'SCALE')])
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return is_picker_space(context) return context.selected_pose_bones and is_picker_space(context.space_data)
''' '''
def execute(self, context): def draw_callback(self):
with context.temp_override(**get_view_3d_override()): gpu.state.blend_set('ALPHA')
if self.mode == 'TRANSLATE': gpu.state.line_width_set(2)
bpy.ops.transform.translate("INVOKE_DEFAULT")
elif self.mode == 'ROTATE':
bpy.ops.transform.rotate("INVOKE_DEFAULT")
elif self.mode == 'SCALE':
bpy.ops.transform.resize("INVOKE_DEFAULT")
return {"FINISHED"} self.dash_shader.bind()
matrix = gpu.matrix.get_projection_matrix()
self.dash_shader.uniform_float("color", [0, 0, 0, 1])
self.dash_shader.uniform_float("viewMatrix", matrix)
self.dash_shader.uniform_float("dashSize", 5)
self.dash_shader.uniform_float("gapSize", 4)
self.batch = batch_for_shader(self.dash_shader, 'LINE_LOOP', {"pos": [self.view_center, self.mouse]})
self.batch.draw(self.dash_shader)
gpu.state.line_width_set(1)
gpu.state.blend_set('NONE')
''' '''
def invoke(self, context, event): def invoke(self, context, event):
self.override = get_view_3d_override() self.override = get_view_3d_override()
if self.mode == 'TRANSLATE': self.view_center = Vector((int(context.region.width / 2), int(context.region.height / 2)))
with context.temp_override(**self.override): self.mouse_start = Vector((event.mouse_region_x, event.mouse_region_y))
if event.alt: self.mouse = Vector((0, 0))
bpy.ops.pose.loc_clear()
else:
bpy.ops.transform.translate("INVOKE_DEFAULT")
return {"FINISHED"}
elif self.mode == 'ROTATE' and event.alt:
with context.temp_override(**self.override):
bpy.ops.pose.rot_clear()
return {"FINISHED"}
elif self.mode == 'SCALE' and event.alt:
with context.temp_override(**self.override):
bpy.ops.pose.scale_clear()
return {"FINISHED"}
self.mouse = event.mouse_region_x, event.mouse_region_y
self.center = context.active_pose_bone.matrix.translation.copy()
transform_type = context.scene.tool_settings.transform_pivot_point
self.center = context.active_pose_bone.matrix.to_translation()
if transform_type == 'MEDIAN_POINT':
origins = [b.matrix.to_translation() for b in context.selected_pose_bones]
self.center = sum(origins, Vector()) / len(context.selected_pose_bones)
# self.bone_data = {}
# for bone in context.selected_pose_bones:
# self.bone_data[bone] = bone.matrix.copy()
self.bone_data = {b: b.matrix.copy() for b in context.selected_pose_bones} self.bone_data = {b: b.matrix.copy() for b in context.selected_pose_bones}
space = self.override['area'].spaces.active
view_matrix = space.region_3d.view_matrix
if space.region_3d.view_perspective == 'CAMERA':
view_matrix = context.scene.camera.matrix_world
context.window.cursor_modal_set('MOVE_X')
self.view_vector = Vector((0, 0, 1))
if self.mode == 'ROTATE':
self.view_vector.rotate(view_matrix)
elif self.mode == 'SCALE':
self.view_vector = Vector((0, 0, 0))
self.transform_type = "VIEW"
self.transform_types = ["VIEW", 'GLOBAL', 'LOCAL']
if context.scene.transform_orientation_slots[0].type == 'LOCAL':
self.transform_types = ["VIEW", 'LOCAL', 'GLOBAL']
self.transform_orientation = context.scene.transform_orientation_slots[0].type
#self.dash_shader = SHADERS['dashed_line']
#self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(self.draw_callback, (), 'WINDOW', 'POST_PIXEL')
context.window_manager.modal_handler_add(self) context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
def change_transform_type(self):
indices = {0: 1, 1:2, 2:1}
index = self.transform_types.index(self.transform_type)
new_index = indices[index]
self.transform_type = self.transform_types[new_index]
def release_event(self, context):
scn = bpy.context.scene
# Insert keyframe
#bpy.ops.ed.undo_push(message="Transform")
if scn.tool_settings.use_keyframe_insert_auto:
try:
bpy.ops.animtoolbox.insert_keyframe()
except Exception:
self.report({"WARNING"}, 'You need animtoolbox addon for inserting keyframe')
self.exit(context)
def exit(self, context):
#bpy.types.SpaceNodeEditor.draw_handler_remove(self._handle, 'WINDOW')
context.window.cursor_modal_restore()
#context.region.tag_redraw()
def modal(self, context, event): def modal(self, context, event):
delta_x = (event.mouse_region_x - self.mouse[0]) / 100
#delta_y = (event.mouse_region_y - self.mouse[1]) / 1000
#for bone, matrix in self.bone_data.items():
# bone.matrix.#translation = matrix.translation + Vector((delta_x, 0, delta_y))
center = context.active_pose_bone.tail
scn = context.scene scn = context.scene
cam = scn.camera self.mouse = Vector((event.mouse_region_x, event.mouse_region_y))
if self.mode == 'ROTATE':
delta = (event.mouse_region_x - self.mouse_start[0]) / 100
elif self.mode == 'SCALE':
delta = 1 + (event.mouse_region_x - self.mouse_start[0]) / 100
#delta = (self.mouse - self.view_center).length / (self.mouse_start - self.view_center).length
transform_type = self.transform_type
#self.batch = batch_for_shader(self.dash_shader, 'LINE_STRIP', {"pos": [self.mouse_start, self.mouse]})
#context.area.tag_redraw()
#print(event.type, event.value)
#rotation_axis = cam.matrix_world.to_3x3().transposed().col[2] if self.mode == 'ROTATE' and event.type == "R" and event.value == 'PRESS':
#rotation_matrix = Matrix.Rotation(delta_x, 4, Vector((0, 0, -1)) @ cam.matrix_world) with context.temp_override(**self.override):
view_vec = Vector((0, 0, 1)) self.exit(context)
view_vec.rotate(cam.matrix_world) bpy.ops.transform.trackball('INVOKE_DEFAULT')
rotation_matrix = Matrix.Rotation(delta_x, 4, view_vec)
#rot_mat = Matrix.Rotation(delta_x, 4, "Z")
#rot_mat = scn.camera.matrix_world.inverted() @ rot_mat
#rot_mat = Matrix.LocRotScale(center, Euler((0, 0, delta_x)), (1, 1, 1))
if event.type == "MOUSEMOVE":
for bone, matrix in self.bone_data.items():
#center =
mat = matrix.copy()
mat.translation -= self.center
mat = rotation_matrix @ mat
mat.translation += self.center
bone.matrix = mat
#rotation_matrix.translation = matrix.translation
#mat = matrix.copy().to_3x3()
#mat.rotate(rotation_matrix)
#mat = mat.to_4x4()
#mat.translation = matrix.translation
#bone.matrix = mat
#bone.matrix = matrix.inverted() @ (rotation_matrix @ bone_mat)
#bone.matrix.translation = 0, 0, 0
#bone.matrix = rotation_matrix @ matrix
#bone.matrix = matrix @ (matrix.inverted() @ rotation_matrix)
#bone.matrix.translation = matrix.translation
#print('Rotate', delta_x)
#with context.temp_override(**get_view_3d_override()):
#for bone, matrix in self.bone_data.items():
# bone.rotation_euler.x = delta_x
#bpy.ops.transform.rotate(value=delta_x)
#print(delta_x, delta_y)
if event.type=="LEFTMOUSE" and event.value == 'RELEASE':
return {'FINISHED'} return {'FINISHED'}
if event.type == "RIGHTMOUSE": if event.type == "LEFTMOUSE" and event.value == 'RELEASE':
self.release_event(context)
return {'FINISHED'}
if event.type in {"RIGHTMOUSE", "ESC"}:
for bone, matrix in self.bone_data.items(): for bone, matrix in self.bone_data.items():
bone.matrix = matrix bone.matrix = matrix
self.exit(context)
return {'CANCELLED'} return {'CANCELLED'}
elif event.type == "X" and event.value == 'PRESS':
self.change_transform_type()
self.view_vector = Vector((1, 0, 0))
#transform_type = self.transform_orientation
elif event.type == "Y" and event.value == 'PRESS':
self.change_transform_type()
self.view_vector = Vector((0, 1, 0))
#transform_type = self.transform_orientation
elif event.type == "Z" and event.value == 'PRESS':
self.change_transform_type()
self.view_vector = Vector((0, 0, 1))
#transform_type = self.transform_orientation
elif event.type == "MOUSEMOVE":
if self.mode == 'ROTATE':
transform_matrix = Matrix.Rotation(delta, 4, self.view_vector)
elif self.mode == 'SCALE':
if self.view_vector.length:
transform_matrix = Matrix.Scale(delta, 4, self.view_vector)
else:
transform_matrix = Matrix.Scale(delta, 4)
for bone, matrix in self.bone_data.items():
center = self.center
if scn.tool_settings.transform_pivot_point == "INDIVIDUAL_ORIGINS":
center = matrix.to_translation()
if self.mode == 'ROTATE':
if transform_type == 'LOCAL':
view_vector = self.view_vector.copy()
view_vector.rotate(matrix)
transform_matrix = Matrix.Rotation(delta, 4, view_vector)
elif self.mode == 'SCALE':
if transform_type == 'LOCAL':
view_vector = self.view_vector.copy()
view_vector.rotate(matrix)
transform_matrix = Matrix.Scale(delta, 4, view_vector)
mat = matrix.copy()
mat.translation -= center
mat = transform_matrix @ mat
mat.translation += center
bone.matrix = mat
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
class RP_OT_function_execute(bpy.types.Operator): class RP_OT_toogle_property(Operator):
bl_idname = "rigpicker.function_execute" """Invert a bone custom property"""
bl_label = 'Function Execute'
shape_index = IntProperty() bl_idname = "rigpicker.toogle_property"
bl_label = 'Toogle Property'
data_path : StringProperty()
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
if not is_picker_space(context): return is_picker_space(context.space_data)
return
def execute(self,context): def execute(self,context):
event = self.event
ob = context.object ob = context.object
shape = ob.data.rig_picker['shapes'][self.shape_index]
try:
value = ob.path_resolve(self.data_path)
except Exception:
return {'CANCELLED'}
function = shape['function'] data = ob
if shape.get('variables'): prop_name = self.data_path
variables=shape['variables'].to_dict() #if '.' in self.data_path:
else: # data, prop = prop_name.rsplit('.', 1)
variables={}
value = type(value)(not value)
exec(f'{repr(ob)}.{self.data_path} = {value}')
variables['event']=event #setattr(data, prop_name, value)
globals()[function](variables) bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='POSE')
if context.scene.tool_settings.use_keyframe_insert_auto:
bone_name, _ = split_path(self.data_path)
ob.keyframe_insert(data_path=self.data_path, group=bone_name)
context.area.tag_redraw()
return {'FINISHED'} return {'FINISHED'}
def invoke(self,context,event):
self.event = event
return self.execute(context)
class RP_OT_reload_picker(Operator):
"""Reload the picker shapes"""
class RP_OT_reload_picker(bpy.types.Operator):
bl_idname = "rigpicker.reload_picker" bl_idname = "rigpicker.reload_picker"
bl_label = 'Reload Picker' bl_label = 'Reload Picker'
@ -356,22 +391,37 @@ class RP_OT_reload_picker(bpy.types.Operator):
# if not is_picker_space(context): # if not is_picker_space(context):
# return # return
def execute(self,context): def execute(self, context):
PICKERS.clear() #PICKERS.clear()
if context.object.type == 'ARMATURE':
rig = context.object
else:
collection = get_picker_collection(context.object)
rig = collection.rig_picker.rig
for a in context.screen.areas: load_picker_data(rig)
a.tag_redraw()
'''
for area in context.screen.areas:
#print(area.type, is_picker_space(area.spaces.active))
if is_picker_space(area.spaces.active):
print('Tag Redraw Region', area.type)
area.regions[0].tag_redraw()
area.tag_redraw()
'''
return {"FINISHED"} return {"FINISHED"}
class RP_OT_toogle_bone_layer(bpy.types.Operator): class RP_OT_toogle_bone_layer(Operator):
"""Toogle bone layer visibility when double clicking on a bone"""
bl_idname = "rigpicker.toogle_bone_layer" bl_idname = "rigpicker.toogle_bone_layer"
bl_label = 'Toogle Bone Layer' bl_label = 'Toogle Bone Layer'
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
if not is_picker_space(context): if not is_picker_space(context.space_data):
return return
ob = context.object ob = context.object
@ -396,13 +446,14 @@ class RP_OT_toogle_bone_layer(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class RP_OT_context_menu_picker(bpy.types.Operator): class RP_OT_context_menu_picker(Operator):
"""Display Menu with the custom properties of the hovered bone if any or the active bone"""
bl_idname = "node.context_menu_picker" bl_idname = "node.context_menu_picker"
bl_label = 'Context Menu Picker' bl_label = 'Context Menu Picker'
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return is_picker_space(context) return is_picker_space(context.space_data)
def execute(self, context): def execute(self, context):
bpy.ops.wm.call_menu(name='RP_MT_context_menu') bpy.ops.wm.call_menu(name='RP_MT_context_menu')
@ -410,75 +461,91 @@ class RP_OT_context_menu_picker(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class RP_OT_pack_picker(bpy.types.Operator): class RP_OT_pack_picker(Operator):
"""Pack Unpack the picker on the rig""" """Pack Unpack the picker on the rig"""
bl_idname = "rigpicker.pack_picker" bl_idname = "rigpicker.pack_picker"
bl_label = 'Toogle Bone Layer' bl_label = 'Pack Picker'
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
ob = context.object ob = context.object
return (ob and ob.type == 'ARMATURE' and ob.data.rig_picker.source) return (ob and ob.type == 'ARMATURE' and ob.data.rig_picker.sources)
def execute(self, context): def execute(self, context):
print('Pack Picker') print('Pack Pickers...')
rig = context.object rig = context.object
picker_path = get_picker_path(rig)
if 'picker' in rig.data.rig_picker.keys(): if 'pickers' in rig.data.rig_picker.keys():
unpack_picker(rig) unpack_picker(rig)
self.report({"INFO"}, f'The picker is unpacked') self.report({"INFO"}, f'The picker is unpacked')
return {"FINISHED"} return {"FINISHED"}
if not picker_path.exists():
self.report({"ERROR"}, f'The path of the picker not exist: {picker_path}')
return {"CANCELLED"}
pack_picker(rig) pack_picker(rig)
if not rig.data.rig_picker['pickers']:
self.report({"ERROR"}, f'No picker have been packed')
return {"CANCELLED"}
elif len(rig.data.rig_picker['pickers']) < len(rig.data.rig_picker.sources):
self.report({"WARNING"}, f'No all pickers have been packed')
return {"FINISHED"}
self.report({"INFO"}, f'The picker is packed') self.report({"INFO"}, f'The picker is packed')
return {"FINISHED"} return {"FINISHED"}
class RP_OT_call_operator(bpy.types.Operator): class RP_OT_call_operator(Operator):
bl_idname = "rigpicker.call_operator" bl_idname = "rigpicker.call_operator"
bl_label = 'Call operator' bl_label = 'Call operator'
operator : bpy.props.StringProperty() operator : StringProperty()
view_3d : bpy.props.BoolProperty() arguments : StringProperty()
invoke: BoolProperty()
view_3d : BoolProperty()
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return is_picker_space(context) return is_picker_space(context.space_data)
def execute(self, context): @classmethod
if not self.operator.startswith('bpy.ops'): def description(cls, context, properties):
print("Operator call_operator can only execute operator") try:
return {"FINISHED"} op = eval_attr(bpy.ops, properties.operator).get_rna_type()
return op.description
print('CAll Operator') except AttributeError:
return 'Call operator'
context.area.tag_redraw() def execute(self, context):
#context.area.tag_redraw()
override = {} override = {}
if self.view_3d: if self.view_3d:
override = get_view_3d_override() override = get_view_3d_override()
arguments = json.loads(self.arguments or "{}")
with context.temp_override(**override): with context.temp_override(**override):
try: try:
exec(self.operator) ops = eval_attr(bpy.ops, self.operator)
if self.invoke:
ops('INVOKE_DEFAULT', **arguments)
else:
ops(**arguments)
except Exception as e: except Exception as e:
self.report({"ERROR"}, e) print(e)
self.report({"ERROR"}, f'The operator {self.operator} cannot be called')
return {"CANCELLED"}
context.area.tag_redraw() context.area.tag_redraw()
return {"FINISHED"} return {"FINISHED"}
class RP_MT_context_menu(bpy.types.Menu): class RP_MT_context_menu(Menu):
bl_label = "Context Menu" bl_label = "Context Menu"
#bl_idname = "RP_MT_context_menu"
# Set the menu operators and draw functions # Set the menu operators and draw functions
def draw(self, context): def draw(self, context):
@ -491,7 +558,10 @@ class RP_MT_context_menu(bpy.types.Menu):
if picker.hover_shape and picker.hover_shape.type == 'bone': if picker.hover_shape and picker.hover_shape.type == 'bone':
bone = picker.hover_shape.bone bone = picker.hover_shape.bone
else:
bone = context.active_pose_bone
if bone:
for key in bone.keys(): for key in bone.keys():
layout.prop(bone,f'["{key}"]', slider=True) layout.prop(bone,f'["{key}"]', slider=True)
@ -499,7 +569,51 @@ class RP_MT_context_menu(bpy.types.Menu):
#layout.operator("rigidbody.objects_add", text="B Add Passive").type = 'PASSIVE' #layout.operator("rigidbody.objects_add", text="B Add Passive").type = 'PASSIVE'
class RP_OT_add_picker_source(bpy.types.Operator): class RP_OT_add_picker_collection(Operator):
bl_idname = "rigpicker.add_picker_collection"
bl_label = "Add a Picker Collection"
bl_description = "Add a Picker Collection"
bl_options = {'UNDO', 'REGISTER'}
@classmethod
def poll(cls, context):
ob = context.object
return (ob and ob.type == 'ARMATURE')
def execute(self, context):
scn = context.scene
ob = context.object
name = ob.name
if name.endswith('_rig'):
name = name[:-4]
canvas_name = f'canevas_{name}'
canvas_points = [(-0.5, 0.5, 0), (0.5, 0.5, 0), (0.5, -0.5, 0), (-0.5, -0.5, 0)]
canvas_faces = [(0, 1, 2, 3)]
canvas_mesh = bpy.data.meshes.new(canvas_name)
canvas_mesh.from_pydata(canvas_points, [], canvas_faces)
canvas_mesh.update(calc_edges=True)
canvas_ob = bpy.data.objects.new(canvas_name, canvas_mesh)
canvas_ob.rig_picker.shape_type = 'DISPLAY'
col = bpy.data.collections.new(f'Picker {name}')
col.rig_picker.enabled = True
col.rig_picker.rig = ob
col.rig_picker.canvas = canvas_ob
col.objects.link(canvas_ob)
scn.collection.children.link(col)
self.report({"INFO"}, f"New Picker Collection {col.name} Created")
return {'FINISHED'}
class RP_OT_add_picker_source(Operator):
bl_idname = "rigpicker.add_picker_source" bl_idname = "rigpicker.add_picker_source"
bl_label = "Add a Picker source" bl_label = "Add a Picker source"
bl_description = "Add a Picker source" bl_description = "Add a Picker source"
@ -512,7 +626,7 @@ class RP_OT_add_picker_source(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
class RP_OT_remove_picker_source(bpy.types.Operator): class RP_OT_remove_picker_source(Operator):
bl_idname = "rigpicker.remove_picker_source" bl_idname = "rigpicker.remove_picker_source"
bl_label = "Delete a Picker source" bl_label = "Delete a Picker source"
bl_description = "Delete a Picker source" bl_description = "Delete a Picker source"
@ -527,7 +641,7 @@ class RP_OT_remove_picker_source(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
class RP_OT_fit_picker(bpy.types.Operator): class RP_OT_fit_picker(Operator):
bl_idname = "rigpicker.fit_picker" bl_idname = "rigpicker.fit_picker"
bl_label = "Fit Picker" bl_label = "Fit Picker"
bl_description = "Fit Picker in 2d view" bl_description = "Fit Picker in 2d view"
@ -537,7 +651,7 @@ class RP_OT_fit_picker(bpy.types.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return is_picker_space(context) and PICKERS.get(context.object) return is_picker_space(context.space_data) and PICKERS.get(context.object)
def execute(self, context): def execute(self, context):
ob = context.object ob = context.object
@ -585,43 +699,58 @@ def register_keymaps():
keymaps.append((km, kmi)) keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.call_operator", type="NUMPAD_PERIOD", value="PRESS") kmi = km.keymap_items.new("rigpicker.call_operator", type="NUMPAD_PERIOD", value="PRESS")
kmi.properties.operator = "bpy.ops.view3d.view_selected()" kmi.properties.operator = "view3d.view_selected"
kmi.properties.view_3d = True kmi.properties.view_3d = True
keymaps.append((km, kmi)) keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.call_operator", type="A", value="PRESS") kmi = km.keymap_items.new("rigpicker.call_operator", type="A", value="PRESS")
kmi.properties.operator = "bpy.ops.pose.select_all(action='SELECT')" kmi.properties.operator = "pose.select_all"
kmi.properties.arguments = '{"action": "SELECT"}'
keymaps.append((km, kmi)) keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.call_operator", type="A", value="PRESS", alt=True) kmi = km.keymap_items.new("rigpicker.call_operator", type="A", value="PRESS", alt=True)
kmi.properties.operator = "bpy.ops.pose.select_all(action='DESELECT')" kmi.properties.operator = "pose.select_all"
kmi.properties.arguments = '{"action": "DESELECT"}'
keymaps.append((km, kmi)) keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.toogle_bone_layer", type="LEFTMOUSE", value="DOUBLE_CLICK") kmi = km.keymap_items.new("rigpicker.toogle_bone_layer", type="LEFTMOUSE", value="DOUBLE_CLICK")
keymaps.append((km, kmi)) keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.picker_transform", type="G", value="PRESS") kmi = km.keymap_items.new("rigpicker.call_operator", type="X", value="PRESS")
kmi.properties.mode = 'TRANSLATE' kmi.properties.operator = "animtoolbox.reset_bone"
keymaps.append((km, kmi)) keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.picker_transform", type="G", value="PRESS", alt=True) kmi = km.keymap_items.new("rigpicker.call_operator", type="K", value="PRESS")
kmi.properties.mode = 'TRANSLATE' kmi.properties.operator = "animtoolbox.insert_keyframe"
keymaps.append((km, kmi))
kmi = km.keymap_items.new("anim.keyframe_delete_v3d", type="K", value="PRESS", alt=True)
keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.call_operator", type="G", value="PRESS")
kmi.properties.operator = 'transform.translate'
kmi.properties.view_3d = True
kmi.properties.invoke = True
keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.call_operator", type="G", value="PRESS", alt=True)
kmi.properties.operator = 'pose.loc_clear'
keymaps.append((km, kmi)) keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.picker_transform", type="R", value="PRESS") kmi = km.keymap_items.new("node.picker_transform", type="R", value="PRESS")
kmi.properties.mode = 'ROTATE' kmi.properties.mode = 'ROTATE'
keymaps.append((km, kmi)) keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.picker_transform", type="R", value="PRESS", alt=True) kmi = km.keymap_items.new("rigpicker.call_operator", type="R", value="PRESS", alt=True)
kmi.properties.mode = 'ROTATE' kmi.properties.operator = 'pose.rot_clear'
keymaps.append((km, kmi)) keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.picker_transform", type="S", value="PRESS") kmi = km.keymap_items.new("node.picker_transform", type="S", value="PRESS")
kmi.properties.mode = 'SCALE' kmi.properties.mode = 'SCALE'
keymaps.append((km, kmi)) keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.picker_transform", type="S", value="PRESS", alt=True) kmi = km.keymap_items.new("rigpicker.call_operator", type="S", value="PRESS", alt=True)
kmi.properties.mode = 'SCALE' kmi.properties.operator = 'pose.scale_clear'
keymaps.append((km, kmi)) keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.context_menu_picker", type="RIGHTMOUSE", value="PRESS") kmi = km.keymap_items.new("node.context_menu_picker", type="RIGHTMOUSE", value="PRESS")
@ -641,9 +770,9 @@ def register_keymaps():
keymaps.append((km, kmi)) keymaps.append((km, kmi))
#km = wm.keyconfigs.addon.keymaps.new(name="View2D") #km = wm.keyconfigs.addon.keymaps.new(name="View2D")
kmi = km.keymap_items.new("rigpicker.call_operator", type="MIDDLEMOUSE", value="PRESS") #kmi = km.keymap_items.new("rigpicker.call_operator", type="MIDDLEMOUSE", value="PRESS")
kmi.properties.operator = "bpy.ops.view2d.pan('INVOKE_DEFAULT')" #kmi.properties.operator = "bpy.ops.view2d.pan('INVOKE_DEFAULT')"
keymaps.append((km, kmi)) #keymaps.append((km, kmi))
def unregister_keymaps(): def unregister_keymaps():
@ -653,7 +782,7 @@ def unregister_keymaps():
classes = ( classes = (
RP_OT_box_select, RP_OT_box_select,
RP_OT_function_execute, RP_OT_toogle_property,
RP_OT_reload_picker, RP_OT_reload_picker,
RP_OT_toogle_bone_layer, RP_OT_toogle_bone_layer,
RP_OT_call_operator, RP_OT_call_operator,
@ -663,7 +792,8 @@ classes = (
RP_OT_pack_picker, RP_OT_pack_picker,
RP_OT_add_picker_source, RP_OT_add_picker_source,
RP_OT_remove_picker_source, RP_OT_remove_picker_source,
RP_OT_fit_picker RP_OT_fit_picker,
RP_OT_add_picker_collection
#RP_OT_ui_draw #RP_OT_ui_draw
) )
@ -674,6 +804,7 @@ def register():
register_keymaps() register_keymaps()
def unregister(): def unregister():
unregister_keymaps() unregister_keymaps()
for cls in reversed(classes): for cls in reversed(classes):

View File

@ -8,7 +8,7 @@ from bpy.props import IntProperty
from mathutils import Matrix from mathutils import Matrix
from ..core.shape import get_picker_data from ..core.shape import get_picker_data
from ..core.addon_utils import is_shape from ..core.addon_utils import is_shape, get_picker_collection
from ..core.bl_utils import link_mat_to_object, flip_name from ..core.bl_utils import link_mat_to_object, flip_name
@ -77,7 +77,7 @@ class RP_OT_name_from_bone(Operator):
class RP_OT_mirror_shape(Operator): class RP_OT_mirror_shape(Operator):
bl_label = 'Mirror UI shape' bl_label = 'Mirror UI shape'
bl_idname = 'rigpicker.mirror_shape' bl_idname = 'rigpicker.mirror_shape'
#bl_options = {'REGISTER', 'UNDO'} #bl_options = g
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@ -86,7 +86,7 @@ class RP_OT_mirror_shape(Operator):
def execute(self,context): def execute(self,context):
scn = context.scene scn = context.scene
ob = context.object ob = context.object
collection = next((c for c in ob.users_collection if c.rig_picker.enabled), None) collection = get_picker_collection(ob)
objects = context.selected_objects objects = context.selected_objects
# Remove mirror object: # Remove mirror object:
@ -192,39 +192,33 @@ class RP_OT_save_picker(Operator):
scn = context.scene scn = context.scene
ob = context.object ob = context.object
print('SAve Picker', self.index)
if self.index == -1: if self.index == -1:
collection = next((c for c in ob.users_collection if c.rig_picker.enabled), None) collection = get_picker_collection(ob)
else: else:
source = context.active_object.data.rig_picker.sources[self.index].source source = context.active_object.data.rig_picker.sources[self.index].source
source = Path(bpy.path.abspath(source, library=ob.data.library)).resolve() source = Path(bpy.path.abspath(source, library=ob.data.library)).resolve()
collection = next((c for c in set(scn.collection.children_recursive) if c.rig_picker.enabled collection = next((c for c in set(scn.collection.children_recursive) if c.rig_picker.enabled
and Path(bpy.path.abspath(c.rig_picker.destination)).resolve() == source), None) and Path(bpy.path.abspath(c.rig_picker.destination)).resolve() == source), None)
print('source', source)
print('colleciton', collection)
if not collection: if not collection:
self.report({"ERROR"}, 'No Picker found') self.report({"ERROR"}, 'No Picker found')
return {'CANCELLED'} return {'CANCELLED'}
canvas = collection.rig_picker.canvas canvas = collection.rig_picker.canvas
rig = collection.rig_picker.rig #rig = collection.rig_picker.rig
shapes = [o for o in collection.all_objects if o != canvas and o.type in ('MESH', 'CURVE', 'FONT')] 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')
return {'CANCELLED'}
if not canvas: if not canvas:
self.report({'ERROR'}, 'Choose a Canvas') self.report({'ERROR'}, 'Choose a Canvas')
return {'CANCELLED'} return {'CANCELLED'}
data = get_picker_data(shapes, canvas, rig) picker_data = get_picker_data(collection)
picker_path = Path(bpy.path.abspath(collection.rig_picker.destination)) picker_path = Path(bpy.path.abspath(collection.rig_picker.destination))
picker_path.write_text(json.dumps(data))
print(f'Save Picker to {picker_path}')
picker_path.write_text(json.dumps(picker_data))
bpy.ops.rigpicker.reload_picker() bpy.ops.rigpicker.reload_picker()

117
panels.py
View File

@ -1,117 +0,0 @@
import bpy
#import collections
#import inspect
from .core.addon_utils import get_operator_from_id
from .core.bl_utils import get_mat
import re
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
layout = self.layout
col = layout.column(align=False)
col.prop_search(scn.rig_picker, 'rig', scn, 'objects', text='Rig ')
col.prop_search(scn.rig_picker, 'canvas', scn, 'objects', text='Canvas ')
col.prop_search(scn.rig_picker, 'symmetry', scn, 'objects', text='Symmetry ')
if ob.type == 'ARMATURE':
row = layout.row(align=True)
is_packed = ('picker' in ob.data.rig_picker.keys())
sub_row = row.row(align=True)
sub_row.prop(ob.data.rig_picker, 'source', text='Picker')
sub_row.enabled = not is_packed
row.operator('rigpicker.pack_picker', icon='PACKAGE' if is_packed else 'UGLYPACKAGE', text='')
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='ARROW_LEFTRIGHT', 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 scn.rig_picker.rig:
col.prop_search(ob.rig_picker, 'name', scn.rig_picker.rig.pose, 'bones', text='Bone')
#layout.separator()
layout.prop(scn.rig_picker, 'destination', text='Filepath')
layout.operator('rigpicker.save_picker', icon='PASTEDOWN', text='Save Picker')
classes = (
RP_PT_picker_maker_panel,
)
register, unregister = bpy.utils.register_classes_factory(classes)

19
shaders/dash_shader.frag Normal file
View File

@ -0,0 +1,19 @@
flat in vec2 startPos;
in vec2 vertPos;
out vec4 fragColor;
uniform vec4 color;
uniform float dashSize;
uniform float gapSize;
void main()
{
vec2 dir = (vertPos.xy - startPos.xy);
float dist = length(dir);
if (fract(dist / (dashSize + gapSize)) > dashSize/(dashSize + gapSize))
discard;
fragColor = color;
}

15
shaders/dash_shader.vert Normal file
View File

@ -0,0 +1,15 @@
layout (location = 0) in vec2 pos;
flat out vec2 startPos;
out vec2 vertPos;
uniform mat4 viewMatrix;
void main()
{
vec4 outPos = viewMatrix * vec4(pos.x, pos.y, 0.0, 1.0);
gl_Position = outPos;
vertPos = pos.xy / outPos.w;
startPos = vertPos;
}

66
ui.py
View File

@ -2,7 +2,7 @@ import bpy
from bpy.types import UIList from bpy.types import UIList
#import collections #import collections
#import inspect #import inspect
from .core.addon_utils import get_operator_from_id from .core.addon_utils import get_operator_from_id, get_picker_collection
from .core.bl_utils import get_mat from .core.bl_utils import get_mat
import re import re
@ -29,10 +29,13 @@ class RP_PT_picker_maker_panel(bpy.types.Panel):
def poll(cls, context): def poll(cls, context):
return context.object return context.object
def draw_header_preset(self, context):
self.layout.operator('rigpicker.add_picker_collection', text="", icon='ADD', emboss=False)
def draw(self, context): def draw(self, context):
ob = context.object ob = context.object
scn = context.scene scn = context.scene
collection = next((c for c in ob.users_collection if c.rig_picker.enabled), None) collection = get_picker_collection(ob)
layout = self.layout layout = self.layout
col = layout.column(align=False) col = layout.column(align=False)
@ -51,24 +54,45 @@ class RP_PT_picker_maker_panel(bpy.types.Panel):
row.separator(factor=0.5) row.separator(factor=0.5)
row.label(text="Sources") row.label(text="Sources")
is_packed = ('picker' in ob.data.rig_picker.keys()) is_packed = ('pickers' in ob.data.rig_picker.keys())
row.operator('rigpicker.pack_picker', icon='PACKAGE' if is_packed else 'UGLYPACKAGE', text='', emboss=False) 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) row.operator("rigpicker.add_picker_source", icon ='ADD', text="", emboss=False)
for i, item in enumerate(ob.data.rig_picker.sources): for i, item in enumerate(ob.data.rig_picker.sources):
row = col.row(align=True) row = col.row(align=True)
is_packed = ('pickers' in ob.data.rig_picker.keys()) row.enabled = not is_packed
row.prop(item, 'source', text='') row.prop(item, 'source', text='')
row.operator("rigpicker.remove_picker_source", icon ='PANEL_CLOSE', text="", emboss=False).index=i row.operator("rigpicker.remove_picker_source", icon ='PANEL_CLOSE', text="", emboss=False).index=i
if collection:
#layout.separator()
layout.prop(collection.rig_picker, 'destination', text='Filepath')
layout.operator('rigpicker.save_picker', icon='PASTEDOWN', text='Save Picker')
class RP_PT_shape(bpy.types.Panel):
bl_label = 'Shape'
bl_category = 'Rigging'
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_parent_id = "RP_PT_picker_maker_panel"
def draw(self, context):
ob = context.object
scn = context.scene
collection = get_picker_collection(ob)
layout = self.layout
col = layout.column(align=False)
if collection: if collection:
#if context.collection and context.collection.rig_picker.enabled: #if context.collection and context.collection.rig_picker.enabled:
col = layout.column(align=True) col = layout.column(align=True)
row = col.row(align=True) row = col.row(align=True)
row.operator('rigpicker.create_shape', icon='MESH_DATA', text='Create shape') row.operator('rigpicker.create_shape', icon='MESH_DATA', text='Create Shape')
row.operator('rigpicker.mirror_shape', icon='MOD_MIRROR', text='Mirror 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.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') col.prop(scn.rig_picker, 'use_pick_bone', icon='EYEDROPPER', text='Auto Bone Assign')
if ob.type != 'ARMATURE': if ob.type != 'ARMATURE':
box = layout.box() box = layout.box()
@ -107,36 +131,14 @@ class RP_PT_picker_maker_panel(bpy.types.Panel):
else: else:
col.prop(ob.rig_picker, 'name', text='Tooltip') col.prop(ob.rig_picker, 'name', text='Tooltip')
''' elif ob.rig_picker.shape_type == 'BONE':
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: if collection and collection.rig_picker.rig:
col.prop_search(ob.rig_picker, 'name', collection.rig_picker.rig.pose, 'bones', text='Bone') 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 = ( classes = (
#RP_UL_picker_source, #RP_UL_picker_source,
RP_PT_picker_maker_panel, RP_PT_picker_maker_panel,
RP_PT_shape
) )
register, unregister = bpy.utils.register_classes_factory(classes) register, unregister = bpy.utils.register_classes_factory(classes)