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 = ( modules = (
'.operators', '.operators',
'.properties', '.properties',
'.panels', '.ui',
'.area', '.area',
'.gizmo', '.gizmo',
'.draw_handlers' '.draw_handlers'

73
area.py
View File

@ -1,5 +1,6 @@
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
from .constants import PICKERS
# Derived from the NodeTree base type, similar to Menu, Operator, Panel, etc. # Derived from the NodeTree base type, similar to Menu, Operator, Panel, etc.
@ -13,36 +14,52 @@ class RigPickerTree(NodeTree):
bl_icon = 'OUTLINER_DATA_ARMATURE' bl_icon = 'OUTLINER_DATA_ARMATURE'
def draw_header(self, context): def draw_header(self, context):
if context.space_data.tree_type == 'RigPickerTree': if not context.space_data.tree_type == 'RigPickerTree':
layout = self.layout
layout.template_header()
#layout.separator_spacer()
if not context.space_data.node_tree:
ntree = bpy.data.node_groups.get('.rig_picker')
if not ntree:
ntree = bpy.data.node_groups.new('.rig_picker', 'RigPickerTree')
context.space_data.node_tree = ntree
#layout.template_ID(context.space_data, "node_tree", new="node.new_node_tree")
#layout.separator_spacer()
layout.operator('rigpicker.reload_picker', icon='FILE_REFRESH', text='')
#layout.prop('rigpicker.reload_picker', icon='FILE_REFRESH', text='')
layout.separator_spacer()
else:
self._draw(context) self._draw(context)
return
layout = self.layout
layout.template_header()
#layout.separator_spacer()
if not context.space_data.node_tree:
ntree = bpy.data.node_groups.get('.rig_picker')
if not ntree:
ntree = bpy.data.node_groups.new('.rig_picker', 'RigPickerTree')
context.space_data.node_tree = ntree
#layout.template_ID(context.space_data, "node_tree", new="node.new_node_tree")
#layout.separator_spacer()
#layout.operator('rigpicker.reload_picker', icon='FILE_REFRESH', text='')
ob = context.object
if not ob or not ob.type == 'ARMATURE':
return
picker_group = PICKERS.get(ob)
if not picker_group:
return
layout.separator_spacer()
layout.label(text=ob.name)
layout.separator_spacer()
#row.alignment = 'RIGHT'
row = layout.row(align=True)
for i, picker in enumerate(picker_group.pickers):
row.operator('rigpicker.fit_picker', text=f'{i+1}').index=i
#row.separator_spacer()
row.operator('rigpicker.fit_picker', text='', icon='FULLSCREEN_ENTER').index = -1
#layout.prop('rigpicker.reload_picker', icon='FILE_REFRESH', text='')
def tools_from_context(context, mode=None): def tools_from_context(context, mode=None):
sp = context.space_data sp = context.space_data

View File

@ -31,40 +31,15 @@ def link_mat_to_object(ob):
sl.link = 'OBJECT' sl.link = 'OBJECT'
sl.material = m sl.material = m
def find_mirror(name): def flip_name(name):
mirror = None if not name:
prop= False return
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
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: else:
return None return bpy.utils.flip_name(name)
def hide_layers(args): 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)) 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 return [
dx = min(border[1][0],bound[1][0]) - max(border[0][0],bound[0][0]) Vector((x_points[0], y_points[-1])),
dy = min(border[0][1],bound[0][1]) - max(border[2][1],bound[2][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): if (dx>=0) and (dy>=0):
return dx*dy 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] 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)): 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 from gpu_extras.batch import batch_for_shader
import blf import blf
from mathutils import bvhtree, Vector 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 ..constants import PICKERS
from .addon_utils import get_operator_from_id from .addon_utils import get_operator_from_id
from .geometry_utils import bounding_rect
from pathlib import Path from pathlib import Path
import re import re
@ -16,7 +18,6 @@ import os
import threading import threading
class Shape: class Shape:
def __init__(self, picker, points, polygons=None, edges=None, tooltip='', color=None, source_name=''): def __init__(self, picker, points, polygons=None, edges=None, tooltip='', color=None, source_name=''):
@ -28,7 +29,6 @@ class Shape:
self.hover = False self.hover = False
self.press = False self.press = False
self.shader = gpu.shader.from_builtin('UNIFORM_COLOR') self.shader = gpu.shader.from_builtin('UNIFORM_COLOR')
#self.hover_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.polygons = polygons or []
self.edges = edges 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.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) self.e_batch = batch_for_shader(self.shader, 'LINES', {"pos": self.points}, indices=self.edges)
#if polygons: self.rect = bounding_rect(points)
# self.batch = batch_for_shader(self.shader, 'TRIS', {"pos": points}, indices=polygons)
#else:
#pts = []
#for loop in self.edges:
# pts += [self.points[i] for i in loop]
# self.batch = batch_for_shader(self.shader, 'LINES', {"pos": points}, indices=indices)
points_x = [v[0] for v in points]
points_y = [v[1] for v in points]
self.bound = [
(min(points_x), max(points_y)),
(max(points_x), max(points_y)),
(max(points_x), min(points_y)),
(min(points_x), min(points_y))
]
@property @property
def color(self): def color(self):
@ -97,7 +78,7 @@ class Shape:
self.e_batch.draw(self.shader) self.e_batch.draw(self.shader)
def move_event(self, location): 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 self.hover = False
return False return False
@ -224,6 +205,7 @@ class BoneShape(Shape):
#Overlay the fill slightly with the bone color #Overlay the fill slightly with the bone color
self.shader.uniform_float("color", (*self.bone_colors['normal'][:3], 0.1)) self.shader.uniform_float("color", (*self.bone_colors['normal'][:3], 0.1))
self.p_batch.draw(self.shader) self.p_batch.draw(self.shader)
gpu.state.blend_set('NONE')
#self.contour_shader.bind() #self.contour_shader.bind()
@ -238,7 +220,6 @@ class BoneShape(Shape):
self.e_batch.draw(self.shader) self.e_batch.draw(self.shader)
gpu.state.line_width_set(1.0) gpu.state.line_width_set(1.0)
gpu.state.blend_set('NONE')
def assign_bone_event(self): def assign_bone_event(self):
@ -275,11 +256,6 @@ class BoneShape(Shape):
def border_select(self, border, mode='SET'): 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: if not self.bone:
return return
@ -287,16 +263,16 @@ class BoneShape(Shape):
self.bone.bone.select = False self.bone.bone.select = False
return return
bound_tri1 = self.bound[0], self.bound[1], self.bound[2] rect_tri1 = self.rect[0], self.rect[1], self.rect[2]
bound_tri2 = self.bound[2], self.bound[3], self.bound[0] rect_tri2 = self.rect[2], self.rect[3], self.rect[0]
border_tri1 = border[0], border[1], border[2] border_tri1 = border[0], border[1], border[2]
border_tri2 = border[2], border[3], border[0] border_tri2 = border[2], border[3], border[0]
if (not intersect_tri_tri_2d(*border_tri1, *bound_tri1) and if (not intersect_tri_tri_2d(*border_tri1, *rect_tri1) and
not intersect_tri_tri_2d(*border_tri1, *bound_tri2) and not intersect_tri_tri_2d(*border_tri1, *rect_tri2) and
not intersect_tri_tri_2d(*border_tri2, *bound_tri1) and not intersect_tri_tri_2d(*border_tri2, *rect_tri1) and
not intersect_tri_tri_2d(*border_tri2, *bound_tri2)): not intersect_tri_tri_2d(*border_tri2, *rect_tri2)):
return return
select = True select = True
@ -388,116 +364,89 @@ class Picker:
self.region = bpy.context.region self.region = bpy.context.region
self.rig = rig self.rig = rig
self.translation = Vector((0, 0))
self.shapes = [] self.shapes = []
self.box_select = None self.box_select = None
self.hover_shape = None self.hover_shape = None
self.shader = gpu.shader.from_builtin('UNIFORM_COLOR') self.shader = gpu.shader.from_builtin('UNIFORM_COLOR')
self.tooltip_shape = None
self.tooltip_mouse = None
self.tooltip = ''
self.timer = None
self.mouse = None self.mouse = None
for s in shapes: for shape_data in shapes:
if not s['points']: if not shape_data['points']:
continue continue
if s['type'] in ('CANVAS', 'DISPLAY'): if shape_data['type'] in ('CANVAS', 'DISPLAY'):
shape = Shape( shape = Shape(
self, self,
points=s['points'], points=shape_data['points'],
polygons=s['polygons'], polygons=shape_data['polygons'],
edges=s['edges'], edges=shape_data['edges'],
color=s['color'] color=shape_data['color']
) )
elif s['type'] == 'BONE': elif shape_data['type'] == 'BONE':
bone = rig.pose.bones.get(s['bone']) bone = rig.pose.bones.get(shape_data['bone'])
#if not bone: #if not bone:
# print(f'Bone {s["bone"]} not exist') # print(f'Bone {shape_data["bone"]} not exist')
# continue # continue
shape = BoneShape( shape = BoneShape(
self, self,
source_name=s['source_name'], source_name=shape_data['source_name'],
points=s['points'], points=shape_data['points'],
polygons=s['polygons'], polygons=shape_data['polygons'],
edges=s['edges'], edges=shape_data['edges'],
bone=bone, bone=bone,
color=s['color'] color=shape_data['color']
) )
elif s['type'] == 'OPERATOR': elif shape_data['type'] == 'OPERATOR':
shape = OperatorShape( shape = OperatorShape(
self, self,
source_name=s['source_name'], source_name=shape_data['source_name'],
points=s['points'], points=shape_data['points'],
polygons=s['polygons'], polygons=shape_data['polygons'],
operator=s['operator'], operator=shape_data['operator'],
color=s['color'], color=shape_data['color'],
tooltip=s['tooltip'], tooltip=shape_data['tooltip'],
) )
self.shapes.append(shape) self.shapes.append(shape)
self.rect = bounding_rect([p for s in self.shapes for p in s.points])
def assign_bone_event(self): def assign_bone_event(self):
for s in self.shapes: for shape in self.shapes:
if s.type=='bone' and s.hover: if shape.type=='bone' and shape.hover:
s.assign_bone_event() shape.assign_bone_event()
bpy.ops.rigpicker.save_picker() bpy.ops.rigpicker.save_picker()
def press_event(self, mode='SET'): def press_event(self, mode='SET'):
for s in self.shapes: for shape in self.shapes:
if s.hover: #print(s)
s.press_event(mode) if shape.hover:
shape.press_event(mode)
else: else:
s.press = False shape.press = False
def release_event(self, mode='SET'): 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') #bpy.ops.pose.select_all(action='DESELECT')
#print('PICKER release event', mode) #print('PICKER release event', mode)
#print(f'type={event.type}, value={event.value}, ctrl={event.ctrl}, shift={event.shift}, alt={event.alt}') #print(f'type={event.type}, value={event.value}, ctrl={event.ctrl}, shift={event.shift}, alt={event.alt}')
for s in self.shapes: for shape in self.shapes:
if s.hover: if shape.hover:
s.release_event(mode) shape.release_event(mode)
s.press = False shape.press = False
#bpy.context.area.tag_redraw() #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') picker.tooltip_event(event='SHOW')
region.tag_redraw() region.tag_redraw()
@ -524,27 +473,27 @@ class Picker:
''' '''
def border_select(self, border, mode): 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): def move_event(self, location):
self.mouse = location self.mouse = Vector(location) - Vector(self.translation)
location = self.region.view2d.region_to_view(*location)
self.hover_shape = None self.hover_shape = None
for shape in reversed(self.shapes): for shape in reversed(self.shapes):
if self.hover_shape: if self.hover_shape:
shape.hover = False shape.hover = False
elif shape.move_event(location): elif shape.move_event(self.mouse):
self.hover_shape = shape 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) # over = point_over_shape(self.end,points, edges)
#if bpy.app.timers.is_registered(self.tooltip_event): #if bpy.app.timers.is_registered(self.tooltip_event):
@ -552,6 +501,85 @@ class Picker:
# bpy.app.timers.unregister(self.tooltip_event) # bpy.app.timers.unregister(self.tooltip_event)
#except: #except:
# pass # 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: if self.tooltip_shape is not self.hover_shape:
self.tooltip = '' self.tooltip = ''
@ -561,27 +589,70 @@ class Picker:
self.timer = threading.Timer(0.5, self.tooltip_event) self.timer = threading.Timer(0.5, self.tooltip_event)
self.timer.start() 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): def release_event(self, mode='SET'):
for s in self.shapes:
s.draw()
'''
if self.box_select:
self.box_shader.uniform_float("color", self.box_select_color) if mode == 'SET':
batch = batch_for_shader(self.shader, 'LINE_LOOP', {"pos": []}) for bone in self.rig.data.bones:
bone.select = False
for picker in self.pickers:
picker.release_event(mode)
def border_select(self, border, mode):
border = [self.region.view2d.region_to_view(*b) for b in border]
if mode == 'SET':
for bone in self.rig.data.bones:
bone.select = False
for picker in self.pickers:
picker.border_select(border, mode)
def clear_tooltip(self):
self.tooltip = ''
self.tooltip_shape = None
self.timer.cancel()
self.region.tag_redraw()
def tooltip_event(self):
#print('Tooltip Event', self)
#print(self.hover_shape, self.hover_shape.type)
if self.hover_shape and self.hover_shape.type != 'display':
if self.hover_shape.type == 'bone':
self.tooltip = self.hover_shape.bone.name
else:
self.tooltip = self.hover_shape.tooltip
self.tooltip_shape = self.hover_shape
else:
return self.clear_tooltip()
for b in self.contour_batches:
b.draw(self.contour_shader)
self.batch.draw(self.shader) self.tooltip_mouse = self.mouse
self.timer.cancel()
#print(self.tooltip)
self.region.tag_redraw()
@property
def rect(self):
return bounding_rect([co - Vector(p.translation) for p in self.pickers for co in p.rect])
@property
def center(self):
center = sum(self.rect, Vector((0, 0))) / len(self.rect)
center[1] = -center[1]
return center
gpu.state.blend_set('NONE')
'''
def get_picker_path(rig, start=None): def get_picker_path(rig, start=None):
picker_path = rig.data.get('rig_picker', {}).get('source') 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)) 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) picker_path = get_picker_path(rig, start=start)
if picker_path and picker_path.exists(): if picker_path and picker_path.exists():
if 'rig_picker' not in rig.data.keys(): if 'rig_picker' not in rig.data.keys():
rig.data['rig_picker'] = {} 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): def unpack_picker(rig):
if 'rig_picker' not in rig.data.keys(): if 'rig_picker' not in rig.data.keys():

