Files
create/src/Mod/Fem/femsolver/solver_taskpanel.py
Uwe 5d1bc93147 [FEM] solver_taskpanel: fix bug with Edit button
- when an error occurred during the Write process, the Edit button must not be enabled
  In this case the machine state is still at femsolver.run.PREPARE. If no error occurred it went one step up.
2023-02-02 18:58:07 +01:00

347 lines
12 KiB
Python

# ***************************************************************************
# * Copyright (c) 2017 Markus Hovorka <m.hovorka@live.de> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program 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 Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
__title__ = "FreeCAD FEM solver job control task panel"
__author__ = "Markus Hovorka"
__url__ = "https://www.freecadweb.org"
## \addtogroup FEM
# @{
from PySide import QtCore
from PySide import QtGui
import FreeCADGui as Gui
import femsolver.report
import femsolver.run
_UPDATE_INTERVAL = 50
_REPORT_TITLE = "Run Report"
_REPORT_ERR = (
"Failed to run. Please try again after all "
"of the following errors are resolved."
)
class ControlTaskPanel(QtCore.QObject):
machineChanged = QtCore.Signal(object)
machineStarted = QtCore.Signal(object)
machineStopped = QtCore.Signal(object)
machineStatusChanged = QtCore.Signal(str)
machineStatusCleared = QtCore.Signal()
machineTimeChanged = QtCore.Signal(float)
machineStateChanged = QtCore.Signal(float)
def __init__(self, machine):
super(ControlTaskPanel, self).__init__()
self.form = ControlWidget()
self._machine = None
# Timer that updates the duration indicator.
self._timer = QtCore.QTimer()
self._timer.setInterval(_UPDATE_INTERVAL)
self._timer.timeout.connect(self._timeProxy)
# Connect object to widget.
self.form.writeClicked.connect(self.write)
self.form.editClicked.connect(self.edit)
self.form.runClicked.connect(self.run)
self.form.abortClicked.connect(self.abort)
self.form.directoryChanged.connect(self.updateMachine)
# Seems that the task panel does not get destroyed. Disconnect
# as soon as the widget of the task panel gets destroyed.
self.form.destroyed.connect(self._disconnectMachine)
self.form.destroyed.connect(self._timer.stop)
# self.form.destroyed.connect(
# lambda: self.machineStatusChanged.disconnect(
# self.form.appendStatus))
# Connect all proxy signals.
self.machineStarted.connect(self._timer.start)
self.machineStarted.connect(self.form.updateState)
self.machineStopped.connect(self._timer.stop)
self.machineStopped.connect(self._displayReport)
self.machineStopped.connect(self.form.updateState)
self.machineStatusChanged.connect(self.form.appendStatus)
self.machineStatusCleared.connect(self.form.clearStatus)
self.machineTimeChanged.connect(self.form.setTime)
self.machineStateChanged.connect(
lambda: self.form.updateState(self.machine))
self.machineChanged.connect(self._updateTimer)
# Set initial machine. Signal updates the widget.
self.machineChanged.connect(self.updateWidget)
self.form.destroyed.connect(
lambda: self.machineChanged.disconnect(self.updateWidget))
self.machine = machine
@property
def machine(self):
return self._machine
@machine.setter
def machine(self, value):
self._connectMachine(value)
self._machine = value
self.machineChanged.emit(value)
@QtCore.Slot()
def write(self):
self.machine.reset()
self.machine.target = femsolver.run.PREPARE
self.machine.start()
@QtCore.Slot()
def run(self):
self.machine.reset(femsolver.run.SOLVE)
self.machine.target = femsolver.run.RESULTS
self.machine.start()
@QtCore.Slot()
def edit(self):
self.machine.reset(femsolver.run.SOLVE)
self.machine.solver.Proxy.edit(
self.machine.directory)
@QtCore.Slot()
def abort(self):
self.machine.abort()
@QtCore.Slot()
def updateWidget(self):
self.form.setDirectory(self.machine.directory)
self.form.setStatus(self.machine.status)
self.form.setTime(self.machine.time)
self.form.updateState(self.machine)
@QtCore.Slot()
def updateMachine(self):
if self.form.directory() != self.machine.directory:
self.machine = femsolver.run.getMachine(
self.machine.solver, self.form.directory())
@QtCore.Slot()
def _updateTimer(self):
if self.machine.running:
self._timer.start()
@QtCore.Slot(object)
def _displayReport(self, machine):
text = _REPORT_ERR if machine.failed else None
femsolver.report.display(machine.report, _REPORT_TITLE, text)
def getStandardButtons(self):
return int(QtGui.QDialogButtonBox.Close)
def reject(self):
Gui.ActiveDocument.resetEdit()
def _connectMachine(self, machine):
self._disconnectMachine()
machine.signalStatus.add(self._statusProxy)
machine.signalStatusCleared.add(self._statusClearedProxy)
machine.signalStarted.add(self._startedProxy)
machine.signalStopped.add(self._stoppedProxy)
machine.signalState.add(self._stateProxy)
def _disconnectMachine(self):
if self.machine is not None:
self.machine.signalStatus.remove(self._statusProxy)
self.machine.signalStatusCleared.add(self._statusClearedProxy)
self.machine.signalStarted.remove(self._startedProxy)
self.machine.signalStopped.remove(self._stoppedProxy)
self.machine.signalState.remove(self._stateProxy)
def _startedProxy(self):
self.machineStarted.emit(self.machine)
def _stoppedProxy(self):
self.machineStopped.emit(self.machine)
def _statusProxy(self, line):
self.machineStatusChanged.emit(line)
def _statusClearedProxy(self):
self.machineStatusCleared.emit()
def _timeProxy(self):
time = self.machine.time
self.machineTimeChanged.emit(time)
def _stateProxy(self):
state = self.machine.state
self.machineStateChanged.emit(state)
class ControlWidget(QtGui.QWidget):
writeClicked = QtCore.Signal()
editClicked = QtCore.Signal()
runClicked = QtCore.Signal()
abortClicked = QtCore.Signal()
directoryChanged = QtCore.Signal()
def __init__(self, parent=None):
super(ControlWidget, self).__init__(parent)
self._setupUi()
self._inputFileName = ""
def _setupUi(self):
self.setWindowTitle(self.tr("Solver Control"))
# Working directory group box
self._directoryTxt = QtGui.QLineEdit()
self._directoryTxt.editingFinished.connect(self.directoryChanged)
directoryBtt = QtGui.QToolButton()
directoryBtt.setText("...")
directoryBtt.clicked.connect(self._selectDirectory)
directoryLyt = QtGui.QHBoxLayout()
directoryLyt.addWidget(self._directoryTxt)
directoryLyt.addWidget(directoryBtt)
self._directoryGrp = QtGui.QGroupBox()
self._directoryGrp.setTitle(self.tr("Working Directory"))
self._directoryGrp.setLayout(directoryLyt)
# Action buttons (Write, Edit, Run)
self._writeBtt = QtGui.QPushButton(self.tr("Write"))
self._editBtt = QtGui.QPushButton(self.tr("Edit"))
self._runBtt = QtGui.QPushButton()
self._writeBtt.clicked.connect(self.writeClicked)
self._editBtt.clicked.connect(self.editClicked)
actionLyt = QtGui.QGridLayout()
actionLyt.addWidget(self._writeBtt, 0, 0)
actionLyt.addWidget(self._editBtt, 0, 1)
actionLyt.addWidget(self._runBtt, 1, 0, 1, 2)
# Solver status log
self._statusEdt = QtGui.QPlainTextEdit()
self._statusEdt.setReadOnly(True)
# for the log we need a certain height
# set it so to almost match the size of the CCX solver panel
self._statusEdt.setMinimumHeight(300)
# Elapsed time indicator
timeHeaderLbl = QtGui.QLabel(self.tr("Elapsed Time:"))
self._timeLbl = QtGui.QLabel()
timeLyt = QtGui.QHBoxLayout()
timeLyt.addWidget(timeHeaderLbl)
timeLyt.addWidget(self._timeLbl)
timeLyt.addStretch()
timeLyt.setContentsMargins(0, 0, 0, 0)
self._timeWid = QtGui.QWidget()
self._timeWid.setLayout(timeLyt)
# Main layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self._directoryGrp)
layout.addLayout(actionLyt)
layout.addWidget(self._statusEdt)
layout.addWidget(self._timeWid)
self.setLayout(layout)
@QtCore.Slot(str)
def setStatus(self, text):
if text is None:
text = ""
self._statusEdt.setPlainText(text)
self._statusEdt.moveCursor(QtGui.QTextCursor.End)
def status(self):
return self._statusEdt.plainText()
@QtCore.Slot(str)
def appendStatus(self, line):
self._statusEdt.moveCursor(QtGui.QTextCursor.End)
self._statusEdt.insertPlainText(line)
self._statusEdt.moveCursor(QtGui.QTextCursor.End)
@QtCore.Slot(str)
def clearStatus(self):
self._statusEdt.setPlainText("")
@QtCore.Slot(float)
def setTime(self, time):
timeStr = "<b>%05.1f</b>" % time if time is not None else ""
self._timeLbl.setText(timeStr)
def time(self):
if self._timeLbl.text() == "":
return None
return float(self._timeLbl.text())
@QtCore.Slot(float)
def setDirectory(self, directory):
self._directoryTxt.setText(directory)
def directory(self):
return self._directoryTxt.text()
@QtCore.Slot(int)
def updateState(self, machine):
if machine.state <= femsolver.run.PREPARE:
self._writeBtt.setText(self.tr("Write"))
self._editBtt.setText(self.tr("Edit"))
self._runBtt.setText(self.tr("Run"))
elif machine.state <= femsolver.run.SOLVE:
self._writeBtt.setText(self.tr("Re-write"))
self._editBtt.setText(self.tr("Edit"))
self._runBtt.setText(self.tr("Run"))
else:
self._writeBtt.setText(self.tr("Re-write"))
self._editBtt.setText(self.tr("Edit"))
self._runBtt.setText(self.tr("Re-run"))
if machine.running:
self._runBtt.setText(self.tr("Abort"))
self.setRunning(machine)
@QtCore.Slot()
def _selectDirectory(self):
path = QtGui.QFileDialog.getExistingDirectory(self)
self.setDirectory(path)
self.directoryChanged.emit()
def setRunning(self, machine):
if machine.running:
self._runBtt.clicked.connect(self.runClicked)
self._runBtt.clicked.disconnect()
self._runBtt.clicked.connect(self.abortClicked)
self._directoryGrp.setDisabled(True)
self._writeBtt.setDisabled(True)
self._editBtt.setDisabled(True)
else:
self._runBtt.clicked.connect(self.abortClicked)
self._runBtt.clicked.disconnect()
self._runBtt.clicked.connect(self.runClicked)
self._directoryGrp.setDisabled(False)
self._writeBtt.setDisabled(False)
self._editBtt.setDisabled(
not machine.solver.Proxy.editSupported()
or machine.state <= femsolver.run.PREPARE
)
## @}