29 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
ChristopheSeux 150bae5920 Add menu and some cleanup 2024-02-26 11:26:49 +01:00
ChristopheSeux 917531f59a fix mouse_under in picker_group 2024-02-20 13:48:24 +01:00
ChristopheSeux c31a73f80f Picker Group 2024-02-19 11:53:15 +01:00
florentin.luce b855e8ac37 Merge pull request 'replace layers with collections' (#1) from for_blender_v4 into master
Reviewed-on: #1
2024-02-08 12:13:11 +01:00
florentin.luce 7627b76d64 replace layers with collections 2023-11-21 11:40:15 +01:00
44 changed files with 2496 additions and 1277 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 > 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. 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 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> <a href="#installation">Installation</a>
</li> </li>
<li> <li>
<a href="#contents">Contents</a> <a href="#create-a-picker-from-scratch">Create a picker from scratch</a>
</li> </li>
<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> </li>
</ol> </ol>
<!-- INSTALLATION --> <!-- INSTALLATION -->
## 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 1. Create your own local directory in
```sh ```sh
/home/<USER>/dev /home/<USER>/dev
@@ -32,10 +43,192 @@ Video of the previous version : https://vimeo.com/241970235
```sh ```sh
git clone ssh://git@git.autourdeminuit.com:222/autour_de_minuit/rig_picker.git git clone ssh://git@git.autourdeminuit.com:222/autour_de_minuit/rig_picker.git
``` ```
### User installation
<!-- CONTENTS --> Addon available on blender 4.X , you can process this way to add this on your blender
## Contents <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": "", "warning": "",
"wiki_url": "", "wiki_url": "",
"tracker_url": "", "tracker_url": "",
"category": "Rigging"} "category": "Rigging",
}
import importlib import importlib
modules = ( modules = (".operators", ".properties", ".ui", ".area", ".gizmo", ".draw_handlers")
'.operators',
'.properties',
'.panels',
'.area',
'.gizmo',
'.draw_handlers'
)
import bpy import bpy
@@ -29,6 +23,7 @@ if "bpy" in locals():
module = importlib.import_module(name, __name__) module = importlib.import_module(name, __name__)
importlib.reload(module) importlib.reload(module)
def register(): def register():
if bpy.app.background: if bpy.app.background:
return return
@@ -36,6 +31,7 @@ def register():
module = importlib.import_module(name, __name__) module = importlib.import_module(name, __name__)
module.register() module.register()
def unregister(): def unregister():
if bpy.app.background: if bpy.app.background:
return return
+120 -20
View File
@@ -1,78 +1,177 @@
import bpy import bpy
from bpy.types import NodeTree, NODE_PT_tools_active, NODE_HT_header from bpy.types import NodeTree, NODE_PT_tools_active, NODE_HT_header, Menu
from .constants import PICKERS
# Derived from the NodeTree base type, similar to Menu, Operator, Panel, etc. # Derived from the NodeTree base type, similar to Menu, Operator, Panel, etc.
class RigPickerTree(NodeTree): class RigPickerTree(NodeTree):
# Description string # 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. # Optional identifier string. If not explicitly defined, the python class name is used.
# Label for nice name display # Label for nice name display
bl_label = "Rig Picker" bl_label = "Rig Picker"
# Icon identifier # 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")
row = layout.row(align=True)
# Has at least one picker collection in the scene
if not [c.rig_picker.enabled for c in scn.collection.children_recursive]:
row.enabled = False
row.prop(scn.rig_picker, "use_pick_bone", text="Auto Bone Assign")
class RP_MT_animation(Menu):
"""Picker"""
bl_label = "Animation"
def draw(self, context):
layout = self.layout
layout.operator("rigpicker.toogle_bone_layer", icon="MOUSE_LMB_DRAG")
row = layout.row()
op = row.operator("node.context_menu_picker", icon="MOUSE_RMB")
if not context.active_pose_bone:
row.enabled = False
layout.separator()
op = layout.operator("rigpicker.call_operator", text="Select All")
op.operator = "pose.select_all"
op.arguments = '{"action": "SELECT"}'
op = layout.operator("rigpicker.call_operator", text="Select All")
op.operator = "pose.select_all"
op.arguments = '{"action": "DESELECT"}'
op = layout.operator("rigpicker.call_operator", text="Frame Selected")
op.operator = "view3d.view_selected"
op.view_3d = True
layout.separator()
layout.operator("rigpicker.call_operator", text="Insert Keyframe").operator = (
"animtoolbox.insert_keyframe"
)
layout.operator("anim.keyframe_delete_v3d", text="Delete Keyframe")
layout.separator()
op = layout.operator("rigpicker.call_operator", text="Move")
op.operator = "transform.translate"
op.invoke = True
op.view_3d = True
layout.operator("node.picker_transform", text="Rotate").mode = "ROTATE"
layout.operator("node.picker_transform", text="Scale").mode = "SCALE"
layout.separator()
layout.operator("rigpicker.call_operator", text="Reset Bone").operator = (
"animtoolbox.reset_bone"
)
layout.operator("rigpicker.call_operator", text="Clear Location").operator = (
"pose.loc_clear"
)
layout.operator("rigpicker.call_operator", text="Clear Rotation").operator = (
"pose.rot_clear"
)
layout.operator("rigpicker.call_operator", text="Clear Scale").operator = (
"pose.scale_clear"
)
def draw_header(self, context): def draw_header(self, context):
if 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 = self.layout
layout.template_header() layout.template_header()
#layout.separator_spacer()
if not context.space_data.node_tree: if not context.space_data.node_tree:
ntree = bpy.data.node_groups.get('.rig_picker') ntree = bpy.data.node_groups.get(".rig_picker")
if not ntree: 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 context.space_data.node_tree = ntree
#layout.template_ID(context.space_data, "node_tree", new="node.new_node_tree") row = layout.row(align=True)
#layout.separator_spacer() row.menu("RP_MT_picker")
row.menu("RP_MT_animation")
layout.operator('rigpicker.reload_picker', icon='FILE_REFRESH', text='') ob = context.object
if not ob or not ob.type == "ARMATURE":
return
#layout.prop('rigpicker.reload_picker', icon='FILE_REFRESH', text='') picker_group = PICKERS.get(ob)
if not picker_group:
return
if scn.rig_picker.use_pick_bone:
layout.alert = True
layout.label(text="Auto Bone Assign")
layout.prop(
scn.rig_picker, "use_pick_bone", icon="PANEL_CLOSE", text="", emboss=False
)
layout.separator_spacer() layout.separator_spacer()
row = layout.row()
row.enabled = False
row.label(text=ob.name)
layout.separator_spacer()
row = layout.row(align=True)
for i, picker in enumerate(picker_group.pickers):
row.operator("rigpicker.fit_picker", text=f"{i+1}").index = i
row.operator("rigpicker.fit_picker", text="", icon="FULLSCREEN_ENTER").index = -1
else:
self._draw(context)
def tools_from_context(context, mode=None): def tools_from_context(context, mode=None):
sp = context.space_data 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 [] return []
else: else:
return NODE_PT_tools_active._tools_from_context(context, mode) return NODE_PT_tools_active._tools_from_context(context, mode)
def tool_set_by_id(self, context): def tool_set_by_id(self, context):
sd = context.space_data 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"} return {"FINISHED"}
else: else:
return self._execute(context) return self._execute(context)
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
sp = context.space_data 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 return False
else: else:
return self._poll(context) return self._poll(context)
classes = (
RigPickerTree, classes = (RigPickerTree, RP_MT_picker, RP_MT_animation)
)
def register(): def register():
bpy.types.WM_OT_tool_set_by_id._execute = bpy.types.WM_OT_tool_set_by_id.execute #tool_set_by_id # Remove the tools inside the picker space
bpy.types.WM_OT_tool_set_by_id._execute = (
bpy.types.WM_OT_tool_set_by_id.execute
) # tool_set_by_id
bpy.types.WM_OT_tool_set_by_id.execute = tool_set_by_id bpy.types.WM_OT_tool_set_by_id.execute = tool_set_by_id
NODE_PT_tools_active._tools_from_context = NODE_PT_tools_active.tools_from_context NODE_PT_tools_active._tools_from_context = NODE_PT_tools_active.tools_from_context
@@ -84,6 +183,7 @@ def register():
for cls in classes: for cls in classes:
bpy.utils.register_class(cls) bpy.utils.register_class(cls)
def unregister(): def unregister():
for cls in reversed(classes): for cls in reversed(classes):
bpy.utils.unregister_class(cls) bpy.utils.unregister_class(cls)
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

