diff --git a/.github/workflows/devbuild.yml b/.github/workflows/devbuild.yml index f3760cc7..416b7160 100644 --- a/.github/workflows/devbuild.yml +++ b/.github/workflows/devbuild.yml @@ -153,7 +153,7 @@ jobs: - name: dependency by pip run: | sudo pip3 install setuptools - sudo pip3 install numpy pytest flake8 jsonschema + sudo pip3 install numpy matplotlib pytest flake8 jsonschema sudo pip3 install pyside6==$(qmake6 -query QT_VERSION) - name: dependency by manual script @@ -327,7 +327,7 @@ jobs: # suppress the warning of pip because brew forces PEP668 since python3.12 python3 -m pip -v install --upgrade setuptools python3 -m pip -v install --upgrade pip - python3 -m pip -v install --upgrade numpy pytest flake8 jsonschema + python3 -m pip -v install --upgrade numpy matplotlib pytest flake8 jsonschema # For now (2024/10/22), pyside6 6.6.3 does not support Python 3.13. # Use --ignore-requires-python to force installation. python3 -m pip -v install --upgrade pyside6==$(qmake -query QT_VERSION) --ignore-requires-python @@ -495,10 +495,10 @@ jobs: uses: jurplel/install-qt-action@v4 with: aqtversion: '==3.1.*' - version: '6.6.3' + version: '6.8.1' host: 'windows' target: 'desktop' - arch: 'win64_msvc2019_64' + arch: 'win64_msvc2022_64' modules: 'qt3d' cache: true @@ -508,7 +508,7 @@ jobs: - name: dependency by pip run: | - pip3 install -U numpy pytest jsonschema flake8 pybind11 pyside6==$(qmake -query QT_VERSION) + pip3 install -U numpy matplotlib pytest jsonschema flake8 pybind11 pyside6==$(qmake -query QT_VERSION) # Add PySide6 and Shiboken6 path into system path, that allow exe file can find # dll during runtime # If user needs to modified system path in github actions container diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d9dad6f7..1d9b0b6b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -106,7 +106,7 @@ jobs: - name: dependency by pip run: | sudo pip3 install setuptools - sudo pip3 install numpy pytest flake8 jsonschema pyside6==$(qmake6 -query QT_VERSION) + sudo pip3 install numpy matplotlib pytest flake8 jsonschema pyside6==$(qmake6 -query QT_VERSION) - name: dependency (manual) run: sudo ${GITHUB_WORKSPACE}/contrib/dependency/install.sh pybind11 @@ -236,7 +236,7 @@ jobs: # suppress the warning of pip because brew forces PEP668 since python3.12 python3 -m pip -v install --upgrade setuptools --break-system-packages python3 -m pip -v install --upgrade pip --break-system-packages - python3 -m pip -v install --upgrade numpy pytest flake8 jsonschema + python3 -m pip -v install --upgrade numpy matplotlib pytest flake8 jsonschema # For now (2024/10/22), pyside6 6.6.3 does not support Python 3.13. # Use --ignore-requires-python to force installation. python3 -m pip -v install --upgrade pyside6==$(qmake -query QT_VERSION) --ignore-requires-python diff --git a/modmesh/pilot/_gui.py b/modmesh/pilot/_gui.py index f3d7c8b5..b2faa7bf 100644 --- a/modmesh/pilot/_gui.py +++ b/modmesh/pilot/_gui.py @@ -39,6 +39,7 @@ enable = False try: from _modmesh import pilot as _vimpl # noqa: F401 + enable = True except ImportError: pass @@ -46,6 +47,7 @@ if enable: from PySide6.QtGui import QAction + from . import _mesh _from_impl = [ # noqa: F822 'R3DWidget', @@ -71,6 +73,9 @@ def _load(): del _load +_holder = {} + + def populate_menu(): wm = _vimpl.RManager.instance @@ -86,12 +91,8 @@ def _addAction(menu, text, tip, funcname): act.triggered.connect(lambda *a: func()) menu.addAction(act) - _addAction( - menu=wm.fileMenu, - text="New file (dummy)", - tip="Create new file", - funcname=lambda: print("This is only a demo: Create new file!"), - ) + _holder['gmsh_dialog'] = _mesh.GmshFileDialog(mgr=wm) + _holder['gmsh_dialog'].populate_menu() if sys.platform != 'darwin': _addAction( diff --git a/modmesh/pilot/_mesh.py b/modmesh/pilot/_mesh.py new file mode 100644 index 00000000..76227340 --- /dev/null +++ b/modmesh/pilot/_mesh.py @@ -0,0 +1,123 @@ +# Copyright (c) 2021, Yung-Yu Chen +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# - Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# - Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# - Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + + +""" +Show meshes. +""" + +import os + +from PySide6 import QtCore, QtWidgets, QtGui + +from .. import core + +__all__ = [ # noqa: F822 + 'GmshFileDialog', +] + + +def _add_menu_item(mainWindow, menu, text, tip, func): + act = QtGui.QAction(text, mainWindow) + act.setStatusTip(tip) + act.triggered.connect(func) + menu.addAction(act) + + +class GmshFileDialog(QtCore.QObject): + def __init__(self, *args, **kw): + self._mgr = kw.pop('mgr') + super(GmshFileDialog, self).__init__(*args, **kw) + self._diag = QtWidgets.QFileDialog() + self._diag.setFileMode(QtWidgets.QFileDialog.ExistingFile) + self._diag.setDirectory(self._get_initial_path()) + self._diag.setWindowTitle('Open Gmsh file ...') + + def run(self): + self._diag.open(self, QtCore.SLOT('on_finished()')) + + def populate_menu(self): + _add_menu_item( + mainWindow=self._mgr.mainWindow, + menu=self._mgr.fileMenu, + text="Open Gmsh file", + tip="Open Gmsh file", + func=self.run, + ) + + @QtCore.Slot() + def on_finished(self): + filenames = [] + for path in self._diag.selectedFiles(): + filenames.append(path) + self._load_gmsh_file(filename=filenames[0]) + + @staticmethod + def _get_initial_path(): + """ + Search for `tests/data/rectangle.msh` and return the directory holding + it. If not found, return an empty string. + + :return: The holding directory in absolute path or empty string. + """ + found = '' + for dp in ('.', core.__file__): + dp = os.path.dirname(os.path.abspath(dp)) + dp2 = os.path.dirname(dp) + while dp != dp2: + tp = os.path.join(dp, "tests", "data") + fp = os.path.join(tp, "rectangle.msh") + if os.path.exists(fp): + found = tp + break + dp = dp2 + dp2 = os.path.dirname(dp) + if found: + break + return found + + def _load_gmsh_file(self, filename): + if not os.path.exists(filename): + self._pycon.writeToHistory(f"{filename} does not exist\n") + return + + with open(filename, 'rb') as fobj: + data = fobj.read() + self._pycon.writeToHistory(f"gmsh mesh file {filename} is read\n") + gmsh = core.Gmsh(data) + mh = gmsh.to_block() + self._pycon.writeToHistory("StaticMesh object created from gmsh\n") + # Open a sub window for triangles and quadrilaterals: + w = self._mgr.add3DWidget() + w.updateMesh(mh) + w.showMark() + self._pycon.writeToHistory(f"nedge: {mh.nedge}\n") + + @property + def _pycon(self): + return self._mgr.pycon + +# vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/tests/test_pilot.py b/tests/test_pilot.py index e450f1e8..6c410ef2 100644 --- a/tests/test_pilot.py +++ b/tests/test_pilot.py @@ -68,17 +68,19 @@ class PilotCameraTB: @classmethod def setUpClass(cls): - widget = pilot.RManager.instance.setUp().add3DWidget() + if modmesh.HAS_PILOT: + widget = pilot.RManager.instance.setUp().add3DWidget() - if cls.camera_type is not None: - widget.setCameraType(cls.camera_type) + if cls.camera_type is not None: + widget.setCameraType(cls.camera_type) - cls.widget = widget - cls.camera = widget.camera + cls.widget = widget + cls.camera = widget.camera @classmethod def tearDownClass(cls): - cls.widget.close_and_destroy() + if modmesh.HAS_PILOT: + cls.widget.close_and_destroy() def angle_axis(self, angle_deg, axis): a = axis @@ -104,7 +106,8 @@ def normalize(self, vec): return vec / np.linalg.norm(vec) -@unittest.skipIf(GITHUB_ACTIONS, "GUI is not available in GitHub Actions") +@unittest.skipIf(GITHUB_ACTIONS or not modmesh.HAS_PILOT, + "GUI is not available in GitHub Actions") class PilotCommonCameraTC(PilotCameraTB, unittest.TestCase): def test_value_get_set(self): c = self.camera @@ -195,7 +198,8 @@ def test_translation(self): self.assertEqual(c.view_center[2], new_view_center[2]) -@unittest.skipIf(GITHUB_ACTIONS, "GUI is not available in GitHub Actions") +@unittest.skipIf(GITHUB_ACTIONS or not modmesh.HAS_PILOT, + "GUI is not available in GitHub Actions") class PilotFPSCameraTC(PilotCameraTB, unittest.TestCase): camera_type = "fps" @@ -266,7 +270,8 @@ def test_rotation(self): self.assertAlmostEqual(view_center[2], new_view_center[2], delta=1e-5) -@unittest.skipIf(GITHUB_ACTIONS, "GUI is not available in GitHub Actions") +@unittest.skipIf(GITHUB_ACTIONS or not modmesh.HAS_PILOT, + "GUI is not available in GitHub Actions") class PilotOrbitCameraTC(PilotCameraTB, unittest.TestCase): camera_type = "orbit"