PrEditor 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of PrEditor might be problematic. Click here for more details.
- preditor/__init__.py +322 -0
- preditor/__main__.py +13 -0
- preditor/about_module.py +161 -0
- preditor/cli.py +192 -0
- preditor/config.py +302 -0
- preditor/contexts.py +119 -0
- preditor/cores/__init__.py +0 -0
- preditor/cores/core.py +20 -0
- preditor/dccs/maya/PrEditor_maya.mod +2 -0
- preditor/dccs/maya/plug-ins/PrEditor_maya.py +110 -0
- preditor/debug.py +144 -0
- preditor/delayable_engine/__init__.py +302 -0
- preditor/delayable_engine/delayables.py +85 -0
- preditor/enum.py +728 -0
- preditor/excepthooks.py +131 -0
- preditor/gui/__init__.py +93 -0
- preditor/gui/app.py +160 -0
- preditor/gui/codehighlighter.py +209 -0
- preditor/gui/completer.py +226 -0
- preditor/gui/console.py +867 -0
- preditor/gui/dialog.py +178 -0
- preditor/gui/drag_tab_bar.py +190 -0
- preditor/gui/editor_chooser.py +57 -0
- preditor/gui/errordialog.py +68 -0
- preditor/gui/find_files.py +125 -0
- preditor/gui/fuzzy_search/__init__.py +0 -0
- preditor/gui/fuzzy_search/fuzzy_search.py +93 -0
- preditor/gui/group_tab_widget/__init__.py +325 -0
- preditor/gui/group_tab_widget/grouped_tab_menu.py +35 -0
- preditor/gui/group_tab_widget/grouped_tab_models.py +108 -0
- preditor/gui/group_tab_widget/grouped_tab_widget.py +78 -0
- preditor/gui/group_tab_widget/one_tab_widget.py +54 -0
- preditor/gui/level_buttons.py +343 -0
- preditor/gui/logger_window_handler.py +48 -0
- preditor/gui/logger_window_plugin.py +32 -0
- preditor/gui/loggerwindow.py +1385 -0
- preditor/gui/newtabwidget.py +69 -0
- preditor/gui/set_text_editor_path_dialog.py +59 -0
- preditor/gui/status_label.py +99 -0
- preditor/gui/suggest_path_quotes_dialog.py +50 -0
- preditor/gui/ui/editor_chooser.ui +93 -0
- preditor/gui/ui/errordialog.ui +74 -0
- preditor/gui/ui/find_files.ui +140 -0
- preditor/gui/ui/loggerwindow.ui +1105 -0
- preditor/gui/ui/set_text_editor_path_dialog.ui +189 -0
- preditor/gui/ui/suggest_path_quotes_dialog.ui +225 -0
- preditor/gui/window.py +161 -0
- preditor/gui/workbox_mixin.py +389 -0
- preditor/gui/workbox_text_edit.py +137 -0
- preditor/gui/workboxwidget.py +298 -0
- preditor/logging_config.py +52 -0
- preditor/osystem.py +401 -0
- preditor/plugins.py +118 -0
- preditor/prefs.py +74 -0
- preditor/resource/environment_variables.html +26 -0
- preditor/resource/error_mail.html +85 -0
- preditor/resource/error_mail_inline.html +41 -0
- preditor/resource/img/README.md +17 -0
- preditor/resource/img/arrow_forward.png +0 -0
- preditor/resource/img/check-bold.png +0 -0
- preditor/resource/img/chevron-down.png +0 -0
- preditor/resource/img/chevron-up.png +0 -0
- preditor/resource/img/close-thick.png +0 -0
- preditor/resource/img/comment-edit.png +0 -0
- preditor/resource/img/content-copy.png +0 -0
- preditor/resource/img/content-cut.png +0 -0
- preditor/resource/img/content-duplicate.png +0 -0
- preditor/resource/img/content-paste.png +0 -0
- preditor/resource/img/content-save.png +0 -0
- preditor/resource/img/debug_disabled.png +0 -0
- preditor/resource/img/eye-check.png +0 -0
- preditor/resource/img/file-plus.png +0 -0
- preditor/resource/img/file-remove.png +0 -0
- preditor/resource/img/format-align-left.png +0 -0
- preditor/resource/img/format-letter-case-lower.png +0 -0
- preditor/resource/img/format-letter-case-upper.png +0 -0
- preditor/resource/img/format-letter-case.svg +1 -0
- preditor/resource/img/information.png +0 -0
- preditor/resource/img/logging_critical.png +0 -0
- preditor/resource/img/logging_custom.png +0 -0
- preditor/resource/img/logging_debug.png +0 -0
- preditor/resource/img/logging_error.png +0 -0
- preditor/resource/img/logging_info.png +0 -0
- preditor/resource/img/logging_not_set.png +0 -0
- preditor/resource/img/logging_warning.png +0 -0
- preditor/resource/img/marker.png +0 -0
- preditor/resource/img/play.png +0 -0
- preditor/resource/img/playlist-play.png +0 -0
- preditor/resource/img/plus-minus-variant.png +0 -0
- preditor/resource/img/preditor.ico +0 -0
- preditor/resource/img/preditor.png +0 -0
- preditor/resource/img/preditor.psd +0 -0
- preditor/resource/img/preditor.svg +44 -0
- preditor/resource/img/regex.svg +1 -0
- preditor/resource/img/restart.svg +1 -0
- preditor/resource/img/skip-forward-outline.png +0 -0
- preditor/resource/img/skip-next-outline.png +0 -0
- preditor/resource/img/skip-next.png +0 -0
- preditor/resource/img/skip-previous.png +0 -0
- preditor/resource/img/subdirectory-arrow-right.png +0 -0
- preditor/resource/img/text-search-variant.png +0 -0
- preditor/resource/img/warning-big.png +0 -0
- preditor/resource/lang/python.json +30 -0
- preditor/resource/settings.ini +25 -0
- preditor/resource/stylesheet/Bright.css +65 -0
- preditor/resource/stylesheet/Dark.css +199 -0
- preditor/scintilla/__init__.py +22 -0
- preditor/scintilla/delayables/__init__.py +11 -0
- preditor/scintilla/delayables/smart_highlight.py +94 -0
- preditor/scintilla/delayables/spell_check.py +173 -0
- preditor/scintilla/documenteditor.py +2038 -0
- preditor/scintilla/finddialog.py +68 -0
- preditor/scintilla/lang/__init__.py +80 -0
- preditor/scintilla/lang/config/bash.ini +15 -0
- preditor/scintilla/lang/config/batch.ini +14 -0
- preditor/scintilla/lang/config/cpp.ini +19 -0
- preditor/scintilla/lang/config/css.ini +19 -0
- preditor/scintilla/lang/config/eyeonscript.ini +17 -0
- preditor/scintilla/lang/config/html.ini +21 -0
- preditor/scintilla/lang/config/javascript.ini +24 -0
- preditor/scintilla/lang/config/lua.ini +16 -0
- preditor/scintilla/lang/config/maxscript.ini +20 -0
- preditor/scintilla/lang/config/mel.ini +18 -0
- preditor/scintilla/lang/config/mu.ini +22 -0
- preditor/scintilla/lang/config/nsi.ini +19 -0
- preditor/scintilla/lang/config/perl.ini +19 -0
- preditor/scintilla/lang/config/puppet.ini +19 -0
- preditor/scintilla/lang/config/python.ini +28 -0
- preditor/scintilla/lang/config/ruby.ini +19 -0
- preditor/scintilla/lang/config/sql.ini +7 -0
- preditor/scintilla/lang/config/xml.ini +21 -0
- preditor/scintilla/lang/config/yaml.ini +18 -0
- preditor/scintilla/lang/language.py +240 -0
- preditor/scintilla/lexers/__init__.py +0 -0
- preditor/scintilla/lexers/cpplexer.py +21 -0
- preditor/scintilla/lexers/javascriptlexer.py +25 -0
- preditor/scintilla/lexers/maxscriptlexer.py +234 -0
- preditor/scintilla/lexers/mellexer.py +368 -0
- preditor/scintilla/lexers/mulexer.py +32 -0
- preditor/scintilla/lexers/pythonlexer.py +41 -0
- preditor/scintilla/ui/finddialog.ui +160 -0
- preditor/settings.py +71 -0
- preditor/stream/__init__.py +80 -0
- preditor/stream/director.py +73 -0
- preditor/stream/manager.py +74 -0
- preditor/streamhandler_helper.py +46 -0
- preditor/utils/__init__.py +0 -0
- preditor/utils/cute.py +30 -0
- preditor/utils/stylesheets.py +54 -0
- preditor/utils/text_search.py +342 -0
- preditor/version.py +21 -0
- preditor/weakref.py +363 -0
- preditor-1.0.0.dist-info/METADATA +224 -0
- preditor-1.0.0.dist-info/RECORD +158 -0
- preditor-1.0.0.dist-info/WHEEL +5 -0
- preditor-1.0.0.dist-info/entry_points.txt +18 -0
- preditor-1.0.0.dist-info/licenses/LICENSE +165 -0
- preditor-1.0.0.dist-info/top_level.txt +1 -0
preditor/config.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from functools import wraps
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def set_if_unlocked(track_state=False):
|
|
10
|
+
def _set_if_unlocked(func):
|
|
11
|
+
"""Decorate property setter functions to prevent editing after locking."""
|
|
12
|
+
|
|
13
|
+
@wraps(func)
|
|
14
|
+
def wrapper(self, value):
|
|
15
|
+
if not self.is_locked(func.__name__, track_state=track_state):
|
|
16
|
+
# logger.debug(f'"{func.__name__}" is unlocked and can be set: {value}')
|
|
17
|
+
return func(self, value)
|
|
18
|
+
# logger.info(
|
|
19
|
+
# f'"{func.__name__}" is locked and can no-longer be set: {value}'
|
|
20
|
+
# )
|
|
21
|
+
|
|
22
|
+
return wrapper
|
|
23
|
+
|
|
24
|
+
return _set_if_unlocked
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class PreditorConfig:
|
|
28
|
+
"""Global configuration of PrEditor's instance.
|
|
29
|
+
|
|
30
|
+
When creating the main PrEditor instance it will use `preditor.config` to
|
|
31
|
+
control how its initialized. That stores an instance of this class.
|
|
32
|
+
|
|
33
|
+
Once `preditor.instance(create=True)` the properties on this class will then
|
|
34
|
+
silently ignore any attempts to set most of its properties. Where noted in
|
|
35
|
+
the property doc-strings, some properties are used to install a setting that
|
|
36
|
+
can't be automatically undone, so they will stop responding after the first
|
|
37
|
+
setting.
|
|
38
|
+
|
|
39
|
+
While this class does not force a singleton pattern, it is expected in normal
|
|
40
|
+
operation that there will only ever be a single instance made and it is stored
|
|
41
|
+
on `preditor.config` when first imported. Things like excepthook and streams
|
|
42
|
+
modify python's state which could lead to duplicate outputs, multiple error
|
|
43
|
+
prompts or other undesirable behavior.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self):
|
|
47
|
+
self._locks = {}
|
|
48
|
+
self._name = None
|
|
49
|
+
self._logging = False
|
|
50
|
+
self._headless_callback = None
|
|
51
|
+
self._parent_callback = None
|
|
52
|
+
self._error_dialog_class = True
|
|
53
|
+
self._excepthooks = []
|
|
54
|
+
|
|
55
|
+
def dump(self, indent=0):
|
|
56
|
+
"""A convenient way to inspect the current configuration."""
|
|
57
|
+
ret = [
|
|
58
|
+
f"{' ' * indent}name: {self.name}",
|
|
59
|
+
f"{' ' * indent}streams: {self.streams}",
|
|
60
|
+
f"{' ' * indent}excepthook: {self.excepthook!r}",
|
|
61
|
+
f"{' ' * indent}excepthooks: {self.excepthooks!r}",
|
|
62
|
+
f"{' ' * indent}error_dialog_class: {self.error_dialog_class!r}",
|
|
63
|
+
f"{' ' * indent}logging: {self.logging}",
|
|
64
|
+
f"{' ' * indent}headless_callback: {self.headless_callback!r}",
|
|
65
|
+
f"{' ' * indent}parent_callback: {self.parent_callback!r}",
|
|
66
|
+
]
|
|
67
|
+
return '\n'.join(ret)
|
|
68
|
+
|
|
69
|
+
def is_locked(self, value, track_state=False):
|
|
70
|
+
"""Returns if value is locked and can not be updated.
|
|
71
|
+
|
|
72
|
+
Once `preditor.instance` returns a value this will always return False.
|
|
73
|
+
The config settings should not be modified once the instance is created.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
value (str): The name of the value to check. Should match a property
|
|
77
|
+
name on this class.
|
|
78
|
+
track_state (bool, optional): If True then also check if value has
|
|
79
|
+
been flagged as locked. This indicates that it was already set
|
|
80
|
+
by a previous call and can no longer be modified even if instance
|
|
81
|
+
is False.
|
|
82
|
+
"""
|
|
83
|
+
if self.instance(create=False):
|
|
84
|
+
# if the instance is created all items are locked
|
|
85
|
+
return True
|
|
86
|
+
if track_state:
|
|
87
|
+
return self._locks.get(value, False)
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def error_dialog_class(self):
|
|
92
|
+
"""Dialog class shown if PrEditor isn't visible and a error happens.
|
|
93
|
+
|
|
94
|
+
This dialog is used to prompt the user to show PrEditor and can be
|
|
95
|
+
sub-classed to add extra functionality. This is called by
|
|
96
|
+
`PreditorExceptHook.ask_to_show_logger` method when added to
|
|
97
|
+
`preditor.config.excepthooks`.
|
|
98
|
+
|
|
99
|
+
If set to `True` then `blurdev.gui.errordialog.ErrorDialog` is used. When
|
|
100
|
+
replacing this, it should be a sub-class of that class or re-implement
|
|
101
|
+
its api.
|
|
102
|
+
|
|
103
|
+
You can use an EntryPoint string like `preditor.gui.errordialog:ErrorDialog`
|
|
104
|
+
instead of passing the actual class object. This lets you delay the import
|
|
105
|
+
until actually needed.
|
|
106
|
+
"""
|
|
107
|
+
return self._error_dialog_class
|
|
108
|
+
|
|
109
|
+
@error_dialog_class.setter
|
|
110
|
+
def error_dialog_class(self, cls):
|
|
111
|
+
self._error_dialog_class = cls
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def excepthook(self):
|
|
115
|
+
"""Installs a `sys.excepthook` handler when first set to True.
|
|
116
|
+
|
|
117
|
+
Replaces `sys.excepthook` with a interactive exception handler that
|
|
118
|
+
prompts the user to show PrEditor when an python exception is raised.
|
|
119
|
+
It is recommended that you only add the excepthook once the Qt UI is
|
|
120
|
+
initialized. See: :py:class:`preditor.excepthooks.PreditorExceptHook`.
|
|
121
|
+
"""
|
|
122
|
+
return self._locks.get("excepthook", False)
|
|
123
|
+
|
|
124
|
+
@excepthook.setter
|
|
125
|
+
@set_if_unlocked(track_state=True)
|
|
126
|
+
def excepthook(self, value):
|
|
127
|
+
if not value:
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
# Install the excepthook:
|
|
131
|
+
import preditor.excepthooks
|
|
132
|
+
|
|
133
|
+
# Note: install checks if the current excepthook is a instance of this
|
|
134
|
+
# class and prevents installing a second time.
|
|
135
|
+
preditor.excepthooks.PreditorExceptHook.install()
|
|
136
|
+
|
|
137
|
+
# Disable future setting via `set_if_unlocked`, the `PreditorExceptHook`
|
|
138
|
+
# is a chaining except hook that calls the previous excepthook when called.
|
|
139
|
+
# We don't want to install multiple automatically, the user can define that
|
|
140
|
+
# logic if required.
|
|
141
|
+
self._locks["excepthook"] = True
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def excepthooks(self):
|
|
145
|
+
"""A list of callables that are called when an exception is handled.
|
|
146
|
+
|
|
147
|
+
If `excepthook` is enabled installing `PreditorExceptHook` then it will
|
|
148
|
+
call each item in this list. The signature of the function should be
|
|
149
|
+
`callable(*args)`. If not configured it will automatically install default
|
|
150
|
+
callables. You can add `None` to disable this.
|
|
151
|
+
"""
|
|
152
|
+
return self._excepthooks
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def headless_callback(self):
|
|
156
|
+
"""A pointer to a method that is called by `is_headless`.
|
|
157
|
+
|
|
158
|
+
This callback returns a bool indicating if PrEditor should attempt to
|
|
159
|
+
create GUI elements. Application integrations can set this callback to
|
|
160
|
+
give them control over what PrEditor gets parent to.
|
|
161
|
+
"""
|
|
162
|
+
return self._headless_callback
|
|
163
|
+
|
|
164
|
+
@headless_callback.setter
|
|
165
|
+
@set_if_unlocked()
|
|
166
|
+
def headless_callback(self, cb):
|
|
167
|
+
self._headless_callback = cb
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def instance(cls, parent=None, run_workbox=False, create=True):
|
|
171
|
+
"""Returns the existing instance of the PrEditor gui creating it on first call.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
parent (QWidget, optional): If the instance hasn't been created yet, create
|
|
175
|
+
it and parent it to this object.
|
|
176
|
+
run_workbox (bool, optional): If the instance hasn't been created yet, this
|
|
177
|
+
will execute the active workbox's code once fully initialized.
|
|
178
|
+
create (bool, optional): Returns None if the instance has not been created.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Returns a fully initialized instance of the PrEditor gui. If called more
|
|
182
|
+
than once, the same instance will be returned. If create is False, it may
|
|
183
|
+
return None.
|
|
184
|
+
"""
|
|
185
|
+
from .gui.loggerwindow import LoggerWindow
|
|
186
|
+
|
|
187
|
+
return LoggerWindow.instance(
|
|
188
|
+
parent=parent, run_workbox=run_workbox, create=create
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def is_headless(self):
|
|
192
|
+
"""Returns True if PrEditor should not create GUI items.
|
|
193
|
+
|
|
194
|
+
Returns None if the `headless_callback` has not been configured. Otherwise
|
|
195
|
+
returns the result of calling `headless_callback()` which should return a bool.
|
|
196
|
+
"""
|
|
197
|
+
if not self.headless_callback:
|
|
198
|
+
return None
|
|
199
|
+
return self.headless_callback()
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def logging(self):
|
|
203
|
+
"""Restore the python logging configuration settings that were recorded
|
|
204
|
+
the last time PrEditor prefs were saved.
|
|
205
|
+
|
|
206
|
+
If called multiple times with different name's before the instance is
|
|
207
|
+
created, this will reset the logging configuration from the previous
|
|
208
|
+
name if logging prefs exist.
|
|
209
|
+
"""
|
|
210
|
+
return self._logging
|
|
211
|
+
|
|
212
|
+
@logging.setter
|
|
213
|
+
def logging(self, state):
|
|
214
|
+
self._logging = state
|
|
215
|
+
self.update_logging()
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def name(self):
|
|
219
|
+
"""The name to use for the global instance of PrEditor.
|
|
220
|
+
|
|
221
|
+
Once this has been set, you can call `preditor.launch` without passing
|
|
222
|
+
name to access the main instance. The name controls what preferences
|
|
223
|
+
are loaded and used by PrEditor including the workbox tabs."""
|
|
224
|
+
return self._name
|
|
225
|
+
|
|
226
|
+
@name.setter
|
|
227
|
+
@set_if_unlocked()
|
|
228
|
+
def name(self, name):
|
|
229
|
+
changed = self.name != name
|
|
230
|
+
self._name = name
|
|
231
|
+
|
|
232
|
+
if changed:
|
|
233
|
+
# If the core name was changed attempt to update the logging config.
|
|
234
|
+
self.update_logging()
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def parent_callback(self):
|
|
238
|
+
"""A pointer to a method that is called by `self.root_window`.
|
|
239
|
+
|
|
240
|
+
This callback returns a QWidget to use as the parent of the LoggerWindow
|
|
241
|
+
when the instance is first created. This can be used by DCC's to set the
|
|
242
|
+
parent to their main window."""
|
|
243
|
+
return self._parent_callback
|
|
244
|
+
|
|
245
|
+
@parent_callback.setter
|
|
246
|
+
@set_if_unlocked()
|
|
247
|
+
def parent_callback(self, cb):
|
|
248
|
+
self._parent_callback = cb
|
|
249
|
+
|
|
250
|
+
def root_window(self):
|
|
251
|
+
"""The QWidget to use as the parent of PrEditor widgets."""
|
|
252
|
+
# If a parent widget callback was configured, use it
|
|
253
|
+
if self.parent_callback is not None:
|
|
254
|
+
return self.parent_callback()
|
|
255
|
+
|
|
256
|
+
# Otherwise, attempt to find the top level widget from Qt
|
|
257
|
+
from .gui.app import App
|
|
258
|
+
|
|
259
|
+
return App.root_window()
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def streams(self):
|
|
263
|
+
"""Installs the stream managers when first set to True.
|
|
264
|
+
|
|
265
|
+
Install the stream manager to capture any stdout/stderr text written.
|
|
266
|
+
Later when calling launch, the LoggerWindow will show all of the captured
|
|
267
|
+
text. This lets you only create the LoggerWindow IF you need to show it,
|
|
268
|
+
but when you do it will have all of the std stream text written after
|
|
269
|
+
this is enabled."""
|
|
270
|
+
return self._locks.get("streams", False)
|
|
271
|
+
|
|
272
|
+
@streams.setter
|
|
273
|
+
@set_if_unlocked(track_state=True)
|
|
274
|
+
def streams(self, value):
|
|
275
|
+
if not value:
|
|
276
|
+
return
|
|
277
|
+
|
|
278
|
+
# Install the stream manager to capture output
|
|
279
|
+
from .stream import install_to_std
|
|
280
|
+
|
|
281
|
+
install_to_std()
|
|
282
|
+
|
|
283
|
+
# Disable re-installing streams.
|
|
284
|
+
self._locks["streams"] = True
|
|
285
|
+
|
|
286
|
+
def update_logging(self):
|
|
287
|
+
"""Install/replace the logging config.
|
|
288
|
+
|
|
289
|
+
This does nothing unless `logging` is set to True and `name` is set.
|
|
290
|
+
|
|
291
|
+
Note: When called repeatedly, this won't remove old logger's added by
|
|
292
|
+
previous calls so you may see some loggers added that were never
|
|
293
|
+
actually added by code other than the LoggingConfig.
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
if not (self.logging and self.name):
|
|
297
|
+
return
|
|
298
|
+
|
|
299
|
+
from .logging_config import LoggingConfig
|
|
300
|
+
|
|
301
|
+
cfg = LoggingConfig(core_name=self.name)
|
|
302
|
+
cfg.load()
|
preditor/contexts.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
_LOGGER = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ErrorReport(object):
|
|
9
|
+
"""Allows you to provide additional debug info if a error happens in this context.
|
|
10
|
+
|
|
11
|
+
PrEditor can send a error email when any python error is raised.
|
|
12
|
+
Sometimes just a traceback does not provide enough information to debug the
|
|
13
|
+
traceback. This class allows you to provide additional information to the error
|
|
14
|
+
report only if it is generated. For example if your treegrunt environment does not
|
|
15
|
+
have a email setup, or the current debug level is not set to Disabled.
|
|
16
|
+
|
|
17
|
+
ErrorReport can be used as a with context, or as a function decorator.
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
This example shows a class using both the with context and a decorated method.
|
|
21
|
+
|
|
22
|
+
from preditor.contexts import ErrorReport
|
|
23
|
+
class Test(object):
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self.value = None
|
|
26
|
+
def errorInfo(self):
|
|
27
|
+
# The text returned by this function will be included in the error email
|
|
28
|
+
return 'Info about the Test class: {}'.format(self.value)
|
|
29
|
+
def doStuff(self):
|
|
30
|
+
with ErrorReport(self.errorInfo, 'Test.doStuff'):
|
|
31
|
+
self.value = 'doStuff'
|
|
32
|
+
raise RuntimeError("BILL")
|
|
33
|
+
@ErrorReport(errorInfo, 'Test.doMoreStuff')
|
|
34
|
+
def doMoreStuff(self):
|
|
35
|
+
self.value = 'doMoreStuff'
|
|
36
|
+
raise RuntimeError("BOB")
|
|
37
|
+
|
|
38
|
+
Using this class does not initialize the Python Logger, so you don't need to worry
|
|
39
|
+
if your class is running headless and not use this class. However unless you set up
|
|
40
|
+
your own error reporting system the callbacks will not be called and nothing will be
|
|
41
|
+
reported.
|
|
42
|
+
|
|
43
|
+
If you want to set up your own error reporting system you need to set
|
|
44
|
+
`ErrorReport.enabled = True`. Then you will need to call ErrorReport.clearReports()
|
|
45
|
+
any time excepthook is called. This prevents a buildup of all error reports any time
|
|
46
|
+
a exception occurs. It should always be in place when you set enabled == True to
|
|
47
|
+
prevent wasting memory. Calling ErrorReport.generateReport() will return the info
|
|
48
|
+
you should include in your report. Calling generateReport is optional, but must be
|
|
49
|
+
called before calling clearReports.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
callback (function): If a exception happens this function is called and its
|
|
53
|
+
returned value is added to the error email if sent. No arguments are passed
|
|
54
|
+
to this function and it is expected to only return a string.
|
|
55
|
+
title (str, optional): This short string is added to the title of the
|
|
56
|
+
ErrorReport.
|
|
57
|
+
Attributes:
|
|
58
|
+
enabled (bool): If False(the default), then all callbacks are cleared even if
|
|
59
|
+
there is a exception. This is used to prevent these functions from leaking
|
|
60
|
+
memory if there isn't a excepthook calling clearReports.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
__reports__ = []
|
|
64
|
+
enabled = False
|
|
65
|
+
|
|
66
|
+
def __init__(self, callback, title=''):
|
|
67
|
+
self._callback = callback
|
|
68
|
+
self._title = title
|
|
69
|
+
|
|
70
|
+
def __call__(self, funct):
|
|
71
|
+
def wrapper(wrappedSelf, *args, **kwargs):
|
|
72
|
+
unbound = self._callback
|
|
73
|
+
self._callback = self._callback.__get__(wrappedSelf)
|
|
74
|
+
try:
|
|
75
|
+
with self:
|
|
76
|
+
return funct(wrappedSelf, *args, **kwargs)
|
|
77
|
+
finally:
|
|
78
|
+
self._callback = unbound
|
|
79
|
+
|
|
80
|
+
return wrapper
|
|
81
|
+
|
|
82
|
+
def __enter__(self):
|
|
83
|
+
type(self).__reports__.append((self._title, self._callback))
|
|
84
|
+
|
|
85
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
86
|
+
# If exc_type is None, then no exception was raised, so we should remove the
|
|
87
|
+
# callback. If cls.enabled is False, then nothing has set itself up to call
|
|
88
|
+
# clearReports. We need to remove the callback so it doesn't stay in memory.
|
|
89
|
+
if exc_type is None or not type(self).enabled:
|
|
90
|
+
type(self).__reports__.remove((self._title, self._callback))
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def clearReports(cls):
|
|
94
|
+
"""Removes all of the currently stored callbacks.
|
|
95
|
+
|
|
96
|
+
This should be called after all error reporting is finished, or if a error
|
|
97
|
+
happened and there is nothing to report it. If you set cls.enabled to True,
|
|
98
|
+
something in excepthook should call this to prevent keeping refrences to
|
|
99
|
+
functions from staying in memory.
|
|
100
|
+
"""
|
|
101
|
+
cls.__reports__ = []
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def generateReport(cls, fmt='{result}'):
|
|
105
|
+
"""Executes and returns all of the currently stored callbacks.
|
|
106
|
+
Args:
|
|
107
|
+
|
|
108
|
+
ftm (str, Optional): The results of the callbacks will be inserted into this
|
|
109
|
+
string using str.format into {results}.
|
|
110
|
+
Returns:
|
|
111
|
+
list: A list of tuples for all active ErrorReport classes. The tuples
|
|
112
|
+
contain two strings; the title string, and result of the passed in
|
|
113
|
+
callback function.
|
|
114
|
+
"""
|
|
115
|
+
ret = []
|
|
116
|
+
for title, callback in cls.__reports__:
|
|
117
|
+
result = callback()
|
|
118
|
+
ret.append((title, fmt.format(result=result)))
|
|
119
|
+
return ret
|
|
File without changes
|
preditor/cores/core.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function
|
|
2
|
+
|
|
3
|
+
from Qt.QtCore import QObject
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Core(QObject):
|
|
7
|
+
"""
|
|
8
|
+
The Core class provides all the main shared functionality and signals that need to
|
|
9
|
+
be distributed between different pacakges.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, objectName=None):
|
|
13
|
+
super(Core, self).__init__()
|
|
14
|
+
if objectName is None:
|
|
15
|
+
objectName = 'PrEditor'
|
|
16
|
+
self.setObjectName(objectName)
|
|
17
|
+
|
|
18
|
+
# Paths in this variable will be removed in
|
|
19
|
+
# preditor.osystem.subprocessEnvironment
|
|
20
|
+
self._removeFromPATHEnv = set()
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
|
|
3
|
+
import maya.mel
|
|
4
|
+
from maya import OpenMayaUI, cmds
|
|
5
|
+
|
|
6
|
+
preditor_menu = None
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def headless():
|
|
10
|
+
"""If true, no Qt gui elements should be used because python is running a
|
|
11
|
+
QCoreApplication."""
|
|
12
|
+
return bool(cmds.about(batch=True))
|
|
13
|
+
|
|
14
|
+
# TODO: This is the old method for detecting batch mode. Remove this once
|
|
15
|
+
# the above about command is vetted as working.
|
|
16
|
+
# basename = os.path.splitext(os.path.basename(sys.executable).lower())[0]
|
|
17
|
+
# return basename in ('mayabatch', 'mayapy')
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def root_window():
|
|
21
|
+
"""Returns the main window of Maya as a Qt object to be used for parenting."""
|
|
22
|
+
from Qt import QtCompat
|
|
23
|
+
|
|
24
|
+
ptr = OpenMayaUI.MQtUtil.mainWindow()
|
|
25
|
+
if ptr is not None:
|
|
26
|
+
pointer = int(ptr)
|
|
27
|
+
return QtCompat.wrapInstance(pointer)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def launch(ignored):
|
|
31
|
+
"""Show the PrEditor GUI and bring it into focus if it was minimized."""
|
|
32
|
+
import preditor
|
|
33
|
+
|
|
34
|
+
widget = preditor.launch()
|
|
35
|
+
return widget
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def initializePlugin(mobject): # noqa: N802
|
|
39
|
+
"""Initialize the script plug-in"""
|
|
40
|
+
global preditor_menu
|
|
41
|
+
|
|
42
|
+
# If running headless, there is no need to build a gui and create the python logger
|
|
43
|
+
if not headless():
|
|
44
|
+
from Qt.QtWidgets import QApplication
|
|
45
|
+
|
|
46
|
+
import preditor
|
|
47
|
+
|
|
48
|
+
maya_ver = cmds.about(version=True).split(" ")[0]
|
|
49
|
+
|
|
50
|
+
# Capture all stderr/out after the plugin is loaded. This makes it so
|
|
51
|
+
# if the PrEditor GUI is shown, it will include all of the output. Also
|
|
52
|
+
# tells PrEditor how to parent itself to the main window and save prefs.
|
|
53
|
+
preditor.configure(
|
|
54
|
+
# Set the core_name so preferences are saved per-maya version.
|
|
55
|
+
"Maya-{}".format(maya_ver),
|
|
56
|
+
# Tell PrEditor how to find the maya root window for parenting
|
|
57
|
+
parent_callback=root_window,
|
|
58
|
+
# Tell it how to check if running in batch mode
|
|
59
|
+
headless_callback=headless,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Detect Maya shutting down and ensure PrEditor's prefs are saved
|
|
63
|
+
if QApplication.instance():
|
|
64
|
+
QApplication.instance().aboutToQuit.connect(preditor.shutdown)
|
|
65
|
+
|
|
66
|
+
# Add a new PrEditor menu with an item that launches PrEditor
|
|
67
|
+
gmainwindow = maya.mel.eval("$temp1=$gMainWindow")
|
|
68
|
+
preditor_menu = cmds.menu(label="PrEditor", parent=gmainwindow, tearOff=True)
|
|
69
|
+
cmds.menuItem(
|
|
70
|
+
label="Show",
|
|
71
|
+
command=launch,
|
|
72
|
+
sourceType="python",
|
|
73
|
+
image=preditor.resourcePath('img/preditor.png'),
|
|
74
|
+
parent=preditor_menu,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# TODO: Alternatively figure out how to add the launcher menuItem to a
|
|
78
|
+
# pre-existing maya menu like next to the "Script Editor" in
|
|
79
|
+
# "Windows -> General Editors"
|
|
80
|
+
# https://github.com/chadmv/cvwrap/blob/master/scripts/cvwrap/menu.py#L18
|
|
81
|
+
|
|
82
|
+
# menu = 'mainWindowMenu'
|
|
83
|
+
# # Make sure the menu widgets exist first.
|
|
84
|
+
# maya.mel.eval('ChaDeformationsMenu MayaWindow|{0};'.format(menu))
|
|
85
|
+
# items = cmds.menu(menu, q=True, ia=True)
|
|
86
|
+
# # print(items)
|
|
87
|
+
# for item in items:
|
|
88
|
+
# menu_label = cmds.menuItem(item, q=True, label=True)
|
|
89
|
+
# # print(menu_label)
|
|
90
|
+
# if menu_label == "General Editors":
|
|
91
|
+
# # cmds.menuItem(parent=item, divider=True, dividerLabel='PrEditor' )
|
|
92
|
+
# cmds.menuItem(
|
|
93
|
+
# label="PrEditor",
|
|
94
|
+
# command=launch,
|
|
95
|
+
# sourceType='python',
|
|
96
|
+
# image=preditor.resourcePath('img/preditor.png'),
|
|
97
|
+
# parent=item,
|
|
98
|
+
# )
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def uninitializePlugin(mobject): # noqa: N802
|
|
102
|
+
"""Uninitialize the script plug-in"""
|
|
103
|
+
import preditor
|
|
104
|
+
|
|
105
|
+
# Remove the PrEditor Menu if it exists
|
|
106
|
+
if preditor_menu and cmds.menu(preditor_menu, exists=True):
|
|
107
|
+
cmds.deleteUI(preditor_menu, menu=True)
|
|
108
|
+
|
|
109
|
+
# Close PrEditor making sure to save prefs
|
|
110
|
+
preditor.core.shutdown()
|