From d9c9551112a2edb078fdde36bd81d563d8dfdee8 Mon Sep 17 00:00:00 2001 From: mmmrqs Date: Tue, 27 Sep 2022 19:15:05 -0300 Subject: [PATCH] Improvements to help with switching areas and a few bug fixes --- __init__.py | 5 +- bl_ui_drag_panel.py | 26 +++++++++-- bl_ui_draw_op.py | 108 +++++++++++++++++++++++++------------------ bl_ui_slider.py | 34 +++++++++++--- bl_ui_widget_demo.py | 54 ++++++++++++++++++---- demo_panel_op.py | 73 ++++++++++++++++++++++++----- prefs.py | 21 +++++---- 7 files changed, 233 insertions(+), 88 deletions(-) diff --git a/__init__.py b/__init__.py index c2714a8..a9799e0 100644 --- a/__init__.py +++ b/__init__.py @@ -20,7 +20,7 @@ bl_info = {"name": "BL UI Widgets", "description": "UI Widgets to draw in the 3D view", "author": "Marcelo M. Marques (fork of Jayanam's original project)", - "version": (1, 0, 2), + "version": (1, 0, 3), "blender": (2, 80, 75), "location": "View3D > side panel ([N]), [BL_UI_Widget] tab", "support": "COMMUNITY", @@ -35,6 +35,9 @@ # Note: Because the way Blender's Preferences window displays the Addon version number, # I am forced to keep this file in sync with the greatest version number of all modules. +# v1.0.3 (09.25.2021) - by Marcelo M. Marques +# Chang: updated version to keep this file in sync + # v1.0.2 (10.31.2021) - by Marcelo M. Marques # Chang: updated version with improvements and some clean up diff --git a/bl_ui_drag_panel.py b/bl_ui_drag_panel.py index 44235c0..026c055 100644 --- a/bl_ui_drag_panel.py +++ b/bl_ui_drag_panel.py @@ -20,7 +20,7 @@ bl_info = {"name": "BL UI Widgets", "description": "UI Widgets to draw in the 3D view", "author": "Marcelo M. Marques (fork of Jayanam's original project)", - "version": (1, 0, 1), + "version": (1, 0, 2), "blender": (2, 80, 75), "location": "View3D > viewport area", "support": "COMMUNITY", @@ -32,6 +32,11 @@ # --- ### Change log +# v1.0.2 (09.25.2022) - by Marcelo M. Marques +# Added: 'quadview' property to indicate whether panel is opened in the QuadView mode or not +# Chang: Logic to save panel screen position only when not in QuadView mode. In some future release this may get improved +# to save the position in both cases, but to distinct sets of variables in the session's saved data dictionary + # v1.0.1 (09.20.2021) - by Marcelo M. Marques # Chang: just some pep8 code formatting @@ -105,6 +110,7 @@ def __init__(self, x, y, width, height): self._has_shadow = False # Indicates whether a shadow must be drawn around the panel self._anchored = False # Indicates whether panel can be dragged around the viewport or not + self._quadview = False # Indicates whether panel is opened in the QuadView mode or not self.__drag_offset_x = 0 self.__drag_offset_y = 0 @@ -118,6 +124,14 @@ def anchored(self): def anchored(self, value): self._anchored = value + @property + def quadview(self): + return self._quadview + + @quadview.setter + def quadview(self, value): + self._quadview = value + def add_widget(self, widget): self.widgets.append(widget) @@ -137,7 +151,9 @@ def child_widget_focused(self, x, y): return False def save_panel_coords(self, x, y): - # Update the new coord values in the session's saved data dictionary. + # Update the new coord values in the session's saved data dictionary, only when not in QuadView mode + if self.quadview: + return None # Note: Because of the scaling logic it was necessary to make this weird correction math below new_x = self.over_scale(x) new_y = self.over_scale(y) @@ -172,13 +188,13 @@ def set_location(self, x, y): # Overrides base class function def mouse_down(self, event, x, y): - if self.child_widget_focused(x, y): - # Means the focus is on some sub-widget (e.g.: a button) - return False if self.anchored: # Means the panel is not draggable return False if self.is_in_rect(x, y): + if self.child_widget_focused(x, y): + # Means the focus is on some sub-widget (e.g.: a button) + return False # When panel is disabled, just ignore the click if self._is_enabled: height = self.get_area_height() diff --git a/bl_ui_draw_op.py b/bl_ui_draw_op.py index 77466c0..b9d66a8 100644 --- a/bl_ui_draw_op.py +++ b/bl_ui_draw_op.py @@ -20,7 +20,7 @@ bl_info = {"name": "BL UI Widgets", "description": "UI Widgets to draw in the 3D view", "author": "Marcelo M. Marques (fork of Jayanam's original project)", - "version": (1, 0, 2), + "version": (1, 0, 3), "blender": (2, 80, 75), "location": "View3D > viewport area", "support": "COMMUNITY", @@ -32,6 +32,10 @@ # --- ### Change log +# v1.0.3 (09.25.2021) - by Marcelo M. Marques +# Added: Many improvements to help with identifying when the panel must be automatically terminated either due to a change +# in region or for any other issues. These changes may help with making the code more stable and reliable. + # v1.0.2 (10.31.2021) - by Marcelo M. Marques # Added: 'region_pointer' class level property to indicate the region in which the drag_panel operator instance has been invoked(). # Added: 'valid_modes' property to indicate the 'bpy.context.mode' valid values for displaying the panel. @@ -79,8 +83,8 @@ class BL_UI_OT_draw_operator(Operator): def __init__(self): self.widgets = [] self.valid_modes = [] - # self.__draw_handle = None # <-- Was like this before implementing the 'lost handler detection logic' - # self.__draw_events = None # (ditto) + # self.__draw_handle = None # <-- Was like this before I had implemented the 'lost handler detection logic' + # self.__draw_events = None # <--(ditto) self.__finished = False self.__informed = False @@ -120,9 +124,9 @@ def invoke(self, context, event): # Avoid "internal error: modal gizmo-map handler has invalid area" terminal messages, after maximizing the viewport, # by switching the workspace back and forth. Not pretty, but at least it avoids the terminal output getting spammed. current = context.workspace - other = [ws for ws in bpy.data.workspaces if ws != current] - if other: - bpy.context.window.workspace = other[0] + others = [ws for ws in bpy.data.workspaces if ws != current] + if others: + bpy.context.window.workspace = others[0] bpy.context.window.workspace = current # ----------------------------------------------------------------- BL_UI_OT_draw_operator.region_pointer = context.region.as_pointer() @@ -165,7 +169,7 @@ def modal(self, context, event): if self.handle_widget_events(event, area, region): return {'RUNNING_MODAL'} - # Not using this escape option, but left it here for documentation purpose + # Not using any escape option, but left it here for documentation purposes # if event.type in {"ESC"}: # self.finish() @@ -178,18 +182,24 @@ def valid_scenario(self, context, event): self.finish() valid = False elif not (area and region): + if self.terminate_execution(area, region, event): + self.finish() # Return but do not finish valid = False - elif self.terminate_execution(area, region): + elif self.terminate_execution(area, region, event): area.tag_redraw() self.finish() valid = False elif event.type != 'TIMER': # Check whether it is drawing on the same region where the panel was initially opened - mouse_region = get_quadview_index(context, event.mouse_x, event.mouse_y)[0] - if mouse_region is None or mouse_region.as_pointer() != self.get_region_pointer(): - # Not the same region, so skip handling events at this time + mouse_region, abend = get_region(context, event.mouse_x, event.mouse_y) + if abend: + self.finish() valid = False + else: + if mouse_region is None or mouse_region.as_pointer() != self.get_region_pointer(): + # Not the same region, so skip handling events at this time, but do not finish + valid = False return (valid, area, region) def handle_widget_events(self, event, area, region): @@ -216,7 +226,7 @@ def suppress_rendering(self, area, region): # This might be overriden by one same named function in the derived (child) class return False - def terminate_execution(self, area, region): + def terminate_execution(self, area, region, event): # This might be overriden by one same named function in the derived (child) class return False @@ -231,27 +241,31 @@ def finish(self): self.unregister_handlers(bpy.context) self.on_finish(bpy.context) + def cancel(self, context): + # Called when Blender cancels the modal operator + self.finish() + # Draw handler to paint onto the screen def draw_callback_px(self, op, context): # Check whether handles are still valid if not BL_UI_OT_draw_operator.valid_handler(): - # -- personalized criteria for the Remote Control panel addon -- - # This is a temporary workaround till I figure out how to signal to - # the N-panel coding that the remote control panel has been finished. - bpy.context.scene.var.RemoVisible = False - bpy.context.scene.var.btnRemoText = "Open Remote Control" - # -- end of the personalized criteria for the given addon -- + try: + # -- personalized criteria for the Remote Control panel addon -- + # This is a temporary workaround till I figure out how to signal to + # the N-panel coding that the remote control panel has been finished. + bpy.context.scene.var.RemoVisible = False + bpy.context.scene.var.btnRemoText = "Open Remote Control" + # -- end of the personalized criteria for the given addon -- + except: + pass return - # Check whether it is drawing on the same region where the panel was initially opened - for region in context.area.regions: - if region.type == 'WINDOW': - if context.region == region: - if region.as_pointer() != self.get_region_pointer(): - # Not the same region, so skip drawing there - return - break - + for region in [region for region in context.area.regions if region.type == 'WINDOW']: + if context.region == region: + if region.as_pointer() != self.get_region_pointer(): + # Not the same region, so skip drawing there + return + break # This is to detect when user moved into an undesired 'bpy.context.mode' # and it will check also the programmer's defined suppress_rendering function if valid_display_mode(self.valid_modes, self.suppress_rendering): @@ -261,22 +275,26 @@ def draw_callback_px(self, op, context): # --- ### Helper functions -def get_quadview_index(context, x, y): - for area in context.screen.areas: - if area.type != 'VIEW_3D': - continue - is_quadview = (len(area.spaces.active.region_quadviews) == 0) - i = -1 - for region in area.regions: - if region.type == 'WINDOW': - i += 1 +def get_region(context, x, y): + abend = False + try: + for area in [area for area in context.screen.areas if area.type == 'VIEW_3D']: + for region in [region for region in area.regions if region.type == 'WINDOW']: if (x >= region.x and y >= region.y and x < region.width + region.x and y < region.height + region.y): - return (region, area.spaces.active, None if is_quadview else i) - return (None, None, None) - + return (region, abend) + except Exception as e: + if __package__.find(".") != -1: + package = __package__[0:__package__.find(".")] + else: + package = __package__ + print("**WARNING** " + package + " addon issue:") + print(" +--> unexpected result in 'get_region' function of bl_ui_draw_op.py module!") + print(" " + e) + abend = True + return (None, abend) def get_3d_area_and_region(prefs=None): abend = False @@ -303,14 +321,12 @@ def get_3d_area_and_region(prefs=None): # if region.type == 'WINDOW': # if region.as_pointer() == BL_UI_OT_draw_operator.region_pointer: # return (area, region, abend) - + # for screen in bpy.data.screens: - for area in screen.areas: - if area.type == 'VIEW_3D': - for region in area.regions: - if region.type == 'WINDOW': - if region.as_pointer() == BL_UI_OT_draw_operator.region_pointer: - return (area, region, abend) + for area in [area for area in screen.areas if area.type == 'VIEW_3D']: + for region in [region for region in area.regions if region.type == 'WINDOW']: + if region.as_pointer() == BL_UI_OT_draw_operator.region_pointer: + return (area, region, abend) except Exception as e: if __package__.find(".") != -1: package = __package__[0:__package__.find(".")] diff --git a/bl_ui_slider.py b/bl_ui_slider.py index f538365..4a37281 100644 --- a/bl_ui_slider.py +++ b/bl_ui_slider.py @@ -20,7 +20,7 @@ bl_info = {"name": "BL UI Widgets", "description": "UI Widgets to draw in the 3D view", "author": "Marcelo M. Marques (fork of Jayanam's original project)", - "version": (1, 0, 2), + "version": (1, 0, 3), "blender": (2, 80, 75), "location": "View3D > viewport area", "support": "COMMUNITY", @@ -32,15 +32,21 @@ # --- ### Change log +# v1.0.3 (09.25.2022) - by Marcelo M. Marques +# Added: 'is_editable' property to indicate if, for the 'NUMBER_SLIDE' style, the user is allowed to change the value by text editing. +# Chang: 'slider_mouse_up_func' function to prevent text editing per property documented above, and gave more flexibility to its logic by +# adding calls to both the 'value_changed_func' and 'update_self_value' functions so user has a final chance to override the changes. +# Fixed: Small issue in the 'calc_slider_bar' function that in some cases would return a float value when an integer was expected. + # v1.0.2 (10.31.2021) - by Marcelo M. Marques # Added: 'valid_modes' parm in the 'init' function and a call to 'super().init_mode' so we get everything correctly initialized. -# Chang: improved reliability on 'mouse_exit' and 'button_mouse_down' overridable functions by conditioning the returned value +# Chang: Improved reliability on 'mouse_exit' and 'button_mouse_down' overridable functions by conditioning the returned value. # v1.0.1 (09.20.2021) - by Marcelo M. Marques -# Chang: just some pep8 code formatting +# Chang: Just some pep8 code formatting. # v1.0.0 (09.01.2021) - by Marcelo M. Marques -# Added: Logic to scale the slider according to both Blender's ui scale configuration and this addon 'preferences' setup +# Added: Logic to scale the slider according to both Blender's ui scale configuration and this addon 'preferences' setup. # Added: 'outline_color' property to allow different color on the slider outline (value is standard color tuple). # Added: 'roundness' property to allow the slider to be painted with rounded corners, # same as that property available in Blender's user themes and it works together with 'rounded_corners' below. @@ -101,6 +107,7 @@ def __init__(self, x, y, width, height): self._step = 1 # Step size to increase/decrease the displayed value (in precision units) self._unit = "" # Unit indicator for the displayed value self._max_input_chars = 20 # Maximum number of characters to be input by the textbox + self._is_editable = True # Indicates whether the value can be changed by text editing self._text_margin = 16 # Slider text left/right margins (when 'NOT' in Textbox edit mode) self._text_size = None # Slider text size @@ -354,6 +361,14 @@ def max_input_chars(self): def max_input_chars(self, value): self._max_input_chars = value + @property + def is_editable(self): + return self._is_editable + + @is_editable.setter + def is_editable(self, value): + self._is_editable = value + @property def text(self): return self._text @@ -463,7 +478,7 @@ def textbox_str_value(self, value): str_value = str_value[:len(str_value) - 2] if str_value[-2:] == ".0" else str_value return str_value - def update_self_value(self, value, mode): + def update_self_value(self, value, mode='DEFAULT'): update_value = value if self._min_value is not None: update_value = self._min_value if update_value < self._min_value else update_value @@ -496,7 +511,7 @@ def calc_slider_bar(self, value): percentage = 0 if percentage < 0 else percentage percentage = 1 if percentage > 1 else percentage slider_bar_width = int(round(self.slider.width * percentage)) - slider_bar_pos_x = self.over_scale(self.slider.x_screen + slider_bar_width - 1) + slider_bar_pos_x = int(self.over_scale(self.slider.x_screen + slider_bar_width - 1)) return [slider_bar_width, slider_bar_pos_x] # Overrides base class function @@ -906,6 +921,13 @@ def slider_mouse_down_func(self, widget, event, x, y): def slider_mouse_up_func(self, widget, event, x, y): self.__is_dragging = False if self.__mouse_moved: + if self._style == 'NUMBER_SLIDE': + # Calls user function to give it a chance to override the changes + self.value_changed_func(self, self._value) + self.update_self_value(self._value) + # Indicates that sliding mode has finished + self.set_exclusive_mode(None) + elif not self._is_editable: # Indicates that sliding mode has finished self.set_exclusive_mode(None) else: diff --git a/bl_ui_widget_demo.py b/bl_ui_widget_demo.py index 48c47d7..f8e28dd 100644 --- a/bl_ui_widget_demo.py +++ b/bl_ui_widget_demo.py @@ -20,7 +20,7 @@ bl_info = {"name": "BL UI Widgets", "description": "UI Widgets to draw in the 3D view", "author": "Marcelo M. Marques", - "version": (1, 0, 1), + "version": (1, 0, 2), "blender": (2, 80, 75), "location": "View3D > side panel ([N]), [BL_UI_Widget] tab", "support": "COMMUNITY", @@ -32,6 +32,12 @@ # --- ### Change log +# v1.0.2 (09.25.2022) - by Marcelo M. Marques +# Added: 'is_quadview_region' function to identify whether screen is in QuadView mode and if yes to return the corresponding area and region. +# Added: 'btnRemoTime' session variable to hold the clock time which is constantly updated by the 'terminate_execution' function in demo_panel_op.py +# so that the N-panel is more precisely informed about the remote panel status when it has to determine how to display the Open/Close button. +# Added: Logic to the 'execute' method of the Set_Demo_Panel class for better displaying the Open/Close button, per item described above. + # v1.0.1 (09.20.2021) - by Marcelo M. Marques # Chang: just some pep8 code formatting @@ -40,7 +46,20 @@ # --- ### Imports import bpy -from bpy.props import StringProperty, BoolProperty +import time + +from bpy.props import StringProperty, IntProperty, BoolProperty + +# --- ### Helper functions +def is_quadview_region(context): + """ Identifies whether screen is in QuadView mode and if yes returns the corresponding area and region + """ + for area in context.screen.areas: + if area.type == 'VIEW_3D': + if len(area.spaces.active.region_quadviews) > 0: + region = [region for region in area.regions if region.type == 'WINDOW'][3] + return (True, area, region) + return (False, None, None) # --- ### Properties @@ -53,6 +72,7 @@ class Variables(bpy.types.PropertyGroup): OpState6: bpy.props.BoolProperty(default=False) RemoVisible: bpy.props.BoolProperty(default=False) btnRemoText: bpy.props.StringProperty(default="Open Demo Panel") + btnRemoTime: IntProperty(default=0) def is_desired_mode(context=None): @@ -64,8 +84,10 @@ def is_desired_mode(context=None): 'EDIT_MESH', 'EDIT_CURVE', 'EDIT_SURFACE', 'EDIT_TEXT', 'EDIT_ARMATURE', 'EDIT_METABALL', 'EDIT_LATTICE', 'POSE', 'SCULPT', 'PAINT_WEIGHT', 'PAINT_VERTEX', 'PAINT_TEXTURE', 'PARTICLE', 'OBJECT', 'PAINT_GPENCIL', 'EDIT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', - Additional desired mode option (as of Blender 2.9): + Additional valid mode option (as of Blender 2.9): 'VERTEX_GPENCIL' + Additional valid mode options (as of Blender 3.2): + 'EDIT_CURVES', 'SCULPT_CURVES' """ desired_modes = ['OBJECT', 'EDIT_MESH', 'POSE', ] if context: @@ -90,13 +112,20 @@ def invoke(self, context, event): return self.execute(context) def execute(self, context): - if context.scene.var.RemoVisible: - context.scene.var.btnRemoText = "Open Demo Panel" + if context.scene.var.RemoVisible and int(time.time()) - context.scene.var.btnRemoTime <= 1: + context.scene.var.btnRemoText = "Open Remote Control" + context.scene.var.RemoVisible = False else: - context.scene.var.btnRemoText = "Close Demo Panel" - context.scene.var.objRemote = bpy.ops.object.dp_ot_draw_operator('INVOKE_DEFAULT') - - context.scene.var.RemoVisible = not context.scene.var.RemoVisible + context.scene.var.btnRemoText = "Close Remote Control" + context.scene.var.RemoVisible = True + is_quadview, area, region = is_quadview_region(context) + if is_quadview: + override = bpy.context.copy() + override["area"] = area + override["region"] = region + context.scene.var.objRemote = bpy.ops.object.dp_ot_draw_operator(override, 'INVOKE_DEFAULT') + else: + context.scene.var.objRemote = bpy.ops.object.dp_ot_draw_operator('INVOKE_DEFAULT') return {'FINISHED'} @@ -112,8 +141,13 @@ def poll(cls, context): def draw(self, context): if context.space_data.type == 'VIEW_3D' and is_desired_mode(): + remoteVisible = (context.scene.var.RemoVisible and int(time.time()) - context.scene.var.btnRemoTime <= 1) # -- remote control switch button - op = self.layout.operator(Set_Demo_Panel.bl_idname, text=context.scene.var.btnRemoText) + if remoteVisible: + op = self.layout.operator(Set_Demo_Panel.bl_idname, text="Close Remote Control") + else: + # Make sure the button starts turned off every time + op = self.layout.operator(Set_Demo_Panel.bl_idname, text="Open Remote Control") return None diff --git a/demo_panel_op.py b/demo_panel_op.py index caece98..f6b88ea 100644 --- a/demo_panel_op.py +++ b/demo_panel_op.py @@ -20,7 +20,7 @@ bl_info = {"name": "BL UI Widgets", "description": "UI Widgets to draw in the 3D view", "author": "Marcelo M. Marques (fork of Jayanam's original project)", - "version": (1, 0, 2), + "version": (1, 0, 3), "blender": (2, 80, 75), "location": "View3D > viewport area", "support": "COMMUNITY", @@ -32,6 +32,15 @@ # --- ### Change log +# v1.0.3 (09.25.2022) - by Marcelo M. Marques +# Added: Logic to 'on_invoke' function to identify when in QuadView mode and to position the remote panel appropriately so it +# does not get stuck lost out of visible region. +# Added: New statement necessary for the remote panel to stay open when called by Blender's menu. +# Added: Logic to 'terminate_execution' function to automatically close the panel if user switches between regular and QuadView display modes. +# Added: Logic to 'terminate_execution' function to keep a session variable updated with the clock time that will allow the modal operator +# to identify any odd situation in which the panel is no more active and must be automatically closed. +# Chang: Some notes and comments for better documentation + # v1.0.2 (10.31.2021) - by Marcelo M. Marques # Added: 'valid_modes' property to indicate the 'bpy.context.mode' valid values for displaying the panel. # Added: 'suppress_rendering' function that can be optionally used to control render bypass of the panel widget. @@ -46,6 +55,7 @@ # --- ### Imports import bpy +import time import os from bpy.types import Operator @@ -60,6 +70,8 @@ from .bl_ui_draw_op import BL_UI_OT_draw_operator from .bl_ui_drag_panel import BL_UI_Drag_Panel +from .bl_ui_widget_demo import is_quadview_region + class DP_OT_draw_operator(BL_UI_OT_draw_operator): # in: bl_ui_draw_op.py ## bl_idname = "object.dp_ot_draw_operator" @@ -67,7 +79,7 @@ class DP_OT_draw_operator(BL_UI_OT_draw_operator): # in: bl_ui_draw_op.py ## bl_description = "Operator for bl ui widgets" bl_options = {'REGISTER'} - # -- Blender interface methods quick documentation -- + # --- Blender interface methods quick documentation # def poll: checked before running the operator, which will never run when poll fails. # used to check if an operator can run, menu items will be greyed out and if key bindings should be ignored. # @@ -76,7 +88,7 @@ class DP_OT_draw_operator(BL_UI_OT_draw_operator): # in: bl_ui_draw_op.py ## # # def description: allows a dynamic tooltip that changes based on the context and operator parameters. # - # def draw: called to draw options, giving control over the layout. Without this, options will draw in the order they are defined. + # def draw: called to draw options, giving control over the layout. Without this, options will draw in the order they are defined. # # def modal: handles events which would normally access other operators, they keep running until they return FINISHED. # used for operators which continuously run, eg: fly mode, knife tool, circle select are all examples of modal operators. @@ -107,12 +119,13 @@ def __init__(self): # Possible valid mode options (as of Blender 2.8): # 'EDIT_MESH', 'EDIT_CURVE', 'EDIT_SURFACE', 'EDIT_TEXT', 'EDIT_ARMATURE', 'EDIT_METABALL', # 'EDIT_LATTICE', 'POSE', 'SCULPT', 'PAINT_WEIGHT', 'PAINT_VERTEX', 'PAINT_TEXTURE', 'PARTICLE', - # 'OBJECT', 'PAINT_GPENCIL', 'EDIT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', + # 'OBJECT', 'PAINT_GPENCIL', 'EDIT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL' # Additional valid mode option (as of Blender 2.9): # 'VERTEX_GPENCIL' - # - # Note: Leave it empty, e.g. self.valid_modes = {}, for no restriction to be applied. - + # Additional valid mode options (as of Blender 3.2): + # 'EDIT_CURVES', 'SCULPT_CURVES' + + # Note: Leave it empty, e.g. self.valid_modes = {}, for no restrictions to be applied. self.valid_modes = {'OBJECT', 'EDIT_MESH'} # From Preferences/Themes/User Interface/"State" @@ -122,8 +135,8 @@ def __init__(self): status_color = tuple(widget_style.inner_changed) + (0.3,) btnC = 0 # Element counter - btnS = 4 # Button separation (for the smaller ones) - btnG = 0 # Button gap (for the bigger ones) + btnS = 4 # Button separation (for the smallest ones) + btnG = 0 # Button gap (for the biggest ones) btnW = 56 # Button width btnH = 40 + btnS # Button height (takes 2 small buttons plus their separation) @@ -142,7 +155,7 @@ def __init__(self): self.button1.rounded_corners = (1, 1, 0, 0) self.button1.set_mouse_up(self.button1_click) self.button1.set_button_pressed(self.button1_pressed) - self.button1.description = "Press this button to unlock its brothers (after the button" + \ + self.button1.description = "Press this button to unlock its brothers (after the button " + \ "with a 'LOCK' caption has been pressed). {Let me " + \ "type a very large description here to showcase a " + \ "tooltip text automatically being wrapped around " + \ @@ -351,7 +364,28 @@ def on_invoke(self, context, event): self.panel.add_widgets(widgets_items) - self.panel.set_location(self.panel.x, self.panel.y) + self.panel.quadview, _, region = is_quadview_region(context) + + if self.panel.quadview and region: + # When in QuadView mode it has to be manually repositioned otherwise may get stuck out of region space + if __package__.find(".") != -1: + package = __package__[0:__package__.find(".")] + else: + package = __package__ + if bpy.context.preferences.addons[package].preferences.RC_UI_BIND: + # From Preferences/Interface/"Display" + ui_scale = bpy.context.preferences.view.ui_scale + else: + ui_scale = 1 + over_scale = bpy.context.preferences.addons[package].preferences.RC_SCALE + panX = int((region.width - (self.panel.width * ui_scale * over_scale)) / 2.0) + 1 + panY = self.panel.height + 10 - 1 # The '10' is just a spacing from region border + self.panel.set_location(panX, panY) + else: + self.panel.set_location(self.panel.x, self.panel.y) + + # This statement is necessary so that the remote panel stays open when called by Blender's menu + bpy.context.scene.var.RemoVisible = True # -- Helper function @@ -364,13 +398,28 @@ def suppress_rendering(self, area, region): ''' return False - def terminate_execution(self, area, region): + def terminate_execution(self, area, region, event): ''' This is a special case 'overriding function' to allow subclass control for terminating/closing the panel. Function is defined in class BL_UI_OT_draw_operator (bl_ui_draw_op.py) and available to be inherited here. If not included here the function in the superclass just returns 'False' and no termination is executed. When 'True" is returned below, the execution is auto terminated and the 'Remote Control' panel closes itself. ''' + if self.panel.quadview and area is None: + bpy.context.scene.var.RemoVisible = False + else: + if area.type == 'VIEW_3D': + # If user switches between regular and QuadView display modes, the panel is automatically closed + is_quadview = (len(area.spaces.active.region_quadviews) > 0) + if self.panel.quadview != is_quadview: + bpy.context.scene.var.RemoVisible = False + + if event.type == 'TIMER' and bpy.context.scene.var.RemoVisible: + # Update the remote panel "clock marker". This marker is used to keep track if the remote panel is + # actually opened and active. In the case that bpy.context.scene.var.RemoVisible state gets misleading + # the panel will be automatically closed when this clock marker has not been updated for more than 1 sec + bpy.context.scene.var.btnRemoTime = int(time.time()) + return (not bpy.context.scene.var.RemoVisible) # -- Button press handlers diff --git a/prefs.py b/prefs.py index 5d2d62e..8c901a5 100644 --- a/prefs.py +++ b/prefs.py @@ -20,7 +20,7 @@ bl_info = {"name": "BL UI Widgets", "description": "UI Widgets to draw in the 3D view", "author": "Marcelo M. Marques (fork of Jayanam's original project)", - "version": (1, 0, 2), + "version": (1, 0, 3), "blender": (2, 80, 75), "location": "View3D > viewport area", "support": "COMMUNITY", @@ -32,14 +32,17 @@ # --- ### Change log +# v1.0.3 (09.25.2022) - by Marcelo M. Marques +# Chang: Just small updates in some comments on the code + # v1.0.2 (10.31.2021) - by Marcelo M. Marques -# Chang: the logic that retrieves region.width of the 3d view screen which has the Remote Control +# Chang: The logic that retrieves region.width of the 3d view screen which has the Remote Control # v1.0.1 (09.20.2021) - by Marcelo M. Marques -# Chang: just some pep8 code formatting +# Chang: Just some pep8 code formatting # v1.0.0 (09.01.2021) - by Marcelo M. Marques -# Added: initial creation +# Added: Initial creation # --- ### Imports import bpy @@ -86,13 +89,13 @@ class BL_UI_Widget_Preferences(AddonPreferences): RC_POS_X: IntProperty( name="", - description="Remote Control panel position X from latest opened scene", + description="Remote Control panel position 'X' from latest opened scene", default=-10000 ) RC_POS_Y: IntProperty( name="", - description="Remote Control panel position Y from latest opened scene", + description="Remote Control panel position 'Y' from latest opened scene", default=-10000 ) @@ -147,7 +150,7 @@ def draw(self, context): panH = bpy.context.preferences.addons[__package__].preferences.RC_PAN_H # Panel height pos_x = int(round(bpy.context.scene.get("bl_ui_panel_saved_data")["panX"])) pos_y = int(round(bpy.context.scene.get("bl_ui_panel_saved_data")["panY"])) - # Note: Because of the scaling logic it was necessary to make this weird correction math below + # Note: Because of the scaling logic it was necessary to do this weird math correction below coords = "x: " + str(pos_x) + " " + \ "y: " + str(pos_y + int(panH * (self.over_scale(1) - 1))) + " " @@ -165,7 +168,7 @@ def draw(self, context): box.label(text=" Additional information and Acknowledge:") box.label(text="") box.label(text=" - This addon prepared and packaged by Marcelo M Marques (mmmrqs@gmail.com)") - box.label(text=" (upgrades at https://github.com/mmmrqs/bl_ui_widgets)") + box.label(text=" (updates at https://github.com/mmmrqs/bl_ui_widgets)") box.label(text=" - BL UI Widgets original project by Jayanam (jayanam.games@gmail.com)") box.label(text=" (download it from https://github.com/jayanam/bl_ui_widgets)") box.label(text="") @@ -209,6 +212,8 @@ def execute(self, context): bpy.context.preferences.addons[__package__].preferences.RC_POS_Y = panY bpy.context.scene.get("bl_ui_panel_saved_data")["panX"] = panX bpy.context.scene.get("bl_ui_panel_saved_data")["panY"] = panY + # These two next statements cause the remote panel to be closed by the BL_UI_OT_draw_operator modal class + # and changes the operator's label on the N-Panel UI accordingly to indicate panel can be opened again. bpy.context.scene.var.RemoVisible = False bpy.context.scene.var.btnRemoText = "Open Remote Control" except Exception as e: