From 25085552efb64155689452013255a77d13fa8667 Mon Sep 17 00:00:00 2001 From: jennyyen Date: Sat, 6 Jul 2024 21:50:04 +0800 Subject: [PATCH 01/28] Approximate a naca 4-digit airfoil with cubic bezier curves remove unncessary changes, blank line, and debug prints --- modmesh/gui/naca.py | 48 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/modmesh/gui/naca.py b/modmesh/gui/naca.py index c149912c..7b48cf7c 100644 --- a/modmesh/gui/naca.py +++ b/modmesh/gui/naca.py @@ -94,16 +94,50 @@ def calc_naca4_points(number, npoint, open_trailing_edge=False, return ret -def draw_naca4(world, number, npoint, fac, off_x, off_y, **kw): +def calc_sample_points(points): + # determine the number of sample points for a cubic bezier curve + spacing = 0.01 + segments = np.hypot((points[:-1, 0] - points[1:, 0]), + (points[:-1, 1] - points[1:, 1])) // spacing + nsample = np.where(segments > 2, segments - 1, 2).astype(int) + return nsample + + +def linear_approximate(world, points): + for it in range(points.shape[0] - 1): + world.add_edge(points[it, 0], points[it, 1], 0, + points[it + 1, 0], points[it + 1, 1], 0) + return + + +def cubic_bezier_approximate(world, points, nsample=None): + Vector = core.Vector3dFp64 + + if nsample is None: + nsample = calc_sample_points(points) + for it in range(points.shape[0] - 1): + p1 = points[it] + (1 / 3) * (points[it + 1] - points[it]) + p2 = points[it] + (2 / 3) * (points[it + 1] - points[it]) + + b = world.add_bezier([Vector(points[it, 0], points[it, 1], 0), + Vector(p1[0], p1[1], 0), + Vector(p2[0], p2[1], 0), + Vector(points[it + 1, 0], points[it + 1, 1], 0)]) + b.sample(nsample[it]) + return + + +def draw_naca4(world, number, npoint, fac, off_x, off_y, + use_bezier=False, **kw): crds = calc_naca4_points(number=number, npoint=npoint, **kw) crds *= fac # scaling factor crds[:, 0] += off_x # offset in x crds[:, 1] += off_y # offset in y - for it in range(crds.shape[0] - 1): - e = world.add_edge(crds[it, 0], crds[it, 1], 0, - crds[it + 1, 0], crds[it + 1, 1], 0) - print(f"{it}: {e}") - print("nedge:", world.nedge) + + if use_bezier: + cubic_bezier_approximate(world, crds) + else: + linear_approximate(world, crds) return crds @@ -113,7 +147,7 @@ def runmain(): """ w = core.WorldFp64() draw_naca4(w, number='0012', npoint=101, fac=5.0, off_x=0.0, off_y=2.0, - open_trailing_edge=False, cosine_spacing=True) + open_trailing_edge=False, use_bezier=True, cosine_spacing=True) wid = view.mgr.add3DWidget() wid.updateWorld(w) wid.showMark() From 740dcbc8345958e8a57c98463c593d03a0a02d61 Mon Sep 17 00:00:00 2001 From: Yung-Yu Chen Date: Sun, 11 Aug 2024 16:45:40 +0800 Subject: [PATCH 02/28] Refactor the NACA airfoil plotting code for further development * A new Python package modmesh.pilot.airfoil is created to house the airfoil code. * It is put under the newly created package modmesh.pilot, which is considered to be the successor of modmesh.view for housing graphical code. * The point definition code is collected in the class Naca4. * The drawing code is collected in the class Naca4Sampler. The name is not very intuitive and may need reconsideration in the future. --- modmesh/gui/naca.py | 119 +------------- modmesh/pilot/__init__.py | 38 +++++ modmesh/pilot/airfoil/__init__.py | 264 ++++++++++++++++++++++++++++++ tests/test_pilot_airfoil.py | 59 +++++++ 4 files changed, 370 insertions(+), 110 deletions(-) create mode 100644 modmesh/pilot/__init__.py create mode 100644 modmesh/pilot/airfoil/__init__.py create mode 100644 tests/test_pilot_airfoil.py diff --git a/modmesh/gui/naca.py b/modmesh/gui/naca.py index 7b48cf7c..cc5ad2ae 100644 --- a/modmesh/gui/naca.py +++ b/modmesh/gui/naca.py @@ -29,116 +29,9 @@ Show NACA airfoil shape """ -import numpy as np - from modmesh import core from modmesh import view - - -def calc_naca4_points(number, npoint, open_trailing_edge=False, - cosine_spacing=False): - """ - Returns points in [0 1] for the given 4 digit NACA number string. - Reference from - https://ntrs.nasa.gov/api/citations/19930091108/downloads/19930091108.pdf - and https://github.com/dgorissen/naca - """ - - camber = float(number[0]) / 100.0 - pos = float(number[1]) / 10.0 - thick = float(number[2:]) / 100.0 - - a0 = +0.2969 - a1 = -0.1260 - a2 = -0.3516 - a3 = +0.2843 - a4 = -0.1015 if open_trailing_edge else -0.1036 - - if cosine_spacing: - xc = 0.5 * (1.0 - np.cos(np.linspace(0.0, np.pi, npoint + 1))) - else: - xc = np.linspace(0.0, 1.0, npoint + 1) - - xh = np.sqrt(xc) - x2 = np.power(xc, 2) - x3 = np.power(xc, 3) - x4 = np.power(xc, 4) - yt = 5.0 * thick * (a0 * xh + a1 * xc + a2 * x2 + a3 * x3 + a4 * x4) - - if pos == 0: - xu = xc - yu = yt - xl = xc - yl = -yt - else: - xc1 = xc[xc <= pos] - yc1 = camber / np.power(pos, 2) * xc1 * (2 * pos - xc1) - dyc1_dx = camber / np.power(pos, 2) * (2 * pos - 2 * xc1) - - xc2 = xc[xc > pos] - yc2 = camber / np.power(1 - pos, 2) * (1 - 2 * pos + xc2) * (1 - xc2) - dyc2_dx = camber / np.power(1 - pos, 2) * (2 * pos - 2 * xc2) - - yc = np.concatenate([yc1, yc2]) - dyc_dx = np.concatenate([dyc1_dx, dyc2_dx]) - theta = np.arctan(dyc_dx) - - xu = xc - yt * np.sin(theta) - yu = yc + yt * np.cos(theta) - xl = xc + yt * np.sin(theta) - yl = yc - yt * np.cos(theta) - - xr = np.concatenate([xu[::-1], xl[1:]]) - yr = np.concatenate([yu[::-1], yl[1:]]) - ret = np.vstack([xr, yr]).T.copy() - return ret - - -def calc_sample_points(points): - # determine the number of sample points for a cubic bezier curve - spacing = 0.01 - segments = np.hypot((points[:-1, 0] - points[1:, 0]), - (points[:-1, 1] - points[1:, 1])) // spacing - nsample = np.where(segments > 2, segments - 1, 2).astype(int) - return nsample - - -def linear_approximate(world, points): - for it in range(points.shape[0] - 1): - world.add_edge(points[it, 0], points[it, 1], 0, - points[it + 1, 0], points[it + 1, 1], 0) - return - - -def cubic_bezier_approximate(world, points, nsample=None): - Vector = core.Vector3dFp64 - - if nsample is None: - nsample = calc_sample_points(points) - for it in range(points.shape[0] - 1): - p1 = points[it] + (1 / 3) * (points[it + 1] - points[it]) - p2 = points[it] + (2 / 3) * (points[it + 1] - points[it]) - - b = world.add_bezier([Vector(points[it, 0], points[it, 1], 0), - Vector(p1[0], p1[1], 0), - Vector(p2[0], p2[1], 0), - Vector(points[it + 1, 0], points[it + 1, 1], 0)]) - b.sample(nsample[it]) - return - - -def draw_naca4(world, number, npoint, fac, off_x, off_y, - use_bezier=False, **kw): - crds = calc_naca4_points(number=number, npoint=npoint, **kw) - crds *= fac # scaling factor - crds[:, 0] += off_x # offset in x - crds[:, 1] += off_y # offset in y - - if use_bezier: - cubic_bezier_approximate(world, crds) - else: - linear_approximate(world, crds) - return crds +from modmesh.pilot import airfoil def runmain(): @@ -146,8 +39,14 @@ def runmain(): A simple example for drawing a couple of cubic Bezier curves. """ w = core.WorldFp64() - draw_naca4(w, number='0012', npoint=101, fac=5.0, off_x=0.0, off_y=2.0, - open_trailing_edge=False, use_bezier=True, cosine_spacing=True) + naca4 = airfoil.Naca4(number='0012', open_trailing_edge=False, + cosine_spacing=False) + sampler = airfoil.Naca4Sampler(w, naca4) + sampler.populate_points(npoint=101, fac=5.0, off_x=0.0, off_y=2.0) + if False: + sampler.draw_line() + else: + sampler.draw_cbc() wid = view.mgr.add3DWidget() wid.updateWorld(w) wid.showMark() diff --git a/modmesh/pilot/__init__.py b/modmesh/pilot/__init__.py new file mode 100644 index 00000000..2f2e72af --- /dev/null +++ b/modmesh/pilot/__init__.py @@ -0,0 +1,38 @@ +# Copyright (c) 2024, 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. + + +""" +Drawing and visualization sub-system of modmesh. +""" + +from . import airfoil # noqa: F401 + +__all__ = [ + 'airfoil', +] + +# vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/modmesh/pilot/airfoil/__init__.py b/modmesh/pilot/airfoil/__init__.py new file mode 100644 index 00000000..0c00e82c --- /dev/null +++ b/modmesh/pilot/airfoil/__init__.py @@ -0,0 +1,264 @@ +# Copyright (c) 2024, 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. + + +""" +NACA airfoil shape +""" + +import numpy as np + +from modmesh import core + +__all__ = [ + 'Naca4', + 'Naca4Sampler', +] + + +class Naca4(object): + """ + Returns points in [0 1] for the given 4 digit NACA number string. + Reference from + https://ntrs.nasa.gov/api/citations/19930091108/downloads/19930091108.pdf + and https://github.com/dgorissen/naca + + See also https://en.wikipedia.org/wiki/NACA_airfoil + """ + + # FUTURE: This class does not use numpy array batch code because the code + # should be ported into C++ for speed. + + A0 = +0.2969 + A1 = -0.1260 + A2 = -0.3516 + A3 = +0.2843 + A4O = -0.1015 # non-zero/open trailing edge + A4C = -0.1036 # zero/close trailing edge + + def __init__(self, number='', camber=None, pos=None, thick=None, + open_trailing_edge=False, cosine_spacing=False): + # Parse the 4-digit NACA number if it is valid. + if len(number) == 4: + if camber is None: + camber = float(number[0]) / 100.0 + if pos is None: + pos = float(number[1]) / 10.0 + if thick is None: + thick = float(number[2:]) / 100.0 + # Check for the required arguments (may be parsed from number). + if camber is None: + raise ValueError('camber cannot be None') + if pos is None: + raise ValueError('pos cannot be None') + if thick is None: + raise ValueError('thick cannot be None') + # Set the attributes. + self.camber = camber + self.pos = pos + self.thick = thick + self.open_trailing_edge = open_trailing_edge + self.cosine_spacing = cosine_spacing + + def calc_yt(self, xc): + r""" + Calculate the y value for the symmetric profile. + + .. math:: + + y_t = 5t(0.2969 \sqrt{x} - 0.1260 x - 0.3516 x^2 + 0.2843 x^3 + - 0.1015 x^4) + + Zero (close) trailing edge: + + .. math:: + + y_t = 5t(0.2969 \sqrt{x} - 0.1260 x - 0.3516 x^2 + 0.2843 x^3 + - 0.1036x^4) + + :param xc: Location along the x direction + :return: The y value (positive) + """ + xh = np.sqrt(xc) + x2 = xc * xc + x3 = x2 * xc + x4 = x3 * xc + a0 = self.A0 + a1 = self.A1 + a2 = self.A2 + a3 = self.A3 + a4 = self.A4O if self.open_trailing_edge else self.A4C + t = self.thick + yt = 5.0 * t * (a0 * xh + a1 * xc + a2 * x2 + a3 * x3 + a4 * x4) + return yt + + def calc_ul(self, xc): + r""" + Calculate the coordinates (xu, yu) and (xl, yl) (u for upper and l for + lower) at the corresponding location on the center/camber. + + For asymmetric camber (pos and camber are non-zero), the mean camber + line is calculated (:math:`m` is the maximum camber, :math:`p` is the + position of the maximum camber): + + .. math: + + y_c = \frac{m}{p^2}(2px - x^2), \quad 0 \le x \le p \\ + y_c = \frac{m}{(1-p)^2}(1 - 2p + 2px - x^2), \quad p \le x \le 1 + + The thickness needs to be perpendicular to the camber line, the upper + and lower profile should be calculated by: + + .. math: + + x_u = x - y_t\sin\theta , \; y_u = y_c + y_t\cos\theta \\ + x_l = x + y_t\sin\theta , \; y_l = y_c - y_t\cos\theta + + The angle :math:`\theta` should be determined by: + + .. math: + + \theta = \arctan\frac{\mathrm{d}y_c}{\mathrm{d}x} \\ + \frac{\mathrm{d}y_c}{\mathrm{d}x} = \frac{2m}{p^2}(p-x), + \quad 0 \le x \le p \\ + \frac{\mathrm{d}y_c}{\mathrm{d}x} = \frac{2m}{(1-p)^2}(p-x), + \quad p \le x \le 1 + + :param xc: x coordinate at center/camber + :return: xu, yu, xl, yl + """ + yt = self.calc_yt(xc) + pos = self.pos + if pos == 0: + return xc, yt, xc, -yt + else: + camber = self.camber + if xc <= pos: + xc1 = xc + yc1 = camber / np.power(pos, 2) * xc1 * (2 * pos - xc1) + dyc1_dx = camber / np.power(pos, 2) * (2 * pos - 2 * xc1) + yc = yc1 + dyc_dx = dyc1_dx + else: + xc2 = xc + yc2 = camber / np.power(1 - pos, 2) * (1 - 2 * pos + xc2) * ( + 1 - xc2) + dyc2_dx = camber / np.power(1 - pos, 2) * (2 * pos - 2 * xc2) + yc = yc2 + dyc_dx = dyc2_dx + + theta = np.arctan(dyc_dx) + + return (xc - yt * np.sin(theta), yc + yt * np.cos(theta), + xc + yt * np.sin(theta), yc - yt * np.cos(theta)) + + def calc_points(self, npoint): + """ + :param npoint: Number of sample points on the center/camber + :return: Arrays for xu, yu, xl, yl + """ + # Make xc array. + if self.cosine_spacing: + _ = np.linspace(0.0, np.pi, npoint + 1, dtype='float64') + xcarr = 0.5 * (1.0 - np.cos(_)) + else: + xcarr = np.linspace(0.0, 1.0, npoint + 1, dtype='float64') + # Prepare and populate arrays for (xu, yu), (xl, yl) + xuarr = np.empty_like(xcarr) + yuarr = np.empty_like(xcarr) + xlarr = np.empty_like(xcarr) + ylarr = np.empty_like(xcarr) + for i, xc in enumerate(xcarr): + xuarr[i], yuarr[i], xlarr[i], ylarr[i] = self.calc_ul(xc) + # Make return array. + xrarr = np.concatenate([xuarr[::-1], xlarr[1:]]) + yrarr = np.concatenate([yuarr[::-1], ylarr[1:]]) + ret = np.vstack([xrarr, yrarr]).T.copy() + return ret + + +class Naca4Sampler(object): + """ + Sample the profile of Naca4 airfoil. + """ + + def __init__(self, world, naca4): + self.world = world + self.naca4 = naca4 + self.points = None + + def populate_points(self, npoint, fac, off_x, off_y): + """ + Populate the points on the airfoil profile. + + :param npoint: Number of points + :param fac: Scaling factor + :param off_x: Offset in x + :param off_y: Offset in y + :return: None + """ + self.points = self.naca4.calc_points(npoint) + self.points *= fac + self.points[:, 0] += off_x + self.points[:, 1] += off_y + + def draw_line(self): + """ + Draw by connecting points using line segments. + + :return: None + """ + points = self.points + world = self.world + for it in range(points.shape[0] - 1): + world.add_edge(points[it, 0], points[it, 1], 0, + points[it + 1, 0], points[it + 1, 1], 0) + + def draw_cbc(self, spacing=0.01): + """ + Draw by connecting points using cubic Bezier curves. + + :param spacing: Spacing for sampling cubic Bezier curve. + :return: None + """ + Vector = core.Vector3dFp64 + points = self.points + world = self.world + + segments = np.hypot((points[:-1, 0] - points[1:, 0]), + (points[:-1, 1] - points[1:, 1])) // spacing + nsample = np.where(segments > 2, segments - 1, 2).astype(int) + for it in range(points.shape[0] - 1): + p1 = points[it] + (1 / 3) * (points[it + 1] - points[it]) + p2 = points[it] + (2 / 3) * (points[it + 1] - points[it]) + b = world.add_bezier([Vector(points[it, 0], points[it, 1], 0), + Vector(p1[0], p1[1], 0), + Vector(p2[0], p2[1], 0), + Vector(points[it + 1, 0], points[it + 1, 1], + 0)]) + b.sample(nsample[it]) + +# vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/tests/test_pilot_airfoil.py b/tests/test_pilot_airfoil.py new file mode 100644 index 00000000..da71220a --- /dev/null +++ b/tests/test_pilot_airfoil.py @@ -0,0 +1,59 @@ +# Copyright (c) 2024, 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. + + +import unittest + +import modmesh as mm +from modmesh.pilot import airfoil + + +class Naca4TC(unittest.TestCase): + def test_npoint(self): + def _check_size(naca4): + points = naca4.calc_points(5) + self.assertEqual((11, 2), points.shape) + points = naca4.calc_points(11) + self.assertEqual((23, 2), points.shape) + + _check_size(airfoil.Naca4(number='0012', open_trailing_edge=False, + cosine_spacing=False)) + _check_size(airfoil.Naca4(number='0012', open_trailing_edge=True, + cosine_spacing=False)) + _check_size(airfoil.Naca4(number='0012', open_trailing_edge=True, + cosine_spacing=False)) + _check_size(airfoil.Naca4(number='0012', open_trailing_edge=True, + cosine_spacing=True)) + + +class Naca4SamplerTC(unittest.TestCase): + def test_construction(self): + w = mm.WorldFp64() + naca4 = airfoil.Naca4(number='0012', open_trailing_edge=False, + cosine_spacing=False) + airfoil.Naca4Sampler(w, naca4) + +# vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: From 8007f61176f5fdf3ba548d746fc57abb81fcbcf3 Mon Sep 17 00:00:00 2001 From: Yung-Yu Chen Date: Sun, 11 Aug 2024 17:04:31 +0800 Subject: [PATCH 03/28] Add missing packages in setup.py --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index eb4fe8d4..462f0376 100644 --- a/setup.py +++ b/setup.py @@ -68,6 +68,9 @@ def main(): 'modmesh', 'modmesh.onedim', 'modmesh.app', + 'modmesh.gui', + 'modmesh.pilot', + 'modmesh.pilot.airfoil', ], ext_modules=[CMakeExtension("_modmesh")], cmdclass={'build_ext': cmake_build_ext}, From 3e840ce1ef87ffc4d6c03e7fa3b76eef28cc2ef6 Mon Sep 17 00:00:00 2001 From: Yung-Yu Chen Date: Sun, 11 Aug 2024 17:36:53 +0800 Subject: [PATCH 04/28] Temporarily turn off the linter run on macos-14 On 20240811, for a while (weeks or months), clang-tidy (llvm@16) runs extremely slowly on macos-14 (i.e., macos-14-arm64), but not macos-13. Temporarily turn off the linter run on macos-14 and revisit in the future. --- .github/workflows/lint.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7c42cfa1..71d3a8fa 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -177,7 +177,12 @@ jobs: strategy: matrix: # https://github.com/actions/runner-images/blob/main/images/macos/macos-13-Readme.md - os: [macos-13, macos-14] + # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md + # 20240811: clang-tidy (llvm@16) runs extremely slowly on macos-14 + # (i.e., macos-14-arm64), but not macos-13. Temporarily turn off the + # linter run on macos-14 and revisit in the future. + #os: [macos-13, macos-14] + os: [macos-13] cmake_build_type: [Debug] fail-fast: false From 26f0f911745cd20933d49ce4a7e2221d9178d337 Mon Sep 17 00:00:00 2001 From: threemonth03 Date: Mon, 29 Jul 2024 02:44:09 +0800 Subject: [PATCH 05/28] Rebuild the call profiler serializer with general serializer Modify the profiler to ensure support for re-entrancy. --- cpp/modmesh/toggle/CMakeLists.txt | 1 + cpp/modmesh/toggle/RadixTree.cpp | 188 +----- cpp/modmesh/toggle/RadixTree.hpp | 93 ++- cpp/modmesh/toggle/SerializableProfiler.hpp | 137 +++++ cpp/modmesh/toggle/pymod/wrap_profile.cpp | 5 +- gtests/CMakeLists.txt | 1 - .../test_nopython_callprofilerserializer.cpp | 554 ------------------ tests/test_callprofiler.py | 97 +++ 8 files changed, 302 insertions(+), 774 deletions(-) create mode 100644 cpp/modmesh/toggle/SerializableProfiler.hpp delete mode 100644 gtests/test_nopython_callprofilerserializer.cpp diff --git a/cpp/modmesh/toggle/CMakeLists.txt b/cpp/modmesh/toggle/CMakeLists.txt index de6e6e08..b21da261 100644 --- a/cpp/modmesh/toggle/CMakeLists.txt +++ b/cpp/modmesh/toggle/CMakeLists.txt @@ -7,6 +7,7 @@ set(MODMESH_TOGGLE_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/profile.hpp ${CMAKE_CURRENT_SOURCE_DIR}/RadixTree.hpp ${CMAKE_CURRENT_SOURCE_DIR}/toggle.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/SerializableProfiler.hpp CACHE FILEPATH "" FORCE) set(MODMESH_TOGGLE_SOURCES diff --git a/cpp/modmesh/toggle/RadixTree.cpp b/cpp/modmesh/toggle/RadixTree.cpp index 982cda22..a487c832 100644 --- a/cpp/modmesh/toggle/RadixTree.cpp +++ b/cpp/modmesh/toggle/RadixTree.cpp @@ -45,8 +45,33 @@ void CallProfiler::reset() } m_radix_tree.reset(); m_cancel_callbacks.clear(); + m_pending_nodes.clear(); } +// Called when a function starts +void CallProfiler::start_caller(const std::string & caller_name, const std::function & cancel_callback) +{ + m_cancel_callbacks.push_back(cancel_callback); + m_radix_tree.entry(caller_name); + CallerProfile & callProfile = m_radix_tree.get_current_node()->data(); + callProfile.caller_name = caller_name; + callProfile.start_stopwatch(); +} + +// Called when a function ends +void CallProfiler::end_caller() +{ + CallerProfile & call_profile = m_radix_tree.get_current_node()->data(); + call_profile.stop_stopwatch(); // Update profiling information to the pending time and count + m_pending_nodes.insert(m_radix_tree.get_current_node()); + m_radix_tree.move_current_to_parent(); // Pop the caller from the call stack + + if (m_radix_tree.get_current_node() == m_radix_tree.get_root()) // If the root function ends, update all pending nodes and stable items + { + update_pending_nodes(); + m_radix_tree.update_stable_items(); + } +} // NOLINTNEXTLINE(misc-no-recursion) void CallProfiler::print_profiling_result(const RadixTreeNode & node, const int depth, std::ostream & outstream) const { @@ -116,169 +141,6 @@ void CallProfiler::print_statistics(const RadixTreeNode & node, s TimeRegistry::me().clear(); } -void CallProfilerSerializer::serialize_call_profiler(const CallProfiler & profiler, std::ostream & outstream) -{ - // Serialize the RadixTree in the CallProfiler. - outstream << R"({)" << '\n'; - CallProfilerSerializer::serialize_radix_tree(profiler, outstream); - outstream << R"(})" << '\n'; -} - -void CallProfilerSerializer::serialize_radix_tree(const CallProfiler & profiler, std::ostream & outstream) -{ - // Serialize the RadixTree. - outstream << R"( "radix_tree": {)" << '\n'; - outstream << R"( "current_node": )" << profiler.radix_tree().get_current_node()->key() << R"(,)" << '\n'; - outstream << R"( "unique_id": )" << profiler.radix_tree().get_unique_node() << R"(,)" << '\n'; - CallProfilerSerializer::serialize_id_map(profiler.radix_tree().get_id_map(RadixTree::CallProfilerPK()), outstream); - CallProfilerSerializer::serialize_radix_tree_nodes(profiler.radix_tree().get_current_node(), outstream); - outstream << R"( })" << '\n'; -} - -void CallProfilerSerializer::serialize_id_map(const std::unordered_map & id_map, std::ostream & outstream) -{ - // Serialize the unordered_map in RadixTree. - outstream << R"( "id_map": {)"; - - // If the id_map is empty, close the map at the same line. - if (id_map.empty()) - { - outstream << R"(},)" << '\n'; - } - else - { - // Newline after the opening brace. - outstream << '\n'; - - bool is_first = true; - for (const auto & [key, value] : id_map) - { - // Avoid the trailing comma for the first element. - if (!is_first) - { - outstream << R"(,)" << '\n'; - } - is_first = false; - outstream << R"( ")" << key << R"(": )" << value; - } - - // Newline after the last element. - outstream << '\n'; - outstream << R"( },)" << '\n'; - } -} - -void CallProfilerSerializer::serialize_radix_tree_nodes(const RadixTreeNode * node, std::ostream & outstream) -{ - // Serialize all the RadixTreeNodes in RadixTree in a breadth-first manner. - outstream << R"( "nodes": [)"; - - // Give each node a unique number - int unique_node_number = -1; - - std::queue *> nodes_buffer; - CallProfilerSerializer::node_to_number_map_type node_to_unique_number; - - nodes_buffer.push(node); - node_to_unique_number[node] = unique_node_number; - bool is_first_node = true; - - // BFS algorithm - while (!nodes_buffer.empty()) - { - const int nodes_buffer_size = nodes_buffer.size(); - for (int i = 0; i < nodes_buffer_size; ++i) - { - const RadixTreeNode * current_node = nodes_buffer.front(); - nodes_buffer.pop(); - - for (const auto & child : current_node->children()) - { - ++unique_node_number; - nodes_buffer.push(child.get()); - node_to_unique_number[child.get()] = unique_node_number; - } - - CallProfilerSerializer::serialize_radix_tree_node(*current_node, is_first_node, node_to_unique_number, outstream); - is_first_node = false; - - // Remove the node from the map - node_to_unique_number.erase(current_node); - } - } - - // Newline after the last element. - outstream << '\n'; - outstream << R"( ])" << '\n'; -} - -void CallProfilerSerializer::serialize_radix_tree_node(const RadixTreeNode & node, bool is_first_node, CallProfilerSerializer::node_to_number_map_type & node_to_unique_number, std::ostream & outstream) -{ - // Serialize the RadixTreeNode to the json format. - - // Avoid the trailing comma for the first node. - if (!is_first_node) - { - outstream << R"(,)" << '\n'; - outstream << R"( {)" << '\n'; - } - else - { - outstream << R"({)" << '\n'; - } - - outstream << R"( "unique_number": )" << node_to_unique_number[&node] << R"(,)" << '\n'; - outstream << R"( "key": )" << node.key() << R"(,)" << '\n'; - outstream << R"( "name": ")" << node.name() << R"(",)" << '\n'; - CallProfilerSerializer::serialize_caller_profile(node.data(), outstream); - CallProfilerSerializer::serialize_radix_tree_node_children(node.children(), node_to_unique_number, outstream); - - outstream << R"( })"; -} - -void CallProfilerSerializer::serialize_radix_tree_node_children(const CallProfilerSerializer::child_list_type & children, CallProfilerSerializer::node_to_number_map_type & node_to_unique_number, std::ostream & outstream) -{ - // Serialize the children list in RadixTreeNode. - outstream << R"( children": [)"; - - // If the children list is empty, close the list at the same line. - if (children.empty()) - { - outstream << R"(])" << '\n'; - } - - else - { - outstream << '\n'; - - bool is_first_child = true; - for (const auto & child : children) - { - // Avoid the trailing comma. - if (!is_first_child) - { - outstream << R"(,)" << '\n'; - } - is_first_child = false; - outstream << R"( )" << node_to_unique_number[child.get()] << R"()"; - } - outstream << '\n'; - outstream << R"( ])" << '\n'; - } -} - -void CallProfilerSerializer::serialize_caller_profile(const CallerProfile & profile, std::ostream & outstream) -{ - // Serialize the CallerProfile to the json format. - outstream << R"( "data": {)" << '\n'; - outstream << R"( "start_time": )" << profile.start_time.time_since_epoch().count() << R"(,)" << '\n'; - outstream << R"( "caller_name": ")" << profile.caller_name << R"(",)" << '\n'; - outstream << R"( "total_time": )" << profile.total_time.count() << R"(,)" << '\n'; - outstream << R"( "call_count": )" << profile.call_count << R"(,)" << '\n'; - outstream << R"( "is_running": )" << profile.is_running << '\n'; - outstream << R"( },)" << '\n'; -} - } /* end namespace modmesh */ // vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/cpp/modmesh/toggle/RadixTree.hpp b/cpp/modmesh/toggle/RadixTree.hpp index 8507e466..b0983b81 100644 --- a/cpp/modmesh/toggle/RadixTree.hpp +++ b/cpp/modmesh/toggle/RadixTree.hpp @@ -40,6 +40,7 @@ #include #include #include +#include namespace modmesh { @@ -121,8 +122,7 @@ namespace detail class CallProfilerTest; // for gtest } /* end namespace detail */ -class CallProfilerSerializer; // for declaration - +class SerializableRadixTree; // forward declaration template class RadixTree { @@ -165,7 +165,9 @@ class RadixTree m_root = std::move(std::make_unique>()); m_current_node = m_root.get(); m_id_map.clear(); + m_stable_id_map.clear(); m_unique_id = 0; + m_stable_unique_id = 0; } bool is_root() const @@ -173,19 +175,18 @@ class RadixTree return m_current_node == m_root.get(); } - RadixTreeNode * get_root() const { return m_root.get(); } - RadixTreeNode * get_current_node() const { return m_current_node; } - key_type get_unique_node() const { return m_unique_id; } - - class CallProfilerPK + void update_stable_items() { - private: - CallProfilerPK() = default; - friend CallProfilerSerializer; - friend detail::CallProfilerTest; - }; + m_stable_id_map = m_id_map; + m_stable_unique_id = m_unique_id; + } - const std::unordered_map & get_id_map(CallProfilerPK const &) const { return m_id_map; } + RadixTreeNode * get_root() const { return m_root.get(); } + RadixTreeNode * get_current_node() const { return m_current_node; } + const key_type get_unique_node() const { return m_unique_id; } + const key_type get_stable_unique_node() const { return m_stable_unique_id; } + const std::unordered_map & get_stable_id_map() const { return m_stable_id_map; } + const std::unordered_map & get_id_map() const { return m_id_map; } private: key_type get_id(const std::string & name) @@ -198,7 +199,9 @@ class RadixTree std::unique_ptr> m_root; RadixTreeNode * m_current_node; std::unordered_map m_id_map; + std::unordered_map m_stable_id_map; // Re-entrant safe key_type m_unique_id = 0; + key_type m_stable_unique_id = 0; // Re-entrant safe }; /* end class RadixTree */ // The profiling result of the caller @@ -218,10 +221,18 @@ struct CallerProfile call_count++; } + void update_stable_items() + { + stable_total_time = total_time; + stable_call_count = call_count; + } + std::chrono::high_resolution_clock::time_point start_time; std::string caller_name; std::chrono::nanoseconds total_time = std::chrono::nanoseconds(0); /// use nanoseconds to have higher precision + std::chrono::nanoseconds stable_total_time = std::chrono::nanoseconds(0); // Re-entrant safe int call_count = 0; + int stable_call_count = 0; // Re-entrant safe bool is_running = false; }; /* end struct CallerProfile */ @@ -250,23 +261,11 @@ class CallProfiler return m_radix_tree; } - // Called when a function starts - void start_caller(const std::string & caller_name, std::function cancel_callback) - { - m_cancel_callbacks.push_back(cancel_callback); - m_radix_tree.entry(caller_name); - CallerProfile & callProfile = m_radix_tree.get_current_node()->data(); - callProfile.caller_name = caller_name; - callProfile.start_stopwatch(); - } + /// Called when a function starts + void start_caller(const std::string & caller_name, const std::function & cancel_callback); - // Called when a function ends - void end_caller() - { - CallerProfile & call_profile = m_radix_tree.get_current_node()->data(); - call_profile.stop_stopwatch(); // Update profiling information - m_radix_tree.move_current_to_parent(); // Pop the caller from the call stack - } + /// Called when a function ends + void end_caller(); /// Print the profiling information void print_profiling_result(std::ostream & outstream) const @@ -274,7 +273,7 @@ class CallProfiler print_profiling_result(*(m_radix_tree.get_current_node()), 0, outstream); } - // Print the statistics of the profiling result + /// Print the statistics of the profiling result void print_statistics(std::ostream & outstream) const { print_statistics(*(m_radix_tree.get_current_node()), outstream); @@ -299,10 +298,19 @@ class CallProfiler void print_profiling_result(const RadixTreeNode & node, const int depth, std::ostream & outstream) const; static void print_statistics(const RadixTreeNode & node, std::ostream & outstream); + void update_pending_nodes() + { + for (auto & node : m_pending_nodes) + { + node->data().update_stable_items(); + } + m_pending_nodes.clear(); + } + private: RadixTree m_radix_tree; /// the data structure of the callers std::vector> m_cancel_callbacks; /// the callback to cancel the profiling from all probes - + std::set *> m_pending_nodes; /// The nodes that are not yet updated friend detail::CallProfilerTest; }; /* end class CallProfiler */ @@ -345,29 +353,6 @@ class CallProfilerProbe CallProfiler & m_profiler; }; /* end struct CallProfilerProbe */ -/// Utility to serialize and deserialize CallProfiler. -class CallProfilerSerializer -{ -public: - using child_list_type = std::list>>; - using node_to_number_map_type = std::unordered_map *, int>; - using key_type = typename RadixTree::key_type; - // It returns the json format of the CallProfiler. - static void serialize(const CallProfiler & profiler, std::ostream & outstream) - { - serialize_call_profiler(profiler, outstream); - } - -private: - static void serialize_call_profiler(const CallProfiler & profiler, std::ostream & outstream); - static void serialize_radix_tree(const CallProfiler & profiler, std::ostream & outstream); - static void serialize_id_map(const std::unordered_map & id_map, std::ostream & outstream); - static void serialize_radix_tree_nodes(const RadixTreeNode * node, std::ostream & outstream); - static void serialize_radix_tree_node(const RadixTreeNode & node, bool is_first_node, node_to_number_map_type & node_to_unique_number, std::ostream & outstream); - static void serialize_radix_tree_node_children(const child_list_type & children, node_to_number_map_type & node_to_unique_number, std::ostream & outstream); - static void serialize_caller_profile(const CallerProfile & profile, std::ostream & outstream); -}; /* end struct CallProfilerSerializer */ - #ifdef CALLPROFILER #ifdef _MSC_VER diff --git a/cpp/modmesh/toggle/SerializableProfiler.hpp b/cpp/modmesh/toggle/SerializableProfiler.hpp new file mode 100644 index 00000000..952d8051 --- /dev/null +++ b/cpp/modmesh/toggle/SerializableProfiler.hpp @@ -0,0 +1,137 @@ +#pragma once + +/* + * Copyright (c) 2024, Chun-Shih Chang + * + * 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. + */ + +#include +#include + +namespace modmesh +{ + +class SerializableRadixTreeNode : SerializableItem +{ + +public: + + using child_list_type = std::vector; + using key_type = typename RadixTree::key_type; + + SerializableRadixTreeNode() = default; + SerializableRadixTreeNode(SerializableRadixTreeNode const &) = default; + SerializableRadixTreeNode(SerializableRadixTreeNode &&) = default; + SerializableRadixTreeNode & operator=(SerializableRadixTreeNode const &) = default; + SerializableRadixTreeNode & operator=(SerializableRadixTreeNode &&) = default; + ~SerializableRadixTreeNode() = default; + SerializableRadixTreeNode(const RadixTreeNode * node) + : m_key(node->key()) + , m_name(node->name()) + , m_total_time(node->data().stable_total_time) + , m_call_count(node->data().stable_call_count) + { + for (const auto & child : node->children()) + { + if (child->data().stable_call_count > 0) + { + m_children.push_back(SerializableRadixTreeNode(child.get())); + } + } + } + + MM_DECL_SERIALIZABLE( + register_member("key", m_key); + register_member("name", m_name); + register_member("total_time", static_cast(m_total_time.count())); + register_member("call_count", m_call_count); + register_member("children", m_children);) + +private: + + /* + * In T data, 'bool is_running' and + * 'std::chrono::high_resolution_clock::time_point start_time' are not serialized + * because they are useless when deserialize the object. + */ + key_type m_key; + std::string m_name; + std::chrono::nanoseconds m_total_time; + int m_call_count; + child_list_type m_children; +}; /* end class SerializableRadixTreeNode */ + +class SerializableRadixTree : SerializableItem +{ + +public: + + using key_type = typename RadixTree::key_type; + SerializableRadixTree() = default; + SerializableRadixTree(SerializableRadixTree const &) = default; + SerializableRadixTree(SerializableRadixTree &&) = default; + SerializableRadixTree & operator=(SerializableRadixTree const &) = default; + SerializableRadixTree & operator=(SerializableRadixTree &&) = default; + ~SerializableRadixTree() = default; + SerializableRadixTree(const RadixTree & radix_tree) + : m_root(radix_tree.get_root()) + , m_id_map(radix_tree.get_stable_id_map()) + , m_unique_id(radix_tree.get_stable_unique_node()) + { + } + + MM_DECL_SERIALIZABLE( + register_member("radix_tree", m_root); + register_member("id_map", m_id_map); + register_member("unique_id", m_unique_id);) + +private: + + SerializableRadixTreeNode m_root; + std::unordered_map m_id_map; + key_type m_unique_id; +}; /* end class SerializableRadixTree */ + +/// Utility to serialize and deserialize CallProfiler. +class CallProfilerSerializer +{ + +public: + + // It returns the json format of the CallProfiler. + static std::string serialize(const CallProfiler & profiler) + { + auto radix_tree_curent_node = profiler.radix_tree().get_current_node(); + auto radix_tree_root = profiler.radix_tree().get_root(); + SerializableRadixTree serializable_radix_tree(profiler.radix_tree()); + return serializable_radix_tree.to_json(); + } + +}; /* end class CallProfilerSerializer */ + +} /* end namespace modmesh */ + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/cpp/modmesh/toggle/pymod/wrap_profile.cpp b/cpp/modmesh/toggle/pymod/wrap_profile.cpp index fdc213ac..77b24bba 100644 --- a/cpp/modmesh/toggle/pymod/wrap_profile.cpp +++ b/cpp/modmesh/toggle/pymod/wrap_profile.cpp @@ -27,6 +27,7 @@ */ #include // Must be the first include. +#include #include #include #include @@ -191,8 +192,8 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapCallProfiler : public WrapBase -#include -#include -#include - -#ifdef Py_PYTHON_H -#error "Python.h should not be included." -#endif - -#define CALLPROFILER 1 -#include -namespace modmesh -{ - -namespace detail -{ -class CallProfilerTest : public ::testing::Test -{ -protected: - void SetUp() override - { - CallProfiler & profiler = CallProfiler::instance(); - pProfiler = &profiler; - } - - RadixTree & radix_tree() - { - return pProfiler->m_radix_tree; - } - - CallProfiler * pProfiler; - -public: - using node_to_number_map_type = std::unordered_map *, int>; - using number_to_node_map_type = std::unordered_map *>; - void check_id_map_serialization(const CallProfiler & profiler, const std::vector & lines, bool functionIsExist); - void check_radix_tree_serialization(const CallProfiler & profiler, const std::vector & lines); - void check_call_profiler_serialization(const CallProfiler & profiler, const std::vector & lines); - void check_radix_tree_nodes_serialization(const CallProfiler & profiler, const std::vector & lines); - std::vector> split_nodes_info(const std::vector & lines); - void BFS_radix_tree(const RadixTreeNode * node, node_to_number_map_type & node_to_unique_number, number_to_node_map_type & unique_number_to_node); -}; - -void CallProfilerTest::check_call_profiler_serialization(const CallProfiler & profiler, const std::vector & lines) -{ - int numOfLines = lines.size(); - - // Expect the first and last line are parantheses. - auto lineCallProfilerBegin = "{"; - auto lineCallProfilerEnd = "}"; - EXPECT_EQ(lines[0], lineCallProfilerBegin); - EXPECT_EQ(lines[numOfLines - 1], lineCallProfilerEnd); - - // Check the radix_tree serialization. - check_radix_tree_serialization(profiler, lines); -} - -void CallProfilerTest::check_radix_tree_serialization(const CallProfiler & profiler, const std::vector & lines) -{ - int numOfLines = lines.size(); - - // Expect the second and second last line are the begin and the end of radix_tree. - auto lineRadixTreeBegin = R"( "radix_tree": {)"; - auto lineRadixTreeEnd = R"( })"; - EXPECT_EQ(lines[1], lineRadixTreeBegin); - EXPECT_EQ(lines[numOfLines - 2], lineRadixTreeEnd); - - // Expect the third line is the current_node. - auto lineCurrentNode = R"( "current_node": )"; - auto CurrentNodeValue = std::to_string(profiler.radix_tree().get_current_node()->key()); - EXPECT_EQ(lines[2], lineCurrentNode + CurrentNodeValue + ","); - - // Expect the fourth line is the unique_id. - auto lineUniqueId = R"( "unique_id": )"; - auto UniqueIdValue = std::to_string(profiler.radix_tree().get_unique_node()); - EXPECT_EQ(lines[3], lineUniqueId + UniqueIdValue + ","); - - bool functionIsExist = profiler.radix_tree().get_unique_node() > 0; - - // Check the id_map serialization. - check_id_map_serialization(profiler, lines, functionIsExist); - - // Check the nodes serialization. - check_radix_tree_nodes_serialization(profiler, lines); -} - -void CallProfilerTest::check_id_map_serialization(const CallProfiler & profiler, const std::vector & lines, bool functionIsExist) -{ - int numOfLines = lines.size(); - - // Expect the id_map contains no function. - if (!functionIsExist) - { - // Expect the fifth line is the id_map. - auto lineIdMap = R"( "id_map": {},)"; - EXPECT_EQ(lines[4], lineIdMap); - - // Expect the line after the end of the id_map is the begin of nodes. - auto lineNodesBegin = R"( "nodes": [{)"; - EXPECT_EQ(lines[5], lineNodesBegin); - } - - else - { - // Expect the fifth line is the begin of id_map. - auto lineIdMapBegin = R"( "id_map": {)"; - EXPECT_EQ(lines[4], lineIdMapBegin); - - auto lineIdMapEnd = R"( },)"; - int lineIdMapEndIndex = std::find(lines.begin(), lines.end(), lineIdMapEnd) - lines.begin(); - - // Expect the end of the id_map is exist. - EXPECT_NE(lineIdMapEndIndex, numOfLines); - - auto lineNodesBegin = R"( "nodes": [{)"; - // Expect the line after the end of the id_map is the begin of nodes. - EXPECT_EQ(lines[lineIdMapEndIndex + 1], lineNodesBegin); - - // Store the serialization of the key-value pair in the hash set. - std::unordered_set idMapPairStrings; - for (int i = 5; i < lineIdMapEndIndex; ++i) - { - if (i < lineIdMapEndIndex - 1) - { - // Expect the trailing comma for the last element. - EXPECT_EQ(lines[i].back(), ','); - idMapPairStrings.insert(lines[i]); - } - else - { - // Expect the last element has no trailing comma. - EXPECT_NE(lines[i].back(), ','); - idMapPairStrings.insert(lines[i] + ","); - } - } - - auto id_map = profiler.radix_tree().get_id_map(RadixTree::CallProfilerPK()); - - for (auto [key, value] : id_map) - { - // Expect the key-value pair is in the hash set. - auto line = R"( ")" + key + R"(": )" + std::to_string(value) + ","; - EXPECT_TRUE(idMapPairStrings.find(line) != idMapPairStrings.end()); - idMapPairStrings.erase(line); - } - } -} - -void CallProfilerTest::check_radix_tree_nodes_serialization(const CallProfiler & profiler, const std::vector & lines) -{ - int numOfLines = lines.size(); - - // Expect there exists the begin of nodes. - auto lineNodesBegin = R"( "nodes": [{)"; - int lineNodesBeginIndex = std::find(lines.begin(), lines.end(), lineNodesBegin) - lines.begin(); - EXPECT_NE(lineNodesBeginIndex, numOfLines); - - // Expect the third last line is the end of nodes. - auto lineNodesEnd = R"( ])"; - EXPECT_EQ(lines[numOfLines - 3], lineNodesEnd); - - // Split the nodes serialization into a vector of a vector of string. - std::vector> nodesInfo = split_nodes_info(lines); - - // BFS and number the radix tree nodes - node_to_number_map_type node_to_unique_number; - number_to_node_map_type unique_number_to_node; - BFS_radix_tree(profiler.radix_tree().get_current_node(), node_to_unique_number, unique_number_to_node); - - // Check the serialization of each node. - int numOfNodes = nodesInfo.size(); - for (int i = 0; i < numOfNodes; ++i) - { - // The serialization of the i-th node. - auto linesNodeInfo = nodesInfo[i]; - int numOfLinesNodeInfo = linesNodeInfo.size(); - - // The pointer to the i-th node. - auto node = unique_number_to_node[i - 1]; - - // Expect the first line is the begin of the node. - auto lineNodeBegin = R"( {)"; - EXPECT_EQ(linesNodeInfo[0], lineNodeBegin); - - // Expect the last line is the end of the node. - auto lineNodeEnd = R"( },)"; - EXPECT_EQ(linesNodeInfo[numOfLinesNodeInfo - 1], lineNodeEnd); - - // Expect the second line is the unique_number. - auto lineUniqueNumber = R"( "unique_number": )"; - auto UniqueNumberValue = std::to_string(i - 1); - EXPECT_EQ(linesNodeInfo[1], lineUniqueNumber + UniqueNumberValue + ","); - - // Expect the third line is the key. - auto lineKey = R"( "key": )"; - auto KeyValue = std::to_string(node->key()); - EXPECT_EQ(linesNodeInfo[2], lineKey + KeyValue + ","); - - // Expect the fourth line is the name. - auto lineName = R"( "name": ")"; - auto NameValue = node->name(); - EXPECT_EQ(linesNodeInfo[3], lineName + NameValue + R"(",)"); - - // Expect the line 5 ~ 11 are the data. - auto lineDataBegin = R"( "data": {)"; - EXPECT_EQ(linesNodeInfo[4], lineDataBegin); - - auto lineStartTime = R"( "start_time": )"; - auto StartTimeValue = std::to_string(node->data().start_time.time_since_epoch().count()); - EXPECT_EQ(linesNodeInfo[5], lineStartTime + StartTimeValue + ","); - - auto lineCallerName = R"( "caller_name": ")"; - auto CallerNameValue = node->data().caller_name; - EXPECT_EQ(linesNodeInfo[6], lineCallerName + CallerNameValue + R"(",)"); - - auto lineTotalTime = R"( "total_time": )"; - auto TotalTimeValue = std::to_string(node->data().total_time.count()); - EXPECT_EQ(linesNodeInfo[7], lineTotalTime + TotalTimeValue + ","); - - auto lineCallCount = R"( "call_count": )"; - auto CallCountValue = std::to_string(node->data().call_count); - EXPECT_EQ(linesNodeInfo[8], lineCallCount + CallCountValue + ","); - - auto lineIsRunning = R"( "is_running": )"; - auto IsRunningValue = std::to_string(node->data().is_running); - EXPECT_EQ(linesNodeInfo[9], lineIsRunning + IsRunningValue); - - auto lineDataEnd = R"( },)"; - EXPECT_EQ(linesNodeInfo[10], lineDataEnd); - - bool childrenIsEmpty = node->children().empty(); - // If the children list is empty, expect the children list is closed at the same line (line 12). - if (childrenIsEmpty) - { - auto lineChildren = R"( children": [])"; - EXPECT_EQ(linesNodeInfo[11], lineChildren); - } - else - { - // line 12 is the begin of children. - auto lineChildrenBegin = R"( children": [)"; - EXPECT_EQ(linesNodeInfo[11], lineChildrenBegin); - - // The second last line is the end of children. - auto lineChildrenEnd = R"( ])"; - EXPECT_EQ(linesNodeInfo[numOfLinesNodeInfo - 2], lineChildrenEnd); - - // Check the trailing comma for the children list. - for (int j = 12; j < numOfLinesNodeInfo - 3; j++) - { - EXPECT_EQ(linesNodeInfo[j].back(), ','); - } - // Expect the last element has no trailing comma. - EXPECT_NE(linesNodeInfo[numOfLinesNodeInfo - 3].back(), ','); - - // Check the unique number of children list is correct. - std::unordered_set childrenUniqueNumbers; - for (int j = 12; j < numOfLinesNodeInfo - 2; j++) - { - std::string uniqueNumberString = linesNodeInfo[j]; - if (uniqueNumberString.back() == ',') - uniqueNumberString.pop_back(); - int uniqueNumber = std::stoi(uniqueNumberString); - childrenUniqueNumbers.insert(uniqueNumber); - } - - for (const auto & child : node->children()) - { - int uniqueNumber = node_to_unique_number[child.get()]; - EXPECT_TRUE(childrenUniqueNumbers.find(uniqueNumber) != childrenUniqueNumbers.end()); - childrenUniqueNumbers.erase(uniqueNumber); - } - } - } -} - -std::vector> CallProfilerTest::split_nodes_info(const std::vector & lines) -{ - int numOfLines = lines.size(); - // Split the nodes serialization into a vector of strings. - std::vector> nodesInfo; - auto lineNodesBegin = R"( "nodes": [{)"; - int curNodeInfoBegin = std::find(lines.begin(), lines.end(), lineNodesBegin) - lines.begin(); - int curNodeInfoEnd = 0; - - while (curNodeInfoEnd < numOfLines - 4) - { - std::vector nodeInfo; - curNodeInfoEnd = std::find(lines.begin() + curNodeInfoBegin, lines.end(), R"( },)") - lines.begin(); - if (curNodeInfoEnd == numOfLines) - { - curNodeInfoEnd = numOfLines - 4; - // Expect the last node has no trailing comma. - EXPECT_NE(lines[curNodeInfoEnd].back(), ','); - } - - // Unify the first line and last line of the node to make the check easier. - nodeInfo.push_back(R"( {)"); - for (int i = curNodeInfoBegin + 1; i <= curNodeInfoEnd; ++i) - { - if (i == numOfLines - 4) - nodeInfo.push_back(lines[i] + ","); - else - nodeInfo.push_back(lines[i]); - } - nodesInfo.push_back(nodeInfo); - curNodeInfoBegin = curNodeInfoEnd + 1; - } - return nodesInfo; -} - -void CallProfilerTest::BFS_radix_tree(const RadixTreeNode * node, node_to_number_map_type & node_to_unique_number, number_to_node_map_type & unique_number_to_node) -{ - // BFS the radix tree and number the nodes. - std::queue *> nodes_buffer; - nodes_buffer.push(node); - node_to_unique_number[node] = -1; - unique_number_to_node[-1] = node; - int unique_node_number = -1; - - while (!nodes_buffer.empty()) - { - const int nodes_buffer_size = nodes_buffer.size(); - for (int i = 0; i < nodes_buffer_size; ++i) - { - const RadixTreeNode * cur_node = nodes_buffer.front(); - nodes_buffer.pop(); - for (const auto & child : cur_node->children()) - { - nodes_buffer.push(child.get()); - // Store the key and value in the two hash maps. - node_to_unique_number[child.get()] = ++unique_node_number; - unique_number_to_node[unique_node_number] = child.get(); - } - } - } -} - -constexpr int uniqueTime1 = 19; -constexpr int uniqueTime2 = 35; -constexpr int uniqueTime3 = 7; - -void func3() -{ - USE_CALLPROFILER_PROFILE_THIS_FUNCTION(); - auto start_time = std::chrono::high_resolution_clock::now(); - while (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() < uniqueTime1) - { - // use busy loop to get a precise duration - } -} - -void func2() -{ - USE_CALLPROFILER_PROFILE_THIS_FUNCTION(); - auto start_time = std::chrono::high_resolution_clock::now(); - while (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() < uniqueTime2) - { - // use busy loop to get a precise duration - } - func3(); -} - -void func1() -{ - USE_CALLPROFILER_PROFILE_THIS_FUNCTION(); - func2(); - auto start_time = std::chrono::high_resolution_clock::now(); - while (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() < uniqueTime3) - { - // use busy loop to get a precise duration - } -} - -std::vector split_str(const std::string & s, char delim) -{ - // Convert a string to a vector of strings by splitting it with a delimiter. - std::vector result; - std::stringstream ss(s); - std::string item; - while (std::getline(ss, item, delim)) - { - result.push_back(item); - } - return result; -} - -TEST_F(CallProfilerTest, test_serialization_1) -{ - /* Here is the expected format of output: -{ - "radix_tree": { - "current_node": -1, - "unique_id": 7, - "id_map": { - "void modmesh::detail::func3()": 2, - "void modmesh::detail::func2()": 1, - "void modmesh::detail::func1()": 0 - }, - "nodes": [{ - "unique_number": -1, - "key": -1, - "name": "", - "data": { - "start_time": 0, - "caller_name": "", - "total_time": 0, - "call_count": 0, - "is_running": 0 - }, - children": [ - 0, - 1, - 2 - ] - }, - { - "unique_number": 0, - "key": 0, - "name": "void modmesh::detail::func1()", - "data": { - "start_time": 2074704984391166, - "caller_name": "void modmesh::detail::func1()", - "total_time": 61000834, - "call_count": 1, - "is_running": 1 - }, - children": [ - 3 - ] - }, - { - "unique_number": 1, - "key": 1, - "name": "void modmesh::detail::func2()", - "data": { - "start_time": 2074705045392250, - "caller_name": "void modmesh::detail::func2()", - "total_time": 54001000, - "call_count": 1, - "is_running": 1 - }, - children": [ - 4 - ] - }, - { - "unique_number": 2, - "key": 2, - "name": "void modmesh::detail::func3()", - "data": { - "start_time": 2074705118393708, - "caller_name": "void modmesh::detail::func3()", - "total_time": 38000208, - "call_count": 2, - "is_running": 1 - }, - children": [] - }, - { - "unique_number": 3, - "key": 1, - "name": "void modmesh::detail::func2()", - "data": { - "start_time": 2074704984391458, - "caller_name": "void modmesh::detail::func2()", - "total_time": 54000417, - "call_count": 1, - "is_running": 1 - }, - children": [ - 5 - ] - }, - { - "unique_number": 4, - "key": 2, - "name": "void modmesh::detail::func3()", - "data": { - "start_time": 2074705080392791, - "caller_name": "void modmesh::detail::func3()", - "total_time": 19000417, - "call_count": 1, - "is_running": 1 - }, - children": [] - }, - { - "unique_number": 5, - "key": 2, - "name": "void modmesh::detail::func3()", - "data": { - "start_time": 2074705019391625, - "caller_name": "void modmesh::detail::func3()", - "total_time": 19000125, - "call_count": 1, - "is_running": 1 - }, - children": [] - } - ] - } -} - */ - pProfiler->reset(); - - func1(); - func2(); - func3(); - func3(); - - std::stringstream ss; - CallProfilerSerializer::serialize(*pProfiler, ss); - std::vector lines = split_str(ss.str(), '\n'); - // std::cout << ss.str() << std::endl; - check_call_profiler_serialization(*pProfiler, lines); -} - -TEST_F(CallProfilerTest, test_serialization_2) -{ - /* Here is the expected format of output: -{ - "radix_tree": { - "current_node": -1, - "unique_id": 0, - "id_map": {}, - "nodes": [{ - "unique_number": -1, - "key": -1, - "name": "", - "data": { - "start_time": 0, - "caller_name": "", - "total_time": 0, - "call_count": 0, - "is_running": 0 - }, - children": [] - } - ] - } -} - */ - pProfiler->reset(); - - std::stringstream ss; - CallProfilerSerializer::serialize(*pProfiler, ss); - std::vector lines = split_str(ss.str(), '\n'); - // std::cout << ss.str() << std::endl; - check_call_profiler_serialization(*pProfiler, lines); -} - -} // namespace detail -} // namespace modmesh \ No newline at end of file diff --git a/tests/test_callprofiler.py b/tests/test_callprofiler.py index 691fd496..742b2e27 100644 --- a/tests/test_callprofiler.py +++ b/tests/test_callprofiler.py @@ -284,3 +284,100 @@ def baz(): self.assertGreaterEqual(baz_dict["total_per_call"], ref_per_call) self.assertGreaterEqual(baz_dict["cumulative_time"], time3) self.assertGreaterEqual(baz_dict["cumulative_per_call"], time3) + + def test_zero_callers_serializing(self): + modmesh.call_profiler.reset() + sdict = json.loads(modmesh.call_profiler.serialize()) + + # There are 3 keys in the serialization_dict + self.assertEqual(sdict["id_map"], {}) + self.assertEqual(sdict["unique_id"], 0) + self.assertIn("radix_tree", sdict) + + radix_tree = sdict["radix_tree"] + self.assertEqual(radix_tree["key"], -1) + self.assertEqual(radix_tree["name"], "") + self.assertEqual(radix_tree["call_count"], 0) + self.assertEqual(radix_tree["total_time"], 0) + self.assertEqual(radix_tree["children"], []) + + def test_two_callers_serializing(self): + + @profile_function + def bar(): + busy_loop(0.5) + + @profile_function + def foo(): + busy_loop(0.1) + bar() + + modmesh.call_profiler.reset() + bar() + foo() + sdict = json.loads(modmesh.call_profiler.serialize()) + + # There are 3 keys in the serialization_dict + self.assertEqual(sdict["id_map"], {'bar': 0, 'foo': 1}) + self.assertEqual(sdict["unique_id"], 3) + self.assertIn("radix_tree", sdict) + + radix_tree = sdict["radix_tree"] + self.assertEqual(radix_tree["key"], -1) + self.assertEqual(radix_tree["name"], "") + self.assertEqual(radix_tree["call_count"], 0) + self.assertEqual(radix_tree["total_time"], 0) + self.assertIn("children", radix_tree) + + children = radix_tree["children"] + self.assertEqual(len(children), 2) + + bar_child = children[0] + self.assertEqual(bar_child["key"], 0) + self.assertEqual(bar_child["name"], "bar") + self.assertEqual(bar_child["call_count"], 1) + self.assertGreaterEqual(bar_child["total_time"], 5e8) + self.assertEqual(bar_child["children"], []) + + foo_child = children[1] + self.assertEqual(foo_child["key"], 1) + self.assertEqual(foo_child["name"], "foo") + self.assertEqual(foo_child["call_count"], 1) + self.assertGreaterEqual(foo_child["total_time"], 1e8) + self.assertEqual(len(foo_child["children"]), 1) + + foo_bar_child = foo_child["children"][0] + self.assertEqual(foo_bar_child["key"], 0) + self.assertEqual(foo_bar_child["name"], "bar") + self.assertEqual(foo_bar_child["call_count"], 1) + self.assertGreaterEqual(foo_bar_child["total_time"], 5e8) + self.assertEqual(foo_bar_child["children"], []) + + def test_serialize_during_profiling(self): + + @profile_function + def bar(): + busy_loop(0.5) + + @profile_function + def foo(): + bar() + busy_loop(0.1) + sdict = json.loads(modmesh.call_profiler.serialize()) + + # There are 3 keys in the serialization_dict + self.assertEqual(sdict["id_map"], {}) + self.assertEqual(sdict["unique_id"], 0) + self.assertIn("radix_tree", sdict) + + radix_tree = sdict["radix_tree"] + self.assertEqual(radix_tree["key"], -1) + self.assertEqual(radix_tree["name"], "") + self.assertEqual(radix_tree["call_count"], 0) + self.assertEqual(radix_tree["total_time"], 0) + self.assertEqual(radix_tree["children"], []) + + modmesh.call_profiler.reset() + foo() + +# vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: From 46b1c14d7ba4fc727fce2afefc7cff4171a5df37 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Tue, 13 Aug 2024 20:58:10 +0600 Subject: [PATCH 06/28] use Ctrl+W/S and Ctrl+Arrows instead of Q/E and Page Up/Down --- cpp/modmesh/view/RCameraController.cpp | 35 +++++++++++++------------- cpp/modmesh/view/RCameraController.hpp | 2 ++ 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/cpp/modmesh/view/RCameraController.cpp b/cpp/modmesh/view/RCameraController.cpp index 97a9b2f1..10ff8d3b 100644 --- a/cpp/modmesh/view/RCameraController.cpp +++ b/cpp/modmesh/view/RCameraController.cpp @@ -28,10 +28,13 @@ #include +#include #include #include #include +#include "R3DWidget.hpp" + namespace modmesh { @@ -56,11 +59,13 @@ RCameraInputListener::RCameraInputListener( , m_right_mouse_button_action(new Qt3DInput::QAction) , m_shift_button_action(new Qt3DInput::QAction) , m_alt_button_action(new Qt3DInput::QAction) + , m_ctrl_button_action(new Qt3DInput::QAction) , m_left_mouse_button_input(new Qt3DInput::QActionInput) , m_middle_mouse_button_input(new Qt3DInput::QActionInput) , m_right_mouse_button_input(new Qt3DInput::QActionInput) , m_shift_button_input(new Qt3DInput::QActionInput) , m_alt_button_input(new Qt3DInput::QActionInput) + , m_ctrl_button_input(new Qt3DInput::QActionInput) , m_mouse_rx_input(new Qt3DInput::QAnalogAxisInput) , m_mouse_ry_input(new Qt3DInput::QAnalogAxisInput) , m_mouse_tz_x_input(new Qt3DInput::QAnalogAxisInput) @@ -81,12 +86,13 @@ RCameraInputListener::RCameraInputListener( [this](const float dt) { Qt3DExtras::QAbstractCameraController::InputState state{}; + const bool isCtrlPressed = m_ctrl_button_action->isActive(); state.rxAxisValue = m_rx_axis->value(); state.ryAxisValue = m_ry_axis->value(); state.txAxisValue = m_tx_axis->value(); - state.tyAxisValue = m_ty_axis->value(); - state.tzAxisValue = m_tz_axis->value(); + state.tyAxisValue = isCtrlPressed ? 0.0f : m_ty_axis->value(); + state.tzAxisValue = isCtrlPressed ? m_ty_axis->value() : 0.0f; state.leftMouseButtonActive = m_left_mouse_button_action->isActive(); state.middleMouseButtonActive = m_middle_mouse_button_action->isActive(); @@ -99,8 +105,7 @@ RCameraInputListener::RCameraInputListener( }); } -void RCameraInputListener::init() -{ +void RCameraInputListener::init() { initMouseListeners(); initKeyboardListeners(); @@ -109,6 +114,7 @@ void RCameraInputListener::init() m_logical_device->addAction(m_right_mouse_button_action); m_logical_device->addAction(m_alt_button_action); m_logical_device->addAction(m_shift_button_action); + m_logical_device->addAction(m_ctrl_button_action); m_logical_device->addAxis(m_rx_axis); m_logical_device->addAxis(m_ry_axis); @@ -178,6 +184,11 @@ void RCameraInputListener::initKeyboardListeners() const m_alt_button_input->setSourceDevice(m_keyboard_device); m_alt_button_action->addInput(m_alt_button_input); + // ctrl button + m_ctrl_button_input->setButtons(QList{Qt::Key_Control}); + m_ctrl_button_input->setSourceDevice(m_keyboard_device); + m_ctrl_button_action->addInput(m_ctrl_button_input); + // keyboard positive x translation m_keyboard_tx_pos_input->setButtons(QList{Qt::Key_D, Qt::Key_Right}); m_keyboard_tx_pos_input->setScale(1.0f); @@ -185,17 +196,11 @@ void RCameraInputListener::initKeyboardListeners() const m_tx_axis->addInput(m_keyboard_tx_pos_input); // keyboard positive y translation - m_keyboard_ty_pos_input->setButtons(QList{Qt::Key_E, Qt::Key_PageUp}); + m_keyboard_ty_pos_input->setButtons(QList{Qt::Key_W, Qt::Key_Up}); m_keyboard_ty_pos_input->setScale(1.0f); m_keyboard_ty_pos_input->setSourceDevice(m_keyboard_device); m_ty_axis->addInput(m_keyboard_ty_pos_input); - // keyboard positive z translation - m_keyboard_tz_pos_input->setButtons(QList{Qt::Key_W, Qt::Key_Up}); - m_keyboard_tz_pos_input->setScale(1.0f); - m_keyboard_tz_pos_input->setSourceDevice(m_keyboard_device); - m_tz_axis->addInput(m_keyboard_tz_pos_input); - // keyboard negative x translation m_keyboard_tx_neg_input->setButtons(QList{Qt::Key_A, Qt::Key_Left}); m_keyboard_tx_neg_input->setScale(-1.0f); @@ -203,16 +208,10 @@ void RCameraInputListener::initKeyboardListeners() const m_tx_axis->addInput(m_keyboard_tx_neg_input); // keyboard negative y translation - m_keyboard_ty_neg_input->setButtons(QList{Qt::Key_Q, Qt::Key_PageDown}); + m_keyboard_ty_neg_input->setButtons(QList{Qt::Key_S, Qt::Key_Down}); m_keyboard_ty_neg_input->setScale(-1.0f); m_keyboard_ty_neg_input->setSourceDevice(m_keyboard_device); m_ty_axis->addInput(m_keyboard_ty_neg_input); - - // keyboard negative z translation - m_keyboard_tz_neg_input->setButtons(QList{Qt::Key_S, Qt::Key_Down}); - m_keyboard_tz_neg_input->setScale(-1.0f); - m_keyboard_tz_neg_input->setSourceDevice(m_keyboard_device); - m_tz_axis->addInput(m_keyboard_tz_neg_input); } RFirstPersonCameraController::RFirstPersonCameraController(QNode * parent) diff --git a/cpp/modmesh/view/RCameraController.hpp b/cpp/modmesh/view/RCameraController.hpp index 8c282c53..37c5738e 100644 --- a/cpp/modmesh/view/RCameraController.hpp +++ b/cpp/modmesh/view/RCameraController.hpp @@ -84,11 +84,13 @@ class RCameraInputListener : public Qt3DCore::QEntity Qt3DInput::QAction * m_right_mouse_button_action; Qt3DInput::QAction * m_shift_button_action; Qt3DInput::QAction * m_alt_button_action; + Qt3DInput::QAction * m_ctrl_button_action; Qt3DInput::QActionInput * m_left_mouse_button_input; Qt3DInput::QActionInput * m_middle_mouse_button_input; Qt3DInput::QActionInput * m_right_mouse_button_input; Qt3DInput::QActionInput * m_shift_button_input; Qt3DInput::QActionInput * m_alt_button_input; + Qt3DInput::QActionInput * m_ctrl_button_input; // mouse rotation input Qt3DInput::QAnalogAxisInput * m_mouse_rx_input; Qt3DInput::QAnalogAxisInput * m_mouse_ry_input; From 4fa8b9920be373fc8e221e8744c7b51df92fb561 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Tue, 13 Aug 2024 22:44:28 +0600 Subject: [PATCH 07/28] add menu items --- cpp/modmesh/view/RCameraController.cpp | 10 +- cpp/modmesh/view/RCameraController.hpp | 34 ++- cpp/modmesh/view/RManager.cpp | 318 ++++++++++++++++++------- cpp/modmesh/view/RManager.hpp | 6 + cpp/modmesh/view/wrap_view.cpp | 2 +- 5 files changed, 279 insertions(+), 91 deletions(-) diff --git a/cpp/modmesh/view/RCameraController.cpp b/cpp/modmesh/view/RCameraController.cpp index 10ff8d3b..268f7ac1 100644 --- a/cpp/modmesh/view/RCameraController.cpp +++ b/cpp/modmesh/view/RCameraController.cpp @@ -85,7 +85,7 @@ RCameraInputListener::RCameraInputListener( this, [this](const float dt) { - Qt3DExtras::QAbstractCameraController::InputState state{}; + CameraInputState state{}; const bool isCtrlPressed = m_ctrl_button_action->isActive(); state.rxAxisValue = m_rx_axis->value(); @@ -217,7 +217,7 @@ void RCameraInputListener::initKeyboardListeners() const RFirstPersonCameraController::RFirstPersonCameraController(QNode * parent) : QFirstPersonCameraController(parent) { - auto callback = [this](const InputState & state, const float dt) + auto callback = [this](const CameraInputState & state, const float dt) { updateCameraPosition(state, dt); }; @@ -225,7 +225,7 @@ RFirstPersonCameraController::RFirstPersonCameraController(QNode * parent) m_listener = new RCameraInputListener(keyboardDevice(), mouseDevice(), callback, this); } -void RFirstPersonCameraController::updateCameraPosition(const InputState & input, const float dt) +void RFirstPersonCameraController::updateCameraPosition(const CameraInputState & input, const float dt) { constexpr auto positiveY = QVector3D(0.f, 1.f, 0.f); @@ -248,7 +248,7 @@ void RFirstPersonCameraController::updateCameraPosition(const InputState & input ROrbitCameraController::ROrbitCameraController(QNode * parent) : QOrbitCameraController(parent) { - auto callback = [this](const InputState & state, const float dt) + auto callback = [this](const CameraInputState & state, const float dt) { updateCameraPosition(state, dt); }; @@ -256,7 +256,7 @@ ROrbitCameraController::ROrbitCameraController(QNode * parent) m_listener = new RCameraInputListener(keyboardDevice(), mouseDevice(), callback, this); } -void ROrbitCameraController::updateCameraPosition(const InputState & input, const float dt) +void ROrbitCameraController::updateCameraPosition(const CameraInputState & input, const float dt) { if (camera() == nullptr) return; diff --git a/cpp/modmesh/view/RCameraController.hpp b/cpp/modmesh/view/RCameraController.hpp index 37c5738e..b3c7e56d 100644 --- a/cpp/modmesh/view/RCameraController.hpp +++ b/cpp/modmesh/view/RCameraController.hpp @@ -45,12 +45,32 @@ namespace modmesh { +struct CameraInputState { + float rxAxisValue = 0; + float ryAxisValue = 0; + float txAxisValue = 0; + float tyAxisValue = 0; + float tzAxisValue = 0; + + bool leftMouseButtonActive = false; + bool middleMouseButtonActive = false; + bool rightMouseButtonActive = false; + + bool altKeyActive = false; + bool shiftKeyActive = false; +}; + +enum class CameraControllerType { + FirstPerson, + Orbit +}; + class RCameraInputListener : public Qt3DCore::QEntity { Q_OBJECT public: - using callback_type = std::function; + using callback_type = std::function; RCameraInputListener( Qt3DInput::QKeyboardDevice * keyboardDevice, @@ -111,7 +131,7 @@ class CameraController public: virtual ~CameraController() = default; - virtual void updateCameraPosition(const Qt3DExtras::QAbstractCameraController::InputState & state, float dt) = 0; + virtual void updateCameraPosition(const CameraInputState & state, float dt) = 0; virtual Qt3DRender::QCamera * getCamera() = 0; @@ -119,6 +139,8 @@ class CameraController virtual float getLookSpeed() = 0; + virtual CameraControllerType getType() = 0; + QVector3D position() { return getCamera()->position(); } QVector3D viewVector() { return getCamera()->viewVector(); } @@ -154,7 +176,9 @@ class RFirstPersonCameraController : public Qt3DExtras::QFirstPersonCameraContro void moveCamera(const InputState & state, float dt) override {} - void updateCameraPosition(const InputState & input, float dt) override; + void updateCameraPosition(const CameraInputState & input, float dt) override; + + CameraControllerType getType() override { return CameraControllerType::FirstPerson; } }; class ROrbitCameraController : public Qt3DExtras::QOrbitCameraController @@ -172,7 +196,7 @@ class ROrbitCameraController : public Qt3DExtras::QOrbitCameraController private: void moveCamera(const InputState & state, float dt) override {} - void updateCameraPosition(const InputState & input, float dt) override; + void updateCameraPosition(const CameraInputState & input, float dt) override; void zoom(float zoomValue) const; @@ -181,6 +205,8 @@ class ROrbitCameraController : public Qt3DExtras::QOrbitCameraController static float clamp(float value); static float zoomDistanceSquared(QVector3D firstPoint, QVector3D secondPoint); + + CameraControllerType getType() override { return CameraControllerType::Orbit; } }; } /* end namespace modmesh */ diff --git a/cpp/modmesh/view/RManager.cpp b/cpp/modmesh/view/RManager.cpp index 42f50e41..68cc1546 100644 --- a/cpp/modmesh/view/RManager.cpp +++ b/cpp/modmesh/view/RManager.cpp @@ -157,87 +157,8 @@ void RManager::setUpMenu() m_viewMenu = m_mainWindow->menuBar()->addMenu(QString("View")); { - auto * use_orbit_camera = new RAction( - QString("Use Orbit Camera Controller"), - QString("Use Oribt Camera Controller"), - [this]() - { - qDebug() << "Use Orbit Camera Controller (menu demo)"; - for (auto subwin : m_mdiArea->subWindowList()) - { - try - { - R3DWidget * viewer = dynamic_cast(subwin->widget()); - viewer->scene()->setOrbitCameraController(); - viewer->scene()->controller()->setCamera(viewer->camera()); - } - catch (std::bad_cast & e) - { - std::cerr << e.what() << std::endl; - } - } - }); - - auto * use_fps_camera = new RAction( - QString("Use First Person Camera Controller"), - QString("Use First Person Camera Controller"), - [this]() - { - qDebug() << "Use First Person Camera Controller (menu demo)"; - for (auto subwin : m_mdiArea->subWindowList()) - { - try - { - R3DWidget * viewer = dynamic_cast(subwin->widget()); - viewer->scene()->setFirstPersonCameraController(); - viewer->scene()->controller()->setCamera(viewer->camera()); - } - catch (std::bad_cast & e) - { - std::cerr << e.what() << std::endl; - } - } - }); - - auto * reset_camera = new RAction( - QString("Reset (esc)"), - QString("Reset (esc)"), - [this]() - { - // implement resetting camera contorl - qDebug() << "Reset to initial status."; - for (auto subwin : m_mdiArea->subWindowList()) - { - try - { - R3DWidget * viewer = dynamic_cast(subwin->widget()); - Qt3DRender::QCamera * camera = viewer->camera(); - if (camera) - { - viewer->resetCamera(camera); - } - } - catch (std::bad_cast & e) - { - std::cerr << e.what() << std::endl; - } - } - }); - - auto * cameraGroup = new QActionGroup(m_mainWindow); - cameraGroup->addAction(use_orbit_camera); - cameraGroup->addAction(use_fps_camera); - cameraGroup->addAction(reset_camera); - - use_orbit_camera->setCheckable(true); - use_fps_camera->setCheckable(true); - use_orbit_camera->setChecked(true); - - reset_camera->setShortcut(QKeySequence(Qt::Key_Escape)); - - m_viewMenu->addAction(use_orbit_camera); - m_viewMenu->addAction(use_fps_camera); - m_viewMenu->addAction(reset_camera); + setUpCameraControllersMenuItems(); + setUpCameraMovementsMenuItems(); } m_oneMenu = m_mainWindow->menuBar()->addMenu(QString("One")); @@ -281,6 +202,241 @@ void RManager::setUpMenu() []() {})); } +void RManager::setUpCameraControllersMenuItems() const { + auto * use_orbit_camera = new RAction( + QString("Use Orbit Camera Controller"), + QString("Use Oribt Camera Controller"), + [this]() + { + qDebug() << "Use Orbit Camera Controller (menu demo)"; + for (auto subwin : m_mdiArea->subWindowList()) + { + try + { + R3DWidget * viewer = dynamic_cast(subwin->widget()); + viewer->scene()->setOrbitCameraController(); + viewer->scene()->controller()->setCamera(viewer->camera()); + } + catch (std::bad_cast & e) + { + std::cerr << e.what() << std::endl; + } + } + }); + + auto * use_fps_camera = new RAction( + QString("Use First Person Camera Controller"), + QString("Use First Person Camera Controller"), + [this]() + { + qDebug() << "Use First Person Camera Controller (menu demo)"; + for (auto subwin : m_mdiArea->subWindowList()) + { + try + { + R3DWidget * viewer = dynamic_cast(subwin->widget()); + viewer->scene()->setFirstPersonCameraController(); + viewer->scene()->controller()->setCamera(viewer->camera()); + } + catch (std::bad_cast & e) + { + std::cerr << e.what() << std::endl; + } + } + }); + + auto * cameraGroup = new QActionGroup(m_mainWindow); + cameraGroup->addAction(use_orbit_camera); + cameraGroup->addAction(use_fps_camera); + + use_orbit_camera->setCheckable(true); + use_fps_camera->setCheckable(true); + use_orbit_camera->setChecked(true); + + m_viewMenu->addAction(use_orbit_camera); + m_viewMenu->addAction(use_fps_camera); +} + +void RManager::setUpCameraMovementsMenuItems() const { + auto * reset_camera = new RAction( + QString("Reset (esc)"), + QString("Reset (esc)"), + [this]() + { + // implement resetting camera control + qDebug() << "Reset to initial status."; + try + { + auto * viewer = dynamic_cast(m_mdiArea->currentSubWindow()->widget()); + Qt3DRender::QCamera * camera = viewer->camera(); + if (camera) + { + viewer->resetCamera(camera); + } + } + catch (std::bad_cast & e) + { + std::cerr << e.what() << std::endl; + } + }); + + auto * move_camera_up = new RAction( + QString("Move camera up (W/⬆)"), + QString("Move camera up (W/⬆)"), + createCameraTranslateItemHandler([](CameraInputState & input) + { + input.tyAxisValue = 1.0; + }) + ); + + auto * move_camera_down = new RAction( + QString("Move camera down (S/⬇)"), + QString("Move camera down (S/⬇)"), + createCameraTranslateItemHandler([](CameraInputState & input) + { + input.tyAxisValue = -1.0; + }) + ); + + auto * move_camera_right = new RAction( + QString("Move camera right (D/➡)"), + QString("Move camera right (D/➡)"), + createCameraTranslateItemHandler([](CameraInputState & input) + { + input.txAxisValue = 1.0; + }) + ); + + auto * move_camera_left = new RAction( + QString("Move camera left (A/⬅)"), + QString("Move camera left (A/⬅)"), + createCameraTranslateItemHandler([](CameraInputState & input) + { + input.txAxisValue = -1.0; + }) + ); + + auto * move_camera_forward = new RAction( + QString("Move camera forward (Ctrl+W/⬆)"), + QString("Move camera forward (Ctrl+W/⬆)"), + createCameraTranslateItemHandler([](CameraInputState & input) + { + input.tzAxisValue = 1.0; + }) + ); + + auto * move_camera_backward = new RAction( + QString("Move camera down (Ctrl+S/⬇)"), + QString("Move camera down (Ctrl+S/⬇)"), + createCameraTranslateItemHandler([](CameraInputState & input) + { + input.tzAxisValue = -1.0; + }) + ); + + auto * rotate_camera_positive_yaw = new RAction( + QString("Rotate camera positive yaw"), + QString("Rotate camera positive yaw"), + createCameraRotateItemHandler([](CameraInputState & input) + { + input.rxAxisValue = 1.0; + }) + ); + + auto * rotate_camera_negative_yaw = new RAction( + QString("Rotate camera negative yaw"), + QString("Rotate camera negative yaw"), + createCameraRotateItemHandler([](CameraInputState & input) + { + input.rxAxisValue = -1.0; + }) + ); + + auto * rotate_camera_positive_pitch = new RAction( + QString("Rotate camera positive pitch"), + QString("Rotate camera positive pitch"), + createCameraRotateItemHandler([](CameraInputState & input) + { + input.ryAxisValue = 1.0; + }) + ); + + auto * rotate_camera_negative_pitch = new RAction( + QString("Rotate camera negative pitch"), + QString("Rotate camera negative pitch"), + createCameraRotateItemHandler([](CameraInputState & input) + { + input.ryAxisValue = -1.0; + }) + ); + + reset_camera->setShortcut(QKeySequence(Qt::Key_Escape)); + + m_viewMenu->addAction(reset_camera); + m_viewMenu->addAction(move_camera_up); + m_viewMenu->addAction(move_camera_down); + m_viewMenu->addAction(move_camera_right); + m_viewMenu->addAction(move_camera_left); + m_viewMenu->addAction(move_camera_forward); + m_viewMenu->addAction(move_camera_backward); + m_viewMenu->addAction(rotate_camera_positive_yaw); + m_viewMenu->addAction(rotate_camera_negative_yaw); + m_viewMenu->addAction(rotate_camera_positive_pitch); + m_viewMenu->addAction(rotate_camera_negative_pitch); +} + +std::function RManager::createCameraTranslateItemHandler(const std::function &func) const { + return [this, func]() { + try + { + auto * viewer = dynamic_cast(m_mdiArea->currentSubWindow()->widget()); + + if (viewer->camera()) + { + CameraInputState input{}; + func(input); + viewer->cameraController()->updateCameraPosition(input, 0.01); + } + } + catch (std::bad_cast & e) + { + std::cerr << e.what() << std::endl; + } + }; +} + +std::function RManager::createCameraRotateItemHandler(const std::function &func) const { + return [this, func]() { + try + { + auto * viewer = dynamic_cast(m_mdiArea->currentSubWindow()->widget()); + + if (viewer->camera()) + { + auto * controller = viewer->cameraController(); + + CameraInputState input{}; + func(input); + + if(controller->getType() == CameraControllerType::Orbit) + { + input.rightMouseButtonActive = true; + } + else if(controller->getType() == CameraControllerType::FirstPerson) + { + input.leftMouseButtonActive = true; + } + + viewer->cameraController()->updateCameraPosition(input, 0.01); + } + } + catch (std::bad_cast & e) + { + std::cerr << e.what() << std::endl; + } + }; +} + void RManager::clearApplications() { for (QAction * a : this->m_addonMenu->actions()) diff --git a/cpp/modmesh/view/RManager.hpp b/cpp/modmesh/view/RManager.hpp index 17413862..bba58ca7 100644 --- a/cpp/modmesh/view/RManager.hpp +++ b/cpp/modmesh/view/RManager.hpp @@ -82,6 +82,12 @@ public slots: void setUpCentral(); void setUpMenu(); + void setUpCameraControllersMenuItems() const; + void setUpCameraMovementsMenuItems() const; + + std::function createCameraTranslateItemHandler(const std::function &) const; + std::function createCameraRotateItemHandler(const std::function &) const; + bool m_already_setup = false; QCoreApplication * m_core = nullptr; diff --git a/cpp/modmesh/view/wrap_view.cpp b/cpp/modmesh/view/wrap_view.cpp index c5ab8ddc..a49c381a 100644 --- a/cpp/modmesh/view/wrap_view.cpp +++ b/cpp/modmesh/view/wrap_view.cpp @@ -470,7 +470,7 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapRCameraController bool shift_key, float dt) { - Qt3DExtras::QAbstractCameraController::InputState input{}; + CameraInputState input{}; input.txAxisValue = x; input.tyAxisValue = y; input.tzAxisValue = z; From 0d7afbff447d7b50221e61e33c4649a3f50804c8 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Tue, 13 Aug 2024 22:52:47 +0600 Subject: [PATCH 08/28] clang-format --- cpp/modmesh/view/RCameraController.cpp | 3 +- cpp/modmesh/view/RCameraController.hpp | 6 +- cpp/modmesh/view/RManager.cpp | 112 ++++++++++--------------- cpp/modmesh/view/RManager.hpp | 4 +- 4 files changed, 52 insertions(+), 73 deletions(-) diff --git a/cpp/modmesh/view/RCameraController.cpp b/cpp/modmesh/view/RCameraController.cpp index 268f7ac1..df47064e 100644 --- a/cpp/modmesh/view/RCameraController.cpp +++ b/cpp/modmesh/view/RCameraController.cpp @@ -105,7 +105,8 @@ RCameraInputListener::RCameraInputListener( }); } -void RCameraInputListener::init() { +void RCameraInputListener::init() +{ initMouseListeners(); initKeyboardListeners(); diff --git a/cpp/modmesh/view/RCameraController.hpp b/cpp/modmesh/view/RCameraController.hpp index b3c7e56d..15dd0282 100644 --- a/cpp/modmesh/view/RCameraController.hpp +++ b/cpp/modmesh/view/RCameraController.hpp @@ -45,7 +45,8 @@ namespace modmesh { -struct CameraInputState { +struct CameraInputState +{ float rxAxisValue = 0; float ryAxisValue = 0; float txAxisValue = 0; @@ -60,7 +61,8 @@ struct CameraInputState { bool shiftKeyActive = false; }; -enum class CameraControllerType { +enum class CameraControllerType +{ FirstPerson, Orbit }; diff --git a/cpp/modmesh/view/RManager.cpp b/cpp/modmesh/view/RManager.cpp index 68cc1546..15cd5e49 100644 --- a/cpp/modmesh/view/RManager.cpp +++ b/cpp/modmesh/view/RManager.cpp @@ -202,7 +202,8 @@ void RManager::setUpMenu() []() {})); } -void RManager::setUpCameraControllersMenuItems() const { +void RManager::setUpCameraControllersMenuItems() const +{ auto * use_orbit_camera = new RAction( QString("Use Orbit Camera Controller"), QString("Use Oribt Camera Controller"), @@ -257,118 +258,89 @@ void RManager::setUpCameraControllersMenuItems() const { m_viewMenu->addAction(use_fps_camera); } -void RManager::setUpCameraMovementsMenuItems() const { +void RManager::setUpCameraMovementsMenuItems() const +{ auto * reset_camera = new RAction( - QString("Reset (esc)"), - QString("Reset (esc)"), - [this]() - { - // implement resetting camera control - qDebug() << "Reset to initial status."; - try - { - auto * viewer = dynamic_cast(m_mdiArea->currentSubWindow()->widget()); - Qt3DRender::QCamera * camera = viewer->camera(); - if (camera) - { - viewer->resetCamera(camera); - } - } - catch (std::bad_cast & e) - { - std::cerr << e.what() << std::endl; - } - }); + QString("Reset (esc)"), + QString("Reset (esc)"), + [this]() + { + // implement resetting camera control + qDebug() << "Reset to initial status."; + try + { + auto * viewer = dynamic_cast(m_mdiArea->currentSubWindow()->widget()); + Qt3DRender::QCamera * camera = viewer->camera(); + if (camera) + { + viewer->resetCamera(camera); + } + } + catch (std::bad_cast & e) + { + std::cerr << e.what() << std::endl; + } + }); auto * move_camera_up = new RAction( QString("Move camera up (W/⬆)"), QString("Move camera up (W/⬆)"), createCameraTranslateItemHandler([](CameraInputState & input) - { - input.tyAxisValue = 1.0; - }) - ); + { input.tyAxisValue = 1.0; })); auto * move_camera_down = new RAction( QString("Move camera down (S/⬇)"), QString("Move camera down (S/⬇)"), createCameraTranslateItemHandler([](CameraInputState & input) - { - input.tyAxisValue = -1.0; - }) - ); + { input.tyAxisValue = -1.0; })); auto * move_camera_right = new RAction( QString("Move camera right (D/➡)"), QString("Move camera right (D/➡)"), createCameraTranslateItemHandler([](CameraInputState & input) - { - input.txAxisValue = 1.0; - }) - ); + { input.txAxisValue = 1.0; })); auto * move_camera_left = new RAction( QString("Move camera left (A/⬅)"), QString("Move camera left (A/⬅)"), createCameraTranslateItemHandler([](CameraInputState & input) - { - input.txAxisValue = -1.0; - }) - ); + { input.txAxisValue = -1.0; })); auto * move_camera_forward = new RAction( QString("Move camera forward (Ctrl+W/⬆)"), QString("Move camera forward (Ctrl+W/⬆)"), createCameraTranslateItemHandler([](CameraInputState & input) - { - input.tzAxisValue = 1.0; - }) - ); + { input.tzAxisValue = 1.0; })); auto * move_camera_backward = new RAction( QString("Move camera down (Ctrl+S/⬇)"), QString("Move camera down (Ctrl+S/⬇)"), createCameraTranslateItemHandler([](CameraInputState & input) - { - input.tzAxisValue = -1.0; - }) - ); + { input.tzAxisValue = -1.0; })); auto * rotate_camera_positive_yaw = new RAction( QString("Rotate camera positive yaw"), QString("Rotate camera positive yaw"), createCameraRotateItemHandler([](CameraInputState & input) - { - input.rxAxisValue = 1.0; - }) - ); + { input.rxAxisValue = 1.0; })); auto * rotate_camera_negative_yaw = new RAction( QString("Rotate camera negative yaw"), QString("Rotate camera negative yaw"), createCameraRotateItemHandler([](CameraInputState & input) - { - input.rxAxisValue = -1.0; - }) - ); + { input.rxAxisValue = -1.0; })); auto * rotate_camera_positive_pitch = new RAction( QString("Rotate camera positive pitch"), QString("Rotate camera positive pitch"), createCameraRotateItemHandler([](CameraInputState & input) - { - input.ryAxisValue = 1.0; - }) - ); + { input.ryAxisValue = 1.0; })); auto * rotate_camera_negative_pitch = new RAction( QString("Rotate camera negative pitch"), QString("Rotate camera negative pitch"), createCameraRotateItemHandler([](CameraInputState & input) - { - input.ryAxisValue = -1.0; - }) - ); + { input.ryAxisValue = -1.0; })); reset_camera->setShortcut(QKeySequence(Qt::Key_Escape)); @@ -385,8 +357,10 @@ void RManager::setUpCameraMovementsMenuItems() const { m_viewMenu->addAction(rotate_camera_negative_pitch); } -std::function RManager::createCameraTranslateItemHandler(const std::function &func) const { - return [this, func]() { +std::function RManager::createCameraTranslateItemHandler(const std::function & func) const +{ + return [this, func]() + { try { auto * viewer = dynamic_cast(m_mdiArea->currentSubWindow()->widget()); @@ -405,8 +379,10 @@ std::function RManager::createCameraTranslateItemHandler(const std::func }; } -std::function RManager::createCameraRotateItemHandler(const std::function &func) const { - return [this, func]() { +std::function RManager::createCameraRotateItemHandler(const std::function & func) const +{ + return [this, func]() + { try { auto * viewer = dynamic_cast(m_mdiArea->currentSubWindow()->widget()); @@ -418,11 +394,11 @@ std::function RManager::createCameraRotateItemHandler(const std::functio CameraInputState input{}; func(input); - if(controller->getType() == CameraControllerType::Orbit) + if (controller->getType() == CameraControllerType::Orbit) { input.rightMouseButtonActive = true; } - else if(controller->getType() == CameraControllerType::FirstPerson) + else if (controller->getType() == CameraControllerType::FirstPerson) { input.leftMouseButtonActive = true; } diff --git a/cpp/modmesh/view/RManager.hpp b/cpp/modmesh/view/RManager.hpp index bba58ca7..2e8b6b8f 100644 --- a/cpp/modmesh/view/RManager.hpp +++ b/cpp/modmesh/view/RManager.hpp @@ -85,8 +85,8 @@ public slots: void setUpCameraControllersMenuItems() const; void setUpCameraMovementsMenuItems() const; - std::function createCameraTranslateItemHandler(const std::function &) const; - std::function createCameraRotateItemHandler(const std::function &) const; + std::function createCameraTranslateItemHandler(const std::function &) const; + std::function createCameraRotateItemHandler(const std::function &) const; bool m_already_setup = false; From 077205578c0a89286f52d9e5e26e7150470726df Mon Sep 17 00:00:00 2001 From: ThreeMonth03 Date: Wed, 14 Aug 2024 09:11:11 +0800 Subject: [PATCH 09/28] Add the syntax highlighting --- STYLE.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/STYLE.rst b/STYLE.rst index 10c42dd2..80f0d7ae 100644 --- a/STYLE.rst +++ b/STYLE.rst @@ -15,7 +15,7 @@ style when adding new code and changing existing code. The rules of thumb are: 1. The linters must be clean. Before creating and updating a `pull request `__, run: - .. code-block:: + .. code-block:: bash make lint @@ -259,7 +259,7 @@ The inclusion guard uses ``#pragma once`` in the first line before everything. Always use path-first inclusion (angle branket). Do not use current-first (double quote). -.. code-block:: +.. code-block:: cpp // Use this: search for include file start with the paths to the compiler. #include @@ -448,7 +448,7 @@ When creating a new file, put the following text at the top of the file (replace ```` with the year you create the file and ```` with your name and maybe email). The license text formatted for C++ files: -.. code-block:: +.. code-block:: cpp /* * Copyright (c) , @@ -480,7 +480,7 @@ your name and maybe email). The license text formatted for C++ files: The license text formatted for Python files: -.. code-block:: +.. code-block:: python # -*- coding: UTF-8 -*- # From aaea72b7ea5a6e5a64a73500c1730f692d60b980 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Wed, 14 Aug 2024 14:04:57 +0600 Subject: [PATCH 10/28] handle keys when no subwindow is open --- cpp/modmesh/view/RCameraController.cpp | 3 - cpp/modmesh/view/RManager.cpp | 110 ++++++++++--------------- cpp/modmesh/view/RManager.hpp | 5 +- 3 files changed, 45 insertions(+), 73 deletions(-) diff --git a/cpp/modmesh/view/RCameraController.cpp b/cpp/modmesh/view/RCameraController.cpp index df47064e..feb88164 100644 --- a/cpp/modmesh/view/RCameraController.cpp +++ b/cpp/modmesh/view/RCameraController.cpp @@ -28,13 +28,10 @@ #include -#include #include #include #include -#include "R3DWidget.hpp" - namespace modmesh { diff --git a/cpp/modmesh/view/RManager.cpp b/cpp/modmesh/view/RManager.cpp index 15cd5e49..ff26afea 100644 --- a/cpp/modmesh/view/RManager.cpp +++ b/cpp/modmesh/view/RManager.cpp @@ -158,7 +158,7 @@ void RManager::setUpMenu() m_viewMenu = m_mainWindow->menuBar()->addMenu(QString("View")); { setUpCameraControllersMenuItems(); - setUpCameraMovementsMenuItems(); + setUpCameraMovementMenuItems(); } m_oneMenu = m_mainWindow->menuBar()->addMenu(QString("One")); @@ -258,14 +258,13 @@ void RManager::setUpCameraControllersMenuItems() const m_viewMenu->addAction(use_fps_camera); } -void RManager::setUpCameraMovementsMenuItems() const +void RManager::setUpCameraMovementMenuItems() const { auto * reset_camera = new RAction( QString("Reset (esc)"), QString("Reset (esc)"), [this]() { - // implement resetting camera control qDebug() << "Reset to initial status."; try { @@ -285,62 +284,62 @@ void RManager::setUpCameraMovementsMenuItems() const auto * move_camera_up = new RAction( QString("Move camera up (W/⬆)"), QString("Move camera up (W/⬆)"), - createCameraTranslateItemHandler([](CameraInputState & input) - { input.tyAxisValue = 1.0; })); + createCameraMovementItemHandler([](CameraInputState & input) + { input.tyAxisValue = 1.0; })); auto * move_camera_down = new RAction( QString("Move camera down (S/⬇)"), QString("Move camera down (S/⬇)"), - createCameraTranslateItemHandler([](CameraInputState & input) - { input.tyAxisValue = -1.0; })); + createCameraMovementItemHandler([](CameraInputState & input) + { input.tyAxisValue = -1.0; })); auto * move_camera_right = new RAction( QString("Move camera right (D/➡)"), QString("Move camera right (D/➡)"), - createCameraTranslateItemHandler([](CameraInputState & input) - { input.txAxisValue = 1.0; })); + createCameraMovementItemHandler([](CameraInputState & input) + { input.txAxisValue = 1.0; })); auto * move_camera_left = new RAction( QString("Move camera left (A/⬅)"), QString("Move camera left (A/⬅)"), - createCameraTranslateItemHandler([](CameraInputState & input) - { input.txAxisValue = -1.0; })); + createCameraMovementItemHandler([](CameraInputState & input) + { input.txAxisValue = -1.0; })); auto * move_camera_forward = new RAction( QString("Move camera forward (Ctrl+W/⬆)"), QString("Move camera forward (Ctrl+W/⬆)"), - createCameraTranslateItemHandler([](CameraInputState & input) - { input.tzAxisValue = 1.0; })); + createCameraMovementItemHandler([](CameraInputState & input) + { input.tzAxisValue = 1.0; })); auto * move_camera_backward = new RAction( QString("Move camera down (Ctrl+S/⬇)"), QString("Move camera down (Ctrl+S/⬇)"), - createCameraTranslateItemHandler([](CameraInputState & input) - { input.tzAxisValue = -1.0; })); + createCameraMovementItemHandler([](CameraInputState & input) + { input.tzAxisValue = -1.0; })); auto * rotate_camera_positive_yaw = new RAction( QString("Rotate camera positive yaw"), QString("Rotate camera positive yaw"), - createCameraRotateItemHandler([](CameraInputState & input) - { input.rxAxisValue = 1.0; })); + createCameraMovementItemHandler([](CameraInputState & input) + { input.rxAxisValue = 1.0; })); auto * rotate_camera_negative_yaw = new RAction( QString("Rotate camera negative yaw"), QString("Rotate camera negative yaw"), - createCameraRotateItemHandler([](CameraInputState & input) - { input.rxAxisValue = -1.0; })); + createCameraMovementItemHandler([](CameraInputState & input) + { input.rxAxisValue = -1.0; })); auto * rotate_camera_positive_pitch = new RAction( QString("Rotate camera positive pitch"), QString("Rotate camera positive pitch"), - createCameraRotateItemHandler([](CameraInputState & input) - { input.ryAxisValue = 1.0; })); + createCameraMovementItemHandler([](CameraInputState & input) + { input.ryAxisValue = 1.0; })); auto * rotate_camera_negative_pitch = new RAction( QString("Rotate camera negative pitch"), QString("Rotate camera negative pitch"), - createCameraRotateItemHandler([](CameraInputState & input) - { input.ryAxisValue = -1.0; })); + createCameraMovementItemHandler([](CameraInputState & input) + { input.ryAxisValue = -1.0; })); reset_camera->setShortcut(QKeySequence(Qt::Key_Escape)); @@ -357,59 +356,36 @@ void RManager::setUpCameraMovementsMenuItems() const m_viewMenu->addAction(rotate_camera_negative_pitch); } -std::function RManager::createCameraTranslateItemHandler(const std::function & func) const +std::function RManager::createCameraMovementItemHandler(const std::function & func) const { return [this, func]() { - try - { - auto * viewer = dynamic_cast(m_mdiArea->currentSubWindow()->widget()); - - if (viewer->camera()) - { - CameraInputState input{}; - func(input); - viewer->cameraController()->updateCameraPosition(input, 0.01); - } - } - catch (std::bad_cast & e) - { - std::cerr << e.what() << std::endl; - } - }; -} - -std::function RManager::createCameraRotateItemHandler(const std::function & func) const -{ - return [this, func]() - { - try - { - auto * viewer = dynamic_cast(m_mdiArea->currentSubWindow()->widget()); + const auto * subwin = m_mdiArea->currentSubWindow(); + if (subwin == nullptr) + return; - if (viewer->camera()) - { - auto * controller = viewer->cameraController(); + auto * viewer = dynamic_cast(subwin->widget()); + if (viewer == nullptr || viewer->camera() == nullptr) + return; - CameraInputState input{}; - func(input); + const auto controllerType = viewer->cameraController()->getType(); + CameraInputState input{}; - if (controller->getType() == CameraControllerType::Orbit) - { - input.rightMouseButtonActive = true; - } - else if (controller->getType() == CameraControllerType::FirstPerson) - { - input.leftMouseButtonActive = true; - } + func(input); - viewer->cameraController()->updateCameraPosition(input, 0.01); - } - } - catch (std::bad_cast & e) + if (input.rxAxisValue != 0.f || input.ryAxisValue != 0.f) { - std::cerr << e.what() << std::endl; + if (controllerType == CameraControllerType::Orbit) + { + input.rightMouseButtonActive = true; + } + else if (controllerType == CameraControllerType::FirstPerson) + { + input.leftMouseButtonActive = true; + } } + + viewer->cameraController()->updateCameraPosition(input, 0.01); }; } diff --git a/cpp/modmesh/view/RManager.hpp b/cpp/modmesh/view/RManager.hpp index 2e8b6b8f..eca1f458 100644 --- a/cpp/modmesh/view/RManager.hpp +++ b/cpp/modmesh/view/RManager.hpp @@ -83,10 +83,9 @@ public slots: void setUpMenu(); void setUpCameraControllersMenuItems() const; - void setUpCameraMovementsMenuItems() const; + void setUpCameraMovementMenuItems() const; - std::function createCameraTranslateItemHandler(const std::function &) const; - std::function createCameraRotateItemHandler(const std::function &) const; + std::function createCameraMovementItemHandler(const std::function &) const; bool m_already_setup = false; From 5e7c8d98846d073e0e81edd31705c120bfa13559 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Wed, 14 Aug 2024 14:20:32 +0600 Subject: [PATCH 11/28] refactor resetCamera --- cpp/modmesh/view/R3DWidget.cpp | 11 +++++------ cpp/modmesh/view/R3DWidget.hpp | 5 +---- cpp/modmesh/view/RManager.cpp | 23 +++++++++-------------- cpp/modmesh/view/wrap_view.cpp | 13 +++---------- 4 files changed, 18 insertions(+), 34 deletions(-) diff --git a/cpp/modmesh/view/R3DWidget.cpp b/cpp/modmesh/view/R3DWidget.cpp index 0716cd76..4471dae0 100644 --- a/cpp/modmesh/view/R3DWidget.cpp +++ b/cpp/modmesh/view/R3DWidget.cpp @@ -41,7 +41,7 @@ R3DWidget::R3DWidget(Qt3DExtras::Qt3DWindow * window, RScene * scene, QWidget * { m_view->setRootEntity(m_scene); - resetCamera(m_view->camera()); + resetCamera(); if (Toggle::instance().fixed().get_show_axis()) { @@ -99,14 +99,13 @@ void R3DWidget::resizeEvent(QResizeEvent * event) m_container->resize(event->size()); } -void R3DWidget::resetCamera(Qt3DRender::QCamera * camera, - float positionX, - float positionY, - float positionZ) +void R3DWidget::resetCamera() const { + Qt3DRender::QCamera * camera = m_view->camera(); + // Set up the camera. camera->lens()->setPerspectiveProjection(45.0f, 16.0f / 9.0f, 0.1f, 1000.0f); - camera->setPosition(QVector3D(positionX, positionY, positionZ)); + camera->setPosition(QVector3D(0.0f, 0.0f, 10.0f)); camera->setViewCenter(QVector3D(0.0f, 0.0f, 0.0f)); camera->setUpVector(QVector3D(0.f, 1.f, 0.f)); diff --git a/cpp/modmesh/view/R3DWidget.hpp b/cpp/modmesh/view/R3DWidget.hpp index c30ef8f9..8727ad4a 100644 --- a/cpp/modmesh/view/R3DWidget.hpp +++ b/cpp/modmesh/view/R3DWidget.hpp @@ -100,10 +100,7 @@ class R3DWidget Qt3DExtras::QAbstractCameraController * qtCameraController() { return m_scene->controller(); } CameraController * cameraController() { return dynamic_cast(m_scene->controller()); } - void resetCamera(Qt3DRender::QCamera * camera, - float positionX = 0.0f, - float positionY = 0.0f, - float positionZ = 10.0f); + void resetCamera() const; QPixmap grabPixmap() const { return m_view->screen()->grabWindow(m_view->winId()); } diff --git a/cpp/modmesh/view/RManager.cpp b/cpp/modmesh/view/RManager.cpp index ff26afea..a8ba20f6 100644 --- a/cpp/modmesh/view/RManager.cpp +++ b/cpp/modmesh/view/RManager.cpp @@ -265,20 +265,15 @@ void RManager::setUpCameraMovementMenuItems() const QString("Reset (esc)"), [this]() { - qDebug() << "Reset to initial status."; - try - { - auto * viewer = dynamic_cast(m_mdiArea->currentSubWindow()->widget()); - Qt3DRender::QCamera * camera = viewer->camera(); - if (camera) - { - viewer->resetCamera(camera); - } - } - catch (std::bad_cast & e) - { - std::cerr << e.what() << std::endl; - } + const auto * subwin = m_mdiArea->currentSubWindow(); + if (subwin == nullptr) + return; + + auto * viewer = dynamic_cast(subwin->widget()); + if (viewer == nullptr || viewer->camera() == nullptr) + return; + + viewer->resetCamera(); }); auto * move_camera_up = new RAction( diff --git a/cpp/modmesh/view/wrap_view.cpp b/cpp/modmesh/view/wrap_view.cpp index a49c381a..2085b889 100644 --- a/cpp/modmesh/view/wrap_view.cpp +++ b/cpp/modmesh/view/wrap_view.cpp @@ -198,17 +198,10 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapR3DWidget py::arg("name")) .def( "resetCamera", - [](wrapped_type & self, float const & positionX, float const & positionY, float const & positionZ) + [](wrapped_type & self) { - Qt3DRender::QCamera * camera = self.camera(); - if (camera) - { - self.resetCamera(camera, positionX, positionY, positionZ); - } - }, - py::arg("positionX") = 0.0f, - py::arg("positionY") = 0.0f, - py::arg("positionZ") = 10.0f) + self.resetCamera(); + }) .def("cameraController", &wrapped_type::cameraController); #define DECL_QVECTOR3D_PROPERTY(NAME, GETTER, SETTER) \ From 527970be1d107be32755388bd688f49e3b63d550 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Wed, 14 Aug 2024 14:26:00 +0600 Subject: [PATCH 12/28] check for nullptr instead of catch bad_cast --- cpp/modmesh/view/RManager.cpp | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/cpp/modmesh/view/RManager.cpp b/cpp/modmesh/view/RManager.cpp index a8ba20f6..1255304c 100644 --- a/cpp/modmesh/view/RManager.cpp +++ b/cpp/modmesh/view/RManager.cpp @@ -212,16 +212,13 @@ void RManager::setUpCameraControllersMenuItems() const qDebug() << "Use Orbit Camera Controller (menu demo)"; for (auto subwin : m_mdiArea->subWindowList()) { - try - { - R3DWidget * viewer = dynamic_cast(subwin->widget()); - viewer->scene()->setOrbitCameraController(); - viewer->scene()->controller()->setCamera(viewer->camera()); - } - catch (std::bad_cast & e) - { - std::cerr << e.what() << std::endl; - } + auto * viewer = dynamic_cast(subwin->widget()); + + if (viewer == nullptr) + continue; + + viewer->scene()->setOrbitCameraController(); + viewer->scene()->controller()->setCamera(viewer->camera()); } }); @@ -233,16 +230,13 @@ void RManager::setUpCameraControllersMenuItems() const qDebug() << "Use First Person Camera Controller (menu demo)"; for (auto subwin : m_mdiArea->subWindowList()) { - try - { - R3DWidget * viewer = dynamic_cast(subwin->widget()); - viewer->scene()->setFirstPersonCameraController(); - viewer->scene()->controller()->setCamera(viewer->camera()); - } - catch (std::bad_cast & e) - { - std::cerr << e.what() << std::endl; - } + auto * viewer = dynamic_cast(subwin->widget()); + + if (viewer == nullptr) + continue; + + viewer->scene()->setFirstPersonCameraController(); + viewer->scene()->controller()->setCamera(viewer->camera()); } }); From ee98cd1c5d4680f9c76ec5c98a45f71a989e7a88 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Fri, 16 Aug 2024 13:07:12 +0600 Subject: [PATCH 13/28] review changes --- cpp/modmesh/view/RCameraController.cpp | 10 ++++----- cpp/modmesh/view/RManager.cpp | 31 +++++++++++++++----------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/cpp/modmesh/view/RCameraController.cpp b/cpp/modmesh/view/RCameraController.cpp index feb88164..c84f9498 100644 --- a/cpp/modmesh/view/RCameraController.cpp +++ b/cpp/modmesh/view/RCameraController.cpp @@ -89,7 +89,7 @@ RCameraInputListener::RCameraInputListener( state.ryAxisValue = m_ry_axis->value(); state.txAxisValue = m_tx_axis->value(); state.tyAxisValue = isCtrlPressed ? 0.0f : m_ty_axis->value(); - state.tzAxisValue = isCtrlPressed ? m_ty_axis->value() : 0.0f; + state.tzAxisValue = (isCtrlPressed ? m_ty_axis->value() : 0.0f) + m_tz_axis->value(); state.leftMouseButtonActive = m_left_mouse_button_action->isActive(); state.middleMouseButtonActive = m_middle_mouse_button_action->isActive(); @@ -182,7 +182,7 @@ void RCameraInputListener::initKeyboardListeners() const m_alt_button_input->setSourceDevice(m_keyboard_device); m_alt_button_action->addInput(m_alt_button_input); - // ctrl button + // ctrl button - On Windows Ctrl key, on Macs Cmd key m_ctrl_button_input->setButtons(QList{Qt::Key_Control}); m_ctrl_button_input->setSourceDevice(m_keyboard_device); m_ctrl_button_action->addInput(m_ctrl_button_input); @@ -295,9 +295,9 @@ void ROrbitCameraController::updateCameraPosition(const CameraInputState & input } else { - const float x = clamp(input.txAxisValue + (input.leftMouseButtonActive ? input.rxAxisValue : 0)); - const float y = clamp(input.tyAxisValue + (input.leftMouseButtonActive ? input.ryAxisValue : 0)); - const auto translation = QVector3D(x, y, input.tzAxisValue) * linearSpeed() * dt; + const auto translation = QVector3D( + input.txAxisValue, input.tyAxisValue, input.tzAxisValue) * + linearSpeed() * dt; camera()->translate(translation); } diff --git a/cpp/modmesh/view/RManager.cpp b/cpp/modmesh/view/RManager.cpp index 1255304c..2516e9d7 100644 --- a/cpp/modmesh/view/RManager.cpp +++ b/cpp/modmesh/view/RManager.cpp @@ -301,8 +301,8 @@ void RManager::setUpCameraMovementMenuItems() const { input.tzAxisValue = 1.0; })); auto * move_camera_backward = new RAction( - QString("Move camera down (Ctrl+S/⬇)"), - QString("Move camera down (Ctrl+S/⬇)"), + QString("Move camera backward (Ctrl+S/⬇)"), + QString("Move camera backward (Ctrl+S/⬇)"), createCameraMovementItemHandler([](CameraInputState & input) { input.tzAxisValue = -1.0; })); @@ -332,17 +332,18 @@ void RManager::setUpCameraMovementMenuItems() const reset_camera->setShortcut(QKeySequence(Qt::Key_Escape)); - m_viewMenu->addAction(reset_camera); - m_viewMenu->addAction(move_camera_up); - m_viewMenu->addAction(move_camera_down); - m_viewMenu->addAction(move_camera_right); - m_viewMenu->addAction(move_camera_left); - m_viewMenu->addAction(move_camera_forward); - m_viewMenu->addAction(move_camera_backward); - m_viewMenu->addAction(rotate_camera_positive_yaw); - m_viewMenu->addAction(rotate_camera_negative_yaw); - m_viewMenu->addAction(rotate_camera_positive_pitch); - m_viewMenu->addAction(rotate_camera_negative_pitch); + auto cameraMoveSubmenu = m_viewMenu->addMenu("Camera move"); + cameraMoveSubmenu->addAction(reset_camera); + cameraMoveSubmenu->addAction(move_camera_up); + cameraMoveSubmenu->addAction(move_camera_down); + cameraMoveSubmenu->addAction(move_camera_right); + cameraMoveSubmenu->addAction(move_camera_left); + cameraMoveSubmenu->addAction(move_camera_forward); + cameraMoveSubmenu->addAction(move_camera_backward); + cameraMoveSubmenu->addAction(rotate_camera_positive_yaw); + cameraMoveSubmenu->addAction(rotate_camera_negative_yaw); + cameraMoveSubmenu->addAction(rotate_camera_positive_pitch); + cameraMoveSubmenu->addAction(rotate_camera_negative_pitch); } std::function RManager::createCameraMovementItemHandler(const std::function & func) const @@ -366,6 +367,10 @@ std::function RManager::createCameraMovementItemHandler(const std::funct { if (controllerType == CameraControllerType::Orbit) { + constexpr float orbitRotationSpeed = 5.0f; + + input.rxAxisValue *= orbitRotationSpeed; + input.ryAxisValue *= orbitRotationSpeed; input.rightMouseButtonActive = true; } else if (controllerType == CameraControllerType::FirstPerson) From 08c5c2434c1b98782bc88a3d4d6c6709f5c07369 Mon Sep 17 00:00:00 2001 From: ThreeMonth03 Date: Sat, 17 Aug 2024 11:41:19 +0800 Subject: [PATCH 14/28] Fix the typo in README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 67240a4c..d9b253d7 100644 --- a/README.rst +++ b/README.rst @@ -25,7 +25,7 @@ Click the Windows release run and scroll down to the "artifacts" section to download the zip file (login to `GitHub `__ is required). A direct download link can be found in https://doc.solvcon.net/. -Refenreces +References ========== * The numerical notes: https://github.com/solvcon/mmnote. From 5fe578afbdf177571c45475ee822438e7eb3baba Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Sat, 17 Aug 2024 13:49:15 +0600 Subject: [PATCH 15/28] fix orbit camera rotation by keyboard and zoom --- cpp/modmesh/view/RCameraController.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/modmesh/view/RCameraController.cpp b/cpp/modmesh/view/RCameraController.cpp index c84f9498..77774f85 100644 --- a/cpp/modmesh/view/RCameraController.cpp +++ b/cpp/modmesh/view/RCameraController.cpp @@ -287,11 +287,11 @@ void ROrbitCameraController::updateCameraPosition(const CameraInputState & input // keyboard Input if (input.altKeyActive) { - orbit(input.txAxisValue * dt, input.tzAxisValue * dt); + orbit(input.txAxisValue * dt, input.tyAxisValue * dt); } else if (input.shiftKeyActive) { - zoom(input.tzAxisValue * linearSpeed() * dt); + zoom(input.tyAxisValue * linearSpeed() * dt); } else { From e2bedb532a6cf6b783b55669940ce82c919472c8 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Sat, 17 Aug 2024 15:08:09 +0600 Subject: [PATCH 16/28] add ending marks --- cpp/modmesh/view/RCameraController.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cpp/modmesh/view/RCameraController.hpp b/cpp/modmesh/view/RCameraController.hpp index 15dd0282..c61df707 100644 --- a/cpp/modmesh/view/RCameraController.hpp +++ b/cpp/modmesh/view/RCameraController.hpp @@ -59,7 +59,7 @@ struct CameraInputState bool altKeyActive = false; bool shiftKeyActive = false; -}; +}; /* end struct CameraInputState */ enum class CameraControllerType { @@ -126,7 +126,7 @@ class RCameraInputListener : public Qt3DCore::QEntity Qt3DInput::QButtonAxisInput * m_keyboard_tx_neg_input; Qt3DInput::QButtonAxisInput * m_keyboard_ty_neg_input; Qt3DInput::QButtonAxisInput * m_keyboard_tz_neg_input; -}; +}; /* end class RCameraInputListener */ class CameraController { @@ -159,7 +159,7 @@ class CameraController { return dynamic_cast(this); } -}; +}; /* end class CameraController */ class RFirstPersonCameraController : public Qt3DExtras::QFirstPersonCameraController , public CameraController @@ -181,7 +181,7 @@ class RFirstPersonCameraController : public Qt3DExtras::QFirstPersonCameraContro void updateCameraPosition(const CameraInputState & input, float dt) override; CameraControllerType getType() override { return CameraControllerType::FirstPerson; } -}; +}; /* end class RFirstPersonCameraController */ class ROrbitCameraController : public Qt3DExtras::QOrbitCameraController , public CameraController @@ -209,7 +209,7 @@ class ROrbitCameraController : public Qt3DExtras::QOrbitCameraController static float zoomDistanceSquared(QVector3D firstPoint, QVector3D secondPoint); CameraControllerType getType() override { return CameraControllerType::Orbit; } -}; +}; /* end class ROrbitCameraController */ } /* end namespace modmesh */ From 6d4c3dac069f3c97ab96a87b49ac9d5483b8f59b Mon Sep 17 00:00:00 2001 From: j8xixo12 Date: Mon, 19 Aug 2024 07:54:10 +0800 Subject: [PATCH 17/28] GUI configuration supporting string based access --- modmesh/app/euler1d.py | 216 +++++++++++++++-------------------------- 1 file changed, 76 insertions(+), 140 deletions(-) diff --git a/modmesh/app/euler1d.py b/modmesh/app/euler1d.py index f1f79598..d3c2d1a4 100644 --- a/modmesh/app/euler1d.py +++ b/modmesh/app/euler1d.py @@ -104,16 +104,16 @@ def update(self, adata, ndata): self.num.figure.canvas.draw() -class SolverConfig(): +class GUIConfig: """ - Configuration class for the solver. + Configuration class for the GUI. - This class provides a configuration interface for the solver, allowing + This class provides a configuration interface for the GUI, allowing users to set and retrieve parameters related to the simulation. Attributes: - `state` (:class:`State`): The state object holding configuration - data in the form of [variable_name, value, description]. + data. - `_tbl_content` (:class:`State`): The content of the configuration table. - `_col_header` (list): The header for the configuration @@ -132,13 +132,40 @@ class SolverConfig(): table. - :meth:`columnCount()`: Get the number of columns in the configuration table. - - :meth:`get_var(key_row, key_col)`: Get the value of a configuration - variable based on its key. """ - def __init__(self, input_data): + class Accessor: + """ + Helper calss to access data within the configuration table using + multiple dimensions, currenlty only support 2-dimensions table. + + This class allows users to access data by chaining multiple [], + supporting string-based. + """ + def __init__(self, data, dimIdx=0, header=None): + self._data = data + self._header = header + self._dimIdx = dimIdx + + def __getitem__(self, key): + if self._dimIdx == 0: + for row_idx, row in enumerate(self._data): + if key == row[0]: + return GUIConfig.Accessor(self._data[row_idx], + self._dimIdx+1, + self._header) + else: + for idx, ele in enumerate(self._header): + if key == ele: + return self._data[idx] + raise KeyError(f'Invaild key: {key} not found in table') + + def __init__(self, input_data, col_headers): self.state = State(input_data) self._tbl_content = self.state - self._col_header = ["variable", "value", "description"] + self._col_header = col_headers + + def __getitem__(self, key): + return self.Accessor(self._tbl_content, 0, self._col_header)[key] def data(self, row, col): """ @@ -209,25 +236,27 @@ def columnCount(self): """ return len(self._tbl_content[0]) - def get_var(self, key_row, key_col): - """ - Get the value of a configuration variable based on its key. - :param key_row: The row key of the variable. - :type key_row: str - :param key_col: The col key of the variable. - :type key_col: str - :return: The value of the specified variable. - """ - for ele in self.state: - if key_row == ele[0]: - for idx, name in enumerate(self._col_header): - if key_col == name: - return ele[idx] - return None +class SolverConfig(GUIConfig): + """ + Configuration class for the solver. + This class provides a configuration interface for the solver, allowing + users to set and retrieve parameters related to the simulation. -class PlotConfig(): + Attributes: + - `state` (:class:`State`): The state object holding configuration + data in the form of [variable_name, value, description]. + - `_tbl_content` (:class:`State`): The content of the + configuration table. + - `_col_header` (list): The header for the configuration + table columns. + """ + def __init__(self, input_data): + super().__init__(input_data, ["variable", "value", "description"]) + + +class PlotConfig(GUIConfig): """ Configuration class for the plot. @@ -242,66 +271,12 @@ class PlotConfig(): configuration table. - `_col_header` (list): The header for the configuration table columns. - - Methods: - - :meth:`data(row, col)`: Get the value at a specific row and column - in the configuration table. - - :meth:`setData(row, col, value)`: Set the value at a specific row - and column in the configuration table. - - :meth:`columnHeader(col)`: Get the header for a specific column - in the configuration table. - - :meth:`editable(row, col)`: Check if a cell in the configuration - table is editable. - - :meth:`rowCount()`: Get the number of rows in the configuration - table. - - :meth:`columnCount()`: Get the number of columns in the - configuration table. - - :meth:`get_var(key, key_row, key_col)`: Get the value of a - configuration variable based on its key. """ def __init__(self, input_data): - self.state = State(input_data) - self._tbl_content = self.state - self._col_header = ["variable", - "line_selection", - "y_axis_upper_limit", - "y_axis_bottom_limit"] - - def data(self, row, col): - """ - Get the value at a specific row and column in the configuration table. - - :param row: Row index. - :type row: int - :param col: Column index. - :type col: int - :return: The value at the specified location in - the configuration table. - """ - return self._tbl_content[row][col] - - def setData(self, row, col, value): - """ - Set the value at a specific row and column in the configuration table. - - :param row: Row index. - :type row: int - :param col: Column index. - :type col: int - :prarm value: Any - :return None - """ - self._tbl_content[row][col] = value - - def columnHeader(self, col): - """ - Get the specific column header in the configuration table. - - :param col: Column index. - :type col: int - :return: The header for the specific column. - """ - return self._col_header[col] + super().__init__(input_data, ["variable", + "line_selection", + "y_axis_upper_limit", + "y_axis_bottom_limit"]) def editable(self, row, col): """ @@ -317,42 +292,6 @@ def editable(self, row, col): return True return False - # Delete row header - rowHeader = None - - def rowCount(self): - """ - Get the number of rows in the configuration table. - - :return: The number of rows. - """ - return len(self._tbl_content) - - def columnCount(self): - """ - Get the number of columns in the configuration table. - - :return: The number of columns. - """ - return len(self._tbl_content[0]) - - def get_var(self, key_row, key_col): - """ - Get the value of a configuration variable based on its key. - - :param key_row: The row key of the variable. - :type key_row: str - :param key_col: The col key of the variable. - :type key_col: str - :return: The value of the specified variable. - """ - for ele in self.state: - if key_row == ele[0]: - for idx, name in enumerate(self._col_header): - if key_col == name: - return ele[idx] - return None - class Euler1DApp(): """ @@ -493,24 +432,21 @@ def set_solver_config(self): :return None """ - self.init_solver(gamma=self.solver_config.get_var("gamma", "value"), - pressure_left=self.solver_config.get_var("p_left", - "value"), - density_left=self.solver_config.get_var("rho_left", - "value"), - pressure_right=self.solver_config.get_var("p_right", - "value"), - density_right=self.solver_config.get_var("rho_right", - "value"), - xmin=self.solver_config.get_var("xmin", "value"), - xmax=self.solver_config.get_var("xmax", "value"), - ncoord=self.solver_config.get_var("ncoord", "value"), - time_increment=(self.solver_config. - get_var("time_increment", "value"))) + self.init_solver(gamma=self.solver_config["gamma"]["value"], + pressure_left=self.solver_config["p_left"]["value"], + density_left=self.solver_config["rho_left"]["value"], + pressure_right=self.solver_config["p_right"]["value"], + density_right=self.solver_config["rho_right"] + ["value"], + xmin=self.solver_config["xmin"]["value"], + xmax=self.solver_config["xmax"]["value"], + ncoord=self.solver_config["ncoord"]["value"], + time_increment=(self.solver_config["time_increment"][ + "value"])) self.current_step = 0 - self.interval = self.solver_config.get_var("timer_interval", "value") - self.max_steps = self.solver_config.get_var("max_steps", "value") - self.profiling = self.solver_config.get_var("profiling", "value") + self.interval = self.solver_config["timer_interval"]["value"] + self.max_steps = self.solver_config["max_steps"]["value"] + self.profiling = self.solver_config["profiling"]["value"] def setup_timer(self): """ @@ -592,7 +528,7 @@ def build_single_figure(self): ): # Plot multiple data line with same X axis, it need to plot a # data line on main axis first - if self.plot_config.get_var(data.name, "line_selection"): + if self.plot_config[data.name]["line_selection"]: data.axis = ax data.ana, = ax.plot(x, np.zeros_like(x), f'{color}-', @@ -683,10 +619,10 @@ def update_layout(self): self.internal_energy, self.entropy ): - line.y_upper_lim = self.plot_config.get_var(line.name, - "y_axis_upper_limit") - line.y_bottom_lim = self.plot_config.get_var(line.name, - "y_axis_bottom_limit") + line.y_upper_lim = self.plot_config[line.name][ + "y_axis_upper_limit"] + line.y_bottom_lim = self.plot_config[line.name][ + "y_axis_bottom_limit"] if self.use_grid_layout: self.plot_holder.plot = self.build_grid_figure() From d25d5c2e7a37da1a74cd142ebc39ee0bf43b76bd Mon Sep 17 00:00:00 2001 From: j8xixo12 Date: Thu, 22 Aug 2024 20:40:19 +0800 Subject: [PATCH 18/28] move Accessor out of GUIConfig and fix docstring style --- modmesh/app/euler1d.py | 99 +++++++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/modmesh/app/euler1d.py b/modmesh/app/euler1d.py index d3c2d1a4..aad01bc9 100644 --- a/modmesh/app/euler1d.py +++ b/modmesh/app/euler1d.py @@ -104,7 +104,41 @@ def update(self, adata, ndata): self.num.figure.canvas.draw() -class GUIConfig: +class _Accessor: + """ + Helper calss to access data within the configuration table using + multiple dimensions, currenlty only support 2-dimensions table. + + This class allows users to access data by chaining multiple [], + supporting string-based. + + Attributes: + :ivar: _data: handle input data that allow user can using + index or string to access it. + :ivar: _header (list): list of data's column header name. + :ivar: _dimIdx (int): using to indicate which dimension is + currenlty being accessed by __getitem__. + """ + def __init__(self, data, dimIdx=0, header=None): + self._data = data + self._header = header + self._dimIdx = dimIdx + + def __getitem__(self, key): + if self._dimIdx == 0: + for row_idx, row in enumerate(self._data): + if key == row[0]: + return _Accessor(self._data[row_idx], + self._dimIdx+1, + self._header) + else: + for idx, ele in enumerate(self._header): + if key == ele: + return self._data[idx] + raise KeyError(f'Invaild key: {key} not found in table') + + +class GUIConfig(object): """ Configuration class for the GUI. @@ -112,52 +146,27 @@ class GUIConfig: users to set and retrieve parameters related to the simulation. Attributes: - - `state` (:class:`State`): The state object holding configuration - data. - - `_tbl_content` (:class:`State`): The content of the - configuration table. - - `_col_header` (list): The header for the configuration - table columns. + :ivar: state (:class:`State`): The state object holding configuration + data. + :ivar: _tbl_content (:class:`State`): The content of the + configuration table. + :ivar: _col_header (list): The header for the configuration + table columns. Methods: - - :meth:`data(row, col)`: Get the value at a specific row and column - in the configuration table. - - :meth:`setData(row, col, value)`: Set the value at a specific row - and column in the configuration table. - - :meth:`columnHeader(col)`: Get the header for a specific column - in the configuration table. - - :meth:`editable(row, col)`: Check if a cell in the configuration - table is editable. - - :meth:`rowCount()`: Get the number of rows in the configuration - table. - - :meth:`columnCount()`: Get the number of columns in the - configuration table. + :meth:`data(row, col)`: Get the value at a specific row and column + in the configuration table. + :meth:`setData(row, col, value)`: Set the value at a specific row + and column in the configuration table. + :meth:`columnHeader(col)`: Get the header for a specific column + in the configuration table. + :meth:`editable(row, col)`: Check if a cell in the configuration + table is editable. + :meth:`rowCount()`: Get the number of rows in the configuration + table. + :meth:`columnCount()`: Get the number of columns in the + configuration table. """ - class Accessor: - """ - Helper calss to access data within the configuration table using - multiple dimensions, currenlty only support 2-dimensions table. - - This class allows users to access data by chaining multiple [], - supporting string-based. - """ - def __init__(self, data, dimIdx=0, header=None): - self._data = data - self._header = header - self._dimIdx = dimIdx - - def __getitem__(self, key): - if self._dimIdx == 0: - for row_idx, row in enumerate(self._data): - if key == row[0]: - return GUIConfig.Accessor(self._data[row_idx], - self._dimIdx+1, - self._header) - else: - for idx, ele in enumerate(self._header): - if key == ele: - return self._data[idx] - raise KeyError(f'Invaild key: {key} not found in table') def __init__(self, input_data, col_headers): self.state = State(input_data) @@ -165,7 +174,7 @@ def __init__(self, input_data, col_headers): self._col_header = col_headers def __getitem__(self, key): - return self.Accessor(self._tbl_content, 0, self._col_header)[key] + return _Accessor(self._tbl_content, 0, self._col_header)[key] def data(self, row, col): """ From effaaad272f48ef7ebf75a66350c1519047b794a Mon Sep 17 00:00:00 2001 From: Yung-Yu Chen Date: Fri, 23 Aug 2024 20:09:18 +0800 Subject: [PATCH 19/28] [style] Always add curly braces --- STYLE.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/STYLE.rst b/STYLE.rst index 80f0d7ae..c3db405d 100644 --- a/STYLE.rst +++ b/STYLE.rst @@ -440,6 +440,26 @@ hundreds of lines) to keep track of. } /* end namespace modmesh */ +C++ Curly Braces +==== + +Always add curly braces and always add them in standalone lines: + +.. code-block:: cpp + + if (condition) + { + return; + } + +That is, never drop curly braces even when you can: + +.. code-block:: cpp + + // NEVER DROP CURLY BRACES + if (condition) + return; + Copyright Notice ==== From c288a98efcaf742583b76284d07690786aab0630 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Fri, 23 Aug 2024 19:16:06 +0600 Subject: [PATCH 20/28] expose complete camera controller to python + tests --- cpp/modmesh/view/R3DWidget.cpp | 20 +-------- cpp/modmesh/view/R3DWidget.hpp | 2 - cpp/modmesh/view/RCameraController.cpp | 12 ++++++ cpp/modmesh/view/RCameraController.hpp | 44 ++++++++++++++------ cpp/modmesh/view/RManager.cpp | 2 +- cpp/modmesh/view/wrap_view.cpp | 57 ++++++++++++++++++++++---- tests/test_view.py | 45 ++++++++++++++++++-- 7 files changed, 135 insertions(+), 47 deletions(-) diff --git a/cpp/modmesh/view/R3DWidget.cpp b/cpp/modmesh/view/R3DWidget.cpp index 4471dae0..9809aaab 100644 --- a/cpp/modmesh/view/R3DWidget.cpp +++ b/cpp/modmesh/view/R3DWidget.cpp @@ -41,7 +41,8 @@ R3DWidget::R3DWidget(Qt3DExtras::Qt3DWindow * window, RScene * scene, QWidget * { m_view->setRootEntity(m_scene); - resetCamera(); + cameraController()->setCamera(m_view->camera()); + cameraController()->reset(); if (Toggle::instance().fixed().get_show_axis()) { @@ -99,23 +100,6 @@ void R3DWidget::resizeEvent(QResizeEvent * event) m_container->resize(event->size()); } -void R3DWidget::resetCamera() const -{ - Qt3DRender::QCamera * camera = m_view->camera(); - - // Set up the camera. - camera->lens()->setPerspectiveProjection(45.0f, 16.0f / 9.0f, 0.1f, 1000.0f); - camera->setPosition(QVector3D(0.0f, 0.0f, 10.0f)); - camera->setViewCenter(QVector3D(0.0f, 0.0f, 0.0f)); - camera->setUpVector(QVector3D(0.f, 1.f, 0.f)); - - // Set up the camera control. - auto * control = m_scene->controller(); - control->setCamera(camera); - control->setLinearSpeed(50.0f); - control->setLookSpeed(180.0f); -} - } /* end namespace modmesh */ // vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/cpp/modmesh/view/R3DWidget.hpp b/cpp/modmesh/view/R3DWidget.hpp index 8727ad4a..b1b032e1 100644 --- a/cpp/modmesh/view/R3DWidget.hpp +++ b/cpp/modmesh/view/R3DWidget.hpp @@ -100,8 +100,6 @@ class R3DWidget Qt3DExtras::QAbstractCameraController * qtCameraController() { return m_scene->controller(); } CameraController * cameraController() { return dynamic_cast(m_scene->controller()); } - void resetCamera() const; - QPixmap grabPixmap() const { return m_view->screen()->grabWindow(m_view->winId()); } void showMark(); diff --git a/cpp/modmesh/view/RCameraController.cpp b/cpp/modmesh/view/RCameraController.cpp index 77774f85..3bb7b572 100644 --- a/cpp/modmesh/view/RCameraController.cpp +++ b/cpp/modmesh/view/RCameraController.cpp @@ -212,6 +212,18 @@ void RCameraInputListener::initKeyboardListeners() const m_ty_axis->addInput(m_keyboard_ty_neg_input); } +void CameraController::reset() +{ + camera()->lens()->setPerspectiveProjection(45.0f, 16.0f / 9.0f, 0.1f, 1000.0f); + + setPosition(QVector3D(0.0f, 0.0f, 10.0f)); + setViewCenter(QVector3D(0.0f, 0.0f, 0.0f)); + setUpVector(QVector3D(0.f, 1.f, 0.f)); + + setLinearSpeed(50.0f); + setLookSpeed(180.0f); +} + RFirstPersonCameraController::RFirstPersonCameraController(QNode * parent) : QFirstPersonCameraController(parent) { diff --git a/cpp/modmesh/view/RCameraController.hpp b/cpp/modmesh/view/RCameraController.hpp index c61df707..d59abb73 100644 --- a/cpp/modmesh/view/RCameraController.hpp +++ b/cpp/modmesh/view/RCameraController.hpp @@ -135,21 +135,29 @@ class CameraController virtual void updateCameraPosition(const CameraInputState & state, float dt) = 0; - virtual Qt3DRender::QCamera * getCamera() = 0; + virtual Qt3DRender::QCamera * camera() const = 0; + virtual void setCamera(Qt3DRender::QCamera * camera) = 0; - virtual float getLinearSpeed() = 0; + virtual float linearSpeed() const = 0; + virtual void setLinearSpeed(float value) = 0; - virtual float getLookSpeed() = 0; + virtual float lookSpeed() const = 0; + virtual void setLookSpeed(float value) = 0; virtual CameraControllerType getType() = 0; - QVector3D position() { return getCamera()->position(); } + QVector3D position() const { return camera()->position(); } + void setPosition(const QVector3D & value) const { camera()->setPosition(value); } - QVector3D viewVector() { return getCamera()->viewVector(); } + QVector3D viewVector() const { return camera()->viewVector(); } - QVector3D viewCenter() { return getCamera()->viewCenter(); } + QVector3D viewCenter() const { return camera()->viewCenter(); } + void setViewCenter(const QVector3D & value) const { camera()->setViewCenter(value); } - QVector3D upVector() { return getCamera()->upVector(); } + QVector3D upVector() const { return camera()->upVector(); } + void setUpVector(const QVector3D & value) const { camera()->setUpVector(value); } + + void reset(); protected: RCameraInputListener * m_listener = nullptr; @@ -169,9 +177,14 @@ class RFirstPersonCameraController : public Qt3DExtras::QFirstPersonCameraContro public: explicit RFirstPersonCameraController(QNode * parent = nullptr); - Qt3DRender::QCamera * getCamera() override { return camera(); } - float getLinearSpeed() override { return linearSpeed(); } - float getLookSpeed() override { return lookSpeed(); } + Qt3DRender::QCamera * camera() const override { return QFirstPersonCameraController::camera(); } + void setCamera(Qt3DRender::QCamera * camera) override { QFirstPersonCameraController::setCamera(camera); } + + float linearSpeed() const override { return QFirstPersonCameraController::linearSpeed(); } + void setLinearSpeed(float value) override { QFirstPersonCameraController::setLinearSpeed(value); } + + float lookSpeed() const override { return QFirstPersonCameraController::lookSpeed(); } + void setLookSpeed(float value) override { QFirstPersonCameraController::setLookSpeed(value); } private: static constexpr auto lookSpeedFactorOnShiftPressed = 0.2f; @@ -191,9 +204,14 @@ class ROrbitCameraController : public Qt3DExtras::QOrbitCameraController public: explicit ROrbitCameraController(QNode * parent = nullptr); - Qt3DRender::QCamera * getCamera() override { return camera(); } - float getLinearSpeed() override { return linearSpeed(); } - float getLookSpeed() override { return lookSpeed(); } + Qt3DRender::QCamera * camera() const override { return QOrbitCameraController::camera(); } + void setCamera(Qt3DRender::QCamera * camera) override { QOrbitCameraController::setCamera(camera); } + + float linearSpeed() const override { return QOrbitCameraController::linearSpeed(); } + void setLinearSpeed(float value) override { QOrbitCameraController::setLinearSpeed(value); } + + float lookSpeed() const override { return QOrbitCameraController::lookSpeed(); } + void setLookSpeed(float value) override { QOrbitCameraController::setLookSpeed(value); } private: void moveCamera(const InputState & state, float dt) override {} diff --git a/cpp/modmesh/view/RManager.cpp b/cpp/modmesh/view/RManager.cpp index 2516e9d7..21177e1d 100644 --- a/cpp/modmesh/view/RManager.cpp +++ b/cpp/modmesh/view/RManager.cpp @@ -267,7 +267,7 @@ void RManager::setUpCameraMovementMenuItems() const if (viewer == nullptr || viewer->camera() == nullptr) return; - viewer->resetCamera(); + viewer->cameraController()->reset(); }); auto * move_camera_up = new RAction( diff --git a/cpp/modmesh/view/wrap_view.cpp b/cpp/modmesh/view/wrap_view.cpp index 2085b889..52b60368 100644 --- a/cpp/modmesh/view/wrap_view.cpp +++ b/cpp/modmesh/view/wrap_view.cpp @@ -196,12 +196,6 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapR3DWidget } }, py::arg("name")) - .def( - "resetCamera", - [](wrapped_type & self) - { - self.resetCamera(); - }) .def("cameraController", &wrapped_type::cameraController); #define DECL_QVECTOR3D_PROPERTY(NAME, GETTER, SETTER) \ @@ -448,6 +442,10 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapRCameraController namespace py = pybind11; (*this) + .def( + "reset", + [](wrapped_type & self) + { self.reset(); }) .def( "updateCameraPosition", []( @@ -496,14 +494,37 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapRCameraController const auto position = self.position(); return py::make_tuple(position.x(), position.y(), position.z()); }) + .def( + "setPosition", + [](wrapped_type & self, float x, float y, float z) + { + self.setPosition(QVector3D(x, y, z)); + }, + py::arg("x"), + py::arg("y"), + py::arg("z")) .def( "linearSpeed", [](wrapped_base_type & self) - { return self.getLinearSpeed(); }) + { return self.linearSpeed(); }) + .def( + "setLinearSpeed", + [](wrapped_base_type & self, float value) + { + self.setLinearSpeed(value); + }, + py::arg("value")) .def( "lookSpeed", [](wrapped_base_type & self) - { return self.getLookSpeed(); }) + { return self.lookSpeed(); }) + .def( + "setLookSpeed", + [](wrapped_base_type & self, float value) + { + self.setLookSpeed(value); + }, + py::arg("value")) .def( "viewCenter", [](wrapped_base_type & self) @@ -511,6 +532,15 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapRCameraController const auto center = self.viewCenter(); return py::make_tuple(center.x(), center.y(), center.z()); }) + .def( + "setViewCenter", + [](wrapped_type & self, float x, float y, float z) + { + self.setViewCenter(QVector3D(x, y, z)); + }, + py::arg("x"), + py::arg("y"), + py::arg("z")) .def( "viewVector", [](wrapped_base_type & self) @@ -524,7 +554,16 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapRCameraController { const auto vector = self.upVector(); return py::make_tuple(vector.x(), vector.y(), vector.z()); - }); + }) + .def( + "setUpVector", + [](wrapped_type & self, float x, float y, float z) + { + self.setUpVector(QVector3D(x, y, z)); + }, + py::arg("x"), + py::arg("y"), + py::arg("z")); } }; diff --git a/tests/test_view.py b/tests/test_view.py index 4424b4b6..27d66831 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -30,6 +30,7 @@ import os import modmesh + try: from modmesh import view except ImportError: @@ -75,13 +76,24 @@ def setUpClass(cls): cls.controller = widget.cameraController() cls.move = cls.controller.updateCameraPosition + cls.resetCamera = cls.controller.reset cls.pos = cls.controller.position + cls.set_pos = cls.controller.setPosition + cls.look_speed = cls.controller.lookSpeed + cls.set_look_speed = cls.controller.setLookSpeed + cls.linear_speed = cls.controller.linearSpeed + cls.set_linear_speed = cls.controller.setLinearSpeed + cls.view_vector = cls.controller.viewVector + cls.view_center = cls.controller.viewCenter + cls.set_view_center = cls.controller.setViewCenter + cls.up_vector = cls.controller.upVector + cls.set_up_vector = cls.controller.setUpVector @classmethod def tearDownClass(cls): @@ -111,12 +123,37 @@ def normalize(self, vec): return vec / np.linalg.norm(vec) +@unittest.skipIf(GITHUB_ACTIONS, "GUI is not available in GitHub Actions") +class ViewCommonCameraTC(ViewCameraTB, unittest.TestCase): + camera_type = "fps" # no difference when use orbit camera + + def setUp(self): + self.resetCamera() + + def test_value_get_set(self): + self.set_linear_speed(123.0) + self.assertEqual(self.linear_speed(), 123.0) + + self.set_look_speed(456.0) + self.assertEqual(self.look_speed(), 456.0) + + def test_vector_get_set(self): + self.set_pos(x=1, y=2, z=3) + self.assertEqual(self.pos(), (1, 2, 3)) + + self.set_view_center(x=4, y=5, z=6) + self.assertEqual(self.view_center(), (4, 5, 6)) + + self.set_up_vector(x=7, y=8, z=9) + self.assertEqual(self.up_vector(), (7, 8, 9)) + + @unittest.skipIf(GITHUB_ACTIONS, "GUI is not available in GitHub Actions") class ViewFPSCameraTC(ViewCameraTB, unittest.TestCase): camera_type = "fps" def setUp(self): - self.widget.resetCamera() + self.resetCamera() def test_reset(self): dt = 0.01 @@ -133,7 +170,7 @@ def test_reset(self): self.assertNotEqual(self.view_center(), initial_view_center) self.assertNotEqual(self.up_vector(), initial_up_vector) - self.widget.resetCamera() + self.resetCamera() self.assertEqual(self.pos(), initial_pos) self.assertEqual(self.view_vector(), initial_view_vector) @@ -212,7 +249,7 @@ class ViewOrbitCameraTC(ViewCameraTB, unittest.TestCase): camera_type = "orbit" def setUp(self): - self.widget.resetCamera() + self.resetCamera() def test_reset(self): dt = 0.01 @@ -229,7 +266,7 @@ def test_reset(self): self.assertNotEqual(self.view_center(), initial_view_center) self.assertNotEqual(self.up_vector(), initial_up_vector) - self.widget.resetCamera() + self.resetCamera() self.assertEqual(self.pos(), initial_pos) self.assertEqual(self.view_vector(), initial_view_vector) From dcf2bc8346a66a94a472ac0918a286f113d6b159 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Fri, 23 Aug 2024 19:36:12 +0600 Subject: [PATCH 21/28] camera default values api + test --- cpp/modmesh/view/RCameraController.cpp | 10 +++--- cpp/modmesh/view/RCameraController.hpp | 12 +++++++ cpp/modmesh/view/wrap_view.cpp | 43 +++++++++++++++++++++++++- tests/test_view.py | 21 +++++++++++++ 4 files changed, 80 insertions(+), 6 deletions(-) diff --git a/cpp/modmesh/view/RCameraController.cpp b/cpp/modmesh/view/RCameraController.cpp index 3bb7b572..3b7bb0ce 100644 --- a/cpp/modmesh/view/RCameraController.cpp +++ b/cpp/modmesh/view/RCameraController.cpp @@ -216,12 +216,12 @@ void CameraController::reset() { camera()->lens()->setPerspectiveProjection(45.0f, 16.0f / 9.0f, 0.1f, 1000.0f); - setPosition(QVector3D(0.0f, 0.0f, 10.0f)); - setViewCenter(QVector3D(0.0f, 0.0f, 0.0f)); - setUpVector(QVector3D(0.f, 1.f, 0.f)); + setPosition(m_default_position); + setViewCenter(m_default_view_center); + setUpVector(m_default_up_vector); - setLinearSpeed(50.0f); - setLookSpeed(180.0f); + setLinearSpeed(m_default_linear_speed); + setLookSpeed(m_default_look_speed); } RFirstPersonCameraController::RFirstPersonCameraController(QNode * parent) diff --git a/cpp/modmesh/view/RCameraController.hpp b/cpp/modmesh/view/RCameraController.hpp index d59abb73..b53790e0 100644 --- a/cpp/modmesh/view/RCameraController.hpp +++ b/cpp/modmesh/view/RCameraController.hpp @@ -159,6 +159,12 @@ class CameraController void reset(); + void setDefaultPosition(QVector3D value) { m_default_position = value; } + void setDefaultViewCenter(QVector3D value) { m_default_view_center = value; } + void setDefaultUpVector(QVector3D value) { m_default_up_vector = value; } + void setDefaultLinearSpeed(float value) { m_default_linear_speed = value; } + void setDefaultLookSpeed(float value) { m_default_look_speed = value; } + protected: RCameraInputListener * m_listener = nullptr; @@ -167,6 +173,12 @@ class CameraController { return dynamic_cast(this); } + + QVector3D m_default_position = QVector3D(0.0f, 0.0f, 10.0f); + QVector3D m_default_view_center = QVector3D(0.0f, 0.0f, 0.0f); + QVector3D m_default_up_vector = QVector3D(0.0f, 1.0f, 0.0f); + float m_default_linear_speed = 50.0f; + float m_default_look_speed = 180.0f; }; /* end class CameraController */ class RFirstPersonCameraController : public Qt3DExtras::QFirstPersonCameraController diff --git a/cpp/modmesh/view/wrap_view.cpp b/cpp/modmesh/view/wrap_view.cpp index 52b60368..fec88c0d 100644 --- a/cpp/modmesh/view/wrap_view.cpp +++ b/cpp/modmesh/view/wrap_view.cpp @@ -563,7 +563,48 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapRCameraController }, py::arg("x"), py::arg("y"), - py::arg("z")); + py::arg("z")) + .def( + "setDefaultPosition", + [](wrapped_type & self, float x, float y, float z) + { + self.setDefaultPosition(QVector3D(x, y, z)); + }, + py::arg("x"), + py::arg("y"), + py::arg("z")) + .def( + "setDefaultViewCenter", + [](wrapped_type & self, float x, float y, float z) + { + self.setDefaultViewCenter(QVector3D(x, y, z)); + }, + py::arg("x"), + py::arg("y"), + py::arg("z")) + .def( + "setDefaultUpVector", + [](wrapped_type & self, float x, float y, float z) + { + self.setDefaultUpVector(QVector3D(x, y, z)); + }, + py::arg("x"), + py::arg("y"), + py::arg("z")) + .def( + "setDefaultLinearSpeed", + [](wrapped_base_type & self, float value) + { + self.setDefaultLinearSpeed(value); + }, + py::arg("value")) + .def( + "setDefaultLookSpeed", + [](wrapped_base_type & self, float value) + { + self.setDefaultLookSpeed(value); + }, + py::arg("value")); } }; diff --git a/tests/test_view.py b/tests/test_view.py index 27d66831..b4cfad27 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -95,6 +95,12 @@ def setUpClass(cls): cls.up_vector = cls.controller.upVector cls.set_up_vector = cls.controller.setUpVector + cls.set_default_pos = cls.controller.setDefaultPosition + cls.set_default_view_center = cls.controller.setDefaultViewCenter + cls.set_default_up_vector = cls.controller.setDefaultUpVector + cls.set_default_linear_speed = cls.controller.setDefaultLinearSpeed + cls.set_default_look_speed = cls.controller.setDefaultLookSpeed + @classmethod def tearDownClass(cls): cls.widget.close_and_destroy() @@ -147,6 +153,21 @@ def test_vector_get_set(self): self.set_up_vector(x=7, y=8, z=9) self.assertEqual(self.up_vector(), (7, 8, 9)) + def test_default_values(self): + self.set_default_pos(x=1, y=2, z=3) + self.set_default_view_center(x=4, y=5, z=6) + self.set_default_up_vector(x=7, y=8, z=9) + self.set_default_linear_speed(123.0) + self.set_default_look_speed(456.0) + + self.resetCamera() + + self.assertEqual(self.pos(), (1, 2, 3)) + self.assertEqual(self.view_center(), (4, 5, 6)) + self.assertEqual(self.up_vector(), (7, 8, 9)) + self.assertEqual(self.linear_speed(), 123.0) + self.assertEqual(self.look_speed(), 456.0) + @unittest.skipIf(GITHUB_ACTIONS, "GUI is not available in GitHub Actions") class ViewFPSCameraTC(ViewCameraTB, unittest.TestCase): From f527710e8a05a72a4d8351bbac9e25df77fbd967 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Fri, 23 Aug 2024 23:52:02 +0600 Subject: [PATCH 22/28] refactor cameraController getters --- cpp/modmesh/view/R3DWidget.hpp | 26 ++++++++++++-------------- cpp/modmesh/view/RCameraController.hpp | 8 ++++---- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/cpp/modmesh/view/R3DWidget.hpp b/cpp/modmesh/view/R3DWidget.hpp index b1b032e1..5f2b769c 100644 --- a/cpp/modmesh/view/R3DWidget.hpp +++ b/cpp/modmesh/view/R3DWidget.hpp @@ -46,24 +46,21 @@ namespace modmesh { -class RScene - : public Qt3DCore::QEntity +class RScene : public Qt3DCore::QEntity { - public: - explicit RScene(Qt3DCore::QNode * parent = nullptr) - : Qt3DCore::QEntity(parent) - , m_controller(new ROrbitCameraController(this)) + explicit RScene(QNode * parent = nullptr) + : QEntity(parent) { + m_controller = new ROrbitCameraController(this); } - Qt3DExtras::QAbstractCameraController const * controller() const { return m_controller; } - Qt3DExtras::QAbstractCameraController * controller() { return m_controller; } + CameraController * controller() const { return m_controller; } + Qt3DExtras::QAbstractCameraController * qtController() const { return m_controller->asQtCameraController(); } - void setCameraController(Qt3DExtras::QAbstractCameraController * controller) - { - m_controller->deleteLater(); + void setCameraController(CameraController * controller) { + qtController()->deleteLater(); m_controller = controller; } @@ -73,7 +70,7 @@ class RScene private: - Qt3DExtras::QAbstractCameraController * m_controller = nullptr; + CameraController* m_controller; }; /* end class RScene */ @@ -97,8 +94,9 @@ class R3DWidget Qt3DExtras::Qt3DWindow * view() { return m_view; } RScene * scene() { return m_scene; } Qt3DRender::QCamera * camera() { return m_view->camera(); } - Qt3DExtras::QAbstractCameraController * qtCameraController() { return m_scene->controller(); } - CameraController * cameraController() { return dynamic_cast(m_scene->controller()); } + + CameraController * cameraController() const { return m_scene->controller(); } + Qt3DExtras::QAbstractCameraController * qtCameraController() const { return m_scene->qtController(); } QPixmap grabPixmap() const { return m_view->screen()->grabWindow(m_view->winId()); } diff --git a/cpp/modmesh/view/RCameraController.hpp b/cpp/modmesh/view/RCameraController.hpp index b53790e0..67118692 100644 --- a/cpp/modmesh/view/RCameraController.hpp +++ b/cpp/modmesh/view/RCameraController.hpp @@ -165,15 +165,15 @@ class CameraController void setDefaultLinearSpeed(float value) { m_default_linear_speed = value; } void setDefaultLookSpeed(float value) { m_default_look_speed = value; } -protected: - RCameraInputListener * m_listener = nullptr; - -private: Qt3DExtras::QAbstractCameraController * asQtCameraController() { return dynamic_cast(this); } +protected: + RCameraInputListener * m_listener = nullptr; + +private: QVector3D m_default_position = QVector3D(0.0f, 0.0f, 10.0f); QVector3D m_default_view_center = QVector3D(0.0f, 0.0f, 0.0f); QVector3D m_default_up_vector = QVector3D(0.0f, 1.0f, 0.0f); From 24f2312c99b50d781c383279db81353854f66bb8 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Fri, 23 Aug 2024 23:53:03 +0600 Subject: [PATCH 23/28] do not change aspect ration of camera when reest --- cpp/modmesh/view/RCameraController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/modmesh/view/RCameraController.cpp b/cpp/modmesh/view/RCameraController.cpp index 3b7bb0ce..cd8e6e98 100644 --- a/cpp/modmesh/view/RCameraController.cpp +++ b/cpp/modmesh/view/RCameraController.cpp @@ -214,7 +214,7 @@ void RCameraInputListener::initKeyboardListeners() const void CameraController::reset() { - camera()->lens()->setPerspectiveProjection(45.0f, 16.0f / 9.0f, 0.1f, 1000.0f); + camera()->lens()->setPerspectiveProjection(45.0f, camera()->lens()->aspectRatio(), 0.1f, 1000.0f); setPosition(m_default_position); setViewCenter(m_default_view_center); From 09842d0eeb50181eb02573cf6d4a62eac74a7374 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Sat, 24 Aug 2024 01:50:30 +0600 Subject: [PATCH 24/28] use properties in camera python api --- cpp/modmesh/view/R3DWidget.hpp | 5 +- cpp/modmesh/view/RCameraController.hpp | 9 + cpp/modmesh/view/wrap_view.cpp | 204 +++++------------ tests/test_view.py | 289 +++++++++++-------------- 4 files changed, 202 insertions(+), 305 deletions(-) diff --git a/cpp/modmesh/view/R3DWidget.hpp b/cpp/modmesh/view/R3DWidget.hpp index 5f2b769c..cb595076 100644 --- a/cpp/modmesh/view/R3DWidget.hpp +++ b/cpp/modmesh/view/R3DWidget.hpp @@ -59,7 +59,8 @@ class RScene : public Qt3DCore::QEntity CameraController * controller() const { return m_controller; } Qt3DExtras::QAbstractCameraController * qtController() const { return m_controller->asQtCameraController(); } - void setCameraController(CameraController * controller) { + void setCameraController(CameraController * controller) + { qtController()->deleteLater(); m_controller = controller; } @@ -70,7 +71,7 @@ class RScene : public Qt3DCore::QEntity private: - CameraController* m_controller; + CameraController * m_controller; }; /* end class RScene */ diff --git a/cpp/modmesh/view/RCameraController.hpp b/cpp/modmesh/view/RCameraController.hpp index 67118692..5a199329 100644 --- a/cpp/modmesh/view/RCameraController.hpp +++ b/cpp/modmesh/view/RCameraController.hpp @@ -159,10 +159,19 @@ class CameraController void reset(); + QVector3D defaultPosition() const { return m_default_position; } void setDefaultPosition(QVector3D value) { m_default_position = value; } + + QVector3D defaultViewCenter() const { return m_default_view_center; } void setDefaultViewCenter(QVector3D value) { m_default_view_center = value; } + + QVector3D defaultUpVector() const { return m_default_up_vector; } void setDefaultUpVector(QVector3D value) { m_default_up_vector = value; } + + float defaultLinearSpeed() const { return m_default_linear_speed; } void setDefaultLinearSpeed(float value) { m_default_linear_speed = value; } + + float defaultLookSpeed() const { return m_default_look_speed; } void setDefaultLookSpeed(float value) { m_default_look_speed = value; } Qt3DExtras::QAbstractCameraController * asQtCameraController() diff --git a/cpp/modmesh/view/wrap_view.cpp b/cpp/modmesh/view/wrap_view.cpp index fec88c0d..7ca475a2 100644 --- a/cpp/modmesh/view/wrap_view.cpp +++ b/cpp/modmesh/view/wrap_view.cpp @@ -196,33 +196,7 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapR3DWidget } }, py::arg("name")) - .def("cameraController", &wrapped_type::cameraController); - -#define DECL_QVECTOR3D_PROPERTY(NAME, GETTER, SETTER) \ - .def_property( \ - #NAME, \ - [](wrapped_type & self) \ - { \ - QVector3D const v = self.camera()->GETTER(); \ - return py::make_tuple(v.x(), v.y(), v.z()); \ - }, \ - [](wrapped_type & self, std::vector const & v) \ - { \ - double const x = v.at(0); \ - double const y = v.at(1); \ - double const z = v.at(2); \ - self.camera()->SETTER(QVector3D(x, y, z)); \ - }) - - (*this) - // clang-format off - DECL_QVECTOR3D_PROPERTY(position, position, setPosition) - DECL_QVECTOR3D_PROPERTY(up_vector, upVector, setUpVector) - DECL_QVECTOR3D_PROPERTY(view_center, viewCenter, setViewCenter) - // clang-format on - ; - -#undef DECL_QVECTOR3D_PROPERTY + .def_property_readonly("camera", &wrapped_type::cameraController); } }; /* end class WrapR3DWidget */ @@ -447,7 +421,7 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapRCameraController [](wrapped_type & self) { self.reset(); }) .def( - "updateCameraPosition", + "move", []( wrapped_type & self, float x, @@ -458,8 +432,7 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapRCameraController bool left_mouse_button, bool right_mouse_button, bool alt_key, - bool shift_key, - float dt) + bool shift_key) { CameraInputState input{}; input.txAxisValue = x; @@ -474,6 +447,7 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapRCameraController input.altKeyActive = alt_key; input.shiftKeyActive = shift_key; + constexpr float dt = 1.0f; self.updateCameraPosition(input, dt); }, @@ -485,126 +459,64 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapRCameraController py::arg("left_mouse_button") = false, py::arg("right_mouse_button") = false, py::arg("alt_key") = false, - py::arg("shift_key") = false, - py::arg("dt")) - .def( - "position", - [](wrapped_type & self) - { - const auto position = self.position(); - return py::make_tuple(position.x(), position.y(), position.z()); - }) - .def( - "setPosition", - [](wrapped_type & self, float x, float y, float z) - { - self.setPosition(QVector3D(x, y, z)); - }, - py::arg("x"), - py::arg("y"), - py::arg("z")) - .def( - "linearSpeed", - [](wrapped_base_type & self) - { return self.linearSpeed(); }) - .def( - "setLinearSpeed", - [](wrapped_base_type & self, float value) - { - self.setLinearSpeed(value); - }, - py::arg("value")) - .def( - "lookSpeed", - [](wrapped_base_type & self) - { return self.lookSpeed(); }) - .def( - "setLookSpeed", - [](wrapped_base_type & self, float value) - { - self.setLookSpeed(value); - }, - py::arg("value")) - .def( - "viewCenter", - [](wrapped_base_type & self) - { - const auto center = self.viewCenter(); - return py::make_tuple(center.x(), center.y(), center.z()); - }) - .def( - "setViewCenter", - [](wrapped_type & self, float x, float y, float z) - { - self.setViewCenter(QVector3D(x, y, z)); - }, - py::arg("x"), - py::arg("y"), - py::arg("z")) - .def( - "viewVector", + py::arg("shift_key") = false) + .def_property_readonly( + "view_vector", [](wrapped_base_type & self) { const auto vector = self.viewVector(); return py::make_tuple(vector.x(), vector.y(), vector.z()); - }) - .def( - "upVector", - [](wrapped_base_type & self) - { - const auto vector = self.upVector(); - return py::make_tuple(vector.x(), vector.y(), vector.z()); - }) - .def( - "setUpVector", - [](wrapped_type & self, float x, float y, float z) - { - self.setUpVector(QVector3D(x, y, z)); - }, - py::arg("x"), - py::arg("y"), - py::arg("z")) - .def( - "setDefaultPosition", - [](wrapped_type & self, float x, float y, float z) - { - self.setDefaultPosition(QVector3D(x, y, z)); - }, - py::arg("x"), - py::arg("y"), - py::arg("z")) - .def( - "setDefaultViewCenter", - [](wrapped_type & self, float x, float y, float z) - { - self.setDefaultViewCenter(QVector3D(x, y, z)); - }, - py::arg("x"), - py::arg("y"), - py::arg("z")) - .def( - "setDefaultUpVector", - [](wrapped_type & self, float x, float y, float z) - { - self.setDefaultUpVector(QVector3D(x, y, z)); - }, - py::arg("x"), - py::arg("y"), - py::arg("z")) - .def( - "setDefaultLinearSpeed", - [](wrapped_base_type & self, float value) - { - self.setDefaultLinearSpeed(value); - }, - py::arg("value")) - .def( - "setDefaultLookSpeed", - [](wrapped_base_type & self, float value) - { - self.setDefaultLookSpeed(value); - }, - py::arg("value")); + }); + +#define DECL_QVECTOR3D_PROPERTY(NAME, GETTER, SETTER) \ + .def_property( \ + #NAME, \ + [](wrapped_type & self) \ + { \ + QVector3D const v = self.GETTER(); \ + return py::make_tuple(v.x(), v.y(), v.z()); \ + }, \ + [](wrapped_type & self, std::vector const & v) \ + { \ + double const x = v.at(0); \ + double const y = v.at(1); \ + double const z = v.at(2); \ + self.SETTER(QVector3D(x, y, z)); \ + }) + + (*this) + // clang-format off + DECL_QVECTOR3D_PROPERTY(position, position, setPosition) + DECL_QVECTOR3D_PROPERTY(up_vector, upVector, setUpVector) + DECL_QVECTOR3D_PROPERTY(view_center, viewCenter, setViewCenter) + DECL_QVECTOR3D_PROPERTY(default_position, defaultPosition, setDefaultPosition) + DECL_QVECTOR3D_PROPERTY(default_view_center, defaultViewCenter, setDefaultViewCenter) + DECL_QVECTOR3D_PROPERTY(default_up_vector, defaultUpVector, setDefaultUpVector) + // clang-format on + ; +#undef DECL_QVECTOR3D_PROPERTY + +#define DECL_FLOAT_PROPERTY(NAME, GETTER, SETTER) \ + .def_property( \ + #NAME, \ + [](wrapped_type & self) \ + { \ + return self.GETTER(); \ + }, \ + [](wrapped_type & self, float v) \ + { \ + self.SETTER(v); \ + }) + + (*this) + // clang-format off + DECL_FLOAT_PROPERTY(linear_speed, linearSpeed, setLinearSpeed) + DECL_FLOAT_PROPERTY(look_speed, lookSpeed, setLookSpeed) + DECL_FLOAT_PROPERTY(default_linear_speed, defaultLinearSpeed, setDefaultLinearSpeed) + DECL_FLOAT_PROPERTY(default_look_speed, defaultLookSpeed, setDefaultLookSpeed) + // clang-format on + ; +#undef DECL_FLOAT_PROPERTY } }; diff --git a/tests/test_view.py b/tests/test_view.py index b4cfad27..e342faaa 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -73,33 +73,7 @@ def setUpClass(cls): widget.setCameraType(cls.camera_type) cls.widget = widget - cls.controller = widget.cameraController() - - cls.move = cls.controller.updateCameraPosition - cls.resetCamera = cls.controller.reset - - cls.pos = cls.controller.position - cls.set_pos = cls.controller.setPosition - - cls.look_speed = cls.controller.lookSpeed - cls.set_look_speed = cls.controller.setLookSpeed - - cls.linear_speed = cls.controller.linearSpeed - cls.set_linear_speed = cls.controller.setLinearSpeed - - cls.view_vector = cls.controller.viewVector - - cls.view_center = cls.controller.viewCenter - cls.set_view_center = cls.controller.setViewCenter - - cls.up_vector = cls.controller.upVector - cls.set_up_vector = cls.controller.setUpVector - - cls.set_default_pos = cls.controller.setDefaultPosition - cls.set_default_view_center = cls.controller.setDefaultViewCenter - cls.set_default_up_vector = cls.controller.setDefaultUpVector - cls.set_default_linear_speed = cls.controller.setDefaultLinearSpeed - cls.set_default_look_speed = cls.controller.setDefaultLookSpeed + cls.camera = widget.camera @classmethod def tearDownClass(cls): @@ -134,39 +108,44 @@ class ViewCommonCameraTC(ViewCameraTB, unittest.TestCase): camera_type = "fps" # no difference when use orbit camera def setUp(self): - self.resetCamera() + self.camera.reset() def test_value_get_set(self): - self.set_linear_speed(123.0) - self.assertEqual(self.linear_speed(), 123.0) + c = self.camera - self.set_look_speed(456.0) - self.assertEqual(self.look_speed(), 456.0) + c.linear_speed = 123.0 + self.assertEqual(c.linear_speed, 123.0) + + c.look_speed = 456.0 + self.assertEqual(c.look_speed, 456.0) def test_vector_get_set(self): - self.set_pos(x=1, y=2, z=3) - self.assertEqual(self.pos(), (1, 2, 3)) + c = self.camera - self.set_view_center(x=4, y=5, z=6) - self.assertEqual(self.view_center(), (4, 5, 6)) + c.position = (1, 2, 3) + c.view_center = (4, 5, 6) + c.up_vector = (7, 8, 9) - self.set_up_vector(x=7, y=8, z=9) - self.assertEqual(self.up_vector(), (7, 8, 9)) + self.assertEqual(c.position, (1, 2, 3)) + self.assertEqual(c.view_center, (4, 5, 6)) + self.assertEqual(c.up_vector, (7, 8, 9)) def test_default_values(self): - self.set_default_pos(x=1, y=2, z=3) - self.set_default_view_center(x=4, y=5, z=6) - self.set_default_up_vector(x=7, y=8, z=9) - self.set_default_linear_speed(123.0) - self.set_default_look_speed(456.0) + c = self.camera + + c.default_position = (1, 2, 3) + c.default_view_center = (4, 5, 6) + c.default_up_vector = (7, 8, 9) + c.default_linear_speed = 123.0 + c.default_look_speed = 456.0 - self.resetCamera() + c.reset() - self.assertEqual(self.pos(), (1, 2, 3)) - self.assertEqual(self.view_center(), (4, 5, 6)) - self.assertEqual(self.up_vector(), (7, 8, 9)) - self.assertEqual(self.linear_speed(), 123.0) - self.assertEqual(self.look_speed(), 456.0) + self.assertEqual(c.position, (1, 2, 3)) + self.assertEqual(c.view_center, (4, 5, 6)) + self.assertEqual(c.up_vector, (7, 8, 9)) + self.assertEqual(c.linear_speed, 123.0) + self.assertEqual(c.look_speed, 456.0) @unittest.skipIf(GITHUB_ACTIONS, "GUI is not available in GitHub Actions") @@ -174,95 +153,92 @@ class ViewFPSCameraTC(ViewCameraTB, unittest.TestCase): camera_type = "fps" def setUp(self): - self.resetCamera() + self.camera.reset() def test_reset(self): - dt = 0.01 - initial_pos = self.pos() - initial_view_vector = self.view_vector() - initial_view_center = self.view_center() - initial_up_vector = self.up_vector() + c = self.camera - self.move(x=1, y=1, z=1, dt=dt) - self.move(yaw=1, pitch=1, dt=dt, left_mouse_button=True) + initial_position = c.position + initial_view_center = c.view_center + initial_up_vector = c.up_vector - self.assertNotEqual(self.pos(), initial_pos) - self.assertNotEqual(self.view_vector(), initial_view_vector) - self.assertNotEqual(self.view_center(), initial_view_center) - self.assertNotEqual(self.up_vector(), initial_up_vector) + c.move(x=0.1, y=0.1, z=0.1) + c.move(yaw=1, pitch=1, left_mouse_button=True) - self.resetCamera() + self.assertNotEqual(c.position, initial_position) + self.assertNotEqual(c.view_center, initial_view_center) + self.assertNotEqual(c.up_vector, initial_up_vector) - self.assertEqual(self.pos(), initial_pos) - self.assertEqual(self.view_vector(), initial_view_vector) - self.assertEqual(self.view_center(), initial_view_center) - self.assertEqual(self.up_vector(), initial_up_vector) + c.reset() + + self.assertEqual(c.position, initial_position) + self.assertEqual(c.view_center, initial_view_center) + self.assertEqual(c.up_vector, initial_up_vector) def test_translation(self): - dt = 0.01 - delta = dt * self.linear_speed() - delta_vec = np.array([delta, delta, -delta]) - new_pos = np.array(self.pos()) + delta_vec - new_view_center = np.array(self.view_center()) + delta_vec + c = self.camera + + speed = c.linear_speed + delta_vec = np.array([speed, speed, -speed]) + new_position = np.array(c.position) + delta_vec + new_view_center = np.array(c.view_center) + delta_vec - self.move(x=1, dt=dt) - self.assertEqual(self.pos()[0], new_pos[0]) + c.move(x=1) + self.assertEqual(c.position[0], new_position[0]) - self.move(y=1, dt=dt) - self.assertEqual(self.pos()[1], new_pos[1]) + c.move(y=1) + self.assertEqual(c.position[1], new_position[1]) # camera moves in negative z direction - self.move(z=1, dt=dt) - self.assertEqual(self.pos()[2], new_pos[2]) + c.move(z=1) + self.assertEqual(c.position[2], new_position[2]) # camera view center should move with camera - view_center = self.view_center() - self.assertEqual(view_center[0], new_view_center[0]) - self.assertEqual(view_center[1], new_view_center[1]) - self.assertEqual(view_center[2], new_view_center[2]) + self.assertEqual(c.view_center[0], new_view_center[0]) + self.assertEqual(c.view_center[1], new_view_center[1]) + self.assertEqual(c.view_center[2], new_view_center[2]) def test_rotation(self): - dt = 0.01 - angle = dt * self.look_speed() + c = self.camera - initial_view_center = self.view_center() - initial_view_vector = self.view_vector() + angle = c.look_speed + + initial_view_center = c.view_center + initial_view_vector = c.view_vector # test camera does not rotate when left mouse button is not pressed - self.move(yaw=1, pitch=1, dt=dt) - self.assertEqual(self.view_vector(), initial_view_vector) - self.assertEqual(self.view_center(), initial_view_center) + c.move(yaw=1, pitch=1) + self.assertEqual(c.view_vector, initial_view_vector) + self.assertEqual(c.view_center, initial_view_center) # test camera rotates around y-axis - self.move(yaw=1, dt=dt, left_mouse_button=True) + c.move(yaw=1, left_mouse_button=True) rotation_matrix = self.angle_axis(angle, (0, 1, 0)) rotated_vector = np.array(initial_view_vector) @ rotation_matrix - view_vector = self.view_vector() - self.assertAlmostEqual(rotated_vector[0], view_vector[0], delta=1e-5) - self.assertAlmostEqual(rotated_vector[1], view_vector[1], delta=1e-5) - self.assertAlmostEqual(rotated_vector[2], view_vector[2], delta=1e-5) + self.assertAlmostEqual(rotated_vector[0], c.view_vector[0], places=5) + self.assertAlmostEqual(rotated_vector[1], c.view_vector[1], places=5) + self.assertAlmostEqual(rotated_vector[2], c.view_vector[2], places=5) # test camera rotates around x-axis - self.move(pitch=1, dt=dt, left_mouse_button=True) + old_view_vector = c.view_vector + c.move(pitch=1, left_mouse_button=True) - x_basis = -self.normalize(np.cross(view_vector, self.up_vector())) + x_basis = -self.normalize(np.cross(old_view_vector, c.up_vector)) rotation_matrix = self.angle_axis(angle, x_basis) - rotated_vector = np.array(view_vector) @ rotation_matrix - view_vector = self.view_vector() + rotated_vector = np.array(old_view_vector) @ rotation_matrix - self.assertAlmostEqual(rotated_vector[0], view_vector[0], delta=1e-5) - self.assertAlmostEqual(rotated_vector[1], view_vector[1], delta=1e-5) - self.assertAlmostEqual(rotated_vector[2], view_vector[2], delta=1e-5) + self.assertAlmostEqual(rotated_vector[0], c.view_vector[0], places=5) + self.assertAlmostEqual(rotated_vector[1], c.view_vector[1], places=5) + self.assertAlmostEqual(rotated_vector[2], c.view_vector[2], places=5) # test view center moved with the camera - new_view_center = view_vector + np.array(self.pos()) - view_center = self.view_center() + new_view_center = c.view_vector + np.array(c.position) - self.assertAlmostEqual(view_center[0], new_view_center[0], delta=1e-5) - self.assertAlmostEqual(view_center[1], new_view_center[1], delta=1e-5) - self.assertAlmostEqual(view_center[2], new_view_center[2], delta=1e-5) + self.assertAlmostEqual(c.view_center[0], new_view_center[0], places=5) + self.assertAlmostEqual(c.view_center[1], new_view_center[1], places=5) + self.assertAlmostEqual(c.view_center[2], new_view_center[2], places=5) @unittest.skipIf(GITHUB_ACTIONS, "GUI is not available in GitHub Actions") @@ -270,89 +246,88 @@ class ViewOrbitCameraTC(ViewCameraTB, unittest.TestCase): camera_type = "orbit" def setUp(self): - self.resetCamera() + self.camera.reset() def test_reset(self): - dt = 0.01 - initial_pos = self.pos() - initial_view_vector = self.view_vector() - initial_view_center = self.view_center() - initial_up_vector = self.up_vector() + c = self.camera + + initial_position = c.position + initial_view_vector = c.view_vector + initial_view_center = c.view_center + initial_up_vector = c.up_vector - self.move(x=1, y=1, z=1, dt=dt) - self.move(yaw=1, pitch=1, dt=dt, right_mouse_button=True) + c.move(x=0.1, y=0.1, z=0.1) + c.move(yaw=1, pitch=1, right_mouse_button=True) - self.assertNotEqual(self.pos(), initial_pos) - self.assertNotEqual(self.view_vector(), initial_view_vector) - self.assertNotEqual(self.view_center(), initial_view_center) - self.assertNotEqual(self.up_vector(), initial_up_vector) + self.assertNotEqual(c.position, initial_position) + self.assertNotEqual(c.view_vector, initial_view_vector) + self.assertNotEqual(c.up_vector, initial_up_vector) - self.resetCamera() + c.reset() - self.assertEqual(self.pos(), initial_pos) - self.assertEqual(self.view_vector(), initial_view_vector) - self.assertEqual(self.view_center(), initial_view_center) - self.assertEqual(self.up_vector(), initial_up_vector) + self.assertEqual(c.position, initial_position) + self.assertEqual(c.view_vector, initial_view_vector) + self.assertEqual(c.view_center, initial_view_center) + self.assertEqual(c.up_vector, initial_up_vector) def test_translation(self): - dt = 0.01 - delta = dt * self.linear_speed() - delta_vec = np.array([delta, delta, -delta]) - new_pos = np.array(self.pos()) + delta_vec - new_view_center = np.array(self.view_center()) + delta_vec + c = self.camera + + speed = c.linear_speed + delta_vec = np.array([speed, speed, -speed]) + new_pos = np.array(c.position) + delta_vec + new_view_center = np.array(c.view_center) + delta_vec - self.move(x=1, dt=dt) - self.assertEqual(self.pos()[0], new_pos[0]) + c.move(x=1) + self.assertEqual(c.position[0], new_pos[0]) - self.move(y=1, dt=dt) - self.assertEqual(self.pos()[1], new_pos[1]) + c.move(y=1) + self.assertEqual(c.position[1], new_pos[1]) # camera moves in negative z direction - self.move(z=1, dt=dt) - self.assertEqual(self.pos()[2], new_pos[2]) + c.move(z=1) + self.assertEqual(c.position[2], new_pos[2]) # camera view center should move with camera - view_center = self.view_center() - self.assertEqual(view_center[0], new_view_center[0]) - self.assertEqual(view_center[1], new_view_center[1]) - self.assertEqual(view_center[2], new_view_center[2]) + self.assertEqual(c.view_center[0], new_view_center[0]) + self.assertEqual(c.view_center[1], new_view_center[1]) + self.assertEqual(c.view_center[2], new_view_center[2]) def test_rotation(self): - dt = 0.01 - angle = dt * self.look_speed() + c = self.camera - initial_view_center = self.view_center() - initial_view_vector = self.view_vector() + angle = c.look_speed + initial_view_center = c.view_center + initial_view_vector = c.view_vector # test camera does not rotate when right mouse button is not pressed - self.move(yaw=1, pitch=1, dt=dt) - self.assertEqual(self.view_vector(), initial_view_vector) - self.assertEqual(self.view_center(), initial_view_center) + c.move(yaw=1, pitch=1) + self.assertEqual(c.view_vector, initial_view_vector) + self.assertEqual(c.view_center, initial_view_center) # test camera rotates around y-axis - self.move(yaw=1, dt=dt, right_mouse_button=True) + c.move(yaw=1, right_mouse_button=True) rotation_matrix = self.angle_axis(angle, (0, -1, 0)) rotated_vector = np.array(initial_view_vector) @ rotation_matrix - view_vector = self.view_vector() - self.assertAlmostEqual(rotated_vector[0], view_vector[0], delta=1e-5) - self.assertAlmostEqual(rotated_vector[1], view_vector[1], delta=1e-5) - self.assertAlmostEqual(rotated_vector[2], view_vector[2], delta=1e-5) + self.assertAlmostEqual(rotated_vector[0], c.view_vector[0], places=5) + self.assertAlmostEqual(rotated_vector[1], c.view_vector[1], places=5) + self.assertAlmostEqual(rotated_vector[2], c.view_vector[2], places=5) # test camera rotates around x-axis - self.move(pitch=1, dt=dt, right_mouse_button=True) + old_view_vector = c.view_vector + c.move(pitch=1, right_mouse_button=True) - x_basis = self.normalize(np.cross(view_vector, self.up_vector())) + x_basis = self.normalize(np.cross(old_view_vector, c.up_vector)) rotation_matrix = self.angle_axis(angle, x_basis) - rotated_vector = np.array(view_vector) @ rotation_matrix - view_vector = self.view_vector() + rotated_vector = np.array(old_view_vector) @ rotation_matrix - self.assertAlmostEqual(rotated_vector[0], view_vector[0], delta=1e-5) - self.assertAlmostEqual(rotated_vector[1], view_vector[1], delta=1e-5) - self.assertAlmostEqual(rotated_vector[2], view_vector[2], delta=1e-5) + self.assertAlmostEqual(rotated_vector[0], c.view_vector[0], places=5) + self.assertAlmostEqual(rotated_vector[1], c.view_vector[1], places=5) + self.assertAlmostEqual(rotated_vector[2], c.view_vector[2], places=5) # camera view center should not change - self.assertEqual(self.view_center(), initial_view_center) + self.assertEqual(c.view_center, initial_view_center) # vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: From 823342ac9d502a3d50bf6f30a60ccdf5a87f874e Mon Sep 17 00:00:00 2001 From: j8xixo12 Date: Sat, 24 Aug 2024 23:50:48 +0800 Subject: [PATCH 25/28] make class compatible with python 2/3 --- modmesh/app/euler1d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modmesh/app/euler1d.py b/modmesh/app/euler1d.py index aad01bc9..efef8fe4 100644 --- a/modmesh/app/euler1d.py +++ b/modmesh/app/euler1d.py @@ -104,7 +104,7 @@ def update(self, adata, ndata): self.num.figure.canvas.draw() -class _Accessor: +class _Accessor(object): """ Helper calss to access data within the configuration table using multiple dimensions, currenlty only support 2-dimensions table. From 3adeb702740c4e9e1d3fb05373893564d38225bb Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Sun, 25 Aug 2024 02:07:54 +0600 Subject: [PATCH 26/28] apply review --- cpp/modmesh/view/R3DWidget.hpp | 7 ++- cpp/modmesh/view/RCameraController.cpp | 21 +++++--- cpp/modmesh/view/RCameraController.hpp | 67 +++++++++----------------- cpp/modmesh/view/RManager.cpp | 2 +- cpp/modmesh/view/wrap_view.cpp | 50 +++++++++---------- tests/test_view.py | 61 ++++++++++++----------- 6 files changed, 97 insertions(+), 111 deletions(-) diff --git a/cpp/modmesh/view/R3DWidget.hpp b/cpp/modmesh/view/R3DWidget.hpp index cb595076..91f4c407 100644 --- a/cpp/modmesh/view/R3DWidget.hpp +++ b/cpp/modmesh/view/R3DWidget.hpp @@ -46,7 +46,8 @@ namespace modmesh { -class RScene : public Qt3DCore::QEntity +class RScene + : public Qt3DCore::QEntity { public: @@ -57,11 +58,10 @@ class RScene : public Qt3DCore::QEntity } CameraController * controller() const { return m_controller; } - Qt3DExtras::QAbstractCameraController * qtController() const { return m_controller->asQtCameraController(); } void setCameraController(CameraController * controller) { - qtController()->deleteLater(); + m_controller->deleteLater(); m_controller = controller; } @@ -97,7 +97,6 @@ class R3DWidget Qt3DRender::QCamera * camera() { return m_view->camera(); } CameraController * cameraController() const { return m_scene->controller(); } - Qt3DExtras::QAbstractCameraController * qtCameraController() const { return m_scene->qtController(); } QPixmap grabPixmap() const { return m_view->screen()->grabWindow(m_view->winId()); } diff --git a/cpp/modmesh/view/RCameraController.cpp b/cpp/modmesh/view/RCameraController.cpp index cd8e6e98..8bc41de5 100644 --- a/cpp/modmesh/view/RCameraController.cpp +++ b/cpp/modmesh/view/RCameraController.cpp @@ -214,7 +214,10 @@ void RCameraInputListener::initKeyboardListeners() const void CameraController::reset() { - camera()->lens()->setPerspectiveProjection(45.0f, camera()->lens()->aspectRatio(), 0.1f, 1000.0f); + constexpr float fov_deg = 45.0f; + constexpr float nearPlane = 0.1f; + constexpr float farPlane = 1000.0f; + camera()->lens()->setPerspectiveProjection(fov_deg, camera()->lens()->aspectRatio(), nearPlane, farPlane); setPosition(m_default_position); setViewCenter(m_default_view_center); @@ -225,17 +228,17 @@ void CameraController::reset() } RFirstPersonCameraController::RFirstPersonCameraController(QNode * parent) - : QFirstPersonCameraController(parent) + : CameraController(parent) { auto callback = [this](const CameraInputState & state, const float dt) { - updateCameraPosition(state, dt); + moveCamera(state, dt); }; m_listener = new RCameraInputListener(keyboardDevice(), mouseDevice(), callback, this); } -void RFirstPersonCameraController::updateCameraPosition(const CameraInputState & input, const float dt) +void RFirstPersonCameraController::moveCamera(const CameraInputState & input, const float dt) { constexpr auto positiveY = QVector3D(0.f, 1.f, 0.f); @@ -256,17 +259,17 @@ void RFirstPersonCameraController::updateCameraPosition(const CameraInputState & } ROrbitCameraController::ROrbitCameraController(QNode * parent) - : QOrbitCameraController(parent) + : CameraController(parent) { auto callback = [this](const CameraInputState & state, const float dt) { - updateCameraPosition(state, dt); + moveCamera(state, dt); }; m_listener = new RCameraInputListener(keyboardDevice(), mouseDevice(), callback, this); } -void ROrbitCameraController::updateCameraPosition(const CameraInputState & input, const float dt) +void ROrbitCameraController::moveCamera(const CameraInputState & input, const float dt) { if (camera() == nullptr) return; @@ -317,7 +320,9 @@ void ROrbitCameraController::updateCameraPosition(const CameraInputState & input void ROrbitCameraController::zoom(const float zoomValue) const { - const float limitSquared = zoomInLimit() * zoomInLimit(); + constexpr float zoomInLimit = 2.0f; // Default Qt zoomInLimit value + + const float limitSquared = zoomInLimit * zoomInLimit; const float distanceSquared = zoomDistanceSquared(camera()->position(), camera()->viewCenter()); const float z = distanceSquared > limitSquared ? zoomValue : -0.5f; diff --git a/cpp/modmesh/view/RCameraController.hpp b/cpp/modmesh/view/RCameraController.hpp index 5a199329..f7af79a9 100644 --- a/cpp/modmesh/view/RCameraController.hpp +++ b/cpp/modmesh/view/RCameraController.hpp @@ -29,10 +29,8 @@ */ #include // Must be the first include. - -#include -#include #include +#include #include #include @@ -128,21 +126,29 @@ class RCameraInputListener : public Qt3DCore::QEntity Qt3DInput::QButtonAxisInput * m_keyboard_tz_neg_input; }; /* end class RCameraInputListener */ -class CameraController +class CameraController : public Qt3DExtras::QAbstractCameraController { + Q_OBJECT + public: - virtual ~CameraController() = default; + explicit CameraController(Qt3DCore::QNode * parent = nullptr) + : QAbstractCameraController(parent) + { + } - virtual void updateCameraPosition(const CameraInputState & state, float dt) = 0; + // Do nothing in QAbstractCameraController's moveCamera + void moveCamera(const InputState & state, float dt) override {} + + virtual void moveCamera(const CameraInputState & state, float dt) = 0; - virtual Qt3DRender::QCamera * camera() const = 0; - virtual void setCamera(Qt3DRender::QCamera * camera) = 0; + Qt3DRender::QCamera * camera() const { return QAbstractCameraController::camera(); } + void setCamera(Qt3DRender::QCamera * camera) { QAbstractCameraController::setCamera(camera); } - virtual float linearSpeed() const = 0; - virtual void setLinearSpeed(float value) = 0; + float linearSpeed() const { return QAbstractCameraController::linearSpeed(); } + void setLinearSpeed(float value) { QAbstractCameraController::setLinearSpeed(value); } - virtual float lookSpeed() const = 0; - virtual void setLookSpeed(float value) = 0; + float lookSpeed() const { return QAbstractCameraController::lookSpeed(); } + void setLookSpeed(float value) { QAbstractCameraController::setLookSpeed(value); } virtual CameraControllerType getType() = 0; @@ -174,11 +180,6 @@ class CameraController float defaultLookSpeed() const { return m_default_look_speed; } void setDefaultLookSpeed(float value) { m_default_look_speed = value; } - Qt3DExtras::QAbstractCameraController * asQtCameraController() - { - return dynamic_cast(this); - } - protected: RCameraInputListener * m_listener = nullptr; @@ -190,54 +191,30 @@ class CameraController float m_default_look_speed = 180.0f; }; /* end class CameraController */ -class RFirstPersonCameraController : public Qt3DExtras::QFirstPersonCameraController - , public CameraController +class RFirstPersonCameraController : public CameraController { Q_OBJECT public: explicit RFirstPersonCameraController(QNode * parent = nullptr); - Qt3DRender::QCamera * camera() const override { return QFirstPersonCameraController::camera(); } - void setCamera(Qt3DRender::QCamera * camera) override { QFirstPersonCameraController::setCamera(camera); } - - float linearSpeed() const override { return QFirstPersonCameraController::linearSpeed(); } - void setLinearSpeed(float value) override { QFirstPersonCameraController::setLinearSpeed(value); } - - float lookSpeed() const override { return QFirstPersonCameraController::lookSpeed(); } - void setLookSpeed(float value) override { QFirstPersonCameraController::setLookSpeed(value); } - private: static constexpr auto lookSpeedFactorOnShiftPressed = 0.2f; - void moveCamera(const InputState & state, float dt) override {} - - void updateCameraPosition(const CameraInputState & input, float dt) override; + void moveCamera(const CameraInputState & input, float dt) override; CameraControllerType getType() override { return CameraControllerType::FirstPerson; } }; /* end class RFirstPersonCameraController */ -class ROrbitCameraController : public Qt3DExtras::QOrbitCameraController - , public CameraController +class ROrbitCameraController : public CameraController { Q_OBJECT public: explicit ROrbitCameraController(QNode * parent = nullptr); - Qt3DRender::QCamera * camera() const override { return QOrbitCameraController::camera(); } - void setCamera(Qt3DRender::QCamera * camera) override { QOrbitCameraController::setCamera(camera); } - - float linearSpeed() const override { return QOrbitCameraController::linearSpeed(); } - void setLinearSpeed(float value) override { QOrbitCameraController::setLinearSpeed(value); } - - float lookSpeed() const override { return QOrbitCameraController::lookSpeed(); } - void setLookSpeed(float value) override { QOrbitCameraController::setLookSpeed(value); } - private: - void moveCamera(const InputState & state, float dt) override {} - - void updateCameraPosition(const CameraInputState & input, float dt) override; + void moveCamera(const CameraInputState & input, float dt) override; void zoom(float zoomValue) const; diff --git a/cpp/modmesh/view/RManager.cpp b/cpp/modmesh/view/RManager.cpp index 21177e1d..002e16b0 100644 --- a/cpp/modmesh/view/RManager.cpp +++ b/cpp/modmesh/view/RManager.cpp @@ -379,7 +379,7 @@ std::function RManager::createCameraMovementItemHandler(const std::funct } } - viewer->cameraController()->updateCameraPosition(input, 0.01); + viewer->cameraController()->moveCamera(input, 0.01); }; } diff --git a/cpp/modmesh/view/wrap_view.cpp b/cpp/modmesh/view/wrap_view.cpp index 7ca475a2..bdb76fe3 100644 --- a/cpp/modmesh/view/wrap_view.cpp +++ b/cpp/modmesh/view/wrap_view.cpp @@ -448,7 +448,7 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapRCameraController input.shiftKeyActive = shift_key; constexpr float dt = 1.0f; - self.updateCameraPosition(input, dt); + self.moveCamera(input, dt); }, py::arg("x") = 0.f, @@ -468,7 +468,7 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapRCameraController return py::make_tuple(vector.x(), vector.y(), vector.z()); }); -#define DECL_QVECTOR3D_PROPERTY(NAME, GETTER, SETTER) \ +#define MM_DECL_QVECTOR3D_PROPERTY(NAME, GETTER, SETTER) \ .def_property( \ #NAME, \ [](wrapped_type & self) \ @@ -486,37 +486,37 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapRCameraController (*this) // clang-format off - DECL_QVECTOR3D_PROPERTY(position, position, setPosition) - DECL_QVECTOR3D_PROPERTY(up_vector, upVector, setUpVector) - DECL_QVECTOR3D_PROPERTY(view_center, viewCenter, setViewCenter) - DECL_QVECTOR3D_PROPERTY(default_position, defaultPosition, setDefaultPosition) - DECL_QVECTOR3D_PROPERTY(default_view_center, defaultViewCenter, setDefaultViewCenter) - DECL_QVECTOR3D_PROPERTY(default_up_vector, defaultUpVector, setDefaultUpVector) + MM_DECL_QVECTOR3D_PROPERTY(position, position, setPosition) + MM_DECL_QVECTOR3D_PROPERTY(up_vector, upVector, setUpVector) + MM_DECL_QVECTOR3D_PROPERTY(view_center, viewCenter, setViewCenter) + MM_DECL_QVECTOR3D_PROPERTY(default_position, defaultPosition, setDefaultPosition) + MM_DECL_QVECTOR3D_PROPERTY(default_view_center, defaultViewCenter, setDefaultViewCenter) + MM_DECL_QVECTOR3D_PROPERTY(default_up_vector, defaultUpVector, setDefaultUpVector) // clang-format on ; -#undef DECL_QVECTOR3D_PROPERTY - -#define DECL_FLOAT_PROPERTY(NAME, GETTER, SETTER) \ - .def_property( \ - #NAME, \ - [](wrapped_type & self) \ - { \ - return self.GETTER(); \ - }, \ - [](wrapped_type & self, float v) \ - { \ - self.SETTER(v); \ +#undef MM_DECL_QVECTOR3D_PROPERTY + +#define MM_DECL_FLOAT_PROPERTY(NAME, GETTER, SETTER) \ + .def_property( \ + #NAME, \ + [](wrapped_type & self) \ + { \ + return self.GETTER(); \ + }, \ + [](wrapped_type & self, float v) \ + { \ + self.SETTER(v); \ }) (*this) // clang-format off - DECL_FLOAT_PROPERTY(linear_speed, linearSpeed, setLinearSpeed) - DECL_FLOAT_PROPERTY(look_speed, lookSpeed, setLookSpeed) - DECL_FLOAT_PROPERTY(default_linear_speed, defaultLinearSpeed, setDefaultLinearSpeed) - DECL_FLOAT_PROPERTY(default_look_speed, defaultLookSpeed, setDefaultLookSpeed) + MM_DECL_FLOAT_PROPERTY(linear_speed, linearSpeed, setLinearSpeed) + MM_DECL_FLOAT_PROPERTY(look_speed, lookSpeed, setLookSpeed) + MM_DECL_FLOAT_PROPERTY(default_linear_speed, defaultLinearSpeed, setDefaultLinearSpeed) + MM_DECL_FLOAT_PROPERTY(default_look_speed, defaultLookSpeed, setDefaultLookSpeed) // clang-format on ; -#undef DECL_FLOAT_PROPERTY +#undef MM_DECL_FLOAT_PROPERTY } }; diff --git a/tests/test_view.py b/tests/test_view.py index e342faaa..03713cf8 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -30,7 +30,6 @@ import os import modmesh - try: from modmesh import view except ImportError: @@ -70,7 +69,9 @@ class ViewCameraTB: @classmethod def setUpClass(cls): widget = view.RManager.instance.setUp().add3DWidget() - widget.setCameraType(cls.camera_type) + + if cls.camera_type is not None: + widget.setCameraType(cls.camera_type) cls.widget = widget cls.camera = widget.camera @@ -105,47 +106,51 @@ def normalize(self, vec): @unittest.skipIf(GITHUB_ACTIONS, "GUI is not available in GitHub Actions") class ViewCommonCameraTC(ViewCameraTB, unittest.TestCase): - camera_type = "fps" # no difference when use orbit camera - - def setUp(self): - self.camera.reset() - def test_value_get_set(self): c = self.camera - c.linear_speed = 123.0 - self.assertEqual(c.linear_speed, 123.0) + for camera_type in ["fps", "orbit"]: + self.widget.setCameraType(camera_type) + + c.linear_speed = 123.0 + self.assertEqual(c.linear_speed, 123.0) - c.look_speed = 456.0 - self.assertEqual(c.look_speed, 456.0) + c.look_speed = 456.0 + self.assertEqual(c.look_speed, 456.0) def test_vector_get_set(self): c = self.camera - c.position = (1, 2, 3) - c.view_center = (4, 5, 6) - c.up_vector = (7, 8, 9) + for camera_type in ["fps", "orbit"]: + self.widget.setCameraType(camera_type) + + c.position = (1, 2, 3) + c.view_center = (4, 5, 6) + c.up_vector = (7, 8, 9) - self.assertEqual(c.position, (1, 2, 3)) - self.assertEqual(c.view_center, (4, 5, 6)) - self.assertEqual(c.up_vector, (7, 8, 9)) + self.assertEqual(c.position, (1, 2, 3)) + self.assertEqual(c.view_center, (4, 5, 6)) + self.assertEqual(c.up_vector, (7, 8, 9)) def test_default_values(self): c = self.camera - c.default_position = (1, 2, 3) - c.default_view_center = (4, 5, 6) - c.default_up_vector = (7, 8, 9) - c.default_linear_speed = 123.0 - c.default_look_speed = 456.0 + for camera_type in ["fps", "orbit"]: + self.widget.setCameraType(camera_type) - c.reset() + c.default_position = (1, 2, 3) + c.default_view_center = (4, 5, 6) + c.default_up_vector = (7, 8, 9) + c.default_linear_speed = 123.0 + c.default_look_speed = 456.0 + + c.reset() - self.assertEqual(c.position, (1, 2, 3)) - self.assertEqual(c.view_center, (4, 5, 6)) - self.assertEqual(c.up_vector, (7, 8, 9)) - self.assertEqual(c.linear_speed, 123.0) - self.assertEqual(c.look_speed, 456.0) + self.assertEqual(c.position, (1, 2, 3)) + self.assertEqual(c.view_center, (4, 5, 6)) + self.assertEqual(c.up_vector, (7, 8, 9)) + self.assertEqual(c.linear_speed, 123.0) + self.assertEqual(c.look_speed, 456.0) @unittest.skipIf(GITHUB_ACTIONS, "GUI is not available in GitHub Actions") From 42161b525396b3f6d032b6b85133f18f74ebc464 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Sun, 25 Aug 2024 17:28:28 +0600 Subject: [PATCH 27/28] apply review 2 --- cpp/modmesh/view/R3DWidget.hpp | 8 +- cpp/modmesh/view/RCameraController.cpp | 6 +- cpp/modmesh/view/RCameraController.hpp | 14 +-- cpp/modmesh/view/wrap_view.cpp | 2 +- tests/test_view.py | 135 ++++++++++++------------- 5 files changed, 82 insertions(+), 83 deletions(-) diff --git a/cpp/modmesh/view/R3DWidget.hpp b/cpp/modmesh/view/R3DWidget.hpp index 91f4c407..2462f342 100644 --- a/cpp/modmesh/view/R3DWidget.hpp +++ b/cpp/modmesh/view/R3DWidget.hpp @@ -57,9 +57,9 @@ class RScene m_controller = new ROrbitCameraController(this); } - CameraController * controller() const { return m_controller; } + RCameraController * controller() const { return m_controller; } - void setCameraController(CameraController * controller) + void setCameraController(RCameraController * controller) { m_controller->deleteLater(); m_controller = controller; @@ -71,7 +71,7 @@ class RScene private: - CameraController * m_controller; + RCameraController * m_controller; }; /* end class RScene */ @@ -96,7 +96,7 @@ class R3DWidget RScene * scene() { return m_scene; } Qt3DRender::QCamera * camera() { return m_view->camera(); } - CameraController * cameraController() const { return m_scene->controller(); } + RCameraController * cameraController() const { return m_scene->controller(); } QPixmap grabPixmap() const { return m_view->screen()->grabWindow(m_view->winId()); } diff --git a/cpp/modmesh/view/RCameraController.cpp b/cpp/modmesh/view/RCameraController.cpp index 8bc41de5..c2bdac15 100644 --- a/cpp/modmesh/view/RCameraController.cpp +++ b/cpp/modmesh/view/RCameraController.cpp @@ -212,7 +212,7 @@ void RCameraInputListener::initKeyboardListeners() const m_ty_axis->addInput(m_keyboard_ty_neg_input); } -void CameraController::reset() +void RCameraController::reset() { constexpr float fov_deg = 45.0f; constexpr float nearPlane = 0.1f; @@ -228,7 +228,7 @@ void CameraController::reset() } RFirstPersonCameraController::RFirstPersonCameraController(QNode * parent) - : CameraController(parent) + : RCameraController(parent) { auto callback = [this](const CameraInputState & state, const float dt) { @@ -259,7 +259,7 @@ void RFirstPersonCameraController::moveCamera(const CameraInputState & input, co } ROrbitCameraController::ROrbitCameraController(QNode * parent) - : CameraController(parent) + : RCameraController(parent) { auto callback = [this](const CameraInputState & state, const float dt) { diff --git a/cpp/modmesh/view/RCameraController.hpp b/cpp/modmesh/view/RCameraController.hpp index f7af79a9..dbe7752a 100644 --- a/cpp/modmesh/view/RCameraController.hpp +++ b/cpp/modmesh/view/RCameraController.hpp @@ -126,18 +126,20 @@ class RCameraInputListener : public Qt3DCore::QEntity Qt3DInput::QButtonAxisInput * m_keyboard_tz_neg_input; }; /* end class RCameraInputListener */ -class CameraController : public Qt3DExtras::QAbstractCameraController +class RCameraController : public Qt3DExtras::QAbstractCameraController { Q_OBJECT public: - explicit CameraController(Qt3DCore::QNode * parent = nullptr) + explicit RCameraController(Qt3DCore::QNode * parent = nullptr) : QAbstractCameraController(parent) { } - // Do nothing in QAbstractCameraController's moveCamera - void moveCamera(const InputState & state, float dt) override {} + void moveCamera(const InputState & state, float dt) override + { + // Do nothing in QAbstractCameraController's moveCamera + } virtual void moveCamera(const CameraInputState & state, float dt) = 0; @@ -191,7 +193,7 @@ class CameraController : public Qt3DExtras::QAbstractCameraController float m_default_look_speed = 180.0f; }; /* end class CameraController */ -class RFirstPersonCameraController : public CameraController +class RFirstPersonCameraController : public RCameraController { Q_OBJECT @@ -206,7 +208,7 @@ class RFirstPersonCameraController : public CameraController CameraControllerType getType() override { return CameraControllerType::FirstPerson; } }; /* end class RFirstPersonCameraController */ -class ROrbitCameraController : public CameraController +class ROrbitCameraController : public RCameraController { Q_OBJECT diff --git a/cpp/modmesh/view/wrap_view.cpp b/cpp/modmesh/view/wrap_view.cpp index bdb76fe3..77becbfb 100644 --- a/cpp/modmesh/view/wrap_view.cpp +++ b/cpp/modmesh/view/wrap_view.cpp @@ -405,7 +405,7 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapRManager }; /* end class WrapRManager */ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapRCameraController - : public WrapBase + : public WrapBase { friend root_base_type; diff --git a/tests/test_view.py b/tests/test_view.py index 03713cf8..c635fbb8 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -111,6 +111,7 @@ def test_value_get_set(self): for camera_type in ["fps", "orbit"]: self.widget.setCameraType(camera_type) + c.reset() c.linear_speed = 123.0 self.assertEqual(c.linear_speed, 123.0) @@ -123,6 +124,7 @@ def test_vector_get_set(self): for camera_type in ["fps", "orbit"]: self.widget.setCameraType(camera_type) + c.reset() c.position = (1, 2, 3) c.view_center = (4, 5, 6) @@ -137,6 +139,13 @@ def test_default_values(self): for camera_type in ["fps", "orbit"]: self.widget.setCameraType(camera_type) + c.reset() + + original_position = c.default_position + original_view_center = c.default_view_center + original_up_vector = c.default_up_vector + original_linear_speed = c.default_linear_speed + original_look_speed = c.default_look_speed c.default_position = (1, 2, 3) c.default_view_center = (4, 5, 6) @@ -152,6 +161,39 @@ def test_default_values(self): self.assertEqual(c.linear_speed, 123.0) self.assertEqual(c.look_speed, 456.0) + c.default_position = original_position + c.default_view_center = original_view_center + c.default_up_vector = original_up_vector + c.default_linear_speed = original_linear_speed + c.default_look_speed = original_look_speed + + def test_translation(self): + c = self.camera + + for camera_type in ["orbit"]: + self.widget.setCameraType(camera_type) + c.reset() + + speed = c.linear_speed + delta_vec = np.array([speed, speed, -speed]) + new_position = np.array(c.position) + delta_vec + new_view_center = np.array(c.view_center) + delta_vec + + c.move(x=1) + self.assertEqual(c.position[0], new_position[0]) + + c.move(y=1) + self.assertEqual(c.position[1], new_position[1]) + + # camera moves in negative z direction + c.move(z=1) + self.assertEqual(c.position[2], new_position[2]) + + # camera view center should move with camera + self.assertEqual(c.view_center[0], new_view_center[0]) + self.assertEqual(c.view_center[1], new_view_center[1]) + self.assertEqual(c.view_center[2], new_view_center[2]) + @unittest.skipIf(GITHUB_ACTIONS, "GUI is not available in GitHub Actions") class ViewFPSCameraTC(ViewCameraTB, unittest.TestCase): @@ -180,29 +222,6 @@ def test_reset(self): self.assertEqual(c.view_center, initial_view_center) self.assertEqual(c.up_vector, initial_up_vector) - def test_translation(self): - c = self.camera - - speed = c.linear_speed - delta_vec = np.array([speed, speed, -speed]) - new_position = np.array(c.position) + delta_vec - new_view_center = np.array(c.view_center) + delta_vec - - c.move(x=1) - self.assertEqual(c.position[0], new_position[0]) - - c.move(y=1) - self.assertEqual(c.position[1], new_position[1]) - - # camera moves in negative z direction - c.move(z=1) - self.assertEqual(c.position[2], new_position[2]) - - # camera view center should move with camera - self.assertEqual(c.view_center[0], new_view_center[0]) - self.assertEqual(c.view_center[1], new_view_center[1]) - self.assertEqual(c.view_center[2], new_view_center[2]) - def test_rotation(self): c = self.camera @@ -219,31 +238,32 @@ def test_rotation(self): # test camera rotates around y-axis c.move(yaw=1, left_mouse_button=True) - rotation_matrix = self.angle_axis(angle, (0, 1, 0)) - rotated_vector = np.array(initial_view_vector) @ rotation_matrix + rot_matrix = self.angle_axis(angle, (0, 1, 0)) + rot_vector = np.array(initial_view_vector) @ rot_matrix - self.assertAlmostEqual(rotated_vector[0], c.view_vector[0], places=5) - self.assertAlmostEqual(rotated_vector[1], c.view_vector[1], places=5) - self.assertAlmostEqual(rotated_vector[2], c.view_vector[2], places=5) + self.assertAlmostEqual(rot_vector[0], c.view_vector[0], delta=1e-5) + self.assertAlmostEqual(rot_vector[1], c.view_vector[1], delta=1e-5) + self.assertAlmostEqual(rot_vector[2], c.view_vector[2], delta=1e-5) # test camera rotates around x-axis old_view_vector = c.view_vector c.move(pitch=1, left_mouse_button=True) x_basis = -self.normalize(np.cross(old_view_vector, c.up_vector)) - rotation_matrix = self.angle_axis(angle, x_basis) - rotated_vector = np.array(old_view_vector) @ rotation_matrix + rot_matrix = self.angle_axis(angle, x_basis) + rot_vector = np.array(old_view_vector) @ rot_matrix - self.assertAlmostEqual(rotated_vector[0], c.view_vector[0], places=5) - self.assertAlmostEqual(rotated_vector[1], c.view_vector[1], places=5) - self.assertAlmostEqual(rotated_vector[2], c.view_vector[2], places=5) + self.assertAlmostEqual(rot_vector[0], c.view_vector[0], delta=1e-5) + self.assertAlmostEqual(rot_vector[1], c.view_vector[1], delta=1e-5) + self.assertAlmostEqual(rot_vector[2], c.view_vector[2], delta=1e-5) # test view center moved with the camera + view_center = c.view_center new_view_center = c.view_vector + np.array(c.position) - self.assertAlmostEqual(c.view_center[0], new_view_center[0], places=5) - self.assertAlmostEqual(c.view_center[1], new_view_center[1], places=5) - self.assertAlmostEqual(c.view_center[2], new_view_center[2], places=5) + self.assertAlmostEqual(view_center[0], new_view_center[0], delta=1e-5) + self.assertAlmostEqual(view_center[1], new_view_center[1], delta=1e-5) + self.assertAlmostEqual(view_center[2], new_view_center[2], delta=1e-5) @unittest.skipIf(GITHUB_ACTIONS, "GUI is not available in GitHub Actions") @@ -275,29 +295,6 @@ def test_reset(self): self.assertEqual(c.view_center, initial_view_center) self.assertEqual(c.up_vector, initial_up_vector) - def test_translation(self): - c = self.camera - - speed = c.linear_speed - delta_vec = np.array([speed, speed, -speed]) - new_pos = np.array(c.position) + delta_vec - new_view_center = np.array(c.view_center) + delta_vec - - c.move(x=1) - self.assertEqual(c.position[0], new_pos[0]) - - c.move(y=1) - self.assertEqual(c.position[1], new_pos[1]) - - # camera moves in negative z direction - c.move(z=1) - self.assertEqual(c.position[2], new_pos[2]) - - # camera view center should move with camera - self.assertEqual(c.view_center[0], new_view_center[0]) - self.assertEqual(c.view_center[1], new_view_center[1]) - self.assertEqual(c.view_center[2], new_view_center[2]) - def test_rotation(self): c = self.camera @@ -313,24 +310,24 @@ def test_rotation(self): # test camera rotates around y-axis c.move(yaw=1, right_mouse_button=True) - rotation_matrix = self.angle_axis(angle, (0, -1, 0)) - rotated_vector = np.array(initial_view_vector) @ rotation_matrix + rot_matrix = self.angle_axis(angle, (0, -1, 0)) + rot_vector = np.array(initial_view_vector) @ rot_matrix - self.assertAlmostEqual(rotated_vector[0], c.view_vector[0], places=5) - self.assertAlmostEqual(rotated_vector[1], c.view_vector[1], places=5) - self.assertAlmostEqual(rotated_vector[2], c.view_vector[2], places=5) + self.assertAlmostEqual(rot_vector[0], c.view_vector[0], delta=1e-5) + self.assertAlmostEqual(rot_vector[1], c.view_vector[1], delta=1e-5) + self.assertAlmostEqual(rot_vector[2], c.view_vector[2], delta=1e-5) # test camera rotates around x-axis old_view_vector = c.view_vector c.move(pitch=1, right_mouse_button=True) x_basis = self.normalize(np.cross(old_view_vector, c.up_vector)) - rotation_matrix = self.angle_axis(angle, x_basis) - rotated_vector = np.array(old_view_vector) @ rotation_matrix + rot_matrix = self.angle_axis(angle, x_basis) + rot_vector = np.array(old_view_vector) @ rot_matrix - self.assertAlmostEqual(rotated_vector[0], c.view_vector[0], places=5) - self.assertAlmostEqual(rotated_vector[1], c.view_vector[1], places=5) - self.assertAlmostEqual(rotated_vector[2], c.view_vector[2], places=5) + self.assertAlmostEqual(rot_vector[0], c.view_vector[0], delta=1e-5) + self.assertAlmostEqual(rot_vector[1], c.view_vector[1], delta=1e-5) + self.assertAlmostEqual(rot_vector[2], c.view_vector[2], delta=1e-5) # camera view center should not change self.assertEqual(c.view_center, initial_view_center) From 11540063a65cc4aaad8f5c8fda5b7035920fbfce Mon Sep 17 00:00:00 2001 From: dragonwu0919 Date: Wed, 28 Aug 2024 19:52:35 +0800 Subject: [PATCH 28/28] Add vim modeline. The C++ and Python files in this project have been scanned. Modelines have been added to those files that had no modeline at all. For files containing a modeline other than the latest version, I left them unchanged. --- cpp/modmesh/buffer/SimpleArray.cpp | 2 ++ cpp/modmesh/buffer/pymod/TypeBroadcast.hpp | 2 ++ cpp/modmesh/inout/pymod/wrap_Gmsh.cpp | 2 ++ cpp/modmesh/inout/pymod/wrap_Plot3d.cpp | 2 ++ cpp/modmesh/onedim/Euler1DCore.cpp | 2 ++ cpp/modmesh/onedim/Euler1DCore.hpp | 4 +++- cpp/modmesh/spacetime/kernel/BadEuler1DSolver.cpp | 2 ++ cpp/modmesh/spacetime/kernel/BadEuler1DSolver.hpp | 4 +++- cpp/modmesh/testhelper/pymod/testbuffer_pymod.cpp | 2 ++ gtests/test_nopython_callprofiler.cpp | 4 +++- gtests/test_nopython_inout.cpp | 2 ++ gtests/test_nopython_serializable.cpp | 4 +++- modmesh/app/euler1d.py | 2 ++ modmesh/params.py | 2 ++ setup.py | 2 ++ tests/test_gmsh.py | 2 ++ 16 files changed, 36 insertions(+), 4 deletions(-) diff --git a/cpp/modmesh/buffer/SimpleArray.cpp b/cpp/modmesh/buffer/SimpleArray.cpp index cfbb930e..ec785a97 100644 --- a/cpp/modmesh/buffer/SimpleArray.cpp +++ b/cpp/modmesh/buffer/SimpleArray.cpp @@ -515,3 +515,5 @@ SimpleArrayPlex::~SimpleArrayPlex() } } /* end namespace modmesh */ + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/cpp/modmesh/buffer/pymod/TypeBroadcast.hpp b/cpp/modmesh/buffer/pymod/TypeBroadcast.hpp index 601abbfb..60cb7d8c 100644 --- a/cpp/modmesh/buffer/pymod/TypeBroadcast.hpp +++ b/cpp/modmesh/buffer/pymod/TypeBroadcast.hpp @@ -234,3 +234,5 @@ struct TypeBroadcast } /* end namespace python */ } /* end namespace modmesh */ + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/cpp/modmesh/inout/pymod/wrap_Gmsh.cpp b/cpp/modmesh/inout/pymod/wrap_Gmsh.cpp index d5e54099..1d0cc24f 100644 --- a/cpp/modmesh/inout/pymod/wrap_Gmsh.cpp +++ b/cpp/modmesh/inout/pymod/wrap_Gmsh.cpp @@ -44,3 +44,5 @@ void wrap_Gmsh(pybind11::module & mod) } /* end namespace python */ } /* end namespace modmesh */ + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/cpp/modmesh/inout/pymod/wrap_Plot3d.cpp b/cpp/modmesh/inout/pymod/wrap_Plot3d.cpp index 4410033c..e9257239 100644 --- a/cpp/modmesh/inout/pymod/wrap_Plot3d.cpp +++ b/cpp/modmesh/inout/pymod/wrap_Plot3d.cpp @@ -44,3 +44,5 @@ void wrap_Plot3d(pybind11::module & mod) } /* end namespace python */ } /* end namespace modmesh */ + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/cpp/modmesh/onedim/Euler1DCore.cpp b/cpp/modmesh/onedim/Euler1DCore.cpp index 8065d791..c21a43c1 100644 --- a/cpp/modmesh/onedim/Euler1DCore.cpp +++ b/cpp/modmesh/onedim/Euler1DCore.cpp @@ -228,3 +228,5 @@ SimpleArray Euler1DCore::entropy() const } /* end namespace onedim */ } /* end namespace modmesh */ + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/cpp/modmesh/onedim/Euler1DCore.hpp b/cpp/modmesh/onedim/Euler1DCore.hpp index 6b419559..46f8f351 100644 --- a/cpp/modmesh/onedim/Euler1DCore.hpp +++ b/cpp/modmesh/onedim/Euler1DCore.hpp @@ -344,4 +344,6 @@ inline void Euler1DCore::march_alpha(size_t steps) } } /* end namespace onedim */ -} /* end namespace modmesh */ \ No newline at end of file +} /* end namespace modmesh */ + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/cpp/modmesh/spacetime/kernel/BadEuler1DSolver.cpp b/cpp/modmesh/spacetime/kernel/BadEuler1DSolver.cpp index d4dfe159..bb78e877 100644 --- a/cpp/modmesh/spacetime/kernel/BadEuler1DSolver.cpp +++ b/cpp/modmesh/spacetime/kernel/BadEuler1DSolver.cpp @@ -131,3 +131,5 @@ void BadEuler1DSolver::treat_boundary_so1() } /* end namespace spacetime */ } /* end namespace modmesh */ + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/cpp/modmesh/spacetime/kernel/BadEuler1DSolver.hpp b/cpp/modmesh/spacetime/kernel/BadEuler1DSolver.hpp index eb2af826..e95c6026 100644 --- a/cpp/modmesh/spacetime/kernel/BadEuler1DSolver.hpp +++ b/cpp/modmesh/spacetime/kernel/BadEuler1DSolver.hpp @@ -300,4 +300,6 @@ inline void BadEuler1DSolver::march_alpha(size_t steps) } } /* end namespace spacetime */ -} /* end namespace modmesh */ \ No newline at end of file +} /* end namespace modmesh */ + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/cpp/modmesh/testhelper/pymod/testbuffer_pymod.cpp b/cpp/modmesh/testhelper/pymod/testbuffer_pymod.cpp index 1887a6db..605f9582 100644 --- a/cpp/modmesh/testhelper/pymod/testbuffer_pymod.cpp +++ b/cpp/modmesh/testhelper/pymod/testbuffer_pymod.cpp @@ -61,3 +61,5 @@ void initialize_testbuffer(pybind11::module & mod) } /* end namespace python */ } /* end namespace modmesh */ + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/gtests/test_nopython_callprofiler.cpp b/gtests/test_nopython_callprofiler.cpp index 254ea926..c854b251 100644 --- a/gtests/test_nopython_callprofiler.cpp +++ b/gtests/test_nopython_callprofiler.cpp @@ -213,4 +213,6 @@ TEST_F(CallProfilerTest, cancel) } } // namespace detail -} // namespace modmesh \ No newline at end of file +} // namespace modmesh + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/gtests/test_nopython_inout.cpp b/gtests/test_nopython_inout.cpp index 9a666479..f254e177 100644 --- a/gtests/test_nopython_inout.cpp +++ b/gtests/test_nopython_inout.cpp @@ -314,3 +314,5 @@ TEST(Gmsh_Parser, Hexahedron125NodeDefinition) EXPECT_EQ(ele_def.mmtpn(), 5); EXPECT_THAT(ele_def.mmcl(), testing::ElementsAre(0, 1, 2, 3, 4, 5, 6, 7)); } + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/gtests/test_nopython_serializable.cpp b/gtests/test_nopython_serializable.cpp index f92d06e3..1cec1e68 100644 --- a/gtests/test_nopython_serializable.cpp +++ b/gtests/test_nopython_serializable.cpp @@ -322,4 +322,6 @@ TEST(Json, deserialize_with_unordered_map) EXPECT_EQ(item.pet_map["cat"].is_cat, true); } -} // namespace modmesh \ No newline at end of file +} // namespace modmesh + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/modmesh/app/euler1d.py b/modmesh/app/euler1d.py index efef8fe4..36065a91 100644 --- a/modmesh/app/euler1d.py +++ b/modmesh/app/euler1d.py @@ -839,3 +839,5 @@ def load_app(): config_window.redraw() plotting_area.redraw() _subwin.show() + +# vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/modmesh/params.py b/modmesh/params.py index d59c43e9..b9d2bb5c 100644 --- a/modmesh/params.py +++ b/modmesh/params.py @@ -109,3 +109,5 @@ def run(self): root = Example() root.run() + +# vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/setup.py b/setup.py index 462f0376..ed1fb858 100644 --- a/setup.py +++ b/setup.py @@ -79,3 +79,5 @@ def main(): if __name__ == '__main__': main() + +# vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/tests/test_gmsh.py b/tests/test_gmsh.py index 2e95c34a..5d19b79c 100644 --- a/tests/test_gmsh.py +++ b/tests/test_gmsh.py @@ -33,3 +33,5 @@ def test_gmsh_parsing(self): self.assertEqual(blk.clnds.ndarray[3:, :4].tolist(), [[3, 0, 1, 2], [3, 0, 2, 3], [3, 0, 3, 1]]) + +# vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: