diff --git a/ceasiompy/CPACS2GMSH/README.md b/ceasiompy/CPACS2GMSH/README.md index f80e80ab2..2480decb3 100644 --- a/ceasiompy/CPACS2GMSH/README.md +++ b/ceasiompy/CPACS2GMSH/README.md @@ -10,7 +10,14 @@
-`CPACS2GMSH` is an automatic mesh generator module for a [CPACS](https://www.cpacs.de) aircraft geometry [[1]](#Alder20) using [GMSH](https://gmsh.info/) ,a finite element mesh generator. An unstructured mesh is automatically generated in a spherical domain surrounding the aircraft. The resulting mesh can be used for a CFD calculation by connecting the `SU2Run` module after `CPACS2GMSH` module. +`CPACS2GMSH` is an automatic mesh generator module for a [CPACS](https://www.cpacs.de) aircraft geometry [[1]](#Alder20) using [GMSH](https://gmsh.info/) ,a finite element mesh generator. + +It's currently possible to choose between two options for 3D meshing of the external domain. +Selecting the 'Euler' an unstructured mesh is automatically generated in a spherical domain surrounding the aircraft. +Instead, selecting the 'RANS' option Gmsh will generate only the 2D mesh of the entire aircraft, which will then be processed by the programme [Pentagrow] to generate the structured part that wraps the geometry, then [Tetgen](https://wias-berlin.de/software/tetgen/1.5/doc/manual/manual.pdf) package provides for meshing of the unstructured part. The hybrid mesh obtained will constitute the 3D domain. + + +The resulting mesh can be used for a CFD calculation by connecting the `SU2Run` module after `CPACS2GMSH` module.
@@ -21,7 +28,7 @@ Surface mesh of the D150 aircraft, with a symmetry plane

-If an engine (simple or doubleflux) is part of the aircraft geometry, CPACS2GMSH will combine the different nacelle parts in one engine and will add an intake and exhaust surface that can be used by SU2Run to simulate the engine operation. For doubleflux engines, only one intake surface will be placed on the fan cowl and two exhaust surfaces will be placed on the fan and center cowl. +By Euler if an engine (simple or doubleflux) is part of the aircraft geometry, CPACS2GMSH will combine the different nacelle parts in one engine and will add an intake and exhaust surface that can be used by SU2Run to simulate the engine operation. For doubleflux engines, only one intake surface will be placed on the fan cowl and two exhaust surfaces will be placed on the fan and center cowl. If the aircraft geometry contains propeller engines, their blades will be replaced by 2D disk surfaces in order to simulate the propeller engines with SU2 disk actuator model. @@ -43,6 +50,11 @@ General options: * `Display mesh with GMSH : False` Open the gmsh GUI after the generation of the surface mesh (2D) and the domain mesh (3D). This option is usefully to control the quality of the automated generated mesh or make extra operation with gmsh GUI. + +Mesh type: +* `Choice the mesh type: Euler or RANS` +Choose between an unstructured domain (Euler) and an hybrid domain (RANS) + Domain: * `Use Symmetry : False` @@ -51,7 +63,8 @@ Apply a symmetry operation to the model with a xz symmetry plane in the center o * `Farfield size factor : 6.0` Enable to control the spherical domain size. The fluid domain surrounding the aircraft is defined with a radius equivalent to the largest xyz aircraft dimension times the `Farfield size factor -Mesh size: +if Euler: +Euler options: * `Farfield : 25.0` Mesh size of the farfield surfaces * `Fuselage : 0.4` Mesh size of the fuselage surfaces @@ -61,6 +74,23 @@ Mesh size: :warning: The mesh size values are unitless. They are consistent with the aircraft dimensions units +else: +RANS options: + +*`Number of layer: 20` +Number of prismatic element layers +*`height of first layer: 3 e-5 mm` +Height of the first prismatic cell, touching the wall, in mesh length units. +*`Max layer thickness: 10 cm` +The maximum allowed absolute thickness of the prismatic layer. +*`Growth factor: 1.2` +Growth factor between edge lengths of coincident tetrahedra +*`Feature angle: 80 grad` +Whenever the dihedral angle of two triangle is smaller than this limit, the resulting edge is understood to represent an actual geometrical feature. Larger angles are treated as resulting from approximation of curved surfaces by linear triangles +*`Surface mesh size: 5 ` + Surface mesh size factor compared to the aircraft largest dimension, omogeneus everywhere + + Advanced mesh parameters : * `LE/TE refinement factor : 7.0` @@ -81,12 +111,16 @@ Engine exhaust surface position from the back of the engine fan cowl in percent ## Analyses -`CPACS2GMSH` Generate .brep files with TiGL for each part of the aircraft configuration. Then all the parts are imported into GMSH to generates a SU2 mesh file. +`CPACS2GMSH` Generate .brep files with TiGL for each part of the aircraft configuration. Then all the parts are imported into GMSH to generates a SU2 mesh file + +for the euler case, instead a .stl file is generated to be read by pentagrow ## Outputs `CPACS2GMSH` outputs a SU2 mesh files (.su2), the path to this file is saved in the CPACS file under this xpath: /cpacs/toolspecific/CEASIOMpy/filesPath/su2Mesh. +With RANS also a configuration file is created in the same directory containing the setup used to generate the hybrid mesh. + ## Installation or requirements `CPACS2GMSH` is a native CEASIOMpy module, hence it is available and installed by default. To run it, you just have to be sure that you are in the CEASIOMpy Conda environment. @@ -95,6 +129,8 @@ Engine exhaust surface position from the back of the engine fan cowl in percent At the time of writing, this module is not able to handle aircraft with control surfaces (they will not be modelled and thus appear in the final mesh). +For the RANS part, it is only possible to process aircraft consisting of .brep files of category 'fuselage' and 'wing' + ## More information * [CPACS official website](https://www.cpacs.de) diff --git a/ceasiompy/CPACS2GMSH/__specs__.py b/ceasiompy/CPACS2GMSH/__specs__.py index cdefcd57c..c9b6235e9 100644 --- a/ceasiompy/CPACS2GMSH/__specs__.py +++ b/ceasiompy/CPACS2GMSH/__specs__.py @@ -20,6 +20,14 @@ SU2MESH_XPATH, GMSH_MESH_SIZE_FACTOR_FUSELAGE_XPATH, GMSH_MESH_SIZE_FACTOR_WINGS_XPATH, + GMSH_MESH_TYPE_XPATH, + GMSH_NUMBER_LAYER_XPATH, + GMSH_H_FIRST_LAYER_XPATH, + GMSH_MAX_THICKNESS_LAYER_XPATH, + GMSH_GROWTH_FACTOR_XPATH, + GMSH_GROWTH_RATIO_XPATH, + GMSH_SURFACE_MESH_SIZE_XPATH, + GMSH_FEATURE_ANGLE_XPATH, ) from ceasiompy.utils.moduleinterfaces import CPACSInOut @@ -52,15 +60,15 @@ ) cpacs_inout.add_input( - var_name="export_propellers", - var_type=bool, - default_value=False, + var_name="type_mesh", + var_type=list, + default_value=["Euler", "RANS"], unit="1", - descr="Export propeller(s) to be use as disk actuator", - xpath=GMSH_EXPORT_PROP_XPATH, + descr="Chose between Euler and RANS mesh", + xpath=GMSH_MESH_TYPE_XPATH, gui=True, - gui_name="Export propeller(s)", - gui_group="General options", + gui_name="Chose the mesh type", + gui_group="Mesh type", ) cpacs_inout.add_input( @@ -78,7 +86,7 @@ cpacs_inout.add_input( var_name="farfield_factor", var_type=float, - default_value=6, + default_value=10, unit="[-]", descr="Farfield size factor compare to the aircraft largest dimension", xpath=GMSH_FARFIELD_FACTOR_XPATH, @@ -93,11 +101,11 @@ default_value=10, unit="[-]", descr="""Factor proportional to the biggest cell on the plane - to obtain cell size on the farfield""", + to obtain cell size on the farfield(just for Euler)""", xpath=GMSH_MESH_SIZE_FARFIELD_XPATH, gui=True, gui_name="Farfield mesh size factor", - gui_group="Mesh size", + gui_group="Euler options", ) cpacs_inout.add_input( @@ -109,7 +117,7 @@ xpath=GMSH_MESH_SIZE_FACTOR_FUSELAGE_XPATH, gui=True, gui_name="Fuselage mesh size factor", - gui_group="Mesh size", + gui_group="Euler options", ) cpacs_inout.add_input( @@ -121,7 +129,7 @@ xpath=GMSH_MESH_SIZE_FACTOR_WINGS_XPATH, gui=True, gui_name="Wings mesh size factor", - gui_group="Mesh size", + gui_group="Euler options", ) cpacs_inout.add_input( @@ -133,7 +141,7 @@ xpath=GMSH_MESH_SIZE_ENGINES_XPATH, gui=True, gui_name="Engines", - gui_group="Mesh size", + gui_group="Euler options", ) cpacs_inout.add_input( @@ -145,7 +153,103 @@ xpath=GMSH_MESH_SIZE_PROPELLERS_XPATH, gui=True, gui_name="Propellers", - gui_group="Mesh size", + gui_group="Euler options", +) + +cpacs_inout.add_input( + var_name="n_layer", + var_type=int, + default_value=20, + unit="[-]", + descr="Number of prismatic element layers.", + xpath=GMSH_NUMBER_LAYER_XPATH, + gui=True, + gui_name="Number of layer", + gui_group="RANS options", +) + +cpacs_inout.add_input( + var_name="h_first_layer", + var_type=float, + default_value=3, + unit="[\u03BCm]", + descr="is the height of the first prismatic cell, touching the wall, in mesh length units.", + xpath=GMSH_H_FIRST_LAYER_XPATH, + gui=True, + gui_name="Height of first layer", + gui_group="RANS options", +) + +cpacs_inout.add_input( + var_name="max_layer_thickness", + var_type=float, + default_value=100, + unit="[mm]", + descr="The maximum allowed absolute thickness of the prismatic layer.", + xpath=GMSH_MAX_THICKNESS_LAYER_XPATH, + gui=True, + gui_name="Max layer thickness", + gui_group="RANS options", +) + +cpacs_inout.add_input( + var_name="growth_ratio", + var_type=float, + default_value=1.2, + unit="[-]", + descr="the largest allowed ratio between the wall-normal edge lengths of consecutive cells", + xpath=GMSH_GROWTH_RATIO_XPATH, + gui=True, + gui_name="Growth ratio", + gui_group="RANS options", +) + +cpacs_inout.add_input( + var_name="growth_factor", + var_type=float, + default_value=1.4, + unit="[-]", + descr="Desired growth factor between edge lengths of coincident tetrahedra", + xpath=GMSH_GROWTH_FACTOR_XPATH, + gui=True, + gui_name="Growth factor", + gui_group="RANS options", +) + +cpacs_inout.add_input( + var_name="feature_angle", + var_type=float, + default_value=40, + unit="[grad]", + descr="Larger angles are treated as resulting from approximation of curved surfaces", + xpath=GMSH_FEATURE_ANGLE_XPATH, + gui=True, + gui_name="Feature Angle", + gui_group="RANS options", +) + +cpacs_inout.add_input( + var_name="surface_mesh_factor", + var_type=float, + default_value=5, + unit="[10^-3]", + descr="Surface mesh size factor compared to aircraft largest dimension (omogeneus everywhere)", + xpath=GMSH_SURFACE_MESH_SIZE_XPATH, + gui=True, + gui_name="Surface mesh size", + gui_group="RANS options", +) + +cpacs_inout.add_input( + var_name="export_propellers", + var_type=bool, + default_value=False, + unit="1", + descr="Export propeller(s) to be use as disk actuator", + xpath=GMSH_EXPORT_PROP_XPATH, + gui=True, + gui_name="Export propeller(s)", + gui_group="General options", ) cpacs_inout.add_input( @@ -157,7 +261,7 @@ xpath=GMSH_N_POWER_FACTOR_XPATH, gui=True, gui_name="n power factor", - gui_group="Advanced mesh parameters", + gui_group="Advanced Euler mesh parameters", ) cpacs_inout.add_input( @@ -169,7 +273,7 @@ xpath=GMSH_N_POWER_FIELD_XPATH, gui=True, gui_name="n power field", - gui_group="Advanced mesh parameters", + gui_group="Advanced Euler mesh parameters", ) cpacs_inout.add_input( @@ -181,7 +285,7 @@ xpath=GMSH_REFINE_FACTOR_XPATH, gui=True, gui_name="LE/TE refinement factor", - gui_group="Advanced mesh parameters", + gui_group="Advanced Euler mesh parameters", ) cpacs_inout.add_input( var_name="refine_truncated", @@ -192,7 +296,7 @@ xpath=GMSH_REFINE_TRUNCATED_XPATH, gui=True, gui_name="Refine truncated TE", - gui_group="Advanced mesh parameters", + gui_group="Advanced Euler mesh parameters", ) cpacs_inout.add_input( @@ -205,7 +309,7 @@ xpath=GMSH_AUTO_REFINE_XPATH, gui=True, gui_name="Auto refine", - gui_group="Advanced mesh parameters", + gui_group="Advanced Euler mesh parameters", ) cpacs_inout.add_input( diff --git a/ceasiompy/CPACS2GMSH/cpacs2gmsh.py b/ceasiompy/CPACS2GMSH/cpacs2gmsh.py index 648a849da..92bf67828 100644 --- a/ceasiompy/CPACS2GMSH/cpacs2gmsh.py +++ b/ceasiompy/CPACS2GMSH/cpacs2gmsh.py @@ -9,6 +9,8 @@ | Author:Tony Govoni | Creation: 2022-03-22 +| Modified by: Giacomo Benedetti, Guido Vallifuoco +| Last modification: 2024-02-01 TODO: @@ -22,6 +24,10 @@ from ceasiompy.CPACS2GMSH.func.exportbrep import export_brep from ceasiompy.CPACS2GMSH.func.generategmesh import generate_gmsh +from ceasiompy.CPACS2GMSH.func.RANS_mesh_generator import ( + generate_2d_mesh_for_pentagrow, + pentagrow_3d_mesh, +) from ceasiompy.utils.ceasiomlogger import get_logger from ceasiompy.utils.ceasiompyutils import get_results_directory from ceasiompy.utils.moduleinterfaces import ( @@ -46,6 +52,14 @@ GMSH_REFINE_TRUNCATED_XPATH, GMSH_SYMMETRY_XPATH, SU2MESH_XPATH, + GMSH_MESH_TYPE_XPATH, + GMSH_NUMBER_LAYER_XPATH, + GMSH_H_FIRST_LAYER_XPATH, + GMSH_MAX_THICKNESS_LAYER_XPATH, + GMSH_GROWTH_FACTOR_XPATH, + GMSH_GROWTH_RATIO_XPATH, + GMSH_SURFACE_MESH_SIZE_XPATH, + GMSH_FEATURE_ANGLE_XPATH, ) from cpacspy.cpacsfunctions import create_branch, get_value_or_default from cpacspy.cpacspy import CPACS @@ -77,7 +91,8 @@ def cpacs2gmsh(cpacs_path, cpacs_out_path): # Retrieve value from the GUI Setting open_gmsh = get_value_or_default(cpacs.tixi, GMSH_OPEN_GUI_XPATH, False) - farfield_factor = get_value_or_default(cpacs.tixi, GMSH_FARFIELD_FACTOR_XPATH, 6) + type_mesh = get_value_or_default(cpacs.tixi, GMSH_MESH_TYPE_XPATH, "Euler") + farfield_factor = get_value_or_default(cpacs.tixi, GMSH_FARFIELD_FACTOR_XPATH, 10) symmetry = get_value_or_default(cpacs.tixi, GMSH_SYMMETRY_XPATH, False) farfield_size_factor = get_value_or_default(cpacs.tixi, GMSH_MESH_SIZE_FARFIELD_XPATH, 17) n_power_factor = get_value_or_default(cpacs.tixi, GMSH_N_POWER_FACTOR_XPATH, 2) @@ -95,34 +110,72 @@ def cpacs2gmsh(cpacs_path, cpacs_out_path): auto_refine = get_value_or_default(cpacs.tixi, GMSH_AUTO_REFINE_XPATH, True) intake_percent = get_value_or_default(cpacs.tixi, GMSH_INTAKE_PERCENT_XPATH, 20) exhaust_percent = get_value_or_default(cpacs.tixi, GMSH_EXHAUST_PERCENT_XPATH, 20) + n_layer = get_value_or_default(cpacs.tixi, GMSH_NUMBER_LAYER_XPATH, 20) + h_first_layer = get_value_or_default(cpacs.tixi, GMSH_H_FIRST_LAYER_XPATH, 3) + max_layer_thickness = get_value_or_default(cpacs.tixi, GMSH_MAX_THICKNESS_LAYER_XPATH, 10) + growth_factor = get_value_or_default(cpacs.tixi, GMSH_GROWTH_FACTOR_XPATH, 1.4) + growth_ratio = get_value_or_default(cpacs.tixi, GMSH_GROWTH_RATIO_XPATH, 1.2) + min_max_mesh_factor = get_value_or_default(cpacs.tixi, GMSH_SURFACE_MESH_SIZE_XPATH, 5) + feature_angle = get_value_or_default(cpacs.tixi, GMSH_FEATURE_ANGLE_XPATH, 40) # Run mesh generation - export_brep(cpacs, brep_dir, (intake_percent, exhaust_percent)) - mesh_path, _ = generate_gmsh( - cpacs, - cpacs_path, - brep_dir, - results_dir, - open_gmsh=open_gmsh, - farfield_factor=farfield_factor, - symmetry=symmetry, - farfield_size_factor=farfield_size_factor, - n_power_factor=n_power_factor, - n_power_field=n_power_field, - fuselage_mesh_size_factor=fuselage_mesh_size_factor, - wing_mesh_size_factor=wing_mesh_size_factor, - mesh_size_engines=mesh_size_engines, - mesh_size_propellers=mesh_size_propellers, - refine_factor=refine_factor, - refine_truncated=refine_truncated, - auto_refine=auto_refine, - testing_gmsh=False, - ) - - if mesh_path.exists(): - create_branch(cpacs.tixi, SU2MESH_XPATH) - cpacs.tixi.updateTextElement(SU2MESH_XPATH, str(mesh_path)) - log.info("SU2 Mesh has been correctly generated.") + if type_mesh == "Euler": + export_brep(cpacs, brep_dir, (intake_percent, exhaust_percent)) + mesh_path, _ = generate_gmsh( + cpacs, + cpacs_path, + brep_dir, + results_dir, + open_gmsh=open_gmsh, + farfield_factor=farfield_factor, + symmetry=symmetry, + farfield_size_factor=farfield_size_factor, + n_power_factor=n_power_factor, + n_power_field=n_power_field, + fuselage_mesh_size_factor=fuselage_mesh_size_factor, + wing_mesh_size_factor=wing_mesh_size_factor, + mesh_size_engines=mesh_size_engines, + mesh_size_propellers=mesh_size_propellers, + refine_factor=refine_factor, + refine_truncated=refine_truncated, + auto_refine=auto_refine, + testing_gmsh=False, + ) + if mesh_path.exists(): + create_branch(cpacs.tixi, SU2MESH_XPATH) + cpacs.tixi.updateTextElement(SU2MESH_XPATH, str(mesh_path)) + log.info("SU2 Mesh has been correctly generated.") + + else: + export_brep(cpacs, brep_dir, (intake_percent, exhaust_percent)) + gmesh_path, fuselage_maxlen = generate_2d_mesh_for_pentagrow( + cpacs, + cpacs_path, + brep_dir, + results_dir, + open_gmsh=open_gmsh, + min_max_mesh_factor=min_max_mesh_factor, + ) + + if gmesh_path.exists(): + log.info("Mesh file exists. Proceeding to 3D mesh generation") + mesh_path = pentagrow_3d_mesh( + results_dir, + fuselage_maxlen, + farfield_factor=farfield_factor, + n_layer=n_layer, + h_first_layer=h_first_layer, + max_layer_thickness=max_layer_thickness, + growth_factor=growth_factor, + growth_ratio=growth_ratio, + feature_angle=feature_angle, + ) + if mesh_path.exists(): + create_branch(cpacs.tixi, SU2MESH_XPATH) + cpacs.tixi.updateTextElement(SU2MESH_XPATH, str(mesh_path)) + log.info("SU2 Mesh has been correctly generated.") + else: + log.error("Error in generating SU2 mesh") # Save CPACS cpacs.save_cpacs(cpacs_out_path, overwrite=True) diff --git a/ceasiompy/CPACS2GMSH/func/RANS_mesh_generator.py b/ceasiompy/CPACS2GMSH/func/RANS_mesh_generator.py new file mode 100644 index 000000000..97b564866 --- /dev/null +++ b/ceasiompy/CPACS2GMSH/func/RANS_mesh_generator.py @@ -0,0 +1,334 @@ +""" +CEASIOMpy: Conceptual Aircraft Design Software + +Developed by CFS ENGINEERING, 1015 Lausanne, Switzerland + +Use .brep files parts of an airplane to generate a fused airplane in GMSH with +the OCC kernel. Then Spherical farfield is created around the airplane and the +resulting domain is meshed using gmsh + +Python version: >=3.8 + +| Author: Guido Vallifuoco +| Creation: 2024-02-01 + +TODO: + + - It may be good to move all the function and some of the code in generategmsh() + that are related to disk actuator to another python script and import it here + + - Add mesh sizing for each aircraft part and as consequence add marker + + - Integrate other parts during fragmentation + + - Use run software function instead subprocess.call + +""" + + +# ================================================================================================= +# IMPORTS +# ================================================================================================= + +import os +import subprocess +from pathlib import Path + +import gmsh +from ceasiompy.CPACS2GMSH.func.generategmesh import ( + # duplicate_disk_actuator_surfaces, + # control_disk_actuator_normal, + # get_entities_from_volume, + ModelPart, + add_disk_actuator, + fuselage_size, + process_gmsh_log, +) +from ceasiompy.utils.ceasiomlogger import get_logger + +# from ceasiompy.utils.commonnames import ( +# ACTUATOR_DISK_OUTLET_SUFFIX, +# ENGINE_EXHAUST_SUFFIX, +# ENGINE_INTAKE_SUFFIX, +# GMSH_ENGINE_CONFIG_NAME, +# ) +from ceasiompy.utils.ceasiompyutils import get_part_type + +# from ceasiompy.utils.commonxpath import GMSH_MESH_SIZE_WINGS_XPATH +from ceasiompy.utils.configfiles import ConfigFile + +log = get_logger() + + +# ================================================================================================= +# FUNCTIONS +# ================================================================================================= + + +def generate_2d_mesh_for_pentagrow( + cpacs, cpacs_path, brep_dir, results_dir, open_gmsh, min_max_mesh_factor, symmetry=False +): + """ + Function to generate a mesh from brep files forming an airplane + Function 'generate_gmsh' is a subfunction of CPACS2GMSH which return a + mesh file useful for pentagrow. + The airplane is fused with the different brep files : fuselage, wings and + other parts are identified and fused together in order to obtain a watertight volume. + Args: + ---------- + cpacs : CPACS + CPACS object + brep_dir : Path + Path to the directory containing the brep files + results_dir : Path + Path to the directory containing the result (mesh) files + open_gmsh : bool + Open gmsh GUI after the mesh generation if set to true + symmetry : bool + If set to true, the mesh will be generated with symmetry wrt the x,z plane + mesh_size_fuselage : float + Size of the fuselage mesh + mesh_size_wings : float + Size of the wing mesh + mesh_size_engines : float + Size of the engine mesh + mesh_size_propellers : float + Size of the propeller mesh + advance_mesh : bool + If set to true, the mesh will be generated with advanced meshing options + refine_factor : float + refine factor for the mesh le and te edge + refine_truncated : bool + If set to true, the refinement can change to match the truncated te thickness + auto_refine : bool + If set to true, the mesh will be checked for quality + testing_gmsh : bool + If set to true, the gmsh sessions will not be clear and killed at the end of + the function, this allow to test the gmsh feature after the call of generate_gmsh() + ... + Returns: + ---------- + mesh_file : Path + Path to the mesh file generated by gmsh + + + """ + # Determine if rotor are present in the aircraft model + rotor_model = False + if Path(brep_dir, "config_rotors.cfg").exists(): + rotor_model = True + + if rotor_model: + log.info("Adding disk actuator") + config_file = ConfigFile(Path(brep_dir, "config_rotors.cfg")) + add_disk_actuator(brep_dir, config_file) + + # Retrieve all brep + brep_files = list(brep_dir.glob("*.brep")) + brep_files.sort() + + # initialize gmsh + gmsh.initialize() + # Stop gmsh output log in the terminal + gmsh.option.setNumber("General.Terminal", 0) + # Log complexity + gmsh.option.setNumber("General.Verbosity", 5) + + # Import each aircraft original parts / parent parts + fuselage_volume_dimtags = [] + wings_volume_dimtags = [] + enginePylons_enginePylon_volume_dimtags = [] + engine_nacelle_fanCowl_volume_dimtags = [] + engine_nacelle_coreCowl_volume_dimtags = [] + vehicles_engines_engine_volume_dimtags = [] + vehicles_rotorcraft_model_rotors_rotor_volume_dimtags = [] + + log.info(f"Importing files from {brep_dir}") + + for brep_file in brep_files: + # Import the part and create the aircraft part object + part_entities = gmsh.model.occ.importShapes(str(brep_file), highestDimOnly=False) + gmsh.model.occ.synchronize() + + # Create the aircraft part object + part_obj = ModelPart(uid=brep_file.stem) + # maybe to cut off --> + part_obj.part_type = get_part_type(cpacs.tixi, part_obj.uid) + + if part_obj.part_type == "fuselage": + fuselage_volume_dimtags.append(part_entities[0]) + model_bb = gmsh.model.get_bounding_box( + fuselage_volume_dimtags[0][0], fuselage_volume_dimtags[0][1] + ) + + elif part_obj.part_type == "wing": + wings_volume_dimtags.append(part_entities[0]) + # return wings_volume_dimtags + + elif part_obj.part_type == "enginePylons/enginePylon": + enginePylons_enginePylon_volume_dimtags.append(part_entities[0]) + # return enginePylons_enginePylon_volume_dimtags + + elif part_obj.part_type == "engine/nacelle/fanCowl": + engine_nacelle_fanCowl_volume_dimtags.append(part_entities[0]) + + elif part_obj.part_type == "engine/nacelle/coreCowl": + engine_nacelle_coreCowl_volume_dimtags.append(part_entities[0]) + + elif part_obj.part_type == "vehicles/engines/engine": + vehicles_engines_engine_volume_dimtags.append(part_entities[0]) + + elif part_obj.part_type == "vehicles/rotorcraft/model/rotors/rotor": + vehicles_rotorcraft_model_rotors_rotor_volume_dimtags.append(part_entities[0]) + else: + log.warning(f"'{brep_file}' cannot be categorized!") + return None + gmsh.model.occ.synchronize() + log.info("Start manipulation to obtain a watertight volume") + # we have to obtain a wathertight volume + gmsh.model.occ.cut(wings_volume_dimtags, fuselage_volume_dimtags, -1, True, False) + + gmsh.model.occ.synchronize() + + gmsh.model.occ.fuse(wings_volume_dimtags, fuselage_volume_dimtags, -1, True, True) + + gmsh.model.occ.synchronize() + + model_dimensions = [ + abs(model_bb[0] - model_bb[3]), + abs(model_bb[1] - model_bb[4]), + abs(model_bb[2] - model_bb[5]), + ] + + fuselage_maxlen, _ = fuselage_size(cpacs_path) + + gmsh.model.occ.translate( + [(3, 1)], + -((model_bb[0]) + (model_dimensions[0] / 2)), + -((model_bb[1]) + (model_dimensions[1] / 2)), + -((model_bb[2]) + (model_dimensions[2] / 2)), + ) + + gmsh.model.occ.synchronize() + log.info("Manipulation finished") + + aircraft_surface_dimtags = gmsh.model.get_entities(2) + len_aircraft_surface = len(aircraft_surface_dimtags) + surface = [] + + for i in range(len_aircraft_surface): + tags = aircraft_surface_dimtags[i][1] + surface.append(tags) + + gmsh.model.add_physical_group(2, surface, -1, name="aircraft_surface") + + # Mesh generation + log.info("Start of gmsh 2D surface meshing process") + + gmsh.option.setNumber("Mesh.Algorithm", 6) + gmsh.option.setNumber("Mesh.LcIntegrationPrecision", 1e-6) + mesh_size = model_dimensions[0] * float(min_max_mesh_factor) * (10**-3) + gmsh.option.set_number("Mesh.MeshSizeMin", mesh_size) + gmsh.option.set_number("Mesh.MeshSizeMax", mesh_size) + gmsh.option.setNumber("Mesh.StlOneSolidPerSurface", 2) + + gmsh.model.occ.synchronize() + gmsh.logger.start() + gmsh.model.mesh.generate(1) + gmsh.model.mesh.generate(2) + if open_gmsh: + log.info("Result of 2D surface mesh") + log.info("GMSH GUI is open, close it to continue...") + gmsh.fltk.run() + + gmsh.model.occ.synchronize() + + gmesh_path = Path(results_dir, "mesh_2d.stl") + gmsh.write(str(gmesh_path)) + + process_gmsh_log(gmsh.logger.get()) + + return gmesh_path, fuselage_maxlen + + +def pentagrow_3d_mesh( + result_dir, + fuselage_maxlen, + farfield_factor, + n_layer, + h_first_layer, + max_layer_thickness, + growth_factor, + growth_ratio, + feature_angle, +) -> None: + # create the config file for pentagrow + config_penta_path = Path(result_dir, "config.cfg") + # Variables + InputFormat = "stl" + NLayers = n_layer + FeatureAngle = feature_angle + InitialHeight = h_first_layer * (10**-5) + MaxGrowthRatio = growth_ratio + MaxLayerThickness = max_layer_thickness / 10 + FarfieldRadius = fuselage_maxlen * farfield_factor * 100 + FarfieldCenter = "0.0 0.0 0.0" + OutputFormat = "su2" + HolePosition = "0.0 0.0 0.0" + TetgenOptions = "-pq1.3VY" + TetGrowthFactor = growth_factor + HeightIterations = 8 + NormalIterations = 8 + MaxCritIterations = 128 + LaplaceIterations = 8 + + # writing to file + file = open(config_penta_path, "w") + file.write(f"InputFormat = {InputFormat}\n") + file.write(f"NLayers = {NLayers}\n") + file.write(f"FeatureAngle = {FeatureAngle}\n") + file.write(f"InitialHeight = {InitialHeight}\n") + file.write(f"MaxGrowthRatio = {MaxGrowthRatio}\n") + file.write(f"MaxLayerThickness = {MaxLayerThickness}\n") + file.write(f"FarfieldRadius = {FarfieldRadius}\n") + file.write(f"OutputFormat = {OutputFormat}\n") + file.write(f"HolePosition = {HolePosition}\n") + file.write(f"FarfieldCenter = {FarfieldCenter}\n") + file.write(f"TetgenOptions = {TetgenOptions}\n") + file.write(f"TetGrowthFactor = {TetGrowthFactor}\n") + file.write(f"HeightIterations = {HeightIterations}\n") + file.write(f"NormalIterations = {NormalIterations}\n") + file.write(f"MaxCritIterations = {MaxCritIterations}\n") + file.write(f"LaplaceIterations = {LaplaceIterations}\n") + + os.chdir("Results/GMSH") + + if os.path.exists("mesh_2d.stl"): + log.info("mesh_2d.stl exists") + else: + log.warning("mesh_2d.stl does not exist") + + if os.path.exists("config.cfg"): + log.info("config.cfg exists") + else: + log.warning("config.cfg does not exist") + + current_dir = os.getcwd() + os.chdir(current_dir) + + # command = "pentagrow mesh_2d.stl config.cfg" + command = ["pentagrow", "mesh_2d.stl", "config.cfg"] + # Specify the file path + file_path = "command.txt" + + command_str = " ".join(command) + + with open(file_path, "w") as file: + file.write(command_str) + + subprocess.call(command, cwd=current_dir, start_new_session=False) + + mesh_path = Path(result_dir, "hybrid.su2") + log.info(f"Mesh path:{mesh_path}") + + return mesh_path diff --git a/ceasiompy/CPACS2GMSH/func/mesh_sizing.py b/ceasiompy/CPACS2GMSH/func/mesh_sizing.py index d1b1ca04e..287cbc0c9 100644 --- a/ceasiompy/CPACS2GMSH/func/mesh_sizing.py +++ b/ceasiompy/CPACS2GMSH/func/mesh_sizing.py @@ -188,7 +188,7 @@ def fuselage_size(cpacs_path): fuselage_minlen = min(0.1 * fuselage_maxlen, min_radius / 2) log.info(f"Fuselage maxlen={fuselage_maxlen:.3f} m") - log.info(f"Fuselage minlen={fuselage_minlen:.3f} m") + log.info(f"Fuselage minlen={fuselage_minlen:.4f} m") return fuselage_maxlen, fuselage_minlen diff --git a/ceasiompy/SU2Run/func/su2config.py b/ceasiompy/SU2Run/func/su2config.py index 8e08de5ed..358adbc35 100644 --- a/ceasiompy/SU2Run/func/su2config.py +++ b/ceasiompy/SU2Run/func/su2config.py @@ -347,6 +347,8 @@ def generate_su2_cfd_config(cpacs_path, cpacs_out_path, wkdir): raise FileNotFoundError(f"SU2 mesh file {su2_mesh} not found") mesh_markers = get_mesh_markers(su2_mesh) + for key, value in mesh_markers.items(): + mesh_markers[key] = [str(item).replace(" ", "") for item in value] create_branch(cpacs.tixi, SU2_BC_WALL_XPATH) bc_wall_str = ";".join(mesh_markers["wall"]) diff --git a/ceasiompy/utils/ceasiompyutils.py b/ceasiompy/utils/ceasiompyutils.py index d766cbc16..35ddbacdb 100644 --- a/ceasiompy/utils/ceasiompyutils.py +++ b/ceasiompy/utils/ceasiompyutils.py @@ -125,11 +125,9 @@ def run_module(module, wkdir=Path.cwd(), iteration=0): log.info("CPACS output file: " + str(module.cpacs_out)) if module.name == "Optimisation" and iteration > 0: - log.info("Optimisation module is only run at first iteration!") else: - for file in module.module_dir.iterdir(): if file.name.endswith(".py") and not file.name.startswith("__"): python_file = file.stem @@ -330,5 +328,4 @@ def remove_file_type_in_dir(directory: Path, file_type_list: List[str]) -> None: # ================================================================================================= if __name__ == "__main__": - log.info("Nothing to execute!") diff --git a/ceasiompy/utils/commonxpath.py b/ceasiompy/utils/commonxpath.py index a297e734e..82e0f8393 100644 --- a/ceasiompy/utils/commonxpath.py +++ b/ceasiompy/utils/commonxpath.py @@ -12,6 +12,7 @@ | Author: Aidan jungo | Creation: 2021-10-21 +| Last modifiction: 2024-01-05 (Mengmeng Zhang, added M-Edge XPATHs ) TODO: @@ -77,6 +78,8 @@ OPTWKDIR_XPATH = CEASIOMPY_XPATH + "/filesPath/optimPath" SMFILE_XPATH = CEASIOMPY_XPATH + "/filesPath/SMpath" SU2MESH_XPATH = CEASIOMPY_XPATH + "/filesPath/su2Mesh" +EDGE_MESH_XPATH = CEASIOMPY_XPATH + "/filesPath/edgeMesh" + SUMOFILE_XPATH = CEASIOMPY_XPATH + "/filesPath/sumoFilePath" WKDIR_XPATH = CEASIOMPY_XPATH + "/filesPath/wkdirPath" @@ -84,6 +87,7 @@ TURBOPROP_XPATH = PROP_XPATH + "/turboprop" # SUMO +SUMO_OUTPUT_MESH_FORMAT_XPATH = MESH_XPATH + "sumoOptions/format" SUMO_REFINE_LEVEL_XPATH = MESH_XPATH + "/sumoOptions/refinementLevel" SUMO_INCLUDE_PYLON_XPATH = CEASIOMPY_XPATH + "/engine/includePylon" SUMO_INCLUDE_ENGINE_XPATH = CEASIOMPY_XPATH + "/engine/includeEngine" @@ -96,6 +100,7 @@ GMSH_FARFIELD_FACTOR_XPATH = GMSH_XPATH + "/farfield_factor" GMSH_N_POWER_FACTOR_XPATH = GMSH_XPATH + "/n_power_factor" GMSH_N_POWER_FIELD_XPATH = GMSH_XPATH + "/n_power_field" +GMSH_MESH_TYPE_XPATH = GMSH_XPATH + "/type_mesh" GMSH_MESH_SIZE_FARFIELD_XPATH = GMSH_XPATH + "/mesh_size/farfield" GMSH_MESH_SIZE_FUSELAGE_XPATH = GMSH_XPATH + "/mesh_size/fuselage/value" GMSH_MESH_SIZE_FACTOR_FUSELAGE_XPATH = GMSH_XPATH + "/mesh_size/fuselage/factor" @@ -108,6 +113,14 @@ GMSH_AUTO_REFINE_XPATH = GMSH_XPATH + "/auto_refine" GMSH_INTAKE_PERCENT_XPATH = GMSH_XPATH + "/intake_percent" GMSH_EXHAUST_PERCENT_XPATH = GMSH_XPATH + "/exhaust_percent" +GMSH_MESH_FORMAT_XPATH = GMSH_XPATH + "/type_output_penta" +GMSH_NUMBER_LAYER_XPATH = GMSH_XPATH + "/number_layer" +GMSH_H_FIRST_LAYER_XPATH = GMSH_XPATH + "/height_first_layer" +GMSH_MAX_THICKNESS_LAYER_XPATH = GMSH_XPATH + "/max_thickness_layer" +GMSH_GROWTH_FACTOR_XPATH = GMSH_XPATH + "/growth_factor" +GMSH_GROWTH_RATIO_XPATH = GMSH_XPATH + "/growth_ratio" +GMSH_SURFACE_MESH_SIZE_XPATH = GMSH_XPATH + "min_max_mesh_factor" +GMSH_FEATURE_ANGLE_XPATH = GMSH_XPATH + "/feature_angle" # SU2 SU2_XPATH = CEASIOMPY_XPATH + "/aerodynamics/su2" @@ -140,6 +153,17 @@ SU2_ACTUATOR_DISK_XPATH = SU2_XPATH + "/options/includeActuatorDisk" SU2_CONFIG_RANS_XPATH = SU2_XPATH + "/options/config_type" +# EDGE +EDGE_XPATH = CEASIOMPY_XPATH + "/aerodynamics/medge" +EDGE_AEROMAP_UID_XPATH = EDGE_XPATH + "/aeroMapUID" +EDGE_NB_CPU_XPATH = EDGE_XPATH + "/settings/nbCPU" +EDGE_SOLVER_XPATH = EDGE_XPATH + "/settings/solver" +EDGE_MAX_ITER_XPATH = EDGE_XPATH + "/settings/maxIter" +EDGE_CFL_NB_XPATH = EDGE_XPATH + "/settings/cflNumber/value" +EDGE_MG_LEVEL_XPATH = EDGE_XPATH + "/settings/multigridLevel" +EDGE_FIXED_CL_XPATH = EDGE_XPATH + "/fixedCL" +EDGE_ABOC_XPATH = EDGE_XPATH + "/boundary_condition" + # RANGE RANGE_LD_RATIO_XPATH = CEASIOMPY_XPATH + "/ranges/lDRatio"