View File

@ -20,7 +20,7 @@ def border_over_shape(border,verts,loops):
return True return True
for point in verts: for point in verts:
if point_inside_rectangle(point,border): if point_inside_rect(point,border):
return True return True
for point in border: for point in border:
@ -29,7 +29,7 @@ def border_over_shape(border,verts,loops):
def border_loop(vert, loop): 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: if border_edge:
for edge in 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 return loops
def get_picker_datas(objects, canvas, rig): def get_picker_data(objects, canvas, rig):
picker_datas = [] picker_datas = []
gamma = 1 / 2.2 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_coords = [canvas.matrix_world@Vector((p.co)) for p in canvas_points]
canvas_x = [p[0] for p in canvas_coords] height = max(canvas_coords).y - min(canvas_coords).y
canvas_y = [p[1] for p in canvas_coords] width = max(canvas_coords).x - min(canvas_coords).x
canvas_center = sum(canvas_coords, Vector()) / len(canvas_coords) 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) objects.append(canvas)

View File

@ -1,39 +1,91 @@
import bpy import bpy
import gpu
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 from .core.picker import Picker, PickerGroup
import json 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(): def draw_callback_view():
sp = bpy.context.space_data sp = bpy.context.space_data
if not sp or not sp.tree_type == 'RigPickerTree': if not sp or not sp.tree_type == 'RigPickerTree':
return return
if not sp.pin:
sp.pin = True
sp.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.source: 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 'picker' in ob.data.rig_picker: if 'pickers' in ob.data.rig_picker:
picker_datas = [s.to_dict() for s in ob.data.rig_picker['picker']] picker_datas = [s.to_dict() for s in ob.data.rig_picker['pickers']]
else: 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(): if not picker_path.exists():
print(f'Picker path not exists: {picker_path.resolve()}') print(f'Picker path not exists: {picker_path.resolve()}')
return continue
print('Load picker from', picker_path.resolve()) 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'))
#shapes = [s.to_dict() for s in ob.data.rig_picker['shapes']] picker_datas.append(picker_data)
PICKERS[ob] = Picker(ob, shapes=picker_datas) #shapes = [s.to_dict() for s in ob.data.rig_picker['shapes']]
picker = PICKERS.get(ob) PICKERS[ob] = PickerGroup(ob, picker_datas)#[Picker(ob, shapes=picker_data) for picker_data in picker_datas]
picker.draw()
# 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(): def draw_callback_px():
sp = bpy.context.space_data sp = bpy.context.space_data
@ -42,23 +94,36 @@ def draw_callback_px():
ob = bpy.context.object ob = bpy.context.object
picker = PICKERS.get(ob) picker_group = PICKERS.get(ob)
if not picker or not picker.tooltip: if not picker_group or not picker_group.tooltip:
return 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) #print('Draw text', text)
font_id = 0 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.dimensions(font_id, text)
blf.enable(font_id, blf.SHADOW) blf.enable(font_id, blf.SHADOW)
#gpu.state.blend_set('ALPHA')
# BLF drawing routine # BLF drawing routine
blf.position(font_id, picker.tooltip_mouse[0]-5, picker.tooltip_mouse[1]+5, 0) blf.position(font_id, round(text_pos[0]), round(text_pos[1]), 0)
blf.size(font_id, 14)
blf.color(font_id, 1, 1, 1, 1) blf.color(font_id, 1, 1, 1, 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)
@ -67,7 +132,6 @@ def draw_callback_px():
blf.draw(font_id, text) blf.draw(font_id, text)
blf.disable(font_id, blf.SHADOW) blf.disable(font_id, blf.SHADOW)
#gpu.state.blend_set('NONE')
handle_view = None handle_view = None
handle_pixel = None handle_pixel = None

