24 Commits
Author SHA1 Message Date
joseph.henry d90fccb014 refactor shader constants 2025-12-08 17:15:02 +01:00
joseph.henry d09119f9d7 fix pose bones 2025-12-08 11:51:08 +01:00
joseph.henry 465a22d5a9 fix bone select 2025-12-05 17:59:06 +01:00
joseph.henry cc60a25aca add fake-bpy-module 2025-12-05 17:58:51 +01:00
joseph.henry 355e7b23cf git ignore revs 2025-12-05 10:05:55 +01:00
joseph.henry 622048d573 black format 2025-12-05 10:05:33 +01:00
jonas.holzman 9d09823321 Merge pull request 'Update of the documentation of the README' (#3) from manon.grabette/rig_picker:documentation into master
Reviewed-on: #3
Reviewed-by: Jonas Holzman <jonas@autourdeminuit.com>
2025-07-22 11:07:44 +02:00
manon.grabette eb5843063e doc : Update README with more documentation 2025-07-21 16:55:07 +02:00
jonas.holzman c7df358fe8 Merge pull request 'fix: shape geometry link behavior.' (#2) from swann.martinez/rig_picker:fix-collection-link-for-create-shape into master
Before the patch, shapes were systematically linked to the scene's main collection (Maybe that's the way it's supposed to be?), regardless of the collection selection. The patch changes this to link geometry only if no canvas collection is selected.

Reviewed-on: #2
Reviewed-by: Jonas Holzman <jonas@autourdeminuit.com>
2025-06-27 16:16:58 +02:00
s.martinez 21c4fe2117 clean: change typo 2025-06-24 18:08:40 +02:00
s.martinez f35fbb95b8 fix: shape geometry link behavior.
Before the patch, shapes were systematically linked to the scene's main collection, regardless of the collection selection. The patch changes this to link geometry only if no canvas collection is selected.
2025-06-24 17:59:53 +02:00
florentin.luce 30064faf3d Create a Property on the scene to handle the use_pick_bone 2025-06-16 15:00:01 +02:00
florentin.luce f4f8ee1613 unregister collection instead scene 2025-06-11 18:38:37 +02:00
florentin.luce e6c9dde63c remove scn.picker : not longer exists 2025-06-11 14:50:27 +02:00
florentin.luce e1abb88b7e create shape in rihg picker collections 2025-06-10 16:53:14 +02:00
florentin.luce 747cfa0f7a Show create shape in pose mode context 2025-06-10 16:21:38 +02:00
florentin.luce fbdd994d67 Rename all shapes from related bones name 2025-06-10 15:43:44 +02:00
florentin.luce 7d2216bc3b get picker collection since rig is stored on the picker collection and not on the scene 2025-06-10 15:24:10 +02:00
florentin.luce b4defc1296 fix canvas size 2025-06-10 15:24:09 +02:00
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
42 changed files with 1438 additions and 1050 deletions
+1
View File
@@ -0,0 +1 @@
622048d573ad87c5be84f65e64a7a3290c9305d1
+1
View File
@@ -0,0 +1 @@
3.12
+199 -6
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
@@ -13,17 +15,26 @@ Video of the previous version : https://vimeo.com/241970235
<a href="#installation">Installation</a>
</li>
<li>
<a href="#contents">Contents</a>
<a href="#create-a-picker-from-scratch">Create a picker from scratch</a>
</li>
<li>
<a href="#location">Location</a>
<a href="#how-to-use-an-existing-picker">How to use an existing picker </a>
</li>
<li>
<a href="#precisions">Precisions</a>
</li>
</ol>
<!-- INSTALLATION -->
## Installation
### Developper 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
@@ -32,10 +43,192 @@ Video of the previous version : https://vimeo.com/241970235
```sh
git clone ssh://git@git.autourdeminuit.com:222/autour_de_minuit/rig_picker.git
```
### User installation
<!-- CONTENTS -->
## Contents
Addon available on blender 4.X , you can process this way to add this on your blender
<br/>
![doc_picker_01.png](attachments/doc_picker_01.png)
<p align="center">
<img src="attachments/doc_picker_02.png">
</p>
<br/><br/>
<!-- Create a picker from scratch -->
## Create a picker from scratch
#### 1. Canvas Creation
<br/>
1. Once the addon is installed , you'll find the "rigging" tab on the right of your 3D viewport, on it to start your picker first click on the "+" on top here
<br/>
<p align="center">
<img src="attachments/Doc_picker_01.gif" width=630/>
</p>
<br/>
It will create a plane inside a "**controller**" collection, this plane will be the background of your picker. The collection is important, you can rename it **but don't delete it.** You can also rename the plane at your liking.
<br/>
If you click on this new plane, on the tab you will find (cf menu picture) :
- The name of the armature you want to pick
- The name of the canvas of your picker
- Symmetry
- The path needed to save your futur picker
- The save button
<br/>
<p align="center">
<img src="attachments/doc_picker_03.png" width=630/>
</p>
<br/><br/>
#### 2. Picker Controller Creation
<br/>
1. Next, select the controllers of your armature, and click on the button "Create Shape". It will create empty meshes with the same names as your controllers, it should already be connected to the good controller of your armature, but for more details on that read the rest of this documentation.
<br/>
<p align="center">
<img src="attachments/Doc_picker_02.gif" width=630/>
</p>
<br/><br/>
2. Create meshes inside the object of the picker's controller freshly generated
<br/>
<p align="center">
<img src="attachments/doc_picker_04.png" width=630/>
</p>
<br/>
3. On those shape, you can add a material with this button in the "rigging" tab, you can add one, delete one if there already is a material here or pick from the scene a material on another element. (it works with a multiple selection)
<br/>
<p align="center">
<img src="attachments/Doc_picker_03.gif" width=630/>
</p>
<br/>
You can also create some shapes display to represent the body of your character
<br/>
<p align="center">
<img src="attachments/doc_picker_06.png" width=630/>
</p>
<br/>
#### 3. Controller's Setup
<br/>
- Back on the "rigging" tab, you will find buttons :
- A create shape button
- A mirror shape button
- An auto Bone assign button
- A "Name from bones" button
<br/>
- The last part is where you define to what bone the shape is connected. If you used the button "create shape" the good bone should already be picked, but if you created your own mesh or if you want the mesh to pick another bone instead, you need to pick it from this selection window.
<br/>
It is also where you decide if this shape is supposed to be :
- A bone : meaning selectable on the picker
- A display : only there for a visual purpose (like representing the character's shape)
- An Operator : to be connected to a script.
<br/>
<p align="center">
<img src="attachments/doc_picker_07.png">
</p>
<br/>
#### 4. Final Touch
<br/>
- Place all of your meshes on top of the canvas, be careful as it works as photoshops layers, meaning, you need to put the mesh up on the Z axis if you want your shape on top on the 2D picker. The mesh with the vertex that is the higher up on the Z axis will be the top one on the picker display.
<br/>
<p align="center">
<img src="attachments/Doc_picker_04.gif" width=630/>
</p>
<br/>
- When you are all done, save the picker then click on the armature , one the "Sources" window a button to add your picker. Select the path and the file.
<br/>
<p align="center">
<img src="attachments/doc_picker_11.png" width=630/>
</p>
<br/>
<!-- Howto -->
## How to use an existing picker
- Open a "picker" view, select your armature in pose mode and in the "rigging" tab click on the small "+" sign. That will allow you to choose the picker file that you want. You can "reload" the picker if it doesn't show (or click on the "1" on the top right which center the page of the pickers in the view, in case you have more han one)
<br/>
<p align="center">
<img src="attachments/doc_picker_12.png" width=630/>
</p>
<br/>
- You can have more than one page for your picker if you need to detail things a bit more. For exemple in here we have a second canvas (in its own collection), that we'll save with a different name and load it as well.
<br/>
<p align="center">
<img src="attachments/doc_picker_13.png" width=630/>
</p>
<br/>
You can select now your controller directly on the picker.
<br/>
<!-- PRECISIONS -->
## Precisions
<br/>
- In this menu, you will find some features that have actions on your picker's controllers
<br/>
<p align="center">
<img src="attachments/doc_picker_14.png" width=630/>
</p>
<br/>
- If you double click on a controller, it will HIDE the whole layer of this bone
<br/>
<p align="center">
<img src="attachments/doc_picker_15.png" width=630/>
</p>
<!-- LOCATION -->
## Location
+5 -9
View File
@@ -8,18 +8,12 @@ bl_info = {
"warning": "",
"wiki_url": "",
"tracker_url": "",
"category": "Rigging"}
"category": "Rigging",
}
import importlib
modules = (
'.operators',
'.properties',
'.ui',
'.area',
'.gizmo',
'.draw_handlers'
)
modules = (".operators", ".properties", ".ui", ".area", ".gizmo", ".draw_handlers")
import bpy
@@ -29,6 +23,7 @@ if "bpy" in locals():
module = importlib.import_module(name, __name__)
importlib.reload(module)
def register():
if bpy.app.background:
return
@@ -36,6 +31,7 @@ def register():
module = importlib.import_module(name, __name__)
module.register()
def unregister():
if bpy.app.background:
return
+59 -43
View File
@@ -6,34 +6,35 @@ from .constants import PICKERS
# 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'''
"""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'
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')
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')
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):
@@ -47,51 +48,62 @@ class RP_MT_animation(Menu):
row.enabled = False
layout.separator()
op = layout.operator("rigpicker.call_operator", text='Select All')
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 = 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 = 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.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 = 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.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'
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 not 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()
if not context.space_data.node_tree:
ntree = bpy.data.node_groups.get('.rig_picker')
ntree = bpy.data.node_groups.get(".rig_picker")
if not ntree:
ntree = bpy.data.node_groups.new('.rig_picker', 'RigPickerTree')
ntree = bpy.data.node_groups.new(".rig_picker", "RigPickerTree")
context.space_data.node_tree = ntree
row = layout.row(align=True)
@@ -99,17 +111,19 @@ def draw_header(self, context):
row.menu("RP_MT_animation")
ob = context.object
if not ob or not ob.type == 'ARMATURE':
if not ob or not ob.type == "ARMATURE":
return
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.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()
@@ -120,43 +134,44 @@ def draw_header(self, context):
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=f"{i+1}").index = i
row.operator("rigpicker.fit_picker", text="", icon="FULLSCREEN_ENTER").index = -1
row.operator('rigpicker.fit_picker', text='', icon='FULLSCREEN_ENTER').index = -1
def tools_from_context(context, mode=None):
sp = context.space_data
if sp and sp.type == 'NODE_EDITOR' and sp.tree_type == 'RigPickerTree':
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':
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':
if sp and sp.type == "NODE_EDITOR" and sp.tree_type == "RigPickerTree":
return False
else:
return self._poll(context)
classes = (
RigPickerTree,
RP_MT_picker,
RP_MT_animation
)
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 = (
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
@@ -168,15 +183,16 @@ def register():
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
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
del NODE_HT_header._draw
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 912 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 874 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 KiB

+26 -4
View File
@@ -1,6 +1,7 @@
import gpu
from pathlib import Path
class LazyDict(dict):
def __getitem__(self, k):
v = super().__getitem__(k)
@@ -19,11 +20,32 @@ PICKERS = {}
MODULE_DIR = Path(__file__).parent
SHADER_DIR = MODULE_DIR / 'shaders'
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')
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)
def create_dashed_line_shader() -> gpu.types.GPUShader:
shader_info = gpu.types.GPUShaderCreateInfo()
shader_info.vertex_in(0, "VEC2", "pos")
shader_info.fragment_out(0, "VEC4", "fragColor")
shader_info.push_constant("VEC4", "color")
shader_info.push_constant("MAT4", "viewMatrix")
shader_info.push_constant("FLOAT", "dashSize")
shader_info.push_constant("FLOAT", "gapSize")
shader_info.vertex_source(vertex_shader)
shader_info.fragment_source(fragment_shader)
shader = gpu.shader.create_from_info(shader_info)
del shader_info
return shader
SHADERS["dashed_line"] = (
create_dashed_line_shader # lambda: gpu.types.GPUShader(vertex_shader, fragment_shader)
)
+17 -13
View File
@@ -1,19 +1,23 @@
import bpy
from .bl_utils import get_mat, get_collection_parents
def get_picker_collection(ob=None):
def get_picker_collection(ob: bpy.types.Object | None = None):
"""Return the picker collection of an object"""
if not ob:
ob = bpy.context.object
if not ob:
raise Exception("No object in context")
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):
if picker_col := next(
(c for c in get_collection_parents(col) if c.rig_picker.enabled), None
):
return picker_col
@@ -22,10 +26,10 @@ def is_shape(ob):
canvas = scn.rig_picker.canvas
if not canvas or ob.hide_render:
return False
shapes = {ob for col in canvas.users_collection for ob in col.all_objects}
if ob.type in ('MESH', 'CURVE', 'FONT') and ob in shapes:
if ob.type in ("MESH", "CURVE", "FONT") and ob in shapes:
return True
return False
@@ -34,20 +38,20 @@ def is_shape(ob):
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')
emit_node = mat.node_tree.nodes.get("Emission")
if not emit_node:
return
return emit_node.inputs['Color'].default_value
return emit_node.inputs["Color"].default_value
def get_operator_from_id(idname):
if not '.' in idname:
if not "." in idname:
return
m, o = idname.split(".")
@@ -56,5 +60,5 @@ def get_operator_from_id(idname):
op.get_rna_type()
except Exception:
return
return op
return op
+27 -18
View File
@@ -1,4 +1,3 @@
import bpy
@@ -30,94 +29,103 @@ def get_collection_parents(col, root=None, cols=None):
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']
areas = [a for w in windows for a in w.screen.areas if a.type == "VIEW_3D"]
if not areas:
print('No view 3d found')
print("No view 3d found")
return
view_3d = None
for area in areas:
if area.spaces.active.camera:
view_3d = area
if not view_3d:
view_3d = max(areas, key=lambda x :x.width)
return {'area': view_3d, 'region': view_3d.regions[-1]}
if not view_3d:
view_3d = max(areas, key=lambda x: x.width)
return {"area": view_3d, "region": view_3d.regions[-1]}
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.link = "OBJECT"
sl.material = m
def eval_attr(ob, name):
resolved = ob
for o in name.split("."):
resolved = getattr(resolved, o)
return resolved
def flip_name(name):
if not name:
return
if name.startswith('[') and name.endswith(']'): #It's a custom property
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 bpy.utils.flip_name(name)
def split_path(path) :
try :
def split_path(path):
try:
bone_name = path.split('["')[1].split('"]')[0]
except Exception:
bone_name = None
try :
try:
prop_name = path.split('["')[2].split('"]')[0]
except Exception:
prop_name = None
return bone_name, prop_name
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):
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 select_layer(args):
ob = bpy.context.object
layers =[]
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]
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]
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]
@@ -129,10 +137,11 @@ def hide_bones(args):
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']
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)
+27 -21
View File
@@ -1,28 +1,31 @@
import bpy
from mathutils import Vector
from mathutils.geometry import intersect_line_line_2d
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
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]
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))
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)
x_points = sorted(p[0] for p in points)
y_points = sorted(p[1] for p in points)
return [
@@ -32,28 +35,31 @@ def bounding_rect(points):
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 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_rect(point, rect):
return rect[0][0]< point[0]< rect[1][0] and rect[2][1]< point[1]< rect[0][1]
return rect[0][0] < point[0] < rect[1][0] and rect[2][1] < point[1] < rect[0][1]
def point_over_shape(point,verts,loops,outside_point=(-1,-1)):
def point_over_shape(point, verts, loops, outside_point=(-1, -1)):
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]])
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):
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
if intersections % 2 == 1: # chek if the nb of intersection is odd
return True
+246 -206
View File
@@ -1,11 +1,13 @@
import bpy
import gpu
from gpu_extras.batch import batch_for_shader
import blf
from mathutils import bvhtree, Vector, Color
from mathutils.geometry import (intersect_point_quad_2d, intersect_point_tri_2d,
intersect_tri_tri_2d)
from mathutils.geometry import (
intersect_point_quad_2d,
intersect_point_tri_2d,
intersect_tri_tri_2d,
)
from ..constants import PICKERS
from .addon_utils import get_operator_from_id
from .geometry_utils import bounding_rect
@@ -15,13 +17,21 @@ import re
import json
import os
import threading
import threading
class Shape:
def __init__(self, picker, points, polygons=None, edges=None, tooltip='', color=None, source_name=''):
self.type = 'display'
def __init__(
self,
picker,
points,
polygons=None,
edges=None,
tooltip="",
color=None,
source_name="",
):
self.type = "display"
self.picker = picker
self.rig = picker.rig
self.source_name = source_name
@@ -29,11 +39,11 @@ class Shape:
self.hover = False
self.press = False
self.shader = gpu.shader.from_builtin('UNIFORM_COLOR')
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
self.shader.bind()
#self.hover_shader = gpu.shader.from_builtin('UNIFORM_COLOR')
#self.hover_shader.uniform_float("color", [1, 1, 1, 0.1])
# self.hover_shader = gpu.shader.from_builtin('UNIFORM_COLOR')
# self.hover_shader.uniform_float("color", [1, 1, 1, 0.1])
self.color = color
self.hover_color = self.brighten_color(self.color)
@@ -43,15 +53,19 @@ class Shape:
self.polygons = polygons or []
self.edges = edges or []
self.p_batch = batch_for_shader(self.shader, 'TRIS', {"pos": self.points}, indices=self.polygons)
self.e_batch = batch_for_shader(self.shader, 'LINES', {"pos": self.points}, indices=self.edges)
self.p_batch = batch_for_shader(
self.shader, "TRIS", {"pos": self.points}, indices=self.polygons
)
self.e_batch = batch_for_shader(
self.shader, "LINES", {"pos": self.points}, indices=self.edges
)
self.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
@@ -70,16 +84,15 @@ class Shape:
elif len(color) == 4:
color = list(color)
else:
raise Exception('color must have a len of 3 or 4')
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)
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.bind()
self.shader.uniform_float("color", self.color)
if self.polygons:
@@ -92,28 +105,45 @@ class Shape:
if not intersect_point_quad_2d(location, *self.rect):
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'):
def press_event(self, mode="SET"):
self.press = True
def release_event(self, mode='SET'):
def release_event(self, mode="SET"):
self.press = False
class BoneShape(Shape):
def __init__(self, picker, points, polygons, edges, bone, tooltip='', color=None, source_name=''):
super().__init__(picker, points=points, polygons=polygons, edges=edges,
tooltip=tooltip, color=color, source_name=source_name)
def __init__(
self,
picker,
points,
polygons,
edges,
bone,
tooltip="",
color=None,
source_name="",
):
super().__init__(
picker,
points=points,
polygons=polygons,
edges=edges,
tooltip=tooltip,
color=color,
source_name=source_name,
)
self.type = 'bone'
self.type = "bone"
self.bone = bone
self.active_color = [1, 1, 1, 0.1]
self.select_color = self.brighten_color(self.hover_color)
@@ -125,23 +155,29 @@ class BoneShape(Shape):
if not self.bone:
return False
return self.bone in (bpy.context.selected_pose_bones or []) #self.bone.bone.select
return self.bone in (
bpy.context.selected_pose_bones or []
) # self.bone.bone.select
@property
def active(self):
if not self.bone:
return False
return self.bone == bpy.context.active_pose_bone #self.rig.data.bones.active == self.bone.bone
return (
self.bone == bpy.context.active_pose_bone
) # self.rig.data.bones.active == self.bone.bone
@property
def hide(self):
if not self.bone:
return False
#return self.bone not in (bpy.context.visible_pose_bones or [])
return self.bone.bone.hide or not any(l.is_visible for l in self.bone.bone.collections)
# return self.bone not in (bpy.context.visible_pose_bones or [])
return self.bone.bone.hide or not any(
l.is_visible for l in self.bone.bone.collections
)
@property
def bone_color(self):
@@ -149,45 +185,44 @@ class BoneShape(Shape):
return [0, 0, 0, 1]
if self.select and self.active:
return self.bone_colors['active']
return self.bone_colors["active"]
elif self.select:
return self.bone_colors['select']
return self.bone_colors["select"]
elif self.hide:
return self.bone_colors['hide']
return self.bone_colors["hide"]
else:
return self.bone_colors['normal']
return self.bone_colors["normal"]
def get_bone_colors(self):
theme = bpy.context.preferences.themes['Default']
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],
"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':
if self.bone.color.palette == "CUSTOM":
bone_color = self.bone
elif self.bone.color.palette == 'DEFAULT':
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]
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.blend_set("ALPHA")
gpu.state.line_width_set(1.0)
line_color = (1, 1, 1, 0.1)
@@ -200,17 +235,17 @@ class BoneShape(Shape):
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')
gpu.state.blend_set("NONE")
def draw_zero_scaled(self):
gpu.state.blend_set('ALPHA')
gpu.state.blend_set("ALPHA")
gpu.state.line_width_set(1.0)
line_color = (*self.bone_colors['normal'][:3], 0.2)
line_color = (*self.bone_colors["normal"][:3], 0.2)
color = Color(self.color[:3])
if self.hover or self.select:
color.v += 0.05
@@ -219,14 +254,14 @@ class BoneShape(Shape):
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')
gpu.state.blend_set("NONE")
def draw(self):
gpu.state.blend_set('ALPHA')
gpu.state.blend_set("ALPHA")
gpu.state.line_width_set(1.0)
if self.hide:
@@ -244,9 +279,9 @@ class BoneShape(Shape):
self.shader.uniform_float("color", color)
self.p_batch.draw(self.shader)
# Draw Outline
gpu.state.blend_set('NONE')
gpu.state.blend_set("NONE")
if self.select or self.active:
gpu.state.line_width_set(2.0)
@@ -254,51 +289,48 @@ class BoneShape(Shape):
self.e_batch.draw(self.shader)
gpu.state.line_width_set(1.0)
def assign_bone_event(self):
#print('assign_bone_event', self)
def assign_bone_event(self):
# print('assign_bone_event', self)
scn = bpy.context.scene
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')
return
active_bone = rig.data.bones.active
if not active_bone:
print('You need to have an active bone')
print(f"Source object {self.source_name} not found")
return
#print(active_bone, source_object)
#print(rig)
active_bone = rig.data.bones.active
if not active_bone:
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'):
def release_event(self, mode="SET"):
super().release_event(mode)
if self.hide or not self.bone:
return
select = True
if mode == 'SUBSTRACT':
if mode == "SUBSTRACT":
select = False
self.bone.bone.select = select
self.rig.pose.bones.get(self.bone.bone.name).select = select
if self.hover:
if mode != 'SUBSTRACT':
if mode != "SUBSTRACT":
self.rig.data.bones.active = self.bone.bone
def border_select(self, border, mode='SET'):
def border_select(self, border, mode="SET"):
if not self.bone:
return
if self.hide:
self.bone.bone.select = False
self.rig.pose.bones.get(self.bone.bone.name).select = False
return
rect_tri1 = self.rect[0], self.rect[1], self.rect[2]
@@ -307,77 +339,79 @@ class BoneShape(Shape):
border_tri1 = border[0], border[1], border[2]
border_tri2 = border[2], border[3], border[0]
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)):
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
if mode == 'SUBSTRACT':
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
self.rig.pose.bones.get(self.bone.bone.name).select = select
return
if intersect_tri_tri_2d(*border_tri2, *points):
self.bone.bone.select = select
self.rig.pose.bones.get(self.bone.bone.name).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, source_name=''):
super().__init__(picker, points=points, polygons=polygons, tooltip=tooltip,
color=color, source_name=source_name)
def __init__(
self, picker, points, polygons, operator, tooltip="", color=None, source_name=""
):
super().__init__(
picker,
points=points,
polygons=polygons,
tooltip=tooltip,
color=color,
source_name=source_name,
)
self.type = 'operator'
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.arguments = arguments#{k:eval(v)}
# self.operator = get_operator_from_id(self.operator)
if not tooltip:
self.tooltip = self.operator.replace('bpy.ops.', '').replace("'INVOKE_DEFAULT', ", '')
self.tooltip = self.operator.replace("bpy.ops.", "").replace(
"'INVOKE_DEFAULT', ", ""
)
#self.reg_args = re.compile(r'(\w+)=')
'''
# 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'):
def release_event(self, mode="SET"):
super().release_event(mode)
#args = self.parse_args()
# args = self.parse_args()
if not self.operator:
return
exec(self.operator)
#f'bpy.ops;{idname}'
# f'bpy.ops;{idname}'
#print(self.idname)
#print(self.arguments)
#else:
# print(self.idname)
# print(self.arguments)
# else:
# self.bone.bone.select = False
def draw(self):
@@ -390,10 +424,10 @@ class OperatorShape(Shape):
else:
return
gpu.state.blend_set('ALPHA')
gpu.state.blend_set("ALPHA")
self.shader.uniform_float("color", color)
self.p_batch.draw(self.shader)
gpu.state.blend_set('NONE')
gpu.state.blend_set("NONE")
class Picker:
@@ -406,48 +440,48 @@ class Picker:
self.shapes = []
self.box_select = None
self.hover_shape = None
self.shader = gpu.shader.from_builtin('UNIFORM_COLOR')
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
self.mouse = None
for shape_data in shapes:
if not shape_data['points']:
if not shape_data["points"]:
continue
if shape_data['type'] in ('CANVAS', 'DISPLAY'):
if shape_data["type"] in ("CANVAS", "DISPLAY"):
shape = Shape(
self,
points=shape_data['points'],
polygons=shape_data['polygons'],
edges=shape_data['edges'],
color=shape_data['color']
points=shape_data["points"],
polygons=shape_data["polygons"],
edges=shape_data["edges"],
color=shape_data["color"],
)
elif shape_data['type'] == 'BONE':
bone = rig.pose.bones.get(shape_data['bone'])
#if not bone:
elif shape_data["type"] == "BONE":
bone = rig.pose.bones.get(shape_data["bone"])
# if not bone:
# print(f'Bone {shape_data["bone"]} not exist')
# continue
shape = BoneShape(
self,
source_name=shape_data['source_name'],
points=shape_data['points'],
polygons=shape_data['polygons'],
edges=shape_data['edges'],
bone=bone,
color=shape_data['color']
source_name=shape_data["source_name"],
points=shape_data["points"],
polygons=shape_data["polygons"],
edges=shape_data["edges"],
bone=bone,
color=shape_data["color"],
)
elif shape_data['type'] == 'OPERATOR':
elif shape_data["type"] == "OPERATOR":
shape = OperatorShape(
self,
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'],
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)
@@ -456,36 +490,34 @@ class Picker:
def assign_bone_event(self):
for shape in self.shapes:
if shape.type == 'bone' and shape.hover:
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'):
bpy.ops.rigpicker.save_picker(index=self.index)
def press_event(self, mode="SET"):
for shape in self.shapes:
#print(s)
# print(s)
if shape.hover:
shape.press_event(mode)
else:
shape.press = False
def release_event(self, mode='SET'):
def release_event(self, mode="SET"):
# bpy.ops.pose.select_all(action='DESELECT')
#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}')
# print('PICKER release event', mode)
# print(f'type={event.type}, value={event.value}, ctrl={event.ctrl}, shift={event.shift}, alt={event.alt}')
for shape in self.shapes:
if shape.hover:
shape.release_event(mode)
shape.press = False
#bpy.context.area.tag_redraw()
'''
shape.press = False
# bpy.context.area.tag_redraw()
"""
picker.tooltip_event(event='SHOW')
region.tag_redraw()
@@ -495,8 +527,9 @@ class Picker:
picker.tooltip_event(event='HIDE')
bpy.app.timers.register(partial(tooltip, context.region), first_interval=1)
'''
'''
"""
"""
def tooltip_event(self, event):
@@ -508,7 +541,7 @@ class Picker:
self.tooltip = self.hover_shape.bone.name
#bpy.context.region.tag_redraw()
'''
"""
def border_select(self, border, mode):
border = [Vector(b) - Vector(self.translation) for b in border]
@@ -516,7 +549,7 @@ class Picker:
for shape in self.shapes:
shape.press = False
shape.hover = False
if shape.type != 'bone':
if shape.type != "bone":
continue
shape.border_select(border, mode)
@@ -531,16 +564,16 @@ class Picker:
elif shape.move_event(self.mouse):
self.hover_shape = shape
#if point_inside_rect(self.end, rect):
# if point_inside_rect(self.end, rect):
# over = point_over_shape(self.end,points, edges)
#if bpy.app.timers.is_registered(self.tooltip_event):
#try:
# if bpy.app.timers.is_registered(self.tooltip_event):
# try:
# bpy.app.timers.unregister(self.tooltip_event)
#except:
# except:
# pass
#bpy.app.timers.register(self.tooltip_event, first_interval=1)
# bpy.app.timers.register(self.tooltip_event, first_interval=1)
def draw(self):
with gpu.matrix.push_pop():
@@ -564,8 +597,7 @@ class Picker:
class PickerGroup:
def __init__(self, rig, pickers):
#self.view_location = Vector((0, 0))
# self.view_location = Vector((0, 0))
self.region = bpy.context.region
self.rig = rig
self.pickers = []
@@ -577,13 +609,13 @@ class PickerGroup:
self.tooltip_shape = None
self.tooltip_mouse = Vector((0, 0))
self.tooltip = ''
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))
@@ -592,12 +624,12 @@ class PickerGroup:
for picker in self.pickers:
height = picker.rect[1][1] - picker.rect[-1][1]
picker.translation = Vector((0, -y-height*0.5))
picker.translation = Vector((0, -y - height * 0.5))
picker.draw()
y += height + 50
#break #TODO for now only draw first picker
# break #TODO for now only draw first picker
def move_event(self, mouse):
self.mouse = mouse
@@ -618,14 +650,14 @@ class PickerGroup:
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 = ''
self.tooltip = ""
if self.timer:
self.timer.cancel()
@@ -633,8 +665,8 @@ class PickerGroup:
self.timer = threading.Timer(0.4, self.tooltip_event)
self.timer.start()
def press_event(self, mode='SET'):
#self.clear_tooltip()
def press_event(self, mode="SET"):
# self.clear_tooltip()
for picker in self.pickers:
if picker.under_mouse(self.location):
@@ -643,9 +675,9 @@ class PickerGroup:
for shape in picker.shapes:
shape.press = False
def release_event(self, mode='SET'):
if mode == 'SET':
for bone in self.rig.data.bones:
def release_event(self, mode="SET"):
if mode == "SET":
for bone in self.rig.pose.bones:
bone.select = False
for picker in self.pickers:
@@ -663,45 +695,46 @@ class PickerGroup:
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:
if mode == "SET":
for bone in self.rig.pose.bones:
bone.select = False
for picker in self.pickers:
picker.border_select(border, mode)
def clear_tooltip(self):
self.tooltip = ''
self.tooltip = ""
self.tooltip_shape = None
self.timer.cancel()
self.region.tag_redraw()
def tooltip_event(self):
#print('Tooltip Event', self)
# print('Tooltip Event', self)
#print(self.hover_shape, self.hover_shape.type)
# print(self.hover_shape, self.hover_shape.type)
if self.hover_shape and self.hover_shape.type != 'display':
if self.hover_shape.type == 'bone' and self.hover_shape.bone:
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()
self.tooltip_mouse = self.mouse
self.timer.cancel()
#print(self.tooltip)
# 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])
return bounding_rect(
[co - Vector(p.translation) for p in self.pickers for co in p.rect]
)
@property
def center(self):
@@ -710,23 +743,27 @@ class PickerGroup:
return center
def load_picker_data(rig):
if 'pickers' in rig.data.rig_picker:
picker_datas = [p.to_dict() for p in rig.rig_picker['pickers']]
def load_picker_data(rig: bpy.types.Object):
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 = []
for picker in rig.data.rig_picker.sources:
picker_path = Path(bpy.path.abspath(picker.source, library=rig.data.library))
picker_path = Path(
bpy.path.abspath(picker.source, library=rig.data.library)
)
if not picker_path.exists():
print(f'Picker path not exists: {picker_path.resolve()}')
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'))
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)
@@ -736,21 +773,24 @@ def get_picker_path(rig, source, start=None):
def pack_picker(rig, start=None):
if not "rig_picker" in rig.data:
return
pickers = []
for picker_source in rig.data.get('rig_picker', {}).get('sources', {}):
picker_path = get_picker_path(rig, picker_source['source'], start)
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')
print(f"{picker_path} not exists")
continue
picker_data = json.loads(picker_path.read_text(encoding='utf-8'))
picker_data = json.loads(picker_path.read_text(encoding="utf-8"))
pickers.append(picker_data)
rig.data['rig_picker']['pickers'] = pickers
rig.data["rig_picker"]["pickers"] = pickers
def unpack_picker(rig):
if 'rig_picker' not in rig.data.keys():
if "rig_picker" not in rig.data.keys():
return
if 'pickers' in rig.data['rig_picker'].keys():
del rig.data['rig_picker']['pickers']
if "pickers" in rig.data["rig_picker"].keys():
del rig.data["rig_picker"]["pickers"]
+49 -47
View File
@@ -6,30 +6,29 @@ from .geometry_utils import bound_box_center
from .addon_utils import get_object_color
def border_over_shape(border,verts,loops):
def border_over_shape(border, verts, loops):
for loop in loops:
for i,p in enumerate(loop):
a = Vector(verts[loop[i-1]])
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]
for j in range(0, 4):
c = border[j - 1]
d = border[j]
if intersect_line_line_2d(a,b,c,d):
if intersect_line_line_2d(a, b, c, d):
return True
for point in verts:
if point_inside_rect(point,border):
if point_inside_rect(point, border):
return True
for point in border:
if point_over_shape(point,verts,loops):
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_rectary]
border_edge = [e for e in vert.link_edges if e.is_rectary]
if border_edge:
for edge in border_edge:
@@ -43,6 +42,7 @@ def border_loop(vert, 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]
@@ -50,7 +50,7 @@ def contour_loops(bm, vert_index=0, loops=None, vert_indices=None):
bm.verts.ensure_lookup_table()
loop = border_loop(bm.verts[vert_index], [bm.verts[vert_index]])
if len(loop) >1:
if len(loop) > 1:
loops.append(loop)
for v in loop:
@@ -75,14 +75,13 @@ def get_shape_data(ob, matrix=None, depsgraph=None):
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.002)
bmesh.ops.dissolve_limit(bm, angle_limit=0.001745, verts=bm.verts, edges=bm.edges)
bmesh.ops.connect_verts_concave(bm, faces=bm.faces)
bmesh.ops.triangulate(bm, faces=bm.faces)
edges = [[v.index for v in e.verts] for e in bm.edges if len(e.link_faces)<=1]
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()
@@ -93,14 +92,14 @@ def get_shape_data(ob, matrix=None, depsgraph=None):
for vert in mesh.vertices:
co = matrix @ (ob.matrix_world @ Vector(vert.co))
points.append([round(co[0]), round(co[1])])
points.append([round(co[0], 1), round(co[1], 1)])
depths += [co[2]]
if depths:
depth = max(depths)
else:
print(f'{ob.name} has no vertices')
print(f"{ob.name} has no vertices")
depth = 0
for face in mesh.polygons:
@@ -113,67 +112,70 @@ def get_shape_data(ob, matrix=None, depsgraph=None):
color = [0.5, 0.5, 0.5]
shape = {
'source_name': ob.name,
'tooltip': ob.rig_picker.name,
'depth': depth,
'points': points,
'polygons': polygons,
'edges': edges,
'color': color,
'type': ob.rig_picker.shape_type
"source_name": ob.name,
"tooltip": ob.rig_picker.name,
"depth": depth,
"points": points,
"polygons": polygons,
"edges": edges,
"color": color,
"type": ob.rig_picker.shape_type,
}
if shape['type'] =='OPERATOR':
shape['operator'] = ob.rig_picker.operator
shape['shortcut'] = ob.rig_picker.shortcut
if shape["type"] == "OPERATOR":
shape["operator"] = ob.rig_picker.operator
shape["shortcut"] = ob.rig_picker.shortcut
elif shape["type"] == "BONE":
shape["bone"] = ob.rig_picker.name
elif shape['type'] =='BONE':
shape['bone'] = ob.rig_picker.name
eval_ob.to_mesh_clear()
return shape
def get_picker_data(collection):
picker_data = []
depsgraph = bpy.context.evaluated_depsgraph_get()
canvas = collection.rig_picker.canvas
if canvas.type == 'CURVE':
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)
canvas_coords = [canvas.matrix_world @ Vector((p.co)) for p in canvas_points]
height = abs(max(co.y for co in canvas_coords) - min(co.y for co in canvas_coords))
width = abs(max(co.x for co in canvas_coords) - min(co.x for co in canvas_coords))
center = sum(canvas_coords, Vector()) / len(canvas_coords)
scale = 2048 / max(height, width)# Reference height for the canvas
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
# 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(
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')
# print(f'{ob.name} of type {ob.type} not supported')
continue
picker_data.sort(key=lambda x : x['depth'])
picker_data.sort(key=lambda x: x["depth"])
#print(picker_datas)
# print(picker_datas)
return picker_data
#rig.data.rig_picker['shapes'] = picker_datas
# rig.data.rig_picker['shapes'] = picker_datas
+27 -23
View File
@@ -1,4 +1,3 @@
import bpy
import gpu
from gpu_extras.batch import batch_for_shader
@@ -20,12 +19,13 @@ def draw_rect_2d(position, width, height, color):
: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')
shader = gpu.shader.from_builtin("UNIFORM_COLOR")
batch = batch_for_shader(
shader, 'TRI_FAN',
shader,
"TRI_FAN",
{"pos": coords},
)
@@ -40,7 +40,7 @@ def draw_rect_2d(position, width, height, color):
def draw_callback_view():
space_data = bpy.context.space_data
if not space_data or not space_data.tree_type == 'RigPickerTree':
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
@@ -49,19 +49,19 @@ def draw_callback_view():
space_data.show_region_ui = False
ob = bpy.context.object
if not ob or ob.type !='ARMATURE' or not ob.data.rig_picker.sources:
if not ob or ob.type != "ARMATURE" or not ob.data.rig_picker.sources:
return
if ob not in PICKERS:
load_picker_data(ob)
picker_group = PICKERS[ob]
picker_group = PICKERS[ob]
picker_group.draw()
def draw_callback_px():
sp = bpy.context.space_data
if not sp.tree_type == 'RigPickerTree':
if not sp.tree_type == "RigPickerTree":
return
ob = bpy.context.object
@@ -74,37 +74,36 @@ def draw_callback_px():
text = picker_group.tooltip
ui_scale = bpy.context.preferences.system.ui_scale
#print('Draw text', text)
# 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])
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])
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')
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')
gpu.state.blend_set("NONE")
#blf.dimensions(font_id, text)
# blf.dimensions(font_id, text)
blf.enable(font_id, blf.SHADOW)
# BLF drawing routine
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(font_id, 5, 0.0, 0.0, 0.0, 1)
blf.shadow_offset(font_id, 2, -2)
blf.draw(font_id, text)
blf.disable(font_id, blf.SHADOW)
handle_view = None
handle_pixel = None
@@ -113,15 +112,20 @@ 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')
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')
bpy.types.SpaceNodeEditor.draw_handler_remove(handle_view, "WINDOW")
bpy.types.SpaceNodeEditor.draw_handler_remove(handle_pixel, "WINDOW")
handle_view = None
handle_pixel = None
handle_pixel = None
+34 -240
View File
@@ -1,17 +1,11 @@
import bpy
from bpy.props import (IntProperty, EnumProperty, BoolProperty)
from bpy.types import (AddonPreferences, GizmoGroup, Operator, Gizmo)
import gpu
from bpy.props import IntProperty, EnumProperty, BoolProperty
from bpy.types import AddonPreferences, GizmoGroup, Operator, Gizmo
from mathutils import Vector, Matrix, Euler
from gpu_extras.batch import batch_for_shader
from .constants import PICKERS, SHADERS
from .constants import PICKERS
from .core.picker import Picker
from .core.geometry_utils import bounding_rect
'''
@@ -41,133 +35,10 @@ class RP_OT_simple_operator(bpy.types.Operator):
print('Select Shape')
return {'FINISHED'}
'''
'''
class RP_OT_box_select(Operator):
"""Box Select bones in the picker view"""
bl_idname = "node.rp_box_select"
bl_label = "Picker Box Select"
mode: EnumProperty(items=[(i, i.title(), '') for i in ('SET', 'EXTEND', 'SUBSTRACT')])
@classmethod
def poll(cls, context):
if not is_picker_space(context.space_data):
return
ob = context.object
return ob and ob in PICKERS
def draw_callback(self):
#print('draw callback border')
if not self.draw_border:
return
gpu.state.blend_set('ALPHA')
#print('DRAW BORDER')
self.color_shader.bind()
self.color_shader.uniform_float("color", self.bg_color)
self.bg_batch.draw(self.color_shader)
self.dash_shader.bind()
matrix = gpu.matrix.get_projection_matrix()
self.dash_shader.uniform_float("color", self.border_color)
self.dash_shader.uniform_float("viewMatrix", matrix)
self.dash_shader.uniform_float("dashSize", 5)
self.dash_shader.uniform_float("gapSize", 4)
self.contour_batch.draw(self.dash_shader)
gpu.state.blend_set('NONE')
def invoke(self, context, event):
#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.color_shader = gpu.shader.from_builtin('UNIFORM_COLOR')
self.dash_shader = SHADERS['dashed_line']
self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(self.draw_callback, (), 'WINDOW', 'POST_PIXEL')
context.window_manager.modal_handler_add(self)
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
self.border = bounding_rect((self.start_mouse, self.mouse))
self.bg_batch = batch_for_shader(self.color_shader, 'TRI_FAN', {"pos": self.border})
self.contour_batch = batch_for_shader(self.dash_shader, 'LINE_LOOP', {"pos": self.border})
self.draw_border = True
self.region.tag_redraw()
if event.value == 'RELEASE':
return self.release_event(context)
return {'RUNNING_MODAL'}
def release_event(self, context):
scn = context.scene
if scn.rig_picker.use_pick_bone:
self.picker.assign_bone_event()
elif (self.start_mouse[0] != self.mouse[0] or self.start_mouse[1] != self.mouse[1]):
self.picker.border_select(self.border, self.mode)
else:
self.picker.move_event(self.mouse)
self.picker.release_event(self.mode)
bpy.ops.ed.undo_push(message="Box Select")
return self.exit(context)
def exit(self, context):
bpy.types.SpaceNodeEditor.draw_handler_remove(self._handle, 'WINDOW')
context.region.tag_redraw()
return {'FINISHED'}
'''
class RP_GT_gizmo(Gizmo):
def mode_from_event(self, event):
if event.alt:
return 'SUBSTRACT'
elif event.ctrl or event.shift:
return 'EXTEND'
else:
return 'SET'
def test_select(self, context, location):
ob = context.object
@@ -176,144 +47,67 @@ class RP_GT_gizmo(Gizmo):
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
# print(location)
return -1
def draw(self, context):
return
print('DRAW_SELECT', self.border)
if not self.draw_border:
return
gpu.state.blend_set('ALPHA')
gpu.state.depth_test_set('ALWAYS')
gpu.state.depth_mask_set(False)
#print('DRAW BORDER')
self.color_shader.bind()
self.color_shader.uniform_float("color", self.bg_color)
self.bg_batch.draw(self.color_shader)
self.dash_shader.bind()
matrix = gpu.matrix.get_projection_matrix()
self.dash_shader.uniform_float("color", self.border_color)
self.dash_shader.uniform_float("viewMatrix", matrix)
self.dash_shader.uniform_float("dashSize", 5)
self.dash_shader.uniform_float("gapSize", 4)
self.contour_batch.draw(self.dash_shader)
#gpu.state.depth_mask_set(False)
gpu.state.blend_set('NONE')
"""
def invoke(self, context, event):
print(f'invoke: type={event.type}, value={event.value}, ctrl={event.ctrl}, shift={event.shift}, alt={event.alt}')
print(self)
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.color_shader = gpu.shader.from_builtin('UNIFORM_COLOR')
self.dash_shader = SHADERS['dashed_line']
#self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(self.draw_callback, (), 'WINDOW', 'POST_PIXEL')
self.border = [(0, 0), (0, 0), (0, 0), (0, 0)]
#context.window_manager.modal_handler_add(self)
self.mode = self.mode_from_event(event)
self.picker.press_event(self.mode)
context.region.tag_redraw()
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}')
self.mouse = event.mouse_region_x, event.mouse_region_y
self.border = bounding_rect((self.start_mouse, self.mouse))
self.bg_batch = batch_for_shader(self.color_shader, 'TRI_FAN', {"pos": self.border})
self.contour_batch = batch_for_shader(self.dash_shader, 'LINE_LOOP', {"pos": self.border})
self.draw_border = True
context.region.tag_redraw()
#self.draw(context)
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):
scn = context.scene
print('EXIT')
if scn.rig_picker.use_pick_bone:
self.picker.assign_bone_event()
"""
elif (self.start_mouse[0] != self.mouse[0] or self.start_mouse[1] != self.mouse[1]):
self.picker.border_select(self.border, self.mode)
else:
self.picker.move_event(self.mouse)
self.picker.release_event(self.mode)
bpy.ops.ed.undo_push(message="Box Select")
self.draw_border = False
context.region.tag_redraw()
class RP_GT_gizmogroup(GizmoGroup):
""" test gizmo button 2d """
"""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'}
bl_space_type = "NODE_EDITOR"
bl_region_type = "WINDOW"
bl_options = {"PERSISTENT"}
@classmethod
def poll(cls, context):
return context.space_data.tree_type == 'RigPickerTree'
return context.space_data.tree_type == "RigPickerTree"
def setup(self, context):
self.gizmo = self.gizmos.new("RP_GT_gizmo")
self.gizmo.draw_border = False
self.gizmo.border = []
self.gizmo.use_draw_modal = True
classes = (RP_GT_gizmo, RP_GT_gizmogroup)
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)
bpy.utils.unregister_class(cls)
+4 -8
View File
@@ -1,20 +1,16 @@
from . import material
from . import picker
from . import shape
modules = (
material,
picker,
shape
)
modules = (material, picker, shape)
def register():
for mod in modules:
mod.register()
def unregister():
for mod in reversed(modules):
mod.unregister()
mod.unregister()
+37 -38
View File
@@ -1,4 +1,3 @@
import bpy
from bpy_extras import view3d_utils
@@ -6,56 +5,55 @@ 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'}
# bl_options = {'REGISTER', 'UNDO'}
def execute(self,context):
def execute(self, context):
scene = context.scene
mat = bpy.data.materials.new('UI')
mat = bpy.data.materials.new("UI")
mat.use_nodes = True
for node in mat.node_tree.nodes:
if node.type == 'OUTPUT_MATERIAL':
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])
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'}
return {"FINISHED"}
class RP_OT_remove_material(bpy.types.Operator):
bl_label = "Remove Ui Material"
bl_idname = "rigpicker.remove_mat"
#bl_options = {'REGISTER', 'UNDO'}
# bl_options = {'REGISTER', 'UNDO'}
def execute(self,context):
def execute(self, context):
scene = context.scene
#print(self.shape_type)
# print(self.shape_type)
for mat in context.object.data.materials:
bpy.data.materials.remove(mat)
context.area.tag_redraw()
return {'FINISHED'}
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()
# bl_options = {'REGISTER', 'UNDO'}
# first_mouse_x = IntProperty()
# first_value = FloatProperty()
def modal(self, context, event):
context.area.tag_redraw()
@@ -66,7 +64,7 @@ class RP_OT_eyedropper_material(bpy.types.Operator):
region = context.region
rv3d = context.region_data
if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
if event.type == "LEFTMOUSE" and event.value == "RELEASE":
self.mouse = event.mouse_region_x, event.mouse_region_y
dg = context.evaluated_depsgraph_get()
@@ -76,47 +74,48 @@ class RP_OT_eyedropper_material(bpy.types.Operator):
raycast = scene.ray_cast(dg, ray_origin, view_vector)
if raycast[0]==True:
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')]:
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.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"}
# return {'FINISHED'}
#return {'FINISHED'}
elif event.type in {'RIGHTMOUSE', 'ESC'}:
#context.object.location.x = self.first_value
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'}
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)
# 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')]
curves = [o for o in context.visible_objects if o.type in ("CURVE", "FONT")]
self.temp_ob = []
@@ -124,17 +123,17 @@ class RP_OT_eyedropper_material(bpy.types.Operator):
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 = 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')
# 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'}
return {"RUNNING_MODAL"}
classes = (
@@ -143,4 +142,4 @@ classes = (
RP_OT_eyedropper_material,
)
register, unregister = bpy.utils.register_classes_factory(classes)
register, unregister = bpy.utils.register_classes_factory(classes)
+279 -217
View File
@@ -6,10 +6,11 @@ from ..core.addon_utils import get_picker_collection
from ..core.bl_utils import get_view_3d_override, split_path, eval_attr
from ..core.geometry_utils import bounding_rect
from ..core.picker import get_picker_path, pack_picker, unpack_picker, load_picker_data
#from .func_bgl import draw_callback_px
#from .func_bgl import select_bone
#from core.picker import *
#from .utils import is_over_region
# from .func_bgl import draw_callback_px
# from .func_bgl import select_bone
# from core.picker import *
# from .utils import is_over_region
import gpu
from mathutils import Vector, Euler, Matrix
from gpu_extras.batch import batch_for_shader
@@ -18,34 +19,36 @@ import json
import os
def is_picker_space(space_data=None):
if not space_data:
space_data = bpy.context.space_data
if space_data and (space_data.type == 'NODE_EDITOR' and space_data.tree_type == 'RigPickerTree'):
if space_data and (
space_data.type == "NODE_EDITOR" and space_data.tree_type == "RigPickerTree"
):
return True
return False
class RP_OT_box_select(Operator):
"""Box Select bones in the picker view"""
bl_idname = "node.rp_box_select"
bl_label = "Picker Box Select"
mode: EnumProperty(items=[(i, i.title(), '') for i in ('SET', 'EXTEND', 'SUBSTRACT')])
mode: EnumProperty(
items=[(i, i.title(), "") for i in ("SET", "EXTEND", "SUBSTRACT")]
)
@classmethod
def poll(cls, context):
if not is_picker_space(context.space_data):
return
ob = context.object
return ob and ob in PICKERS
'''
"""
def mode_from_event(self, event):
if event.alt:
return 'SUBSTRACT'
@@ -53,17 +56,17 @@ class RP_OT_box_select(Operator):
return 'EXTEND'
else:
return 'SET'
'''
"""
def draw_callback(self):
#print('draw callback border')
# print('draw callback border')
if not self.draw_border:
return
gpu.state.blend_set('ALPHA')
gpu.state.blend_set("ALPHA")
# print('DRAW BORDER')
#print('DRAW BORDER')
self.color_shader.bind()
self.color_shader.uniform_float("color", self.bg_color)
self.bg_batch.draw(self.color_shader)
@@ -75,94 +78,100 @@ class RP_OT_box_select(Operator):
self.dash_shader.uniform_float("viewMatrix", matrix)
self.dash_shader.uniform_float("dashSize", 5)
self.dash_shader.uniform_float("gapSize", 4)
self.contour_batch.draw(self.dash_shader)
gpu.state.blend_set('NONE')
gpu.state.blend_set("NONE")
def invoke(self, context, event):
#print(f'invoke: type={event.type}, value={event.value}, ctrl={event.ctrl}, shift={event.shift}, alt={event.alt}')
# print(f'invoke: type={event.type}, value={event.value}, ctrl={event.ctrl}, shift={event.shift}, alt={event.alt}')
if context.object.mode != 'POSE':
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.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.shader = line_strip_shader
self.border_color = [1, 1, 1, 1]
self.bg_color = [1, 1, 1, 0.05]
#args = (self, context)
self.color_shader = gpu.shader.from_builtin('UNIFORM_COLOR')
self.dash_shader = SHADERS['dashed_line']
self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(self.draw_callback, (), 'WINDOW', 'POST_PIXEL')
# args = (self, context)
self.color_shader = gpu.shader.from_builtin("UNIFORM_COLOR")
self.dash_shader = SHADERS["dashed_line"]
self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(
self.draw_callback, (), "WINDOW", "POST_PIXEL"
)
context.window_manager.modal_handler_add(self)
self.picker.press_event(self.mode)
self.region.tag_redraw()
return {'RUNNING_MODAL'}
def modal(self, context, event):
return {"RUNNING_MODAL"}
def modal(self, context, event):
self.mouse = event.mouse_region_x, event.mouse_region_y
self.border = bounding_rect((self.start_mouse, self.mouse))
self.bg_batch = batch_for_shader(self.color_shader, 'TRI_FAN', {"pos": self.border})
self.contour_batch = batch_for_shader(self.dash_shader, 'LINE_LOOP', {"pos": self.border})
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':
if event.value == "RELEASE":
return self.release_event(context)
return {'RUNNING_MODAL'}
return {"RUNNING_MODAL"}
def release_event(self, context):
scn = context.scene
if scn.rig_picker.use_pick_bone:
if get_picker_collection():
self.picker.assign_bone_event()
elif (self.start_mouse[0] != self.mouse[0] or self.start_mouse[1] != self.mouse[1]):
elif (
self.start_mouse[0] != self.mouse[0] or self.start_mouse[1] != self.mouse[1]
):
self.picker.border_select(self.border, self.mode)
else:
self.picker.move_event(self.mouse)
self.picker.release_event(self.mode)
bpy.ops.ed.undo_push(message="Box Select")
return self.exit(context)
def exit(self, context):
bpy.types.SpaceNodeEditor.draw_handler_remove(self._handle, 'WINDOW')
bpy.types.SpaceNodeEditor.draw_handler_remove(self._handle, "WINDOW")
context.region.tag_redraw()
return {'FINISHED'}
return {"FINISHED"}
class RP_OT_picker_transform(Operator):
"""Transform Bones in the picker view"""
bl_idname = "node.picker_transform"
bl_label = "Transform Bone in Picker View"
mode : EnumProperty(items=[(m, m.title(), '') for m in ('ROTATE', 'SCALE')])
mode: EnumProperty(items=[(m, m.title(), "") for m in ("ROTATE", "SCALE")])
@classmethod
def poll(cls, context):
return context.selected_pose_bones and is_picker_space(context.space_data)
'''
"""
def draw_callback(self):
gpu.state.blend_set('ALPHA')
gpu.state.line_width_set(2)
@@ -180,18 +189,20 @@ class RP_OT_picker_transform(Operator):
gpu.state.line_width_set(1)
gpu.state.blend_set('NONE')
'''
"""
def invoke(self, context, event):
self.override = get_view_3d_override()
self.view_center = Vector((int(context.region.width / 2), int(context.region.height / 2)))
self.view_center = Vector(
(int(context.region.width / 2), int(context.region.height / 2))
)
self.mouse_start = Vector((event.mouse_region_x, event.mouse_region_y))
self.mouse = Vector((0, 0))
transform_type = context.scene.tool_settings.transform_pivot_point
self.center = context.active_pose_bone.matrix.to_translation()
if transform_type == 'MEDIAN_POINT':
if transform_type == "MEDIAN_POINT":
origins = [b.matrix.to_translation() for b in context.selected_pose_bones]
self.center = sum(origins, Vector()) / len(context.selected_pose_bones)
# self.bone_data = {}
@@ -199,38 +210,36 @@ class RP_OT_picker_transform(Operator):
# self.bone_data[bone] = bone.matrix.copy()
self.bone_data = {b: b.matrix.copy() for b in context.selected_pose_bones}
space = self.override['area'].spaces.active
space = self.override["area"].spaces.active
view_matrix = space.region_3d.view_matrix
if space.region_3d.view_perspective == 'CAMERA':
if space.region_3d.view_perspective == "CAMERA":
view_matrix = context.scene.camera.matrix_world
context.window.cursor_modal_set('MOVE_X')
context.window.cursor_modal_set("MOVE_X")
self.view_vector = Vector((0, 0, 1))
if self.mode == 'ROTATE':
if self.mode == "ROTATE":
self.view_vector.rotate(view_matrix)
elif self.mode == 'SCALE':
elif self.mode == "SCALE":
self.view_vector = Vector((0, 0, 0))
self.transform_type = "VIEW"
self.transform_types = ["VIEW", 'GLOBAL', 'LOCAL']
if context.scene.transform_orientation_slots[0].type == 'LOCAL':
self.transform_types = ["VIEW", 'LOCAL', 'GLOBAL']
self.transform_types = ["VIEW", "GLOBAL", "LOCAL"]
if context.scene.transform_orientation_slots[0].type == "LOCAL":
self.transform_types = ["VIEW", "LOCAL", "GLOBAL"]
self.transform_orientation = context.scene.transform_orientation_slots[0].type
#self.dash_shader = SHADERS['dashed_line']
# self.dash_shader = SHADERS['dashed_line']
#self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(self.draw_callback, (), 'WINDOW', 'POST_PIXEL')
# self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(self.draw_callback, (), 'WINDOW', 'POST_PIXEL')
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
return {"RUNNING_MODAL"}
def change_transform_type(self):
indices = {0: 1, 1:2, 2:1}
indices = {0: 1, 1: 2, 2: 1}
index = self.transform_types.index(self.transform_type)
new_index = indices[index]
self.transform_type = self.transform_types[new_index]
@@ -238,73 +247,73 @@ class RP_OT_picker_transform(Operator):
def release_event(self, context):
scn = bpy.context.scene
# Insert keyframe
#bpy.ops.ed.undo_push(message="Transform")
# Insert keyframe
# bpy.ops.ed.undo_push(message="Transform")
if scn.tool_settings.use_keyframe_insert_auto:
try:
bpy.ops.animtoolbox.insert_keyframe()
except Exception:
self.report({"WARNING"}, 'You need animtoolbox addon for inserting keyframe')
self.report(
{"WARNING"}, "You need animtoolbox addon for inserting keyframe"
)
self.exit(context)
def exit(self, context):
#bpy.types.SpaceNodeEditor.draw_handler_remove(self._handle, 'WINDOW')
# bpy.types.SpaceNodeEditor.draw_handler_remove(self._handle, 'WINDOW')
context.window.cursor_modal_restore()
#context.region.tag_redraw()
# context.region.tag_redraw()
def modal(self, context, event):
scn = context.scene
self.mouse = Vector((event.mouse_region_x, event.mouse_region_y))
if self.mode == 'ROTATE':
if self.mode == "ROTATE":
delta = (event.mouse_region_x - self.mouse_start[0]) / 100
elif self.mode == 'SCALE':
delta = 1 + (event.mouse_region_x - self.mouse_start[0]) / 100
#delta = (self.mouse - self.view_center).length / (self.mouse_start - self.view_center).length
elif self.mode == "SCALE":
delta = 1 + (event.mouse_region_x - self.mouse_start[0]) / 100
# delta = (self.mouse - self.view_center).length / (self.mouse_start - self.view_center).length
transform_type = self.transform_type
#self.batch = batch_for_shader(self.dash_shader, 'LINE_STRIP', {"pos": [self.mouse_start, self.mouse]})
#context.area.tag_redraw()
#print(event.type, event.value)
if self.mode == 'ROTATE' and event.type == "R" and event.value == 'PRESS':
# self.batch = batch_for_shader(self.dash_shader, 'LINE_STRIP', {"pos": [self.mouse_start, self.mouse]})
# context.area.tag_redraw()
# print(event.type, event.value)
if self.mode == "ROTATE" and event.type == "R" and event.value == "PRESS":
with context.temp_override(**self.override):
self.exit(context)
bpy.ops.transform.trackball('INVOKE_DEFAULT')
return {'FINISHED'}
bpy.ops.transform.trackball("INVOKE_DEFAULT")
return {"FINISHED"}
if event.type == "LEFTMOUSE" and event.value == 'RELEASE':
if event.type == "LEFTMOUSE" and event.value == "RELEASE":
self.release_event(context)
return {'FINISHED'}
return {"FINISHED"}
if event.type in {"RIGHTMOUSE", "ESC"}:
for bone, matrix in self.bone_data.items():
bone.matrix = matrix
self.exit(context)
return {'CANCELLED'}
return {"CANCELLED"}
elif event.type == "X" and event.value == 'PRESS':
elif event.type == "X" and event.value == "PRESS":
self.change_transform_type()
self.view_vector = Vector((1, 0, 0))
#transform_type = self.transform_orientation
# transform_type = self.transform_orientation
elif event.type == "Y" and event.value == 'PRESS':
elif event.type == "Y" and event.value == "PRESS":
self.change_transform_type()
self.view_vector = Vector((0, 1, 0))
#transform_type = self.transform_orientation
# transform_type = self.transform_orientation
elif event.type == "Z" and event.value == 'PRESS':
elif event.type == "Z" and event.value == "PRESS":
self.change_transform_type()
self.view_vector = Vector((0, 0, 1))
#transform_type = self.transform_orientation
# transform_type = self.transform_orientation
elif event.type == "MOUSEMOVE":
if self.mode == 'ROTATE':
if self.mode == "ROTATE":
transform_matrix = Matrix.Rotation(delta, 4, self.view_vector)
elif self.mode == 'SCALE':
elif self.mode == "SCALE":
if self.view_vector.length:
transform_matrix = Matrix.Scale(delta, 4, self.view_vector)
else:
@@ -315,14 +324,14 @@ class RP_OT_picker_transform(Operator):
if scn.tool_settings.transform_pivot_point == "INDIVIDUAL_ORIGINS":
center = matrix.to_translation()
if self.mode == 'ROTATE':
if transform_type == 'LOCAL':
if self.mode == "ROTATE":
if transform_type == "LOCAL":
view_vector = self.view_vector.copy()
view_vector.rotate(matrix)
transform_matrix = Matrix.Rotation(delta, 4, view_vector)
elif self.mode == 'SCALE':
if transform_type == 'LOCAL':
elif self.mode == "SCALE":
if transform_type == "LOCAL":
view_vector = self.view_vector.copy()
view_vector.rotate(matrix)
transform_matrix = Matrix.Scale(delta, 4, view_vector)
@@ -336,40 +345,40 @@ class RP_OT_picker_transform(Operator):
bone.matrix = mat
return {'RUNNING_MODAL'}
return {"RUNNING_MODAL"}
class RP_OT_toogle_property(Operator):
"""Invert a bone custom property"""
bl_idname = "rigpicker.toogle_property"
bl_label = 'Toogle Property'
bl_label = "Toogle Property"
data_path : StringProperty()
data_path: StringProperty()
@classmethod
def poll(cls, context):
return is_picker_space(context.space_data)
def execute(self,context):
def execute(self, context):
ob = context.object
try:
value = ob.path_resolve(self.data_path)
except Exception:
return {'CANCELLED'}
return {"CANCELLED"}
data = ob
prop_name = self.data_path
#if '.' in self.data_path:
# if '.' in self.data_path:
# data, prop = prop_name.rsplit('.', 1)
value = type(value)(not value)
exec(f'{repr(ob)}.{self.data_path} = {value}')
#setattr(data, prop_name, value)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='POSE')
value = type(value)(not value)
exec(f"{repr(ob)}.{self.data_path} = {value}")
# setattr(data, prop_name, value)
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="POSE")
if context.scene.tool_settings.use_keyframe_insert_auto:
bone_name, _ = split_path(self.data_path)
@@ -377,47 +386,45 @@ class RP_OT_toogle_property(Operator):
context.area.tag_redraw()
return {'FINISHED'}
return {"FINISHED"}
class RP_OT_reload_picker(Operator):
"""Reload the picker shapes"""
bl_idname = "rigpicker.reload_picker"
bl_label = 'Reload Picker'
bl_idname: str = "rigpicker.reload_picker"
bl_label: str = "Reload Picker"
#@classmethod
#def poll(cls, context):
# @classmethod
# def poll(cls, context):
# if not is_picker_space(context):
# return
def execute(self, context):
#PICKERS.clear()
if context.object.type == 'ARMATURE':
def execute(self, context: bpy.types.Context):
if not context.object:
return
if context.object.type == "ARMATURE":
rig = context.object
else:
collection = get_picker_collection(context.object)
rig = collection.rig_picker.rig
# print('Reload', rig)
load_picker_data(rig)
'''
for area in context.screen.areas:
#print(area.type, is_picker_space(area.spaces.active))
if is_picker_space(area.spaces.active):
print('Tag Redraw Region', area.type)
area.regions[0].tag_redraw()
area.tag_redraw()
'''
if context.screen:
for area in context.screen.areas:
if is_picker_space(area.spaces.active):
area.tag_redraw()
return {"FINISHED"}
class RP_OT_toogle_bone_layer(Operator):
"""Toogle bone layer visibility when double clicking on a bone"""
bl_idname = "rigpicker.toogle_bone_layer"
bl_label = 'Toogle Bone Layer'
bl_idname: str = "rigpicker.toogle_bone_layer"
bl_label: str = "Toogle Bone Layer"
@classmethod
def poll(cls, context):
@@ -427,7 +434,7 @@ class RP_OT_toogle_bone_layer(Operator):
ob = context.object
picker = PICKERS.get(ob)
if picker.hover_shape and picker.hover_shape.type == 'bone':
if picker.hover_shape and picker.hover_shape.type == "bone":
return True
def execute(self, context):
@@ -448,62 +455,64 @@ class RP_OT_toogle_bone_layer(Operator):
class RP_OT_context_menu_picker(Operator):
"""Display Menu with the custom properties of the hovered bone if any or the active bone"""
bl_idname = "node.context_menu_picker"
bl_label = 'Context Menu Picker'
bl_label = "Context Menu Picker"
@classmethod
def poll(cls, context):
return is_picker_space(context.space_data)
def execute(self, context):
bpy.ops.wm.call_menu(name='RP_MT_context_menu')
bpy.ops.wm.call_menu(name="RP_MT_context_menu")
return {"FINISHED"}
class RP_OT_pack_picker(Operator):
"""Pack Unpack the picker on the rig"""
bl_idname = "rigpicker.pack_picker"
bl_label = 'Pack Picker'
bl_label = "Pack Picker"
@classmethod
def poll(cls, context):
ob = context.object
return (ob and ob.type == 'ARMATURE' and ob.data.rig_picker.sources)
return ob and ob.type == "ARMATURE" and ob.data.rig_picker.sources
def execute(self, context):
print('Pack Pickers...')
print("Pack Pickers...")
rig = context.object
if 'pickers' in rig.data.rig_picker.keys():
if "pickers" in rig.data.rig_picker.keys():
unpack_picker(rig)
self.report({"INFO"}, f'The picker is unpacked')
self.report({"INFO"}, f"The picker is unpacked")
return {"FINISHED"}
pack_picker(rig)
if not rig.data.rig_picker['pickers']:
self.report({"ERROR"}, f'No picker have been packed')
return {"CANCELLED"}
if not rig.data.rig_picker["pickers"]:
self.report({"ERROR"}, f"No picker have been packed")
return {"CANCELLED"}
elif len(rig.data.rig_picker['pickers']) < len(rig.data.rig_picker.sources):
self.report({"WARNING"}, f'No all pickers have been packed')
return {"FINISHED"}
elif len(rig.data.rig_picker["pickers"]) < len(rig.data.rig_picker.sources):
self.report({"WARNING"}, f"No all pickers have been packed")
return {"FINISHED"}
self.report({"INFO"}, f'The picker is packed')
self.report({"INFO"}, f"The picker is packed")
return {"FINISHED"}
class RP_OT_call_operator(Operator):
bl_idname = "rigpicker.call_operator"
bl_label = 'Call operator'
bl_label = "Call operator"
operator : StringProperty()
arguments : StringProperty()
operator: StringProperty()
arguments: StringProperty()
invoke: BoolProperty()
view_3d : BoolProperty()
view_3d: BoolProperty()
@classmethod
def poll(cls, context):
@@ -515,30 +524,30 @@ class RP_OT_call_operator(Operator):
op = eval_attr(bpy.ops, properties.operator).get_rna_type()
return op.description
except AttributeError:
return 'Call operator'
return "Call operator"
def execute(self, context):
#context.area.tag_redraw()
def execute(self, context):
# context.area.tag_redraw()
override = {}
if self.view_3d:
override = get_view_3d_override()
arguments = json.loads(self.arguments or "{}")
with context.temp_override(**override):
try:
ops = eval_attr(bpy.ops, self.operator)
if self.invoke:
ops('INVOKE_DEFAULT', **arguments)
ops("INVOKE_DEFAULT", **arguments)
else:
ops(**arguments)
except Exception as e:
print(e)
self.report({"ERROR"}, f'The operator {self.operator} cannot be called')
self.report({"ERROR"}, f"The operator {self.operator} cannot be called")
return {"CANCELLED"}
context.area.tag_redraw()
return {"FINISHED"}
@@ -551,55 +560,77 @@ class RP_MT_context_menu(Menu):
def draw(self, context):
layout = self.layout
col = layout.column()
col.use_property_split = True
col.operator_context = "INVOKE_DEFAULT"
# col.use_property_split = True
ob = context.object
picker = PICKERS.get(ob)
if picker.hover_shape and picker.hover_shape.type == 'bone':
if picker.hover_shape and picker.hover_shape.type == "bone":
bone = picker.hover_shape.bone
else:
bone = context.active_pose_bone
if bone:
for key in bone.keys():
layout.prop(bone,f'["{key}"]', slider=True)
# Draw Space Switch Operator
if getattr(ob.data, "space_switch"):
space_switch = ob.data.space_switch
#layout.operator("rigpicker.show_bone_layer", text="Show Bone Layer", ).type = 'ACTIVE'
#layout.operator("rigidbody.objects_add", text="B Add Passive").type = 'PASSIVE'
data_paths = [f'pose.bones["{bone.name}"]["{k}"]' for k in bone.keys()]
space_bone = next(
(s for s in space_switch.bones if s.data_path in data_paths), None
)
if space_bone:
index = list(space_switch.bones).index(space_bone)
value = ob.path_resolve(space_bone.data_path)
space = next(
(s.name for s in space_bone.spaces if s.value == value), None
)
op = col.operator(
"spaceswitch.change_space", text=f"({space})", icon="PINNED"
)
op.index = index
col.separator()
if bone:
for key in bone.keys():
col.prop(bone, f'["{key}"]', slider=True)
# 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_add_picker_collection(Operator):
bl_idname = "rigpicker.add_picker_collection"
bl_label = "Add a Picker Collection"
bl_description = "Add a Picker Collection"
bl_options = {'UNDO', 'REGISTER'}
bl_options = {"UNDO", "REGISTER"}
@classmethod
def poll(cls, context):
ob = context.object
return (ob and ob.type == 'ARMATURE')
return ob and ob.type == "ARMATURE"
def execute(self, context):
scn = context.scene
ob = context.object
name = ob.name
if name.endswith('_rig'):
if name.endswith("_rig"):
name = name[:-4]
canvas_name = f'canevas_{name}'
canvas_name = f"canevas_{name}"
canvas_points = [(-0.5, 0.5, 0), (0.5, 0.5, 0), (0.5, -0.5, 0), (-0.5, -0.5, 0)]
canvas_faces = [(0, 1, 2, 3)]
canvas_mesh = bpy.data.meshes.new(canvas_name)
canvas_mesh.from_pydata(canvas_points, [], canvas_faces)
canvas_mesh.update(calc_edges=True)
canvas_ob = bpy.data.objects.new(canvas_name, canvas_mesh)
canvas_ob.rig_picker.shape_type = 'DISPLAY'
canvas_ob.rig_picker.shape_type = "DISPLAY"
col = bpy.data.collections.new(f'Picker {name}')
col = bpy.data.collections.new(f"Picker {name}")
col.rig_picker.enabled = True
col.rig_picker.rig = ob
col.rig_picker.canvas = canvas_ob
@@ -610,44 +641,44 @@ class RP_OT_add_picker_collection(Operator):
self.report({"INFO"}, f"New Picker Collection {col.name} Created")
return {'FINISHED'}
return {"FINISHED"}
class RP_OT_add_picker_source(Operator):
bl_idname = "rigpicker.add_picker_source"
bl_label = "Add a Picker source"
bl_description = "Add a Picker source"
bl_options = {'UNDO', 'REGISTER'}
bl_options = {"UNDO", "REGISTER"}
def execute(self, context):
arm = context.object.data
arm.rig_picker.sources.add()
return {'FINISHED'}
return {"FINISHED"}
class RP_OT_remove_picker_source(Operator):
bl_idname = "rigpicker.remove_picker_source"
bl_label = "Delete a Picker source"
bl_description = "Delete a Picker source"
bl_options = {'UNDO', 'REGISTER'}
bl_options = {"UNDO", "REGISTER"}
index : IntProperty(default=-1)
index: IntProperty(default=-1)
def execute(self, context):
arm = context.object.data
arm.rig_picker.sources.remove(self.index)
return {'FINISHED'}
return {"FINISHED"}
class RP_OT_fit_picker(Operator):
bl_idname = "rigpicker.fit_picker"
bl_label = "Fit Picker"
bl_description = "Fit Picker in 2d view"
bl_options = {'UNDO', 'REGISTER'}
bl_options = {"UNDO", "REGISTER"}
index : IntProperty()
index: IntProperty()
@classmethod
def poll(cls, context):
@@ -664,17 +695,22 @@ class RP_OT_fit_picker(Operator):
view2d = context.region.view2d
if self.index == -1:
#print('Picker Group')
# print('Picker Group')
picker = picker_group
view_rect = [view2d.view_to_region(*co, clip=False) for co in picker.rect]
bpy.ops.view2d.zoom_border(
xmin=round(view_rect[3][0]), xmax=round(view_rect[1][0]),
ymin=round(view_rect[3][1]), ymax=round(view_rect[1][1]),
wait_for_input=False, zoom_out=False
xmin=round(view_rect[3][0]),
xmax=round(view_rect[1][0]),
ymin=round(view_rect[3][1]),
ymax=round(view_rect[1][1]),
wait_for_input=False,
zoom_out=False,
)
region_center = Vector((context.region.width * 0.5, context.region.height * 0.5))
region_center = Vector(
(context.region.width * 0.5, context.region.height * 0.5)
)
view_center = view2d.region_to_view(*region_center)
view_offset = Vector(view_center) + (picker.center - Vector(view_center))
@@ -684,21 +720,27 @@ class RP_OT_fit_picker(Operator):
bpy.ops.view2d.pan(deltax=round(delta[0]), deltay=round(delta[1]))
return {'FINISHED'}
return {"FINISHED"}
keymaps = []
def register_keymaps():
wm = bpy.context.window_manager
km = wm.keyconfigs.addon.keymaps.new(name="Node Editor", space_type="NODE_EDITOR")
for i in range(10):
kmi = km.keymap_items.new("rigpicker.fit_picker", type=f"NUMPAD_{i}", value="PRESS")
kmi = km.keymap_items.new(
"rigpicker.fit_picker", type=f"NUMPAD_{i}", value="PRESS"
)
kmi.properties.index = i - 1
keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.call_operator", type="NUMPAD_PERIOD", value="PRESS")
kmi = km.keymap_items.new(
"rigpicker.call_operator", type="NUMPAD_PERIOD", value="PRESS"
)
kmi.properties.operator = "view3d.view_selected"
kmi.properties.view_3d = True
keymaps.append((km, kmi))
@@ -708,12 +750,16 @@ def register_keymaps():
kmi.properties.arguments = '{"action": "SELECT"}'
keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.call_operator", type="A", value="PRESS", alt=True)
kmi = km.keymap_items.new(
"rigpicker.call_operator", type="A", value="PRESS", alt=True
)
kmi.properties.operator = "pose.select_all"
kmi.properties.arguments = '{"action": "DESELECT"}'
keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.toogle_bone_layer", type="LEFTMOUSE", value="DOUBLE_CLICK")
kmi = km.keymap_items.new(
"rigpicker.toogle_bone_layer", type="LEFTMOUSE", value="DOUBLE_CLICK"
)
keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.call_operator", type="X", value="PRESS")
@@ -724,54 +770,69 @@ def register_keymaps():
kmi.properties.operator = "animtoolbox.insert_keyframe"
keymaps.append((km, kmi))
kmi = km.keymap_items.new("anim.keyframe_delete_v3d", type="K", value="PRESS", alt=True)
kmi = km.keymap_items.new(
"anim.keyframe_delete_v3d", type="K", value="PRESS", alt=True
)
keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.call_operator", type="G", value="PRESS")
kmi.properties.operator = 'transform.translate'
kmi.properties.operator = "transform.translate"
kmi.properties.view_3d = True
kmi.properties.invoke = True
keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.call_operator", type="G", value="PRESS", alt=True)
kmi.properties.operator = 'pose.loc_clear'
kmi = km.keymap_items.new(
"rigpicker.call_operator", type="G", value="PRESS", alt=True
)
kmi.properties.operator = "pose.loc_clear"
keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.picker_transform", type="R", value="PRESS")
kmi.properties.mode = 'ROTATE'
kmi.properties.mode = "ROTATE"
keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.call_operator", type="R", value="PRESS", alt=True)
kmi.properties.operator = 'pose.rot_clear'
kmi = km.keymap_items.new(
"rigpicker.call_operator", type="R", value="PRESS", alt=True
)
kmi.properties.operator = "pose.rot_clear"
keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.picker_transform", type="S", value="PRESS")
kmi.properties.mode = 'SCALE'
kmi.properties.mode = "SCALE"
keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.call_operator", type="S", value="PRESS", alt=True)
kmi.properties.operator = 'pose.scale_clear'
kmi = km.keymap_items.new(
"rigpicker.call_operator", type="S", value="PRESS", alt=True
)
kmi.properties.operator = "pose.scale_clear"
keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.context_menu_picker", type="RIGHTMOUSE", value="PRESS")
kmi = km.keymap_items.new(
"node.context_menu_picker", type="RIGHTMOUSE", value="PRESS"
)
keymaps.append((km, kmi))
'''
kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS")
kmi.properties.mode = 'SET'
kmi.properties.mode = "SET"
keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS", shift=True)
kmi.properties.mode = 'EXTEND'
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", alt=True)
kmi.properties.mode = 'SUBSTRACT'
# kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS", ctrl=True)
kmi = km.keymap_items.new(
"node.rp_box_select", type="LEFTMOUSE", value="PRESS", alt=True
)
kmi.properties.mode = "SUBSTRACT"
keymaps.append((km, kmi))
'''
#km = wm.keyconfigs.addon.keymaps.new(name="View2D")
#kmi = km.keymap_items.new("rigpicker.call_operator", type="MIDDLEMOUSE", value="PRESS")
#kmi.properties.operator = "bpy.ops.view2d.pan('INVOKE_DEFAULT')"
#keymaps.append((km, kmi))
# km = wm.keyconfigs.addon.keymaps.new(name="View2D")
# kmi = km.keymap_items.new("rigpicker.call_operator", type="MIDDLEMOUSE", value="PRESS")
# kmi.properties.operator = "bpy.ops.view2d.pan('INVOKE_DEFAULT')"
# keymaps.append((km, kmi))
def unregister_keymaps():
@@ -779,6 +840,7 @@ def unregister_keymaps():
km.keymap_items.remove(kmi)
keymaps.clear()
classes = (
RP_OT_box_select,
RP_OT_toogle_property,
@@ -792,19 +854,19 @@ classes = (
RP_OT_add_picker_source,
RP_OT_remove_picker_source,
RP_OT_fit_picker,
RP_OT_add_picker_collection
#RP_OT_ui_draw
RP_OT_add_picker_collection,
# 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)
bpy.utils.unregister_class(cls)
+104 -75
View File
@@ -1,4 +1,3 @@
import json
from os.path import abspath
from pathlib import Path
@@ -13,15 +12,23 @@ from ..core.bl_utils import link_mat_to_object, flip_name
class RP_OT_create_shape(Operator):
bl_label = 'Create UI shape'
bl_idname = 'rigpicker.create_shape'
#bl_options = {'REGISTER', 'UNDO'}
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')
return context.object and context.object.mode == "POSE"
def execute(self,context):
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
for col in bpy.data.collections:
if col.rig_picker.enabled:
self.layout.prop(col.rig_picker, "link_shape", text=col.name)
def execute(self, context):
scn = context.scene
vl = context.view_layer
@@ -34,14 +41,18 @@ class RP_OT_create_shape(Operator):
ob.rig_picker.name = name
verts=[(0,0,0)]
verts = [(0, 0, 0)]
edges = []
faces =[]
faces = []
mesh.from_pydata(verts, edges, faces)
for c in scn.rig_picker.canvas.users_collection:
c.objects.link(ob)
picker_selected_cols = [
col for col in bpy.data.collections if col.rig_picker.link_shape
]
for col in picker_selected_cols or [scn.collection]:
col.objects.link(ob)
ob.location.z = 0.05
ob.location.x = offset
@@ -54,39 +65,37 @@ class RP_OT_create_shape(Operator):
offset += 0.05
return {'FINISHED'}
return {"FINISHED"}
class RP_OT_name_from_bone(Operator):
bl_label = 'Name Shape from selected bones'
bl_idname = 'rigpicker.name_from_bone'
#bl_options = {'REGISTER', 'UNDO'}
bl_label = "Name Shape from selected bones"
bl_idname = "rigpicker.name_from_bone"
bl_description = "Rename all shapes from related bones name"
# bl_options = {'REGISTER', 'UNDO'}
def execute(self,context):
scene = context.scene
rig = scene.rig_picker.rig
bone = rig.data.bones.active
def execute(self, context):
if bone:
context.object.name = bone.name
context.object.rig_picker.name = bone.name
col = get_picker_collection(context.object)
return {'FINISHED'}
for ob in col.all_objects:
if ob.rig_picker.shape_type == "BONE":
ob.name = ob.rig_picker.name
return {"FINISHED"}
class RP_OT_mirror_shape(Operator):
bl_label = 'Mirror UI shape'
bl_idname = 'rigpicker.mirror_shape'
#bl_options = g
bl_label = "Mirror UI shape"
bl_idname = "rigpicker.mirror_shape"
# bl_options = g
@classmethod
def poll(cls, context):
return (context.object and context.object.type in ('MESH', 'CURVE', 'TEXT'))
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)
def execute(self, context):
collection = get_picker_collection(context.object)
objects = context.selected_objects
# Remove mirror object:
@@ -95,9 +104,13 @@ class RP_OT_mirror_shape(Operator):
continue
for mod in ob.modifiers:
if (mod.type == 'NODES' and mod.node_group.name == 'Symmetrize' and
mod.get('Socket_2') in objects):
if (
mod.type == "NODES"
and mod.node_group
and mod.node_group.name == "Symmetrize"
and mod.get("Socket_2") in objects
):
bpy.data.objects.remove(ob)
if collection.rig_picker.symmetry:
@@ -108,14 +121,14 @@ class RP_OT_mirror_shape(Operator):
for ob in objects:
flipped_name = flip_name(ob.name)
if flipped_name == ob.name:
suffix = '.L'
flipped_suffix = '.R'
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
flipped_name = f'{ob.name}{flipped_suffix}'
ob.name = f'{ob.name}{suffix}'
flipped_name = f"{ob.name}{flipped_suffix}"
ob.name = f"{ob.name}{suffix}"
flipped_object = ob.copy()
flipped_object.name = flipped_name
@@ -124,21 +137,21 @@ class RP_OT_mirror_shape(Operator):
for mod in list(flipped_object.modifiers):
flipped_object.modifiers.remove(mod)
# 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
# Add symmetrize modifier TODO add it into a resource
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 col in ob.users_collection:
col.objects.link(flipped_object)
if ob.rig_picker.shape_type == 'BONE':
if ob.rig_picker.shape_type == "BONE":
flipped_object.rig_picker.name = flip_name(ob.rig_picker.name)
elif ob.rig_picker.shape_type == 'FUNCTION':
elif ob.rig_picker.shape_type == "FUNCTION":
args = {}
for key,value in eval(ob.rig_picker.arguments).items():
for key, value in eval(ob.rig_picker.arguments).items():
if type(value) == list:
mirrored_value = []
for item in value:
@@ -150,32 +163,37 @@ class RP_OT_mirror_shape(Operator):
flipped_object.rig_picker.arguments = str(args)
return {'FINISHED'}
return {"FINISHED"}
class RP_OT_select_shape_type(Operator):
bl_label = 'Select Shape by Type'
bl_idname = 'rigpicker.select_shape_type'
#bl_options = {'REGISTER', 'UNDO'}
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')])
shape_type = bpy.props.EnumProperty(
items=[(i.upper(), i, "") for i in ("Bone", "Display", "Function")]
)
def draw(self,context):
def draw(self, context):
layout = self.layout
col = layout.column()
col.prop(self,'shape_type', expand=True)
col.prop(self, "shape_type", expand=True)
def execute(self,context):
def execute(self, context):
scene = context.scene
#print(self.shape_type)
# 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:
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'}
return {"FINISHED"}
def invoke(self, context, event):
wm = context.window_manager
@@ -183,10 +201,10 @@ class RP_OT_select_shape_type(Operator):
class RP_OT_save_picker(Operator):
bl_label = 'Store UI Data'
bl_idname = 'rigpicker.save_picker'
bl_label = "Store UI Data"
bl_idname = "rigpicker.save_picker"
index : IntProperty(default=-1)
index: IntProperty(default=-1)
def execute(self, context):
scn = context.scene
@@ -197,40 +215,51 @@ class RP_OT_save_picker(Operator):
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)
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'}
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')]
# 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'}
self.report({"ERROR"}, "Choose a Canvas")
return {"CANCELLED"}
picker_data = get_picker_data(collection)
picker_path = Path(bpy.path.abspath(collection.rig_picker.destination))
print(f'Save Picker to {picker_path}')
print(f"Save Picker to {picker_path}")
picker_path.write_text(json.dumps(picker_data))
bpy.ops.rigpicker.reload_picker()
return {"FINISHED"}
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
RP_OT_save_picker,
)
register, unregister = bpy.utils.register_classes_factory(classes)
register, unregister = bpy.utils.register_classes_factory(classes)
+45 -24
View File
@@ -1,10 +1,16 @@
import bpy
from bpy.props import (EnumProperty, StringProperty, PointerProperty, BoolProperty,
CollectionProperty, IntProperty)
from bpy.props import (
EnumProperty,
StringProperty,
PointerProperty,
BoolProperty,
CollectionProperty,
IntProperty,
)
import inspect
'''
"""
def get_operator_items(self,context):
items = []
from . import func_picker as mod
@@ -14,52 +20,58 @@ def get_operator_items(self,context):
items.append((name,name,""))
return items
'''
"""
def bones_item(self,context):
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,''))
items.append((bone.name, bone.name, ""))
return items
class RP_PG_picker_source(bpy.types.PropertyGroup):
source : StringProperty(subtype='FILE_PATH')
source: StringProperty(subtype="FILE_PATH")
class RP_PG_armature_ui_settings(bpy.types.PropertyGroup):
name: StringProperty()
source: StringProperty(subtype='FILE_PATH')
source: StringProperty(subtype="FILE_PATH")
sources: CollectionProperty(type=RP_PG_picker_source)
class RP_PG_object_ui_settings(bpy.types.PropertyGroup):
shape_type: EnumProperty(items=[(i.upper(), i, "") for i in ('Bone', 'Display', 'Operator')])
#idname: StringProperty()
#arguments: StringProperty()
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):
enabled : BoolProperty(default=False)
class RP_PG_collection_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)
#idname: EnumProperty(items=[])
destination: StringProperty(subtype='FILE_PATH')
#bone_list: bpy.props.EnumProperty(items = bones_item
use_pick_bone : BoolProperty(default=False)
destination: StringProperty(subtype="FILE_PATH")
use_pick_bone: BoolProperty(default=False)
link_shape: BoolProperty(default=False)
class RP_PG_scene_ui_settings(bpy.types.PropertyGroup):
use_pick_bone: BoolProperty(default=False)
class RP_OT_operator_selector(bpy.types.Operator):
bl_label = "Select function"
bl_idname = "rigpicker.operator_selector"
bl_property = "idname"
#bl_options = {'REGISTER', 'UNDO'}
# bl_options = {'REGISTER', 'UNDO'}
idname: EnumProperty(items=[])
@@ -67,36 +79,45 @@ class RP_OT_operator_selector(bpy.types.Operator):
ob = context.object
ob.rig_picker.idname = self.idname
context.area.tag_redraw()
return {'FINISHED'}
return {"FINISHED"}
def invoke(self, context, event):
wm = context.window_manager
wm.invoke_search_popup(self)
return {'FINISHED'}
return {"FINISHED"}
classes = (
RP_PG_picker_source,
RP_PG_object_ui_settings,
RP_PG_collection_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.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.Collection.rig_picker = bpy.props.PointerProperty(
type=RP_PG_collection_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
del bpy.types.Collection.rig_picker
del bpy.types.Object.rig_picker
del bpy.types.Armature.rig_picker
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
+13
View File
@@ -0,0 +1,13 @@
[project]
name = "rig-picker"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
[dependency-groups]
dev = [
"black>=25.11.0",
"fake-bpy-module>=20251003",
]
+5 -5
View File
@@ -2,11 +2,11 @@
flat in vec2 startPos;
in vec2 vertPos;
out vec4 fragColor;
// out vec4 fragColor;
uniform vec4 color;
uniform float dashSize;
uniform float gapSize;
// uniform vec4 color;
// uniform float dashSize;
// uniform float gapSize;
void main()
{
@@ -16,4 +16,4 @@ void main()
if (fract(dist / (dashSize + gapSize)) > dashSize/(dashSize + gapSize))
discard;
fragColor = color;
}
}
+3 -3
View File
@@ -1,10 +1,10 @@
layout (location = 0) in vec2 pos;
// layout (location = 0) in vec2 pos;
flat out vec2 startPos;
out vec2 vertPos;
uniform mat4 viewMatrix;
// uniform mat4 viewMatrix;
void main()
{
@@ -12,4 +12,4 @@ void main()
gl_Position = outPos;
vertPos = pos.xy / outPos.w;
startPos = vertPos;
}
}
+104 -50
View File
@@ -1,7 +1,8 @@
import bpy
from bpy.types import UIList
#import collections
#import inspect
# import collections
# import inspect
from .core.addon_utils import get_operator_from_id, get_picker_collection
from .core.bl_utils import get_mat
@@ -20,17 +21,19 @@ 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'
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)
self.layout.operator(
"rigpicker.add_picker_collection", text="", icon="ADD", emboss=False
)
def draw(self, context):
ob = context.object
@@ -41,11 +44,15 @@ class RP_PT_picker_maker_panel(bpy.types.Panel):
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 ')
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':
if ob.type == "ARMATURE":
box = col.box()
box.enabled = not ob.data.library
col = box.column(align=False)
@@ -54,27 +61,41 @@ class RP_PT_picker_maker_panel(bpy.types.Panel):
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)
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
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')
# 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_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):
@@ -86,59 +107,92 @@ class RP_PT_shape(bpy.types.Panel):
col = layout.column(align=False)
if collection:
#if context.collection and context.collection.rig_picker.enabled:
# if context.collection and context.collection.rig_picker.enabled:
col = layout.column(align=True)
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')
row.operator(
"rigpicker.create_shape", icon="MESH_DATA", text="Create Shape"
)
row.operator(
"rigpicker.mirror_shape", icon="MOD_MIRROR", text="Mirror Shape"
)
col.prop(
scn.rig_picker,
"use_pick_bone",
icon="EYEDROPPER",
text="Auto Bone Assign",
)
col.operator(
"rigpicker.name_from_bone", icon="SORTALPHA", text="Name From Bones"
)
else:
col = layout.column(align=True)
row = col.row(align=True)
row.operator(
"rigpicker.create_shape", icon="MESH_DATA", text="Create Shape"
)
if ob.type != 'ARMATURE':
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='')
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:
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']
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='')
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='')
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')
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":
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')
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')
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')
col.prop(ob.rig_picker, "name", text="Tooltip")
elif ob.rig_picker.shape_type == 'BONE':
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')
col.prop_search(
ob.rig_picker,
"name",
collection.rig_picker.rig.pose,
"bones",
text="Bone",
)
classes = (
#RP_UL_picker_source,
# RP_UL_picker_source,
RP_PT_picker_maker_panel,
RP_PT_shape
RP_PT_shape,
)
register, unregister = bpy.utils.register_classes_factory(classes)
register, unregister = bpy.utils.register_classes_factory(classes)
Generated
+126
View File
@@ -0,0 +1,126 @@
version = 1
revision = 3
requires-python = ">=3.12"
[[package]]
name = "black"
version = "25.11.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "mypy-extensions" },
{ name = "packaging" },
{ name = "pathspec" },
{ name = "platformdirs" },
{ name = "pytokens" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8c/ad/33adf4708633d047950ff2dfdea2e215d84ac50ef95aff14a614e4b6e9b2/black-25.11.0.tar.gz", hash = "sha256:9a323ac32f5dc75ce7470501b887250be5005a01602e931a15e45593f70f6e08", size = 655669, upload-time = "2025-11-10T01:53:50.558Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7f/12/5c35e600b515f35ffd737da7febdb2ab66bb8c24d88560d5e3ef3d28c3fd/black-25.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:80e7486ad3535636657aa180ad32a7d67d7c273a80e12f1b4bfa0823d54e8fac", size = 1772831, upload-time = "2025-11-10T02:03:47Z" },
{ url = "https://files.pythonhosted.org/packages/1a/75/b3896bec5a2bb9ed2f989a970ea40e7062f8936f95425879bbe162746fe5/black-25.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cced12b747c4c76bc09b4db057c319d8545307266f41aaee665540bc0e04e96", size = 1608520, upload-time = "2025-11-10T01:58:46.895Z" },
{ url = "https://files.pythonhosted.org/packages/f3/b5/2bfc18330eddbcfb5aab8d2d720663cd410f51b2ed01375f5be3751595b0/black-25.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb2d54a39e0ef021d6c5eef442e10fd71fcb491be6413d083a320ee768329dd", size = 1682719, upload-time = "2025-11-10T01:56:55.24Z" },
{ url = "https://files.pythonhosted.org/packages/96/fb/f7dc2793a22cdf74a72114b5ed77fe3349a2e09ef34565857a2f917abdf2/black-25.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae263af2f496940438e5be1a0c1020e13b09154f3af4df0835ea7f9fe7bfa409", size = 1362684, upload-time = "2025-11-10T01:57:07.639Z" },
{ url = "https://files.pythonhosted.org/packages/ad/47/3378d6a2ddefe18553d1115e36aea98f4a90de53b6a3017ed861ba1bd3bc/black-25.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a1d40348b6621cc20d3d7530a5b8d67e9714906dfd7346338249ad9c6cedf2b", size = 1772446, upload-time = "2025-11-10T02:02:16.181Z" },
{ url = "https://files.pythonhosted.org/packages/ba/4b/0f00bfb3d1f7e05e25bfc7c363f54dc523bb6ba502f98f4ad3acf01ab2e4/black-25.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51c65d7d60bb25429ea2bf0731c32b2a2442eb4bd3b2afcb47830f0b13e58bfd", size = 1607983, upload-time = "2025-11-10T02:02:52.502Z" },
{ url = "https://files.pythonhosted.org/packages/99/fe/49b0768f8c9ae57eb74cc10a1f87b4c70453551d8ad498959721cc345cb7/black-25.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:936c4dd07669269f40b497440159a221ee435e3fddcf668e0c05244a9be71993", size = 1682481, upload-time = "2025-11-10T01:57:12.35Z" },
{ url = "https://files.pythonhosted.org/packages/55/17/7e10ff1267bfa950cc16f0a411d457cdff79678fbb77a6c73b73a5317904/black-25.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:f42c0ea7f59994490f4dccd64e6b2dd49ac57c7c84f38b8faab50f8759db245c", size = 1363869, upload-time = "2025-11-10T01:58:24.608Z" },
{ url = "https://files.pythonhosted.org/packages/67/c0/cc865ce594d09e4cd4dfca5e11994ebb51604328489f3ca3ae7bb38a7db5/black-25.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:35690a383f22dd3e468c85dc4b915217f87667ad9cce781d7b42678ce63c4170", size = 1771358, upload-time = "2025-11-10T02:03:33.331Z" },
{ url = "https://files.pythonhosted.org/packages/37/77/4297114d9e2fd2fc8ab0ab87192643cd49409eb059e2940391e7d2340e57/black-25.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dae49ef7369c6caa1a1833fd5efb7c3024bb7e4499bf64833f65ad27791b1545", size = 1612902, upload-time = "2025-11-10T01:59:33.382Z" },
{ url = "https://files.pythonhosted.org/packages/de/63/d45ef97ada84111e330b2b2d45e1dd163e90bd116f00ac55927fb6bf8adb/black-25.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bd4a22a0b37401c8e492e994bce79e614f91b14d9ea911f44f36e262195fdda", size = 1680571, upload-time = "2025-11-10T01:57:04.239Z" },
{ url = "https://files.pythonhosted.org/packages/ff/4b/5604710d61cdff613584028b4cb4607e56e148801ed9b38ee7970799dab6/black-25.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:aa211411e94fdf86519996b7f5f05e71ba34835d8f0c0f03c00a26271da02664", size = 1382599, upload-time = "2025-11-10T01:57:57.427Z" },
{ url = "https://files.pythonhosted.org/packages/00/5d/aed32636ed30a6e7f9efd6ad14e2a0b0d687ae7c8c7ec4e4a557174b895c/black-25.11.0-py3-none-any.whl", hash = "sha256:e3f562da087791e96cefcd9dda058380a442ab322a02e222add53736451f604b", size = 204918, upload-time = "2025-11-10T01:53:48.917Z" },
]
[[package]]
name = "click"
version = "8.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "fake-bpy-module"
version = "20251003"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/3a/d7/8e34a795715d18497a257929ace8776a94d6c2c744e36b61e75567cbda64/fake_bpy_module-20251003.tar.gz", hash = "sha256:43d7fd082efc34497e238de3ad4d31fb850f2d1a8bb07418ae36eae56c1c7434", size = 973644, upload-time = "2025-10-03T06:19:57.638Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c6/24/d318f51ae1ebe953038984ed26fa0f9cd5466ef83da30c317282645695ec/fake_bpy_module-20251003-py3-none-any.whl", hash = "sha256:424f75fee6f300fc6d8e0d39abb28dbccd8777f611afa47ecdb9a03c00f2f43f", size = 1103011, upload-time = "2025-10-03T06:19:55.524Z" },
]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
]
[[package]]
name = "packaging"
version = "25.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
]
[[package]]
name = "pathspec"
version = "0.12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
]
[[package]]
name = "platformdirs"
version = "4.5.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" },
]
[[package]]
name = "pytokens"
version = "0.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4e/8d/a762be14dae1c3bf280202ba3172020b2b0b4c537f94427435f19c413b72/pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a", size = 17644, upload-time = "2025-11-05T13:36:35.34Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/84/25/d9db8be44e205a124f6c98bc0324b2bb149b7431c53877fc6d1038dddaf5/pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3", size = 12195, upload-time = "2025-11-05T13:36:33.183Z" },
]
[[package]]
name = "rig-picker"
version = "0.1.0"
source = { virtual = "." }
[package.dev-dependencies]
dev = [
{ name = "black" },
{ name = "fake-bpy-module" },
]
[package.metadata]
[package.metadata.requires-dev]
dev = [
{ name = "black", specifier = ">=25.11.0" },
{ name = "fake-bpy-module", specifier = ">=20251003" },
]