+49
View File
@@ -1,2 +1,51 @@
import gpu
from pathlib import Path
class LazyDict(dict):
def __getitem__(self, k):
v = super().__getitem__(k)
if callable(v):
v = v()
super().__setitem__(k, v)
return v
def get(self, k, default=None):
if k in self:
return self.__getitem__(k)
return default
PICKERS = {} PICKERS = {}
MODULE_DIR = Path(__file__).parent
SHADER_DIR = MODULE_DIR / "shaders"
SHADERS = LazyDict()
vertex_shader = Path(SHADER_DIR, "dash_shader.vert").read_text(encoding="utf-8")
fragment_shader = Path(SHADER_DIR, "dash_shader.frag").read_text(encoding="utf-8")
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)
)
+27 -6
View File
@@ -1,6 +1,25 @@
import bpy import bpy
from .bl_utils import get_mat from .bl_utils import get_mat, get_collection_parents
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
):
return picker_col
def is_shape(ob): def is_shape(ob):
scn = bpy.context.scene scn = bpy.context.scene
@@ -10,11 +29,12 @@ def is_shape(ob):
shapes = {ob for col in canvas.users_collection for ob in col.all_objects} 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 True
return False return False
def get_object_color(ob): def get_object_color(ob):
if not ob.data.materials: if not ob.data.materials:
return return
@@ -23,14 +43,15 @@ def get_object_color(ob):
if not mat or not mat.node_tree or not mat.node_tree.nodes: if not mat or not mat.node_tree or not mat.node_tree.nodes:
return return
emit_node = mat.node_tree.nodes.get('Emission') emit_node = mat.node_tree.nodes.get("Emission")
if not emit_node: if not emit_node:
return return
return emit_node.inputs['Color'].default_value return emit_node.inputs["Color"].default_value
def get_operator_from_id(idname): def get_operator_from_id(idname):
if not '.' in idname: if not "." in idname:
return return
m, o = idname.split(".") m, o = idname.split(".")
+72 -41
View File
@@ -1,13 +1,41 @@
import bpy import bpy
def get_collection_parents(col, root=None, cols=None):
"""Return all direct collection parents
Args:
col (bpy.types.Collection): collection to get parents from
root (bpy.types.Collection, optional): collection to search in (recursive) instead of the scene collection.
Defaults to None.
cols (_type_, optional): for recursivity, store the parent collections.
Defaults to None.
Returns:
list[bpy.types.Collection]: a list of direct parents
"""
if cols is None:
cols = []
if root is None:
root = bpy.context.scene.collection
for sub in root.children:
if sub == col:
cols.append(root)
if len(sub.children):
cols = get_collection_parents(col, root=sub, cols=cols)
return cols
def get_view_3d_override(): def get_view_3d_override():
windows = bpy.context.window_manager.windows windows = bpy.context.window_manager.windows
areas = [a for w in windows for a in w.screen.areas if a.type == 'VIEW_3D'] areas = [a for w in windows for a in w.screen.areas if a.type == "VIEW_3D"]
if not areas: if not areas:
print('No view 3d found') print("No view 3d found")
return return
view_3d = None view_3d = None
@@ -16,55 +44,55 @@ def get_view_3d_override():
view_3d = area view_3d = area
if not view_3d: if not view_3d:
view_3d = max(areas, key=lambda x :x.width) view_3d = max(areas, key=lambda x: x.width)
return {"area": view_3d, "region": view_3d.regions[-1]}
return {'area': view_3d, 'region': view_3d.regions[-1]}
def get_mat(ob): def get_mat(ob):
for sl in ob.material_slots: for sl in ob.material_slots:
if sl.material: if sl.material:
return sl.material return sl.material
def link_mat_to_object(ob): def link_mat_to_object(ob):
for sl in ob.material_slots: for sl in ob.material_slots:
m = sl.material m = sl.material
sl.link = 'OBJECT' sl.link = "OBJECT"
sl.material = m sl.material = m
def find_mirror(name):
mirror = None
prop= False
if name: def eval_attr(ob, name):
resolved = ob
for o in name.split("."):
resolved = getattr(resolved, o)
return resolved
if name.startswith('[')and name.endswith(']'):
prop = True
name= name[:-2][2:]
match={ def flip_name(name):
'R': 'L', if not name:
'r': 'l', return
'L': 'R',
'l': 'r',
}
separator=['.','_']
if name.startswith(tuple(match.keys())):
if name[1] in separator:
mirror = match[name[0]]+name[1:]
if name.endswith(tuple(match.keys())):
if name[-2] in separator:
mirror = name[:-1]+match[name[-1]]
if mirror and prop == True:
mirror='["%s"]'%mirror
return mirror
if name.startswith("[") and name.endswith("]"): # It's a custom property
flipped_name = bpy.utils.flip_name(name[:-2][2:])
return f'["{flipped_name}"]'
else: else:
return None return bpy.utils.flip_name(name)
def split_path(path):
try:
bone_name = path.split('["')[1].split('"]')[0]
except Exception:
bone_name = None
try:
prop_name = path.split('["')[2].split('"]')[0]
except Exception:
prop_name = None
return bone_name, prop_name
def hide_layers(args): def hide_layers(args):
""" """ """ """
@@ -72,30 +100,32 @@ def hide_layers(args):
layers = [] layers = []
for bone in [b for b in ob.pose.bones if b.bone.select]: 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: if l and i not in layers:
layers.append(i) layers.append(i)
for i in layers: for i in layers:
ob.data.layers[i] = not ob.data.layers[i] ob.data.layers[i] = not ob.data.layers[i]
def select_layer(args): def select_layer(args):
ob = bpy.context.object ob = bpy.context.object
layers =[] layers = []
for bone in [b for b in ob.pose.bones if b.bone.select]: 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: for l in bone_layers:
if l not in layers: if l not in layers:
layers.append(l) layers.append(l)
for bone in ob.pose.bones: 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)): if len(set(bone_layers).intersection(layers)):
bone.bone.select = True bone.bone.select = True
def hide_bones(args): def hide_bones(args):
ob = bpy.context.object ob = bpy.context.object
selected_bone = [b for b in ob.pose.bones if b.bone.select] selected_bone = [b for b in ob.pose.bones if b.bone.select]
@@ -107,10 +137,11 @@ def hide_bones(args):
for bone in selected_bone: for bone in selected_bone:
bone.bone.hide = visibility bone.bone.hide = visibility
def select_all(args): def select_all(args):
ob = bpy.context.object ob = bpy.context.object
shapes = ob.data.rig_picker['shapes'] shapes = ob.data.rig_picker["shapes"]
bones = [s['bone'] for s in shapes if s['shape_type']=='BONE'] bones = [s["bone"] for s in shapes if s["shape_type"] == "BONE"]
for bone_name in bones: for bone_name in bones:
bone = ob.pose.bones.get(bone_name) bone = ob.pose.bones.get(bone_name)
+36 -20
View File
@@ -1,49 +1,65 @@
import bpy import bpy
from mathutils import Vector from mathutils import Vector
from mathutils.geometry import intersect_line_line_2d from mathutils.geometry import intersect_line_line_2d
def is_over_region(self,context,event): def is_over_region(self, context, event):
inside = 2 < event.mouse_region_x < context.region.width -2 and \ inside = (
2 < event.mouse_region_y < context.region.height -2 and \ 2 < event.mouse_region_x < context.region.width - 2
[a for a in context.screen.areas if a.as_pointer()==self.adress] and \ and 2 < event.mouse_region_y < context.region.height - 2
not context.screen.show_fullscreen and [a for a in context.screen.areas if a.as_pointer() == self.adress]
and not context.screen.show_fullscreen
)
return inside return inside
def bound_box_center(ob): 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] x = [v[0] for v in points]
y = [v[1] for v in points] y = [v[1] for v in points]
z = [v[2] 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 intersect_rectangles(bound, border): # returns None if rectangles don't intersect def bounding_rect(points):
dx = min(border[1][0],bound[1][0]) - max(border[0][0],bound[0][0]) x_points = sorted(p[0] for p in points)
dy = min(border[0][1],bound[0][1]) - max(border[2][1],bound[2][1]) y_points = sorted(p[1] for p in points)
if (dx>=0) and (dy>=0): return [
return dx*dy Vector((x_points[0], y_points[-1])),
Vector((x_points[-1], y_points[-1])),
Vector((x_points[-1], y_points[0])),
Vector((x_points[0], y_points[0])),
]
def point_inside_rectangle(point, rect):
return rect[0][0]< point[0]< rect[1][0] and rect[2][1]< point[1]< rect[0][1]
def point_over_shape(point,verts,loops,outside_point=(-1,-1)): 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]
def point_over_shape(point, verts, loops, outside_point=(-1, -1)):
out = Vector(outside_point) out = Vector(outside_point)
pt = Vector(point) pt = Vector(point)
intersections = 0 intersections = 0
for loop in loops: for loop in loops:
for i,p in enumerate(loop): for i, p in enumerate(loop):
a = Vector(verts[loop[i-1]]) a = Vector(verts[loop[i - 1]])
b = Vector(verts[p]) b = Vector(verts[p])
if intersect_line_line_2d(pt,out,a,b): if intersect_line_line_2d(pt, out, a, b):
intersections += 1 intersections += 1
if intersections%2 == 1: #chek if the nb of intersection is odd if intersections % 2 == 1: # chek if the nb of intersection is odd
return True return True
+484 -298
View File
File diff suppressed because it is too large Load Diff
+95 -73
View File
@@ -6,30 +6,29 @@ from .geometry_utils import bound_box_center
from .addon_utils import get_object_color 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 loop in loops:
for i,p in enumerate(loop): for i, p in enumerate(loop):
a = Vector(verts[loop[i-1]]) a = Vector(verts[loop[i - 1]])
b = Vector(verts[p]) b = Vector(verts[p])
for j in range(0,4): for j in range(0, 4):
c = border[j-1] c = border[j - 1]
d = border[j] d = border[j]
if intersect_line_line_2d(a,b,c,d): if intersect_line_line_2d(a, b, c, d):
return True return True
for point in verts: for point in verts:
if point_inside_rectangle(point,border): if point_inside_rect(point, border):
return True return True
for point in border: for point in border:
if point_over_shape(point,verts,loops): if point_over_shape(point, verts, loops):
return True return True
def border_loop(vert, loop): def border_loop(vert, loop):
border_edge =[e for e in vert.link_edges if e.is_boundary] border_edge = [e for e in vert.link_edges if e.is_rectary]
if border_edge: if border_edge:
for edge in border_edge: for edge in border_edge:
@@ -43,6 +42,7 @@ def border_loop(vert, loop):
else: else:
return [vert] return [vert]
def contour_loops(bm, vert_index=0, loops=None, vert_indices=None): def contour_loops(bm, vert_index=0, loops=None, vert_indices=None):
loops = loops or [] loops = loops or []
vert_indices = vert_indices or [v.index for v in bm.verts] 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() bm.verts.ensure_lookup_table()
loop = border_loop(bm.verts[vert_index], [bm.verts[vert_index]]) loop = border_loop(bm.verts[vert_index], [bm.verts[vert_index]])
if len(loop) >1: if len(loop) > 1:
loops.append(loop) loops.append(loop)
for v in loop: for v in loop:
@@ -61,67 +61,49 @@ def contour_loops(bm, vert_index=0, loops=None, vert_indices=None):
return loops return loops
def get_picker_datas(objects, canvas, rig):
picker_datas = [] def get_shape_data(ob, matrix=None, depsgraph=None):
if not matrix:
matrix = Matrix()
if not depsgraph:
depsgraph = bpy.context.evaluated_depsgraph_get()
gamma = 1 / 2.2 gamma = 1 / 2.2
eval_ob = ob.evaluated_get(depsgraph)
if canvas.type =='CURVE': mesh = eval_ob.to_mesh()
canvas_points = canvas.data.splines[0].points
else:
canvas_points = canvas.data.vertices
canvas_coords = [canvas.matrix_world@Vector((p.co)) for p in canvas_points]
canvas_x = [p[0] for p in canvas_coords]
canvas_y = [p[1] for p in canvas_coords]
canvas_center = sum(canvas_coords, Vector()) / len(canvas_coords)
canvas_scale_fac = 4096 / (max(canvas_y) - min(canvas_y))# Reference height for the canvas
objects.append(canvas)
dg = bpy.context.evaluated_depsgraph_get()
#sorted by their z axes
for ob in sorted(objects, key=lambda x: bound_box_center(x)[2]):
print('Storing shape', ob.name)
mesh = bpy.data.meshes.new_from_object(ob.evaluated_get(dg))
bm = bmesh.new() bm = bmesh.new()
bm.from_mesh(mesh) bm.from_mesh(mesh)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.002) 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.dissolve_limit(bm, angle_limit=0.001745, verts=bm.verts, edges=bm.edges)
bmesh.ops.connect_verts_concave(bm, faces=bm.faces) bmesh.ops.connect_verts_concave(bm, faces=bm.faces)
bmesh.ops.triangulate(bm, faces=bm.faces) bmesh.ops.triangulate(bm, faces=bm.faces)
#bm_loops = contour_loops(bm) edges = [[v.index for v in e.verts] for e in bm.edges if len(e.link_faces) <= 1]
#loops = [[l.index for l in loop] for loop in bm_loops]
loops = []
edges = [[v.index for v in e.verts] for e in bm.edges if len(e.link_faces)<=1]
bm.to_mesh(mesh) bm.to_mesh(mesh)
mesh.update() mesh.update()
bm.clear() bm.clear()
points = [] points = []
#edges = []
polygons = [] polygons = []
depths = []
for p in mesh.vertices: for vert in mesh.vertices:
point = ob.matrix_world@Vector((p.co)) co = matrix @ (ob.matrix_world @ Vector(vert.co))
point = (point - canvas_center) * canvas_scale_fac points.append([round(co[0], 1), round(co[1], 1)])
points.append([round(point[0]), round(point[1])])
for f in mesh.polygons: depths += [co[2]]
polygons.append([v for v in f.vertices])
#for e in mesh.edges: if depths:
# depth = max(depths)
# edges.append([v for v in e.vertices]) else:
print(f"{ob.name} has no vertices")
depth = 0
for face in mesh.polygons:
polygons.append([v for v in face.vertices])
color = get_object_color(ob) color = get_object_color(ob)
if color: if color:
@@ -130,30 +112,70 @@ def get_picker_datas(objects, canvas, rig):
color = [0.5, 0.5, 0.5] color = [0.5, 0.5, 0.5]
shape = { shape = {
'source_name': ob.name, "source_name": ob.name,
'tooltip': ob.rig_picker.name, "tooltip": ob.rig_picker.name,
'points': points, "depth": depth,
'polygons': polygons, "points": points,
'edges': edges, "polygons": polygons,
'loops': loops, "edges": edges,
'color': color, "color": color,
'type': 'CANVAS' if ob == canvas else ob.rig_picker.shape_type "type": ob.rig_picker.shape_type,
} }
if shape['type'] =='OPERATOR': if shape["type"] == "OPERATOR":
shape['operator'] = ob.rig_picker.operator shape["operator"] = ob.rig_picker.operator
#if ob.rig_picker.arguments: shape["shortcut"] = ob.rig_picker.shortcut
#shape['arguments'] = ob.rig_picker.arguments
#if ob.rig_picker.shortcut:
shape['shortcut'] = ob.rig_picker.shortcut
elif shape['type'] =='BONE': elif shape["type"] == "BONE":
shape['bone'] = ob.rig_picker.name shape["bone"] = ob.rig_picker.name
picker_datas.append(shape) eval_ob.to_mesh_clear()
return shape
#print(picker_datas) def get_picker_data(collection):
picker_data = []
return picker_datas depsgraph = bpy.context.evaluated_depsgraph_get()
#rig.data.rig_picker['shapes'] = picker_datas canvas = collection.rig_picker.canvas
if canvas.type == "CURVE":
canvas_points = canvas.data.splines[0].points
else:
canvas_points = canvas.data.vertices
canvas_coords = [canvas.matrix_world @ Vector((p.co)) for p in canvas_points]
height = abs(max(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
matrix = Matrix.Translation(-center)
matrix = Matrix.Scale(scale, 4) @ matrix
# sorted by their z axes
for ob in collection.all_objects:
if ob.instance_collection:
for shape in ob.instance_collection.all_objects:
picker_data.append(
get_shape_data(
shape, matrix=matrix @ ob.matrix_world, depsgraph=depsgraph
)
)
elif ob.type in ("MESH", "CURVE", "FONT"):
picker_data.append(get_shape_data(ob, matrix=matrix, depsgraph=depsgraph))
else:
# print(f'{ob.name} of type {ob.type} not supported')
continue
picker_data.sort(key=lambda x: x["depth"])
# print(picker_datas)
return picker_data
# rig.data.rig_picker['shapes'] = picker_datas
+78 -38
View File
@@ -1,73 +1,108 @@
import bpy import bpy
import gpu
from gpu_extras.batch import batch_for_shader
import blf import blf
from pathlib import Path from pathlib import Path
from .constants import PICKERS from .constants import PICKERS
from .core.picker import Picker from .core.picker import Picker, PickerGroup, load_picker_data
import json import json
def draw_callback_view(): def draw_rect_2d(position, width, height, color):
sp = bpy.context.space_data """
Draw a 2d rectangele.
if not sp or not sp.tree_type == 'RigPickerTree': :arg position: Position of the lower left corner.
:type position: 2D Vector
:arg width: Width of the rect.
:type width: float
:arg height: Height of the rect.
:type height: float
"""
coords = ((0, 0), (1, 0), (1, 1), (0, 1))
shader = gpu.shader.from_builtin("UNIFORM_COLOR")
batch = batch_for_shader(
shader,
"TRI_FAN",
{"pos": coords},
)
with gpu.matrix.push_pop():
gpu.matrix.translate(position)
gpu.matrix.scale((width, height))
shader.uniform_float("color", color)
batch.draw(shader)
def draw_callback_view():
space_data = bpy.context.space_data
if not space_data or not space_data.tree_type == "RigPickerTree":
return return
# Use the pin to know if this is the first time this picker window in created to hide the n panel
if not space_data.pin:
space_data.pin = True
space_data.show_region_ui = False
ob = bpy.context.object ob = bpy.context.object
if not ob or ob.type !='ARMATURE' or not ob.data.rig_picker.source: if not ob or ob.type != "ARMATURE" or not ob.data.rig_picker.sources:
return return
if ob not in PICKERS: if ob not in PICKERS:
if 'picker' in ob.data.rig_picker: load_picker_data(ob)
picker_datas = [s.to_dict() for s in ob.data.rig_picker['picker']]
else:
picker_path = Path(bpy.path.abspath(ob.data.rig_picker.source, library=ob.data.library))
if not picker_path.exists(): picker_group = PICKERS[ob]
print(f'Picker path not exists: {picker_path.resolve()}') picker_group.draw()
return
print('Load picker from', picker_path.resolve())
picker_datas = json.loads(picker_path.read_text())
#shapes = [s.to_dict() for s in ob.data.rig_picker['shapes']]
PICKERS[ob] = Picker(ob, shapes=picker_datas)
picker = PICKERS.get(ob)
picker.draw()
def draw_callback_px(): def draw_callback_px():
sp = bpy.context.space_data sp = bpy.context.space_data
if not sp.tree_type == 'RigPickerTree': if not sp.tree_type == "RigPickerTree":
return return
ob = bpy.context.object ob = bpy.context.object
picker = PICKERS.get(ob) picker_group = PICKERS.get(ob)
if not picker or not picker.tooltip: if not picker_group or not picker_group.tooltip:
return return
text = picker.tooltip region = bpy.context.region
text = picker_group.tooltip
#print('Draw text', text) ui_scale = bpy.context.preferences.system.ui_scale
# print('Draw text', text)
font_id = 0 font_id = 0
#blf.dimensions(font_id, text) blf.size(font_id, int(13 * ui_scale))
blf.enable(font_id, blf.SHADOW)
#gpu.state.blend_set('ALPHA')
margins = [12, 5]
text_size = blf.dimensions(font_id, text)
text_pos = (region.width - text_size[0] - margins[0], margins[1])
bg_pos = (text_pos[0] - margins[0], text_pos[1] - margins[1] - 1)
bg_size = (text_size[0] + 2 * margins[0], text_size[1] + 2 * margins[1])
gpu.state.blend_set("ALPHA")
draw_rect_2d(bg_pos, *bg_size, (0.1, 0.1, 0.1, 0.66))
gpu.state.blend_set("NONE")
# blf.dimensions(font_id, text)
blf.enable(font_id, blf.SHADOW)
# BLF drawing routine # BLF drawing routine
blf.position(font_id, picker.tooltip_mouse[0]-5, picker.tooltip_mouse[1]+5, 0) blf.position(font_id, round(text_pos[0]), round(text_pos[1]), 0)
blf.size(font_id, 14) blf.color(font_id, 0.85, 0.85, 0.85, 1)
blf.color(font_id, 1, 1, 1, 1)
blf.shadow(font_id , 5, 0.0, 0.0, 0.0, 1) blf.shadow(font_id, 5, 0.0, 0.0, 0.0, 1)
blf.shadow_offset(font_id, 2, -2) blf.shadow_offset(font_id, 2, -2)
blf.draw(font_id, text) blf.draw(font_id, text)
blf.disable(font_id, blf.SHADOW) blf.disable(font_id, blf.SHADOW)
#gpu.state.blend_set('NONE')
handle_view = None handle_view = None
handle_pixel = None handle_pixel = None
@@ -77,15 +112,20 @@ def register():
global handle_view global handle_view
global handle_pixel global handle_pixel
handle_view = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_view, (), 'WINDOW', 'POST_VIEW') handle_view = bpy.types.SpaceNodeEditor.draw_handler_add(
handle_pixel = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_px, (), 'WINDOW', 'POST_PIXEL') draw_callback_view, (), "WINDOW", "POST_VIEW"
)
handle_pixel = bpy.types.SpaceNodeEditor.draw_handler_add(
draw_callback_px, (), "WINDOW", "POST_PIXEL"
)
def unregister(): def unregister():
global handle_view global handle_view
global handle_pixel global handle_pixel
bpy.types.SpaceNodeEditor.draw_handler_remove(handle_view, 'WINDOW') 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_pixel, "WINDOW")
handle_view = None handle_view = None
handle_pixel = None handle_pixel = None
+19 -30
View File
@@ -1,16 +1,13 @@
import bpy import bpy
from bpy.props import (IntProperty, EnumProperty, BoolProperty) from bpy.props import IntProperty, EnumProperty, BoolProperty
from bpy.types import (AddonPreferences, GizmoGroup, Operator, Gizmo) from bpy.types import AddonPreferences, GizmoGroup, Operator, Gizmo
from mathutils import Vector, Matrix, Euler from mathutils import Vector, Matrix, Euler
from .constants import PICKERS from .constants import PICKERS
from .core.picker import Picker from .core.picker import Picker
''' '''
class RP_OT_simple_operator(bpy.types.Operator): class RP_OT_simple_operator(bpy.types.Operator):
"""Tooltip""" """Tooltip"""
@@ -40,10 +37,6 @@ class RP_OT_simple_operator(bpy.types.Operator):
''' '''
class RP_GT_gizmo(Gizmo): class RP_GT_gizmo(Gizmo):
def test_select(self, context, location): def test_select(self, context, location):
@@ -55,25 +48,25 @@ class RP_GT_gizmo(Gizmo):
picker.move_event(location) picker.move_event(location)
#if bpy.app.timers.is_registered(tooltip): # if bpy.app.timers.is_registered(tooltip):
# bpy.app.timers.unregister(tooltip) # bpy.app.timers.unregister(tooltip)
#context.region.tag_redraw() # context.region.tag_redraw()
#picker.tooltip_event(event='START')
#bpy.app.timers.register(partial(tooltip, context.region), first_interval=1)
#print(location)
# picker.tooltip_event(event='START')
# bpy.app.timers.register(partial(tooltip, context.region), first_interval=1)
# print(location)
context.region.tag_redraw() context.region.tag_redraw()
return -1 # print(location)
return -1
def draw(self, context): def draw(self, context):
return return
'''
"""
def invoke(self, context, event): def invoke(self, context, event):
print(f'invoke: type={event.type}, value={event.value}, ctrl={event.ctrl}, shift={event.shift}, alt={event.alt}') print(f'invoke: type={event.type}, value={event.value}, ctrl={event.ctrl}, shift={event.shift}, alt={event.alt}')
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
@@ -86,33 +79,29 @@ class RP_GT_gizmo(Gizmo):
def exit(self, context, cancel): def exit(self, context, cancel):
print('EXIT') print('EXIT')
''' """
class RP_GT_gizmogroup(GizmoGroup): class RP_GT_gizmogroup(GizmoGroup):
""" test gizmo button 2d """ """test gizmo button 2d"""
bl_idname = "view3d.gizmo_button_2d" bl_idname = "view3d.gizmo_button_2d"
bl_label = "Test button 2d" bl_label = "Test button 2d"
bl_space_type = 'NODE_EDITOR' bl_space_type = "NODE_EDITOR"
bl_region_type = 'WINDOW' bl_region_type = "WINDOW"
bl_options = {'PERSISTENT'} bl_options = {"PERSISTENT"}
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return context.space_data.tree_type == 'RigPickerTree' return context.space_data.tree_type == "RigPickerTree"
def setup(self, context): def setup(self, context):
self.gizmo = self.gizmos.new("RP_GT_gizmo") self.gizmo = self.gizmos.new("RP_GT_gizmo")
classes = (RP_GT_gizmo, RP_GT_gizmogroup)
classes = (
RP_GT_gizmo,
RP_GT_gizmogroup
)
def register(): def register():
for cls in classes: for cls in classes:
bpy.utils.register_class(cls) bpy.utils.register_class(cls)
+3 -7
View File
@@ -1,20 +1,16 @@
from . import material from . import material
from . import picker from . import picker
from . import shape from . import shape
modules = ( modules = (material, picker, shape)
material,
picker,
shape
)
def register(): def register():
for mod in modules: for mod in modules:
mod.register() mod.register()
def unregister(): def unregister():
for mod in reversed(modules): for mod in reversed(modules):
mod.unregister() mod.unregister()
+36 -37
View File
@@ -1,4 +1,3 @@
import bpy import bpy
from bpy_extras import view3d_utils from bpy_extras import view3d_utils
@@ -6,56 +5,55 @@ from bpy_extras import view3d_utils
class RP_OT_add_material(bpy.types.Operator): class RP_OT_add_material(bpy.types.Operator):
bl_label = "Add Ui Material" bl_label = "Add Ui Material"
bl_idname = "rigpicker.add_mat" bl_idname = "rigpicker.add_mat"
#bl_options = {'REGISTER', 'UNDO'} # bl_options = {'REGISTER', 'UNDO'}
def execute(self,context): def execute(self, context):
scene = context.scene scene = context.scene
mat = bpy.data.materials.new('UI') mat = bpy.data.materials.new("UI")
mat.use_nodes = True mat.use_nodes = True
for node in mat.node_tree.nodes: for node in mat.node_tree.nodes:
if node.type == 'OUTPUT_MATERIAL': if node.type == "OUTPUT_MATERIAL":
mat_output = node mat_output = node
else: else:
mat.node_tree.nodes.remove(node) mat.node_tree.nodes.remove(node)
emission = mat.node_tree.nodes.new('ShaderNodeEmission') emission = mat.node_tree.nodes.new("ShaderNodeEmission")
mat.node_tree.links.new(emission.outputs[0],mat_output.inputs[0]) mat.node_tree.links.new(emission.outputs[0], mat_output.inputs[0])
if not context.object.data.materials: if not context.object.data.materials:
context.object.data.materials.append(mat) context.object.data.materials.append(mat)
else: else:
context.object.material_slots[0].material = mat context.object.material_slots[0].material = mat
return {'FINISHED'} return {"FINISHED"}
class RP_OT_remove_material(bpy.types.Operator): class RP_OT_remove_material(bpy.types.Operator):
bl_label = "Remove Ui Material" bl_label = "Remove Ui Material"
bl_idname = "rigpicker.remove_mat" bl_idname = "rigpicker.remove_mat"
#bl_options = {'REGISTER', 'UNDO'} # bl_options = {'REGISTER', 'UNDO'}
def execute(self,context): def execute(self, context):
scene = context.scene scene = context.scene
#print(self.shape_type) # print(self.shape_type)
for mat in context.object.data.materials: for mat in context.object.data.materials:
bpy.data.materials.remove(mat) bpy.data.materials.remove(mat)
context.area.tag_redraw() context.area.tag_redraw()
return {'FINISHED'} return {"FINISHED"}
class RP_OT_eyedropper_material(bpy.types.Operator): class RP_OT_eyedropper_material(bpy.types.Operator):
"""Tooltip""" """Tooltip"""
bl_idname = "rigpicker.eyedropper_mat" bl_idname = "rigpicker.eyedropper_mat"
bl_label = "Eye Dropper mat" bl_label = "Eye Dropper mat"
#bl_options = {'REGISTER', 'UNDO'} # bl_options = {'REGISTER', 'UNDO'}
#first_mouse_x = IntProperty()
#first_value = FloatProperty()
# first_mouse_x = IntProperty()
# first_value = FloatProperty()
def modal(self, context, event): def modal(self, context, event):
context.area.tag_redraw() context.area.tag_redraw()
@@ -66,7 +64,7 @@ class RP_OT_eyedropper_material(bpy.types.Operator):
region = context.region region = context.region
rv3d = context.region_data 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 self.mouse = event.mouse_region_x, event.mouse_region_y
dg = context.evaluated_depsgraph_get() 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) raycast = scene.ray_cast(dg, ray_origin, view_vector)
if raycast[0]==True: if raycast[0] == True:
ob = raycast[4] ob = raycast[4]
if ob.data.materials: if ob.data.materials:
mat = ob.data.materials[0] 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: if not shape.data.materials:
shape.data.materials.append(mat) shape.data.materials.append(mat)
else: else:
shape.material_slots[0].material = mat 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() context.window.cursor_modal_restore()
for ob in self.temp_ob: for ob in self.temp_ob:
bpy.data.objects.remove(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() context.window.cursor_modal_restore()
for ob in self.temp_ob: for ob in self.temp_ob:
bpy.data.objects.remove(ob) bpy.data.objects.remove(ob)
return {'CANCELLED'} return {"CANCELLED"}
return {'RUNNING_MODAL'}
return {"RUNNING_MODAL"}
def invoke(self, context, event): def invoke(self, context, event):
scene = context.scene scene = context.scene
#self.local_cursor = tuple(context.space_data.cursor.location) # self.local_cursor = tuple(context.space_data.cursor.location)
#self.cursor = tuple(context.scene.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 = [] self.temp_ob = []
@@ -124,17 +123,17 @@ class RP_OT_eyedropper_material(bpy.types.Operator):
for c in curves: for c in curves:
mesh = bpy.data.meshes.new_from_object(c.evaluated_get(dg)) 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 copy.matrix_world = c.matrix_world
for mat in c.data.materials: for mat in c.data.materials:
copy.data.materials.append(mat) copy.data.materials.append(mat)
scene.collection.objects.link(copy) scene.collection.objects.link(copy)
self.temp_ob.append(copy) self.temp_ob.append(copy)
#args = (self,context) # args = (self,context)
#self._handle = context.space_data.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL') # self._handle = context.space_data.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
context.window_manager.modal_handler_add(self) context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'} return {"RUNNING_MODAL"}
classes = ( classes = (
+618 -373
View File
File diff suppressed because it is too large Load Diff
+159 -111
View File
@@ -1,22 +1,34 @@
import bpy
from ..core.shape import get_picker_datas
from ..core.addon_utils import is_shape
from ..core.bl_utils import link_mat_to_object, find_mirror
from pathlib import Path
import json import json
from os.path import abspath
from pathlib import Path
import bpy
from bpy.types import Operator
from bpy.props import IntProperty
from mathutils import Matrix
from ..core.shape import get_picker_data
from ..core.addon_utils import is_shape, get_picker_collection
from ..core.bl_utils import link_mat_to_object, flip_name
class RP_OT_create_shape(bpy.types.Operator): class RP_OT_create_shape(Operator):
bl_label = 'Create UI shape' bl_label = "Create UI shape"
bl_idname = 'rigpicker.create_shape' bl_idname = "rigpicker.create_shape"
#bl_options = {'REGISTER', 'UNDO'} # bl_options = {'REGISTER', 'UNDO'}
@classmethod @classmethod
def poll(cls, context): 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 scn = context.scene
vl = context.view_layer vl = context.view_layer
@@ -29,14 +41,18 @@ class RP_OT_create_shape(bpy.types.Operator):
ob.rig_picker.name = name ob.rig_picker.name = name
verts=[(0,0,0)] verts = [(0, 0, 0)]
edges = [] edges = []
faces =[] faces = []
mesh.from_pydata(verts, edges, faces) mesh.from_pydata(verts, edges, faces)
for c in scn.rig_picker.canvas.users_collection: picker_selected_cols = [
c.objects.link(ob) 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.z = 0.05
ob.location.x = offset ob.location.x = offset
@@ -49,169 +65,201 @@ class RP_OT_create_shape(bpy.types.Operator):
offset += 0.05 offset += 0.05
return {'FINISHED'} return {"FINISHED"}
class RP_OT_name_from_bone(bpy.types.Operator): class RP_OT_name_from_bone(Operator):
bl_label = 'Name Shape from selected bones' bl_label = "Name Shape from selected bones"
bl_idname = 'rigpicker.name_from_bone' bl_idname = "rigpicker.name_from_bone"
#bl_options = {'REGISTER', 'UNDO'} bl_description = "Rename all shapes from related bones name"
# bl_options = {'REGISTER', 'UNDO'}
def execute(self,context): def execute(self, context):
scene = context.scene
rig = scene.rig_picker.rig
bone = rig.data.bones.active
if bone: col = get_picker_collection(context.object)
context.object.name = bone.name
context.object.rig_picker.name = bone.name
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(bpy.types.Operator): class RP_OT_mirror_shape(Operator):
bl_label = 'Mirror UI shape' bl_label = "Mirror UI shape"
bl_idname = 'rigpicker.mirror_shape' bl_idname = "rigpicker.mirror_shape"
#bl_options = {'REGISTER', 'UNDO'} # bl_options = g
def execute(self,context): @classmethod
scn = context.scene def poll(cls, context):
return context.object and context.object.type in ("MESH", "CURVE", "TEXT")
for ob in bpy.context.selected_objects: def execute(self, context):
collection = get_picker_collection(context.object)
objects = context.selected_objects
name = find_mirror(ob.name) # Remove mirror object:
link_mat_to_object(ob) for ob in list(collection.all_objects):
if ob in objects:
continue
if not name: for mod in ob.modifiers:
name = ob.name + '_flip' if (
mod.type == "NODES"
and mod.node_group
and mod.node_group.name == "Symmetrize"
and mod.get("Socket_2") in objects
):
old_shape = bpy.data.objects.get(name) bpy.data.objects.remove(ob)
old_mat = None
if old_shape:
old_mat = next((sl.material for sl in old_shape.material_slots if sl.material), None)
bpy.data.objects.remove(old_shape)
mirror_ob = ob.copy() if collection.rig_picker.symmetry:
mirror_ob.data = ob.data x_axis = collection.rig_picker.symmetry.matrix_world.to_translation()[0]
mirror_ob.name = name
if old_mat:
#mirror_ob.data.materials.clear()
#mirror_ob.data.materials.append(None)
#mirror_ob.material_slots[0].link = 'OBJECT'
mirror_ob.material_slots[0].material = old_mat
#mirror_ob = bpy.data.objects.new(name,ob.data.copy())
for c in ob.users_collection:
c.objects.link(mirror_ob)
if scn.rig_picker.symmetry:
symmetry_loc = scn.rig_picker.symmetry.matrix_world.to_translation()[0]
else: else:
symmetry_loc = 0 x_axis = 0
#print(symmetry_loc) for ob in objects:
mirror_ob.matrix_world = ob.matrix_world flipped_name = flip_name(ob.name)
if flipped_name == ob.name:
suffix = ".L"
flipped_suffix = ".R"
# Determine side of the object
if ob.matrix_world.to_translation()[0] < x_axis:
suffix, flipped_suffix = flipped_suffix, suffix
#if mirror_ob.location[0] < symmetry_loc: flipped_name = f"{ob.name}{flipped_suffix}"
mirror_ob.location.x = symmetry_loc + (symmetry_loc - ob.location.x) ob.name = f"{ob.name}{suffix}"
#else:
# mirror_ob.location[0]= symmetry_loc+ (symmetry_loc- ob.location[0])
mirror_ob.rotation_euler.y = -ob.rotation_euler.y flipped_object = ob.copy()
mirror_ob.rotation_euler.z = -ob.rotation_euler.z flipped_object.name = flipped_name
flipped_object.parent = None
flipped_object.matrix_world = Matrix()
for mod in list(flipped_object.modifiers):
flipped_object.modifiers.remove(mod)
mirror_ob.scale.x = -ob.scale.x # Add symmetrize modifier 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 key, value in ob.items(): for col in ob.users_collection:
if key not in ['_RNA_UI','cycles']: col.objects.link(flipped_object)
mirror_ob[key] = value
if ob.rig_picker.shape_type == 'BONE': if ob.rig_picker.shape_type == "BONE":
mirror_ob.rig_picker.name = find_mirror(ob.rig_picker.name) flipped_object.rig_picker.name = flip_name(ob.rig_picker.name)
elif ob.rig_picker.shape_type == 'FUNCTION': elif ob.rig_picker.shape_type == "FUNCTION":
args = {} args = {}
for key,value in eval(ob.rig_picker.arguments).items(): for key, value in eval(ob.rig_picker.arguments).items():
if type(value) == list: if type(value) == list:
mirrored_value = [] mirrored_value = []
for item in value: for item in value:
mirrored_value.append(find_mirror(item)) mirrored_value.append(flip_name(item))
elif type(value) == str: elif type(value) == str:
mirrored_value = find_mirror(value) mirrored_value = flip_name(value)
args[key] = mirrored_value args[key] = mirrored_value
mirror_ob.rig_picker.arguments = str(args) flipped_object.rig_picker.arguments = str(args)
return {'FINISHED'} return {"FINISHED"}
class RP_OT_select_shape_type(bpy.types.Operator): class RP_OT_select_shape_type(Operator):
bl_label = 'Select Shape by Type' bl_label = "Select Shape by Type"
bl_idname = 'rigpicker.select_shape_type' bl_idname = "rigpicker.select_shape_type"
#bl_options = {'REGISTER', 'UNDO'} # bl_options = {'REGISTER', 'UNDO'}
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 layout = self.layout
col = layout.column() 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 scene = context.scene
#print(self.shape_type) # print(self.shape_type)
canvas = context.scene.rig_picker.canvas canvas = context.scene.rig_picker.canvas
if canvas: if canvas:
for ob in [o for o in bpy.data.objects if o.layers==canvas.layers]: 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: if (
ob.type in ["MESH", "CURVE", "FONT"]
and ob.rig_picker.shape_type == self.shape_type
):
ob.select = True ob.select = True
return {'FINISHED'} return {"FINISHED"}
def invoke(self, context, event): def invoke(self, context, event):
wm = context.window_manager wm = context.window_manager
return wm.invoke_props_dialog(self, width=150) return wm.invoke_props_dialog(self, width=150)
class RP_OT_save_picker(bpy.types.Operator): class RP_OT_save_picker(Operator):
bl_label = 'Store UI Data' bl_label = "Store UI Data"
bl_idname = 'rigpicker.save_picker' bl_idname = "rigpicker.save_picker"
index: IntProperty(default=-1)
def execute(self, context): def execute(self, context):
scn = context.scene scn = context.scene
canvas = scn.rig_picker.canvas ob = context.object
rig = scn.rig_picker.rig
shapes = [o for o in scn.objects if o != canvas and is_shape(o)]
if not rig: if self.index == -1:
self.report({'ERROR'}, 'Choose a Rig') collection = get_picker_collection(ob)
return {'CANCELLED'} else:
source = context.active_object.data.rig_picker.sources[self.index].source
source = Path(bpy.path.abspath(source, library=ob.data.library)).resolve()
collection = next(
(
c
for c in set(scn.collection.children_recursive)
if c.rig_picker.enabled
and Path(bpy.path.abspath(c.rig_picker.destination)).resolve()
== source
),
None,
)
if not collection:
self.report({"ERROR"}, "No Picker found")
return {"CANCELLED"}
canvas = collection.rig_picker.canvas
# rig = collection.rig_picker.rig
shapes = [
o
for o in collection.all_objects
if o != canvas and o.type in ("MESH", "CURVE", "FONT")
]
if not canvas: if not canvas:
self.report({'ERROR'}, 'Choose a Canvas') self.report({"ERROR"}, "Choose a Canvas")
return {'CANCELLED'} return {"CANCELLED"}
picker_data = get_picker_data(collection)
data = get_picker_datas(shapes, canvas, rig) picker_path = Path(bpy.path.abspath(collection.rig_picker.destination))
picker_path = Path(bpy.path.abspath(scn.rig_picker.destination)) print(f"Save Picker to {picker_path}")
picker_path.write_text(json.dumps(data)) picker_path.write_text(json.dumps(picker_data))
bpy.ops.rigpicker.reload_picker() bpy.ops.rigpicker.reload_picker()
return {"FINISHED"}
return {'FINISHED'}
classes = ( classes = (
RP_OT_create_shape, RP_OT_create_shape,
RP_OT_name_from_bone, RP_OT_name_from_bone,
RP_OT_mirror_shape, RP_OT_mirror_shape,
RP_OT_select_shape_type, 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)
-117
View File
@@ -1,117 +0,0 @@
import bpy
#import collections
#import inspect
from .core.addon_utils import get_operator_from_id
from .core.bl_utils import get_mat
import re
class RP_PT_picker_maker_panel(bpy.types.Panel):
bl_label = 'Rig Picker'
bl_category = 'Rigging'
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
@classmethod
def poll(cls, context):
return context.object
def draw(self, context):
ob = context.object
scn = context.scene
layout = self.layout
col = layout.column(align=False)
col.prop_search(scn.rig_picker, 'rig', scn, 'objects', text='Rig ')
col.prop_search(scn.rig_picker, 'canvas', scn, 'objects', text='Canvas ')
col.prop_search(scn.rig_picker, 'symmetry', scn, 'objects', text='Symmetry ')
if ob.type == 'ARMATURE':
row = layout.row(align=True)
is_packed = ('picker' in ob.data.rig_picker.keys())
sub_row = row.row(align=True)
sub_row.prop(ob.data.rig_picker, 'source', text='Picker')
sub_row.enabled = not is_packed
row.operator('rigpicker.pack_picker', icon='PACKAGE' if is_packed else 'UGLYPACKAGE', text='')
col = layout.column(align=True)
row = col.row(align=True)
row.operator('rigpicker.create_shape', icon='MESH_DATA', text='Create shape')
row.operator('rigpicker.mirror_shape', icon='ARROW_LEFTRIGHT', text='Mirror shape')
col.operator('rigpicker.name_from_bone', icon='SORTALPHA' , text='Name from bones')
col.prop(scn.rig_picker, 'use_pick_bone', icon='EYEDROPPER', text='Auto bone assign')
if ob.type !='ARMATURE':
box = layout.box()
col = box.column(align=False)
material_row = col.row(align=True)
material_row.operator('rigpicker.remove_mat', icon='REMOVE', text='')
material_row.operator('rigpicker.add_mat', icon='ADD', text='')
mat = False
if ob.type in ('MESH', 'CURVE', 'FONT') and ob.data.materials:
mat = get_mat(ob)
if mat and mat.node_tree:
emission_nodes = [n for n in mat.node_tree.nodes if n.type =='EMISSION']
if emission_nodes:
material_row.prop(emission_nodes[0].inputs[0], 'default_value', text='')
mat = True
if not mat:
material_row.label(text='No Material')
material_row.operator('rigpicker.eyedropper_mat', icon='EYEDROPPER', text='')
shape_type_row = col.row(align=True)
shape_type_row.prop(ob.rig_picker,'shape_type',expand = True)
shape_type_row.operator('rigpicker.select_shape_type', text='', icon='RESTRICT_SELECT_OFF')
if ob.rig_picker.shape_type == 'OPERATOR':
op_row = col.row(align=True)
op_row.prop(ob.rig_picker, 'operator', text='')
op_row.operator('rigpicker.operator_selector', text='', icon='COLLAPSEMENU')
if ob.rig_picker.operator:
col.prop(ob.rig_picker, 'name', text='Tooltip')
col.prop(ob.rig_picker,'shortcut', text='Shortcut')
else:
col.prop(ob.rig_picker, 'name', text='Tooltip')
'''
if ob.rig_picker.operator:
op = get_operator_from_id(ob.rig_picker.idname)
if op:
doc = re.findall(r'\(([^\)]+)\)', op.__doc__)[0]
else:
doc = 'Operator not found'
col.prop(ob.rig_picker,'name', text='Tooltip')
if op:
col.prop(ob.rig_picker,'shortcut', text='Shortcut')
col.prop(ob.rig_picker,'arguments', text='')
col.label(text=doc)
else:
col.label(text=doc)
'''
if ob.rig_picker.shape_type == 'BONE':
if scn.rig_picker.rig:
col.prop_search(ob.rig_picker, 'name', scn.rig_picker.rig.pose, 'bones', text='Bone')
#layout.separator()
layout.prop(scn.rig_picker, 'destination', text='Filepath')
layout.operator('rigpicker.save_picker', icon='PASTEDOWN', text='Save Picker')
classes = (
RP_PT_picker_maker_panel,
)
register, unregister = bpy.utils.register_classes_factory(classes)
+52 -20
View File
@@ -1,8 +1,16 @@
import bpy import bpy
from bpy.props import EnumProperty, StringProperty, PointerProperty, BoolProperty from bpy.props import (
EnumProperty,
StringProperty,
PointerProperty,
BoolProperty,
CollectionProperty,
IntProperty,
)
import inspect import inspect
''' """
def get_operator_items(self,context): def get_operator_items(self,context):
items = [] items = []
from . import func_picker as mod from . import func_picker as mod
@@ -12,45 +20,58 @@ def get_operator_items(self,context):
items.append((name,name,"")) items.append((name,name,""))
return items return items
''' """
def bones_item(self,context):
def bones_item(self, context):
items = [] items = []
if context.scene.rig_picker.rig: if context.scene.rig_picker.rig:
for bone in context.scene.rig_picker.rig.pose.bones: for bone in context.scene.rig_picker.rig.pose.bones:
items.append((bone.name,bone.name,'')) items.append((bone.name, bone.name, ""))
return items return items
class RP_PG_picker_source(bpy.types.PropertyGroup):
source: StringProperty(subtype="FILE_PATH")
class RP_PG_armature_ui_settings(bpy.types.PropertyGroup): class RP_PG_armature_ui_settings(bpy.types.PropertyGroup):
name: StringProperty() name: StringProperty()
source: StringProperty(subtype='FILE_PATH') source: StringProperty(subtype="FILE_PATH")
sources: CollectionProperty(type=RP_PG_picker_source)
class RP_PG_object_ui_settings(bpy.types.PropertyGroup): class RP_PG_object_ui_settings(bpy.types.PropertyGroup):
shape_type: EnumProperty(items=[(i.upper(), i, "") for i in ('Bone', 'Display', 'Operator')]) shape_type: EnumProperty(
#idname: StringProperty() items=[(i.upper(), i, "") for i in ("Bone", "Display", "Operator")]
#arguments: StringProperty() )
# idname: StringProperty()
# arguments: StringProperty()
operator: StringProperty() operator: StringProperty()
shortcut: StringProperty() shortcut: StringProperty()
name: StringProperty() name: StringProperty()
class RP_PG_scene_ui_settings(bpy.types.PropertyGroup): class RP_PG_collection_ui_settings(bpy.types.PropertyGroup):
enabled: BoolProperty(default=False)
rig: PointerProperty(type=bpy.types.Object) rig: PointerProperty(type=bpy.types.Object)
canvas: PointerProperty(type=bpy.types.Object) canvas: PointerProperty(type=bpy.types.Object)
symmetry: PointerProperty(type=bpy.types.Object) symmetry: PointerProperty(type=bpy.types.Object)
#idname: EnumProperty(items=[]) destination: StringProperty(subtype="FILE_PATH")
destination: StringProperty(subtype='FILE_PATH') use_pick_bone: BoolProperty(default=False)
#bone_list: bpy.props.EnumProperty(items = bones_item link_shape: BoolProperty(default=False)
use_pick_bone : 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): class RP_OT_operator_selector(bpy.types.Operator):
bl_label = "Select function" bl_label = "Select function"
bl_idname = "rigpicker.operator_selector" bl_idname = "rigpicker.operator_selector"
bl_property = "idname" bl_property = "idname"
#bl_options = {'REGISTER', 'UNDO'} # bl_options = {'REGISTER', 'UNDO'}
idname: EnumProperty(items=[]) idname: EnumProperty(items=[])
@@ -58,34 +79,45 @@ class RP_OT_operator_selector(bpy.types.Operator):
ob = context.object ob = context.object
ob.rig_picker.idname = self.idname ob.rig_picker.idname = self.idname
context.area.tag_redraw() context.area.tag_redraw()
return {'FINISHED'} return {"FINISHED"}
def invoke(self, context, event): def invoke(self, context, event):
wm = context.window_manager wm = context.window_manager
wm.invoke_search_popup(self) wm.invoke_search_popup(self)
return {'FINISHED'} return {"FINISHED"}
classes = ( classes = (
RP_PG_picker_source,
RP_PG_object_ui_settings, RP_PG_object_ui_settings,
RP_PG_collection_ui_settings,
RP_PG_scene_ui_settings, RP_PG_scene_ui_settings,
RP_PG_armature_ui_settings, RP_PG_armature_ui_settings,
RP_OT_operator_selector, RP_OT_operator_selector,
) )
def register(): def register():
for cls in classes: for cls in classes:
bpy.utils.register_class(cls) bpy.utils.register_class(cls)
bpy.types.Armature.rig_picker = bpy.props.PointerProperty(type=RP_PG_armature_ui_settings) bpy.types.Armature.rig_picker = bpy.props.PointerProperty(
bpy.types.Object.rig_picker = bpy.props.PointerProperty(type=RP_PG_object_ui_settings) 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.Scene.rig_picker = bpy.props.PointerProperty(type=RP_PG_scene_ui_settings)
def unregister(): def unregister():
del bpy.types.Scene.rig_picker del bpy.types.Scene.rig_picker
del bpy.types.Collection.rig_picker
del bpy.types.Object.rig_picker del bpy.types.Object.rig_picker
del bpy.types.Armature.rig_picker del bpy.types.Armature.rig_picker
for cls in reversed(classes): for cls in reversed(classes):
bpy.utils.unregister_class(cls) 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",
]
Binary file not shown.
+19
View File
@@ -0,0 +1,19 @@
flat in vec2 startPos;
in vec2 vertPos;
// out vec4 fragColor;
// uniform vec4 color;
// uniform float dashSize;
// uniform float gapSize;
void main()
{
vec2 dir = (vertPos.xy - startPos.xy);
float dist = length(dir);
if (fract(dist / (dashSize + gapSize)) > dashSize/(dashSize + gapSize))
discard;
fragColor = color;
}
+15
View File
@@ -0,0 +1,15 @@
// layout (location = 0) in vec2 pos;
flat out vec2 startPos;
out vec2 vertPos;
// uniform mat4 viewMatrix;
void main()
{
vec4 outPos = viewMatrix * vec4(pos.x, pos.y, 0.0, 1.0);
gl_Position = outPos;
vertPos = pos.xy / outPos.w;
startPos = vertPos;
}
+198
View File
@@ -0,0 +1,198 @@
import bpy
from bpy.types import UIList
# import collections
# import inspect
from .core.addon_utils import get_operator_from_id, get_picker_collection
from .core.bl_utils import get_mat
import re
# class RP_UL_picker_source(UIList):
# def draw_item(self, context, layout, data, item, _icon, _active_data, _active_propname, _index):
# ob = context.object
# row = layout.row(align=True)
# is_packed = ('picker' in ob.data.rig_picker.keys())
# sub_row = row.row(align=True)
# sub_row.prop(item, 'source', text='')
# sub_row.enabled = not is_packed
# #row.operator('rigpicker.pack_picker', icon='PACKAGE' if is_packed else 'UGLYPACKAGE', text='')
class RP_PT_picker_maker_panel(bpy.types.Panel):
bl_label = "Rig Picker"
bl_category = "Rigging"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
@classmethod
def poll(cls, context):
return context.object
def draw_header_preset(self, context):
self.layout.operator(
"rigpicker.add_picker_collection", text="", icon="ADD", emboss=False
)
def draw(self, context):
ob = context.object
scn = context.scene
collection = get_picker_collection(ob)
layout = self.layout
col = layout.column(align=False)
if collection:
col.prop_search(collection.rig_picker, "rig", scn, "objects", text="Rig ")
col.prop_search(
collection.rig_picker, "canvas", scn, "objects", text="Canvas "
)
col.prop_search(
collection.rig_picker, "symmetry", scn, "objects", text="Symmetry "
)
if ob.type == "ARMATURE":
box = col.box()
box.enabled = not ob.data.library
col = box.column(align=False)
row = col.row(align=True)
row.separator(factor=0.5)
row.label(text="Sources")
is_packed = "pickers" in ob.data.rig_picker.keys()
row.operator(
"rigpicker.pack_picker",
icon="PACKAGE" if is_packed else "UGLYPACKAGE",
text="",
emboss=False,
)
row.operator(
"rigpicker.add_picker_source", icon="ADD", text="", emboss=False
)
for i, item in enumerate(ob.data.rig_picker.sources):
row = col.row(align=True)
row.enabled = not is_packed
row.prop(item, "source", text="")
row.operator(
"rigpicker.remove_picker_source",
icon="PANEL_CLOSE",
text="",
emboss=False,
).index = i
if collection:
# layout.separator()
layout.prop(collection.rig_picker, "destination", text="Filepath")
layout.operator(
"rigpicker.save_picker", icon="PASTEDOWN", text="Save Picker"
)
class RP_PT_shape(bpy.types.Panel):
bl_label = "Shape"
bl_category = "Rigging"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_parent_id = "RP_PT_picker_maker_panel"
def draw(self, context):
ob = context.object
scn = context.scene
collection = get_picker_collection(ob)
layout = self.layout
col = layout.column(align=False)
if collection:
# if context.collection and context.collection.rig_picker.enabled:
col = layout.column(align=True)
row = col.row(align=True)
row.operator(
"rigpicker.create_shape", icon="MESH_DATA", text="Create Shape"
)
row.operator(
"rigpicker.mirror_shape", icon="MOD_MIRROR", text="Mirror Shape"
)
col.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":
box = layout.box()
col = box.column(align=False)
material_row = col.row(align=True)
material_row.operator("rigpicker.remove_mat", icon="REMOVE", text="")
material_row.operator("rigpicker.add_mat", icon="ADD", text="")
mat = False
if ob.type in ("MESH", "CURVE", "FONT") and ob.data.materials:
mat = get_mat(ob)
if mat and mat.node_tree:
emission_nodes = [
n for n in mat.node_tree.nodes if n.type == "EMISSION"
]
if emission_nodes:
material_row.prop(
emission_nodes[0].inputs[0], "default_value", text=""
)
mat = True
if not mat:
material_row.label(text="No Material")
material_row.operator(
"rigpicker.eyedropper_mat", icon="EYEDROPPER", text=""
)
shape_type_row = col.row(align=True)
shape_type_row.prop(ob.rig_picker, "shape_type", expand=True)
shape_type_row.operator(
"rigpicker.select_shape_type", text="", icon="RESTRICT_SELECT_OFF"
)
if ob.rig_picker.shape_type == "OPERATOR":
op_row = col.row(align=True)
op_row.prop(ob.rig_picker, "operator", text="")
op_row.operator(
"rigpicker.operator_selector", text="", icon="COLLAPSEMENU"
)
if ob.rig_picker.operator:
col.prop(ob.rig_picker, "name", text="Tooltip")
col.prop(ob.rig_picker, "shortcut", text="Shortcut")
else:
col.prop(ob.rig_picker, "name", text="Tooltip")
elif ob.rig_picker.shape_type == "BONE":
if collection and collection.rig_picker.rig:
col.prop_search(
ob.rig_picker,
"name",
collection.rig_picker.rig.pose,
"bones",
text="Bone",
)
classes = (
# RP_UL_picker_source,
RP_PT_picker_maker_panel,
RP_PT_shape,
)
register, unregister = bpy.utils.register_classes_factory(classes)
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" },
]