View File

@ -1,7 +1,8 @@
import bpy import bpy
from bpy.props import EnumProperty from bpy.props import EnumProperty, IntProperty
from ..constants import PICKERS from ..constants import PICKERS
from ..core.bl_utils import get_view_3d_override 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 ..core.picker import get_picker_path, pack_picker, unpack_picker
#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
@ -151,15 +152,8 @@ class RP_OT_box_select(bpy.types.Operator):
def modal(self, context, event): def modal(self, context, event):
self.mouse = event.mouse_region_x, event.mouse_region_y 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 = [ self.border = bounding_rect((self.start_mouse, self.mouse))
(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.bg_batch = batch_for_shader(self.color_shader, 'TRI_FAN', {"pos": self.border}) 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}) 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: if scn.rig_picker.use_pick_bone:
self.picker.assign_bone_event() 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) self.picker.border_select(self.border, self.mode)
else: else:
self.picker.move_event(self.mouse) self.picker.move_event(self.mouse)
@ -255,7 +249,7 @@ class RP_OT_function_execute(bpy.types.Operator):
bl_idname = "rigpicker.function_execute" bl_idname = "rigpicker.function_execute"
bl_label = 'Function Execute' bl_label = 'Function Execute'
shape_index = bpy.props.IntProperty() shape_index = IntProperty()
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@ -295,7 +289,7 @@ class RP_OT_reload_picker(bpy.types.Operator):
def execute(self,context): def execute(self,context):
PICKERS.clear() PICKERS.clear()
for a in context.screen.areas: for a in context.screen.areas:
a.tag_redraw() a.tag_redraw()
return {"FINISHED"} return {"FINISHED"}
@ -379,24 +373,35 @@ class RP_OT_pack_picker(bpy.types.Operator):
class RP_OT_call_operator(bpy.types.Operator): class RP_OT_call_operator(bpy.types.Operator):
bl_idname = "rigpicker.call_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 @classmethod
def poll(cls, context): def poll(cls, context):
return is_picker_space(context) return is_picker_space(context)
def execute(self, context): def execute(self, context):
if not self.operator.startswith('bpy.ops'):
print('CALL OPERATOR', self.operator) print("Operator call_operator can only execute operator")
return {"FINISHED"}
try:
exec(self.operator)
except Exception as e:
self.report({"ERROR"}, e)
context.region.tag_redraw() print('CAll Operator')
context.area.tag_redraw()
override = {}
if self.view_3d:
override = get_view_3d_override()
with context.temp_override(**override):
try:
exec(self.operator)
except Exception as e:
self.report({"ERROR"}, e)
context.area.tag_redraw()
return {"FINISHED"} 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("rigpicker.show_bone_layer", text="Show Bone Layer", ).type = 'ACTIVE'
#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_ui_draw(bpy.types.Operator):
bl_idname = "rigpicker.ui_draw"
bl_label = "Rig UI Draw"
_handle = None class RP_OT_add_picker_source(bpy.types.Operator):
tmp_ob = None bl_idname = "rigpicker.add_picker_source"
tmp_bones = [] bl_label = "Add a Picker source"
start = (0,0) bl_description = "Add a Picker source"
end = (0,0) bl_options = {'UNDO', 'REGISTER'}
border = ((0,0),(0,0),(0,0),(0,0))
is_border = False
press = False
scale = 1
offset = 0
outside_point = (-1,-1)
addon_keymaps = []
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 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: if self.index >= len(picker_group.pickers):
for i,shape in [(i,s) for i,s in enumerate(ob.data.rig_picker['shapes']) if s.get('function') and s.get('shortcut')]: return {"CANCELLED"}
km = addon.keymaps.new(name = 'Image Generic', space_type = 'IMAGE_EDITOR',region_type = 'WINDOW')
split = shape["shortcut"].split(' ') picker = picker_group.pickers[self.index]
if len(split)==1: view2d = context.region.view2d
shortcut = shape["shortcut"].upper()
modifier = None
else:
shortcut = split[1].upper()
modifier = split[0].lower()
kmi = km.keymap_items.new("rigpicker.function_execute", type = shortcut, value = "CLICK") if self.index == -1:
kmi.properties.shape_index = i #print('Picker Group')
picker = picker_group
if modifier: view_rect = [view2d.view_to_region(*co, clip=False) for co in picker.rect]
setattr(kmi,modifier,True) 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): view_offset = Vector(view_center) + (picker.center - Vector(view_center))
# Remove Shortcut region_offset = view2d.view_to_region(*view_offset, clip=False)
wm = bpy.context.window_manager
for km in self.addon_keymaps:
for kmi in km.keymap_items:
km.keymap_items.remove(kmi)
self.addon_keymaps.clear() delta = Vector(region_offset) - region_center
def modal(self, context, event): bpy.ops.view2d.pan(deltax=round(delta[0]), deltay=round(delta[1]))
inside = is_over_region(self,context,event)
if context.object and context.object.type == 'ARMATURE' and context.area: return {'FINISHED'}
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
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 = [] keymaps = []
def register_keymaps(): def register_keymaps():
@ -551,10 +509,16 @@ def register_keymaps():
km = wm.keyconfigs.addon.keymaps.new(name="Node Editor", space_type="NODE_EDITOR") 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") for i in range(10):
kmi.properties.operator = "bpy.ops.animtoolbox.reset_bone()" 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)) 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 = "bpy.ops.pose.select_all(action='SELECT')"
keymaps.append((km, kmi)) keymaps.append((km, kmi))
@ -581,6 +545,8 @@ def register_keymaps():
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")
keymaps.append((km, kmi)) keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS") kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS")
kmi.properties.mode = 'SET' kmi.properties.mode = 'SET'
keymaps.append((km, kmi)) 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", ctrl=True)
kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS", alt=True) kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS", alt=True)
kmi.properties.mode = 'SUBSTRACT' kmi.properties.mode = 'SUBSTRACT'
keymaps.append((km, kmi)) 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(): def unregister_keymaps():
for km, kmi in keymaps: for km, kmi in keymaps:
km.keymap_items.remove(kmi) km.keymap_items.remove(kmi)
@ -609,7 +580,10 @@ classes = (
RP_MT_context_menu, RP_MT_context_menu,
RP_OT_picker_transform, RP_OT_picker_transform,
RP_OT_context_menu_picker, 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 #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 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_label = 'Create UI shape'
bl_idname = 'rigpicker.create_shape' bl_idname = 'rigpicker.create_shape'
#bl_options = {'REGISTER', 'UNDO'} #bl_options = {'REGISTER', 'UNDO'}
@ -52,7 +55,7 @@ class RP_OT_create_shape(bpy.types.Operator):
return {'FINISHED'} 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_label = 'Name Shape from selected bones'
bl_idname = 'rigpicker.name_from_bone' bl_idname = 'rigpicker.name_from_bone'
#bl_options = {'REGISTER', 'UNDO'} #bl_options = {'REGISTER', 'UNDO'}
@ -69,67 +72,67 @@ class RP_OT_name_from_bone(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
class RP_OT_mirror_shape(bpy.types.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 = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return (context.object and context.object.type in ('MESH', 'CURVE', 'TEXT'))
def execute(self,context): def execute(self,context):
scn = context.scene 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) for mod in ob.modifiers:
link_mat_to_object(ob) if (mod.type == 'NODES' and mod.node_group.name == 'Symmetrize' and
mod.get('Socket_2') in objects):
bpy.data.objects.remove(ob)
if not name: if collection.rig_picker.symmetry:
name = ob.name + '_flip' x_axis = collection.rig_picker.symmetry.matrix_world.to_translation()[0]
else:
x_axis = 0
old_shape = bpy.data.objects.get(name) for ob in objects:
old_mat = None flipped_name = flip_name(ob.name)
if old_shape: if flipped_name == ob.name:
old_mat = next((sl.material for sl in old_shape.material_slots if sl.material), None) suffix = '.L'
bpy.data.objects.remove(old_shape) flipped_suffix = '.R'
# Determine side of the object
if ob.matrix_world.to_translation()[0] < x_axis:
suffix, flipped_suffix = flipped_suffix, suffix
flipped_name = f'{ob.name}{flipped_suffix}'
ob.name = f'{ob.name}{suffix}'
mirror_ob = ob.copy() flipped_object = ob.copy()
mirror_ob.data = ob.data flipped_object.name = flipped_name
mirror_ob.name = name flipped_object.parent = None
flipped_object.matrix_world = Matrix()
for mod in list(flipped_object.modifiers):
flipped_object.modifiers.remove(mod)
if old_mat: # Add symmetrize modifier
#mirror_ob.data.materials.clear() mod = flipped_object.modifiers.new(name='Symmetrize', type='NODES')
#mirror_ob.data.materials.append(None) mod.node_group = bpy.data.node_groups['Symmetrize']
#mirror_ob.material_slots[0].link = 'OBJECT' mod['Socket_2'] = ob
mod['Socket_3'] = collection.rig_picker.symmetry
mirror_ob.material_slots[0].material = old_mat for col in ob.users_collection:
#mirror_ob = bpy.data.objects.new(name,ob.data.copy()) col.objects.link(flipped_object)
for c in ob.users_collection:
c.objects.link(mirror_ob)
if scn.rig_picker.symmetry:
symmetry_loc = scn.rig_picker.symmetry.matrix_world.to_translation()[0]
else:
symmetry_loc = 0
#print(symmetry_loc)
mirror_ob.matrix_world = ob.matrix_world
#if mirror_ob.location[0] < symmetry_loc:
mirror_ob.location.x = symmetry_loc + (symmetry_loc - ob.location.x)
#else:
# mirror_ob.location[0]= symmetry_loc+ (symmetry_loc- ob.location[0])
mirror_ob.rotation_euler.y = -ob.rotation_euler.y
mirror_ob.rotation_euler.z = -ob.rotation_euler.z
mirror_ob.scale.x = -ob.scale.x
for key, value in ob.items():
if key not in ['_RNA_UI','cycles']:
mirror_ob[key] = value
if ob.rig_picker.shape_type == 'BONE': 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': elif ob.rig_picker.shape_type == 'FUNCTION':
args = {} args = {}
@ -137,18 +140,18 @@ class RP_OT_mirror_shape(bpy.types.Operator):
if type(value) == list: if type(value) == list:
mirrored_value = [] mirrored_value = []
for item in value: for item in value:
mirrored_value.append(find_mirror(item)) mirrored_value.append(flip_name(item))
elif type(value) == str: elif type(value) == str:
mirrored_value = find_mirror(value) mirrored_value = flip_name(value)
args[key] = mirrored_value args[key] = mirrored_value
mirror_ob.rig_picker.arguments = str(args) flipped_object.rig_picker.arguments = str(args)
return {'FINISHED'} 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_label = 'Select Shape by Type'
bl_idname = 'rigpicker.select_shape_type' bl_idname = 'rigpicker.select_shape_type'
#bl_options = {'REGISTER', 'UNDO'} #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) 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_label = 'Store UI Data'
bl_idname = 'rigpicker.save_picker' bl_idname = 'rigpicker.save_picker'
def execute(self, context): def execute(self, context):
scn = context.scene scn = context.scene
canvas = scn.rig_picker.canvas ob = context.object
rig = scn.rig_picker.rig collection = next((c for c in ob.users_collection if c.rig_picker.enabled), None)
shapes = [o for o in scn.objects if o != canvas and is_shape(o)]
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: if not rig:
self.report({'ERROR'}, 'Choose a Rig') self.report({'ERROR'}, 'Choose a Rig')
@ -196,9 +202,9 @@ class RP_OT_save_picker(bpy.types.Operator):
return {'CANCELLED'} 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)) picker_path.write_text(json.dumps(data))
bpy.ops.rigpicker.reload_picker() bpy.ops.rigpicker.reload_picker()

