First Commit

blender3.6
ChristopheSeux 2022-04-06 10:12:32 +02:00
parent 5efad05dcb
commit 2d16d82c99
18 changed files with 3025 additions and 88 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.pyc
*.pyc

View File

@ -1,92 +1,7 @@
# rig_picker # rig_picker
An OpenGl tool for having a 2d interface for the 3d animators
## Getting started Previous Version tutorial:
https://vimeo.com/241970235
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
## Add your files
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin https://gitlab.com/autour-de-minuit/blender/rig_picker.git
git branch -M main
git push -uf origin main
```
## Integrate with your tools
- [ ] [Set up project integrations](https://gitlab.com/autour-de-minuit/blender/rig_picker/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.

48
__init__.py Normal file
View File

@ -0,0 +1,48 @@
bl_info = {
"name": "Rig Picker",
"author": "Christophe Seux",
"version": (0, 1),
"blender": (3, 0, 1),
"location": "",
"description": "",
"warning": "",
"wiki_url": "",
"tracker_url": "",
"category": "Rigging"}
import importlib
modules = [
'.op_material',
'.op_picker',
'.op_shape',
'.properties',
'.panels',
'.area',
'.gizmo',
'.picker'
]
functions = [
'.func_picker',
'.func_shape',
'.snapping_utils',
'.utils'
]
import bpy
if "bpy" in locals():
for name in modules + functions:
module = importlib.import_module(name, __name__)
importlib.reload(module)
def register():
for name in modules:
module = importlib.import_module(name, __name__)
module.register()
def unregister():
for name in reversed(modules):
module = importlib.import_module(name, __name__)
module.unregister()

289
_draw_handler.py Normal file
View File

@ -0,0 +1,289 @@
import bpy
import bgl
import blf
from mathutils import Vector
from .func_picker import *
from .utils import intersect_rectangles, point_inside_rectangle,\
point_over_shape, border_over_shape, canvas_space
from gpu_extras.batch import batch_for_shader
from .constants import PICKERS
from .picker import Picker
"""
def draw_polygon_2d(verts, indices, loops, color, contour, width=None):
dpi = int(bpy.context.user_preferences.system.pixel_size)
bgl.glColor4f(*color)
bgl.glEnable(bgl.GL_BLEND)
#bgl.glEnable(bgl.GL_LINE_SMOOTH)
bgl.glColor4f(*color)
for face in faces:
bgl.glBegin(bgl.GL_POLYGON)
for v_index in face:
coord = verts[v_index]
bgl.glVertex2f(coord[0],coord[1])
bgl.glEnd()
if width:
bgl.glLineWidth(width*dpi)
bgl.glColor4f(*contour)
for loop in loops:
if faces:
bgl.glBegin(bgl.GL_LINE_LOOP)
else:
bgl.glBegin(bgl.GL_LINE_STRIP)
for v_index in loop:
coord = verts[v_index]
bgl.glVertex2f(coord[0],coord[1])
bgl.glEnd()
bgl.glDisable(bgl.GL_BLEND)
#bgl.glDisable(bgl.GL_LINE_SMOOTH)
bgl.glEnd()
return
def draw_border(border):
bgl.glEnable(bgl.GL_BLEND)
bgl.glColor4f(1,1,1,0.2)
bgl.glBegin(bgl.GL_POLYGON)
for v in border:
bgl.glVertex2f(v[0],v[1])
bgl.glEnd()
bgl.glColor4f(0.0, 0.0, 0.0, 0.5)
bgl.glLineWidth(2)
bgl.glLineStipple(3, 0xAAAA)
bgl.glEnable(bgl.GL_LINE_STIPPLE)
bgl.glBegin(bgl.GL_LINE_LOOP)
for v in border:
bgl.glVertex2f(v[0],v[1])
bgl.glEnd()
bgl.glColor4f(1.0, 1.0, 1.0, 1)
bgl.glLineWidth(1)
bgl.glBegin(bgl.GL_LINE_LOOP)
for v in border:
bgl.glVertex2f(v[0],v[1])
bgl.glEnd()
bgl.glDisable(bgl.GL_LINE_STIPPLE)
bgl.glDisable(bgl.GL_BLEND)
def draw_text(mouse,text,color):
dpi = int(bpy.context.user_preferences.system.pixel_size)
bgl.glEnable(bgl.GL_BLEND)
font_id =0 # XXX, need to find out how best to get this.
# draw some text
bgl.glColor4f(0,0,0,0.75)
blf.blur(font_id,5)
blf.position(font_id, mouse[0]+10*dpi, mouse[1]-20*dpi, 0)
blf.size(font_id, 9*dpi, 96)
blf.draw(font_id, text)
bgl.glEnd()
bgl.glColor4f(*color)
blf.blur(font_id,0)
blf.draw(font_id, text)
bgl.glDisable(bgl.GL_BLEND)
def select_bone(self,context,event):
ob = context.object
if ob and ob.type =='ARMATURE' and ob.data.rig_picker:
shape_data = ob.data.rig_picker
selected_bones = [b for b in ob.pose.bones if b.bone.select]
if not event.shift and not event.alt:
for b in ob.pose.bones:
b.bone.select= False
for shape in [s for s in shape_data['shapes'] if not s['shape_type']==["DISPLAY"]]:
points = [canvas_space(p,self.scale,self.offset) for p in shape['points']]
bound = [canvas_space(p,self.scale,self.offset) for p in shape['bound']]
loops = shape['loops']
## Colision check
over = False
if self.is_border:
if intersect_rectangles(self.border,bound): #start check on over bound_box
over = border_over_shape(self.border,points,loops)
else:
if point_inside_rectangle(self.end,bound):
over = point_over_shape(self.end,points,loops)
if over:
if shape['shape_type'] == 'BONE':
bone = context.object.pose.bones.get(shape['bone'])
if bone:
if event.alt:
bone.bone.select = False
else:
bone.bone.select = True
context.object.data.bones.active = bone.bone
if shape['shape_type'] == 'FUNCTION' and event.value== 'RELEASE' and not self.is_border:
# restore selection
for b in selected_bones:
b.bone.select = True
function = shape['function']
if shape.get('variables'):
variables=shape['variables'].to_dict()
else:
variables={}
variables['event']=event
print(variables)
globals()[function](variables)
"""
def draw_callback_view():
ob = bpy.context.object
if not ob or ob.type !='ARMATURE' or 'shapes' not in ob.data.rig_picker.keys():
return
if ob not in PICKERS:
shapes = [s.to_dict() for s in ob.data.rig_picker['shapes']]
PICKERS[ob] = Picker(ob, shapes=shapes)
picker = PICKERS.get(ob)
picker.draw()
handle_view = None
handle_pixel = None
def register():
global handle_view, handle_pixel
handle_view = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_view, (), 'WINDOW', 'POST_VIEW')
handle_pixel = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_px, (), 'WINDOW', 'POST_PIXEL')
def unregister():
global handle_view, handle_pixel
bpy.types.SpaceNodeEditor.draw_handler_remove(handle_view, 'WINDOW')
bpy.types.SpaceNodeEditor.draw_handler_remove(handle_pixel, 'WINDOW')
handle_view = None
handle_pixel = None
'''
return
shape_data = ob.data.rig_picker
rig_layers = [i for i,l in enumerate(ob.data.layers) if l]
r = context.region
#self.scale = region.height
#draw BG
canvas = shape_data['shapes'][0]
#bg_point = [(0, r.height), (r.width, r.height), (r.width, 0),(0, 0)]
bg_color = [*canvas['color'], 1]
draw_polygon_2d(bg_point,[[0,1,2,3]],[[0,1,2,3]], bg_color,(0,0,0,1),0)
show_tooltip = False
for shape in shape_data['shapes']:
color = shape['color']
points = [canvas_space(p,self.scale,self.offset) for p in shape['points']]
bound = [canvas_space(p,self.scale,self.offset) for p in shape['bound']]
loops = shape['loops']
faces = shape['faces']
select=None
contour_color = [0,0,0]
contour_alpha = 1
width = 0
shape_color = [c for c in color]
shape_alpha = 1
if shape['shape_type'] == 'DISPLAY' and not faces:
width = 1
if shape['shape_type'] != 'DISPLAY':
if shape['shape_type'] == 'BONE':
bone = ob.pose.bones.get(shape['bone'])
if bone:
b_layers = [i for i,l in enumerate(bone.bone.layers) if l]
if bone.bone_group:
group_color = list(bone.bone_group.colors.normal)
contour_color = [c*0.85 for c in group_color]
width = 1
if bone.bone.select :
shape_color = [c*1.2+0.1 for c in color]
if bone.bone_group:
contour_color = [0.05,0.95,0.95]
if ob.data.bones.active and shape['bone'] == ob.data.bones.active.name:
if bone.bone_group:
if bone.bone.select :
shape_color = [c*1.2+0.2 for c in color]
contour_color = [1,1,1]
width = 1.5
else:
shape_color = [c*1.2+0.15 for c in color]
contour_color = [0.9,0.9,0.9]
width = 1
if bone.bone.hide or not len(set(b_layers).intersection(rig_layers)):
shape_alpha = 0.33
contour_alpha = 0.33
elif shape['shape_type'] == 'FUNCTION':
if shape['function'] == 'boolean':
path = shape['variables']['data_path']
if ob.path_resolve(path):
shape_color = [c*1.4+0.08 for c in color]
else:
shape_color = [color[0],color[1],color[2]]
## On mouse over checking
over = False
if self.is_border:
if intersect_rectangles(self.border,bound): #start check on over bound_box
over = border_over_shape(self.border,points,loops)
else:
if point_inside_rectangle(self.end,bound):
over = point_over_shape(self.end,points,loops)
if over:
show_tooltip = True
tooltip = shape['tooltip']
if not self.press:
shape_color = [c*1.02+0.05 for c in shape_color]
contour_color = [c*1.03+0.05 for c in contour_color]
shape_color.append(shape_alpha)
contour_color.append(contour_alpha)
draw_polygon_2d(points,faces,loops,shape_color,contour_color,width)
if show_tooltip:
draw_text(self.end,tooltip,(1,1,1,1))
if self.is_border:
draw_border(self.border)
'''

95
area.py Normal file
View File

@ -0,0 +1,95 @@
import bpy
from bpy.types import NodeTree, NODE_PT_tools_active, NODE_HT_header
# Derived from the NodeTree base type, similar to Menu, Operator, Panel, etc.
class RigPickerTree(NodeTree):
# Description string
'''A custom node tree type that will show up in the editor type list'''
# Optional identifier string. If not explicitly defined, the python class name is used.
# Label for nice name display
bl_label = "Rig Picker"
# Icon identifier
bl_icon = 'OUTLINER_DATA_ARMATURE'
def draw_header(self, context):
if context.space_data.tree_type == 'RigPickerTree':
layout = self.layout
layout.template_header()
#layout.separator_spacer()
if not context.space_data.node_tree:
ntree = bpy.data.node_groups.get('.rig_picker')
if not ntree:
ntree = bpy.data.node_groups.new('.rig_picker', 'RigPickerTree')
context.space_data.node_tree = ntree
#layout.template_ID(context.space_data, "node_tree", new="node.new_node_tree")
#layout.separator_spacer()
layout.operator('rigpicker.reload_picker', icon='FILE_REFRESH', text='')
layout.separator_spacer()
else:
self._draw(context)
def tools_from_context(context, mode=None):
sp = context.space_data
if sp and sp.type == 'NODE_EDITOR' and sp.tree_type == 'RigPickerTree':
return []
else:
return NODE_PT_tools_active._tools_from_context(context, mode)
def tool_set_by_id(self, context):
sd = context.space_data
if sd and sd.type == 'NODE_EDITOR' and sd.tree_type == 'RigPickerTree':
return {"FINISHED"}
else:
return self._execute(context)
@classmethod
def poll(cls, context):
sp = context.space_data
if sp and sp.type == 'NODE_EDITOR' and sp.tree_type == 'RigPickerTree':
return False
else:
return self._poll(context)
classes = (
RigPickerTree,
)
def register():
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
NODE_PT_tools_active._tools_from_context = NODE_PT_tools_active.tools_from_context
NODE_PT_tools_active.tools_from_context = tools_from_context
NODE_HT_header._draw = NODE_HT_header.draw
NODE_HT_header.draw = draw_header
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
bpy.types.WM_OT_tool_set_by_id.execute = bpy.types.WM_OT_tool_set_by_id._execute
del bpy.types.WM_OT_tool_set_by_id._execute
NODE_PT_tools_active.tools_from_context = NODE_PT_tools_active._tools_from_context
del NODE_PT_tools_active._tools_from_context
NODE_HT_header.draw = NODE_HT_header._draw
del NODE_HT_header._draw

2
constants.py Normal file
View File

@ -0,0 +1,2 @@
PICKERS = {}

248
func_picker.py Normal file
View File

@ -0,0 +1,248 @@
import bpy
#from. insert_keyframe import insert_keyframe
from. snapping_utils import *
from .utils import get_IK_bones
try:
from rigutils.driver_utils import split_path
from rigutils.insert_keyframe import insert_keyframe
from rigutils.snap_ik_fk import snap_ik_fk
from rigutils.utils import find_mirror
except:
print('You need to install the rigutils module in your blender modules path')
def hide_layers(args):
""" """
ob = bpy.context.object
layers=[]
for bone in [b for b in ob.pose.bones if b.bone.select]:
for i,l in enumerate(bone.bone.layers):
if l and i not in layers:
layers.append(i)
for i in layers:
ob.data.layers[i] = not ob.data.layers[i]
def boolean(args):
""" data_path, keyable """
ob = bpy.context.object
data_path = args['data_path']
keyable = args['keyable']
#bone,prop = split_path(data_path)
try:
value = ob.path_resolve(data_path)
#setattr(ob.pose.bones.get(bone),'["%s"]'%prop,not value)
try:
exec("ob.%s = %s"%(data_path,not value))
except:
exec("ob%s= %s"%(data_path,not value))
if keyable and bpy.context.scene.tool_settings.use_keyframe_insert_auto:
if not ob.animation_data:
ob.animation_data_create()
ob.keyframe_insert(data_path = data_path ,group = bone)
except ValueError:
print("Property don't exist")
def select_layer(args):
ob = bpy.context.object
layers =[]
for bone in [b for b in ob.pose.bones if b.bone.select]:
bone_layers = [i for i,l in enumerate(bone.bone.layers) if l]
for l in bone_layers:
if l not in layers:
layers.append(l)
for bone in ob.pose.bones:
bone_layers = [i for i,l in enumerate(bone.bone.layers) if l]
if len(set(bone_layers).intersection(layers)):
bone.bone.select = True
def hide_bones(args):
ob = bpy.context.object
selected_bone = [b for b in ob.pose.bones if b.bone.select]
hide = [b.bone.hide for b in selected_bone if not b.bone.hide]
visibility = True if len(hide) else False
for bone in selected_bone:
bone.bone.hide = visibility
def select_all(args):
ob = bpy.context.object
shapes = ob.data.rig_picker['shapes']
bones = [s['bone'] for s in shapes if s['shape_type']=='BONE']
for bone_name in bones:
bone = ob.pose.bones.get(bone_name)
if bone:
bone.bone.select = True
def select_bones(args):
"""bones (name list)"""
ob = bpy.context.object
pBones = ob.pose.bones
bones_name =args['bones']
event = args['event']
if not event.shift:
for bone in bpy.context.object.pose.bones:
bone.bone.select = False
bones = [pBones.get(b) for b in bones_name]
select = False
for bone in bones:
if bone.bone.select == False:
select =True
break
for bone in bones:
bone.bone.select = select
ob.data.bones.active = bones[-1].bone
def keyframe_bones(args):
print(args)
event=args['event']
bones=[]
for bone in bpy.context.object.pose.bones:
if not bone.name.startswith(('DEF','ORG','MCH')) and not bone.get('_unkeyable_') ==1:
if event.shift:
bones.append(bone)
elif not event.shift and bone.bone.select :
bones.append(bone)
for bone in bones:
insert_keyframe(bone)
def reset_bones(args):
event=args['event']
avoid_value =args['avoid_value']
ob = bpy.context.object
bones=[]
for bone in bpy.context.object.pose.bones:
if not bone.name.startswith(('DEF','ORG','MCH')) and not bone.get('_unkeyable_') ==1:
if event.shift:
bones.append(bone)
elif not event.shift and bone.bone.select :
bones.append(bone)
for bone in bones:
if bone.rotation_mode =='QUATERNION':
bone.rotation_quaternion = 1, 0, 0, 0
if bone.rotation_mode == 'AXIS_ANGLE':
bone.rotation_axis_angle = 0, 0, 1, 0
else:
bone.rotation_euler = 0, 0, 0
bone.location = 0, 0, 0
bone.scale = 1, 1, 1
for key,value in bone.items():
if key not in avoid_value and type(value) in (int,float):
if ob.data.get("DefaultValues") and ob.data.DefaultValues['bones'].get(bone.name):
if key in ob.data.DefaultValues['bones'][bone.name]:
bone[key] = ob.data.DefaultValues['bones'][bone.name][key]
else:
if type(value)== int:
bone[key]=0
else:
bone[key]=0.0
else:
if type(value)== int:
bone[key]=0
else:
bone[key]=0.0
if bpy.context.scene.tool_settings.use_keyframe_insert_auto:
insert_keyframe(bone)
def flip_bones(args):
event=args['event']
ob = bpy.context.object
arm = bpy.context.object.pose.bones
selected_bones = [bone for bone in ob.pose.bones if bone.bone.select==True ]
mirrorActive = None
for bone in selected_bones:
boneName = bone.name
mirrorBoneName= find_mirror(boneName)
mirrorBone = ob.pose.bones.get(mirrorBoneName) if mirrorBoneName else None
if bpy.context.active_pose_bone == bone:
mirrorActive = mirrorBone
#print(mirrorBone)
if not event.shift and mirrorBone:
bone.bone.select = False
if mirrorBone:
mirrorBone.bone.select = True
if mirrorActive:
ob.data.bones.active = mirrorActive.bone
def snap_ikfk(args):
""" way, chain_index """
way =args['way']
chain_index = args['chain_index']
#auto_switch = self.auto_switch
ob = bpy.context.object
armature = ob.data
SnappingChain = armature.get('SnappingChain')
poseBone = ob.pose.bones
dataBone = ob.data.bones
IKFK_chain = SnappingChain['IKFK_bones'][chain_index]
switch_prop = IKFK_chain['switch_prop']
FK_root = poseBone.get(IKFK_chain['FK_root'])
FK_mid = [poseBone.get(b['name']) for b in IKFK_chain['FK_mid']]
FK_tip = poseBone.get(IKFK_chain['FK_tip'])
IK_last = poseBone.get(IKFK_chain['IK_last'])
IK_tip = poseBone.get(IKFK_chain['IK_tip'])
IK_pole = poseBone.get(IKFK_chain['IK_pole'])
invert = IKFK_chain['invert_switch']
ik_fk_layer = (IKFK_chain['FK_layer'],IKFK_chain['IK_layer'])
for lock in ('lock_ik_x','lock_ik_y','lock_ik_z'):
if getattr(IK_last,lock):
full_snapping = False
break
snap_ik_fk(ob,way,switch_prop,FK_root,FK_tip,IK_last,IK_tip,IK_pole,FK_mid,full_snapping,invert,ik_fk_layer,auto_switch=True)

102
func_shape.py Normal file
View File

@ -0,0 +1,102 @@
import bpy
import bmesh
from mathutils import Vector, Matrix
from bpy_extras import mesh_utils
from .utils import bound_box_center, contour_loops, get_object_color
def get_picker_datas(objects, canvas, rig):
picker_datas = []
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 = 1024 / (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))
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.002)
bmesh.ops.dissolve_limit(bm, angle_limit=0.001745, verts=bm.verts, edges=bm.edges)
bmesh.ops.connect_verts_concave(bm, faces=bm.faces)
bmesh.ops.triangulate(bm, faces=bm.faces)
#bm_loops = contour_loops(bm)
#loops = [[l.index for l in loop] for loop in bm_loops]
loops = []
edges = [[v.index for v in e.verts] for e in bm.edges if len(e.link_faces)<=1]
bm.to_mesh(mesh)
mesh.update()
bm.clear()
points = []
#edges = []
polygons = []
for p in mesh.vertices:
point = ob.matrix_world@Vector((p.co))
point = (point - canvas_center) * canvas_scale_fac
points.append([round(point[0]), round(point[1])])
for f in mesh.polygons:
polygons.append([v for v in f.vertices])
#for e in mesh.edges:
#
# edges.append([v for v in e.vertices])
color = get_object_color(ob)
if color:
color = [round(pow(c, gamma), 4) for c in color]
else:
color = [0.5, 0.5, 0.5]
shape = {
'tooltip': ob.rig_picker.name,
'points': points,
'polygons': polygons,
'edges': edges,
'loops': loops,
'color': color,
'type': 'CANVAS' if ob == canvas else ob.rig_picker.shape_type
}
if shape['type'] =='OPERATOR':
shape['operator'] = ob.rig_picker.operator
#if ob.rig_picker.arguments:
#shape['arguments'] = ob.rig_picker.arguments
#if ob.rig_picker.shortcut:
shape['shortcut'] = ob.rig_picker.shortcut
elif shape['type'] =='BONE':
shape['bone'] = ob.rig_picker.name
picker_datas.append(shape)
print(picker_datas)
return picker_datas
#rig.data.rig_picker['shapes'] = picker_datas

124
gizmo.py Normal file
View File

@ -0,0 +1,124 @@
import bpy
from bpy.props import (IntProperty, EnumProperty, BoolProperty)
from bpy.types import (AddonPreferences, GizmoGroup, Operator, Gizmo)
from mathutils import Vector, Matrix, Euler
from .constants import PICKERS
from .picker import Picker
'''
class RP_OT_simple_operator(bpy.types.Operator):
"""Tooltip"""
bl_idname = "object.simple_operator"
bl_label = "Simple Object Operator"
def invoke(self, context, event):
context.window_manager.modal_handler_add(self)
print('Invoke RP_OT_simple_operator')
bpy.ops.node.rp_box_select('INVOKE_DEFAULT', tweak=True)
#bpy.ops.node.select_box('INVOKE_DEFAULT', wait_for_input=True, tweak=True)
return {'RUNNING_MODAL'}
def modal(self, context, event):
print('modal op', event.type, event.value)
return {'RUNNING_MODAL'}
def execute(self, context):
print('Select Shape')
return {'FINISHED'}
'''
class RP_GT_gizmo(Gizmo):
def test_select(self, context, location):
ob = context.object
picker = PICKERS.get(ob)
if not picker:
return -1
picker.move_event(location)
#if bpy.app.timers.is_registered(tooltip):
# bpy.app.timers.unregister(tooltip)
#context.region.tag_redraw()
#picker.tooltip_event(event='START')
#bpy.app.timers.register(partial(tooltip, context.region), first_interval=1)
#print(location)
context.region.tag_redraw()
return -1
def draw(self, context):
return
'''
def invoke(self, context, event):
print(f'invoke: type={event.type}, value={event.value}, ctrl={event.ctrl}, shift={event.shift}, alt={event.alt}')
return {'RUNNING_MODAL'}
def modal(self, context, event, tweak):
print(f'invoke: type={event.type}, value={event.value}, ctrl={event.ctrl}, shift={event.shift}, alt={event.alt}')
return {'RUNNING_MODAL'}
def exit(self, context, cancel):
print('EXIT')
'''
class RP_GT_gizmogroup(GizmoGroup):
""" test gizmo button 2d """
bl_idname = "view3d.gizmo_button_2d"
bl_label = "Test button 2d"
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'WINDOW'
bl_options = {'PERSISTENT'}
@classmethod
def poll(cls, context):
return context.space_data.tree_type == 'RigPickerTree'
def setup(self, context):
self.gizmo = self.gizmos.new("RP_GT_gizmo")
classes = (
RP_GT_gizmo,
RP_GT_gizmogroup
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)

146
op_material.py Normal file
View File

@ -0,0 +1,146 @@
import bpy
from bpy_extras import view3d_utils
class RP_OT_add_material(bpy.types.Operator):
bl_label = "Add Ui Material"
bl_idname = "rigpicker.add_mat"
#bl_options = {'REGISTER', 'UNDO'}
def execute(self,context):
scene = context.scene
mat = bpy.data.materials.new('UI')
mat.use_nodes = True
for node in mat.node_tree.nodes:
if node.type == 'OUTPUT_MATERIAL':
mat_output = node
else:
mat.node_tree.nodes.remove(node)
emission = mat.node_tree.nodes.new('ShaderNodeEmission')
mat.node_tree.links.new(emission.outputs[0],mat_output.inputs[0])
if not context.object.data.materials:
context.object.data.materials.append(mat)
else:
context.object.material_slots[0].material = mat
return {'FINISHED'}
class RP_OT_remove_material(bpy.types.Operator):
bl_label = "Remove Ui Material"
bl_idname = "rigpicker.remove_mat"
#bl_options = {'REGISTER', 'UNDO'}
def execute(self,context):
scene = context.scene
#print(self.shape_type)
for mat in context.object.data.materials:
bpy.data.materials.remove(mat)
context.area.tag_redraw()
return {'FINISHED'}
class RP_OT_eyedropper_material(bpy.types.Operator):
"""Tooltip"""
bl_idname = "rigpicker.eyedropper_mat"
bl_label = "Eye Dropper mat"
#bl_options = {'REGISTER', 'UNDO'}
#first_mouse_x = IntProperty()
#first_value = FloatProperty()
def modal(self, context, event):
context.area.tag_redraw()
context.window.cursor_modal_set("EYEDROPPER")
scene = context.scene
region = context.region
rv3d = context.region_data
if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
self.mouse = event.mouse_region_x, event.mouse_region_y
dg = context.evaluated_depsgraph_get()
view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, self.mouse)
ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, self.mouse)
raycast = scene.ray_cast(dg, ray_origin, view_vector)
if raycast[0]==True:
ob = raycast[4]
if ob.data.materials:
mat = ob.data.materials[0]
for shape in [o for o in context.selected_objects if o.type in ('MESH','CURVE','FONT')]:
if not shape.data.materials:
shape.data.materials.append(mat)
else:
shape.material_slots[0].material = mat
#context.space_data.draw_handler_remove(self._handle, 'WINDOW')
context.window.cursor_modal_restore()
for ob in self.temp_ob:
bpy.data.objects.remove(ob)
return {'FINISHED'}
#return {'FINISHED'}
elif event.type in {'RIGHTMOUSE', 'ESC'}:
#context.object.location.x = self.first_value
context.window.cursor_modal_restore()
for ob in self.temp_ob:
bpy.data.objects.remove(ob)
return {'CANCELLED'}
return {'RUNNING_MODAL'}
def invoke(self, context, event):
scene = context.scene
#self.local_cursor = tuple(context.space_data.cursor.location)
#self.cursor = tuple(context.scene.cursor.location)
curves =[o for o in context.visible_objects if o.type in ('CURVE', 'FONT')]
self.temp_ob = []
dg = bpy.context.evaluated_depsgraph_get()
for c in curves:
mesh = bpy.data.meshes.new_from_object(c.evaluated_get(dg))
copy = bpy.data.objects.new(c.name+'_tmp', mesh)
copy.matrix_world = c.matrix_world
for mat in c.data.materials:
copy.data.materials.append(mat)
scene.collection.objects.link(copy)
self.temp_ob.append(copy)
#args = (self,context)
#self._handle = context.space_data.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
classes = (
RP_OT_add_material,
RP_OT_remove_material,
RP_OT_eyedropper_material,
)
register, unregister = bpy.utils.register_classes_factory(classes)

532
op_picker.py Normal file
View File

@ -0,0 +1,532 @@
import bpy
from .constants import PICKERS
#from .func_bgl import draw_callback_px
#from .func_bgl import select_bone
#from .func_picker import *
#from .utils import is_over_region
import gpu
import bgl
from mathutils import Vector
from gpu_extras.batch import batch_for_shader
vertex_shader = '''
layout (location = 0) in vec2 pos;
flat out vec2 startPos;
out vec2 vertPos;
uniform mat4 viewMatrix;
void main()
{
vec4 outPos = viewMatrix * vec4(pos.x, pos.y, 0.0, 1.0);
gl_Position = outPos;
vertPos = pos.xy / outPos.w;
startPos = vertPos;
}
'''
fragment_shader = '''
flat in vec2 startPos;
in vec2 vertPos;
out vec4 fragColor;
uniform vec4 color;
uniform float dashSize;
uniform float gapSize;
void main()
{
vec2 dir = (vertPos.xy - startPos.xy);
float dist = length(dir);
if (fract(dist / (dashSize + gapSize)) > dashSize/(dashSize + gapSize))
discard;
fragColor = color;
}
'''
def draw_callback(self):
#print('draw callback border')
if not self.draw_border:
return
bgl.glEnable(bgl.GL_BLEND)
#print('DRAW BORDER')
self.color_shader.bind()
self.color_shader.uniform_float("color", self.bg_color)
self.bg_batch.draw(self.color_shader)
self.dash_shader.bind()
matrix = gpu.matrix.get_projection_matrix()
self.dash_shader.uniform_float("color", self.border_color)
self.dash_shader.uniform_float("viewMatrix", matrix)
self.dash_shader.uniform_float("dashSize", 5)
self.dash_shader.uniform_float("gapSize", 4)
self.contour_batch.draw(self.dash_shader)
bgl.glDisable(bgl.GL_BLEND)
class RP_OT_box_select(bpy.types.Operator):
"""Tooltip"""
bl_idname = "node.rp_box_select"
bl_label = "Picker Box Select"
mode: bpy.props.EnumProperty(items=[(i, i.title(), '') for i in ('SET', 'EXTEND', 'SUBSTRACT')])
color_shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
dash_shader = gpu.types.GPUShader(vertex_shader, fragment_shader)
@classmethod
def poll(cls, context):
sp = context.space_data
if not sp.tree_type == 'RigPickerTree':
return
ob = context.object
return ob and ob in PICKERS
'''
def mode_from_event(self, event):
if event.alt:
return 'SUBSTRACT'
elif event.ctrl or event.shift:
return 'EXTEND'
else:
return 'SET'
'''
def invoke(self, context, event):
#print(f'invoke: type={event.type}, value={event.value}, ctrl={event.ctrl}, shift={event.shift}, alt={event.alt}')
if context.object.mode != 'POSE':
bpy.ops.object.posemode_toggle()
self.timer = None
#self.mode = self.mode_from_event(event)
#self.invoke_event = event.copy()
self.region = context.region
self.draw_border = False
self.picker = PICKERS[context.object]
self.start_mouse = event.mouse_region_x, event.mouse_region_y
#self.shader = line_strip_shader
self.border_color = [1, 1, 1, 1]
self.bg_color = [1, 1, 1, 0.05]
#args = (self, context)
self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback, (self,), 'WINDOW', 'POST_PIXEL')
context.window_manager.modal_handler_add(self)
self.picker.press_event(self.mode)
self.region.tag_redraw()
return {'RUNNING_MODAL'}
def modal(self, context, event):
self.mouse = event.mouse_region_x, event.mouse_region_y
points_x = [v[0] for v in (self.start_mouse, self.mouse)]
points_y = [v[1] for v in (self.start_mouse, self.mouse)]
self.border = [
(min(points_x), max(points_y)),
(max(points_x), max(points_y)),
(max(points_x), min(points_y)),
(min(points_x), min(points_y))
]
self.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.draw_border = True
self.region.tag_redraw()
if event.value == 'RELEASE':
return self.release_event()
return {'RUNNING_MODAL'}
def release_event(self):
if (self.start_mouse[0] != self.mouse[0] and self.start_mouse[1] != self.mouse[1]):
self.picker.border_select(self.border, self.mode)
else:
self.picker.move_event(self.mouse)
self.picker.release_event(self.mode)
return self.exit()
def exit(self):
#print('Border Select Finished')
bpy.types.SpaceNodeEditor.draw_handler_remove(self._handle, 'WINDOW')
self.region.tag_redraw()
return {'FINISHED'}
class RP_OT_move_bone(bpy.types.Operator):
"""Tooltip"""
bl_idname = "node.move_bone"
bl_label = "Move Bone in Picker View"
'''
@classmethod
def poll(cls, context):
sp = context.space_data
if not sp.tree_type == 'RigPickerTree':
return
ob = context.object
return ob and ob in PICKERS
'''
def invoke(self, context, event):
self.mouse = event.mouse_region_x, event.mouse_region_y
self.bone_data = {b: b.matrix.copy() for b in context.selected_pose_bones}
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
def modal(self, context, event):
delta_x = (event.mouse_region_x - self.mouse[0]) / 1000
delta_y = (event.mouse_region_y - self.mouse[1]) / 1000
for bone, matrix in self.bone_data.items():
bone.matrix.translation = matrix.translation + Vector((delta_x, 0, delta_y))
#print(delta_x, delta_y)
if event.type=="LEFTMOUSE" and event.value == 'RELEASE':
return {'FINISHED'}
if event.type=="RIGHTMOUSE":
for bone, matrix in self.bone_data.items():
bone.matrix = matrix
return {'CANCELLED'}
return {'RUNNING_MODAL'}
class RP_OT_function_execute(bpy.types.Operator):
bl_idname = "rigpicker.function_execute"
bl_label = 'Function Execute'
shape_index = bpy.props.IntProperty()
def execute(self,context):
event = self.event
ob = context.object
shape = ob.data.rig_picker['shapes'][self.shape_index]
function = shape['function']
if shape.get('variables'):
variables=shape['variables'].to_dict()
else:
variables={}
variables['event']=event
globals()[function](variables)
return {'FINISHED'}
def invoke(self,context,event):
self.event = event
return self.execute(context)
class RP_OT_reload_picker(bpy.types.Operator):
bl_idname = "rigpicker.reload_picker"
bl_label = 'Reload Picker'
def execute(self,context):
PICKERS.clear()
context.region.tag_redraw()
return {"FINISHED"}
class RP_OT_toogle_bone_layer(bpy.types.Operator):
bl_idname = "rigpicker.toogle_bone_layer"
bl_label = 'Toogle Bone Layer'
@classmethod
def poll(cls, context):
if not context.space_data.tree_type == 'RigPickerTree':
return
ob = context.object
picker = PICKERS.get(ob)
if picker.hover_shape and picker.hover_shape.type=='bone':
return True
def execute(self, context):
ob = context.object
picker = PICKERS.get(ob)
bone = picker.hover_shape.bone
hide = picker.hover_shape.hide
if bone:
for i, l in enumerate(bone.bone.layers):
if l:
ob.data.layers[i] = hide
print('Bone Layer toogle')
#if picker.hover_bone:
context.region.tag_redraw()
return {"FINISHED"}
class RP_OT_call_operator(bpy.types.Operator):
bl_idname = "rigpicker.call_operator"
bl_label = 'Toogle Bone Layer'
operator: bpy.props.StringProperty()
@classmethod
def poll(cls, context):
sp = context.space_data
return (sp.type=='NODE_EDITOR' and sp.tree_type != 'RigPickerTree')
def execute(self, context):
print('CALL OPERATOR', self.operator)
exec(self.operator)
context.region.tag_redraw()
return {"FINISHED"}
class RP_MT_context_menu(bpy.types.Menu):
bl_label = "Context Menu"
bl_idname = "rigpicker.context_menu"
# Set the menu operators and draw functions
def draw(self, context):
layout = self.layout
layout.operator("rigpicker.show_bone_layer", text="Show Bone Layer", ).type = 'ACTIVE'
#layout.operator("rigidbody.objects_add", text="B Add Passive").type = 'PASSIVE'
'''
class RP_OT_ui_draw(bpy.types.Operator):
bl_idname = "rigpicker.ui_draw"
bl_label = "Rig UI Draw"
_handle = None
tmp_ob = None
tmp_bones = []
start = (0,0)
end = (0,0)
border = ((0,0),(0,0),(0,0),(0,0))
is_border = False
press = False
scale = 1
offset = 0
outside_point = (-1,-1)
addon_keymaps = []
def set_shorcut(self,context):
ob = context.object
addon = bpy.context.window_manager.keyconfigs.addon
if ob and ob.type =='ARMATURE' and ob.data.rig_picker and addon:
for i,shape in [(i,s) for i,s in enumerate(ob.data.rig_picker['shapes']) if s.get('function') and s.get('shortcut')]:
km = addon.keymaps.new(name = 'Image Generic', space_type = 'IMAGE_EDITOR',region_type = 'WINDOW')
split = shape["shortcut"].split(' ')
if len(split)==1:
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")
kmi.properties.shape_index = i
if modifier:
setattr(kmi,modifier,True)
self.addon_keymaps.append(km)
def remove_shorcut(self,context):
# Remove Shortcut
wm = bpy.context.window_manager
for km in self.addon_keymaps:
for kmi in km.keymap_items:
km.keymap_items.remove(kmi)
self.addon_keymaps.clear()
def modal(self, context, event):
inside = is_over_region(self,context,event)
if context.object and context.object.type == 'ARMATURE' and context.area:
if not context.screen.is_animation_playing:
if self.tmp_ob != context.object:
context.area.tag_redraw()
self.remove_shorcut(context)
self.set_shorcut(context)
self.tmp_ob = context.object
if self.tmp_bones != context.selected_pose_bones:
context.area.tag_redraw()
self.tmp_bones = context.selected_pose_bones
if inside:
context.area.tag_redraw()
if event.type == 'LEFTMOUSE':
if event.value == 'PRESS': # start selection
if inside:
self.start = (event.mouse_region_x,event.mouse_region_y)
self.press = True
elif event.value == 'RELEASE' and self.press:
self.end = (event.mouse_region_x, event.mouse_region_y)
select_bone(self, context, event)
bpy.ops.ed.undo_push()
self.is_border= False
self.press = False
if event.type == 'MOUSEMOVE':
self.end = (event.mouse_region_x, event.mouse_region_y)
if self.press:
b_x = (min(self.start[0], self.end[0]), max(self.start[0], self.end[0]))
b_y = (min(self.start[1], self.end[1]), max(self.start[1], self.end[1]))
self.border = ((b_x[0], b_y[1]), (b_x[1], b_y[1]), (b_x[1], b_y[0]), (b_x[0], b_y[0]))
self.is_border = True if (b_x[1]-b_x[0])+(b_y[1]-b_y[0]) > 4 else False
if self.is_border:
select_bone(self, context, event)
elif event.type in {'ESC',} and inside:
bpy.types.SpaceImageEditor.draw_handler_remove(self._handle, 'WINDOW')
self.remove_shorcut(context)
return {'CANCELLED'}
return {'PASS_THROUGH'}
def invoke(self, context, event):
#shortcut Creation
context.space_data.image = None
self.adress = context.area.as_pointer()
args = (self, context)
self._handle = bpy.types.SpaceImageEditor.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
'''
keymaps = []
def register_keymaps():
wm = bpy.context.window_manager
km = wm.keyconfigs.addon.keymaps.new(name="Node Editor", space_type="NODE_EDITOR")
kmi = km.keymap_items.new("rigpicker.call_operator", type="X", value="PRESS")
kmi.properties.operator = "bpy.ops.animtoolbox.reset_bone()"
keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.call_operator", type="A", value="PRESS")
kmi.properties.operator = "bpy.ops.pose.select_all(action='SELECT')"
keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.call_operator", type="A", value="PRESS", alt=True)
kmi.properties.operator = "bpy.ops.pose.select_all(action='DESELECT')"
keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.toogle_bone_layer", type="LEFTMOUSE", value="DOUBLE_CLICK")
keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.move_bone", type="G", value="PRESS")
keymaps.append((km, kmi))
kmi = km.keymap_items.new("wm.call_menu", type="RIGHTMOUSE", value="PRESS")
kmi.properties.name = "rigpicker.context_menu"
keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS")
kmi.properties.mode = 'SET'
keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS", shift=True)
kmi.properties.mode = 'EXTEND'
keymaps.append((km, kmi))
#kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS", ctrl=True)
kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS", alt=True)
kmi.properties.mode = 'SUBSTRACT'
keymaps.append((km, kmi))
def unregister_keymaps():
for km, kmi in keymaps:
km.keymap_items.remove(kmi)
keymaps.clear()
classes = (
RP_OT_box_select,
RP_OT_function_execute,
RP_OT_reload_picker,
RP_OT_toogle_bone_layer,
RP_OT_call_operator,
RP_MT_context_menu,
RP_OT_move_bone
#RP_OT_ui_draw
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
register_keymaps()
def unregister():
unregister_keymaps()
for cls in reversed(classes):
bpy.utils.unregister_class(cls)

217
op_shape.py Normal file
View File

@ -0,0 +1,217 @@
import bpy
from .func_shape import get_picker_datas
from .utils import is_shape, find_mirror, link_mat_to_object
#import os
from pathlib import Path
import json
class RP_OT_create_shape(bpy.types.Operator):
bl_label = 'Create UI shape'
bl_idname = 'rigpicker.create_shape'
#bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return (context.object and context.object.mode == 'POSE')
def execute(self,context):
scn = context.scene
vl = context.view_layer
offset = 0
for bone in context.selected_pose_bones:
name = bone.name
mesh = bpy.data.meshes.new(name)
ob = bpy.data.objects.new(name, mesh)
ob.rig_picker.name = name
verts=[(0,0,0)]
edges = []
faces =[]
mesh.from_pydata(verts, edges, faces)
for c in scn.rig_picker.canvas.users_collection:
c.objects.link(ob)
ob.location.z = 0.05
ob.location.x = offset
for obj in scn.objects:
obj.select_set(False)
vl.objects.active = ob
ob.select_set(True)
offset += 0.05
return {'FINISHED'}
class RP_OT_name_from_bone(bpy.types.Operator):
bl_label = 'Name Shape from selected bones'
bl_idname = 'rigpicker.name_from_bone'
#bl_options = {'REGISTER', 'UNDO'}
def execute(self,context):
scene = context.scene
rig = scene.rig_picker.rig
bone = rig.data.bones.active
if bone:
context.object.name = bone.name
context.object.rig_picker.name = bone.name
return {'FINISHED'}
class RP_OT_mirror_shape(bpy.types.Operator):
bl_label = 'Mirror UI shape'
bl_idname = 'rigpicker.mirror_shape'
#bl_options = {'REGISTER', 'UNDO'}
def execute(self,context):
scn = context.scene
for ob in bpy.context.selected_objects:
name = find_mirror(ob.name)
link_mat_to_object(ob)
if not name:
name = ob.name + '_flip'
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]
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':
mirror_ob.rig_picker.name = find_mirror(ob.rig_picker.name)
elif ob.rig_picker.shape_type == 'FUNCTION':
args = {}
for key,value in eval(ob.rig_picker.arguments).items():
if type(value) == list:
mirrored_value = []
for item in value:
mirrored_value.append(find_mirror(item))
elif type(value) == str:
mirrored_value = find_mirror(value)
args[key] = mirrored_value
mirror_ob.rig_picker.arguments = str(args)
return {'FINISHED'}
class RP_OT_select_shape_type(bpy.types.Operator):
bl_label = 'Select Shape by Type'
bl_idname = 'rigpicker.select_shape_type'
#bl_options = {'REGISTER', 'UNDO'}
shape_type = bpy.props.EnumProperty(items =[(i.upper(), i, '') for i in ('Bone', 'Display', 'Function')])
def draw(self,context):
layout = self.layout
col = layout.column()
col.prop(self,'shape_type', expand=True)
def execute(self,context):
scene = context.scene
#print(self.shape_type)
canvas = context.scene.rig_picker.canvas
if canvas:
for ob in [o for o in bpy.data.objects if o.layers==canvas.layers]:
if ob.type in ['MESH', 'CURVE', 'FONT'] and ob.rig_picker.shape_type == self.shape_type:
ob.select = True
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self, width=150)
class RP_OT_save_picker(bpy.types.Operator):
bl_label = 'Store UI Data'
bl_idname = 'rigpicker.save_picker'
def execute(self, context):
scn = context.scene
canvas= scn.rig_picker.canvas
rig = scn.rig_picker.rig
shapes = [o for o in scn.objects if o != canvas and is_shape(o)]
if not rig:
self.report({'ERROR'}, 'Choose a Rig')
return {'CANCELLED'}
if not canvas:
self.report({'ERROR'}, 'Choose a Canvas')
return {'CANCELLED'}
data = get_picker_datas(shapes, canvas, rig)
picker_path = Path(bpy.path.abspath(scn.rig_picker.destination))
picker_path.write_text(json.dumps(data))
bpy.ops.rigpicker.reload_picker()
return {'FINISHED'}
classes = (
RP_OT_create_shape,
RP_OT_name_from_bone,
RP_OT_mirror_shape,
RP_OT_select_shape_type,
RP_OT_save_picker
)
register, unregister = bpy.utils.register_classes_factory(classes)

110
panels.py Normal file
View File

@ -0,0 +1,110 @@
import bpy
#import collections
#import inspect
from .utils import get_operator_from_id, get_mat
import re
class RP_PT_picker_maker_panel(bpy.types.Panel):
bl_label = 'Rig UI'
bl_category = 'RIG Tools'
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':
layout.prop(ob.data.rig_picker, 'source', text='Picker')
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')
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)

589
picker.py Normal file
View File

@ -0,0 +1,589 @@
import bpy
import gpu
from gpu_extras.batch import batch_for_shader
import bgl
import blf
from mathutils import bvhtree, Vector
from mathutils.geometry import intersect_point_quad_2d, intersect_point_tri_2d, intersect_tri_tri_2d
from .constants import PICKERS
from .utils import get_operator_from_id
from pathlib import Path
import re
import json
import threading
class Shape:
def __init__(self, picker, points, polygons=None, edges=None, tooltip='', color=None):
self.type = 'display'
self.picker = picker
self.rig = picker.rig
self.hover = False
self.press = False
self.shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
#self.hover_shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
#self.hover_shader.uniform_float("color", [1, 1, 1, 0.1])
self.hover_color = [1, 1, 1, 0.1]
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))
]
@property
def color(self):
return self._color
@color.setter
def color(self, color=None):
if not color:
color = [0.5, 0.5, 0.5, 1]
elif isinstance(color, (tuple, list)) and len(color) in (3, 4):
if len(color) == 3:
color = [*color, 1]
elif len(color) == 4:
color = list(color)
else:
raise Exception('color must have a len of 3 or 4')
else:
raise Exception(f'color is {type(color)} must be None or (tuple, list)')
#self.shader.uniform_float("color", color)
self._color = color
def draw(self):
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):
self.hover = False
return False
for p in self.polygons:
if intersect_point_tri_2d(location, *[self.points[i] for i in p]):
self.hover = True
return True
self.hover = False
return False
def press_event(self, mode='SET'):
self.press = True
def release_event(self, mode='SET'):
self.press = False
class BoneShape(Shape):
def __init__(self, picker, points, polygons, edges, bone, tooltip='', color=None):
super().__init__(picker, points=points, polygons=polygons, edges=edges,
tooltip=tooltip, color=color)
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.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.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]
@property
def select(self):
return self.bone in (bpy.context.selected_pose_bones or []) #self.bone.bone.select
@property
def active(self):
return self.bone == bpy.context.active_pose_bone #self.rig.data.bones.active == self.bone.bone
@property
def hide(self):
#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))
@property
def bone_color(self):
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:
return self.bone_colors['select']
elif self.hide:
return self.bone_colors['hide']
else:
return self.bone_colors['normal']
def draw(self):
bgl.glEnable(bgl.GL_BLEND)
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()
if self.select:
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)
#self.contour_shader.bind()
#print(self.bone_color)
if self.select or self.active:
bgl.glLineWidth(2)
#if not self.hide:
self.shader.uniform_float("color", self.bone_color)
#for b in self.contour_batches:
self.e_batch.draw(self.shader)
bgl.glLineWidth(1)
bgl.glDisable(bgl.GL_BLEND)
def release_event(self, mode='SET'):
super().release_event(mode)
if self.hide:
return
select = True
if mode == 'SUBSTRACT':
select = False
if self.hover:
self.bone.bone.select = select
if mode != 'SUBSTRACT':
self.rig.data.bones.active = self.bone.bone
else:
self.bone.bone.select = select
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 self.hide:
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]
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, *border_tri2) and
not intersect_tri_tri_2d(*border_tri2, *border_tri2) and
not intersect_tri_tri_2d(*border_tri2, *border_tri2)):
return
select = True
if mode == 'SUBSTRACT':
select = False
for polygon in self.polygons:
points = [self.points[i] for i in polygon]
if intersect_tri_tri_2d(*border_tri1, *points):
self.bone.bone.select = select
return
if intersect_tri_tri_2d(*border_tri2, *points):
self.bone.bone.select = select
return
'''
for b in border:
if intersect_point_tri_2d(b, *points):
self.bone.bone.select = select
return
for p in points:
if intersect_point_quad_2d(p, *border):
self.bone.bone.select = select
return
'''
class OperatorShape(Shape):
def __init__(self, picker, points, polygons, operator, tooltip='', color=None):
super().__init__(picker, points=points, polygons=polygons, tooltip=tooltip, color=color)
self.type = 'operator'
self.active_color = [1, 1, 1, 0.15]
self.press_color = [0, 0, 0, 0.25]
self.operator = operator
#self.arguments = arguments#{k:eval(v)}
#self.operator = get_operator_from_id(self.operator)
#self.reg_args = re.compile(r'(\w+)=')
'''
def parse_args(self):
args = self.reg_args.split(self.arguments)[1:]
#print(args, zip(args[::2], args[1::2]))
return {k: eval(v) for k, v in zip(args[::2], args[1::2])}
#return {k:eval(v) for k, v in self.reg_args.split(self.arguments)}
'''
def release_event(self, mode='SET'):
super().release_event(mode)
#args = self.parse_args()
if not self.operator:
return
exec(self.operator)
#f'bpy.ops;{idname}'
#print(self.idname)
#print(self.arguments)
#else:
# self.bone.bone.select = False
def draw(self):
super().draw()
if self.press:
color = self.press_color
elif self.hover:
color = self.hover_color
else:
return
bgl.glEnable(bgl.GL_BLEND)
self.shader.uniform_float("color", color)
self.p_batch.draw(self.shader)
bgl.glDisable(bgl.GL_BLEND)
class Picker:
def __init__(self, rig, shapes):
self.region = bpy.context.region
self.rig = rig
self.shapes = []
self.box_select = None
self.hover_shape = None
self.shader = gpu.shader.from_builtin('2D_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']:
continue
if s['type'] in ('CANVAS', 'DISPLAY'):
shape = Shape(
self,
points=s['points'],
polygons=s['polygons'],
edges=s['edges'],
color=s['color']
)
elif s['type'] == 'BONE':
shape = BoneShape(
self,
points=s['points'],
polygons=s['polygons'],
edges=s['edges'],
bone=rig.pose.bones.get(s['bone']),
color=s['color']
)
elif s['type'] == 'OPERATOR':
shape = OperatorShape(
self,
points=s['points'],
polygons=s['polygons'],
operator=s['operator'],
color=s['color']
)
self.shapes.append(shape)
def press_event(self, mode='SET'):
for s in self.shapes:
if s.hover:
s.press_event(mode)
else:
s.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)
s.press = False
def tooltip_event(self):
print('Tooltip Event', self)
if self.hover_shape:
if self.hover_shape.type == 'bone':
self.tooltip = self.hover_shape.bone.name
self.tooltip_shape = self.hover_shape
else:
self.tooltip = ''
self.tooltip_shape = None
self.tooltip_mouse = self.mouse
self.timer.cancel()
self.region.tag_redraw()
'''
picker.tooltip_event(event='SHOW')
region.tag_redraw()
#context.region.tag_redraw()
picker.tooltip_event(event='HIDE')
bpy.app.timers.register(partial(tooltip, context.region), first_interval=1)
'''
'''
def tooltip_event(self, event):
self.tooltip = ''
if event == 'SHOW':
if self.hover_shape.type == 'bone':
self.tooltip = self.hover_shape.bone.name
#bpy.context.region.tag_redraw()
'''
def border_select(self, border, mode):
border = [bpy.context.region.view2d.region_to_view(*b) 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)
def move_event(self, location):
self.mouse = location
location = self.region.view2d.region_to_view(*location)
for s in self.shapes:
if s.move_event(location):
self.hover_shape = s
#if point_inside_rectangle(self.end, bound):
# over = point_over_shape(self.end,points, edges)
#if bpy.app.timers.is_registered(self.tooltip_event):
#try:
# bpy.app.timers.unregister(self.tooltip_event)
#except:
# pass
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.start()
#bpy.app.timers.register(self.tooltip_event, first_interval=1)
def draw(self):
for s in self.shapes:
s.draw()
'''
if self.box_select:
self.box_shader.uniform_float("color", self.box_select_color)
batch = batch_for_shader(self.shader, 'LINE_LOOP', {"pos": []})
for b in self.contour_batches:
b.draw(self.contour_shader)
self.batch.draw(self.shader)
bgl.glDisable(bgl.GL_BLEND)
'''
def draw_callback_view():
sp = bpy.context.space_data
if not sp.tree_type == 'RigPickerTree':
return
ob = bpy.context.object
if not ob or ob.type !='ARMATURE' or not ob.data.rig_picker.source:
return
if ob not in PICKERS:
picker_path = Path(bpy.path.abspath(ob.data.rig_picker.source))
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
if not sp.tree_type == 'RigPickerTree':
return
ob = bpy.context.object
picker = PICKERS.get(ob)
if not picker or not picker.tooltip:
return
text = picker.tooltip
font_id = 0
#blf.dimensions(font_id, text)
blf.enable(font_id, blf.SHADOW)
#bgl.glEnable(bgl.GL_BLEND)
# BLF drawing routine
blf.position(font_id, picker.tooltip_mouse[0]-5, picker.tooltip_mouse[1]+5, 0)
blf.size(font_id, 14, 0)
blf.color(font_id, 1, 1, 1, 1)
blf.shadow(font_id , 5, 0.0, 0.0, 0.0, 1)
blf.shadow_offset(font_id, 1, -1)
blf.draw(font_id, text)
blf.disable(font_id, blf.SHADOW)
#bgl.glDisable(bgl.GL_BLEND)
handle_view = None
handle_pixel = None
def register():
global handle_view
global handle_pixel
handle_view = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_view, (), 'WINDOW', 'POST_VIEW')
handle_pixel = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_px, (), 'WINDOW', 'POST_PIXEL')
def unregister():
global handle_view
global handle_pixel
bpy.types.SpaceNodeEditor.draw_handler_remove(handle_view, 'WINDOW')
bpy.types.SpaceNodeEditor.draw_handler_remove(handle_pixel, 'WINDOW')
handle_view = None
handle_pixel = None

90
properties.py Normal file
View File

@ -0,0 +1,90 @@
import bpy
from bpy.props import EnumProperty, StringProperty, PointerProperty
import inspect
'''
def get_operator_items(self,context):
items = []
from . import func_picker as mod
for name, func in inspect.getmembers(mod, inspect.isfunction):
if inspect.getmodule(func) == mod:
items.append((name,name,""))
return items
'''
def bones_item(self,context):
items = []
if context.scene.rig_picker.rig:
for bone in context.scene.rig_picker.rig.pose.bones:
items.append((bone.name,bone.name,''))
return items
class RP_PG_armature_ui_settings(bpy.types.PropertyGroup):
name: StringProperty()
source: StringProperty(subtype='FILE_PATH')
class RP_PG_object_ui_settings(bpy.types.PropertyGroup):
shape_type: EnumProperty(items=[(i.upper(), i, "") for i in ('Bone', 'Display', 'Operator')])
#idname: StringProperty()
#arguments: StringProperty()
operator: StringProperty()
shortcut: StringProperty()
name: StringProperty()
class RP_PG_scene_ui_settings(bpy.types.PropertyGroup):
rig: PointerProperty(type=bpy.types.Object)
canvas: PointerProperty(type=bpy.types.Object)
symmetry: PointerProperty(type=bpy.types.Object)
#idname: EnumProperty(items=[])
destination: StringProperty(subtype='FILE_PATH')
#bone_list: bpy.props.EnumProperty(items = bones_item)
class RP_OT_operator_selector(bpy.types.Operator):
bl_label = "Select function"
bl_idname = "rigpicker.operator_selector"
bl_property = "idname"
#bl_options = {'REGISTER', 'UNDO'}
idname: EnumProperty(items=[])
def execute(self, context):
ob = context.object
ob.rig_picker.idname = self.idname
context.area.tag_redraw()
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
wm.invoke_search_popup(self)
return {'FINISHED'}
classes = (
RP_PG_object_ui_settings,
RP_PG_scene_ui_settings,
RP_PG_armature_ui_settings,
RP_OT_operator_selector,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
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)
def unregister():
del bpy.types.Scene.rig_picker
del bpy.types.Object.rig_picker
del bpy.types.Armature.rig_picker
for cls in reversed(classes):
bpy.utils.unregister_class(cls)

209
snapping_utils.py Normal file
View File

@ -0,0 +1,209 @@
import bpy
from mathutils import Vector,Matrix
from math import acos, pi
#from .insert_keyframe import insert_keyframe
############################
## Math utility functions ##
############################
def perpendicular_vector(v):
""" Returns a vector that is perpendicular to the one given.
The returned vector is _not_ guaranteed to be normalized.
"""
# Create a vector that is not aligned with v.
# It doesn't matter what vector. Just any vector
# that's guaranteed to not be pointing in the same
# direction.
if abs(v[0]) < abs(v[1]):
tv = Vector((1,0,0))
else:
tv = Vector((0,1,0))
# Use cross prouct to generate a vector perpendicular to
# both tv and (more importantly) v.
return v.cross(tv)
def rotation_difference(mat1, mat2):
""" Returns the shortest-path rotational difference between two
matrices.
"""
q1 = mat1.to_quaternion()
q2 = mat2.to_quaternion()
angle = acos(min(1,max(-1,q1.dot(q2)))) * 2
if angle > pi:
angle = -angle + (2*pi)
return angle
#########################################
## "Visual Transform" helper functions ##
#########################################
def get_pose_matrix_in_other_space(mat, pose_bone):
""" Returns the transform matrix relative to pose_bone's current
transform space. In other words, presuming that mat is in
armature space, slapping the returned matrix onto pose_bone
should give it the armature-space transforms of mat.
TODO: try to handle cases with axis-scaled parents better.
"""
rest = pose_bone.bone.matrix_local.copy()
rest_inv = rest.inverted()
if pose_bone.parent:
par_mat = pose_bone.parent.matrix.copy()
par_inv = par_mat.inverted()
par_rest = pose_bone.parent.bone.matrix_local.copy()
else:
par_mat = Matrix()
par_inv = Matrix()
par_rest = Matrix()
# Get matrix in bone's current transform space
smat = rest_inv * (par_rest * (par_inv * mat))
# Compensate for non-local location
#if not pose_bone.bone.use_local_location:
# loc = smat.to_translation() * (par_rest.inverted() * rest).to_quaternion()
# smat.translation = loc
return smat
def get_local_pose_matrix(pose_bone):
""" Returns the local transform matrix of the given pose bone.
"""
return get_pose_matrix_in_other_space(pose_bone.matrix, pose_bone)
def set_pose_translation(pose_bone, mat):
""" Sets the pose bone's translation to the same translation as the given matrix.
Matrix should be given in bone's local space.
"""
if pose_bone.bone.use_local_location is True:
pose_bone.location = mat.to_translation()
else:
loc = mat.to_translation()
rest = pose_bone.bone.matrix_local.copy()
if pose_bone.bone.parent:
par_rest = pose_bone.bone.parent.matrix_local.copy()
else:
par_rest = Matrix()
q = (par_rest.inverted() * rest).to_quaternion()
pose_bone.location = q * loc
def set_pose_rotation(pose_bone, mat):
""" Sets the pose bone's rotation to the same rotation as the given matrix.
Matrix should be given in bone's local space.
"""
q = mat.to_quaternion()
if pose_bone.rotation_mode == 'QUATERNION':
pose_bone.rotation_quaternion = q
elif pose_bone.rotation_mode == 'AXIS_ANGLE':
pose_bone.rotation_axis_angle[0] = q.angle
pose_bone.rotation_axis_angle[1] = q.axis[0]
pose_bone.rotation_axis_angle[2] = q.axis[1]
pose_bone.rotation_axis_angle[3] = q.axis[2]
else:
pose_bone.rotation_euler = q.to_euler(pose_bone.rotation_mode)
def set_pose_scale(pose_bone, mat):
""" Sets the pose bone's scale to the same scale as the given matrix.
Matrix should be given in bone's local space.
"""
pose_bone.scale = mat.to_scale()
def match_pose_translation(pose_bone, target_bone):
""" Matches pose_bone's visual translation to target_bone's visual
translation.
This function assumes you are in pose mode on the relevant armature.
"""
mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
set_pose_translation(pose_bone, mat)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='POSE')
def match_pose_rotation(pose_bone, target_bone):
""" Matches pose_bone's visual rotation to target_bone's visual
rotation.
This function assumes you are in pose mode on the relevant armature.
"""
mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
set_pose_rotation(pose_bone, mat)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='POSE')
def match_pose_scale(pose_bone, target_bone):
""" Matches pose_bone's visual scale to target_bone's visual
scale.
This function assumes you are in pose mode on the relevant armature.
"""
mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
set_pose_scale(pose_bone, mat)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='POSE')
##############################
## IK/FK snapping functions ##
##############################
def match_pole_target(ik_first, ik_last, pole, match_bone, length):
""" Places an IK chain's pole target to match ik_first's
transforms to match_bone. All bones should be given as pose bones.
You need to be in pose mode on the relevant armature object.
ik_first: first bone in the IK chain
ik_last: last bone in the IK chain
pole: pole target bone for the IK chain
match_bone: bone to match ik_first to (probably first bone in a matching FK chain)
length: distance pole target should be placed from the chain center
"""
a = ik_first.matrix.to_translation()
b = ik_last.matrix.to_translation() + ik_last.vector
# Vector from the head of ik_first to the
# tip of ik_last
ikv = b - a
# Get a vector perpendicular to ikv
pv = perpendicular_vector(ikv).normalized() * length
def set_pole(pvi):
""" Set pole target's position based on a vector
from the arm center line.
"""
# Translate pvi into armature space
ploc = a + (ikv/2) + pvi
# Set pole target to location
mat = get_pose_matrix_in_other_space(Matrix.Translation(ploc), pole)
set_pose_translation(pole, mat)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='POSE')
set_pole(pv)
# Get the rotation difference between ik_first and match_bone
angle = rotation_difference(ik_first.matrix, match_bone.matrix)
# Try compensating for the rotation difference in both directions
pv1 = Matrix.Rotation(angle, 4, ikv) * pv
set_pole(pv1)
ang1 = rotation_difference(ik_first.matrix, match_bone.matrix)
pv2 = Matrix.Rotation(-angle, 4, ikv) * pv
set_pole(pv2)
ang2 = rotation_difference(ik_first.matrix, match_bone.matrix)
# Do the one with the smaller angle
if ang1 < ang2:
set_pole(pv1)

217
utils.py Normal file
View File

@ -0,0 +1,217 @@
import bpy
from mathutils import Vector
from mathutils.geometry import intersect_line_line_2d
def get_mat(ob):
for sl in ob.material_slots:
if sl.material:
return sl.material
def link_mat_to_object(ob):
for sl in ob.material_slots:
m = sl.material
sl.link = 'OBJECT'
sl.material = m
def get_operator_from_id(idname):
if not '.' in idname:
return
m, o = idname.split(".")
try:
op = getattr(getattr(bpy.ops, m), o)
op.get_rna_type()
except Exception:
return
return op
'''
def canvas_space(point,scale,offset):
return scale*Vector(point)+Vector(offset)
'''
def get_object_color(ob):
if not ob.data.materials:
return
mat = get_mat(ob)
if not mat or not mat.node_tree or not mat.node_tree.nodes:
return
emit_node = mat.node_tree.nodes.get('Emission')
if not emit_node:
return
return emit_node.inputs['Color'].default_value
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])
if (dx>=0) and (dy>=0):
return dx*dy
def point_inside_rectangle(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)):
out = Vector(outside_point)
pt = Vector(point)
intersections = 0
for loop in loops:
for i,p in enumerate(loop):
a = Vector(verts[loop[i-1]])
b = Vector(verts[p])
if intersect_line_line_2d(pt,out,a,b):
intersections += 1
if intersections%2 == 1: #chek if the nb of intersection is odd
return True
def border_over_shape(border,verts,loops):
for loop in loops:
for i,p in enumerate(loop):
a = Vector(verts[loop[i-1]])
b = Vector(verts[p])
for j in range(0,4):
c = border[j-1]
d = border[j]
if intersect_line_line_2d(a,b,c,d):
return True
for point in verts:
if point_inside_rectangle(point,border):
return True
for point in border:
if point_over_shape(point,verts,loops):
return True
def border_loop(vert, loop):
border_edge =[e for e in vert.link_edges if e.is_boundary]
if border_edge:
for edge in border_edge:
other_vert = edge.other_vert(vert)
if not other_vert in loop:
loop.append(other_vert)
border_loop(other_vert, loop)
return loop
else:
return [vert]
def contour_loops(bm, vert_index=0, loops=None, vert_indices=None):
loops = loops or []
vert_indices = vert_indices or [v.index for v in bm.verts]
bm.verts.ensure_lookup_table()
loop = border_loop(bm.verts[vert_index], [bm.verts[vert_index]])
if len(loop) >1:
loops.append(loop)
for v in loop:
vert_indices.remove(v.index)
if len(vert_indices):
contour_loops(bm, vert_indices[0], loops, vert_indices)
return loops
def get_IK_bones(IK_last):
ik_chain = IK_last.parent_recursive
ik_len = 0
#Get IK len:
for c in IK_last.constraints:
if c.type == 'IK':
ik_len = c.chain_count -2
break
IK_root = ik_chain[ik_len]
IK_mid= ik_chain[:ik_len]
IK_mid.reverse()
IK_mid.append(IK_last)
return IK_root,IK_mid
def find_mirror(name):
mirror = None
prop= False
if name:
if name.startswith('[')and name.endswith(']'):
prop = True
name= name[:-2][2:]
match={
'R': 'L',
'r': 'l',
'L': 'R',
'l': 'r',
}
separator=['.','_']
if name.startswith(tuple(match.keys())):
if name[1] in separator:
mirror = match[name[0]]+name[1:]
if name.endswith(tuple(match.keys())):
if name[-2] in separator:
mirror = name[:-1]+match[name[-1]]
if mirror and prop == True:
mirror='["%s"]'%mirror
return mirror
else:
return None
def is_shape(ob):
shape = False
if ob.hide_render:
return shape
if ob.type in ('MESH', 'CURVE', 'FONT'):
if ob.rig_picker.shape_type == 'BONE':
if ob.rig_picker.name:
shape = True
else:
shape = True
return shape
def is_over_region(self,context,event):
inside = 2 < event.mouse_region_x < context.region.width -2 and \
2 < event.mouse_region_y < context.region.height -2 and \
[a for a in context.screen.areas if a.as_pointer()==self.adress] and \
not context.screen.show_fullscreen
return inside
def bound_box_center(ob):
points = [ob.matrix_world@Vector(p) for p in ob.bound_box]
x = [v[0] for v in points]
y = [v[1] for v in points]
z = [v[2] for v in points]
return (sum(x) / len(points), sum(y) / len(points),sum(z) / len(points))