Compare commits
8
Commits
v2.5.0
..
c02b890915
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c02b890915 | ||
|
|
19e26f8cee | ||
|
|
01ce06201e | ||
|
|
92e53f8368 | ||
|
|
386be46251 | ||
|
|
47b9b68e9e | ||
|
|
810256f5cb | ||
|
|
cf2ba8448a |
@@ -1,5 +1,32 @@
|
||||
# Changelog
|
||||
|
||||
3.3.0
|
||||
|
||||
- added: `Move Material To Layer` has now option to copy instead of moving in pop-up menu.
|
||||
|
||||
3.2.0
|
||||
|
||||
- added: UI settings to show GP tool settings placement and orientation
|
||||
- fixed: Bug with reproject orientation settings
|
||||
- added: show current orientation in batch reproject popup UI (if current is selected)
|
||||
|
||||
3.1.0
|
||||
|
||||
- added: Feature to move all strokes using active material to an existing or new layer (material dropdown menu > `Move Material To Layer`)
|
||||
|
||||
3.0.2
|
||||
|
||||
- changed: Exposed `Copy/Move Keys To Layer` in Dopesheet(Gpencil), in right clic context menu and `Keys` menu.
|
||||
|
||||
3.0.1
|
||||
|
||||
- fixed: Crash after generating empty frames
|
||||
|
||||
3.0.0
|
||||
|
||||
- Update for Blender 4.0 (Breaking release, removed bgl to use gpu)
|
||||
- fixed: openGL draw camera frame and passepartout
|
||||
|
||||
2.5.0
|
||||
|
||||
- added: Animation manager new button `Frame Select Step` (sort of a checker deselect, but in GP dopesheet)
|
||||
|
||||
@@ -166,6 +166,7 @@ class GP_OT_create_empty_frames(bpy.types.Operator):
|
||||
gpl.active.frames.new(num, active=False)
|
||||
fct += 1
|
||||
|
||||
gpl.update()
|
||||
if fct:
|
||||
self.report({'INFO'}, f"{fct} frame created on layer {gpl.active.info}")
|
||||
else:
|
||||
|
||||
+3
-4
@@ -1,6 +1,5 @@
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
import bgl
|
||||
from gpu_extras.presets import draw_circle_2d
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
import gpu
|
||||
@@ -190,7 +189,7 @@ class GPTB_OT_eraser(Operator):
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def draw_callback_px(self):
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
gpu.state.blend_set('ALPHA')
|
||||
#bgl.glBlendFunc(bgl.GL_CONSTANT_ALPHA, bgl.GL_ONE_MINUS_CONSTANT_ALPHA)
|
||||
#bgl.glBlendColor(1.0, 1.0, 1.0, 0.1)
|
||||
|
||||
@@ -201,7 +200,7 @@ class GPTB_OT_eraser(Operator):
|
||||
bg_color = area.spaces.active.shading.background_color
|
||||
#print(bg_color)
|
||||
|
||||
shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
|
||||
shader = gpu.shader.from_builtin('POLYLINE_UNIFORM_COLOR')
|
||||
shader.bind()
|
||||
shader.uniform_float("color", (1, 1, 1, 1))
|
||||
for mouse, radius in self.mouse_path:
|
||||
@@ -210,7 +209,7 @@ class GPTB_OT_eraser(Operator):
|
||||
batch.draw(shader)
|
||||
|
||||
draw_circle_2d(self.mouse, (0.75, 0.25, 0.35, 1.0), self.radius, 24)
|
||||
bgl.glDisable(bgl.GL_BLEND)
|
||||
gpu.state.blend_set('NONE')
|
||||
|
||||
|
||||
|
||||
|
||||
+27
-13
@@ -17,20 +17,27 @@ def get_layer_list(self, context):
|
||||
|
||||
class GPTB_OT_duplicate_send_to_layer(Operator) :
|
||||
bl_idname = "gp.duplicate_send_to_layer"
|
||||
bl_label = 'Duplicate and send to layer'
|
||||
bl_label = 'Duplicate Send To Layer'
|
||||
bl_description = 'Duplicate selected keys in active layer and send to chosen layer'
|
||||
# important to have the updated enum here as bl_property
|
||||
bl_property = "layers_enum"
|
||||
|
||||
|
||||
layers_enum : bpy.props.EnumProperty(
|
||||
name="Duplicate to layers",
|
||||
description="Duplicate selected keys in active layer and send them to choosen layer",
|
||||
description="Duplicate selected keys in active layer and send them to chosen layer",
|
||||
items=get_layer_list,
|
||||
options={'HIDDEN'},
|
||||
)
|
||||
|
||||
delete_source : bpy.props.BoolProperty(default=False, options={'SKIP_SAVE'})
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, properties):
|
||||
if properties.delete_source:
|
||||
return f"Move selected keys in active layer to chosen layer"
|
||||
else:
|
||||
return f"Copy selected keys in active layer and send to chosen layer"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object and context.object.type == 'GPENCIL'\
|
||||
@@ -56,16 +63,16 @@ class GPTB_OT_duplicate_send_to_layer(Operator) :
|
||||
|
||||
replaced = len(to_replace)
|
||||
|
||||
## remove overlapping frames
|
||||
## Remove overlapping frames
|
||||
for f in reversed(to_replace):
|
||||
target_layer.frames.remove(f)
|
||||
|
||||
## copy original frames
|
||||
## Copy original frames
|
||||
for f in selected_frames:
|
||||
target_layer.frames.copy(f)
|
||||
sent = len(selected_frames)
|
||||
|
||||
## delete original frames as an option
|
||||
## Delete original frames as an option
|
||||
if self.delete_source:
|
||||
for f in reversed(selected_frames):
|
||||
act_layer.frames.remove(f)
|
||||
@@ -73,9 +80,6 @@ class GPTB_OT_duplicate_send_to_layer(Operator) :
|
||||
mess = f'{sent} keys copied'
|
||||
if replaced:
|
||||
mess += f' ({replaced} replaced)'
|
||||
|
||||
# context.view_layer.update()
|
||||
# bpy.ops.gpencil.editmode_toggle()
|
||||
|
||||
mod = context.mode
|
||||
bpy.ops.gpencil.editmode_toggle()
|
||||
@@ -109,9 +113,6 @@ class GPTB_OT_duplicate_send_to_layer(Operator) :
|
||||
|
||||
addon_keymaps = []
|
||||
def register_keymaps():
|
||||
# pref = get_addon_prefs()
|
||||
# if not pref.kfj_use_shortcut:
|
||||
# return
|
||||
addon = bpy.context.window_manager.keyconfigs.addon
|
||||
km = addon.keymaps.new(name = "Dopesheet", space_type = "DOPESHEET_EDITOR")
|
||||
kmi = km.keymap_items.new('gp.duplicate_send_to_layer', type='D', value="PRESS", ctrl=True, shift=True)
|
||||
@@ -129,6 +130,12 @@ def unregister_keymaps():
|
||||
addon_keymaps.clear()
|
||||
|
||||
|
||||
def menu_duplicate_and_send_to_layer(self, context):
|
||||
if context.space_data.ui_mode == 'GPENCIL':
|
||||
self.layout.operator_context = 'INVOKE_REGION_WIN'
|
||||
self.layout.operator('gp.duplicate_send_to_layer', text='Move Keys To Layer').delete_source = True
|
||||
self.layout.operator('gp.duplicate_send_to_layer', text='Copy Keys To Layer')
|
||||
|
||||
classes = (
|
||||
GPTB_OT_duplicate_send_to_layer,
|
||||
)
|
||||
@@ -139,12 +146,19 @@ def register():
|
||||
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
register_keymaps()
|
||||
bpy.types.DOPESHEET_MT_gpencil_key.append(menu_duplicate_and_send_to_layer)
|
||||
bpy.types.DOPESHEET_MT_context_menu.append(menu_duplicate_and_send_to_layer)
|
||||
|
||||
|
||||
def unregister():
|
||||
if bpy.app.background:
|
||||
return
|
||||
|
||||
|
||||
bpy.types.DOPESHEET_MT_context_menu.remove(menu_duplicate_and_send_to_layer)
|
||||
bpy.types.DOPESHEET_MT_gpencil_key.remove(menu_duplicate_and_send_to_layer)
|
||||
unregister_keymaps()
|
||||
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
@@ -0,0 +1,183 @@
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
import mathutils
|
||||
from mathutils import Vector, Matrix, geometry
|
||||
from bpy_extras import view3d_utils
|
||||
from . import utils
|
||||
|
||||
# def get_layer_list(self, context):
|
||||
# '''return (identifier, name, description) of enum content'''
|
||||
# if not context:
|
||||
# return [('None', 'None','None')]
|
||||
# if not context.object:
|
||||
# return [('None', 'None','None')]
|
||||
# return [(l.info, l.info, '') for l in context.object.data.layers] # if l != context.object.data.layers.active
|
||||
|
||||
## in Class
|
||||
# bl_property = "layers_enum"
|
||||
|
||||
# layers_enum : bpy.props.EnumProperty(
|
||||
# name="Send Material To Layer",
|
||||
# description="Send active material to layer",
|
||||
# items=get_layer_list,
|
||||
# options={'HIDDEN'},
|
||||
# )
|
||||
|
||||
class GPTB_OT_move_material_to_layer(Operator) :
|
||||
bl_idname = "gp.move_material_to_layer"
|
||||
bl_label = 'Move Material To Layer'
|
||||
bl_description = 'Move active material to an existing or new layer'
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
layer_name : bpy.props.StringProperty(
|
||||
name='Layer Name', default='', options={'SKIP_SAVE'})
|
||||
|
||||
copy : bpy.props.BoolProperty(
|
||||
name='Copy to layer', default=False,
|
||||
description='Copy strokes to layer instead of moving',
|
||||
options={'SKIP_SAVE'})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object and context.object.type == 'GPENCIL'
|
||||
|
||||
def invoke(self, context, event):
|
||||
if self.layer_name:
|
||||
return self.execute(context)
|
||||
if not len(context.object.data.layers):
|
||||
self.report({'WARNING'}, 'No layers on current GP object')
|
||||
return {'CANCELLED'}
|
||||
|
||||
mat = context.object.data.materials[context.object.active_material_index]
|
||||
self.mat_name = mat.name
|
||||
|
||||
# wm.invoke_search_popup(self)
|
||||
return context.window_manager.invoke_props_dialog(self, width=250)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
# layout.operator_context = "INVOKE_DEFAULT"
|
||||
layout.prop(self, 'copy', text='Copy Strokes')
|
||||
action_label = 'Copy' if self.copy else 'Move'
|
||||
layout.label(text=f'{action_label} material "{self.mat_name}" to layer:', icon='MATERIAL')
|
||||
|
||||
col = layout.column()
|
||||
col.prop(self, 'layer_name', text='', icon='ADD')
|
||||
# if self.layer_name:
|
||||
# col.label(text='Ok/Enter to create new layer', icon='INFO')
|
||||
|
||||
col.separator()
|
||||
for l in reversed(context.object.data.layers):
|
||||
|
||||
icon = 'GREASEPENCIL' if l == context.object.data.layers.active else 'BLANK1'
|
||||
row = col.row()
|
||||
row.alignment = 'LEFT'
|
||||
op = col.operator('gp.move_material_to_layer', text=l.info, icon=icon, emboss=False)
|
||||
op.layer_name = l.info
|
||||
op.copy = self.copy
|
||||
|
||||
def execute(self, context):
|
||||
if not self.layer_name:
|
||||
print('Out')
|
||||
return {'CANCELLED'}
|
||||
|
||||
## Active + selection
|
||||
pool = [o for o in bpy.context.selected_objects if o.type == 'GPENCIL']
|
||||
if not context.object in pool:
|
||||
pool.append(context.object)
|
||||
|
||||
mat = context.object.data.materials[context.object.active_material_index]
|
||||
|
||||
print(f'Moving strokes using material "{mat.name}" on {len(pool)} object(s)')
|
||||
# import time
|
||||
# t = time.time() # Dbg
|
||||
total = 0
|
||||
oct = 0
|
||||
for ob in pool:
|
||||
mat_index = next((i for i, ms in enumerate(ob.material_slots) if ms.material and ms.material == mat), None)
|
||||
if mat_index is None:
|
||||
print(f'/!\ {ob.name} has no Material {mat.name} in stack')
|
||||
continue
|
||||
|
||||
gpl = ob.data.layers
|
||||
|
||||
if not (target_layer := gpl.get(self.layer_name)):
|
||||
target_layer = gpl.new(self.layer_name)
|
||||
|
||||
## List existing frames
|
||||
key_dict = {f.frame_number : f for f in target_layer.frames}
|
||||
|
||||
### Move Strokes to a new key (or existing key if comming for yet another layer)
|
||||
fct = 0
|
||||
sct = 0
|
||||
for l in gpl:
|
||||
if l == target_layer:
|
||||
## ! infinite loop if target layer is included
|
||||
continue
|
||||
for f in l.frames:
|
||||
## skip if no stroke has active material
|
||||
if not next((s for s in f.strokes if s.material_index == mat_index), None):
|
||||
continue
|
||||
## Get/Create a destination frame and keep a reference to it
|
||||
if not (dest_key := key_dict.get(f.frame_number)):
|
||||
dest_key = target_layer.frames.new(f.frame_number)
|
||||
key_dict[dest_key.frame_number] = dest_key
|
||||
|
||||
print(f'{ob.name} : frame {f.frame_number}')
|
||||
## Replicate strokes in dest_keys
|
||||
stroke_to_delete = []
|
||||
for s in f.strokes:
|
||||
if s.material_index == mat_index:
|
||||
utils.copy_stroke_to_frame(s, dest_key)
|
||||
stroke_to_delete.append(s)
|
||||
|
||||
## Debug
|
||||
# if time.time() - t > 10:
|
||||
# print('TIMEOUT')
|
||||
# return {'CANCELLED'}
|
||||
|
||||
sct += len(stroke_to_delete)
|
||||
|
||||
# print('Removing frames') # Dbg
|
||||
## Remove from source frame (f)
|
||||
if not self.copy:
|
||||
for s in reversed(stroke_to_delete):
|
||||
f.strokes.remove(s)
|
||||
|
||||
## ? Remove frame if layer is empty ? -> probably not, will show previous frame
|
||||
|
||||
fct += 1
|
||||
l.frames.update()
|
||||
|
||||
|
||||
if fct:
|
||||
oct += 1
|
||||
print(f'{ob.name}: Moved {fct} frames -> {sct} Strokes') # Dbg
|
||||
|
||||
total += fct
|
||||
|
||||
report_type = 'INFO' if total else 'WARNING'
|
||||
if self.copy:
|
||||
self.report({report_type}, f'Copied {total} frames accross {oct} object(s)')
|
||||
else:
|
||||
self.report({report_type}, f'Moved {total} frames accross {oct} object(s)')
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# def menu_duplicate_and_send_to_layer(self, context):
|
||||
# if context.space_data.ui_mode == 'GPENCIL':
|
||||
# self.layout.operator_context = 'INVOKE_REGION_WIN'
|
||||
# self.layout.operator('gp.duplicate_send_to_layer', text='Move Keys To Layer').delete_source = True
|
||||
# self.layout.operator('gp.duplicate_send_to_layer', text='Copy Keys To Layer')
|
||||
|
||||
classes = (
|
||||
GPTB_OT_move_material_to_layer,
|
||||
)
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
+56
-34
@@ -15,6 +15,35 @@ def get_scale_matrix(scale):
|
||||
matscale = matscale_x @ matscale_y @ matscale_z
|
||||
return matscale
|
||||
|
||||
'''
|
||||
## Old reproject method using Operators:
|
||||
omode = bpy.context.mode
|
||||
|
||||
if all_strokes:
|
||||
layers_state = [[l, l.hide, l.lock, l.lock_frame] for l in obj.data.layers]
|
||||
for l in obj.data.layers:
|
||||
l.hide = False
|
||||
l.lock = False
|
||||
l.lock_frame = False
|
||||
bpy.ops.object.mode_set(mode='EDIT_GPENCIL')
|
||||
|
||||
|
||||
for fnum in frame_list:
|
||||
bpy.context.scene.frame_current = fnum
|
||||
bpy.ops.gpencil.select_all(action='SELECT')
|
||||
bpy.ops.gpencil.reproject(type=proj_type) # 'INVOKE_DEFAULT'
|
||||
bpy.ops.gpencil.select_all(action='DESELECT')
|
||||
|
||||
# restore
|
||||
if all_strokes:
|
||||
for layer, hide, lock, lock_frame in layers_state:
|
||||
layer.hide = hide
|
||||
layer.lock = lock
|
||||
layer.lock_frame = lock_frame
|
||||
|
||||
bpy.ops.object.mode_set(mode=omode)
|
||||
'''
|
||||
|
||||
def batch_reproject(obj, proj_type='VIEW', all_strokes=True, restore_frame=False):
|
||||
'''Reproject - ops method
|
||||
:all_stroke: affect hided, locked layers
|
||||
@@ -72,40 +101,13 @@ def batch_reproject(obj, proj_type='VIEW', all_strokes=True, restore_frame=False
|
||||
# new_local_coords = utils.matrix_transform(new_world_co_3d, matrix_inv).ravel()
|
||||
s.points.foreach_set('co', new_local_coords)
|
||||
|
||||
bpy.context.area.tag_redraw()
|
||||
|
||||
'''
|
||||
## Old method using Operators:
|
||||
omode = bpy.context.mode
|
||||
|
||||
if all_strokes:
|
||||
layers_state = [[l, l.hide, l.lock, l.lock_frame] for l in obj.data.layers]
|
||||
for l in obj.data.layers:
|
||||
l.hide = False
|
||||
l.lock = False
|
||||
l.lock_frame = False
|
||||
bpy.ops.object.mode_set(mode='EDIT_GPENCIL')
|
||||
|
||||
|
||||
for fnum in frame_list:
|
||||
bpy.context.scene.frame_current = fnum
|
||||
bpy.ops.gpencil.select_all(action='SELECT')
|
||||
bpy.ops.gpencil.reproject(type=proj_type) # 'INVOKE_DEFAULT'
|
||||
bpy.ops.gpencil.select_all(action='DESELECT')
|
||||
|
||||
# restore
|
||||
if all_strokes:
|
||||
for layer, hide, lock, lock_frame in layers_state:
|
||||
layer.hide = hide
|
||||
layer.lock = lock
|
||||
layer.lock_frame = lock_frame
|
||||
|
||||
bpy.ops.object.mode_set(mode=omode)
|
||||
'''
|
||||
|
||||
if restore_frame:
|
||||
bpy.context.scene.frame_current = oframe
|
||||
|
||||
## Update the layer and redraw all viewports
|
||||
obj.data.layers.update()
|
||||
utils.refresh_areas()
|
||||
|
||||
def align_global(reproject=True, ref=None, all_strokes=True):
|
||||
|
||||
@@ -369,14 +371,14 @@ class GPTB_OT_batch_reproject_all_frames(bpy.types.Operator):
|
||||
name='All Strokes', default=True,
|
||||
description='Hided and locked layer will also be reprojected')
|
||||
|
||||
type: bpy.props.EnumProperty(name='Type',
|
||||
type : bpy.props.EnumProperty(name='Type',
|
||||
items=(('CURRENT', "Current", ""),
|
||||
('FRONT', "Front", ""),
|
||||
('SIDE', "Side", ""),
|
||||
('TOP', "Top", ""),
|
||||
('VIEW', "View", ""),
|
||||
('SURFACE', "Surface", ""),
|
||||
('CURSOR', "Cursor", ""),
|
||||
# ('SURFACE', "Surface", ""),
|
||||
),
|
||||
default='CURRENT')
|
||||
|
||||
@@ -390,9 +392,29 @@ class GPTB_OT_batch_reproject_all_frames(bpy.types.Operator):
|
||||
layout = self.layout
|
||||
if not context.region_data.view_perspective == 'CAMERA':
|
||||
# layout.label(text='Not in camera ! (reprojection is made from view)', icon='ERROR')
|
||||
layout.label(text='Reprojection is made from camera, not current view', icon='ERROR')
|
||||
layout.label(text='Reprojection is made from camera', icon='ERROR')
|
||||
layout.prop(self, "all_strokes")
|
||||
layout.prop(self, "type")
|
||||
layout.prop(self, "type", text='Project Axis')
|
||||
|
||||
## Hint show axis
|
||||
if self.type == 'CURRENT':
|
||||
## Show as prop
|
||||
# row = layout.row()
|
||||
# row.prop(context.scene.tool_settings.gpencil_sculpt, 'lock_axis', text='Current', icon='INFO')
|
||||
# row.enabled = False
|
||||
|
||||
orient = {
|
||||
'VIEW' : ['View', 'RESTRICT_VIEW_ON'],
|
||||
'AXIS_Y': ['front (X-Z)', 'AXIS_FRONT'], # AXIS_Y
|
||||
'AXIS_X': ['side (Y-Z)', 'AXIS_SIDE'], # AXIS_X
|
||||
'AXIS_Z': ['top (X-Y)', 'AXIS_TOP'], # AXIS_Z
|
||||
'CURSOR': ['Cursor', 'PIVOT_CURSOR'],
|
||||
}
|
||||
box = layout.box()
|
||||
axis = context.scene.tool_settings.gpencil_sculpt.lock_axis
|
||||
box.label(text=orient[axis][0], icon=orient[axis][1])
|
||||
|
||||
|
||||
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Blender addon - Various tool to help with grease pencil in animation productions.
|
||||
|
||||
**[Download latest](https://gitlab.com/autour-de-minuit/blender/gp_toolbox/-/archive/master/gp_toolbox-master.zip)**
|
||||
**[Download latest](https://git.autourdeminuit.com/autour_de_minuit/gp_toolbox/archive/master.zip)**
|
||||
|
||||
**[Demo video](https://www.youtube.com/watch?v=Htgao_uPWNs)**
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
Blender addon - Boîte à outils de grease pencil pour la production d'animation.
|
||||
|
||||
**[Télécharger la dernière version](https://gitlab.com/autour-de-minuit/blender/gp_toolbox/-/archive/master/gp_toolbox-master.zip)**
|
||||
**[Télécharger la dernière version](https://git.autourdeminuit.com/autour_de_minuit/gp_toolbox/archive/master.zip)**
|
||||
|
||||
**[Demo video](https://www.youtube.com/watch?v=Htgao_uPWNs)**
|
||||
|
||||
|
||||
@@ -58,6 +58,11 @@ class GPTB_PT_sidebar_panel(Panel):
|
||||
|
||||
## flip X cam
|
||||
# layout.label(text='! Flipped !')
|
||||
|
||||
row = col.row(align=True)
|
||||
row.prop(context.scene.tool_settings, 'gpencil_stroke_placement_view3d', text='')
|
||||
row.prop(context.scene.tool_settings.gpencil_sculpt, 'lock_axis', text='')
|
||||
|
||||
row = col.row(align=True)
|
||||
|
||||
row.operator('view3d.camera_mirror_flipx', text = 'Mirror Flip', icon = 'MOD_MIRROR')# ARROW_LEFTRIGHT
|
||||
@@ -432,6 +437,8 @@ def palette_manager_menu(self, context):
|
||||
layout.separator()
|
||||
layout.operator("gp.load_palette", text='Load json Palette', icon='IMPORT').filepath = prefs.palette_path
|
||||
layout.operator("gp.save_palette", text='Save json Palette', icon='EXPORT').filepath = prefs.palette_path
|
||||
layout.separator()
|
||||
layout.operator("gp.move_material_to_layer", text='Move Material To Layer', icon='MATERIAL')
|
||||
|
||||
|
||||
def expose_use_channel_color_pref(self, context):
|
||||
|
||||
+6
-4
@@ -4,12 +4,12 @@ bl_info = {
|
||||
"name": "GP toolbox",
|
||||
"description": "Tool set for Grease Pencil in animation production",
|
||||
"author": "Samuel Bernou, Christophe Seux",
|
||||
"version": (2, 5, 0),
|
||||
"blender": (3, 0, 0),
|
||||
"version": (3, 3, 0),
|
||||
"blender": (4, 0, 0),
|
||||
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
||||
"warning": "",
|
||||
"doc_url": "https://gitlab.com/autour-de-minuit/blender/gp_toolbox",
|
||||
"tracker_url": "https://gitlab.com/autour-de-minuit/blender/gp_toolbox/-/issues",
|
||||
"doc_url": "https://git.autourdeminuit.com/autour_de_minuit/gp_toolbox",
|
||||
"tracker_url": "https://git.autourdeminuit.com/autour_de_minuit/gp_toolbox/issues",
|
||||
"category": "3D View",
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ from . import OP_git_update
|
||||
from . import OP_layer_namespace
|
||||
from . import OP_pseudo_tint
|
||||
from . import OP_follow_curve
|
||||
from . import OP_material_move_to_layer
|
||||
# from . import OP_eraser_brush
|
||||
# from . import TOOL_eraser_brush
|
||||
from . import handler_draw_cam
|
||||
@@ -803,6 +804,7 @@ addon_modules = (
|
||||
OP_layer_picker,
|
||||
OP_layer_nav,
|
||||
OP_follow_curve,
|
||||
OP_material_move_to_layer,
|
||||
# OP_eraser_brush,
|
||||
# TOOL_eraser_brush, # experimental eraser brush
|
||||
handler_draw_cam,
|
||||
|
||||
@@ -165,7 +165,6 @@ def randomise_points(mat, points, attr, strength) :
|
||||
setattr(point,attr,value+random*strength)
|
||||
|
||||
|
||||
|
||||
def zoom_to_object(cam, resolution, box, margin=0.01) :
|
||||
min_x= box[0]
|
||||
max_x= box[1]
|
||||
@@ -216,25 +215,6 @@ def zoom_to_object(cam, resolution, box, margin=0.01) :
|
||||
#print(matrix,resolution)
|
||||
return modelview_matrix,projection_matrix,frame,resolution
|
||||
|
||||
|
||||
|
||||
def set_viewport_matrix(width, height, mat):
|
||||
from bgl import glViewport,glMatrixMode,GL_PROJECTION,glLoadMatrixf,Buffer,GL_FLOAT,glMatrixMode,GL_MODELVIEW,glLoadIdentity
|
||||
|
||||
glViewport(0,0,width,height)
|
||||
|
||||
#glLoadIdentity()
|
||||
|
||||
glMatrixMode(GL_PROJECTION)
|
||||
|
||||
projection = [mat[j][i] for i in range(4) for j in range(4)]
|
||||
glLoadMatrixf(Buffer(GL_FLOAT, 16, projection))
|
||||
|
||||
#glMatrixMode( GL_MODELVIEW )
|
||||
#glLoadIdentity()
|
||||
|
||||
|
||||
|
||||
# get object info
|
||||
def get_object_info(mesh_groups, order_list = []) :
|
||||
scene = bpy.context.scene
|
||||
|
||||
+22
-9
@@ -1,6 +1,5 @@
|
||||
import bpy
|
||||
import gpu
|
||||
import bgl
|
||||
# import blf
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
||||
@@ -30,6 +29,20 @@ def view3d_camera_border_2d(context, cam):
|
||||
frame_px = [location_3d_to_region_2d(region, rv3d, v) for v in frame]
|
||||
return frame_px
|
||||
|
||||
def vertices_to_line_loop(v_list, closed=True) -> list:
|
||||
'''Take a sequence of vertices
|
||||
return a position lists of segments to create a line loop passing in all points
|
||||
the result is usable with gpu_shader 'LINES'
|
||||
ex: vlist = [a,b,c] -> closed=True return [a,b,b,c,c,a], closed=False return [a,b,b,c]
|
||||
'''
|
||||
loop = []
|
||||
for i in range(len(v_list) - 1):
|
||||
loop += [v_list[i], v_list[i + 1]]
|
||||
if closed:
|
||||
# Add segment between last and first to close loop
|
||||
loop += [v_list[-1], v_list[0]]
|
||||
return loop
|
||||
|
||||
def draw_cam_frame_callback_2d():
|
||||
context = bpy.context
|
||||
if context.region_data.view_perspective != 'CAMERA':
|
||||
@@ -41,11 +54,12 @@ def draw_cam_frame_callback_2d():
|
||||
if not main_cam:
|
||||
return
|
||||
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
gpu.state.blend_set('ALPHA')
|
||||
|
||||
frame_point = view3d_camera_border_2d(
|
||||
context, context.scene.camera.parent)
|
||||
shader_2d = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
|
||||
shader_2d = gpu.shader.from_builtin('UNIFORM_COLOR') # POLYLINE_FLAT_COLOR
|
||||
# gpu.shader.from_builtin('2D_UNIFORM_COLOR')
|
||||
|
||||
if context.scene.gptoolprops.drawcam_passepartout:
|
||||
### PASSEPARTOUT
|
||||
@@ -109,8 +123,8 @@ def draw_cam_frame_callback_2d():
|
||||
|
||||
### Camera framing trace over
|
||||
|
||||
bgl.glLineWidth(1)
|
||||
bgl.glEnable(bgl.GL_LINE_SMOOTH)
|
||||
gpu.state.line_width_set(1.0)
|
||||
# bgl.glEnable(bgl.GL_LINE_SMOOTH) # old smooth
|
||||
|
||||
"""
|
||||
## need to accurately detect viewport background color (difficult)
|
||||
@@ -135,15 +149,14 @@ def draw_cam_frame_callback_2d():
|
||||
frame_color = (0.0, 0.0, 0.25, 1.0)
|
||||
|
||||
screen_framing = batch_for_shader(
|
||||
shader_2d, 'LINE_LOOP', {"pos": frame_point})
|
||||
shader_2d, 'LINES', {"pos": vertices_to_line_loop(frame_point)})
|
||||
|
||||
shader_2d.bind()
|
||||
shader_2d.uniform_float("color", frame_color)
|
||||
screen_framing.draw(shader_2d)
|
||||
|
||||
# bgl.glLineWidth(1)
|
||||
bgl.glDisable(bgl.GL_LINE_SMOOTH)
|
||||
bgl.glDisable(bgl.GL_BLEND)
|
||||
# bgl.glDisable(bgl.GL_LINE_SMOOTH) # old smooth
|
||||
gpu.state.blend_set('NONE')
|
||||
|
||||
|
||||
draw_handle = None
|
||||
|
||||
@@ -2,12 +2,13 @@ import bpy, os
|
||||
import numpy as np
|
||||
import bmesh
|
||||
import mathutils
|
||||
from mathutils import Vector
|
||||
import math
|
||||
from math import sqrt
|
||||
from sys import platform
|
||||
import subprocess
|
||||
|
||||
from math import sqrt
|
||||
from mathutils import Vector
|
||||
from sys import platform
|
||||
|
||||
|
||||
|
||||
""" def get_gp_parent(layer) :
|
||||
@@ -263,55 +264,6 @@ def remapping(value, leftMin, leftMax, rightMin, rightMax):
|
||||
### GP funcs
|
||||
# -----------------
|
||||
|
||||
""" V1
|
||||
def get_gp_draw_plane(obj=None):
|
||||
''' return tuple with plane coordinate and normal
|
||||
of the curent drawing accordign to geometry'''
|
||||
|
||||
context = bpy.context
|
||||
settings = context.scene.tool_settings
|
||||
orient = settings.gpencil_sculpt.lock_axis #'VIEW', 'AXIS_Y', 'AXIS_X', 'AXIS_Z', 'CURSOR'
|
||||
loc = settings.gpencil_stroke_placement_view3d #'ORIGIN', 'CURSOR', 'SURFACE', 'STROKE'
|
||||
if obj:
|
||||
mat = obj.matrix_world
|
||||
else:
|
||||
mat = context.object.matrix_world if context.object else None
|
||||
|
||||
# -> placement
|
||||
if loc == "CURSOR":
|
||||
plane_co = context.scene.cursor.location
|
||||
|
||||
else: # ORIGIN (also on origin if set to 'SURFACE', 'STROKE')
|
||||
if not context.object:
|
||||
plane_co = None
|
||||
else:
|
||||
plane_co = context.object.matrix_world.to_translation()# context.object.location
|
||||
|
||||
# -> orientation
|
||||
if orient == 'VIEW':
|
||||
#only depth is important, no need to get view vector
|
||||
plane_no = None
|
||||
|
||||
elif orient == 'AXIS_Y':#front (X-Z)
|
||||
plane_no = Vector((0,1,0))
|
||||
plane_no.rotate(mat)
|
||||
|
||||
elif orient == 'AXIS_X':#side (Y-Z)
|
||||
plane_no = Vector((1,0,0))
|
||||
plane_no.rotate(mat)
|
||||
|
||||
elif orient == 'AXIS_Z':#top (X-Y)
|
||||
plane_no = Vector((0,0,1))
|
||||
plane_no.rotate(mat)
|
||||
|
||||
elif orient == 'CURSOR':
|
||||
plane_no = Vector((0,0,1))
|
||||
plane_no.rotate(context.scene.cursor.matrix)
|
||||
|
||||
return plane_co, plane_no
|
||||
"""
|
||||
|
||||
## V2
|
||||
def get_gp_draw_plane(obj=None, orient=None):
|
||||
''' return tuple with plane coordinate and normal
|
||||
of the curent drawing according to geometry'''
|
||||
@@ -336,13 +288,13 @@ def get_gp_draw_plane(obj=None, orient=None):
|
||||
plane_co = bpy.context.scene.cursor.location
|
||||
mat = bpy.context.scene.cursor.matrix
|
||||
|
||||
elif orient == 'AXIS_Y':#front (X-Z)
|
||||
elif orient in ('AXIS_Y', 'FRONT'): # front (X-Z)
|
||||
plane_no = Vector((0,1,0))
|
||||
|
||||
elif orient == 'AXIS_X':#side (Y-Z)
|
||||
elif orient in ('AXIS_X', 'SIDE'): # side (Y-Z)
|
||||
plane_no = Vector((1,0,0))
|
||||
|
||||
elif orient == 'AXIS_Z':#top (X-Y)
|
||||
elif orient in ('AXIS_Z', 'TOP'): # top (X-Y)
|
||||
plane_no = Vector((0,0,1))
|
||||
|
||||
plane_no.rotate(mat)
|
||||
@@ -459,14 +411,14 @@ def get_gp_datas(selection=True):
|
||||
print('EOL. No active GP object')
|
||||
return []
|
||||
|
||||
def get_gp_layer(gp_data_block,name) :
|
||||
def get_gp_layer(gp_data_block, name) :
|
||||
gp_layer = gp_data_block.layers.get(name)
|
||||
if not gp_layer :
|
||||
gp_layer = gp_data_block.layers.new(name)
|
||||
|
||||
return gp_layer
|
||||
|
||||
def get_gp_frame(layer,frame_nb = None) :
|
||||
def get_gp_frame(layer, frame_nb=None) :
|
||||
scene = bpy.context.scene
|
||||
if not frame_nb :
|
||||
frame_nb = scene.frame_current
|
||||
@@ -532,9 +484,72 @@ def selected_strokes(frame):
|
||||
stlist.append(s)
|
||||
return stlist
|
||||
|
||||
from math import sqrt
|
||||
from mathutils import Vector
|
||||
## Copy stroke to a frame
|
||||
|
||||
def copy_stroke_to_frame(s, frame, select=True):
|
||||
'''Copy stroke to given frame
|
||||
return created stroke
|
||||
'''
|
||||
|
||||
ns = frame.strokes.new()
|
||||
|
||||
## Set strokes attr
|
||||
stroke_attr = [
|
||||
'line_width',
|
||||
'material_index',
|
||||
'draw_cyclic',
|
||||
'use_cyclic',
|
||||
'uv_scale',
|
||||
'uv_rotation',
|
||||
'hardness',
|
||||
'uv_translation',
|
||||
'vertex_color_fill',
|
||||
]
|
||||
|
||||
for attr in stroke_attr:
|
||||
if not hasattr(s, attr):
|
||||
continue
|
||||
# print(f'transfer stroke {attr}') # Dbg
|
||||
setattr(ns, attr, getattr(s, attr))
|
||||
|
||||
## create points
|
||||
point_count = len(s.points)
|
||||
ns.points.add(len(s.points))
|
||||
|
||||
## Set points attr
|
||||
# for p, np in zip(s.points, ns.points):
|
||||
flat_list = [0.0] * point_count
|
||||
flat_uv_fill_list = [0.0, 0.0] * point_count
|
||||
flat_vector_list = [0.0, 0.0, 0.0] * point_count
|
||||
flat_color_list = [0.0, 0.0, 0.0, 0.0] * point_count
|
||||
|
||||
single_attr = [
|
||||
'pressure',
|
||||
'strength',
|
||||
'uv_factor',
|
||||
'uv_rotation',
|
||||
]
|
||||
|
||||
for attr in single_attr:
|
||||
# print(f'transfer point {attr}') # Dbg
|
||||
s.points.foreach_get(attr, flat_list)
|
||||
ns.points.foreach_set(attr, flat_list)
|
||||
|
||||
# print(f'transfer point co') # Dbg
|
||||
s.points.foreach_get('co', flat_vector_list)
|
||||
ns.points.foreach_set('co', flat_vector_list)
|
||||
|
||||
# print(f'transfer point uv_fill') # Dbg
|
||||
s.points.foreach_get('uv_fill', flat_uv_fill_list)
|
||||
ns.points.foreach_set('uv_fill', flat_uv_fill_list)
|
||||
|
||||
# print(f'transfer point vertex_color') # Dbg
|
||||
s.points.foreach_get('vertex_color', flat_color_list)
|
||||
ns.points.foreach_set('vertex_color', flat_color_list)
|
||||
|
||||
ns.select = select
|
||||
ns.points.update()
|
||||
return ns
|
||||
|
||||
# -----------------
|
||||
### Vector utils 3d
|
||||
@@ -838,6 +853,13 @@ def show_message_box(_message = "", _title = "Message Box", _icon = 'INFO'):
|
||||
### UI utils
|
||||
# -----------------
|
||||
|
||||
def refresh_areas():
|
||||
for window in bpy.context.window_manager.windows:
|
||||
for area in window.screen.areas:
|
||||
area.tag_redraw()
|
||||
# for area in bpy.context.screen.areas:
|
||||
# area.tag_redraw()
|
||||
|
||||
## kmi draw for addon without delete button
|
||||
def draw_kmi(km, kmi, layout):
|
||||
map_type = kmi.map_type
|
||||
|
||||
Reference in New Issue
Block a user