Пример #1
0
Файл: gui.py Проект: mwm126/mfix
    def __setup_vtk_widget(self):
        " setup the vtk widget "

        self.vtkwidget = VtkWidget(self.project, parent=self)
        self.ui.horizontalLayoutModelGraphics.addWidget(self.vtkwidget)

        # register with project manager
        self.project.register_widget(
            self.vtkwidget, ["xmin", "xlength", "ymin", "ylength", "zmin", "zlength", "imax", "jmax", "kmax"]
        )
Пример #2
0
Файл: gui.py Проект: mwm126/mfix
class MfixGui(QtGui.QMainWindow):
    """
    Main window class handling all gui interactions
    """

    def __init__(self, app, parent=None):
        QtGui.QMainWindow.__init__(self, parent)

        # reference to qapp instance
        self.app = app

        # load ui file
        self.customWidgets = {
            "LineEdit": LineEdit,
            "CheckBox": CheckBox,
            "ComboBox": ComboBox,
            "DoubleSpinBox": DoubleSpinBox,
            "SpinBox": SpinBox,
        }
        self.ui = uic.loadUi(os.path.join("uifiles", "gui.ui"), self)

        # load settings
        self.settings = QSettings("MFIX", "MFIX")

        # set title and icon
        self.setWindowTitle("MFIX")
        self.setWindowIcon(get_icon("mfix.png"))

        # build keyword documentation from namelist docstrings
        self.keyword_doc = buildKeywordDoc(os.path.join(SCRIPT_DIRECTORY, os.pardir, "model"))

        self.project = ProjectManager(self, self.keyword_doc)

        # --- data ---
        self.modebuttondict = {
            "modeler": self.ui.pushButtonModeler,
            "workflow": self.ui.pushButtonWorkflow,
            "developer": self.ui.pushButtonDeveloper,
        }

        self.booleanbtndict = {
            "union": self.ui.toolbutton_geometry_union,
            "intersection": self.ui.toolbutton_geometry_intersect,
            "difference": self.ui.toolbutton_geometry_difference,
        }
        self.animation_speed = 400
        self.animating = False
        self.stack_animation = None

        # --- icons ---
        # loop through all widgets, because I am lazy
        for widget in widget_iter(self.ui):
            if isinstance(widget, QtGui.QToolButton):
                name = str(widget.objectName())
                if "add" in name:
                    widget.setIcon(get_icon("add.png"))
                elif "delete" in name or "remove" in name:
                    widget.setIcon(get_icon("remove.png"))
                elif "copy" in name:
                    widget.setIcon(get_icon("copy.png"))

        self.ui.toolbutton_new.setIcon(get_icon("newfolder.png"))
        self.ui.toolbutton_open.setIcon(get_icon("openfolder.png"))
        self.ui.toolbutton_save.setIcon(get_icon("save.png"))

        self.ui.toolbutton_compile.setIcon(get_icon("build.png"))
        self.ui.toolbutton_run.setIcon(get_icon("play.png"))
        self.ui.toolbutton_restart.setIcon(get_icon("restart.png"))
        self.ui.toolbutton_interact.setIcon(get_icon("flash.png"))

        self.ui.toolbutton_add_geometry.setIcon(get_icon("geometry.png"))
        self.ui.toolbutton_add_filter.setIcon(get_icon("filter.png"))
        self.ui.toolbutton_geometry_union.setIcon(get_icon("union.png"))
        self.ui.toolbutton_geometry_intersect.setIcon(get_icon("intersect.png"))
        self.ui.toolbutton_geometry_difference.setIcon(get_icon("difference.png"))

        self.ui.toolButtonTFMSolidsDatabase.setIcon(get_icon("download.png"))

        # --- Connect Signals to Slots---
        # open/save/new project
        self.ui.toolbutton_open.pressed.connect(self.open_project)
        self.ui.toolbutton_save.pressed.connect(self.save_project)

        # mode (modeler, workflow, developer)
        for mode, btn in self.modebuttondict.items():
            btn.released.connect(make_callback(self.mode_changed, mode))

        # navigation tree
        self.ui.treewidget_model_navigation.itemSelectionChanged.connect(self.navigation_changed)

        # build/run/connect MFIX
        self.ui.build_mfix_button.pressed.connect(self.build_mfix)
        self.ui.run_mfix_button.pressed.connect(self.run_mfix)
        self.ui.connect_mfix_button.pressed.connect(self.connect_mfix)
        self.ui.clear_output_button.pressed.connect(self.clear_output)

        # --- Threads ---
        self.build_thread = BuildThread(self)
        self.run_thread = RunThread(self)
        self.clear_thread = ClearThread(self)

        def make_handler(qtextbrowser):
            " make a closure to read stdout from external process "

            def handle_line(line):
                " closure to read stdout from external process "

                log = logging.getLogger(__name__)
                log.debug(str(line).strip())
                cursor = qtextbrowser.textCursor()
                cursor.movePosition(cursor.End)
                cursor.insertText(line)
                qtextbrowser.ensureCursorVisible()

            return handle_line

        self.build_thread.line_printed.connect(make_handler(self.ui.command_output))
        self.run_thread.line_printed.connect(make_handler(self.ui.command_output))
        self.clear_thread.line_printed.connect(make_handler(self.ui.command_output))

        # --- setup simple widgets ---
        self.__setup_simple_keyword_widgets()

        # --- vtk setup ---
        self.__setup_vtk_widget()

        # --- workflow setup ---
        if NodeWidget is not None:
            self.__setup_workflow_widget()

        # --- default ---
        self.mode_changed("modeler")
        self.change_pane("geometry")

        # autoload last project
        if self.get_project_dir():
            self.open_project(self.get_project_dir())

    def __setup_simple_keyword_widgets(self):
        """
        Look for and connect simple keyword widgets to the project manager.
        Keyword informtation from the namelist doc strings is added to each
        keyword widget. The widget must be named: *_keyword_<keyword> where
        <keyword> is the actual keyword. For example:
        lineedit_keyword_run_name
        """

        # loop through all widgets looking for *_keyword_<keyword>
        for widget in widget_iter(self.ui):
            name_list = str(widget.objectName()).lower().split("_")

            if "keyword" in name_list:
                keyword = "_".join(name_list[name_list.index("keyword") + 1 :])

                # set the key attribute to the keyword
                widget.key = keyword

                # add info from keyword documentation
                if keyword in self.keyword_doc:
                    widget.setdtype(self.keyword_doc[keyword]["dtype"])

                    if "required" in self.keyword_doc[keyword]:
                        widget.setValInfo(req=self.keyword_doc[keyword]["required"] == "true")
                    if "validrange" in self.keyword_doc[keyword]:
                        if "max" in self.keyword_doc[keyword]["validrange"]:
                            widget.setValInfo(_max=self.keyword_doc[keyword]["validrange"]["max"])
                        if "min" in self.keyword_doc[keyword]["validrange"]:
                            widget.setValInfo(_min=self.keyword_doc[keyword]["validrange"]["min"])

                    if "initpython" in self.keyword_doc[keyword]:
                        widget.default(self.keyword_doc[keyword]["initpython"])

                    if isinstance(widget, QtGui.QComboBox) and widget.count() < 1:
                        widget.addItems(list(self.keyword_doc[keyword]["valids"].keys()))

                # register the widget with the project manager
                self.project.register_widget(widget, [keyword])

                # connect to unsaved method
                widget.value_updated.connect(self.unsaved)

    def __setup_vtk_widget(self):
        " setup the vtk widget "

        self.vtkwidget = VtkWidget(self.project, parent=self)
        self.ui.horizontalLayoutModelGraphics.addWidget(self.vtkwidget)

        # register with project manager
        self.project.register_widget(
            self.vtkwidget, ["xmin", "xlength", "ymin", "ylength", "zmin", "zlength", "imax", "jmax", "kmax"]
        )

    def __setup_workflow_widget(self):

        self.nodeChart = NodeWidget(showtoolbar=False)
        # Build defualt node library
        self.nodeChart.nodeLibrary.buildDefualtLibrary()
        self.ui.horizontalLayoutPyqtnode.addWidget(self.nodeChart)

    def get_project_dir(self):
        " get the current project directory"

        last_dir = self.settings.value("project_dir")
        if last_dir:
            return last_dir
        else:
            return None

    def mode_changed(self, mode):
        " change the Modeler, Workflow, Developer tab"

        current_index = 0
        for i in range(self.ui.stackedwidget_mode.count()):
            widget = self.ui.stackedwidget_mode.widget(i)
            if mode == str(widget.objectName()):
                current_index = i
                break

        for key, btn in self.modebuttondict.items():
            btn.setChecked(mode == key)

        self.animate_stacked_widget(
            self.ui.stackedwidget_mode, self.ui.stackedwidget_mode.currentIndex(), current_index, "horizontal"
        )

    # --- modeler pane navigation ---
    def change_pane(self, name):
        """ change to the specified pane """

        clist = self.ui.treewidget_model_navigation.findItems(
            name, QtCore.Qt.MatchContains | QtCore.Qt.MatchRecursive, 0
        )

        for item in clist:
            if str(item.text(0)).lower() == name.lower():
                break

        self.ui.treewidget_model_navigation.setCurrentItem(item)
        self.navigation_changed()

    def navigation_changed(self):
        """ an item in the tree was selected, change panes """
        current_selection = self.ui.treewidget_model_navigation.selectedItems()

        if current_selection:

            text = str(current_selection[-1].text(0))
            text = "_".join(text.lower().split(" "))

            current_index = 0
            for i in range(self.ui.stackedWidgetTaskPane.count()):
                widget = self.ui.stackedWidgetTaskPane.widget(i)
                if text == str(widget.objectName()):
                    current_index = i
                    break

            self.animate_stacked_widget(
                self.ui.stackedWidgetTaskPane, self.ui.stackedWidgetTaskPane.currentIndex(), current_index
            )

    # --- animation methods ---
    def animate_stacked_widget(
        self, stackedwidget, from_, to, direction="vertical", line=None, to_btn=None, btn_layout=None
    ):
        """ animate changing of qstackedwidget """

        # check to see if already animating
        if self.animating and self.stack_animation is not None:
            self.stack_animation.stop()

        from_widget = stackedwidget.widget(from_)
        to_widget = stackedwidget.widget(to)

        # get from geometry
        width = from_widget.frameGeometry().width()
        height = from_widget.frameGeometry().height()

        # offset
        # bottom to top
        if direction == "vertical" and from_ < to:
            offsetx = 0
            offsety = height
        # top to bottom
        elif direction == "vertical" and from_ > to:
            offsetx = 0
            offsety = -height
        elif direction == "horizontal" and from_ < to:
            offsetx = width
            offsety = 0
        elif direction == "horizontal" and from_ > to:
            offsetx = -width
            offsety = 0
        else:
            return

        # move to widget and show
        # set the geometry of the next widget
        to_widget.setGeometry(0 + offsetx, 0 + offsety, width, height)
        to_widget.show()
        to_widget.raise_()

        # animate
        # from widget
        animnow = QtCore.QPropertyAnimation(from_widget, "pos")
        animnow.setDuration(self.animation_speed)
        animnow.setEasingCurve(QtCore.QEasingCurve.InOutQuint)
        animnow.setStartValue(QtCore.QPoint(0, 0))
        animnow.setEndValue(QtCore.QPoint(0 - offsetx, 0 - offsety))

        # to widget
        animnext = QtCore.QPropertyAnimation(to_widget, "pos")
        animnext.setDuration(self.animation_speed)
        animnext.setEasingCurve(QtCore.QEasingCurve.InOutQuint)
        animnext.setStartValue(QtCore.QPoint(0 + offsetx, 0 + offsety))
        animnext.setEndValue(QtCore.QPoint(0, 0))

        # line
        animline = None
        if line is not None and to_btn is not None:
            animline = QtCore.QPropertyAnimation(line, "pos")
            animline.setDuration(self.animation_speed)
            animline.setEasingCurve(QtCore.QEasingCurve.InOutQuint)
            animline.setStartValue(QtCore.QPoint(line.geometry().x(), line.geometry().y()))
            animline.setEndValue(QtCore.QPoint(to_btn.geometry().x(), line.geometry().y()))

        # animation group
        self.stack_animation = QtCore.QParallelAnimationGroup()
        self.stack_animation.addAnimation(animnow)
        self.stack_animation.addAnimation(animnext)
        if animline is not None:
            self.stack_animation.addAnimation(animline)
        self.stack_animation.finished.connect(
            make_callback(self.animate_stacked_widget_finished, stackedwidget, from_, to, btn_layout, line)
        )
        self.stack_animation.stateChanged.connect(
            make_callback(self.animate_stacked_widget_finished, stackedwidget, from_, to, btn_layout, line)
        )

        self.animating = True
        self.stack_animation.start()

    def animate_stacked_widget_finished(self, widget, from_, to, btn_layout=None, line=None):
        """ cleanup after animation """
        if self.stack_animation.state() == QtCore.QAbstractAnimation.Stopped:
            widget.setCurrentIndex(to)
            from_widget = widget.widget(from_)
            from_widget.hide()
            from_widget.move(0, 0)

            if btn_layout is not None and line is not None:
                btn_layout.addItem(btn_layout.takeAt(btn_layout.indexOf(line)), 1, to)

            self.animating = False

    # --- helper methods ---
    def message(
        self,
        title="Warning",
        icon="warning",
        text="This is a warning.",
        buttons=["ok"],
        default="ok",
        infoText=None,
        detailedtext=None,
    ):
        """
        Create a message box:
        title = 'title'
        icon = 'warning' or 'info'
        text = 'test to show'
        buttons = ['ok',...] where value is 'ok', 'yes', 'no', 'cancel',
            'discard'
        default = 'ok' the default selected button
        infotext = 'extended information text'
        detailedtext = 'Some details'
        """
        msgBox = QtGui.QMessageBox(self)
        msgBox.setWindowTitle(title)

        # Icon
        if icon == "warning":
            icon = QtGui.QMessageBox.Warning
        else:
            icon = QtGui.QMessageBox.Information

        msgBox.setIcon(icon)

        # Text
        msgBox.setText(text)

        if infoText:
            msgBox.setInformativeText(infoText)

        if detailedtext:
            msgBox.setDetailedText(detailedtext)

        # buttons
        qbuttonDict = {
            "ok": QtGui.QMessageBox.Ok,
            "yes": QtGui.QMessageBox.Yes,
            "no": QtGui.QMessageBox.No,
            "cancel": QtGui.QMessageBox.Cancel,
            "discard": QtGui.QMessageBox.Discard,
        }
        for button in buttons:
            msgBox.addButton(qbuttonDict[button])

            if button == default:
                msgBox.setDefaultButton(qbuttonDict[button])

        ret = msgBox.exec_()

        for key, value in qbuttonDict.items():
            if value == ret:
                result = key
                break

        return result

    def print_internal(self, text):
        if not text.endswith("\n"):
            text += "\n"
        LOG.debug(str(text).strip())
        cursor = self.ui.command_output.textCursor()
        cursor.movePosition(cursor.End)
        cursor.insertText(text)
        cursor.movePosition(cursor.End)
        vbar = self.ui.command_output.verticalScrollBar()
        vbar.triggerAction(QtGui.QAbstractSlider.SliderToMaximum)
        self.ui.command_output.ensureCursorVisible()

    # --- mfix methods ---
    def set_mpi(self):
        " set MPI checkbox if more than one node selected "
        nodesi = int(self.ui.nodes_i.text())
        nodesj = int(self.ui.nodes_j.text())
        nodesk = int(self.ui.nodes_k.text())
        if max(nodesi, nodesj, nodesk) > 1:
            self.ui.dmp_button.setChecked(True)

    def set_nodes(self):
        " set all nodes to one if MPI checkbox is unchecked "
        if self.ui.dmp_button.isChecked():
            self.ui.nodes_i.setValue(1)
            self.ui.nodes_j.setValue(1)
            self.ui.nodes_k.setValue(1)

    def make_build_cmd(self):
        """ build mfix """
        mfix_home = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
        dmp = "--dmp" if self.ui.dmp_button.isChecked() else ""
        smp = "--smp" if self.ui.smp_button.isChecked() else ""
        return os.path.join(mfix_home, "configure_mfix --python %s %s && make -j pymfix" % (smp, dmp))

    def build_mfix(self):
        """ build mfix """
        self.build_thread.start_command(self.make_build_cmd(), self.get_project_dir())

    def clear_output(self):
        """ build mfix """
        self.run_thread.start_command(
            'echo "Removing:";' " ls *.LOG *.OUT *.RES *.SP? *.pvd *vtp;" " rm -f *.LOG *.OUT *.RES *.SP? *.pvd *vtp",
            self.get_project_dir(),
        )

    def run_mfix(self):
        """ build mfix """
        if not self.ui.dmp_button.isChecked():
            pymfix_exe = os.path.join(self.get_project_dir(), "pymfix")
        else:
            nodesi = int(self.ui.nodes_i.text())
            nodesj = int(self.ui.nodes_j.text())
            nodesk = int(self.ui.nodes_k.text())
            total = nodesi * nodesj * nodesk
            pymfix_exe = "mpirun -np {} ./pymfix NODESI={} NODESJ={} NODESK={}".format(total, nodesi, nodesj, nodesk)

        build_and_run_cmd = "{} && {}".format(self.make_build_cmd(), pymfix_exe)
        self.run_thread.start_command(build_and_run_cmd, self.get_project_dir())

    def update_residuals(self):
        self.ui.residuals.setText(self.updater.residuals)
        if self.updater.job_done:
            self.ui.mfix_browser.setHTML("")

    def connect_mfix(self):
        """ connect to running instance of mfix """
        url = "http://{}:{}".format(self.ui.mfix_host.text(), self.ui.mfix_port.text())
        log = logging.getLogger(__name__)
        log.debug("trying to connect to {}".format(url))
        qurl = QUrl(url)
        self.ui.mfix_browser.load(qurl)

        self.updater = UpdateResidualsThread()
        self.updater.sig.connect(self.update_residuals)
        self.updater.start()

        self.change_pane("interact")

    # --- open/save/new ---
    def save_project(self):
        # make sure the button is not down
        self.ui.toolbutton_save.setDown(False)

        project_dir = self.settings.value("project_dir")

        # export geometry
        self.vtkwidget.export_stl(os.path.join(project_dir, "geometry.stl"))

        self.setWindowTitle("MFIX - %s" % project_dir)
        self.project.writeDatFile(os.path.join(project_dir, "mfix.dat"))

    def unsaved(self):
        project_dir = self.settings.value("project_dir")
        self.setWindowTitle("MFIX - %s *" % project_dir)

    def new_project(self, project_dir=None):
        if not project_dir:
            project_dir = str(
                QtGui.QFileDialog.getExistingDirectory(
                    self, "Create Project in Directory", "", QtGui.QFileDialog.ShowDirsOnly
                )
            )
        if len(project_dir) < 1:
            return
        try:
            shutil.copyfile("mfix.dat.template", os.path.join(project_dir, "mfix.dat"))
        except IOError:
            self.message(
                title="Warning",
                icon="warning",
                text=("You do not have write access to this " "directory.\n"),
                buttons=["ok"],
                default="ok",
            )
            return
        self.open_project(project_dir)

    def open_project(self, project_dir=None):
        """
        Open MFiX Project
        """
        # make sure the button is not left down
        self.ui.toolbutton_open.setDown(False)

        if not project_dir:
            project_dir = str(
                QtGui.QFileDialog.getExistingDirectory(
                    self, "Open Project Directory", "", QtGui.QFileDialog.ShowDirsOnly
                )
            )

        if len(project_dir) < 1:
            return

        writable = True
        try:
            import tempfile

            testfile = tempfile.TemporaryFile(dir=project_dir)
            testfile.close()
        except IOError:
            writable = False

        if not writable:
            self.message(
                title="Warning",
                icon="warning",
                text=("You do not have write access to this " "directory.\n"),
                buttons=["ok"],
                default="ok",
            )
            return

        mfix_dat = os.path.abspath(os.path.join(project_dir, "mfix.dat"))

        if not os.path.exists(mfix_dat):
            self.message(
                title="Warning",
                icon="warning",
                text=("mfix.dat file does not exist in this " "directory.\n"),
                buttons=["ok"],
                default="ok",
            )
            return

        self.settings.setValue("project_dir", project_dir)
        self.setWindowTitle("MFIX - %s" % project_dir)

        # read the file
        with open(mfix_dat, "r") as mfix_dat_file:
            src = mfix_dat_file.read()
        self.ui.mfix_dat_source.setPlainText(src)
        # self.mode_changed('developer')

        self.project.load_mfix_dat(mfix_dat)

        self.ui.energy_eq.setChecked(self.project["energy_eq"])