Compare commits

...

10 Commits

Author SHA1 Message Date
christophe.seux 6798053389 Update README.md 2024-10-30 17:36:07 +01:00
christophe.seux de3b78e741 Update README.md 2024-10-30 17:35:16 +01:00
christophe.seux 76ea717df8 Update README.md 2024-10-30 17:34:17 +01:00
ChristopheSeux 7d2d69ba0f Fix pack picker if missing 2024-03-04 09:55:46 +01:00
ChristopheSeux 75bc414e26 bone space in context menu 2024-02-28 09:59:11 +01:00
ChristopheSeux 150bae5920 Add menu and some cleanup 2024-02-26 11:26:49 +01:00
ChristopheSeux 917531f59a fix mouse_under in picker_group 2024-02-20 13:48:24 +01:00
ChristopheSeux c31a73f80f Picker Group 2024-02-19 11:53:15 +01:00
florentin.luce b855e8ac37 Merge pull request 'replace layers with collections' (#1) from for_blender_v4 into master
Reviewed-on: #1
2024-02-08 12:13:11 +01:00
florentin.luce 7627b76d64 replace layers with collections 2023-11-21 11:40:15 +01:00
19 changed files with 1527 additions and 867 deletions

View File

@ -2,6 +2,8 @@
> Blender addon for picking rig contollers
Rig_picker is an OpenGl tool for having a 2d interface for the 3d animators allowing them to pick a controller easily.
The addon is drawing 2d shapes inside a dedicated Node Editor Area using the gpu module.
You can use multiple pickers for one rig, each picker shapes are in there own collection.
Video of the previous version : https://vimeo.com/241970235
@ -23,7 +25,12 @@ Video of the previous version : https://vimeo.com/241970235
<!-- INSTALLATION -->
## Installation
For external user, you can clone the repository using:
```sh
git clone https://git.autourdeminuit.com/autour_de_minuit/rig_picker.git
```
For Internal user:
1. Create your own local directory in
```sh
/home/<USER>/dev

View File

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

102
area.py
View File

@ -1,5 +1,6 @@
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
# Derived from the NodeTree base type, similar to Menu, Operator, Panel, etc.
@ -13,17 +14,78 @@ class RigPickerTree(NodeTree):
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):
if context.space_data.tree_type == 'RigPickerTree':
if not context.space_data.tree_type == 'RigPickerTree':
self._draw(context)
return
scn = context.scene
layout = self.layout
layout.template_header()
#layout.separator_spacer()
if not context.space_data.node_tree:
ntree = bpy.data.node_groups.get('.rig_picker')
@ -32,17 +94,36 @@ def draw_header(self, context):
context.space_data.node_tree = ntree
#layout.template_ID(context.space_data, "node_tree", new="node.new_node_tree")
#layout.separator_spacer()
row = layout.row(align=True)
row.menu("RP_MT_picker")
row.menu("RP_MT_animation")
layout.operator('rigpicker.reload_picker', icon='FILE_REFRESH', text='')
ob = context.object
if not ob or not ob.type == 'ARMATURE':
return
#layout.prop('rigpicker.reload_picker', icon='FILE_REFRESH', text='')
picker_group = PICKERS.get(ob)
if not picker_group:
return
if scn.rig_picker.use_pick_bone:
layout.alert = True
layout.label(text='Auto Bone Assign')
layout.prop(scn.rig_picker, 'use_pick_bone', icon='PANEL_CLOSE', text='', emboss=False)
layout.separator_spacer()
row = layout.row()
row.enabled = False
row.label(text=ob.name)
layout.separator_spacer()
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.operator('rigpicker.fit_picker', text='', icon='FULLSCREEN_ENTER').index = -1
else:
self._draw(context)
def tools_from_context(context, mode=None):
sp = context.space_data
@ -68,10 +149,13 @@ def poll(cls, context):
classes = (
RigPickerTree,
RP_MT_picker,
RP_MT_animation
)
def register():
# Remove the tools inside the picker space
bpy.types.WM_OT_tool_set_by_id._execute = bpy.types.WM_OT_tool_set_by_id.execute #tool_set_by_id
bpy.types.WM_OT_tool_set_by_id.execute = tool_set_by_id

View File

@ -1,2 +1,29 @@
import gpu
from pathlib import Path
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
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):
scn = bpy.context.scene
@ -15,6 +30,7 @@ def is_shape(ob):
return False
def get_object_color(ob):
if not ob.data.materials:
return
@ -29,6 +45,7 @@ def get_object_color(ob):
return emit_node.inputs['Color'].default_value
def get_operator_from_id(idname):
if not '.' in idname:
return

View File

@ -2,6 +2,34 @@
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():
windows = bpy.context.window_manager.windows
areas = [a for w in windows for a in w.screen.areas if a.type == 'VIEW_3D']
@ -31,40 +59,34 @@ def link_mat_to_object(ob):
sl.link = 'OBJECT'
sl.material = m
def find_mirror(name):
mirror = None
prop= False
def eval_attr(ob, name):
resolved = ob
for o in name.split("."):
resolved = getattr(resolved, o)
return resolved
if name:
if name.startswith('[')and name.endswith(']'):
prop = True
name= name[:-2][2:]
match={
'R': 'L',
'r': 'l',
'L': 'R',
'l': 'r',
}
separator=['.','_']
if name.startswith(tuple(match.keys())):
if name[1] in separator:
mirror = match[name[0]]+name[1:]
if name.endswith(tuple(match.keys())):
if name[-2] in separator:
mirror = name[:-1]+match[name[-1]]
if mirror and prop == True:
mirror='["%s"]'%mirror
return mirror
def flip_name(name):
if not name:
return
if name.startswith('[') and name.endswith(']'): #It's a custom property
flipped_name = bpy.utils.flip_name(name[:-2][2:])
return f'["{flipped_name}"]'
else:
return None
return bpy.utils.flip_name(name)
def 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):
""" """

View File

@ -21,15 +21,25 @@ def bound_box_center(ob):
return (sum(x) / len(points), sum(y) / len(points),sum(z) / len(points))
def bounding_rect(points):
x_points = sorted(p[0] for p in points)
y_points = sorted(p[1] for p in points)
def intersect_rectangles(bound, border): # returns None if rectangles don't intersect
dx = min(border[1][0],bound[1][0]) - max(border[0][0],bound[0][0])
dy = min(border[0][1],bound[0][1]) - max(border[2][1],bound[2][1])
return [
Vector((x_points[0], y_points[-1])),
Vector((x_points[-1], y_points[-1])),
Vector((x_points[-1], y_points[0])),
Vector((x_points[0], y_points[0])),
]
def intersect_rects(rect, border): # returns None if rectangles don't intersect
dx = min(border[1][0],rect[1][0]) - max(border[0][0],rect[0][0])
dy = min(border[0][1],rect[0][1]) - max(border[2][1],rect[2][1])
if (dx>=0) and (dy>=0):
return dx*dy
def point_inside_rectangle(point, rect):
def point_inside_rect(point, rect):
return rect[0][0]< point[0]< rect[1][0] and rect[2][1]< point[1]< rect[0][1]
def point_over_shape(point,verts,loops,outside_point=(-1,-1)):

View File

@ -3,10 +3,12 @@ import bpy
import gpu
from gpu_extras.batch import batch_for_shader
import blf
from mathutils import bvhtree, Vector
from mathutils.geometry import intersect_point_quad_2d, intersect_point_tri_2d, intersect_tri_tri_2d
from mathutils import bvhtree, Vector, Color
from mathutils.geometry import (intersect_point_quad_2d, intersect_point_tri_2d,
intersect_tri_tri_2d)
from ..constants import PICKERS
from .addon_utils import get_operator_from_id
from .geometry_utils import bounding_rect
from pathlib import Path
import re
@ -16,7 +18,6 @@ import os
import threading
class Shape:
def __init__(self, picker, points, polygons=None, edges=None, tooltip='', color=None, source_name=''):
@ -28,47 +29,37 @@ class Shape:
self.hover = False
self.press = False
self.shader = gpu.shader.from_builtin('UNIFORM_COLOR')
self.shader.bind()
self.shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
#self.hover_shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
#self.hover_shader = gpu.shader.from_builtin('UNIFORM_COLOR')
#self.hover_shader.uniform_float("color", [1, 1, 1, 0.1])
self.hover_color = [1, 1, 1, 0.1]
self.color = color
self.hover_color = self.brighten_color(self.color)
self.tooltip = tooltip
self.color = color
self.points = points
self.polygons = polygons or []
self.edges = edges or []
#print(points, self.polygons)
self.p_batch = batch_for_shader(self.shader, 'TRIS', {"pos": self.points}, indices=self.polygons)
self.e_batch = batch_for_shader(self.shader, 'LINES', {"pos": self.points}, indices=self.edges)
#if polygons:
# self.batch = batch_for_shader(self.shader, 'TRIS', {"pos": points}, indices=polygons)
#else:
#pts = []
#for loop in self.edges:
# pts += [self.points[i] for i in loop]
# self.batch = batch_for_shader(self.shader, 'LINES', {"pos": points}, indices=indices)
points_x = [v[0] for v in points]
points_y = [v[1] for v in points]
self.bound = [
(min(points_x), max(points_y)),
(max(points_x), max(points_y)),
(max(points_x), min(points_y)),
(min(points_x), min(points_y))
]
self.rect = bounding_rect(points)
@property
def color(self):
return self._color
def brighten_color(self, color):
brighten_color = Color(color[:3])
brighten_color.v += 0.05
brighten_color.v *= 1.1
brighten_color.v = max(brighten_color.v, 0.15)
return [*brighten_color, color[3]]
@color.setter
def color(self, color=None):
if not color:
@ -88,16 +79,17 @@ class Shape:
def draw(self):
self.shader.bind()
#self.shader.bind()
self.shader.uniform_float("color", self.color)
if self.polygons:
self.p_batch.draw(self.shader)
if self.edges:
self.e_batch.draw(self.shader)
def move_event(self, location):
if not intersect_point_quad_2d(location, *self.bound):
if not intersect_point_quad_2d(location, *self.rect):
self.hover = False
return False
@ -124,33 +116,9 @@ class BoneShape(Shape):
self.type = 'bone'
self.bone = bone
self.active_color = [1, 1, 1, 0.1]
#self.contour_shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
self.select_color = self.brighten_color(self.hover_color)
#self.contour_batches = []
#self.line_batch =
#for loop in edges:
# loop_points = [points[i] for i in loop]
# batch = batch_for_shader(self.shader, 'LINE_LOOP', {"pos": loop_points})
# #self.contour_batches.append(batch)
theme = bpy.context.preferences.themes['Default']
self.bone_colors = {
'select': [*theme.view_3d.bone_pose, 1],
'normal': [0.05, 0.05, 0.05, 1],
'active': [*theme.view_3d.bone_pose_active, 1],
'hide': [0.85, 0.85, 0.85, 0.2],
}
if bone and bone.bone_group:
normal_color = bone.bone_group.colors.normal.copy()
normal_color.s *= 0.75
self.bone_colors['normal'] = [*normal_color, 1]
self.bone_colors['select'] = [*bone.bone_group.colors.select, 1]
self.bone_colors['active'] = [*bone.bone_group.colors.active, 1]
self.bone_colors['hide'] = [*normal_color, 0.1]
#self.color = [i for i in self.color]
self.bone_colors = self.get_bone_colors()
@property
def select(self):
@ -172,20 +140,14 @@ class BoneShape(Shape):
return False
#return self.bone not in (bpy.context.visible_pose_bones or [])
bl = [i for i, l in enumerate(self.bone.bone.layers) if l]
rl = [i for i, l in enumerate(self.rig.data.layers) if l]
return self.bone.bone.hide or not len(set(bl).intersection(rl))
return self.bone.bone.hide or not any(l.is_visible for l in self.bone.bone.collections)
@property
def bone_color(self):
if not self.bone:
return [0, 0, 0, 1]
bone = self.bone.bone
bl = bone.layers
rl = self.rig.data.layers
if self.select and self.active:
return self.bone_colors['active']
elif self.select:
@ -195,58 +157,110 @@ class BoneShape(Shape):
else:
return self.bone_colors['normal']
def get_bone_colors(self):
theme = bpy.context.preferences.themes['Default']
bone_colors = {
'select': [*theme.view_3d.bone_pose, 1],
'normal': [0.05, 0.05, 0.05, 1],
'active': [*theme.view_3d.bone_pose_active, 1],
'hide': [0.85, 0.85, 0.85, 0.2],
}
if not self.bone:
return bone_colors
if self.bone.color.palette == 'CUSTOM':
bone_color = self.bone
elif self.bone.color.palette == 'DEFAULT':
bone_color = self.bone.bone
normal_color = bone_color.color.custom.normal.copy()
normal_color.s *= 0.75
bone_colors['normal'] = [*normal_color, 1]
bone_colors['select'] = [*bone_color.color.custom.select, 1]
bone_colors['active'] = [*bone_color.color.custom.active, 1]
bone_colors['hide'] = [*normal_color, 0.1]
return bone_colors
def draw_hided(self):
gpu.state.blend_set('ALPHA')
gpu.state.line_width_set(1.0)
line_color = (1, 1, 1, 0.1)
color = Color(self.color[:3])
if self.hover:
color.v += 0.05
color.v *= 1.1
line_color = (1, 1, 1, 0.3)
self.shader.uniform_float("color", (*color[:3], 0.5))
self.p_batch.draw(self.shader)
self.shader.uniform_float("color", line_color)
self.e_batch.draw(self.shader)
gpu.state.blend_set('NONE')
def draw_zero_scaled(self):
gpu.state.blend_set('ALPHA')
gpu.state.line_width_set(1.0)
line_color = (*self.bone_colors['normal'][:3], 0.2)
color = Color(self.color[:3])
if self.hover or self.select:
color.v += 0.05
color.v *= 1.1
line_color = (*self.bone_color[:3], 0.66)
self.shader.uniform_float("color", (*color[:3], 0.5))
self.p_batch.draw(self.shader)
self.shader.uniform_float("color", line_color)
self.e_batch.draw(self.shader)
gpu.state.blend_set('NONE')
def draw(self):
gpu.state.blend_set('ALPHA')
gpu.state.line_width_set(1.0)
if self.hide:
self.shader.uniform_float("color", (*self.color[:3], 0.4))
self.p_batch.draw(self.shader)
#elif self.select:
# self.shader.uniform_float("color", self.bone_color)
# self.p_batch.draw(self.shader)
else:
super().draw()
return self.draw_hided()
# Overlay the fill slightly with the bone color
#self.shader.uniform_float("color", (*self.bone_color[:3], 0.1))
#self.p_batch.draw(self.shader)
elif self.bone and self.bone.custom_shape_scale_xyz.length < 0.00001:
return self.draw_zero_scaled()
# Draw Fill
color = self.color
if self.select:
color = self.select_color
elif self.hover:
color = self.hover_color
if self.select or self.hover:
color = self.hover_color
if self.select and self.hover:
color = self.active_color
self.shader.uniform_float("color", color)
self.p_batch.draw(self.shader)
#Overlay the fill slightly with the bone color
self.shader.uniform_float("color", (*self.bone_colors['normal'][:3], 0.1))
self.p_batch.draw(self.shader)
#self.contour_shader.bind()
#print(self.bone_color)
# Draw Outline
gpu.state.blend_set('NONE')
if self.select or self.active:
gpu.state.line_width_set(2.0)
#if not self.hide:
self.shader.uniform_float("color", self.bone_color)
#for b in self.contour_batches:
self.e_batch.draw(self.shader)
gpu.state.line_width_set(1.0)
gpu.state.blend_set('NONE')
def assign_bone_event(self):
#print('assign_bone_event', self)
scn = bpy.context.scene
rig = scn.rig_picker.rig
rig = self.picker.rig
source_object = scn.objects.get(self.source_name)
if not source_object:
print(f'Source object {self.source_name} not found')
@ -257,6 +271,9 @@ class BoneShape(Shape):
print('You need to have an active bone')
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
def release_event(self, mode='SET'):
@ -277,11 +294,6 @@ class BoneShape(Shape):
def border_select(self, border, mode='SET'):
'''
if ( not any(intersect_point_quad_2d(b, *self.bound) for b in border) and
not any(intersect_point_quad_2d(b, *border) for b in self.bound) ):
return
'''
if not self.bone:
return
@ -289,16 +301,16 @@ class BoneShape(Shape):
self.bone.bone.select = False
return
bound_tri1 = self.bound[0], self.bound[1], self.bound[2]
bound_tri2 = self.bound[2], self.bound[3], self.bound[0]
rect_tri1 = self.rect[0], self.rect[1], self.rect[2]
rect_tri2 = self.rect[2], self.rect[3], self.rect[0]
border_tri1 = border[0], border[1], border[2]
border_tri2 = border[2], border[3], border[0]
if (not intersect_tri_tri_2d(*border_tri1, *bound_tri1) and
not intersect_tri_tri_2d(*border_tri1, *bound_tri2) and
not intersect_tri_tri_2d(*border_tri2, *bound_tri1) and
not intersect_tri_tri_2d(*border_tri2, *bound_tri2)):
if (not intersect_tri_tri_2d(*border_tri1, *rect_tri1) and
not intersect_tri_tri_2d(*border_tri1, *rect_tri2) and
not intersect_tri_tri_2d(*border_tri2, *rect_tri1) and
not intersect_tri_tri_2d(*border_tri2, *rect_tri2)):
return
select = True
@ -385,121 +397,94 @@ class OperatorShape(Shape):
class Picker:
def __init__(self, rig, shapes):
def __init__(self, parent, rig, shapes):
self.parent = parent
self.region = bpy.context.region
self.rig = rig
self.translation = Vector((0, 0))
self.shapes = []
self.box_select = None
self.hover_shape = None
self.shader = gpu.shader.from_builtin('2D_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
for s in shapes:
if not s['points']:
for shape_data in shapes:
if not shape_data['points']:
continue
if s['type'] in ('CANVAS', 'DISPLAY'):
if shape_data['type'] in ('CANVAS', 'DISPLAY'):
shape = Shape(
self,
points=s['points'],
polygons=s['polygons'],
edges=s['edges'],
color=s['color']
points=shape_data['points'],
polygons=shape_data['polygons'],
edges=shape_data['edges'],
color=shape_data['color']
)
elif s['type'] == 'BONE':
bone = rig.pose.bones.get(s['bone'])
elif shape_data['type'] == 'BONE':
bone = rig.pose.bones.get(shape_data['bone'])
#if not bone:
# print(f'Bone {s["bone"]} not exist')
# print(f'Bone {shape_data["bone"]} not exist')
# continue
shape = BoneShape(
self,
source_name=s['source_name'],
points=s['points'],
polygons=s['polygons'],
edges=s['edges'],
source_name=shape_data['source_name'],
points=shape_data['points'],
polygons=shape_data['polygons'],
edges=shape_data['edges'],
bone=bone,
color=s['color']
color=shape_data['color']
)
elif s['type'] == 'OPERATOR':
elif shape_data['type'] == 'OPERATOR':
shape = OperatorShape(
self,
source_name=s['source_name'],
points=s['points'],
polygons=s['polygons'],
operator=s['operator'],
color=s['color'],
tooltip=s['tooltip'],
source_name=shape_data['source_name'],
points=shape_data['points'],
polygons=shape_data['polygons'],
operator=shape_data['operator'],
color=shape_data['color'],
tooltip=shape_data['tooltip'],
)
self.shapes.append(shape)
def assign_bone_event(self):
for s in self.shapes:
if s.type=='bone' and s.hover:
s.assign_bone_event()
self.rect = bounding_rect([p for s in self.shapes for p in s.points])
bpy.ops.rigpicker.save_picker()
def assign_bone_event(self):
for shape in self.shapes:
if shape.type == 'bone' and shape.hover:
shape.assign_bone_event()
bpy.ops.rigpicker.save_picker(index=self.index)
def press_event(self, mode='SET'):
for s in self.shapes:
if s.hover:
s.press_event(mode)
for shape in self.shapes:
#print(s)
if shape.hover:
shape.press_event(mode)
else:
s.press = False
shape.press = False
def release_event(self, mode='SET'):
if mode == 'SET':
for b in self.rig.pose.bones:
b.bone.select = False
#bpy.ops.pose.select_all(action='DESELECT')
#print('PICKER release event', mode)
#print(f'type={event.type}, value={event.value}, ctrl={event.ctrl}, shift={event.shift}, alt={event.alt}')
for s in self.shapes:
if s.hover:
s.release_event(mode)
for shape in self.shapes:
if shape.hover:
shape.release_event(mode)
s.press = False
shape.press = False
#bpy.context.area.tag_redraw()
def tooltip_event(self):
#print('Tooltip Event', self)
#print(self.hover_shape, self.hover_shape.type)
if self.hover_shape and self.hover_shape.type != 'display':
if self.hover_shape.type == 'bone':
self.tooltip = self.hover_shape.bone.name
else:
self.tooltip = self.hover_shape.tooltip
self.tooltip_shape = self.hover_shape
else:
self.tooltip = ''
self.tooltip_shape = None
self.tooltip_mouse = self.mouse
self.timer.cancel()
#print(self.tooltip)
self.region.tag_redraw()
'''
picker.tooltip_event(event='SHOW')
region.tag_redraw()
@ -526,27 +511,27 @@ class Picker:
'''
def border_select(self, border, mode):
border = [bpy.context.region.view2d.region_to_view(*b) for b in border]
border = [Vector(b) - Vector(self.translation) for b in border]
if mode == 'SET':
for b in self.rig.pose.bones:
b.bone.select = False
for shape in self.shapes:
shape.press = False
shape.hover = False
if shape.type != 'bone':
continue
for s in (s for s in self.shapes if s.type=='bone'):
s.border_select(border, mode)
shape.border_select(border, mode)
def move_event(self, location):
self.mouse = location
location = self.region.view2d.region_to_view(*location)
self.mouse = Vector(location) - Vector(self.translation)
self.hover_shape = None
for shape in reversed(self.shapes):
if self.hover_shape:
shape.hover = False
elif shape.move_event(location):
elif shape.move_event(self.mouse):
self.hover_shape = shape
#if point_inside_rectangle(self.end, bound):
#if point_inside_rect(self.end, rect):
# over = point_over_shape(self.end,points, edges)
#if bpy.app.timers.is_registered(self.tooltip_event):
@ -554,57 +539,221 @@ class Picker:
# bpy.app.timers.unregister(self.tooltip_event)
#except:
# pass
#bpy.app.timers.register(self.tooltip_event, first_interval=1)
def draw(self):
with gpu.matrix.push_pop():
gpu.matrix.translate(self.translation)
for shape in self.shapes:
shape.draw()
def under_mouse(self, location):
location = Vector(location) - Vector(self.translation)
return intersect_point_quad_2d(location, *self.rect)
@property
def center(self):
relative_center = (self.rect[1] + self.rect[3]) * 0.5
return relative_center + self.translation
@property
def index(self):
return self.parent.pickers.index(self)
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.location = 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, 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
self.location = self.region.view2d.region_to_view(*mouse)
# Try to detect view pan to remove tooltip
# 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(self.location):
continue
picker.move_event(self.location)
if picker.hover_shape:
hover_shape = picker.hover_shape
self.hover_shape = hover_shape
if self.tooltip_shape is not self.hover_shape:
self.tooltip = ''
if self.timer:
self.timer.cancel()
self.timer = threading.Timer(0.5, self.tooltip_event)
self.timer = threading.Timer(0.4, self.tooltip_event)
self.timer.start()
def press_event(self, mode='SET'):
#self.clear_tooltip()
#bpy.app.timers.register(self.tooltip_event, first_interval=1)
for picker in self.pickers:
if picker.under_mouse(self.location):
picker.press_event(mode)
else:
for shape in picker.shapes:
shape.press = False
def draw(self):
for s in self.shapes:
s.draw()
'''
if self.box_select:
def release_event(self, mode='SET'):
if mode == 'SET':
for bone in self.rig.data.bones:
bone.select = False
self.box_shader.uniform_float("color", self.box_select_color)
batch = batch_for_shader(self.shader, 'LINE_LOOP', {"pos": []})
for picker in self.pickers:
if picker.under_mouse(self.location):
picker.release_event(mode)
else:
for shape in picker.shapes:
shape.press = False
def assign_bone_event(self):
for picker in self.pickers:
if picker.under_mouse(self.location):
picker.assign_bone_event()
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' and self.hover_shape.bone:
self.tooltip = self.hover_shape.bone.name
else:
self.tooltip = self.hover_shape.tooltip
self.tooltip_shape = self.hover_shape
else:
return self.clear_tooltip()
for b in self.contour_batches:
b.draw(self.contour_shader)
self.tooltip_mouse = self.mouse
self.batch.draw(self.shader)
self.timer.cancel()
#print(self.tooltip)
self.region.tag_redraw()
@property
def rect(self):
return bounding_rect([co - Vector(p.translation) for p in self.pickers for co in p.rect])
@property
def center(self):
center = sum(self.rect, Vector((0, 0))) / len(self.rect)
center[1] = -center[1]
return center
gpu.state.blend_set('NONE')
'''
def load_picker_data(rig):
if 'pickers' in rig.data.rig_picker:
picker_datas = [[s.to_dict() for s in p] for p in rig.data.rig_picker['pickers']]
else:
picker_datas = []
def get_picker_path(rig, start=None):
picker_path = rig.data.get('rig_picker', {}).get('source')
if not picker_path:
return
for picker in rig.data.rig_picker.sources:
picker_path = Path(bpy.path.abspath(picker.source, library=rig.data.library))
picker_path = bpy.path.abspath(picker_path, library=rig.data.library, start=start)
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))
def pack_picker(rig, start=None):
picker_path = get_picker_path(rig, start=start)
if picker_path and picker_path.exists():
if 'rig_picker' not in rig.data.keys():
rig.data['rig_picker'] = {}
rig.data['rig_picker']['picker'] = json.loads(picker_path.read_text())
def pack_picker(rig, start=None):
if not 'rig_picker' in rig.data:
return
pickers = []
for picker_source in rig.data['rig_picker'].get('sources', []):
picker_path = get_picker_path(rig, picker_source['source'], start)
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']['pickers'] = pickers
def unpack_picker(rig):
if 'rig_picker' not in rig.data.keys():
return
if 'picker' in rig.data['rig_picker'].keys():
del rig.data['rig_picker']['picker']
if 'pickers' in rig.data['rig_picker'].keys():
del rig.data['rig_picker']['pickers']

View File

@ -20,7 +20,7 @@ def border_over_shape(border,verts,loops):
return True
for point in verts:
if point_inside_rectangle(point,border):
if point_inside_rect(point,border):
return True
for point in border:
@ -29,7 +29,7 @@ def border_over_shape(border,verts,loops):
def border_loop(vert, loop):
border_edge =[e for e in vert.link_edges if e.is_boundary]
border_edge =[e for e in vert.link_edges if e.is_rectary]
if border_edge:
for edge in border_edge:
@ -61,67 +61,50 @@ def contour_loops(bm, vert_index=0, loops=None, vert_indices=None):
return loops
def get_picker_datas(objects, canvas, rig):
picker_datas = []
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
if canvas.type =='CURVE':
canvas_points = canvas.data.splines[0].points
else:
canvas_points = canvas.data.vertices
canvas_coords = [canvas.matrix_world@Vector((p.co)) for p in canvas_points]
canvas_x = [p[0] for p in canvas_coords]
canvas_y = [p[1] for p in canvas_coords]
canvas_center = sum(canvas_coords, Vector()) / len(canvas_coords)
canvas_scale_fac = 4096 / (max(canvas_y) - min(canvas_y))# Reference height for the canvas
objects.append(canvas)
dg = bpy.context.evaluated_depsgraph_get()
#sorted by their z axes
for ob in sorted(objects, key=lambda x: bound_box_center(x)[2]):
print('Storing shape', ob.name)
mesh = bpy.data.meshes.new_from_object(ob.evaluated_get(dg))
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)
#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 = []
depths = []
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 vert in mesh.vertices:
co = matrix @ (ob.matrix_world @ Vector(vert.co))
points.append([round(co[0], 1), round(co[1], 1)])
for f in mesh.polygons:
polygons.append([v for v in f.vertices])
depths += [co[2]]
#for e in mesh.edges:
#
# edges.append([v for v in e.vertices])
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:
@ -132,28 +115,65 @@ def get_picker_datas(objects, canvas, rig):
shape = {
'source_name': ob.name,
'tooltip': ob.rig_picker.name,
'depth': depth,
'points': points,
'polygons': polygons,
'edges': edges,
'loops': loops,
'color': color,
'type': 'CANVAS' if ob == canvas else ob.rig_picker.shape_type
'type': 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)
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
else:
canvas_points = canvas.data.vertices
canvas_coords = [canvas.matrix_world@Vector((p.co)) for p in canvas_points]
height = abs(max(canvas_coords).y - min(canvas_coords).y)
width = abs(max(canvas_coords).x - min(canvas_coords).x)
center = sum(canvas_coords, Vector()) / len(canvas_coords)
scale = 2048 / max(height, width)# Reference height for the canvas
matrix = Matrix.Translation(-center)
matrix = Matrix.Scale(scale, 4) @ matrix
#sorted by their z axes
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))
else:
#print(f'{ob.name} of type {ob.type} not supported')
continue
picker_data.sort(key=lambda x : x['depth'])
#print(picker_datas)
return picker_datas
return picker_data
#rig.data.rig_picker['shapes'] = picker_datas

View File

@ -1,39 +1,63 @@
import bpy
import gpu
from gpu_extras.batch import batch_for_shader
import blf
from pathlib import Path
from .constants import PICKERS
from .core.picker import Picker
from .core.picker import Picker, PickerGroup, load_picker_data
import json
def draw_callback_view():
sp = bpy.context.space_data
def draw_rect_2d(position, width, height, color):
"""
Draw a 2d rectangele.
if not sp or not sp.tree_type == 'RigPickerTree':
: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():
space_data = bpy.context.space_data
if not space_data or not space_data.tree_type == 'RigPickerTree':
return
# Use the pin to know if this is the first time this picker window in created to hide the n panel
if not space_data.pin:
space_data.pin = True
space_data.show_region_ui = False
ob = bpy.context.object
if not ob or ob.type !='ARMATURE' or not ob.data.rig_picker.source:
if not ob or ob.type !='ARMATURE' or not ob.data.rig_picker.sources:
return
if ob not in PICKERS:
if 'picker' in ob.data.rig_picker:
picker_datas = [s.to_dict() for s in ob.data.rig_picker['picker']]
else:
picker_path = Path(bpy.path.abspath(ob.data.rig_picker.source, library=ob.data.library))
load_picker_data(ob)
if not picker_path.exists():
print(f'Picker path not exists: {picker_path.resolve()}')
return
picker_group = PICKERS[ob]
picker_group.draw()
print('Load picker from', picker_path.resolve())
picker_datas = json.loads(picker_path.read_text())
#shapes = [s.to_dict() for s in ob.data.rig_picker['shapes']]
PICKERS[ob] = Picker(ob, shapes=picker_datas)
picker = PICKERS.get(ob)
picker.draw()
def draw_callback_px():
sp = bpy.context.space_data
@ -42,24 +66,37 @@ def draw_callback_px():
ob = bpy.context.object
picker = PICKERS.get(ob)
if not picker or not picker.tooltip:
picker_group = PICKERS.get(ob)
if not picker_group or not picker_group.tooltip:
return
text = picker.tooltip
region = bpy.context.region
text = picker_group.tooltip
ui_scale = bpy.context.preferences.system.ui_scale
#print('Draw text', text)
font_id = 0
blf.size(font_id, int(13 * ui_scale))
margins = [12, 5]
text_size = blf.dimensions(font_id, text)
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_size = (text_size[0] + 2*margins[0], text_size[1] + 2*margins[1])
gpu.state.blend_set('ALPHA')
draw_rect_2d(bg_pos, *bg_size, (0.1, 0.1, 0.1, 0.66))
gpu.state.blend_set('NONE')
#blf.dimensions(font_id, text)
blf.enable(font_id, blf.SHADOW)
#gpu.state.blend_set('ALPHA')
# BLF drawing routine
blf.position(font_id, picker.tooltip_mouse[0]-5, picker.tooltip_mouse[1]+5, 0)
blf.size(font_id, 14)
blf.color(font_id, 1, 1, 1, 1)
blf.position(font_id, round(text_pos[0]), round(text_pos[1]), 0)
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_offset(font_id, 2, -2)
@ -67,7 +104,6 @@ def draw_callback_px():
blf.draw(font_id, text)
blf.disable(font_id, blf.SHADOW)
#gpu.state.blend_set('NONE')
handle_view = None
handle_pixel = None

View File

@ -68,6 +68,8 @@ class RP_GT_gizmo(Gizmo):
context.region.tag_redraw()
#print(location)
return -1

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,18 @@
import bpy
from ..core.shape import get_picker_datas
from ..core.addon_utils import is_shape
from ..core.bl_utils import link_mat_to_object, find_mirror
from pathlib import Path
import json
from os.path import abspath
from pathlib import Path
import bpy
from bpy.types import Operator
from bpy.props import IntProperty
from mathutils import Matrix
from ..core.shape import get_picker_data
from ..core.addon_utils import is_shape, get_picker_collection
from ..core.bl_utils import link_mat_to_object, flip_name
class RP_OT_create_shape(bpy.types.Operator):
class RP_OT_create_shape(Operator):
bl_label = 'Create UI shape'
bl_idname = 'rigpicker.create_shape'
#bl_options = {'REGISTER', 'UNDO'}
@ -52,7 +57,7 @@ class RP_OT_create_shape(bpy.types.Operator):
return {'FINISHED'}
class RP_OT_name_from_bone(bpy.types.Operator):
class RP_OT_name_from_bone(Operator):
bl_label = 'Name Shape from selected bones'
bl_idname = 'rigpicker.name_from_bone'
#bl_options = {'REGISTER', 'UNDO'}
@ -69,67 +74,67 @@ class RP_OT_name_from_bone(bpy.types.Operator):
return {'FINISHED'}
class RP_OT_mirror_shape(bpy.types.Operator):
class RP_OT_mirror_shape(Operator):
bl_label = 'Mirror UI shape'
bl_idname = 'rigpicker.mirror_shape'
#bl_options = {'REGISTER', 'UNDO'}
#bl_options = g
@classmethod
def poll(cls, context):
return (context.object and context.object.type in ('MESH', 'CURVE', 'TEXT'))
def execute(self,context):
scn = context.scene
ob = context.object
collection = get_picker_collection(ob)
objects = context.selected_objects
for ob in bpy.context.selected_objects:
# Remove mirror object:
for ob in list(collection.all_objects):
if ob in objects:
continue
name = find_mirror(ob.name)
link_mat_to_object(ob)
for mod in ob.modifiers:
if (mod.type == 'NODES' and mod.node_group.name == 'Symmetrize' and
mod.get('Socket_2') in objects):
if not name:
name = ob.name + '_flip'
bpy.data.objects.remove(ob)
old_shape = bpy.data.objects.get(name)
old_mat = None
if old_shape:
old_mat = next((sl.material for sl in old_shape.material_slots if sl.material), None)
bpy.data.objects.remove(old_shape)
mirror_ob = ob.copy()
mirror_ob.data = ob.data
mirror_ob.name = name
if old_mat:
#mirror_ob.data.materials.clear()
#mirror_ob.data.materials.append(None)
#mirror_ob.material_slots[0].link = 'OBJECT'
mirror_ob.material_slots[0].material = old_mat
#mirror_ob = bpy.data.objects.new(name,ob.data.copy())
for c in ob.users_collection:
c.objects.link(mirror_ob)
if scn.rig_picker.symmetry:
symmetry_loc = scn.rig_picker.symmetry.matrix_world.to_translation()[0]
if collection.rig_picker.symmetry:
x_axis = collection.rig_picker.symmetry.matrix_world.to_translation()[0]
else:
symmetry_loc = 0
x_axis = 0
#print(symmetry_loc)
mirror_ob.matrix_world = ob.matrix_world
for ob in objects:
flipped_name = flip_name(ob.name)
if flipped_name == ob.name:
suffix = '.L'
flipped_suffix = '.R'
# Determine side of the object
if ob.matrix_world.to_translation()[0] < x_axis:
suffix, flipped_suffix = flipped_suffix, suffix
#if mirror_ob.location[0] < symmetry_loc:
mirror_ob.location.x = symmetry_loc + (symmetry_loc - ob.location.x)
#else:
# mirror_ob.location[0]= symmetry_loc+ (symmetry_loc- ob.location[0])
flipped_name = f'{ob.name}{flipped_suffix}'
ob.name = f'{ob.name}{suffix}'
mirror_ob.rotation_euler.y = -ob.rotation_euler.y
mirror_ob.rotation_euler.z = -ob.rotation_euler.z
flipped_object = ob.copy()
flipped_object.name = flipped_name
flipped_object.parent = None
flipped_object.matrix_world = Matrix()
for mod in list(flipped_object.modifiers):
flipped_object.modifiers.remove(mod)
mirror_ob.scale.x = -ob.scale.x
# Add symmetrize modifier
mod = flipped_object.modifiers.new(name='Symmetrize', type='NODES')
mod.node_group = bpy.data.node_groups['Symmetrize']
mod['Socket_2'] = ob
mod['Socket_3'] = collection.rig_picker.symmetry
for key, value in ob.items():
if key not in ['_RNA_UI','cycles']:
mirror_ob[key] = value
for col in ob.users_collection:
col.objects.link(flipped_object)
if ob.rig_picker.shape_type == 'BONE':
mirror_ob.rig_picker.name = find_mirror(ob.rig_picker.name)
flipped_object.rig_picker.name = flip_name(ob.rig_picker.name)
elif ob.rig_picker.shape_type == 'FUNCTION':
args = {}
@ -137,18 +142,18 @@ class RP_OT_mirror_shape(bpy.types.Operator):
if type(value) == list:
mirrored_value = []
for item in value:
mirrored_value.append(find_mirror(item))
mirrored_value.append(flip_name(item))
elif type(value) == str:
mirrored_value = find_mirror(value)
mirrored_value = flip_name(value)
args[key] = mirrored_value
mirror_ob.rig_picker.arguments = str(args)
flipped_object.rig_picker.arguments = str(args)
return {'FINISHED'}
class RP_OT_select_shape_type(bpy.types.Operator):
class RP_OT_select_shape_type(Operator):
bl_label = 'Select Shape by Type'
bl_idname = 'rigpicker.select_shape_type'
#bl_options = {'REGISTER', 'UNDO'}
@ -177,33 +182,46 @@ class RP_OT_select_shape_type(bpy.types.Operator):
return wm.invoke_props_dialog(self, width=150)
class RP_OT_save_picker(bpy.types.Operator):
class RP_OT_save_picker(Operator):
bl_label = 'Store UI Data'
bl_idname = 'rigpicker.save_picker'
index : IntProperty(default=-1)
def execute(self, context):
scn = context.scene
canvas = scn.rig_picker.canvas
rig = scn.rig_picker.rig
shapes = [o for o in scn.objects if o != canvas and is_shape(o)]
ob = context.object
if not rig:
self.report({'ERROR'}, 'Choose a Rig')
if self.index == -1:
collection = get_picker_collection(ob)
else:
source = context.active_object.data.rig_picker.sources[self.index].source
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
and Path(bpy.path.abspath(c.rig_picker.destination)).resolve() == source), None)
if not collection:
self.report({"ERROR"}, 'No Picker found')
return {'CANCELLED'}
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 canvas:
self.report({'ERROR'}, 'Choose a Canvas')
return {'CANCELLED'}
data = get_picker_datas(shapes, canvas, rig)
picker_data = get_picker_data(collection)
picker_path = Path(bpy.path.abspath(scn.rig_picker.destination))
picker_path.write_text(json.dumps(data))
picker_path = Path(bpy.path.abspath(collection.rig_picker.destination))
print(f'Save Picker to {picker_path}')
picker_path.write_text(json.dumps(picker_data))
bpy.ops.rigpicker.reload_picker()
return {'FINISHED'}
classes = (

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)

View File

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

Binary file not shown.

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;
}

144
ui.py Normal file
View File

@ -0,0 +1,144 @@
import bpy
from bpy.types import UIList
#import collections
#import inspect
from .core.addon_utils import get_operator_from_id, get_picker_collection
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_header_preset(self, context):
self.layout.operator('rigpicker.add_picker_collection', text="", icon='ADD', emboss=False)
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:
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 = ('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.add_picker_source", icon ='ADD', text="", emboss=False)
for i, item in enumerate(ob.data.rig_picker.sources):
row = col.row(align=True)
row.enabled = not is_packed
row.prop(item, 'source', text='')
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 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')
elif 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')
classes = (
#RP_UL_picker_source,
RP_PT_picker_maker_panel,
RP_PT_shape
)
register, unregister = bpy.utils.register_classes_factory(classes)