diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index b793c570c7..9a53d41f94 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -571,6 +571,7 @@ SET(FemGuiObjects_SRCS SET(FemGuiTaskPanels_SRCS femtaskpanels/__init__.py femtaskpanels/base_femtaskpanel.py + femtaskpanels/base_femmeshtaskpanel.py femtaskpanels/task_constraint_bodyheatsource.py femtaskpanels/task_constraint_centrif.py femtaskpanels/task_constraint_currentdensity.py diff --git a/src/Mod/Fem/femtaskpanels/base_femmeshtaskpanel.py b/src/Mod/Fem/femtaskpanels/base_femmeshtaskpanel.py new file mode 100644 index 0000000000..aa4adb613a --- /dev/null +++ b/src/Mod/Fem/femtaskpanels/base_femmeshtaskpanel.py @@ -0,0 +1,188 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +# *************************************************************************** +# * Copyright (c) 2024 Mario Passaglia * +# * * +# * This file is part of FreeCAD. * +# * * +# * FreeCAD is free software: you can redistribute it and/or modify it * +# * under the terms of the GNU Lesser General Public License as * +# * published by the Free Software Foundation, either version 2.1 of the * +# * License, or (at your option) any later version. * +# * * +# * FreeCAD is distributed in the hope that it will be useful, but * +# * WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +# * Lesser General Public License for more details. * +# * * +# * You should have received a copy of the GNU Lesser General Public * +# * License along with FreeCAD. If not, see * +# * . * +# * * +# *************************************************************************** + +__title__ = "FreeCAD FEM mesh base task panel for mesh object object" +__author__ = "Mario Passaglia" +__url__ = "https://www.freecad.org" + +## @package base_femmeshtaskpanel +# \ingroup FEM +# \brief base task panel for mesh object + +import time +import threading +from abc import ABC, abstractmethod + +from PySide import QtCore +from PySide import QtGui + +import FreeCAD + +from femtools.femutils import getOutputWinColor + +from . import base_femtaskpanel + + +class _Process(threading.Thread): + """ + Class for thread and subprocess manipulation + 'tool' argument must be an object with a 'compute' method + and a 'process' attribute of type Popen object + """ + + def __init__(self, tool): + 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 + + +class _BaseMeshTaskPanel(base_femtaskpanel._BaseTaskPanel, ABC): + """ + Abstract base class for FemMesh object TaskPanel + """ + + def __init__(self, obj): + super().__init__(obj) + + self.tool = None + self.form = None + self.timer = QtCore.QTimer() + self.process = None + self.console_message = "" + + @abstractmethod + def set_mesh_params(self): + pass + + @abstractmethod + def get_mesh_params(self): + pass + + def getStandardButtons(self): + button_value = ( + QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Apply | QtGui.QDialogButtonBox.Cancel + ) + return button_value + + def accept(self): + if self.process and self.process.is_alive(): + FreeCAD.Console.PrintWarning("Process still running\n") + return None + + self.timer.stop() + QtGui.QApplication.restoreOverrideCursor() + self.set_mesh_params() + return super().accept() + + def reject(self): + self.timer.stop() + QtGui.QApplication.restoreOverrideCursor() + if self.process and self.process.is_alive(): + self.console_log("Process aborted", "#ff6700") + self.process.finish() + else: + return super().reject() + + def clicked(self, button): + if button == QtGui.QDialogButtonBox.Apply: + if self.process and self.process.is_alive(): + 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}: ") + 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.process.init() + + def get_version(self): + full_message = self.tool.version() + QtGui.QMessageBox.information(None, "{} - Information".format(self.tool.name), full_message)