From 7985844226ce7b6ada96260dd1ac80bc3732b4da Mon Sep 17 00:00:00 2001 From: Pullusb Date: Tue, 18 Jan 2022 22:53:08 +0100 Subject: [PATCH] Add disable use light in file checker 1.9.1 - fix: add error handling on palette linker when blend_path isn't valid anymore - added: file checker entry, Disable all GP object `use lights` (True by default in addon pref `checklist`) - added: WIP of Batch reproject all on cursor. - No UI. In search menu `Flat Reproject Selected On cursor` (idname `gp.batch_flat_reproject`) --- CHANGELOG.md | 7 ++ OP_depth_move.py | 2 +- OP_file_checker.py | 9 +++ OP_flat_reproject.py | 160 ++++++++++++++++++++++++++++++++++++++++++ OP_palettes_linker.py | 109 ++++++++++++++++++++++++++++ UI_tools.py | 83 +++++++++++++++++----- __init__.py | 6 +- properties.py | 5 ++ 8 files changed, 363 insertions(+), 18 deletions(-) create mode 100644 OP_flat_reproject.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 14c0cad..ddc6346 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +1.9.1 + +- fix: add error handling on palette linker when blend_path isn't valid anymore +- added: file checker entry, Disable all GP object `use lights` (True by default in addon pref `checklist`) +- added: WIP of Batch reproject all on cursor. + - No UI. In search menu `Flat Reproject Selected On cursor` (idname `gp.batch_flat_reproject`) + 1.9.0 - feat: New shortcuts: diff --git a/OP_depth_move.py b/OP_depth_move.py index b64abfb..03d9db3 100644 --- a/OP_depth_move.py +++ b/OP_depth_move.py @@ -102,7 +102,7 @@ class ODM_PT_sudden_depth_panel(bpy.types.Panel): layout = self.layout row = layout.row() row.operator('object.depth_proportional_move', text='Depth move', icon='TRANSFORM_ORIGINS') - """ +""" ### --- REGISTER --- diff --git a/OP_file_checker.py b/OP_file_checker.py index 76573b3..b1d72cf 100755 --- a/OP_file_checker.py +++ b/OP_file_checker.py @@ -24,6 +24,7 @@ class GPTB_OT_file_checker(bpy.types.Operator): # Set onion skin filter to 'All type' # Set filepath type # Set Lock object mode state + # Disable use light on all object def invoke(self, context, event): # need some self-control (I had to...) @@ -115,6 +116,14 @@ class GPTB_OT_file_checker(bpy.types.Operator): if bpy.context.scene.tool_settings.gpencil_stroke_placement_view3d != 'ORIGIN': problems.append('/!\\ Draw placement not "Origin" (Need Manual change if not Ok)') + ## GP Use light disable + if fix.set_gp_use_lights_off: + gp_with_lights = [o for o in context.scene.objects if o.type == 'GPENCIL' and o.use_grease_pencil_lights] + if gp_with_lights: + problems.append(f'Disable "Use Lights" on {len(gp_with_lights)} Gpencil objects') + if apply: + for o in gp_with_lights: + o.use_grease_pencil_lights = False ## Disabled animation if fix.list_disabled_anim: diff --git a/OP_flat_reproject.py b/OP_flat_reproject.py new file mode 100644 index 0000000..55ec1ce --- /dev/null +++ b/OP_flat_reproject.py @@ -0,0 +1,160 @@ +import bpy +import mathutils +from mathutils import Matrix, Vector +from mathutils.geometry import intersect_line_plane +from math import pi +import numpy as np +from time import time +from .utils import (location_to_region, region_to_location) + + +""" +## Do not work on multiple object +def batch_flat_reproject(obj, proj_type='VIEW', all_strokes=True, restore_frame=False): + '''Reproject + :all_stroke: affect hided, locked layers + ''' + + if restore_frame: + oframe = bpy.context.scene.frame_current + omode = bpy.context.mode + + # frame_list = [ f.frame_number for l in obj.data.layers for f in l.frames if len(f.strokes)] + # frame_list = list(set(frame_list)) + # frame_list.sort() + # for fnum in frame_list: + # bpy.context.scene.frame_current = fnum + t0 = time() + scn = bpy.context.scene + laynum = len(obj.data.layers) + for i, l in enumerate(obj.data.layers): + ## \x1b[2K\r ? + fnum = len(l.frames) + zf = len(str(fnum)) + for j, f in enumerate(reversed(l.frames)): # whynot... + print(f'{obj.name} : {i+1}/{laynum} : {l.info} : {str(j+1).zfill(zf)}/{fnum}{" "*30}', end='\r') + scn.frame_set(f.frame_number) # more chance to update the matrix + bpy.context.view_layer.update() # update the matrix ? + bpy.context.scene.camera.location = bpy.context.scene.camera.location + scn.frame_current = f.frame_number + + for s in f.strokes: + for p in s.points: + p.co = obj.matrix_world.inverted() @ region_to_location(location_to_region(obj.matrix_world @ p.co), scn.cursor.location) + + if restore_frame: + bpy.context.scene.frame_current = oframe + print(' '*50,end='\x1b[1K\r') # clear the line + print(f'{obj.name} ok ({time()-t0:.2f})') + """ + +""" +def batch_flat_reproject(obj): + '''Reproject all strokes on 3D cursor for all existing frame of passed GP object''' + + scn = bpy.context.scene + cam = scn.camera + + for l in obj.data.layers: + for f in l.frames: + scn.frame_set(f.frame_number) + + cam_mat = cam.matrix_local.copy() + origin = cam.matrix_world.to_translation() + mat_inv = obj.matrix_world.inverted() + + plane_no = Vector((0,0,1)) + plane_no.rotate(cam_mat) + plane_co = scn.cursor.location + + for s in f.strokes: + points_co = [obj.matrix_world @ p.co for p in s.points] + points_co = [mat_inv @ intersect_line_plane(origin, p, plane_co, plane_no) for p in points_co] + points_co = [co for vector in points_co for co in vector] + + s.points.foreach_set('co', points_co) + s.points.add(1) # update + s.points.pop() # update + #for p in s.points: + # loc_2d = location_to_region(obj.matrix_world @ p.co) + # p.co = obj.matrix_world.inverted() @ region_to_location(loc_2d, scn.cursor.location) + """ + +def batch_flat_reproject(obj): + '''Reproject strokes of passed GP object on 3D cursor full scene range''' + + scn = bpy.context.scene + cam = scn.camera + + for i in range(scn.frame_start, scn.frame_end + 1): + scn.frame_set(i) + cam_mat = cam.matrix_local.copy() + origin = cam.matrix_world.to_translation() + mat_inv = obj.matrix_world.inverted() + plane_no = Vector((0,0,1)) + plane_no.rotate(cam_mat) + plane_co = scn.cursor.location + + for l in obj.data.layers: + f = l.active_frame + if not f: # No active frame + continue + + if f.frame_number != scn.frame_current: + f = l.frames.copy(f) # duplicate content of the previous frame + for s in f.strokes: + points_co = [obj.matrix_world @ p.co for p in s.points] + points_co = [mat_inv @ intersect_line_plane(origin, p, plane_co, plane_no) for p in points_co] + points_co = [co for vector in points_co for co in vector] + + s.points.foreach_set('co', points_co) + s.points.add(1) # update + s.points.pop() # update + +class GPTB_OT_batch_flat_reproject(bpy.types.Operator): + bl_idname = "gp.batch_flat_reproject" + bl_label = "Flat Reproject Selected On cursor" + bl_description = "Reproject all frames of all selected gp object on cursor" + bl_options = {"REGISTER"} + + @classmethod + def poll(cls, context): + return context.object and context.object.type == 'GPENCIL' + + def execute(self, context): + for o in context.selected_objects: + if o.type != 'GPENCIL' or not o.select_get(): + continue + batch_flat_reproject(o) + + return {"FINISHED"} + +### -- MENU ENTRY -- + +def flat_reproject_clean_menu(self, context): + if context.mode == 'EDIT_GPENCIL': + self.layout.operator_context = 'INVOKE_REGION_WIN' # needed for popup (also works with 'INVOKE_DEFAULT') + self.layout.operator('gp.batch_flat_reproject', icon='SMALL_TRI_RIGHT_VEC') # KEYTYPE_JITTER_VEC + +def flat_reproject_context_menu(self, context): + if context.mode == 'EDIT_GPENCIL' and context.scene.tool_settings.gpencil_selectmode_edit == 'STROKE': + self.layout.operator_context = 'INVOKE_REGION_WIN' # needed for popup + self.layout.operator('gp.batch_flat_reproject', icon='SMALL_TRI_RIGHT_VEC') # KEYTYPE_JITTER_VEC + +classes = ( +GPTB_OT_batch_flat_reproject, +) + +def register(): + for cl in classes: + bpy.utils.register_class(cl) + + # bpy.types.VIEW3D_MT_gpencil_edit_context_menu.append(flat_reproject_context_menu) + # bpy.types.GPENCIL_MT_cleanup.append(flat_reproject_clean_menu) + +def unregister(): + # bpy.types.GPENCIL_MT_cleanup.remove(flat_reproject_clean_menu) + # bpy.types.VIEW3D_MT_gpencil_edit_context_menu.remove(flat_reproject_context_menu) + + for cl in reversed(classes): + bpy.utils.unregister_class(cl) \ No newline at end of file diff --git a/OP_palettes_linker.py b/OP_palettes_linker.py index e343b53..c122c71 100644 --- a/OP_palettes_linker.py +++ b/OP_palettes_linker.py @@ -79,6 +79,10 @@ class GPTB_OT_import_obj_palette(Operator): self.report({'ERROR'}, 'Not supported yet, use link') return {'CANCELLED'} + if not Path(blend_path).exists(): + utils.show_message_box([['gp.palettes_reload_blends', 'Invalid blend path! Click here to refresh source blends', 'FILE_REFRESH']], 'Invalid Palette', 'ERROR') + return {'CANCELLED'} + # get relative path blend_path = bpy.path.relpath(blend_path) @@ -197,6 +201,109 @@ class GPTB_OT_palette_fuzzy_search_obj(Operator): self.report({'INFO'}, f'Select {bl_props.objects[bl_props.ob_idx].name} (match at {final_ratio*100:.1f}% with "{context.object.name}")') return {"FINISHED"} + +## Unused for now, all libs are linked to one library data. need to replace material links one by one. +class GPTB_OT_palette_version_update(Operator): + bl_idname = "gptb.palette_version_update" + bl_label = "Update Palette Version" + bl_description = "Update linked material to selected palette version if curent link has same basename" + bl_options = {"REGISTER"} + + mat_scope : EnumProperty( + name='Targeted Materials', + items=(('ALL', "All Materials", "Update all linked material in file to next version"), + ('SELECTED', "Selected Objects", "Update all linked material on selected gp objects"), + ), + default='ALL', + description='Choose material targeted for library update' + ) + + mat_type : EnumProperty( + name='Materials Type', + items=(('ALL', "All Materials", "Update both gp and obj materials"), + ('GP', "Gpencil Materials", "update only grease pencil materials"), + ('OBJ', "Non-Gpencil Materials", "update only non-gpencil objects materials"), + ), + default='GP', + description='Filter material type for library update' + ) + + def invoke(self, context, event): + self.bl_props = context.scene.bl_palettes_props + if not self.bl_props.blends or not self.bl_props.blends[0].blend_path: + self.report({'ERROR'}, 'No blend selected') + return {"CANCELLED"} + return context.window_manager.invoke_props_dialog(self, width=450) + + def draw(self, context): + layout = self.layout + layout.label(text=f'Update links path to palette: {self.bl_props.blends[self.bl_props.bl_idx].blend_name}', icon='LINK_BLEND') + self.bl_props + layout.prop(self, 'mat_scope') + layout.prop(self, 'mat_type') + col = layout.column(align=True) + col.label(text='Does not check if material exists in target blend', icon='INFO') + col.label(text='Just change source filepath if different version of same source name is found') + # col.label(text='version of same source name is found') + + + def execute(self, context): + if self.mat_scope == 'SELECTED' and not context.selected_objects: + self.report({'ERROR'}, 'No selected objects') + return {"CANCELLED"} + + bl_props = context.scene.bl_palettes_props + bl = bl_props.blends[bl_props.bl_idx] + bl_name, bl_path = bl.blend_name, bl.blend_path + + if not Path(bl_path).exists(): + self.report({'ERROR'}, f'Current selected blend source seem unreachable, try to refresh\ninvalid path: {bl_path}') + return {"CANCELLED"} + + reversion = re.compile(r'\d{2,4}$') # version padding from 2 to 4 + bl_relpath = bpy.path.relpath(bl_path) + + if self.mat_scope == 'SELECTED': + pool = [] + for o in context.selected_objects: + for m in o.data.materials: + pool.append(m) + + elif self.mat_scope == 'ALL': + pool = [m for m in bpy.data.materials] + + ct = 0 + for m in pool: + if not m.library: + continue + if self.mat_type == 'GP' and not m.is_grease_pencil: + continue + if self.mat_type == 'OBJ' and m.is_grease_pencil: + continue + + cur_fp = m.library.filepath + if not cur_fp: + print(f'! {m.name} has an empty library filepath !') + continue + + p_cur_fp = Path(cur_fp) + if p_cur_fp.stem == bl_name: + continue # already good + + if reversion.sub('', p_cur_fp.stem) != reversion.sub('', bl_name): + continue # not same stem base + + # Same stem without version, can update to this one + print(f'{m.name}: {p_cur_fp} >> {bl_relpath}') + ct += 1 + m.library.filepath = bl_relpath + + if ct: + self.report({'INFO'}, f'{ct} material link path updated') + else: + self.report({'WARNING'}, 'No material path updated') + return {"FINISHED"} + #--- UI LIST class GPTB_UL_blend_list(UIList): @@ -452,6 +559,8 @@ GPTB_UL_object_list, GPTB_PG_palette_settings, GPTB_OT_import_obj_palette, +# GPTB_OT_palette_version_update, + # TEST_OT_import_obj_palette_test, ) diff --git a/UI_tools.py b/UI_tools.py index 4dda1fd..55f6d75 100644 --- a/UI_tools.py +++ b/UI_tools.py @@ -3,6 +3,13 @@ from .utils import get_addon_prefs import bpy from pathlib import Path from bpy.types import Panel +from bpy.props import ( + IntProperty, + BoolProperty, + StringProperty, + FloatProperty, + EnumProperty, + ) ## UI in properties @@ -420,6 +427,8 @@ def palettes_path_ui(self, context): col.prop(pl_prop, 'custom_dir', text='Custom Dir') # if not pl_prop.custom_dir or pl_prop.show_path: # col.prop(pl_prop, 'custom_dir', text='Custom Dir') + + # col.operator('gptb.palette_version_update', text='Update Palette Version') # when update is ready def palettes_lists_ui(self, context, popup=False): @@ -589,7 +598,16 @@ class GPTB_PT_tools_grease_pencil_interpolate(Panel): def draw(self, context): layout = self.layout - settings = context.tool_settings.gpencil_interpolate + layout.use_property_split = True + # settings = context.tool_settings.gpencil_interpolate # old 2.92 global settings + ## access active tool settings + # settings = context.workspace.tools[0].operator_properties('gpencil.interpolate') + settings = context.workspace.tools.from_space_view3d_mode('PAINT_GPENCIL').operator_properties('gpencil.interpolate') + + ## custom curve access (still in gp interpolate tools) + interpolate_settings = context.tool_settings.gpencil_interpolate + # ex : interpolate_settings.interpolation_curve.curves[0].points[1].location + col = layout.column(align=True) col.label(text="Interpolate Strokes") @@ -599,29 +617,62 @@ class GPTB_PT_tools_grease_pencil_interpolate(Panel): col = layout.column(align=True) col.label(text="Options:") - col.prop(settings, "interpolate_all_layers") - + # col.prop(settings, "interpolate_all_layers") # now the enum "layers" gpd = context.gpencil_data if gpd.use_stroke_edit_mode: col.prop(settings, "interpolate_selected_only") + col.prop(settings, "layers") + col.prop(settings, "flip") + col.prop(settings, "smooth_factor") + col.prop(settings, "smooth_steps") + + '''## Sequence Options + seq_settings = context.window_manager.operators.get('GPENCIL_OT_interpolate_sequence') col = layout.column(align=True) col.label(text="Sequence Options:") - col.prop(settings, "step") - col.prop(settings, "type") - if settings.type == 'CUSTOM': - # TODO: Options for loading/saving curve presets? - col.template_curve_mapping(settings, "interpolation_curve", brush=True, - use_negative_slope=True) - elif settings.type != 'LINEAR': - col.prop(settings, "easing") + if not seq_settings: + # col.label(text='Launch Interpolate Sequence Once') + # col.operator('gpencil.interpolate_sequence',text='Interpolate Sequence Once') + col.label(text='Interpolate sequence', icon='INFO') + col.label(text='must be launched') + col.label(text="once per session") + col.label(text="to expose it's properties") + return - if settings.type == 'BACK': - layout.prop(settings, "back") - elif settings.type == 'ELASTIC': + col.prop(seq_settings, "step") + col.prop(seq_settings, "layers") + col.prop(seq_settings, "interpolate_selected_only") + col.prop(seq_settings, "flip") + col.prop(seq_settings, "smooth_factor") + col.prop(seq_settings, "smooth_steps") + col.prop(seq_settings, "type") + if seq_settings.type == 'CUSTOM': + # TODO: Options for loading/saving curve presets? + col.template_curve_mapping(interpolate_settings, "interpolation_curve", brush=True, + use_negative_slope=True) + elif seq_settings.type != 'LINEAR': + col.prop(seq_settings, "easing") + + if seq_settings.type == 'BACK': + layout.prop(seq_settings, "back") + elif seq_settings.type == 'ELASTIC': sub = layout.column(align=True) - sub.prop(settings, "amplitude") - sub.prop(settings, "period") + sub.prop(seq_settings, "amplitude") + sub.prop(seq_settings, "period") + ''' + + +## recreate property group from operator options +# inspect context.window_manager.operators['GPENCIL_OT_interpolate_sequence'] +# separate options from single interpolation and sequence interpolation + +# class GPTB_PG_interpolate_sequence_prop(bpy.types.PropertyGroup): +# interpolate_selected_only : BoolProperty( +# name="Selected Only", +# description="", +# default=True, +# options={'HIDDEN'}) def interpolate_header_ui(self, context): layout = self.layout diff --git a/__init__.py b/__init__.py index dba2bde..c62a53f 100755 --- a/__init__.py +++ b/__init__.py @@ -15,7 +15,7 @@ bl_info = { "name": "GP toolbox", "description": "Tool set for Grease Pencil in animation production", "author": "Samuel Bernou, Christophe Seux", -"version": (1, 9, 0), +"version": (1, 9, 1), "blender": (2, 91, 0), "location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties", "warning": "", @@ -47,6 +47,7 @@ from . import OP_brushes from . import OP_file_checker from . import OP_copy_paste from . import OP_realign +from . import OP_flat_reproject from . import OP_depth_move from . import OP_key_duplicate_send from . import OP_layer_manager @@ -574,6 +575,7 @@ class GPTB_prefs(bpy.types.AddonPreferences): col.prop(self.fixprops, 'set_slider_n_sync') col.prop(self.fixprops, 'check_front_axis') col.prop(self.fixprops, 'check_placement') + col.prop(self.fixprops, 'set_gp_use_lights_off') col.prop(self.fixprops, 'set_pivot_median_point') col.prop(self.fixprops, 'disable_guide') col.prop(self.fixprops, 'list_disabled_anim') @@ -686,6 +688,7 @@ def register(): OP_brushes.register() OP_cursor_snap_canvas.register() OP_copy_paste.register() + OP_flat_reproject.register() OP_realign.register() OP_depth_move.register() OP_key_duplicate_send.register() @@ -728,6 +731,7 @@ def unregister(): OP_key_duplicate_send.unregister() OP_depth_move.unregister() OP_realign.unregister() + OP_flat_reproject.unregister() OP_copy_paste.unregister() OP_cursor_snap_canvas.unregister() OP_brushes.unregister() diff --git a/properties.py b/properties.py index 6846a88..7784dfc 100755 --- a/properties.py +++ b/properties.py @@ -60,6 +60,11 @@ class GP_PG_FixSettings(bpy.types.PropertyGroup): description="Toggle on the use of show slider and sync range", default=True, options={'HIDDEN'}) + set_gp_use_lights_off : BoolProperty( + name="Set Off Use lights On All Gpencil Objects", + description="Uncheck Use lights on all grease pencil objects\nAt object level, not layer level (Object properties > Visibility > GP uselight)", + 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)",