Last active
December 9, 2024 13:35
-
-
Save BigRoy/1972822065e38f8fae7521078e44eca2 to your computer and use it in GitHub Desktop.
Pyblish debug stepper - pauses between each plug-in process and shows the Context + Instances with their data at that point in time
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pprint | |
import inspect | |
import html | |
import copy | |
import pyblish.api | |
from Qt import QtWidgets, QtCore, QtGui | |
TAB = 4* " " | |
HEADER_SIZE = "15px" | |
KEY_COLOR = "#ffffff" | |
NEW_KEY_COLOR = "#00ff00" | |
VALUE_TYPE_COLOR = "#ffbbbb" | |
VALUE_COLOR = "#777799" | |
NEW_VALUE_COLOR = "#DDDDCC" | |
COLORED = "<font style='color:{color}'>{txt}</font>" | |
MAX_VALUE_STR_LEN = 100 | |
def format_data(data, previous_data): | |
previous_data = previous_data or {} | |
msg = "" | |
for key, value in sorted(data.items()): | |
type_str = type(value).__name__ | |
key_color = NEW_KEY_COLOR if key not in previous_data else KEY_COLOR | |
value_color = VALUE_COLOR | |
if key not in previous_data or previous_data[key] != value: | |
value_color = NEW_VALUE_COLOR | |
value_str = str(value) | |
if len(value_str) > MAX_VALUE_STR_LEN: | |
value_str = value_str[:MAX_VALUE_STR_LEN] + "..." | |
key_str = COLORED.format(txt=key, color=key_color) | |
type_str = COLORED.format(txt=type_str, color=VALUE_TYPE_COLOR) | |
value_str = COLORED.format(txt=html.escape(value_str), color=value_color) | |
data_str = TAB + f"{key_str} ({type_str}): {value_str} <br>" | |
msg += data_str | |
return msg | |
class DebugUI(QtWidgets.QDialog): | |
def __init__(self, parent=None): | |
super(DebugUI, self).__init__(parent=parent) | |
self.setWindowTitle("Pyblish Debug Stepper") | |
self.setWindowFlags( | |
QtCore.Qt.Window | |
| QtCore.Qt.CustomizeWindowHint | |
| QtCore.Qt.WindowTitleHint | |
| QtCore.Qt.WindowMinimizeButtonHint | |
| QtCore.Qt.WindowCloseButtonHint | |
| QtCore.Qt.WindowStaysOnTopHint | |
) | |
layout = QtWidgets.QVBoxLayout(self) | |
text_edit = QtWidgets.QTextEdit() | |
font = QtGui.QFont("NONEXISTENTFONT") | |
font.setStyleHint(font.TypeWriter) | |
text_edit.setFont(font) | |
text_edit.setLineWrapMode(text_edit.NoWrap) | |
step = QtWidgets.QPushButton("Step") | |
step.setEnabled(False) | |
layout.addWidget(text_edit) | |
layout.addWidget(step) | |
step.clicked.connect(self.on_step) | |
self._pause = False | |
self.text = text_edit | |
self.step = step | |
self.resize(700, 500) | |
self._previous_data = {} | |
def pause(self, state): | |
self._pause = state | |
self.step.setEnabled(state) | |
def on_step(self): | |
self.pause(False) | |
def showEvent(self, event): | |
print("Registering callback..") | |
pyblish.api.register_callback("pluginProcessed", | |
self.on_plugin_processed) | |
def hideEvent(self, event): | |
self.pause(False) | |
print("Deregistering callback..") | |
pyblish.api.deregister_callback("pluginProcessed", | |
self.on_plugin_processed) | |
def on_plugin_processed(self, result): | |
self.pause(True) | |
# Don't tell me why - but the pyblish event does not | |
# pass along the context with the result. And thus | |
# it's non trivial to debug step by step. So, we | |
# get the context like the evil bastards we are. | |
i = 0 | |
found_context = None | |
current_frame = inspect.currentframe() | |
for frame_info in inspect.getouterframes(current_frame): | |
frame_locals = frame_info.frame.f_locals | |
if "context" in frame_locals: | |
found_context = frame_locals["context"] | |
break | |
i += 1 | |
if i > 5: | |
print("Warning: Pyblish context not found..") | |
# We should be getting to the context within | |
# a few frames | |
break | |
plugin_name = result["plugin"].__name__ | |
duration = result['duration'] | |
plugin_instance = result["instance"] | |
msg = "" | |
msg += f"Plugin: {plugin_name}" | |
if plugin_instance is not None: | |
msg += f" -> instance: {plugin_instance}" | |
msg += "<br>" | |
msg += f"Duration: {duration}ms<br>" | |
msg += "====<br>" | |
context = found_context | |
if context is not None: | |
id = "context" | |
msg += f"""<font style='font-size: {HEADER_SIZE};'><b>Context:</b></font><br>""" | |
msg += format_data(context.data, previous_data=self._previous_data.get(id)) | |
msg += "====<br>" | |
self._previous_data[id] = copy.deepcopy(context.data) | |
for instance in context: | |
id = instance.name | |
msg += f"""<font style='font-size: {HEADER_SIZE};'><b>Instance:</b> {instance}</font><br>""" | |
msg += format_data(instance.data, previous_data=self._previous_data.get(id)) | |
msg += "----<br>" | |
self._previous_data[id] = copy.deepcopy(instance.data) | |
self.text.setHtml(msg) | |
app = QtWidgets.QApplication.instance() | |
while self._pause: | |
# Allow user interaction with the UI | |
app.processEvents() | |
window = DebugUI() | |
window.show() |
A Tip for whom it may concern,
There's a ready to run pyblish_debug_stepper
for AYON here
where, you can copy & paste it to your script editor in your DCC and then run
window = DebugUI()
window.show()
Alternatively, You can get the tool by checking out my PR Feature/add pyblish debug stepper to experimental tools #753
Where I've included the Pyblish Debug Stepper
as an experimental tool that can be accessed from any Host/DCC.
For more info, please refer to Pyblish Plugins Debugging | Ynput Forums
Many thanks for BigRoy for making this awesome tool.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note that this example uses a customized
pluginProcessedContext
event that Pyblish does NOT emit by defaultAnd quick and dirty with updating existing items instead of clearing and always adding new: