Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b4c797e7e | ||
|
|
beba17fe2f | ||
|
|
2efbb36ddf | ||
|
|
060fa8ff92 | ||
|
|
81e21bb428 | ||
|
|
9c7184836b | ||
|
|
bfcac1615d | ||
|
|
6eabc02ce8 | ||
|
|
3d7a208a50 | ||
|
|
adfd887924 | ||
|
|
86139a20fb | ||
|
|
682048af63 | ||
|
|
94e3b7a7ad | ||
|
|
f10f572bdc | ||
|
|
d8f71e3356 | ||
|
|
2754ebf89b | ||
|
|
4b9cd20891 | ||
|
|
e22b91265b | ||
|
|
6cf22b81e8 | ||
|
|
517eceab76 | ||
|
|
9b06aee9af | ||
|
|
3594c4812d | ||
|
|
bf85582f28 | ||
|
|
492095d333 | ||
|
|
07a44190c9 | ||
|
|
b193c67358 | ||
|
|
02ed04cd3d | ||
|
|
801235c760 | ||
|
|
6d43064f0b | ||
|
|
b9dd1196ca | ||
|
|
5e876f360b |
+341
@@ -0,0 +1,341 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
1.5.1
|
||||||
|
|
||||||
|
- fix: eraser brush
|
||||||
|
- change: check file has custom check list in addon prefs
|
||||||
|
|
||||||
|
1.5.0
|
||||||
|
|
||||||
|
- feat: Eraser Brush Tool (Need to be enable in the preferences)
|
||||||
|
|
||||||
|
1.4.3
|
||||||
|
|
||||||
|
- feat: load brushes from blend
|
||||||
|
- ui: add load brushes within tool brush dropdown panel and in the top bar in drawmode
|
||||||
|
- pref: Set project brushes folder in addon preferences
|
||||||
|
|
||||||
|
1.4.2
|
||||||
|
|
||||||
|
- feat: new material cleaner in GP layer menu with 3 options
|
||||||
|
- clean material duplication (with sub option to not clear if color settings are different)
|
||||||
|
- fuse material slots that have the same materials
|
||||||
|
- remove empty slots
|
||||||
|
|
||||||
|
1.4.1
|
||||||
|
|
||||||
|
- fix: custom passepartout size limit when dezooming in camera
|
||||||
|
|
||||||
|
1.4.0
|
||||||
|
|
||||||
|
- feat: Passepartout displayed in main cam (permanent draw handler)
|
||||||
|
- UI: add Straight stroke operator button in Toolbox if GP tools official addon is on
|
||||||
|
- UI: placed Toolbox Playblast in a subpanel
|
||||||
|
- UI: removed Onion skin and Autolock layer checkbox (not often used)
|
||||||
|
- UI: sent rarely `cursor follow` to bottom of the panel
|
||||||
|
|
||||||
|
1.3.3
|
||||||
|
|
||||||
|
- feat: show main cam frame when in draw_cam
|
||||||
|
|
||||||
|
1.3.2:
|
||||||
|
|
||||||
|
- change: disable manip cam name drawing
|
||||||
|
- code: add initial support for main cam frame draw within camera view
|
||||||
|
|
||||||
|
1.3.1:
|
||||||
|
|
||||||
|
- fix: native refresh error that rarely happen that doesn't completely refresh the scene on keyframe jump.
|
||||||
|
- Use a double frame change to ensure refresh.
|
||||||
|
|
||||||
|
1.3.0:
|
||||||
|
|
||||||
|
- feat: new duplicate send to layer feaure - `ctrl + shift + D` in GP dopesheet
|
||||||
|
|
||||||
|
1.2.2:
|
||||||
|
|
||||||
|
- fix: realign anim return error
|
||||||
|
|
||||||
|
1.2.1:
|
||||||
|
|
||||||
|
- fix: Breakdowner initial error check
|
||||||
|
|
||||||
|
1.2.0:
|
||||||
|
|
||||||
|
- feat: New depth move operator that handle both perspective and orthographic cam
|
||||||
|
- feat: Realign, added drawing plane checkbox to autoset to Front after realign
|
||||||
|
- UI: Reorganised realign panel
|
||||||
|
- UI: Switched part of the sidebar items to columns intead of basic layout to gain space
|
||||||
|
- doc: Added changelog file (moved list from readme)
|
||||||
|
- doc: relative link to changelog and FR_readme in main readme
|
||||||
|
|
||||||
|
1.1.0:
|
||||||
|
|
||||||
|
- Important change : Remap relative is now disabled by default in addon preferences
|
||||||
|
- feat: Add realign operator in sidebar with reproject as true by default
|
||||||
|
- UI: Batch reproject all frames is now in menus. Same places as native reproject
|
||||||
|
|
||||||
|
1.0.9:
|
||||||
|
|
||||||
|
- feat: Reproject all frames operator
|
||||||
|
|
||||||
|
1.0.8:
|
||||||
|
|
||||||
|
- feat: Keyframe jump filter added in UI to change general behavior. Keymap own jump filter can override this new global settings if specified
|
||||||
|
|
||||||
|
1.0.7:
|
||||||
|
|
||||||
|
- feat: Keyframe jump filter by type. User can now choose if the shortcut should jump on a specific keyframe type (All by default)
|
||||||
|
|
||||||
|
1.0.5:
|
||||||
|
|
||||||
|
- GP copy-paste : Pasted stroke are now selected (allow to use it to quickly rip/split strokes with cut/paste on the same layer)
|
||||||
|
|
||||||
|
1.0.4:
|
||||||
|
|
||||||
|
- UI: Better cam ref exposition in Toolbox panel
|
||||||
|
- Access to opacity
|
||||||
|
- merge activation bool with source type icon
|
||||||
|
- feat: Added a clear active frame operator (`gp.clear_active_frame` to add manually in keymaps)
|
||||||
|
|
||||||
|
1.0.3:
|
||||||
|
|
||||||
|
- feat: add "Append Materials To Selected" to material submenu. Append materials to other selected GP objects if there aren't there.
|
||||||
|
|
||||||
|
1.0.2:
|
||||||
|
|
||||||
|
- pref: Added option to disable always remap relative on save in addon-preference
|
||||||
|
|
||||||
|
1.0.1:
|
||||||
|
|
||||||
|
- fix: copy paste problems
|
||||||
|
- Get points uv_properties (used for brushed points)
|
||||||
|
- Trigger an update on each pasted strokes, recalculate badly drawn uv and fills (works in 2.93+)
|
||||||
|
|
||||||
|
1.0.0:
|
||||||
|
|
||||||
|
- Compatible with official grease pencil tools
|
||||||
|
- removed box deform and rotate canvas that existed in other
|
||||||
|
|
||||||
|
0.9.3:
|
||||||
|
|
||||||
|
- feat: keyframe jump keys are now auto-binded
|
||||||
|
- UI: added keyframe jump customisation in addon pref
|
||||||
|
- code: split keyframe jump in a separate file with his new key updater
|
||||||
|
|
||||||
|
0.9.2:
|
||||||
|
|
||||||
|
- doc: Correct download link (important, bugged the addon install) + update
|
||||||
|
- code: added tracker url
|
||||||
|
- updater: remove updater temp file, reset minimum version, turn off verbose mode
|
||||||
|
|
||||||
|
0.9.1:
|
||||||
|
|
||||||
|
- Public release
|
||||||
|
- prefs: added fps as part of project settings
|
||||||
|
- check file use pref fps value (previously used harcoded 24fps value)
|
||||||
|
- cleanup: Remove wip GMIC-bridge tools that need to be done separately (if needed)
|
||||||
|
- update: apply changes in integrated copy-paste from the last version of standalone addon
|
||||||
|
- doc: Added fully-detailed french readme
|
||||||
|
|
||||||
|
|
||||||
|
0.8.0:
|
||||||
|
|
||||||
|
- feat: Added background_rendering playblast, derivating from Tonton's playblaster
|
||||||
|
- stripped associated properties from properties.py and passed as wm props.
|
||||||
|
|
||||||
|
0.7.2:
|
||||||
|
|
||||||
|
- fix: Palette importer bug
|
||||||
|
|
||||||
|
0.7.0:
|
||||||
|
|
||||||
|
- feat: auto create empty frame on color layer
|
||||||
|
|
||||||
|
0.6.3:
|
||||||
|
|
||||||
|
- shortcut: added 1,2,3 to change sculpt mask mode (like native edit mode shortcut)
|
||||||
|
|
||||||
|
0.6.2:
|
||||||
|
|
||||||
|
- feat: colorisation, Option to change stop lines length
|
||||||
|
- Change behavior of `cursor_snap` ops when a non-GP object is selected to mode: `surface project`
|
||||||
|
- Minor refactor for submodule register
|
||||||
|
|
||||||
|
0.6.1:
|
||||||
|
|
||||||
|
- feat: render objects grouped, one anim render with all ticked object using manual output name
|
||||||
|
|
||||||
|
0.6.0:
|
||||||
|
|
||||||
|
- feat: Include GP clipoard's "In place" custom cut/copy/paste using OS clipboard
|
||||||
|
|
||||||
|
0.5.9:
|
||||||
|
|
||||||
|
- feat: render exporter
|
||||||
|
- Render a selection of GP object isolated from the rest
|
||||||
|
- added exclusions names for GP object listing
|
||||||
|
- setup settings and output according to a name
|
||||||
|
- open render folder
|
||||||
|
- check file: set onion skin keyframe filter to 'All_type' on all GP datablock
|
||||||
|
- check file: set scene resolution to settings in prefs (default 2048x1080)
|
||||||
|
|
||||||
|
0.5.8:
|
||||||
|
|
||||||
|
- feat: GP material append on active object from single blend file
|
||||||
|
|
||||||
|
0.5.7:
|
||||||
|
|
||||||
|
- Added warning message for cursor snapping
|
||||||
|
|
||||||
|
0.5.5 - 0.5.6:
|
||||||
|
|
||||||
|
- check file: added check for placement an projection mode for Gpencil.
|
||||||
|
- add a slider to change edit_lines_opacity globally for all GP data at once
|
||||||
|
- check file: auto-check additive drawing (to avoid empty frame with "only selected channel" in Dopesheet)
|
||||||
|
|
||||||
|
0.5.4:
|
||||||
|
|
||||||
|
- feat: anim manager in his own GP_toolbox submenu:
|
||||||
|
- button to list disabled anim (allow to quickly check state of the scene)
|
||||||
|
- disable/enable all fcurve in for GP object or other object separately to paint
|
||||||
|
- shift clic to target selection only
|
||||||
|
- check file: added disabled fcurved counter alert with detail in console
|
||||||
|
|
||||||
|
0.5.3:
|
||||||
|
|
||||||
|
- fix: broken obj cam (add custom prop on objcam to track wich was main cam)
|
||||||
|
- check file option: change select active tool (choice added in addon preferences)
|
||||||
|
|
||||||
|
0.5.2:
|
||||||
|
|
||||||
|
- Revert back obj_cam operator for following object (native lock view follow only translation)
|
||||||
|
- Changed method for canvas rotation to more robust rotate axis.
|
||||||
|
- Add operators on link checker to open containing folder/file of link
|
||||||
|
- Refactor: file checkers in their own file
|
||||||
|
|
||||||
|
0.5.1:
|
||||||
|
|
||||||
|
- fix: error when empty material slot on GP object.
|
||||||
|
- fix: cursor snap on GP canvas when GP is parented
|
||||||
|
- change: Deleted obj cam (and related set view) operator
|
||||||
|
- change: blacker note background for playblast (stamp_background)
|
||||||
|
- feat: Always playblast from main camera (if in draw_cam)
|
||||||
|
- feat: Handler added to Remap relative on save (pre)
|
||||||
|
- ops: Check for broken links with porposition to find missing files
|
||||||
|
- ops: Added basic hardcoded file checker
|
||||||
|
- Lock main cam
|
||||||
|
- set scene percentage at 100
|
||||||
|
- set show slider and sync range
|
||||||
|
- set fps to 24
|
||||||
|
|
||||||
|
0.4.6:
|
||||||
|
|
||||||
|
- feat: basic Palette manager with base material check and warning
|
||||||
|
|
||||||
|
0.4.5:
|
||||||
|
|
||||||
|
- open blender config folder from addon preference
|
||||||
|
- fix: obj cam parent on selected object
|
||||||
|
- added wip rotate canvas axis file. still not ready to replace current canvas rotate:
|
||||||
|
- freeview : bug when rotating free viewfrom cardianl views
|
||||||
|
- camview: potential bug when cam is parented with some specific angle (could not reproduce)
|
||||||
|
|
||||||
|
|
||||||
|
0.4.4:
|
||||||
|
|
||||||
|
- feat: added cursor follow handlers and UI toggle
|
||||||
|
|
||||||
|
0.4.3:
|
||||||
|
|
||||||
|
- change playblast out to 'images' and add playblast as name prefix
|
||||||
|
|
||||||
|
0.4.2:
|
||||||
|
|
||||||
|
- feat: GP canvas cursor snap wiht new `view3d.cusor_snap` operator
|
||||||
|
- fix: canvas rotate works with parented camera !
|
||||||
|
- wip: added an attmpt to replicate camera rotate modal with view matrix but no luck.
|
||||||
|
|
||||||
|
0.4.1:
|
||||||
|
|
||||||
|
- feat: Alternative cameras: parent to main cam (roll without affecting main cam), parent to active object at current view (follow current Grease pencil object)
|
||||||
|
|
||||||
|
0.4.0:
|
||||||
|
|
||||||
|
- Added a standalone working version of box_deform (stripped preferences keeping only best configuration with autoswap)
|
||||||
|
|
||||||
|
0.3.8:
|
||||||
|
|
||||||
|
- UI: expose onion skin in interface
|
||||||
|
- UI: expose autolock in interface
|
||||||
|
- UI : putted tint layers in a submenu
|
||||||
|
- code: refactor, pushed most of class register in their owner file
|
||||||
|
- tool: tool to rename current or all grease pencil datablock with different name than container object
|
||||||
|
|
||||||
|
0.3.7:
|
||||||
|
|
||||||
|
- UI: new interface with tabs for addon preferences
|
||||||
|
- UI: possible to disable color panel from preference (might be deleted if unusable)
|
||||||
|
- docs: change readme changelog format and correct doc
|
||||||
|
|
||||||
|
0.3.6:
|
||||||
|
|
||||||
|
- UI: Stoplines : add a button for quickly set stoplines visibility.
|
||||||
|
|
||||||
|
0.3.5:
|
||||||
|
|
||||||
|
- Fix : No more camera rotation undo when ctrl+Z on next stroke (canvas rotate push and undo)
|
||||||
|
- Fix: Enter key added to valid object-breakdown modal.
|
||||||
|
|
||||||
|
0.3.3:
|
||||||
|
|
||||||
|
- version 1 beta (stable) of line gap closing tools for better bucket fill tool performance with UI
|
||||||
|
|
||||||
|
0.3.3:
|
||||||
|
|
||||||
|
- version 1 beta of gmic colorize
|
||||||
|
- variant of `screen.gp_keyframe_jump` through keymap seetings
|
||||||
|
|
||||||
|
0.3.0:
|
||||||
|
|
||||||
|
- new homemade [breakdowner operator for object](https://blenderartists.org/t/pose-mode-animation-tools-for-object-mode/1221322) mode with auto keymap : Shift + E
|
||||||
|
- GP cutter shortcut ops to map with `wm.temp_cutter` (with "Any" as press mode) or `wm.sticky_cutter` (Modal sticky-key version)
|
||||||
|
|
||||||
|
0.2.3:
|
||||||
|
|
||||||
|
- add operator to `screen.gp_keyframe_jump`
|
||||||
|
- add shortcut to rotate canvas
|
||||||
|
- fix duplicate class
|
||||||
|
|
||||||
|
0.2.2:
|
||||||
|
|
||||||
|
- separated props resolution_percentage parameter
|
||||||
|
- playblast options for launching folder and opening folder
|
||||||
|
|
||||||
|
0.2.1:
|
||||||
|
|
||||||
|
- playblast feature
|
||||||
|
- Button to go zoom 100% or fit screen
|
||||||
|
- display scene resolution with res indicator
|
||||||
|
- Fix reference panel : works with video and display in a box layout.
|
||||||
|
- close pseudo-color panel by default (plan to move it to Gpencil tab)
|
||||||
|
|
||||||
|
0.2.0:
|
||||||
|
|
||||||
|
- UI: Toggle camera background images from Toolbox panel
|
||||||
|
- UI: quick access to passepartout
|
||||||
|
- Feature: option to use namespace for pseudo color
|
||||||
|
|
||||||
|
0.1.5:
|
||||||
|
|
||||||
|
- added CGC-auto-updater
|
||||||
|
|
||||||
|
0.1.3:
|
||||||
|
|
||||||
|
- flip cam x
|
||||||
|
- inital stage of overlay toggle (need pref/multiple pref)
|
||||||
|
|
||||||
|
0.1.2:
|
||||||
|
|
||||||
|
- subpanel of GP data (instead of direct append)
|
||||||
|
- initial commit with GP pseudo color
|
||||||
+16
-9
@@ -211,16 +211,23 @@ class OBJ_OT_breakdown_obj_anim(bpy.types.Operator):
|
|||||||
## cursors
|
## cursors
|
||||||
## 'DEFAULT', 'NONE', 'WAIT', 'CROSSHAIR', 'MOVE_X', 'MOVE_Y', 'KNIFE', 'TEXT', 'PAINT_BRUSH', 'PAINT_CROSS', 'DOT', 'ERASER', 'HAND', 'SCROLL_X', 'SCROLL_Y', 'SCROLL_XY', 'EYEDROPPER'
|
## 'DEFAULT', 'NONE', 'WAIT', 'CROSSHAIR', 'MOVE_X', 'MOVE_Y', 'KNIFE', 'TEXT', 'PAINT_BRUSH', 'PAINT_CROSS', 'DOT', 'ERASER', 'HAND', 'SCROLL_X', 'SCROLL_Y', 'SCROLL_XY', 'EYEDROPPER'
|
||||||
## start checks
|
## start checks
|
||||||
message = None
|
if context.area.type != 'VIEW_3D':
|
||||||
if context.area.type != 'VIEW_3D':message = 'View3D not found, cannot run operator'
|
self.report({'WARNING'}, 'View3D not found, cannot run operator')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
obj = bpy.context.object# better use self.context
|
obj = bpy.context.object# better use self.context
|
||||||
if not obj:message = 'no active object'
|
if not obj:
|
||||||
|
self.report({'WARNING'}, 'No active object')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
anim_data = obj.animation_data
|
anim_data = obj.animation_data
|
||||||
if not anim_data:message = f'no animation data on obj: {obj.name}'
|
if not anim_data:
|
||||||
|
self.report({'WARNING'}, f'No animation data on obj: {obj.name}')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
action = anim_data.action
|
action = anim_data.action
|
||||||
if not action:message = f'no action on animation data of obj: {obj.name}'
|
if not action:
|
||||||
if message:
|
self.report({'WARNING'}, f'No action on animation data of obj: {obj.name}')
|
||||||
self.report({'WARNING'}, message)# ERROR
|
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
## initiate variable to use
|
## initiate variable to use
|
||||||
@@ -325,12 +332,12 @@ def unregister_keymaps():
|
|||||||
### --- REGISTER ---
|
### --- REGISTER ---
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
if not bpy.app.background:
|
# if not bpy.app.background:
|
||||||
bpy.utils.register_class(OBJ_OT_breakdown_obj_anim)
|
bpy.utils.register_class(OBJ_OT_breakdown_obj_anim)
|
||||||
register_keymaps()
|
register_keymaps()
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
if not bpy.app.background:
|
# if not bpy.app.background:
|
||||||
unregister_keymaps()
|
unregister_keymaps()
|
||||||
bpy.utils.unregister_class(OBJ_OT_breakdown_obj_anim)
|
bpy.utils.unregister_class(OBJ_OT_breakdown_obj_anim)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import bpy
|
||||||
|
from bpy_extras.io_utils import ImportHelper
|
||||||
|
from pathlib import Path
|
||||||
|
from .utils import get_addon_prefs
|
||||||
|
|
||||||
|
|
||||||
|
def get_brushes(blend_fp):
|
||||||
|
'''Get all brush from passed blend that aren't already in there (can take Path object)'''
|
||||||
|
cur_brushes = [b.name for b in bpy.data.brushes]
|
||||||
|
with bpy.data.libraries.load(str(blend_fp), link=False) as (data_from, data_to):
|
||||||
|
# load brushes if not already there
|
||||||
|
data_to.brushes = [b for b in data_from.brushes if not b in cur_brushes]
|
||||||
|
|
||||||
|
## force fake user for appended the brushes
|
||||||
|
for b in data_to.brushes:
|
||||||
|
print(f'Append Brush: {b.name}')
|
||||||
|
b.use_fake_user = True
|
||||||
|
|
||||||
|
return len(data_to.brushes)
|
||||||
|
|
||||||
|
|
||||||
|
class GPTB_OT_load_brushes(bpy.types.Operator, ImportHelper):
|
||||||
|
bl_idname = "gp.load_brushes"
|
||||||
|
bl_label = "Load Brushes"
|
||||||
|
bl_description = "Load all brushes from chosen blend file in current if brushes aren't already there\nIf a replacement is needed, delete the previous brush before"
|
||||||
|
#bl_options = {"REGISTER", "INTERNAL"}
|
||||||
|
|
||||||
|
# @classmethod
|
||||||
|
# def poll(cls, context):
|
||||||
|
# return context.object and context.object.type == 'GPENCIL'
|
||||||
|
|
||||||
|
filename_ext = '.blend'
|
||||||
|
|
||||||
|
filter_glob: bpy.props.StringProperty(default='*.blend', options={'HIDDEN'} )
|
||||||
|
|
||||||
|
filepath : bpy.props.StringProperty(
|
||||||
|
name="File Path",
|
||||||
|
description="File path used for import",
|
||||||
|
maxlen= 1024)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
print(f'Appending brushes from file : {self.filepath}')
|
||||||
|
bct = get_brushes(self.filepath)
|
||||||
|
if bct:
|
||||||
|
self.report({'INFO'}, f'{bct} brushes appended')
|
||||||
|
else:
|
||||||
|
self.report({'WARNING'}, 'Brushes are already there (if need to re-import, delete first)')
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
### -- MENU ENTRY --
|
||||||
|
|
||||||
|
def load_brush_ui(self, context):
|
||||||
|
if context.mode == 'PAINT_GPENCIL':
|
||||||
|
self.layout.operator('gp.load_brushes', icon='SMALL_TRI_RIGHT_VEC') # KEYTYPE_JITTER_VEC
|
||||||
|
|
||||||
|
def load_brush_top_bar_ui(self, context):
|
||||||
|
if context.mode == 'PAINT_GPENCIL':
|
||||||
|
self.layout.operator('gp.load_brushes')
|
||||||
|
|
||||||
|
classes = (
|
||||||
|
GPTB_OT_load_brushes,
|
||||||
|
)
|
||||||
|
|
||||||
|
def register():
|
||||||
|
for cl in classes:
|
||||||
|
bpy.utils.register_class(cl)
|
||||||
|
|
||||||
|
bpy.types.VIEW3D_MT_brush_gpencil_context_menu.append(load_brush_ui)
|
||||||
|
bpy.types.VIEW3D_HT_tool_header.append(load_brush_top_bar_ui)
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
bpy.types.VIEW3D_HT_tool_header.remove(load_brush_top_bar_ui)
|
||||||
|
bpy.types.VIEW3D_MT_brush_gpencil_context_menu.remove(load_brush_ui)
|
||||||
|
|
||||||
|
for cl in reversed(classes):
|
||||||
|
bpy.utils.unregister_class(cl)
|
||||||
+10
-4
@@ -316,7 +316,7 @@ def copy_all_strokes_in_frame(frame=None, layers=None, obj=None):
|
|||||||
#print(stroke_list)
|
#print(stroke_list)
|
||||||
return stroke_list
|
return stroke_list
|
||||||
|
|
||||||
def add_stroke(s, frame, layer, obj):
|
def add_stroke(s, frame, layer, obj, select=False):
|
||||||
'''add stroke on a given frame, (layer is for parentage setting)'''
|
'''add stroke on a given frame, (layer is for parentage setting)'''
|
||||||
# print(3*'-',s)
|
# print(3*'-',s)
|
||||||
ns = frame.strokes.new()
|
ns = frame.strokes.new()
|
||||||
@@ -343,6 +343,9 @@ def add_stroke(s, frame, layer, obj):
|
|||||||
ns.points[i].co = ob_mat_inv @ mat @ ns.points[i].co# invert of object * invert of layer * coordinate
|
ns.points[i].co = ob_mat_inv @ mat @ ns.points[i].co# invert of object * invert of layer * coordinate
|
||||||
else:
|
else:
|
||||||
setattr(ns.points[i], k, v)
|
setattr(ns.points[i], k, v)
|
||||||
|
if select:
|
||||||
|
ns.points[i].select = True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for i, pt in enumerate(s['points']):
|
for i, pt in enumerate(s['points']):
|
||||||
for k, v in pt.items():
|
for k, v in pt.items():
|
||||||
@@ -351,6 +354,8 @@ def add_stroke(s, frame, layer, obj):
|
|||||||
ns.points[i].co = ob_mat_inv @ ns.points[i].co# invert of object * coordinate
|
ns.points[i].co = ob_mat_inv @ ns.points[i].co# invert of object * coordinate
|
||||||
else:
|
else:
|
||||||
setattr(ns.points[i], k, v)
|
setattr(ns.points[i], k, v)
|
||||||
|
if select:
|
||||||
|
ns.points[i].select = True
|
||||||
|
|
||||||
## trigger updapte (in 2.93 fix some drawing problem with fills and UVs)
|
## trigger updapte (in 2.93 fix some drawing problem with fills and UVs)
|
||||||
ns.points.update()
|
ns.points.update()
|
||||||
@@ -358,11 +363,12 @@ def add_stroke(s, frame, layer, obj):
|
|||||||
## patch pressure 2
|
## patch pressure 2
|
||||||
# ns.points.foreach_set('pressure', pressure_flat_list)
|
# ns.points.foreach_set('pressure', pressure_flat_list)
|
||||||
|
|
||||||
def add_multiple_strokes(stroke_list, layer=None, use_current_frame=True):
|
def add_multiple_strokes(stroke_list, layer=None, use_current_frame=True, select=False):
|
||||||
'''
|
'''
|
||||||
add a list of strokes to active frame of given layer
|
add a list of strokes to active frame of given layer
|
||||||
if no layer specified, active layer is used
|
if no layer specified, active layer is used
|
||||||
if use_current_frame is True, a new frame will be created only if needed
|
if use_current_frame is True, a new frame will be created only if needed
|
||||||
|
if select is True, newly added strokes are selected
|
||||||
'''
|
'''
|
||||||
scene = bpy.context.scene
|
scene = bpy.context.scene
|
||||||
obj = bpy.context.object
|
obj = bpy.context.object
|
||||||
@@ -388,7 +394,7 @@ def add_multiple_strokes(stroke_list, layer=None, use_current_frame=True):
|
|||||||
#or active exists but not aligned scene.current with use_current_frame disabled
|
#or active exists but not aligned scene.current with use_current_frame disabled
|
||||||
target_frame = layer.frames.new(fnum)
|
target_frame = layer.frames.new(fnum)
|
||||||
|
|
||||||
add_stroke(s, target_frame, layer, obj)
|
add_stroke(s, target_frame, layer, obj, select=select)
|
||||||
'''
|
'''
|
||||||
for s in stroke_data:
|
for s in stroke_data:
|
||||||
add_stroke(s, target_frame)
|
add_stroke(s, target_frame)
|
||||||
@@ -478,7 +484,7 @@ class GPCLIP_OT_paste_strokes(bpy.types.Operator):
|
|||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
print('data loaded', time() - t0)
|
print('data loaded', time() - t0)
|
||||||
add_multiple_strokes(data, use_current_frame=True)
|
add_multiple_strokes(data, use_current_frame=True, select=True)
|
||||||
print('total_time', time() - t0)
|
print('total_time', time() - t0)
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import bpy
|
||||||
|
from mathutils import Vector
|
||||||
|
|
||||||
|
class ODM_OT_depth_move(bpy.types.Operator):
|
||||||
|
bl_idname = "object.depth_proportional_move"
|
||||||
|
bl_label = "Depth move"
|
||||||
|
bl_description = "Move object in the depth from camera POV while retaining same size in framing"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.object and context.object.type != 'CAMERA' # and context.scene.camera
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
self.init_mouse_x = event.mouse_x
|
||||||
|
self.cam = bpy.context.scene.camera
|
||||||
|
if not self.cam:
|
||||||
|
self.report({'ERROR'}, 'No active camera')
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
self.cam_pos = self.cam.matrix_world.translation
|
||||||
|
self.mode = 'distance'
|
||||||
|
self.objects = [o for o in context.selected_objects if o.type != 'CAMERA']
|
||||||
|
self.init_mats = [o.matrix_world.copy() for o in self.objects]
|
||||||
|
|
||||||
|
if self.cam.data.type == 'ORTHO':
|
||||||
|
context.area.header_text_set(f'Move factor: 0.00')
|
||||||
|
# distance is view vector based
|
||||||
|
self.view_vector = Vector((0,0,-1))
|
||||||
|
self.view_vector.rotate(self.cam.matrix_world)
|
||||||
|
else:
|
||||||
|
self.init_vecs = [o.matrix_world.translation - self.cam_pos for o in self.objects]
|
||||||
|
self.init_dists = [v.length for v in self.init_vecs]
|
||||||
|
context.area.header_text_set(f'Move factor: 0.00 | Mode: {self.mode} (M to switch)')
|
||||||
|
|
||||||
|
context.window_manager.modal_handler_add(self)
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
|
def modal(self, context, event):
|
||||||
|
if self.mode == 'distance':
|
||||||
|
factor = 0.1
|
||||||
|
if event.shift:
|
||||||
|
factor = 0.01
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Smaller factor for proportional dist
|
||||||
|
factor = 0.01
|
||||||
|
if event.shift:
|
||||||
|
factor = 0.001
|
||||||
|
|
||||||
|
if event.type in {'MOUSEMOVE'}:
|
||||||
|
diff = (event.mouse_x - self.init_mouse_x) * factor
|
||||||
|
|
||||||
|
if self.cam.data.type == 'ORTHO':
|
||||||
|
# just push in view vector direction
|
||||||
|
context.area.header_text_set(f'Move factor: {diff:.2f}')
|
||||||
|
for i, obj in enumerate(self.objects):
|
||||||
|
new_vec = self.init_mats[i].translation + (self.view_vector * diff)
|
||||||
|
obj.matrix_world.translation = new_vec
|
||||||
|
else:
|
||||||
|
# Push from camera point and scale accordingly
|
||||||
|
context.area.header_text_set(f'Move factor: {diff:.2f} | Mode: {self.mode} (M to switch)')
|
||||||
|
|
||||||
|
for i, obj in enumerate(self.objects):
|
||||||
|
if self.mode == 'distance':
|
||||||
|
## move with the same length for everyone
|
||||||
|
new_vec = self.init_vecs[i] + (self.init_vecs[i].normalized() * diff)
|
||||||
|
|
||||||
|
else:
|
||||||
|
## move with proportional factor from individual distance vector to camera
|
||||||
|
new_vec = self.init_vecs[i] + (self.init_vecs[i] * diff)
|
||||||
|
|
||||||
|
obj.matrix_world.translation = self.cam_pos + new_vec
|
||||||
|
dist_percentage = new_vec.length / self.init_dists[i]
|
||||||
|
|
||||||
|
obj.scale = self.init_mats[i].to_scale() * dist_percentage
|
||||||
|
|
||||||
|
if event.type in {'M'} and event.value == 'PRESS':
|
||||||
|
# Switch mode
|
||||||
|
self.mode = 'distance' if self.mode == 'proportional' else 'proportional'
|
||||||
|
|
||||||
|
if event.type in {'LEFTMOUSE'} and event.value == 'PRESS':
|
||||||
|
context.area.header_text_set(None)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
if event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS':
|
||||||
|
for i, obj in enumerate(self.objects):
|
||||||
|
obj.matrix_world = self.init_mats[i]
|
||||||
|
context.area.header_text_set(None)
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
return {"RUNNING_MODAL"}
|
||||||
|
|
||||||
|
""" # Own standalone panel
|
||||||
|
class ODM_PT_sudden_depth_panel(bpy.types.Panel):
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
bl_category = "Gpencil"
|
||||||
|
bl_label = "Depth move"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
row = layout.row()
|
||||||
|
row.operator('object.depth_proportional_move', text='Depth move', icon='TRANSFORM_ORIGINS')
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
### --- REGISTER ---
|
||||||
|
|
||||||
|
classes=(
|
||||||
|
ODM_OT_depth_move,
|
||||||
|
)
|
||||||
|
|
||||||
|
def register():
|
||||||
|
for cls in classes:
|
||||||
|
bpy.utils.register_class(cls)
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
for cls in reversed(classes):
|
||||||
|
bpy.utils.unregister_class(cls)
|
||||||
@@ -0,0 +1,517 @@
|
|||||||
|
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
|
||||||
|
from time import time
|
||||||
|
from mathutils import Vector, Matrix, Euler
|
||||||
|
from mathutils.kdtree import KDTree
|
||||||
|
from mathutils.geometry import intersect_line_plane, intersect_line_sphere_2d, intersect_line_line
|
||||||
|
from bpy_extras.view3d_utils import region_2d_to_location_3d, region_2d_to_vector_3d, \
|
||||||
|
location_3d_to_region_2d, region_2d_to_origin_3d, region_2d_to_location_3d
|
||||||
|
from time import time
|
||||||
|
from math import pi, cos, sin
|
||||||
|
|
||||||
|
|
||||||
|
def get_gp_mat(gp, name, set_active=False):
|
||||||
|
mat = bpy.data.materials.get(name)
|
||||||
|
if not mat:
|
||||||
|
mat = bpy.data.materials.new(name)
|
||||||
|
bpy.data.materials.create_gpencil_data(mat)
|
||||||
|
|
||||||
|
if mat not in gp.data.materials[:]:
|
||||||
|
gp.data.materials.append(mat)
|
||||||
|
mat_index = gp.data.materials[:].index(mat)
|
||||||
|
|
||||||
|
if set_active:
|
||||||
|
gp.active_material_index = mat_index
|
||||||
|
|
||||||
|
return mat
|
||||||
|
|
||||||
|
def get_gp_frame(gp_layer, frame=None):
|
||||||
|
if frame is None:
|
||||||
|
frame = bpy.context.scene.frame_current
|
||||||
|
gp_frame = next((f for f in gp_layer.frames if f.frame_number==frame), None)
|
||||||
|
if not gp_frame:
|
||||||
|
gp_frame = gp_layer.frames.new(frame)
|
||||||
|
|
||||||
|
return gp_frame
|
||||||
|
|
||||||
|
def get_gp_layer(gp, name=None):
|
||||||
|
if not name:
|
||||||
|
return gp.data.layers.active
|
||||||
|
|
||||||
|
layer = gp.data.layers.get(name)
|
||||||
|
if not layer:
|
||||||
|
layer = gp.data.layers.new(name)
|
||||||
|
|
||||||
|
gp.data.layers.active = layer
|
||||||
|
|
||||||
|
return layer
|
||||||
|
|
||||||
|
def co_2d_to_3d(co, depth=0.1):
|
||||||
|
area = bpy.context.area
|
||||||
|
region = bpy.context.region
|
||||||
|
rv3d = area.spaces.active.region_3d
|
||||||
|
view_mat = rv3d.view_matrix.inverted()
|
||||||
|
org = view_mat.to_translation()
|
||||||
|
|
||||||
|
depth_3d = view_mat @ Vector((0, 0, -depth))
|
||||||
|
#org = region_2d_to_origin_3d(region, rv3d, (region.width/2.0, region.height/2.0))
|
||||||
|
|
||||||
|
return region_2d_to_location_3d(region, rv3d, co, depth_3d)
|
||||||
|
|
||||||
|
|
||||||
|
#vec = (region_2d_to_origin_3d(region, rv3d, co) - org).normalized()
|
||||||
|
|
||||||
|
return org + vec
|
||||||
|
|
||||||
|
return org + region_2d_to_vector_3d(region, rv3d , co)
|
||||||
|
|
||||||
|
def get_cuts_data(strokes, mouse, radius):
|
||||||
|
gp = bpy.context.object
|
||||||
|
|
||||||
|
area = bpy.context.area
|
||||||
|
region = bpy.context.region
|
||||||
|
rv3d = area.spaces.active.region_3d
|
||||||
|
view_mat = rv3d.view_matrix.inverted()
|
||||||
|
org = view_mat.to_translation()
|
||||||
|
mat = gp.matrix_world
|
||||||
|
|
||||||
|
cuts_data = []
|
||||||
|
for s in strokes:
|
||||||
|
is_polyline = 2<=len(s.points)<=5
|
||||||
|
|
||||||
|
if not is_polyline and not s.select:
|
||||||
|
continue
|
||||||
|
|
||||||
|
print('Cut Stroke', s)
|
||||||
|
for i, p in enumerate(s.points):
|
||||||
|
|
||||||
|
if not p.select and not is_polyline:
|
||||||
|
continue
|
||||||
|
# Test if the next or previous is unselected
|
||||||
|
|
||||||
|
edges = []
|
||||||
|
|
||||||
|
if i > 0:
|
||||||
|
prev_p = s.points[i-1]
|
||||||
|
if not prev_p.select and not is_polyline:
|
||||||
|
edges.append((i-1, i))
|
||||||
|
|
||||||
|
if i < len(s.points)-1:
|
||||||
|
next_p = s.points[i+1]
|
||||||
|
if not next_p.select or is_polyline:
|
||||||
|
edges.append((i, i+1))
|
||||||
|
|
||||||
|
for p1_index, p2_index in edges:
|
||||||
|
p1 = s.points[p1_index]
|
||||||
|
p2 = s.points[p2_index]
|
||||||
|
|
||||||
|
length_3d = (p2.co-p1.co).length
|
||||||
|
|
||||||
|
p1_3d = mat @ p1.co
|
||||||
|
p2_3d = mat @ p2.co
|
||||||
|
|
||||||
|
p1_2d = location_3d_to_region_2d(region, rv3d, p1_3d)
|
||||||
|
p2_2d = location_3d_to_region_2d(region, rv3d, p2_3d)
|
||||||
|
|
||||||
|
if p1_2d is None or p2_2d is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
length_2d = (p2_2d-p1_2d).length
|
||||||
|
|
||||||
|
if length_2d <= 4:
|
||||||
|
continue
|
||||||
|
|
||||||
|
intersects = intersect_line_sphere_2d(p1_2d, p2_2d, mouse, radius+2)
|
||||||
|
intersects = [i for i in intersects if i is not None]
|
||||||
|
if not intersects:
|
||||||
|
continue
|
||||||
|
|
||||||
|
close_points = [(p1_2d-i).length < 1 or (p2_2d-i).length < 1 for i in intersects]
|
||||||
|
if any(close_points):
|
||||||
|
#print('close_points', close_points)
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
print('intersects', intersects)
|
||||||
|
|
||||||
|
line_intersects = []
|
||||||
|
for i_2d in intersects:
|
||||||
|
#factor = ((i_2d-p1_2d).length) / length_2d
|
||||||
|
#factor_3d = factor_2d * length_3d
|
||||||
|
#vec = region_2d_to_vector_3d(region, rv3d, i_2d)
|
||||||
|
#p3_3d = region_2d_to_location_3d(region, rv3d, i_2d, org)
|
||||||
|
#p4_3d = region_2d_to_origin_3d(region, rv3d, i_2d)
|
||||||
|
|
||||||
|
p3_3d = co_2d_to_3d(i_2d, 0.1)
|
||||||
|
p4_3d = co_2d_to_3d(i_2d, 1000)
|
||||||
|
|
||||||
|
#bpy.context.scene.cursor.location = p4_3d
|
||||||
|
|
||||||
|
line_intersect = intersect_line_line(p1_3d, p2_3d, p3_3d, p4_3d)
|
||||||
|
if not line_intersect:
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
i1_3d, _ = line_intersect
|
||||||
|
|
||||||
|
line_intersects += [i1_3d]
|
||||||
|
|
||||||
|
#context.scene.cursor.location = i1_3d
|
||||||
|
|
||||||
|
print('line_intersects', line_intersects)
|
||||||
|
if line_intersects:
|
||||||
|
line_intersects.sort(key=lambda x : (x-p1_3d).length)
|
||||||
|
#cut_data[-1].sort(key=lambda x : (x-p1_3d).length)
|
||||||
|
cut_data = [p1_index, p2_index, s, line_intersects]
|
||||||
|
cuts_data.append(cut_data)
|
||||||
|
|
||||||
|
return cuts_data
|
||||||
|
|
||||||
|
|
||||||
|
def circle(x, y, radius, segments):
|
||||||
|
coords = []
|
||||||
|
m = (1.0 / (segments - 1)) * (pi * 2)
|
||||||
|
|
||||||
|
for p in range(segments):
|
||||||
|
p1 = x + cos(m * p) * radius
|
||||||
|
p2 = y + sin(m * p) * radius
|
||||||
|
coords.append((p1, p2))
|
||||||
|
return coords
|
||||||
|
|
||||||
|
|
||||||
|
class GPTB_OT_eraser(Operator):
|
||||||
|
"""Draw a line with the mouse"""
|
||||||
|
bl_idname = "gp.eraser"
|
||||||
|
bl_label = "Eraser Brush"
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
def draw_callback_px(self):
|
||||||
|
bgl.glEnable(bgl.GL_BLEND)
|
||||||
|
#bgl.glBlendFunc(bgl.GL_CONSTANT_ALPHA, bgl.GL_ONE_MINUS_CONSTANT_ALPHA)
|
||||||
|
#bgl.glBlendColor(1.0, 1.0, 1.0, 0.1)
|
||||||
|
|
||||||
|
area = bpy.context.area
|
||||||
|
#region = bpy.context.region
|
||||||
|
#rv3d = area.spaces.active.region_3d
|
||||||
|
|
||||||
|
bg_color = area.spaces.active.shading.background_color
|
||||||
|
#print(bg_color)
|
||||||
|
|
||||||
|
shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
|
||||||
|
shader.bind()
|
||||||
|
shader.uniform_float("color", (1, 1, 1, 1))
|
||||||
|
for mouse, radius in self.mouse_path:
|
||||||
|
circle_co = circle(*mouse, radius, 24)
|
||||||
|
batch = batch_for_shader(shader, 'TRI_FAN', {"pos": circle_co})
|
||||||
|
batch.draw(shader)
|
||||||
|
|
||||||
|
draw_circle_2d(self.mouse, (0.75, 0.25, 0.35, 1.0), self.radius, 24)
|
||||||
|
bgl.glDisable(bgl.GL_BLEND)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
def draw_holdout(self, context, event):
|
||||||
|
gp = context.object
|
||||||
|
mat_inv = gp.matrix_world.inverted()
|
||||||
|
mouse_3d = co_2d_to_3d(self.mouse)
|
||||||
|
radius_3d = co_2d_to_3d(self.mouse + Vector((self.radius, 0)))
|
||||||
|
search_radius = (radius_3d-mouse_3d).length
|
||||||
|
|
||||||
|
#print('search_radius', search_radius)
|
||||||
|
#print('radius', self.radius)
|
||||||
|
|
||||||
|
#bpy.context.scene.cursor.location = mouse_3d
|
||||||
|
|
||||||
|
for gp_frame, hld_stroke in self.hld_strokes:
|
||||||
|
#print('Add Point')
|
||||||
|
|
||||||
|
hld_stroke.points.add(count=1)
|
||||||
|
p = hld_stroke.points[-1]
|
||||||
|
p.co = mat_inv @ mouse_3d
|
||||||
|
p.pressure = search_radius * 2000
|
||||||
|
|
||||||
|
#context.scene.cursor.location = mouse_3d
|
||||||
|
'''
|
||||||
|
def get_radius(self, context, event):
|
||||||
|
pressure = event.pressure or 1
|
||||||
|
return context.scene.gptoolprops.eraser_radius * pressure
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def erase(self, context, event):
|
||||||
|
gp = context.object
|
||||||
|
mat_inv = gp.matrix_world.inverted()
|
||||||
|
|
||||||
|
new_points = []
|
||||||
|
|
||||||
|
#print(self.cuts_data)
|
||||||
|
|
||||||
|
# for f in self.gp_frames:
|
||||||
|
# for s in [s for s in f.strokes if s.material_index==self.hld_index]:
|
||||||
|
# f.strokes.remove(s)
|
||||||
|
|
||||||
|
#gp.data.materials.pop(index=self.hld_index)
|
||||||
|
#bpy.data.materials.remove(self.hld_mat)
|
||||||
|
|
||||||
|
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT_GPENCIL')
|
||||||
|
context.scene.tool_settings.gpencil_selectmode_edit = 'POINT'
|
||||||
|
#context.scene.tool_settings.gpencil_selectmode_edit = 'POINT'
|
||||||
|
|
||||||
|
#bpy.ops.gpencil.select_circle(x=x, y=y, radius=radius, wait_for_input=False)
|
||||||
|
|
||||||
|
#for cut_data in self.cuts_data:
|
||||||
|
|
||||||
|
# print(cut_data, len(cut_data))
|
||||||
|
t0 = time()
|
||||||
|
print()
|
||||||
|
|
||||||
|
print('Number of cuts', len(self.mouse_path))
|
||||||
|
|
||||||
|
for mouse, radius in self.mouse_path:
|
||||||
|
t1 = time()
|
||||||
|
|
||||||
|
print()
|
||||||
|
x, y = mouse
|
||||||
|
bpy.ops.gpencil.select_all(action='DESELECT')
|
||||||
|
bpy.ops.gpencil.select_circle(x=x, y=y, radius=radius, wait_for_input=False)
|
||||||
|
|
||||||
|
strokes = [s for f in self.gp_frames for s in f.strokes]
|
||||||
|
#print('select_circle', time()-t1)
|
||||||
|
|
||||||
|
t2 = time()
|
||||||
|
cut_data = get_cuts_data(strokes, mouse, radius)
|
||||||
|
#print('get_cuts_data', time()-t2)
|
||||||
|
|
||||||
|
#print([s for s in strokes if s.select])
|
||||||
|
print('cut_data', cut_data)
|
||||||
|
|
||||||
|
t3 = time()
|
||||||
|
for p1_index, p2_index, stroke, intersects in cut_data[::-1]:
|
||||||
|
bpy.ops.gpencil.select_all(action='DESELECT')
|
||||||
|
|
||||||
|
#print('p1_index', p1_index)
|
||||||
|
#print('p2_index', p2_index)
|
||||||
|
|
||||||
|
p1 = stroke.points[p1_index]
|
||||||
|
p2 = stroke.points[p2_index]
|
||||||
|
|
||||||
|
p1.select = True
|
||||||
|
p2.select = True
|
||||||
|
|
||||||
|
number_cuts = len(intersects)
|
||||||
|
|
||||||
|
bpy.ops.gpencil.stroke_subdivide(number_cuts=number_cuts, only_selected=True)
|
||||||
|
|
||||||
|
new_p1 = stroke.points[p1_index+1]
|
||||||
|
new_p1.co = mat_inv@intersects[0]
|
||||||
|
new_points += [(stroke, p1_index+1)]
|
||||||
|
|
||||||
|
#print('number_cuts', number_cuts)
|
||||||
|
|
||||||
|
if number_cuts == 2:
|
||||||
|
new_p2 = stroke.points[p1_index+2]
|
||||||
|
new_p2.co = mat_inv@( (intersects[0] + intersects[1])/2 )
|
||||||
|
#new_points += [new_p2]
|
||||||
|
|
||||||
|
new_p3 = stroke.points[p1_index+3]
|
||||||
|
new_p3.co = mat_inv@intersects[1]
|
||||||
|
new_points += [(stroke, p1_index+3)]
|
||||||
|
|
||||||
|
#print('subdivide', time() - t3)
|
||||||
|
|
||||||
|
bpy.ops.gpencil.select_all(action='DESELECT')
|
||||||
|
bpy.ops.gpencil.select_circle(x=x, y=y, radius=radius, wait_for_input=False)
|
||||||
|
|
||||||
|
'''
|
||||||
|
selected_strokes = [s for f in self.gp_frames for s in f.strokes if s.select]
|
||||||
|
tip_points = [p for s in selected_strokes for i, p in enumerate(s.points) if p.select and (i==0 or i == len(s.points)-1)]
|
||||||
|
|
||||||
|
bpy.ops.gpencil.select_less()
|
||||||
|
|
||||||
|
for p in tip_points:
|
||||||
|
p.select = True
|
||||||
|
for stroke, index in new_points:
|
||||||
|
stroke.points[index].select = False
|
||||||
|
'''
|
||||||
|
|
||||||
|
t4 = time()
|
||||||
|
selected_strokes = [s for f in self.gp_frames for s in f.strokes if s.select]
|
||||||
|
|
||||||
|
if selected_strokes:
|
||||||
|
bpy.ops.gpencil.delete(type='POINTS')
|
||||||
|
|
||||||
|
print('remove points', time()- t4)
|
||||||
|
|
||||||
|
|
||||||
|
#print('Total one cut', time()-t1)
|
||||||
|
|
||||||
|
#print('Total all cuts', time()-t0)
|
||||||
|
#bpy.ops.gpencil.select_less()
|
||||||
|
#for stroke, index in new_points:
|
||||||
|
# stroke.points[index].select = False
|
||||||
|
|
||||||
|
#bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
context.scene.tool_settings.gpencil_selectmode_edit = self.gpencil_selectmode_edit
|
||||||
|
bpy.ops.object.mode_set(mode='PAINT_GPENCIL')
|
||||||
|
#selected_strokes = [s for s in self.gp_frame.strokes if s.select]
|
||||||
|
#bpy.ops.object.mode_set(mode='PAINT_GPENCIL')
|
||||||
|
|
||||||
|
def modal(self, context, event):
|
||||||
|
self.mouse = Vector((event.mouse_region_x, event.mouse_region_y))
|
||||||
|
|
||||||
|
self.radius = self.get_radius(context, event)
|
||||||
|
context.area.tag_redraw()
|
||||||
|
|
||||||
|
if event.type == 'LEFTMOUSE':
|
||||||
|
#self.mouse = mouse
|
||||||
|
#self.mouse_path.append((self.mouse, self.radius))
|
||||||
|
self.erase(context, event)
|
||||||
|
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
if (self.mouse-self.mouse_prev).length < max(self.radius/1.33, 2):
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
|
self.mouse_prev = self.mouse
|
||||||
|
|
||||||
|
if event.type in {'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE'}:
|
||||||
|
#self.draw_holdout(context, event)
|
||||||
|
self.mouse_path.append((self.mouse, self.radius))
|
||||||
|
#self.update_cuts_data(context, event)
|
||||||
|
#self.erase(context, event)
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
|
elif event.type in {'RIGHTMOUSE', 'ESC'}:
|
||||||
|
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
gp = context.object
|
||||||
|
matrix = gp.matrix_world
|
||||||
|
|
||||||
|
self.gpencil_selectmode_edit = context.scene.tool_settings.gpencil_selectmode_edit
|
||||||
|
self.radius = self.get_radius(context, event)
|
||||||
|
self.mouse_prev = self.mouse = Vector((event.mouse_region_x, event.mouse_region_y))
|
||||||
|
self.mouse_path = [(self.mouse_prev, self.radius)]
|
||||||
|
|
||||||
|
area = context.area
|
||||||
|
region = context.region
|
||||||
|
w, h = region.width, region.height
|
||||||
|
|
||||||
|
rv3d = area.spaces.active.region_3d
|
||||||
|
view_mat = rv3d.view_matrix.inverted()
|
||||||
|
org = self.org = view_mat.to_translation()
|
||||||
|
|
||||||
|
#org = region_2d_to_origin_3d(region, rv3d, (region.width/2.0, region.height/2.0))
|
||||||
|
|
||||||
|
|
||||||
|
#print('ORG', org)
|
||||||
|
#print('view_mat', view_mat)
|
||||||
|
|
||||||
|
|
||||||
|
self.cuts_data = []
|
||||||
|
|
||||||
|
#org = self.view_mat @ Vector((0, 0, -10))
|
||||||
|
#self.plane_no = self.plane_co-self.org
|
||||||
|
|
||||||
|
#bottom_left = region_2d_to_location_3d(region, rv3d , (0, 0), self.plane_co)
|
||||||
|
#bottom_right = region_2d_to_location_3d(region, rv3d , (0, w), self.plane_co)
|
||||||
|
|
||||||
|
#bottom_left = intersect_line_plane(self.org, bottom_left, self.plane_co, self.plane_no)
|
||||||
|
#bottom_right = intersect_line_plane(self.org, bottom_right, self.plane_co, self.plane_no)
|
||||||
|
|
||||||
|
#self.scale_fac = (bottom_right-bottom_left).length / w
|
||||||
|
|
||||||
|
#print('scale_fac', self.scale_fac)
|
||||||
|
#depth_location = view_mat @ Vector((0, 0, -1))
|
||||||
|
#context.scene.cursor.location = depth_location
|
||||||
|
|
||||||
|
#plane_2d = [(0, 0), (0, h), (w, h), (w, h)]
|
||||||
|
#plane_3d = [region_2d_to_location_3d(p)]
|
||||||
|
|
||||||
|
|
||||||
|
t0 = time()
|
||||||
|
gp_mats = gp.data.materials
|
||||||
|
gp_layers = [l for l in gp.data.layers if not l.lock or l.hide]
|
||||||
|
self.gp_frames = [l.active_frame for l in gp_layers]
|
||||||
|
'''
|
||||||
|
points_data = [(s, f, gp_mats[s.material_index]) for f in gp_frames for s in f.strokes]
|
||||||
|
points_data = [(s, f, m) for s, f, m in points_data if not m.grease_pencil.hide or m.grease_pencil.lock]
|
||||||
|
print('get_gp_points', time()-t0)
|
||||||
|
|
||||||
|
t0 = time()
|
||||||
|
#points_data = [(s, f, m, p, get_screen_co(p.co, matrix)) for s, f, m in points_data for p in reversed(s.points)]
|
||||||
|
points_data = [(s, f, m, p, org + ((matrix @ p.co)-org).normalized()*1) for s, f, m in points_data for p in reversed(s.points)]
|
||||||
|
print('points_to_2d', time()-t0)
|
||||||
|
|
||||||
|
#print(points_data)
|
||||||
|
self.points_data = [(s, f, m, p, co) for s, f, m, p, co in points_data if co is not None]
|
||||||
|
|
||||||
|
#for s, f, m, p, co in self.points_data:
|
||||||
|
# p.co = co
|
||||||
|
|
||||||
|
|
||||||
|
t0 = time()
|
||||||
|
self.kd_tree = KDTree(len(self.points_data))
|
||||||
|
for i, point_data in enumerate(self.points_data):
|
||||||
|
s, f, m, p, co = point_data
|
||||||
|
self.kd_tree.insert(co, i)
|
||||||
|
|
||||||
|
self.kd_tree.balance()
|
||||||
|
print('create kdtree', time()-t0)
|
||||||
|
'''
|
||||||
|
|
||||||
|
'''
|
||||||
|
# Create holdout mat
|
||||||
|
self.hld_mat = get_gp_mat(gp, name='Eraser Holdout Stroke')
|
||||||
|
self.hld_mat.grease_pencil.use_stroke_holdout = True
|
||||||
|
self.hld_mat.grease_pencil.show_stroke = True
|
||||||
|
self.hld_mat.grease_pencil.show_fill = False
|
||||||
|
self.hld_mat.grease_pencil.use_overlap_strokes = True
|
||||||
|
|
||||||
|
self.hld_index = gp_mats[:].index(self.hld_mat)
|
||||||
|
|
||||||
|
self.hld_strokes = []
|
||||||
|
for f in self.gp_frames:
|
||||||
|
hld_stroke = f.strokes.new()
|
||||||
|
hld_stroke.start_cap_mode = 'ROUND'
|
||||||
|
hld_stroke.end_cap_mode = 'ROUND'
|
||||||
|
hld_stroke.material_index = self.hld_index
|
||||||
|
|
||||||
|
#hld_stroke.line_width = self.radius
|
||||||
|
|
||||||
|
self.hld_strokes.append((f, hld_stroke))
|
||||||
|
|
||||||
|
self.draw_holdout(context, event)
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
context.area.tag_redraw()
|
||||||
|
self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, (), 'WINDOW', 'POST_PIXEL')
|
||||||
|
|
||||||
|
context.window_manager.modal_handler_add(self)
|
||||||
|
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
|
### --- REGISTER ---
|
||||||
|
|
||||||
|
classes=(
|
||||||
|
GPTB_OT_eraser,
|
||||||
|
)
|
||||||
|
|
||||||
|
def register():
|
||||||
|
for cls in classes:
|
||||||
|
bpy.utils.register_class(cls)
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
for cls in reversed(classes):
|
||||||
|
bpy.utils.unregister_class(cls)
|
||||||
Regular → Executable
+48
-19
@@ -27,10 +27,12 @@ class GPTB_OT_file_checker(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
prefs = get_addon_prefs()
|
prefs = get_addon_prefs()
|
||||||
|
fix = prefs.fixprops
|
||||||
problems = []
|
problems = []
|
||||||
|
|
||||||
## Lock main cam:
|
## Lock main cam:
|
||||||
if not 'layout' in Path(bpy.data.filepath).stem:#dont touch layout cameras
|
if fix.lock_main_cam:
|
||||||
|
if not 'layout' in Path(bpy.data.filepath).stem.lower():#dont touch layout cameras
|
||||||
if context.scene.camera:
|
if context.scene.camera:
|
||||||
cam = context.scene.camera
|
cam = context.scene.camera
|
||||||
if cam.name == 'draw_cam' and cam.parent:
|
if cam.name == 'draw_cam' and cam.parent:
|
||||||
@@ -45,6 +47,7 @@ class GPTB_OT_file_checker(bpy.types.Operator):
|
|||||||
cam.lock_location = cam.lock_rotation = triple
|
cam.lock_location = cam.lock_rotation = triple
|
||||||
|
|
||||||
## set scene res at pref res according to addon pref
|
## set scene res at pref res according to addon pref
|
||||||
|
if fix.set_scene_res:
|
||||||
rx, ry = prefs.render_res_x, prefs.render_res_y
|
rx, ry = prefs.render_res_x, prefs.render_res_y
|
||||||
# TODO set (rx, ry) to camera resolution if specified in camera name
|
# TODO set (rx, ry) to camera resolution if specified in camera name
|
||||||
if context.scene.render.resolution_x != rx or context.scene.render.resolution_y != ry:
|
if context.scene.render.resolution_x != rx or context.scene.render.resolution_y != ry:
|
||||||
@@ -52,11 +55,19 @@ class GPTB_OT_file_checker(bpy.types.Operator):
|
|||||||
context.scene.render.resolution_x, context.scene.render.resolution_y = rx, ry
|
context.scene.render.resolution_x, context.scene.render.resolution_y = rx, ry
|
||||||
|
|
||||||
## set scene percentage at 100:
|
## set scene percentage at 100:
|
||||||
|
if fix.set_res_percentage:
|
||||||
if context.scene.render.resolution_percentage != 100:
|
if context.scene.render.resolution_percentage != 100:
|
||||||
problems.append('Resolution output to 100%')
|
problems.append('Resolution output to 100%')
|
||||||
context.scene.render.resolution_percentage = 100
|
context.scene.render.resolution_percentage = 100
|
||||||
|
|
||||||
|
## set fps according to preferences settings
|
||||||
|
if fix.set_fps:
|
||||||
|
if context.scene.render.fps != prefs.fps:
|
||||||
|
problems.append( (f"framerate corrected {context.scene.render.fps} >> {prefs.fps}", 'ERROR') )
|
||||||
|
context.scene.render.fps = prefs.fps
|
||||||
|
|
||||||
## set show slider and sync range
|
## set show slider and sync range
|
||||||
|
if fix.set_slider_n_sync:
|
||||||
for window in bpy.context.window_manager.windows:
|
for window in bpy.context.window_manager.windows:
|
||||||
screen = window.screen
|
screen = window.screen
|
||||||
for area in screen.areas:
|
for area in screen.areas:
|
||||||
@@ -67,12 +78,8 @@ class GPTB_OT_file_checker(bpy.types.Operator):
|
|||||||
if hasattr(area.spaces[0], 'show_locked_time'):
|
if hasattr(area.spaces[0], 'show_locked_time'):
|
||||||
setattr(area.spaces[0], 'show_locked_time', True)
|
setattr(area.spaces[0], 'show_locked_time', True)
|
||||||
|
|
||||||
## set fps according to preferences settings
|
|
||||||
if context.scene.render.fps != prefs.fps:
|
|
||||||
problems.append( (f"framerate corrected {context.scene.render.fps} >> {prefs.fps}", 'ERROR') )
|
|
||||||
context.scene.render.fps = prefs.fps
|
|
||||||
|
|
||||||
## set cursor type (according to prefs ?)
|
## set cursor type (according to prefs ?)
|
||||||
|
if fix.set_cursor_type:
|
||||||
if context.mode in ("EDIT_GPENCIL", "SCULPT_GPENCIL"):
|
if context.mode in ("EDIT_GPENCIL", "SCULPT_GPENCIL"):
|
||||||
tool = prefs.select_active_tool
|
tool = prefs.select_active_tool
|
||||||
if tool != 'none':
|
if tool != 'none':
|
||||||
@@ -80,19 +87,24 @@ class GPTB_OT_file_checker(bpy.types.Operator):
|
|||||||
bpy.ops.wm.tool_set_by_id(name=tool)# Tweaktoolcode
|
bpy.ops.wm.tool_set_by_id(name=tool)# Tweaktoolcode
|
||||||
problems.append(f'tool changed to {tool.split(".")[1]}')
|
problems.append(f'tool changed to {tool.split(".")[1]}')
|
||||||
|
|
||||||
## GP use additive drawing (else creating a frame in dopesheet makes it blank...)
|
# ## GP use additive drawing (else creating a frame in dopesheet makes it blank...)
|
||||||
if not context.scene.tool_settings.use_gpencil_draw_additive:
|
# if not context.scene.tool_settings.use_gpencil_draw_additive:
|
||||||
problems.append(f'Activated Gp additive drawing mode (snowflake)')
|
# problems.append(f'Activated Gp additive drawing mode (snowflake)')
|
||||||
context.scene.tool_settings.use_gpencil_draw_additive = True
|
# context.scene.tool_settings.use_gpencil_draw_additive = True
|
||||||
|
|
||||||
|
|
||||||
## GP stroke placement/projection check
|
## GP stroke placement/projection check
|
||||||
|
if fix.check_front_axis:
|
||||||
if context.scene.tool_settings.gpencil_sculpt.lock_axis != 'AXIS_Y':
|
if context.scene.tool_settings.gpencil_sculpt.lock_axis != 'AXIS_Y':
|
||||||
problems.append('/!\\ Draw axis not "Front" (Need Manual change if not Ok)')
|
problems.append('/!\\ Draw axis not "Front" (Need Manual change if not Ok)')
|
||||||
|
|
||||||
|
if fix.check_placement:
|
||||||
if bpy.context.scene.tool_settings.gpencil_stroke_placement_view3d != 'ORIGIN':
|
if bpy.context.scene.tool_settings.gpencil_stroke_placement_view3d != 'ORIGIN':
|
||||||
problems.append('/!\\ Draw placement not "Origin" (Need Manual change if not Ok)')
|
problems.append('/!\\ Draw placement not "Origin" (Need Manual change if not Ok)')
|
||||||
|
|
||||||
|
|
||||||
## Disabled animation
|
## Disabled animation
|
||||||
|
if fix.list_disabled_anim:
|
||||||
fcu_ct = 0
|
fcu_ct = 0
|
||||||
for act in bpy.data.actions:
|
for act in bpy.data.actions:
|
||||||
if not act.users:
|
if not act.users:
|
||||||
@@ -104,15 +116,32 @@ class GPTB_OT_file_checker(bpy.types.Operator):
|
|||||||
if fcu_ct:
|
if fcu_ct:
|
||||||
problems.append(f'{fcu_ct} anim channel disabled (details -> console)')
|
problems.append(f'{fcu_ct} anim channel disabled (details -> console)')
|
||||||
|
|
||||||
## Set onion skin filter to 'All type'
|
## Use median point
|
||||||
fix_kf_type = 0
|
if fix.set_pivot_median_point:
|
||||||
for gp in bpy.data.grease_pencils:#from data
|
if context.scene.tool_settings.transform_pivot_point != 'MEDIAN_POINT':
|
||||||
if not gp.is_annotation:
|
problems.append(f"Pivot changed from '{context.scene.tool_settings.transform_pivot_point}' to 'MEDIAN_POINT'")
|
||||||
if gp.onion_keyframe_type != 'ALL':
|
context.scene.tool_settings.transform_pivot_point = 'MEDIAN_POINT'
|
||||||
gp.onion_keyframe_type = 'ALL'
|
|
||||||
fix_kf_type += 1
|
if fix.disable_guide:
|
||||||
if fix_kf_type:
|
if context.scene.tool_settings.gpencil_sculpt.guide.use_guide == True:
|
||||||
problems.append(f"{fix_kf_type} GP onion skin filter to 'All type'")
|
problems.append(f"Disabled Draw Guide")
|
||||||
|
context.scene.tool_settings.gpencil_sculpt.guide.use_guide = False
|
||||||
|
|
||||||
|
if fix.autokey_add_n_replace:
|
||||||
|
if context.scene.tool_settings.auto_keying_mode != 'ADD_REPLACE_KEYS':
|
||||||
|
problems.append(f"Autokey mode reset to 'Add & Replace'")
|
||||||
|
context.scene.tool_settings.auto_keying_mode = 'ADD_REPLACE_KEYS'
|
||||||
|
|
||||||
|
# ## Set onion skin filter to 'All type'
|
||||||
|
# fix_kf_type = 0
|
||||||
|
# for gp in bpy.data.grease_pencils:#from data
|
||||||
|
# if not gp.is_annotation:
|
||||||
|
# if gp.onion_keyframe_type != 'ALL':
|
||||||
|
# gp.onion_keyframe_type = 'ALL'
|
||||||
|
# fix_kf_type += 1
|
||||||
|
# if fix_kf_type:
|
||||||
|
# problems.append(f"{fix_kf_type} GP onion skin filter to 'All type'")
|
||||||
|
|
||||||
# for ob in context.scene.objects:#from object
|
# for ob in context.scene.objects:#from object
|
||||||
# if ob.type == 'GPENCIL':
|
# if ob.type == 'GPENCIL':
|
||||||
# ob.data.onion_keyframe_type = 'ALL'
|
# ob.data.onion_keyframe_type = 'ALL'
|
||||||
|
|||||||
+41
-4
@@ -153,10 +153,11 @@ class GPTB_OT_draw_cam(bpy.types.Operator):
|
|||||||
# get main cam and error if not available
|
# get main cam and error if not available
|
||||||
if drawcam.name == 'draw_cam':
|
if drawcam.name == 'draw_cam':
|
||||||
maincam = drawcam.parent
|
maincam = drawcam.parent
|
||||||
|
maincam.data.show_passepartout = context.scene.gptoolprops.drawcam_passepartout
|
||||||
|
|
||||||
else:
|
else:
|
||||||
maincam = None
|
maincam = None
|
||||||
main_name = drawcam.get('maincam_name')# Custom prop with previous avtive cam.
|
main_name = drawcam.get('maincam_name')# Custom prop with previous active cam.
|
||||||
if main_name:
|
if main_name:
|
||||||
maincam = context.scene.objects.get(main_name)
|
maincam = context.scene.objects.get(main_name)
|
||||||
|
|
||||||
@@ -187,14 +188,17 @@ class GPTB_OT_draw_cam(bpy.types.Operator):
|
|||||||
if not drawcam:
|
if not drawcam:
|
||||||
created=True
|
created=True
|
||||||
drawcam = bpy.data.objects.new(dcam_name, context.scene.camera.data)
|
drawcam = bpy.data.objects.new(dcam_name, context.scene.camera.data)
|
||||||
drawcam.show_name = True
|
|
||||||
set_collection(drawcam, 'manip_cams')
|
set_collection(drawcam, 'manip_cams')
|
||||||
|
|
||||||
if dcam_name == 'draw_cam':
|
if dcam_name == 'draw_cam':
|
||||||
drawcam.parent = maincam
|
drawcam.parent = maincam
|
||||||
if created:#set to main at creation time
|
if created: # set to main at creation time
|
||||||
drawcam.matrix_world = maincam.matrix_world
|
drawcam.matrix_world = maincam.matrix_world
|
||||||
drawcam.lock_location = (True,True,True)
|
drawcam.lock_location = (True,True,True)
|
||||||
|
# drawcam.hide_viewport = True
|
||||||
|
context.scene.gptoolprops.drawcam_passepartout = maincam.data.show_passepartout
|
||||||
|
maincam.data.show_passepartout = False
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if created:
|
if created:
|
||||||
@@ -226,7 +230,7 @@ class GPTB_OT_draw_cam(bpy.types.Operator):
|
|||||||
drawcam.hide_viewport = False
|
drawcam.hide_viewport = False
|
||||||
maincam.hide_viewport = True
|
maincam.hide_viewport = True
|
||||||
|
|
||||||
if created and drawcam.name == 'obj_cam':#Go in camera view
|
if created and drawcam.name == 'obj_cam': # Go in camera view
|
||||||
context.region_data.view_perspective = 'CAMERA'
|
context.region_data.view_perspective = 'CAMERA'
|
||||||
# ## make active
|
# ## make active
|
||||||
# bpy.context.view_layer.objects.active = ob
|
# bpy.context.view_layer.objects.active = ob
|
||||||
@@ -449,6 +453,38 @@ class GPTB_OT_overlay_presets(bpy.types.Operator):
|
|||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
class GPTB_OT_clear_active_frame(bpy.types.Operator):
|
||||||
|
bl_idname = "gp.clear_active_frame"
|
||||||
|
bl_label = "Clear Active Frame"
|
||||||
|
bl_description = "Delete all strokes in active frames"
|
||||||
|
bl_options = {"REGISTER"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.object and context.object.type == 'GPENCIL'
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
obj = context.object
|
||||||
|
l = obj.data.layers.active
|
||||||
|
if not l:
|
||||||
|
self.report({'ERROR'}, 'No layers')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
f = l.active_frame
|
||||||
|
if not f:
|
||||||
|
self.report({'ERROR'}, 'No active frame')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
ct = len(f.strokes)
|
||||||
|
if not ct:
|
||||||
|
self.report({'ERROR'}, 'Active frame already empty')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
for s in reversed(f.strokes):
|
||||||
|
f.strokes.remove(s)
|
||||||
|
self.report({'INFO'}, f'Cleared active frame ({ct} strokes removed)')
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
GPTB_OT_copy_text,
|
GPTB_OT_copy_text,
|
||||||
GPTB_OT_flipx_view,
|
GPTB_OT_flipx_view,
|
||||||
@@ -458,6 +494,7 @@ GPTB_OT_set_view_as_cam,
|
|||||||
GPTB_OT_reset_cam_rot,
|
GPTB_OT_reset_cam_rot,
|
||||||
GPTB_OT_toggle_mute_animation,
|
GPTB_OT_toggle_mute_animation,
|
||||||
GPTB_OT_list_disabled_anims,
|
GPTB_OT_list_disabled_anims,
|
||||||
|
GPTB_OT_clear_active_frame,
|
||||||
)
|
)
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
import bpy
|
||||||
|
from bpy.types import Operator
|
||||||
|
|
||||||
|
|
||||||
|
def get_layer_list(self, context):
|
||||||
|
'''return (identifier, name, description) of enum content'''
|
||||||
|
return [(l.info, l.info, '') for l in context.object.data.layers if l != context.object.data.layers.active]
|
||||||
|
# try:
|
||||||
|
# except:
|
||||||
|
# return [("", "", "")]
|
||||||
|
# return [(i, basename(i), "") for i in blends]
|
||||||
|
# return [(i.path, basename(i.path), "") for i in self.blends]
|
||||||
|
|
||||||
|
class GPTB_OT_duplicate_send_to_layer(Operator) :
|
||||||
|
bl_idname = "gp.duplicate_send_to_layer"
|
||||||
|
bl_label = 'Duplicate and send to 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",
|
||||||
|
items=get_layer_list
|
||||||
|
)
|
||||||
|
|
||||||
|
delete_source : bpy.props.BoolProperty(default=False, options={'SKIP_SAVE'})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.object and context.object.type == 'GPENCIL' and context.space_data.ui_mode == 'GPENCIL'
|
||||||
|
|
||||||
|
# history : bpy.props.StringProperty(default='', options={'SKIP_SAVE'}) # need to have a variable to store (to get it in self)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
target_layer = self.layers_enum
|
||||||
|
if not target_layer:
|
||||||
|
self.report({'WARNING'}, 'Target layer not specified')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
gpl = context.object.data.layers
|
||||||
|
target_layer = gpl.get(target_layer)
|
||||||
|
|
||||||
|
act_layer = gpl.active
|
||||||
|
selected_frames = [f for f in act_layer.frames if f.select]
|
||||||
|
|
||||||
|
act_frame_num = [f.frame_number for f in act_layer.frames if f.select]
|
||||||
|
|
||||||
|
to_replace = [f for f in target_layer.frames if f.frame_number in act_frame_num]
|
||||||
|
|
||||||
|
replaced = len(to_replace)
|
||||||
|
|
||||||
|
## remove overlapping frames
|
||||||
|
for f in reversed(to_replace):
|
||||||
|
target_layer.frames.remove(f)
|
||||||
|
|
||||||
|
## copy original frames
|
||||||
|
for f in selected_frames:
|
||||||
|
target_layer.frames.copy(f)
|
||||||
|
sent = len(selected_frames)
|
||||||
|
|
||||||
|
## delete original frames as an option
|
||||||
|
if self.delete_source:
|
||||||
|
for f in reversed(selected_frames):
|
||||||
|
act_layer.frames.remove(f)
|
||||||
|
|
||||||
|
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()
|
||||||
|
bpy.ops.object.mode_set(mode=mod)
|
||||||
|
|
||||||
|
self.report({'INFO'}, mess)
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
gp = context.object.data
|
||||||
|
if not len(gp.layers):
|
||||||
|
self.report({'WARNING'}, 'No layers on current GP object')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
active = gp.layers.active
|
||||||
|
|
||||||
|
if not active:
|
||||||
|
self.report({'WARNING'}, 'No active layer to take keys from')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
self.selected_frames = [f for f in active.frames if f.select]
|
||||||
|
|
||||||
|
if not self.selected_frames:
|
||||||
|
self.report({'WARNING'}, 'No selected keys in active layer')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
wm = context.window_manager
|
||||||
|
wm.invoke_search_popup(self) # can't specify size... width=500, height=600
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
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 = "Screen", space_type = "EMPTY")
|
||||||
|
km = addon.keymaps.new(name = "Dopesheet", space_type = "DOPESHEET_EDITOR")
|
||||||
|
kmi = km.keymap_items.new('gp.duplicate_send_to_layer', type='D', value="PRESS", alt=False, ctrl=True, shift=True, any=False)
|
||||||
|
addon_keymaps.append((km,kmi))
|
||||||
|
kmi = km.keymap_items.new('gp.duplicate_send_to_layer', type='X', value="PRESS", alt=False, ctrl=True, shift=True, any=False)
|
||||||
|
kmi.properties.delete_source = True
|
||||||
|
addon_keymaps.append((km,kmi))
|
||||||
|
|
||||||
|
|
||||||
|
def unregister_keymaps():
|
||||||
|
for km, kmi in addon_keymaps:
|
||||||
|
km.keymap_items.remove(kmi)
|
||||||
|
addon_keymaps.clear()
|
||||||
|
|
||||||
|
|
||||||
|
classes = (
|
||||||
|
GPTB_OT_duplicate_send_to_layer,
|
||||||
|
)
|
||||||
|
|
||||||
|
def register():
|
||||||
|
if not bpy.app.background:
|
||||||
|
for cls in classes:
|
||||||
|
bpy.utils.register_class(cls)
|
||||||
|
register_keymaps()
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
if not bpy.app.background:
|
||||||
|
unregister_keymaps()
|
||||||
|
for cls in reversed(classes):
|
||||||
|
bpy.utils.unregister_class(cls)
|
||||||
+36
-9
@@ -1,5 +1,6 @@
|
|||||||
import bpy
|
import bpy
|
||||||
from .utils import get_addon_prefs
|
from .utils import get_addon_prefs
|
||||||
|
from bpy.props import BoolProperty ,EnumProperty ,StringProperty
|
||||||
|
|
||||||
class GPTB_OT_jump_gp_keyframe(bpy.types.Operator):
|
class GPTB_OT_jump_gp_keyframe(bpy.types.Operator):
|
||||||
bl_idname = "screen.gp_keyframe_jump"
|
bl_idname = "screen.gp_keyframe_jump"
|
||||||
@@ -11,17 +12,31 @@ class GPTB_OT_jump_gp_keyframe(bpy.types.Operator):
|
|||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return context.object and context.object.type == 'GPENCIL'
|
return context.object and context.object.type == 'GPENCIL'
|
||||||
|
|
||||||
next : bpy.props.BoolProperty(
|
next : BoolProperty(
|
||||||
name="Next GP keyframe", description="Go to next active GP keyframe", default=True)
|
name="Next GP keyframe", description="Go to next active GP keyframe",
|
||||||
|
default=True, options={'HIDDEN', 'SKIP_SAVE'})
|
||||||
|
|
||||||
target : bpy.props.EnumProperty(
|
target : EnumProperty(
|
||||||
name="Target layer", description="Choose wich layer to evaluate for keyframe change", default='ACTIVE',# options={'ANIMATABLE'}, update=None, get=None, set=None,
|
name="Target layer", description="Choose wich layer to evaluate for keyframe change",
|
||||||
|
default='ACTIVE', options={'HIDDEN', 'SKIP_SAVE'},
|
||||||
items=(
|
items=(
|
||||||
('ACTIVE', 'Active and selected', 'jump in keyframes of active and other selected layers ', 0),
|
('ACTIVE', 'Active and selected', 'jump in keyframes of active and other selected layers ', 0),
|
||||||
('VISIBLE', 'Visibles layers', 'jump in keyframes of visibles layers', 1),
|
('VISIBLE', 'Visibles layers', 'jump in keyframes of visibles layers', 1),
|
||||||
('ACCESSIBLE', 'Visible and unlocked layers', 'jump in keyframe of all layers', 2),
|
('ACCESSIBLE', 'Visible and unlocked layers', 'jump in keyframe of all layers', 2),
|
||||||
))
|
))
|
||||||
#(key, label, descr, id[, icon])
|
|
||||||
|
keyframe_type : EnumProperty(
|
||||||
|
name="Keyframe Filter", description="Jump to choosen keyframe type, else use the UI jump filter",
|
||||||
|
default='NONE', options={'HIDDEN', 'SKIP_SAVE'},
|
||||||
|
items=(
|
||||||
|
('NONE', 'Use UI Filter', '', 0), # 'KEYFRAME'
|
||||||
|
('ALL', 'All', '', 1),
|
||||||
|
('KEYFRAME', 'Keyframe', '', 'KEYTYPE_KEYFRAME_VEC', 2),
|
||||||
|
('BREAKDOWN', 'Breakdown', '', 'KEYTYPE_BREAKDOWN_VEC', 3),
|
||||||
|
('MOVING_HOLD', 'Moving Hold', '', 'KEYTYPE_MOVING_HOLD_VEC', 4),
|
||||||
|
('EXTREME', 'Extreme', '', 'KEYTYPE_EXTREME_VEC', 5),
|
||||||
|
('JITTER', 'Jitter', '', 'KEYTYPE_JITTER_VEC', 6),
|
||||||
|
))
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
if not context.object.data.layers.active:
|
if not context.object.data.layers.active:
|
||||||
@@ -39,6 +54,11 @@ class GPTB_OT_jump_gp_keyframe(bpy.types.Operator):
|
|||||||
elif self.target == 'ACCESSIBLE':
|
elif self.target == 'ACCESSIBLE':
|
||||||
gpl = [l for l in context.object.data.layers if not l.hide and not l.lock]
|
gpl = [l for l in context.object.data.layers if not l.hide and not l.lock]
|
||||||
|
|
||||||
|
if self.keyframe_type != 'NONE':
|
||||||
|
# use shortcut choice override
|
||||||
|
kftype = self.keyframe_type
|
||||||
|
else:
|
||||||
|
kftype = context.scene.gptoolprops.keyframe_type
|
||||||
|
|
||||||
current = context.scene.frame_current
|
current = context.scene.frame_current
|
||||||
p = n = None
|
p = n = None
|
||||||
@@ -47,6 +67,10 @@ class GPTB_OT_jump_gp_keyframe(bpy.types.Operator):
|
|||||||
maxs = []
|
maxs = []
|
||||||
for l in gpl:
|
for l in gpl:
|
||||||
for f in l.frames:
|
for f in l.frames:
|
||||||
|
# keyframe type filter
|
||||||
|
if kftype != 'ALL' and f.keyframe_type != kftype:
|
||||||
|
continue
|
||||||
|
|
||||||
if f.frame_number < current:
|
if f.frame_number < current:
|
||||||
p = f.frame_number
|
p = f.frame_number
|
||||||
if f.frame_number > current:
|
if f.frame_number > current:
|
||||||
@@ -64,10 +88,13 @@ class GPTB_OT_jump_gp_keyframe(bpy.types.Operator):
|
|||||||
if maxs:
|
if maxs:
|
||||||
n = min(maxs)
|
n = min(maxs)
|
||||||
|
|
||||||
|
## Double the frame set to avoid refresh problem (had one in 2.91.2)
|
||||||
if self.next and n is not None:
|
if self.next and n is not None:
|
||||||
context.scene.frame_set(n)
|
context.scene.frame_set(n)
|
||||||
|
context.scene.frame_current = n
|
||||||
elif not self.next and p is not None:
|
elif not self.next and p is not None:
|
||||||
context.scene.frame_set(p)
|
context.scene.frame_set(p)
|
||||||
|
context.scene.frame_current = p
|
||||||
else:
|
else:
|
||||||
self.report({'INFO'}, 'No keyframe in this direction')
|
self.report({'INFO'}, 'No keyframe in this direction')
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
@@ -80,10 +107,10 @@ class KFJ_OT_rebinder(bpy.types.Operator):
|
|||||||
bl_label = "rebind keyframe jump shortcut"
|
bl_label = "rebind keyframe jump shortcut"
|
||||||
bl_options = {'REGISTER', 'INTERNAL'}
|
bl_options = {'REGISTER', 'INTERNAL'}
|
||||||
|
|
||||||
s_keycode: bpy.props.StringProperty()
|
s_keycode: StringProperty()
|
||||||
s_ctrl: bpy.props.StringProperty()
|
s_ctrl: StringProperty()
|
||||||
s_shift: bpy.props.StringProperty()
|
s_shift: StringProperty()
|
||||||
s_alt: bpy.props.StringProperty()
|
s_alt: StringProperty()
|
||||||
|
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
|
|||||||
+180
@@ -282,12 +282,192 @@ class GPTB_OT_copy_active_to_selected_palette(bpy.types.Operator):
|
|||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class GPTB_OT_clean_material_stack(bpy.types.Operator):
|
||||||
|
bl_idname = "gp.clean_material_stack"
|
||||||
|
bl_label = "Clean Material Stack"
|
||||||
|
bl_description = "Clean materials duplication in active GP object stack"
|
||||||
|
bl_options = {"REGISTER", "UNDO"} # , "INTERNAL"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
use_clean_mats : bpy.props.BoolProperty(name="Remove Duplication",
|
||||||
|
description="All duplicated material (with suffix .001, .002 ...) will be replaced by the material with clean name (if found in scene)" ,
|
||||||
|
default=True)
|
||||||
|
|
||||||
|
skip_different_materials : bpy.props.BoolProperty(name="Skip Different Material",
|
||||||
|
description="Will not touch duplication if color settings are different (and show infos about skipped materials)",
|
||||||
|
default=True)
|
||||||
|
|
||||||
|
use_fuses_mats : bpy.props.BoolProperty(name="Fuse Materials Slots",
|
||||||
|
description="Fuse materials slots when multiple uses same materials",
|
||||||
|
default=True)
|
||||||
|
|
||||||
|
remove_empty_slots : bpy.props.BoolProperty(name="Remove Empty Slots",
|
||||||
|
description="Remove slots that haven't any material attached ",
|
||||||
|
default=True)
|
||||||
|
|
||||||
|
# skip_binded_empty_slots : bpy.props.BoolProperty(name="Skip Binded Empty slots",
|
||||||
|
# description="Remove only empty slots that haven't any material attached",
|
||||||
|
# default=False)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.object and context.object.type == 'GPENCIL'
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
self.ob = context.object
|
||||||
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
|
||||||
|
box = layout.box()
|
||||||
|
box.prop(self, 'use_clean_mats')
|
||||||
|
if self.use_clean_mats:
|
||||||
|
box.prop(self, 'skip_different_materials')
|
||||||
|
|
||||||
|
# layout.separator()
|
||||||
|
box = layout.box()
|
||||||
|
box.prop(self, 'use_fuses_mats')
|
||||||
|
box = layout.box()
|
||||||
|
box.prop(self, 'remove_empty_slots')
|
||||||
|
# if self.remove_empty_slots:
|
||||||
|
# box.prop(self, 'skip_binded_empty_slots')
|
||||||
|
|
||||||
|
|
||||||
|
def different_gp_mat(self, mata, matb):
|
||||||
|
a = mata.grease_pencil
|
||||||
|
b = matb.grease_pencil
|
||||||
|
if a.color[:] != b.color[:]:
|
||||||
|
return f'! {self.ob.name}: {mata.name} and {matb.name} stroke color is different'
|
||||||
|
if a.fill_color[:] != b.fill_color[:]:
|
||||||
|
return f'! {self.ob.name}: {mata.name} and {matb.name} fill_color color is different'
|
||||||
|
if a.show_stroke != b.show_stroke:
|
||||||
|
return f'! {self.ob.name}: {mata.name} and {matb.name} stroke has different state'
|
||||||
|
if a.show_fill != b.show_fill:
|
||||||
|
return f'! {self.ob.name}: {mata.name} and {matb.name} fill has different state'
|
||||||
|
|
||||||
|
## Clean dups
|
||||||
|
|
||||||
|
def clean_mats_duplication(self, ob):
|
||||||
|
import re
|
||||||
|
diff_ct = 0
|
||||||
|
todel = []
|
||||||
|
if ob.type != 'GPENCIL':
|
||||||
|
return
|
||||||
|
if not hasattr(ob, 'material_slots'):
|
||||||
|
return
|
||||||
|
for i, ms in enumerate(ob.material_slots):
|
||||||
|
mat = ms.material
|
||||||
|
if not mat:
|
||||||
|
continue
|
||||||
|
match = re.search(r'(.*)\.\d{3}$', mat.name)
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
basemat = bpy.data.materials.get(match.group(1))
|
||||||
|
if not basemat:
|
||||||
|
continue
|
||||||
|
diff = self.different_gp_mat(mat, basemat)
|
||||||
|
if diff:
|
||||||
|
print(diff)
|
||||||
|
diff_ct += 1
|
||||||
|
if self.skip_different_materials:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if mat not in todel:
|
||||||
|
todel.append(mat)
|
||||||
|
ms.material = basemat
|
||||||
|
print(f'{ob.name} : slot {i} >> replaced {mat.name}')
|
||||||
|
mat.use_fake_user = False
|
||||||
|
|
||||||
|
### delete (only when using on all objects loop, else can delete another objects mat...)
|
||||||
|
## for m in reversed(todel):
|
||||||
|
## bpy.data.materials.remove(m)
|
||||||
|
|
||||||
|
if diff_ct:
|
||||||
|
return('INFO', f'{diff_ct} mat skipped >> same name but different color settings!')
|
||||||
|
|
||||||
|
## fuse
|
||||||
|
|
||||||
|
def fuse_object_mats(self, ob):
|
||||||
|
for i in range(len(ob.material_slots))[::-1]:
|
||||||
|
ms = ob.material_slots[i]
|
||||||
|
mat = ms.material
|
||||||
|
# if not mat:
|
||||||
|
# # remove empty slots
|
||||||
|
# if self.remove_empty_slots:
|
||||||
|
# ob.active_material_index = i
|
||||||
|
# bpy.ops.object.material_slot_remove()
|
||||||
|
# continue
|
||||||
|
|
||||||
|
# update mat list
|
||||||
|
mlist = [ms.material for ms in ob.material_slots if ms.material]
|
||||||
|
if mlist.count(mat) > 1:
|
||||||
|
# get first material in list
|
||||||
|
new_mat_id = mlist.index(mat)
|
||||||
|
|
||||||
|
# iterate in all strokes and replace with new_mat_id
|
||||||
|
for l in ob.data.layers:
|
||||||
|
for f in l.frames:
|
||||||
|
for s in f.strokes:
|
||||||
|
if s.material_index == i:
|
||||||
|
s.material_index = new_mat_id
|
||||||
|
|
||||||
|
# delete slot (or add to the remove_slot list
|
||||||
|
ob.active_material_index = i
|
||||||
|
bpy.ops.object.material_slot_remove()
|
||||||
|
|
||||||
|
def delete_empty_material_slots(self, ob):
|
||||||
|
for i in range(len(ob.material_slots))[::-1]:
|
||||||
|
ms = ob.material_slots[i]
|
||||||
|
mat = ms.material
|
||||||
|
if not mat:
|
||||||
|
# is_binded=False
|
||||||
|
# if self.skip_binded_empty_slots:
|
||||||
|
# for l in ob.data.layers:
|
||||||
|
# for f in l.frames:
|
||||||
|
# for s in f.strokes:
|
||||||
|
# if s.material_index == i:
|
||||||
|
# is_binded = True
|
||||||
|
# break
|
||||||
|
# if is_binded:
|
||||||
|
# continue
|
||||||
|
|
||||||
|
ob.active_material_index = i
|
||||||
|
bpy.ops.object.material_slot_remove()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
ob = context.object
|
||||||
|
info = None
|
||||||
|
|
||||||
|
if not self.use_clean_mats and not self.use_fuses_mats and not self.remove_empty_slots:
|
||||||
|
self.report({'ERROR'}, 'At least one operation should be selected')
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
if self.use_clean_mats:
|
||||||
|
info = self.clean_mats_duplication(ob)
|
||||||
|
if self.use_fuses_mats:
|
||||||
|
self.fuse_object_mats(ob)
|
||||||
|
if self.remove_empty_slots:
|
||||||
|
self.delete_empty_material_slots(ob)
|
||||||
|
|
||||||
|
if info:
|
||||||
|
self.report({info[0]}, info[1])
|
||||||
|
# else:
|
||||||
|
# self.report({'WARNING'}, '')
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
GPTB_OT_load_palette,
|
GPTB_OT_load_palette,
|
||||||
GPTB_OT_save_palette,
|
GPTB_OT_save_palette,
|
||||||
GPTB_OT_load_default_palette,
|
GPTB_OT_load_default_palette,
|
||||||
GPTB_OT_load_blend_palette,
|
GPTB_OT_load_blend_palette,
|
||||||
GPTB_OT_copy_active_to_selected_palette,
|
GPTB_OT_copy_active_to_selected_palette,
|
||||||
|
GPTB_OT_clean_material_stack,
|
||||||
)
|
)
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
|
|||||||
+382
@@ -0,0 +1,382 @@
|
|||||||
|
import bpy
|
||||||
|
import mathutils
|
||||||
|
from mathutils import Matrix, Vector
|
||||||
|
from math import pi
|
||||||
|
import numpy as np
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
|
||||||
|
def get_scale_matrix(scale):
|
||||||
|
# recreate a neutral mat scale
|
||||||
|
matscale_x = Matrix.Scale(scale[0], 4,(1,0,0))
|
||||||
|
matscale_y = Matrix.Scale(scale[1], 4,(0,1,0))
|
||||||
|
matscale_z = Matrix.Scale(scale[2], 4,(0,0,1))
|
||||||
|
matscale = matscale_x @ matscale_y @ matscale_z
|
||||||
|
return matscale
|
||||||
|
|
||||||
|
def batch_reproject(obj, proj_type='VIEW', all_strokes=True, restore_frame=False):
|
||||||
|
'''Reproject - ops method
|
||||||
|
:all_stroke: affect hided, locked layers
|
||||||
|
'''
|
||||||
|
|
||||||
|
if restore_frame:
|
||||||
|
oframe = bpy.context.scene.frame_current
|
||||||
|
omode = bpy.context.mode
|
||||||
|
|
||||||
|
# FIXME : if all_stroke is False, might be better to still store>set>restore "lock_frame"
|
||||||
|
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 l in obj.data.layers:
|
||||||
|
for f in l.frames:
|
||||||
|
if not len(f.strokes):
|
||||||
|
continue
|
||||||
|
bpy.context.scene.frame_set(f.frame_number)
|
||||||
|
|
||||||
|
# switch to edit to reproject through ops
|
||||||
|
bpy.ops.gpencil.select_all(action='SELECT')
|
||||||
|
bpy.ops.gpencil.reproject(type=proj_type) # default is VIEW
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def align_global(reproject=True, ref=None, all_strokes=True):
|
||||||
|
|
||||||
|
if not ref:
|
||||||
|
ref = bpy.context.scene.camera
|
||||||
|
|
||||||
|
o = bpy.context.object
|
||||||
|
# if o.matrix_basis != o.matrix_world and not o.parent:
|
||||||
|
|
||||||
|
ref = bpy.context.scene.camera
|
||||||
|
ref_mat = ref.matrix_world
|
||||||
|
ref_loc, ref_rot, ref_scale = ref_mat.decompose()
|
||||||
|
|
||||||
|
if o.parent:
|
||||||
|
mat = o.matrix_world
|
||||||
|
else:
|
||||||
|
mat = o.matrix_basis
|
||||||
|
|
||||||
|
o_loc, o_rot, o_scale = mat.decompose()
|
||||||
|
|
||||||
|
mat_90 = Matrix.Rotation(-pi/2, 4, 'X')
|
||||||
|
|
||||||
|
loc_mat = Matrix.Translation(o_loc)
|
||||||
|
rot_mat = ref_rot.to_matrix().to_4x4() @ mat_90
|
||||||
|
scale_mat = get_scale_matrix(o_scale)
|
||||||
|
|
||||||
|
new_mat = loc_mat @ rot_mat @ scale_mat
|
||||||
|
|
||||||
|
# world_coords = []
|
||||||
|
for l in o.data.layers:
|
||||||
|
for f in l.frames:
|
||||||
|
for s in f.strokes:
|
||||||
|
## foreach
|
||||||
|
coords = [p.co @ mat.inverted() @ new_mat for p in s.points]
|
||||||
|
# print('coords: ', coords)
|
||||||
|
# print([co for v in coords for co in v])
|
||||||
|
s.points.foreach_set('co', [co for v in coords for co in v])
|
||||||
|
# s.points.update() # seem to works # but adding/deleting a point is "safer"
|
||||||
|
## force update
|
||||||
|
s.points.add(1)
|
||||||
|
s.points.pop()
|
||||||
|
|
||||||
|
# for p in s.points:
|
||||||
|
## GOOD :
|
||||||
|
# world_co = mat @ p.co
|
||||||
|
# p.co = new_mat.inverted() @ world_co
|
||||||
|
|
||||||
|
## GOOD :
|
||||||
|
# p.co = p.co @ mat.inverted() @ new_mat
|
||||||
|
|
||||||
|
if o.parent:
|
||||||
|
o.matrix_world = new_mat
|
||||||
|
else:
|
||||||
|
o.matrix_basis = new_mat
|
||||||
|
|
||||||
|
if reproject:
|
||||||
|
batch_reproject(o, proj_type='FRONT', all_strokes=all_strokes)
|
||||||
|
|
||||||
|
|
||||||
|
def align_all_frames(reproject=True, ref=None, all_strokes=True):
|
||||||
|
|
||||||
|
print('aligning all frames...')
|
||||||
|
|
||||||
|
o = bpy.context.object
|
||||||
|
if not ref:
|
||||||
|
ref = bpy.context.scene.camera
|
||||||
|
|
||||||
|
# get all rot
|
||||||
|
chanel = 'rotation_quaternion' if o.rotation_mode == 'QUATERNION' else 'rotation_euler'
|
||||||
|
|
||||||
|
## double list keys
|
||||||
|
rot_keys = [k.co.x for fcu in o.animation_data.action.fcurves for k in fcu.keyframe_points if fcu.data_path == chanel]
|
||||||
|
|
||||||
|
## normal iter
|
||||||
|
# for fcu in o.animation_data.action.fcurves:
|
||||||
|
# if fcu.data_path != chanel :
|
||||||
|
# continue
|
||||||
|
# for k in fcu.keyframe_points():
|
||||||
|
# rot_keys.append(k.co.x)
|
||||||
|
|
||||||
|
rot_keys = list(set(rot_keys))
|
||||||
|
|
||||||
|
# TODO # TOTHINK
|
||||||
|
# for now the rotation of the object is adjusted at every frame....
|
||||||
|
# might be better to check camera rotation of the current frame only (stored as copy).
|
||||||
|
# else the object rotate following the cameraview vector (not constant)...
|
||||||
|
|
||||||
|
mat_90 = Matrix.Rotation(-pi/2, 4, 'X')
|
||||||
|
|
||||||
|
for l in o.data.layers:
|
||||||
|
for f in l.frames:
|
||||||
|
# set the frame to dedicated
|
||||||
|
bpy.context.scene.frame_set(f.frame_number)
|
||||||
|
|
||||||
|
ref_mat = ref.matrix_world
|
||||||
|
ref_loc, ref_rot, ref_scale = ref_mat.decompose()
|
||||||
|
|
||||||
|
if o.parent:
|
||||||
|
mat = o.matrix_world
|
||||||
|
else:
|
||||||
|
mat = o.matrix_basis
|
||||||
|
|
||||||
|
o_loc, o_rot, o_scale = mat.decompose()
|
||||||
|
loc_mat = Matrix.Translation(o_loc)
|
||||||
|
rot_mat = ref_rot.to_matrix().to_4x4() @ mat_90
|
||||||
|
scale_mat = get_scale_matrix(o_scale)
|
||||||
|
new_mat = loc_mat @ rot_mat @ scale_mat
|
||||||
|
|
||||||
|
for s in f.strokes:
|
||||||
|
## foreach
|
||||||
|
coords = [p.co @ mat.inverted() @ new_mat for p in s.points]
|
||||||
|
# print('coords: ', coords)
|
||||||
|
# print([co for v in coords for co in v])
|
||||||
|
s.points.foreach_set('co', [co for v in coords for co in v])
|
||||||
|
# s.points.update() # seem to works
|
||||||
|
## force update
|
||||||
|
s.points.add(1)
|
||||||
|
s.points.pop()
|
||||||
|
|
||||||
|
for fnum in rot_keys:
|
||||||
|
bpy.context.scene.frame_set(fnum)
|
||||||
|
#/update calculation block
|
||||||
|
ref_mat = ref.matrix_world
|
||||||
|
ref_loc, ref_rot, ref_scale = ref_mat.decompose()
|
||||||
|
|
||||||
|
if o.parent:
|
||||||
|
mat = o.matrix_world
|
||||||
|
else:
|
||||||
|
mat = o.matrix_basis
|
||||||
|
|
||||||
|
o_loc, o_rot, o_scale = mat.decompose()
|
||||||
|
loc_mat = Matrix.Translation(o_loc)
|
||||||
|
rot_mat = ref_rot.to_matrix().to_4x4() @ mat_90
|
||||||
|
scale_mat = get_scale_matrix(o_scale)
|
||||||
|
new_mat = loc_mat @ rot_mat @ scale_mat
|
||||||
|
# update calculation block/
|
||||||
|
|
||||||
|
if o.parent:
|
||||||
|
o.matrix_world = new_mat
|
||||||
|
else:
|
||||||
|
o.matrix_basis = new_mat
|
||||||
|
|
||||||
|
o.keyframe_insert(chanel, index=-1, frame=bpy.context.scene.frame_current, options={'INSERTKEY_AVAILABLE'})
|
||||||
|
|
||||||
|
|
||||||
|
if reproject:
|
||||||
|
batch_reproject(o, proj_type='FRONT', all_strokes=all_strokes)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class GPTB_OT_realign(bpy.types.Operator):
|
||||||
|
bl_idname = "gp.realign"
|
||||||
|
bl_label = "Realign GP"
|
||||||
|
bl_description = "Realign the grease pencil front axis with active camera"
|
||||||
|
bl_options = {"REGISTER"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.object and context.object.type == 'GPENCIL'
|
||||||
|
|
||||||
|
reproject : bpy.props.BoolProperty(
|
||||||
|
name='Reproject', default=True,
|
||||||
|
description='Reproject stroke on the new alignment')
|
||||||
|
|
||||||
|
all_strokes : bpy.props.BoolProperty(
|
||||||
|
name='All Strokes', default=True,
|
||||||
|
description='Hided and locked layer will also be reprojected')
|
||||||
|
|
||||||
|
set_draw_axis : bpy.props.BoolProperty(
|
||||||
|
name='Set draw axis to Front', default=True,
|
||||||
|
description='Set the gpencil draw plane axis to Front')
|
||||||
|
## add option to bake strokes if rotation anim is not constant ? might generate too many Keyframes
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
if context.object.data.use_multiedit:
|
||||||
|
self.report({'ERROR'}, 'Does not work in Multi-edit')
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
self.alert = ''
|
||||||
|
o = context.object
|
||||||
|
if o.animation_data and o.animation_data.action:
|
||||||
|
act = o.animation_data.action
|
||||||
|
for chan in ('rotation_euler', 'rotation_quaternion'):
|
||||||
|
if act.fcurves.find(chan):
|
||||||
|
self.alert = 'Animated Rotation (CONSTANT interpolation)'
|
||||||
|
interpos = [p for fcu in act.fcurves if fcu.data_path == chan for p in fcu.keyframe_points if p.interpolation != 'CONSTANT']
|
||||||
|
if interpos:
|
||||||
|
self.alert = f'Animated Rotation ! ({len(interpos)} key not constant)'
|
||||||
|
break
|
||||||
|
|
||||||
|
return context.window_manager.invoke_props_dialog(self, width=450)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.label(text='Realign the GP object : Front axis facing active camera')
|
||||||
|
if self.alert:
|
||||||
|
layout.label(text=self.alert, icon='ERROR')
|
||||||
|
layout.label(text='(rotations key will be overwritten to face camera)')
|
||||||
|
|
||||||
|
# layout.separator()
|
||||||
|
box = layout.box()
|
||||||
|
box.prop(self, "reproject")
|
||||||
|
if self.reproject:
|
||||||
|
box.label(text='After Realigning, reproject each frames on front axis')
|
||||||
|
if not context.region_data.view_perspective == 'CAMERA':
|
||||||
|
box.label(text='Not in camera ! (reprojection is made from view)', icon='ERROR')
|
||||||
|
box.prop(self, "all_strokes")
|
||||||
|
if not self.all_strokes:
|
||||||
|
box.label(text='Only visible and unlocked layers will be reprojected', icon='INFO')
|
||||||
|
|
||||||
|
axis = context.scene.tool_settings.gpencil_sculpt.lock_axis
|
||||||
|
if axis != 'AXIS_Y':
|
||||||
|
orient = {
|
||||||
|
'VIEW' : ['View', 'RESTRICT_VIEW_ON'],
|
||||||
|
# 'AXIS_Y': ['front (X-Z)', 'AXIS_FRONT'], #
|
||||||
|
'AXIS_X': ['side (Y-Z)', 'AXIS_SIDE'],
|
||||||
|
'AXIS_Z': ['top (X-Y)', 'AXIS_TOP'],
|
||||||
|
'CURSOR': ['Cursor', 'PIVOT_CURSOR'],
|
||||||
|
}
|
||||||
|
|
||||||
|
box = layout.box()
|
||||||
|
box.label(text=f'Current drawing plane : {orient[axis][0]}', icon=orient[axis][1])
|
||||||
|
box.prop(self, "set_draw_axis")
|
||||||
|
|
||||||
|
|
||||||
|
def exit(self, context, frame):
|
||||||
|
context.scene.frame_current = frame
|
||||||
|
if context.scene.tool_settings.gpencil_sculpt.lock_axis != 'AXIS_Y' and self.set_draw_axis:
|
||||||
|
context.scene.tool_settings.gpencil_sculpt.lock_axis = 'AXIS_Y'
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
t0 = time()
|
||||||
|
oframe = context.scene.frame_current
|
||||||
|
|
||||||
|
o = bpy.context.object
|
||||||
|
if o.animation_data and o.animation_data.action:
|
||||||
|
if o.animation_data.action.fcurves.find('rotation_euler') or o.animation_data.action.fcurves.find('rotation_quaternion'):
|
||||||
|
align_all_frames(reproject=self.reproject)
|
||||||
|
print(f'\nAnim realign ({time()-t0:.2f}s)')
|
||||||
|
self.exit(context, oframe)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
align_global(reproject=self.reproject)
|
||||||
|
print(f'\nGlobal Realign ({time()-t0:.2f}s)')
|
||||||
|
self.exit(context, oframe)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class GPTB_OT_batch_reproject_all_frames(bpy.types.Operator):
|
||||||
|
bl_idname = "gp.batch_reproject_all_frames"
|
||||||
|
bl_label = "Reproject All Frames"
|
||||||
|
bl_description = "Reproject all frames of active object."
|
||||||
|
bl_options = {"REGISTER"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.object and context.object.type == 'GPENCIL'
|
||||||
|
|
||||||
|
all_strokes : bpy.props.BoolProperty(
|
||||||
|
name='All Strokes', default=True,
|
||||||
|
description='Hided and locked layer will also be reprojected')
|
||||||
|
|
||||||
|
type: bpy.props.EnumProperty(name='Type',
|
||||||
|
items=(('FRONT', "Front", ""),
|
||||||
|
('SIDE', "Side", ""),
|
||||||
|
('TOP', "Top", ""),
|
||||||
|
('VIEW', "View", ""),
|
||||||
|
('SURFACE', "Surface", ""),
|
||||||
|
('CURSOR', "Cursor", ""),
|
||||||
|
),
|
||||||
|
default='FRONT')
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
if context.object.data.use_multiedit:
|
||||||
|
self.report({'ERROR'}, 'Does not work in Multi-edit')
|
||||||
|
return {"CANCELLED"}
|
||||||
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
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.prop(self, "all_strokes")
|
||||||
|
layout.prop(self, "type")
|
||||||
|
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
t0 = time()
|
||||||
|
|
||||||
|
batch_reproject(context.object, proj_type=self.type, all_strokes=self.all_strokes, restore_frame=True)
|
||||||
|
|
||||||
|
self.report({'INFO'}, f'Reprojected in ({time()-t0:.2f}s)' )
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
### -- MENU ENTRY --
|
||||||
|
|
||||||
|
def reproject_clean_menu(self, context):
|
||||||
|
if context.mode == 'EDIT_GPENCIL':
|
||||||
|
self.layout.operator('gp.batch_reproject_all_frames', icon='SMALL_TRI_RIGHT_VEC') # KEYTYPE_JITTER_VEC
|
||||||
|
|
||||||
|
def reproject_context_menu(self, context):
|
||||||
|
if context.mode == 'EDIT_GPENCIL' and context.scene.tool_settings.gpencil_selectmode_edit == 'STROKE':
|
||||||
|
self.layout.operator('gp.batch_reproject_all_frames', icon='SMALL_TRI_RIGHT_VEC') # KEYTYPE_JITTER_VEC
|
||||||
|
|
||||||
|
classes = (
|
||||||
|
GPTB_OT_realign,
|
||||||
|
GPTB_OT_batch_reproject_all_frames,
|
||||||
|
)
|
||||||
|
|
||||||
|
def register():
|
||||||
|
for cl in classes:
|
||||||
|
bpy.utils.register_class(cl)
|
||||||
|
|
||||||
|
bpy.types.VIEW3D_MT_gpencil_edit_context_menu.append(reproject_context_menu)
|
||||||
|
bpy.types.GPENCIL_MT_cleanup.append(reproject_clean_menu)
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
bpy.types.GPENCIL_MT_cleanup.remove(reproject_clean_menu)
|
||||||
|
bpy.types.VIEW3D_MT_gpencil_edit_context_menu.remove(reproject_context_menu)
|
||||||
|
|
||||||
|
for cl in reversed(classes):
|
||||||
|
bpy.utils.unregister_class(cl)
|
||||||
@@ -4,9 +4,9 @@ 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://gitlab.com/autour-de-minuit/blender/gp_toolbox/-/archive/master/gp_toolbox-master.zip)**
|
||||||
|
|
||||||
<!-- ### [Demo Youtube]() -->
|
**[Demo video](https://www.youtube.com/watch?v=Htgao_uPWNs)**
|
||||||
|
|
||||||
**[Readme Doc in French (Documentation en Français et plus détaillée)](https://gitlab.com/autour-de-minuit/blender/gp_toolbox/-/blob/master/README_FR.md)**
|
**[Readme Doc in French (Documentation en Français et plus détaillée)](README_FR.md)**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ Note about palette : For now the importer is not working with linked palette as
|
|||||||
|
|
||||||
### Passive action
|
### Passive action
|
||||||
|
|
||||||
Add an "on save" Handler that trigger relative remap of all path.
|
Add an "on save" Handler that trigger relative remap of all path (can be disabled in addon prefs).
|
||||||
|
|
||||||
### function
|
### function
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ In material submenu you have mutliple new entry:
|
|||||||
|
|
||||||
Panel in sidebar : 3D view > sidebar 'N' > Gpencil
|
Panel in sidebar : 3D view > sidebar 'N' > Gpencil
|
||||||
|
|
||||||
|
<!--
|
||||||
## Todo:
|
## Todo:
|
||||||
|
|
||||||
- Allow to render resolution from cam name
|
- Allow to render resolution from cam name
|
||||||
@@ -105,251 +105,9 @@ Panel in sidebar : 3D view > sidebar 'N' > Gpencil
|
|||||||
|
|
||||||
- Move automatically view to match GP Front (depending on Gpencil view settings)
|
- Move automatically view to match GP Front (depending on Gpencil view settings)
|
||||||
|
|
||||||
- move GP keyframes selection and Object keyframe selection simultaneouly (Already Done by Tom Viguier at [Andarta](https://gitlab.com/andarta-pictures)
|
- move GP keyframes selection and Object keyframe selection simultaneouly (Already Done by Tom Viguier at [Andarta](https://gitlab.com/andarta-pictures) -->
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Changelog:
|
Consult [Changelog here](CHANGELOG.md)
|
||||||
|
|
||||||
1.0.3:
|
|
||||||
|
|
||||||
- feat: add "Append Materials To Selected" to material submenu. Append materials to other selected GP objects if there aren't there.
|
|
||||||
|
|
||||||
1.0.2:
|
|
||||||
|
|
||||||
- pref: Added option to disable always remap relative on save in addon-preference
|
|
||||||
|
|
||||||
1.0.1:
|
|
||||||
|
|
||||||
- fix: copy paste problems
|
|
||||||
- Get points uv_properties (used for brushed points)
|
|
||||||
- Trigger an update on each pasted strokes, recalculate badly drawn uv and fills (works in 2.93+)
|
|
||||||
|
|
||||||
1.0.0:
|
|
||||||
|
|
||||||
- Compatible with official grease pencil tools
|
|
||||||
- removed box deform and rotate canvas that existed in other
|
|
||||||
|
|
||||||
0.9.3:
|
|
||||||
|
|
||||||
- feat: keyframe jump keys are now auto-binded
|
|
||||||
- UI: added keyframe jump customisation in addon pref
|
|
||||||
- code: split keyframe jump in a separate file with his new key updater
|
|
||||||
|
|
||||||
0.9.2:
|
|
||||||
|
|
||||||
- doc: Correct download link (important, bugged the addon install) + update
|
|
||||||
- code: added tracker url
|
|
||||||
- updater: remove updater temp file, reset minimum version, turn off verbose mode
|
|
||||||
|
|
||||||
0.9.1:
|
|
||||||
|
|
||||||
- Public release
|
|
||||||
- prefs: added fps as part of project settings
|
|
||||||
- check file use pref fps value (previously used harcoded 24fps value)
|
|
||||||
- cleanup: Remove wip GMIC-bridge tools that need to be done separately (if needed)
|
|
||||||
- update: apply changes in integrated copy-paste from the last version of standalone addon
|
|
||||||
- doc: Added fully-detailed french readme
|
|
||||||
|
|
||||||
|
|
||||||
0.8.0:
|
|
||||||
|
|
||||||
- feat: Added background_rendering playblast, derivating from Tonton's playblaster
|
|
||||||
- stripped associated properties from properties.py and passed as wm props.
|
|
||||||
|
|
||||||
0.7.2:
|
|
||||||
|
|
||||||
- fix: Palette importer bug
|
|
||||||
|
|
||||||
0.7.0:
|
|
||||||
|
|
||||||
- feat: auto create empty frame on color layer
|
|
||||||
|
|
||||||
0.6.3:
|
|
||||||
|
|
||||||
- shortcut: added 1,2,3 to change sculpt mask mode (like native edit mode shortcut)
|
|
||||||
|
|
||||||
0.6.2:
|
|
||||||
|
|
||||||
- feat: colorisation, Option to change stop lines length
|
|
||||||
- Change behavior of `cursor_snap` ops when a non-GP object is selected to mode: `surface project`
|
|
||||||
- Minor refactor for submodule register
|
|
||||||
|
|
||||||
0.6.1:
|
|
||||||
|
|
||||||
- feat: render objects grouped, one anim render with all ticked object using manual output name
|
|
||||||
|
|
||||||
0.6.0:
|
|
||||||
|
|
||||||
- feat: Include GP clipoard's "In place" custom cut/copy/paste using OS clipboard
|
|
||||||
|
|
||||||
0.5.9:
|
|
||||||
|
|
||||||
- feat: render exporter
|
|
||||||
- Render a selection of GP object isolated from the rest
|
|
||||||
- added exclusions names for GP object listing
|
|
||||||
- setup settings and output according to a name
|
|
||||||
- open render folder
|
|
||||||
- check file: set onion skin keyframe filter to 'All_type' on all GP datablock
|
|
||||||
- check file: set scene resolution to settings in prefs (default 2048x1080)
|
|
||||||
|
|
||||||
0.5.8:
|
|
||||||
|
|
||||||
- feat: GP material append on active object from single blend file
|
|
||||||
|
|
||||||
0.5.7:
|
|
||||||
|
|
||||||
- Added warning message for cursor snapping
|
|
||||||
|
|
||||||
0.5.5 - 0.5.6:
|
|
||||||
|
|
||||||
- check file: added check for placement an projection mode for Gpencil.
|
|
||||||
- add a slider to change edit_lines_opacity globally for all GP data at once
|
|
||||||
- check file: auto-check additive drawing (to avoid empty frame with "only selected channel" in Dopesheet)
|
|
||||||
|
|
||||||
0.5.4:
|
|
||||||
|
|
||||||
- feat: anim manager in his own GP_toolbox submenu:
|
|
||||||
- button to list disabled anim (allow to quickly check state of the scene)
|
|
||||||
- disable/enable all fcurve in for GP object or other object separately to paint
|
|
||||||
- shift clic to target selection only
|
|
||||||
- check file: added disabled fcurved counter alert with detail in console
|
|
||||||
|
|
||||||
0.5.3:
|
|
||||||
|
|
||||||
- fix: broken obj cam (add custom prop on objcam to track wich was main cam)
|
|
||||||
- check file option: change select active tool (choice added in addon preferences)
|
|
||||||
|
|
||||||
0.5.2:
|
|
||||||
|
|
||||||
- Revert back obj_cam operator for following object (native lock view follow only translation)
|
|
||||||
- Changed method for canvas rotation to more robust rotate axis.
|
|
||||||
- Add operators on link checker to open containing folder/file of link
|
|
||||||
- Refactor: file checkers in their own file
|
|
||||||
|
|
||||||
0.5.1:
|
|
||||||
|
|
||||||
- fix: error when empty material slot on GP object.
|
|
||||||
- fix: cursor snap on GP canvas when GP is parented
|
|
||||||
- change: Deleted obj cam (and related set view) operator
|
|
||||||
- change: blacker note background for playblast (stamp_background)
|
|
||||||
- feat: Always playblast from main camera (if in draw_cam)
|
|
||||||
- feat: Handler added to Remap relative on save (pre)
|
|
||||||
- ops: Check for broken links with porposition to find missing files
|
|
||||||
- ops: Added basic hardcoded file checker
|
|
||||||
- Lock main cam
|
|
||||||
- set scene percentage at 100
|
|
||||||
- set show slider and sync range
|
|
||||||
- set fps to 24
|
|
||||||
|
|
||||||
0.4.6:
|
|
||||||
|
|
||||||
- feat: basic Palette manager with base material check and warning
|
|
||||||
|
|
||||||
0.4.5:
|
|
||||||
|
|
||||||
- open blender config folder from addon preference
|
|
||||||
- fix: obj cam parent on selected object
|
|
||||||
- added wip rotate canvas axis file. still not ready to replace current canvas rotate:
|
|
||||||
- freeview : bug when rotating free viewfrom cardianl views
|
|
||||||
- camview: potential bug when cam is parented with some specific angle (could not reproduce)
|
|
||||||
|
|
||||||
|
|
||||||
0.4.4:
|
|
||||||
|
|
||||||
- feat: added cursor follow handlers and UI toggle
|
|
||||||
|
|
||||||
0.4.3:
|
|
||||||
|
|
||||||
- change playblast out to 'images' and add playblast as name prefix
|
|
||||||
|
|
||||||
0.4.2:
|
|
||||||
|
|
||||||
- feat: GP canvas cursor snap wiht new `view3d.cusor_snap` operator
|
|
||||||
- fix: canvas rotate works with parented camera !
|
|
||||||
- wip: added an attmpt to replicate camera rotate modal with view matrix but no luck.
|
|
||||||
|
|
||||||
0.4.1:
|
|
||||||
|
|
||||||
- feat: Alternative cameras: parent to main cam (roll without affecting main cam), parent to active object at current view (follow current Grease pencil object)
|
|
||||||
|
|
||||||
0.4.0:
|
|
||||||
|
|
||||||
- Added a standalone working version of box_deform (stripped preferences keeping only best configuration with autoswap)
|
|
||||||
|
|
||||||
0.3.8:
|
|
||||||
|
|
||||||
- UI: expose onion skin in interface
|
|
||||||
- UI: expose autolock in interface
|
|
||||||
- UI : putted tint layers in a submenu
|
|
||||||
- code: refactor, pushed most of class register in their owner file
|
|
||||||
- tool: tool to rename current or all grease pencil datablock with different name than container object
|
|
||||||
|
|
||||||
0.3.7:
|
|
||||||
|
|
||||||
- UI: new interface with tabs for addon preferences
|
|
||||||
- UI: possible to disable color panel from preference (might be deleted if unusable)
|
|
||||||
- docs: change readme changelog format and correct doc
|
|
||||||
|
|
||||||
0.3.6:
|
|
||||||
|
|
||||||
- UI: Stoplines : add a button for quickly set stoplines visibility.
|
|
||||||
|
|
||||||
0.3.5:
|
|
||||||
|
|
||||||
- Fix : No more camera rotation undo when ctrl+Z on next stroke (canvas rotate push and undo)
|
|
||||||
- Fix: Enter key added to valid object-breakdown modal.
|
|
||||||
|
|
||||||
0.3.3:
|
|
||||||
|
|
||||||
- version 1 beta (stable) of line gap closing tools for better bucket fill tool performance with UI
|
|
||||||
|
|
||||||
0.3.3:
|
|
||||||
|
|
||||||
- version 1 beta of gmic colorize
|
|
||||||
- variant of `screen.gp_keyframe_jump` through keymap seetings
|
|
||||||
|
|
||||||
0.3.0:
|
|
||||||
|
|
||||||
- new homemade [breakdowner operator for object](https://blenderartists.org/t/pose-mode-animation-tools-for-object-mode/1221322) mode with auto keymap : Shift + E
|
|
||||||
- GP cutter shortcut ops to map with `wm.temp_cutter` (with "Any" as press mode) or `wm.sticky_cutter` (Modal sticky-key version)
|
|
||||||
|
|
||||||
0.2.3:
|
|
||||||
|
|
||||||
- add operator to `screen.gp_keyframe_jump`
|
|
||||||
- add shortcut to rotate canvas
|
|
||||||
- fix duplicate class
|
|
||||||
|
|
||||||
0.2.2:
|
|
||||||
|
|
||||||
- separated props resolution_percentage parameter
|
|
||||||
- playblast options for launching folder and opening folder
|
|
||||||
|
|
||||||
0.2.1:
|
|
||||||
|
|
||||||
- playblast feature
|
|
||||||
- Button to go zoom 100% or fit screen
|
|
||||||
- display scene resolution with res indicator
|
|
||||||
- Fix reference panel : works with video and display in a box layout.
|
|
||||||
- close pseudo-color panel by default (plan to move it to Gpencil tab)
|
|
||||||
|
|
||||||
0.2.0:
|
|
||||||
|
|
||||||
- UI: Toggle camera background images from Toolbox panel
|
|
||||||
- UI: quick access to passepartout
|
|
||||||
- Feature: option to use namespace for pseudo color
|
|
||||||
|
|
||||||
0.1.5:
|
|
||||||
|
|
||||||
- added CGC-auto-updater
|
|
||||||
|
|
||||||
0.1.3:
|
|
||||||
|
|
||||||
- flip cam x
|
|
||||||
- inital stage of overlay toggle (need pref/multiple pref)
|
|
||||||
|
|
||||||
0.1.2:
|
|
||||||
|
|
||||||
- subpanel of GP data (instead of direct append)
|
|
||||||
- initial commit with GP pseudo color
|
|
||||||
@@ -4,6 +4,8 @@ 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://gitlab.com/autour-de-minuit/blender/gp_toolbox/-/archive/master/gp_toolbox-master.zip)**
|
||||||
|
|
||||||
|
**[Demo video](https://www.youtube.com/watch?v=Htgao_uPWNs)**
|
||||||
|
|
||||||
> Une fois en place un système de mise a jour facilité est accessible dans les préférence (La vérification automatique de nouvelle mise a jour peut y être activé)
|
> Une fois en place un système de mise a jour facilité est accessible dans les préférence (La vérification automatique de nouvelle mise a jour peut y être activé)
|
||||||
|
|
||||||
Il est recommandé de désactiver l'addon natif "Grease pencil tools" car ces outils sont déjà intégré dans la toolbox et risque de créer des conflit.
|
Il est recommandé de désactiver l'addon natif "Grease pencil tools" car ces outils sont déjà intégré dans la toolbox et risque de créer des conflit.
|
||||||
@@ -147,3 +149,7 @@ pour set : Résolution du film, dossier palette, render settings
|
|||||||
- Déplacer les clés de dopesheet en même temps que les clés de GP (Déjà Créer par Tom Viguier sur [Andarta](https://gitlab.com/andarta-pictures)
|
- Déplacer les clés de dopesheet en même temps que les clés de GP (Déjà Créer par Tom Viguier sur [Andarta](https://gitlab.com/andarta-pictures)
|
||||||
|
|
||||||
- Meilleure table lumineuse (grosse réflexion et travail en perspective)
|
- Meilleure table lumineuse (grosse réflexion et travail en perspective)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[Liste des changements ici](CHANGELOG.md)
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import bpy
|
||||||
|
from bpy.types import WorkSpaceTool
|
||||||
|
from gpu_extras.presets import draw_circle_2d
|
||||||
|
from time import time
|
||||||
|
from .utils import get_addon_prefs
|
||||||
|
|
||||||
|
|
||||||
|
class GPTB_WT_eraser(WorkSpaceTool):
|
||||||
|
bl_space_type = 'VIEW_3D'
|
||||||
|
bl_context_mode = 'PAINT_GPENCIL'
|
||||||
|
|
||||||
|
# The prefix of the idname should be your add-on name.
|
||||||
|
bl_idname = "gp.eraser_tool"
|
||||||
|
bl_label = "Eraser"
|
||||||
|
bl_description = (
|
||||||
|
"This is a tooltip\n"
|
||||||
|
"with multiple lines"
|
||||||
|
)
|
||||||
|
bl_icon = "brush.paint_vertex.draw"
|
||||||
|
bl_widget = None
|
||||||
|
bl_keymap = (
|
||||||
|
("gp.eraser", {"type": 'LEFTMOUSE', "value": 'PRESS'},
|
||||||
|
{"properties": []}),
|
||||||
|
("wm.radial_control", {"type": 'F', "value": 'PRESS'},
|
||||||
|
{"properties": [("data_path_primary", 'scene.gptoolprops.eraser_radius')]}),
|
||||||
|
)
|
||||||
|
|
||||||
|
bl_cursor = 'DOT'
|
||||||
|
|
||||||
|
'''
|
||||||
|
def draw_cursor(context, tool, xy):
|
||||||
|
from gpu_extras.presets import draw_circle_2d
|
||||||
|
|
||||||
|
radius = context.scene.gptoolprops.eraser_radius
|
||||||
|
draw_circle_2d(xy, (0.75, 0.25, 0.35, 0.85), radius, 32)
|
||||||
|
'''
|
||||||
|
|
||||||
|
def draw_settings(context, layout, tool):
|
||||||
|
layout.prop(context.scene.gptoolprops, "eraser_radius")
|
||||||
|
|
||||||
|
### --- REGISTER ---
|
||||||
|
|
||||||
|
## --- KEYMAP
|
||||||
|
addon_keymaps = []
|
||||||
|
def register_keymaps():
|
||||||
|
addon = bpy.context.window_manager.keyconfigs.addon
|
||||||
|
|
||||||
|
km = addon.keymaps.new(name="Grease Pencil Stroke Paint (Draw brush)", space_type="EMPTY", region_type='WINDOW')
|
||||||
|
kmi = km.keymap_items.new("gp.eraser", type='LEFTMOUSE', value="PRESS", ctrl=True)
|
||||||
|
|
||||||
|
prefs = get_addon_prefs()
|
||||||
|
kmi.active = prefs.use_precise_eraser
|
||||||
|
addon_keymaps.append((km, kmi))
|
||||||
|
|
||||||
|
def unregister_keymaps():
|
||||||
|
for km, kmi in addon_keymaps:
|
||||||
|
km.keymap_items.remove(kmi)
|
||||||
|
addon_keymaps.clear()
|
||||||
|
|
||||||
|
def register():
|
||||||
|
bpy.utils.register_tool(GPTB_WT_eraser, after={"builtin.cursor"})
|
||||||
|
#bpy.context.window_manager.keyconfigs.default.keymaps['Grease Pencil Stroke Paint (Draw brush)'].keymap_items[3].idname = 'gp.eraser'
|
||||||
|
|
||||||
|
register_keymaps()
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
bpy.utils.unregister_tool(GPTB_WT_eraser)
|
||||||
|
unregister_keymaps()
|
||||||
+73
-41
@@ -81,10 +81,13 @@ class GPTB_PT_sidebar_panel(bpy.types.Panel):
|
|||||||
if context.scene.camera:
|
if context.scene.camera:
|
||||||
row = layout.row(align=True)# .split(factor=0.5)
|
row = layout.row(align=True)# .split(factor=0.5)
|
||||||
row.label(text='Passepartout')
|
row.label(text='Passepartout')
|
||||||
row.prop(context.scene.camera.data, 'show_passepartout',text='', icon ='OBJECT_HIDDEN' )
|
if context.scene.camera.name == 'draw_cam':
|
||||||
|
row.prop(context.scene.gptoolprops, 'drawcam_passepartout', text='', icon ='OBJECT_HIDDEN')
|
||||||
|
else:
|
||||||
|
row.prop(context.scene.camera.data, 'show_passepartout', text='', icon ='OBJECT_HIDDEN')
|
||||||
row.prop(context.scene.camera.data, 'passepartout_alpha', text='')
|
row.prop(context.scene.camera.data, 'passepartout_alpha', text='')
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.operator('view3d.zoom_camera_1_to_1', text = 'Zoom 1:1', icon = 'ZOOM_PREVIOUS')# FULLSCREEN_EXIT
|
row.operator('view3d.zoom_camera_1_to_1', text = 'Zoom 1:1', icon = 'ZOOM_PREVIOUS') # FULLSCREEN_EXIT
|
||||||
row.operator('view3d.view_center_camera', text = 'Zoom fit', icon = 'FULLSCREEN_ENTER')
|
row.operator('view3d.view_center_camera', text = 'Zoom fit', icon = 'FULLSCREEN_ENTER')
|
||||||
|
|
||||||
## background images/videos
|
## background images/videos
|
||||||
@@ -99,48 +102,45 @@ class GPTB_PT_sidebar_panel(bpy.types.Panel):
|
|||||||
for bg_img in context.scene.camera.data.background_images:
|
for bg_img in context.scene.camera.data.background_images:
|
||||||
if bg_img.source == 'IMAGE' and bg_img.image:
|
if bg_img.source == 'IMAGE' and bg_img.image:
|
||||||
row = box.row(align=True)
|
row = box.row(align=True)
|
||||||
row.label(text=bg_img.image.name, icon='IMAGE_RGB')# FILE_IMAGE
|
row.prop(bg_img, 'show_background_image', text='', icon='IMAGE_RGB')
|
||||||
# row.prop(bg_img, 'alpha', text='')# options={'HIDDEN'}
|
row.prop(bg_img, 'alpha', text=bg_img.image.name) # options={'HIDDEN'}
|
||||||
row.prop(bg_img, 'show_background_image', text='')# options={'HIDDEN'}
|
# row.label(icon='IMAGE_RGB')
|
||||||
|
# icon = 'HIDE_OFF' if bg_img.show_background_image else 'HIDE_ON'
|
||||||
|
# row.prop(bg_img, 'show_background_image', text='', icon=icon)
|
||||||
|
|
||||||
if bg_img.source == 'MOVIE_CLIP' and bg_img.clip:
|
if bg_img.source == 'MOVIE_CLIP' and bg_img.clip:
|
||||||
row = box.row(align=True)
|
row = box.row(align=True)
|
||||||
row.label(text=bg_img.clip.name, icon='FILE_MOVIE')
|
row.prop(bg_img, 'show_background_image', text='', icon='FILE_MOVIE')
|
||||||
# row.prop(bg_img, 'alpha', text='')# options={'HIDDEN'}
|
row.prop(bg_img, 'alpha', text=bg_img.clip.name) # options={'HIDDEN'}
|
||||||
row.prop(bg_img, 'show_background_image', text='')# options={'HIDDEN'}
|
# row.label(icon='FILE_MOVIE')
|
||||||
|
# icon = 'HIDE_OFF' if bg_img.show_background_image else 'HIDE_ON'
|
||||||
## playblast params
|
# row.prop(bg_img, 'show_background_image', text='', icon=icon)
|
||||||
layout.separator()
|
|
||||||
layout.label(text = 'Playblast:')
|
|
||||||
row = layout.row(align=False)#split(factor=0.6)
|
|
||||||
row.label(text = f'{rd.resolution_x * context.scene.gptoolprops.resolution_percentage // 100} x {rd.resolution_y * context.scene.gptoolprops.resolution_percentage // 100}')
|
|
||||||
row.prop(context.scene.gptoolprops, 'resolution_percentage', text='')
|
|
||||||
# row.prop(rd, 'resolution_percentage', text='')#real percent scene percentage
|
|
||||||
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.operator('render.thread_playblast', text = 'Playblast', icon = 'RENDER_ANIMATION')# non blocking background render playblast
|
|
||||||
# row.operator('render.playblast_anim', text = 'Playblast', icon = 'RENDER_ANIMATION').use_view = False # old (but robust) blocking playblast
|
|
||||||
row.operator('render.playblast_anim', text = 'Viewport').use_view = True
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
layout.label(text='No camera !', icon = 'ERROR')
|
layout.label(text='No camera !', icon = 'ERROR')
|
||||||
|
|
||||||
|
## Straight line ops from official greasepencil_tools addon if enabled.
|
||||||
|
# if any(x in context.preferences.addons.keys() for x in ('greasepencil_tools', 'greasepencil-addon')): # check enabled addons
|
||||||
|
if hasattr(bpy.types, 'GP_OT_straight_stroke'): # check if operator exists : bpy.ops.gp.straight_stroke.idname()
|
||||||
|
layout.operator('gp.straight_stroke', icon ="CURVE_PATH")
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
layout.separator()
|
layout.separator()
|
||||||
layout.label(text = 'Options:')
|
layout.label(text = 'Options:')
|
||||||
# row = layout.row(align=False)
|
|
||||||
## maybe remove cursor_follow icon that look like
|
|
||||||
text, icon = ('Cursor Follow On', 'PIVOT_CURSOR') if context.scene.gptoolprops.cursor_follow else ('Cursor Follow Off', 'CURSOR')
|
|
||||||
layout.prop(context.scene.gptoolprops, 'cursor_follow', text=text, icon=icon)
|
|
||||||
layout.prop(context.space_data.overlay, 'use_gpencil_onion_skin')
|
|
||||||
|
|
||||||
|
## Kf Jump filter
|
||||||
|
layout.prop(context.scene.gptoolprops, 'keyframe_type', text='Jump On') # Keyframe Jump
|
||||||
|
|
||||||
|
col = layout.column()
|
||||||
|
# col.prop(context.space_data.overlay, 'use_gpencil_onion_skin') # not often used
|
||||||
|
|
||||||
if context.object and context.object.type == 'GPENCIL':
|
if context.object and context.object.type == 'GPENCIL':
|
||||||
layout.prop(context.object.data, 'use_autolock_layers')
|
# col.prop(context.object.data, 'use_autolock_layers') # not often used
|
||||||
layout.prop(context.object, 'show_in_front', text='X-ray')#default text "In Front"
|
col.prop(context.object, 'show_in_front', text='X-ray') # default text "In Front"
|
||||||
|
|
||||||
## rename datablock temporary layout
|
## rename datablock temporary layout
|
||||||
if context.object.name != context.object.data.name:
|
if context.object.name != context.object.data.name:
|
||||||
box = layout.box()
|
box = col.box()
|
||||||
box.label(text='different name for object and data:', icon='INFO')
|
box.label(text='different name for object and data:', icon='INFO')
|
||||||
row = box.row(align=False)
|
row = box.row(align=False)
|
||||||
row.operator('gp.rename_data_from_obj').rename_all = False
|
row.operator('gp.rename_data_from_obj').rename_all = False
|
||||||
@@ -148,24 +148,34 @@ class GPTB_PT_sidebar_panel(bpy.types.Panel):
|
|||||||
|
|
||||||
## Check base palette
|
## Check base palette
|
||||||
if not all(x in [m.name for m in context.object.data.materials if m] for x in ("line", "invisible")):
|
if not all(x in [m.name for m in context.object.data.materials if m] for x in ("line", "invisible")):
|
||||||
box = layout.box()
|
box = col.box()
|
||||||
box.label(text='Missing base material setup', icon='INFO')
|
box.label(text='Missing base material setup', icon='INFO')
|
||||||
box.operator('gp.load_default_palette')
|
box.operator('gp.load_default_palette')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
layout.label(text='No GP object selected')
|
col.label(text='No GP object selected')
|
||||||
|
|
||||||
|
|
||||||
layout.prop(context.scene.gptoolprops, 'edit_lines_opacity')
|
col.prop(context.scene.gptoolprops, 'edit_lines_opacity')
|
||||||
|
|
||||||
|
row = col.row(align=True)
|
||||||
|
## realign / reproject
|
||||||
|
row.operator('gp.realign', icon='AXIS_FRONT')
|
||||||
|
## move in depth
|
||||||
|
row.operator('object.depth_proportional_move', text='Depth move', icon='TRANSFORM_ORIGINS')
|
||||||
|
|
||||||
|
## col.operator('gp.batch_reproject_all_frames') # text=Batch Reproject # added to context menu
|
||||||
## Create empty frame on layer (ops stored under GP_colorize... might be best to separate in another panel )
|
## Create empty frame on layer (ops stored under GP_colorize... might be best to separate in another panel )
|
||||||
layout.operator('gp.create_empty_frames', icon = 'DECORATE_KEYFRAME')
|
col.operator('gp.create_empty_frames', icon='DECORATE_KEYFRAME')
|
||||||
|
|
||||||
## File checker
|
## File checker
|
||||||
row = layout.row(align=True)
|
row = col.row(align=True)
|
||||||
row.operator('gp.file_checker', text = 'Check file', icon = 'SCENE_DATA')
|
row.operator('gp.file_checker', text = 'Check file', icon = 'SCENE_DATA')
|
||||||
row.operator('gp.links_checker', text = 'Check links', icon = 'UNLINKED')
|
row.operator('gp.links_checker', text = 'Check links', icon = 'UNLINKED')
|
||||||
|
|
||||||
|
text, icon = ('Cursor Follow On', 'PIVOT_CURSOR') if context.scene.gptoolprops.cursor_follow else ('Cursor Follow Off', 'CURSOR')
|
||||||
|
col.prop(context.scene.gptoolprops, 'cursor_follow', text=text, icon=icon)
|
||||||
|
|
||||||
# Mention update as notice
|
# Mention update as notice
|
||||||
addon_updater_ops.update_notice_box_ui(self, context)
|
addon_updater_ops.update_notice_box_ui(self, context)
|
||||||
|
|
||||||
@@ -217,6 +227,28 @@ class GPTB_PT_anim_manager(bpy.types.Panel):
|
|||||||
ops.skip_obj = True
|
ops.skip_obj = True
|
||||||
ops.mute = True
|
ops.mute = True
|
||||||
|
|
||||||
|
class GPTB_PT_toolbox_playblast(bpy.types.Panel):
|
||||||
|
bl_label = "Playblast"
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
bl_category = "Gpencil"
|
||||||
|
bl_parent_id = "GPTB_PT_sidebar_panel"
|
||||||
|
bl_options = {'DEFAULT_CLOSED'}
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
rd = context.scene.render
|
||||||
|
row = layout.row(align=False) # split(factor=0.6)
|
||||||
|
percent = context.scene.gptoolprops.resolution_percentage
|
||||||
|
row.label(text = f'{rd.resolution_x * percent // 100} x {rd.resolution_y * percent // 100}')
|
||||||
|
row.prop(context.scene.gptoolprops, 'resolution_percentage', text='')
|
||||||
|
# row.prop(rd, 'resolution_percentage', text='') # real percent scene percentage
|
||||||
|
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.operator('render.thread_playblast', text = 'Playblast', icon = 'RENDER_ANIMATION')# non blocking background render playblast
|
||||||
|
# row.operator('render.playblast_anim', text = 'Playblast', icon = 'RENDER_ANIMATION').use_view = False # old (but robust) blocking playblast
|
||||||
|
row.operator('render.playblast_anim', text = 'Viewport').use_view = True
|
||||||
|
|
||||||
class GPTB_PT_tint_layers(bpy.types.Panel):
|
class GPTB_PT_tint_layers(bpy.types.Panel):
|
||||||
bl_label = "Tint layers"
|
bl_label = "Tint layers"
|
||||||
bl_space_type = "VIEW_3D"
|
bl_space_type = "VIEW_3D"
|
||||||
@@ -272,13 +304,14 @@ class GPTB_PT_render(bpy.types.Panel):
|
|||||||
|
|
||||||
layout.operator('render.setup_render_path', text = 'Setup output', icon = 'TOOL_SETTINGS')#SETTINGS
|
layout.operator('render.setup_render_path', text = 'Setup output', icon = 'TOOL_SETTINGS')#SETTINGS
|
||||||
|
|
||||||
blend = Path(bpy.data.filepath)
|
blend = bpy.data.filepath
|
||||||
|
if blend:
|
||||||
|
blend = Path(blend)
|
||||||
out = blend.parents[1] / "compo" / "base"
|
out = blend.parents[1] / "compo" / "base"
|
||||||
layout.operator("wm.path_open", text='Open render folder', icon='FILE_FOLDER').filepath = str(out)
|
layout.operator("wm.path_open", text='Open render folder', icon='FILE_FOLDER').filepath = str(out)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
## unused -- (integrated in sidebar_panel)
|
## unused -- (integrated in sidebar_panel)
|
||||||
class GPTB_PT_cam_ref_panel(bpy.types.Panel):
|
class GPTB_PT_cam_ref_panel(bpy.types.Panel):
|
||||||
@@ -320,11 +353,13 @@ def palette_manager_menu(self, context):
|
|||||||
layout.operator("gp.load_palette", text='Load json Palette', icon='IMPORT').filepath = prefs.palette_path
|
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.operator("gp.save_palette", text='Save json Palette', icon='EXPORT').filepath = prefs.palette_path
|
||||||
layout.operator("gp.load_blend_palette", text='Load color Palette', icon='COLOR').filepath = prefs.palette_path
|
layout.operator("gp.load_blend_palette", text='Load color Palette', icon='COLOR').filepath = prefs.palette_path
|
||||||
|
layout.operator("gp.clean_material_stack", text='Clean material Stack', icon='NODE_MATERIAL')
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
GPTB_PT_sidebar_panel,
|
GPTB_PT_sidebar_panel,
|
||||||
GPTB_PT_anim_manager,
|
GPTB_PT_anim_manager,
|
||||||
|
GPTB_PT_toolbox_playblast,
|
||||||
GPTB_PT_tint_layers,
|
GPTB_PT_tint_layers,
|
||||||
GPTB_PT_render,
|
GPTB_PT_render,
|
||||||
## GPTB_PT_cam_ref_panel,
|
## GPTB_PT_cam_ref_panel,
|
||||||
@@ -385,8 +420,5 @@ def GPdata_toolbox_panel(self, context):
|
|||||||
row.label("Extra tools")
|
row.label("Extra tools")
|
||||||
|
|
||||||
if settings.extra_tools :
|
if settings.extra_tools :
|
||||||
layout.operator_menu_enum("gpencil.stroke_arrange", text="Arrange Strokes...", property="direction") """
|
layout.operator_menu_enum("gpencil.stroke_arrange", text="Arrange Strokes...", property="direction")
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Regular → Executable
+92
-11
@@ -14,10 +14,10 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "GP toolbox",
|
"name": "GP toolbox",
|
||||||
"description": "Set of tools for Grease Pencil in animation production",
|
"description": "Set of tools for Grease Pencil in animation production",
|
||||||
"author": "Samuel Bernou",
|
"author": "Samuel Bernou, Christophe Seux",
|
||||||
"version": (1, 0, 3),
|
"version": (1, 5, 1),
|
||||||
"blender": (2, 91, 0),
|
"blender": (2, 91, 0),
|
||||||
"location": "sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
||||||
"warning": "",
|
"warning": "",
|
||||||
"doc_url": "https://gitlab.com/autour-de-minuit/blender/gp_toolbox",
|
"doc_url": "https://gitlab.com/autour-de-minuit/blender/gp_toolbox",
|
||||||
"tracker_url": "https://gitlab.com/autour-de-minuit/blender/gp_toolbox/-/issues",
|
"tracker_url": "https://gitlab.com/autour-de-minuit/blender/gp_toolbox/-/issues",
|
||||||
@@ -41,16 +41,23 @@ from . import OP_helpers
|
|||||||
from . import OP_keyframe_jump
|
from . import OP_keyframe_jump
|
||||||
from . import OP_cursor_snap_canvas
|
from . import OP_cursor_snap_canvas
|
||||||
from . import OP_palettes
|
from . import OP_palettes
|
||||||
|
from . import OP_brushes
|
||||||
from . import OP_file_checker
|
from . import OP_file_checker
|
||||||
from . import OP_render
|
from . import OP_render
|
||||||
from . import OP_copy_paste
|
from . import OP_copy_paste
|
||||||
|
from . import OP_realign
|
||||||
|
from . import OP_depth_move
|
||||||
|
from . import OP_key_duplicate_send
|
||||||
|
from . import OP_eraser_brush
|
||||||
|
from . import TOOL_eraser_brush
|
||||||
|
from . import handler_draw_cam
|
||||||
from . import keymaps
|
from . import keymaps
|
||||||
|
|
||||||
from .OP_pseudo_tint import GPT_OT_auto_tint_gp_layers
|
from .OP_pseudo_tint import GPT_OT_auto_tint_gp_layers
|
||||||
|
|
||||||
from . import UI_tools
|
from . import UI_tools
|
||||||
|
|
||||||
from .properties import GP_PG_ToolsSettings
|
from .properties import GP_PG_ToolsSettings, GP_PG_FixSettings
|
||||||
|
|
||||||
from bpy.props import (FloatProperty,
|
from bpy.props import (FloatProperty,
|
||||||
BoolProperty,
|
BoolProperty,
|
||||||
@@ -73,11 +80,14 @@ from pathlib import Path
|
|||||||
|
|
||||||
@persistent
|
@persistent
|
||||||
def remap_relative(dummy):
|
def remap_relative(dummy):
|
||||||
|
# try:
|
||||||
all_path = [lib for lib in bpy.utils.blend_paths(local=True)]
|
all_path = [lib for lib in bpy.utils.blend_paths(local=True)]
|
||||||
bpy.ops.file.make_paths_relative()
|
bpy.ops.file.make_paths_relative()
|
||||||
for i, lib in enumerate(bpy.utils.blend_paths(local=True)):
|
for i, lib in enumerate(bpy.utils.blend_paths(local=True)):
|
||||||
if all_path[i] != lib:
|
if all_path[i] != lib:
|
||||||
print('Remapped:', all_path[i], '\n>> ', lib)
|
print('Remapped:', all_path[i], '\n>> ', lib)
|
||||||
|
# except Exception as e:
|
||||||
|
# print(e)
|
||||||
|
|
||||||
def remap_on_save_update(self, context):
|
def remap_on_save_update(self, context):
|
||||||
pref = get_addon_prefs()
|
pref = get_addon_prefs()
|
||||||
@@ -89,15 +99,30 @@ def remap_on_save_update(self, context):
|
|||||||
if 'remap_relative' in [hand.__name__ for hand in bpy.app.handlers.save_pre]:
|
if 'remap_relative' in [hand.__name__ for hand in bpy.app.handlers.save_pre]:
|
||||||
bpy.app.handlers.save_pre.remove(remap_relative)
|
bpy.app.handlers.save_pre.remove(remap_relative)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def update_use_precise_eraser(self, context):
|
||||||
|
|
||||||
|
km, kmi = TOOL_eraser_brush.addon_keymaps[0]
|
||||||
|
|
||||||
|
kmi.active = self.use_precise_eraser
|
||||||
|
|
||||||
|
|
||||||
class GPTB_prefs(bpy.types.AddonPreferences):
|
class GPTB_prefs(bpy.types.AddonPreferences):
|
||||||
bl_idname = __name__
|
bl_idname = __name__
|
||||||
|
|
||||||
|
use_precise_eraser : BoolProperty(
|
||||||
|
name='Precise Eraser',
|
||||||
|
default=False,
|
||||||
|
update=update_use_precise_eraser
|
||||||
|
)
|
||||||
## tabs
|
## tabs
|
||||||
|
|
||||||
pref_tabs : EnumProperty(
|
pref_tabs : EnumProperty(
|
||||||
items=(('PREF', "Preferences", "Change some preferences of the modal"),
|
items=(('PREF', "Preferences", "Change some preferences of the modal"),
|
||||||
('MAN_OPS', "Operator", "Operator to add Manually"),
|
('MAN_OPS', "Operator", "Operator to add Manually"),
|
||||||
# ('TUTO', "Tutorial", "How to use the tool"),
|
# ('TUTO', "Tutorial", "How to use the tool"),
|
||||||
|
('CHECKS', "Check List", "Customise what should happend when hitting 'check fix' button"),
|
||||||
('UPDATE', "Update", "Check and apply updates"),
|
('UPDATE', "Update", "Check and apply updates"),
|
||||||
# ('KEYMAP', "Keymap", "customise the default keymap"),
|
# ('KEYMAP', "Keymap", "customise the default keymap"),
|
||||||
),
|
),
|
||||||
@@ -149,14 +174,14 @@ class GPTB_prefs(bpy.types.AddonPreferences):
|
|||||||
use_relative_remap_on_save : BoolProperty(
|
use_relative_remap_on_save : BoolProperty(
|
||||||
name="Relative Remap On Save",
|
name="Relative Remap On Save",
|
||||||
description="Always remap all external path to relative when saving\nNeed blender restart if changed",
|
description="Always remap all external path to relative when saving\nNeed blender restart if changed",
|
||||||
default=True,
|
default=False,
|
||||||
update=remap_on_save_update
|
update=remap_on_save_update
|
||||||
)
|
)
|
||||||
|
|
||||||
fps : IntProperty(
|
fps : IntProperty(
|
||||||
name='Frame Rate',
|
name='Frame Rate',
|
||||||
description="Fps of the project, Used to conform the file when you use Check file operator",
|
description="Fps of the project, Used to conform the file when you use Check file operator",
|
||||||
default=25,
|
default=24,
|
||||||
min=1,
|
min=1,
|
||||||
max=10000
|
max=10000
|
||||||
)
|
)
|
||||||
@@ -185,6 +210,11 @@ class GPTB_prefs(bpy.types.AddonPreferences):
|
|||||||
description="Path to palette containing palette.json files to save and load",
|
description="Path to palette containing palette.json files to save and load",
|
||||||
default="//", maxlen=0, subtype='DIR_PATH')#, update = set_palette_path
|
default="//", maxlen=0, subtype='DIR_PATH')#, update = set_palette_path
|
||||||
|
|
||||||
|
brush_path : StringProperty(
|
||||||
|
name="Brushes directory",
|
||||||
|
description="Path to brushes containing the blends holding the brushes",
|
||||||
|
default="//", maxlen=0, subtype='DIR_PATH')#, update = set_palette_path
|
||||||
|
|
||||||
## Playblast prefs
|
## Playblast prefs
|
||||||
playblast_auto_play : BoolProperty(
|
playblast_auto_play : BoolProperty(
|
||||||
name="Playblast auto play",
|
name="Playblast auto play",
|
||||||
@@ -235,7 +265,7 @@ class GPTB_prefs(bpy.types.AddonPreferences):
|
|||||||
## KF jumper
|
## KF jumper
|
||||||
kfj_use_shortcut: BoolProperty(
|
kfj_use_shortcut: BoolProperty(
|
||||||
name = "Use Keyframe Jump Shortcut",
|
name = "Use Keyframe Jump Shortcut",
|
||||||
description = "Auto bind shotcut for keyframe jump (else you can bien manually using 'screen.gp_keyframe_jump' id_name)",
|
description = "Auto bind shotcut for keyframe jump (else you can bind manually using 'screen.gp_keyframe_jump' id_name)",
|
||||||
default = True)
|
default = True)
|
||||||
|
|
||||||
kfj_prev_keycode : StringProperty(
|
kfj_prev_keycode : StringProperty(
|
||||||
@@ -278,6 +308,9 @@ class GPTB_prefs(bpy.types.AddonPreferences):
|
|||||||
description = "add ctrl",
|
description = "add ctrl",
|
||||||
default = False)
|
default = False)
|
||||||
|
|
||||||
|
|
||||||
|
fixprops: bpy.props.PointerProperty(type = GP_PG_FixSettings)
|
||||||
|
|
||||||
## Temp cutter
|
## Temp cutter
|
||||||
# temp_cutter_use_shortcut: BoolProperty(
|
# temp_cutter_use_shortcut: BoolProperty(
|
||||||
# name = "Use temp cutter Shortcut",
|
# name = "Use temp cutter Shortcut",
|
||||||
@@ -304,8 +337,9 @@ class GPTB_prefs(bpy.types.AddonPreferences):
|
|||||||
row.prop(self, 'render_res_x', text='Width')
|
row.prop(self, 'render_res_x', text='Width')
|
||||||
row.prop(self, 'render_res_y', text='Height')
|
row.prop(self, 'render_res_y', text='Height')
|
||||||
## Palette
|
## Palette
|
||||||
box.label(text='Palette library folder:')
|
box.label(text='Project folders:')
|
||||||
box.prop(self, 'palette_path')
|
box.prop(self, 'palette_path')
|
||||||
|
box.prop(self, 'brush_path')
|
||||||
|
|
||||||
## render output
|
## render output
|
||||||
box.prop(self, 'output_path')
|
box.prop(self, 'output_path')
|
||||||
@@ -321,7 +355,7 @@ class GPTB_prefs(bpy.types.AddonPreferences):
|
|||||||
|
|
||||||
# box.separator()## Keyframe jumper
|
# box.separator()## Keyframe jumper
|
||||||
box = layout.box()
|
box = layout.box()
|
||||||
box.label(text='Keyframe Jump option:')
|
box.label(text='Keyframe Jump options:')
|
||||||
|
|
||||||
box.prop(self, "kfj_use_shortcut", text='Bind shortcuts')
|
box.prop(self, "kfj_use_shortcut", text='Bind shortcuts')
|
||||||
if self.kfj_use_shortcut:
|
if self.kfj_use_shortcut:
|
||||||
@@ -367,11 +401,14 @@ class GPTB_prefs(bpy.types.AddonPreferences):
|
|||||||
box.label(text='Random color options:')
|
box.label(text='Random color options:')
|
||||||
box.prop(self, 'separator')
|
box.prop(self, 'separator')
|
||||||
|
|
||||||
|
box = layout.box()
|
||||||
|
box.label(text='Tools options:')
|
||||||
|
box.prop(self, 'use_precise_eraser')
|
||||||
|
|
||||||
if self.pref_tabs == 'MAN_OPS':
|
if self.pref_tabs == 'MAN_OPS':
|
||||||
# layout.separator()## notes
|
# layout.separator()## notes
|
||||||
# layout.label(text='Notes:')
|
# layout.label(text='Notes:')
|
||||||
layout.label(text='Following operators ID have to be set manually :')
|
layout.label(text='Following operators ID have to be set manually in keymap if needed :')
|
||||||
|
|
||||||
## keyframe jump
|
## keyframe jump
|
||||||
box = layout.box()
|
box = layout.box()
|
||||||
@@ -392,11 +429,38 @@ class GPTB_prefs(bpy.types.AddonPreferences):
|
|||||||
row.operator('wm.copytext', text='Copy "view3d.cusor_snap"', icon='COPYDOWN').text = 'view3d.cusor_snap'
|
row.operator('wm.copytext', text='Copy "view3d.cusor_snap"', icon='COPYDOWN').text = 'view3d.cusor_snap'
|
||||||
box.label(text='Or just create a new shortcut using cursor_snap')
|
box.label(text='Or just create a new shortcut using cursor_snap')
|
||||||
|
|
||||||
|
## Clear keyframe
|
||||||
|
box = layout.box()
|
||||||
|
box.label(text='Clear active frame (delete all strokes without deleting the frame)')
|
||||||
|
row = box.row()
|
||||||
|
row.label(text='gp.clear_active_frame')
|
||||||
|
row.operator('wm.copytext', icon='COPYDOWN').text = 'gp.clear_active_frame'
|
||||||
|
|
||||||
## user prefs
|
## user prefs
|
||||||
box = layout.box()
|
box = layout.box()
|
||||||
box.label(text='Note: You can access user pref file and startup file in config folder')
|
box.label(text='Note: You can access user pref file and startup file in config folder')
|
||||||
box.operator("wm.path_open", text='Open config location').filepath = bpy.utils.user_resource('CONFIG')
|
box.operator("wm.path_open", text='Open config location').filepath = bpy.utils.user_resource('CONFIG')
|
||||||
|
|
||||||
|
if self.pref_tabs == 'CHECKS':
|
||||||
|
layout.label(text='Following checks will be made when clicking "Check File" button:')
|
||||||
|
col = layout.column()
|
||||||
|
# row = col.row()
|
||||||
|
col.prop(self.fixprops, 'lock_main_cam')
|
||||||
|
col.prop(self.fixprops, 'set_scene_res', text=f'Reset Scene Resolution (to {self.render_res_x}x{self.render_res_y})')
|
||||||
|
col.prop(self.fixprops, 'set_res_percentage')
|
||||||
|
col.prop(self.fixprops, 'set_fps', text=f'Reset FPS (to {self.fps})')
|
||||||
|
col.prop(self.fixprops, 'set_slider_n_sync')
|
||||||
|
col.prop(self.fixprops, 'set_cursor_type')
|
||||||
|
col.prop(self.fixprops, 'check_front_axis')
|
||||||
|
col.prop(self.fixprops, 'check_placement')
|
||||||
|
col.prop(self.fixprops, 'set_pivot_median_point')
|
||||||
|
col.prop(self.fixprops, 'disable_guide')
|
||||||
|
col.prop(self.fixprops, 'list_disabled_anim')
|
||||||
|
col.prop(self.fixprops, 'autokey_add_n_replace')
|
||||||
|
# row.label(text='lock the active camera if not a draw cam (and if not "layout" in blendfile name)')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if self.pref_tabs == 'UPDATE':
|
if self.pref_tabs == 'UPDATE':
|
||||||
addon_updater_ops.update_settings_ui(self, context)
|
addon_updater_ops.update_settings_ui(self, context)
|
||||||
|
|
||||||
@@ -408,8 +472,9 @@ class GPTB_prefs(bpy.types.AddonPreferences):
|
|||||||
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
GPTB_prefs,
|
GP_PG_FixSettings,
|
||||||
GP_PG_ToolsSettings,
|
GP_PG_ToolsSettings,
|
||||||
|
GPTB_prefs,
|
||||||
GPT_OT_auto_tint_gp_layers,
|
GPT_OT_auto_tint_gp_layers,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -418,6 +483,7 @@ GPT_OT_auto_tint_gp_layers,
|
|||||||
|
|
||||||
def register():
|
def register():
|
||||||
addon_updater_ops.register(bl_info)
|
addon_updater_ops.register(bl_info)
|
||||||
|
# bpy.types.Scene.gpfixprops = bpy.props.PointerProperty(type = GP_PG_FixSettings) # used in prefs
|
||||||
for cls in classes:
|
for cls in classes:
|
||||||
bpy.utils.register_class(cls)
|
bpy.utils.register_class(cls)
|
||||||
OP_helpers.register()
|
OP_helpers.register()
|
||||||
@@ -429,9 +495,16 @@ def register():
|
|||||||
OP_playblast_bg.register()
|
OP_playblast_bg.register()
|
||||||
OP_playblast.register()
|
OP_playblast.register()
|
||||||
OP_palettes.register()
|
OP_palettes.register()
|
||||||
|
OP_brushes.register()
|
||||||
OP_cursor_snap_canvas.register()
|
OP_cursor_snap_canvas.register()
|
||||||
OP_render.register()
|
OP_render.register()
|
||||||
OP_copy_paste.register()
|
OP_copy_paste.register()
|
||||||
|
OP_realign.register()
|
||||||
|
OP_depth_move.register()
|
||||||
|
OP_key_duplicate_send.register()
|
||||||
|
OP_eraser_brush.register()
|
||||||
|
TOOL_eraser_brush.register()
|
||||||
|
handler_draw_cam.register()
|
||||||
UI_tools.register()
|
UI_tools.register()
|
||||||
keymaps.register()
|
keymaps.register()
|
||||||
bpy.types.Scene.gptoolprops = bpy.props.PointerProperty(type = GP_PG_ToolsSettings)
|
bpy.types.Scene.gptoolprops = bpy.props.PointerProperty(type = GP_PG_ToolsSettings)
|
||||||
@@ -453,9 +526,16 @@ def unregister():
|
|||||||
for cls in reversed(classes):
|
for cls in reversed(classes):
|
||||||
bpy.utils.unregister_class(cls)
|
bpy.utils.unregister_class(cls)
|
||||||
UI_tools.unregister()
|
UI_tools.unregister()
|
||||||
|
handler_draw_cam.unregister()
|
||||||
|
OP_eraser_brush.unregister()
|
||||||
|
TOOL_eraser_brush.unregister()
|
||||||
|
OP_key_duplicate_send.unregister()
|
||||||
|
OP_depth_move.unregister()
|
||||||
|
OP_realign.unregister()
|
||||||
OP_copy_paste.unregister()
|
OP_copy_paste.unregister()
|
||||||
OP_render.unregister()
|
OP_render.unregister()
|
||||||
OP_cursor_snap_canvas.unregister()
|
OP_cursor_snap_canvas.unregister()
|
||||||
|
OP_brushes.unregister()
|
||||||
OP_palettes.unregister()
|
OP_palettes.unregister()
|
||||||
OP_file_checker.unregister()
|
OP_file_checker.unregister()
|
||||||
OP_helpers.unregister()
|
OP_helpers.unregister()
|
||||||
@@ -465,6 +545,7 @@ def unregister():
|
|||||||
GP_colorize.unregister()## GP_guided_colorize.
|
GP_colorize.unregister()## GP_guided_colorize.
|
||||||
OP_playblast_bg.unregister()
|
OP_playblast_bg.unregister()
|
||||||
OP_playblast.unregister()
|
OP_playblast.unregister()
|
||||||
|
# del bpy.types.Scene.gpfixprops
|
||||||
del bpy.types.Scene.gptoolprops
|
del bpy.types.Scene.gptoolprops
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
from bpy.app.handlers import persistent
|
||||||
|
|
||||||
|
|
||||||
|
def extrapolate_points_by_length(a, b, length):
|
||||||
|
'''
|
||||||
|
Return a third point C from by continuing in AB direction
|
||||||
|
Length define BC distance. both vector2 and vector3
|
||||||
|
'''
|
||||||
|
# return b + ((b - a).normalized() * length)# one shot
|
||||||
|
ab = b - a
|
||||||
|
if not ab:
|
||||||
|
return None
|
||||||
|
return b + (ab.normalized() * length)
|
||||||
|
|
||||||
|
def view3d_camera_border_2d(context, cam):
|
||||||
|
# based on https://blender.stackexchange.com/questions/6377/coordinates-of-corners-of-camera-view-border
|
||||||
|
# cam = context.scene.camera
|
||||||
|
frame = cam.data.view_frame(scene=context.scene)
|
||||||
|
# to world-space
|
||||||
|
frame = [cam.matrix_world @ v for v in frame]
|
||||||
|
# to pixelspace
|
||||||
|
region, rv3d = context.region, context.space_data.region_3d
|
||||||
|
frame_px = [location_3d_to_region_2d(region, rv3d, v) for v in frame]
|
||||||
|
return frame_px
|
||||||
|
|
||||||
|
def draw_cam_frame_callback_2d():
|
||||||
|
context = bpy.context
|
||||||
|
if context.region_data.view_perspective != 'CAMERA':
|
||||||
|
return
|
||||||
|
if context.scene.camera.name != 'draw_cam':
|
||||||
|
return
|
||||||
|
|
||||||
|
main_cam = context.scene.camera.parent
|
||||||
|
if not main_cam:
|
||||||
|
return
|
||||||
|
|
||||||
|
bgl.glEnable(bgl.GL_BLEND)
|
||||||
|
|
||||||
|
frame_point = view3d_camera_border_2d(
|
||||||
|
context, context.scene.camera.parent)
|
||||||
|
shader_2d = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
|
||||||
|
|
||||||
|
if context.scene.gptoolprops.drawcam_passepartout:
|
||||||
|
### PASSEPARTOUT
|
||||||
|
|
||||||
|
# frame positions
|
||||||
|
#
|
||||||
|
# lupext lup rup rupext
|
||||||
|
# D-----A
|
||||||
|
# | |
|
||||||
|
# C-----B
|
||||||
|
# ldnext ldn rdn lnext
|
||||||
|
|
||||||
|
a = frame_point[0]
|
||||||
|
b = frame_point[1]
|
||||||
|
c = frame_point[2]
|
||||||
|
d = frame_point[3]
|
||||||
|
|
||||||
|
ext = 10000
|
||||||
|
rup = extrapolate_points_by_length(b,a, ext)
|
||||||
|
rdn = extrapolate_points_by_length(a,b, ext)
|
||||||
|
rupext = rup + ((a-d).normalized() * ext)
|
||||||
|
rdnext = rdn + ((a-d).normalized() * ext)
|
||||||
|
lup = extrapolate_points_by_length(c,d, ext)
|
||||||
|
ldn = extrapolate_points_by_length(d,c, ext)
|
||||||
|
lupext = lup + ((c-b).normalized() * ext)
|
||||||
|
ldnext = ldn + ((c-b).normalized() * ext)
|
||||||
|
|
||||||
|
|
||||||
|
# ppp_color = (1.0, 1.0, 0.0, 1)
|
||||||
|
ppp_color = (0.0, 0.0, 0.0, context.scene.camera.data.passepartout_alpha)
|
||||||
|
|
||||||
|
rect = [rup, rdn, rupext, rdnext,
|
||||||
|
lup, ldn, lupext, ldnext,
|
||||||
|
a, b, c, d]
|
||||||
|
|
||||||
|
## convert to 2d
|
||||||
|
# region, rv3d = context.region, context.space_data.region_3d
|
||||||
|
# rect = [location_3d_to_region_2d(region, rv3d, v) for v in rect]
|
||||||
|
|
||||||
|
# lupext=6 lup=4 rup=0 rupext=2
|
||||||
|
# d=11, a=8
|
||||||
|
# c=10, b=9
|
||||||
|
# ldnext=7 ldn=5 rdn=1 dnpext=3
|
||||||
|
|
||||||
|
indices = [(0,1,2), (1,2,3),
|
||||||
|
(4,5,6), (5,6,7),
|
||||||
|
(8,11,4), (8,4,0),
|
||||||
|
(10,9,5), (9,5,1),
|
||||||
|
]
|
||||||
|
|
||||||
|
# ### passpartout_points
|
||||||
|
|
||||||
|
passepartout = batch_for_shader(
|
||||||
|
shader_2d, 'TRIS', {"pos": rect}, indices=indices)
|
||||||
|
# shader_2d, 'LINE_LOOP', {"pos": rect})
|
||||||
|
|
||||||
|
shader_2d.bind()
|
||||||
|
shader_2d.uniform_float("color", ppp_color)
|
||||||
|
passepartout.draw(shader_2d)
|
||||||
|
|
||||||
|
|
||||||
|
### Camera framing trace over
|
||||||
|
|
||||||
|
bgl.glLineWidth(1)
|
||||||
|
bgl.glEnable(bgl.GL_LINE_SMOOTH)
|
||||||
|
|
||||||
|
"""
|
||||||
|
## need to accurately detect viewport background color (difficult)
|
||||||
|
### COUNTER CURRENT FRAMING
|
||||||
|
if False:
|
||||||
|
camf = view3d_camera_border_2d(
|
||||||
|
context, context.scene.camera)
|
||||||
|
ca = (camf[0].x + 1, camf[0].y + 1)
|
||||||
|
cb = (camf[1].x + 1, camf[1].y - 1)
|
||||||
|
cc = (camf[2].x - 1, camf[2].y - 1)
|
||||||
|
cd = (camf[3].x - 1, camf[3].y + 1)
|
||||||
|
screen_framing = batch_for_shader(
|
||||||
|
shader_2d, 'LINE_LOOP', {"pos": [ca, cb, cc, cd]})
|
||||||
|
shader_2d.bind()
|
||||||
|
shader_2d.uniform_float("color", (0.25, 0.25, 0.25, 1.0))
|
||||||
|
# shader_2d.uniform_float("color", (0.9, 0.9, 0.9, 1.0))
|
||||||
|
screen_framing.draw(shader_2d)
|
||||||
|
"""
|
||||||
|
### FRAMING
|
||||||
|
|
||||||
|
# frame_color = (0.06, 0.4, 0.040, 0.5)
|
||||||
|
frame_color = (0.0, 0.0, 0.25, 1.0)
|
||||||
|
|
||||||
|
screen_framing = batch_for_shader(
|
||||||
|
shader_2d, 'LINE_LOOP', {"pos": 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)
|
||||||
|
|
||||||
|
|
||||||
|
draw_handle = None
|
||||||
|
|
||||||
|
def register():
|
||||||
|
if bpy.app.background:
|
||||||
|
return
|
||||||
|
|
||||||
|
global draw_handle
|
||||||
|
draw_handle = bpy.types.SpaceView3D.draw_handler_add(
|
||||||
|
draw_cam_frame_callback_2d, (), "WINDOW", "POST_PIXEL")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
if bpy.app.background:
|
||||||
|
return
|
||||||
|
|
||||||
|
global draw_handle
|
||||||
|
if draw_handle:
|
||||||
|
bpy.types.SpaceView3D.draw_handler_remove(draw_handle, 'WINDOW')
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
register()
|
||||||
Regular → Executable
+88
-6
@@ -4,20 +4,90 @@ from bpy.props import (
|
|||||||
BoolProperty,
|
BoolProperty,
|
||||||
StringProperty,
|
StringProperty,
|
||||||
FloatProperty,
|
FloatProperty,
|
||||||
|
EnumProperty,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .OP_cursor_snap_canvas import cursor_follow_update
|
from .OP_cursor_snap_canvas import cursor_follow_update
|
||||||
|
|
||||||
|
|
||||||
def change_edit_lines_opacity(self, context):
|
def change_edit_lines_opacity(self, context):
|
||||||
# for o in context.scene.objects:
|
|
||||||
# if o.type != 'GPENCIL':
|
|
||||||
# continue
|
|
||||||
# o.data.edit_line_color[3]=self.edit_lines_opacity
|
|
||||||
for gp in bpy.data.grease_pencils:
|
for gp in bpy.data.grease_pencils:
|
||||||
if not gp.is_annotation:
|
if not gp.is_annotation:
|
||||||
gp.edit_line_color[3]=self.edit_lines_opacity
|
gp.edit_line_color[3]=self.edit_lines_opacity
|
||||||
|
|
||||||
class GP_PG_ToolsSettings(bpy.types.PropertyGroup) :
|
|
||||||
|
class GP_PG_FixSettings(bpy.types.PropertyGroup):
|
||||||
|
lock_main_cam : BoolProperty(
|
||||||
|
name="Lock Main Cam",
|
||||||
|
description="Lock the main camera (works only if 'layout' is not in name)",
|
||||||
|
default=False, options={'HIDDEN'})
|
||||||
|
|
||||||
|
set_scene_res : BoolProperty(
|
||||||
|
name="Reset Scene Resolution",
|
||||||
|
description="Set the scene resolution to current prefs project settings",
|
||||||
|
default=True, options={'HIDDEN'})
|
||||||
|
|
||||||
|
set_res_percentage : BoolProperty(
|
||||||
|
name="Reset Resolution Percentage To 100%",
|
||||||
|
description="",
|
||||||
|
default=True, options={'HIDDEN'})
|
||||||
|
|
||||||
|
set_fps : BoolProperty(
|
||||||
|
name="Reset Fps",
|
||||||
|
description="Set the framerate of the scene to current prefs project settings",
|
||||||
|
default=False, options={'HIDDEN'})
|
||||||
|
|
||||||
|
set_slider_n_sync : BoolProperty(
|
||||||
|
name="Dopesheets Show Slider And Sync Range",
|
||||||
|
description="Toggle on the use of show slider and sync range",
|
||||||
|
default=True, options={'HIDDEN'})
|
||||||
|
|
||||||
|
set_cursor_type : BoolProperty(
|
||||||
|
name="Set Select Cursor Mode",
|
||||||
|
description="Set the type of the selection cursor (according to addon prefs)",
|
||||||
|
default=True, options={'HIDDEN'})
|
||||||
|
|
||||||
|
check_front_axis : BoolProperty(
|
||||||
|
name="Check If Draw Axis is Front (X-Z)",
|
||||||
|
description="Alert if the current grease pencil draw axis is not front (X-Z)",
|
||||||
|
default=True, options={'HIDDEN'})
|
||||||
|
|
||||||
|
check_placement : BoolProperty(
|
||||||
|
name="Check Stroke Placement",
|
||||||
|
description="Alert if the current grease pencil stroke placement is not Origin",
|
||||||
|
default=True, options={'HIDDEN'})
|
||||||
|
|
||||||
|
set_pivot_median_point : BoolProperty(
|
||||||
|
name="Set Pivot To Median Point",
|
||||||
|
description="Change the pivot point to the most used median point",
|
||||||
|
default=True, options={'HIDDEN'})
|
||||||
|
|
||||||
|
disable_guide : BoolProperty(
|
||||||
|
name="Disable Drawing Guide",
|
||||||
|
description="Disable constrained guide in draw mode",
|
||||||
|
default=False, options={'HIDDEN'})
|
||||||
|
|
||||||
|
list_disabled_anim : BoolProperty(
|
||||||
|
name="List Disabled Animation",
|
||||||
|
description="Alert if there are disabled animations",
|
||||||
|
default=True, options={'HIDDEN'})
|
||||||
|
|
||||||
|
autokey_add_n_replace : BoolProperty(
|
||||||
|
name="Autokey Mode Add and Replace",
|
||||||
|
description="Change autokey mode back to 'Add & Replace'",
|
||||||
|
default=True, options={'HIDDEN'})
|
||||||
|
|
||||||
|
class GP_PG_ToolsSettings(bpy.types.PropertyGroup):
|
||||||
|
eraser_radius : IntProperty(
|
||||||
|
name="Eraser Radius", description="Radius of eraser brush",
|
||||||
|
default=20, min=0, max=500, subtype='PIXEL')
|
||||||
|
|
||||||
|
drawcam_passepartout : BoolProperty(
|
||||||
|
name="Show cam passepartout",
|
||||||
|
description="Show a darkened overlay outside image area in Camera view",
|
||||||
|
default=True,
|
||||||
|
options={'HIDDEN'})
|
||||||
|
|
||||||
autotint_offset : IntProperty(
|
autotint_offset : IntProperty(
|
||||||
name="Tint hue offset", description="offset the tint by this value for better color",
|
name="Tint hue offset", description="offset the tint by this value for better color",
|
||||||
default=0, min=-5000, max=5000, soft_min=-999, soft_max=999, step=1,
|
default=0, min=-5000, max=5000, soft_min=-999, soft_max=999, step=1,
|
||||||
@@ -37,13 +107,25 @@ class GP_PG_ToolsSettings(bpy.types.PropertyGroup) :
|
|||||||
default=False, update=cursor_follow_update)
|
default=False, update=cursor_follow_update)
|
||||||
|
|
||||||
edit_lines_opacity : FloatProperty(
|
edit_lines_opacity : FloatProperty(
|
||||||
name="edit lines Opacity", description="Change edit lines opacity for all grease pencils", default=0.5, min=0.0, max=1.0, step=3, precision=2, update=change_edit_lines_opacity)#, get=None, set=None
|
name="edit lines Opacity", description="Change edit lines opacity for all grease pencils",
|
||||||
|
default=0.5, min=0.0, max=1.0, step=3, precision=2, update=change_edit_lines_opacity)
|
||||||
|
|
||||||
## render
|
## render
|
||||||
name_for_current_render : StringProperty(
|
name_for_current_render : StringProperty(
|
||||||
name="Render_name", description="Name use for render current",
|
name="Render_name", description="Name use for render current",
|
||||||
default="")
|
default="")
|
||||||
|
|
||||||
|
keyframe_type : EnumProperty(
|
||||||
|
name="Keyframe Filter", description="Only jump to defined keyframe type",
|
||||||
|
default='ALL', options={'HIDDEN', 'SKIP_SAVE'},
|
||||||
|
items=(
|
||||||
|
('ALL', 'All', '', 0), # 'KEYFRAME'
|
||||||
|
('KEYFRAME', 'Keyframe', '', 'KEYTYPE_KEYFRAME_VEC', 1),
|
||||||
|
('BREAKDOWN', 'Breakdown', '', 'KEYTYPE_BREAKDOWN_VEC', 2),
|
||||||
|
('MOVING_HOLD', 'Moving Hold', '', 'KEYTYPE_MOVING_HOLD_VEC', 3),
|
||||||
|
('EXTREME', 'Extreme', '', 'KEYTYPE_EXTREME_VEC', 4),
|
||||||
|
('JITTER', 'Jitter', '', 'KEYTYPE_JITTER_VEC', 5),
|
||||||
|
))
|
||||||
|
|
||||||
"""
|
"""
|
||||||
reconnect_parent = bpy.props.PointerProperty(type =bpy.types.Object,poll=poll_armature)
|
reconnect_parent = bpy.props.PointerProperty(type =bpy.types.Object,poll=poll_armature)
|
||||||
|
|||||||
Reference in New Issue
Block a user