Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
9d09823321 | |||
eb5843063e | |||
c7df358fe8 | |||
|
21c4fe2117 | ||
|
f35fbb95b8 | ||
30064faf3d | |||
f4f8ee1613 | |||
e6c9dde63c | |||
e1abb88b7e | |||
747cfa0f7a | |||
fbdd994d67 | |||
7d2216bc3b | |||
b4defc1296 | |||
6798053389 | |||
de3b78e741 | |||
76ea717df8 | |||
|
7d2d69ba0f | ||
|
75bc414e26 |
205
README.md
@ -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/>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<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
|
|
||||||
|
|
||||||
|
BIN
attachments/Doc_picker_01.gif
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
attachments/Doc_picker_02.gif
Normal file
After Width: | Height: | Size: 2.3 MiB |
BIN
attachments/Doc_picker_03.gif
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
attachments/Doc_picker_04.gif
Normal file
After Width: | Height: | Size: 6.0 MiB |
BIN
attachments/doc_picker_01.png
Normal file
After Width: | Height: | Size: 123 KiB |
BIN
attachments/doc_picker_02.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
attachments/doc_picker_03.png
Normal file
After Width: | Height: | Size: 165 KiB |
BIN
attachments/doc_picker_04.png
Normal file
After Width: | Height: | Size: 308 KiB |
BIN
attachments/doc_picker_05.png
Normal file
After Width: | Height: | Size: 523 KiB |
BIN
attachments/doc_picker_06.png
Normal file
After Width: | Height: | Size: 504 KiB |
BIN
attachments/doc_picker_07.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
attachments/doc_picker_08.png
Normal file
After Width: | Height: | Size: 912 KiB |
BIN
attachments/doc_picker_09.png
Normal file
After Width: | Height: | Size: 340 KiB |
BIN
attachments/doc_picker_10.png
Normal file
After Width: | Height: | Size: 310 KiB |
BIN
attachments/doc_picker_11.png
Normal file
After Width: | Height: | Size: 406 KiB |
BIN
attachments/doc_picker_12.png
Normal file
After Width: | Height: | Size: 874 KiB |
BIN
attachments/doc_picker_13.png
Normal file
After Width: | Height: | Size: 709 KiB |
BIN
attachments/doc_picker_14.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
attachments/doc_picker_15.png
Normal file
After Width: | Height: | Size: 656 KiB |
@ -459,7 +459,7 @@ class Picker:
|
|||||||
if shape.type == 'bone' and shape.hover:
|
if shape.type == 'bone' and shape.hover:
|
||||||
shape.assign_bone_event()
|
shape.assign_bone_event()
|
||||||
|
|
||||||
bpy.ops._rigpicker.save_picker(index=self.index)
|
bpy.ops.rigpicker.save_picker(index=self.index)
|
||||||
|
|
||||||
def press_event(self, mode='SET'):
|
def press_event(self, mode='SET'):
|
||||||
for shape in self.shapes:
|
for shape in self.shapes:
|
||||||
@ -712,7 +712,7 @@ class PickerGroup:
|
|||||||
|
|
||||||
def load_picker_data(rig):
|
def load_picker_data(rig):
|
||||||
if 'pickers' in rig.data.rig_picker:
|
if 'pickers' in rig.data.rig_picker:
|
||||||
picker_datas = [p.to_dict() for p in rig.rig_picker['pickers']]
|
picker_datas = [[s.to_dict() for s in p] for p in rig.data.rig_picker['pickers']]
|
||||||
else:
|
else:
|
||||||
picker_datas = []
|
picker_datas = []
|
||||||
|
|
||||||
@ -736,8 +736,11 @@ def get_picker_path(rig, source, start=None):
|
|||||||
|
|
||||||
|
|
||||||
def pack_picker(rig, start=None):
|
def pack_picker(rig, start=None):
|
||||||
|
if not 'rig_picker' in rig.data:
|
||||||
|
return
|
||||||
|
|
||||||
pickers = []
|
pickers = []
|
||||||
for picker_source in rig.data.get('rig_picker', {}).get('sources', {}):
|
for picker_source in rig.data['rig_picker'].get('sources', []):
|
||||||
picker_path = get_picker_path(rig, picker_source['source'], start)
|
picker_path = get_picker_path(rig, picker_source['source'], start)
|
||||||
if not picker_path.exists():
|
if not picker_path.exists():
|
||||||
print(f'{picker_path} not exists')
|
print(f'{picker_path} not exists')
|
||||||
|
@ -93,7 +93,7 @@ def get_shape_data(ob, matrix=None, depsgraph=None):
|
|||||||
|
|
||||||
for vert in mesh.vertices:
|
for vert in mesh.vertices:
|
||||||
co = matrix @ (ob.matrix_world @ Vector(vert.co))
|
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]]
|
depths += [co[2]]
|
||||||
|
|
||||||
@ -148,8 +148,8 @@ def get_picker_data(collection):
|
|||||||
|
|
||||||
canvas_coords = [canvas.matrix_world@Vector((p.co)) for p in canvas_points]
|
canvas_coords = [canvas.matrix_world@Vector((p.co)) for p in canvas_points]
|
||||||
|
|
||||||
height = abs(max(canvas_coords).y - min(canvas_coords).y)
|
height = abs(max(co.y for co in canvas_coords) - min(co.y for co in canvas_coords))
|
||||||
width = abs(max(canvas_coords).x - min(canvas_coords).x)
|
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)
|
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
|
||||||
|
235
gizmo.py
@ -3,13 +3,10 @@ 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)
|
||||||
import gpu
|
|
||||||
from mathutils import Vector, Matrix, Euler
|
|
||||||
from gpu_extras.batch import batch_for_shader
|
|
||||||
|
|
||||||
from .constants import PICKERS, SHADERS
|
from mathutils import Vector, Matrix, Euler
|
||||||
|
from .constants import PICKERS
|
||||||
from .core.picker import Picker
|
from .core.picker import Picker
|
||||||
from .core.geometry_utils import bounding_rect
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -41,133 +38,14 @@ class RP_OT_simple_operator(bpy.types.Operator):
|
|||||||
print('Select Shape')
|
print('Select Shape')
|
||||||
return {'FINISHED'}
|
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):
|
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):
|
def test_select(self, context, location):
|
||||||
ob = context.object
|
ob = context.object
|
||||||
|
|
||||||
@ -176,109 +54,41 @@ class RP_GT_gizmo(Gizmo):
|
|||||||
return -1
|
return -1
|
||||||
|
|
||||||
picker.move_event(location)
|
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()
|
context.region.tag_redraw()
|
||||||
|
|
||||||
return 1
|
#print(location)
|
||||||
|
|
||||||
|
return -1
|
||||||
|
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
|
|
||||||
print('DRAW_SELECT', self.border)
|
|
||||||
|
|
||||||
if not self.draw_border:
|
|
||||||
return
|
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):
|
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}')
|
||||||
|
|
||||||
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'}
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
def modal(self, context, event, tweak):
|
def modal(self, context, event, tweak):
|
||||||
#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}')
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
return {'RUNNING_MODAL'}
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
def exit(self, context, cancel):
|
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()
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -296,9 +106,6 @@ class RP_GT_gizmogroup(GizmoGroup):
|
|||||||
|
|
||||||
def setup(self, context):
|
def setup(self, context):
|
||||||
self.gizmo = self.gizmos.new("RP_GT_gizmo")
|
self.gizmo = self.gizmos.new("RP_GT_gizmo")
|
||||||
self.gizmo.draw_border = False
|
|
||||||
self.gizmo.border = []
|
|
||||||
self.gizmo.use_draw_modal = True
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,9 +130,8 @@ class RP_OT_box_select(Operator):
|
|||||||
return {'RUNNING_MODAL'}
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
def release_event(self, context):
|
def release_event(self, context):
|
||||||
scn = context.scene
|
|
||||||
|
|
||||||
if scn.rig_picker.use_pick_bone:
|
if get_picker_collection():
|
||||||
self.picker.assign_bone_event()
|
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]):
|
||||||
@ -392,24 +391,19 @@ class RP_OT_reload_picker(Operator):
|
|||||||
# return
|
# return
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
#PICKERS.clear()
|
|
||||||
if context.object.type == 'ARMATURE':
|
if context.object.type == 'ARMATURE':
|
||||||
rig = context.object
|
rig = context.object
|
||||||
else:
|
else:
|
||||||
collection = get_picker_collection(context.object)
|
collection = get_picker_collection(context.object)
|
||||||
rig = collection.rig_picker.rig
|
rig = collection.rig_picker.rig
|
||||||
|
|
||||||
|
#print('Reload', rig)
|
||||||
load_picker_data(rig)
|
load_picker_data(rig)
|
||||||
|
|
||||||
'''
|
|
||||||
for area in context.screen.areas:
|
for area in context.screen.areas:
|
||||||
#print(area.type, is_picker_space(area.spaces.active))
|
|
||||||
if 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()
|
area.tag_redraw()
|
||||||
'''
|
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@ -551,7 +545,8 @@ class RP_MT_context_menu(Menu):
|
|||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
col = layout.column()
|
col = layout.column()
|
||||||
col.use_property_split = True
|
col.operator_context = 'INVOKE_DEFAULT'
|
||||||
|
#col.use_property_split = True
|
||||||
|
|
||||||
ob = context.object
|
ob = context.object
|
||||||
picker = PICKERS.get(ob)
|
picker = PICKERS.get(ob)
|
||||||
@ -561,9 +556,25 @@ class RP_MT_context_menu(Menu):
|
|||||||
else:
|
else:
|
||||||
bone = context.active_pose_bone
|
bone = context.active_pose_bone
|
||||||
|
|
||||||
|
# Draw Space Switch Operator
|
||||||
|
if getattr(ob.data, 'space_switch'):
|
||||||
|
space_switch = ob.data.space_switch
|
||||||
|
|
||||||
|
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:
|
if bone:
|
||||||
for key in bone.keys():
|
for key in bone.keys():
|
||||||
layout.prop(bone,f'["{key}"]', slider=True)
|
col.prop(bone,f'["{key}"]', slider=True)
|
||||||
|
|
||||||
#layout.operator("rigpicker.show_bone_layer", text="Show Bone Layer", ).type = 'ACTIVE'
|
#layout.operator("rigpicker.show_bone_layer", text="Show Bone Layer", ).type = 'ACTIVE'
|
||||||
#layout.operator("rigidbody.objects_add", text="B Add Passive").type = 'PASSIVE'
|
#layout.operator("rigidbody.objects_add", text="B Add Passive").type = 'PASSIVE'
|
||||||
@ -755,7 +766,7 @@ def register_keymaps():
|
|||||||
|
|
||||||
kmi = km.keymap_items.new("node.context_menu_picker", type="RIGHTMOUSE", value="PRESS")
|
kmi = km.keymap_items.new("node.context_menu_picker", type="RIGHTMOUSE", value="PRESS")
|
||||||
keymaps.append((km, kmi))
|
keymaps.append((km, kmi))
|
||||||
'''
|
|
||||||
kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS")
|
kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS")
|
||||||
kmi.properties.mode = 'SET'
|
kmi.properties.mode = 'SET'
|
||||||
keymaps.append((km, kmi))
|
keymaps.append((km, kmi))
|
||||||
@ -764,10 +775,11 @@ def register_keymaps():
|
|||||||
kmi.properties.mode = 'EXTEND'
|
kmi.properties.mode = 'EXTEND'
|
||||||
keymaps.append((km, kmi))
|
keymaps.append((km, kmi))
|
||||||
|
|
||||||
|
#kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS", ctrl=True)
|
||||||
kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS", alt=True)
|
kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS", alt=True)
|
||||||
kmi.properties.mode = 'SUBSTRACT'
|
kmi.properties.mode = 'SUBSTRACT'
|
||||||
keymaps.append((km, kmi))
|
keymaps.append((km, kmi))
|
||||||
'''
|
|
||||||
#km = wm.keyconfigs.addon.keymaps.new(name="View2D")
|
#km = wm.keyconfigs.addon.keymaps.new(name="View2D")
|
||||||
#kmi = km.keymap_items.new("rigpicker.call_operator", type="MIDDLEMOUSE", value="PRESS")
|
#kmi = km.keymap_items.new("rigpicker.call_operator", type="MIDDLEMOUSE", value="PRESS")
|
||||||
#kmi.properties.operator = "bpy.ops.view2d.pan('INVOKE_DEFAULT')"
|
#kmi.properties.operator = "bpy.ops.view2d.pan('INVOKE_DEFAULT')"
|
||||||
|
@ -21,6 +21,14 @@ class RP_OT_create_shape(Operator):
|
|||||||
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 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):
|
def execute(self,context):
|
||||||
scn = context.scene
|
scn = context.scene
|
||||||
vl = context.view_layer
|
vl = context.view_layer
|
||||||
@ -40,8 +48,10 @@ class RP_OT_create_shape(Operator):
|
|||||||
|
|
||||||
mesh.from_pydata(verts, edges, faces)
|
mesh.from_pydata(verts, edges, faces)
|
||||||
|
|
||||||
for c in scn.rig_picker.canvas.users_collection:
|
picker_selected_cols = [col for col in bpy.data.collections if col.rig_picker.link_shape]
|
||||||
c.objects.link(ob)
|
|
||||||
|
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
|
||||||
@ -60,16 +70,16 @@ class RP_OT_create_shape(Operator):
|
|||||||
class RP_OT_name_from_bone(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_description = 'Rename all shapes from related bones name'
|
||||||
#bl_options = {'REGISTER', 'UNDO'}
|
#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
|
for ob in col.all_objects:
|
||||||
|
if ob.rig_picker.shape_type == 'BONE':
|
||||||
|
ob.name = ob.rig_picker.name
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
@ -84,9 +94,7 @@ class RP_OT_mirror_shape(Operator):
|
|||||||
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):
|
def execute(self,context):
|
||||||
scn = context.scene
|
collection = get_picker_collection(context.object)
|
||||||
ob = context.object
|
|
||||||
collection = get_picker_collection(ob)
|
|
||||||
objects = context.selected_objects
|
objects = context.selected_objects
|
||||||
|
|
||||||
# Remove mirror object:
|
# Remove mirror object:
|
||||||
@ -95,7 +103,7 @@ class RP_OT_mirror_shape(Operator):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
for mod in ob.modifiers:
|
for mod in ob.modifiers:
|
||||||
if (mod.type == 'NODES' and mod.node_group.name == 'Symmetrize' and
|
if (mod.type == 'NODES' and mod.node_group and mod.node_group.name == 'Symmetrize' and
|
||||||
mod.get('Socket_2') in objects):
|
mod.get('Socket_2') in objects):
|
||||||
|
|
||||||
bpy.data.objects.remove(ob)
|
bpy.data.objects.remove(ob)
|
||||||
@ -124,7 +132,7 @@ class RP_OT_mirror_shape(Operator):
|
|||||||
for mod in list(flipped_object.modifiers):
|
for mod in list(flipped_object.modifiers):
|
||||||
flipped_object.modifiers.remove(mod)
|
flipped_object.modifiers.remove(mod)
|
||||||
|
|
||||||
# Add symmetrize modifier
|
# Add symmetrize modifier TODO add it into a resource
|
||||||
mod = flipped_object.modifiers.new(name='Symmetrize', type='NODES')
|
mod = flipped_object.modifiers.new(name='Symmetrize', type='NODES')
|
||||||
mod.node_group = bpy.data.node_groups['Symmetrize']
|
mod.node_group = bpy.data.node_groups['Symmetrize']
|
||||||
mod['Socket_2'] = ob
|
mod['Socket_2'] = ob
|
||||||
@ -222,7 +230,6 @@ class RP_OT_save_picker(Operator):
|
|||||||
|
|
||||||
bpy.ops.rigpicker.reload_picker()
|
bpy.ops.rigpicker.reload_picker()
|
||||||
|
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
|
@ -44,14 +44,17 @@ class RP_PG_object_ui_settings(bpy.types.PropertyGroup):
|
|||||||
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)
|
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')
|
||||||
#bone_list: bpy.props.EnumProperty(items = bones_item
|
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)
|
use_pick_bone : BoolProperty(default=False)
|
||||||
|
|
||||||
|
|
||||||
@ -78,6 +81,7 @@ class RP_OT_operator_selector(bpy.types.Operator):
|
|||||||
classes = (
|
classes = (
|
||||||
RP_PG_picker_source,
|
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,
|
||||||
@ -89,11 +93,12 @@ def register():
|
|||||||
|
|
||||||
bpy.types.Armature.rig_picker = bpy.props.PointerProperty(type=RP_PG_armature_ui_settings)
|
bpy.types.Armature.rig_picker = bpy.props.PointerProperty(type=RP_PG_armature_ui_settings)
|
||||||
bpy.types.Object.rig_picker = bpy.props.PointerProperty(type=RP_PG_object_ui_settings)
|
bpy.types.Object.rig_picker = bpy.props.PointerProperty(type=RP_PG_object_ui_settings)
|
||||||
|
bpy.types.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)
|
||||||
bpy.types.Collection.rig_picker = bpy.props.PointerProperty(type=RP_PG_scene_ui_settings)
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
del bpy.types.Scene.rig_picker
|
del bpy.types.Scene.rig_picker
|
||||||
|
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
|
||||||
|
|
||||||
|
6
ui.py
@ -91,8 +91,12 @@ class RP_PT_shape(bpy.types.Panel):
|
|||||||
row = col.row(align=True)
|
row = col.row(align=True)
|
||||||
row.operator('rigpicker.create_shape', icon='MESH_DATA', text='Create Shape')
|
row.operator('rigpicker.create_shape', icon='MESH_DATA', text='Create Shape')
|
||||||
row.operator('rigpicker.mirror_shape', icon='MOD_MIRROR', text='Mirror 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')
|
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()
|
box = layout.box()
|
||||||
|