From e0a50ea49a94264a4535ed467f54d123e4198750 Mon Sep 17 00:00:00 2001 From: Pullusb Date: Thu, 13 Oct 2022 00:09:12 +0200 Subject: [PATCH] ui redraw on UIlist actions and code cleanup 2.0.9 - fix: prefix/suffix UIlist actions trigger UI redraw to see changes live - changed: PropertyGroups are now registered in their own file - code: cleanup --- CHANGELOG.md | 6 ++ OP_layer_namespace.py | 157 ++++++++++++++++++++++++++++++++++++++++ OP_pseudo_tint.py | 13 ++-- UI_tools.py | 14 ++-- __init__.py | 163 +++--------------------------------------- functions.py | 47 +++++------- properties.py | 20 +++++- utils.py | 4 ++ 8 files changed, 226 insertions(+), 198 deletions(-) create mode 100644 OP_layer_namespace.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 7db6ff2..b7a2842 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +2.0.9 + +- fix: prefix/suffix UIlist actions trigger UI redraw to see changes live +- changed: PropertyGroups are now registered in their own file +- code: cleanup + 2.0.8 - changed: suffix as UIlist in prefs, diff --git a/OP_layer_namespace.py b/OP_layer_namespace.py new file mode 100644 index 0000000..c9ec040 --- /dev/null +++ b/OP_layer_namespace.py @@ -0,0 +1,157 @@ + +import bpy +import re +from .utils import get_addon_prefs +from .functions import redraw_ui + +class GPTB_OT_add_namespace_entry(bpy.types.Operator): + bl_idname = "gptb.add_namespace_entry" + bl_label = "Add Namespace Entry" + bl_description = "Add item in list" + bl_options = {'REGISTER', 'INTERNAL'} + + idx : bpy.props.IntProperty() + new : bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'}) + propname : bpy.props.StringProperty(default='prefixes', options={'SKIP_SAVE'}) + + def invoke(self, context, event): + self.pg = getattr(get_addon_prefs(), self.propname) + self.proptype = self.propname[:-2] + ## Basic: + # self.pg.namespaces.add() + # return {'FINISHED'}# can just add empty entry and leave... + if self.new: + self.pg.namespaces.add() + self.idx = len(self.pg.namespaces) - 1 + return context.window_manager.invoke_props_dialog(self, width=450) + + def draw(self, context): + layout = self.layout + # layout.use_property_split = True + item = self.pg.namespaces[self.idx] + layout.label(text=f'Enter {self.proptype.title()}:', icon='INFO') + layout.prop(item, 'tag', text=self.proptype.title()) + if item.tag and not re.match(r'^[A-Z]{2}$', item.tag): + layout.label(text=f'{self.propname.title()} are preferably two capital letter (ex: CO)', icon='ERROR') + + layout.separator() + layout.label(text='Provide a name (Optional):', icon='INFO') + layout.prop(item, 'name') + + def execute(self, context): + item = self.pg.namespaces[self.idx] + ## Here can perform post add checks + # (check for duplicate ?) + # all_prefix = [n.tag for i, n in enumerate(self.pg.namespaces) if i != self.pg.idx] + + if self.new: + # in case of new addition, remove just added if nothing specified + if not item.tag and not item.name: + self.pg.namespaces.remove(self.idx) + + redraw_ui() + return {'FINISHED'} + +class GPTB_OT_remove_namespace_entry(bpy.types.Operator): + bl_idname = "gptb.remove_namespace_entry" + bl_label = "Remove Namespace Entry" + bl_description = "Remove item in list" + bl_options = {'REGISTER', 'INTERNAL'} + + propname : bpy.props.StringProperty(default='prefixes', options={'SKIP_SAVE'}) + + def execute(self, context): + self.pg = getattr(get_addon_prefs(), self.propname) + entry_count = len(self.pg.namespaces) + if not entry_count: + return {'CANCELLED'} + # check if index is out of range + if not (0 <= self.pg.idx < entry_count): + self.report({"ERROR"}, 'Must select an entry to remove it') + return {'CANCELLED'} + + item = self.pg.namespaces[self.pg.idx] + if item.is_project: + self.report({"ERROR"}, 'Cannot remove a prefix that is defined by project, hide it instead') + return {'CANCELLED'} + + self.pg.namespaces.remove(self.pg.idx) + self.pg.idx -= 1 + redraw_ui() + return {'FINISHED'} + +class GPTB_OT_move_item(bpy.types.Operator): + bl_idname = "gptb.move_item" + bl_label = "Move Item" + bl_description = "Move item in list up or down" + bl_options = {'REGISTER', 'INTERNAL'} + + # direction : bpy.props.IntProperty(default=1) + direction : bpy.props.EnumProperty( + items=( + ('UP', 'Move Up', 'Move up'), + ('DOWN', 'Move down', 'Move down'), + ), + default='UP', + + ) + propname : bpy.props.StringProperty() + + def execute(self, context): + pg = getattr(get_addon_prefs(), self.propname) + uilist = pg.namespaces + index = pg.idx + + neighbor = index + (-1 if self.direction == 'UP' else 1) + uilist.move(neighbor, index) + list_length = len(uilist) - 1 # (index starts at 0) + new_index = index + (-1 if self.direction == 'UP' else 1) + list_index = max(0, min(new_index, list_length)) + + setattr(pg, 'idx', list_index) + redraw_ui() + return {'FINISHED'} + +class GPTB_UL_namespace_list(bpy.types.UIList): + + # show_desc : BoolProperty(name="Show Description", default=True, + # description="Display Description") + + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + # self.use_filter_show = True # force open/close the search feature + # prefs = get_addon_prefs() + # split = layout.split(align=False, factor=0.3) + row = layout.row() + hide_ico = 'HIDE_ON' if item.hide else 'HIDE_OFF' + source_ico = 'NETWORK_DRIVE' if item.is_project else 'USER' # BLANK1 + + row.label(text='', icon=source_ico) + row.prop(item, 'hide', text='', icon=hide_ico, invert_checkbox=True) + subrow = row.row(align=True) + subrow.prop(item, 'tag', text='') + subrow.prop(item, 'name', text='') + subrow.enabled = not item.is_project + + # row = layout.split(align=False) + # row.label(text=item.prefix) + # row.label(text=item.name) + + # if self.show_desc: + # row.label(text=item.description) + # row.operator('sbam.open_online_repo', text='', icon='URL') + +classes = ( + ## layer name management + GPTB_OT_add_namespace_entry, + GPTB_OT_remove_namespace_entry, + GPTB_OT_move_item, + GPTB_UL_namespace_list, +) + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + +def unregister(): + for cls in reversed(classes): + bpy.utils.unregister_class(cls) \ No newline at end of file diff --git a/OP_pseudo_tint.py b/OP_pseudo_tint.py index 9caef6f..eb44677 100644 --- a/OP_pseudo_tint.py +++ b/OP_pseudo_tint.py @@ -1,8 +1,6 @@ -from .utils import get_gp_objects, get_gp_datas, get_addon_prefs import bpy +from .utils import get_gp_datas, get_addon_prefs, translate_range -def translate_range(OldValue, OldMin, OldMax, NewMax, NewMin): - return (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin def get_hue_by_name(name, offset=0): ''' @@ -126,4 +124,11 @@ class GPT_OT_auto_tint_gp_layers(bpy.types.Operator): def invoke(self, context, event): self.autotint_offset = context.scene.gptoolprops.autotint_offset - return self.execute(context) \ No newline at end of file + return self.execute(context) + + +def register(): + bpy.utils.register_class(GPT_OT_auto_tint_gp_layers) + +def unregister(): + bpy.utils.unregister_class(GPT_OT_auto_tint_gp_layers) \ No newline at end of file diff --git a/UI_tools.py b/UI_tools.py index 6af9dd2..1287fa2 100644 --- a/UI_tools.py +++ b/UI_tools.py @@ -3,13 +3,6 @@ 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 @@ -274,9 +267,10 @@ class GPTB_PT_tint_layers(Panel): ## pseudo color layers # layout.separator() col = layout.column(align = True) - row = col.split(align=False, factor=0.63) - row.prop(context.scene.gptoolprops, 'autotint_offset') - row.prop(context.scene.gptoolprops, 'autotint_namespace') + # row = col.split(align=False, factor=0.63) + # row = col.row() + col.prop(context.scene.gptoolprops, 'autotint_offset', text='Hue Offset') + col.prop(context.scene.gptoolprops, 'autotint_namespace') col.operator("gp.auto_tint_gp_layers", icon = "COLOR").reset = False col.operator("gp.auto_tint_gp_layers", text = "Reset tint", icon = "COLOR").reset = True diff --git a/__init__.py b/__init__.py index 9f52a2e..d990dd3 100755 --- a/__init__.py +++ b/__init__.py @@ -4,7 +4,7 @@ bl_info = { "name": "GP toolbox", "description": "Tool set for Grease Pencil in animation production", "author": "Samuel Bernou, Christophe Seux", -"version": (2, 0, 8), +"version": (2, 0, 9), "blender": (3, 0, 0), "location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties", "warning": "", @@ -43,23 +43,22 @@ from . import OP_layer_picker from . import OP_layer_nav from . import OP_material_picker from . import OP_git_update +from . import OP_layer_namespace +from . import OP_pseudo_tint # from . import OP_eraser_brush # from . import TOOL_eraser_brush from . import handler_draw_cam from . import keymaps -from .OP_pseudo_tint import GPT_OT_auto_tint_gp_layers from . import UI_tools from .properties import ( GP_PG_ToolsSettings, GP_PG_FixSettings, - GP_PG_namespace_props, GP_PG_namespaces, ) - from bpy.props import (FloatProperty, BoolProperty, EnumProperty, @@ -109,145 +108,6 @@ def remap_on_save_update(self, context): # km, kmi = TOOL_eraser_brush.addon_keymaps[0] # kmi.active = self.use_precise_eraser - -class GPTB_OT_add_namespace_entry(bpy.types.Operator): - bl_idname = "gptb.add_namespace_entry" - bl_label = "Add Namespace Entry" - bl_description = "Add item in list" - bl_options = {'REGISTER', 'INTERNAL'} - - idx : bpy.props.IntProperty() - new : bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'}) - propname : bpy.props.StringProperty(default='prefixes', options={'SKIP_SAVE'}) - - def invoke(self, context, event): - self.pg = getattr(get_addon_prefs(), self.propname) - self.proptype = self.propname[:-2] - ## Basic: - # self.pg.namespaces.add() - # return {'FINISHED'}# can just add empty entry and leave... - if self.new: - self.pg.namespaces.add() - self.idx = len(self.pg.namespaces) - 1 - return context.window_manager.invoke_props_dialog(self, width=450) - - def draw(self, context): - import re - layout = self.layout - # layout.use_property_split = True - item = self.pg.namespaces[self.idx] - layout.label(text=f'Enter {self.proptype.title()}:', icon='INFO') - layout.prop(item, 'tag', text=self.proptype.title()) - if item.tag and not re.match(r'^[A-Z]{2}$', item.tag): - layout.label(text=f'{self.propname.title()} are preferably two capital letter (ex: CO)', icon='ERROR') - - layout.separator() - layout.label(text='Provide a name (Optional):', icon='INFO') - layout.prop(item, 'name') - - def execute(self, context): - item = self.pg.namespaces[self.idx] - ## Here can perform post add checks - # (check for duplicate ?) - # all_prefix = [n.tag for i, n in enumerate(self.pg.namespaces) if i != self.pg.idx] - - if self.new: - # in case of new addition, remove just added if nothing specified - if not item.tag and not item.name: - self.pg.namespaces.remove(self.idx) - - context.area.tag_redraw() - return {'FINISHED'} - -class GPTB_OT_remove_namespace_entry(bpy.types.Operator): - bl_idname = "gptb.remove_namespace_entry" - bl_label = "Remove Namespace Entry" - bl_description = "Remove item in list" - bl_options = {'REGISTER', 'INTERNAL'} - - propname : bpy.props.StringProperty(default='prefixes', options={'SKIP_SAVE'}) - - def execute(self, context): - self.pg = getattr(get_addon_prefs(), self.propname) - entry_count = len(self.pg.namespaces) - if not entry_count: - return {'CANCELLED'} - # check if index is out of range - if not (0 <= self.pg.idx < entry_count): - self.report({"ERROR"}, 'Must select an entry to remove it') - return {'CANCELLED'} - - item = self.pg.namespaces[self.pg.idx] - if item.is_project: - self.report({"ERROR"}, 'Cannot remove a prefix that is defined by project, hide it instead') - return {'CANCELLED'} - - self.pg.namespaces.remove(self.pg.idx) - self.pg.idx -= 1 - context.area.tag_redraw() - return {'FINISHED'} - -class GPTB_OT_move_item(bpy.types.Operator): - bl_idname = "gptb.move_item" - bl_label = "Move Item" - bl_description = "Move item in list up or down" - bl_options = {'REGISTER', 'INTERNAL'} - - # direction : bpy.props.IntProperty(default=1) - direction : bpy.props.EnumProperty( - items=( - ('UP', 'Move Up', 'Move up'), - ('DOWN', 'Move down', 'Move down'), - ), - default='UP', - - ) - propname : bpy.props.StringProperty() - - def execute(self, context): - pg = getattr(get_addon_prefs(), self.propname) - uilist = pg.namespaces - index = pg.idx - - neighbor = index + (-1 if self.direction == 'UP' else 1) - uilist.move(neighbor, index) - list_length = len(uilist) - 1 # (index starts at 0) - new_index = index + (-1 if self.direction == 'UP' else 1) - list_index = max(0, min(new_index, list_length)) - - setattr(pg, 'idx', list_index) - context.area.tag_redraw() - return {'FINISHED'} - -class GPTB_UL_namespace_list(bpy.types.UIList): - - # show_desc : BoolProperty(name="Show Description", default=True, - # description="Display Description") - - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - # self.use_filter_show = True # force open/close the search feature - # prefs = get_addon_prefs() - # split = layout.split(align=False, factor=0.3) - row = layout.row() - hide_ico = 'HIDE_ON' if item.hide else 'HIDE_OFF' - source_ico = 'NETWORK_DRIVE' if item.is_project else 'USER' # BLANK1 - - row.label(text='', icon=source_ico) - row.prop(item, 'hide', text='', icon=hide_ico, invert_checkbox=True) - subrow = row.row(align=True) - subrow.prop(item, 'tag', text='') - subrow.prop(item, 'name', text='') - subrow.enabled = not item.is_project - - # row = layout.split(align=False) - # row.label(text=item.prefix) - # row.label(text=item.name) - - # if self.show_desc: - # row.label(text=item.description) - # row.operator('sbam.open_online_repo', text='', icon='URL') - - class GPTB_prefs(bpy.types.AddonPreferences): bl_idname = __name__ @@ -883,23 +743,13 @@ class GPTB_set_env_settings(bpy.types.Operator): classes = ( - ## layer name management - GP_PG_namespace_props, - GP_PG_namespaces, - GPTB_OT_add_namespace_entry, - GPTB_OT_remove_namespace_entry, - GPTB_OT_move_item, - GPTB_UL_namespace_list, - - GP_PG_FixSettings, - GP_PG_ToolsSettings, GPTB_set_env_settings, GPTB_prefs, - GPT_OT_auto_tint_gp_layers, ) addon_modules = ( OP_helpers, + OP_pseudo_tint, OP_keyframe_jump, OP_file_checker, OP_breakdowner, @@ -916,6 +766,7 @@ addon_modules = ( OP_realign, OP_depth_move, OP_key_duplicate_send, + OP_layer_namespace, OP_layer_manager, OP_material_picker, OP_git_update, @@ -929,6 +780,8 @@ addon_modules = ( ) def register(): + # Register property group first + properties.register() for cls in classes: bpy.utils.register_class(cls) @@ -960,6 +813,8 @@ def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls) + + properties.unregister() del bpy.types.Scene.gptoolprops diff --git a/functions.py b/functions.py index 43f594c..d3c06af 100644 --- a/functions.py +++ b/functions.py @@ -9,24 +9,7 @@ import bmesh from .utils import get_gp_draw_plane, link_vert,gp_stroke_to_bmesh,draw_gp_stroke,remapping -def get_view_origin_position(): - #method 1 - from bpy_extras import view3d_utils - region = bpy.context.region - rv3d = bpy.context.region_data - view_loc = view3d_utils.region_2d_to_origin_3d(region, rv3d, (region.width/2.0, region.height/2.0)) - print("view_loc1", view_loc)#Dbg - - #method 2 - r3d = bpy.context.space_data.region_3d - view_loc2 = r3d.view_matrix.inverted().translation - print("view_loc2", view_loc2)#Dbg - if view_loc != view_loc2: print('there might be an errror when finding view coordinate') - - return view_loc - - -def to_bl_image(array,img): +def to_bl_image(array, img): # Write the result to Blender preview width = len(array[0]) height = len(array) @@ -56,7 +39,7 @@ def to_bl_image(array,img): image.pixels = output_pixels -def bm_angle_split(bm,angle) : +def bm_angle_split(bm, angle) : bm.verts.ensure_lookup_table() loop = link_vert(bm.verts[0],[bm.verts[0]]) splitted = [] @@ -83,7 +66,7 @@ def bm_angle_split(bm,angle) : return loops -def bm_uniform_density(bm,cam,max_spacing): +def bm_uniform_density(bm, cam, max_spacing): from bpy_extras.object_utils import world_to_camera_view as cam_space scene = bpy.context.scene ratio = scene.render.resolution_y/scene.render.resolution_x @@ -102,7 +85,7 @@ def bm_uniform_density(bm,cam,max_spacing): return bm -def gp_stroke_angle_split (frame,strokes,angle): +def gp_stroke_angle_split (frame, strokes, angle): strokes_info = gp_stroke_to_bmesh(strokes) new_strokes = [] @@ -125,7 +108,7 @@ def gp_stroke_angle_split (frame,strokes,angle): return new_strokes -def gp_stroke_uniform_density(cam,frame,strokes,max_spacing): +def gp_stroke_uniform_density(cam, frame, strokes, max_spacing): strokes_info = gp_stroke_to_bmesh(strokes) new_strokes = [] @@ -152,7 +135,7 @@ def gp_stroke_uniform_density(cam,frame,strokes,max_spacing): return new_strokes -def along_stroke(stroke,attr,length,min,max) : +def along_stroke(stroke, attr, length, min, max) : strokelen = len(stroke.points) for index,point in enumerate(stroke.points) : value = getattr(point,attr) @@ -164,9 +147,9 @@ def along_stroke(stroke,attr,length,min,max) : remap = remapping((strokelen-index)/length,0,1,min,max) setattr(point,attr,value*remap) -def randomise_points(mat,points,attr,strength) : +def randomise_points(mat, points, attr, strength) : for point in points : - if attr is 'co' : + if attr == 'co' : random_x = (rand()-0.5) random_y = (rand()-0.5) @@ -183,7 +166,7 @@ def randomise_points(mat,points,attr,strength) : -def zoom_to_object(cam,resolution,box,margin=0.01) : +def zoom_to_object(cam, resolution, box, margin=0.01) : min_x= box[0] max_x= box[1] min_y= box[2] @@ -208,7 +191,7 @@ def zoom_to_object(cam,resolution,box,margin=0.01) : bpy.context.scene.objects.link(zoom_cam) - resolution = (int(resolution[0]*factor),int(resolution[1]*factor)) + resolution = (int(resolution[0]*factor), int(resolution[1]*factor)) scene = bpy.context.scene @@ -235,7 +218,7 @@ def zoom_to_object(cam,resolution,box,margin=0.01) : -def set_viewport_matrix(width,height,mat): +def set_viewport_matrix(width, height, mat): from bgl import glViewport,glMatrixMode,GL_PROJECTION,glLoadMatrixf,Buffer,GL_FLOAT,glMatrixMode,GL_MODELVIEW,glLoadIdentity glViewport(0,0,width,height) @@ -376,4 +359,10 @@ def get_object_info(mesh_groups, order_list = []) : scene.render.resolution_y = res_y - return mesh_info, convert_table \ No newline at end of file + return mesh_info, convert_table + +def redraw_ui() -> None: + """Forces blender to redraw the UI.""" + for screen in bpy.data.screens: + for area in screen.areas: + area.tag_redraw() diff --git a/properties.py b/properties.py index 4b17400..1951b43 100755 --- a/properties.py +++ b/properties.py @@ -244,4 +244,22 @@ class GP_PG_namespace_props(PropertyGroup): class GP_PG_namespaces(PropertyGroup): idx : IntProperty(default=-1) - namespaces : bpy.props.CollectionProperty(type=GP_PG_namespace_props) \ No newline at end of file + namespaces : bpy.props.CollectionProperty(type=GP_PG_namespace_props) + +classes = ( + # Prefix/suiffix prefs prop group + GP_PG_namespace_props, + GP_PG_namespaces, + + ## General toolbox settings + GP_PG_FixSettings, + GP_PG_ToolsSettings, +) + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + +def unregister(): + for cls in reversed(classes): + bpy.utils.unregister_class(cls) \ No newline at end of file diff --git a/utils.py b/utils.py index 543714a..4bea059 100644 --- a/utils.py +++ b/utils.py @@ -16,6 +16,10 @@ import subprocess else : return layer.parent """ + +def translate_range(OldValue, OldMin, OldMax, NewMax, NewMin): + return (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin + def get_matrix(ob) : '''return a copy of the world_matrix, applied object matrix if its a bone''' if isinstance(ob, bpy.types.PoseBone) :