View File

@ -1,5 +1,7 @@
import bpy import bpy
from bpy.props import EnumProperty, StringProperty, PointerProperty, BoolProperty from bpy.props import (EnumProperty, StringProperty, PointerProperty, BoolProperty,
CollectionProperty, IntProperty)
import inspect import inspect
''' '''
@ -22,9 +24,15 @@ def bones_item(self,context):
items.append((bone.name,bone.name,'')) items.append((bone.name,bone.name,''))
return items return items
class RP_PG_picker_source(bpy.types.PropertyGroup):
source : StringProperty(subtype='FILE_PATH')
class RP_PG_armature_ui_settings(bpy.types.PropertyGroup): class RP_PG_armature_ui_settings(bpy.types.PropertyGroup):
name: StringProperty() name: StringProperty()
source: StringProperty(subtype='FILE_PATH') source: StringProperty(subtype='FILE_PATH')
sources: CollectionProperty(type=RP_PG_picker_source)
class RP_PG_object_ui_settings(bpy.types.PropertyGroup): 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): class RP_PG_scene_ui_settings(bpy.types.PropertyGroup):
enabled : BoolProperty(default=False)
rig: PointerProperty(type=bpy.types.Object) rig: PointerProperty(type=bpy.types.Object)
canvas: PointerProperty(type=bpy.types.Object) canvas: PointerProperty(type=bpy.types.Object)
symmetry: PointerProperty(type=bpy.types.Object) symmetry: PointerProperty(type=bpy.types.Object)
@ -67,6 +76,7 @@ class RP_OT_operator_selector(bpy.types.Operator):
classes = ( classes = (
RP_PG_picker_source,
RP_PG_object_ui_settings, RP_PG_object_ui_settings,
RP_PG_scene_ui_settings, RP_PG_scene_ui_settings,
RP_PG_armature_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.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.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.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(): def unregister():
del bpy.types.Scene.rig_picker 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)