Skip to content

Commit c641522

Browse files
fix: Resolve QObject thread affinity error in rendering
Co-authored-by: aider (gemini/gemini-2.5-pro) <[email protected]>
1 parent 1cc94d2 commit c641522

File tree

1 file changed

+82
-40
lines changed

1 file changed

+82
-40
lines changed

pysdfscad_qtgui/main.py

Lines changed: 82 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,56 @@ def flush(self):
138138

139139
log_format='<level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>'
140140

141+
class RenderWorker(QObject):
142+
ast_ready = pyqtSignal(str)
143+
python_ready = pyqtSignal(str)
144+
mesh_ready = pyqtSignal(object)
145+
log_message = pyqtSignal(str)
146+
finished = pyqtSignal()
147+
148+
def __init__(self, openscad_file):
149+
super().__init__()
150+
self.openscad_file = openscad_file
151+
152+
@pyqtSlot()
153+
def run(self):
154+
try:
155+
#Try and set background threads to a *low* priority,
156+
# since people on the internet seem confused about this
157+
# a higher nice value means your program is *nicer* to
158+
# other programs, and will get out of their way.
159+
try:
160+
os.nice(14)
161+
except: pass
162+
163+
ast_text = self.openscad_file.as_ast()
164+
self.ast_ready.emit(ast_text)
165+
166+
python_text = self.openscad_file.as_python()
167+
self.python_ready.emit(python_text)
168+
169+
result = list(self.openscad_file.run())
170+
if not result:
171+
self.log_message.emit("No top level geometry to render")
172+
else:
173+
res = result[0]
174+
if isinstance(res, sdf.SDF2):
175+
res = res.extrude(0.1)
176+
177+
with redirect_stdout(LoggerWriter(logger.opt(depth=1).info)):
178+
points = res.generate()
179+
180+
points, cells = np.unique(points, axis=0, return_inverse=True)
181+
cells = cells.reshape((-1, 3))
182+
183+
self.mesh_ready.emit((points, cells, res))
184+
185+
except Exception:
186+
logger.exception("Error during render")
187+
finally:
188+
self.finished.emit()
189+
190+
141191
class MainUi(QMainWindow):
142192
def __init__(self):
143193
super().__init__() # Call the inherited classes __init__ method
@@ -167,6 +217,7 @@ def __init__(self):
167217

168218
self.openscadFile=OpenscadFile()
169219
self.mesh=None
220+
self.result=None
170221

171222
self.preview3d=gl.GLViewWidget(self.sideSplitter)
172223
self.preview3d.setCameraPosition(distance=40)
@@ -228,51 +279,29 @@ def saveFileAs(self):
228279
self.setWindowTitle(f"{self.openscadFile.file} - pySdfScad")
229280

230281

231-
@logger.catch
232-
def _render(self):
233-
try:
234-
#Try and set background threads to a *low* priority,
235-
# since people on the internet seem confused about this
236-
# a higher nice value means your program is *nicer* to
237-
# other programs, and will get out of their way.
238-
os.nice(14)
239-
except: pass
240-
241-
self.openscadFile.text=self.editor.toPlainText()
242-
self.astPreview.setPlainText(self.openscadFile.as_ast())
243-
self.pythonPreview.setPlainText(self.openscadFile.as_python())
244-
result = list(self.openscadFile.run())
245-
if not result:
246-
logger.info("No top level geometry to render")
247-
else:
248-
self.result=result[0]
249-
if isinstance(self.result,sdf.SDF2):
250-
self.result=self.result.extrude(0.1)
251-
import numpy as np
252-
with redirect_stdout(LoggerWriter(logger.opt(depth=1).info)):
253-
points = self.result.generate()
254-
points, cells = np.unique(points, axis=0, return_inverse=True)
255-
cells = cells.reshape((-1, 3))
256-
self.mesh=(points,cells)
257-
258-
meshdata = gl.MeshData(vertexes=points, faces=cells)
259-
mesh = gl.GLMeshItem(meshdata=meshdata,
282+
def update_mesh(self, data):
283+
points, cells, result_obj = data
284+
self.result = result_obj
285+
self.mesh = (points, cells)
286+
287+
meshdata = gl.MeshData(vertexes=points, faces=cells)
288+
mesh = gl.GLMeshItem(meshdata=meshdata,
260289
smooth=False, drawFaces=True,
261290
drawEdges=False,
262291
shader="normalColor",
263292
color = (1,1,1,1), edgeColor=(0.2, 0.5, 0.2, 1)
264293
)
265-
self.preview3d.clear()
266-
g = gl.GLGridItem()
267-
g.setSize(200, 200)
268-
g.setSpacing(10, 10)
294+
self.preview3d.clear()
295+
g = gl.GLGridItem()
296+
g.setSize(200, 200)
297+
g.setSpacing(10, 10)
269298

270-
a=gl.GLAxisItem()
271-
a.setSize(10,10,10)
299+
a=gl.GLAxisItem()
300+
a.setSize(10,10,10)
272301

273-
self.preview3d.addItem(g)
274-
self.preview3d.addItem(a)
275-
self.preview3d.addItem(mesh)
302+
self.preview3d.addItem(g)
303+
self.preview3d.addItem(a)
304+
self.preview3d.addItem(mesh)
276305

277306
def exportMesh(self):
278307
if not self.result:
@@ -312,8 +341,21 @@ def render(self):
312341
self.preview3d.clear()
313342
self.astPreview.setPlainText("")
314343
self.pythonPreview.setPlainText("")
315-
thread = Thread(target=self._render)
316-
thread.start()
344+
345+
self.openscadFile.text = self.editor.toPlainText()
346+
347+
self.thread = QThread()
348+
self.worker = RenderWorker(self.openscadFile)
349+
self.worker.moveToThread(self.thread)
350+
self.thread.started.connect(self.worker.run)
351+
self.worker.finished.connect(self.thread.quit)
352+
self.worker.finished.connect(self.worker.deleteLater)
353+
self.thread.finished.connect(self.thread.deleteLater)
354+
self.worker.ast_ready.connect(self.astPreview.setPlainText)
355+
self.worker.python_ready.connect(self.pythonPreview.setPlainText)
356+
self.worker.mesh_ready.connect(self.update_mesh)
357+
self.worker.log_message.connect(logger.info)
358+
self.thread.start()
317359

318360
def closeEvent(self, event):
319361
logger.remove(self._logger_handle_id)

0 commit comments

Comments
 (0)