diff --git a/src/Meshing/AMeshingTool.py b/src/Meshing/AMeshingTool.py new file mode 100644 index 0000000000000000000000000000000000000000..8c4c9aa5db5d400dee217d2d2bcd955002022d01 --- /dev/null +++ b/src/Meshing/AMeshingTool.py @@ -0,0 +1,27 @@ +# AMeshingTool.py -- Pamhyr +# Copyright (C) 2023 INRAE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +# -*- coding: utf-8 -*- + +from Model.Except import NotImplementedMethodeError + + +class AMeshingTool(object): + def __init__(self): + super(AMeshingTool, self).__init__() + + def meshing(self, reach, **kwargs): + raise NotImplementedMethodeError(self, self.meshing) diff --git a/src/Meshing/Mage.py b/src/Meshing/Mage.py new file mode 100644 index 0000000000000000000000000000000000000000..56c15bc47b9a59ea6f50fd2bff01de7769727933 --- /dev/null +++ b/src/Meshing/Mage.py @@ -0,0 +1,232 @@ +# Mage.py -- Pamhyr +# Copyright (C) 2023 INRAE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +# -*- coding: utf-8 -*- + +import os +import logging +import tempfile + +from ctypes import ( + cdll, + byref, Structure, + c_char_p, c_wchar_p, + create_string_buffer, + POINTER, c_void_p, + c_int, c_double, c_bool +) + +from Meshing.AMeshingTool import AMeshingTool + +logger = logging.getLogger() + + +class MeshingWithMage(AMeshingTool): + def __init__(self): + super(MeshingWithMage, self).__init__() + + self._init_bief_lib() + + def _init_bief_lib(self): + self._bief_lib = cdll.LoadLibrary(self._lib_path()) + + self._init_c_init_bief_from_geo_file() + self._init_c_get_nb_sections() + self._init_c_get_nb_points_section() + self._init_c_set_bief_name() + self._init_c_st_to_m_compl() + self._init_c_interpolate_profils_pas_transversal() + self._init_c_output_bief() + + def _lib_path(self): + return os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "..", "..", "mage", "libbief.so" + ) + ) + + def _init_c_init_bief_from_geo_file(self): + self._c_init_bief_from_geo_file = getattr( + self._bief_lib, 'c_init_bief_from_geo_file' + ) + self._c_init_bief_from_geo_file.argtypes = [ + c_char_p, POINTER(c_int), POINTER(c_int) + ] + self._c_init_bief_from_geo_file.restype = None + + def _init_c_get_nb_sections(self): + self._c_get_nb_sections = getattr(self._bief_lib, 'c_get_nb_sections') + self._c_get_nb_sections.argtypes = [POINTER(c_int)] + self._c_get_nb_sections.restype = None + + def _init_c_get_nb_points_section(self): + self._c_get_nb_points_section = getattr( + self._bief_lib, 'c_get_nb_points_section' + ) + self._c_get_nb_points_section.argtypes = [ + POINTER(c_int), POINTER(c_int) + ] + self._c_get_nb_points_section.restype = None + + def _init_c_set_bief_name(self): + self._c_set_bief_name = getattr(self._bief_lib, 'c_set_bief_name') + self._c_set_bief_name.argtypes = [c_char_p] + self._c_set_bief_name.restype = None + + def _init_c_st_to_m_compl(self): + self._c_st_to_m_compl = getattr(self._bief_lib, 'c_st_to_m_compl') + self._c_st_to_m_compl.argtypes = [POINTER(c_int), c_char_p, c_char_p] + self._c_st_to_m_compl.restype = None + + def _init_c_interpolate_profils_pas_transversal(self): + self._c_interpolate_profils_pas_transversal = getattr( + self._bief_lib, 'c_interpolate_profils_pas_transversal' + ) + self._c_interpolate_profils_pas_transversal.argtypes = [ + POINTER(c_int), POINTER(c_int), + c_char_p, c_char_p, + POINTER(c_double), POINTER(c_bool), + POINTER(c_int), POINTER(c_bool) + ] + self._c_interpolate_profils_pas_transversal.restype = None + + def _init_c_output_bief(self): + self._c_output_bief = getattr(self._bief_lib, 'c_output_bief') + self._c_output_bief.argtypes = [c_char_p] + self._c_output_bief.restype = None + + ##################### + # Binding functions # + ##################### + + def init_bief_from_geo_file(self, name, with_charriage, with_water): + cname = create_string_buffer(name.encode()) + self._c_init_bief_from_geo_file( + cname, + byref(c_int(with_charriage)), + byref(c_int(with_water)) + ) + + def get_nb_sections(self): + nb_sections = c_int(0) + self._c_get_nb_sections(byref(nb_sections)) + return nb_sections.value + + def get_nb_points_section(self, section): + nb_points = c_int(0) + self._c_get_nb_points_section( + byref(c_int(section)), byref(nb_points) + ) + return nb_points.value + + def set_bief_name(self, name): + cname = create_string_buffer(name.encode()) + self._c_set_bief_name(cname) + + def st_to_m_compl(self, npoints, tag1=' ', tag2=' '): + cnpoints = c_int(npoints) + ctag1 = create_string_buffer(tag1.encode()) + ctag2 = create_string_buffer(tag2.encode()) + + self._c_st_to_m_compl(byref(cnpoints), ctag1, ctag2) + + def interpolate_profils_pas_transversal(self, limite1, limite2, + directrice1, directrice2, + pas, lplan=False, + lm=3, lineaire=False): + climite1 = c_int(limite1) + climite2 = c_int(limite2) + cpas = c_double(pas) + clplan = c_bool(lplan) + clm = c_int(lm) + clineaire = c_bool(lineaire) + cdirectrice1 = create_string_buffer(directrice1.encode()) + cdirectrice2 = create_string_buffer(directrice2.encode()) + + self._c_interpolate_profils_pas_transversal( + byref(climite1), byref(climite2), + cdirectrice1, cdirectrice2, + byref(cpas), byref(clplan), + byref(clm), byref(clineaire) + ) + + def output_bief(self, name): + cname = create_string_buffer(name.encode()) + self._c_output_bief(cname) + + ########### + # Meshing # + ########### + + def meshing(self, reach, step: float = 50): + with tempfile.TemporaryDirectory() as tmp: + st_file = self.export_reach_to_st(reach, tmp) + m_file = st_file.rsplit(".ST", 1)[0] + ".M" + + self.load_st_file(st_file) + + ns, npts_max = self.get_reach_stat() + + self.complete_cross_section() + self.interpolate_cross_section(ns, step) + + self.export_to_m(m_file) + + self.import_m_file(reach, m_file) + return reach + + def export_reach_to_st(self, reach, tmp): + tmp_st = os.path.join(tmp, "meshing.ST") + + logger.debug(f"meshing: Export ST to {tmp_st}") + + reach.export_reach(tmp_st) + return tmp_st + + def load_st_file(self, st): + self.init_bief_from_geo_file(st, 0, 0) + self.set_bief_name("tmp") + + def get_reach_stat(self): + ns = self.get_nb_sections() + npts_max = max( + map( + lambda i: self.get_nb_points_section(i), + range(1, ns) + ) + ) + + return ns, npts_max + + def complete_cross_section(self): + self.st_to_m_compl(0, ' ', ' ') + + def interpolate_cross_section(self, ns, step: float): + self.interpolate_profils_pas_transversal( + 1, ns, + 'un', 'np', + step + ) + + def export_to_m(self, m): + self.output_bief(m) + + def import_m_file(self, reach, m): + reach.purge() + + logger.debug(f"meshing: Import geometry from {m}") + reach.import_geometry(m) diff --git a/src/Model/Geometry/Reach.py b/src/Model/Geometry/Reach.py index 3c02f16a68541a78b4fd79dfdd09d2c5aeb3f63b..f7c7e4856b0e0ac4158962d91bace9fef1339dd3 100644 --- a/src/Model/Geometry/Reach.py +++ b/src/Model/Geometry/Reach.py @@ -247,6 +247,11 @@ class Reach(SQLSubModel): self._update_profile_numbers() self._status.modified() + def purge(self): + self._profiles = [] + self._update_profile_numbers() + self._status.modified() + def move_up_profile(self, index: int): if index < len(self.profiles): next = index - 1 @@ -525,6 +530,8 @@ class Reach(SQLSubModel): list_header = [] stop_code = "999.999" + logger.debug(f"Import geometry from {filename}") + with open(filename, encoding="utf-8") as file_st: for line in file_st: if not (line.startswith("#") or line.startswith("*")): @@ -563,21 +570,31 @@ class Reach(SQLSubModel): # TODO: Move this function to model reach def export_reach(self, filename): - with open(f"{filename}", "w") as file_st: - for index in range(len(self.profiles)): - profile = self.profiles[index] - - for v in profile.header: - file_st.write(f"{v:>6}") - file_st.write("\n") - - for point in self._data.profile[index_pro].points: - for i in [point.x, point.y, point.z, point.name]: - file_st.write(f"{i:>13.4f}") - file_st.write("\n") - - file_st.write(" 999.9990 999.9990 999.9990") - file_st.write("\n") + with open(f"{filename}", "w") as st: + cnt = 0 + for profile in self.profiles: + num = f"{cnt:>6}" + c1 = f"{profile.code1:>6}" + c2 = f"{profile.code2:>6}" + t = f"{len(profile.points):>6}" + kp = f"{profile.kp:>12f}"[0:12] + pname = profile.name + if profile.name == "": + pname = f"p{profile.id:>3}".replace(" ", "0") + + st.write(f"{num}{c1}{c2}{t} {kp} {pname}\n") + + for point in profile.points: + x = f"{point.x:<12.4f}"[0:12] + y = f"{point.y:<12.4f}"[0:12] + z = f"{point.z:<12.4f}"[0:12] + n = f"{point.name:<3}" + + st.write(f"{x} {y} {z} {n}\n") + + st.write(" 999.9990 999.9990 999.9990") + st.write("\n") + cnt += 1 def get_incline(self): first = self.profile(0) diff --git a/src/Scripts/MageMesh.py b/src/Scripts/MageMesh.py index 47edf649605c4dd0352f4b57312068b652e2197b..6b9aea4090de7236b754e6ca4bfc3c85f40d6f15 100644 --- a/src/Scripts/MageMesh.py +++ b/src/Scripts/MageMesh.py @@ -139,6 +139,7 @@ def init_c_output_bief(bief_lib): # Binding functions # ##################### + def init_bief_from_geo_file(name, with_charriage, with_water): logger.info("! call init_bief_from_geo_file:") cname = create_string_buffer(name.encode()) @@ -148,6 +149,7 @@ def init_bief_from_geo_file(name, with_charriage, with_water): byref(c_int(with_water)) ) + def get_nb_sections(): nb_sections = c_int(0) c_get_nb_sections(byref(nb_sections)) diff --git a/src/View/Geometry/Window.py b/src/View/Geometry/Window.py index 4f1e0b22de4202b0c5f2b4ff811932a3ace047ad..99eb848a53ef0e5ced368ee78a50d47261f2e67d 100644 --- a/src/View/Geometry/Window.py +++ b/src/View/Geometry/Window.py @@ -43,6 +43,8 @@ from View.Tools.PamhyrWindow import PamhyrWindow from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar from View.Tools.Plot.PamhyrCanvas import MplCanvas +from Meshing.Mage import MeshingWithMage + from View.Geometry.Table import GeometryReachTableModel from View.Geometry.PlotXY import PlotXY from View.Geometry.PlotAC import PlotAC @@ -168,6 +170,7 @@ class GeometryWindow(PamhyrWindow): "action_add": self.add, "action_delete": self.delete, "action_edit": self.edit_profile, + "action_meshing": self.edit_meshing, } for action in actions: @@ -243,6 +246,20 @@ class GeometryWindow(PamhyrWindow): self.tableView.model().blockSignals(False) + def edit_meshing(self): + self.tableView.model().blockSignals(True) + + mesher = MeshingWithMage() + mesher.meshing(self._reach) + + self.tableView.model().blockSignals(False) + + self.update_profile_windows() + self.plot_xy() + self.plot_kpc() + self.plot_ac() + + pyqtSlot(bool) def changed_profile_slot(self, status): diff --git a/src/View/ui/GeometryReach.ui b/src/View/ui/GeometryReach.ui index d18d0e14e34cb335b9b9f5ae31a99e023241bcb8..af5160d3fb7938b995cb8a07d45cc96558bb0f50 100644 --- a/src/View/ui/GeometryReach.ui +++ b/src/View/ui/GeometryReach.ui @@ -126,6 +126,7 @@ <addaction name="action_up"/> <addaction name="action_down"/> <addaction name="action_export"/> + <addaction name="action_meshing"/> </widget> <action name="action_import"> <property name="text"> @@ -227,6 +228,11 @@ <string>Move down selected cross-section(s)</string> </property> </action> + <action name="action_meshing"> + <property name="text"> + <string>meshing</string> + </property> + </action> </widget> <resources/> <connections/>