forked from jayanam/bl_ui_widgets
-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathbl_ui_draw_op.py
226 lines (193 loc) · 9.99 KB
/
bl_ui_draw_op.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# --- ### Header
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),
"blender": (2, 80, 75),
"location": "View3D > viewport area",
"support": "COMMUNITY",
"category": "3D View",
"warning": "Version numbering diverges from Jayanam's original project",
"doc_url": "https://github.com/mmmrqs/bl_ui_widgets",
"tracker_url": "https://github.com/mmmrqs/bl_ui_widgets/issues"
}
# --- ### Change log
# v1.0.1 (09.20.2021) - by Marcelo M. Marques
# Chang: just some pep8 code formatting
# v1.0.0 (09.01.2021) - by Marcelo M. Marques
# Added: 'terminate_execution' function that can be overriden by programmer in the subclass to control termination of the panel widget.
# Added: A call to a new 'handle_event_finalize' function in the widgets so that after finishing processing of all the widgets primary 'handle_event'
# function, a final pass is done one more time to wrap up any pending change of state for prior widgets already on the widgets list. Without
# this additional pass it was not possible to make widgets that keep a 'pressed' state in relation to others, to work alright.
# Added: New logic to finish execution of the widget whenever the user moves out of the 3D VIEW display mode (e.g. going into Sculpt editor).
# Added: New logic to only allow paint onto the screen if the user is in the 3D VIEW display mode.
# Added: New logic to detect when drawback handler gets lost (e.g. after opening other blender file) so that it can finish the operator without crashing.
# Chang: Disabled code that finished execution by pressing the ESC key, since the addon has control to finish it by a 'terminate_execution' function.
# Chang: Renamed some local variables so that those become restricted to this class only.
# --- ### Imports
import bpy
import sys
from bpy.types import Operator
class BL_UI_OT_draw_operator(Operator):
bl_idname = "object.bl_ui_ot_draw_operator"
bl_label = "bl ui widgets operator"
bl_description = "Operator for bl ui widgets"
bl_options = {'REGISTER'}
handlers = []
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.__finished = False
self.__informed = False
@classmethod
def validate(cls):
""" A draw callback belonging to the space is persistent when another file is opened, whereas a modal operator is not.
Solution below removes the draw callback if the operator becomes invalid. The RNA is how Blender objects store their
properties under the hood. When the instance of the Blender operator is no longer required its RNA is trashed.
Using 'repr()' avoids using a try catch clause. Would be keen to find out if there is a nicer way to check for this.
"""
invalids = [(type, op, context, handler) for type, op, context, handler in cls.handlers if repr(op).endswith("invalid>")]
valid = not(invalids)
while invalids:
type, op, context, handler = invalids.pop()
if type == 'H':
bpy.types.SpaceView3D.draw_handler_remove(handler, 'WINDOW')
if type == 'T':
context.window_manager.event_timer_remove(handler)
cls.handlers.remove((type, op, context, handler))
return valid
def init_widgets(self, context, widgets):
self.widgets = widgets
for widget in self.widgets:
widget.init(context)
def on_invoke(self, context, event):
pass
def on_finish(self, context):
self.__finished = True
def invoke(self, context, event):
self.on_invoke(context, event)
args = (self, context)
self.register_handlers(args, context)
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
def register_handlers(self, args, context):
BL_UI_OT_draw_operator.handlers = []
BL_UI_OT_draw_operator.handlers.append(('H', self, context, bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, args, 'WINDOW', 'POST_PIXEL')))
BL_UI_OT_draw_operator.handlers.append(('T', self, context, context.window_manager.event_timer_add(0.1, window=context.window)))
# Was as below before implementing the 'lost handler detection logic'
# self.__draw_handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, args, "WINDOW", "POST_PIXEL")
# self.__draw_events = context.window_manager.event_timer_add(0.1, window=context.window)
def unregister_handlers(self, context):
for handler in BL_UI_OT_draw_operator.handlers:
if handler[0] == 'H':
bpy.types.SpaceView3D.draw_handler_remove(handler[3], 'WINDOW')
if handler[0] == 'T':
context.window_manager.event_timer_remove(handler[3])
BL_UI_OT_draw_operator.handlers = []
# Was as below before implementing the 'lost handler detection logic'
# context.window_manager.event_timer_remove(self.__draw_events)
# bpy.types.SpaceView3D.draw_handler_remove(self.__draw_handle, "WINDOW")
# self.__draw_handle = None
# self.__draw_events = None
def modal(self, context, event):
if self.__finished:
return {'FINISHED'}
# -- personalized criteria for the Reference Cameras addon --
# This is an ugly workaround till I figure out how to signal to the N-panel coding that this remote control panel has been finished.
# This is to detect when user changed workspace
try:
testing = context.space_data.type
except Exception as e:
self.finish()
try:
if not (context.space_data.type == 'VIEW_3D'):
self.finish()
if self.terminate_execution():
self.finish()
except Exception as e:
pass
# -- end of the personalized criteria for the given addon --
if context.area:
context.area.tag_redraw()
if self.handle_widget_events(event):
return {'RUNNING_MODAL'}
# Not using this escape option, but left it here for documentation purpose
# if event.type in {"ESC"}:
# self.finish()
return {'PASS_THROUGH'}
def handle_widget_events(self, event):
result = False
for widget in self.widgets:
if widget.visible or event.type == 'TIMER':
if widget.handle_event(event):
result = True
break
if event.type != 'TIMER':
for widget in self.widgets:
if widget.visible:
# Need to pass one more time to wrap up any pending change of state for widgets on the widgets list
widget.handle_event_finalize(event)
return result
def terminate_execution(self):
# This might be overriden by one same named function in the derived (child) class
return False
def finish(self):
# -- personalized criteria for the Reference Cameras addon --
# This is an ugly workaround till I figure out how to signal to the N-panel coding that this remote control panel has been finished.
# This is to detect when user changed workspace
try:
testing = bpy.context.space_data.type
except Exception as e:
bpy.context.scene.var.RemoVisible = False
bpy.context.scene.var.btnRemoText = "Open Remote Control"
# -- end of the personalized criteria for the given addon --
self.unregister_handlers(bpy.context)
self.on_finish(bpy.context)
# 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.validate():
bpy.context.scene.var.RemoVisible = False
bpy.context.scene.var.btnRemoText = "Open Remote Control"
return
success = True
##-- personalized criteria for the Reference Cameras addon --
# This is an ugly workaround till I figure out how to signal to the N-panel coding that this remote control panel has been finished.
# This is to detect when user changed workspace
try:
testing = context.space_data.type
except Exception as e:
self.finish()
try:
if context.space_data.type == 'VIEW_3D':
if hasattr(context.scene.var, 'addon_ident'):
if context.scene.var.addon_ident == 'RC_CAMERA':
# The above "marker" is generated in the 'reference_camera.py' module
success = (context.mode == 'OBJECT' and context.region_data.view_perspective == 'CAMERA')
else:
success = False
except Exception as e:
success = False
##-- end of the personalized criteria for the given addon --
if success:
for widget in self.widgets:
widget.draw()