Fem: Print real-time log messages in mesher task panels - fixes #17594

This commit is contained in:
marioalexis
2024-11-04 13:00:47 -03:00
committed by Chris Hennes
parent 38ea260fef
commit 616b78865f
5 changed files with 130 additions and 113 deletions

View File

@@ -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)

View File

@@ -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):

View File

@@ -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 + (
'<font color="{}"><b>{:4.1f}:</b></font> '.format(
getOutputWinColor("Logging"), time.time() - self.time_start
)
)
if outputwin_color_type:
self.console_message += '<font color="{}">{}</font><br>'.format(
outputwin_color_type, message
)
else:
self.console_message += message + "<br>"
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()

View File

@@ -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
)

View File

@@ -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
)