Skip to content

Commit b128005

Browse files
committed
Experiment with threading
1 parent b9f12aa commit b128005

File tree

1 file changed

+74
-20
lines changed

1 file changed

+74
-20
lines changed

cq_editor/widgets/debugger.py

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from PyQt5.QtCore import (
1212
Qt,
1313
QObject,
14+
QThread,
1415
pyqtSlot,
1516
pyqtSignal,
1617
QEventLoop,
@@ -111,6 +112,50 @@ def update_frame(self, frame):
111112
self.setModel(model)
112113

113114

115+
class RenderWorker(QObject):
116+
"""Runs a CadQuery script on a background thread.
117+
118+
All signals are emitted from the worker thread; Qt's automatic queued-
119+
connection mechanism ensures they are safely delivered to main-thread slots.
120+
"""
121+
122+
finished = pyqtSignal()
123+
sigRendered = pyqtSignal(dict)
124+
sigTraceback = pyqtSignal(object, str)
125+
sigLocals = pyqtSignal(dict)
126+
127+
def __init__(self, debugger, cq_script, cq_script_path):
128+
super().__init__()
129+
self._debugger = debugger
130+
self._cq_script = cq_script
131+
self._cq_script_path = cq_script_path
132+
133+
@pyqtSlot()
134+
def run(self):
135+
d = self._debugger
136+
cq_code, module = d.compile_code(self._cq_script, self._cq_script_path)
137+
138+
if cq_code is not None:
139+
cq_objects, injected_names = d._inject_locals(module)
140+
try:
141+
d._exec(cq_code, module.__dict__, module.__dict__,
142+
self._cq_script_path)
143+
d._cleanup_locals(module, injected_names)
144+
145+
if len(cq_objects) == 0:
146+
cq_objects = find_cq_objects(module.__dict__)
147+
148+
self.sigRendered.emit(cq_objects)
149+
self.sigTraceback.emit(None, self._cq_script)
150+
self.sigLocals.emit(module.__dict__)
151+
except Exception:
152+
exc_info = sys.exc_info()
153+
sys.last_traceback = exc_info[-1]
154+
self.sigTraceback.emit(exc_info, self._cq_script)
155+
156+
self.finished.emit()
157+
158+
114159
class Debugger(QObject, ComponentMixin):
115160

116161
name = "Debugger"
@@ -184,6 +229,8 @@ def __init__(self, parent):
184229

185230
self._frames = []
186231
self._stop_debugging = False
232+
self._render_thread = None
233+
self._render_worker = None
187234

188235
def get_current_script(self):
189236

@@ -214,10 +261,10 @@ def compile_code(self, cq_script, cq_script_path=None):
214261
self.sigTraceback.emit(sys.exc_info(), cq_script)
215262
return None, None
216263

217-
def _exec(self, code, locals_dict, globals_dict):
264+
def _exec(self, code, locals_dict, globals_dict, script_path=None):
218265

219266
with ExitStack() as stack:
220-
p = (self.get_current_script_path() or Path("")).absolute().dirname()
267+
p = (script_path or Path("")).absolute().dirname()
221268

222269
if self.preferences["Add script dir to path"] and p.exists():
223270
sys.path.insert(0, p)
@@ -292,35 +339,42 @@ def _cleanup_locals(self, module, injected_names):
292339
@pyqtSlot(bool)
293340
def render(self):
294341

342+
if self._render_thread is not None and self._render_thread.isRunning():
343+
return # ignore re-entrant render requests
344+
295345
seed(59798267586177)
296346
if self.preferences["Reload CQ"]:
297347
reload_cq()
298348

349+
# Capture editor state on the main thread before handing off.
299350
cq_script = self.get_current_script()
300351
cq_script_path = self.get_current_script_path()
301-
cq_code, module = self.compile_code(cq_script, cq_script_path)
302352

303-
if cq_code is None:
304-
return
353+
for action in self._actions["Run"]:
354+
action.setEnabled(False)
305355

306-
cq_objects, injected_names = self._inject_locals(module)
356+
self._render_worker = RenderWorker(self, cq_script, cq_script_path)
357+
self._render_thread = QThread()
358+
self._render_worker.moveToThread(self._render_thread)
307359

308-
try:
309-
self._exec(cq_code, module.__dict__, module.__dict__)
360+
self._render_thread.started.connect(self._render_worker.run)
361+
self._render_worker.sigRendered.connect(self.sigRendered)
362+
self._render_worker.sigTraceback.connect(self.sigTraceback)
363+
self._render_worker.sigLocals.connect(self.sigLocals)
364+
self._render_worker.finished.connect(self._render_thread.quit)
365+
self._render_worker.finished.connect(self._render_worker.deleteLater)
366+
self._render_thread.finished.connect(self._render_thread.deleteLater)
367+
self._render_thread.finished.connect(self._on_render_finished)
310368

311-
# remove the special methods
312-
self._cleanup_locals(module, injected_names)
369+
self._render_thread.start()
313370

314-
# collect all CQ objects if no explicit show_object was called
315-
if len(cq_objects) == 0:
316-
cq_objects = find_cq_objects(module.__dict__)
317-
self.sigRendered.emit(cq_objects)
318-
self.sigTraceback.emit(None, cq_script)
319-
self.sigLocals.emit(module.__dict__)
320-
except Exception:
321-
exc_info = sys.exc_info()
322-
sys.last_traceback = exc_info[-1]
323-
self.sigTraceback.emit(exc_info, cq_script)
371+
def _on_render_finished(self):
372+
373+
self._render_thread = None
374+
self._render_worker = None
375+
376+
for action in self._actions["Run"]:
377+
action.setEnabled(True)
324378

325379
@property
326380
def breakpoints(self):

0 commit comments

Comments
 (0)