diff --git a/src/Mod/Fem/femmesh/gmshtools.py b/src/Mod/Fem/femmesh/gmshtools.py
index d263b88c8d..6feebb75ff 100644
--- a/src/Mod/Fem/femmesh/gmshtools.py
+++ b/src/Mod/Fem/femmesh/gmshtools.py
@@ -30,6 +30,7 @@ __url__ = "https://www.freecad.org"
import os
import re
import subprocess
+from PySide.QtCore import QProcess
import FreeCAD
from FreeCAD import Console
@@ -54,7 +55,7 @@ class GmshTools:
# mesh obj
self.mesh_obj = gmsh_mesh_obj
- self.process = None
+ self.process = QProcess()
# analysis
self.analysis = None
if analysis:
@@ -210,27 +211,17 @@ class GmshTools:
self.write_part_file()
self.write_geo()
- def compute(self):
+ def prepare(self):
self.load_properties()
self.update_mesh_data()
self.get_tmp_file_paths()
self.get_gmsh_command()
self.write_gmsh_input_files()
- command_list = [self.gmsh_bin, "-", self.temp_file_geo]
- self.process = subprocess.Popen(
- command_list,
- shell=False,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- startupinfo=femutils.startProgramInfo("hide"),
- )
-
- out, err = self.process.communicate()
- if self.process.returncode != 0:
- raise RuntimeError(err.decode("utf-8"))
-
- return True
+ def compute(self):
+ command_list = ["-v", "4", "-", self.temp_file_geo]
+ self.process.start(self.gmsh_bin, command_list)
+ return self.process
def update_properties(self):
self.mesh_obj.FemMesh = Fem.read(self.temp_file_mesh)
diff --git a/src/Mod/Fem/femmesh/netgentools.py b/src/Mod/Fem/femmesh/netgentools.py
index c4ef9bbd0b..701937ed54 100644
--- a/src/Mod/Fem/femmesh/netgentools.py
+++ b/src/Mod/Fem/femmesh/netgentools.py
@@ -27,9 +27,9 @@ __url__ = "https://www.freecad.org"
import numpy as np
import shutil
-import subprocess
import sys
import tempfile
+from PySide.QtCore import QProcess
import FreeCAD
import Fem
@@ -81,6 +81,8 @@ class NetgenTools:
self.fem_mesh = None
self.process = None
self.tmpdir = ""
+ self.process = QProcess()
+ self.mesh_params = {}
def write_geom(self):
if not self.tmpdir:
@@ -101,9 +103,9 @@ from femmesh.netgentools import NetgenTools
NetgenTools.run_netgen(**{params})
"""
- def compute(self):
+ def prepare(self):
self.write_geom()
- mesh_params = {
+ self.mesh_params = {
"brep_file": self.brep_file,
"threads": self.obj.Threads,
"heal": self.obj.HealShape,
@@ -112,19 +114,11 @@ NetgenTools.run_netgen(**{params})
"result_file": self.result_file,
}
- code_str = self.code.format(params=mesh_params)
+ def compute(self):
+ code_str = self.code.format(params=self.mesh_params)
+ self.process.start(sys.executable, ["-c", code_str])
- cmd_list = [
- sys.executable,
- "-c",
- code_str,
- ]
- self.process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out, err = self.process.communicate()
- if self.process.returncode != 0:
- raise RuntimeError(err.decode("utf-8"))
-
- return True
+ return self.process
@staticmethod
def run_netgen(brep_file, threads, heal, params, second_order, result_file):
diff --git a/src/Mod/Fem/femtaskpanels/base_femmeshtaskpanel.py b/src/Mod/Fem/femtaskpanels/base_femmeshtaskpanel.py
index aa4adb613a..5ab36ebdd4 100644
--- a/src/Mod/Fem/femtaskpanels/base_femmeshtaskpanel.py
+++ b/src/Mod/Fem/femtaskpanels/base_femmeshtaskpanel.py
@@ -30,7 +30,6 @@ __url__ = "https://www.freecad.org"
# \brief base task panel for mesh object
import time
-import threading
from abc import ABC, abstractmethod
from PySide import QtCore
@@ -43,48 +42,19 @@ from femtools.femutils import getOutputWinColor
from . import base_femtaskpanel
-class _Process(threading.Thread):
+class _Thread(QtCore.QThread):
"""
Class for thread and subprocess manipulation
- 'tool' argument must be an object with a 'compute' method
- and a 'process' attribute of type Popen object
+ 'tool' argument must be an object with 'compute' and 'prepare' methods
+ and a 'process' attribute of type QProcess object
"""
def __init__(self, tool):
+ super().__init__()
self.tool = tool
- self._timer = QtCore.QTimer()
- self.success = False
- self.update = False
- self.error = ""
- super().__init__(target=self.tool.compute)
- QtCore.QObject.connect(self._timer, QtCore.SIGNAL("timeout()"), self._check)
-
- def init(self):
- self._timer.start(100)
- self.start()
def run(self):
- try:
- self.success = self._target(*self._args, **self._kwargs)
- except Exception as e:
- self.error = str(e)
-
- def finish(self):
- if self.tool.process:
- self.tool.process.terminate()
- self.join()
-
- def _check(self):
- if not self.is_alive():
- self._timer.stop()
- self.join()
- if self.success:
- try:
- self.tool.update_properties()
- self.update = True
- except Exception as e:
- self.error = str(e)
- self.success = False
+ self.tool.prepare()
class _BaseMeshTaskPanel(base_femtaskpanel._BaseTaskPanel, ABC):
@@ -92,14 +62,79 @@ class _BaseMeshTaskPanel(base_femtaskpanel._BaseTaskPanel, ABC):
Abstract base class for FemMesh object TaskPanel
"""
- def __init__(self, obj):
+ def __init__(self, obj, tool):
super().__init__(obj)
-
- self.tool = None
- self.form = None
+ self.tool = tool
self.timer = QtCore.QTimer()
- self.process = None
- self.console_message = ""
+ self._thread = _Thread(self.tool)
+ self.text_log = None
+ self.text_time = None
+
+ def setup_connections(self):
+ QtCore.QObject.connect(self._thread, QtCore.SIGNAL("started()"), self.thread_started)
+ QtCore.QObject.connect(self._thread, QtCore.SIGNAL("finished()"), self.thread_finished)
+ QtCore.QObject.connect(self.tool.process, QtCore.SIGNAL("started()"), self.process_started)
+ QtCore.QObject.connect(
+ self.tool.process,
+ QtCore.SIGNAL("finished(int,QProcess::ExitStatus)"),
+ self.process_finished,
+ )
+ QtCore.QObject.connect(
+ self.tool.process,
+ QtCore.SIGNAL("readyReadStandardOutput()"),
+ self.write_output,
+ )
+ QtCore.QObject.connect(
+ self.tool.process,
+ QtCore.SIGNAL("readyReadStandardError()"),
+ self.write_error,
+ )
+ QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.update_timer_text)
+
+ def thread_started(self):
+ self.text_log.clear()
+ self.write_log("Prepare meshing...\n", QtGui.QColor(getOutputWinColor("Text")))
+ QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
+
+ def thread_finished(self):
+ self.tool.compute()
+
+ def process_finished(self, code, status):
+ if status == QtCore.QProcess.ExitStatus.NormalExit:
+ if code != 0:
+ self.write_log(
+ "Meshing finished with errors\n", QtGui.QColor(getOutputWinColor("Error"))
+ )
+ self.tool.update_properties()
+ self.write_log("Process finished\n", QtGui.QColor(getOutputWinColor("Text")))
+ else:
+ self.write_log("Process crashed\n", QtGui.QColor(getOutputWinColor("Error")))
+
+ def process_started(self):
+ self.write_log("Start meshing...\n", QtGui.QColor(getOutputWinColor("Text")))
+
+ def write_output(self):
+ self.write_log(
+ self.tool.process.readAllStandardOutput().data().decode("utf-8"),
+ QtGui.QColor(getOutputWinColor("Logging")),
+ )
+
+ def write_error(self):
+ self.write_log(
+ self.tool.process.readAllStandardError().data().decode("utf-8"),
+ QtGui.QColor(getOutputWinColor("Error")),
+ )
+
+ def write_log(self, data, color):
+ cursor = QtGui.QTextCursor(self.text_log.document())
+ cursor.beginEditBlock()
+ cursor.movePosition(QtGui.QTextCursor.End)
+ fmt = QtGui.QTextCharFormat()
+ fmt.setForeground(color)
+ cursor.mergeCharFormat(fmt)
+ cursor.insertText(data)
+ cursor.endEditBlock()
+ self.text_log.ensureCursorVisible()
@abstractmethod
def set_mesh_params(self):
@@ -116,7 +151,10 @@ class _BaseMeshTaskPanel(base_femtaskpanel._BaseTaskPanel, ABC):
return button_value
def accept(self):
- if self.process and self.process.is_alive():
+ if (
+ self._thread.isRunning()
+ or self.tool.process.state() != QtCore.QProcess.ProcessState.NotRunning
+ ):
FreeCAD.Console.PrintWarning("Process still running\n")
return None
@@ -126,62 +164,46 @@ class _BaseMeshTaskPanel(base_femtaskpanel._BaseTaskPanel, ABC):
return super().accept()
def reject(self):
+ # self_thread may be blocking
+ if self._thread.isRunning():
+ return None
self.timer.stop()
QtGui.QApplication.restoreOverrideCursor()
- if self.process and self.process.is_alive():
- self.console_log("Process aborted", "#ff6700")
- self.process.finish()
+ if self.tool.process.state() != QtCore.QProcess.ProcessState.NotRunning:
+ self.tool.process.kill()
+ self._thread.quit()
+ FreeCAD.Console.PrintWarning("Process aborted\n")
else:
return super().reject()
def clicked(self, button):
if button == QtGui.QDialogButtonBox.Apply:
- if self.process and self.process.is_alive():
+ if (
+ self._thread.isRunning()
+ or self.tool.process.state() != QtCore.QProcess.ProcessState.NotRunning
+ ):
FreeCAD.Console.PrintWarning("Process already running\n")
return None
self.set_mesh_params()
self.run_mesher()
- def console_log(self, message="", outputwin_color_type=None):
- self.console_message = self.console_message + (
- '{:4.1f}: '.format(
- getOutputWinColor("Logging"), time.time() - self.time_start
- )
- )
- if outputwin_color_type:
- self.console_message += '{}
'.format(
- outputwin_color_type, message
- )
- else:
- self.console_message += message + "
"
- self.form.te_output.setText(self.console_message)
- self.form.te_output.moveCursor(QtGui.QTextCursor.End)
-
def update_timer_text(self):
- if self.process and self.process.is_alive():
- self.form.l_time.setText(f"Time: {time.time() - self.time_start:4.1f}: ")
+ if (
+ self._thread.isRunning()
+ or self.tool.process.state() != QtCore.QProcess.ProcessState.NotRunning
+ ):
+ self.text_time.setText(f"Time: {time.time() - self.time_start:4.1f}")
else:
- if self.process:
- if self.process.success:
- if not self.process.update:
- return None
- self.console_log("Success!", "#00AA00")
- else:
- self.console_log(self.process.error, "#AA0000")
self.timer.stop()
QtGui.QApplication.restoreOverrideCursor()
def run_mesher(self):
- self.process = _Process(self.tool)
self.timer.start(100)
self.time_start = time.time()
- self.form.l_time.setText(f"Time: {time.time() - self.time_start:4.1f}: ")
- self.console_message = ""
- self.console_log("Start process...")
- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
+ self.text_time.setText(f"Time: {time.time() - self.time_start:4.1f}: ")
- self.process.init()
+ self._thread.start()
def get_version(self):
full_message = self.tool.version()
diff --git a/src/Mod/Fem/femtaskpanels/task_mesh_gmsh.py b/src/Mod/Fem/femtaskpanels/task_mesh_gmsh.py
index 32afdba764..bbdf906d2c 100644
--- a/src/Mod/Fem/femtaskpanels/task_mesh_gmsh.py
+++ b/src/Mod/Fem/femtaskpanels/task_mesh_gmsh.py
@@ -46,13 +46,18 @@ class _TaskPanel(base_femmeshtaskpanel._BaseMeshTaskPanel):
"""
def __init__(self, obj):
- super().__init__(obj)
+ super().__init__(obj, gmshtools.GmshTools(obj))
self.form = FreeCADGui.PySideUic.loadUi(
FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/MeshGmsh.ui"
)
+ self.text_log = self.form.te_output
+ self.text_time = self.form.l_time
- self.tool = gmshtools.GmshTools(obj)
+ self.setup_connections()
+
+ def setup_connections(self):
+ super().setup_connections()
QtCore.QObject.connect(
self.form.qsb_max_size, QtCore.SIGNAL("valueChanged(Base::Quantity)"), self.max_changed
@@ -69,7 +74,6 @@ class _TaskPanel(base_femmeshtaskpanel._BaseMeshTaskPanel):
self.form.cb_dimension.addItems(self.obj.getEnumerationsOfProperty("ElementDimension"))
self.form.cb_order.addItems(self.obj.getEnumerationsOfProperty("ElementOrder"))
- QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.update_timer_text)
QtCore.QObject.connect(
self.form.pb_get_gmsh_version, QtCore.SIGNAL("clicked()"), self.get_version
)
diff --git a/src/Mod/Fem/femtaskpanels/task_mesh_netgen.py b/src/Mod/Fem/femtaskpanels/task_mesh_netgen.py
index 3d9a3b03d1..8d8081170c 100644
--- a/src/Mod/Fem/femtaskpanels/task_mesh_netgen.py
+++ b/src/Mod/Fem/femtaskpanels/task_mesh_netgen.py
@@ -46,12 +46,19 @@ class _TaskPanel(base_femmeshtaskpanel._BaseMeshTaskPanel):
"""
def __init__(self, obj):
- super().__init__(obj)
+ super().__init__(obj, netgentools.NetgenTools(obj))
+
self.form = FreeCADGui.PySideUic.loadUi(
FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/MeshNetgen.ui"
)
- self.tool = netgentools.NetgenTools(obj)
+ self.text_log = self.form.te_output
+ self.text_time = self.form.l_time
+
+ self.setup_connections()
+
+ def setup_connections(self):
+ super().setup_connections()
QtCore.QObject.connect(
self.form.qsb_max_size,
@@ -86,7 +93,6 @@ class _TaskPanel(base_femmeshtaskpanel._BaseMeshTaskPanel):
QtCore.SIGNAL("currentIndexChanged(int)"),
self.fineness_changed,
)
- QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.update_timer_text)
QtCore.QObject.connect(
self.form.pb_get_netgen_version, QtCore.SIGNAL("clicked()"), self.get_version
)